Merge "Move setDisplayViewport to InputReader."
diff --git a/Android.bp b/Android.bp
index 93b37f2..3ecb5a9 100644
--- a/Android.bp
+++ b/Android.bp
@@ -53,6 +53,7 @@
         "core/java/android/view/DisplayAdjustments.java",
     ],
     path: "core/java",
+    visibility: ["//frameworks/base/test-mock"],
 }
 
 filegroup {
@@ -73,6 +74,14 @@
 }
 
 filegroup {
+    name: "framework-identity-sources",
+    srcs: [
+        "identity/java/**/*.java",
+    ],
+    path: "identity/java",
+}
+
+filegroup {
     name: "framework-keystore-sources",
     srcs: [
         "keystore/java/**/*.java",
@@ -209,6 +218,7 @@
         ":framework-graphics-sources",
         ":framework-jobscheduler-sources", // jobscheduler is not a module for R
         ":framework-keystore-sources",
+        ":framework-identity-sources",
         ":framework-location-sources",
         ":framework-lowpan-sources",
         ":framework-mca-effect-sources",
@@ -221,16 +231,17 @@
         ":framework-sax-sources",
         ":framework-telecomm-sources",
         ":framework-telephony-common-sources",
-        ":framework-telephony-sources",
         ":framework-wifi-annotations",
         ":framework-wifi-non-updatable-sources",
         ":PacProcessor-aidl-sources",
         ":ProxyHandler-aidl-sources",
+        ":net-utils-framework-common-srcs",
 
         // AIDL from frameworks/base/native/
         ":platform-compat-native-aidl",
 
         // AIDL sources from external directories
+        ":credstore_aidl",
         ":dumpstate_aidl",
         ":framework_native_aidl",
         ":gatekeeper_aidl",
@@ -255,6 +266,9 @@
         // etc.
         ":framework-javastream-protos",
         ":framework-statslog-gen",
+
+        // telephony annotations
+        ":framework-telephony-annotations",
     ],
 }
 
@@ -267,7 +281,9 @@
         ":framework-tethering-srcs",
         ":updatable-media-srcs",
         ":framework-mediaprovider-sources",
+        ":framework-permission-sources",
         ":framework-wifi-updatable-sources",
+        ":framework-telephony-sources",
         ":ike-srcs",
     ]
 }
@@ -281,6 +297,8 @@
     ],
 }
 
+// AIDL files under these paths are mixture of public and private ones.
+// They shouldn't be exported across module boundaries.
 java_defaults {
     name: "framework-aidl-export-defaults",
     aidl: {
@@ -289,6 +307,7 @@
             "core/java",
             "drm/java",
             "graphics/java",
+            "identity/java",
             "keystore/java",
             "location/java",
             "lowpan/java",
@@ -301,16 +320,9 @@
             "rs/java",
             "sax/java",
             "telecomm/java",
-            "telephony/java",
-            "wifi/java",
+            "wifi/aidl-export",
         ],
     },
-
-    required: [
-        // TODO: remove gps_debug and protolog.conf.json when the build system propagates "required" properly.
-        "gps_debug.conf",
-	"protolog.conf.json.gz",
-    ],
 }
 
 // Collection of classes that are generated from non-Java files that are not listed in
@@ -323,8 +335,9 @@
         "framework-protos",
         "game-driver-protos",
         "android.hidl.base-V1.0-java",
-        "android.hardware.cas-V1.1-java",
         "android.hardware.cas-V1.0-java",
+        "android.hardware.cas-V1.1-java",
+        "android.hardware.cas-V1.2-java",
         "android.hardware.contexthub-V1.0-java",
         "android.hardware.gnss-V1.0-java",
         "android.hardware.health-V1.0-java-constants",
@@ -348,7 +361,6 @@
         "android.hardware.vibrator-V1.1-java",
         "android.hardware.vibrator-V1.2-java",
         "android.hardware.vibrator-V1.3-java",
-        "android.hardware.wifi-V1.0-java-constants",
         "devicepolicyprotosnano",
 
         "com.android.sysprop.apex",
@@ -382,6 +394,7 @@
         "updatable_media_stubs",
         "framework_mediaprovider_stubs",
         "framework-tethering",
+        "framework-telephony-stubs",
     ],
 
     jarjar_rules: ":framework-jarjar-rules",
@@ -399,6 +412,12 @@
         "view-inspector-annotation-processor",
         "staledataclass-annotation-processor",
     ],
+
+    required: [
+        // TODO: remove gps_debug and protolog.conf.json when the build system propagates "required" properly.
+        "gps_debug.conf",
+        "protolog.conf.json.gz",
+    ],
 }
 
 filegroup {
@@ -409,6 +428,7 @@
 filegroup {
     name: "libincident_aidl",
     srcs: [
+        "core/java/android/os/IIncidentDumpCallback.aidl",
         "core/java/android/os/IIncidentManager.aidl",
         "core/java/android/os/IIncidentReportStatusListener.aidl",
     ],
@@ -416,6 +436,13 @@
 }
 
 filegroup {
+    name: "graphicsstats_proto",
+    srcs: [
+        "libs/hwui/protos/graphicsstats.proto",
+    ],
+}
+
+filegroup {
     name: "libvibrator_aidl",
     srcs: [
         "core/java/android/os/IExternalVibrationController.aidl",
@@ -430,8 +457,9 @@
     srcs: [":framework-non-updatable-sources"],
     libs: [
         "framework-appsearch-stubs",
-        // TODO(b/146167933): Use framework-statsd-stubs
-        "framework-statsd",
+        "framework-sdkextensions-stubs-systemapi",
+        "framework-statsd", // TODO(b/146167933): Use framework-statsd-stubs
+        "framework-permission-stubs",
         "framework-wifi-stubs",
         "ike-stubs",
     ],
@@ -442,8 +470,6 @@
         "libcore-platform-compat-config",
         "services-platform-compat-config",
         "media-provider-platform-compat-config",
-        "services-devicepolicy-platform-compat-config",
-        "services-core-platform-compat-config",
     ],
     static_libs: [
         // If MimeMap ever becomes its own APEX, then this dependency would need to be removed
@@ -459,8 +485,9 @@
         "//frameworks/base/apex/appsearch/framework",
         "//frameworks/base/apex/blobstore/framework",
         "//frameworks/base/apex/jobscheduler/framework",
+        "//frameworks/base/apex/permission/framework",
         "//frameworks/base/apex/statsd/service",
-        "//frameworks/base/wifi",
+        "//frameworks/base/telephony",
         "//frameworks/opt/net/wifi/service",
     ],
 }
@@ -483,13 +510,17 @@
         "updatable_media_stubs",
         "framework_mediaprovider_stubs",
         "framework-appsearch", // TODO(b/146218515): should be framework-appsearch-stubs
+        "framework-permission-stubs",
         "framework-sdkextensions-stubs-systemapi",
         // TODO(b/146167933): Use framework-statsd-stubs instead.
         "framework-statsd",
-        // TODO(b/140299412): should be framework-wifi-stubs
-        "framework-wifi",
+        "framework-wifi-stubs",
         "ike-stubs",
-        // TODO(jiyong): add more stubs for APEXes here
+        // TODO(b/147200698): should be the stub of framework-tethering
+        "framework-tethering",
+        // TODO (b/147688669) should be framework-telephony-stubs
+        "framework-telephony",
+        // TODO(jiyong): add stubs for APEXes here
     ],
     sdk_version: "core_platform",
     apex_available: ["//apex_available:platform"],
@@ -500,13 +531,16 @@
     defaults: ["framework-defaults"],
     srcs: [":framework-all-sources"],
     installable: false,
-    static_libs: ["exoplayer2-core"],
+    static_libs: [
+        "exoplayer2-core",
+        "android.hardware.wifi-V1.0-java-constants",
+    ],
+    libs: ["icing-java-proto-lite"],
     apex_available: ["//apex_available:platform"],
 }
 
 java_library {
     name: "framework-annotation-proc",
-    defaults: ["framework-aidl-export-defaults"],
     srcs: [":framework-all-sources"],
     libs: [
         "app-compat-annotations",
@@ -518,7 +552,8 @@
         "compat-changeid-annotation-processor",
     ],
     static_libs: [
-        "exoplayer2-core"
+        "exoplayer2-core",
+        "android.hardware.wifi-V1.0-java-constants",
     ]
 }
 
@@ -564,7 +599,7 @@
 genrule {
     name: "framework-statslog-gen",
     tools: ["stats-log-api-gen"],
-    cmd: "$(location stats-log-api-gen) --java $(out)",
+    cmd: "$(location stats-log-api-gen) --java $(out) --worksource",
     out: ["android/util/StatsLogInternal.java"],
 }
 
@@ -589,6 +624,7 @@
         "&& $(location soong_zip) -jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)",
 
     srcs: [
+        ":ipconnectivity-proto-src",
         "core/proto/**/*.proto",
         "libs/incident/**/*.proto",
     ],
@@ -598,17 +634,22 @@
 filegroup {
     name: "framework-annotations",
     srcs: [
+        "core/java/android/annotation/CallbackExecutor.java",
+        "core/java/android/annotation/CheckResult.java",
         "core/java/android/annotation/IntDef.java",
         "core/java/android/annotation/IntRange.java",
         "core/java/android/annotation/NonNull.java",
         "core/java/android/annotation/Nullable.java",
         "core/java/android/annotation/RequiresPermission.java",
         "core/java/android/annotation/SdkConstant.java",
+        "core/java/android/annotation/StringDef.java",
         "core/java/android/annotation/SystemApi.java",
+        "core/java/android/annotation/SystemService.java",
         "core/java/android/annotation/TestApi.java",
         "core/java/android/annotation/UnsupportedAppUsage.java",
         "core/java/com/android/internal/annotations/GuardedBy.java",
         "core/java/com/android/internal/annotations/VisibleForTesting.java",
+        "core/java/com/android/internal/annotations/Immutable.java",
     ],
 }
 
@@ -619,6 +660,19 @@
 }
 
 filegroup {
+    name: "framework-ike-shared-srcs",
+    visibility: ["//frameworks/opt/net/ike"],
+    srcs: [
+        "core/java/android/annotation/StringDef.java",
+        "core/java/android/net/annotations/PolicyDirection.java",
+        "core/java/com/android/internal/util/IState.java",
+        "core/java/com/android/internal/util/State.java",
+        "core/java/com/android/internal/util/StateMachine.java",
+        "telephony/java/android/telephony/Annotation.java",
+    ],
+}
+
+filegroup {
     name: "framework-networkstack-shared-srcs",
     srcs: [
         // TODO: remove these annotations as soon as we can use andoid.support.annotations.*
@@ -651,6 +705,7 @@
         "core/java/com/android/internal/util/Preconditions.java",
         "core/java/com/android/internal/util/State.java",
         "core/java/com/android/internal/util/StateMachine.java",
+        "core/java/com/android/internal/util/TrafficStatsConstants.java",
         "core/java/android/net/shared/Inet4AddressUtils.java",
     ],
 }
@@ -659,7 +714,10 @@
     name: "framework-tethering-annotations",
     srcs: [
         "core/java/android/annotation/NonNull.java",
+        "core/java/android/annotation/Nullable.java",
+        "core/java/android/annotation/RequiresPermission.java",
         "core/java/android/annotation/SystemApi.java",
+        "core/java/android/annotation/TestApi.java",
     ],
 }
 // Build ext.jar
@@ -684,6 +742,7 @@
 java_library_host {
     name: "platformprotos",
     srcs: [
+        ":ipconnectivity-proto-src",
         "cmds/am/proto/instrumentation_data.proto",
         "cmds/statsd/src/**/*.proto",
         "core/proto/**/*.proto",
@@ -712,6 +771,7 @@
     ],
     sdk_version: "9",
     srcs: [
+        ":ipconnectivity-proto-src",
         "core/proto/**/*.proto",
         "libs/incident/proto/android/os/**/*.proto",
     ],
@@ -726,6 +786,7 @@
     },
 
     srcs: [
+        ":ipconnectivity-proto-src",
         "core/proto/**/*.proto",
         "libs/incident/proto/android/os/**/*.proto",
     ],
@@ -756,6 +817,7 @@
     ],
 
     srcs: [
+        ":ipconnectivity-proto-src",
         "core/proto/**/*.proto",
     ],
 }
@@ -1085,9 +1147,10 @@
 }
 
 // Avoid including Parcelable classes as we don't want to have two copies of
-// Parcelable cross the process.
+// Parcelable cross the libraries. This is used by telephony-common (frameworks/opt/telephony)
+// and TeleService app (packages/services/Telephony).
 filegroup {
-    name: "framework-telephony-stack-shared-srcs",
+    name: "framework-telephony-common-shared-srcs",
     srcs: [
         "core/java/android/os/BasicShellCommandHandler.java",
         "core/java/android/os/RegistrantList.java",
@@ -1096,6 +1159,7 @@
         "core/java/android/util/TimeUtils.java",
         "core/java/com/android/internal/os/SomeArgs.java",
         "core/java/com/android/internal/util/AsyncChannel.java",
+        "core/java/com/android/internal/util/AsyncService.java",
         "core/java/com/android/internal/util/BitwiseInputStream.java",
         "core/java/com/android/internal/util/FastXmlSerializer.java",
         "core/java/com/android/internal/util/HexDump.java",
@@ -1109,6 +1173,21 @@
 }
 
 // Avoid including Parcelable classes as we don't want to have two copies of
+// Parcelable cross the process. This is used by framework-telephony (frameworks/base/telephony).
+filegroup {
+    name: "framework-telephony-shared-srcs",
+    srcs: [
+        "core/java/android/util/RecurrenceRule.java",
+        "core/java/com/android/internal/os/SomeArgs.java",
+        "core/java/com/android/internal/util/BitwiseInputStream.java",
+        "core/java/com/android/internal/util/BitwiseOutputStream.java",
+        "core/java/com/android/internal/util/HexDump.java",
+        "core/java/com/android/internal/util/IndentingPrintWriter.java",
+        "core/java/com/android/internal/util/Preconditions.java",
+    ],
+}
+
+// Avoid including Parcelable classes as we don't want to have two copies of
 // Parcelable cross the process.
 filegroup {
     name: "framework-cellbroadcast-shared-srcs",
@@ -1132,12 +1211,37 @@
     ],
 }
 
+// utility classes statically linked into framework-wifi and dynamically linked
+// into wifi-service
+java_library {
+    name: "framework-wifi-util-lib",
+    sdk_version: "core_current",
+    srcs: [
+        "core/java/android/content/pm/BaseParceledListSlice.java",
+        "core/java/android/content/pm/ParceledListSlice.java",
+        "core/java/android/net/shared/Inet4AddressUtils.java",
+        "core/java/android/os/HandlerExecutor.java",
+        "core/java/com/android/internal/util/AsyncChannel.java",
+        "core/java/com/android/internal/util/AsyncService.java",
+        "core/java/com/android/internal/util/Protocol.java",
+        "core/java/com/android/internal/util/Preconditions.java",
+        "telephony/java/android/telephony/Annotation.java",
+        ":net-utils-framework-wifi-common-srcs",
+    ],
+    libs: [
+        "framework-annotations-lib",
+        "unsupportedappusage",
+        "android_system_stubs_current",
+    ],
+    visibility: ["//frameworks/base/wifi"],
+}
+
+// utility classes statically linked into wifi-service
 filegroup {
     name: "framework-wifi-service-shared-srcs",
     srcs: [
-        ":framework-annotations",
         "core/java/android/net/InterfaceConfiguration.java",
-        "core/java/android/os/HandlerExecutor.java",
+        "core/java/android/os/BasicShellCommandHandler.java",
         "core/java/android/util/BackupUtils.java",
         "core/java/android/util/LocalLog.java",
         "core/java/android/util/Rational.java",
@@ -1145,12 +1249,11 @@
         "core/java/com/android/internal/util/HexDump.java",
         "core/java/com/android/internal/util/IState.java",
         "core/java/com/android/internal/util/MessageUtils.java",
-        "core/java/com/android/internal/util/Preconditions.java",
         "core/java/com/android/internal/util/State.java",
         "core/java/com/android/internal/util/StateMachine.java",
         "core/java/com/android/internal/util/WakeupMessage.java",
-        "core/java/com/android/internal/util/XmlUtils.java",
     ],
+    visibility: ["//frameworks/opt/net/wifi/service"],
 }
 
 // TODO(b/145644363): move this to under StubLibraries.bp or ApiDocs.bp
@@ -1169,3 +1272,85 @@
     "StubLibraries.bp",
     "ApiDocs.bp",
 ]
+
+// TODO(b/147699819): move to frameworks/base/telephony/ folder
+droidstubs {
+    name: "framework-telephony-stubs-srcs",
+    srcs: [
+        ":framework-telephony-sources",
+        ":framework_native_aidl",
+        ":framework-javastream-protos",
+    ],
+    aidl: {
+        local_include_dirs: [
+            "core/java",
+            "telecomm/java"
+        ],
+    },
+    libs: [
+        "framework-annotations-lib",
+        "android.hardware.radio-V1.5-java",
+    ],
+    defaults: ["framework-module-stubs-defaults-systemapi"],
+    filter_packages: ["android.telephony"],
+    sdk_version: "system_current",
+}
+
+java_library {
+    name: "framework-telephony-stubs",
+    srcs: [":framework-telephony-stubs-srcs"],
+    // TODO(b/147699819): move public aidls to a separate folder and potentially remove
+    // below aidl exports.
+    aidl: {
+        export_include_dirs: ["telephony/java"],
+    },
+    sdk_version: "core_current",
+    libs: ["android_system_stubs_current"],
+}
+
+java_library {
+    name: "framework-telephony",
+    srcs: [
+        ":framework-telephony-sources",
+        ":framework-telephony-shared-srcs",
+    ],
+    // TODO: change to framework-system-stub to build against system APIs.
+    libs: [
+        "framework-minus-apex",
+        "unsupportedappusage",
+        "ike-stubs",
+    ],
+    static_libs: [
+        "libphonenumber-platform",
+        "app-compat-annotations",
+    ],
+    sdk_version: "core_platform",
+    aidl: {
+        export_include_dirs: ["telephony/java"],
+        include_dirs: [
+            "frameworks/native/aidl/binder",
+            "frameworks/native/aidl/gui",
+        ]
+    },
+    jarjar_rules: ":framework-telephony-jarjar-rules",
+    dxflags: [
+        "--core-library",
+        "--multi-dex",
+    ],
+    // This is to break the dependency from boot jars.
+    dex_preopt: {
+        enabled: false,
+    },
+    installable: true,
+}
+
+filegroup {
+    // TODO (b/147690217): move to frameworks/base/telephony/common.
+    name: "framework-telephony-annotations",
+    srcs: ["telephony/java/android/telephony/Annotation.java"],
+}
+
+filegroup {
+    name: "framework-telephony-jarjar-rules",
+    srcs: ["telephony/framework-telephony-jarjar-rules.txt"],
+}
diff --git a/ApiDocs.bp b/ApiDocs.bp
index e373db6..c40004c 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -121,8 +121,10 @@
 
 doc_defaults {
     name: "framework-docs-default",
-    libs: framework_docs_only_libs +
-        ["stub-annotations"],
+    libs: framework_docs_only_libs + [
+        "stub-annotations",
+        "unsupportedappusage",
+    ],
     html_dirs: [
         "docs/html",
     ],
diff --git a/StubLibraries.bp b/StubLibraries.bp
index d195047..6927f44 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -41,10 +41,9 @@
 ]
 
 stubs_defaults {
-    name: "metalava-api-stubs-default",
+    name: "metalava-non-updatable-api-stubs-default",
     srcs: [
         ":framework-non-updatable-sources",
-        ":framework-updatable-sources",
         "core/java/**/*.logtags",
         ":opt-telephony-srcs",
         ":opt-net-voip-srcs",
@@ -52,6 +51,10 @@
         ":core_public_api_files",
         ":ike-api-srcs",
     ],
+    // TODO(b/147699819): remove below aidl includes.
+    aidl: {
+        local_include_dirs: ["telephony/java"],
+    },
     libs: ["framework-internal-utils"],
     installable: false,
     annotations_enabled: true,
@@ -64,14 +67,23 @@
         "sdk-dir",
         "api-versions-jars-dir",
     ],
-    sdk_version: "core_platform",
     filter_packages: packages_to_document,
 }
 
+stubs_defaults {
+    name: "metalava-api-stubs-default",
+    defaults: ["metalava-non-updatable-api-stubs-default"],
+    srcs: [":framework-updatable-sources"],
+    sdk_version: "core_platform",
+}
+
 /////////////////////////////////////////////////////////////////////
 // *-api-stubs-docs modules providing source files for the stub libraries
 /////////////////////////////////////////////////////////////////////
 
+// api-stubs-docs, system-api-stubs-docs, and test-api-stubs-docs have APIs
+// from the non-updatable part of the platform as well as from the updatable
+// modules
 droidstubs {
     name: "api-stubs-docs",
     defaults: ["metalava-api-stubs-default"],
@@ -112,7 +124,10 @@
     arg_files: [
         "core/res/AndroidManifest.xml",
     ],
-    args: metalava_framework_docs_args + " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\)",
+    args: metalava_framework_docs_args +
+        " --show-annotation android.annotation.SystemApi\\(" +
+        "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+        "process=android.annotation.SystemApi.Process.ALL\\)",
     check_api: {
         current: {
             api_file: "api/system-current.txt",
@@ -155,6 +170,111 @@
 }
 
 /////////////////////////////////////////////////////////////////////
+// Following droidstubs modules are for extra APIs for modules.
+// The framework currently have two more API surfaces for modules:
+// @SystemApi(client=MODULE_APPS) and @SystemApi(client=MODULE_LIBRARIES)
+/////////////////////////////////////////////////////////////////////
+
+// TODO(b/146727827) remove the *-api modules when we can teach metalava
+// about the relationship among the API surfaces. Currently, these modules are only to generate
+// the API signature files and ensure that the APIs evolve in a backwards compatible manner.
+// They however are NOT used for building the API stub.
+droidstubs {
+    name: "module-app-api",
+    defaults: ["metalava-api-stubs-default"],
+    libs: ["framework-all"],
+    arg_files: ["core/res/AndroidManifest.xml"],
+    args: metalava_framework_docs_args +
+        " --show-annotation android.annotation.SystemApi\\(" +
+        "client=android.annotation.SystemApi.Client.MODULE_APPS," +
+        "process=android.annotation.SystemApi.Process.ALL\\)",
+    check_api: {
+        current: {
+            api_file: "api/module-app-current.txt",
+            removed_api_file: "api/module-app-removed.txt",
+        },
+        // TODO(b/147559833) enable the compatibility check against the last release API
+        // and the API lint
+        //last_released: {
+        //    api_file: ":last-released-module-app-api",
+        //    removed_api_file: "api/module-app-removed.txt",
+        //    baseline_file: ":module-app-api-incompatibilities-with-last-released"
+        //},
+        //api_lint: {
+        //    enabled: true,
+        //    new_since: ":last-released-module-app-api",
+        //    baseline_file: "api/module-app-lint-baseline.txt",
+        //},
+    },
+    //jdiff_enabled: true,
+}
+
+droidstubs {
+    name: "module-lib-api",
+    defaults: ["metalava-api-stubs-default"],
+    libs: ["framework-all"],
+    arg_files: ["core/res/AndroidManifest.xml"],
+    args: metalava_framework_docs_args +
+        " --show-annotation android.annotation.SystemApi\\(" +
+        "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES," +
+        "process=android.annotation.SystemApi.Process.ALL\\)",
+    check_api: {
+        current: {
+            api_file: "api/module-lib-current.txt",
+            removed_api_file: "api/module-lib-removed.txt",
+        },
+        // TODO(b/147559833) enable the compatibility check against the last release API
+        // and the API lint
+        //last_released: {
+        //    api_file: ":last-released-module-lib-api",
+        //    removed_api_file: "api/module-lib-removed.txt",
+        //    baseline_file: ":module-lib-api-incompatibilities-with-last-released"
+        //},
+        //api_lint: {
+        //    enabled: true,
+        //    new_since: ":last-released-module-lib-api",
+        //    baseline_file: "api/module-lib-lint-baseline.txt",
+        //},
+    },
+    //jdiff_enabled: true,
+}
+
+// The following two droidstubs modules generate source files for the API stub libraries for
+// modules. Note that they not only include their own APIs but also other APIs that have
+// narrower scope. For example, module-lib-api-stubs-docs includes all @SystemApis not just
+// the ones with 'client=MODULE_LIBRARIES'.
+droidstubs {
+    name: "module-app-api-stubs-docs",
+    defaults: ["metalava-api-stubs-default"],
+    libs: ["framework-all"],
+    arg_files: ["core/res/AndroidManifest.xml"],
+    args: metalava_framework_docs_args +
+        " --show-annotation android.annotation.SystemApi\\(" +
+        "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+        "process=android.annotation.SystemApi.Process.ALL\\)" +
+        " --show-annotation android.annotation.SystemApi\\(" +
+        "client=android.annotation.SystemApi.Client.MODULE_APPS," +
+        "process=android.annotation.SystemApi.Process.ALL\\)",
+}
+
+droidstubs {
+    name: "module-lib-api-stubs-docs",
+    defaults: ["metalava-api-stubs-default"],
+    libs: ["framework-all"],
+    arg_files: ["core/res/AndroidManifest.xml"],
+    args: metalava_framework_docs_args +
+        " --show-annotation android.annotation.SystemApi\\(" +
+        "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+        "process=android.annotation.SystemApi.Process.ALL\\)" +
+        " --show-annotation android.annotation.SystemApi\\(" +
+        "client=android.annotation.SystemApi.Client.MODULE_APPS," +
+        "process=android.annotation.SystemApi.Process.ALL\\)" +
+        " --show-annotation android.annotation.SystemApi\\(" +
+        "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES," +
+        "process=android.annotation.SystemApi.Process.ALL\\)",
+}
+
+/////////////////////////////////////////////////////////////////////
 // android_*_stubs_current modules are the stubs libraries compiled
 // from *-api-stubs-docs
 /////////////////////////////////////////////////////////////////////
@@ -169,7 +289,6 @@
     java_resources: [
         ":notices-for-framework-stubs",
     ],
-    sdk_version: "core_current",
     system_modules: "none",
     java_version: "1.8",
     compile_dex: true,
@@ -187,6 +306,7 @@
         "private-stub-annotations-jar",
     ],
     defaults: ["framework-stubs-default"],
+    sdk_version: "core_current",
 }
 
 java_library_static {
@@ -201,6 +321,7 @@
         "private-stub-annotations-jar",
     ],
     defaults: ["framework-stubs-default"],
+    sdk_version: "core_current",
 }
 
 java_library_static {
@@ -215,6 +336,37 @@
         "private-stub-annotations-jar",
     ],
     defaults: ["framework-stubs-default"],
+    sdk_version: "core_current",
+}
+
+java_library_static {
+    name: "framework_module_app_stubs_current",
+    srcs: [
+        ":module-app-api-stubs-docs",
+    ],
+    libs: [
+        "stub-annotations",
+        "framework-all",
+    ],
+    static_libs: [
+        "private-stub-annotations-jar",
+    ],
+    defaults: ["framework-stubs-default"],
+}
+
+java_library_static {
+    name: "framework_module_lib_stubs_current",
+    srcs: [
+        ":module-lib-api-stubs-docs",
+    ],
+    libs: [
+        "stub-annotations",
+        "framework-all",
+    ],
+    static_libs: [
+        "private-stub-annotations-jar",
+    ],
+    defaults: ["framework-stubs-default"],
 }
 
 /////////////////////////////////////////////////////////////////////
diff --git a/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java
index f32bf9a..c62aad6 100644
--- a/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java
@@ -20,6 +20,7 @@
 
 import android.app.Activity;
 import android.content.Context;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.RemoteException;
 import android.perftests.utils.BenchmarkState;
@@ -149,7 +150,7 @@
                         mViewVisibility.getAsInt(), mFlags, mFrameNumber, mOutFrame,
                         mOutContentInsets, mOutVisibleInsets, mOutStableInsets,
                         mOutBackDropFrame, mOutDisplayCutout, mOutMergedConfiguration,
-                        mOutSurfaceControl, mOutInsetsState);
+                        mOutSurfaceControl, mOutInsetsState, new Point());
             }
         }
     }
diff --git a/apex/Android.bp b/apex/Android.bp
index 56f7db2..abebfa3 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -37,6 +37,36 @@
 
 stubs_defaults {
     name: "framework-module-stubs-defaults-systemapi",
-    args: mainline_stubs_args + " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\) ",
+    args: mainline_stubs_args +
+    " --show-annotation android.annotation.SystemApi\\(" +
+    "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+    "process=android.annotation.SystemApi.Process.ALL\\) ",
+    installable: false,
+}
+
+stubs_defaults {
+    name: "framework-module-stubs-defaults-module_apps_api",
+    args: mainline_stubs_args +
+    " --show-annotation android.annotation.SystemApi\\(" +
+    "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+    "process=android.annotation.SystemApi.Process.ALL\\) " +
+    " --show-annotation android.annotation.SystemApi\\(" +
+    "client=android.annotation.SystemApi.Client.MODULE_APPS," +
+    "process=android.annotation.SystemApi.Process.ALL\\) ",
+    installable: false,
+}
+
+stubs_defaults {
+    name: "framework-module-stubs-defaults-module_libs_api",
+    args: mainline_stubs_args +
+    " --show-annotation android.annotation.SystemApi\\(" +
+    "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+    "process=android.annotation.SystemApi.Process.ALL\\) " +
+    " --show-annotation android.annotation.SystemApi\\(" +
+    "client=android.annotation.SystemApi.Client.MODULE_APPS," +
+    "process=android.annotation.SystemApi.Process.ALL\\) " +
+    " --show-annotation android.annotation.SystemApi\\(" +
+    "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES," +
+    "process=android.annotation.SystemApi.Process.ALL\\) ",
     installable: false,
 }
diff --git a/apex/appsearch/apex_manifest.json b/apex/appsearch/apex_manifest.json
index 273b867..39a2d38 100644
--- a/apex/appsearch/apex_manifest.json
+++ b/apex/appsearch/apex_manifest.json
@@ -1,4 +1,4 @@
 {
   "name": "com.android.appsearch",
-  "version": 1
+  "version": 300000000
 }
diff --git a/apex/appsearch/framework/Android.bp b/apex/appsearch/framework/Android.bp
index 3dc5a2c..24309d7 100644
--- a/apex/appsearch/framework/Android.bp
+++ b/apex/appsearch/framework/Android.bp
@@ -13,22 +13,31 @@
 // limitations under the License.
 
 filegroup {
-  name: "framework-appsearch-sources",
-  srcs: [
-    "java/**/*.java",
-    "java/**/*.aidl",
-  ],
-  path: "java",
+    name: "framework-appsearch-sources",
+    srcs: [
+        "java/**/*.java",
+        "java/**/*.aidl",
+    ],
+    path: "java",
 }
 
 java_library {
-  name: "framework-appsearch",
-  installable: true,
-  sdk_version: "core_platform", // TODO(b/146218515) should be core_current
-  srcs: [":framework-appsearch-sources"],
-  libs: [
-    "framework-minus-apex",  // TODO(b/146218515) should be framework-system-stubs
-  ],
+    name: "framework-appsearch",
+    installable: true,
+    sdk_version: "core_platform", // TODO(b/146218515) should be core_current
+    srcs: [":framework-appsearch-sources"],
+    hostdex: true, // for hiddenapi check
+    libs: [
+        "framework-minus-apex",  // TODO(b/146218515) should be framework-system-stubs
+    ],
+    static_libs: ["icing-java-proto-lite"],
+    visibility: [
+        // TODO(b/146218515) remove this when framework is built with the stub of appsearch
+        "//frameworks/base",
+        "//frameworks/base/apex/appsearch:__subpackages__",
+    ],
+    permitted_packages: ["android.app.appsearch"],
+    apex_available: ["com.android.appsearch"],
 }
 
 metalava_appsearch_docs_args =
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearch.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearch.java
new file mode 100644
index 0000000..8bf13ee
--- /dev/null
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearch.java
@@ -0,0 +1,854 @@
+/*
+ * 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.appsearch;
+
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.appsearch.AppSearchSchema.PropertyConfig;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+
+import com.google.android.icing.proto.DocumentProto;
+import com.google.android.icing.proto.PropertyProto;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Objects;
+
+/**
+ * Collection of all AppSearch Document Types.
+ *
+ * @hide
+ */
+// TODO(b/143789408) Spilt this class to make all subclasses to their own file.
+public final class AppSearch {
+
+    private AppSearch() {}
+    /**
+     * Represents a document unit.
+     *
+     * <p>Documents are constructed via {@link Document.Builder}.
+     *
+     * @hide
+     */
+    // TODO(b/143789408) set TTL for document in mProtoBuilder
+    // TODO(b/144518768) add visibility field if the stakeholders are comfortable with a no-op
+    //  opt-in for this release.
+    public static class Document {
+        private static final String TAG = "AppSearch.Document";
+
+        /**
+         * 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;
+
+        /**
+         * The maximum {@link String#length} of a {@link String} field. Will reject the request if
+         * {@link String}s longer than this.
+         */
+        private static final int MAX_STRING_LENGTH = 20_000;
+
+        /**
+         * Contains {@link Document} basic information (uri, schemaType etc) and properties ordered
+         * by keys.
+         */
+        @NonNull
+        private final DocumentProto mProto;
+
+        /** Contains all properties in {@link #mProto} to support get properties via keys. */
+        @NonNull
+        private final Bundle mPropertyBundle;
+
+        /**
+         * Create a new {@link Document}.
+         * @param proto Contains {@link Document} basic information (uri, schemaType etc) and
+         *               properties ordered by keys.
+         * @param propertyBundle Contains all properties in {@link #mProto} to support get
+         *                        properties via keys.
+         */
+        private Document(@NonNull DocumentProto proto, @NonNull Bundle propertyBundle) {
+            this.mProto = proto;
+            this.mPropertyBundle = propertyBundle;
+        }
+
+        /**
+         * Create a new {@link Document} from an existing instance.
+         *
+         * <p>This method should be only used by constructor of a subclass.
+         */
+        // TODO(b/143789408) add constructor take DocumentProto to create a document.
+        protected Document(@NonNull Document document) {
+            this(document.mProto, document.mPropertyBundle);
+        }
+
+        /** @hide */
+        Document(@NonNull DocumentProto documentProto) {
+            this(documentProto, new Bundle());
+            for (int i = 0; i < documentProto.getPropertiesCount(); i++) {
+                PropertyProto property = documentProto.getProperties(i);
+                String name = property.getName();
+                if (property.getStringValuesCount() > 0) {
+                    String[] values = new String[property.getStringValuesCount()];
+                    for (int j = 0; j < values.length; j++) {
+                        values[j] = property.getStringValues(j);
+                    }
+                    mPropertyBundle.putStringArray(name, values);
+                } else if (property.getInt64ValuesCount() > 0) {
+                    long[] values = new long[property.getInt64ValuesCount()];
+                    for (int j = 0; j < values.length; j++) {
+                        values[j] = property.getInt64Values(j);
+                    }
+                    mPropertyBundle.putLongArray(property.getName(), values);
+                } else if (property.getDoubleValuesCount() > 0) {
+                    double[] values = new double[property.getDoubleValuesCount()];
+                    for (int j = 0; j < values.length; j++) {
+                        values[j] = property.getDoubleValues(j);
+                    }
+                    mPropertyBundle.putDoubleArray(property.getName(), values);
+                } else if (property.getBooleanValuesCount() > 0) {
+                    boolean[] values = new boolean[property.getBooleanValuesCount()];
+                    for (int j = 0; j < values.length; j++) {
+                        values[j] = property.getBooleanValues(j);
+                    }
+                    mPropertyBundle.putBooleanArray(property.getName(), values);
+                } else if (property.getBytesValuesCount() > 0) {
+                    byte[][] values = new byte[property.getBytesValuesCount()][];
+                    for (int j = 0; j < values.length; j++) {
+                        values[j] = property.getBytesValues(j).toByteArray();
+                    }
+                    mPropertyBundle.putObject(name, values);
+                } else if (property.getDocumentValuesCount() > 0) {
+                    Document[] values = new Document[property.getDocumentValuesCount()];
+                    for (int j = 0; j < values.length; j++) {
+                        values[j] = new Document(property.getDocumentValues(j));
+                    }
+                    mPropertyBundle.putObject(name, values);
+                } else {
+                    throw new IllegalStateException("Unknown type of value: " + name);
+                }
+            }
+        }
+
+        /**
+         * Creates a new {@link Document.Builder}.
+         *
+         * @param uri The uri of {@link Document}.
+         * @param schemaType The schema type of the {@link Document}. The passed-in
+         *     {@code schemaType} must be defined using {@link AppSearchManager#setSchema} prior to
+         *     inserting a document of this {@code schemaType} into the AppSearch index using
+         *     {@link AppSearchManager#put}. Otherwise, the document will be rejected by
+         *     {@link AppSearchManager#put}.
+         * @hide
+         */
+        @NonNull
+        public static Builder newBuilder(@NonNull String uri, @NonNull String schemaType) {
+            return new Builder(uri, schemaType);
+        }
+
+        /**
+         * Get the {@link DocumentProto} of the {@link Document}.
+         *
+         * <p>The {@link DocumentProto} contains {@link Document}'s basic information and all
+         *    properties ordered by keys.
+         * @hide
+         */
+        @NonNull
+        @VisibleForTesting
+        public DocumentProto getProto() {
+            return mProto;
+        }
+
+        /**
+         * Get the uri of the {@link Document}.
+         *
+         * @hide
+         */
+        @NonNull
+        public String getUri() {
+            return mProto.getUri();
+        }
+
+        /**
+         * Get the schema type of the {@link Document}.
+         * @hide
+         */
+        @NonNull
+        public String getSchemaType() {
+            return mProto.getSchema();
+        }
+
+        /**
+         * Get the creation timestamp in milliseconds of the {@link Document}. Value will be in the
+         * {@link System#currentTimeMillis()} time base.
+         *
+         * @hide
+         */
+        @CurrentTimeMillisLong
+        public long getCreationTimestampMillis() {
+            return mProto.getCreationTimestampMs();
+        }
+
+        /**
+         * Returns the score of the {@link Document}.
+         *
+         * <p>The score is a query-independent measure of the document's quality, relative to other
+         * {@link Document}s of the same type.
+         *
+         * <p>The default value is 0.
+         *
+         * @hide
+         */
+        public int getScore() {
+            return mProto.getScore();
+        }
+
+        /**
+         * Retrieve 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.
+         * @hide
+         */
+        @Nullable
+        public String getPropertyString(@NonNull String key) {
+            String[] propertyArray = getPropertyStringArray(key);
+            if (ArrayUtils.isEmpty(propertyArray)) {
+                return null;
+            }
+            warnIfSinglePropertyTooLong("String", key, propertyArray.length);
+            return propertyArray[0];
+        }
+
+        /**
+         * Retrieve a {@link Long} value by key.
+         *
+         * @param key The key to look for.
+         * @return The first {@link Long} associated with the given key or {@code null} if there
+         *     is no such key or the value is of a different type.
+         * @hide
+         */
+        @Nullable
+        public Long getPropertyLong(@NonNull String key) {
+            long[] propertyArray = getPropertyLongArray(key);
+            if (ArrayUtils.isEmpty(propertyArray)) {
+                return null;
+            }
+            warnIfSinglePropertyTooLong("Long", key, propertyArray.length);
+            return propertyArray[0];
+        }
+
+        /**
+         * Retrieve a {@link Double} value by key.
+         *
+         * @param key The key to look for.
+         * @return The first {@link Double} associated with the given key or {@code null} if there
+         *     is no such key or the value is of a different type.
+         * @hide
+         */
+        @Nullable
+        public Double getPropertyDouble(@NonNull String key) {
+            double[] propertyArray = getPropertyDoubleArray(key);
+            // TODO(tytytyww): Add support double array to ArraysUtils.isEmpty().
+            if (propertyArray == null || propertyArray.length == 0) {
+                return null;
+            }
+            warnIfSinglePropertyTooLong("Double", key, propertyArray.length);
+            return propertyArray[0];
+        }
+
+        /**
+         * Retrieve a {@link Boolean} value by key.
+         *
+         * @param key The key to look for.
+         * @return The first {@link Boolean} associated with the given key or {@code null} if there
+         *     is no such key or the value is of a different type.
+         * @hide
+         */
+        @Nullable
+        public Boolean getPropertyBoolean(@NonNull String key) {
+            boolean[] propertyArray = getPropertyBooleanArray(key);
+            if (ArrayUtils.isEmpty(propertyArray)) {
+                return null;
+            }
+            warnIfSinglePropertyTooLong("Boolean", key, propertyArray.length);
+            return propertyArray[0];
+        }
+
+        /** Prints a warning to logcat if the given propertyLength is greater than 1. */
+        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().");
+            }
+        }
+
+        /**
+         * Retrieve 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.
+         * @hide
+         */
+        @Nullable
+        public String[] getPropertyStringArray(@NonNull String key) {
+            return getAndCastPropertyArray(key, String[].class);
+        }
+
+        /**
+         * Retrieve a repeated {@code long} property by key.
+         *
+         * @param key The key to look for.
+         * @return The {@code long[]} associated with the given key, or {@code null} if no value is
+         *     set or the value is of a different type.
+         * @hide
+         */
+        @Nullable
+        public long[] getPropertyLongArray(@NonNull String key) {
+            return getAndCastPropertyArray(key, long[].class);
+        }
+
+        /**
+         * Retrieve 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.
+         * @hide
+         */
+        @Nullable
+        public double[] getPropertyDoubleArray(@NonNull String key) {
+            return getAndCastPropertyArray(key, double[].class);
+        }
+
+        /**
+         * Retrieve 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.
+         * @hide
+         */
+        @Nullable
+        public boolean[] getPropertyBooleanArray(@NonNull String key) {
+            return getAndCastPropertyArray(key, boolean[].class);
+        }
+
+        /**
+         * 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) {
+            Object value = mPropertyBundle.get(key);
+            if (value == null) {
+                return null;
+            }
+            try {
+                return tClass.cast(value);
+            } catch (ClassCastException e) {
+                Log.w(TAG, "Error casting to requested type for key \"" + key + "\"", e);
+                return null;
+            }
+        }
+
+        @Override
+        public boolean equals(@Nullable Object other) {
+            // Check only proto's equality is sufficient here since all properties in
+            // mPropertyBundle are ordered by keys and stored in proto.
+            if (this == other) {
+                return true;
+            }
+            if (!(other instanceof Document)) {
+                return false;
+            }
+            Document otherDocument = (Document) other;
+            return this.mProto.equals(otherDocument.mProto);
+        }
+
+        @Override
+        public int hashCode() {
+            // Hash only proto is sufficient here since all properties in mPropertyBundle are
+            // ordered by keys and stored in proto.
+            return mProto.hashCode();
+        }
+
+        @Override
+        public String toString() {
+            return mProto.toString();
+        }
+
+        /**
+         * The builder class for {@link Document}.
+         *
+         * @param <BuilderType> Type of subclass who extend this.
+         * @hide
+         */
+        public static class Builder<BuilderType extends Builder> {
+
+            private final Bundle mPropertyBundle = new Bundle();
+            private final DocumentProto.Builder mProtoBuilder = DocumentProto.newBuilder();
+            private final BuilderType mBuilderTypeInstance;
+
+            /**
+             * Create a new {@link Document.Builder}.
+             *
+             * @param uri The uri of {@link Document}.
+             * @param schemaType The schema type of the {@link Document}. The passed-in
+             *     {@code schemaType} must be defined using {@link AppSearchManager#setSchema} prior
+             *     to inserting a document of this {@code schemaType} into the AppSearch index using
+             *     {@link AppSearchManager#put}. Otherwise, the document will be rejected by
+             *     {@link AppSearchManager#put}.
+             * @hide
+             */
+            protected Builder(@NonNull String uri, @NonNull String schemaType) {
+                mBuilderTypeInstance = (BuilderType) this;
+                mProtoBuilder.setUri(uri).setSchema(schemaType);
+                 // Set current timestamp for creation timestamp by default.
+                setCreationTimestampMillis(System.currentTimeMillis());
+            }
+
+            /**
+             * Set the score of the {@link Document}.
+             *
+             * <p>The score is a query-independent measure of the document's quality, relative to
+             * other {@link Document}s of the same type.
+             *
+             * @throws IllegalArgumentException If the provided value is negative.
+             * @hide
+             */
+            @NonNull
+            public BuilderType setScore(@IntRange(from = 0, to = Integer.MAX_VALUE) int score) {
+                if (score < 0) {
+                    throw new IllegalArgumentException("Document score cannot be negative");
+                }
+                mProtoBuilder.setScore(score);
+                return mBuilderTypeInstance;
+            }
+
+            /**
+             * Set the creation timestamp in milliseconds of the {@link Document}. Should be set
+             * using a value obtained from the {@link System#currentTimeMillis()} time base.
+             *
+             * @hide
+             */
+            @NonNull
+            public BuilderType setCreationTimestampMillis(
+                    @CurrentTimeMillisLong long creationTimestampMillis) {
+                mProtoBuilder.setCreationTimestampMs(creationTimestampMillis);
+                return mBuilderTypeInstance;
+            }
+
+            /**
+             * 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.
+             * @hide
+             */
+            @NonNull
+            public BuilderType setProperty(@NonNull String key, @NonNull String... values) {
+                putInBundle(mPropertyBundle, key, values);
+                return mBuilderTypeInstance;
+            }
+
+            /**
+             * Sets one or multiple {@code boolean} values for a property, replacing its previous
+             * values.
+             *
+             * @param key The key associated with the {@code values}.
+             * @param values The {@code boolean} values of the schema.org property.
+             * @hide
+             */
+            @NonNull
+            public BuilderType setProperty(@NonNull String key, @NonNull boolean... values) {
+                putInBundle(mPropertyBundle, key, values);
+                return mBuilderTypeInstance;
+            }
+
+            /**
+             * 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 schema.org property.
+             * @hide
+             */
+            @NonNull
+            public BuilderType setProperty(@NonNull String key, @NonNull long... values) {
+                putInBundle(mPropertyBundle, key, values);
+                return mBuilderTypeInstance;
+            }
+
+            /**
+             * 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 schema.org property.
+             * @hide
+             */
+            @NonNull
+            public BuilderType setProperty(@NonNull String key, @NonNull double... values) {
+                putInBundle(mPropertyBundle, key, values);
+                return mBuilderTypeInstance;
+            }
+
+            private static void putInBundle(
+                    @NonNull Bundle bundle, @NonNull String key, @NonNull String... values)
+                    throws IllegalArgumentException {
+                Objects.requireNonNull(key);
+                Objects.requireNonNull(values);
+                validateRepeatedPropertyLength(key, values.length);
+                for (int i = 0; i < values.length; i++) {
+                    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 + ".");
+                    }
+                }
+                bundle.putStringArray(key, values);
+            }
+
+            private static void putInBundle(
+                    @NonNull Bundle bundle, @NonNull String key, @NonNull boolean... values) {
+                Objects.requireNonNull(key);
+                Objects.requireNonNull(values);
+                validateRepeatedPropertyLength(key, values.length);
+                bundle.putBooleanArray(key, values);
+            }
+
+            private static void putInBundle(
+                    @NonNull Bundle bundle, @NonNull String key, @NonNull double... values) {
+                Objects.requireNonNull(key);
+                Objects.requireNonNull(values);
+                validateRepeatedPropertyLength(key, values.length);
+                bundle.putDoubleArray(key, values);
+            }
+
+            private static void putInBundle(
+                    @NonNull Bundle bundle, @NonNull String key, @NonNull long... values) {
+                Objects.requireNonNull(key);
+                Objects.requireNonNull(values);
+                validateRepeatedPropertyLength(key, values.length);
+                bundle.putLongArray(key, values);
+            }
+
+            private static void validateRepeatedPropertyLength(@NonNull String key, int length) {
+                if (length == 0) {
+                    throw new IllegalArgumentException("The input array is empty.");
+                } else if (length > MAX_REPEATED_PROPERTY_LENGTH) {
+                    throw new IllegalArgumentException(
+                            "Repeated property \"" + key + "\" has length " + length
+                                    + ", which exceeds the limit of "
+                                    + MAX_REPEATED_PROPERTY_LENGTH);
+                }
+            }
+
+            /**
+             * Builds the {@link Document} object.
+             * @hide
+             */
+            public Document build() {
+                // Build proto by sorting the keys in propertyBundle to exclude the influence of
+                // order. Therefore documents will generate same proto as long as the contents are
+                // same. Note that the order of repeated fields is still preserved.
+                ArrayList<String> keys = new ArrayList<>(mPropertyBundle.keySet());
+                Collections.sort(keys);
+                for (String key : keys) {
+                    Object values = mPropertyBundle.get(key);
+                    PropertyProto.Builder propertyProto = PropertyProto.newBuilder().setName(key);
+                    if (values instanceof boolean[]) {
+                        for (boolean value : (boolean[]) values) {
+                            propertyProto.addBooleanValues(value);
+                        }
+                    } else if (values instanceof long[]) {
+                        for (long value : (long[]) values) {
+                            propertyProto.addInt64Values(value);
+                        }
+                    } else if (values instanceof double[]) {
+                        for (double value : (double[]) values) {
+                            propertyProto.addDoubleValues(value);
+                        }
+                    } else if (values instanceof String[]) {
+                        for (String value : (String[]) values) {
+                            propertyProto.addStringValues(value);
+                        }
+                    } else {
+                        throw new IllegalStateException(
+                                "Property \"" + key + "\" has unsupported value type \""
+                                        + values.getClass().getSimpleName() + "\"");
+                    }
+                    mProtoBuilder.addProperties(propertyProto);
+                }
+                return new Document(mProtoBuilder.build(), mPropertyBundle);
+            }
+        }
+    }
+
+    /**
+     * Encapsulates a {@link Document} that represent an email.
+     *
+     * <p>This class is a higher level implement of {@link Document}.
+     *
+     * <p>This class will eventually migrate to Jetpack, where it will become public API.
+     *
+     * @hide
+     */
+    public static class Email extends Document {
+        private static final String KEY_FROM = "from";
+        private static final String KEY_TO = "to";
+        private static final String KEY_CC = "cc";
+        private static final String KEY_BCC = "bcc";
+        private static final String KEY_SUBJECT = "subject";
+        private static final String KEY_BODY = "body";
+
+        /** The name of the schema type for {@link Email} documents.*/
+        public static final String SCHEMA_TYPE = "builtin:Email";
+
+        public static final AppSearchSchema SCHEMA = AppSearchSchema.newBuilder(SCHEMA_TYPE)
+                .addProperty(AppSearchSchema.newPropertyBuilder(KEY_FROM)
+                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .build()
+
+                ).addProperty(AppSearchSchema.newPropertyBuilder(KEY_TO)
+                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                        .setCardinality(PropertyConfig.CARDINALITY_REPEATED)
+                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .build()
+
+                ).addProperty(AppSearchSchema.newPropertyBuilder(KEY_CC)
+                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                        .setCardinality(PropertyConfig.CARDINALITY_REPEATED)
+                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .build()
+
+                ).addProperty(AppSearchSchema.newPropertyBuilder(KEY_BCC)
+                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                        .setCardinality(PropertyConfig.CARDINALITY_REPEATED)
+                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .build()
+
+                ).addProperty(AppSearchSchema.newPropertyBuilder(KEY_SUBJECT)
+                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .build()
+
+                ).addProperty(AppSearchSchema.newPropertyBuilder(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 Email.Builder}.
+         *
+         * @param uri The uri of {@link Email}.
+         */
+        public static Builder newBuilder(@NonNull String uri) {
+            return new Builder(uri);
+        }
+
+        /**
+         * Creates a new {@link Email} from the contents of an existing {@link Document}.
+         *
+         * @param document The {@link Document} containing the email content.
+         */
+        public Email(@NonNull Document document) {
+            super(document);
+        }
+
+        /**
+         * Get the from address of {@link Email}.
+         *
+         * @return Returns the subject of {@link Email} or {@code null} if it's not been set yet.
+         * @hide
+         */
+        @Nullable
+        public String getFrom() {
+            return getPropertyString(KEY_FROM);
+        }
+
+        /**
+         * Get the destination addresses of {@link Email}.
+         *
+         * @return Returns the destination addresses of {@link Email} or {@code null} if it's not
+         *         been set yet.
+         * @hide
+         */
+        @Nullable
+        public String[] getTo() {
+            return getPropertyStringArray(KEY_TO);
+        }
+
+        /**
+         * Get the CC list of {@link Email}.
+         *
+         * @return Returns the CC list of {@link Email} or {@code null} if it's not been set yet.
+         * @hide
+         */
+        @Nullable
+        public String[] getCc() {
+            return getPropertyStringArray(KEY_CC);
+        }
+
+        /**
+         * Get the BCC list of {@link Email}.
+         *
+         * @return Returns the BCC list of {@link Email} or {@code null} if it's not been set yet.
+         * @hide
+         */
+        @Nullable
+        public String[] getBcc() {
+            return getPropertyStringArray(KEY_BCC);
+        }
+
+        /**
+         * Get the subject of {@link Email}.
+         *
+         * @return Returns the value subject of {@link Email} or {@code null} if it's not been set
+         * yet.
+         * @hide
+         */
+        @Nullable
+        public String getSubject() {
+            return getPropertyString(KEY_SUBJECT);
+        }
+
+        /**
+         * Get the body of {@link Email}.
+         *
+         * @return Returns the body of {@link Email} or {@code null} if it's not been set yet.
+         * @hide
+         */
+        @Nullable
+        public String getBody() {
+            return getPropertyString(KEY_BODY);
+        }
+
+        /**
+         * The builder class for {@link Email}.
+         * @hide
+         */
+        public static class Builder extends Document.Builder<Email.Builder> {
+
+            /**
+             * Create a new {@link Email.Builder}
+             * @param uri The Uri of the Email.
+             * @hide
+             */
+            private Builder(@NonNull String uri) {
+                super(uri, SCHEMA_TYPE);
+            }
+
+            /**
+             * Set the from address of {@link Email}
+             * @hide
+             */
+            @NonNull
+            public Email.Builder setFrom(@NonNull String from) {
+                setProperty(KEY_FROM, from);
+                return this;
+            }
+
+            /**
+             * Set the destination address of {@link Email}
+             * @hide
+             */
+            @NonNull
+            public Email.Builder setTo(@NonNull String... to) {
+                setProperty(KEY_TO, to);
+                return this;
+            }
+
+            /**
+             * Set the CC list of {@link Email}
+             * @hide
+             */
+            @NonNull
+            public Email.Builder setCc(@NonNull String... cc) {
+                setProperty(KEY_CC, cc);
+                return this;
+            }
+
+            /**
+             * Set the BCC list of {@link Email}
+             * @hide
+             */
+            @NonNull
+            public Email.Builder setBcc(@NonNull String... bcc) {
+                setProperty(KEY_BCC, bcc);
+                return this;
+            }
+
+            /**
+             * Set the subject of {@link Email}
+             * @hide
+             */
+            @NonNull
+            public Email.Builder setSubject(@NonNull String subject) {
+                setProperty(KEY_SUBJECT, subject);
+                return this;
+            }
+
+            /**
+             * Set the body of {@link Email}
+             * @hide
+             */
+            @NonNull
+            public Email.Builder setBody(@NonNull String body) {
+                setProperty(KEY_BODY, body);
+                return this;
+            }
+
+            /**
+             * Builds the {@link Email} object.
+             *
+             * @hide
+             */
+            @NonNull
+            @Override
+            public Email build() {
+                return new Email(super.build());
+            }
+        }
+    }
+}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java
new file mode 100644
index 0000000..773db93
--- /dev/null
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java
@@ -0,0 +1,150 @@
+/*
+ * 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.appsearch;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArrayMap;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Provides access to multiple results from a batch operation accepting multiple inputs.
+ *
+ * @param <KeyType> The type of the keys for {@link #getResults} and {@link #getFailures}.
+ * @param <ValueType> The type of result objects associated with the keys.
+ * @hide
+ */
+public class AppSearchBatchResult<KeyType, ValueType> implements Parcelable {
+    @NonNull private final Map<KeyType, ValueType> mResults;
+    @NonNull private final Map<KeyType, Throwable> mFailures;
+
+    private AppSearchBatchResult(
+            @NonNull Map<KeyType, ValueType> results, @NonNull Map<KeyType, Throwable> failures) {
+        mResults = results;
+        mFailures = failures;
+    }
+
+    private AppSearchBatchResult(@NonNull Parcel in) {
+        mResults = Collections.unmodifiableMap(in.readHashMap(/*loader=*/ null));
+        mFailures = Collections.unmodifiableMap(in.readHashMap(/*loader=*/ null));
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeMap(mResults);
+        dest.writeMap(mFailures);
+    }
+
+    /** Returns {@code true} if this {@link AppSearchBatchResult} has no failures. */
+    public boolean isSuccess() {
+        return mFailures.isEmpty();
+    }
+
+    /**
+     * Returns a {@link Map} of all successful keys mapped to the results they produced.
+     *
+     * <p>The values of the {@link Map} may be {@code null}.
+     */
+    @NonNull
+    public Map<KeyType, ValueType> getResults() {
+        return mResults;
+    }
+
+    /**
+     * Returns a {@link Map} of all failed keys mapped to a {@link Throwable} representing the cause
+     * of failure.
+     *
+     * <p>The values of the {@link Map} may be {@code null}.
+     */
+    @NonNull
+    public Map<KeyType, Throwable> getFailures() {
+        return mFailures;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<AppSearchBatchResult> CREATOR =
+            new Creator<AppSearchBatchResult>() {
+        @NonNull
+        @Override
+        public AppSearchBatchResult createFromParcel(@NonNull Parcel in) {
+            return new AppSearchBatchResult(in);
+        }
+
+        @NonNull
+        @Override
+        public AppSearchBatchResult[] newArray(int size) {
+            return new AppSearchBatchResult[size];
+        }
+    };
+
+    /**
+     * Creates a new {@link Builder} for this {@link AppSearchBatchResult}.
+     * @hide
+     */
+    @NonNull
+    public static <KeyType, ValueType> Builder<KeyType, ValueType> newBuilder() {
+        return new Builder<>();
+    }
+
+    /**
+     * Builder for {@link AppSearchBatchResult} objects.
+     *
+     * @param <KeyType> The type of keys.
+     * @param <ValueType> The type of result objects associated with the keys.
+     * @hide
+     */
+    public static final class Builder<KeyType, ValueType> {
+        @NonNull private final Map<KeyType, ValueType> mResults = new ArrayMap<>();
+        @NonNull private final Map<KeyType, Throwable> mFailures = new ArrayMap<>();
+
+        private Builder() {}
+
+        /**
+         * Registers that the {@code key} was processed successfully and associates it with
+         * {@code value}. Any previous mapping for a key, whether success or failure, is deleted.
+         */
+        public Builder setSuccess(@NonNull KeyType key, @Nullable ValueType value) {
+            mResults.put(key, value);
+            mFailures.remove(key);
+            return this;
+        }
+
+        /**
+         * Registers that the {@code key} failed and associates it with {@code throwable}. Any
+         * previous mapping for a key, whether success or failure, is deleted.
+         */
+        public Builder setFailure(@NonNull KeyType key, @Nullable Throwable throwable) {
+            mFailures.put(key, throwable);
+            mResults.remove(key);
+            return this;
+        }
+
+        /** Builds an {@link AppSearchBatchResult} from the contents of this {@link Builder}. */
+        @NonNull
+        public AppSearchBatchResult<KeyType, ValueType> build() {
+            return new AppSearchBatchResult<>(mResults, mFailures);
+        }
+    }
+}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
index a8ee35c..15c3368 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -15,21 +15,279 @@
  */
 package android.app.appsearch;
 
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
 import android.annotation.SystemService;
+import android.app.appsearch.AppSearch.Document;
 import android.content.Context;
+import android.os.RemoteException;
+
+import com.android.internal.infra.AndroidFuture;
+
+import com.google.android.icing.proto.SchemaProto;
+import com.google.android.icing.proto.SearchResultProto;
+import com.google.android.icing.proto.SearchSpecProto;
+import com.google.android.icing.proto.StatusProto;
+import com.google.android.icing.protobuf.InvalidProtocolBufferException;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
 
 /**
- * TODO(b/142567528): add comments when implement this class
+ * This class provides access to the centralized AppSearch index maintained by the system.
+ *
+ * <p>Apps can index structured text documents with AppSearch, which can then be retrieved through
+ * the query API.
+ *
  * @hide
  */
 @SystemService(Context.APP_SEARCH_SERVICE)
 public class AppSearchManager {
     private final IAppSearchManager mService;
+
+    /** @hide */
+    public AppSearchManager(@NonNull IAppSearchManager service) {
+        mService = service;
+    }
+
     /**
-     * TODO(b/142567528): add comments when implement this class
+     * Sets the schema being used by documents provided to the {@link #putDocuments} method.
+     *
+     * <p>The schema provided here is compared to the stored copy of the schema previously supplied
+     * to {@link #setSchema}, if any, to determine how to treat existing documents. The following
+     * types of schema modifications are always safe and are made without deleting any existing
+     * documents:
+     * <ul>
+     *     <li>Addition of new types
+     *     <li>Addition of new
+     *         {@link android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL
+     *             OPTIONAL} or
+     *         {@link android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_REPEATED
+     *             REPEATED} properties to a type
+     *     <li>Changing the cardinality of a data type to be less restrictive (e.g. changing an
+     *         {@link android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL
+     *             OPTIONAL} property into a
+     *         {@link android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_REPEATED
+     *             REPEATED} property.
+     * </ul>
+     *
+     * <p>The following types of schema changes are not backwards-compatible. Supplying a schema
+     * with such changes will result in this call throwing an {@link IllegalSchemaException}
+     * describing the incompatibility, and the previously set schema will remain active:
+     * <ul>
+     *     <li>Removal of an existing type
+     *     <li>Removal of a property from a type
+     *     <li>Changing the data type ({@code boolean}, {@code long}, etc.) of an existing property
+     *     <li>For properties of {@code Document} type, changing the schema type of
+     *         {@code Document Documents} of that property
+     *     <li>Changing the cardinality of a data type to be more restrictive (e.g. changing an
+     *         {@link android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL
+     *             OPTIONAL} property into a
+     *         {@link android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED
+     *             REQUIRED} property).
+     *     <li>Adding a
+     *         {@link android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED
+     *             REQUIRED} property.
+     * </ul>
+     *
+     * <p>If you need to make non-backwards-compatible changes as described above, instead use the
+     * {@link #setSchema(List, boolean)} method with the {@code forceOverride} parameter set to
+     * {@code true}.
+     *
+     * <p>It is a no-op to set the same schema as has been previously set; this is handled
+     * efficiently.
+     *
+     * @param schemas The schema configs for the types used by the calling app.
+     * @throws IllegalSchemaException If the provided schema is invalid, or is incompatible with the
+     *     previous schema.
+     *
      * @hide
      */
-    public AppSearchManager(IAppSearchManager service) {
-        mService = service;
+    public void setSchema(@NonNull AppSearchSchema... schemas) {
+        setSchema(Arrays.asList(schemas), /*forceOverride=*/false);
+    }
+
+    /**
+     * Sets the schema being used by documents provided to the {@link #putDocuments} method.
+     *
+     * <p>This method is similar to {@link #setSchema(AppSearchSchema...)}, except for the
+     * {@code forceOverride} parameter. If a backwards-incompatible schema is specified but the
+     * {@code forceOverride} parameter is set to {@code true}, instead of throwing an
+     * {@link IllegalSchemaException}, all documents which are not compatible with the new schema
+     * will be deleted and the incompatible schema will be applied.
+     *
+     * @param schemas The schema configs for the types used by the calling app.
+     * @param forceOverride Whether to force the new schema to be applied even if there are
+     *     incompatible changes versus the previously set schema. Documents which are incompatible
+     *     with the new schema will be deleted.
+     * @throws IllegalSchemaException If the provided schema is invalid, or is incompatible with the
+     *     previous schema and the {@code forceOverride} parameter is set to {@code false}.
+     *
+     * @hide
+     */
+    public void setSchema(@NonNull List<AppSearchSchema> schemas, boolean forceOverride) {
+        // Prepare the merged schema for transmission.
+        SchemaProto.Builder schemaProtoBuilder = SchemaProto.newBuilder();
+        for (AppSearchSchema schema : schemas) {
+            schemaProtoBuilder.addTypes(schema.getProto());
+        }
+
+        // Serialize and send the schema.
+        // TODO: This should use com.android.internal.infra.RemoteStream or another mechanism to
+        //  avoid binder limits.
+        byte[] schemaBytes = schemaProtoBuilder.build().toByteArray();
+        AndroidFuture<Void> future = new AndroidFuture<>();
+        try {
+            mService.setSchema(schemaBytes, forceOverride, future);
+        } catch (RemoteException e) {
+            future.completeExceptionally(e);
+        }
+        getFutureOrThrow(future);
+    }
+
+    /**
+     * Index {@link android.app.appsearch.AppSearch.Document Documents} into AppSearch.
+     *
+     * <p>You should not call this method directly; instead, use the
+     * {@code AppSearch#putDocuments()} API provided by JetPack.
+     *
+     * <p>Each {@link AppSearch.Document Document's} {@code schemaType} field must be set to the
+     * name of a schema type previously registered via the {@link #setSchema} method.
+     *
+     * @param documents {@link Document Documents} that need to be indexed.
+     * @return An {@link AppSearchBatchResult} mapping the document URIs to {@link Void} if they
+     *     were successfully indexed, or a {@link Throwable} describing the failure if they could
+     *     not be indexed.
+     * @hide
+     */
+    public AppSearchBatchResult<String, Void> putDocuments(@NonNull List<Document> documents) {
+        // TODO(b/146386470): Transmit these documents as a RemoteStream instead of sending them in
+        // one big list.
+        List<byte[]> documentsBytes = new ArrayList<>(documents.size());
+        for (Document document : documents) {
+            documentsBytes.add(document.getProto().toByteArray());
+        }
+        AndroidFuture<AppSearchBatchResult> future = new AndroidFuture<>();
+        try {
+            mService.putDocuments(documentsBytes, future);
+        } catch (RemoteException e) {
+            future.completeExceptionally(e);
+        }
+        return getFutureOrThrow(future);
+    }
+
+    /**
+     * This method searches for documents based on a given query string. It also accepts
+     * specifications regarding how to search and format the results.
+     *
+     *<p>Currently we support following features in the raw query format:
+     * <ul>
+     *     <li>AND
+     *     <p>AND joins (e.g. “match documents that have both the terms ‘dog’ and
+     *     ‘cat’”).
+     *     Example: hello world matches documents that have both ‘hello’ and ‘world’
+     *     <li>OR
+     *     <p>OR joins (e.g. “match documents that have either the term ‘dog’ or
+     *     ‘cat’”).
+     *     Example: dog OR puppy
+     *     <li>Exclusion
+     *     <p>Exclude a term (e.g. “match documents that do
+     *     not have the term ‘dog’”).
+     *     Example: -dog excludes the term ‘dog’
+     *     <li>Grouping terms
+     *     <p>Allow for conceptual grouping of subqueries to enable hierarchical structures (e.g.
+     *     “match documents that have either ‘dog’ or ‘puppy’, and either ‘cat’ or ‘kitten’”).
+     *     Example: (dog puppy) (cat kitten) two one group containing two terms.
+     *     <li>Property restricts
+     *     <p> Specifies which properties of a document to specifically match terms in (e.g.
+     *     “match documents where the ‘subject’ property contains ‘important’”).
+     *     Example: subject:important matches documents with the term ‘important’ in the
+     *     ‘subject’ property
+     *     <li>Schema type restricts
+     *     <p>This is similar to property restricts, but allows for restricts on top-level document
+     *     fields, such as schema_type. Clients should be able to limit their query to documents of
+     *     a certain schema_type (e.g. “match documents that are of the ‘Email’ schema_type”).
+     *     Example: { schema_type_filters: “Email”, “Video”,query: “dog” } will match documents
+     *     that contain the query term ‘dog’ and are of either the ‘Email’ schema type or the
+     *     ‘Video’ schema type.
+     * </ul>
+     *
+     * <p> It is strongly recommended to use Jetpack APIs.
+     *
+     * @param queryExpression Query String to search.
+     * @param searchSpec Spec for setting filters, raw query etc.
+     * @param executor Executor on which to invoke the callback.
+     * @param callback  Callback to receive errors resulting from the query operation. If the
+     *                 operation succeeds, the callback will be invoked with {@code null}.
+     * @hide
+     */
+    @NonNull
+    public void query(
+            @NonNull String queryExpression,
+            @NonNull SearchSpec searchSpec,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull BiConsumer<? super SearchResults, ? super Throwable> callback) {
+        AndroidFuture<byte[]> future = new AndroidFuture<>();
+        future.whenCompleteAsync((searchResultBytes, err) -> {
+            if (err != null) {
+                callback.accept(null, err);
+                return;
+            }
+
+            if (searchResultBytes != null) {
+                SearchResultProto searchResultProto;
+                try {
+                    searchResultProto = SearchResultProto.parseFrom(searchResultBytes);
+                } catch (InvalidProtocolBufferException e) {
+                    callback.accept(null, e);
+                    return;
+                }
+                if (searchResultProto.getStatus().getCode() != StatusProto.Code.OK) {
+                    // TODO(sidchhabra): Add better exception handling.
+                    callback.accept(
+                            null,
+                            new RuntimeException(searchResultProto.getStatus().getMessage()));
+                    return;
+                }
+                SearchResults searchResults = new SearchResults(searchResultProto);
+                callback.accept(searchResults, null);
+                return;
+            }
+
+            // Nothing was supplied in the future at all
+            callback.accept(
+                    null, new IllegalStateException("Unknown failure occurred while querying"));
+        }, executor);
+
+        try {
+            SearchSpecProto searchSpecProto = searchSpec.getSearchSpecProto();
+            searchSpecProto = searchSpecProto.toBuilder().setQuery(queryExpression).build();
+            mService.query(searchSpecProto.toByteArray(),
+                    searchSpec.getResultSpecProto().toByteArray(),
+                    searchSpec.getScoringSpecProto().toByteArray(), future);
+        } catch (RemoteException e) {
+            future.completeExceptionally(e);
+        }
+    }
+
+    private static <T> T getFutureOrThrow(@NonNull AndroidFuture<T> future) {
+        try {
+            return future.get();
+        } catch (Throwable e) {
+            if (e instanceof ExecutionException) {
+                e = e.getCause();
+            }
+            if (e instanceof RuntimeException) {
+                throw (RuntimeException) e;
+            }
+            if (e instanceof Error) {
+                throw (Error) e;
+            }
+            throw new RuntimeException(e);
+        }
     }
 }
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java
new file mode 100644
index 0000000..1d54dc4
--- /dev/null
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java
@@ -0,0 +1,369 @@
+/*
+ * 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.app.appsearch;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.android.icing.proto.PropertyConfigProto;
+import com.google.android.icing.proto.SchemaTypeConfigProto;
+import com.google.android.icing.proto.TermMatchType;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Set;
+
+/**
+ * The AppSearch Schema for a particular type of document.
+ *
+ * <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).
+ *
+ * @hide
+ */
+public final class AppSearchSchema {
+    private final SchemaTypeConfigProto mProto;
+
+    private AppSearchSchema(SchemaTypeConfigProto proto) {
+        mProto = proto;
+    }
+
+    /** Creates a new {@link AppSearchSchema.Builder}. */
+    @NonNull
+    public static AppSearchSchema.Builder newBuilder(@NonNull String typeName) {
+        return new AppSearchSchema.Builder(typeName);
+    }
+
+    /** Creates a new {@link PropertyConfig.Builder}. */
+    @NonNull
+    public static PropertyConfig.Builder newPropertyBuilder(@NonNull String propertyName) {
+        return new PropertyConfig.Builder(propertyName);
+    }
+
+    /**
+     * Returns the {@link SchemaTypeConfigProto} populated by this builder.
+     * @hide
+     */
+    @NonNull
+    @VisibleForTesting
+    public SchemaTypeConfigProto getProto() {
+        return mProto;
+    }
+
+    @Override
+    public String toString() {
+        return mProto.toString();
+    }
+
+    /** Builder for {@link AppSearchSchema objects}. */
+    public static final class Builder {
+        private final SchemaTypeConfigProto.Builder mProtoBuilder =
+                SchemaTypeConfigProto.newBuilder();
+
+        private Builder(@NonNull String typeName) {
+            mProtoBuilder.setSchemaType(typeName);
+        }
+
+        /** Adds a property to the given type. */
+        @NonNull
+        public AppSearchSchema.Builder addProperty(@NonNull PropertyConfig propertyConfig) {
+            mProtoBuilder.addProperties(propertyConfig.mProto);
+            return this;
+        }
+
+        /**
+         * Constructs a new {@link AppSearchSchema} from the contents of this builder.
+         *
+         * <p>After calling this method, the builder must no longer be used.
+         */
+        @NonNull
+        public AppSearchSchema build() {
+            Set<String> propertyNames = new ArraySet<>();
+            for (PropertyConfigProto propertyConfigProto : mProtoBuilder.getPropertiesList()) {
+                if (!propertyNames.add(propertyConfigProto.getPropertyName())) {
+                    throw new IllegalSchemaException(
+                            "Property defined more than once: "
+                                    + propertyConfigProto.getPropertyName());
+                }
+            }
+            return new AppSearchSchema(mProtoBuilder.build());
+        }
+    }
+
+    /**
+     * 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.
+     */
+    public static final class PropertyConfig {
+        /** Physical data-types of the contents of the property. */
+        // NOTE: The integer values of these constants must match the proto enum constants in
+        // com.google.android.icing.proto.PropertyConfigProto.DataType.Code.
+        @IntDef(prefix = {"DATA_TYPE_"}, 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 {}
+
+        public static final int DATA_TYPE_STRING = 1;
+        public static final int DATA_TYPE_INT64 = 2;
+        public static final int DATA_TYPE_DOUBLE = 3;
+        public static final int DATA_TYPE_BOOLEAN = 4;
+
+        /** Unstructured BLOB. */
+        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}.
+         */
+        public static final int DATA_TYPE_DOCUMENT = 6;
+
+        /** The cardinality of the property (whether it is required, optional or repeated). */
+        // NOTE: The integer values of these constants must match the proto enum constants in
+        // com.google.android.icing.proto.PropertyConfigProto.Cardinality.Code.
+        @IntDef(prefix = {"CARDINALITY_"}, value = {
+                CARDINALITY_REPEATED,
+                CARDINALITY_OPTIONAL,
+                CARDINALITY_REQUIRED,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface Cardinality {}
+
+        /** Any number of items (including zero) [0...*]. */
+        public static final int CARDINALITY_REPEATED = 1;
+
+        /** Zero or one value [0,1]. */
+        public static final int CARDINALITY_OPTIONAL = 2;
+
+        /** Exactly one value [1]. */
+        public static final int CARDINALITY_REQUIRED = 3;
+
+        /** Encapsulates the configurations on how AppSearch should query/index these terms. */
+        @IntDef(prefix = {"INDEXING_TYPE_"}, 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.
+         */
+        public static final int INDEXING_TYPE_NONE = 0;
+
+        /**
+         * Content in this property should only be returned for queries matching the exact tokens
+         * appearing in this property.
+         *
+         * <p>Ex. A property with "fool" should NOT match a query for "foo".
+         */
+        public static final int INDEXING_TYPE_EXACT_TERMS = 1;
+
+        /**
+         * Content in this property should be returned for queries that are either exact matches or
+         * query matches of the tokens appearing in this property.
+         *
+         * <p>Ex. A property with "fool" <b>should</b> match a query for "foo".
+         */
+        public static final int INDEXING_TYPE_PREFIXES = 2;
+
+        /** Configures how tokens should be extracted from this property. */
+        // NOTE: The integer values of these constants must match the proto enum constants in
+        // com.google.android.icing.proto.IndexingConfig.TokenizerType.Code.
+        @IntDef(prefix = {"TOKENIZER_TYPE_"}, 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}.
+         */
+        public static final int TOKENIZER_TYPE_NONE = 0;
+
+        /** Tokenization for plain text. */
+        public static final int TOKENIZER_TYPE_PLAIN = 1;
+
+        private final PropertyConfigProto mProto;
+
+        private PropertyConfig(PropertyConfigProto proto) {
+            mProto = proto;
+        }
+
+        @Override
+        public String toString() {
+            return mProto.toString();
+        }
+
+        /**
+         * Builder for {@link PropertyConfig}.
+         *
+         * <p>The following properties must be set, or {@link PropertyConfig} construction will
+         * fail:
+         * <ul>
+         *     <li>dataType
+         *     <li>cardinality
+         * </ul>
+         *
+         * <p>In addition, if {@code schemaType} is {@link #DATA_TYPE_DOCUMENT}, {@code schemaType}
+         * is also required.
+         */
+        public static final class Builder {
+            private final PropertyConfigProto.Builder mPropertyConfigProto =
+                    PropertyConfigProto.newBuilder();
+            private final com.google.android.icing.proto.IndexingConfig.Builder
+                    mIndexingConfigProto =
+                        com.google.android.icing.proto.IndexingConfig.newBuilder();
+
+            private Builder(String propertyName) {
+                mPropertyConfigProto.setPropertyName(propertyName);
+            }
+
+            /**
+             * Type of data the property contains (e.g. string, int, bytes, etc).
+             *
+             * <p>This property must be set.
+             */
+            @NonNull
+            public PropertyConfig.Builder setDataType(@DataType int dataType) {
+                PropertyConfigProto.DataType.Code dataTypeProto =
+                        PropertyConfigProto.DataType.Code.forNumber(dataType);
+                if (dataTypeProto == null) {
+                    throw new IllegalArgumentException("Invalid dataType: " + dataType);
+                }
+                mPropertyConfigProto.setDataType(dataTypeProto);
+                return this;
+            }
+
+            /**
+             * The logical schema-type of the contents of this property.
+             *
+             * <p>Only required when {@link #setDataType(int)} is set to
+             * {@link #DATA_TYPE_DOCUMENT}. Otherwise, it is ignored.
+             */
+            @NonNull
+            public PropertyConfig.Builder setSchemaType(@NonNull String schemaType) {
+                mPropertyConfigProto.setSchemaType(schemaType);
+                return this;
+            }
+
+            /**
+             * The cardinality of the property (whether it is optional, required or repeated).
+             *
+             * <p>This property must be set.
+             */
+            @NonNull
+            public PropertyConfig.Builder setCardinality(@Cardinality int cardinality) {
+                PropertyConfigProto.Cardinality.Code cardinalityProto =
+                        PropertyConfigProto.Cardinality.Code.forNumber(cardinality);
+                if (cardinalityProto == null) {
+                    throw new IllegalArgumentException("Invalid cardinality: " + cardinality);
+                }
+                mPropertyConfigProto.setCardinality(cardinalityProto);
+                return this;
+            }
+
+            /**
+             * Configures how a property should be indexed so that it can be retrieved by queries.
+             */
+            @NonNull
+            public PropertyConfig.Builder setIndexingType(@IndexingType int indexingType) {
+                TermMatchType.Code termMatchTypeProto;
+                switch (indexingType) {
+                    case INDEXING_TYPE_NONE:
+                        termMatchTypeProto = TermMatchType.Code.UNKNOWN;
+                        break;
+                    case INDEXING_TYPE_EXACT_TERMS:
+                        termMatchTypeProto = TermMatchType.Code.EXACT_ONLY;
+                        break;
+                    case INDEXING_TYPE_PREFIXES:
+                        termMatchTypeProto = TermMatchType.Code.PREFIX;
+                        break;
+                    default:
+                        throw new IllegalArgumentException("Invalid indexingType: " + indexingType);
+                }
+                mIndexingConfigProto.setTermMatchType(termMatchTypeProto);
+                return this;
+            }
+
+            /** Configures how this property should be tokenized (split into words). */
+            @NonNull
+            public PropertyConfig.Builder setTokenizerType(@TokenizerType int tokenizerType) {
+                com.google.android.icing.proto.IndexingConfig.TokenizerType.Code
+                        tokenizerTypeProto =
+                            com.google.android.icing.proto.IndexingConfig
+                                .TokenizerType.Code.forNumber(tokenizerType);
+                if (tokenizerTypeProto == null) {
+                    throw new IllegalArgumentException("Invalid tokenizerType: " + tokenizerType);
+                }
+                mIndexingConfigProto.setTokenizerType(tokenizerTypeProto);
+                return this;
+            }
+
+            /**
+             * Constructs a new {@link PropertyConfig} from the contents of this builder.
+             *
+             * <p>After calling this method, the builder must no longer be used.
+             *
+             * @throws IllegalSchemaException If the property is not correctly populated (e.g.
+             *     missing {@code dataType}).
+             */
+            @NonNull
+            public PropertyConfig build() {
+                mPropertyConfigProto.setIndexingConfig(mIndexingConfigProto);
+                // TODO(b/147692920): Send the schema to Icing Lib for official validation, instead
+                //     of partially reimplementing some of the validation Icing does here.
+                if (mPropertyConfigProto.getDataType()
+                        == PropertyConfigProto.DataType.Code.UNKNOWN) {
+                    throw new IllegalSchemaException("Missing field: dataType");
+                }
+                if (mPropertyConfigProto.getSchemaType().isEmpty()
+                        && mPropertyConfigProto.getDataType()
+                            == PropertyConfigProto.DataType.Code.DOCUMENT) {
+                    throw new IllegalSchemaException(
+                            "Missing field: schemaType (required for configs with "
+                                    + "dataType = DOCUMENT)");
+                }
+                if (mPropertyConfigProto.getCardinality()
+                        == PropertyConfigProto.Cardinality.Code.UNKNOWN) {
+                    throw new IllegalSchemaException("Missing field: cardinality");
+                }
+                return new PropertyConfig(mPropertyConfigProto.build());
+            }
+        }
+    }
+}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
index f0f4f51..eef41ed 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
+++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
@@ -1,5 +1,5 @@
 /**
- * Copyright 2019, 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.
@@ -14,6 +14,47 @@
  * limitations under the License.
  */
 package android.app.appsearch;
+
+import com.android.internal.infra.AndroidFuture;
+
+parcelable AppSearchBatchResult;
+
 /** {@hide} */
 interface IAppSearchManager {
+    /**
+     * Sets the schema.
+     *
+     * @param schemaBytes Serialized SchemaProto.
+     * @param forceOverride Whether to apply the new schema even if it is incompatible. All
+     *     incompatible documents will be deleted.
+     * @param callback {@link AndroidFuture}&lt;{@link Void}&gt;. Will be completed with
+     *     {@code null} upon successful completion of the setSchema call, or completed
+     *     exceptionally if setSchema fails.
+     */
+    void setSchema(in byte[] schemaBytes, boolean forceOverride, in AndroidFuture callback);
+
+    /**
+     * Inserts documents into the index.
+     *
+     * @param documentsBytes {@link List}&lt;byte[]&gt; of serialized DocumentProtos.
+     * @param callback
+     *     {@link AndroidFuture}&lt;{@link AppSearchBatchResult}&lt;{@link String}, {@link Void}&gt;&gt;.
+     *     If the call fails to start, {@code callback} will be completed exceptionally. Otherwise,
+     *     {@code callback} will be completed with an
+     *     {@link AppSearchBatchResult}&lt;{@link String}, {@link Void}&gt;
+     *     where the keys are document URIs, and the values are {@code null}.
+     */
+    void putDocuments(in List documentsBytes, in AndroidFuture<AppSearchBatchResult> callback);
+
+    /**
+     * Searches a document based on a given specifications.
+     *
+     * @param searchSpecBytes Serialized SearchSpecProto.
+     * @param resultSpecBytes Serialized SearchResultsProto.
+     * @param scoringSpecBytes Serialized ScoringSpecProto.
+     * @param callback {@link AndroidFuture}. Will be completed with a serialized
+     *     {@link SearchResultsProto}, or completed exceptionally if query fails.
+     */
+    void query(in byte[] searchSpecBytes, in byte[] resultSpecBytes,
+            in byte[] scoringSpecBytes, in AndroidFuture callback);
 }
diff --git a/apex/appsearch/framework/java/android/app/appsearch/IllegalSchemaException.java b/apex/appsearch/framework/java/android/app/appsearch/IllegalSchemaException.java
new file mode 100644
index 0000000..f9e528c
--- /dev/null
+++ b/apex/appsearch/framework/java/android/app/appsearch/IllegalSchemaException.java
@@ -0,0 +1,36 @@
+/*
+ * 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.app.appsearch;
+
+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}.
+     *
+     * @param message A developer-readable description of the issue with the bundle.
+     */
+    public IllegalSchemaException(@NonNull String message) {
+        super(message);
+    }
+}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/IllegalSearchSpecException.java b/apex/appsearch/framework/java/android/app/appsearch/IllegalSearchSpecException.java
new file mode 100644
index 0000000..0d029f0
--- /dev/null
+++ b/apex/appsearch/framework/java/android/app/appsearch/IllegalSearchSpecException.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch;
+
+import android.annotation.NonNull;
+
+/**
+ * Indicates that a {@link android.app.appsearch.SearchResults} has logical inconsistencies such
+ * as unpopulated mandatory fields or illegal combinations of parameters.
+ *
+ * @hide
+ */
+public class IllegalSearchSpecException extends IllegalArgumentException {
+    /**
+     * Constructs a new {@link IllegalSearchSpecException}.
+     *
+     * @param message A developer-readable description of the issue with the bundle.
+     */
+    public IllegalSearchSpecException(@NonNull String message) {
+        super(message);
+    }
+}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/MatchInfo.java b/apex/appsearch/framework/java/android/app/appsearch/MatchInfo.java
new file mode 100644
index 0000000..6aa91a3f
--- /dev/null
+++ b/apex/appsearch/framework/java/android/app/appsearch/MatchInfo.java
@@ -0,0 +1,182 @@
+/*
+ * 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.appsearch;
+
+import android.annotation.NonNull;
+import android.util.Range;
+
+import com.google.android.icing.proto.SnippetMatchProto;
+
+/**
+ * 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.
+ *
+ * <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 [29, 41]
+ * <p>{@link MatchInfo#getSnippet()} returns "is foo. Another"
+ * <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>If the queryExpression is "Test". We will have 2 matches.
+ *
+ * <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 Jr."
+ * <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"
+ * @hide
+ */
+// TODO(sidchhabra): Capture real snippet after integration with icingLib.
+public final class MatchInfo {
+
+    private final String mPropertyPath;
+    private final SnippetMatchProto mSnippetMatch;
+    private final AppSearch.Document mDocument;
+    /**
+     * List of content with same property path in a document when there are multiple matches in
+     * repeated sections.
+     */
+    private final String[] mValues;
+
+    /** @hide */
+    public MatchInfo(@NonNull String propertyPath, @NonNull SnippetMatchProto snippetMatch,
+            @NonNull AppSearch.Document document) {
+        mPropertyPath = propertyPath;
+        mSnippetMatch = snippetMatch;
+        mDocument = document;
+        // In IcingLib snippeting is available for only 3 data types i.e String, double and long,
+        // so we need to check which of these three are requested.
+        // TODO (sidchhabra): getPropertyStringArray takes property name, handle for property path.
+        String[] values = mDocument.getPropertyStringArray(propertyPath);
+        if (values == null) {
+            values = doubleToString(mDocument.getPropertyDoubleArray(propertyPath));
+        }
+        if (values == null) {
+            values = longToString(mDocument.getPropertyLongArray(propertyPath));
+        }
+        if (values == null) {
+            throw new IllegalStateException("No content found for requested property path!");
+        }
+        mValues = values;
+    }
+
+    /**
+     * 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"
+     */
+    @NonNull
+    public String getPropertyPath() {
+        return mPropertyPath;
+    }
+
+    /**
+     * 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."
+     */
+    @NonNull
+    public String getFullText() {
+        return mValues[mSnippetMatch.getValuesIndex()];
+    }
+
+    /**
+     * Gets the exact match range corresponding to the given entry.
+     * <p>For class example 1 this returns [29, 32]
+     */
+    @NonNull
+    public Range getExactMatchPosition() {
+        return new Range(mSnippetMatch.getExactMatchPosition(),
+                mSnippetMatch.getExactMatchPosition() + mSnippetMatch.getExactMatchBytes());
+    }
+
+    /**
+     * Gets the exact match corresponding to the given entry.
+     * <p>For class example 1 this returns "foo"
+     */
+    @NonNull
+    public CharSequence getExactMatch() {
+        return getSubstring(getExactMatchPosition());
+    }
+
+    /**
+     * Gets the snippet range corresponding to the given entry.
+     * <p>For class example 1 this returns [29, 41]
+     */
+    @NonNull
+    public Range getSnippetPosition() {
+        return new Range(mSnippetMatch.getWindowPosition(),
+                mSnippetMatch.getWindowPosition() + mSnippetMatch.getWindowBytes());
+    }
+
+    /**
+     * Gets the snippet corresponding to the given entry.
+     * <p>Snippet - Provides a subset of the content to display. The
+     * length of this content can be changed {@link SearchSpec.Builder#setMaxSnippetSize(int)}.
+     * 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
+    public CharSequence getSnippet() {
+        return getSubstring(getSnippetPosition());
+    }
+
+    private CharSequence getSubstring(Range range) {
+        return getFullText()
+                .substring((int) range.getLower(), (int) range.getUpper());
+    }
+
+    /** Utility method to convert double[] to String[] */
+    private String[] doubleToString(double[] values) {
+        //TODO(sidchhabra): Implement the method.
+        return null;
+    }
+
+    /** Utility method to convert long[] to String[] */
+    private String[] longToString(long[] values) {
+        //TODO(sidchhabra): Implement the method.
+        return null;
+    }
+}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java b/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java
new file mode 100644
index 0000000..f48ebde
--- /dev/null
+++ b/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java
@@ -0,0 +1,128 @@
+/*
+ * 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.appsearch;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.google.android.icing.proto.SearchResultProto;
+import com.google.android.icing.proto.SnippetMatchProto;
+import com.google.android.icing.proto.SnippetProto;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+/**
+ * SearchResults are a list of results that are returned from a query. Each result from this
+ * list contains a document and may contain other fields like snippets based on request.
+ * This iterator class is not thread safe.
+ * @hide
+ */
+public final class SearchResults implements Iterator<SearchResults.Result> {
+
+    private final SearchResultProto mSearchResultProto;
+    private int mNextIdx;
+
+    /** @hide */
+    public SearchResults(SearchResultProto searchResultProto) {
+        mSearchResultProto = searchResultProto;
+    }
+
+    @Override
+    public boolean hasNext() {
+        return mNextIdx < mSearchResultProto.getResultsCount();
+    }
+
+    @NonNull
+    @Override
+    public Result next() {
+        if (!hasNext()) {
+            throw new NoSuchElementException();
+        }
+        Result result = new Result(mSearchResultProto.getResults(mNextIdx));
+        mNextIdx++;
+        return result;
+    }
+
+
+
+    /**
+     * This class represents the result obtained from the query. It will contain the document which
+     * which matched the specified query string and specifications.
+     * @hide
+     */
+    public static final class Result {
+        private final SearchResultProto.ResultProto mResultProto;
+
+        @Nullable
+        private AppSearch.Document mDocument;
+
+        private Result(SearchResultProto.ResultProto resultProto) {
+            mResultProto = resultProto;
+        }
+
+        /**
+         * Contains the matching {@link AppSearch.Document}.
+         * @return Document object which matched the query.
+         * @hide
+         */
+        @NonNull
+        public AppSearch.Document getDocument() {
+            if (mDocument == null) {
+                mDocument = new AppSearch.Document(mResultProto.getDocument());
+            }
+            return mDocument;
+        }
+
+        /**
+         * Contains a list of Snippets that matched the request. Only populated when requested in
+         * {@link SearchSpec.Builder#setMaxSnippetSize(int)}.
+         * @return  List of matches based on {@link SearchSpec}, if snippeting is disabled and this
+         * method is called it will return {@code null}. Users can also restrict snippet population
+         * using {@link SearchSpec.Builder#setNumToSnippet} and
+         * {@link SearchSpec.Builder#setNumMatchesPerProperty}, for all results after that value
+         * this method will return {@code null}.
+         * @hide
+         */
+        // TODO(sidchhabra): Replace Document with proper constructor.
+        @Nullable
+        public List<MatchInfo> getMatchInfo() {
+            if (!mResultProto.hasSnippet()) {
+                return null;
+            }
+            AppSearch.Document document = getDocument();
+            List<MatchInfo> matchList = new ArrayList<>();
+            for (Iterator entryProtoIterator = mResultProto.getSnippet()
+                    .getEntriesList().iterator(); entryProtoIterator.hasNext(); ) {
+                SnippetProto.EntryProto entry = (SnippetProto.EntryProto) entryProtoIterator.next();
+                for (Iterator snippetMatchProtoIterator = entry.getSnippetMatchesList().iterator();
+                        snippetMatchProtoIterator.hasNext(); ) {
+                    matchList.add(new MatchInfo(entry.getPropertyName(),
+                            (SnippetMatchProto) snippetMatchProtoIterator.next(), document));
+                }
+            }
+            return matchList;
+        }
+    }
+
+    @Override
+    public String toString() {
+        return mSearchResultProto.toString();
+    }
+}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/SearchSpec.java b/apex/appsearch/framework/java/android/app/appsearch/SearchSpec.java
new file mode 100644
index 0000000..c276ae1
--- /dev/null
+++ b/apex/appsearch/framework/java/android/app/appsearch/SearchSpec.java
@@ -0,0 +1,260 @@
+/*
+ * 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.appsearch;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+
+import com.google.android.icing.proto.ResultSpecProto;
+import com.google.android.icing.proto.ScoringSpecProto;
+import com.google.android.icing.proto.SearchSpecProto;
+import com.google.android.icing.proto.TermMatchType;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+
+/**
+ * This class represents the specification logic for AppSearch. It can be used to set the type of
+ * search, like prefix or exact only or apply filters to search for a specific schema type only etc.
+ * @hide
+ */
+// TODO(sidchhabra) : AddResultSpec fields for Snippets etc.
+public final class SearchSpec {
+
+    private final SearchSpecProto mSearchSpecProto;
+    private final ResultSpecProto mResultSpecProto;
+    private final ScoringSpecProto mScoringSpecProto;
+
+    private SearchSpec(@NonNull SearchSpecProto searchSpecProto,
+            @NonNull ResultSpecProto resultSpecProto, @NonNull ScoringSpecProto scoringSpecProto) {
+        mSearchSpecProto = searchSpecProto;
+        mResultSpecProto = resultSpecProto;
+        mScoringSpecProto = scoringSpecProto;
+    }
+
+    /** Creates a new {@link SearchSpec.Builder}. */
+    @NonNull
+    public static SearchSpec.Builder newBuilder() {
+        return new SearchSpec.Builder();
+    }
+
+    /** @hide */
+    @NonNull
+    SearchSpecProto getSearchSpecProto() {
+        return mSearchSpecProto;
+    }
+
+    /** @hide */
+    @NonNull
+    ResultSpecProto getResultSpecProto() {
+        return mResultSpecProto;
+    }
+
+    /** @hide */
+    @NonNull
+    ScoringSpecProto getScoringSpecProto() {
+        return mScoringSpecProto;
+    }
+
+    /** Term Match Type for the query. */
+    // NOTE: The integer values of these constants must match the proto enum constants in
+    // {@link com.google.android.icing.proto.SearchSpecProto.termMatchType}
+    @IntDef(prefix = {"TERM_MATCH_TYPE_"}, value = {
+            TERM_MATCH_TYPE_EXACT_ONLY,
+            TERM_MATCH_TYPE_PREFIX
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TermMatchTypeCode {}
+
+    /**
+     * 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_TYPE_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_TYPE_PREFIX = 2;
+
+    /** Ranking Strategy for query result.*/
+    // NOTE: The integer values of these constants must match the proto enum constants in
+    // {@link ScoringSpecProto.RankingStrategy.Code }
+    @IntDef(prefix = {"RANKING_STRATEGY_"}, value = {
+            RANKING_STRATEGY_NONE,
+            RANKING_STRATEGY_DOCUMENT_SCORE,
+            RANKING_STRATEGY_CREATION_TIMESTAMP
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RankingStrategyCode {}
+
+    /** 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;
+    /** Ranked by document creation timestamps. */
+    public static final int RANKING_STRATEGY_CREATION_TIMESTAMP = 2;
+
+    /** Order for query result.*/
+    // NOTE: The integer values of these constants must match the proto enum constants in
+    // {@link ScoringSpecProto.Order.Code }
+    @IntDef(prefix = {"ORDER_"}, value = {
+            ORDER_DESCENDING,
+            ORDER_ASCENDING
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface OrderCode {}
+
+    /** Search results will be returned in a descending order. */
+    public static final int ORDER_DESCENDING = 0;
+    /** Search results will be returned in an ascending order. */
+    public static final int ORDER_ASCENDING = 1;
+
+    /** Builder for {@link SearchSpec objects}. */
+    public static final class Builder {
+
+        private final SearchSpecProto.Builder mSearchSpecBuilder = SearchSpecProto.newBuilder();
+        private final ResultSpecProto.Builder mResultSpecBuilder = ResultSpecProto.newBuilder();
+        private final ScoringSpecProto.Builder mScoringSpecBuilder = ScoringSpecProto.newBuilder();
+        private final ResultSpecProto.SnippetSpecProto.Builder mSnippetSpecBuilder =
+                ResultSpecProto.SnippetSpecProto.newBuilder();
+
+        private Builder() {
+        }
+
+        /**
+         * Indicates how the query terms should match {@link TermMatchTypeCode} in the index.
+         */
+        @NonNull
+        public Builder setTermMatchType(@TermMatchTypeCode int termMatchTypeCode) {
+            TermMatchType.Code termMatchTypeCodeProto =
+                    TermMatchType.Code.forNumber(termMatchTypeCode);
+            if (termMatchTypeCodeProto == null) {
+                throw new IllegalArgumentException("Invalid term match type: "
+                        + termMatchTypeCode);
+            }
+            mSearchSpecBuilder.setTermMatchType(termMatchTypeCodeProto);
+            return this;
+        }
+
+        /**
+         * Adds a Schema type filter to {@link SearchSpec} Entry. Only search for documents that
+         * have the specified schema types.
+         * <p>If unset, the query will search over all schema types.
+         */
+        @NonNull
+        public Builder setSchemaTypes(@NonNull String... schemaTypes) {
+            for (String schemaType : schemaTypes) {
+                mSearchSpecBuilder.addSchemaTypeFilters(schemaType);
+            }
+            return this;
+        }
+
+        /** Sets the maximum number of results to retrieve from the query */
+        @NonNull
+        public SearchSpec.Builder setNumToRetrieve(int numToRetrieve) {
+            mResultSpecBuilder.setNumToRetrieve(numToRetrieve);
+            return this;
+        }
+
+        /** Sets ranking strategy for AppSearch results.*/
+        @NonNull
+        public Builder setRankingStrategy(@RankingStrategyCode int rankingStrategy) {
+            ScoringSpecProto.RankingStrategy.Code rankingStrategyCodeProto =
+                    ScoringSpecProto.RankingStrategy.Code.forNumber(rankingStrategy);
+            if (rankingStrategyCodeProto == null) {
+                throw new IllegalArgumentException("Invalid result ranking strategy: "
+                        + rankingStrategyCodeProto);
+            }
+            mScoringSpecBuilder.setRankBy(rankingStrategyCodeProto);
+            return this;
+        }
+
+        /**
+         * Indicates the order of returned search results, the default is DESC, 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(@OrderCode int order) {
+            ScoringSpecProto.Order.Code orderCodeProto =
+                    ScoringSpecProto.Order.Code.forNumber(order);
+            if (orderCodeProto == null) {
+                throw new IllegalArgumentException("Invalid result ranking order: "
+                        + orderCodeProto);
+            }
+            mScoringSpecBuilder.setOrderBy(orderCodeProto);
+            return this;
+        }
+
+        /**
+         * Only the first {@code numToSnippet} documents based on the ranking strategy
+         * will have snippet information provided.
+         * <p>If set to 0 (default), snippeting is disabled and
+         * {@link SearchResults.Result#getMatchInfo} will return {@code null} for that result.
+         */
+        @NonNull
+        public SearchSpec.Builder setNumToSnippet(int numToSnippet) {
+            mSnippetSpecBuilder.setNumToSnippet(numToSnippet);
+            return this;
+        }
+
+        /**
+         * Only the first {@code numMatchesPerProperty} matches for a every property of
+         * {@link AppSearchDocument} will contain snippet information.
+         * <p>If set to 0, snippeting is disabled and {@link SearchResults.Result#getMatchInfo}
+         * will return {@code null} for that result.
+         */
+        @NonNull
+        public SearchSpec.Builder setNumMatchesPerProperty(int numMatchesPerProperty) {
+            mSnippetSpecBuilder.setNumMatchesPerProperty(numMatchesPerProperty);
+            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.
+         * <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.
+         */
+        @NonNull
+        public SearchSpec.Builder setMaxSnippetSize(int maxSnippetSize) {
+            mSnippetSpecBuilder.setMaxWindowBytes(maxSnippetSize);
+            return this;
+        }
+
+        /**
+         * Constructs a new {@link SearchSpec} from the contents of this builder.
+         *
+         * <p>After calling this method, the builder must no longer be used.
+         */
+        @NonNull
+        public SearchSpec build() {
+            if (mSearchSpecBuilder.getTermMatchType() == TermMatchType.Code.UNKNOWN) {
+                throw new IllegalSearchSpecException("Missing termMatchType field.");
+            }
+            mResultSpecBuilder.setSnippetSpec(mSnippetSpecBuilder);
+            return new SearchSpec(mSearchSpecBuilder.build(), mResultSpecBuilder.build(),
+                    mScoringSpecBuilder.build());
+        }
+    }
+}
diff --git a/apex/appsearch/service/Android.bp b/apex/appsearch/service/Android.bp
index 4ebafce8..04f385e 100644
--- a/apex/appsearch/service/Android.bp
+++ b/apex/appsearch/service/Android.bp
@@ -12,16 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 java_library {
-  name: "service-appsearch",
-  installable: true,
-  srcs: [
-    "java/**/*.java",
-  ],
-  libs: [
-    "framework",
-    "services.core",
-  ],
-  static_libs: [
-    "icing-java-proto-lite",
-  ]
+    name: "service-appsearch",
+    installable: true,
+    srcs: ["java/**/*.java"],
+    libs: [
+        "framework",
+        "framework-appsearch",
+        "services.core",
+    ],
+    apex_available: ["com.android.appsearch"],
 }
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
index 4d44d9d..6293ee7 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -15,10 +15,27 @@
  */
 package com.android.server.appsearch;
 
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearchBatchResult;
 import android.app.appsearch.IAppSearchManager;
 import android.content.Context;
+import android.os.Binder;
+import android.os.UserHandle;
 
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.util.Preconditions;
 import com.android.server.SystemService;
+import com.android.server.appsearch.impl.AppSearchImpl;
+import com.android.server.appsearch.impl.FakeIcing;
+import com.android.server.appsearch.impl.ImplInstanceManager;
+
+import com.google.android.icing.proto.DocumentProto;
+import com.google.android.icing.proto.SchemaProto;
+import com.google.android.icing.proto.SearchResultProto;
+import com.google.android.icing.proto.SearchSpecProto;
+import com.google.android.icing.protobuf.InvalidProtocolBufferException;
+
+import java.util.List;
 
 /**
  * TODO(b/142567528): add comments when implement this class
@@ -27,13 +44,82 @@
 
     public AppSearchManagerService(Context context) {
         super(context);
+        mFakeIcing = new FakeIcing();
     }
 
+    private final FakeIcing mFakeIcing;
+
     @Override
     public void onStart() {
         publishBinderService(Context.APP_SEARCH_SERVICE, new Stub());
     }
 
     private class Stub extends IAppSearchManager.Stub {
+        @Override
+        public void setSchema(byte[] schemaBytes, boolean forceOverride, AndroidFuture callback) {
+            Preconditions.checkNotNull(schemaBytes);
+            Preconditions.checkNotNull(callback);
+            int callingUid = Binder.getCallingUidOrThrow();
+            int callingUserId = UserHandle.getUserId(callingUid);
+            long callingIdentity = Binder.clearCallingIdentity();
+            try {
+                SchemaProto schema = SchemaProto.parseFrom(schemaBytes);
+                AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
+                impl.setSchema(callingUid, schema, forceOverride);
+                callback.complete(null);
+            } catch (Throwable t) {
+                callback.completeExceptionally(t);
+            } finally {
+                Binder.restoreCallingIdentity(callingIdentity);
+            }
+        }
+
+        @Override
+        public void putDocuments(
+                List documentsBytes, AndroidFuture<AppSearchBatchResult> callback) {
+            Preconditions.checkNotNull(documentsBytes);
+            Preconditions.checkNotNull(callback);
+            int callingUid = Binder.getCallingUidOrThrow();
+            int callingUserId = UserHandle.getUserId(callingUid);
+            long callingIdentity = Binder.clearCallingIdentity();
+            try {
+                AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
+                AppSearchBatchResult.Builder<String, Void> resultBuilder =
+                        AppSearchBatchResult.newBuilder();
+                for (int i = 0; i < documentsBytes.size(); i++) {
+                    byte[] documentBytes = (byte[]) documentsBytes.get(i);
+                    DocumentProto document = DocumentProto.parseFrom(documentBytes);
+                    try {
+                        impl.putDocument(callingUid, document);
+                        resultBuilder.setSuccess(document.getUri(), /*value=*/ null);
+                    } catch (Throwable t) {
+                        resultBuilder.setFailure(document.getUri(), t);
+                    }
+                }
+                callback.complete(resultBuilder.build());
+            } catch (Throwable t) {
+                callback.completeExceptionally(t);
+            } finally {
+                Binder.restoreCallingIdentity(callingIdentity);
+            }
+        }
+        // TODO(sidchhabra):Init FakeIcing properly.
+        // TODO(sidchhabra): Do this in a threadpool.
+        @Override
+        public void query(@NonNull byte[] searchSpec, @NonNull byte[] resultSpec,
+                @NonNull byte[] scoringSpec, AndroidFuture callback) {
+            Preconditions.checkNotNull(searchSpec);
+            Preconditions.checkNotNull(resultSpec);
+            Preconditions.checkNotNull(scoringSpec);
+            SearchSpecProto searchSpecProto = null;
+            try {
+                searchSpecProto = SearchSpecProto.parseFrom(searchSpec);
+            } catch (InvalidProtocolBufferException e) {
+                throw new RuntimeException(e);
+            }
+            SearchResultProto searchResults =
+                    mFakeIcing.query(searchSpecProto.getQuery());
+            callback.complete(searchResults.toByteArray());
+        }
     }
 }
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/TEST_MAPPING b/apex/appsearch/service/java/com/android/server/appsearch/TEST_MAPPING
index 08811f8..ca5b884 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/TEST_MAPPING
+++ b/apex/appsearch/service/java/com/android/server/appsearch/TEST_MAPPING
@@ -10,6 +10,14 @@
           "include-filter": "com.android.server.appsearch"
         }
       ]
+    },
+    {
+      "name": "FrameworksCoreTests",
+      "options": [
+        {
+           "include-filter": "android.app.appsearch"
+        }
+      ]
     }
   ]
 }
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java
new file mode 100644
index 0000000..04b4b14
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appsearch.impl;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.Context;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.android.icing.proto.DocumentProto;
+import com.google.android.icing.proto.PropertyConfigProto;
+import com.google.android.icing.proto.PropertyProto;
+import com.google.android.icing.proto.SchemaProto;
+import com.google.android.icing.proto.SchemaTypeConfigProto;
+
+/**
+ * Manages interaction with {@link FakeIcing} and other components to implement AppSearch
+ * functionality.
+ */
+public final class AppSearchImpl {
+    private final Context mContext;
+    private final @UserIdInt int mUserId;
+    private final FakeIcing mFakeIcing = new FakeIcing();
+
+    AppSearchImpl(@NonNull Context context, @UserIdInt int userId) {
+        mContext = context;
+        mUserId = userId;
+    }
+
+    /**
+     * Updates the AppSearch schema for this app.
+     *
+     * @param callingUid The uid of the app calling AppSearch.
+     * @param origSchema The schema 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.
+     */
+    public void setSchema(int callingUid, @NonNull SchemaProto origSchema, boolean forceOverride) {
+        // Rewrite schema type names to include the calling app's package and uid.
+        String typePrefix = getTypePrefix(callingUid);
+        SchemaProto.Builder schemaBuilder = origSchema.toBuilder();
+        rewriteSchemaTypes(typePrefix, schemaBuilder);
+
+        // TODO(b/145635424): Save in schema type map
+        // TODO(b/145635424): Apply the schema to Icing and report results
+    }
+
+    /**
+     * Rewrites all types mentioned in the given {@code schemaBuilder} to prepend
+     * {@code typePrefix}.
+     *
+     * @param typePrefix The prefix to add
+     * @param schemaBuilder The schema to mutate
+     */
+    @VisibleForTesting
+    void rewriteSchemaTypes(
+            @NonNull String typePrefix, @NonNull SchemaProto.Builder schemaBuilder) {
+        for (int typeIdx = 0; typeIdx < schemaBuilder.getTypesCount(); typeIdx++) {
+            SchemaTypeConfigProto.Builder typeConfigBuilder =
+                    schemaBuilder.getTypes(typeIdx).toBuilder();
+
+            // Rewrite SchemaProto.types.schema_type
+            String newSchemaType = typePrefix + typeConfigBuilder.getSchemaType();
+            typeConfigBuilder.setSchemaType(newSchemaType);
+
+            // Rewrite SchemaProto.types.properties.schema_type
+            for (int propertyIdx = 0;
+                    propertyIdx < typeConfigBuilder.getPropertiesCount();
+                    propertyIdx++) {
+                PropertyConfigProto.Builder propertyConfigBuilder =
+                        typeConfigBuilder.getProperties(propertyIdx).toBuilder();
+                if (!propertyConfigBuilder.getSchemaType().isEmpty()) {
+                    String newPropertySchemaType =
+                            typePrefix + propertyConfigBuilder.getSchemaType();
+                    propertyConfigBuilder.setSchemaType(newPropertySchemaType);
+                    typeConfigBuilder.setProperties(propertyIdx, propertyConfigBuilder);
+                }
+            }
+
+            schemaBuilder.setTypes(typeIdx, typeConfigBuilder);
+        }
+    }
+
+    /**
+     * Adds a document to the AppSearch index.
+     *
+     * @param callingUid The uid of the app calling AppSearch.
+     * @param origDocument The document to index.
+     */
+    public void putDocument(int callingUid, @NonNull DocumentProto origDocument) {
+        // Rewrite the type names to include the app's prefix
+        String typePrefix = getTypePrefix(callingUid);
+        DocumentProto.Builder documentBuilder = origDocument.toBuilder();
+        rewriteDocumentTypes(typePrefix, documentBuilder);
+        mFakeIcing.put(documentBuilder.build());
+    }
+
+    /**
+     * Rewrites all types mentioned anywhere in {@code documentBuilder} to prepend
+     * {@code typePrefix}.
+     *
+     * @param typePrefix The prefix to add
+     * @param documentBuilder The document to mutate
+     */
+    @VisibleForTesting
+    void rewriteDocumentTypes(
+            @NonNull String typePrefix,
+            @NonNull DocumentProto.Builder documentBuilder) {
+        // Rewrite the type name to include the app's prefix
+        String newSchema = typePrefix + documentBuilder.getSchema();
+        documentBuilder.setSchema(newSchema);
+
+        // Add namespace. If we ever allow users to set their own namespaces, this will have
+        // to change to prepend the prefix instead of setting the whole namespace. We will also have
+        // to store the namespaces in a map similar to the type map so we can rewrite queries with
+        // empty namespaces.
+        documentBuilder.setNamespace(typePrefix);
+
+        // Recurse into derived documents
+        for (int propertyIdx = 0;
+                propertyIdx < documentBuilder.getPropertiesCount();
+                propertyIdx++) {
+            int documentCount = documentBuilder.getProperties(propertyIdx).getDocumentValuesCount();
+            if (documentCount > 0) {
+                PropertyProto.Builder propertyBuilder =
+                        documentBuilder.getProperties(propertyIdx).toBuilder();
+                for (int documentIdx = 0; documentIdx < documentCount; documentIdx++) {
+                    DocumentProto.Builder derivedDocumentBuilder =
+                            propertyBuilder.getDocumentValues(documentIdx).toBuilder();
+                    rewriteDocumentTypes(typePrefix, derivedDocumentBuilder);
+                    propertyBuilder.setDocumentValues(documentIdx, derivedDocumentBuilder);
+                }
+                documentBuilder.setProperties(propertyIdx, propertyBuilder);
+            }
+        }
+    }
+
+   /**
+     * Returns a type prefix in a format like {@code com.example.package@1000/} or
+     * {@code com.example.sharedname:5678@1000/}.
+     */
+    @NonNull
+    private String getTypePrefix(int callingUid) {
+        // For regular apps, this call will return the package name. If callingUid is an
+        // android:sharedUserId, this value may be another type of name and have a :uid suffix.
+        String callingUidName = mContext.getPackageManager().getNameForUid(callingUid);
+        if (callingUidName == null) {
+            // Not sure how this is possible --- maybe app was uninstalled?
+            throw new IllegalStateException("Failed to look up package name for uid " + callingUid);
+        }
+        return callingUidName + "@" + mUserId + "/";
+    }
+}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java b/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java
index 3dbb5cf..d07ef4b 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java
@@ -25,6 +25,7 @@
 import com.google.android.icing.proto.DocumentProto;
 import com.google.android.icing.proto.PropertyProto;
 import com.google.android.icing.proto.SearchResultProto;
+import com.google.android.icing.proto.StatusProto;
 
 import java.util.Locale;
 import java.util.Map;
@@ -36,8 +37,6 @@
  * <p>
  * Currently, only queries by single exact term are supported. There is no support for persistence,
  * namespaces, i18n tokenization, or schema.
- *
- * @hide
  */
 public class FakeIcing {
     private final AtomicInteger mNextDocId = new AtomicInteger();
@@ -99,10 +98,12 @@
     public SearchResultProto query(@NonNull String term) {
         String normTerm = normalizeString(term);
         Set<Integer> docIds = mIndex.get(normTerm);
+        SearchResultProto.Builder results = SearchResultProto.newBuilder()
+                .setStatus(StatusProto.newBuilder().setCode(StatusProto.Code.OK));
         if (docIds == null || docIds.isEmpty()) {
-            return SearchResultProto.getDefaultInstance();
+            return results.build();
         }
-        SearchResultProto.Builder results = SearchResultProto.newBuilder();
+
         for (int docId : docIds) {
             DocumentProto document = mDocStore.get(docId);
             if (document != null) {
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/impl/ImplInstanceManager.java b/apex/appsearch/service/java/com/android/server/appsearch/impl/ImplInstanceManager.java
new file mode 100644
index 0000000..395e30e
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/impl/ImplInstanceManager.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appsearch.impl;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.util.SparseArray;
+
+/**
+ * Manages the lifecycle of instances of {@link AppSearchImpl}.
+ *
+ * <p>These instances are managed per unique device-user.
+ */
+public final class ImplInstanceManager {
+    private static final SparseArray<AppSearchImpl> sInstances = new SparseArray<>();
+
+    /**
+     * Gets an instance of AppSearchImpl for the given user.
+     *
+     * <p>If no AppSearchImpl instance exists for this user, Icing will be initialized and one will
+     * be created.
+     *
+     * @param context The Android context
+     * @param userId The multi-user userId of the device user calling AppSearch
+     * @return An initialized {@link AppSearchImpl} for this user
+     */
+    @NonNull
+    public static AppSearchImpl getInstance(@NonNull Context context, @UserIdInt int userId) {
+        AppSearchImpl instance = sInstances.get(userId);
+        if (instance == null) {
+            synchronized (ImplInstanceManager.class) {
+                instance = sInstances.get(userId);
+                if (instance == null) {
+                    instance = new AppSearchImpl(context, userId);
+                    sInstances.put(userId, instance);
+                }
+            }
+        }
+        return instance;
+    }
+}
diff --git a/apex/blobstore/OWNERS b/apex/blobstore/OWNERS
new file mode 100644
index 0000000..8e04399
--- /dev/null
+++ b/apex/blobstore/OWNERS
@@ -0,0 +1,4 @@
+set noparent
+
+sudheersai@google.com
+yamasani@google.com
diff --git a/apex/blobstore/TEST_MAPPING b/apex/blobstore/TEST_MAPPING
new file mode 100644
index 0000000..4dc0c49
--- /dev/null
+++ b/apex/blobstore/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsBlobStoreTestCases"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/apex/blobstore/framework/java/android/app/blob/BlobHandle.aidl b/apex/blobstore/framework/java/android/app/blob/BlobHandle.aidl
new file mode 100644
index 0000000..02d0740
--- /dev/null
+++ b/apex/blobstore/framework/java/android/app/blob/BlobHandle.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.blob;
+
+/** {@hide} */
+parcelable BlobHandle;
\ No newline at end of file
diff --git a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java
new file mode 100644
index 0000000..60c3136
--- /dev/null
+++ b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java
@@ -0,0 +1,211 @@
+/*
+ * 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.blob;
+
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * An identifier to represent a blob.
+ */
+// TODO: use datagen tool?
+public final class BlobHandle implements Parcelable {
+    private static final String ALGO_SHA_256 = "SHA-256";
+
+    private static final int LIMIT_BLOB_TAG_LENGTH = 128; // characters
+
+    /**
+     * Cyrptographically secure hash algorithm used to generate hash of the blob this handle is
+     * representing.
+     *
+     * @hide
+     */
+    @NonNull public final String algorithm;
+    /**
+     * Hash of the blob this handle is representing using {@link #algorithm}.
+     *
+     * @hide
+     */
+    @NonNull public final byte[] digest;
+    /**
+     * Label of the blob that can be surfaced to the user.
+     * @hide
+     */
+    @NonNull public final CharSequence label;
+    /**
+     * Time in milliseconds after which the blob should be invalidated and not
+     * allowed to be accessed by any other app, in {@link System#currentTimeMillis()} timebase.
+     *
+     * @hide
+     */
+    @CurrentTimeMillisLong public final long expiryTimeMillis;
+    /**
+     * An opaque {@link String} associated with the blob.
+     *
+     * @hide
+     */
+    @NonNull public final String tag;
+
+    private BlobHandle(String algorithm, byte[] digest, CharSequence label, long expiryTimeMillis,
+            String tag) {
+        this.algorithm = algorithm;
+        this.digest = digest;
+        this.label = label;
+        this.expiryTimeMillis = expiryTimeMillis;
+        this.tag = tag;
+    }
+
+    private BlobHandle(Parcel in) {
+        this.algorithm = in.readString();
+        this.digest = in.createByteArray();
+        this.label = in.readCharSequence();
+        this.expiryTimeMillis = in.readLong();
+        this.tag = in.readString();
+    }
+
+    /** @hide */
+    public static @NonNull BlobHandle create(@NonNull String algorithm, @NonNull byte[] digest,
+            @NonNull CharSequence label, @CurrentTimeMillisLong long expiryTimeMillis,
+            @NonNull String tag) {
+        Preconditions.checkNotNull(algorithm, "algorithm must not be null");
+        Preconditions.checkNotNull(digest, "digest must not be null");
+        Preconditions.checkNotNull(label, "label must not be null");
+        Preconditions.checkArgumentNonnegative(expiryTimeMillis,
+                "expiryTimeMillis must not be negative");
+        Preconditions.checkNotNull(tag, "tag must not be null");
+        Preconditions.checkArgument(tag.length() <= LIMIT_BLOB_TAG_LENGTH, "tag too long");
+        return new BlobHandle(algorithm, digest, label, expiryTimeMillis, tag);
+    }
+
+    /**
+     * Create a new blob identifier.
+     *
+     * <p> For two objects of {@link BlobHandle} to be considered equal, the following arguments
+     * must be equal:
+     * <ul>
+     * <li> {@code digest}
+     * <li> {@code label}
+     * <li> {@code expiryTimeMillis}
+     * <li> {@code tag}
+     * </ul>
+     *
+     * @param digest the SHA-256 hash of the blob this is representing.
+     * @param label a label indicating what the blob is, that can be surfaced to the user.
+     * @param expiryTimeMillis the time in secs after which the blob should be invalidated and not
+     *                         allowed to be accessed by any other app,
+     *                         in {@link System#currentTimeMillis()} timebase.
+     * @param tag an opaque {@link String} associated with the blob. The length of the tag
+     *            cannot be more than 128 characters.
+     *
+     * @return a new instance of {@link BlobHandle} object.
+     */
+    public static @NonNull BlobHandle createWithSha256(@NonNull byte[] digest,
+            @NonNull CharSequence label, @CurrentTimeMillisLong long expiryTimeMillis,
+            @NonNull String tag) {
+        return create(ALGO_SHA_256, digest, label, expiryTimeMillis, tag);
+    }
+
+    /**
+     * Returns the SHA-256 hash of the blob that this object is representing.
+     *
+     * @see #createWithSha256(byte[], CharSequence, long, String)
+     */
+    public @NonNull byte[] getSha256Digest() {
+        return digest;
+    }
+
+    /**
+     * Returns the label associated with the blob that this object is representing.
+     *
+     * @see #createWithSha256(byte[], CharSequence, long, String)
+     */
+    public @NonNull CharSequence getLabel() {
+        return label;
+    }
+
+    /**
+     * Returns the expiry time in milliseconds of the blob that this object is representing, in
+     *         {@link System#currentTimeMillis()} timebase.
+     *
+     * @see #createWithSha256(byte[], CharSequence, long, String)
+     */
+    public @CurrentTimeMillisLong long getExpiryTimeMillis() {
+        return expiryTimeMillis;
+    }
+
+    /**
+     * Returns the opaque {@link String} associated with the blob this object is representing.
+     *
+     * @see #createWithSha256(byte[], CharSequence, long, String)
+     */
+    public @NonNull String getTag() {
+        return tag;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(algorithm);
+        dest.writeByteArray(digest);
+        dest.writeCharSequence(label);
+        dest.writeLong(expiryTimeMillis);
+        dest.writeString(tag);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || !(obj instanceof BlobHandle)) {
+            return false;
+        }
+        final BlobHandle other = (BlobHandle) obj;
+        return this.algorithm.equals(other.algorithm)
+                && Arrays.equals(this.digest, other.digest)
+                && this.label.equals(other.label)
+                && this.expiryTimeMillis == other.expiryTimeMillis
+                && this.tag.equals(tag);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(algorithm, Arrays.hashCode(digest), label, expiryTimeMillis, tag);
+    }
+
+    public static final @NonNull Creator<BlobHandle> CREATOR = new Creator<BlobHandle>() {
+        @Override
+        public @NonNull BlobHandle createFromParcel(@NonNull Parcel source) {
+            return new BlobHandle(source);
+        }
+
+        @Override
+        public @NonNull BlobHandle[] newArray(int size) {
+            return new BlobHandle[size];
+        }
+    };
+}
diff --git a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java
index 1ed188e..47af7c0 100644
--- a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java
+++ b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java
@@ -15,27 +15,557 @@
  */
 package android.app.blob;
 
+import android.annotation.BytesLong;
+import android.annotation.CallbackExecutor;
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.IdRes;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
 import android.annotation.SystemService;
 import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.os.ParcelableException;
+import android.os.RemoteException;
+
+import com.android.internal.util.function.pooled.PooledLambda;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 /**
- * This class provides access to the blob store maintained by the system.
+ * This class provides access to the blob store managed by the system.
  *
  * Apps can publish data blobs which might be useful for other apps on the device to be
- * maintained by the system and apps that would like to access these data blobs can do so
+ * managed by the system and apps that would like to access these data blobs can do so
  * by addressing them via their cryptographically secure hashes.
  *
- * TODO: make this public once the APIs are added.
- * @hide
+ * TODO: More documentation.
  */
 @SystemService(Context.BLOB_STORE_SERVICE)
 public class BlobStoreManager {
+    /** @hide */
+    public static final int COMMIT_RESULT_SUCCESS = 0;
+    /** @hide */
+    public static final int COMMIT_RESULT_ERROR = 1;
+
     private final Context mContext;
     private final IBlobStoreManager mService;
 
     /** @hide */
-    public BlobStoreManager(Context context, IBlobStoreManager service) {
+    public BlobStoreManager(@NonNull Context context, @NonNull IBlobStoreManager service) {
         mContext = context;
         mService = service;
     }
+
+    /**
+     * Create a new session using the given {@link BlobHandle}, returning a unique id
+     * that represents the session. Once created, the session can be opened
+     * multiple times across multiple device boots.
+     *
+     * <p> The system may automatically destroy sessions that have not been
+     * finalized (either committed or abandoned) within a reasonable period of
+     * time, typically about a week.
+     *
+     * @param blobHandle the {@link BlobHandle} identifier for which a new session
+     *                   needs to be created.
+     * @return positive, non-zero unique id that represents the created session.
+     *         This id remains consistent across device reboots until the
+     *         session is finalized. IDs are not reused during a given boot.
+     *
+     * @throws IOException when there is an I/O error while creating the session.
+     * @throws SecurityException when the caller is not allowed to create a session, such
+     *                           as when called from an Instant app.
+     * @throws IllegalArgumentException when {@code blobHandle} is invalid.
+     * @throws IllegalStateException when a new session could not be created, such as when the
+     *                               caller is trying to create too many sessions or when the
+     *                               device is running low on space.
+     */
+    public @IntRange(from = 1) long createSession(@NonNull BlobHandle blobHandle)
+            throws IOException {
+        try {
+            return mService.createSession(blobHandle, mContext.getOpPackageName());
+        } catch (ParcelableException e) {
+            e.maybeRethrow(IOException.class);
+            throw new RuntimeException(e);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Open an existing session to actively perform work.
+     *
+     * @param sessionId a unique id obtained via {@link #createSession(BlobHandle)} that
+     *                  represents a particular session.
+     * @return the {@link Session} object corresponding to the {@code sessionId}.
+     *
+     * @throws IOException when there is an I/O error while opening the session.
+     * @throws SecurityException when the caller does not own the session, or
+     *                           the session does not exist or is invalid.
+     */
+    public @NonNull Session openSession(@IntRange(from = 1) long sessionId) throws IOException {
+        try {
+            return new Session(mService.openSession(sessionId, mContext.getOpPackageName()));
+        } catch (ParcelableException e) {
+            e.maybeRethrow(IOException.class);
+            throw new RuntimeException(e);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Delete an existing session and any data that was written to that session so far.
+     *
+     * @param sessionId a unique id obtained via {@link #createSession(BlobHandle)} that
+     *                  represents a particular session.
+     *
+     * @throws IOException when there is an I/O error while deleting the session.
+     * @throws SecurityException when the caller does not own the session, or
+     *                           the session does not exist or is invalid.
+     */
+    public void deleteSession(@IntRange(from = 1) long sessionId) throws IOException {
+        try {
+            mService.deleteSession(sessionId, mContext.getOpPackageName());
+        } catch (ParcelableException e) {
+            e.maybeRethrow(IOException.class);
+            throw new RuntimeException(e);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Opens an existing blob for reading from the blob store managed by the system.
+     *
+     * @param blobHandle the {@link BlobHandle} representing the blob that the caller
+     *                   wants to access.
+     * @return a {@link ParcelFileDescriptor} that can be used to read the blob content.
+     *
+     * @throws IOException when there is an I/O while opening the blob for read.
+     * @throws IllegalArgumentException when {@code blobHandle} is invalid.
+     * @throws SecurityException when the blob represented by the {@code blobHandle} does not
+     *                           exist or the caller does not have access to it.
+     */
+    public @NonNull ParcelFileDescriptor openBlob(@NonNull BlobHandle blobHandle)
+            throws IOException {
+        try {
+            return mService.openBlob(blobHandle, mContext.getOpPackageName());
+        } catch (ParcelableException e) {
+            e.maybeRethrow(IOException.class);
+            throw new RuntimeException(e);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Acquire a lease to the blob represented by {@code blobHandle}. This lease indicates to the
+     * system that the caller wants the blob to be kept around.
+     *
+     * <p> Any active leases will be automatically released when the blob's expiry time
+     * ({@link BlobHandle#getExpiryTimeMillis()}) is elapsed.
+     *
+     * <p> This lease information is persisted and calling this more than once will result in
+     * latest lease overriding any previous lease.
+     *
+     * @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to
+     *                   acquire a lease for.
+     * @param descriptionResId the resource id for a short description string that can be surfaced
+     *                         to the user explaining what the blob is used for.
+     * @param leaseExpiryTimeMillis the time in milliseconds after which the lease can be
+     *                              automatically released, in {@link System#currentTimeMillis()}
+     *                              timebase. If its value is {@code 0}, then the behavior of this
+     *                              API is identical to {@link #acquireLease(BlobHandle, int)}
+     *                              where clients have to explicitly call
+     *                              {@link #releaseLease(BlobHandle)} when they don't
+     *                              need the blob anymore.
+     *
+     * @throws IOException when there is an I/O error while acquiring a lease to the blob.
+     * @throws SecurityException when the blob represented by the {@code blobHandle} does not
+     *                           exist or the caller does not have access to it.
+     * @throws IllegalArgumentException when {@code blobHandle} is invalid or
+     *                                  if the {@code leaseExpiryTimeMillis} is greater than the
+     *                                  {@link BlobHandle#getExpiryTimeMillis()}.
+     * @throws IllegalStateException when a lease could not be acquired, such as when the
+     *                               caller is trying to acquire too many leases.
+     *
+     * @see {@link #acquireLease(BlobHandle, int)}
+     */
+    public void acquireLease(@NonNull BlobHandle blobHandle, @IdRes int descriptionResId,
+            @CurrentTimeMillisLong long leaseExpiryTimeMillis) throws IOException {
+        try {
+            mService.acquireLease(blobHandle, descriptionResId, leaseExpiryTimeMillis,
+                    mContext.getOpPackageName());
+        } catch (ParcelableException e) {
+            e.maybeRethrow(IOException.class);
+            throw new RuntimeException(e);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Acquire a lease to the blob represented by {@code blobHandle}. This lease indicates to the
+     * system that the caller wants the blob to be kept around.
+     *
+     * <p> This is similar to {@link #acquireLease(BlobHandle, int, long)} except clients don't
+     * have to specify the lease expiry time upfront using this API and need to explicitly
+     * release the lease using {@link #releaseLease(BlobHandle)} when they no longer like to keep
+     * a blob around.
+     *
+     * <p> Any active leases will be automatically released when the blob's expiry time
+     * ({@link BlobHandle#getExpiryTimeMillis()}) is elapsed.
+     *
+     * <p> This lease information is persisted and calling this more than once will result in
+     * latest lease overriding any previous lease.
+     *
+     * @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to
+     *                   acquire a lease for.
+     * @param descriptionResId the resource id for a short description string that can be surfaced
+     *                         to the user explaining what the blob is used for.
+     *
+     * @throws IOException when there is an I/O error while acquiring a lease to the blob.
+     * @throws SecurityException when the blob represented by the {@code blobHandle} does not
+     *                           exist or the caller does not have access to it.
+     * @throws IllegalArgumentException when {@code blobHandle} is invalid.
+     * @throws IllegalStateException when a lease could not be acquired, such as when the
+     *                               caller is trying to acquire too many leases.
+     *
+     * @see {@link #acquireLease(BlobHandle, int, long)}
+     */
+    public void acquireLease(@NonNull BlobHandle blobHandle, @IdRes int descriptionResId)
+            throws IOException {
+        acquireLease(blobHandle, descriptionResId, 0);
+    }
+
+    /**
+     * Release all active leases to the blob represented by {@code blobHandle} which are
+     * currently held by the caller.
+     *
+     * @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to
+     *                   release the leases for.
+     *
+     * @throws IOException when there is an I/O error while releasing the releases to the blob.
+     * @throws SecurityException when the blob represented by the {@code blobHandle} does not
+     *                           exist or the caller does not have access to it.
+     * @throws IllegalArgumentException when {@code blobHandle} is invalid.
+     */
+    public void releaseLease(@NonNull BlobHandle blobHandle) throws IOException {
+        try {
+            mService.releaseLease(blobHandle, mContext.getOpPackageName());
+        } catch (ParcelableException e) {
+            e.maybeRethrow(IOException.class);
+            throw new RuntimeException(e);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Represents an ongoing session of a blob's contribution to the blob store managed by the
+     * system.
+     *
+     * <p> Clients that want to contribute a blob need to first create a {@link Session} using
+     * {@link #createSession(BlobHandle)} and once the session is created, clients can open and
+     * close this session multiple times using {@link #openSession(long)} and
+     * {@link Session#close()} before committing it using
+     * {@link Session#commit(Executor, Consumer)}, at which point system will take
+     * ownership of the blob and the client can no longer make any modifications to the blob's
+     * content.
+     */
+    public static class Session implements Closeable {
+        private final IBlobStoreSession mSession;
+
+        private Session(@NonNull IBlobStoreSession session) {
+            mSession = session;
+        }
+
+        /**
+         * Opens a file descriptor to write a blob into the session.
+         *
+         * <p> The returned file descriptor will start writing data at the requested offset
+         * in the underlying file, which can be used to resume a partially
+         * written file. If a valid file length is specified, the system will
+         * preallocate the underlying disk space to optimize placement on disk.
+         * It is strongly recommended to provide a valid file length when known.
+         *
+         * @param offsetBytes offset into the file to begin writing at, or 0 to
+         *                    start at the beginning of the file.
+         * @param lengthBytes total size of the file being written, used to
+         *                    preallocate the underlying disk space, or -1 if unknown.
+         *                    The system may clear various caches as needed to allocate
+         *                    this space.
+         *
+         * @return a {@link ParcelFileDescriptor} for writing to the blob file.
+         *
+         * @throws IOException when there is an I/O error while opening the file to write.
+         * @throws SecurityException when the caller is not the owner of the session.
+         * @throws IllegalStateException when the caller tries to write to the file after it is
+         *                               abandoned (using {@link #abandon()})
+         *                               or committed (using {@link #commit})
+         *                               or closed (using {@link #close()}).
+         */
+        public @NonNull ParcelFileDescriptor openWrite(@BytesLong long offsetBytes,
+                @BytesLong long lengthBytes) throws IOException {
+            try {
+                final ParcelFileDescriptor pfd = mSession.openWrite(offsetBytes, lengthBytes);
+                pfd.seekTo(offsetBytes);
+                return pfd;
+            } catch (ParcelableException e) {
+                e.maybeRethrow(IOException.class);
+                throw new RuntimeException(e);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Gets the size of the blob file that was written to the session so far.
+         *
+         * @return the size of the blob file so far.
+         *
+         * @throws IOException when there is an I/O error while opening the file to read.
+         * @throws SecurityException when the caller is not the owner of the session.
+         * @throws IllegalStateException when the caller tries to get the file size after it is
+         *                               abandoned (using {@link #abandon()})
+         *                               or closed (using {@link #close()}).
+         */
+        public @BytesLong long getSize() throws IOException {
+            try {
+                return mSession.getSize();
+            } catch (ParcelableException e) {
+                e.maybeRethrow(IOException.class);
+                throw new RuntimeException(e);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Close this session. It can be re-opened for writing/reading if it has not been
+         * abandoned (using {@link #abandon}) or closed (using {@link #commit}).
+         *
+         * @throws IOException when there is an I/O error while closing the session.
+         * @throws SecurityException when the caller is not the owner of the session.
+         */
+        public void close() throws IOException {
+            try {
+                mSession.close();
+            } catch (ParcelableException e) {
+                e.maybeRethrow(IOException.class);
+                throw new RuntimeException(e);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Abandon this session and delete any data that was written to this session so far.
+         *
+         * @throws IOException when there is an I/O error while abandoning the session.
+         * @throws SecurityException when the caller is not the owner of the session.
+         * @throws IllegalStateException when the caller tries to abandon a session which was
+         *                               already finalized.
+         */
+        public void abandon() throws IOException {
+            try {
+                mSession.abandon();
+            } catch (ParcelableException e) {
+                e.maybeRethrow(IOException.class);
+                throw new RuntimeException(e);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Allow {@code packageName} with a particular signing certificate to access this blob
+         * data once it is committed using a {@link BlobHandle} representing the blob.
+         *
+         * <p> This needs to be called before committing the blob using
+         * {@link #commit(Executor, Consumer)}.
+         *
+         * @param packageName the name of the package which should be allowed to access the blob.
+         * @param certificate the input bytes representing a certificate of type
+         *                    {@link android.content.pm.PackageManager#CERT_INPUT_SHA256}.
+         *
+         * @throws IOException when there is an I/O error while changing the access.
+         * @throws SecurityException when the caller is not the owner of the session.
+         * @throws IllegalStateException when the caller tries to change access for a blob which is
+         *                               already committed.
+         */
+        public void allowPackageAccess(@NonNull String packageName, @NonNull byte[] certificate)
+                throws IOException {
+            try {
+                mSession.allowPackageAccess(packageName, certificate);
+            } catch (ParcelableException e) {
+                e.maybeRethrow(IOException.class);
+                throw new RuntimeException(e);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Returns {@code true} if access has been allowed for a {@code packageName} using either
+         * {@link #allowPackageAccess(String, byte[])}.
+         * Otherwise, {@code false}.
+         *
+         * @param packageName the name of the package to check the access for.
+         * @param certificate the input bytes representing a certificate of type
+         *                    {@link android.content.pm.PackageManager#CERT_INPUT_SHA256}.
+         *
+         * @throws IOException when there is an I/O error while getting the access type.
+         * @throws IllegalStateException when the caller tries to get access type from a session
+         *                               which is closed or abandoned.
+         */
+        public boolean isPackageAccessAllowed(@NonNull String packageName,
+                @NonNull byte[] certificate) throws IOException {
+            try {
+                return mSession.isPackageAccessAllowed(packageName, certificate);
+            } catch (ParcelableException e) {
+                e.maybeRethrow(IOException.class);
+                throw new RuntimeException(e);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Allow packages which are signed with the same certificate as the caller to access this
+         * blob data once it is committed using a {@link BlobHandle} representing the blob.
+         *
+         * <p> This needs to be called before committing the blob using
+         * {@link #commit(Executor, Consumer)}.
+         *
+         * @throws IOException when there is an I/O error while changing the access.
+         * @throws SecurityException when the caller is not the owner of the session.
+         * @throws IllegalStateException when the caller tries to change access for a blob which is
+         *                               already committed.
+         */
+        public void allowSameSignatureAccess() throws IOException {
+            try {
+                mSession.allowSameSignatureAccess();
+            } catch (ParcelableException e) {
+                e.maybeRethrow(IOException.class);
+                throw new RuntimeException(e);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Returns {@code true} if access has been allowed for packages signed with the same
+         * certificate as the caller by using {@link #allowSameSignatureAccess()}.
+         * Otherwise, {@code false}.
+         *
+         * @throws IOException when there is an I/O error while getting the access type.
+         * @throws IllegalStateException when the caller tries to get access type from a session
+         *                               which is closed or abandoned.
+         */
+        public boolean isSameSignatureAccessAllowed() throws IOException {
+            try {
+                return mSession.isSameSignatureAccessAllowed();
+            } catch (ParcelableException e) {
+                e.maybeRethrow(IOException.class);
+                throw new RuntimeException(e);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Allow any app on the device to access this blob data once it is committed using
+         * a {@link BlobHandle} representing the blob.
+         *
+         * <p><strong>Note:</strong> This is only meant to be used from libraries and SDKs where
+         * the apps which we want to allow access is not known ahead of time.
+         * If a blob is being committed to be shared with a particular set of apps, it is highly
+         * recommended to use {@link #allowPackageAccess(String, byte[])} instead.
+         *
+         * <p> This needs to be called before committing the blob using
+         * {@link #commit(Executor, Consumer)}.
+         *
+         * @throws IOException when there is an I/O error while changing the access.
+         * @throws SecurityException when the caller is not the owner of the session.
+         * @throws IllegalStateException when the caller tries to change access for a blob which is
+         *                               already committed.
+         */
+        public void allowPublicAccess() throws IOException {
+            try {
+                mSession.allowPublicAccess();
+            } catch (ParcelableException e) {
+                e.maybeRethrow(IOException.class);
+                throw new RuntimeException(e);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Returns {@code true} if public access has been allowed by using
+         * {@link #allowPublicAccess()}. Otherwise, {@code false}.
+         *
+         * @throws IOException when there is an I/O error while getting the access type.
+         * @throws IllegalStateException when the caller tries to get access type from a session
+         *                               which is closed or abandoned.
+         */
+        public boolean isPublicAccessAllowed() throws IOException {
+            try {
+                return mSession.isPublicAccessAllowed();
+            } catch (ParcelableException e) {
+                e.maybeRethrow(IOException.class);
+                throw new RuntimeException(e);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Commit the file that was written so far to this session to the blob store maintained by
+         * the system.
+         *
+         * <p> Once this method is called, the session is finalized and no additional
+         * mutations can be performed on the session. If the device reboots
+         * before the session has been finalized, you may commit the session again.
+         *
+         * <p> Note that this commit operation will fail if the hash of the data written so far
+         * to this session does not match with the one used for
+         * {@link BlobHandle#createWithSha256(byte[], CharSequence, long, String)}  BlobHandle}
+         * associated with this session.
+         *
+         * <p> Committing the same data more than once will result in replacing the corresponding
+         * access mode (via calling one of {@link #allowPackageAccess(String, byte[])},
+         * {@link #allowSameSignatureAccess()}, etc) with the latest one.
+         *
+         * @param executor the executor on which result callback will be invoked.
+         * @param resultCallback a callback to receive the commit result. when the result is
+         *                       {@code 0}, it indicates success. Otherwise, failure.
+         *
+         * @throws IOException when there is an I/O error while committing the session.
+         * @throws SecurityException when the caller is not the owner of the session.
+         * @throws IllegalArgumentException when the passed parameters are not valid.
+         * @throws IllegalStateException when the caller tries to commit a session which was
+         *                               already finalized.
+         */
+        public void commit(@NonNull @CallbackExecutor Executor executor,
+                @NonNull Consumer<Integer> resultCallback) throws IOException {
+            try {
+                mSession.commit(new IBlobCommitCallback.Stub() {
+                    public void onResult(int result) {
+                        executor.execute(PooledLambda.obtainRunnable(
+                                Consumer::accept, resultCallback, result));
+                    }
+                });
+            } catch (ParcelableException e) {
+                e.maybeRethrow(IOException.class);
+                throw new RuntimeException(e);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
 }
diff --git a/apex/blobstore/framework/java/android/app/blob/IBlobCommitCallback.aidl b/apex/blobstore/framework/java/android/app/blob/IBlobCommitCallback.aidl
new file mode 100644
index 0000000..a9b30e2
--- /dev/null
+++ b/apex/blobstore/framework/java/android/app/blob/IBlobCommitCallback.aidl
@@ -0,0 +1,21 @@
+/*
+ * 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.blob;
+
+/** {@hide} */
+oneway interface IBlobCommitCallback {
+    void onResult(int result);
+}
\ No newline at end of file
diff --git a/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl b/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl
index 00c1ed4..dfbf78f 100644
--- a/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl
+++ b/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl
@@ -15,6 +15,17 @@
  */
 package android.app.blob;
 
+import android.app.blob.BlobHandle;
+import android.app.blob.IBlobStoreSession;
+
 /** {@hide} */
 interface IBlobStoreManager {
+    long createSession(in BlobHandle handle, in String packageName);
+    IBlobStoreSession openSession(long sessionId, in String packageName);
+    ParcelFileDescriptor openBlob(in BlobHandle handle, in String packageName);
+    void deleteSession(long sessionId, in String packageName);
+
+    void acquireLease(in BlobHandle handle, int descriptionResId, long leaseTimeout,
+            in String packageName);
+    void releaseLease(in BlobHandle handle, in String packageName);
 }
\ No newline at end of file
diff --git a/apex/blobstore/framework/java/android/app/blob/IBlobStoreSession.aidl b/apex/blobstore/framework/java/android/app/blob/IBlobStoreSession.aidl
new file mode 100644
index 0000000..4ae919b
--- /dev/null
+++ b/apex/blobstore/framework/java/android/app/blob/IBlobStoreSession.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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.blob;
+
+import android.app.blob.IBlobCommitCallback;
+import android.os.ParcelFileDescriptor;
+
+/** {@hide} */
+interface IBlobStoreSession {
+    ParcelFileDescriptor openWrite(long offsetBytes, long lengthBytes);
+
+    void allowPackageAccess(in String packageName, in byte[] certificate);
+    void allowSameSignatureAccess();
+    void allowPublicAccess();
+
+    boolean isPackageAccessAllowed(in String packageName, in byte[] certificate);
+    boolean isSameSignatureAccessAllowed();
+    boolean isPublicAccessAllowed();
+
+    long getSize();
+    void close();
+    void abandon();
+
+    void commit(in IBlobCommitCallback callback);
+}
\ No newline at end of file
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java b/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java
new file mode 100644
index 0000000..357250a
--- /dev/null
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java
@@ -0,0 +1,147 @@
+/*
+ * 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.blob;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.util.ArraySet;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Class for representing how a blob can be shared.
+ *
+ * Note that this class is not thread-safe, callers need to take of synchronizing access.
+ */
+class BlobAccessMode {
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, value = {
+            ACCESS_TYPE_PRIVATE,
+            ACCESS_TYPE_PUBLIC,
+            ACCESS_TYPE_SAME_SIGNATURE,
+            ACCESS_TYPE_WHITELIST,
+    })
+    @interface AccessType {}
+    static final int ACCESS_TYPE_PRIVATE = 1 << 0;
+    static final int ACCESS_TYPE_PUBLIC = 1 << 1;
+    static final int ACCESS_TYPE_SAME_SIGNATURE = 1 << 2;
+    static final int ACCESS_TYPE_WHITELIST = 1 << 3;
+
+    private int mAccessType = ACCESS_TYPE_PRIVATE;
+
+    private final ArraySet<PackageIdentifier> mWhitelistedPackages = new ArraySet<>();
+
+    void allow(BlobAccessMode other) {
+        if ((other.mAccessType & ACCESS_TYPE_WHITELIST) != 0) {
+            mWhitelistedPackages.addAll(other.mWhitelistedPackages);
+        }
+        mAccessType |= other.mAccessType;
+    }
+
+    void allowPublicAccess() {
+        mAccessType |= ACCESS_TYPE_PUBLIC;
+    }
+
+    void allowSameSignatureAccess() {
+        mAccessType |= ACCESS_TYPE_SAME_SIGNATURE;
+    }
+
+    void allowPackageAccess(@NonNull String packageName, @NonNull byte[] certificate) {
+        mAccessType |= ACCESS_TYPE_WHITELIST;
+        mWhitelistedPackages.add(PackageIdentifier.create(packageName, certificate));
+    }
+
+    boolean isPublicAccessAllowed() {
+        return (mAccessType & ACCESS_TYPE_PUBLIC) != 0;
+    }
+
+    boolean isSameSignatureAccessAllowed() {
+        return (mAccessType & ACCESS_TYPE_SAME_SIGNATURE) != 0;
+    }
+
+    boolean isPackageAccessAllowed(@NonNull String packageName, @NonNull byte[] certificate) {
+        if ((mAccessType & ACCESS_TYPE_WHITELIST) == 0) {
+            return false;
+        }
+        return mWhitelistedPackages.contains(PackageIdentifier.create(packageName, certificate));
+    }
+
+    boolean isAccessAllowedForCaller(Context context,
+            @NonNull String callingPackage, @NonNull String committerPackage) {
+        if ((mAccessType & ACCESS_TYPE_PUBLIC) != 0) {
+            return true;
+        }
+
+        final PackageManager pm = context.getPackageManager();
+        if ((mAccessType & ACCESS_TYPE_SAME_SIGNATURE) != 0) {
+            if (pm.checkSignatures(committerPackage, callingPackage)
+                    == PackageManager.SIGNATURE_MATCH) {
+                return true;
+            }
+        }
+
+        if ((mAccessType & ACCESS_TYPE_WHITELIST) != 0) {
+            for (int i = 0; i < mWhitelistedPackages.size(); ++i) {
+                final PackageIdentifier packageIdentifier = mWhitelistedPackages.valueAt(i);
+                if (packageIdentifier.packageName.equals(callingPackage)
+                        && pm.hasSigningCertificate(callingPackage, packageIdentifier.certificate,
+                                PackageManager.CERT_INPUT_SHA256)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    private static final class PackageIdentifier {
+        public final String packageName;
+        public final byte[] certificate;
+
+        private PackageIdentifier(@NonNull String packageName, @NonNull byte[] certificate) {
+            this.packageName = packageName;
+            this.certificate = certificate;
+        }
+
+        public static PackageIdentifier create(@NonNull String packageName,
+                @NonNull byte[] certificate) {
+            return new PackageIdentifier(packageName, certificate);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null || !(obj instanceof PackageIdentifier)) {
+                return false;
+            }
+            final PackageIdentifier other = (PackageIdentifier) obj;
+            return this.packageName.equals(other.packageName)
+                    && Arrays.equals(this.certificate, other.certificate);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(packageName, Arrays.hashCode(certificate));
+        }
+    }
+}
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
new file mode 100644
index 0000000..d3a2271
--- /dev/null
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
@@ -0,0 +1,211 @@
+/*
+ * 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.blob;
+
+import static android.system.OsConstants.O_RDONLY;
+
+import android.annotation.NonNull;
+import android.app.blob.BlobHandle;
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.os.RevocableFileDescriptor;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.util.Objects;
+
+class BlobMetadata {
+    private final Object mMetadataLock = new Object();
+
+    private final Context mContext;
+    private final long mBlobId;
+    private final BlobHandle mBlobHandle;
+
+    @GuardedBy("mMetadataLock")
+    private final ArraySet<Committer> mCommitters = new ArraySet<>();
+
+    @GuardedBy("mMetadataLock")
+    private final ArraySet<Leasee> mLeasees = new ArraySet<>();
+
+    /**
+     * Contains packageName -> {RevocableFileDescriptors}.
+     *
+     * Keep track of RevocableFileDescriptors given to clients which are not yet revoked/closed so
+     * that when clients access is revoked or the blob gets deleted, we can be sure that clients
+     * do not have any reference to the blob and the space occupied by the blob can be freed.
+     */
+    @GuardedBy("mRevocableFds")
+    private final ArrayMap<String, ArraySet<RevocableFileDescriptor>> mRevocableFds =
+            new ArrayMap<>();
+
+    BlobMetadata(Context context, long blobId, BlobHandle blobHandle) {
+        mContext = context;
+        mBlobId = blobId;
+        mBlobHandle = blobHandle;
+    }
+
+    void addCommitter(String packageName, int uid, BlobAccessMode blobAccessMode) {
+        synchronized (mMetadataLock) {
+            mCommitters.add(new Committer(packageName, uid, blobAccessMode));
+        }
+    }
+
+    void addLeasee(String callingPackage, int callingUid,
+            int descriptionResId, long leaseExpiryTimeMillis) {
+        synchronized (mMetadataLock) {
+            mLeasees.add(new Leasee(callingPackage, callingUid,
+                    descriptionResId, leaseExpiryTimeMillis));
+        }
+    }
+
+    void removeLeasee(String packageName, int uid) {
+        synchronized (mMetadataLock) {
+            mLeasees.remove(new Accessor(packageName, uid));
+        }
+    }
+
+    boolean isAccessAllowedForCaller(String callingPackage, int callingUid) {
+        // TODO: verify blob is still valid (expiryTime is not elapsed)
+        synchronized (mMetadataLock) {
+            // Check if packageName already holds a lease on the blob.
+            for (int i = 0, size = mLeasees.size(); i < size; ++i) {
+                final Leasee leasee = mLeasees.valueAt(i);
+                if (leasee.equals(callingPackage, callingUid)
+                        && leasee.isStillValid()) {
+                    return true;
+                }
+            }
+
+            for (int i = 0, size = mCommitters.size(); i < size; ++i) {
+                final Committer committer = mCommitters.valueAt(i);
+
+                // Check if the caller is the same package that committed the blob.
+                if (committer.equals(callingPackage, callingUid)) {
+                    return true;
+                }
+
+                // Check if the caller is allowed access as per the access mode specified
+                // by the committer.
+                if (committer.blobAccessMode.isAccessAllowedForCaller(mContext,
+                        callingPackage, committer.packageName)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    ParcelFileDescriptor openForRead(String callingPackage) throws IOException {
+        // TODO: Add limit on opened fds
+        FileDescriptor fd;
+        try {
+            fd = Os.open(BlobStoreConfig.getBlobFile(mBlobId).getPath(), O_RDONLY, 0);
+        } catch (ErrnoException e) {
+            throw e.rethrowAsIOException();
+        }
+        synchronized (mMetadataLock) {
+            return createRevocableFdLocked(fd, callingPackage);
+        }
+    }
+
+    @GuardedBy("mMetadataLock")
+    @NonNull
+    private ParcelFileDescriptor createRevocableFdLocked(FileDescriptor fd,
+            String callingPackage) throws IOException {
+        final RevocableFileDescriptor revocableFd =
+                new RevocableFileDescriptor(mContext, fd);
+        synchronized (mRevocableFds) {
+            ArraySet<RevocableFileDescriptor> revocableFdsForPkg =
+                    mRevocableFds.get(callingPackage);
+            if (revocableFdsForPkg == null) {
+                revocableFdsForPkg = new ArraySet<>();
+                mRevocableFds.put(callingPackage, revocableFdsForPkg);
+            }
+            revocableFdsForPkg.add(revocableFd);
+        }
+        revocableFd.addOnCloseListener((e) -> {
+            synchronized (mRevocableFds) {
+                final ArraySet<RevocableFileDescriptor> revocableFdsForPkg =
+                        mRevocableFds.get(callingPackage);
+                if (revocableFdsForPkg != null) {
+                    revocableFdsForPkg.remove(revocableFd);
+                }
+            }
+        });
+        return revocableFd.getRevocableFileDescriptor();
+    }
+
+    static final class Committer extends Accessor {
+        public final BlobAccessMode blobAccessMode;
+
+        Committer(String packageName, int uid, BlobAccessMode blobAccessMode) {
+            super(packageName, uid);
+            this.blobAccessMode = blobAccessMode;
+        }
+    }
+
+    static final class Leasee extends Accessor {
+        public final int descriptionResId;
+        public final long expiryTimeMillis;
+
+        Leasee(String packageName, int uid, int descriptionResId, long expiryTimeMillis) {
+            super(packageName, uid);
+            this.descriptionResId = descriptionResId;
+            this.expiryTimeMillis = expiryTimeMillis;
+        }
+
+        boolean isStillValid() {
+            return expiryTimeMillis == 0 || expiryTimeMillis <= System.currentTimeMillis();
+        }
+    }
+
+    static class Accessor {
+        public final String packageName;
+        public final int uid;
+
+        Accessor(String packageName, int uid) {
+            this.packageName = packageName;
+            this.uid = uid;
+        }
+
+        public boolean equals(String packageName, int uid) {
+            return this.uid == uid && this.packageName.equals(packageName);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null || !(obj instanceof Accessor)) {
+                return false;
+            }
+            final Accessor other = (Accessor) obj;
+            return this.uid == other.uid && this.packageName.equals(other.packageName);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(packageName, uid);
+        }
+    }
+}
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
new file mode 100644
index 0000000..b9a4b17
--- /dev/null
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
@@ -0,0 +1,78 @@
+/*
+ * 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.blob;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Environment;
+import android.util.Slog;
+
+import java.io.File;
+
+class BlobStoreConfig {
+    public static final String TAG = "BlobStore";
+
+    @Nullable
+    public static File prepareBlobFile(long sessionId) {
+        final File blobsDir = prepareBlobsDir();
+        return blobsDir == null ? null : getBlobFile(blobsDir, sessionId);
+    }
+
+    @NonNull
+    public static File getBlobFile(long sessionId) {
+        return getBlobFile(getBlobsDir(), sessionId);
+    }
+
+    @NonNull
+    private static File getBlobFile(File blobsDir, long sessionId) {
+        return new File(blobsDir, String.valueOf(sessionId));
+    }
+
+    @Nullable
+    public static File prepareBlobsDir() {
+        final File blobsDir = getBlobsDir(prepareBlobStoreRootDir());
+        if (!blobsDir.exists() && !blobsDir.mkdir()) {
+            Slog.e(TAG, "Failed to mkdir(): " + blobsDir);
+            return null;
+        }
+        return blobsDir;
+    }
+
+    @NonNull
+    public static File getBlobsDir() {
+        return getBlobsDir(getBlobStoreRootDir());
+    }
+
+    @NonNull
+    private static File getBlobsDir(File blobsRootDir) {
+        return new File(blobsRootDir, "blobs");
+    }
+
+    @Nullable
+    public static File prepareBlobStoreRootDir() {
+        final File blobStoreRootDir = getBlobStoreRootDir();
+        if (!blobStoreRootDir.exists() && !blobStoreRootDir.mkdir()) {
+            Slog.e(TAG, "Failed to mkdir(): " + blobStoreRootDir);
+            return null;
+        }
+        return blobStoreRootDir;
+    }
+
+    @NonNull
+    public static File getBlobStoreRootDir() {
+        return new File(Environment.getDataSystemDirectory(), "blobstore");
+    }
+}
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
index d7cab59..9d60f86 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -15,25 +15,350 @@
  */
 package com.android.server.blob;
 
-import android.app.blob.IBlobStoreManager;
-import android.content.Context;
+import static android.app.blob.BlobStoreManager.COMMIT_RESULT_SUCCESS;
 
+import static com.android.server.blob.BlobStoreConfig.TAG;
+import static com.android.server.blob.BlobStoreSession.STATE_ABANDONED;
+import static com.android.server.blob.BlobStoreSession.STATE_COMMITTED;
+import static com.android.server.blob.BlobStoreSession.STATE_VERIFIED_INVALID;
+import static com.android.server.blob.BlobStoreSession.STATE_VERIFIED_VALID;
+import static com.android.server.blob.BlobStoreSession.stateToString;
+
+import android.annotation.CurrentTimeSecondsLong;
+import android.annotation.IdRes;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.app.blob.BlobHandle;
+import android.app.blob.IBlobStoreManager;
+import android.app.blob.IBlobStoreSession;
+import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.ExceptionUtils;
+import android.util.LongSparseArray;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.server.LocalServices;
+import com.android.server.ServiceThread;
 import com.android.server.SystemService;
+import com.android.server.Watchdog;
+
+import java.io.IOException;
 
 /**
  * Service responsible for maintaining and facilitating access to data blobs published by apps.
  */
 public class BlobStoreManagerService extends SystemService {
 
+    private final Object mBlobsLock = new Object();
+
+    // Contains data of userId -> {sessionId -> {BlobStoreSession}}.
+    @GuardedBy("mBlobsLock")
+    private final SparseArray<LongSparseArray<BlobStoreSession>> mSessions = new SparseArray<>();
+
+    @GuardedBy("mBlobsLock")
+    private long mCurrentMaxSessionId;
+
+    // Contains data of userId -> {BlobHandle -> {BlobMetadata}}
+    @GuardedBy("mBlobsLock")
+    private final SparseArray<ArrayMap<BlobHandle, BlobMetadata>> mBlobsMap = new SparseArray<>();
+
+    private final Context mContext;
+    private final Handler mHandler;
+    private final SessionStateChangeListener mSessionStateChangeListener =
+            new SessionStateChangeListener();
+
+    private PackageManagerInternal mPackageManagerInternal;
+
     public BlobStoreManagerService(Context context) {
         super(context);
+        mContext = context;
+
+        final HandlerThread handlerThread = new ServiceThread(TAG,
+                Process.THREAD_PRIORITY_BACKGROUND, true /* allowIo */);
+        handlerThread.start();
+        mHandler = new Handler(handlerThread.getLooper());
+        Watchdog.getInstance().addThread(mHandler);
     }
 
     @Override
     public void onStart() {
         publishBinderService(Context.BLOB_STORE_SERVICE, new Stub());
+
+        mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+    }
+
+
+    @GuardedBy("mBlobsLock")
+    private long generateNextSessionIdLocked() {
+        return ++mCurrentMaxSessionId;
+    }
+
+    @GuardedBy("mBlobsLock")
+    private LongSparseArray<BlobStoreSession> getUserSessionsLocked(int userId) {
+        LongSparseArray<BlobStoreSession> userSessions = mSessions.get(userId);
+        if (userSessions == null) {
+            userSessions = new LongSparseArray<>();
+            mSessions.put(userId, userSessions);
+        }
+        return userSessions;
+    }
+
+    @GuardedBy("mBlobsLock")
+    private ArrayMap<BlobHandle, BlobMetadata> getUserBlobsLocked(int userId) {
+        ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.get(userId);
+        if (userBlobs == null) {
+            userBlobs = new ArrayMap<>();
+            mBlobsMap.put(userId, userBlobs);
+        }
+        return userBlobs;
+    }
+
+    private long createSessionInternal(BlobHandle blobHandle,
+            int callingUid, String callingPackage) {
+        synchronized (mBlobsLock) {
+            // TODO: throw if there is already an active session associated with blobHandle.
+            final long sessionId = generateNextSessionIdLocked();
+            final BlobStoreSession session = new BlobStoreSession(mContext,
+                    sessionId, blobHandle, callingUid, callingPackage,
+                    mSessionStateChangeListener);
+            getUserSessionsLocked(UserHandle.getUserId(callingUid)).put(sessionId, session);
+            // TODO: persist sessions data
+            return sessionId;
+        }
+    }
+
+    private BlobStoreSession openSessionInternal(long sessionId,
+            int callingUid, String callingPackage) {
+        final BlobStoreSession session;
+        synchronized (mBlobsLock) {
+            session = getUserSessionsLocked(
+                    UserHandle.getUserId(callingUid)).get(sessionId);
+            if (session == null || !session.hasAccess(callingUid, callingPackage)
+                    || session.isFinalized()) {
+                throw new SecurityException("Session not found: " + sessionId);
+            }
+        }
+        session.open();
+        return session;
+    }
+
+    private void deleteSessionInternal(long sessionId,
+            int callingUid, String callingPackage) {
+        synchronized (mBlobsLock) {
+            final BlobStoreSession session = openSessionInternal(sessionId,
+                    callingUid, callingPackage);
+            session.open();
+            session.abandon();
+            // TODO: persist sessions data
+        }
+    }
+
+    private ParcelFileDescriptor openBlobInternal(BlobHandle blobHandle, int callingUid,
+            String callingPackage) throws IOException {
+        synchronized (mBlobsLock) {
+            final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
+                    .get(blobHandle);
+            if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
+                    callingPackage, callingUid)) {
+                throw new SecurityException("Caller not allowed to access " + blobHandle
+                        + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
+            }
+            return blobMetadata.openForRead(callingPackage);
+        }
+    }
+
+    private void acquireLeaseInternal(BlobHandle blobHandle, int descriptionResId,
+            long leaseExpiryTimeMillis, int callingUid, String callingPackage) {
+        synchronized (mBlobsLock) {
+            final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
+                    .get(blobHandle);
+            if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
+                    callingPackage, callingUid)) {
+                throw new SecurityException("Caller not allowed to access " + blobHandle
+                        + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
+            }
+            if (leaseExpiryTimeMillis != 0 && leaseExpiryTimeMillis > blobHandle.expiryTimeMillis) {
+                throw new IllegalArgumentException(
+                        "Lease expiry cannot be later than blobs expiry time");
+            }
+            blobMetadata.addLeasee(callingPackage, callingUid,
+                    descriptionResId, leaseExpiryTimeMillis);
+            // TODO: persist blobs data
+        }
+    }
+
+    private void releaseLeaseInternal(BlobHandle blobHandle, int callingUid,
+            String callingPackage) {
+        synchronized (mBlobsLock) {
+            final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
+                    .get(blobHandle);
+            if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
+                    callingPackage, callingUid)) {
+                throw new SecurityException("Caller not allowed to access " + blobHandle
+                        + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
+            }
+            blobMetadata.removeLeasee(callingPackage, callingUid);
+        }
+    }
+
+    private void verifyCallingPackage(int callingUid, String callingPackage) {
+        if (mPackageManagerInternal.getPackageUid(
+                callingPackage, 0, UserHandle.getUserId(callingUid)) != callingUid) {
+            throw new SecurityException("Specified calling package [" + callingPackage
+                    + "] does not match the calling uid " + callingUid);
+        }
+    }
+
+    class SessionStateChangeListener {
+        public void onStateChanged(@NonNull BlobStoreSession session) {
+            mHandler.post(PooledLambda.obtainRunnable(
+                    BlobStoreManagerService::onStateChangedInternal,
+                    BlobStoreManagerService.this, session));
+        }
+    }
+
+    private void onStateChangedInternal(@NonNull BlobStoreSession session) {
+        synchronized (mBlobsLock) {
+            switch (session.getState()) {
+                case STATE_ABANDONED:
+                case STATE_VERIFIED_INVALID:
+                    session.getSessionFile().delete();
+                    getUserSessionsLocked(UserHandle.getUserId(session.ownerUid))
+                            .remove(session.sessionId);
+                    break;
+                case STATE_COMMITTED:
+                    session.verifyBlobData();
+                    break;
+                case STATE_VERIFIED_VALID:
+                    final ArrayMap<BlobHandle, BlobMetadata> userBlobs =
+                            getUserBlobsLocked(UserHandle.getUserId(session.ownerUid));
+                    BlobMetadata blob = userBlobs.get(session.blobHandle);
+                    if (blob == null) {
+                        blob = new BlobMetadata(mContext,
+                                session.sessionId, session.blobHandle);
+                        userBlobs.put(session.blobHandle, blob);
+                    }
+                    blob.addCommitter(session.ownerPackageName, session.ownerUid,
+                            session.getBlobAccessMode());
+                    // TODO: Persist blobs data.
+                    session.sendCommitCallbackResult(COMMIT_RESULT_SUCCESS);
+                    getUserSessionsLocked(UserHandle.getUserId(session.ownerUid))
+                            .remove(session.sessionId);
+                    break;
+                default:
+                    Slog.wtf(TAG, "Invalid session state: "
+                            + stateToString(session.getState()));
+            }
+            // TODO: Persist sessions data.
+        }
     }
 
     private class Stub extends IBlobStoreManager.Stub {
+        @Override
+        @IntRange(from = 1)
+        public long createSession(@NonNull BlobHandle blobHandle,
+                @NonNull String packageName) {
+            Preconditions.checkNotNull(blobHandle, "blobHandle must not be null");
+            Preconditions.checkNotNull(packageName, "packageName must not be null");
+            // TODO: verify blobHandle.algorithm is sha-256
+            // TODO: assert blobHandle is valid.
+
+            final int callingUid = Binder.getCallingUid();
+            verifyCallingPackage(callingUid, packageName);
+
+            if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp(
+                    packageName, UserHandle.getUserId(callingUid))) {
+                throw new SecurityException("Caller not allowed to create session; "
+                        + "callingUid=" + callingUid + ", callingPackage=" + packageName);
+            }
+
+            // TODO: Verify caller request is within limits (no. of calls/blob sessions/blobs)
+            return createSessionInternal(blobHandle, callingUid, packageName);
+        }
+
+        @Override
+        @NonNull
+        public IBlobStoreSession openSession(@IntRange(from = 1) long sessionId,
+                @NonNull String packageName) {
+            Preconditions.checkArgumentPositive(sessionId,
+                    "sessionId must be positive: " + sessionId);
+            Preconditions.checkNotNull(packageName, "packageName must not be null");
+
+            final int callingUid = Binder.getCallingUid();
+            verifyCallingPackage(callingUid, packageName);
+
+            return openSessionInternal(sessionId, callingUid, packageName);
+        }
+
+        @Override
+        public void deleteSession(@IntRange(from = 1) long sessionId,
+                @NonNull String packageName) {
+            Preconditions.checkArgumentPositive(sessionId,
+                    "sessionId must be positive: " + sessionId);
+            Preconditions.checkNotNull(packageName, "packageName must not be null");
+
+            final int callingUid = Binder.getCallingUid();
+            verifyCallingPackage(callingUid, packageName);
+
+            deleteSessionInternal(sessionId, callingUid, packageName);
+        }
+
+        @Override
+        public ParcelFileDescriptor openBlob(@NonNull BlobHandle blobHandle,
+                @NonNull String packageName) {
+            Preconditions.checkNotNull(blobHandle, "blobHandle must not be null");
+            Preconditions.checkNotNull(packageName, "packageName must not be null");
+
+            final int callingUid = Binder.getCallingUid();
+            verifyCallingPackage(callingUid, packageName);
+
+            if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp(
+                    packageName, UserHandle.getUserId(callingUid))) {
+                throw new SecurityException("Caller not allowed to open blob; "
+                        + "callingUid=" + callingUid + ", callingPackage=" + packageName);
+            }
+
+            try {
+                return openBlobInternal(blobHandle, callingUid, packageName);
+            } catch (IOException e) {
+                throw ExceptionUtils.wrap(e);
+            }
+        }
+
+        @Override
+        public void acquireLease(@NonNull BlobHandle blobHandle, @IdRes int descriptionResId,
+                @CurrentTimeSecondsLong long leaseTimeoutSecs, @NonNull String packageName) {
+            Preconditions.checkNotNull(blobHandle, "blobHandle must not be null");
+            Preconditions.checkNotNull(packageName, "packageName must not be null");
+
+            final int callingUid = Binder.getCallingUid();
+            verifyCallingPackage(callingUid, packageName);
+
+            acquireLeaseInternal(blobHandle, descriptionResId, leaseTimeoutSecs,
+                    callingUid, packageName);
+        }
+
+        @Override
+        public void releaseLease(@NonNull BlobHandle blobHandle, @NonNull String packageName) {
+            Preconditions.checkNotNull(blobHandle, "blobHandle must not be null");
+            Preconditions.checkNotNull(packageName, "packageName must not be null");
+
+
+            final int callingUid = Binder.getCallingUid();
+            verifyCallingPackage(callingUid, packageName);
+
+            releaseLeaseInternal(blobHandle, callingUid, packageName);
+        }
     }
 }
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
new file mode 100644
index 0000000..29092b3
--- /dev/null
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
@@ -0,0 +1,386 @@
+/*
+ * 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.blob;
+
+import static android.app.blob.BlobStoreManager.COMMIT_RESULT_ERROR;
+import static android.system.OsConstants.O_CREAT;
+import static android.system.OsConstants.O_RDWR;
+import static android.system.OsConstants.SEEK_SET;
+
+import static com.android.server.blob.BlobStoreConfig.TAG;
+
+import android.annotation.BytesLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.blob.BlobHandle;
+import android.app.blob.IBlobCommitCallback;
+import android.app.blob.IBlobStoreSession;
+import android.content.Context;
+import android.os.Binder;
+import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.RevocableFileDescriptor;
+import android.os.storage.StorageManager;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.ExceptionUtils;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+import com.android.server.blob.BlobStoreManagerService.SessionStateChangeListener;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/** TODO: add doc */
+public class BlobStoreSession extends IBlobStoreSession.Stub {
+
+    static final int STATE_OPENED = 1;
+    static final int STATE_CLOSED = 0;
+    static final int STATE_ABANDONED = 2;
+    static final int STATE_COMMITTED = 3;
+    static final int STATE_VERIFIED_VALID = 4;
+    static final int STATE_VERIFIED_INVALID = 5;
+
+    private final Object mSessionLock = new Object();
+
+    private final Context mContext;
+    private final SessionStateChangeListener mListener;
+
+    public final BlobHandle blobHandle;
+    public final long sessionId;
+    public final int ownerUid;
+    public final String ownerPackageName;
+
+    // Do not access this directly, instead use getSessionFile().
+    private File mSessionFile;
+
+    @GuardedBy("mRevocableFds")
+    private ArrayList<RevocableFileDescriptor> mRevocableFds = new ArrayList<>();
+
+    @GuardedBy("mSessionLock")
+    private int mState = STATE_CLOSED;
+
+    @GuardedBy("mSessionLock")
+    private final BlobAccessMode mBlobAccessMode = new BlobAccessMode();
+
+    @GuardedBy("mSessionLock")
+    private IBlobCommitCallback mBlobCommitCallback;
+
+    BlobStoreSession(Context context, long sessionId, BlobHandle blobHandle,
+            int ownerUid, String ownerPackageName, SessionStateChangeListener listener) {
+        this.mContext = context;
+        this.blobHandle = blobHandle;
+        this.sessionId = sessionId;
+        this.ownerUid = ownerUid;
+        this.ownerPackageName = ownerPackageName;
+        this.mListener = listener;
+    }
+
+    boolean hasAccess(int callingUid, String callingPackageName) {
+        return ownerUid == callingUid && ownerPackageName.equals(callingPackageName);
+    }
+
+    void open() {
+        synchronized (mSessionLock) {
+            if (isFinalized()) {
+                throw new IllegalStateException("Not allowed to open session with state: "
+                        + stateToString(mState));
+            }
+            mState = STATE_OPENED;
+        }
+    }
+
+    int getState() {
+        synchronized (mSessionLock) {
+            return mState;
+        }
+    }
+
+    void sendCommitCallbackResult(int result) {
+        synchronized (mSessionLock) {
+            try {
+                mBlobCommitCallback.onResult(result);
+            } catch (RemoteException e) {
+                Slog.d(TAG, "Error sending the callback result", e);
+            }
+            mBlobCommitCallback = null;
+        }
+    }
+
+    BlobAccessMode getBlobAccessMode() {
+        synchronized (mSessionLock) {
+            return mBlobAccessMode;
+        }
+    }
+
+    boolean isFinalized() {
+        synchronized (mSessionLock) {
+            return mState == STATE_COMMITTED || mState == STATE_ABANDONED;
+        }
+    }
+
+    @Override
+    @NonNull
+    public ParcelFileDescriptor openWrite(@BytesLong long offsetBytes,
+            @BytesLong long lengthBytes) {
+        assertCallerIsOwner();
+        synchronized (mSessionLock) {
+            if (mState != STATE_OPENED) {
+                throw new IllegalStateException("Not allowed to write in state: "
+                        + stateToString(mState));
+            }
+
+            try {
+                return openWriteLocked(offsetBytes, lengthBytes);
+            } catch (IOException e) {
+                throw ExceptionUtils.wrap(e);
+            }
+        }
+    }
+
+    @GuardedBy("mSessionLock")
+    @NonNull
+    private ParcelFileDescriptor openWriteLocked(@BytesLong long offsetBytes,
+            @BytesLong long lengthBytes) throws IOException {
+        // TODO: Add limit on active open sessions/writes/reads
+        FileDescriptor fd = null;
+        try {
+            final File sessionFile = getSessionFile();
+            if (sessionFile == null) {
+                throw new IllegalStateException("Couldn't get the file for this session");
+            }
+            fd = Os.open(sessionFile.getPath(), O_CREAT | O_RDWR, 0600);
+            if (offsetBytes > 0) {
+                final long curOffset = Os.lseek(fd, offsetBytes, SEEK_SET);
+                if (curOffset != offsetBytes) {
+                    throw new IllegalStateException("Failed to seek " + offsetBytes
+                            + "; curOffset=" + offsetBytes);
+                }
+            }
+            if (lengthBytes > 0) {
+                mContext.getSystemService(StorageManager.class).allocateBytes(fd, lengthBytes);
+            }
+        } catch (ErrnoException e) {
+            e.rethrowAsIOException();
+        }
+        return createRevocableFdLocked(fd);
+    }
+
+    @Override
+    @BytesLong
+    public long getSize() {
+        return 0;
+    }
+
+    @Override
+    public void allowPackageAccess(@NonNull String packageName,
+            @NonNull byte[] certificate) {
+        assertCallerIsOwner();
+        Preconditions.checkNotNull(packageName, "packageName must not be null");
+        synchronized (mSessionLock) {
+            if (mState != STATE_OPENED) {
+                throw new IllegalStateException("Not allowed to change access type in state: "
+                        + stateToString(mState));
+            }
+            mBlobAccessMode.allowPackageAccess(packageName, certificate);
+        }
+    }
+
+    @Override
+    public void allowSameSignatureAccess() {
+        assertCallerIsOwner();
+        synchronized (mSessionLock) {
+            if (mState != STATE_OPENED) {
+                throw new IllegalStateException("Not allowed to change access type in state: "
+                        + stateToString(mState));
+            }
+            mBlobAccessMode.allowSameSignatureAccess();
+        }
+    }
+
+    @Override
+    public void allowPublicAccess() {
+        assertCallerIsOwner();
+        synchronized (mSessionLock) {
+            if (mState != STATE_OPENED) {
+                throw new IllegalStateException("Not allowed to change access type in state: "
+                        + stateToString(mState));
+            }
+            mBlobAccessMode.allowPublicAccess();
+        }
+    }
+
+    @Override
+    public boolean isPackageAccessAllowed(@NonNull String packageName,
+            @NonNull byte[] certificate) {
+        assertCallerIsOwner();
+        Preconditions.checkNotNull(packageName, "packageName must not be null");
+        synchronized (mSessionLock) {
+            if (mState != STATE_OPENED) {
+                throw new IllegalStateException("Not allowed to get access type in state: "
+                        + stateToString(mState));
+            }
+            return mBlobAccessMode.isPackageAccessAllowed(packageName, certificate);
+        }
+    }
+
+    @Override
+    public boolean isSameSignatureAccessAllowed() {
+        assertCallerIsOwner();
+        synchronized (mSessionLock) {
+            if (mState != STATE_OPENED) {
+                throw new IllegalStateException("Not allowed to get access type in state: "
+                        + stateToString(mState));
+            }
+            return mBlobAccessMode.isSameSignatureAccessAllowed();
+        }
+    }
+
+    @Override
+    public boolean isPublicAccessAllowed() {
+        assertCallerIsOwner();
+        synchronized (mSessionLock) {
+            if (mState != STATE_OPENED) {
+                throw new IllegalStateException("Not allowed to get access type in state: "
+                        + stateToString(mState));
+            }
+            return mBlobAccessMode.isPublicAccessAllowed();
+        }
+    }
+
+    @Override
+    public void close() {
+        closeSession(STATE_CLOSED);
+    }
+
+    @Override
+    public void abandon() {
+        closeSession(STATE_ABANDONED);
+    }
+
+    @Override
+    public void commit(IBlobCommitCallback callback) {
+        synchronized (mSessionLock) {
+            mBlobCommitCallback = callback;
+
+            closeSession(STATE_COMMITTED);
+        }
+    }
+
+    private void closeSession(int state) {
+        assertCallerIsOwner();
+        synchronized (mSessionLock) {
+            if (mState != STATE_OPENED) {
+                if (state == STATE_CLOSED) {
+                    // Just trying to close the session which is already deleted or abandoned,
+                    // ignore.
+                    return;
+                } else {
+                    throw new IllegalStateException("Not allowed to delete or abandon a session"
+                            + " with state: " + stateToString(mState));
+                }
+            }
+
+            mState = state;
+            revokeAllFdsLocked();
+
+            mListener.onStateChanged(this);
+        }
+    }
+
+    void verifyBlobData() {
+        byte[] actualDigest = null;
+        try {
+            actualDigest = FileUtils.digest(getSessionFile(), blobHandle.algorithm);
+        } catch (IOException | NoSuchAlgorithmException e) {
+            Slog.e(TAG, "Error computing the digest", e);
+        }
+        synchronized (mSessionLock) {
+            if (actualDigest != null && Arrays.equals(actualDigest, blobHandle.digest)) {
+                mState = STATE_VERIFIED_VALID;
+                // Commit callback will be sent once the data is persisted.
+            } else {
+                mState = STATE_VERIFIED_INVALID;
+                sendCommitCallbackResult(COMMIT_RESULT_ERROR);
+            }
+            mListener.onStateChanged(this);
+        }
+    }
+
+    @GuardedBy("mSessionLock")
+    private void revokeAllFdsLocked() {
+        for (int i = mRevocableFds.size() - 1; i >= 0; --i) {
+            mRevocableFds.get(i).revoke();
+            mRevocableFds.remove(i);
+        }
+    }
+
+    @GuardedBy("mSessionLock")
+    @NonNull
+    private ParcelFileDescriptor createRevocableFdLocked(FileDescriptor fd)
+            throws IOException {
+        final RevocableFileDescriptor revocableFd =
+                new RevocableFileDescriptor(mContext, fd);
+        synchronized (mRevocableFds) {
+            mRevocableFds.add(revocableFd);
+        }
+        revocableFd.addOnCloseListener((e) -> {
+            synchronized (mRevocableFds) {
+                mRevocableFds.remove(revocableFd);
+            }
+        });
+        return revocableFd.getRevocableFileDescriptor();
+    }
+
+    @Nullable
+    File getSessionFile() {
+        if (mSessionFile == null) {
+            mSessionFile = BlobStoreConfig.prepareBlobFile(sessionId);
+        }
+        return mSessionFile;
+    }
+
+    @NonNull
+    static String stateToString(int state) {
+        switch (state) {
+            case STATE_OPENED:
+                return "<opened>";
+            case STATE_CLOSED:
+                return "<closed>";
+            case STATE_ABANDONED:
+                return "<abandoned>";
+            case STATE_COMMITTED:
+                return "<committed>";
+            default:
+                Slog.wtf(TAG, "Unknown state: " + state);
+                return "<unknown>";
+        }
+    }
+
+    private void assertCallerIsOwner() {
+        final int callingUid = Binder.getCallingUid();
+        if (callingUid != ownerUid) {
+            throw new SecurityException(ownerUid + " is not the session owner");
+        }
+    }
+}
diff --git a/apex/extservices/Android.bp b/apex/extservices/Android.bp
new file mode 100644
index 0000000..c89f694
--- /dev/null
+++ b/apex/extservices/Android.bp
@@ -0,0 +1,37 @@
+// 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.
+
+apex {
+    name: "com.android.extservices",
+    defaults: ["com.android.extservices-defaults"],
+    manifest: "apex_manifest.json",
+}
+
+apex_defaults {
+    name: "com.android.extservices-defaults",
+    key: "com.android.extservices.key",
+    certificate: ":com.android.extservices.certificate",
+    apps: ["ExtServices"],
+}
+
+apex_key {
+    name: "com.android.extservices.key",
+    public_key: "com.android.extservices.avbpubkey",
+    private_key: "com.android.extservices.pem",
+}
+
+android_app_certificate {
+    name: "com.android.extservices.certificate",
+    certificate: "com.android.extservices",
+}
diff --git a/apex/extservices/apex_manifest.json b/apex/extservices/apex_manifest.json
new file mode 100644
index 0000000..b4acf128
--- /dev/null
+++ b/apex/extservices/apex_manifest.json
@@ -0,0 +1,4 @@
+{
+  "name": "com.android.extservices",
+  "version": 300000000
+}
diff --git a/apex/extservices/com.android.extservices.avbpubkey b/apex/extservices/com.android.extservices.avbpubkey
new file mode 100644
index 0000000..f37d3e4
--- /dev/null
+++ b/apex/extservices/com.android.extservices.avbpubkey
Binary files differ
diff --git a/apex/extservices/com.android.extservices.pem b/apex/extservices/com.android.extservices.pem
new file mode 100644
index 0000000..7bfbd34
--- /dev/null
+++ b/apex/extservices/com.android.extservices.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKAIBAAKCAgEAuYshVDiRkt3tmBhqcWkKOm5GcviKpLbHSPpYQDHGDwS0dqqL
+SqAd1/BgT/bVVtUkAciFApPnXn96WhNYCypptyC5FHCxM21uBCGmow+3WermD++w
+5dQk4QP2ONPIpG+KzOWBl9SiBud4SpOHDyr0JycBsrXS89Tln9kAsTDuDEFfXL/J
+8cX/S3IUwhPV0pAlgUIHdDp0DGFjZaJlEZBZ+HmImriC/AUNUMVb5lfbczXOEZPF
+0A9+JzYschfXUxn8nu1N7RN5GDbq+chszx1FMVhuFUheukkd4dLNSDl0O0RlUnD+
+C/xz1ilDzEVZhnMtMnxS9oJ8bA/HUVMfsFnaQbgGmQ0CcxFxnfbYyGXGG1H+b8vA
+MTVQi5rZXG2p+VgHIAKVrYmpETVnRPgoMqp18KuGtp5SDngi13G3YEzS7iFbqfYh
+6iW2G974nD/Dq0cSire8Oljd9PEaMCMZiP5PTFJp0G/mtw7ROoyZqsSM6rX3XVTo
+Y5dBmBMctSJ8rgDMi0ZNvRH+rq/E5+RT6yMAJ7DDbOJzBnQ3IIoGn8NzUT3P1FCB
+HYEp1U2N7QNirIQMAuVz3IlHae9N1kl3eGAO6f2CjV7vZmFpDeWw+KSYs71mRkOb
+WBgl6D9FFq4u1azrU3AwV0dj3x1eU6yVnKUy1J7ppF/mcR+VzH7ThzTdV7cCAwEA
+AQKCAgEApWFU2Mv/PYhg0bPZlLLKsiA+3RWaBo0AfpTd+oIjBpnr/OWweFjVoPcZ
+8cyShe4/RPOlUxHgJcO8m/MoA/PO/LLHJWf5GlzMthQEgs1sYVJVtBiydXitUn+E
+hUyIR8FAV7et1lZqAXtqJhbvSF7B9u/2vIMCv+GgtuTmkAmL9RKD3Jj6eG1CS84o
+oICrkx52v4rKOBgt/icEQMAKFCi1eRti3n3eCqK6JqdzbZIcAcoQnmw34mccy/im
+jx+fBuxf1oywa8NyqVmyAehazBVL6lrm7ENwY9zuLK4H2fuUFYu2QFCEsMxZt6da
+TgX2cTfSLnDQRfcyzeMWhu9vjHHabjpLNjiCKhIhGyO0rO1rtea8ajZHgM/2sxXq
+6gLynW0dlatlxmjANlN9WQPGNdzvcIFJ0TLnI4mlJnWpqCsN9iW1d4ey13WiZUVR
+DgtnR60zao+LRCCM4D3cuVLq0DjL2BlHGXnOPK/LpQG1LbI1TroZpgSEHSZlQRzT
+ql9txgNqTHxijXuPL2VhhwhW7cqDoO8sLwV3BqDMIH56U0cbUBiSA/G9fKeI/DEG
+i7LcrMgrBk+xnuAWoFHuzfBMAdD9i3kYyk+41tOmcza2TNJgxadVYp5woHFvYvS/
+GKaNiRz0XmcijO5Ir0yxgCq21BdkWzo5zVrTFABiKeR7YXiee8kCggEBAOeULWgR
+spolJJrACWJspRvKb9FGnbGiYOnCGJoAc751kuXmNxoyWnEwgcjrSEoayNPUfOtz
+IgA+twqjgl0Zec2XFPfUcgWUBrrvvUEV4NIH5ibaR7ezHGeovCWs9XoDyzHHvhDr
+c6T5kXFZ60rS5h6LGUnE1hkHFJoHuTIBbn9j7eIbri8S71i7HWQ04s4KuQ+Bwbxm
+UnkEhbc+zMWHXfXy7rx4/eEZcZwtEybIORcHXYNPGeqMfOlcEMHpKEOi+NvDA6cp
+vTaTSwJ6ZBgYh7Tw3bNgRxSknaIhcGwMD0ojStjC5xzXT1Zr2Z3GXwYvOGcq3MeZ
+z+V2cx5xuwyp7R0CggEBAM0cKKNZEZwi/1zBPUDMFB4iJoX12BxQX6e5wdlHGXgF
+XeZwCnaIxOxMDxH79M5Svmpdu/jkUijI/pRvcE1iohFyIBvTUSDmlAoy4keXqMEQ
+M2hA+TwVA3JLmMcV8HKy/MFlwwKJB1JDcoxGjnXsM5UjVTD2jilO7vlJZs3+0ws0
+R7qzRT3ED25QTpZyDYcKE2otc5bzIZG3yAaJtWd3NugWsKpxDgr2RFUGJiHBq72n
+48FkSjfgaDTn83zYcPvS0Uykb2ho8G/N+EurstL41n3nQo0I7FLbyptOopDDwsSp
+Ndejn08NVAQ+xFAafOyqHkA3Ytpl0QCZDpMBuLdvw+MCggEAOVMt1kgjPRMat4/4
+ArxANtvqyBRB7vnyIYthiaW5ARmbrntJgpuaVdCbIABWGbn9oqpD7gjHDuZ3axPE
+roUi6KiQkTSusQDOlbHI2Haw+2znJRD9ldSpoGNdh7oD3htYTk9Sll+ideEthrCq
+lRAV1NO8A83M7c8Z43Mr/dvq3XAAL+uIN7DpPL687NRGnJh87QDC039ExR5Ad3b9
+O5xhvwNO46rTtcgVnoJt7ji8IR46oMmQ8cWrGh0nLMkppWyPS98/ZT7ozryxYcCo
+TGquFTVWvBOGJO8G8l5ytNxbYI/R9Exy52nJAuyZpvu3BBHmVWt/0Y0asIOcxZmD
+owPhZQKCAQAfWAFBzReq05JQe1s/7q/YVwGqEQKgeQvVFsbvzDSxKajK0S5YJNhq
+/8iByA4GBZEBsidKhqGjh+uXhVwVB1Ca9+S+O9G3BGV1FYeMxzlLn40rjlpH+zIW
+okTLj6e5724+o61kUspioNn9Y77beGf9j3OyUsswttZAFB54tktL+AZKGqEnKjHt
+eqo3xWAZ1clXvXBfjfIAUaRok1y8XfRvDSCcO0CZHj8c+x6SpAT5q5FbeVb6KPnj
+s9p6ppzFbtb7Llm0C+1KOKCL98YRBWPJw7Bg2w86LkpM53xiQPgfk3gd5uwuaWwA
+ZhMb5qBWjjynNY+OrmZ8/+bBQk8XASZfAoIBAFkHOnZOD1JJQ0QvaJ9tuCgHi216
+I8QPMMTdm3ZEDHSYMNwl7ayeseBcmB2zaqBKYz75qcU0SK4lnZkR2wIpbsHZNSVM
+J0WpN6r9G4JdnVi11J04RsfSMjCUr/PTVMmPvw8xPHrCxkJmB+d56olSE80I1Jrx
+djCv1LtSsT10W7FIcY82/cOi4xxGLOA70lDCf+szofQgVP8WvuOA1YaFw98ca8zc
+A401CyNexk24/c3d6C19YW/MppdE0uGMxL/oHsPgwkZAf6LmvF/UF71PsBUEniLc
+YFaJl3wn1cPfBBo9L4sZzyP2qokL8YHdg+wW7b4IOsYwbeqceBvqPtcUUPs=
+-----END RSA PRIVATE KEY-----
diff --git a/apex/extservices/com.android.extservices.pk8 b/apex/extservices/com.android.extservices.pk8
new file mode 100644
index 0000000..59585a2
--- /dev/null
+++ b/apex/extservices/com.android.extservices.pk8
Binary files differ
diff --git a/apex/extservices/com.android.extservices.x509.pem b/apex/extservices/com.android.extservices.x509.pem
new file mode 100644
index 0000000..e0343b8
--- /dev/null
+++ b/apex/extservices/com.android.extservices.x509.pem
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIGLTCCBBWgAwIBAgIUdqdMmx/5OsCP3Ew3/hcr7+1ACHEwDQYJKoZIhvcNAQEL
+BQAwgaQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
+DA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRy
+b2lkMSAwHgYDVQQDDBdjb20uYW5kcm9pZC5leHRzZXJ2aWNlczEiMCAGCSqGSIb3
+DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAgFw0yMDAxMTcxMDIxMzZaGA80NzU3
+MTIxMzEwMjEzNlowgaQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh
+MRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYD
+VQQLDAdBbmRyb2lkMSAwHgYDVQQDDBdjb20uYW5kcm9pZC5leHRzZXJ2aWNlczEi
+MCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCAiIwDQYJKoZIhvcN
+AQEBBQADggIPADCCAgoCggIBANKaSeLGaFRRt779vAtTfG3t2aQZrWOByUYc7yUN
+RdmJqWxU47OL5urYmanWPbz2f972Q9oi8x+8y4ny9SEY3wg0pUbzvKNTXpkxWyG1
+HE2C2zTfzuDDLpDIf2usWynt1wLVhpYC3k+7Yv2vOIK5dKkezh6PfdKmsbDae5DE
+d22tTSYZ5KwNpIWrgQle26cRG5sqhAFdkpgGMF00Huz06cjUoTjs2sNSlXTRBOTP
+CCy8UoRjBivQZkwHbddfsn+Z22ARPG8JDg/n4mEi8C0T6bJeQeirSPkBCkD6Djgq
+7RddJ2eLYZII8l8r6A6x+6cnTkXHaV5g3LUwPvi8XEn9IUuT9WJNRje/vfYLycTQ
+kP415CZMxDvsi1Ul4YsbL3enE89ryGMTpVZPogch/36DG5Sye28yISItNUy3urJa
+OXbg7mh+MwPd4bQaW4CJk+AUweKaF4aV0SZFT+nCewL4xLdGdy889KazlW98NqtK
+hOSxIg1jHkZq48ajuq2A+ns1yDKt1l0f9IYCz3mz/IXInokbkjPvHahJTJ+OMHXO
+THD8e5gBzcK841jJk+H3EsIYOHsp66uy2IgEHN+9pAS6vI0xfrXOYuKzuSL3oxcV
+FlVTimt4xokMMerdcW4KD+MC5NFEip4DUS4JKCyG0wRI3ffEs9Zcpxi3QSibrjLW
+rz+hAgMBAAGjUzBRMB0GA1UdDgQWBBTP2AhZzEUUgtAFlkaMaq+RvY06fDAfBgNV
+HSMEGDAWgBTP2AhZzEUUgtAFlkaMaq+RvY06fDAPBgNVHRMBAf8EBTADAQH/MA0G
+CSqGSIb3DQEBCwUAA4ICAQCbwtfo37j62Sudmt32PCfRN/r5ZNDNNA2JhR8uDUmX
+xXfF5YfDvSKsNLiQKcDagu6a+0C+QnzXHXCBlXZFrTJ8NAVMlmqdHGwoFoYMfJZH
+R1lCTidyFMoMLJ8GRGPJjzDkKnOeAqKMCtKvXoH2r12+JB2/ov4ooLREu/wPkEXT
+OymkyWNP5XLQTKWqfEQyXXFpuwZ+m35Wkr0Fm92mZeJpVeIZPK7M7aK3zyoj7XJP
+YLMsR/AQs8OULdpfNMddAuN3ndlYu03LZlsF6LG5bduaDDcESJ5hdJrgBa/NBKRU
+IbS+q/6WAjYKMNRT/fPGew4wUzlWKi1Ihdk79oaqKKijE1b2JSJD1/SEYiBf+JPE
+bXobUrMbBwFpdhT+YLMF9FsuPQKsUIONaWiO4QcQoY/rQwGxPP6fV8ZbBrUWJewj
+MpSdU9foZNa/TmOAgfS/JxH+nXnG4+H1m8mdNBsxvsYmF2ZuGb/jdEeA2cuHIJDZ
+FJeWwCFxzlCGZJaUsxsnZByADBuufUVaO/9gGs0YQC/JP1i9hK4DyZdKwZpXdLi2
+Nw27Qma4WEIZnMb6Rgk1nTV+7ALcOSIhGgFOOeDTuCGfnEcz2coai5fbD/K6Q7Xu
+IRNyxHQjheZPdei2x912Ex/KqKGfaFaZJxrvCSKdhzxcTFIsO4JuZs+SDpRTKcI7
+Cw==
+-----END CERTIFICATE-----
diff --git a/apex/jobscheduler/framework/Android.bp b/apex/jobscheduler/framework/Android.bp
index 98bbe82..ec07426 100644
--- a/apex/jobscheduler/framework/Android.bp
+++ b/apex/jobscheduler/framework/Android.bp
@@ -25,5 +25,6 @@
     },
     libs: [
         "framework-minus-apex",
+        "unsupportedappusage",
     ],
 }
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 8b3b3a2..0bb07ca 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -29,7 +29,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ClipData;
 import android.content.ComponentName;
 import android.net.NetworkRequest;
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index 42cf17b..b96161a 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -18,8 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
-import android.app.job.IJobCallback;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ClipData;
 import android.net.Network;
 import android.net.Uri;
@@ -49,6 +48,13 @@
     public static final int REASON_DEVICE_IDLE = JobProtoEnums.STOP_REASON_DEVICE_IDLE; // 4.
     /** @hide */
     public static final int REASON_DEVICE_THERMAL = JobProtoEnums.STOP_REASON_DEVICE_THERMAL; // 5.
+    /**
+     * The job is in the {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED}
+     * bucket.
+     *
+     * @hide
+     */
+    public static final int REASON_RESTRAINED = JobProtoEnums.STOP_REASON_RESTRAINED; // 6.
 
     /**
      * All the stop reason codes. This should be regarded as an immutable array at runtime.
@@ -66,6 +72,7 @@
             REASON_TIMEOUT,
             REASON_DEVICE_IDLE,
             REASON_DEVICE_THERMAL,
+            REASON_RESTRAINED,
     };
 
     /**
@@ -81,6 +88,7 @@
             case REASON_TIMEOUT: return "timeout";
             case REASON_DEVICE_IDLE: return "device_idle";
             case REASON_DEVICE_THERMAL: return "thermal";
+            case REASON_RESTRAINED: return "restrained";
             default: return "unknown:" + reason;
         }
     }
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobWorkItem.java b/apex/jobscheduler/framework/java/android/app/job/JobWorkItem.java
index c6631fa..0c45cbf 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobWorkItem.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobWorkItem.java
@@ -19,7 +19,7 @@
 import static android.app.job.JobInfo.NETWORK_BYTES_UNKNOWN;
 
 import android.annotation.BytesLong;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Intent;
 import android.os.Build;
 import android.os.Parcel;
diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
index 041825c..d2d942a 100644
--- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
@@ -1,5 +1,6 @@
 package com.android.server.usage;
 
+import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.app.usage.AppStandbyInfo;
 import android.app.usage.UsageEvents;
@@ -99,8 +100,31 @@
 
     List<AppStandbyInfo> getAppStandbyBuckets(int userId);
 
-    void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket,
-            int reason, long elapsedRealtime, boolean resetTimeout);
+    /**
+     * Changes an app's standby bucket to the provided value. The caller can only set the standby
+     * bucket for a different app than itself.
+     * If attempting to automatically place an app in the RESTRICTED bucket, use
+     * {@link #restrictApp(String, int, int)} instead.
+     */
+    void setAppStandbyBucket(@NonNull String packageName, int bucket, int userId, int callingUid,
+            int callingPid);
+
+    /**
+     * Changes the app standby bucket for multiple apps at once.
+     */
+    void setAppStandbyBuckets(@NonNull List<AppStandbyInfo> appBuckets, int userId, int callingUid,
+            int callingPid);
+
+    /**
+     * Put the specified app in the
+     * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED}
+     * bucket. If it has been used by the user recently, the restriction will delayed until an
+     * appropriate time.
+     *
+     * @param restrictReason The restrictReason for restricting the app. Should be one of the
+     *                       UsageStatsManager.REASON_SUB_RESTRICT_* reasons.
+     */
+    void restrictApp(@NonNull String packageName, int userId, int restrictReason);
 
     void addActiveDeviceAdmin(String adminPkg, int userId);
 
diff --git a/apex/jobscheduler/service/Android.bp b/apex/jobscheduler/service/Android.bp
index c9d9d6c..69a9fd8 100644
--- a/apex/jobscheduler/service/Android.bp
+++ b/apex/jobscheduler/service/Android.bp
@@ -9,6 +9,7 @@
     ],
 
     libs: [
+        "app-compat-annotations",
         "framework",
         "services.core",
     ],
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 3f58c72..f8b2f32 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -761,6 +761,7 @@
         @Override
         public void onTrigger(TriggerEvent event) {
             synchronized (DeviceIdleController.this) {
+                // One_shot sensors (which call onTrigger) are unregistered when onTrigger is called
                 active = false;
                 motionLocked();
             }
@@ -769,6 +770,9 @@
         @Override
         public void onSensorChanged(SensorEvent event) {
             synchronized (DeviceIdleController.this) {
+                // Since one_shot sensors are unregistered when onTrigger is called, unregister
+                // listeners here so that the MotionListener is in a consistent state when it calls
+                // out to motionLocked.
                 mSensorManager.unregisterListener(this, mMotionSensor);
                 active = false;
                 motionLocked();
diff --git a/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING
new file mode 100644
index 0000000..8fbfb1d
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING
@@ -0,0 +1,22 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksMockingServicesTests",
+      "file_patterns": [
+        "DeviceIdleController\\.java"
+      ],
+      "options": [
+        {"include-filter": "com.android.server.DeviceIdleControllerTest"},
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+      ]
+    }
+  ],
+  "postsubmit": [
+    {
+      "name": "FrameworksMockingServicesTests",
+      "options": [
+        {"include-filter": "com.android.server"}
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING
new file mode 100644
index 0000000..bc7a7d3
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING
@@ -0,0 +1,19 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksMockingServicesTests",
+      "options": [
+        {"include-filter": "com.android.server.DeviceIdleControllerTest"},
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+      ]
+    }
+  ],
+  "postsubmit": [
+    {
+      "name": "FrameworksMockingServicesTests",
+      "options": [
+        {"include-filter": "com.android.server"}
+      ]
+    }
+  ]
+}
\ No newline at end of file
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 9310762..ed5626a 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -37,12 +37,15 @@
 import android.app.job.JobWorkItem;
 import android.app.usage.UsageStatsManager;
 import android.app.usage.UsageStatsManagerInternal;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 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.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -54,6 +57,7 @@
 import android.os.BatteryStats;
 import android.os.BatteryStatsInternal;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -67,6 +71,7 @@
 import android.os.WorkSource;
 import android.provider.Settings;
 import android.text.format.DateUtils;
+import android.util.ArrayMap;
 import android.util.KeyValueListParser;
 import android.util.Log;
 import android.util.Slog;
@@ -85,6 +90,7 @@
 import com.android.server.DeviceIdleInternal;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
+import com.android.server.compat.PlatformCompat;
 import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob;
 import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob;
 import com.android.server.job.controllers.BackgroundJobsController;
@@ -95,6 +101,7 @@
 import com.android.server.job.controllers.IdleController;
 import com.android.server.job.controllers.JobStatus;
 import com.android.server.job.controllers.QuotaController;
+import com.android.server.job.controllers.RestrictingController;
 import com.android.server.job.controllers.StateController;
 import com.android.server.job.controllers.StorageController;
 import com.android.server.job.controllers.TimeController;
@@ -102,6 +109,9 @@
 import com.android.server.job.restrictions.ThermalStatusRestriction;
 import com.android.server.usage.AppStandbyInternal;
 import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
+import com.android.server.utils.quota.Categorizer;
+import com.android.server.utils.quota.Category;
+import com.android.server.utils.quota.CountQuotaTracker;
 
 import libcore.util.EmptyArray;
 
@@ -145,6 +155,16 @@
     /** The maximum number of jobs that we allow an unprivileged app to schedule */
     private static final int MAX_JOBS_PER_APP = 100;
 
+    /**
+     * {@link #schedule(JobInfo)}, {@link #scheduleAsPackage(JobInfo, String, int, String)}, and
+     * {@link #enqueue(JobInfo, JobWorkItem)} will throw a {@link IllegalStateException} if the app
+     * calls the APIs too frequently.
+     */
+    @ChangeId
+    // This means the change will be enabled for target SDK larger than 29 (Q), meaning R and up.
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+    protected static final long CRASH_ON_EXCEEDED_LIMIT = 144363383L;
+
     @VisibleForTesting
     public static Clock sSystemClock = Clock.systemUTC();
 
@@ -222,6 +242,11 @@
 
     /** List of controllers that will notify this service of updates to jobs. */
     final List<StateController> mControllers;
+    /**
+     * List of controllers that will apply to all jobs in the RESTRICTED bucket. This is a subset of
+     * {@link #mControllers}.
+     */
+    private final List<RestrictingController> mRestrictiveControllers;
     /** Need direct access to this for testing. */
     private final BatteryController mBatteryController;
     /** Need direct access to this for testing. */
@@ -237,6 +262,10 @@
      */
     private final List<JobRestriction> mJobRestrictions;
 
+    private final CountQuotaTracker mQuotaTracker;
+    private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()";
+    private final PlatformCompat mPlatformCompat;
+
     /**
      * Queue of pending jobs. The JobServiceContext class will receive jobs from this list
      * when ready to execute them.
@@ -254,6 +283,7 @@
     DeviceIdleInternal mLocalDeviceIdleController;
     AppStateTracker mAppStateTracker;
     final UsageStatsManagerInternal mUsageStats;
+    private final AppStandbyInternal mAppStandbyInternal;
 
     /**
      * Set to true once we are allowed to run third party apps.
@@ -276,6 +306,11 @@
     final SparseIntArray mBackingUpUids = new SparseIntArray();
 
     /**
+     * Cache of debuggable app status.
+     */
+    final ArrayMap<String, Boolean> mDebuggableApps = new ArrayMap<>();
+
+    /**
      * Named indices into standby bucket arrays, for clarity in referring to
      * specific buckets' bookkeeping.
      */
@@ -284,6 +319,9 @@
     public static final int FREQUENT_INDEX = 2;
     public static final int RARE_INDEX = 3;
     public static final int NEVER_INDEX = 4;
+    // Putting RESTRICTED_INDEX after NEVER_INDEX to make it easier for proto dumping
+    // (ScheduledJobStateChanged and JobStatusDumpProto).
+    public static final int RESTRICTED_INDEX = 5;
 
     // -- Pre-allocated temporaries only for use in assignJobsToContextsLocked --
 
@@ -315,6 +353,10 @@
                         final StateController sc = mControllers.get(controller);
                         sc.onConstantsUpdatedLocked();
                     }
+                    mQuotaTracker.setEnabled(mConstants.ENABLE_API_QUOTAS);
+                    mQuotaTracker.setCountLimit(Category.SINGLE_CATEGORY,
+                            mConstants.API_QUOTA_SCHEDULE_COUNT,
+                            mConstants.API_QUOTA_SCHEDULE_WINDOW_MS);
                 } catch (IllegalArgumentException e) {
                     // Failed to parse the settings string, log this and move on
                     // with defaults.
@@ -466,6 +508,11 @@
         private static final String KEY_CONN_CONGESTION_DELAY_FRAC = "conn_congestion_delay_frac";
         private static final String KEY_CONN_PREFETCH_RELAX_FRAC = "conn_prefetch_relax_frac";
         private static final String DEPRECATED_KEY_USE_HEARTBEATS = "use_heartbeats";
+        private static final String KEY_ENABLE_API_QUOTAS = "enable_api_quotas";
+        private static final String KEY_API_QUOTA_SCHEDULE_COUNT = "aq_schedule_count";
+        private static final String KEY_API_QUOTA_SCHEDULE_WINDOW_MS = "aq_schedule_window_ms";
+        private static final String KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION =
+                "aq_schedule_throw_exception";
 
         private static final int DEFAULT_MIN_IDLE_COUNT = 1;
         private static final int DEFAULT_MIN_CHARGING_COUNT = 1;
@@ -484,6 +531,10 @@
         private static final long DEFAULT_MIN_EXP_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS;
         private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f;
         private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f;
+        private static final boolean DEFAULT_ENABLE_API_QUOTAS = true;
+        private static final int DEFAULT_API_QUOTA_SCHEDULE_COUNT = 250;
+        private static final long DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS = MINUTE_IN_MILLIS;
+        private static final boolean DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION = true;
 
         /**
          * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
@@ -618,6 +669,24 @@
          */
         public float CONN_PREFETCH_RELAX_FRAC = DEFAULT_CONN_PREFETCH_RELAX_FRAC;
 
+        /**
+         * Whether to enable quota limits on APIs.
+         */
+        public boolean ENABLE_API_QUOTAS = DEFAULT_ENABLE_API_QUOTAS;
+        /**
+         * The maximum number of schedule() calls an app can make in a set amount of time.
+         */
+        public int API_QUOTA_SCHEDULE_COUNT = DEFAULT_API_QUOTA_SCHEDULE_COUNT;
+        /**
+         * The time window that {@link #API_QUOTA_SCHEDULE_COUNT} should be evaluated over.
+         */
+        public long API_QUOTA_SCHEDULE_WINDOW_MS = DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS;
+        /**
+         * Whether to throw an exception when an app hits its schedule quota limit.
+         */
+        public boolean API_QUOTA_SCHEDULE_THROW_EXCEPTION =
+                DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION;
+
         private final KeyValueListParser mParser = new KeyValueListParser(',');
 
         void updateConstantsLocked(String value) {
@@ -678,6 +747,18 @@
                     DEFAULT_CONN_CONGESTION_DELAY_FRAC);
             CONN_PREFETCH_RELAX_FRAC = mParser.getFloat(KEY_CONN_PREFETCH_RELAX_FRAC,
                     DEFAULT_CONN_PREFETCH_RELAX_FRAC);
+
+            ENABLE_API_QUOTAS = mParser.getBoolean(KEY_ENABLE_API_QUOTAS,
+                DEFAULT_ENABLE_API_QUOTAS);
+            // Set a minimum value on the quota limit so it's not so low that it interferes with
+            // legitimate use cases.
+            API_QUOTA_SCHEDULE_COUNT = Math.max(250,
+                    mParser.getInt(KEY_API_QUOTA_SCHEDULE_COUNT, DEFAULT_API_QUOTA_SCHEDULE_COUNT));
+            API_QUOTA_SCHEDULE_WINDOW_MS = mParser.getDurationMillis(
+                KEY_API_QUOTA_SCHEDULE_WINDOW_MS, DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS);
+            API_QUOTA_SCHEDULE_THROW_EXCEPTION = mParser.getBoolean(
+                    KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION,
+                    DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION);
         }
 
         void dump(IndentingPrintWriter pw) {
@@ -716,6 +797,12 @@
             pw.printPair(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
             pw.printPair(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println();
 
+            pw.printPair(KEY_ENABLE_API_QUOTAS, ENABLE_API_QUOTAS).println();
+            pw.printPair(KEY_API_QUOTA_SCHEDULE_COUNT, API_QUOTA_SCHEDULE_COUNT).println();
+            pw.printPair(KEY_API_QUOTA_SCHEDULE_WINDOW_MS, API_QUOTA_SCHEDULE_WINDOW_MS).println();
+            pw.printPair(KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION,
+                    API_QUOTA_SCHEDULE_THROW_EXCEPTION).println();
+
             pw.decreaseIndent();
         }
 
@@ -746,6 +833,12 @@
             proto.write(ConstantsProto.MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME);
             proto.write(ConstantsProto.CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC);
             proto.write(ConstantsProto.CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC);
+
+            proto.write(ConstantsProto.ENABLE_API_QUOTAS, ENABLE_API_QUOTAS);
+            proto.write(ConstantsProto.API_QUOTA_SCHEDULE_COUNT, API_QUOTA_SCHEDULE_COUNT);
+            proto.write(ConstantsProto.API_QUOTA_SCHEDULE_WINDOW_MS, API_QUOTA_SCHEDULE_WINDOW_MS);
+            proto.write(ConstantsProto.API_QUOTA_SCHEDULE_THROW_EXCEPTION,
+                    API_QUOTA_SCHEDULE_THROW_EXCEPTION);
         }
     }
 
@@ -847,6 +940,7 @@
                         for (int c = 0; c < mControllers.size(); ++c) {
                             mControllers.get(c).onAppRemovedLocked(pkgName, pkgUid);
                         }
+                        mDebuggableApps.remove(pkgName);
                     }
                 }
             } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
@@ -972,6 +1066,50 @@
 
     public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName,
             int userId, String tag) {
+        if (job.isPersisted()) {
+            // Only limit schedule calls for persisted jobs.
+            final String pkg =
+                    packageName == null ? job.getService().getPackageName() : packageName;
+            if (!mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG)) {
+                Slog.e(TAG, userId + "-" + pkg + " has called schedule() too many times");
+                mAppStandbyInternal.restrictApp(
+                        pkg, userId, UsageStatsManager.REASON_SUB_RESTRICT_BUGGY);
+                if (mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION
+                        && mPlatformCompat.isChangeEnabledByPackageName(
+                                CRASH_ON_EXCEEDED_LIMIT, pkg, userId)) {
+                    final boolean isDebuggable;
+                    synchronized (mLock) {
+                        if (!mDebuggableApps.containsKey(packageName)) {
+                            try {
+                                final ApplicationInfo appInfo = AppGlobals.getPackageManager()
+                                        .getApplicationInfo(pkg, 0, userId);
+                                if (appInfo != null) {
+                                    mDebuggableApps.put(packageName,
+                                            (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0);
+                                } else {
+                                    return JobScheduler.RESULT_FAILURE;
+                                }
+                            } catch (RemoteException e) {
+                                throw new RuntimeException(e);
+                            }
+                        }
+                        isDebuggable = mDebuggableApps.get(packageName);
+                    }
+                    if (isDebuggable) {
+                        // Only throw the exception for debuggable apps.
+                        throw new IllegalStateException(
+                                "schedule()/enqueue() called more than "
+                                        + mQuotaTracker.getLimit(Category.SINGLE_CATEGORY)
+                                        + " times in the past "
+                                        + mQuotaTracker.getWindowSizeMs(Category.SINGLE_CATEGORY)
+                                        + "ms");
+                    }
+                }
+                return JobScheduler.RESULT_FAILURE;
+            }
+            mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG);
+        }
+
         try {
             if (ActivityManager.getService().isAppStartModeDisabled(uId,
                     job.getService().getPackageName())) {
@@ -1238,6 +1376,40 @@
         }
     }
 
+    @Override
+    public void onRestrictedBucketChanged(List<JobStatus> jobs) {
+        final int len = jobs.size();
+        if (len == 0) {
+            Slog.wtf(TAG, "onRestrictedBucketChanged called with no jobs");
+            return;
+        }
+        synchronized (mLock) {
+            for (int i = 0; i < len; ++i) {
+                JobStatus js = jobs.get(i);
+                for (int j = mRestrictiveControllers.size() - 1; j >= 0; --j) {
+                    // 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 (js.getStandbyBucket() == RESTRICTED_INDEX) {
+                        js.addDynamicConstraint(JobStatus.CONSTRAINT_BATTERY_NOT_LOW);
+                        js.addDynamicConstraint(JobStatus.CONSTRAINT_CHARGING);
+                        js.addDynamicConstraint(JobStatus.CONSTRAINT_CONNECTIVITY);
+                        js.addDynamicConstraint(JobStatus.CONSTRAINT_IDLE);
+
+                        mRestrictiveControllers.get(j).startTrackingRestrictedJobLocked(js);
+                    } else {
+                        js.removeDynamicConstraint(JobStatus.CONSTRAINT_BATTERY_NOT_LOW);
+                        js.removeDynamicConstraint(JobStatus.CONSTRAINT_CHARGING);
+                        js.removeDynamicConstraint(JobStatus.CONSTRAINT_CONNECTIVITY);
+                        js.removeDynamicConstraint(JobStatus.CONSTRAINT_IDLE);
+
+                        mRestrictiveControllers.get(j).stopTrackingRestrictedJobLocked(js);
+                    }
+                }
+            }
+        }
+        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
+    }
+
     void reportActiveLocked() {
         // active is true if pending queue contains jobs OR some job is running.
         boolean active = mPendingJobs.size() > 0;
@@ -1296,9 +1468,15 @@
         // Set up the app standby bucketing tracker
         mStandbyTracker = new StandbyTracker();
         mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
+        mPlatformCompat =
+                (PlatformCompat) ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE);
+        mQuotaTracker = new CountQuotaTracker(context, Categorizer.SINGLE_CATEGORIZER);
+        mQuotaTracker.setCountLimit(Category.SINGLE_CATEGORY,
+                mConstants.API_QUOTA_SCHEDULE_COUNT,
+                mConstants.API_QUOTA_SCHEDULE_WINDOW_MS);
 
-        AppStandbyInternal appStandby = LocalServices.getService(AppStandbyInternal.class);
-        appStandby.addListener(mStandbyTracker);
+        mAppStandbyInternal = LocalServices.getService(AppStandbyInternal.class);
+        mAppStandbyInternal.addListener(mStandbyTracker);
 
         // The job store needs to call back
         publishLocalService(JobSchedulerInternal.class, new LocalService());
@@ -1308,9 +1486,11 @@
 
         // Create the controllers.
         mControllers = new ArrayList<StateController>();
-        mControllers.add(new ConnectivityController(this));
+        final ConnectivityController connectivityController = new ConnectivityController(this);
+        mControllers.add(connectivityController);
         mControllers.add(new TimeController(this));
-        mControllers.add(new IdleController(this));
+        final IdleController idleController = new IdleController(this);
+        mControllers.add(idleController);
         mBatteryController = new BatteryController(this);
         mControllers.add(mBatteryController);
         mStorageController = new StorageController(this);
@@ -1322,6 +1502,11 @@
         mQuotaController = new QuotaController(this);
         mControllers.add(mQuotaController);
 
+        mRestrictiveControllers = new ArrayList<>();
+        mRestrictiveControllers.add(mBatteryController);
+        mRestrictiveControllers.add(connectivityController);
+        mRestrictiveControllers.add(idleController);
+
         // Create restrictions
         mJobRestrictions = new ArrayList<>();
         mJobRestrictions.add(new ThermalStatusRestriction(this));
@@ -1992,11 +2177,13 @@
                     }
                 } catch (RemoteException e) {
                 }
-                if (mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT > 1
+                // Restricted jobs must always be batched
+                if (job.getEffectiveStandbyBucket() == RESTRICTED_INDEX
+                        || (mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT > 1
                         && job.getEffectiveStandbyBucket() != ACTIVE_INDEX
                         && (job.getFirstForceBatchedTimeElapsed() == 0
                         || sElapsedRealtimeClock.millis() - job.getFirstForceBatchedTimeElapsed()
-                                < mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS)) {
+                                < mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS))) {
                     // Force batching non-ACTIVE jobs. Don't include them in the other counts.
                     forceBatchedCount++;
                     if (job.getFirstForceBatchedTimeElapsed() == 0) {
@@ -2403,11 +2590,19 @@
 
     public static int standbyBucketToBucketIndex(int bucket) {
         // Normalize AppStandby constants to indices into our bookkeeping
-        if (bucket == UsageStatsManager.STANDBY_BUCKET_NEVER) return NEVER_INDEX;
-        else if (bucket > UsageStatsManager.STANDBY_BUCKET_FREQUENT) return RARE_INDEX;
-        else if (bucket > UsageStatsManager.STANDBY_BUCKET_WORKING_SET) return FREQUENT_INDEX;
-        else if (bucket > UsageStatsManager.STANDBY_BUCKET_ACTIVE) return WORKING_INDEX;
-        else return ACTIVE_INDEX;
+        if (bucket == UsageStatsManager.STANDBY_BUCKET_NEVER) {
+            return NEVER_INDEX;
+        } else if (bucket > UsageStatsManager.STANDBY_BUCKET_RARE) {
+            return RESTRICTED_INDEX;
+        } else if (bucket > UsageStatsManager.STANDBY_BUCKET_FREQUENT) {
+            return RARE_INDEX;
+        } else if (bucket > UsageStatsManager.STANDBY_BUCKET_WORKING_SET) {
+            return FREQUENT_INDEX;
+        } else if (bucket > UsageStatsManager.STANDBY_BUCKET_ACTIVE) {
+            return WORKING_INDEX;
+        } else {
+            return ACTIVE_INDEX;
+        }
     }
 
     // Static to support external callers
@@ -2745,7 +2940,7 @@
                 return new ParceledListSlice<>(snapshots);
             }
         }
-    };
+    }
 
     // Shell command infrastructure: run the given job immediately
     int executeRunCommand(String pkgName, int userId, int jobId, boolean force) {
@@ -2968,6 +3163,10 @@
         return 0;
     }
 
+    void resetScheduleQuota() {
+        mQuotaTracker.clear();
+    }
+
     void triggerDockState(boolean idleState) {
         final Intent dockIntent;
         if (idleState) {
@@ -3030,6 +3229,9 @@
             }
             pw.println();
 
+            mQuotaTracker.dump(pw);
+            pw.println();
+
             pw.println("Started users: " + Arrays.toString(mStartedUsers));
             pw.print("Registered ");
             pw.print(mJobs.size());
@@ -3217,6 +3419,9 @@
             for (int u : mStartedUsers) {
                 proto.write(JobSchedulerServiceDumpProto.STARTED_USERS, u);
             }
+
+            mQuotaTracker.dump(proto, JobSchedulerServiceDumpProto.QUOTA_TRACKER);
+
             if (mJobs.size() > 0) {
                 final List<JobStatus> jobs = mJobs.mJobSet.getAllJobs();
                 sortJobs(jobs);
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 a5c6c01..6becf04 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -66,6 +66,8 @@
                     return getJobState(pw);
                 case "heartbeat":
                     return doHeartbeat(pw);
+                case "reset-schedule-quota":
+                    return resetScheduleQuota(pw);
                 case "trigger-dock-state":
                     return triggerDockState(pw);
                 default:
@@ -344,6 +346,18 @@
         return -1;
     }
 
+    private int resetScheduleQuota(PrintWriter pw) throws Exception {
+        checkPermission("reset schedule quota");
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mInternal.resetScheduleQuota();
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+        return 0;
+    }
+
     private int triggerDockState(PrintWriter pw) throws Exception {
         checkPermission("trigger wireless charging dock state");
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java b/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java
index 87bfc27..cb3c437 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java
@@ -16,8 +16,12 @@
 
 package com.android.server.job;
 
+import android.annotation.NonNull;
+
 import com.android.server.job.controllers.JobStatus;
 
+import java.util.List;
+
 /**
  * Interface through which a {@link com.android.server.job.controllers.StateController} informs
  * the {@link com.android.server.job.JobSchedulerService} that there are some tasks potentially
@@ -39,4 +43,10 @@
     public void onRunJobNow(JobStatus jobStatus);
 
     public void onDeviceIdleStateChanged(boolean deviceIdle);
+
+    /**
+     * Called when these jobs are added or removed from the
+     * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} bucket.
+     */
+    void onRestrictedBucketChanged(@NonNull List<JobStatus> jobs);
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
index 46658ad..461ef21 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
@@ -43,7 +43,7 @@
  * be charging when it's been plugged in for more than two minutes, and the system has broadcast
  * ACTION_BATTERY_OK.
  */
-public final class BatteryController extends StateController {
+public final class BatteryController extends RestrictingController {
     private static final String TAG = "JobScheduler.Battery";
     private static final boolean DEBUG = JobSchedulerService.DEBUG
             || Log.isLoggable(TAG, Log.DEBUG);
@@ -73,12 +73,24 @@
     }
 
     @Override
+    public void startTrackingRestrictedJobLocked(JobStatus jobStatus) {
+        maybeStartTrackingJobLocked(jobStatus, null);
+    }
+
+    @Override
     public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate) {
         if (taskStatus.clearTrackingController(JobStatus.TRACKING_BATTERY)) {
             mTrackedTasks.remove(taskStatus);
         }
     }
 
+    @Override
+    public void stopTrackingRestrictedJobLocked(JobStatus jobStatus) {
+        if (!jobStatus.hasPowerConstraint()) {
+            maybeStopTrackingJobLocked(jobStatus, null, false);
+        }
+    }
+
     private void maybeReportNewChargingStateLocked() {
         final boolean stablePower = mChargeTracker.isOnStablePower();
         final boolean batteryNotLow = mChargeTracker.isBatteryNotLow();
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 8eeea1b..a0e83da 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
@@ -20,6 +20,8 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 
+import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
+
 import android.app.job.JobInfo;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
@@ -63,7 +65,7 @@
  *
  * Test: atest com.android.server.job.controllers.ConnectivityControllerTest
  */
-public final class ConnectivityController extends StateController implements
+public final class ConnectivityController extends RestrictingController implements
         ConnectivityManager.OnNetworkActiveListener {
     private static final String TAG = "JobScheduler.Connectivity";
     private static final boolean DEBUG = JobSchedulerService.DEBUG
@@ -138,8 +140,22 @@
         }
     }
 
+    @Override
+    public void startTrackingRestrictedJobLocked(JobStatus jobStatus) {
+        // Don't need to start tracking the job. If the job needed network, it would already be
+        // tracked.
+        updateConstraintsSatisfied(jobStatus);
+    }
+
+    @Override
+    public void stopTrackingRestrictedJobLocked(JobStatus jobStatus) {
+        // Shouldn't stop tracking the job here. If the job was tracked, it still needs network,
+        // even after being unrestricted.
+        updateConstraintsSatisfied(jobStatus);
+    }
+
     /**
-     * Returns true if the job's requested network is available. This DOES NOT necesarilly mean
+     * Returns true if the job's requested network is available. This DOES NOT necessarily mean
      * that the UID has been granted access to the network.
      */
     public boolean isNetworkAvailable(JobStatus job) {
@@ -353,14 +369,24 @@
 
     private static boolean isStrictSatisfied(JobStatus jobStatus, Network network,
             NetworkCapabilities capabilities, Constants constants) {
-        return jobStatus.getJob().getRequiredNetwork().networkCapabilities
-                .satisfiedByNetworkCapabilities(capabilities);
+        final NetworkCapabilities required;
+        // A restricted job that's out of quota MUST use an unmetered network.
+        if (jobStatus.getEffectiveStandbyBucket() == RESTRICTED_INDEX
+                && !jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)) {
+            required = new NetworkCapabilities(
+                    jobStatus.getJob().getRequiredNetwork().networkCapabilities)
+                    .addCapability(NET_CAPABILITY_NOT_METERED);
+        } else {
+            required = jobStatus.getJob().getRequiredNetwork().networkCapabilities;
+        }
+
+        return required.satisfiedByNetworkCapabilities(capabilities);
     }
 
     private static boolean isRelaxedSatisfied(JobStatus jobStatus, Network network,
             NetworkCapabilities capabilities, Constants constants) {
-        // Only consider doing this for prefetching jobs
-        if (!jobStatus.getJob().isPrefetch()) {
+        // Only consider doing this for unrestricted prefetching jobs
+        if (!jobStatus.getJob().isPrefetch() || jobStatus.getStandbyBucket() == RESTRICTED_INDEX) {
             return false;
         }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java
index d355715..c0b3204 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java
@@ -32,7 +32,15 @@
 
 import java.util.function.Predicate;
 
-public final class IdleController extends StateController implements IdlenessListener {
+/**
+ * Simple controller that tracks whether the device is idle or not. Idleness depends on the device
+ * type and is not related to device-idle (Doze mode) despite the similar naming.
+ *
+ * @see CarIdlenessTracker
+ * @see DeviceIdlenessTracker
+ * @see IdlenessTracker
+ */
+public final class IdleController extends RestrictingController implements IdlenessListener {
     private static final String TAG = "JobScheduler.IdleController";
     // Policy: we decide that we're "idle" if the device has been unused /
     // screen off or dreaming or wireless charging dock idle for at least this long
@@ -57,6 +65,11 @@
     }
 
     @Override
+    public void startTrackingRestrictedJobLocked(JobStatus jobStatus) {
+        maybeStartTrackingJobLocked(jobStatus, null);
+    }
+
+    @Override
     public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob,
             boolean forUpdate) {
         if (taskStatus.clearTrackingController(JobStatus.TRACKING_IDLE)) {
@@ -64,6 +77,13 @@
         }
     }
 
+    @Override
+    public void stopTrackingRestrictedJobLocked(JobStatus jobStatus) {
+        if (!jobStatus.hasIdleConstraint()) {
+            maybeStopTrackingJobLocked(jobStatus, null, false);
+        }
+    }
+
     /**
      * State-change notifications from the idleness tracker
      */
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 a8d8bd9..dbdce70 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
@@ -69,13 +69,14 @@
     public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE;
     public static final long NO_EARLIEST_RUNTIME = 0L;
 
-    static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; // 1 < 0
-    static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE;  // 1 << 2
-    static final int CONSTRAINT_BATTERY_NOT_LOW = JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1
+    public static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; // 1 < 0
+    public static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE;  // 1 << 2
+    public static final int CONSTRAINT_BATTERY_NOT_LOW =
+            JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1
     static final int CONSTRAINT_STORAGE_NOT_LOW = JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW; // 1 << 3
     static final int CONSTRAINT_TIMING_DELAY = 1<<31;
     static final int CONSTRAINT_DEADLINE = 1<<30;
-    static final int CONSTRAINT_CONNECTIVITY = 1<<28;
+    public static final int CONSTRAINT_CONNECTIVITY = 1 << 28;
     static final int CONSTRAINT_CONTENT_TRIGGER = 1<<26;
     static final int CONSTRAINT_DEVICE_NOT_DOZING = 1 << 25; // Implicit constraint
     static final int CONSTRAINT_WITHIN_QUOTA = 1 << 24;      // Implicit constraint
@@ -117,7 +118,7 @@
     /** The minimum possible update delay is 1/2 second. */
     public static final long MIN_TRIGGER_UPDATE_DELAY = 500;
 
-    /** If not specified, trigger maxumum delay is 2 minutes. */
+    /** If not specified, trigger maximum delay is 2 minutes. */
     public static final long DEFAULT_TRIGGER_MAX_DELAY = 2*60*1000;
 
     /** The minimum possible update delay is 1 second. */
@@ -188,6 +189,11 @@
     private final int mRequiredConstraintsOfInterest;
     int satisfiedConstraints = 0;
     private int mSatisfiedConstraintsOfInterest = 0;
+    /**
+     * Set of constraints that must be satisfied for the job if/because it's in the RESTRICTED
+     * bucket.
+     */
+    private int mDynamicConstraints = 0;
 
     // Set to true if doze constraint was satisfied due to app being whitelisted.
     public boolean dozeWhitelisted;
@@ -328,6 +334,9 @@
     /** The job is within its quota based on its standby bucket. */
     private boolean mReadyWithinQuota;
 
+    /** The job's dynamic requirements have been satisfied. */
+    private boolean mReadyDynamicSatisfied;
+
     /** Provide a handle to the service that this job will be run on. */
     public int getServiceToken() {
         return callingUid;
@@ -410,6 +419,7 @@
         this.requiredConstraints = requiredConstraints;
         mRequiredConstraintsOfInterest = requiredConstraints & CONSTRAINTS_OF_INTEREST;
         mReadyNotDozing = (job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
+        mReadyDynamicSatisfied = true;
 
         mLastSuccessfulRunTime = lastSuccessfulRunTime;
         mLastFailedRunTime = lastFailedRunTime;
@@ -830,41 +840,54 @@
 
     /** Does this job have any sort of networking constraint? */
     public boolean hasConnectivityConstraint() {
+        // No need to check mDynamicConstraints since connectivity will only be in that list if
+        // it's already in the requiredConstraints list.
         return (requiredConstraints&CONSTRAINT_CONNECTIVITY) != 0;
     }
 
     public boolean hasChargingConstraint() {
-        return (requiredConstraints&CONSTRAINT_CHARGING) != 0;
+        return hasConstraint(CONSTRAINT_CHARGING);
     }
 
     public boolean hasBatteryNotLowConstraint() {
-        return (requiredConstraints&CONSTRAINT_BATTERY_NOT_LOW) != 0;
+        return hasConstraint(CONSTRAINT_BATTERY_NOT_LOW);
     }
 
-    public boolean hasPowerConstraint() {
-        return (requiredConstraints&(CONSTRAINT_CHARGING|CONSTRAINT_BATTERY_NOT_LOW)) != 0;
+    /** Returns true if the job requires charging OR battery not low. */
+    boolean hasPowerConstraint() {
+        return hasConstraint(CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW);
     }
 
     public boolean hasStorageNotLowConstraint() {
-        return (requiredConstraints&CONSTRAINT_STORAGE_NOT_LOW) != 0;
+        return hasConstraint(CONSTRAINT_STORAGE_NOT_LOW);
     }
 
     public boolean hasTimingDelayConstraint() {
-        return (requiredConstraints&CONSTRAINT_TIMING_DELAY) != 0;
+        return hasConstraint(CONSTRAINT_TIMING_DELAY);
     }
 
     public boolean hasDeadlineConstraint() {
-        return (requiredConstraints&CONSTRAINT_DEADLINE) != 0;
+        return hasConstraint(CONSTRAINT_DEADLINE);
     }
 
     public boolean hasIdleConstraint() {
-        return (requiredConstraints&CONSTRAINT_IDLE) != 0;
+        return hasConstraint(CONSTRAINT_IDLE);
     }
 
     public boolean hasContentTriggerConstraint() {
+        // No need to check mDynamicConstraints since content trigger will only be in that list if
+        // it's already in the requiredConstraints list.
         return (requiredConstraints&CONSTRAINT_CONTENT_TRIGGER) != 0;
     }
 
+    /**
+     * Checks both {@link #requiredConstraints} and {@link #mDynamicConstraints} to see if this job
+     * requires the specified constraint.
+     */
+    private boolean hasConstraint(int constraint) {
+        return (requiredConstraints & constraint) != 0 || (mDynamicConstraints & constraint) != 0;
+    }
+
     public long getTriggerContentUpdateDelay() {
         long time = job.getTriggerContentUpdateDelay();
         if (time < 0) {
@@ -1033,6 +1056,8 @@
         }
         satisfiedConstraints = (satisfiedConstraints&~constraint) | (state ? constraint : 0);
         mSatisfiedConstraintsOfInterest = satisfiedConstraints & CONSTRAINTS_OF_INTEREST;
+        mReadyDynamicSatisfied =
+                mDynamicConstraints == (satisfiedConstraints & mDynamicConstraints);
         if (STATS_LOG_ENABLED && (STATSD_CONSTRAINTS_TO_LOG & constraint) != 0) {
             StatsLog.write_non_chained(StatsLog.SCHEDULED_JOB_CONSTRAINT_CHANGED,
                     sourceUid, null, getBatteryName(), getProtoConstraint(constraint),
@@ -1058,6 +1083,43 @@
         trackingControllers |= which;
     }
 
+    /**
+     * Indicates that this job cannot run without the specified constraint. This is evaluated
+     * separately from the job's explicitly requested constraints and MUST be satisfied before
+     * the job can run if the app doesn't have quota.
+     *
+     */
+    public void addDynamicConstraint(int constraint) {
+        if (constraint == CONSTRAINT_WITHIN_QUOTA) {
+            Slog.wtf(TAG, "Tried to set quota as a dynamic constraint");
+            return;
+        }
+
+        // Connectivity and content trigger are special since they're only valid to add if the
+        // job has requested network or specific content URIs. Adding these constraints to jobs
+        // that don't need them doesn't make sense.
+        if ((constraint == CONSTRAINT_CONNECTIVITY && !hasConnectivityConstraint())
+                || (constraint == CONSTRAINT_CONTENT_TRIGGER && !hasContentTriggerConstraint())) {
+            return;
+        }
+
+        mDynamicConstraints |= constraint;
+        mReadyDynamicSatisfied =
+                mDynamicConstraints == (satisfiedConstraints & mDynamicConstraints);
+    }
+
+    /**
+     * Removes a dynamic constraint from a job, meaning that the requirement is not required for
+     * the job to run (if the job itself hasn't requested the constraint. This is separate from
+     * the job's explicitly requested constraints and does not remove those requested constraints.
+     *
+     */
+    public void removeDynamicConstraint(int constraint) {
+        mDynamicConstraints &= ~constraint;
+        mReadyDynamicSatisfied =
+                mDynamicConstraints == (satisfiedConstraints & mDynamicConstraints);
+    }
+
     public long getLastSuccessfulRunTime() {
         return mLastSuccessfulRunTime;
     }
@@ -1099,6 +1161,8 @@
                 break;
             default:
                 satisfied |= constraint;
+                mReadyDynamicSatisfied =
+                        mDynamicConstraints == (satisfied & mDynamicConstraints);
                 break;
         }
 
@@ -1117,24 +1181,29 @@
             case CONSTRAINT_WITHIN_QUOTA:
                 mReadyWithinQuota = oldValue;
                 break;
+            default:
+                mReadyDynamicSatisfied =
+                        mDynamicConstraints == (satisfiedConstraints & mDynamicConstraints);
+                break;
         }
         return toReturn;
     }
 
     private boolean isReady(int satisfiedConstraints) {
-        // Quota constraints trumps all other constraints.
-        if (!mReadyWithinQuota) {
+        // Quota and dynamic constraints trump all other constraints.
+        if (!mReadyWithinQuota && !mReadyDynamicSatisfied) {
             return false;
         }
-        // Deadline constraint trumps other constraints besides quota (except for periodic jobs
-        // where deadline is an implementation detail. A periodic job should only run if its
-        // constraints are satisfied).
+        // Deadline constraint trumps other constraints besides quota and dynamic (except for
+        // periodic jobs where deadline is an implementation detail. A periodic job should only
+        // run if its constraints are satisfied).
         // DeviceNotDozing implicit constraint must be satisfied
         // NotRestrictedInBackground implicit constraint must be satisfied
         return mReadyNotDozing && mReadyNotRestrictedInBg && (mReadyDeadlineSatisfied
                 || isConstraintsSatisfied(satisfiedConstraints));
     }
 
+    /** All constraints besides implicit and deadline. */
     static final int CONSTRAINTS_OF_INTEREST = CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW
             | CONSTRAINT_STORAGE_NOT_LOW | CONSTRAINT_TIMING_DELAY | CONSTRAINT_CONNECTIVITY
             | CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER;
@@ -1441,6 +1510,8 @@
             case 2: return "FREQUENT";
             case 3: return "RARE";
             case 4: return "NEVER";
+            case 5:
+                return "RESTRICTED";
             default:
                 return "Unknown: " + standbyBucket;
         }
@@ -1560,6 +1631,10 @@
         pw.print(prefix); pw.print("Required constraints:");
         dumpConstraints(pw, requiredConstraints);
         pw.println();
+        pw.print(prefix);
+        pw.print("Dynamic constraints:");
+        dumpConstraints(pw, mDynamicConstraints);
+        pw.println();
         if (full) {
             pw.print(prefix); pw.print("Satisfied constraints:");
             dumpConstraints(pw, satisfiedConstraints);
@@ -1599,6 +1674,9 @@
             pw.print(prefix); pw.print("  readyDeadlineSatisfied: ");
             pw.println(mReadyDeadlineSatisfied);
         }
+        pw.print(prefix);
+        pw.print("  readyDynamicSatisfied: ");
+        pw.println(mReadyDynamicSatisfied);
 
         if (changedAuthorities != null) {
             pw.print(prefix); pw.println("Changed authorities:");
@@ -1760,6 +1838,7 @@
         }
 
         dumpConstraints(proto, JobStatusDumpProto.REQUIRED_CONSTRAINTS, requiredConstraints);
+        dumpConstraints(proto, JobStatusDumpProto.DYNAMIC_CONSTRAINTS, mDynamicConstraints);
         if (full) {
             dumpConstraints(proto, JobStatusDumpProto.SATISFIED_CONSTRAINTS, satisfiedConstraints);
             dumpConstraints(proto, JobStatusDumpProto.UNSATISFIED_CONSTRAINTS,
@@ -1807,6 +1886,8 @@
                 mReadyNotRestrictedInBg);
         // mReadyDeadlineSatisfied isn't an implicit constraint...and can be determined from other
         // field values.
+        proto.write(JobStatusDumpProto.ImplicitConstraints.IS_DYNAMIC_SATISFIED,
+                mReadyDynamicSatisfied);
         proto.end(icToken);
 
         if (changedAuthorities != 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 2e735a4..8eefac8 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
@@ -24,6 +24,7 @@
 import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
 import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
 import static com.android.server.job.JobSchedulerService.RARE_INDEX;
+import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
 import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 
@@ -424,7 +425,9 @@
             QcConstants.DEFAULT_WINDOW_SIZE_ACTIVE_MS,
             QcConstants.DEFAULT_WINDOW_SIZE_WORKING_MS,
             QcConstants.DEFAULT_WINDOW_SIZE_FREQUENT_MS,
-            QcConstants.DEFAULT_WINDOW_SIZE_RARE_MS
+            QcConstants.DEFAULT_WINDOW_SIZE_RARE_MS,
+            0, // NEVER
+            QcConstants.DEFAULT_WINDOW_SIZE_RESTRICTED_MS
     };
 
     /** The maximum period any bucket can have. */
@@ -441,7 +444,9 @@
             QcConstants.DEFAULT_MAX_JOB_COUNT_ACTIVE,
             QcConstants.DEFAULT_MAX_JOB_COUNT_WORKING,
             QcConstants.DEFAULT_MAX_JOB_COUNT_FREQUENT,
-            QcConstants.DEFAULT_MAX_JOB_COUNT_RARE
+            QcConstants.DEFAULT_MAX_JOB_COUNT_RARE,
+            0, // NEVER
+            QcConstants.DEFAULT_MAX_JOB_COUNT_RESTRICTED
     };
 
     /**
@@ -455,7 +460,9 @@
             QcConstants.DEFAULT_MAX_SESSION_COUNT_ACTIVE,
             QcConstants.DEFAULT_MAX_SESSION_COUNT_WORKING,
             QcConstants.DEFAULT_MAX_SESSION_COUNT_FREQUENT,
-            QcConstants.DEFAULT_MAX_SESSION_COUNT_RARE
+            QcConstants.DEFAULT_MAX_SESSION_COUNT_RARE,
+            0, // NEVER
+            QcConstants.DEFAULT_MAX_SESSION_COUNT_RESTRICTED,
     };
 
     /**
@@ -648,7 +655,11 @@
 
         // Quota constraint is not enforced while charging.
         if (mChargeTracker.isCharging()) {
-            return true;
+            // Restricted jobs require additional constraints when charging, so don't immediately
+            // mark quota as free when charging.
+            if (standbyBucket != RESTRICTED_INDEX) {
+                return true;
+            }
         }
 
         ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
@@ -1105,14 +1116,37 @@
         }
     }
 
+    private class TimerChargingUpdateFunctor implements Consumer<Timer> {
+        private long mNowElapsed;
+        private boolean mIsCharging;
+
+        private void setStatus(long nowElapsed, boolean isCharging) {
+            mNowElapsed = nowElapsed;
+            mIsCharging = isCharging;
+        }
+
+        @Override
+        public void accept(Timer timer) {
+            if (JobSchedulerService.standbyBucketForPackage(timer.mPkg.packageName,
+                    timer.mPkg.userId, mNowElapsed) != RESTRICTED_INDEX) {
+                // Restricted jobs need additional constraints even when charging, so don't
+                // immediately say that quota is free.
+                timer.onStateChangedLocked(mNowElapsed, mIsCharging);
+            }
+        }
+    }
+
+    private final TimerChargingUpdateFunctor
+            mTimerChargingUpdateFunctor = new TimerChargingUpdateFunctor();
+
     private void handleNewChargingStateLocked() {
-        final long nowElapsed = sElapsedRealtimeClock.millis();
-        final boolean isCharging = mChargeTracker.isCharging();
+        mTimerChargingUpdateFunctor.setStatus(sElapsedRealtimeClock.millis(),
+                mChargeTracker.isCharging());
         if (DEBUG) {
-            Slog.d(TAG, "handleNewChargingStateLocked: " + isCharging);
+            Slog.d(TAG, "handleNewChargingStateLocked: " + mChargeTracker.isCharging());
         }
         // Deal with Timers first.
-        mPkgTimers.forEach((t) -> t.onStateChangedLocked(nowElapsed, isCharging));
+        mPkgTimers.forEach(mTimerChargingUpdateFunctor);
         // Now update jobs.
         maybeUpdateAllConstraintsLocked();
     }
@@ -1555,7 +1589,10 @@
         }
 
         private boolean shouldTrackLocked() {
-            return !mChargeTracker.isCharging() && !mForegroundUids.get(mUid);
+            final int standbyBucket = JobSchedulerService.standbyBucketForPackage(mPkg.packageName,
+                    mPkg.userId, sElapsedRealtimeClock.millis());
+            return (standbyBucket == RESTRICTED_INDEX || !mChargeTracker.isCharging())
+                    && !mForegroundUids.get(mUid);
         }
 
         void onStateChangedLocked(long nowElapsed, boolean isQuotaFree) {
@@ -1670,6 +1707,7 @@
                     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) {
@@ -1677,6 +1715,13 @@
                     }
                     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);
@@ -1687,6 +1732,9 @@
                         mStateChangedListener.onControllerStateChanged();
                     }
                 }
+                if (restrictedChanges.size() > 0) {
+                    mStateChangedListener.onRestrictedBucketChanged(restrictedChanges);
+                }
             });
         }
     }
@@ -1863,11 +1911,13 @@
         private static final String KEY_WINDOW_SIZE_WORKING_MS = "window_size_working_ms";
         private static final String KEY_WINDOW_SIZE_FREQUENT_MS = "window_size_frequent_ms";
         private static final String KEY_WINDOW_SIZE_RARE_MS = "window_size_rare_ms";
+        private static final String KEY_WINDOW_SIZE_RESTRICTED_MS = "window_size_restricted_ms";
         private static final String KEY_MAX_EXECUTION_TIME_MS = "max_execution_time_ms";
         private static final String KEY_MAX_JOB_COUNT_ACTIVE = "max_job_count_active";
         private static final String KEY_MAX_JOB_COUNT_WORKING = "max_job_count_working";
         private static final String KEY_MAX_JOB_COUNT_FREQUENT = "max_job_count_frequent";
         private static final String KEY_MAX_JOB_COUNT_RARE = "max_job_count_rare";
+        private static final String KEY_MAX_JOB_COUNT_RESTRICTED = "max_job_count_restricted";
         private static final String KEY_RATE_LIMITING_WINDOW_MS = "rate_limiting_window_ms";
         private static final String KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW =
                 "max_job_count_per_rate_limiting_window";
@@ -1875,6 +1925,8 @@
         private static final String KEY_MAX_SESSION_COUNT_WORKING = "max_session_count_working";
         private static final String KEY_MAX_SESSION_COUNT_FREQUENT = "max_session_count_frequent";
         private static final String KEY_MAX_SESSION_COUNT_RARE = "max_session_count_rare";
+        private static final String KEY_MAX_SESSION_COUNT_RESTRICTED =
+                "max_session_count_restricted";
         private static final String KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW =
                 "max_session_count_per_rate_limiting_window";
         private static final String KEY_TIMING_SESSION_COALESCING_DURATION_MS =
@@ -1892,6 +1944,8 @@
                 8 * 60 * 60 * 1000L; // 8 hours
         private static final long DEFAULT_WINDOW_SIZE_RARE_MS =
                 24 * 60 * 60 * 1000L; // 24 hours
+        private static final long DEFAULT_WINDOW_SIZE_RESTRICTED_MS =
+                24 * 60 * 60 * 1000L; // 24 hours
         private static final long DEFAULT_MAX_EXECUTION_TIME_MS =
                 4 * HOUR_IN_MILLIS;
         private static final long DEFAULT_RATE_LIMITING_WINDOW_MS =
@@ -1905,6 +1959,7 @@
                 (int) (25.0 * DEFAULT_WINDOW_SIZE_FREQUENT_MS / HOUR_IN_MILLIS);
         private static final int DEFAULT_MAX_JOB_COUNT_RARE = // 48/window = 2/hr = 16/session
                 (int) (2.0 * DEFAULT_WINDOW_SIZE_RARE_MS / HOUR_IN_MILLIS);
+        private static final int DEFAULT_MAX_JOB_COUNT_RESTRICTED = 10;
         private static final int DEFAULT_MAX_SESSION_COUNT_ACTIVE =
                 75; // 450/hr
         private static final int DEFAULT_MAX_SESSION_COUNT_WORKING =
@@ -1913,6 +1968,7 @@
                 8; // 1/hr
         private static final int DEFAULT_MAX_SESSION_COUNT_RARE =
                 3; // .125/hr
+        private static final int DEFAULT_MAX_SESSION_COUNT_RESTRICTED = 1; // 1/day
         private static final int DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 20;
         private static final long DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS = 5000; // 5 seconds
 
@@ -1954,6 +2010,13 @@
         public long WINDOW_SIZE_RARE_MS = DEFAULT_WINDOW_SIZE_RARE_MS;
 
         /**
+         * The quota window size of the particular standby bucket. Apps in this standby bucket are
+         * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past
+         * WINDOW_SIZE_MS.
+         */
+        public long WINDOW_SIZE_RESTRICTED_MS = DEFAULT_WINDOW_SIZE_RESTRICTED_MS;
+
+        /**
          * The maximum amount of time an app can have its jobs running within a 24 hour window.
          */
         public long MAX_EXECUTION_TIME_MS = DEFAULT_MAX_EXECUTION_TIME_MS;
@@ -1982,6 +2045,12 @@
          */
         public int MAX_JOB_COUNT_RARE = DEFAULT_MAX_JOB_COUNT_RARE;
 
+        /**
+         * The maximum number of jobs an app can run within this particular standby bucket's
+         * window size.
+         */
+        public int MAX_JOB_COUNT_RESTRICTED = DEFAULT_MAX_JOB_COUNT_RESTRICTED;
+
         /** The period of time used to rate limit recently run jobs. */
         public long RATE_LIMITING_WINDOW_MS = DEFAULT_RATE_LIMITING_WINDOW_MS;
 
@@ -2016,6 +2085,12 @@
         public int MAX_SESSION_COUNT_RARE = DEFAULT_MAX_SESSION_COUNT_RARE;
 
         /**
+         * The maximum number of {@link TimingSession}s an app can run within this particular
+         * standby bucket's window size.
+         */
+        public int MAX_SESSION_COUNT_RESTRICTED = DEFAULT_MAX_SESSION_COUNT_RESTRICTED;
+
+        /**
          * The maximum number of {@link TimingSession}s that can run within the past
          * {@link #ALLOWED_TIME_PER_PERIOD_MS}.
          */
@@ -2087,6 +2162,8 @@
                     KEY_WINDOW_SIZE_FREQUENT_MS, DEFAULT_WINDOW_SIZE_FREQUENT_MS);
             WINDOW_SIZE_RARE_MS = mParser.getDurationMillis(
                     KEY_WINDOW_SIZE_RARE_MS, DEFAULT_WINDOW_SIZE_RARE_MS);
+            WINDOW_SIZE_RESTRICTED_MS = mParser.getDurationMillis(
+                    KEY_WINDOW_SIZE_RESTRICTED_MS, DEFAULT_WINDOW_SIZE_RESTRICTED_MS);
             MAX_EXECUTION_TIME_MS = mParser.getDurationMillis(
                     KEY_MAX_EXECUTION_TIME_MS, DEFAULT_MAX_EXECUTION_TIME_MS);
             MAX_JOB_COUNT_ACTIVE = mParser.getInt(
@@ -2097,6 +2174,8 @@
                     KEY_MAX_JOB_COUNT_FREQUENT, DEFAULT_MAX_JOB_COUNT_FREQUENT);
             MAX_JOB_COUNT_RARE = mParser.getInt(
                     KEY_MAX_JOB_COUNT_RARE, DEFAULT_MAX_JOB_COUNT_RARE);
+            MAX_JOB_COUNT_RESTRICTED = mParser.getInt(
+                    KEY_MAX_JOB_COUNT_RESTRICTED, DEFAULT_MAX_JOB_COUNT_RESTRICTED);
             RATE_LIMITING_WINDOW_MS = mParser.getLong(
                     KEY_RATE_LIMITING_WINDOW_MS, DEFAULT_RATE_LIMITING_WINDOW_MS);
             MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = mParser.getInt(
@@ -2110,6 +2189,8 @@
                     KEY_MAX_SESSION_COUNT_FREQUENT, DEFAULT_MAX_SESSION_COUNT_FREQUENT);
             MAX_SESSION_COUNT_RARE = mParser.getInt(
                     KEY_MAX_SESSION_COUNT_RARE, DEFAULT_MAX_SESSION_COUNT_RARE);
+            MAX_SESSION_COUNT_RESTRICTED = mParser.getInt(
+                    KEY_MAX_SESSION_COUNT_RESTRICTED, DEFAULT_MAX_SESSION_COUNT_RESTRICTED);
             MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = mParser.getInt(
                     KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
                     DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW);
@@ -2173,6 +2254,13 @@
                     mBucketPeriodsMs[RARE_INDEX] = newRarePeriodMs;
                     changed = true;
                 }
+                // Fit in the range [allowed time (10 mins), 1 week].
+                long newRestrictedPeriodMs = Math.max(mAllowedTimePerPeriodMs,
+                        Math.min(7 * 24 * 60 * MINUTE_IN_MILLIS, WINDOW_SIZE_RESTRICTED_MS));
+                if (mBucketPeriodsMs[RESTRICTED_INDEX] != newRestrictedPeriodMs) {
+                    mBucketPeriodsMs[RESTRICTED_INDEX] = newRestrictedPeriodMs;
+                    changed = true;
+                }
                 long newRateLimitingWindowMs = Math.min(MAX_PERIOD_MS,
                         Math.max(MIN_RATE_LIMITING_WINDOW_MS, RATE_LIMITING_WINDOW_MS));
                 if (mRateLimitingWindowMs != newRateLimitingWindowMs) {
@@ -2206,6 +2294,12 @@
                     mMaxBucketJobCounts[RARE_INDEX] = newRareMaxJobCount;
                     changed = true;
                 }
+                int newRestrictedMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT,
+                        MAX_JOB_COUNT_RESTRICTED);
+                if (mMaxBucketJobCounts[RESTRICTED_INDEX] != newRestrictedMaxJobCount) {
+                    mMaxBucketJobCounts[RESTRICTED_INDEX] = newRestrictedMaxJobCount;
+                    changed = true;
+                }
                 int newMaxSessionCountPerRateLimitPeriod = Math.max(
                         MIN_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
                         MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW);
@@ -2237,6 +2331,11 @@
                     mMaxBucketSessionCounts[RARE_INDEX] = newRareMaxSessionCount;
                     changed = true;
                 }
+                int newRestrictedMaxSessionCount = Math.max(0, MAX_SESSION_COUNT_RESTRICTED);
+                if (mMaxBucketSessionCounts[RESTRICTED_INDEX] != newRestrictedMaxSessionCount) {
+                    mMaxBucketSessionCounts[RESTRICTED_INDEX] = newRestrictedMaxSessionCount;
+                    changed = true;
+                }
                 long newSessionCoalescingDurationMs = Math.min(15 * MINUTE_IN_MILLIS,
                         Math.max(0, TIMING_SESSION_COALESCING_DURATION_MS));
                 if (mTimingSessionCoalescingDurationMs != newSessionCoalescingDurationMs) {
@@ -2266,11 +2365,13 @@
             pw.printPair(KEY_WINDOW_SIZE_WORKING_MS, WINDOW_SIZE_WORKING_MS).println();
             pw.printPair(KEY_WINDOW_SIZE_FREQUENT_MS, WINDOW_SIZE_FREQUENT_MS).println();
             pw.printPair(KEY_WINDOW_SIZE_RARE_MS, WINDOW_SIZE_RARE_MS).println();
+            pw.printPair(KEY_WINDOW_SIZE_RESTRICTED_MS, WINDOW_SIZE_RESTRICTED_MS).println();
             pw.printPair(KEY_MAX_EXECUTION_TIME_MS, MAX_EXECUTION_TIME_MS).println();
             pw.printPair(KEY_MAX_JOB_COUNT_ACTIVE, MAX_JOB_COUNT_ACTIVE).println();
             pw.printPair(KEY_MAX_JOB_COUNT_WORKING, MAX_JOB_COUNT_WORKING).println();
             pw.printPair(KEY_MAX_JOB_COUNT_FREQUENT, MAX_JOB_COUNT_FREQUENT).println();
             pw.printPair(KEY_MAX_JOB_COUNT_RARE, MAX_JOB_COUNT_RARE).println();
+            pw.printPair(KEY_MAX_JOB_COUNT_RESTRICTED, MAX_JOB_COUNT_RESTRICTED).println();
             pw.printPair(KEY_RATE_LIMITING_WINDOW_MS, RATE_LIMITING_WINDOW_MS).println();
             pw.printPair(KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
                     MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW).println();
@@ -2278,6 +2379,7 @@
             pw.printPair(KEY_MAX_SESSION_COUNT_WORKING, MAX_SESSION_COUNT_WORKING).println();
             pw.printPair(KEY_MAX_SESSION_COUNT_FREQUENT, MAX_SESSION_COUNT_FREQUENT).println();
             pw.printPair(KEY_MAX_SESSION_COUNT_RARE, MAX_SESSION_COUNT_RARE).println();
+            pw.printPair(KEY_MAX_SESSION_COUNT_RESTRICTED, MAX_SESSION_COUNT_RESTRICTED).println();
             pw.printPair(KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
                     MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW).println();
             pw.printPair(KEY_TIMING_SESSION_COALESCING_DURATION_MS,
@@ -2297,6 +2399,8 @@
             proto.write(ConstantsProto.QuotaController.FREQUENT_WINDOW_SIZE_MS,
                     WINDOW_SIZE_FREQUENT_MS);
             proto.write(ConstantsProto.QuotaController.RARE_WINDOW_SIZE_MS, WINDOW_SIZE_RARE_MS);
+            proto.write(ConstantsProto.QuotaController.RESTRICTED_WINDOW_SIZE_MS,
+                    WINDOW_SIZE_RESTRICTED_MS);
             proto.write(ConstantsProto.QuotaController.MAX_EXECUTION_TIME_MS,
                     MAX_EXECUTION_TIME_MS);
             proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_ACTIVE, MAX_JOB_COUNT_ACTIVE);
@@ -2305,6 +2409,8 @@
             proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_FREQUENT,
                     MAX_JOB_COUNT_FREQUENT);
             proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_RARE, MAX_JOB_COUNT_RARE);
+            proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_RESTRICTED,
+                    MAX_JOB_COUNT_RESTRICTED);
             proto.write(ConstantsProto.QuotaController.RATE_LIMITING_WINDOW_MS,
                     RATE_LIMITING_WINDOW_MS);
             proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
@@ -2317,6 +2423,8 @@
                     MAX_SESSION_COUNT_FREQUENT);
             proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_RARE,
                     MAX_SESSION_COUNT_RARE);
+            proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_RESTRICTED,
+                    MAX_SESSION_COUNT_RESTRICTED);
             proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
                     MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW);
             proto.write(ConstantsProto.QuotaController.TIMING_SESSION_COALESCING_DURATION_MS,
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/RestrictingController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/RestrictingController.java
new file mode 100644
index 0000000..5c637bb
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/RestrictingController.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.job.controllers;
+
+import com.android.server.job.JobSchedulerService;
+
+/**
+ * Controller that can also handle jobs in the
+ * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} bucket.
+ */
+public abstract class RestrictingController extends StateController {
+    RestrictingController(JobSchedulerService service) {
+        super(service);
+    }
+
+    /**
+     * Start tracking a job that has been added to the
+     * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} bucket.
+     */
+    public abstract void startTrackingRestrictedJobLocked(JobStatus jobStatus);
+
+    /**
+     * Stop tracking a job that has been removed from the
+     * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} bucket.
+     */
+    public abstract void stopTrackingRestrictedJobLocked(JobStatus jobStatus);
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
index 82292cf..9d6e012 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
@@ -17,7 +17,7 @@
 package com.android.server.usage;
 
 import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT;
-import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER;
 import static android.app.usage.UsageStatsManager.REASON_MAIN_MASK;
 import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED;
 import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
@@ -25,8 +25,11 @@
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
 
+import static com.android.server.usage.AppStandbyController.isUserUsage;
+
 import android.app.usage.AppStandbyInfo;
 import android.app.usage.UsageStatsManager;
 import android.os.SystemClock;
@@ -81,6 +84,8 @@
     private static final String ATTR_SCREEN_IDLE = "screenIdleTime";
     // Elapsed timebase time when app was last used
     private static final String ATTR_ELAPSED_IDLE = "elapsedIdleTime";
+    // Elapsed timebase time when app was last used by the user
+    private static final String ATTR_LAST_USED_BY_USER_ELAPSED = "lastUsedByUserElapsedTime";
     // Elapsed timebase time when the app bucket was last predicted externally
     private static final String ATTR_LAST_PREDICTED_TIME = "lastPredictedTime";
     // The standby bucket for the app
@@ -93,6 +98,12 @@
     private static final String ATTR_BUCKET_ACTIVE_TIMEOUT_TIME = "activeTimeoutTime";
     // The time when the forced working_set state can be overridden.
     private static final String ATTR_BUCKET_WORKING_SET_TIMEOUT_TIME = "workingSetTimeoutTime";
+    // Elapsed timebase time when the app was last marked for restriction.
+    private static final String ATTR_LAST_RESTRICTION_ATTEMPT_ELAPSED =
+            "lastRestrictionAttemptElapsedTime";
+    // Reason why the app was last marked for restriction.
+    private static final String ATTR_LAST_RESTRICTION_ATTEMPT_REASON =
+            "lastRestrictionAttemptReason";
 
     // device on time = mElapsedDuration + (timeNow - mElapsedSnapshot)
     private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration
@@ -107,8 +118,10 @@
     private boolean mScreenOn;
 
     static class AppUsageHistory {
-        // Last used time using elapsed timebase
+        // Last used time (including system usage), using elapsed timebase
         long lastUsedElapsedTime;
+        // Last time the user used the app, using elapsed timebase
+        long lastUsedByUserElapsedTime;
         // Last used time using screen_on timebase
         long lastUsedScreenTime;
         // Last predicted time using elapsed timebase
@@ -136,6 +149,10 @@
         // under any active state timeout, so that it becomes applicable after the active state
         // timeout expires.
         long bucketWorkingSetTimeoutTime;
+        // The last time an agent attempted to put the app into the RESTRICTED bucket.
+        long lastRestrictAttemptElapsedTime;
+        // The last reason the app was marked to be put into the RESTRICTED bucket.
+        int lastRestrictReason;
     }
 
     AppIdleHistory(File storageDir, long elapsedRealtime) {
@@ -229,25 +246,37 @@
      */
     public AppUsageHistory reportUsage(AppUsageHistory appUsageHistory, String packageName,
             int newBucket, int usageReason, long elapsedRealtime, long timeout) {
-        // Set the timeout if applicable
-        if (timeout > elapsedRealtime) {
-            // Convert to elapsed timebase
-            final long timeoutTime = mElapsedDuration + (timeout - mElapsedSnapshot);
-            if (newBucket == STANDBY_BUCKET_ACTIVE) {
-                appUsageHistory.bucketActiveTimeoutTime = Math.max(timeoutTime,
-                        appUsageHistory.bucketActiveTimeoutTime);
-            } else if (newBucket == STANDBY_BUCKET_WORKING_SET) {
-                appUsageHistory.bucketWorkingSetTimeoutTime = Math.max(timeoutTime,
-                        appUsageHistory.bucketWorkingSetTimeoutTime);
-            } else {
-                throw new IllegalArgumentException("Cannot set a timeout on bucket=" +
-                        newBucket);
+        int bucketingReason = REASON_MAIN_USAGE | usageReason;
+        final boolean isUserUsage = isUserUsage(bucketingReason);
+
+        if (appUsageHistory.currentBucket == STANDBY_BUCKET_RESTRICTED && !isUserUsage) {
+            // Only user usage should bring an app out of the RESTRICTED bucket.
+            newBucket = STANDBY_BUCKET_RESTRICTED;
+            bucketingReason = appUsageHistory.bucketingReason;
+        } else {
+            // Set the timeout if applicable
+            if (timeout > elapsedRealtime) {
+                // Convert to elapsed timebase
+                final long timeoutTime = mElapsedDuration + (timeout - mElapsedSnapshot);
+                if (newBucket == STANDBY_BUCKET_ACTIVE) {
+                    appUsageHistory.bucketActiveTimeoutTime = Math.max(timeoutTime,
+                            appUsageHistory.bucketActiveTimeoutTime);
+                } else if (newBucket == STANDBY_BUCKET_WORKING_SET) {
+                    appUsageHistory.bucketWorkingSetTimeoutTime = Math.max(timeoutTime,
+                            appUsageHistory.bucketWorkingSetTimeoutTime);
+                } else {
+                    throw new IllegalArgumentException("Cannot set a timeout on bucket="
+                            + newBucket);
+                }
             }
         }
 
         if (elapsedRealtime != 0) {
             appUsageHistory.lastUsedElapsedTime = mElapsedDuration
                     + (elapsedRealtime - mElapsedSnapshot);
+            if (isUserUsage) {
+                appUsageHistory.lastUsedByUserElapsedTime = appUsageHistory.lastUsedElapsedTime;
+            }
             appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
         }
 
@@ -259,7 +288,7 @@
                         + ", reason=0x0" + Integer.toHexString(appUsageHistory.bucketingReason));
             }
         }
-        appUsageHistory.bucketingReason = REASON_MAIN_USAGE | usageReason;
+        appUsageHistory.bucketingReason = bucketingReason;
 
         return appUsageHistory;
     }
@@ -386,6 +415,24 @@
     }
 
     /**
+     * Notes an attempt to put the app in the {@link UsageStatsManager#STANDBY_BUCKET_RESTRICTED}
+     * bucket.
+     *
+     * @param packageName     The package name of the app that is being restricted
+     * @param userId          The ID of the user in which the app is being restricted
+     * @param elapsedRealtime The time the attempt was made, in the (unadjusted) elapsed realtime
+     *                        timebase
+     * @param reason          The reason for the restriction attempt
+     */
+    void noteRestrictionAttempt(String packageName, int userId, long elapsedRealtime, int reason) {
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory =
+                getPackageHistory(userHistory, packageName, elapsedRealtime, true);
+        appUsageHistory.lastRestrictAttemptElapsedTime = getElapsedTime(elapsedRealtime);
+        appUsageHistory.lastRestrictReason = reason;
+    }
+
+    /**
      * Returns the time since the last job was run for this app. This can be larger than the
      * current elapsedRealtime, in case it happened before boot or a really large value if no jobs
      * were ever run.
@@ -441,7 +488,7 @@
                 elapsedRealtime, true);
         if (idle) {
             appUsageHistory.currentBucket = STANDBY_BUCKET_RARE;
-            appUsageHistory.bucketingReason = REASON_MAIN_FORCED;
+            appUsageHistory.bucketingReason = REASON_MAIN_FORCED_BY_USER;
         } else {
             appUsageHistory.currentBucket = STANDBY_BUCKET_ACTIVE;
             // This is to pretend that the app was just used, don't freeze the state anymore.
@@ -547,6 +594,9 @@
                         AppUsageHistory appUsageHistory = new AppUsageHistory();
                         appUsageHistory.lastUsedElapsedTime =
                                 Long.parseLong(parser.getAttributeValue(null, ATTR_ELAPSED_IDLE));
+                        appUsageHistory.lastUsedByUserElapsedTime = getLongValue(parser,
+                                ATTR_LAST_USED_BY_USER_ELAPSED,
+                                appUsageHistory.lastUsedElapsedTime);
                         appUsageHistory.lastUsedScreenTime =
                                 Long.parseLong(parser.getAttributeValue(null, ATTR_SCREEN_IDLE));
                         appUsageHistory.lastPredictedTime = getLongValue(parser,
@@ -570,6 +620,19 @@
                                 appUsageHistory.bucketingReason =
                                         Integer.parseInt(bucketingReason, 16);
                             } catch (NumberFormatException nfe) {
+                                Slog.wtf(TAG, "Unable to read bucketing reason", nfe);
+                            }
+                        }
+                        appUsageHistory.lastRestrictAttemptElapsedTime =
+                                getLongValue(parser, ATTR_LAST_RESTRICTION_ATTEMPT_ELAPSED, 0);
+                        String lastRestrictReason = parser.getAttributeValue(
+                                null, ATTR_LAST_RESTRICTION_ATTEMPT_REASON);
+                        if (lastRestrictReason != null) {
+                            try {
+                                appUsageHistory.lastRestrictReason =
+                                        Integer.parseInt(lastRestrictReason, 16);
+                            } catch (NumberFormatException nfe) {
+                                Slog.wtf(TAG, "Unable to read last restrict reason", nfe);
                             }
                         }
                         appUsageHistory.lastInformedBucket = -1;
@@ -618,6 +681,8 @@
                 xml.attribute(null, ATTR_NAME, packageName);
                 xml.attribute(null, ATTR_ELAPSED_IDLE,
                         Long.toString(history.lastUsedElapsedTime));
+                xml.attribute(null, ATTR_LAST_USED_BY_USER_ELAPSED,
+                        Long.toString(history.lastUsedByUserElapsedTime));
                 xml.attribute(null, ATTR_SCREEN_IDLE,
                         Long.toString(history.lastUsedScreenTime));
                 xml.attribute(null, ATTR_LAST_PREDICTED_TIME,
@@ -638,6 +703,12 @@
                     xml.attribute(null, ATTR_LAST_RUN_JOB_TIME, Long.toString(history
                             .lastJobRunTime));
                 }
+                if (history.lastRestrictAttemptElapsedTime > 0) {
+                    xml.attribute(null, ATTR_LAST_RESTRICTION_ATTEMPT_ELAPSED,
+                            Long.toString(history.lastRestrictAttemptElapsedTime));
+                }
+                xml.attribute(null, ATTR_LAST_RESTRICTION_ATTEMPT_REASON,
+                        Integer.toHexString(history.lastRestrictReason));
                 xml.endTag(null, TAG_PACKAGE);
             }
 
@@ -672,6 +743,9 @@
                     + UsageStatsManager.reasonToString(appUsageHistory.bucketingReason));
             idpw.print(" used=");
             TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastUsedElapsedTime, idpw);
+            idpw.print(" usedByUser=");
+            TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastUsedByUserElapsedTime,
+                    idpw);
             idpw.print(" usedScr=");
             TimeUtils.formatDuration(screenOnTime - appUsageHistory.lastUsedScreenTime, idpw);
             idpw.print(" lastPred=");
@@ -684,6 +758,13 @@
                     idpw);
             idpw.print(" lastJob=");
             TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastJobRunTime, idpw);
+            if (appUsageHistory.lastRestrictAttemptElapsedTime > 0) {
+                idpw.print(" lastRestrictAttempt=");
+                TimeUtils.formatDuration(
+                        totalElapsedTime - appUsageHistory.lastRestrictAttemptElapsedTime, idpw);
+                idpw.print(" lastRestrictReason="
+                        + UsageStatsManager.reasonToString(appUsageHistory.lastRestrictReason));
+            }
             idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n"));
             idpw.println();
         }
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 2f8b513..b1b8fba 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -17,11 +17,13 @@
 package com.android.server.usage;
 
 import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT;
-import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER;
 import static android.app.usage.UsageStatsManager.REASON_MAIN_MASK;
 import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED;
 import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT;
 import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_MASK;
 import static android.app.usage.UsageStatsManager.REASON_SUB_PREDICTED_RESTORED;
 import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_ACTIVE_TIMEOUT;
 import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_EXEMPTED_SYNC_SCHEDULED_DOZE;
@@ -43,10 +45,12 @@
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
 
 import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 
+import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.AppGlobals;
@@ -71,6 +75,7 @@
 import android.net.NetworkRequest;
 import android.net.NetworkScoreManager;
 import android.os.BatteryStats;
+import android.os.Build;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.IDeviceIdleController;
@@ -91,7 +96,9 @@
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
 import android.view.Display;
+import android.widget.Toast;
 
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
@@ -101,7 +108,6 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
 import com.android.server.usage.AppIdleHistory.AppUsageHistory;
-import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
 
 import java.io.File;
 import java.io.PrintWriter;
@@ -109,6 +115,7 @@
 import java.time.format.DateTimeParseException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
@@ -122,7 +129,7 @@
 public class AppStandbyController implements AppStandbyInternal {
 
     private static final String TAG = "AppStandbyController";
-    static final boolean DEBUG = false;
+    static final boolean DEBUG = true;
 
     static final boolean COMPRESS_TIME = false;
     private static final long ONE_MINUTE = 60 * 1000;
@@ -564,7 +571,7 @@
 
                 // If the bucket was forced by the user/developer, leave it alone.
                 // A usage event will be the only way to bring it out of this forced state
-                if (oldMainReason == REASON_MAIN_FORCED) {
+                if (oldMainReason == REASON_MAIN_FORCED_BY_USER) {
                     return;
                 }
                 final int oldBucket = app.currentBucket;
@@ -613,6 +620,16 @@
                         Slog.d(TAG, "    Keeping at WORKING_SET due to min timeout");
                     }
                 }
+
+                if (app.lastRestrictAttemptElapsedTime > app.lastUsedByUserElapsedTime
+                        && elapsedTimeAdjusted - app.lastUsedByUserElapsedTime
+                        >= mInjector.getRestrictedBucketDelayMs()) {
+                    newBucket = STANDBY_BUCKET_RESTRICTED;
+                    reason = app.lastRestrictReason;
+                    if (DEBUG) {
+                        Slog.d(TAG, "Bringing down to RESTRICTED due to timeout");
+                    }
+                }
                 if (DEBUG) {
                     Slog.d(TAG, "     Old bucket=" + oldBucket
                             + ", newBucket=" + newBucket);
@@ -731,15 +748,16 @@
                             elapsedRealtime, elapsedRealtime + mStrongUsageTimeoutMillis);
                     nextCheckTime = mStrongUsageTimeoutMillis;
                 }
-                mHandler.sendMessageDelayed(mHandler.obtainMessage
-                        (MSG_CHECK_PACKAGE_IDLE_STATE, userId, -1, pkg),
-                        nextCheckTime);
-                final boolean userStartedInteracting =
-                        appHistory.currentBucket == STANDBY_BUCKET_ACTIVE &&
-                        prevBucket != appHistory.currentBucket &&
-                        (prevBucketReason & REASON_MAIN_MASK) != REASON_MAIN_USAGE;
-                maybeInformListeners(pkg, userId, elapsedRealtime,
-                        appHistory.currentBucket, reason, userStartedInteracting);
+                if (appHistory.currentBucket != prevBucket) {
+                    mHandler.sendMessageDelayed(
+                            mHandler.obtainMessage(MSG_CHECK_PACKAGE_IDLE_STATE, userId, -1, pkg),
+                            nextCheckTime);
+                    final boolean userStartedInteracting =
+                            appHistory.currentBucket == STANDBY_BUCKET_ACTIVE
+                                    && (prevBucketReason & REASON_MAIN_MASK) != REASON_MAIN_USAGE;
+                    maybeInformListeners(pkg, userId, elapsedRealtime,
+                            appHistory.currentBucket, reason, userStartedInteracting);
+                }
 
                 if (previouslyIdle) {
                     notifyBatteryStats(pkg, userId, false);
@@ -782,7 +800,7 @@
         // Inform listeners if necessary
         if (previouslyIdle != stillIdle) {
             maybeInformListeners(packageName, userId, elapsedRealtime, standbyBucket,
-                    REASON_MAIN_FORCED, false);
+                    REASON_MAIN_FORCED_BY_USER, false);
             if (!stillIdle) {
                 notifyBatteryStats(packageName, userId, idle);
             }
@@ -921,6 +939,15 @@
         }
     }
 
+    static boolean isUserUsage(int reason) {
+        if ((reason & REASON_MAIN_MASK) == REASON_MAIN_USAGE) {
+            final int subReason = reason & REASON_SUB_MASK;
+            return subReason == REASON_SUB_USAGE_USER_INTERACTION
+                    || subReason == REASON_SUB_USAGE_MOVE_TO_FOREGROUND;
+        }
+        return false;
+    }
+
     @Override
     public int[] getIdleUidsForUser(int userId) {
         if (!mAppIdleEnabled) {
@@ -1014,18 +1041,85 @@
         }
     }
 
-    @VisibleForTesting
-    void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket,
-            int reason, long elapsedRealtime) {
-        setAppStandbyBucket(packageName, userId, newBucket, reason, elapsedRealtime, false);
+    @Override
+    public void restrictApp(@NonNull String packageName, int userId, int restrictReason) {
+        // If the package is not installed, don't allow the bucket to be set.
+        if (!mInjector.isPackageInstalled(packageName, 0, userId)) {
+            Slog.e(TAG, "Tried to restrict uninstalled app: " + packageName);
+            return;
+        }
+
+        final int reason = REASON_MAIN_FORCED_BY_SYSTEM | (REASON_SUB_MASK & restrictReason);
+        final long nowElapsed = mInjector.elapsedRealtime();
+        setAppStandbyBucket(packageName, userId, STANDBY_BUCKET_RESTRICTED, reason,
+                nowElapsed, false);
     }
 
     @Override
-    public void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket,
+    public void setAppStandbyBucket(@NonNull String packageName, int bucket, int userId,
+            int callingUid, int callingPid) {
+        setAppStandbyBuckets(
+                Collections.singletonList(new AppStandbyInfo(packageName, bucket)),
+                userId, callingUid, callingPid);
+    }
+
+    @Override
+    public void setAppStandbyBuckets(@NonNull List<AppStandbyInfo> appBuckets, int userId,
+            int callingUid, int callingPid) {
+        userId = ActivityManager.handleIncomingUser(
+                callingPid, callingUid, userId, false, true, "setAppStandbyBucket", null);
+        final boolean shellCaller = callingUid == Process.ROOT_UID
+                || callingUid == Process.SHELL_UID;
+        final int reason;
+        // The Settings app runs in the system UID but in a separate process. Assume
+        // things coming from other processes are due to the user.
+        if ((UserHandle.isSameApp(callingUid, Process.SYSTEM_UID) && callingPid != Process.myPid())
+                || shellCaller) {
+            reason = REASON_MAIN_FORCED_BY_USER;
+        } else if (UserHandle.isCore(callingUid)) {
+            reason = REASON_MAIN_FORCED_BY_SYSTEM;
+        } else {
+            reason = REASON_MAIN_PREDICTED;
+        }
+        final int packageFlags = PackageManager.MATCH_ANY_USER
+                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                | PackageManager.MATCH_DIRECT_BOOT_AWARE;
+        final int numApps = appBuckets.size();
+        final long elapsedRealtime = mInjector.elapsedRealtime();
+        for (int i = 0; i < numApps; ++i) {
+            final AppStandbyInfo bucketInfo = appBuckets.get(i);
+            final String packageName = bucketInfo.mPackageName;
+            final int bucket = bucketInfo.mStandbyBucket;
+            if (bucket < STANDBY_BUCKET_ACTIVE || bucket > STANDBY_BUCKET_NEVER) {
+                throw new IllegalArgumentException("Cannot set the standby bucket to " + bucket);
+            }
+            final int packageUid = mInjector.getPackageManagerInternal()
+                    .getPackageUid(packageName, packageFlags, userId);
+            // Caller cannot set their own standby state
+            if (packageUid == callingUid) {
+                throw new IllegalArgumentException("Cannot set your own standby bucket");
+            }
+            if (packageUid < 0) {
+                throw new IllegalArgumentException(
+                        "Cannot set standby bucket for non existent package (" + packageName + ")");
+            }
+            setAppStandbyBucket(packageName, userId, bucket, reason, elapsedRealtime, shellCaller);
+        }
+    }
+
+    @VisibleForTesting
+    void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket,
+            int reason) {
+        setAppStandbyBucket(
+                packageName, userId, newBucket, reason, mInjector.elapsedRealtime(), false);
+    }
+
+    private void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket,
             int reason, long elapsedRealtime, boolean resetTimeout) {
         synchronized (mAppIdleLock) {
             // If the package is not installed, don't allow the bucket to be set.
             if (!mInjector.isPackageInstalled(packageName, 0, userId)) {
+                Slog.e(TAG, "Tried to set bucket of uninstalled app: " + packageName);
                 return;
             }
             AppIdleHistory.AppUsageHistory app = mAppIdleHistory.getAppUsageHistory(packageName,
@@ -1035,15 +1129,64 @@
             // Don't allow changing bucket if higher than ACTIVE
             if (app.currentBucket < STANDBY_BUCKET_ACTIVE) return;
 
-            // Don't allow prediction to change from/to NEVER
+            // Don't allow prediction to change from/to NEVER or from RESTRICTED.
             if ((app.currentBucket == STANDBY_BUCKET_NEVER
+                    || app.currentBucket == STANDBY_BUCKET_RESTRICTED
                     || newBucket == STANDBY_BUCKET_NEVER)
                     && predicted) {
                 return;
             }
 
             // If the bucket was forced, don't allow prediction to override
-            if ((app.bucketingReason & REASON_MAIN_MASK) == REASON_MAIN_FORCED && predicted) return;
+            if (predicted
+                    && ((app.bucketingReason & REASON_MAIN_MASK) == REASON_MAIN_FORCED_BY_USER
+                    || (app.bucketingReason & REASON_MAIN_MASK) == REASON_MAIN_FORCED_BY_SYSTEM)) {
+                return;
+            }
+
+            final boolean isForcedByUser =
+                    (reason & REASON_MAIN_MASK) == REASON_MAIN_FORCED_BY_USER;
+
+            // If the current bucket is RESTRICTED, only user force or usage should bring it out.
+            if (app.currentBucket == STANDBY_BUCKET_RESTRICTED && !isUserUsage(reason)
+                    && !isForcedByUser) {
+                return;
+            }
+
+            if (newBucket == STANDBY_BUCKET_RESTRICTED) {
+                mAppIdleHistory
+                        .noteRestrictionAttempt(packageName, userId, elapsedRealtime, reason);
+
+                if (isForcedByUser) {
+                    // Only user force can bypass the delay restriction. If the user forced the
+                    // app into the RESTRICTED bucket, then a toast confirming the action
+                    // shouldn't be surprising.
+                    if (Build.IS_DEBUGGABLE) {
+                        Toast.makeText(mContext,
+                                // Since AppStandbyController sits low in the lock hierarchy,
+                                // make sure not to call out with the lock held.
+                                mHandler.getLooper(),
+                                mContext.getResources().getString(
+                                        R.string.as_app_forced_to_restricted_bucket, packageName),
+                                Toast.LENGTH_SHORT)
+                                .show();
+                    } else {
+                        Slog.i(TAG, packageName + " restricted by user");
+                    }
+                } else {
+                    final long timeUntilRestrictPossibleMs = app.lastUsedByUserElapsedTime
+                            + mInjector.getRestrictedBucketDelayMs() - elapsedRealtime;
+                    if (timeUntilRestrictPossibleMs > 0) {
+                        Slog.w(TAG, "Tried to restrict recently used app: " + packageName
+                                + " due to " + reason);
+                        mHandler.sendMessageDelayed(
+                                mHandler.obtainMessage(
+                                        MSG_CHECK_PACKAGE_IDLE_STATE, userId, -1, packageName),
+                                timeUntilRestrictPossibleMs);
+                        return;
+                    }
+                }
+            }
 
             // If the bucket is required to stay in a higher state for a specified duration, don't
             // override unless the duration has passed
@@ -1377,6 +1520,12 @@
         private DisplayManager mDisplayManager;
         private PowerManager mPowerManager;
         int mBootPhase;
+        /**
+         * The minimum amount of time required since the last user interaction before an app can be
+         * placed in the RESTRICTED bucket.
+         */
+        // TODO: make configurable via DeviceConfig
+        private long mRestrictedBucketDelayMs = ONE_DAY;
 
         Injector(Context context, Looper looper) {
             mContext = context;
@@ -1401,6 +1550,12 @@
                 mDisplayManager = (DisplayManager) mContext.getSystemService(
                         Context.DISPLAY_SERVICE);
                 mPowerManager = mContext.getSystemService(PowerManager.class);
+
+                final ActivityManager activityManager =
+                        (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+                if (activityManager.isLowRamDevice() || ActivityManager.isSmallBatteryDevice()) {
+                    mRestrictedBucketDelayMs = 12 * ONE_HOUR;
+                }
             }
             mBootPhase = phase;
         }
@@ -1440,10 +1595,18 @@
             return Environment.getDataSystemDirectory();
         }
 
+        long getRestrictedBucketDelayMs() {
+            return mRestrictedBucketDelayMs;
+        }
+
         void noteEvent(int event, String packageName, int uid) throws RemoteException {
             mBatteryStats.noteEvent(event, packageName, uid);
         }
 
+        PackageManagerInternal getPackageManagerInternal() {
+            return mPackageManagerInternal;
+        }
+
         boolean isPackageEphemeral(int userId, String packageName) {
             return mPackageManagerInternal.isPackageEphemeral(userId, packageName);
         }
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
new file mode 100644
index 0000000..cf70878
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
@@ -0,0 +1,19 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {"include-filter": "com.android.server.usage"},
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+      ]
+    }
+  ],
+  "postsubmit": [
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {"include-filter": "com.android.server.usage"}
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp
index 6bd0086..18382a4 100644
--- a/apex/media/framework/Android.bp
+++ b/apex/media/framework/Android.bp
@@ -55,6 +55,13 @@
     jarjar_rules: "jarjar_rules.txt",
 
     plugins: ["java_api_finder"],
+
+    hostdex: true, // for hiddenapi check
+    visibility: ["//frameworks/av/apex:__subpackages__"],
+    apex_available: [
+        "com.android.media",
+        "test_com.android.media",
+    ],
 }
 
 filegroup {
diff --git a/apex/media/framework/java/android/media/MediaController2.java b/apex/media/framework/java/android/media/MediaController2.java
index c3dd3fe..d059c67 100644
--- a/apex/media/framework/java/android/media/MediaController2.java
+++ b/apex/media/framework/java/android/media/MediaController2.java
@@ -141,6 +141,9 @@
                 // Note: unbindService() throws IllegalArgumentException when it's called twice.
                 return;
             }
+            if (DEBUG) {
+                Log.d(TAG, "closing " + this);
+            }
             mClosed = true;
             if (mServiceConnection != null) {
                 // Note: This should be called even when the bindService() has returned false.
diff --git a/apex/permission/Android.bp b/apex/permission/Android.bp
index 5945fb3..d746ea6 100644
--- a/apex/permission/Android.bp
+++ b/apex/permission/Android.bp
@@ -22,6 +22,11 @@
     name: "com.android.permission-defaults",
     key: "com.android.permission.key",
     certificate: ":com.android.permission.certificate",
+    java_libs: [
+        "framework-permission",
+        "service-permission",
+    ],
+    apps: ["PermissionController"],
 }
 
 apex_key {
diff --git a/apex/permission/apex_manifest.json b/apex/permission/apex_manifest.json
index 2a8c4f7..7960598 100644
--- a/apex/permission/apex_manifest.json
+++ b/apex/permission/apex_manifest.json
@@ -1,4 +1,4 @@
 {
   "name": "com.android.permission",
-  "version": 1
+  "version": 300000000
 }
diff --git a/apex/permission/framework/Android.bp b/apex/permission/framework/Android.bp
new file mode 100644
index 0000000..09571a1
--- /dev/null
+++ b/apex/permission/framework/Android.bp
@@ -0,0 +1,66 @@
+// 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.
+
+filegroup {
+    name: "framework-permission-sources",
+    srcs: [
+        "java/**/*.java",
+        "java/**/*.aidl",
+    ],
+    path: "java",
+}
+
+java_library {
+    name: "framework-permission",
+    srcs: [
+        ":framework-permission-sources",
+    ],
+    // TODO(b/146758669): Use "system_current" after nullability annotations are system APIs.
+    sdk_version: "core_current",
+    libs: [
+        "framework-annotations-lib",
+        // TODO(b/146758669): Remove this line after nullability annotations are system APIs.
+        "android_system_stubs_current",
+    ],
+    apex_available: [
+        "com.android.permission",
+        "test_com.android.permission",
+    ],
+    hostdex: true,
+    installable: true,
+    visibility: [
+        "//frameworks/base/apex/permission:__subpackages__",
+    ],
+}
+
+droidstubs {
+    name: "framework-permission-stubs-sources",
+    srcs: [
+        ":framework-annotations",
+        ":framework-permission-sources",
+    ],
+    sdk_version: "system_current",
+    defaults: [
+        "framework-module-stubs-defaults-systemapi",
+    ],
+}
+
+java_library {
+    name: "framework-permission-stubs",
+    srcs: [
+        ":framework-permission-stubs-sources",
+    ],
+    sdk_version: "system_current",
+    installable: false,
+}
diff --git a/apex/permission/framework/java/android/permission/PermissionState.java b/apex/permission/framework/java/android/permission/PermissionState.java
new file mode 100644
index 0000000..e810db8
--- /dev/null
+++ b/apex/permission/framework/java/android/permission/PermissionState.java
@@ -0,0 +1,22 @@
+/*
+ * 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.permission;
+
+/**
+ * @hide
+ */
+public class PermissionState {}
diff --git a/apex/permission/service/Android.bp b/apex/permission/service/Android.bp
new file mode 100644
index 0000000..4172e95
--- /dev/null
+++ b/apex/permission/service/Android.bp
@@ -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.
+
+filegroup {
+    name: "service-permission-sources",
+    srcs: [
+        "java/**/*.java",
+    ],
+}
+
+java_library {
+    name: "service-permission",
+    srcs: [
+        ":service-permission-sources",
+    ],
+    // TODO(b/146758669): Use "system_current" after nullability annotations are system APIs.
+    sdk_version: "core_current",
+    libs: [
+        "framework-annotations-lib",
+        // TODO(b/146758669): Remove this line after nullability annotations are system APIs.
+        "android_system_stubs_current",
+        "framework-permission",
+    ],
+    apex_available: [
+        "com.android.permission",
+        "test_com.android.permission",
+    ],
+    installable: true,
+}
diff --git a/apex/permission/service/java/com/android/permission/persistence/IoUtils.java b/apex/permission/service/java/com/android/permission/persistence/IoUtils.java
new file mode 100644
index 0000000..0ae4460
--- /dev/null
+++ b/apex/permission/service/java/com/android/permission/persistence/IoUtils.java
@@ -0,0 +1,38 @@
+/*
+ * 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.permission.persistence;
+
+import android.annotation.NonNull;
+
+/**
+ * Utility class for IO.
+ */
+public class IoUtils {
+
+    private IoUtils() {}
+
+    /**
+     * Close 'closeable' ignoring any exceptions.
+     */
+    public static void closeQuietly(@NonNull AutoCloseable closeable) {
+        try {
+            closeable.close();
+        } catch (Exception ignored) {
+            // Ignored.
+        }
+    }
+}
diff --git a/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistence.java b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistence.java
new file mode 100644
index 0000000..5f2d944
--- /dev/null
+++ b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistence.java
@@ -0,0 +1,72 @@
+/*
+ * 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.permission.persistence;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.UserHandle;
+
+/**
+ * Persistence for runtime permissions.
+ *
+ * TODO(b/147914847): Remove @hide when it becomes the default.
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES, process = SystemApi.Process.SYSTEM_SERVER)
+public interface RuntimePermissionsPersistence {
+
+    /**
+     * Read the runtime permissions from persistence.
+     *
+     * This will perform I/O operations synchronously.
+     *
+     * @param user the user to read for
+     * @return the runtime permissions read
+     */
+    @Nullable
+    RuntimePermissionsState read(@NonNull UserHandle user);
+
+    /**
+     * Write the runtime permissions to persistence.
+     *
+     * This will perform I/O operations synchronously.
+     *
+     * @param runtimePermissions the runtime permissions to write
+     * @param user the user to write for
+     */
+    void write(@NonNull RuntimePermissionsState runtimePermissions, @NonNull UserHandle user);
+
+    /**
+     * Delete the runtime permissions from persistence.
+     *
+     * This will perform I/O operations synchronously.
+     *
+     * @param user the user to delete for
+     */
+    void delete(@NonNull UserHandle user);
+
+    /**
+     * Create a new instance of {@link RuntimePermissionsPersistence} implementation.
+     *
+     * @return the new instance.
+     */
+    @NonNull
+    static RuntimePermissionsPersistence createInstance() {
+        return new RuntimePermissionsPersistenceImpl();
+    }
+}
diff --git a/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java
new file mode 100644
index 0000000..1dbad45
--- /dev/null
+++ b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java
@@ -0,0 +1,263 @@
+/*
+ * 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.permission.persistence;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ApexContext;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Persistence implementation for runtime permissions.
+ *
+ * TODO(b/147914847): Remove @hide when it becomes the default.
+ * @hide
+ */
+public class RuntimePermissionsPersistenceImpl implements RuntimePermissionsPersistence {
+
+    private static final String LOG_TAG = RuntimePermissionsPersistenceImpl.class.getSimpleName();
+
+    private static final String APEX_MODULE_NAME = "com.android.permission";
+
+    private static final String RUNTIME_PERMISSIONS_FILE_NAME = "runtime-permissions.xml";
+
+    private static final String TAG_PACKAGE = "package";
+    private static final String TAG_PERMISSION = "permission";
+    private static final String TAG_RUNTIME_PERMISSIONS = "runtime-permissions";
+    private static final String TAG_SHARED_USER = "shared-user";
+
+    private static final String ATTRIBUTE_FINGERPRINT = "fingerprint";
+    private static final String ATTRIBUTE_FLAGS = "flags";
+    private static final String ATTRIBUTE_GRANTED = "granted";
+    private static final String ATTRIBUTE_NAME = "name";
+    private static final String ATTRIBUTE_VERSION = "version";
+
+    @Nullable
+    @Override
+    public RuntimePermissionsState read(@NonNull UserHandle user) {
+        File file = getFile(user);
+        try (FileInputStream inputStream = new AtomicFile(file).openRead()) {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(inputStream, null);
+            return parseXml(parser);
+        } catch (FileNotFoundException e) {
+            Log.i(LOG_TAG, "runtime-permissions.xml not found");
+            return null;
+        } catch (XmlPullParserException | IOException e) {
+            throw new IllegalStateException("Failed to read runtime-permissions.xml: " + file , e);
+        }
+    }
+
+    @NonNull
+    private static RuntimePermissionsState parseXml(@NonNull XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        int type;
+        int depth;
+        int innerDepth = parser.getDepth() + 1;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
+            if (depth > innerDepth || type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            if (parser.getName().equals(TAG_RUNTIME_PERMISSIONS)) {
+                return parseRuntimePermissions(parser);
+            }
+        }
+        throw new IllegalStateException("Missing <" + TAG_RUNTIME_PERMISSIONS
+                + "> in runtime-permissions.xml");
+    }
+
+    @NonNull
+    private static RuntimePermissionsState parseRuntimePermissions(@NonNull XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        String versionValue = parser.getAttributeValue(null, ATTRIBUTE_VERSION);
+        int version = versionValue != null ? Integer.parseInt(versionValue)
+                : RuntimePermissionsState.NO_VERSION;
+        String fingerprint = parser.getAttributeValue(null, ATTRIBUTE_FINGERPRINT);
+
+        Map<String, List<RuntimePermissionsState.PermissionState>> packagePermissions =
+                new ArrayMap<>();
+        Map<String, List<RuntimePermissionsState.PermissionState>> sharedUserPermissions =
+                new ArrayMap<>();
+        int type;
+        int depth;
+        int innerDepth = parser.getDepth() + 1;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
+            if (depth > innerDepth || type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            switch (parser.getName()) {
+                case TAG_PACKAGE: {
+                    String packageName = parser.getAttributeValue(null, ATTRIBUTE_NAME);
+                    List<RuntimePermissionsState.PermissionState> permissions = parsePermissions(
+                            parser);
+                    packagePermissions.put(packageName, permissions);
+                    break;
+                }
+                case TAG_SHARED_USER: {
+                    String sharedUserName = parser.getAttributeValue(null, ATTRIBUTE_NAME);
+                    List<RuntimePermissionsState.PermissionState> permissions = parsePermissions(
+                            parser);
+                    sharedUserPermissions.put(sharedUserName, permissions);
+                    break;
+                }
+            }
+        }
+
+        return new RuntimePermissionsState(version, fingerprint, packagePermissions,
+                sharedUserPermissions);
+    }
+
+    @NonNull
+    private static List<RuntimePermissionsState.PermissionState> parsePermissions(
+            @NonNull XmlPullParser parser) throws IOException, XmlPullParserException {
+        List<RuntimePermissionsState.PermissionState> permissions = new ArrayList<>();
+        int type;
+        int depth;
+        int innerDepth = parser.getDepth() + 1;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
+            if (depth > innerDepth || type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            if (parser.getName().equals(TAG_PERMISSION)) {
+                String name = parser.getAttributeValue(null, ATTRIBUTE_NAME);
+                boolean granted = Boolean.parseBoolean(parser.getAttributeValue(null,
+                        ATTRIBUTE_GRANTED));
+                int flags = Integer.parseInt(parser.getAttributeValue(null,
+                        ATTRIBUTE_FLAGS), 16);
+                RuntimePermissionsState.PermissionState permission =
+                        new RuntimePermissionsState.PermissionState(name, granted, flags);
+                permissions.add(permission);
+            }
+        }
+        return permissions;
+    }
+
+    @Override
+    public void write(@NonNull RuntimePermissionsState runtimePermissions,
+            @NonNull UserHandle user) {
+        File file = getFile(user);
+        AtomicFile atomicFile = new AtomicFile(file);
+        FileOutputStream outputStream = null;
+        try {
+            outputStream = atomicFile.startWrite();
+
+            XmlSerializer serializer = Xml.newSerializer();
+            serializer.setOutput(outputStream, StandardCharsets.UTF_8.name());
+            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+            serializer.startDocument(null, true);
+
+            serializeRuntimePermissions(serializer, runtimePermissions);
+
+            serializer.endDocument();
+            atomicFile.finishWrite(outputStream);
+        } catch (Exception e) {
+            Log.wtf(LOG_TAG, "Failed to write runtime-permissions.xml, restoring backup: " + file,
+                    e);
+            atomicFile.failWrite(outputStream);
+        } finally {
+            IoUtils.closeQuietly(outputStream);
+        }
+    }
+
+    private static void serializeRuntimePermissions(@NonNull XmlSerializer serializer,
+            @NonNull RuntimePermissionsState runtimePermissions) throws IOException {
+        serializer.startTag(null, TAG_RUNTIME_PERMISSIONS);
+
+        int version = runtimePermissions.getVersion();
+        serializer.attribute(null, ATTRIBUTE_VERSION, Integer.toString(version));
+        String fingerprint = runtimePermissions.getFingerprint();
+        if (fingerprint != null) {
+            serializer.attribute(null, ATTRIBUTE_FINGERPRINT, fingerprint);
+        }
+
+        for (Map.Entry<String, List<RuntimePermissionsState.PermissionState>> entry
+                : runtimePermissions.getPackagePermissions().entrySet()) {
+            String packageName = entry.getKey();
+            List<RuntimePermissionsState.PermissionState> permissions = entry.getValue();
+
+            serializer.startTag(null, TAG_PACKAGE);
+            serializer.attribute(null, ATTRIBUTE_NAME, packageName);
+            serializePermissions(serializer, permissions);
+            serializer.endTag(null, TAG_PACKAGE);
+        }
+
+        for (Map.Entry<String, List<RuntimePermissionsState.PermissionState>> entry
+                : runtimePermissions.getSharedUserPermissions().entrySet()) {
+            String sharedUserName = entry.getKey();
+            List<RuntimePermissionsState.PermissionState> permissions = entry.getValue();
+
+            serializer.startTag(null, TAG_SHARED_USER);
+            serializer.attribute(null, ATTRIBUTE_NAME, sharedUserName);
+            serializePermissions(serializer, permissions);
+            serializer.endTag(null, TAG_SHARED_USER);
+        }
+
+        serializer.endTag(null, TAG_RUNTIME_PERMISSIONS);
+    }
+
+    private static void serializePermissions(@NonNull XmlSerializer serializer,
+            @NonNull List<RuntimePermissionsState.PermissionState> permissions) throws IOException {
+        int permissionsSize = permissions.size();
+        for (int i = 0; i < permissionsSize; i++) {
+            RuntimePermissionsState.PermissionState permissionState = permissions.get(i);
+
+            serializer.startTag(null, TAG_PERMISSION);
+            serializer.attribute(null, ATTRIBUTE_NAME, permissionState.getName());
+            serializer.attribute(null, ATTRIBUTE_GRANTED, Boolean.toString(
+                    permissionState.isGranted()));
+            serializer.attribute(null, ATTRIBUTE_FLAGS, Integer.toHexString(
+                    permissionState.getFlags()));
+            serializer.endTag(null, TAG_PERMISSION);
+        }
+    }
+
+    @Override
+    public void delete(@NonNull UserHandle user) {
+        getFile(user).delete();
+    }
+
+    @NonNull
+    private static File getFile(@NonNull UserHandle user) {
+        ApexContext apexContext = ApexContext.getApexContext(APEX_MODULE_NAME);
+        File dataDirectory = apexContext.getDeviceProtectedDataDirForUser(user);
+        return new File(dataDirectory, RUNTIME_PERMISSIONS_FILE_NAME);
+    }
+}
diff --git a/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsState.java b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsState.java
new file mode 100644
index 0000000..2a939e5
--- /dev/null
+++ b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsState.java
@@ -0,0 +1,131 @@
+/*
+ * 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.permission.persistence;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * State of all runtime permissions.
+ *
+ * TODO(b/147914847): Remove @hide when it becomes the default.
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES, process = SystemApi.Process.SYSTEM_SERVER)
+public final class RuntimePermissionsState {
+
+    /**
+     * Special value for {@link #mVersion} to indicate that no version was read.
+     */
+    public static final int NO_VERSION = -1;
+
+    /**
+     * The version of the runtime permissions.
+     */
+    private final int mVersion;
+
+    /**
+     * The fingerprint of the runtime permissions.
+     */
+    @Nullable
+    private final String mFingerprint;
+
+    /**
+     * The runtime permissions by packages.
+     */
+    @NonNull
+    private final Map<String, List<PermissionState>> mPackagePermissions;
+
+    /**
+     * The runtime permissions by shared users.
+     */
+    @NonNull
+    private final Map<String, List<PermissionState>> mSharedUserPermissions;
+
+    public RuntimePermissionsState(int version, @Nullable String fingerprint,
+            @NonNull Map<String, List<PermissionState>> packagePermissions,
+            @NonNull Map<String, List<PermissionState>> sharedUserPermissions) {
+        mVersion = version;
+        mFingerprint = fingerprint;
+        mPackagePermissions = packagePermissions;
+        mSharedUserPermissions = sharedUserPermissions;
+    }
+
+    public int getVersion() {
+        return mVersion;
+    }
+
+    @Nullable
+    public String getFingerprint() {
+        return mFingerprint;
+    }
+
+    @NonNull
+    public Map<String, List<PermissionState>> getPackagePermissions() {
+        return mPackagePermissions;
+    }
+
+    @NonNull
+    public Map<String, List<PermissionState>> getSharedUserPermissions() {
+        return mSharedUserPermissions;
+    }
+
+    /**
+     * State of a single permission.
+     */
+    public static class PermissionState {
+
+        /**
+         * Name of the permission.
+         */
+        @NonNull
+        private final String mName;
+
+        /**
+         * Whether the permission is granted.
+         */
+        private final boolean mGranted;
+
+        /**
+         * Flags of the permission.
+         */
+        private final int mFlags;
+
+        public PermissionState(@NonNull String name, boolean granted, int flags) {
+            mName = name;
+            mGranted = granted;
+            mFlags = flags;
+        }
+
+        @NonNull
+        public String getName() {
+            return mName;
+        }
+
+        public boolean isGranted() {
+            return mGranted;
+        }
+
+        public int getFlags() {
+            return mFlags;
+        }
+    }
+}
diff --git a/apex/permission/service/java/com/android/role/persistence/RolesPersistence.java b/apex/permission/service/java/com/android/role/persistence/RolesPersistence.java
new file mode 100644
index 0000000..63c8eed
--- /dev/null
+++ b/apex/permission/service/java/com/android/role/persistence/RolesPersistence.java
@@ -0,0 +1,72 @@
+/*
+ * 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.role.persistence;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.UserHandle;
+
+/**
+ * Persistence for roles.
+ *
+ * TODO(b/147914847): Remove @hide when it becomes the default.
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES, process = SystemApi.Process.SYSTEM_SERVER)
+public interface RolesPersistence {
+
+    /**
+     * Read the roles from persistence.
+     *
+     * This will perform I/O operations synchronously.
+     *
+     * @param user the user to read for
+     * @return the roles read
+     */
+    @Nullable
+    RolesState read(@NonNull UserHandle user);
+
+    /**
+     * Write the roles to persistence.
+     *
+     * This will perform I/O operations synchronously.
+     *
+     * @param roles the roles to write
+     * @param user the user to write for
+     */
+    void write(@NonNull RolesState roles, @NonNull UserHandle user);
+
+    /**
+     * Delete the roles from persistence.
+     *
+     * This will perform I/O operations synchronously.
+     *
+     * @param user the user to delete for
+     */
+    void delete(@NonNull UserHandle user);
+
+    /**
+     * Create a new instance of {@link RolesPersistence} implementation.
+     *
+     * @return the new instance.
+     */
+    @NonNull
+    static RolesPersistence createInstance() {
+        return new RolesPersistenceImpl();
+    }
+}
diff --git a/apex/permission/service/java/com/android/role/persistence/RolesPersistenceImpl.java b/apex/permission/service/java/com/android/role/persistence/RolesPersistenceImpl.java
new file mode 100644
index 0000000..06fad77
--- /dev/null
+++ b/apex/permission/service/java/com/android/role/persistence/RolesPersistenceImpl.java
@@ -0,0 +1,219 @@
+/*
+ * 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.role.persistence;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ApexContext;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.permission.persistence.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Persistence implementation for roles.
+ *
+ * TODO(b/147914847): Remove @hide when it becomes the default.
+ * @hide
+ */
+public class RolesPersistenceImpl implements RolesPersistence {
+
+    private static final String LOG_TAG = RolesPersistenceImpl.class.getSimpleName();
+
+    private static final String APEX_MODULE_NAME = "com.android.permission";
+
+    private static final String ROLES_FILE_NAME = "roles.xml";
+
+    private static final String TAG_ROLES = "roles";
+    private static final String TAG_ROLE = "role";
+    private static final String TAG_HOLDER = "holder";
+
+    private static final String ATTRIBUTE_VERSION = "version";
+    private static final String ATTRIBUTE_NAME = "name";
+    private static final String ATTRIBUTE_PACKAGES_HASH = "packagesHash";
+
+    @Nullable
+    @Override
+    public RolesState read(@NonNull UserHandle user) {
+        File file = getFile(user);
+        try (FileInputStream inputStream = new AtomicFile(file).openRead()) {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(inputStream, null);
+            return parseXml(parser);
+        } catch (FileNotFoundException e) {
+            Log.i(LOG_TAG, "roles.xml not found");
+            return null;
+        } catch (XmlPullParserException | IOException e) {
+            throw new IllegalStateException("Failed to read roles.xml: " + file , e);
+        }
+    }
+
+    @NonNull
+    private static RolesState parseXml(@NonNull XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        int type;
+        int depth;
+        int innerDepth = parser.getDepth() + 1;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
+            if (depth > innerDepth || type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            if (parser.getName().equals(TAG_ROLES)) {
+                return parseRoles(parser);
+            }
+        }
+        throw new IllegalStateException("Missing <" + TAG_ROLES + "> in roles.xml");
+    }
+
+    @NonNull
+    private static RolesState parseRoles(@NonNull XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        int version = Integer.parseInt(parser.getAttributeValue(null, ATTRIBUTE_VERSION));
+        String packagesHash = parser.getAttributeValue(null, ATTRIBUTE_PACKAGES_HASH);
+
+        Map<String, Set<String>> roles = new ArrayMap<>();
+        int type;
+        int depth;
+        int innerDepth = parser.getDepth() + 1;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
+            if (depth > innerDepth || type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            if (parser.getName().equals(TAG_ROLE)) {
+                String roleName = parser.getAttributeValue(null, ATTRIBUTE_NAME);
+                Set<String> roleHolders = parseRoleHolders(parser);
+                roles.put(roleName, roleHolders);
+            }
+        }
+
+        return new RolesState(version, packagesHash, roles);
+    }
+
+    @NonNull
+    private static Set<String> parseRoleHolders(@NonNull XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        Set<String> roleHolders = new ArraySet<>();
+        int type;
+        int depth;
+        int innerDepth = parser.getDepth() + 1;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
+            if (depth > innerDepth || type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            if (parser.getName().equals(TAG_HOLDER)) {
+                String roleHolder = parser.getAttributeValue(null, ATTRIBUTE_NAME);
+                roleHolders.add(roleHolder);
+            }
+        }
+        return roleHolders;
+    }
+
+    @Override
+    public void write(@NonNull RolesState roles,
+            @NonNull UserHandle user) {
+        File file = getFile(user);
+        AtomicFile atomicFile = new AtomicFile(file);
+        FileOutputStream outputStream = null;
+        try {
+            outputStream = atomicFile.startWrite();
+
+            XmlSerializer serializer = Xml.newSerializer();
+            serializer.setOutput(outputStream, StandardCharsets.UTF_8.name());
+            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+            serializer.startDocument(null, true);
+
+            serializeRoles(serializer, roles);
+
+            serializer.endDocument();
+            atomicFile.finishWrite(outputStream);
+        } catch (Exception e) {
+            Log.wtf(LOG_TAG, "Failed to write roles.xml, restoring backup: " + file,
+                    e);
+            atomicFile.failWrite(outputStream);
+        } finally {
+            IoUtils.closeQuietly(outputStream);
+        }
+    }
+
+    private static void serializeRoles(@NonNull XmlSerializer serializer,
+            @NonNull RolesState roles) throws IOException {
+        serializer.startTag(null, TAG_ROLES);
+
+        int version = roles.getVersion();
+        serializer.attribute(null, ATTRIBUTE_VERSION, Integer.toString(version));
+        String packagesHash = roles.getPackagesHash();
+        if (packagesHash != null) {
+            serializer.attribute(null, ATTRIBUTE_PACKAGES_HASH, packagesHash);
+        }
+
+        for (Map.Entry<String, Set<String>> entry : roles.getRoles().entrySet()) {
+            String roleName = entry.getKey();
+            Set<String> roleHolders = entry.getValue();
+
+            serializer.startTag(null, TAG_ROLE);
+            serializer.attribute(null, ATTRIBUTE_NAME, roleName);
+            serializeRoleHolders(serializer, roleHolders);
+            serializer.endTag(null, TAG_ROLE);
+        }
+
+        serializer.endTag(null, TAG_ROLES);
+    }
+
+    private static void serializeRoleHolders(@NonNull XmlSerializer serializer,
+            @NonNull Set<String> roleHolders) throws IOException {
+        for (String roleHolder : roleHolders) {
+            serializer.startTag(null, TAG_HOLDER);
+            serializer.attribute(null, ATTRIBUTE_NAME, roleHolder);
+            serializer.endTag(null, TAG_HOLDER);
+        }
+    }
+
+    @Override
+    public void delete(@NonNull UserHandle user) {
+        getFile(user).delete();
+    }
+
+    @NonNull
+    private static File getFile(@NonNull UserHandle user) {
+        ApexContext apexContext = ApexContext.getApexContext(APEX_MODULE_NAME);
+        File dataDirectory = apexContext.getDeviceProtectedDataDirForUser(user);
+        return new File(dataDirectory, ROLES_FILE_NAME);
+    }
+}
diff --git a/apex/permission/service/java/com/android/role/persistence/RolesState.java b/apex/permission/service/java/com/android/role/persistence/RolesState.java
new file mode 100644
index 0000000..bff980e
--- /dev/null
+++ b/apex/permission/service/java/com/android/role/persistence/RolesState.java
@@ -0,0 +1,72 @@
+/*
+ * 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.role.persistence;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * State of all roles.
+ *
+ * TODO(b/147914847): Remove @hide when it becomes the default.
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES, process = SystemApi.Process.SYSTEM_SERVER)
+public final class RolesState {
+
+    /**
+     * The version of the roles.
+     */
+    private final int mVersion;
+
+    /**
+     * The hash of all packages in the system.
+     */
+    @Nullable
+    private final String mPackagesHash;
+
+    /**
+     * The roles.
+     */
+    @NonNull
+    private final Map<String, Set<String>> mRoles;
+
+    public RolesState(int version, @Nullable String packagesHash,
+            @NonNull Map<String, Set<String>> roles) {
+        mVersion = version;
+        mPackagesHash = packagesHash;
+        mRoles = roles;
+    }
+
+    public int getVersion() {
+        return mVersion;
+    }
+
+    @Nullable
+    public String getPackagesHash() {
+        return mPackagesHash;
+    }
+
+    @NonNull
+    public Map<String, Set<String>> getRoles() {
+        return mRoles;
+    }
+}
diff --git a/apex/sdkextensions/TEST_MAPPING b/apex/sdkextensions/TEST_MAPPING
index 7e77623..4e18833 100644
--- a/apex/sdkextensions/TEST_MAPPING
+++ b/apex/sdkextensions/TEST_MAPPING
@@ -1,7 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "CtsSdkExtTestCases"
+      "name": "CtsSdkExtensionsTestCases"
     },
     {
       "name": "apiextensions_e2e_tests"
diff --git a/apex/sdkextensions/framework/Android.bp b/apex/sdkextensions/framework/Android.bp
index 5504f4e..dd17473 100644
--- a/apex/sdkextensions/framework/Android.bp
+++ b/apex/sdkextensions/framework/Android.bp
@@ -36,6 +36,11 @@
         "//frameworks/base/apex/sdkextensions",
         "//frameworks/base/apex/sdkextensions/testing",
     ],
+    hostdex: true, // for hiddenapi check
+    apex_available: [
+        "com.android.sdkext",
+        "test_com.android.sdkext",
+    ],
 }
 
 droidstubs {
diff --git a/apex/sdkextensions/manifest.json b/apex/sdkextensions/manifest.json
index 048f5c4..deeb29e 100644
--- a/apex/sdkextensions/manifest.json
+++ b/apex/sdkextensions/manifest.json
@@ -1,4 +1,4 @@
 {
   "name": "com.android.sdkext",
-  "version": 1
+  "version": 300000000
 }
diff --git a/apex/statsd/aidl/Android.bp b/apex/statsd/aidl/Android.bp
index f8325d4..cc5172c6 100644
--- a/apex/statsd/aidl/Android.bp
+++ b/apex/statsd/aidl/Android.bp
@@ -23,7 +23,6 @@
         "android/os/IPullAtomResultReceiver.aidl",
         "android/os/IStatsCompanionService.aidl",
         "android/os/IStatsd.aidl",
-        "android/os/IStatsPullerCallback.aidl",
         "android/util/StatsEventParcel.aidl",
     ],
 }
diff --git a/apex/statsd/aidl/android/os/IStatsCompanionService.aidl b/apex/statsd/aidl/android/os/IStatsCompanionService.aidl
index 21b7767..bdd1da7 100644
--- a/apex/statsd/aidl/android/os/IStatsCompanionService.aidl
+++ b/apex/statsd/aidl/android/os/IStatsCompanionService.aidl
@@ -16,9 +16,6 @@
 
 package android.os;
 
-import android.os.IPullAtomCallback;
-import android.os.StatsLogEventWrapper;
-
 /**
   * Binder interface to communicate with the Java-based statistics service helper.
   * {@hide}
@@ -62,16 +59,6 @@
     /** Cancel any alarm for the purpose of subscriber triggering. */
     oneway void cancelAlarmForSubscriberTriggering();
 
-    /** Pull the specified data. Results will be sent to statsd when complete. */
-    StatsLogEventWrapper[] pullData(int pullCode);
-
     /** Tells StatsCompaionService to grab the uid map snapshot and send it to statsd. */
     oneway void triggerUidSnapshot();
-
-    /** Tells StatsCompanionService to tell statsd to register a puller for the given atom id */
-    oneway void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs,
-            in int[] additiveFields, IPullAtomCallback pullerCallback);
-
-    /** Tells StatsCompanionService to tell statsd to unregister a puller for the given atom id */
-    oneway void unregisterPullAtomCallback(int atomTag);
 }
diff --git a/apex/statsd/aidl/android/os/IStatsManagerService.aidl b/apex/statsd/aidl/android/os/IStatsManagerService.aidl
index 2a3665c..4a259f5 100644
--- a/apex/statsd/aidl/android/os/IStatsManagerService.aidl
+++ b/apex/statsd/aidl/android/os/IStatsManagerService.aidl
@@ -17,6 +17,7 @@
 package android.os;
 
 import android.app.PendingIntent;
+import android.os.IPullAtomCallback;
 
 /**
   * Binder interface to communicate with the Java-based statistics service helper.
@@ -107,4 +108,29 @@
      * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
      */
     byte[] getData(in long key, in String packageName);
-}
\ No newline at end of file
+
+    /**
+     * Sets a configuration with the specified config id and subscribes to updates for this
+     * configuration id. Broadcasts will be sent if this configuration needs to be collected.
+     * The configuration must be a wire-encoded StatsdConfig. The receiver for this data is
+     * registered in a separate function.
+     *
+     * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
+     */
+    void addConfiguration(in long configId, in byte[] config, in String packageName);
+
+    /**
+     * Removes the configuration with the matching config id. No-op if this config id does not
+     * exist.
+     *
+     * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
+     */
+    void removeConfiguration(in long configId, in String packageName);
+
+    /** Tell StatsManagerService to register a puller for the given atom tag with statsd. */
+    oneway void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs,
+            in int[] additiveFields, IPullAtomCallback pullerCallback);
+
+    /** Tell StatsManagerService to unregister the pulller for the given atom tag from statsd. */
+    oneway void unregisterPullAtomCallback(int atomTag);
+}
diff --git a/apex/statsd/aidl/android/os/IStatsPullerCallback.aidl b/apex/statsd/aidl/android/os/IStatsPullerCallback.aidl
deleted file mode 100644
index c3e1e55..0000000
--- a/apex/statsd/aidl/android/os/IStatsPullerCallback.aidl
+++ /dev/null
@@ -1,36 +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.os.StatsLogEventWrapper;
-
-/**
-  * DEPRECATED
-  * Binder interface to pull atoms for the stats service.
-  * {@hide}
-  */
-interface IStatsPullerCallback {
-    /**
-     * Pull data for the specified atom tag. Returns an array of StatsLogEventWrapper containing
-     * the data.
-     *
-     * Note: These pulled atoms should not have uid/attribution chain. Additionally, the event
-     * timestamps will be truncated to the nearest 5 minutes.
-     */
-    StatsLogEventWrapper[] pullData(int atomTag, long elapsedNanos, long wallClocknanos);
-
-}
diff --git a/apex/statsd/aidl/android/os/IStatsd.aidl b/apex/statsd/aidl/android/os/IStatsd.aidl
index c08abdd..a2564212 100644
--- a/apex/statsd/aidl/android/os/IStatsd.aidl
+++ b/apex/statsd/aidl/android/os/IStatsd.aidl
@@ -16,7 +16,6 @@
 
 package android.os;
 
-import android.os.IStatsPullerCallback;
 import android.os.IPendingIntentRef;
 import android.os.IPullAtomCallback;
 import android.os.ParcelFileDescriptor;
@@ -99,14 +98,14 @@
     byte[] getMetadata();
 
     /**
-     * Sets a configuration with the specified config key and subscribes to updates for this
+     * Sets a configuration with the specified config id and subscribes to updates for this
      * configuration key. Broadcasts will be sent if this configuration needs to be collected.
      * The configuration must be a wire-encoded StatsdConfig. The receiver for this data is
      * registered in a separate function.
      *
      * Requires Manifest.permission.DUMP.
      */
-    void addConfiguration(in long configKey, in byte[] config, in String packageName);
+    void addConfiguration(in long configId, in byte[] config, in int callingUid);
 
     /**
      * Registers the given pending intent for this config key. This intent is invoked when the
@@ -143,12 +142,12 @@
     void removeActiveConfigsChangedOperation(int callingUid);
 
     /**
-     * Removes the configuration with the matching config key. No-op if this config key does not
+     * Removes the configuration with the matching config id. No-op if this config id does not
      * exist.
      *
      * Requires Manifest.permission.DUMP.
      */
-    void removeConfiguration(in long configKey, in String packageName);
+    void removeConfiguration(in long configId, in int callingUid);
 
     /**
      * Set the PendingIntentRef to be used when broadcasting subscriber
@@ -183,16 +182,6 @@
      */
     void sendAppBreadcrumbAtom(int label, int state);
 
-    /**
-     * Registers a puller callback function that, when invoked, pulls the data
-     * for the specified vendor atom tag.
-     *
-     * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS
-     * @deprecated please use registerPullAtomCallback.
-     */
-    oneway void registerPullerCallback(int atomTag, IStatsPullerCallback pullerCallback,
-                                       String packageName);
-
    /**
     * Registers a puller callback function that, when invoked, pulls the data
     * for the specified atom tag.
@@ -207,17 +196,15 @@
     oneway void registerNativePullAtomCallback(int atomTag, long coolDownNs, long timeoutNs,
                            in int[] additiveFields, IPullAtomCallback pullerCallback);
 
-   /**
-    * Unregisters a puller callback function for the given vendor atom.
-    *
-    * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS
-    */
-   oneway void unregisterPullerCallback(int atomTag, String packageName);
+    /**
+     * Unregisters any pullAtomCallback for the given uid/atom.
+     */
+    oneway void unregisterPullAtomCallback(int uid, int atomTag);
 
-  /**
-   * Unregisters any pullAtomCallback for the given uid/atom.
-   */
-   oneway void unregisterPullAtomCallback(int uid, int atomTag);
+    /**
+     * Unregisters any pullAtomCallback for the given atom.
+     */
+    oneway void unregisterNativePullAtomCallback(int atomTag);
 
     /**
      * The install requires staging.
diff --git a/apex/statsd/apex_manifest.json b/apex/statsd/apex_manifest.json
index 0c0ad86..e2972e7 100644
--- a/apex/statsd/apex_manifest.json
+++ b/apex/statsd/apex_manifest.json
@@ -1,5 +1,5 @@
 {
   "name": "com.android.os.statsd",
-  "version": 1
+  "version": 300000000
 }
 
diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp
index a2b0577..0b46645a 100644
--- a/apex/statsd/framework/Android.bp
+++ b/apex/statsd/framework/Android.bp
@@ -37,7 +37,16 @@
         // TODO(b/146230220): Use framework-system-stubs instead.
         "android_system_stubs_current",
     ],
-    // TODO:(b/146210774): Add apex_available field.
+    hostdex: true, // for hiddenapi check
+    visibility: [
+        "//frameworks/base/apex/statsd:__subpackages__",
+        //TODO(b/146167933) remove this when framework is built with framework-statsd-stubs
+        "//frameworks/base",
+    ],
+    apex_available: [
+        "com.android.os.statsd",
+        "test_com.android.os.statsd",
+    ],
 }
 
 droidstubs {
diff --git a/apex/statsd/framework/java/android/util/StatsEvent.java b/apex/statsd/framework/java/android/util/StatsEvent.java
index c765945..1a45c4a 100644
--- a/apex/statsd/framework/java/android/util/StatsEvent.java
+++ b/apex/statsd/framework/java/android/util/StatsEvent.java
@@ -20,6 +20,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.os.SystemClock;
 
 import com.android.internal.annotations.GuardedBy;
@@ -51,6 +52,7 @@
  * </pre>
  * @hide
  **/
+@SystemApi
 public final class StatsEvent {
     // Type Ids.
     /**
@@ -270,6 +272,8 @@
     /**
      * Recycle resources used by this StatsEvent object.
      * No actions should be taken on this StatsEvent after release() is called.
+     *
+     * @hide
      **/
     public void release() {
         if (mBuffer != null) {
@@ -363,16 +367,6 @@
         }
 
         /**
-         * Sets the timestamp in nanos for this StatsEvent.
-         **/
-        @VisibleForTesting
-        @NonNull
-        public Builder setTimestampNs(final long timestampNs) {
-            mTimestampNs = timestampNs;
-            return this;
-        }
-
-        /**
          * Write a boolean field to this StatsEvent.
          **/
         @NonNull
@@ -500,14 +494,14 @@
          **/
         @NonNull
         public Builder writeKeyValuePairs(
-                @NonNull final SparseIntArray intMap,
-                @NonNull final SparseLongArray longMap,
-                @NonNull final SparseArray<String> stringMap,
-                @NonNull final SparseArray<Float> floatMap) {
-            final int intMapSize = intMap.size();
-            final int longMapSize = longMap.size();
-            final int stringMapSize = stringMap.size();
-            final int floatMapSize = floatMap.size();
+                @Nullable final SparseIntArray intMap,
+                @Nullable final SparseLongArray longMap,
+                @Nullable final SparseArray<String> stringMap,
+                @Nullable final SparseArray<Float> floatMap) {
+            final int intMapSize = null == intMap ? 0 : intMap.size();
+            final int longMapSize = null == longMap ? 0 : longMap.size();
+            final int stringMapSize = null == stringMap ? 0 : stringMap.size();
+            final int floatMapSize = null == floatMap ? 0 : floatMap.size();
             final int totalCount = intMapSize + longMapSize + stringMapSize + floatMapSize;
 
             if (totalCount > MAX_KEY_VALUE_PAIRS) {
diff --git a/apex/statsd/service/Android.bp b/apex/statsd/service/Android.bp
index f3a8989..9103848 100644
--- a/apex/statsd/service/Android.bp
+++ b/apex/statsd/service/Android.bp
@@ -13,4 +13,8 @@
         "framework-minus-apex",
         "services.core",
     ],
+    apex_available: [
+        "com.android.os.statsd",
+        "test_com.android.os.statsd",
+    ],
 }
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
index d57afee..bcbb5a1 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
@@ -24,11 +24,11 @@
 import static android.os.storage.VolumeInfo.TYPE_PUBLIC;
 
 import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
-import static com.android.server.stats.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
-import static com.android.server.stats.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
-import static com.android.server.stats.ProcfsMemoryUtil.forEachPid;
-import static com.android.server.stats.ProcfsMemoryUtil.readCmdlineFromProcfs;
-import static com.android.server.stats.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
+import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
+import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
+import static com.android.server.stats.pull.ProcfsMemoryUtil.forEachPid;
+import static com.android.server.stats.pull.ProcfsMemoryUtil.readCmdlineFromProcfs;
+import static com.android.server.stats.pull.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -75,7 +75,6 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
-import android.os.IPullAtomCallback;
 import android.os.IStatsCompanionService;
 import android.os.IStatsd;
 import android.os.IStoraged;
@@ -136,8 +135,8 @@
 import com.android.server.am.MemoryStatUtil.MemoryStat;
 import com.android.server.notification.NotificationManagerService;
 import com.android.server.role.RoleManagerInternal;
-import com.android.server.stats.IonMemoryUtil.IonAllocations;
-import com.android.server.stats.ProcfsMemoryUtil.MemorySnapshot;
+import com.android.server.stats.pull.IonMemoryUtil.IonAllocations;
+import com.android.server.stats.pull.ProcfsMemoryUtil.MemorySnapshot;
 import com.android.server.storage.DiskStatsFileLogger;
 import com.android.server.storage.DiskStatsLoggingService;
 
@@ -263,71 +262,6 @@
 
     private StatsManagerService mStatsManagerService;
 
-    private static final class PullerKey {
-        private final int mUid;
-        private final int mAtomTag;
-
-        PullerKey(int uid, int atom) {
-            mUid = uid;
-            mAtomTag = atom;
-        }
-
-        public int getUid() {
-            return mUid;
-        }
-
-        public int getAtom() {
-            return mAtomTag;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(mUid, mAtomTag);
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (obj instanceof PullerKey) {
-                PullerKey other = (PullerKey) obj;
-                return this.mUid == other.getUid() && this.mAtomTag == other.getAtom();
-            }
-            return false;
-        }
-    }
-
-    private static final class PullerValue {
-        private final long mCoolDownNs;
-        private final long mTimeoutNs;
-        private int[] mAdditiveFields;
-        private IPullAtomCallback mCallback;
-
-        PullerValue(long coolDownNs, long timeoutNs, int[] additiveFields,
-                IPullAtomCallback callback) {
-            mCoolDownNs = coolDownNs;
-            mTimeoutNs = timeoutNs;
-            mAdditiveFields = additiveFields;
-            mCallback = callback;
-        }
-
-        public long getCoolDownNs() {
-            return mCoolDownNs;
-        }
-
-        public long getTimeoutNs() {
-            return mTimeoutNs;
-        }
-
-        public int[] getAdditiveFields() {
-            return mAdditiveFields;
-        }
-
-        public IPullAtomCallback getCallback() {
-            return mCallback;
-        }
-    }
-
-    private final HashMap<PullerKey, PullerValue> mPullers = new HashMap<>();
-
     private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
     private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
     private WifiManager mWifiManager = null;
@@ -780,1833 +714,6 @@
         }
     }
 
-    private void addNetworkStats(
-            int tag, List<StatsLogEventWrapper> ret, NetworkStats stats, boolean withFGBG) {
-        int size = stats.size();
-        long elapsedNanos = SystemClock.elapsedRealtimeNanos();
-        long wallClockNanos = SystemClock.currentTimeMicro() * 1000L;
-        NetworkStats.Entry entry = new NetworkStats.Entry(); // For recycling
-        for (int j = 0; j < size; j++) {
-            stats.getValues(j, entry);
-            StatsLogEventWrapper e = new StatsLogEventWrapper(tag, elapsedNanos, wallClockNanos);
-            e.writeInt(entry.uid);
-            if (withFGBG) {
-                e.writeInt(entry.set);
-            }
-            e.writeLong(entry.rxBytes);
-            e.writeLong(entry.rxPackets);
-            e.writeLong(entry.txBytes);
-            e.writeLong(entry.txPackets);
-            ret.add(e);
-        }
-    }
-
-    /**
-     * Allows rollups per UID but keeping the set (foreground/background) slicing.
-     * Adapted from groupedByUid in frameworks/base/core/java/android/net/NetworkStats.java
-     */
-    private NetworkStats rollupNetworkStatsByFGBG(NetworkStats stats) {
-        final NetworkStats ret = new NetworkStats(stats.getElapsedRealtime(), 1);
-
-        final NetworkStats.Entry entry = new NetworkStats.Entry();
-        entry.iface = NetworkStats.IFACE_ALL;
-        entry.tag = NetworkStats.TAG_NONE;
-        entry.metered = NetworkStats.METERED_ALL;
-        entry.roaming = NetworkStats.ROAMING_ALL;
-
-        int size = stats.size();
-        NetworkStats.Entry recycle = new NetworkStats.Entry(); // Used for retrieving values
-        for (int i = 0; i < size; i++) {
-            stats.getValues(i, recycle);
-
-            // Skip specific tags, since already counted in TAG_NONE
-            if (recycle.tag != NetworkStats.TAG_NONE) continue;
-
-            entry.set = recycle.set; // Allows slicing by background/foreground
-            entry.uid = recycle.uid;
-            entry.rxBytes = recycle.rxBytes;
-            entry.rxPackets = recycle.rxPackets;
-            entry.txBytes = recycle.txBytes;
-            entry.txPackets = recycle.txPackets;
-            // Operations purposefully omitted since we don't use them for statsd.
-            ret.combineValues(entry);
-        }
-        return ret;
-    }
-
-    /**
-     * Helper method to extract the Parcelable controller info from a
-     * SynchronousResultReceiver.
-     */
-    private static <T extends Parcelable> T awaitControllerInfo(
-            @Nullable SynchronousResultReceiver receiver) {
-        if (receiver == null) {
-            return null;
-        }
-
-        try {
-            final SynchronousResultReceiver.Result result =
-                    receiver.awaitResult(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS);
-            if (result.bundle != null) {
-                // This is the final destination for the Bundle.
-                result.bundle.setDefusable(true);
-
-                final T data = result.bundle.getParcelable(
-                        RESULT_RECEIVER_CONTROLLER_KEY);
-                if (data != null) {
-                    return data;
-                }
-            }
-            Slog.e(TAG, "no controller energy info supplied for " + receiver.getName());
-        } catch (TimeoutException e) {
-            Slog.w(TAG, "timeout reading " + receiver.getName() + " stats");
-        }
-        return null;
-    }
-
-    private void pullKernelWakelock(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        final KernelWakelockStats wakelockStats =
-                mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats);
-        for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
-            String name = ent.getKey();
-            KernelWakelockStats.Entry kws = ent.getValue();
-            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeString(name);
-            e.writeInt(kws.mCount);
-            e.writeInt(kws.mVersion);
-            e.writeLong(kws.mTotalTime);
-            pulledData.add(e);
-        }
-    }
-
-    private void pullWifiBytesTransfer(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        long token = Binder.clearCallingIdentity();
-        try {
-            // TODO: Consider caching the following call to get BatteryStatsInternal.
-            BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
-            String[] ifaces = bs.getWifiIfaces();
-            if (ifaces.length == 0) {
-                return;
-            }
-            if (mNetworkStatsService == null) {
-                Slog.e(TAG, "NetworkStats Service is not available!");
-                return;
-            }
-            // Combine all the metrics per Uid into one record.
-            NetworkStats stats = mNetworkStatsService.getDetailedUidStats(ifaces).groupedByUid();
-            addNetworkStats(tagId, pulledData, stats, false);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Pulling netstats for wifi bytes has error", e);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    private void pullWifiBytesTransferByFgBg(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        long token = Binder.clearCallingIdentity();
-        try {
-            BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
-            String[] ifaces = bs.getWifiIfaces();
-            if (ifaces.length == 0) {
-                return;
-            }
-            if (mNetworkStatsService == null) {
-                Slog.e(TAG, "NetworkStats Service is not available!");
-                return;
-            }
-            NetworkStats stats = rollupNetworkStatsByFGBG(
-                    mNetworkStatsService.getDetailedUidStats(ifaces));
-            addNetworkStats(tagId, pulledData, stats, true);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Pulling netstats for wifi bytes w/ fg/bg has error", e);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    private void pullMobileBytesTransfer(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        long token = Binder.clearCallingIdentity();
-        try {
-            BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
-            String[] ifaces = bs.getMobileIfaces();
-            if (ifaces.length == 0) {
-                return;
-            }
-            if (mNetworkStatsService == null) {
-                Slog.e(TAG, "NetworkStats Service is not available!");
-                return;
-            }
-            // Combine all the metrics per Uid into one record.
-            NetworkStats stats = mNetworkStatsService.getDetailedUidStats(ifaces).groupedByUid();
-            addNetworkStats(tagId, pulledData, stats, false);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Pulling netstats for mobile bytes has error", e);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    private void pullBluetoothBytesTransfer(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        BluetoothActivityEnergyInfo info = fetchBluetoothData();
-        if (info.getUidTraffic() != null) {
-            for (UidTraffic traffic : info.getUidTraffic()) {
-                StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
-                        wallClockNanos);
-                e.writeInt(traffic.getUid());
-                e.writeLong(traffic.getRxBytes());
-                e.writeLong(traffic.getTxBytes());
-                pulledData.add(e);
-            }
-        }
-    }
-
-    private void pullMobileBytesTransferByFgBg(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        long token = Binder.clearCallingIdentity();
-        try {
-            BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
-            String[] ifaces = bs.getMobileIfaces();
-            if (ifaces.length == 0) {
-                return;
-            }
-            if (mNetworkStatsService == null) {
-                Slog.e(TAG, "NetworkStats Service is not available!");
-                return;
-            }
-            NetworkStats stats = rollupNetworkStatsByFGBG(
-                    mNetworkStatsService.getDetailedUidStats(ifaces));
-            addNetworkStats(tagId, pulledData, stats, true);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Pulling netstats for mobile bytes w/ fg/bg has error", e);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    private void pullCpuTimePerFreq(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) {
-            long[] clusterTimeMs = mKernelCpuSpeedReaders[cluster].readAbsolute();
-            if (clusterTimeMs != null) {
-                for (int speed = clusterTimeMs.length - 1; speed >= 0; --speed) {
-                    StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
-                            wallClockNanos);
-                    e.writeInt(cluster);
-                    e.writeInt(speed);
-                    e.writeLong(clusterTimeMs[speed]);
-                    pulledData.add(e);
-                }
-            }
-        }
-    }
-
-    private void pullKernelUidCpuTime(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        mCpuUidUserSysTimeReader.readAbsolute((uid, timesUs) -> {
-            long userTimeUs = timesUs[0], systemTimeUs = timesUs[1];
-            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeInt(uid);
-            e.writeLong(userTimeUs);
-            e.writeLong(systemTimeUs);
-            pulledData.add(e);
-        });
-    }
-
-    private void pullKernelUidCpuFreqTime(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        mCpuUidFreqTimeReader.readAbsolute((uid, cpuFreqTimeMs) -> {
-            for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) {
-                if (cpuFreqTimeMs[freqIndex] != 0) {
-                    StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
-                            wallClockNanos);
-                    e.writeInt(uid);
-                    e.writeInt(freqIndex);
-                    e.writeLong(cpuFreqTimeMs[freqIndex]);
-                    pulledData.add(e);
-                }
-            }
-        });
-    }
-
-    private void pullKernelUidCpuClusterTime(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        mCpuUidClusterTimeReader.readAbsolute((uid, cpuClusterTimesMs) -> {
-            for (int i = 0; i < cpuClusterTimesMs.length; i++) {
-                StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
-                        wallClockNanos);
-                e.writeInt(uid);
-                e.writeInt(i);
-                e.writeLong(cpuClusterTimesMs[i]);
-                pulledData.add(e);
-            }
-        });
-    }
-
-    private void pullKernelUidCpuActiveTime(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        mCpuUidActiveTimeReader.readAbsolute((uid, cpuActiveTimesMs) -> {
-            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeInt(uid);
-            e.writeLong((long) cpuActiveTimesMs);
-            pulledData.add(e);
-        });
-    }
-
-    private void pullWifiActivityInfo(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        WifiManager wifiManager;
-        synchronized (this) {
-            if (mWifiManager == null) {
-                mWifiManager = mContext.getSystemService(WifiManager.class);
-            }
-            wifiManager = mWifiManager;
-        }
-        if (wifiManager == null) {
-            return;
-        }
-        long token = Binder.clearCallingIdentity();
-        try {
-            SynchronousResultReceiver wifiReceiver = new SynchronousResultReceiver("wifi");
-            wifiManager.getWifiActivityEnergyInfoAsync(
-                    new Executor() {
-                        @Override
-                        public void execute(Runnable runnable) {
-                            // run the listener on the binder thread, if it was run on the main
-                            // thread it would deadlock since we would be waiting on ourselves
-                            runnable.run();
-                        }
-                    },
-                    info -> {
-                        Bundle bundle = new Bundle();
-                        bundle.putParcelable(BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY, info);
-                        wifiReceiver.send(0, bundle);
-                    }
-            );
-            final WifiActivityEnergyInfo wifiInfo = awaitControllerInfo(wifiReceiver);
-            if (wifiInfo == null) {
-                return;
-            }
-            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeLong(wifiInfo.getTimeSinceBootMillis());
-            e.writeInt(wifiInfo.getStackState());
-            e.writeLong(wifiInfo.getControllerTxDurationMillis());
-            e.writeLong(wifiInfo.getControllerRxDurationMillis());
-            e.writeLong(wifiInfo.getControllerIdleDurationMillis());
-            e.writeLong(wifiInfo.getControllerEnergyUsedMicroJoules());
-            pulledData.add(e);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    private void pullModemActivityInfo(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        long token = Binder.clearCallingIdentity();
-        synchronized (this) {
-            if (mTelephony == null) {
-                mTelephony = mContext.getSystemService(TelephonyManager.class);
-            }
-        }
-        if (mTelephony != null) {
-            SynchronousResultReceiver modemReceiver = new SynchronousResultReceiver("telephony");
-            mTelephony.requestModemActivityInfo(modemReceiver);
-            final ModemActivityInfo modemInfo = awaitControllerInfo(modemReceiver);
-            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeLong(modemInfo.getTimestamp());
-            e.writeLong(modemInfo.getSleepTimeMillis());
-            e.writeLong(modemInfo.getIdleTimeMillis());
-            e.writeLong(modemInfo.getTransmitPowerInfo().get(0).getTimeInMillis());
-            e.writeLong(modemInfo.getTransmitPowerInfo().get(1).getTimeInMillis());
-            e.writeLong(modemInfo.getTransmitPowerInfo().get(2).getTimeInMillis());
-            e.writeLong(modemInfo.getTransmitPowerInfo().get(3).getTimeInMillis());
-            e.writeLong(modemInfo.getTransmitPowerInfo().get(4).getTimeInMillis());
-            e.writeLong(modemInfo.getReceiveTimeMillis());
-            pulledData.add(e);
-        }
-    }
-
-    private void pullBluetoothActivityInfo(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        BluetoothActivityEnergyInfo info = fetchBluetoothData();
-        StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-        e.writeLong(info.getTimeStamp());
-        e.writeInt(info.getBluetoothStackState());
-        e.writeLong(info.getControllerTxTimeMillis());
-        e.writeLong(info.getControllerRxTimeMillis());
-        e.writeLong(info.getControllerIdleTimeMillis());
-        e.writeLong(info.getControllerEnergyUsed());
-        pulledData.add(e);
-    }
-
-    private synchronized BluetoothActivityEnergyInfo fetchBluetoothData() {
-        final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        if (adapter != null) {
-            SynchronousResultReceiver bluetoothReceiver = new SynchronousResultReceiver(
-                    "bluetooth");
-            adapter.requestControllerActivityEnergyInfo(bluetoothReceiver);
-            return awaitControllerInfo(bluetoothReceiver);
-        } else {
-            Slog.e(TAG, "Failed to get bluetooth adapter!");
-            return null;
-        }
-    }
-
-    private void pullSystemElapsedRealtime(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-        e.writeLong(SystemClock.elapsedRealtime());
-        pulledData.add(e);
-    }
-
-    private void pullSystemUpTime(int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-        e.writeLong(SystemClock.uptimeMillis());
-        pulledData.add(e);
-    }
-
-    private void pullProcessMemoryState(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        List<ProcessMemoryState> processMemoryStates =
-                LocalServices.getService(
-                        ActivityManagerInternal.class).getMemoryStateForProcesses();
-        for (ProcessMemoryState processMemoryState : processMemoryStates) {
-            final MemoryStat memoryStat = readMemoryStatFromFilesystem(processMemoryState.uid,
-                    processMemoryState.pid);
-            if (memoryStat == null) {
-                continue;
-            }
-            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeInt(processMemoryState.uid);
-            e.writeString(processMemoryState.processName);
-            e.writeInt(processMemoryState.oomScore);
-            e.writeLong(memoryStat.pgfault);
-            e.writeLong(memoryStat.pgmajfault);
-            e.writeLong(memoryStat.rssInBytes);
-            e.writeLong(memoryStat.cacheInBytes);
-            e.writeLong(memoryStat.swapInBytes);
-            e.writeLong(-1);  // unused
-            e.writeLong(-1);  // unused
-            e.writeInt(-1);  // unsed
-            pulledData.add(e);
-        }
-    }
-
-    private void pullProcessMemoryHighWaterMark(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        List<ProcessMemoryState> managedProcessList =
-                LocalServices.getService(
-                        ActivityManagerInternal.class).getMemoryStateForProcesses();
-        for (ProcessMemoryState managedProcess : managedProcessList) {
-            final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(managedProcess.pid);
-            if (snapshot == null) {
-                continue;
-            }
-            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeInt(managedProcess.uid);
-            e.writeString(managedProcess.processName);
-            // RSS high-water mark in bytes.
-            e.writeLong((long) snapshot.rssHighWaterMarkInKilobytes * 1024L);
-            e.writeInt(snapshot.rssHighWaterMarkInKilobytes);
-            pulledData.add(e);
-        }
-        forEachPid((pid, cmdLine) -> {
-            if (!MEMORY_INTERESTING_NATIVE_PROCESSES.contains(cmdLine)) {
-                return;
-            }
-            final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(pid);
-            if (snapshot == null) {
-                return;
-            }
-            // Sometimes we get here a process that is not included in the whitelist. It comes
-            // from forking the zygote for an app. We can ignore that sample because this process
-            // is collected by ProcessMemoryState.
-            if (isAppUid(snapshot.uid)) {
-                return;
-            }
-            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeInt(snapshot.uid);
-            e.writeString(cmdLine);
-            // RSS high-water mark in bytes.
-            e.writeLong((long) snapshot.rssHighWaterMarkInKilobytes * 1024L);
-            e.writeInt(snapshot.rssHighWaterMarkInKilobytes);
-            pulledData.add(e);
-        });
-        // Invoke rss_hwm_reset binary to reset RSS HWM counters for all processes.
-        SystemProperties.set("sys.rss_hwm_reset.on", "1");
-    }
-
-    private void pullProcessMemorySnapshot(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        List<ProcessMemoryState> managedProcessList =
-                LocalServices.getService(
-                        ActivityManagerInternal.class).getMemoryStateForProcesses();
-        for (ProcessMemoryState managedProcess : managedProcessList) {
-            final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(managedProcess.pid);
-            if (snapshot == null) {
-                continue;
-            }
-            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeInt(managedProcess.uid);
-            e.writeString(managedProcess.processName);
-            e.writeInt(managedProcess.pid);
-            e.writeInt(managedProcess.oomScore);
-            e.writeInt(snapshot.rssInKilobytes);
-            e.writeInt(snapshot.anonRssInKilobytes);
-            e.writeInt(snapshot.swapInKilobytes);
-            e.writeInt(snapshot.anonRssInKilobytes + snapshot.swapInKilobytes);
-            pulledData.add(e);
-        }
-        forEachPid((pid, cmdLine) -> {
-            if (!MEMORY_INTERESTING_NATIVE_PROCESSES.contains(cmdLine)) {
-                return;
-            }
-            final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(pid);
-            if (snapshot == null) {
-                return;
-            }
-            // Sometimes we get here a process that is not included in the whitelist. It comes
-            // from forking the zygote for an app. We can ignore that sample because this process
-            // is collected by ProcessMemoryState.
-            if (isAppUid(snapshot.uid)) {
-                return;
-            }
-            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeInt(snapshot.uid);
-            e.writeString(cmdLine);
-            e.writeInt(pid);
-            e.writeInt(-1001);  // Placeholder for native processes, OOM_SCORE_ADJ_MIN - 1.
-            e.writeInt(snapshot.rssInKilobytes);
-            e.writeInt(snapshot.anonRssInKilobytes);
-            e.writeInt(snapshot.swapInKilobytes);
-            e.writeInt(snapshot.anonRssInKilobytes + snapshot.swapInKilobytes);
-            pulledData.add(e);
-        });
-    }
-
-    private static boolean isAppUid(int uid) {
-        return uid >= MIN_APP_UID;
-    }
-
-    private void pullSystemIonHeapSize(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        final long systemIonHeapSizeInBytes = readSystemIonHeapSizeFromDebugfs();
-        StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-        e.writeLong(systemIonHeapSizeInBytes);
-        pulledData.add(e);
-    }
-
-    private void pullProcessSystemIonHeapSize(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        List<IonAllocations> result = readProcessSystemIonHeapSizesFromDebugfs();
-        for (IonAllocations allocations : result) {
-            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeInt(getUidForPid(allocations.pid));
-            e.writeString(readCmdlineFromProcfs(allocations.pid));
-            e.writeInt((int) (allocations.totalSizeInBytes / 1024));
-            e.writeInt(allocations.count);
-            e.writeInt((int) (allocations.maxSizeInBytes / 1024));
-            pulledData.add(e);
-        }
-    }
-
-    private void pullBinderCallsStats(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        BinderCallsStatsService.Internal binderStats =
-                LocalServices.getService(BinderCallsStatsService.Internal.class);
-        if (binderStats == null) {
-            throw new IllegalStateException("binderStats is null");
-        }
-
-        List<ExportedCallStat> callStats = binderStats.getExportedCallStats();
-        binderStats.reset();
-        for (ExportedCallStat callStat : callStats) {
-            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeInt(callStat.workSourceUid);
-            e.writeString(callStat.className);
-            e.writeString(callStat.methodName);
-            e.writeLong(callStat.callCount);
-            e.writeLong(callStat.exceptionCount);
-            e.writeLong(callStat.latencyMicros);
-            e.writeLong(callStat.maxLatencyMicros);
-            e.writeLong(callStat.cpuTimeMicros);
-            e.writeLong(callStat.maxCpuTimeMicros);
-            e.writeLong(callStat.maxReplySizeBytes);
-            e.writeLong(callStat.maxRequestSizeBytes);
-            e.writeLong(callStat.recordedCallCount);
-            e.writeInt(callStat.screenInteractive ? 1 : 0);
-            e.writeInt(callStat.callingUid);
-            pulledData.add(e);
-        }
-    }
-
-    private void pullBinderCallsStatsExceptions(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        BinderCallsStatsService.Internal binderStats =
-                LocalServices.getService(BinderCallsStatsService.Internal.class);
-        if (binderStats == null) {
-            throw new IllegalStateException("binderStats is null");
-        }
-
-        ArrayMap<String, Integer> exceptionStats = binderStats.getExportedExceptionStats();
-        // TODO: decouple binder calls exceptions with the rest of the binder calls data so that we
-        // can reset the exception stats.
-        for (Entry<String, Integer> entry : exceptionStats.entrySet()) {
-            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeString(entry.getKey());
-            e.writeInt(entry.getValue());
-            pulledData.add(e);
-        }
-    }
-
-    private void pullLooperStats(int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        LooperStats looperStats = LocalServices.getService(LooperStats.class);
-        if (looperStats == null) {
-            throw new IllegalStateException("looperStats null");
-        }
-
-        List<LooperStats.ExportedEntry> entries = looperStats.getEntries();
-        looperStats.reset();
-        for (LooperStats.ExportedEntry entry : entries) {
-            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeInt(entry.workSourceUid);
-            e.writeString(entry.handlerClassName);
-            e.writeString(entry.threadName);
-            e.writeString(entry.messageName);
-            e.writeLong(entry.messageCount);
-            e.writeLong(entry.exceptionCount);
-            e.writeLong(entry.recordedMessageCount);
-            e.writeLong(entry.totalLatencyMicros);
-            e.writeLong(entry.cpuUsageMicros);
-            e.writeBoolean(entry.isInteractive);
-            e.writeLong(entry.maxCpuUsageMicros);
-            e.writeLong(entry.maxLatencyMicros);
-            e.writeLong(entry.recordedDelayMessageCount);
-            e.writeLong(entry.delayMillis);
-            e.writeLong(entry.maxDelayMillis);
-            pulledData.add(e);
-        }
-    }
-
-    private void pullDiskStats(int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        // Run a quick-and-dirty performance test: write 512 bytes
-        byte[] junk = new byte[512];
-        for (int i = 0; i < junk.length; i++) junk[i] = (byte) i;  // Write nonzero bytes
-
-        File tmp = new File(Environment.getDataDirectory(), "system/statsdperftest.tmp");
-        FileOutputStream fos = null;
-        IOException error = null;
-
-        long before = SystemClock.elapsedRealtime();
-        try {
-            fos = new FileOutputStream(tmp);
-            fos.write(junk);
-        } catch (IOException e) {
-            error = e;
-        } finally {
-            try {
-                if (fos != null) fos.close();
-            } catch (IOException e) {
-                // Do nothing.
-            }
-        }
-
-        long latency = SystemClock.elapsedRealtime() - before;
-        if (tmp.exists()) tmp.delete();
-
-        if (error != null) {
-            Slog.e(TAG, "Error performing diskstats latency test");
-            latency = -1;
-        }
-        // File based encryption.
-        boolean fileBased = StorageManager.isFileEncryptedNativeOnly();
-
-        //Recent disk write speed. Binder call to storaged.
-        int writeSpeed = -1;
-        try {
-            IBinder binder = ServiceManager.getService("storaged");
-            if (binder == null) {
-                Slog.e(TAG, "storaged not found");
-            }
-            IStoraged storaged = IStoraged.Stub.asInterface(binder);
-            writeSpeed = storaged.getRecentPerf();
-        } catch (RemoteException e) {
-            Slog.e(TAG, "storaged not found");
-        }
-
-        // Add info pulledData.
-        StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-        e.writeLong(latency);
-        e.writeBoolean(fileBased);
-        e.writeInt(writeSpeed);
-        pulledData.add(e);
-    }
-
-    private void pullDirectoryUsage(int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        StatFs statFsData = new StatFs(Environment.getDataDirectory().getAbsolutePath());
-        StatFs statFsSystem = new StatFs(Environment.getRootDirectory().getAbsolutePath());
-        StatFs statFsCache = new StatFs(Environment.getDownloadCacheDirectory().getAbsolutePath());
-
-        StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-        e.writeInt(StatsLog.DIRECTORY_USAGE__DIRECTORY__DATA);
-        e.writeLong(statFsData.getAvailableBytes());
-        e.writeLong(statFsData.getTotalBytes());
-        pulledData.add(e);
-
-        e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-        e.writeInt(StatsLog.DIRECTORY_USAGE__DIRECTORY__CACHE);
-        e.writeLong(statFsCache.getAvailableBytes());
-        e.writeLong(statFsCache.getTotalBytes());
-        pulledData.add(e);
-
-        e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-        e.writeInt(StatsLog.DIRECTORY_USAGE__DIRECTORY__SYSTEM);
-        e.writeLong(statFsSystem.getAvailableBytes());
-        e.writeLong(statFsSystem.getTotalBytes());
-        pulledData.add(e);
-    }
-
-    private void pullAppSize(int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        try {
-            String jsonStr = IoUtils.readFileAsString(DiskStatsLoggingService.DUMPSYS_CACHE_PATH);
-            JSONObject json = new JSONObject(jsonStr);
-            long cache_time = json.optLong(DiskStatsFileLogger.LAST_QUERY_TIMESTAMP_KEY, -1L);
-            JSONArray pkg_names = json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY);
-            JSONArray app_sizes = json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY);
-            JSONArray app_data_sizes = json.getJSONArray(DiskStatsFileLogger.APP_DATA_KEY);
-            JSONArray app_cache_sizes = json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY);
-            // Sanity check: Ensure all 4 lists have the same length.
-            int length = pkg_names.length();
-            if (app_sizes.length() != length || app_data_sizes.length() != length
-                    || app_cache_sizes.length() != length) {
-                Slog.e(TAG, "formatting error in diskstats cache file!");
-                return;
-            }
-            for (int i = 0; i < length; i++) {
-                StatsLogEventWrapper e =
-                        new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-                e.writeString(pkg_names.getString(i));
-                e.writeLong(app_sizes.optLong(i, -1L));
-                e.writeLong(app_data_sizes.optLong(i, -1L));
-                e.writeLong(app_cache_sizes.optLong(i, -1L));
-                e.writeLong(cache_time);
-                pulledData.add(e);
-            }
-        } catch (IOException | JSONException e) {
-            Slog.e(TAG, "exception reading diskstats cache file", e);
-        }
-    }
-
-    private void pullCategorySize(int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        try {
-            String jsonStr = IoUtils.readFileAsString(DiskStatsLoggingService.DUMPSYS_CACHE_PATH);
-            JSONObject json = new JSONObject(jsonStr);
-            long cacheTime = json.optLong(DiskStatsFileLogger.LAST_QUERY_TIMESTAMP_KEY, -1L);
-
-            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeInt(StatsLog.CATEGORY_SIZE__CATEGORY__APP_SIZE);
-            e.writeLong(json.optLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY, -1L));
-            e.writeLong(cacheTime);
-            pulledData.add(e);
-
-            e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeInt(StatsLog.CATEGORY_SIZE__CATEGORY__APP_DATA_SIZE);
-            e.writeLong(json.optLong(DiskStatsFileLogger.APP_DATA_SIZE_AGG_KEY, -1L));
-            e.writeLong(cacheTime);
-            pulledData.add(e);
-
-            e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeInt(StatsLog.CATEGORY_SIZE__CATEGORY__APP_CACHE_SIZE);
-            e.writeLong(json.optLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY, -1L));
-            e.writeLong(cacheTime);
-            pulledData.add(e);
-
-            e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeInt(StatsLog.CATEGORY_SIZE__CATEGORY__PHOTOS);
-            e.writeLong(json.optLong(DiskStatsFileLogger.PHOTOS_KEY, -1L));
-            e.writeLong(cacheTime);
-            pulledData.add(e);
-
-            e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeInt(StatsLog.CATEGORY_SIZE__CATEGORY__VIDEOS);
-            e.writeLong(json.optLong(DiskStatsFileLogger.VIDEOS_KEY, -1L));
-            e.writeLong(cacheTime);
-            pulledData.add(e);
-
-            e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeInt(StatsLog.CATEGORY_SIZE__CATEGORY__AUDIO);
-            e.writeLong(json.optLong(DiskStatsFileLogger.AUDIO_KEY, -1L));
-            e.writeLong(cacheTime);
-            pulledData.add(e);
-
-            e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeInt(StatsLog.CATEGORY_SIZE__CATEGORY__DOWNLOADS);
-            e.writeLong(json.optLong(DiskStatsFileLogger.DOWNLOADS_KEY, -1L));
-            e.writeLong(cacheTime);
-            pulledData.add(e);
-
-            e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeInt(StatsLog.CATEGORY_SIZE__CATEGORY__SYSTEM);
-            e.writeLong(json.optLong(DiskStatsFileLogger.SYSTEM_KEY, -1L));
-            e.writeLong(cacheTime);
-            pulledData.add(e);
-
-            e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeInt(StatsLog.CATEGORY_SIZE__CATEGORY__OTHER);
-            e.writeLong(json.optLong(DiskStatsFileLogger.MISC_KEY, -1L));
-            e.writeLong(cacheTime);
-            pulledData.add(e);
-        } catch (IOException | JSONException e) {
-            Slog.e(TAG, "exception reading diskstats cache file", e);
-        }
-    }
-
-    private void pullNumBiometricsEnrolled(int modality, int tagId, long elapsedNanos,
-            long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
-        final PackageManager pm = mContext.getPackageManager();
-        FingerprintManager fingerprintManager = null;
-        FaceManager faceManager = null;
-
-        if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
-            fingerprintManager = mContext.getSystemService(
-                    FingerprintManager.class);
-        }
-        if (pm.hasSystemFeature(PackageManager.FEATURE_FACE)) {
-            faceManager = mContext.getSystemService(FaceManager.class);
-        }
-
-        if (modality == BiometricsProtoEnums.MODALITY_FINGERPRINT && fingerprintManager == null) {
-            return;
-        }
-        if (modality == BiometricsProtoEnums.MODALITY_FACE && faceManager == null) {
-            return;
-        }
-        UserManager userManager = mContext.getSystemService(UserManager.class);
-        if (userManager == null) {
-            return;
-        }
-
-        final long token = Binder.clearCallingIdentity();
-        for (UserInfo user : userManager.getUsers()) {
-            final int userId = user.getUserHandle().getIdentifier();
-            int numEnrolled = 0;
-            if (modality == BiometricsProtoEnums.MODALITY_FINGERPRINT) {
-                numEnrolled = fingerprintManager.getEnrolledFingerprints(userId).size();
-            } else if (modality == BiometricsProtoEnums.MODALITY_FACE) {
-                numEnrolled = faceManager.getEnrolledFaces(userId).size();
-            } else {
-                return;
-            }
-            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeInt(userId);
-            e.writeInt(numEnrolled);
-            pulledData.add(e);
-        }
-        Binder.restoreCallingIdentity(token);
-    }
-
-    // read high watermark for section
-    private long readProcStatsHighWaterMark(int section) {
-        try {
-            File[] files = mBaseDir.listFiles((d, name) -> {
-                return name.toLowerCase().startsWith(String.valueOf(section) + '_');
-            });
-            if (files == null || files.length == 0) {
-                return 0;
-            }
-            if (files.length > 1) {
-                Log.e(TAG, "Only 1 file expected for high water mark. Found " + files.length);
-            }
-            return Long.valueOf(files[0].getName().split("_")[1]);
-        } catch (SecurityException e) {
-            Log.e(TAG, "Failed to get procstats high watermark file.", e);
-        } catch (NumberFormatException e) {
-            Log.e(TAG, "Failed to parse file name.", e);
-        }
-        return 0;
-    }
-
-    private IProcessStats mProcessStats =
-            IProcessStats.Stub.asInterface(ServiceManager.getService(ProcessStats.SERVICE_NAME));
-
-    private void pullProcessStats(int section, int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        synchronized (this) {
-            try {
-                long lastHighWaterMark = readProcStatsHighWaterMark(section);
-                List<ParcelFileDescriptor> statsFiles = new ArrayList<>();
-                long highWaterMark = mProcessStats.getCommittedStats(
-                        lastHighWaterMark, section, true, statsFiles);
-                if (statsFiles.size() != 1) {
-                    return;
-                }
-                unpackStreamedData(tagId, elapsedNanos, wallClockNanos, pulledData, statsFiles);
-                new File(mBaseDir.getAbsolutePath() + "/" + section + "_"
-                        + lastHighWaterMark).delete();
-                new File(
-                        mBaseDir.getAbsolutePath() + "/" + section + "_"
-                                + highWaterMark).createNewFile();
-            } catch (IOException e) {
-                Log.e(TAG, "Getting procstats failed: ", e);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Getting procstats failed: ", e);
-            } catch (SecurityException e) {
-                Log.e(TAG, "Getting procstats failed: ", e);
-            }
-        }
-    }
-
-    private INotificationManager mNotificationManager =
-            INotificationManager.Stub.asInterface(
-                    ServiceManager.getService(Context.NOTIFICATION_SERVICE));
-
-    private void pullNotificationStats(int reportId, int tagId, long elapsedNanos,
-            long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        final long callingToken = Binder.clearCallingIdentity();
-        try {
-            // determine last pull tine. Copy file trick from pullProcessStats?
-            long lastNotificationStatsNs = wallClockNanos -
-                    TimeUnit.NANOSECONDS.convert(1, TimeUnit.DAYS);
-
-            List<ParcelFileDescriptor> statsFiles = new ArrayList<>();
-            long notificationStatsNs = mNotificationManager.pullStats(
-                    lastNotificationStatsNs, reportId, true, statsFiles);
-            if (statsFiles.size() != 1) {
-                return;
-            }
-            unpackStreamedData(tagId, elapsedNanos, wallClockNanos, pulledData, statsFiles);
-        } catch (IOException e) {
-            Log.e(TAG, "Getting notistats failed: ", e);
-
-        } catch (RemoteException e) {
-            Log.e(TAG, "Getting notistats failed: ", e);
-        } catch (SecurityException e) {
-            Log.e(TAG, "Getting notistats failed: ", e);
-        } finally {
-            Binder.restoreCallingIdentity(callingToken);
-        }
-
-    }
-
-    static void unpackStreamedData(int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData, List<ParcelFileDescriptor> statsFiles)
-            throws IOException {
-        InputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(
-                statsFiles.get(0));
-        int[] len = new int[1];
-        byte[] stats = readFully(stream, len);
-        StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
-                wallClockNanos);
-        e.writeStorage(Arrays.copyOf(stats, len[0]));
-        pulledData.add(e);
-    }
-
-    static byte[] readFully(InputStream stream, int[] outLen) throws IOException {
-        int pos = 0;
-        final int initialAvail = stream.available();
-        byte[] data = new byte[initialAvail > 0 ? (initialAvail + 1) : 16384];
-        while (true) {
-            int amt = stream.read(data, pos, data.length - pos);
-            if (DEBUG) {
-                Slog.i(TAG, "Read " + amt + " bytes at " + pos + " of avail " + data.length);
-            }
-            if (amt < 0) {
-                if (DEBUG) {
-                    Slog.i(TAG, "**** FINISHED READING: pos=" + pos + " len=" + data.length);
-                }
-                outLen[0] = pos;
-                return data;
-            }
-            pos += amt;
-            if (pos >= data.length) {
-                byte[] newData = new byte[pos + 16384];
-                if (DEBUG) {
-                    Slog.i(TAG, "Copying " + pos + " bytes to new array len " + newData.length);
-                }
-                System.arraycopy(data, 0, newData, 0, pos);
-                data = newData;
-            }
-        }
-    }
-
-    private void pullPowerProfile(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        PowerProfile powerProfile = new PowerProfile(mContext);
-        Objects.requireNonNull(powerProfile);
-
-        StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
-                wallClockNanos);
-        ProtoOutputStream proto = new ProtoOutputStream();
-        powerProfile.dumpDebug(proto);
-        proto.flush();
-        e.writeStorage(proto.getBytes());
-        pulledData.add(e);
-    }
-
-    private void pullBuildInformation(int tagId,
-            long elapsedNanos, long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
-        StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-        e.writeString(Build.FINGERPRINT);
-        e.writeString(Build.BRAND);
-        e.writeString(Build.PRODUCT);
-        e.writeString(Build.DEVICE);
-        e.writeString(Build.VERSION.RELEASE);
-        e.writeString(Build.ID);
-        e.writeString(Build.VERSION.INCREMENTAL);
-        e.writeString(Build.TYPE);
-        e.writeString(Build.TAGS);
-        pulledData.add(e);
-    }
-
-    private BatteryStatsHelper getBatteryStatsHelper() {
-        if (mBatteryStatsHelper == null) {
-            final long callingToken = Binder.clearCallingIdentity();
-            try {
-                // clearCallingIdentity required for BatteryStatsHelper.checkWifiOnly().
-                mBatteryStatsHelper = new BatteryStatsHelper(mContext, false);
-            } finally {
-                Binder.restoreCallingIdentity(callingToken);
-            }
-            mBatteryStatsHelper.create((Bundle) null);
-        }
-        long currentTime = SystemClock.elapsedRealtime();
-        if (currentTime - mBatteryStatsHelperTimestampMs >= MAX_BATTERY_STATS_HELPER_FREQUENCY_MS) {
-            // Load BatteryStats and do all the calculations.
-            mBatteryStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.USER_ALL);
-            // Calculations are done so we don't need to save the raw BatteryStats data in RAM.
-            mBatteryStatsHelper.clearStats();
-            mBatteryStatsHelperTimestampMs = currentTime;
-        }
-        return mBatteryStatsHelper;
-    }
-
-    private long milliAmpHrsToNanoAmpSecs(double mAh) {
-        final long MILLI_AMP_HR_TO_NANO_AMP_SECS = 1_000_000L * 3600L;
-        return (long) (mAh * MILLI_AMP_HR_TO_NANO_AMP_SECS + 0.5);
-    }
-
-    private void pullDeviceCalculatedPowerUse(int tagId,
-            long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
-        BatteryStatsHelper bsHelper = getBatteryStatsHelper();
-        StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-        e.writeLong(milliAmpHrsToNanoAmpSecs(bsHelper.getComputedPower()));
-        pulledData.add(e);
-    }
-
-    private void pullDeviceCalculatedPowerBlameUid(int tagId,
-            long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
-        final List<BatterySipper> sippers = getBatteryStatsHelper().getUsageList();
-        if (sippers == null) {
-            return;
-        }
-        for (BatterySipper bs : sippers) {
-            if (bs.drainType != bs.drainType.APP) {
-                continue;
-            }
-            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeInt(bs.uidObj.getUid());
-            e.writeLong(milliAmpHrsToNanoAmpSecs(bs.totalPowerMah));
-            pulledData.add(e);
-        }
-    }
-
-    private void pullDeviceCalculatedPowerBlameOther(int tagId,
-            long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
-        final List<BatterySipper> sippers = getBatteryStatsHelper().getUsageList();
-        if (sippers == null) {
-            return;
-        }
-        for (BatterySipper bs : sippers) {
-            if (bs.drainType == bs.drainType.APP) {
-                continue; // This is a separate atom; see pullDeviceCalculatedPowerBlameUid().
-            }
-            if (bs.drainType == bs.drainType.USER) {
-                continue; // This is not supported. We purposefully calculate over USER_ALL.
-            }
-            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeInt(bs.drainType.ordinal());
-            e.writeLong(milliAmpHrsToNanoAmpSecs(bs.totalPowerMah));
-            pulledData.add(e);
-        }
-    }
-
-    private void pullDiskIo(int tagId, long elapsedNanos, final long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        mStoragedUidIoStatsReader.readAbsolute((uid, fgCharsRead, fgCharsWrite, fgBytesRead,
-                fgBytesWrite, bgCharsRead, bgCharsWrite, bgBytesRead, bgBytesWrite,
-                fgFsync, bgFsync) -> {
-            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
-                    wallClockNanos);
-            e.writeInt(uid);
-            e.writeLong(fgCharsRead);
-            e.writeLong(fgCharsWrite);
-            e.writeLong(fgBytesRead);
-            e.writeLong(fgBytesWrite);
-            e.writeLong(bgCharsRead);
-            e.writeLong(bgCharsWrite);
-            e.writeLong(bgBytesRead);
-            e.writeLong(bgBytesWrite);
-            e.writeLong(fgFsync);
-            e.writeLong(bgFsync);
-            pulledData.add(e);
-        });
-    }
-
-    private void pullProcessCpuTime(int tagId, long elapsedNanos, final long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        synchronized (this) {
-            if (mProcessCpuTracker == null) {
-                mProcessCpuTracker = new ProcessCpuTracker(false);
-                mProcessCpuTracker.init();
-            }
-            mProcessCpuTracker.update();
-            for (int i = 0; i < mProcessCpuTracker.countStats(); i++) {
-                ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i);
-                StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
-                        wallClockNanos);
-                e.writeInt(st.uid);
-                e.writeString(st.name);
-                e.writeLong(st.base_utime);
-                e.writeLong(st.base_stime);
-                pulledData.add(e);
-            }
-        }
-    }
-
-    private void pullCpuTimePerThreadFreq(int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        if (this.mKernelCpuThreadReader == null) {
-            throw new IllegalStateException("mKernelCpuThreadReader is null");
-        }
-        ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsages =
-                this.mKernelCpuThreadReader.getProcessCpuUsageDiffed();
-        if (processCpuUsages == null) {
-            throw new IllegalStateException("processCpuUsages is null");
-        }
-        int[] cpuFrequencies = mKernelCpuThreadReader.getCpuFrequenciesKhz();
-        if (cpuFrequencies.length > CPU_TIME_PER_THREAD_FREQ_MAX_NUM_FREQUENCIES) {
-            String message = "Expected maximum " + CPU_TIME_PER_THREAD_FREQ_MAX_NUM_FREQUENCIES
-                    + " frequencies, but got " + cpuFrequencies.length;
-            Slog.w(TAG, message);
-            throw new IllegalStateException(message);
-        }
-        for (int i = 0; i < processCpuUsages.size(); i++) {
-            KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = processCpuUsages.get(i);
-            ArrayList<KernelCpuThreadReader.ThreadCpuUsage> threadCpuUsages =
-                    processCpuUsage.threadCpuUsages;
-            for (int j = 0; j < threadCpuUsages.size(); j++) {
-                KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage = threadCpuUsages.get(j);
-                if (threadCpuUsage.usageTimesMillis.length != cpuFrequencies.length) {
-                    String message = "Unexpected number of usage times,"
-                            + " expected " + cpuFrequencies.length
-                            + " but got " + threadCpuUsage.usageTimesMillis.length;
-                    Slog.w(TAG, message);
-                    throw new IllegalStateException(message);
-                }
-
-                StatsLogEventWrapper e =
-                        new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-                e.writeInt(processCpuUsage.uid);
-                e.writeInt(processCpuUsage.processId);
-                e.writeInt(threadCpuUsage.threadId);
-                e.writeString(processCpuUsage.processName);
-                e.writeString(threadCpuUsage.threadName);
-                for (int k = 0; k < CPU_TIME_PER_THREAD_FREQ_MAX_NUM_FREQUENCIES; k++) {
-                    if (k < cpuFrequencies.length) {
-                        e.writeInt(cpuFrequencies[k]);
-                        e.writeInt(threadCpuUsage.usageTimesMillis[k]);
-                    } else {
-                        // If we have no more frequencies to write, we still must write empty data.
-                        // We know that this data is empty (and not just zero) because all
-                        // frequencies are expected to be greater than zero
-                        e.writeInt(0);
-                        e.writeInt(0);
-                    }
-                }
-                pulledData.add(e);
-            }
-        }
-    }
-
-    private void pullTemperature(int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        long callingToken = Binder.clearCallingIdentity();
-        try {
-            List<Temperature> temperatures = sThermalService.getCurrentTemperatures();
-            for (Temperature temp : temperatures) {
-                StatsLogEventWrapper e =
-                        new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-                e.writeInt(temp.getType());
-                e.writeString(temp.getName());
-                e.writeInt((int) (temp.getValue() * 10));
-                e.writeInt(temp.getStatus());
-                pulledData.add(e);
-            }
-        } catch (RemoteException e) {
-            // Should not happen.
-            Slog.e(TAG, "Disconnected from thermal service. Cannot pull temperatures.");
-        } finally {
-            Binder.restoreCallingIdentity(callingToken);
-        }
-    }
-
-    private void pullCoolingDevices(int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        long callingToken = Binder.clearCallingIdentity();
-        try {
-            List<CoolingDevice> devices = sThermalService.getCurrentCoolingDevices();
-            for (CoolingDevice device : devices) {
-                StatsLogEventWrapper e =
-                        new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-                e.writeInt(device.getType());
-                e.writeString(device.getName());
-                e.writeInt((int) (device.getValue()));
-                pulledData.add(e);
-            }
-        } catch (RemoteException e) {
-            // Should not happen.
-            Slog.e(TAG, "Disconnected from thermal service. Cannot pull temperatures.");
-        } finally {
-            Binder.restoreCallingIdentity(callingToken);
-        }
-    }
-
-    private void pullDebugElapsedClock(int tagId,
-            long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
-        final long elapsedMillis = SystemClock.elapsedRealtime();
-        final long clockDiffMillis = mDebugElapsedClockPreviousValue == 0
-                ? 0 : elapsedMillis - mDebugElapsedClockPreviousValue;
-
-        StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-        e.writeLong(mDebugElapsedClockPullCount);
-        e.writeLong(elapsedMillis);
-        // Log it twice to be able to test multi-value aggregation from ValueMetric.
-        e.writeLong(elapsedMillis);
-        e.writeLong(clockDiffMillis);
-        e.writeInt(1 /* always set */);
-        pulledData.add(e);
-
-        if (mDebugElapsedClockPullCount % 2 == 1) {
-            StatsLogEventWrapper e2 = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e2.writeLong(mDebugElapsedClockPullCount);
-            e2.writeLong(elapsedMillis);
-            // Log it twice to be able to test multi-value aggregation from ValueMetric.
-            e2.writeLong(elapsedMillis);
-            e2.writeLong(clockDiffMillis);
-            e2.writeInt(2 /* set on odd pulls */);
-            pulledData.add(e2);
-        }
-
-        mDebugElapsedClockPullCount++;
-        mDebugElapsedClockPreviousValue = elapsedMillis;
-    }
-
-    private void pullDebugFailingElapsedClock(int tagId,
-            long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
-        StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-        final long elapsedMillis = SystemClock.elapsedRealtime();
-        // Fails every 5 buckets.
-        if (mDebugFailingElapsedClockPullCount++ % 5 == 0) {
-            mDebugFailingElapsedClockPreviousValue = elapsedMillis;
-            throw new RuntimeException("Failing debug elapsed clock");
-        }
-
-        e.writeLong(mDebugFailingElapsedClockPullCount);
-        e.writeLong(elapsedMillis);
-        // Log it twice to be able to test multi-value aggregation from ValueMetric.
-        e.writeLong(elapsedMillis);
-        e.writeLong(mDebugFailingElapsedClockPreviousValue == 0
-                ? 0 : elapsedMillis - mDebugFailingElapsedClockPreviousValue);
-        mDebugFailingElapsedClockPreviousValue = elapsedMillis;
-        pulledData.add(e);
-    }
-
-    private void pullDangerousPermissionState(int atomId, long elapsedNanos,
-            final long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
-        long token = Binder.clearCallingIdentity();
-        Set<Integer> reportedUids = new HashSet<>();
-        try {
-            PackageManager pm = mContext.getPackageManager();
-
-            List<UserInfo> users = mContext.getSystemService(UserManager.class).getUsers();
-
-            int numUsers = users.size();
-            for (int userNum = 0; userNum < numUsers; userNum++) {
-                UserHandle user = users.get(userNum).getUserHandle();
-
-                List<PackageInfo> pkgs = pm.getInstalledPackagesAsUser(
-                        PackageManager.GET_PERMISSIONS, user.getIdentifier());
-
-                int numPkgs = pkgs.size();
-                for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
-                    PackageInfo pkg = pkgs.get(pkgNum);
-
-                    if (pkg.requestedPermissions == null) {
-                        continue;
-                    }
-
-                    if (reportedUids.contains(pkg.applicationInfo.uid)) {
-                        // do not report same uid twice
-                        continue;
-                    }
-                    reportedUids.add(pkg.applicationInfo.uid);
-
-                    if (atomId == StatsLog.DANGEROUS_PERMISSION_STATE_SAMPLED
-                            && ThreadLocalRandom.current().nextFloat() > 0.2f) {
-                        continue;
-                    }
-
-                    int numPerms = pkg.requestedPermissions.length;
-                    for (int permNum  = 0; permNum < numPerms; permNum++) {
-                        String permName = pkg.requestedPermissions[permNum];
-
-                        PermissionInfo permissionInfo;
-                        int permissionFlags = 0;
-                        try {
-                            permissionInfo = pm.getPermissionInfo(permName, 0);
-                            permissionFlags =
-                                    pm.getPermissionFlags(permName, pkg.packageName, user);
-
-                        } catch (PackageManager.NameNotFoundException ignored) {
-                            continue;
-                        }
-
-                        if (permissionInfo.getProtection() != PROTECTION_DANGEROUS) {
-                            continue;
-                        }
-
-                        StatsLogEventWrapper e = new StatsLogEventWrapper(
-                                atomId, elapsedNanos, wallClockNanos);
-
-                        e.writeString(permName);
-                        e.writeInt(pkg.applicationInfo.uid);
-                        if (atomId == StatsLog.DANGEROUS_PERMISSION_STATE) {
-                            e.writeString(null);
-                        }
-                        e.writeBoolean((pkg.requestedPermissionsFlags[permNum]
-                                & REQUESTED_PERMISSION_GRANTED) != 0);
-                        e.writeInt(permissionFlags);
-
-                        pulledData.add(e);
-                    }
-                }
-            }
-        } catch (Throwable t) {
-            Log.e(TAG, "Could not read permissions", t);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    private void pullAppOps(long elapsedNanos, final long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        long token = Binder.clearCallingIdentity();
-        try {
-            AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
-
-            CompletableFuture<HistoricalOps> ops = new CompletableFuture<>();
-            HistoricalOpsRequest histOpsRequest =
-                    new HistoricalOpsRequest.Builder(0, Long.MAX_VALUE).build();
-            appOps.getHistoricalOps(histOpsRequest, mContext.getMainExecutor(), ops::complete);
-
-            HistoricalOps histOps = ops.get(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS,
-                    TimeUnit.MILLISECONDS);
-
-            for (int uidIdx = 0; uidIdx < histOps.getUidCount(); uidIdx++) {
-                final HistoricalUidOps uidOps = histOps.getUidOpsAt(uidIdx);
-                final int uid = uidOps.getUid();
-                for (int pkgIdx = 0; pkgIdx < uidOps.getPackageCount(); pkgIdx++) {
-                    final HistoricalPackageOps packageOps = uidOps.getPackageOpsAt(pkgIdx);
-                    for (int opIdx = 0; opIdx < packageOps.getOpCount(); opIdx++) {
-                        final AppOpsManager.HistoricalOp op  = packageOps.getOpAt(opIdx);
-                        StatsLogEventWrapper e = new StatsLogEventWrapper(StatsLog.APP_OPS,
-                                elapsedNanos, wallClockNanos);
-
-                        e.writeInt(uid);
-                        e.writeString(packageOps.getPackageName());
-                        e.writeInt(op.getOpCode());
-                        e.writeLong(op.getForegroundAccessCount(OP_FLAGS_ALL_TRUSTED));
-                        e.writeLong(op.getBackgroundAccessCount(OP_FLAGS_ALL_TRUSTED));
-                        e.writeLong(op.getForegroundRejectCount(OP_FLAGS_ALL_TRUSTED));
-                        e.writeLong(op.getBackgroundRejectCount(OP_FLAGS_ALL_TRUSTED));
-                        e.writeLong(op.getForegroundAccessDuration(OP_FLAGS_ALL_TRUSTED));
-                        e.writeLong(op.getBackgroundAccessDuration(OP_FLAGS_ALL_TRUSTED));
-
-                        String perm = AppOpsManager.opToPermission(op.getOpCode());
-                        if (perm == null) {
-                            e.writeBoolean(false);
-                        } else {
-                            PermissionInfo permInfo;
-                            try {
-                                permInfo = mContext.getPackageManager().getPermissionInfo(perm, 0);
-                                e.writeBoolean(permInfo.getProtection() == PROTECTION_DANGEROUS);
-                            } catch (PackageManager.NameNotFoundException exception) {
-                                e.writeBoolean(false);
-                            }
-                        }
-
-                        pulledData.add(e);
-                    }
-                }
-            }
-        } catch (Throwable t) {
-            Log.e(TAG, "Could not read appops", t);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-
-    /**
-     * Add a RoleHolder atom for each package that holds a role.
-     *
-     * @param elapsedNanos the time since boot
-     * @param wallClockNanos the time on the clock
-     * @param pulledData the data sink to write to
-     */
-    private void pullRoleHolders(long elapsedNanos, final long wallClockNanos,
-            @NonNull List<StatsLogEventWrapper> pulledData) {
-        long callingToken = Binder.clearCallingIdentity();
-        try {
-            PackageManager pm = mContext.getPackageManager();
-            RoleManagerInternal rmi = LocalServices.getService(RoleManagerInternal.class);
-
-            List<UserInfo> users = mContext.getSystemService(UserManager.class).getUsers();
-
-            int numUsers = users.size();
-            for (int userNum = 0; userNum < numUsers; userNum++) {
-                int userId = users.get(userNum).getUserHandle().getIdentifier();
-
-                ArrayMap<String, ArraySet<String>> roles = rmi.getRolesAndHolders(
-                        userId);
-
-                int numRoles = roles.size();
-                for (int roleNum = 0; roleNum < numRoles; roleNum++) {
-                    String roleName = roles.keyAt(roleNum);
-                    ArraySet<String> holders = roles.valueAt(roleNum);
-
-                    int numHolders = holders.size();
-                    for (int holderNum = 0; holderNum < numHolders; holderNum++) {
-                        String holderName = holders.valueAt(holderNum);
-
-                        PackageInfo pkg;
-                        try {
-                            pkg = pm.getPackageInfoAsUser(holderName, 0, userId);
-                        } catch (PackageManager.NameNotFoundException e) {
-                            Log.w(TAG, "Role holder " + holderName + " not found");
-                            return;
-                        }
-
-                        StatsLogEventWrapper e = new StatsLogEventWrapper(StatsLog.ROLE_HOLDER,
-                                elapsedNanos, wallClockNanos);
-                        e.writeInt(pkg.applicationInfo.uid);
-                        e.writeString(holderName);
-                        e.writeString(roleName);
-                        pulledData.add(e);
-                    }
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(callingToken);
-        }
-    }
-
-    private void pullTimeZoneDataInfo(int tagId,
-            long elapsedNanos, long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
-        String tzDbVersion = "Unknown";
-        try {
-            tzDbVersion = android.icu.util.TimeZone.getTZDataVersion();
-        } catch (Exception e) {
-            Log.e(TAG, "Getting tzdb version failed: ", e);
-        }
-
-        StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-        e.writeString(tzDbVersion);
-        pulledData.add(e);
-    }
-
-    private void pullExternalStorageInfo(int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        StorageManager storageManager = mContext.getSystemService(StorageManager.class);
-        if (storageManager != null) {
-            List<VolumeInfo> volumes = storageManager.getVolumes();
-            for (VolumeInfo vol : volumes) {
-                final String envState = VolumeInfo.getEnvironmentForState(vol.getState());
-                final DiskInfo diskInfo = vol.getDisk();
-                if (diskInfo != null) {
-                    if (envState.equals(Environment.MEDIA_MOUNTED)) {
-                        // Get the type of the volume, if it is adoptable or portable.
-                        int volumeType = StatsLog.EXTERNAL_STORAGE_INFO__VOLUME_TYPE__OTHER;
-                        if (vol.getType() == TYPE_PUBLIC) {
-                            volumeType = StatsLog.EXTERNAL_STORAGE_INFO__VOLUME_TYPE__PUBLIC;
-                        } else if (vol.getType() == TYPE_PRIVATE) {
-                            volumeType = StatsLog.EXTERNAL_STORAGE_INFO__VOLUME_TYPE__PRIVATE;
-                        }
-                        // Get the type of external storage inserted in the device (sd cards,
-                        // usb, etc)
-                        int externalStorageType;
-                        if (diskInfo.isSd()) {
-                            externalStorageType = StorageEnums.SD_CARD;
-                        } else if (diskInfo.isUsb()) {
-                            externalStorageType = StorageEnums.USB;
-                        } else {
-                            externalStorageType = StorageEnums.OTHER;
-                        }
-                        StatsLogEventWrapper e =
-                                new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-                        e.writeInt(externalStorageType);
-                        e.writeInt(volumeType);
-                        e.writeLong(diskInfo.size);
-                        pulledData.add(e);
-                    }
-                }
-            }
-        }
-    }
-
-    private void pullAppsOnExternalStorageInfo(int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        PackageManager pm = mContext.getPackageManager();
-        StorageManager storage = mContext.getSystemService(StorageManager.class);
-        List<ApplicationInfo> apps = pm.getInstalledApplications(/* flags = */ 0);
-        for (ApplicationInfo appInfo : apps) {
-            UUID storageUuid = appInfo.storageUuid;
-            if (storageUuid != null) {
-                VolumeInfo volumeInfo = storage.findVolumeByUuid(appInfo.storageUuid.toString());
-                if (volumeInfo != null) {
-                    DiskInfo diskInfo = volumeInfo.getDisk();
-                    if (diskInfo != null) {
-                        int externalStorageType = -1;
-                        if (diskInfo.isSd()) {
-                            externalStorageType = StorageEnums.SD_CARD;
-                        } else if (diskInfo.isUsb()) {
-                            externalStorageType = StorageEnums.USB;
-                        } else if (appInfo.isExternal()) {
-                            externalStorageType = StorageEnums.OTHER;
-                        }
-                        // App is installed on external storage.
-                        if (externalStorageType != -1) {
-                            StatsLogEventWrapper e =
-                                    new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-                            e.writeInt(externalStorageType);
-                            e.writeString(appInfo.packageName);
-                            pulledData.add(e);
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    private void pullFaceSettings(int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        long callingToken = Binder.clearCallingIdentity();
-        try {
-            List<UserInfo> users = mContext.getSystemService(UserManager.class).getUsers();
-            int numUsers = users.size();
-            for (int userNum = 0; userNum < numUsers; userNum++) {
-                int userId = users.get(userNum).getUserHandle().getIdentifier();
-
-                StatsLogEventWrapper e =
-                        new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-                e.writeBoolean(Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                        Settings.Secure.FACE_UNLOCK_KEYGUARD_ENABLED, 1,
-                        userId) != 0);
-                e.writeBoolean(Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                        Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD,
-                        0, userId) != 0);
-                e.writeBoolean(Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                        Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED, 1,
-                        userId) != 0);
-                e.writeBoolean(Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                        Settings.Secure.FACE_UNLOCK_APP_ENABLED, 1,
-                        userId) != 0);
-                e.writeBoolean(Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                        Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, 0,
-                        userId) != 0);
-                e.writeBoolean(Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                        Settings.Secure.FACE_UNLOCK_DIVERSITY_REQUIRED, 1,
-                        userId) != 0);
-
-                pulledData.add(e);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(callingToken);
-        }
-    }
-
-    /**
-     * Pulls various data.
-     */
-    @Override // Binder call
-    public StatsLogEventWrapper[] pullData(int tagId) {
-        StatsCompanion.enforceStatsCompanionPermission(mContext);
-        if (DEBUG) {
-            Slog.d(TAG, "Pulling " + tagId);
-        }
-        List<StatsLogEventWrapper> ret = new ArrayList<>();
-        long elapsedNanos = SystemClock.elapsedRealtimeNanos();
-        long wallClockNanos = SystemClock.currentTimeMicro() * 1000L;
-        switch (tagId) {
-            case StatsLog.WIFI_BYTES_TRANSFER: {
-                pullWifiBytesTransfer(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.MOBILE_BYTES_TRANSFER: {
-                pullMobileBytesTransfer(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG: {
-                pullWifiBytesTransferByFgBg(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG: {
-                pullMobileBytesTransferByFgBg(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.BLUETOOTH_BYTES_TRANSFER: {
-                pullBluetoothBytesTransfer(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.KERNEL_WAKELOCK: {
-                pullKernelWakelock(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.CPU_TIME_PER_FREQ: {
-                pullCpuTimePerFreq(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.CPU_TIME_PER_UID: {
-                pullKernelUidCpuTime(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.CPU_TIME_PER_UID_FREQ: {
-                pullKernelUidCpuFreqTime(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.CPU_CLUSTER_TIME: {
-                pullKernelUidCpuClusterTime(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.CPU_ACTIVE_TIME: {
-                pullKernelUidCpuActiveTime(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.WIFI_ACTIVITY_INFO: {
-                pullWifiActivityInfo(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.MODEM_ACTIVITY_INFO: {
-                pullModemActivityInfo(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.BLUETOOTH_ACTIVITY_INFO: {
-                pullBluetoothActivityInfo(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.SYSTEM_UPTIME: {
-                pullSystemUpTime(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.SYSTEM_ELAPSED_REALTIME: {
-                pullSystemElapsedRealtime(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.PROCESS_MEMORY_STATE: {
-                pullProcessMemoryState(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.PROCESS_MEMORY_HIGH_WATER_MARK: {
-                pullProcessMemoryHighWaterMark(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.PROCESS_MEMORY_SNAPSHOT: {
-                pullProcessMemorySnapshot(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.SYSTEM_ION_HEAP_SIZE: {
-                pullSystemIonHeapSize(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.PROCESS_SYSTEM_ION_HEAP_SIZE: {
-                pullProcessSystemIonHeapSize(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.BINDER_CALLS: {
-                pullBinderCallsStats(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.BINDER_CALLS_EXCEPTIONS: {
-                pullBinderCallsStatsExceptions(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.LOOPER_STATS: {
-                pullLooperStats(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.DISK_STATS: {
-                pullDiskStats(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.DIRECTORY_USAGE: {
-                pullDirectoryUsage(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.APP_SIZE: {
-                pullAppSize(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.CATEGORY_SIZE: {
-                pullCategorySize(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.NUM_FINGERPRINTS_ENROLLED: {
-                pullNumBiometricsEnrolled(BiometricsProtoEnums.MODALITY_FINGERPRINT, tagId,
-                        elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.NUM_FACES_ENROLLED: {
-                pullNumBiometricsEnrolled(BiometricsProtoEnums.MODALITY_FACE, tagId, elapsedNanos,
-                        wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.PROC_STATS: {
-                pullProcessStats(ProcessStats.REPORT_ALL, tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.PROC_STATS_PKG_PROC: {
-                pullProcessStats(ProcessStats.REPORT_PKG_PROC_STATS, tagId, elapsedNanos,
-                        wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.DISK_IO: {
-                pullDiskIo(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.POWER_PROFILE: {
-                pullPowerProfile(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.BUILD_INFORMATION: {
-                pullBuildInformation(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.PROCESS_CPU_TIME: {
-                pullProcessCpuTime(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.CPU_TIME_PER_THREAD_FREQ: {
-                pullCpuTimePerThreadFreq(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.DEVICE_CALCULATED_POWER_USE: {
-                pullDeviceCalculatedPowerUse(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.DEVICE_CALCULATED_POWER_BLAME_UID: {
-                pullDeviceCalculatedPowerBlameUid(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER: {
-                pullDeviceCalculatedPowerBlameOther(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.TEMPERATURE: {
-                pullTemperature(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.COOLING_DEVICE: {
-                pullCoolingDevices(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.DEBUG_ELAPSED_CLOCK: {
-                pullDebugElapsedClock(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.DEBUG_FAILING_ELAPSED_CLOCK: {
-                pullDebugFailingElapsedClock(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.ROLE_HOLDER: {
-                pullRoleHolders(elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.DANGEROUS_PERMISSION_STATE: {
-                pullDangerousPermissionState(StatsLog.DANGEROUS_PERMISSION_STATE, elapsedNanos,
-                        wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.DANGEROUS_PERMISSION_STATE_SAMPLED: {
-                pullDangerousPermissionState(StatsLog.DANGEROUS_PERMISSION_STATE_SAMPLED,
-                        elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.TIME_ZONE_DATA_INFO: {
-                pullTimeZoneDataInfo(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.EXTERNAL_STORAGE_INFO: {
-                pullExternalStorageInfo(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.APPS_ON_EXTERNAL_STORAGE_INFO: {
-                pullAppsOnExternalStorageInfo(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.FACE_SETTINGS: {
-                pullFaceSettings(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.APP_OPS: {
-                pullAppOps(elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            case StatsLog.NOTIFICATION_REMOTE_VIEWS: {
-                pullNotificationStats(NotificationManagerService.REPORT_REMOTE_VIEWS,
-                        tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
-            default:
-                Slog.w(TAG, "No such tagId data as " + tagId);
-                return null;
-        }
-        return ret.toArray(new StatsLogEventWrapper[ret.size()]);
-    }
-
     @Override // Binder call
     public void statsdReady() {
         StatsCompanion.enforceStatsCompanionPermission(mContext);
@@ -2634,57 +741,6 @@
         }
     }
 
-    @Override
-    public void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs,
-            int[] additiveFields, IPullAtomCallback pullerCallback) {
-        synchronized (sStatsdLock) {
-            // Always cache the puller in SCS.
-            // If statsd is down, we will register it when it comes back up.
-            int callingUid = Binder.getCallingUid();
-            final long token = Binder.clearCallingIdentity();
-            PullerKey key = new PullerKey(callingUid, atomTag);
-            PullerValue val = new PullerValue(
-                    coolDownNs, timeoutNs, additiveFields, pullerCallback);
-            mPullers.put(key, val);
-
-            if (sStatsd == null) {
-                Slog.w(TAG, "Could not access statsd for registering puller for atom " + atomTag);
-                return;
-            }
-            try {
-                sStatsd.registerPullAtomCallback(
-                        callingUid, atomTag, coolDownNs, timeoutNs, additiveFields, pullerCallback);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag);
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-    }
-
-    @Override
-    public void unregisterPullAtomCallback(int atomTag) {
-        synchronized (sStatsdLock) {
-            // Always remove the puller in SCS.
-            // If statsd is down, we will not register it when it comes back up.
-            int callingUid = Binder.getCallingUid();
-            final long token = Binder.clearCallingIdentity();
-            PullerKey key = new PullerKey(callingUid, atomTag);
-            mPullers.remove(key);
-
-            if (sStatsd == null) {
-                Slog.w(TAG, "Could not access statsd for registering puller for atom " + atomTag);
-                return;
-            }
-            try {
-                sStatsd.unregisterPullAtomCallback(callingUid, atomTag);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag);
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-    }
 
     // Statsd related code
 
@@ -2763,8 +819,6 @@
                     // Pull the latest state of UID->app name, version mapping when
                     // statsd starts.
                     informAllUidsLocked(mContext);
-                    // Register all pullers. If SCS has just started, this should be empty.
-                    registerAllPullersLocked();
                 } finally {
                     restoreCallingIdentity(token);
                 }
@@ -2776,17 +830,6 @@
         }
     }
 
-    @GuardedBy("sStatsdLock")
-    private void registerAllPullersLocked() throws RemoteException {
-        // TODO: pass in one call, using a file descriptor (similar to uidmap).
-        for (Map.Entry<PullerKey, PullerValue> entry : mPullers.entrySet()) {
-            PullerKey key = entry.getKey();
-            PullerValue val = entry.getValue();
-            sStatsd.registerPullAtomCallback(key.getUid(), key.getAtom(), val.getCoolDownNs(),
-                    val.getTimeoutNs(), val.getAdditiveFields(), val.getCallback());
-        }
-    }
-
     private class StatsdDeathRecipient implements IBinder.DeathRecipient {
         @Override
         public void binderDied() {
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
index eff9bda..04d8b00 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
@@ -24,6 +24,7 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.os.Binder;
+import android.os.IPullAtomCallback;
 import android.os.IStatsManagerService;
 import android.os.IStatsd;
 import android.os.Process;
@@ -60,8 +61,7 @@
     @GuardedBy("mLock")
     private ArrayMap<ConfigKey, PendingIntentRef> mDataFetchPirMap = new ArrayMap<>();
     @GuardedBy("mLock")
-    private ArrayMap<Integer, PendingIntentRef> mActiveConfigsPirMap =
-            new ArrayMap<>();
+    private ArrayMap<Integer, PendingIntentRef> mActiveConfigsPirMap = new ArrayMap<>();
     @GuardedBy("mLock")
     private ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> mBroadcastSubscriberPirMap =
             new ArrayMap<>();
@@ -72,8 +72,8 @@
     }
 
     private static class ConfigKey {
-        private int mUid;
-        private long mConfigId;
+        private final int mUid;
+        private final long mConfigId;
 
         ConfigKey(int uid, long configId) {
             mUid = uid;
@@ -103,6 +103,126 @@
         }
     }
 
+    private static class PullerKey {
+        private final int mUid;
+        private final int mAtomTag;
+
+        PullerKey(int uid, int atom) {
+            mUid = uid;
+            mAtomTag = atom;
+        }
+
+        public int getUid() {
+            return mUid;
+        }
+
+        public int getAtom() {
+            return mAtomTag;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mUid, mAtomTag);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof PullerKey) {
+                PullerKey other = (PullerKey) obj;
+                return this.mUid == other.getUid() && this.mAtomTag == other.getAtom();
+            }
+            return false;
+        }
+    }
+
+    private static class PullerValue {
+        private final long mCoolDownNs;
+        private final long mTimeoutNs;
+        private final int[] mAdditiveFields;
+        private final IPullAtomCallback mCallback;
+
+        PullerValue(long coolDownNs, long timeoutNs, int[] additiveFields,
+                IPullAtomCallback callback) {
+            mCoolDownNs = coolDownNs;
+            mTimeoutNs = timeoutNs;
+            mAdditiveFields = additiveFields;
+            mCallback = callback;
+        }
+
+        public long getCoolDownNs() {
+            return mCoolDownNs;
+        }
+
+        public long getTimeoutNs() {
+            return mTimeoutNs;
+        }
+
+        public int[] getAdditiveFields() {
+            return mAdditiveFields;
+        }
+
+        public IPullAtomCallback getCallback() {
+            return mCallback;
+        }
+    }
+
+    private final ArrayMap<PullerKey, PullerValue> mPullers = new ArrayMap<>();
+
+    @Override
+    public void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs,
+            int[] additiveFields, IPullAtomCallback pullerCallback) {
+        int callingUid = Binder.getCallingUid();
+        final long token = Binder.clearCallingIdentity();
+        PullerKey key = new PullerKey(callingUid, atomTag);
+        PullerValue val = new PullerValue(coolDownNs, timeoutNs, additiveFields, pullerCallback);
+
+        // Always cache the puller in StatsManagerService. If statsd is down, we will register the
+        // puller when statsd comes back up.
+        synchronized (mLock) {
+            mPullers.put(key, val);
+        }
+
+        IStatsd statsd = getStatsdNonblocking();
+        if (statsd == null) {
+            return;
+        }
+
+        try {
+            statsd.registerPullAtomCallback(
+                    callingUid, atomTag, coolDownNs, timeoutNs, additiveFields, pullerCallback);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public void unregisterPullAtomCallback(int atomTag) {
+        int callingUid = Binder.getCallingUid();
+        final long token = Binder.clearCallingIdentity();
+        PullerKey key = new PullerKey(callingUid, atomTag);
+
+        // Always remove the puller from StatsManagerService even if statsd is down. When statsd
+        // comes back up, we will not re-register the removed puller.
+        synchronized (mLock) {
+            mPullers.remove(key);
+        }
+
+        IStatsd statsd = getStatsdNonblocking();
+        if (statsd == null) {
+            return;
+        }
+
+        try {
+            statsd.unregisterPullAtomCallback(callingUid, atomTag);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to access statsd to unregister puller for atom " + atomTag);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     @Override
     public void setDataFetchOperation(long configId, PendingIntent pendingIntent,
             String packageName) {
@@ -301,6 +421,48 @@
         throw new IllegalStateException("Failed to connect to statsd to getData");
     }
 
+    @Override
+    public void addConfiguration(long configId, byte[] config, String packageName)
+            throws IllegalStateException {
+        enforceDumpAndUsageStatsPermission(packageName);
+        int callingUid = Binder.getCallingUid();
+        final long token = Binder.clearCallingIdentity();
+        try {
+            IStatsd statsd = waitForStatsd();
+            if (statsd != null) {
+                statsd.addConfiguration(configId, config, callingUid);
+                return;
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to addConfiguration with statsd");
+            throw new IllegalStateException(e.getMessage(), e);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        throw new IllegalStateException("Failed to connect to statsd to addConfig");
+    }
+
+    @Override
+    public void removeConfiguration(long configId, String packageName)
+            throws IllegalStateException {
+        enforceDumpAndUsageStatsPermission(packageName);
+        int callingUid = Binder.getCallingUid();
+        final long token = Binder.clearCallingIdentity();
+        try {
+            IStatsd statsd = waitForStatsd();
+            if (statsd != null) {
+                statsd.removeConfiguration(configId, callingUid);
+                return;
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to removeConfiguration with statsd");
+            throw new IllegalStateException(e.getMessage(), e);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        throw new IllegalStateException("Failed to connect to statsd to removeConfig");
+    }
+
     void setStatsCompanionService(StatsCompanionService statsCompanionService) {
         mStatsCompanionService = statsCompanionService;
     }
@@ -399,46 +561,85 @@
         if (statsd == null) {
             return;
         }
-        // Since we do not want to make an IPC with the a lock held, we first create local deep
-        // copies of the data with the lock held before iterating through the maps.
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            registerAllPullers(statsd);
+            registerAllDataFetchOperations(statsd);
+            registerAllActiveConfigsChangedOperations(statsd);
+            registerAllBroadcastSubscribers(statsd);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "StatsManager failed to (re-)register data with statsd");
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    // Pre-condition: the Binder calling identity has already been cleared
+    private void registerAllPullers(IStatsd statsd) throws RemoteException {
+        // Since we do not want to make an IPC with the lock held, we first create a copy of the
+        // data with the lock held before iterating through the map.
+        ArrayMap<PullerKey, PullerValue> pullersCopy;
+        synchronized (mLock) {
+            pullersCopy = new ArrayMap<>(mPullers);
+        }
+
+        for (Map.Entry<PullerKey, PullerValue> entry : pullersCopy.entrySet()) {
+            PullerKey key = entry.getKey();
+            PullerValue value = entry.getValue();
+            statsd.registerPullAtomCallback(key.getUid(), key.getAtom(), value.getCoolDownNs(),
+                    value.getTimeoutNs(), value.getAdditiveFields(), value.getCallback());
+        }
+    }
+
+    // Pre-condition: the Binder calling identity has already been cleared
+    private void registerAllDataFetchOperations(IStatsd statsd) throws RemoteException {
+        // Since we do not want to make an IPC with the lock held, we first create a copy of the
+        // data with the lock held before iterating through the map.
         ArrayMap<ConfigKey, PendingIntentRef> dataFetchCopy;
-        ArrayMap<Integer, PendingIntentRef> activeConfigsChangedCopy;
-        ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> broadcastSubscriberCopy;
         synchronized (mLock) {
             dataFetchCopy = new ArrayMap<>(mDataFetchPirMap);
-            activeConfigsChangedCopy = new ArrayMap<>(mActiveConfigsPirMap);
-            broadcastSubscriberCopy = new ArrayMap<>();
-            for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry
-                    : mBroadcastSubscriberPirMap.entrySet()) {
-                broadcastSubscriberCopy.put(entry.getKey(), new ArrayMap<>(entry.getValue()));
-            }
         }
+
         for (Map.Entry<ConfigKey, PendingIntentRef> entry : dataFetchCopy.entrySet()) {
             ConfigKey key = entry.getKey();
-            try {
-                statsd.setDataFetchOperation(key.getConfigId(), entry.getValue(), key.getUid());
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to setDataFetchOperation from pirMap");
+            statsd.setDataFetchOperation(key.getConfigId(), entry.getValue(), key.getUid());
+        }
+    }
+
+    // Pre-condition: the Binder calling identity has already been cleared
+    private void registerAllActiveConfigsChangedOperations(IStatsd statsd) throws RemoteException {
+        // Since we do not want to make an IPC with the lock held, we first create a copy of the
+        // data with the lock held before iterating through the map.
+        ArrayMap<Integer, PendingIntentRef> activeConfigsChangedCopy;
+        synchronized (mLock) {
+            activeConfigsChangedCopy = new ArrayMap<>(mActiveConfigsPirMap);
+        }
+
+        for (Map.Entry<Integer, PendingIntentRef> entry : activeConfigsChangedCopy.entrySet()) {
+            statsd.setActiveConfigsChangedOperation(entry.getValue(), entry.getKey());
+        }
+    }
+
+    // Pre-condition: the Binder calling identity has already been cleared
+    private void registerAllBroadcastSubscribers(IStatsd statsd) throws RemoteException {
+        // Since we do not want to make an IPC with the lock held, we first create a deep copy of
+        // the data with the lock held before iterating through the map.
+        ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> broadcastSubscriberCopy =
+                new ArrayMap<>();
+        synchronized (mLock) {
+            for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry :
+                    mBroadcastSubscriberPirMap.entrySet()) {
+                broadcastSubscriberCopy.put(entry.getKey(), new ArrayMap(entry.getValue()));
             }
         }
-        for (Map.Entry<Integer, PendingIntentRef> entry
-                : activeConfigsChangedCopy.entrySet()) {
-            try {
-                statsd.setActiveConfigsChangedOperation(entry.getValue(), entry.getKey());
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to setActiveConfigsChangedOperation from pirMap");
-            }
-        }
-        for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry
-                : broadcastSubscriberCopy.entrySet()) {
+
+        for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry :
+                mBroadcastSubscriberPirMap.entrySet()) {
+            ConfigKey configKey = entry.getKey();
             for (Map.Entry<Long, PendingIntentRef> subscriberEntry : entry.getValue().entrySet()) {
-                ConfigKey configKey = entry.getKey();
-                try {
-                    statsd.setBroadcastSubscriber(configKey.getConfigId(), subscriberEntry.getKey(),
-                            subscriberEntry.getValue(), configKey.getUid());
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Failed to setBroadcastSubscriber from pirMap");
-                }
+                statsd.setBroadcastSubscriber(configKey.getConfigId(), subscriberEntry.getKey(),
+                        subscriberEntry.getValue(), configKey.getUid());
             }
         }
     }
diff --git a/api/current.txt b/api/current.txt
index 65a4217..6eb6582 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -39,6 +39,7 @@
     field public static final String BIND_NFC_SERVICE = "android.permission.BIND_NFC_SERVICE";
     field public static final String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE";
     field public static final String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE";
+    field public static final String BIND_QUICK_ACCESS_WALLET_SERVICE = "android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE";
     field public static final String BIND_QUICK_SETTINGS_TILE = "android.permission.BIND_QUICK_SETTINGS_TILE";
     field public static final String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS";
     field public static final String BIND_SCREENING_SERVICE = "android.permission.BIND_SCREENING_SERVICE";
@@ -115,6 +116,7 @@
     field public static final String READ_LOGS = "android.permission.READ_LOGS";
     field public static final String READ_PHONE_NUMBERS = "android.permission.READ_PHONE_NUMBERS";
     field public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
+    field public static final String READ_PRECISE_PHONE_STATE = "android.permission.READ_PRECISE_PHONE_STATE";
     field public static final String READ_SMS = "android.permission.READ_SMS";
     field public static final String READ_SYNC_SETTINGS = "android.permission.READ_SYNC_SETTINGS";
     field public static final String READ_SYNC_STATS = "android.permission.READ_SYNC_STATS";
@@ -385,6 +387,7 @@
     field public static final int canRequestFingerprintGestures = 16844109; // 0x101054d
     field public static final int canRequestTouchExplorationMode = 16843735; // 0x10103d7
     field public static final int canRetrieveWindowContent = 16843653; // 0x1010385
+    field public static final int canTakeScreenshot = 16844304; // 0x1010610
     field public static final int candidatesTextStyleSpans = 16843312; // 0x1010230
     field public static final int cantSaveState = 16844142; // 0x101056e
     field @Deprecated public static final int capitalize = 16843113; // 0x1010169
@@ -1144,6 +1147,7 @@
     field public static final int resizeable = 16843405; // 0x101028d
     field public static final int resizeableActivity = 16844022; // 0x10104f6
     field public static final int resource = 16842789; // 0x1010025
+    field public static final int resourcesMap = 16844297; // 0x1010609
     field public static final int restoreAnyVersion = 16843450; // 0x10102ba
     field @Deprecated public static final int restoreNeedsApplication = 16843421; // 0x101029d
     field public static final int restrictedAccountType = 16843733; // 0x10103d5
@@ -2851,6 +2855,7 @@
     method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
     method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
     method @NonNull public final android.accessibilityservice.AccessibilityService.SoftKeyboardController getSoftKeyboardController();
+    method @NonNull public final java.util.List<android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction> getSystemActions();
     method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows();
     method @NonNull public final android.util.SparseArray<java.util.List<android.view.accessibility.AccessibilityWindowInfo>> getWindowsOnAllDisplays();
     method public abstract void onAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
@@ -2860,8 +2865,10 @@
     method public abstract void onInterrupt();
     method protected boolean onKeyEvent(android.view.KeyEvent);
     method protected void onServiceConnected();
+    method public void onSystemActionsChanged();
     method public final boolean performGlobalAction(int);
     method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo);
+    method public boolean takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.graphics.Bitmap>);
     field public static final int GESTURE_SWIPE_DOWN = 2; // 0x2
     field public static final int GESTURE_SWIPE_DOWN_AND_LEFT = 15; // 0xf
     field public static final int GESTURE_SWIPE_DOWN_AND_RIGHT = 16; // 0x10
@@ -2923,6 +2930,7 @@
     method public int getShowMode();
     method public boolean removeOnShowModeChangedListener(@NonNull android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener);
     method public boolean setShowMode(int);
+    method public boolean switchToInputMethod(@NonNull String);
   }
 
   public static interface AccessibilityService.SoftKeyboardController.OnShowModeChangedListener {
@@ -2957,6 +2965,7 @@
     field public static final int CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES = 64; // 0x40
     field public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 2; // 0x2
     field public static final int CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT = 1; // 0x1
+    field public static final int CAPABILITY_CAN_TAKE_SCREENSHOT = 128; // 0x80
     field @NonNull public static final android.os.Parcelable.Creator<android.accessibilityservice.AccessibilityServiceInfo> CREATOR;
     field public static final int DEFAULT = 1; // 0x1
     field public static final int FEEDBACK_ALL_MASK = -1; // 0xffffffff
@@ -3969,6 +3978,7 @@
     method public android.util.Size getAppTaskThumbnailSize();
     method public java.util.List<android.app.ActivityManager.AppTask> getAppTasks();
     method public android.content.pm.ConfigurationInfo getDeviceConfigurationInfo();
+    method @Nullable public java.util.List<android.app.ApplicationExitInfo> getHistoricalProcessExitReasons(@Nullable String, @IntRange(from=0) int, @IntRange(from=0) int);
     method public int getLargeMemoryClass();
     method public int getLauncherLargeIconDensity();
     method public int getLauncherLargeIconSize();
@@ -3986,6 +3996,7 @@
     method public boolean isActivityStartAllowedOnDisplay(@NonNull android.content.Context, int, @NonNull android.content.Intent);
     method public boolean isBackgroundRestricted();
     method @Deprecated public boolean isInLockTaskMode();
+    method public static boolean isLowMemoryKillReportSupported();
     method public boolean isLowRamDevice();
     method @Deprecated public static boolean isRunningInTestHarness();
     method public static boolean isRunningInUserTestHarness();
@@ -4499,6 +4510,35 @@
     field public String serviceDetails;
   }
 
+  public final class ApplicationExitInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getDefiningUid();
+    method @Nullable public String getDescription();
+    method public int getImportance();
+    method public int getPackageUid();
+    method public int getPid();
+    method @NonNull public String getProcessName();
+    method public int getPss();
+    method public int getRealUid();
+    method public int getReason();
+    method public int getRss();
+    method public int getStatus();
+    method public long getTimestamp();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.ApplicationExitInfo> CREATOR;
+    field public static final int REASON_ANR = 6; // 0x6
+    field public static final int REASON_CRASH = 4; // 0x4
+    field public static final int REASON_CRASH_NATIVE = 5; // 0x5
+    field public static final int REASON_EXCESSIVE_RESOURCE_USAGE = 9; // 0x9
+    field public static final int REASON_EXIT_SELF = 1; // 0x1
+    field public static final int REASON_INITIALIZATION_FAILURE = 7; // 0x7
+    field public static final int REASON_LOW_MEMORY = 3; // 0x3
+    field public static final int REASON_OTHER = 10; // 0xa
+    field public static final int REASON_PERMISSION_CHANGE = 8; // 0x8
+    field public static final int REASON_SIGNALED = 2; // 0x2
+    field public static final int REASON_UNKNOWN = 0; // 0x0
+  }
+
   public final class AsyncNotedAppOp implements android.os.Parcelable {
     method public int describeContents();
     method @Nullable public String getFeatureId();
@@ -5569,11 +5609,14 @@
   public static final class Notification.BubbleMetadata implements android.os.Parcelable {
     method public int describeContents();
     method public boolean getAutoExpandBubble();
+    method @Nullable public android.graphics.drawable.Icon getBubbleIcon();
+    method @Nullable public android.app.PendingIntent getBubbleIntent();
     method @Nullable public android.app.PendingIntent getDeleteIntent();
     method @Dimension(unit=android.annotation.Dimension.DP) public int getDesiredHeight();
     method @DimenRes public int getDesiredHeightResId();
-    method @NonNull public android.graphics.drawable.Icon getIcon();
-    method @NonNull public android.app.PendingIntent getIntent();
+    method @Deprecated @NonNull public android.graphics.drawable.Icon getIcon();
+    method @Deprecated @NonNull public android.app.PendingIntent getIntent();
+    method @Nullable public String getShortcutId();
     method public boolean isNotificationSuppressed();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.Notification.BubbleMetadata> CREATOR;
@@ -5582,12 +5625,14 @@
   public static final class Notification.BubbleMetadata.Builder {
     ctor public Notification.BubbleMetadata.Builder();
     method @NonNull public android.app.Notification.BubbleMetadata build();
+    method @NonNull public android.app.Notification.BubbleMetadata.Builder createIntentBubble(@NonNull android.app.PendingIntent, @NonNull android.graphics.drawable.Icon);
+    method @NonNull public android.app.Notification.BubbleMetadata.Builder createShortcutBubble(@NonNull String);
     method @NonNull public android.app.Notification.BubbleMetadata.Builder setAutoExpandBubble(boolean);
     method @NonNull public android.app.Notification.BubbleMetadata.Builder setDeleteIntent(@Nullable android.app.PendingIntent);
     method @NonNull public android.app.Notification.BubbleMetadata.Builder setDesiredHeight(@Dimension(unit=android.annotation.Dimension.DP) int);
     method @NonNull public android.app.Notification.BubbleMetadata.Builder setDesiredHeightResId(@DimenRes int);
-    method @NonNull public android.app.Notification.BubbleMetadata.Builder setIcon(@NonNull android.graphics.drawable.Icon);
-    method @NonNull public android.app.Notification.BubbleMetadata.Builder setIntent(@NonNull android.app.PendingIntent);
+    method @Deprecated @NonNull public android.app.Notification.BubbleMetadata.Builder setIcon(@NonNull android.graphics.drawable.Icon);
+    method @Deprecated @NonNull public android.app.Notification.BubbleMetadata.Builder setIntent(@NonNull android.app.PendingIntent);
     method @NonNull public android.app.Notification.BubbleMetadata.Builder setSuppressNotification(boolean);
   }
 
@@ -5837,6 +5882,7 @@
     method public void enableLights(boolean);
     method public void enableVibration(boolean);
     method public android.media.AudioAttributes getAudioAttributes();
+    method @Nullable public String getConversationId();
     method public String getDescription();
     method public String getGroup();
     method public String getId();
@@ -5844,12 +5890,14 @@
     method public int getLightColor();
     method public int getLockscreenVisibility();
     method public CharSequence getName();
+    method @Nullable public String getParentChannelId();
     method public android.net.Uri getSound();
     method public long[] getVibrationPattern();
     method public boolean hasUserSetImportance();
     method public boolean hasUserSetSound();
     method public void setAllowBubbles(boolean);
     method public void setBypassDnd(boolean);
+    method public void setConversationId(@Nullable String, @Nullable String);
     method public void setDescription(String);
     method public void setGroup(String);
     method public void setImportance(int);
@@ -5862,6 +5910,7 @@
     method public boolean shouldShowLights();
     method public boolean shouldVibrate();
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final String CONVERSATION_CHANNEL_ID_FORMAT = "%1$s : %2$s";
     field @NonNull public static final android.os.Parcelable.Creator<android.app.NotificationChannel> CREATOR;
     field public static final String DEFAULT_CHANNEL_ID = "miscellaneous";
   }
@@ -5903,6 +5952,7 @@
     method public final int getCurrentInterruptionFilter();
     method public int getImportance();
     method public android.app.NotificationChannel getNotificationChannel(String);
+    method @Nullable public android.app.NotificationChannel getNotificationChannel(@NonNull String, @NonNull String);
     method public android.app.NotificationChannelGroup getNotificationChannelGroup(String);
     method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups();
     method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
@@ -6426,7 +6476,11 @@
     method public void disableCarMode(int);
     method public void enableCarMode(int);
     method public int getCurrentModeType();
+    method @NonNull public java.time.LocalTime getCustomNightModeEnd();
+    method @NonNull public java.time.LocalTime getCustomNightModeStart();
     method public int getNightMode();
+    method public void setCustomNightModeEnd(@NonNull java.time.LocalTime);
+    method public void setCustomNightModeStart(@NonNull java.time.LocalTime);
     method public void setNightMode(int);
     field public static String ACTION_ENTER_CAR_MODE;
     field public static String ACTION_ENTER_DESK_MODE;
@@ -6436,6 +6490,7 @@
     field public static final int ENABLE_CAR_MODE_ALLOW_SLEEP = 2; // 0x2
     field public static final int ENABLE_CAR_MODE_GO_CAR_HOME = 1; // 0x1
     field public static final int MODE_NIGHT_AUTO = 0; // 0x0
+    field public static final int MODE_NIGHT_CUSTOM = 3; // 0x3
     field public static final int MODE_NIGHT_NO = 1; // 0x1
     field public static final int MODE_NIGHT_YES = 2; // 0x2
   }
@@ -6713,6 +6768,13 @@
     method public final android.os.IBinder onBind(android.content.Intent);
   }
 
+  public class DevicePolicyKeyguardService extends android.app.Service {
+    ctor public DevicePolicyKeyguardService();
+    method @Nullable public void dismiss();
+    method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
+    method @Nullable public android.view.SurfaceControl onSurfaceReady(@Nullable android.os.IBinder);
+  }
+
   public class DevicePolicyManager {
     method public void addCrossProfileIntentFilter(@NonNull android.content.ComponentName, android.content.IntentFilter, int);
     method public boolean addCrossProfileWidgetProvider(@NonNull android.content.ComponentName, String);
@@ -6756,6 +6818,7 @@
     method @NonNull public java.util.List<java.lang.String> getDelegatedScopes(@Nullable android.content.ComponentName, @NonNull String);
     method public CharSequence getDeviceOwnerLockScreenInfo();
     method public CharSequence getEndUserSessionMessage(@NonNull android.content.ComponentName);
+    method @Nullable public android.app.admin.FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(@Nullable android.content.ComponentName);
     method @Nullable public String getGlobalPrivateDnsHost(@NonNull android.content.ComponentName);
     method public int getGlobalPrivateDnsMode(@NonNull android.content.ComponentName);
     method @NonNull public java.util.List<byte[]> getInstalledCaCerts(@Nullable android.content.ComponentName);
@@ -6790,13 +6853,14 @@
     method @Nullable public java.util.List<java.lang.String> getPermittedAccessibilityServices(@NonNull android.content.ComponentName);
     method @Nullable public java.util.List<java.lang.String> getPermittedCrossProfileNotificationListeners(@NonNull android.content.ComponentName);
     method @Nullable public java.util.List<java.lang.String> getPermittedInputMethods(@NonNull android.content.ComponentName);
+    method public int getPersonalAppsSuspendedReasons(@NonNull android.content.ComponentName);
     method @NonNull public java.util.List<java.lang.String> getProtectedPackages(@NonNull android.content.ComponentName);
     method public long getRequiredStrongAuthTimeout(@Nullable android.content.ComponentName);
     method public boolean getScreenCaptureDisabled(@Nullable android.content.ComponentName);
     method public java.util.List<android.os.UserHandle> getSecondaryUsers(@NonNull android.content.ComponentName);
     method public CharSequence getShortSupportMessage(@NonNull android.content.ComponentName);
     method public CharSequence getStartUserSessionMessage(@NonNull android.content.ComponentName);
-    method public boolean getStorageEncryption(@Nullable android.content.ComponentName);
+    method @Deprecated public boolean getStorageEncryption(@Nullable android.content.ComponentName);
     method public int getStorageEncryptionStatus();
     method @Nullable public android.app.admin.SystemUpdatePolicy getSystemUpdatePolicy();
     method @Nullable public android.os.PersistableBundle getTransferOwnershipBundle();
@@ -6819,14 +6883,17 @@
     method public boolean isApplicationHidden(@NonNull android.content.ComponentName, String);
     method public boolean isBackupServiceEnabled(@NonNull android.content.ComponentName);
     method @Deprecated public boolean isCallerApplicationRestrictionsManagingPackage();
+    method public boolean isCommonCriteriaModeEnabled(@NonNull android.content.ComponentName);
     method public boolean isDeviceIdAttestationSupported();
     method public boolean isDeviceOwnerApp(String);
     method public boolean isEphemeralUser(@NonNull android.content.ComponentName);
     method public boolean isLockTaskPermitted(String);
+    method public boolean isLockdownAdminConfiguredNetworks(@NonNull android.content.ComponentName);
     method public boolean isLogoutEnabled();
     method public boolean isManagedProfile(@NonNull android.content.ComponentName);
     method public boolean isMasterVolumeMuted(@NonNull android.content.ComponentName);
     method public boolean isNetworkLoggingEnabled(@Nullable android.content.ComponentName);
+    method public boolean isOrganizationOwnedDeviceWithManagedProfile();
     method public boolean isOverrideApnEnabled(@NonNull android.content.ComponentName);
     method public boolean isPackageSuspended(@NonNull android.content.ComponentName, String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public boolean isProfileOwnerApp(String);
@@ -6866,6 +6933,7 @@
     method public void setBluetoothContactSharingDisabled(@NonNull android.content.ComponentName, boolean);
     method public void setCameraDisabled(@NonNull android.content.ComponentName, boolean);
     method @Deprecated public void setCertInstallerPackage(@NonNull android.content.ComponentName, @Nullable String) throws java.lang.SecurityException;
+    method public void setCommonCriteriaModeEnabled(@NonNull android.content.ComponentName, boolean);
     method public void setCrossProfileCalendarPackages(@NonNull android.content.ComponentName, @Nullable java.util.Set<java.lang.String>);
     method public void setCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName, boolean);
     method public void setCrossProfileContactsSearchDisabled(@NonNull android.content.ComponentName, boolean);
@@ -6874,6 +6942,7 @@
     method public void setDelegatedScopes(@NonNull android.content.ComponentName, @NonNull String, @NonNull java.util.List<java.lang.String>);
     method public void setDeviceOwnerLockScreenInfo(@NonNull android.content.ComponentName, CharSequence);
     method public void setEndUserSessionMessage(@NonNull android.content.ComponentName, @Nullable CharSequence);
+    method public void setFactoryResetProtectionPolicy(@NonNull android.content.ComponentName, @Nullable android.app.admin.FactoryResetProtectionPolicy);
     method public int setGlobalPrivateDnsModeOpportunistic(@NonNull android.content.ComponentName);
     method @WorkerThread public int setGlobalPrivateDnsModeSpecifiedHost(@NonNull android.content.ComponentName, @NonNull String);
     method public void setGlobalSetting(@NonNull android.content.ComponentName, String, String);
@@ -6884,6 +6953,7 @@
     method public void setLocationEnabled(@NonNull android.content.ComponentName, boolean);
     method public void setLockTaskFeatures(@NonNull android.content.ComponentName, int);
     method public void setLockTaskPackages(@NonNull android.content.ComponentName, @NonNull String[]) throws java.lang.SecurityException;
+    method public void setLockdownAdminConfiguredNetworks(@NonNull android.content.ComponentName, boolean);
     method public void setLogoutEnabled(@NonNull android.content.ComponentName, boolean);
     method public void setLongSupportMessage(@NonNull android.content.ComponentName, @Nullable CharSequence);
     method public void setMasterVolumeMuted(@NonNull android.content.ComponentName, boolean);
@@ -6910,6 +6980,7 @@
     method public boolean setPermittedAccessibilityServices(@NonNull android.content.ComponentName, java.util.List<java.lang.String>);
     method public boolean setPermittedCrossProfileNotificationListeners(@NonNull android.content.ComponentName, @Nullable java.util.List<java.lang.String>);
     method public boolean setPermittedInputMethods(@NonNull android.content.ComponentName, java.util.List<java.lang.String>);
+    method public void setPersonalAppsSuspended(@NonNull android.content.ComponentName, boolean);
     method public void setProfileEnabled(@NonNull android.content.ComponentName);
     method public void setProfileName(@NonNull android.content.ComponentName, String);
     method public void setProtectedPackages(@NonNull android.content.ComponentName, @NonNull java.util.List<java.lang.String>);
@@ -6918,12 +6989,13 @@
     method public boolean setResetPasswordToken(android.content.ComponentName, byte[]);
     method public void setRestrictionsProvider(@NonNull android.content.ComponentName, @Nullable android.content.ComponentName);
     method public void setScreenCaptureDisabled(@NonNull android.content.ComponentName, boolean);
+    method public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean);
     method public void setSecureSetting(@NonNull android.content.ComponentName, String, String);
     method public void setSecurityLoggingEnabled(@NonNull android.content.ComponentName, boolean);
     method public void setShortSupportMessage(@NonNull android.content.ComponentName, @Nullable CharSequence);
     method public void setStartUserSessionMessage(@NonNull android.content.ComponentName, @Nullable CharSequence);
     method public boolean setStatusBarDisabled(@NonNull android.content.ComponentName, boolean);
-    method public int setStorageEncryption(@NonNull android.content.ComponentName, boolean);
+    method @Deprecated public int setStorageEncryption(@NonNull android.content.ComponentName, boolean);
     method public void setSystemSetting(@NonNull android.content.ComponentName, @NonNull String, String);
     method public void setSystemUpdatePolicy(@NonNull android.content.ComponentName, android.app.admin.SystemUpdatePolicy);
     method public boolean setTime(@NonNull android.content.ComponentName, long);
@@ -6943,6 +7015,8 @@
     field public static final String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
     field public static final String ACTION_ADMIN_POLICY_COMPLIANCE = "android.app.action.ADMIN_POLICY_COMPLIANCE";
     field public static final String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
+    field public static final String ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE = "android.app.action.BIND_SECONDARY_LOCKSCREEN_SERVICE";
+    field public static final String ACTION_CHECK_POLICY_COMPLIANCE = "android.app.action.CHECK_POLICY_COMPLIANCE";
     field public static final String ACTION_DEVICE_ADMIN_SERVICE = "android.app.action.DEVICE_ADMIN_SERVICE";
     field public static final String ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED";
     field public static final String ACTION_GET_PROVISIONING_MODE = "android.app.action.GET_PROVISIONING_MODE";
@@ -7066,6 +7140,8 @@
     field public static final int PERMISSION_POLICY_AUTO_DENY = 2; // 0x2
     field public static final int PERMISSION_POLICY_AUTO_GRANT = 1; // 0x1
     field public static final int PERMISSION_POLICY_PROMPT = 0; // 0x0
+    field public static final int PERSONAL_APPS_NOT_SUSPENDED = 0; // 0x0
+    field public static final int PERSONAL_APPS_SUSPENDED_EXPLICITLY = 1; // 0x1
     field public static final String POLICY_DISABLE_CAMERA = "policy_disable_camera";
     field public static final String POLICY_DISABLE_SCREEN_CAPTURE = "policy_disable_screen_capture";
     field public static final int PRIVATE_DNS_MODE_OFF = 1; // 0x1
@@ -7108,6 +7184,21 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DnsEvent> CREATOR;
   }
 
+  public final class FactoryResetProtectionPolicy implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<java.lang.String> getFactoryResetProtectionAccounts();
+    method public boolean isFactoryResetProtectionDisabled();
+    method public void writeToParcel(@NonNull android.os.Parcel, @Nullable int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.FactoryResetProtectionPolicy> CREATOR;
+  }
+
+  public static class FactoryResetProtectionPolicy.Builder {
+    ctor public FactoryResetProtectionPolicy.Builder();
+    method @NonNull public android.app.admin.FactoryResetProtectionPolicy build();
+    method @NonNull public android.app.admin.FactoryResetProtectionPolicy.Builder setFactoryResetProtectionAccounts(@NonNull java.util.List<java.lang.String>);
+    method @NonNull public android.app.admin.FactoryResetProtectionPolicy.Builder setFactoryResetProtectionDisabled(boolean);
+  }
+
   public class FreezePeriod {
     ctor public FreezePeriod(java.time.MonthDay, java.time.MonthDay);
     method public java.time.MonthDay getEnd();
@@ -7418,6 +7509,45 @@
 
 }
 
+package android.app.blob {
+
+  public final class BlobHandle implements android.os.Parcelable {
+    method @NonNull public static android.app.blob.BlobHandle createWithSha256(@NonNull byte[], @NonNull CharSequence, long, @NonNull String);
+    method public int describeContents();
+    method public long getExpiryTimeMillis();
+    method @NonNull public CharSequence getLabel();
+    method @NonNull public byte[] getSha256Digest();
+    method @NonNull public String getTag();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.blob.BlobHandle> CREATOR;
+  }
+
+  public class BlobStoreManager {
+    method public void acquireLease(@NonNull android.app.blob.BlobHandle, @IdRes int, long) throws java.io.IOException;
+    method public void acquireLease(@NonNull android.app.blob.BlobHandle, @IdRes int) throws java.io.IOException;
+    method @IntRange(from=1) public long createSession(@NonNull android.app.blob.BlobHandle) throws java.io.IOException;
+    method public void deleteSession(@IntRange(from=1) long) throws java.io.IOException;
+    method @NonNull public android.os.ParcelFileDescriptor openBlob(@NonNull android.app.blob.BlobHandle) throws java.io.IOException;
+    method @NonNull public android.app.blob.BlobStoreManager.Session openSession(@IntRange(from=1) long) throws java.io.IOException;
+    method public void releaseLease(@NonNull android.app.blob.BlobHandle) throws java.io.IOException;
+  }
+
+  public static class BlobStoreManager.Session implements java.io.Closeable {
+    method public void abandon() throws java.io.IOException;
+    method public void allowPackageAccess(@NonNull String, @NonNull byte[]) throws java.io.IOException;
+    method public void allowPublicAccess() throws java.io.IOException;
+    method public void allowSameSignatureAccess() throws java.io.IOException;
+    method public void close() throws java.io.IOException;
+    method public void commit(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>) throws java.io.IOException;
+    method public long getSize() throws java.io.IOException;
+    method public boolean isPackageAccessAllowed(@NonNull String, @NonNull byte[]) throws java.io.IOException;
+    method public boolean isPublicAccessAllowed() throws java.io.IOException;
+    method public boolean isSameSignatureAccessAllowed() throws java.io.IOException;
+    method @NonNull public android.os.ParcelFileDescriptor openWrite(long, long) throws java.io.IOException;
+  }
+
+}
+
 package android.app.job {
 
   public class JobInfo implements android.os.Parcelable {
@@ -7905,6 +8035,7 @@
     field public static final int STANDBY_BUCKET_ACTIVE = 10; // 0xa
     field public static final int STANDBY_BUCKET_FREQUENT = 30; // 0x1e
     field public static final int STANDBY_BUCKET_RARE = 40; // 0x28
+    field public static final int STANDBY_BUCKET_RESTRICTED = 45; // 0x2d
     field public static final int STANDBY_BUCKET_WORKING_SET = 20; // 0x14
   }
 
@@ -9504,6 +9635,7 @@
     method public void dump(java.io.FileDescriptor, java.io.PrintWriter, String[]);
     method @Nullable public final String getCallingFeatureId();
     method @Nullable public final String getCallingPackage();
+    method @Nullable public final String getCallingPackageUnchecked();
     method @Nullable public final android.content.Context getContext();
     method @Nullable public final android.content.pm.PathPermission[] getPathPermissions();
     method @Nullable public final String getReadPermission();
@@ -9513,6 +9645,7 @@
     method @Nullable public abstract android.net.Uri insert(@NonNull android.net.Uri, @Nullable android.content.ContentValues);
     method @Nullable public android.net.Uri insert(@NonNull android.net.Uri, @Nullable android.content.ContentValues, @Nullable android.os.Bundle);
     method protected boolean isTemporary();
+    method public void onCallingPackageChanged();
     method public void onConfigurationChanged(android.content.res.Configuration);
     method public abstract boolean onCreate();
     method public void onLowMemory();
@@ -9627,13 +9760,13 @@
     ctor public ContentProviderResult(@NonNull android.net.Uri);
     ctor public ContentProviderResult(int);
     ctor public ContentProviderResult(@NonNull android.os.Bundle);
-    ctor public ContentProviderResult(@NonNull Exception);
+    ctor public ContentProviderResult(@NonNull Throwable);
     ctor public ContentProviderResult(android.os.Parcel);
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.content.ContentProviderResult> CREATOR;
     field @Nullable public final Integer count;
-    field @Nullable public final Exception exception;
+    field @Nullable public final Throwable exception;
     field @Nullable public final android.os.Bundle extras;
     field @Nullable public final android.net.Uri uri;
   }
@@ -9821,6 +9954,7 @@
     method public boolean bindIsolatedService(@NonNull @RequiresPermission android.content.Intent, int, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.content.ServiceConnection);
     method public abstract boolean bindService(@RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int);
     method public boolean bindService(@NonNull @RequiresPermission android.content.Intent, int, @NonNull java.util.concurrent.Executor, @NonNull android.content.ServiceConnection);
+    method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.INTERACT_ACROSS_PROFILES}) public boolean bindServiceAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull android.os.UserHandle);
     method @CheckResult(suggest="#enforceCallingOrSelfPermission(String,String)") public abstract int checkCallingOrSelfPermission(@NonNull String);
     method @CheckResult(suggest="#enforceCallingOrSelfUriPermission(Uri,int,String)") public abstract int checkCallingOrSelfUriPermission(android.net.Uri, int);
     method @CheckResult(suggest="#enforceCallingPermission(String,String)") public abstract int checkCallingPermission(@NonNull String);
@@ -9836,6 +9970,7 @@
     method public abstract android.content.Context createDisplayContext(@NonNull android.view.Display);
     method @NonNull public android.content.Context createFeatureContext(@Nullable String);
     method public abstract android.content.Context createPackageContext(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull public android.content.Context createWindowContext(int);
     method public abstract String[] databaseList();
     method public abstract boolean deleteDatabase(String);
     method public abstract boolean deleteFile(String);
@@ -9860,6 +9995,7 @@
     method public abstract java.io.File getDataDir();
     method public abstract java.io.File getDatabasePath(String);
     method public abstract java.io.File getDir(String, int);
+    method @Nullable public android.view.Display getDisplay();
     method @Nullable public final android.graphics.drawable.Drawable getDrawable(@DrawableRes int);
     method @Nullable public abstract java.io.File getExternalCacheDir();
     method public abstract java.io.File[] getExternalCacheDirs();
@@ -9972,6 +10108,7 @@
     field public static final String CARRIER_CONFIG_SERVICE = "carrier_config";
     field public static final String CLIPBOARD_SERVICE = "clipboard";
     field public static final String COMPANION_DEVICE_SERVICE = "companiondevice";
+    field public static final String CONNECTIVITY_DIAGNOSTICS_SERVICE = "connectivity_diagnostics";
     field public static final String CONNECTIVITY_SERVICE = "connectivity";
     field public static final String CONSUMER_IR_SERVICE = "consumer_ir";
     field public static final int CONTEXT_IGNORE_SECURITY = 2; // 0x2
@@ -9998,6 +10135,7 @@
     field public static final String MEDIA_ROUTER_SERVICE = "media_router";
     field public static final String MEDIA_SESSION_SERVICE = "media_session";
     field public static final String MIDI_SERVICE = "midi";
+    field public static final String MMS_SERVICE = "mms";
     field public static final int MODE_APPEND = 32768; // 0x8000
     field public static final int MODE_ENABLE_WRITE_AHEAD_LOGGING = 8; // 0x8
     field @Deprecated public static final int MODE_MULTI_PROCESS = 4; // 0x4
@@ -10372,6 +10510,7 @@
     field public static final String ACTION_CLOSE_SYSTEM_DIALOGS = "android.intent.action.CLOSE_SYSTEM_DIALOGS";
     field public static final String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED";
     field public static final String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT";
+    field public static final String ACTION_CREATE_REMINDER = "android.intent.action.CREATE_REMINDER";
     field public static final String ACTION_CREATE_SHORTCUT = "android.intent.action.CREATE_SHORTCUT";
     field public static final String ACTION_DATE_CHANGED = "android.intent.action.DATE_CHANGED";
     field public static final String ACTION_DEFAULT = "android.intent.action.VIEW";
@@ -10597,6 +10736,7 @@
     field public static final String EXTRA_SUSPENDED_PACKAGE_EXTRAS = "android.intent.extra.SUSPENDED_PACKAGE_EXTRAS";
     field public static final String EXTRA_TEMPLATE = "android.intent.extra.TEMPLATE";
     field public static final String EXTRA_TEXT = "android.intent.extra.TEXT";
+    field public static final String EXTRA_TIME = "android.intent.extra.TIME";
     field public static final String EXTRA_TITLE = "android.intent.extra.TITLE";
     field public static final String EXTRA_UID = "android.intent.extra.UID";
     field public static final String EXTRA_USER = "android.intent.extra.USER";
@@ -11353,10 +11493,15 @@
   }
 
   public class CrossProfileApps {
+    method public boolean canInteractAcrossProfiles();
+    method public boolean canRequestInteractAcrossProfiles();
+    method @Nullable public android.content.Intent createRequestInteractAcrossProfilesIntent();
     method @NonNull public android.graphics.drawable.Drawable getProfileSwitchingIconDrawable(@NonNull android.os.UserHandle);
     method @NonNull public CharSequence getProfileSwitchingLabel(@NonNull android.os.UserHandle);
     method @NonNull public java.util.List<android.os.UserHandle> getTargetUserProfiles();
+    method @RequiresPermission(anyOf={android.Manifest.permission.INTERACT_ACROSS_PROFILES, "android.permission.INTERACT_ACROSS_USERS"}) public void startActivity(@NonNull android.content.Intent, @NonNull android.os.UserHandle);
     method public void startMainActivity(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle);
+    field public static final String ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED = "android.content.pm.action.CAN_INTERACT_ACROSS_PROFILES_CHANGED";
   }
 
   public final class FeatureGroupInfo implements android.os.Parcelable {
@@ -11386,6 +11531,7 @@
   public final class InstallSourceInfo implements android.os.Parcelable {
     method public int describeContents();
     method @Nullable public String getInitiatingPackageName();
+    method @Nullable public android.content.pm.SigningInfo getInitiatingPackageSigningInfo();
     method @Nullable public String getInstallingPackageName();
     method @Nullable public String getOriginatingPackageName();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -11590,11 +11736,8 @@
     field public static final int STATUS_FAILURE_ABORTED = 3; // 0x3
     field public static final int STATUS_FAILURE_BLOCKED = 2; // 0x2
     field public static final int STATUS_FAILURE_CONFLICT = 5; // 0x5
-    field public static final int STATUS_FAILURE_ILLEGAL_STATE = 9; // 0x9
     field public static final int STATUS_FAILURE_INCOMPATIBLE = 7; // 0x7
     field public static final int STATUS_FAILURE_INVALID = 4; // 0x4
-    field public static final int STATUS_FAILURE_NAME_NOT_FOUND = 8; // 0x8
-    field public static final int STATUS_FAILURE_SECURITY = 10; // 0xa
     field public static final int STATUS_FAILURE_STORAGE = 6; // 0x6
     field public static final int STATUS_PENDING_USER_ACTION = -1; // 0xffffffff
     field public static final int STATUS_SUCCESS = 0; // 0x0
@@ -11616,7 +11759,6 @@
     method public void removeChildSessionId(int);
     method public void removeSplit(@NonNull String) throws java.io.IOException;
     method public void setStagingProgress(float);
-    method public void transfer(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException;
     method public void transfer(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
   }
 
@@ -11636,6 +11778,7 @@
     method @Nullable public CharSequence getAppLabel();
     method @Nullable public String getAppPackageName();
     method @NonNull public int[] getChildSessionIds();
+    method public long getCreatedMillis();
     method public int getInstallLocation();
     method public int getInstallReason();
     method @Nullable public String getInstallerPackageName();
@@ -11752,6 +11895,7 @@
     method @NonNull public abstract CharSequence getApplicationLabel(@NonNull android.content.pm.ApplicationInfo);
     method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull android.content.pm.ApplicationInfo);
     method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull public CharSequence getBackgroundPermissionOptionLabel();
     method @Nullable public abstract android.content.pm.ChangedPackages getChangedPackages(@IntRange(from=0) int);
     method public abstract int getComponentEnabledSetting(@NonNull android.content.ComponentName);
     method @NonNull public abstract android.graphics.drawable.Drawable getDefaultActivityIcon();
@@ -11802,6 +11946,7 @@
     method public boolean hasSigningCertificate(int, @NonNull byte[], int);
     method public abstract boolean hasSystemFeature(@NonNull String);
     method public abstract boolean hasSystemFeature(@NonNull String, int);
+    method public boolean isDefaultApplicationIcon(@NonNull android.graphics.drawable.Drawable);
     method public boolean isDeviceUpgrading();
     method public abstract boolean isInstantApp();
     method public abstract boolean isInstantApp(@NonNull String);
@@ -11864,6 +12009,7 @@
     field public static final String FEATURE_COMPANION_DEVICE_SETUP = "android.software.companion_device_setup";
     field public static final String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
     field public static final String FEATURE_CONSUMER_IR = "android.hardware.consumerir";
+    field public static final String FEATURE_CONTEXTHUB = "android.hardware.context_hub";
     field public static final String FEATURE_DEVICE_ADMIN = "android.software.device_admin";
     field public static final String FEATURE_EMBEDDED = "android.hardware.type.embedded";
     field public static final String FEATURE_ETHERNET = "android.hardware.ethernet";
@@ -11940,6 +12086,7 @@
     field public static final String FEATURE_VR_HEADTRACKING = "android.hardware.vr.headtracking";
     field @Deprecated public static final String FEATURE_VR_MODE = "android.software.vr.mode";
     field public static final String FEATURE_VR_MODE_HIGH_PERFORMANCE = "android.hardware.vr.high_performance";
+    field public static final String FEATURE_VULKAN_DEQP_LEVEL = "android.software.vulkan.deqp.level";
     field public static final String FEATURE_VULKAN_HARDWARE_COMPUTE = "android.hardware.vulkan.compute";
     field public static final String FEATURE_VULKAN_HARDWARE_LEVEL = "android.hardware.vulkan.level";
     field public static final String FEATURE_VULKAN_HARDWARE_VERSION = "android.hardware.vulkan.version";
@@ -12145,12 +12292,14 @@
     field public static final int FLAG_SINGLE_USER = 1073741824; // 0x40000000
     field public static final int FLAG_STOP_WITH_TASK = 1; // 0x1
     field public static final int FLAG_USE_APP_ZYGOTE = 8; // 0x8
+    field public static final int FOREGROUND_SERVICE_TYPE_CAMERA = 64; // 0x40
     field public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 16; // 0x10
     field public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1; // 0x1
     field public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 8; // 0x8
     field public static final int FOREGROUND_SERVICE_TYPE_MANIFEST = -1; // 0xffffffff
     field public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK = 2; // 0x2
     field public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION = 32; // 0x20
+    field public static final int FOREGROUND_SERVICE_TYPE_MICROPHONE = 128; // 0x80
     field public static final int FOREGROUND_SERVICE_TYPE_NONE = 0; // 0x0
     field public static final int FOREGROUND_SERVICE_TYPE_PHONE_CALL = 4; // 0x4
     field public int flags;
@@ -13170,6 +13319,8 @@
     method public static int releaseMemory();
     method public long replace(String, String, android.content.ContentValues);
     method public long replaceOrThrow(String, String, android.content.ContentValues) throws android.database.SQLException;
+    method public void setCustomAggregateFunction(@NonNull String, @NonNull java.util.function.BinaryOperator<java.lang.String>) throws android.database.sqlite.SQLiteException;
+    method public void setCustomScalarFunction(@NonNull String, @NonNull java.util.function.UnaryOperator<java.lang.String>) throws android.database.sqlite.SQLiteException;
     method public void setForeignKeyConstraintsEnabled(boolean);
     method public void setLocale(java.util.Locale);
     method @Deprecated public void setLockingEnabled(boolean);
@@ -13323,6 +13474,7 @@
     method @Deprecated public String buildUnionSubQuery(String, String[], java.util.Set<java.lang.String>, int, String, String, String[], String, String);
     method public int delete(@NonNull android.database.sqlite.SQLiteDatabase, @Nullable String, @Nullable String[]);
     method @Nullable public android.database.sqlite.SQLiteDatabase.CursorFactory getCursorFactory();
+    method @Nullable public java.util.Collection<java.util.regex.Pattern> getProjectionGreylist();
     method @Nullable public java.util.Map<java.lang.String,java.lang.String> getProjectionMap();
     method @Nullable public String getTables();
     method public long insert(@NonNull android.database.sqlite.SQLiteDatabase, @NonNull android.content.ContentValues);
@@ -13335,6 +13487,7 @@
     method public android.database.Cursor query(android.database.sqlite.SQLiteDatabase, String[], String, String[], String, String, String, String, android.os.CancellationSignal);
     method public void setCursorFactory(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory);
     method public void setDistinct(boolean);
+    method public void setProjectionGreylist(@Nullable java.util.Collection<java.util.regex.Pattern>);
     method public void setProjectionMap(@Nullable java.util.Map<java.lang.String,java.lang.String>);
     method public void setStrict(boolean);
     method public void setStrictColumns(boolean);
@@ -13372,227 +13525,227 @@
 
 package android.drm {
 
-  public class DrmConvertedStatus {
-    ctor public DrmConvertedStatus(int, byte[], int);
-    field public static final int STATUS_ERROR = 3; // 0x3
-    field public static final int STATUS_INPUTDATA_ERROR = 2; // 0x2
-    field public static final int STATUS_OK = 1; // 0x1
-    field public final byte[] convertedData;
-    field public final int offset;
-    field public final int statusCode;
+  @Deprecated public class DrmConvertedStatus {
+    ctor @Deprecated public DrmConvertedStatus(int, byte[], int);
+    field @Deprecated public static final int STATUS_ERROR = 3; // 0x3
+    field @Deprecated public static final int STATUS_INPUTDATA_ERROR = 2; // 0x2
+    field @Deprecated public static final int STATUS_OK = 1; // 0x1
+    field @Deprecated public final byte[] convertedData;
+    field @Deprecated public final int offset;
+    field @Deprecated public final int statusCode;
   }
 
-  public class DrmErrorEvent extends android.drm.DrmEvent {
-    ctor public DrmErrorEvent(int, int, String);
-    ctor public DrmErrorEvent(int, int, String, java.util.HashMap<java.lang.String,java.lang.Object>);
-    field public static final int TYPE_ACQUIRE_DRM_INFO_FAILED = 2008; // 0x7d8
-    field public static final int TYPE_NOT_SUPPORTED = 2003; // 0x7d3
-    field public static final int TYPE_NO_INTERNET_CONNECTION = 2005; // 0x7d5
-    field public static final int TYPE_OUT_OF_MEMORY = 2004; // 0x7d4
-    field public static final int TYPE_PROCESS_DRM_INFO_FAILED = 2006; // 0x7d6
-    field public static final int TYPE_REMOVE_ALL_RIGHTS_FAILED = 2007; // 0x7d7
-    field public static final int TYPE_RIGHTS_NOT_INSTALLED = 2001; // 0x7d1
-    field public static final int TYPE_RIGHTS_RENEWAL_NOT_ALLOWED = 2002; // 0x7d2
+  @Deprecated public class DrmErrorEvent extends android.drm.DrmEvent {
+    ctor @Deprecated public DrmErrorEvent(int, int, String);
+    ctor @Deprecated public DrmErrorEvent(int, int, String, java.util.HashMap<java.lang.String,java.lang.Object>);
+    field @Deprecated public static final int TYPE_ACQUIRE_DRM_INFO_FAILED = 2008; // 0x7d8
+    field @Deprecated public static final int TYPE_NOT_SUPPORTED = 2003; // 0x7d3
+    field @Deprecated public static final int TYPE_NO_INTERNET_CONNECTION = 2005; // 0x7d5
+    field @Deprecated public static final int TYPE_OUT_OF_MEMORY = 2004; // 0x7d4
+    field @Deprecated public static final int TYPE_PROCESS_DRM_INFO_FAILED = 2006; // 0x7d6
+    field @Deprecated public static final int TYPE_REMOVE_ALL_RIGHTS_FAILED = 2007; // 0x7d7
+    field @Deprecated public static final int TYPE_RIGHTS_NOT_INSTALLED = 2001; // 0x7d1
+    field @Deprecated public static final int TYPE_RIGHTS_RENEWAL_NOT_ALLOWED = 2002; // 0x7d2
   }
 
-  public class DrmEvent {
-    ctor protected DrmEvent(int, int, String, java.util.HashMap<java.lang.String,java.lang.Object>);
-    ctor protected DrmEvent(int, int, String);
-    method public Object getAttribute(String);
-    method public String getMessage();
-    method public int getType();
-    method public int getUniqueId();
-    field public static final String DRM_INFO_OBJECT = "drm_info_object";
-    field public static final String DRM_INFO_STATUS_OBJECT = "drm_info_status_object";
-    field public static final int TYPE_ALL_RIGHTS_REMOVED = 1001; // 0x3e9
-    field public static final int TYPE_DRM_INFO_PROCESSED = 1002; // 0x3ea
+  @Deprecated public class DrmEvent {
+    ctor @Deprecated protected DrmEvent(int, int, String, java.util.HashMap<java.lang.String,java.lang.Object>);
+    ctor @Deprecated protected DrmEvent(int, int, String);
+    method @Deprecated public Object getAttribute(String);
+    method @Deprecated public String getMessage();
+    method @Deprecated public int getType();
+    method @Deprecated public int getUniqueId();
+    field @Deprecated public static final String DRM_INFO_OBJECT = "drm_info_object";
+    field @Deprecated public static final String DRM_INFO_STATUS_OBJECT = "drm_info_status_object";
+    field @Deprecated public static final int TYPE_ALL_RIGHTS_REMOVED = 1001; // 0x3e9
+    field @Deprecated public static final int TYPE_DRM_INFO_PROCESSED = 1002; // 0x3ea
   }
 
-  public class DrmInfo {
-    ctor public DrmInfo(int, byte[], String);
-    ctor public DrmInfo(int, String, String);
-    method public Object get(String);
-    method public byte[] getData();
-    method public int getInfoType();
-    method public String getMimeType();
-    method public java.util.Iterator<java.lang.Object> iterator();
-    method public java.util.Iterator<java.lang.String> keyIterator();
-    method public void put(String, Object);
+  @Deprecated public class DrmInfo {
+    ctor @Deprecated public DrmInfo(int, byte[], String);
+    ctor @Deprecated public DrmInfo(int, String, String);
+    method @Deprecated public Object get(String);
+    method @Deprecated public byte[] getData();
+    method @Deprecated public int getInfoType();
+    method @Deprecated public String getMimeType();
+    method @Deprecated public java.util.Iterator<java.lang.Object> iterator();
+    method @Deprecated public java.util.Iterator<java.lang.String> keyIterator();
+    method @Deprecated public void put(String, Object);
   }
 
-  public class DrmInfoEvent extends android.drm.DrmEvent {
-    ctor public DrmInfoEvent(int, int, String);
-    ctor public DrmInfoEvent(int, int, String, java.util.HashMap<java.lang.String,java.lang.Object>);
-    field public static final int TYPE_ACCOUNT_ALREADY_REGISTERED = 5; // 0x5
-    field public static final int TYPE_ALREADY_REGISTERED_BY_ANOTHER_ACCOUNT = 1; // 0x1
-    field public static final int TYPE_REMOVE_RIGHTS = 2; // 0x2
-    field public static final int TYPE_RIGHTS_INSTALLED = 3; // 0x3
-    field public static final int TYPE_RIGHTS_REMOVED = 6; // 0x6
-    field public static final int TYPE_WAIT_FOR_RIGHTS = 4; // 0x4
+  @Deprecated public class DrmInfoEvent extends android.drm.DrmEvent {
+    ctor @Deprecated public DrmInfoEvent(int, int, String);
+    ctor @Deprecated public DrmInfoEvent(int, int, String, java.util.HashMap<java.lang.String,java.lang.Object>);
+    field @Deprecated public static final int TYPE_ACCOUNT_ALREADY_REGISTERED = 5; // 0x5
+    field @Deprecated public static final int TYPE_ALREADY_REGISTERED_BY_ANOTHER_ACCOUNT = 1; // 0x1
+    field @Deprecated public static final int TYPE_REMOVE_RIGHTS = 2; // 0x2
+    field @Deprecated public static final int TYPE_RIGHTS_INSTALLED = 3; // 0x3
+    field @Deprecated public static final int TYPE_RIGHTS_REMOVED = 6; // 0x6
+    field @Deprecated public static final int TYPE_WAIT_FOR_RIGHTS = 4; // 0x4
   }
 
-  public class DrmInfoRequest {
-    ctor public DrmInfoRequest(int, String);
-    method public Object get(String);
-    method public int getInfoType();
-    method public String getMimeType();
-    method public java.util.Iterator<java.lang.Object> iterator();
-    method public java.util.Iterator<java.lang.String> keyIterator();
-    method public void put(String, Object);
-    field public static final String ACCOUNT_ID = "account_id";
-    field public static final String SUBSCRIPTION_ID = "subscription_id";
-    field public static final int TYPE_REGISTRATION_INFO = 1; // 0x1
-    field public static final int TYPE_RIGHTS_ACQUISITION_INFO = 3; // 0x3
-    field public static final int TYPE_RIGHTS_ACQUISITION_PROGRESS_INFO = 4; // 0x4
-    field public static final int TYPE_UNREGISTRATION_INFO = 2; // 0x2
+  @Deprecated public class DrmInfoRequest {
+    ctor @Deprecated public DrmInfoRequest(int, String);
+    method @Deprecated public Object get(String);
+    method @Deprecated public int getInfoType();
+    method @Deprecated public String getMimeType();
+    method @Deprecated public java.util.Iterator<java.lang.Object> iterator();
+    method @Deprecated public java.util.Iterator<java.lang.String> keyIterator();
+    method @Deprecated public void put(String, Object);
+    field @Deprecated public static final String ACCOUNT_ID = "account_id";
+    field @Deprecated public static final String SUBSCRIPTION_ID = "subscription_id";
+    field @Deprecated public static final int TYPE_REGISTRATION_INFO = 1; // 0x1
+    field @Deprecated public static final int TYPE_RIGHTS_ACQUISITION_INFO = 3; // 0x3
+    field @Deprecated public static final int TYPE_RIGHTS_ACQUISITION_PROGRESS_INFO = 4; // 0x4
+    field @Deprecated public static final int TYPE_UNREGISTRATION_INFO = 2; // 0x2
   }
 
-  public class DrmInfoStatus {
-    ctor public DrmInfoStatus(int, int, android.drm.ProcessedData, String);
-    field public static final int STATUS_ERROR = 2; // 0x2
-    field public static final int STATUS_OK = 1; // 0x1
-    field public final android.drm.ProcessedData data;
-    field public final int infoType;
-    field public final String mimeType;
-    field public final int statusCode;
+  @Deprecated public class DrmInfoStatus {
+    ctor @Deprecated public DrmInfoStatus(int, int, android.drm.ProcessedData, String);
+    field @Deprecated public static final int STATUS_ERROR = 2; // 0x2
+    field @Deprecated public static final int STATUS_OK = 1; // 0x1
+    field @Deprecated public final android.drm.ProcessedData data;
+    field @Deprecated public final int infoType;
+    field @Deprecated public final String mimeType;
+    field @Deprecated public final int statusCode;
   }
 
-  public class DrmManagerClient implements java.lang.AutoCloseable {
-    ctor public DrmManagerClient(android.content.Context);
-    method public android.drm.DrmInfo acquireDrmInfo(android.drm.DrmInfoRequest);
-    method public int acquireRights(android.drm.DrmInfoRequest);
-    method public boolean canHandle(String, String);
-    method public boolean canHandle(android.net.Uri, String);
-    method public int checkRightsStatus(String);
-    method public int checkRightsStatus(android.net.Uri);
-    method public int checkRightsStatus(String, int);
-    method public int checkRightsStatus(android.net.Uri, int);
-    method public void close();
-    method public android.drm.DrmConvertedStatus closeConvertSession(int);
-    method public android.drm.DrmConvertedStatus convertData(int, byte[]);
-    method public String[] getAvailableDrmEngines();
-    method @NonNull public java.util.Collection<android.drm.DrmSupportInfo> getAvailableDrmSupportInfo();
-    method public android.content.ContentValues getConstraints(String, int);
-    method public android.content.ContentValues getConstraints(android.net.Uri, int);
-    method public int getDrmObjectType(String, String);
-    method public int getDrmObjectType(android.net.Uri, String);
-    method public android.content.ContentValues getMetadata(String);
-    method public android.content.ContentValues getMetadata(android.net.Uri);
-    method public String getOriginalMimeType(String);
-    method public String getOriginalMimeType(android.net.Uri);
-    method public int openConvertSession(String);
-    method public int processDrmInfo(android.drm.DrmInfo);
+  @Deprecated public class DrmManagerClient implements java.lang.AutoCloseable {
+    ctor @Deprecated public DrmManagerClient(android.content.Context);
+    method @Deprecated public android.drm.DrmInfo acquireDrmInfo(android.drm.DrmInfoRequest);
+    method @Deprecated public int acquireRights(android.drm.DrmInfoRequest);
+    method @Deprecated public boolean canHandle(String, String);
+    method @Deprecated public boolean canHandle(android.net.Uri, String);
+    method @Deprecated public int checkRightsStatus(String);
+    method @Deprecated public int checkRightsStatus(android.net.Uri);
+    method @Deprecated public int checkRightsStatus(String, int);
+    method @Deprecated public int checkRightsStatus(android.net.Uri, int);
+    method @Deprecated public void close();
+    method @Deprecated public android.drm.DrmConvertedStatus closeConvertSession(int);
+    method @Deprecated public android.drm.DrmConvertedStatus convertData(int, byte[]);
+    method @Deprecated public String[] getAvailableDrmEngines();
+    method @Deprecated @NonNull public java.util.Collection<android.drm.DrmSupportInfo> getAvailableDrmSupportInfo();
+    method @Deprecated public android.content.ContentValues getConstraints(String, int);
+    method @Deprecated public android.content.ContentValues getConstraints(android.net.Uri, int);
+    method @Deprecated public int getDrmObjectType(String, String);
+    method @Deprecated public int getDrmObjectType(android.net.Uri, String);
+    method @Deprecated public android.content.ContentValues getMetadata(String);
+    method @Deprecated public android.content.ContentValues getMetadata(android.net.Uri);
+    method @Deprecated public String getOriginalMimeType(String);
+    method @Deprecated public String getOriginalMimeType(android.net.Uri);
+    method @Deprecated public int openConvertSession(String);
+    method @Deprecated public int processDrmInfo(android.drm.DrmInfo);
     method @Deprecated public void release();
-    method public int removeAllRights();
-    method public int removeRights(String);
-    method public int removeRights(android.net.Uri);
-    method public int saveRights(android.drm.DrmRights, String, String) throws java.io.IOException;
-    method public void setOnErrorListener(android.drm.DrmManagerClient.OnErrorListener);
-    method public void setOnEventListener(android.drm.DrmManagerClient.OnEventListener);
-    method public void setOnInfoListener(android.drm.DrmManagerClient.OnInfoListener);
-    field public static final int ERROR_NONE = 0; // 0x0
-    field public static final int ERROR_UNKNOWN = -2000; // 0xfffff830
+    method @Deprecated public int removeAllRights();
+    method @Deprecated public int removeRights(String);
+    method @Deprecated public int removeRights(android.net.Uri);
+    method @Deprecated public int saveRights(android.drm.DrmRights, String, String) throws java.io.IOException;
+    method @Deprecated public void setOnErrorListener(android.drm.DrmManagerClient.OnErrorListener);
+    method @Deprecated public void setOnEventListener(android.drm.DrmManagerClient.OnEventListener);
+    method @Deprecated public void setOnInfoListener(android.drm.DrmManagerClient.OnInfoListener);
+    field @Deprecated public static final int ERROR_NONE = 0; // 0x0
+    field @Deprecated public static final int ERROR_UNKNOWN = -2000; // 0xfffff830
   }
 
-  public static interface DrmManagerClient.OnErrorListener {
-    method public void onError(android.drm.DrmManagerClient, android.drm.DrmErrorEvent);
+  @Deprecated public static interface DrmManagerClient.OnErrorListener {
+    method @Deprecated public void onError(android.drm.DrmManagerClient, android.drm.DrmErrorEvent);
   }
 
-  public static interface DrmManagerClient.OnEventListener {
-    method public void onEvent(android.drm.DrmManagerClient, android.drm.DrmEvent);
+  @Deprecated public static interface DrmManagerClient.OnEventListener {
+    method @Deprecated public void onEvent(android.drm.DrmManagerClient, android.drm.DrmEvent);
   }
 
-  public static interface DrmManagerClient.OnInfoListener {
-    method public void onInfo(android.drm.DrmManagerClient, android.drm.DrmInfoEvent);
+  @Deprecated public static interface DrmManagerClient.OnInfoListener {
+    method @Deprecated public void onInfo(android.drm.DrmManagerClient, android.drm.DrmInfoEvent);
   }
 
-  public class DrmRights {
-    ctor public DrmRights(String, String);
-    ctor public DrmRights(String, String, String);
-    ctor public DrmRights(String, String, String, String);
-    ctor public DrmRights(java.io.File, String);
-    ctor public DrmRights(android.drm.ProcessedData, String);
-    method public String getAccountId();
-    method public byte[] getData();
-    method public String getMimeType();
-    method public String getSubscriptionId();
+  @Deprecated public class DrmRights {
+    ctor @Deprecated public DrmRights(String, String);
+    ctor @Deprecated public DrmRights(String, String, String);
+    ctor @Deprecated public DrmRights(String, String, String, String);
+    ctor @Deprecated public DrmRights(java.io.File, String);
+    ctor @Deprecated public DrmRights(android.drm.ProcessedData, String);
+    method @Deprecated public String getAccountId();
+    method @Deprecated public byte[] getData();
+    method @Deprecated public String getMimeType();
+    method @Deprecated public String getSubscriptionId();
   }
 
-  public class DrmStore {
+  @Deprecated public class DrmStore {
     ctor @Deprecated public DrmStore();
   }
 
-  public static class DrmStore.Action {
+  @Deprecated public static class DrmStore.Action {
     ctor @Deprecated public DrmStore.Action();
-    field public static final int DEFAULT = 0; // 0x0
-    field public static final int DISPLAY = 7; // 0x7
-    field public static final int EXECUTE = 6; // 0x6
-    field public static final int OUTPUT = 4; // 0x4
-    field public static final int PLAY = 1; // 0x1
-    field public static final int PREVIEW = 5; // 0x5
-    field public static final int RINGTONE = 2; // 0x2
-    field public static final int TRANSFER = 3; // 0x3
+    field @Deprecated public static final int DEFAULT = 0; // 0x0
+    field @Deprecated public static final int DISPLAY = 7; // 0x7
+    field @Deprecated public static final int EXECUTE = 6; // 0x6
+    field @Deprecated public static final int OUTPUT = 4; // 0x4
+    field @Deprecated public static final int PLAY = 1; // 0x1
+    field @Deprecated public static final int PREVIEW = 5; // 0x5
+    field @Deprecated public static final int RINGTONE = 2; // 0x2
+    field @Deprecated public static final int TRANSFER = 3; // 0x3
   }
 
-  public static interface DrmStore.ConstraintsColumns {
-    field public static final String EXTENDED_METADATA = "extended_metadata";
-    field public static final String LICENSE_AVAILABLE_TIME = "license_available_time";
-    field public static final String LICENSE_EXPIRY_TIME = "license_expiry_time";
-    field public static final String LICENSE_START_TIME = "license_start_time";
-    field public static final String MAX_REPEAT_COUNT = "max_repeat_count";
-    field public static final String REMAINING_REPEAT_COUNT = "remaining_repeat_count";
+  @Deprecated public static interface DrmStore.ConstraintsColumns {
+    field @Deprecated public static final String EXTENDED_METADATA = "extended_metadata";
+    field @Deprecated public static final String LICENSE_AVAILABLE_TIME = "license_available_time";
+    field @Deprecated public static final String LICENSE_EXPIRY_TIME = "license_expiry_time";
+    field @Deprecated public static final String LICENSE_START_TIME = "license_start_time";
+    field @Deprecated public static final String MAX_REPEAT_COUNT = "max_repeat_count";
+    field @Deprecated public static final String REMAINING_REPEAT_COUNT = "remaining_repeat_count";
   }
 
-  public static class DrmStore.DrmObjectType {
+  @Deprecated public static class DrmStore.DrmObjectType {
     ctor @Deprecated public DrmStore.DrmObjectType();
-    field public static final int CONTENT = 1; // 0x1
-    field public static final int RIGHTS_OBJECT = 2; // 0x2
-    field public static final int TRIGGER_OBJECT = 3; // 0x3
-    field public static final int UNKNOWN = 0; // 0x0
+    field @Deprecated public static final int CONTENT = 1; // 0x1
+    field @Deprecated public static final int RIGHTS_OBJECT = 2; // 0x2
+    field @Deprecated public static final int TRIGGER_OBJECT = 3; // 0x3
+    field @Deprecated public static final int UNKNOWN = 0; // 0x0
   }
 
-  public static class DrmStore.Playback {
+  @Deprecated public static class DrmStore.Playback {
     ctor @Deprecated public DrmStore.Playback();
-    field public static final int PAUSE = 2; // 0x2
-    field public static final int RESUME = 3; // 0x3
-    field public static final int START = 0; // 0x0
-    field public static final int STOP = 1; // 0x1
+    field @Deprecated public static final int PAUSE = 2; // 0x2
+    field @Deprecated public static final int RESUME = 3; // 0x3
+    field @Deprecated public static final int START = 0; // 0x0
+    field @Deprecated public static final int STOP = 1; // 0x1
   }
 
-  public static class DrmStore.RightsStatus {
+  @Deprecated public static class DrmStore.RightsStatus {
     ctor @Deprecated public DrmStore.RightsStatus();
-    field public static final int RIGHTS_EXPIRED = 2; // 0x2
-    field public static final int RIGHTS_INVALID = 1; // 0x1
-    field public static final int RIGHTS_NOT_ACQUIRED = 3; // 0x3
-    field public static final int RIGHTS_VALID = 0; // 0x0
+    field @Deprecated public static final int RIGHTS_EXPIRED = 2; // 0x2
+    field @Deprecated public static final int RIGHTS_INVALID = 1; // 0x1
+    field @Deprecated public static final int RIGHTS_NOT_ACQUIRED = 3; // 0x3
+    field @Deprecated public static final int RIGHTS_VALID = 0; // 0x0
   }
 
-  public class DrmSupportInfo {
-    ctor public DrmSupportInfo();
-    method public void addFileSuffix(String);
-    method public void addMimeType(String);
+  @Deprecated public class DrmSupportInfo {
+    ctor @Deprecated public DrmSupportInfo();
+    method @Deprecated public void addFileSuffix(String);
+    method @Deprecated public void addMimeType(String);
     method @Deprecated public String getDescriprition();
-    method public String getDescription();
-    method public java.util.Iterator<java.lang.String> getFileSuffixIterator();
-    method public java.util.Iterator<java.lang.String> getMimeTypeIterator();
-    method public void setDescription(String);
+    method @Deprecated public String getDescription();
+    method @Deprecated public java.util.Iterator<java.lang.String> getFileSuffixIterator();
+    method @Deprecated public java.util.Iterator<java.lang.String> getMimeTypeIterator();
+    method @Deprecated public void setDescription(String);
   }
 
-  public class DrmUtils {
-    ctor public DrmUtils();
-    method public static android.drm.DrmUtils.ExtendedMetadataParser getExtendedMetadataParser(byte[]);
+  @Deprecated public class DrmUtils {
+    ctor @Deprecated public DrmUtils();
+    method @Deprecated public static android.drm.DrmUtils.ExtendedMetadataParser getExtendedMetadataParser(byte[]);
   }
 
-  public static class DrmUtils.ExtendedMetadataParser {
-    method public String get(String);
-    method public java.util.Iterator<java.lang.String> iterator();
-    method public java.util.Iterator<java.lang.String> keyIterator();
+  @Deprecated public static class DrmUtils.ExtendedMetadataParser {
+    method @Deprecated public String get(String);
+    method @Deprecated public java.util.Iterator<java.lang.String> iterator();
+    method @Deprecated public java.util.Iterator<java.lang.String> keyIterator();
   }
 
-  public class ProcessedData {
-    method public String getAccountId();
-    method public byte[] getData();
-    method public String getSubscriptionId();
+  @Deprecated public class ProcessedData {
+    method @Deprecated public String getAccountId();
+    method @Deprecated public byte[] getData();
+    method @Deprecated public String getSubscriptionId();
   }
 
 }
@@ -16817,6 +16970,8 @@
     method @Nullable public CharSequence getSubtitle();
     method @NonNull public CharSequence getTitle();
     method public boolean isConfirmationRequired();
+    field public static final int AUTHENTICATION_RESULT_TYPE_BIOMETRIC = 2; // 0x2
+    field public static final int AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL = 1; // 0x1
     field public static final int BIOMETRIC_ACQUIRED_GOOD = 0; // 0x0
     field public static final int BIOMETRIC_ACQUIRED_IMAGER_DIRTY = 3; // 0x3
     field public static final int BIOMETRIC_ACQUIRED_INSUFFICIENT = 2; // 0x2
@@ -16846,6 +17001,7 @@
   }
 
   public static class BiometricPrompt.AuthenticationResult {
+    method public int getAuthenticationType();
     method public android.hardware.biometrics.BiometricPrompt.CryptoObject getCryptoObject();
   }
 
@@ -16865,7 +17021,9 @@
     ctor public BiometricPrompt.CryptoObject(@NonNull java.security.Signature);
     ctor public BiometricPrompt.CryptoObject(@NonNull javax.crypto.Cipher);
     ctor public BiometricPrompt.CryptoObject(@NonNull javax.crypto.Mac);
+    ctor public BiometricPrompt.CryptoObject(@NonNull android.security.identity.IdentityCredential);
     method public javax.crypto.Cipher getCipher();
+    method @Nullable public android.security.identity.IdentityCredential getIdentityCredential();
     method public javax.crypto.Mac getMac();
     method public java.security.Signature getSignature();
   }
@@ -16905,6 +17063,8 @@
     method public abstract int setRepeatingRequest(@NonNull android.hardware.camera2.CaptureRequest, @Nullable android.hardware.camera2.CameraCaptureSession.CaptureCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     method public int setSingleRepeatingRequest(@NonNull android.hardware.camera2.CaptureRequest, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraCaptureSession.CaptureCallback) throws android.hardware.camera2.CameraAccessException;
     method public abstract void stopRepeating() throws android.hardware.camera2.CameraAccessException;
+    method public boolean supportsOfflineProcessing(@NonNull android.view.Surface);
+    method @Nullable public android.hardware.camera2.CameraOfflineSession switchToOffline(@NonNull java.util.Collection<android.view.Surface>, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraOfflineSession.CameraOfflineSessionCallback) throws android.hardware.camera2.CameraAccessException;
     method public void updateOutputConfiguration(android.hardware.camera2.params.OutputConfiguration) throws android.hardware.camera2.CameraAccessException;
   }
 
@@ -17045,13 +17205,13 @@
     method public abstract void close();
     method @NonNull public abstract android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int) throws android.hardware.camera2.CameraAccessException;
     method @NonNull public android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int, java.util.Set<java.lang.String>) throws android.hardware.camera2.CameraAccessException;
-    method public abstract void createCaptureSession(@NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+    method @Deprecated public abstract void createCaptureSession(@NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     method public void createCaptureSession(android.hardware.camera2.params.SessionConfiguration) throws android.hardware.camera2.CameraAccessException;
-    method public abstract void createCaptureSessionByOutputConfigurations(java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
-    method public abstract void createConstrainedHighSpeedCaptureSession(@NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+    method @Deprecated public abstract void createCaptureSessionByOutputConfigurations(java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+    method @Deprecated public abstract void createConstrainedHighSpeedCaptureSession(@NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     method @NonNull public abstract android.hardware.camera2.CaptureRequest.Builder createReprocessCaptureRequest(@NonNull android.hardware.camera2.TotalCaptureResult) throws android.hardware.camera2.CameraAccessException;
-    method public abstract void createReprocessableCaptureSession(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
-    method public abstract void createReprocessableCaptureSessionByConfigurations(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+    method @Deprecated public abstract void createReprocessableCaptureSession(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+    method @Deprecated public abstract void createReprocessableCaptureSessionByConfigurations(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     method public int getCameraAudioRestriction() throws android.hardware.camera2.CameraAccessException;
     method @NonNull public abstract String getId();
     method public boolean isSessionConfigurationSupported(@NonNull android.hardware.camera2.params.SessionConfiguration) throws android.hardware.camera2.CameraAccessException;
@@ -17243,6 +17403,7 @@
     field public static final int LENS_OPTICAL_STABILIZATION_MODE_ON = 1; // 0x1
     field public static final int LENS_POSE_REFERENCE_GYROSCOPE = 1; // 0x1
     field public static final int LENS_POSE_REFERENCE_PRIMARY_CAMERA = 0; // 0x0
+    field public static final int LENS_POSE_REFERENCE_UNDEFINED = 2; // 0x2
     field public static final int LENS_STATE_MOVING = 1; // 0x1
     field public static final int LENS_STATE_STATIONARY = 0; // 0x0
     field public static final int LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_APPROXIMATE = 0; // 0x0
@@ -17328,6 +17489,20 @@
     field public static final int TONEMAP_PRESET_CURVE_SRGB = 0; // 0x0
   }
 
+  public abstract class CameraOfflineSession extends android.hardware.camera2.CameraCaptureSession {
+    ctor public CameraOfflineSession();
+  }
+
+  public abstract static class CameraOfflineSession.CameraOfflineSessionCallback {
+    ctor public CameraOfflineSession.CameraOfflineSessionCallback();
+    method public abstract void onClosed(@NonNull android.hardware.camera2.CameraOfflineSession);
+    method public abstract void onError(@NonNull android.hardware.camera2.CameraOfflineSession, int);
+    method public abstract void onIdle(@NonNull android.hardware.camera2.CameraOfflineSession);
+    method public abstract void onReady(@NonNull android.hardware.camera2.CameraOfflineSession);
+    method public abstract void onSwitchFailed(@NonNull android.hardware.camera2.CameraOfflineSession);
+    field public static final int STATUS_INTERNAL_ERROR = 0; // 0x0
+  }
+
   public class CaptureFailure {
     method public long getFrameNumber();
     method @Nullable public String getPhysicalCameraId();
@@ -17825,7 +18000,9 @@
     ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull java.security.Signature);
     ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull javax.crypto.Cipher);
     ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull javax.crypto.Mac);
+    ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull android.security.identity.IdentityCredential);
     method @Deprecated public javax.crypto.Cipher getCipher();
+    method @Deprecated @Nullable public android.security.identity.IdentityCredential getIdentityCredential();
     method @Deprecated public javax.crypto.Mac getMac();
     method @Deprecated public java.security.Signature getSignature();
   }
@@ -18214,6 +18391,7 @@
     field public static final int RIGHT = 7; // 0x7
     field public static final int TOP = 8; // 0x8
     field public static final int TOP_AND_BOTTOM = 9; // 0x9
+    field public static final int TOP_AND_BOTTOM_AND_LEFT = 15; // 0xf
     field public static final int TOP_AND_BOTTOM_AND_RIGHT = 10; // 0xa
     field public static final int TOP_AND_LEFT = 11; // 0xb
     field public static final int TOP_AND_LEFT_AND_RIGHT = 12; // 0xc
@@ -18536,6 +18714,8 @@
     field public static final int CHEROKEE_SUPPLEMENT_ID = 255; // 0xff
     field public static final android.icu.lang.UCharacter.UnicodeBlock CHESS_SYMBOLS;
     field public static final int CHESS_SYMBOLS_ID = 281; // 0x119
+    field public static final android.icu.lang.UCharacter.UnicodeBlock CHORASMIAN;
+    field public static final int CHORASMIAN_ID = 301; // 0x12d
     field public static final android.icu.lang.UCharacter.UnicodeBlock CJK_COMPATIBILITY;
     field public static final android.icu.lang.UCharacter.UnicodeBlock CJK_COMPATIBILITY_FORMS;
     field public static final int CJK_COMPATIBILITY_FORMS_ID = 83; // 0x53
@@ -18563,6 +18743,8 @@
     field public static final int CJK_UNIFIED_IDEOGRAPHS_EXTENSION_E_ID = 256; // 0x100
     field public static final android.icu.lang.UCharacter.UnicodeBlock CJK_UNIFIED_IDEOGRAPHS_EXTENSION_F;
     field public static final int CJK_UNIFIED_IDEOGRAPHS_EXTENSION_F_ID = 274; // 0x112
+    field public static final android.icu.lang.UCharacter.UnicodeBlock CJK_UNIFIED_IDEOGRAPHS_EXTENSION_G;
+    field public static final int CJK_UNIFIED_IDEOGRAPHS_EXTENSION_G_ID = 302; // 0x12e
     field public static final int CJK_UNIFIED_IDEOGRAPHS_ID = 71; // 0x47
     field public static final android.icu.lang.UCharacter.UnicodeBlock COMBINING_DIACRITICAL_MARKS;
     field public static final android.icu.lang.UCharacter.UnicodeBlock COMBINING_DIACRITICAL_MARKS_EXTENDED;
@@ -18612,6 +18794,8 @@
     field public static final int DEVANAGARI_ID = 15; // 0xf
     field public static final android.icu.lang.UCharacter.UnicodeBlock DINGBATS;
     field public static final int DINGBATS_ID = 56; // 0x38
+    field public static final android.icu.lang.UCharacter.UnicodeBlock DIVES_AKURU;
+    field public static final int DIVES_AKURU_ID = 303; // 0x12f
     field public static final android.icu.lang.UCharacter.UnicodeBlock DOGRA;
     field public static final int DOGRA_ID = 282; // 0x11a
     field public static final android.icu.lang.UCharacter.UnicodeBlock DOMINO_TILES;
@@ -18740,6 +18924,8 @@
     field public static final int KAYAH_LI_ID = 162; // 0xa2
     field public static final android.icu.lang.UCharacter.UnicodeBlock KHAROSHTHI;
     field public static final int KHAROSHTHI_ID = 137; // 0x89
+    field public static final android.icu.lang.UCharacter.UnicodeBlock KHITAN_SMALL_SCRIPT;
+    field public static final int KHITAN_SMALL_SCRIPT_ID = 304; // 0x130
     field public static final android.icu.lang.UCharacter.UnicodeBlock KHMER;
     field public static final int KHMER_ID = 36; // 0x24
     field public static final android.icu.lang.UCharacter.UnicodeBlock KHMER_SYMBOLS;
@@ -18778,6 +18964,8 @@
     field public static final int LINEAR_B_SYLLABARY_ID = 117; // 0x75
     field public static final android.icu.lang.UCharacter.UnicodeBlock LISU;
     field public static final int LISU_ID = 176; // 0xb0
+    field public static final android.icu.lang.UCharacter.UnicodeBlock LISU_SUPPLEMENT;
+    field public static final int LISU_SUPPLEMENT_ID = 305; // 0x131
     field public static final android.icu.lang.UCharacter.UnicodeBlock LOW_SURROGATES;
     field public static final int LOW_SURROGATES_ID = 77; // 0x4d
     field public static final android.icu.lang.UCharacter.UnicodeBlock LYCIAN;
@@ -18989,6 +19177,8 @@
     field public static final int SYLOTI_NAGRI_ID = 143; // 0x8f
     field public static final android.icu.lang.UCharacter.UnicodeBlock SYMBOLS_AND_PICTOGRAPHS_EXTENDED_A;
     field public static final int SYMBOLS_AND_PICTOGRAPHS_EXTENDED_A_ID = 298; // 0x12a
+    field public static final android.icu.lang.UCharacter.UnicodeBlock SYMBOLS_FOR_LEGACY_COMPUTING;
+    field public static final int SYMBOLS_FOR_LEGACY_COMPUTING_ID = 306; // 0x132
     field public static final android.icu.lang.UCharacter.UnicodeBlock SYRIAC;
     field public static final int SYRIAC_ID = 13; // 0xd
     field public static final android.icu.lang.UCharacter.UnicodeBlock SYRIAC_SUPPLEMENT;
@@ -19017,6 +19207,8 @@
     field public static final android.icu.lang.UCharacter.UnicodeBlock TANGUT_COMPONENTS;
     field public static final int TANGUT_COMPONENTS_ID = 273; // 0x111
     field public static final int TANGUT_ID = 272; // 0x110
+    field public static final android.icu.lang.UCharacter.UnicodeBlock TANGUT_SUPPLEMENT;
+    field public static final int TANGUT_SUPPLEMENT_ID = 307; // 0x133
     field public static final android.icu.lang.UCharacter.UnicodeBlock TELUGU;
     field public static final int TELUGU_ID = 21; // 0x15
     field public static final android.icu.lang.UCharacter.UnicodeBlock THAANA;
@@ -19051,6 +19243,8 @@
     field public static final int WANCHO_ID = 300; // 0x12c
     field public static final android.icu.lang.UCharacter.UnicodeBlock WARANG_CITI;
     field public static final int WARANG_CITI_ID = 252; // 0xfc
+    field public static final android.icu.lang.UCharacter.UnicodeBlock YEZIDI;
+    field public static final int YEZIDI_ID = 308; // 0x134
     field public static final android.icu.lang.UCharacter.UnicodeBlock YIJING_HEXAGRAM_SYMBOLS;
     field public static final int YIJING_HEXAGRAM_SYMBOLS_ID = 116; // 0x74
     field public static final android.icu.lang.UCharacter.UnicodeBlock YI_RADICALS;
@@ -19347,6 +19541,7 @@
     field public static final int CHAKMA = 118; // 0x76
     field public static final int CHAM = 66; // 0x42
     field public static final int CHEROKEE = 6; // 0x6
+    field public static final int CHORASMIAN = 189; // 0xbd
     field public static final int CIRTH = 67; // 0x43
     field public static final int COMMON = 0; // 0x0
     field public static final int COPTIC = 7; // 0x7
@@ -19356,6 +19551,7 @@
     field public static final int DEMOTIC_EGYPTIAN = 69; // 0x45
     field public static final int DESERET = 9; // 0x9
     field public static final int DEVANAGARI = 10; // 0xa
+    field public static final int DIVES_AKURU = 190; // 0xbe
     field public static final int DOGRA = 178; // 0xb2
     field public static final int DUPLOYAN = 135; // 0x87
     field public static final int EASTERN_SYRIAC = 97; // 0x61
@@ -19397,6 +19593,7 @@
     field public static final int KATAKANA_OR_HIRAGANA = 54; // 0x36
     field public static final int KAYAH_LI = 79; // 0x4f
     field public static final int KHAROSHTHI = 57; // 0x39
+    field public static final int KHITAN_SMALL_SCRIPT = 191; // 0xbf
     field public static final int KHMER = 23; // 0x17
     field public static final int KHOJKI = 157; // 0x9d
     field public static final int KHUDAWADI = 145; // 0x91
@@ -19514,6 +19711,7 @@
     field public static final int WARANG_CITI = 146; // 0x92
     field public static final int WESTERN_SYRIAC = 96; // 0x60
     field public static final int WOLEAI = 155; // 0x9b
+    field public static final int YEZIDI = 192; // 0xc0
     field public static final int YI = 41; // 0x29
     field public static final int ZANABAZAR_SQUARE = 177; // 0xb1
   }
@@ -22769,6 +22967,7 @@
     field public static final android.icu.util.VersionInfo UNICODE_11_0;
     field public static final android.icu.util.VersionInfo UNICODE_12_0;
     field public static final android.icu.util.VersionInfo UNICODE_12_1;
+    field public static final android.icu.util.VersionInfo UNICODE_13_0;
     field public static final android.icu.util.VersionInfo UNICODE_1_0;
     field public static final android.icu.util.VersionInfo UNICODE_1_0_1;
     field public static final android.icu.util.VersionInfo UNICODE_1_1_0;
@@ -23212,12 +23411,12 @@
     method public int getAccumulatedDeltaRangeState();
     method public double getAccumulatedDeltaRangeUncertaintyMeters();
     method public double getAutomaticGainControlLevelDb();
-    method @FloatRange(from=0, to=50) public double getBasebandCn0DbHz();
+    method @FloatRange(from=0, to=63) public double getBasebandCn0DbHz();
     method @Deprecated public long getCarrierCycles();
     method public float getCarrierFrequencyHz();
     method @Deprecated public double getCarrierPhase();
     method @Deprecated public double getCarrierPhaseUncertainty();
-    method public double getCn0DbHz();
+    method @FloatRange(from=0, to=63) public double getCn0DbHz();
     method @NonNull public String getCodeType();
     method public int getConstellationType();
     method public int getMultipathIndicator();
@@ -23299,6 +23498,8 @@
     field public static final int STATUS_PARITY_PASSED = 1; // 0x1
     field public static final int STATUS_PARITY_REBUILT = 2; // 0x2
     field public static final int STATUS_UNKNOWN = 0; // 0x0
+    field public static final int TYPE_BDS_CNAV1 = 1283; // 0x503
+    field public static final int TYPE_BDS_CNAV2 = 1284; // 0x504
     field public static final int TYPE_BDS_D1 = 1281; // 0x501
     field public static final int TYPE_BDS_D2 = 1282; // 0x502
     field public static final int TYPE_GAL_F = 1538; // 0x602
@@ -23308,6 +23509,9 @@
     field public static final int TYPE_GPS_L1CA = 257; // 0x101
     field public static final int TYPE_GPS_L2CNAV = 258; // 0x102
     field public static final int TYPE_GPS_L5CNAV = 259; // 0x103
+    field public static final int TYPE_IRN_L5CA = 1793; // 0x701
+    field public static final int TYPE_QZS_L1CA = 1025; // 0x401
+    field public static final int TYPE_SBS = 513; // 0x201
     field public static final int TYPE_UNKNOWN = 0; // 0x0
   }
 
@@ -23634,6 +23838,8 @@
     method @NonNull public int[] getChannelCounts();
     method @NonNull public int[] getChannelIndexMasks();
     method @NonNull public int[] getChannelMasks();
+    method @NonNull public int[] getEncapsulationMetadataTypes();
+    method @NonNull public int[] getEncapsulationModes();
     method @NonNull public int[] getEncodings();
     method public int getId();
     method public CharSequence getProductName();
@@ -23647,6 +23853,7 @@
     field public static final int TYPE_BUILTIN_EARPIECE = 1; // 0x1
     field public static final int TYPE_BUILTIN_MIC = 15; // 0xf
     field public static final int TYPE_BUILTIN_SPEAKER = 2; // 0x2
+    field public static final int TYPE_BUILTIN_SPEAKER_SAFE = 24; // 0x18
     field public static final int TYPE_BUS = 21; // 0x15
     field public static final int TYPE_DOCK = 13; // 0xd
     field public static final int TYPE_FM = 14; // 0xe
@@ -23956,11 +24163,44 @@
     method public void onAudioFocusChange(int);
   }
 
+  public final class AudioMetadata {
+    method @NonNull public static android.media.AudioMetadata.Map createMap();
+  }
+
+  public static class AudioMetadata.Format {
+    field @NonNull public static final android.media.AudioMetadata.Key<java.lang.Boolean> KEY_ATMOS_PRESENT;
+    field @NonNull public static final android.media.AudioMetadata.Key<java.lang.Integer> KEY_AUDIO_ENCODING;
+    field @NonNull public static final android.media.AudioMetadata.Key<java.lang.Integer> KEY_BIT_RATE;
+    field @NonNull public static final android.media.AudioMetadata.Key<java.lang.Integer> KEY_BIT_WIDTH;
+    field @NonNull public static final android.media.AudioMetadata.Key<java.lang.Integer> KEY_CHANNEL_MASK;
+    field @NonNull public static final android.media.AudioMetadata.Key<java.lang.String> KEY_MIME;
+    field @NonNull public static final android.media.AudioMetadata.Key<java.lang.Integer> KEY_SAMPLE_RATE;
+  }
+
+  public static interface AudioMetadata.Key<T> {
+    method @NonNull public String getName();
+    method @NonNull public Class<T> getValueClass();
+  }
+
+  public static interface AudioMetadata.Map extends android.media.AudioMetadata.ReadMap {
+    method @Nullable public <T> T remove(@NonNull android.media.AudioMetadata.Key<T>);
+    method @Nullable public <T> T set(@NonNull android.media.AudioMetadata.Key<T>, @NonNull T);
+  }
+
+  public static interface AudioMetadata.ReadMap {
+    method public <T> boolean containsKey(@NonNull android.media.AudioMetadata.Key<T>);
+    method @NonNull public android.media.AudioMetadata.Map dup();
+    method @Nullable public <T> T get(@NonNull android.media.AudioMetadata.Key<T>);
+    method public int size();
+  }
+
   public final class AudioPlaybackCaptureConfiguration {
     method @NonNull public int[] getExcludeUids();
     method @NonNull public int[] getExcludeUsages();
+    method @NonNull public int[] getExcludeUserIds();
     method @NonNull public int[] getMatchingUids();
     method @NonNull public int[] getMatchingUsages();
+    method @NonNull public int[] getMatchingUserIds();
     method @NonNull public android.media.projection.MediaProjection getMediaProjection();
   }
 
@@ -23968,9 +24208,11 @@
     ctor public AudioPlaybackCaptureConfiguration.Builder(@NonNull android.media.projection.MediaProjection);
     method @NonNull public android.media.AudioPlaybackCaptureConfiguration.Builder addMatchingUid(int);
     method @NonNull public android.media.AudioPlaybackCaptureConfiguration.Builder addMatchingUsage(int);
+    method @NonNull public android.media.AudioPlaybackCaptureConfiguration.Builder addMatchingUserId(int);
     method @NonNull public android.media.AudioPlaybackCaptureConfiguration build();
     method @NonNull public android.media.AudioPlaybackCaptureConfiguration.Builder excludeUid(int);
     method @NonNull public android.media.AudioPlaybackCaptureConfiguration.Builder excludeUsage(int);
+    method @NonNull public android.media.AudioPlaybackCaptureConfiguration.Builder excludeUserId(int);
   }
 
   public final class AudioPlaybackConfiguration implements android.os.Parcelable {
@@ -24141,6 +24383,7 @@
     ctor @Deprecated public AudioTrack(int, int, int, int, int, int) throws java.lang.IllegalArgumentException;
     ctor @Deprecated public AudioTrack(int, int, int, int, int, int, int) throws java.lang.IllegalArgumentException;
     ctor public AudioTrack(android.media.AudioAttributes, android.media.AudioFormat, int, int, int) throws java.lang.IllegalArgumentException;
+    method public void addOnCodecFormatChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioTrack.OnCodecFormatChangedListener);
     method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
     method @Deprecated public void addOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener, android.os.Handler);
     method public int attachAuxEffect(int);
@@ -24148,12 +24391,14 @@
     method protected void finalize();
     method public void flush();
     method @NonNull public android.media.AudioAttributes getAudioAttributes();
+    method public float getAudioDescriptionMixLeveldB();
     method public int getAudioFormat();
     method public int getAudioSessionId();
     method @IntRange(from=0) public int getBufferCapacityInFrames();
     method @IntRange(from=0) public int getBufferSizeInFrames();
     method public int getChannelConfiguration();
     method public int getChannelCount();
+    method public int getDualMonoMode();
     method @NonNull public android.media.AudioFormat getFormat();
     method public static float getMaxVolume();
     method public android.os.PersistableBundle getMetrics();
@@ -24184,10 +24429,13 @@
     method public void registerStreamEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioTrack.StreamEventCallback);
     method public void release();
     method public int reloadStaticData();
+    method public void removeOnCodecFormatChangedListener(@NonNull android.media.AudioTrack.OnCodecFormatChangedListener);
     method public void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener);
     method @Deprecated public void removeOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener);
+    method public boolean setAudioDescriptionMixLeveldB(@FloatRange(to=48.0f, toInclusive=true) float);
     method public int setAuxEffectSendLevel(@FloatRange(from=0.0) float);
     method public int setBufferSizeInFrames(@IntRange(from=0) int);
+    method public boolean setDualMonoMode(int);
     method public int setLoopPoints(@IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0xffffffff) int);
     method public int setNotificationMarkerPosition(int);
     method public void setOffloadDelayPadding(@IntRange(from=0) int, @IntRange(from=0) int);
@@ -24212,6 +24460,15 @@
     method public int write(@NonNull float[], int, int, int);
     method public int write(@NonNull java.nio.ByteBuffer, int, int);
     method public int write(@NonNull java.nio.ByteBuffer, int, int, long);
+    field public static final int DUAL_MONO_MODE_LL = 2; // 0x2
+    field public static final int DUAL_MONO_MODE_LR = 1; // 0x1
+    field public static final int DUAL_MONO_MODE_OFF = 0; // 0x0
+    field public static final int DUAL_MONO_MODE_RR = 3; // 0x3
+    field public static final int ENCAPSULATION_METADATA_TYPE_DVB_AD_DESCRIPTOR = 2; // 0x2
+    field public static final int ENCAPSULATION_METADATA_TYPE_FRAMEWORK_TUNER = 1; // 0x1
+    field public static final int ENCAPSULATION_MODE_ELEMENTARY_STREAM = 1; // 0x1
+    field public static final int ENCAPSULATION_MODE_HANDLE = 2; // 0x2
+    field public static final int ENCAPSULATION_MODE_NONE = 0; // 0x0
     field public static final int ERROR = -1; // 0xffffffff
     field public static final int ERROR_BAD_VALUE = -2; // 0xfffffffe
     field public static final int ERROR_DEAD_OBJECT = -6; // 0xfffffffa
@@ -24238,10 +24495,12 @@
     method @NonNull public android.media.AudioTrack.Builder setAudioAttributes(@NonNull android.media.AudioAttributes) throws java.lang.IllegalArgumentException;
     method @NonNull public android.media.AudioTrack.Builder setAudioFormat(@NonNull android.media.AudioFormat) throws java.lang.IllegalArgumentException;
     method @NonNull public android.media.AudioTrack.Builder setBufferSizeInBytes(@IntRange(from=0) int) throws java.lang.IllegalArgumentException;
+    method @NonNull public android.media.AudioTrack.Builder setEncapsulationMode(int);
     method @NonNull public android.media.AudioTrack.Builder setOffloadedPlayback(boolean);
     method @NonNull public android.media.AudioTrack.Builder setPerformanceMode(int);
     method @NonNull public android.media.AudioTrack.Builder setSessionId(@IntRange(from=1) int) throws java.lang.IllegalArgumentException;
     method @NonNull public android.media.AudioTrack.Builder setTransferMode(int) throws java.lang.IllegalArgumentException;
+    method @NonNull public android.media.AudioTrack.Builder setTunerConfiguration(@NonNull android.media.AudioTrack.TunerConfiguration);
   }
 
   public static final class AudioTrack.MetricsConstants {
@@ -24252,6 +24511,10 @@
     field public static final String USAGE = "android.media.audiotrack.usage";
   }
 
+  public static interface AudioTrack.OnCodecFormatChangedListener {
+    method public void onCodecFormatChanged(@NonNull android.media.AudioTrack, @Nullable android.media.AudioMetadata.ReadMap);
+  }
+
   public static interface AudioTrack.OnPlaybackPositionUpdateListener {
     method public void onMarkerReached(android.media.AudioTrack);
     method public void onPeriodicNotification(android.media.AudioTrack);
@@ -24269,6 +24532,18 @@
     method public void onTearDown(@NonNull android.media.AudioTrack);
   }
 
+  public static class AudioTrack.TunerConfiguration {
+    method public int getContentId();
+    method public int getSyncId();
+  }
+
+  public static class AudioTrack.TunerConfiguration.Builder {
+    ctor public AudioTrack.TunerConfiguration.Builder();
+    method @NonNull public android.media.AudioTrack.TunerConfiguration build();
+    method @NonNull public android.media.AudioTrack.TunerConfiguration.Builder setContentId(@IntRange(from=1) int);
+    method @NonNull public android.media.AudioTrack.TunerConfiguration.Builder setSyncId(@IntRange(from=1) int);
+  }
+
   public class CamcorderProfile {
     method public static android.media.CamcorderProfile get(int);
     method public static android.media.CamcorderProfile get(int, int);
@@ -24276,28 +24551,39 @@
     method public static boolean hasProfile(int, int);
     field public static final int QUALITY_1080P = 6; // 0x6
     field public static final int QUALITY_2160P = 8; // 0x8
+    field public static final int QUALITY_2K = 12; // 0xc
     field public static final int QUALITY_480P = 4; // 0x4
+    field public static final int QUALITY_4KDCI = 10; // 0xa
     field public static final int QUALITY_720P = 5; // 0x5
     field public static final int QUALITY_CIF = 3; // 0x3
     field public static final int QUALITY_HIGH = 1; // 0x1
     field public static final int QUALITY_HIGH_SPEED_1080P = 2004; // 0x7d4
     field public static final int QUALITY_HIGH_SPEED_2160P = 2005; // 0x7d5
     field public static final int QUALITY_HIGH_SPEED_480P = 2002; // 0x7d2
+    field public static final int QUALITY_HIGH_SPEED_4KDCI = 2008; // 0x7d8
     field public static final int QUALITY_HIGH_SPEED_720P = 2003; // 0x7d3
+    field public static final int QUALITY_HIGH_SPEED_CIF = 2006; // 0x7d6
     field public static final int QUALITY_HIGH_SPEED_HIGH = 2001; // 0x7d1
     field public static final int QUALITY_HIGH_SPEED_LOW = 2000; // 0x7d0
+    field public static final int QUALITY_HIGH_SPEED_VGA = 2007; // 0x7d7
     field public static final int QUALITY_LOW = 0; // 0x0
     field public static final int QUALITY_QCIF = 2; // 0x2
+    field public static final int QUALITY_QHD = 11; // 0xb
     field public static final int QUALITY_QVGA = 7; // 0x7
     field public static final int QUALITY_TIME_LAPSE_1080P = 1006; // 0x3ee
     field public static final int QUALITY_TIME_LAPSE_2160P = 1008; // 0x3f0
+    field public static final int QUALITY_TIME_LAPSE_2K = 1012; // 0x3f4
     field public static final int QUALITY_TIME_LAPSE_480P = 1004; // 0x3ec
+    field public static final int QUALITY_TIME_LAPSE_4KDCI = 1010; // 0x3f2
     field public static final int QUALITY_TIME_LAPSE_720P = 1005; // 0x3ed
     field public static final int QUALITY_TIME_LAPSE_CIF = 1003; // 0x3eb
     field public static final int QUALITY_TIME_LAPSE_HIGH = 1001; // 0x3e9
     field public static final int QUALITY_TIME_LAPSE_LOW = 1000; // 0x3e8
     field public static final int QUALITY_TIME_LAPSE_QCIF = 1002; // 0x3ea
+    field public static final int QUALITY_TIME_LAPSE_QHD = 1011; // 0x3f3
     field public static final int QUALITY_TIME_LAPSE_QVGA = 1007; // 0x3ef
+    field public static final int QUALITY_TIME_LAPSE_VGA = 1009; // 0x3f1
+    field public static final int QUALITY_VGA = 9; // 0x9
     field public int audioBitRate;
     field public int audioChannels;
     field public int audioCodec;
@@ -24626,11 +24912,13 @@
 
   public final class MediaCas implements java.lang.AutoCloseable {
     ctor public MediaCas(int) throws android.media.MediaCasException.UnsupportedCasException;
+    ctor public MediaCas(int, @Nullable String, int) throws android.media.MediaCasException.UnsupportedCasException;
     method public void close();
     method public static android.media.MediaCas.PluginDescriptor[] enumeratePlugins();
     method protected void finalize();
     method public static boolean isSystemIdSupported(int);
     method public android.media.MediaCas.Session openSession() throws android.media.MediaCasException;
+    method @Nullable public android.media.MediaCas.Session openSession(int, int) throws android.media.MediaCasException;
     method public void processEmm(@NonNull byte[], int, int) throws android.media.MediaCasException;
     method public void processEmm(@NonNull byte[]) throws android.media.MediaCasException;
     method public void provision(@NonNull String) throws android.media.MediaCasException;
@@ -24638,10 +24926,32 @@
     method public void sendEvent(int, int, @Nullable byte[]) throws android.media.MediaCasException;
     method public void setEventListener(@Nullable android.media.MediaCas.EventListener, @Nullable android.os.Handler);
     method public void setPrivateData(@NonNull byte[]) throws android.media.MediaCasException;
+    field public static final int PLUGIN_STATUS_PHYSICAL_MODULE_CHANGED = 0; // 0x0
+    field public static final int PLUGIN_STATUS_SESSION_NUMBER_CHANGED = 1; // 0x1
+    field public static final int SCRAMBLING_MODE_AES128 = 9; // 0x9
+    field public static final int SCRAMBLING_MODE_AES_ECB = 10; // 0xa
+    field public static final int SCRAMBLING_MODE_AES_SCTE52 = 11; // 0xb
+    field public static final int SCRAMBLING_MODE_DVB_CISSA_V1 = 6; // 0x6
+    field public static final int SCRAMBLING_MODE_DVB_CSA1 = 1; // 0x1
+    field public static final int SCRAMBLING_MODE_DVB_CSA2 = 2; // 0x2
+    field public static final int SCRAMBLING_MODE_DVB_CSA3_ENHANCE = 5; // 0x5
+    field public static final int SCRAMBLING_MODE_DVB_CSA3_MINIMAL = 4; // 0x4
+    field public static final int SCRAMBLING_MODE_DVB_CSA3_STANDARD = 3; // 0x3
+    field public static final int SCRAMBLING_MODE_DVB_IDSA = 7; // 0x7
+    field public static final int SCRAMBLING_MODE_MULTI2 = 8; // 0x8
+    field public static final int SCRAMBLING_MODE_RESERVED = 0; // 0x0
+    field public static final int SCRAMBLING_MODE_TDES_ECB = 12; // 0xc
+    field public static final int SCRAMBLING_MODE_TDES_SCTE52 = 13; // 0xd
+    field public static final int SESSION_USAGE_LIVE = 0; // 0x0
+    field public static final int SESSION_USAGE_PLAYBACK = 1; // 0x1
+    field public static final int SESSION_USAGE_RECORD = 2; // 0x2
+    field public static final int SESSION_USAGE_TIMESHIFT = 3; // 0x3
   }
 
   public static interface MediaCas.EventListener {
     method public void onEvent(@NonNull android.media.MediaCas, int, int, @Nullable byte[]);
+    method public default void onPluginStatusUpdate(@NonNull android.media.MediaCas, int, int);
+    method public default void onResourceLost(@NonNull android.media.MediaCas);
     method public default void onSessionEvent(@NonNull android.media.MediaCas, @NonNull android.media.MediaCas.Session, int, int, @Nullable byte[]);
   }
 
@@ -24652,6 +24962,7 @@
 
   public final class MediaCas.Session implements java.lang.AutoCloseable {
     method public void close();
+    method @NonNull public byte[] getSessionId();
     method public void processEcm(@NonNull byte[], int, int) throws android.media.MediaCasException;
     method public void processEcm(@NonNull byte[]) throws android.media.MediaCasException;
     method public void sendSessionEvent(int, int, @Nullable byte[]) throws android.media.MediaCasException;
@@ -24664,6 +24975,9 @@
   public static final class MediaCasException.DeniedByServerException extends android.media.MediaCasException {
   }
 
+  public static final class MediaCasException.InsufficientResourceException extends android.media.MediaCasException {
+  }
+
   public static final class MediaCasException.NotProvisionedException extends android.media.MediaCasException {
   }
 
@@ -24701,7 +25015,9 @@
     method @Deprecated @NonNull public java.nio.ByteBuffer[] getOutputBuffers();
     method @NonNull public android.media.MediaFormat getOutputFormat();
     method @NonNull public android.media.MediaFormat getOutputFormat(int);
+    method @NonNull public android.media.MediaCodec.OutputFrame getOutputFrame(int);
     method @Nullable public android.media.Image getOutputImage(int);
+    method @NonNull public android.media.MediaCodec.QueueRequest getQueueRequest(int);
     method public void queueInputBuffer(int, int, int, long, int) throws android.media.MediaCodec.CryptoException;
     method public void queueSecureInputBuffer(int, int, @NonNull android.media.MediaCodec.CryptoInfo, long, int) throws android.media.MediaCodec.CryptoException;
     method public void release();
@@ -24725,6 +25041,7 @@
     field public static final int BUFFER_FLAG_PARTIAL_FRAME = 8; // 0x8
     field @Deprecated public static final int BUFFER_FLAG_SYNC_FRAME = 1; // 0x1
     field public static final int CONFIGURE_FLAG_ENCODE = 1; // 0x1
+    field public static final int CONFIGURE_FLAG_USE_BLOCK_MODEL = 2; // 0x2
     field public static final int CRYPTO_MODE_AES_CBC = 2; // 0x2
     field public static final int CRYPTO_MODE_AES_CTR = 1; // 0x1
     field public static final int CRYPTO_MODE_UNENCRYPTED = 0; // 0x0
@@ -24801,6 +25118,24 @@
     method public void set(int, int);
   }
 
+  public static final class MediaCodec.GraphicBlock {
+    method protected void finalize();
+    method public static boolean isCodecCopyFreeCompatible(@NonNull String[]);
+    method public boolean isMappable();
+    method @NonNull public android.media.Image map();
+    method @NonNull public static android.media.MediaCodec.GraphicBlock obtain(int, int, int, long, @NonNull String[]);
+    method public void recycle();
+  }
+
+  public static final class MediaCodec.LinearBlock {
+    method protected void finalize();
+    method public static boolean isCodecCopyFreeCompatible(@NonNull String[]);
+    method public boolean isMappable();
+    method @NonNull public java.nio.ByteBuffer map();
+    method @Nullable public static android.media.MediaCodec.LinearBlock obtain(int, @NonNull String[]);
+    method public void recycle();
+  }
+
   public static final class MediaCodec.MetricsConstants {
     field public static final String CODEC = "android.media.mediacodec.codec";
     field public static final String ENCODER = "android.media.mediacodec.encoder";
@@ -24818,6 +25153,27 @@
     method public void onFrameRendered(@NonNull android.media.MediaCodec, long, long);
   }
 
+  public static final class MediaCodec.OutputFrame {
+    method public void getChangedKeys(@NonNull java.util.Set<java.lang.String>);
+    method public int getFlags();
+    method @NonNull public android.media.MediaFormat getFormat();
+    method @Nullable public android.media.MediaCodec.GraphicBlock getGraphicBlock();
+    method @Nullable public android.media.MediaCodec.LinearBlock getLinearBlock();
+    method public long getPresentationTimeUs();
+  }
+
+  public final class MediaCodec.QueueRequest {
+    method public void queue();
+    method @NonNull public android.media.MediaCodec.QueueRequest setByteBufferParameter(@NonNull String, @NonNull java.nio.ByteBuffer);
+    method @NonNull public android.media.MediaCodec.QueueRequest setEncryptedLinearBlock(@NonNull android.media.MediaCodec.LinearBlock, int, @NonNull android.media.MediaCodec.CryptoInfo, long, int);
+    method @NonNull public android.media.MediaCodec.QueueRequest setFloatParameter(@NonNull String, float);
+    method @NonNull public android.media.MediaCodec.QueueRequest setGraphicBlock(@NonNull android.media.MediaCodec.GraphicBlock, long, int);
+    method @NonNull public android.media.MediaCodec.QueueRequest setIntegerParameter(@NonNull String, int);
+    method @NonNull public android.media.MediaCodec.QueueRequest setLinearBlock(@NonNull android.media.MediaCodec.LinearBlock, int, int, long, int);
+    method @NonNull public android.media.MediaCodec.QueueRequest setLongParameter(@NonNull String, long);
+    method @NonNull public android.media.MediaCodec.QueueRequest setStringParameter(@NonNull String, @NonNull String);
+  }
+
   public final class MediaCodecInfo {
     method @NonNull public String getCanonicalName();
     method public android.media.MediaCodecInfo.CodecCapabilities getCapabilitiesForType(String);
@@ -25580,10 +25936,12 @@
     field public static final int COLOR_TRANSFER_LINEAR = 1; // 0x1
     field public static final int COLOR_TRANSFER_SDR_VIDEO = 3; // 0x3
     field public static final int COLOR_TRANSFER_ST2084 = 6; // 0x6
+    field public static final String KEY_AAC_DRC_ALBUM_MODE = "aac-drc-album-mode";
     field public static final String KEY_AAC_DRC_ATTENUATION_FACTOR = "aac-drc-cut-level";
     field public static final String KEY_AAC_DRC_BOOST_FACTOR = "aac-drc-boost-level";
     field public static final String KEY_AAC_DRC_EFFECT_TYPE = "aac-drc-effect-type";
     field public static final String KEY_AAC_DRC_HEAVY_COMPRESSION = "aac-drc-heavy-compression";
+    field public static final String KEY_AAC_DRC_OUTPUT_LOUDNESS = "aac-drc-output-loudness";
     field public static final String KEY_AAC_DRC_TARGET_REFERENCE_LEVEL = "aac-target-ref-level";
     field public static final String KEY_AAC_ENCODED_TARGET_LEVEL = "aac-encoded-target-level";
     field public static final String KEY_AAC_MAX_OUTPUT_CHANNEL_COUNT = "aac-max-output-channel_count";
@@ -25609,6 +25967,7 @@
     field public static final String KEY_GRID_COLUMNS = "grid-cols";
     field public static final String KEY_GRID_ROWS = "grid-rows";
     field public static final String KEY_HAPTIC_CHANNEL_COUNT = "haptic-channel-count";
+    field public static final String KEY_HARDWARE_AV_SYNC_ID = "hw-av-sync-id";
     field public static final String KEY_HDR10_PLUS_INFO = "hdr10-plus-info";
     field public static final String KEY_HDR_STATIC_INFO = "hdr-static-info";
     field public static final String KEY_HEIGHT = "height";
@@ -26280,6 +26639,77 @@
     field public static final int SURFACE = 2; // 0x2
   }
 
+  public final class MediaRoute2Info implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public String getClientPackageName();
+    method public int getConnectionState();
+    method @Nullable public CharSequence getDescription();
+    method public int getDeviceType();
+    method @Nullable public android.os.Bundle getExtras();
+    method @NonNull public java.util.List<java.lang.String> getFeatures();
+    method @Nullable public android.net.Uri getIconUri();
+    method @NonNull public String getId();
+    method @NonNull public CharSequence getName();
+    method public int getVolume();
+    method public int getVolumeHandling();
+    method public int getVolumeMax();
+    method public boolean hasAnyFeatures(@NonNull java.util.Collection<java.lang.String>);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int CONNECTION_STATE_CONNECTED = 2; // 0x2
+    field public static final int CONNECTION_STATE_CONNECTING = 1; // 0x1
+    field public static final int CONNECTION_STATE_DISCONNECTED = 0; // 0x0
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.MediaRoute2Info> CREATOR;
+    field public static final int DEVICE_TYPE_BLUETOOTH = 3; // 0x3
+    field public static final int DEVICE_TYPE_REMOTE_SPEAKER = 2; // 0x2
+    field public static final int DEVICE_TYPE_REMOTE_TV = 1; // 0x1
+    field public static final int DEVICE_TYPE_UNKNOWN = 0; // 0x0
+    field public static final String FEATURE_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO";
+    field public static final String FEATURE_LIVE_VIDEO = "android.media.intent.category.LIVE_VIDEO";
+    field public static final String FEATURE_REMOTE_PLAYBACK = "android.media.intent.category.REMOTE_PLAYBACK";
+    field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0
+    field public static final int PLAYBACK_VOLUME_VARIABLE = 1; // 0x1
+  }
+
+  public static final class MediaRoute2Info.Builder {
+    ctor public MediaRoute2Info.Builder(@NonNull String, @NonNull CharSequence);
+    ctor public MediaRoute2Info.Builder(@NonNull android.media.MediaRoute2Info);
+    method @NonNull public android.media.MediaRoute2Info.Builder addFeature(@NonNull String);
+    method @NonNull public android.media.MediaRoute2Info.Builder addFeatures(@NonNull java.util.Collection<java.lang.String>);
+    method @NonNull public android.media.MediaRoute2Info build();
+    method @NonNull public android.media.MediaRoute2Info.Builder clearFeatures();
+    method @NonNull public android.media.MediaRoute2Info.Builder setClientPackageName(@Nullable String);
+    method @NonNull public android.media.MediaRoute2Info.Builder setConnectionState(int);
+    method @NonNull public android.media.MediaRoute2Info.Builder setDescription(@Nullable CharSequence);
+    method @NonNull public android.media.MediaRoute2Info.Builder setDeviceType(int);
+    method @NonNull public android.media.MediaRoute2Info.Builder setExtras(@Nullable android.os.Bundle);
+    method @NonNull public android.media.MediaRoute2Info.Builder setIconUri(@Nullable android.net.Uri);
+    method @NonNull public android.media.MediaRoute2Info.Builder setVolume(int);
+    method @NonNull public android.media.MediaRoute2Info.Builder setVolumeHandling(int);
+    method @NonNull public android.media.MediaRoute2Info.Builder setVolumeMax(int);
+  }
+
+  public abstract class MediaRoute2ProviderService extends android.app.Service {
+    ctor public MediaRoute2ProviderService();
+    method @NonNull public final java.util.List<android.media.RoutingSessionInfo> getAllSessionInfo();
+    method @Nullable public final android.media.RoutingSessionInfo getSessionInfo(@NonNull String);
+    method public final void notifyRoutes(@NonNull java.util.Collection<android.media.MediaRoute2Info>);
+    method public final void notifySessionCreated(@NonNull android.media.RoutingSessionInfo, long);
+    method public final void notifySessionCreationFailed(long);
+    method public final void notifySessionReleased(@NonNull String);
+    method public final void notifySessionUpdated(@NonNull android.media.RoutingSessionInfo);
+    method @CallSuper @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
+    method public abstract void onCreateSession(@NonNull String, @NonNull String, long, @Nullable android.os.Bundle);
+    method public abstract void onDeselectRoute(@NonNull String, @NonNull String);
+    method public void onDiscoveryPreferenceChanged(@NonNull android.media.RouteDiscoveryPreference);
+    method public abstract void onReleaseSession(@NonNull String);
+    method public abstract void onSelectRoute(@NonNull String, @NonNull String);
+    method public abstract void onSetVolume(@NonNull String, int);
+    method public abstract void onTransferToRoute(@NonNull String, @NonNull String);
+    method public abstract void onUpdateVolume(@NonNull String, int);
+    field public static final long REQUEST_ID_UNKNOWN = 0L; // 0x0L
+    field public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService";
+  }
+
   public class MediaRouter {
     method public void addCallback(int, android.media.MediaRouter.Callback);
     method public void addCallback(int, android.media.MediaRouter.Callback, int);
@@ -26403,6 +26833,52 @@
     method public abstract void onVolumeUpdateRequest(android.media.MediaRouter.RouteInfo, int);
   }
 
+  public class MediaRouter2 {
+    method @NonNull public java.util.List<android.media.MediaRouter2.RoutingController> getControllers();
+    method @NonNull public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context);
+    method @NonNull public java.util.List<android.media.MediaRoute2Info> getRoutes();
+    method @NonNull public android.media.MediaRouter2.RoutingController getSystemController();
+    method public void registerControllerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RoutingControllerCallback);
+    method public void registerRouteCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RouteCallback, @NonNull android.media.RouteDiscoveryPreference);
+    method public void requestCreateController(@NonNull android.media.MediaRoute2Info);
+    method public void setOnGetControllerHintsListener(@Nullable android.media.MediaRouter2.OnGetControllerHintsListener);
+    method public void unregisterControllerCallback(@NonNull android.media.MediaRouter2.RoutingControllerCallback);
+    method public void unregisterRouteCallback(@NonNull android.media.MediaRouter2.RouteCallback);
+  }
+
+  public static interface MediaRouter2.OnGetControllerHintsListener {
+    method @Nullable public android.os.Bundle onGetControllerHints(@NonNull android.media.MediaRoute2Info);
+  }
+
+  public static class MediaRouter2.RouteCallback {
+    ctor public MediaRouter2.RouteCallback();
+    method public void onRoutesAdded(@NonNull java.util.List<android.media.MediaRoute2Info>);
+    method public void onRoutesChanged(@NonNull java.util.List<android.media.MediaRoute2Info>);
+    method public void onRoutesRemoved(@NonNull java.util.List<android.media.MediaRoute2Info>);
+  }
+
+  public class MediaRouter2.RoutingController {
+    method public void deselectRoute(@NonNull android.media.MediaRoute2Info);
+    method @Nullable public android.os.Bundle getControlHints();
+    method @NonNull public java.util.List<android.media.MediaRoute2Info> getDeselectableRoutes();
+    method @NonNull public String getId();
+    method @NonNull public java.util.List<android.media.MediaRoute2Info> getSelectableRoutes();
+    method @NonNull public java.util.List<android.media.MediaRoute2Info> getSelectedRoutes();
+    method @NonNull public java.util.List<android.media.MediaRoute2Info> getTransferrableRoutes();
+    method public boolean isReleased();
+    method public void release();
+    method public void selectRoute(@NonNull android.media.MediaRoute2Info);
+    method public void transferToRoute(@NonNull android.media.MediaRoute2Info);
+  }
+
+  public static class MediaRouter2.RoutingControllerCallback {
+    ctor public MediaRouter2.RoutingControllerCallback();
+    method public void onControllerCreated(@NonNull android.media.MediaRouter2.RoutingController);
+    method public void onControllerCreationFailed(@NonNull android.media.MediaRoute2Info);
+    method public void onControllerReleased(@NonNull android.media.MediaRouter2.RoutingController);
+    method public void onControllerUpdated(@NonNull android.media.MediaRouter2.RoutingController);
+  }
+
   public class MediaScannerConnection implements android.content.ServiceConnection {
     ctor public MediaScannerConnection(android.content.Context, android.media.MediaScannerConnection.MediaScannerConnectionClient);
     method public void connect();
@@ -26761,6 +27237,54 @@
     field public static final int URI_COLUMN_INDEX = 2; // 0x2
   }
 
+  public final class RouteDiscoveryPreference implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<java.lang.String> getPreferredFeatures();
+    method public boolean isActiveScan();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteDiscoveryPreference> CREATOR;
+  }
+
+  public static final class RouteDiscoveryPreference.Builder {
+    ctor public RouteDiscoveryPreference.Builder(@NonNull java.util.List<java.lang.String>, boolean);
+    ctor public RouteDiscoveryPreference.Builder(@NonNull android.media.RouteDiscoveryPreference);
+    method @NonNull public android.media.RouteDiscoveryPreference build();
+    method @NonNull public android.media.RouteDiscoveryPreference.Builder setActiveScan(boolean);
+    method @NonNull public android.media.RouteDiscoveryPreference.Builder setPreferredFeatures(@NonNull java.util.List<java.lang.String>);
+  }
+
+  public final class RoutingSessionInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public String getClientPackageName();
+    method @Nullable public android.os.Bundle getControlHints();
+    method @NonNull public java.util.List<java.lang.String> getDeselectableRoutes();
+    method @NonNull public String getId();
+    method @NonNull public java.util.List<java.lang.String> getSelectableRoutes();
+    method @NonNull public java.util.List<java.lang.String> getSelectedRoutes();
+    method @NonNull public java.util.List<java.lang.String> getTransferrableRoutes();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.RoutingSessionInfo> CREATOR;
+  }
+
+  public static final class RoutingSessionInfo.Builder {
+    ctor public RoutingSessionInfo.Builder(@NonNull String, @NonNull String);
+    ctor public RoutingSessionInfo.Builder(@NonNull android.media.RoutingSessionInfo);
+    method @NonNull public android.media.RoutingSessionInfo.Builder addDeselectableRoute(@NonNull String);
+    method @NonNull public android.media.RoutingSessionInfo.Builder addSelectableRoute(@NonNull String);
+    method @NonNull public android.media.RoutingSessionInfo.Builder addSelectedRoute(@NonNull String);
+    method @NonNull public android.media.RoutingSessionInfo.Builder addTransferrableRoute(@NonNull String);
+    method @NonNull public android.media.RoutingSessionInfo build();
+    method @NonNull public android.media.RoutingSessionInfo.Builder clearDeselectableRoutes();
+    method @NonNull public android.media.RoutingSessionInfo.Builder clearSelectableRoutes();
+    method @NonNull public android.media.RoutingSessionInfo.Builder clearSelectedRoutes();
+    method @NonNull public android.media.RoutingSessionInfo.Builder clearTransferrableRoutes();
+    method @NonNull public android.media.RoutingSessionInfo.Builder removeDeselectableRoute(@NonNull String);
+    method @NonNull public android.media.RoutingSessionInfo.Builder removeSelectableRoute(@NonNull String);
+    method @NonNull public android.media.RoutingSessionInfo.Builder removeSelectedRoute(@NonNull String);
+    method @NonNull public android.media.RoutingSessionInfo.Builder removeTransferrableRoute(@NonNull String);
+    method @NonNull public android.media.RoutingSessionInfo.Builder setControlHints(@Nullable android.os.Bundle);
+  }
+
   public final class Session2Command implements android.os.Parcelable {
     ctor public Session2Command(int);
     ctor public Session2Command(@NonNull String, @Nullable android.os.Bundle);
@@ -27019,9 +27543,11 @@
 
   public abstract class VolumeProvider {
     ctor public VolumeProvider(int, int, int);
+    ctor public VolumeProvider(int, int, int, @Nullable String);
     method public final int getCurrentVolume();
     method public final int getMaxVolume();
     method public final int getVolumeControl();
+    method @Nullable public final String getVolumeControlId();
     method public void onAdjustVolume(int);
     method public void onSetVolumeTo(int);
     method public final void setCurrentVolume(int);
@@ -27881,6 +28407,7 @@
     method public int getMaxVolume();
     method public int getPlaybackType();
     method public int getVolumeControl();
+    method @Nullable public String getVolumeControlId();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.media.session.MediaController.PlaybackInfo> CREATOR;
     field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
@@ -28564,6 +29091,19 @@
     field public static final int TIME_SHIFT_STATUS_UNSUPPORTED = 1; // 0x1
     field public static final int VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY = 4; // 0x4
     field public static final int VIDEO_UNAVAILABLE_REASON_BUFFERING = 3; // 0x3
+    field public static final int VIDEO_UNAVAILABLE_REASON_CAS_BLACKOUT = 16; // 0x10
+    field public static final int VIDEO_UNAVAILABLE_REASON_CAS_CARD_INVALID = 15; // 0xf
+    field public static final int VIDEO_UNAVAILABLE_REASON_CAS_CARD_MUTE = 14; // 0xe
+    field public static final int VIDEO_UNAVAILABLE_REASON_CAS_INSUFFICIENT_OUTPUT_PROTECTION = 7; // 0x7
+    field public static final int VIDEO_UNAVAILABLE_REASON_CAS_LICENSE_EXPIRED = 10; // 0xa
+    field public static final int VIDEO_UNAVAILABLE_REASON_CAS_NEED_ACTIVATION = 11; // 0xb
+    field public static final int VIDEO_UNAVAILABLE_REASON_CAS_NEED_PAIRING = 12; // 0xc
+    field public static final int VIDEO_UNAVAILABLE_REASON_CAS_NO_CARD = 13; // 0xd
+    field public static final int VIDEO_UNAVAILABLE_REASON_CAS_PVR_RECORDING_NOT_ALLOWED = 8; // 0x8
+    field public static final int VIDEO_UNAVAILABLE_REASON_CAS_REBOOTING = 17; // 0x11
+    field public static final int VIDEO_UNAVAILABLE_REASON_CAS_UNKNOWN = 18; // 0x12
+    field public static final int VIDEO_UNAVAILABLE_REASON_INSUFFICIENT_RESOURCE = 6; // 0x6
+    field public static final int VIDEO_UNAVAILABLE_REASON_NOT_CONNECTED = 5; // 0x5
     field public static final int VIDEO_UNAVAILABLE_REASON_TUNING = 1; // 0x1
     field public static final int VIDEO_UNAVAILABLE_REASON_UNKNOWN = 0; // 0x0
     field public static final int VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL = 2; // 0x2
@@ -28582,7 +29122,14 @@
     ctor public TvInputService();
     method public final android.os.IBinder onBind(android.content.Intent);
     method @Nullable public android.media.tv.TvInputService.RecordingSession onCreateRecordingSession(String);
+    method @Nullable public android.media.tv.TvInputService.RecordingSession onCreateRecordingSession(@NonNull String, @NonNull String);
     method @Nullable public abstract android.media.tv.TvInputService.Session onCreateSession(String);
+    method @Nullable public android.media.tv.TvInputService.Session onCreateSession(@NonNull String, @NonNull String);
+    field public static final int PRIORITY_HINT_USE_CASE_TYPE_BACKGROUND = 100; // 0x64
+    field public static final int PRIORITY_HINT_USE_CASE_TYPE_LIVE = 400; // 0x190
+    field public static final int PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK = 300; // 0x12c
+    field public static final int PRIORITY_HINT_USE_CASE_TYPE_RECORD = 500; // 0x1f4
+    field public static final int PRIORITY_HINT_USE_CASE_TYPE_SCAN = 200; // 0xc8
     field public static final String SERVICE_INTERFACE = "android.media.tv.TvInputService";
     field public static final String SERVICE_META_DATA = "android.media.tv.input";
   }
@@ -28684,7 +29231,8 @@
     method public boolean isAudioDescription();
     method public boolean isEncrypted();
     method public boolean isHardOfHearing();
-    method public void writeToParcel(android.os.Parcel, int);
+    method public boolean isSpokenSubtitle();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.TvTrackInfo> CREATOR;
     field public static final int TYPE_AUDIO = 0; // 0x0
     field public static final int TYPE_SUBTITLE = 2; // 0x2
@@ -28693,20 +29241,21 @@
 
   public static final class TvTrackInfo.Builder {
     ctor public TvTrackInfo.Builder(int, @NonNull String);
-    method public android.media.tv.TvTrackInfo build();
-    method public android.media.tv.TvTrackInfo.Builder setAudioChannelCount(int);
+    method @NonNull public android.media.tv.TvTrackInfo build();
+    method @NonNull public android.media.tv.TvTrackInfo.Builder setAudioChannelCount(int);
     method @NonNull public android.media.tv.TvTrackInfo.Builder setAudioDescription(boolean);
-    method public android.media.tv.TvTrackInfo.Builder setAudioSampleRate(int);
-    method public android.media.tv.TvTrackInfo.Builder setDescription(CharSequence);
+    method @NonNull public android.media.tv.TvTrackInfo.Builder setAudioSampleRate(int);
+    method @NonNull public android.media.tv.TvTrackInfo.Builder setDescription(@NonNull CharSequence);
     method @NonNull public android.media.tv.TvTrackInfo.Builder setEncrypted(boolean);
-    method public android.media.tv.TvTrackInfo.Builder setExtra(android.os.Bundle);
+    method @NonNull public android.media.tv.TvTrackInfo.Builder setExtra(@NonNull android.os.Bundle);
     method @NonNull public android.media.tv.TvTrackInfo.Builder setHardOfHearing(boolean);
-    method public android.media.tv.TvTrackInfo.Builder setLanguage(String);
-    method public android.media.tv.TvTrackInfo.Builder setVideoActiveFormatDescription(byte);
-    method public android.media.tv.TvTrackInfo.Builder setVideoFrameRate(float);
-    method public android.media.tv.TvTrackInfo.Builder setVideoHeight(int);
-    method public android.media.tv.TvTrackInfo.Builder setVideoPixelAspectRatio(float);
-    method public android.media.tv.TvTrackInfo.Builder setVideoWidth(int);
+    method @NonNull public android.media.tv.TvTrackInfo.Builder setLanguage(@NonNull String);
+    method @NonNull public android.media.tv.TvTrackInfo.Builder setSpokenSubtitle(boolean);
+    method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoActiveFormatDescription(byte);
+    method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoFrameRate(float);
+    method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoHeight(int);
+    method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoPixelAspectRatio(float);
+    method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoWidth(int);
   }
 
   public class TvView extends android.view.ViewGroup {
@@ -29013,6 +29562,43 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.net.CaptivePortal> CREATOR;
   }
 
+  public class ConnectivityDiagnosticsManager {
+    method public void registerConnectivityDiagnosticsCallback(@NonNull android.net.NetworkRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback);
+    method public void unregisterConnectivityDiagnosticsCallback(@NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback);
+  }
+
+  public abstract static class ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback {
+    ctor public ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback();
+    method public void onConnectivityReport(@NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityReport);
+    method public void onDataStallSuspected(@NonNull android.net.ConnectivityDiagnosticsManager.DataStallReport);
+    method public void onNetworkConnectivityReported(@NonNull android.net.Network, boolean);
+  }
+
+  public static final class ConnectivityDiagnosticsManager.ConnectivityReport implements android.os.Parcelable {
+    ctor public ConnectivityDiagnosticsManager.ConnectivityReport(@NonNull android.net.Network, long, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkCapabilities, @NonNull android.os.PersistableBundle);
+    method public int describeContents();
+    method @NonNull public android.os.PersistableBundle getAdditionalInfo();
+    method @NonNull public android.net.LinkProperties getLinkProperties();
+    method @NonNull public android.net.Network getNetwork();
+    method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities();
+    method public long getReportTimestamp();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.ConnectivityDiagnosticsManager.ConnectivityReport> CREATOR;
+  }
+
+  public static final class ConnectivityDiagnosticsManager.DataStallReport implements android.os.Parcelable {
+    ctor public ConnectivityDiagnosticsManager.DataStallReport(@NonNull android.net.Network, long, int, @NonNull android.os.PersistableBundle);
+    method public int describeContents();
+    method public int getDetectionMethod();
+    method @NonNull public android.net.Network getNetwork();
+    method public long getReportTimestamp();
+    method @NonNull public android.os.PersistableBundle getStallDetails();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.ConnectivityDiagnosticsManager.DataStallReport> CREATOR;
+    field public static final int DETECTION_METHOD_DNS_EVENTS = 1; // 0x1
+    field public static final int DETECTION_METHOD_TCP_METRICS = 2; // 0x2
+  }
+
   public class ConnectivityManager {
     method public void addDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener);
     method public boolean bindProcessToNetwork(@Nullable android.net.Network);
@@ -29117,6 +29703,7 @@
     ctor public DhcpInfo();
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.DhcpInfo> CREATOR;
     field public int dns1;
     field public int dns2;
     field public int gateway;
@@ -29426,6 +30013,7 @@
   }
 
   @Deprecated public class NetworkInfo implements android.os.Parcelable {
+    ctor @Deprecated public NetworkInfo(int, int, @Nullable String, @Nullable String);
     method @Deprecated public int describeContents();
     method @Deprecated @NonNull public android.net.NetworkInfo.DetailedState getDetailedState();
     method @Deprecated public String getExtraInfo();
@@ -29440,6 +30028,7 @@
     method @Deprecated public boolean isConnectedOrConnecting();
     method @Deprecated public boolean isFailover();
     method @Deprecated public boolean isRoaming();
+    method @Deprecated public void setDetailedState(@NonNull android.net.NetworkInfo.DetailedState, @Nullable String, @Nullable String);
     method @Deprecated public void writeToParcel(android.os.Parcel, int);
     field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkInfo> CREATOR;
   }
@@ -29486,7 +30075,7 @@
     method @NonNull public android.net.NetworkRequest.Builder clearCapabilities();
     method public android.net.NetworkRequest.Builder removeCapability(int);
     method public android.net.NetworkRequest.Builder removeTransportType(int);
-    method public android.net.NetworkRequest.Builder setNetworkSpecifier(String);
+    method @Deprecated public android.net.NetworkRequest.Builder setNetworkSpecifier(String);
     method public android.net.NetworkRequest.Builder setNetworkSpecifier(android.net.NetworkSpecifier);
   }
 
@@ -29585,6 +30174,19 @@
     method public void onStopped();
   }
 
+  public final class TelephonyNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getSubscriptionId();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.TelephonyNetworkSpecifier> CREATOR;
+  }
+
+  public static final class TelephonyNetworkSpecifier.Builder {
+    ctor public TelephonyNetworkSpecifier.Builder();
+    method @NonNull public android.net.TelephonyNetworkSpecifier build();
+    method @NonNull public android.net.TelephonyNetworkSpecifier.Builder setSubscriptionId(int);
+  }
+
   public class TrafficStats {
     ctor public TrafficStats();
     method public static void clearThreadStatsTag();
@@ -29998,6 +30600,7 @@
     method public void close();
     method public void continueCall(int) throws android.net.sip.SipException;
     method public void endCall() throws android.net.sip.SipException;
+    method @Nullable public android.net.rtp.AudioGroup getAudioGroup();
     method public android.net.sip.SipProfile getLocalProfile();
     method public android.net.sip.SipProfile getPeerProfile();
     method public int getState();
@@ -30008,6 +30611,7 @@
     method public void makeCall(android.net.sip.SipProfile, android.net.sip.SipSession, int) throws android.net.sip.SipException;
     method public void sendDtmf(int);
     method public void sendDtmf(int, android.os.Message);
+    method public void setAudioGroup(@NonNull android.net.rtp.AudioGroup);
     method public void setListener(android.net.sip.SipAudioCall.Listener);
     method public void setListener(android.net.sip.SipAudioCall.Listener, boolean);
     method public void setSpeakerMode(boolean);
@@ -30056,6 +30660,7 @@
     method public void close(String) throws android.net.sip.SipException;
     method public android.net.sip.SipSession createSipSession(android.net.sip.SipProfile, android.net.sip.SipSession.Listener) throws android.net.sip.SipException;
     method public static String getCallId(android.content.Intent);
+    method @NonNull public java.util.List<android.net.sip.SipProfile> getListOfProfiles() throws android.net.sip.SipException;
     method public static String getOfferSessionDescription(android.content.Intent);
     method public android.net.sip.SipSession getSessionFor(android.content.Intent) throws android.net.sip.SipException;
     method public static boolean isApiSupported(android.content.Context);
@@ -30073,6 +30678,11 @@
     method public void setRegistrationListener(String, android.net.sip.SipRegistrationListener) throws android.net.sip.SipException;
     method public android.net.sip.SipAudioCall takeAudioCall(android.content.Intent, android.net.sip.SipAudioCall.Listener) throws android.net.sip.SipException;
     method public void unregister(android.net.sip.SipProfile, android.net.sip.SipRegistrationListener) throws android.net.sip.SipException;
+    field public static final String ACTION_SIP_CALL_OPTION_CHANGED = "android.net.sip.action.SIP_CALL_OPTION_CHANGED";
+    field public static final String ACTION_SIP_INCOMING_CALL = "android.net.sip.action.SIP_INCOMING_CALL";
+    field public static final String ACTION_SIP_REMOVE_PROFILE = "android.net.sip.action.SIP_REMOVE_PROFILE";
+    field public static final String ACTION_SIP_SERVICE_UP = "android.net.sip.action.SIP_SERVICE_UP";
+    field public static final String ACTION_START_SIP = "android.net.sip.action.START_SIP";
     field public static final String EXTRA_CALL_ID = "android:sipCallID";
     field public static final String EXTRA_OFFER_SD = "android:sipOfferSD";
     field public static final int INCOMING_CALL_RESULT_CODE = 101; // 0x65
@@ -30082,6 +30692,7 @@
     method public int describeContents();
     method public String getAuthUserName();
     method public boolean getAutoRegistration();
+    method public int getCallingUid();
     method public String getDisplayName();
     method public String getPassword();
     method public int getPort();
@@ -30092,6 +30703,7 @@
     method public String getSipDomain();
     method public String getUriString();
     method public String getUserName();
+    method public void setCallingUid(int);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.net.sip.SipProfile> CREATOR;
   }
@@ -30199,7 +30811,9 @@
   }
 
   public class ScanResult implements android.os.Parcelable {
+    ctor public ScanResult(@NonNull android.net.wifi.ScanResult);
     method public int describeContents();
+    method @NonNull public java.util.List<android.net.wifi.ScanResult.InformationElement> getInformationElements();
     method public int getWifiStandard();
     method public boolean is80211mcResponder();
     method public boolean isPasspointNetwork();
@@ -30210,6 +30824,7 @@
     field public static final int CHANNEL_WIDTH_40MHZ = 1; // 0x1
     field public static final int CHANNEL_WIDTH_80MHZ = 2; // 0x2
     field public static final int CHANNEL_WIDTH_80MHZ_PLUS_MHZ = 4; // 0x4
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.ScanResult> CREATOR;
     field public String SSID;
     field public static final int WIFI_STANDARD_11AC = 5; // 0x5
     field public static final int WIFI_STANDARD_11AX = 6; // 0x6
@@ -30227,6 +30842,28 @@
     field public CharSequence venueName;
   }
 
+  public static class ScanResult.InformationElement {
+    ctor public ScanResult.InformationElement(@NonNull android.net.wifi.ScanResult.InformationElement);
+    method @NonNull public java.nio.ByteBuffer getBytes();
+    method public int getId();
+    method public int getIdExt();
+  }
+
+  public final class SoftApConfiguration implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.net.MacAddress getBssid();
+    method @Nullable public String getPassphrase();
+    method public int getSecurityType();
+    method @Nullable public String getSsid();
+    method public boolean isHiddenSsid();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.SoftApConfiguration> CREATOR;
+    field public static final int SECURITY_TYPE_OPEN = 0; // 0x0
+    field public static final int SECURITY_TYPE_WPA2_PSK = 1; // 0x1
+    field public static final int SECURITY_TYPE_WPA3_SAE = 3; // 0x3
+    field public static final int SECURITY_TYPE_WPA3_SAE_TRANSITION = 2; // 0x2
+  }
+
   public enum SupplicantState implements android.os.Parcelable {
     method public int describeContents();
     method public static boolean isValidState(android.net.wifi.SupplicantState);
@@ -30255,9 +30892,19 @@
     method @Deprecated @NonNull public android.net.MacAddress getRandomizedMacAddress();
     method @Deprecated public boolean isPasspoint();
     method @Deprecated public void setHttpProxy(android.net.ProxyInfo);
+    method @Deprecated public void setSecurityParams(int);
     method public void writeToParcel(android.os.Parcel, int);
     field @Deprecated public String BSSID;
     field @Deprecated public String FQDN;
+    field @Deprecated public static final int SECURITY_TYPE_EAP = 3; // 0x3
+    field @Deprecated public static final int SECURITY_TYPE_EAP_SUITE_B = 5; // 0x5
+    field @Deprecated public static final int SECURITY_TYPE_OPEN = 0; // 0x0
+    field @Deprecated public static final int SECURITY_TYPE_OWE = 6; // 0x6
+    field @Deprecated public static final int SECURITY_TYPE_PSK = 2; // 0x2
+    field @Deprecated public static final int SECURITY_TYPE_SAE = 4; // 0x4
+    field @Deprecated public static final int SECURITY_TYPE_WAPI_CERT = 8; // 0x8
+    field @Deprecated public static final int SECURITY_TYPE_WAPI_PSK = 7; // 0x7
+    field @Deprecated public static final int SECURITY_TYPE_WEP = 1; // 0x1
     field @Deprecated public String SSID;
     field @Deprecated @NonNull public java.util.BitSet allowedAuthAlgorithms;
     field @Deprecated @NonNull public java.util.BitSet allowedGroupCiphers;
@@ -30282,6 +30929,7 @@
   @Deprecated public static class WifiConfiguration.AuthAlgorithm {
     field @Deprecated public static final int LEAP = 2; // 0x2
     field @Deprecated public static final int OPEN = 0; // 0x0
+    field @Deprecated public static final int SAE = 3; // 0x3
     field @Deprecated public static final int SHARED = 1; // 0x1
     field @Deprecated public static final String[] strings;
     field @Deprecated public static final String varName = "auth_alg";
@@ -30341,6 +30989,11 @@
     field @Deprecated public static final String[] strings;
   }
 
+  @Deprecated public static class WifiConfiguration.SuiteBCipher {
+    field @Deprecated public static final int ECDHE_ECDSA = 0; // 0x0
+    field @Deprecated public static final int ECDHE_RSA = 1; // 0x1
+  }
+
   public class WifiEnterpriseConfig implements android.os.Parcelable {
     ctor public WifiEnterpriseConfig();
     ctor public WifiEnterpriseConfig(android.net.wifi.WifiEnterpriseConfig);
@@ -30351,6 +31004,7 @@
     method @Nullable public java.security.cert.X509Certificate[] getCaCertificates();
     method public java.security.cert.X509Certificate getClientCertificate();
     method @Nullable public java.security.cert.X509Certificate[] getClientCertificateChain();
+    method @Nullable public java.security.PrivateKey getClientPrivateKey();
     method public String getDomainSuffixMatch();
     method public int getEapMethod();
     method public String getIdentity();
@@ -30417,6 +31071,8 @@
     method public int getIpAddress();
     method public int getLinkSpeed();
     method public String getMacAddress();
+    method public int getMaxSupportedRxLinkSpeedMbps();
+    method public int getMaxSupportedTxLinkSpeedMbps();
     method public int getNetworkId();
     method @Nullable public String getPasspointFqdn();
     method @Nullable public String getPasspointProviderFriendlyName();
@@ -30438,7 +31094,7 @@
     method public void addOrUpdatePasspointConfiguration(android.net.wifi.hotspot2.PasspointConfiguration);
     method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE}) public void addSuggestionConnectionStatusListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.SuggestionConnectionStatusListener);
     method @Deprecated public static int calculateSignalLevel(int, int);
-    method public int calculateSignalLevel(int);
+    method @IntRange(from=0) public int calculateSignalLevel(int);
     method @Deprecated public void cancelWps(android.net.wifi.WifiManager.WpsCallback);
     method public static int compareSignalLevel(int, int);
     method public android.net.wifi.WifiManager.MulticastLock createMulticastLock(String);
@@ -30451,7 +31107,7 @@
     method public android.net.wifi.WifiInfo getConnectionInfo();
     method public android.net.DhcpInfo getDhcpInfo();
     method public int getMaxNumberOfNetworkSuggestionsPerApp();
-    method public int getMaxSignalLevel();
+    method @IntRange(from=0) public int getMaxSignalLevel();
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public java.util.List<android.net.wifi.WifiNetworkSuggestion> getNetworkSuggestions();
     method @Deprecated @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", "android.permission.NETWORK_SETUP_WIZARD"}) public java.util.List<android.net.wifi.hotspot2.PasspointConfiguration> getPasspointConfigurations();
     method public java.util.List<android.net.wifi.ScanResult> getScanResults();
@@ -30465,9 +31121,11 @@
     method public boolean isP2pSupported();
     method public boolean isPreferredNetworkOffloadSupported();
     method @Deprecated public boolean isScanAlwaysAvailable();
+    method public boolean isStaApConcurrencySupported();
     method public boolean isTdlsSupported();
     method public boolean isWapiSupported();
     method public boolean isWifiEnabled();
+    method public boolean isWifiStandardSupported(int);
     method public boolean isWpa3SaeSupported();
     method public boolean isWpa3SuiteBSupported();
     method @Deprecated public boolean pingSupplicant();
@@ -30490,6 +31148,7 @@
     field public static final String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK";
     field public static final String ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE = "android.net.wifi.action.REQUEST_SCAN_ALWAYS_AVAILABLE";
     field public static final String ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION = "android.net.wifi.action.WIFI_NETWORK_SUGGESTION_POST_CONNECTION";
+    field public static final String ACTION_WIFI_SCAN_AVAILABLE = "android.net.wifi.action.WIFI_SCAN_AVAILABLE";
     field @Deprecated public static final int ERROR_AUTHENTICATING = 1; // 0x1
     field @Deprecated public static final String EXTRA_BSSID = "bssid";
     field public static final String EXTRA_NETWORK_INFO = "networkInfo";
@@ -30498,6 +31157,7 @@
     field @Deprecated public static final String EXTRA_NEW_STATE = "newState";
     field public static final String EXTRA_PREVIOUS_WIFI_STATE = "previous_wifi_state";
     field public static final String EXTRA_RESULTS_UPDATED = "resultsUpdated";
+    field public static final String EXTRA_SCAN_AVAILABLE = "android.net.wifi.extra.SCAN_AVAILABLE";
     field @Deprecated public static final String EXTRA_SUPPLICANT_CONNECTED = "connected";
     field @Deprecated public static final String EXTRA_SUPPLICANT_ERROR = "supplicantError";
     field @Deprecated public static final String EXTRA_WIFI_INFO = "wifiInfo";
@@ -30508,6 +31168,8 @@
     field public static final String SCAN_RESULTS_AVAILABLE_ACTION = "android.net.wifi.SCAN_RESULTS";
     field public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_DUPLICATE = 3; // 0x3
     field public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_EXCEEDS_MAX_PER_APP = 4; // 0x4
+    field public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_INVALID = 7; // 0x7
+    field public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_NOT_ALLOWED = 6; // 0x6
     field public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_APP_DISALLOWED = 2; // 0x2
     field public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL = 1; // 0x1
     field public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID = 5; // 0x5
@@ -30549,7 +31211,8 @@
 
   public class WifiManager.LocalOnlyHotspotReservation implements java.lang.AutoCloseable {
     method public void close();
-    method public android.net.wifi.WifiConfiguration getWifiConfiguration();
+    method @NonNull public android.net.wifi.SoftApConfiguration getSoftApConfiguration();
+    method @Deprecated @Nullable public android.net.wifi.WifiConfiguration getWifiConfiguration();
   }
 
   public class WifiManager.MulticastLock {
@@ -30614,15 +31277,17 @@
     ctor public WifiNetworkSuggestion.Builder();
     method @NonNull public android.net.wifi.WifiNetworkSuggestion build();
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setBssid(@NonNull android.net.MacAddress);
+    method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setCredentialSharedWithUser(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsAppInteractionRequired(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsEnhancedOpen(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsHiddenSsid(boolean);
+    method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsInitialAutoJoinEnabled(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsMetered(boolean);
-    method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsUserAllowedToManuallyConnect(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsUserInteractionRequired(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPasspointConfig(@NonNull android.net.wifi.hotspot2.PasspointConfiguration);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPriority(@IntRange(from=0) int);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setSsid(@NonNull String);
+    method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setUntrusted(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setWapiEnterpriseConfig(@NonNull android.net.wifi.WifiEnterpriseConfig);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setWapiPassphrase(@NonNull String);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setWpa2EnterpriseConfig(@NonNull android.net.wifi.WifiEnterpriseConfig);
@@ -31118,8 +31783,10 @@
     method public int getControlPort();
     method public int getDeviceType();
     method public int getMaxThroughput();
+    method public boolean isContentProtectionSupported();
     method public boolean isSessionAvailable();
     method public boolean isWfdEnabled();
+    method public void setContentProtectionSupported(boolean);
     method public void setControlPort(int);
     method public boolean setDeviceType(int);
     method public void setMaxThroughput(int);
@@ -31276,7 +31943,7 @@
     method public boolean isLciSubelementValid();
     method public boolean isZaxisSubelementValid();
     method @Nullable public android.location.Address toCivicLocationAddress();
-    method @Nullable public android.util.SparseArray toCivicLocationSparseArray();
+    method @Nullable public android.util.SparseArray<java.lang.String> toCivicLocationSparseArray();
     method @NonNull public android.location.Location toLocation();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int ALTITUDE_FLOORS = 2; // 0x2
@@ -35684,7 +36351,9 @@
     method public int describeContents();
     method @Nullable public android.os.PersistableBundle getPersistableBundle(@Nullable String);
     method public void putPersistableBundle(@Nullable String, @Nullable android.os.PersistableBundle);
+    method @NonNull public static android.os.PersistableBundle readFromStream(@NonNull java.io.InputStream) throws java.io.IOException;
     method public void writeToParcel(android.os.Parcel, int);
+    method public void writeToStream(@NonNull java.io.OutputStream) throws java.io.IOException;
     field @NonNull public static final android.os.Parcelable.Creator<android.os.PersistableBundle> CREATOR;
     field public static final android.os.PersistableBundle EMPTY;
   }
@@ -35786,6 +36455,7 @@
     field public static final int THREAD_PRIORITY_URGENT_AUDIO = -19; // 0xffffffed
     field public static final int THREAD_PRIORITY_URGENT_DISPLAY = -8; // 0xfffffff8
     field public static final int THREAD_PRIORITY_VIDEO = -10; // 0xfffffff6
+    field public static final int WIFI_UID = 1010; // 0x3f2
   }
 
   public abstract class ProxyFileDescriptorCallback {
@@ -36011,25 +36681,27 @@
     method public static android.content.Intent createUserCreationIntent(@Nullable String, @Nullable String, @Nullable String, @Nullable android.os.PersistableBundle);
     method @WorkerThread public android.os.Bundle getApplicationRestrictions(String);
     method public long getSerialNumberForUser(android.os.UserHandle);
-    method public int getUserCount();
+    method @RequiresPermission("android.permission.MANAGE_USERS") public int getUserCount();
     method public long getUserCreationTime(android.os.UserHandle);
     method public android.os.UserHandle getUserForSerialNumber(long);
-    method public String getUserName();
+    method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}, conditional=true) public String getUserName();
     method public java.util.List<android.os.UserHandle> getUserProfiles();
     method public android.os.Bundle getUserRestrictions();
     method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.os.Bundle getUserRestrictions(android.os.UserHandle);
     method public boolean hasUserRestriction(String);
     method public boolean isDemoUser();
+    method public boolean isManagedProfile();
     method public boolean isQuietModeEnabled(android.os.UserHandle);
     method public boolean isSystemUser();
     method public boolean isUserAGoat();
-    method public boolean isUserRunning(android.os.UserHandle);
-    method public boolean isUserRunningOrStopping(android.os.UserHandle);
+    method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public boolean isUserRunning(android.os.UserHandle);
+    method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public boolean isUserRunningOrStopping(android.os.UserHandle);
     method public boolean isUserUnlocked();
-    method public boolean isUserUnlocked(android.os.UserHandle);
-    method public boolean requestQuietModeEnabled(boolean, @NonNull android.os.UserHandle);
+    method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public boolean isUserUnlocked(android.os.UserHandle);
+    method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.MODIFY_QUIET_MODE"}, conditional=true) public boolean requestQuietModeEnabled(boolean, @NonNull android.os.UserHandle);
+    method public boolean requestQuietModeEnabled(boolean, @NonNull android.os.UserHandle, int);
     method @Deprecated public boolean setRestrictionsChallenge(String);
-    method @Deprecated public void setUserRestriction(String, boolean);
+    method @Deprecated @RequiresPermission("android.permission.MANAGE_USERS") public void setUserRestriction(String, boolean);
     method @Deprecated public void setUserRestrictions(android.os.Bundle);
     method @Deprecated public void setUserRestrictions(android.os.Bundle, android.os.UserHandle);
     method public static boolean supportsMultipleUsers();
@@ -36089,6 +36761,7 @@
     field public static final String DISALLOW_USER_SWITCH = "no_user_switch";
     field public static final String ENSURE_VERIFY_APPS = "ensure_verify_apps";
     field public static final String KEY_RESTRICTIONS_PENDING = "restrictions_pending";
+    field public static final int QUIET_MODE_DISABLE_ONLY_IF_CREDENTIAL_NOT_REQUIRED = 1; // 0x1
     field public static final int USER_CREATION_FAILED_NOT_PERMITTED = 1; // 0x1
     field public static final int USER_CREATION_FAILED_NO_MORE_USERS = 2; // 0x2
     field public static final int USER_OPERATION_ERROR_CURRENT_USER = 4; // 0x4
@@ -36352,15 +37025,22 @@
     method public boolean isObbMounted(String);
     method public boolean mountObb(String, String, android.os.storage.OnObbStateChangeListener);
     method @NonNull public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback, android.os.Handler) throws java.io.IOException;
+    method public void registerStorageVolumeCallback(@NonNull java.util.concurrent.Executor, @NonNull android.os.storage.StorageManager.StorageVolumeCallback);
     method public void setCacheBehaviorGroup(java.io.File, boolean) throws java.io.IOException;
     method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException;
     method public boolean unmountObb(String, boolean, android.os.storage.OnObbStateChangeListener);
+    method public void unregisterStorageVolumeCallback(@NonNull android.os.storage.StorageManager.StorageVolumeCallback);
     field public static final String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE";
     field public static final String EXTRA_REQUESTED_BYTES = "android.os.storage.extra.REQUESTED_BYTES";
     field public static final String EXTRA_UUID = "android.os.storage.extra.UUID";
     field public static final java.util.UUID UUID_DEFAULT;
   }
 
+  public static class StorageManager.StorageVolumeCallback {
+    ctor public StorageManager.StorageVolumeCallback();
+    method public void onStateChanged(@NonNull android.os.storage.StorageVolume);
+  }
+
   public final class StorageVolume implements android.os.Parcelable {
     method @Deprecated @Nullable public android.content.Intent createAccessIntent(String);
     method @NonNull public android.content.Intent createOpenDocumentTreeIntent();
@@ -38864,7 +39544,7 @@
     field public static final String COLUMN_MIME_TYPE = "mime_type";
     field public static final String COLUMN_SIZE = "_size";
     field public static final String COLUMN_SUMMARY = "summary";
-    field public static final int FLAG_DIR_BLOCKS_TREE = 32768; // 0x8000
+    field public static final int FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE = 32768; // 0x8000
     field public static final int FLAG_DIR_PREFERS_GRID = 16; // 0x10
     field public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 32; // 0x20
     field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8
@@ -39036,6 +39716,7 @@
     method @NonNull public static android.app.PendingIntent createWriteRequest(@NonNull android.content.ContentResolver, @NonNull java.util.Collection<android.net.Uri>);
     method @Nullable public static android.net.Uri getDocumentUri(@NonNull android.content.Context, @NonNull android.net.Uri);
     method @NonNull public static java.util.Set<java.lang.String> getExternalVolumeNames(@NonNull android.content.Context);
+    method public static long getGeneration(@NonNull android.content.Context, @NonNull String);
     method public static android.net.Uri getMediaScannerUri();
     method @Nullable public static android.net.Uri getMediaUri(@NonNull android.content.Context, @NonNull android.net.Uri);
     method @NonNull public static java.util.Set<java.lang.String> getRecentExternalVolumeNames(@NonNull android.content.Context);
@@ -39258,6 +39939,7 @@
   public static interface MediaStore.Files.FileColumns extends android.provider.MediaStore.MediaColumns {
     field public static final String MEDIA_TYPE = "media_type";
     field public static final int MEDIA_TYPE_AUDIO = 2; // 0x2
+    field public static final int MEDIA_TYPE_DOCUMENT = 6; // 0x6
     field public static final int MEDIA_TYPE_IMAGE = 1; // 0x1
     field public static final int MEDIA_TYPE_NONE = 0; // 0x0
     field public static final int MEDIA_TYPE_PLAYLIST = 4; // 0x4
@@ -39281,6 +39963,7 @@
     field @Deprecated public static final String LONGITUDE = "longitude";
     field @Deprecated public static final String MINI_THUMB_MAGIC = "mini_thumb_magic";
     field @Deprecated public static final String PICASA_ID = "picasa_id";
+    field public static final String SCENE_CAPTURE_TYPE = "scene_capture_type";
   }
 
   public static final class MediaStore.Images.Media implements android.provider.MediaStore.Images.ImageColumns {
@@ -39345,6 +40028,8 @@
     field public static final String DISPLAY_NAME = "_display_name";
     field public static final String DOCUMENT_ID = "document_id";
     field public static final String DURATION = "duration";
+    field public static final String GENERATION_ADDED = "generation_added";
+    field public static final String GENERATION_MODIFIED = "generation_modified";
     field public static final String GENRE = "genre";
     field public static final String HEIGHT = "height";
     field public static final String INSTANCE_ID = "instance_id";
@@ -39476,7 +40161,9 @@
     field public static final String ACTION_LOCALE_SETTINGS = "android.settings.LOCALE_SETTINGS";
     field public static final String ACTION_LOCATION_SOURCE_SETTINGS = "android.settings.LOCATION_SOURCE_SETTINGS";
     field public static final String ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS = "android.settings.MANAGE_ALL_APPLICATIONS_SETTINGS";
+    field public static final String ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_ALL_FILES_ACCESS_PERMISSION";
     field public static final String ACTION_MANAGE_APPLICATIONS_SETTINGS = "android.settings.MANAGE_APPLICATIONS_SETTINGS";
+    field public static final String ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION";
     field public static final String ACTION_MANAGE_DEFAULT_APPS_SETTINGS = "android.settings.MANAGE_DEFAULT_APPS_SETTINGS";
     field public static final String ACTION_MANAGE_OVERLAY_PERMISSION = "android.settings.action.MANAGE_OVERLAY_PERMISSION";
     field public static final String ACTION_MANAGE_UNKNOWN_APP_SOURCES = "android.settings.MANAGE_UNKNOWN_APP_SOURCES";
@@ -39494,6 +40181,7 @@
     field public static final String ACTION_PRINT_SETTINGS = "android.settings.ACTION_PRINT_SETTINGS";
     field public static final String ACTION_PRIVACY_SETTINGS = "android.settings.PRIVACY_SETTINGS";
     field public static final String ACTION_PROCESS_WIFI_EASY_CONNECT_URI = "android.settings.PROCESS_WIFI_EASY_CONNECT_URI";
+    field public static final String ACTION_QUICK_ACCESS_WALLET_SETTINGS = "android.settings.QUICK_ACCESS_WALLET_SETTINGS";
     field public static final String ACTION_QUICK_LAUNCH_SETTINGS = "android.settings.QUICK_LAUNCH_SETTINGS";
     field public static final String ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
     field public static final String ACTION_REQUEST_SET_AUTOFILL_SERVICE = "android.settings.REQUEST_SET_AUTOFILL_SERVICE";
@@ -39539,8 +40227,8 @@
     field public static final String EXTRA_INPUT_METHOD_ID = "input_method_id";
     field public static final String EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME = "android.provider.extra.NOTIFICATION_LISTENER_COMPONENT_NAME";
     field public static final String EXTRA_SUB_ID = "android.provider.extra.SUB_ID";
-    field public static final String EXTRA_WIFI_CONFIGURATION_LIST = "android.provider.extra.WIFI_CONFIGURATION_LIST";
-    field public static final String EXTRA_WIFI_CONFIGURATION_RESULT_LIST = "android.provider.extra.WIFI_CONFIGURATION_RESULT_LIST";
+    field public static final String EXTRA_WIFI_NETWORK_LIST = "android.provider.extra.WIFI_NETWORK_LIST";
+    field public static final String EXTRA_WIFI_NETWORK_RESULT_LIST = "android.provider.extra.WIFI_NETWORK_RESULT_LIST";
     field public static final String INTENT_CATEGORY_USAGE_ACCESS_CONFIG = "android.intent.category.USAGE_ACCESS_CONFIG";
     field public static final String METADATA_USAGE_ACCESS_REASON = "android.settings.metadata.USAGE_ACCESS_REASON";
   }
@@ -41409,6 +42097,7 @@
   public final class SEService {
     ctor public SEService(@NonNull android.content.Context, @NonNull java.util.concurrent.Executor, @NonNull android.se.omapi.SEService.OnConnectedListener);
     method @NonNull public android.se.omapi.Reader[] getReaders();
+    method @NonNull public android.se.omapi.Reader getUiccReader(int);
     method @NonNull public String getVersion();
     method public boolean isConnected();
     method public void shutdown();
@@ -41553,6 +42242,138 @@
 
 }
 
+package android.security.identity {
+
+  public class AccessControlProfile {
+  }
+
+  public static final class AccessControlProfile.Builder {
+    ctor public AccessControlProfile.Builder(@NonNull android.security.identity.AccessControlProfileId);
+    method @NonNull public android.security.identity.AccessControlProfile build();
+    method @NonNull public android.security.identity.AccessControlProfile.Builder setReaderCertificate(@NonNull java.security.cert.X509Certificate);
+    method @NonNull public android.security.identity.AccessControlProfile.Builder setUserAuthenticationRequired(boolean);
+    method @NonNull public android.security.identity.AccessControlProfile.Builder setUserAuthenticationTimeout(long);
+  }
+
+  public class AccessControlProfileId {
+    ctor public AccessControlProfileId(int);
+    method public int getId();
+  }
+
+  public class AlreadyPersonalizedException extends android.security.identity.IdentityCredentialException {
+    ctor public AlreadyPersonalizedException(@NonNull String);
+    ctor public AlreadyPersonalizedException(@NonNull String, @NonNull Throwable);
+  }
+
+  public class CipherSuiteNotSupportedException extends android.security.identity.IdentityCredentialException {
+    ctor public CipherSuiteNotSupportedException(@NonNull String);
+    ctor public CipherSuiteNotSupportedException(@NonNull String, @NonNull Throwable);
+  }
+
+  public class DocTypeNotSupportedException extends android.security.identity.IdentityCredentialException {
+    ctor public DocTypeNotSupportedException(@NonNull String);
+    ctor public DocTypeNotSupportedException(@NonNull String, @NonNull Throwable);
+  }
+
+  public class EphemeralPublicKeyNotFoundException extends android.security.identity.IdentityCredentialException {
+    ctor public EphemeralPublicKeyNotFoundException(@NonNull String);
+    ctor public EphemeralPublicKeyNotFoundException(@NonNull String, @NonNull Throwable);
+  }
+
+  public abstract class IdentityCredential {
+    method @NonNull public abstract java.security.KeyPair createEphemeralKeyPair();
+    method @NonNull public abstract byte[] decryptMessageFromReader(@NonNull byte[]) throws android.security.identity.MessageDecryptionException;
+    method @NonNull public abstract byte[] encryptMessageToReader(@NonNull byte[]);
+    method @NonNull public abstract java.util.Collection<java.security.cert.X509Certificate> getAuthKeysNeedingCertification();
+    method @NonNull public abstract int[] getAuthenticationDataUsageCount();
+    method @NonNull public abstract java.util.Collection<java.security.cert.X509Certificate> getCredentialKeyCertificateChain();
+    method @NonNull public abstract android.security.identity.ResultData getEntries(@Nullable byte[], @NonNull java.util.Map<java.lang.String,java.util.Collection<java.lang.String>>, @Nullable byte[], @Nullable byte[]) throws android.security.identity.EphemeralPublicKeyNotFoundException, android.security.identity.InvalidReaderSignatureException, android.security.identity.InvalidRequestMessageException, android.security.identity.NoAuthenticationKeyAvailableException, android.security.identity.SessionTranscriptMismatchException;
+    method public abstract void setAllowUsingExhaustedKeys(boolean);
+    method public abstract void setAvailableAuthenticationKeys(int, int);
+    method public abstract void setReaderEphemeralPublicKey(@NonNull java.security.PublicKey) throws java.security.InvalidKeyException;
+    method public abstract void storeStaticAuthenticationData(@NonNull java.security.cert.X509Certificate, @NonNull byte[]) throws android.security.identity.UnknownAuthenticationKeyException;
+  }
+
+  public class IdentityCredentialException extends java.lang.Exception {
+    ctor public IdentityCredentialException(@NonNull String);
+    ctor public IdentityCredentialException(@NonNull String, @NonNull Throwable);
+  }
+
+  public abstract class IdentityCredentialStore {
+    method @NonNull public abstract android.security.identity.WritableIdentityCredential createCredential(@NonNull String, @NonNull String) throws android.security.identity.AlreadyPersonalizedException, android.security.identity.DocTypeNotSupportedException;
+    method @Nullable public abstract byte[] deleteCredentialByName(@NonNull String);
+    method @Nullable public abstract android.security.identity.IdentityCredential getCredentialByName(@NonNull String, int) throws android.security.identity.CipherSuiteNotSupportedException;
+    method @Nullable public static android.security.identity.IdentityCredentialStore getDirectAccessInstance(@NonNull android.content.Context);
+    method @Nullable public static android.security.identity.IdentityCredentialStore getInstance(@NonNull android.content.Context);
+    method @NonNull public abstract String[] getSupportedDocTypes();
+    field public static final int CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256 = 1; // 0x1
+  }
+
+  public class InvalidReaderSignatureException extends android.security.identity.IdentityCredentialException {
+    ctor public InvalidReaderSignatureException(@NonNull String);
+    ctor public InvalidReaderSignatureException(@NonNull String, @NonNull Throwable);
+  }
+
+  public class InvalidRequestMessageException extends android.security.identity.IdentityCredentialException {
+    ctor public InvalidRequestMessageException(@NonNull String);
+    ctor public InvalidRequestMessageException(@NonNull String, @NonNull Throwable);
+  }
+
+  public class MessageDecryptionException extends android.security.identity.IdentityCredentialException {
+    ctor public MessageDecryptionException(@NonNull String);
+    ctor public MessageDecryptionException(@NonNull String, @NonNull Throwable);
+  }
+
+  public class NoAuthenticationKeyAvailableException extends android.security.identity.IdentityCredentialException {
+    ctor public NoAuthenticationKeyAvailableException(@NonNull String);
+    ctor public NoAuthenticationKeyAvailableException(@NonNull String, @NonNull Throwable);
+  }
+
+  public class PersonalizationData {
+  }
+
+  public static final class PersonalizationData.Builder {
+    ctor public PersonalizationData.Builder();
+    method @NonNull public android.security.identity.PersonalizationData.Builder addAccessControlProfile(@NonNull android.security.identity.AccessControlProfile);
+    method @NonNull public android.security.identity.PersonalizationData build();
+    method @NonNull public android.security.identity.PersonalizationData.Builder setEntry(@NonNull String, @NonNull String, @NonNull java.util.Collection<android.security.identity.AccessControlProfileId>, @NonNull byte[]);
+  }
+
+  public abstract class ResultData {
+    method @NonNull public abstract byte[] getAuthenticatedData();
+    method @Nullable public abstract byte[] getEntry(@NonNull String, @NonNull String);
+    method @Nullable public abstract java.util.Collection<java.lang.String> getEntryNames(@NonNull String);
+    method @Nullable public abstract byte[] getMessageAuthenticationCode();
+    method @NonNull public abstract java.util.Collection<java.lang.String> getNamespaceNames();
+    method @Nullable public abstract java.util.Collection<java.lang.String> getRetrievedEntryNames(@NonNull String);
+    method @NonNull public abstract byte[] getStaticAuthenticationData();
+    method public abstract int getStatus(@NonNull String, @NonNull String);
+    field public static final int STATUS_NOT_IN_REQUEST_MESSAGE = 3; // 0x3
+    field public static final int STATUS_NOT_REQUESTED = 2; // 0x2
+    field public static final int STATUS_NO_ACCESS_CONTROL_PROFILES = 6; // 0x6
+    field public static final int STATUS_NO_SUCH_ENTRY = 1; // 0x1
+    field public static final int STATUS_OK = 0; // 0x0
+    field public static final int STATUS_READER_AUTHENTICATION_FAILED = 5; // 0x5
+    field public static final int STATUS_USER_AUTHENTICATION_FAILED = 4; // 0x4
+  }
+
+  public class SessionTranscriptMismatchException extends android.security.identity.IdentityCredentialException {
+    ctor public SessionTranscriptMismatchException(@NonNull String);
+    ctor public SessionTranscriptMismatchException(@NonNull String, @NonNull Throwable);
+  }
+
+  public class UnknownAuthenticationKeyException extends android.security.identity.IdentityCredentialException {
+    ctor public UnknownAuthenticationKeyException(@NonNull String);
+    ctor public UnknownAuthenticationKeyException(@NonNull String, @NonNull Throwable);
+  }
+
+  public abstract class WritableIdentityCredential {
+    method @NonNull public abstract java.util.Collection<java.security.cert.X509Certificate> getCredentialKeyCertificateChain(@NonNull byte[]);
+    method @NonNull public abstract byte[] personalize(@NonNull android.security.identity.PersonalizationData);
+  }
+
+}
+
 package android.security.keystore {
 
   public class KeyExpiredException extends java.security.InvalidKeyException {
@@ -41931,13 +42752,13 @@
     method @NonNull public android.service.autofill.FillResponse build();
     method @NonNull public android.service.autofill.FillResponse.Builder disableAutofill(long);
     method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews);
-    method @NonNull public android.service.autofill.FillResponse.Builder setCancelTargetIds(@Nullable int[]);
     method @NonNull public android.service.autofill.FillResponse.Builder setClientState(@Nullable android.os.Bundle);
     method @NonNull public android.service.autofill.FillResponse.Builder setFieldClassificationIds(@NonNull android.view.autofill.AutofillId...);
     method @NonNull public android.service.autofill.FillResponse.Builder setFlags(int);
     method @NonNull public android.service.autofill.FillResponse.Builder setFooter(@NonNull android.widget.RemoteViews);
     method @NonNull public android.service.autofill.FillResponse.Builder setHeader(@NonNull android.widget.RemoteViews);
     method @NonNull public android.service.autofill.FillResponse.Builder setIgnoredIds(android.view.autofill.AutofillId...);
+    method @NonNull public android.service.autofill.FillResponse.Builder setPresentationCancelIds(@Nullable int[]);
     method @NonNull public android.service.autofill.FillResponse.Builder setSaveInfo(@NonNull android.service.autofill.SaveInfo);
     method @NonNull public android.service.autofill.FillResponse.Builder setUserData(@NonNull android.service.autofill.UserData);
   }
@@ -42536,6 +43357,94 @@
 
 }
 
+package android.service.quickaccesswallet {
+
+  public final class GetWalletCardsCallback {
+    method public void onFailure(@NonNull android.service.quickaccesswallet.GetWalletCardsError);
+    method public void onSuccess(@NonNull android.service.quickaccesswallet.GetWalletCardsResponse);
+  }
+
+  public final class GetWalletCardsError implements android.os.Parcelable {
+    ctor public GetWalletCardsError(@Nullable android.graphics.drawable.Icon, @Nullable CharSequence);
+    method public int describeContents();
+    method @Nullable public android.graphics.drawable.Icon getIcon();
+    method @Nullable public CharSequence getMessage();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.quickaccesswallet.GetWalletCardsError> CREATOR;
+  }
+
+  public final class GetWalletCardsRequest implements android.os.Parcelable {
+    ctor public GetWalletCardsRequest(int, int, int, int);
+    method public int describeContents();
+    method public int getCardHeightPx();
+    method public int getCardWidthPx();
+    method public int getIconSizePx();
+    method public int getMaxCards();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.quickaccesswallet.GetWalletCardsRequest> CREATOR;
+  }
+
+  public final class GetWalletCardsResponse implements android.os.Parcelable {
+    ctor public GetWalletCardsResponse(@NonNull java.util.List<android.service.quickaccesswallet.WalletCard>, int);
+    method public int describeContents();
+    method public int getSelectedIndex();
+    method @NonNull public java.util.List<android.service.quickaccesswallet.WalletCard> getWalletCards();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.quickaccesswallet.GetWalletCardsResponse> CREATOR;
+  }
+
+  public abstract class QuickAccessWalletService extends android.app.Service {
+    ctor public QuickAccessWalletService();
+    method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
+    method public abstract void onWalletCardSelected(@NonNull android.service.quickaccesswallet.SelectWalletCardRequest);
+    method public abstract void onWalletCardsRequested(@NonNull android.service.quickaccesswallet.GetWalletCardsRequest, @NonNull android.service.quickaccesswallet.GetWalletCardsCallback);
+    method public abstract void onWalletDismissed();
+    method public final void sendWalletServiceEvent(@NonNull android.service.quickaccesswallet.WalletServiceEvent);
+    field public static final String ACTION_DISMISS_WALLET = "android.service.quickaccesswallet.action.DISMISS_WALLET";
+    field public static final String ACTION_VIEW_WALLET = "android.service.quickaccesswallet.action.VIEW_WALLET";
+    field public static final String ACTION_VIEW_WALLET_SETTINGS = "android.service.quickaccesswallet.action.VIEW_WALLET_SETTINGS";
+    field public static final String SERVICE_INTERFACE = "android.service.quickaccesswallet.QuickAccessWalletService";
+    field public static final String SERVICE_META_DATA = "android.quickaccesswallet";
+  }
+
+  public final class SelectWalletCardRequest implements android.os.Parcelable {
+    ctor public SelectWalletCardRequest(@NonNull String);
+    method public int describeContents();
+    method @NonNull public String getCardId();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.quickaccesswallet.SelectWalletCardRequest> CREATOR;
+  }
+
+  public final class WalletCard implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.graphics.drawable.Icon getCardIcon();
+    method @NonNull public String getCardId();
+    method @NonNull public android.graphics.drawable.Icon getCardImage();
+    method @Nullable public CharSequence getCardLabel();
+    method @NonNull public CharSequence getContentDescription();
+    method @NonNull public android.app.PendingIntent getPendingIntent();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.quickaccesswallet.WalletCard> CREATOR;
+  }
+
+  public static final class WalletCard.Builder {
+    ctor public WalletCard.Builder(@NonNull String, @NonNull android.graphics.drawable.Icon, @NonNull CharSequence, @NonNull android.app.PendingIntent);
+    method @NonNull public android.service.quickaccesswallet.WalletCard build();
+    method @NonNull public android.service.quickaccesswallet.WalletCard.Builder setCardIcon(@Nullable android.graphics.drawable.Icon);
+    method @NonNull public android.service.quickaccesswallet.WalletCard.Builder setCardLabel(@Nullable CharSequence);
+  }
+
+  public final class WalletServiceEvent implements android.os.Parcelable {
+    ctor public WalletServiceEvent(int);
+    method public int describeContents();
+    method public int getEventType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.quickaccesswallet.WalletServiceEvent> CREATOR;
+    field public static final int TYPE_NFC_PAYMENT_STARTED = 1; // 0x1
+  }
+
+}
+
 package android.service.quicksettings {
 
   public final class Tile implements android.os.Parcelable {
@@ -42620,17 +43529,26 @@
     method public android.content.Intent createEnrollIntent();
     method public android.content.Intent createReEnrollIntent();
     method public android.content.Intent createUnEnrollIntent();
+    method public int getParameter(int);
+    method public int getSupportedAudioCapabilities();
     method public int getSupportedRecognitionModes();
+    method @Nullable public android.service.voice.AlwaysOnHotwordDetector.ModelParamRange queryParameter(int);
+    method public int setParameter(int, int);
     method public boolean startRecognition(int);
     method public boolean stopRecognition();
+    field public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 1; // 0x1
+    field public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2
+    field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0
     field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2
     field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1
+    field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 4; // 0x4
+    field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 8; // 0x8
     field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2
     field public static final int RECOGNITION_MODE_VOICE_TRIGGER = 1; // 0x1
     field public static final int STATE_HARDWARE_UNAVAILABLE = -2; // 0xfffffffe
     field public static final int STATE_KEYPHRASE_ENROLLED = 2; // 0x2
     field public static final int STATE_KEYPHRASE_UNENROLLED = 1; // 0x1
-    field public static final int STATE_KEYPHRASE_UNSUPPORTED = -1; // 0xffffffff
+    field @Deprecated public static final int STATE_KEYPHRASE_UNSUPPORTED = -1; // 0xffffffff
   }
 
   public abstract static class AlwaysOnHotwordDetector.Callback {
@@ -42647,6 +43565,11 @@
     method @Nullable public byte[] getTriggerAudio();
   }
 
+  public static final class AlwaysOnHotwordDetector.ModelParamRange {
+    method public int end();
+    method public int start();
+  }
+
   public class VoiceInteractionService extends android.app.Service {
     ctor public VoiceInteractionService();
     method public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback);
@@ -42844,6 +43767,7 @@
     method public void onSurfaceRedrawNeeded(android.view.SurfaceHolder);
     method public void onTouchEvent(android.view.MotionEvent);
     method public void onVisibilityChanged(boolean);
+    method public void onZoomChanged(@FloatRange(from=0.0f, to=1.0f) float);
     method public void setOffsetNotificationsEnabled(boolean);
     method public void setTouchEventsEnabled(boolean);
   }
@@ -43861,6 +44785,7 @@
     method public java.util.List<android.telecom.Call> getChildren();
     method public java.util.List<android.telecom.Call> getConferenceableCalls();
     method public android.telecom.Call.Details getDetails();
+    method @Nullable public android.telecom.Call getGenericConferenceActiveChildCall();
     method public android.telecom.Call getParent();
     method public String getRemainingPostDialSequence();
     method @Nullable public android.telecom.Call.RttCall getRttCall();
@@ -43944,6 +44869,7 @@
     method public int getCallerDisplayNamePresentation();
     method public int getCallerNumberVerificationStatus();
     method public final long getConnectTimeMillis();
+    method @Nullable public String getContactDisplayName();
     method public long getCreationTimeMillis();
     method public android.telecom.DisconnectCause getDisconnectCause();
     method public android.os.Bundle getExtras();
@@ -43985,6 +44911,7 @@
     field public static final int PROPERTY_GENERIC_CONFERENCE = 2; // 0x2
     field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 128; // 0x80
     field public static final int PROPERTY_HIGH_DEF_AUDIO = 16; // 0x10
+    field public static final int PROPERTY_IS_ADHOC_CONFERENCE = 8192; // 0x2000
     field public static final int PROPERTY_IS_EXTERNAL_CALL = 64; // 0x40
     field public static final int PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL = 2048; // 0x800
     field public static final int PROPERTY_RTT = 1024; // 0x400
@@ -44062,6 +44989,7 @@
   public abstract class Conference extends android.telecom.Conferenceable {
     ctor public Conference(android.telecom.PhoneAccountHandle);
     method public final boolean addConnection(android.telecom.Connection);
+    method @NonNull public static android.telecom.Conference createFailedConference(@NonNull android.telecom.DisconnectCause, @NonNull android.telecom.PhoneAccountHandle);
     method public final void destroy();
     method public final android.telecom.CallAudioState getCallAudioState();
     method public final java.util.List<android.telecom.Connection> getConferenceableConnections();
@@ -44076,6 +45004,8 @@
     method public final android.telecom.StatusHints getStatusHints();
     method public android.telecom.Connection.VideoProvider getVideoProvider();
     method public int getVideoState();
+    method public final boolean isRingbackRequested();
+    method public void onAnswer(int);
     method public void onCallAudioStateChanged(android.telecom.CallAudioState);
     method public void onConnectionAdded(android.telecom.Connection);
     method public void onDisconnect();
@@ -44084,6 +45014,7 @@
     method public void onMerge(android.telecom.Connection);
     method public void onMerge();
     method public void onPlayDtmfTone(char);
+    method public void onReject();
     method public void onSeparate(android.telecom.Connection);
     method public void onStopDtmfTone();
     method public void onSwap();
@@ -44103,6 +45034,8 @@
     method public final void setDisconnected(android.telecom.DisconnectCause);
     method public final void setExtras(@Nullable android.os.Bundle);
     method public final void setOnHold();
+    method public final void setRingbackRequested(boolean);
+    method public final void setRinging();
     method public final void setStatusHints(android.telecom.StatusHints);
     method public final void setVideoProvider(android.telecom.Connection, android.telecom.Connection.VideoProvider);
     method public final void setVideoState(android.telecom.Connection, int);
@@ -44260,6 +45193,7 @@
     field public static final int PROPERTY_ASSISTED_DIALING_USED = 512; // 0x200
     field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 32; // 0x20
     field public static final int PROPERTY_HIGH_DEF_AUDIO = 4; // 0x4
+    field public static final int PROPERTY_IS_ADHOC_CONFERENCE = 4096; // 0x1000
     field public static final int PROPERTY_IS_EXTERNAL_CALL = 16; // 0x10
     field public static final int PROPERTY_IS_RTT = 256; // 0x100
     field public static final int PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL = 1024; // 0x400
@@ -44332,8 +45266,10 @@
     method public android.telecom.PhoneAccountHandle getAccountHandle();
     method public android.net.Uri getAddress();
     method public android.os.Bundle getExtras();
+    method @Nullable public java.util.List<android.net.Uri> getParticipants();
     method public android.telecom.Connection.RttTextStream getRttTextStream();
     method public int getVideoState();
+    method public boolean isAdhocConferenceCall();
     method public boolean isRequestingRtt();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telecom.ConnectionRequest> CREATOR;
@@ -44353,9 +45289,13 @@
     method public void onConference(android.telecom.Connection, android.telecom.Connection);
     method public void onConnectionServiceFocusGained();
     method public void onConnectionServiceFocusLost();
+    method @Nullable public android.telecom.Conference onCreateIncomingConference(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest);
+    method public void onCreateIncomingConferenceFailed(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest);
     method public android.telecom.Connection onCreateIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
     method public void onCreateIncomingConnectionFailed(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
     method public android.telecom.Connection onCreateIncomingHandoverConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
+    method @Nullable public android.telecom.Conference onCreateOutgoingConference(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest);
+    method public void onCreateOutgoingConferenceFailed(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest);
     method public android.telecom.Connection onCreateOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
     method public void onCreateOutgoingConnectionFailed(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
     method public android.telecom.Connection onCreateOutgoingHandoverConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
@@ -44470,6 +45410,7 @@
     method public boolean supportsUriScheme(String);
     method public android.telecom.PhoneAccount.Builder toBuilder();
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final int CAPABILITY_ADHOC_CONFERENCE_CALLING = 16384; // 0x4000
     field public static final int CAPABILITY_CALL_PROVIDER = 2; // 0x2
     field public static final int CAPABILITY_CALL_SUBJECT = 64; // 0x40
     field public static final int CAPABILITY_CONNECTION_MANAGER = 1; // 0x1
@@ -44665,6 +45606,7 @@
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ANSWER_PHONE_CALLS, android.Manifest.permission.MODIFY_PHONE_STATE}) public void acceptRingingCall();
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ANSWER_PHONE_CALLS, android.Manifest.permission.MODIFY_PHONE_STATE}) public void acceptRingingCall(int);
     method public void addNewIncomingCall(android.telecom.PhoneAccountHandle, android.os.Bundle);
+    method public void addNewIncomingConference(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.os.Bundle);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void cancelMissedCallsNotification();
     method public android.content.Intent createManageBlockedNumbersIntent();
     method @Deprecated @RequiresPermission(android.Manifest.permission.ANSWER_PHONE_CALLS) public boolean endCall();
@@ -44692,6 +45634,7 @@
     method public void registerPhoneAccount(android.telecom.PhoneAccount);
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void showInCallScreen(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void silenceRinger();
+    method @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void startConference(@NonNull java.util.List<android.net.Uri>, @NonNull android.os.Bundle);
     method public void unregisterPhoneAccount(android.telecom.PhoneAccountHandle);
     field public static final String ACTION_CHANGE_DEFAULT_DIALER = "android.telecom.action.CHANGE_DEFAULT_DIALER";
     field public static final String ACTION_CHANGE_PHONE_ACCOUNTS = "android.telecom.action.CHANGE_PHONE_ACCOUNTS";
@@ -44790,6 +45733,8 @@
 package android.telephony {
 
   public final class AccessNetworkConstants {
+    field public static final int TRANSPORT_TYPE_WLAN = 2; // 0x2
+    field public static final int TRANSPORT_TYPE_WWAN = 1; // 0x1
   }
 
   public static final class AccessNetworkConstants.AccessNetworkType {
@@ -44937,6 +45882,12 @@
     field public static final int BAND_7 = 7; // 0x7
     field public static final int BAND_8 = 8; // 0x8
     field public static final int BAND_9 = 9; // 0x9
+    field public static final int BAND_A = 101; // 0x65
+    field public static final int BAND_B = 102; // 0x66
+    field public static final int BAND_C = 103; // 0x67
+    field public static final int BAND_D = 104; // 0x68
+    field public static final int BAND_E = 105; // 0x69
+    field public static final int BAND_F = 106; // 0x6a
   }
 
   public final class AvailableNetworkInfo implements android.os.Parcelable {
@@ -44953,6 +45904,39 @@
     field public static final int PRIORITY_MED = 2; // 0x2
   }
 
+  public final class BarringInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.telephony.BarringInfo.BarringServiceInfo getBarringServiceInfo(int);
+    method public boolean isServiceBarred(int);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int BARRING_SERVICE_TYPE_CS_FALLBACK = 5; // 0x5
+    field public static final int BARRING_SERVICE_TYPE_CS_SERVICE = 0; // 0x0
+    field public static final int BARRING_SERVICE_TYPE_CS_VOICE = 2; // 0x2
+    field public static final int BARRING_SERVICE_TYPE_EMERGENCY = 8; // 0x8
+    field public static final int BARRING_SERVICE_TYPE_MMTEL_VIDEO = 7; // 0x7
+    field public static final int BARRING_SERVICE_TYPE_MMTEL_VOICE = 6; // 0x6
+    field public static final int BARRING_SERVICE_TYPE_MO_DATA = 4; // 0x4
+    field public static final int BARRING_SERVICE_TYPE_MO_SIGNALLING = 3; // 0x3
+    field public static final int BARRING_SERVICE_TYPE_PS_SERVICE = 1; // 0x1
+    field public static final int BARRING_SERVICE_TYPE_SMS = 9; // 0x9
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.BarringInfo> CREATOR;
+  }
+
+  public static final class BarringInfo.BarringServiceInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getBarringType();
+    method public int getConditionalBarringFactor();
+    method public int getConditionalBarringTimeSeconds();
+    method public boolean isBarred();
+    method public boolean isConditionallyBarred();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int BARRING_TYPE_CONDITIONAL = 1; // 0x1
+    field public static final int BARRING_TYPE_NONE = 0; // 0x0
+    field public static final int BARRING_TYPE_UNCONDITIONAL = 2; // 0x2
+    field public static final int BARRING_TYPE_UNKNOWN = -1; // 0xffffffff
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.BarringInfo.BarringServiceInfo> CREATOR;
+  }
+
   public class CarrierConfigManager {
     method @Nullable public android.os.PersistableBundle getConfig();
     method @Nullable public android.os.PersistableBundle getConfigByComponentForSubId(@NonNull String, int);
@@ -44962,11 +45946,16 @@
     field public static final String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
     field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe
     field public static final int DATA_CYCLE_USE_PLATFORM_DEFAULT = -1; // 0xffffffff
+    field public static final String ENABLE_EAP_METHOD_PREFIX_BOOL = "enable_eap_method_prefix_bool";
     field public static final String EXTRA_SLOT_INDEX = "android.telephony.extra.SLOT_INDEX";
     field public static final String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX";
+    field public static final String IMSI_KEY_AVAILABILITY_INT = "imsi_key_availability_int";
+    field public static final String KEY_5G_ICON_CONFIGURATION_STRING = "5g_icon_configuration_string";
+    field public static final String KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT = "5g_icon_display_grace_period_sec_int";
     field public static final String KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY = "5g_nr_ssrsrp_thresholds_int_array";
     field public static final String KEY_5G_NR_SSRSRQ_THRESHOLDS_INT_ARRAY = "5g_nr_ssrsrq_thresholds_int_array";
     field public static final String KEY_5G_NR_SSSINR_THRESHOLDS_INT_ARRAY = "5g_nr_sssinr_thresholds_int_array";
+    field public static final String KEY_5G_WATCHDOG_TIME_MS_LONG = "5g_watchdog_time_long";
     field public static final String KEY_ADDITIONAL_CALL_SETTING_BOOL = "additional_call_setting_bool";
     field public static final String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool";
     field public static final String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL = "allow_add_call_during_video_call";
@@ -44977,18 +45966,32 @@
     field public static final String KEY_ALLOW_LOCAL_DTMF_TONES_BOOL = "allow_local_dtmf_tones_bool";
     field public static final String KEY_ALLOW_MERGE_WIFI_CALLS_WHEN_VOWIFI_OFF_BOOL = "allow_merge_wifi_calls_when_vowifi_off_bool";
     field public static final String KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL = "allow_non_emergency_calls_in_ecm_bool";
-    field public static final String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL = "always_show_emergency_alert_onoff_bool";
+    field public static final String KEY_ALLOW_VIDEO_CALLING_FALLBACK_BOOL = "allow_video_calling_fallback_bool";
+    field public static final String KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL = "always_show_data_rat_icon_bool";
+    field @Deprecated public static final String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL = "always_show_emergency_alert_onoff_bool";
+    field public static final String KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN = "always_show_primary_signal_bar_in_opportunistic_network_boolean";
     field public static final String KEY_APN_EXPAND_BOOL = "apn_expand_bool";
+    field public static final String KEY_APN_SETTINGS_DEFAULT_APN_TYPES_STRING_ARRAY = "apn_settings_default_apn_types_string_array";
     field public static final String KEY_AUTO_RETRY_ENABLED_BOOL = "auto_retry_enabled_bool";
     field public static final String KEY_CALL_BARRING_SUPPORTS_DEACTIVATE_ALL_BOOL = "call_barring_supports_deactivate_all_bool";
     field public static final String KEY_CALL_BARRING_SUPPORTS_PASSWORD_CHANGE_BOOL = "call_barring_supports_password_change_bool";
     field public static final String KEY_CALL_BARRING_VISIBILITY_BOOL = "call_barring_visibility_bool";
     field public static final String KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY = "call_forwarding_blocks_while_roaming_string_array";
+    field public static final String KEY_CALL_REDIRECTION_SERVICE_COMPONENT_NAME_STRING = "call_redirection_service_component_name_string";
+    field public static final String KEY_CARRIER_ALLOW_DEFLECT_IMS_CALL_BOOL = "carrier_allow_deflect_ims_call_bool";
     field public static final String KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL = "carrier_allow_turnoff_ims_bool";
     field public static final String KEY_CARRIER_APP_REQUIRED_DURING_SIM_SETUP_BOOL = "carrier_app_required_during_setup_bool";
     field public static final String KEY_CARRIER_CALL_SCREENING_APP_STRING = "call_screening_app";
+    field public static final String KEY_CARRIER_CERTIFICATE_STRING_ARRAY = "carrier_certificate_string_array";
+    field public static final String KEY_CARRIER_CONFIG_APPLIED_BOOL = "carrier_config_applied_bool";
     field public static final String KEY_CARRIER_CONFIG_VERSION_STRING = "carrier_config_version_string";
     field public static final String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS = "carrier_data_call_permanent_failure_strings";
+    field public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DCFAILURE_STRING_ARRAY = "carrier_default_actions_on_dcfailure_string_array";
+    field public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE = "carrier_default_actions_on_default_network_available_string_array";
+    field public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY = "carrier_default_actions_on_redirection_string_array";
+    field public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_RESET = "carrier_default_actions_on_reset_string_array";
+    field public static final String KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY = "carrier_default_redirection_url_string_array";
+    field public static final String KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL = "carrier_default_wfc_ims_enabled_bool";
     field public static final String KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT = "carrier_default_wfc_ims_mode_int";
     field public static final String KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT = "carrier_default_wfc_ims_roaming_mode_int";
     field @Deprecated public static final String KEY_CARRIER_FORCE_DISABLE_ETWS_CMAS_TEST_BOOL = "carrier_force_disable_etws_cmas_test_bool";
@@ -45000,11 +46003,14 @@
     field public static final String KEY_CARRIER_INSTANT_LETTERING_LENGTH_LIMIT_INT = "carrier_instant_lettering_length_limit_int";
     field public static final String KEY_CARRIER_NAME_OVERRIDE_BOOL = "carrier_name_override_bool";
     field public static final String KEY_CARRIER_NAME_STRING = "carrier_name_string";
+    field public static final String KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL = "carrier_rcs_provisioning_required_bool";
+    field public static final String KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING = "carrier_settings_activity_component_name_string";
     field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool";
     field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool";
     field public static final String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool";
     field public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL = "carrier_ut_provisioning_required_bool";
     field public static final String KEY_CARRIER_VOLTE_AVAILABLE_BOOL = "carrier_volte_available_bool";
+    field public static final String KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL = "carrier_volte_override_wfc_provisioning_bool";
     field public static final String KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool";
     field public static final String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool";
     field public static final String KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL = "carrier_volte_tty_supported_bool";
@@ -45018,6 +46024,7 @@
     field public static final String KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY = "cdma_nonroaming_networks_string_array";
     field public static final String KEY_CDMA_ROAMING_MODE_INT = "cdma_roaming_mode_int";
     field public static final String KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY = "cdma_roaming_networks_string_array";
+    field public static final String KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL = "check_pricing_with_carrier_data_roaming_bool";
     field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_BOOL = "ci_action_on_sys_update_bool";
     field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = "ci_action_on_sys_update_extra_string";
     field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING = "ci_action_on_sys_update_extra_val_string";
@@ -45027,10 +46034,12 @@
     field public static final String KEY_CONFIG_IMS_RCS_PACKAGE_OVERRIDE_STRING = "config_ims_rcs_package_override_string";
     field public static final String KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING = "config_plans_package_override_string";
     field public static final String KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL = "config_telephony_use_own_number_for_voicemail_bool";
+    field public static final String KEY_CONFIG_WIFI_DISABLE_IN_ECBM = "config_wifi_disable_in_ecbm";
     field public static final String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
     field public static final String KEY_DATA_LIMIT_NOTIFICATION_BOOL = "data_limit_notification_bool";
     field public static final String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long";
     field public static final String KEY_DATA_RAPID_NOTIFICATION_BOOL = "data_rapid_notification_bool";
+    field public static final String KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG = "data_switch_validation_timeout_long";
     field public static final String KEY_DATA_WARNING_NOTIFICATION_BOOL = "data_warning_notification_bool";
     field public static final String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long";
     field public static final String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string";
@@ -45038,6 +46047,7 @@
     field public static final String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string";
     field public static final String KEY_DIAL_STRING_REPLACE_STRING_ARRAY = "dial_string_replace_string_array";
     field public static final String KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL = "disable_cdma_activation_code_bool";
+    field public static final String KEY_DISABLE_CHARGE_INDICATION_BOOL = "disable_charge_indication_bool";
     field public static final String KEY_DISABLE_SUPPLEMENTARY_SERVICES_IN_AIRPLANE_MODE_BOOL = "disable_supplementary_services_in_airplane_mode_bool";
     field public static final String KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY = "disconnect_cause_play_busytone_int_array";
     field public static final String KEY_DISPLAY_HD_AUDIO_PROPERTY_BOOL = "display_hd_audio_property_bool";
@@ -45047,9 +46057,13 @@
     field public static final String KEY_EDITABLE_ENHANCED_4G_LTE_BOOL = "editable_enhanced_4g_lte_bool";
     field public static final String KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL = "editable_voicemail_number_bool";
     field public static final String KEY_EDITABLE_VOICEMAIL_NUMBER_SETTING_BOOL = "editable_voicemail_number_setting_bool";
+    field public static final String KEY_EDITABLE_WFC_MODE_BOOL = "editable_wfc_mode_bool";
+    field public static final String KEY_EDITABLE_WFC_ROAMING_MODE_BOOL = "editable_wfc_roaming_mode_bool";
+    field public static final String KEY_EMERGENCY_NOTIFICATION_DELAY_INT = "emergency_notification_delay_int";
     field public static final String KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY = "emergency_number_prefix_string_array";
     field public static final String KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL = "enable_dialer_key_vibration_bool";
     field public static final String KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL = "enhanced_4g_lte_on_by_default_bool";
+    field public static final String KEY_ENHANCED_4G_LTE_TITLE_VARIANT_INT = "enhanced_4g_lte_title_variant_int";
     field public static final String KEY_FORCE_HOME_NETWORK_BOOL = "force_home_network_bool";
     field public static final String KEY_GSM_DTMF_TONE_DELAY_INT = "gsm_dtmf_tone_delay_int";
     field public static final String KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY = "gsm_nonroaming_networks_string_array";
@@ -45058,13 +46072,17 @@
     field public static final String KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL = "hide_carrier_network_settings_bool";
     field public static final String KEY_HIDE_ENHANCED_4G_LTE_BOOL = "hide_enhanced_4g_lte_bool";
     field public static final String KEY_HIDE_IMS_APN_BOOL = "hide_ims_apn_bool";
+    field public static final String KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL = "hide_lte_plus_data_icon_bool";
     field public static final String KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL = "hide_preferred_network_type_bool";
     field public static final String KEY_HIDE_PRESET_APN_DETAILS_BOOL = "hide_preset_apn_details_bool";
     field public static final String KEY_HIDE_SIM_LOCK_SETTINGS_BOOL = "hide_sim_lock_settings_bool";
+    field public static final String KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS = "ignore_data_enabled_changed_for_video_calls";
+    field public static final String KEY_IGNORE_RTT_MODE_SETTING_BOOL = "ignore_rtt_mode_setting_bool";
     field public static final String KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL = "ignore_sim_network_locked_events_bool";
     field public static final String KEY_IMS_CONFERENCE_SIZE_LIMIT_INT = "ims_conference_size_limit_int";
     field public static final String KEY_IMS_DTMF_TONE_DELAY_INT = "ims_dtmf_tone_delay_int";
     field public static final String KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL = "is_ims_conference_size_enforced_bool";
+    field public static final String KEY_LTE_ENABLED_BOOL = "lte_enabled_bool";
     field public static final String KEY_LTE_RSRQ_THRESHOLDS_INT_ARRAY = "lte_rsrq_thresholds_int_array";
     field public static final String KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY = "lte_rssnr_thresholds_int_array";
     field public static final String KEY_MDN_IS_ADDITIONAL_VOICEMAIL_NUMBER_BOOL = "mdn_is_additional_voicemail_number_bool";
@@ -45073,6 +46091,7 @@
     field public static final String KEY_MMS_ALIAS_MIN_CHARS_INT = "aliasMinChars";
     field public static final String KEY_MMS_ALLOW_ATTACH_AUDIO_BOOL = "allowAttachAudio";
     field public static final String KEY_MMS_APPEND_TRANSACTION_ID_BOOL = "enabledTransID";
+    field public static final String KEY_MMS_CLOSE_CONNECTION_BOOL = "mmsCloseConnection";
     field public static final String KEY_MMS_EMAIL_GATEWAY_NUMBER_STRING = "emailGatewayNumber";
     field public static final String KEY_MMS_GROUP_MMS_ENABLED_BOOL = "enableGroupMms";
     field public static final String KEY_MMS_HTTP_PARAMS_STRING = "httpParams";
@@ -45100,8 +46119,11 @@
     field public static final String KEY_MMS_UA_PROF_URL_STRING = "uaProfUrl";
     field public static final String KEY_MMS_USER_AGENT_STRING = "userAgent";
     field public static final String KEY_MONTHLY_DATA_CYCLE_DAY_INT = "monthly_data_cycle_day_int";
+    field public static final String KEY_ONLY_AUTO_SELECT_IN_HOME_NETWORK_BOOL = "only_auto_select_in_home_network";
     field public static final String KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY = "only_single_dc_allowed_int_array";
     field public static final String KEY_OPERATOR_SELECTION_EXPAND_BOOL = "operator_selection_expand_bool";
+    field public static final String KEY_OPPORTUNISTIC_NETWORK_BACKOFF_TIME_LONG = "opportunistic_network_backoff_time_long";
+    field public static final String KEY_OPPORTUNISTIC_NETWORK_DATA_SWITCH_EXIT_HYSTERESIS_TIME_LONG = "opportunistic_network_data_switch_exit_hysteresis_time_long";
     field public static final String KEY_OPPORTUNISTIC_NETWORK_DATA_SWITCH_HYSTERESIS_TIME_LONG = "opportunistic_network_data_switch_hysteresis_time_long";
     field public static final String KEY_OPPORTUNISTIC_NETWORK_ENTRY_OR_EXIT_HYSTERESIS_TIME_LONG = "opportunistic_network_entry_or_exit_hysteresis_time_long";
     field public static final String KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_BANDWIDTH_INT = "opportunistic_network_entry_threshold_bandwidth_int";
@@ -45109,34 +46131,55 @@
     field public static final String KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_RSSNR_INT = "opportunistic_network_entry_threshold_rssnr_int";
     field public static final String KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSRP_INT = "opportunistic_network_exit_threshold_rsrp_int";
     field public static final String KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSSNR_INT = "opportunistic_network_exit_threshold_rssnr_int";
+    field public static final String KEY_OPPORTUNISTIC_NETWORK_MAX_BACKOFF_TIME_LONG = "opportunistic_network_max_backoff_time_long";
+    field public static final String KEY_OPPORTUNISTIC_NETWORK_PING_PONG_TIME_LONG = "opportunistic_network_ping_pong_time_long";
+    field public static final String KEY_PING_TEST_BEFORE_DATA_SWITCH_BOOL = "ping_test_before_data_switch_bool";
     field public static final String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
     field public static final String KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL = "prevent_clir_activation_and_deactivation_code_bool";
     field public static final String KEY_RADIO_RESTART_FAILURE_CAUSES_INT_ARRAY = "radio_restart_failure_causes_int_array";
     field public static final String KEY_RCS_CONFIG_SERVER_URL_STRING = "rcs_config_server_url_string";
+    field public static final String KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY = "read_only_apn_fields_string_array";
+    field public static final String KEY_READ_ONLY_APN_TYPES_STRING_ARRAY = "read_only_apn_types_string_array";
     field public static final String KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL = "require_entitlement_checks_bool";
     field @Deprecated public static final String KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL = "restart_radio_on_pdp_fail_regular_deactivation_bool";
     field public static final String KEY_RTT_SUPPORTED_BOOL = "rtt_supported_bool";
+    field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool";
+    field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool";
     field public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool";
+    field public static final String KEY_SHOW_BLOCKING_PAY_PHONE_OPTION_BOOL = "show_blocking_pay_phone_option_bool";
     field public static final String KEY_SHOW_CALL_BLOCKING_DISABLED_NOTIFICATION_ALWAYS_BOOL = "show_call_blocking_disabled_notification_always_bool";
+    field public static final String KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING = "show_carrier_data_icon_pattern_string";
     field public static final String KEY_SHOW_CDMA_CHOICES_BOOL = "show_cdma_choices_bool";
+    field public static final String KEY_SHOW_FORWARDED_NUMBER_BOOL = "show_forwarded_number_bool";
     field public static final String KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL = "show_iccid_in_sim_status_bool";
+    field public static final String KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL = "show_ims_registration_status_bool";
     field public static final String KEY_SHOW_ONSCREEN_DIAL_BUTTON_BOOL = "show_onscreen_dial_button_bool";
     field public static final String KEY_SHOW_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL = "show_signal_strength_in_sim_status_bool";
+    field public static final String KEY_SHOW_VIDEO_CALL_CHARGES_ALERT_DIALOG_BOOL = "show_video_call_charges_alert_dialog_bool";
+    field public static final String KEY_SHOW_WFC_LOCATION_PRIVACY_POLICY_BOOL = "show_wfc_location_privacy_policy_bool";
     field public static final String KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL = "simplified_network_settings_bool";
     field public static final String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool";
     field public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool";
     field public static final String KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL = "support_3gpp_call_forwarding_while_roaming_bool";
+    field public static final String KEY_SUPPORT_ADHOC_CONFERENCE_CALLS_BOOL = "support_adhoc_conference_calls_bool";
     field public static final String KEY_SUPPORT_CLIR_NETWORK_DEFAULT_BOOL = "support_clir_network_default_bool";
     field public static final String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool";
     field public static final String KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL = "support_emergency_sms_over_ims_bool";
+    field public static final String KEY_SUPPORT_ENHANCED_CALL_BLOCKING_BOOL = "support_enhanced_call_blocking_bool";
+    field public static final String KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL = "support_ims_conference_event_package_bool";
     field public static final String KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL = "support_pause_ims_video_calls_bool";
     field public static final String KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL = "support_swap_after_merge_bool";
+    field public static final String KEY_SUPPORT_TDSCDMA_BOOL = "support_tdscdma_bool";
+    field public static final String KEY_SUPPORT_TDSCDMA_ROAMING_NETWORKS_STRING_ARRAY = "support_tdscdma_roaming_networks_string_array";
+    field public static final String KEY_SWITCH_DATA_TO_PRIMARY_IF_PRIMARY_IS_OOS_BOOL = "switch_data_to_primary_if_primary_is_oos_bool";
     field public static final String KEY_TREAT_DOWNGRADED_VIDEO_CALLS_AS_VIDEO_CALLS_BOOL = "treat_downgraded_video_calls_as_video_calls_bool";
     field public static final String KEY_TTY_SUPPORTED_BOOL = "tty_supported_bool";
+    field public static final String KEY_UNLOGGABLE_NUMBERS_STRING_ARRAY = "unloggable_numbers_string_array";
     field public static final String KEY_USE_HFA_FOR_PROVISIONING_BOOL = "use_hfa_for_provisioning_bool";
     field public static final String KEY_USE_OTASP_FOR_PROVISIONING_BOOL = "use_otasp_for_provisioning_bool";
     field public static final String KEY_USE_RCS_PRESENCE_BOOL = "use_rcs_presence_bool";
     field public static final String KEY_USE_RCS_SIP_OPTIONS_BOOL = "use_rcs_sip_options_bool";
+    field public static final String KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL = "use_wfc_home_network_mode_in_roaming_network_bool";
     field public static final String KEY_VOICEMAIL_NOTIFICATION_PERSISTENT_BOOL = "voicemail_notification_persistent_bool";
     field public static final String KEY_VOICE_PRIVACY_DISABLE_UI_BOOL = "voice_privacy_disable_ui_bool";
     field public static final String KEY_VOLTE_REPLACEMENT_RAT_INT = "volte_replacement_rat_int";
@@ -45149,9 +46192,20 @@
     field public static final String KEY_VVM_PREFETCH_BOOL = "vvm_prefetch_bool";
     field public static final String KEY_VVM_SSL_ENABLED_BOOL = "vvm_ssl_enabled_bool";
     field public static final String KEY_VVM_TYPE_STRING = "vvm_type_string";
+    field public static final String KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING = "wfc_emergency_address_carrier_app_string";
+    field public static final String KEY_WORLD_MODE_ENABLED_BOOL = "world_mode_enabled_bool";
     field public static final String KEY_WORLD_PHONE_BOOL = "world_phone_bool";
   }
 
+  public static final class CarrierConfigManager.Apn {
+    field public static final String KEY_PREFIX = "apn.";
+    field public static final String KEY_SETTINGS_DEFAULT_PROTOCOL_STRING = "apn.settings_default_protocol_string";
+    field public static final String KEY_SETTINGS_DEFAULT_ROAMING_PROTOCOL_STRING = "apn.settings_default_roaming_protocol_string";
+    field public static final String PROTOCOL_IPV4 = "IP";
+    field public static final String PROTOCOL_IPV4V6 = "IPV4V6";
+    field public static final String PROTOCOL_IPV6 = "IPV6";
+  }
+
   public static final class CarrierConfigManager.Gps {
     field public static final String KEY_PERSIST_LPP_MODE_BOOL = "gps.persist_lpp_mode_bool";
     field public static final String KEY_PREFIX = "gps.";
@@ -45159,6 +46213,60 @@
 
   public static final class CarrierConfigManager.Ims {
     field public static final String KEY_PREFIX = "ims.";
+    field public static final String KEY_WIFI_OFF_DEFERRING_TIME_INT = "ims.wifi_off_deferring_time_int";
+  }
+
+  public static final class CarrierConfigManager.Iwlan {
+    field public static final int AUTHENTICATION_METHOD_CERT = 1; // 0x1
+    field public static final int AUTHENTICATION_METHOD_EAP_ONLY = 0; // 0x0
+    field public static final int DH_GROUP_1024_BIT_MODP = 2; // 0x2
+    field public static final int DH_GROUP_2048_BIT_MODP = 14; // 0xe
+    field public static final int DH_GROUP_NONE = 0; // 0x0
+    field public static final int ENCRYPTION_ALGORITHM_3DES = 3; // 0x3
+    field public static final int ENCRYPTION_ALGORITHM_AES_CBC = 12; // 0xc
+    field public static final int ENCRYPTION_ALGORITHM_AES_GCM_12 = 19; // 0x13
+    field public static final int ENCRYPTION_ALGORITHM_AES_GCM_16 = 20; // 0x14
+    field public static final int ENCRYPTION_ALGORITHM_AES_GCM_8 = 18; // 0x12
+    field public static final int EPDG_ADDRESS_PCO = 2; // 0x2
+    field public static final int EPDG_ADDRESS_PLMN = 1; // 0x1
+    field public static final int EPDG_ADDRESS_STATIC = 0; // 0x0
+    field public static final int INTEGRITY_ALGORITHM_AES_XCBC_96 = 5; // 0x5
+    field public static final int INTEGRITY_ALGORITHM_HMAC_SHA1_96 = 2; // 0x2
+    field public static final int INTEGRITY_ALGORITHM_HMAC_SHA2_256_128 = 12; // 0xc
+    field public static final int INTEGRITY_ALGORITHM_HMAC_SHA2_384_192 = 13; // 0xd
+    field public static final int INTEGRITY_ALGORITHM_HMAC_SHA2_512_256 = 14; // 0xe
+    field public static final int INTEGRITY_ALGORITHM_NONE = 0; // 0x0
+    field public static final String KEY_CHILD_SA_REKEY_HARD_TIMER_SEC_INT = "iwlan.child_sa_rekey_hard_timer_sec_int";
+    field public static final String KEY_CHILD_SA_REKEY_SOFT_TIMER_SEC_INT = "iwlan.child_sa_rekey_soft_timer_sec_int";
+    field public static final String KEY_CHILD_SESSION_AES_CBC_KEY_SIZE_INT_ARRAY = "iwlan.child_session_aes_cbc_key_size_int_array";
+    field public static final String KEY_CHILD_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY = "iwlan.child_encryption_aes_ctr_key_size_int_array";
+    field public static final String KEY_DIFFIE_HELLMAN_GROUPS_INT_ARRAY = "iwlan.diffie_hellman_groups_int_array";
+    field public static final String KEY_DPD_TIMER_SEC_INT = "iwlan.dpd_timer_sec_int";
+    field public static final String KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY = "iwlan.epdg_address_priority_int_array";
+    field public static final String KEY_EPDG_AUTHENTICATION_METHOD_INT = "iwlan.epdg_authentication_method_int";
+    field public static final String KEY_EPDG_STATIC_ADDRESS_ROAMING_STRING = "iwlan.epdg_static_address_roaming_string";
+    field public static final String KEY_EPDG_STATIC_ADDRESS_STRING = "iwlan.epdg_static_address_string";
+    field public static final String KEY_IKE_FRAGMENTATION_ENABLED_BOOL = "iwlan.ike_fragmentation_enabled_bool";
+    field public static final String KEY_IKE_REKEY_HARD_TIMER_SEC_INT = "iwlan.ike_rekey_hard_timer_in_sec";
+    field public static final String KEY_IKE_REKEY_SOFT_TIMER_SEC_INT = "iwlan.ike_rekey_soft_timer_sec_int";
+    field public static final String KEY_IKE_SESSION_AES_CBC_KEY_SIZE_INT_ARRAY = "iwlan.ike_session_encryption_aes_cbc_key_size_int_array";
+    field public static final String KEY_IKE_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY = "iwlan.ike_session_aes_ctr_key_size_int_array";
+    field public static final int KEY_LEN_AES_128 = 128; // 0x80
+    field public static final int KEY_LEN_AES_192 = 192; // 0xc0
+    field public static final int KEY_LEN_AES_256 = 256; // 0x100
+    field public static final int KEY_LEN_UNUSED = 0; // 0x0
+    field public static final String KEY_MAX_RETRIES_INT = "iwlan.max_retries_int";
+    field public static final String KEY_MCC_MNCS_STRING_ARRAY = "iwlan.mcc_mncs_string_array";
+    field public static final String KEY_NATT_ENABLED_BOOL = "iwlan.natt_enabled_bool";
+    field public static final String KEY_NATT_KEEP_ALIVE_TIMER_SEC_INT = "iwlan.natt_keep_alive_timer_sec_int";
+    field public static final String KEY_PREFIX = "iwlan.";
+    field public static final String KEY_RETRANSMIT_TIMER_SEC_INT = "iwlan.retransmit_timer_sec_int";
+    field public static final String KEY_SUPPORTED_CHILD_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY = "iwlan.supported_child_session_encryption_algorithms_int_array";
+    field public static final String KEY_SUPPORTED_IKE_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY = "iwlan.supported_ike_session_encryption_algorithms_int_array";
+    field public static final String KEY_SUPPORTED_INTEGRITY_ALGORITHMS_INT_ARRAY = "iwlan.supported_integrity_algorithms_int_array";
+    field public static final String KEY_SUPPORTED_PRF_ALGORITHMS_INT_ARRAY = "iwlan.supported_prf_algorithms_int_array";
+    field public static final int PSEUDORANDOM_FUNCTION_AES128_XCBC = 4; // 0x4
+    field public static final int PSEUDORANDOM_FUNCTION_HMAC_SHA1 = 2; // 0x2
   }
 
   public abstract class CellIdentity implements android.os.Parcelable {
@@ -45180,6 +46288,7 @@
   }
 
   public final class CellIdentityGsm extends android.telephony.CellIdentity {
+    method @NonNull public java.util.List<java.lang.String> getAdditionalPlmns();
     method public int getArfcn();
     method public int getBsic();
     method public int getCid();
@@ -45195,8 +46304,11 @@
   }
 
   public final class CellIdentityLte extends android.telephony.CellIdentity {
+    method @NonNull public java.util.List<java.lang.String> getAdditionalPlmns();
+    method @NonNull public java.util.List<java.lang.Integer> getBands();
     method public int getBandwidth();
     method public int getCi();
+    method @Nullable public android.telephony.ClosedSubscriberGroupInfo getClosedSubscriberGroupInfo();
     method public int getEarfcn();
     method @Deprecated public int getMcc();
     method @Nullable public String getMccString();
@@ -45210,6 +46322,8 @@
   }
 
   public final class CellIdentityNr extends android.telephony.CellIdentity {
+    method @NonNull public java.util.List<java.lang.String> getAdditionalPlmns();
+    method @NonNull public java.util.List<java.lang.Integer> getBands();
     method @Nullable public String getMccString();
     method @Nullable public String getMncString();
     method public long getNci();
@@ -45221,7 +46335,9 @@
   }
 
   public final class CellIdentityTdscdma extends android.telephony.CellIdentity {
+    method @NonNull public java.util.List<java.lang.String> getAdditionalPlmns();
     method public int getCid();
+    method @Nullable public android.telephony.ClosedSubscriberGroupInfo getClosedSubscriberGroupInfo();
     method public int getCpid();
     method public int getLac();
     method @Nullable public String getMccString();
@@ -45233,7 +46349,9 @@
   }
 
   public final class CellIdentityWcdma extends android.telephony.CellIdentity {
+    method @NonNull public java.util.List<java.lang.String> getAdditionalPlmns();
     method public int getCid();
+    method @Nullable public android.telephony.ClosedSubscriberGroupInfo getClosedSubscriberGroupInfo();
     method public int getLac();
     method @Deprecated public int getMcc();
     method @Nullable public String getMccString();
@@ -45251,7 +46369,8 @@
     method public int getCellConnectionStatus();
     method @NonNull public abstract android.telephony.CellIdentity getCellIdentity();
     method @NonNull public abstract android.telephony.CellSignalStrength getCellSignalStrength();
-    method public long getTimeStamp();
+    method @Deprecated public long getTimeStamp();
+    method public long getTimestampNanos();
     method public boolean isRegistered();
     field public static final int CONNECTION_NONE = 0; // 0x0
     field public static final int CONNECTION_PRIMARY_SERVING = 1; // 0x1
@@ -45402,6 +46521,15 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthWcdma> CREATOR;
   }
 
+  public final class ClosedSubscriberGroupInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method @IntRange(from=0, to=134217727) public int getCsgIdentity();
+    method public boolean getCsgIndicator();
+    method @NonNull public String getHomeNodebName();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ClosedSubscriberGroupInfo> CREATOR;
+  }
+
   public class IccOpenLogicalChannelResponse implements android.os.Parcelable {
     method public int describeContents();
     method public int getChannel();
@@ -45467,6 +46595,11 @@
     method @Nullable public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, @NonNull java.util.concurrent.Executor, android.telephony.mbms.StreamingServiceCallback);
   }
 
+  public class MmsManager {
+    method public void downloadMultimediaMessage(int, @NonNull String, @NonNull android.net.Uri, @Nullable android.os.Bundle, @Nullable android.app.PendingIntent);
+    method public void sendMultimediaMessage(int, @NonNull android.net.Uri, @Nullable String, @Nullable android.os.Bundle, @Nullable android.app.PendingIntent);
+  }
+
   @Deprecated public class NeighboringCellInfo implements android.os.Parcelable {
     ctor @Deprecated public NeighboringCellInfo();
     ctor @Deprecated public NeighboringCellInfo(int, int);
@@ -45486,6 +46619,35 @@
     field @Deprecated public static final int UNKNOWN_RSSI = 99; // 0x63
   }
 
+  public final class NetworkRegistrationInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getAccessNetworkTechnology();
+    method @NonNull public java.util.List<java.lang.Integer> getAvailableServices();
+    method @Nullable public android.telephony.CellIdentity getCellIdentity();
+    method public int getDomain();
+    method public int getNrState();
+    method public int getTransportType();
+    method public boolean isRegistered();
+    method public boolean isRoaming();
+    method public boolean isSearching();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.NetworkRegistrationInfo> CREATOR;
+    field public static final int DOMAIN_CS = 1; // 0x1
+    field public static final int DOMAIN_CS_PS = 3; // 0x3
+    field public static final int DOMAIN_PS = 2; // 0x2
+    field public static final int DOMAIN_UNKNOWN = 0; // 0x0
+    field public static final int NR_STATE_CONNECTED = 3; // 0x3
+    field public static final int NR_STATE_NONE = 0; // 0x0
+    field public static final int NR_STATE_NOT_RESTRICTED = 2; // 0x2
+    field public static final int NR_STATE_RESTRICTED = 1; // 0x1
+    field public static final int SERVICE_TYPE_DATA = 2; // 0x2
+    field public static final int SERVICE_TYPE_EMERGENCY = 5; // 0x5
+    field public static final int SERVICE_TYPE_SMS = 3; // 0x3
+    field public static final int SERVICE_TYPE_UNKNOWN = 0; // 0x0
+    field public static final int SERVICE_TYPE_VIDEO = 4; // 0x4
+    field public static final int SERVICE_TYPE_VOICE = 1; // 0x1
+  }
+
   public class NetworkScan {
     method public void stopScan();
     field public static final int ERROR_INTERRUPTED = 10002; // 0x2712
@@ -45516,8 +46678,32 @@
 
   public final class PhoneCapability implements android.os.Parcelable {
     method public int describeContents();
+    method @NonNull public java.util.List<java.lang.Integer> getBands(int);
+    method @NonNull public java.util.List<java.util.List<java.lang.Long>> getConcurrentFeaturesSupport();
+    method @NonNull public java.util.List<java.lang.String> getLogicalModemUuids();
+    method public int getMaxActiveDedicatedBearers();
+    method public int getMaxActiveInternetData();
+    method public int getMaxActivePsVoice();
+    method public long getPsDataConnectionLingerTimeMillis();
+    method @NonNull public java.util.List<android.telephony.SimSlotCapability> getSimSlotCapabilities();
+    method public long getSupportedRats();
+    method public int getUeCategory(boolean, int);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PhoneCapability> CREATOR;
+    field public static final long MODEM_FEATURE_3GPP2_REG = 1L; // 0x1L
+    field public static final long MODEM_FEATURE_3GPP_REG = 2L; // 0x2L
+    field public static final long MODEM_FEATURE_CDMA2000_EHRPD_REG = 4L; // 0x4L
+    field public static final long MODEM_FEATURE_CSIM = 8192L; // 0x2000L
+    field public static final long MODEM_FEATURE_CS_VOICE_SESSION = 512L; // 0x200L
+    field public static final long MODEM_FEATURE_DEDICATED_BEARER = 2048L; // 0x800L
+    field public static final long MODEM_FEATURE_EUTRAN_REG = 32L; // 0x20L
+    field public static final long MODEM_FEATURE_EUTRA_NR_DUAL_CONNECTIVITY_REG = 128L; // 0x80L
+    field public static final long MODEM_FEATURE_GERAN_REG = 8L; // 0x8L
+    field public static final long MODEM_FEATURE_INTERACTIVE_DATA_SESSION = 1024L; // 0x400L
+    field public static final long MODEM_FEATURE_NETWORK_SCAN = 4096L; // 0x1000L
+    field public static final long MODEM_FEATURE_NGRAN_REG = 64L; // 0x40L
+    field public static final long MODEM_FEATURE_PS_VOICE_REG = 256L; // 0x100L
+    field public static final long MODEM_FEATURE_UTRAN_REG = 16L; // 0x10L
   }
 
   public class PhoneNumberFormattingTextWatcher implements android.text.TextWatcher {
@@ -45590,6 +46776,7 @@
     ctor public PhoneStateListener();
     ctor public PhoneStateListener(@NonNull java.util.concurrent.Executor);
     method public void onActiveDataSubscriptionIdChanged(int);
+    method public void onBarringInfoChanged(@NonNull android.telephony.BarringInfo);
     method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onCallDisconnectCauseChanged(int, int);
     method public void onCallForwardingIndicatorChanged(boolean);
     method public void onCallStateChanged(int, String);
@@ -45607,6 +46794,7 @@
     method public void onSignalStrengthsChanged(android.telephony.SignalStrength);
     method public void onUserMobileDataStateChanged(boolean);
     field public static final int LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE = 4194304; // 0x400000
+    field @RequiresPermission(android.Manifest.permission.READ_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
@@ -45618,7 +46806,7 @@
     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 @RequiresPermission("android.permission.MODIFY_PHONE_STATE") public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 4096; // 0x1000
+    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_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
@@ -45658,11 +46846,13 @@
     method public int getChannelNumber();
     method public int getDuplexMode();
     method public boolean getIsManualSelection();
+    method @NonNull public java.util.List<android.telephony.NetworkRegistrationInfo> getNetworkRegistrationInfoList();
     method public String getOperatorAlphaLong();
     method public String getOperatorAlphaShort();
     method public String getOperatorNumeric();
     method public boolean getRoaming();
     method public int getState();
+    method public boolean isSearching();
     method public void setIsManualSelection(boolean);
     method public void setOperatorName(String, String, String);
     method public void setRoaming(boolean);
@@ -45693,18 +46883,31 @@
     method @Deprecated public int getGsmBitErrorRate();
     method @Deprecated public int getGsmSignalStrength();
     method public int getLevel();
+    method public long getTimestampNanos();
     method @Deprecated public boolean isGsm();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.SignalStrength> CREATOR;
     field public static final int INVALID = 2147483647; // 0x7fffffff
   }
 
+  public final class SimSlotCapability implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getPhysicalSlotIndex();
+    method public int getSlotType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.SimSlotCapability> CREATOR;
+    field public static final int SLOT_TYPE_EUICC = 3; // 0x3
+    field public static final int SLOT_TYPE_IUICC = 2; // 0x2
+    field public static final int SLOT_TYPE_SOFT_SIM = 4; // 0x4
+    field public static final int SLOT_TYPE_UICC = 1; // 0x1
+  }
+
   public final class SmsManager {
     method public String createAppSpecificSmsToken(android.app.PendingIntent);
     method @Nullable public String createAppSpecificSmsTokenWithPackageInfo(@Nullable String, @NonNull android.app.PendingIntent);
     method public java.util.ArrayList<java.lang.String> divideMessage(String);
-    method public void downloadMultimediaMessage(android.content.Context, String, android.net.Uri, android.os.Bundle, android.app.PendingIntent);
-    method @Nullable public android.os.Bundle getCarrierConfigValues();
+    method @Deprecated public void downloadMultimediaMessage(android.content.Context, String, android.net.Uri, android.os.Bundle, android.app.PendingIntent);
+    method @NonNull public android.os.Bundle getCarrierConfigValues();
     method public static android.telephony.SmsManager getDefault();
     method public static int getDefaultSmsSubscriptionId();
     method public static android.telephony.SmsManager getSmsManagerForSubscriptionId(int);
@@ -45713,9 +46916,11 @@
     method public int getSubscriptionId();
     method public void injectSmsPdu(byte[], String, android.app.PendingIntent);
     method public void sendDataMessage(String, String, short, byte[], android.app.PendingIntent, android.app.PendingIntent);
-    method public void sendMultimediaMessage(android.content.Context, android.net.Uri, String, android.os.Bundle, android.app.PendingIntent);
+    method @Deprecated public void sendMultimediaMessage(android.content.Context, android.net.Uri, String, android.os.Bundle, android.app.PendingIntent);
     method public void sendMultipartTextMessage(String, String, java.util.ArrayList<java.lang.String>, java.util.ArrayList<android.app.PendingIntent>, java.util.ArrayList<android.app.PendingIntent>);
+    method public void sendMultipartTextMessage(@NonNull String, @Nullable String, @NonNull java.util.List<java.lang.String>, @Nullable java.util.List<android.app.PendingIntent>, @Nullable java.util.List<android.app.PendingIntent>, long);
     method public void sendTextMessage(String, String, String, android.app.PendingIntent, android.app.PendingIntent);
+    method public void sendTextMessage(@NonNull String, @Nullable String, @NonNull String, @Nullable android.app.PendingIntent, @Nullable android.app.PendingIntent, long);
     method @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.SEND_SMS}) public void sendTextMessageWithoutPersisting(String, String, String, android.app.PendingIntent, android.app.PendingIntent);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setSmscAddress(@NonNull String);
     field public static final String EXTRA_MMS_DATA = "android.telephony.extra.MMS_DATA";
@@ -45921,6 +47126,7 @@
   public class SubscriptionManager {
     method public void addOnOpportunisticSubscriptionsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener);
     method public void addOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
+    method public void addOnSubscriptionsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void addSubscriptionsIntoGroup(@NonNull java.util.List<java.lang.Integer>, @NonNull android.os.ParcelUuid);
     method public boolean canManageSubscription(android.telephony.SubscriptionInfo);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.os.ParcelUuid createSubscriptionGroup(@NonNull java.util.List<java.lang.Integer>);
@@ -46038,6 +47244,7 @@
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getImei();
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getImei(int);
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, android.Manifest.permission.READ_PHONE_NUMBERS}) public String getLine1Number();
+    method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public String getManualNetworkSelectionPlmn();
     method @Nullable public String getManufacturerCode();
     method @Nullable public String getManufacturerCode(int);
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getMeid();
@@ -46048,8 +47255,10 @@
     method public String getNetworkCountryIso();
     method public String getNetworkOperator();
     method public String getNetworkOperatorName();
+    method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public int getNetworkSelectionMode();
     method public String getNetworkSpecifier();
     method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getNetworkType();
+    method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telephony.PhoneCapability getPhoneCapability();
     method @Deprecated public int getPhoneCount();
     method public int getPhoneType();
     method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE}) public int getPreferredOpportunisticDataSubscription();
@@ -46079,17 +47288,19 @@
     method public android.net.Uri getVoicemailRingtoneUri(android.telecom.PhoneAccountHandle);
     method public boolean hasCarrierPrivileges();
     method public boolean hasIccCard();
-    method public boolean iccCloseLogicalChannel(int);
-    method public byte[] iccExchangeSimIO(int, int, int, int, int, String);
+    method @Deprecated public boolean iccCloseLogicalChannel(int);
+    method @Deprecated public byte[] iccExchangeSimIO(int, int, int, int, int, String);
     method @Deprecated public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(String);
-    method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(String, int);
-    method public String iccTransmitApduBasicChannel(int, int, int, int, int, String);
-    method public String iccTransmitApduLogicalChannel(int, int, int, int, int, int, String);
+    method @Deprecated public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(String, int);
+    method @Deprecated public String iccTransmitApduBasicChannel(int, int, int, int, int, String);
+    method @Deprecated public String iccTransmitApduLogicalChannel(int, int, int, int, int, int, String);
     method public boolean isConcurrentVoiceAndDataSupported();
+    method public boolean isDataCapable();
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean isDataEnabled();
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataRoamingEnabled();
     method public boolean isEmergencyNumber(@NonNull String);
     method public boolean isHearingAidCompatibilitySupported();
+    method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public boolean isManualNetworkSelectionAllowed();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int isMultiSimSupported();
     method public boolean isNetworkRoaming();
     method public boolean isRttSupported();
@@ -46102,7 +47313,7 @@
     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);
-    method public String sendEnvelopeWithStatus(String);
+    method @Deprecated public String sendEnvelopeWithStatus(String);
     method @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void sendUssdRequest(String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler);
     method public void sendVisualVoicemailSms(String, int, String, android.app.PendingIntent);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(boolean);
@@ -46181,6 +47392,9 @@
     field public static final int MULTISIM_ALLOWED = 0; // 0x0
     field public static final int MULTISIM_NOT_SUPPORTED_BY_CARRIER = 2; // 0x2
     field public static final int MULTISIM_NOT_SUPPORTED_BY_HARDWARE = 1; // 0x1
+    field public static final int NETWORK_SELECTION_MODE_AUTO = 1; // 0x1
+    field public static final int NETWORK_SELECTION_MODE_MANUAL = 2; // 0x2
+    field public static final int NETWORK_SELECTION_MODE_UNKNOWN = 0; // 0x0
     field public static final int NETWORK_TYPE_1xRTT = 7; // 0x7
     field public static final int NETWORK_TYPE_CDMA = 4; // 0x4
     field public static final int NETWORK_TYPE_EDGE = 2; // 0x2
@@ -46203,6 +47417,7 @@
     field public static final int NETWORK_TYPE_UNKNOWN = 0; // 0x0
     field public static final int PHONE_TYPE_CDMA = 2; // 0x2
     field public static final int PHONE_TYPE_GSM = 1; // 0x1
+    field public static final int PHONE_TYPE_IMS = 5; // 0x5
     field public static final int PHONE_TYPE_NONE = 0; // 0x0
     field public static final int PHONE_TYPE_SIP = 3; // 0x3
     field public static final int SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION = 2; // 0x2
@@ -46494,10 +47709,42 @@
     field public static final int EMBEDDED_SUBSCRIPTION_RESULT_ERROR = 2; // 0x2
     field public static final int EMBEDDED_SUBSCRIPTION_RESULT_OK = 0; // 0x0
     field public static final int EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR = 1; // 0x1
+    field public static final int ERROR_ADDRESS_MISSING = 10011; // 0x271b
+    field public static final int ERROR_CARRIER_LOCKED = 10000; // 0x2710
+    field public static final int ERROR_CERTIFICATE_ERROR = 10012; // 0x271c
+    field public static final int ERROR_CONNECTION_ERROR = 10014; // 0x271e
+    field public static final int ERROR_DISALLOWED_BY_PPR = 10010; // 0x271a
+    field public static final int ERROR_EUICC_GSMA_INSTALL_ERROR = 10009; // 0x2719
+    field public static final int ERROR_EUICC_INSUFFICIENT_MEMORY = 10004; // 0x2714
+    field public static final int ERROR_EUICC_MISSING = 10006; // 0x2716
+    field public static final int ERROR_INCOMPATIBLE_CARRIER = 10003; // 0x2713
+    field public static final int ERROR_INVALID_ACTIVATION_CODE = 10001; // 0x2711
+    field public static final int ERROR_INVALID_CONFIRMATION_CODE = 10002; // 0x2712
+    field public static final int ERROR_INVALID_RESPONSE = 10015; // 0x271f
+    field public static final int ERROR_NO_PROFILES_AVAILABLE = 10013; // 0x271d
+    field public static final int ERROR_OPERATION_BUSY = 10016; // 0x2720
+    field public static final int ERROR_SIM_MISSING = 10008; // 0x2718
+    field public static final int ERROR_TIME_OUT = 10005; // 0x2715
+    field public static final int ERROR_UNSUPPORTED_VERSION = 10007; // 0x2717
     field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_DETAILED_CODE";
     field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION";
+    field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_ERROR_CODE = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_ERROR_CODE";
+    field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_OPERATION_CODE = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_OPERATION_CODE";
+    field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_SMDX_REASON_CODE = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_SMDX_REASON_CODE";
+    field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_SMDX_SUBJECT_CODE = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_SMDX_SUBJECT_CODE";
     field public static final String EXTRA_USE_QR_SCANNER = "android.telephony.euicc.extra.USE_QR_SCANNER";
     field public static final String META_DATA_CARRIER_ICON = "android.telephony.euicc.carriericon";
+    field public static final int OPERATION_APDU = 8; // 0x8
+    field public static final int OPERATION_DOWNLOAD = 5; // 0x5
+    field public static final int OPERATION_EUICC_CARD = 3; // 0x3
+    field public static final int OPERATION_EUICC_GSMA = 7; // 0x7
+    field public static final int OPERATION_HTTP = 11; // 0xb
+    field public static final int OPERATION_METADATA = 6; // 0x6
+    field public static final int OPERATION_SIM_SLOT = 2; // 0x2
+    field public static final int OPERATION_SMDX = 9; // 0x9
+    field public static final int OPERATION_SMDX_SUBJECT_REASON_CODE = 10; // 0xa
+    field public static final int OPERATION_SWITCH = 4; // 0x4
+    field public static final int OPERATION_SYSTEM = 1; // 0x1
   }
 
 }
@@ -46591,6 +47838,37 @@
 
 package android.telephony.ims {
 
+  public final class ImsException extends java.lang.Exception {
+    method public int getCode();
+    field public static final int CODE_ERROR_SERVICE_UNAVAILABLE = 1; // 0x1
+    field public static final int CODE_ERROR_UNSPECIFIED = 0; // 0x0
+    field public static final int CODE_ERROR_UNSUPPORTED_OPERATION = 2; // 0x2
+  }
+
+  public class ImsMmTelManager implements android.telephony.ims.RegistrationManager {
+    method @NonNull @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(int);
+    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getRegistrationState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public void getRegistrationTransportType(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public int getVoWiFiModeSetting();
+    method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public boolean isAdvancedCallingSettingEnabled();
+    method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public boolean isTtyOverVolteEnabled();
+    method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public boolean isVoWiFiRoamingSettingEnabled();
+    method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public boolean isVoWiFiSettingEnabled();
+    method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public boolean isVtSettingEnabled();
+    method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public void registerImsRegistrationCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RegistrationManager.RegistrationCallback) throws android.telephony.ims.ImsException;
+    method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public void registerMmTelCapabilityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.CapabilityCallback) throws android.telephony.ims.ImsException;
+    method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public void unregisterImsRegistrationCallback(@NonNull android.telephony.ims.RegistrationManager.RegistrationCallback);
+    method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public void unregisterMmTelCapabilityCallback(@NonNull android.telephony.ims.ImsMmTelManager.CapabilityCallback);
+    field public static final int WIFI_MODE_CELLULAR_PREFERRED = 1; // 0x1
+    field public static final int WIFI_MODE_WIFI_ONLY = 0; // 0x0
+    field public static final int WIFI_MODE_WIFI_PREFERRED = 2; // 0x2
+  }
+
+  public static class ImsMmTelManager.CapabilityCallback {
+    ctor public ImsMmTelManager.CapabilityCallback();
+    method public void onCapabilitiesStatusChanged(@NonNull android.telephony.ims.feature.MmTelFeature.MmTelCapabilities);
+  }
+
   public final class ImsReasonInfo implements android.os.Parcelable {
     ctor public ImsReasonInfo(int, int, @Nullable String);
     method public int describeContents();
@@ -46774,6 +48052,38 @@
     field public static final int EXTRA_CODE_CALL_RETRY_SILENT_REDIAL = 2; // 0x2
   }
 
+  public interface RegistrationManager {
+    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getRegistrationState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getRegistrationTransportType(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void registerImsRegistrationCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RegistrationManager.RegistrationCallback) throws android.telephony.ims.ImsException;
+    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void unregisterImsRegistrationCallback(@NonNull android.telephony.ims.RegistrationManager.RegistrationCallback);
+    field public static final int REGISTRATION_STATE_NOT_REGISTERED = 0; // 0x0
+    field public static final int REGISTRATION_STATE_REGISTERED = 2; // 0x2
+    field public static final int REGISTRATION_STATE_REGISTERING = 1; // 0x1
+  }
+
+  public static class RegistrationManager.RegistrationCallback {
+    ctor public RegistrationManager.RegistrationCallback();
+    method public void onRegistered(int);
+    method public void onRegistering(int);
+    method public void onTechnologyChangeFailed(int, @Nullable android.telephony.ims.ImsReasonInfo);
+    method public void onUnregistered(@Nullable android.telephony.ims.ImsReasonInfo);
+  }
+
+}
+
+package android.telephony.ims.feature {
+
+  public class MmTelFeature {
+  }
+
+  public static class MmTelFeature.MmTelCapabilities {
+    field public static final int CAPABILITY_TYPE_SMS = 8; // 0x8
+    field public static final int CAPABILITY_TYPE_UT = 4; // 0x4
+    field public static final int CAPABILITY_TYPE_VIDEO = 2; // 0x2
+    field public static final int CAPABILITY_TYPE_VOICE = 1; // 0x1
+  }
+
 }
 
 package android.telephony.mbms {
@@ -49706,6 +51016,7 @@
     method public static java.util.TimeZone getTimeZone(int, boolean, long, String);
     method public static String getTimeZoneDatabaseVersion();
     method @Nullable public static java.util.List<java.lang.String> getTimeZoneIdsForCountryCode(@NonNull String);
+    method public static boolean isTimeBetween(@NonNull java.time.LocalTime, @NonNull java.time.LocalTime, @NonNull java.time.LocalTime);
   }
 
   @Deprecated public class TimingLogger {
@@ -50047,6 +51358,7 @@
 
   public final class DisplayCutout {
     ctor public DisplayCutout(@NonNull android.graphics.Insets, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect);
+    ctor public DisplayCutout(@NonNull android.graphics.Insets, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, @NonNull android.graphics.Insets);
     ctor @Deprecated public DisplayCutout(@Nullable android.graphics.Rect, @Nullable java.util.List<android.graphics.Rect>);
     method @NonNull public android.graphics.Rect getBoundingRectBottom();
     method @NonNull public android.graphics.Rect getBoundingRectLeft();
@@ -50057,6 +51369,7 @@
     method public int getSafeInsetLeft();
     method public int getSafeInsetRight();
     method public int getSafeInsetTop();
+    method @NonNull public android.graphics.Insets getWaterfallInsets();
   }
 
   public final class DragAndDropPermissions implements android.os.Parcelable {
@@ -51304,6 +52617,7 @@
     method public android.graphics.Canvas lockHardwareCanvas();
     method public void readFromParcel(android.os.Parcel);
     method public void release();
+    method public void setFrameRate(@FloatRange(from=0.0) float);
     method @Deprecated public void unlockCanvas(android.graphics.Canvas);
     method public void unlockCanvasAndPost(android.graphics.Canvas);
     method public void writeToParcel(android.os.Parcel, int);
@@ -51347,6 +52661,7 @@
     method @NonNull public android.view.SurfaceControl.Transaction reparent(@NonNull android.view.SurfaceControl, @Nullable android.view.SurfaceControl);
     method @NonNull public android.view.SurfaceControl.Transaction setAlpha(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0, to=1.0) float);
     method @NonNull public android.view.SurfaceControl.Transaction setBufferSize(@NonNull android.view.SurfaceControl, @IntRange(from=0) int, @IntRange(from=0) int);
+    method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float);
     method @NonNull public android.view.SurfaceControl.Transaction setGeometry(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, int);
     method @NonNull public android.view.SurfaceControl.Transaction setLayer(@NonNull android.view.SurfaceControl, @IntRange(from=java.lang.Integer.MIN_VALUE, to=java.lang.Integer.MAX_VALUE) int);
     method @NonNull public android.view.SurfaceControl.Transaction setVisibility(@NonNull android.view.SurfaceControl, boolean);
@@ -51354,6 +52669,20 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.view.SurfaceControl.Transaction> CREATOR;
   }
 
+  public class SurfaceControlViewHost {
+    ctor public SurfaceControlViewHost(@NonNull android.content.Context, @NonNull android.view.Display, @Nullable android.os.IBinder);
+    method public void addView(@NonNull android.view.View, int, int);
+    method @Nullable public android.view.SurfaceControlViewHost.SurfacePackage getSurfacePackage();
+    method public void relayout(int, int);
+    method public void release();
+  }
+
+  public static final class SurfaceControlViewHost.SurfacePackage 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.view.SurfaceControlViewHost.SurfacePackage> CREATOR;
+  }
+
   public interface SurfaceHolder {
     method public void addCallback(android.view.SurfaceHolder.Callback);
     method public android.view.Surface getSurface();
@@ -51398,7 +52727,9 @@
     ctor public SurfaceView(android.content.Context, android.util.AttributeSet, int, int);
     method public boolean gatherTransparentRegion(android.graphics.Region);
     method public android.view.SurfaceHolder getHolder();
+    method @Nullable public android.os.IBinder getHostToken();
     method public android.view.SurfaceControl getSurfaceControl();
+    method public void setChildSurfacePackage(@NonNull android.view.SurfaceControlViewHost.SurfacePackage);
     method public void setSecure(boolean);
     method public void setZOrderMediaOverlay(boolean);
     method public void setZOrderOnTop(boolean);
@@ -51538,16 +52869,17 @@
     method protected void dispatchSetPressed(boolean);
     method protected void dispatchSetSelected(boolean);
     method @CallSuper public void dispatchStartTemporaryDetach();
-    method public void dispatchSystemUiVisibilityChanged(int);
+    method @Deprecated public void dispatchSystemUiVisibilityChanged(int);
     method public boolean dispatchTouchEvent(android.view.MotionEvent);
     method public boolean dispatchTrackballEvent(android.view.MotionEvent);
     method public boolean dispatchUnhandledMove(android.view.View, int);
     method protected void dispatchVisibilityChanged(@NonNull android.view.View, int);
     method public void dispatchWindowFocusChanged(boolean);
-    method public void dispatchWindowInsetsAnimationFinished(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
+    method public void dispatchWindowInsetsAnimationFinish(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
+    method public void dispatchWindowInsetsAnimationPrepare(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
     method @NonNull public android.view.WindowInsets dispatchWindowInsetsAnimationProgress(@NonNull android.view.WindowInsets);
-    method @NonNull public android.view.WindowInsetsAnimationCallback.AnimationBounds dispatchWindowInsetsAnimationStarted(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds);
-    method public void dispatchWindowSystemUiVisiblityChanged(int);
+    method @NonNull public android.view.WindowInsetsAnimationCallback.AnimationBounds dispatchWindowInsetsAnimationStart(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds);
+    method @Deprecated public void dispatchWindowSystemUiVisiblityChanged(int);
     method public void dispatchWindowVisibilityChanged(int);
     method @CallSuper public void draw(android.graphics.Canvas);
     method @CallSuper public void drawableHotspotChanged(float, float);
@@ -51700,7 +53032,7 @@
     method protected int getSuggestedMinimumHeight();
     method protected int getSuggestedMinimumWidth();
     method @NonNull public java.util.List<android.graphics.Rect> getSystemGestureExclusionRects();
-    method public int getSystemUiVisibility();
+    method @Deprecated public int getSystemUiVisibility();
     method @android.view.ViewDebug.ExportedProperty public Object getTag();
     method public Object getTag(int);
     method @android.view.ViewDebug.ExportedProperty(category="text", mapping={@android.view.ViewDebug.IntToString(from=android.view.View.TEXT_ALIGNMENT_INHERIT, to="INHERIT"), @android.view.ViewDebug.IntToString(from=android.view.View.TEXT_ALIGNMENT_GRAVITY, to="GRAVITY"), @android.view.ViewDebug.IntToString(from=android.view.View.TEXT_ALIGNMENT_TEXT_START, to="TEXT_START"), @android.view.ViewDebug.IntToString(from=android.view.View.TEXT_ALIGNMENT_TEXT_END, to="TEXT_END"), @android.view.ViewDebug.IntToString(from=android.view.View.TEXT_ALIGNMENT_CENTER, to="CENTER"), @android.view.ViewDebug.IntToString(from=android.view.View.TEXT_ALIGNMENT_VIEW_START, to="VIEW_START"), @android.view.ViewDebug.IntToString(from=android.view.View.TEXT_ALIGNMENT_VIEW_END, to="VIEW_END")}) public int getTextAlignment();
@@ -51728,7 +53060,7 @@
     method protected int getWindowAttachCount();
     method public android.view.WindowId getWindowId();
     method @Nullable public android.view.WindowInsetsController getWindowInsetsController();
-    method public int getWindowSystemUiVisibility();
+    method @Deprecated public int getWindowSystemUiVisibility();
     method public android.os.IBinder getWindowToken();
     method public int getWindowVisibility();
     method public void getWindowVisibleDisplayFrame(android.graphics.Rect);
@@ -51866,7 +53198,7 @@
     method @CallSuper public void onVisibilityAggregated(boolean);
     method protected void onVisibilityChanged(@NonNull android.view.View, int);
     method public void onWindowFocusChanged(boolean);
-    method public void onWindowSystemUiVisibilityChanged(int);
+    method @Deprecated public void onWindowSystemUiVisibilityChanged(int);
     method protected void onWindowVisibilityChanged(int);
     method protected boolean overScrollBy(int, int, int, int, int, int, int, int, boolean);
     method public boolean performAccessibilityAction(int, android.os.Bundle);
@@ -52008,7 +53340,7 @@
     method public void setOnKeyListener(android.view.View.OnKeyListener);
     method public void setOnLongClickListener(@Nullable android.view.View.OnLongClickListener);
     method public void setOnScrollChangeListener(android.view.View.OnScrollChangeListener);
-    method public void setOnSystemUiVisibilityChangeListener(android.view.View.OnSystemUiVisibilityChangeListener);
+    method @Deprecated public void setOnSystemUiVisibilityChangeListener(android.view.View.OnSystemUiVisibilityChangeListener);
     method public void setOnTouchListener(android.view.View.OnTouchListener);
     method public void setOutlineAmbientShadowColor(@ColorInt int);
     method public void setOutlineProvider(android.view.ViewOutlineProvider);
@@ -52045,7 +53377,7 @@
     method public void setStateDescription(@Nullable CharSequence);
     method public void setStateListAnimator(android.animation.StateListAnimator);
     method public void setSystemGestureExclusionRects(@NonNull java.util.List<android.graphics.Rect>);
-    method public void setSystemUiVisibility(int);
+    method @Deprecated public void setSystemUiVisibility(int);
     method public void setTag(Object);
     method public void setTag(int, Object);
     method public void setTextAlignment(int);
@@ -52223,18 +53555,18 @@
     field public static final int SOUND_EFFECTS_ENABLED = 134217728; // 0x8000000
     field @Deprecated public static final int STATUS_BAR_HIDDEN = 1; // 0x1
     field @Deprecated public static final int STATUS_BAR_VISIBLE = 0; // 0x0
-    field public static final int SYSTEM_UI_FLAG_FULLSCREEN = 4; // 0x4
-    field public static final int SYSTEM_UI_FLAG_HIDE_NAVIGATION = 2; // 0x2
-    field public static final int SYSTEM_UI_FLAG_IMMERSIVE = 2048; // 0x800
-    field public static final int SYSTEM_UI_FLAG_IMMERSIVE_STICKY = 4096; // 0x1000
-    field public static final int SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = 1024; // 0x400
-    field public static final int SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION = 512; // 0x200
-    field public static final int SYSTEM_UI_FLAG_LAYOUT_STABLE = 256; // 0x100
-    field public static final int SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR = 16; // 0x10
-    field public static final int SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = 8192; // 0x2000
-    field public static final int SYSTEM_UI_FLAG_LOW_PROFILE = 1; // 0x1
-    field public static final int SYSTEM_UI_FLAG_VISIBLE = 0; // 0x0
-    field public static final int SYSTEM_UI_LAYOUT_FLAGS = 1536; // 0x600
+    field @Deprecated public static final int SYSTEM_UI_FLAG_FULLSCREEN = 4; // 0x4
+    field @Deprecated public static final int SYSTEM_UI_FLAG_HIDE_NAVIGATION = 2; // 0x2
+    field @Deprecated public static final int SYSTEM_UI_FLAG_IMMERSIVE = 2048; // 0x800
+    field @Deprecated public static final int SYSTEM_UI_FLAG_IMMERSIVE_STICKY = 4096; // 0x1000
+    field @Deprecated public static final int SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = 1024; // 0x400
+    field @Deprecated public static final int SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION = 512; // 0x200
+    field @Deprecated public static final int SYSTEM_UI_FLAG_LAYOUT_STABLE = 256; // 0x100
+    field @Deprecated public static final int SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR = 16; // 0x10
+    field @Deprecated public static final int SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = 8192; // 0x2000
+    field @Deprecated public static final int SYSTEM_UI_FLAG_LOW_PROFILE = 1; // 0x1
+    field @Deprecated public static final int SYSTEM_UI_FLAG_VISIBLE = 0; // 0x0
+    field @Deprecated public static final int SYSTEM_UI_LAYOUT_FLAGS = 1536; // 0x600
     field public static final int TEXT_ALIGNMENT_CENTER = 4; // 0x4
     field public static final int TEXT_ALIGNMENT_GRAVITY = 1; // 0x1
     field public static final int TEXT_ALIGNMENT_INHERIT = 0; // 0x0
@@ -52358,8 +53690,8 @@
     method public void onScrollChange(android.view.View, int, int, int, int);
   }
 
-  public static interface View.OnSystemUiVisibilityChangeListener {
-    method public void onSystemUiVisibilityChange(int);
+  @Deprecated public static interface View.OnSystemUiVisibilityChangeListener {
+    method @Deprecated public void onSystemUiVisibilityChange(int);
   }
 
   public static interface View.OnTouchListener {
@@ -52996,6 +54328,7 @@
     method public final void removeOnFrameMetricsAvailableListener(android.view.Window.OnFrameMetricsAvailableListener);
     method public boolean requestFeature(int);
     method @NonNull public final <T extends android.view.View> T requireViewById(@IdRes int);
+    method public void resetOnContentApplyWindowInsetsListener();
     method public abstract void restoreHierarchyState(android.os.Bundle);
     method public abstract android.os.Bundle saveHierarchyState();
     method public void setAllowEnterTransitionOverlap(boolean);
@@ -53034,6 +54367,7 @@
     method public abstract void setNavigationBarColor(@ColorInt int);
     method public void setNavigationBarContrastEnforced(boolean);
     method public void setNavigationBarDividerColor(@ColorInt int);
+    method public void setOnContentApplyWindowInsetsListener(@Nullable android.view.Window.OnContentApplyWindowInsetsListener);
     method public void setPreferMinimalPostProcessing(boolean);
     method public void setReenterTransition(android.transition.Transition);
     method public abstract void setResizingCaptionDrawable(android.graphics.drawable.Drawable);
@@ -53128,6 +54462,10 @@
     method @Nullable public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int);
   }
 
+  public static interface Window.OnContentApplyWindowInsetsListener {
+    method @NonNull public android.util.Pair<android.graphics.Insets,android.view.WindowInsets> onContentApplyWindowInsets(@NonNull android.view.WindowInsets);
+  }
+
   public static interface Window.OnFrameMetricsAvailableListener {
     method public void onFrameMetricsAvailable(android.view.Window, android.view.FrameMetrics, int);
   }
@@ -53167,34 +54505,35 @@
 
   public final class WindowInsets {
     ctor public WindowInsets(android.view.WindowInsets);
-    method @NonNull public android.view.WindowInsets consumeDisplayCutout();
-    method @NonNull public android.view.WindowInsets consumeStableInsets();
-    method @NonNull public android.view.WindowInsets consumeSystemWindowInsets();
+    method @Deprecated @NonNull public android.view.WindowInsets consumeDisplayCutout();
+    method @Deprecated @NonNull public android.view.WindowInsets consumeStableInsets();
+    method @Deprecated @NonNull public android.view.WindowInsets consumeSystemWindowInsets();
     method @Nullable public android.view.DisplayCutout getDisplayCutout();
     method @NonNull public android.graphics.Insets getInsets(int);
-    method @NonNull public android.graphics.Insets getMandatorySystemGestureInsets();
-    method @NonNull public android.graphics.Insets getMaxInsets(int) throws java.lang.IllegalArgumentException;
-    method public int getStableInsetBottom();
-    method public int getStableInsetLeft();
-    method public int getStableInsetRight();
-    method public int getStableInsetTop();
-    method @NonNull public android.graphics.Insets getStableInsets();
-    method @NonNull public android.graphics.Insets getSystemGestureInsets();
-    method public int getSystemWindowInsetBottom();
-    method public int getSystemWindowInsetLeft();
-    method public int getSystemWindowInsetRight();
-    method public int getSystemWindowInsetTop();
-    method @NonNull public android.graphics.Insets getSystemWindowInsets();
-    method @NonNull public android.graphics.Insets getTappableElementInsets();
+    method @NonNull public android.graphics.Insets getInsetsIgnoringVisibility(int);
+    method @Deprecated @NonNull public android.graphics.Insets getMandatorySystemGestureInsets();
+    method @Deprecated public int getStableInsetBottom();
+    method @Deprecated public int getStableInsetLeft();
+    method @Deprecated public int getStableInsetRight();
+    method @Deprecated public int getStableInsetTop();
+    method @Deprecated @NonNull public android.graphics.Insets getStableInsets();
+    method @Deprecated @NonNull public android.graphics.Insets getSystemGestureInsets();
+    method @Deprecated public int getSystemWindowInsetBottom();
+    method @Deprecated public int getSystemWindowInsetLeft();
+    method @Deprecated public int getSystemWindowInsetRight();
+    method @Deprecated public int getSystemWindowInsetTop();
+    method @Deprecated @NonNull public android.graphics.Insets getSystemWindowInsets();
+    method @Deprecated @NonNull public android.graphics.Insets getTappableElementInsets();
     method public boolean hasInsets();
-    method public boolean hasStableInsets();
-    method public boolean hasSystemWindowInsets();
+    method @Deprecated public boolean hasStableInsets();
+    method @Deprecated public boolean hasSystemWindowInsets();
     method @NonNull public android.view.WindowInsets inset(@IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int);
     method public boolean isConsumed();
     method public boolean isRound();
     method public boolean isVisible(int);
     method @Deprecated @NonNull public android.view.WindowInsets replaceSystemWindowInsets(int, int, int, int);
     method @Deprecated @NonNull public android.view.WindowInsets replaceSystemWindowInsets(android.graphics.Rect);
+    field @NonNull public static final android.view.WindowInsets CONSUMED;
   }
 
   public static final class WindowInsets.Builder {
@@ -53203,17 +54542,24 @@
     method @NonNull public android.view.WindowInsets build();
     method @NonNull public android.view.WindowInsets.Builder setDisplayCutout(@Nullable android.view.DisplayCutout);
     method @NonNull public android.view.WindowInsets.Builder setInsets(int, @NonNull android.graphics.Insets);
-    method @NonNull public android.view.WindowInsets.Builder setMandatorySystemGestureInsets(@NonNull android.graphics.Insets);
-    method @NonNull public android.view.WindowInsets.Builder setMaxInsets(int, @NonNull android.graphics.Insets) throws java.lang.IllegalArgumentException;
-    method @NonNull public android.view.WindowInsets.Builder setStableInsets(@NonNull android.graphics.Insets);
-    method @NonNull public android.view.WindowInsets.Builder setSystemGestureInsets(@NonNull android.graphics.Insets);
-    method @NonNull public android.view.WindowInsets.Builder setSystemWindowInsets(@NonNull android.graphics.Insets);
-    method @NonNull public android.view.WindowInsets.Builder setTappableElementInsets(@NonNull android.graphics.Insets);
+    method @NonNull public android.view.WindowInsets.Builder setInsetsIgnoringVisibility(int, @NonNull android.graphics.Insets) throws java.lang.IllegalArgumentException;
+    method @Deprecated @NonNull public android.view.WindowInsets.Builder setMandatorySystemGestureInsets(@NonNull android.graphics.Insets);
+    method @Deprecated @NonNull public android.view.WindowInsets.Builder setStableInsets(@NonNull android.graphics.Insets);
+    method @Deprecated @NonNull public android.view.WindowInsets.Builder setSystemGestureInsets(@NonNull android.graphics.Insets);
+    method @Deprecated @NonNull public android.view.WindowInsets.Builder setSystemWindowInsets(@NonNull android.graphics.Insets);
+    method @Deprecated @NonNull public android.view.WindowInsets.Builder setTappableElementInsets(@NonNull android.graphics.Insets);
     method @NonNull public android.view.WindowInsets.Builder setVisible(int, boolean);
   }
 
-  public static final class WindowInsets.Type {
+  public static final class WindowInsets.Side {
     method public static int all();
+    field public static final int BOTTOM = 8; // 0x8
+    field public static final int LEFT = 1; // 0x1
+    field public static final int RIGHT = 4; // 0x4
+    field public static final int TOP = 2; // 0x2
+  }
+
+  public static final class WindowInsets.Type {
     method public static int captionBar();
     method public static int ime();
     method public static int mandatorySystemGestures();
@@ -53222,13 +54568,16 @@
     method public static int systemBars();
     method public static int systemGestures();
     method public static int tappableElement();
-    method public static int windowDecor();
   }
 
   public interface WindowInsetsAnimationCallback {
-    method public default void onFinished(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
+    method public int getDispatchMode();
+    method public default void onFinish(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
+    method public default void onPrepare(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
     method @NonNull public android.view.WindowInsets onProgress(@NonNull android.view.WindowInsets);
-    method @NonNull public default android.view.WindowInsetsAnimationCallback.AnimationBounds onStarted(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds);
+    method @NonNull public default android.view.WindowInsetsAnimationCallback.AnimationBounds onStart(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds);
+    field public static final int DISPATCH_MODE_CONTINUE_ON_SUBTREE = 1; // 0x1
+    field public static final int DISPATCH_MODE_STOP = 0; // 0x0
   }
 
   public static final class WindowInsetsAnimationCallback.AnimationBounds {
@@ -53246,7 +54595,7 @@
     method public float getInterpolatedFraction();
     method @Nullable public android.view.animation.Interpolator getInterpolator();
     method public int getTypeMask();
-    method public void setDuration(long);
+    method public void setAlpha(@FloatRange(from=0.0f, to=1.0f) float);
     method public void setFraction(@FloatRange(from=0.0f, to=1.0f) float);
   }
 
@@ -53267,17 +54616,27 @@
   }
 
   public interface WindowInsetsController {
-    method public default void controlInputMethodAnimation(long, @NonNull android.view.WindowInsetsAnimationControlListener);
+    method public default void controlInputMethodAnimation(long, @Nullable android.view.animation.Interpolator, @NonNull android.view.WindowInsetsAnimationControlListener);
+    method public void controlWindowInsetsAnimation(int, long, @Nullable android.view.animation.Interpolator, @NonNull android.view.WindowInsetsAnimationControlListener);
+    method public int getSystemBarsAppearance();
+    method public int getSystemBarsBehavior();
+    method public void hide(int);
     method public default void hideInputMethod();
-    method public void setSystemBarsAppearance(int);
+    method public void setSystemBarsAppearance(int, int);
     method public void setSystemBarsBehavior(int);
+    method public void show(int);
     method public default void showInputMethod();
     field public static final int APPEARANCE_LIGHT_NAVIGATION_BARS = 16; // 0x10
     field public static final int APPEARANCE_LIGHT_STATUS_BARS = 8; // 0x8
+    field public static final int BEHAVIOR_SHOW_BARS_BY_SWIPE = 1; // 0x1
+    field public static final int BEHAVIOR_SHOW_BARS_BY_TOUCH = 0; // 0x0
+    field public static final int BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE = 2; // 0x2
   }
 
   public interface WindowManager extends android.view.ViewManager {
-    method public android.view.Display getDefaultDisplay();
+    method @NonNull public default android.view.WindowMetrics getCurrentWindowMetrics();
+    method @Deprecated public android.view.Display getDefaultDisplay();
+    method @NonNull public default android.view.WindowMetrics getMaximumWindowMetrics();
     method public void removeViewImmediate(android.view.View);
   }
 
@@ -53303,9 +54662,15 @@
     method public String debug(String);
     method public int describeContents();
     method public int getColorMode();
+    method public int getFitInsetsSides();
+    method public int getFitInsetsTypes();
     method public final CharSequence getTitle();
+    method public boolean isFitInsetsIgnoringVisibility();
     method public static boolean mayUseInputMethod(int);
     method public void setColorMode(int);
+    method public void setFitInsetsIgnoringVisibility(boolean);
+    method public void setFitInsetsSides(int);
+    method public void setFitInsetsTypes(int);
     method public final void setTitle(CharSequence);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int ALPHA_CHANGED = 128; // 0x80
@@ -53326,13 +54691,13 @@
     field @Deprecated public static final int FLAG_DISMISS_KEYGUARD = 4194304; // 0x400000
     field @Deprecated public static final int FLAG_DITHER = 4096; // 0x1000
     field public static final int FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = -2147483648; // 0x80000000
-    field public static final int FLAG_FORCE_NOT_FULLSCREEN = 2048; // 0x800
-    field public static final int FLAG_FULLSCREEN = 1024; // 0x400
+    field @Deprecated public static final int FLAG_FORCE_NOT_FULLSCREEN = 2048; // 0x800
+    field @Deprecated public static final int FLAG_FULLSCREEN = 1024; // 0x400
     field public static final int FLAG_HARDWARE_ACCELERATED = 16777216; // 0x1000000
     field public static final int FLAG_IGNORE_CHEEK_PRESSES = 32768; // 0x8000
     field public static final int FLAG_KEEP_SCREEN_ON = 128; // 0x80
-    field public static final int FLAG_LAYOUT_ATTACHED_IN_DECOR = 1073741824; // 0x40000000
-    field public static final int FLAG_LAYOUT_INSET_DECOR = 65536; // 0x10000
+    field @Deprecated public static final int FLAG_LAYOUT_ATTACHED_IN_DECOR = 1073741824; // 0x40000000
+    field @Deprecated public static final int FLAG_LAYOUT_INSET_DECOR = 65536; // 0x10000
     field @Deprecated public static final int FLAG_LAYOUT_IN_OVERSCAN = 33554432; // 0x2000000
     field public static final int FLAG_LAYOUT_IN_SCREEN = 256; // 0x100
     field public static final int FLAG_LAYOUT_NO_LIMITS = 512; // 0x200
@@ -53346,8 +54711,8 @@
     field @Deprecated public static final int FLAG_SHOW_WHEN_LOCKED = 524288; // 0x80000
     field public static final int FLAG_SPLIT_TOUCH = 8388608; // 0x800000
     field @Deprecated public static final int FLAG_TOUCHABLE_WHEN_WAKING = 64; // 0x40
-    field public static final int FLAG_TRANSLUCENT_NAVIGATION = 134217728; // 0x8000000
-    field public static final int FLAG_TRANSLUCENT_STATUS = 67108864; // 0x4000000
+    field @Deprecated public static final int FLAG_TRANSLUCENT_NAVIGATION = 134217728; // 0x8000000
+    field @Deprecated public static final int FLAG_TRANSLUCENT_STATUS = 67108864; // 0x4000000
     field @Deprecated public static final int FLAG_TURN_SCREEN_ON = 2097152; // 0x200000
     field public static final int FLAG_WATCH_OUTSIDE_TOUCH = 262144; // 0x40000
     field public static final int FORMAT_CHANGED = 8; // 0x8
@@ -53355,6 +54720,7 @@
     field public static final int LAST_SUB_WINDOW = 1999; // 0x7cf
     field public static final int LAST_SYSTEM_WINDOW = 2999; // 0xbb7
     field public static final int LAYOUT_CHANGED = 1; // 0x1
+    field public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS = 3; // 0x3
     field public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT = 0; // 0x0
     field public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER = 2; // 0x2
     field public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES = 1; // 0x1
@@ -53372,7 +54738,7 @@
     field public static final int SCREEN_ORIENTATION_CHANGED = 1024; // 0x400
     field public static final int SOFT_INPUT_ADJUST_NOTHING = 48; // 0x30
     field public static final int SOFT_INPUT_ADJUST_PAN = 32; // 0x20
-    field public static final int SOFT_INPUT_ADJUST_RESIZE = 16; // 0x10
+    field @Deprecated public static final int SOFT_INPUT_ADJUST_RESIZE = 16; // 0x10
     field public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0; // 0x0
     field public static final int SOFT_INPUT_IS_FORWARD_NAVIGATION = 256; // 0x100
     field public static final int SOFT_INPUT_MASK_ADJUST = 240; // 0xf0
@@ -53429,7 +54795,7 @@
     field public float screenBrightness;
     field public int screenOrientation;
     field public int softInputMode;
-    field public int systemUiVisibility;
+    field @Deprecated public int systemUiVisibility;
     field public android.os.IBinder token;
     field @android.view.ViewDebug.ExportedProperty(mapping={@android.view.ViewDebug.IntToString(from=android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION, to="BASE_APPLICATION"), @android.view.ViewDebug.IntToString(from=android.view.WindowManager.LayoutParams.TYPE_APPLICATION, to="APPLICATION"), @android.view.ViewDebug.IntToString(from=android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING, to="APPLICATION_STARTING"), @android.view.ViewDebug.IntToString(from=android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION, to="DRAWN_APPLICATION"), @android.view.ViewDebug.IntToString(from=android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL, to="APPLICATION_PANEL"), @android.view.ViewDebug.IntToString(from=android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA, to="APPLICATION_MEDIA"), @android.view.ViewDebug.IntToString(from=android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL, to="APPLICATION_SUB_PANEL"), @android.view.ViewDebug.IntToString(from=0x3ed, to="APPLICATION_ABOVE_SUB_PANEL"), @android.view.ViewDebug.IntToString(from=android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG, to="APPLICATION_ATTACHED_DIALOG"), @android.view.ViewDebug.IntToString(from=0x3ec, to="APPLICATION_MEDIA_OVERLAY"), @android.view.ViewDebug.IntToString(from=android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR, to="STATUS_BAR"), @android.view.ViewDebug.IntToString(from=android.view.WindowManager.LayoutParams.TYPE_SEARCH_BAR, to="SEARCH_BAR"), @android.view.ViewDebug.IntToString(from=android.view.WindowManager.LayoutParams.TYPE_PHONE, to="PHONE"), @android.view.ViewDebug.IntToString(from=android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT, to="SYSTEM_ALERT"), @android.view.ViewDebug.IntToString(from=android.view.WindowManager.LayoutParams.TYPE_TOAST, to="TOAST"), @android.view.ViewDebug.IntToString(from=android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY, to="SYSTEM_OVERLAY"), @android.view.ViewDebug.IntToString(from=android.view.WindowManager.LayoutParams.TYPE_PRIORITY_PHONE, to="PRIORITY_PHONE"), @android.view.ViewDebug.IntToString(from=android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG, to="SYSTEM_DIALOG"), @android.view.ViewDebug.IntToString(from=android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG, to="KEYGUARD_DIALOG"), @android.view.ViewDebug.IntToString(from=android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR, to="SYSTEM_ERROR"), @android.view.ViewDebug.IntToString(from=android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD, to="INPUT_METHOD"), @android.view.ViewDebug.IntToString(from=android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG, to="INPUT_METHOD_DIALOG"), @android.view.ViewDebug.IntToString(from=android.view.WindowManager.LayoutParams.TYPE_WALLPAPER, to="WALLPAPER"), @android.view.ViewDebug.IntToString(from=android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, to="STATUS_BAR_PANEL"), @android.view.ViewDebug.IntToString(from=0x7df, to="SECURE_SYSTEM_OVERLAY"), @android.view.ViewDebug.IntToString(from=0x7e0, to="DRAG"), @android.view.ViewDebug.IntToString(from=0x7e1, to="STATUS_BAR_SUB_PANEL"), @android.view.ViewDebug.IntToString(from=0x7e2, to="POINTER"), @android.view.ViewDebug.IntToString(from=0x7e3, to="NAVIGATION_BAR"), @android.view.ViewDebug.IntToString(from=0x7e4, to="VOLUME_OVERLAY"), @android.view.ViewDebug.IntToString(from=0x7e5, to="BOOT_PROGRESS"), @android.view.ViewDebug.IntToString(from=0x7e6, to="INPUT_CONSUMER"), @android.view.ViewDebug.IntToString(from=0x7e7, to="DREAM"), @android.view.ViewDebug.IntToString(from=0x7e8, to="NAVIGATION_BAR_PANEL"), @android.view.ViewDebug.IntToString(from=0x7ea, to="DISPLAY_OVERLAY"), @android.view.ViewDebug.IntToString(from=0x7eb, to="MAGNIFICATION_OVERLAY"), @android.view.ViewDebug.IntToString(from=0x7f5, to="PRESENTATION"), @android.view.ViewDebug.IntToString(from=android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION, to="PRIVATE_PRESENTATION"), @android.view.ViewDebug.IntToString(from=0x7ef, to="VOICE_INTERACTION"), @android.view.ViewDebug.IntToString(from=0x7f1, to="VOICE_INTERACTION_STARTING"), @android.view.ViewDebug.IntToString(from=0x7f2, to="DOCK_DIVIDER"), @android.view.ViewDebug.IntToString(from=0x7f3, to="QS_DIALOG"), @android.view.ViewDebug.IntToString(from=0x7f4, to="SCREENSHOT"), @android.view.ViewDebug.IntToString(from=android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, to="APPLICATION_OVERLAY")}) public int type;
     field public float verticalMargin;
@@ -53439,6 +54805,12 @@
     field @android.view.ViewDebug.ExportedProperty public int y;
   }
 
+  public final class WindowMetrics {
+    ctor public WindowMetrics(@NonNull android.util.Size, @NonNull android.view.WindowInsets);
+    method @NonNull public android.util.Size getSize();
+    method @NonNull public android.view.WindowInsets getWindowInsets();
+  }
+
 }
 
 package android.view.accessibility {
@@ -54536,7 +55908,12 @@
     ctor public EditorInfo();
     method public int describeContents();
     method public void dump(android.util.Printer, String);
+    method @Nullable public CharSequence getInitialSelectedText(int);
+    method @Nullable public CharSequence getInitialTextAfterCursor(int, int);
+    method @Nullable public CharSequence getInitialTextBeforeCursor(int, int);
     method public final void makeCompatible(int);
+    method public void setInitialSurroundingSubText(@NonNull CharSequence, int);
+    method public void setInitialSurroundingText(@NonNull CharSequence);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.EditorInfo> CREATOR;
     field public static final int IME_ACTION_DONE = 6; // 0x6
@@ -54624,6 +56001,7 @@
 
   public final class InlineSuggestionsRequest implements android.os.Parcelable {
     method public int describeContents();
+    method @NonNull public String getHostPackageName();
     method public int getMaxSuggestionCount();
     method @NonNull public java.util.List<android.view.inline.InlinePresentationSpec> getPresentationSpecs();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -55373,7 +56751,7 @@
     method public int describeContents();
     method @NonNull public android.os.Bundle getExtras();
     method @NonNull public java.util.Collection<android.view.textclassifier.TextLinks.TextLink> getLinks();
-    method @NonNull public String getText();
+    method @NonNull public CharSequence getText();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int APPLY_STRATEGY_IGNORE = 0; // 0x0
     field public static final int APPLY_STRATEGY_REPLACE = 1; // 0x1
@@ -58647,7 +60025,7 @@
     method @android.view.ViewDebug.ExportedProperty public CharSequence getFormat24Hour();
     method public String getTimeZone();
     method public boolean is24HourModeEnabled();
-    method public void refresh();
+    method public void refreshTime();
     method public void setFormat12Hour(CharSequence);
     method public void setFormat24Hour(CharSequence);
     method public void setTimeZone(String);
@@ -58962,27 +60340,35 @@
 
   public class Toast {
     ctor public Toast(android.content.Context);
+    method public void addCallback(@NonNull android.widget.Toast.Callback);
     method public void cancel();
     method public int getDuration();
     method public int getGravity();
     method public float getHorizontalMargin();
     method public float getVerticalMargin();
-    method public android.view.View getView();
+    method @Deprecated public android.view.View getView();
     method public int getXOffset();
     method public int getYOffset();
     method public static android.widget.Toast makeText(android.content.Context, CharSequence, int);
     method public static android.widget.Toast makeText(android.content.Context, @StringRes int, int) throws android.content.res.Resources.NotFoundException;
+    method public void removeCallback(@NonNull android.widget.Toast.Callback);
     method public void setDuration(int);
     method public void setGravity(int, int, int);
     method public void setMargin(float, float);
     method public void setText(@StringRes int);
     method public void setText(CharSequence);
-    method public void setView(android.view.View);
+    method @Deprecated public void setView(android.view.View);
     method public void show();
     field public static final int LENGTH_LONG = 1; // 0x1
     field public static final int LENGTH_SHORT = 0; // 0x0
   }
 
+  public abstract static class Toast.Callback {
+    ctor public Toast.Callback();
+    method public void onToastHidden();
+    method public void onToastShown();
+  }
+
   public class ToggleButton extends android.widget.CompoundButton {
     ctor public ToggleButton(android.content.Context, android.util.AttributeSet, int, int);
     ctor public ToggleButton(android.content.Context, android.util.AttributeSet, int);
@@ -71552,6 +72938,18 @@
     method public int lastIndexOf(@Nullable Object);
     method @NonNull public java.util.ListIterator<E> listIterator();
     method @NonNull public java.util.ListIterator<E> listIterator(int);
+    method @NonNull public static <E> java.util.List<E> of();
+    method @NonNull public static <E> java.util.List<E> of(@NonNull E);
+    method @NonNull public static <E> java.util.List<E> of(@NonNull E, @NonNull E);
+    method @NonNull public static <E> java.util.List<E> of(@NonNull E, @NonNull E, @NonNull E);
+    method @NonNull public static <E> java.util.List<E> of(@NonNull E, @NonNull E, @NonNull E, @NonNull E);
+    method @NonNull public static <E> java.util.List<E> of(@NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E);
+    method @NonNull public static <E> java.util.List<E> of(@NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E);
+    method @NonNull public static <E> java.util.List<E> of(@NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E);
+    method @NonNull public static <E> java.util.List<E> of(@NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E);
+    method @NonNull public static <E> java.util.List<E> of(@NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E);
+    method @NonNull public static <E> java.util.List<E> of(@NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E);
+    method @NonNull @java.lang.SafeVarargs public static <E> java.util.List<E> of(@NonNull E...);
     method public E remove(int);
     method public default void replaceAll(@NonNull java.util.function.UnaryOperator<E>);
     method public E set(int, E);
@@ -71706,6 +73104,7 @@
     method @Nullable public default V computeIfPresent(K, @NonNull java.util.function.BiFunction<? super K,? super V,? extends V>);
     method public boolean containsKey(@Nullable Object);
     method public boolean containsValue(@Nullable Object);
+    method @NonNull public static <K, V> java.util.Map.Entry<K,V> entry(@NonNull K, @NonNull V);
     method @NonNull public java.util.Set<java.util.Map.Entry<K,V>> entrySet();
     method public boolean equals(@Nullable Object);
     method public default void forEach(@NonNull java.util.function.BiConsumer<? super K,? super V>);
@@ -71715,6 +73114,18 @@
     method public boolean isEmpty();
     method @NonNull public java.util.Set<K> keySet();
     method @Nullable public default V merge(K, @NonNull V, @NonNull java.util.function.BiFunction<? super V,? super V,? extends V>);
+    method @NonNull public static <K, V> java.util.Map<K,V> of();
+    method @NonNull public static <K, V> java.util.Map<K,V> of(@NonNull K, @NonNull V);
+    method @NonNull public static <K, V> java.util.Map<K,V> of(@NonNull K, @NonNull V, @NonNull K, @NonNull V);
+    method @NonNull public static <K, V> java.util.Map<K,V> of(@NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V);
+    method @NonNull public static <K, V> java.util.Map<K,V> of(@NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V);
+    method @NonNull public static <K, V> java.util.Map<K,V> of(@NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V);
+    method @NonNull public static <K, V> java.util.Map<K,V> of(@NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V);
+    method @NonNull public static <K, V> java.util.Map<K,V> of(@NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V);
+    method @NonNull public static <K, V> java.util.Map<K,V> of(@NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V);
+    method @NonNull public static <K, V> java.util.Map<K,V> of(@NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V);
+    method @NonNull public static <K, V> java.util.Map<K,V> of(@NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V, @NonNull K, @NonNull V);
+    method @NonNull @java.lang.SafeVarargs public static <K, V> java.util.Map<K,V> ofEntries(@NonNull java.util.Map.Entry<? extends K,? extends V>...);
     method @Nullable public V put(K, V);
     method public void putAll(@NonNull java.util.Map<? extends K,? extends V>);
     method @Nullable public default V putIfAbsent(K, V);
@@ -71796,6 +73207,9 @@
   }
 
   public final class Objects {
+    method public static int checkFromIndexSize(int, int, int);
+    method public static int checkFromToIndex(int, int, int);
+    method public static int checkIndex(int, int);
     method public static <T> int compare(T, T, @NonNull java.util.Comparator<? super T>);
     method public static boolean deepEquals(@Nullable Object, @Nullable Object);
     method public static boolean equals(@Nullable Object, @Nullable Object);
@@ -71806,6 +73220,8 @@
     method @NonNull public static <T> T requireNonNull(@Nullable T);
     method @NonNull public static <T> T requireNonNull(@Nullable T, @NonNull String);
     method @NonNull public static <T> T requireNonNull(@Nullable T, @NonNull java.util.function.Supplier<java.lang.String>);
+    method @NonNull public static <T> T requireNonNullElse(@Nullable T, @NonNull T);
+    method @NonNull public static <T> T requireNonNullElseGet(@Nullable T, @NonNull java.util.function.Supplier<? extends T>);
     method @NonNull public static String toString(@Nullable Object);
     method @NonNull public static String toString(@Nullable Object, @NonNull String);
   }
@@ -72110,6 +73526,18 @@
   }
 
   public interface Set<E> extends java.util.Collection<E> {
+    method @NonNull public static <E> java.util.Set<E> of();
+    method @NonNull public static <E> java.util.Set<E> of(@NonNull E);
+    method @NonNull public static <E> java.util.Set<E> of(@NonNull E, @NonNull E);
+    method @NonNull public static <E> java.util.Set<E> of(@NonNull E, @NonNull E, @NonNull E);
+    method @NonNull public static <E> java.util.Set<E> of(@NonNull E, @NonNull E, @NonNull E, @NonNull E);
+    method @NonNull public static <E> java.util.Set<E> of(@NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E);
+    method @NonNull public static <E> java.util.Set<E> of(@NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E);
+    method @NonNull public static <E> java.util.Set<E> of(@NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E);
+    method @NonNull public static <E> java.util.Set<E> of(@NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E);
+    method @NonNull public static <E> java.util.Set<E> of(@NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E);
+    method @NonNull public static <E> java.util.Set<E> of(@NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E, @NonNull E);
+    method @NonNull @java.lang.SafeVarargs public static <E> java.util.Set<E> of(@NonNull E...);
   }
 
   public class SimpleTimeZone extends java.util.TimeZone {
@@ -73048,6 +74476,29 @@
     method public static java.util.concurrent.ScheduledExecutorService unconfigurableScheduledExecutorService(java.util.concurrent.ScheduledExecutorService);
   }
 
+  public final class Flow {
+    method public static int defaultBufferSize();
+  }
+
+  public static interface Flow.Processor<T, R> extends java.util.concurrent.Flow.Subscriber<T> java.util.concurrent.Flow.Publisher<R> {
+  }
+
+  @java.lang.FunctionalInterface public static interface Flow.Publisher<T> {
+    method public void subscribe(java.util.concurrent.Flow.Subscriber<? super T>);
+  }
+
+  public static interface Flow.Subscriber<T> {
+    method public void onComplete();
+    method public void onError(Throwable);
+    method public void onNext(T);
+    method public void onSubscribe(java.util.concurrent.Flow.Subscription);
+  }
+
+  public static interface Flow.Subscription {
+    method public void cancel();
+    method public void request(long);
+  }
+
   public class ForkJoinPool extends java.util.concurrent.AbstractExecutorService {
     ctor public ForkJoinPool();
     ctor public ForkJoinPool(int);
diff --git a/api/lint-baseline.txt b/api/lint-baseline.txt
index 508718e..2a8f04f 100644
--- a/api/lint-baseline.txt
+++ b/api/lint-baseline.txt
@@ -505,10 +505,20 @@
     
 MissingNullability: android.app.SyncNotedAppOp#equals(Object) parameter #0:
     
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#CHORASMIAN:
+    Missing nullability on field `CHORASMIAN` in class `class android.icu.lang.UCharacter.UnicodeBlock`
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#CJK_UNIFIED_IDEOGRAPHS_EXTENSION_G:
+    Missing nullability on field `CJK_UNIFIED_IDEOGRAPHS_EXTENSION_G` in class `class android.icu.lang.UCharacter.UnicodeBlock`
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#DIVES_AKURU:
+    Missing nullability on field `DIVES_AKURU` in class `class android.icu.lang.UCharacter.UnicodeBlock`
 MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#EGYPTIAN_HIEROGLYPH_FORMAT_CONTROLS:
     
 MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#ELYMAIC:
     
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#KHITAN_SMALL_SCRIPT:
+    
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#LISU_SUPPLEMENT:
+    
 MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#NANDINAGARI:
     
 MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#NYIAKENG_PUACHUE_HMONG:
@@ -519,26 +529,34 @@
     
 MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#SYMBOLS_AND_PICTOGRAPHS_EXTENDED_A:
     
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#SYMBOLS_FOR_LEGACY_COMPUTING:
+    
 MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#TAMIL_SUPPLEMENT:
     
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#TANGUT_SUPPLEMENT:
+    
 MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#WANCHO:
     
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#YEZIDI:
+    
 MissingNullability: android.icu.text.DateTimePatternGenerator#getFieldDisplayName(int, android.icu.text.DateTimePatternGenerator.DisplayWidth):
     
 MissingNullability: android.icu.text.DateTimePatternGenerator#getFieldDisplayName(int, android.icu.text.DateTimePatternGenerator.DisplayWidth) parameter #1:
     
 MissingNullability: android.icu.util.MeasureUnit#ATMOSPHERE:
-    Missing nullability on field `ATMOSPHERE` in class `class android.icu.util.MeasureUnit`
+    
 MissingNullability: android.icu.util.MeasureUnit#PERCENT:
-    Missing nullability on field `PERCENT` in class `class android.icu.util.MeasureUnit`
+    
 MissingNullability: android.icu.util.MeasureUnit#PERMILLE:
-    Missing nullability on field `PERMILLE` in class `class android.icu.util.MeasureUnit`
+    
 MissingNullability: android.icu.util.MeasureUnit#PETABYTE:
-    Missing nullability on field `PETABYTE` in class `class android.icu.util.MeasureUnit`
+    
 MissingNullability: android.icu.util.VersionInfo#UNICODE_12_0:
     
 MissingNullability: android.icu.util.VersionInfo#UNICODE_12_1:
     
+MissingNullability: android.icu.util.VersionInfo#UNICODE_13_0:
+    Missing nullability on field `UNICODE_13_0` in class `class android.icu.util.VersionInfo`
 MissingNullability: android.media.MediaMetadataRetriever#getFrameAtTime(long, int, android.media.MediaMetadataRetriever.BitmapParams):
     
 MissingNullability: android.media.MediaMetadataRetriever#getScaledFrameAtTime(long, int, int, int, android.media.MediaMetadataRetriever.BitmapParams):
diff --git a/api/module-app-current.txt b/api/module-app-current.txt
new file mode 100644
index 0000000..dadbd79
--- /dev/null
+++ b/api/module-app-current.txt
@@ -0,0 +1,21 @@
+// Signature format: 2.0
+package android.app {
+
+  public final class NotificationChannel implements android.os.Parcelable {
+    method public void setBlockableSystem(boolean);
+  }
+
+}
+
+package android.provider {
+
+  public final class DocumentsContract {
+    method @NonNull public static android.net.Uri buildDocumentUriAsUser(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
+  }
+
+  public static final class Settings.Global extends android.provider.Settings.NameValueTable {
+    field public static final String COMMON_CRITERIA_MODE = "common_criteria_mode";
+  }
+
+}
+
diff --git a/api/module-app-removed.txt b/api/module-app-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/api/module-app-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt
new file mode 100644
index 0000000..6f4a27e
--- /dev/null
+++ b/api/module-lib-current.txt
@@ -0,0 +1,156 @@
+// Signature format: 2.0
+package android.app.timedetector {
+
+  public final class PhoneTimeSuggestion implements android.os.Parcelable {
+    method public void addDebugInfo(@NonNull String);
+    method public void addDebugInfo(@NonNull java.util.List<java.lang.String>);
+    method public int describeContents();
+    method @NonNull public java.util.List<java.lang.String> getDebugInfo();
+    method public int getPhoneId();
+    method @Nullable public android.os.TimestampedValue<java.lang.Long> getUtcTime();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.timedetector.PhoneTimeSuggestion> CREATOR;
+  }
+
+  public static final class PhoneTimeSuggestion.Builder {
+    ctor public PhoneTimeSuggestion.Builder(int);
+    method @NonNull public android.app.timedetector.PhoneTimeSuggestion.Builder addDebugInfo(@NonNull String);
+    method @NonNull public android.app.timedetector.PhoneTimeSuggestion build();
+    method @NonNull public android.app.timedetector.PhoneTimeSuggestion.Builder setUtcTime(@Nullable android.os.TimestampedValue<java.lang.Long>);
+  }
+
+  public class TimeDetector {
+    method @RequiresPermission("android.permission.SUGGEST_PHONE_TIME_AND_ZONE") public void suggestPhoneTime(@NonNull android.app.timedetector.PhoneTimeSuggestion);
+  }
+
+}
+
+package android.app.timezonedetector {
+
+  public final class PhoneTimeZoneSuggestion implements android.os.Parcelable {
+    method public void addDebugInfo(@NonNull String);
+    method public void addDebugInfo(@NonNull java.util.List<java.lang.String>);
+    method @NonNull public static android.app.timezonedetector.PhoneTimeZoneSuggestion createEmptySuggestion(int, @NonNull String);
+    method public int describeContents();
+    method @NonNull public java.util.List<java.lang.String> getDebugInfo();
+    method public int getMatchType();
+    method public int getPhoneId();
+    method public int getQuality();
+    method @Nullable public String getZoneId();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.timezonedetector.PhoneTimeZoneSuggestion> CREATOR;
+    field public static final int MATCH_TYPE_EMULATOR_ZONE_ID = 4; // 0x4
+    field public static final int MATCH_TYPE_NA = 0; // 0x0
+    field public static final int MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET = 3; // 0x3
+    field public static final int MATCH_TYPE_NETWORK_COUNTRY_ONLY = 2; // 0x2
+    field public static final int MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY = 5; // 0x5
+    field public static final int QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS = 3; // 0x3
+    field public static final int QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET = 2; // 0x2
+    field public static final int QUALITY_NA = 0; // 0x0
+    field public static final int QUALITY_SINGLE_ZONE = 1; // 0x1
+  }
+
+  public static final class PhoneTimeZoneSuggestion.Builder {
+    ctor public PhoneTimeZoneSuggestion.Builder(int);
+    method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder addDebugInfo(@NonNull String);
+    method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion build();
+    method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder setMatchType(int);
+    method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder setQuality(int);
+    method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder setZoneId(@Nullable String);
+  }
+
+  public class TimeZoneDetector {
+    method @RequiresPermission("android.permission.SUGGEST_PHONE_TIME_AND_ZONE") public void suggestPhoneTimeZone(@NonNull android.app.timezonedetector.PhoneTimeZoneSuggestion);
+  }
+
+}
+
+package android.os {
+
+  public final class TimestampedValue<T> implements android.os.Parcelable {
+    ctor public TimestampedValue(long, @Nullable T);
+    method public int describeContents();
+    method public long getReferenceTimeMillis();
+    method @Nullable public T getValue();
+    method public static long referenceTimeDifference(@NonNull android.os.TimestampedValue<?>, @NonNull android.os.TimestampedValue<?>);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.os.TimestampedValue<?>> CREATOR;
+  }
+
+}
+
+package android.timezone {
+
+  public final class CountryTimeZones {
+    method @Nullable public android.icu.util.TimeZone getDefaultTimeZone();
+    method @Nullable public String getDefaultTimeZoneId();
+    method @NonNull public java.util.List<android.timezone.CountryTimeZones.TimeZoneMapping> getEffectiveTimeZoneMappingsAt(long);
+    method public boolean hasUtcZone(long);
+    method public boolean isDefaultTimeZoneBoosted();
+    method public boolean isForCountryCode(@NonNull String);
+    method @Nullable public android.timezone.CountryTimeZones.OffsetResult lookupByOffsetWithBias(int, @Nullable Boolean, @Nullable Integer, long, @Nullable android.icu.util.TimeZone);
+  }
+
+  public static final class CountryTimeZones.OffsetResult {
+    ctor public CountryTimeZones.OffsetResult(@NonNull android.icu.util.TimeZone, boolean);
+    method @NonNull public android.icu.util.TimeZone getTimeZone();
+    method public boolean isOnlyMatch();
+  }
+
+  public static final class CountryTimeZones.TimeZoneMapping {
+    method @Nullable public android.icu.util.TimeZone getTimeZone();
+    method @NonNull public String getTimeZoneId();
+  }
+
+  public class TelephonyLookup {
+    method @NonNull public static android.timezone.TelephonyLookup getInstance();
+    method @Nullable public android.timezone.TelephonyNetworkFinder getTelephonyNetworkFinder();
+  }
+
+  public class TelephonyNetwork {
+    method @NonNull public String getCountryIsoCode();
+    method @NonNull public String getMcc();
+    method @NonNull public String getMnc();
+  }
+
+  public class TelephonyNetworkFinder {
+    method @Nullable public android.timezone.TelephonyNetwork findNetworkByMccMnc(@NonNull String, @NonNull String);
+  }
+
+  public final class TimeZoneFinder {
+    method @Nullable public String getIanaVersion();
+    method @NonNull public static android.timezone.TimeZoneFinder getInstance();
+    method @Nullable public android.timezone.CountryTimeZones lookupCountryTimeZones(@NonNull String);
+  }
+
+  public final class TzDataSetVersion {
+    method public static int currentFormatMajorVersion();
+    method public static int currentFormatMinorVersion();
+    method public int getFormatMajorVersion();
+    method public int getFormatMinorVersion();
+    method public int getRevision();
+    method @NonNull public String getRulesVersion();
+    method public static boolean isCompatibleWithThisDevice(android.timezone.TzDataSetVersion);
+    method @NonNull public static android.timezone.TzDataSetVersion read() throws java.io.IOException, android.timezone.TzDataSetVersion.TzDataSetException;
+  }
+
+  public static class TzDataSetVersion.TzDataSetException extends java.lang.Exception {
+    ctor public TzDataSetVersion.TzDataSetException(String);
+    ctor public TzDataSetVersion.TzDataSetException(String, Throwable);
+  }
+
+  public final class ZoneInfoDb {
+    method @NonNull public static android.timezone.ZoneInfoDb getInstance();
+    method @NonNull public String getVersion();
+  }
+
+}
+
+package android.util {
+
+  public final class Log {
+    method public static int logToRadioBuffer(int, @Nullable String, @Nullable String);
+  }
+
+}
+
diff --git a/api/module-lib-removed.txt b/api/module-lib-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/api/module-lib-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/api/system-current.txt b/api/system-current.txt
index 33e3f5b..0cc1af4 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -55,18 +55,22 @@
     field public static final String CAPTURE_AUDIO_HOTWORD = "android.permission.CAPTURE_AUDIO_HOTWORD";
     field public static final String CAPTURE_MEDIA_OUTPUT = "android.permission.CAPTURE_MEDIA_OUTPUT";
     field public static final String CAPTURE_TV_INPUT = "android.permission.CAPTURE_TV_INPUT";
+    field public static final String CAPTURE_VOICE_COMMUNICATION_OUTPUT = "android.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT";
     field public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE";
     field public static final String CHANGE_DEVICE_IDLE_TEMP_WHITELIST = "android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST";
     field public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA";
+    field public static final String COMPANION_APPROVE_WIFI_CONNECTIONS = "android.permission.COMPANION_APPROVE_WIFI_CONNECTIONS";
     field public static final String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS";
     field public static final String CONFIGURE_WIFI_DISPLAY = "android.permission.CONFIGURE_WIFI_DISPLAY";
     field @Deprecated public static final String CONNECTIVITY_INTERNAL = "android.permission.CONNECTIVITY_INTERNAL";
     field public static final String CONNECTIVITY_USE_RESTRICTED_NETWORKS = "android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS";
+    field public static final String CONTROL_DEVICE_LIGHTS = "android.permission.CONTROL_DEVICE_LIGHTS";
     field public static final String CONTROL_DISPLAY_COLOR_TRANSFORMS = "android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS";
     field public static final String CONTROL_DISPLAY_SATURATION = "android.permission.CONTROL_DISPLAY_SATURATION";
     field public static final String CONTROL_INCALL_EXPERIENCE = "android.permission.CONTROL_INCALL_EXPERIENCE";
     field public static final String CONTROL_KEYGUARD_SECURE_NOTIFICATIONS = "android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS";
     field public static final String CONTROL_VPN = "android.permission.CONTROL_VPN";
+    field public static final String CREATE_USERS = "android.permission.CREATE_USERS";
     field public static final String CRYPT_KEEPER = "android.permission.CRYPT_KEEPER";
     field public static final String DEVICE_POWER = "android.permission.DEVICE_POWER";
     field public static final String DISPATCH_PROVISIONING_MESSAGE = "android.permission.DISPATCH_PROVISIONING_MESSAGE";
@@ -95,6 +99,7 @@
     field public static final String INTERNAL_SYSTEM_WINDOW = "android.permission.INTERNAL_SYSTEM_WINDOW";
     field public static final String INVOKE_CARRIER_SETUP = "android.permission.INVOKE_CARRIER_SETUP";
     field public static final String KILL_UID = "android.permission.KILL_UID";
+    field public static final String LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH = "android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH";
     field public static final String LOCAL_MAC_ADDRESS = "android.permission.LOCAL_MAC_ADDRESS";
     field public static final String LOCK_DEVICE = "android.permission.LOCK_DEVICE";
     field public static final String LOOP_RADIO = "android.permission.LOOP_RADIO";
@@ -126,6 +131,8 @@
     field @Deprecated public static final String MODIFY_NETWORK_ACCOUNTING = "android.permission.MODIFY_NETWORK_ACCOUNTING";
     field public static final String MODIFY_PARENTAL_CONTROLS = "android.permission.MODIFY_PARENTAL_CONTROLS";
     field public static final String MODIFY_QUIET_MODE = "android.permission.MODIFY_QUIET_MODE";
+    field public static final String MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE = "android.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE";
+    field public static final String MONITOR_DEVICE_CONFIG_ACCESS = "android.permission.MONITOR_DEVICE_CONFIG_ACCESS";
     field public static final String MOVE_PACKAGE = "android.permission.MOVE_PACKAGE";
     field public static final String NETWORK_AIRPLANE_MODE = "android.permission.NETWORK_AIRPLANE_MODE";
     field public static final String NETWORK_CARRIER_PROVISIONING = "android.permission.NETWORK_CARRIER_PROVISIONING";
@@ -152,7 +159,9 @@
     field public static final String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE";
     field public static final String PROVIDE_TRUST_AGENT = "android.permission.PROVIDE_TRUST_AGENT";
     field public static final String QUERY_TIME_ZONE_RULES = "android.permission.QUERY_TIME_ZONE_RULES";
+    field public static final String RADIO_SCAN_WITHOUT_LOCATION = "android.permission.RADIO_SCAN_WITHOUT_LOCATION";
     field public static final String READ_ACTIVE_EMERGENCY_SESSION = "android.permission.READ_ACTIVE_EMERGENCY_SESSION";
+    field public static final String READ_CARRIER_APP_INFO = "android.permission.READ_CARRIER_APP_INFO";
     field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
     field public static final String READ_CONTENT_RATING_SYSTEMS = "android.permission.READ_CONTENT_RATING_SYSTEMS";
     field public static final String READ_DEVICE_CONFIG = "android.permission.READ_DEVICE_CONFIG";
@@ -181,6 +190,7 @@
     field public static final String REMOTE_DISPLAY_PROVIDER = "android.permission.REMOTE_DISPLAY_PROVIDER";
     field public static final String REMOVE_DRM_CERTIFICATES = "android.permission.REMOVE_DRM_CERTIFICATES";
     field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
+    field public static final String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES";
     field public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE";
     field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD";
     field public static final String RESTORE_RUNTIME_PERMISSIONS = "android.permission.RESTORE_RUNTIME_PERMISSIONS";
@@ -189,6 +199,7 @@
     field public static final String REVIEW_ACCESSIBILITY_SERVICES = "android.permission.REVIEW_ACCESSIBILITY_SERVICES";
     field public static final String REVOKE_RUNTIME_PERMISSIONS = "android.permission.REVOKE_RUNTIME_PERMISSIONS";
     field public static final String SCORE_NETWORKS = "android.permission.SCORE_NETWORKS";
+    field public static final String SECURE_ELEMENT_PRIVILEGED = "android.permission.SECURE_ELEMENT_PRIVILEGED";
     field public static final String SEND_DEVICE_CUSTOMIZATION_READY = "android.permission.SEND_DEVICE_CUSTOMIZATION_READY";
     field public static final String SEND_SHOW_SUSPENDED_APP_DETAILS = "android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS";
     field public static final String SEND_SMS_NO_CONFIRMATION = "android.permission.SEND_SMS_NO_CONFIRMATION";
@@ -206,9 +217,11 @@
     field public static final String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES";
     field public static final String SUBSTITUTE_NOTIFICATION_APP_NAME = "android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME";
     field public static final String SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON = "android.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON";
+    field public static final String SUGGEST_PHONE_TIME_AND_ZONE = "android.permission.SUGGEST_PHONE_TIME_AND_ZONE";
     field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
     field public static final String SYSTEM_CAMERA = "android.permission.SYSTEM_CAMERA";
     field public static final String TETHER_PRIVILEGED = "android.permission.TETHER_PRIVILEGED";
+    field public static final String TUNER_RESOURCE_ACCESS = "android.permission.TUNER_RESOURCE_ACCESS";
     field public static final String TV_INPUT_HARDWARE = "android.permission.TV_INPUT_HARDWARE";
     field public static final String TV_VIRTUAL_REMOTE_CONTROLLER = "android.permission.TV_VIRTUAL_REMOTE_CONTROLLER";
     field public static final String UNLIMITED_SHORTCUTS_API_CALLS = "android.permission.UNLIMITED_SHORTCUTS_API_CALLS";
@@ -223,7 +236,7 @@
     field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG";
     field public static final String WRITE_DREAM_STATE = "android.permission.WRITE_DREAM_STATE";
     field public static final String WRITE_EMBEDDED_SUBSCRIPTIONS = "android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS";
-    field public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE";
+    field @Deprecated public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE";
     field public static final String WRITE_OBB = "android.permission.WRITE_OBB";
   }
 
@@ -238,9 +251,10 @@
   public static final class R.attr {
     field public static final int allowClearUserDataOnFailedRestore = 16844288; // 0x1010600
     field public static final int isVrOnly = 16844152; // 0x1010578
+    field public static final int minExtensionVersion = 16844306; // 0x1010612
     field public static final int requiredSystemPropertyName = 16844133; // 0x1010565
     field public static final int requiredSystemPropertyValue = 16844134; // 0x1010566
-    field public static final int resourcesMap = 16844297; // 0x1010609
+    field public static final int sdkVersion = 16844305; // 0x1010611
     field public static final int supportsAmbientMode = 16844173; // 0x101058d
     field public static final int userRestriction = 16844164; // 0x1010584
   }
@@ -282,6 +296,7 @@
     field public static final int config_helpIntentNameKey = 17039390; // 0x104001e
     field public static final int config_helpPackageNameKey = 17039387; // 0x104001b
     field public static final int config_helpPackageNameValue = 17039388; // 0x104001c
+    field public static final int config_systemGallery = 17039402; // 0x104002a
   }
 
   public static final class R.style {
@@ -319,11 +334,16 @@
     method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getPackageImportance(String);
     method @NonNull public java.util.Collection<java.util.Locale> getSupportedLocales();
     method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getUidImportance(int);
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isProfileForeground(@NonNull android.os.UserHandle);
+    method @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) public void killProcessesWhenImperceptible(@NonNull int[], @NonNull String);
     method @RequiresPermission(android.Manifest.permission.KILL_UID) public void killUid(int, String);
+    method public void registerHomeVisibilityObserver(@NonNull android.app.HomeVisibilityObserver);
     method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void removeOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener);
     method public void setDeviceLocales(@NonNull android.os.LocaleList);
     method @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS) public static void setPersistentVrThread(int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean switchUser(@NonNull android.os.UserHandle);
+    method public void unregisterHomeVisibilityObserver(@NonNull android.app.HomeVisibilityObserver);
+    method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String);
   }
 
   public static interface ActivityManager.OnUidImportanceListener {
@@ -373,6 +393,7 @@
     field public static final String OPSTR_POST_NOTIFICATION = "android:post_notification";
     field public static final String OPSTR_PROJECT_MEDIA = "android:project_media";
     field public static final String OPSTR_READ_CLIPBOARD = "android:read_clipboard";
+    field public static final String OPSTR_READ_DEVICE_IDENTIFIERS = "android:read_device_identifiers";
     field public static final String OPSTR_READ_ICC_SMS = "android:read_icc_sms";
     field public static final String OPSTR_READ_MEDIA_AUDIO = "android:read_media_audio";
     field public static final String OPSTR_READ_MEDIA_IMAGES = "android:read_media_images";
@@ -558,6 +579,10 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.PackageOps> CREATOR;
   }
 
+  public final class ApplicationExitInfo implements android.os.Parcelable {
+    method @NonNull public android.os.UserHandle getUserHandle();
+  }
+
   public class BroadcastOptions {
     method public static android.app.BroadcastOptions makeBasic();
     method @RequiresPermission("android.permission.START_ACTIVITIES_FROM_BACKGROUND") public void setBackgroundActivityStartsAllowed(boolean);
@@ -567,9 +592,15 @@
   }
 
   public class DownloadManager {
+    method @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public void onMediaStoreDownloadsDeleted(@NonNull android.util.LongSparseArray<java.lang.String>);
     field public static final String ACTION_DOWNLOAD_COMPLETED = "android.intent.action.DOWNLOAD_COMPLETED";
   }
 
+  public abstract class HomeVisibilityObserver {
+    ctor public HomeVisibilityObserver();
+    method public abstract void onHomeVisibilityChanged(boolean);
+  }
+
   public abstract class InstantAppResolverService extends android.app.Service {
     ctor public InstantAppResolverService();
     method public final void attachBaseContext(android.content.Context);
@@ -652,6 +683,7 @@
     method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public long[] getRegisteredExperimentIds() throws android.app.StatsManager.StatsUnavailableException;
     method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public byte[] getReports(long) throws android.app.StatsManager.StatsUnavailableException;
     method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public byte[] getStatsMetadata() throws android.app.StatsManager.StatsUnavailableException;
+    method public void registerPullAtomCallback(int, @Nullable android.app.StatsManager.PullAtomMetadata, @NonNull java.util.concurrent.Executor, @NonNull android.app.StatsManager.StatsPullAtomCallback);
     method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void removeConfig(long) throws android.app.StatsManager.StatsUnavailableException;
     method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean removeConfiguration(long);
     method @NonNull @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public long[] setActiveConfigsChangedOperation(@Nullable android.app.PendingIntent) throws android.app.StatsManager.StatsUnavailableException;
@@ -659,6 +691,7 @@
     method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean setBroadcastSubscriber(long, long, android.app.PendingIntent);
     method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean setDataFetchOperation(long, android.app.PendingIntent);
     method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void setFetchReportsOperation(android.app.PendingIntent, long) throws android.app.StatsManager.StatsUnavailableException;
+    method public void unregisterPullAtomCallback(int);
     field public static final String ACTION_STATSD_STARTED = "android.app.action.STATSD_STARTED";
     field public static final String EXTRA_STATS_ACTIVE_CONFIG_KEYS = "android.app.extra.STATS_ACTIVE_CONFIG_KEYS";
     field public static final String EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES = "android.app.extra.STATS_BROADCAST_SUBSCRIBER_COOKIES";
@@ -667,6 +700,23 @@
     field public static final String EXTRA_STATS_DIMENSIONS_VALUE = "android.app.extra.STATS_DIMENSIONS_VALUE";
     field public static final String EXTRA_STATS_SUBSCRIPTION_ID = "android.app.extra.STATS_SUBSCRIPTION_ID";
     field public static final String EXTRA_STATS_SUBSCRIPTION_RULE_ID = "android.app.extra.STATS_SUBSCRIPTION_RULE_ID";
+    field public static final int PULL_SKIP = 1; // 0x1
+    field public static final int PULL_SUCCESS = 0; // 0x0
+  }
+
+  public static class StatsManager.PullAtomMetadata {
+  }
+
+  public static class StatsManager.PullAtomMetadata.Builder {
+    ctor public StatsManager.PullAtomMetadata.Builder();
+    method @NonNull public android.app.StatsManager.PullAtomMetadata build();
+    method @NonNull public android.app.StatsManager.PullAtomMetadata.Builder setAdditiveFields(@NonNull int[]);
+    method @NonNull public android.app.StatsManager.PullAtomMetadata.Builder setCoolDownNs(long);
+    method @NonNull public android.app.StatsManager.PullAtomMetadata.Builder setTimeoutNs(long);
+  }
+
+  public static interface StatsManager.StatsPullAtomCallback {
+    method public int onPullAtom(int, @NonNull java.util.List<android.util.StatsEvent>);
   }
 
   public static class StatsManager.StatsUnavailableException extends android.util.AndroidException {
@@ -677,6 +727,7 @@
   public class StatusBarManager {
     method @NonNull @RequiresPermission(android.Manifest.permission.STATUS_BAR) public android.app.StatusBarManager.DisableInfo getDisableInfo();
     method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSetup(boolean);
+    method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSimNetworkLock(boolean);
   }
 
   public static final class StatusBarManager.DisableInfo {
@@ -784,6 +835,7 @@
 package android.app.admin {
 
   public class DevicePolicyManager {
+    method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwner();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.content.ComponentName getDeviceOwnerComponentOnAnyUser();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwnerNameOnAnyUser();
@@ -799,7 +851,7 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioningConfigApplied();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedKiosk();
-    method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isOrganizationOwnedDeviceWithManagedProfile();
+    method public boolean isSecondaryLockscreenEnabled(int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isUnattendedManagedKiosk();
     method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long);
     method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long, boolean);
@@ -810,7 +862,9 @@
     field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED";
     field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED";
     field public static final String ACTION_PROVISION_FINALIZATION = "android.app.action.PROVISION_FINALIZATION";
+    field public static final String ACTION_PROVISION_FINANCED_DEVICE = "android.app.action.PROVISION_FINANCED_DEVICE";
     field public static final String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
+    field public static final String ACTION_RESET_PROTECTION_POLICY_CHANGED = "android.app.action.RESET_PROTECTION_POLICY_CHANGED";
     field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
     field public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE";
     field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME";
@@ -1028,6 +1082,7 @@
     field public static final int AGENT_ERROR = -1003; // 0xfffffc15
     field public static final int AGENT_UNKNOWN = -1004; // 0xfffffc14
     field public static final String EXTRA_TRANSPORT_REGISTRATION = "android.app.backup.extra.TRANSPORT_REGISTRATION";
+    field public static final int FLAG_DATA_NOT_CHANGED = 8; // 0x8
     field public static final int FLAG_INCREMENTAL = 2; // 0x2
     field public static final int FLAG_NON_INCREMENTAL = 4; // 0x4
     field public static final int FLAG_USER_INITIATED = 1; // 0x1
@@ -1125,6 +1180,7 @@
     method public void classifyContentSelections(@NonNull android.app.contentsuggestions.ClassificationsRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.contentsuggestions.ContentSuggestionsManager.ClassificationsCallback);
     method public boolean isEnabled();
     method public void notifyInteraction(@NonNull String, @NonNull android.os.Bundle);
+    method public void provideContextImage(@NonNull android.graphics.Bitmap, @NonNull android.os.Bundle);
     method public void provideContextImage(int, @NonNull android.os.Bundle);
     method public void suggestContentSelections(@NonNull android.app.contentsuggestions.SelectionsRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.contentsuggestions.ContentSuggestionsManager.SelectionsCallback);
   }
@@ -1327,6 +1383,10 @@
     field public static final String SERVICE_INTERFACE = "android.app.usage.CacheQuotaService";
   }
 
+  public class NetworkStatsManager {
+    method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.net.netstats.provider.NetworkStatsProviderCallback registerNetworkStatsProvider(@NonNull String, @NonNull android.net.netstats.provider.AbstractNetworkStatsProvider);
+  }
+
   public static final class UsageEvents.Event {
     method public int getInstanceId();
     method @Nullable public String getNotificationChannelId();
@@ -1533,6 +1593,10 @@
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
   }
 
+  public final class BluetoothHidDevice implements android.bluetooth.BluetoothProfile {
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
+  }
+
   public final class BluetoothHidHost implements android.bluetooth.BluetoothProfile {
     method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice);
@@ -1541,12 +1605,21 @@
     field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED";
   }
 
-  public final class BluetoothPan implements android.bluetooth.BluetoothProfile {
-    method protected void finalize();
+  public final class BluetoothMap implements android.bluetooth.BluetoothProfile {
     method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
-    method public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice);
-    method public boolean isTetheringOn();
-    method public void setBluetoothTethering(boolean);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice, int);
+    field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED";
+  }
+
+  public final class BluetoothPan implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile {
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public void close();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) protected void finalize();
+    method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isTetheringOn();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public void setBluetoothTethering(boolean);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
     field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED";
     field public static final String EXTRA_LOCAL_ROLE = "android.bluetooth.pan.extra.LOCAL_ROLE";
     field public static final int LOCAL_NAP_ROLE = 1; // 0x1
@@ -1563,10 +1636,14 @@
   }
 
   public interface BluetoothProfile {
+    field public static final int A2DP_SINK = 11; // 0xb
+    field public static final int AVRCP_CONTROLLER = 12; // 0xc
     field public static final int CONNECTION_POLICY_ALLOWED = 100; // 0x64
     field public static final int CONNECTION_POLICY_FORBIDDEN = 0; // 0x0
     field public static final int CONNECTION_POLICY_UNKNOWN = -1; // 0xffffffff
+    field public static final int HEADSET_CLIENT = 16; // 0x10
     field public static final int PAN = 5; // 0x5
+    field public static final int PBAP_CLIENT = 17; // 0x11
     field @Deprecated public static final int PRIORITY_OFF = 0; // 0x0
     field @Deprecated public static final int PRIORITY_ON = 100; // 0x64
   }
@@ -1642,28 +1719,40 @@
 package android.companion {
 
   public final class CompanionDeviceManager {
-    method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean isDeviceAssociated(@NonNull String, @NonNull android.net.MacAddress, @NonNull android.os.UserHandle);
+    method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean isDeviceAssociatedForWifiConnection(@NonNull String, @NonNull android.net.MacAddress, @NonNull android.os.UserHandle);
   }
 
 }
 
 package android.content {
 
+  public class ApexContext {
+    method @NonNull public static android.content.ApexContext getApexContext(@NonNull String);
+    method @NonNull public java.io.File getCredentialProtectedDataDirForUser(@NonNull android.os.UserHandle);
+    method @NonNull public java.io.File getDeviceProtectedDataDir();
+    method @NonNull public java.io.File getDeviceProtectedDataDirForUser(@NonNull android.os.UserHandle);
+  }
+
   public abstract class BroadcastReceiver {
     method @NonNull public final android.os.UserHandle getSendingUser();
   }
 
+  public abstract class ContentProvider implements android.content.ComponentCallbacks2 {
+    method public int checkUriPermission(@NonNull android.net.Uri, int, int);
+  }
+
   public class ContentProviderClient implements java.lang.AutoCloseable {
     method @RequiresPermission(android.Manifest.permission.REMOVE_TASKS) public void setDetectNotResponding(long);
   }
 
   public abstract class ContentResolver {
+    method @NonNull public static android.net.Uri decodeFromFile(@NonNull java.io.File);
+    method @NonNull public static java.io.File encodeToFile(@NonNull android.net.Uri);
     method @Nullable @RequiresPermission("android.permission.CACHE_CONTENT") public android.os.Bundle getCache(@NonNull android.net.Uri);
     method @RequiresPermission("android.permission.CACHE_CONTENT") public void putCache(@NonNull android.net.Uri, @Nullable android.os.Bundle);
   }
 
   public abstract class Context {
-    method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean bindServiceAsUser(@RequiresPermission android.content.Intent, android.content.ServiceConnection, int, android.os.UserHandle);
     method @NonNull public android.content.Context createContextAsUser(@NonNull android.os.UserHandle, int);
     method public abstract android.content.Context createCredentialProtectedStorageContext();
     method @NonNull public android.content.Context createPackageContextAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -1673,7 +1762,7 @@
     method public abstract void sendBroadcast(android.content.Intent, @Nullable String, @Nullable android.os.Bundle);
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, @Nullable String, @Nullable android.os.Bundle);
     method public abstract void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable android.os.Bundle, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
-    method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle);
+    method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle);
     field public static final String APP_INTEGRITY_SERVICE = "app_integrity";
     field public static final String APP_PREDICTION_SERVICE = "app_prediction";
     field public static final String BACKUP_SERVICE = "backup";
@@ -1686,6 +1775,7 @@
     field public static final String NETD_SERVICE = "netd";
     field public static final String NETWORK_POLICY_SERVICE = "netpolicy";
     field public static final String NETWORK_SCORE_SERVICE = "network_score";
+    field public static final String NETWORK_STACK_SERVICE = "network_stack";
     field public static final String OEM_LOCK_SERVICE = "oem_lock";
     field public static final String PERMISSION_SERVICE = "permission";
     field public static final String PERSISTENT_DATA_BLOCK_SERVICE = "persistent_data_block";
@@ -1693,11 +1783,13 @@
     field public static final String SECURE_ELEMENT_SERVICE = "secure_element";
     field public static final String STATS_MANAGER = "stats";
     field public static final String STATUS_BAR_SERVICE = "statusbar";
+    field public static final String SYSTEM_CONFIG_SERVICE = "system_config";
     field public static final String SYSTEM_UPDATE_SERVICE = "system_update";
     field public static final String TELEPHONY_IMS_SERVICE = "telephony_ims";
     field public static final String TELEPHONY_REGISTRY_SERVICE = "telephony_registry";
     field public static final String TETHERING_SERVICE = "tethering";
     field public static final String VR_SERVICE = "vrmanager";
+    field public static final String WIFI_COND_SERVICE = "wificond";
     field @Deprecated public static final String WIFI_RTT_SERVICE = "rttmanager";
     field public static final String WIFI_SCANNING_SERVICE = "wifiscanner";
   }
@@ -1764,6 +1856,7 @@
     field public static final String EXTRA_PERMISSION_GROUP_NAME = "android.intent.extra.PERMISSION_GROUP_NAME";
     field public static final String EXTRA_PERMISSION_NAME = "android.intent.extra.PERMISSION_NAME";
     field public static final String EXTRA_REASON = "android.intent.extra.REASON";
+    field @Deprecated public static final String EXTRA_REBROADCAST_ON_UNLOCK = "rebroadcastOnUnlock";
     field public static final String EXTRA_REMOTE_CALLBACK = "android.intent.extra.REMOTE_CALLBACK";
     field public static final String EXTRA_RESULT_NEEDED = "android.intent.extra.RESULT_NEEDED";
     field public static final String EXTRA_ROLE_NAME = "android.intent.extra.ROLE_NAME";
@@ -1771,6 +1864,7 @@
     field @Deprecated public static final String EXTRA_SIM_STATE = "ss";
     field public static final String EXTRA_UNKNOWN_INSTANT_APP = "android.intent.extra.UNKNOWN_INSTANT_APP";
     field public static final String EXTRA_VERIFICATION_BUNDLE = "android.intent.extra.VERIFICATION_BUNDLE";
+    field public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 67108864; // 0x4000000
     field public static final String METADATA_SETUP_VERSION = "android.SETUP_VERSION";
     field @Deprecated public static final String SIM_ABSENT_ON_PERM_DISABLED = "PERM_DISABLED";
     field @Deprecated public static final String SIM_LOCKED_NETWORK = "NETWORK";
@@ -1969,6 +2063,21 @@
     method @NonNull public final int getType();
   }
 
+  public final class InstallationFile implements android.os.Parcelable {
+    ctor public InstallationFile(@NonNull String, long, @Nullable byte[]);
+    method public int describeContents();
+    method public int getFileType();
+    method @Nullable public byte[] getMetadata();
+    method @NonNull public String getName();
+    method public long getSize();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.InstallationFile> CREATOR;
+    field public static final int FILE_TYPE_APK = 0; // 0x0
+    field public static final int FILE_TYPE_LIB = 1; // 0x1
+    field public static final int FILE_TYPE_OBB = 2; // 0x2
+    field public static final int FILE_TYPE_UNKNOWN = -1; // 0xffffffff
+  }
+
   public final class InstantAppInfo implements android.os.Parcelable {
     ctor public InstantAppInfo(android.content.pm.ApplicationInfo, String[], String[]);
     ctor public InstantAppInfo(String, CharSequence, String[], String[]);
@@ -2055,11 +2164,20 @@
 
   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
+    field public static final int DATA_LOADER_TYPE_NONE = 0; // 0x0
+    field public static final int DATA_LOADER_TYPE_STREAMING = 1; // 0x1
+    field public static final String EXTRA_DATA_LOADER_TYPE = "android.content.pm.extra.DATA_LOADER_TYPE";
+    field public static final int LOCATION_DATA_APP = 0; // 0x0
+    field public static final int LOCATION_MEDIA_DATA = 2; // 0x2
+    field public static final int LOCATION_MEDIA_OBB = 1; // 0x1
   }
 
   public static class PackageInstaller.Session implements java.io.Closeable {
-    method public void addFile(@NonNull String, long, @NonNull byte[]);
+    method public void addFile(int, @NonNull String, long, @NonNull byte[], @Nullable byte[]);
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void commitTransferred(@NonNull android.content.IntentSender);
+    method @Nullable public android.content.pm.DataLoaderParams getDataLoaderParams();
+    method public void removeFile(int, @NonNull String);
   }
 
   public static class PackageInstaller.SessionInfo implements android.os.Parcelable {
@@ -2208,6 +2326,7 @@
     field public static final int MATCH_ANY_USER = 4194304; // 0x400000
     field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000
     field public static final int MATCH_INSTANT = 8388608; // 0x800000
+    field public static final int MODULE_APEX_NAME = 1; // 0x1
     field public static final int RESTRICTION_HIDE_FROM_SUGGESTIONS = 1; // 0x1
     field public static final int RESTRICTION_HIDE_NOTIFICATIONS = 2; // 0x2
     field public static final int RESTRICTION_NONE = 0; // 0x0
@@ -2356,6 +2475,15 @@
 
 }
 
+package android.debug {
+
+  public class AdbManager {
+    method @RequiresPermission(android.Manifest.permission.MANAGE_DEBUGGING) public boolean isAdbWifiQrSupported();
+    method @RequiresPermission(android.Manifest.permission.MANAGE_DEBUGGING) public boolean isAdbWifiSupported();
+  }
+
+}
+
 package android.hardware {
 
   public final class Sensor {
@@ -2386,7 +2514,7 @@
 package android.hardware.camera2 {
 
   public abstract class CameraDevice implements java.lang.AutoCloseable {
-    method public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+    method @Deprecated public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     field public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED = 1; // 0x1
     field public static final int SESSION_OPERATION_MODE_NORMAL = 0; // 0x0
     field public static final int SESSION_OPERATION_MODE_VENDOR_START = 32768; // 0x8000
@@ -2820,6 +2948,48 @@
 
 }
 
+package android.hardware.lights {
+
+  public final class Light implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getId();
+    method public int getOrdinal();
+    method public int getType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.lights.Light> CREATOR;
+  }
+
+  public final class LightState implements android.os.Parcelable {
+    ctor public LightState(@ColorInt int);
+    method public int describeContents();
+    method @ColorInt public int getColor();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.lights.LightState> CREATOR;
+  }
+
+  public final class LightsManager {
+    method @NonNull @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS) public java.util.List<android.hardware.lights.Light> getLights();
+    method @NonNull @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS) public android.hardware.lights.LightsManager.LightsSession openSession();
+    field public static final int LIGHT_TYPE_MICROPHONE = 8; // 0x8
+  }
+
+  public final class LightsManager.LightsSession implements java.lang.AutoCloseable {
+    method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS) public void close();
+    method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS) public void setLights(@NonNull android.hardware.lights.LightsRequest);
+  }
+
+  public final class LightsRequest {
+  }
+
+  public static final class LightsRequest.Builder {
+    ctor public LightsRequest.Builder();
+    method @NonNull public android.hardware.lights.LightsRequest build();
+    method @NonNull public android.hardware.lights.LightsRequest.Builder clearLight(@NonNull android.hardware.lights.Light);
+    method @NonNull public android.hardware.lights.LightsRequest.Builder setLight(@NonNull android.hardware.lights.Light, @NonNull android.hardware.lights.LightState);
+  }
+
+}
+
 package android.hardware.location {
 
   public class ContextHubClient implements java.io.Closeable {
@@ -3532,11 +3702,35 @@
 package android.hardware.soundtrigger {
 
   public class SoundTrigger {
+    field public static final int RECOGNITION_MODE_GENERIC = 8; // 0x8
+    field public static final int RECOGNITION_MODE_USER_AUTHENTICATION = 4; // 0x4
+    field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2
+    field public static final int RECOGNITION_MODE_VOICE_TRIGGER = 1; // 0x1
     field public static final int STATUS_OK = 0; // 0x0
   }
 
+  public static final class SoundTrigger.Keyphrase implements android.os.Parcelable {
+    ctor public SoundTrigger.Keyphrase(int, int, @NonNull java.util.Locale, @NonNull String, @Nullable int[]);
+    method @NonNull public static android.hardware.soundtrigger.SoundTrigger.Keyphrase readFromParcel(@NonNull android.os.Parcel);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.Keyphrase> CREATOR;
+    field public final int id;
+    field @NonNull public final java.util.Locale locale;
+    field public final int recognitionModes;
+    field @NonNull public final String text;
+    field @NonNull public final int[] users;
+  }
+
+  public static final class SoundTrigger.KeyphraseSoundModel extends android.hardware.soundtrigger.SoundTrigger.SoundModel implements android.os.Parcelable {
+    ctor public SoundTrigger.KeyphraseSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], @Nullable android.hardware.soundtrigger.SoundTrigger.Keyphrase[], int);
+    ctor public SoundTrigger.KeyphraseSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], @Nullable android.hardware.soundtrigger.SoundTrigger.Keyphrase[]);
+    method @NonNull public static android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel readFromParcel(@NonNull android.os.Parcel);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel> CREATOR;
+    field @NonNull public final android.hardware.soundtrigger.SoundTrigger.Keyphrase[] keyphrases;
+  }
+
   public static final class SoundTrigger.ModelParamRange 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.hardware.soundtrigger.SoundTrigger.ModelParamRange> CREATOR;
     field public final int end;
@@ -3546,7 +3740,10 @@
   public static final class SoundTrigger.ModuleProperties implements android.os.Parcelable {
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final int CAPABILITY_ECHO_CANCELLATION = 1; // 0x1
+    field public static final int CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.ModuleProperties> CREATOR;
+    field public final int audioCapabilities;
     field @NonNull public final String description;
     field public final int id;
     field @NonNull public final String implementor;
@@ -3557,6 +3754,7 @@
     field public final int powerConsumptionMw;
     field public final int recognitionModes;
     field public final boolean returnsTriggerInEvent;
+    field @NonNull public final String supportedModelArch;
     field public final boolean supportsCaptureTransition;
     field public final boolean supportsConcurrentCapture;
     field @NonNull public final java.util.UUID uuid;
@@ -3570,6 +3768,16 @@
     method public boolean isCaptureAvailable();
   }
 
+  public static class SoundTrigger.SoundModel {
+    field public static final int TYPE_GENERIC_SOUND = 1; // 0x1
+    field public static final int TYPE_KEYPHRASE = 0; // 0x0
+    field @NonNull public final byte[] data;
+    field public final int type;
+    field @NonNull public final java.util.UUID uuid;
+    field @NonNull public final java.util.UUID vendorUuid;
+    field public final int version;
+  }
+
 }
 
 package android.hardware.usb {
@@ -3579,9 +3787,18 @@
   }
 
   public class UsbManager {
+    method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public long getCurrentFunctions();
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_USB) public java.util.List<android.hardware.usb.UsbPort> getPorts();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void grantPermission(android.hardware.usb.UsbDevice, String);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void resetUsbGadget();
+    method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void setCurrentFunctions(long);
     field @RequiresPermission(android.Manifest.permission.MANAGE_USB) public static final String ACTION_USB_PORT_CHANGED = "android.hardware.usb.action.USB_PORT_CHANGED";
+    field public static final String ACTION_USB_STATE = "android.hardware.usb.action.USB_STATE";
+    field public static final long FUNCTION_NONE = 0L; // 0x0L
+    field public static final long FUNCTION_RNDIS = 32L; // 0x20L
+    field public static final String USB_CONFIGURED = "configured";
+    field public static final String USB_CONNECTED = "connected";
+    field public static final String USB_FUNCTION_RNDIS = "rndis";
   }
 
   public final class UsbPort {
@@ -4008,17 +4225,24 @@
     method public int getAllFlags();
     method public android.os.Bundle getBundle();
     method public int getCapturePreset();
+    method public int getSystemUsage();
+    method public static boolean isSystemUsage(int);
     field public static final int FLAG_BEACON = 8; // 0x8
     field public static final int FLAG_BYPASS_INTERRUPTION_POLICY = 64; // 0x40
     field public static final int FLAG_BYPASS_MUTE = 128; // 0x80
     field public static final int FLAG_HW_HOTWORD = 32; // 0x20
-    field @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public static final int USAGE_CALL_ASSISTANT = 17; // 0x11
+    field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int USAGE_ANNOUNCEMENT = 1003; // 0x3eb
+    field @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.MODIFY_AUDIO_ROUTING}) public static final int USAGE_CALL_ASSISTANT = 17; // 0x11
+    field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int USAGE_EMERGENCY = 1000; // 0x3e8
+    field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int USAGE_SAFETY = 1001; // 0x3e9
+    field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int USAGE_VEHICLE_STATUS = 1002; // 0x3ea
   }
 
   public static class AudioAttributes.Builder {
     method public android.media.AudioAttributes.Builder addBundle(@NonNull android.os.Bundle);
     method public android.media.AudioAttributes.Builder setCapturePreset(int);
     method public android.media.AudioAttributes.Builder setInternalCapturePreset(int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioAttributes.Builder setSystemUsage(int);
   }
 
   public final class AudioDeviceAddress implements android.os.Parcelable {
@@ -4034,6 +4258,10 @@
     field public static final int ROLE_OUTPUT = 2; // 0x2
   }
 
+  public final class AudioDeviceInfo {
+    field public static final int TYPE_REMOTE_SUBMIX = 25; // 0x19
+  }
+
   public final class AudioFocusInfo implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public android.media.AudioAttributes getAttributes();
@@ -4059,11 +4287,15 @@
     method @Deprecated public int abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes);
     method public void clearAudioServerStateCallback();
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
+    method @IntRange(from=0) public int getAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioProductStrategy> getAudioProductStrategies();
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioVolumeGroup> getAudioVolumeGroups();
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAddress> getDevicesForAttributes(@NonNull android.media.AudioAttributes);
+    method @IntRange(from=0) public int getMaxAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo);
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMaxVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
     method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDeviceAddress getPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int[] getSupportedSystemUsages();
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
     method public boolean isAudioServerRunning();
     method public boolean isHdmiSystemAudioSupported();
@@ -4073,9 +4305,11 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, @NonNull android.media.AudioAttributes, int, int) throws java.lang.IllegalArgumentException;
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.MODIFY_AUDIO_ROUTING}) public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, @NonNull android.media.AudioAttributes, int, int, android.media.audiopolicy.AudioPolicy) throws java.lang.IllegalArgumentException;
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int requestAudioFocus(@NonNull android.media.AudioFocusRequest, @Nullable android.media.audiopolicy.AudioPolicy);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo, @IntRange(from=0) int);
     method public void setAudioServerStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.AudioServerStateCallback);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setFocusRequestResult(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull android.media.AudioDeviceAddress);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setSupportedSystemUsages(@NonNull int[]);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setVolumeIndexForAttributes(@NonNull android.media.AudioAttributes, int, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicyAsync(@NonNull android.media.audiopolicy.AudioPolicy);
@@ -4198,6 +4432,7 @@
     field public static final int RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET = 2; // 0x2
     field public static final int RULE_MATCH_ATTRIBUTE_USAGE = 1; // 0x1
     field public static final int RULE_MATCH_UID = 4; // 0x4
+    field public static final int RULE_MATCH_USERID = 8; // 0x8
   }
 
   public static class AudioMixingRule.Builder {
@@ -4218,9 +4453,11 @@
     method public int getFocusDuckingBehavior();
     method public int getStatus();
     method public boolean removeUidDeviceAffinity(int);
+    method public boolean removeUserIdDeviceAffinity(int);
     method public int setFocusDuckingBehavior(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
     method public void setRegistration(String);
     method public boolean setUidDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>);
+    method public boolean setUserIdDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>);
     method public String toLogFriendlyString();
     field public static final int FOCUS_POLICY_DUCKING_DEFAULT = 0; // 0x0
     field public static final int FOCUS_POLICY_DUCKING_IN_APP = 0; // 0x0
@@ -4294,7 +4531,7 @@
   }
 
   public static interface MediaSessionManager.OnMediaKeyEventDispatchedListener {
-    method public default void onMediaKeyEventDispatched(@NonNull android.view.KeyEvent, @NonNull String, @NonNull android.media.session.MediaSession.Token);
+    method public default void onMediaKeyEventDispatched(@NonNull android.view.KeyEvent, @NonNull String, @Nullable android.media.session.MediaSession.Token);
   }
 
   public static interface MediaSessionManager.OnMediaKeyEventSessionChangedListener {
@@ -4328,6 +4565,8 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public boolean stopRecognition();
     field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2
     field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1
+    field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 4; // 0x4
+    field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 8; // 0x8
   }
 
   public abstract static class SoundTriggerDetector.Callback {
@@ -4350,17 +4589,19 @@
     method public int getDetectionServiceOperationsTimeout();
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.media.soundtrigger.SoundTriggerManager.Model getModel(java.util.UUID);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModuleProperties getModuleProperties();
-    method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull java.util.UUID, int) throws java.lang.IllegalArgumentException, java.lang.UnsupportedOperationException;
+    method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull java.util.UUID, int);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModelParamRange queryParameter(@Nullable java.util.UUID, int);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable java.util.UUID, int, int) throws java.lang.IllegalArgumentException, java.lang.UnsupportedOperationException;
+    method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable java.util.UUID, int, int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public void updateModel(android.media.soundtrigger.SoundTriggerManager.Model);
   }
 
   public static class SoundTriggerManager.Model {
-    method public static android.media.soundtrigger.SoundTriggerManager.Model create(java.util.UUID, java.util.UUID, byte[]);
-    method public byte[] getModelData();
-    method public java.util.UUID getModelUuid();
-    method public java.util.UUID getVendorUuid();
+    method @NonNull public static android.media.soundtrigger.SoundTriggerManager.Model create(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], int);
+    method @NonNull public static android.media.soundtrigger.SoundTriggerManager.Model create(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[]);
+    method @Nullable public byte[] getModelData();
+    method @NonNull public java.util.UUID getModelUuid();
+    method @NonNull public java.util.UUID getVendorUuid();
+    method public int getVersion();
   }
 
 }
@@ -4486,10 +4727,12 @@
   }
 
   public final class TvInputManager {
-    method @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE) public android.media.tv.TvInputManager.Hardware acquireTvInputHardware(int, android.media.tv.TvInputInfo, android.media.tv.TvInputManager.HardwareCallback);
+    method @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE) public android.media.tv.TvInputManager.Hardware acquireTvInputHardware(int, @NonNull android.media.tv.TvInputInfo, @NonNull android.media.tv.TvInputManager.HardwareCallback);
+    method @Nullable @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE) public android.media.tv.TvInputManager.Hardware acquireTvInputHardware(int, @NonNull android.media.tv.TvInputInfo, @NonNull android.media.tv.TvInputManager.HardwareCallback, @Nullable String, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS) public void addBlockedRating(@NonNull android.media.tv.TvContentRating);
     method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public boolean captureFrame(String, android.view.Surface, android.media.tv.TvStreamConfig);
     method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public java.util.List<android.media.tv.TvStreamConfig> getAvailableTvStreamConfigList(String);
+    method @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS) public int getClientPid(@NonNull String);
     method @NonNull @RequiresPermission("android.permission.DVB_DEVICE") public java.util.List<android.media.tv.DvbDeviceInfo> getDvbDeviceList();
     method @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE) public java.util.List<android.media.tv.TvInputHardwareInfo> getHardwareList();
     method @RequiresPermission(android.Manifest.permission.READ_CONTENT_RATING_SYSTEMS) public java.util.List<android.media.tv.TvContentRatingSystemInfo> getTvContentRatingSystemList();
@@ -4501,6 +4744,7 @@
     method @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE) public void releaseTvInputHardware(int, android.media.tv.TvInputManager.Hardware);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS) public void removeBlockedRating(@NonNull android.media.tv.TvContentRating);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS) public void setParentalControlsEnabled(boolean);
+    field public static final int UNKNOWN_CLIENT_PID = -1; // 0xffffffff
   }
 
   public static final class TvInputManager.Hardware {
@@ -4572,43 +4816,401 @@
 
 package android.media.tv.tuner {
 
-  public abstract class FrontendSettings {
-    method public final int getFrequency();
-    method public abstract int getType();
+  public class DemuxCapabilities {
+    method public int getAudioFilterCount();
+    method public int getDemuxCount();
+    method public int getFilterCapabilities();
+    method @Nullable @Size(5) public int[] getLinkCapabilities();
+    method public int getPcrFilterCount();
+    method public int getPesFilterCount();
+    method public int getPlaybackCount();
+    method public int getRecordCount();
+    method public int getSectionFilterCount();
+    method public long getSectionFilterLength();
+    method public int getTsFilterCount();
+    method public int getVideoFilterCount();
+    method public boolean isTimeFilterSupported();
   }
 
-  public final class Tuner implements java.lang.AutoCloseable {
-    ctor public Tuner(@NonNull android.content.Context);
-    method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.Tuner.Descrambler openDescrambler();
-    method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int tune(@NonNull android.media.tv.tuner.FrontendSettings);
+  public class Descrambler implements java.lang.AutoCloseable {
+    method public int addPid(int, int, @Nullable android.media.tv.tuner.filter.Filter);
+    method public void close();
+    method public int removePid(int, int, @Nullable android.media.tv.tuner.filter.Filter);
+    method public int setKeyToken(@Nullable byte[]);
+    field public static final int PID_TYPE_MMTP = 2; // 0x2
+    field public static final int PID_TYPE_T = 1; // 0x1
   }
 
-  public class Tuner.Descrambler {
+  public class Lnb implements java.lang.AutoCloseable {
+    method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void close();
+    method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int sendDiseqcMessage(@NonNull byte[]);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int setSatellitePosition(int);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int setTone(int);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int setVoltage(int);
+    field public static final int EVENT_TYPE_DISEQC_RX_OVERFLOW = 0; // 0x0
+    field public static final int EVENT_TYPE_DISEQC_RX_PARITY_ERROR = 2; // 0x2
+    field public static final int EVENT_TYPE_DISEQC_RX_TIMEOUT = 1; // 0x1
+    field public static final int EVENT_TYPE_LNB_OVERLOAD = 3; // 0x3
+    field public static final int POSITION_A = 1; // 0x1
+    field public static final int POSITION_B = 2; // 0x2
+    field public static final int POSITION_UNDEFINED = 0; // 0x0
+    field public static final int TONE_CONTINUOUS = 1; // 0x1
+    field public static final int TONE_NONE = 0; // 0x0
+    field public static final int VOLTAGE_11V = 2; // 0x2
+    field public static final int VOLTAGE_12V = 3; // 0x3
+    field public static final int VOLTAGE_13V = 4; // 0x4
+    field public static final int VOLTAGE_14V = 5; // 0x5
+    field public static final int VOLTAGE_15V = 6; // 0x6
+    field public static final int VOLTAGE_18V = 7; // 0x7
+    field public static final int VOLTAGE_19V = 8; // 0x8
+    field public static final int VOLTAGE_5V = 1; // 0x1
+    field public static final int VOLTAGE_NONE = 0; // 0x0
   }
 
-  public class Tuner.Filter {
+  public interface LnbCallback {
+    method public void onDiseqcMessage(@NonNull byte[]);
+    method public void onEvent(int);
   }
 
-  public static interface Tuner.FilterCallback {
-    method public void onFilterEvent(@NonNull android.media.tv.tuner.Tuner.Filter, @NonNull android.media.tv.tuner.filter.FilterEvent[]);
-    method public void onFilterStatusChanged(@NonNull android.media.tv.tuner.Tuner.Filter, int);
+  public class Tuner implements java.lang.AutoCloseable {
+    ctor @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public Tuner(@NonNull android.content.Context, @NonNull String, int, @Nullable android.media.tv.tuner.Tuner.OnResourceLostListener);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void clearOnTuneEventListener();
+    method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void close();
+    method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int connectCiCam(int);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int disconnectCiCam();
+    method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int getAvSyncHwId(@NonNull android.media.tv.tuner.filter.Filter);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public long getAvSyncTime(int);
+    method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.DemuxCapabilities getDemuxCapabilities(@NonNull android.content.Context);
+    method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.frontend.FrontendInfo getFrontendInfo();
+    method @Nullable public android.media.tv.tuner.frontend.FrontendStatus getFrontendStatus(@NonNull int[]);
+    method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.Descrambler openDescrambler();
+    method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.dvr.DvrPlayback openDvrPlayback(long, @Nullable java.util.concurrent.Executor, @Nullable android.media.tv.tuner.dvr.OnPlaybackStatusChangedListener);
+    method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.dvr.DvrRecorder openDvrRecorder(long, @Nullable java.util.concurrent.Executor, @Nullable android.media.tv.tuner.dvr.OnRecordStatusChangedListener);
+    method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.filter.Filter openFilter(int, int, long, @Nullable java.util.concurrent.Executor, @Nullable android.media.tv.tuner.filter.FilterCallback);
+    method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.Lnb openLnb(@Nullable java.util.concurrent.Executor, @Nullable android.media.tv.tuner.LnbCallback);
+    method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.Lnb openLnbByName(@Nullable String, @Nullable java.util.concurrent.Executor, @NonNull android.media.tv.tuner.LnbCallback);
+    method @Nullable public android.media.tv.tuner.filter.TimeFilter openTimeFilter();
+    method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int scan(@NonNull android.media.tv.tuner.frontend.FrontendSettings, int, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.ScanCallback);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int setLna(boolean);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void setOnTuneEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.OnTuneEventListener);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void shareFrontendFromTuner(@NonNull android.media.tv.tuner.Tuner);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int stopScan();
+    method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int stopTune();
+    method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int tune(@NonNull android.media.tv.tuner.frontend.FrontendSettings);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void updateResourcePriority(int, int);
+  }
+
+  public static interface Tuner.OnResourceLostListener {
+    method public void onResourceLost(@NonNull android.media.tv.tuner.Tuner);
   }
 
   public final class TunerConstants {
-    field public static final int FILTER_STATUS_DATA_READY = 1; // 0x1
-    field public static final int FILTER_STATUS_HIGH_WATER = 4; // 0x4
-    field public static final int FILTER_STATUS_LOW_WATER = 2; // 0x2
-    field public static final int FILTER_STATUS_OVERFLOW = 8; // 0x8
+    field public static final int INVALID_STREAM_ID = -1; // 0xffffffff
+    field public static final int INVALID_TS_PID = -1; // 0xffffffff
+    field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
+    field public static final int RESULT_INVALID_STATE = 3; // 0x3
+    field public static final int RESULT_NOT_INITIALIZED = 2; // 0x2
+    field public static final int RESULT_OUT_OF_MEMORY = 5; // 0x5
+    field public static final int RESULT_SUCCESS = 0; // 0x0
+    field public static final int RESULT_UNAVAILABLE = 1; // 0x1
+    field public static final int RESULT_UNKNOWN_ERROR = 6; // 0x6
+    field public static final int SCAN_TYPE_AUTO = 1; // 0x1
+    field public static final int SCAN_TYPE_BLIND = 2; // 0x2
+    field public static final int SCAN_TYPE_UNDEFINED = 0; // 0x0
+  }
+
+}
+
+package android.media.tv.tuner.dvr {
+
+  public class Dvr implements java.lang.AutoCloseable {
+    ctor protected Dvr(int);
+    method public int attachFilter(@NonNull android.media.tv.tuner.filter.Filter);
+    method public void close();
+    method public int configure(@NonNull android.media.tv.tuner.dvr.DvrSettings);
+    method public int detachFilter(@NonNull android.media.tv.tuner.filter.Filter);
+    method public int flush();
+    method public void setFileDescriptor(@NonNull android.os.ParcelFileDescriptor);
+    method public int start();
+    method public int stop();
+    field public static final int TYPE_PLAYBACK = 1; // 0x1
+    field public static final int TYPE_RECORD = 0; // 0x0
+  }
+
+  public class DvrPlayback extends android.media.tv.tuner.dvr.Dvr {
+    method public long read(long);
+    method public long read(@NonNull byte[], long, long);
+    field public static final int PLAYBACK_STATUS_ALMOST_EMPTY = 2; // 0x2
+    field public static final int PLAYBACK_STATUS_ALMOST_FULL = 4; // 0x4
+    field public static final int PLAYBACK_STATUS_EMPTY = 1; // 0x1
+    field public static final int PLAYBACK_STATUS_FULL = 8; // 0x8
+  }
+
+  public class DvrRecorder extends android.media.tv.tuner.dvr.Dvr {
+    method public long write(long);
+    method public long write(@NonNull byte[], long, long);
+  }
+
+  public class DvrSettings {
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.dvr.DvrSettings.Builder builder(@NonNull android.content.Context);
+    method public int getDataFormat();
+    method public long getHighThreshold();
+    method public long getLowThreshold();
+    method public long getPacketSize();
+    method public int getStatusMask();
+    field public static final int DATA_FORMAT_ES = 2; // 0x2
+    field public static final int DATA_FORMAT_PES = 1; // 0x1
+    field public static final int DATA_FORMAT_SHV_TLV = 3; // 0x3
+    field public static final int DATA_FORMAT_TS = 0; // 0x0
+  }
+
+  public static final class DvrSettings.Builder {
+    ctor public DvrSettings.Builder();
+    method @NonNull public android.media.tv.tuner.dvr.DvrSettings build();
+    method @NonNull public android.media.tv.tuner.dvr.DvrSettings.Builder setDataFormat(int);
+    method @NonNull public android.media.tv.tuner.dvr.DvrSettings.Builder setHighThreshold(long);
+    method @NonNull public android.media.tv.tuner.dvr.DvrSettings.Builder setLowThreshold(long);
+    method @NonNull public android.media.tv.tuner.dvr.DvrSettings.Builder setPacketSize(long);
+    method @NonNull public android.media.tv.tuner.dvr.DvrSettings.Builder setStatusMask(int);
+  }
+
+  public interface OnPlaybackStatusChangedListener {
+    method public void onPlaybackStatusChanged(int);
+  }
+
+  public interface OnRecordStatusChangedListener {
+    method public void onRecordStatusChanged(int);
   }
 
 }
 
 package android.media.tv.tuner.filter {
 
+  public class AlpFilterConfiguration extends android.media.tv.tuner.filter.FilterConfiguration {
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.filter.AlpFilterConfiguration.Builder builder(@NonNull android.content.Context);
+    method public int getLengthType();
+    method public int getPacketType();
+    method public int getType();
+    field public static final int LENGTH_TYPE_UNDEFINED = 0; // 0x0
+    field public static final int LENGTH_TYPE_WITHOUT_ADDITIONAL_HEADER = 1; // 0x1
+    field public static final int LENGTH_TYPE_WITH_ADDITIONAL_HEADER = 2; // 0x2
+  }
+
+  public static class AlpFilterConfiguration.Builder extends android.media.tv.tuner.filter.FilterConfiguration.Builder<android.media.tv.tuner.filter.AlpFilterConfiguration.Builder> {
+    method @NonNull public android.media.tv.tuner.filter.AlpFilterConfiguration build();
+    method @NonNull public android.media.tv.tuner.filter.AlpFilterConfiguration.Builder setLengthType(int);
+    method @NonNull public android.media.tv.tuner.filter.AlpFilterConfiguration.Builder setPacketType(int);
+  }
+
+  public class AudioDescriptor {
+    method public byte getAdFade();
+    method public byte getAdGainCenter();
+    method public byte getAdGainFront();
+    method public byte getAdGainSurround();
+    method public byte getAdPan();
+    method public char getAdVersionTextTag();
+  }
+
+  public class AvSettings extends android.media.tv.tuner.filter.Settings {
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.filter.AvSettings.Builder builder(@NonNull android.content.Context, int, boolean);
+    method public boolean isPassthrough();
+  }
+
+  public static class AvSettings.Builder extends android.media.tv.tuner.filter.Settings.Builder<android.media.tv.tuner.filter.AvSettings.Builder> {
+    method @NonNull public android.media.tv.tuner.filter.AvSettings build();
+    method @NonNull public android.media.tv.tuner.filter.AvSettings.Builder setPassthrough(boolean);
+  }
+
+  public class DownloadEvent extends android.media.tv.tuner.filter.FilterEvent {
+    method public int getDataLength();
+    method public int getItemFragmentIndex();
+    method public int getItemId();
+    method public int getLastItemFragmentIndex();
+    method public int getMpuSequenceNumber();
+  }
+
+  public class DownloadSettings extends android.media.tv.tuner.filter.Settings {
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.filter.DownloadSettings.Builder builder(@NonNull android.content.Context, int);
+    method public int getDownloadId();
+  }
+
+  public static class DownloadSettings.Builder extends android.media.tv.tuner.filter.Settings.Builder<android.media.tv.tuner.filter.DownloadSettings.Builder> {
+    method @NonNull public android.media.tv.tuner.filter.DownloadSettings build();
+    method @NonNull public android.media.tv.tuner.filter.DownloadSettings.Builder setDownloadId(int);
+  }
+
+  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 flush();
+    method public int getId();
+    method public int read(@NonNull byte[], long, long);
+    method public int setDataSource(@Nullable android.media.tv.tuner.filter.Filter);
+    method public int start();
+    method public int stop();
+    field public static final int STATUS_DATA_READY = 1; // 0x1
+    field public static final int STATUS_HIGH_WATER = 4; // 0x4
+    field public static final int STATUS_LOW_WATER = 2; // 0x2
+    field public static final int STATUS_OVERFLOW = 8; // 0x8
+    field public static final int SUBTYPE_AUDIO = 3; // 0x3
+    field public static final int SUBTYPE_DOWNLOAD = 5; // 0x5
+    field public static final int SUBTYPE_IP = 13; // 0xd
+    field public static final int SUBTYPE_IP_PAYLOAD = 12; // 0xc
+    field public static final int SUBTYPE_MMTP = 10; // 0xa
+    field public static final int SUBTYPE_NTP = 11; // 0xb
+    field public static final int SUBTYPE_PAYLOAD_THROUGH = 14; // 0xe
+    field public static final int SUBTYPE_PCR = 8; // 0x8
+    field public static final int SUBTYPE_PES = 2; // 0x2
+    field public static final int SUBTYPE_PTP = 16; // 0x10
+    field public static final int SUBTYPE_RECORD = 6; // 0x6
+    field public static final int SUBTYPE_SECTION = 1; // 0x1
+    field public static final int SUBTYPE_TEMI = 9; // 0x9
+    field public static final int SUBTYPE_TLV = 15; // 0xf
+    field public static final int SUBTYPE_TS = 7; // 0x7
+    field public static final int SUBTYPE_UNDEFINED = 0; // 0x0
+    field public static final int SUBTYPE_VIDEO = 4; // 0x4
+    field public static final int TYPE_ALP = 16; // 0x10
+    field public static final int TYPE_IP = 4; // 0x4
+    field public static final int TYPE_MMTP = 2; // 0x2
+    field public static final int TYPE_TLV = 8; // 0x8
+    field public static final int TYPE_TS = 1; // 0x1
+  }
+
+  public interface FilterCallback {
+    method public void onFilterEvent(@NonNull android.media.tv.tuner.filter.Filter, @NonNull android.media.tv.tuner.filter.FilterEvent[]);
+    method public void onFilterStatusChanged(@NonNull android.media.tv.tuner.filter.Filter, int);
+  }
+
+  public abstract class FilterConfiguration {
+    method @Nullable public android.media.tv.tuner.filter.Settings getSettings();
+    method public abstract int getType();
+    field public static final int PACKET_TYPE_COMPRESSED = 2; // 0x2
+    field public static final int PACKET_TYPE_IPV4 = 0; // 0x0
+    field public static final int PACKET_TYPE_SIGNALING = 4; // 0x4
+  }
+
+  public abstract static class FilterConfiguration.Builder<T extends android.media.tv.tuner.filter.FilterConfiguration.Builder<T>> {
+    method @NonNull public T setSettings(@Nullable android.media.tv.tuner.filter.Settings);
+  }
+
   public abstract class FilterEvent {
     ctor public FilterEvent();
   }
 
+  public class IpFilterConfiguration extends android.media.tv.tuner.filter.FilterConfiguration {
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.filter.IpFilterConfiguration.Builder builder(@NonNull android.content.Context);
+    method @NonNull @Size(min=4, max=16) public byte[] getDstIpAddress();
+    method public int getDstPort();
+    method @NonNull @Size(min=4, max=16) public byte[] getSrcIpAddress();
+    method public int getSrcPort();
+    method public int getType();
+    method public boolean isPassthrough();
+  }
+
+  public static class IpFilterConfiguration.Builder extends android.media.tv.tuner.filter.FilterConfiguration.Builder<android.media.tv.tuner.filter.IpFilterConfiguration.Builder> {
+    method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration build();
+    method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration.Builder setDstIpAddress(@NonNull byte[]);
+    method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration.Builder setDstPort(int);
+    method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration.Builder setPassthrough(boolean);
+    method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration.Builder setSrcIpAddress(@NonNull byte[]);
+    method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration.Builder setSrcPort(int);
+  }
+
+  public class IpPayloadEvent extends android.media.tv.tuner.filter.FilterEvent {
+    method public int getDataLength();
+  }
+
+  public class MediaEvent extends android.media.tv.tuner.filter.FilterEvent {
+    method public long getAudioHandle();
+    method public long getAvDataId();
+    method public long getDataLength();
+    method @Nullable public android.media.tv.tuner.filter.AudioDescriptor getExtraMetaData();
+    method @Nullable public android.media.MediaCodec.LinearBlock getLinearBlock();
+    method public int getMpuSequenceNumber();
+    method public long getOffset();
+    method public long getPts();
+    method public int getStreamId();
+    method public boolean isPrivateData();
+    method public boolean isPtsPresent();
+    method public boolean isSecureMemory();
+  }
+
+  public class MmtpFilterConfiguration extends android.media.tv.tuner.filter.FilterConfiguration {
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.filter.MmtpFilterConfiguration.Builder builder(@NonNull android.content.Context);
+    method public int getMmtpPacketId();
+    method public int getType();
+  }
+
+  public static class MmtpFilterConfiguration.Builder extends android.media.tv.tuner.filter.FilterConfiguration.Builder<android.media.tv.tuner.filter.MmtpFilterConfiguration.Builder> {
+    method @NonNull public android.media.tv.tuner.filter.MmtpFilterConfiguration build();
+    method @NonNull public android.media.tv.tuner.filter.MmtpFilterConfiguration.Builder setMmtpPacketId(int);
+  }
+
+  public class MmtpRecordEvent extends android.media.tv.tuner.filter.FilterEvent {
+    method public long getDataLength();
+    method public int getScHevcIndexMask();
+  }
+
+  public class PesEvent extends android.media.tv.tuner.filter.FilterEvent {
+    method public int getDataLength();
+    method public int getMpuSequenceNumber();
+    method public int getStreamId();
+  }
+
+  public class PesSettings extends android.media.tv.tuner.filter.Settings {
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.filter.PesSettings.Builder builder(@NonNull android.content.Context, int);
+    method public int getStreamId();
+    method public boolean isRaw();
+  }
+
+  public static class PesSettings.Builder {
+    method @NonNull public android.media.tv.tuner.filter.PesSettings build();
+    method @NonNull public android.media.tv.tuner.filter.PesSettings.Builder setRaw(boolean);
+    method @NonNull public android.media.tv.tuner.filter.PesSettings.Builder setStreamId(int);
+  }
+
+  public class RecordSettings extends android.media.tv.tuner.filter.Settings {
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.filter.RecordSettings.Builder builder(@NonNull android.content.Context, int);
+    method public int getScIndexMask();
+    method public int getScIndexType();
+    method public int getTsIndexMask();
+    field public static final int INDEX_TYPE_NONE = 0; // 0x0
+    field public static final int INDEX_TYPE_SC = 1; // 0x1
+    field public static final int INDEX_TYPE_SC_HEVC = 2; // 0x2
+    field public static final int SC_HEVC_INDEX_AUD = 2; // 0x2
+    field public static final int SC_HEVC_INDEX_SLICE_BLA_N_LP = 16; // 0x10
+    field public static final int SC_HEVC_INDEX_SLICE_BLA_W_RADL = 8; // 0x8
+    field public static final int SC_HEVC_INDEX_SLICE_CE_BLA_W_LP = 4; // 0x4
+    field public static final int SC_HEVC_INDEX_SLICE_IDR_N_LP = 64; // 0x40
+    field public static final int SC_HEVC_INDEX_SLICE_IDR_W_RADL = 32; // 0x20
+    field public static final int SC_HEVC_INDEX_SLICE_TRAIL_CRA = 128; // 0x80
+    field public static final int SC_HEVC_INDEX_SPS = 1; // 0x1
+    field public static final int SC_INDEX_B_FRAME = 4; // 0x4
+    field public static final int SC_INDEX_I_FRAME = 1; // 0x1
+    field public static final int SC_INDEX_P_FRAME = 2; // 0x2
+    field public static final int SC_INDEX_SEQUENCE = 8; // 0x8
+    field public static final int TS_INDEX_ADAPTATION_EXTENSION_FLAG = 4096; // 0x1000
+    field public static final int TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED = 8; // 0x8
+    field public static final int TS_INDEX_CHANGE_TO_NOT_SCRAMBLED = 4; // 0x4
+    field public static final int TS_INDEX_CHANGE_TO_ODD_SCRAMBLED = 16; // 0x10
+    field public static final int TS_INDEX_DISCONTINUITY_INDICATOR = 32; // 0x20
+    field public static final int TS_INDEX_FIRST_PACKET = 1; // 0x1
+    field public static final int TS_INDEX_OPCR_FLAG = 512; // 0x200
+    field public static final int TS_INDEX_PAYLOAD_UNIT_START_INDICATOR = 2; // 0x2
+    field public static final int TS_INDEX_PCR_FLAG = 256; // 0x100
+    field public static final int TS_INDEX_PRIORITY_INDICATOR = 128; // 0x80
+    field public static final int TS_INDEX_PRIVATE_DATA = 2048; // 0x800
+    field public static final int TS_INDEX_RANDOM_ACCESS_INDICATOR = 64; // 0x40
+    field public static final int TS_INDEX_SPLICING_POINT_FLAG = 1024; // 0x400
+  }
+
+  public static class RecordSettings.Builder extends android.media.tv.tuner.filter.Settings.Builder<android.media.tv.tuner.filter.RecordSettings.Builder> {
+    method @NonNull public android.media.tv.tuner.filter.RecordSettings build();
+    method @NonNull public android.media.tv.tuner.filter.RecordSettings.Builder setScIndexMask(int);
+    method @NonNull public android.media.tv.tuner.filter.RecordSettings.Builder setScIndexType(int);
+    method @NonNull public android.media.tv.tuner.filter.RecordSettings.Builder setTsIndexMask(int);
+  }
+
   public class SectionEvent extends android.media.tv.tuner.filter.FilterEvent {
     method public int getDataLength();
     method public int getSectionNumber();
@@ -4616,6 +5218,785 @@
     method public int getVersion();
   }
 
+  public abstract class SectionSettings extends android.media.tv.tuner.filter.Settings {
+    method public boolean isCrcEnabled();
+    method public boolean isRaw();
+    method public boolean isRepeat();
+  }
+
+  public abstract static class SectionSettings.Builder<T extends android.media.tv.tuner.filter.SectionSettings.Builder<T>> extends android.media.tv.tuner.filter.Settings.Builder<android.media.tv.tuner.filter.SectionSettings.Builder<T>> {
+    method @NonNull public T setCrcEnabled(boolean);
+    method @NonNull public T setRaw(boolean);
+    method @NonNull public T setRepeat(boolean);
+  }
+
+  public class SectionSettingsWithSectionBits extends android.media.tv.tuner.filter.SectionSettings {
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.filter.SectionSettingsWithSectionBits.Builder builder(@NonNull android.content.Context, int);
+    method @NonNull public byte[] getFilterBytes();
+    method @NonNull public byte[] getMask();
+    method @NonNull public byte[] getMode();
+  }
+
+  public static class SectionSettingsWithSectionBits.Builder extends android.media.tv.tuner.filter.SectionSettings.Builder<android.media.tv.tuner.filter.SectionSettingsWithSectionBits.Builder> {
+    method @NonNull public android.media.tv.tuner.filter.SectionSettingsWithSectionBits build();
+    method @NonNull public android.media.tv.tuner.filter.SectionSettingsWithSectionBits.Builder setFilter(@NonNull byte[]);
+    method @NonNull public android.media.tv.tuner.filter.SectionSettingsWithSectionBits.Builder setMask(@NonNull byte[]);
+    method @NonNull public android.media.tv.tuner.filter.SectionSettingsWithSectionBits.Builder setMode(@NonNull byte[]);
+  }
+
+  public class SectionSettingsWithTableInfo extends android.media.tv.tuner.filter.SectionSettings {
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.filter.SectionSettingsWithTableInfo.Builder builder(@NonNull android.content.Context, int);
+    method public int getTableId();
+    method public int getVersion();
+  }
+
+  public static class SectionSettingsWithTableInfo.Builder extends android.media.tv.tuner.filter.SectionSettings.Builder<android.media.tv.tuner.filter.SectionSettingsWithTableInfo.Builder> {
+    method @NonNull public android.media.tv.tuner.filter.SectionSettingsWithTableInfo build();
+    method @NonNull public android.media.tv.tuner.filter.SectionSettingsWithTableInfo.Builder setTableId(int);
+    method @NonNull public android.media.tv.tuner.filter.SectionSettingsWithTableInfo.Builder setVersion(int);
+  }
+
+  public abstract class Settings {
+    method public int getType();
+  }
+
+  public abstract static class Settings.Builder<T extends android.media.tv.tuner.filter.Settings.Builder<T>> {
+  }
+
+  public class TemiEvent extends android.media.tv.tuner.filter.FilterEvent {
+    method @NonNull public byte[] getDescriptorData();
+    method public byte getDescriptorTag();
+    method public long getPts();
+  }
+
+  public class TimeFilter implements java.lang.AutoCloseable {
+    method public int clearTimestamp();
+    method public void close();
+    method public long getSourceTime();
+    method public long getTimeStamp();
+    method public int setCurrentTimestamp(long);
+    field public static final long TIMESTAMP_UNAVAILABLE = -1L; // 0xffffffffffffffffL
+  }
+
+  public class TlvFilterConfiguration extends android.media.tv.tuner.filter.FilterConfiguration {
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.filter.TlvFilterConfiguration.Builder builder(@NonNull android.content.Context);
+    method public int getPacketType();
+    method public int getType();
+    method public boolean isCompressedIpPacket();
+    method public boolean isPassthrough();
+  }
+
+  public static class TlvFilterConfiguration.Builder extends android.media.tv.tuner.filter.FilterConfiguration.Builder<android.media.tv.tuner.filter.TlvFilterConfiguration.Builder> {
+    method @NonNull public android.media.tv.tuner.filter.TlvFilterConfiguration build();
+    method @NonNull public android.media.tv.tuner.filter.TlvFilterConfiguration.Builder setCompressedIpPacket(boolean);
+    method @NonNull public android.media.tv.tuner.filter.TlvFilterConfiguration.Builder setPacketType(int);
+    method @NonNull public android.media.tv.tuner.filter.TlvFilterConfiguration.Builder setPassthrough(boolean);
+  }
+
+  public class TsFilterConfiguration extends android.media.tv.tuner.filter.FilterConfiguration {
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.filter.TsFilterConfiguration.Builder builder(@NonNull android.content.Context);
+    method public int getTpid();
+    method public int getType();
+  }
+
+  public static class TsFilterConfiguration.Builder {
+    method @NonNull public android.media.tv.tuner.filter.TsFilterConfiguration build();
+    method @NonNull public android.media.tv.tuner.filter.TsFilterConfiguration.Builder setSettings(@NonNull android.media.tv.tuner.filter.Settings);
+    method @NonNull public android.media.tv.tuner.filter.TsFilterConfiguration.Builder setTpid(int);
+  }
+
+  public class TsRecordEvent extends android.media.tv.tuner.filter.FilterEvent {
+    method public long getDataLength();
+    method public int getPacketId();
+    method public int getScIndexMask();
+    method public int getTsIndexMask();
+  }
+
+}
+
+package android.media.tv.tuner.frontend {
+
+  public class AnalogFrontendCapabilities extends android.media.tv.tuner.frontend.FrontendCapabilities {
+    method public int getSifStandardCapability();
+    method public int getSignalTypeCapability();
+  }
+
+  public class AnalogFrontendSettings extends android.media.tv.tuner.frontend.FrontendSettings {
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.frontend.AnalogFrontendSettings.Builder builder(@NonNull android.content.Context);
+    method public int getSifStandard();
+    method public int getSignalType();
+    method public int getType();
+    field public static final int SIF_AUTO = 1; // 0x1
+    field public static final int SIF_BG = 2; // 0x2
+    field public static final int SIF_BG_A2 = 4; // 0x4
+    field public static final int SIF_BG_NICAM = 8; // 0x8
+    field public static final int SIF_DK = 32; // 0x20
+    field public static final int SIF_DK1_A2 = 64; // 0x40
+    field public static final int SIF_DK2_A2 = 128; // 0x80
+    field public static final int SIF_DK3_A2 = 256; // 0x100
+    field public static final int SIF_DK_NICAM = 512; // 0x200
+    field public static final int SIF_I = 16; // 0x10
+    field public static final int SIF_I_NICAM = 32768; // 0x8000
+    field public static final int SIF_L = 1024; // 0x400
+    field public static final int SIF_L_NICAM = 65536; // 0x10000
+    field public static final int SIF_L_PRIME = 131072; // 0x20000
+    field public static final int SIF_M = 2048; // 0x800
+    field public static final int SIF_M_A2 = 8192; // 0x2000
+    field public static final int SIF_M_BTSC = 4096; // 0x1000
+    field public static final int SIF_M_EIAJ = 16384; // 0x4000
+    field public static final int SIF_UNDEFINED = 0; // 0x0
+    field public static final int SIGNAL_TYPE_AUTO = 1; // 0x1
+    field public static final int SIGNAL_TYPE_NTSC = 32; // 0x20
+    field public static final int SIGNAL_TYPE_NTSC_443 = 64; // 0x40
+    field public static final int SIGNAL_TYPE_PAL = 2; // 0x2
+    field public static final int SIGNAL_TYPE_PAL_60 = 16; // 0x10
+    field public static final int SIGNAL_TYPE_PAL_M = 4; // 0x4
+    field public static final int SIGNAL_TYPE_PAL_N = 8; // 0x8
+    field public static final int SIGNAL_TYPE_SECAM = 128; // 0x80
+    field public static final int SIGNAL_TYPE_UNDEFINED = 0; // 0x0
+  }
+
+  public static class AnalogFrontendSettings.Builder extends android.media.tv.tuner.frontend.FrontendSettings.Builder<android.media.tv.tuner.frontend.AnalogFrontendSettings.Builder> {
+    method @NonNull public android.media.tv.tuner.frontend.AnalogFrontendSettings build();
+    method @NonNull public android.media.tv.tuner.frontend.AnalogFrontendSettings.Builder setSifStandard(int);
+    method @NonNull public android.media.tv.tuner.frontend.AnalogFrontendSettings.Builder setSignalType(int);
+  }
+
+  public class Atsc3FrontendCapabilities extends android.media.tv.tuner.frontend.FrontendCapabilities {
+    method public int getBandwidthCapability();
+    method public int getDemodOutputFormatCapability();
+    method public int getFecCapability();
+    method public int getModulationCapability();
+    method public int getPlpCodeRateCapability();
+    method public int getTimeInterleaveModeCapability();
+  }
+
+  public class Atsc3FrontendSettings extends android.media.tv.tuner.frontend.FrontendSettings {
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.frontend.Atsc3FrontendSettings.Builder builder(@NonNull android.content.Context);
+    method public int getBandwidth();
+    method public int getDemodOutputFormat();
+    method @NonNull public android.media.tv.tuner.frontend.Atsc3PlpSettings[] getPlpSettings();
+    method public int getType();
+    field public static final int BANDWIDTH_AUTO = 1; // 0x1
+    field public static final int BANDWIDTH_BANDWIDTH_6MHZ = 2; // 0x2
+    field public static final int BANDWIDTH_BANDWIDTH_7MHZ = 4; // 0x4
+    field public static final int BANDWIDTH_BANDWIDTH_8MHZ = 8; // 0x8
+    field public static final int BANDWIDTH_UNDEFINED = 0; // 0x0
+    field public static final int CODERATE_10_15 = 512; // 0x200
+    field public static final int CODERATE_11_15 = 1024; // 0x400
+    field public static final int CODERATE_12_15 = 2048; // 0x800
+    field public static final int CODERATE_13_15 = 4096; // 0x1000
+    field public static final int CODERATE_2_15 = 2; // 0x2
+    field public static final int CODERATE_3_15 = 4; // 0x4
+    field public static final int CODERATE_4_15 = 8; // 0x8
+    field public static final int CODERATE_5_15 = 16; // 0x10
+    field public static final int CODERATE_6_15 = 32; // 0x20
+    field public static final int CODERATE_7_15 = 64; // 0x40
+    field public static final int CODERATE_8_15 = 128; // 0x80
+    field public static final int CODERATE_9_15 = 256; // 0x100
+    field public static final int CODERATE_AUTO = 1; // 0x1
+    field public static final int CODERATE_UNDEFINED = 0; // 0x0
+    field public static final int DEMOD_OUTPUT_FORMAT_ATSC3_LINKLAYER_PACKET = 1; // 0x1
+    field public static final int DEMOD_OUTPUT_FORMAT_BASEBAND_PACKET = 2; // 0x2
+    field public static final int DEMOD_OUTPUT_FORMAT_UNDEFINED = 0; // 0x0
+    field public static final int FEC_AUTO = 1; // 0x1
+    field public static final int FEC_BCH_LDPC_16K = 2; // 0x2
+    field public static final int FEC_BCH_LDPC_64K = 4; // 0x4
+    field public static final int FEC_CRC_LDPC_16K = 8; // 0x8
+    field public static final int FEC_CRC_LDPC_64K = 16; // 0x10
+    field public static final int FEC_LDPC_16K = 32; // 0x20
+    field public static final int FEC_LDPC_64K = 64; // 0x40
+    field public static final int FEC_UNDEFINED = 0; // 0x0
+    field public static final int MODULATION_AUTO = 1; // 0x1
+    field public static final int MODULATION_MOD_1024QAM = 32; // 0x20
+    field public static final int MODULATION_MOD_16QAM = 4; // 0x4
+    field public static final int MODULATION_MOD_256QAM = 16; // 0x10
+    field public static final int MODULATION_MOD_4096QAM = 64; // 0x40
+    field public static final int MODULATION_MOD_64QAM = 8; // 0x8
+    field public static final int MODULATION_MOD_QPSK = 2; // 0x2
+    field public static final int MODULATION_UNDEFINED = 0; // 0x0
+    field public static final int TIME_INTERLEAVE_MODE_AUTO = 1; // 0x1
+    field public static final int TIME_INTERLEAVE_MODE_CTI = 2; // 0x2
+    field public static final int TIME_INTERLEAVE_MODE_HTI = 4; // 0x4
+    field public static final int TIME_INTERLEAVE_MODE_UNDEFINED = 0; // 0x0
+  }
+
+  public static class Atsc3FrontendSettings.Builder extends android.media.tv.tuner.frontend.FrontendSettings.Builder<android.media.tv.tuner.frontend.Atsc3FrontendSettings.Builder> {
+    method @NonNull public android.media.tv.tuner.frontend.Atsc3FrontendSettings build();
+    method @NonNull public android.media.tv.tuner.frontend.Atsc3FrontendSettings.Builder setBandwidth(int);
+    method @NonNull public android.media.tv.tuner.frontend.Atsc3FrontendSettings.Builder setDemodOutputFormat(byte);
+    method @NonNull public android.media.tv.tuner.frontend.Atsc3FrontendSettings.Builder setPlpSettings(@NonNull android.media.tv.tuner.frontend.Atsc3PlpSettings[]);
+  }
+
+  public class Atsc3PlpInfo {
+    method public boolean getLlsFlag();
+    method public int getPlpId();
+  }
+
+  public class Atsc3PlpSettings {
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.frontend.Atsc3PlpSettings.Builder builder(@NonNull android.content.Context);
+    method public int getCodeRate();
+    method public int getFec();
+    method public int getInterleaveMode();
+    method public int getModulation();
+    method public int getPlpId();
+  }
+
+  public static class Atsc3PlpSettings.Builder {
+    method @NonNull public android.media.tv.tuner.frontend.Atsc3PlpSettings build();
+    method @NonNull public android.media.tv.tuner.frontend.Atsc3PlpSettings.Builder setCodeRate(int);
+    method @NonNull public android.media.tv.tuner.frontend.Atsc3PlpSettings.Builder setFec(int);
+    method @NonNull public android.media.tv.tuner.frontend.Atsc3PlpSettings.Builder setInterleaveMode(int);
+    method @NonNull public android.media.tv.tuner.frontend.Atsc3PlpSettings.Builder setModulation(int);
+    method @NonNull public android.media.tv.tuner.frontend.Atsc3PlpSettings.Builder setPlpId(int);
+  }
+
+  public class AtscFrontendCapabilities extends android.media.tv.tuner.frontend.FrontendCapabilities {
+    method public int getModulationCapability();
+  }
+
+  public class AtscFrontendSettings extends android.media.tv.tuner.frontend.FrontendSettings {
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.frontend.AtscFrontendSettings.Builder builder(@NonNull android.content.Context);
+    method public int getModulation();
+    method public int getType();
+    field public static final int MODULATION_AUTO = 1; // 0x1
+    field public static final int MODULATION_MOD_16VSB = 8; // 0x8
+    field public static final int MODULATION_MOD_8VSB = 4; // 0x4
+    field public static final int MODULATION_UNDEFINED = 0; // 0x0
+  }
+
+  public static class AtscFrontendSettings.Builder extends android.media.tv.tuner.frontend.FrontendSettings.Builder<android.media.tv.tuner.frontend.AtscFrontendSettings.Builder> {
+    method @NonNull public android.media.tv.tuner.frontend.AtscFrontendSettings build();
+    method @NonNull public android.media.tv.tuner.frontend.AtscFrontendSettings.Builder setModulation(int);
+  }
+
+  public class DvbcFrontendCapabilities extends android.media.tv.tuner.frontend.FrontendCapabilities {
+    method public int getAnnexCapability();
+    method public int getFecCapability();
+    method public int getModulationCapability();
+  }
+
+  public class DvbcFrontendSettings extends android.media.tv.tuner.frontend.FrontendSettings {
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.frontend.DvbcFrontendSettings.Builder builder(@NonNull android.content.Context);
+    method public byte getAnnex();
+    method public long getFec();
+    method public int getModulation();
+    method public int getOuterFec();
+    method public int getSpectralInversion();
+    method public int getSymbolRate();
+    method public int getType();
+    field public static final int ANNEX_A = 1; // 0x1
+    field public static final int ANNEX_B = 2; // 0x2
+    field public static final int ANNEX_C = 4; // 0x4
+    field public static final int ANNEX_UNDEFINED = 0; // 0x0
+    field public static final int MODULATION_AUTO = 1; // 0x1
+    field public static final int MODULATION_MOD_128QAM = 16; // 0x10
+    field public static final int MODULATION_MOD_16QAM = 2; // 0x2
+    field public static final int MODULATION_MOD_256QAM = 32; // 0x20
+    field public static final int MODULATION_MOD_32QAM = 4; // 0x4
+    field public static final int MODULATION_MOD_64QAM = 8; // 0x8
+    field public static final int MODULATION_UNDEFINED = 0; // 0x0
+    field public static final int OUTER_FEC_OUTER_FEC_NONE = 1; // 0x1
+    field public static final int OUTER_FEC_OUTER_FEC_RS = 2; // 0x2
+    field public static final int OUTER_FEC_UNDEFINED = 0; // 0x0
+    field public static final int SPECTRAL_INVERSION_INVERTED = 2; // 0x2
+    field public static final int SPECTRAL_INVERSION_NORMAL = 1; // 0x1
+    field public static final int SPECTRAL_INVERSION_UNDEFINED = 0; // 0x0
+  }
+
+  public static class DvbcFrontendSettings.Builder extends android.media.tv.tuner.frontend.FrontendSettings.Builder<android.media.tv.tuner.frontend.DvbcFrontendSettings.Builder> {
+    method @NonNull public android.media.tv.tuner.frontend.DvbcFrontendSettings build();
+    method @NonNull public android.media.tv.tuner.frontend.DvbcFrontendSettings.Builder setAnnex(byte);
+    method @NonNull public android.media.tv.tuner.frontend.DvbcFrontendSettings.Builder setFec(long);
+    method @NonNull public android.media.tv.tuner.frontend.DvbcFrontendSettings.Builder setModulation(int);
+    method @NonNull public android.media.tv.tuner.frontend.DvbcFrontendSettings.Builder setOuterFec(int);
+    method @NonNull public android.media.tv.tuner.frontend.DvbcFrontendSettings.Builder setSpectralInversion(int);
+    method @NonNull public android.media.tv.tuner.frontend.DvbcFrontendSettings.Builder setSymbolRate(int);
+  }
+
+  public class DvbsCodeRate {
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.frontend.DvbsCodeRate.Builder builder(@NonNull android.content.Context);
+    method public int getBitsPer1000Symbol();
+    method public long getInnerFec();
+    method public boolean isLinear();
+    method public boolean isShortFrameEnabled();
+  }
+
+  public static class DvbsCodeRate.Builder {
+    method @NonNull public android.media.tv.tuner.frontend.DvbsCodeRate build();
+    method @NonNull public android.media.tv.tuner.frontend.DvbsCodeRate.Builder setBitsPer1000Symbol(int);
+    method @NonNull public android.media.tv.tuner.frontend.DvbsCodeRate.Builder setInnerFec(long);
+    method @NonNull public android.media.tv.tuner.frontend.DvbsCodeRate.Builder setLinear(boolean);
+    method @NonNull public android.media.tv.tuner.frontend.DvbsCodeRate.Builder setShortFrameEnabled(boolean);
+  }
+
+  public class DvbsFrontendCapabilities extends android.media.tv.tuner.frontend.FrontendCapabilities {
+    method public long getInnerFecCapability();
+    method public int getModulationCapability();
+    method public int getStandardCapability();
+  }
+
+  public class DvbsFrontendSettings extends android.media.tv.tuner.frontend.FrontendSettings {
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder builder(@NonNull android.content.Context);
+    method @Nullable public android.media.tv.tuner.frontend.DvbsCodeRate getCodeRate();
+    method public int getInputStreamId();
+    method public int getModulation();
+    method public int getPilot();
+    method public int getRolloff();
+    method public int getStandard();
+    method public int getSymbolRate();
+    method public int getType();
+    method public int getVcmMode();
+    field public static final int MODULATION_AUTO = 1; // 0x1
+    field public static final int MODULATION_MOD_128APSK = 2048; // 0x800
+    field public static final int MODULATION_MOD_16APSK = 256; // 0x100
+    field public static final int MODULATION_MOD_16PSK = 16; // 0x10
+    field public static final int MODULATION_MOD_16QAM = 8; // 0x8
+    field public static final int MODULATION_MOD_256APSK = 4096; // 0x1000
+    field public static final int MODULATION_MOD_32APSK = 512; // 0x200
+    field public static final int MODULATION_MOD_32PSK = 32; // 0x20
+    field public static final int MODULATION_MOD_64APSK = 1024; // 0x400
+    field public static final int MODULATION_MOD_8APSK = 128; // 0x80
+    field public static final int MODULATION_MOD_8PSK = 4; // 0x4
+    field public static final int MODULATION_MOD_ACM = 64; // 0x40
+    field public static final int MODULATION_MOD_QPSK = 2; // 0x2
+    field public static final int MODULATION_MOD_RESERVED = 8192; // 0x2000
+    field public static final int MODULATION_UNDEFINED = 0; // 0x0
+    field public static final int PILOT_AUTO = 3; // 0x3
+    field public static final int PILOT_OFF = 2; // 0x2
+    field public static final int PILOT_ON = 1; // 0x1
+    field public static final int PILOT_UNDEFINED = 0; // 0x0
+    field public static final int ROLLOFF_0_10 = 5; // 0x5
+    field public static final int ROLLOFF_0_15 = 4; // 0x4
+    field public static final int ROLLOFF_0_20 = 3; // 0x3
+    field public static final int ROLLOFF_0_25 = 2; // 0x2
+    field public static final int ROLLOFF_0_35 = 1; // 0x1
+    field public static final int ROLLOFF_0_5 = 6; // 0x6
+    field public static final int ROLLOFF_UNDEFINED = 0; // 0x0
+    field public static final int STANDARD_AUTO = 1; // 0x1
+    field public static final int STANDARD_S = 2; // 0x2
+    field public static final int STANDARD_S2 = 4; // 0x4
+    field public static final int STANDARD_S2X = 8; // 0x8
+    field public static final int VCM_MODE_AUTO = 1; // 0x1
+    field public static final int VCM_MODE_MANUAL = 2; // 0x2
+    field public static final int VCM_MODE_UNDEFINED = 0; // 0x0
+  }
+
+  public static class DvbsFrontendSettings.Builder extends android.media.tv.tuner.frontend.FrontendSettings.Builder<android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder> {
+    method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings build();
+    method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder setCodeRate(@Nullable android.media.tv.tuner.frontend.DvbsCodeRate);
+    method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder setInputStreamId(int);
+    method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder setModulation(int);
+    method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder setPilot(int);
+    method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder setRolloff(int);
+    method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder setStandard(int);
+    method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder setSymbolRate(int);
+    method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder setVcmMode(int);
+  }
+
+  public class DvbtFrontendCapabilities extends android.media.tv.tuner.frontend.FrontendCapabilities {
+    method public int getBandwidthCapability();
+    method public int getCodeRateCapability();
+    method public int getConstellationCapability();
+    method public int getGuardIntervalCapability();
+    method public int getHierarchyCapability();
+    method public int getTransmissionModeCapability();
+    method public boolean isMisoSupported();
+    method public boolean isT2Supported();
+  }
+
+  public class DvbtFrontendSettings extends android.media.tv.tuner.frontend.FrontendSettings {
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.frontend.DvbtFrontendSettings.Builder builder(@NonNull android.content.Context);
+    method public int getBandwidth();
+    method public int getConstellation();
+    method public int getGuardInterval();
+    method public int getHierarchy();
+    method public int getHpCodeRate();
+    method public int getLpCodeRate();
+    method public int getPlpGroupId();
+    method public int getPlpId();
+    method public int getPlpMode();
+    method public int getStandard();
+    method public int getTransmissionMode();
+    method public int getType();
+    method public boolean isHighPriority();
+    method public boolean isMiso();
+    field public static final int BANDWIDTH_10MHZ = 64; // 0x40
+    field public static final int BANDWIDTH_1_7MHZ = 32; // 0x20
+    field public static final int BANDWIDTH_5MHZ = 16; // 0x10
+    field public static final int BANDWIDTH_6MHZ = 8; // 0x8
+    field public static final int BANDWIDTH_7MHZ = 4; // 0x4
+    field public static final int BANDWIDTH_8MHZ = 2; // 0x2
+    field public static final int BANDWIDTH_AUTO = 1; // 0x1
+    field public static final int BANDWIDTH_UNDEFINED = 0; // 0x0
+    field public static final int CODERATE_1_2 = 2; // 0x2
+    field public static final int CODERATE_2_3 = 4; // 0x4
+    field public static final int CODERATE_3_4 = 8; // 0x8
+    field public static final int CODERATE_3_5 = 64; // 0x40
+    field public static final int CODERATE_4_5 = 128; // 0x80
+    field public static final int CODERATE_5_6 = 16; // 0x10
+    field public static final int CODERATE_6_7 = 256; // 0x100
+    field public static final int CODERATE_7_8 = 32; // 0x20
+    field public static final int CODERATE_8_9 = 512; // 0x200
+    field public static final int CODERATE_AUTO = 1; // 0x1
+    field public static final int CODERATE_UNDEFINED = 0; // 0x0
+    field public static final int CONSTELLATION_AUTO = 1; // 0x1
+    field public static final int CONSTELLATION_CONSTELLATION_16QAM = 4; // 0x4
+    field public static final int CONSTELLATION_CONSTELLATION_256QAM = 16; // 0x10
+    field public static final int CONSTELLATION_CONSTELLATION_64QAM = 8; // 0x8
+    field public static final int CONSTELLATION_CONSTELLATION_QPSK = 2; // 0x2
+    field public static final int CONSTELLATION_UNDEFINED = 0; // 0x0
+    field public static final int GUARD_INTERVAL_AUTO = 1; // 0x1
+    field public static final int GUARD_INTERVAL_INTERVAL_19_128 = 64; // 0x40
+    field public static final int GUARD_INTERVAL_INTERVAL_19_256 = 128; // 0x80
+    field public static final int GUARD_INTERVAL_INTERVAL_1_128 = 32; // 0x20
+    field public static final int GUARD_INTERVAL_INTERVAL_1_16 = 4; // 0x4
+    field public static final int GUARD_INTERVAL_INTERVAL_1_32 = 2; // 0x2
+    field public static final int GUARD_INTERVAL_INTERVAL_1_4 = 16; // 0x10
+    field public static final int GUARD_INTERVAL_INTERVAL_1_8 = 8; // 0x8
+    field public static final int GUARD_INTERVAL_UNDEFINED = 0; // 0x0
+    field public static final int HIERARCHY_1_INDEPTH = 64; // 0x40
+    field public static final int HIERARCHY_1_NATIVE = 4; // 0x4
+    field public static final int HIERARCHY_2_INDEPTH = 128; // 0x80
+    field public static final int HIERARCHY_2_NATIVE = 8; // 0x8
+    field public static final int HIERARCHY_4_INDEPTH = 256; // 0x100
+    field public static final int HIERARCHY_4_NATIVE = 16; // 0x10
+    field public static final int HIERARCHY_AUTO = 1; // 0x1
+    field public static final int HIERARCHY_NON_INDEPTH = 32; // 0x20
+    field public static final int HIERARCHY_NON_NATIVE = 2; // 0x2
+    field public static final int HIERARCHY_UNDEFINED = 0; // 0x0
+    field public static final int PLP_MODE_AUTO = 1; // 0x1
+    field public static final int PLP_MODE_MANUAL = 2; // 0x2
+    field public static final int PLP_MODE_UNDEFINED = 0; // 0x0
+    field public static final int STANDARD_AUTO = 1; // 0x1
+    field public static final int STANDARD_T = 2; // 0x2
+    field public static final int STANDARD_T2 = 4; // 0x4
+    field public static final int TRANSMISSION_MODE_16K = 32; // 0x20
+    field public static final int TRANSMISSION_MODE_1K = 16; // 0x10
+    field public static final int TRANSMISSION_MODE_2K = 2; // 0x2
+    field public static final int TRANSMISSION_MODE_32K = 64; // 0x40
+    field public static final int TRANSMISSION_MODE_4K = 8; // 0x8
+    field public static final int TRANSMISSION_MODE_8K = 4; // 0x4
+    field public static final int TRANSMISSION_MODE_AUTO = 1; // 0x1
+    field public static final int TRANSMISSION_MODE_UNDEFINED = 0; // 0x0
+  }
+
+  public static class DvbtFrontendSettings.Builder extends android.media.tv.tuner.frontend.FrontendSettings.Builder<android.media.tv.tuner.frontend.DvbtFrontendSettings.Builder> {
+    method @NonNull public android.media.tv.tuner.frontend.DvbtFrontendSettings build();
+    method @NonNull public android.media.tv.tuner.frontend.DvbtFrontendSettings.Builder setBandwidth(int);
+    method @NonNull public android.media.tv.tuner.frontend.DvbtFrontendSettings.Builder setConstellation(int);
+    method @NonNull public android.media.tv.tuner.frontend.DvbtFrontendSettings.Builder setGuardInterval(int);
+    method @NonNull public android.media.tv.tuner.frontend.DvbtFrontendSettings.Builder setHierarchy(int);
+    method @NonNull public android.media.tv.tuner.frontend.DvbtFrontendSettings.Builder setHighPriority(boolean);
+    method @NonNull public android.media.tv.tuner.frontend.DvbtFrontendSettings.Builder setHpCodeRate(int);
+    method @NonNull public android.media.tv.tuner.frontend.DvbtFrontendSettings.Builder setLpCodeRate(int);
+    method @NonNull public android.media.tv.tuner.frontend.DvbtFrontendSettings.Builder setMiso(boolean);
+    method @NonNull public android.media.tv.tuner.frontend.DvbtFrontendSettings.Builder setPlpGroupId(int);
+    method @NonNull public android.media.tv.tuner.frontend.DvbtFrontendSettings.Builder setPlpId(int);
+    method @NonNull public android.media.tv.tuner.frontend.DvbtFrontendSettings.Builder setPlpMode(int);
+    method @NonNull public android.media.tv.tuner.frontend.DvbtFrontendSettings.Builder setStandard(int);
+    method @NonNull public android.media.tv.tuner.frontend.DvbtFrontendSettings.Builder setTransmissionMode(int);
+  }
+
+  public abstract class FrontendCapabilities {
+    ctor public FrontendCapabilities();
+  }
+
+  public class FrontendInfo {
+    method public int getAcquireRange();
+    method public int getExclusiveGroupId();
+    method @NonNull public android.util.Range<java.lang.Integer> getFrequencyRange();
+    method @NonNull public android.media.tv.tuner.frontend.FrontendCapabilities getFrontendCapability();
+    method public int getId();
+    method @NonNull public int[] getStatusCapabilities();
+    method @NonNull public android.util.Range<java.lang.Integer> getSymbolRateRange();
+    method public int getType();
+  }
+
+  public abstract class FrontendSettings {
+    method public int getFrequency();
+    method public abstract int getType();
+    field public static final long FEC_11_15 = 4194304L; // 0x400000L
+    field public static final long FEC_11_20 = 8388608L; // 0x800000L
+    field public static final long FEC_11_45 = 16777216L; // 0x1000000L
+    field public static final long FEC_13_18 = 33554432L; // 0x2000000L
+    field public static final long FEC_13_45 = 67108864L; // 0x4000000L
+    field public static final long FEC_14_45 = 134217728L; // 0x8000000L
+    field public static final long FEC_1_2 = 2L; // 0x2L
+    field public static final long FEC_1_3 = 4L; // 0x4L
+    field public static final long FEC_1_4 = 8L; // 0x8L
+    field public static final long FEC_1_5 = 16L; // 0x10L
+    field public static final long FEC_23_36 = 268435456L; // 0x10000000L
+    field public static final long FEC_25_36 = 536870912L; // 0x20000000L
+    field public static final long FEC_26_45 = 1073741824L; // 0x40000000L
+    field public static final long FEC_28_45 = -2147483648L; // 0xffffffff80000000L
+    field public static final long FEC_29_45 = 1L; // 0x1L
+    field public static final long FEC_2_3 = 32L; // 0x20L
+    field public static final long FEC_2_5 = 64L; // 0x40L
+    field public static final long FEC_2_9 = 128L; // 0x80L
+    field public static final long FEC_31_45 = 2L; // 0x2L
+    field public static final long FEC_32_45 = 4L; // 0x4L
+    field public static final long FEC_3_4 = 256L; // 0x100L
+    field public static final long FEC_3_5 = 512L; // 0x200L
+    field public static final long FEC_4_15 = 2048L; // 0x800L
+    field public static final long FEC_4_5 = 1024L; // 0x400L
+    field public static final long FEC_5_6 = 4096L; // 0x1000L
+    field public static final long FEC_5_9 = 8192L; // 0x2000L
+    field public static final long FEC_6_7 = 16384L; // 0x4000L
+    field public static final long FEC_77_90 = 8L; // 0x8L
+    field public static final long FEC_7_15 = 131072L; // 0x20000L
+    field public static final long FEC_7_8 = 32768L; // 0x8000L
+    field public static final long FEC_7_9 = 65536L; // 0x10000L
+    field public static final long FEC_8_15 = 524288L; // 0x80000L
+    field public static final long FEC_8_9 = 262144L; // 0x40000L
+    field public static final long FEC_9_10 = 1048576L; // 0x100000L
+    field public static final long FEC_9_20 = 2097152L; // 0x200000L
+    field public static final long FEC_AUTO = 1L; // 0x1L
+    field public static final long FEC_UNDEFINED = 0L; // 0x0L
+    field public static final int TYPE_ANALOG = 1; // 0x1
+    field public static final int TYPE_ATSC = 2; // 0x2
+    field public static final int TYPE_ATSC3 = 3; // 0x3
+    field public static final int TYPE_DVBC = 4; // 0x4
+    field public static final int TYPE_DVBS = 5; // 0x5
+    field public static final int TYPE_DVBT = 6; // 0x6
+    field public static final int TYPE_ISDBS = 7; // 0x7
+    field public static final int TYPE_ISDBS3 = 8; // 0x8
+    field public static final int TYPE_ISDBT = 9; // 0x9
+    field public static final int TYPE_UNDEFINED = 0; // 0x0
+  }
+
+  public abstract static class FrontendSettings.Builder<T extends android.media.tv.tuner.frontend.FrontendSettings.Builder<T>> {
+    method @IntRange(from=1) @NonNull public T setFrequency(int);
+  }
+
+  public class FrontendStatus {
+    method public int getAgc();
+    method @NonNull public android.media.tv.tuner.frontend.FrontendStatus.Atsc3PlpInfo[] getAtsc3PlpInfo();
+    method public int getBer();
+    method public long getFec();
+    method public int getFreqOffset();
+    method public int getHierarchy();
+    method @NonNull public boolean[] getLayerErrors();
+    method public int getLberCn();
+    method public int getLnbVoltage();
+    method public int getMer();
+    method public int getModulation();
+    method public int getPer();
+    method public int getPerBer();
+    method public int getPlpId();
+    method public int getSignalQuality();
+    method public int getSignalStrength();
+    method public int getSnr();
+    method public int getSpectralInversion();
+    method public int getSymbolRate();
+    method public int getVberCn();
+    method public int getXerCn();
+    method public boolean isDemodLocked();
+    method public boolean isEwbs();
+    method public boolean isLnaOn();
+    method public boolean isRfLock();
+    field public static final int FRONTEND_STATUS_TYPE_AGC = 14; // 0xe
+    field public static final int FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO = 24; // 0x18
+    field public static final int FRONTEND_STATUS_TYPE_BER = 2; // 0x2
+    field public static final int FRONTEND_STATUS_TYPE_DEMOD_LOCK = 0; // 0x0
+    field public static final int FRONTEND_STATUS_TYPE_EWBS = 13; // 0xd
+    field public static final int FRONTEND_STATUS_TYPE_FEC = 8; // 0x8
+    field public static final int FRONTEND_STATUS_TYPE_FREQ_OFFSET = 21; // 0x15
+    field public static final int FRONTEND_STATUS_TYPE_HIERARCHY = 22; // 0x16
+    field public static final int FRONTEND_STATUS_TYPE_LAYER_ERROR = 16; // 0x10
+    field public static final int FRONTEND_STATUS_TYPE_LBER_CN = 18; // 0x12
+    field public static final int FRONTEND_STATUS_TYPE_LNA = 15; // 0xf
+    field public static final int FRONTEND_STATUS_TYPE_LNB_VOLTAGE = 11; // 0xb
+    field public static final int FRONTEND_STATUS_TYPE_MER = 20; // 0x14
+    field public static final int FRONTEND_STATUS_TYPE_MODULATION = 9; // 0x9
+    field public static final int FRONTEND_STATUS_TYPE_PER = 3; // 0x3
+    field public static final int FRONTEND_STATUS_TYPE_PLP_ID = 12; // 0xc
+    field public static final int FRONTEND_STATUS_TYPE_PRE_BER = 4; // 0x4
+    field public static final int FRONTEND_STATUS_TYPE_RF_LOCK = 23; // 0x17
+    field public static final int FRONTEND_STATUS_TYPE_SIGNAL_QUALITY = 5; // 0x5
+    field public static final int FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH = 6; // 0x6
+    field public static final int FRONTEND_STATUS_TYPE_SNR = 1; // 0x1
+    field public static final int FRONTEND_STATUS_TYPE_SPECTRAL = 10; // 0xa
+    field public static final int FRONTEND_STATUS_TYPE_SYMBOL_RATE = 7; // 0x7
+    field public static final int FRONTEND_STATUS_TYPE_VBER_CN = 17; // 0x11
+    field public static final int FRONTEND_STATUS_TYPE_XER_CN = 19; // 0x13
+  }
+
+  public static class FrontendStatus.Atsc3PlpInfo {
+    method public int getPlpId();
+    method public int getUec();
+    method public boolean isLock();
+  }
+
+  public class Isdbs3FrontendCapabilities extends android.media.tv.tuner.frontend.FrontendCapabilities {
+    method public int getCodeRateCapability();
+    method public int getModulationCapability();
+  }
+
+  public class Isdbs3FrontendSettings extends android.media.tv.tuner.frontend.FrontendSettings {
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.frontend.Isdbs3FrontendSettings.Builder builder(@NonNull android.content.Context);
+    method public int getCodeRate();
+    method public int getModulation();
+    method public int getRolloff();
+    method public int getStreamId();
+    method public int getStreamIdType();
+    method public int getSymbolRate();
+    method public int getType();
+    field public static final int CODERATE_1_2 = 8; // 0x8
+    field public static final int CODERATE_1_3 = 2; // 0x2
+    field public static final int CODERATE_2_3 = 32; // 0x20
+    field public static final int CODERATE_2_5 = 4; // 0x4
+    field public static final int CODERATE_3_4 = 64; // 0x40
+    field public static final int CODERATE_3_5 = 16; // 0x10
+    field public static final int CODERATE_4_5 = 256; // 0x100
+    field public static final int CODERATE_5_6 = 512; // 0x200
+    field public static final int CODERATE_7_8 = 1024; // 0x400
+    field public static final int CODERATE_7_9 = 128; // 0x80
+    field public static final int CODERATE_9_10 = 2048; // 0x800
+    field public static final int CODERATE_AUTO = 1; // 0x1
+    field public static final int CODERATE_UNDEFINED = 0; // 0x0
+    field public static final int MODULATION_AUTO = 1; // 0x1
+    field public static final int MODULATION_MOD_16APSK = 16; // 0x10
+    field public static final int MODULATION_MOD_32APSK = 32; // 0x20
+    field public static final int MODULATION_MOD_8PSK = 8; // 0x8
+    field public static final int MODULATION_MOD_BPSK = 2; // 0x2
+    field public static final int MODULATION_MOD_QPSK = 4; // 0x4
+    field public static final int MODULATION_UNDEFINED = 0; // 0x0
+    field public static final int ROLLOFF_0_03 = 1; // 0x1
+    field public static final int ROLLOFF_UNDEFINED = 0; // 0x0
+  }
+
+  public static class Isdbs3FrontendSettings.Builder extends android.media.tv.tuner.frontend.FrontendSettings.Builder<android.media.tv.tuner.frontend.Isdbs3FrontendSettings.Builder> {
+    method @NonNull public android.media.tv.tuner.frontend.Isdbs3FrontendSettings build();
+    method @NonNull public android.media.tv.tuner.frontend.Isdbs3FrontendSettings.Builder setCodeRate(int);
+    method @NonNull public android.media.tv.tuner.frontend.Isdbs3FrontendSettings.Builder setModulation(int);
+    method @NonNull public android.media.tv.tuner.frontend.Isdbs3FrontendSettings.Builder setRolloff(int);
+    method @NonNull public android.media.tv.tuner.frontend.Isdbs3FrontendSettings.Builder setStreamId(int);
+    method @NonNull public android.media.tv.tuner.frontend.Isdbs3FrontendSettings.Builder setStreamIdType(int);
+    method @NonNull public android.media.tv.tuner.frontend.Isdbs3FrontendSettings.Builder setSymbolRate(int);
+  }
+
+  public class IsdbsFrontendCapabilities extends android.media.tv.tuner.frontend.FrontendCapabilities {
+    method public int getCodeRateCapability();
+    method public int getModulationCapability();
+  }
+
+  public class IsdbsFrontendSettings extends android.media.tv.tuner.frontend.FrontendSettings {
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.frontend.IsdbsFrontendSettings.Builder builder(@NonNull android.content.Context);
+    method public int getCodeRate();
+    method public int getModulation();
+    method public int getRolloff();
+    method public int getStreamId();
+    method public int getStreamIdType();
+    method public int getSymbolRate();
+    method public int getType();
+    field public static final int CODERATE_1_2 = 2; // 0x2
+    field public static final int CODERATE_2_3 = 4; // 0x4
+    field public static final int CODERATE_3_4 = 8; // 0x8
+    field public static final int CODERATE_5_6 = 16; // 0x10
+    field public static final int CODERATE_7_8 = 32; // 0x20
+    field public static final int CODERATE_AUTO = 1; // 0x1
+    field public static final int CODERATE_UNDEFINED = 0; // 0x0
+    field public static final int MODULATION_AUTO = 1; // 0x1
+    field public static final int MODULATION_MOD_BPSK = 2; // 0x2
+    field public static final int MODULATION_MOD_QPSK = 4; // 0x4
+    field public static final int MODULATION_MOD_TC8PSK = 8; // 0x8
+    field public static final int MODULATION_UNDEFINED = 0; // 0x0
+    field public static final int ROLLOFF_0_35 = 1; // 0x1
+    field public static final int ROLLOFF_UNDEFINED = 0; // 0x0
+    field public static final int STREAM_ID_TYPE_ID = 0; // 0x0
+    field public static final int STREAM_ID_TYPE_RELATIVE_NUMBER = 1; // 0x1
+  }
+
+  public static class IsdbsFrontendSettings.Builder extends android.media.tv.tuner.frontend.FrontendSettings.Builder<android.media.tv.tuner.frontend.IsdbsFrontendSettings.Builder> {
+    method @NonNull public android.media.tv.tuner.frontend.IsdbsFrontendSettings build();
+    method @NonNull public android.media.tv.tuner.frontend.IsdbsFrontendSettings.Builder setCodeRate(int);
+    method @NonNull public android.media.tv.tuner.frontend.IsdbsFrontendSettings.Builder setModulation(int);
+    method @NonNull public android.media.tv.tuner.frontend.IsdbsFrontendSettings.Builder setRolloff(int);
+    method @NonNull public android.media.tv.tuner.frontend.IsdbsFrontendSettings.Builder setStreamId(int);
+    method @NonNull public android.media.tv.tuner.frontend.IsdbsFrontendSettings.Builder setStreamIdType(int);
+    method @NonNull public android.media.tv.tuner.frontend.IsdbsFrontendSettings.Builder setSymbolRate(int);
+  }
+
+  public class IsdbtFrontendCapabilities extends android.media.tv.tuner.frontend.FrontendCapabilities {
+    method public int getBandwidthCapability();
+    method public int getCodeRateCapability();
+    method public int getGuardIntervalCapability();
+    method public int getModeCapability();
+    method public int getModulationCapability();
+  }
+
+  public class IsdbtFrontendSettings extends android.media.tv.tuner.frontend.FrontendSettings {
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.frontend.IsdbtFrontendSettings.Builder builder(@NonNull android.content.Context);
+    method public int getBandwidth();
+    method public int getCodeRate();
+    method public int getGuardInterval();
+    method public int getMode();
+    method public int getModulation();
+    method public int getServiceAreaId();
+    method public int getType();
+    field public static final int BANDWIDTH_6MHZ = 8; // 0x8
+    field public static final int BANDWIDTH_7MHZ = 4; // 0x4
+    field public static final int BANDWIDTH_8MHZ = 2; // 0x2
+    field public static final int BANDWIDTH_AUTO = 1; // 0x1
+    field public static final int BANDWIDTH_UNDEFINED = 0; // 0x0
+    field public static final int MODE_1 = 2; // 0x2
+    field public static final int MODE_2 = 4; // 0x4
+    field public static final int MODE_3 = 8; // 0x8
+    field public static final int MODE_AUTO = 1; // 0x1
+    field public static final int MODE_UNDEFINED = 0; // 0x0
+    field public static final int MODULATION_AUTO = 1; // 0x1
+    field public static final int MODULATION_MOD_16QAM = 8; // 0x8
+    field public static final int MODULATION_MOD_64QAM = 16; // 0x10
+    field public static final int MODULATION_MOD_DQPSK = 2; // 0x2
+    field public static final int MODULATION_MOD_QPSK = 4; // 0x4
+    field public static final int MODULATION_UNDEFINED = 0; // 0x0
+  }
+
+  public static class IsdbtFrontendSettings.Builder extends android.media.tv.tuner.frontend.FrontendSettings.Builder<android.media.tv.tuner.frontend.IsdbtFrontendSettings.Builder> {
+    method @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings build();
+    method @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings.Builder setBandwidth(int);
+    method @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings.Builder setCodeRate(int);
+    method @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings.Builder setGuardInterval(int);
+    method @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings.Builder setMode(int);
+    method @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings.Builder setModulation(int);
+    method @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings.Builder setServiceAreaId(int);
+  }
+
+  public interface OnTuneEventListener {
+    method public void onTuneEvent(int);
+    field public static final int SIGNAL_LOCKED = 0; // 0x0
+    field public static final int SIGNAL_LOST_LOCK = 2; // 0x2
+    field public static final int SIGNAL_NO_SIGNAL = 1; // 0x1
+  }
+
+  public interface ScanCallback {
+    method public void onAnalogSifStandard(int);
+    method public void onAtsc3PlpInfos(@NonNull android.media.tv.tuner.frontend.Atsc3PlpInfo[]);
+    method public void onDvbsStandard(int);
+    method public void onDvbtStandard(int);
+    method public void onFrequenciesReport(@NonNull int[]);
+    method public void onGroupIds(@NonNull int[]);
+    method public void onHierarchy(int);
+    method public void onInputStreamIds(@NonNull int[]);
+    method public void onLocked();
+    method public void onPlpIds(@NonNull int[]);
+    method public void onProgress(@IntRange(from=0, to=100) int);
+    method public void onScanStopped();
+    method public void onSignalType(int);
+    method public void onSymbolRates(@NonNull int[]);
+  }
+
+}
+
+package android.media.voice {
+
+  public final class KeyphraseModelManager {
+    method @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public void deleteKeyphraseSoundModel(int, @NonNull java.util.Locale);
+    method @Nullable @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel getKeyphraseSoundModel(int, @NonNull java.util.Locale);
+    method @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public void updateKeyphraseSoundModel(@NonNull android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel);
+  }
+
 }
 
 package android.metrics {
@@ -4666,34 +6047,67 @@
 
   public class CaptivePortal implements android.os.Parcelable {
     method public void logEvent(int, @NonNull String);
+    method public void reevaluateNetwork();
     method public void useNetwork();
+    field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64
     field public static final int APP_RETURN_DISMISSED = 0; // 0x0
     field public static final int APP_RETURN_UNWANTED = 1; // 0x1
     field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2
   }
 
+  public final class CaptivePortalData implements android.os.Parcelable {
+    method public int describeContents();
+    method public long getByteLimit();
+    method public long getExpiryTimeMillis();
+    method public long getRefreshTimeMillis();
+    method @Nullable public android.net.Uri getUserPortalUrl();
+    method @Nullable public android.net.Uri getVenueInfoUrl();
+    method public boolean isCaptive();
+    method public boolean isSessionExtendable();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.CaptivePortalData> CREATOR;
+  }
+
+  public static class CaptivePortalData.Builder {
+    ctor public CaptivePortalData.Builder();
+    ctor public CaptivePortalData.Builder(@Nullable android.net.CaptivePortalData);
+    method @NonNull public android.net.CaptivePortalData build();
+    method @NonNull public android.net.CaptivePortalData.Builder setBytesRemaining(long);
+    method @NonNull public android.net.CaptivePortalData.Builder setCaptive(boolean);
+    method @NonNull public android.net.CaptivePortalData.Builder setExpiryTime(long);
+    method @NonNull public android.net.CaptivePortalData.Builder setRefreshTime(long);
+    method @NonNull public android.net.CaptivePortalData.Builder setSessionExtendable(boolean);
+    method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri);
+    method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri);
+  }
+
   public class ConnectivityManager {
     method @NonNull @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createNattKeepalive(@NonNull android.net.Network, @NonNull android.os.ParcelFileDescriptor, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
     method @NonNull @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull java.net.Socket, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
     method @Deprecated @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public String getCaptivePortalServerUrl();
-    method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEntitlementResultListener);
-    method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported();
-    method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEntitlementResultListener);
+    method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported();
+    method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public int registerNetworkProvider(@NonNull android.net.NetworkProvider);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback);
+    method @Deprecated public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, int, int, @NonNull android.os.Handler);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean);
     method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi();
     method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle);
     method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback);
     method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback, android.os.Handler);
-    method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void stopTethering(int);
-    method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void unregisterTetheringEventCallback(@NonNull android.net.ConnectivityManager.OnTetheringEventCallback);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void stopTethering(int);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void unregisterNetworkProvider(@NonNull android.net.NetworkProvider);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void unregisterTetheringEventCallback(@NonNull android.net.ConnectivityManager.OnTetheringEventCallback);
     field public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC";
     field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT";
     field public static final int TETHERING_BLUETOOTH = 2; // 0x2
     field public static final int TETHERING_USB = 1; // 0x1
     field public static final int TETHERING_WIFI = 0; // 0x0
-    field public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13; // 0xd
-    field public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0
-    field public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb
+    field @Deprecated public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13; // 0xd
+    field @Deprecated public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0
+    field @Deprecated public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb
+    field public static final int TYPE_NONE = -1; // 0xffffffff
+    field @Deprecated public static final int TYPE_WIFI_P2P = 13; // 0xd
   }
 
   public abstract static class ConnectivityManager.OnStartTetheringCallback {
@@ -4702,13 +6116,13 @@
     method public void onTetheringStarted();
   }
 
-  public static interface ConnectivityManager.OnTetheringEntitlementResultListener {
-    method public void onTetheringEntitlementResult(int);
+  @Deprecated public static interface ConnectivityManager.OnTetheringEntitlementResultListener {
+    method @Deprecated public void onTetheringEntitlementResult(int);
   }
 
-  public abstract static class ConnectivityManager.OnTetheringEventCallback {
-    ctor public ConnectivityManager.OnTetheringEventCallback();
-    method public void onUpstreamChanged(@Nullable android.net.Network);
+  @Deprecated public abstract static class ConnectivityManager.OnTetheringEventCallback {
+    ctor @Deprecated public ConnectivityManager.OnTetheringEventCallback();
+    method @Deprecated public void onUpstreamChanged(@Nullable android.net.Network);
   }
 
   public class InvalidPacketException extends java.lang.Exception {
@@ -4798,6 +6212,8 @@
     method @NonNull public java.util.List<java.lang.String> getAllInterfaceNames();
     method @NonNull public java.util.List<android.net.LinkAddress> getAllLinkAddresses();
     method @NonNull public java.util.List<android.net.RouteInfo> getAllRoutes();
+    method @Nullable public android.net.Uri getCaptivePortalApiUrl();
+    method @Nullable public android.net.CaptivePortalData getCaptivePortalData();
     method @NonNull public java.util.List<java.net.InetAddress> getPcscfServers();
     method @Nullable public String getTcpBufferSizes();
     method @NonNull public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers();
@@ -4811,9 +6227,12 @@
     method public boolean isIpv6Provisioned();
     method public boolean isProvisioned();
     method public boolean isReachable(@NonNull java.net.InetAddress);
+    method @NonNull public android.net.LinkProperties makeSensitiveFieldsParcelingCopy();
     method public boolean removeDnsServer(@NonNull java.net.InetAddress);
     method public boolean removeLinkAddress(@NonNull android.net.LinkAddress);
     method public boolean removeRoute(@NonNull android.net.RouteInfo);
+    method public void setCaptivePortalApiUrl(@Nullable android.net.Uri);
+    method public void setCaptivePortalData(@Nullable android.net.CaptivePortalData);
     method public void setPcscfServers(@NonNull java.util.Collection<java.net.InetAddress>);
     method public void setPrivateDnsServerName(@Nullable String);
     method public void setTcpBufferSizes(@Nullable String);
@@ -4829,13 +6248,76 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.net.MatchAllNetworkSpecifier> CREATOR;
   }
 
+  public final class NattKeepalivePacketData extends android.net.KeepalivePacketData implements android.os.Parcelable {
+    ctor public NattKeepalivePacketData(@NonNull java.net.InetAddress, int, @NonNull java.net.InetAddress, int, @NonNull byte[]) throws android.net.InvalidPacketException;
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.NattKeepalivePacketData> CREATOR;
+  }
+
   public class Network implements android.os.Parcelable {
     ctor public Network(@NonNull android.net.Network);
     method @NonNull public android.net.Network getPrivateDnsBypassingCopy();
     field public final int netId;
   }
 
+  public abstract class NetworkAgent {
+    ctor public NetworkAgent(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, int, @NonNull android.net.NetworkAgentConfig, @Nullable android.net.NetworkProvider);
+    method @Nullable public android.net.Network getNetwork();
+    method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData);
+    method public void onAutomaticReconnectDisabled();
+    method public void onBandwidthUpdateRequested();
+    method public void onNetworkUnwanted();
+    method public void onRemoveKeepalivePacketFilter(int);
+    method public void onSaveAcceptUnvalidated(boolean);
+    method public void onSignalStrengthThresholdsUpdated(@NonNull int[]);
+    method public void onStartSocketKeepalive(int, int, @NonNull android.net.KeepalivePacketData);
+    method public void onStopSocketKeepalive(int);
+    method public void onValidationStatus(int, @Nullable String);
+    method @NonNull public android.net.Network register();
+    method public void sendLinkProperties(@NonNull android.net.LinkProperties);
+    method public void sendNetworkCapabilities(@NonNull android.net.NetworkCapabilities);
+    method public void sendNetworkScore(int);
+    method public void sendSocketKeepaliveEvent(int, int);
+    method public void setConnected();
+    method @Deprecated public void setLegacyExtraInfo(@Nullable String);
+    method @Deprecated public void setLegacySubtype(int, @NonNull String);
+    method public void unregister();
+    field public static final int VALIDATION_STATUS_NOT_VALID = 2; // 0x2
+    field public static final int VALIDATION_STATUS_VALID = 1; // 0x1
+    field public final int providerId;
+  }
+
+  public final class NetworkAgentConfig implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getLegacyType();
+    method @NonNull public String getLegacyTypeName();
+    method @Nullable public String getSubscriberId();
+    method public boolean isExplicitlySelected();
+    method public boolean isNat64DetectionEnabled();
+    method public boolean isPartialConnectivityAcceptable();
+    method public boolean isProvisioningNotificationEnabled();
+    method public boolean isUnvalidatedConnectivityAcceptable();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkAgentConfig> CREATOR;
+  }
+
+  public static class NetworkAgentConfig.Builder {
+    ctor public NetworkAgentConfig.Builder();
+    method @NonNull public android.net.NetworkAgentConfig build();
+    method @NonNull public android.net.NetworkAgentConfig.Builder disableNat64Detection();
+    method @NonNull public android.net.NetworkAgentConfig.Builder disableProvisioningNotification();
+    method @NonNull public android.net.NetworkAgentConfig.Builder setExplicitlySelected(boolean);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyType(int);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyTypeName(@NonNull String);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setPartialConnectivityAcceptable(boolean);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setSubscriberId(@Nullable String);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setUnvalidatedConnectivityAcceptable(boolean);
+  }
+
   public final class NetworkCapabilities implements android.os.Parcelable {
+    method public boolean deduceRestrictedCapability();
+    method @Nullable public String getSSID();
     method @NonNull public int[] getTransportTypes();
     method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities);
     method @NonNull public android.net.NetworkCapabilities setSSID(@Nullable String);
@@ -4846,7 +6328,7 @@
 
   public class NetworkKey implements android.os.Parcelable {
     ctor public NetworkKey(android.net.WifiKey);
-    method @Nullable public static android.net.NetworkKey createFromScanResult(@Nullable android.net.wifi.ScanResult);
+    method @Nullable public static android.net.NetworkKey createFromScanResult(@NonNull android.net.wifi.ScanResult);
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkKey> CREATOR;
@@ -4855,23 +6337,54 @@
     field public final android.net.WifiKey wifiKey;
   }
 
+  public class NetworkPolicyManager {
+    method @NonNull public android.telephony.SubscriptionPlan[] getSubscriptionPlans(int, @NonNull String);
+    method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public void registerSubscriptionCallback(@NonNull android.net.NetworkPolicyManager.SubscriptionCallback);
+    method public void setSubscriptionOverride(int, int, int, long, @NonNull String);
+    method public void setSubscriptionPlans(int, @NonNull android.telephony.SubscriptionPlan[], @NonNull String);
+    method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public void unregisterSubscriptionCallback(@NonNull android.net.NetworkPolicyManager.SubscriptionCallback);
+    field public static final int SUBSCRIPTION_OVERRIDE_CONGESTED = 2; // 0x2
+    field public static final int SUBSCRIPTION_OVERRIDE_UNMETERED = 1; // 0x1
+  }
+
+  public static class NetworkPolicyManager.SubscriptionCallback {
+    ctor public NetworkPolicyManager.SubscriptionCallback();
+    method public void onSubscriptionOverride(int, int, int);
+    method public void onSubscriptionPlansChanged(int, @NonNull android.telephony.SubscriptionPlan[]);
+  }
+
+  public class NetworkProvider {
+    ctor public NetworkProvider(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void declareNetworkRequestUnfulfillable(@NonNull android.net.NetworkRequest);
+    method @Nullable public android.os.Messenger getMessenger();
+    method @NonNull public String getName();
+    method public int getProviderId();
+    method public void onNetworkRequested(@NonNull android.net.NetworkRequest, int, int);
+    method public void onRequestWithdrawn(@NonNull android.net.NetworkRequest);
+    field public static final int ID_NONE = -1; // 0xffffffff
+  }
+
   public abstract class NetworkRecommendationProvider {
     ctor public NetworkRecommendationProvider(android.content.Context, java.util.concurrent.Executor);
     method public final android.os.IBinder getBinder();
     method public abstract void onRequestScores(android.net.NetworkKey[]);
   }
 
+  public class NetworkRequest implements android.os.Parcelable {
+    method public boolean satisfiedBy(@Nullable android.net.NetworkCapabilities);
+  }
+
   public static class NetworkRequest.Builder {
     method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int);
   }
 
   public class NetworkScoreManager {
-    method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, "android.permission.REQUEST_NETWORK_SCORES"}) public boolean clearScores() throws java.lang.SecurityException;
-    method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, "android.permission.REQUEST_NETWORK_SCORES"}) public void disableScoring() throws java.lang.SecurityException;
-    method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, "android.permission.REQUEST_NETWORK_SCORES"}) public String getActiveScorerPackage();
-    method @RequiresPermission("android.permission.REQUEST_NETWORK_SCORES") public void registerNetworkScoreCallback(int, int, @NonNull java.util.concurrent.Executor, @NonNull android.net.NetworkScoreManager.NetworkScoreCallback) throws java.lang.SecurityException;
-    method @RequiresPermission("android.permission.REQUEST_NETWORK_SCORES") public boolean requestScores(@NonNull android.net.NetworkKey[]) throws java.lang.SecurityException;
-    method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, "android.permission.REQUEST_NETWORK_SCORES"}) public boolean setActiveScorer(String) throws java.lang.SecurityException;
+    method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, android.Manifest.permission.REQUEST_NETWORK_SCORES}) public boolean clearScores() throws java.lang.SecurityException;
+    method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, android.Manifest.permission.REQUEST_NETWORK_SCORES}) public void disableScoring() throws java.lang.SecurityException;
+    method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, android.Manifest.permission.REQUEST_NETWORK_SCORES}) public String getActiveScorerPackage();
+    method @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES) public void registerNetworkScoreCallback(int, int, @NonNull java.util.concurrent.Executor, @NonNull android.net.NetworkScoreManager.NetworkScoreCallback) throws java.lang.SecurityException;
+    method @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES) public boolean requestScores(@NonNull java.util.Collection<android.net.NetworkKey>) throws java.lang.SecurityException;
+    method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, android.Manifest.permission.REQUEST_NETWORK_SCORES}) public boolean setActiveScorer(String) throws java.lang.SecurityException;
     method @RequiresPermission(android.Manifest.permission.SCORE_NETWORKS) public boolean updateScores(@NonNull android.net.ScoredNetwork[]) throws java.lang.SecurityException;
     field @Deprecated public static final String ACTION_CHANGE_ACTIVE = "android.net.scoring.CHANGE_ACTIVE";
     field public static final String ACTION_CUSTOM_ENABLE = "android.net.scoring.CUSTOM_ENABLE";
@@ -4881,11 +6394,15 @@
     field @Deprecated public static final String EXTRA_NETWORKS_TO_SCORE = "networksToScore";
     field public static final String EXTRA_NEW_SCORER = "newScorer";
     field @Deprecated public static final String EXTRA_PACKAGE_NAME = "packageName";
+    field public static final int SCORE_FILTER_CURRENT_NETWORK = 1; // 0x1
+    field public static final int SCORE_FILTER_NONE = 0; // 0x0
+    field public static final int SCORE_FILTER_SCAN_RESULTS = 2; // 0x2
   }
 
-  public static interface NetworkScoreManager.NetworkScoreCallback {
-    method public void clearScores();
-    method public void updateScores(@NonNull java.util.List<android.net.ScoredNetwork>);
+  public abstract static class NetworkScoreManager.NetworkScoreCallback {
+    ctor public NetworkScoreManager.NetworkScoreCallback();
+    method public abstract void onScoresInvalidated();
+    method public abstract void onScoresUpdated(@NonNull java.util.Collection<android.net.ScoredNetwork>);
   }
 
   public abstract class NetworkSpecifier {
@@ -4898,8 +6415,37 @@
     field public static final String PERMISSION_MAINLINE_NETWORK_STACK = "android.permission.MAINLINE_NETWORK_STACK";
   }
 
+  public final class NetworkStats implements android.os.Parcelable {
+    ctor public NetworkStats(long, int);
+    method @NonNull public android.net.NetworkStats add(@NonNull android.net.NetworkStats);
+    method @NonNull public android.net.NetworkStats addValues(@NonNull android.net.NetworkStats.Entry);
+    method public int describeContents();
+    method @NonNull public android.net.NetworkStats subtract(@NonNull android.net.NetworkStats);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkStats> CREATOR;
+    field public static final int DEFAULT_NETWORK_NO = 0; // 0x0
+    field public static final int DEFAULT_NETWORK_YES = 1; // 0x1
+    field @Nullable public static final String IFACE_ALL;
+    field public static final String IFACE_VT = "vt_data0";
+    field public static final int METERED_NO = 0; // 0x0
+    field public static final int METERED_YES = 1; // 0x1
+    field public static final int ROAMING_NO = 0; // 0x0
+    field public static final int ROAMING_YES = 1; // 0x1
+    field public static final int SET_DEFAULT = 0; // 0x0
+    field public static final int SET_FOREGROUND = 1; // 0x1
+    field public static final int TAG_NONE = 0; // 0x0
+    field public static final int UID_ALL = -1; // 0xffffffff
+    field public static final int UID_TETHERING = -5; // 0xfffffffb
+  }
+
+  public static class NetworkStats.Entry {
+    ctor public NetworkStats.Entry(@Nullable String, int, int, int, int, int, int, long, long, long, long, long);
+  }
+
   public final class RouteInfo implements android.os.Parcelable {
     ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int);
+    ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int, int);
+    method public int getMtu();
     method public int getType();
     field public static final int RTN_THROW = 9; // 0x9
     field public static final int RTN_UNICAST = 1; // 0x1
@@ -4937,6 +6483,10 @@
     field public final android.net.RssiCurve rssiCurve;
   }
 
+  public abstract class SocketKeepalive implements java.lang.AutoCloseable {
+    field public static final int SUCCESS = 0; // 0x0
+  }
+
   public final class StaticIpConfiguration implements android.os.Parcelable {
     ctor public StaticIpConfiguration();
     ctor public StaticIpConfiguration(@Nullable android.net.StaticIpConfiguration);
@@ -4961,13 +6511,63 @@
     method @NonNull public android.net.StaticIpConfiguration.Builder setIpAddress(@Nullable android.net.LinkAddress);
   }
 
-  public final class StringNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
-    ctor public StringNetworkSpecifier(@NonNull String);
-    method public int describeContents();
+  public final class TelephonyNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
     method public boolean satisfiedBy(android.net.NetworkSpecifier);
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.net.StringNetworkSpecifier> CREATOR;
-    field @NonNull public final String specifier;
+  }
+
+  public class TetheringManager {
+    method public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.TetheringEventCallback);
+    method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void requestLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.OnTetheringEntitlementResultListener);
+    method public void stopAllTethering();
+    method public void stopTethering(int);
+    method public void unregisterTetheringEventCallback(@NonNull android.net.TetheringManager.TetheringEventCallback);
+    field public static final String ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED";
+    field public static final String EXTRA_ACTIVE_LOCAL_ONLY = "android.net.extra.ACTIVE_LOCAL_ONLY";
+    field public static final String EXTRA_ACTIVE_TETHER = "tetherArray";
+    field public static final String EXTRA_AVAILABLE_TETHER = "availableArray";
+    field public static final String EXTRA_ERRORED_TETHER = "erroredArray";
+    field public static final int TETHERING_BLUETOOTH = 2; // 0x2
+    field public static final int TETHERING_INVALID = -1; // 0xffffffff
+    field public static final int TETHERING_USB = 1; // 0x1
+    field public static final int TETHERING_WIFI = 0; // 0x0
+    field public static final int TETHERING_WIFI_P2P = 3; // 0x3
+    field public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12; // 0xc
+    field public static final int TETHER_ERROR_DISABLE_NAT_ERROR = 9; // 0x9
+    field public static final int TETHER_ERROR_ENABLE_NAT_ERROR = 8; // 0x8
+    field public static final int TETHER_ERROR_ENTITLEMENT_UNKNOWN = 13; // 0xd
+    field public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10; // 0xa
+    field public static final int TETHER_ERROR_MASTER_ERROR = 5; // 0x5
+    field public static final int TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION = 15; // 0xf
+    field public static final int TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION = 14; // 0xe
+    field public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0
+    field public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb
+    field public static final int TETHER_ERROR_SERVICE_UNAVAIL = 2; // 0x2
+    field public static final int TETHER_ERROR_TETHER_IFACE_ERROR = 6; // 0x6
+    field public static final int TETHER_ERROR_UNAVAIL_IFACE = 4; // 0x4
+    field public static final int TETHER_ERROR_UNKNOWN_IFACE = 1; // 0x1
+    field public static final int TETHER_ERROR_UNSUPPORTED = 3; // 0x3
+    field public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7; // 0x7
+  }
+
+  public static interface TetheringManager.OnTetheringEntitlementResultListener {
+    method public void onTetheringEntitlementResult(int);
+  }
+
+  public abstract static class TetheringManager.TetheringEventCallback {
+    ctor public TetheringManager.TetheringEventCallback();
+    method public void onError(@NonNull String, int);
+    method @Deprecated public void onTetherableInterfaceRegexpsChanged(@NonNull android.net.TetheringManager.TetheringInterfaceRegexps);
+    method public void onTetherableInterfacesChanged(@NonNull java.util.List<java.lang.String>);
+    method public void onTetheredInterfacesChanged(@NonNull java.util.List<java.lang.String>);
+    method public void onTetheringSupported(boolean);
+    method public void onUpstreamChanged(@Nullable android.net.Network);
+  }
+
+  @Deprecated public static class TetheringManager.TetheringInterfaceRegexps {
+    ctor @Deprecated public TetheringManager.TetheringInterfaceRegexps(@NonNull String[], @NonNull String[], @NonNull String[]);
+    method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableBluetoothRegexs();
+    method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableUsbRegexs();
+    method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableWifiRegexs();
   }
 
   public class TrafficStats {
@@ -5095,9 +6695,11 @@
   }
 
   public abstract class ChildSessionParams {
+    method public long getHardLifetime();
     method @NonNull public java.util.List<android.net.ipsec.ike.IkeTrafficSelector> getLocalTrafficSelectors();
     method @NonNull public java.util.List<android.net.ipsec.ike.IkeTrafficSelector> getRemoteTrafficSelectors();
     method @NonNull public java.util.List<android.net.ipsec.ike.ChildSaProposal> getSaProposals();
+    method public long getSoftLifetime();
   }
 
   public class IkeFqdnIdentification extends android.net.ipsec.ike.IkeIdentification {
@@ -5166,12 +6768,14 @@
   }
 
   public final class IkeSessionParams {
+    method public long getHardLifetime();
     method @NonNull public android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig getLocalAuthConfig();
     method @NonNull public android.net.ipsec.ike.IkeIdentification getLocalIdentification();
     method @NonNull public android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig getRemoteAuthConfig();
     method @NonNull public android.net.ipsec.ike.IkeIdentification getRemoteIdentification();
     method @NonNull public java.util.List<android.net.ipsec.ike.IkeSaProposal> getSaProposals();
     method @NonNull public java.net.InetAddress getServerAddress();
+    method public long getSoftLifetime();
     method @NonNull public android.net.IpSecManager.UdpEncapsulationSocket getUdpEncapsulationSocket();
   }
 
@@ -5183,6 +6787,7 @@
     method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthDigitalSignature(@Nullable java.security.cert.X509Certificate, @NonNull java.security.cert.X509Certificate, @NonNull java.util.List<java.security.cert.X509Certificate>, @NonNull java.security.PrivateKey);
     method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthEap(@Nullable java.security.cert.X509Certificate, @NonNull android.net.eap.EapSessionConfig);
     method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthPsk(@NonNull byte[]);
+    method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setLifetime(long, long);
     method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setLocalIdentification(@NonNull android.net.ipsec.ike.IkeIdentification);
     method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setRemoteIdentification(@NonNull android.net.ipsec.ike.IkeIdentification);
     method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setServerAddress(@NonNull java.net.InetAddress);
@@ -5253,6 +6858,7 @@
     method @NonNull public android.net.ipsec.ike.TransportModeChildSessionParams.Builder addOutboundTrafficSelectors(@NonNull android.net.ipsec.ike.IkeTrafficSelector);
     method @NonNull public android.net.ipsec.ike.TransportModeChildSessionParams.Builder addSaProposal(@NonNull android.net.ipsec.ike.ChildSaProposal);
     method @NonNull public android.net.ipsec.ike.TransportModeChildSessionParams build();
+    method @NonNull public android.net.ipsec.ike.TransportModeChildSessionParams.Builder setLifetime(long, long);
   }
 
   public final class TunnelModeChildSessionParams extends android.net.ipsec.ike.ChildSessionParams {
@@ -5270,6 +6876,7 @@
     method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addOutboundTrafficSelectors(@NonNull android.net.ipsec.ike.IkeTrafficSelector);
     method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addSaProposal(@NonNull android.net.ipsec.ike.ChildSaProposal);
     method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams build();
+    method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder setLifetime(long, long);
   }
 
   public static interface TunnelModeChildSessionParams.ConfigRequest {
@@ -5489,6 +7096,25 @@
 
 }
 
+package android.net.netstats.provider {
+
+  public abstract class AbstractNetworkStatsProvider {
+    ctor public AbstractNetworkStatsProvider();
+    method public abstract void requestStatsUpdate(int);
+    method public abstract void setAlert(long);
+    method public abstract void setLimit(@NonNull String, long);
+    field public static final int QUOTA_UNLIMITED = -1; // 0xffffffff
+  }
+
+  public class NetworkStatsProviderCallback {
+    method public void onAlertReached();
+    method public void onLimitReached();
+    method public void onStatsUpdated(int, @NonNull android.net.NetworkStats, @NonNull android.net.NetworkStats);
+    method public void unregister();
+  }
+
+}
+
 package android.net.util {
 
   public final class SocketUtils {
@@ -5712,6 +7338,7 @@
   }
 
   public class ScanResult implements android.os.Parcelable {
+    ctor public ScanResult();
     field public static final int CIPHER_CCMP = 3; // 0x3
     field public static final int CIPHER_GCMP_256 = 4; // 0x4
     field public static final int CIPHER_NONE = 0; // 0x0
@@ -5752,40 +7379,34 @@
   }
 
   public final class SoftApConfiguration implements android.os.Parcelable {
-    method public int describeContents();
+    method @NonNull public java.util.List<android.net.MacAddress> getAllowedClientList();
     method public int getBand();
-    method @Nullable public android.net.MacAddress getBssid();
+    method @NonNull public java.util.List<android.net.MacAddress> getBlockedClientList();
     method public int getChannel();
     method public int getMaxNumberOfClients();
-    method @Nullable public String getPassphrase();
-    method public int getSecurityType();
-    method @Nullable public String getSsid();
-    method @Nullable public String getWpa2Passphrase();
-    method public boolean isHiddenSsid();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    method public int getShutdownTimeoutMillis();
+    method public boolean isClientControlByUserEnabled();
+    method @Nullable public android.net.wifi.WifiConfiguration toWifiConfiguration();
     field public static final int BAND_2GHZ = 1; // 0x1
     field public static final int BAND_5GHZ = 2; // 0x2
     field public static final int BAND_6GHZ = 4; // 0x4
     field public static final int BAND_ANY = 7; // 0x7
-    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.SoftApConfiguration> CREATOR;
-    field public static final int SECURITY_TYPE_OPEN = 0; // 0x0
-    field public static final int SECURITY_TYPE_WPA2_PSK = 1; // 0x1
-    field public static final int SECURITY_TYPE_WPA3_SAE = 3; // 0x3
-    field public static final int SECURITY_TYPE_WPA3_SAE_TRANSITION = 2; // 0x2
   }
 
   public static final class SoftApConfiguration.Builder {
     ctor public SoftApConfiguration.Builder();
     ctor public SoftApConfiguration.Builder(@NonNull android.net.wifi.SoftApConfiguration);
     method @NonNull public android.net.wifi.SoftApConfiguration build();
+    method @NonNull public android.net.wifi.SoftApConfiguration.Builder enableClientControlByUser(boolean);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setBand(int);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setBssid(@Nullable android.net.MacAddress);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setChannel(int, int);
+    method @NonNull public android.net.wifi.SoftApConfiguration.Builder setClientList(@NonNull java.util.List<android.net.MacAddress>, @NonNull java.util.List<android.net.MacAddress>);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setHiddenSsid(boolean);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setMaxNumberOfClients(int);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setPassphrase(@Nullable String, int);
+    method @NonNull public android.net.wifi.SoftApConfiguration.Builder setShutdownTimeoutMillis(int);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setSsid(@Nullable String);
-    method @NonNull public android.net.wifi.SoftApConfiguration.Builder setWpa2Passphrase(@Nullable String);
   }
 
   public final class SoftApInfo implements android.os.Parcelable {
@@ -5823,6 +7444,7 @@
     method @Deprecated public static boolean isMetered(@Nullable android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiInfo);
     method @Deprecated public boolean isNoInternetAccessExpected();
     method @Deprecated public void setIpConfiguration(@Nullable android.net.IpConfiguration);
+    method @Deprecated public void setNetworkSelectionStatus(@NonNull android.net.wifi.WifiConfiguration.NetworkSelectionStatus);
     method @Deprecated public void setProxy(@NonNull android.net.IpConfiguration.ProxySettings, @NonNull android.net.ProxyInfo);
     field @Deprecated public static final int AP_BAND_2GHZ = 0; // 0x0
     field @Deprecated public static final int AP_BAND_5GHZ = 1; // 0x1
@@ -5867,6 +7489,7 @@
     method @Deprecated public boolean getHasEverConnected();
     method @Deprecated @Nullable public static String getNetworkDisableReasonString(int);
     method @Deprecated public int getNetworkSelectionDisableReason();
+    method @Deprecated public int getNetworkSelectionStatus();
     method @Deprecated @NonNull public String getNetworkStatusString();
     method @Deprecated public boolean isNetworkEnabled();
     method @Deprecated public boolean isNetworkPermanentlyDisabled();
@@ -5881,6 +7504,16 @@
     field @Deprecated public static final int DISABLED_NO_INTERNET_TEMPORARY = 4; // 0x4
     field @Deprecated public static final int NETWORK_SELECTION_DISABLED_MAX = 10; // 0xa
     field @Deprecated public static final int NETWORK_SELECTION_ENABLE = 0; // 0x0
+    field @Deprecated public static final int NETWORK_SELECTION_ENABLED = 0; // 0x0
+    field @Deprecated public static final int NETWORK_SELECTION_PERMANENTLY_DISABLED = 2; // 0x2
+    field @Deprecated public static final int NETWORK_SELECTION_TEMPORARY_DISABLED = 1; // 0x1
+  }
+
+  @Deprecated public static final class WifiConfiguration.NetworkSelectionStatus.Builder {
+    ctor @Deprecated public WifiConfiguration.NetworkSelectionStatus.Builder();
+    method @Deprecated @NonNull public android.net.wifi.WifiConfiguration.NetworkSelectionStatus build();
+    method @Deprecated @NonNull public android.net.wifi.WifiConfiguration.NetworkSelectionStatus.Builder setNetworkSelectionDisableReason(int);
+    method @Deprecated @NonNull public android.net.wifi.WifiConfiguration.NetworkSelectionStatus.Builder setNetworkSelectionStatus(int);
   }
 
   @Deprecated public static class WifiConfiguration.RecentFailure {
@@ -5894,12 +7527,12 @@
     method @NonNull public String getCaPath();
     method @NonNull public String getClientCertificateAlias();
     method public int getOcsp();
-    method @Nullable public String getWapiCertSuite();
+    method @NonNull public String getWapiCertSuite();
     method public void setCaCertificateAliases(@Nullable String[]);
-    method public void setCaPath(@Nullable String);
-    method public void setClientCertificateAlias(@Nullable String);
+    method public void setCaPath(@NonNull String);
+    method public void setClientCertificateAlias(@NonNull String);
     method public void setOcsp(int);
-    method public void setWapiCertSuite(@Nullable String);
+    method public void setWapiCertSuite(@NonNull String);
     field public static final int OCSP_NONE = 0; // 0x0
     field public static final int OCSP_REQUEST_CERT_STATUS = 1; // 0x1
     field public static final int OCSP_REQUIRE_ALL_NON_TRUSTED_CERTS_STATUS = 3; // 0x3
@@ -5925,9 +7558,21 @@
     field public static final int INVALID_RSSI = -127; // 0xffffff81
   }
 
+  public static final class WifiInfo.Builder {
+    ctor public WifiInfo.Builder();
+    method @NonNull public android.net.wifi.WifiInfo build();
+    method @NonNull public android.net.wifi.WifiInfo.Builder setBssid(@NonNull String);
+    method @NonNull public android.net.wifi.WifiInfo.Builder setNetworkId(int);
+    method @NonNull public android.net.wifi.WifiInfo.Builder setRssi(int);
+    method @NonNull public android.net.wifi.WifiInfo.Builder setSsid(@NonNull byte[]);
+  }
+
   public class WifiManager {
     method @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE) public void addOnWifiUsabilityStatsListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.OnWifiUsabilityStatsListener);
     method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void allowAutojoin(int, boolean);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void allowAutojoinGlobal(boolean);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void allowAutojoinPasspoint(@NonNull String, boolean);
+    method @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE) public void clearWifiConnectedNetworkScorer();
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void connect(@NonNull android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiManager.ActionListener);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void connect(int, @Nullable android.net.wifi.WifiManager.ActionListener);
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void disable(int, @Nullable android.net.wifi.WifiManager.ActionListener);
@@ -5941,12 +7586,14 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public String[] getFactoryMacAddresses();
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.Map<android.net.wifi.hotspot2.OsuProvider,java.util.List<android.net.wifi.ScanResult>> getMatchingOsuProviders(@Nullable java.util.List<android.net.wifi.ScanResult>);
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.Map<android.net.wifi.hotspot2.OsuProvider,android.net.wifi.hotspot2.PasspointConfiguration> getMatchingPasspointConfigsForOsuProviders(@NonNull java.util.Set<android.net.wifi.hotspot2.OsuProvider>);
+    method @NonNull @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE}) public java.util.Map<android.net.wifi.WifiNetworkSuggestion,java.util.List<android.net.wifi.ScanResult>> getMatchingScanResults(@NonNull java.util.List<android.net.wifi.WifiNetworkSuggestion>, @Nullable java.util.List<android.net.wifi.ScanResult>);
     method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.READ_WIFI_CREDENTIAL}) public java.util.List<android.net.wifi.WifiConfiguration> getPrivilegedConfiguredNetworks();
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public android.net.wifi.SoftApConfiguration getSoftApConfiguration();
     method public int getVerboseLoggingLevel();
     method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void getWifiActivityEnergyInfoAsync(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.OnWifiActivityEnergyInfoListener);
-    method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public android.net.wifi.WifiConfiguration getWifiApConfiguration();
+    method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public android.net.wifi.WifiConfiguration getWifiApConfiguration();
     method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public int getWifiApState();
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.net.wifi.WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(@NonNull java.util.List<android.net.wifi.ScanResult>);
     method public boolean isApMacRandomizationSupported();
     method public boolean isConnectedMacRandomizationSupported();
     method @Deprecated public boolean isDeviceToDeviceRttSupported();
@@ -5968,8 +7615,10 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public byte[] retrieveSoftApBackupData();
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void save(@NonNull android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiManager.ActionListener);
     method @RequiresPermission(android.Manifest.permission.WIFI_SET_DEVICE_MOBILITY_STATE) public void setDeviceMobilityState(int);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setMacRandomizationSettingPasspointEnabled(@NonNull String, boolean);
     method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public boolean setSoftApConfiguration(@NonNull android.net.wifi.SoftApConfiguration);
     method @Deprecated @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public boolean setWifiApConfiguration(android.net.wifi.WifiConfiguration);
+    method @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE) public boolean setWifiConnectedNetworkScorer(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.WifiConnectedNetworkScorer);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startEasyConnectAsConfiguratorInitiator(@NonNull String, int, int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.EasyConnectStatusCallback);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startEasyConnectAsEnrolleeInitiator(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.EasyConnectStatusCallback);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startLocalOnlyHotspot(@NonNull android.net.wifi.SoftApConfiguration, @Nullable java.util.concurrent.Executor, @Nullable android.net.wifi.WifiManager.LocalOnlyHotspotCallback);
@@ -6017,6 +7666,8 @@
     field public static final int IFACE_IP_MODE_UNSPECIFIED = -1; // 0xffffffff
     field public static final int PASSPOINT_HOME_NETWORK = 0; // 0x0
     field public static final int PASSPOINT_ROAMING_NETWORK = 1; // 0x1
+    field public static final int SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER = 0; // 0x0
+    field public static final int SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS = 1; // 0x1
     field public static final int SAP_START_FAILURE_GENERAL = 0; // 0x0
     field public static final int SAP_START_FAILURE_NO_CHANNEL = 1; // 0x1
     field public static final int SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION = 2; // 0x2
@@ -6057,7 +7708,13 @@
     method public void onWifiUsabilityStats(int, boolean, @NonNull android.net.wifi.WifiUsabilityStatsEntry);
   }
 
+  public static interface WifiManager.ScoreChangeCallback {
+    method public void onStatusChange(int, boolean);
+    method public void onTriggerUpdateOfWifiUsabilityStats(int);
+  }
+
   public static interface WifiManager.SoftApCallback {
+    method public default void onBlockedClientConnecting(@NonNull android.net.wifi.WifiClient, int);
     method public default void onCapabilityChanged(@NonNull android.net.wifi.SoftApCapability);
     method public default void onConnectedClientsChanged(@NonNull java.util.List<android.net.wifi.WifiClient>);
     method public default void onInfoChanged(@NonNull android.net.wifi.SoftApInfo);
@@ -6072,6 +7729,12 @@
     field public static final int DATA_ACTIVITY_OUT = 2; // 0x2
   }
 
+  public static interface WifiManager.WifiConnectedNetworkScorer {
+    method public void setScoreChangeCallback(@NonNull android.net.wifi.WifiManager.ScoreChangeCallback);
+    method public void start(int);
+    method public void stop(int);
+  }
+
   public class WifiNetworkConnectionStatistics implements android.os.Parcelable {
     ctor public WifiNetworkConnectionStatistics(int, int);
     ctor public WifiNetworkConnectionStatistics();
@@ -6087,25 +7750,49 @@
     method public boolean satisfiedBy(android.net.NetworkSpecifier);
   }
 
+  public final class WifiNetworkSuggestion implements android.os.Parcelable {
+    method @Nullable public android.net.wifi.hotspot2.PasspointConfiguration getPasspointConfiguration();
+    method @NonNull public android.net.wifi.WifiConfiguration getWifiConfiguration();
+  }
+
   public static final class WifiNetworkSuggestion.Builder {
     method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING) public android.net.wifi.WifiNetworkSuggestion.Builder setCarrierId(int);
   }
 
+  public final class WifiOemConfigStoreMigrationHook {
+    method @Nullable public static android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData load();
+  }
+
+  public static final class WifiOemConfigStoreMigrationHook.MigrationData implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public java.util.List<android.net.wifi.WifiConfiguration> getUserSavedNetworkConfigurations();
+    method @Nullable public android.net.wifi.SoftApConfiguration getUserSoftApConfiguration();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData> CREATOR;
+  }
+
+  public static final class WifiOemConfigStoreMigrationHook.MigrationData.Builder {
+    ctor public WifiOemConfigStoreMigrationHook.MigrationData.Builder();
+    method @NonNull public android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData build();
+    method @NonNull public android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData.Builder setUserSavedNetworkConfigurations(@NonNull java.util.List<android.net.wifi.WifiConfiguration>);
+    method @NonNull public android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData.Builder setUserSoftApConfiguration(@NonNull android.net.wifi.SoftApConfiguration);
+  }
+
   public class WifiScanner {
     method @Deprecated public void configureWifiChange(int, int, int, int, int, android.net.wifi.WifiScanner.BssidInfo[]);
     method @Deprecated public void configureWifiChange(android.net.wifi.WifiScanner.WifiChangeSettings);
     method @NonNull @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public java.util.List<java.lang.Integer> getAvailableChannels(int);
-    method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public boolean getScanResults();
+    method @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public boolean getScanResults();
     method @NonNull @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public java.util.List<android.net.wifi.ScanResult> getSingleScanResults();
     method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void registerScanListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiScanner.ScanListener);
     method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setScanningEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void startBackgroundScan(android.net.wifi.WifiScanner.ScanSettings, android.net.wifi.WifiScanner.ScanListener);
-    method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void startBackgroundScan(android.net.wifi.WifiScanner.ScanSettings, android.net.wifi.WifiScanner.ScanListener, android.os.WorkSource);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void startBackgroundScan(android.net.wifi.WifiScanner.ScanSettings, android.net.wifi.WifiScanner.ScanListener, android.os.WorkSource);
     method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void startScan(android.net.wifi.WifiScanner.ScanSettings, android.net.wifi.WifiScanner.ScanListener);
     method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void startScan(android.net.wifi.WifiScanner.ScanSettings, android.net.wifi.WifiScanner.ScanListener, android.os.WorkSource);
     method @Deprecated public void startTrackingBssids(android.net.wifi.WifiScanner.BssidInfo[], int, android.net.wifi.WifiScanner.BssidListener);
     method @Deprecated public void startTrackingWifiChange(android.net.wifi.WifiScanner.WifiChangeListener);
-    method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void stopBackgroundScan(android.net.wifi.WifiScanner.ScanListener);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void stopBackgroundScan(android.net.wifi.WifiScanner.ScanListener);
     method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void stopScan(android.net.wifi.WifiScanner.ScanListener);
     method @Deprecated public void stopTrackingBssids(android.net.wifi.WifiScanner.BssidListener);
     method @Deprecated public void stopTrackingWifiChange(android.net.wifi.WifiScanner.WifiChangeListener);
@@ -6188,7 +7875,7 @@
 
   public static interface WifiScanner.ScanListener extends android.net.wifi.WifiScanner.ActionListener {
     method public void onFullResult(android.net.wifi.ScanResult);
-    method public void onPeriodChanged(int);
+    method @Deprecated public void onPeriodChanged(int);
     method public void onResults(android.net.wifi.WifiScanner.ScanData[]);
   }
 
@@ -6199,12 +7886,12 @@
     field @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public final java.util.List<android.net.wifi.WifiScanner.ScanSettings.HiddenNetwork> hiddenNetworks;
     field public boolean hideFromAppOps;
     field public boolean ignoreLocationSettings;
-    field public int maxPeriodInMs;
-    field public int maxScansToCache;
-    field public int numBssidsPerScan;
-    field public int periodInMs;
-    field public int reportEvents;
-    field public int stepCount;
+    field @Deprecated public int maxPeriodInMs;
+    field @Deprecated public int maxScansToCache;
+    field @Deprecated public int numBssidsPerScan;
+    field @Deprecated public int periodInMs;
+    field @Deprecated public int reportEvents;
+    field @Deprecated public int stepCount;
     field @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public int type;
   }
 
@@ -6293,6 +7980,11 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.OsuProvider> CREATOR;
   }
 
+  public final class PasspointConfiguration implements android.os.Parcelable {
+    method public boolean isAutoJoinEnabled();
+    method public boolean isMacRandomizationEnabled();
+  }
+
   public abstract class ProvisioningCallback {
     ctor public ProvisioningCallback();
     method public abstract void onProvisioningComplete();
@@ -6424,6 +8116,21 @@
 
 package android.net.wifi.wificond {
 
+  public final class DeviceWiphyCapabilities implements android.os.Parcelable {
+    ctor public DeviceWiphyCapabilities();
+    method public int describeContents();
+    method public int getMaxNumberRxSpatialStreams();
+    method public int getMaxNumberTxSpatialStreams();
+    method public boolean isChannelWidthSupported(int);
+    method public boolean isWifiStandardSupported(int);
+    method public void setChannelWidthSupported(int, boolean);
+    method public void setMaxNumberRxSpatialStreams(int);
+    method public void setMaxNumberTxSpatialStreams(int);
+    method public void setWifiStandardSupport(int, boolean);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.wificond.DeviceWiphyCapabilities> CREATOR;
+  }
+
   public final class NativeScanResult implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public byte[] getBssid();
@@ -6488,6 +8195,7 @@
     method public void abortScan(@NonNull String);
     method public void enableVerboseLogging(boolean);
     method @NonNull public int[] getChannelsMhzForBand(int);
+    method @Nullable public android.net.wifi.wificond.DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String);
     method @NonNull public java.util.List<android.net.wifi.wificond.NativeScanResult> getScanResults(@NonNull String, int);
     method @Nullable public android.net.wifi.wificond.WifiCondManager.TxPacketCounters getTxPacketCounters(@NonNull String);
     method public boolean initialize(@NonNull Runnable);
@@ -6747,7 +8455,7 @@
     method @Nullable public android.os.NativeHandle getHandle();
     method @NonNull public String getName();
     method public long getSize();
-    method @NonNull public android.os.NativeHandle releaseHandle();
+    method @Nullable public android.os.NativeHandle releaseHandle();
   }
 
   public class HidlSupport {
@@ -6898,8 +8606,11 @@
     method @Nullable @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public android.os.IncidentManager.IncidentReport getIncidentReport(android.net.Uri);
     method @NonNull @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public java.util.List<android.net.Uri> getIncidentReportList(String);
     method @RequiresPermission(android.Manifest.permission.APPROVE_INCIDENT_REPORTS) public java.util.List<android.os.IncidentManager.PendingReport> getPendingReports();
+    method public void registerSection(int, @NonNull String, @NonNull android.os.IncidentManager.DumpCallback);
+    method public void registerSection(int, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.IncidentManager.DumpCallback);
     method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void reportIncident(android.os.IncidentReportArgs);
     method @RequiresPermission("android.permission.REQUEST_INCIDENT_REPORT_APPROVAL") public void requestAuthorization(int, String, int, android.os.IncidentManager.AuthListener);
+    method public void unregisterSection(int);
     field public static final int FLAG_CONFIRMATION_DIALOG = 1; // 0x1
     field public static final int PRIVACY_POLICY_AUTO = 200; // 0xc8
     field public static final int PRIVACY_POLICY_EXPLICIT = 100; // 0x64
@@ -6912,6 +8623,11 @@
     method public void onReportDenied();
   }
 
+  public static class IncidentManager.DumpCallback {
+    ctor public IncidentManager.DumpCallback();
+    method public void onDumpSection(int, @NonNull java.io.OutputStream);
+  }
+
   public static class IncidentManager.IncidentReport implements java.io.Closeable android.os.Parcelable {
     ctor public IncidentManager.IncidentReport(android.os.Parcel);
     method public void close();
@@ -6959,14 +8675,22 @@
     method public boolean hasSingleFileDescriptor();
   }
 
+  public class ParcelFileDescriptor implements java.io.Closeable android.os.Parcelable {
+    method @NonNull public static android.os.ParcelFileDescriptor wrap(@NonNull android.os.ParcelFileDescriptor, @NonNull android.os.Handler, @NonNull android.os.ParcelFileDescriptor.OnCloseListener) throws java.io.IOException;
+  }
+
   public final class PowerManager {
     method @RequiresPermission(allOf={android.Manifest.permission.READ_DREAM_STATE, android.Manifest.permission.WRITE_DREAM_STATE}) public void dream(long);
     method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public boolean forceSuspend();
     method @RequiresPermission(android.Manifest.permission.POWER_SAVER) public int getPowerSaveModeTrigger();
+    method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isAmbientDisplayAvailable();
+    method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isAmbientDisplaySuppressed();
+    method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isAmbientDisplaySuppressedForToken(@NonNull String);
     method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setAdaptivePowerSaveEnabled(boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setAdaptivePowerSavePolicy(@NonNull android.os.BatterySaverPolicyConfig);
     method @RequiresPermission(android.Manifest.permission.POWER_SAVER) public boolean setDynamicPowerSaveHint(boolean, int);
     method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setPowerSaveModeEnabled(boolean);
+    method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void suppressAmbientDisplay(@NonNull String, boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.USER_ACTIVITY}) public void userActivity(long, int, int);
     field public static final int POWER_SAVE_MODE_TRIGGER_DYNAMIC = 1; // 0x1
     field public static final int POWER_SAVE_MODE_TRIGGER_PERCENTAGE = 0; // 0x0
@@ -7042,6 +8766,11 @@
     field public static final int TUPLE_VALUE_TYPE = 7; // 0x7
   }
 
+  public class SystemConfigManager {
+    method @NonNull @RequiresPermission(android.Manifest.permission.READ_CARRIER_APP_INFO) public java.util.Set<java.lang.String> getDisabledUntilUsedPreinstalledCarrierApps();
+    method @NonNull @RequiresPermission(android.Manifest.permission.READ_CARRIER_APP_INFO) public java.util.Map<java.lang.String,java.util.List<java.lang.String>> getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
+  }
+
   public class SystemProperties {
     method @NonNull public static String get(@NonNull String);
     method @NonNull public static String get(@NonNull String, @Nullable String);
@@ -7067,6 +8796,20 @@
   }
 
   public class TelephonyServiceManager {
+    method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getCarrierConfigServiceRegisterer();
+    method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getEuiccCardControllerServiceRegisterer();
+    method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getEuiccControllerService();
+    method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getIccPhoneBookServiceRegisterer();
+    method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getNetworkPolicyServiceRegisterer();
+    method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getOpportunisticNetworkServiceRegisterer();
+    method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getPackageManagerServiceRegisterer();
+    method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getPermissionManagerServiceRegisterer();
+    method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getPhoneSubServiceRegisterer();
+    method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getSmsServiceRegisterer();
+    method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getSubscriptionServiceRegisterer();
+    method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getTelephonyImsServiceRegisterer();
+    method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getTelephonyRcsMessageServiceRegisterer();
+    method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getTelephonyRegistryServiceRegisterer();
     method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getTelephonyServiceRegisterer();
   }
 
@@ -7085,7 +8828,7 @@
     ctor public UpdateEngine();
     method @NonNull public android.os.UpdateEngine.AllocateSpaceResult allocateSpace(@NonNull String, @NonNull String[]);
     method public void applyPayload(String, long, long, String[]);
-    method public void applyPayload(@NonNull android.os.ParcelFileDescriptor, long, long, @NonNull String[]);
+    method public void applyPayload(@NonNull android.content.res.AssetFileDescriptor, @NonNull String[]);
     method public boolean bind(android.os.UpdateEngineCallback, android.os.Handler);
     method public boolean bind(android.os.UpdateEngineCallback);
     method public void cancel();
@@ -7156,6 +8899,7 @@
 
   public class UserManager {
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void clearSeedAccountData();
+    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.UserHandle createProfile(@NonNull String, @NonNull String, @Nullable String[]) throws android.os.UserManager.UserOperationException;
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.UserHandle getProfileParent(@NonNull android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getSeedAccountName();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.PersistableBundle getSeedAccountOptions();
@@ -7163,23 +8907,26 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public long[] getSerialNumbersOfUsers(boolean);
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public java.util.List<android.os.UserHandle> getUserHandles(boolean);
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public android.graphics.Bitmap getUserIcon();
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional=true) public java.util.List<android.os.UserHandle> getUserProfiles(boolean);
     method @Deprecated @android.os.UserManager.UserRestrictionSource @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public int getUserRestrictionSource(String, android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public java.util.List<android.os.UserManager.EnforcingUser> getUserRestrictionSources(String, android.os.UserHandle);
     method @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public int getUserSwitchability();
-    method public boolean hasRestrictedProfiles();
+    method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean hasRestrictedProfiles();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean hasUserRestrictionForUser(@NonNull String, @NonNull android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isAdminUser();
-    method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isGuestUser();
-    method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedProfile();
-    method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedProfile(int);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isPrimaryUser();
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isGuestUser();
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isManagedProfile(int);
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isPrimaryUser();
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isProfile();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isRestrictedProfile();
-    method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isRestrictedProfile(@NonNull android.os.UserHandle);
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isRestrictedProfile(@NonNull android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isSameProfileGroup(@NonNull android.os.UserHandle, @NonNull android.os.UserHandle);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public boolean isUserNameSet();
-    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public boolean isUserUnlockingOrUnlocked(@NonNull android.os.UserHandle);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean removeUser(@NonNull android.os.UserHandle);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserIcon(@NonNull android.graphics.Bitmap);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isUserOfType(@NonNull String);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isUserOfType(@NonNull android.os.UserHandle, @NonNull String);
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isUserUnlockingOrUnlocked(@NonNull android.os.UserHandle);
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean removeUser(@NonNull android.os.UserHandle);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserIcon(@NonNull android.graphics.Bitmap) throws android.os.UserManager.UserOperationException;
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserName(@Nullable String);
     field public static final String ACTION_USER_RESTRICTIONS_CHANGED = "android.os.action.USER_RESTRICTIONS_CHANGED";
     field @Deprecated public static final String DISALLOW_OEM_UNLOCK = "no_oem_unlock";
@@ -7192,6 +8939,10 @@
     field public static final int SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED = 4; // 0x4
     field public static final int SWITCHABILITY_STATUS_USER_IN_CALL = 1; // 0x1
     field public static final int SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED = 2; // 0x2
+    field public static final String USER_TYPE_FULL_SECONDARY = "android.os.usertype.full.SECONDARY";
+    field public static final String USER_TYPE_FULL_SYSTEM = "android.os.usertype.full.SYSTEM";
+    field public static final String USER_TYPE_PROFILE_MANAGED = "android.os.usertype.profile.MANAGED";
+    field public static final String USER_TYPE_SYSTEM_HEADLESS = "android.os.usertype.system.HEADLESS";
   }
 
   public static final class UserManager.EnforcingUser implements android.os.Parcelable {
@@ -7347,6 +9098,10 @@
     field @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE) public static final int FLAG_ALLOCATE_AGGRESSIVE = 1; // 0x1
   }
 
+  public final class StorageVolume implements android.os.Parcelable {
+    method @NonNull public String getId();
+  }
+
 }
 
 package android.permission {
@@ -7390,7 +9145,10 @@
   public final class PermissionManager {
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public int getRuntimePermissionsVersion();
     method @NonNull public java.util.List<android.permission.PermissionManager.SplitPermissionInfo> getSplitPermissions();
+    method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS) public void grantDefaultPermissionsToEnabledImsServices(@NonNull String[], @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS) public void grantDefaultPermissionsToEnabledTelephonyDataServices(@NonNull String[], @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS) public void grantDefaultPermissionsToLuiApp(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS) public void revokeDefaultPermissionsFromDisabledTelephonyDataServices(@NonNull String[], @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS) public void revokeDefaultPermissionsFromLuiApps(@NonNull String[], @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public void setRuntimePermissionsVersion(@IntRange(from=0) int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS) public void startOneTimePermissionSession(@NonNull String, long, int, int);
@@ -7590,6 +9348,7 @@
     field public static final String NAMESPACE_SYSTEMUI = "systemui";
     field public static final String NAMESPACE_TELEPHONY = "telephony";
     field public static final String NAMESPACE_TEXTCLASSIFIER = "textclassifier";
+    field public static final String NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT = "window_manager_native_boot";
   }
 
   public static class DeviceConfig.BadConfigException extends java.lang.Exception {
@@ -7691,6 +9450,8 @@
     field public static final int COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE = 5; // 0x5
     field public static final int COLUMN_INDEX_XML_RES_RANK = 0; // 0x0
     field public static final int COLUMN_INDEX_XML_RES_RESID = 1; // 0x1
+    field public static final String DYNAMIC_INDEXABLES_RAW = "dynamic_indexables_raw";
+    field public static final String DYNAMIC_INDEXABLES_RAW_PATH = "settings/dynamic_indexables_raw";
     field public static final String INDEXABLES_RAW = "indexables_raw";
     field public static final String[] INDEXABLES_RAW_COLUMNS;
     field public static final String INDEXABLES_RAW_PATH = "settings/indexables_raw";
@@ -7748,6 +9509,7 @@
     method public String getType(android.net.Uri);
     method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
     method public android.database.Cursor query(android.net.Uri, String[], String, String[], String);
+    method @Nullable public android.database.Cursor queryDynamicRawData(@Nullable String[]);
     method public abstract android.database.Cursor queryNonIndexableKeys(String[]);
     method public abstract android.database.Cursor queryRawData(String[]);
     method @Nullable public android.database.Cursor querySliceUriPairs();
@@ -7756,6 +9518,7 @@
   }
 
   public final class Settings {
+    method public static boolean checkAndNoteWriteSettingsOperation(@NonNull android.content.Context, int, @NonNull String, boolean);
     field public static final String ACTION_ACCESSIBILITY_DETAILS_SETTINGS = "android.settings.ACCESSIBILITY_DETAILS_SETTINGS";
     field public static final String ACTION_BUGREPORT_HANDLER_SETTINGS = "android.settings.BUGREPORT_HANDLER_SETTINGS";
     field public static final String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS";
@@ -7766,11 +9529,13 @@
     field public static final String ACTION_NOTIFICATION_POLICY_ACCESS_DETAIL_SETTINGS = "android.settings.NOTIFICATION_POLICY_ACCESS_DETAIL_SETTINGS";
     field public static final String ACTION_REQUEST_ENABLE_CONTENT_CAPTURE = "android.settings.REQUEST_ENABLE_CONTENT_CAPTURE";
     field public static final String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS";
+    field public static final String ACTION_TETHER_PROVISIONING_UI = "android.settings.TETHER_PROVISIONING_UI";
   }
 
   public static final class Settings.Global extends android.provider.Settings.NameValueTable {
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static boolean putString(@NonNull android.content.ContentResolver, @NonNull String, @Nullable String, @Nullable String, boolean);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static void resetToDefaults(@NonNull android.content.ContentResolver, @Nullable String);
+    field public static final String AIRPLANE_MODE_TOGGLEABLE_RADIOS = "airplane_mode_toggleable_radios";
     field public static final String APP_STANDBY_ENABLED = "app_standby_enabled";
     field public static final String AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES = "autofill_compat_mode_allowed_packages";
     field public static final String CARRIER_APP_NAMES = "carrier_app_names";
@@ -7783,12 +9548,21 @@
     field public static final String EUICC_UNSUPPORTED_COUNTRIES = "euicc_unsupported_countries";
     field public static final String INSTALL_CARRIER_APP_NOTIFICATION_PERSISTENT = "install_carrier_app_notification_persistent";
     field public static final String INSTALL_CARRIER_APP_NOTIFICATION_SLEEP_MILLIS = "install_carrier_app_notification_sleep_millis";
+    field public static final String NETWORK_RECOMMENDATIONS_ENABLED = "network_recommendations_enabled";
     field public static final String OTA_DISABLE_AUTOMATIC_UPDATE = "ota_disable_automatic_update";
     field public static final String REQUIRE_PASSWORD_TO_DECRYPT = "require_password_to_decrypt";
+    field public static final String SOFT_AP_TIMEOUT_ENABLED = "soft_ap_timeout_enabled";
+    field public static final String TETHER_OFFLOAD_DISABLED = "tether_offload_disabled";
     field public static final String TETHER_SUPPORTED = "tether_supported";
     field public static final String THEATER_MODE_ON = "theater_mode_on";
     field public static final String WEBVIEW_MULTIPROCESS = "webview_multiprocess";
     field public static final String WIFI_BADGING_THRESHOLDS = "wifi_badging_thresholds";
+    field public static final String WIFI_P2P_DEVICE_NAME = "wifi_p2p_device_name";
+    field public static final String WIFI_P2P_PENDING_FACTORY_RESET = "wifi_p2p_pending_factory_reset";
+    field public static final String WIFI_SCAN_ALWAYS_AVAILABLE = "wifi_scan_always_enabled";
+    field public static final String WIFI_SCAN_THROTTLE_ENABLED = "wifi_scan_throttle_enabled";
+    field public static final String WIFI_SCORE_PARAMS = "wifi_score_params";
+    field public static final String WIFI_VERBOSE_LOGGING_ENABLED = "wifi_verbose_logging_enabled";
     field public static final String WIFI_WAKEUP_ENABLED = "wifi_wakeup_enabled";
   }
 
@@ -7829,6 +9603,10 @@
     field public static final int VOLUME_HUSH_VIBRATE = 1; // 0x1
   }
 
+  public static final class Settings.System extends android.provider.Settings.NameValueTable {
+    method @RequiresPermission(android.Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE) public static boolean putString(@NonNull android.content.ContentResolver, @NonNull String, @Nullable String, boolean);
+  }
+
   public static interface Telephony.CarrierColumns extends android.provider.BaseColumns {
     field @NonNull public static final android.net.Uri CONTENT_URI;
     field public static final String EXPIRATION_TIME = "expiration_time";
@@ -7917,14 +9695,78 @@
     field @NonNull public static final String ENABLE_CMAS_PRESIDENTIAL_PREF = "enable_cmas_presidential_alerts";
     field @NonNull public static final String ENABLE_CMAS_SEVERE_THREAT_PREF = "enable_cmas_severe_threat_alerts";
     field @NonNull public static final String ENABLE_EMERGENCY_PERF = "enable_emergency_alerts";
-    field @NonNull public static final String ENABLE_FULL_VOLUME_PREF = "use_full_volume";
     field @NonNull public static final String ENABLE_PUBLIC_SAFETY_PREF = "enable_public_safety_messages";
     field @NonNull public static final String ENABLE_STATE_LOCAL_TEST_PREF = "enable_state_local_test_alerts";
     field @NonNull public static final String ENABLE_TEST_ALERT_PREF = "enable_test_alerts";
   }
 
   public static final class Telephony.SimInfo {
+    field public static final String ACCESS_RULES = "access_rules";
+    field public static final String ACCESS_RULES_FROM_CARRIER_CONFIGS = "access_rules_from_carrier_configs";
+    field public static final String ALLOWED_NETWORK_TYPES = "allowed_network_types";
+    field public static final String CARD_ID = "card_id";
+    field public static final String CARRIER_ID = "carrier_id";
+    field public static final String CARRIER_NAME = "carrier_name";
+    field public static final String CB_ALERT_REMINDER_INTERVAL = "alert_reminder_interval";
+    field public static final String CB_ALERT_SOUND_DURATION = "alert_sound_duration";
+    field public static final String CB_ALERT_SPEECH = "enable_alert_speech";
+    field public static final String CB_ALERT_VIBRATE = "enable_alert_vibrate";
+    field public static final String CB_AMBER_ALERT = "enable_cmas_amber_alerts";
+    field public static final String CB_CHANNEL_50_ALERT = "enable_channel_50_alerts";
+    field public static final String CB_CMAS_TEST_ALERT = "enable_cmas_test_alerts";
+    field public static final String CB_EMERGENCY_ALERT = "enable_emergency_alerts";
+    field public static final String CB_ETWS_TEST_ALERT = "enable_etws_test_alerts";
+    field public static final String CB_EXTREME_THREAT_ALERT = "enable_cmas_extreme_threat_alerts";
+    field public static final String CB_OPT_OUT_DIALOG = "show_cmas_opt_out_dialog";
+    field public static final String CB_SEVERE_THREAT_ALERT = "enable_cmas_severe_threat_alerts";
+    field public static final String COLOR = "color";
     field @NonNull public static final android.net.Uri CONTENT_URI;
+    field public static final String DATA_ENABLED_OVERRIDE_RULES = "data_enabled_override_rules";
+    field public static final String DATA_ROAMING = "data_roaming";
+    field public static final int DATA_ROAMING_DEFAULT = 0; // 0x0
+    field public static final int DATA_ROAMING_DISABLE = 0; // 0x0
+    field public static final int DATA_ROAMING_ENABLE = 1; // 0x1
+    field public static final String DISPLAY_NAME = "display_name";
+    field public static final String EHPLMNS = "ehplmns";
+    field public static final String ENHANCED_4G_MODE_ENABLED = "volte_vt_enabled";
+    field public static final String GROUP_OWNER = "group_owner";
+    field public static final String GROUP_UUID = "group_uuid";
+    field public static final String HPLMNS = "hplmns";
+    field public static final String ICC_ID = "icc_id";
+    field public static final String IMSI = "imsi";
+    field public static final String ISO_COUNTRY_CODE = "iso_country_code";
+    field public static final String IS_EMBEDDED = "is_embedded";
+    field public static final String IS_OPPORTUNISTIC = "is_opportunistic";
+    field public static final String IS_REMOVABLE = "is_removable";
+    field public static final String MCC = "mcc";
+    field public static final String MCC_STRING = "mcc_string";
+    field public static final String MNC = "mnc";
+    field public static final String MNC_STRING = "mnc_string";
+    field public static final String NAME_SOURCE = "name_source";
+    field public static final int NAME_SOURCE_CARRIER = 3; // 0x3
+    field public static final int NAME_SOURCE_DEFAULT = 0; // 0x0
+    field public static final int NAME_SOURCE_SIM_PNN = 4; // 0x4
+    field public static final int NAME_SOURCE_SIM_SPN = 1; // 0x1
+    field public static final int NAME_SOURCE_USER_INPUT = 2; // 0x2
+    field public static final String NUMBER = "number";
+    field public static final String PROFILE_CLASS = "profile_class";
+    field public static final int PROFILE_CLASS_DEFAULT = -1; // 0xffffffff
+    field public static final int PROFILE_CLASS_OPERATIONAL = 2; // 0x2
+    field public static final int PROFILE_CLASS_PROVISIONING = 1; // 0x1
+    field public static final int PROFILE_CLASS_TESTING = 0; // 0x0
+    field public static final int PROFILE_CLASS_UNSET = -1; // 0xffffffff
+    field public static final int SIM_NOT_INSERTED = -1; // 0xffffffff
+    field public static final String SIM_SLOT_INDEX = "sim_id";
+    field public static final String SUBSCRIPTION_TYPE = "subscription_type";
+    field public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0; // 0x0
+    field public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1; // 0x1
+    field public static final String UICC_APPLICATIONS_ENABLED = "uicc_applications_enabled";
+    field public static final String UNIQUE_KEY_SUBSCRIPTION_ID = "_id";
+    field public static final String VT_IMS_ENABLED = "vt_ims_enabled";
+    field public static final String WFC_IMS_ENABLED = "wfc_ims_enabled";
+    field public static final String WFC_IMS_MODE = "wfc_ims_mode";
+    field public static final String WFC_IMS_ROAMING_ENABLED = "wfc_ims_roaming_enabled";
+    field public static final String WFC_IMS_ROAMING_MODE = "wfc_ims_roaming_mode";
   }
 
   public static final class Telephony.Sms.Intents {
@@ -8304,6 +10146,16 @@
 
   public abstract class DataLoaderService extends android.app.Service {
     ctor public DataLoaderService();
+    method @Nullable public android.service.dataloader.DataLoaderService.DataLoader onCreateDataLoader();
+  }
+
+  public static interface DataLoaderService.DataLoader {
+    method public boolean onCreate(@NonNull android.content.pm.DataLoaderParams, @NonNull android.service.dataloader.DataLoaderService.FileSystemConnector);
+    method public boolean onPrepareImage(@NonNull java.util.Collection<android.content.pm.InstallationFile>, @NonNull java.util.Collection<java.lang.String>);
+  }
+
+  public static final class DataLoaderService.FileSystemConnector {
+    method public void writeData(@NonNull String, long, long, @NonNull android.os.ParcelFileDescriptor) throws java.io.IOException;
   }
 
 }
@@ -8371,6 +10223,8 @@
 
   public abstract class EuiccService extends android.app.Service {
     ctor public EuiccService();
+    method public void dump(@NonNull java.io.PrintWriter);
+    method public int encodeSmdxSubjectAndReasonCode(@Nullable String, @Nullable String) throws java.lang.IllegalArgumentException, java.lang.NumberFormatException, java.lang.UnsupportedOperationException;
     method @CallSuper public android.os.IBinder onBind(android.content.Intent);
     method public abstract int onDeleteSubscription(int, String);
     method public android.service.euicc.DownloadSubscriptionResult onDownloadSubscription(int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean, boolean, @Nullable android.os.Bundle);
@@ -8469,6 +10323,7 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
     field public static final String KEY_CONTEXTUAL_ACTIONS = "key_contextual_actions";
     field public static final String KEY_IMPORTANCE = "key_importance";
+    field public static final String KEY_NOT_CONVERSATION = "key_not_conversation";
     field public static final String KEY_PEOPLE = "key_people";
     field public static final String KEY_RANKING_SCORE = "key_ranking_score";
     field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
@@ -8488,7 +10343,10 @@
     method @Nullable public android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification, @NonNull android.app.NotificationChannel);
     method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean);
     method public abstract void onNotificationSnoozedUntilContext(@NonNull android.service.notification.StatusBarNotification, @NonNull String);
+    method public void onNotificationVisibilityChanged(@NonNull String, boolean);
     method public void onNotificationsSeen(@NonNull java.util.List<java.lang.String>);
+    method public void onPanelHidden();
+    method public void onPanelRevealed(int);
     method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int);
     method public final void unsnoozeNotification(@NonNull String);
     field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
@@ -8727,6 +10585,14 @@
 
 }
 
+package android.service.voice {
+
+  public class VoiceInteractionService extends android.app.Service {
+    method @NonNull @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public final android.media.voice.KeyphraseModelManager createKeyphraseModelManager();
+  }
+
+}
+
 package android.service.wallpaper {
 
   public class WallpaperService.Engine {
@@ -9070,8 +10936,18 @@
 
   public final class AccessNetworkConstants {
     field public static final int TRANSPORT_TYPE_INVALID = -1; // 0xffffffff
-    field public static final int TRANSPORT_TYPE_WLAN = 2; // 0x2
-    field public static final int TRANSPORT_TYPE_WWAN = 1; // 0x1
+  }
+
+  public static final class AccessNetworkConstants.NgranBands {
+    method public static int getFrequencyRangeGroup(int);
+    field public static final int FREQUENCY_RANGE_GROUP_1 = 1; // 0x1
+    field public static final int FREQUENCY_RANGE_GROUP_2 = 2; // 0x2
+    field public static final int FREQUENCY_RANGE_GROUP_UNKNOWN = 0; // 0x0
+  }
+
+  public final class BarringInfo implements android.os.Parcelable {
+    ctor public BarringInfo();
+    method @NonNull public android.telephony.BarringInfo createLocationInfoSanitizedCopy();
   }
 
   public final class CallAttributes implements android.os.Parcelable {
@@ -9086,6 +10962,7 @@
 
   public final class CallQuality implements android.os.Parcelable {
     ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int);
+    ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int, boolean, boolean, boolean);
     method public int describeContents();
     method public int getAverageRelativeJitter();
     method public int getAverageRoundTripTime();
@@ -9098,6 +10975,9 @@
     method public int getNumRtpPacketsTransmitted();
     method public int getNumRtpPacketsTransmittedLost();
     method public int getUplinkCallQualityLevel();
+    method public boolean isIncomingSilenceDetected();
+    method public boolean isOutgoingSilenceDetected();
+    method public boolean isRtpInactivityDetected();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int CALL_QUALITY_BAD = 4; // 0x4
     field public static final int CALL_QUALITY_EXCELLENT = 0; // 0x0
@@ -9176,6 +11056,19 @@
     method @NonNull public java.util.List<android.telephony.CbGeoUtils.LatLng> getVertices();
   }
 
+  public final class CdmaEriInformation implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getEriIconIndex();
+    method public int getEriIconMode();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CdmaEriInformation> CREATOR;
+    field public static final int ERI_FLASH = 2; // 0x2
+    field public static final int ERI_ICON_MODE_FLASH = 1; // 0x1
+    field public static final int ERI_ICON_MODE_NORMAL = 0; // 0x0
+    field public static final int ERI_OFF = 1; // 0x1
+    field public static final int ERI_ON = 0; // 0x0
+  }
+
   public class CellBroadcastIntents {
     method public static void sendOrderedBroadcastForBackgroundReceivers(@NonNull android.content.Context, @Nullable android.os.UserHandle, @NonNull android.content.Intent, @Nullable String, @Nullable String, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
   }
@@ -9707,37 +11600,18 @@
   }
 
   public final class NetworkRegistrationInfo implements android.os.Parcelable {
-    method public int describeContents();
-    method public int getAccessNetworkTechnology();
-    method @NonNull public java.util.List<java.lang.Integer> getAvailableServices();
-    method @Nullable public android.telephony.CellIdentity getCellIdentity();
     method @Nullable public android.telephony.DataSpecificRegistrationInfo getDataSpecificInfo();
-    method public int getDomain();
-    method public int getNrState();
     method public int getRegistrationState();
     method public int getRejectCause();
     method public int getRoamingType();
-    method public int getTransportType();
     method public boolean isEmergencyEnabled();
-    method public boolean isRoaming();
     method public void writeToParcel(android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.NetworkRegistrationInfo> CREATOR;
-    field public static final int DOMAIN_CS = 1; // 0x1
-    field public static final int DOMAIN_CS_PS = 3; // 0x3
-    field public static final int DOMAIN_PS = 2; // 0x2
-    field public static final int DOMAIN_UNKNOWN = 0; // 0x0
     field public static final int REGISTRATION_STATE_DENIED = 3; // 0x3
     field public static final int REGISTRATION_STATE_HOME = 1; // 0x1
     field public static final int REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING = 0; // 0x0
     field public static final int REGISTRATION_STATE_NOT_REGISTERED_SEARCHING = 2; // 0x2
     field public static final int REGISTRATION_STATE_ROAMING = 5; // 0x5
     field public static final int REGISTRATION_STATE_UNKNOWN = 4; // 0x4
-    field public static final int SERVICE_TYPE_DATA = 2; // 0x2
-    field public static final int SERVICE_TYPE_EMERGENCY = 5; // 0x5
-    field public static final int SERVICE_TYPE_SMS = 3; // 0x3
-    field public static final int SERVICE_TYPE_UNKNOWN = 0; // 0x0
-    field public static final int SERVICE_TYPE_VIDEO = 4; // 0x4
-    field public static final int SERVICE_TYPE_VOICE = 1; // 0x1
   }
 
   public static final class NetworkRegistrationInfo.Builder {
@@ -9812,7 +11686,8 @@
     method public void onRadioPowerStateChanged(int);
     method public void onSrvccStateChanged(int);
     method public void onVoiceActivationStateChanged(int);
-    field public static final int LISTEN_CALL_ATTRIBUTES_CHANGED = 67108864; // 0x4000000
+    field @RequiresPermission(android.Manifest.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH) public static final int LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH = 512; // 0x200
+    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.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_PRECISE_CALL_STATE = 2048; // 0x800
@@ -9821,6 +11696,19 @@
     field public static final int LISTEN_VOICE_ACTIVATION_STATE = 131072; // 0x20000
   }
 
+  public final class PinResult implements android.os.Parcelable {
+    ctor public PinResult(int, int);
+    method public int describeContents();
+    method public int getAttemptsRemaining();
+    method @NonNull public static android.telephony.PinResult getDefaultFailedResult();
+    method public int getType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PinResult> CREATOR;
+    field public static final int PIN_RESULT_TYPE_FAILURE = 2; // 0x2
+    field public static final int PIN_RESULT_TYPE_INCORRECT = 1; // 0x1
+    field public static final int PIN_RESULT_TYPE_SUCCESS = 0; // 0x0
+  }
+
   public final class PreciseCallState implements android.os.Parcelable {
     ctor public PreciseCallState(int, int, int, int, int);
     method public int describeContents();
@@ -9951,9 +11839,10 @@
   public class ServiceState implements android.os.Parcelable {
     method @NonNull public android.telephony.ServiceState createLocationInfoSanitizedCopy(boolean);
     method public void fillInNotifierBundle(@NonNull android.os.Bundle);
+    method public int getDataNetworkType();
     method public int getDataRegistrationState();
+    method public boolean getDataRoamingFromRegistration();
     method @Nullable public android.telephony.NetworkRegistrationInfo getNetworkRegistrationInfo(int, int);
-    method @NonNull public java.util.List<android.telephony.NetworkRegistrationInfo> getNetworkRegistrationInfoList();
     method @NonNull public java.util.List<android.telephony.NetworkRegistrationInfo> getNetworkRegistrationInfoListForDomain(int);
     method @NonNull public java.util.List<android.telephony.NetworkRegistrationInfo> getNetworkRegistrationInfoListForTransportType(int);
     method public int getNrFrequencyRange();
@@ -10093,13 +11982,26 @@
   }
 
   public final class SmsManager {
+    method @RequiresPermission(android.Manifest.permission.ACCESS_MESSAGES_ON_ICC) public boolean copyMessageToIcc(@Nullable byte[], @NonNull byte[], int);
     method @RequiresPermission(android.Manifest.permission.ACCESS_MESSAGES_ON_ICC) public boolean deleteMessageFromIcc(int);
     method public boolean disableCellBroadcastRange(int, int, int);
     method public boolean enableCellBroadcastRange(int, int, int);
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_MESSAGES_ON_ICC) public java.util.List<android.telephony.SmsMessage> getMessagesFromIcc();
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getPremiumSmsConsent(@NonNull String);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSmsCapacityOnIcc();
-    method public void sendMultipartTextMessage(@NonNull String, @NonNull String, @NonNull java.util.List<java.lang.String>, @Nullable java.util.List<android.app.PendingIntent>, @Nullable java.util.List<android.app.PendingIntent>, @NonNull String);
+    method public void sendMultipartTextMessage(@NonNull String, @Nullable String, @NonNull java.util.List<java.lang.String>, @Nullable java.util.List<android.app.PendingIntent>, @Nullable java.util.List<android.app.PendingIntent>, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void sendMultipartTextMessageWithoutPersisting(String, String, java.util.List<java.lang.String>, java.util.List<android.app.PendingIntent>, java.util.List<android.app.PendingIntent>);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setPremiumSmsConsent(@NonNull String, int);
+    field public static final int PREMIUM_SMS_CONSENT_ALWAYS_ALLOW = 3; // 0x3
+    field public static final int PREMIUM_SMS_CONSENT_ASK_USER = 1; // 0x1
+    field public static final int PREMIUM_SMS_CONSENT_NEVER_ALLOW = 2; // 0x2
+    field public static final int PREMIUM_SMS_CONSENT_UNKNOWN = 0; // 0x0
+  }
+
+  public class SmsMessage {
+    method @Nullable public static android.telephony.SmsMessage createFromNativeSmsSubmitPdu(@NonNull byte[], boolean);
+    method @Nullable public static android.telephony.SmsMessage.SubmitPdu getSmsPdu(int, int, @Nullable String, @NonNull String, @NonNull String, long);
+    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public static byte[] getSubmitPduEncodedMessage(boolean, @NonNull String, @NonNull String, int, int, int, int, int, int);
   }
 
   public class SubscriptionInfo implements android.os.Parcelable {
@@ -10177,6 +12079,7 @@
   public class TelephonyManager {
     method public int addDevicePolicyOverrideApn(@NonNull android.content.Context, @NonNull android.telephony.data.ApnSetting);
     method @Deprecated @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void call(String, String);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int changeIccLockPassword(@NonNull String, @NonNull String);
     method public int checkCarrierPrivilegesForPackage(String);
     method public int checkCarrierPrivilegesForPackageAnyPhone(String);
     method public void dial(String);
@@ -10186,17 +12089,20 @@
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void enableVideoCalling(boolean);
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getAidForAppType(int);
     method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.List<android.service.carrier.CarrierIdentifier> getAllowedCarriers(int);
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getAllowedNetworkTypes();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int);
     method public java.util.List<java.lang.String> getCarrierPackageNamesForIntent(android.content.Intent);
     method public java.util.List<java.lang.String> getCarrierPackageNamesForIntentAndPhone(android.content.Intent, int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCarrierPrivilegeStatus(int);
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.List<java.lang.String> getCarrierPrivilegedPackagesForAllActiveSubscriptions();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.CarrierRestrictionRules getCarrierRestrictionRules();
+    method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.CdmaEriInformation getCdmaEriInformation();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMdn();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMdn(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMin();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMin(int);
     method public String getCdmaPrlVersion();
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCdmaRoamingMode();
     method public int getCurrentPhoneType();
     method public int getCurrentPhoneType(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getDataActivationState();
@@ -10209,6 +12115,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getEmergencyCallbackMode();
     method public int getEmergencyNumberDbVersion();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimDomain();
+    method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String[] getIsimImpu();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimIst();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Map<java.lang.Integer,java.lang.Integer> getLogicalToPhysicalSlotMapping();
     method public int getMaxNumberOfSimultaneouslyActiveSims();
@@ -10229,20 +12136,23 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoiceActivationState();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handlePinMmi(String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handlePinMmiForSubscriber(int, String);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean iccCloseLogicalChannelBySlot(int, int);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean iccCloseLogicalChannelBySlot(int, int);
     method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannelBySlot(int, @Nullable String, int);
-    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduBasicChannelBySlot(int, int, int, int, int, int, @Nullable String);
-    method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduLogicalChannelBySlot(int, int, int, int, int, int, int, @Nullable String);
+    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduBasicChannelBySlot(int, int, int, int, int, int, @Nullable String);
+    method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduLogicalChannelBySlot(int, int, int, int, int, int, int, @Nullable String);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAnyRadioPoweredOn();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApnMetered(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApplicationOnUicc(int);
     method public boolean isCurrentSimOperator(@NonNull String, int, @Nullable String);
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDataAllowedInVoiceCall();
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDataConnectionEnabled();
     method public boolean isDataConnectivityPossible();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDataEnabledForApn(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isEmergencyAssistanceEnabled();
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isGlobalModeEnabled();
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean isIccLockEnabled();
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isIdle();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isInEmergencySmsMode();
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isManualNetworkSelectionAllowed();
     method public boolean isModemEnabledForSlot(int);
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isOffhook();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isOpportunisticNetworkEnabled();
@@ -10255,6 +12165,7 @@
     method public boolean modifyDevicePolicyOverrideApn(@NonNull android.content.Context, int, @NonNull android.telephony.data.ApnSetting);
     method public boolean needsOtaServiceProvisioning();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOtaEmergencyNumberDbInstalled();
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyUserActivity();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void reportDefaultNetworkStatus(boolean);
     method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull android.os.WorkSource, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);
@@ -10265,13 +12176,20 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean resetRadioConfig();
     method @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL) public void resetSettings();
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setAllowedNetworkTypes(long);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setAlwaysAllowMmsData(boolean);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setAlwaysReportSignalStrength(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCarrierDataEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setCarrierRestrictionRules(@NonNull android.telephony.CarrierRestrictionRules);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setCdmaRoamingMode(int);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setCdmaSubscriptionMode(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataActivationState(int);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setDataAllowedDuringVoiceCall(boolean);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(int, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setIccLockEnabled(boolean, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMultiSimCarrierRestriction(boolean);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setNetworkSelectionModeManual(@NonNull String, int, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunisticNetworkState(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setPolicyDataEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setPreferredNetworkTypeBitmask(long);
@@ -10280,41 +12198,84 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadioPower(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSimPowerState(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSimPowerStateForSlot(int, int);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSystemSelectionChannels(@NonNull java.util.List<android.telephony.RadioAccessSpecifier>, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Boolean>);
     method @Deprecated public void setVisualVoicemailEnabled(android.telecom.PhoneAccountHandle, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setVoiceActivationState(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void shutdownAllRadios();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean supplyPin(String);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPinReportResult(String);
+    method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult supplyPinReportPinResult(@NonNull String);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPinReportResult(String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean supplyPuk(String, String);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPukReportResult(String, String);
+    method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult supplyPukReportPinResult(@NonNull String, @NonNull String);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPukReportResult(String, String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean switchSlots(int[]);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void toggleRadioOnOff();
     method public void updateServiceLocation();
     method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void updateTestOtaEmergencyNumberDbFilePath(@NonNull String);
     field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final String ACTION_ANOMALY_REPORTED = "android.telephony.action.ANOMALY_REPORTED";
+    field public static final String ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE = "com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE";
+    field public static final String ACTION_CARRIER_SIGNAL_PCO_VALUE = "com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE";
+    field public static final String ACTION_CARRIER_SIGNAL_REDIRECTED = "com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED";
+    field public static final String ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED = "com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED";
+    field public static final String ACTION_CARRIER_SIGNAL_RESET = "com.android.internal.telephony.CARRIER_SIGNAL_RESET";
     field public static final String ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED = "android.intent.action.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED";
     field public static final String ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED = "android.intent.action.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED";
     field public static final String ACTION_EMERGENCY_ASSISTANCE = "android.telephony.action.EMERGENCY_ASSISTANCE";
     field public static final String ACTION_EMERGENCY_CALLBACK_MODE_CHANGED = "android.intent.action.EMERGENCY_CALLBACK_MODE_CHANGED";
     field public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED = "android.intent.action.EMERGENCY_CALL_STATE_CHANGED";
-    field public static final String ACTION_NETWORK_SET_TIME = "android.telephony.action.NETWORK_SET_TIME";
     field public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE = "com.android.omadm.service.CONFIGURATION_UPDATE";
+    field public static final String ACTION_SERVICE_PROVIDERS_UPDATED = "android.telephony.action.SERVICE_PROVIDERS_UPDATED";
     field public static final String ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS = "android.telephony.action.SHOW_NOTICE_ECM_BLOCK_OTHERS";
     field public static final String ACTION_SIM_APPLICATION_STATE_CHANGED = "android.telephony.action.SIM_APPLICATION_STATE_CHANGED";
     field public static final String ACTION_SIM_CARD_STATE_CHANGED = "android.telephony.action.SIM_CARD_STATE_CHANGED";
     field public static final String ACTION_SIM_SLOT_STATUS_CHANGED = "android.telephony.action.SIM_SLOT_STATUS_CHANGED";
+    field public static final int CARD_POWER_DOWN = 0; // 0x0
+    field public static final int CARD_POWER_UP = 1; // 0x1
+    field public static final int CARD_POWER_UP_PASS_THROUGH = 2; // 0x2
     field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe
     field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
     field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
     field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff
+    field public static final int CDMA_SUBSCRIPTION_NV = 1; // 0x1
+    field public static final int CDMA_SUBSCRIPTION_RUIM_SIM = 0; // 0x0
+    field public static final int CDMA_SUBSCRIPTION_UNKNOWN = -1; // 0xffffffff
+    field public static final int CHANGE_ICC_LOCK_SUCCESS = 2147483647; // 0x7fffffff
+    field public static final int DEFAULT_PREFERRED_NETWORK_MODE = 0; // 0x0
     field public static final String EXTRA_ANOMALY_DESCRIPTION = "android.telephony.extra.ANOMALY_DESCRIPTION";
     field public static final String EXTRA_ANOMALY_ID = "android.telephony.extra.ANOMALY_ID";
+    field @Deprecated public static final String EXTRA_APN_PROTOCOL = "apnProto";
+    field public static final String EXTRA_APN_PROTOCOL_INT = "apnProtoInt";
+    field @Deprecated public static final String EXTRA_APN_TYPE = "apnType";
+    field public static final String EXTRA_APN_TYPE_INT = "apnTypeInt";
+    field public static final String EXTRA_DATA_SPN = "android.telephony.extra.DATA_SPN";
+    field public static final String EXTRA_DEFAULT_NETWORK_AVAILABLE = "defaultNetworkAvailable";
+    field public static final String EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE = "android.telephony.extra.DEFAULT_SUBSCRIPTION_SELECT_TYPE";
+    field public static final int EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_ALL = 4; // 0x4
+    field public static final int EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_DATA = 1; // 0x1
+    field public static final int EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_NONE = 0; // 0x0
+    field public static final int EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_SMS = 3; // 0x3
+    field public static final int EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_VOICE = 2; // 0x2
+    field public static final String EXTRA_ERROR_CODE = "errorCode";
+    field public static final String EXTRA_PCO_ID = "pcoId";
+    field public static final String EXTRA_PCO_VALUE = "pcoValue";
+    field public static final String EXTRA_PHONE_IN_ECM_STATE = "android.telephony.extra.PHONE_IN_ECM_STATE";
+    field public static final String EXTRA_PHONE_IN_EMERGENCY_CALL = "android.telephony.extra.PHONE_IN_EMERGENCY_CALL";
+    field public static final String EXTRA_PLMN = "android.telephony.extra.PLMN";
+    field public static final String EXTRA_REDIRECTION_URL = "redirectionUrl";
+    field public static final String EXTRA_SHOW_PLMN = "android.telephony.extra.SHOW_PLMN";
+    field public static final String EXTRA_SHOW_SPN = "android.telephony.extra.SHOW_SPN";
+    field public static final String EXTRA_SIM_COMBINATION_NAMES = "android.telephony.extra.SIM_COMBINATION_NAMES";
+    field public static final String EXTRA_SIM_COMBINATION_WARNING_TYPE = "android.telephony.extra.SIM_COMBINATION_WARNING_TYPE";
+    field public static final int EXTRA_SIM_COMBINATION_WARNING_TYPE_DUAL_CDMA = 1; // 0x1
+    field public static final int EXTRA_SIM_COMBINATION_WARNING_TYPE_NONE = 0; // 0x0
     field public static final String EXTRA_SIM_STATE = "android.telephony.extra.SIM_STATE";
+    field public static final String EXTRA_SPN = "android.telephony.extra.SPN";
     field public static final String EXTRA_VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL = "android.telephony.extra.VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL";
     field public static final String EXTRA_VOICEMAIL_SCRAMBLED_PIN_STRING = "android.telephony.extra.VOICEMAIL_SCRAMBLED_PIN_STRING";
     field public static final int INVALID_EMERGENCY_NUMBER_DB_VERSION = -1; // 0xffffffff
     field public static final int KEY_TYPE_EPDG = 1; // 0x1
     field public static final int KEY_TYPE_WLAN = 2; // 0x2
+    field public static final String MODEM_ACTIVITY_RESULT_KEY = "controller_activity";
     field public static final long NETWORK_TYPE_BITMASK_1xRTT = 64L; // 0x40L
     field public static final long NETWORK_TYPE_BITMASK_CDMA = 8L; // 0x8L
     field public static final long NETWORK_TYPE_BITMASK_EDGE = 2L; // 0x2L
@@ -10335,6 +12296,7 @@
     field public static final long NETWORK_TYPE_BITMASK_TD_SCDMA = 65536L; // 0x10000L
     field public static final long NETWORK_TYPE_BITMASK_UMTS = 4L; // 0x4L
     field public static final long NETWORK_TYPE_BITMASK_UNKNOWN = 0L; // 0x0L
+    field public static final int PHONE_TYPE_THIRD_PARTY = 4; // 0x4
     field public static final int RADIO_POWER_OFF = 0; // 0x0
     field public static final int RADIO_POWER_ON = 1; // 0x1
     field public static final int RADIO_POWER_UNAVAILABLE = 2; // 0x2
@@ -10358,9 +12320,32 @@
   public class TelephonyRegistryManager {
     method public void addOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener, @NonNull java.util.concurrent.Executor);
     method public void addOnSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener, @NonNull java.util.concurrent.Executor);
+    method public void notifyActiveDataSubIdChanged(int);
+    method public void notifyBarringInfoChanged(int, int, @NonNull android.telephony.BarringInfo);
+    method public void notifyCallForwardingChanged(int, boolean);
+    method public void notifyCallQualityChanged(int, int, @NonNull android.telephony.CallQuality, int);
+    method public void notifyCallStateChanged(int, int, int, @Nullable String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyCallStateChangedForAllSubscriptions(int, @Nullable String);
     method public void notifyCarrierNetworkChange(boolean);
+    method public void notifyCellInfoChanged(int, @NonNull java.util.List<android.telephony.CellInfo>);
+    method public void notifyCellLocation(int, @NonNull android.telephony.CellIdentity);
+    method public void notifyDataActivationStateChanged(int, int, int);
+    method public void notifyDataActivityChanged(int, int);
+    method public void notifyDataConnectionForSubscriber(int, int, int, @Nullable android.telephony.PreciseDataConnectionState);
+    method public void notifyDisconnectCause(int, int, int, int);
+    method public void notifyEmergencyNumberList(int, int);
+    method public void notifyImsDisconnectCause(int, @NonNull android.telephony.ims.ImsReasonInfo);
+    method public void notifyMessageWaitingChanged(int, int, boolean);
+    method public void notifyPhoneCapabilityChanged(@NonNull android.telephony.PhoneCapability);
+    method public void notifyPreciseCallState(int, int, int, int, int);
+    method public void notifyPreciseDataConnectionFailed(int, int, int, @Nullable String, int);
+    method public void notifyRadioPowerStateChanged(int, int, int);
     method public void notifyRegistrationFailed(int, int, @NonNull android.telephony.CellIdentity, @NonNull String, int, int, int);
+    method public void notifyServiceStateChanged(int, int, @NonNull android.telephony.ServiceState);
+    method public void notifySignalStrengthChanged(int, int, @NonNull android.telephony.SignalStrength);
+    method public void notifySrvccStateChanged(int, int);
+    method public void notifyUserMobileDataStateChanged(int, int, boolean);
+    method public void notifyVoiceActivationStateChanged(int, int, int);
     method public void removeOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener);
     method public void removeOnSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
   }
@@ -10399,6 +12384,20 @@
     method public static final void setSmsFilterSettings(android.content.Context, android.telecom.PhoneAccountHandle, android.telephony.VisualVoicemailSmsFilterSettings);
   }
 
+  public final class WapPushManagerConnector {
+    ctor public WapPushManagerConnector(@NonNull android.content.Context);
+    method public boolean bindToWapPushManagerService();
+    method @Nullable public String getConnectedWapPushManagerServicePackage();
+    method public int processMessage(@NonNull String, @NonNull String, @NonNull android.content.Intent);
+    method public void unbindWapPushManagerService();
+    field public static final int RESULT_APP_QUERY_FAILED = 2; // 0x2
+    field public static final int RESULT_EXCEPTION_CAUGHT = 16; // 0x10
+    field public static final int RESULT_FURTHER_PROCESSING = 32768; // 0x8000
+    field public static final int RESULT_INVALID_RECEIVER_NAME = 8; // 0x8
+    field public static final int RESULT_MESSAGE_HANDLED = 1; // 0x1
+    field public static final int RESULT_SIGNATURE_NO_MATCH = 4; // 0x4
+  }
+
 }
 
 package android.telephony.cdma {
@@ -10424,6 +12423,23 @@
 
 package android.telephony.data {
 
+  public class ApnSetting implements android.os.Parcelable {
+    method @NonNull public static String getApnTypesStringFromBitmask(int);
+    field public static final String TYPE_ALL_STRING = "*";
+    field public static final String TYPE_CBS_STRING = "cbs";
+    field public static final String TYPE_DEFAULT_STRING = "default";
+    field public static final String TYPE_DUN_STRING = "dun";
+    field public static final String TYPE_EMERGENCY_STRING = "emergency";
+    field public static final String TYPE_FOTA_STRING = "fota";
+    field public static final String TYPE_HIPRI_STRING = "hipri";
+    field public static final String TYPE_IA_STRING = "ia";
+    field public static final String TYPE_IMS_STRING = "ims";
+    field public static final String TYPE_MCX_STRING = "mcx";
+    field public static final String TYPE_MMS_STRING = "mms";
+    field public static final String TYPE_SUPL_STRING = "supl";
+    field public static final String TYPE_XCAP_STRING = "xcap";
+  }
+
   public final class DataCallResponse implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public java.util.List<android.net.LinkAddress> getAddresses();
@@ -10433,7 +12449,9 @@
     method public int getId();
     method @NonNull public String getInterfaceName();
     method public int getLinkStatus();
-    method public int getMtu();
+    method @Deprecated public int getMtu();
+    method public int getMtuV4();
+    method public int getMtuV6();
     method @NonNull public java.util.List<java.net.InetAddress> getPcscfAddresses();
     method public int getProtocolType();
     method public int getSuggestedRetryTime();
@@ -10455,7 +12473,9 @@
     method @NonNull public android.telephony.data.DataCallResponse.Builder setId(int);
     method @NonNull public android.telephony.data.DataCallResponse.Builder setInterfaceName(@NonNull String);
     method @NonNull public android.telephony.data.DataCallResponse.Builder setLinkStatus(int);
-    method @NonNull public android.telephony.data.DataCallResponse.Builder setMtu(int);
+    method @Deprecated @NonNull public android.telephony.data.DataCallResponse.Builder setMtu(int);
+    method @NonNull public android.telephony.data.DataCallResponse.Builder setMtuV4(int);
+    method @NonNull public android.telephony.data.DataCallResponse.Builder setMtuV6(int);
     method @NonNull public android.telephony.data.DataCallResponse.Builder setPcscfAddresses(@NonNull java.util.List<java.net.InetAddress>);
     method @NonNull public android.telephony.data.DataCallResponse.Builder setProtocolType(int);
     method @NonNull public android.telephony.data.DataCallResponse.Builder setSuggestedRetryTime(int);
@@ -10466,7 +12486,9 @@
     method @NonNull public String getApn();
     method public int getAuthType();
     method public int getBearerBitmask();
-    method public int getMtu();
+    method @Deprecated public int getMtu();
+    method public int getMtuV4();
+    method public int getMtuV6();
     method @Nullable public String getPassword();
     method public int getProfileId();
     method public int getProtocolType();
@@ -10491,7 +12513,9 @@
     method @NonNull public android.telephony.data.DataProfile.Builder setApn(@NonNull String);
     method @NonNull public android.telephony.data.DataProfile.Builder setAuthType(int);
     method @NonNull public android.telephony.data.DataProfile.Builder setBearerBitmask(int);
-    method @NonNull public android.telephony.data.DataProfile.Builder setMtu(int);
+    method @Deprecated @NonNull public android.telephony.data.DataProfile.Builder setMtu(int);
+    method @NonNull public android.telephony.data.DataProfile.Builder setMtuV4(int);
+    method @NonNull public android.telephony.data.DataProfile.Builder setMtuV6(int);
     method @NonNull public android.telephony.data.DataProfile.Builder setPassword(@NonNull String);
     method @NonNull public android.telephony.data.DataProfile.Builder setPersistent(boolean);
     method @NonNull public android.telephony.data.DataProfile.Builder setPreferred(boolean);
@@ -10736,6 +12760,7 @@
     method public int getEmergencyServiceCategories();
     method @NonNull public java.util.List<java.lang.String> getEmergencyUrns();
     method public android.telephony.ims.ImsStreamMediaProfile getMediaProfile();
+    method @Nullable public android.os.Bundle getProprietaryCallExtras();
     method public int getRestrictCause();
     method public int getServiceType();
     method public static int getVideoStateFromCallType(int);
@@ -10780,7 +12805,8 @@
     field public static final String EXTRA_ADDITIONAL_CALL_INFO = "AdditionalCallInfo";
     field public static final String EXTRA_ADDITIONAL_SIP_INVITE_FIELDS = "android.telephony.ims.extra.ADDITIONAL_SIP_INVITE_FIELDS";
     field public static final String EXTRA_CALL_DISCONNECT_CAUSE = "android.telephony.ims.extra.CALL_DISCONNECT_CAUSE";
-    field public static final String EXTRA_CALL_RAT_TYPE = "CallRadioTech";
+    field public static final String EXTRA_CALL_NETWORK_TYPE = "android.telephony.ims.extra.CALL_NETWORK_TYPE";
+    field @Deprecated public static final String EXTRA_CALL_RAT_TYPE = "CallRadioTech";
     field public static final String EXTRA_CHILD_NUMBER = "ChildNum";
     field public static final String EXTRA_CNA = "cna";
     field public static final String EXTRA_CNAP = "cnap";
@@ -10788,6 +12814,7 @@
     field public static final String EXTRA_DIALSTRING = "dialstring";
     field public static final String EXTRA_DISPLAY_TEXT = "DisplayText";
     field public static final String EXTRA_EMERGENCY_CALL = "e_call";
+    field public static final String EXTRA_FORWARDED_NUMBER = "android.telephony.ims.extra.FORWARDED_NUMBER";
     field public static final String EXTRA_IS_CALL_PULL = "CallPull";
     field public static final String EXTRA_OI = "oi";
     field public static final String EXTRA_OIR = "oir";
@@ -10812,8 +12839,8 @@
     method public void callSessionConferenceExtendReceived(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile);
     method public void callSessionConferenceExtended(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile);
     method public void callSessionConferenceStateUpdated(android.telephony.ims.ImsConferenceState);
-    method public void callSessionHandover(int, int, android.telephony.ims.ImsReasonInfo);
-    method public void callSessionHandoverFailed(int, int, android.telephony.ims.ImsReasonInfo);
+    method @Deprecated public void callSessionHandover(int, int, android.telephony.ims.ImsReasonInfo);
+    method @Deprecated public void callSessionHandoverFailed(int, int, android.telephony.ims.ImsReasonInfo);
     method public void callSessionHeld(android.telephony.ims.ImsCallProfile);
     method public void callSessionHoldFailed(android.telephony.ims.ImsReasonInfo);
     method public void callSessionHoldReceived(android.telephony.ims.ImsCallProfile);
@@ -10821,7 +12848,7 @@
     method public void callSessionInitiatedFailed(android.telephony.ims.ImsReasonInfo);
     method public void callSessionInviteParticipantsRequestDelivered();
     method public void callSessionInviteParticipantsRequestFailed(android.telephony.ims.ImsReasonInfo);
-    method public void callSessionMayHandover(int, int);
+    method @Deprecated public void callSessionMayHandover(int, int);
     method public void callSessionMergeComplete(android.telephony.ims.stub.ImsCallSessionImplBase);
     method public void callSessionMergeFailed(android.telephony.ims.ImsReasonInfo);
     method public void callSessionMergeStarted(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile);
@@ -10843,6 +12870,9 @@
     method public void callSessionUpdateReceived(android.telephony.ims.ImsCallProfile);
     method public void callSessionUpdated(android.telephony.ims.ImsCallProfile);
     method public void callSessionUssdMessageReceived(int, String);
+    method public void onHandover(int, int, @Nullable android.telephony.ims.ImsReasonInfo);
+    method public void onHandoverFailed(int, int, @NonNull android.telephony.ims.ImsReasonInfo);
+    method public void onMayHandover(int, int);
   }
 
   public final class ImsConferenceState implements android.os.Parcelable {
@@ -10874,10 +12904,6 @@
     ctor public ImsException(@Nullable String);
     ctor public ImsException(@Nullable String, int);
     ctor public ImsException(@Nullable String, int, @Nullable Throwable);
-    method public int getCode();
-    field public static final int CODE_ERROR_SERVICE_UNAVAILABLE = 1; // 0x1
-    field public static final int CODE_ERROR_UNSPECIFIED = 0; // 0x0
-    field public static final int CODE_ERROR_UNSUPPORTED_OPERATION = 2; // 0x2
   }
 
   public final class ImsExternalCallState implements android.os.Parcelable {
@@ -10899,26 +12925,20 @@
   public class ImsManager {
     method @NonNull public android.telephony.ims.ImsMmTelManager getImsMmTelManager(int);
     method @NonNull public android.telephony.ims.ImsRcsManager getImsRcsManager(int);
+    field public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION = "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION";
+    field public static final String ACTION_WFC_IMS_REGISTRATION_ERROR = "android.telephony.ims.action.WFC_IMS_REGISTRATION_ERROR";
+    field public static final String EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE = "android.telephony.ims.extra.WFC_REGISTRATION_FAILURE_MESSAGE";
+    field public static final String EXTRA_WFC_REGISTRATION_FAILURE_TITLE = "android.telephony.ims.extra.WFC_REGISTRATION_FAILURE_TITLE";
   }
 
   public class ImsMmTelManager implements android.telephony.ims.RegistrationManager {
-    method @NonNull public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getFeatureState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>) throws android.telephony.ims.ImsException;
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getRegistrationState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getRegistrationTransportType(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoWiFiModeSetting();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoWiFiRoamingModeSetting();
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAdvancedCallingSettingEnabled();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAvailable(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isCapable(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void isSupported(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>) throws android.telephony.ims.ImsException;
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isTtyOverVolteEnabled();
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isVoWiFiRoamingSettingEnabled();
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isVoWiFiSettingEnabled();
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isVtSettingEnabled();
     method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerImsRegistrationCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.RegistrationCallback) throws android.telephony.ims.ImsException;
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerImsRegistrationCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RegistrationManager.RegistrationCallback) throws android.telephony.ims.ImsException;
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerMmTelCapabilityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.CapabilityCallback) throws android.telephony.ims.ImsException;
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setAdvancedCallingSettingEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setRttCapabilitySetting(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setVoWiFiModeSetting(int);
@@ -10928,16 +12948,6 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setVoWiFiSettingEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setVtSettingEnabled(boolean);
     method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterImsRegistrationCallback(@NonNull android.telephony.ims.ImsMmTelManager.RegistrationCallback);
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterImsRegistrationCallback(@NonNull android.telephony.ims.RegistrationManager.RegistrationCallback);
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterMmTelCapabilityCallback(@NonNull android.telephony.ims.ImsMmTelManager.CapabilityCallback);
-    field public static final int WIFI_MODE_CELLULAR_PREFERRED = 1; // 0x1
-    field public static final int WIFI_MODE_WIFI_ONLY = 0; // 0x0
-    field public static final int WIFI_MODE_WIFI_PREFERRED = 2; // 0x2
-  }
-
-  public static class ImsMmTelManager.CapabilityCallback {
-    ctor public ImsMmTelManager.CapabilityCallback();
-    method public void onCapabilitiesStatusChanged(@NonNull android.telephony.ims.feature.MmTelFeature.MmTelCapabilities);
   }
 
   @Deprecated public static class ImsMmTelManager.RegistrationCallback extends android.telephony.ims.RegistrationManager.RegistrationCallback {
@@ -11193,17 +13203,90 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public int getProvisioningIntValue(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean getProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int);
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public String getProvisioningStringValue(int);
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean getRcsProvisioningStatusForCapability(int);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback) throws android.telephony.ims.ImsException;
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback);
+    field public static final int KEY_1X_EPDG_TIMER_SEC = 64; // 0x40
+    field public static final int KEY_1X_THRESHOLD = 59; // 0x3b
+    field public static final int KEY_AMR_BANDWIDTH_EFFICIENT_PAYLOAD_TYPE = 50; // 0x32
+    field public static final int KEY_AMR_CODEC_MODE_SET_VALUES = 0; // 0x0
+    field public static final int KEY_AMR_DEFAULT_ENCODING_MODE = 53; // 0x35
+    field public static final int KEY_AMR_OCTET_ALIGNED_PAYLOAD_TYPE = 49; // 0x31
+    field public static final int KEY_AMR_WB_BANDWIDTH_EFFICIENT_PAYLOAD_TYPE = 48; // 0x30
+    field public static final int KEY_AMR_WB_CODEC_MODE_SET_VALUES = 1; // 0x1
+    field public static final int KEY_AMR_WB_OCTET_ALIGNED_PAYLOAD_TYPE = 47; // 0x2f
+    field public static final int KEY_DTMF_NB_PAYLOAD_TYPE = 52; // 0x34
+    field public static final int KEY_DTMF_WB_PAYLOAD_TYPE = 51; // 0x33
+    field public static final int KEY_EAB_PROVISIONING_STATUS = 25; // 0x19
+    field public static final int KEY_ENABLE_SILENT_REDIAL = 6; // 0x6
+    field public static final int KEY_LOCAL_BREAKOUT_PCSCF_ADDRESS = 31; // 0x1f
+    field public static final int KEY_LTE_EPDG_TIMER_SEC = 62; // 0x3e
+    field public static final int KEY_LTE_THRESHOLD_1 = 56; // 0x38
+    field public static final int KEY_LTE_THRESHOLD_2 = 57; // 0x39
+    field public static final int KEY_LTE_THRESHOLD_3 = 58; // 0x3a
+    field public static final int KEY_MINIMUM_SIP_SESSION_EXPIRATION_TIMER_SEC = 3; // 0x3
+    field public static final int KEY_MOBILE_DATA_ENABLED = 29; // 0x1d
+    field public static final int KEY_MULTIENDPOINT_ENABLED = 65; // 0x41
+    field public static final int KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC = 19; // 0x13
+    field public static final int KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC = 18; // 0x12
+    field public static final int KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC = 20; // 0x14
+    field public static final int KEY_RCS_CAPABILITY_DISCOVERY_ENABLED = 17; // 0x11
+    field public static final int KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC = 23; // 0x17
+    field public static final int KEY_RCS_MAX_NUM_ENTRIES_IN_RCL = 22; // 0x16
+    field public static final int KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS = 21; // 0x15
+    field public static final int KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC = 16; // 0x10
+    field public static final int KEY_RCS_PUBLISH_TIMER_SEC = 15; // 0xf
+    field public static final int KEY_REGISTRATION_DOMAIN_NAME = 12; // 0xc
+    field public static final int KEY_REGISTRATION_RETRY_BASE_TIME_SEC = 33; // 0x21
+    field public static final int KEY_REGISTRATION_RETRY_MAX_TIME_SEC = 34; // 0x22
+    field public static final int KEY_RTP_SPEECH_END_PORT = 36; // 0x24
+    field public static final int KEY_RTP_SPEECH_START_PORT = 35; // 0x23
+    field public static final int KEY_RTT_ENABLED = 66; // 0x42
+    field public static final int KEY_SIP_ACK_RECEIPT_WAIT_TIME_MS = 43; // 0x2b
+    field public static final int KEY_SIP_ACK_RETRANSMIT_WAIT_TIME_MS = 44; // 0x2c
+    field public static final int KEY_SIP_INVITE_ACK_WAIT_TIME_MS = 38; // 0x26
+    field public static final int KEY_SIP_INVITE_CANCELLATION_TIMER_MS = 4; // 0x4
+    field public static final int KEY_SIP_INVITE_REQUEST_TRANSMIT_INTERVAL_MS = 37; // 0x25
+    field public static final int KEY_SIP_INVITE_RESPONSE_RETRANSMIT_INTERVAL_MS = 42; // 0x2a
+    field public static final int KEY_SIP_INVITE_RESPONSE_RETRANSMIT_WAIT_TIME_MS = 39; // 0x27
+    field public static final int KEY_SIP_KEEP_ALIVE_ENABLED = 32; // 0x20
+    field public static final int KEY_SIP_NON_INVITE_REQUEST_RETRANSMISSION_WAIT_TIME_MS = 45; // 0x2d
+    field public static final int KEY_SIP_NON_INVITE_REQUEST_RETRANSMIT_INTERVAL_MS = 40; // 0x28
+    field public static final int KEY_SIP_NON_INVITE_RESPONSE_RETRANSMISSION_WAIT_TIME_MS = 46; // 0x2e
+    field public static final int KEY_SIP_NON_INVITE_TRANSACTION_TIMEOUT_TIMER_MS = 41; // 0x29
+    field public static final int KEY_SIP_SESSION_TIMER_SEC = 2; // 0x2
+    field public static final int KEY_SMS_FORMAT = 13; // 0xd
+    field public static final int KEY_SMS_OVER_IP_ENABLED = 14; // 0xe
+    field public static final int KEY_SMS_PUBLIC_SERVICE_IDENTITY = 54; // 0x36
+    field public static final int KEY_T1_TIMER_VALUE_MS = 7; // 0x7
+    field public static final int KEY_T2_TIMER_VALUE_MS = 8; // 0x8
+    field public static final int KEY_TF_TIMER_VALUE_MS = 9; // 0x9
+    field public static final int KEY_TRANSITION_TO_LTE_DELAY_MS = 5; // 0x5
+    field public static final int KEY_USE_GZIP_FOR_LIST_SUBSCRIPTION = 24; // 0x18
+    field public static final int KEY_VIDEO_QUALITY = 55; // 0x37
+    field public static final int KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE = 28; // 0x1c
     field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b
     field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a
+    field public static final int KEY_VOLTE_PROVISIONING_STATUS = 10; // 0xa
+    field public static final int KEY_VOLTE_USER_OPT_IN_STATUS = 30; // 0x1e
+    field public static final int KEY_VT_PROVISIONING_STATUS = 11; // 0xb
+    field public static final int KEY_WIFI_EPDG_TIMER_SEC = 63; // 0x3f
+    field public static final int KEY_WIFI_THRESHOLD_A = 60; // 0x3c
+    field public static final int KEY_WIFI_THRESHOLD_B = 61; // 0x3d
+    field public static final int PROVISIONING_RESULT_UNKNOWN = -1; // 0xffffffff
     field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0
     field public static final int PROVISIONING_VALUE_ENABLED = 1; // 0x1
+    field public static final int SMS_FORMAT_3GPP = 1; // 0x1
+    field public static final int SMS_FORMAT_3GPP2 = 0; // 0x0
     field public static final String STRING_QUERY_RESULT_ERROR_GENERIC = "STRING_QUERY_RESULT_ERROR_GENERIC";
     field public static final String STRING_QUERY_RESULT_ERROR_NOT_READY = "STRING_QUERY_RESULT_ERROR_NOT_READY";
+    field public static final int VIDEO_QUALITY_HIGH = 1; // 0x1
+    field public static final int VIDEO_QUALITY_LOW = 0; // 0x0
   }
 
   public static class ProvisioningManager.Callback {
@@ -11212,22 +13295,52 @@
     method public void onProvisioningStringChanged(int, @NonNull String);
   }
 
-  public interface RegistrationManager {
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getRegistrationState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getRegistrationTransportType(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerImsRegistrationCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RegistrationManager.RegistrationCallback) throws android.telephony.ims.ImsException;
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterImsRegistrationCallback(@NonNull android.telephony.ims.RegistrationManager.RegistrationCallback);
-    field public static final int REGISTRATION_STATE_NOT_REGISTERED = 0; // 0x0
-    field public static final int REGISTRATION_STATE_REGISTERED = 2; // 0x2
-    field public static final int REGISTRATION_STATE_REGISTERING = 1; // 0x1
+  public final class RcsContactUceCapability implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<java.lang.String> getCapableExtensionTags();
+    method @NonNull public android.net.Uri getContactUri();
+    method @Nullable public android.net.Uri getServiceUri(int);
+    method public boolean isCapable(int);
+    method public boolean isCapable(@NonNull String);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int CAPABILITY_CALL_COMPOSER = 4194304; // 0x400000
+    field public static final int CAPABILITY_CHAT_BOT = 67108864; // 0x4000000
+    field public static final int CAPABILITY_CHAT_BOT_ROLE = 134217728; // 0x8000000
+    field public static final int CAPABILITY_CHAT_SESSION = 2; // 0x2
+    field public static final int CAPABILITY_CHAT_SESSION_STORE_FORWARD = 4; // 0x4
+    field public static final int CAPABILITY_CHAT_STANDALONE = 1; // 0x1
+    field public static final int CAPABILITY_DISCOVERY_VIA_PRESENCE = 4096; // 0x1000
+    field public static final int CAPABILITY_FILE_TRANSFER = 8; // 0x8
+    field public static final int CAPABILITY_FILE_TRANSFER_HTTP = 64; // 0x40
+    field public static final int CAPABILITY_FILE_TRANSFER_SMS = 128; // 0x80
+    field public static final int CAPABILITY_FILE_TRANSFER_STORE_FORWARD = 32; // 0x20
+    field public static final int CAPABILITY_FILE_TRANSFER_THUMBNAIL = 16; // 0x10
+    field public static final int CAPABILITY_GEOLOCATION_PULL = 131072; // 0x20000
+    field public static final int CAPABILITY_GEOLOCATION_PULL_FILE_TRANSFER = 262144; // 0x40000
+    field public static final int CAPABILITY_GEOLOCATION_PUSH = 32768; // 0x8000
+    field public static final int CAPABILITY_GEOLOCATION_PUSH_SMS = 65536; // 0x10000
+    field public static final int CAPABILITY_IMAGE_SHARE = 256; // 0x100
+    field public static final int CAPABILITY_IP_VIDEO_CALL = 16384; // 0x4000
+    field public static final int CAPABILITY_IP_VOICE_CALL = 8192; // 0x2000
+    field public static final int CAPABILITY_PLUG_IN = 268435456; // 0x10000000
+    field public static final int CAPABILITY_POST_CALL = 8388608; // 0x800000
+    field public static final int CAPABILITY_RCS_VIDEO_CALL = 1048576; // 0x100000
+    field public static final int CAPABILITY_RCS_VIDEO_ONLY_CALL = 2097152; // 0x200000
+    field public static final int CAPABILITY_RCS_VOICE_CALL = 524288; // 0x80000
+    field public static final int CAPABILITY_SHARED_MAP = 16777216; // 0x1000000
+    field public static final int CAPABILITY_SHARED_SKETCH = 33554432; // 0x2000000
+    field public static final int CAPABILITY_SOCIAL_PRESENCE = 2048; // 0x800
+    field public static final int CAPABILITY_VIDEO_SHARE = 1024; // 0x400
+    field public static final int CAPABILITY_VIDEO_SHARE_DURING_CS_CALL = 512; // 0x200
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.RcsContactUceCapability> CREATOR;
   }
 
-  public static class RegistrationManager.RegistrationCallback {
-    ctor public RegistrationManager.RegistrationCallback();
-    method public void onRegistered(int);
-    method public void onRegistering(int);
-    method public void onTechnologyChangeFailed(int, @Nullable android.telephony.ims.ImsReasonInfo);
-    method public void onUnregistered(@Nullable android.telephony.ims.ImsReasonInfo);
+  public static class RcsContactUceCapability.Builder {
+    ctor public RcsContactUceCapability.Builder(@NonNull android.net.Uri);
+    method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(int, @NonNull android.net.Uri);
+    method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(int);
+    method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(@NonNull String);
+    method @NonNull public android.telephony.ims.RcsContactUceCapability build();
   }
 
 }
@@ -11253,6 +13366,7 @@
   public abstract class ImsFeature {
     ctor public ImsFeature();
     method public abstract void changeEnabledCapabilities(android.telephony.ims.feature.CapabilityChangeRequest, android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy);
+    method public int getFeatureState();
     method public final int getSlotIndex();
     method public abstract void onFeatureReady();
     method public abstract void onFeatureRemoved();
@@ -11291,7 +13405,7 @@
     method public void onFeatureReady();
     method public void onFeatureRemoved();
     method public boolean queryCapabilityConfiguration(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int);
-    method public final android.telephony.ims.feature.MmTelFeature.MmTelCapabilities queryCapabilityStatus();
+    method @NonNull public final android.telephony.ims.feature.MmTelFeature.MmTelCapabilities queryCapabilityStatus();
     method public void setUiTtyMode(int, @Nullable android.os.Message);
     method @android.telephony.ims.feature.MmTelFeature.ProcessCallResult public int shouldProcessCall(@NonNull String[]);
     field public static final String EXTRA_IS_UNKNOWN_CALL = "android.telephony.ims.feature.extra.IS_UNKNOWN_CALL";
@@ -11307,10 +13421,6 @@
     method public final void addCapabilities(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int);
     method public final boolean isCapable(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int);
     method public final void removeCapabilities(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int);
-    field public static final int CAPABILITY_TYPE_SMS = 8; // 0x8
-    field public static final int CAPABILITY_TYPE_UT = 4; // 0x4
-    field public static final int CAPABILITY_TYPE_VIDEO = 2; // 0x2
-    field public static final int CAPABILITY_TYPE_VOICE = 1; // 0x1
   }
 
   @IntDef(flag=true, value={android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE, android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO, android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT, android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_SMS}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MmTelFeature.MmTelCapabilities.MmTelCapability {
@@ -11401,6 +13511,7 @@
     method public String getConfigString(int);
     method public final void notifyProvisionedValueChanged(int, int);
     method public final void notifyProvisionedValueChanged(int, String);
+    method public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean);
     method public int setConfig(int, int);
     method public int setConfig(int, String);
     field public static final int CONFIG_RESULT_FAILED = 1; // 0x1
@@ -11493,12 +13604,24 @@
     method public int transact(android.os.Bundle);
     method public int updateCallBarring(int, int, String[]);
     method public int updateCallBarringForServiceClass(int, int, String[], int);
+    method public int updateCallBarringWithPassword(int, int, @Nullable String[], int, @NonNull String);
     method public int updateCallForward(int, int, String, int, int);
     method public int updateCallWaiting(boolean, int);
     method public int updateClip(boolean);
     method public int updateClir(int);
     method public int updateColp(boolean);
     method public int updateColr(int);
+    field public static final int CALL_BARRING_ALL = 7; // 0x7
+    field public static final int CALL_BARRING_ALL_INCOMING = 1; // 0x1
+    field public static final int CALL_BARRING_ALL_OUTGOING = 2; // 0x2
+    field public static final int CALL_BARRING_ANONYMOUS_INCOMING = 6; // 0x6
+    field public static final int CALL_BARRING_INCOMING_ALL_SERVICES = 9; // 0x9
+    field public static final int CALL_BARRING_OUTGOING_ALL_SERVICES = 8; // 0x8
+    field public static final int CALL_BARRING_OUTGOING_INTL = 3; // 0x3
+    field public static final int CALL_BARRING_OUTGOING_INTL_EXCL_HOME = 4; // 0x4
+    field public static final int CALL_BARRING_SPECIFIC_INCOMING_CALLS = 10; // 0xa
+    field public static final int CALL_BLOCKING_INCOMING_WHEN_ROAMING = 5; // 0x5
+    field public static final int INVALID_RESULT = -1; // 0xffffffff
   }
 
 }
@@ -11616,7 +13739,28 @@
     method public int getUid();
   }
 
+  public final class StatsEvent {
+    method @NonNull public static android.util.StatsEvent.Builder newBuilder();
+  }
+
+  public static final class StatsEvent.Builder {
+    method @NonNull public android.util.StatsEvent.Builder addBooleanAnnotation(byte, boolean);
+    method @NonNull public android.util.StatsEvent.Builder addIntAnnotation(byte, int);
+    method @NonNull public android.util.StatsEvent build();
+    method @NonNull public android.util.StatsEvent.Builder setAtomId(int);
+    method @NonNull public android.util.StatsEvent.Builder usePooledBuffer();
+    method @NonNull public android.util.StatsEvent.Builder writeAttributionChain(@NonNull int[], @NonNull String[]);
+    method @NonNull public android.util.StatsEvent.Builder writeBoolean(boolean);
+    method @NonNull public android.util.StatsEvent.Builder writeByteArray(@NonNull byte[]);
+    method @NonNull public android.util.StatsEvent.Builder writeFloat(float);
+    method @NonNull public android.util.StatsEvent.Builder writeInt(int);
+    method @NonNull public android.util.StatsEvent.Builder writeKeyValuePairs(@Nullable android.util.SparseIntArray, @Nullable android.util.SparseLongArray, @Nullable android.util.SparseArray<java.lang.String>, @Nullable android.util.SparseArray<java.lang.Float>);
+    method @NonNull public android.util.StatsEvent.Builder writeLong(long);
+    method @NonNull public android.util.StatsEvent.Builder writeString(@NonNull String);
+  }
+
   public final class StatsLog {
+    method public static void write(@NonNull android.util.StatsEvent);
     method public static void writeRaw(@NonNull byte[], int);
   }
 
@@ -11649,6 +13793,8 @@
   public final class AccessibilityManager {
     method public int getAccessibilityWindowId(@Nullable android.os.IBinder);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void performAccessibilityShortcut();
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void registerSystemAction(@NonNull android.app.RemoteAction, int);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void unregisterSystemAction(int);
   }
 
 }
@@ -11776,6 +13922,12 @@
     method public void onJsResultComplete(android.webkit.JsResult);
   }
 
+  public interface PacProcessor {
+    method @Nullable public String findProxyForUrl(@NonNull String);
+    method @NonNull public static android.webkit.PacProcessor getInstance();
+    method public boolean setProxyScript(@NonNull String);
+  }
+
   public class SslErrorHandler extends android.os.Handler {
     ctor public SslErrorHandler();
   }
@@ -11910,6 +14062,7 @@
     method public android.webkit.WebViewProvider createWebView(android.webkit.WebView, android.webkit.WebView.PrivateAccess);
     method public android.webkit.CookieManager getCookieManager();
     method public android.webkit.GeolocationPermissions getGeolocationPermissions();
+    method @NonNull public default android.webkit.PacProcessor getPacProcessor();
     method public android.webkit.ServiceWorkerController getServiceWorkerController();
     method public android.webkit.WebViewFactoryProvider.Statics getStatics();
     method @Deprecated public android.webkit.TokenBindingService getTokenBindingService();
@@ -12121,4 +14274,3 @@
   }
 
 }
-
diff --git a/api/system-lint-baseline.txt b/api/system-lint-baseline.txt
index da0aae0..23e1ed7 100644
--- a/api/system-lint-baseline.txt
+++ b/api/system-lint-baseline.txt
@@ -1,33 +1,57 @@
 // Baseline format: 1.0
+AcronymName: android.net.NetworkCapabilities#setSSID(String):
+    Acronyms should not be capitalized in method names: was `setSSID`, should this be `setSsid`?
+
+
 ActionValue: android.location.Location#EXTRA_NO_GPS_LOCATION:
     
 ActionValue: android.net.wifi.WifiManager#ACTION_LINK_CONFIGURATION_CHANGED:
-    Inconsistent action value; expected `android.net.wifi.action.LINK_CONFIGURATION_CHANGED`, was `android.net.wifi.LINK_CONFIGURATION_CHANGED`
+    
 
+// Tethering broadcast action / extras cannot change name for backwards compatibility
+ActionValue: android.net.TetheringManager#ACTION_TETHER_STATE_CHANGED:
+    Inconsistent action value; expected `android.net.action.TETHER_STATE_CHANGED`, was `android.net.conn.TETHER_STATE_CHANGED`
+ActionValue: android.net.TetheringManager#EXTRA_ACTIVE_TETHER:
+    Inconsistent extra value; expected `android.net.extra.ACTIVE_TETHER`, was `tetherArray`
+ActionValue: android.net.TetheringManager#EXTRA_AVAILABLE_TETHER:
+    Inconsistent extra value; expected `android.net.extra.AVAILABLE_TETHER`, was `availableArray`
+ActionValue: android.net.TetheringManager#EXTRA_ERRORED_TETHER:
+    Inconsistent extra value; expected `android.net.extra.ERRORED_TETHER`, was `erroredArray`
 
+ArrayReturn: android.bluetooth.BluetoothCodecStatus#BluetoothCodecStatus(android.bluetooth.BluetoothCodecConfig, android.bluetooth.BluetoothCodecConfig[], android.bluetooth.BluetoothCodecConfig[]) parameter #1:
+    Method parameter should be Collection<BluetoothCodecConfig> (or subclass) instead of raw array; was `android.bluetooth.BluetoothCodecConfig[]`
+ArrayReturn: android.bluetooth.BluetoothCodecStatus#BluetoothCodecStatus(android.bluetooth.BluetoothCodecConfig, android.bluetooth.BluetoothCodecConfig[], android.bluetooth.BluetoothCodecConfig[]) parameter #2:
+    Method parameter should be Collection<BluetoothCodecConfig> (or subclass) instead of raw array; was `android.bluetooth.BluetoothCodecConfig[]`
+ArrayReturn: android.bluetooth.BluetoothCodecStatus#getCodecsLocalCapabilities():
+    Method should return Collection<BluetoothCodecConfig> (or subclass) instead of raw array; was `android.bluetooth.BluetoothCodecConfig[]`
+ArrayReturn: android.bluetooth.BluetoothCodecStatus#getCodecsSelectableCapabilities():
+    Method should return Collection<BluetoothCodecConfig> (or subclass) instead of raw array; was `android.bluetooth.BluetoothCodecConfig[]`
+ArrayReturn: android.bluetooth.BluetoothUuid#containsAnyUuid(android.os.ParcelUuid[], android.os.ParcelUuid[]) parameter #0:
+    Method parameter should be Collection<ParcelUuid> (or subclass) instead of raw array; was `android.os.ParcelUuid[]`
+ArrayReturn: android.bluetooth.BluetoothUuid#containsAnyUuid(android.os.ParcelUuid[], android.os.ParcelUuid[]) parameter #1:
+    Method parameter should be Collection<ParcelUuid> (or subclass) instead of raw array; was `android.os.ParcelUuid[]`
+ArrayReturn: android.media.tv.tuner.Tuner.FilterCallback#onFilterEvent(android.media.tv.tuner.Tuner.Filter, android.media.tv.tuner.filter.FilterEvent[]) parameter #1:
+    Method parameter should be Collection<FilterEvent> (or subclass) instead of raw array; was `android.media.tv.tuner.filter.FilterEvent[]`
+ArrayReturn: android.net.NetworkScoreManager#requestScores(android.net.NetworkKey[]) parameter #0:
+    Method parameter should be Collection<NetworkKey> (or subclass) instead of raw array; was `android.net.NetworkKey[]`
 ArrayReturn: android.view.contentcapture.ViewNode#getAutofillOptions():
     
 
 
 ExecutorRegistration: android.net.wifi.p2p.WifiP2pManager#deletePersistentGroup(android.net.wifi.p2p.WifiP2pManager.Channel, int, android.net.wifi.p2p.WifiP2pManager.ActionListener):
-    Registration methods should have overload that accepts delivery Executor: `deletePersistentGroup`
+    
 ExecutorRegistration: android.net.wifi.p2p.WifiP2pManager#factoryReset(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener):
-    Registration methods should have overload that accepts delivery Executor: `factoryReset`
+    
 ExecutorRegistration: android.net.wifi.p2p.WifiP2pManager#listen(android.net.wifi.p2p.WifiP2pManager.Channel, boolean, android.net.wifi.p2p.WifiP2pManager.ActionListener):
-    Registration methods should have overload that accepts delivery Executor: `listen`
+    
 ExecutorRegistration: android.net.wifi.p2p.WifiP2pManager#requestPersistentGroupInfo(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.PersistentGroupInfoListener):
-    Registration methods should have overload that accepts delivery Executor: `requestPersistentGroupInfo`
+    
 ExecutorRegistration: android.net.wifi.p2p.WifiP2pManager#setDeviceName(android.net.wifi.p2p.WifiP2pManager.Channel, String, android.net.wifi.p2p.WifiP2pManager.ActionListener):
-    Registration methods should have overload that accepts delivery Executor: `setDeviceName`
+    
 ExecutorRegistration: android.net.wifi.p2p.WifiP2pManager#setWfdInfo(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pWfdInfo, android.net.wifi.p2p.WifiP2pManager.ActionListener):
-    Registration methods should have overload that accepts delivery Executor: `setWfdInfo`
+    
 ExecutorRegistration: android.net.wifi.p2p.WifiP2pManager#setWifiP2pChannels(android.net.wifi.p2p.WifiP2pManager.Channel, int, int, android.net.wifi.p2p.WifiP2pManager.ActionListener):
-    Registration methods should have overload that accepts delivery Executor: `setWifiP2pChannels`
-
-HeavyBitSet: android.net.wifi.wificond.NativeScanResult#getCapabilities():
-    Type must not be heavy BitSet (method android.net.wifi.wificond.NativeScanResult.getCapabilities())
-PairedRegistration: android.net.wifi.wificond.WifiCondManager#registerApCallback(String, java.util.concurrent.Executor, android.net.wifi.wificond.WifiCondManager.SoftApCallback):
-    Found registerApCallback but not unregisterApCallback in android.net.wifi.wificond.WifiCondManager
+    
 
 
 GenericException: android.app.prediction.AppPredictor#finalize():
@@ -40,13 +64,22 @@
     
 
 
+HeavyBitSet: android.net.wifi.wificond.NativeScanResult#getCapabilities():
+    
 
 
+IntentBuilderName: android.content.Context#registerReceiverForAllUsers(android.content.BroadcastReceiver, android.content.IntentFilter, String, android.os.Handler):
+    Methods creating an Intent should be named `create<Foo>Intent()`, was `registerReceiverForAllUsers`
+
 
 KotlinKeyword: android.app.Notification#when:
     
 
 
+KotlinOperator: android.telephony.CbGeoUtils.Geometry#contains(android.telephony.CbGeoUtils.LatLng):
+    Method can be invoked as a "in" operator from Kotlin: `contains` (this is usually desirable; just make sure it makes sense for this type of object)
+
+
 MissingNullability: android.hardware.soundtrigger.SoundTrigger.ModuleProperties#toString():
     
 MissingNullability: android.hardware.soundtrigger.SoundTrigger.ModuleProperties#writeToParcel(android.os.Parcel, int) parameter #0:
@@ -70,7 +103,7 @@
 MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent(String, String, android.os.Bundle) parameter #1:
     
 MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent(String, String, android.os.Bundle) parameter #2:
-
+    
 MissingNullability: android.net.wifi.rtt.RangingRequest.Builder#addResponder(android.net.wifi.rtt.ResponderConfig):
     
 MissingNullability: android.printservice.recommendation.RecommendationService#attachBaseContext(android.content.Context) parameter #0:
@@ -157,43 +190,60 @@
     
 
 
-
 MutableBareField: android.net.IpConfiguration#httpProxy:
-    Bare field httpProxy must be marked final, or moved behind accessors if mutable
+    
 MutableBareField: android.net.IpConfiguration#ipAssignment:
-    Bare field ipAssignment must be marked final, or moved behind accessors if mutable
+    
 MutableBareField: android.net.IpConfiguration#proxySettings:
-    Bare field proxySettings must be marked final, or moved behind accessors if mutable
+    
 MutableBareField: android.net.IpConfiguration#staticIpConfiguration:
-    Bare field staticIpConfiguration must be marked final, or moved behind accessors if mutable
+    
 MutableBareField: android.net.wifi.WifiConfiguration#allowAutojoin:
     
 MutableBareField: android.net.wifi.WifiConfiguration#apBand:
-    Bare field apBand must be marked final, or moved behind accessors if mutable
+    
 MutableBareField: android.net.wifi.WifiConfiguration#carrierId:
     
 MutableBareField: android.net.wifi.WifiConfiguration#fromWifiNetworkSpecifier:
-    Bare field fromWifiNetworkSpecifier must be marked final, or moved behind accessors if mutable
+    
 MutableBareField: android.net.wifi.WifiConfiguration#fromWifiNetworkSuggestion:
-    Bare field fromWifiNetworkSuggestion must be marked final, or moved behind accessors if mutable
+    
 MutableBareField: android.net.wifi.WifiConfiguration#macRandomizationSetting:
-    Bare field macRandomizationSetting must be marked final, or moved behind accessors if mutable
+    
 MutableBareField: android.net.wifi.WifiConfiguration#meteredOverride:
-    Bare field meteredOverride must be marked final, or moved behind accessors if mutable
+    
 MutableBareField: android.net.wifi.WifiConfiguration#requirePMF:
-    Bare field requirePMF must be marked final, or moved behind accessors if mutable
+    
 MutableBareField: android.net.wifi.WifiConfiguration#saePasswordId:
-    Bare field saePasswordId must be marked final, or moved behind accessors if mutable
+    
 MutableBareField: android.net.wifi.WifiConfiguration#shared:
-    Bare field shared must be marked final, or moved behind accessors if mutable
+    
 MutableBareField: android.net.wifi.WifiScanner.ScanSettings#type:
-    Bare field type must be marked final, or moved behind accessors if mutable
+    
 
 
 NoClone: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #0:
     
 
 
+NotCloseable: android.bluetooth.BluetoothA2dpSink:
+    Classes that release resources (finalize()) should implement AutoClosable and CloseGuard: class android.bluetooth.BluetoothA2dpSink
+NotCloseable: android.bluetooth.BluetoothMap:
+    Classes that release resources (finalize()) should implement AutoClosable and CloseGuard: class android.bluetooth.BluetoothMap
+NotCloseable: android.bluetooth.BluetoothPan:
+    Classes that release resources (finalize()) should implement AutoClosable and CloseGuard: class android.bluetooth.BluetoothPan
+NotCloseable: android.bluetooth.BluetoothPbap:
+    Classes that release resources (finalize()) should implement AutoClosable and CloseGuard: class android.bluetooth.BluetoothPbap
+
+
+OnNameExpected: android.content.ContentProvider#checkUriPermission(android.net.Uri, int, int):
+    If implemented by developer, should follow the on<Something> style; otherwise consider marking final
+
+
+PairedRegistration: android.net.wifi.wificond.WifiCondManager#registerApCallback(String, java.util.concurrent.Executor, android.net.wifi.wificond.WifiCondManager.SoftApCallback):
+    
+
+
 ProtectedMember: android.printservice.recommendation.RecommendationService#attachBaseContext(android.content.Context):
     
 ProtectedMember: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]):
@@ -201,6 +251,7 @@
 ProtectedMember: android.service.notification.NotificationAssistantService#attachBaseContext(android.content.Context):
     
 
+
 SamShouldBeLast: android.accounts.AccountManager#addAccount(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
     
 SamShouldBeLast: android.accounts.AccountManager#addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean):
@@ -246,9 +297,11 @@
 SamShouldBeLast: android.app.AlarmManager#setWindow(int, long, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler):
     
 SamShouldBeLast: android.app.WallpaperInfo#dump(android.util.Printer, String):
-
+    
+SamShouldBeLast: android.app.WallpaperManager#addOnColorsChangedListener(android.app.WallpaperManager.OnColorsChangedListener, android.os.Handler):
+    SAM-compatible parameters (such as parameter 1, "listener", in android.app.WallpaperManager.addOnColorsChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.app.admin.DevicePolicyManager#installSystemUpdate(android.content.ComponentName, android.net.Uri, java.util.concurrent.Executor, android.app.admin.DevicePolicyManager.InstallSystemUpdateCallback):
-
+    
 SamShouldBeLast: android.content.Context#bindIsolatedService(android.content.Intent, int, String, java.util.concurrent.Executor, android.content.ServiceConnection):
     
 SamShouldBeLast: android.content.Context#bindService(android.content.Intent, int, java.util.concurrent.Executor, android.content.ServiceConnection):
@@ -279,12 +332,20 @@
     
 SamShouldBeLast: android.location.LocationManager#registerGnssStatusCallback(java.util.concurrent.Executor, android.location.GnssStatus.Callback):
     
+SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(String, long, float, android.location.LocationListener, android.os.Looper):
+    SAM-compatible parameters (such as parameter 4, "listener", in android.location.LocationManager.requestLocationUpdates) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(String, long, float, java.util.concurrent.Executor, android.location.LocationListener):
     
 SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(android.location.LocationRequest, java.util.concurrent.Executor, android.location.LocationListener):
     
+SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(long, float, android.location.Criteria, android.location.LocationListener, android.os.Looper):
+    SAM-compatible parameters (such as parameter 4, "listener", in android.location.LocationManager.requestLocationUpdates) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(long, float, android.location.Criteria, java.util.concurrent.Executor, android.location.LocationListener):
     
+SamShouldBeLast: android.location.LocationManager#requestSingleUpdate(String, android.location.LocationListener, android.os.Looper):
+    SAM-compatible parameters (such as parameter 2, "listener", in android.location.LocationManager.requestSingleUpdate) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.location.LocationManager#requestSingleUpdate(android.location.Criteria, android.location.LocationListener, android.os.Looper):
+    SAM-compatible parameters (such as parameter 2, "listener", in android.location.LocationManager.requestSingleUpdate) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.media.AudioFocusRequest.Builder#setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener, android.os.Handler):
     
 SamShouldBeLast: android.media.AudioManager#requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, int, int):
@@ -380,7 +441,7 @@
 SamShouldBeLast: android.telephony.TelephonyManager#setPreferredOpportunisticDataSubscription(int, boolean, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>):
     
 SamShouldBeLast: android.telephony.TelephonyManager#updateAvailableNetworks(java.util.List<android.telephony.AvailableNetworkInfo>, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>):
-
+    
 SamShouldBeLast: android.view.View#postDelayed(Runnable, long):
     
 SamShouldBeLast: android.view.View#postOnAnimationDelayed(Runnable, long):
@@ -445,3 +506,11 @@
     
 ServiceName: android.provider.DeviceConfig#NAMESPACE_PACKAGE_MANAGER_SERVICE:
     
+
+
+UserHandle: android.companion.CompanionDeviceManager#isDeviceAssociated(String, android.net.MacAddress, android.os.UserHandle):
+    When a method overload is needed to target a specific UserHandle, callers should be directed to use Context.createPackageContextAsUser() and re-obtain the relevant Manager, and no new API should be added
+
+
+UserHandleName: android.telephony.CellBroadcastIntents#sendOrderedBroadcastForBackgroundReceivers(android.content.Context, android.os.UserHandle, android.content.Intent, String, String, android.content.BroadcastReceiver, android.os.Handler, int, String, android.os.Bundle):
+    Method taking UserHandle should be named `doFooAsUser` or `queryFooForUser`, was `sendOrderedBroadcastForBackgroundReceivers`
diff --git a/api/test-current.txt b/api/test-current.txt
index 9967942..e2407d6 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -10,6 +10,7 @@
     field public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE";
     field public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA";
     field public static final String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS";
+    field public static final String CONTROL_DEVICE_LIGHTS = "android.permission.CONTROL_DEVICE_LIGHTS";
     field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
     field public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
     field public static final String MANAGE_CRATES = "android.permission.MANAGE_CRATES";
@@ -19,7 +20,7 @@
     field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
     field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS";
     field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG";
-    field public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE";
+    field @Deprecated public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE";
     field public static final String WRITE_OBB = "android.permission.WRITE_OBB";
   }
 
@@ -34,6 +35,7 @@
   public static final class R.string {
     field public static final int config_defaultAssistant = 17039393; // 0x1040021
     field public static final int config_defaultDialer = 17039395; // 0x1040023
+    field public static final int config_systemGallery = 17039402; // 0x104002a
   }
 
 }
@@ -69,11 +71,15 @@
     method public long getTotalRam();
     method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getUidImportance(int);
     method public static boolean isHighEndGfx();
+    method @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) public void killProcessesWhenImperceptible(@NonNull int[], @NonNull String);
     method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void removeOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener);
     method public static void resumeAppSwitches() throws android.os.RemoteException;
     method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int);
-    field public static final int PROCESS_CAPABILITY_ALL = 1; // 0x1
+    method @RequiresPermission("android.permission.MANAGE_USERS") public boolean switchUser(@NonNull android.os.UserHandle);
+    field public static final int PROCESS_CAPABILITY_ALL = 7; // 0x7
+    field public static final int PROCESS_CAPABILITY_FOREGROUND_CAMERA = 2; // 0x2
     field public static final int PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1; // 0x1
+    field public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 4; // 0x4
     field public static final int PROCESS_CAPABILITY_NONE = 0; // 0x0
   }
 
@@ -435,8 +441,11 @@
   }
 
   public class StatusBarManager {
+    method public void collapsePanels();
+    method public void expandNotificationsPanel();
     method @NonNull @RequiresPermission(android.Manifest.permission.STATUS_BAR) public android.app.StatusBarManager.DisableInfo getDisableInfo();
     method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSetup(boolean);
+    method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSimNetworkLock(boolean);
   }
 
   public static final class StatusBarManager.DisableInfo {
@@ -467,7 +476,10 @@
   }
 
   public class WallpaperManager {
+    method @Nullable public android.graphics.Bitmap getBitmap();
     method @RequiresPermission("android.permission.SET_WALLPAPER_COMPONENT") public boolean setWallpaperComponent(android.content.ComponentName);
+    method public boolean shouldEnableWideColorGamut();
+    method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public boolean wallpaperSupportsWcg(int);
   }
 
   public class WindowConfiguration implements java.lang.Comparable<android.app.WindowConfiguration> android.os.Parcelable {
@@ -693,7 +705,7 @@
 package android.companion {
 
   public final class CompanionDeviceManager {
-    method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean isDeviceAssociated(@NonNull String, @NonNull android.net.MacAddress, @NonNull android.os.UserHandle);
+    method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean isDeviceAssociatedForWifiConnection(@NonNull String, @NonNull android.net.MacAddress, @NonNull android.os.UserHandle);
   }
 
 }
@@ -731,6 +743,10 @@
     field @Nullable public final android.util.ArraySet<android.content.ComponentName> whitelistedComponents;
   }
 
+  public abstract class ContentProvider implements android.content.ComponentCallbacks2 {
+    method @NonNull public static android.os.UserHandle getUserHandleFromUri(@NonNull android.net.Uri);
+  }
+
   public class ContentProviderClient implements java.lang.AutoCloseable {
     method @RequiresPermission(android.Manifest.permission.REMOVE_TASKS) public void setDetectNotResponding(long);
   }
@@ -743,16 +759,17 @@
     method @NonNull public android.content.Context createContextAsUser(@NonNull android.os.UserHandle, int);
     method @NonNull public android.content.Context createPackageContextAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public java.io.File getCrateDir(@NonNull String);
-    method public abstract android.view.Display getDisplay();
     method public abstract int getDisplayId();
     method public android.os.UserHandle getUser();
     method public int getUserId();
     method public void setAutofillOptions(@Nullable android.content.AutofillOptions);
     method public void setContentCaptureOptions(@Nullable android.content.ContentCaptureOptions);
+    method @RequiresPermission("android.permission.INTERACT_ACROSS_USERS") public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle);
     field public static final String BLOB_STORE_SERVICE = "blob_store";
     field public static final String BUGREPORT_SERVICE = "bugreport";
     field public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture";
     field public static final String DEVICE_IDLE_CONTROLLER = "deviceidle";
+    field public static final String NETWORK_STACK_SERVICE = "network_stack";
     field public static final String PERMISSION_SERVICE = "permission";
     field public static final String POWER_WHITELIST_MANAGER = "power_whitelist";
     field public static final String ROLLBACK_SERVICE = "rollback";
@@ -762,7 +779,6 @@
   }
 
   public class ContextWrapper extends android.content.Context {
-    method public android.view.Display getDisplay();
     method public int getDisplayId();
   }
 
@@ -1017,7 +1033,7 @@
 package android.hardware.camera2 {
 
   public abstract class CameraDevice implements java.lang.AutoCloseable {
-    method public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+    method @Deprecated public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     field public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED = 1; // 0x1
     field public static final int SESSION_OPERATION_MODE_NORMAL = 0; // 0x0
     field public static final int SESSION_OPERATION_MODE_VENDOR_START = 32768; // 0x8000
@@ -1109,11 +1125,55 @@
     method @RequiresPermission(android.Manifest.permission.BRIGHTNESS_SLIDER_USAGE) public java.util.List<android.hardware.display.BrightnessChangeEvent> getBrightnessEvents();
     method @Nullable @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public android.hardware.display.BrightnessConfiguration getDefaultBrightnessConfiguration();
     method public android.graphics.Point getStableDisplaySize();
+    method public boolean isMinimalPostProcessingRequested(int);
     method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration);
   }
 
 }
 
+package android.hardware.lights {
+
+  public final class Light implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getId();
+    method public int getOrdinal();
+    method public int getType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.lights.Light> CREATOR;
+  }
+
+  public final class LightState implements android.os.Parcelable {
+    ctor public LightState(@ColorInt int);
+    method public int describeContents();
+    method @ColorInt public int getColor();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.lights.LightState> CREATOR;
+  }
+
+  public final class LightsManager {
+    method @NonNull @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS) public android.hardware.lights.LightState getLightState(@NonNull android.hardware.lights.Light);
+    method @NonNull @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS) public java.util.List<android.hardware.lights.Light> getLights();
+    method @NonNull @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS) public android.hardware.lights.LightsManager.LightsSession openSession();
+    field public static final int LIGHT_TYPE_MICROPHONE = 8; // 0x8
+  }
+
+  public final class LightsManager.LightsSession implements java.lang.AutoCloseable {
+    method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS) public void close();
+    method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS) public void setLights(@NonNull android.hardware.lights.LightsRequest);
+  }
+
+  public final class LightsRequest {
+  }
+
+  public static final class LightsRequest.Builder {
+    ctor public LightsRequest.Builder();
+    method @NonNull public android.hardware.lights.LightsRequest build();
+    method @NonNull public android.hardware.lights.LightsRequest.Builder clearLight(@NonNull android.hardware.lights.Light);
+    method @NonNull public android.hardware.lights.LightsRequest.Builder setLight(@NonNull android.hardware.lights.Light, @NonNull android.hardware.lights.LightState);
+  }
+
+}
+
 package android.location {
 
   public final class GnssClock implements android.os.Parcelable {
@@ -1381,6 +1441,7 @@
     field public static final int RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET = 2; // 0x2
     field public static final int RULE_MATCH_ATTRIBUTE_USAGE = 1; // 0x1
     field public static final int RULE_MATCH_UID = 4; // 0x4
+    field public static final int RULE_MATCH_USERID = 8; // 0x8
   }
 
   public static class AudioMixingRule.Builder {
@@ -1401,9 +1462,11 @@
     method public int getFocusDuckingBehavior();
     method public int getStatus();
     method public boolean removeUidDeviceAffinity(int);
+    method public boolean removeUserIdDeviceAffinity(int);
     method public int setFocusDuckingBehavior(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
     method public void setRegistration(String);
     method public boolean setUidDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>);
+    method public boolean setUserIdDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>);
     method public String toLogFriendlyString();
     field public static final int FOCUS_POLICY_DUCKING_DEFAULT = 0; // 0x0
     field public static final int FOCUS_POLICY_DUCKING_IN_APP = 0; // 0x0
@@ -1493,12 +1556,40 @@
 
   public class CaptivePortal implements android.os.Parcelable {
     method public void logEvent(int, @NonNull String);
+    method public void reevaluateNetwork();
     method public void useNetwork();
+    field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64
     field public static final int APP_RETURN_DISMISSED = 0; // 0x0
     field public static final int APP_RETURN_UNWANTED = 1; // 0x1
     field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2
   }
 
+  public final class CaptivePortalData implements android.os.Parcelable {
+    method public int describeContents();
+    method public long getByteLimit();
+    method public long getExpiryTimeMillis();
+    method public long getRefreshTimeMillis();
+    method @Nullable public android.net.Uri getUserPortalUrl();
+    method @Nullable public android.net.Uri getVenueInfoUrl();
+    method public boolean isCaptive();
+    method public boolean isSessionExtendable();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.CaptivePortalData> CREATOR;
+  }
+
+  public static class CaptivePortalData.Builder {
+    ctor public CaptivePortalData.Builder();
+    ctor public CaptivePortalData.Builder(@Nullable android.net.CaptivePortalData);
+    method @NonNull public android.net.CaptivePortalData build();
+    method @NonNull public android.net.CaptivePortalData.Builder setBytesRemaining(long);
+    method @NonNull public android.net.CaptivePortalData.Builder setCaptive(boolean);
+    method @NonNull public android.net.CaptivePortalData.Builder setExpiryTime(long);
+    method @NonNull public android.net.CaptivePortalData.Builder setRefreshTime(long);
+    method @NonNull public android.net.CaptivePortalData.Builder setSessionExtendable(boolean);
+    method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri);
+    method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri);
+  }
+
   public class ConnectivityManager {
     method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle);
     field public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC";
@@ -1529,6 +1620,8 @@
     ctor public LinkProperties(@Nullable android.net.LinkProperties);
     method public boolean addDnsServer(@NonNull java.net.InetAddress);
     method public boolean addLinkAddress(@NonNull android.net.LinkAddress);
+    method @Nullable public android.net.Uri getCaptivePortalApiUrl();
+    method @Nullable public android.net.CaptivePortalData getCaptivePortalData();
     method @NonNull public java.util.List<java.net.InetAddress> getPcscfServers();
     method @Nullable public String getTcpBufferSizes();
     method @NonNull public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers();
@@ -1539,9 +1632,12 @@
     method public boolean isIpv6Provisioned();
     method public boolean isProvisioned();
     method public boolean isReachable(@NonNull java.net.InetAddress);
+    method @NonNull public android.net.LinkProperties makeSensitiveFieldsParcelingCopy();
     method public boolean removeDnsServer(@NonNull java.net.InetAddress);
     method public boolean removeLinkAddress(@NonNull android.net.LinkAddress);
     method public boolean removeRoute(@NonNull android.net.RouteInfo);
+    method public void setCaptivePortalApiUrl(@Nullable android.net.Uri);
+    method public void setCaptivePortalData(@Nullable android.net.CaptivePortalData);
     method public void setPcscfServers(@NonNull java.util.Collection<java.net.InetAddress>);
     method public void setPrivateDnsServerName(@Nullable String);
     method public void setTcpBufferSizes(@Nullable String);
@@ -1613,6 +1709,61 @@
     method public void teardownTestNetwork(@NonNull android.net.Network);
   }
 
+  public class TetheringManager {
+    method public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.TetheringEventCallback);
+    method @RequiresPermission("android.permission.TETHER_PRIVILEGED") public void requestLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.OnTetheringEntitlementResultListener);
+    method public void stopAllTethering();
+    method public void stopTethering(int);
+    method public void unregisterTetheringEventCallback(@NonNull android.net.TetheringManager.TetheringEventCallback);
+    field public static final String ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED";
+    field public static final String EXTRA_ACTIVE_LOCAL_ONLY = "android.net.extra.ACTIVE_LOCAL_ONLY";
+    field public static final String EXTRA_ACTIVE_TETHER = "tetherArray";
+    field public static final String EXTRA_AVAILABLE_TETHER = "availableArray";
+    field public static final String EXTRA_ERRORED_TETHER = "erroredArray";
+    field public static final int TETHERING_BLUETOOTH = 2; // 0x2
+    field public static final int TETHERING_INVALID = -1; // 0xffffffff
+    field public static final int TETHERING_USB = 1; // 0x1
+    field public static final int TETHERING_WIFI = 0; // 0x0
+    field public static final int TETHERING_WIFI_P2P = 3; // 0x3
+    field public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12; // 0xc
+    field public static final int TETHER_ERROR_DISABLE_NAT_ERROR = 9; // 0x9
+    field public static final int TETHER_ERROR_ENABLE_NAT_ERROR = 8; // 0x8
+    field public static final int TETHER_ERROR_ENTITLEMENT_UNKNOWN = 13; // 0xd
+    field public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10; // 0xa
+    field public static final int TETHER_ERROR_MASTER_ERROR = 5; // 0x5
+    field public static final int TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION = 15; // 0xf
+    field public static final int TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION = 14; // 0xe
+    field public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0
+    field public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb
+    field public static final int TETHER_ERROR_SERVICE_UNAVAIL = 2; // 0x2
+    field public static final int TETHER_ERROR_TETHER_IFACE_ERROR = 6; // 0x6
+    field public static final int TETHER_ERROR_UNAVAIL_IFACE = 4; // 0x4
+    field public static final int TETHER_ERROR_UNKNOWN_IFACE = 1; // 0x1
+    field public static final int TETHER_ERROR_UNSUPPORTED = 3; // 0x3
+    field public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7; // 0x7
+  }
+
+  public static interface TetheringManager.OnTetheringEntitlementResultListener {
+    method public void onTetheringEntitlementResult(int);
+  }
+
+  public abstract static class TetheringManager.TetheringEventCallback {
+    ctor public TetheringManager.TetheringEventCallback();
+    method public void onError(@NonNull String, int);
+    method @Deprecated public void onTetherableInterfaceRegexpsChanged(@NonNull android.net.TetheringManager.TetheringInterfaceRegexps);
+    method public void onTetherableInterfacesChanged(@NonNull java.util.List<java.lang.String>);
+    method public void onTetheredInterfacesChanged(@NonNull java.util.List<java.lang.String>);
+    method public void onTetheringSupported(boolean);
+    method public void onUpstreamChanged(@Nullable android.net.Network);
+  }
+
+  @Deprecated public static class TetheringManager.TetheringInterfaceRegexps {
+    ctor @Deprecated public TetheringManager.TetheringInterfaceRegexps(@NonNull String[], @NonNull String[], @NonNull String[]);
+    method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableBluetoothRegexs();
+    method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableUsbRegexs();
+    method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableWifiRegexs();
+  }
+
   public class TrafficStats {
     method public static long getLoopbackRxBytes();
     method public static long getLoopbackRxPackets();
@@ -1877,7 +2028,7 @@
     method @Nullable public android.os.NativeHandle getHandle();
     method @NonNull public String getName();
     method public long getSize();
-    method @NonNull public android.os.NativeHandle releaseHandle();
+    method @Nullable public android.os.NativeHandle releaseHandle();
   }
 
   public abstract class HwBinder implements android.os.IHwBinder {
@@ -2021,8 +2172,11 @@
     method @Nullable @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public android.os.IncidentManager.IncidentReport getIncidentReport(android.net.Uri);
     method @NonNull @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public java.util.List<android.net.Uri> getIncidentReportList(String);
     method @RequiresPermission(android.Manifest.permission.APPROVE_INCIDENT_REPORTS) public java.util.List<android.os.IncidentManager.PendingReport> getPendingReports();
+    method public void registerSection(int, @NonNull String, @NonNull android.os.IncidentManager.DumpCallback);
+    method public void registerSection(int, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.IncidentManager.DumpCallback);
     method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void reportIncident(android.os.IncidentReportArgs);
     method @RequiresPermission("android.permission.REQUEST_INCIDENT_REPORT_APPROVAL") public void requestAuthorization(int, String, int, android.os.IncidentManager.AuthListener);
+    method public void unregisterSection(int);
     field public static final int FLAG_CONFIRMATION_DIALOG = 1; // 0x1
     field public static final int PRIVACY_POLICY_AUTO = 200; // 0xc8
     field public static final int PRIVACY_POLICY_EXPLICIT = 100; // 0x64
@@ -2035,6 +2189,11 @@
     method public void onReportDenied();
   }
 
+  public static class IncidentManager.DumpCallback {
+    ctor public IncidentManager.DumpCallback();
+    method public void onDumpSection(int, @NonNull java.io.OutputStream);
+  }
+
   public static class IncidentManager.IncidentReport implements java.io.Closeable android.os.Parcelable {
     ctor public IncidentManager.IncidentReport(android.os.Parcel);
     method public void close();
@@ -2167,6 +2326,11 @@
     method public void log(android.os.StrictMode.ViolationInfo);
   }
 
+  public class SystemConfigManager {
+    method @NonNull @RequiresPermission("android.permission.READ_CARRIER_APP_INFO") public java.util.Set<java.lang.String> getDisabledUntilUsedPreinstalledCarrierApps();
+    method @NonNull @RequiresPermission("android.permission.READ_CARRIER_APP_INFO") public java.util.Map<java.lang.String,java.util.List<java.lang.String>> getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
+  }
+
   public class SystemProperties {
     method @NonNull public static String get(@NonNull String);
     method @NonNull public static String get(@NonNull String, @Nullable String);
@@ -2502,10 +2666,12 @@
     method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static float getFloat(@NonNull String, @NonNull String, float);
     method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static int getInt(@NonNull String, @NonNull String, int);
     method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static long getLong(@NonNull String, @NonNull String, long);
+    method @NonNull @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static android.provider.DeviceConfig.Properties getProperties(@NonNull String, @NonNull java.lang.String...);
     method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static String getProperty(@NonNull String, @NonNull String);
     method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static String getString(@NonNull String, @NonNull String, @Nullable String);
     method public static void removeOnPropertiesChangedListener(@NonNull android.provider.DeviceConfig.OnPropertiesChangedListener);
     method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static void resetToDefaults(int, @Nullable String);
+    method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperties(@NonNull android.provider.DeviceConfig.Properties) throws android.provider.DeviceConfig.BadConfigException;
     method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperty(@NonNull String, @NonNull String, @Nullable String, boolean);
     field public static final String NAMESPACE_ANDROID = "android";
     field public static final String NAMESPACE_AUTOFILL = "autofill";
@@ -2517,6 +2683,10 @@
     field public static final String NAMESPACE_ROLLBACK_BOOT = "rollback_boot";
   }
 
+  public static class DeviceConfig.BadConfigException extends java.lang.Exception {
+    ctor public DeviceConfig.BadConfigException();
+  }
+
   public static interface DeviceConfig.OnPropertiesChangedListener {
     method public void onPropertiesChanged(@NonNull android.provider.DeviceConfig.Properties);
   }
@@ -2551,6 +2721,7 @@
     field public static final String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS";
     field public static final String ACTION_MANAGE_APP_OVERLAY_PERMISSION = "android.settings.MANAGE_APP_OVERLAY_PERMISSION";
     field public static final String ACTION_REQUEST_ENABLE_CONTENT_CAPTURE = "android.settings.REQUEST_ENABLE_CONTENT_CAPTURE";
+    field public static final String ACTION_TETHER_PROVISIONING_UI = "android.settings.TETHER_PROVISIONING_UI";
     field public static final int RESET_MODE_PACKAGE_DEFAULTS = 1; // 0x1
   }
 
@@ -2569,6 +2740,7 @@
     field public static final String LOW_POWER_MODE_STICKY = "low_power_sticky";
     field public static final String NOTIFICATION_BUBBLES = "notification_bubbles";
     field public static final String OVERLAY_DISPLAY_DEVICES = "overlay_display_devices";
+    field public static final String TETHER_OFFLOAD_DISABLED = "tether_offload_disabled";
     field public static final String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package";
   }
 
@@ -2728,6 +2900,11 @@
     method @Nullable public android.util.SparseArray<android.service.autofill.InternalOnClickAction> getActions();
   }
 
+  public static final class Dataset.Builder {
+    ctor public Dataset.Builder(@NonNull android.service.autofill.InlinePresentation);
+    method @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.service.autofill.InlinePresentation);
+  }
+
   public final class DateTransformation extends android.service.autofill.InternalTransformation implements android.os.Parcelable android.service.autofill.Transformation {
     method public void apply(@NonNull android.service.autofill.ValueFinder, @NonNull android.widget.RemoteViews, int) throws java.lang.Exception;
   }
@@ -2926,7 +3103,10 @@
     method @Nullable public android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification, @NonNull android.app.NotificationChannel);
     method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean);
     method public abstract void onNotificationSnoozedUntilContext(@NonNull android.service.notification.StatusBarNotification, @NonNull String);
+    method public void onNotificationVisibilityChanged(@NonNull String, boolean);
     method public void onNotificationsSeen(@NonNull java.util.List<java.lang.String>);
+    method public void onPanelHidden();
+    method public void onPanelRevealed(int);
     method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int);
     method public final void unsnoozeNotification(@NonNull String);
     field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
@@ -3076,6 +3256,8 @@
     method @NonNull public android.telecom.ConnectionRequest.Builder setAccountHandle(@NonNull android.telecom.PhoneAccountHandle);
     method @NonNull public android.telecom.ConnectionRequest.Builder setAddress(@NonNull android.net.Uri);
     method @NonNull public android.telecom.ConnectionRequest.Builder setExtras(@NonNull android.os.Bundle);
+    method @NonNull public android.telecom.ConnectionRequest.Builder setIsAdhocConferenceCall(boolean);
+    method @NonNull public android.telecom.ConnectionRequest.Builder setParticipants(@Nullable java.util.List<android.net.Uri>);
     method @NonNull public android.telecom.ConnectionRequest.Builder setRttPipeFromInCall(@NonNull android.os.ParcelFileDescriptor);
     method @NonNull public android.telecom.ConnectionRequest.Builder setRttPipeToInCall(@NonNull android.os.ParcelFileDescriptor);
     method @NonNull public android.telecom.ConnectionRequest.Builder setShouldShowIncomingCallUi(boolean);
@@ -3113,12 +3295,27 @@
 
   public final class AccessNetworkConstants {
     field public static final int TRANSPORT_TYPE_INVALID = -1; // 0xffffffff
-    field public static final int TRANSPORT_TYPE_WLAN = 2; // 0x2
-    field public static final int TRANSPORT_TYPE_WWAN = 1; // 0x1
+  }
+
+  public static final class AccessNetworkConstants.NgranBands {
+    method public static int getFrequencyRangeGroup(int);
+    field public static final int FREQUENCY_RANGE_GROUP_1 = 1; // 0x1
+    field public static final int FREQUENCY_RANGE_GROUP_2 = 2; // 0x2
+    field public static final int FREQUENCY_RANGE_GROUP_UNKNOWN = 0; // 0x0
+  }
+
+  public final class BarringInfo implements android.os.Parcelable {
+    ctor public BarringInfo();
+    ctor public BarringInfo(@Nullable android.telephony.CellIdentity, @NonNull android.util.SparseArray<android.telephony.BarringInfo.BarringServiceInfo>);
+  }
+
+  public static final class BarringInfo.BarringServiceInfo implements android.os.Parcelable {
+    ctor public BarringInfo.BarringServiceInfo(int, boolean, int, int);
   }
 
   public final class CallQuality implements android.os.Parcelable {
     ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int);
+    ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int, boolean, boolean, boolean);
     method public int describeContents();
     method public int getAverageRelativeJitter();
     method public int getAverageRoundTripTime();
@@ -3131,6 +3328,9 @@
     method public int getNumRtpPacketsTransmitted();
     method public int getNumRtpPacketsTransmittedLost();
     method public int getUplinkCallQualityLevel();
+    method public boolean isIncomingSilenceDetected();
+    method public boolean isOutgoingSilenceDetected();
+    method public boolean isRtpInactivityDetected();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int CALL_QUALITY_BAD = 4; // 0x4
     field public static final int CALL_QUALITY_EXCELLENT = 0; // 0x0
@@ -3177,36 +3377,18 @@
   }
 
   public final class NetworkRegistrationInfo implements android.os.Parcelable {
-    method public int describeContents();
-    method public int getAccessNetworkTechnology();
-    method @NonNull public java.util.List<java.lang.Integer> getAvailableServices();
-    method @Nullable public android.telephony.CellIdentity getCellIdentity();
     method @Nullable public android.telephony.DataSpecificRegistrationInfo getDataSpecificInfo();
-    method public int getDomain();
     method public int getRegistrationState();
     method public int getRejectCause();
     method public int getRoamingType();
-    method public int getTransportType();
     method public boolean isEmergencyEnabled();
-    method public boolean isRoaming();
     method public void writeToParcel(android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.NetworkRegistrationInfo> CREATOR;
-    field public static final int DOMAIN_CS = 1; // 0x1
-    field public static final int DOMAIN_CS_PS = 3; // 0x3
-    field public static final int DOMAIN_PS = 2; // 0x2
-    field public static final int DOMAIN_UNKNOWN = 0; // 0x0
     field public static final int REGISTRATION_STATE_DENIED = 3; // 0x3
     field public static final int REGISTRATION_STATE_HOME = 1; // 0x1
     field public static final int REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING = 0; // 0x0
     field public static final int REGISTRATION_STATE_NOT_REGISTERED_SEARCHING = 2; // 0x2
     field public static final int REGISTRATION_STATE_ROAMING = 5; // 0x5
     field public static final int REGISTRATION_STATE_UNKNOWN = 4; // 0x4
-    field public static final int SERVICE_TYPE_DATA = 2; // 0x2
-    field public static final int SERVICE_TYPE_EMERGENCY = 5; // 0x5
-    field public static final int SERVICE_TYPE_SMS = 3; // 0x3
-    field public static final int SERVICE_TYPE_UNKNOWN = 0; // 0x0
-    field public static final int SERVICE_TYPE_VIDEO = 4; // 0x4
-    field public static final int SERVICE_TYPE_VOICE = 1; // 0x1
   }
 
   public static final class NetworkRegistrationInfo.Builder {
@@ -3243,6 +3425,7 @@
 
   public class ServiceState implements android.os.Parcelable {
     method public void addNetworkRegistrationInfo(android.telephony.NetworkRegistrationInfo);
+    method public int getDataNetworkType();
     method public void setCdmaSystemAndNetworkId(int, int);
     method public void setCellBandwidths(int[]);
     method public void setChannelNumber(int);
@@ -3254,7 +3437,7 @@
 
   public final class SmsManager {
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int checkSmsShortCodeDestination(String, String);
-    method public void sendMultipartTextMessage(@NonNull String, @NonNull String, @NonNull java.util.List<java.lang.String>, @Nullable java.util.List<android.app.PendingIntent>, @Nullable java.util.List<android.app.PendingIntent>, @NonNull String);
+    method public void sendMultipartTextMessage(@NonNull String, @Nullable String, @NonNull java.util.List<java.lang.String>, @Nullable java.util.List<android.app.PendingIntent>, @Nullable java.util.List<android.app.PendingIntent>, @NonNull String);
     field public static final int SMS_CATEGORY_FREE_SHORT_CODE = 1; // 0x1
     field public static final int SMS_CATEGORY_NOT_SHORT_CODE = 0; // 0x0
     field public static final int SMS_CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE = 3; // 0x3
@@ -3287,6 +3470,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void refreshUiccProfile();
     method @Deprecated public void setCarrierTestOverride(String, String, String, String, String, String, String);
     method public void setCarrierTestOverride(String, String, String, String, String, String, String, String, String);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSystemSelectionChannels(@NonNull java.util.List<android.telephony.RadioAccessSpecifier>, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Boolean>);
     method @RequiresPermission("android.permission.READ_ACTIVE_EMERGENCY_SESSION") public void updateTestOtaEmergencyNumberDbFilePath(@NonNull String);
     field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe
     field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
@@ -3296,6 +3480,39 @@
     field public static final int UNKNOWN_CARRIER_ID_LIST_VERSION = -1; // 0xffffffff
   }
 
+  public class TelephonyRegistryManager {
+    method public void addOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener, @NonNull java.util.concurrent.Executor);
+    method public void addOnSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener, @NonNull java.util.concurrent.Executor);
+    method public void notifyActiveDataSubIdChanged(int);
+    method public void notifyBarringInfoChanged(int, int, @NonNull android.telephony.BarringInfo);
+    method public void notifyCallForwardingChanged(int, boolean);
+    method public void notifyCallQualityChanged(int, int, @NonNull android.telephony.CallQuality, int);
+    method public void notifyCallStateChanged(int, int, int, @Nullable String);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyCallStateChangedForAllSubscriptions(int, @Nullable String);
+    method public void notifyCarrierNetworkChange(boolean);
+    method public void notifyCellInfoChanged(int, @NonNull java.util.List<android.telephony.CellInfo>);
+    method public void notifyCellLocation(int, @NonNull android.telephony.CellIdentity);
+    method public void notifyDataActivationStateChanged(int, int, int);
+    method public void notifyDataActivityChanged(int, int);
+    method public void notifyDataConnectionForSubscriber(int, int, int, @Nullable android.telephony.PreciseDataConnectionState);
+    method public void notifyDisconnectCause(int, int, int, int);
+    method public void notifyEmergencyNumberList(int, int);
+    method public void notifyImsDisconnectCause(int, @NonNull android.telephony.ims.ImsReasonInfo);
+    method public void notifyMessageWaitingChanged(int, int, boolean);
+    method public void notifyPhoneCapabilityChanged(@NonNull android.telephony.PhoneCapability);
+    method public void notifyPreciseCallState(int, int, int, int, int);
+    method public void notifyPreciseDataConnectionFailed(int, int, int, @Nullable String, int);
+    method public void notifyRadioPowerStateChanged(int, int, int);
+    method public void notifyRegistrationFailed(int, int, @NonNull android.telephony.CellIdentity, @NonNull String, int, int, int);
+    method public void notifyServiceStateChanged(int, int, @NonNull android.telephony.ServiceState);
+    method public void notifySignalStrengthChanged(int, int, @NonNull android.telephony.SignalStrength);
+    method public void notifySrvccStateChanged(int, int);
+    method public void notifyUserMobileDataStateChanged(int, int, boolean);
+    method public void notifyVoiceActivationStateChanged(int, int, int);
+    method public void removeOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener);
+    method public void removeOnSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
+  }
+
 }
 
 package android.telephony.emergency {
@@ -3351,6 +3568,7 @@
     method public int getEmergencyServiceCategories();
     method @NonNull public java.util.List<java.lang.String> getEmergencyUrns();
     method public android.telephony.ims.ImsStreamMediaProfile getMediaProfile();
+    method @Nullable public android.os.Bundle getProprietaryCallExtras();
     method public int getRestrictCause();
     method public int getServiceType();
     method public static int getVideoStateFromCallType(int);
@@ -3395,7 +3613,8 @@
     field public static final String EXTRA_ADDITIONAL_CALL_INFO = "AdditionalCallInfo";
     field public static final String EXTRA_ADDITIONAL_SIP_INVITE_FIELDS = "android.telephony.ims.extra.ADDITIONAL_SIP_INVITE_FIELDS";
     field public static final String EXTRA_CALL_DISCONNECT_CAUSE = "android.telephony.ims.extra.CALL_DISCONNECT_CAUSE";
-    field public static final String EXTRA_CALL_RAT_TYPE = "CallRadioTech";
+    field public static final String EXTRA_CALL_NETWORK_TYPE = "android.telephony.ims.extra.CALL_NETWORK_TYPE";
+    field @Deprecated public static final String EXTRA_CALL_RAT_TYPE = "CallRadioTech";
     field public static final String EXTRA_CHILD_NUMBER = "ChildNum";
     field public static final String EXTRA_CNA = "cna";
     field public static final String EXTRA_CNAP = "cnap";
@@ -3403,7 +3622,9 @@
     field public static final String EXTRA_DIALSTRING = "dialstring";
     field public static final String EXTRA_DISPLAY_TEXT = "DisplayText";
     field public static final String EXTRA_EMERGENCY_CALL = "e_call";
+    field public static final String EXTRA_FORWARDED_NUMBER = "android.telephony.ims.extra.FORWARDED_NUMBER";
     field public static final String EXTRA_IS_CALL_PULL = "CallPull";
+    field public static final String EXTRA_OEM_EXTRAS = "android.telephony.ims.extra.OEM_EXTRAS";
     field public static final String EXTRA_OI = "oi";
     field public static final String EXTRA_OIR = "oir";
     field public static final String EXTRA_REMOTE_URI = "remote_uri";
@@ -3427,8 +3648,8 @@
     method public void callSessionConferenceExtendReceived(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile);
     method public void callSessionConferenceExtended(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile);
     method public void callSessionConferenceStateUpdated(android.telephony.ims.ImsConferenceState);
-    method public void callSessionHandover(int, int, android.telephony.ims.ImsReasonInfo);
-    method public void callSessionHandoverFailed(int, int, android.telephony.ims.ImsReasonInfo);
+    method @Deprecated public void callSessionHandover(int, int, android.telephony.ims.ImsReasonInfo);
+    method @Deprecated public void callSessionHandoverFailed(int, int, android.telephony.ims.ImsReasonInfo);
     method public void callSessionHeld(android.telephony.ims.ImsCallProfile);
     method public void callSessionHoldFailed(android.telephony.ims.ImsReasonInfo);
     method public void callSessionHoldReceived(android.telephony.ims.ImsCallProfile);
@@ -3436,7 +3657,7 @@
     method public void callSessionInitiatedFailed(android.telephony.ims.ImsReasonInfo);
     method public void callSessionInviteParticipantsRequestDelivered();
     method public void callSessionInviteParticipantsRequestFailed(android.telephony.ims.ImsReasonInfo);
-    method public void callSessionMayHandover(int, int);
+    method @Deprecated public void callSessionMayHandover(int, int);
     method public void callSessionMergeComplete(android.telephony.ims.stub.ImsCallSessionImplBase);
     method public void callSessionMergeFailed(android.telephony.ims.ImsReasonInfo);
     method public void callSessionMergeStarted(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile);
@@ -3458,6 +3679,9 @@
     method public void callSessionUpdateReceived(android.telephony.ims.ImsCallProfile);
     method public void callSessionUpdated(android.telephony.ims.ImsCallProfile);
     method public void callSessionUssdMessageReceived(int, String);
+    method public void onHandover(int, int, @Nullable android.telephony.ims.ImsReasonInfo);
+    method public void onHandoverFailed(int, int, @NonNull android.telephony.ims.ImsReasonInfo);
+    method public void onMayHandover(int, int);
   }
 
   public final class ImsConferenceState implements android.os.Parcelable {
@@ -3489,10 +3713,6 @@
     ctor public ImsException(@Nullable String);
     ctor public ImsException(@Nullable String, int);
     ctor public ImsException(@Nullable String, int, @Nullable Throwable);
-    method public int getCode();
-    field public static final int CODE_ERROR_SERVICE_UNAVAILABLE = 1; // 0x1
-    field public static final int CODE_ERROR_UNSPECIFIED = 0; // 0x0
-    field public static final int CODE_ERROR_UNSUPPORTED_OPERATION = 2; // 0x2
   }
 
   public final class ImsExternalCallState implements android.os.Parcelable {
@@ -3514,26 +3734,20 @@
   public class ImsManager {
     method @NonNull public android.telephony.ims.ImsMmTelManager getImsMmTelManager(int);
     method @NonNull public android.telephony.ims.ImsRcsManager getImsRcsManager(int);
+    field public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION = "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION";
+    field public static final String ACTION_WFC_IMS_REGISTRATION_ERROR = "android.telephony.ims.action.WFC_IMS_REGISTRATION_ERROR";
+    field public static final String EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE = "android.telephony.ims.extra.WFC_REGISTRATION_FAILURE_MESSAGE";
+    field public static final String EXTRA_WFC_REGISTRATION_FAILURE_TITLE = "android.telephony.ims.extra.WFC_REGISTRATION_FAILURE_TITLE";
   }
 
   public class ImsMmTelManager implements android.telephony.ims.RegistrationManager {
-    method @NonNull public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(int);
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getFeatureState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>) throws android.telephony.ims.ImsException;
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getRegistrationState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
-    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getRegistrationTransportType(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
-    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public int getVoWiFiModeSetting();
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public int getVoWiFiRoamingModeSetting();
-    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public boolean isAdvancedCallingSettingEnabled();
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public boolean isAvailable(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int);
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public boolean isCapable(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int);
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void isSupported(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>) throws android.telephony.ims.ImsException;
-    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public boolean isTtyOverVolteEnabled();
-    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public boolean isVoWiFiRoamingSettingEnabled();
-    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public boolean isVoWiFiSettingEnabled();
-    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public boolean isVtSettingEnabled();
     method @Deprecated @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void registerImsRegistrationCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.RegistrationCallback) throws android.telephony.ims.ImsException;
-    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void registerImsRegistrationCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RegistrationManager.RegistrationCallback) throws android.telephony.ims.ImsException;
-    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void registerMmTelCapabilityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.CapabilityCallback) throws android.telephony.ims.ImsException;
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setAdvancedCallingSettingEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setRttCapabilitySetting(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setVoWiFiModeSetting(int);
@@ -3543,16 +3757,6 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setVoWiFiSettingEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setVtSettingEnabled(boolean);
     method @Deprecated @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void unregisterImsRegistrationCallback(@NonNull android.telephony.ims.ImsMmTelManager.RegistrationCallback);
-    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void unregisterImsRegistrationCallback(@NonNull android.telephony.ims.RegistrationManager.RegistrationCallback);
-    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void unregisterMmTelCapabilityCallback(@NonNull android.telephony.ims.ImsMmTelManager.CapabilityCallback);
-    field public static final int WIFI_MODE_CELLULAR_PREFERRED = 1; // 0x1
-    field public static final int WIFI_MODE_WIFI_ONLY = 0; // 0x0
-    field public static final int WIFI_MODE_WIFI_PREFERRED = 2; // 0x2
-  }
-
-  public static class ImsMmTelManager.CapabilityCallback {
-    ctor public ImsMmTelManager.CapabilityCallback();
-    method public void onCapabilitiesStatusChanged(@NonNull android.telephony.ims.feature.MmTelFeature.MmTelCapabilities);
   }
 
   @Deprecated public static class ImsMmTelManager.RegistrationCallback extends android.telephony.ims.RegistrationManager.RegistrationCallback {
@@ -3804,17 +4008,90 @@
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @WorkerThread public int getProvisioningIntValue(int);
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @WorkerThread public boolean getProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int);
     method @Nullable @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @WorkerThread public String getProvisioningStringValue(int);
+    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @WorkerThread public boolean getRcsProvisioningStatusForCapability(int);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean);
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void registerProvisioningChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback) throws android.telephony.ims.ImsException;
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean);
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback);
+    field public static final int KEY_1X_EPDG_TIMER_SEC = 64; // 0x40
+    field public static final int KEY_1X_THRESHOLD = 59; // 0x3b
+    field public static final int KEY_AMR_BANDWIDTH_EFFICIENT_PAYLOAD_TYPE = 50; // 0x32
+    field public static final int KEY_AMR_CODEC_MODE_SET_VALUES = 0; // 0x0
+    field public static final int KEY_AMR_DEFAULT_ENCODING_MODE = 53; // 0x35
+    field public static final int KEY_AMR_OCTET_ALIGNED_PAYLOAD_TYPE = 49; // 0x31
+    field public static final int KEY_AMR_WB_BANDWIDTH_EFFICIENT_PAYLOAD_TYPE = 48; // 0x30
+    field public static final int KEY_AMR_WB_CODEC_MODE_SET_VALUES = 1; // 0x1
+    field public static final int KEY_AMR_WB_OCTET_ALIGNED_PAYLOAD_TYPE = 47; // 0x2f
+    field public static final int KEY_DTMF_NB_PAYLOAD_TYPE = 52; // 0x34
+    field public static final int KEY_DTMF_WB_PAYLOAD_TYPE = 51; // 0x33
+    field public static final int KEY_EAB_PROVISIONING_STATUS = 25; // 0x19
+    field public static final int KEY_ENABLE_SILENT_REDIAL = 6; // 0x6
+    field public static final int KEY_LOCAL_BREAKOUT_PCSCF_ADDRESS = 31; // 0x1f
+    field public static final int KEY_LTE_EPDG_TIMER_SEC = 62; // 0x3e
+    field public static final int KEY_LTE_THRESHOLD_1 = 56; // 0x38
+    field public static final int KEY_LTE_THRESHOLD_2 = 57; // 0x39
+    field public static final int KEY_LTE_THRESHOLD_3 = 58; // 0x3a
+    field public static final int KEY_MINIMUM_SIP_SESSION_EXPIRATION_TIMER_SEC = 3; // 0x3
+    field public static final int KEY_MOBILE_DATA_ENABLED = 29; // 0x1d
+    field public static final int KEY_MULTIENDPOINT_ENABLED = 65; // 0x41
+    field public static final int KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC = 19; // 0x13
+    field public static final int KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC = 18; // 0x12
+    field public static final int KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC = 20; // 0x14
+    field public static final int KEY_RCS_CAPABILITY_DISCOVERY_ENABLED = 17; // 0x11
+    field public static final int KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC = 23; // 0x17
+    field public static final int KEY_RCS_MAX_NUM_ENTRIES_IN_RCL = 22; // 0x16
+    field public static final int KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS = 21; // 0x15
+    field public static final int KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC = 16; // 0x10
+    field public static final int KEY_RCS_PUBLISH_TIMER_SEC = 15; // 0xf
+    field public static final int KEY_REGISTRATION_DOMAIN_NAME = 12; // 0xc
+    field public static final int KEY_REGISTRATION_RETRY_BASE_TIME_SEC = 33; // 0x21
+    field public static final int KEY_REGISTRATION_RETRY_MAX_TIME_SEC = 34; // 0x22
+    field public static final int KEY_RTP_SPEECH_END_PORT = 36; // 0x24
+    field public static final int KEY_RTP_SPEECH_START_PORT = 35; // 0x23
+    field public static final int KEY_RTT_ENABLED = 66; // 0x42
+    field public static final int KEY_SIP_ACK_RECEIPT_WAIT_TIME_MS = 43; // 0x2b
+    field public static final int KEY_SIP_ACK_RETRANSMIT_WAIT_TIME_MS = 44; // 0x2c
+    field public static final int KEY_SIP_INVITE_ACK_WAIT_TIME_MS = 38; // 0x26
+    field public static final int KEY_SIP_INVITE_CANCELLATION_TIMER_MS = 4; // 0x4
+    field public static final int KEY_SIP_INVITE_REQUEST_TRANSMIT_INTERVAL_MS = 37; // 0x25
+    field public static final int KEY_SIP_INVITE_RESPONSE_RETRANSMIT_INTERVAL_MS = 42; // 0x2a
+    field public static final int KEY_SIP_INVITE_RESPONSE_RETRANSMIT_WAIT_TIME_MS = 39; // 0x27
+    field public static final int KEY_SIP_KEEP_ALIVE_ENABLED = 32; // 0x20
+    field public static final int KEY_SIP_NON_INVITE_REQUEST_RETRANSMISSION_WAIT_TIME_MS = 45; // 0x2d
+    field public static final int KEY_SIP_NON_INVITE_REQUEST_RETRANSMIT_INTERVAL_MS = 40; // 0x28
+    field public static final int KEY_SIP_NON_INVITE_RESPONSE_RETRANSMISSION_WAIT_TIME_MS = 46; // 0x2e
+    field public static final int KEY_SIP_NON_INVITE_TRANSACTION_TIMEOUT_TIMER_MS = 41; // 0x29
+    field public static final int KEY_SIP_SESSION_TIMER_SEC = 2; // 0x2
+    field public static final int KEY_SMS_FORMAT = 13; // 0xd
+    field public static final int KEY_SMS_OVER_IP_ENABLED = 14; // 0xe
+    field public static final int KEY_SMS_PUBLIC_SERVICE_IDENTITY = 54; // 0x36
+    field public static final int KEY_T1_TIMER_VALUE_MS = 7; // 0x7
+    field public static final int KEY_T2_TIMER_VALUE_MS = 8; // 0x8
+    field public static final int KEY_TF_TIMER_VALUE_MS = 9; // 0x9
+    field public static final int KEY_TRANSITION_TO_LTE_DELAY_MS = 5; // 0x5
+    field public static final int KEY_USE_GZIP_FOR_LIST_SUBSCRIPTION = 24; // 0x18
+    field public static final int KEY_VIDEO_QUALITY = 55; // 0x37
+    field public static final int KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE = 28; // 0x1c
     field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b
     field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a
+    field public static final int KEY_VOLTE_PROVISIONING_STATUS = 10; // 0xa
+    field public static final int KEY_VOLTE_USER_OPT_IN_STATUS = 30; // 0x1e
+    field public static final int KEY_VT_PROVISIONING_STATUS = 11; // 0xb
+    field public static final int KEY_WIFI_EPDG_TIMER_SEC = 63; // 0x3f
+    field public static final int KEY_WIFI_THRESHOLD_A = 60; // 0x3c
+    field public static final int KEY_WIFI_THRESHOLD_B = 61; // 0x3d
+    field public static final int PROVISIONING_RESULT_UNKNOWN = -1; // 0xffffffff
     field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0
     field public static final int PROVISIONING_VALUE_ENABLED = 1; // 0x1
+    field public static final int SMS_FORMAT_3GPP = 1; // 0x1
+    field public static final int SMS_FORMAT_3GPP2 = 0; // 0x0
     field public static final String STRING_QUERY_RESULT_ERROR_GENERIC = "STRING_QUERY_RESULT_ERROR_GENERIC";
     field public static final String STRING_QUERY_RESULT_ERROR_NOT_READY = "STRING_QUERY_RESULT_ERROR_NOT_READY";
+    field public static final int VIDEO_QUALITY_HIGH = 1; // 0x1
+    field public static final int VIDEO_QUALITY_LOW = 0; // 0x0
   }
 
   public static class ProvisioningManager.Callback {
@@ -3823,24 +4100,6 @@
     method public void onProvisioningStringChanged(int, @NonNull String);
   }
 
-  public interface RegistrationManager {
-    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getRegistrationState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
-    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getRegistrationTransportType(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
-    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void registerImsRegistrationCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RegistrationManager.RegistrationCallback) throws android.telephony.ims.ImsException;
-    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void unregisterImsRegistrationCallback(@NonNull android.telephony.ims.RegistrationManager.RegistrationCallback);
-    field public static final int REGISTRATION_STATE_NOT_REGISTERED = 0; // 0x0
-    field public static final int REGISTRATION_STATE_REGISTERED = 2; // 0x2
-    field public static final int REGISTRATION_STATE_REGISTERING = 1; // 0x1
-  }
-
-  public static class RegistrationManager.RegistrationCallback {
-    ctor public RegistrationManager.RegistrationCallback();
-    method public void onRegistered(int);
-    method public void onRegistering(int);
-    method public void onTechnologyChangeFailed(int, @Nullable android.telephony.ims.ImsReasonInfo);
-    method public void onUnregistered(@Nullable android.telephony.ims.ImsReasonInfo);
-  }
-
 }
 
 package android.telephony.ims.feature {
@@ -3864,6 +4123,7 @@
   public abstract class ImsFeature {
     ctor public ImsFeature();
     method public abstract void changeEnabledCapabilities(android.telephony.ims.feature.CapabilityChangeRequest, android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy);
+    method public int getFeatureState();
     method public final int getSlotIndex();
     method public abstract void onFeatureReady();
     method public abstract void onFeatureRemoved();
@@ -3902,7 +4162,7 @@
     method public void onFeatureReady();
     method public void onFeatureRemoved();
     method public boolean queryCapabilityConfiguration(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int);
-    method public final android.telephony.ims.feature.MmTelFeature.MmTelCapabilities queryCapabilityStatus();
+    method @NonNull public final android.telephony.ims.feature.MmTelFeature.MmTelCapabilities queryCapabilityStatus();
     method public void setUiTtyMode(int, @Nullable android.os.Message);
     method @android.telephony.ims.feature.MmTelFeature.ProcessCallResult public int shouldProcessCall(@NonNull String[]);
     field public static final String EXTRA_IS_UNKNOWN_CALL = "android.telephony.ims.feature.extra.IS_UNKNOWN_CALL";
@@ -3918,10 +4178,6 @@
     method public final void addCapabilities(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int);
     method public final boolean isCapable(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int);
     method public final void removeCapabilities(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int);
-    field public static final int CAPABILITY_TYPE_SMS = 8; // 0x8
-    field public static final int CAPABILITY_TYPE_UT = 4; // 0x4
-    field public static final int CAPABILITY_TYPE_VIDEO = 2; // 0x2
-    field public static final int CAPABILITY_TYPE_VOICE = 1; // 0x1
   }
 
   @IntDef(flag=true, value={android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE, android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO, android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT, android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_SMS}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MmTelFeature.MmTelCapabilities.MmTelCapability {
@@ -4012,6 +4268,7 @@
     method public String getConfigString(int);
     method public final void notifyProvisionedValueChanged(int, int);
     method public final void notifyProvisionedValueChanged(int, String);
+    method public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean);
     method public int setConfig(int, int);
     method public int setConfig(int, String);
     field public static final int CONFIG_RESULT_FAILED = 1; // 0x1
@@ -4104,12 +4361,24 @@
     method public int transact(android.os.Bundle);
     method public int updateCallBarring(int, int, String[]);
     method public int updateCallBarringForServiceClass(int, int, String[], int);
+    method public int updateCallBarringWithPassword(int, int, @Nullable String[], int, @NonNull String);
     method public int updateCallForward(int, int, String, int, int);
     method public int updateCallWaiting(boolean, int);
     method public int updateClip(boolean);
     method public int updateClir(int);
     method public int updateColp(boolean);
     method public int updateColr(int);
+    field public static final int CALL_BARRING_ALL = 7; // 0x7
+    field public static final int CALL_BARRING_ALL_INCOMING = 1; // 0x1
+    field public static final int CALL_BARRING_ALL_OUTGOING = 2; // 0x2
+    field public static final int CALL_BARRING_ANONYMOUS_INCOMING = 6; // 0x6
+    field public static final int CALL_BARRING_INCOMING_ALL_SERVICES = 9; // 0x9
+    field public static final int CALL_BARRING_OUTGOING_ALL_SERVICES = 8; // 0x8
+    field public static final int CALL_BARRING_OUTGOING_INTL = 3; // 0x3
+    field public static final int CALL_BARRING_OUTGOING_INTL_EXCL_HOME = 4; // 0x4
+    field public static final int CALL_BARRING_SPECIFIC_INCOMING_CALLS = 10; // 0xa
+    field public static final int CALL_BLOCKING_INCOMING_WHEN_ROAMING = 5; // 0x5
+    field public static final int INVALID_RESULT = -1; // 0xffffffff
   }
 
 }
@@ -4241,6 +4510,7 @@
     field public static final String FFLAG_OVERRIDE_PREFIX = "sys.fflag.override.";
     field public static final String FFLAG_PREFIX = "sys.fflag.";
     field public static final String HEARING_AID_SETTINGS = "settings_bluetooth_hearing_aid";
+    field public static final String NOTIF_CONVO_BYPASS_SHORTCUT_REQ = "settings_notif_convo_bypass_shortcut_req";
     field public static final String PERSIST_PREFIX = "persist.sys.fflag.override.";
     field public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press";
     field public static final String SEAMLESS_TRANSFER = "settings_seamless_transfer";
@@ -4340,6 +4610,7 @@
   }
 
   public final class Display {
+    method @NonNull public android.graphics.ColorSpace[] getSupportedWideColorGamut();
     method public boolean hasAccess(int);
   }
 
@@ -4378,22 +4649,6 @@
     method public abstract String asyncImpl() default "";
   }
 
-  public class SurfaceControlViewHost {
-    ctor public SurfaceControlViewHost(@NonNull android.content.Context, @NonNull android.view.Display, @Nullable android.os.IBinder);
-    method public void addView(android.view.View, android.view.WindowManager.LayoutParams);
-    method public void dispose();
-    method @Nullable public android.view.SurfaceControlViewHost.SurfacePackage getSurfacePackage();
-    method public void relayout(android.view.WindowManager.LayoutParams);
-  }
-
-  public class SurfaceControlViewHost.SurfacePackage {
-    method @NonNull public android.view.SurfaceControl getSurfaceControl();
-  }
-
-  public class SurfaceView extends android.view.View {
-    method @Nullable public android.os.IBinder getInputToken();
-  }
-
   @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback {
     method public android.view.View getTooltipView();
     method public boolean isAutofilled();
@@ -4438,7 +4693,7 @@
     field public static final int ACCESSIBILITY_TITLE_CHANGED = 33554432; // 0x2000000
     field public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 64; // 0x40
     field public CharSequence accessibilityTitle;
-    field @android.view.ViewDebug.ExportedProperty(flagMapping={@android.view.ViewDebug.FlagToString(mask=0x1, equals=0x1, name="FAKE_HARDWARE_ACCELERATED"), @android.view.ViewDebug.FlagToString(mask=0x2, equals=0x2, name="FORCE_HARDWARE_ACCELERATED"), @android.view.ViewDebug.FlagToString(mask=0x4, equals=0x4, name="WANTS_OFFSET_NOTIFICATIONS"), @android.view.ViewDebug.FlagToString(mask=0x10, equals=0x10, name="SHOW_FOR_ALL_USERS"), @android.view.ViewDebug.FlagToString(mask=android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION, equals=android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION, name="NO_MOVE_ANIMATION"), @android.view.ViewDebug.FlagToString(mask=0x80, equals=0x80, name="COMPATIBLE_WINDOW"), @android.view.ViewDebug.FlagToString(mask=0x100, equals=0x100, name="SYSTEM_ERROR"), @android.view.ViewDebug.FlagToString(mask=0x400, equals=0x400, name="KEYGUARD"), @android.view.ViewDebug.FlagToString(mask=0x800, equals=0x800, name="DISABLE_WALLPAPER_TOUCH_EVENTS"), @android.view.ViewDebug.FlagToString(mask=0x1000, equals=0x1000, name="FORCE_STATUS_BAR_VISIBLE_TRANSPARENT"), @android.view.ViewDebug.FlagToString(mask=0x2000, equals=0x2000, name="PRESERVE_GEOMETRY"), @android.view.ViewDebug.FlagToString(mask=0x4000, equals=0x4000, name="FORCE_DECOR_VIEW_VISIBILITY"), @android.view.ViewDebug.FlagToString(mask=0x8000, equals=0x8000, name="WILL_NOT_REPLACE_ON_RELAUNCH"), @android.view.ViewDebug.FlagToString(mask=0x10000, equals=0x10000, name="LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME"), @android.view.ViewDebug.FlagToString(mask=0x20000, equals=0x20000, name="FORCE_DRAW_STATUS_BAR_BACKGROUND"), @android.view.ViewDebug.FlagToString(mask=0x40000, equals=0x40000, name="SUSTAINED_PERFORMANCE_MODE"), @android.view.ViewDebug.FlagToString(mask=0x80000, equals=0x80000, name="HIDE_NON_SYSTEM_OVERLAY_WINDOWS"), @android.view.ViewDebug.FlagToString(mask=0x100000, equals=0x100000, name="IS_ROUNDED_CORNERS_OVERLAY"), @android.view.ViewDebug.FlagToString(mask=0x400000, equals=0x400000, name="IS_SCREEN_DECOR"), @android.view.ViewDebug.FlagToString(mask=0x800000, equals=0x800000, name="STATUS_FORCE_SHOW_NAVIGATION"), @android.view.ViewDebug.FlagToString(mask=0x1000000, equals=0x1000000, name="COLOR_SPACE_AGNOSTIC"), @android.view.ViewDebug.FlagToString(mask=0x4000000, equals=0x4000000, name="FIT_INSETS_CONTROLLED"), @android.view.ViewDebug.FlagToString(mask=0x8000000, equals=0x8000000, name="ONLY_DRAW_BOTTOM_BAR_BACKGROUND")}) public int privateFlags;
+    field @android.view.ViewDebug.ExportedProperty(flagMapping={@android.view.ViewDebug.FlagToString(mask=0x1, equals=0x1, name="FAKE_HARDWARE_ACCELERATED"), @android.view.ViewDebug.FlagToString(mask=0x2, equals=0x2, name="FORCE_HARDWARE_ACCELERATED"), @android.view.ViewDebug.FlagToString(mask=0x4, equals=0x4, name="WANTS_OFFSET_NOTIFICATIONS"), @android.view.ViewDebug.FlagToString(mask=0x10, equals=0x10, name="SHOW_FOR_ALL_USERS"), @android.view.ViewDebug.FlagToString(mask=android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION, equals=android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION, name="NO_MOVE_ANIMATION"), @android.view.ViewDebug.FlagToString(mask=0x80, equals=0x80, name="COMPATIBLE_WINDOW"), @android.view.ViewDebug.FlagToString(mask=0x100, equals=0x100, name="SYSTEM_ERROR"), @android.view.ViewDebug.FlagToString(mask=0x800, equals=0x800, name="DISABLE_WALLPAPER_TOUCH_EVENTS"), @android.view.ViewDebug.FlagToString(mask=0x1000, equals=0x1000, name="FORCE_STATUS_BAR_VISIBLE"), @android.view.ViewDebug.FlagToString(mask=0x2000, equals=0x2000, name="PRESERVE_GEOMETRY"), @android.view.ViewDebug.FlagToString(mask=0x4000, equals=0x4000, name="FORCE_DECOR_VIEW_VISIBILITY"), @android.view.ViewDebug.FlagToString(mask=0x8000, equals=0x8000, name="WILL_NOT_REPLACE_ON_RELAUNCH"), @android.view.ViewDebug.FlagToString(mask=0x10000, equals=0x10000, name="LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME"), @android.view.ViewDebug.FlagToString(mask=0x20000, equals=0x20000, name="FORCE_DRAW_STATUS_BAR_BACKGROUND"), @android.view.ViewDebug.FlagToString(mask=0x40000, equals=0x40000, name="SUSTAINED_PERFORMANCE_MODE"), @android.view.ViewDebug.FlagToString(mask=0x80000, equals=0x80000, name="HIDE_NON_SYSTEM_OVERLAY_WINDOWS"), @android.view.ViewDebug.FlagToString(mask=0x100000, equals=0x100000, name="IS_ROUNDED_CORNERS_OVERLAY"), @android.view.ViewDebug.FlagToString(mask=0x400000, equals=0x400000, name="IS_SCREEN_DECOR"), @android.view.ViewDebug.FlagToString(mask=0x800000, equals=0x800000, name="STATUS_FORCE_SHOW_NAVIGATION"), @android.view.ViewDebug.FlagToString(mask=0x1000000, equals=0x1000000, name="COLOR_SPACE_AGNOSTIC"), @android.view.ViewDebug.FlagToString(mask=0x4000000, equals=0x4000000, name="APPEARANCE_CONTROLLED"), @android.view.ViewDebug.FlagToString(mask=0x8000000, equals=0x8000000, name="BEHAVIOR_CONTROLLED"), @android.view.ViewDebug.FlagToString(mask=0x10000000, equals=0x10000000, name="FIT_INSETS_CONTROLLED")}) public int privateFlags;
   }
 
 }
@@ -4457,6 +4712,8 @@
   }
 
   public class AccessibilityNodeInfo implements android.os.Parcelable {
+    method public void addChild(@NonNull android.os.IBinder);
+    method public void setLeashedParent(@Nullable android.os.IBinder, int);
     method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger);
     method public void writeToParcelNoRecycle(android.os.Parcel, int);
   }
@@ -4615,6 +4872,18 @@
 
 package android.view.inputmethod {
 
+  public final class InlineSuggestion implements android.os.Parcelable {
+    method @NonNull public static android.view.inputmethod.InlineSuggestion newInlineSuggestion(@NonNull android.view.inputmethod.InlineSuggestionInfo);
+  }
+
+  public final class InlineSuggestionInfo implements android.os.Parcelable {
+    method @NonNull public static android.view.inputmethod.InlineSuggestionInfo newInlineSuggestionInfo(@NonNull android.view.inline.InlinePresentationSpec, @NonNull String, @Nullable String[]);
+  }
+
+  public final class InlineSuggestionsResponse implements android.os.Parcelable {
+    method @NonNull public static android.view.inputmethod.InlineSuggestionsResponse newInlineSuggestionsResponse(@NonNull java.util.List<android.view.inputmethod.InlineSuggestion>);
+  }
+
   public final class InputMethodManager {
     method public int getDisplayId();
     method public boolean isInputMethodPickerShown();
diff --git a/api/test-lint-baseline.txt b/api/test-lint-baseline.txt
index 6d1f291..54f7f68 100644
--- a/api/test-lint-baseline.txt
+++ b/api/test-lint-baseline.txt
@@ -7,6 +7,16 @@
 
 ActionValue: android.location.Location#EXTRA_NO_GPS_LOCATION:
     
+// Tethering broadcast action / extras cannot change name for backwards compatibility
+ActionValue: android.net.TetheringManager#ACTION_TETHER_STATE_CHANGED:
+    Inconsistent action value; expected `android.net.action.TETHER_STATE_CHANGED`, was `android.net.conn.TETHER_STATE_CHANGED`
+ActionValue: android.net.TetheringManager#EXTRA_ACTIVE_TETHER:
+    Inconsistent extra value; expected `android.net.extra.ACTIVE_TETHER`, was `tetherArray`
+ActionValue: android.net.TetheringManager#EXTRA_AVAILABLE_TETHER:
+    Inconsistent extra value; expected `android.net.extra.AVAILABLE_TETHER`, was `availableArray`
+ActionValue: android.net.TetheringManager#EXTRA_ERRORED_TETHER:
+    Inconsistent extra value; expected `android.net.extra.ERRORED_TETHER`, was `erroredArray`
+
 ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_ADDITIONAL_CALL_INFO:
     
 ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_CALL_RAT_TYPE:
@@ -400,7 +410,7 @@
 GetterSetterNames: android.location.GnssClock#setTimeUncertaintyNanos(double):
     
 GetterSetterNames: android.location.GnssMeasurement#setBasebandCn0DbHz(double):
-
+    
 GetterSetterNames: android.location.GnssMeasurement#setCarrierFrequencyHz(float):
     
 GetterSetterNames: android.location.GnssMeasurement#setCodeType(String):
@@ -466,7 +476,7 @@
 KotlinOperator: android.os.WorkSource#get(int):
     
 KotlinOperator: android.util.SparseArrayMap#get(int, String):
-    Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
+    
 
 
 ListenerInterface: android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener:
@@ -2059,6 +2069,18 @@
     
 MissingNullability: android.view.KeyEvent#actionToString(int):
     
+MissingNullability: android.view.SurfaceControlViewHost#SurfaceControlViewHost(android.content.Context, android.view.Display, android.view.SurfaceControl) parameter #0:
+    
+MissingNullability: android.view.SurfaceControlViewHost#SurfaceControlViewHost(android.content.Context, android.view.Display, android.view.SurfaceControl) parameter #1:
+    
+MissingNullability: android.view.SurfaceControlViewHost#SurfaceControlViewHost(android.content.Context, android.view.Display, android.view.SurfaceControl) parameter #2:
+    
+MissingNullability: android.view.SurfaceControlViewHost#addView(android.view.View, android.view.WindowManager.LayoutParams) parameter #0:
+    
+MissingNullability: android.view.SurfaceControlViewHost#addView(android.view.View, android.view.WindowManager.LayoutParams) parameter #1:
+    
+MissingNullability: android.view.SurfaceControlViewHost#relayout(android.view.WindowManager.LayoutParams) parameter #0:
+    
 MissingNullability: android.view.View#getTooltipView():
     
 MissingNullability: android.view.View#isDefaultFocusHighlightNeeded(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable) parameter #0:
@@ -2079,18 +2101,6 @@
     
 MissingNullability: android.view.WindowManager.LayoutParams#accessibilityTitle:
     
-MissingNullability: android.view.SurfaceControlViewHost#SurfaceControlViewHost(android.content.Context, android.view.Display, android.view.SurfaceControl) parameter #0:
-    
-MissingNullability: android.view.SurfaceControlViewHost#SurfaceControlViewHost(android.content.Context, android.view.Display, android.view.SurfaceControl) parameter #1:
-    
-MissingNullability: android.view.SurfaceControlViewHost#SurfaceControlViewHost(android.content.Context, android.view.Display, android.view.SurfaceControl) parameter #2:
-    
-MissingNullability: android.view.SurfaceControlViewHost#addView(android.view.View, android.view.WindowManager.LayoutParams) parameter #0:
-    
-MissingNullability: android.view.SurfaceControlViewHost#addView(android.view.View, android.view.WindowManager.LayoutParams) parameter #1:
-    
-MissingNullability: android.view.SurfaceControlViewHost#relayout(android.view.WindowManager.LayoutParams) parameter #0:
-    
 MissingNullability: android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener#onAccessibilityServicesStateChanged(android.view.accessibility.AccessibilityManager) parameter #0:
     
 MissingNullability: android.view.accessibility.AccessibilityNodeInfo#setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger) parameter #0:
@@ -2426,11 +2436,13 @@
 ProtectedMember: android.view.ViewGroup#resetResolvedDrawables():
     
 
-PublicTypedef: android.os.HwParcel.Status: Don't expose @IntDef: @Status must be hidden.
 
-PublicTypedef: android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability: Don't expose @IntDef: @MmTelCapability must be hidden.
-
-PublicTypedef: android.telephony.ims.feature.MmTelFeature.ProcessCallResult: Don't expose @IntDef: @ProcessCallResult must be hidden.
+PublicTypedef: android.os.HwParcel.Status:
+    
+PublicTypedef: android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability:
+    
+PublicTypedef: android.telephony.ims.feature.MmTelFeature.ProcessCallResult:
+    
 
 
 RawAidl: android.telephony.mbms.vendor.MbmsDownloadServiceBase:
@@ -2513,6 +2525,8 @@
     
 SamShouldBeLast: android.database.sqlite.SQLiteDirectCursorDriver#query(android.database.sqlite.SQLiteDatabase.CursorFactory, String[]):
     
+SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(android.location.LocationRequest, android.location.LocationListener, android.os.Looper):
+    SAM-compatible parameters (such as parameter 2, "listener", in android.location.LocationManager.requestLocationUpdates) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(android.location.LocationRequest, java.util.concurrent.Executor, android.location.LocationListener):
     
 SamShouldBeLast: android.os.BugreportManager#startBugreport(android.os.ParcelFileDescriptor, android.os.ParcelFileDescriptor, android.os.BugreportParams, java.util.concurrent.Executor, android.os.BugreportManager.BugreportCallback):
@@ -2593,6 +2607,8 @@
     
 
 
+UserHandle: android.app.ActivityManager#switchUser(android.os.UserHandle):
+    When a method overload is needed to target a specific UserHandle, callers should be directed to use Context.createPackageContextAsUser() and re-obtain the relevant Manager, and no new API should be added
 UserHandle: android.app.admin.DevicePolicyManager#getOwnerInstalledCaCerts(android.os.UserHandle):
     
 UserHandle: android.app.role.RoleManager#addOnRoleHoldersChangedListenerAsUser(java.util.concurrent.Executor, android.app.role.OnRoleHoldersChangedListener, android.os.UserHandle):
@@ -2607,8 +2623,12 @@
     
 UserHandle: android.app.role.RoleManager#removeRoleHolderAsUser(String, String, int, android.os.UserHandle, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Boolean>):
     
-UserHandle: android.companion.CompanionDeviceManager#isDeviceAssociated(String, android.net.MacAddress, android.os.UserHandle):
+UserHandle: android.app.usage.StorageStatsManager#queryCratesForPackage(java.util.UUID, String, android.os.UserHandle):
     When a method overload is needed to target a specific UserHandle, callers should be directed to use Context.createPackageContextAsUser() and re-obtain the relevant Manager, and no new API should be added
+UserHandle: android.app.usage.StorageStatsManager#queryCratesForUser(java.util.UUID, android.os.UserHandle):
+    When a method overload is needed to target a specific UserHandle, callers should be directed to use Context.createPackageContextAsUser() and re-obtain the relevant Manager, and no new API should be added
+UserHandle: android.companion.CompanionDeviceManager#isDeviceAssociated(String, android.net.MacAddress, android.os.UserHandle):
+    
 UserHandle: android.content.pm.PackageManager#getInstallReason(String, android.os.UserHandle):
     
 UserHandle: android.content.pm.PackageManager#getPermissionFlags(String, String, android.os.UserHandle):
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 459520a..2537eda 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -100,6 +100,7 @@
 static const int TEXT_CENTER_VALUE = INT_MAX;
 static const int TEXT_MISSING_VALUE = INT_MIN;
 static const char EXIT_PROP_NAME[] = "service.bootanim.exit";
+static const char DISPLAYS_PROP_NAME[] = "persist.service.bootanim.displays";
 static const int ANIM_ENTRY_NAME_MAX = ANIM_PATH_MAX + 1;
 static constexpr size_t TEXT_POS_LEN_MAX = 16;
 
@@ -291,10 +292,10 @@
 
     // this guest property specifies multi-display IDs to show the boot animation
     // multiple ids can be set with comma (,) as separator, for example:
-    // setprop boot.animation.displays 19260422155234049,19261083906282754
+    // setprop persist.boot.animation.displays 19260422155234049,19261083906282754
     Vector<uint64_t> physicalDisplayIds;
     char displayValue[PROPERTY_VALUE_MAX] = "";
-    property_get("boot.animation.displays", displayValue, "");
+    property_get(DISPLAYS_PROP_NAME, displayValue, "");
     bool isValid = displayValue[0] != '\0';
     if (isValid) {
         char *p = displayValue;
@@ -306,7 +307,7 @@
             p ++;
         }
         if (!isValid)
-            SLOGE("Invalid syntax for the value of system prop: boot.animation.displays");
+            SLOGE("Invalid syntax for the value of system prop: %s", DISPLAYS_PROP_NAME);
     }
     if (isValid) {
         std::istringstream stream(displayValue);
@@ -1113,7 +1114,7 @@
         SurfaceComposerClient::Transaction t;
         t.setPosition(mFlingerSurfaceControl, 0, -mTargetInset)
                 .setCrop(mFlingerSurfaceControl, Rect(0, mTargetInset, mWidth, mHeight));
-        t.setDisplayProjection(mDisplayToken, 0 /* orientation */, layerStackRect, displayRect);
+        t.setDisplayProjection(mDisplayToken, ui::ROTATION_0, layerStackRect, displayRect);
         t.apply();
 
         mTargetInset = mCurrentInset = 0;
diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp
index 229628c..4074789 100644
--- a/cmds/idmap2/libidmap2/ResourceMapping.cpp
+++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp
@@ -33,6 +33,8 @@
 
 namespace {
 
+#define REWRITE_PACKAGE(resid, package_id) \
+  (((resid)&0x00ffffffU) | (((uint32_t)(package_id)) << 24U))
 #define EXTRACT_PACKAGE(resid) ((0xff000000 & (resid)) >> 24)
 
 std::string ConcatPolicies(const std::vector<std::string>& policies) {
@@ -154,6 +156,7 @@
     return Error("root element is not <overlay> tag");
   }
 
+  const uint8_t target_package_id = target_package->GetPackageId();
   const uint8_t overlay_package_id = overlay_package->GetPackageId();
   auto overlay_it_end = root_it.end();
   for (auto overlay_it = root_it.begin(); overlay_it != overlay_it_end; ++overlay_it) {
@@ -187,6 +190,9 @@
       continue;
     }
 
+    // Retrieve the compile-time resource id of the target resource.
+    target_id = REWRITE_PACKAGE(target_id, target_package_id);
+
     if (overlay_resource->dataType == Res_value::TYPE_STRING) {
       overlay_resource->data += string_pool_offset;
     }
@@ -214,6 +220,7 @@
     const AssetManager2* target_am, const AssetManager2* overlay_am,
     const LoadedPackage* target_package, const LoadedPackage* overlay_package) {
   ResourceMapping resource_mapping;
+  const uint8_t target_package_id = target_package->GetPackageId();
   const auto end = overlay_package->end();
   for (auto iter = overlay_package->begin(); iter != end; ++iter) {
     const ResourceId overlay_resid = *iter;
@@ -225,11 +232,14 @@
     // Find the resource with the same type and entry name within the target package.
     const std::string full_name =
         base::StringPrintf("%s:%s", target_package->GetPackageName().c_str(), name->c_str());
-    const ResourceId target_resource = target_am->GetResourceId(full_name);
+    ResourceId target_resource = target_am->GetResourceId(full_name);
     if (target_resource == 0U) {
       continue;
     }
 
+    // Retrieve the compile-time resource id of the target resource.
+    target_resource = REWRITE_PACKAGE(target_resource, target_package_id);
+
     resource_mapping.AddMapping(target_resource, Res_value::TYPE_REFERENCE, overlay_resid,
                                 /* rewrite_overlay_reference */ false);
   }
diff --git a/cmds/incident_helper/Android.bp b/cmds/incident_helper/Android.bp
index d7b6d69..64f4c66 100644
--- a/cmds/incident_helper/Android.bp
+++ b/cmds/incident_helper/Android.bp
@@ -1,3 +1,28 @@
+// 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.
+
+java_binary {
+    name: "incident-helper-cmd",
+    wrapper: "incident_helper_cmd",
+    srcs: [
+        "java/**/*.java",
+    ],
+    proto: {
+        plugin: "javastream",
+    },
+}
+
 cc_defaults {
     name: "incident_helper_defaults",
 
diff --git a/cmds/incident_helper/incident_helper_cmd b/cmds/incident_helper/incident_helper_cmd
new file mode 100644
index 0000000..d45f7df
--- /dev/null
+++ b/cmds/incident_helper/incident_helper_cmd
@@ -0,0 +1,6 @@
+#!/system/bin/sh
+# Script to start "incident_helper_cmd" on the device
+#
+base=/system
+export CLASSPATH=$base/framework/incident-helper-cmd.jar
+exec app_process $base/bin com.android.commands.incident.IncidentHelper "$@"
diff --git a/cmds/incident_helper/java/com/android/commands/incident/ExecutionException.java b/cmds/incident_helper/java/com/android/commands/incident/ExecutionException.java
new file mode 100644
index 0000000..d97b17e
--- /dev/null
+++ b/cmds/incident_helper/java/com/android/commands/incident/ExecutionException.java
@@ -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.commands.incident;
+
+/**
+ * Thrown when there is an error executing a section.
+ */
+public class ExecutionException extends Exception {
+    /**
+     * Constructs a ExecutionException.
+     *
+     * @param msg the message
+     */
+    public ExecutionException(String msg) {
+        super(msg);
+    }
+
+    /**
+     * Constructs a ExecutionException from another exception.
+     *
+     * @param e the exception
+     */
+    public ExecutionException(Exception e) {
+        super(e);
+    }
+}
diff --git a/cmds/incident_helper/java/com/android/commands/incident/IncidentHelper.java b/cmds/incident_helper/java/com/android/commands/incident/IncidentHelper.java
new file mode 100644
index 0000000..e5874e0
--- /dev/null
+++ b/cmds/incident_helper/java/com/android/commands/incident/IncidentHelper.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.commands.incident;
+
+import android.util.Log;
+
+import com.android.commands.incident.sections.PersistLogSection;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * Helper command runner for incidentd to run customized command to gather data for a non-standard
+ * section.
+ */
+public class IncidentHelper {
+    private static final String TAG = "IncidentHelper";
+    private static boolean sLog = false;
+    private final List<String> mArgs;
+    private ListIterator<String> mArgsIterator;
+
+    private IncidentHelper(String[] args) {
+        mArgs = Collections.unmodifiableList(Arrays.asList(args));
+        mArgsIterator = mArgs.listIterator();
+    }
+
+    private static void showUsage(PrintStream out) {
+        out.println("This command is not designed to be run manually.");
+        out.println("Usage:");
+        out.println("  run [sectionName]");
+    }
+
+    private void run(String[] args) throws ExecutionException {
+        Section section = null;
+        List<String> sectionArgs = new ArrayList<>();
+        while (mArgsIterator.hasNext()) {
+            String arg = mArgsIterator.next();
+            if ("-l".equals(arg)) {
+                sLog = true;
+                Log.i(TAG, "Args: [" + String.join(",", args) + "]");
+            } else if ("run".equals(arg)) {
+                section = getSection(nextArgRequired());
+                mArgsIterator.forEachRemaining(sectionArgs::add);
+                break;
+            } else {
+                log(Log.WARN, TAG, "Error: Unknown argument: " + arg);
+                return;
+            }
+        }
+        section.run(System.in, System.out, sectionArgs);
+    }
+
+    private static Section getSection(String name) throws IllegalArgumentException {
+        if ("persisted_logs".equals(name)) {
+            return new PersistLogSection();
+        }
+        throw new IllegalArgumentException("Section not found: " + name);
+    }
+
+    private String nextArgRequired() {
+        if (!mArgsIterator.hasNext()) {
+            throw new IllegalArgumentException(
+                    "Arg required after \"" + mArgs.get(mArgsIterator.previousIndex()) + "\"");
+        }
+        return mArgsIterator.next();
+    }
+
+    /**
+     * Print the given message to stderr, also log it if asked to (set by -l cmd arg).
+     */
+    public static void log(int priority, String tag, String msg) {
+        System.err.println(tag + ": " + msg);
+        if (sLog) {
+            Log.println(priority, tag, msg);
+        }
+    }
+
+    /**
+     * Command-line entry point.
+     *
+     * @param args The command-line arguments
+     */
+    public static void main(String[] args) {
+        if (args.length == 0) {
+            showUsage(System.err);
+            System.exit(0);
+        }
+        IncidentHelper incidentHelper = new IncidentHelper(args);
+        try {
+            incidentHelper.run(args);
+        } catch (IllegalArgumentException e) {
+            showUsage(System.err);
+            System.err.println();
+            e.printStackTrace(System.err);
+            if (sLog) {
+                Log.e(TAG, "Error: ", e);
+            }
+        } catch (Exception e) {
+            e.printStackTrace(System.err);
+            if (sLog) {
+                Log.e(TAG, "Error: ", e);
+            }
+            System.exit(1);
+        }
+    }
+}
diff --git a/cmds/incident_helper/java/com/android/commands/incident/Section.java b/cmds/incident_helper/java/com/android/commands/incident/Section.java
new file mode 100644
index 0000000..1c8c657
--- /dev/null
+++ b/cmds/incident_helper/java/com/android/commands/incident/Section.java
@@ -0,0 +1,29 @@
+/*
+ * 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.commands.incident;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+
+/** Section interface used by {@link IncidentHelper}. */
+public interface Section {
+    /**
+     * Writes protobuf wire format to out, optionally reads data from in, with supplied args.
+     */
+    void run(InputStream in, OutputStream out, List<String> args) throws ExecutionException;
+}
diff --git a/cmds/incident_helper/java/com/android/commands/incident/sections/PersistLogSection.java b/cmds/incident_helper/java/com/android/commands/incident/sections/PersistLogSection.java
new file mode 100644
index 0000000..f9d2e79
--- /dev/null
+++ b/cmds/incident_helper/java/com/android/commands/incident/sections/PersistLogSection.java
@@ -0,0 +1,287 @@
+/*
+ * 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.commands.incident.sections;
+
+import android.util.Log;
+import android.util.PersistedLogProto;
+import android.util.TextLogEntry;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.commands.incident.ExecutionException;
+import com.android.commands.incident.IncidentHelper;
+import com.android.commands.incident.Section;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Comparator;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+/** PersistLogSection reads persisted logs and parses them into a PersistedLogProto. */
+public class PersistLogSection implements Section {
+    private static final String TAG = "IH_PersistLog";
+    private static final String LOG_DIR = "/data/misc/logd/";
+    // Persist log files are named logcat, logcat.001, logcat.002, logcat.003, ...
+    private static final Pattern LOG_FILE_RE = Pattern.compile("logcat(\\.\\d+)?");
+    private static final Pattern BUFFER_BEGIN_RE =
+            Pattern.compile("--------- (?:beginning of|switch to) (.*)");
+    private static final Map<String, Long> SECTION_NAME_TO_ID = new HashMap<>();
+    private static final Map<Character, Integer> LOG_PRIORITY_MAP = new HashMap<>();
+    private static final String DEFAULT_BUFFER = "main";
+
+    static {
+        SECTION_NAME_TO_ID.put("main", PersistedLogProto.MAIN_LOGS);
+        SECTION_NAME_TO_ID.put("radio", PersistedLogProto.RADIO_LOGS);
+        SECTION_NAME_TO_ID.put("events", PersistedLogProto.EVENTS_LOGS);
+        SECTION_NAME_TO_ID.put("system", PersistedLogProto.SYSTEM_LOGS);
+        SECTION_NAME_TO_ID.put("crash", PersistedLogProto.CRASH_LOGS);
+        SECTION_NAME_TO_ID.put("kernel", PersistedLogProto.KERNEL_LOGS);
+    }
+
+    static {
+        LOG_PRIORITY_MAP.put('V', TextLogEntry.LOG_VERBOSE);
+        LOG_PRIORITY_MAP.put('D', TextLogEntry.LOG_DEBUG);
+        LOG_PRIORITY_MAP.put('I', TextLogEntry.LOG_INFO);
+        LOG_PRIORITY_MAP.put('W', TextLogEntry.LOG_WARN);
+        LOG_PRIORITY_MAP.put('E', TextLogEntry.LOG_ERROR);
+        LOG_PRIORITY_MAP.put('F', TextLogEntry.LOG_FATAL);
+        LOG_PRIORITY_MAP.put('S', TextLogEntry.LOG_SILENT);
+    }
+
+    /**
+     * Caches dates at 00:00:00 to epoch second elapsed conversion. There are only a few different
+     * dates in persisted logs in one device, and constructing DateTime object is relatively
+     * expensive.
+     */
+    private Map<Integer, Long> mEpochTimeCache = new HashMap<>();
+    private ProtoOutputStream mProto;
+    private long mCurrFieldId;
+    private long mMaxBytes = Long.MAX_VALUE;
+
+    @Override
+    public void run(InputStream in, OutputStream out, List<String> args) throws ExecutionException {
+        parseArgs(args);
+        Path logDirPath = Paths.get(LOG_DIR);
+        if (!Files.exists(logDirPath)) {
+            IncidentHelper.log(Log.WARN, TAG, "Skip dump. " + logDirPath + " does not exist.");
+            return;
+        }
+        if (!Files.isReadable(logDirPath)) {
+            IncidentHelper.log(Log.WARN, TAG, "Skip dump. " + logDirPath + " is not readable.");
+            return;
+        }
+        mProto = new ProtoOutputStream(out);
+        setCurrentSection(DEFAULT_BUFFER);
+        final Matcher logFileRe = LOG_FILE_RE.matcher("");
+        // Need to process older log files first and write logs to proto in chronological order
+        // But we want to process only the latest ones if there is a size limit
+        try (Stream<File> stream = Files.list(logDirPath).map(Path::toFile)
+                .filter(f -> !f.isDirectory() && match(logFileRe, f.getName()) != null)
+                .sorted(Comparator.comparingLong(File::lastModified).reversed())) {
+            Iterator<File> iter = stream.iterator();
+            List<File> filesToProcess = new ArrayList<>();
+            long sumBytes = 0;
+            while (iter.hasNext()) {
+                File file = iter.next();
+                sumBytes += file.length();
+                if (sumBytes > mMaxBytes) {
+                    break;
+                }
+                filesToProcess.add(file);
+            }
+            IncidentHelper.log(Log.INFO, TAG, "Limit # log files to " + filesToProcess.size());
+            filesToProcess.stream()
+                    .sorted(Comparator.comparingLong(File::lastModified))
+                    .forEachOrdered(this::processFile);
+        } catch (IOException e) {
+            throw new ExecutionException(e);
+        } finally {
+            mProto.flush();
+        }
+        IncidentHelper.log(Log.DEBUG, TAG, "Bytes written: " + mProto.getBytes().length);
+    }
+
+    private void parseArgs(List<String> args) {
+        Iterator<String> iter = args.iterator();
+        while (iter.hasNext()) {
+            String arg = iter.next();
+            if ("--limit".equals(arg) && iter.hasNext()) {
+                String sizeStr = iter.next().toLowerCase();
+                if (sizeStr.endsWith("mb")) {
+                    mMaxBytes = Long.parseLong(sizeStr.replace("mb", "")) * 1024 * 1024;
+                } else if (sizeStr.endsWith("kb")) {
+                    mMaxBytes = Long.parseLong(sizeStr.replace("kb", "")) * 1024;
+                } else {
+                    mMaxBytes = Long.parseLong(sizeStr);
+                }
+            } else {
+                throw new IllegalArgumentException("Unknown argument: " + arg);
+            }
+        }
+    }
+
+    private void processFile(File file) {
+        final Matcher bufferBeginRe = BUFFER_BEGIN_RE.matcher("");
+        try (BufferedReader reader = Files.newBufferedReader(file.toPath(),
+                StandardCharsets.UTF_8)) {
+            String line;
+            Matcher m;
+            while ((line = reader.readLine()) != null) {
+                if ((m = match(bufferBeginRe, line)) != null) {
+                    setCurrentSection(m.group(1));
+                    continue;
+                }
+                parseLine(line);
+            }
+        } catch (IOException e) {
+            // Non-fatal error. We can skip and still process other files.
+            IncidentHelper.log(Log.WARN, TAG, "Error reading \"" + file + "\": " + e.getMessage());
+        }
+        IncidentHelper.log(Log.DEBUG, TAG, "Finished reading " + file);
+    }
+
+    private void setCurrentSection(String sectionName) {
+        Long sectionId = SECTION_NAME_TO_ID.get(sectionName);
+        if (sectionId == null) {
+            IncidentHelper.log(Log.WARN, TAG, "Section does not exist: " + sectionName);
+            sectionId = SECTION_NAME_TO_ID.get(DEFAULT_BUFFER);
+        }
+        mCurrFieldId = sectionId;
+    }
+
+    /**
+     * Parse a log line in the following format:
+     * 01-01 15:01:47.723501  2738  2895 I Exp_TAG: example log line
+     *
+     * It does not use RegExp for performance reasons. Using this RegExp "(\\d{2})-(\\d{2})\\s
+     * (\\d{2}):(\\d{2}):(\\d{2}).(\\d{6})\\s+(\\d+)\\s+(\\d+)\\s+(.)\\s+(.*?):\\s(.*)" is twice as
+     * slow as the current approach.
+     */
+    private void parseLine(String line) {
+        long token = mProto.start(mCurrFieldId);
+        try {
+            mProto.write(TextLogEntry.SEC, getEpochSec(line));
+            // Nanosec is 15th to 20th digits of "10-01 02:57:27.710652" times 1000
+            mProto.write(TextLogEntry.NANOSEC, parseInt(line, 15, 21) * 1000L);
+
+            int start = nextNonBlank(line, 21);
+            int end = line.indexOf(' ', start + 1);
+            mProto.write(TextLogEntry.PID, parseInt(line, start, end));
+
+            start = nextNonBlank(line, end);
+            end = line.indexOf(' ', start + 1);
+            mProto.write(TextLogEntry.TID, parseInt(line, start, end));
+
+            start = nextNonBlank(line, end);
+            char priority = line.charAt(start);
+            mProto.write(TextLogEntry.PRIORITY,
+                    LOG_PRIORITY_MAP.getOrDefault(priority, TextLogEntry.LOG_DEFAULT));
+
+            start = nextNonBlank(line, start + 1);
+            end = line.indexOf(": ", start);
+            mProto.write(TextLogEntry.TAG, line.substring(start, end).trim());
+            mProto.write(TextLogEntry.LOG, line.substring(Math.min(end + 2, line.length())));
+        } catch (RuntimeException e) {
+            // Error reporting is likely piped to /dev/null. Inserting it into the proto to make
+            // it more useful.
+            mProto.write(TextLogEntry.SEC, System.currentTimeMillis() / 1000);
+            mProto.write(TextLogEntry.PRIORITY, TextLogEntry.LOG_ERROR);
+            mProto.write(TextLogEntry.TAG, TAG);
+            mProto.write(TextLogEntry.LOG,
+                    "Error parsing \"" + line + "\"" + ": " + e.getMessage());
+        }
+        mProto.end(token);
+    }
+
+    // ============== Below are util methods to parse log lines ==============
+
+    private static int nextNonBlank(String line, int start) {
+        for (int i = start; i < line.length(); i++) {
+            if (line.charAt(i) != ' ') {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Gets the epoch second from the line string. Line starts with a fixed-length timestamp like
+     * "10-01 02:57:27.710652"
+     */
+    private long getEpochSec(String line) {
+        int month = getDigit(line, 0) * 10 + getDigit(line, 1);
+        int day = getDigit(line, 3) * 10 + getDigit(line, 4);
+
+        int mmdd = month * 100 + day;
+        long epochSecBase = mEpochTimeCache.computeIfAbsent(mmdd, (key) -> {
+            final GregorianCalendar calendar = new GregorianCalendar();
+            calendar.set(Calendar.MONTH, (month + 12 - 1) % 12);
+            calendar.set(Calendar.DAY_OF_MONTH, day);
+            calendar.set(Calendar.HOUR_OF_DAY, 0);
+            calendar.set(Calendar.MINUTE, 0);
+            calendar.set(Calendar.SECOND, 0);
+            calendar.set(Calendar.MILLISECOND, 0);
+            // Date in log entries can never be in the future. If it happens, it means we are off
+            // by one year.
+            if (calendar.getTimeInMillis() > System.currentTimeMillis()) {
+                calendar.roll(Calendar.YEAR, /*amount=*/-1);
+            }
+            return calendar.getTimeInMillis() / 1000;
+        });
+
+        int hh = getDigit(line, 6) * 10 + getDigit(line, 7);
+        int mm = getDigit(line, 9) * 10 + getDigit(line, 10);
+        int ss = getDigit(line, 12) * 10 + getDigit(line, 13);
+        return epochSecBase + hh * 3600 + mm * 60 + ss;
+    }
+
+    private static int parseInt(String line, /*inclusive*/ int start, /*exclusive*/ int end) {
+        int num = 0;
+        for (int i = start; i < end; i++) {
+            num = num * 10 + getDigit(line, i);
+        }
+        return num;
+    }
+
+    private static int getDigit(String str, int pos) {
+        int digit = str.charAt(pos) - '0';
+        if (digit < 0 || digit > 9) {
+            throw new NumberFormatException("'" + str.charAt(pos) + "' is not a digit.");
+        }
+        return digit;
+    }
+
+    private static Matcher match(Matcher matcher, String text) {
+        matcher.reset(text);
+        return matcher.matches() ? matcher : null;
+    }
+}
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index cfd77c2..6c2b855 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -123,14 +123,17 @@
 
 // ================================================================================
 ReportHandler::ReportHandler(const sp<WorkDirectory>& workDirectory,
-            const sp<Broadcaster>& broadcaster, const sp<Looper>& handlerLooper,
-            const sp<Throttler>& throttler)
+                             const sp<Broadcaster>& broadcaster,
+                             const sp<Looper>& handlerLooper,
+                             const sp<Throttler>& throttler,
+                             const vector<BringYourOwnSection*>& registeredSections)
         :mLock(),
          mWorkDirectory(workDirectory),
          mBroadcaster(broadcaster),
          mHandlerLooper(handlerLooper),
          mBacklogDelay(DEFAULT_DELAY_NS),
          mThrottler(throttler),
+         mRegisteredSections(registeredSections),
          mBatch(new ReportBatch()) {
 }
 
@@ -185,7 +188,7 @@
         return;
     }
 
-    sp<Reporter> reporter = new Reporter(mWorkDirectory, batch);
+    sp<Reporter> reporter = new Reporter(mWorkDirectory, batch, mRegisteredSections);
 
     // Take the report, which might take a while. More requests might queue
     // up while we're doing this, and we'll handle them in their next batch.
@@ -237,7 +240,7 @@
     mWorkDirectory = new WorkDirectory();
     mBroadcaster = new Broadcaster(mWorkDirectory);
     mHandler = new ReportHandler(mWorkDirectory, mBroadcaster, handlerLooper,
-            mThrottler);
+            mThrottler, mRegisteredSections);
     mBroadcaster->setHandler(mHandler);
 }
 
@@ -327,6 +330,11 @@
             incidentArgs.addSection(id);
         }
     }
+    for (const Section* section : mRegisteredSections) {
+        if (!section_requires_specific_mention(section->id)) {
+            incidentArgs.addSection(section->id);
+        }
+    }
 
     // The ReportRequest takes ownership of the fd, so we need to dup it.
     int fd = dup(stream.get());
@@ -339,6 +347,46 @@
     return Status::ok();
 }
 
+Status IncidentService::registerSection(const int id, const String16& name16,
+        const sp<IIncidentDumpCallback>& callback) {
+    const char* name = String8(name16).c_str();
+    const uid_t callingUid = IPCThreadState::self()->getCallingUid();
+    ALOGI("Uid %d registers section %d '%s'", callingUid, id, name);
+    if (callback == nullptr) {
+        return Status::fromExceptionCode(Status::EX_NULL_POINTER);
+    }
+    for (int i = 0; i < mRegisteredSections.size(); i++) {
+        if (mRegisteredSections.at(i)->id == id) {
+            if (mRegisteredSections.at(i)->uid != callingUid) {
+                ALOGW("Error registering section %d: calling uid does not match", id);
+                return Status::fromExceptionCode(Status::EX_SECURITY);
+            }
+            mRegisteredSections.at(i) = new BringYourOwnSection(id, name, callingUid, callback);
+            return Status::ok();
+        }
+    }
+    mRegisteredSections.push_back(new BringYourOwnSection(id, name, callingUid, callback));
+    return Status::ok();
+}
+
+Status IncidentService::unregisterSection(const int id) {
+    uid_t callingUid = IPCThreadState::self()->getCallingUid();
+    ALOGI("Uid %d unregisters section %d", callingUid, id);
+
+    for (auto it = mRegisteredSections.begin(); it != mRegisteredSections.end(); it++) {
+        if ((*it)->id == id) {
+            if ((*it)->uid != callingUid) {
+                ALOGW("Error unregistering section %d: calling uid does not match", id);
+                return Status::fromExceptionCode(Status::EX_SECURITY);
+            }
+            mRegisteredSections.erase(it);
+            return Status::ok();
+        }
+    }
+    ALOGW("Section %d not found", id);
+    return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
+}
+
 Status IncidentService::systemRunning() {
     if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
         return Status::fromExceptionCode(Status::EX_SECURITY,
diff --git a/cmds/incidentd/src/IncidentService.h b/cmds/incidentd/src/IncidentService.h
index b2c7f23..49fc566 100644
--- a/cmds/incidentd/src/IncidentService.h
+++ b/cmds/incidentd/src/IncidentService.h
@@ -40,12 +40,16 @@
 using namespace android::binder;
 using namespace android::os;
 
+class BringYourOwnSection;
+
 // ================================================================================
 class ReportHandler : public MessageHandler {
 public:
     ReportHandler(const sp<WorkDirectory>& workDirectory,
-            const sp<Broadcaster>& broadcaster, const sp<Looper>& handlerLooper,
-            const sp<Throttler>& throttler);
+                  const sp<Broadcaster>& broadcaster,
+                  const sp<Looper>& handlerLooper,
+                  const sp<Throttler>& throttler,
+                  const vector<BringYourOwnSection*>& registeredSections);
     virtual ~ReportHandler();
 
     virtual void handleMessage(const Message& message);
@@ -79,6 +83,8 @@
     nsecs_t mBacklogDelay;
     sp<Throttler> mThrottler;
 
+    const vector<BringYourOwnSection*>& mRegisteredSections;
+
     sp<ReportBatch> mBatch;
 
     /**
@@ -126,6 +132,11 @@
     virtual Status reportIncidentToDumpstate(unique_fd stream,
             const sp<IIncidentReportStatusListener>& listener);
 
+    virtual Status registerSection(int id, const String16& name,
+            const sp<IIncidentDumpCallback>& callback);
+
+    virtual Status unregisterSection(int id);
+
     virtual Status systemRunning();
 
     virtual Status getIncidentReportList(const String16& pkg, const String16& cls,
@@ -149,6 +160,7 @@
     sp<Broadcaster> mBroadcaster;
     sp<ReportHandler> mHandler;
     sp<Throttler> mThrottler;
+    vector<BringYourOwnSection*> mRegisteredSections;
 
     /**
      * Commands print out help.
diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp
index 02b6bbe..aa40f85 100644
--- a/cmds/incidentd/src/Reporter.cpp
+++ b/cmds/incidentd/src/Reporter.cpp
@@ -364,7 +364,6 @@
     mSectionBufferSuccess = false;
     mHadError = false;
     mSectionErrors.clear();
-    
 }
 
 void ReportWriter::setSectionStats(const FdBuffer& buffer) {
@@ -470,10 +469,13 @@
 
 
 // ================================================================================
-Reporter::Reporter(const sp<WorkDirectory>& workDirectory, const sp<ReportBatch>& batch)
+Reporter::Reporter(const sp<WorkDirectory>& workDirectory,
+                   const sp<ReportBatch>& batch,
+                   const vector<BringYourOwnSection*>& registeredSections)
         :mWorkDirectory(workDirectory),
          mWriter(batch),
-         mBatch(batch) {
+         mBatch(batch),
+         mRegisteredSections(registeredSections) {
 }
 
 Reporter::~Reporter() {
@@ -580,50 +582,15 @@
     // For each of the report fields, see if we need it, and if so, execute the command
     // and report to those that care that we're doing it.
     for (const Section** section = SECTION_LIST; *section; section++) {
-        const int sectionId = (*section)->id;
-
-        // If nobody wants this section, skip it.
-        if (!mBatch->containsSection(sectionId)) {
-            continue;
-        }
-
-        ALOGD("Start incident report section %d '%s'", sectionId, (*section)->name.string());
-        IncidentMetadata::SectionStats* sectionMetadata = metadata.add_sections();
-
-        // Notify listener of starting
-        mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
-            listener->onReportSectionStatus(
-                    sectionId, IIncidentReportStatusListener::STATUS_STARTING);
-        });
-
-        // Go get the data and write it into the file descriptors.
-        mWriter.startSection(sectionId);
-        err = (*section)->Execute(&mWriter);
-        mWriter.endSection(sectionMetadata);
-
-        // Sections returning errors are fatal. Most errors should not be fatal.
-        if (err != NO_ERROR) {
-            mWriter.error((*section), err, "Section failed. Stopping report.");
+        if (execute_section(*section, &metadata, reportByteSize) != NO_ERROR) {
             goto DONE;
         }
+    }
 
-        // The returned max data size is used for throttling too many incident reports.
-        (*reportByteSize) += sectionMetadata->report_size_bytes();
-
-        // For any requests that failed during this section, remove them now.  We do this
-        // before calling back about section finished, so listeners do not erroniously get the
-        // impression that the section succeeded.  But we do it here instead of inside
-        // writeSection so that the callback is done from a known context and not from the
-        // bowels of a section, where changing the batch could cause odd errors.
-        cancel_and_remove_failed_requests();
-
-        // Notify listener of finishing
-        mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
-                listener->onReportSectionStatus(
-                        sectionId, IIncidentReportStatusListener::STATUS_FINISHED);
-        });
-
-        ALOGD("Finish incident report section %d '%s'", sectionId, (*section)->name.string());
+    for (const Section* section : mRegisteredSections) {
+        if (execute_section(section, &metadata, reportByteSize) != NO_ERROR) {
+            goto DONE;
+        }
     }
 
 DONE:
@@ -681,6 +648,55 @@
     ALOGI("Done taking incident report err=%s", strerror(-err));
 }
 
+status_t Reporter::execute_section(const Section* section, IncidentMetadata* metadata,
+        size_t* reportByteSize) {
+    const int sectionId = section->id;
+
+    // If nobody wants this section, skip it.
+    if (!mBatch->containsSection(sectionId)) {
+        return NO_ERROR;
+    }
+
+    ALOGD("Start incident report section %d '%s'", sectionId, section->name.string());
+    IncidentMetadata::SectionStats* sectionMetadata = metadata->add_sections();
+
+    // Notify listener of starting
+    mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
+        listener->onReportSectionStatus(
+                sectionId, IIncidentReportStatusListener::STATUS_STARTING);
+    });
+
+    // Go get the data and write it into the file descriptors.
+    mWriter.startSection(sectionId);
+    status_t err = section->Execute(&mWriter);
+    mWriter.endSection(sectionMetadata);
+
+    // Sections returning errors are fatal. Most errors should not be fatal.
+    if (err != NO_ERROR) {
+        mWriter.error(section, err, "Section failed. Stopping report.");
+        return err;
+    }
+
+    // The returned max data size is used for throttling too many incident reports.
+    (*reportByteSize) += sectionMetadata->report_size_bytes();
+
+    // For any requests that failed during this section, remove them now.  We do this
+    // before calling back about section finished, so listeners do not erroniously get the
+    // impression that the section succeeded.  But we do it here instead of inside
+    // writeSection so that the callback is done from a known context and not from the
+    // bowels of a section, where changing the batch could cause odd errors.
+    cancel_and_remove_failed_requests();
+
+    // Notify listener of finishing
+    mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
+            listener->onReportSectionStatus(
+                    sectionId, IIncidentReportStatusListener::STATUS_FINISHED);
+    });
+
+    ALOGD("Finish incident report section %d '%s'", sectionId, section->name.string());
+    return NO_ERROR;
+}
+
 void Reporter::cancel_and_remove_failed_requests() {
     // Handle a failure in the persisted file
     if (mPersistedFile != nullptr) {
diff --git a/cmds/incidentd/src/Reporter.h b/cmds/incidentd/src/Reporter.h
index fb3961a..cbc8b13 100644
--- a/cmds/incidentd/src/Reporter.h
+++ b/cmds/incidentd/src/Reporter.h
@@ -21,6 +21,7 @@
 #include "frameworks/base/core/proto/android/os/metadata.pb.h"
 #include <android/content/ComponentName.h>
 #include <android/os/IIncidentReportStatusListener.h>
+#include <android/os/IIncidentDumpCallback.h>
 #include <android/os/IncidentReportArgs.h>
 #include <android/util/protobuf.h>
 
@@ -39,6 +40,7 @@
 using namespace android::content;
 using namespace android::os;
 
+class BringYourOwnSection;
 class Section;
 
 // ================================================================================
@@ -122,7 +124,7 @@
     void forEachStreamingRequest(const function<void (const sp<ReportRequest>&)>& func);
 
     /**
-     * Call func(request) for each file descriptor that has 
+     * Call func(request) for each file descriptor.
      */
     void forEachFd(int sectionId, const function<void (const sp<ReportRequest>&)>& func);
 
@@ -251,7 +253,9 @@
 // ================================================================================
 class Reporter : public virtual RefBase {
 public:
-    Reporter(const sp<WorkDirectory>& workDirectory, const sp<ReportBatch>& batch);
+    Reporter(const sp<WorkDirectory>& workDirectory,
+             const sp<ReportBatch>& batch,
+             const vector<BringYourOwnSection*>& registeredSections);
 
     virtual ~Reporter();
 
@@ -263,6 +267,10 @@
     ReportWriter mWriter;
     sp<ReportBatch> mBatch;
     sp<ReportFile> mPersistedFile;
+    const vector<BringYourOwnSection*>& mRegisteredSections;
+
+    status_t execute_section(const Section* section, IncidentMetadata* metadata,
+        size_t* reportByteSize);
 
     void cancel_and_remove_failed_requests();
 };
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
index c9277a5..2229e1c 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -267,7 +267,7 @@
     signal(SIGPIPE, sigpipe_handler);
 
     WorkerThreadData* data = (WorkerThreadData*)cookie;
-    status_t err = data->section->BlockingCall(data->pipe.writeFd().get());
+    status_t err = data->section->BlockingCall(data->pipe.writeFd());
 
     {
         unique_lock<mutex> lock(data->lock);
@@ -458,7 +458,7 @@
 
 DumpsysSection::~DumpsysSection() {}
 
-status_t DumpsysSection::BlockingCall(int pipeWriteFd) const {
+status_t DumpsysSection::BlockingCall(unique_fd& pipeWriteFd) const {
     // checkService won't wait for the service to show up like getService will.
     sp<IBinder> service = defaultServiceManager()->checkService(mService);
 
@@ -467,7 +467,7 @@
         return NAME_NOT_FOUND;
     }
 
-    service->dump(pipeWriteFd, mArgs);
+    service->dump(pipeWriteFd.get(), mArgs);
 
     return NO_ERROR;
 }
@@ -526,7 +526,7 @@
     return src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
 }
 
-status_t LogSection::BlockingCall(int pipeWriteFd) const {
+status_t LogSection::BlockingCall(unique_fd& pipeWriteFd) const {
     // Open log buffer and getting logs since last retrieved time if any.
     unique_ptr<logger_list, void (*)(logger_list*)> loggers(
             gLastLogsRetrieved.find(mLogID) == gLastLogsRetrieved.end()
@@ -643,7 +643,7 @@
         }
     }
     gLastLogsRetrieved[mLogID] = lastTimestamp;
-    if (!proto.flush(pipeWriteFd) && errno == EPIPE) {
+    if (!proto.flush(pipeWriteFd.get()) && errno == EPIPE) {
         ALOGE("[%s] wrote to a broken pipe\n", this->name.string());
         return EPIPE;
     }
@@ -660,7 +660,7 @@
 
 TombstoneSection::~TombstoneSection() {}
 
-status_t TombstoneSection::BlockingCall(int pipeWriteFd) const {
+status_t TombstoneSection::BlockingCall(unique_fd& pipeWriteFd) const {
     std::unique_ptr<DIR, decltype(&closedir)> proc(opendir("/proc"), closedir);
     if (proc.get() == nullptr) {
         ALOGE("opendir /proc failed: %s\n", strerror(errno));
@@ -768,7 +768,7 @@
         dumpPipe.readFd().reset();
     }
 
-    if (!proto.flush(pipeWriteFd) && errno == EPIPE) {
+    if (!proto.flush(pipeWriteFd.get()) && errno == EPIPE) {
         ALOGE("[%s] wrote to a broken pipe\n", this->name.string());
         if (err != NO_ERROR) {
             return EPIPE;
@@ -778,6 +778,22 @@
     return err;
 }
 
+// ================================================================================
+BringYourOwnSection::BringYourOwnSection(int id, const char* customName, const uid_t callingUid,
+        const sp<IIncidentDumpCallback>& callback)
+    : WorkerThreadSection(id, REMOTE_CALL_TIMEOUT_MS), uid(callingUid), mCallback(callback) {
+    name = "registered ";
+    name += customName;
+}
+
+BringYourOwnSection::~BringYourOwnSection() {}
+
+status_t BringYourOwnSection::BlockingCall(unique_fd& pipeWriteFd) const {
+    android::os::ParcelFileDescriptor pfd(std::move(pipeWriteFd));
+    mCallback->onDumpSection(pfd);
+    return NO_ERROR;
+}
+
 }  // namespace incidentd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h
index fcf12f7..0bb9da9 100644
--- a/cmds/incidentd/src/Section.h
+++ b/cmds/incidentd/src/Section.h
@@ -23,6 +23,8 @@
 #include <stdarg.h>
 #include <map>
 
+#include <android/os/IIncidentDumpCallback.h>
+
 #include <utils/String16.h>
 #include <utils/String8.h>
 #include <utils/Vector.h>
@@ -89,7 +91,7 @@
 
     virtual status_t Execute(ReportWriter* writer) const;
 
-    virtual status_t BlockingCall(int pipeWriteFd) const = 0;
+    virtual status_t BlockingCall(unique_fd& pipeWriteFd) const = 0;
 };
 
 /**
@@ -117,7 +119,7 @@
     DumpsysSection(int id, const char* service, ...);
     virtual ~DumpsysSection();
 
-    virtual status_t BlockingCall(int pipeWriteFd) const;
+    virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
 
 private:
     String16 mService;
@@ -132,7 +134,7 @@
     SystemPropertyDumpsysSection(int id, const char* service, ...);
     virtual ~SystemPropertyDumpsysSection();
 
-    virtual status_t BlockingCall(int pipeWriteFd) const;
+    virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
 
 private:
     String16 mService;
@@ -153,7 +155,7 @@
     LogSection(int id, const char* logID, ...);
     virtual ~LogSection();
 
-    virtual status_t BlockingCall(int pipeWriteFd) const;
+    virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
 
 private:
     log_id_t mLogID;
@@ -169,12 +171,29 @@
     TombstoneSection(int id, const char* type, int64_t timeoutMs = 120000 /* 2 minutes */);
     virtual ~TombstoneSection();
 
-    virtual status_t BlockingCall(int pipeWriteFd) const;
+    virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
 
 private:
     std::string mType;
 };
 
+/**
+ * Section that gets data from a registered dump callback.
+ */
+class BringYourOwnSection : public WorkerThreadSection {
+public:
+    const uid_t uid;
+
+    BringYourOwnSection(int id, const char* customName, const uid_t callingUid,
+        const sp<IIncidentDumpCallback>& callback);
+    virtual ~BringYourOwnSection();
+
+    virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
+
+private:
+    const sp<IIncidentDumpCallback>& mCallback;
+};
+
 
 /**
  * These sections will not be generated when doing an 'all' report, either
diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java
index 6033655..c2ee6dc 100644
--- a/cmds/sm/src/com/android/commands/sm/Sm.java
+++ b/cmds/sm/src/com/android/commands/sm/Sm.java
@@ -103,6 +103,10 @@
             runSetVirtualDisk();
         } else if ("set-isolated-storage".equals(op)) {
             runIsolatedStorage();
+        } else if ("start-checkpoint".equals(op)) {
+            runStartCheckpoint();
+        } else if ("supports-checkpoint".equals(op)) {
+            runSupportsCheckpoint();
         } else {
             throw new IllegalArgumentException();
         }
@@ -313,6 +317,27 @@
         }
     }
 
+    private void runStartCheckpoint() throws RemoteException {
+        final String numRetriesString = nextArg();
+        if (numRetriesString == null) {
+            throw new IllegalArgumentException("Expected <num-retries>");
+        }
+        int numRetries;
+        try {
+            numRetries = Integer.parseInt(numRetriesString);
+        } catch (NumberFormatException e) {
+            throw new IllegalArgumentException("<num-retries> must be a positive integer");
+        }
+        if (numRetries <= 0) {
+            throw new IllegalArgumentException("<num-retries> must be a positive integer");
+        }
+        mSm.startCheckpoint(numRetries);
+    }
+
+    private void runSupportsCheckpoint() throws RemoteException {
+        System.out.println(mSm.supportsCheckpoint());
+    }
+
     private String nextArg() {
         if (mNextArg >= mArgs.length) {
             return null;
@@ -344,6 +369,10 @@
         System.err.println("");
         System.err.println("       sm set-isolated-storage [on|off|default]");
         System.err.println("");
+        System.err.println("       sm start-checkpoint <num-retries>");
+        System.err.println("");
+        System.err.println("       sm supports-checkpoint");
+        System.err.println("");
         return 1;
     }
 }
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 7b96ce9..080b1af 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -73,12 +73,9 @@
         "src/external/puller_util.cpp",
         "src/external/ResourceHealthManagerPuller.cpp",
         "src/external/StatsCallbackPuller.cpp",
-        "src/external/StatsCallbackPullerDeprecated.cpp",
-        "src/external/StatsCompanionServicePuller.cpp",
         "src/external/StatsPuller.cpp",
         "src/external/StatsPullerManager.cpp",
         "src/external/SubsystemSleepStatePuller.cpp",
-        "src/external/SurfaceflingerStatsPuller.cpp",
         "src/external/TrainInfoPuller.cpp",
         "src/FieldValue.cpp",
         "src/guardrail/StatsdStats.cpp",
@@ -126,30 +123,28 @@
     ],
 
     static_libs: [
-        "libhealthhalutils",
-        "libplatformprotos",
-    ],
-
-    shared_libs: [
         "android.frameworks.stats@1.0",
-        "android.hardware.health@2.0",
         "android.hardware.power.stats@1.0",
         "android.hardware.power@1.0",
         "android.hardware.power@1.1",
         "libbase",
-        "libbinder",
         "libcutils",
+        "libhealthhalutils",
+        "liblog",
+        "libplatformprotos",
+        "libprotoutil",
+        "libstatslog",
+        "libstatssocket",
+        "libsysutils",
+    ],
+    shared_libs: [
+        "android.hardware.health@2.0",
+        "libbinder",
         "libgraphicsenv",
         "libhidlbase",
         "libincident",
-        "liblog",
-        "libprotoutil",
         "libservices",
-        "libstatslog",
         "libstatsmetadata",
-        "libstatssocket",
-        "libsysutils",
-        "libtimestats_proto",
         "libutils",
     ],
 }
@@ -298,7 +293,6 @@
         "tests/external/puller_util_test.cpp",
         "tests/external/StatsCallbackPuller_test.cpp",
         "tests/external/StatsPuller_test.cpp",
-        "tests/external/SurfaceflingerStatsPuller_test.cpp",
         "tests/FieldValue_test.cpp",
         "tests/guardrail/StatsdStats_test.cpp",
         "tests/indexed_priority_queue_test.cpp",
diff --git a/cmds/statsd/OWNERS b/cmds/statsd/OWNERS
index 04464ce..a61babf 100644
--- a/cmds/statsd/OWNERS
+++ b/cmds/statsd/OWNERS
@@ -1,7 +1,8 @@
-jianjin@google.com
+jeffreyhuang@google.com
 joeo@google.com
 jtnguyen@google.com
 muhammadq@google.com
+ruchirr@google.com
 singhtejinder@google.com
 tsaichristine@google.com
 yaochen@google.com
diff --git a/cmds/statsd/src/FieldValue.h b/cmds/statsd/src/FieldValue.h
index 6fc1e23..967fd32 100644
--- a/cmds/statsd/src/FieldValue.h
+++ b/cmds/statsd/src/FieldValue.h
@@ -261,6 +261,11 @@
     return Matcher(Field(tag, getSimpleField(field)), 0xff7f0000);
 }
 
+inline Matcher getFirstUidMatcher(int32_t atomId) {
+    int32_t pos[] = {1, 1, 1};
+    return Matcher(Field(atomId, pos, 2), 0xff7f7f7f);
+}
+
 /**
  * A wrapper for a union type to contain multiple types of values.
  *
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index b2a5b50..c1a8d69 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -1155,11 +1155,10 @@
 }
 
 Status StatsService::addConfiguration(int64_t key, const vector <uint8_t>& config,
-                                      const String16& packageName) {
-    ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+                                      const int32_t callingUid) {
+    ENFORCE_UID(AID_SYSTEM);
 
-    IPCThreadState* ipc = IPCThreadState::self();
-    if (addConfigurationChecked(ipc->getCallingUid(), key, config)) {
+    if (addConfigurationChecked(callingUid, key, config)) {
         return Status::ok();
     } else {
         ALOGE("Could not parse malformatted StatsdConfig");
@@ -1224,13 +1223,11 @@
     return Status::ok();
 }
 
-Status StatsService::removeConfiguration(int64_t key, const String16& packageName) {
-    ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+Status StatsService::removeConfiguration(int64_t key, const int32_t callingUid) {
+    ENFORCE_UID(AID_SYSTEM);
 
-    IPCThreadState* ipc = IPCThreadState::self();
-    ConfigKey configKey(ipc->getCallingUid(), key);
+    ConfigKey configKey(callingUid, key);
     mConfigManager->RemoveConfig(configKey);
-    SubscriberReporter::getInstance().removeConfig(configKey);
     return Status::ok();
 }
 
@@ -1268,16 +1265,6 @@
     return Status::ok();
 }
 
-Status StatsService::registerPullerCallback(int32_t atomTag,
-        const sp<android::os::IStatsPullerCallback>& pullerCallback,
-        const String16& packageName) {
-    ENFORCE_DUMP_AND_USAGE_STATS(packageName);
-
-    VLOG("StatsService::registerPullerCallback called.");
-    mPullerManager->RegisterPullerCallback(atomTag, pullerCallback);
-    return Status::ok();
-}
-
 Status StatsService::registerPullAtomCallback(int32_t uid, int32_t atomTag, int64_t coolDownNs,
                                     int64_t timeoutNs, const std::vector<int32_t>& additiveFields,
                                     const sp<android::os::IPullAtomCallback>& pullerCallback) {
@@ -1300,14 +1287,6 @@
     return Status::ok();
 }
 
-Status StatsService::unregisterPullerCallback(int32_t atomTag, const String16& packageName) {
-    ENFORCE_DUMP_AND_USAGE_STATS(packageName);
-
-    VLOG("StatsService::unregisterPullerCallback called.");
-    mPullerManager->UnregisterPullerCallback(atomTag);
-    return Status::ok();
-}
-
 Status StatsService::unregisterPullAtomCallback(int32_t uid, int32_t atomTag) {
     ENFORCE_UID(AID_SYSTEM);
     VLOG("StatsService::unregisterPullAtomCallback called.");
@@ -1315,6 +1294,13 @@
     return Status::ok();
 }
 
+Status StatsService::unregisterNativePullAtomCallback(int32_t atomTag) {
+    VLOG("StatsService::unregisterNativePullAtomCallback called.");
+    int32_t uid = IPCThreadState::self()->getCallingUid();
+    mPullerManager->UnregisterPullAtomCallback(uid, atomTag);
+    return Status::ok();
+}
+
 Status StatsService::sendBinaryPushStateChangedAtom(const android::String16& trainNameIn,
                                                     const int64_t trainVersionCodeIn,
                                                     const int options,
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 56d87f2..f2079d9 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -115,7 +115,7 @@
      */
     virtual Status addConfiguration(int64_t key,
                                     const vector<uint8_t>& config,
-                                    const String16& packageName) override;
+                                    const int32_t callingUid) override;
 
     /**
      * Binder call to let clients register the data fetch operation for a configuration.
@@ -145,7 +145,7 @@
      * Binder call to allow clients to remove the specified configuration.
      */
     virtual Status removeConfiguration(int64_t key,
-                                       const String16& packageName) override;
+                                       const int32_t callingUid) override;
 
     /**
      * Binder call to associate the given config's subscriberId with the given pendingIntentRef.
@@ -171,14 +171,6 @@
     virtual Status sendAppBreadcrumbAtom(int32_t label, int32_t state) override;
 
     /**
-     * Binder call to register a callback function for a vendor pulled atom.
-     * Note: this atom must NOT have uid as a field.
-     */
-    virtual Status registerPullerCallback(int32_t atomTag,
-        const sp<android::os::IStatsPullerCallback>& pullerCallback,
-        const String16& packageName) override;
-
-    /**
      * Binder call to register a callback function for a pulled atom.
      */
     virtual Status registerPullAtomCallback(int32_t uid, int32_t atomTag, int64_t coolDownNs,
@@ -193,16 +185,16 @@
             const sp<android::os::IPullAtomCallback>& pullerCallback) override;
 
     /**
-     * Binder call to unregister any existing callback function for a vendor pulled atom.
-     */
-    virtual Status unregisterPullerCallback(int32_t atomTag, const String16& packageName) override;
-
-    /**
      * Binder call to unregister any existing callback for the given uid and atom.
      */
     virtual Status unregisterPullAtomCallback(int32_t uid, int32_t atomTag) override;
 
     /**
+     * Binder call to unregister any existing callback for the given atom and calling uid.
+     */
+    virtual Status unregisterNativePullAtomCallback(int32_t atomTag) override;
+
+    /**
      * Binder call to log BinaryPushStateChanged atom.
      */
     virtual Status sendBinaryPushStateChangedAtom(
diff --git a/cmds/statsd/src/atom_field_options.proto b/cmds/statsd/src/atom_field_options.proto
index 6d2bd04..946c550 100644
--- a/cmds/statsd/src/atom_field_options.proto
+++ b/cmds/statsd/src/atom_field_options.proto
@@ -30,6 +30,8 @@
     PRIMARY = 1;
     // The field that represents the state. It's an exclusive state.
     EXCLUSIVE = 2;
+
+    PRIMARY_FIELD_FIRST_UID = 3;
 }
 
 // Used to annotate an atom that reprsents a state change. A state change atom must have exactly ONE
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 19b9709..d9b3a6c 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -53,6 +53,7 @@
 import "frameworks/base/core/proto/android/stats/mediaprovider/mediaprovider_enums.proto";
 import "frameworks/base/core/proto/android/stats/storage/storage_enums.proto";
 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/telecomm/enums.proto";
 import "frameworks/base/core/proto/android/telephony/enums.proto";
 import "frameworks/base/core/proto/android/view/enums.proto";
@@ -94,7 +95,7 @@
         ExcessiveCpuUsageReported excessive_cpu_usage_reported = 16;
         CachedKillReported cached_kill_reported = 17;
         ProcessMemoryStatReported process_memory_stat_reported = 18;
-        LauncherUIChanged launcher_event = 19;
+        LauncherUIChanged launcher_event = 19 [(module) = "sysui"];
         BatterySaverModeStateChanged battery_saver_mode_state_changed = 20;
         DeviceIdleModeStateChanged device_idle_mode_state_changed = 21;
         DeviceIdlingModeStateChanged device_idling_mode_state_changed = 22;
@@ -126,10 +127,10 @@
         AppStartOccurred app_start_occurred = 48;
         AppStartCanceled app_start_canceled = 49;
         AppStartFullyDrawn app_start_fully_drawn = 50;
-        LmkKillOccurred lmk_kill_occurred = 51;
+        LmkKillOccurred lmk_kill_occurred = 51 [(module) = "lmkd"];
         PictureInPictureStateChanged picture_in_picture_state_changed = 52;
         WifiMulticastLockStateChanged wifi_multicast_lock_state_changed = 53 [(module) = "wifi"];
-        LmkStateChanged lmk_state_changed = 54;
+        LmkStateChanged lmk_state_changed = 54 [(module) = "lmkd"];
         AppStartMemoryStateCaptured app_start_memory_state_captured = 55;
         ShutdownSequenceReported shutdown_sequence_reported = 56;
         BootSequenceReported boot_sequence_reported = 57;
@@ -137,9 +138,9 @@
         OverlayStateChanged overlay_state_changed = 59;
         ForegroundServiceStateChanged foreground_service_state_changed = 60;
         CallStateChanged call_state_changed = 61;
-        KeyguardStateChanged keyguard_state_changed = 62;
-        KeyguardBouncerStateChanged keyguard_bouncer_state_changed = 63;
-        KeyguardBouncerPasswordEntered keyguard_bouncer_password_entered = 64;
+        KeyguardStateChanged keyguard_state_changed = 62 [(module) = "sysui"];
+        KeyguardBouncerStateChanged keyguard_bouncer_state_changed = 63 [(module) = "sysui"];
+        KeyguardBouncerPasswordEntered keyguard_bouncer_password_entered = 64 [(module) = "sysui"];
         AppDied app_died = 65;
         ResourceConfigurationChanged resource_configuration_changed = 66;
         BluetoothEnabledStateChanged bluetooth_enabled_state_changed = 67;
@@ -230,7 +231,7 @@
         UsbContaminantReported usb_contaminant_reported = 146;
         WatchdogRollbackOccurred watchdog_rollback_occurred = 147;
         BiometricSystemHealthIssueDetected biometric_system_health_issue_detected = 148;
-        BubbleUIChanged bubble_ui_changed = 149;
+        BubbleUIChanged bubble_ui_changed = 149 [(module) = "sysui"];
         ScheduledJobConstraintChanged scheduled_job_constraint_changed = 150;
         BluetoothActiveDeviceChanged bluetooth_active_device_changed = 151;
         BluetoothA2dpPlaybackStateChanged bluetooth_a2dp_playback_state_changed = 152;
@@ -261,7 +262,7 @@
         AssistGestureProgressReported assist_gesture_progress_reported = 176;
         TouchGestureClassified touch_gesture_classified = 177;
         HiddenApiUsed hidden_api_used = 178 [(allow_from_any_uid) = true];
-        StyleUIChanged style_ui_changed = 179 [(module) = "style"];
+        StyleUIChanged style_ui_changed = 179 [(module) = "sysui"];
         PrivacyIndicatorsInteracted privacy_indicators_interacted =
             180 [(module) = "permissioncontroller"];
         AppInstallOnExternalStorageReported app_install_on_external_storage_reported = 181;
@@ -316,7 +317,7 @@
         ConversationActionsEvent conversation_actions_event = 221  [(module) = "textclassifier"];
         LanguageDetectionEvent language_detection_event = 222  [(module) = "textclassifier"];
         ExclusionRectStateChanged exclusion_rect_state_changed = 223;
-        BackGesture back_gesture_reported_reported = 224;
+        BackGesture back_gesture_reported_reported = 224 [(module) = "sysui"];
         UpdateEngineUpdateAttemptReported update_engine_update_attempt_reported = 225;
         UpdateEngineSuccessfulUpdateReported update_engine_successful_update_reported = 226;
         CameraActionEvent camera_action_event = 227;
@@ -324,8 +325,6 @@
             228 [(allow_from_any_uid) = true];
         PerfettoUploaded perfetto_uploaded = 229 [(module) = "perfetto"];
         VmsClientConnectionStateChanged vms_client_connection_state_changed = 230;
-        GpsLocationStatusReported gps_location_status_reported = 231;
-        GpsTimeToFirstFixReported gps_time_to_first_fix_reported = 232;
         MediaProviderScanEvent media_provider_scan_event = 233 [(module) = "mediaprovider"];
         MediaProviderDeletionEvent media_provider_deletion_event = 234 [(module) = "mediaprovider"];
         MediaProviderPermissionEvent media_provider_permission_event =
@@ -333,10 +332,19 @@
         MediaProviderSchemaChange media_provider_schema_change = 236 [(module) = "mediaprovider"];
         MediaProviderIdleMaintenance media_provider_idle_maintenance =
             237 [(module) = "mediaprovider"];
+        RebootEscrowRecoveryReported reboot_escrow_recovery_reported = 238;
+        BootTimeEventDuration boot_time_event_duration_reported = 239;
+        BootTimeEventElapsedTime boot_time_event_elapsed_time_reported = 240;
+        BootTimeEventUtcTime boot_time_event_utc_time_reported = 241;
+        BootTimeEventErrorCode boot_time_event_error_code_reported = 242;
+        UserspaceRebootReported userspace_reboot_reported = 243;
+        NotificationReported notification_reported = 244;
+        NotificationPanelReported notification_panel_reported = 245;
+        NotificationChannelModified notification_panel_modified = 246;
     }
 
     // Pulled events will start at field 10000.
-    // Next: 10068
+    // Next: 10071
     oneof pulled {
         WifiBytesTransfer wifi_bytes_transfer = 10000;
         WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001;
@@ -393,7 +401,7 @@
         ExternalStorageInfo external_storage_info = 10053;
         GpuStatsGlobalInfo gpu_stats_global_info = 10054;
         GpuStatsAppInfo gpu_stats_app_info = 10055;
-        SystemIonHeapSize system_ion_heap_size = 10056;
+        SystemIonHeapSize system_ion_heap_size = 10056 [deprecated = true];
         AppsOnExternalStorageInfo apps_on_external_storage_info = 10057;
         FaceSettings face_settings = 10058;
         CoolingDevice cooling_device = 10059;
@@ -405,6 +413,9 @@
         VmsClientStats vms_client_stats = 10065;
         NotificationRemoteViews notification_remote_views = 10066;
         DangerousPermissionStateSampled dangerous_permission_state_sampled = 10067;
+        GraphicsStats graphics_stats = 10068;
+        RuntimeAppOpsAccess runtime_app_ops_access = 10069;
+        IonHeapSize ion_heap_size = 10070;
     }
 
     // DO NOT USE field numbers above 100,000 in AOSP.
@@ -729,27 +740,6 @@
     optional android.server.location.GpsSignalQualityEnum level = 1;
 }
 
-/**
- * Gps location status report
- *
- * Logged from:
- *   /frameworks/base/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
- */
-message GpsLocationStatusReported {
-    // Boolean stating if location was acquired
-    optional bool location_success = 1;
-}
-
-/**
- * Gps log time to first fix report
- *
- * Logged from:
- *   /frameworks/base/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
- */
-message GpsTimeToFirstFixReported {
-    // int32 reporting the time to first fix in milliseconds
-    optional int32 time_to_first_fix_millis = 1;
-}
 
 /**
  * Logs when a sync manager sync state changes.
@@ -820,6 +810,7 @@
         FREQUENT = 2;
         RARE = 3;
         NEVER = 4;
+        RESTRICTED = 5;
     }
     optional Bucket standby_bucket = 5 [default = UNKNOWN];
 
@@ -906,14 +897,16 @@
  *   TODO
  */
 message WakelockStateChanged {
-    repeated AttributionNode attribution_node = 1;
+    repeated AttributionNode attribution_node = 1
+            [(state_field_option).option = PRIMARY_FIELD_FIRST_UID];
 
     // The type (level) of the wakelock; e.g. a partial wakelock or a full wakelock.
     // From frameworks/base/core/proto/android/os/enums.proto.
-    optional android.os.WakeLockLevelEnum type = 2;
+    optional android.os.WakeLockLevelEnum type = 2 [(state_field_option).option = PRIMARY];
+    ;
 
     // The wakelock tag (Called tag in the Java API, sometimes name elsewhere).
-    optional string tag = 3;
+    optional string tag = 3 [(state_field_option).option = PRIMARY];
 
     enum State {
         RELEASE = 0;
@@ -921,7 +914,7 @@
         CHANGE_RELEASE = 2;
         CHANGE_ACQUIRE = 3;
     }
-    optional State state = 4;
+    optional State state = 4 [(state_field_option).option = EXCLUSIVE];
 }
 
 /**
@@ -1731,6 +1724,7 @@
         REASON_EXPLICIT_HEALTH_CHECK = 2;
         REASON_APP_CRASH = 3;
         REASON_APP_NOT_RESPONDING = 4;
+        REASON_NATIVE_CRASH_DURING_BOOT = 5;
     }
     optional RollbackReasonType rollback_reason = 4;
 
@@ -3299,6 +3293,10 @@
  * this button" or "this dialog was displayed".
  * Keep the UI event stream clean: don't use for system or background events.
  * Log using the UiEventLogger wrapper - don't write with the StatsLog API directly.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/
+ *   frameworks/base/packages/SystemUI/src/com/android/systemui/
  */
 message UiEventReported {
     // The event_id.
@@ -3307,9 +3305,128 @@
     // For example, the package posting a notification, or the destination package of a share.
     optional int32 uid = 2 [(is_uid) = true];
     optional string package_name = 3;
+    // An identifier used to disambiguate which logs refer to a particular instance of some
+    // UI element. Useful when there might be multiple instances simultaneously active.
+    optional int32 instance_id = 4;
 }
 
 /**
+ * Reports a notification was created or updated.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/notification/
+ */
+message NotificationReported {
+    // The event_id (as for UiEventReported).
+    optional int32 event_id = 1;
+    // The notifying app's uid and package.
+    optional int32 uid = 2 [(is_uid) = true];
+    optional string package_name = 3;
+    // A small system-assigned identifier for the notification.
+    // Locally probably-unique, but expect collisions across users and/or days.
+    optional int32 instance_id = 4;
+    // The app-assigned notification ID and tag
+    optional int32 notification_id = 5;
+    optional string notification_tag = 6;
+    optional string channel_id = 7;  // App-assigned channel ID
+
+    // Grouping information
+    optional string group_id = 8;  // Group the notification currently belongs to
+    optional int32 group_instance_id = 9;  // Instance_id of the group-summary notification
+    optional bool is_group_summary = 10;  // Tags the group-summary notification
+
+    // Attributes
+    optional string category = 11;   // App-assigned notification category (API-defined strings)
+    optional int32 style = 12;       // App-assigned notification style
+    optional int32 num_people = 13;  // Number of Person records attached to the notification
+
+    // Ordering, importance and interruptiveness
+
+    optional int32 position = 14;    // Position in NotificationManager's list
+
+    optional android.stats.sysui.NotificationImportance importance = 15;
+    optional int32 alerting = 16;    // Bitfield, 1=buzz 2=beep 4=blink
+
+    enum NotificationImportanceExplanation {
+        IMPORTANCE_EXPLANATION_UNKNOWN = 0;
+        IMPORTANCE_EXPLANATION_APP = 1;     // App-specified channel importance.
+        IMPORTANCE_EXPLANATION_USER = 2;    // User-specified channel importance.
+        IMPORTANCE_EXPLANATION_ASST = 3;    // Notification Assistant override.
+        IMPORTANCE_EXPLANATION_SYSTEM = 4;  // System override.
+        // Like _APP, but based on pre-channels priority signal.
+        IMPORTANCE_EXPLANATION_APP_PRE_CHANNELS = 5;
+    }
+
+    optional NotificationImportanceExplanation importance_source = 17;
+    optional android.stats.sysui.NotificationImportance importance_initial = 18;
+    optional NotificationImportanceExplanation importance_initial_source = 19;
+    optional android.stats.sysui.NotificationImportance importance_asst = 20;
+    optional int32 assistant_hash = 21;
+    optional float assistant_ranking_score = 22;
+}
+
+message Notification {
+    // The notifying app's uid and package.
+    optional int32 uid = 1 [(is_uid) = true];
+    optional string package_name = 2;
+    // A small system-assigned identifier for the notification.
+    optional int32 instance_id = 3;
+
+    // Grouping information.
+    optional int32 group_instance_id = 4;
+    optional bool is_group_summary = 5;
+
+    // The section of the shade that the notification is in.
+    // See NotificationSectionsManager.PriorityBucket.
+    enum NotificationSection {
+        SECTION_UNKNOWN = 0;
+        SECTION_PEOPLE = 1;
+        SECTION_ALERTING = 2;
+        SECTION_SILENT = 3;
+    }
+    optional NotificationSection section = 6;
+}
+
+message NotificationList {
+    repeated Notification notifications = 1;  // An ordered sequence of notifications.
+}
+
+/**
+ * Reports a notification panel was displayed, e.g. from the lockscreen or status bar.
+ *
+ * Logged from:
+ *   frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/
+ */
+message NotificationPanelReported {
+    // The event_id (as for UiEventReported).
+    optional int32 event_id = 1;
+    optional int32 num_notifications = 2;
+    // The notifications in the panel, in the order that they appear there.
+    optional NotificationList notifications = 3 [(log_mode) = MODE_BYTES];
+}
+
+/**
+ * Reports a notification channel, or channel group, was created, updated, or deleted.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/notification/
+ */
+message NotificationChannelModified {
+    // The event_id (as for UiEventReported).
+    optional int32 event_id = 1;
+    // The notifying app's uid and package.
+    optional int32 uid = 2 [(is_uid) = true];
+    optional string package_name = 3;
+    // App-assigned notification channel ID or channel-group ID
+    optional string channel_id = 4;
+    // Previous importance setting, if applicable
+    optional android.stats.sysui.NotificationImportance old_importance = 5;
+    // New importance setting
+    optional android.stats.sysui.NotificationImportance importance = 6;
+}
+
+
+/**
  * Logs when a biometric acquire event occurs.
  *
  * Logged from:
@@ -3497,12 +3614,14 @@
         INSTALL_FAILURE_DOWNLOAD = 23;
         INSTALL_FAILURE_STATE_MISMATCH = 24;
         INSTALL_FAILURE_COMMIT = 25;
+        REBOOT_TRIGGERED = 26;
     }
     optional State state = 6;
     // Possible experiment ids for monitoring this push.
     optional TrainExperimentIds experiment_ids = 7 [(log_mode) = MODE_BYTES];
     // user id
     optional int32 user_id = 8;
+    optional int32 reason = 9;
 }
 
 /* Test atom, is not logged anywhere */
@@ -3923,6 +4042,207 @@
     optional float normalized_expired_media = 5;
 }
 
+/**
+ * Represents boot time event with duration in ms.
+ *
+ * Logged from: bootstat and various system server components. Check each enums for details.
+ */
+message BootTimeEventDuration {
+    enum DurationEvent {
+        UNKNOWN = 0;
+        // Bootloader time excluding BOOTLOADER_UI_WAIT + boot complete time. Logged from bootstat.
+        ABSOLUTE_BOOT_TIME = 1;
+        // Bootloader's 1st stage execution time.
+        // Logged from bootstat.
+        BOOTLOADER_FIRST_STAGE_EXEC = 2;
+        // Bootloader's 1st stage loading time.
+        // Logged from bootstat.
+        BOOTLOADER_FIRST_STAGE_LOAD = 3;
+        // Bootloader's kernel loading time.
+        // Logged from bootstat.
+        BOOTLOADER_KERNEL_LOAD = 4;
+        // Bootloader's 2nd stage execution time.
+        // Logged from bootstat.
+        BOOTLOADER_SECOND_STAGE_EXEC = 5;
+        // Bootloader's 2nd stage loading time.
+        // Logged from bootstat.
+        BOOTLOADER_SECOND_STAGE_LOAD = 6;
+        // Duration for Bootloader to show unlocked device's warning UI. This should not happen
+        // for locked device.
+        // Logged from bootstat.
+        BOOTLOADER_UI_WAIT = 7;
+        // Total time spend in bootloader. This is the sum of all BOOTLOADER_* listed above.
+        // Logged from bootstat.
+        BOOTLOADER_TOTAL = 8;
+        // Shutdown duration inside init for the reboot before the current boot up.
+        // Logged from f/b/services/.../BootReceiver.java.
+        SHUTDOWN_DURATION = 9;
+        // Total time for mounting of disk devices during bootup.
+        // Logged from f/b/services/.../BootReceiver.java.
+        MOUNT_DEFAULT_DURATION = 10;
+        // Total time for early stage mounting of disk devices during bootup.
+        // Logged from f/b/services/.../BootReceiver.java.
+        MOUNT_EARLY_DURATION = 11;
+        // Total time for late stage mounting of disk devices during bootup.
+        // Logged from f/b/services/.../BootReceiver.java.
+        MOUNT_LATE_DURATION = 12;
+        // Average time to scan non-system app after OTA
+        // Logged from f/b/services/.../PackageManagerService.java
+        OTA_PACKAGE_MANAGER_INIT_TIME = 13;
+        // Time to initialize Package manager after OTA
+        // Logged from f/b/services/.../PackageManagerService.java
+        OTA_PACKAGE_MANAGER_DATA_APP_AVG_SCAN_TIME = 14;
+        // Time to scan all system app from Package manager after OTA
+        // Logged from f/b/services/.../PackageManagerService.java
+        OTA_PACKAGE_MANAGER_SYSTEM_APP_AVG_SCAN_TIME = 15;
+        // Init's total time for cold boot stage.
+        // Logged from bootstat.
+        COLDBOOT_WAIT = 16;
+        // Init's total time for initializing selinux.
+        // Logged from bootstat.
+        SELINUX_INIT = 17;
+        // Time since last factory reset.
+        // Logged from bootstat.
+        FACTORY_RESET_TIME_SINCE_RESET = 18;
+    }
+
+    // Type of the event.
+    optional DurationEvent event = 1;
+    // Duration of the event in ms.
+    optional int64 duration_millis = 2;
+}
+
+/**
+ * Represents the start of specific boot time event during bootup in ms. This is usually a time
+ * since boot-up.
+ *
+ * Logged from: bootstat and various system server components. Check each enums for details.
+ */
+message BootTimeEventElapsedTime {
+    enum ElapsedTimeEvent {
+        UNKNOWN = 0;
+        // Time when init starts 1st stage. Logged from bootstat.
+        ANDROID_INIT_STAGE_1 = 1;
+        // Time when sys.boot_completed prop is set.
+        // Logged from bootstat.
+        BOOT_COMPLETE = 2;
+        // BOOT_COMPLETE for encrypted device.
+        BOOT_COMPLETE_ENCRYPTION = 3;
+        // BOOT_COMPLETE for device with no encryption.
+        BOOT_COMPLETE_NO_ENCRYPTION = 4;
+        // Adjusted BOOT_COMPLETE for encrypted device extracting decryption time.
+        BOOT_COMPLETE_POST_DESCRYPT = 5;
+        // BOOT_COMPLETE after factory reset.
+        FACTORY_RESET_BOOT_COMPLETE = 6;
+        // BOOT_COMPLETE_NO_ENCRYPTION after factory reset.
+        FACTORY_RESET_BOOT_COMPLETE_NO_ENCRYPTION = 7;
+        // BOOT_COMPLETE_POST_DESCRYPT after factory reset.
+        FACTORY_RESET_BOOT_COMPLETE_POST_DESCRYPT = 8;
+        // BOOT_COMPLETE after OTA.
+        OTA_BOOT_COMPLETE = 9;
+        // BOOT_COMPLETE_NO_ENCRYPTION after OTA.
+        OTA_BOOT_COMPLETE_NO_ENCRYPTION = 10;
+        // BOOT_COMPLETE_POST_DESCRYPT after OTA.
+        OTA_BOOT_COMPLETE_POST_DESCRYPT = 11;
+        // Time when the system starts sending LOCKED_BOOT_COMPLETED broadcast.
+        // Logged from  f/b/services/.../UserController.java
+        FRAMEWORK_LOCKED_BOOT_COMPLETED = 12;
+        // Time when the system starts sending BOOT_COMPLETED broadcast.
+        // Logged from  f/b/services/.../UserController.java
+        FRAMEWORK_BOOT_COMPLETED = 13;
+        // Time when the package manager starts init.
+        // Logged from f/b/services/.../SystemServer.java
+        PACKAGE_MANAGER_INIT_START = 14;
+        // Time when package manager is ready
+        // Logged from f/b/services/.../SystemServer.java
+        PACKAGE_MANAGER_INIT_READY = 15;
+        // Represents the time when user has entered unlock credential for system with user pin.
+        // Logged from bootstat.
+        POST_DECRYPT = 16;
+        // Represents the start of zygote's init.
+        // Logged from zygote itself.
+        ZYGOTE_INIT_START = 17;
+        // Represents the start of secondary zygote's init.
+        // TODO: add logging to zygote
+        SECONDARY_ZYGOTE_INIT_START = 18;
+        // Represents the start of system server's init.
+        // Logged from f/b/services/.../SystemServer.java
+        SYSTEM_SERVER_INIT_START = 19;
+        // Represents the completion of system server's init.
+        // Logged from f/b/services/.../SystemServer.java
+        SYSTEM_SERVER_READY = 20;
+        // Represents the start of launcher during boot-up.
+        // TODO: add logging
+        LAUNCHER_START = 21;
+        // Represents the completion of launcher's initial rendering. User can use other apps from
+        // launcher from this point.
+        // TODO: add logging
+        LAUNCHER_SHOWN = 22;
+    }
+
+    // Type of the event.
+    optional ElapsedTimeEvent event = 1;
+    // Time since bootup for the event.
+    // It should be acquired from SystemClock elapsedRealtime() call or equivalent.
+    optional int64 time_millis = 2;
+}
+
+/**
+ * Boot time events with UTC time.
+ *
+ * Logged from: bootstat and various system server components. Check each enums for details.
+ */
+message BootTimeEventUtcTime {
+    enum UtcTimeEvent {
+        UNKNOWN = 0;
+        // Time of the bootstat's marking of 1st boot after the last factory reset.
+        // Logged from bootstat.
+        FACTORY_RESET_RESET_TIME = 1;
+        // The time when bootstat records FACTORY_RESET_* events. This is close to
+        // BOOT_COMPLETE time for the current bootup.
+        // Logged from bootstat.
+        FACTORY_RESET_CURRENT_TIME = 2;
+        // DUplicate of FACTORY_RESET_RESET_TIME added for debugging purpose.
+        // Logged from bootstat.
+        FACTORY_RESET_RECORD_VALUE = 3;
+    }
+
+    // Type of the event.
+    optional UtcTimeEvent event = 1;
+    // UTC time for the event.
+    optional int64 utc_time_secs = 2;
+}
+
+/**
+ * Boot time events representing specific error code during bootup.
+ * Meaning of error code can be different per each event type.
+ *
+ * Logged from: bootstat and various system server components. Check each enums for details.
+ */
+message BootTimeEventErrorCode {
+    enum ErrorCodeEvent {
+        UNKNOWN = 0;
+        // Linux error code for time() call to get the current UTC time.
+        // Logged from bootstat.
+        FACTORY_RESET_CURRENT_TIME_FAILURE = 1;
+        // Represents UmountStat before the reboot for the current boot up. Error codes defined
+        // as UMOUNT_STAT_* from init/reboot.cpp.
+        // Logged from f/b/services/.../BootReceiver.java.
+        SHUTDOWN_UMOUNT_STAT = 2;
+        // Reprepsents fie system mounting error code of /data partition for the current boot.
+        // Error codes defined as combination of FsStatFlags from system/core/fs_mgr/fs_mgr.cpp.
+        // Logged from f/b/services/.../BootReceiver.java.
+        FS_MGR_FS_STAT_DATA_PARTITION = 3;
+    }
+
+    // Type of the event.
+    optional ErrorCodeEvent event = 1;
+    // error code defined per each event type.
+    // For example, this can have a value of FsStatFlags.FS_STAT_FULL_MOUNT_FAILED for the event of
+    // FS_MGR_FS_STAT.
+    optional int32 error_code = 2;
+}
+
 //////////////////////////////////////////////////////////////////////
 // Pulled atoms below this line //
 //////////////////////////////////////////////////////////////////////
@@ -4723,36 +5043,69 @@
 }
 
 message AggStats {
-    optional int64 min = 1;
+    // These are all in byte resolution.
+    optional int64 min = 1 [deprecated = true];
+    optional int64 average = 2 [deprecated = true];
+    optional int64 max = 3 [deprecated = true];
 
-    optional int64 average = 2;
-
-    optional int64 max = 3;
+    // These are all in kilobyte resolution. Can fit in int32, so smaller on the wire than the above
+    // int64 fields.
+    optional int32 mean_kb = 4;
+    optional int32 max_kb = 5;
 }
 
+// A reduced subset of process states; reducing the number of possible states allows more
+// aggressive device-side aggregation of statistics and hence reduces metric upload size.
+enum ProcessStateAggregated {
+    PROCESS_STATE_UNKNOWN = 0;
+    // Persistent system process.
+    PROCESS_STATE_PERSISTENT = 1;
+    // Top activity; actually any visible activity.
+    PROCESS_STATE_TOP = 2;
+    // Process binding to top or a foreground service.
+    PROCESS_STATE_BOUND_TOP_OR_FGS = 3;
+    // Processing running a foreground service.
+    PROCESS_STATE_FGS = 4;
+    // Important foreground process (ime, wallpaper, etc).
+    PROCESS_STATE_IMPORTANT_FOREGROUND = 5;
+    // Important background process.
+    PROCESS_STATE_BACKGROUND = 6;
+    // Process running a receiver.
+    PROCESS_STATE_RECEIVER = 7;
+    // All kinds of cached processes.
+    PROCESS_STATE_CACHED = 8;
+}
+
+// Next tag: 13
 message ProcessStatsStateProto {
     optional android.service.procstats.ScreenState screen_state = 1;
 
-    optional android.service.procstats.MemoryState memory_state = 2;
+    optional android.service.procstats.MemoryState memory_state = 2 [deprecated = true];
 
     // this enum list is from frameworks/base/core/java/com/android/internal/app/procstats/ProcessStats.java
     // and not frameworks/base/core/java/android/app/ActivityManager.java
-    optional android.service.procstats.ProcessState process_state = 3;
+    optional android.service.procstats.ProcessState process_state = 3 [deprecated = true];
+
+    optional ProcessStateAggregated process_state_aggregated = 10;
 
     // Millisecond uptime duration spent in this state
-    optional int64 duration_millis = 4;
+    optional int64 duration_millis = 4 [deprecated = true];
+    // Same as above, but with minute resolution so it fits into an int32.
+    optional int32 duration_minutes = 11;
 
     // Millisecond elapsed realtime duration spent in this state
-    optional int64 realtime_duration_millis = 9;
+    optional int64 realtime_duration_millis = 9 [deprecated = true];
+    // Same as above, but with minute resolution so it fits into an int32.
+    optional int32 realtime_duration_minutes = 12;
 
     // # of samples taken
     optional int32 sample_size = 5;
 
     // PSS is memory reserved for this process
-    optional AggStats pss = 6;
+    optional AggStats pss = 6 [deprecated = true];
 
     // USS is memory shared between processes, divided evenly for accounting
-    optional AggStats uss = 7;
+    optional AggStats uss = 7 [deprecated = true];
 
     // RSS is memory resident for this process
     optional AggStats rss = 8;
@@ -4777,7 +5130,7 @@
         // PSS stats during cached kill
         optional AggStats cached_pss = 3;
     }
-    optional Kill kill = 3;
+    optional Kill kill = 3 [deprecated = true];
 
     // Time and memory spent in various states.
     repeated ProcessStatsStateProto states = 5;
@@ -6560,6 +6913,7 @@
         INSTALL_FAILURE_DOWNLOAD = 23;
         INSTALL_FAILURE_STATE_MISMATCH = 24;
         INSTALL_FAILURE_COMMIT = 25;
+        REBOOT_TRIGGERED = 26;
     }
     optional Status status = 4;
 }
@@ -6702,11 +7056,27 @@
  * Pulled from StatsCompanionService.
  */
 message SystemIonHeapSize {
+    // Deprecated due to limited support of ion stats in debugfs.
+    // Use `IonHeapSize` instead.
+    option deprecated = true;
+
     // Size of the system ion heap in bytes.
+    // Read from debugfs.
     optional int64 size_in_bytes = 1;
 }
 
 /*
+ * Logs the total size of the ion heap.
+ *
+ * Pulled from StatsCompanionService.
+ */
+message IonHeapSize {
+    // Total size of all ion heaps in kilobytes.
+    // Read from: /sys/kernel/ion/total_heaps_kb.
+    optional int32 total_size_kb = 1;
+}
+
+/*
  * Logs the per-process size of the system ion heap.
  *
  * Pulled from StatsCompanionService.
@@ -6844,7 +7214,7 @@
     // Uid of the package requesting the op
     optional int32 uid = 1 [(is_uid) = true];
 
-    // Nmae of the package performing the op
+    // Name of the package performing the op
     optional string package_name = 2;
 
     // operation id; maps to the OP_* constants in AppOpsManager.java
@@ -7338,6 +7708,17 @@
 }
 
 /**
+ * Reported when the RebootEscrow HAL has attempted to recover the escrowed
+ * key to indicate whether it was successful or not.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+ */
+message RebootEscrowRecoveryReported {
+    optional bool successful = 1;
+}
+
+/**
  * Global display pipeline metrics reported by SurfaceFlinger.
  * Pulled from:
  *    frameworks/native/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -7553,3 +7934,139 @@
     optional int32 permission_flags = 4;
 }
 
+/**
+ * HWUI renders pipeline type: GL (0) or Vulkan (1).
+ */
+enum PipelineType {
+    GL = 0;
+    VULKAN = 1;
+}
+
+/**
+ * HWUI stats for a given app.
+ */
+message GraphicsStats {
+    // The package name of the app
+    optional string package_name = 1;
+
+    // The version code of the app
+    optional int64 version_code = 2;
+
+    // The start & end timestamps in UTC as
+    // milliseconds since January 1, 1970
+    // Compatible with java.util.Date#setTime()
+    optional int64 stats_start = 3;
+
+    optional int64 stats_end = 4;
+
+    // HWUI renders pipeline type: GL or Vulkan.
+    optional PipelineType pipeline = 5;
+
+    // Distinct frame count.
+    optional int32 total_frames = 6;
+
+    // Number of "missed vsync" events.
+    optional int32 missed_vsync_count = 7;
+
+    // Number of frames in triple-buffering scenario (high input latency)
+    optional int32 high_input_latency_count = 8;
+
+    // Number of "slow UI thread" events.
+    optional int32 slow_ui_thread_count = 9;
+
+    // Number of "slow bitmap upload" events.
+    optional int32 slow_bitmap_upload_count = 10;
+
+    // Number of "slow draw" events.
+    optional int32 slow_draw_count = 11;
+
+    // Number of frames that missed their deadline (aka, visibly janked)
+    optional int32 missed_deadline_count = 12;
+
+    // The frame time histogram for the package
+    optional FrameTimingHistogram cpu_histogram = 13
+    [(android.os.statsd.log_mode) = MODE_BYTES];
+
+    // The gpu frame time histogram for the package
+    optional FrameTimingHistogram gpu_histogram = 14
+    [(android.os.statsd.log_mode) = MODE_BYTES];
+
+    // UI mainline module version.
+    optional int64 version_ui_module = 15;
+
+    // If true, these are HWUI stats for up to a 24h period for a given app from today.
+    // If false, these are HWUI stats for a 24h period for a given app from the last complete
+    // day (yesterday). Stats from yesterday stay constant, while stats from today may change as
+    // more apps are running / rendering.
+    optional bool is_today = 16;
+}
+
+/**
+ * Message related to dangerous (runtime) app ops access
+ */
+message RuntimeAppOpsAccess {
+    // Uid of the package accessing app op
+    optional int32 uid = 1 [(is_uid) = true];
+
+    // Name of the package accessing app op
+    optional string package_name = 2;
+
+    // operation id; maps to the OP_* constants in AppOpsManager.java
+    optional int32 op_id = 3;
+
+    // feature id; provided by developer when accessing related API, limited at 50 chars by API.
+    // Features must be provided through manifest using <feature> tag available in R and above.
+    optional string feature_id = 4;
+
+    // message related to app op access, limited to 600 chars by API
+    optional string message = 5;
+
+    enum SamplingStrategy {
+        DEFAULT = 0;
+        UNIFORM = 1;
+        RARELY_USED = 2;
+    }
+
+    // sampling strategy used to collect this message
+    optional SamplingStrategy sampling_strategy = 6;
+}
+
+/*
+ * Logs userspace reboot outcome and duration.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/server/BootReceiver.java
+ */
+message UserspaceRebootReported {
+    // Possible outcomes of userspace reboot.
+    enum Outcome {
+        // Default value in case platform failed to determine the outcome.
+        OUTCOME_UNKNOWN = 0;
+        // Userspace reboot succeeded (i.e. boot completed without a fall back to hard reboot).
+        SUCCESS = 1;
+        // Userspace reboot shutdown sequence was aborted.
+        FAILED_SHUTDOWN_SEQUENCE_ABORTED = 2;
+        // Remounting userdata into checkpointing mode failed.
+        FAILED_USERDATA_REMOUNT = 3;
+        // Device didn't finish booting before timeout and userspace reboot watchdog issued a hard
+        // reboot.
+        FAILED_USERSPACE_REBOOT_WATCHDOG_TRIGGERED = 4;
+    }
+    // Outcome of userspace reboot. Always set.
+    optional Outcome outcome = 1;
+    // Duration of userspace reboot in case it has a successful outcome.
+    // Duration is measured as time between userspace reboot was initiated and until boot completed
+    // (e.g. sys.boot_completed=1).
+    optional int64 duration_millis = 2;
+    // State of primary user's (user0) credential encryption storage.
+    enum UserEncryptionState {
+        // Default value.
+        USER_ENCRYPTION_STATE_UNKNOWN = 0;
+        // Credential encrypted storage is unlocked.
+        UNLOCKED = 1;
+        // Credential encrypted storage is locked.
+        LOCKED = 2;
+    }
+    // State of primary user's encryption storage at the moment boot completed. Always set.
+    optional UserEncryptionState user_encryption_state = 3;
+}
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 55d73c1..972adf7 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -189,25 +189,11 @@
             // Remove from map
             uidIt->second.erase(key);
 
-            // No more configs for this uid, lets remove the active configs callback.
-            if (uidIt->second.empty()) {
-                auto itActiveConfigsChangedReceiver = mActiveConfigsChangedReceivers.find(uid);
-                    if (itActiveConfigsChangedReceiver != mActiveConfigsChangedReceivers.end()) {
-                        mActiveConfigsChangedReceivers.erase(itActiveConfigsChangedReceiver);
-                    }
-            }
-
             for (const sp<ConfigListener>& listener : mListeners) {
                 broadcastList.push_back(listener);
             }
         }
 
-        auto itReceiver = mConfigReceivers.find(key);
-        if (itReceiver != mConfigReceivers.end()) {
-            // Remove from map
-            mConfigReceivers.erase(itReceiver);
-        }
-
         // Remove from disk. There can still be a lingering file on disk so we check
         // whether or not the config was on memory.
         remove_saved_configs(key);
@@ -238,12 +224,6 @@
             // Remove from map
                 remove_saved_configs(*it);
                 removed.push_back(*it);
-                mConfigReceivers.erase(*it);
-        }
-
-        auto itActiveConfigsChangedReceiver = mActiveConfigsChangedReceivers.find(uid);
-        if (itActiveConfigsChangedReceiver != mActiveConfigsChangedReceivers.end()) {
-            mActiveConfigsChangedReceivers.erase(itActiveConfigsChangedReceiver);
         }
 
         mConfigs.erase(uidIt);
@@ -277,8 +257,6 @@
             uidIt = mConfigs.erase(uidIt);
         }
 
-        mConfigReceivers.clear();
-        mActiveConfigsChangedReceivers.clear();
         for (const sp<ConfigListener>& listener : mListeners) {
             broadcastList.push_back(listener);
         }
diff --git a/cmds/statsd/src/external/StatsCallbackPuller.cpp b/cmds/statsd/src/external/StatsCallbackPuller.cpp
index 0e6b677..e5a83a2 100644
--- a/cmds/statsd/src/external/StatsCallbackPuller.cpp
+++ b/cmds/statsd/src/external/StatsCallbackPuller.cpp
@@ -42,7 +42,7 @@
 }
 
 bool StatsCallbackPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) {
-    VLOG("StatsCallbackPuller called for tag %d", mTagId)
+    VLOG("StatsCallbackPuller called for tag %d", mTagId);
     if(mCallback == nullptr) {
         ALOGW("No callback registered");
         return false;
diff --git a/cmds/statsd/src/external/StatsCallbackPullerDeprecated.cpp b/cmds/statsd/src/external/StatsCallbackPullerDeprecated.cpp
deleted file mode 100644
index 4f88a91..0000000
--- a/cmds/statsd/src/external/StatsCallbackPullerDeprecated.cpp
+++ /dev/null
@@ -1,63 +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.
- */
-
-#define DEBUG false  // STOPSHIP if true
-#include "Log.h"
-
-#include "StatsCallbackPullerDeprecated.h"
-
-#include <android/os/IStatsPullerCallback.h>
-
-#include "logd/LogEvent.h"
-#include "stats_log_util.h"
-
-using namespace android::binder;
-
-namespace android {
-namespace os {
-namespace statsd {
-
-StatsCallbackPullerDeprecated::StatsCallbackPullerDeprecated(
-        int tagId, const sp<IStatsPullerCallback>& callback)
-    : StatsPuller(tagId), mCallback(callback) {
-    VLOG("StatsCallbackPuller created for tag %d", tagId);
-}
-
-bool StatsCallbackPullerDeprecated::PullInternal(vector<shared_ptr<LogEvent>>* data) {
-    VLOG("StatsCallbackPuller called for tag %d", mTagId)
-    if (mCallback == nullptr) {
-        ALOGW("No callback registered");
-        return false;
-    }
-    int64_t wallClockTimeNs = getWallClockNs();
-    int64_t elapsedTimeNs = getElapsedRealtimeNs();
-    vector<StatsLogEventWrapper> returned_value;
-    Status status = mCallback->pullData(mTagId, elapsedTimeNs, wallClockTimeNs, &returned_value);
-    if (!status.isOk()) {
-        ALOGW("StatsCallbackPuller::pull failed for %d", mTagId);
-        return false;
-    }
-    data->clear();
-    for (const StatsLogEventWrapper& it : returned_value) {
-        LogEvent::createLogEvents(it, *data);
-    }
-    VLOG("StatsCallbackPuller::pull succeeded for %d", mTagId);
-    return true;
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/external/StatsCallbackPullerDeprecated.h b/cmds/statsd/src/external/StatsCallbackPullerDeprecated.h
deleted file mode 100644
index 0289029..0000000
--- a/cmds/statsd/src/external/StatsCallbackPullerDeprecated.h
+++ /dev/null
@@ -1,39 +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.
- */
-
-#pragma once
-
-#include <android/os/IStatsPullerCallback.h>
-#include <utils/String16.h>
-
-#include "StatsPuller.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-class StatsCallbackPullerDeprecated : public StatsPuller {
-public:
-    explicit StatsCallbackPullerDeprecated(int tagId, const sp<IStatsPullerCallback>& callback);
-
-private:
-    bool PullInternal(vector<std::shared_ptr<LogEvent> >* data) override;
-    const sp<IStatsPullerCallback> mCallback;
-};
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/external/StatsCompanionServicePuller.cpp b/cmds/statsd/src/external/StatsCompanionServicePuller.cpp
deleted file mode 100644
index f37d2be..0000000
--- a/cmds/statsd/src/external/StatsCompanionServicePuller.cpp
+++ /dev/null
@@ -1,78 +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.
- */
-
-#define DEBUG false
-#include "Log.h"
-
-#include <android/os/IStatsCompanionService.h>
-#include <binder/IPCThreadState.h>
-#include <private/android_filesystem_config.h>
-#include "../stats_log_util.h"
-#include "../statscompanion_util.h"
-#include "StatsCompanionServicePuller.h"
-
-using namespace android;
-using namespace android::base;
-using namespace android::binder;
-using namespace android::os;
-using std::make_shared;
-using std::shared_ptr;
-using std::vector;
-
-namespace android {
-namespace os {
-namespace statsd {
-
-// The reading and parsing are implemented in Java. It is not difficult to port over. But for now
-// let StatsCompanionService handle that and send the data back.
-StatsCompanionServicePuller::StatsCompanionServicePuller(int tagId) : StatsPuller(tagId) {
-}
-
-void StatsCompanionServicePuller::SetStatsCompanionService(
-        sp<IStatsCompanionService> statsCompanionService) {
-    AutoMutex _l(mStatsCompanionServiceLock);
-    sp<IStatsCompanionService> tmpForLock = mStatsCompanionService;
-    mStatsCompanionService = statsCompanionService;
-}
-
-bool StatsCompanionServicePuller::PullInternal(vector<shared_ptr<LogEvent> >* data) {
-    sp<IStatsCompanionService> statsCompanionServiceCopy = mStatsCompanionService;
-    if (statsCompanionServiceCopy != nullptr) {
-        vector<StatsLogEventWrapper> returned_value;
-        Status status = statsCompanionServiceCopy->pullData(mTagId, &returned_value);
-        if (!status.isOk()) {
-            ALOGW("StatsCompanionServicePuller::pull failed for %d", mTagId);
-            StatsdStats::getInstance().noteStatsCompanionPullFailed(mTagId);
-            if (status.exceptionCode() == Status::Exception::EX_TRANSACTION_FAILED) {
-                StatsdStats::getInstance().noteStatsCompanionPullBinderTransactionFailed(mTagId);
-            }
-            return false;
-        }
-        data->clear();
-        for (const StatsLogEventWrapper& it : returned_value) {
-            LogEvent::createLogEvents(it, *data);
-        }
-        VLOG("StatsCompanionServicePuller::pull succeeded for %d", mTagId);
-        return true;
-    } else {
-        ALOGW("statsCompanion not found!");
-        return false;
-    }
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/external/StatsCompanionServicePuller.h b/cmds/statsd/src/external/StatsCompanionServicePuller.h
deleted file mode 100644
index 2e13320..0000000
--- a/cmds/statsd/src/external/StatsCompanionServicePuller.h
+++ /dev/null
@@ -1,40 +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.
- */
-
-#pragma once
-
-#include <utils/String16.h>
-#include "StatsPuller.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-class StatsCompanionServicePuller : public StatsPuller {
-public:
-    explicit StatsCompanionServicePuller(int tagId);
-
-    void SetStatsCompanionService(sp<IStatsCompanionService> statsCompanionService) override;
-
-private:
-    Mutex mStatsCompanionServiceLock;
-    sp<IStatsCompanionService> mStatsCompanionService = nullptr;
-    bool PullInternal(vector<std::shared_ptr<LogEvent> >* data) override;
-};
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 50896f8..8d67b5c 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -21,7 +21,6 @@
 
 #include <android/os/IPullAtomCallback.h>
 #include <android/os/IStatsCompanionService.h>
-#include <android/os/IStatsPullerCallback.h>
 #include <cutils/log.h>
 #include <math.h>
 #include <stdint.h>
@@ -38,10 +37,7 @@
 #include "PowerStatsPuller.h"
 #include "ResourceHealthManagerPuller.h"
 #include "StatsCallbackPuller.h"
-#include "StatsCallbackPullerDeprecated.h"
-#include "StatsCompanionServicePuller.h"
 #include "SubsystemSleepStatePuller.h"
-#include "SurfaceflingerStatsPuller.h"
 #include "TrainInfoPuller.h"
 #include "statslog.h"
 
@@ -60,234 +56,50 @@
 const int64_t NO_ALARM_UPDATE = INT64_MAX;
 
 std::map<PullerKey, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = {
-        // wifi_bytes_transfer
-        {{.atomTag = android::util::WIFI_BYTES_TRANSFER},
-         {.additiveFields = {2, 3, 4, 5},
-          .puller = new StatsCompanionServicePuller(android::util::WIFI_BYTES_TRANSFER)}},
-        // wifi_bytes_transfer_by_fg_bg
-        {{.atomTag = android::util::WIFI_BYTES_TRANSFER_BY_FG_BG},
-         {.additiveFields = {3, 4, 5, 6},
-          .puller = new StatsCompanionServicePuller(android::util::WIFI_BYTES_TRANSFER_BY_FG_BG)}},
-        // mobile_bytes_transfer
-        {{.atomTag = android::util::MOBILE_BYTES_TRANSFER},
-         {.additiveFields = {2, 3, 4, 5},
-          .puller = new StatsCompanionServicePuller(android::util::MOBILE_BYTES_TRANSFER)}},
-        // mobile_bytes_transfer_by_fg_bg
-        {{.atomTag = android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG},
-         {.additiveFields = {3, 4, 5, 6},
-          .puller =
-                  new StatsCompanionServicePuller(android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG)}},
-        // bluetooth_bytes_transfer
-        {{.atomTag = android::util::BLUETOOTH_BYTES_TRANSFER},
-         {.additiveFields = {2, 3},
-          .puller = new StatsCompanionServicePuller(android::util::BLUETOOTH_BYTES_TRANSFER)}},
-        // kernel_wakelock
-        {{.atomTag = android::util::KERNEL_WAKELOCK},
-         {.puller = new StatsCompanionServicePuller(android::util::KERNEL_WAKELOCK)}},
+
         // subsystem_sleep_state
         {{.atomTag = android::util::SUBSYSTEM_SLEEP_STATE},
          {.puller = new SubsystemSleepStatePuller()}},
+
         // on_device_power_measurement
         {{.atomTag = android::util::ON_DEVICE_POWER_MEASUREMENT},
          {.puller = new PowerStatsPuller()}},
-        // cpu_time_per_freq
-        {{.atomTag = android::util::CPU_TIME_PER_FREQ},
-         {.additiveFields = {3},
-          .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_FREQ)}},
-        // cpu_time_per_uid
-        {{.atomTag = android::util::CPU_TIME_PER_UID},
-         {.additiveFields = {2, 3},
-          .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_UID)}},
-        // cpu_time_per_uid_freq
-        // the throttling is 3sec, handled in
-        // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
-        {{.atomTag = android::util::CPU_TIME_PER_UID_FREQ},
-         {.additiveFields = {4},
-          .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_UID_FREQ)}},
-        // cpu_active_time
-        // the throttling is 3sec, handled in
-        // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
-        {{.atomTag = android::util::CPU_ACTIVE_TIME},
-         {.additiveFields = {2},
-          .puller = new StatsCompanionServicePuller(android::util::CPU_ACTIVE_TIME)}},
-        // cpu_cluster_time
-        // the throttling is 3sec, handled in
-        // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
-        {{.atomTag = android::util::CPU_CLUSTER_TIME},
-         {.additiveFields = {3},
-          .puller = new StatsCompanionServicePuller(android::util::CPU_CLUSTER_TIME)}},
-        // wifi_activity_energy_info
-        {{.atomTag = android::util::WIFI_ACTIVITY_INFO},
-         {.puller = new StatsCompanionServicePuller(android::util::WIFI_ACTIVITY_INFO)}},
-        // modem_activity_info
-        {{.atomTag = android::util::MODEM_ACTIVITY_INFO},
-         {.puller = new StatsCompanionServicePuller(android::util::MODEM_ACTIVITY_INFO)}},
-        // bluetooth_activity_info
-        {{.atomTag = android::util::BLUETOOTH_ACTIVITY_INFO},
-         {.puller = new StatsCompanionServicePuller(android::util::BLUETOOTH_ACTIVITY_INFO)}},
-        // system_elapsed_realtime
-        {{.atomTag = android::util::SYSTEM_ELAPSED_REALTIME},
-         {.coolDownNs = NS_PER_SEC,
-          .puller = new StatsCompanionServicePuller(android::util::SYSTEM_ELAPSED_REALTIME),
-          .pullTimeoutNs = NS_PER_SEC / 2,
-         }},
-        // system_uptime
-        {{.atomTag = android::util::SYSTEM_UPTIME},
-         {.puller = new StatsCompanionServicePuller(android::util::SYSTEM_UPTIME)}},
+
         // remaining_battery_capacity
         {{.atomTag = android::util::REMAINING_BATTERY_CAPACITY},
          {.puller = new ResourceHealthManagerPuller(android::util::REMAINING_BATTERY_CAPACITY)}},
+
         // full_battery_capacity
         {{.atomTag = android::util::FULL_BATTERY_CAPACITY},
          {.puller = new ResourceHealthManagerPuller(android::util::FULL_BATTERY_CAPACITY)}},
+
         // battery_voltage
         {{.atomTag = android::util::BATTERY_VOLTAGE},
          {.puller = new ResourceHealthManagerPuller(android::util::BATTERY_VOLTAGE)}},
+
         // battery_level
         {{.atomTag = android::util::BATTERY_LEVEL},
          {.puller = new ResourceHealthManagerPuller(android::util::BATTERY_LEVEL)}},
+
         // battery_cycle_count
         {{.atomTag = android::util::BATTERY_CYCLE_COUNT},
          {.puller = new ResourceHealthManagerPuller(android::util::BATTERY_CYCLE_COUNT)}},
-        // process_memory_state
-        {{.atomTag = android::util::PROCESS_MEMORY_STATE},
-         {.additiveFields = {4, 5, 6, 7, 8},
-          .puller = new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_STATE)}},
-        // process_memory_high_water_mark
-        {{.atomTag = android::util::PROCESS_MEMORY_HIGH_WATER_MARK},
-         {.puller =
-                  new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_HIGH_WATER_MARK)}},
-        // process_memory_snapshot
-        {{.atomTag = android::util::PROCESS_MEMORY_SNAPSHOT},
-         {.puller = new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_SNAPSHOT)}},
-        // system_ion_heap_size
-        {{.atomTag = android::util::SYSTEM_ION_HEAP_SIZE},
-         {.puller = new StatsCompanionServicePuller(android::util::SYSTEM_ION_HEAP_SIZE)}},
-        // process_system_ion_heap_size
-        {{.atomTag = android::util::PROCESS_SYSTEM_ION_HEAP_SIZE},
-         {.puller = new StatsCompanionServicePuller(android::util::PROCESS_SYSTEM_ION_HEAP_SIZE)}},
-        // temperature
-        {{.atomTag = android::util::TEMPERATURE},
-         {.puller = new StatsCompanionServicePuller(android::util::TEMPERATURE)}},
-        // cooling_device
-        {{.atomTag = android::util::COOLING_DEVICE},
-         {.puller = new StatsCompanionServicePuller(android::util::COOLING_DEVICE)}},
-        // binder_calls
-        {{.atomTag = android::util::BINDER_CALLS},
-         {.additiveFields = {4, 5, 6, 8, 12},
-          .puller = new StatsCompanionServicePuller(android::util::BINDER_CALLS)}},
-        // binder_calls_exceptions
-        {{.atomTag = android::util::BINDER_CALLS_EXCEPTIONS},
-         {.puller = new StatsCompanionServicePuller(android::util::BINDER_CALLS_EXCEPTIONS)}},
-        // looper_stats
-        {{.atomTag = android::util::LOOPER_STATS},
-         {.additiveFields = {5, 6, 7, 8, 9},
-          .puller = new StatsCompanionServicePuller(android::util::LOOPER_STATS)}},
-        // Disk Stats
-        {{.atomTag = android::util::DISK_STATS},
-         {.puller = new StatsCompanionServicePuller(android::util::DISK_STATS)}},
-        // Directory usage
-        {{.atomTag = android::util::DIRECTORY_USAGE},
-         {.puller = new StatsCompanionServicePuller(android::util::DIRECTORY_USAGE)}},
-        // Size of app's code, data, and cache
-        {{.atomTag = android::util::APP_SIZE},
-         {.puller = new StatsCompanionServicePuller(android::util::APP_SIZE)}},
-        // Size of specific categories of files. Eg. Music.
-        {{.atomTag = android::util::CATEGORY_SIZE},
-         {.puller = new StatsCompanionServicePuller(android::util::CATEGORY_SIZE)}},
-        // Number of fingerprints enrolled for each user.
-        {{.atomTag = android::util::NUM_FINGERPRINTS_ENROLLED},
-         {.puller = new StatsCompanionServicePuller(android::util::NUM_FINGERPRINTS_ENROLLED)}},
-        // Number of faces enrolled for each user.
-        {{.atomTag = android::util::NUM_FACES_ENROLLED},
-         {.puller = new StatsCompanionServicePuller(android::util::NUM_FACES_ENROLLED)}},
-        // ProcStats.
-        {{.atomTag = android::util::PROC_STATS},
-         {.puller = new StatsCompanionServicePuller(android::util::PROC_STATS)}},
-        // ProcStatsPkgProc.
-        {{.atomTag = android::util::PROC_STATS_PKG_PROC},
-         {.puller = new StatsCompanionServicePuller(android::util::PROC_STATS_PKG_PROC)}},
-        // Disk I/O stats per uid.
-        {{.atomTag = android::util::DISK_IO},
-         {.additiveFields = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
-          .coolDownNs = 3 * NS_PER_SEC,
-          .puller = new StatsCompanionServicePuller(android::util::DISK_IO)}},
-        // PowerProfile constants for power model calculations.
-        {{.atomTag = android::util::POWER_PROFILE},
-         {.puller = new StatsCompanionServicePuller(android::util::POWER_PROFILE)}},
-        // Process cpu stats. Min cool-down is 5 sec, inline with what AcitivityManagerService uses.
-        {{.atomTag = android::util::PROCESS_CPU_TIME},
-         {.coolDownNs = 5 * NS_PER_SEC /* min cool-down in seconds*/,
-          .puller = new StatsCompanionServicePuller(android::util::PROCESS_CPU_TIME)}},
-        {{.atomTag = android::util::CPU_TIME_PER_THREAD_FREQ},
-         {.additiveFields = {7, 9, 11, 13, 15, 17, 19, 21},
-          .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_THREAD_FREQ)}},
-        // DeviceCalculatedPowerUse.
-        {{.atomTag = android::util::DEVICE_CALCULATED_POWER_USE},
-         {.puller = new StatsCompanionServicePuller(android::util::DEVICE_CALCULATED_POWER_USE)}},
-        // DeviceCalculatedPowerBlameUid.
-        {{.atomTag = android::util::DEVICE_CALCULATED_POWER_BLAME_UID},
-         {.puller = new StatsCompanionServicePuller(
-                  android::util::DEVICE_CALCULATED_POWER_BLAME_UID)}},
-        // DeviceCalculatedPowerBlameOther.
-        {{.atomTag = android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER},
-         {.puller = new StatsCompanionServicePuller(
-                  android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER)}},
-        // DebugElapsedClock.
-        {{.atomTag = android::util::DEBUG_ELAPSED_CLOCK},
-         {.additiveFields = {1, 2, 3, 4},
-          .puller = new StatsCompanionServicePuller(android::util::DEBUG_ELAPSED_CLOCK)}},
-        // DebugFailingElapsedClock.
-        {{.atomTag = android::util::DEBUG_FAILING_ELAPSED_CLOCK},
-         {.additiveFields = {1, 2, 3, 4},
-          .puller = new StatsCompanionServicePuller(android::util::DEBUG_FAILING_ELAPSED_CLOCK)}},
-        // BuildInformation.
-        {{.atomTag = android::util::BUILD_INFORMATION},
-         {.puller = new StatsCompanionServicePuller(android::util::BUILD_INFORMATION)}},
-        // RoleHolder.
-        {{.atomTag = android::util::ROLE_HOLDER},
-         {.puller = new StatsCompanionServicePuller(android::util::ROLE_HOLDER)}},
-        // PermissionState.
-        {{.atomTag = android::util::DANGEROUS_PERMISSION_STATE},
-         {.puller = new StatsCompanionServicePuller(android::util::DANGEROUS_PERMISSION_STATE)}},
+
         // TrainInfo.
         {{.atomTag = android::util::TRAIN_INFO}, {.puller = new TrainInfoPuller()}},
-        // TimeZoneDataInfo.
-        {{.atomTag = android::util::TIME_ZONE_DATA_INFO},
-         {.puller = new StatsCompanionServicePuller(android::util::TIME_ZONE_DATA_INFO)}},
-        // ExternalStorageInfo
-        {{.atomTag = android::util::EXTERNAL_STORAGE_INFO},
-         {.puller = new StatsCompanionServicePuller(android::util::EXTERNAL_STORAGE_INFO)}},
+
         // GpuStatsGlobalInfo
         {{.atomTag = android::util::GPU_STATS_GLOBAL_INFO},
          {.puller = new GpuStatsPuller(android::util::GPU_STATS_GLOBAL_INFO)}},
+
         // GpuStatsAppInfo
         {{.atomTag = android::util::GPU_STATS_APP_INFO},
          {.puller = new GpuStatsPuller(android::util::GPU_STATS_APP_INFO)}},
-        // AppsOnExternalStorageInfo
-        {{.atomTag = android::util::APPS_ON_EXTERNAL_STORAGE_INFO},
-         {.puller = new StatsCompanionServicePuller(android::util::APPS_ON_EXTERNAL_STORAGE_INFO)}},
-        // Face Settings
-        {{.atomTag = android::util::FACE_SETTINGS},
-         {.puller = new StatsCompanionServicePuller(android::util::FACE_SETTINGS)}},
-        // App ops
-        {{.atomTag = android::util::APP_OPS},
-         {.puller = new StatsCompanionServicePuller(android::util::APP_OPS)}},
-        // SurfaceflingerStatsGlobalInfo
-        {{.atomTag = android::util::SURFACEFLINGER_STATS_GLOBAL_INFO},
-         {.puller =
-                  new SurfaceflingerStatsPuller(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO)}},
+
         // VmsClientStats
         {{.atomTag = android::util::VMS_CLIENT_STATS},
          {.additiveFields = {5, 6, 7, 8, 9, 10},
           .puller = new CarStatsPuller(android::util::VMS_CLIENT_STATS)}},
-        // NotiifcationRemoteViews.
-        {{.atomTag = android::util::NOTIFICATION_REMOTE_VIEWS},
-         {.puller = new StatsCompanionServicePuller(android::util::NOTIFICATION_REMOTE_VIEWS)}},
-        // PermissionStateSampled.
-        {{.atomTag = android::util::DANGEROUS_PERMISSION_STATE_SAMPLED},
-         {.puller =
-               new StatsCompanionServicePuller(android::util::DANGEROUS_PERMISSION_STATE_SAMPLED)}},
 };
 
 StatsPullerManager::StatsPullerManager() : mNextPullTimeNs(NO_ALARM_UPDATE) {
@@ -486,21 +298,6 @@
     return totalCleared;
 }
 
-// Deprecated, remove after puller API is complete.
-void StatsPullerManager::RegisterPullerCallback(int32_t atomTag,
-        const sp<IStatsPullerCallback>& callback) {
-    AutoMutex _l(mLock);
-    // Platform pullers cannot be changed.
-    if (!isVendorPulledAtom(atomTag)) {
-        VLOG("RegisterPullerCallback: atom tag %d is not vendor pulled", atomTag);
-        return;
-    }
-    VLOG("RegisterPullerCallback: adding puller for tag %d", atomTag);
-    StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag, /*registered=*/true);
-    kAllPullAtomInfo[{.atomTag = atomTag}] = {
-            .puller = new StatsCallbackPullerDeprecated(atomTag, callback)};
-}
-
 void StatsPullerManager::RegisterPullAtomCallback(const int uid, const int32_t atomTag,
                                                   const int64_t coolDownNs, const int64_t timeoutNs,
                                                   const vector<int32_t>& additiveFields,
@@ -517,16 +314,6 @@
     };
 }
 
-void StatsPullerManager::UnregisterPullerCallback(int32_t atomTag) {
-    AutoMutex _l(mLock);
-    // Platform pullers cannot be changed.
-    if (!isVendorPulledAtom(atomTag)) {
-        return;
-    }
-    StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag, /*registered=*/false);
-    kAllPullAtomInfo.erase({.atomTag = atomTag});
-}
-
 void StatsPullerManager::UnregisterPullAtomCallback(const int uid, const int32_t atomTag) {
     AutoMutex _l(mLock);
     StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag, /*registered=*/false);
diff --git a/cmds/statsd/src/external/StatsPullerManager.h b/cmds/statsd/src/external/StatsPullerManager.h
index 349fd47..4b6ab57 100644
--- a/cmds/statsd/src/external/StatsPullerManager.h
+++ b/cmds/statsd/src/external/StatsPullerManager.h
@@ -18,7 +18,6 @@
 
 #include <android/os/IPullAtomCallback.h>
 #include <android/os/IStatsCompanionService.h>
-#include <android/os/IStatsPullerCallback.h>
 #include <binder/IServiceManager.h>
 #include <utils/RefBase.h>
 #include <utils/threads.h>
@@ -116,15 +115,10 @@
 
     void SetStatsCompanionService(sp<IStatsCompanionService> statsCompanionService);
 
-    // Deprecated, remove after puller API is complete.
-    void RegisterPullerCallback(int32_t atomTag, const sp<IStatsPullerCallback>& callback);
-
     void RegisterPullAtomCallback(const int uid, const int32_t atomTag, const int64_t coolDownNs,
                                   const int64_t timeoutNs, const vector<int32_t>& additiveFields,
                                   const sp<IPullAtomCallback>& callback);
 
-    void UnregisterPullerCallback(int32_t atomTag);
-
     void UnregisterPullAtomCallback(const int uid, const int32_t atomTag);
 
     static std::map<PullerKey, PullAtomInfo> kAllPullAtomInfo;
diff --git a/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp b/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp
deleted file mode 100644
index 23b2236..0000000
--- a/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright 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.
- */
-
-#include "SurfaceflingerStatsPuller.h"
-
-#include <cutils/compiler.h>
-
-#include <numeric>
-
-#include "logd/LogEvent.h"
-#include "stats_log_util.h"
-#include "statslog.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-SurfaceflingerStatsPuller::SurfaceflingerStatsPuller(const int tagId) : StatsPuller(tagId) {
-}
-
-bool SurfaceflingerStatsPuller::PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) {
-    switch (mTagId) {
-        case android::util::SURFACEFLINGER_STATS_GLOBAL_INFO:
-            return pullGlobalInfo(data);
-        default:
-            break;
-    }
-
-    return false;
-}
-
-static int64_t getTotalTime(
-        const google::protobuf::RepeatedPtrField<surfaceflinger::SFTimeStatsHistogramBucketProto>&
-                buckets) {
-    int64_t total = 0;
-    for (const auto& bucket : buckets) {
-        if (bucket.time_millis() == 1000) {
-            continue;
-        }
-
-        total += bucket.time_millis() * bucket.frame_count();
-    }
-
-    return total;
-}
-
-bool SurfaceflingerStatsPuller::pullGlobalInfo(std::vector<std::shared_ptr<LogEvent>>* data) {
-    std::string protoBytes;
-    if (CC_UNLIKELY(mStatsProvider)) {
-        protoBytes = mStatsProvider();
-    } else {
-        std::unique_ptr<FILE, decltype(&pclose)> pipe(popen("dumpsys SurfaceFlinger --timestats -dump --proto", "r"), pclose);
-        if (!pipe.get()) {
-            return false;
-        }
-        char buf[1024];
-        size_t bytesRead = 0;
-        do {
-            bytesRead = fread(buf, 1, sizeof(buf), pipe.get());
-            protoBytes.append(buf, bytesRead);
-        } while (bytesRead > 0);
-    }
-    surfaceflinger::SFTimeStatsGlobalProto proto;
-    proto.ParseFromString(protoBytes);
-
-    int64_t totalTime = getTotalTime(proto.present_to_present());
-
-    data->clear();
-    data->reserve(1);
-    std::shared_ptr<LogEvent> event =
-            make_shared<LogEvent>(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO, getWallClockNs(),
-                                  getElapsedRealtimeNs());
-    if (!event->write(proto.total_frames())) return false;
-    if (!event->write(proto.missed_frames())) return false;
-    if (!event->write(proto.client_composition_frames())) return false;
-    if (!event->write(proto.display_on_time())) return false;
-    if (!event->write(totalTime)) return false;
-    event->init();
-    data->emplace_back(event);
-
-    return true;
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/external/SurfaceflingerStatsPuller.h b/cmds/statsd/src/external/SurfaceflingerStatsPuller.h
deleted file mode 100644
index ed7153e..0000000
--- a/cmds/statsd/src/external/SurfaceflingerStatsPuller.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 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.
- */
-
-#pragma once
-
-#include <timestatsproto/TimeStatsProtoHeader.h>
-
-#include "StatsPuller.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-/**
- * Pull metrics from Surfaceflinger
- */
-class SurfaceflingerStatsPuller : public StatsPuller {
-public:
-    explicit SurfaceflingerStatsPuller(const int tagId);
-
-    // StatsPuller interface
-    bool PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) override;
-
-protected:
-    // Test-only, for injecting fake data
-    using StatsProvider = std::function<std::string()>;
-    StatsProvider mStatsProvider;
-
-private:
-    bool pullGlobalInfo(std::vector<std::shared_ptr<LogEvent>>* data);
-};
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h
index 692d91e..3282785 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.h
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -164,9 +164,6 @@
     // Maximum number of pushed atoms statsd stats will track above kMaxPushedAtomId.
     static const int kMaxNonPlatformPushedAtoms = 100;
 
-    // Max platform atom tag number.
-    static const int32_t kMaxPlatformAtomTag = 100000;
-
     // Vendor pulled atom start id.
     static const int32_t kVendorPulledAtomStartTag = 150000;
 
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 35c6d37..e85b975 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -207,10 +207,7 @@
                                      &linkedConditionDimensionKey);
             if (trueConditionDimensions.find(linkedConditionDimensionKey) !=
                     trueConditionDimensions.end()) {
-                for (auto& condIt : whatIt.second) {
-                    condIt.second->onConditionChanged(
-                            currentUnSlicedPartCondition, eventTime);
-                }
+                whatIt.second->onConditionChanged(currentUnSlicedPartCondition, eventTime);
             }
         }
     } else {
@@ -222,15 +219,11 @@
                                          &linkedConditionDimensionKey);
                 if (dimensionsChangedToTrue->find(linkedConditionDimensionKey) !=
                         dimensionsChangedToTrue->end()) {
-                    for (auto& condIt : whatIt.second) {
-                        condIt.second->onConditionChanged(true, eventTime);
-                    }
+                    whatIt.second->onConditionChanged(true, eventTime);
                 }
                 if (dimensionsChangedToFalse->find(linkedConditionDimensionKey) !=
                         dimensionsChangedToFalse->end()) {
-                    for (auto& condIt : whatIt.second) {
-                        condIt.second->onConditionChanged(false, eventTime);
-                    }
+                    whatIt.second->onConditionChanged(false, eventTime);
                 }
             }
         }
@@ -247,9 +240,7 @@
 
     // Now for each of the on-going event, check if the condition has changed for them.
     for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
-        for (auto& pair : whatIt.second) {
-            pair.second->onSlicedConditionMayChange(overallCondition, eventTimeNs);
-        }
+        whatIt.second->onSlicedConditionMayChange(overallCondition, eventTimeNs);
     }
 }
 
@@ -283,18 +274,14 @@
         }
 
         for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
-            for (auto& pair : whatIt.second) {
-                pair.second->onConditionChanged(mIsActive, eventTimeNs);
-            }
+            whatIt.second->onConditionChanged(mIsActive, eventTimeNs);
         }
     } else if (mIsActive) {
         flushIfNeededLocked(eventTimeNs);
         onSlicedConditionMayChangeInternalLocked(mIsActive, eventTimeNs);
     } else { // mConditionSliced == true && !mIsActive
         for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
-            for (auto& pair : whatIt.second) {
-                pair.second->onConditionChanged(mIsActive, eventTimeNs);
-            }
+            whatIt.second->onConditionChanged(mIsActive, eventTimeNs);
         }
     }
 }
@@ -310,9 +297,7 @@
 
     flushIfNeededLocked(eventTime);
     for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
-        for (auto& pair : whatIt.second) {
-            pair.second->onConditionChanged(conditionMet, eventTime);
-        }
+        whatIt.second->onConditionChanged(conditionMet, eventTime);
     }
 }
 
@@ -425,19 +410,11 @@
                                                       const int64_t& nextBucketStartTimeNs) {
     for (auto whatIt = mCurrentSlicedDurationTrackerMap.begin();
             whatIt != mCurrentSlicedDurationTrackerMap.end();) {
-        for (auto it = whatIt->second.begin(); it != whatIt->second.end();) {
-            if (it->second->flushCurrentBucket(eventTimeNs, &mPastBuckets)) {
-                VLOG("erase bucket for key %s %s", whatIt->first.toString().c_str(),
-                     it->first.toString().c_str());
-                it = whatIt->second.erase(it);
-            } else {
-                ++it;
-            }
-        }
-        if (whatIt->second.empty()) {
+        if (whatIt->second->flushCurrentBucket(eventTimeNs, &mPastBuckets)) {
+            VLOG("erase bucket for key %s", whatIt->first.toString().c_str());
             whatIt = mCurrentSlicedDurationTrackerMap.erase(whatIt);
         } else {
-            whatIt++;
+            ++whatIt;
         }
     }
     StatsdStats::getInstance().noteBucketCount(mMetricId);
@@ -453,35 +430,15 @@
             (unsigned long)mCurrentSlicedDurationTrackerMap.size());
     if (verbose) {
         for (const auto& whatIt : mCurrentSlicedDurationTrackerMap) {
-            for (const auto& slice : whatIt.second) {
-                fprintf(out, "\t(what)%s\t(states)%s\n", whatIt.first.toString().c_str(),
-                        slice.first.toString().c_str());
-                slice.second->dumpStates(out, verbose);
-            }
+            fprintf(out, "\t(what)%s\n", whatIt.first.toString().c_str());
+            whatIt.second->dumpStates(out, verbose);
         }
     }
 }
 
 bool DurationMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) {
     auto whatIt = mCurrentSlicedDurationTrackerMap.find(newKey.getDimensionKeyInWhat());
-    if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
-        auto stateIt = whatIt->second.find(newKey.getStateValuesKey());
-        if (stateIt != whatIt->second.end()) {
-            return false;
-        }
-        if (whatIt->second.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
-            size_t newTupleCount = whatIt->second.size() + 1;
-            StatsdStats::getInstance().noteMetricDimensionInConditionSize(
-                    mConfigKey, mMetricId, newTupleCount);
-            // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
-            if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
-                ALOGE("DurationMetric %lld dropping data for state values key %s",
-                      (long long)mMetricId, newKey.getStateValuesKey().toString().c_str());
-                StatsdStats::getInstance().noteHardDimensionLimitReached(mMetricId);
-                return true;
-            }
-        }
-    } else {
+    if (whatIt == mCurrentSlicedDurationTrackerMap.end()) {
         // 1. Report the tuple count if the tuple count > soft limit
         if (mCurrentSlicedDurationTrackerMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
             size_t newTupleCount = mCurrentSlicedDurationTrackerMap.size() + 1;
@@ -503,24 +460,16 @@
                                               const ConditionKey& conditionKeys,
                                               bool condition, const LogEvent& event) {
     const auto& whatKey = eventKey.getDimensionKeyInWhat();
-    const auto& stateKey = eventKey.getStateValuesKey();
 
     auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatKey);
     if (whatIt == mCurrentSlicedDurationTrackerMap.end()) {
         if (hitGuardRailLocked(eventKey)) {
             return;
         }
-        mCurrentSlicedDurationTrackerMap[whatKey][stateKey] = createDurationTracker(eventKey);
-    } else {
-        if (whatIt->second.find(stateKey) == whatIt->second.end()) {
-            if (hitGuardRailLocked(eventKey)) {
-                return;
-            }
-            mCurrentSlicedDurationTrackerMap[whatKey][stateKey] = createDurationTracker(eventKey);
-        }
+        mCurrentSlicedDurationTrackerMap[whatKey] = createDurationTracker(eventKey);
     }
 
-    auto it = mCurrentSlicedDurationTrackerMap.find(whatKey)->second.find(stateKey);
+    auto it = mCurrentSlicedDurationTrackerMap.find(whatKey);
     if (mUseWhatDimensionAsInternalDimension) {
         it->second->noteStart(whatKey, condition,
                               event.GetElapsedTimestampNs(), conditionKeys);
@@ -560,18 +509,14 @@
     // Handles Stopall events.
     if (matcherIndex == mStopAllIndex) {
         for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
-            for (auto& pair : whatIt.second) {
-                pair.second->noteStopAll(event.GetElapsedTimestampNs());
-            }
+            whatIt.second->noteStopAll(event.GetElapsedTimestampNs());
         }
         return;
     }
 
-    HashableDimensionKey dimensionInWhat;
+    HashableDimensionKey dimensionInWhat = DEFAULT_DIMENSION_KEY;
     if (!mDimensionsInWhat.empty()) {
         filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat);
-    } else {
-       dimensionInWhat = DEFAULT_DIMENSION_KEY;
     }
 
     // Handles Stop events.
@@ -579,9 +524,7 @@
         if (mUseWhatDimensionAsInternalDimension) {
             auto whatIt = mCurrentSlicedDurationTrackerMap.find(dimensionInWhat);
             if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
-                for (const auto& stateIt : whatIt->second) {
-                    stateIt.second->noteStop(dimensionInWhat, event.GetElapsedTimestampNs(), false);
-                }
+                whatIt->second->noteStop(dimensionInWhat, event.GetElapsedTimestampNs(), false);
             }
             return;
         }
@@ -593,10 +536,7 @@
 
         auto whatIt = mCurrentSlicedDurationTrackerMap.find(dimensionInWhat);
         if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
-            for (const auto& stateIt : whatIt->second) {
-                stateIt.second->noteStop(internalDimensionKey, event.GetElapsedTimestampNs(),
-                                         false);
-            }
+            whatIt->second->noteStop(internalDimensionKey, event.GetElapsedTimestampNs(), false);
         }
         return;
     }
@@ -619,8 +559,8 @@
 
     condition = condition && mIsActive;
 
-    handleStartEvent(MetricDimensionKey(dimensionInWhat, DEFAULT_DIMENSION_KEY),
-                     conditionKey, condition, event);
+    handleStartEvent(MetricDimensionKey(dimensionInWhat, DEFAULT_DIMENSION_KEY), conditionKey,
+                     condition, event);
 }
 
 size_t DurationMetricProducer::byteSizeLocked() const {
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 45908fb..06da0f6 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -132,8 +132,7 @@
     std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>> mPastBuckets;
 
     // The duration trackers in the current bucket.
-    std::unordered_map<HashableDimensionKey,
-        std::unordered_map<HashableDimensionKey, std::unique_ptr<DurationTracker>>>
+    std::unordered_map<HashableDimensionKey, std::unique_ptr<DurationTracker>>
             mCurrentSlicedDurationTrackerMap;
 
     // Helper function to create a duration tracker given the metric aggregation type.
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
index 6b5c299..afe93d4 100644
--- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -82,8 +82,6 @@
 
     virtual ~DurationTracker(){};
 
-    virtual unique_ptr<DurationTracker> clone(const int64_t eventTime) = 0;
-
     virtual void noteStart(const HashableDimensionKey& key, bool condition,
                            const int64_t eventTime, const ConditionKey& conditionKey) = 0;
     virtual void noteStop(const HashableDimensionKey& key, const int64_t eventTime,
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
index df66cb0..2be5855 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -37,24 +37,6 @@
                       conditionSliced, fullLink, anomalyTrackers) {
 }
 
-unique_ptr<DurationTracker> MaxDurationTracker::clone(const int64_t eventTime) {
-    auto clonedTracker = make_unique<MaxDurationTracker>(*this);
-    for (auto it = clonedTracker->mInfos.begin(); it != clonedTracker->mInfos.end();) {
-        if (it->second.state  != kStopped) {
-            it->second.lastStartTime = eventTime;
-            it->second.lastDuration = 0;
-            it++;
-        } else {
-            it = clonedTracker->mInfos.erase(it);
-        }
-    }
-    if (clonedTracker->mInfos.empty()) {
-        return nullptr;
-    } else {
-        return clonedTracker;
-    }
-}
-
 bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) {
     // ===========GuardRail==============
     if (mInfos.find(newKey) != mInfos.end()) {
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
index d0371da..efb8dc7 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
@@ -38,8 +38,6 @@
 
     MaxDurationTracker(const MaxDurationTracker& tracker) = default;
 
-    unique_ptr<DurationTracker> clone(const int64_t eventTime) override;
-
     void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime,
                    const ConditionKey& conditionKey) override;
     void noteStop(const HashableDimensionKey& key, const int64_t eventTime,
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
index b0fd975..57f3965 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -38,13 +38,6 @@
     mLastStartTime = 0;
 }
 
-unique_ptr<DurationTracker> OringDurationTracker::clone(const int64_t eventTime) {
-    auto clonedTracker = make_unique<OringDurationTracker>(*this);
-    clonedTracker->mLastStartTime = eventTime;
-    clonedTracker->mDuration = 0;
-    return clonedTracker;
-}
-
 bool OringDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) {
     // ===========GuardRail==============
     // 1. Report the tuple count if the tuple count > soft limit
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
index 43c48d5..c3aad66 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
@@ -37,8 +37,6 @@
 
     OringDurationTracker(const OringDurationTracker& tracker) = default;
 
-    unique_ptr<DurationTracker> clone(const int64_t eventTime) override;
-
     void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime,
                    const ConditionKey& conditionKey) override;
     void noteStop(const HashableDimensionKey& key, const int64_t eventTime,
diff --git a/cmds/statsd/src/state/StateTracker.cpp b/cmds/statsd/src/state/StateTracker.cpp
index ef59c92..3ad21e0 100644
--- a/cmds/statsd/src/state/StateTracker.cpp
+++ b/cmds/statsd/src/state/StateTracker.cpp
@@ -28,10 +28,14 @@
 StateTracker::StateTracker(const int32_t atomId, const util::StateAtomFieldOptions& stateAtomInfo)
     : mAtomId(atomId), mStateField(getSimpleMatcher(atomId, stateAtomInfo.exclusiveField)) {
     // create matcher for each primary field
-    // TODO(tsaichristine): b/142108433 handle when primary field is first uid in chain
-    for (const auto& primary : stateAtomInfo.primaryFields) {
-        Matcher matcher = getSimpleMatcher(atomId, primary);
-        mPrimaryFields.push_back(matcher);
+    for (const auto& primaryField : stateAtomInfo.primaryFields) {
+        if (primaryField == util::FIRST_UID_IN_CHAIN) {
+            Matcher matcher = getFirstUidMatcher(atomId);
+            mPrimaryFields.push_back(matcher);
+        } else {
+            Matcher matcher = getSimpleMatcher(atomId, primaryField);
+            mPrimaryFields.push_back(matcher);
+        }
     }
 
     // TODO(tsaichristine): b/142108433 set default state, reset state, and nesting
diff --git a/cmds/statsd/src/state/StateTracker.h b/cmds/statsd/src/state/StateTracker.h
index 7453370..70f1627 100644
--- a/cmds/statsd/src/state/StateTracker.h
+++ b/cmds/statsd/src/state/StateTracker.h
@@ -72,7 +72,7 @@
 
     int32_t mDefaultState = kStateUnknown;
 
-    int32_t mResetState;
+    int32_t mResetState = kStateUnknown;
 
     // Maps primary key to state value info
     std::unordered_map<HashableDimensionKey, StateValueInfo> mStateMap;
diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.cpp b/cmds/statsd/src/subscriber/SubscriberReporter.cpp
index a9a105f..a37cad1 100644
--- a/cmds/statsd/src/subscriber/SubscriberReporter.cpp
+++ b/cmds/statsd/src/subscriber/SubscriberReporter.cpp
@@ -69,12 +69,6 @@
     }
 }
 
-void SubscriberReporter::removeConfig(const ConfigKey& configKey) {
-    VLOG("SubscriberReporter::removeConfig called.");
-    lock_guard<std::mutex> lock(mLock);
-    mIntentMap.erase(configKey);
-}
-
 void SubscriberReporter::alertBroadcastSubscriber(const ConfigKey& configKey,
                                                   const Subscription& subscription,
                                                   const MetricDimensionKey& dimKey) const {
diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.h b/cmds/statsd/src/subscriber/SubscriberReporter.h
index 8ccc8ee..087a1b8 100644
--- a/cmds/statsd/src/subscriber/SubscriberReporter.h
+++ b/cmds/statsd/src/subscriber/SubscriberReporter.h
@@ -59,9 +59,6 @@
      */
     void unsetBroadcastSubscriber(const ConfigKey& configKey, int64_t subscriberId);
 
-    /** Remove all information stored by SubscriberReporter about the given config. */
-    void removeConfig(const ConfigKey& configKey);
-
     /**
      * Sends a broadcast via the intentSender previously stored for the
      * given (configKey, subscriberId) pair by setBroadcastSubscriber.
diff --git a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
index 0bc3ebb..16b51d9 100644
--- a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
@@ -28,16 +28,16 @@
 
 #ifdef __ANDROID__
 
-const string kAndroid = "android";
 const string kApp1 = "app1.sharing.1";
 const int kConfigKey = 789130123;  // Randomly chosen to avoid collisions with existing configs.
+const int kCallingUid = 0; // Randomly chosen
 
 void SendConfig(StatsService& service, const StatsdConfig& config) {
     string str;
     config.SerializeToString(&str);
     std::vector<uint8_t> configAsVec(str.begin(), str.end());
     bool success;
-    service.addConfiguration(kConfigKey, configAsVec, String16(kAndroid.c_str()));
+    service.addConfiguration(kConfigKey, configAsVec, kCallingUid);
 }
 
 ConfigMetricsReport GetReports(sp<StatsLogProcessor> processor, int64_t timestamp,
@@ -50,7 +50,7 @@
     ConfigMetricsReportList reports;
     reports.ParseFromArray(output.data(), output.size());
     EXPECT_EQ(1, reports.reports_size());
-    return reports.reports(0);
+    return reports.reports(kCallingUid);
 }
 
 StatsdConfig MakeConfig() {
diff --git a/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp b/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp
deleted file mode 100644
index 5b7a30d..0000000
--- a/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright 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.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "SurfaceflingerStatsPuller_test"
-
-#include "src/external/SurfaceflingerStatsPuller.h"
-#include "statslog.h"
-
-#include <gtest/gtest.h>
-#include <log/log.h>
-
-#ifdef __ANDROID__
-
-namespace android {
-namespace os {
-namespace statsd {
-
-class TestableSurfaceflingerStatsPuller : public SurfaceflingerStatsPuller {
-public:
-    TestableSurfaceflingerStatsPuller(const int tagId) : SurfaceflingerStatsPuller(tagId){};
-
-    void injectStats(const StatsProvider& statsProvider) {
-        mStatsProvider = statsProvider;
-    }
-};
-
-class SurfaceflingerStatsPullerTest : public ::testing::Test {
-public:
-    SurfaceflingerStatsPullerTest() {
-        const ::testing::TestInfo* const test_info =
-                ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
-    }
-
-    ~SurfaceflingerStatsPullerTest() {
-        const ::testing::TestInfo* const test_info =
-                ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
-    }
-};
-
-TEST_F(SurfaceflingerStatsPullerTest, pullGlobalStats) {
-    surfaceflinger::SFTimeStatsGlobalProto proto;
-    proto.set_total_frames(1);
-    proto.set_missed_frames(2);
-    proto.set_client_composition_frames(2);
-    proto.set_display_on_time(4);
-
-    auto bucketOne = proto.add_present_to_present();
-    bucketOne->set_time_millis(2);
-    bucketOne->set_frame_count(4);
-    auto bucketTwo = proto.add_present_to_present();
-    bucketTwo->set_time_millis(4);
-    bucketTwo->set_frame_count(1);
-    auto bucketThree = proto.add_present_to_present();
-    bucketThree->set_time_millis(1000);
-    bucketThree->set_frame_count(1);
-    static constexpr int64_t expectedAnimationMillis = 12;
-    TestableSurfaceflingerStatsPuller puller(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO);
-
-    puller.injectStats([&] {
-        return proto.SerializeAsString();
-    });
-    puller.ForceClearCache();
-    vector<std::shared_ptr<LogEvent>> outData;
-    puller.Pull(&outData);
-
-    ASSERT_EQ(1, outData.size());
-    EXPECT_EQ(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO, outData[0]->GetTagId());
-    EXPECT_EQ(proto.total_frames(), outData[0]->getValues()[0].mValue.long_value);
-    EXPECT_EQ(proto.missed_frames(), outData[0]->getValues()[1].mValue.long_value);
-    EXPECT_EQ(proto.client_composition_frames(), outData[0]->getValues()[2].mValue.long_value);
-    EXPECT_EQ(proto.display_on_time(), outData[0]->getValues()[3].mValue.long_value);
-    EXPECT_EQ(expectedAnimationMillis, outData[0]->getValues()[4].mValue.long_value);
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
-#else
-GTEST_LOG_(INFO) << "This test does nothing.\n";
-#endif
diff --git a/cmds/statsd/tests/state/StateTracker_test.cpp b/cmds/statsd/tests/state/StateTracker_test.cpp
index 26a3733..84aaa54 100644
--- a/cmds/statsd/tests/state/StateTracker_test.cpp
+++ b/cmds/statsd/tests/state/StateTracker_test.cpp
@@ -76,6 +76,23 @@
     return event;
 }
 
+// State with first uid in attribution chain as primary field - WakelockStateChanged
+std::shared_ptr<LogEvent> buildPartialWakelockEvent(int uid, const std::string& tag, bool acquire) {
+    std::vector<AttributionNodeInternal> chain;
+    chain.push_back(AttributionNodeInternal());
+    AttributionNodeInternal& attr = chain.back();
+    attr.set_uid(uid);
+
+    std::shared_ptr<LogEvent> event =
+            std::make_shared<LogEvent>(android::util::WAKELOCK_STATE_CHANGED, 1000 /* timestamp */);
+    event->write(chain);
+    event->write((int32_t)1);  // PARTIAL_WAKE_LOCK
+    event->write(tag);
+    event->write(acquire ? 1 : 0);
+    event->init();
+    return event;
+}
+
 // State with multiple primary fields - OverlayStateChanged
 std::shared_ptr<LogEvent> buildOverlayEvent(int uid, const std::string& packageName, int state) {
     std::shared_ptr<LogEvent> event =
@@ -134,6 +151,39 @@
     key->addValue(FieldValue(field1, value1));
     key->addValue(FieldValue(field2, value2));
 }
+
+void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key) {
+    int pos1[] = {1, 1, 1};
+    int pos3[] = {2, 0, 0};
+    int pos4[] = {3, 0, 0};
+
+    Field field1(10 /* atom id */, pos1, 2 /* depth */);
+
+    Field field3(10 /* atom id */, pos3, 0 /* depth */);
+    Field field4(10 /* atom id */, pos4, 0 /* depth */);
+
+    Value value1((int32_t)uid);
+    Value value3((int32_t)1 /*partial*/);
+    Value value4(tag);
+
+    key->addValue(FieldValue(field1, value1));
+    key->addValue(FieldValue(field3, value3));
+    key->addValue(FieldValue(field4, value4));
+}
+
+void getPartialWakelockKey(int uid, HashableDimensionKey* key) {
+    int pos1[] = {1, 1, 1};
+    int pos3[] = {2, 0, 0};
+
+    Field field1(10 /* atom id */, pos1, 2 /* depth */);
+    Field field3(10 /* atom id */, pos3, 0 /* depth */);
+
+    Value value1((int32_t)uid);
+    Value value3((int32_t)1 /*partial*/);
+
+    key->addValue(FieldValue(field1, value1));
+    key->addValue(FieldValue(field3, value3));
+}
 // END: get primary key functions
 
 TEST(StateListenerTest, TestStateListenerWeakPointer) {
@@ -247,7 +297,8 @@
 
     // check StateTracker was updated by querying for state
     HashableDimensionKey queryKey = DEFAULT_DIMENSION_KEY;
-    EXPECT_EQ(2, getStateInt(mgr, android::util::SCREEN_STATE_CHANGED, queryKey));
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
+              getStateInt(mgr, android::util::SCREEN_STATE_CHANGED, queryKey));
 }
 
 /**
@@ -272,7 +323,46 @@
     // check StateTracker was updated by querying for state
     HashableDimensionKey queryKey;
     getUidProcessKey(1000 /* uid */, &queryKey);
-    EXPECT_EQ(1002, getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey));
+    EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_TOP,
+              getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey));
+}
+
+TEST(StateTrackerTest, TestStateChangePrimaryFieldAttrChain) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    StateManager mgr;
+    mgr.registerListener(android::util::WAKELOCK_STATE_CHANGED, listener1);
+
+    // Log event.
+    std::shared_ptr<LogEvent> event =
+            buildPartialWakelockEvent(1001 /* uid */, "tag1", false /* acquire */);
+    mgr.onLogEvent(*event);
+
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+    EXPECT_EQ(1, mgr.getListenersCount(android::util::WAKELOCK_STATE_CHANGED));
+
+    // Check listener was updated.
+    EXPECT_EQ(1, listener1->updates.size());
+    EXPECT_EQ(3, listener1->updates[0].mKey.getValues().size());
+    EXPECT_EQ(1001, listener1->updates[0].mKey.getValues()[0].mValue.int_value);
+    EXPECT_EQ(1, listener1->updates[0].mKey.getValues()[1].mValue.int_value);
+    EXPECT_EQ("tag1", listener1->updates[0].mKey.getValues()[2].mValue.str_value);
+    EXPECT_EQ(WakelockStateChanged::RELEASE, listener1->updates[0].mState);
+
+    // Check StateTracker was updated by querying for state.
+    HashableDimensionKey queryKey;
+    getPartialWakelockKey(1001 /* uid */, "tag1", &queryKey);
+    EXPECT_EQ(WakelockStateChanged::RELEASE,
+              getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey));
+
+    // No state stored for this query key.
+    HashableDimensionKey queryKey2;
+    getPartialWakelockKey(1002 /* uid */, "tag1", &queryKey2);
+    EXPECT_EQ(-1, getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey2));
+
+    // Partial query fails.
+    HashableDimensionKey queryKey3;
+    getPartialWakelockKey(1001 /* uid */, &queryKey3);
+    EXPECT_EQ(-1, getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey3));
 }
 
 /**
@@ -297,7 +387,8 @@
     // check StateTracker was updated by querying for state
     HashableDimensionKey queryKey;
     getOverlayKey(1000 /* uid */, "package1", &queryKey);
-    EXPECT_EQ(1, getStateInt(mgr, android::util::OVERLAY_STATE_CHANGED, queryKey));
+    EXPECT_EQ(OverlayStateChanged::ENTERED,
+              getStateInt(mgr, android::util::OVERLAY_STATE_CHANGED, queryKey));
 }
 
 /**
@@ -326,10 +417,12 @@
     sp<TestStateListener> listener1 = new TestStateListener();
     sp<TestStateListener> listener2 = new TestStateListener();
     sp<TestStateListener> listener3 = new TestStateListener();
+    sp<TestStateListener> listener4 = new TestStateListener();
     StateManager mgr;
     mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener1);
     mgr.registerListener(android::util::UID_PROCESS_STATE_CHANGED, listener2);
     mgr.registerListener(android::util::OVERLAY_STATE_CHANGED, listener3);
+    mgr.registerListener(android::util::WAKELOCK_STATE_CHANGED, listener4);
 
     std::shared_ptr<LogEvent> event1 = buildUidProcessEvent(
             1000,
@@ -346,8 +439,12 @@
             android::app::ProcessStateEnum::PROCESS_STATE_TOP);  //  state value: 1002
     std::shared_ptr<LogEvent> event5 =
             buildScreenEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON);
-    std::shared_ptr<LogEvent> event6 = buildOverlayEvent(1000, "package1", 1);
-    std::shared_ptr<LogEvent> event7 = buildOverlayEvent(1000, "package2", 2);
+    std::shared_ptr<LogEvent> event6 =
+            buildOverlayEvent(1000, "package1", OverlayStateChanged::ENTERED);
+    std::shared_ptr<LogEvent> event7 =
+            buildOverlayEvent(1000, "package2", OverlayStateChanged::EXITED);
+    std::shared_ptr<LogEvent> event8 = buildPartialWakelockEvent(1005, "tag1", true);
+    std::shared_ptr<LogEvent> event9 = buildPartialWakelockEvent(1005, "tag2", false);
 
     mgr.onLogEvent(*event1);
     mgr.onLogEvent(*event2);
@@ -356,11 +453,14 @@
     mgr.onLogEvent(*event5);
     mgr.onLogEvent(*event6);
     mgr.onLogEvent(*event7);
+    mgr.onLogEvent(*event8);
+    mgr.onLogEvent(*event9);
 
     // Query for UidProcessState of uid 1001
     HashableDimensionKey queryKey1;
     getUidProcessKey(1001, &queryKey1);
-    EXPECT_EQ(1003, getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey1));
+    EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE,
+              getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey1));
 
     // Query for UidProcessState of uid 1004 - not in state map
     HashableDimensionKey queryKey2;
@@ -370,15 +470,30 @@
 
     // Query for UidProcessState of uid 1001 - after change in state
     mgr.onLogEvent(*event4);
-    EXPECT_EQ(1002, getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey1));
+    EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_TOP,
+              getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey1));
 
     // Query for ScreenState
-    EXPECT_EQ(2, getStateInt(mgr, android::util::SCREEN_STATE_CHANGED, DEFAULT_DIMENSION_KEY));
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
+              getStateInt(mgr, android::util::SCREEN_STATE_CHANGED, DEFAULT_DIMENSION_KEY));
 
     // Query for OverlayState of uid 1000, package name "package2"
     HashableDimensionKey queryKey3;
     getOverlayKey(1000, "package2", &queryKey3);
-    EXPECT_EQ(2, getStateInt(mgr, android::util::OVERLAY_STATE_CHANGED, queryKey3));
+    EXPECT_EQ(OverlayStateChanged::EXITED,
+              getStateInt(mgr, android::util::OVERLAY_STATE_CHANGED, queryKey3));
+
+    // Query for WakelockState of uid 1005, tag 2
+    HashableDimensionKey queryKey4;
+    getPartialWakelockKey(1005, "tag2", &queryKey4);
+    EXPECT_EQ(WakelockStateChanged::RELEASE,
+              getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey4));
+
+    // Query for WakelockState of uid 1005, tag 1
+    HashableDimensionKey queryKey5;
+    getPartialWakelockKey(1005, "tag1", &queryKey5);
+    EXPECT_EQ(WakelockStateChanged::ACQUIRE,
+              getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey5));
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 7b651df..e0aecce 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -617,19 +617,6 @@
 
 void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid) {
     EXPECT_EQ(value.field(), atomId);
-    // Attribution field.
-    EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1);
-    // Uid only.
-    EXPECT_EQ(value.value_tuple().dimensions_value(0)
-        .value_tuple().dimensions_value_size(), 1);
-    EXPECT_EQ(value.value_tuple().dimensions_value(0)
-        .value_tuple().dimensions_value(0).field(), 1);
-    EXPECT_EQ(value.value_tuple().dimensions_value(0)
-        .value_tuple().dimensions_value(0).value_int(), uid);
-}
-
-void ValidateUidDimension(const DimensionsValue& value, int atomId, int uid) {
-    EXPECT_EQ(value.field(), atomId);
     EXPECT_EQ(value.value_tuple().dimensions_value_size(), 1);
     // Attribution field.
     EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1);
diff --git a/cmds/svc/src/com/android/commands/svc/UsbCommand.java b/cmds/svc/src/com/android/commands/svc/UsbCommand.java
index 3893be4..cd751f4 100644
--- a/cmds/svc/src/com/android/commands/svc/UsbCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/UsbCommand.java
@@ -42,7 +42,9 @@
                 + "         Sets the functions which, if the device was charging, become current on"
                     + "screen unlock. If function is blank, turn off this feature.\n"
                 + "       svc usb getFunctions\n"
-                + "          Gets the list of currently enabled functions\n\n"
+                + "          Gets the list of currently enabled functions\n"
+                + "       svc usb resetUsbGadget\n"
+                + "          Reset usb gadget\n\n"
                 + "possible values of [function] are any of 'mtp', 'ptp', 'rndis', 'midi'\n";
     }
 
@@ -75,6 +77,13 @@
                     System.err.println("Error communicating with UsbManager: " + e);
                 }
                 return;
+            } else if ("resetUsbGadget".equals(args[1])) {
+                try {
+                    usbMgr.resetUsbGadget();
+                } catch (RemoteException e) {
+                    System.err.println("Error communicating with UsbManager: " + e);
+                }
+                return;
             }
         }
         System.err.println(longHelp());
diff --git a/config/boot-image-profile.txt b/config/boot-image-profile.txt
index 4d0acb3..4aaf727 100644
--- a/config/boot-image-profile.txt
+++ b/config/boot-image-profile.txt
@@ -22519,8 +22519,6 @@
 HSPLcom/android/internal/telephony/PhoneFactory;->makeDefaultPhones(Landroid/content/Context;)V
 HSPLcom/android/internal/telephony/PhoneInternalInterface$DataActivityState;-><init>(Ljava/lang/String;I)V
 HSPLcom/android/internal/telephony/PhoneInternalInterface$DataActivityState;->values()[Lcom/android/internal/telephony/PhoneInternalInterface$DataActivityState;
-HSPLcom/android/internal/telephony/PhoneStateIntentReceiver;-><init>(Landroid/content/Context;Landroid/os/Handler;)V
-HSPLcom/android/internal/telephony/PhoneStateIntentReceiver;->notifyServiceState(I)V
 HSPLcom/android/internal/telephony/PhoneSubInfoController;->callPhoneMethodWithPermissionCheck(ILjava/lang/String;Ljava/lang/String;Lcom/android/internal/telephony/PhoneSubInfoController$CallPhoneMethodHelper;Lcom/android/internal/telephony/PhoneSubInfoController$PermissionCheckHelper;)Ljava/lang/Object;
 HSPLcom/android/internal/telephony/PhoneSubInfoController;->getCarrierInfoForImsiEncryption(IILjava/lang/String;)Landroid/telephony/ImsiEncryptionInfo;
 HSPLcom/android/internal/telephony/PhoneSubInfoController;->getGroupIdLevel1ForSubscriber(ILjava/lang/String;)Ljava/lang/String;
@@ -37729,7 +37727,6 @@
 Lcom/android/internal/telephony/PhoneInternalInterface$DataActivityState;
 Lcom/android/internal/telephony/PhoneInternalInterface;
 Lcom/android/internal/telephony/PhoneNotifier;
-Lcom/android/internal/telephony/PhoneStateIntentReceiver;
 Lcom/android/internal/telephony/PhoneSubInfoController$CallPhoneMethodHelper;
 Lcom/android/internal/telephony/PhoneSubInfoController$PermissionCheckHelper;
 Lcom/android/internal/telephony/PhoneSubInfoController;
diff --git a/config/preloaded-classes b/config/preloaded-classes
index 97f009c..e53c74b 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -4841,7 +4841,6 @@
 com.android.internal.telephony.PhoneFactory
 com.android.internal.telephony.PhoneInternalInterface
 com.android.internal.telephony.PhoneNotifier
-com.android.internal.telephony.PhoneStateIntentReceiver
 com.android.internal.telephony.PhoneSubInfoController$CallPhoneMethodHelper
 com.android.internal.telephony.PhoneSubInfoController$PermissionCheckHelper
 com.android.internal.telephony.PhoneSubInfoController
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index c0fee6e..ee6ccc2 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -17,6 +17,7 @@
 package android.accessibilityservice;
 
 import android.accessibilityservice.GestureDescription.MotionEventGenerator;
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -26,12 +27,15 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
 import android.graphics.Region;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -44,14 +48,19 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityInteractionClient;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 import android.view.accessibility.AccessibilityWindowInfo;
 
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.os.SomeArgs;
+import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 /**
  * Accessibility services should only be used to assist users with disabilities in using
@@ -385,7 +394,8 @@
     private static final String LOG_TAG = "AccessibilityService";
 
     /**
-     * Interface used by IAccessibilityServiceWrapper to call the service from its main thread.
+     * Interface used by IAccessibilityServiceClientWrapper to call the service from its main
+     * thread.
      * @hide
      */
     public interface Callbacks {
@@ -406,6 +416,8 @@
         /** Accessbility button clicked callbacks for different displays */
         void onAccessibilityButtonClicked(int displayId);
         void onAccessibilityButtonAvailabilityChanged(boolean available);
+        /** This is called when the system action list is changed. */
+        void onSystemActionsChanged();
     }
 
     /**
@@ -483,6 +495,9 @@
 
     private FingerprintGestureController mFingerprintGestureController;
 
+    /** @hide */
+    public static final String KEY_ACCESSIBILITY_SCREENSHOT = "screenshot";
+
     /**
      * Callback for {@link android.view.accessibility.AccessibilityEvent}s.
      *
@@ -1547,6 +1562,34 @@
             void onShowModeChanged(@NonNull SoftKeyboardController controller,
                     @SoftKeyboardShowMode int showMode);
         }
+
+        /**
+         * Switches the current IME for the user for whom the service is enabled. The change will
+         * persist until the current IME is explicitly changed again, and may persist beyond the
+         * life cycle of the requesting service.
+         *
+         * @param imeId The ID of the input method to make current. This IME must be installed and
+         *              enabled.
+         * @return {@code true} if the current input method was successfully switched to the input
+         *         method by {@code imeId},
+         *         {@code false} if the input method specified is not installed, not enabled, or
+         *         otherwise not available to become the current IME
+         *
+         * @see android.view.inputmethod.InputMethodInfo#getId()
+         */
+        public boolean switchToInputMethod(@NonNull String imeId) {
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getInstance().getConnection(
+                            mService.mConnectionId);
+            if (connection != null) {
+                try {
+                    return connection.switchToInputMethod(imeId);
+                } catch (RemoteException re) {
+                    throw new RuntimeException(re);
+                }
+            }
+            return false;
+        }
     }
 
     /**
@@ -1605,6 +1648,29 @@
                 available);
     }
 
+    /** This is called when the system action list is changed. */
+    public void onSystemActionsChanged() {
+    }
+
+    /**
+     * Returns a list of system actions available in the system right now.
+     *
+     * @return A list of available system actions.
+     */
+    public final @NonNull List<AccessibilityAction> getSystemActions() {
+        IAccessibilityServiceConnection connection =
+                AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
+        if (connection != null) {
+            try {
+                return connection.getSystemActions();
+            } catch (RemoteException re) {
+                Log.w(LOG_TAG, "Error while calling getSystemActions", re);
+                re.rethrowFromSystemServer();
+            }
+        }
+        return Collections.emptyList();
+    }
+
     /**
      * Performs a global action. Such an action can be performed
      * at any moment regardless of the current application or user
@@ -1733,6 +1799,51 @@
     }
 
     /**
+     * Takes a screenshot of the specified display and returns it by {@link Bitmap.Config#HARDWARE}
+     * format.
+     * <p>
+     * <strong>Note:</strong> In order to take screenshot your service has
+     * to declare the capability to take screenshot by setting the
+     * {@link android.R.styleable#AccessibilityService_canTakeScreenshot}
+     * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
+     * Besides, This API is only supported for default display now
+     * {@link Display#DEFAULT_DISPLAY}.
+     * </p>
+     *
+     * @param displayId The logic display id, must be {@link Display#DEFAULT_DISPLAY} for
+     *                  default display.
+     * @param executor Executor on which to run the callback.
+     * @param callback The callback invoked when the taking screenshot is done.
+     *
+     * @return {@code true} if the taking screenshot accepted, {@code false} if not.
+     */
+    public boolean takeScreenshot(int displayId, @NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<Bitmap> callback) {
+        Preconditions.checkNotNull(executor, "executor cannot be null");
+        Preconditions.checkNotNull(callback, "callback cannot be null");
+        final IAccessibilityServiceConnection connection =
+                AccessibilityInteractionClient.getInstance().getConnection(
+                        mConnectionId);
+        if (connection == null) {
+            return false;
+        }
+        try {
+            connection.takeScreenshotWithCallback(displayId, new RemoteCallback((result) -> {
+                final Bitmap screenshot = result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT);
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    executor.execute(() -> callback.accept(screenshot));
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            }));
+        } catch (RemoteException re) {
+            throw new RuntimeException(re);
+        }
+        return true;
+    }
+
+    /**
      * Implement to return the implementation of the internal accessibility
      * service interface.
      */
@@ -1811,6 +1922,11 @@
             public void onAccessibilityButtonAvailabilityChanged(boolean available) {
                 AccessibilityService.this.onAccessibilityButtonAvailabilityChanged(available);
             }
+
+            @Override
+            public void onSystemActionsChanged() {
+                AccessibilityService.this.onSystemActionsChanged();
+            }
         });
     }
 
@@ -1835,6 +1951,7 @@
         private static final int DO_ON_FINGERPRINT_GESTURE = 11;
         private static final int DO_ACCESSIBILITY_BUTTON_CLICKED = 12;
         private static final int DO_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED = 13;
+        private static final int DO_ON_SYSTEM_ACTIONS_CHANGED = 14;
 
         private final HandlerCaller mCaller;
 
@@ -1931,6 +2048,11 @@
             mCaller.sendMessage(message);
         }
 
+        /** This is called when the system action list is changed. */
+        public void onSystemActionsChanged() {
+            mCaller.sendMessage(mCaller.obtainMessage(DO_ON_SYSTEM_ACTIONS_CHANGED));
+        }
+
         @Override
         public void executeMessage(Message message) {
             switch (message.what) {
@@ -1952,14 +2074,14 @@
                             /* ignore - best effort */
                         }
                     }
-                } return;
-
+                    return;
+                }
                 case DO_ON_INTERRUPT: {
                     if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
                         mCallback.onInterrupt();
                     }
-                } return;
-
+                    return;
+                }
                 case DO_INIT: {
                     mConnectionId = message.arg1;
                     SomeArgs args = (SomeArgs) message.obj;
@@ -1979,18 +2101,18 @@
                         AccessibilityInteractionClient.getInstance().clearCache();
                         mCallback.init(AccessibilityInteractionClient.NO_ID, null);
                     }
-                } return;
-
+                    return;
+                }
                 case DO_ON_GESTURE: {
                     if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
                         mCallback.onGesture((AccessibilityGestureEvent) message.obj);
                     }
-                } return;
-
+                    return;
+                }
                 case DO_CLEAR_ACCESSIBILITY_CACHE: {
                     AccessibilityInteractionClient.getInstance().clearCache();
-                } return;
-
+                    return;
+                }
                 case DO_ON_KEY_EVENT: {
                     KeyEvent event = (KeyEvent) message.obj;
                     try {
@@ -2013,8 +2135,8 @@
                             /* ignore - best effort */
                         }
                     }
-                } return;
-
+                    return;
+                }
                 case DO_ON_MAGNIFICATION_CHANGED: {
                     if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
                         final SomeArgs args = (SomeArgs) message.obj;
@@ -2027,45 +2149,53 @@
                         mCallback.onMagnificationChanged(displayId, region, scale,
                                 centerX, centerY);
                     }
-                } return;
-
+                    return;
+                }
                 case DO_ON_SOFT_KEYBOARD_SHOW_MODE_CHANGED: {
                     if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
                         final int showMode = (int) message.arg1;
                         mCallback.onSoftKeyboardShowModeChanged(showMode);
                     }
-                } return;
-
+                    return;
+                }
                 case DO_GESTURE_COMPLETE: {
                     if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
                         final boolean successfully = message.arg2 == 1;
                         mCallback.onPerformGestureResult(message.arg1, successfully);
                     }
-                } return;
+                    return;
+                }
                 case DO_ON_FINGERPRINT_ACTIVE_CHANGED: {
                     if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
                         mCallback.onFingerprintCapturingGesturesChanged(message.arg1 == 1);
                     }
-                } return;
+                    return;
+                }
                 case DO_ON_FINGERPRINT_GESTURE: {
                     if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
                         mCallback.onFingerprintGesture(message.arg1);
                     }
-                } return;
-
-                case (DO_ACCESSIBILITY_BUTTON_CLICKED): {
+                    return;
+                }
+                case DO_ACCESSIBILITY_BUTTON_CLICKED: {
                     if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
                         mCallback.onAccessibilityButtonClicked(message.arg1);
                     }
-                } return;
-
-                case (DO_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED): {
+                    return;
+                }
+                case DO_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED: {
                     if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
                         final boolean available = (message.arg1 != 0);
                         mCallback.onAccessibilityButtonAvailabilityChanged(available);
                     }
-                } return;
-
+                    return;
+                }
+                case DO_ON_SYSTEM_ACTIONS_CHANGED: {
+                    if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+                        mCallback.onSystemActionsChanged();
+                    }
+                    return;
+                }
                 default :
                     Log.w(LOG_TAG, "Unknown message type " + message.what);
             }
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 5e2c1fa..12f2c3b 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -86,6 +86,7 @@
  * @attr ref android.R.styleable#AccessibilityService_settingsActivity
  * @attr ref android.R.styleable#AccessibilityService_nonInteractiveUiTimeout
  * @attr ref android.R.styleable#AccessibilityService_interactiveUiTimeout
+ * @attr ref android.R.styleable#AccessibilityService_canTakeScreenshot
  * @see AccessibilityService
  * @see android.view.accessibility.AccessibilityEvent
  * @see android.view.accessibility.AccessibilityManager
@@ -136,6 +137,12 @@
      */
     public static final int CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES = 0x00000040;
 
+    /**
+     * Capability: This accessibility service can take screenshot.
+     * @see android.R.styleable#AccessibilityService_canTakeScreenshot
+     */
+    public static final int CAPABILITY_CAN_TAKE_SCREENSHOT = 0x00000080;
+
     private static SparseArray<CapabilityInfo> sAvailableCapabilityInfos;
 
     /**
@@ -625,6 +632,10 @@
                     .AccessibilityService_canRequestFingerprintGestures, false)) {
                 mCapabilities |= CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES;
             }
+            if (asAttributes.getBoolean(com.android.internal.R.styleable
+                    .AccessibilityService_canTakeScreenshot, false)) {
+                mCapabilities |= CAPABILITY_CAN_TAKE_SCREENSHOT;
+            }
             TypedValue peekedValue = asAttributes.peekValue(
                     com.android.internal.R.styleable.AccessibilityService_description);
             if (peekedValue != null) {
@@ -794,6 +805,7 @@
      * @see #CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS
      * @see #CAPABILITY_CAN_CONTROL_MAGNIFICATION
      * @see #CAPABILITY_CAN_PERFORM_GESTURES
+     * @see #CAPABILITY_CAN_TAKE_SCREENSHOT
      */
     public int getCapabilities() {
         return mCapabilities;
@@ -810,6 +822,7 @@
      * @see #CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS
      * @see #CAPABILITY_CAN_CONTROL_MAGNIFICATION
      * @see #CAPABILITY_CAN_PERFORM_GESTURES
+     * @see #CAPABILITY_CAN_TAKE_SCREENSHOT
      *
      * @hide
      */
@@ -1253,6 +1266,8 @@
                 return "CAPABILITY_CAN_PERFORM_GESTURES";
             case CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES:
                 return "CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES";
+            case CAPABILITY_CAN_TAKE_SCREENSHOT:
+                return "CAPABILITY_CAN_TAKE_SCREENSHOT";
             default:
                 return "UNKNOWN";
         }
@@ -1314,6 +1329,10 @@
                     new CapabilityInfo(CAPABILITY_CAN_PERFORM_GESTURES,
                             R.string.capability_title_canPerformGestures,
                             R.string.capability_desc_canPerformGestures));
+            sAvailableCapabilityInfos.put(CAPABILITY_CAN_TAKE_SCREENSHOT,
+                    new CapabilityInfo(CAPABILITY_CAN_TAKE_SCREENSHOT,
+                            R.string.capability_title_canTakeScreenshot,
+                            R.string.capability_desc_canTakeScreenshot));
             if ((context == null) || fingerprintAvailable(context)) {
                 sAvailableCapabilityInfos.put(CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES,
                         new CapabilityInfo(CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES,
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
index 8ec3aea..58b0d19 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
@@ -55,4 +55,6 @@
     void onAccessibilityButtonClicked(int displayId);
 
     void onAccessibilityButtonAvailabilityChanged(boolean available);
+
+    void onSystemActionsChanged();
 }
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 4841781..5db4dd7 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -18,13 +18,17 @@
 
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
 import android.graphics.Region;
 import android.os.Bundle;
+import android.os.RemoteCallback;
 import android.view.MagnificationSpec;
 import android.view.MotionEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
 import android.view.accessibility.AccessibilityWindowInfo;
+import java.util.List;
 
 /**
  * Interface given to an AccessibilitySerivce to talk to the AccessibilityManagerService.
@@ -65,6 +69,7 @@
     AccessibilityServiceInfo getServiceInfo();
 
     boolean performGlobalAction(int action);
+    List<AccessibilityNodeInfo.AccessibilityAction> getSystemActions();
 
     void disableSelf();
 
@@ -91,6 +96,8 @@
 
     void setSoftKeyboardCallbackEnabled(boolean enabled);
 
+    boolean switchToInputMethod(String imeId);
+
     boolean isAccessibilityButtonAvailable();
 
     void sendGesture(int sequence, in ParceledListSlice gestureSteps);
@@ -100,4 +107,10 @@
     boolean isFingerprintGestureDetectionAvailable();
 
     IBinder getOverlayWindowToken(int displayid);
+
+    int getWindowIdForLeashToken(IBinder token);
+
+    Bitmap takeScreenshot(int displayId);
+
+    void takeScreenshotWithCallback(int displayId, in RemoteCallback callback);
 }
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 8fe2f12..f2702a8 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -25,6 +25,7 @@
 import android.annotation.Size;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.annotation.UserHandleAware;
 import android.app.Activity;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
@@ -40,6 +41,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Parcelable;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.text.TextUtils;
@@ -528,12 +530,9 @@
      *     authenticator known to the AccountManager service.  Empty (never
      *     null) if no authenticators are known.
      */
+    @UserHandleAware
     public AuthenticatorDescription[] getAuthenticatorTypes() {
-        try {
-            return mService.getAuthenticatorTypes(UserHandle.getCallingUserId());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return getAuthenticatorTypesAsUser(mContext.getUserId());
     }
 
     /**
@@ -584,13 +583,10 @@
      * @return An array of {@link Account}, one for each account. Empty (never null) if no accounts
      *         have been added.
      */
+    @UserHandleAware
     @NonNull
     public Account[] getAccounts() {
-        try {
-            return mService.getAccounts(null, mContext.getOpPackageName());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return getAccountsAsUser(mContext.getUserId());
     }
 
     /**
@@ -708,6 +704,7 @@
      * @return An array of {@link Account}, one per matching account. Empty (never null) if no
      *         accounts of the specified type have been added.
      */
+    @UserHandleAware
     @NonNull
     public Account[] getAccountsByType(String type) {
         return getAccountsByTypeAsUser(type, mContext.getUser());
@@ -1183,23 +1180,11 @@
      *     {@link #removeAccount(Account, Activity, AccountManagerCallback, Handler)}
      *     instead
      */
+    @UserHandleAware
     @Deprecated
     public AccountManagerFuture<Boolean> removeAccount(final Account account,
             AccountManagerCallback<Boolean> callback, Handler handler) {
-        if (account == null) throw new IllegalArgumentException("account is null");
-        return new Future2Task<Boolean>(handler, callback) {
-            @Override
-            public void doWork() throws RemoteException {
-                mService.removeAccount(mResponse, account, false);
-            }
-            @Override
-            public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
-                if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
-                    throw new AuthenticatorException("no result in response");
-                }
-                return bundle.getBoolean(KEY_BOOLEAN_RESULT);
-            }
-        }.start();
+        return removeAccountAsUser(account, callback, handler, mContext.getUser());
     }
 
     /**
@@ -1243,15 +1228,10 @@
      *      adding accounts (of this type) has been disabled by policy
      * </ul>
      */
+    @UserHandleAware
     public AccountManagerFuture<Bundle> removeAccount(final Account account,
             final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
-        if (account == null) throw new IllegalArgumentException("account is null");
-        return new AmsTask(activity, handler, callback) {
-            @Override
-            public void doWork() throws RemoteException {
-                mService.removeAccount(mResponse, account, activity != null);
-            }
-        }.start();
+        return removeAccountAsUser(account, activity, callback, handler, mContext.getUser());
     }
 
     /**
@@ -1841,24 +1821,30 @@
      *      creating a new account, usually because of network trouble
      * </ul>
      */
+    @UserHandleAware
     public AccountManagerFuture<Bundle> addAccount(final String accountType,
             final String authTokenType, final String[] requiredFeatures,
             final Bundle addAccountOptions,
             final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
-        if (accountType == null) throw new IllegalArgumentException("accountType is null");
-        final Bundle optionsIn = new Bundle();
-        if (addAccountOptions != null) {
-            optionsIn.putAll(addAccountOptions);
-        }
-        optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
-
-        return new AmsTask(activity, handler, callback) {
-            @Override
-            public void doWork() throws RemoteException {
-                mService.addAccount(mResponse, accountType, authTokenType,
-                        requiredFeatures, activity != null, optionsIn);
+        if (Process.myUserHandle().equals(mContext.getUser())) {
+            if (accountType == null) throw new IllegalArgumentException("accountType is null");
+            final Bundle optionsIn = new Bundle();
+            if (addAccountOptions != null) {
+                optionsIn.putAll(addAccountOptions);
             }
-        }.start();
+            optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
+
+            return new AmsTask(activity, handler, callback) {
+                @Override
+                public void doWork() throws RemoteException {
+                    mService.addAccount(mResponse, accountType, authTokenType,
+                            requiredFeatures, activity != null, optionsIn);
+                }
+            }.start();
+        } else {
+            return addAccountAsUser(accountType, authTokenType, requiredFeatures, addAccountOptions,
+                    activity, callback, handler, mContext.getUser());
+        }
     }
 
     /**
@@ -2002,6 +1988,7 @@
      *      verifying the password, usually because of network trouble
      * </ul>
      */
+    @UserHandleAware
     public AccountManagerFuture<Bundle> confirmCredentials(final Account account,
             final Bundle options,
             final Activity activity,
@@ -3209,6 +3196,7 @@
      *         </ul>
      * @see #startAddAccountSession and #startUpdateCredentialsSession
      */
+    @UserHandleAware
     public AccountManagerFuture<Bundle> finishSession(
             final Bundle sessionBundle,
             final Activity activity,
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index 0127138..ce68e08 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -34,7 +34,6 @@
     String getPassword(in Account account);
     String getUserData(in Account account, String key);
     AuthenticatorDescription[] getAuthenticatorTypes(int userId);
-    Account[] getAccounts(String accountType, String opPackageName);
     Account[] getAccountsForPackage(String packageName, int uid, String opPackageName);
     Account[] getAccountsByTypeForPackage(String type, String packageName, String opPackageName);
     Account[] getAccountsAsUser(String accountType, int userId, String opPackageName);
@@ -45,8 +44,6 @@
     void getAccountsByFeatures(in IAccountManagerResponse response, String accountType,
         in String[] features, String opPackageName);
     boolean addAccountExplicitly(in Account account, String password, in Bundle extras);
-    void removeAccount(in IAccountManagerResponse response, in Account account,
-        boolean expectActivityLaunch);
     void removeAccountAsUser(in IAccountManagerResponse response, in Account account,
         boolean expectActivityLaunch, int userId);
     boolean removeAccountExplicitly(in Account account);
diff --git a/core/java/android/annotation/SystemService.java b/core/java/android/annotation/SystemService.java
index 0c5d15e..c05c1ba 100644
--- a/core/java/android/annotation/SystemService.java
+++ b/core/java/android/annotation/SystemService.java
@@ -19,14 +19,12 @@
 import static java.lang.annotation.ElementType.TYPE;
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
-import android.content.Context;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 
 /**
  * Description of a system service available through
- * {@link Context#getSystemService(Class)}. This is used to auto-generate
+ * {@link android.content.Context#getSystemService(Class)}. This is used to auto-generate
  * documentation explaining how to obtain a reference to the service.
  *
  * @hide
@@ -36,9 +34,9 @@
 public @interface SystemService {
     /**
      * The string name of the system service that can be passed to
-     * {@link Context#getSystemService(String)}.
+     * {@link android.content.Context#getSystemService(String)}.
      *
-     * @see Context#getSystemServiceName(Class)
+     * @see android.content.Context#getSystemServiceName(Class)
      */
     String value();
 }
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 070a4f8..d952be5 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2539,7 +2539,8 @@
         mCalled = true;
 
         if (mAutoFillResetNeeded) {
-            getAutofillManager().onInvisibleForAutofill();
+            // If stopped without changing the configurations, the response should expire.
+            getAutofillManager().onInvisibleForAutofill(!mChangingConfigurations);
         } else if (mIntent != null
                 && mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)
                 && mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_CROSS_ACTIVITY)) {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 9aef20b..f7c4d96 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -22,6 +22,7 @@
 import android.Manifest;
 import android.annotation.DrawableRes;
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -65,6 +66,7 @@
 import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.WorkSource;
 import android.util.ArrayMap;
 import android.util.DisplayMetrics;
@@ -78,6 +80,7 @@
 import com.android.internal.os.TransferPipe;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.MemInfoReader;
+import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
 
 import org.xmlpull.v1.XmlSerializer;
@@ -571,6 +574,8 @@
     @IntDef(flag = true, prefix = { "PROCESS_CAPABILITY_" }, value = {
             PROCESS_CAPABILITY_NONE,
             PROCESS_CAPABILITY_FOREGROUND_LOCATION,
+            PROCESS_CAPABILITY_FOREGROUND_CAMERA,
+            PROCESS_CAPABILITY_FOREGROUND_MICROPHONE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ProcessCapability {}
@@ -583,9 +588,19 @@
     @TestApi
     public static final int PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1 << 0;
 
+    /** @hide Process can access camera while in foreground */
+    @TestApi
+    public static final int PROCESS_CAPABILITY_FOREGROUND_CAMERA = 1 << 1;
+
+    /** @hide Process can access microphone while in foreground */
+    @TestApi
+    public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 1 << 2;
+
     /** @hide all capabilities, the ORing of all flags in {@link ProcessCapability}*/
     @TestApi
-    public static final int PROCESS_CAPABILITY_ALL = PROCESS_CAPABILITY_FOREGROUND_LOCATION;
+    public static final int PROCESS_CAPABILITY_ALL = PROCESS_CAPABILITY_FOREGROUND_LOCATION
+            | PROCESS_CAPABILITY_FOREGROUND_CAMERA
+            | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
 
     // NOTE: If PROCESS_STATEs are added, then new fields must be added
     // to frameworks/base/core/proto/android/app/enums.proto and the following method must
@@ -3479,6 +3494,50 @@
     }
 
     /**
+     * Return a list of {@link ApplicationExitInfo} records containing the reasons for the most
+     * recent app deaths.
+     *
+     * <p class="note"> Note: System stores this historical information in a ring buffer and only
+     * the most recent records will be returned. </p>
+     *
+     * <p class="note"> Note: In the case that this application was bound to an external service
+     * with flag {@link android.content.Context#BIND_EXTERNAL_SERVICE}, the process of that external
+     * service will be included in this package's exit info. </p>
+     *
+     * @param packageName Optional, a null value means match all packages belonging to the
+     *                    caller's UID. If this package belongs to another UID, you must hold
+     *                    {@link android.Manifest.permission#DUMP} in order to retrieve it.
+     * @param pid         A process ID that used to belong to this package but died later; a value
+     *                    of 0 means to ignore this parameter and return all matching records.
+     * @param maxNum      The maximum number of results to be returned; a value of 0
+     *                    means to ignore this parameter and return all matching records
+     *
+     * @return a list of {@link ApplicationExitInfo} records matching the criteria, sorted in
+     *         the order from most recent to least recent.
+     */
+    @Nullable
+    public List<ApplicationExitInfo> getHistoricalProcessExitReasons(@Nullable String packageName,
+            @IntRange(from = 0) int pid, @IntRange(from = 0) int maxNum) {
+        try {
+            ParceledListSlice<ApplicationExitInfo> r = getService().getHistoricalProcessExitReasons(
+                    packageName, pid, maxNum, mContext.getUserId());
+            return r == null ? null : r.getList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /*
+     * @return Whether or not the low memory kill will be reported in
+     * {@link #getHistoricalProcessExitReasons}.
+     *
+     * @see {@link ApplicationExitInfo#REASON_LOW_MEMORY}
+     */
+    public static boolean isLowMemoryKillReportSupported() {
+        return SystemProperties.getBoolean("persist.sys.lmk.reportkills", false);
+    }
+
+    /**
      * Return the importance of a given package name, based on the processes that are
      * currently running.  The return value is one of the importance constants defined
      * in {@link RunningAppProcessInfo}, giving you the highest importance of all the
@@ -4064,6 +4123,7 @@
      * @hide
      */
     @SystemApi
+    @TestApi
     @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public boolean switchUser(@NonNull UserHandle user) {
         if (user == null) {
@@ -4073,6 +4133,29 @@
     }
 
     /**
+     * Updates mcc mnc configuration and applies changes to the entire system.
+     *
+     * @param mcc mcc configuration to update.
+     * @param mnc mnc configuration to update.
+     * @throws RemoteException; IllegalArgumentException if mcc or mnc is null;
+     * @return Returns {@code true} if the configuration was updated successfully;
+     *         {@code false} otherwise.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION)
+    public boolean updateMccMncConfiguration(@NonNull String mcc, @NonNull String mnc) {
+        if (mcc == null || mnc == null) {
+            throw new IllegalArgumentException("mcc or mnc cannot be null.");
+        }
+        try {
+            return getService().updateMccMncConfiguration(mcc, mnc);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Logs out current current foreground user by switching to the system user and stopping the
      * user being switched from.
      * @hide
@@ -4439,6 +4522,53 @@
     }
 
     /**
+     * Return if a given profile is in the foreground.
+     * @param userHandle UserHandle to check
+     * @return Returns the boolean result.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.CREATE_USERS
+    })
+    public boolean isProfileForeground(@NonNull UserHandle userHandle) {
+        UserManager userManager = mContext.getSystemService(UserManager.class);
+        if (userManager != null) {
+            for (UserInfo userInfo : userManager.getProfiles(getCurrentUser())) {
+                if (userInfo.id == userHandle.getIdentifier()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Kill the given PIDs, but the killing will be delayed until the device is idle
+     * and the given process is imperceptible.
+     *
+     * <p>You must hold the permission
+     * {@link android.Manifest.permission#FORCE_STOP_PACKAGES} to be able to
+     * call this method.
+     * </p>
+     *
+     * @param pids The list of the pids to be killed
+     * @pram reason The reason of the kill
+     *
+     * @hide
+     */
+    @SystemApi @TestApi
+    @RequiresPermission(Manifest.permission.FORCE_STOP_PACKAGES)
+    public void killProcessesWhenImperceptible(@NonNull int[] pids, @NonNull String reason) {
+        try {
+            getService().killProcessesWhenImperceptible(pids, reason);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * The AppTask allows you to manage your own application's tasks.
      * See {@link android.app.ActivityManager#getAppTasks()}
      */
@@ -4560,4 +4690,35 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Register with {@link HomeVisibilityObserver} with ActivityManager.
+     * @hide
+     */
+    @SystemApi
+    public void registerHomeVisibilityObserver(@NonNull HomeVisibilityObserver observer) {
+        Preconditions.checkNotNull(observer);
+        try {
+            observer.init(mContext, this);
+            getService().registerProcessObserver(observer.mObserver);
+            // Notify upon first registration.
+            observer.onHomeVisibilityChanged(observer.mIsHomeActivityVisible);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Unregister with {@link HomeVisibilityObserver} with ActivityManager.
+     * @hide
+     */
+    @SystemApi
+    public void unregisterHomeVisibilityObserver(@NonNull HomeVisibilityObserver observer) {
+        Preconditions.checkNotNull(observer);
+        try {
+            getService().unregisterProcessObserver(observer.mObserver);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 032e824..4f3e8ec 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -45,8 +45,19 @@
 
     // Access modes for handleIncomingUser.
     public static final int ALLOW_NON_FULL = 0;
+    /**
+     * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_USERS}
+     * if in the same profile group.
+     * Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required.
+     */
     public static final int ALLOW_NON_FULL_IN_PROFILE = 1;
     public static final int ALLOW_FULL_ONLY = 2;
+    /**
+     * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES}
+     * or {@link android.Manifest.permission#INTERACT_ACROSS_USERS} if in the same profile group.
+     * Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required.
+     */
+    public static final int ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE = 3;
 
     /**
      * Verify that calling app has access to the given provider.
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index a4f6f57..2ca5b1d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3349,8 +3349,8 @@
     }
 
     @Override
-    public void handleStartActivity(ActivityClientRecord r,
-            PendingTransactionActions pendingActions) {
+    public void handleStartActivity(IBinder token, PendingTransactionActions pendingActions) {
+        final ActivityClientRecord r = mActivities.get(token);
         final Activity activity = r.activity;
         if (r.activity == null) {
             // TODO(lifecycler): What do we do in this case?
@@ -3364,6 +3364,8 @@
             return;
         }
 
+        unscheduleGcIdler();
+
         // Start
         activity.performStart("handleStartActivity");
         r.setState(ON_START);
@@ -3400,6 +3402,9 @@
                                 + " did not call through to super.onPostCreate()");
             }
         }
+
+        updateVisibility(r, true /* show */);
+        mSomeActivitiesChanged = true;
     }
 
     /**
@@ -4660,8 +4665,8 @@
     @UnsupportedAppUsage
     final void performStopActivity(IBinder token, boolean saveState, String reason) {
         ActivityClientRecord r = mActivities.get(token);
-        performStopActivityInner(r, null /* stopInfo */, false /* keepShown */, saveState,
-                false /* finalStateRequest */, reason);
+        performStopActivityInner(r, null /* stopInfo */, saveState, false /* finalStateRequest */,
+                reason);
     }
 
     private static final class ProviderRefCount {
@@ -4687,25 +4692,19 @@
     }
 
     /**
-     * Core implementation of stopping an activity.  Note this is a little
-     * tricky because the server's meaning of stop is slightly different
-     * than our client -- for the server, stop means to save state and give
-     * it the result when it is done, but the window may still be visible.
-     * For the client, we want to call onStop()/onStart() to indicate when
-     * the activity's UI visibility changes.
+     * Core implementation of stopping an activity.
      * @param r Target activity client record.
      * @param info Action that will report activity stop to server.
-     * @param keepShown Flag indicating whether the activity is still shown.
      * @param saveState Flag indicating whether the activity state should be saved.
      * @param finalStateRequest Flag indicating if this call is handling final lifecycle state
      *                          request for a transaction.
      * @param reason Reason for performing this operation.
      */
-    private void performStopActivityInner(ActivityClientRecord r, StopInfo info, boolean keepShown,
+    private void performStopActivityInner(ActivityClientRecord r, StopInfo info,
             boolean saveState, boolean finalStateRequest, String reason) {
         if (localLOGV) Slog.v(TAG, "Performing stop of " + r);
         if (r != null) {
-            if (!keepShown && r.stopped) {
+            if (r.stopped) {
                 if (r.activity.mFinished) {
                     // If we are finishing, we won't call onResume() in certain
                     // cases.  So here we likewise don't want to call onStop()
@@ -4740,9 +4739,7 @@
                 }
             }
 
-            if (!keepShown) {
-                callActivityOnStop(r, saveState, reason);
-            }
+            callActivityOnStop(r, saveState, reason);
         }
     }
 
@@ -4810,20 +4807,19 @@
     }
 
     @Override
-    public void handleStopActivity(IBinder token, boolean show, int configChanges,
+    public void handleStopActivity(IBinder token, int configChanges,
             PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) {
         final ActivityClientRecord r = mActivities.get(token);
         r.activity.mConfigChangeFlags |= configChanges;
 
         final StopInfo stopInfo = new StopInfo();
-        performStopActivityInner(r, stopInfo, show, true /* saveState */, finalStateRequest,
+        performStopActivityInner(r, stopInfo, true /* saveState */, finalStateRequest,
                 reason);
 
         if (localLOGV) Slog.v(
-            TAG, "Finishing stop of " + r + ": show=" + show
-            + " win=" + r.window);
+            TAG, "Finishing stop of " + r + ": win=" + r.window);
 
-        updateVisibility(r, show);
+        updateVisibility(r, false);
 
         // Make sure any pending writes are now committed.
         if (!r.isPreHoneycomb()) {
@@ -4859,34 +4855,6 @@
         }
     }
 
-    @Override
-    public void handleWindowVisibility(IBinder token, boolean show) {
-        ActivityClientRecord r = mActivities.get(token);
-
-        if (r == null) {
-            Log.w(TAG, "handleWindowVisibility: no activity for token " + token);
-            return;
-        }
-
-        if (!show && !r.stopped) {
-            performStopActivityInner(r, null /* stopInfo */, show, false /* saveState */,
-                    false /* finalStateRequest */, "handleWindowVisibility");
-        } else if (show && r.getLifecycleState() == ON_STOP) {
-            // If we are getting ready to gc after going to the background, well
-            // we are back active so skip it.
-            unscheduleGcIdler();
-
-            r.activity.performRestart(true /* start */, "handleWindowVisibility");
-            r.setState(ON_START);
-        }
-        if (r.activity.mDecor != null) {
-            if (false) Slog.v(
-                TAG, "Handle window " + r + " visibility: " + show);
-            updateVisibility(r, show);
-        }
-        mSomeActivitiesChanged = true;
-    }
-
     // TODO: This method should be changed to use {@link #performStopActivityInner} to perform to
     // stop operation on the activity to reduce code duplication and the chance of fixing a bug in
     // one place and missing the other.
@@ -7358,6 +7326,15 @@
         }
     }
 
+    float getFloatCoreSetting(String key, float defaultValue) {
+        synchronized (mResourcesManager) {
+            if (mCoreSettings != null) {
+                return mCoreSettings.getFloat(key, defaultValue);
+            }
+            return defaultValue;
+        }
+    }
+
     private static class AndroidOs extends ForwardingOs {
         /**
          * Install selective syscall interception. For example, this is used to
diff --git a/core/java/android/app/AppGlobals.java b/core/java/android/app/AppGlobals.java
index 81e1565..f66bf0d 100644
--- a/core/java/android/app/AppGlobals.java
+++ b/core/java/android/app/AppGlobals.java
@@ -75,4 +75,20 @@
             return defaultValue;
         }
     }
+
+    /**
+     * Gets the value of a float core setting.
+     *
+     * @param key The setting key.
+     * @param defaultValue The setting default value.
+     * @return The core settings.
+     */
+    public static float getFloatCoreSetting(String key, float defaultValue) {
+        ActivityThread currentActivityThread = ActivityThread.currentActivityThread();
+        if (currentActivityThread != null) {
+            return currentActivityThread.getFloatCoreSetting(key, defaultValue);
+        } else {
+            return defaultValue;
+        }
+    }
 }
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 4a8e4e2..46f8669 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -726,7 +726,17 @@
     /** @hide Capture the device's display contents and/or audio */
     @UnsupportedAppUsage
     public static final int OP_PROJECT_MEDIA = 46;
-    /** @hide Activate a VPN connection without user intervention. */
+    /**
+     * Start (without additional user intervention) a VPN connection, as used by {@link
+     * android.net.VpnService} along with as Platform VPN connections, as used by {@link
+     * android.net.VpnManager}
+     *
+     * <p>This appop is granted to apps that have already been given user consent to start
+     * VpnService based VPN connections. As this is a superset of OP_ACTIVATE_PLATFORM_VPN, this
+     * appop also allows the starting of Platform VPNs.
+     *
+     * @hide
+     */
     @UnsupportedAppUsage
     public static final int OP_ACTIVATE_VPN = 47;
     /** @hide Access the WallpaperManagerAPI to write wallpapers. */
@@ -852,10 +862,21 @@
     public static final int OP_MANAGE_EXTERNAL_STORAGE = 92;
     /** @hide Communicate cross-profile within the same profile group. */
     public static final int OP_INTERACT_ACROSS_PROFILES = 93;
+    /**
+     * Start (without additional user intervention) a Platform VPN connection, as used by {@link
+     * android.net.VpnManager}
+     *
+     * <p>This appop is granted to apps that have already been given user consent to start Platform
+     * VPN connections. This appop is insufficient to start VpnService based VPNs; OP_ACTIVATE_VPN
+     * is needed for that.
+     *
+     * @hide
+     */
+    public static final int OP_ACTIVATE_PLATFORM_VPN = 94;
 
     /** @hide */
     @UnsupportedAppUsage
-    public static final int _NUM_OP = 94;
+    public static final int _NUM_OP = 95;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1138,6 +1159,7 @@
     @SystemApi
     public static final String OPSTR_ACCESS_ACCESSIBILITY = "android:access_accessibility";
     /** @hide Read device identifiers */
+    @SystemApi
     public static final String OPSTR_READ_DEVICE_IDENTIFIERS = "android:read_device_identifiers";
     /** @hide Query all packages on device */
     public static final String OPSTR_QUERY_ALL_PACKAGES = "android:query_all_packages";
@@ -1149,6 +1171,8 @@
     /** @hide Communicate cross-profile within the same profile group. */
     @SystemApi
     public static final String OPSTR_INTERACT_ACROSS_PROFILES = "android:interact_across_profiles";
+    /** @hide Start Platform VPN without user intervention */
+    public static final String OPSTR_ACTIVATE_PLATFORM_VPN = "android:activate_platform_vpn";
 
 
     /** {@link #sAppOpsToNote} not initialized yet for this op */
@@ -1333,6 +1357,7 @@
             OP_QUERY_ALL_PACKAGES,              // QUERY_ALL_PACKAGES
             OP_MANAGE_EXTERNAL_STORAGE,         // MANAGE_EXTERNAL_STORAGE
             OP_INTERACT_ACROSS_PROFILES,        //INTERACT_ACROSS_PROFILES
+            OP_ACTIVATE_PLATFORM_VPN,           // ACTIVATE_PLATFORM_VPN
     };
 
     /**
@@ -1433,6 +1458,7 @@
             OPSTR_QUERY_ALL_PACKAGES,
             OPSTR_MANAGE_EXTERNAL_STORAGE,
             OPSTR_INTERACT_ACROSS_PROFILES,
+            OPSTR_ACTIVATE_PLATFORM_VPN,
     };
 
     /**
@@ -1533,7 +1559,8 @@
             "ACCESS_MEDIA_LOCATION",
             "QUERY_ALL_PACKAGES",
             "MANAGE_EXTERNAL_STORAGE",
-            "INTERACT_ACROSS_PROFILES"
+            "INTERACT_ACROSS_PROFILES",
+            "ACTIVATE_PLATFORM_VPN",
     };
 
     /**
@@ -1636,6 +1663,7 @@
             null, // no permission for OP_QUERY_ALL_PACKAGES
             Manifest.permission.MANAGE_EXTERNAL_STORAGE,
             android.Manifest.permission.INTERACT_ACROSS_PROFILES,
+            null, // no permission for OP_ACTIVATE_PLATFORM_VPN
     };
 
     /**
@@ -1738,6 +1766,7 @@
             null, // QUERY_ALL_PACKAGES
             null, // MANAGE_EXTERNAL_STORAGE
             null, // INTERACT_ACROSS_PROFILES
+            null, // ACTIVATE_PLATFORM_VPN
     };
 
     /**
@@ -1839,6 +1868,7 @@
             false, // QUERY_ALL_PACKAGES
             false, // MANAGE_EXTERNAL_STORAGE
             false, // INTERACT_ACROSS_PROFILES
+            false, // ACTIVATE_PLATFORM_VPN
     };
 
     /**
@@ -1939,6 +1969,7 @@
             AppOpsManager.MODE_DEFAULT, // QUERY_ALL_PACKAGES
             AppOpsManager.MODE_DEFAULT, // MANAGE_EXTERNAL_STORAGE
             AppOpsManager.MODE_DEFAULT, // INTERACT_ACROSS_PROFILES
+            AppOpsManager.MODE_IGNORED, // ACTIVATE_PLATFORM_VPN
     };
 
     /**
@@ -2043,6 +2074,7 @@
             false, // QUERY_ALL_PACKAGES
             false, // MANAGE_EXTERNAL_STORAGE
             false, // INTERACT_ACROSS_PROFILES
+            false, // ACTIVATE_PLATFORM_VPN
     };
 
     /**
@@ -4311,6 +4343,9 @@
                     mHistoricalUidOps.removeAt(i);
                 } else {
                     uidOp.filter(packageName, featureId, opNames, filter, scaleFactor);
+                    if (uidOp.getPackageCount() == 0) {
+                        mHistoricalUidOps.removeAt(i);
+                    }
                 }
             }
         }
@@ -4650,6 +4685,9 @@
                     mHistoricalPackageOps.removeAt(i);
                 } else {
                     packageOps.filter(featureId, opNames, filter, fractionToRemove);
+                    if (packageOps.getFeatureCount() == 0) {
+                        mHistoricalPackageOps.removeAt(i);
+                    }
                 }
             }
         }
@@ -4899,6 +4937,9 @@
                     mHistoricalFeatureOps.removeAt(i);
                 } else {
                     featureOps.filter(opNames, filter, fractionToRemove);
+                    if (featureOps.getOpCount() == 0) {
+                        mHistoricalFeatureOps.removeAt(i);
+                    }
                 }
             }
         }
diff --git a/core/java/android/app/ApplicationExitInfo.aidl b/core/java/android/app/ApplicationExitInfo.aidl
new file mode 100644
index 0000000..fb7d8f6
--- /dev/null
+++ b/core/java/android/app/ApplicationExitInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.app;
+
+parcelable ApplicationExitInfo;
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
new file mode 100644
index 0000000..4bf5f07
--- /dev/null
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -0,0 +1,901 @@
+/*
+ * 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.app;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.ActivityManager.RunningAppProcessInfo.Importance;
+import android.icu.text.SimpleDateFormat;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.DebugUtils;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+import android.util.proto.WireTypeMismatchException;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Date;
+import java.util.Objects;
+
+/**
+ * Describes the information of an application process's death.
+ *
+ * <p>
+ * Application process could die for many reasons, for example {@link #REASON_LOW_MEMORY}
+ * when it was killed by the ystem because it was running low on memory. Reason
+ * of the death can be retrieved via {@link #getReason}. Besides the reason, there are a few other
+ * auxiliary APIs like {@link #getStatus} and {@link #getImportance} to help the caller with
+ * additional diagnostic information.
+ * </p>
+ *
+ */
+public final class ApplicationExitInfo implements Parcelable {
+
+    /**
+     * Application process died due to unknown reason.
+     */
+    public static final int REASON_UNKNOWN = 0;
+
+    /**
+     * Application process exit normally by itself, for example,
+     * via {@link java.lang.System#exit}; {@link #getStatus} will specify the exit code.
+     *
+     * <p>Applications should normally not do this, as the system has a better knowledge
+     * in terms of process management.</p>
+     */
+    public static final int REASON_EXIT_SELF = 1;
+
+    /**
+     * Application process died due to the result of an OS signal; for example,
+     * {@link android.system.OsConstants#SIGKILL}; {@link #getStatus} will specify the signal
+     * number.
+     */
+    public static final int REASON_SIGNALED = 2;
+
+    /**
+     * Application process was killed by the system low memory killer, meaning the system was
+     * under memory pressure at the time of kill.
+     *
+     * <p class="note">
+     * Not all devices support reporting {@link #REASON_LOW_MEMORY}; on a device with no such
+     * support, when a process is killed due to memory pressure, the {@link #getReason} will return
+     * {@link #REASON_SIGNALED} and {@link #getStatus} will return
+     * the value {@link android.system.OsConstants#SIGKILL}.
+     *
+     * Application should use {@link ActivityManager#isLowMemoryKillReportSupported} to check
+     * if the device supports reporting {@link #REASON_LOW_MEMORY} or not.
+     * </p>
+     */
+    public static final int REASON_LOW_MEMORY = 3;
+
+    /**
+     * Application process died because of an unhandled exception in Java code.
+     */
+    public static final int REASON_CRASH = 4;
+
+    /**
+     * Application process died because of a native code crash.
+     */
+    public static final int REASON_CRASH_NATIVE = 5;
+
+    /**
+     * Application process was killed due to being unresponsive (ANR).
+     */
+    public static final int REASON_ANR = 6;
+
+    /**
+     * Application process was killed because of initialization failure,
+     * for example, it took too long to attach to the system during the start,
+     * or there was an error during initialization.
+     */
+    public static final int REASON_INITIALIZATION_FAILURE = 7;
+
+    /**
+     * Application process was killed due to a runtime permission change.
+     */
+    public static final int REASON_PERMISSION_CHANGE = 8;
+
+    /**
+     * Application process was killed by the system due to excessive resource usage.
+     */
+    public static final int REASON_EXCESSIVE_RESOURCE_USAGE = 9;
+
+    /**
+     * Application process was killed by the system for various other reasons,
+     * for example, the application package got disabled by the user;
+     * {@link #getDescription} will specify the cause given by the system.
+     */
+    public static final int REASON_OTHER = 10;
+
+    /**
+     * Application process kills subreason is unknown.
+     *
+     * For internal use only.
+     * @hide
+     */
+    public static final int SUBREASON_UNKNOWN = 0;
+
+    /**
+     * Application process was killed because user quit it on the "wait for debugger" dialog;
+     * this would be set when the reason is {@link #REASON_OTHER}.
+     *
+     * For internal use only.
+     * @hide
+     */
+    public static final int SUBREASON_WAIT_FOR_DEBUGGER = 1;
+
+    /**
+     * Application process was killed by the activity manager because there were too many cached
+     * processes; this would be set only when the reason is {@link #REASON_OTHER}.
+     *
+     * For internal use only.
+     * @hide
+     */
+    public static final int SUBREASON_TOO_MANY_CACHED = 2;
+
+    /**
+     * Application process was killed by the activity manager because there were too many empty
+     * processes; this would be set only when the reason is {@link #REASON_OTHER}.
+     *
+     * For internal use only.
+     * @hide
+     */
+    public static final int SUBREASON_TOO_MANY_EMPTY = 3;
+
+    /**
+     * Application process was killed by the activity manager because there were too many cached
+     * processes and this process had been in empty state for a long time;
+     * this would be set only when the reason is {@link #REASON_OTHER}.
+     *
+     * For internal use only.
+     * @hide
+     */
+    public static final int SUBREASON_TRIM_EMPTY = 4;
+
+    /**
+     * Application process was killed by the activity manager because system was on memory pressure
+     * and this process took large amount of cached memory;
+     * this would be set only when the reason is {@link #REASON_OTHER}.
+     *
+     * For internal use only.
+     * @hide
+     */
+    public static final int SUBREASON_LARGE_CACHED = 5;
+
+    /**
+     * Application process was killed by the activity manager because the system was on low memory
+     * pressure for a significant amount of time since last idle;
+     * this would be set only when the reason is {@link #REASON_OTHER}.
+     *
+     * For internal use only.
+     * @hide
+     */
+    public static final int SUBREASON_MEMORY_PRESSURE = 6;
+
+    /**
+     * Application process was killed by the activity manager due to excessive CPU usage;
+     * this would be set only when the reason is {@link #REASON_EXCESSIVE_RESOURCE_USAGE}.
+     *
+     * For internal use only.
+     * @hide
+     */
+    public static final int SUBREASON_EXCESSIVE_CPU = 7;
+
+    /**
+     * @see {@link #getPid}
+     */
+    private int mPid;
+
+    /**
+     * @see {@link #getRealUid}
+     */
+    private int mRealUid;
+
+    /**
+     * @see {@link #getPackageUid}
+     */
+    private int mPackageUid;
+
+    /**
+     * @see {@link #getDefiningUid}
+     */
+    private int mDefiningUid;
+
+    /**
+     * @see {@link #getProcessName}
+     */
+    private String mProcessName;
+
+    /**
+     * @see {@link #getReason}
+     */
+    private @Reason int mReason;
+
+    /**
+     * @see {@link #getStatus}
+     */
+    private int mStatus;
+
+    /**
+     * @see {@link #getImportance}
+     */
+    private @Importance int mImportance;
+
+    /**
+     * @see {@link #getPss}
+     */
+    private int mPss;
+
+    /**
+     * @see {@link #getRss}
+     */
+    private int mRss;
+
+    /**
+     * @see {@link #getTimestamp}
+     */
+    private long mTimestamp;
+
+    /**
+     * @see {@link #getDescription}
+     */
+    private @Nullable String mDescription;
+
+    /**
+     * @see {@link #getSubReason}
+     */
+    private @SubReason int mSubReason;
+
+    /**
+     * @see {@link #getConnectionGroup}
+     */
+    private int mConnectionGroup;
+
+    /**
+     * @see {@link #getPackageName}
+     */
+    private String mPackageName;
+
+    /**
+     * @see {@link #getPackageList}
+     */
+    private String[] mPackageList;
+
+    /** @hide */
+    @IntDef(prefix = { "REASON_" }, value = {
+        REASON_UNKNOWN,
+        REASON_EXIT_SELF,
+        REASON_SIGNALED,
+        REASON_LOW_MEMORY,
+        REASON_CRASH,
+        REASON_CRASH_NATIVE,
+        REASON_ANR,
+        REASON_INITIALIZATION_FAILURE,
+        REASON_PERMISSION_CHANGE,
+        REASON_EXCESSIVE_RESOURCE_USAGE,
+        REASON_OTHER,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Reason {}
+
+    /** @hide */
+    @IntDef(prefix = { "SUBREASON_" }, value = {
+        SUBREASON_UNKNOWN,
+        SUBREASON_WAIT_FOR_DEBUGGER,
+        SUBREASON_TOO_MANY_CACHED,
+        SUBREASON_TOO_MANY_EMPTY,
+        SUBREASON_TRIM_EMPTY,
+        SUBREASON_LARGE_CACHED,
+        SUBREASON_MEMORY_PRESSURE,
+        SUBREASON_EXCESSIVE_CPU,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SubReason {}
+
+    /**
+     * The process id of the process that died.
+     */
+    public int getPid() {
+        return mPid;
+    }
+
+    /**
+     * The kernel user identifier of the process, most of the time the system uses this
+     * to do access control checks. It's typically the uid of the package where the component is
+     * running from, except the case of isolated process, where this field identifies the kernel
+     * user identifier that this process is actually running with, while the {@link #getPackageUid}
+     * identifies the kernel user identifier that is assigned at the package installation time.
+     */
+    public int getRealUid() {
+        return mRealUid;
+    }
+
+    /**
+     * Similar to {@link #getRealUid}, it's the kernel user identifier that is assigned at the
+     * package installation time.
+     */
+    public int getPackageUid() {
+        return mPackageUid;
+    }
+
+    /**
+     * Return the defining kernel user identifier, maybe different from {@link #getRealUid} and
+     * {@link #getPackageUid}, if an external service was bound with the flag
+     * {@link android.content.Context#BIND_EXTERNAL_SERVICE} - in this case, this field here
+     * will be the kernel user identifier of the external service provider.
+     */
+    public int getDefiningUid() {
+        return mDefiningUid;
+    }
+
+    /**
+     * The actual process name it was running with.
+     */
+    public @NonNull String getProcessName() {
+        return mProcessName;
+    }
+
+    /**
+     * The reason code of the process's death.
+     */
+    public @Reason int getReason() {
+        return mReason;
+    }
+
+    /*
+     * The exit status argument of exit() if the application calls it, or the signal
+     * number if the application is signaled.
+     */
+    public int getStatus() {
+        return mStatus;
+    }
+
+    /**
+     * The importance of the process that it used to have before the death.
+     */
+    public @Importance int getImportance() {
+        return mImportance;
+    }
+
+    /*
+     * Last proportional set size of the memory that the process had used in kB.
+     *
+     * <p class="note">Note: This is the value from last sampling on the process,
+     * it's NOT the exact memory information prior to its death; and it'll be zero
+     * if the process died before system had a chance to take the sample. </p>
+     */
+    public int getPss() {
+        return mPss;
+    }
+
+    /**
+     * Last resident set size of the memory that the process had used in kB.
+     *
+     * <p class="note">Note: This is the value from last sampling on the process,
+     * it's NOT the exact memory information prior to its death; and it'll be zero
+     * if the process died before system had a chance to take the sample. </p>
+     */
+    public int getRss() {
+        return mRss;
+    }
+
+    /**
+     * The timestamp of the process's death, in milliseconds since the epoch.
+     */
+    public long getTimestamp() {
+        return mTimestamp;
+    }
+
+    /**
+     * The human readable description of the process's death, given by the system; could be null.
+     */
+    public @Nullable String getDescription() {
+        return mDescription;
+    }
+
+    /**
+     * Return the user id of the record on a multi-user system.
+     *
+     * @hide
+     */
+    @SystemApi
+    public @NonNull UserHandle getUserHandle() {
+        return UserHandle.of(UserHandle.getUserId(mRealUid));
+    }
+
+    /**
+     * A subtype reason in conjunction with {@link #mReason}.
+     *
+     * For internal use only.
+     *
+     * @hide
+     */
+    public @SubReason int getSubReason() {
+        return mSubReason;
+    }
+
+    /**
+     * The connection group this process belongs to, if there is any.
+     * @see {@link android.content.Context#updateServiceGroup}.
+     *
+     * For internal use only.
+     *
+     * @hide
+     */
+    public int getConnectionGroup() {
+        return mConnectionGroup;
+    }
+
+    /**
+     * Name of first package running in this process;
+     *
+     * For system internal use only, will not retain across processes.
+     *
+     * @hide
+     */
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
+     * List of packages running in this process;
+     *
+     * For system internal use only, will not retain across processes.
+     *
+     * @hide
+     */
+    public String[] getPackageList() {
+        return mPackageList;
+    }
+
+    /**
+     * @see {@link #getPid}
+     *
+     * @hide
+     */
+    public void setPid(final int pid) {
+        mPid = pid;
+    }
+
+    /**
+     * @see {@link #getRealUid}
+     *
+     * @hide
+     */
+    public void setRealUid(final int uid) {
+        mRealUid = uid;
+    }
+
+    /**
+     * @see {@link #getPackageUid}
+     *
+     * @hide
+     */
+    public void setPackageUid(final int uid) {
+        mPackageUid = uid;
+    }
+
+    /**
+     * @see {@link #getDefiningUid}
+     *
+     * @hide
+     */
+    public void setDefiningUid(final int uid) {
+        mDefiningUid = uid;
+    }
+
+    /**
+     * @see {@link #getProcessName}
+     *
+     * @hide
+     */
+    public void setProcessName(final String processName) {
+        mProcessName = processName;
+    }
+
+    /**
+     * @see {@link #getReason}
+     *
+     * @hide
+     */
+    public void setReason(final @Reason int reason) {
+        mReason = reason;
+    }
+
+    /**
+     * @see {@link #getStatus}
+     *
+     * @hide
+     */
+    public void setStatus(final int status) {
+        mStatus = status;
+    }
+
+    /**
+     * @see {@link #getImportance}
+     *
+     * @hide
+     */
+    public void setImportance(final @Importance int importance) {
+        mImportance = importance;
+    }
+
+    /**
+     * @see {@link #getPss}
+     *
+     * @hide
+     */
+    public void setPss(final int pss) {
+        mPss = pss;
+    }
+
+    /**
+     * @see {@link #getRss}
+     *
+     * @hide
+     */
+    public void setRss(final int rss) {
+        mRss = rss;
+    }
+
+    /**
+     * @see {@link #getTimestamp}
+     *
+     * @hide
+     */
+    public void setTimestamp(final long timestamp) {
+        mTimestamp = timestamp;
+    }
+
+    /**
+     * @see {@link #getDescription}
+     *
+     * @hide
+     */
+    public void setDescription(final String description) {
+        mDescription = description;
+    }
+
+    /**
+     * @see {@link #getSubReason}
+     *
+     * @hide
+     */
+    public void setSubReason(final @SubReason int subReason) {
+        mSubReason = subReason;
+    }
+
+    /**
+     * @see {@link #getConnectionGroup}
+     *
+     * @hide
+     */
+    public void setConnectionGroup(final int connectionGroup) {
+        mConnectionGroup = connectionGroup;
+    }
+
+    /**
+     * @see {@link #getPackageName}
+     *
+     * @hide
+     */
+    public void setPackageName(final String packageName) {
+        mPackageName = packageName;
+    }
+
+    /**
+     * @see {@link #getPackageList}
+     *
+     * @hide
+     */
+    public void setPackageList(final String[] packageList) {
+        mPackageList = packageList;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mPid);
+        dest.writeInt(mRealUid);
+        dest.writeInt(mPackageUid);
+        dest.writeInt(mDefiningUid);
+        dest.writeString(mProcessName);
+        dest.writeInt(mConnectionGroup);
+        dest.writeInt(mReason);
+        dest.writeInt(mSubReason);
+        dest.writeInt(mStatus);
+        dest.writeInt(mImportance);
+        dest.writeInt(mPss);
+        dest.writeInt(mRss);
+        dest.writeLong(mTimestamp);
+        dest.writeString(mDescription);
+    }
+
+    /** @hide */
+    public ApplicationExitInfo() {
+    }
+
+    /** @hide */
+    public ApplicationExitInfo(ApplicationExitInfo other) {
+        mPid = other.mPid;
+        mRealUid = other.mRealUid;
+        mPackageUid = other.mPackageUid;
+        mDefiningUid = other.mDefiningUid;
+        mProcessName = other.mProcessName;
+        mConnectionGroup = other.mConnectionGroup;
+        mReason = other.mReason;
+        mStatus = other.mStatus;
+        mSubReason = other.mSubReason;
+        mImportance = other.mImportance;
+        mPss = other.mPss;
+        mRss = other.mRss;
+        mTimestamp = other.mTimestamp;
+        mDescription = other.mDescription;
+    }
+
+    private ApplicationExitInfo(@NonNull Parcel in) {
+        mPid = in.readInt();
+        mRealUid = in.readInt();
+        mPackageUid = in.readInt();
+        mDefiningUid = in.readInt();
+        mProcessName = in.readString();
+        mConnectionGroup = in.readInt();
+        mReason = in.readInt();
+        mSubReason = in.readInt();
+        mStatus = in.readInt();
+        mImportance = in.readInt();
+        mPss = in.readInt();
+        mRss = in.readInt();
+        mTimestamp = in.readLong();
+        mDescription = in.readString();
+    }
+
+    public @NonNull static final Creator<ApplicationExitInfo> CREATOR =
+            new Creator<ApplicationExitInfo>() {
+        @Override
+        public ApplicationExitInfo createFromParcel(Parcel in) {
+            return new ApplicationExitInfo(in);
+        }
+
+        @Override
+        public ApplicationExitInfo[] newArray(int size) {
+            return new ApplicationExitInfo[size];
+        }
+    };
+
+    /** @hide */
+    public void dump(@NonNull PrintWriter pw, @Nullable String prefix, @Nullable String seqSuffix,
+            @NonNull SimpleDateFormat sdf) {
+        pw.println(prefix + "ApplicationExitInfo " + seqSuffix + ":");
+        pw.println(prefix + "  timestamp=" + sdf.format(new Date(mTimestamp)));
+        pw.println(prefix + "  pid=" + mPid);
+        pw.println(prefix + "  realUid=" + mRealUid);
+        pw.println(prefix + "  packageUid=" + mPackageUid);
+        pw.println(prefix + "  definingUid=" + mDefiningUid);
+        pw.println(prefix + "  user=" + UserHandle.getUserId(mPackageUid));
+        pw.println(prefix + "  process=" + mProcessName);
+        pw.println(prefix + "  reason=" + mReason + " (" + reasonCodeToString(mReason) + ")");
+        pw.println(prefix + "  status=" + mStatus);
+        pw.println(prefix + "  importance=" + mImportance);
+        pw.print(prefix + "  pss="); DebugUtils.printSizeValue(pw, mPss << 10); pw.println();
+        pw.print(prefix + "  rss="); DebugUtils.printSizeValue(pw, mRss << 10); pw.println();
+        pw.println(prefix + "  description=" + mDescription);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("ApplicationExitInfo(timestamp=");
+        sb.append(new SimpleDateFormat().format(new Date(mTimestamp)));
+        sb.append(" pid=").append(mPid);
+        sb.append(" realUid=").append(mRealUid);
+        sb.append(" packageUid=").append(mPackageUid);
+        sb.append(" definingUid=").append(mDefiningUid);
+        sb.append(" user=").append(UserHandle.getUserId(mPackageUid));
+        sb.append(" process=").append(mProcessName);
+        sb.append(" reason=").append(mReason).append(" (")
+                .append(reasonCodeToString(mReason)).append(")");
+        sb.append(" status=").append(mStatus);
+        sb.append(" importance=").append(mImportance);
+        sb.append(" pss="); DebugUtils.sizeValueToString(mPss << 10, sb);
+        sb.append(" rss="); DebugUtils.sizeValueToString(mRss << 10, sb);
+        sb.append(" description=").append(mDescription);
+        return sb.toString();
+    }
+
+    private static String reasonCodeToString(@Reason int reason) {
+        switch (reason) {
+            case REASON_EXIT_SELF:
+                return "EXIT_SELF";
+            case REASON_SIGNALED:
+                return "SIGNALED";
+            case REASON_LOW_MEMORY:
+                return "LOW_MEMORY";
+            case REASON_CRASH:
+                return "APP CRASH(EXCEPTION)";
+            case REASON_CRASH_NATIVE:
+                return "APP CRASH(NATIVE)";
+            case REASON_ANR:
+                return "ANR";
+            case REASON_INITIALIZATION_FAILURE:
+                return "INITIALIZATION FAILURE";
+            case REASON_PERMISSION_CHANGE:
+                return "PERMISSION CHANGE";
+            case REASON_EXCESSIVE_RESOURCE_USAGE:
+                return "EXCESSIVE RESOURCE USAGE";
+            case REASON_OTHER:
+                return "OTHER KILLS BY SYSTEM";
+            default:
+                return "UNKNOWN";
+        }
+    }
+
+    /** @hide */
+    public static String subreasonToString(@SubReason int subreason) {
+        switch (subreason) {
+            case SUBREASON_WAIT_FOR_DEBUGGER:
+                return "WAIT FOR DEBUGGER";
+            case SUBREASON_TOO_MANY_CACHED:
+                return "TOO MANY CACHED PROCS";
+            case SUBREASON_TOO_MANY_EMPTY:
+                return "TOO MANY EMPTY PROCS";
+            case SUBREASON_TRIM_EMPTY:
+                return "TRIM EMPTY";
+            case SUBREASON_LARGE_CACHED:
+                return "LARGE CACHED";
+            case SUBREASON_MEMORY_PRESSURE:
+                return "MEMORY PRESSURE";
+            case SUBREASON_EXCESSIVE_CPU:
+                return "EXCESSIVE CPU USAGE";
+            default:
+                return "UNKNOWN";
+        }
+    }
+
+    /**
+     * Write to a protocol buffer output stream.
+     * Protocol buffer message definition at {@link android.app.ApplicationExitInfoProto}
+     *
+     * @param proto    Stream to write the ApplicationExitInfo object to.
+     * @param fieldId  Field Id of the ApplicationExitInfo as defined in the parent message
+     * @hide
+     */
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(ApplicationExitInfoProto.PID, mPid);
+        proto.write(ApplicationExitInfoProto.REAL_UID, mRealUid);
+        proto.write(ApplicationExitInfoProto.PACKAGE_UID, mPackageUid);
+        proto.write(ApplicationExitInfoProto.DEFINING_UID, mDefiningUid);
+        proto.write(ApplicationExitInfoProto.PROCESS_NAME, mProcessName);
+        proto.write(ApplicationExitInfoProto.CONNECTION_GROUP, mConnectionGroup);
+        proto.write(ApplicationExitInfoProto.REASON, mReason);
+        proto.write(ApplicationExitInfoProto.SUB_REASON, mSubReason);
+        proto.write(ApplicationExitInfoProto.STATUS, mStatus);
+        proto.write(ApplicationExitInfoProto.IMPORTANCE, mImportance);
+        proto.write(ApplicationExitInfoProto.PSS, mPss);
+        proto.write(ApplicationExitInfoProto.RSS, mRss);
+        proto.write(ApplicationExitInfoProto.TIMESTAMP, mTimestamp);
+        proto.write(ApplicationExitInfoProto.DESCRIPTION, mDescription);
+        proto.end(token);
+    }
+
+    /**
+     * Read from a protocol buffer input stream.
+     * Protocol buffer message definition at {@link android.app.ApplicationExitInfoProto}
+     *
+     * @param proto   Stream to read the ApplicationExitInfo object from.
+     * @param fieldId Field Id of the ApplicationExitInfo as defined in the parent message
+     * @hide
+     */
+    public void readFromProto(ProtoInputStream proto, long fieldId)
+            throws IOException, WireTypeMismatchException {
+        final long token = proto.start(fieldId);
+        while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (proto.getFieldNumber()) {
+                case (int) ApplicationExitInfoProto.PID:
+                    mPid = proto.readInt(ApplicationExitInfoProto.PID);
+                    break;
+                case (int) ApplicationExitInfoProto.REAL_UID:
+                    mRealUid = proto.readInt(ApplicationExitInfoProto.REAL_UID);
+                    break;
+                case (int) ApplicationExitInfoProto.PACKAGE_UID:
+                    mPackageUid = proto.readInt(ApplicationExitInfoProto.PACKAGE_UID);
+                    break;
+                case (int) ApplicationExitInfoProto.DEFINING_UID:
+                    mDefiningUid = proto.readInt(ApplicationExitInfoProto.DEFINING_UID);
+                    break;
+                case (int) ApplicationExitInfoProto.PROCESS_NAME:
+                    mProcessName = proto.readString(ApplicationExitInfoProto.PROCESS_NAME);
+                    break;
+                case (int) ApplicationExitInfoProto.CONNECTION_GROUP:
+                    mConnectionGroup = proto.readInt(ApplicationExitInfoProto.CONNECTION_GROUP);
+                    break;
+                case (int) ApplicationExitInfoProto.REASON:
+                    mReason = proto.readInt(ApplicationExitInfoProto.REASON);
+                    break;
+                case (int) ApplicationExitInfoProto.SUB_REASON:
+                    mSubReason = proto.readInt(ApplicationExitInfoProto.SUB_REASON);
+                    break;
+                case (int) ApplicationExitInfoProto.STATUS:
+                    mStatus = proto.readInt(ApplicationExitInfoProto.STATUS);
+                    break;
+                case (int) ApplicationExitInfoProto.IMPORTANCE:
+                    mImportance = proto.readInt(ApplicationExitInfoProto.IMPORTANCE);
+                    break;
+                case (int) ApplicationExitInfoProto.PSS:
+                    mPss = proto.readInt(ApplicationExitInfoProto.PSS);
+                    break;
+                case (int) ApplicationExitInfoProto.RSS:
+                    mRss = proto.readInt(ApplicationExitInfoProto.RSS);
+                    break;
+                case (int) ApplicationExitInfoProto.TIMESTAMP:
+                    mTimestamp = proto.readLong(ApplicationExitInfoProto.TIMESTAMP);
+                    break;
+                case (int) ApplicationExitInfoProto.DESCRIPTION:
+                    mDescription = proto.readString(ApplicationExitInfoProto.DESCRIPTION);
+                    break;
+            }
+        }
+        proto.end(token);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (other == null || !(other instanceof ApplicationExitInfo)) {
+            return false;
+        }
+        ApplicationExitInfo o = (ApplicationExitInfo) other;
+        return mPid == o.mPid && mRealUid == o.mRealUid && mPackageUid == o.mPackageUid
+                && mDefiningUid == o.mDefiningUid
+                && mConnectionGroup == o.mConnectionGroup && mReason == o.mReason
+                && mSubReason == o.mSubReason && mImportance == o.mImportance
+                && mStatus == o.mStatus && mTimestamp == o.mTimestamp
+                && mPss == o.mPss && mRss == o.mRss
+                && TextUtils.equals(mProcessName, o.mProcessName)
+                && TextUtils.equals(mDescription, o.mDescription);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mPid;
+        result = 31 * result + mRealUid;
+        result = 31 * result + mPackageUid;
+        result = 31 * result + mDefiningUid;
+        result = 31 * result + mConnectionGroup;
+        result = 31 * result + mReason;
+        result = 31 * result + mSubReason;
+        result = 31 * result + mImportance;
+        result = 31 * result + mStatus;
+        result = 31 * result + mPss;
+        result = 31 * result + mRss;
+        result = 31 * result + Long.hashCode(mTimestamp);
+        result = 31 * result + Objects.hashCode(mProcessName);
+        result = 31 * result + Objects.hashCode(mDescription);
+        return result;
+    }
+}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 1c6a561..4e6319d 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -133,6 +133,10 @@
     // Default flags to use with PackageManager when no flags are given.
     private final static int sDefaultFlags = PackageManager.GET_SHARED_LIBRARY_FILES;
 
+    // Name of the resource which provides background permission button string
+    public static final String APP_PERMISSION_BUTTON_ALLOW_ALWAYS =
+            "app_permission_button_allow_always";
+
     private final Object mLock = new Object();
 
     @GuardedBy("mLock")
@@ -614,8 +618,7 @@
         return hasSystemFeature(name, 0);
     }
 
-    @Override
-    public boolean hasSystemFeature(String name, int version) {
+    private boolean hasSystemFeatureUncached(String name, int version) {
         try {
             return mPM.hasSystemFeature(name, version);
         } catch (RemoteException e) {
@@ -623,6 +626,64 @@
         }
     }
 
+    // Make this cache relatively large.  There are many system features and
+    // none are ever invalidated.  MPTS tests suggests that the cache should
+    // hold at least 150 entries.
+    private static final int SYS_FEATURE_CACHE_SIZE = 256;
+    private static final String CACHE_KEY_SYS_FEATURE_PROPERTY = "cache_key.has_system_feature";
+
+    private class SystemFeatureQuery {
+        public final String name;
+        public final int version;
+        public SystemFeatureQuery(String n, int v) {
+            name = n;
+            version = v;
+        }
+        @Override
+        public String toString() {
+            return String.format("SystemFeatureQuery(name=\"%s\", version=%d)",
+                    name, version);
+        }
+        @Override
+        public boolean equals(Object o) {
+            if (o instanceof SystemFeatureQuery) {
+                SystemFeatureQuery r = (SystemFeatureQuery) o;
+                return Objects.equals(name, r.name) &&  version == r.version;
+            } else {
+                return false;
+            }
+        }
+        @Override
+        public int hashCode() {
+            return Objects.hashCode(name) + version;
+        }
+    }
+
+    private final PropertyInvalidatedCache<SystemFeatureQuery, Boolean> mSysFeatureCache =
+            new PropertyInvalidatedCache<SystemFeatureQuery, Boolean>(
+                SYS_FEATURE_CACHE_SIZE,
+                CACHE_KEY_SYS_FEATURE_PROPERTY) {
+                @Override
+                protected Boolean recompute(SystemFeatureQuery query) {
+                    return hasSystemFeatureUncached(query.name, query.version);
+                }
+            };
+
+    @Override
+    public boolean hasSystemFeature(String name, int version) {
+        return mSysFeatureCache.query(new SystemFeatureQuery(name, version)).booleanValue();
+    }
+
+    /** @hide */
+    public void disableSysFeatureCache() {
+        mSysFeatureCache.disableLocal();
+    }
+
+    /** @hide */
+    public static void invalidateSysFeatureCache() {
+        PropertyInvalidatedCache.invalidateCache(CACHE_KEY_SYS_FEATURE_PROPERTY);
+    }
+
     @Override
     public int checkPermission(String permName, String pkgName) {
         try {
@@ -807,6 +868,26 @@
     }
 
     @Override
+    public CharSequence getBackgroundPermissionOptionLabel() {
+        try {
+
+            String permissionController = getPermissionControllerPackageName();
+            Context context =
+                    mContext.createPackageContext(permissionController, 0);
+
+            int textId = context.getResources().getIdentifier(APP_PERMISSION_BUTTON_ALLOW_ALWAYS,
+                    "string", "com.android.permissioncontroller");
+//                    permissionController); STOPSHIP b/147434671
+            if (textId != 0) {
+                return context.getText(textId);
+            }
+        } catch (NameNotFoundException e) {
+            Log.e(TAG, "Permission controller not found.", e);
+        }
+        return "";
+    }
+
+    @Override
     public int checkSignatures(String pkg1, String pkg2) {
         try {
             return mPM.checkSignatures(pkg1, pkg2);
@@ -1651,6 +1732,10 @@
     }
 
     @UnsupportedAppUsage
+    protected ApplicationPackageManager(ContextImpl context, IPackageManager pm) {
+        this(context, pm, ActivityThread.getPermissionManager());
+    }
+
     protected ApplicationPackageManager(ContextImpl context, IPackageManager pm,
             IPermissionManager permissionManager) {
         mContext = context;
diff --git a/core/java/android/app/AsyncNotedAppOp.java b/core/java/android/app/AsyncNotedAppOp.java
index d993ec1..3febf71 100644
--- a/core/java/android/app/AsyncNotedAppOp.java
+++ b/core/java/android/app/AsyncNotedAppOp.java
@@ -256,10 +256,10 @@
     };
 
     @DataClass.Generated(
-            time = 1578321462996L,
+            time = 1578516519372L,
             codegenVersion = "1.0.14",
             sourceFile = "frameworks/base/core/java/android/app/AsyncNotedAppOp.java",
-            inputSignatures = "private final @android.annotation.IntRange(from=0L, to=93L) int mOpCode\nprivate final @android.annotation.IntRange(from=0L) int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mFeatureId\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.IntRange(from=0L) long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true)")
+            inputSignatures = "private final @android.annotation.IntRange(from=0L, to=94L) int mOpCode\nprivate final @android.annotation.IntRange(from=0L) int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mFeatureId\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.IntRange(from=0L) long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index f9a689a..d2235f1 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -119,7 +119,6 @@
     /**
      * Stop the activity.
      * @param token Target activity token.
-     * @param show Flag indicating whether activity is still shown.
      * @param configChanges Activity configuration changes.
      * @param pendingActions Pending actions to be used on this or later stages of activity
      *                       transaction.
@@ -127,7 +126,7 @@
      *                          request for a transaction.
      * @param reason Reason for performing this operation.
      */
-    public abstract void handleStopActivity(IBinder token, boolean show, int configChanges,
+    public abstract void handleStopActivity(IBinder token, int configChanges,
             PendingTransactionActions pendingActions, boolean finalStateRequest, String reason);
 
     /** Report that activity was stopped to server. */
@@ -161,15 +160,12 @@
     /** Request that an activity enter picture-in-picture. */
     public abstract void handlePictureInPictureRequested(IBinder token);
 
-    /** Update window visibility. */
-    public abstract void handleWindowVisibility(IBinder token, boolean show);
-
     /** Perform activity launch. */
     public abstract Activity handleLaunchActivity(ActivityThread.ActivityClientRecord r,
             PendingTransactionActions pendingActions, Intent customIntent);
 
     /** Perform activity start. */
-    public abstract void handleStartActivity(ActivityThread.ActivityClientRecord r,
+    public abstract void handleStartActivity(IBinder token,
             PendingTransactionActions pendingActions);
 
     /** Get package info. */
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index cd84310..b7555ee 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -19,7 +19,6 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.AutofillOptions;
 import android.content.BroadcastReceiver;
@@ -201,7 +200,7 @@
     @UnsupportedAppUsage
     private @Nullable ClassLoader mClassLoader;
 
-    private final @Nullable IBinder mActivityToken;
+    private final @Nullable IBinder mToken;
 
     private final @NonNull UserHandle mUser;
 
@@ -219,7 +218,7 @@
     private final @NonNull ResourcesManager mResourcesManager;
     @UnsupportedAppUsage
     private @NonNull Resources mResources;
-    private @Nullable Display mDisplay; // may be null if default display
+    private @Nullable Display mDisplay; // may be null if invalid display or not initialized yet.
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private final int mFlags;
@@ -244,6 +243,9 @@
 
     private final Object mSync = new Object();
 
+    private boolean mIsSystemOrSystemUiContext;
+    private boolean mIsUiContext;
+
     @GuardedBy("mSync")
     private File mDatabasesDir;
     @GuardedBy("mSync")
@@ -1883,6 +1885,9 @@
 
     @Override
     public Object getSystemService(String name) {
+        if (isUiComponent(name) && !isUiContext()) {
+            Log.w(TAG, name + " should be accessed from Activity or other visual Context");
+        }
         return SystemServiceRegistry.getSystemService(this, name);
     }
 
@@ -1891,6 +1896,15 @@
         return SystemServiceRegistry.getSystemServiceName(serviceClass);
     }
 
+    boolean isUiContext() {
+        return mIsSystemOrSystemUiContext || mIsUiContext;
+    }
+
+    private static boolean isUiComponent(String name) {
+        return WINDOW_SERVICE.equals(name) || LAYOUT_INFLATER_SERVICE.equals(name)
+                || WALLPAPER_SERVICE.equals(name);
+    }
+
     @Override
     public int checkPermission(String permission, int pid, int uid) {
         if (permission == null) {
@@ -2229,12 +2243,12 @@
         LoadedApk pi = mMainThread.getPackageInfo(application, mResources.getCompatibilityInfo(),
                 flags | CONTEXT_REGISTER_PACKAGE);
         if (pi != null) {
-            ContextImpl c = new ContextImpl(this, mMainThread, pi, null, null, mActivityToken,
+            ContextImpl c = new ContextImpl(this, mMainThread, pi, null, null, mToken,
                     new UserHandle(UserHandle.getUserId(application.uid)), flags, null, null);
 
             final int displayId = getDisplayId();
 
-            c.setResources(createResources(mActivityToken, pi, null, displayId, null,
+            c.setResources(createResources(mToken, pi, null, displayId, null,
                     getDisplayAdjustments(displayId).getCompatibilityInfo()));
             if (c.mResources != null) {
                 return c;
@@ -2258,18 +2272,18 @@
             // The system resources are loaded in every application, so we can safely copy
             // the context without reloading Resources.
             return new ContextImpl(this, mMainThread, mPackageInfo, mFeatureId, null,
-                    mActivityToken, user, flags, null, null);
+                    mToken, user, flags, null, null);
         }
 
         LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(),
                 flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier());
         if (pi != null) {
             ContextImpl c = new ContextImpl(this, mMainThread, pi, mFeatureId, null,
-                    mActivityToken, user, flags, null, null);
+                    mToken, user, flags, null, null);
 
             final int displayId = getDisplayId();
 
-            c.setResources(createResources(mActivityToken, pi, null, displayId, null,
+            c.setResources(createResources(mToken, pi, null, displayId, null,
                     getDisplayAdjustments(displayId).getCompatibilityInfo()));
             if (c.mResources != null) {
                 return c;
@@ -2301,12 +2315,12 @@
         final String[] paths = mPackageInfo.getSplitPaths(splitName);
 
         final ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo,
-                mFeatureId, splitName, mActivityToken, mUser, mFlags, classLoader, null);
+                mFeatureId, splitName, mToken, mUser, mFlags, classLoader, null);
 
         final int displayId = getDisplayId();
 
         context.setResources(ResourcesManager.getInstance().getResources(
-                mActivityToken,
+                mToken,
                 mPackageInfo.getResDir(),
                 paths,
                 mPackageInfo.getOverlayDirs(),
@@ -2325,10 +2339,10 @@
         }
 
         ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mFeatureId,
-                mSplitName, mActivityToken, mUser, mFlags, mClassLoader, null);
+                mSplitName, mToken, mUser, mFlags, mClassLoader, null);
 
         final int displayId = getDisplayId();
-        context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId,
+        context.setResources(createResources(mToken, mPackageInfo, mSplitName, displayId,
                 overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo()));
         return context;
     }
@@ -2340,19 +2354,36 @@
         }
 
         ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mFeatureId,
-                mSplitName, mActivityToken, mUser, mFlags, mClassLoader, null);
+                mSplitName, mToken, mUser, mFlags, mClassLoader, null);
 
         final int displayId = display.getDisplayId();
-        context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId,
+        context.setResources(createResources(mToken, mPackageInfo, mSplitName, displayId,
                 null, getDisplayAdjustments(displayId).getCompatibilityInfo()));
         context.mDisplay = display;
         return context;
     }
 
     @Override
+    public @NonNull WindowContext createWindowContext(int type) {
+        if (getDisplay() == null) {
+            throw new UnsupportedOperationException("WindowContext can only be created from "
+                    + "other visual contexts, such as Activity or one created with "
+                    + "Context#createDisplayContext(Display)");
+        }
+        return new WindowContext(this, null /* token */, type);
+    }
+
+    ContextImpl createBaseWindowContext(IBinder token) {
+        ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mFeatureId,
+                mSplitName, token, mUser, mFlags, mClassLoader, null);
+        context.mIsUiContext = true;
+        return context;
+    }
+
+    @Override
     public @NonNull Context createFeatureContext(@Nullable String featureId) {
         return new ContextImpl(this, mMainThread, mPackageInfo, featureId, mSplitName,
-                mActivityToken, mUser, mFlags, mClassLoader, null);
+                mToken, mUser, mFlags, mClassLoader, null);
     }
 
     @Override
@@ -2360,7 +2391,7 @@
         final int flags = (mFlags & ~Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE)
                 | Context.CONTEXT_DEVICE_PROTECTED_STORAGE;
         return new ContextImpl(this, mMainThread, mPackageInfo, mFeatureId, mSplitName,
-                mActivityToken, mUser, flags, mClassLoader, null);
+                mToken, mUser, flags, mClassLoader, null);
     }
 
     @Override
@@ -2368,7 +2399,7 @@
         final int flags = (mFlags & ~Context.CONTEXT_DEVICE_PROTECTED_STORAGE)
                 | Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE;
         return new ContextImpl(this, mMainThread, mPackageInfo, mFeatureId, mSplitName,
-                mActivityToken, mUser, flags, mClassLoader, null);
+                mToken, mUser, flags, mClassLoader, null);
     }
 
     @Override
@@ -2394,8 +2425,6 @@
         return (mFlags & Context.CONTEXT_IGNORE_SECURITY) != 0;
     }
 
-    @UnsupportedAppUsage
-    @TestApi
     @Override
     public Display getDisplay() {
         if (mDisplay == null) {
@@ -2408,7 +2437,8 @@
 
     @Override
     public int getDisplayId() {
-        return mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
+        final Display display = getDisplay();
+        return display != null ? display.getDisplayId() : Display.DEFAULT_DISPLAY;
     }
 
     @Override
@@ -2518,6 +2548,7 @@
         context.setResources(packageInfo.getResources());
         context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(),
                 context.mResourcesManager.getDisplayMetrics());
+        context.mIsSystemOrSystemUiContext = true;
         return context;
     }
 
@@ -2535,6 +2566,7 @@
         context.setResources(createResources(null, packageInfo, null, displayId, null,
                 packageInfo.getCompatibilityInfo()));
         context.updateDisplay(displayId);
+        context.mIsSystemOrSystemUiContext = true;
         return context;
     }
 
@@ -2584,6 +2616,7 @@
 
         ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null,
                 activityInfo.splitName, activityToken, null, 0, classLoader, null);
+        context.mIsUiContext = true;
 
         // Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY.
         displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY;
@@ -2629,7 +2662,7 @@
         }
 
         mMainThread = mainThread;
-        mActivityToken = activityToken;
+        mToken = activityToken;
         mFlags = flags;
 
         if (user == null) {
@@ -2649,6 +2682,7 @@
             opPackageName = container.mOpPackageName;
             setResources(container.mResources);
             mDisplay = container.mDisplay;
+            mIsSystemOrSystemUiContext = container.mIsSystemOrSystemUiContext;
         } else {
             mBasePackageName = packageInfo.mPackageName;
             ApplicationInfo ainfo = packageInfo.getApplicationInfo();
@@ -2710,7 +2744,7 @@
     @Override
     @UnsupportedAppUsage
     public IBinder getActivityToken() {
-        return mActivityToken;
+        return mToken;
     }
 
     private void checkMode(int mode) {
diff --git a/core/java/android/app/DisabledWallpaperManager.java b/core/java/android/app/DisabledWallpaperManager.java
index 5185941..1f323c3 100644
--- a/core/java/android/app/DisabledWallpaperManager.java
+++ b/core/java/android/app/DisabledWallpaperManager.java
@@ -41,8 +41,7 @@
     // Don't need to worry about synchronization
     private static DisabledWallpaperManager sInstance;
 
-    // TODO(b/138939803): STOPSHIP changed to false and/or remove it
-    private static final boolean DEBUG = true;
+    private static final boolean DEBUG = false;
 
     @NonNull
     static DisabledWallpaperManager getInstance() {
@@ -53,7 +52,6 @@
     }
 
     private DisabledWallpaperManager() {
-        super(null, null, null);
     }
 
     @Override
@@ -66,10 +64,6 @@
         return false;
     }
 
-    // TODO(b/138939803): STOPSHIP methods below should not be necessary,
-    // callers should check if isWallpaperSupported(), consider removing them to keep this class
-    // simpler
-
     private static <T> T unsupported() {
         if (DEBUG) Log.w(TAG, "unsupported method called; returning null", new Exception());
         return null;
@@ -343,4 +337,9 @@
     public boolean isWallpaperBackupEligible(int which) {
         return unsupportedBoolean();
     }
+
+    @Override
+    public boolean wallpaperSupportsWcg(int which) {
+        return unsupportedBoolean();
+    }
 }
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index 49c389a..1278ff6 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -16,7 +16,9 @@
 
 package android.app;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemApi;
@@ -40,11 +42,13 @@
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
+import android.provider.BaseColumns;
 import android.provider.Downloads;
 import android.provider.MediaStore;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.text.TextUtils;
+import android.util.LongSparseArray;
 import android.util.Pair;
 
 import java.io.File;
@@ -1069,6 +1073,37 @@
     }
 
     /**
+     * Notify {@link DownloadManager} that the given {@link MediaStore} items
+     * were just deleted so that {@link DownloadManager} internal data
+     * structures can be cleaned up.
+     *
+     * @param idToMime map from {@link BaseColumns#_ID} to
+     *            {@link ContentResolver#getType(Uri)}.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE)
+    public void onMediaStoreDownloadsDeleted(@NonNull LongSparseArray<String> idToMime) {
+        try (ContentProviderClient client = mResolver
+                .acquireUnstableContentProviderClient(mBaseUri)) {
+           final Bundle callExtras = new Bundle();
+           final long[] ids = new long[idToMime.size()];
+           final String[] mimeTypes = new String[idToMime.size()];
+           for (int i = idToMime.size() - 1; i >= 0; --i) {
+               ids[i] = idToMime.keyAt(i);
+               mimeTypes[i] = idToMime.valueAt(i);
+           }
+           callExtras.putLongArray(android.provider.Downloads.EXTRA_IDS, ids);
+           callExtras.putStringArray(android.provider.Downloads.EXTRA_MIME_TYPES,
+                   mimeTypes);
+           client.call(android.provider.Downloads.CALL_MEDIASTORE_DOWNLOADS_DELETED,
+                   null, callExtras);
+        } catch (RemoteException e) {
+            // Should not happen
+        }
+    }
+
+    /**
      * Enqueue a new download.  The download will start automatically once the download manager is
      * ready to execute it and connectivity is available.
      *
diff --git a/core/java/android/app/HomeVisibilityObserver.java b/core/java/android/app/HomeVisibilityObserver.java
new file mode 100644
index 0000000..f3465f8
--- /dev/null
+++ b/core/java/android/app/HomeVisibilityObserver.java
@@ -0,0 +1,98 @@
+/*
+ * 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;
+
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+
+import java.util.List;
+
+/**
+ * An observer / callback to create and register by
+ * {@link ActivityManager#registerHomeVisibilityObserver} so that it's triggered when
+ * visibility of home page changes.
+ * @hide
+ */
+@SystemApi
+public abstract class HomeVisibilityObserver {
+    private Context mContext;
+    private ActivityManager mActivityManager;
+    /** @hide */
+    IProcessObserver.Stub mObserver;
+    /** @hide */
+    boolean mIsHomeActivityVisible;
+
+    /** @hide */
+    void init(Context context, ActivityManager activityManager) {
+        mContext = context;
+        mActivityManager = activityManager;
+        mIsHomeActivityVisible = isHomeActivityVisible();
+    }
+
+    /**
+     * The API that needs implemented and will be triggered when activity on home page changes.
+     */
+    public abstract void onHomeVisibilityChanged(boolean isHomeActivityVisible);
+
+    public HomeVisibilityObserver() {
+        mObserver = new IProcessObserver.Stub() {
+            @Override
+            public void onForegroundActivitiesChanged(int pid, int uid, boolean fg) {
+                boolean isHomeActivityVisible = isHomeActivityVisible();
+                if (mIsHomeActivityVisible != isHomeActivityVisible) {
+                    mIsHomeActivityVisible = isHomeActivityVisible;
+                    onHomeVisibilityChanged(mIsHomeActivityVisible);
+                }
+            }
+
+            @Override
+            public void onForegroundServicesChanged(int pid, int uid, int fgServiceTypes) {
+            }
+
+            @Override
+            public void onProcessDied(int pid, int uid) {
+            }
+        };
+    }
+
+    private boolean isHomeActivityVisible() {
+        List<ActivityManager.RunningTaskInfo> tasks = mActivityManager.getRunningTasks(1);
+        if (tasks == null || tasks.isEmpty()) {
+            return false;
+        }
+
+        String top = tasks.get(0).topActivity.getPackageName();
+        if (top == null) {
+            return false;
+        }
+
+        // We can assume that the screen is idle if the home application is in the foreground.
+        final Intent intent = new Intent(Intent.ACTION_MAIN, null);
+        intent.addCategory(Intent.CATEGORY_HOME);
+
+        ResolveInfo info = mContext.getPackageManager().resolveActivity(intent,
+                PackageManager.MATCH_DEFAULT_ONLY);
+        if (info != null && top.equals(info.activityInfo.packageName)) {
+            return true;
+        }
+
+        return false;
+    }
+}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index e8494c4..7d04ca0 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -18,6 +18,7 @@
 
 import android.app.ActivityManager;
 import android.app.ApplicationErrorReport;
+import android.app.ApplicationExitInfo;
 import android.app.ContentProviderHolder;
 import android.app.GrantedUriPermission;
 import android.app.IApplicationThread;
@@ -188,6 +189,16 @@
      */
     @UnsupportedAppUsage
     boolean updateConfiguration(in Configuration values);
+    /**
+     * Updates mcc mnc configuration and applies changes to the entire system.
+     *
+     * @param mcc mcc configuration to update.
+     * @param mnc mnc configuration to update.
+     * @throws RemoteException; IllegalArgumentException if mcc or mnc is null.
+     * @return Returns {@code true} if the configuration was updated;
+     *         {@code false} otherwise.
+     */
+    boolean updateMccMncConfiguration(in String mcc, in String mnc);
     boolean stopServiceToken(in ComponentName className, in IBinder token, int startId);
     @UnsupportedAppUsage
     void setProcessLimit(int max);
@@ -345,6 +356,12 @@
             in Bundle options, int userId);
     @UnsupportedAppUsage
     int stopUser(int userid, boolean force, in IStopUserCallback callback);
+    /**
+     * Check {@link com.android.server.am.ActivityManagerService#stopUserWithDelayedLocking(int, boolean, IStopUserCallback)}
+     * for details.
+     */
+    int stopUserWithDelayedLocking(int userid, boolean force, in IStopUserCallback callback);
+
     @UnsupportedAppUsage
     void registerUserSwitchObserver(in IUserSwitchObserver observer, in String name);
     void unregisterUserSwitchObserver(in IUserSwitchObserver observer);
@@ -588,4 +605,36 @@
      * Method for the app to tell system that it's wedged and would like to trigger an ANR.
      */
     void appNotResponding(String reason);
+
+    /**
+     * Return a list of {@link ApplicationExitInfo} records.
+     *
+     * <p class="note"> Note: System stores these historical information in a ring buffer, older
+     * records would be overwritten by newer records. </p>
+     *
+     * <p class="note"> Note: In the case that this application bound to an external service with
+     * flag {@link android.content.Context#BIND_EXTERNAL_SERVICE}, the process of that external
+     * service will be included in this package's exit info. </p>
+     *
+     * @param packageName Optional, an empty value means match all packages belonging to the
+     *                    caller's UID. If this package belongs to another UID, you must hold
+     *                    {@link android.Manifest.permission#DUMP} in order to retrieve it.
+     * @param pid         Optional, it could be a process ID that used to belong to this package but
+     *                    died later; A value of 0 means to ignore this parameter and return all
+     *                    matching records.
+     * @param maxNum      Optional, the maximum number of results should be returned; A value of 0
+     *                    means to ignore this parameter and return all matching records
+     * @param userId      The userId in the multi-user environment.
+     *
+     * @return a list of {@link ApplicationExitInfo} records with the matching criteria, sorted in
+     *         the order from most recent to least recent.
+     */
+    ParceledListSlice<ApplicationExitInfo> getHistoricalProcessExitReasons(String packageName,
+            int pid, int maxNum, int userId);
+
+    /*
+     * Kill the given PIDs, but the killing will be delayed until the device is idle
+     * and the given process is imperceptible.
+     */
+    void killProcessesWhenImperceptible(in int[] pids, String reason);
 }
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 700b3c1..e5c046c 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -68,6 +68,7 @@
 import android.os.WorkSource;
 import android.service.voice.IVoiceInteractionSession;
 import android.view.IRecentsAnimationRunner;
+import android.view.ITaskOrganizer;
 import android.view.RemoteAnimationDefinition;
 import android.view.RemoteAnimationAdapter;
 import android.view.WindowContainerTransaction;
@@ -121,6 +122,9 @@
             in Intent intent, in String resolvedType, in IBinder resultTo, in String resultWho,
             int requestCode, int flags, in ProfilerInfo profilerInfo, in Bundle options,
             IBinder permissionToken, boolean ignoreTargetSecurity, int userId);
+
+    void registerTaskOrganizer(in ITaskOrganizer organizer, int windowingMode);
+
     boolean isActivityStartAllowedOnDisplay(int displayId, in Intent intent, in String resolvedType,
             int userId);
 
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 86f52af..d665f33 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -46,12 +46,10 @@
 
     void clearData(String pkg, int uid, boolean fromApp);
     // TODO: Replace parameter (ITransientNotification callback) with (CharSequence text)
-    void enqueueTextToast(String pkg, ITransientNotification callback, int duration, int displayId);
-    @UnsupportedAppUsage
-    void enqueueToast(String pkg, ITransientNotification callback, int duration, int displayId);
-    @UnsupportedAppUsage
-    void cancelToast(String pkg, ITransientNotification callback);
-    void finishToken(String pkg, ITransientNotification callback);
+    void enqueueTextToast(String pkg, IBinder token, ITransientNotification callback, int duration, int displayId);
+    void enqueueToast(String pkg, IBinder token, ITransientNotification callback, int duration, int displayId);
+    void cancelToast(String pkg, IBinder token);
+    void finishToken(String pkg, IBinder token);
 
     void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,
             in Notification notification, int userId);
@@ -94,8 +92,11 @@
     void updateNotificationChannelGroupForPackage(String pkg, int uid, in NotificationChannelGroup group);
     void updateNotificationChannelForPackage(String pkg, int uid, in NotificationChannel channel);
     NotificationChannel getNotificationChannel(String callingPkg, int userId, String pkg, String channelId);
+    NotificationChannel getConversationNotificationChannel(String callingPkg, int userId, String pkg, String channelId, boolean returnParentIfNoConversationChannel, String conversationId);
+    void createConversationNotificationChannelForPackage(String pkg, int uid, String triggeringKey, in NotificationChannel parentChannel, String conversationId);
     NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId, boolean includeDeleted);
     void deleteNotificationChannel(String pkg, String channelId);
+    void deleteConversationNotificationChannels(String pkg, int uid, String conversationId);
     ParceledListSlice getNotificationChannels(String callingPkg, String targetPkg, int userId);
     ParceledListSlice getNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted);
     int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted);
diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl
index 8c3180b..80ba464 100644
--- a/core/java/android/app/IUiAutomationConnection.aidl
+++ b/core/java/android/app/IUiAutomationConnection.aidl
@@ -39,7 +39,6 @@
     boolean injectInputEvent(in InputEvent event, boolean sync);
     void syncInputTransactions();
     boolean setRotation(int rotation);
-    Bitmap takeScreenshot(in Rect crop, int rotation);
     boolean clearWindowContentFrameStats(int windowId);
     WindowContentFrameStats getWindowContentFrameStats(int windowId);
     void clearWindowAnimationFrameStats();
diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl
index f5809ba..41e2ec9 100644
--- a/core/java/android/app/IUiModeManager.aidl
+++ b/core/java/android/app/IUiModeManager.aidl
@@ -70,7 +70,27 @@
     boolean isNightModeLocked();
 
     /**
-    * @hide
+    * [De]Activates night mode
     */
     boolean setNightModeActivated(boolean active);
+
+    /**
+    * Returns custom start clock time
+    */
+    long getCustomNightModeStart();
+
+    /**
+    * Sets custom start clock time
+    */
+    void setCustomNightModeStart(long time);
+
+    /**
+    * Returns custom end clock time
+    */
+    long getCustomNightModeEnd();
+
+    /**
+    * Sets custom end clock time
+    */
+    void setCustomNightModeEnd(long time);
 }
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 4cb8d93..34684c4 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -175,4 +175,11 @@
      * Called from SystemUI when it shows the AoD UI.
      */
     oneway void setInAmbientMode(boolean inAmbientMode, long animationDuration);
+
+    /**
+     * Called when the wallpaper needs to zoom out.
+     * The zoom value goes from 0 to 1 (inclusive) where 1 means fully zoomed out,
+     * 0 means fully zoomed in
+     */
+    oneway void setWallpaperZoomOut(float zoom, String callingPackage, int displayId);
 }
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 775b1d1..f0d0e98 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -251,7 +251,7 @@
     }
 
     private AppComponentFactory createAppFactory(ApplicationInfo appInfo, ClassLoader cl) {
-        if (appInfo.appComponentFactory != null && cl != null) {
+        if (mIncludeCode && appInfo.appComponentFactory != null && cl != null) {
             try {
                 return (AppComponentFactory)
                         cl.loadClass(appInfo.appComponentFactory).newInstance();
@@ -464,6 +464,9 @@
                     || appDir.equals(instrumentedAppDir)) {
                 outZipPaths.clear();
                 outZipPaths.add(instrumentationAppDir);
+                if (!instrumentationAppDir.equals(instrumentedAppDir)) {
+                    outZipPaths.add(instrumentedAppDir);
+                }
 
                 // Only add splits if the app did not request isolated split loading.
                 if (!aInfo.requestsIsolatedSplitLoading()) {
@@ -472,7 +475,6 @@
                     }
 
                     if (!instrumentationAppDir.equals(instrumentedAppDir)) {
-                        outZipPaths.add(instrumentedAppDir);
                         if (instrumentedSplitAppDirs != null) {
                             Collections.addAll(outZipPaths, instrumentedSplitAppDirs);
                         }
@@ -1178,13 +1180,26 @@
         }
 
         try {
-            java.lang.ClassLoader cl = getClassLoader();
+            final java.lang.ClassLoader cl = getClassLoader();
             if (!mPackageName.equals("android")) {
                 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                         "initializeJavaContextClassLoader");
                 initializeJavaContextClassLoader();
                 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
             }
+
+            // Rewrite the R 'constants' for all library apks.
+            SparseArray<String> packageIdentifiers = getAssets().getAssignedPackageIdentifiers(
+                    false, false);
+            for (int i = 0, n = packageIdentifiers.size(); i < n; i++) {
+                final int id = packageIdentifiers.keyAt(i);
+                if (id == 0x01 || id == 0x7f) {
+                    continue;
+                }
+
+                rewriteRValues(cl, packageIdentifiers.valueAt(i), id);
+            }
+
             ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
             app = mActivityThread.mInstrumentation.newApplication(
                     cl, appClass, appContext);
@@ -1213,19 +1228,6 @@
             }
         }
 
-        // Rewrite the R 'constants' for all library apks.
-        SparseArray<String> packageIdentifiers = getAssets().getAssignedPackageIdentifiers(
-                false, false);
-        final int N = packageIdentifiers.size();
-        for (int i = 0; i < N; i++) {
-            final int id = packageIdentifiers.keyAt(i);
-            if (id == 0x01 || id == 0x7f) {
-                continue;
-            }
-
-            rewriteRValues(getClassLoader(), packageIdentifiers.valueAt(i), id);
-        }
-
         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
 
         return app;
diff --git a/core/java/android/app/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java
index 4033aea..7cdf85e 100644
--- a/core/java/android/app/LocalActivityManager.java
+++ b/core/java/android/app/LocalActivityManager.java
@@ -177,7 +177,7 @@
                 pendingActions = null;
             }
 
-            mActivityThread.handleStartActivity(clientRecord, pendingActions);
+            mActivityThread.handleStartActivity(r, pendingActions);
             r.curState = STARTED;
             
             if (desiredState == RESUMED) {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 6f63eea..1af275f 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1004,6 +1004,31 @@
      */
     public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory";
 
+
+    /**
+     * {@link #extras} key: this is a remote input history which can include media messages
+     * in addition to text, as supplied to
+     * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} or
+     * {@link Builder#setRemoteInputHistory(CharSequence[])}.
+     *
+     * SystemUI can populate this through
+     * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} with the most recent inputs
+     * that have been sent through a {@link RemoteInput} of this Notification. These items can
+     * represent either media content (specified by a URI and a MIME type) or a text message
+     * (described by a CharSequence).
+     *
+     * To maintain compatibility, this can also be set by apps with
+     * {@link Builder#setRemoteInputHistory(CharSequence[])}, which will create a
+     * {@link RemoteInputHistoryItem} for each of the provided text-only messages.
+     *
+     * The extra with this key is of type {@link RemoteInputHistoryItem[]} and contains the most
+     * recent entry at the 0 index, the second most recent at the 1 index, etc.
+     *
+     * @see Builder#setRemoteInputHistory(RemoteInputHistoryItem[])
+     * @hide
+     */
+    public static final String EXTRA_REMOTE_INPUT_HISTORY_ITEMS = "android.remoteInputHistoryItems";
+
     /**
      * {@link #extras} key: boolean as supplied to
      * {@link Builder#setShowRemoteInputSpinner(boolean)}.
@@ -3833,12 +3858,37 @@
             if (text == null) {
                 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, null);
             } else {
-                final int N = Math.min(MAX_REPLY_HISTORY, text.length);
-                CharSequence[] safe = new CharSequence[N];
-                for (int i = 0; i < N; i++) {
+                final int itemCount = Math.min(MAX_REPLY_HISTORY, text.length);
+                CharSequence[] safe = new CharSequence[itemCount];
+                RemoteInputHistoryItem[] items = new RemoteInputHistoryItem[itemCount];
+                for (int i = 0; i < itemCount; i++) {
                     safe[i] = safeCharSequence(text[i]);
+                    items[i] = new RemoteInputHistoryItem(text[i]);
                 }
                 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, safe);
+
+                // Also add these messages as structured history items.
+                mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, items);
+            }
+            return this;
+        }
+
+        /**
+         * Set the remote input history, with support for embedding URIs and mime types for
+         * images and other media.
+         * @hide
+         */
+        @NonNull
+        public Builder setRemoteInputHistory(RemoteInputHistoryItem[] items) {
+            if (items == null) {
+                mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, null);
+            } else {
+                final int itemCount = Math.min(MAX_REPLY_HISTORY, items.length);
+                RemoteInputHistoryItem[] history = new RemoteInputHistoryItem[itemCount];
+                for (int i = 0; i < itemCount; i++) {
+                    history[i] = items[i];
+                }
+                mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, history);
             }
             return this;
         }
@@ -5246,16 +5296,17 @@
                 big.setViewVisibility(R.id.actions_container, View.GONE);
             }
 
-            CharSequence[] replyText = mN.extras.getCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY);
-            if (validRemoteInput && replyText != null
-                    && replyText.length > 0 && !TextUtils.isEmpty(replyText[0])
+            RemoteInputHistoryItem[] replyText = (RemoteInputHistoryItem[])
+                    mN.extras.getParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+            if (validRemoteInput && replyText != null && replyText.length > 0
+                    && !TextUtils.isEmpty(replyText[0].getText())
                     && p.maxRemoteInputHistory > 0) {
                 boolean showSpinner = mN.extras.getBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER);
                 big.setViewVisibility(R.id.notification_material_reply_container, View.VISIBLE);
                 big.setViewVisibility(R.id.notification_material_reply_text_1_container,
                         View.VISIBLE);
                 big.setTextViewText(R.id.notification_material_reply_text_1,
-                        processTextSpans(replyText[0]));
+                        processTextSpans(replyText[0].getText()));
                 setTextViewColorSecondary(big, R.id.notification_material_reply_text_1, p);
                 big.setViewVisibility(R.id.notification_material_reply_progress,
                         showSpinner ? View.VISIBLE : View.GONE);
@@ -5264,19 +5315,19 @@
                         ColorStateList.valueOf(
                                 isColorized(p) ? getPrimaryTextColor(p) : resolveContrastColor(p)));
 
-                if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1])
+                if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1].getText())
                         && p.maxRemoteInputHistory > 1) {
                     big.setViewVisibility(R.id.notification_material_reply_text_2, View.VISIBLE);
                     big.setTextViewText(R.id.notification_material_reply_text_2,
-                            processTextSpans(replyText[1]));
+                            processTextSpans(replyText[1].getText()));
                     setTextViewColorSecondary(big, R.id.notification_material_reply_text_2, p);
 
-                    if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2])
+                    if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2].getText())
                             && p.maxRemoteInputHistory > 2) {
                         big.setViewVisibility(
                                 R.id.notification_material_reply_text_3, View.VISIBLE);
                         big.setTextViewText(R.id.notification_material_reply_text_3,
-                                processTextSpans(replyText[2]));
+                                processTextSpans(replyText[2].getText()));
                         setTextViewColorSecondary(big, R.id.notification_material_reply_text_3, p);
                     }
                 }
@@ -7517,7 +7568,7 @@
             @Nullable
             private final Person mSender;
             /** True if this message was generated from the extra
-             *  {@link Notification#EXTRA_REMOTE_INPUT_HISTORY}
+             *  {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}
              */
             private final boolean mRemoteInputHistory;
 
@@ -7569,7 +7620,7 @@
              * Should be <code>null</code> for messages by the current user, in which case
              * the platform will insert the user set in {@code MessagingStyle(Person)}.
              * @param remoteInputHistory True if the messages was generated from the extra
-             * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY}.
+             * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}.
              * <p>
              * The person provided should contain an Icon, set with
              * {@link Person.Builder#setIcon(Icon)} and also have a name provided
@@ -7676,7 +7727,7 @@
 
             /**
              * @return True if the message was generated from
-             * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY}.
+             * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}.
              * @hide
              */
             public boolean isRemoteInputHistory() {
@@ -7906,8 +7957,8 @@
             if (mBuilder.mActions.size() > 0) {
                 maxRows--;
             }
-            CharSequence[] remoteInputHistory = mBuilder.mN.extras.getCharSequenceArray(
-                    EXTRA_REMOTE_INPUT_HISTORY);
+            RemoteInputHistoryItem[] remoteInputHistory = (RemoteInputHistoryItem[])
+                    mBuilder.mN.extras.getParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
             if (remoteInputHistory != null
                     && remoteInputHistory.length > NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION) {
                 // Let's remove some messages to make room for the remote input history.
@@ -8542,13 +8593,16 @@
      * Encapsulates the information needed to display a notification as a bubble.
      *
      * <p>A bubble is used to display app content in a floating window over the existing
-     * foreground activity. A bubble has a collapsed state represented by an icon,
-     * {@link BubbleMetadata.Builder#setIcon(Icon)} and an expanded state which is populated
-     * via {@link BubbleMetadata.Builder#setIntent(PendingIntent)}.</p>
+     * foreground activity. A bubble has a collapsed state represented by an icon and an
+     * expanded state that displays an activity. These may be defined via
+     * {@link BubbleMetadata.Builder#createIntentBubble(PendingIntent, Icon)} or they may
+     * be definied via an existing shortcut using
+     * {@link BubbleMetadata.Builder#createShortcutBubble(String)}.
+     * </p>
      *
      * <b>Notifications with a valid and allowed bubble will display in collapsed state
      * outside of the notification shade on unlocked devices. When a user interacts with the
-     * collapsed bubble, the bubble intent will be invoked and displayed.</b>
+     * collapsed bubble, the bubble activity will be invoked and displayed.</b>
      *
      * @see Notification.Builder#setBubbleMetadata(BubbleMetadata)
      */
@@ -8560,10 +8614,12 @@
         private int mDesiredHeight;
         @DimenRes private int mDesiredHeightResId;
         private int mFlags;
+        private String mShortcutId;
 
         /**
          * If set and the app creating the bubble is in the foreground, the bubble will be posted
-         * in its expanded state, with the contents of {@link #getIntent()} in a floating window.
+         * in its expanded state, with the contents of {@link #getBubbleIntent()} in a floating
+         * window.
          *
          * <p>This flag has no effect if the app posting the bubble is not in the foreground.
          * The app is considered foreground if it is visible and on the screen, note that
@@ -8595,34 +8651,61 @@
         public static final int FLAG_SUPPRESS_NOTIFICATION = 0x00000002;
 
         private BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent,
-                Icon icon, int height, @DimenRes int heightResId) {
+                Icon icon, int height, @DimenRes int heightResId, String shortcutId) {
             mPendingIntent = expandIntent;
             mIcon = icon;
             mDesiredHeight = height;
             mDesiredHeightResId = heightResId;
             mDeleteIntent = deleteIntent;
+            mShortcutId = shortcutId;
         }
 
         private BubbleMetadata(Parcel in) {
-            mPendingIntent = PendingIntent.CREATOR.createFromParcel(in);
-            mIcon = Icon.CREATOR.createFromParcel(in);
+            if (in.readInt() != 0) {
+                mPendingIntent = PendingIntent.CREATOR.createFromParcel(in);
+            }
+            if (in.readInt() != 0) {
+                mIcon = Icon.CREATOR.createFromParcel(in);
+            }
             mDesiredHeight = in.readInt();
             mFlags = in.readInt();
             if (in.readInt() != 0) {
                 mDeleteIntent = PendingIntent.CREATOR.createFromParcel(in);
             }
             mDesiredHeightResId = in.readInt();
+            if (in.readInt() != 0) {
+                mShortcutId = in.readString();
+            }
         }
 
         /**
-         * @return the pending intent used to populate the floating window for this bubble.
+         * @return the shortcut id used to populate the bubble, if it exists.
          */
+        @Nullable
+        public String getShortcutId() {
+            return mShortcutId;
+        }
+
+        /**
+         * @deprecated use {@link #getBubbleIntent()} or use {@link #getShortcutId()} if created
+         * with a valid shortcut instead.
+         */
+        @Deprecated
         @NonNull
         public PendingIntent getIntent() {
             return mPendingIntent;
         }
 
         /**
+         * @return the pending intent used to populate the floating window for this bubble, or
+         * null if this bubble is shortcut based.
+         */
+        @Nullable
+        public PendingIntent getBubbleIntent() {
+            return mPendingIntent;
+        }
+
+        /**
          * @return the pending intent to send when the bubble is dismissed by a user, if one exists.
          */
         @Nullable
@@ -8631,17 +8714,28 @@
         }
 
         /**
-         * @return the icon that will be displayed for this bubble when it is collapsed.
+         * @deprecated use {@link #getBubbleIcon()} or use {@link #getShortcutId()} if created
+         * with a valid shortcut instead.
          */
+        @Deprecated
         @NonNull
         public Icon getIcon() {
             return mIcon;
         }
 
         /**
+         * @return the icon that will be displayed for this bubble when it is collapsed, or null
+         * if the bubble is shortcut based.
+         */
+        @Nullable
+        public Icon getBubbleIcon() {
+            return mIcon;
+        }
+
+        /**
          * @return the ideal height, in DPs, for the floating window that app content defined by
-         * {@link #getIntent()} for this bubble. A value of 0 indicates a desired height has not
-         * been set.
+         * {@link #getBubbleIntent()} for this bubble. A value of 0 indicates a desired height has
+         * not been set.
          */
         @Dimension(unit = DP)
         public int getDesiredHeight() {
@@ -8650,7 +8744,7 @@
 
         /**
          * @return the resId of ideal height for the floating window that app content defined by
-         * {@link #getIntent()} for this bubble. A value of 0 indicates a res value has not
+         * {@link #getBubbleIntent()} for this bubble. A value of 0 indicates a res value has not
          * been provided for the desired height.
          */
         @DimenRes
@@ -8697,8 +8791,14 @@
 
         @Override
         public void writeToParcel(Parcel out, int flags) {
-            mPendingIntent.writeToParcel(out, 0);
-            mIcon.writeToParcel(out, 0);
+            out.writeInt(mPendingIntent != null ? 1 : 0);
+            if (mPendingIntent != null) {
+                mPendingIntent.writeToParcel(out, 0);
+            }
+            out.writeInt(mIcon != null ? 1 : 0);
+            if (mIcon != null) {
+                mIcon.writeToParcel(out, 0);
+            }
             out.writeInt(mDesiredHeight);
             out.writeInt(mFlags);
             out.writeInt(mDeleteIntent != null ? 1 : 0);
@@ -8706,6 +8806,10 @@
                 mDeleteIntent.writeToParcel(out, 0);
             }
             out.writeInt(mDesiredHeightResId);
+            out.writeInt(TextUtils.isEmpty(mShortcutId) ? 0 : 1);
+            if (!TextUtils.isEmpty(mShortcutId)) {
+                out.writeString(mShortcutId);
+            }
         }
 
         /**
@@ -8733,6 +8837,7 @@
             @DimenRes private int mDesiredHeightResId;
             private int mFlags;
             private PendingIntent mDeleteIntent;
+            private String mShortcutId;
 
             /**
              * Constructs a new builder object.
@@ -8741,50 +8846,98 @@
             }
 
             /**
-             * Sets the intent that will be used when the bubble is expanded. This will display the
-             * app content in a floating window over the existing foreground activity.
+             * Creates a {@link BubbleMetadata.Builder} based on a shortcut. Only
+             * {@link android.content.pm.ShortcutManager#addDynamicShortcuts(List)} shortcuts are
+             * supported.
              *
-             * <p>An intent is required.</p>
+             * <p>The shortcut icon will be used to represent the bubble when it is collapsed.</p>
              *
-             * @throws IllegalArgumentException if intent is null
+             * <p>The shortcut activity will be used when the bubble is expanded. This will display
+             * the shortcut activity in a floating window over the existing foreground activity.</p>
+             *
+             * <p>If the shortcut has not been published when the bubble notification is sent,
+             * no bubble will be produced. If the shortcut is deleted while the bubble is active,
+             * the bubble will be removed.</p>
+             *
+             * <p>Calling this method will clear the contents of
+             * {@link #createIntentBubble(PendingIntent, Icon)} if it was previously called on
+             * this builder.</p>
              */
             @NonNull
-            public BubbleMetadata.Builder setIntent(@NonNull PendingIntent intent) {
-                if (intent == null) {
-                    throw new IllegalArgumentException("Bubble requires non-null pending intent");
+            public BubbleMetadata.Builder createShortcutBubble(@NonNull String shortcutId) {
+                if (!TextUtils.isEmpty(shortcutId)) {
+                    // If shortcut id is set, we don't use these if they were previously set.
+                    mPendingIntent = null;
+                    mIcon = null;
                 }
-                mPendingIntent = intent;
+                mShortcutId = shortcutId;
                 return this;
             }
 
             /**
-             * Sets the icon that will represent the bubble when it is collapsed.
+             * Creates a {@link BubbleMetadata.Builder} based on the provided intent and icon.
              *
-             * <p>An icon is required and should be representative of the content within the bubble.
-             * If your app produces multiple bubbles, the image should be unique for each of them.
+             * <p>The icon will be used to represent the bubble when it is collapsed. An icon
+             * should be representative of the content within the bubble. If your app produces
+             * multiple bubbles, the icon should be unique for each of them.</p>
+             *
+             * <p>The intent that will be used when the bubble is expanded. This will display the
+             * app content in a floating window over the existing foreground activity.</p>
+             *
+             * <p>Calling this method will clear the contents of
+             * {@link #createShortcutBubble(String)} if it was previously called on this builder.
              * </p>
              *
-             * <p>The shape of a bubble icon is adaptive and will match the device theme.
-             *
-             * Ideally your icon should be constructed via
-             * {@link Icon#createWithAdaptiveBitmap(Bitmap)}, otherwise, the icon will be shrunk
-             * and placed on an adaptive shape.
-             * </p>
-             *
+             * @throws IllegalArgumentException if intent is null.
              * @throws IllegalArgumentException if icon is null.
              */
             @NonNull
-            public BubbleMetadata.Builder setIcon(@NonNull Icon icon) {
+            public BubbleMetadata.Builder createIntentBubble(@NonNull PendingIntent intent,
+                    @NonNull Icon icon) {
+                if (intent == null) {
+                    throw new IllegalArgumentException("Bubble requires non-null pending intent");
+                }
                 if (icon == null) {
                     throw new IllegalArgumentException("Bubbles require non-null icon");
                 }
+                mShortcutId = null;
+                mPendingIntent = intent;
                 mIcon = icon;
                 return this;
             }
 
             /**
-             * Sets the desired height in DPs for the app content defined by
-             * {@link #setIntent(PendingIntent)}.
+             * @deprecated use {@link #createIntentBubble(PendingIntent, Icon)}
+             * or {@link #createShortcutBubble(String)} instead.
+             */
+            @Deprecated
+            @NonNull
+            public BubbleMetadata.Builder setIntent(@NonNull PendingIntent intent) {
+                if (intent == null) {
+                    throw new IllegalArgumentException("Bubble requires non-null pending intent");
+                }
+                mShortcutId = null;
+                mPendingIntent = intent;
+                return this;
+            }
+
+            /**
+             * @deprecated use {@link #createIntentBubble(PendingIntent, Icon)}
+             * or {@link #createShortcutBubble(String)} instead.
+             */
+            @Deprecated
+            @NonNull
+            public BubbleMetadata.Builder setIcon(@NonNull Icon icon) {
+                if (icon == null) {
+                    throw new IllegalArgumentException("Bubbles require non-null icon");
+                }
+                mShortcutId = null;
+                mIcon = icon;
+                return this;
+            }
+
+            /**
+             * Sets the desired height in DPs for the expanded content of the bubble.
              *
              * <p>This height may not be respected if there is not enough space on the screen or if
              * the provided height is too small to be useful.</p>
@@ -8806,8 +8959,7 @@
 
 
             /**
-             * Sets the desired height via resId for the app content defined by
-             * {@link #setIntent(PendingIntent)}.
+             * Sets the desired height via resId for the expanded content of the bubble.
              *
              * <p>This height may not be respected if there is not enough space on the screen or if
              * the provided height is too small to be useful.</p>
@@ -8829,7 +8981,7 @@
 
             /**
              * Sets whether the bubble will be posted in its expanded state (with the contents of
-             * {@link #getIntent()} in a floating window).
+             * {@link #getBubbleIntent()} in a floating window).
              *
              * <p>This flag has no effect if the app posting the bubble is not in the foreground.
              * The app is considered foreground if it is visible and on the screen, note that
@@ -8882,20 +9034,21 @@
             /**
              * Creates the {@link BubbleMetadata} defined by this builder.
              *
-             * @throws IllegalStateException if {@link #setIntent(PendingIntent)} and/or
-             *                               {@link #setIcon(Icon)} have not been called on this
-             *                               builder.
+             * @throws IllegalStateException if neither {@link #createShortcutBubble(String)} or
+             * {@link #createIntentBubble(PendingIntent, Icon)} have been called on this builder.
              */
             @NonNull
             public BubbleMetadata build() {
-                if (mPendingIntent == null) {
-                    throw new IllegalStateException("Must supply pending intent to bubble");
+                if (mShortcutId == null && mPendingIntent == null) {
+                    throw new IllegalStateException(
+                            "Must supply pending intent or shortcut to bubble");
                 }
-                if (mIcon == null) {
-                    throw new IllegalStateException("Must supply an icon for the bubble");
+                if (mShortcutId == null && mIcon == null) {
+                    throw new IllegalStateException(
+                            "Must supply an icon or shortcut for the bubble");
                 }
                 BubbleMetadata data = new BubbleMetadata(mPendingIntent, mDeleteIntent,
-                        mIcon, mDesiredHeight, mDesiredHeightResId);
+                        mIcon, mDesiredHeight, mDesiredHeightResId, mShortcutId);
                 data.setFlags(mFlags);
                 return data;
             }
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 3eee1ae..5a4622e 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -15,6 +15,8 @@
  */
 package android.app;
 
+import static android.annotation.SystemApi.Client.MODULE_APPS;
+
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
@@ -23,6 +25,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ShortcutInfo;
 import android.media.AudioAttributes;
 import android.net.Uri;
 import android.os.Parcel;
@@ -30,6 +33,7 @@
 import android.provider.Settings;
 import android.service.notification.NotificationListenerService;
 import android.text.TextUtils;
+import android.util.Log;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.Preconditions;
@@ -57,6 +61,22 @@
     public static final String DEFAULT_CHANNEL_ID = "miscellaneous";
 
     /**
+     * The formatter used by the system to create an id for notification
+     * channels when it automatically creates conversation channels on behalf of an app. The format
+     * string takes two arguments, in this order: the
+     * {@link #getId()} of the original notification channel, and the
+     * {@link ShortcutInfo#getId() id} of the conversation.
+     */
+    public static final String CONVERSATION_CHANNEL_ID_FORMAT = "%1$s : %2$s";
+
+    /**
+     * TODO: STOPSHIP  remove
+     * Conversation id to use for apps that aren't providing them yet.
+     * @hide
+     */
+    public static final String PLACEHOLDER_CONVERSATION_ID = ":placeholder_id";
+
+    /**
      * The maximum length for text fields in a NotificationChannel. Fields will be truncated at this
      * limit.
      */
@@ -85,6 +105,9 @@
     private static final String ATT_BLOCKABLE_SYSTEM = "blockable_system";
     private static final String ATT_ALLOW_BUBBLE = "can_bubble";
     private static final String ATT_ORIG_IMP = "orig_imp";
+    private static final String ATT_PARENT_CHANNEL = "parent";
+    private static final String ATT_CONVERSATION_ID = "conv_id";
+    private static final String ATT_DEMOTE = "dem";
     private static final String DELIMITER = ",";
 
     /**
@@ -147,7 +170,7 @@
     private static final boolean DEFAULT_ALLOW_BUBBLE = true;
 
     @UnsupportedAppUsage
-    private final String mId;
+    private String mId;
     private String mName;
     private String mDesc;
     private int mImportance = DEFAULT_IMPORTANCE;
@@ -172,6 +195,9 @@
     private boolean mAllowBubbles = DEFAULT_ALLOW_BUBBLE;
     private boolean mImportanceLockedByOEM;
     private boolean mImportanceLockedDefaultApp;
+    private String mParentId = null;
+    private String mConversationId = null;
+    private boolean mDemoted = false;
 
     /**
      * Creates a notification channel.
@@ -236,6 +262,9 @@
         mAllowBubbles = in.readBoolean();
         mImportanceLockedByOEM = in.readBoolean();
         mOriginalImportance = in.readInt();
+        mParentId = in.readString();
+        mConversationId = in.readString();
+        mDemoted = in.readBoolean();
     }
 
     @Override
@@ -291,6 +320,9 @@
         dest.writeBoolean(mAllowBubbles);
         dest.writeBoolean(mImportanceLockedByOEM);
         dest.writeInt(mOriginalImportance);
+        dest.writeString(mParentId);
+        dest.writeString(mConversationId);
+        dest.writeBoolean(mDemoted);
     }
 
     /**
@@ -324,9 +356,13 @@
     }
 
     /**
+     * Allows users to block notifications sent through this channel, if this channel belongs to
+     * a package that is signed with the system signature. If the channel does not belong to a
+     * package that is signed with the system signature, this method does nothing.
+     * @param blockableSystem if {@code true}, allows users to block notifications on this channel.
      * @hide
      */
-    @UnsupportedAppUsage
+    @SystemApi(client = MODULE_APPS)
     @TestApi
     public void setBlockableSystem(boolean blockableSystem) {
         mBlockableSystem = blockableSystem;
@@ -360,6 +396,13 @@
         return input;
     }
 
+    /**
+     * @hide
+     */
+    public void setId(String id) {
+        mId = id;
+    }
+
     // Modifiable by apps on channel creation.
 
     /**
@@ -502,6 +545,23 @@
     }
 
     /**
+     * Sets this channel as being person-centric. Different settings and functionality may be
+     * exposed for people-centric channels.
+     *
+     * @param parentChannelId The {@link #getId()} id} of the generic channel that notifications of
+     *                        this type would be posted to in absence of a specific conversation id.
+     *                        For example, if this channel represents 'Messages from Person A', the
+     *                        parent channel would be 'Messages.'
+     * @param conversationId The {@link ShortcutInfo#getId()} of the shortcut representing this
+     *                       channel's conversation.
+     */
+    public void setConversationId(@Nullable String parentChannelId,
+            @Nullable String conversationId) {
+        mParentId = parentChannelId;
+        mConversationId = conversationId;
+    }
+
+    /**
      * Returns the id of this channel.
      */
     public String getId() {
@@ -622,6 +682,22 @@
     }
 
     /**
+     * Returns the {@link #getId() id} of the parent notification channel to this channel, if it's
+     * a conversation related channel. See {@link #setConversationId(String, String)}.
+     */
+    public @Nullable String getParentChannelId() {
+        return mParentId;
+    }
+
+    /**
+     * Returns the {@link ShortcutInfo#getId() id} of the conversation backing this channel, if it's
+     * associated with a conversation. See {@link #setConversationId(String, String)}.
+     */
+    public @Nullable String getConversationId() {
+        return mConversationId;
+    }
+
+    /**
      * @hide
      */
     @SystemApi
@@ -701,6 +777,20 @@
     }
 
     /**
+     * @hide
+     */
+    public void setDemoted(boolean demoted) {
+        mDemoted = demoted;
+    }
+
+    /**
+     * @hide
+     */
+    public boolean isDemoted() {
+        return mDemoted;
+    }
+
+    /**
      * Returns whether the user has chosen the importance of this channel, either to affirm the
      * initial selection from the app, or changed it to be higher or lower.
      * @see #getImportance()
@@ -761,6 +851,9 @@
         setBlockableSystem(safeBool(parser, ATT_BLOCKABLE_SYSTEM, false));
         setAllowBubbles(safeBool(parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE));
         setOriginalImportance(safeInt(parser, ATT_ORIG_IMP, DEFAULT_IMPORTANCE));
+        setConversationId(parser.getAttributeValue(null, ATT_PARENT_CHANNEL),
+                parser.getAttributeValue(null, ATT_CONVERSATION_ID));
+        setDemoted(safeBool(parser, ATT_DEMOTE, false));
     }
 
     @Nullable
@@ -885,6 +978,15 @@
         if (getOriginalImportance() != DEFAULT_IMPORTANCE) {
             out.attribute(null, ATT_ORIG_IMP, Integer.toString(getOriginalImportance()));
         }
+        if (getParentChannelId() != null) {
+            out.attribute(null, ATT_PARENT_CHANNEL, getParentChannelId());
+        }
+        if (getConversationId() != null) {
+            out.attribute(null, ATT_CONVERSATION_ID, getConversationId());
+        }
+        if (isDemoted()) {
+            out.attribute(null, ATT_DEMOTE, Boolean.toString(isDemoted()));
+        }
 
         // mImportanceLockedDefaultApp and mImportanceLockedByOEM have a different source of
         // truth and so aren't written to this xml file
@@ -1042,7 +1144,10 @@
                 && Objects.equals(getAudioAttributes(), that.getAudioAttributes())
                 && mImportanceLockedByOEM == that.mImportanceLockedByOEM
                 && mImportanceLockedDefaultApp == that.mImportanceLockedDefaultApp
-                && mOriginalImportance == that.mOriginalImportance;
+                && mOriginalImportance == that.mOriginalImportance
+                && Objects.equals(getParentChannelId(), that.getParentChannelId())
+                && Objects.equals(getConversationId(), that.getConversationId())
+                && isDemoted() == that.isDemoted();
     }
 
     @Override
@@ -1052,7 +1157,8 @@
                 getUserLockedFields(),
                 isFgServiceShown(), mVibrationEnabled, mShowBadge, isDeleted(), getGroup(),
                 getAudioAttributes(), isBlockableSystem(), mAllowBubbles,
-                mImportanceLockedByOEM, mImportanceLockedDefaultApp, mOriginalImportance);
+                mImportanceLockedByOEM, mImportanceLockedDefaultApp, mOriginalImportance,
+                mParentId, mConversationId, mDemoted);
         result = 31 * result + Arrays.hashCode(mVibration);
         return result;
     }
@@ -1063,26 +1169,7 @@
         String output = "NotificationChannel{"
                 + "mId='" + mId + '\''
                 + ", mName=" + redactedName
-                + ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "")
-                + ", mImportance=" + mImportance
-                + ", mBypassDnd=" + mBypassDnd
-                + ", mLockscreenVisibility=" + mLockscreenVisibility
-                + ", mSound=" + mSound
-                + ", mLights=" + mLights
-                + ", mLightColor=" + mLightColor
-                + ", mVibration=" + Arrays.toString(mVibration)
-                + ", mUserLockedFields=" + Integer.toHexString(mUserLockedFields)
-                + ", mFgServiceShown=" + mFgServiceShown
-                + ", mVibrationEnabled=" + mVibrationEnabled
-                + ", mShowBadge=" + mShowBadge
-                + ", mDeleted=" + mDeleted
-                + ", mGroup='" + mGroup + '\''
-                + ", mAudioAttributes=" + mAudioAttributes
-                + ", mBlockableSystem=" + mBlockableSystem
-                + ", mAllowBubbles=" + mAllowBubbles
-                + ", mImportanceLockedByOEM=" + mImportanceLockedByOEM
-                + ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp
-                + ", mOriginalImp=" + mOriginalImportance
+                + getFieldsString()
                 + '}';
         pw.println(prefix + output);
     }
@@ -1092,7 +1179,12 @@
         return "NotificationChannel{"
                 + "mId='" + mId + '\''
                 + ", mName=" + mName
-                + ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "")
+                + getFieldsString()
+                + '}';
+    }
+
+    private String getFieldsString() {
+        return  ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "")
                 + ", mImportance=" + mImportance
                 + ", mBypassDnd=" + mBypassDnd
                 + ", mLockscreenVisibility=" + mLockscreenVisibility
@@ -1112,7 +1204,9 @@
                 + ", mImportanceLockedByOEM=" + mImportanceLockedByOEM
                 + ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp
                 + ", mOriginalImp=" + mOriginalImportance
-                + '}';
+                + ", mParent=" + mParentId
+                + ", mConversationId=" + mConversationId
+                + ", mDemoted=" + mDemoted;
     }
 
     /** @hide */
diff --git a/core/java/android/app/NotificationHistory.java b/core/java/android/app/NotificationHistory.java
index 8ba39a8..909a476 100644
--- a/core/java/android/app/NotificationHistory.java
+++ b/core/java/android/app/NotificationHistory.java
@@ -24,6 +24,8 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
@@ -311,11 +313,23 @@
         mHistoryCount++;
     }
 
+    /**
+     * Used when populating a history from disk; adds an historical notification.
+     */
+    public void addNewNotificationToWrite(@NonNull HistoricalNotification notification) {
+        if (notification == null) {
+            return;
+        }
+        mNotificationsToWrite.add(0, notification);
+        mHistoryCount++;
+    }
+
     public void addNotificationsToWrite(@NonNull NotificationHistory notificationHistory) {
         for (HistoricalNotification hn : notificationHistory.getNotificationsToWrite()) {
-            // TODO: consider merging by date
             addNotificationToWrite(hn);
         }
+        Collections.sort(mNotificationsToWrite,
+                (o1, o2) -> -1 * Long.compare(o1.getPostedTimeMs(), o2.getPostedTimeMs()));
         poolStringsFromNotifications();
     }
 
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index fdbb8bb..1a8e15c 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -843,6 +843,26 @@
     }
 
     /**
+     * Returns the notification channel settings for a given channel and conversation id.
+     *
+     * <p>The channel must belong to your package, or to a package you are an approved notification
+     * delegate for (see {@link #canNotifyAsPackage(String)}), or it will not be returned. To query
+     * a channel as a notification delegate, call this method from a context created for that
+     * package (see {@link Context#createPackageContext(String, int)}).</p>
+     */
+    public @Nullable NotificationChannel getNotificationChannel(@NonNull String channelId,
+            @NonNull String conversationId) {
+        INotificationManager service = getService();
+        try {
+            return service.getConversationNotificationChannel(mContext.getOpPackageName(),
+                    mContext.getUserId(), mContext.getPackageName(), channelId, true,
+                    conversationId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns all notification channels belonging to the calling package.
      *
      * <p>Approved notification delegates (see {@link #canNotifyAsPackage(String)}) can query
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index 844e72e..736efb6 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -15,6 +15,7 @@
  */
 
 package android.app;
+
 import android.annotation.NonNull;
 import android.os.SystemProperties;
 import android.util.Log;
@@ -23,6 +24,7 @@
 
 import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Random;
 import java.util.concurrent.atomic.AtomicLong;
 
@@ -164,6 +166,7 @@
     private static final String TAG = "PropertyInvalidatedCache";
     private static final boolean DEBUG = false;
     private static final boolean ENABLE = true;
+    private static final boolean VERIFY = false;
 
     private final Object mLock = new Object();
 
@@ -228,6 +231,18 @@
     protected abstract Result recompute(Query query);
 
     /**
+     * Determines if a pair of responses are considered equal. Used to determine whether
+     * a cache is inadvertently returning stale results when VERIFY is set to true.
+     */
+    protected boolean debugCompareQueryResults(Result cachedResult, Result fetchedResult) {
+        // If a service crashes and returns a null result, the cached value remains valid.
+        if (fetchedResult != null) {
+            return Objects.equals(cachedResult, fetchedResult);
+        }
+        return true;
+    }
+
+    /**
      * Make result up-to-date on a cache hit.  Called unlocked;
      * may block.
      *
@@ -334,12 +349,12 @@
                             mCache.put(query, refreshedResult);
                         }
                     }
-                    return refreshedResult;
+                    return maybeCheckConsistency(query, refreshedResult);
                 }
                 if (DEBUG) {
                     Log.d(TAG, "cache hit for " + query);
                 }
-                return cachedResult;
+                return maybeCheckConsistency(query, cachedResult);
             }
             // Cache miss: make the value from scratch.
             if (DEBUG) {
@@ -353,7 +368,7 @@
                     mCache.put(query, result);
                 }
             }
-            return result;
+            return maybeCheckConsistency(query, result);
         }
     }
 
@@ -425,4 +440,15 @@
         }
         SystemProperties.set(name, newValueString);
     }
+
+    private Result maybeCheckConsistency(Query query, Result proposedResult) {
+        if (VERIFY) {
+            Result resultToCompare = recompute(query);
+            boolean nonceChanged = (getCurrentNonce() != mLastSeenNonce);
+            if (!nonceChanged && !debugCompareQueryResults(proposedResult, resultToCompare)) {
+                throw new AssertionError("cache returned out of date response for " + query);
+            }
+        }
+        return proposedResult;
+    }
 }
diff --git a/core/java/android/app/RemoteInputHistoryItem.java b/core/java/android/app/RemoteInputHistoryItem.java
new file mode 100644
index 0000000..091db3f
--- /dev/null
+++ b/core/java/android/app/RemoteInputHistoryItem.java
@@ -0,0 +1,90 @@
+/*
+ * 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.app;
+
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Stores historical input from a RemoteInput attached to a Notification.
+ *
+ * History items represent either a text message (specified by providing a CharSequence,
+ * or a media message (specified by providing a URI and a MIME type). Media messages must also
+ * include text to insert when the image cannot be loaded, ex. when URI read permission has not been
+ * granted correctly.
+ *
+ * @hide
+ */
+public class RemoteInputHistoryItem implements Parcelable {
+    private CharSequence mText;
+    private String mMimeType;
+    private Uri mUri;
+
+    public RemoteInputHistoryItem(String mimeType, Uri uri, CharSequence backupText) {
+        this.mMimeType = mimeType;
+        this.mUri = uri;
+        this.mText = Notification.safeCharSequence(backupText);
+    }
+
+    public RemoteInputHistoryItem(CharSequence text) {
+        this.mText = Notification.safeCharSequence(text);
+    }
+
+    protected RemoteInputHistoryItem(Parcel in) {
+        mText = in.readCharSequence();
+        mMimeType = in.readStringNoHelper();
+        mUri = in.readParcelable(Uri.class.getClassLoader());
+    }
+
+    public static final Creator<RemoteInputHistoryItem> CREATOR =
+            new Creator<RemoteInputHistoryItem>() {
+                @Override
+                public RemoteInputHistoryItem createFromParcel(Parcel in) {
+                    return new RemoteInputHistoryItem(in);
+                }
+
+                @Override
+                public RemoteInputHistoryItem[] newArray(int size) {
+                    return new RemoteInputHistoryItem[size];
+                }
+            };
+
+    public CharSequence getText() {
+        return mText;
+    }
+
+    public String getMimeType() {
+        return mMimeType;
+    }
+
+    public Uri getUri() {
+        return mUri;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeCharSequence(mText);
+        dest.writeStringNoHelper(mMimeType);
+        dest.writeParcelable(mUri, flags);
+    }
+}
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index aa11598..7ab85a4 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -482,19 +482,6 @@
             }
         }
 
-        if (key.mOverlayDirs != null) {
-            for (final String idmapPath : key.mOverlayDirs) {
-                try {
-                    builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/,
-                            true /*overlay*/));
-                } catch (IOException e) {
-                    Log.w(TAG, "failed to add overlay path " + idmapPath);
-
-                    // continue.
-                }
-            }
-        }
-
         if (key.mLibDirs != null) {
             for (final String libDir : key.mLibDirs) {
                 if (libDir.endsWith(".apk")) {
@@ -513,6 +500,19 @@
             }
         }
 
+        if (key.mOverlayDirs != null) {
+            for (final String idmapPath : key.mOverlayDirs) {
+                try {
+                    builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/,
+                            true /*overlay*/));
+                } catch (IOException e) {
+                    Log.w(TAG, "failed to add overlay path " + idmapPath);
+
+                    // continue.
+                }
+            }
+        }
+
         return builder.build();
     }
 
@@ -1208,8 +1208,7 @@
                 WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
                 ResourcesImpl r = weakImplRef != null ? weakImplRef.get() : null;
                 if (r != null) {
-                    applyConfigurationToResourcesLocked(config, compat, tmpConfig,
-                            defaultDisplayMetrics, key, r);
+                    applyConfigurationToResourcesLocked(config, compat, tmpConfig, key, r);
                 } else {
                     mResourceImpls.removeAt(i);
                 }
@@ -1224,8 +1223,7 @@
                 }
 
                 applyConfigurationToResourcesLocked(config, compat, tmpConfig,
-                        defaultDisplayMetrics, resourcesWithLoaders.resourcesKey(),
-                        resources.getImpl());
+                        resourcesWithLoaders.resourcesKey(), resources.getImpl());
             }
 
             return changes != 0;
@@ -1236,40 +1234,33 @@
 
     private void applyConfigurationToResourcesLocked(@NonNull Configuration config,
             @Nullable CompatibilityInfo compat, Configuration tmpConfig,
-            DisplayMetrics defaultDisplayMetrics, ResourcesKey key, ResourcesImpl resourcesImpl) {
+            ResourcesKey key, ResourcesImpl resourcesImpl) {
         if (DEBUG || DEBUG_CONFIGURATION) {
             Slog.v(TAG, "Changing resources "
                     + resourcesImpl + " config to: " + config);
         }
         int displayId = key.mDisplayId;
-        boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
-        DisplayMetrics dm = defaultDisplayMetrics;
         final boolean hasOverrideConfiguration = key.hasOverrideConfiguration();
-        if (!isDefaultDisplay || hasOverrideConfiguration) {
-            tmpConfig.setTo(config);
+        tmpConfig.setTo(config);
 
-            // Get new DisplayMetrics based on the DisplayAdjustments given
-            // to the ResourcesImpl. Update a copy if the CompatibilityInfo
-            // changed, because the ResourcesImpl object will handle the
-            // update internally.
-            DisplayAdjustments daj = resourcesImpl.getDisplayAdjustments();
-            if (compat != null) {
-                daj = new DisplayAdjustments(daj);
-                daj.setCompatibilityInfo(compat);
-            }
-            dm = getDisplayMetrics(displayId, daj);
-
-            if (!isDefaultDisplay) {
-                applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig);
-            }
-
-            if (hasOverrideConfiguration) {
-                tmpConfig.updateFrom(key.mOverrideConfiguration);
-            }
-            resourcesImpl.updateConfiguration(tmpConfig, dm, compat);
-        } else {
-            resourcesImpl.updateConfiguration(config, dm, compat);
+        // Get new DisplayMetrics based on the DisplayAdjustments given to the ResourcesImpl. Update
+        // a copy if the CompatibilityInfo changed, because the ResourcesImpl object will handle the
+        // update internally.
+        DisplayAdjustments daj = resourcesImpl.getDisplayAdjustments();
+        if (compat != null) {
+            daj = new DisplayAdjustments(daj);
+            daj.setCompatibilityInfo(compat);
         }
+        daj.setConfiguration(config);
+        DisplayMetrics dm = getDisplayMetrics(displayId, daj);
+        if (displayId != Display.DEFAULT_DISPLAY) {
+            applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig);
+        }
+
+        if (hasOverrideConfiguration) {
+            tmpConfig.updateFrom(key.mOverrideConfiguration);
+        }
+        resourcesImpl.updateConfiguration(tmpConfig, dm, compat);
     }
 
     /**
diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java
index abb0cfc..3df1648 100644
--- a/core/java/android/app/SharedPreferencesImpl.java
+++ b/core/java/android/app/SharedPreferencesImpl.java
@@ -66,8 +66,9 @@
 
     /**
      * There will now be a callback to {@link
-     * OnSharedPreferenceChangeListener#onSharedPreferenceChanged(SharedPreferences, String)} with
-     * a {@code null} key on {@link Editor#clear()}.
+     * android.content.SharedPreferences.OnSharedPreferenceChangeListener#onSharedPreferenceChanged
+     * OnSharedPreferenceChangeListener.onSharedPreferenceChanged} with a {@code null} key on
+     * {@link android.content.SharedPreferences.Editor#clear Editor.clear}.
      */
     @ChangeId
     @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
diff --git a/core/java/android/app/StatsManager.java b/core/java/android/app/StatsManager.java
index 4e50a3f..0ea05d8 100644
--- a/core/java/android/app/StatsManager.java
+++ b/core/java/android/app/StatsManager.java
@@ -18,17 +18,16 @@
 import static android.Manifest.permission.DUMP;
 import static android.Manifest.permission.PACKAGE_USAGE_STATS;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.content.Context;
-import android.os.IBinder;
+import android.os.Binder;
 import android.os.IPullAtomCallback;
 import android.os.IPullAtomResultReceiver;
-import android.os.IStatsCompanionService;
 import android.os.IStatsManagerService;
-import android.os.IStatsPullerCallback;
 import android.os.IStatsd;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -61,9 +60,6 @@
     private IStatsd mService;
 
     @GuardedBy("sLock")
-    private IStatsCompanionService mStatsCompanion;
-
-    @GuardedBy("sLock")
     private IStatsManagerService mStatsManagerService;
 
     /**
@@ -112,13 +108,11 @@
     /**
      * Value indicating that this pull was successful and that the result should be used.
      *
-     * @hide
      **/
     public static final int PULL_SUCCESS = 0;
 
     /**
      * Value indicating that this pull was unsuccessful and that the result should not be used.
-     * @hide
      **/
     public static final int PULL_SKIP = 1;
 
@@ -157,11 +151,11 @@
     public void addConfig(long configKey, byte[] config) throws StatsUnavailableException {
         synchronized (sLock) {
             try {
-                IStatsd service = getIStatsdLocked();
+                IStatsManagerService service = getIStatsManagerServiceLocked();
                 // can throw IllegalArgumentException
                 service.addConfiguration(configKey, config, mContext.getOpPackageName());
             } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to connect to statsd when adding configuration");
+                Slog.e(TAG, "Failed to connect to statsmanager when adding configuration");
                 throw new StatsUnavailableException("could not connect", e);
             } catch (SecurityException e) {
                 throw new StatsUnavailableException(e.getMessage(), e);
@@ -194,10 +188,10 @@
     public void removeConfig(long configKey) throws StatsUnavailableException {
         synchronized (sLock) {
             try {
-                IStatsd service = getIStatsdLocked();
+                IStatsManagerService service = getIStatsManagerServiceLocked();
                 service.removeConfiguration(configKey, mContext.getOpPackageName());
             } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to connect to statsd when removing configuration");
+                Slog.e(TAG, "Failed to connect to statsmanager when removing configuration");
                 throw new StatsUnavailableException("could not connect", e);
             } catch (SecurityException e) {
                 throw new StatsUnavailableException(e.getMessage(), e);
@@ -480,41 +474,16 @@
     }
 
     /**
-     * Registers a callback for an atom when that atom is to be pulled. The stats service will
-     * invoke pullData in the callback when the stats service determines that this atom needs to be
-     * pulled. Currently, this only works for atoms with tags above 100,000 that do not have a uid.
-     *
-     * @param atomTag   The tag of the atom for this puller callback. Must be at least 100000.
-     * @param callback  The callback to be invoked when the stats service pulls the atom.
-     * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
+     * Temp registration for while the migration is in progress.
      *
      * @hide
-     * @deprecated Please use registerPullAtomCallback
      */
-    @Deprecated
-    @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
-    public void setPullerCallback(int atomTag, IStatsPullerCallback callback)
-            throws StatsUnavailableException {
-        synchronized (sLock) {
-            try {
-                IStatsd service = getIStatsdLocked();
-                if (callback == null) {
-                    service.unregisterPullerCallback(atomTag, mContext.getOpPackageName());
-                } else {
-                    service.registerPullerCallback(atomTag, callback,
-                            mContext.getOpPackageName());
-                }
-
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to connect to statsd when registering data listener.");
-                throw new StatsUnavailableException("could not connect", e);
-            } catch (SecurityException e) {
-                throw new StatsUnavailableException(e.getMessage(), e);
-            }
-        }
+    public void registerPullAtomCallback(int atomTag, @Nullable PullAtomMetadata metadata,
+            @NonNull StatsPullAtomCallback callback,
+            @NonNull @CallbackExecutor Executor executor) {
+        registerPullAtomCallback(atomTag, metadata, executor, callback);
     }
 
-
     /**
      * Registers a callback for an atom when that atom is to be pulled. The stats service will
      * invoke pullData in the callback when the stats service determines that this atom needs to be
@@ -524,21 +493,22 @@
      * @param metadata          Optional metadata specifying the timeout, cool down time, and
      *                          additive fields for mapping isolated to host uids.
      * @param callback          The callback to be invoked when the stats service pulls the atom.
-     * @param executor          The executor in which to run the callback
+     * @param executor          The executor in which to run the callback.
      *
-     * @hide
      */
     public void registerPullAtomCallback(int atomTag, @Nullable PullAtomMetadata metadata,
-            @NonNull StatsPullAtomCallback callback, @NonNull Executor executor) {
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull StatsPullAtomCallback callback) {
         long coolDownNs = metadata == null ? DEFAULT_COOL_DOWN_NS : metadata.mCoolDownNs;
         long timeoutNs = metadata == null ? DEFAULT_TIMEOUT_NS : metadata.mTimeoutNs;
         int[] additiveFields = metadata == null ? new int[0] : metadata.mAdditiveFields;
         if (additiveFields == null) {
             additiveFields = new int[0];
         }
+
         synchronized (sLock) {
             try {
-                IStatsCompanionService service = getIStatsCompanionServiceLocked();
+                IStatsManagerService service = getIStatsManagerServiceLocked();
                 PullAtomCallbackInternal rec =
                     new PullAtomCallbackInternal(atomTag, callback, executor);
                 service.registerPullAtomCallback(atomTag, coolDownNs, timeoutNs, additiveFields,
@@ -555,12 +525,11 @@
      *
      * @param atomTag           The tag of the atom of which to unregister
      *
-     * @hide
      */
     public void unregisterPullAtomCallback(int atomTag) {
         synchronized (sLock) {
             try {
-                IStatsCompanionService service = getIStatsCompanionServiceLocked();
+                IStatsManagerService service = getIStatsManagerServiceLocked();
                 service.unregisterPullAtomCallback(atomTag);
             } catch (RemoteException e) {
                 throw new RuntimeException("Unable to unregister pull atom callback");
@@ -581,21 +550,26 @@
 
         @Override
         public void onPullAtom(int atomTag, IPullAtomResultReceiver resultReceiver) {
-            mExecutor.execute(() -> {
-                List<StatsEvent> data = new ArrayList<>();
-                int successInt = mCallback.onPullAtom(atomTag, data);
-                boolean success = successInt == PULL_SUCCESS;
-                StatsEventParcel[] parcels = new StatsEventParcel[data.size()];
-                for (int i = 0; i < data.size(); i++) {
-                    parcels[i] = new StatsEventParcel();
-                    parcels[i].buffer = data.get(i).getBytes();
-                }
-                try {
-                    resultReceiver.pullFinished(atomTag, success, parcels);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "StatsPullResultReceiver failed for tag " + mAtomId);
-                }
-            });
+            long token = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> {
+                    List<StatsEvent> data = new ArrayList<>();
+                    int successInt = mCallback.onPullAtom(atomTag, data);
+                    boolean success = successInt == PULL_SUCCESS;
+                    StatsEventParcel[] parcels = new StatsEventParcel[data.size()];
+                    for (int i = 0; i < data.size(); i++) {
+                        parcels[i] = new StatsEventParcel();
+                        parcels[i].buffer = data.get(i).getBytes();
+                    }
+                    try {
+                        resultReceiver.pullFinished(atomTag, success, parcels);
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "StatsPullResultReceiver failed for tag " + mAtomId);
+                    }
+                });
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
     }
 
@@ -603,7 +577,6 @@
      * Metadata required for registering a StatsPullAtomCallback.
      * All fields are optional, and defaults will be used for fields that are unspecified.
      *
-     * @hide
      */
     public static class PullAtomMetadata {
         private final long mCoolDownNs;
@@ -618,22 +591,27 @@
         }
 
         /**
-         * Returns a new PullAtomMetadata.Builder object for constructing PullAtomMetadata for
-         * StatsManager#registerPullAtomCallback
+         * Temp for while migrations are in progress.
+         *
+         * @hide
          */
         public static PullAtomMetadata.Builder newBuilder() {
             return new PullAtomMetadata.Builder();
         }
 
         /**
-         * Builder for PullAtomMetadata.
+         *  Builder for PullAtomMetadata.
          */
         public static class Builder {
             private long mCoolDownNs;
             private long mTimeoutNs;
             private int[] mAdditiveFields;
 
-            private Builder() {
+            /**
+             * Returns a new PullAtomMetadata.Builder object for constructing PullAtomMetadata for
+             * StatsManager#registerPullAtomCallback
+             */
+            public Builder() {
                 mCoolDownNs = DEFAULT_COOL_DOWN_NS;
                 mTimeoutNs = DEFAULT_TIMEOUT_NS;
                 mAdditiveFields = null;
@@ -666,7 +644,7 @@
              * will be combined when the non-additive fields are the same.
              */
             @NonNull
-            public Builder setAdditiveFields(int[] additiveFields) {
+            public Builder setAdditiveFields(@NonNull int[] additiveFields) {
                 mAdditiveFields = additiveFields;
                 return this;
             }
@@ -709,50 +687,13 @@
     /**
      * Callback interface for pulling atoms requested by the stats service.
      *
-     * @hide
      */
     public interface StatsPullAtomCallback {
         /**
          * Pull data for the specified atom tag, filling in the provided list of StatsEvent data.
          * @return {@link #PULL_SUCCESS} if the pull was successful, or {@link #PULL_SKIP} if not.
          */
-        int onPullAtom(int atomTag, List<StatsEvent> data);
-    }
-
-    private class StatsdDeathRecipient implements IBinder.DeathRecipient {
-        @Override
-        public void binderDied() {
-            synchronized (sLock) {
-                mService = null;
-            }
-        }
-    }
-
-    @GuardedBy("sLock")
-    private IStatsd getIStatsdLocked() throws StatsUnavailableException {
-        if (mService != null) {
-            return mService;
-        }
-        mService = IStatsd.Stub.asInterface(ServiceManager.getService("stats"));
-        if (mService == null) {
-            throw new StatsUnavailableException("could not be found");
-        }
-        try {
-            mService.asBinder().linkToDeath(new StatsdDeathRecipient(), 0);
-        } catch (RemoteException e) {
-            throw new StatsUnavailableException("could not connect when linkToDeath", e);
-        }
-        return mService;
-    }
-
-    @GuardedBy("sLock")
-    private IStatsCompanionService getIStatsCompanionServiceLocked() {
-        if (mStatsCompanion != null) {
-            return mStatsCompanion;
-        }
-        mStatsCompanion = IStatsCompanionService.Stub.asInterface(
-                ServiceManager.getService("statscompanion"));
-        return mStatsCompanion;
+        int onPullAtom(int atomTag, @NonNull List<StatsEvent> data);
     }
 
     @GuardedBy("sLock")
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index a1765c8..42563b5 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -153,6 +153,11 @@
      */
     public static final int DEFAULT_SETUP_DISABLE2_FLAGS = DISABLE2_ROTATE_SUGGESTIONS;
 
+    /**
+     * disable flags to be applied when the device is sim-locked.
+     */
+    private static final int DEFAULT_SIM_LOCKED_DISABLED_FLAGS = DISABLE_EXPAND;
+
     /** @hide */
     public static final int NAVIGATION_HINT_BACK_ALT      = 1 << 0;
     /** @hide */
@@ -262,6 +267,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @TestApi
     public void expandNotificationsPanel() {
         try {
             final IStatusBarService svc = getService();
@@ -279,6 +285,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @TestApi
     public void collapsePanels() {
         try {
             final IStatusBarService svc = getService();
@@ -385,6 +392,30 @@
     }
 
     /**
+     * Enable or disable expansion of the status bar. When the device is SIM-locked, the status
+     * bar should not be expandable.
+     *
+     * @param disabled If {@code true}, the status bar will be set to non-expandable. If
+     *                 {@code false}, re-enables expansion of the status bar.
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.STATUS_BAR)
+    public void setDisabledForSimNetworkLock(boolean disabled) {
+        try {
+            final int userId = Binder.getCallingUserHandle().getIdentifier();
+            final IStatusBarService svc = getService();
+            if (svc != null) {
+                svc.disableForUser(disabled ? DEFAULT_SIM_LOCKED_DISABLED_FLAGS : DISABLE_NONE,
+                        mToken, mContext.getPackageName(), userId);
+            }
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Get this app's currently requested disabled components
      *
      * @return a new DisableInfo
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index ca3d0d7..7f69865 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -54,6 +54,7 @@
 import android.content.integrity.IAppIntegrityManager;
 import android.content.om.IOverlayManager;
 import android.content.om.OverlayManager;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.CrossProfileApps;
 import android.content.pm.DataLoaderManager;
 import android.content.pm.ICrossProfileApps;
@@ -88,6 +89,7 @@
 import android.hardware.input.InputManager;
 import android.hardware.iris.IIrisService;
 import android.hardware.iris.IrisManager;
+import android.hardware.lights.LightsManager;
 import android.hardware.location.ContextHubManager;
 import android.hardware.radio.RadioManager;
 import android.hardware.usb.IUsbManager;
@@ -105,6 +107,7 @@
 import android.media.soundtrigger.SoundTriggerManager;
 import android.media.tv.ITvInputManager;
 import android.media.tv.TvInputManager;
+import android.net.ConnectivityDiagnosticsManager;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityThread;
 import android.net.EthernetManager;
@@ -147,6 +150,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceManager.ServiceNotFoundException;
+import android.os.SystemConfigManager;
 import android.os.SystemUpdateManager;
 import android.os.SystemVibrator;
 import android.os.UserHandle;
@@ -170,6 +174,7 @@
 import android.service.persistentdata.PersistentDataBlockManager;
 import android.service.vr.IVrManager;
 import android.telecom.TelecomManager;
+import android.telephony.MmsManager;
 import android.telephony.TelephonyFrameworkInitializer;
 import android.telephony.TelephonyRegistryManager;
 import android.util.ArrayMap;
@@ -345,6 +350,14 @@
             }
         });
 
+        registerService(Context.NETWORK_STACK_SERVICE, IBinder.class,
+                new StaticServiceFetcher<IBinder>() {
+                    @Override
+                    public IBinder createService() {
+                        return ServiceManager.getService(Context.NETWORK_STACK_SERVICE);
+                    }
+                });
+
         registerService(Context.TETHERING_SERVICE, TetheringManager.class,
                 new CachedServiceFetcher<TetheringManager>() {
             @Override
@@ -365,6 +378,18 @@
                 return new IpSecManager(ctx, service);
             }});
 
+        registerService(Context.CONNECTIVITY_DIAGNOSTICS_SERVICE,
+                ConnectivityDiagnosticsManager.class,
+                new CachedServiceFetcher<ConnectivityDiagnosticsManager>() {
+            @Override
+            public ConnectivityDiagnosticsManager createService(ContextImpl ctx)
+                    throws ServiceNotFoundException {
+                // ConnectivityDiagnosticsManager is backed by ConnectivityService
+                IBinder b = ServiceManager.getServiceOrThrow(Context.CONNECTIVITY_SERVICE);
+                IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
+                return new ConnectivityDiagnosticsManager(ctx, service);
+            }});
+
         registerService(
                 Context.TEST_NETWORK_SERVICE,
                 TestNetworkManager.class,
@@ -617,6 +642,13 @@
                         return new SystemUpdateManager(service);
                     }});
 
+        registerService(Context.SYSTEM_CONFIG_SERVICE, SystemConfigManager.class,
+                new CachedServiceFetcher<SystemConfigManager>() {
+                    @Override
+                    public SystemConfigManager createService(ContextImpl ctx) {
+                        return new SystemConfigManager();
+                    }});
+
         registerService(Context.TELEPHONY_REGISTRY_SERVICE, TelephonyRegistryManager.class,
             new CachedServiceFetcher<TelephonyRegistryManager>() {
                 @Override
@@ -631,6 +663,13 @@
                 return new TelecomManager(ctx.getOuterContext());
             }});
 
+        registerService(Context.MMS_SERVICE, MmsManager.class,
+                new CachedServiceFetcher<MmsManager>() {
+                    @Override
+                    public MmsManager createService(ContextImpl ctx) {
+                        return new MmsManager(ctx.getOuterContext());
+                    }});
+
         registerService(Context.UI_MODE_SERVICE, UiModeManager.class,
                 new CachedServiceFetcher<UiModeManager>() {
             @Override
@@ -677,20 +716,20 @@
                     throws ServiceNotFoundException {
                 final IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
                 if (b == null) {
-                    // There are 2 reason service can be null:
-                    // 1.Device doesn't support it - that's fine
-                    // 2.App is running on instant mode - should fail
-                    final boolean enabled = Resources.getSystem()
-                            .getBoolean(com.android.internal.R.bool.config_enableWallpaperService);
-                    if (!enabled) {
-                        // Life moves on...
-                        return DisabledWallpaperManager.getInstance();
-                    }
-                    if (ctx.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.P) {
+                    ApplicationInfo appInfo = ctx.getApplicationInfo();
+                    if (appInfo.targetSdkVersion >= Build.VERSION_CODES.P
+                            && appInfo.isInstantApp()) {
                         // Instant app
                         throw new ServiceNotFoundException(Context.WALLPAPER_SERVICE);
                     }
+                    final boolean enabled = Resources.getSystem()
+                            .getBoolean(com.android.internal.R.bool.config_enableWallpaperService);
+                    if (!enabled) {
+                        // Device doesn't support wallpaper, return a limited manager
+                        return DisabledWallpaperManager.getInstance();
+                    }
                     // Bad state - WallpaperManager methods will throw exception
+                    Log.e(TAG, "No wallpaper service");
                 }
                 IWallpaperManager service = IWallpaperManager.Stub.asInterface(b);
                 return new WallpaperManager(service, ctx.getOuterContext(),
@@ -1237,6 +1276,13 @@
                                 Context.DATA_LOADER_MANAGER_SERVICE);
                         return new DataLoaderManager(IDataLoaderManager.Stub.asInterface(b));
                     }});
+        registerService(Context.LIGHTS_SERVICE, LightsManager.class,
+            new CachedServiceFetcher<LightsManager>() {
+                @Override
+                public LightsManager createService(ContextImpl ctx)
+                    throws ServiceNotFoundException {
+                    return new LightsManager(ctx);
+                }});
         //TODO(b/136132412): refactor this: 1) merge IIncrementalManager.aidl and
         //IIncrementalManagerNative.aidl, 2) implement the binder interface in
         //IncrementalManagerService.java, 3) use JNI to call native functions
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 18a3e6e..35cf687 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -27,10 +27,7 @@
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.Bitmap;
-import android.graphics.Point;
-import android.graphics.Rect;
 import android.graphics.Region;
-import android.hardware.display.DisplayManagerGlobal;
 import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -831,39 +828,20 @@
     }
 
     /**
-     * Takes a screenshot.
+     * Takes a screenshot of the default display and returns it by {@link Bitmap.Config#HARDWARE}
+     * format.
      *
      * @return The screenshot bitmap on success, null otherwise.
      */
     public Bitmap takeScreenshot() {
+        final int connectionId;
         synchronized (mLock) {
             throwIfNotConnectedLocked();
+            connectionId = mConnectionId;
         }
-        Display display = DisplayManagerGlobal.getInstance()
-                .getRealDisplay(Display.DEFAULT_DISPLAY);
-        Point displaySize = new Point();
-        display.getRealSize(displaySize);
-
-        int rotation = display.getRotation();
-
-        // Take the screenshot
-        Bitmap screenShot = null;
-        try {
-            // Calling out without a lock held.
-            screenShot = mUiAutomationConnection.takeScreenshot(
-                    new Rect(0, 0, displaySize.x, displaySize.y), rotation);
-            if (screenShot == null) {
-                return null;
-            }
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error while taking screnshot!", re);
-            return null;
-        }
-
-        // Optimization
-        screenShot.setHasAlpha(false);
-
-        return screenShot;
+        // Calling out without a lock held.
+        return AccessibilityInteractionClient.getInstance()
+                .takeScreenshot(connectionId, Display.DEFAULT_DISPLAY);
     }
 
     /**
@@ -1276,6 +1254,11 @@
                 }
 
                 @Override
+                public void onSystemActionsChanged() {
+                    /* do nothing */
+                }
+
+                @Override
                 public boolean onGesture(AccessibilityGestureEvent gestureEvent) {
                     /* do nothing */
                     return false;
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 82e9881..4fb9743 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -21,8 +21,6 @@
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Rect;
 import android.hardware.input.InputManager;
 import android.os.Binder;
 import android.os.IBinder;
@@ -180,23 +178,6 @@
     }
 
     @Override
-    public Bitmap takeScreenshot(Rect crop, int rotation) {
-        synchronized (mLock) {
-            throwIfCalledByNotTrustedUidLocked();
-            throwIfShutdownLocked();
-            throwIfNotConnectedLocked();
-        }
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            int width = crop.width();
-            int height = crop.height();
-            return SurfaceControl.screenshot(crop, width, height, rotation);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    @Override
     public boolean clearWindowContentFrameStats(int windowId) throws RemoteException {
         synchronized (mLock) {
             throwIfCalledByNotTrustedUidLocked();
@@ -449,7 +430,8 @@
         info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
                 | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
                 | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
-                | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS);
+                | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS
+                | AccessibilityServiceInfo.CAPABILITY_CAN_TAKE_SCREENSHOT);
         try {
             // Calling out with a lock held is fine since if the system
             // process is gone the client calling in will be killed.
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index 3633064..24873b8 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.IntRange;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
@@ -32,6 +33,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.time.LocalTime;
 
 /**
  * This class provides access to the system uimode services.  These services
@@ -163,6 +165,7 @@
     /** @hide */
     @IntDef(prefix = { "MODE_" }, value = {
             MODE_NIGHT_AUTO,
+            MODE_NIGHT_CUSTOM,
             MODE_NIGHT_NO,
             MODE_NIGHT_YES
     })
@@ -173,19 +176,25 @@
      * Constant for {@link #setNightMode(int)} and {@link #getNightMode()}:
      * automatically switch night mode on and off based on the time.
      */
-    public static final int MODE_NIGHT_AUTO = Configuration.UI_MODE_NIGHT_UNDEFINED >> 4;
+    public static final int MODE_NIGHT_AUTO = 0;
+
+    /**
+     * Constant for {@link #setNightMode(int)} and {@link #getNightMode()}:
+     * automatically switch night mode on and off based on the time.
+     */
+    public static final int MODE_NIGHT_CUSTOM = 3;
     
     /**
      * Constant for {@link #setNightMode(int)} and {@link #getNightMode()}:
      * never run in night mode.
      */
-    public static final int MODE_NIGHT_NO = Configuration.UI_MODE_NIGHT_NO >> 4;
+    public static final int MODE_NIGHT_NO = 1;
     
     /**
      * Constant for {@link #setNightMode(int)} and {@link #getNightMode()}:
      * always run in night mode.
      */
-    public static final int MODE_NIGHT_YES = Configuration.UI_MODE_NIGHT_YES >> 4;
+    public static final int MODE_NIGHT_YES = 2;
 
     private IUiModeManager mService;
 
@@ -377,6 +386,8 @@
      *       {@code notnight} mode</li>
      *   <li><em>{@link #MODE_NIGHT_YES}</em> sets the device into
      *       {@code night} mode</li>
+     *   <li><em>{@link #MODE_NIGHT_CUSTOM}</em> automatically switches between
+     *       {@code night} and {@code notnight} based on the custom time set (or default)</li>
      *   <li><em>{@link #MODE_NIGHT_AUTO}</em> automatically switches between
      *       {@code night} and {@code notnight} based on the device's current
      *       location and certain other sensors</li>
@@ -418,6 +429,7 @@
      *   <li>{@link #MODE_NIGHT_NO}</li>
      *   <li>{@link #MODE_NIGHT_YES}</li>
      *   <li>{@link #MODE_NIGHT_AUTO}</li>
+     *   <li>{@link #MODE_NIGHT_CUSTOM}</li>
      *   <li>{@code -1} on error</li>
      * </ul>
      *
@@ -475,7 +487,7 @@
     }
 
     /**
-     * @hide*
+     * @hide
      */
     public boolean setNightModeActivated(boolean active) {
         if (mService != null) {
@@ -487,4 +499,75 @@
         }
         return false;
     }
+
+    /**
+     * Returns the time of the day Dark theme activates
+     * <p>
+     * When night mode is {@link #MODE_NIGHT_CUSTOM}, the system uses
+     * this time set to activate it automatically.
+     */
+    @NonNull
+    public LocalTime getCustomNightModeStart() {
+        if (mService != null) {
+            try {
+                return LocalTime.ofNanoOfDay(mService.getCustomNightModeStart() * 1000);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return LocalTime.MIDNIGHT;
+    }
+
+    /**
+     * Sets the time of the day Dark theme activates
+     * <p>
+     * When night mode is {@link #MODE_NIGHT_CUSTOM}, the system uses
+     * this time set to activate it automatically
+     * @param time The time of the day Dark theme should activate
+     */
+    public void setCustomNightModeStart(@NonNull LocalTime time) {
+        if (mService != null) {
+            try {
+                mService.setCustomNightModeStart(time.toNanoOfDay() / 1000);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Returns the time of the day Dark theme deactivates
+     * <p>
+     * When night mode is {@link #MODE_NIGHT_CUSTOM}, the system uses
+     * this time set to deactivate it automatically.
+     */
+    @NonNull
+    public LocalTime getCustomNightModeEnd() {
+        if (mService != null) {
+            try {
+                return LocalTime.ofNanoOfDay(mService.getCustomNightModeEnd() * 1000);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return LocalTime.MIDNIGHT;
+    }
+
+    /**
+     * Sets the time of the day Dark theme deactivates
+     * <p>
+     * When night mode is {@link #MODE_NIGHT_CUSTOM}, the system uses
+     * this time set to deactivate it automatically.
+     * @param time The time of the day Dark theme should deactivate
+     */
+    public void setCustomNightModeEnd(@NonNull LocalTime time) {
+        if (mService != null) {
+            try {
+                mService.setCustomNightModeEnd(time.toNanoOfDay() / 1000);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
 }
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 2507991..6f1effd 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -543,6 +543,13 @@
         mCmProxy = new ColorManagementProxy(context);
     }
 
+    // no-op constructor called just by DisabledWallpaperManager
+    /*package*/ WallpaperManager() {
+        mContext = null;
+        mCmProxy = null;
+        mWcgEnabled = false;
+    }
+
     /**
      * Retrieve a WallpaperManager associated with the given Context.
      */
@@ -568,8 +575,10 @@
      *
      * @see Configuration#isScreenWideColorGamut()
      * @return True if wcg should be enabled for this device.
+     * @hide
      */
-    private boolean shouldEnableWideColorGamut() {
+    @TestApi
+    public boolean shouldEnableWideColorGamut() {
         return mWcgEnabled;
     }
 
@@ -877,6 +886,7 @@
      * @see #FLAG_SYSTEM
      * @hide
      */
+    @TestApi
     @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
     public boolean wallpaperSupportsWcg(int which) {
         if (!shouldEnableWideColorGamut()) {
@@ -893,6 +903,8 @@
      *
      * @hide
      */
+    @TestApi
+    @Nullable
     @UnsupportedAppUsage
     public Bitmap getBitmap() {
         return getBitmap(false);
@@ -1552,6 +1564,8 @@
      * @return The desired minimum width for the wallpaper. This value should
      * be honored by applications that set the wallpaper but it is not
      * mandatory.
+     *
+     * @see #getDesiredMinimumHeight()
      */
     public int getDesiredMinimumWidth() {
         if (sGlobals.mService == null) {
@@ -1578,6 +1592,8 @@
      * @return The desired minimum height for the wallpaper. This value should
      * be honored by applications that set the wallpaper but it is not
      * mandatory.
+     *
+     * @see #getDesiredMinimumWidth()
      */
     public int getDesiredMinimumHeight() {
         if (sGlobals.mService == null) {
@@ -1597,12 +1613,11 @@
      * a virtual wallpaper that is larger than the physical screen, matching
      * the size of their workspace.
      *
-     * <p>Note developers, who don't seem to be reading this.  This is
-     * for <em>home apps</em> to tell what size wallpaper they would like.
-     * Nobody else should be calling this!  Certainly not other non-home
-     * apps that change the wallpaper.  Those apps are supposed to
-     * <b>retrieve</b> the suggested size so they can construct a wallpaper
-     * that matches it.
+     * <p class="note">Calling this method from apps other than the active
+     * home app is not guaranteed to work properly.  Other apps that supply
+     * wallpaper imagery should use {@link #getDesiredMinimumWidth()} and
+     * {@link #getDesiredMinimumHeight()} and construct a wallpaper that
+     * matches those dimensions.
      *
      * <p>This method requires the caller to hold the permission
      * {@link android.Manifest.permission#SET_WALLPAPER_HINTS}.
@@ -1833,6 +1848,24 @@
     }
 
     /**
+     * Set the current zoom out level of the wallpaper
+     * @param zoom from 0 to 1 (inclusive) where 1 means fully zoomed out, 0 means fully zoomed in
+     *
+     * @hide
+     */
+    public void setWallpaperZoomOut(float zoom) {
+        if (zoom < 0 || zoom > 1f) {
+            throw new IllegalArgumentException("zoom must be between 0 and one: " + zoom);
+        }
+        try {
+            sGlobals.mService.setWallpaperZoomOut(zoom, mContext.getOpPackageName(),
+                    mContext.getDisplayId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns whether wallpapers are supported for the calling user. If this function returns
      * {@code false}, any attempts to changing the wallpaper will have no effect,
      * and any attempt to obtain of the wallpaper will return {@code null}.
diff --git a/core/java/android/app/WindowContext.java b/core/java/android/app/WindowContext.java
new file mode 100644
index 0000000..22cc14b
--- /dev/null
+++ b/core/java/android/app/WindowContext.java
@@ -0,0 +1,123 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
+import android.view.WindowManagerImpl;
+
+/**
+ * {@link WindowContext} is a context for non-activity windows such as
+ * {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY} windows or system
+ * windows. Its resources and configuration are adjusted to the area of the display that will be
+ * used when a new window is added via {@link android.view.WindowManager.addView}.
+ *
+ * @see Context#createWindowContext(int)
+ * @hide
+ */
+// TODO(b/128338354): Handle config/display changes from server side.
+public class WindowContext extends ContextWrapper {
+    private final WindowManagerImpl mWindowManager;
+    private final IWindowManager mWms;
+    private final IBinder mToken;
+    private final int mDisplayId;
+    private boolean mOwnsToken;
+
+    /**
+     * Default constructor. Can either accept an existing token or generate one and registers it
+     * with the server if necessary.
+     *
+     * @param base Base {@link Context} for this new instance.
+     * @param token A valid {@link com.android.server.wm.WindowToken}. Pass {@code null} to generate
+     *              one.
+     * @param type Window type to be used with this context.
+     * @hide
+     */
+    public WindowContext(Context base, IBinder token, int type) {
+        super(null /* base */);
+
+        mWms = WindowManagerGlobal.getWindowManagerService();
+        if (token != null && !isWindowToken(token)) {
+            throw new IllegalArgumentException("Token must be registered to server.");
+        }
+
+        final ContextImpl contextImpl = createBaseWindowContext(base, token);
+        attachBaseContext(contextImpl);
+        contextImpl.setOuterContext(this);
+
+        mToken = token != null ? token : new Binder();
+        mDisplayId = getDisplayId();
+        mWindowManager = new WindowManagerImpl(this);
+        mWindowManager.setDefaultToken(mToken);
+
+        // TODO(b/128338354): Obtain the correct config from WM and adjust resources.
+        if (token != null) {
+            mOwnsToken = false;
+            return;
+        }
+        try {
+            mWms.addWindowContextToken(mToken, type, mDisplayId, getPackageName());
+            // TODO(window-context): remove token with a DeathObserver
+        }  catch (RemoteException e) {
+            mOwnsToken = false;
+            throw e.rethrowFromSystemServer();
+        }
+        mOwnsToken = true;
+    }
+
+    /** Check if the passed window token is registered with the server. */
+    private boolean isWindowToken(@NonNull IBinder token) {
+        try {
+            return mWms.isWindowToken(token);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return false;
+    }
+
+    private static ContextImpl createBaseWindowContext(Context outer, IBinder token) {
+        final ContextImpl contextImpl = ContextImpl.getImpl(outer);
+        return contextImpl.createBaseWindowContext(token);
+    }
+
+    @Override
+    public Object getSystemService(String name) {
+        if (WINDOW_SERVICE.equals(name)) {
+            return mWindowManager;
+        }
+        return super.getSystemService(name);
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        if (mOwnsToken) {
+            try {
+                mWms.removeWindowToken(mToken, mDisplayId);
+                mOwnsToken = false;
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        super.finalize();
+    }
+}
diff --git a/core/java/android/app/admin/DevicePolicyKeyguardService.java b/core/java/android/app/admin/DevicePolicyKeyguardService.java
new file mode 100644
index 0000000..c2a76c5
--- /dev/null
+++ b/core/java/android/app/admin/DevicePolicyKeyguardService.java
@@ -0,0 +1,87 @@
+/*
+ * 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.admin;
+
+import android.annotation.Nullable;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.SurfaceControl;
+
+/**
+ * Client interface for providing the SystemUI with secondary lockscreen information.
+ *
+ * <p>An implementation must be provided by the device admin app when
+ * {@link DevicePolicyManager#setSecondaryLockscreenEnabled} is set to true and the service must be
+ * declared in the manifest as handling the action
+ * {@link DevicePolicyManager#ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE}, otherwise the keyguard
+ * will fail to bind to the service and continue to unlock.
+ *
+ * @see DevicePolicyManager#setSecondaryLockscreenEnabled
+ */
+public class DevicePolicyKeyguardService extends Service {
+    private static final String TAG = "DevicePolicyKeyguardService";
+    private IKeyguardCallback mCallback;
+
+    private final IKeyguardClient mClient = new IKeyguardClient.Stub() {
+        @Override
+        public void onSurfaceReady(@Nullable IBinder hostInputToken, IKeyguardCallback callback) {
+            mCallback = callback;
+            SurfaceControl surfaceControl =
+                    DevicePolicyKeyguardService.this.onSurfaceReady(hostInputToken);
+
+            if (mCallback != null) {
+                try {
+                    mCallback.onSurfaceControlCreated(surfaceControl);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Failed to return created SurfaceControl", e);
+                }
+            }
+        }
+    };
+
+    @Override
+    @Nullable
+    public final IBinder onBind(@Nullable Intent intent) {
+        return mClient.asBinder();
+    }
+
+    /**
+     * Called by keyguard once the host surface for the secondary lockscreen is ready to display
+     * remote content.
+     * @return the {@link SurfaceControl} for the Surface the secondary lockscreen content is
+     *      attached to.
+     */
+    @Nullable
+    public SurfaceControl onSurfaceReady(@Nullable IBinder hostInputToken) {
+        return null;
+    }
+
+    /**
+     * Signals to keyguard that the secondary lock screen is ready to be dismissed.
+     */
+    @Nullable
+    public void dismiss() {
+        try {
+            mCallback.onDismiss();
+        } catch (RemoteException e) {
+            Log.e(TAG, "onDismiss failed", e);
+        }
+    }
+}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 2aac94c..62557dc 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -398,6 +398,42 @@
             "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
 
     /**
+     * Activity action: Starts the provisioning flow which sets up a financed device.
+     *
+     * <p>During financed device provisioning, a device admin app is downloaded and set as the owner
+     * of the device. A device owner has full control over the device. The device owner can not be
+     * modified by the user.
+     *
+     * <p>A typical use case would be a device that is bought from the reseller through financing
+     * program.
+     *
+     * <p>An intent with this action can be sent only on an unprovisioned device.
+     *
+     * <p>Unlike {@link #ACTION_PROVISION_MANAGED_DEVICE}, the provisioning message can only be sent
+     * by a privileged app with the permission
+     * {@link android.Manifest.permission#DISPATCH_PROVISIONING_MESSAGE}.
+     *
+     * <p>The provisioning intent contains the following properties:
+     * <ul>
+     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}</li>
+     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_SUPPORT_URL}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_ORGANIZATION_NAME}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
+     * </ul>
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    @SystemApi
+    public static final String ACTION_PROVISION_FINANCED_DEVICE =
+            "android.app.action.PROVISION_FINANCED_DEVICE";
+
+    /**
      * Activity action: Starts the provisioning flow which sets up a managed device.
      * Must be started with {@link android.app.Activity#startActivityForResult(Intent, int)}.
      *
@@ -864,6 +900,7 @@
      * The name is displayed only during provisioning.
      *
      * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
+     * or {@link #ACTION_PROVISION_FINANCED_DEVICE}
      *
      * @hide
      */
@@ -876,6 +913,7 @@
      * during provisioning. If the url is not HTTPS, an error will be shown.
      *
      * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
+     * or {@link #ACTION_PROVISION_FINANCED_DEVICE}
      *
      * @hide
      */
@@ -888,6 +926,7 @@
      * as the app label of the package.
      *
      * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
+     * or {@link #ACTION_PROVISION_FINANCED_DEVICE}
      *
      * @hide
      */
@@ -912,6 +951,7 @@
      * {@link android.content.ClipData} of the intent too.
      *
      * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
+     * or {@link #ACTION_PROVISION_FINANCED_DEVICE}
      *
      * @hide
      */
@@ -1371,6 +1411,16 @@
             = "android.app.action.DEVICE_OWNER_CHANGED";
 
     /**
+     * Broadcast action: sent when the factory reset protection (FRP) policy is changed.
+     *
+     * @see #setFactoryResetProtectionPolicy
+     * @hide
+     */
+    @SystemApi
+    public static final String ACTION_RESET_PROTECTION_POLICY_CHANGED =
+            "android.app.action.RESET_PROTECTION_POLICY_CHANGED";
+
+    /**
      * The ComponentName of the administrator component.
      *
      * @see #ACTION_ADD_DEVICE_ADMIN
@@ -1942,16 +1992,6 @@
     public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14;
 
     /**
-     * Result code for {@link #checkProvisioningPreCondition}.
-     *
-     * <p>Returned for {@link #ACTION_PROVISION_MANAGED_PROFILE} when adding a managed profile is
-     * disallowed by {@link UserManager#DISALLOW_ADD_MANAGED_PROFILE}.
-     *
-     * @hide
-     */
-    public static final int CODE_ADD_MANAGED_PROFILE_DISALLOWED = 15;
-
-    /**
      * Result codes for {@link #checkProvisioningPreCondition} indicating all the provisioning pre
      * conditions.
      *
@@ -1963,7 +2003,7 @@
             CODE_USER_SETUP_COMPLETED, CODE_NOT_SYSTEM_USER, CODE_HAS_PAIRED,
             CODE_MANAGED_USERS_NOT_SUPPORTED, CODE_SYSTEM_USER, CODE_CANNOT_ADD_MANAGED_PROFILE,
             CODE_NOT_SYSTEM_USER_SPLIT, CODE_DEVICE_ADMIN_NOT_SUPPORTED,
-            CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER, CODE_ADD_MANAGED_PROFILE_DISALLOWED
+            CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER
     })
     public @interface ProvisioningPreCondition {}
 
@@ -2344,6 +2384,35 @@
     public static final int MAX_PASSWORD_LENGTH = 16;
 
     /**
+     * Service Action: Service implemented by a device owner or profile owner to provide a
+     * secondary lockscreen.
+     */
+    public static final String ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE =
+            "android.app.action.BIND_SECONDARY_LOCKSCREEN_SERVICE";
+
+    /**
+     * Return value for {@link #getPersonalAppsSuspendedReasons} when personal apps are not
+     * suspended.
+     */
+    public static final int PERSONAL_APPS_NOT_SUSPENDED = 0;
+
+    /**
+     * Flag for {@link #getPersonalAppsSuspendedReasons} return value. Set when personal
+     * apps are suspended by an admin explicitly via {@link #setPersonalAppsSuspended}.
+     */
+    public static final int PERSONAL_APPS_SUSPENDED_EXPLICITLY = 1 << 0;
+
+    /**
+     * @hide
+     */
+    @IntDef(flag = true, prefix = { "PERSONAL_APPS_" }, value = {
+            PERSONAL_APPS_NOT_SUSPENDED,
+            PERSONAL_APPS_SUSPENDED_EXPLICITLY
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PersonalAppSuspensionReason {}
+
+    /**
      * Return true if the given administrator component is currently active (enabled) in the system.
      *
      * @param admin The administrator component to check for.
@@ -4163,7 +4232,18 @@
      * device by first calling {@link #resetPassword} to set the password and then lock the device.
      * <p>
      * This method can be called on the {@link DevicePolicyManager} instance returned by
-     * {@link #getParentProfileInstance(ComponentName)} in order to lock the parent profile.
+     * {@link #getParentProfileInstance(ComponentName)} in order to lock the parent profile as
+     * well as the managed profile.
+     * <p>
+     * NOTE: In order to lock the parent profile and evict the encryption key of the managed
+     * profile, {@link #lockNow()} must be called twice: First, {@link #lockNow()} should be called
+     * on the {@link DevicePolicyManager} instance returned by
+     * {@link #getParentProfileInstance(ComponentName)}, then {@link #lockNow(int)} should be
+     * called on the {@link DevicePolicyManager} instance associated with the managed profile,
+     * with the {@link #FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY} flag.
+     * Calling the method twice in this order ensures that all users are locked and does not
+     * stop the device admin on the managed profile from issuing a second call to lock its own
+     * profile.
      *
      * @param flags May be 0 or {@link #FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY}.
      * @throws SecurityException if the calling application does not own an active administrator
@@ -4289,6 +4369,60 @@
     }
 
     /**
+     * Callable by device owner or profile owner of an organization-owned device, to set a
+     * factory reset protection (FRP) policy. When a new policy is set, the system
+     * notifies the FRP management agent of a policy change by broadcasting
+     * {@code ACTION_RESET_PROTECTION_POLICY_CHANGED}.
+     *
+     * @param admin  Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param policy the new FRP policy, or {@code null} to clear the current policy.
+     * @throws SecurityException if {@code admin} is not a device owner or a profile owner of
+     *                           an organization-owned device.
+     * @throws UnsupportedOperationException if factory reset protection is not
+     *                           supported on the device.
+     */
+    public void setFactoryResetProtectionPolicy(@NonNull ComponentName admin,
+            @Nullable FactoryResetProtectionPolicy policy) {
+        throwIfParentInstance("setFactoryResetProtectionPolicy");
+        if (mService != null) {
+            try {
+                mService.setFactoryResetProtectionPolicy(admin, policy);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Callable by device owner or profile owner of an organization-owned device, to retrieve
+     * the current factory reset protection (FRP) policy set previously by
+     * {@link #setFactoryResetProtectionPolicy}.
+     * <p>
+     * This method can also be called by the FRP management agent on device, in which case,
+     * it can pass {@code null} as the ComponentName.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with or
+     *              {@code null} if called by the FRP management agent on device.
+     * @return The current FRP policy object or {@code null} if no policy is set.
+     * @throws SecurityException if {@code admin} is not a device owner, a profile owner of
+     *                           an organization-owned device or the FRP management agent.
+     * @throws UnsupportedOperationException if factory reset protection is not
+     *                           supported on the device.
+     */
+    public @Nullable FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(
+            @Nullable ComponentName admin) {
+        throwIfParentInstance("getFactoryResetProtectionPolicy");
+        if (mService != null) {
+            try {
+                return mService.getFactoryResetProtectionPolicy(admin);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return null;
+    }
+
+    /**
      * Called by an application that is administering the device to set the
      * global proxy and exclusion list.
      * <p>
@@ -4465,6 +4599,18 @@
             = "android.app.action.START_ENCRYPTION";
 
     /**
+     * Activity action: launch the DPC to check policy compliance. This intent is launched when
+     * the user taps on the notification about personal apps suspension. When handling this intent
+     * the DPC must check if personal apps should still be suspended and either unsuspend them or
+     * instruct the user on how to resolve the noncompliance causing the suspension.
+     *
+     * @see #setPersonalAppsSuspended
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_CHECK_POLICY_COMPLIANCE =
+            "android.app.action.CHECK_POLICY_COMPLIANCE";
+
+    /**
      * Broadcast action: notify managed provisioning that new managed user is created.
      *
      * @hide
@@ -4559,6 +4705,9 @@
             | DevicePolicyManager.KEYGUARD_DISABLE_BIOMETRICS;
 
     /**
+     * @deprecated This method does not actually modify the storage encryption of the device.
+     * It has never affected the encryption status of a device.
+     *
      * Called by an application that is administering the device to request that the storage system
      * be encrypted. Does nothing if the caller is on a secondary user or a managed profile.
      * <p>
@@ -4592,6 +4741,7 @@
      * @throws SecurityException if {@code admin} is not an active administrator or does not use
      *             {@link DeviceAdminInfo#USES_ENCRYPTED_STORAGE}
      */
+    @Deprecated
     public int setStorageEncryption(@NonNull ComponentName admin, boolean encrypt) {
         throwIfParentInstance("setStorageEncryption");
         if (mService != null) {
@@ -4605,6 +4755,10 @@
     }
 
     /**
+     * @deprecated This method only returns the value set by {@link #setStorageEncryption}.
+     * It does not actually reflect the storage encryption status.
+     * Use {@link #getStorageEncryptionStatus} for that.
+     *
      * Called by an application that is administering the device to
      * determine the requested setting for secure storage.
      *
@@ -4613,6 +4767,7 @@
      * administrators.
      * @return true if the admin(s) are requesting encryption, false if not.
      */
+    @Deprecated
     public boolean getStorageEncryption(@Nullable ComponentName admin) {
         throwIfParentInstance("getStorageEncryption");
         if (mService != null) {
@@ -6756,21 +6911,18 @@
     }
 
     /**
-     * @hide
-     * Privileged apps can use this method to find out if the device was provisioned as
+     * Apps can use this method to find out if the device was provisioned as
      * organization-owend device with a managed profile.
      *
      * This, together with checking whether the device has a device owner (by calling
-     * {@link #isDeviceManaged()}), could be used to learn whether the device is owned by an
+     * {@link #isDeviceOwnerApp}), could be used to learn whether the device is owned by an
      * organization or an individual:
-     * If this method returns true OR {@link #isDeviceManaged()} returns true, then
-     * the device is owned by an organization. Otherwise, it's owned by an individual.
+     * If this method returns true OR {@link #isDeviceOwnerApp} returns true (for any package),
+     * then the device is owned by an organization. Otherwise, it's owned by an individual.
      *
      * @return {@code true} if the device was provisioned as organization-owned device,
      * {@code false} otherwise.
      */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public boolean isOrganizationOwnedDeviceWithManagedProfile() {
         throwIfParentInstance("isOrganizationOwnedDeviceWithManagedProfile");
         if (mService != null) {
@@ -7324,7 +7476,9 @@
      * @param userHandle The user for whom to check the caller-id permission
      * @hide
      */
-    public boolean getBluetoothContactSharingDisabled(UserHandle userHandle) {
+    @SystemApi
+    @RequiresPermission(permission.INTERACT_ACROSS_USERS)
+    public boolean getBluetoothContactSharingDisabled(@NonNull UserHandle userHandle) {
         if (mService != null) {
             try {
                 return mService.getBluetoothContactSharingDisabledForUser(userHandle
@@ -8106,6 +8260,11 @@
      * actual package file remain. This function can be called by a device owner, profile owner, or
      * by a delegate given the {@link #DELEGATION_PACKAGE_ACCESS} scope via
      * {@link #setDelegatedScopes}.
+     * <p>
+     * This method can be called on the {@link DevicePolicyManager} instance, returned by
+     * {@link #getParentProfileInstance(ComponentName)}, where the caller must be the profile owner
+     * of an organization-owned managed profile and the package must be a system package. If called
+     * on the parent instance, then the package is hidden or unhidden in the personal profile.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
      *            {@code null} if the caller is a package access delegate.
@@ -8113,17 +8272,20 @@
      * @param hidden {@code true} if the package should be hidden, {@code false} if it should be
      *            unhidden.
      * @return boolean Whether the hidden setting of the package was successfully updated.
-     * @throws SecurityException if {@code admin} is not a device or profile owner.
+     * @throws SecurityException if {@code admin} is not a device or profile owner or if called on
+     *            the parent profile and the {@code admin} is not a profile owner of an
+     *            organization-owned managed profile.
+     * @throws IllegalArgumentException if called on the parent profile and the package provided
+     *            is not a system package.
      * @see #setDelegatedScopes
      * @see #DELEGATION_PACKAGE_ACCESS
      */
     public boolean setApplicationHidden(@NonNull ComponentName admin, String packageName,
             boolean hidden) {
-        throwIfParentInstance("setApplicationHidden");
         if (mService != null) {
             try {
                 return mService.setApplicationHidden(admin, mContext.getPackageName(), packageName,
-                        hidden);
+                        hidden, mParentInstance);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -8135,20 +8297,30 @@
      * Determine if a package is hidden. This function can be called by a device owner, profile
      * owner, or by a delegate given the {@link #DELEGATION_PACKAGE_ACCESS} scope via
      * {@link #setDelegatedScopes}.
+     * <p>
+     * This method can be called on the {@link DevicePolicyManager} instance, returned by
+     * {@link #getParentProfileInstance(ComponentName)}, where the caller must be the profile owner
+     * of an organization-owned managed profile and the package must be a system package. If called
+     * on the parent instance, this will determine whether the package is hidden or unhidden in the
+     * personal profile.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
      *            {@code null} if the caller is a package access delegate.
      * @param packageName The name of the package to retrieve the hidden status of.
      * @return boolean {@code true} if the package is hidden, {@code false} otherwise.
-     * @throws SecurityException if {@code admin} is not a device or profile owner.
+     * @throws SecurityException if {@code admin} is not a device or profile owner or if called on
+     *            the parent profile and the {@code admin} is not a profile owner of an
+     *            organization-owned managed profile.
+     * @throws IllegalArgumentException if called on the parent profile and the package provided
+     *            is not a system package.
      * @see #setDelegatedScopes
      * @see #DELEGATION_PACKAGE_ACCESS
      */
     public boolean isApplicationHidden(@NonNull ComponentName admin, String packageName) {
-        throwIfParentInstance("isApplicationHidden");
         if (mService != null) {
             try {
-                return mService.isApplicationHidden(admin, mContext.getPackageName(), packageName);
+                return mService.isApplicationHidden(admin, mContext.getPackageName(), packageName,
+                        mParentInstance);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -8300,6 +8472,52 @@
     }
 
     /**
+     * Called by device owner or profile owner to set whether a secondary lockscreen needs to be
+     * shown.
+     *
+     * <p>The secondary lockscreen will by displayed after the primary keyguard security screen
+     * requirements are met. To provide the lockscreen content the DO/PO will need to provide a
+     * service handling the {@link #ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE} intent action,
+     * extending the {@link DevicePolicyKeyguardService} class.
+     *
+     * <p>Relevant interactions on the secondary lockscreen should be communicated back to the
+     * keyguard via {@link IKeyguardCallback}, such as when the screen is ready to be dismissed.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param enabled Whether or not the lockscreen needs to be shown.
+     * @throws SecurityException if {@code admin} is not a device or profile owner.
+     * @see #isSecondaryLockscreenEnabled
+     **/
+    public void setSecondaryLockscreenEnabled(@NonNull ComponentName admin, boolean enabled) {
+        throwIfParentInstance("setSecondaryLockscreenEnabled");
+        if (mService != null) {
+            try {
+                mService.setSecondaryLockscreenEnabled(admin, enabled);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Returns whether the secondary lock screen needs to be shown.
+     * @see #setSecondaryLockscreenEnabled
+     * @hide
+     */
+    @SystemApi
+    public boolean isSecondaryLockscreenEnabled(int userId) {
+        throwIfParentInstance("isSecondaryLockscreenEnabled");
+        if (mService != null) {
+            try {
+                return mService.isSecondaryLockscreenEnabled(userId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return false;
+    }
+
+    /**
      * Sets which packages may enter lock task mode.
      * <p>
      * Any packages that share uid with an allowed package will also be allowed to activate lock
@@ -8512,6 +8730,55 @@
     }
 
     /**
+     * Called by a device owner or a profile owner of an organization-owned managed profile to
+     * control whether the user can change networks configured by the admin.
+     * <p>
+     * WiFi network configuration lockdown is controlled by a global settings
+     * {@link android.provider.Settings.Global#WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN} and calling
+     * this API effectively modifies the global settings. Previously device owners can also
+     * control this directly via {@link #setGlobalSetting} but they are recommended to switch
+     * to this API.
+     *
+     * @param admin             admin Which {@link DeviceAdminReceiver} this request is associated
+     *                          with.
+     * @param lockdown Whether the admin configured networks should be unmodifiable by the
+     *                          user.
+     * @throws SecurityException if caller is not a device owner or a profile owner of an
+     *                           organization-owned managed profile.
+     */
+    public void setLockdownAdminConfiguredNetworks(@NonNull ComponentName admin, boolean lockdown) {
+        throwIfParentInstance("setLockdownAdminConfiguredNetworks");
+        if (mService != null) {
+            try {
+                mService.setLockdownAdminConfiguredNetworks(admin, lockdown);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Called by a device owner or a profile owner of an organization-owned managed profile to
+     * determine whether the user is prevented from modifying networks configured by the admin.
+     *
+     * @param admin             admin Which {@link DeviceAdminReceiver} this request is associated
+     *                          with.
+     * @throws SecurityException if caller is not a device owner or a profile owner of an
+     *                           organization-owned managed profile.
+     */
+    public boolean isLockdownAdminConfiguredNetworks(@NonNull ComponentName admin) {
+        throwIfParentInstance("setLockdownAdminConfiguredNetworks");
+        if (mService != null) {
+            try {
+                return mService.isLockdownAdminConfiguredNetworks(admin);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return false;
+    }
+
+    /**
      * Called by a device owner or a profile owner of an organization-owned managed
      * profile to set the system wall clock time. This only takes effect if called when
      * {@link android.provider.Settings.Global#AUTO_TIME} is 0, otherwise {@code false}
@@ -9119,6 +9386,16 @@
      * {@link android.os.Build.VERSION_CODES#M} the app-op matching the permission is set to
      * {@link android.app.AppOpsManager#MODE_IGNORED}, but the permission stays granted.
      *
+     * NOTE: Starting from Android R, location-related permissions cannot be granted by the
+     * admin: Calling this method with {@link #PERMISSION_GRANT_STATE_GRANTED} for any of the
+     * following permissions will return false:
+     *
+     * <ul>
+     * <li>{@code ACCESS_FINE_LOCATION}</li>
+     * <li>{@code ACCESS_BACKGROUND_LOCATION}</li>
+     * <li>{@code ACCESS_COARSE_LOCATION}</li>
+     * </ul>
+     *
      * @param admin Which profile or device owner this request is associated with.
      * @param packageName The application to grant or revoke a permission to.
      * @param permission The permission to grant or revoke.
@@ -11195,6 +11472,10 @@
      *
      * <p>Previous calls are overridden by each subsequent call to this method.
      *
+     * <p>When previously-set cross-profile packages are missing from {@code packageNames}, the
+     * app-op for {@code INTERACT_ACROSS_PROFILES} will be reset for those packages. This will not
+     * occur for packages that are whitelisted by the OEM.
+     *
      * @param admin the {@link DeviceAdminReceiver} this request is associated with
      * @param packageNames the new cross-profile package names
      */
@@ -11375,6 +11656,7 @@
      * @throws SecurityException if {@code admin} is not a device owner.
      */
     public void setProtectedPackages(@NonNull ComponentName admin, @NonNull List<String> packages) {
+        throwIfParentInstance("setProtectedPackages");
         if (mService != null) {
             try {
                 mService.setProtectedPackages(admin, packages);
@@ -11391,6 +11673,7 @@
      * @throws SecurityException if {@code admin} is not a device owner.
      */
     public @NonNull List<String> getProtectedPackages(@NonNull ComponentName admin) {
+        throwIfParentInstance("getProtectedPackages");
         if (mService != null) {
             try {
                 return mService.getProtectedPackages(admin);
@@ -11400,4 +11683,92 @@
         }
         return Collections.emptyList();
     }
+
+    /**
+     * Called by device owner or profile owner of an organization-owned managed profile to toggle
+     * Common Criteria mode for the device. When the device is in Common Criteria mode,
+     * certain device functionalities are tuned to meet the higher
+     * security level required by Common Criteria certification. For example:
+     * <ul>
+     * <li> Bluetooth long term key material is additionally integrity-protected with AES-GCM. </li>
+     * <li> WiFi configuration store is additionally integrity-protected with AES-GCM. </li>
+     * </ul>
+     * Common Criteria mode is disabled by default.
+     *
+     * @param admin which {@link DeviceAdminReceiver} this request is associated with.
+     * @param enabled whether Common Criteria mode should be enabled or not.
+     */
+    public void setCommonCriteriaModeEnabled(@NonNull ComponentName admin, boolean enabled) {
+        throwIfParentInstance("setCommonCriteriaModeEnabled");
+        if (mService != null) {
+            try {
+                mService.setCommonCriteriaModeEnabled(admin, enabled);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Called by device owner or profile owner of an organization-owned managed profile to return
+     * whether Common Criteria mode is currently enabled for the device.
+     *
+     * @param admin which {@link DeviceAdminReceiver} this request is associated with.
+     * @return {@code true} if Common Criteria mode is enabled, {@code false} otherwise.
+     */
+    public boolean isCommonCriteriaModeEnabled(@NonNull ComponentName admin) {
+        throwIfParentInstance("isCommonCriteriaModeEnabled");
+        if (mService != null) {
+            try {
+                return mService.isCommonCriteriaModeEnabled(admin);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Called by profile owner of an organization-owned managed profile to check whether
+     * personal apps are suspended.
+     *
+     * @return a bitmask of reasons for personal apps suspension or
+     *     {@link #PERSONAL_APPS_NOT_SUSPENDED} if apps are not suspended.
+     * @see #setPersonalAppsSuspended
+     */
+    public @PersonalAppSuspensionReason int getPersonalAppsSuspendedReasons(
+            @NonNull ComponentName admin) {
+        throwIfParentInstance("getPersonalAppsSuspendedReasons");
+        if (mService != null) {
+            try {
+                return mService.getPersonalAppsSuspendedReasons(admin);
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * Called by a profile owner of an organization-owned managed profile to suspend personal
+     * apps on the device. When personal apps are suspended the device can only be used for calls.
+     *
+     * <p>When personal apps are suspended, an ongoing notification about that is shown to the user.
+     * When the user taps the notification, system invokes {@link #ACTION_CHECK_POLICY_COMPLIANCE}
+     * in the profile owner package. Profile owner implementation that uses personal apps suspension
+     * must handle this intent.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with
+     * @param suspended Whether personal apps should be suspended.
+     */
+    public void setPersonalAppsSuspended(@NonNull ComponentName admin, boolean suspended) {
+        throwIfParentInstance("setPersonalAppsSuspended");
+        if (mService != null) {
+            try {
+                mService.setPersonalAppsSuspended(admin, suspended);
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
+        }
+    }
 }
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index f299d45..e6c89d9 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -17,9 +17,12 @@
 package android.app.admin;
 
 import android.annotation.UserIdInt;
+import android.content.ComponentName;
 import android.content.Intent;
+import android.os.UserHandle;
 
 import java.util.List;
+import java.util.Set;
 
 /**
  * Device policy manager local system service interface.
@@ -165,4 +168,23 @@
      * Do not call it directly. Use {@link DevicePolicyCache#getInstance()} instead.
      */
     protected abstract DeviceStateCache getDeviceStateCache();
+
+    /**
+     * Returns the combined set of the following:
+     * <ul>
+     * <li>The package names that the admin has previously set as allowed to request user consent
+     * for cross-profile communication, via {@link
+     * DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)}.</li>
+     * <li>The default package names that are allowed to request user consent for cross-profile
+     * communication without being explicitly enabled by the admin , via {@link
+     * DevicePolicyManager#setDefaultCrossProfilePackages(ComponentName, UserHandle, Set)}.</li>
+     * </ul>
+     *
+     * @return the combined set of whitelisted package names set via
+     * {@link DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)} and
+     * {@link DevicePolicyManager#setDefaultCrossProfilePackages(ComponentName, UserHandle, Set)}
+     *
+     * @hide
+     */
+    public abstract List<String> getAllCrossProfilePackages();
 }
diff --git a/core/java/android/app/admin/FactoryResetProtectionPolicy.aidl b/core/java/android/app/admin/FactoryResetProtectionPolicy.aidl
new file mode 100644
index 0000000..72e639a
--- /dev/null
+++ b/core/java/android/app/admin/FactoryResetProtectionPolicy.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.app.admin;
+
+parcelable FactoryResetProtectionPolicy;
diff --git a/core/java/android/app/admin/FactoryResetProtectionPolicy.java b/core/java/android/app/admin/FactoryResetProtectionPolicy.java
new file mode 100644
index 0000000..ed74779
--- /dev/null
+++ b/core/java/android/app/admin/FactoryResetProtectionPolicy.java
@@ -0,0 +1,237 @@
+/*
+ * 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.app.admin;
+
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.END_TAG;
+import static org.xmlpull.v1.XmlPullParser.TEXT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The factory reset protection policy determines which accounts can unlock a device that
+ * has gone through untrusted factory reset.
+ * <p>
+ * Only a device owner or profile owner of an organization-owned device can set a factory
+ * reset protection policy for the device by calling the {@code DevicePolicyManager} method
+ * {@link DevicePolicyManager#setFactoryResetProtectionPolicy(ComponentName,
+ * FactoryResetProtectionPolicy)}}.
+ *
+ * @see DevicePolicyManager#setFactoryResetProtectionPolicy
+ * @see DevicePolicyManager#getFactoryResetProtectionPolicy
+ */
+public final class FactoryResetProtectionPolicy implements Parcelable {
+
+    private static final String LOG_TAG = "FactoryResetProtectionPolicy";
+
+    private static final String KEY_FACTORY_RESET_PROTECTION_ACCOUNT =
+            "factory_reset_protection_account";
+    private static final String KEY_FACTORY_RESET_PROTECTION_DISABLED =
+            "factory_reset_protection_disabled";
+    private static final String ATTR_VALUE = "value";
+
+    private final List<String> mFactoryResetProtectionAccounts;
+    private final boolean mFactoryResetProtectionDisabled;
+
+    private FactoryResetProtectionPolicy(List<String> factoryResetProtectionAccounts,
+            boolean factoryResetProtectionDisabled) {
+        mFactoryResetProtectionAccounts = factoryResetProtectionAccounts;
+        mFactoryResetProtectionDisabled = factoryResetProtectionDisabled;
+    }
+
+    /**
+     * Get the list of accounts that can provision a device which has been factory reset.
+     */
+    public @NonNull List<String> getFactoryResetProtectionAccounts() {
+        return mFactoryResetProtectionAccounts;
+    }
+
+    /**
+     * Return whether factory reset protection for the device is disabled or not.
+     */
+    public boolean isFactoryResetProtectionDisabled() {
+        return mFactoryResetProtectionDisabled;
+    }
+
+    /**
+     * Builder class for {@link FactoryResetProtectionPolicy} objects.
+     */
+    public static class Builder {
+        private List<String> mFactoryResetProtectionAccounts;
+        private boolean mFactoryResetProtectionDisabled;
+
+        /**
+         * Initialize a new Builder to construct a {@link FactoryResetProtectionPolicy}.
+         */
+        public Builder() {
+        };
+
+        /**
+         * Sets which accounts can unlock a device that has been factory reset.
+         * <p>
+         * Once set, the consumer unlock flow will be disabled and only accounts in this list
+         * can unlock factory reset protection after untrusted factory reset.
+         * <p>
+         * It's up to the FRP management agent to interpret the {@code String} as account it
+         * supports. Please consult their relevant documentation for details.
+         *
+         * @param factoryResetProtectionAccounts list of accounts.
+         * @return the same Builder instance.
+         */
+        @NonNull
+        public Builder setFactoryResetProtectionAccounts(
+                @NonNull List<String> factoryResetProtectionAccounts) {
+            mFactoryResetProtectionAccounts = new ArrayList<>(factoryResetProtectionAccounts);
+            return this;
+        }
+
+        /**
+         * Sets whether factory reset protection is disabled or not.
+         * <p>
+         * Once disabled, factory reset protection will not kick in all together when the device
+         * goes through untrusted factory reset. This applies to both the consumer unlock flow and
+         * the admin account overrides via {@link #setFactoryResetProtectionAccounts}
+         *
+         * @param factoryResetProtectionDisabled Whether the policy is disabled or not.
+         * @return the same Builder instance.
+         */
+        @NonNull
+        public Builder setFactoryResetProtectionDisabled(boolean factoryResetProtectionDisabled) {
+            mFactoryResetProtectionDisabled = factoryResetProtectionDisabled;
+            return this;
+        }
+
+        /**
+         * Combines all of the attributes that have been set on this {@code Builder}
+         *
+         * @return a new {@link FactoryResetProtectionPolicy} object.
+         */
+        @NonNull
+        public FactoryResetProtectionPolicy build() {
+            return new FactoryResetProtectionPolicy(mFactoryResetProtectionAccounts,
+                    mFactoryResetProtectionDisabled);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "FactoryResetProtectionPolicy{"
+                + "mFactoryResetProtectionAccounts=" + mFactoryResetProtectionAccounts
+                + ", mFactoryResetProtectionDisabled=" + mFactoryResetProtectionDisabled
+                + '}';
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, @Nullable int flags) {
+        int accountsCount = mFactoryResetProtectionAccounts.size();
+        dest.writeInt(accountsCount);
+        for (String account: mFactoryResetProtectionAccounts) {
+            dest.writeString(account);
+        }
+        dest.writeBoolean(mFactoryResetProtectionDisabled);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Creator<FactoryResetProtectionPolicy> CREATOR =
+            new Creator<FactoryResetProtectionPolicy>() {
+
+                @Override
+                public FactoryResetProtectionPolicy createFromParcel(Parcel in) {
+                    List<String> factoryResetProtectionAccounts = new ArrayList<>();
+                    int accountsCount = in.readInt();
+                    for (int i = 0; i < accountsCount; i++) {
+                        factoryResetProtectionAccounts.add(in.readString());
+                    }
+                    boolean factoryResetProtectionDisabled = in.readBoolean();
+
+                    return new FactoryResetProtectionPolicy(factoryResetProtectionAccounts,
+                            factoryResetProtectionDisabled);
+                }
+
+                @Override
+                public FactoryResetProtectionPolicy[] newArray(int size) {
+                    return new FactoryResetProtectionPolicy[size];
+                }
+    };
+
+    /**
+     * Restore a previously saved FactoryResetProtectionPolicy from XML.
+     * <p>
+     * No validation is required on the reconstructed policy since the XML was previously
+     * created by the system server from a validated policy.
+     * @hide
+     */
+    @Nullable
+    public static FactoryResetProtectionPolicy readFromXml(@NonNull XmlPullParser parser) {
+        try {
+            boolean factoryResetProtectionDisabled = Boolean.parseBoolean(
+                    parser.getAttributeValue(null, KEY_FACTORY_RESET_PROTECTION_DISABLED));
+
+            List<String> factoryResetProtectionAccounts = new ArrayList<>();
+            int outerDepth = parser.getDepth();
+            int type;
+            while ((type = parser.next()) != END_DOCUMENT
+                    && (type != END_TAG || parser.getDepth() > outerDepth)) {
+                if (type == END_TAG || type == TEXT) {
+                    continue;
+                }
+                if (!parser.getName().equals(KEY_FACTORY_RESET_PROTECTION_ACCOUNT)) {
+                    continue;
+                }
+                factoryResetProtectionAccounts.add(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            }
+
+            return new FactoryResetProtectionPolicy(factoryResetProtectionAccounts,
+                    factoryResetProtectionDisabled);
+        } catch (XmlPullParserException | IOException e) {
+            Log.w(LOG_TAG, "Reading from xml failed", e);
+        }
+        return null;
+    }
+
+    /**
+     * @hide
+     */
+    public void writeToXml(@NonNull XmlSerializer out) throws IOException {
+        out.attribute(null, KEY_FACTORY_RESET_PROTECTION_DISABLED,
+                Boolean.toString(mFactoryResetProtectionDisabled));
+        for (String account : mFactoryResetProtectionAccounts) {
+            out.startTag(null, KEY_FACTORY_RESET_PROTECTION_ACCOUNT);
+            out.attribute(null, ATTR_VALUE, account);
+            out.endTag(null, KEY_FACTORY_RESET_PROTECTION_ACCOUNT);
+        }
+    }
+
+}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 3eec46b..3d6bf9d 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -24,6 +24,7 @@
 import android.app.admin.SystemUpdateInfo;
 import android.app.admin.SystemUpdatePolicy;
 import android.app.admin.PasswordMetrics;
+import android.app.admin.FactoryResetProtectionPolicy;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -104,6 +105,9 @@
 
     void wipeDataWithReason(int flags, String wipeReasonForUser, boolean parent);
 
+    void setFactoryResetProtectionPolicy(in ComponentName who, in FactoryResetProtectionPolicy policy);
+    FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(in ComponentName who);
+
     ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList);
     ComponentName getGlobalProxyAdmin(int userHandle);
     void setRecommendedGlobalProxy(in ComponentName admin, in ProxyInfo proxyInfo);
@@ -229,8 +233,8 @@
     boolean isNotificationListenerServicePermitted(in String packageName, int userId);
 
     Intent createAdminSupportIntent(in String restriction);
-    boolean setApplicationHidden(in ComponentName admin, in String callerPackage, in String packageName, boolean hidden);
-    boolean isApplicationHidden(in ComponentName admin, in String callerPackage, in String packageName);
+    boolean setApplicationHidden(in ComponentName admin, in String callerPackage, in String packageName, boolean hidden, boolean parent);
+    boolean isApplicationHidden(in ComponentName admin, in String callerPackage, in String packageName, boolean parent);
 
     UserHandle createAndManageUser(in ComponentName who, in String name, in ComponentName profileOwner, in PersistableBundle adminExtras, in int flags);
     boolean removeUser(in ComponentName who, in UserHandle userHandle);
@@ -248,6 +252,9 @@
     String[] getAccountTypesWithManagementDisabled();
     String[] getAccountTypesWithManagementDisabledAsUser(int userId);
 
+    void setSecondaryLockscreenEnabled(in ComponentName who, boolean enabled);
+    boolean isSecondaryLockscreenEnabled(int userId);
+
     void setLockTaskPackages(in ComponentName who, in String[] packages);
     String[] getLockTaskPackages(in ComponentName who);
     boolean isLockTaskPermitted(in String pkg);
@@ -259,6 +266,9 @@
     void setSystemSetting(in ComponentName who, in String setting, in String value);
     void setSecureSetting(in ComponentName who, in String setting, in String value);
 
+    void setLockdownAdminConfiguredNetworks(in ComponentName who, boolean lockdown);
+    boolean isLockdownAdminConfiguredNetworks(in ComponentName who);
+
     void setLocationEnabled(in ComponentName who, boolean locationEnabled);
 
     boolean setTime(in ComponentName who, long millis);
@@ -457,4 +467,10 @@
     void setProtectedPackages(in ComponentName admin, in List<String> packages);
 
     List<String> getProtectedPackages(in ComponentName admin);
+
+    void setCommonCriteriaModeEnabled(in ComponentName admin, boolean enabled);
+    boolean isCommonCriteriaModeEnabled(in ComponentName admin);
+
+    int getPersonalAppsSuspendedReasons(in ComponentName admin);
+    void setPersonalAppsSuspended(in ComponentName admin, boolean suspended);
 }
diff --git a/core/java/android/app/admin/IKeyguardCallback.aidl b/core/java/android/app/admin/IKeyguardCallback.aidl
new file mode 100644
index 0000000..81e7d4d
--- /dev/null
+++ b/core/java/android/app/admin/IKeyguardCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2011 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.admin;
+
+import android.view.SurfaceControl;
+
+/**
+ * Internal IPC interface for informing the keyguard of events on the secondary lockscreen.
+ * @hide
+ */
+interface IKeyguardCallback {
+    oneway void onSurfaceControlCreated(in SurfaceControl remoteSurfaceControl);
+    oneway void onDismiss();
+}
diff --git a/core/java/android/app/admin/IKeyguardClient.aidl b/core/java/android/app/admin/IKeyguardClient.aidl
new file mode 100644
index 0000000..4bfd990
--- /dev/null
+++ b/core/java/android/app/admin/IKeyguardClient.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2011 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.admin;
+
+import android.app.admin.IKeyguardCallback;
+
+/**
+ * Internal IPC interface for a service to provide the SystemUI with secondary lockscreen
+ * information.
+ * @hide
+ */
+interface IKeyguardClient {
+    oneway void onSurfaceReady(in IBinder hostInputToken, in IKeyguardCallback keyguardCallback);
+}
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index c8f2ff3..567eb4a 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -85,6 +85,15 @@
     public static final int FLAG_NON_INCREMENTAL = 1 << 2;
 
     /**
+     * For key value backup, indicates that the backup contains no new data since the last backup
+     * attempt completed without any errors. The transport should use this to record that
+     * a successful backup attempt has been completed but no backup data has been changed.
+     *
+     * @see #performBackup(PackageInfo, ParcelFileDescriptor, int)
+     */
+    public static final int FLAG_DATA_NOT_CHANGED = 1 << 3;
+
+    /**
      * Used as a boolean extra in the binding intent of transports. We pass {@code true} to
      * notify transports that the current connection is used for registering the transport.
      */
@@ -302,7 +311,8 @@
      *   BackupService.doBackup() method.  This may be a pipe rather than a file on
      *   persistent media, so it may not be seekable.
      * @param flags a combination of {@link BackupTransport#FLAG_USER_INITIATED}, {@link
-     *   BackupTransport#FLAG_NON_INCREMENTAL}, {@link BackupTransport#FLAG_INCREMENTAL}, or 0.
+     *   BackupTransport#FLAG_NON_INCREMENTAL}, {@link BackupTransport#FLAG_INCREMENTAL},
+     *   {@link BackupTransport#FLAG_DATA_NOT_CHANGED},or 0.
      * @return one of {@link BackupTransport#TRANSPORT_OK} (OK so far),
      *  {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED} (to suppress backup of this
      *  specific package, but allow others to proceed),
diff --git a/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java b/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java
index 1e6ab41..bea1bd6 100644
--- a/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java
+++ b/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.UserIdInt;
+import android.graphics.Bitmap;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.RemoteException;
@@ -77,6 +78,28 @@
     }
 
     /**
+     * Hints to the system that a new context image using the provided bitmap should be sent to
+     * the system content suggestions service.
+     *
+     * @param bitmap the new context image
+     * @param imageContextRequestExtras sent with request to provide implementation specific
+     *                                  extra information.
+     */
+    public void provideContextImage(
+            @NonNull Bitmap bitmap, @NonNull Bundle imageContextRequestExtras) {
+        if (mService == null) {
+            Log.e(TAG, "provideContextImage called, but no ContentSuggestionsManager configured");
+            return;
+        }
+
+        try {
+            mService.provideContextBitmap(mUser, bitmap, imageContextRequestExtras);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Hints to the system that a new context image for the provided task should be sent to the
      * system content suggestions service.
      *
diff --git a/core/java/android/app/contentsuggestions/IContentSuggestionsManager.aidl b/core/java/android/app/contentsuggestions/IContentSuggestionsManager.aidl
index b18a758..8e6338b 100644
--- a/core/java/android/app/contentsuggestions/IContentSuggestionsManager.aidl
+++ b/core/java/android/app/contentsuggestions/IContentSuggestionsManager.aidl
@@ -20,6 +20,7 @@
 import android.app.contentsuggestions.ISelectionsCallback;
 import android.app.contentsuggestions.ClassificationsRequest;
 import android.app.contentsuggestions.SelectionsRequest;
+import android.graphics.Bitmap;
 import android.os.Bundle;
 import android.os.UserHandle;
 import com.android.internal.os.IResultReceiver;
@@ -30,6 +31,10 @@
             int userId,
             int taskId,
             in Bundle imageContextRequestExtras);
+    void provideContextBitmap(
+            int userId,
+            in Bitmap bitmap,
+            in Bundle imageContextRequestExtras);
     void suggestContentSelections(
             int userId,
             in SelectionsRequest request,
diff --git a/core/java/android/app/servertransaction/StartActivityItem.java b/core/java/android/app/servertransaction/StartActivityItem.java
new file mode 100644
index 0000000..4fbe02b
--- /dev/null
+++ b/core/java/android/app/servertransaction/StartActivityItem.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 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.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+
+/**
+ * Request to move an activity to started and visible state.
+ * @hide
+ */
+public class StartActivityItem extends ActivityLifecycleItem {
+
+    private static final String TAG = "StartActivityItem";
+
+    @Override
+    public void execute(ClientTransactionHandler client, IBinder token,
+            PendingTransactionActions pendingActions) {
+        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "startActivityItem");
+        client.handleStartActivity(token, pendingActions);
+        Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+    }
+
+    @Override
+    public int getTargetState() {
+        return ON_START;
+    }
+
+
+    // ObjectPoolItem implementation
+
+    private StartActivityItem() {}
+
+    /** Obtain an instance initialized with provided params. */
+    public static StartActivityItem obtain() {
+        StartActivityItem instance = ObjectPool.obtain(StartActivityItem.class);
+        if (instance == null) {
+            instance = new StartActivityItem();
+        }
+
+        return instance;
+    }
+
+    @Override
+    public void recycle() {
+        super.recycle();
+        ObjectPool.recycle(this);
+    }
+
+
+    // Parcelable implementation
+
+    /** Write to Parcel. */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        // Empty
+    }
+
+    /** Read from Parcel. */
+    private StartActivityItem(Parcel in) {
+        // Empty
+    }
+
+    public static final @android.annotation.NonNull Creator<StartActivityItem> CREATOR =
+            new Creator<StartActivityItem>() {
+                public StartActivityItem createFromParcel(Parcel in) {
+                    return new StartActivityItem(in);
+                }
+
+                public StartActivityItem[] newArray(int size) {
+                    return new StartActivityItem[size];
+                }
+            };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return 17;
+    }
+
+    @Override
+    public String toString() {
+        return "StartActivityItem{}";
+    }
+}
+
diff --git a/core/java/android/app/servertransaction/StopActivityItem.java b/core/java/android/app/servertransaction/StopActivityItem.java
index 63efa6f..8668bd4 100644
--- a/core/java/android/app/servertransaction/StopActivityItem.java
+++ b/core/java/android/app/servertransaction/StopActivityItem.java
@@ -31,14 +31,13 @@
 
     private static final String TAG = "StopActivityItem";
 
-    private boolean mShowWindow;
     private int mConfigChanges;
 
     @Override
     public void execute(ClientTransactionHandler client, IBinder token,
             PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
-        client.handleStopActivity(token, mShowWindow, mConfigChanges, pendingActions,
+        client.handleStopActivity(token, mConfigChanges, pendingActions,
                 true /* finalStateRequest */, "STOP_ACTIVITY_ITEM");
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
@@ -59,13 +58,15 @@
 
     private StopActivityItem() {}
 
-    /** Obtain an instance initialized with provided params. */
-    public static StopActivityItem obtain(boolean showWindow, int configChanges) {
+    /**
+     * Obtain an instance initialized with provided params.
+     * @param configChanges Configuration pieces that changed.
+     */
+    public static StopActivityItem obtain(int configChanges) {
         StopActivityItem instance = ObjectPool.obtain(StopActivityItem.class);
         if (instance == null) {
             instance = new StopActivityItem();
         }
-        instance.mShowWindow = showWindow;
         instance.mConfigChanges = configChanges;
 
         return instance;
@@ -74,7 +75,6 @@
     @Override
     public void recycle() {
         super.recycle();
-        mShowWindow = false;
         mConfigChanges = 0;
         ObjectPool.recycle(this);
     }
@@ -85,13 +85,11 @@
     /** Write to Parcel. */
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeBoolean(mShowWindow);
         dest.writeInt(mConfigChanges);
     }
 
     /** Read from Parcel. */
     private StopActivityItem(Parcel in) {
-        mShowWindow = in.readBoolean();
         mConfigChanges = in.readInt();
     }
 
@@ -115,20 +113,18 @@
             return false;
         }
         final StopActivityItem other = (StopActivityItem) o;
-        return mShowWindow == other.mShowWindow && mConfigChanges == other.mConfigChanges;
+        return mConfigChanges == other.mConfigChanges;
     }
 
     @Override
     public int hashCode() {
         int result = 17;
-        result = 31 * result + (mShowWindow ? 1 : 0);
         result = 31 * result + mConfigChanges;
         return result;
     }
 
     @Override
     public String toString() {
-        return "StopActivityItem{showWindow=" + mShowWindow + ",configChanges=" + mConfigChanges
-                + "}";
+        return "StopActivityItem{configChanges=" + mConfigChanges + "}";
     }
 }
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 20e0da3..17fcda5 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -218,7 +218,7 @@
                             null /* customIntent */);
                     break;
                 case ON_START:
-                    mTransactionHandler.handleStartActivity(r, mPendingActions);
+                    mTransactionHandler.handleStartActivity(r.token, mPendingActions);
                     break;
                 case ON_RESUME:
                     mTransactionHandler.handleResumeActivity(r.token, false /* finalStateRequest */,
@@ -230,8 +230,8 @@
                             "LIFECYCLER_PAUSE_ACTIVITY");
                     break;
                 case ON_STOP:
-                    mTransactionHandler.handleStopActivity(r.token, false /* show */,
-                            0 /* configChanges */, mPendingActions, false /* finalStateRequest */,
+                    mTransactionHandler.handleStopActivity(r.token, 0 /* configChanges */,
+                            mPendingActions, false /* finalStateRequest */,
                             "LIFECYCLER_STOP_ACTIVITY");
                     break;
                 case ON_DESTROY:
diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
index 0ea8c3c..a34be5c 100644
--- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java
+++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
@@ -75,9 +75,15 @@
 
         mLifecycleSequence.clear();
         if (finish >= start) {
-            // just go there
-            for (int i = start + 1; i <= finish; i++) {
-                mLifecycleSequence.add(i);
+            if (start == ON_START && finish == ON_STOP) {
+                // A case when we from start to stop state soon, we don't need to go
+                // through the resumed, paused state.
+                mLifecycleSequence.add(ON_STOP);
+            } else {
+                // just go there
+                for (int i = start + 1; i <= finish; i++) {
+                    mLifecycleSequence.add(i);
+                }
             }
         } else { // finish < start, can't just cycle down
             if (start == ON_PAUSE && finish == ON_RESUME) {
@@ -183,8 +189,7 @@
                 lifecycleItem = PauseActivityItem.obtain();
                 break;
             case ON_STOP:
-                lifecycleItem = StopActivityItem.obtain(r.isVisibleFromServer(),
-                        0 /* configChanges */);
+                lifecycleItem = StopActivityItem.obtain(0 /* configChanges */);
                 break;
             default:
                 lifecycleItem = ResumeActivityItem.obtain(false /* isForward */);
diff --git a/core/java/android/app/servertransaction/WindowVisibilityItem.java b/core/java/android/app/servertransaction/WindowVisibilityItem.java
deleted file mode 100644
index 115d1ec..0000000
--- a/core/java/android/app/servertransaction/WindowVisibilityItem.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright 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 android.app.servertransaction;
-
-import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
-
-import android.app.ClientTransactionHandler;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Trace;
-
-/**
- * Window visibility change message.
- * @hide
- */
-public class WindowVisibilityItem extends ClientTransactionItem {
-
-    private boolean mShowWindow;
-
-    @Override
-    public void execute(ClientTransactionHandler client, IBinder token,
-            PendingTransactionActions pendingActions) {
-        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER,
-                mShowWindow ? "activityShowWindow" : "activityHideWindow");
-        client.handleWindowVisibility(token, mShowWindow);
-        Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
-    }
-
-
-    // ObjectPoolItem implementation
-
-    private WindowVisibilityItem() {}
-
-    /** Obtain an instance initialized with provided params. */
-    public static WindowVisibilityItem obtain(boolean showWindow) {
-        WindowVisibilityItem instance = ObjectPool.obtain(WindowVisibilityItem.class);
-        if (instance == null) {
-            instance = new WindowVisibilityItem();
-        }
-        instance.mShowWindow = showWindow;
-
-        return instance;
-    }
-
-    @Override
-    public void recycle() {
-        mShowWindow = false;
-        ObjectPool.recycle(this);
-    }
-
-
-    // Parcelable implementation
-
-    /** Write to Parcel. */
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeBoolean(mShowWindow);
-    }
-
-    /** Read from Parcel. */
-    private WindowVisibilityItem(Parcel in) {
-        mShowWindow = in.readBoolean();
-    }
-
-    public static final @android.annotation.NonNull Creator<WindowVisibilityItem> CREATOR =
-            new Creator<WindowVisibilityItem>() {
-        public WindowVisibilityItem createFromParcel(Parcel in) {
-            return new WindowVisibilityItem(in);
-        }
-
-        public WindowVisibilityItem[] newArray(int size) {
-            return new WindowVisibilityItem[size];
-        }
-    };
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-        final WindowVisibilityItem other = (WindowVisibilityItem) o;
-        return mShowWindow == other.mShowWindow;
-    }
-
-    @Override
-    public int hashCode() {
-        return 17 + 31 * (mShowWindow ? 1 : 0);
-    }
-
-    @Override
-    public String toString() {
-        return "WindowVisibilityItem{showWindow=" + mShowWindow + "}";
-    }
-}
diff --git a/core/java/android/app/timedetector/PhoneTimeSuggestion.java b/core/java/android/app/timedetector/PhoneTimeSuggestion.java
index 479e4b4..bd649f8 100644
--- a/core/java/android/app/timedetector/PhoneTimeSuggestion.java
+++ b/core/java/android/app/timedetector/PhoneTimeSuggestion.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.TimestampedValue;
@@ -28,17 +29,23 @@
 import java.util.Objects;
 
 /**
- * A time signal from a telephony source. The value can be {@code null} to indicate that the
- * telephony source has entered an "un-opinionated" state and any previously sent suggestions are
- * being withdrawn. When not {@code null}, the value consists of the number of milliseconds elapsed
- * since 1/1/1970 00:00:00 UTC and the time according to the elapsed realtime clock when that number
- * was established. The elapsed realtime clock is considered accurate but volatile, so time signals
- * must not be persisted across device resets.
+ * A time suggestion from an identified telephony source. e.g. from NITZ information from a specific
+ * radio.
+ *
+ * <p>The time value can be {@code null} to indicate that the telephony source has entered an
+ * "un-opinionated" state and any previous suggestions from the source are being withdrawn. When not
+ * {@code null}, the value consists of the number of milliseconds elapsed since 1/1/1970 00:00:00
+ * UTC and the time according to the elapsed realtime clock when that number was established. The
+ * elapsed realtime clock is considered accurate but volatile, so time suggestions must not be
+ * persisted across device resets.
  *
  * @hide
  */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 public final class PhoneTimeSuggestion implements Parcelable {
 
+    /** @hide */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final @NonNull Parcelable.Creator<PhoneTimeSuggestion> CREATOR =
             new Parcelable.Creator<PhoneTimeSuggestion>() {
                 public PhoneTimeSuggestion createFromParcel(Parcel in) {
@@ -85,15 +92,27 @@
         dest.writeList(mDebugInfo);
     }
 
+    /**
+     * Returns an identifier for the source of this suggestion. When a device has several "phones",
+     * i.e. sim slots or equivalent, it is used to identify which one.
+     */
     public int getPhoneId() {
         return mPhoneId;
     }
 
+    /**
+     * Returns the suggestion. {@code null} means that the caller is no longer sure what time it
+     * is.
+     */
     @Nullable
     public TimestampedValue<Long> getUtcTime() {
         return mUtcTime;
     }
 
+    /**
+     * Returns debug metadata for the suggestion. The information is present in {@link #toString()}
+     * but is not considered for {@link #equals(Object)} and {@link #hashCode()}.
+     */
     @NonNull
     public List<String> getDebugInfo() {
         return mDebugInfo == null
@@ -105,7 +124,7 @@
      * information is present in {@link #toString()} but is not considered for
      * {@link #equals(Object)} and {@link #hashCode()}.
      */
-    public void addDebugInfo(String debugInfo) {
+    public void addDebugInfo(@NonNull String debugInfo) {
         if (mDebugInfo == null) {
             mDebugInfo = new ArrayList<>();
         }
@@ -156,16 +175,19 @@
      *
      * @hide
      */
-    public static class Builder {
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final class Builder {
         private final int mPhoneId;
-        private TimestampedValue<Long> mUtcTime;
-        private List<String> mDebugInfo;
+        @Nullable private TimestampedValue<Long> mUtcTime;
+        @Nullable private List<String> mDebugInfo;
 
+        /** Creates a builder with the specified {@code phoneId}. */
         public Builder(int phoneId) {
             mPhoneId = phoneId;
         }
 
         /** Returns the builder for call chaining. */
+        @NonNull
         public Builder setUtcTime(@Nullable TimestampedValue<Long> utcTime) {
             if (utcTime != null) {
                 // utcTime can be null, but the value it holds cannot.
@@ -177,6 +199,7 @@
         }
 
         /** Returns the builder for call chaining. */
+        @NonNull
         public Builder addDebugInfo(@NonNull String debugInfo) {
             if (mDebugInfo == null) {
                 mDebugInfo = new ArrayList<>();
@@ -186,6 +209,7 @@
         }
 
         /** Returns the {@link PhoneTimeSuggestion}. */
+        @NonNull
         public PhoneTimeSuggestion build() {
             return new PhoneTimeSuggestion(this);
         }
diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java
index 54dd1be..7c29f01 100644
--- a/core/java/android/app/timedetector/TimeDetector.java
+++ b/core/java/android/app/timedetector/TimeDetector.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.content.Context;
 import android.os.RemoteException;
@@ -29,8 +30,11 @@
 
 /**
  * The interface through which system components can send signals to the TimeDetectorService.
+ *
+ * <p>This class is marked non-final for mockito.
  * @hide
  */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 @SystemService(Context.TIME_DETECTOR_SERVICE)
 public class TimeDetector {
     private static final String TAG = "timedetector.TimeDetector";
@@ -38,6 +42,7 @@
 
     private final ITimeDetectorService mITimeDetectorService;
 
+    /** @hide */
     public TimeDetector() throws ServiceNotFoundException {
         mITimeDetectorService = ITimeDetectorService.Stub.asInterface(
                 ServiceManager.getServiceOrThrow(Context.TIME_DETECTOR_SERVICE));
@@ -62,6 +67,8 @@
 
     /**
      * Suggests the user's manually entered current time to the detector.
+     *
+     * @hide
      */
     @RequiresPermission(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE)
     public void suggestManualTime(@NonNull ManualTimeSuggestion timeSuggestion) {
@@ -77,6 +84,8 @@
 
     /**
      * A shared utility method to create a {@link ManualTimeSuggestion}.
+     *
+     * @hide
      */
     public static ManualTimeSuggestion createManualTimeSuggestion(long when, String why) {
         TimestampedValue<Long> utcTime =
@@ -88,6 +97,8 @@
 
     /**
      * Suggests the time according to a network time source like NTP.
+     *
+     * @hide
      */
     @RequiresPermission(android.Manifest.permission.SET_TIME)
     public void suggestNetworkTime(NetworkTimeSuggestion timeSuggestion) {
diff --git a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java b/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java
index e8162488..d71ffcb 100644
--- a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java
+++ b/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -30,12 +31,27 @@
 import java.util.Objects;
 
 /**
- * A suggested time zone from a Phone-based signal, e.g. from MCC and NITZ information.
+ * A time zone suggestion from an identified telephony source, e.g. from MCC and NITZ information
+ * associated with a specific radio.
+ *
+ * <p>The time zone ID can be {@code null} to indicate that the telephony source has entered an
+ * "un-opinionated" state and any previous suggestions from that source are being withdrawn.
+ * When not {@code null}, the value consists of a suggested time zone ID and metadata that can be
+ * used to judge quality / certainty of the suggestion.
+ *
+ * <p>{@code matchType} must be set to {@link #MATCH_TYPE_NA} when {@code zoneId} is {@code null},
+ * and one of the other {@code MATCH_TYPE_} values when it is not {@code null}.
+ *
+ * <p>{@code quality} must be set to {@link #QUALITY_NA} when {@code zoneId} is {@code null},
+ * and one of the other {@code QUALITY_} values when it is not {@code null}.
  *
  * @hide
  */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 public final class PhoneTimeZoneSuggestion implements Parcelable {
 
+    /** @hide */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     @NonNull
     public static final Creator<PhoneTimeZoneSuggestion> CREATOR =
             new Creator<PhoneTimeZoneSuggestion>() {
@@ -58,6 +74,7 @@
         return new Builder(phoneId).addDebugInfo(debugInfo).build();
     }
 
+    /** @hide */
     @IntDef({ MATCH_TYPE_NA, MATCH_TYPE_NETWORK_COUNTRY_ONLY, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET,
             MATCH_TYPE_EMULATOR_ZONE_ID, MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY })
     @Retention(RetentionPolicy.SOURCE)
@@ -90,6 +107,7 @@
      */
     public static final int MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY = 5;
 
+    /** @hide */
     @IntDef({ QUALITY_NA, QUALITY_SINGLE_ZONE, QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET,
             QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS })
     @Retention(RetentionPolicy.SOURCE)
@@ -115,7 +133,7 @@
 
     /**
      * The ID of the phone this suggestion is associated with. For multiple-sim devices this
-     * helps to establish origin so filtering / stickiness can be implemented.
+     * helps to establish source so filtering / stickiness can be implemented.
      */
     private final int mPhoneId;
 
@@ -123,6 +141,7 @@
      * The suggestion. {@code null} means there is no current suggestion and any previous suggestion
      * should be forgotten.
      */
+    @Nullable
     private final String mZoneId;
 
     /**
@@ -139,9 +158,10 @@
     private final int mQuality;
 
     /**
-     * Free-form debug information about how the signal was derived. Used for debug only,
+     * Free-form debug information about how the suggestion was derived. Used for debug only,
      * intentionally not used in equals(), etc.
      */
+    @Nullable
     private List<String> mDebugInfo;
 
     private PhoneTimeZoneSuggestion(Builder builder) {
@@ -182,25 +202,47 @@
         return 0;
     }
 
+    /**
+     * Returns an identifier for the source of this suggestion. When a device has several "phones",
+     * i.e. sim slots or equivalent, it is used to identify which one.
+     */
     public int getPhoneId() {
         return mPhoneId;
     }
 
+    /**
+     * Returns the suggested time zone Olson ID, e.g. "America/Los_Angeles". {@code null} means that
+     * the caller is no longer sure what the current time zone is. See
+     * {@link PhoneTimeZoneSuggestion} for the associated {@code matchType} / {@code quality} rules.
+     */
     @Nullable
     public String getZoneId() {
         return mZoneId;
     }
 
+    /**
+     * Returns information about how the suggestion was determined which could be used to rank
+     * suggestions when several are available from different sources. See
+     * {@link PhoneTimeZoneSuggestion} for the associated rules.
+     */
     @MatchType
     public int getMatchType() {
         return mMatchType;
     }
 
+    /**
+     * Returns information about the likelihood of the suggested zone being correct.  See
+     * {@link PhoneTimeZoneSuggestion} for the associated rules.
+     */
     @Quality
     public int getQuality() {
         return mQuality;
     }
 
+    /**
+     * Returns debug metadata for the suggestion. The information is present in {@link #toString()}
+     * but is not considered for {@link #equals(Object)} and {@link #hashCode()}.
+     */
     @NonNull
     public List<String> getDebugInfo() {
         return mDebugInfo == null
@@ -267,36 +309,43 @@
      *
      * @hide
      */
-    public static class Builder {
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final class Builder {
         private final int mPhoneId;
-        private String mZoneId;
+        @Nullable private String mZoneId;
         @MatchType private int mMatchType;
         @Quality private int mQuality;
-        private List<String> mDebugInfo;
+        @Nullable private List<String> mDebugInfo;
 
         public Builder(int phoneId) {
             mPhoneId = phoneId;
         }
 
-        /** Returns the builder for call chaining. */
-        public Builder setZoneId(String zoneId) {
+        /**
+         * Returns the builder for call chaining.
+         */
+        @NonNull
+        public Builder setZoneId(@Nullable String zoneId) {
             mZoneId = zoneId;
             return this;
         }
 
         /** Returns the builder for call chaining. */
+        @NonNull
         public Builder setMatchType(@MatchType int matchType) {
             mMatchType = matchType;
             return this;
         }
 
         /** Returns the builder for call chaining. */
+        @NonNull
         public Builder setQuality(@Quality int quality) {
             mQuality = quality;
             return this;
         }
 
         /** Returns the builder for call chaining. */
+        @NonNull
         public Builder addDebugInfo(@NonNull String debugInfo) {
             if (mDebugInfo == null) {
                 mDebugInfo = new ArrayList<>();
@@ -333,6 +382,7 @@
         }
 
         /** Returns the {@link PhoneTimeZoneSuggestion}. */
+        @NonNull
         public PhoneTimeZoneSuggestion build() {
             validate();
             return new PhoneTimeZoneSuggestion(this);
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java
index e165d8a..5b5f311 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetector.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.content.Context;
 import android.os.RemoteException;
@@ -28,8 +29,10 @@
 /**
  * The interface through which system components can send signals to the TimeZoneDetectorService.
  *
+ * <p>This class is non-final for mockito.
  * @hide
  */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 @SystemService(Context.TIME_ZONE_DETECTOR_SERVICE)
 public class TimeZoneDetector {
     private static final String TAG = "timezonedetector.TimeZoneDetector";
@@ -37,6 +40,7 @@
 
     private final ITimeZoneDetectorService mITimeZoneDetectorService;
 
+    /** @hide */
     public TimeZoneDetector() throws ServiceNotFoundException {
         mITimeZoneDetectorService = ITimeZoneDetectorService.Stub.asInterface(
                 ServiceManager.getServiceOrThrow(Context.TIME_ZONE_DETECTOR_SERVICE));
@@ -46,7 +50,10 @@
      * Suggests the current time zone, determined using telephony signals, to the detector. The
      * detector may ignore the signal based on system settings, whether better information is
      * available, and so on.
+     *
+     * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     @RequiresPermission(android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE)
     public void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion timeZoneSuggestion) {
         if (DEBUG) {
@@ -62,6 +69,8 @@
     /**
      * Suggests the current time zone, determined for the user's manually information, to the
      * detector. The detector may ignore the signal based on system settings.
+     *
+     * @hide
      */
     @RequiresPermission(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE)
     public void suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion timeZoneSuggestion) {
@@ -77,6 +86,8 @@
 
     /**
      * A shared utility method to create a {@link ManualTimeZoneSuggestion}.
+     *
+     * @hide
      */
     public static ManualTimeZoneSuggestion createManualTimeZoneSuggestion(String tzId, String why) {
         ManualTimeZoneSuggestion suggestion = new ManualTimeZoneSuggestion(tzId);
diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java
index 4346d97..9c4a8f4 100644
--- a/core/java/android/app/usage/NetworkStatsManager.java
+++ b/core/java/android/app/usage/NetworkStatsManager.java
@@ -16,7 +16,10 @@
 
 package android.app.usage;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.app.usage.NetworkStats.Bucket;
@@ -27,6 +30,9 @@
 import android.net.INetworkStatsService;
 import android.net.NetworkIdentity;
 import android.net.NetworkTemplate;
+import android.net.netstats.provider.AbstractNetworkStatsProvider;
+import android.net.netstats.provider.NetworkStatsProviderCallback;
+import android.net.netstats.provider.NetworkStatsProviderWrapper;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.Looper;
@@ -519,6 +525,34 @@
         private DataUsageRequest request;
     }
 
+    /**
+     * Registers a custom provider of {@link android.net.NetworkStats} to combine the network
+     * statistics that cannot be seen by the kernel to system. To unregister, invoke
+     * {@link NetworkStatsProviderCallback#unregister()}.
+     *
+     * @param tag a human readable identifier of the custom network stats provider.
+     * @param provider a custom implementation of {@link AbstractNetworkStatsProvider} that needs to
+     *                 be registered to the system.
+     * @return a {@link NetworkStatsProviderCallback}, which can be used to report events to the
+     *         system.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+    @NonNull public NetworkStatsProviderCallback registerNetworkStatsProvider(
+            @NonNull String tag,
+            @NonNull AbstractNetworkStatsProvider provider) {
+        try {
+            final NetworkStatsProviderWrapper wrapper = new NetworkStatsProviderWrapper(provider);
+            return new NetworkStatsProviderCallback(
+                    mService.registerNetworkStatsProvider(tag, wrapper));
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+        // Unreachable code, but compiler doesn't know about it.
+        return null;
+    }
+
     private static NetworkTemplate createTemplate(int networkType, String subscriberId) {
         final NetworkTemplate template;
         switch (networkType) {
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index d840c1c..6ab880d 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -451,21 +451,7 @@
 
         /** @hide */
         public Event(Event orig) {
-            mPackage = orig.mPackage;
-            mClass = orig.mClass;
-            mInstanceId = orig.mInstanceId;
-            mTaskRootPackage = orig.mTaskRootPackage;
-            mTaskRootClass = orig.mTaskRootClass;
-            mTimeStamp = orig.mTimeStamp;
-            mEventType = orig.mEventType;
-            mConfiguration = orig.mConfiguration;
-            mShortcutId = orig.mShortcutId;
-            mAction = orig.mAction;
-            mContentType = orig.mContentType;
-            mContentAnnotations = orig.mContentAnnotations;
-            mFlags = orig.mFlags;
-            mBucketAndReason = orig.mBucketAndReason;
-            mNotificationChannelId = orig.mNotificationChannelId;
+            copyFrom(orig);
         }
 
         /**
@@ -622,6 +608,24 @@
             // which instant apps can't use anyway, so there's no need to hide them.
             return ret;
         }
+
+        private void copyFrom(Event orig) {
+            mPackage = orig.mPackage;
+            mClass = orig.mClass;
+            mInstanceId = orig.mInstanceId;
+            mTaskRootPackage = orig.mTaskRootPackage;
+            mTaskRootClass = orig.mTaskRootClass;
+            mTimeStamp = orig.mTimeStamp;
+            mEventType = orig.mEventType;
+            mConfiguration = orig.mConfiguration;
+            mShortcutId = orig.mShortcutId;
+            mAction = orig.mAction;
+            mContentType = orig.mContentType;
+            mContentAnnotations = orig.mContentAnnotations;
+            mFlags = orig.mFlags;
+            mBucketAndReason = orig.mBucketAndReason;
+            mNotificationChannelId = orig.mNotificationChannelId;
+        }
     }
 
     // Only used when creating the resulting events. Not used for reading/unparceling.
@@ -725,10 +729,14 @@
             return false;
         }
 
-        readEventFromParcel(mParcel, eventOut);
+        if (mParcel != null) {
+            readEventFromParcel(mParcel, eventOut);
+        } else {
+            eventOut.copyFrom(mEventsToWrite.get(mIndex));
+        }
 
         mIndex++;
-        if (mIndex >= mEventCount) {
+        if (mIndex >= mEventCount && mParcel != null) {
             mParcel.recycle();
             mParcel = null;
         }
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 176a181..5668944 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -142,7 +142,7 @@
 
     /**
      * The app has not be used for several days and/or is unlikely to be used for several days.
-     * Apps in this bucket will have the most restrictions, including network restrictions, except
+     * Apps in this bucket will have more restrictions, including network restrictions, except
      * during certain short periods (at a minimum, once a day) when they are allowed to execute
      * jobs, access the network, etc.
      * @see #getAppStandbyBucket()
@@ -150,6 +150,15 @@
     public static final int STANDBY_BUCKET_RARE = 40;
 
     /**
+     * The app has not be used for several days, is unlikely to be used for several days, and has
+     * been misbehaving in some manner.
+     * Apps in this bucket will have the most restrictions, including network restrictions and
+     * additional restrictions on jobs.
+     * @see #getAppStandbyBucket()
+     */
+    public static final int STANDBY_BUCKET_RESTRICTED = 45;
+
+    /**
      * The app has never been used.
      * {@hide}
      */
@@ -163,7 +172,6 @@
     /**
      * The app spent sufficient time in the old bucket without any substantial event so it reached
      * the timeout threshold to have its bucket lowered.
-     *
      * @hide
      */
     public static final int REASON_MAIN_TIMEOUT =   0x0200;
@@ -173,15 +181,25 @@
      */
     public static final int REASON_MAIN_USAGE =     0x0300;
     /**
-     * Forced by a core UID.
+     * Forced by the user/developer, either explicitly or implicitly through some action. If user
+     * action was not involved and this is purely due to the system,
+     * {@link #REASON_MAIN_FORCED_BY_SYSTEM} should be used instead.
      * @hide
      */
-    public static final int REASON_MAIN_FORCED =    0x0400;
+    public static final int REASON_MAIN_FORCED_BY_USER = 0x0400;
     /**
-     * Set by a privileged system app.
+     * Set by a privileged system app. This may be overridden by
+     * {@link #REASON_MAIN_FORCED_BY_SYSTEM} or user action.
      * @hide
      */
     public static final int REASON_MAIN_PREDICTED = 0x0500;
+    /**
+     * Forced by the system, independent of user action. If user action is involved,
+     * {@link #REASON_MAIN_FORCED_BY_USER} should be used instead. When this is used, only
+     * {@link #REASON_MAIN_FORCED_BY_SYSTEM} or user action can change the bucket.
+     * @hide
+     */
+    public static final int REASON_MAIN_FORCED_BY_SYSTEM = 0x0600;
 
     /** @hide */
     public static final int REASON_SUB_MASK = 0x00FF;
@@ -269,6 +287,26 @@
      * @hide
      */
     public static final int REASON_SUB_PREDICTED_RESTORED       = 0x0001;
+    /**
+     * The reason for restricting the app is unknown or undefined.
+     * @hide
+     */
+    public static final int REASON_SUB_RESTRICT_UNDEFINED = 0x0000;
+    /**
+     * The app was unnecessarily using system resources (battery, memory, etc) in the background.
+     * @hide
+     */
+    public static final int REASON_SUB_RESTRICT_BACKGROUND_RESOURCE_USAGE = 0x0001;
+    /**
+     * The app was deemed to be intentionally abusive.
+     * @hide
+     */
+    public static final int REASON_SUB_RESTRICT_ABUSE = 0x0002;
+    /**
+     * The app was displaying buggy behavior.
+     * @hide
+     */
+    public static final int REASON_SUB_RESTRICT_BUGGY = 0x0003;
 
 
     /** @hide */
@@ -278,6 +316,7 @@
             STANDBY_BUCKET_WORKING_SET,
             STANDBY_BUCKET_FREQUENT,
             STANDBY_BUCKET_RARE,
+            STANDBY_BUCKET_RESTRICTED,
             STANDBY_BUCKET_NEVER,
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -589,7 +628,7 @@
      * state of the app based on app usage patterns. Standby buckets determine how much an app will
      * be restricted from running background tasks such as jobs and alarms.
      * <p>Restrictions increase progressively from {@link #STANDBY_BUCKET_ACTIVE} to
-     * {@link #STANDBY_BUCKET_RARE}, with {@link #STANDBY_BUCKET_ACTIVE} being the least
+     * {@link #STANDBY_BUCKET_RESTRICTED}, with {@link #STANDBY_BUCKET_ACTIVE} being the least
      * restrictive. The battery level of the device might also affect the restrictions.
      * <p>Apps in buckets &le; {@link #STANDBY_BUCKET_ACTIVE} have no standby restrictions imposed.
      * Apps in buckets &gt; {@link #STANDBY_BUCKET_FREQUENT} may have network access restricted when
@@ -633,7 +672,8 @@
     /**
      * {@hide}
      * Changes an app's standby bucket to the provided value. The caller can only set the standby
-     * bucket for a different app than itself.
+     * bucket for a different app than itself. The caller will not be able to change an app's
+     * standby bucket if that app is in the {@link #STANDBY_BUCKET_RESTRICTED} bucket.
      * @param packageName the package name of the app to set the bucket for. A SecurityException
      *                    will be thrown if the package name is that of the caller.
      * @param bucket the standby bucket to set it to, which should be one of STANDBY_BUCKET_*.
@@ -679,7 +719,8 @@
     /**
      * {@hide}
      * Changes the app standby bucket for multiple apps at once. The Map is keyed by the package
-     * name and the value is one of STANDBY_BUCKET_*.
+     * name and the value is one of STANDBY_BUCKET_*. The caller will not be able to change an
+     * app's standby bucket if that app is in the {@link #STANDBY_BUCKET_RESTRICTED} bucket.
      * @param appBuckets a map of package name to bucket value.
      */
     @SystemApi
@@ -1016,7 +1057,24 @@
             case REASON_MAIN_DEFAULT:
                 sb.append("d");
                 break;
-            case REASON_MAIN_FORCED:
+            case REASON_MAIN_FORCED_BY_SYSTEM:
+                sb.append("s");
+                switch (standbyReason & REASON_SUB_MASK) {
+                    case REASON_SUB_RESTRICT_ABUSE:
+                        sb.append("-ra");
+                        break;
+                    case REASON_SUB_RESTRICT_BACKGROUND_RESOURCE_USAGE:
+                        sb.append("-rbru");
+                        break;
+                    case REASON_SUB_RESTRICT_BUGGY:
+                        sb.append("-rb");
+                        break;
+                    case REASON_SUB_RESTRICT_UNDEFINED:
+                        sb.append("-r");
+                        break;
+                }
+                break;
+            case REASON_MAIN_FORCED_BY_USER:
                 sb.append("f");
                 break;
             case REASON_MAIN_PREDICTED:
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index b1b6f0d..e751354 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -1222,6 +1222,7 @@
             if (mService != null) {
                 return mService.factoryReset();
             }
+            Log.e(TAG, "factoryReset(): IBluetooth Service is null");
             SystemProperties.set("persist.bluetooth.factoryreset", "true");
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
@@ -1239,7 +1240,7 @@
      */
     @UnsupportedAppUsage
     @RequiresPermission(Manifest.permission.BLUETOOTH)
-    public @NonNull ParcelUuid[] getUuids() {
+    public @Nullable ParcelUuid[] getUuids() {
         if (getState() != STATE_ON) {
             return null;
         }
@@ -2670,6 +2671,9 @@
         } else if (profile == BluetoothProfile.PAN) {
             BluetoothPan pan = new BluetoothPan(context, listener);
             return true;
+        } else if (profile == BluetoothProfile.PBAP) {
+            BluetoothPbap pbap = new BluetoothPbap(context, listener);
+            return true;
         } else if (profile == BluetoothProfile.HEALTH) {
             Log.e(TAG, "getProfileProxy(): BluetoothHealth is deprecated");
             return false;
@@ -2742,6 +2746,10 @@
                 BluetoothPan pan = (BluetoothPan) proxy;
                 pan.close();
                 break;
+            case BluetoothProfile.PBAP:
+                BluetoothPbap pbap = (BluetoothPbap) proxy;
+                pbap.close();
+                break;
             case BluetoothProfile.GATT:
                 BluetoothGatt gatt = (BluetoothGatt) proxy;
                 gatt.close();
diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java
index e9b0be2..a923be6 100644
--- a/core/java/android/bluetooth/BluetoothHidDevice.java
+++ b/core/java/android/bluetooth/BluetoothHidDevice.java
@@ -16,8 +16,12 @@
 
 package android.bluetooth;
 
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
 import android.content.Context;
 import android.os.Binder;
 import android.os.IBinder;
@@ -36,6 +40,7 @@
  */
 public final class BluetoothHidDevice implements BluetoothProfile {
     private static final String TAG = BluetoothHidDevice.class.getSimpleName();
+    private static final boolean DBG = false;
 
     /**
      * Intent used to broadcast the change in connection state of the Input Host profile.
@@ -682,4 +687,62 @@
 
         return result;
     }
+
+    /**
+     * Connects Hid Device if connectionPolicy is {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}
+     * and disconnects Hid device if connectionPolicy is
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}.
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of:
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+     * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy determines whether hid device should be connected or disconnected
+     * @return true if hid device is connected or disconnected, false otherwise
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
+            @ConnectionPolicy int connectionPolicy) {
+        log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+        try {
+            final IBluetoothHidDevice service = getService();
+            if (service != null && isEnabled()
+                    && isValidDevice(device)) {
+                if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                        && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+                    return false;
+                }
+                return service.setConnectionPolicy(device, connectionPolicy);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return false;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return false;
+        }
+    }
+
+    private boolean isEnabled() {
+        if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
+        return false;
+    }
+
+    private boolean isValidDevice(BluetoothDevice device) {
+        if (device == null) return false;
+
+        if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
+        return false;
+    }
+
+    private static void log(String msg) {
+        if (DBG) {
+            Log.d(TAG, msg);
+        }
+    }
 }
diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java
index 917e7fa..4674706 100644
--- a/core/java/android/bluetooth/BluetoothMap.java
+++ b/core/java/android/bluetooth/BluetoothMap.java
@@ -17,7 +17,10 @@
 package android.bluetooth;
 
 import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -35,21 +38,35 @@
  *
  * @hide
  */
+@SystemApi
 public final class BluetoothMap implements BluetoothProfile {
 
     private static final String TAG = "BluetoothMap";
     private static final boolean DBG = true;
     private static final boolean VDBG = false;
 
+    /** @hide */
+    @SuppressLint("ActionValue")
+    @SystemApi
     public static final String ACTION_CONNECTION_STATE_CHANGED =
             "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED";
 
-    /** There was an error trying to obtain the state */
+    /**
+     * There was an error trying to obtain the state
+     *
+     * @hide
+     */
     public static final int STATE_ERROR = -1;
 
+    /** @hide */
     public static final int RESULT_FAILURE = 0;
+    /** @hide */
     public static final int RESULT_SUCCESS = 1;
-    /** Connection canceled before completion. */
+    /**
+     * Connection canceled before completion.
+     *
+     * @hide
+     */
     public static final int RESULT_CANCELED = 2;
 
     private BluetoothAdapter mAdapter;
@@ -71,6 +88,7 @@
         mProfileConnector.connect(context, listener);
     }
 
+    @SuppressLint("GenericException")
     protected void finalize() throws Throwable {
         try {
             close();
@@ -84,6 +102,8 @@
      * Other public functions of BluetoothMap will return default error
      * results once close() has been called. Multiple invocations of close()
      * are ok.
+     *
+     * @hide
      */
     public synchronized void close() {
         mProfileConnector.disconnect();
@@ -98,6 +118,8 @@
      *
      * @return One of the STATE_ return codes, or STATE_ERROR if this proxy object is currently not
      * connected to the Map service.
+     *
+     * @hide
      */
     public int getState() {
         if (VDBG) log("getState()");
@@ -120,6 +142,8 @@
      *
      * @return The remote Bluetooth device, or null if not in connected or connecting state, or if
      * this proxy object is not connected to the Map service.
+     *
+     * @hide
      */
     public BluetoothDevice getClient() {
         if (VDBG) log("getClient()");
@@ -141,6 +165,8 @@
      * Returns true if the specified Bluetooth device is connected.
      * Returns false if not connected, or if this proxy object is not
      * currently connected to the Map service.
+     *
+     * @hide
      */
     public boolean isConnected(BluetoothDevice device) {
         if (VDBG) log("isConnected(" + device + ")");
@@ -161,6 +187,8 @@
     /**
      * Initiate connection. Initiation of outgoing connections is not
      * supported for MAP server.
+     *
+     * @hide
      */
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")" + "not supported for MAPS");
@@ -172,6 +200,8 @@
      *
      * @param device Remote Bluetooth Device
      * @return false on error, true otherwise
+     *
+     * @hide
      */
     @UnsupportedAppUsage
     public boolean disconnect(BluetoothDevice device) {
@@ -196,6 +226,8 @@
      * devices. It tries to err on the side of false positives.
      *
      * @return True if this device might support Map.
+     *
+     * @hide
      */
     public static boolean doesClassMatchSink(BluetoothClass btClass) {
         // TODO optimize the rule
@@ -214,8 +246,11 @@
      * Get the list of connected devices. Currently at most one.
      *
      * @return list of connected devices
+     *
+     * @hide
      */
-    public List<BluetoothDevice> getConnectedDevices() {
+    @SystemApi
+    public @NonNull List<BluetoothDevice> getConnectedDevices() {
         if (DBG) log("getConnectedDevices()");
         final IBluetoothMap service = getService();
         if (service != null && isEnabled()) {
@@ -234,6 +269,8 @@
      * Get the list of devices matching specified states. Currently at most one.
      *
      * @return list of matching devices
+     *
+     * @hide
      */
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (DBG) log("getDevicesMatchingStates()");
@@ -254,6 +291,8 @@
      * Get connection state of device
      *
      * @return device connection state
+     *
+     * @hide
      */
     public int getConnectionState(BluetoothDevice device) {
         if (DBG) log("getConnectionState(" + device + ")");
@@ -301,7 +340,7 @@
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
-    public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
+    public boolean setConnectionPolicy(@Nullable BluetoothDevice device, int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
         final IBluetoothMap service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
@@ -349,7 +388,7 @@
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.BLUETOOTH)
-    public int getConnectionPolicy(BluetoothDevice device) {
+    public int getConnectionPolicy(@Nullable BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
         final IBluetoothMap service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java
index 42f27f2..ec63fd0 100644
--- a/core/java/android/bluetooth/BluetoothPan.java
+++ b/core/java/android/bluetooth/BluetoothPan.java
@@ -16,9 +16,11 @@
 
 package android.bluetooth;
 
+import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SuppressLint;
@@ -28,6 +30,7 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.CloseGuard;
 import android.util.Log;
 
 import java.lang.annotation.Retention;
@@ -48,10 +51,11 @@
  * @hide
  */
 @SystemApi
-public final class BluetoothPan implements BluetoothProfile {
+public final class BluetoothPan implements BluetoothProfile, AutoCloseable {
     private static final String TAG = "BluetoothPan";
     private static final boolean DBG = true;
     private static final boolean VDBG = false;
+    private CloseGuard mCloseGuard;
 
     /**
      * Intent used to broadcast the change in connection state of the Pan
@@ -164,10 +168,15 @@
         mAdapter = BluetoothAdapter.getDefaultAdapter();
         mContext = context;
         mProfileConnector.connect(context, listener);
+        mCloseGuard = new CloseGuard();
+        mCloseGuard.open("close");
     }
 
-    @UnsupportedAppUsage
-    /*package*/ void close() {
+    /**
+     * Closes the connection to the service and unregisters callbacks
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public void close() {
         if (VDBG) log("close()");
         mProfileConnector.disconnect();
     }
@@ -176,8 +185,11 @@
         return mProfileConnector.getService();
     }
 
-
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
     protected void finalize() {
+        if (mCloseGuard != null) {
+            mCloseGuard.warnIfOpen();
+        }
         close();
     }
 
@@ -256,6 +268,41 @@
     }
 
     /**
+     * Set connection policy of the profile
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
+     * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy is the connection policy to set to for this profile
+     * @return true if connectionPolicy is set, false on error
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
+            @ConnectionPolicy int connectionPolicy) {
+        if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+        try {
+            final IBluetoothPan service = getService();
+            if (service != null && isEnabled()
+                    && isValidDevice(device)) {
+                if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                        && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+                    return false;
+                }
+                return service.setConnectionPolicy(device, connectionPolicy);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return false;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return false;
+        }
+    }
+
+    /**
      * {@inheritDoc}
      */
     @Override
@@ -279,6 +326,7 @@
      * @hide
      */
     @Override
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
         final IBluetoothPan service = getService();
@@ -298,6 +346,7 @@
      * {@inheritDoc}
      */
     @Override
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
     public int getConnectionState(@Nullable BluetoothDevice device) {
         if (VDBG) log("getState(" + device + ")");
         final IBluetoothPan service = getService();
@@ -318,6 +367,7 @@
      *
      * @param value is whether to enable or disable bluetooth tethering
      */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
     public void setBluetoothTethering(boolean value) {
         String pkgName = mContext.getOpPackageName();
         if (DBG) log("setBluetoothTethering(" + value + "), calling package:" + pkgName);
@@ -336,6 +386,7 @@
      *
      * @return true if tethering is on, false if not or some error occurred
      */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
     public boolean isTetheringOn() {
         if (VDBG) log("isTetheringOn()");
         final IBluetoothPan service = getService();
diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java
index 948885e..e07ca52 100644
--- a/core/java/android/bluetooth/BluetoothPbap.java
+++ b/core/java/android/bluetooth/BluetoothPbap.java
@@ -274,15 +274,15 @@
     }
 
     /**
-     * Pbap does not store connection policy, so this function only disconnects Pbap if
-     * connectionPolicy is CONNECTION_POLICY_FORBIDDEN.
+     * Pbap does not store connection policy, so this function only disconnects pbap if
+     * connectionPolicy is {@link #CONNECTION_POLICY_FORBIDDEN}.
      *
      * <p> The device should already be paired.
      * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
      * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
      *
      * @param device Paired bluetooth device
-     * @param connectionPolicy is the connection policy to set to for this profile
+     * @param connectionPolicy determines whether to disconnect the device
      * @return true if pbap is successfully disconnected, false otherwise
      * @hide
      */
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 638e6de..7538df8 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -146,7 +146,7 @@
      *
      * @hide
      */
-    @UnsupportedAppUsage
+    @SystemApi
     int A2DP_SINK = 11;
 
     /**
@@ -154,7 +154,7 @@
      *
      * @hide
      */
-    @UnsupportedAppUsage
+    @SystemApi
     int AVRCP_CONTROLLER = 12;
 
     /**
@@ -169,6 +169,7 @@
      *
      * @hide
      */
+    @SystemApi
     int HEADSET_CLIENT = 16;
 
     /**
@@ -176,6 +177,7 @@
      *
      * @hide
      */
+    @SystemApi
     int PBAP_CLIENT = 17;
 
     /**
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 3107c63..d4b5b1a 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -269,7 +269,7 @@
     @SystemApi
     @TestApi
     @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
-    public boolean isDeviceAssociated(
+    public boolean isDeviceAssociatedForWifiConnection(
             @NonNull String packageName,
             @NonNull MacAddress macAddress,
             @NonNull UserHandle user) {
@@ -280,7 +280,7 @@
         Objects.requireNonNull(macAddress, "mac address cannot be null");
         Objects.requireNonNull(user, "user cannot be null");
         try {
-            return mService.isDeviceAssociated(
+            return mService.isDeviceAssociatedForWifiConnection(
                     packageName, macAddress.toString(), user.getIdentifier());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index 2e1ff0b..b323f58 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -40,5 +40,6 @@
     boolean hasNotificationAccess(in ComponentName component);
     PendingIntent requestNotificationAccess(in ComponentName component);
 
-    boolean isDeviceAssociated(in String packageName, in String macAddress, int userId);
+    boolean isDeviceAssociatedForWifiConnection(in String packageName, in String macAddress,
+        int userId);
 }
diff --git a/core/java/android/content/ApexContext.java b/core/java/android/content/ApexContext.java
new file mode 100644
index 0000000..fe5cedc
--- /dev/null
+++ b/core/java/android/content/ApexContext.java
@@ -0,0 +1,98 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Environment;
+import android.os.UserHandle;
+
+import java.io.File;
+import java.util.Objects;
+
+/**
+ * Provides information about the environment for a particular APEX.
+ *
+ * @hide
+ */
+@SystemApi
+public class ApexContext {
+
+    private static final String APEX_DATA = "apexdata";
+
+    /**
+     * Returns an ApexContext instance for the APEX with the provided {@code apexModuleName}.
+     *
+     * <p>To preserve the safety and integrity of APEX modules, you must only obtain the ApexContext
+     * for your specific APEX, and you <em>must never</em> attempt to obtain an ApexContext for
+     * another APEX.  Any coordination between APEXs must be performed through well-defined
+     * interfaces; attempting to directly read or write raw files belonging to another APEX will
+     * violate the hermetic storage requirements placed upon each module.
+     */
+    @NonNull
+    public static ApexContext getApexContext(@NonNull String apexModuleName) {
+        Objects.requireNonNull(apexModuleName, "apexModuleName cannot be null");
+        //TODO(b/141148175): Check that apexModuleName is an actual APEX name
+        return new ApexContext(apexModuleName);
+    }
+
+    private final String mApexModuleName;
+
+    private ApexContext(String apexModuleName) {
+        mApexModuleName = apexModuleName;
+    }
+
+    /**
+     * Returns the data directory for the APEX in device-encrypted, non-user-specific storage.
+     *
+     * <p>This directory is automatically created by the system for installed APEXes, and its
+     * contents will be rolled back if the APEX is rolled back.
+     */
+    @NonNull
+    public File getDeviceProtectedDataDir() {
+        return Environment.buildPath(
+                Environment.getDataMiscDirectory(), APEX_DATA, mApexModuleName);
+    }
+
+    /**
+     * Returns the data directory for the APEX in device-encrypted, user-specific storage for the
+     * specified {@code user}.
+     *
+     * <p>This directory is automatically created by the system for each user and for each installed
+     * APEX, and its contents will be rolled back if the APEX is rolled back.
+     */
+    @NonNull
+    public File getDeviceProtectedDataDirForUser(@NonNull UserHandle user) {
+        return Environment.buildPath(
+                Environment.getDataMiscDeDirectory(user.getIdentifier()), APEX_DATA,
+                mApexModuleName);
+    }
+
+    /**
+     * Returns the data directory for the APEX in credential-encrypted, user-specific storage for
+     * the specified {@code user}.
+     *
+     * <p>This directory is automatically created by the system for each user and for each installed
+     * APEX, and its contents will be rolled back if the APEX is rolled back.
+     */
+    @NonNull
+    public File getCredentialProtectedDataDirForUser(@NonNull UserHandle user) {
+        return Environment.buildPath(
+                Environment.getDataMiscCeDirectory(user.getIdentifier()), APEX_DATA,
+                mApexModuleName);
+    }
+}
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 393d488..c271e3c 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -27,6 +27,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.app.AppOpsManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.PackageManager;
@@ -942,7 +944,18 @@
         return null;
     }
 
-    /** {@hide} */
+    /**
+     * Return the package name of the caller that initiated the request being
+     * processed on the current thread. The returned package will have
+     * <em>not</em> been verified to belong to the calling UID. Returns
+     * {@code null} if not currently processing a request.
+     * <p>
+     * This will always return {@code null} when processing
+     * {@link #getType(Uri)} or {@link #getStreamTypes(Uri, String)} requests.
+     *
+     * @see Binder#getCallingUid()
+     * @see Context#grantUriPermission(String, Uri, int)
+     */
     public final @Nullable String getCallingPackageUnchecked() {
         final Pair<String, String> pkg = mCallingPackage.get();
         if (pkg != null) {
@@ -952,7 +965,14 @@
         return null;
     }
 
-    /** {@hide} */
+    /**
+     * Called whenever the value of {@link #getCallingPackage()} changes, giving
+     * the provider an opportunity to invalidate any security related caching it
+     * may be performing.
+     * <p>
+     * This typically happens when a {@link ContentProvider} makes a nested call
+     * back into itself when already processing a call from a remote process.
+     */
     public void onCallingPackageChanged() {
     }
 
@@ -1390,8 +1410,11 @@
      * @param uri The URI to query. This will be the full URI sent by the client.
      * @param projection The list of columns to put into the cursor.
      *            If {@code null} provide a default set of columns.
-     * @param queryArgs A Bundle containing all additional information necessary for the query.
-     *            Values in the Bundle may include SQL style arguments.
+     * @param queryArgs A Bundle containing additional information necessary for
+     *            the operation. Arguments may include SQL style arguments, such
+     *            as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+     *            the documentation for each individual provider will indicate
+     *            which arguments they support.
      * @param cancellationSignal A signal to cancel the operation in progress,
      *            or {@code null}.
      * @return a Cursor or {@code null}.
@@ -1525,8 +1548,24 @@
         return false;
     }
 
-    /** {@hide} */
+    /**
+     * Perform a detailed internal check on a {@link Uri} to determine if a UID
+     * is able to access it with specific mode flags.
+     * <p>
+     * This method is typically used when the provider implements more dynamic
+     * access controls that cannot be expressed with {@code <path-permission>}
+     * style static rules.
+     *
+     * @param uri the {@link Uri} to perform an access check on.
+     * @param uid the UID to check the permission for.
+     * @param modeFlags the access flags to use for the access check, such as
+     *            {@link Intent#FLAG_GRANT_READ_URI_PERMISSION}.
+     * @return {@link PackageManager#PERMISSION_GRANTED} if access is allowed,
+     *         otherwise {@link PackageManager#PERMISSION_DENIED}.
+     * @hide
+     */
     @Override
+    @SystemApi
     public int checkUriPermission(@NonNull Uri uri, int uid, @Intent.AccessUriMode int modeFlags) {
         return PackageManager.PERMISSION_DENIED;
     }
@@ -1574,9 +1613,14 @@
      *
      * @param uri The content:// URI of the insertion request.
      * @param values A set of column_name/value pairs to add to the database.
-     * @param extras A Bundle containing all additional information necessary
-     *            for the insert.
+     * @param extras A Bundle containing additional information necessary for
+     *            the operation. Arguments may include SQL style arguments, such
+     *            as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+     *            the documentation for each individual provider will indicate
+     *            which arguments they support.
      * @return The URI for the newly inserted item.
+     * @throws IllegalArgumentException if the provider doesn't support one of
+     *             the requested Bundle arguments.
      */
     @Override
     public @Nullable Uri insert(@NonNull Uri uri, @Nullable ContentValues values,
@@ -1653,10 +1697,13 @@
      *
      * @param uri The full URI to query, including a row ID (if a specific
      *            record is requested).
-     * @param extras A Bundle containing all additional information necessary
-     *            for the delete. Values in the Bundle may include SQL style
-     *            arguments.
-     * @return The number of rows affected.
+     * @param extras A Bundle containing additional information necessary for
+     *            the operation. Arguments may include SQL style arguments, such
+     *            as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+     *            the documentation for each individual provider will indicate
+     *            which arguments they support.
+     * @throws IllegalArgumentException if the provider doesn't support one of
+     *             the requested Bundle arguments.
      * @throws SQLException
      */
     @Override
@@ -1699,10 +1746,14 @@
      * @param uri The URI to query. This can potentially have a record ID if
      *            this is an update request for a specific record.
      * @param values A set of column_name/value pairs to update in the database.
-     * @param extras A Bundle containing all additional information necessary
-     *            for the update. Values in the Bundle may include SQL style
-     *            arguments.
+     * @param extras A Bundle containing additional information necessary for
+     *            the operation. Arguments may include SQL style arguments, such
+     *            as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+     *            the documentation for each individual provider will indicate
+     *            which arguments they support.
      * @return the number of rows affected.
+     * @throws IllegalArgumentException if the provider doesn't support one of
+     *             the requested Bundle arguments.
      */
     @Override
     public int update(@NonNull Uri uri, @Nullable ContentValues values,
@@ -2476,6 +2527,16 @@
     }
 
     /**
+     * Returns the user associated with the given URI.
+     *
+     * @hide
+     */
+    @TestApi
+    public @NonNull static UserHandle getUserHandleFromUri(@NonNull Uri uri) {
+        return UserHandle.of(getUserIdFromUri(uri, Process.myUserHandle().getIdentifier()));
+    }
+
+    /**
      * Removes userId part from authority string. Expects format:
      * userId@some.authority
      * If there is no userId in the authority, it symply returns the argument
diff --git a/core/java/android/content/ContentProviderOperation.java b/core/java/android/content/ContentProviderOperation.java
index 93f4287..494d2aea 100644
--- a/core/java/android/content/ContentProviderOperation.java
+++ b/core/java/android/content/ContentProviderOperation.java
@@ -18,7 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
diff --git a/core/java/android/content/ContentProviderResult.java b/core/java/android/content/ContentProviderResult.java
index 11dda83..4fb1ddb 100644
--- a/core/java/android/content/ContentProviderResult.java
+++ b/core/java/android/content/ContentProviderResult.java
@@ -36,7 +36,7 @@
     public final @Nullable Uri uri;
     public final @Nullable Integer count;
     public final @Nullable Bundle extras;
-    public final @Nullable Exception exception;
+    public final @Nullable Throwable exception;
 
     public ContentProviderResult(@NonNull Uri uri) {
         this(Objects.requireNonNull(uri), null, null, null);
@@ -50,12 +50,12 @@
         this(null, null, Objects.requireNonNull(extras), null);
     }
 
-    public ContentProviderResult(@NonNull Exception exception) {
+    public ContentProviderResult(@NonNull Throwable exception) {
         this(null, null, null, exception);
     }
 
     /** {@hide} */
-    public ContentProviderResult(Uri uri, Integer count, Bundle extras, Exception exception) {
+    public ContentProviderResult(Uri uri, Integer count, Bundle extras, Throwable exception) {
         this.uri = uri;
         this.count = count;
         this.extras = extras;
@@ -79,7 +79,7 @@
             extras = null;
         }
         if (source.readInt() != 0) {
-            exception = (Exception) ParcelableException.readFromParcel(source);
+            exception = ParcelableException.readFromParcel(source);
         } else {
             exception = null;
         }
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 1d3c650..6cd1cd3 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -984,7 +984,11 @@
      *         retrieve.
      * @param projection A list of which columns to return. Passing null will
      *         return all columns, which is inefficient.
-     * @param queryArgs A Bundle containing any arguments to the query.
+     * @param queryArgs A Bundle containing additional information necessary for
+     *            the operation. Arguments may include SQL style arguments, such
+     *            as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+     *            the documentation for each individual provider will indicate
+     *            which arguments they support.
      * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
      * If the operation is canceled, then {@link OperationCanceledException} will be thrown
      * when the query is executed.
@@ -1925,9 +1929,15 @@
      * @param url The URL of the table to insert into.
      * @param values The initial values for the newly inserted row. The key is the column name for
      *               the field. Passing an empty ContentValues will create an empty row.
-     * @param extras A Bundle containing all additional information necessary for the insert.
+     * @param extras A Bundle containing additional information necessary for
+     *            the operation. Arguments may include SQL style arguments, such
+     *            as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+     *            the documentation for each individual provider will indicate
+     *            which arguments they support.
      * @return the URL of the newly created row. May return <code>null</code> if the underlying
      *         content provider returns <code>null</code>, or if it crashes.
+     * @throws IllegalArgumentException if the provider doesn't support one of
+     *             the requested Bundle arguments.
      */
     @Override
     public final @Nullable Uri insert(@RequiresPermission.Write @NonNull Uri url,
@@ -2061,9 +2071,14 @@
      * If the content provider supports transactions, the deletion will be atomic.
      *
      * @param url The URL of the row to delete.
-     * @param extras A Bundle containing all additional information necessary for the delete.
-     *            Values in the Bundle may include SQL style arguments.
+     * @param extras A Bundle containing additional information necessary for
+     *            the operation. Arguments may include SQL style arguments, such
+     *            as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+     *            the documentation for each individual provider will indicate
+     *            which arguments they support.
      * @return The number of rows deleted.
+     * @throws IllegalArgumentException if the provider doesn't support one of
+     *             the requested Bundle arguments.
      */
     @Override
     public final int delete(@RequiresPermission.Write @NonNull Uri url, @Nullable Bundle extras) {
@@ -2121,10 +2136,15 @@
      * @param uri The URI to modify.
      * @param values The new field values. The key is the column name for the field.
                      A null value will remove an existing field value.
-     * @param extras A Bundle containing all additional information necessary for the update.
-     *            Values in the Bundle may include SQL style arguments.
+     * @param extras A Bundle containing additional information necessary for
+     *            the operation. Arguments may include SQL style arguments, such
+     *            as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+     *            the documentation for each individual provider will indicate
+     *            which arguments they support.
      * @return the number of rows updated.
      * @throws NullPointerException if uri or values are null
+     * @throws IllegalArgumentException if the provider doesn't support one of
+     *             the requested Bundle arguments.
      */
     @Override
     public final int update(@RequiresPermission.Write @NonNull Uri uri,
@@ -3851,15 +3871,47 @@
         }
     }
 
+    /**
+     * Decode a path generated by {@link #encodeToFile(Uri)} back into
+     * the original {@link Uri}.
+     * <p>
+     * This is used to offer a way to intercept filesystem calls in
+     * {@link ContentProvider} unaware code and redirect them to a
+     * {@link ContentProvider} when they attempt to use {@code _DATA} columns
+     * that are otherwise deprecated.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static @NonNull Uri decodeFromFile(@NonNull File file) {
+        return translateDeprecatedDataPath(file.getAbsolutePath());
+    }
+
+    /**
+     * Encode a {@link Uri} into an opaque filesystem path which can then be
+     * resurrected by {@link #decodeFromFile(File)}.
+     * <p>
+     * This is used to offer a way to intercept filesystem calls in
+     * {@link ContentProvider} unaware code and redirect them to a
+     * {@link ContentProvider} when they attempt to use {@code _DATA} columns
+     * that are otherwise deprecated.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static @NonNull File encodeToFile(@NonNull Uri uri) {
+        return new File(translateDeprecatedDataPath(uri));
+    }
+
     /** {@hide} */
-    public static Uri translateDeprecatedDataPath(String path) {
+    public static @NonNull Uri translateDeprecatedDataPath(@NonNull String path) {
         final String ssp = "//" + path.substring(DEPRECATE_DATA_PREFIX.length());
         return Uri.parse(new Uri.Builder().scheme(SCHEME_CONTENT)
                 .encodedOpaquePart(ssp).build().toString());
     }
 
     /** {@hide} */
-    public static String translateDeprecatedDataPath(Uri uri) {
+    public static @NonNull String translateDeprecatedDataPath(@NonNull Uri uri) {
         return DEPRECATE_DATA_PREFIX + uri.getEncodedSchemeSpecificPart().substring(2);
     }
 }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index a28868e..ebc5e0e 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -30,6 +30,7 @@
 import android.annotation.StringRes;
 import android.annotation.StyleRes;
 import android.annotation.StyleableRes;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.annotation.UserIdInt;
@@ -1825,8 +1826,9 @@
      * @throws ActivityNotFoundException &nbsp;
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
     @SystemApi
+    @TestApi
     public void startActivityAsUser(@RequiresPermission @NonNull Intent intent,
             @NonNull UserHandle user) {
         throw new RuntimeException("Not implemented. Must override in a subclass.");
@@ -1873,7 +1875,7 @@
      * @throws ActivityNotFoundException &nbsp;
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
     @UnsupportedAppUsage
     public void startActivityAsUser(@RequiresPermission Intent intent, @Nullable Bundle options,
             UserHandle userId) {
@@ -1979,7 +1981,7 @@
      * @see #startActivities(Intent[])
      * @see PackageManager#resolveActivity
      */
-    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
     public int startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
         throw new RuntimeException("Not implemented. Must override in a subclass.");
     }
@@ -3240,15 +3242,40 @@
     }
 
     /**
-     * Same as {@link #bindService(Intent, ServiceConnection, int)}, but with an explicit userHandle
-     * argument for use by system server and other multi-user aware code.
-     * @hide
+     * Binds to a service in the given {@code user} in the same manner as
+     * {@link #bindService(Intent, ServiceConnection, int)}.
+     *
+     * <p>If the given {@code user} is in the same profile group and the target package is the
+     * same as the caller, {@code android.Manifest.permission.INTERACT_ACROSS_PROFILES} is
+     * sufficient. Otherwise, requires {@code android.Manifest.permission.INTERACT_ACROSS_USERS}
+     * for interacting with other users.
+     *
+     * @param service Identifies the service to connect to.  The Intent must
+     *      specify an explicit component name.
+     * @param conn Receives information as the service is started and stopped.
+     *      This must be a valid ServiceConnection object; it must not be null.
+     * @param flags Operation options for the binding.  May be 0,
+     *          {@link #BIND_AUTO_CREATE}, {@link #BIND_DEBUG_UNBIND},
+     *          {@link #BIND_NOT_FOREGROUND}, {@link #BIND_ABOVE_CLIENT},
+     *          {@link #BIND_ALLOW_OOM_MANAGEMENT}, {@link #BIND_WAIVE_PRIORITY}.
+     *          {@link #BIND_IMPORTANT}, or
+     *          {@link #BIND_ADJUST_WITH_ACTIVITY}.
+     * @return {@code true} if the system is in the process of bringing up a
+     *         service that your client has permission to bind to; {@code false}
+     *         if the system couldn't find the service. If this value is {@code true}, you
+     *         should later call {@link #unbindService} to release the
+     *         connection.
+     *
+     * @throws SecurityException if the client does not have the required permission to bind.
      */
-    @SystemApi
     @SuppressWarnings("unused")
-    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
-    public boolean bindServiceAsUser(@RequiresPermission Intent service, ServiceConnection conn,
-            int flags, UserHandle user) {
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.INTERACT_ACROSS_USERS,
+            android.Manifest.permission.INTERACT_ACROSS_PROFILES
+    })
+    public boolean bindServiceAsUser(
+            @NonNull @RequiresPermission Intent service, @NonNull ServiceConnection conn, int flags,
+            @NonNull UserHandle user) {
         throw new RuntimeException("Not implemented. Must override in a subclass.");
     }
 
@@ -3391,6 +3418,7 @@
             TELEPHONY_SUBSCRIPTION_SERVICE,
             CARRIER_CONFIG_SERVICE,
             EUICC_SERVICE,
+            MMS_SERVICE,
             TELECOM_SERVICE,
             CLIPBOARD_SERVICE,
             INPUT_METHOD_SERVICE,
@@ -3452,6 +3480,7 @@
             //@hide: TIME_DETECTOR_SERVICE,
             //@hide: TIME_ZONE_DETECTOR_SERVICE,
             PERMISSION_SERVICE,
+            LIGHTS_SERVICE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ServiceName {}
@@ -3587,6 +3616,8 @@
      * @see android.telephony.CarrierConfigManager
      * @see #EUICC_SERVICE
      * @see android.telephony.euicc.EuiccManager
+     * @see #MMS_SERVICE
+     * @see android.telephony.MmsManager
      * @see #INPUT_METHOD_SERVICE
      * @see android.view.inputmethod.InputMethodManager
      * @see #UI_MODE_SERVICE
@@ -3901,6 +3932,7 @@
      */
     @SystemApi
     @TestApi
+    @SuppressLint("ServiceName")
     public static final String STATUS_BAR_SERVICE = "statusbar";
 
     /**
@@ -3925,10 +3957,12 @@
 
     /**
      * Use with {@link android.os.ServiceManager.getService()} to retrieve a
-     * {@link NetworkStackClient} IBinder for communicating with the network stack
+     * {@link INetworkStackConnector} IBinder for communicating with the network stack
      * @hide
      * @see NetworkStackClient
      */
+    @SystemApi
+    @TestApi
     public static final String NETWORK_STACK_SERVICE = "network_stack";
 
     /**
@@ -3951,6 +3985,16 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve a {@link
+     * android.net.ConnectivityDiagnosticsManager} for performing network connectivity diagnostics
+     * as well as receiving network connectivity information from the system.
+     *
+     * @see #getSystemService(String)
+     * @see android.net.ConnectivityDiagnosticsManager
+     */
+    public static final String CONNECTIVITY_DIAGNOSTICS_SERVICE = "connectivity_diagnostics";
+
+    /**
+     * Use with {@link #getSystemService(String)} to retrieve a {@link
      * android.net.TestNetworkManager} for building TUNs and limited-use Networks
      *
      * @see #getSystemService(String)
@@ -3993,6 +4037,7 @@
     public static final String NETWORK_STATS_SERVICE = "netstats";
     /** {@hide} */
     @SystemApi
+    @SuppressLint("ServiceName")
     public static final String NETWORK_POLICY_SERVICE = "netpolicy";
     /** {@hide} */
     public static final String NETWORK_WATCHLIST_SERVICE = "network_watchlist";
@@ -4016,6 +4061,8 @@
      * @see android.net.wifi.WifiCondManager
      * @hide
      */
+    @SystemApi
+    @SuppressLint("ServiceName")
     public static final String WIFI_COND_SERVICE = "wificond";
 
     /**
@@ -4262,6 +4309,15 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link android.telephony.MmsManager} to send/receive MMS messages.
+     *
+     * @see #getSystemService(String)
+     * @see android.telephony.MmsManager
+     */
+    public static final String MMS_SERVICE = "mms";
+
+    /**
+     * Use with {@link #getSystemService(String)} to retrieve a
      * {@link android.content.ClipboardManager} for accessing and modifying
      * the contents of the global clipboard.
      *
@@ -4338,6 +4394,7 @@
      * @see #getSystemService(String)
      */
     @TestApi
+    @SuppressLint("ServiceName")  // TODO: This should be renamed to CONTENT_CAPTURE_SERVICE
     public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture";
 
     /**
@@ -4433,6 +4490,7 @@
      * @hide
      */
     @TestApi
+    @SuppressLint("ServiceName")  // TODO: This should be renamed to DEVICE_IDLE_SERVICE
     public static final String DEVICE_IDLE_CONTROLLER = "deviceidle";
 
     /**
@@ -4442,6 +4500,7 @@
      * @hide
      */
     @TestApi
+    @SuppressLint("ServiceName")  // TODO: This should be renamed to POWER_WHITELIST_SERVICE
     public static final String POWER_WHITELIST_MANAGER = "power_whitelist";
 
     /**
@@ -4972,6 +5031,14 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve an
+     * {@link android.os.SystemConfigManager}.
+     * @hide
+     */
+    @SystemApi
+    public static final String SYSTEM_CONFIG_SERVICE = "system_config";
+
+    /**
+     * Use with {@link #getSystemService(String)} to retrieve an
      * {@link android.telephony.ims.RcsMessageManager}.
      * @hide
      */
@@ -5012,6 +5079,7 @@
      * @hide
      */
     @SystemApi
+    @SuppressLint("ServiceName")
     public static final String BATTERY_STATS_SERVICE = "batterystats";
 
     /**
@@ -5054,6 +5122,15 @@
     public static final String FILE_INTEGRITY_SERVICE = "file_integrity";
 
     /**
+     * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link android.hardware.lights.LightsManager} for controlling device lights.
+     *
+     * @see #getSystemService(String)
+     * @hide
+     */
+    public static final String LIGHTS_SERVICE = "lights";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
@@ -5663,6 +5740,63 @@
     public abstract Context createDisplayContext(@NonNull Display display);
 
     /**
+     * Creates a Context for a non-activity window.
+     *
+     * <p>
+     * A window context is a context that can be used to add non-activity windows, such as
+     * {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY}. A window context
+     * must be created from a context that has an associated {@link Display}, such as
+     * {@link android.app.Activity Activity} or a context created with
+     * {@link #createDisplayContext(Display)}.
+     *
+     * <p>
+     * The window context is created with the appropriate {@link Configuration} for the area of the
+     * display that the windows created with it can occupy; it must be used when
+     * {@link android.view.LayoutInflater inflating} views, such that they can be inflated with
+     * proper {@link Resources}.
+     *
+     * Below is a sample code to <b>add an application overlay window on the primary display:<b/>
+     * <pre class="prettyprint">
+     * ...
+     * final DisplayManager dm = anyContext.getSystemService(DisplayManager.class);
+     * final Display primaryDisplay = dm.getDisplay(DEFAULT_DISPLAY);
+     * final Context windowContext = anyContext.createDisplayContext(primaryDisplay)
+     *         .createWindowContext(TYPE_APPLICATION_OVERLAY);
+     * final View overlayView = Inflater.from(windowContext).inflate(someLayoutXml, null);
+     *
+     * // WindowManager.LayoutParams initialization
+     * ...
+     * mParams.type = TYPE_APPLICATION_OVERLAY;
+     * ...
+     *
+     * mWindowContext.getSystemService(WindowManager.class).addView(overlayView, mParams);
+     * </pre>
+     *
+     * <p>
+     * This context's configuration and resources are adjusted to a display area where the windows
+     * with provided type will be added. <b>Note that all windows associated with the same context
+     * will have an affinity and can only be moved together between different displays or areas on a
+     * display.</b> If there is a need to add different window types, or non-associated windows,
+     * separate Contexts should be used.
+     * </p>
+     *
+     * @param type Window type in {@link WindowManager.LayoutParams}
+     * @return A {@link Context} that can be used to create windows.
+     * @throws UnsupportedOperationException if this is called on a non-UI context, such as
+     *         {@link android.app.Application Application} or {@link android.app.Service Service}.
+     *
+     * @see #getSystemService(String)
+     * @see #getSystemService(Class)
+     * @see #WINDOW_SERVICE
+     * @see #LAYOUT_INFLATER_SERVICE
+     * @see #WALLPAPER_SERVICE
+     * @throws IllegalArgumentException if token is invalid
+     */
+    public @NonNull Context createWindowContext(int type)  {
+        throw new RuntimeException("Not implemented. Must override in a subclass.");
+    }
+
+    /**
      * Return a new Context object for the current Context but for a different feature in the app.
      * Features can be used by complex apps to separate logical parts.
      *
@@ -5746,17 +5880,22 @@
     public abstract DisplayAdjustments getDisplayAdjustments(int displayId);
 
     /**
+     * Get the display this context is associated with. Applications should use this method with
+     * {@link android.app.Activity} or a context associated with a {@link Display} via
+     * {@link #createDisplayContext(Display)} to get a display object associated with a Context, or
+     * {@link android.hardware.display.DisplayManager#getDisplay} to get a display object by id.
      * @return Returns the {@link Display} object this context is associated with.
-     * @hide
      */
-    @UnsupportedAppUsage
-    @TestApi
-    public abstract Display getDisplay();
+    @Nullable
+    public Display getDisplay() {
+        throw new RuntimeException("Not implemented. Must override in a subclass.");
+    }
 
     /**
-     * Gets the display ID.
+     * Gets the ID of the display this context is associated with.
      *
      * @return display ID associated with this {@link Context}.
+     * @see #getDisplay()
      * @hide
      */
     @TestApi
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 6fe1187..b2b7988 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -977,6 +977,12 @@
     }
 
     @Override
+    @NonNull
+    public Context createWindowContext(int type) {
+        return mBase.createWindowContext(type);
+    }
+
+    @Override
     public @NonNull Context createFeatureContext(@Nullable String featureId) {
         return mBase.createFeatureContext(featureId);
     }
@@ -992,11 +998,8 @@
         return mBase.getDisplayAdjustments(displayId);
     }
 
-    /** @hide */
-    @UnsupportedAppUsage
-    @TestApi
     @Override
-    public Display getDisplay() {
+    public @Nullable Display getDisplay() {
         return mBase.getDisplay();
     }
 
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 3bb0f92..c8c3102 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -752,6 +752,22 @@
     public static final String ACTION_PICK = "android.intent.action.PICK";
 
     /**
+     * Activity Action: Creates a reminder.
+     * <p>Input: {@link #EXTRA_TITLE} The title of the reminder that will be shown to the user.
+     * {@link #EXTRA_TEXT} The reminder text that will be shown to the user. The intent should at
+     * least specify a title or a text. {@link #EXTRA_TIME} The time when the reminder will be shown
+     * to the user. The time is specified in milliseconds since the Epoch (optional).
+     * </p>
+     * <p>Output: Nothing.</p>
+     *
+     * @see #EXTRA_TITLE
+     * @see #EXTRA_TEXT
+     * @see #EXTRA_TIME
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_CREATE_REMINDER = "android.intent.action.CREATE_REMINDER";
+
+    /**
      * Activity Action: Creates a shortcut.
      * <p>Input: Nothing.</p>
      * <p>Output: An Intent representing the {@link android.content.pm.ShortcutInfo} result.</p>
@@ -4005,6 +4021,7 @@
      * <p>
      * @see #EXTRA_SIM_STATE
      * @see #EXTRA_SIM_LOCKED_REASON
+     * @see #EXTRA_REBROADCAST_ON_UNLOCK
      *
      * @deprecated Use {@link #ACTION_SIM_CARD_STATE_CHANGED} or
      * {@link #ACTION_SIM_APPLICATION_STATE_CHANGED}
@@ -4181,6 +4198,18 @@
     public static final String SIM_ABSENT_ON_PERM_DISABLED = "PERM_DISABLED";
 
     /**
+     * The extra used with {@link #ACTION_SIM_STATE_CHANGED} for indicating whether this broadcast
+     * is a rebroadcast on unlock. Defaults to {@code false} if not specified.
+     *
+     * @hide
+     * @deprecated Use {@link #ACTION_SIM_CARD_STATE_CHANGED} or
+     * {@link #ACTION_SIM_APPLICATION_STATE_CHANGED}
+     */
+    @Deprecated
+    @SystemApi
+    public static final String EXTRA_REBROADCAST_ON_UNLOCK = "rebroadcastOnUnlock";
+
+    /**
      * Broadcast Action: indicate that the phone service state has changed.
      * The intent will have the following extra values:</p>
      * <p>
@@ -5726,6 +5755,15 @@
             = "android.intent.extra.SHUTDOWN_USERSPACE_ONLY";
 
     /**
+     * Optional extra specifying a time in milliseconds since the Epoch. The value must be
+     * non-negative.
+     * <p>
+     * Type: long
+     * </p>
+     */
+    public static final String EXTRA_TIME = "android.intent.extra.TIME";
+
+    /**
      * Optional int extra for {@link #ACTION_TIME_CHANGED} that indicates the
      * user has set their time format preference. See {@link #EXTRA_TIME_PREF_VALUE_USE_12_HOUR},
      * {@link #EXTRA_TIME_PREF_VALUE_USE_24_HOUR} and
@@ -6440,19 +6478,22 @@
      */
     public static final int FLAG_RECEIVER_NO_ABORT = 0x08000000;
     /**
-     * If set, when sending a broadcast <i>before boot has completed</i> only
+     * If set, when sending a broadcast <i>before the system has fully booted up
+     * (which is even before {@link #ACTION_LOCKED_BOOT_COMPLETED} has been sent)"</i> only
      * registered receivers will be called -- no BroadcastReceiver components
      * will be launched.  Sticky intent state will be recorded properly even
      * if no receivers wind up being called.  If {@link #FLAG_RECEIVER_REGISTERED_ONLY}
      * is specified in the broadcast intent, this flag is unnecessary.
      *
-     * <p>This flag is only for use by system sevices as a convenience to
-     * avoid having to implement a more complex mechanism around detection
+     * <p>This flag is only for use by system services (even services from mainline modules) as a
+     * convenience to avoid having to implement a more complex mechanism around detection
      * of boot completion.
      *
+     * <p>This is useful to system server mainline modules
+     *
      * @hide
      */
-    @UnsupportedAppUsage
+    @SystemApi
     public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 0x04000000;
     /**
      * Set when this broadcast is for a boot upgrade, a special mode that
diff --git a/core/java/android/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java
index 6954b31..33bd839 100644
--- a/core/java/android/content/PermissionChecker.java
+++ b/core/java/android/content/PermissionChecker.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
 import android.os.Binder;
 import android.os.Process;
 
@@ -67,22 +68,30 @@
  * @hide
  */
 public final class PermissionChecker {
-    /** Permission result: The permission is granted. */
+    /** The permission is granted. */
     public static final int PERMISSION_GRANTED =  PackageManager.PERMISSION_GRANTED;
 
-    /** Permission result: The permission is denied. */
-    public static final int PERMISSION_DENIED =  PackageManager.PERMISSION_DENIED;
+    /** Returned when:
+     * <ul>
+     * <li>For non app op permissions, returned when the permission is denied.</li>
+     * <li>For app op permissions, returned when the app op is denied or app op is
+     * {@link AppOpsManager#MODE_DEFAULT} and permission is denied.</li>
+     * </ul>
+     *
+     */
+    public static final int PERMISSION_HARD_DENIED =  PackageManager.PERMISSION_DENIED;
 
-    /** Permission result: The permission is denied because the app op is not allowed. */
-    public static final int PERMISSION_DENIED_APP_OP =  PackageManager.PERMISSION_DENIED  - 1;
+    /** Only for runtime permissions, its returned when the runtime permission
+     * is granted, but the corresponding app op is denied. */
+    public static final int PERMISSION_SOFT_DENIED =  PackageManager.PERMISSION_DENIED - 1;
 
     /** Constant when the PID for which we check permissions is unknown. */
     public static final int PID_UNKNOWN = -1;
 
     /** @hide */
     @IntDef({PERMISSION_GRANTED,
-            PERMISSION_DENIED,
-            PERMISSION_DENIED_APP_OP})
+            PERMISSION_SOFT_DENIED,
+            PERMISSION_HARD_DENIED})
     @Retention(RetentionPolicy.SOURCE)
     public @interface PermissionResult {}
 
@@ -116,7 +125,7 @@
      *     the first package for the calling UID will be used.
      * @param featureId Feature in the package
      * @return The permission check result which is either {@link #PERMISSION_GRANTED}
-     *     or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
+     *     or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
      * @param message A message describing the reason the permission was checked
      *
      * @see #checkPermissionForPreflight(Context, String, int, int, String)
@@ -155,7 +164,7 @@
      * @param packageName The package name for which to check. If null the
      *     the first package for the calling UID will be used.
      * @return The permission check result which is either {@link #PERMISSION_GRANTED}
-     *     or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
+     *     or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
      *
      * @see #checkPermissionForDataDelivery(Context, String, int, int, String, String)
      */
@@ -189,7 +198,7 @@
      * @param context Context for accessing resources.
      * @param permission The permission to check.
      * @return The permission check result which is either {@link #PERMISSION_GRANTED}
-     *     or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
+     *     or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
      * @param message A message describing the reason the permission was checked
      *
      * @see #checkSelfPermissionForPreflight(Context, String)
@@ -225,7 +234,7 @@
      * @param context Context for accessing resources.
      * @param permission The permission to check.
      * @return The permission check result which is either {@link #PERMISSION_GRANTED}
-     *     or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
+     *     or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
      *
      * @see #checkSelfPermissionForDataDelivery(Context, String, String)
      */
@@ -259,7 +268,7 @@
      *     the first package for the calling UID will be used.
      * @param featureId The feature inside of the app
      * @return The permission check result which is either {@link #PERMISSION_GRANTED}
-     *     or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
+     *     or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
      * @param message A message describing the reason the permission was checked
      *
      * @see #checkCallingPermissionForPreflight(Context, String, String)
@@ -269,7 +278,7 @@
             @NonNull String permission, @Nullable String packageName,
             @Nullable String featureId, @Nullable String message) {
         if (Binder.getCallingPid() == Process.myPid()) {
-            return PERMISSION_DENIED;
+            return PERMISSION_HARD_DENIED;
         }
         return checkPermissionForDataDelivery(context, permission, Binder.getCallingPid(),
                 Binder.getCallingUid(), packageName, featureId, message);
@@ -299,7 +308,7 @@
      * @param packageName The package name making the IPC. If null the
      *     the first package for the calling UID will be used.
      * @return The permission check result which is either {@link #PERMISSION_GRANTED}
-     *     or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
+     *     or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
      *
      * @see #checkCallingPermissionForDataDelivery(Context, String, String, String)
      */
@@ -307,7 +316,7 @@
     public static int checkCallingPermissionForPreflight(@NonNull Context context,
             @NonNull String permission, @Nullable String packageName) {
         if (Binder.getCallingPid() == Process.myPid()) {
-            return PERMISSION_DENIED;
+            return PERMISSION_HARD_DENIED;
         }
         return checkPermissionForPreflight(context, permission, Binder.getCallingPid(),
                 Binder.getCallingUid(), packageName);
@@ -333,7 +342,7 @@
      * @param context Context for accessing resources.
      * @param permission The permission to check.
      * @return The permission check result which is either {@link #PERMISSION_GRANTED}
-     *     or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
+     *     or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
      * @param featureId feature Id of caller (if not self)
      * @param message A message describing the reason the permission was checked
      *
@@ -372,7 +381,7 @@
      * @param context Context for accessing resources.
      * @param permission The permission to check.
      * @return The permission check result which is either {@link #PERMISSION_GRANTED}
-     *     or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
+     *     or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
      *
      * @see #checkCallingOrSelfPermissionForDataDelivery(Context, String, String, String)
      */
@@ -385,39 +394,85 @@
                 Binder.getCallingUid(), packageName);
     }
 
-    private static int checkPermissionCommon(@NonNull Context context, @NonNull String permission,
+    static int checkPermissionCommon(@NonNull Context context, @NonNull String permission,
             int pid, int uid, @Nullable String packageName, @Nullable String featureId,
             @Nullable String message, boolean forDataDelivery) {
-        if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) {
-            return PERMISSION_DENIED;
-        }
-
-        AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
-        String op = appOpsManager.permissionToOp(permission);
-        if (op == null) {
-            return PERMISSION_GRANTED;
+        final PermissionInfo permissionInfo;
+        try {
+            // TODO(b/147869157): Cache platform defined app op and runtime permissions to avoid
+            // calling into the package manager every time.
+            permissionInfo = context.getPackageManager().getPermissionInfo(permission, 0);
+        } catch (PackageManager.NameNotFoundException ignored) {
+            return PERMISSION_HARD_DENIED;
         }
 
         if (packageName == null) {
             String[] packageNames = context.getPackageManager().getPackagesForUid(uid);
-            if (packageNames == null || packageNames.length <= 0) {
-                return PERMISSION_DENIED;
+            if (packageNames != null && packageNames.length > 0) {
+                packageName = packageNames[0];
             }
-            packageName = packageNames[0];
         }
 
-        if (forDataDelivery) {
-            if (appOpsManager.noteProxyOpNoThrow(op, packageName, uid, featureId, message)
-                    != AppOpsManager.MODE_ALLOWED) {
-                return PERMISSION_DENIED_APP_OP;
+        if (permissionInfo.isAppOp()) {
+            return checkAppOpPermission(context, permission, pid, uid, packageName, featureId,
+                    message, forDataDelivery);
+        }
+        if (permissionInfo.isRuntime()) {
+            return checkRuntimePermission(context, permission, pid, uid, packageName, featureId,
+                    message, forDataDelivery);
+        }
+        return context.checkPermission(permission, pid, uid);
+    }
+
+    private static int checkAppOpPermission(@NonNull Context context, @NonNull String permission,
+            int pid, int uid, @Nullable String packageName, @Nullable String featureId,
+            @Nullable String message, boolean forDataDelivery) {
+        final String op = AppOpsManager.permissionToOp(permission);
+        if (op == null || packageName == null) {
+            return PERMISSION_HARD_DENIED;
+        }
+
+        final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+        final int opMode = (forDataDelivery)
+                ? appOpsManager.noteProxyOpNoThrow(op, packageName, uid, featureId, message)
+                : appOpsManager.unsafeCheckOpNoThrow(op, uid, packageName);
+
+        switch (opMode) {
+            case AppOpsManager.MODE_ALLOWED: {
+                return PERMISSION_GRANTED;
             }
+            case AppOpsManager.MODE_DEFAULT: {
+                return context.checkPermission(permission, pid, uid)
+                            == PackageManager.PERMISSION_GRANTED
+                        ? PERMISSION_GRANTED : PERMISSION_HARD_DENIED;
+            }
+            default: {
+                return PERMISSION_HARD_DENIED;
+            }
+        }
+    }
+
+    private static int checkRuntimePermission(@NonNull Context context, @NonNull String permission,
+            int pid, int uid, @Nullable String packageName, @Nullable String featureId,
+            @Nullable String message, boolean forDataDelivery) {
+        if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) {
+            return PERMISSION_HARD_DENIED;
+        }
+
+        final String op = AppOpsManager.permissionToOp(permission);
+        if (op == null || packageName == null) {
+            return PERMISSION_GRANTED;
+        }
+
+        final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+        final int opMode = (forDataDelivery)
+                ? appOpsManager.noteProxyOpNoThrow(op, packageName, uid, featureId, message)
+                : appOpsManager.unsafeCheckOpNoThrow(op, uid, packageName);
+
+        if (opMode == AppOpsManager.MODE_ALLOWED) {
+            return PERMISSION_GRANTED;
         } else {
-            final int mode = appOpsManager.unsafeCheckOpRawNoThrow(op, uid, packageName);
-            if (mode != AppOpsManager.MODE_ALLOWED && mode != AppOpsManager.MODE_FOREGROUND) {
-                return PERMISSION_DENIED_APP_OP;
-            }
+            return PERMISSION_SOFT_DENIED;
         }
-
-        return PERMISSION_GRANTED;
     }
 }
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index e5daaca..622588b 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -290,6 +290,15 @@
     public int colorMode = COLOR_MODE_DEFAULT;
 
     /**
+     * Value for {@link #preferMinimalPostProcessing} indicating that by default
+     * minimal post processing is not preferred.
+     *
+     * @see android.R.attr#preferMinimalPostProcessing
+     * @hide
+     */
+    public static final boolean MINIMAL_POST_PROCESSING_DEFAULT = false;
+
+    /**
      * Indicates whether the activity wants the connected display to do minimal post processing on
      * the produced image or video frames. This will only be requested if this activity's main
      * window is visible on the screen.
@@ -315,7 +324,7 @@
      * @see android.view.WindowManager.LayoutParams#preferMinimalPostProcessing
      * @see android.view.Display#isMinimalPostProcessingSupported
      */
-    public boolean preferMinimalPostProcessing = false;
+    public boolean preferMinimalPostProcessing = MINIMAL_POST_PROCESSING_DEFAULT;
 
     /**
      * Bit in {@link #flags} indicating whether this activity is able to
diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java
index e897b91..de153d0 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -16,20 +16,28 @@
 package android.content.pm;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
+import android.app.AppOpsManager.Mode;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
+import android.net.Uri;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 
 import com.android.internal.R;
 import com.android.internal.util.UserIcons;
 
+import java.util.Collection;
 import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * Class for handling cross profile operations. Apps can use this class to interact with its
@@ -37,6 +45,18 @@
  * use this class to start its main activity in managed profile.
  */
 public class CrossProfileApps {
+
+    /**
+     * Broadcast signalling that the receiving app's ability to interact across profiles has
+     * changed, as defined by the return value of {@link #canInteractAcrossProfiles()}.
+     *
+     * <p>Apps that have set the {@code android:crossProfile} manifest attribute to {@code true}
+     * can receive this broadcast in manifest broadcast receivers. Otherwise, it can only be
+     * received by dynamically-registered broadcast receivers.
+     */
+    public static final String ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED =
+            "android.content.pm.action.CAN_INTERACT_ACROSS_PROFILES_CHANGED";
+
     private final Context mContext;
     private final ICrossProfileApps mService;
     private final UserManager mUserManager;
@@ -76,6 +96,32 @@
     }
 
     /**
+     * Starts the specified activity of the caller package in the specified profile.
+     *
+     * <p>The caller must have the {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES}
+     * permission and both the caller and target user profiles must be in the same profile group.
+     *
+     * @param intent The intent to launch. A component in the caller package must be specified.
+     * @param targetUser The {@link UserHandle} of the profile; must be one of the users returned by
+     *        {@link #getTargetUserProfiles()} if different to the calling user, otherwise a
+     *        {@link SecurityException} will be thrown.
+     */
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.INTERACT_ACROSS_PROFILES,
+            android.Manifest.permission.INTERACT_ACROSS_USERS})
+    public void startActivity(@NonNull Intent intent, @NonNull UserHandle targetUser) {
+        try {
+            mService.startActivityAsUserByIntent(
+                    mContext.getIApplicationThread(),
+                    mContext.getPackageName(),
+                    intent,
+                    targetUser.getIdentifier());
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Starts the specified activity of the caller package in the specified profile. Unlike
      * {@link #startMainActivity}, this can start any activity of the caller package, not just
      * the main activity.
@@ -169,6 +215,175 @@
         }
     }
 
+    /**
+     * Returns whether the calling package can request to interact across profiles.
+     *
+     * <p>The package's current ability to interact across profiles can be checked with
+     * {@link #canInteractAcrossProfiles()}.
+     *
+     * <p>Specifically, returns whether the following are all true:
+     * <ul>
+     * <li>{@link #getTargetUserProfiles()} returns a non-empty list for the calling user.</li>
+     * <li>The calling app has requested</li>
+     * {@code android.Manifest.permission.INTERACT_ACROSS_PROFILES} in its manifest.
+     * <li>The calling package has either been whitelisted by default by the OEM or has been
+     * explicitly whitelisted by the admin via
+     * {@link android.app.admin.DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)}.
+     * </li>
+     * </ul>
+     *
+     * @return true if the calling package can request to interact across profiles.
+     */
+    public boolean canRequestInteractAcrossProfiles() {
+        try {
+            return mService.canRequestInteractAcrossProfiles(mContext.getPackageName());
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns whether the calling package can interact across profiles.
+     *
+     * <p>The package's current ability to request to interact across profiles can be checked with
+     * {@link #canRequestInteractAcrossProfiles()}.
+     *
+     * <p>Specifically, returns whether the following are all true:
+     * <ul>
+     * <li>{@link #getTargetUserProfiles()} returns a non-empty list for the calling user.</li>
+     * <li>The user has previously consented to cross-profile communication for the calling
+     * package.</li>
+     * <li>The calling package has either been whitelisted by default by the OEM or has been
+     * explicitly whitelisted by the admin via
+     * {@link android.app.admin.DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)}.
+     * </li>
+     * </ul>
+     *
+     * @return true if the calling package can interact across profiles.
+     * @throws SecurityException if {@code mContext.getPackageName()} does not belong to the
+     * calling UID.
+     */
+    public boolean canInteractAcrossProfiles() {
+        try {
+            return mService.canInteractAcrossProfiles(mContext.getPackageName());
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns an {@link Intent} to open the settings page that allows the user to decide whether
+     * the calling app can interact across profiles. The current state is given by
+     * {@link #canInteractAcrossProfiles()}.
+     *
+     * <p>Returns {@code null} if {@link #canRequestInteractAcrossProfiles()} is {@code false}.
+     *
+     * @return an {@link Intent} to open the settings page that allows the user to decide whether
+     * the app can interact across profiles
+     *
+     * @throws SecurityException if {@code mContext.getPackageName()} does not belong to the
+     * calling UID.
+     */
+    public @Nullable Intent createRequestInteractAcrossProfilesIntent() {
+        if (!canRequestInteractAcrossProfiles()) {
+            return null;
+        }
+        final Intent settingsIntent = new Intent();
+        settingsIntent.setAction(Settings.ACTION_MANAGE_CROSS_PROFILE_ACCESS);
+        final Uri packageUri = Uri.parse("package:" + mContext.getPackageName());
+        settingsIntent.setData(packageUri);
+        return settingsIntent;
+    }
+
+    /**
+     * Sets the app-op for {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES} that is
+     * configurable by users in Settings. This configures it for the profile group of the calling
+     * package.
+     *
+     * <p>Before calling, check {@link #canConfigureInteractAcrossProfiles(String)} and do not call
+     * if it is {@code false}. If presenting a user interface, do not allow the user to configure
+     * the app-op in that case.
+     *
+     * <p>The underlying app-op {@link android.app.AppOpsManager#OP_INTERACT_ACROSS_PROFILES} should
+     * never be set directly. This method ensures that the app-op is kept in sync for the app across
+     * each user in the profile group and that those apps are sent a broadcast when their ability to
+     * interact across profiles changes.
+     *
+     * <p>This method should be used directly whenever a user's action results in a change in an
+     * app's ability to interact across profiles, as defined by the return value of {@link
+     * #canInteractAcrossProfiles()}. This includes user consent changes in Settings or during
+     * provisioning.
+     *
+     * <p>If other changes could have affected the app's ability to interact across profiles, as
+     * defined by the return value of {@link #canInteractAcrossProfiles()}, such as changes to the
+     * admin or OEM consent whitelists, then {@link
+     * #resetInteractAcrossProfilesAppOpsIfInvalid(List)} should be used.
+     *
+     * @hide
+     */
+    @RequiresPermission(
+            allOf={android.Manifest.permission.MANAGE_APP_OPS_MODES,
+                    android.Manifest.permission.INTERACT_ACROSS_USERS})
+    public void setInteractAcrossProfilesAppOp(@NonNull String packageName, @Mode int newMode) {
+        try {
+            mService.setInteractAcrossProfilesAppOp(packageName, newMode);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns whether the given package can have its ability to interact across profiles configured
+     * by the user. This means that every other condition to interact across profiles has been set.
+     *
+     * <p>This differs from {@link #canRequestInteractAcrossProfiles()} since it will not return
+     * {@code false} simply when the target profile is disabled.
+     *
+     * @hide
+     */
+    public boolean canConfigureInteractAcrossProfiles(@NonNull String packageName) {
+        try {
+            return mService.canConfigureInteractAcrossProfiles(packageName);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * For each of the packages defined in {@code previousCrossProfilePackages} but not included in
+     * {@code newCrossProfilePackages}, resets the app-op for {@link android.Manifest.permission
+     * #INTERACT_ACROSS_PROFILES} back to its default value if it can no longer be configured by
+     * users in Settings, as defined by {@link #canConfigureInteractAcrossProfiles(String)}.
+     *
+     * <p>This method should be used whenever an app's ability to interact across profiles could
+     * have changed as a result of non-user actions, such as changes to admin or OEM consent
+     * whitelists.
+     *
+     * @hide
+     */
+    @RequiresPermission(
+            allOf={android.Manifest.permission.MANAGE_APP_OPS_MODES,
+                    android.Manifest.permission.INTERACT_ACROSS_USERS})
+    public void resetInteractAcrossProfilesAppOps(
+            @NonNull Collection<String> previousCrossProfilePackages,
+            @NonNull Set<String> newCrossProfilePackages) {
+        if (previousCrossProfilePackages.isEmpty()) {
+            return;
+        }
+        final List<String> unsetCrossProfilePackages =
+                previousCrossProfilePackages.stream()
+                        .filter(packageName -> !newCrossProfilePackages.contains(packageName))
+                        .collect(Collectors.toList());
+        if (unsetCrossProfilePackages.isEmpty()) {
+            return;
+        }
+        try {
+            mService.resetInteractAcrossProfilesAppOps(unsetCrossProfilePackages);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
     private void verifyCanAccessUser(UserHandle userHandle) {
         if (!getTargetUserProfiles().contains(userHandle)) {
             throw new SecurityException("Not allowed to access " + userHandle);
diff --git a/core/java/android/content/pm/ICrossProfileApps.aidl b/core/java/android/content/pm/ICrossProfileApps.aidl
index d2d66cb..a69b988 100644
--- a/core/java/android/content/pm/ICrossProfileApps.aidl
+++ b/core/java/android/content/pm/ICrossProfileApps.aidl
@@ -29,5 +29,12 @@
 interface ICrossProfileApps {
     void startActivityAsUser(in IApplicationThread caller, in String callingPackage,
             in ComponentName component, int userId, boolean launchMainActivity);
+    void startActivityAsUserByIntent(in IApplicationThread caller, in String callingPackage,
+                        in Intent intent, int userId);
     List<UserHandle> getTargetUserProfiles(in String callingPackage);
+    boolean canInteractAcrossProfiles(in String callingPackage);
+    boolean canRequestInteractAcrossProfiles(in String callingPackage);
+    void setInteractAcrossProfilesAppOp(in String packageName, int newMode);
+    boolean canConfigureInteractAcrossProfiles(in String packageName);
+    void resetInteractAcrossProfilesAppOps(in List<String> packageNames);
 }
\ No newline at end of file
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
index e954635..fc20263 100644
--- a/core/java/android/content/pm/IPackageInstallerSession.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -16,6 +16,7 @@
 
 package android.content.pm;
 
+import android.content.pm.DataLoaderParamsParcel;
 import android.content.pm.IPackageInstallObserver2;
 import android.content.IntentSender;
 import android.os.ParcelFileDescriptor;
@@ -36,11 +37,12 @@
 
     void close();
     void commit(in IntentSender statusReceiver, boolean forTransferred);
-    void transfer(in String packageName, in IntentSender statusReceiver);
+    void transfer(in String packageName);
     void abandon();
 
-    void addFile(String name, long lengthBytes, in byte[] metadata);
-    void removeFile(String name);
+    DataLoaderParamsParcel getDataLoaderParams();
+    void addFile(int location, String name, long lengthBytes, in byte[] metadata, in byte[] signature);
+    void removeFile(int location, String name);
 
     boolean isMultiPackage();
     int[] getChildSessionIds();
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index b3d8eb5..93126b8 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -230,7 +230,7 @@
      * @param versionedPackage The package to delete.
      * @param observer a callback to use to notify when the package deletion in finished.
      * @param userId the id of the user for whom to delete the package
-     * @param flags - possible values: {@link #DONT_DELETE_DATA}
+     * @param flags - possible values: {@link #DELETE_KEEP_DATA}
      */
     void deletePackageVersioned(in VersionedPackage versionedPackage,
             IPackageDeleteObserver2 observer, int userId, int flags);
diff --git a/core/java/android/content/pm/InstallSourceInfo.java b/core/java/android/content/pm/InstallSourceInfo.java
index 4d235f1..c0fdcc9 100644
--- a/core/java/android/content/pm/InstallSourceInfo.java
+++ b/core/java/android/content/pm/InstallSourceInfo.java
@@ -29,32 +29,39 @@
 
     @Nullable private final String mInitiatingPackageName;
 
+    @Nullable private final SigningInfo mInitiatingPackageSigningInfo;
+
     @Nullable private final String mOriginatingPackageName;
 
     @Nullable private final String mInstallingPackageName;
 
     /** @hide */
     public InstallSourceInfo(@Nullable String initiatingPackageName,
+            @Nullable SigningInfo initiatingPackageSigningInfo,
             @Nullable String originatingPackageName, @Nullable String installingPackageName) {
-        this.mInitiatingPackageName = initiatingPackageName;
-        this.mOriginatingPackageName = originatingPackageName;
-        this.mInstallingPackageName = installingPackageName;
+        mInitiatingPackageName = initiatingPackageName;
+        mInitiatingPackageSigningInfo = initiatingPackageSigningInfo;
+        mOriginatingPackageName = originatingPackageName;
+        mInstallingPackageName = installingPackageName;
     }
 
     @Override
     public int describeContents() {
-        return 0;
+        return mInitiatingPackageSigningInfo == null
+                ? 0 : mInitiatingPackageSigningInfo.describeContents();
     }
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString(mInitiatingPackageName);
+        dest.writeParcelable(mInitiatingPackageSigningInfo, flags);
         dest.writeString(mOriginatingPackageName);
         dest.writeString(mInstallingPackageName);
     }
 
     private InstallSourceInfo(Parcel source) {
         mInitiatingPackageName = source.readString();
+        mInitiatingPackageSigningInfo = source.readParcelable(SigningInfo.class.getClassLoader());
         mOriginatingPackageName = source.readString();
         mInstallingPackageName = source.readString();
     }
@@ -66,6 +73,14 @@
     }
 
     /**
+     * Information about the signing certificates used to sign the initiating package, if available.
+     */
+    @Nullable
+    public SigningInfo getInitiatingPackageSigningInfo() {
+        return mInitiatingPackageSigningInfo;
+    }
+
+    /**
      * The name of the package on behalf of which the initiating package requested the installation,
      * or null if not available.
      * <p>
diff --git a/core/java/android/content/pm/InstallationFile.java b/core/java/android/content/pm/InstallationFile.java
index ac5fd1e..111ad32 100644
--- a/core/java/android/content/pm/InstallationFile.java
+++ b/core/java/android/content/pm/InstallationFile.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -31,12 +32,14 @@
  *
  * @hide
  */
+@SystemApi
 public final class InstallationFile implements Parcelable {
     public static final int FILE_TYPE_UNKNOWN = -1;
     public static final int FILE_TYPE_APK = 0;
     public static final int FILE_TYPE_LIB = 1;
     public static final int FILE_TYPE_OBB = 2;
 
+    /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"FILE_TYPE_"}, value = {
             FILE_TYPE_APK,
diff --git a/core/java/android/content/pm/ModuleInfo.java b/core/java/android/content/pm/ModuleInfo.java
index d930c92..a6db662 100644
--- a/core/java/android/content/pm/ModuleInfo.java
+++ b/core/java/android/content/pm/ModuleInfo.java
@@ -37,6 +37,12 @@
     /** The package name of this module. */
     private String mPackageName;
 
+    /**
+     * The name of the APEX this module is distributed as, or null if it is not distributed via
+     * APEX.
+     */
+    @Nullable private String mApexModuleName;
+
     /** Whether or not this module is hidden from the user. */
     private boolean mHidden;
 
@@ -54,6 +60,7 @@
         mName = orig.mName;
         mPackageName = orig.mPackageName;
         mHidden = orig.mHidden;
+        mApexModuleName = orig.mApexModuleName;
     }
 
     /** @hide Sets the public name of this module. */
@@ -89,6 +96,17 @@
         return mHidden;
     }
 
+    /** @hide Sets the apex module name. */
+    public ModuleInfo setApexModuleName(@Nullable String apexModuleName) {
+        mApexModuleName = apexModuleName;
+        return this;
+    }
+
+    /** @hide Gets the apex module name. */
+    public @Nullable String getApexModuleName() {
+        return mApexModuleName;
+    }
+
     /** Returns a string representation of this object. */
     public String toString() {
         return "ModuleInfo{"
@@ -106,6 +124,7 @@
         int hashCode = 0;
         hashCode = 31 * hashCode + Objects.hashCode(mName);
         hashCode = 31 * hashCode + Objects.hashCode(mPackageName);
+        hashCode = 31 * hashCode + Objects.hashCode(mApexModuleName);
         hashCode = 31 * hashCode + Boolean.hashCode(mHidden);
         return hashCode;
     }
@@ -118,6 +137,7 @@
         final ModuleInfo other = (ModuleInfo) obj;
         return Objects.equals(mName, other.mName)
                 && Objects.equals(mPackageName, other.mPackageName)
+                && Objects.equals(mApexModuleName, other.mApexModuleName)
                 && mHidden == other.mHidden;
     }
 
@@ -126,12 +146,14 @@
         dest.writeCharSequence(mName);
         dest.writeString(mPackageName);
         dest.writeBoolean(mHidden);
+        dest.writeString(mApexModuleName);
     }
 
     private ModuleInfo(Parcel source) {
         mName = source.readCharSequence();
         mPackageName = source.readString();
         mHidden = source.readBoolean();
+        mApexModuleName = source.readString();
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<ModuleInfo> CREATOR =
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 7a28022..b1b9454 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -29,8 +29,6 @@
 import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.content.IIntentReceiver;
-import android.content.IIntentSender;
 import android.content.Intent;
 import android.content.IntentSender;
 import android.content.pm.PackageManager.DeleteFlags;
@@ -38,11 +36,9 @@
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.Build;
-import android.os.Bundle;
 import android.os.FileBridge;
 import android.os.Handler;
 import android.os.HandlerExecutor;
-import android.os.IBinder;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
@@ -71,8 +67,6 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.stream.Collectors;
 
@@ -182,9 +176,8 @@
      * {@link #STATUS_PENDING_USER_ACTION}, {@link #STATUS_SUCCESS},
      * {@link #STATUS_FAILURE}, {@link #STATUS_FAILURE_ABORTED},
      * {@link #STATUS_FAILURE_BLOCKED}, {@link #STATUS_FAILURE_CONFLICT},
-     * {@link #STATUS_FAILURE_INCOMPATIBLE}, {@link #STATUS_FAILURE_INVALID},
-     * {@link #STATUS_FAILURE_STORAGE}, {@link #STATUS_FAILURE_NAME_NOT_FOUND},
-     * {@link #STATUS_FAILURE_ILLEGAL_STATE} or {@link #STATUS_FAILURE_SECURITY}.
+     * {@link #STATUS_FAILURE_INCOMPATIBLE}, {@link #STATUS_FAILURE_INVALID}, or
+     * {@link #STATUS_FAILURE_STORAGE}.
      * <p>
      * More information about a status may be available through additional
      * extras; see the individual status documentation for details.
@@ -229,6 +222,19 @@
     public static final String EXTRA_CALLBACK = "android.content.pm.extra.CALLBACK";
 
     /**
+     * Type of DataLoader for this session. Will be one of
+     * {@link #DATA_LOADER_TYPE_NONE}, {@link #DATA_LOADER_TYPE_STREAMING},
+     * {@link #DATA_LOADER_TYPE_INCREMENTAL}.
+     * <p>
+     * See the individual types documentation for details.
+     *
+     * @see Intent#getIntExtra(String, int)
+     * {@hide}
+     */
+    @SystemApi
+    public static final String EXTRA_DATA_LOADER_TYPE = "android.content.pm.extra.DATA_LOADER_TYPE";
+
+    /**
      * Streaming installation pending.
      * Caller should make sure DataLoader is able to prepare image and reinitiate the operation.
      *
@@ -333,32 +339,66 @@
     public static final int STATUS_FAILURE_INCOMPATIBLE = 7;
 
     /**
-     * The transfer failed because a target package can't be found. For example
-     * transferring a session to a non-existing package.
-     * <p>
-     * The result may also contain {@link #EXTRA_OTHER_PACKAGE_NAME} with the
-     * missing package.
+     * Default value, non-streaming installation session.
      *
-     * @see #EXTRA_STATUS_MESSAGE
-     * @see #EXTRA_OTHER_PACKAGE_NAME
+     * @see #EXTRA_DATA_LOADER_TYPE
+     * {@hide}
      */
-    public static final int STATUS_FAILURE_NAME_NOT_FOUND = 8;
+    @SystemApi
+    public static final int DATA_LOADER_TYPE_NONE = DataLoaderType.NONE;
 
     /**
-     * The transfer failed because a session is in invalid state. For example
-     * transferring an already committed session.
+     * Streaming installation using data loader.
      *
-     * @see #EXTRA_STATUS_MESSAGE
+     * @see #EXTRA_DATA_LOADER_TYPE
+     * {@hide}
      */
-    public static final int STATUS_FAILURE_ILLEGAL_STATE = 9;
+    @SystemApi
+    public static final int DATA_LOADER_TYPE_STREAMING = DataLoaderType.STREAMING;
 
     /**
-     * The transfer failed for security reasons. For example transferring
-     * to a package which does not have INSTALL_PACKAGES permission.
+     * Streaming installation using Incremental FileSystem.
      *
-     * @see #EXTRA_STATUS_MESSAGE
+     * @see #EXTRA_DATA_LOADER_TYPE
+     * {@hide}
      */
-    public static final int STATUS_FAILURE_SECURITY = 10;
+    @SystemApi
+    public static final int DATA_LOADER_TYPE_INCREMENTAL = DataLoaderType.INCREMENTAL;
+
+    /**
+     * Target location for the file in installation session is /data/app/<packageName>-<id>.
+     * This is the intended location for APKs.
+     * Requires permission to install packages.
+     * {@hide}
+     */
+    @SystemApi
+    public static final int LOCATION_DATA_APP = 0;
+
+    /**
+     * Target location for the file in installation session is
+     * /data/media/<userid>/Android/obb/<packageName>. This is the intended location for OBBs.
+     * {@hide}
+     */
+    @SystemApi
+    public static final int LOCATION_MEDIA_OBB = 1;
+
+    /**
+     * Target location for the file in installation session is
+     * /data/media/<userid>/Android/data/<packageName>.
+     * This is the intended location for application data.
+     * Can only be used by an app itself running under specific user.
+     * {@hide}
+     */
+    @SystemApi
+    public static final int LOCATION_MEDIA_DATA = 2;
+
+    /** @hide */
+    @IntDef(prefix = { "LOCATION_" }, value = {
+            LOCATION_DATA_APP,
+            LOCATION_MEDIA_OBB,
+            LOCATION_MEDIA_DATA})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FileLocation{}
 
     private final IPackageInstaller mInstaller;
     private final int mUserId;
@@ -1066,10 +1106,33 @@
             }
         }
 
+        /**
+         * @return data loader params or null if the session is not using one.
+         *
+         * WARNING: This is a system API to aid internal development.
+         * Use at your own risk. It will change or be removed without warning.
+         * {@hide}
+         */
+        @SystemApi
+        public @Nullable DataLoaderParams getDataLoaderParams() {
+            try {
+                DataLoaderParamsParcel data = mSession.getDataLoaderParams();
+                if (data == null) {
+                    return null;
+                }
+                return new DataLoaderParams(data);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
 
         /**
          * Adds a file to session. On commit this file will be pulled from dataLoader.
          *
+         * @param location target location for the file. Possible values:
+         *            {@link #LOCATION_DATA_APP},
+         *            {@link #LOCATION_MEDIA_OBB},
+         *            {@link #LOCATION_MEDIA_DATA}.
          * @param name arbitrary, unique name of your choosing to identify the
          *            APK being written. You can open a file again for
          *            additional writes (such as after a reboot) by using the
@@ -1079,6 +1142,8 @@
          *            The system may clear various caches as needed to allocate
          *            this space.
          * @param metadata additional info use by dataLoader to pull data for the file.
+         * @param signature additional file signature, e.g.
+         *                  <a href="https://source.android.com/security/apksigning/v4.html">APK Signature Scheme v4</a>
          * @throws SecurityException if called after the session has been
          *             sealed or abandoned
          * @throws IllegalStateException if called for non-callback session
@@ -1088,9 +1153,10 @@
          * {@hide}
          */
         @SystemApi
-        public void addFile(@NonNull String name, long lengthBytes, @NonNull byte[] metadata) {
+        public void addFile(@FileLocation int location, @NonNull String name, long lengthBytes,
+                @NonNull byte[] metadata, @Nullable byte[] signature) {
             try {
-                mSession.addFile(name, lengthBytes, metadata);
+                mSession.addFile(location, name, lengthBytes, metadata, signature);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -1099,15 +1165,20 @@
         /**
          * Removes a file.
          *
+         * @param location target location for the file. Possible values:
+         *            {@link #LOCATION_DATA_APP},
+         *            {@link #LOCATION_MEDIA_OBB},
+         *            {@link #LOCATION_MEDIA_DATA}.
          * @param name name of a file, e.g. split.
          * @throws SecurityException if called after the session has been
          *             sealed or abandoned
          * @throws IllegalStateException if called for non-callback session
          * {@hide}
          */
-        public void removeFile(@NonNull String name) {
+        @SystemApi
+        public void removeFile(@FileLocation int location, @NonNull String name) {
             try {
-                mSession.removeFile(name);
+                mSession.removeFile(location, name);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -1143,8 +1214,7 @@
         }
 
         /**
-         * Attempt to commit a session that has been {@link #transfer(String, IntentSender)
-         * transferred}.
+         * Attempt to commit a session that has been {@link #transfer(String) transferred}.
          *
          * <p>If the device reboots before the session has been finalized, you may commit the
          * session again.
@@ -1185,43 +1255,6 @@
          *
          * @param packageName The package of the new owner. Needs to hold the INSTALL_PACKAGES
          *                    permission.
-         * @param statusReceiver Called when the state of the session changes. Intents sent to
-         *                       this receiver contain {@link #EXTRA_STATUS}. Possible statuses:
-         *                       {@link #STATUS_FAILURE_NAME_NOT_FOUND},
-         *                       {@link #STATUS_FAILURE_ILLEGAL_STATE},
-         *                       {@link #STATUS_FAILURE_SECURITY},
-         *                       {@link #STATUS_FAILURE}.
-         *                       Refer to the individual transfer status codes on how to handle
-         *                       them.
-         *
-         * @throws PackageManager.NameNotFoundException if the new owner could not be found.
-         * @throws SecurityException if called after the session has been committed or abandoned.
-         * @throws SecurityException if the session does not update the original installer
-         * @throws SecurityException if streams opened through
-         *                           {@link #openWrite(String, long, long) are still open.
-         */
-        public void transfer(@NonNull String packageName, @NonNull IntentSender statusReceiver)
-                throws PackageManager.NameNotFoundException {
-            Objects.requireNonNull(statusReceiver);
-            Objects.requireNonNull(packageName);
-
-            try {
-                mSession.transfer(packageName, statusReceiver);
-            } catch (ParcelableException e) {
-                e.maybeRethrow(PackageManager.NameNotFoundException.class);
-                throw new RuntimeException(e);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-        }
-
-        /**
-         * Transfer the session to a new owner.
-         * This is a convenience blocking wrapper around {@link #transfer(String, IntentSender)}.
-         * Converts all statuses into exceptions.
-         *
-         * @param packageName The package of the new owner. Needs to hold the INSTALL_PACKAGES
-         *                    permission.
          *
          * @throws PackageManager.NameNotFoundException if the new owner could not be found.
          * @throws SecurityException if called after the session has been committed or abandoned.
@@ -1233,43 +1266,13 @@
                 throws PackageManager.NameNotFoundException {
             Objects.requireNonNull(packageName);
 
-            CompletableFuture<Intent> intentFuture = new CompletableFuture<Intent>();
             try {
-                IIntentSender localSender = new IIntentSender.Stub() {
-                    @Override
-                    public void send(int code, Intent intent, String resolvedType,
-                            IBinder whitelistToken,
-                            IIntentReceiver finishedReceiver, String requiredPermission,
-                            Bundle options) {
-                        intentFuture.complete(intent);
-                    }
-                };
-                transfer(packageName, new IntentSender(localSender));
+                mSession.transfer(packageName);
             } catch (ParcelableException e) {
                 e.maybeRethrow(PackageManager.NameNotFoundException.class);
                 throw new RuntimeException(e);
-            }
-
-            try {
-                Intent intent = intentFuture.get();
-                final int status = intent.getIntExtra(EXTRA_STATUS, Integer.MIN_VALUE);
-                final String statusMessage = intent.getStringExtra(EXTRA_STATUS_MESSAGE);
-                switch (status) {
-                    case STATUS_SUCCESS:
-                        break;
-                    case STATUS_FAILURE_NAME_NOT_FOUND:
-                        throw new PackageManager.NameNotFoundException(statusMessage);
-                    case STATUS_FAILURE_ILLEGAL_STATE:
-                        throw new IllegalStateException(statusMessage);
-                    case STATUS_FAILURE_SECURITY:
-                        throw new SecurityException(statusMessage);
-                    default:
-                        throw new RuntimeException(statusMessage);
-                }
-            } catch (InterruptedException e) {
-                throw new RuntimeException(e);
-            } catch (ExecutionException e) {
-                throw new RuntimeException(e);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
             }
         }
 
@@ -2092,6 +2095,9 @@
         public boolean isCommitted;
 
         /** {@hide} */
+        public long createdMillis;
+
+        /** {@hide} */
         public long updatedMillis;
 
         /** {@hide} */
@@ -2141,6 +2147,7 @@
             mStagedSessionErrorMessage = source.readString();
             isCommitted = source.readBoolean();
             rollbackDataPolicy = source.readInt();
+            createdMillis = source.readLong();
         }
 
         /**
@@ -2583,6 +2590,13 @@
         }
 
         /**
+         * The timestamp of the initial creation of the session.
+         */
+        public long getCreatedMillis() {
+            return createdMillis;
+        }
+
+        /**
          * The timestamp of the last update that occurred to the session, including changing of
          * states in case of staged sessions.
          */
@@ -2631,6 +2645,7 @@
             dest.writeString(mStagedSessionErrorMessage);
             dest.writeBoolean(isCommitted);
             dest.writeInt(rollbackDataPolicy);
+            dest.writeLong(createdMillis);
         }
 
         public static final Parcelable.Creator<SessionInfo>
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b85c58a..2056170 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -50,6 +50,7 @@
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.graphics.Rect;
+import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.wifi.WifiManager;
 import android.os.Build;
@@ -229,7 +230,7 @@
             MATCH_ALL,
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface ModuleInfoFlags {}
+    public @interface InstalledModulesFlags {}
 
     /** @hide */
     @IntDef(flag = true, prefix = { "GET_", "MATCH_" }, value = {
@@ -368,7 +369,7 @@
      * Flag parameter to retrieve some information about all applications (even
      * uninstalled ones) which have data directories. This state could have
      * resulted if applications have been deleted with flag
-     * {@code DONT_DELETE_DATA} with a possibility of being replaced or
+     * {@code DELETE_KEEP_DATA} with a possibility of being replaced or
      * reinstalled in future.
      * <p>
      * Note: this flag may cause less information about currently installed
@@ -581,6 +582,22 @@
     public static final int ONLY_IF_NO_MATCH_FOUND = 0x00000004;
 
     /** @hide */
+    @IntDef(flag = true, prefix = { "MODULE_" }, value = {
+            MODULE_APEX_NAME,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ModuleInfoFlags {}
+
+    /**
+     * Flag for {@link #getModuleInfo}: allow ModuleInfo to be retrieved using the apex module
+     * name, rather than the package name.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int MODULE_APEX_NAME = 0x00000001;
+
+    /** @hide */
     @IntDef(prefix = { "PERMISSION_" }, value = {
             PERMISSION_GRANTED,
             PERMISSION_DENIED
@@ -1948,6 +1965,13 @@
     @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_CONSUMER_IR = "android.hardware.consumerir";
 
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The device supports a Context Hub.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_CONTEXTHUB = "android.hardware.context_hub";
+
     /** {@hide} */
     @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_CTS = "android.software.cts";
@@ -2179,6 +2203,23 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature(String, int)}: If this feature is supported, the feature version
+     * specifies a date such that the device is known to pass the Vulkan dEQP test suite associated
+     * with that date.  The date is encoded as follows:
+     * <ul>
+     * <li>Year in bits 31-16</li>
+     * <li>Month in bits 15-8</li>
+     * <li>Day in bits 7-0</li>
+     * </ul>
+     * <p>
+     * Example: 2019-03-01 is encoded as 0x07E30301, and would indicate that the device passes the
+     * Vulkan dEQP test suite version that was current on 2019-03-01.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_VULKAN_DEQP_LEVEL = "android.software.vulkan.deqp.level";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device includes broadcast radio tuner.
      * @hide
      */
@@ -3372,6 +3413,10 @@
      * etc. This library is versioned and backwards compatible. Clients
      * should check its version via {@link android.ext.services.Version
      * #getVersionCode()} and avoid calling APIs added in later versions.
+     * <p>
+     * This shared library no longer exists since Android R.
+     *
+     * @see #getServicesSystemSharedLibraryPackageName()
      *
      * @hide
      */
@@ -3489,7 +3534,7 @@
      *         information is retrieved from the list of uninstalled
      *         applications (which includes installed applications as well as
      *         applications with data directory i.e. applications which had been
-     *         deleted with {@code DONT_DELETE_DATA} flag set).
+     *         deleted with {@code DELETE_KEEP_DATA} flag set).
      * @throws NameNotFoundException if a package with the given name cannot be
      *             found on the system.
      */
@@ -3515,7 +3560,7 @@
      *         information is retrieved from the list of uninstalled
      *         applications (which includes installed applications as well as
      *         applications with data directory i.e. applications which had been
-     *         deleted with {@code DONT_DELETE_DATA} flag set).
+     *         deleted with {@code DELETE_KEEP_DATA} flag set).
      * @throws NameNotFoundException if a package with the given name cannot be
      *             found on the system.
      */
@@ -3536,7 +3581,7 @@
      *         information is retrieved from the list of uninstalled
      *         applications (which includes installed applications as well as
      *         applications with data directory i.e. applications which had been
-     *         deleted with {@code DONT_DELETE_DATA} flag set).
+     *         deleted with {@code DELETE_KEEP_DATA} flag set).
      * @throws NameNotFoundException if a package with the given name cannot be
      *             found on the system.
      * @hide
@@ -3783,7 +3828,7 @@
      *         the application information is retrieved from the list of
      *         uninstalled applications (which includes installed applications
      *         as well as applications with data directory i.e. applications
-     *         which had been deleted with {@code DONT_DELETE_DATA} flag set).
+     *         which had been deleted with {@code DELETE_KEEP_DATA} flag set).
      * @throws NameNotFoundException if a package with the given name cannot be
      *             found on the system.
      */
@@ -3810,7 +3855,7 @@
      *         the application information is retrieved from the list of
      *         uninstalled applications (which includes installed applications
      *         as well as applications with data directory i.e. applications
-     *         which had been deleted with {@code DONT_DELETE_DATA} flag set).
+     *         which had been deleted with {@code DELETE_KEEP_DATA} flag set).
      * @throws NameNotFoundException if a package with the given name cannot be
      *             found on the system.
      * @hide
@@ -3917,7 +3962,7 @@
      *         there are no installed modules, an empty list is returned.
      */
     @NonNull
-    public List<ModuleInfo> getInstalledModules(@ModuleInfoFlags int flags) {
+    public List<ModuleInfo> getInstalledModules(@InstalledModulesFlags int flags) {
         throw new UnsupportedOperationException(
                 "getInstalledModules not implemented in subclass");
     }
@@ -3933,7 +3978,7 @@
      *         information is retrieved from the list of uninstalled
      *         applications (which includes installed applications as well as
      *         applications with data directory i.e. applications which had been
-     *         deleted with {@code DONT_DELETE_DATA} flag set).
+     *         deleted with {@code DELETE_KEEP_DATA} flag set).
      */
     @NonNull
     public abstract List<PackageInfo> getInstalledPackages(@PackageInfoFlags int flags);
@@ -3951,7 +3996,7 @@
      *         information is retrieved from the list of uninstalled
      *         applications (which includes installed applications as well as
      *         applications with data directory i.e. applications which had been
-     *         deleted with {@code DONT_DELETE_DATA} flag set).
+     *         deleted with {@code DELETE_KEEP_DATA} flag set).
      */
     @NonNull
     public abstract List<PackageInfo> getPackagesHoldingPermissions(
@@ -3970,7 +4015,7 @@
      *         information is retrieved from the list of uninstalled
      *         applications (which includes installed applications as well as
      *         applications with data directory i.e. applications which had been
-     *         deleted with {@code DONT_DELETE_DATA} flag set).
+     *         deleted with {@code DELETE_KEEP_DATA} flag set).
      * @hide
      */
     @NonNull
@@ -4382,6 +4427,21 @@
     public abstract boolean shouldShowRequestPermissionRationale(@NonNull String permName);
 
     /**
+     * Gets the localized label that corresponds to the option in settings for granting
+     * background access.
+     *
+     * <p>The intended use is for apps to reference this label in its instruction for users to grant
+     * a background permission.
+     *
+     * @return the localized label that corresponds to the settings option for granting
+     * background access
+     */
+    @NonNull
+    public CharSequence getBackgroundPermissionOptionLabel() {
+        return "";
+    }
+
+    /**
      * Returns an {@link android.content.Intent} suitable for passing to
      * {@link android.app.Activity#startActivityForResult(android.content.Intent, int)}
      * which prompts the user to grant permissions to this application.
@@ -4501,7 +4561,7 @@
     /**
      * Return a List of all application packages that are installed for the
      * current user. If flag GET_UNINSTALLED_PACKAGES has been set, a list of all
-     * applications including those deleted with {@code DONT_DELETE_DATA}
+     * applications including those deleted with {@code DELETE_KEEP_DATA}
      * (partially installed apps with data directory) will be returned.
      *
      * @param flags Additional option flags to modify the data returned.
@@ -4512,7 +4572,7 @@
      *         information is retrieved from the list of uninstalled
      *         applications (which includes installed applications as well as
      *         applications with data directory i.e. applications which had been
-     *         deleted with {@code DONT_DELETE_DATA} flag set).
+     *         deleted with {@code DELETE_KEEP_DATA} flag set).
      */
     @NonNull
     public abstract List<ApplicationInfo> getInstalledApplications(@ApplicationInfoFlags int flags);
@@ -4521,7 +4581,7 @@
      * Return a List of all application packages that are installed on the
      * device, for a specific user. If flag GET_UNINSTALLED_PACKAGES has been
      * set, a list of all applications including those deleted with
-     * {@code DONT_DELETE_DATA} (partially installed apps with data directory)
+     * {@code DELETE_KEEP_DATA} (partially installed apps with data directory)
      * will be returned.
      *
      * @param flags Additional option flags to modify the data returned.
@@ -4534,7 +4594,7 @@
      *         information is retrieved from the list of uninstalled
      *         applications (which includes installed applications as well as
      *         applications with data directory i.e. applications which had been
-     *         deleted with {@code DONT_DELETE_DATA} flag set).
+     *         deleted with {@code DELETE_KEEP_DATA} flag set).
      * @hide
      */
     @NonNull
@@ -4726,6 +4786,9 @@
 
     /**
      * Get the name of the package hosting the services shared library.
+     * <p>
+     * Note that this package is no longer a shared library since Android R. It is now a package
+     * that hosts for a bunch of updatable services that the system binds to.
      *
      * @return The library host package.
      *
@@ -6046,6 +6109,11 @@
      * If the calling application does not hold the INSTALL_PACKAGES permission then
      * the result will always return {@code null} from
      * {@link InstallSourceInfo#getOriginatingPackageName()}.
+     * <p>
+     * If the package that requested the install has been uninstalled, then information about it
+     * will only be returned from {@link InstallSourceInfo#getInitiatingPackageName()} and
+     * {@link InstallSourceInfo#getInitiatingPackageSigningInfo()} if the calling package is
+     * requesting its own install information and is not an instant app.
      *
      * @param packageName The name of the package to query
      * @throws NameNotFoundException if the given package name is not installed
@@ -7588,4 +7656,19 @@
             "sendDeviceCustomizationReadyBroadcast not implemented in subclass");
     }
 
+    /**
+     * Returns if the provided drawable represents the default activity icon provided by the system.
+     *
+     * PackageManager provides a default icon for any package/activity if the app itself does not
+     * define one or if the system encountered any error when loading the icon.
+     *
+     * @return true if the drawable represents the default activity icon, false otherwise
+     * @see #getDefaultActivityIcon()
+     */
+    public boolean isDefaultApplicationIcon(@NonNull Drawable drawable) {
+        int resId = drawable instanceof AdaptiveIconDrawable
+                ? ((AdaptiveIconDrawable) drawable).getSourceDrawableResId() : Resources.ID_NULL;
+        return resId == com.android.internal.R.drawable.sym_def_app_icon
+                || resId == com.android.internal.R.drawable.sym_app_on_sd_unavailable_icon;
+    }
 }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 32803ab..fd4c265 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -59,7 +59,6 @@
 import android.content.pm.PackageParserCacheHelper.WriteHelper;
 import android.content.pm.parsing.AndroidPackage;
 import android.content.pm.parsing.ApkParseUtils;
-import android.content.pm.parsing.ComponentParseUtils;
 import android.content.pm.parsing.PackageImpl;
 import android.content.pm.parsing.PackageInfoUtils;
 import android.content.pm.parsing.ParsedPackage;
@@ -4350,6 +4349,7 @@
         a.info.directBootAware = false;
         a.info.rotationAnimation = ROTATION_ANIMATION_UNSPECIFIED;
         a.info.colorMode = ActivityInfo.COLOR_MODE_DEFAULT;
+        a.info.preferMinimalPostProcessing = ActivityInfo.MINIMAL_POST_PROCESSING_DEFAULT;
         if (hardwareAccelerated) {
             a.info.flags |= ActivityInfo.FLAG_HARDWARE_ACCELERATED;
         }
@@ -4564,6 +4564,10 @@
             a.info.colorMode = sa.getInt(R.styleable.AndroidManifestActivity_colorMode,
                     ActivityInfo.COLOR_MODE_DEFAULT);
 
+            a.info.preferMinimalPostProcessing = sa.getBoolean(
+                    R.styleable.AndroidManifestActivity_preferMinimalPostProcessing,
+                    ActivityInfo.MINIMAL_POST_PROCESSING_DEFAULT);
+
             if (sa.getBoolean(R.styleable.AndroidManifestActivity_showWhenLocked, false)) {
                 a.info.flags |= ActivityInfo.FLAG_SHOW_WHEN_LOCKED;
             }
@@ -7803,7 +7807,7 @@
             ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName);
         }
         ai.seInfoUser = SELinuxUtil.assignSeinfoUser(state);
-        ai.resourceDirs = state.overlayPaths;
+        ai.resourceDirs = state.getAllOverlayPaths();
         ai.icon = (sUseRoundIcon && ai.roundIconRes != 0) ? ai.roundIconRes : ai.iconRes;
     }
 
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index f0f6753..da7623a 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -46,6 +46,8 @@
 
 import java.io.IOException;
 import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -77,7 +79,9 @@
     public ArraySet<String> disabledComponents;
     public ArraySet<String> enabledComponents;
 
-    public String[] overlayPaths;
+    private String[] overlayPaths;
+    private ArrayMap<String, String[]> sharedLibraryOverlayPaths; // Lib name to overlay paths
+    private String[] cachedOverlayPaths;
 
     @UnsupportedAppUsage
     public PackageUserState() {
@@ -112,9 +116,33 @@
         enabledComponents = ArrayUtils.cloneOrNull(o.enabledComponents);
         overlayPaths =
             o.overlayPaths == null ? null : Arrays.copyOf(o.overlayPaths, o.overlayPaths.length);
+        if (o.sharedLibraryOverlayPaths != null) {
+            sharedLibraryOverlayPaths = new ArrayMap<>(o.sharedLibraryOverlayPaths);
+        }
         harmfulAppWarning = o.harmfulAppWarning;
     }
 
+    public String[] getOverlayPaths() {
+        return overlayPaths;
+    }
+
+    public void setOverlayPaths(String[] paths) {
+        overlayPaths = paths;
+        cachedOverlayPaths = null;
+    }
+
+    public Map<String, String[]> getSharedLibraryOverlayPaths() {
+        return sharedLibraryOverlayPaths;
+    }
+
+    public void setSharedLibraryOverlayPaths(String library, String[] paths) {
+        if (sharedLibraryOverlayPaths == null) {
+            sharedLibraryOverlayPaths = new ArrayMap<>();
+        }
+        sharedLibraryOverlayPaths.put(library, paths);
+        cachedOverlayPaths = null;
+    }
+
     /**
      * Test if this package is installed.
      */
@@ -235,6 +263,38 @@
         return isComponentEnabled;
     }
 
+    public String[] getAllOverlayPaths() {
+        if (overlayPaths == null && sharedLibraryOverlayPaths == null) {
+            return null;
+        }
+
+        if (cachedOverlayPaths != null) {
+            return cachedOverlayPaths;
+        }
+
+        final LinkedHashSet<String> paths = new LinkedHashSet<>();
+        if (overlayPaths != null) {
+            final int N = overlayPaths.length;
+            for (int i = 0; i < N; i++) {
+                paths.add(overlayPaths[i]);
+            }
+        }
+
+        if (sharedLibraryOverlayPaths != null) {
+            for (String[] libOverlayPaths : sharedLibraryOverlayPaths.values()) {
+                if (libOverlayPaths != null) {
+                    final int N = libOverlayPaths.length;
+                    for (int i = 0; i < N; i++) {
+                        paths.add(libOverlayPaths[i]);
+                    }
+                }
+            }
+        }
+
+        cachedOverlayPaths = paths.toArray(new String[0]);
+        return cachedOverlayPaths;
+    }
+
     @Override
     final public boolean equals(Object obj) {
         if (!(obj instanceof PackageUserState)) {
diff --git a/core/java/android/content/pm/ProcessInfo.java b/core/java/android/content/pm/ProcessInfo.java
new file mode 100644
index 0000000..c77a267
--- /dev/null
+++ b/core/java/android/content/pm/ProcessInfo.java
@@ -0,0 +1,88 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.ArraySet;
+
+/**
+ * Information about a process an app may run.  This corresponds to information collected from the
+ * AndroidManifest.xml's &lt;permission-group&gt; tags.
+ * @hide
+ */
+public class ProcessInfo implements Parcelable {
+    /**
+     * The name of the process, fully-qualified based on the app's package name.
+     */
+    public String name;
+
+    /**
+     * If non-null, these are permissions that are not allowed in this process.
+     */
+    @Nullable
+    public ArraySet<String> deniedPermissions;
+
+    public ProcessInfo(String name, ArraySet<String> deniedPermissions) {
+        this.name = name;
+        this.deniedPermissions = deniedPermissions;
+    }
+
+    @Deprecated
+    public ProcessInfo(@NonNull ProcessInfo orig) {
+        this.name = orig.name;
+        this.deniedPermissions = orig.deniedPermissions;
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int parcelableFlags) {
+        dest.writeString(this.name);
+        final int numDenied = this.deniedPermissions != null
+                ? this.deniedPermissions.size() : 0;
+        dest.writeInt(numDenied);
+        for (int i = 0; i < numDenied; i++) {
+            dest.writeString(this.deniedPermissions.valueAt(i));
+        }
+    }
+
+    public static final @NonNull Creator<ProcessInfo> CREATOR =
+            new Creator<ProcessInfo>() {
+                public ProcessInfo createFromParcel(Parcel source) {
+                    return new ProcessInfo(source);
+                }
+                public ProcessInfo[] newArray(int size) {
+                    return new ProcessInfo[size];
+                }
+            };
+
+    private ProcessInfo(Parcel source) {
+        this.name = source.readString();
+        final int numDenied = source.readInt();
+        if (numDenied > 0) {
+            this.deniedPermissions = new ArraySet<>(numDenied);
+            for (int i = numDenied - 1; i >= 0; i--) {
+                this.deniedPermissions.add(TextUtils.safeIntern(source.readString()));
+            }
+        }
+    }
+}
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 00507e1..5f90b6c 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -147,6 +147,28 @@
     public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION = 1 << 5;
 
     /**
+     * Constant corresponding to {@code camera} in
+     * the {@link android.R.attr#foregroundServiceType} attribute.
+     * Use the camera device or record video.
+     * For apps with <code>targetSdkVersion</code> {@link android.os.Build.VERSION_CODES#R} and
+     * above, a foreground service will not be able to access the camera if this type is not
+     * specified in the manifest and in
+     * {@link android.app.Service#startForeground(int, android.app.Notification, int)}.
+     */
+    public static final int FOREGROUND_SERVICE_TYPE_CAMERA = 1 << 6;
+
+    /**
+     * Constant corresponding to {@code microphone} in
+     * the {@link android.R.attr#foregroundServiceType} attribute.
+     * Use the microphone device or record audio.
+     * For apps with <code>targetSdkVersion</code> {@link android.os.Build.VERSION_CODES#R} and
+     * above, a foreground service will not be able to access the microphone if this type is not
+     * specified in the manifest and in
+     * {@link android.app.Service#startForeground(int, android.app.Notification, int)}.
+     */
+    public static final int FOREGROUND_SERVICE_TYPE_MICROPHONE = 1 << 7;
+
+    /**
      * A special value indicates to use all types set in manifest file.
      */
     public static final int FOREGROUND_SERVICE_TYPE_MANIFEST = -1;
@@ -166,6 +188,8 @@
             FOREGROUND_SERVICE_TYPE_LOCATION,
             FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE,
             FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION,
+            FOREGROUND_SERVICE_TYPE_CAMERA,
+            FOREGROUND_SERVICE_TYPE_MICROPHONE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ForegroundServiceType {}
diff --git a/core/java/android/content/pm/parsing/AndroidPackage.java b/core/java/android/content/pm/parsing/AndroidPackage.java
index 990c835..fbe5a48 100644
--- a/core/java/android/content/pm/parsing/AndroidPackage.java
+++ b/core/java/android/content/pm/parsing/AndroidPackage.java
@@ -36,6 +36,7 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.SparseArray;
 
@@ -379,6 +380,9 @@
     @Nullable
     long[] getUsesStaticLibrariesVersions();
 
+    @Nullable
+    ArrayMap<String, ComponentParseUtils.ParsedProcess> getProcesses();
+
     int getVersionCode();
 
     int getVersionCodeMajor();
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index ac2e373..6639b3d 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -18,7 +18,7 @@
 
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageParser;
diff --git a/core/java/android/content/pm/parsing/ApkParseUtils.java b/core/java/android/content/pm/parsing/ApkParseUtils.java
index a001ada..3018230 100644
--- a/core/java/android/content/pm/parsing/ApkParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkParseUtils.java
@@ -65,6 +65,7 @@
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.Trace;
+import android.os.ext.SdkExtensions;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -1615,11 +1616,72 @@
                 );
             }
 
+            int type;
+            final int innerDepth = parser.getDepth();
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                    && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
+                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                    continue;
+                }
+                if (parser.getName().equals("extension-sdk")) {
+                    final ParseResult result =
+                            parseExtensionSdk(parseInput, parsingPackage, res, parser);
+                    if (!result.isSuccess()) {
+                        return result;
+                    }
+                } else {
+                    Slog.w(TAG, "Unknown element under <uses-sdk>: " + parser.getName()
+                            + " at " + parsingPackage.getBaseCodePath() + " "
+                            + parser.getPositionDescription());
+                }
+                XmlUtils.skipCurrentTag(parser);
+            }
+
             parsingPackage.setMinSdkVersion(minSdkVersion)
                     .setTargetSdkVersion(targetSdkVersion);
         }
+        return parseInput.success(parsingPackage);
+    }
 
-        XmlUtils.skipCurrentTag(parser);
+    private static ParseResult parseExtensionSdk(
+            ParseInput parseInput,
+            ParsingPackage parsingPackage,
+            Resources res,
+            XmlResourceParser parser
+    ) throws IOException, XmlPullParserException {
+        TypedArray sa = res.obtainAttributes(parser,
+                com.android.internal.R.styleable.AndroidManifestExtensionSdk);
+        int sdkVersion = sa.getInt(
+                com.android.internal.R.styleable.AndroidManifestExtensionSdk_sdkVersion, -1);
+        int minVersion = sa.getInt(
+                com.android.internal.R.styleable.AndroidManifestExtensionSdk_minExtensionVersion,
+                -1);
+        sa.recycle();
+
+        if (sdkVersion < 0) {
+            return parseInput.error(
+                PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+                "<extension-sdk> must specify an sdkVersion >= 0");
+        }
+        if (minVersion < 0) {
+            return parseInput.error(
+                PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+                "<extension-sdk> must specify minExtensionVersion >= 0");
+        }
+
+        try {
+            int version = SdkExtensions.getExtensionVersion(sdkVersion);
+            if (version < minVersion) {
+                return parseInput.error(
+                        PackageManager.INSTALL_FAILED_OLDER_SDK,
+                        "Package requires " + sdkVersion + " extension version " + minVersion
+                                + " which exceeds device version " + version);
+            }
+        } catch (RuntimeException e) {
+            return parseInput.error(
+                    PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+                    "Specified sdkVersion " + sdkVersion + " is not valid");
+        }
         return parseInput.success(parsingPackage);
     }
 
@@ -2364,6 +2426,21 @@
                     XmlUtils.skipCurrentTag(parser);
 
                     break;
+                case "processes":
+                    ArrayMap<String, ComponentParseUtils.ParsedProcess> processes =
+                            ComponentParseUtils.parseProcesses(separateProcesses,
+                                    parsingPackage,
+                                    res, parser, flags,
+                                    outError);
+                    if (processes == null) {
+                        return parseInput.error(
+                                PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+                                outError[0]
+                        );
+                    }
+
+                    parsingPackage.setProcesses(processes);
+                    break;
                 case "uses-package":
                     // Dependencies for app installers; we don't currently try to
                     // enforce this.
@@ -2578,6 +2655,7 @@
         activity.directBootAware = false;
         activity.rotationAnimation = ROTATION_ANIMATION_UNSPECIFIED;
         activity.colorMode = ActivityInfo.COLOR_MODE_DEFAULT;
+        activity.preferMinimalPostProcessing = ActivityInfo.MINIMAL_POST_PROCESSING_DEFAULT;
         if (hardwareAccelerated) {
             activity.flags |= ActivityInfo.FLAG_HARDWARE_ACCELERATED;
         }
diff --git a/core/java/android/content/pm/parsing/ComponentParseUtils.java b/core/java/android/content/pm/parsing/ComponentParseUtils.java
index 5956857..9a0a6d5 100644
--- a/core/java/android/content/pm/parsing/ComponentParseUtils.java
+++ b/core/java/android/content/pm/parsing/ComponentParseUtils.java
@@ -27,8 +27,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StringRes;
-import android.annotation.UnsupportedAppUsage;
 import android.app.ActivityTaskManager;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -50,6 +50,7 @@
 import android.os.Parcelable;
 import android.os.PatternMatcher;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -58,6 +59,7 @@
 import android.view.Gravity;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.XmlUtils;
 
@@ -552,6 +554,7 @@
         public String requestedVrComponent;
         public int rotationAnimation = -1;
         public int colorMode;
+        public boolean preferMinimalPostProcessing;
         public int order;
 
         public ActivityInfo.WindowLayout windowLayout;
@@ -639,6 +642,7 @@
             dest.writeString(this.requestedVrComponent);
             dest.writeInt(this.rotationAnimation);
             dest.writeInt(this.colorMode);
+            dest.writeBoolean(this.preferMinimalPostProcessing);
             dest.writeInt(this.order);
             dest.writeBundle(this.metaData);
 
@@ -684,6 +688,7 @@
             this.requestedVrComponent = in.readString();
             this.rotationAnimation = in.readInt();
             this.colorMode = in.readInt();
+            this.preferMinimalPostProcessing = in.readByte() != 0;
             this.order = in.readInt();
             this.metaData = in.readBundle();
             if (in.readInt() == 1) {
@@ -814,6 +819,11 @@
             return exported;
         }
 
+        @VisibleForTesting
+        public void setExported(boolean exported) {
+            this.exported = exported;
+        }
+
         public List<ParsedProviderIntentInfo> getIntents() {
             return intents;
         }
@@ -1357,6 +1367,72 @@
                 };
     }
 
+    public static class ParsedProcess implements Parcelable {
+
+        public String name;
+        @Nullable
+        public ArraySet<String> deniedPermissions;
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeString(this.name);
+            final int numDenied = this.deniedPermissions != null
+                    ? this.deniedPermissions.size() : 0;
+            dest.writeInt(numDenied);
+            for (int i = 0; i < numDenied; i++) {
+                dest.writeString(this.deniedPermissions.valueAt(i));
+            }
+        }
+
+        public ParsedProcess() {
+        }
+
+        public ParsedProcess(@NonNull ParsedProcess other) {
+            name = other.name;
+            if (other.deniedPermissions != null) {
+                deniedPermissions = new ArraySet<>(other.deniedPermissions);
+            }
+        }
+
+        public void addStateFrom(@NonNull ParsedProcess other) {
+            if (other.deniedPermissions != null) {
+                for (int i = other.deniedPermissions.size() - 1; i >= 0; i--) {
+                    if (deniedPermissions == null) {
+                        deniedPermissions = new ArraySet<>(other.deniedPermissions.size());
+                    }
+                    deniedPermissions.add(other.deniedPermissions.valueAt(i));
+                }
+            }
+        }
+
+        protected ParsedProcess(Parcel in) {
+            this.name = TextUtils.safeIntern(in.readString());
+            final int numDenied = in.readInt();
+            if (numDenied > 0) {
+                this.deniedPermissions = new ArraySet<>(numDenied);
+                this.deniedPermissions.add(TextUtils.safeIntern(in.readString()));
+            }
+        }
+
+        public static final Creator<ParsedProcess> CREATOR =
+                new Creator<ParsedProcess>() {
+                    @Override
+                    public ParsedProcess createFromParcel(Parcel source) {
+                        return new ParsedProcess(source);
+                    }
+
+                    @Override
+                    public ParsedProcess[] newArray(int size) {
+                        return new ParsedProcess[size];
+                    }
+                };
+    }
+
     public static ParsedActivity parseActivity(
             String[] separateProcesses,
             ParsingPackage parsingPackage,
@@ -1639,6 +1715,10 @@
                 result.colorMode = sa.getInt(R.styleable.AndroidManifestActivity_colorMode,
                         ActivityInfo.COLOR_MODE_DEFAULT);
 
+                result.preferMinimalPostProcessing = sa.getBoolean(
+                        R.styleable.AndroidManifestActivity_preferMinimalPostProcessing,
+                        ActivityInfo.MINIMAL_POST_PROCESSING_DEFAULT);
+
                 if (sa.getBoolean(R.styleable.AndroidManifestActivity_showWhenLocked, false)) {
                     result.flags |= ActivityInfo.FLAG_SHOW_WHEN_LOCKED;
                 }
@@ -3253,6 +3333,189 @@
         return result;
     }
 
+    private static @Nullable ArraySet<String> parseDenyPermission(
+            ArraySet<String> perms,
+            Resources res,
+            XmlResourceParser parser,
+            String[] outError
+    ) throws IOException, XmlPullParserException {
+        TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestDenyPermission);
+        if (sa == null) {
+            outError[0] = "<deny-permission> could not be parsed";
+            return null;
+        }
+
+        try {
+            String perm = sa.getNonConfigurationString(
+                    R.styleable.AndroidManifestDenyPermission_name,0);
+            if (perm != null && perm.equals(android.Manifest.permission.INTERNET)) {
+                if (perms == null) {
+                    perms = new ArraySet<>();
+                }
+                perms.add(perm);
+            }
+        } finally {
+            sa.recycle();
+        }
+        XmlUtils.skipCurrentTag(parser);
+        return perms;
+    }
+
+    private static ArraySet<String> parseAllowPermission(
+            ArraySet<String> perms,
+            Resources res,
+            XmlResourceParser parser,
+            String[] outError
+    ) throws IOException, XmlPullParserException {
+        TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestAllowPermission);
+        if (sa == null) {
+            outError[0] = "<allow-permission> could not be parsed";
+            return null;
+        }
+
+        try {
+            String perm = sa.getNonConfigurationString(
+                    R.styleable.AndroidManifestAllowPermission_name,0);
+            if (perm != null && perm.equals(android.Manifest.permission.INTERNET)
+                    && perms != null) {
+                perms.remove(perm);
+                if (perms.size() <= 0) {
+                    perms = null;
+                }
+            }
+        } finally {
+            sa.recycle();
+        }
+        XmlUtils.skipCurrentTag(parser);
+        return perms;
+    }
+
+    public static ParsedProcess parseProcess(
+            ArraySet<String> perms,
+            String[] separateProcesses,
+            ParsingPackage parsingPackage,
+            Resources res,
+            XmlResourceParser parser,
+            int flags,
+            String[] outError
+    ) throws IOException, XmlPullParserException {
+        TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestProcess);
+        if (sa == null) {
+            outError[0] = "<process> could not be parsed";
+            return null;
+        }
+
+        ParsedProcess proc = new ParsedProcess();
+        if (perms != null) {
+            proc.deniedPermissions = new ArraySet(perms);
+        }
+
+        try {
+            proc.name = sa.getNonConfigurationString(
+                    R.styleable.AndroidManifestProcess_process,0);
+            proc.name = PackageParser.buildProcessName(parsingPackage.getPackageName(),
+                    null, proc.name, flags, separateProcesses, outError);
+
+            if (proc.name == null || proc.name.length() <= 0) {
+                outError[0] = "<process> does not specify android:process";
+                return null;
+            }
+            proc.name = PackageParser.buildProcessName(parsingPackage.getPackageName(),
+                    parsingPackage.getPackageName(), proc.name,
+                    flags, separateProcesses, outError);
+            if (outError[0] != null) {
+                return null;
+            }
+        } finally {
+            sa.recycle();
+        }
+
+        int type;
+        final int innerDepth = parser.getDepth();
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("deny-permission")) {
+                proc.deniedPermissions = parseDenyPermission(proc.deniedPermissions, res, parser,
+                        outError);
+                if (outError[0] != null) {
+                    return null;
+                }
+            } else if (tagName.equals("allow-permission")) {
+                proc.deniedPermissions = parseAllowPermission(proc.deniedPermissions, res, parser,
+                        outError);
+                if (outError[0] != null) {
+                    return null;
+                }
+            } else {
+                Slog.w(TAG, "Unknown element under <process>: " + tagName
+                        + " at " + parsingPackage.getBaseCodePath() + " "
+                        + parser.getPositionDescription());
+                XmlUtils.skipCurrentTag(parser);
+                continue;
+            }
+        }
+
+        return proc;
+    }
+
+    public static ArrayMap<String, ParsedProcess> parseProcesses(
+            String[] separateProcesses,
+            ParsingPackage parsingPackage,
+            Resources res,
+            XmlResourceParser parser,
+            int flags,
+            String[] outError
+    ) throws IOException, XmlPullParserException {
+        ArraySet<String> deniedPerms = null;
+        ArrayMap<String, ParsedProcess> processes = new ArrayMap<>();
+
+        int type;
+        final int innerDepth = parser.getDepth();
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("deny-permission")) {
+                deniedPerms = parseDenyPermission(deniedPerms, res, parser, outError);
+                if (outError[0] != null) {
+                    return null;
+                }
+            } else if (tagName.equals("allow-permission")) {
+                deniedPerms = parseAllowPermission(deniedPerms, res, parser, outError);
+                if (outError[0] != null) {
+                    return null;
+                }
+            } else if (tagName.equals("process")) {
+                ParsedProcess proc = parseProcess(deniedPerms, separateProcesses, parsingPackage,
+                        res, parser, flags, outError);
+                if (outError[0] != null) {
+                    return null;
+                }
+                if (processes.get(proc.name) != null) {
+                    outError[0] = "<process> specified existing name '" + proc.name + "'";
+                    return null;
+                }
+                processes.put(proc.name, proc);
+            } else {
+                Slog.w(TAG, "Unknown element under <processes>: " + tagName
+                        + " at " + parsingPackage.getBaseCodePath() + " "
+                        + parser.getPositionDescription());
+                XmlUtils.skipCurrentTag(parser);
+                continue;
+            }
+        }
+
+        return processes;
+    }
+
     public static ActivityInfo.WindowLayout parseLayout(Resources res, AttributeSet attrs) {
         TypedArray sw = res.obtainAttributes(attrs,
                 R.styleable.AndroidManifestLayout);
diff --git a/core/java/android/content/pm/parsing/PackageImpl.java b/core/java/android/content/pm/parsing/PackageImpl.java
index 8677fce..9baf325 100644
--- a/core/java/android/content/pm/parsing/PackageImpl.java
+++ b/core/java/android/content/pm/parsing/PackageImpl.java
@@ -215,6 +215,9 @@
     @Nullable
     private ArrayList<String> queriesPackages;
 
+    @Nullable
+    private ArrayMap<String, ComponentParseUtils.ParsedProcess> processes;
+
     private String[] splitClassLoaderNames;
     private String[] splitCodePaths;
     private SparseArray<int[]> splitDependencies;
@@ -527,6 +530,12 @@
         return usesStaticLibraries;
     }
 
+    @Nullable
+    @Override
+    public ArrayMap<String, ComponentParseUtils.ParsedProcess> getProcesses() {
+        return processes;
+    }
+
     @Override
     public boolean isBaseHardwareAccelerated() {
         return baseHardwareAccelerated;
@@ -948,6 +957,12 @@
     }
 
     @Override
+    public PackageImpl setProcesses(ArrayMap<String, ComponentParseUtils.ParsedProcess> processes) {
+        this.processes = processes;
+        return this;
+    }
+
+    @Override
     public PackageImpl setSupportsSmallScreens(int supportsSmallScreens) {
         if (supportsSmallScreens == 1) {
             return this;
@@ -3010,6 +3025,11 @@
         dest.writeStringList(this.usesOptionalLibraries);
         dest.writeStringList(this.usesStaticLibraries);
         dest.writeLongArray(this.usesStaticLibrariesVersions);
+        final int numProcesses = this.processes != null ? this.processes.size() : 0;
+        dest.writeInt(numProcesses);
+        for (int i = 0; i < numProcesses; i++) {
+            this.processes.valueAt(i).writeToParcel(dest, 0);
+        }
 
         if (this.usesStaticLibrariesCertDigests == null) {
             dest.writeInt(-1);
@@ -3161,6 +3181,16 @@
         this.usesStaticLibraries = in.createStringArrayList();
         internStringArrayList(usesStaticLibraries);
         this.usesStaticLibrariesVersions = in.createLongArray();
+        final int numProcesses = in.readInt();
+        if (numProcesses > 0) {
+            this.processes = new ArrayMap<>(numProcesses);
+            for (int i = 0; i < numProcesses; i++) {
+                ComponentParseUtils.ParsedProcess proc = new ComponentParseUtils.ParsedProcess(in);
+                this.processes.put(proc.name, proc);
+            }
+        } else {
+            this.processes = null;
+        }
 
         int digestsSize = in.readInt();
         if (digestsSize >= 0) {
diff --git a/core/java/android/content/pm/parsing/PackageInfoUtils.java b/core/java/android/content/pm/parsing/PackageInfoUtils.java
index f2cf9a4..72df189 100644
--- a/core/java/android/content/pm/parsing/PackageInfoUtils.java
+++ b/core/java/android/content/pm/parsing/PackageInfoUtils.java
@@ -32,6 +32,7 @@
 import android.content.pm.PackageUserState;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
+import android.content.pm.ProcessInfo;
 import android.content.pm.ProviderInfo;
 import android.content.pm.SELinuxUtil;
 import android.content.pm.ServiceInfo;
@@ -41,6 +42,8 @@
 import android.content.pm.parsing.ComponentParseUtils.ParsedInstrumentation;
 import android.content.pm.parsing.ComponentParseUtils.ParsedPermission;
 import android.content.pm.parsing.ComponentParseUtils.ParsedPermissionGroup;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 
 import com.android.internal.util.ArrayUtils;
 
@@ -343,6 +346,7 @@
         ai.requestedVrComponent = a.requestedVrComponent;
         ai.rotationAnimation = a.rotationAnimation;
         ai.colorMode = a.colorMode;
+        ai.preferMinimalPostProcessing = a.preferMinimalPostProcessing;
         ai.windowLayout = a.windowLayout;
         ai.metaData = a.metaData;
         ai.applicationInfo = applicationInfo;
@@ -456,6 +460,24 @@
         return ii;
     }
 
+    public static ArrayMap<String, ProcessInfo> generateProcessInfo(
+            ArrayMap<String, ComponentParseUtils.ParsedProcess> procs,
+            @PackageManager.ComponentInfoFlags int flags) {
+        if (procs == null) {
+            return null;
+        }
+
+        final int numProcs = procs.size();
+        ArrayMap<String, ProcessInfo> retProcs = new ArrayMap(numProcs);
+        for (int i = 0; i < numProcs; i++) {
+            ComponentParseUtils.ParsedProcess proc = procs.valueAt(i);
+            retProcs.put(proc.name, new ProcessInfo(proc.name,
+                    proc.deniedPermissions != null
+                            ? new ArraySet<>(proc.deniedPermissions) : null));
+        }
+        return retProcs;
+    }
+
     public static PermissionInfo generatePermissionInfo(ParsedPermission p,
             @PackageManager.ComponentInfoFlags int flags) {
         if (p == null) return null;
@@ -545,7 +567,7 @@
             ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName);
         }
         ai.seInfoUser = SELinuxUtil.assignSeinfoUser(state);
-        ai.resourceDirs = state.overlayPaths;
+        ai.resourceDirs = state.getAllOverlayPaths();
         ai.icon = (PackageParser.sUseRoundIcon && ai.roundIconRes != 0)
                 ? ai.roundIconRes : ai.iconRes;
     }
diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java
index 411c749..9ddcc09 100644
--- a/core/java/android/content/pm/parsing/ParsingPackage.java
+++ b/core/java/android/content/pm/parsing/ParsingPackage.java
@@ -31,6 +31,7 @@
 import android.content.pm.parsing.ComponentParseUtils.ParsedProvider;
 import android.content.pm.parsing.ComponentParseUtils.ParsedService;
 import android.os.Bundle;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.SparseArray;
 
@@ -99,6 +100,8 @@
 
     ParsingPackage addQueriesPackage(String packageName);
 
+    ParsingPackage setProcesses(ArrayMap<String, ComponentParseUtils.ParsedProcess> processes);
+
     ParsingPackage asSplit(
             String[] splitNames,
             String[] splitCodePaths,
diff --git a/core/java/android/content/res/TEST_MAPPING b/core/java/android/content/res/TEST_MAPPING
new file mode 100644
index 0000000..daf9a14
--- /dev/null
+++ b/core/java/android/content/res/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksResourceLoaderTests"
+    }
+  ]
+}
diff --git a/core/java/android/content/rollback/PackageRollbackInfo.java b/core/java/android/content/rollback/PackageRollbackInfo.java
index 6378db0..b273cd6 100644
--- a/core/java/android/content/rollback/PackageRollbackInfo.java
+++ b/core/java/android/content/rollback/PackageRollbackInfo.java
@@ -76,6 +76,11 @@
      */
     private final boolean mIsApex;
 
+    /**
+     * Whether this instance represents the PackageRollbackInfo for an APK in APEX.
+     */
+    private final boolean mIsApkInApex;
+
     /*
      * The list of users for which snapshots have been saved.
      */
@@ -157,6 +162,10 @@
     public @PackageManager.RollbackDataPolicy int getRollbackDataPolicy() {
         return mRollbackDataPolicy;
     }
+    /** @hide */
+    public boolean isApkInApex() {
+        return mIsApkInApex;
+    }
 
     /** @hide */
     public IntArray getSnapshottedUsers() {
@@ -190,17 +199,18 @@
     public PackageRollbackInfo(VersionedPackage packageRolledBackFrom,
             VersionedPackage packageRolledBackTo,
             @NonNull IntArray pendingBackups, @NonNull ArrayList<RestoreInfo> pendingRestores,
-            boolean isApex, @NonNull IntArray snapshottedUsers,
+            boolean isApex, boolean isApkInApex, @NonNull IntArray snapshottedUsers,
             @NonNull SparseLongArray ceSnapshotInodes) {
         this(packageRolledBackFrom, packageRolledBackTo, pendingBackups, pendingRestores, isApex,
-                snapshottedUsers, ceSnapshotInodes, PackageManager.RollbackDataPolicy.RESTORE);
+                isApkInApex, snapshottedUsers, ceSnapshotInodes,
+                PackageManager.RollbackDataPolicy.RESTORE);
     }
 
     /** @hide */
     public PackageRollbackInfo(VersionedPackage packageRolledBackFrom,
             VersionedPackage packageRolledBackTo,
             @NonNull IntArray pendingBackups, @NonNull ArrayList<RestoreInfo> pendingRestores,
-            boolean isApex, @NonNull IntArray snapshottedUsers,
+            boolean isApex, boolean isApkInApex, @NonNull IntArray snapshottedUsers,
             @NonNull SparseLongArray ceSnapshotInodes,
             @PackageManager.RollbackDataPolicy int rollbackDataPolicy) {
         this.mVersionRolledBackFrom = packageRolledBackFrom;
@@ -209,6 +219,7 @@
         this.mPendingRestores = pendingRestores;
         this.mIsApex = isApex;
         this.mRollbackDataPolicy = rollbackDataPolicy;
+        this.mIsApkInApex = isApkInApex;
         this.mSnapshottedUsers = snapshottedUsers;
         this.mCeSnapshotInodes = ceSnapshotInodes;
     }
@@ -217,6 +228,7 @@
         this.mVersionRolledBackFrom = VersionedPackage.CREATOR.createFromParcel(in);
         this.mVersionRolledBackTo = VersionedPackage.CREATOR.createFromParcel(in);
         this.mIsApex = in.readBoolean();
+        this.mIsApkInApex = in.readBoolean();
         this.mPendingRestores = null;
         this.mPendingBackups = null;
         this.mSnapshottedUsers = null;
@@ -234,6 +246,7 @@
         mVersionRolledBackFrom.writeToParcel(out, flags);
         mVersionRolledBackTo.writeToParcel(out, flags);
         out.writeBoolean(mIsApex);
+        out.writeBoolean(mIsApkInApex);
     }
 
     public static final @NonNull Parcelable.Creator<PackageRollbackInfo> CREATOR =
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index f722275..796cfdc 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -39,6 +39,8 @@
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.Map;
+import java.util.function.BinaryOperator;
+import java.util.function.UnaryOperator;
 
 /**
  * Represents a SQLite database connection.
@@ -123,8 +125,10 @@
             boolean enableTrace, boolean enableProfile, int lookasideSlotSize,
             int lookasideSlotCount);
     private static native void nativeClose(long connectionPtr);
-    private static native void nativeRegisterCustomFunction(long connectionPtr,
-            SQLiteCustomFunction function);
+    private static native void nativeRegisterCustomScalarFunction(long connectionPtr,
+            String name, UnaryOperator<String> function);
+    private static native void nativeRegisterCustomAggregateFunction(long connectionPtr,
+            String name, BinaryOperator<String> function);
     private static native void nativeRegisterLocalizedCollators(long connectionPtr, String locale);
     private static native long nativePrepareStatement(long connectionPtr, String sql);
     private static native void nativeFinalizeStatement(long connectionPtr, long statementPtr);
@@ -225,13 +229,7 @@
         setJournalSizeLimit();
         setAutoCheckpointInterval();
         setLocaleFromConfiguration();
-
-        // Register custom functions.
-        final int functionCount = mConfiguration.customFunctions.size();
-        for (int i = 0; i < functionCount; i++) {
-            SQLiteCustomFunction function = mConfiguration.customFunctions.get(i);
-            nativeRegisterCustomFunction(mConnectionPtr, function);
-        }
+        setCustomFunctionsFromConfiguration();
     }
 
     private void dispose(boolean finalized) {
@@ -457,6 +455,19 @@
         }
     }
 
+    private void setCustomFunctionsFromConfiguration() {
+        for (int i = 0; i < mConfiguration.customScalarFunctions.size(); i++) {
+            nativeRegisterCustomScalarFunction(mConnectionPtr,
+                    mConfiguration.customScalarFunctions.keyAt(i),
+                    mConfiguration.customScalarFunctions.valueAt(i));
+        }
+        for (int i = 0; i < mConfiguration.customAggregateFunctions.size(); i++) {
+            nativeRegisterCustomAggregateFunction(mConnectionPtr,
+                    mConfiguration.customAggregateFunctions.keyAt(i),
+                    mConfiguration.customAggregateFunctions.valueAt(i));
+        }
+    }
+
     private void checkDatabaseWiped() {
         if (!SQLiteGlobal.checkDbWipe()) {
             return;
@@ -491,15 +502,6 @@
     void reconfigure(SQLiteDatabaseConfiguration configuration) {
         mOnlyAllowReadOnlyOperations = false;
 
-        // Register custom functions.
-        final int functionCount = configuration.customFunctions.size();
-        for (int i = 0; i < functionCount; i++) {
-            SQLiteCustomFunction function = configuration.customFunctions.get(i);
-            if (!mConfiguration.customFunctions.contains(function)) {
-                nativeRegisterCustomFunction(mConnectionPtr, function);
-            }
-        }
-
         // Remember what changed.
         boolean foreignKeyModeChanged = configuration.foreignKeyConstraintsEnabled
                 != mConfiguration.foreignKeyConstraintsEnabled;
@@ -507,6 +509,10 @@
                 & (SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING
                 | SQLiteDatabase.ENABLE_LEGACY_COMPATIBILITY_WAL)) != 0;
         boolean localeChanged = !configuration.locale.equals(mConfiguration.locale);
+        boolean customScalarFunctionsChanged = !configuration.customScalarFunctions
+                .equals(mConfiguration.customScalarFunctions);
+        boolean customAggregateFunctionsChanged = !configuration.customAggregateFunctions
+                .equals(mConfiguration.customAggregateFunctions);
 
         // Update configuration parameters.
         mConfiguration.updateParametersFrom(configuration);
@@ -514,20 +520,18 @@
         // Update prepared statement cache size.
         mPreparedStatementCache.resize(configuration.maxSqlCacheSize);
 
-        // Update foreign key mode.
         if (foreignKeyModeChanged) {
             setForeignKeyModeFromConfiguration();
         }
-
-        // Update WAL.
         if (walModeChanged) {
             setWalModeFromConfiguration();
         }
-
-        // Update locale.
         if (localeChanged) {
             setLocaleFromConfiguration();
         }
+        if (customScalarFunctionsChanged || customAggregateFunctionsChanged) {
+            setCustomFunctionsFromConfiguration();
+        }
     }
 
     // Called by SQLiteConnectionPool only.
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 44c78aa..458914e 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -62,6 +62,8 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.WeakHashMap;
+import java.util.function.BinaryOperator;
+import java.util.function.UnaryOperator;
 
 /**
  * Exposes methods to manage a SQLite database.
@@ -958,26 +960,87 @@
     }
 
     /**
-     * Registers a CustomFunction callback as a function that can be called from
-     * SQLite database triggers.
+     * Register a custom scalar function that can be called from SQL
+     * expressions.
+     * <p>
+     * For example, registering a custom scalar function named {@code REVERSE}
+     * could be used in a query like
+     * {@code SELECT REVERSE(name) FROM employees}.
+     * <p>
+     * When attempting to register multiple functions with the same function
+     * name, SQLite will replace any previously defined functions with the
+     * latest definition, regardless of what function type they are. SQLite does
+     * not support unregistering functions.
      *
-     * @param name the name of the sqlite3 function
-     * @param numArgs the number of arguments for the function
-     * @param function callback to call when the function is executed
-     * @hide
+     * @param functionName Case-insensitive name to register this function
+     *            under, limited to 255 UTF-8 bytes in length.
+     * @param scalarFunction Functional interface that will be invoked when the
+     *            function name is used by a SQL statement. The argument values
+     *            from the SQL statement are passed to the functional interface,
+     *            and the return values from the functional interface are
+     *            returned back into the SQL statement.
+     * @throws SQLiteException if the custom function could not be registered.
+     * @see #setCustomAggregateFunction(String, BinaryOperator)
      */
-    public void addCustomFunction(String name, int numArgs, CustomFunction function) {
-        // Create wrapper (also validates arguments).
-        SQLiteCustomFunction wrapper = new SQLiteCustomFunction(name, numArgs, function);
+    public void setCustomScalarFunction(@NonNull String functionName,
+            @NonNull UnaryOperator<String> scalarFunction) throws SQLiteException {
+        Objects.requireNonNull(functionName);
+        Objects.requireNonNull(scalarFunction);
 
         synchronized (mLock) {
             throwIfNotOpenLocked();
 
-            mConfigurationLocked.customFunctions.add(wrapper);
+            mConfigurationLocked.customScalarFunctions.put(functionName, scalarFunction);
             try {
                 mConnectionPoolLocked.reconfigure(mConfigurationLocked);
             } catch (RuntimeException ex) {
-                mConfigurationLocked.customFunctions.remove(wrapper);
+                mConfigurationLocked.customScalarFunctions.remove(functionName);
+                throw ex;
+            }
+        }
+    }
+
+    /**
+     * Register a custom aggregate function that can be called from SQL
+     * expressions.
+     * <p>
+     * For example, registering a custom aggregation function named
+     * {@code LONGEST} could be used in a query like
+     * {@code SELECT LONGEST(name) FROM employees}.
+     * <p>
+     * The implementation of this method follows the reduction flow outlined in
+     * {@link java.util.stream.Stream#reduce(BinaryOperator)}, and the custom
+     * aggregation function is expected to be an associative accumulation
+     * function, as defined by that class.
+     * <p>
+     * When attempting to register multiple functions with the same function
+     * name, SQLite will replace any previously defined functions with the
+     * latest definition, regardless of what function type they are. SQLite does
+     * not support unregistering functions.
+     *
+     * @param functionName Case-insensitive name to register this function
+     *            under, limited to 255 UTF-8 bytes in length.
+     * @param aggregateFunction Functional interface that will be invoked when
+     *            the function name is used by a SQL statement. The argument
+     *            values from the SQL statement are passed to the functional
+     *            interface, and the return values from the functional interface
+     *            are returned back into the SQL statement.
+     * @throws SQLiteException if the custom function could not be registered.
+     * @see #setCustomScalarFunction(String, UnaryOperator)
+     */
+    public void setCustomAggregateFunction(@NonNull String functionName,
+            @NonNull BinaryOperator<String> aggregateFunction) throws SQLiteException {
+        Objects.requireNonNull(functionName);
+        Objects.requireNonNull(aggregateFunction);
+
+        synchronized (mLock) {
+            throwIfNotOpenLocked();
+
+            mConfigurationLocked.customAggregateFunctions.put(functionName, aggregateFunction);
+            try {
+                mConnectionPoolLocked.reconfigure(mConfigurationLocked);
+            } catch (RuntimeException ex) {
+                mConfigurationLocked.customAggregateFunctions.remove(functionName);
                 throw ex;
             }
         }
diff --git a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
index 6a52b72..b11942a 100644
--- a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
+++ b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
@@ -17,9 +17,12 @@
 package android.database.sqlite;
 
 import android.compat.annotation.UnsupportedAppUsage;
+import android.util.ArrayMap;
 
-import java.util.ArrayList;
 import java.util.Locale;
+import java.util.Map;
+import java.util.function.BinaryOperator;
+import java.util.function.UnaryOperator;
 import java.util.regex.Pattern;
 
 /**
@@ -87,10 +90,16 @@
     public boolean foreignKeyConstraintsEnabled;
 
     /**
-     * The custom functions to register.
+     * The custom scalar functions to register.
      */
-    public final ArrayList<SQLiteCustomFunction> customFunctions =
-            new ArrayList<SQLiteCustomFunction>();
+    public final ArrayMap<String, UnaryOperator<String>> customScalarFunctions
+            = new ArrayMap<>();
+
+    /**
+     * The custom aggregate functions to register.
+     */
+    public final ArrayMap<String, BinaryOperator<String>> customAggregateFunctions
+            = new ArrayMap<>();
 
     /**
      * The size in bytes of each lookaside slot
@@ -181,8 +190,10 @@
         maxSqlCacheSize = other.maxSqlCacheSize;
         locale = other.locale;
         foreignKeyConstraintsEnabled = other.foreignKeyConstraintsEnabled;
-        customFunctions.clear();
-        customFunctions.addAll(other.customFunctions);
+        customScalarFunctions.clear();
+        customScalarFunctions.putAll(other.customScalarFunctions);
+        customAggregateFunctions.clear();
+        customAggregateFunctions.putAll(other.customAggregateFunctions);
         lookasideSlotSize = other.lookasideSlotSize;
         lookasideSlotCount = other.lookasideSlotCount;
         idleConnectionTimeoutMs = other.idleConnectionTimeoutMs;
diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
index bba14c3..36ec67e 100644
--- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java
+++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
@@ -35,8 +35,8 @@
 import libcore.util.EmptyArray;
 
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Iterator;
-import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -56,7 +56,7 @@
             "(?i)(AVG|COUNT|MAX|MIN|SUM|TOTAL|GROUP_CONCAT)\\((.+)\\)");
 
     private Map<String, String> mProjectionMap = null;
-    private List<Pattern> mProjectionGreylist = null;
+    private Collection<Pattern> mProjectionGreylist = null;
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private String mTables = "";
@@ -196,20 +196,16 @@
      * Sets a projection greylist of columns that will be allowed through, even
      * when {@link #setStrict(boolean)} is enabled. This provides a way for
      * abusive custom columns like {@code COUNT(*)} to continue working.
-     *
-     * @hide
      */
-    public void setProjectionGreylist(@Nullable List<Pattern> projectionGreylist) {
+    public void setProjectionGreylist(@Nullable Collection<Pattern> projectionGreylist) {
         mProjectionGreylist = projectionGreylist;
     }
 
     /**
      * Gets the projection greylist for the query, as last configured by
-     * {@link #setProjectionGreylist(List)}.
-     *
-     * @hide
+     * {@link #setProjectionGreylist}.
      */
-    public @Nullable List<Pattern> getProjectionGreylist() {
+    public @Nullable Collection<Pattern> getProjectionGreylist() {
         return mProjectionGreylist;
     }
 
@@ -244,25 +240,27 @@
     }
 
     /**
-     * When set, the selection is verified against malicious arguments.
-     * When using this class to create a statement using
+     * When set, the selection is verified against malicious arguments. When
+     * using this class to create a statement using
      * {@link #buildQueryString(boolean, String, String[], String, String, String, String, String)},
-     * non-numeric limits will raise an exception. If a projection map is specified, fields
-     * not in that map will be ignored.
-     * If this class is used to execute the statement directly using
+     * non-numeric limits will raise an exception. If a projection map is
+     * specified, fields not in that map will be ignored. If this class is used
+     * to execute the statement directly using
      * {@link #query(SQLiteDatabase, String[], String, String[], String, String, String)}
      * or
      * {@link #query(SQLiteDatabase, String[], String, String[], String, String, String, String)},
-     * additionally also parenthesis escaping selection are caught.
-     *
-     * To summarize: To get maximum protection against malicious third party apps (for example
-     * content provider consumers), make sure to do the following:
+     * additionally also parenthesis escaping selection are caught. To
+     * summarize: To get maximum protection against malicious third party apps
+     * (for example content provider consumers), make sure to do the following:
      * <ul>
      * <li>Set this value to true</li>
      * <li>Use a projection map</li>
-     * <li>Use one of the query overloads instead of getting the statement as a sql string</li>
+     * <li>Use one of the query overloads instead of getting the statement as a
+     * sql string</li>
      * </ul>
-     * By default, this value is false.
+     * <p>
+     * This feature is disabled by default on each newly constructed
+     * {@link SQLiteQueryBuilder} and needs to be manually enabled.
      */
     public void setStrict(boolean strict) {
         if (strict) {
@@ -287,6 +285,9 @@
      * This enforcement applies to {@link #insert}, {@link #query}, and
      * {@link #update} operations. Any enforcement failures will throw an
      * {@link IllegalArgumentException}.
+     * <p>
+     * This feature is disabled by default on each newly constructed
+     * {@link SQLiteQueryBuilder} and needs to be manually enabled.
      */
     public void setStrictColumns(boolean strictColumns) {
         if (strictColumns) {
@@ -323,6 +324,9 @@
      * {@link #delete} operations. This enforcement does not apply to trusted
      * inputs, such as those provided by {@link #appendWhere}. Any enforcement
      * failures will throw an {@link IllegalArgumentException}.
+     * <p>
+     * This feature is disabled by default on each newly constructed
+     * {@link SQLiteQueryBuilder} and needs to be manually enabled.
      */
     public void setStrictGrammar(boolean strictGrammar) {
         if (strictGrammar) {
diff --git a/core/java/android/debug/AdbManager.java b/core/java/android/debug/AdbManager.java
index ae3d794..0a76bed 100644
--- a/core/java/android/debug/AdbManager.java
+++ b/core/java/android/debug/AdbManager.java
@@ -16,15 +16,17 @@
 
 package android.debug;
 
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.content.Context;
+import android.os.RemoteException;
 
 /**
- * This class allows the control of ADB-related functions. Currently only ADB over USB is
- * supported, and none of the API is public.
- *
+ * This class allows the control of ADB-related functions.
  * @hide
  */
+@SystemApi
 @SystemService(Context.ADB_SERVICE)
 public class AdbManager {
     private static final String TAG = "AdbManager";
@@ -39,4 +41,33 @@
         mContext = context;
         mService = service;
     }
+
+    /**
+     * @return true if the device supports secure ADB over Wi-Fi.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_DEBUGGING)
+    public boolean isAdbWifiSupported() {
+        try {
+            return mService.isAdbWifiSupported();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @return true if the device supports secure ADB over Wi-Fi and device pairing by
+     * QR code.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_DEBUGGING)
+    public boolean isAdbWifiQrSupported() {
+        try {
+            return mService.isAdbWifiQrSupported();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/debug/IAdbManager.aidl b/core/java/android/debug/IAdbManager.aidl
index 79e0794..c48fc07 100644
--- a/core/java/android/debug/IAdbManager.aidl
+++ b/core/java/android/debug/IAdbManager.aidl
@@ -41,4 +41,15 @@
      * Clear all public keys installed for secure ADB debugging.
      */
     void clearDebuggingKeys();
+
+    /**
+     * Returns true if device supports secure Adb over Wi-Fi.
+     */
+    boolean isAdbWifiSupported();
+
+    /**
+     * Returns true if device supports secure Adb over Wi-Fi and device pairing by
+     * QR code.
+     */
+    boolean isAdbWifiQrSupported();
 }
diff --git a/core/java/android/hardware/biometrics/BiometricAuthenticator.java b/core/java/android/hardware/biometrics/BiometricAuthenticator.java
index 698876b..11cf2d6 100644
--- a/core/java/android/hardware/biometrics/BiometricAuthenticator.java
+++ b/core/java/android/hardware/biometrics/BiometricAuthenticator.java
@@ -18,6 +18,7 @@
 
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
+import android.hardware.biometrics.BiometricPrompt.AuthenticationResultType;
 import android.os.CancellationSignal;
 import android.os.Parcelable;
 
@@ -119,6 +120,7 @@
     class AuthenticationResult {
         private Identifier mIdentifier;
         private CryptoObject mCryptoObject;
+        private @AuthenticationResultType int mAuthenticationType;
         private int mUserId;
 
         /**
@@ -129,27 +131,41 @@
         /**
          * Authentication result
          * @param crypto
+         * @param authenticationType
          * @param identifier
          * @param userId
          * @hide
          */
-        public AuthenticationResult(CryptoObject crypto, Identifier identifier,
+        public AuthenticationResult(CryptoObject crypto,
+                @AuthenticationResultType int authenticationType, Identifier identifier,
                 int userId) {
             mCryptoObject = crypto;
+            mAuthenticationType = authenticationType;
             mIdentifier = identifier;
             mUserId = userId;
         }
 
         /**
-         * Obtain the crypto object associated with this transaction
-         * @return crypto object provided to {@link BiometricAuthenticator#authenticate(
-         * CryptoObject, CancellationSignal, Executor, AuthenticationCallback)}
+         * Provides the crypto object associated with this transaction.
+         * @return The crypto object provided to {@link BiometricPrompt#authenticate(
+         * BiometricPrompt.CryptoObject, CancellationSignal, Executor,
+         * BiometricPrompt.AuthenticationCallback)}
          */
         public CryptoObject getCryptoObject() {
             return mCryptoObject;
         }
 
         /**
+         * Provides the type of authentication (e.g. device credential or biometric) that was
+         * requested from and successfully provided by the user.
+         *
+         * @return An integer value representing the authentication method used.
+         */
+        public @AuthenticationResultType int getAuthenticationType() {
+            return mAuthenticationType;
+        }
+
+        /**
          * Obtain the biometric identifier associated with this operation. Applications are strongly
          * discouraged from associating specific identifiers with specific applications or
          * operations.
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index cb8fc8b..c686624 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -21,6 +21,7 @@
 import static android.hardware.biometrics.BiometricManager.Authenticators;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -35,11 +36,14 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.security.identity.IdentityCredential;
 import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.R;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.security.Signature;
 import java.util.concurrent.Executor;
 
@@ -397,9 +401,11 @@
             new IBiometricServiceReceiver.Stub() {
 
         @Override
-        public void onAuthenticationSucceeded() throws RemoteException {
+        public void onAuthenticationSucceeded(@AuthenticationResultType int authenticationType)
+                throws RemoteException {
             mExecutor.execute(() -> {
-                final AuthenticationResult result = new AuthenticationResult(mCryptoObject);
+                final AuthenticationResult result =
+                        new AuthenticationResult(mCryptoObject, authenticationType);
                 mAuthenticationCallback.onAuthenticationSucceeded(result);
             });
         }
@@ -550,6 +556,10 @@
             super(mac);
         }
 
+        public CryptoObject(@NonNull IdentityCredential credential) {
+            super(credential);
+        }
+
         /**
          * Get {@link Signature} object.
          * @return {@link Signature} object or null if this doesn't contain one.
@@ -573,31 +583,73 @@
         public Mac getMac() {
             return super.getMac();
         }
+
+        /**
+         * Get {@link IdentityCredential} object.
+         * @return {@link IdentityCredential} object or null if this doesn't contain one.
+         */
+        public @Nullable IdentityCredential getIdentityCredential() {
+            return super.getIdentityCredential();
+        }
     }
 
     /**
-     * Container for callback data from {@link #authenticate( CancellationSignal, Executor,
+     * Authentication type reported by {@link AuthenticationResult} when the user authenticated by
+     * entering their device PIN, pattern, or password.
+     */
+    public static final int AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL = 1;
+
+    /**
+     * Authentication type reported by {@link AuthenticationResult} when the user authenticated by
+     * presenting some form of biometric (e.g. fingerprint or face).
+     */
+    public static final int AUTHENTICATION_RESULT_TYPE_BIOMETRIC = 2;
+
+    /**
+     * An {@link IntDef} representing the type of auth, as reported by {@link AuthenticationResult}.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL, AUTHENTICATION_RESULT_TYPE_BIOMETRIC})
+    public @interface AuthenticationResultType {
+    }
+
+    /**
+     * Container for callback data from {@link #authenticate(CancellationSignal, Executor,
      * AuthenticationCallback)} and {@link #authenticate(CryptoObject, CancellationSignal, Executor,
-     * AuthenticationCallback)}
+     * AuthenticationCallback)}.
      */
     public static class AuthenticationResult extends BiometricAuthenticator.AuthenticationResult {
         /**
          * Authentication result
          * @param crypto
+         * @param authenticationType
          * @hide
          */
-        public AuthenticationResult(CryptoObject crypto) {
+        public AuthenticationResult(CryptoObject crypto,
+                @AuthenticationResultType int authenticationType) {
             // Identifier and userId is not used for BiometricPrompt.
-            super(crypto, null /* identifier */, 0 /* userId */);
+            super(crypto, authenticationType, null /* identifier */, 0 /* userId */);
         }
+
         /**
-         * Obtain the crypto object associated with this transaction
-         * @return crypto object provided to {@link #authenticate( CryptoObject, CancellationSignal,
-         * Executor, AuthenticationCallback)}
+         * Provides the crypto object associated with this transaction.
+         * @return The crypto object provided to {@link #authenticate(CryptoObject,
+         * CancellationSignal, Executor, AuthenticationCallback)}
          */
         public CryptoObject getCryptoObject() {
             return (CryptoObject) super.getCryptoObject();
         }
+
+        /**
+         * Provides the type of authentication (e.g. device credential or biometric) that was
+         * requested from and successfully provided by the user.
+         *
+         * @return An integer value representing the authentication method used.
+         */
+        public @AuthenticationResultType int getAuthenticationType() {
+            return super.getAuthenticationType();
+        }
     }
 
     /**
diff --git a/core/java/android/hardware/biometrics/CryptoObject.java b/core/java/android/hardware/biometrics/CryptoObject.java
index 787dc66..0af18df 100644
--- a/core/java/android/hardware/biometrics/CryptoObject.java
+++ b/core/java/android/hardware/biometrics/CryptoObject.java
@@ -17,6 +17,7 @@
 package android.hardware.biometrics;
 
 import android.annotation.NonNull;
+import android.security.identity.IdentityCredential;
 import android.security.keystore.AndroidKeyStoreProvider;
 
 import java.security.Signature;
@@ -26,7 +27,8 @@
 
 /**
  * A wrapper class for the crypto objects supported by BiometricPrompt and FingerprintManager.
- * Currently the framework supports {@link Signature}, {@link Cipher} and {@link Mac} objects.
+ * Currently the framework supports {@link Signature}, {@link Cipher}, {@link Mac} and
+ * {@link IdentityCredential} objects.
  * @hide
  */
 public class CryptoObject {
@@ -44,6 +46,10 @@
         mCrypto = mac;
     }
 
+    public CryptoObject(@NonNull IdentityCredential credential) {
+        mCrypto = credential;
+    }
+
     /**
      * Get {@link Signature} object.
      * @return {@link Signature} object or null if this doesn't contain one.
@@ -69,11 +75,23 @@
     }
 
     /**
+     * Get {@link IdentityCredential} object.
+     * @return {@link IdentityCredential} object or null if this doesn't contain one.
+     */
+    public IdentityCredential getIdentityCredential() {
+        return mCrypto instanceof IdentityCredential ? (IdentityCredential) mCrypto : null;
+    }
+
+    /**
      * @hide
      * @return the opId associated with this object or 0 if none
      */
     public final long getOpId() {
-        return mCrypto != null
-                ? AndroidKeyStoreProvider.getKeyStoreOperationHandle(mCrypto) : 0;
+        if (mCrypto == null) {
+            return 0;
+        } else if (mCrypto instanceof IdentityCredential) {
+            return ((IdentityCredential) mCrypto).getCredstoreOperationHandle();
+        }
+        return AndroidKeyStoreProvider.getKeyStoreOperationHandle(mCrypto);
     }
 };
diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl
index c960049..1d43aa6 100644
--- a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl
@@ -20,8 +20,8 @@
  * @hide
  */
 oneway interface IBiometricServiceReceiver {
-    // Notify BiometricPrompt that authentication was successful
-    void onAuthenticationSucceeded();
+    // Notify BiometricPrompt that authentication was successful.
+    void onAuthenticationSucceeded(int authenticationType);
     // Noties that authentication failed.
     void onAuthenticationFailed();
     // Notify BiometricPrompt that an error has occurred.
diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java
index bc7ab47..2e48ce9 100644
--- a/core/java/android/hardware/camera2/CameraCaptureSession.java
+++ b/core/java/android/hardware/camera2/CameraCaptureSession.java
@@ -19,10 +19,13 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.hardware.camera2.CameraOfflineSession;
+import android.hardware.camera2.CameraOfflineSession.CameraOfflineSessionCallback;
 import android.hardware.camera2.params.OutputConfiguration;
 import android.os.Handler;
 import android.view.Surface;
 
+import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.Executor;
 
@@ -855,6 +858,97 @@
     }
 
     /**
+     * Switch the current capture session and a given set of registered camera surfaces
+     * to offline processing mode.
+     *
+     * <p>Offline processing mode and the corresponding {@link CameraOfflineSession} differ from
+     * a regular online camera capture session in several ways. Successful offline switches will
+     * close the currently active camera capture session. Camera clients are also allowed
+     * to call {@link CameraDevice#close} while offline processing of selected capture
+     * requests is still in progress. Such side effects free device close is only possible
+     * when the offline session moves to the ready state. Once this happens, closing the camera
+     * device will not affect the pending offline requests and they must complete as normal.</p>
+     *
+     * <p>Offline processing mode switches may need several hundred milliseconds to complete
+     * as the underlying camera implementation must abort all currently active repeating requests
+     * as well as all other pending requests not specified by the client. Additionally the switch
+     * will be blocked until all requests that continue processing within the offline session
+     * acquire their initial input frame from camera sensor. The call to {@link #switchToOffline}
+     * itself is not blocking and will only trigger the offline switch sequence. Clients will
+     * be notified via {@link CameraOfflineSessionCallback#onReady} once the entire sequence is
+     * complete.</p>
+     *
+     * <p>Please note that after a successful call to this method the currently active capture
+     * session will no longer be valid and clients will begin receiving capture
+     * callbacks with a corresponding {@link CameraOfflineSession} passed as a session
+     * argument.</p>
+     *
+     * @param offlineSurfaces Client-specified collection of input/output camera registered surfaces
+     *                        that need to be switched to offline mode along with their pending
+     *                        capture requests. Do note that not all camera registered
+     *                        surfaces can be switched to offline mode. Offline processing
+     *                        support for individual surfaces can be queried using
+     *                        {@link #supportsOfflineProcessing}. Additionally offline mode
+     *                        switches are not available for shared surfaces
+     *                        {@link OutputConfiguration#enableSurfaceSharing} and surfaces
+     *                        as part of a surface group.
+     *
+     * @param executor The executor which will be used for invoking the offline callback listener.
+     *
+     * @param listener The callback object to notify for offline session events.
+     *
+     * @return camera offline session which in case of successful offline switch will move in ready
+     *         state after clients receive {@link CameraOfflineSessionCallback#onReady}. In case the
+     *         offline switch was not successful clients will receive respective
+     *         {@link CameraOfflineSessionCallback#onSwitchFailed} notification.
+     *
+     * @throws IllegalArgumentException if an attempt was made to pass a {@link Surface} that was
+     *                                  not registered with this capture session or a shared
+     *                                  surface {@link OutputConfiguration#enableSurfaceSharing} or
+     *                                  surface as part of a surface group. The current capture
+     *                                  session will remain valid and active in case of this
+     *                                  exception.
+     *
+     * @throws CameraAccessException if the camera device is no longer connected or has
+     *                               encountered a fatal error.
+     *
+     * @see CameraOfflineSession
+     * @see CameraOfflineSessionCallback
+     * @see #supportsOfflineProcessing
+     */
+    @Nullable
+    public CameraOfflineSession switchToOffline(@NonNull Collection<Surface> offlineSurfaces,
+            @NonNull Executor executor, @NonNull CameraOfflineSessionCallback listener)
+            throws CameraAccessException {
+        throw new UnsupportedOperationException("Subclasses must override this method");
+    }
+
+    /**
+     * <p>Query whether a given Surface is able to support offline mode. </p>
+     *
+     * <p>Surfaces that support offline mode can be passed as arguments to {@link #switchToOffline}.
+     * </p>
+     *
+     * @param surface An input/output surface that was used to create this session or the result of
+     *                {@link #getInputSurface}.
+     *
+     * @return {@code true} if the surface can support offline mode and can be passed as argument to
+     *         {@link #switchToOffline}. {@code false} otherwise.
+     *
+     * @throws IllegalArgumentException if an attempt was made to pass a {@link Surface} that was
+     *                                  not registered with this capture session.
+     * @throws UnsupportedOperationException if an attempt was made to call this method using
+     *                                       unsupported camera capture session like
+     *                                       {@link CameraConstrainedHighSpeedCaptureSession} or
+     *                                       {@link CameraOfflineSession}.
+     *
+     * @see #switchToOffline
+     */
+    public boolean supportsOfflineProcessing(@NonNull Surface surface) {
+        throw new UnsupportedOperationException("Subclasses must override this method");
+    }
+
+    /**
      * Close this capture session asynchronously.
      *
      * <p>Closing a session frees up the target output Surfaces of the session for reuse with either
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index a45648f..7bddc1d 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1539,10 +1539,15 @@
      * <p><code>p' = Rp</code></p>
      * <p>where <code>p</code> is in the device sensor coordinate system, and
      *  <code>p'</code> is in the camera-oriented coordinate system.</p>
+     * <p>If {@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} is UNDEFINED, the quaternion rotation cannot
+     *  be accurately represented by the camera device, and will be represented by
+     *  default values matching its default facing.</p>
      * <p><b>Units</b>:
      * Quaternion coefficients</p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
      * <p><b>Permission {@link android.Manifest.permission#CAMERA } is needed to access this property</b></p>
+     *
+     * @see CameraCharacteristics#LENS_POSE_REFERENCE
      */
     @PublicKey
     @NonNull
@@ -1577,6 +1582,8 @@
      * <p>When {@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} is GYROSCOPE, then this position is relative to
      * the center of the primary gyroscope on the device. The axis definitions are the same as
      * with PRIMARY_CAMERA.</p>
+     * <p>When {@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} is UNDEFINED, this position cannot be accurately
+     * represented by the camera device, and will be represented as <code>(0, 0, 0)</code>.</p>
      * <p><b>Units</b>: Meters</p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
      * <p><b>Permission {@link android.Manifest.permission#CAMERA } is needed to access this property</b></p>
@@ -1714,20 +1721,24 @@
             new Key<float[]>("android.lens.radialDistortion", float[].class);
 
     /**
-     * <p>The origin for {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}.</p>
+     * <p>The origin for {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}, and the accuracy of
+     * {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation} and {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}.</p>
      * <p>Different calibration methods and use cases can produce better or worse results
      * depending on the selected coordinate origin.</p>
      * <p><b>Possible values:</b>
      * <ul>
      *   <li>{@link #LENS_POSE_REFERENCE_PRIMARY_CAMERA PRIMARY_CAMERA}</li>
      *   <li>{@link #LENS_POSE_REFERENCE_GYROSCOPE GYROSCOPE}</li>
+     *   <li>{@link #LENS_POSE_REFERENCE_UNDEFINED UNDEFINED}</li>
      * </ul></p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
      * <p><b>Permission {@link android.Manifest.permission#CAMERA } is needed to access this property</b></p>
      *
+     * @see CameraCharacteristics#LENS_POSE_ROTATION
      * @see CameraCharacteristics#LENS_POSE_TRANSLATION
      * @see #LENS_POSE_REFERENCE_PRIMARY_CAMERA
      * @see #LENS_POSE_REFERENCE_GYROSCOPE
+     * @see #LENS_POSE_REFERENCE_UNDEFINED
      */
     @PublicKey
     @NonNull
diff --git a/core/java/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java b/core/java/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java
index 07d2443..6ead64c 100644
--- a/core/java/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java
+++ b/core/java/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java
@@ -25,9 +25,12 @@
  * A constrained high speed capture session for a {@link CameraDevice}, used for capturing high
  * speed images from the {@link CameraDevice} for high speed video recording use case.
  * <p>
- * A CameraHighSpeedCaptureSession is created by providing a set of target output surfaces to
- * {@link CameraDevice#createConstrainedHighSpeedCaptureSession}, Once created, the session is
- * active until a new session is created by the camera device, or the camera device is closed.
+ * A CameraConstrainedHighSpeedCaptureSession is created by providing a session configuration to
+ * {@link CameraDevice#createCaptureSession(SessionConfiguration)} with a type of
+ * {@link android.hardware.camera2.params.SessionConfiguration#SESSION_HIGH_SPEED}. The
+ * CameraCaptureSession returned from {@link CameraCaptureSession.StateCallback} can then be cast to
+ * a CameraConstrainedHighSpeedCaptureSession. Once created, the session is active until a new
+ * session is created by the camera device, or the camera device is closed.
  * </p>
  * <p>
  * An active high speed capture session is a specialized capture session that is only targeted at
@@ -37,8 +40,8 @@
  * accepts request lists created via {@link #createHighSpeedRequestList}, and the request list can
  * only be submitted to this session via {@link CameraCaptureSession#captureBurst captureBurst}, or
  * {@link CameraCaptureSession#setRepeatingBurst setRepeatingBurst}. See
- * {@link CameraDevice#createConstrainedHighSpeedCaptureSession} for more details of the
- * limitations.
+ * {@link CameraDevice#createCaptureSession(android.hardware.camera2.params.SessionConfiguration)}
+ * for more details of the limitations.
  * </p>
  * <p>
  * Creating a session is an expensive operation and can take several hundred milliseconds, since it
@@ -50,13 +53,6 @@
  * completed, then the {@link CameraCaptureSession.StateCallback#onConfigureFailed} is called, and
  * the session will not become active.
  * </p>
- * <!--
- * <p>
- * Any capture requests (repeating or non-repeating) submitted before the session is ready will be
- * queued up and will begin capture once the session becomes ready. In case the session cannot be
- * configured and {@link CameraCaptureSession.StateCallback#onConfigureFailed onConfigureFailed} is
- * called, all queued capture requests are discarded.  </p>
- * -->
  * <p>
  * If a new session is created by the camera device, then the previous session is closed, and its
  * associated {@link CameraCaptureSession.StateCallback#onClosed onClosed} callback will be
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index fb1ece2..cc06681 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -16,24 +16,22 @@
 
 package android.hardware.camera2;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.IntDef;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
-import static android.hardware.camera2.ICameraDeviceUser.NORMAL_MODE;
-import static android.hardware.camera2.ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE;
 import android.hardware.camera2.params.InputConfiguration;
-import android.hardware.camera2.params.StreamConfigurationMap;
 import android.hardware.camera2.params.OutputConfiguration;
 import android.hardware.camera2.params.SessionConfiguration;
+import android.hardware.camera2.params.StreamConfigurationMap;
 import android.os.Handler;
 import android.view.Surface;
 
-import java.util.List;
-import java.util.Set;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.Set;
 
 /**
  * <p>The CameraDevice class is a representation of a single camera connected to an
@@ -220,6 +218,224 @@
      * <p>Create a new camera capture session by providing the target output set of Surfaces to the
      * camera device.</p>
      *
+     * @param outputs The new set of Surfaces that should be made available as
+     *                targets for captured image data.
+     * @param callback The callback to notify about the status of the new capture session.
+     * @param handler The handler on which the callback should be invoked, or {@code null} to use
+     *                the current thread's {@link android.os.Looper looper}.
+     *
+     * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements,
+     *                                  the callback is null, or the handler is null but the current
+     *                                  thread has no looper.
+     * @throws CameraAccessException if the camera device is no longer connected or has
+     *                               encountered a fatal error
+     * @throws IllegalStateException if the camera device has been closed
+     *
+     * @see CameraCaptureSession
+     * @see StreamConfigurationMap#getOutputFormats()
+     * @see StreamConfigurationMap#getOutputSizes(int)
+     * @see StreamConfigurationMap#getOutputSizes(Class)
+     * @deprecated Please use @{link
+     *      #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the
+     *      full set of configuration options available.
+     */
+    @Deprecated
+    public abstract void createCaptureSession(@NonNull List<Surface> outputs,
+            @NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler)
+            throws CameraAccessException;
+
+    /**
+     * <p>Create a new camera capture session by providing the target output set of Surfaces and
+     * its corresponding surface configuration to the camera device.</p>
+     *
+     * @see #createCaptureSession
+     * @see OutputConfiguration
+     * @deprecated Please use @{link
+     *      #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the
+     *      full set of configuration options available.
+     */
+    @Deprecated
+    public abstract void createCaptureSessionByOutputConfigurations(
+            List<OutputConfiguration> outputConfigurations,
+            CameraCaptureSession.StateCallback callback, @Nullable Handler handler)
+            throws CameraAccessException;
+    /**
+     * Create a new reprocessable camera capture session by providing the desired reprocessing
+     * input Surface configuration and the target output set of Surfaces to the camera device.
+     *
+     * @param inputConfig The configuration for the input {@link Surface}
+     * @param outputs The new set of Surfaces that should be made available as
+     *                targets for captured image data.
+     * @param callback The callback to notify about the status of the new capture session.
+     * @param handler The handler on which the callback should be invoked, or {@code null} to use
+     *                the current thread's {@link android.os.Looper looper}.
+     *
+     * @throws IllegalArgumentException if the input configuration is null or not supported, the set
+     *                                  of output Surfaces do not meet the requirements, the
+     *                                  callback is null, or the handler is null but the current
+     *                                  thread has no looper.
+     * @throws CameraAccessException if the camera device is no longer connected or has
+     *                               encountered a fatal error
+     * @throws IllegalStateException if the camera device has been closed
+     *
+     * @see #createCaptureSession
+     * @see CameraCaptureSession
+     * @see StreamConfigurationMap#getInputFormats
+     * @see StreamConfigurationMap#getInputSizes
+     * @see StreamConfigurationMap#getValidOutputFormatsForInput
+     * @see StreamConfigurationMap#getOutputSizes
+     * @see android.media.ImageWriter
+     * @see android.media.ImageReader
+     * @deprecated Please use @{link
+     *      #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the
+     *      full set of configuration options available.
+     */
+    @Deprecated
+    public abstract void createReprocessableCaptureSession(@NonNull InputConfiguration inputConfig,
+            @NonNull List<Surface> outputs, @NonNull CameraCaptureSession.StateCallback callback,
+            @Nullable Handler handler)
+            throws CameraAccessException;
+
+    /**
+     * Create a new reprocessable camera capture session by providing the desired reprocessing
+     * input configuration and output {@link OutputConfiguration}
+     * to the camera device.
+     *
+     * @see #createReprocessableCaptureSession
+     * @see OutputConfiguration
+     * @deprecated Please use @{link
+     *      #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the
+     *      full set of configuration options available.
+     */
+    @Deprecated
+    public abstract void createReprocessableCaptureSessionByConfigurations(
+            @NonNull InputConfiguration inputConfig,
+            @NonNull List<OutputConfiguration> outputs,
+            @NonNull CameraCaptureSession.StateCallback callback,
+            @Nullable Handler handler)
+            throws CameraAccessException;
+
+    /**
+     * <p>Create a new constrained high speed capture session.</p>
+     *
+     * @param outputs The new set of Surfaces that should be made available as
+     *                targets for captured high speed image data.
+     * @param callback The callback to notify about the status of the new capture session.
+     * @param handler The handler on which the callback should be invoked, or {@code null} to use
+     *                the current thread's {@link android.os.Looper looper}.
+     *
+     * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements,
+     *                                  the callback is null, or the handler is null but the current
+     *                                  thread has no looper, or the camera device doesn't support
+     *                                  high speed video capability.
+     * @throws CameraAccessException if the camera device is no longer connected or has
+     *                               encountered a fatal error
+     * @throws IllegalStateException if the camera device has been closed
+     *
+     * @see #createCaptureSession
+     * @see CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE
+     * @see StreamConfigurationMap#getHighSpeedVideoSizes
+     * @see StreamConfigurationMap#getHighSpeedVideoFpsRangesFor
+     * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+     * @see CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO
+     * @see CameraCaptureSession#captureBurst
+     * @see CameraCaptureSession#setRepeatingBurst
+     * @see CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList
+     * @deprecated Please use @{link
+     *      #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the
+     *      full set of configuration options available.
+     */
+    @Deprecated
+    public abstract void createConstrainedHighSpeedCaptureSession(@NonNull List<Surface> outputs,
+            @NonNull CameraCaptureSession.StateCallback callback,
+            @Nullable Handler handler)
+            throws CameraAccessException;
+
+    /**
+     * Standard camera operation mode.
+     *
+     * @see #createCustomCaptureSession
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public static final int SESSION_OPERATION_MODE_NORMAL =
+            0; // ICameraDeviceUser.NORMAL_MODE;
+
+    /**
+     * Constrained high-speed operation mode.
+     *
+     * @see #createCustomCaptureSession
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED =
+            1; // ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE;
+
+    /**
+     * First vendor-specific operating mode
+     *
+     * @see #createCustomCaptureSession
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public static final int SESSION_OPERATION_MODE_VENDOR_START =
+            0x8000; // ICameraDeviceUser.VENDOR_MODE_START;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"SESSION_OPERATION_MODE"}, value =
+            {SESSION_OPERATION_MODE_NORMAL,
+             SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED,
+             SESSION_OPERATION_MODE_VENDOR_START})
+    public @interface SessionOperatingMode {};
+
+    /**
+     * Create a new camera capture session with a custom operating mode.
+     *
+     * @param inputConfig The configuration for the input {@link Surface} if a reprocessing session
+     *                is desired, or {@code null} otherwise.
+     * @param outputs The new set of {@link OutputConfiguration OutputConfigurations} that should be
+     *                made available as targets for captured image data.
+     * @param operatingMode The custom operating mode to use; a nonnegative value, either a custom
+     *                vendor value or one of the SESSION_OPERATION_MODE_* values.
+     * @param callback The callback to notify about the status of the new capture session.
+     * @param handler The handler on which the callback should be invoked, or {@code null} to use
+     *                the current thread's {@link android.os.Looper looper}.
+     *
+     * @throws IllegalArgumentException if the input configuration is null or not supported, the set
+     *                                  of output Surfaces do not meet the requirements, the
+     *                                  callback is null, or the handler is null but the current
+     *                                  thread has no looper.
+     * @throws CameraAccessException if the camera device is no longer connected or has
+     *                               encountered a fatal error
+     * @throws IllegalStateException if the camera device has been closed
+     *
+     * @see #createCaptureSession
+     * @see #createReprocessableCaptureSession
+     * @see CameraCaptureSession
+     * @see OutputConfiguration
+     * @deprecated Please use @{link
+     *      #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the
+     *      full set of configuration options available.
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    @Deprecated
+    public abstract void createCustomCaptureSession(
+            InputConfiguration inputConfig,
+            @NonNull List<OutputConfiguration> outputs,
+            @SessionOperatingMode int operatingMode,
+            @NonNull CameraCaptureSession.StateCallback callback,
+            @Nullable Handler handler)
+            throws CameraAccessException;
+
+    /**
+     * <p>Create a new {@link CameraCaptureSession} using a {@link SessionConfiguration} helper
+     * object that aggregates all supported parameters.</p>
      * <p>The active capture session determines the set of potential output Surfaces for
      * the camera device for each capture request. A given request may use all
      * or only some of the outputs. Once the CameraCaptureSession is created, requests can be
@@ -308,11 +524,15 @@
      * <p>Configuring a session with an empty or null list will close the current session, if
      * any. This can be used to release the current session's target surfaces for another use.</p>
      *
+     * <h3>Regular capture</h3>
+     *
      * <p>While any of the sizes from {@link StreamConfigurationMap#getOutputSizes} can be used when
      * a single output stream is configured, a given camera device may not be able to support all
      * combination of sizes, formats, and targets when multiple outputs are configured at once.  The
      * tables below list the maximum guaranteed resolutions for combinations of streams and targets,
-     * given the capabilities of the camera device.</p>
+     * given the capabilities of the camera device. These are valid for when the
+     * {@link android.hardware.camera2.params.SessionConfiguration#setInputConfiguration
+     * input configuration} is not set and therefore no reprocessing is active.</p>
      *
      * <p>If an application tries to create a session using a set of targets that exceed the limits
      * described in the below tables, one of three possibilities may occur. First, the session may
@@ -488,56 +708,22 @@
      * (either width or height) might not be supported, and capture session creation will fail if it
      * is not.</p>
      *
-     * @param outputs The new set of Surfaces that should be made available as
-     *                targets for captured image data.
-     * @param callback The callback to notify about the status of the new capture session.
-     * @param handler The handler on which the callback should be invoked, or {@code null} to use
-     *                the current thread's {@link android.os.Looper looper}.
-     *
-     * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements,
-     *                                  the callback is null, or the handler is null but the current
-     *                                  thread has no looper.
-     * @throws CameraAccessException if the camera device is no longer connected or has
-     *                               encountered a fatal error
-     * @throws IllegalStateException if the camera device has been closed
-     *
-     * @see CameraCaptureSession
-     * @see StreamConfigurationMap#getOutputFormats()
-     * @see StreamConfigurationMap#getOutputSizes(int)
-     * @see StreamConfigurationMap#getOutputSizes(Class)
-     */
-    public abstract void createCaptureSession(@NonNull List<Surface> outputs,
-            @NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler)
-            throws CameraAccessException;
-
-    /**
-     * <p>Create a new camera capture session by providing the target output set of Surfaces and
-     * its corresponding surface configuration to the camera device.</p>
-     *
-     * @see #createCaptureSession
-     * @see OutputConfiguration
-     */
-    public abstract void createCaptureSessionByOutputConfigurations(
-            List<OutputConfiguration> outputConfigurations,
-            CameraCaptureSession.StateCallback callback, @Nullable Handler handler)
-            throws CameraAccessException;
-    /**
-     * Create a new reprocessable camera capture session by providing the desired reprocessing
-     * input Surface configuration and the target output set of Surfaces to the camera device.
+     * <h3>Reprocessing</h3>
      *
      * <p>If a camera device supports YUV reprocessing
      * ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING}) or PRIVATE
      * reprocessing
-     * ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING}), besides
-     * the capture session created via {@link #createCaptureSession createCaptureSession}, the
+     * ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING}), the
      * application can also create a reprocessable capture session to submit reprocess capture
-     * requests in addition to regular capture requests. A reprocess capture request takes the next
-     * available buffer from the session's input Surface, and sends it through the camera device's
-     * processing pipeline again, to produce buffers for the request's target output Surfaces. No
-     * new image data is captured for a reprocess request. However the input buffer provided by
-     * the application must be captured previously by the same camera device in the same session
-     * directly (e.g. for Zero-Shutter-Lag use case) or indirectly (e.g. combining multiple output
-     * images).</p>
+     * requests in addition to regular capture requests, by setting an
+     * {@link android.hardware.camera2.params.SessionConfiguration#setInputConfiguration
+     * input configuration} for the session. A reprocess capture request takes the next available
+     * buffer from the
+     * session's input Surface, and sends it through the camera device's processing pipeline again,
+     * to produce buffers for the request's target output Surfaces. No new image data is captured
+     * for a reprocess request. However the input buffer provided by the application must be
+     * captured previously by the same camera device in the same session directly (e.g. for
+     * Zero-Shutter-Lag use case) or indirectly (e.g. combining multiple output images).</p>
      *
      * <p>The active reprocessable capture session determines an input {@link Surface} and the set
      * of potential output Surfaces for the camera devices for each capture request. The application
@@ -570,10 +756,7 @@
      * <p>Starting from API level 30, recreating a reprocessable capture session will flush all the
      * queued but not yet processed buffers from the input surface.</p>
      *
-     * <p>The guaranteed stream configurations listed in
-     * {@link #createCaptureSession createCaptureSession} are also guaranteed to work for
-     * {@link #createReprocessableCaptureSession createReprocessableCaptureSession}. In addition,
-     * the configurations in the tables below are also guaranteed for creating a reprocessable
+     * <p>The configurations in the tables below are guaranteed for creating a reprocessable
      * capture session if the camera device supports YUV reprocessing or PRIVATE reprocessing.
      * However, not all output targets used to create a reprocessable session may be used in a
      * {@link CaptureRequest} simultaneously. For devices that support only 1 output target in a
@@ -602,7 +785,7 @@
      * <p>LIMITED-level ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL}
      * {@code == }{@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED LIMITED}) devices
      * support at least the following stream combinations for creating a reprocessable capture
-     * session in addition to those listed in {@link #createCaptureSession createCaptureSession} for
+     * session in addition to those listed earlier for regular captures for
      * {@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED LIMITED} devices:
      *
      * <table>
@@ -671,74 +854,30 @@
      * </table><br>
      * </p>
      *
-     * <p>Clients can access the above mandatory stream combination tables via
-     * {@link android.hardware.camera2.params.MandatoryStreamCombination}.</p>
+     * <h3>Constrained high-speed recording</h3>
      *
-     * @param inputConfig The configuration for the input {@link Surface}
-     * @param outputs The new set of Surfaces that should be made available as
-     *                targets for captured image data.
-     * @param callback The callback to notify about the status of the new capture session.
-     * @param handler The handler on which the callback should be invoked, or {@code null} to use
-     *                the current thread's {@link android.os.Looper looper}.
-     *
-     * @throws IllegalArgumentException if the input configuration is null or not supported, the set
-     *                                  of output Surfaces do not meet the requirements, the
-     *                                  callback is null, or the handler is null but the current
-     *                                  thread has no looper.
-     * @throws CameraAccessException if the camera device is no longer connected or has
-     *                               encountered a fatal error
-     * @throws IllegalStateException if the camera device has been closed
-     *
-     * @see #createCaptureSession
-     * @see CameraCaptureSession
-     * @see StreamConfigurationMap#getInputFormats
-     * @see StreamConfigurationMap#getInputSizes
-     * @see StreamConfigurationMap#getValidOutputFormatsForInput
-     * @see StreamConfigurationMap#getOutputSizes
-     * @see android.media.ImageWriter
-     * @see android.media.ImageReader
-     */
-    public abstract void createReprocessableCaptureSession(@NonNull InputConfiguration inputConfig,
-            @NonNull List<Surface> outputs, @NonNull CameraCaptureSession.StateCallback callback,
-            @Nullable Handler handler)
-            throws CameraAccessException;
-
-    /**
-     * Create a new reprocessable camera capture session by providing the desired reprocessing
-     * input configuration and output {@link OutputConfiguration}
-     * to the camera device.
-     *
-     * @see #createReprocessableCaptureSession
-     * @see OutputConfiguration
-     *
-     */
-    public abstract void createReprocessableCaptureSessionByConfigurations(
-            @NonNull InputConfiguration inputConfig,
-            @NonNull List<OutputConfiguration> outputs,
-            @NonNull CameraCaptureSession.StateCallback callback,
-            @Nullable Handler handler)
-            throws CameraAccessException;
-
-    /**
-     * <p>Create a new constrained high speed capture session.</p>
-     *
-     * <p>The application can use normal capture session (created via {@link #createCaptureSession})
+     * <p>The application can use a
+     * {@link android.hardware.camera2.params.SessionConfiguration#SESSION_REGULAR
+     * normal capture session}
      * for high speed capture if the desired high speed FPS ranges are advertised by
      * {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES}, in which case all API
      * semantics associated with normal capture sessions applies.</p>
      *
-     * <p>The method creates a specialized capture session that is only targeted at high speed
-     * video recording (>=120fps) use case if the camera device supports high speed video
-     * capability (i.e., {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES} contains
-     * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO}).
-     * Therefore, it has special characteristics compared with a normal capture session:</p>
+     * <p>A
+     * {@link android.hardware.camera2.params.SessionConfiguration#SESSION_HIGH_SPEED
+     * high-speed capture session}
+     * can be use for high speed video recording (>=120fps) when the camera device supports high
+     * speed video capability (i.e., {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES}
+     * contains {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO}).
+     * A constrained high-speed capture session has special limitations compared with a normal
+     * capture session:</p>
      *
      * <ul>
      *
-     * <li>In addition to the output target Surface requirements specified by the
-     *   {@link #createCaptureSession} method, an active high speed capture session will support up
-     *   to 2 output Surfaces, though the application might choose to configure just one Surface
-     *   (e.g., preview only). All Surfaces must be either video encoder surfaces (acquired by
+     * <li>In addition to the output target Surface requirements specified above for regular
+     *   captures, a high speed capture session will only support up to 2 output Surfaces, though
+     *   the application might choose to configure just one Surface (e.g., preview only). All
+     *   Surfaces must be either video encoder surfaces (acquired by
      *   {@link android.media.MediaRecorder#getSurface} or
      *   {@link android.media.MediaCodec#createInputSurface}) or preview surfaces (obtained from
      *   {@link android.view.SurfaceView}, {@link android.graphics.SurfaceTexture} via
@@ -774,116 +913,6 @@
      *
      * </ul>
      *
-     * @param outputs The new set of Surfaces that should be made available as
-     *                targets for captured high speed image data.
-     * @param callback The callback to notify about the status of the new capture session.
-     * @param handler The handler on which the callback should be invoked, or {@code null} to use
-     *                the current thread's {@link android.os.Looper looper}.
-     *
-     * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements,
-     *                                  the callback is null, or the handler is null but the current
-     *                                  thread has no looper, or the camera device doesn't support
-     *                                  high speed video capability.
-     * @throws CameraAccessException if the camera device is no longer connected or has
-     *                               encountered a fatal error
-     * @throws IllegalStateException if the camera device has been closed
-     *
-     * @see #createCaptureSession
-     * @see CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE
-     * @see StreamConfigurationMap#getHighSpeedVideoSizes
-     * @see StreamConfigurationMap#getHighSpeedVideoFpsRangesFor
-     * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
-     * @see CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO
-     * @see CameraCaptureSession#captureBurst
-     * @see CameraCaptureSession#setRepeatingBurst
-     * @see CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList
-     */
-    public abstract void createConstrainedHighSpeedCaptureSession(@NonNull List<Surface> outputs,
-            @NonNull CameraCaptureSession.StateCallback callback,
-            @Nullable Handler handler)
-            throws CameraAccessException;
-
-    /**
-     * Standard camera operation mode.
-     *
-     * @see #createCustomCaptureSession
-     * @hide
-     */
-    @SystemApi
-    @TestApi
-    public static final int SESSION_OPERATION_MODE_NORMAL =
-            0; // ICameraDeviceUser.NORMAL_MODE;
-
-    /**
-     * Constrained high-speed operation mode.
-     *
-     * @see #createCustomCaptureSession
-     * @hide
-     */
-    @SystemApi
-    @TestApi
-    public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED =
-            1; // ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE;
-
-    /**
-     * First vendor-specific operating mode
-     *
-     * @see #createCustomCaptureSession
-     * @hide
-     */
-    @SystemApi
-    @TestApi
-    public static final int SESSION_OPERATION_MODE_VENDOR_START =
-            0x8000; // ICameraDeviceUser.VENDOR_MODE_START;
-
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = {"SESSION_OPERATION_MODE"}, value =
-            {SESSION_OPERATION_MODE_NORMAL,
-             SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED,
-             SESSION_OPERATION_MODE_VENDOR_START})
-    public @interface SessionOperatingMode {};
-
-    /**
-     * Create a new camera capture session with a custom operating mode.
-     *
-     * @param inputConfig The configuration for the input {@link Surface} if a reprocessing session
-     *                is desired, or {@code null} otherwise.
-     * @param outputs The new set of {@link OutputConfiguration OutputConfigurations} that should be
-     *                made available as targets for captured image data.
-     * @param operatingMode The custom operating mode to use; a nonnegative value, either a custom
-     *                vendor value or one of the SESSION_OPERATION_MODE_* values.
-     * @param callback The callback to notify about the status of the new capture session.
-     * @param handler The handler on which the callback should be invoked, or {@code null} to use
-     *                the current thread's {@link android.os.Looper looper}.
-     *
-     * @throws IllegalArgumentException if the input configuration is null or not supported, the set
-     *                                  of output Surfaces do not meet the requirements, the
-     *                                  callback is null, or the handler is null but the current
-     *                                  thread has no looper.
-     * @throws CameraAccessException if the camera device is no longer connected or has
-     *                               encountered a fatal error
-     * @throws IllegalStateException if the camera device has been closed
-     *
-     * @see #createCaptureSession
-     * @see #createReprocessableCaptureSession
-     * @see CameraCaptureSession
-     * @see OutputConfiguration
-     * @hide
-     */
-    @SystemApi
-    @TestApi
-    public abstract void createCustomCaptureSession(
-            InputConfiguration inputConfig,
-            @NonNull List<OutputConfiguration> outputs,
-            @SessionOperatingMode int operatingMode,
-            @NonNull CameraCaptureSession.StateCallback callback,
-            @Nullable Handler handler)
-            throws CameraAccessException;
-
-    /**
-     * <p>Create a new {@link CameraCaptureSession} using a {@link SessionConfiguration} helper
-     * object that aggregates all supported parameters.</p>
      *
      * @param config A session configuration (see {@link SessionConfiguration}).
      *
@@ -997,7 +1026,7 @@
      *
      * @see CaptureRequest.Builder
      * @see TotalCaptureResult
-     * @see CameraDevice#createReprocessableCaptureSession
+     * @see CameraDevice#createCaptureSession(android.hardware.camera2.params.SessionConfiguration)
      * @see android.media.ImageWriter
      */
     @NonNull
@@ -1028,7 +1057,8 @@
      * <p>This method performs a runtime check of a given {@link SessionConfiguration}. The result
      * confirms whether or not the passed session configuration can be successfully used to
      * create a camera capture session using
-     * {@link CameraDevice#createCaptureSession(SessionConfiguration)}.
+     * {@link CameraDevice#createCaptureSession(
+     * android.hardware.camera2.params.SessionConfiguration)}.
      * </p>
      *
      * <p>The method can be called at any point before, during and after active capture session.
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 8e0a46d..89dac2a 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -87,17 +87,31 @@
      */
      protected abstract <T> T getProtected(TKey key);
 
-     /**
-      * @hide
-      */
-     protected void setNativeInstance(CameraMetadataNative nativeInstance) {
+    /**
+     * @hide
+     */
+    protected void setNativeInstance(CameraMetadataNative nativeInstance) {
         mNativeInstance = nativeInstance;
-     }
+    }
 
-     /**
-      * @hide
-      */
-     protected abstract Class<TKey> getKeyClass();
+    /**
+     * Retrieves the native CameraMetadata* as a Java long.
+     * Returns 0 if mNativeInstance is null.
+     *
+     * @hide
+     */
+    public long getNativeMetadataPtr() {
+        if (mNativeInstance == null) {
+            return 0;
+        } else {
+            return mNativeInstance.getMetadataPtr();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    protected abstract Class<TKey> getKeyClass();
 
     /**
      * Returns a list of the keys contained in this map.
@@ -366,6 +380,20 @@
      */
     public static final int LENS_POSE_REFERENCE_GYROSCOPE = 1;
 
+    /**
+     * <p>The camera device cannot represent the values of {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}
+     * and {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} accurately enough. One such example is a camera device
+     * on the cover of a foldable phone: in order to measure the pose translation and rotation,
+     * some kind of hinge position sensor would be needed.</p>
+     * <p>The value of {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation} must be all zeros, and
+     * {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} must be values matching its default facing.</p>
+     *
+     * @see CameraCharacteristics#LENS_POSE_ROTATION
+     * @see CameraCharacteristics#LENS_POSE_TRANSLATION
+     * @see CameraCharacteristics#LENS_POSE_REFERENCE
+     */
+    public static final int LENS_POSE_REFERENCE_UNDEFINED = 2;
+
     //
     // Enumeration values for CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
     //
@@ -1121,12 +1149,16 @@
     //
 
     /**
-     * <p>Timestamps from {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} are in nanoseconds and monotonic,
-     * but can not be compared to timestamps from other subsystems
-     * (e.g. accelerometer, gyro etc.), or other instances of the same or different
-     * camera devices in the same system. Timestamps between streams and results for
-     * a single camera instance are comparable, and the timestamps for all buffers
-     * and the result metadata generated by a single capture are identical.</p>
+     * <p>Timestamps from {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} are in nanoseconds and monotonic, but can
+     * not be compared to timestamps from other subsystems (e.g. accelerometer, gyro etc.),
+     * or other instances of the same or different camera devices in the same system with
+     * accuracy. However, the timestamps are roughly in the same timebase as
+     * {@link android.os.SystemClock#uptimeMillis }.  The accuracy is sufficient for tasks
+     * like A/V synchronization for video recording, at least, and the timestamps can be
+     * directly used together with timestamps from the audio subsystem for that task.</p>
+     * <p>Timestamps between streams and results for a single camera instance are comparable,
+     * and the timestamps for all buffers and the result metadata generated by a single
+     * capture are identical.</p>
      *
      * @see CaptureResult#SENSOR_TIMESTAMP
      * @see CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE
@@ -1137,6 +1169,14 @@
      * <p>Timestamps from {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} are in the same timebase as
      * {@link android.os.SystemClock#elapsedRealtimeNanos },
      * and they can be compared to other timestamps using that base.</p>
+     * <p>When buffers from a REALTIME device are passed directly to a video encoder from the
+     * camera, automatic compensation is done to account for differing timebases of the
+     * audio and camera subsystems.  If the application is receiving buffers and then later
+     * sending them to a video encoder or other application where they are compared with
+     * audio subsystem timestamps or similar, this compensation is not present.  In those
+     * cases, applications need to adjust the timestamps themselves.  Since {@link android.os.SystemClock#elapsedRealtimeNanos } and {@link android.os.SystemClock#uptimeMillis } only diverge while the device is asleep, an
+     * offset between the two sources can be measured once per active session and applied
+     * to timestamps for sufficient accuracy for A/V sync.</p>
      *
      * @see CaptureResult#SENSOR_TIMESTAMP
      * @see CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE
diff --git a/core/java/android/hardware/camera2/CameraOfflineSession.java b/core/java/android/hardware/camera2/CameraOfflineSession.java
new file mode 100644
index 0000000..312559c
--- /dev/null
+++ b/core/java/android/hardware/camera2/CameraOfflineSession.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 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.hardware.camera2;
+
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraDevice;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A camera capture session that was switched to offline mode via successful call to
+ * {@link CameraCaptureSession#switchToOffline}.
+ *
+ * <p>Offline capture sessions allow clients to select a set of camera registered surfaces that
+ * support offline mode.  After a successful offline mode switch all non-repeating pending requests
+ * on those surfaces will continue to be processed by the camera stack even if clients close the
+ * corresponding camera device.<p>
+ *
+ * <p>Offline capture session instances will replace the previously active capture session arguments
+ * in all capture callbacks after {@link CameraCaptureSession#switchToOffline} completes.</p>
+ *
+ * <p>Processing of pending offline capture requests will begin only after the offline session
+ * moves to ready state which will be indicated by the {@link CameraOfflineSessionCallback#onReady}
+ * callback.</p>
+ *
+ * <p>In contrast to a regular {@link CameraCaptureSession} an offline capture session will
+ * not accept any further capture requests. Besides {@link CameraOfflineSession#close} all
+ * remaining methods will throw {@link UnsupportedOperationException} and are not supported.</p>
+ *
+ * @see CameraCaptureSession#supportsOfflineProcessing
+ */
+public abstract class CameraOfflineSession extends CameraCaptureSession {
+    public static abstract class CameraOfflineSessionCallback {
+        /**
+         * This method indicates that the offline switch call
+         * {@link CameraCaptureSession#switchToOffline} was successful.
+         *
+         * <p>This callback will be invoked once the offline session moves to the ready state.</p>
+         *
+         * <p>Calls to {@link CameraDevice#close} will not have impact on the processing of offline
+         * requests once the offline session moves in ready state.</p>
+         *
+         * @param session the currently ready offline session
+         *
+         */
+        public abstract void onReady(@NonNull CameraOfflineSession session);
+
+        /**
+         * This method indicates that the offline switch call
+         * {@link CameraCaptureSession#switchToOffline} was not able to complete successfully.
+         *
+         * <p>The offline switch can fail either due to internal camera error during the switch
+         * sequence or because the camera implementation was not able to find any pending capture
+         * requests that can be migrated to offline mode.</p>
+         *
+         * <p>Calling {@link CameraOfflineSession#close} is not necessary and clients will not
+         * receive any further offline session notifications.</p>
+         *
+         * @param session the offline session that failed to switch to ready state
+         */
+        public abstract void onSwitchFailed(@NonNull CameraOfflineSession session);
+
+        /**
+         * This method indicates that all pending offline requests were processed.
+         *
+         * <p>This callback will be invoked once the offline session finishes processing
+         * all of its pending offline capture requests.</p>
+         *
+         * @param session the currently ready offline session
+         *
+         */
+        public abstract void onIdle(@NonNull CameraOfflineSession session);
+
+        /**
+         * This status code indicates unexpected and fatal internal camera error.
+         *
+         * <p>Pending offline requests will be discarded and the respective registered
+         * capture callbacks may not get triggered.</p>
+         *
+         * @see #onError
+         */
+        public static final int STATUS_INTERNAL_ERROR = 0;
+
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(prefix = {"STATUS_"}, value = {STATUS_INTERNAL_ERROR})
+        public @interface StatusCode {};
+
+        /**
+         * This method is called when the offline session encounters an unexpected error.
+         *
+         * <p>This notification will only be invoked for sessions that reached the ready state.
+         * Clients will need to call {@link CameraOfflineSession#close} to close and release all
+         * resources. {@link #onClosed} will not be triggered automatically in error scenarios.</p>
+         *
+         * @param session the current offline session
+         * @param status error status
+         *
+         */
+        public abstract void onError(@NonNull CameraOfflineSession session, @StatusCode int status);
+
+        /**
+         * This method is called when the offline session is closed.
+         *
+         * <p>An offline session will be closed after a call to
+         * {@link CameraOfflineSession#close}.</p>
+         *
+         * <p>In case of failure to switch to offline mode, only {@link #onSwitchFailed} will be
+         * called and {@link #onClosed} will not be.</p>
+         *
+         * <p>In case there was no previous {@link #onIdle} notification any in-progress
+         * offline capture requests within the offline session will be discarded
+         * and further result callbacks will not be triggered.</p>
+         *
+         * @param session the session returned by {@link CameraCaptureSession#switchToOffline}
+         *
+         */
+        public abstract void onClosed(@NonNull CameraOfflineSession session);
+    }
+
+    /**
+     * Close this offline capture session.
+     *
+     * <p>Abort all pending offline requests and close the connection to the offline camera session
+     * as quickly as possible.</p>
+     *
+     * <p>This method can be called only after clients receive
+     * {@link CameraOfflineSessionCallback#onReady}.</p>
+     *
+     * <p>Immediately after this call, besides the final
+     * {@link CameraOfflineSessionCallback#onClosed} notification, no further callbacks from the
+     * offline session will be triggered and all remaining offline capture requests will be
+     * discarded.</p>
+     *
+     * <p>Closing a session is idempotent; closing more than once has no effect.</p>
+     *
+     * @throws IllegalStateException if the offline sesion is not ready.
+     */
+    @Override
+    public abstract void close();
+}
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 9b305b32..6f0d135 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -3027,10 +3027,15 @@
      * <p><code>p' = Rp</code></p>
      * <p>where <code>p</code> is in the device sensor coordinate system, and
      *  <code>p'</code> is in the camera-oriented coordinate system.</p>
+     * <p>If {@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} is UNDEFINED, the quaternion rotation cannot
+     *  be accurately represented by the camera device, and will be represented by
+     *  default values matching its default facing.</p>
      * <p><b>Units</b>:
      * Quaternion coefficients</p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
      * <p><b>Permission {@link android.Manifest.permission#CAMERA } is needed to access this property</b></p>
+     *
+     * @see CameraCharacteristics#LENS_POSE_REFERENCE
      */
     @PublicKey
     @NonNull
@@ -3065,6 +3070,8 @@
      * <p>When {@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} is GYROSCOPE, then this position is relative to
      * the center of the primary gyroscope on the device. The axis definitions are the same as
      * with PRIMARY_CAMERA.</p>
+     * <p>When {@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} is UNDEFINED, this position cannot be accurately
+     * represented by the camera device, and will be represented as <code>(0, 0, 0)</code>.</p>
      * <p><b>Units</b>: Meters</p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
      * <p><b>Permission {@link android.Manifest.permission#CAMERA } is needed to access this property</b></p>
diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionCore.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionCore.java
index 116f0f1..44408c2 100644
--- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionCore.java
+++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionCore.java
@@ -61,4 +61,14 @@
      */
     boolean isAborting();
 
+    /**
+     * Close the capture session without draining the pending requests.
+     *
+     * <p>This is usually used when switching to offline session mode. Depending
+     * on the client input, some of the pending requests will be flushed and some
+     * will remain for further processing. In either case, the regular drain logic
+     * needs to be skipped.</p>
+     *
+     */
+    void closeWithoutDraining();
 }
diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
index a4640c1..b6f4bd3 100644
--- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@@ -18,6 +18,8 @@
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCaptureSession;
 import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraOfflineSession;
+import android.hardware.camera2.CameraOfflineSession.CameraOfflineSessionCallback;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.ICameraDeviceUser;
 import android.hardware.camera2.params.OutputConfiguration;
@@ -29,6 +31,7 @@
 import android.view.Surface;
 
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.Executor;
 
@@ -106,7 +109,7 @@
          * Use the same handler as the device's StateCallback for all the internal coming events
          *
          * This ensures total ordering between CameraDevice.StateCallback and
-         * CameraDeviceImpl.CaptureCallback events.
+         * CaptureCallback events.
          */
         mSequenceDrainer = new TaskDrainer<>(mDeviceExecutor, new SequenceDrainListener(),
                 /*name*/"seq");
@@ -136,23 +139,35 @@
 
     @Override
     public void prepare(Surface surface) throws CameraAccessException {
-        mDeviceImpl.prepare(surface);
+        synchronized (mDeviceImpl.mInterfaceLock) {
+            checkNotClosed();
+            mDeviceImpl.prepare(surface);
+        }
     }
 
     @Override
     public void prepare(int maxCount, Surface surface) throws CameraAccessException {
-        mDeviceImpl.prepare(maxCount, surface);
+        synchronized (mDeviceImpl.mInterfaceLock) {
+            checkNotClosed();
+            mDeviceImpl.prepare(maxCount, surface);
+        }
     }
 
     @Override
     public void tearDown(Surface surface) throws CameraAccessException {
-        mDeviceImpl.tearDown(surface);
+        synchronized (mDeviceImpl.mInterfaceLock) {
+            checkNotClosed();
+            mDeviceImpl.tearDown(surface);
+        }
     }
 
     @Override
     public void finalizeOutputConfigurations(
             List<OutputConfiguration> outputConfigs) throws CameraAccessException {
-        mDeviceImpl.finalizeOutputConfigs(outputConfigs);
+        synchronized (mDeviceImpl.mInterfaceLock) {
+            checkNotClosed();
+            mDeviceImpl.finalizeOutputConfigs(outputConfigs);
+        }
     }
 
     @Override
@@ -446,6 +461,24 @@
     }
 
     @Override
+    public CameraOfflineSession switchToOffline(Collection<Surface> offlineOutputs,
+            Executor executor, CameraOfflineSessionCallback listener) throws CameraAccessException {
+        synchronized (mDeviceImpl.mInterfaceLock) {
+            checkNotClosed();
+        }
+        return mDeviceImpl.switchToOffline(offlineOutputs, executor, listener);
+    }
+
+
+    @Override
+    public boolean supportsOfflineProcessing(Surface surface) {
+        synchronized (mDeviceImpl.mInterfaceLock) {
+            checkNotClosed();
+        }
+        return mDeviceImpl.supportsOfflineProcessing(surface);
+    }
+
+    @Override
     public boolean isReprocessable() {
         return mInput != null;
     }
@@ -501,6 +534,25 @@
     }
 
     @Override
+    public void closeWithoutDraining() {
+        synchronized (mDeviceImpl.mInterfaceLock) {
+            if (mClosed) {
+                if (DEBUG) Log.v(TAG, mIdString + "close - reentering");
+                return;
+            }
+
+            if (DEBUG) Log.v(TAG, mIdString + "close - first time");
+
+            mClosed = true;
+            mStateCallback.onClosed(this);
+        }
+
+        if (mInput != null) {
+            mInput.release();
+        }
+    }
+
+    @Override
     public void close() {
         synchronized (mDeviceImpl.mInterfaceLock) {
             if (mClosed) {
@@ -571,13 +623,13 @@
     }
 
     /**
-     * Forward callbacks from
-     * CameraDeviceImpl.CaptureCallback to the CameraCaptureSession.CaptureCallback.
+     * Forward callbacks that usually originate from
+     * CameraDeviceImpl.CameraDeviceCallbacks to the CameraCaptureSession.CaptureCallback.
      *
      * <p>When a capture sequence finishes, update the pending checked sequences set.</p>
      */
     @SuppressWarnings("deprecation")
-    private CameraDeviceImpl.CaptureCallback createCaptureCallbackProxy(
+    private android.hardware.camera2.impl.CaptureCallback createCaptureCallbackProxy(
             Handler handler, CaptureCallback callback) {
         final Executor executor = (callback != null) ? CameraDeviceImpl.checkAndWrapHandler(
                 handler) : null;
@@ -585,9 +637,9 @@
         return createCaptureCallbackProxyWithExecutor(executor, callback);
     }
 
-    private CameraDeviceImpl.CaptureCallback createCaptureCallbackProxyWithExecutor(
+    private android.hardware.camera2.impl.CaptureCallback createCaptureCallbackProxyWithExecutor(
             Executor executor, CaptureCallback callback) {
-        return new CameraDeviceImpl.CaptureCallback() {
+        return new android.hardware.camera2.impl.CaptureCallback(executor, callback) {
             @Override
             public void onCaptureStarted(CameraDevice camera,
                     CaptureRequest request, long timestamp, long frameNumber) {
diff --git a/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
index eb33137..0a42981 100644
--- a/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
@@ -20,6 +20,8 @@
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession;
 import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraOfflineSession;
+import android.hardware.camera2.CameraOfflineSession.CameraOfflineSessionCallback;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.params.OutputConfiguration;
 import android.hardware.camera2.params.StreamConfigurationMap;
@@ -283,6 +285,25 @@
     }
 
     @Override
+    public CameraOfflineSession switchToOffline(Collection<Surface> offlineOutputs,
+            Executor executor, CameraOfflineSessionCallback listener) throws CameraAccessException {
+        throw new UnsupportedOperationException("Constrained high speed session doesn't support"
+                + " this method");
+    }
+
+    @Override
+    public boolean supportsOfflineProcessing(Surface surface) {
+        throw new UnsupportedOperationException("Constrained high speed session doesn't support" +
+                " offline mode");
+    }
+
+    @Override
+    public void closeWithoutDraining() {
+        throw new UnsupportedOperationException("Constrained high speed session doesn't support"
+                + " this method");
+    }
+
+    @Override
     public void close() {
         mSessionImpl.close();
     }
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 67e7a4c..a385771 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -23,6 +23,7 @@
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCaptureSession;
 import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraOfflineSession;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CaptureFailure;
 import android.hardware.camera2.CaptureRequest;
@@ -52,18 +53,17 @@
 
 import java.util.AbstractMap.SimpleEntry;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
-import java.util.TreeMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.Executor;
-
+import java.util.concurrent.Executors;
+import java.util.concurrent.ExecutorService;
 
 /**
  * HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate
@@ -103,6 +103,9 @@
     private final SparseArray<OutputConfiguration> mConfiguredOutputs =
             new SparseArray<>();
 
+    // Cache all stream IDs capable of supporting offline mode.
+    private final HashSet<Integer> mOfflineSupport = new HashSet<>();
+
     private final String mCameraId;
     private final CameraCharacteristics mCharacteristics;
     private final int mTotalPartialCount;
@@ -127,6 +130,9 @@
 
     private final int mAppTargetSdkVersion;
 
+    private ExecutorService mOfflineSwitchService;
+    private CameraOfflineSessionImpl mOfflineSessionImpl;
+
     // Runnables for all state transitions, except error, which needs the
     // error code argument
 
@@ -470,10 +476,19 @@
                     }
                 }
 
+                int offlineStreamIds[];
                 if (sessionParams != null) {
-                    mRemoteDevice.endConfigure(operatingMode, sessionParams.getNativeCopy());
+                    offlineStreamIds = mRemoteDevice.endConfigure(operatingMode,
+                            sessionParams.getNativeCopy());
                 } else {
-                    mRemoteDevice.endConfigure(operatingMode, null);
+                    offlineStreamIds = mRemoteDevice.endConfigure(operatingMode, null);
+                }
+
+                mOfflineSupport.clear();
+                if ((offlineStreamIds != null) && (offlineStreamIds.length > 0)) {
+                    for (int offlineStreamId : offlineStreamIds) {
+                        mOfflineSupport.add(offlineStreamId);
+                    }
                 }
 
                 success = true;
@@ -866,22 +881,29 @@
         }
     }
 
-    public void switchToOffline(ICameraDeviceCallbacks cbs, Surface[] offlineOutputs)
+    public CameraOfflineSession switchToOffline(
+            @NonNull Collection<Surface> offlineOutputs, @NonNull Executor executor,
+            @NonNull CameraOfflineSession.CameraOfflineSessionCallback listener)
             throws CameraAccessException {
-        if ((offlineOutputs == null) || (offlineOutputs.length == 0)) {
-            throw new IllegalArgumentException("Invalid offline outputs!");
-        }
-        if (cbs == null) {
-            throw new IllegalArgumentException("Invalid device callbacks!");
+        if (offlineOutputs.isEmpty()) {
+            throw new IllegalArgumentException("Invalid offline surfaces!");
         }
 
-        ICameraOfflineSession offlineSession = null;
+        HashSet<Integer> offlineStreamIds = new HashSet<Integer>();
+        SparseArray<OutputConfiguration> offlineConfiguredOutputs =
+                new SparseArray<OutputConfiguration>();
+
         synchronized(mInterfaceLock) {
-            int streamId = -1;
+            if (mOfflineSessionImpl != null) {
+                throw new IllegalStateException("Switch to offline mode already in progress");
+            }
+
             for (Surface surface : offlineOutputs) {
+                int streamId = -1;
                 for (int i = 0; i < mConfiguredOutputs.size(); i++) {
                     if (surface == mConfiguredOutputs.valueAt(i).getSurface()) {
                         streamId = mConfiguredOutputs.keyAt(i);
+                        offlineConfiguredOutputs.append(streamId, mConfiguredOutputs.valueAt(i));
                         break;
                     }
                 }
@@ -889,12 +911,67 @@
                     throw new IllegalArgumentException("Offline surface is not part of this" +
                             " session");
                 }
+
+                if (!mOfflineSupport.contains(streamId)) {
+                    throw new IllegalArgumentException("Surface: "  + surface + " does not " +
+                            " support offline mode");
+                }
+
+                offlineStreamIds.add(streamId);
             }
 
-            offlineSession = mRemoteDevice.switchToOffline(cbs,
-                    offlineOutputs);
-            // TODO: Initialize CameraOfflineSession wrapper, clear 'mConfiguredOutputs',
-            // and update request tracking
+            mOfflineSessionImpl = new CameraOfflineSessionImpl(mCameraId,
+                    mCharacteristics, executor, listener, offlineConfiguredOutputs,
+                    mConfiguredInput, mFrameNumberTracker, mCaptureCallbackMap,
+                    mRequestLastFrameNumbersList);
+
+            mOfflineSwitchService = Executors.newSingleThreadExecutor();
+            mConfiguredOutputs.clear();
+            mConfiguredInput = new SimpleEntry<Integer, InputConfiguration>(REQUEST_ID_NONE, null);
+
+            mCurrentSession.closeWithoutDraining();
+            mCurrentSession = null;
+        }
+
+        mOfflineSwitchService.execute(new Runnable() {
+            @Override
+            public void run() {
+                // We cannot hold 'mInterfaceLock' during the remote IPC in 'switchToOffline'.
+                // The call will block until all non-offline requests are completed and/or flushed.
+                // The results/errors need to be handled by 'CameraDeviceCallbacks' which also sync
+                // on 'mInterfaceLock'.
+                try {
+                    ICameraOfflineSession remoteOfflineSession = mRemoteDevice.switchToOffline(
+                            mOfflineSessionImpl.getCallbacks(),
+                            Arrays.stream(offlineStreamIds.toArray(
+                                    new Integer[offlineStreamIds.size()])).mapToInt(
+                                            Integer::intValue).toArray());
+                    mOfflineSessionImpl.setRemoteSession(remoteOfflineSession);
+                } catch (CameraAccessException e) {
+                    mOfflineSessionImpl.notifyFailedSwitch();
+                }
+            }
+        });
+
+        return mOfflineSessionImpl;
+    }
+
+    public boolean supportsOfflineProcessing(Surface surface) {
+        if (surface == null) throw new IllegalArgumentException("Surface is null");
+
+        synchronized(mInterfaceLock) {
+            int streamId = -1;
+            for (int i = 0; i < mConfiguredOutputs.size(); i++) {
+                if (surface == mConfiguredOutputs.valueAt(i).getSurface()) {
+                    streamId = mConfiguredOutputs.keyAt(i);
+                    break;
+                }
+            }
+            if (streamId == -1) {
+                throw new IllegalArgumentException("Surface is not part of this session");
+            }
+
+            return mOfflineSupport.contains(streamId);
         }
     }
 
@@ -985,7 +1062,7 @@
             final int[] repeatingRequestTypes) {
         // lastFrameNumber being equal to NO_FRAMES_CAPTURED means that the request
         // was never sent to HAL. Should trigger onCaptureSequenceAborted immediately.
-        if (lastFrameNumber == CaptureCallback.NO_FRAMES_CAPTURED) {
+        if (lastFrameNumber == CameraCaptureSession.CaptureCallback.NO_FRAMES_CAPTURED) {
             final CaptureCallbackHolder holder;
             int index = mCaptureCallbackMap.indexOfKey(requestId);
             holder = (index >= 0) ? mCaptureCallbackMap.valueAt(index) : null;
@@ -1225,6 +1302,11 @@
                 return;
             }
 
+            if (mOfflineSwitchService != null) {
+                mOfflineSwitchService.shutdownNow();
+                mOfflineSwitchService = null;
+            }
+
             if (mRemoteDevice != null) {
                 mRemoteDevice.disconnect();
                 mRemoteDevice.unlinkToDeath(this, /*flags*/0);
@@ -1286,91 +1368,6 @@
     }
 
     /**
-     * <p>A callback for tracking the progress of a {@link CaptureRequest}
-     * submitted to the camera device.</p>
-     *
-     * An interface instead of an abstract class because this is internal and
-     * we want to make sure we always implement all its callbacks until we reach
-     * the public layer.
-     */
-    public interface CaptureCallback {
-
-        /**
-         * This constant is used to indicate that no images were captured for
-         * the request.
-         *
-         * @hide
-         */
-        public static final int NO_FRAMES_CAPTURED = -1;
-
-        /**
-         * This method is called when the camera device has started capturing
-         * the output image for the request, at the beginning of image exposure.
-         *
-         * @see android.media.MediaActionSound
-         */
-        public void onCaptureStarted(CameraDevice camera,
-                CaptureRequest request, long timestamp, long frameNumber);
-
-        /**
-         * This method is called when some results from an image capture are
-         * available.
-         *
-         * @hide
-         */
-        public void onCapturePartial(CameraDevice camera,
-                CaptureRequest request, CaptureResult result);
-
-        /**
-         * This method is called when an image capture makes partial forward progress; some
-         * (but not all) results from an image capture are available.
-         *
-         */
-        public void onCaptureProgressed(CameraDevice camera,
-                CaptureRequest request, CaptureResult partialResult);
-
-        /**
-         * This method is called when an image capture has fully completed and all the
-         * result metadata is available.
-         */
-        public void onCaptureCompleted(CameraDevice camera,
-                CaptureRequest request, TotalCaptureResult result);
-
-        /**
-         * This method is called instead of {@link #onCaptureCompleted} when the
-         * camera device failed to produce a {@link CaptureResult} for the
-         * request.
-         */
-        public void onCaptureFailed(CameraDevice camera,
-                CaptureRequest request, CaptureFailure failure);
-
-        /**
-         * This method is called independently of the others in CaptureCallback,
-         * when a capture sequence finishes and all {@link CaptureResult}
-         * or {@link CaptureFailure} for it have been returned via this callback.
-         */
-        public void onCaptureSequenceCompleted(CameraDevice camera,
-                int sequenceId, long frameNumber);
-
-        /**
-         * This method is called independently of the others in CaptureCallback,
-         * when a capture sequence aborts before any {@link CaptureResult}
-         * or {@link CaptureFailure} for it have been returned via this callback.
-         */
-        public void onCaptureSequenceAborted(CameraDevice camera,
-                int sequenceId);
-
-        /**
-         * This method is called independently of the others in CaptureCallback, if an output buffer
-         * is dropped for a particular capture request.
-         *
-         * Loss of metadata is communicated via onCaptureFailed, independently of any buffer loss.
-         */
-        public void onCaptureBufferLost(CameraDevice camera,
-                CaptureRequest request, Surface target, long frameNumber);
-    }
-
-    /**
      * A callback for notifications about the state of a camera device, adding in the callbacks that
      * were part of the earlier KK API design, but now only used internally.
      */
@@ -1426,478 +1423,6 @@
         }
     }
 
-    static class CaptureCallbackHolder {
-
-        private final boolean mRepeating;
-        private final CaptureCallback mCallback;
-        private final List<CaptureRequest> mRequestList;
-        private final Executor mExecutor;
-        private final int mSessionId;
-        /**
-         * <p>Determine if the callback holder is for a constrained high speed request list that
-         * expects batched capture results. Capture results will be batched if the request list
-         * is interleaved with preview and video requests. Capture results won't be batched if the
-         * request list only contains preview requests, or if the request doesn't belong to a
-         * constrained high speed list.
-         */
-        private final boolean mHasBatchedOutputs;
-
-        CaptureCallbackHolder(CaptureCallback callback, List<CaptureRequest> requestList,
-                Executor executor, boolean repeating, int sessionId) {
-            if (callback == null || executor == null) {
-                throw new UnsupportedOperationException(
-                    "Must have a valid handler and a valid callback");
-            }
-            mRepeating = repeating;
-            mExecutor = executor;
-            mRequestList = new ArrayList<CaptureRequest>(requestList);
-            mCallback = callback;
-            mSessionId = sessionId;
-
-            // Check whether this callback holder is for batched outputs.
-            // The logic here should match createHighSpeedRequestList.
-            boolean hasBatchedOutputs = true;
-            for (int i = 0; i < requestList.size(); i++) {
-                CaptureRequest request = requestList.get(i);
-                if (!request.isPartOfCRequestList()) {
-                    hasBatchedOutputs = false;
-                    break;
-                }
-                if (i == 0) {
-                    Collection<Surface> targets = request.getTargets();
-                    if (targets.size() != 2) {
-                        hasBatchedOutputs = false;
-                        break;
-                    }
-                }
-            }
-            mHasBatchedOutputs = hasBatchedOutputs;
-        }
-
-        public boolean isRepeating() {
-            return mRepeating;
-        }
-
-        public CaptureCallback getCallback() {
-            return mCallback;
-        }
-
-        public CaptureRequest getRequest(int subsequenceId) {
-            if (subsequenceId >= mRequestList.size()) {
-                throw new IllegalArgumentException(
-                        String.format(
-                                "Requested subsequenceId %d is larger than request list size %d.",
-                                subsequenceId, mRequestList.size()));
-            } else {
-                if (subsequenceId < 0) {
-                    throw new IllegalArgumentException(String.format(
-                            "Requested subsequenceId %d is negative", subsequenceId));
-                } else {
-                    return mRequestList.get(subsequenceId);
-                }
-            }
-        }
-
-        public CaptureRequest getRequest() {
-            return getRequest(0);
-        }
-
-        public Executor getExecutor() {
-            return mExecutor;
-        }
-
-        public int getSessionId() {
-            return mSessionId;
-        }
-
-        public int getRequestCount() {
-            return mRequestList.size();
-        }
-
-        public boolean hasBatchedOutputs() {
-            return mHasBatchedOutputs;
-        }
-    }
-
-    /**
-     * This class holds a capture ID and its expected last regular, zslStill, and reprocess
-     * frame number.
-     */
-    static class RequestLastFrameNumbersHolder {
-        // request ID
-        private final int mRequestId;
-        // The last regular frame number for this request ID. It's
-        // CaptureCallback.NO_FRAMES_CAPTURED if the request ID has no regular request.
-        private final long mLastRegularFrameNumber;
-        // The last reprocess frame number for this request ID. It's
-        // CaptureCallback.NO_FRAMES_CAPTURED if the request ID has no reprocess request.
-        private final long mLastReprocessFrameNumber;
-        // The last ZSL still capture frame number for this request ID. It's
-        // CaptureCallback.NO_FRAMES_CAPTURED if the request ID has no zsl request.
-        private final long mLastZslStillFrameNumber;
-
-        /**
-         * Create a request-last-frame-numbers holder with a list of requests, request ID, and
-         * the last frame number returned by camera service.
-         */
-        public RequestLastFrameNumbersHolder(List<CaptureRequest> requestList, SubmitInfo requestInfo) {
-            long lastRegularFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED;
-            long lastReprocessFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED;
-            long lastZslStillFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED;
-            long frameNumber = requestInfo.getLastFrameNumber();
-
-            if (requestInfo.getLastFrameNumber() < requestList.size() - 1) {
-                throw new IllegalArgumentException(
-                        "lastFrameNumber: " + requestInfo.getLastFrameNumber() +
-                        " should be at least " + (requestList.size() - 1) + " for the number of " +
-                        " requests in the list: " + requestList.size());
-            }
-
-            // find the last regular, zslStill, and reprocess frame number
-            for (int i = requestList.size() - 1; i >= 0; i--) {
-                CaptureRequest request = requestList.get(i);
-                int requestType = request.getRequestType();
-                if (requestType == CaptureRequest.REQUEST_TYPE_REPROCESS
-                        && lastReprocessFrameNumber == CaptureCallback.NO_FRAMES_CAPTURED) {
-                    lastReprocessFrameNumber = frameNumber;
-                } else if (requestType == CaptureRequest.REQUEST_TYPE_ZSL_STILL
-                        && lastZslStillFrameNumber == CaptureCallback.NO_FRAMES_CAPTURED) {
-                    lastZslStillFrameNumber = frameNumber;
-                } else if (requestType == CaptureRequest.REQUEST_TYPE_REGULAR
-                        && lastRegularFrameNumber == CaptureCallback.NO_FRAMES_CAPTURED) {
-                    lastRegularFrameNumber = frameNumber;
-                }
-
-                if (lastReprocessFrameNumber != CaptureCallback.NO_FRAMES_CAPTURED
-                        && lastZslStillFrameNumber != CaptureCallback.NO_FRAMES_CAPTURED
-                        && lastRegularFrameNumber != CaptureCallback.NO_FRAMES_CAPTURED) {
-                    break;
-                }
-
-                frameNumber--;
-            }
-
-            mLastRegularFrameNumber = lastRegularFrameNumber;
-            mLastReprocessFrameNumber = lastReprocessFrameNumber;
-            mLastZslStillFrameNumber = lastZslStillFrameNumber;
-            mRequestId = requestInfo.getRequestId();
-        }
-
-        /**
-         * Create a request-last-frame-numbers holder with a request ID and last regular/ZslStill
-         * frame number.
-         */
-        RequestLastFrameNumbersHolder(int requestId, long lastFrameNumber,
-                int[] repeatingRequestTypes) {
-            long lastRegularFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED;
-            long lastZslStillFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED;
-
-            if (repeatingRequestTypes == null) {
-                throw new IllegalArgumentException(
-                        "repeatingRequest list must not be null");
-            }
-            if (lastFrameNumber < repeatingRequestTypes.length - 1) {
-                throw new IllegalArgumentException(
-                        "lastFrameNumber: " + lastFrameNumber + " should be at least "
-                        + (repeatingRequestTypes.length - 1)
-                        + " for the number of requests in the list: "
-                        + repeatingRequestTypes.length);
-            }
-
-            long frameNumber = lastFrameNumber;
-            for (int i = repeatingRequestTypes.length - 1; i >= 0; i--) {
-                if (repeatingRequestTypes[i] == CaptureRequest.REQUEST_TYPE_ZSL_STILL
-                        && lastZslStillFrameNumber == CaptureCallback.NO_FRAMES_CAPTURED) {
-                    lastZslStillFrameNumber = frameNumber;
-                } else if (repeatingRequestTypes[i] == CaptureRequest.REQUEST_TYPE_REGULAR
-                        && lastRegularFrameNumber == CaptureCallback.NO_FRAMES_CAPTURED) {
-                    lastRegularFrameNumber = frameNumber;
-                }
-
-                if (lastZslStillFrameNumber != CaptureCallback.NO_FRAMES_CAPTURED
-                        && lastRegularFrameNumber != CaptureCallback.NO_FRAMES_CAPTURED) {
-                    break;
-                }
-
-                frameNumber--;
-            }
-
-            mLastRegularFrameNumber = lastRegularFrameNumber;
-            mLastZslStillFrameNumber = lastZslStillFrameNumber;
-            mLastReprocessFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED;
-            mRequestId = requestId;
-        }
-
-        /**
-         * Return the last regular frame number. Return CaptureCallback.NO_FRAMES_CAPTURED if
-         * it contains no regular request.
-         */
-        public long getLastRegularFrameNumber() {
-            return mLastRegularFrameNumber;
-        }
-
-        /**
-         * Return the last reprocess frame number. Return CaptureCallback.NO_FRAMES_CAPTURED if
-         * it contains no reprocess request.
-         */
-        public long getLastReprocessFrameNumber() {
-            return mLastReprocessFrameNumber;
-        }
-
-        /**
-         * Return the last ZslStill frame number. Return CaptureCallback.NO_FRAMES_CAPTURED if
-         * it contains no Zsl request.
-         */
-        public long getLastZslStillFrameNumber() {
-            return mLastZslStillFrameNumber;
-        }
-
-        /**
-         * Return the last frame number overall.
-         */
-        public long getLastFrameNumber() {
-            return Math.max(mLastZslStillFrameNumber,
-                    Math.max(mLastRegularFrameNumber, mLastReprocessFrameNumber));
-        }
-
-        /**
-         * Return the request ID.
-         */
-        public int getRequestId() {
-            return mRequestId;
-        }
-    }
-
-    /**
-     * This class tracks the last frame number for submitted requests.
-     */
-    public class FrameNumberTracker {
-
-        /** the completed frame number for each type of capture results */
-        private long[] mCompletedFrameNumber = new long[CaptureRequest.REQUEST_TYPE_COUNT];
-
-        /** the skipped frame numbers that don't belong to each type of capture results */
-        private final LinkedList<Long>[] mSkippedOtherFrameNumbers =
-                new LinkedList[CaptureRequest.REQUEST_TYPE_COUNT];
-
-        /** the skipped frame numbers that belong to each type of capture results */
-        private final LinkedList<Long>[] mSkippedFrameNumbers =
-                new LinkedList[CaptureRequest.REQUEST_TYPE_COUNT];
-
-        /** frame number -> request type */
-        private final TreeMap<Long, Integer> mFutureErrorMap = new TreeMap<Long, Integer>();
-        /** Map frame numbers to list of partial results */
-        private final HashMap<Long, List<CaptureResult>> mPartialResults = new HashMap<>();
-
-        public FrameNumberTracker() {
-            for (int i = 0; i < CaptureRequest.REQUEST_TYPE_COUNT; i++) {
-                mCompletedFrameNumber[i] = CaptureCallback.NO_FRAMES_CAPTURED;
-                mSkippedOtherFrameNumbers[i] = new LinkedList<Long>();
-                mSkippedFrameNumbers[i] = new LinkedList<Long>();
-            }
-        }
-
-        private void update() {
-            Iterator iter = mFutureErrorMap.entrySet().iterator();
-            while (iter.hasNext()) {
-                TreeMap.Entry pair = (TreeMap.Entry)iter.next();
-                Long errorFrameNumber = (Long)pair.getKey();
-                int requestType = (int) pair.getValue();
-                Boolean removeError = false;
-                if (errorFrameNumber == mCompletedFrameNumber[requestType] + 1) {
-                    mCompletedFrameNumber[requestType] = errorFrameNumber;
-                    removeError = true;
-                } else {
-                    if (!mSkippedFrameNumbers[requestType].isEmpty()) {
-                        if (errorFrameNumber == mSkippedFrameNumbers[requestType].element()) {
-                            mCompletedFrameNumber[requestType] = errorFrameNumber;
-                            mSkippedFrameNumbers[requestType].remove();
-                            removeError = true;
-                        }
-                    } else {
-                        for (int i = 1; i < CaptureRequest.REQUEST_TYPE_COUNT; i++) {
-                            int otherType = (requestType + i) % CaptureRequest.REQUEST_TYPE_COUNT;
-                            if (!mSkippedOtherFrameNumbers[otherType].isEmpty() && errorFrameNumber
-                                    == mSkippedOtherFrameNumbers[otherType].element()) {
-                                mCompletedFrameNumber[requestType] = errorFrameNumber;
-                                mSkippedOtherFrameNumbers[otherType].remove();
-                                removeError = true;
-                                break;
-                            }
-                        }
-                    }
-                }
-                if (removeError) {
-                    iter.remove();
-                }
-            }
-        }
-
-        /**
-         * This function is called every time when a result or an error is received.
-         * @param frameNumber the frame number corresponding to the result or error
-         * @param isError true if it is an error, false if it is not an error
-         * @param requestType the type of capture request: Reprocess, ZslStill, or Regular.
-         */
-        public void updateTracker(long frameNumber, boolean isError, int requestType) {
-            if (isError) {
-                mFutureErrorMap.put(frameNumber, requestType);
-            } else {
-                try {
-                    updateCompletedFrameNumber(frameNumber, requestType);
-                } catch (IllegalArgumentException e) {
-                    Log.e(TAG, e.getMessage());
-                }
-            }
-            update();
-        }
-
-        /**
-         * This function is called every time a result has been completed.
-         *
-         * <p>It keeps a track of all the partial results already created for a particular
-         * frame number.</p>
-         *
-         * @param frameNumber the frame number corresponding to the result
-         * @param result the total or partial result
-         * @param partial {@true} if the result is partial, {@code false} if total
-         * @param requestType the type of capture request: Reprocess, ZslStill, or Regular.
-         */
-        public void updateTracker(long frameNumber, CaptureResult result, boolean partial,
-                int requestType) {
-            if (!partial) {
-                // Update the total result's frame status as being successful
-                updateTracker(frameNumber, /*isError*/false, requestType);
-                // Don't keep a list of total results, we don't need to track them
-                return;
-            }
-
-            if (result == null) {
-                // Do not record blank results; this also means there will be no total result
-                // so it doesn't matter that the partials were not recorded
-                return;
-            }
-
-            // Partial results must be aggregated in-order for that frame number
-            List<CaptureResult> partials = mPartialResults.get(frameNumber);
-            if (partials == null) {
-                partials = new ArrayList<>();
-                mPartialResults.put(frameNumber, partials);
-            }
-
-            partials.add(result);
-        }
-
-        /**
-         * Attempt to pop off all of the partial results seen so far for the {@code frameNumber}.
-         *
-         * <p>Once popped-off, the partial results are forgotten (unless {@code updateTracker}
-         * is called again with new partials for that frame number).</p>
-         *
-         * @param frameNumber the frame number corresponding to the result
-         * @return a list of partial results for that frame with at least 1 element,
-         *         or {@code null} if there were no partials recorded for that frame
-         */
-        public List<CaptureResult> popPartialResults(long frameNumber) {
-            return mPartialResults.remove(frameNumber);
-        }
-
-        public long getCompletedFrameNumber() {
-            return mCompletedFrameNumber[CaptureRequest.REQUEST_TYPE_REGULAR];
-        }
-
-        public long getCompletedReprocessFrameNumber() {
-            return mCompletedFrameNumber[CaptureRequest.REQUEST_TYPE_REPROCESS];
-        }
-
-        public long getCompletedZslStillFrameNumber() {
-            return mCompletedFrameNumber[CaptureRequest.REQUEST_TYPE_ZSL_STILL];
-        }
-
-        /**
-         * Update the completed frame number for results of 3 categories
-         * (Regular/Reprocess/ZslStill).
-         *
-         * It validates that all previous frames of the same category have arrived.
-         *
-         * If there is a gap since previous frame number of the same category, assume the frames in
-         * the gap are other categories and store them in the skipped frame number queue to check
-         * against when frames of those categories arrive.
-         */
-        private void updateCompletedFrameNumber(long frameNumber,
-                int requestType) throws IllegalArgumentException {
-            if (frameNumber <= mCompletedFrameNumber[requestType]) {
-                throw new IllegalArgumentException("frame number " + frameNumber + " is a repeat");
-            }
-
-            // Assume there are only 3 different types of capture requests.
-            int otherType1 = (requestType + 1) % CaptureRequest.REQUEST_TYPE_COUNT;
-            int otherType2 = (requestType + 2) % CaptureRequest.REQUEST_TYPE_COUNT;
-            long maxOtherFrameNumberSeen =
-                    Math.max(mCompletedFrameNumber[otherType1], mCompletedFrameNumber[otherType2]);
-            if (frameNumber < maxOtherFrameNumberSeen) {
-                // if frame number is smaller than completed frame numbers of other categories,
-                // it must be:
-                // - the head of mSkippedFrameNumbers for this category, or
-                // - in one of other mSkippedOtherFrameNumbers
-                if (!mSkippedFrameNumbers[requestType].isEmpty()) {
-                    // frame number must be head of current type of mSkippedFrameNumbers if
-                    // mSkippedFrameNumbers isn't empty.
-                    if (frameNumber < mSkippedFrameNumbers[requestType].element()) {
-                        throw new IllegalArgumentException("frame number " + frameNumber
-                                + " is a repeat");
-                    } else if (frameNumber > mSkippedFrameNumbers[requestType].element()) {
-                        throw new IllegalArgumentException("frame number " + frameNumber
-                                + " comes out of order. Expecting "
-                                + mSkippedFrameNumbers[requestType].element());
-                    }
-                    // frame number matches the head of the skipped frame number queue.
-                    mSkippedFrameNumbers[requestType].remove();
-                } else {
-                    // frame number must be in one of the other mSkippedOtherFrameNumbers.
-                    int index1 = mSkippedOtherFrameNumbers[otherType1].indexOf(frameNumber);
-                    int index2 = mSkippedOtherFrameNumbers[otherType2].indexOf(frameNumber);
-                    boolean inSkippedOther1 = index1 != -1;
-                    boolean inSkippedOther2 = index2 != -1;
-                    if (!(inSkippedOther1 ^ inSkippedOther2)) {
-                        throw new IllegalArgumentException("frame number " + frameNumber
-                                + " is a repeat or invalid");
-                    }
-
-                    // We know the category of frame numbers in skippedOtherFrameNumbers leading up
-                    // to the current frame number. Move them into the correct skippedFrameNumbers.
-                    LinkedList<Long> srcList, dstList;
-                    int index;
-                    if (inSkippedOther1) {
-                        srcList = mSkippedOtherFrameNumbers[otherType1];
-                        dstList = mSkippedFrameNumbers[otherType2];
-                        index = index1;
-                    } else {
-                        srcList = mSkippedOtherFrameNumbers[otherType2];
-                        dstList = mSkippedFrameNumbers[otherType1];
-                        index = index2;
-                    }
-                    for (int i = 0; i < index; i++) {
-                        dstList.add(srcList.removeFirst());
-                    }
-
-                    // Remove current frame number from skippedOtherFrameNumbers
-                    srcList.remove();
-                }
-            } else {
-                // there is a gap of unseen frame numbers which should belong to the other
-                // 2 categories. Put all the skipped frame numbers in the queue.
-                for (long i =
-                        Math.max(maxOtherFrameNumberSeen, mCompletedFrameNumber[requestType]) + 1;
-                        i < frameNumber; i++) {
-                    mSkippedOtherFrameNumbers[requestType].add(i);
-                }
-            }
-
-            mCompletedFrameNumber[requestType] = frameNumber;
-        }
-    }
-
     private void checkAndFireSequenceComplete() {
         long completedFrameNumber = mFrameNumberTracker.getCompletedFrameNumber();
         long completedReprocessFrameNumber = mFrameNumberTracker.getCompletedReprocessFrameNumber();
@@ -1979,6 +1504,201 @@
         }
     }
 
+    public void onDeviceError(final int errorCode, CaptureResultExtras resultExtras) {
+        if (DEBUG) {
+            Log.d(TAG, String.format(
+                    "Device error received, code %d, frame number %d, request ID %d, subseq ID %d",
+                    errorCode, resultExtras.getFrameNumber(), resultExtras.getRequestId(),
+                    resultExtras.getSubsequenceId()));
+        }
+
+        synchronized(mInterfaceLock) {
+            if (mRemoteDevice == null) {
+                return; // Camera already closed
+            }
+
+            // Redirect device callback to the offline session in case we are in the middle
+            // of an offline switch
+            if (mOfflineSessionImpl != null) {
+                mOfflineSessionImpl.getCallbacks().onDeviceError(errorCode, resultExtras);
+                return;
+            }
+
+            switch (errorCode) {
+                case CameraDeviceCallbacks.ERROR_CAMERA_DISCONNECTED:
+                    final long ident = Binder.clearCallingIdentity();
+                    try {
+                        mDeviceExecutor.execute(mCallOnDisconnected);
+                    } finally {
+                        Binder.restoreCallingIdentity(ident);
+                    }
+                    break;
+                case CameraDeviceCallbacks.ERROR_CAMERA_REQUEST:
+                case CameraDeviceCallbacks.ERROR_CAMERA_RESULT:
+                case CameraDeviceCallbacks.ERROR_CAMERA_BUFFER:
+                    onCaptureErrorLocked(errorCode, resultExtras);
+                    break;
+                case CameraDeviceCallbacks.ERROR_CAMERA_DEVICE:
+                    scheduleNotifyError(StateCallback.ERROR_CAMERA_DEVICE);
+                    break;
+                case CameraDeviceCallbacks.ERROR_CAMERA_DISABLED:
+                    scheduleNotifyError(StateCallback.ERROR_CAMERA_DISABLED);
+                    break;
+                default:
+                    Log.e(TAG, "Unknown error from camera device: " + errorCode);
+                    scheduleNotifyError(StateCallback.ERROR_CAMERA_SERVICE);
+            }
+        }
+    }
+
+    private void scheduleNotifyError(int code) {
+        mInError = true;
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mDeviceExecutor.execute(obtainRunnable(
+                        CameraDeviceImpl::notifyError, this, code).recycleOnUse());
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void notifyError(int code) {
+        if (!CameraDeviceImpl.this.isClosed()) {
+            mDeviceCallback.onError(CameraDeviceImpl.this, code);
+        }
+    }
+
+    /**
+     * Called by onDeviceError for handling single-capture failures.
+     */
+    private void onCaptureErrorLocked(int errorCode, CaptureResultExtras resultExtras) {
+
+        final int requestId = resultExtras.getRequestId();
+        final int subsequenceId = resultExtras.getSubsequenceId();
+        final long frameNumber = resultExtras.getFrameNumber();
+        final String errorPhysicalCameraId = resultExtras.getErrorPhysicalCameraId();
+        final CaptureCallbackHolder holder = mCaptureCallbackMap.get(requestId);
+
+        if (holder == null) {
+            Log.e(TAG, String.format("Receive capture error on unknown request ID %d",
+                    requestId));
+            return;
+        }
+
+        final CaptureRequest request = holder.getRequest(subsequenceId);
+
+        Runnable failureDispatch = null;
+        if (errorCode == CameraDeviceCallbacks.ERROR_CAMERA_BUFFER) {
+            // Because 1 stream id could map to multiple surfaces, we need to specify both
+            // streamId and surfaceId.
+            OutputConfiguration config = mConfiguredOutputs.get(
+                    resultExtras.getErrorStreamId());
+            if (config == null) {
+                Log.v(TAG, String.format(
+                        "Stream %d has been removed. Skipping buffer lost callback",
+                        resultExtras.getErrorStreamId()));
+                return;
+            }
+            for (Surface surface : config.getSurfaces()) {
+                if (!request.containsTarget(surface)) {
+                    continue;
+                }
+                if (DEBUG) {
+                    Log.v(TAG, String.format(
+                            "Lost output buffer reported for frame %d, target %s",
+                            frameNumber, surface));
+                }
+                failureDispatch = new Runnable() {
+                    @Override
+                    public void run() {
+                        if (!isClosed()){
+                            holder.getCallback().onCaptureBufferLost(CameraDeviceImpl.this, request,
+                                    surface, frameNumber);
+                        }
+                    }
+                };
+                // Dispatch the failure callback
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    holder.getExecutor().execute(failureDispatch);
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
+        } else {
+            boolean mayHaveBuffers = (errorCode == CameraDeviceCallbacks.ERROR_CAMERA_RESULT);
+
+            // This is only approximate - exact handling needs the camera service and HAL to
+            // disambiguate between request failures to due abort and due to real errors.  For
+            // now, assume that if the session believes we're mid-abort, then the error is due
+            // to abort.
+            int reason = (mCurrentSession != null && mCurrentSession.isAborting()) ?
+                    CaptureFailure.REASON_FLUSHED :
+                    CaptureFailure.REASON_ERROR;
+
+            final CaptureFailure failure = new CaptureFailure(
+                request,
+                reason,
+                /*dropped*/ mayHaveBuffers,
+                requestId,
+                frameNumber,
+                errorPhysicalCameraId);
+
+            failureDispatch = new Runnable() {
+                @Override
+                public void run() {
+                    if (!isClosed()){
+                        holder.getCallback().onCaptureFailed(CameraDeviceImpl.this, request,
+                                failure);
+                    }
+                }
+            };
+
+            // Fire onCaptureSequenceCompleted if appropriate
+            if (DEBUG) {
+                Log.v(TAG, String.format("got error frame %d", frameNumber));
+            }
+            mFrameNumberTracker.updateTracker(frameNumber,
+                    /*error*/true, request.getRequestType());
+            checkAndFireSequenceComplete();
+
+            // Dispatch the failure callback
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                holder.getExecutor().execute(failureDispatch);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+    }
+
+    public void onDeviceIdle() {
+        if (DEBUG) {
+            Log.d(TAG, "Camera now idle");
+        }
+        synchronized(mInterfaceLock) {
+            if (mRemoteDevice == null) return; // Camera already closed
+
+            // Redirect device callback to the offline session in case we are in the middle
+            // of an offline switch
+            if (mOfflineSessionImpl != null) {
+                mOfflineSessionImpl.getCallbacks().onDeviceIdle();
+                return;
+            }
+
+            if (!CameraDeviceImpl.this.mIdle) {
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    mDeviceExecutor.execute(mCallOnIdle);
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
+            mIdle = true;
+        }
+    }
+
     public class CameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
 
         @Override
@@ -1988,60 +1708,7 @@
 
         @Override
         public void onDeviceError(final int errorCode, CaptureResultExtras resultExtras) {
-            if (DEBUG) {
-                Log.d(TAG, String.format(
-                        "Device error received, code %d, frame number %d, request ID %d, subseq ID %d",
-                        errorCode, resultExtras.getFrameNumber(), resultExtras.getRequestId(),
-                        resultExtras.getSubsequenceId()));
-            }
-
-            synchronized(mInterfaceLock) {
-                if (mRemoteDevice == null) {
-                    return; // Camera already closed
-                }
-
-                switch (errorCode) {
-                    case ERROR_CAMERA_DISCONNECTED:
-                        final long ident = Binder.clearCallingIdentity();
-                        try {
-                            CameraDeviceImpl.this.mDeviceExecutor.execute(mCallOnDisconnected);
-                        } finally {
-                            Binder.restoreCallingIdentity(ident);
-                        }
-                        break;
-                    case ERROR_CAMERA_REQUEST:
-                    case ERROR_CAMERA_RESULT:
-                    case ERROR_CAMERA_BUFFER:
-                        onCaptureErrorLocked(errorCode, resultExtras);
-                        break;
-                    case ERROR_CAMERA_DEVICE:
-                        scheduleNotifyError(StateCallback.ERROR_CAMERA_DEVICE);
-                        break;
-                    case ERROR_CAMERA_DISABLED:
-                        scheduleNotifyError(StateCallback.ERROR_CAMERA_DISABLED);
-                        break;
-                    default:
-                        Log.e(TAG, "Unknown error from camera device: " + errorCode);
-                        scheduleNotifyError(StateCallback.ERROR_CAMERA_SERVICE);
-                }
-            }
-        }
-
-        private void scheduleNotifyError(int code) {
-            mInError = true;
-            final long ident = Binder.clearCallingIdentity();
-            try {
-                CameraDeviceImpl.this.mDeviceExecutor.execute(obtainRunnable(
-                            CameraDeviceCallbacks::notifyError, this, code).recycleOnUse());
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-
-        private void notifyError(int code) {
-            if (!CameraDeviceImpl.this.isClosed()) {
-                mDeviceCallback.onError(CameraDeviceImpl.this, code);
-            }
+            CameraDeviceImpl.this.onDeviceError(errorCode, resultExtras);
         }
 
         @Override
@@ -2057,6 +1724,14 @@
                     return; // Camera already closed
                 }
 
+                // Redirect device callback to the offline session in case we are in the middle
+                // of an offline switch
+                if (mOfflineSessionImpl != null) {
+                    mOfflineSessionImpl.getCallbacks().onRepeatingRequestError(
+                           lastFrameNumber, repeatingRequestId);
+                    return;
+                }
+
                 checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber,
                         mRepeatingRequestTypes);
                 // Check if there is already a new repeating request
@@ -2069,22 +1744,7 @@
 
         @Override
         public void onDeviceIdle() {
-            if (DEBUG) {
-                Log.d(TAG, "Camera now idle");
-            }
-            synchronized(mInterfaceLock) {
-                if (mRemoteDevice == null) return; // Camera already closed
-
-                if (!CameraDeviceImpl.this.mIdle) {
-                    final long ident = Binder.clearCallingIdentity();
-                    try {
-                        CameraDeviceImpl.this.mDeviceExecutor.execute(mCallOnIdle);
-                    } finally {
-                        Binder.restoreCallingIdentity(ident);
-                    }
-                }
-                CameraDeviceImpl.this.mIdle = true;
-            }
+            CameraDeviceImpl.this.onDeviceIdle();
         }
 
         @Override
@@ -2100,6 +1760,15 @@
             synchronized(mInterfaceLock) {
                 if (mRemoteDevice == null) return; // Camera already closed
 
+
+                // Redirect device callback to the offline session in case we are in the middle
+                // of an offline switch
+                if (mOfflineSessionImpl != null) {
+                    mOfflineSessionImpl.getCallbacks().onCaptureStarted(resultExtras,
+                            timestamp);
+                    return;
+                }
+
                 // Get the callback for this frame ID, if there is one
                 holder = CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId);
 
@@ -2164,6 +1833,15 @@
             synchronized(mInterfaceLock) {
                 if (mRemoteDevice == null) return; // Camera already closed
 
+
+                // Redirect device callback to the offline session in case we are in the middle
+                // of an offline switch
+                if (mOfflineSessionImpl != null) {
+                    mOfflineSessionImpl.getCallbacks().onResultReceived(result, resultExtras,
+                            physicalResults);
+                    return;
+                }
+
                 // TODO: Handle CameraCharacteristics access from CaptureResult correctly.
                 result.set(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE,
                         getCharacteristics().get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE));
@@ -2325,6 +2003,13 @@
             }
 
             synchronized(mInterfaceLock) {
+                // Redirect device callback to the offline session in case we are in the middle
+                // of an offline switch
+                if (mOfflineSessionImpl != null) {
+                    mOfflineSessionImpl.getCallbacks().onPrepared(streamId);
+                    return;
+                }
+
                 output = mConfiguredOutputs.get(streamId);
                 sessionCallback = mSessionStateCallback;
             }
@@ -2350,6 +2035,13 @@
             }
 
             synchronized(mInterfaceLock) {
+                // Redirect device callback to the offline session in case we are in the middle
+                // of an offline switch
+                if (mOfflineSessionImpl != null) {
+                    mOfflineSessionImpl.getCallbacks().onRequestQueueEmpty();
+                    return;
+                }
+
                 sessionCallback = mSessionStateCallback;
             }
 
@@ -2358,117 +2050,6 @@
             sessionCallback.onRequestQueueEmpty();
         }
 
-        /**
-         * Called by onDeviceError for handling single-capture failures.
-         */
-        private void onCaptureErrorLocked(int errorCode, CaptureResultExtras resultExtras) {
-
-            final int requestId = resultExtras.getRequestId();
-            final int subsequenceId = resultExtras.getSubsequenceId();
-            final long frameNumber = resultExtras.getFrameNumber();
-            final String errorPhysicalCameraId = resultExtras.getErrorPhysicalCameraId();
-            final CaptureCallbackHolder holder =
-                    CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId);
-
-            if (holder == null) {
-                Log.e(TAG, String.format("Receive capture error on unknown request ID %d",
-                        requestId));
-                return;
-            }
-
-            final CaptureRequest request = holder.getRequest(subsequenceId);
-
-            Runnable failureDispatch = null;
-            if (errorCode == ERROR_CAMERA_BUFFER) {
-                // Because 1 stream id could map to multiple surfaces, we need to specify both
-                // streamId and surfaceId.
-                OutputConfiguration config = mConfiguredOutputs.get(
-                        resultExtras.getErrorStreamId());
-                if (config == null) {
-                    Log.v(TAG, String.format(
-                            "Stream %d has been removed. Skipping buffer lost callback",
-                            resultExtras.getErrorStreamId()));
-                    return;
-                }
-                for (Surface surface : config.getSurfaces()) {
-                    if (!request.containsTarget(surface)) {
-                        continue;
-                    }
-                    if (DEBUG) {
-                        Log.v(TAG, String.format(
-                                "Lost output buffer reported for frame %d, target %s",
-                                frameNumber, surface));
-                    }
-                    failureDispatch = new Runnable() {
-                        @Override
-                        public void run() {
-                            if (!CameraDeviceImpl.this.isClosed()){
-                                holder.getCallback().onCaptureBufferLost(
-                                    CameraDeviceImpl.this,
-                                    request,
-                                    surface,
-                                    frameNumber);
-                            }
-                        }
-                    };
-                    // Dispatch the failure callback
-                    final long ident = Binder.clearCallingIdentity();
-                    try {
-                        holder.getExecutor().execute(failureDispatch);
-                    } finally {
-                        Binder.restoreCallingIdentity(ident);
-                    }
-                }
-            } else {
-                boolean mayHaveBuffers = (errorCode == ERROR_CAMERA_RESULT);
-
-                // This is only approximate - exact handling needs the camera service and HAL to
-                // disambiguate between request failures to due abort and due to real errors.  For
-                // now, assume that if the session believes we're mid-abort, then the error is due
-                // to abort.
-                int reason = (mCurrentSession != null && mCurrentSession.isAborting()) ?
-                        CaptureFailure.REASON_FLUSHED :
-                        CaptureFailure.REASON_ERROR;
-
-                final CaptureFailure failure = new CaptureFailure(
-                    request,
-                    reason,
-                    /*dropped*/ mayHaveBuffers,
-                    requestId,
-                    frameNumber,
-                    errorPhysicalCameraId);
-
-                failureDispatch = new Runnable() {
-                    @Override
-                    public void run() {
-                        if (!CameraDeviceImpl.this.isClosed()){
-                            holder.getCallback().onCaptureFailed(
-                                CameraDeviceImpl.this,
-                                request,
-                                failure);
-                        }
-                    }
-                };
-
-                // Fire onCaptureSequenceCompleted if appropriate
-                if (DEBUG) {
-                    Log.v(TAG, String.format("got error frame %d", frameNumber));
-                }
-                mFrameNumberTracker.updateTracker(frameNumber,
-                        /*error*/true, request.getRequestType());
-                checkAndFireSequenceComplete();
-
-                // Dispatch the failure callback
-                final long ident = Binder.clearCallingIdentity();
-                try {
-                    holder.getExecutor().execute(failureDispatch);
-                } finally {
-                    Binder.restoreCallingIdentity(ident);
-                }
-            }
-
-        }
-
     } // public class CameraDeviceCallbacks
 
     /**
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 1fab666..3ae3d78 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -1703,6 +1703,15 @@
 
 
     /**
+     * Retrieves the pointer to the native CameraMetadata as a Java long.
+     *
+     * @hide
+     */
+    public long getMetadataPtr() {
+        return mMetadataPtr;
+    }
+
+    /**
      * Return a list containing keys of the given key class for all defined vendor tags.
      *
      * @hide
diff --git a/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java
new file mode 100644
index 0000000..1db377a
--- /dev/null
+++ b/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java
@@ -0,0 +1,840 @@
+/*
+ * 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.hardware.camera2.impl;
+
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraOfflineSession;
+import android.hardware.camera2.CameraOfflineSession.CameraOfflineSessionCallback;
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.ICameraDeviceCallbacks;
+import android.hardware.camera2.ICameraOfflineSession;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.InputConfiguration;
+import android.hardware.camera2.params.OutputConfiguration;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Range;
+import android.util.SparseArray;
+import android.view.Surface;
+
+import java.util.AbstractMap.SimpleEntry;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.Executor;
+
+import static com.android.internal.util.Preconditions.*;
+
+public class CameraOfflineSessionImpl extends CameraOfflineSession
+        implements IBinder.DeathRecipient {
+    private static final String TAG = "CameraOfflineSessionImpl";
+    private static final int REQUEST_ID_NONE = -1;
+    private static final long NANO_PER_SECOND = 1000000000; //ns
+    private final boolean DEBUG = false;
+
+    private ICameraOfflineSession mRemoteSession;
+    private final AtomicBoolean mClosing = new AtomicBoolean();
+
+    private SimpleEntry<Integer, InputConfiguration> mOfflineInput =
+            new SimpleEntry<>(REQUEST_ID_NONE, null);
+    private SparseArray<OutputConfiguration> mOfflineOutputs = new SparseArray<>();
+
+    final Object mInterfaceLock = new Object(); // access from this class and Session only!
+
+    private final String mCameraId;
+    private final CameraCharacteristics mCharacteristics;
+    private final int mTotalPartialCount;
+
+    private final Executor mOfflineExecutor;
+    private final CameraOfflineSessionCallback mOfflineCallback;
+
+    private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks();
+
+    /**
+     * A list tracking request and its expected last regular/reprocess/zslStill frame
+     * number.
+     */
+    private List<RequestLastFrameNumbersHolder> mOfflineRequestLastFrameNumbersList =
+            new ArrayList<>();
+
+    /**
+     * An object tracking received frame numbers.
+     * Updated when receiving callbacks from ICameraDeviceCallbacks.
+     */
+    private FrameNumberTracker mFrameNumberTracker = new FrameNumberTracker();
+
+    /** map request IDs to callback/request data */
+    private SparseArray<CaptureCallbackHolder> mCaptureCallbackMap =
+            new SparseArray<CaptureCallbackHolder>();
+
+    public CameraOfflineSessionImpl(String cameraId, CameraCharacteristics characteristics,
+            Executor offlineExecutor, CameraOfflineSessionCallback offlineCallback,
+            SparseArray<OutputConfiguration> offlineOutputs,
+            SimpleEntry<Integer, InputConfiguration> offlineInput,
+            FrameNumberTracker frameNumberTracker, SparseArray<CaptureCallbackHolder> callbackMap,
+            List<RequestLastFrameNumbersHolder> frameNumberList) {
+        if ((cameraId == null) || (characteristics == null)) {
+            throw new IllegalArgumentException("Null argument given");
+        }
+
+        mCameraId = cameraId;
+        mCharacteristics = characteristics;
+
+        Integer partialCount =
+                mCharacteristics.get(CameraCharacteristics.REQUEST_PARTIAL_RESULT_COUNT);
+        if (partialCount == null) {
+            // 1 means partial result is not supported.
+            mTotalPartialCount = 1;
+        } else {
+            mTotalPartialCount = partialCount;
+        }
+
+        mOfflineRequestLastFrameNumbersList.addAll(frameNumberList);
+        mFrameNumberTracker = frameNumberTracker;
+        mCaptureCallbackMap = callbackMap;
+        mOfflineOutputs = offlineOutputs;
+        mOfflineInput = offlineInput;
+        mOfflineExecutor = checkNotNull(offlineExecutor, "offline executor must not be null");
+        mOfflineCallback = checkNotNull(offlineCallback, "offline callback must not be null");
+
+    }
+
+    public CameraDeviceCallbacks getCallbacks() {
+        return mCallbacks;
+    }
+
+    public class CameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
+        @Override
+        public IBinder asBinder() {
+            return this;
+        }
+
+        @Override
+        public void onDeviceError(final int errorCode, CaptureResultExtras resultExtras) {
+            synchronized(mInterfaceLock) {
+                if (mRemoteSession == null) {
+                    return; // Camera already closed
+                }
+
+                switch (errorCode) {
+                    case CameraDeviceCallbacks.ERROR_CAMERA_REQUEST:
+                    case CameraDeviceCallbacks.ERROR_CAMERA_RESULT:
+                    case CameraDeviceCallbacks.ERROR_CAMERA_BUFFER:
+                        onCaptureErrorLocked(errorCode, resultExtras);
+                        break;
+                    default:
+                        Runnable errorDispatch = new Runnable() {
+                            @Override
+                            public void run() {
+                                if (!isClosed()) {
+                                    mOfflineCallback.onError(CameraOfflineSessionImpl.this,
+                                            CameraOfflineSessionCallback.STATUS_INTERNAL_ERROR);
+                                }
+                            }
+                        };
+
+                        final long ident = Binder.clearCallingIdentity();
+                        try {
+                            mOfflineExecutor.execute(errorDispatch);
+                        } finally {
+                            Binder.restoreCallingIdentity(ident);
+                        }
+                }
+            }
+        }
+
+        @Override
+        public void onRepeatingRequestError(long lastFrameNumber, int repeatingRequestId) {
+            Log.e(TAG, "Unexpected repeating request error received. Last frame number is " +
+                    lastFrameNumber);
+        }
+
+        @Override
+        public void onDeviceIdle() {
+            synchronized(mInterfaceLock) {
+                Runnable idleDispatch = new Runnable() {
+                    @Override
+                    public void run() {
+                        if (!isClosed()) {
+                            mOfflineCallback.onIdle(CameraOfflineSessionImpl.this);
+                        }
+                    }
+                };
+
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    mOfflineExecutor.execute(idleDispatch);
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
+        }
+
+        @Override
+        public void onCaptureStarted(final CaptureResultExtras resultExtras, final long timestamp) {
+            int requestId = resultExtras.getRequestId();
+            final long frameNumber = resultExtras.getFrameNumber();
+
+            final CaptureCallbackHolder holder;
+
+            synchronized(mInterfaceLock) {
+                if (mRemoteSession == null) return; // Camera already closed
+
+                // Get the callback for this frame ID, if there is one
+                holder = CameraOfflineSessionImpl.this.mCaptureCallbackMap.get(requestId);
+
+                if (holder == null) {
+                    return;
+                }
+
+                final Executor executor = holder.getCallback().getExecutor();
+                if (isClosed() || (executor == null)) return;
+
+                // Dispatch capture start notice
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    executor.execute(
+                        new Runnable() {
+                            @Override
+                            public void run() {
+                                final CameraCaptureSession.CaptureCallback callback =
+                                        holder.getCallback().getSessionCallback();
+                                if (!CameraOfflineSessionImpl.this.isClosed() &&
+                                        (callback != null)) {
+                                    final int subsequenceId = resultExtras.getSubsequenceId();
+                                    final CaptureRequest request = holder.getRequest(subsequenceId);
+
+                                    if (holder.hasBatchedOutputs()) {
+                                        // Send derived onCaptureStarted for requests within the
+                                        // batch
+                                        final Range<Integer> fpsRange =
+                                                request.get(
+                                                        CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE);
+                                        for (int i = 0; i < holder.getRequestCount(); i++) {
+                                            final CaptureRequest cbRequest = holder.getRequest(i);
+                                            final long cbTimestamp =
+                                                        timestamp - (subsequenceId - i) *
+                                                        NANO_PER_SECOND/fpsRange.getUpper();
+                                            final long cbFrameNumber =
+                                                    frameNumber - (subsequenceId - i);
+                                            callback.onCaptureStarted(CameraOfflineSessionImpl.this,
+                                                    cbRequest, cbTimestamp, cbFrameNumber);
+                                        }
+                                    } else {
+                                        callback.onCaptureStarted(CameraOfflineSessionImpl.this,
+                                                holder.getRequest(
+                                                    resultExtras.getSubsequenceId()),
+                                                timestamp, frameNumber);
+                                    }
+                                }
+                            }
+                        });
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
+        }
+
+        @Override
+        public void onResultReceived(CameraMetadataNative result,
+                CaptureResultExtras resultExtras, PhysicalCaptureResultInfo physicalResults[])
+                throws RemoteException {
+
+            int requestId = resultExtras.getRequestId();
+            long frameNumber = resultExtras.getFrameNumber();
+
+            synchronized(mInterfaceLock) {
+                if (mRemoteSession == null) return; // Camera already closed
+
+                // TODO: Handle CameraCharacteristics access from CaptureResult correctly.
+                result.set(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE,
+                        mCharacteristics.get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE));
+
+                final CaptureCallbackHolder holder =
+                        CameraOfflineSessionImpl.this.mCaptureCallbackMap.get(requestId);
+                final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId());
+
+                boolean isPartialResult =
+                        (resultExtras.getPartialResultCount() < mTotalPartialCount);
+                int requestType = request.getRequestType();
+
+                // Check if we have a callback for this
+                if (holder == null) {
+                    mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult,
+                            requestType);
+
+                    return;
+                }
+
+                if (isClosed()) {
+                    mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult,
+                            requestType);
+                    return;
+                }
+
+
+                Runnable resultDispatch = null;
+
+                CaptureResult finalResult;
+                // Make a copy of the native metadata before it gets moved to a CaptureResult
+                // object.
+                final CameraMetadataNative resultCopy;
+                if (holder.hasBatchedOutputs()) {
+                    resultCopy = new CameraMetadataNative(result);
+                } else {
+                    resultCopy = null;
+                }
+
+                final Executor executor = holder.getCallback().getExecutor();
+                // Either send a partial result or the final capture completed result
+                if (isPartialResult) {
+                    final CaptureResult resultAsCapture =
+                            new CaptureResult(result, request, resultExtras);
+                    // Partial result
+                    resultDispatch = new Runnable() {
+                        @Override
+                        public void run() {
+                            final CameraCaptureSession.CaptureCallback callback =
+                                    holder.getCallback().getSessionCallback();
+                            if (!CameraOfflineSessionImpl.this.isClosed() && (callback != null)) {
+                                if (holder.hasBatchedOutputs()) {
+                                    // Send derived onCaptureProgressed for requests within
+                                    // the batch.
+                                    for (int i = 0; i < holder.getRequestCount(); i++) {
+                                        CameraMetadataNative resultLocal =
+                                                new CameraMetadataNative(resultCopy);
+                                        final CaptureResult resultInBatch = new CaptureResult(
+                                                resultLocal, holder.getRequest(i), resultExtras);
+
+                                        final CaptureRequest cbRequest = holder.getRequest(i);
+                                        callback.onCaptureProgressed(CameraOfflineSessionImpl.this,
+                                                cbRequest, resultInBatch);
+                                    }
+                                } else {
+                                    callback.onCaptureProgressed(CameraOfflineSessionImpl.this,
+                                            request, resultAsCapture);
+                                }
+                            }
+                        }
+                    };
+                    finalResult = resultAsCapture;
+                } else {
+                    List<CaptureResult> partialResults =
+                            mFrameNumberTracker.popPartialResults(frameNumber);
+
+                    final long sensorTimestamp =
+                            result.get(CaptureResult.SENSOR_TIMESTAMP);
+                    final Range<Integer> fpsRange =
+                            request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE);
+                    final int subsequenceId = resultExtras.getSubsequenceId();
+                    final TotalCaptureResult resultAsCapture = new TotalCaptureResult(result,
+                            request, resultExtras, partialResults, holder.getSessionId(),
+                            physicalResults);
+                    // Final capture result
+                    resultDispatch = new Runnable() {
+                        @Override
+                        public void run() {
+                            final CameraCaptureSession.CaptureCallback callback =
+                                    holder.getCallback().getSessionCallback();
+                            if (!CameraOfflineSessionImpl.this.isClosed() && (callback != null)) {
+                                if (holder.hasBatchedOutputs()) {
+                                    // Send derived onCaptureCompleted for requests within
+                                    // the batch.
+                                    for (int i = 0; i < holder.getRequestCount(); i++) {
+                                        resultCopy.set(CaptureResult.SENSOR_TIMESTAMP,
+                                                sensorTimestamp - (subsequenceId - i) *
+                                                NANO_PER_SECOND/fpsRange.getUpper());
+                                        CameraMetadataNative resultLocal =
+                                                new CameraMetadataNative(resultCopy);
+                                        // No logical multi-camera support for batched output mode.
+                                        TotalCaptureResult resultInBatch = new TotalCaptureResult(
+                                            resultLocal, holder.getRequest(i), resultExtras,
+                                            partialResults, holder.getSessionId(),
+                                            new PhysicalCaptureResultInfo[0]);
+
+                                        final CaptureRequest cbRequest = holder.getRequest(i);
+                                        callback.onCaptureCompleted(CameraOfflineSessionImpl.this,
+                                                cbRequest, resultInBatch);
+                                    }
+                                } else {
+                                    callback.onCaptureCompleted(CameraOfflineSessionImpl.this,
+                                            request, resultAsCapture);
+                                }
+                            }
+                        }
+                    };
+                    finalResult = resultAsCapture;
+                }
+
+                if (executor != null) {
+                    final long ident = Binder.clearCallingIdentity();
+                    try {
+                        executor.execute(resultDispatch);
+                    } finally {
+                        Binder.restoreCallingIdentity(ident);
+                    }
+                }
+
+                // Collect the partials for a total result; or mark the frame as totally completed
+                mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult,
+                        requestType);
+
+                // Fire onCaptureSequenceCompleted
+                if (!isPartialResult) {
+                    checkAndFireSequenceComplete();
+                }
+            }
+        }
+
+        @Override
+        public void onPrepared(int streamId) {
+            Log.e(TAG, "Unexpected stream " + streamId + " is prepared");
+        }
+
+        @Override
+        public void onRequestQueueEmpty() {
+            // No-op during offline mode
+            Log.v(TAG, "onRequestQueueEmpty");
+        }
+
+        /**
+         * Called by onDeviceError for handling single-capture failures.
+         */
+        private void onCaptureErrorLocked(int errorCode, CaptureResultExtras resultExtras) {
+            final int requestId = resultExtras.getRequestId();
+            final int subsequenceId = resultExtras.getSubsequenceId();
+            final long frameNumber = resultExtras.getFrameNumber();
+            final String errorPhysicalCameraId = resultExtras.getErrorPhysicalCameraId();
+            final CaptureCallbackHolder holder =
+                    CameraOfflineSessionImpl.this.mCaptureCallbackMap.get(requestId);
+
+            if (holder == null) {
+                Log.e(TAG, String.format("Receive capture error on unknown request ID %d",
+                        requestId));
+                return;
+            }
+
+            final CaptureRequest request = holder.getRequest(subsequenceId);
+
+            Runnable failureDispatch = null;
+            if (errorCode == ERROR_CAMERA_BUFFER) {
+                // Because 1 stream id could map to multiple surfaces, we need to specify both
+                // streamId and surfaceId.
+                OutputConfiguration config = mOfflineOutputs.get(
+                        resultExtras.getErrorStreamId());
+                if (config == null) {
+                    Log.v(TAG, String.format(
+                            "Stream %d has been removed. Skipping buffer lost callback",
+                            resultExtras.getErrorStreamId()));
+                    return;
+                }
+                for (Surface surface : config.getSurfaces()) {
+                    if (!request.containsTarget(surface)) {
+                        continue;
+                    }
+                    final Executor executor = holder.getCallback().getExecutor();
+                    failureDispatch = new Runnable() {
+                        @Override
+                        public void run() {
+                            final CameraCaptureSession.CaptureCallback callback =
+                                    holder.getCallback().getSessionCallback();
+                            if (!CameraOfflineSessionImpl.this.isClosed() && (callback != null)) {
+                                callback.onCaptureBufferLost( CameraOfflineSessionImpl.this,
+                                        request, surface, frameNumber);
+                            }
+                        }
+                    };
+                    if (executor != null) {
+                        // Dispatch the failure callback
+                        final long ident = Binder.clearCallingIdentity();
+                        try {
+                            executor.execute(failureDispatch);
+                        } finally {
+                            Binder.restoreCallingIdentity(ident);
+                        }
+                    }
+                }
+            } else {
+                boolean mayHaveBuffers = (errorCode == ERROR_CAMERA_RESULT);
+                int reason = CaptureFailure.REASON_ERROR;
+
+                final CaptureFailure failure = new CaptureFailure(
+                    request,
+                    reason,
+                    /*dropped*/ mayHaveBuffers,
+                    requestId,
+                    frameNumber,
+                    errorPhysicalCameraId);
+
+                final Executor executor = holder.getCallback().getExecutor();
+                failureDispatch = new Runnable() {
+                    @Override
+                    public void run() {
+                        final CameraCaptureSession.CaptureCallback callback =
+                                holder.getCallback().getSessionCallback();
+                        if (!CameraOfflineSessionImpl.this.isClosed() && (callback != null)) {
+                            callback.onCaptureFailed(CameraOfflineSessionImpl.this, request,
+                                    failure);
+                        }
+                    }
+                };
+
+                // Fire onCaptureSequenceCompleted if appropriate
+                mFrameNumberTracker.updateTracker(frameNumber,
+                        /*error*/true, request.getRequestType());
+                checkAndFireSequenceComplete();
+
+                if (executor != null) {
+                    // Dispatch the failure callback
+                    final long ident = Binder.clearCallingIdentity();
+                    try {
+                        executor.execute(failureDispatch);
+                    } finally {
+                        Binder.restoreCallingIdentity(ident);
+                    }
+                }
+            }
+
+        }
+
+    }
+
+    private void checkAndFireSequenceComplete() {
+        long completedFrameNumber = mFrameNumberTracker.getCompletedFrameNumber();
+        long completedReprocessFrameNumber = mFrameNumberTracker.getCompletedReprocessFrameNumber();
+        long completedZslStillFrameNumber = mFrameNumberTracker.getCompletedZslStillFrameNumber();
+        Iterator<RequestLastFrameNumbersHolder> iter =
+                mOfflineRequestLastFrameNumbersList.iterator();
+        while (iter.hasNext()) {
+            final RequestLastFrameNumbersHolder requestLastFrameNumbers = iter.next();
+            boolean sequenceCompleted = false;
+            final int requestId = requestLastFrameNumbers.getRequestId();
+            final CaptureCallbackHolder holder;
+            final Executor executor;
+            final CameraCaptureSession.CaptureCallback callback;
+            synchronized(mInterfaceLock) {
+                if (mRemoteSession == null) {
+                    Log.w(TAG, "Camera closed while checking sequences");
+                    return;
+                }
+
+                int index = mCaptureCallbackMap.indexOfKey(requestId);
+                holder = (index >= 0) ?
+                        mCaptureCallbackMap.valueAt(index) : null;
+                if (holder != null) {
+                    long lastRegularFrameNumber =
+                            requestLastFrameNumbers.getLastRegularFrameNumber();
+                    long lastReprocessFrameNumber =
+                            requestLastFrameNumbers.getLastReprocessFrameNumber();
+                    long lastZslStillFrameNumber =
+                            requestLastFrameNumbers.getLastZslStillFrameNumber();
+                    executor = holder.getCallback().getExecutor();
+                    callback = holder.getCallback().getSessionCallback();
+                    // check if it's okay to remove request from mCaptureCallbackMap
+                    if (lastRegularFrameNumber <= completedFrameNumber
+                            && lastReprocessFrameNumber <= completedReprocessFrameNumber
+                            && lastZslStillFrameNumber <= completedZslStillFrameNumber) {
+                        sequenceCompleted = true;
+                        mCaptureCallbackMap.removeAt(index);
+                    }
+                } else {
+                    executor = null;
+                    callback = null;
+                }
+            }
+
+            // If no callback is registered for this requestId or sequence completed, remove it
+            // from the frame number->request pair because it's not needed anymore.
+            if (holder == null || sequenceCompleted) {
+                iter.remove();
+            }
+
+            // Call onCaptureSequenceCompleted
+            if ((sequenceCompleted) && (callback != null) && (executor == null)) {
+                Runnable resultDispatch = new Runnable() {
+                    @Override
+                    public void run() {
+                        if (!isClosed()) {
+                            callback.onCaptureSequenceCompleted(CameraOfflineSessionImpl.this,
+                                    requestId, requestLastFrameNumbers.getLastFrameNumber());
+                        }
+                    }
+                };
+
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    executor.execute(resultDispatch);
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
+        }
+    }
+
+    public void notifyFailedSwitch() {
+        synchronized(mInterfaceLock) {
+            Runnable switchFailDispatch = new Runnable() {
+                @Override
+                public void run() {
+                    mOfflineCallback.onSwitchFailed(CameraOfflineSessionImpl.this);
+                }
+            };
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                mOfflineExecutor.execute(switchFailDispatch);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    /**
+     * Set remote session.
+     *
+     */
+    public void setRemoteSession(ICameraOfflineSession remoteSession) throws CameraAccessException {
+        synchronized(mInterfaceLock) {
+            if (remoteSession == null) {
+                notifyFailedSwitch();
+                return;
+            }
+
+            mRemoteSession = remoteSession;
+
+            IBinder remoteSessionBinder = remoteSession.asBinder();
+            if (remoteSessionBinder == null) {
+                throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
+                        "The camera offline session has encountered a serious error");
+            }
+
+            try {
+                remoteSessionBinder.linkToDeath(this, /*flag*/ 0);
+            } catch (RemoteException e) {
+                throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
+                        "The camera offline session has encountered a serious error");
+            }
+
+            Runnable readyDispatch = new Runnable() {
+                @Override
+                public void run() {
+                    if (!isClosed()) {
+                        mOfflineCallback.onReady(CameraOfflineSessionImpl.this);
+                    }
+                }
+            };
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                mOfflineExecutor.execute(readyDispatch);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    /** Whether the offline session has started to close (may not yet have finished) */
+    private boolean isClosed() {
+        return mClosing.get();
+    }
+
+    private void disconnect() {
+        synchronized (mInterfaceLock) {
+            if (mClosing.getAndSet(true)) {
+                return;
+            }
+
+            if (mRemoteSession != null) {
+                mRemoteSession.asBinder().unlinkToDeath(this, /*flags*/0);
+
+                try {
+                    mRemoteSession.disconnect();
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Exception while disconnecting from offline session: ", e);
+                }
+            } else {
+                throw new IllegalStateException("Offline session is not yet ready");
+            }
+
+            mRemoteSession = null;
+
+            Runnable closeDispatch = new Runnable() {
+                @Override
+                public void run() {
+                    if (!isClosed()) {
+                        mOfflineCallback.onClosed(CameraOfflineSessionImpl.this);
+                    }
+                }
+            };
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                mOfflineExecutor.execute(closeDispatch);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            disconnect();
+        }
+        finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Listener for binder death.
+     *
+     * <p> Handle binder death for ICameraOfflineSession.</p>
+     */
+    @Override
+    public void binderDied() {
+        Log.w(TAG, "CameraOfflineSession on device " + mCameraId + " died unexpectedly");
+        disconnect();
+    }
+
+    @Override
+    public CameraDevice getDevice() {
+        throw new UnsupportedOperationException("Operation not supported in offline mode");
+    }
+
+    @Override
+    public void prepare(Surface surface) throws CameraAccessException {
+        throw new UnsupportedOperationException("Operation not supported in offline mode");
+    }
+
+    @Override
+    public void prepare(int maxCount, Surface surface) throws CameraAccessException {
+        throw new UnsupportedOperationException("Operation not supported in offline mode");
+    }
+
+    @Override
+    public void tearDown(Surface surface) throws CameraAccessException {
+        throw new UnsupportedOperationException("Operation not supported in offline mode");
+    }
+
+    @Override
+    public void finalizeOutputConfigurations(
+            List<OutputConfiguration> outputConfigs) throws CameraAccessException {
+        throw new UnsupportedOperationException("Operation not supported in offline mode");
+    }
+
+    @Override
+    public int capture(CaptureRequest request, CaptureCallback callback,
+            Handler handler) throws CameraAccessException {
+        throw new UnsupportedOperationException("Operation not supported in offline mode");
+    }
+
+    @Override
+    public int captureSingleRequest(CaptureRequest request, Executor executor,
+            CaptureCallback callback) throws CameraAccessException {
+        throw new UnsupportedOperationException("Operation not supported in offline mode");
+    }
+
+    @Override
+    public int captureBurst(List<CaptureRequest> requests, CaptureCallback callback,
+            Handler handler) throws CameraAccessException {
+        throw new UnsupportedOperationException("Operation not supported in offline mode");
+    }
+
+    @Override
+    public int captureBurstRequests(List<CaptureRequest> requests, Executor executor,
+            CaptureCallback callback) throws CameraAccessException {
+        throw new UnsupportedOperationException("Operation not supported in offline mode");
+    }
+
+    @Override
+    public int setRepeatingRequest(CaptureRequest request, CaptureCallback callback,
+            Handler handler) throws CameraAccessException {
+        throw new UnsupportedOperationException("Operation not supported in offline mode");
+    }
+
+    @Override
+    public int setSingleRepeatingRequest(CaptureRequest request, Executor executor,
+            CaptureCallback callback) throws CameraAccessException {
+        throw new UnsupportedOperationException("Operation not supported in offline mode");
+    }
+
+    @Override
+    public int setRepeatingBurst(List<CaptureRequest> requests,
+            CaptureCallback callback, Handler handler) throws CameraAccessException {
+        throw new UnsupportedOperationException("Operation not supported in offline mode");
+    }
+
+    @Override
+    public int setRepeatingBurstRequests(List<CaptureRequest> requests, Executor executor,
+            CaptureCallback callback) throws CameraAccessException {
+        throw new UnsupportedOperationException("Operation not supported in offline mode");
+    }
+
+    @Override
+    public void stopRepeating() throws CameraAccessException {
+        throw new UnsupportedOperationException("Operation not supported in offline mode");
+    }
+
+    @Override
+    public void abortCaptures() throws CameraAccessException {
+        throw new UnsupportedOperationException("Operation not supported in offline mode");
+    }
+
+    @Override
+    public void updateOutputConfiguration(OutputConfiguration config)
+            throws CameraAccessException {
+        throw new UnsupportedOperationException("Operation not supported in offline mode");
+    }
+
+    @Override
+    public boolean isReprocessable() {
+        throw new UnsupportedOperationException("Operation not supported in offline mode");
+    }
+
+    @Override
+    public Surface getInputSurface() {
+        throw new UnsupportedOperationException("Operation not supported in offline mode");
+    }
+
+    @Override
+    public CameraOfflineSession switchToOffline(Collection<Surface> offlineOutputs,
+            Executor executor, CameraOfflineSessionCallback listener) throws CameraAccessException {
+        throw new UnsupportedOperationException("Operation not supported in offline mode");
+    }
+
+    @Override
+    public boolean supportsOfflineProcessing(Surface surface) {
+        throw new UnsupportedOperationException("Operation not supported in offline mode");
+    }
+
+    @Override
+    public void close() {
+        disconnect();
+    }
+}
diff --git a/core/java/android/hardware/camera2/impl/CaptureCallback.java b/core/java/android/hardware/camera2/impl/CaptureCallback.java
new file mode 100644
index 0000000..6defe63
--- /dev/null
+++ b/core/java/android/hardware/camera2/impl/CaptureCallback.java
@@ -0,0 +1,124 @@
+/*
+ * 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.hardware.camera2.impl;
+
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.view.Surface;
+
+import java.util.concurrent.Executor;
+
+/**
+ * <p>An internal callback for tracking the progress of a {@link CaptureRequest}
+ * submitted to the camera device.</p>
+ */
+public abstract class CaptureCallback {
+
+    private Executor mExecutor;
+    private CameraCaptureSession.CaptureCallback mCallback;
+
+    public CaptureCallback(Executor executor, CameraCaptureSession.CaptureCallback callback) {
+        mExecutor = executor;
+        mCallback = callback;
+    }
+
+    /**
+     * Retrieve callback executor
+     *
+     */
+    public Executor getExecutor() {
+        return mExecutor;
+    }
+
+    /**
+     * Retrieve capture callback
+     *
+     */
+    public CameraCaptureSession.CaptureCallback getSessionCallback() {
+        return mCallback;
+    }
+
+    /**
+     * This method is called when the camera device has started capturing
+     * the output image for the request, at the beginning of image exposure.
+     *
+     * @see android.media.MediaActionSound
+     */
+    public abstract void onCaptureStarted(CameraDevice camera,
+            CaptureRequest request, long timestamp, long frameNumber);
+
+    /**
+     * This method is called when some results from an image capture are
+     * available.
+     *
+     * @hide
+     */
+    public abstract void onCapturePartial(CameraDevice camera,
+            CaptureRequest request, CaptureResult result);
+
+    /**
+     * This method is called when an image capture makes partial forward progress; some
+     * (but not all) results from an image capture are available.
+     *
+     */
+    public abstract void onCaptureProgressed(CameraDevice camera,
+            CaptureRequest request, CaptureResult partialResult);
+
+    /**
+     * This method is called when an image capture has fully completed and all the
+     * result metadata is available.
+     */
+    public abstract void onCaptureCompleted(CameraDevice camera,
+            CaptureRequest request, TotalCaptureResult result);
+
+    /**
+     * This method is called instead of {@link #onCaptureCompleted} when the
+     * camera device failed to produce a {@link CaptureResult} for the
+     * request.
+     */
+    public abstract void onCaptureFailed(CameraDevice camera,
+            CaptureRequest request, CaptureFailure failure);
+
+    /**
+     * This method is called independently of the others in CaptureCallback,
+     * when a capture sequence finishes and all {@link CaptureResult}
+     * or {@link CaptureFailure} for it have been returned via this callback.
+     */
+    public abstract void onCaptureSequenceCompleted(CameraDevice camera,
+            int sequenceId, long frameNumber);
+
+    /**
+     * This method is called independently of the others in CaptureCallback,
+     * when a capture sequence aborts before any {@link CaptureResult}
+     * or {@link CaptureFailure} for it have been returned via this callback.
+     */
+    public abstract void onCaptureSequenceAborted(CameraDevice camera,
+            int sequenceId);
+
+    /**
+     * This method is called independently of the others in CaptureCallback, if an output buffer
+     * is dropped for a particular capture request.
+     *
+     * Loss of metadata is communicated via onCaptureFailed, independently of any buffer loss.
+     */
+    public abstract void onCaptureBufferLost(CameraDevice camera,
+            CaptureRequest request, Surface target, long frameNumber);
+}
diff --git a/core/java/android/hardware/camera2/impl/CaptureCallbackHolder.java b/core/java/android/hardware/camera2/impl/CaptureCallbackHolder.java
new file mode 100644
index 0000000..01c3890
--- /dev/null
+++ b/core/java/android/hardware/camera2/impl/CaptureCallbackHolder.java
@@ -0,0 +1,119 @@
+/*
+ * 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.hardware.camera2.impl;
+
+import android.hardware.camera2.CaptureRequest;
+import android.view.Surface;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public class CaptureCallbackHolder {
+
+    private final boolean mRepeating;
+    private final CaptureCallback mCallback;
+    private final List<CaptureRequest> mRequestList;
+    private final Executor mExecutor;
+    private final int mSessionId;
+    /**
+     * <p>Determine if the callback holder is for a constrained high speed request list that
+     * expects batched capture results. Capture results will be batched if the request list
+     * is interleaved with preview and video requests. Capture results won't be batched if the
+     * request list only contains preview requests, or if the request doesn't belong to a
+     * constrained high speed list.
+     */
+    private final boolean mHasBatchedOutputs;
+
+    CaptureCallbackHolder(CaptureCallback callback, List<CaptureRequest> requestList,
+            Executor executor, boolean repeating, int sessionId) {
+        if (callback == null || executor == null) {
+            throw new UnsupportedOperationException(
+                "Must have a valid handler and a valid callback");
+        }
+        mRepeating = repeating;
+        mExecutor = executor;
+        mRequestList = new ArrayList<CaptureRequest>(requestList);
+        mCallback = callback;
+        mSessionId = sessionId;
+
+        // Check whether this callback holder is for batched outputs.
+        // The logic here should match createHighSpeedRequestList.
+        boolean hasBatchedOutputs = true;
+        for (int i = 0; i < requestList.size(); i++) {
+            CaptureRequest request = requestList.get(i);
+            if (!request.isPartOfCRequestList()) {
+                hasBatchedOutputs = false;
+                break;
+            }
+            if (i == 0) {
+                Collection<Surface> targets = request.getTargets();
+                if (targets.size() != 2) {
+                    hasBatchedOutputs = false;
+                    break;
+                }
+            }
+        }
+        mHasBatchedOutputs = hasBatchedOutputs;
+    }
+
+    public boolean isRepeating() {
+        return mRepeating;
+    }
+
+    public CaptureCallback getCallback() {
+        return mCallback;
+    }
+
+    public CaptureRequest getRequest(int subsequenceId) {
+        if (subsequenceId >= mRequestList.size()) {
+            throw new IllegalArgumentException(
+                    String.format(
+                            "Requested subsequenceId %d is larger than request list size %d.",
+                            subsequenceId, mRequestList.size()));
+        } else {
+            if (subsequenceId < 0) {
+                throw new IllegalArgumentException(String.format(
+                        "Requested subsequenceId %d is negative", subsequenceId));
+            } else {
+                return mRequestList.get(subsequenceId);
+            }
+        }
+    }
+
+    public CaptureRequest getRequest() {
+        return getRequest(0);
+    }
+
+    public Executor getExecutor() {
+        return mExecutor;
+    }
+
+    public int getSessionId() {
+        return mSessionId;
+    }
+
+    public int getRequestCount() {
+        return mRequestList.size();
+    }
+
+    public boolean hasBatchedOutputs() {
+        return mHasBatchedOutputs;
+    }
+}
diff --git a/core/java/android/hardware/camera2/impl/FrameNumberTracker.java b/core/java/android/hardware/camera2/impl/FrameNumberTracker.java
new file mode 100644
index 0000000..27f8a61
--- /dev/null
+++ b/core/java/android/hardware/camera2/impl/FrameNumberTracker.java
@@ -0,0 +1,261 @@
+/*
+ * 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.hardware.camera2.impl;
+
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.TreeMap;
+
+/**
+ * This class tracks the last frame number for submitted requests.
+ */
+public class FrameNumberTracker {
+    private static final String TAG = "FrameNumberTracker";
+
+    /** the completed frame number for each type of capture results */
+    private long[] mCompletedFrameNumber = new long[CaptureRequest.REQUEST_TYPE_COUNT];
+
+    /** the skipped frame numbers that don't belong to each type of capture results */
+    private final LinkedList<Long>[] mSkippedOtherFrameNumbers =
+            new LinkedList[CaptureRequest.REQUEST_TYPE_COUNT];
+
+    /** the skipped frame numbers that belong to each type of capture results */
+    private final LinkedList<Long>[] mSkippedFrameNumbers =
+            new LinkedList[CaptureRequest.REQUEST_TYPE_COUNT];
+
+    /** frame number -> request type */
+    private final TreeMap<Long, Integer> mFutureErrorMap = new TreeMap<Long, Integer>();
+    /** Map frame numbers to list of partial results */
+    private final HashMap<Long, List<CaptureResult>> mPartialResults = new HashMap<>();
+
+    public FrameNumberTracker() {
+        for (int i = 0; i < CaptureRequest.REQUEST_TYPE_COUNT; i++) {
+            mCompletedFrameNumber[i] = CameraCaptureSession.CaptureCallback.NO_FRAMES_CAPTURED;
+            mSkippedOtherFrameNumbers[i] = new LinkedList<Long>();
+            mSkippedFrameNumbers[i] = new LinkedList<Long>();
+        }
+    }
+
+    private void update() {
+        Iterator iter = mFutureErrorMap.entrySet().iterator();
+        while (iter.hasNext()) {
+            TreeMap.Entry pair = (TreeMap.Entry)iter.next();
+            Long errorFrameNumber = (Long)pair.getKey();
+            int requestType = (int) pair.getValue();
+            Boolean removeError = false;
+            if (errorFrameNumber == mCompletedFrameNumber[requestType] + 1) {
+                mCompletedFrameNumber[requestType] = errorFrameNumber;
+                removeError = true;
+            } else {
+                if (!mSkippedFrameNumbers[requestType].isEmpty()) {
+                    if (errorFrameNumber == mSkippedFrameNumbers[requestType].element()) {
+                        mCompletedFrameNumber[requestType] = errorFrameNumber;
+                        mSkippedFrameNumbers[requestType].remove();
+                        removeError = true;
+                    }
+                } else {
+                    for (int i = 1; i < CaptureRequest.REQUEST_TYPE_COUNT; i++) {
+                        int otherType = (requestType + i) % CaptureRequest.REQUEST_TYPE_COUNT;
+                        if (!mSkippedOtherFrameNumbers[otherType].isEmpty() && errorFrameNumber
+                                == mSkippedOtherFrameNumbers[otherType].element()) {
+                            mCompletedFrameNumber[requestType] = errorFrameNumber;
+                            mSkippedOtherFrameNumbers[otherType].remove();
+                            removeError = true;
+                            break;
+                        }
+                    }
+                }
+            }
+            if (removeError) {
+                iter.remove();
+            }
+        }
+    }
+
+    /**
+     * This function is called every time when a result or an error is received.
+     * @param frameNumber the frame number corresponding to the result or error
+     * @param isError true if it is an error, false if it is not an error
+     * @param requestType the type of capture request: Reprocess, ZslStill, or Regular.
+     */
+    public void updateTracker(long frameNumber, boolean isError, int requestType) {
+        if (isError) {
+            mFutureErrorMap.put(frameNumber, requestType);
+        } else {
+            try {
+                updateCompletedFrameNumber(frameNumber, requestType);
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, e.getMessage());
+            }
+        }
+        update();
+    }
+
+    /**
+     * This function is called every time a result has been completed.
+     *
+     * <p>It keeps a track of all the partial results already created for a particular
+     * frame number.</p>
+     *
+     * @param frameNumber the frame number corresponding to the result
+     * @param result the total or partial result
+     * @param partial {@true} if the result is partial, {@code false} if total
+     * @param requestType the type of capture request: Reprocess, ZslStill, or Regular.
+     */
+    public void updateTracker(long frameNumber, CaptureResult result, boolean partial,
+            int requestType) {
+        if (!partial) {
+            // Update the total result's frame status as being successful
+            updateTracker(frameNumber, /*isError*/false, requestType);
+            // Don't keep a list of total results, we don't need to track them
+            return;
+        }
+
+        if (result == null) {
+            // Do not record blank results; this also means there will be no total result
+            // so it doesn't matter that the partials were not recorded
+            return;
+        }
+
+        // Partial results must be aggregated in-order for that frame number
+        List<CaptureResult> partials = mPartialResults.get(frameNumber);
+        if (partials == null) {
+            partials = new ArrayList<>();
+            mPartialResults.put(frameNumber, partials);
+        }
+
+        partials.add(result);
+    }
+
+    /**
+     * Attempt to pop off all of the partial results seen so far for the {@code frameNumber}.
+     *
+     * <p>Once popped-off, the partial results are forgotten (unless {@code updateTracker}
+     * is called again with new partials for that frame number).</p>
+     *
+     * @param frameNumber the frame number corresponding to the result
+     * @return a list of partial results for that frame with at least 1 element,
+     *         or {@code null} if there were no partials recorded for that frame
+     */
+    public List<CaptureResult> popPartialResults(long frameNumber) {
+        return mPartialResults.remove(frameNumber);
+    }
+
+    public long getCompletedFrameNumber() {
+        return mCompletedFrameNumber[CaptureRequest.REQUEST_TYPE_REGULAR];
+    }
+
+    public long getCompletedReprocessFrameNumber() {
+        return mCompletedFrameNumber[CaptureRequest.REQUEST_TYPE_REPROCESS];
+    }
+
+    public long getCompletedZslStillFrameNumber() {
+        return mCompletedFrameNumber[CaptureRequest.REQUEST_TYPE_ZSL_STILL];
+    }
+
+    /**
+     * Update the completed frame number for results of 3 categories
+     * (Regular/Reprocess/ZslStill).
+     *
+     * It validates that all previous frames of the same category have arrived.
+     *
+     * If there is a gap since previous frame number of the same category, assume the frames in
+     * the gap are other categories and store them in the skipped frame number queue to check
+     * against when frames of those categories arrive.
+     */
+    private void updateCompletedFrameNumber(long frameNumber,
+            int requestType) throws IllegalArgumentException {
+        if (frameNumber <= mCompletedFrameNumber[requestType]) {
+            throw new IllegalArgumentException("frame number " + frameNumber + " is a repeat");
+        }
+
+        // Assume there are only 3 different types of capture requests.
+        int otherType1 = (requestType + 1) % CaptureRequest.REQUEST_TYPE_COUNT;
+        int otherType2 = (requestType + 2) % CaptureRequest.REQUEST_TYPE_COUNT;
+        long maxOtherFrameNumberSeen =
+                Math.max(mCompletedFrameNumber[otherType1], mCompletedFrameNumber[otherType2]);
+        if (frameNumber < maxOtherFrameNumberSeen) {
+            // if frame number is smaller than completed frame numbers of other categories,
+            // it must be:
+            // - the head of mSkippedFrameNumbers for this category, or
+            // - in one of other mSkippedOtherFrameNumbers
+            if (!mSkippedFrameNumbers[requestType].isEmpty()) {
+                // frame number must be head of current type of mSkippedFrameNumbers if
+                // mSkippedFrameNumbers isn't empty.
+                if (frameNumber < mSkippedFrameNumbers[requestType].element()) {
+                    throw new IllegalArgumentException("frame number " + frameNumber
+                            + " is a repeat");
+                } else if (frameNumber > mSkippedFrameNumbers[requestType].element()) {
+                    throw new IllegalArgumentException("frame number " + frameNumber
+                            + " comes out of order. Expecting "
+                            + mSkippedFrameNumbers[requestType].element());
+                }
+                // frame number matches the head of the skipped frame number queue.
+                mSkippedFrameNumbers[requestType].remove();
+            } else {
+                // frame number must be in one of the other mSkippedOtherFrameNumbers.
+                int index1 = mSkippedOtherFrameNumbers[otherType1].indexOf(frameNumber);
+                int index2 = mSkippedOtherFrameNumbers[otherType2].indexOf(frameNumber);
+                boolean inSkippedOther1 = index1 != -1;
+                boolean inSkippedOther2 = index2 != -1;
+                if (!(inSkippedOther1 ^ inSkippedOther2)) {
+                    throw new IllegalArgumentException("frame number " + frameNumber
+                            + " is a repeat or invalid");
+                }
+
+                // We know the category of frame numbers in skippedOtherFrameNumbers leading up
+                // to the current frame number. Move them into the correct skippedFrameNumbers.
+                LinkedList<Long> srcList, dstList;
+                int index;
+                if (inSkippedOther1) {
+                    srcList = mSkippedOtherFrameNumbers[otherType1];
+                    dstList = mSkippedFrameNumbers[otherType2];
+                    index = index1;
+                } else {
+                    srcList = mSkippedOtherFrameNumbers[otherType2];
+                    dstList = mSkippedFrameNumbers[otherType1];
+                    index = index2;
+                }
+                for (int i = 0; i < index; i++) {
+                    dstList.add(srcList.removeFirst());
+                }
+
+                // Remove current frame number from skippedOtherFrameNumbers
+                srcList.remove();
+            }
+        } else {
+            // there is a gap of unseen frame numbers which should belong to the other
+            // 2 categories. Put all the skipped frame numbers in the queue.
+            for (long i =
+                    Math.max(maxOtherFrameNumberSeen, mCompletedFrameNumber[requestType]) + 1;
+                    i < frameNumber; i++) {
+                mSkippedOtherFrameNumbers[requestType].add(i);
+            }
+        }
+
+        mCompletedFrameNumber[requestType] = frameNumber;
+    }
+}
+
diff --git a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
index 397417b..fa7301b 100644
--- a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
+++ b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
@@ -110,10 +110,10 @@
         }
     }
 
-    public void endConfigure(int operatingMode, CameraMetadataNative sessionParams)
+    public int[] endConfigure(int operatingMode, CameraMetadataNative sessionParams)
            throws CameraAccessException {
         try {
-            mRemoteDevice.endConfigure(operatingMode, (sessionParams == null) ?
+            return mRemoteDevice.endConfigure(operatingMode, (sessionParams == null) ?
                     new CameraMetadataNative() : sessionParams);
         } catch (Throwable t) {
             CameraManager.throwAsPublicException(t);
@@ -251,10 +251,9 @@
     }
 
     public ICameraOfflineSession switchToOffline(ICameraDeviceCallbacks cbs,
-            Surface[] offlineOutputs)
-            throws CameraAccessException {
+            int[] offlineOutputIds) throws CameraAccessException {
         try {
-            return mRemoteDevice.switchToOffline(cbs, offlineOutputs);
+            return mRemoteDevice.switchToOffline(cbs, offlineOutputIds);
         } catch (Throwable t) {
             CameraManager.throwAsPublicException(t);
             throw new UnsupportedOperationException("Unexpected exception", t);
diff --git a/core/java/android/hardware/camera2/impl/RequestLastFrameNumbersHolder.java b/core/java/android/hardware/camera2/impl/RequestLastFrameNumbersHolder.java
new file mode 100644
index 0000000..bd1df9e
--- /dev/null
+++ b/core/java/android/hardware/camera2/impl/RequestLastFrameNumbersHolder.java
@@ -0,0 +1,181 @@
+/*
+ * 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.hardware.camera2.impl;
+
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.utils.SubmitInfo;
+
+import java.util.List;
+
+/**
+ * This class holds a capture ID and its expected last regular, zslStill, and reprocess
+ * frame number.
+ */
+public class RequestLastFrameNumbersHolder {
+    // request ID
+    private final int mRequestId;
+    // The last regular frame number for this request ID. It's
+    // CaptureCallback.NO_FRAMES_CAPTURED if the request ID has no regular request.
+    private final long mLastRegularFrameNumber;
+    // The last reprocess frame number for this request ID. It's
+    // CaptureCallback.NO_FRAMES_CAPTURED if the request ID has no reprocess request.
+    private final long mLastReprocessFrameNumber;
+    // The last ZSL still capture frame number for this request ID. It's
+    // CaptureCallback.NO_FRAMES_CAPTURED if the request ID has no zsl request.
+    private final long mLastZslStillFrameNumber;
+
+    /**
+     * Create a request-last-frame-numbers holder with a list of requests, request ID, and
+     * the last frame number returned by camera service.
+     */
+    public RequestLastFrameNumbersHolder(List<CaptureRequest> requestList, SubmitInfo requestInfo) {
+        long lastRegularFrameNumber = CameraCaptureSession.CaptureCallback.NO_FRAMES_CAPTURED;
+        long lastReprocessFrameNumber = CameraCaptureSession.CaptureCallback.NO_FRAMES_CAPTURED;
+        long lastZslStillFrameNumber = CameraCaptureSession.CaptureCallback.NO_FRAMES_CAPTURED;
+        long frameNumber = requestInfo.getLastFrameNumber();
+
+        if (requestInfo.getLastFrameNumber() < requestList.size() - 1) {
+            throw new IllegalArgumentException(
+                    "lastFrameNumber: " + requestInfo.getLastFrameNumber() +
+                    " should be at least " + (requestList.size() - 1) + " for the number of " +
+                    " requests in the list: " + requestList.size());
+        }
+
+        // find the last regular, zslStill, and reprocess frame number
+        for (int i = requestList.size() - 1; i >= 0; i--) {
+            CaptureRequest request = requestList.get(i);
+            int requestType = request.getRequestType();
+            if (requestType == CaptureRequest.REQUEST_TYPE_REPROCESS
+                    && lastReprocessFrameNumber ==
+                    CameraCaptureSession.CaptureCallback.NO_FRAMES_CAPTURED) {
+                lastReprocessFrameNumber = frameNumber;
+            } else if (requestType == CaptureRequest.REQUEST_TYPE_ZSL_STILL
+                    && lastZslStillFrameNumber ==
+                    CameraCaptureSession.CaptureCallback.NO_FRAMES_CAPTURED) {
+                lastZslStillFrameNumber = frameNumber;
+            } else if (requestType == CaptureRequest.REQUEST_TYPE_REGULAR
+                    && lastRegularFrameNumber ==
+                    CameraCaptureSession.CaptureCallback.NO_FRAMES_CAPTURED) {
+                lastRegularFrameNumber = frameNumber;
+            }
+
+            if (lastReprocessFrameNumber != CameraCaptureSession.CaptureCallback.NO_FRAMES_CAPTURED
+                    && lastZslStillFrameNumber !=
+                    CameraCaptureSession.CaptureCallback.NO_FRAMES_CAPTURED
+                    && lastRegularFrameNumber !=
+                    CameraCaptureSession.CaptureCallback.NO_FRAMES_CAPTURED) {
+                break;
+            }
+
+            frameNumber--;
+        }
+
+        mLastRegularFrameNumber = lastRegularFrameNumber;
+        mLastReprocessFrameNumber = lastReprocessFrameNumber;
+        mLastZslStillFrameNumber = lastZslStillFrameNumber;
+        mRequestId = requestInfo.getRequestId();
+    }
+
+    /**
+     * Create a request-last-frame-numbers holder with a request ID and last regular/ZslStill
+     * frame number.
+     */
+    RequestLastFrameNumbersHolder(int requestId, long lastFrameNumber,
+            int[] repeatingRequestTypes) {
+        long lastRegularFrameNumber = CameraCaptureSession.CaptureCallback.NO_FRAMES_CAPTURED;
+        long lastZslStillFrameNumber = CameraCaptureSession.CaptureCallback.NO_FRAMES_CAPTURED;
+
+        if (repeatingRequestTypes == null) {
+            throw new IllegalArgumentException(
+                    "repeatingRequest list must not be null");
+        }
+        if (lastFrameNumber < repeatingRequestTypes.length - 1) {
+            throw new IllegalArgumentException(
+                    "lastFrameNumber: " + lastFrameNumber + " should be at least "
+                    + (repeatingRequestTypes.length - 1)
+                    + " for the number of requests in the list: "
+                    + repeatingRequestTypes.length);
+        }
+
+        long frameNumber = lastFrameNumber;
+        for (int i = repeatingRequestTypes.length - 1; i >= 0; i--) {
+            if (repeatingRequestTypes[i] == CaptureRequest.REQUEST_TYPE_ZSL_STILL
+                    && lastZslStillFrameNumber ==
+                    CameraCaptureSession.CaptureCallback.NO_FRAMES_CAPTURED) {
+                lastZslStillFrameNumber = frameNumber;
+            } else if (repeatingRequestTypes[i] == CaptureRequest.REQUEST_TYPE_REGULAR
+                    && lastRegularFrameNumber ==
+                    CameraCaptureSession.CaptureCallback.NO_FRAMES_CAPTURED) {
+                lastRegularFrameNumber = frameNumber;
+            }
+
+            if (lastZslStillFrameNumber != CameraCaptureSession.CaptureCallback.NO_FRAMES_CAPTURED
+                    && lastRegularFrameNumber !=
+                    CameraCaptureSession.CaptureCallback.NO_FRAMES_CAPTURED) {
+                break;
+            }
+
+            frameNumber--;
+        }
+
+        mLastRegularFrameNumber = lastRegularFrameNumber;
+        mLastZslStillFrameNumber = lastZslStillFrameNumber;
+        mLastReprocessFrameNumber = CameraCaptureSession.CaptureCallback.NO_FRAMES_CAPTURED;
+        mRequestId = requestId;
+    }
+
+    /**
+     * Return the last regular frame number. Return CaptureCallback.NO_FRAMES_CAPTURED if
+     * it contains no regular request.
+     */
+    public long getLastRegularFrameNumber() {
+        return mLastRegularFrameNumber;
+    }
+
+    /**
+     * Return the last reprocess frame number. Return CaptureCallback.NO_FRAMES_CAPTURED if
+     * it contains no reprocess request.
+     */
+    public long getLastReprocessFrameNumber() {
+        return mLastReprocessFrameNumber;
+    }
+
+    /**
+     * Return the last ZslStill frame number. Return CaptureCallback.NO_FRAMES_CAPTURED if
+     * it contains no Zsl request.
+     */
+    public long getLastZslStillFrameNumber() {
+        return mLastZslStillFrameNumber;
+    }
+
+    /**
+     * Return the last frame number overall.
+     */
+    public long getLastFrameNumber() {
+        return Math.max(mLastZslStillFrameNumber,
+                Math.max(mLastRegularFrameNumber, mLastReprocessFrameNumber));
+    }
+
+    /**
+     * Return the request ID.
+     */
+    public int getRequestId() {
+        return mRequestId;
+    }
+}
+
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index 6ab0c29..cf8cab2 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -541,7 +541,7 @@
     }
 
     @Override
-    public void endConfigure(int operatingMode, CameraMetadataNative sessionParams) {
+    public int[] endConfigure(int operatingMode, CameraMetadataNative sessionParams) {
         if (DEBUG) {
             Log.d(TAG, "endConfigure called.");
         }
@@ -576,6 +576,8 @@
             mConfiguring = false;
         }
         mLegacyDevice.configureOutputs(surfaces);
+
+        return new int[0]; // Offline mode is not supported
     }
 
     @Override
@@ -791,8 +793,8 @@
 
     @Override
     public ICameraOfflineSession switchToOffline(ICameraDeviceCallbacks cbs,
-            Surface[] offlineOutputs) {
-        throw new UnsupportedOperationException("Legacy device does not support switchToOffline");
+            int[] offlineOutputIds) {
+        throw new UnsupportedOperationException("Legacy device does not support offline mode");
     }
 
     @Override
diff --git a/core/java/android/hardware/camera2/params/SessionConfiguration.java b/core/java/android/hardware/camera2/params/SessionConfiguration.java
index 555ff9a..47a897c 100644
--- a/core/java/android/hardware/camera2/params/SessionConfiguration.java
+++ b/core/java/android/hardware/camera2/params/SessionConfiguration.java
@@ -17,10 +17,11 @@
 
 package android.hardware.camera2.params;
 
+import static com.android.internal.util.Preconditions.*;
+
 import android.annotation.CallbackExecutor;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.hardware.camera2.CameraCaptureSession;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraDevice;
@@ -32,14 +33,12 @@
 import android.os.Parcelable;
 import android.util.Log;
 
-import java.util.Collections;
-import java.util.List;
-import java.util.ArrayList;
-import java.util.concurrent.Executor;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-
-import static com.android.internal.util.Preconditions.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
  * A helper class that aggregates all supported arguments for capture session initialization.
@@ -61,6 +60,12 @@
      * A high speed session type that can only contain instances of {@link OutputConfiguration}.
      * The outputs can run using high speed FPS ranges. Calls to {@link #setInputConfiguration}
      * are not supported.
+     * <p>
+     * When using this type, the CameraCaptureSession returned by
+     * {@link android.hardware.camera2.CameraCaptureSession.StateCallback} can be cast to a
+     * {@link android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession} to access the extra
+     * methods for constrained high speed recording.
+     * </p>
      *
      * @see CameraDevice#createConstrainedHighSpeedCaptureSession
      */
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 799dff9..d67de09 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -26,6 +26,7 @@
 import android.app.KeyguardManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Point;
 import android.media.projection.MediaProjection;
 import android.os.Handler;
@@ -400,10 +401,10 @@
         if (display == null) {
             // TODO: We cannot currently provide any override configurations for metrics on displays
             // other than the display the context is associated with.
-            final Context context = mContext.getDisplayId() == displayId
-                    ? mContext : mContext.getApplicationContext();
+            final Resources resources = mContext.getDisplayId() == displayId
+                    ? mContext.getResources() : null;
 
-            display = mGlobal.getCompatibleDisplay(displayId, context.getResources());
+            display = mGlobal.getCompatibleDisplay(displayId, resources);
             if (display != null) {
                 mDisplays.put(displayId, display);
             }
@@ -748,6 +749,17 @@
         return mGlobal.getDefaultBrightnessConfiguration();
     }
 
+
+    /**
+     * Gets the last requested minimal post processing setting for the display with displayId.
+     *
+     * @hide
+     */
+    @TestApi
+    public boolean isMinimalPostProcessingRequested(int displayId) {
+        return mGlobal.isMinimalPostProcessingRequested(displayId);
+    }
+
     /**
      * Temporarily sets the brightness of the display.
      * <p>
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 2a58495..fd539e8 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -594,6 +594,19 @@
     }
 
     /**
+     * Gets the last requested minimal post processing setting for the display with displayId.
+     *
+     * @hide
+     */
+    public boolean isMinimalPostProcessingRequested(int displayId) {
+        try {
+            return mDm.isMinimalPostProcessingRequested(displayId);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Temporarily sets the brightness of the display.
      * <p>
      * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS} permission.
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 0143c91..ccf221b 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -111,6 +111,9 @@
     // Gets the default brightness configuration if configured.
     BrightnessConfiguration getDefaultBrightnessConfiguration();
 
+    // Gets the last requested minimal post processing settings for display with displayId.
+    boolean isMinimalPostProcessingRequested(int displayId);
+
     // Temporarily sets the display brightness.
     void setTemporaryBrightness(int brightness);
 
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 7bc4529..ff9d145 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -44,6 +44,7 @@
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.security.identity.IdentityCredential;
 import android.util.Slog;
 
 import java.security.Signature;
@@ -125,6 +126,10 @@
             super(mac);
         }
 
+        public CryptoObject(@NonNull IdentityCredential credential) {
+            super(credential);
+        }
+
         /**
          * Get {@link Signature} object.
          * @return {@link Signature} object or null if this doesn't contain one.
@@ -148,6 +153,14 @@
         public Mac getMac() {
             return super.getMac();
         }
+
+        /**
+         * Get {@link IdentityCredential} object.
+         * @return {@link IdentityCredential} object or null if this doesn't contain one.
+         */
+        public @Nullable IdentityCredential getIdentityCredential() {
+            return super.getIdentityCredential();
+        }
     }
 
     /**
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 638d81b..6e1987c 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -90,4 +90,11 @@
 
     /** Create an input monitor for gestures. */
     InputMonitor monitorGestureInput(String name, int displayId);
+
+    // Add a runtime association between the input port and the display port. This overrides any
+    // static associations.
+    void addPortAssociation(in String inputPort, int displayPort);
+    // Remove the runtime association between the input port and the display port. Any existing
+    // static association for the cleared input port will be restored.
+    void removePortAssociation(in String inputPort);
 }
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 8d32db0..83f01a5 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -17,6 +17,7 @@
 package android.hardware.input;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemService;
@@ -963,6 +964,41 @@
         }
     }
 
+    /**
+     * Add a runtime association between the input port and the display port. This overrides any
+     * static associations.
+     * @param inputPort The port of the input device.
+     * @param displayPort The physical port of the associated display.
+     * <p>
+     * Requires {@link android.Manifest.permissions.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT}.
+     * </p>
+     * @hide
+     */
+    public void addPortAssociation(@NonNull String inputPort, int displayPort) {
+        try {
+            mIm.addPortAssociation(inputPort, displayPort);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Remove the runtime association between the input port and the display port. Any existing
+     * static association for the cleared input port will be restored.
+     * @param inputPort The port of the input device to be cleared.
+     * <p>
+     * Requires {@link android.Manifest.permissions.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT}.
+     * </p>
+     * @hide
+     */
+    public void removePortAssociation(@NonNull String inputPort) {
+        try {
+            mIm.removePortAssociation(inputPort);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
     private void populateInputDevicesLocked() {
         if (mInputDevicesChangedListener == null) {
             final InputDevicesChangedListener listener = new InputDevicesChangedListener();
diff --git a/core/java/android/hardware/lights/ILightsManager.aidl b/core/java/android/hardware/lights/ILightsManager.aidl
new file mode 100644
index 0000000..6ea24b7
--- /dev/null
+++ b/core/java/android/hardware/lights/ILightsManager.aidl
@@ -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.
+ */
+
+package android.hardware.lights;
+
+import android.hardware.lights.Light;
+import android.hardware.lights.LightState;
+
+/**
+ * API to lights manager service.
+ *
+ * {@hide}
+ */
+interface ILightsManager {
+  List<Light> getLights();
+  LightState getLightState(int lightId);
+  void openSession(in IBinder sessionToken);
+  void closeSession(in IBinder sessionToken);
+  void setLightStates(in IBinder sessionToken, in int[] lightIds, in LightState[] states);
+}
diff --git a/core/java/android/hardware/lights/Light.aidl b/core/java/android/hardware/lights/Light.aidl
new file mode 100644
index 0000000..946e06d4
--- /dev/null
+++ b/core/java/android/hardware/lights/Light.aidl
@@ -0,0 +1,20 @@
+/**
+ * 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.lights;
+
+/** @hide */
+parcelable Light;
diff --git a/core/java/android/hardware/lights/Light.java b/core/java/android/hardware/lights/Light.java
new file mode 100644
index 0000000..c5cb803
--- /dev/null
+++ b/core/java/android/hardware/lights/Light.java
@@ -0,0 +1,105 @@
+/**
+ * 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.lights;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents a logical light on the device.
+ *
+ * @hide
+ */
+@SystemApi
+@TestApi
+public final class Light implements Parcelable {
+    private final int mId;
+    private final int mOrdinal;
+    private final int mType;
+
+    /**
+     * Creates a new light with the given data.
+     *
+     * @hide */
+    public Light(int id, int ordinal, int type) {
+        mId = id;
+        mOrdinal = ordinal;
+        mType = type;
+    }
+
+    private Light(@NonNull Parcel in) {
+        mId = in.readInt();
+        mOrdinal = in.readInt();
+        mType = in.readInt();
+    }
+
+    /** Implement the Parcelable interface */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mId);
+        dest.writeInt(mOrdinal);
+        dest.writeInt(mType);
+    }
+
+    /** Implement the Parcelable interface */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Implement the Parcelable interface */
+    public static final @android.annotation.NonNull Parcelable.Creator<Light> CREATOR =
+            new Parcelable.Creator<Light>() {
+                public Light createFromParcel(Parcel in) {
+                    return new Light(in);
+                }
+
+                public Light[] newArray(int size) {
+                    return new Light[size];
+                }
+            };
+
+    /**
+     * Returns the id of the light.
+     */
+    public int getId() {
+        return mId;
+    }
+
+    /**
+     * Returns the ordinal of the light.
+     *
+     * <p>This represents the physical order of the lights on the device. The exact values are
+     * device-dependent, but for example, if there are lights in a row, sorting the Light objects
+     * by ordinal should match the order in which they appear on the device. If the device has
+     * 4 lights, the ordinals could be [1, 2, 3, 4] or [0, 10, 20, 30] or any other values that
+     * have the same sort order.
+     */
+    public int getOrdinal() {
+        return mOrdinal;
+    }
+
+    /**
+     * Returns the logical type of the light.
+     */
+    public @LightsManager.LightType int getType() {
+        return mType;
+    }
+}
diff --git a/core/java/android/hardware/lights/LightState.aidl b/core/java/android/hardware/lights/LightState.aidl
new file mode 100644
index 0000000..d598336
--- /dev/null
+++ b/core/java/android/hardware/lights/LightState.aidl
@@ -0,0 +1,20 @@
+/**
+ * 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.lights;
+
+/** @hide */
+parcelable LightState;
diff --git a/core/java/android/hardware/lights/LightState.java b/core/java/android/hardware/lights/LightState.java
new file mode 100644
index 0000000..e55aa70
--- /dev/null
+++ b/core/java/android/hardware/lights/LightState.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 android.hardware.lights;
+
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents the state of a device light.
+ *
+ * <p>Controlling the color and brightness of a light is done on a best-effort basis. Each of the R,
+ * G and B channels represent the intensities of the respective part of an RGB LED, if that is
+ * supported. For devices that only support on or off lights, everything that's not off will turn
+ * the light on. If the light is monochrome and only the brightness can be controlled, the RGB color
+ * will be converted to only a brightness value and that will be used for the light's single
+ * channel.
+ *
+ * @hide
+ */
+@SystemApi
+@TestApi
+public final class LightState implements Parcelable {
+    private final int mColor;
+
+    /**
+     * Creates a new LightState with the desired color and intensity.
+     *
+     * @param color the desired color and intensity in ARGB format.
+     */
+    public LightState(@ColorInt int color) {
+        mColor = color;
+    }
+
+    private LightState(@NonNull Parcel in) {
+        mColor = in.readInt();
+    }
+
+    /**
+     * Return the color and intensity associated with this LightState.
+     * @return the color and intensity in ARGB format. The A channel is ignored.
+     */
+    public @ColorInt int getColor() {
+        return mColor;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mColor);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Parcelable.Creator<LightState> CREATOR =
+            new Parcelable.Creator<LightState>() {
+                public LightState createFromParcel(Parcel in) {
+                    return new LightState(in);
+                }
+
+                public LightState[] newArray(int size) {
+                    return new LightState[size];
+                }
+            };
+}
diff --git a/core/java/android/hardware/lights/LightsManager.java b/core/java/android/hardware/lights/LightsManager.java
new file mode 100644
index 0000000..1bc051b
--- /dev/null
+++ b/core/java/android/hardware/lights/LightsManager.java
@@ -0,0 +1,204 @@
+/*
+ * 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.lights;
+
+import android.Manifest;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.content.Context;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceManager.ServiceNotFoundException;
+import android.util.CloseGuard;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.Reference;
+import java.util.List;
+
+/**
+ * The LightsManager class allows control over device lights.
+ *
+ * @hide
+ */
+@SystemApi
+@TestApi
+@SystemService(Context.LIGHTS_SERVICE)
+public final class LightsManager {
+    private static final String TAG = "LightsManager";
+
+    // These enum values copy the values from {@link com.android.server.lights.LightsManager}
+    // and the light HAL. Since 0-7 are lights reserved for system use, only the microphone light
+    // is available through this API.
+    /** Type for lights that indicate microphone usage */
+    public static final int LIGHT_TYPE_MICROPHONE = 8;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"LIGHT_TYPE_"},
+        value = {
+            LIGHT_TYPE_MICROPHONE,
+        })
+    public @interface LightType {}
+
+    @NonNull private final Context mContext;
+    @NonNull private final ILightsManager mService;
+
+    /**
+     * Creates a LightsManager.
+     *
+     * @hide
+     */
+    public LightsManager(@NonNull Context context) throws ServiceNotFoundException {
+        this(context, ILightsManager.Stub.asInterface(
+            ServiceManager.getServiceOrThrow(Context.LIGHTS_SERVICE)));
+    }
+
+    /**
+     * Creates a LightsManager with a provided service implementation.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public LightsManager(@NonNull Context context, @NonNull ILightsManager service) {
+        mContext = Preconditions.checkNotNull(context);
+        mService = Preconditions.checkNotNull(service);
+    }
+
+    /**
+     * Returns the lights available on the device.
+     *
+     * @return A list of available lights
+     */
+    @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS)
+    public @NonNull List<Light> getLights() {
+        try {
+            return mService.getLights();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the state of a specified light.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS)
+    @TestApi
+    public @NonNull LightState getLightState(@NonNull Light light) {
+        Preconditions.checkNotNull(light);
+        try {
+            return mService.getLightState(light.getId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Creates a new LightsSession that can be used to control the device lights.
+     */
+    @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS)
+    public @NonNull LightsSession openSession() {
+        try {
+            final LightsSession session = new LightsSession();
+            mService.openSession(session.mToken);
+            return session;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Encapsulates a session that can be used to control device lights and represents the lifetime
+     * of the requests.
+     */
+    public final class LightsSession implements AutoCloseable {
+
+        private final IBinder mToken = new Binder();
+
+        private final CloseGuard mCloseGuard = new CloseGuard();
+        private boolean mClosed = false;
+
+        /**
+         * Instantiated by {@link LightsManager#openSession()}.
+         */
+        @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS)
+        private LightsSession() {
+            mCloseGuard.open("close");
+        }
+
+        /**
+         * Sends a request to modify the states of multiple lights.
+         *
+         * <p>This method only controls lights that aren't overridden by higher-priority sessions.
+         * Additionally, lights not controlled by this session can be controlled by lower-priority
+         * sessions.
+         *
+         * @param request the settings for lights that should change
+         */
+        @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS)
+        public void setLights(@NonNull LightsRequest request) {
+            Preconditions.checkNotNull(request);
+            if (!mClosed) {
+                try {
+                    mService.setLightStates(mToken, request.mLightIds, request.mLightStates);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        }
+
+        /**
+         * Closes the session, reverting all changes made through it.
+         */
+        @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS)
+        @Override
+        public void close() {
+            if (!mClosed) {
+                try {
+                    mService.closeSession(mToken);
+                    mClosed = true;
+                    mCloseGuard.close();
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+            Reference.reachabilityFence(this);
+        }
+
+        /** @hide */
+        @Override
+        protected void finalize() throws Throwable {
+            try {
+                mCloseGuard.warnIfOpen();
+                close();
+            } finally {
+                super.finalize();
+            }
+        }
+    }
+}
diff --git a/core/java/android/hardware/lights/LightsRequest.java b/core/java/android/hardware/lights/LightsRequest.java
new file mode 100644
index 0000000..a36da4c
--- /dev/null
+++ b/core/java/android/hardware/lights/LightsRequest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.lights;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.util.SparseArray;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Encapsulates a request to modify the state of multiple lights.
+ *
+ * @hide
+ */
+@SystemApi
+@TestApi
+public final class LightsRequest {
+
+    /** Visible to {@link LightsManager.Session}. */
+    final int[] mLightIds;
+
+    /** Visible to {@link LightsManager.Session}. */
+    final LightState[] mLightStates;
+
+    /**
+     * Can only be constructed via {@link LightsRequest.Builder#build()}.
+     */
+    private LightsRequest(SparseArray<LightState> changes) {
+        final int n = changes.size();
+        mLightIds = new int[n];
+        mLightStates = new LightState[n];
+        for (int i = 0; i < n; i++) {
+            mLightIds[i] = changes.keyAt(i);
+            mLightStates[i] = changes.valueAt(i);
+        }
+    }
+
+    /**
+     * Builder for creating device light change requests.
+     */
+    public static final class Builder {
+
+        private final SparseArray<LightState> mChanges = new SparseArray<>();
+
+        /**
+         * Overrides the color and intensity of a given light.
+         *
+         * @param light the light to modify
+         * @param state the desired color and intensity of the light
+         */
+        public @NonNull Builder setLight(@NonNull Light light, @NonNull LightState state) {
+            Preconditions.checkNotNull(light);
+            Preconditions.checkNotNull(state);
+            mChanges.put(light.getId(), state);
+            return this;
+        }
+
+        /**
+         * Removes the override for the color and intensity of a given light.
+         *
+         * @param light the light to modify
+         */
+        public @NonNull Builder clearLight(@NonNull Light light) {
+            Preconditions.checkNotNull(light);
+            mChanges.put(light.getId(), null);
+            return this;
+        }
+
+        /**
+         * Create a LightsRequest object used to override lights on the device.
+         *
+         * <p>The generated {@link LightsRequest} should be used in
+         * {@link LightsManager.Session#setLights(LightsLightsRequest).
+         */
+        public @NonNull LightsRequest build() {
+            return new LightsRequest(mChanges);
+        }
+    }
+}
diff --git a/core/java/android/hardware/location/ContextHubInfo.java b/core/java/android/hardware/location/ContextHubInfo.java
index a11f2e9..6d56d2d 100644
--- a/core/java/android/hardware/location/ContextHubInfo.java
+++ b/core/java/android/hardware/location/ContextHubInfo.java
@@ -21,6 +21,7 @@
 import android.hardware.contexthub.V1_0.ContextHub;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.proto.ProtoOutputStream;
 
 import java.util.Arrays;
 
@@ -270,6 +271,30 @@
         return retVal;
     }
 
+    /**
+     * Dump the internal state as a ContextHubInfoProto to the given ProtoOutputStream.
+     *
+     * If the output belongs to a sub message, the caller is responsible for wrapping this function
+     * between {@link ProtoOutputStream#start(long)} and {@link ProtoOutputStream#end(long)}.
+     *
+     * @hide
+     */
+    public void dump(ProtoOutputStream proto) {
+        proto.write(ContextHubInfoProto.ID, mId);
+        proto.write(ContextHubInfoProto.NAME, mName);
+        proto.write(ContextHubInfoProto.VENDOR, mVendor);
+        proto.write(ContextHubInfoProto.TOOLCHAIN, mToolchain);
+        proto.write(ContextHubInfoProto.PLATFORM_VERSION, mPlatformVersion);
+        proto.write(ContextHubInfoProto.STATIC_SW_VERSION, getStaticSwVersion());
+        proto.write(ContextHubInfoProto.TOOLCHAIN_VERSION, mToolchainVersion);
+        proto.write(ContextHubInfoProto.CHRE_PLATFORM_ID, mChrePlatformId);
+        proto.write(ContextHubInfoProto.PEAK_MIPS, mPeakMips);
+        proto.write(ContextHubInfoProto.STOPPED_POWER_DRAW_MW, mStoppedPowerDrawMw);
+        proto.write(ContextHubInfoProto.SLEEP_POWER_DRAW_MW, mSleepPowerDrawMw);
+        proto.write(ContextHubInfoProto.PEAK_POWER_DRAW_MW, mPeakPowerDrawMw);
+        proto.write(ContextHubInfoProto.MAX_PACKET_LENGTH_BYTES, mMaxPacketLengthBytes);
+    }
+
     @Override
     public boolean equals(@Nullable Object object) {
         if (object == this) {
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
index 8231c58..a30fd6b 100644
--- a/core/java/android/hardware/soundtrigger/ConversionUtil.java
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -16,9 +16,11 @@
 
 package android.hardware.soundtrigger;
 
+import android.annotation.Nullable;
 import android.hardware.soundtrigger.ModelParams;
 import android.media.AudioFormat;
 import android.media.audio.common.AudioConfig;
+import android.media.soundtrigger_middleware.AudioCapabilities;
 import android.media.soundtrigger_middleware.ConfidenceLevel;
 import android.media.soundtrigger_middleware.ModelParameterRange;
 import android.media.soundtrigger_middleware.Phrase;
@@ -32,8 +34,6 @@
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
 import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
 
-import android.annotation.Nullable;
-
 import java.util.Arrays;
 import java.util.UUID;
 
@@ -48,6 +48,7 @@
                 properties.description,
                 properties.uuid,
                 properties.version,
+                properties.supportedModelArch,
                 properties.maxSoundModels,
                 properties.maxKeyPhrases,
                 properties.maxUsers,
@@ -56,7 +57,8 @@
                 properties.maxBufferMs,
                 properties.concurrentCapture,
                 properties.powerConsumptionMw,
-                properties.triggerInEvent
+                properties.triggerInEvent,
+                aidl2apiAudioCapabilities(properties.audioCapabilities)
         );
     }
 
@@ -128,7 +130,7 @@
         aidlPhrase.id = apiPhrase.id;
         aidlPhrase.recognitionModes = api2aidlRecognitionModes(apiPhrase.recognitionModes);
         aidlPhrase.users = Arrays.copyOf(apiPhrase.users, apiPhrase.users.length);
-        aidlPhrase.locale = apiPhrase.locale;
+        aidlPhrase.locale = apiPhrase.locale.toLanguageTag();
         aidlPhrase.text = apiPhrase.text;
         return aidlPhrase;
     }
@@ -145,6 +147,7 @@
                     apiConfig.keyphrases[i]);
         }
         aidlConfig.data = Arrays.copyOf(apiConfig.data, apiConfig.data.length);
+        aidlConfig.audioCapabilities = api2aidlAudioCapabilities(apiConfig.audioCapabilities);
         return aidlConfig;
     }
 
@@ -326,4 +329,26 @@
         }
         return new SoundTrigger.ModelParamRange(aidlRange.minInclusive, aidlRange.maxInclusive);
     }
+
+    public static int aidl2apiAudioCapabilities(int aidlCapabilities) {
+        int result = 0;
+        if ((aidlCapabilities & AudioCapabilities.ECHO_CANCELLATION) != 0) {
+            result |= SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION;
+        }
+        if ((aidlCapabilities & AudioCapabilities.NOISE_SUPPRESSION) != 0) {
+            result |= SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION;
+        }
+        return result;
+    }
+
+    public static int api2aidlAudioCapabilities(int apiCapabilities) {
+        int result = 0;
+        if ((apiCapabilities & SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION) != 0) {
+            result |= AudioCapabilities.ECHO_CANCELLATION;
+        }
+        if ((apiCapabilities & SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION) != 0) {
+            result |= AudioCapabilities.NOISE_SUPPRESSION;
+        }
+        return result;
+    }
 }
diff --git a/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java b/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java
index eb5d0cb..ef76c62 100644
--- a/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java
+++ b/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java
@@ -17,6 +17,7 @@
 package android.hardware.soundtrigger;
 
 import android.Manifest;
+import android.annotation.IntDef;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -24,7 +25,6 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
-import android.service.voice.AlwaysOnHotwordDetector;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.AttributeSet;
@@ -35,6 +35,8 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedList;
@@ -66,9 +68,10 @@
             "com.android.intent.action.MANAGE_VOICE_KEYPHRASES";
     /**
      * Intent extra: The intent extra for the specific manage action that needs to be performed.
-     * Possible values are {@link AlwaysOnHotwordDetector#MANAGE_ACTION_ENROLL},
-     * {@link AlwaysOnHotwordDetector#MANAGE_ACTION_RE_ENROLL}
-     * or {@link AlwaysOnHotwordDetector#MANAGE_ACTION_UN_ENROLL}.
+     *
+     * @see #MANAGE_ACTION_ENROLL
+     * @see #MANAGE_ACTION_RE_ENROLL
+     * @see #MANAGE_ACTION_UN_ENROLL
      */
     public static final String EXTRA_VOICE_KEYPHRASE_ACTION =
             "com.android.intent.extra.VOICE_KEYPHRASE_ACTION";
@@ -86,6 +89,31 @@
             "com.android.intent.extra.VOICE_KEYPHRASE_LOCALE";
 
     /**
+     * Keyphrase management actions used with the {@link #EXTRA_VOICE_KEYPHRASE_ACTION} intent extra
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "MANAGE_ACTION_" }, value = {
+            MANAGE_ACTION_ENROLL,
+            MANAGE_ACTION_RE_ENROLL,
+            MANAGE_ACTION_UN_ENROLL
+    })
+    public @interface ManageActions {}
+
+    /**
+     * Indicates desired action to enroll keyphrase model
+     */
+    public static final int MANAGE_ACTION_ENROLL = 0;
+    /**
+     * Indicates desired action to re-enroll keyphrase model
+     */
+    public static final int MANAGE_ACTION_RE_ENROLL = 1;
+    /**
+     * Indicates desired action to un-enroll keyphrase model
+     */
+    public static final int MANAGE_ACTION_UN_ENROLL = 2;
+
+    /**
      * List of available keyphrases.
      */
     final private KeyphraseMetadata[] mKeyphrases;
@@ -294,15 +322,13 @@
      * for the locale.
      *
      * @param action The enrollment related action that this intent is supposed to perform.
-     *        This can be one of {@link AlwaysOnHotwordDetector#MANAGE_ACTION_ENROLL},
-     *        {@link AlwaysOnHotwordDetector#MANAGE_ACTION_RE_ENROLL}
-     *        or {@link AlwaysOnHotwordDetector#MANAGE_ACTION_UN_ENROLL}
      * @param keyphrase The keyphrase that the user needs to be enrolled to.
      * @param locale The locale for which the enrollment needs to be performed.
      * @return An {@link Intent} to manage the keyphrase. This can be null if managing the
      *         given keyphrase/locale combination isn't possible.
      */
-    public Intent getManageKeyphraseIntent(int action, String keyphrase, Locale locale) {
+    public Intent getManageKeyphraseIntent(@ManageActions int action, String keyphrase,
+            Locale locale) {
         if (mKeyphrasePackageMap == null || mKeyphrasePackageMap.isEmpty()) {
             Slog.w(TAG, "No enrollment application exists");
             return null;
diff --git a/core/java/android/hardware/soundtrigger/KeyphraseMetadata.aidl b/core/java/android/hardware/soundtrigger/KeyphraseMetadata.aidl
new file mode 100644
index 0000000..7a5e932
--- /dev/null
+++ b/core/java/android/hardware/soundtrigger/KeyphraseMetadata.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (C) 2014 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.soundtrigger;
+
+parcelable KeyphraseMetadata;
diff --git a/core/java/android/hardware/soundtrigger/KeyphraseMetadata.java b/core/java/android/hardware/soundtrigger/KeyphraseMetadata.java
index ed8c296..15462de 100644
--- a/core/java/android/hardware/soundtrigger/KeyphraseMetadata.java
+++ b/core/java/android/hardware/soundtrigger/KeyphraseMetadata.java
@@ -16,8 +16,13 @@
 
 package android.hardware.soundtrigger;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcelable;
 import android.util.ArraySet;
 
+import com.android.internal.util.DataClass;
+
 import java.util.Locale;
 
 /**
@@ -25,37 +30,168 @@
  *
  * @hide
  */
-public class KeyphraseMetadata {
+@DataClass(
+        genEqualsHashCode = true,
+        genToString = true,
+        genConstructor = false,
+        genHiddenConstDefs = true)
+public final class KeyphraseMetadata implements Parcelable {
     public final int id;
+    @NonNull
     public final String keyphrase;
+    @NonNull
     public final ArraySet<Locale> supportedLocales;
     public final int recognitionModeFlags;
 
-    public KeyphraseMetadata(int id, String keyphrase, ArraySet<Locale> supportedLocales,
-            int recognitionModeFlags) {
+    public KeyphraseMetadata(int id, @NonNull String keyphrase,
+            @NonNull ArraySet<Locale> supportedLocales, int recognitionModeFlags) {
         this.id = id;
         this.keyphrase = keyphrase;
         this.supportedLocales = supportedLocales;
         this.recognitionModeFlags = recognitionModeFlags;
     }
 
-    @Override
-    public String toString() {
-        return "id=" + id + ", keyphrase=" + keyphrase + ", supported-locales=" + supportedLocales
-                + ", recognition-modes=" + recognitionModeFlags;
-    }
-
     /**
      * @return Indicates if we support the given phrase.
      */
-    public boolean supportsPhrase(String phrase) {
+    public boolean supportsPhrase(@Nullable String phrase) {
         return keyphrase.isEmpty() || keyphrase.equalsIgnoreCase(phrase);
     }
 
     /**
      * @return Indicates if we support the given locale.
      */
-    public boolean supportsLocale(Locale locale) {
+    public boolean supportsLocale(@Nullable Locale locale) {
         return supportedLocales.isEmpty() || supportedLocales.contains(locale);
     }
+
+
+
+
+    // Code below generated by codegen v1.0.14.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/hardware/soundtrigger/KeyphraseMetadata.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "KeyphraseMetadata { " +
+                "id = " + id + ", " +
+                "keyphrase = " + keyphrase + ", " +
+                "supportedLocales = " + supportedLocales + ", " +
+                "recognitionModeFlags = " + recognitionModeFlags +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(KeyphraseMetadata other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        KeyphraseMetadata that = (KeyphraseMetadata) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && id == that.id
+                && java.util.Objects.equals(keyphrase, that.keyphrase)
+                && java.util.Objects.equals(supportedLocales, that.supportedLocales)
+                && recognitionModeFlags == that.recognitionModeFlags;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + id;
+        _hash = 31 * _hash + java.util.Objects.hashCode(keyphrase);
+        _hash = 31 * _hash + java.util.Objects.hashCode(supportedLocales);
+        _hash = 31 * _hash + recognitionModeFlags;
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeInt(id);
+        dest.writeString(keyphrase);
+        dest.writeArraySet(supportedLocales);
+        dest.writeInt(recognitionModeFlags);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ KeyphraseMetadata(@NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        int _id = in.readInt();
+        String _keyphrase = in.readString();
+        ArraySet<Locale> _supportedLocales = (ArraySet) in.readArraySet(null);
+        int _recognitionModeFlags = in.readInt();
+
+        this.id = _id;
+        this.keyphrase = _keyphrase;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, keyphrase);
+        this.supportedLocales = _supportedLocales;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, supportedLocales);
+        this.recognitionModeFlags = _recognitionModeFlags;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<KeyphraseMetadata> CREATOR
+            = new Parcelable.Creator<KeyphraseMetadata>() {
+        @Override
+        public KeyphraseMetadata[] newArray(int size) {
+            return new KeyphraseMetadata[size];
+        }
+
+        @Override
+        public KeyphraseMetadata createFromParcel(@NonNull android.os.Parcel in) {
+            return new KeyphraseMetadata(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1579290593964L,
+            codegenVersion = "1.0.14",
+            sourceFile = "frameworks/base/core/java/android/hardware/soundtrigger/KeyphraseMetadata.java",
+            inputSignatures = "public final  int id\npublic final @android.annotation.NonNull java.lang.String keyphrase\npublic final @android.annotation.NonNull android.util.ArraySet<java.util.Locale> supportedLocales\npublic final  int recognitionModeFlags\npublic  boolean supportsPhrase(java.lang.String)\npublic  boolean supportsLocale(java.util.Locale)\nclass KeyphraseMetadata extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genConstructor=false, genHiddenConstDefs=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
 }
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 55505ba..d505ae5 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -24,6 +24,7 @@
 
 import static java.util.Objects.requireNonNull;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -33,6 +34,7 @@
 import android.media.AudioFormat;
 import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.media.soundtrigger_middleware.Status;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -40,10 +42,14 @@
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
 import android.util.Log;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Locale;
 import java.util.UUID;
 
 /**
@@ -83,6 +89,30 @@
      *
      ****************************************************************************/
     public static final class ModuleProperties implements Parcelable {
+
+        /**
+         * Bit field values of AudioCapabilities supported by the implemented HAL
+         * driver.
+         * @hide
+         */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(flag = true, prefix = { "AUDIO_CAPABILITY_" }, value = {
+                CAPABILITY_ECHO_CANCELLATION,
+                CAPABILITY_NOISE_SUPPRESSION
+        })
+        public @interface AudioCapabilities {}
+
+        /**
+         * If set the underlying module supports AEC.
+         * Describes bit field {@link ModuleProperties#audioCapabilities}
+         */
+        public static final int CAPABILITY_ECHO_CANCELLATION = 0x1;
+        /**
+         * If set, the underlying module supports noise suppression.
+         * Describes bit field {@link ModuleProperties#audioCapabilities}
+         */
+        public static final int CAPABILITY_NOISE_SUPPRESSION = 0x2;
+
         /** Unique module ID provided by the native service */
         public final int id;
 
@@ -101,6 +131,14 @@
         /** Voice detection engine version */
         public final int version;
 
+        /**
+         * String naming the architecture used for running the supported models.
+         * (eg. a platform running models on a DSP could implement this string to convey the DSP
+         * architecture used)
+         */
+        @NonNull
+        public final String supportedModelArch;
+
         /** Maximum number of active sound models */
         public final int maxSoundModels;
 
@@ -111,6 +149,7 @@
         public final int maxUsers;
 
         /** Supported recognition modes (bit field, RECOGNITION_MODE_VOICE_TRIGGER ...) */
+        @RecognitionModes
         public final int recognitionModes;
 
         /** Supports seamless transition to capture mode after recognition */
@@ -129,16 +168,25 @@
          * recognition callback event */
         public final boolean returnsTriggerInEvent;
 
+        /**
+         * Bit field encoding of the AudioCapabilities
+         * supported by the firmware.
+         */
+        @AudioCapabilities
+        public final int audioCapabilities;
+
         ModuleProperties(int id, @NonNull String implementor, @NonNull String description,
-                @NonNull String uuid, int version, int maxSoundModels, int maxKeyphrases,
-                int maxUsers, int recognitionModes, boolean supportsCaptureTransition,
-                int maxBufferMs, boolean supportsConcurrentCapture,
-                int powerConsumptionMw, boolean returnsTriggerInEvent) {
+                @NonNull String uuid, int version, @NonNull String supportedModelArch,
+                int maxSoundModels, int maxKeyphrases, int maxUsers,
+                @RecognitionModes int recognitionModes, boolean supportsCaptureTransition,
+                int maxBufferMs, boolean supportsConcurrentCapture, int powerConsumptionMw,
+                boolean returnsTriggerInEvent, int audioCapabilities) {
             this.id = id;
             this.implementor = requireNonNull(implementor);
             this.description = requireNonNull(description);
             this.uuid = UUID.fromString(requireNonNull(uuid));
             this.version = version;
+            this.supportedModelArch = requireNonNull(supportedModelArch);
             this.maxSoundModels = maxSoundModels;
             this.maxKeyphrases = maxKeyphrases;
             this.maxUsers = maxUsers;
@@ -148,6 +196,7 @@
             this.supportsConcurrentCapture = supportsConcurrentCapture;
             this.powerConsumptionMw = powerConsumptionMw;
             this.returnsTriggerInEvent = returnsTriggerInEvent;
+            this.audioCapabilities = audioCapabilities;
         }
 
         public static final @android.annotation.NonNull Parcelable.Creator<ModuleProperties> CREATOR
@@ -167,6 +216,7 @@
             String description = in.readString();
             String uuid = in.readString();
             int version = in.readInt();
+            String supportedModelArch = in.readString();
             int maxSoundModels = in.readInt();
             int maxKeyphrases = in.readInt();
             int maxUsers = in.readInt();
@@ -176,10 +226,11 @@
             boolean supportsConcurrentCapture = in.readByte() == 1;
             int powerConsumptionMw = in.readInt();
             boolean returnsTriggerInEvent = in.readByte() == 1;
+            int audioCapabilities = in.readInt();
             return new ModuleProperties(id, implementor, description, uuid, version,
-                    maxSoundModels, maxKeyphrases, maxUsers, recognitionModes,
+                    supportedModelArch, maxSoundModels, maxKeyphrases, maxUsers, recognitionModes,
                     supportsCaptureTransition, maxBufferMs, supportsConcurrentCapture,
-                    powerConsumptionMw, returnsTriggerInEvent);
+                    powerConsumptionMw, returnsTriggerInEvent, audioCapabilities);
         }
 
         @Override
@@ -189,6 +240,7 @@
             dest.writeString(description);
             dest.writeString(uuid.toString());
             dest.writeInt(version);
+            dest.writeString(supportedModelArch);
             dest.writeInt(maxSoundModels);
             dest.writeInt(maxKeyphrases);
             dest.writeInt(maxUsers);
@@ -198,6 +250,7 @@
             dest.writeByte((byte) (supportsConcurrentCapture ? 1 : 0));
             dest.writeInt(powerConsumptionMw);
             dest.writeByte((byte) (returnsTriggerInEvent ? 1 : 0));
+            dest.writeInt(audioCapabilities);
         }
 
         @Override
@@ -208,26 +261,39 @@
         @Override
         public String toString() {
             return "ModuleProperties [id=" + id + ", implementor=" + implementor + ", description="
-                    + description + ", uuid=" + uuid + ", version=" + version + ", maxSoundModels="
+                    + description + ", uuid=" + uuid + ", version=" + version
+                    + " , supportedModelArch=" + supportedModelArch + ", maxSoundModels="
                     + maxSoundModels + ", maxKeyphrases=" + maxKeyphrases + ", maxUsers="
                     + maxUsers + ", recognitionModes=" + recognitionModes
                     + ", supportsCaptureTransition=" + supportsCaptureTransition + ", maxBufferMs="
                     + maxBufferMs + ", supportsConcurrentCapture=" + supportsConcurrentCapture
                     + ", powerConsumptionMw=" + powerConsumptionMw
-                    + ", returnsTriggerInEvent=" + returnsTriggerInEvent + "]";
+                    + ", returnsTriggerInEvent=" + returnsTriggerInEvent
+                    + ", audioCapabilities=" + audioCapabilities + "]";
         }
     }
 
-    /*****************************************************************************
+    /**
      * A SoundModel describes the attributes and contains the binary data used by the hardware
      * implementation to detect a particular sound pattern.
      * A specialized version {@link KeyphraseSoundModel} is defined for key phrase
      * sound models.
-     *
-     * @hide
-     ****************************************************************************/
+     */
     public static class SoundModel {
-        /** Undefined sound model type */
+
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef({
+                TYPE_GENERIC_SOUND,
+                TYPE_KEYPHRASE,
+                TYPE_UNKNOWN,
+        })
+        public @interface SoundModelType {}
+
+        /**
+         * Undefined sound model type
+         * @hide
+         */
         public static final int TYPE_UNKNOWN = -1;
 
         /** Keyphrase sound model */
@@ -240,28 +306,31 @@
         public static final int TYPE_GENERIC_SOUND = 1;
 
         /** Unique sound model identifier */
-        @UnsupportedAppUsage
         @NonNull
         public final UUID uuid;
 
         /** Sound model type (e.g. TYPE_KEYPHRASE); */
+        @SoundModelType
         public final int type;
 
         /** Unique sound model vendor identifier */
-        @UnsupportedAppUsage
         @NonNull
         public final UUID vendorUuid;
 
+        /** vendor specific version number of the model */
+        public final int version;
+
         /** Opaque data. For use by vendor implementation and enrollment application */
-        @UnsupportedAppUsage
         @NonNull
         public final byte[] data;
 
-        public SoundModel(@NonNull UUID uuid, @Nullable UUID vendorUuid, int type,
-                @Nullable byte[] data) {
+        /** @hide */
+        public SoundModel(@NonNull UUID uuid, @Nullable UUID vendorUuid, @SoundModelType int type,
+                @Nullable byte[] data, int version) {
             this.uuid = requireNonNull(uuid);
             this.vendorUuid = vendorUuid != null ? vendorUuid : new UUID(0, 0);
             this.type = type;
+            this.version = version;
             this.data = data != null ? data : new byte[0];
         }
 
@@ -269,6 +338,7 @@
         public int hashCode() {
             final int prime = 31;
             int result = 1;
+            result = prime * result + version;
             result = prime * result + Arrays.hashCode(data);
             result = prime * result + type;
             result = prime * result + ((uuid == null) ? 0 : uuid.hashCode());
@@ -278,65 +348,90 @@
 
         @Override
         public boolean equals(Object obj) {
-            if (this == obj)
+            if (this == obj) {
                 return true;
-            if (obj == null)
+            }
+            if (obj == null) {
                 return false;
-            if (!(obj instanceof SoundModel))
+            }
+            if (!(obj instanceof SoundModel)) {
                 return false;
+            }
             SoundModel other = (SoundModel) obj;
-            if (type != other.type)
+            if (type != other.type) {
                 return false;
+            }
             if (uuid == null) {
-                if (other.uuid != null)
+                if (other.uuid != null) {
                     return false;
-            } else if (!uuid.equals(other.uuid))
+                }
+            } else if (!uuid.equals(other.uuid)) {
                 return false;
+            }
             if (vendorUuid == null) {
-                if (other.vendorUuid != null)
+                if (other.vendorUuid != null) {
                     return false;
-            } else if (!vendorUuid.equals(other.vendorUuid))
+                }
+            } else if (!vendorUuid.equals(other.vendorUuid)) {
                 return false;
-            if (!Arrays.equals(data, other.data))
+            }
+            if (!Arrays.equals(data, other.data)) {
                 return false;
+            }
+            if (version != other.version) {
+                return false;
+            }
             return true;
         }
     }
 
-    /*****************************************************************************
+    /**
      * A Keyphrase describes a key phrase that can be detected by a
      * {@link KeyphraseSoundModel}
-     *
-     * @hide
-     ****************************************************************************/
-    public static class Keyphrase implements Parcelable {
+     */
+    public static final class Keyphrase implements Parcelable {
         /** Unique identifier for this keyphrase */
-        @UnsupportedAppUsage
         public final int id;
 
-        /** Recognition modes supported for this key phrase in the model */
-        @UnsupportedAppUsage
+        /**
+         * Recognition modes supported for this key phrase in the model
+         *
+         * @see #RECOGNITION_MODE_VOICE_TRIGGER
+         * @see #RECOGNITION_MODE_USER_IDENTIFICATION
+         * @see #RECOGNITION_MODE_USER_AUTHENTICATION
+         * @see #RECOGNITION_MODE_GENERIC
+         */
+        @RecognitionModes
         public final int recognitionModes;
 
-        /** Locale of the keyphrase. JAVA Locale string e.g en_US */
-        @UnsupportedAppUsage
+        /** Locale of the keyphrase. */
         @NonNull
-        public final String locale;
+        public final Locale locale;
 
         /** Key phrase text */
-        @UnsupportedAppUsage
         @NonNull
         public final String text;
 
-        /** Users this key phrase has been trained for. countains sound trigger specific user IDs
-         * derived from system user IDs {@link android.os.UserHandle#getIdentifier()}. */
-        @UnsupportedAppUsage
+        /**
+         * Users this key phrase has been trained for. countains sound trigger specific user IDs
+         * derived from system user IDs {@link android.os.UserHandle#getIdentifier()}.
+         */
         @NonNull
         public final int[] users;
 
-        @UnsupportedAppUsage
-        public Keyphrase(int id, int recognitionModes, @NonNull String locale, @NonNull String text,
-                @Nullable int[] users) {
+        /**
+         * Constructor for Keyphrase describes a key phrase that can be detected by a
+         * {@link KeyphraseSoundModel}
+         *
+         * @param id Unique keyphrase identifier for this keyphrase
+         * @param recognitionModes Bit field representation of recognition modes this keyphrase
+         *                         supports
+         * @param locale Locale of the keyphrase
+         * @param text Key phrase text
+         * @param users Users this key phrase has been trained for.
+         */
+        public Keyphrase(int id, @RecognitionModes int recognitionModes, @NonNull Locale locale,
+                @NonNull String text, @Nullable int[] users) {
             this.id = id;
             this.recognitionModes = recognitionModes;
             this.locale = requireNonNull(locale);
@@ -344,21 +439,27 @@
             this.users = users != null ? users : new int[0];
         }
 
-        public static final @android.annotation.NonNull Parcelable.Creator<Keyphrase> CREATOR
-                = new Parcelable.Creator<Keyphrase>() {
-            public Keyphrase createFromParcel(Parcel in) {
-                return Keyphrase.fromParcel(in);
+        public static final @NonNull Parcelable.Creator<Keyphrase> CREATOR =
+                new Parcelable.Creator<Keyphrase>() {
+            @NonNull
+            public Keyphrase createFromParcel(@NonNull Parcel in) {
+                return Keyphrase.readFromParcel(in);
             }
 
+            @NonNull
             public Keyphrase[] newArray(int size) {
                 return new Keyphrase[size];
             }
         };
 
-        private static Keyphrase fromParcel(Parcel in) {
+        /**
+         * Read from Parcel to generate keyphrase
+         */
+        @NonNull
+        public static Keyphrase readFromParcel(@NonNull Parcel in) {
             int id = in.readInt();
             int recognitionModes = in.readInt();
-            String locale = in.readString();
+            Locale locale = Locale.forLanguageTag(in.readString());
             String text = in.readString();
             int[] users = null;
             int numUsers = in.readInt();
@@ -370,10 +471,10 @@
         }
 
         @Override
-        public void writeToParcel(Parcel dest, int flags) {
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
             dest.writeInt(id);
             dest.writeInt(recognitionModes);
-            dest.writeString(locale);
+            dest.writeString(locale.toLanguageTag());
             dest.writeString(text);
             if (users != null) {
                 dest.writeInt(users.length);
@@ -383,6 +484,7 @@
             }
         }
 
+        /** @hide */
         @Override
         public int describeContents() {
             return 0;
@@ -402,90 +504,110 @@
 
         @Override
         public boolean equals(Object obj) {
-            if (this == obj)
+            if (this == obj) {
                 return true;
-            if (obj == null)
+            }
+            if (obj == null) {
                 return false;
-            if (getClass() != obj.getClass())
+            }
+            if (getClass() != obj.getClass()) {
                 return false;
+            }
             Keyphrase other = (Keyphrase) obj;
             if (text == null) {
-                if (other.text != null)
+                if (other.text != null) {
                     return false;
-            } else if (!text.equals(other.text))
+                }
+            } else if (!text.equals(other.text)) {
                 return false;
-            if (id != other.id)
+            }
+            if (id != other.id) {
                 return false;
+            }
             if (locale == null) {
-                if (other.locale != null)
+                if (other.locale != null) {
                     return false;
-            } else if (!locale.equals(other.locale))
+                }
+            } else if (!locale.equals(other.locale)) {
                 return false;
-            if (recognitionModes != other.recognitionModes)
+            }
+            if (recognitionModes != other.recognitionModes) {
                 return false;
-            if (!Arrays.equals(users, other.users))
+            }
+            if (!Arrays.equals(users, other.users)) {
                 return false;
+            }
             return true;
         }
 
         @Override
         public String toString() {
-            return "Keyphrase [id=" + id + ", recognitionModes=" + recognitionModes + ", locale="
-                    + locale + ", text=" + text + ", users=" + Arrays.toString(users) + "]";
+            return "Keyphrase [id=" + id + ", recognitionModes=" + recognitionModes
+                    + ", locale=" + locale.toLanguageTag() + ", text=" + text
+                    + ", users=" + Arrays.toString(users) + "]";
         }
     }
 
-    /*****************************************************************************
+    /**
      * A KeyphraseSoundModel is a specialized {@link SoundModel} for key phrases.
      * It contains data needed by the hardware to detect a certain number of key phrases
      * and the list of corresponding {@link Keyphrase} descriptors.
-     *
-     * @hide
-     ****************************************************************************/
-    public static class KeyphraseSoundModel extends SoundModel implements Parcelable {
+     */
+    public static final class KeyphraseSoundModel extends SoundModel implements Parcelable {
         /** Key phrases in this sound model */
-        @UnsupportedAppUsage
         @NonNull
         public final Keyphrase[] keyphrases; // keyword phrases in model
 
-        @UnsupportedAppUsage
         public KeyphraseSoundModel(
                 @NonNull UUID uuid, @NonNull UUID vendorUuid, @Nullable byte[] data,
-                @Nullable Keyphrase[] keyphrases) {
-            super(uuid, vendorUuid, TYPE_KEYPHRASE, data);
+                @Nullable Keyphrase[] keyphrases, int version) {
+            super(uuid, vendorUuid, TYPE_KEYPHRASE, data, version);
             this.keyphrases = keyphrases != null ? keyphrases : new Keyphrase[0];
         }
 
-        public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseSoundModel> CREATOR
-                = new Parcelable.Creator<KeyphraseSoundModel>() {
-            public KeyphraseSoundModel createFromParcel(Parcel in) {
-                return KeyphraseSoundModel.fromParcel(in);
+        public KeyphraseSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid,
+                @Nullable byte[] data, @Nullable Keyphrase[] keyphrases) {
+            this(uuid, vendorUuid, data, keyphrases, -1);
+        }
+
+        public static final @NonNull Parcelable.Creator<KeyphraseSoundModel> CREATOR =
+                new Parcelable.Creator<KeyphraseSoundModel>() {
+            @NonNull
+            public KeyphraseSoundModel createFromParcel(@NonNull Parcel in) {
+                return KeyphraseSoundModel.readFromParcel(in);
             }
 
+            @NonNull
             public KeyphraseSoundModel[] newArray(int size) {
                 return new KeyphraseSoundModel[size];
             }
         };
 
-        private static KeyphraseSoundModel fromParcel(Parcel in) {
+        /**
+         * Read from Parcel to generate KeyphraseSoundModel
+         */
+        @NonNull
+        public static KeyphraseSoundModel readFromParcel(@NonNull Parcel in) {
             UUID uuid = UUID.fromString(in.readString());
             UUID vendorUuid = null;
             int length = in.readInt();
             if (length >= 0) {
                 vendorUuid = UUID.fromString(in.readString());
             }
+            int version = in.readInt();
             byte[] data = in.readBlob();
             Keyphrase[] keyphrases = in.createTypedArray(Keyphrase.CREATOR);
-            return new KeyphraseSoundModel(uuid, vendorUuid, data, keyphrases);
+            return new KeyphraseSoundModel(uuid, vendorUuid, data, keyphrases, version);
         }
 
+        /** @hide */
         @Override
         public int describeContents() {
             return 0;
         }
 
         @Override
-        public void writeToParcel(Parcel dest, int flags) {
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
             dest.writeString(uuid.toString());
             if (vendorUuid == null) {
                 dest.writeInt(-1);
@@ -493,6 +615,7 @@
                 dest.writeInt(vendorUuid.toString().length());
                 dest.writeString(vendorUuid.toString());
             }
+            dest.writeInt(version);
             dest.writeBlob(data);
             dest.writeTypedArray(keyphrases, flags);
         }
@@ -501,7 +624,9 @@
         public String toString() {
             return "KeyphraseSoundModel [keyphrases=" + Arrays.toString(keyphrases)
                     + ", uuid=" + uuid + ", vendorUuid=" + vendorUuid
-                    + ", type=" + type + ", data=" + (data == null ? 0 : data.length) + "]";
+                    + ", type=" + type
+                    + ", data=" + (data == null ? 0 : data.length)
+                    + ", version=" + version + "]";
         }
 
         @Override
@@ -514,15 +639,19 @@
 
         @Override
         public boolean equals(Object obj) {
-            if (this == obj)
+            if (this == obj) {
                 return true;
-            if (!super.equals(obj))
+            }
+            if (!super.equals(obj)) {
                 return false;
-            if (!(obj instanceof KeyphraseSoundModel))
+            }
+            if (!(obj instanceof KeyphraseSoundModel)) {
                 return false;
+            }
             KeyphraseSoundModel other = (KeyphraseSoundModel) obj;
-            if (!Arrays.equals(keyphrases, other.keyphrases))
+            if (!Arrays.equals(keyphrases, other.keyphrases)) {
                 return false;
+            }
             return true;
         }
     }
@@ -547,10 +676,15 @@
             }
         };
 
+        public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid,
+                @Nullable byte[] data, int version) {
+            super(uuid, vendorUuid, TYPE_GENERIC_SOUND, data, version);
+        }
+
         @UnsupportedAppUsage
         public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid,
                 @Nullable byte[] data) {
-            super(uuid, vendorUuid, TYPE_GENERIC_SOUND, data);
+            this(uuid, vendorUuid, data, -1);
         }
 
         @Override
@@ -566,7 +700,8 @@
                 vendorUuid = UUID.fromString(in.readString());
             }
             byte[] data = in.readBlob();
-            return new GenericSoundModel(uuid, vendorUuid, data);
+            int version = in.readInt();
+            return new GenericSoundModel(uuid, vendorUuid, data, version);
         }
 
         @Override
@@ -579,28 +714,31 @@
                 dest.writeString(vendorUuid.toString());
             }
             dest.writeBlob(data);
+            dest.writeInt(version);
         }
 
         @Override
         public String toString() {
             return "GenericSoundModel [uuid=" + uuid + ", vendorUuid=" + vendorUuid
-                    + ", type=" + type + ", data=" + (data == null ? 0 : data.length) + "]";
+                    + ", type=" + type
+                    + ", data=" + (data == null ? 0 : data.length)
+                    + ", version=" + version + "]";
         }
     }
 
-    /*****************************************************************************
+    /**
      * A ModelParamRange is a representation of supported parameter range for a
      * given loaded model.
-     ****************************************************************************/
+     */
     public static final class ModelParamRange implements Parcelable {
 
         /**
-         * start of supported range inclusive
+         * The inclusive start of supported range.
          */
         public final int start;
 
         /**
-         * end of supported range inclusive
+         * The inclusive end of supported range.
          */
         public final int end;
 
@@ -609,31 +747,65 @@
             this.end = end;
         }
 
+        /** @hide */
         private ModelParamRange(@NonNull Parcel in) {
             this.start = in.readInt();
             this.end = in.readInt();
         }
 
         @NonNull
-        public static final Creator<ModelParamRange> CREATOR = new Creator<ModelParamRange>() {
-            @Override
-            @NonNull
-            public ModelParamRange createFromParcel(@NonNull Parcel in) {
-                return new ModelParamRange(in);
-            }
+        public static final Creator<ModelParamRange> CREATOR =
+                new Creator<ModelParamRange>() {
+                    @Override
+                    @NonNull
+                    public ModelParamRange createFromParcel(@NonNull Parcel in) {
+                        return new ModelParamRange(in);
+                    }
 
-            @Override
-            @NonNull
-            public ModelParamRange[] newArray(int size) {
-                return new ModelParamRange[size];
-            }
-        };
+                    @Override
+                    @NonNull
+                    public ModelParamRange[] newArray(int size) {
+                        return new ModelParamRange[size];
+                    }
+                };
 
+        /** @hide */
         @Override
         public int describeContents() {
             return 0;
         }
 
+        /** @hide */
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + (start);
+            result = prime * result + (end);
+            return result;
+        }
+
+        @Override
+        public boolean equals(@Nullable Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null) {
+                return false;
+            }
+            if (getClass() != obj.getClass()) {
+                return false;
+            }
+            ModelParamRange other = (ModelParamRange) obj;
+            if (start != other.start) {
+                return false;
+            }
+            if (end != other.end) {
+                return false;
+            }
+            return true;
+        }
+
         @Override
         public void writeToParcel(@NonNull Parcel dest, int flags) {
             dest.writeInt(start);
@@ -648,31 +820,32 @@
     }
 
     /**
-     *  Modes for key phrase recognition
+     * Modes for key phrase recognition
+     * @hide
      */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "RECOGNITION_MODE_" }, value = {
+            RECOGNITION_MODE_VOICE_TRIGGER,
+            RECOGNITION_MODE_USER_IDENTIFICATION,
+            RECOGNITION_MODE_USER_AUTHENTICATION,
+            RECOGNITION_MODE_GENERIC
+    })
+    public @interface RecognitionModes {}
 
     /**
-     * Simple recognition of the key phrase
-     *
-     * @hide
+     * Trigger on recognition of a key phrase
      */
     public static final int RECOGNITION_MODE_VOICE_TRIGGER = 0x1;
     /**
      * Trigger only if one user is identified
-     *
-     * @hide
      */
     public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 0x2;
     /**
      * Trigger only if one user is authenticated
-     *
-     * @hide
      */
     public static final int RECOGNITION_MODE_USER_AUTHENTICATION = 0x4;
     /**
      * Generic (non-speech) recognition.
-     *
-     * @hide
      */
     public static final int RECOGNITION_MODE_GENERIC = 0x8;
 
@@ -1002,13 +1175,27 @@
         @NonNull
         public final byte[] data;
 
-        @UnsupportedAppUsage
+        /**
+         * Bit field encoding of the AudioCapabilities
+         * supported by the firmware.
+         */
+        @ModuleProperties.AudioCapabilities
+        public final int audioCapabilities;
+
         public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
-                @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) {
+                @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data,
+                int audioCapabilities) {
             this.captureRequested = captureRequested;
             this.allowMultipleTriggers = allowMultipleTriggers;
             this.keyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0];
             this.data = data != null ? data : new byte[0];
+            this.audioCapabilities = audioCapabilities;
+        }
+
+        @UnsupportedAppUsage
+        public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
+                @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) {
+            this(captureRequested, allowMultipleTriggers, keyphrases, data, 0);
         }
 
         public static final @android.annotation.NonNull Parcelable.Creator<RecognitionConfig> CREATOR
@@ -1028,7 +1215,9 @@
             KeyphraseRecognitionExtra[] keyphrases =
                     in.createTypedArray(KeyphraseRecognitionExtra.CREATOR);
             byte[] data = in.readBlob();
-            return new RecognitionConfig(captureRequested, allowMultipleTriggers, keyphrases, data);
+            int audioCapabilities = in.readInt();
+            return new RecognitionConfig(captureRequested, allowMultipleTriggers, keyphrases, data,
+                    audioCapabilities);
         }
 
         @Override
@@ -1037,6 +1226,7 @@
             dest.writeByte((byte) (allowMultipleTriggers ? 1 : 0));
             dest.writeTypedArray(keyphrases, flags);
             dest.writeBlob(data);
+            dest.writeInt(audioCapabilities);
         }
 
         @Override
@@ -1048,7 +1238,8 @@
         public String toString() {
             return "RecognitionConfig [captureRequested=" + captureRequested
                     + ", allowMultipleTriggers=" + allowMultipleTriggers + ", keyphrases="
-                    + Arrays.toString(keyphrases) + ", data=" + Arrays.toString(data) + "]";
+                    + Arrays.toString(keyphrases) + ", data=" + Arrays.toString(data)
+                    + ", audioCapabilities=" + Integer.toHexString(audioCapabilities) + "]";
         }
     }
 
@@ -1548,6 +1739,45 @@
     }
 
     /**
+     * Translate an exception thrown from interaction with the underlying service to an error code.
+     * Throws a runtime exception for unexpected conditions.
+     * @param e The caught exception.
+     * @return The error code.
+     *
+     * @hide
+     */
+    static int handleException(Exception e) {
+        Log.w(TAG, "Exception caught", e);
+        if (e instanceof RemoteException) {
+            return STATUS_DEAD_OBJECT;
+        }
+        if (e instanceof ServiceSpecificException) {
+            switch (((ServiceSpecificException) e).errorCode) {
+                case Status.OPERATION_NOT_SUPPORTED:
+                    return STATUS_INVALID_OPERATION;
+                case Status.TEMPORARY_PERMISSION_DENIED:
+                    return STATUS_PERMISSION_DENIED;
+                case Status.DEAD_OBJECT:
+                    return STATUS_DEAD_OBJECT;
+            }
+            return STATUS_ERROR;
+        }
+        if (e instanceof SecurityException) {
+            return STATUS_PERMISSION_DENIED;
+        }
+        if (e instanceof IllegalStateException) {
+            return STATUS_INVALID_OPERATION;
+        }
+        if (e instanceof IllegalArgumentException || e instanceof NullPointerException) {
+            return STATUS_BAD_VALUE;
+        }
+        // This is not one of the conditions represented by our error code, escalate to a
+        // RuntimeException.
+        Log.e(TAG, "Escalating unexpected exception: ", e);
+        throw new RuntimeException(e);
+    }
+
+    /**
      * Returns a list of descriptors for all hardware modules loaded.
      * @param modules A ModuleProperties array where the list will be returned.
      * @return - {@link #STATUS_OK} in case of success
@@ -1569,9 +1799,8 @@
                 modules.add(ConversionUtil.aidl2apiModuleDescriptor(desc));
             }
             return STATUS_OK;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Exception caught", e);
-            return STATUS_DEAD_OBJECT;
+        } catch (Exception e) {
+            return handleException(e);
         }
     }
 
diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
index 7291419..9bd3992 100644
--- a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
+++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
@@ -78,7 +78,7 @@
                 mService = null;
             }
         } catch (Exception e) {
-            handleException(e);
+            SoundTrigger.handleException(e);
         }
     }
 
@@ -115,7 +115,7 @@
             }
             return SoundTrigger.STATUS_BAD_VALUE;
         } catch (Exception e) {
-            return handleException(e);
+            return SoundTrigger.handleException(e);
         }
     }
 
@@ -137,7 +137,7 @@
             mService.unloadModel(soundModelHandle);
             return SoundTrigger.STATUS_OK;
         } catch (Exception e) {
-            return handleException(e);
+            return SoundTrigger.handleException(e);
         }
     }
 
@@ -166,7 +166,7 @@
                     ConversionUtil.api2aidlRecognitionConfig(config));
             return SoundTrigger.STATUS_OK;
         } catch (Exception e) {
-            return handleException(e);
+            return SoundTrigger.handleException(e);
         }
     }
 
@@ -189,7 +189,7 @@
             mService.stopRecognition(soundModelHandle);
             return SoundTrigger.STATUS_OK;
         } catch (Exception e) {
-            return handleException(e);
+            return SoundTrigger.handleException(e);
         }
     }
 
@@ -214,7 +214,7 @@
             mService.forceRecognitionEvent(soundModelHandle);
             return SoundTrigger.STATUS_OK;
         } catch (Exception e) {
-            return handleException(e);
+            return SoundTrigger.handleException(e);
         }
     }
 
@@ -242,7 +242,7 @@
                     ConversionUtil.api2aidlModelParameter(modelParam), value);
             return SoundTrigger.STATUS_OK;
         } catch (Exception e) {
-            return handleException(e);
+            return SoundTrigger.handleException(e);
         }
     }
 
@@ -296,23 +296,6 @@
         }
     }
 
-    private int handleException(Exception e) {
-        Log.e(TAG, "", e);
-        if (e instanceof NullPointerException) {
-            return SoundTrigger.STATUS_NO_INIT;
-        }
-        if (e instanceof RemoteException) {
-            return SoundTrigger.STATUS_DEAD_OBJECT;
-        }
-        if (e instanceof IllegalArgumentException) {
-            return SoundTrigger.STATUS_BAD_VALUE;
-        }
-        if (e instanceof IllegalStateException) {
-            return SoundTrigger.STATUS_INVALID_OPERATION;
-        }
-        return SoundTrigger.STATUS_ERROR;
-    }
-
     private class EventHandlerDelegate extends ISoundTriggerCallback.Stub implements
             IBinder.DeathRecipient {
         private final Handler mHandler;
@@ -370,6 +353,12 @@
         }
 
         @Override
+        public synchronized void onModuleDied() {
+            Message m = mHandler.obtainMessage(EVENT_SERVICE_DIED);
+            mHandler.sendMessage(m);
+        }
+
+        @Override
         public synchronized void binderDied() {
             Message m = mHandler.obtainMessage(EVENT_SERVICE_DIED);
             mHandler.sendMessage(m);
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index c8dbd16..e32865e 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -126,6 +126,9 @@
     /* Gets the current screen unlocked functions. */
     long getScreenUnlockedFunctions();
 
+    /* Resets the USB gadget. */
+    void resetUsbGadget();
+
     /* Get the functionfs control handle for the given function. Usb
      * descriptors will already be written, and the handle will be
      * ready to use.
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 73b9d17..827353b 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -91,7 +91,7 @@
      *
      * {@hide}
      */
-    @UnsupportedAppUsage
+    @SystemApi
     public static final String ACTION_USB_STATE =
             "android.hardware.usb.action.USB_STATE";
 
@@ -164,7 +164,7 @@
      *
      * {@hide}
      */
-    @UnsupportedAppUsage
+    @SystemApi
     public static final String USB_CONNECTED = "connected";
 
     /**
@@ -181,6 +181,7 @@
      *
      * {@hide}
      */
+    @SystemApi
     public static final String USB_CONFIGURED = "configured";
 
     /**
@@ -217,6 +218,7 @@
      *
      * {@hide}
      */
+    @SystemApi
     public static final String USB_FUNCTION_RNDIS = "rndis";
 
     /**
@@ -319,6 +321,7 @@
      * Code for the charging usb function. Passed into {@link #setCurrentFunctions(long)}
      * {@hide}
      */
+    @SystemApi
     public static final long FUNCTION_NONE = 0;
 
     /**
@@ -337,6 +340,7 @@
      * Code for the rndis usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
      * {@hide}
      */
+    @SystemApi
     public static final long FUNCTION_RNDIS = GadgetFunction.RNDIS;
 
     /**
@@ -698,6 +702,8 @@
      *
      * {@hide}
      */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.MANAGE_USB)
     public void setCurrentFunctions(long functions) {
         try {
             mService.setCurrentFunctions(functions);
@@ -737,6 +743,8 @@
      *
      * {@hide}
      */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.MANAGE_USB)
     public long getCurrentFunctions() {
         try {
             return mService.getCurrentFunctions();
@@ -786,6 +794,25 @@
     }
 
     /**
+     * Resets the USB Gadget.
+     * <p>
+     * Performs USB data stack reset through USB Gadget HAL.
+     * It will force USB data connection reset. The connection will disconnect and reconnect.
+     * </p>
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.MANAGE_USB)
+    public void resetUsbGadget() {
+        try {
+            mService.resetUsbGadget();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns a list of physical USB ports on the device.
      * <p>
      * This list is guaranteed to contain all dual-role USB Type C ports but it might
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 4d5fabb..2a441de 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -221,7 +221,9 @@
                 inputMethod.revokeSession((InputMethodSession)msg.obj);
                 return;
             case DO_SHOW_SOFT_INPUT:
-                inputMethod.showSoftInput(msg.arg1, (ResultReceiver)msg.obj);
+                SomeArgs args = (SomeArgs)msg.obj;
+                inputMethod.showSoftInputWithToken(
+                        msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1);
                 return;
             case DO_HIDE_SOFT_INPUT:
                 inputMethod.hideSoftInput(msg.arg1, (ResultReceiver)msg.obj);
@@ -230,10 +232,11 @@
                 inputMethod.changeInputMethodSubtype((InputMethodSubtype)msg.obj);
                 return;
             case DO_CREATE_INLINE_SUGGESTIONS_REQUEST:
-                SomeArgs args = (SomeArgs) msg.obj;
+                args = (SomeArgs) msg.obj;
                 inputMethod.onCreateInlineSuggestionsRequest((ComponentName) args.arg1,
                         (AutofillId) args.arg2, (IInlineSuggestionsRequestCallback) args.arg3);
                 return;
+
         }
         Log.w(TAG, "Unhandled message code: " + msg.what);
     }
@@ -371,9 +374,9 @@
 
     @BinderThread
     @Override
-    public void showSoftInput(int flags, ResultReceiver resultReceiver) {
-        mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_SHOW_SOFT_INPUT,
-                flags, resultReceiver));
+    public void showSoftInput(IBinder showInputToken, int flags, ResultReceiver resultReceiver) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_SHOW_SOFT_INPUT,
+                flags, showInputToken, resultReceiver));
     }
 
     @BinderThread
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 8e52ee9..92047dc 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -20,8 +20,8 @@
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
+import static android.view.WindowInsets.Type.navigationBars;
 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND;
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
@@ -46,11 +46,13 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.SystemClock;
@@ -450,6 +452,16 @@
     @Nullable
     private InlineSuggestionsRequestInfo mInlineSuggestionsRequestInfo = null;
 
+    /**
+     * An opaque {@link Binder} token of window requesting {@link InputMethodImpl#showSoftInput}
+     * The original app window token is passed from client app window.
+     * {@link com.android.server.inputmethod.InputMethodManagerService} creates a unique dummy
+     * token to identify this window.
+     * This dummy token is only valid for a single call to {@link InputMethodImpl#showSoftInput},
+     * after which it is set null until next call.
+     */
+    private IBinder mCurShowInputToken;
+
     private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
 
     final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> {
@@ -491,6 +503,9 @@
      * all of the standard behavior for an input method.
      */
     public class InputMethodImpl extends AbstractInputMethodImpl {
+
+        private boolean mSystemCallingShowSoftInput;
+
         /**
          * {@inheritDoc}
          * @hide
@@ -659,11 +674,33 @@
 
         /**
          * {@inheritDoc}
+         * @hide
+         */
+        @MainThread
+        @Override
+        public void showSoftInputWithToken(int flags, ResultReceiver resultReceiver,
+                IBinder showInputToken) {
+            mSystemCallingShowSoftInput = true;
+            mCurShowInputToken = showInputToken;
+            showSoftInput(flags, resultReceiver);
+            mCurShowInputToken = null;
+            mSystemCallingShowSoftInput = false;
+        }
+
+        /**
+         * {@inheritDoc}
          */
         @MainThread
         @Override
         public void showSoftInput(int flags, ResultReceiver resultReceiver) {
             if (DEBUG) Log.v(TAG, "showSoftInput()");
+            // TODO(b/148086656): Disallow IME developers from calling InputMethodImpl methods.
+            if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R
+                    && !mSystemCallingShowSoftInput) {
+                Log.e(TAG," IME shouldn't call showSoftInput on itself."
+                        + " Use requestShowSelf(int) itself");
+                return;
+            }
             final boolean wasVisible = mIsPreRendered
                     ? mDecorViewVisible && mWindowVisible : isInputViewShown();
             if (dispatchOnShowInputRequested(flags, false)) {
@@ -698,6 +735,15 @@
         public void changeInputMethodSubtype(InputMethodSubtype subtype) {
             dispatchOnCurrentInputMethodSubtypeChanged(subtype);
         }
+
+        /**
+         * {@inheritDoc}
+         * @hide
+         */
+        @Override
+        public void setCurrentShowInputToken(IBinder showInputToken) {
+            mCurShowInputToken = showInputToken;
+        }
     }
 
     // TODO(b/137800469): Add detailed docs explaining the inline suggestions process.
@@ -1194,16 +1240,16 @@
                 Context.LAYOUT_INFLATER_SERVICE);
         mWindow = new SoftInputWindow(this, "InputMethod", mTheme, null, null, mDispatcherState,
                 WindowManager.LayoutParams.TYPE_INPUT_METHOD, Gravity.BOTTOM, false);
-        mWindow.getWindow().setFitWindowInsetsTypes(WindowInsets.Type.systemBars());
-        mWindow.getWindow().addPrivateFlags(PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND);
+        mWindow.getWindow().getAttributes().setFitInsetsTypes(WindowInsets.Type.statusBars());
+
+        // IME layout should always be inset by navigation bar, no matter it's current visibility.
         mWindow.getWindow().getDecorView().setOnApplyWindowInsetsListener(
                 (v, insets) -> v.onApplyWindowInsets(
-                        new WindowInsets.Builder(insets).setSystemWindowInsets(
-                                android.graphics.Insets.of(
-                                        insets.getSystemWindowInsetLeft(),
-                                        insets.getSystemWindowInsetTop(),
-                                        insets.getSystemWindowInsetRight(),
-                                        insets.getStableInsetBottom())).build()));
+                        new WindowInsets.Builder(insets).setInsets(
+                                navigationBars(),
+                                insets.getInsetsIgnoringVisibility(navigationBars()))
+                                .build()));
+
         // For ColorView in DecorView to work, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS needs to be set
         // by default (but IME developers can opt this out later if they want a new behavior).
         mWindow.getWindow().setFlags(
@@ -2181,7 +2227,7 @@
         if (!isVisibilityAppliedUsingInsetsConsumer()) {
             return;
         }
-        mPrivOps.applyImeVisibility(setVisible);
+        mPrivOps.applyImeVisibility(mCurShowInputToken, setVisible);
     }
 
     private boolean isVisibilityAppliedUsingInsetsConsumer() {
diff --git a/core/java/android/net/CaptivePortal.java b/core/java/android/net/CaptivePortal.java
index a66fcae..fb35b4b 100644
--- a/core/java/android/net/CaptivePortal.java
+++ b/core/java/android/net/CaptivePortal.java
@@ -60,6 +60,18 @@
     @SystemApi
     @TestApi
     public static final int APP_RETURN_WANTED_AS_IS = 2;
+    /** Event offset of request codes from captive portal application. */
+    private static final int APP_REQUEST_BASE = 100;
+    /**
+     * Request code from the captive portal application, indicating that the network condition may
+     * have changed and the network should be re-validated.
+     * @see ICaptivePortal#appRequest(int)
+     * @see android.net.INetworkMonitor#forceReevaluation(int)
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public static final int APP_REQUEST_REEVALUATION_REQUIRED = APP_REQUEST_BASE + 0;
 
     private final IBinder mBinder;
 
@@ -136,6 +148,19 @@
     }
 
     /**
+     * Request that the system reevaluates the captive portal status.
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public void reevaluateNetwork() {
+        try {
+            ICaptivePortal.Stub.asInterface(mBinder).appRequest(APP_REQUEST_REEVALUATION_REQUIRED);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
      * Log a captive portal login event.
      * @param eventId one of the CAPTIVE_PORTAL_LOGIN_* constants in metrics_constants.proto.
      * @param packageName captive portal application package name.
diff --git a/core/java/android/net/CaptivePortalData.aidl b/core/java/android/net/CaptivePortalData.aidl
new file mode 100644
index 0000000..1d57ee7
--- /dev/null
+++ b/core/java/android/net/CaptivePortalData.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.net;
+
+@JavaOnlyStableParcelable parcelable CaptivePortalData;
diff --git a/core/java/android/net/CaptivePortalData.java b/core/java/android/net/CaptivePortalData.java
new file mode 100644
index 0000000..1357803
--- /dev/null
+++ b/core/java/android/net/CaptivePortalData.java
@@ -0,0 +1,281 @@
+/*
+ * 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.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Metadata sent by captive portals, see https://www.ietf.org/id/draft-ietf-capport-api-03.txt.
+ * @hide
+ */
+@SystemApi
+@TestApi
+public final class CaptivePortalData implements Parcelable {
+    private final long mRefreshTimeMillis;
+    @Nullable
+    private final Uri mUserPortalUrl;
+    @Nullable
+    private final Uri mVenueInfoUrl;
+    private final boolean mIsSessionExtendable;
+    private final long mByteLimit;
+    private final long mExpiryTimeMillis;
+    private final boolean mCaptive;
+
+    private CaptivePortalData(long refreshTimeMillis, Uri userPortalUrl, Uri venueInfoUrl,
+            boolean isSessionExtendable, long byteLimit, long expiryTimeMillis, boolean captive) {
+        mRefreshTimeMillis = refreshTimeMillis;
+        mUserPortalUrl = userPortalUrl;
+        mVenueInfoUrl = venueInfoUrl;
+        mIsSessionExtendable = isSessionExtendable;
+        mByteLimit = byteLimit;
+        mExpiryTimeMillis = expiryTimeMillis;
+        mCaptive = captive;
+    }
+
+    private CaptivePortalData(Parcel p) {
+        this(p.readLong(), p.readParcelable(null), p.readParcelable(null), p.readBoolean(),
+                p.readLong(), p.readLong(), p.readBoolean());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeLong(mRefreshTimeMillis);
+        dest.writeParcelable(mUserPortalUrl, 0);
+        dest.writeParcelable(mVenueInfoUrl, 0);
+        dest.writeBoolean(mIsSessionExtendable);
+        dest.writeLong(mByteLimit);
+        dest.writeLong(mExpiryTimeMillis);
+        dest.writeBoolean(mCaptive);
+    }
+
+    /**
+     * A builder to create new {@link CaptivePortalData}.
+     */
+    public static class Builder {
+        private long mRefreshTime;
+        private Uri mUserPortalUrl;
+        private Uri mVenueInfoUrl;
+        private boolean mIsSessionExtendable;
+        private long mBytesRemaining = -1;
+        private long mExpiryTime = -1;
+        private boolean mCaptive;
+
+        /**
+         * Create an empty builder.
+         */
+        public Builder() {}
+
+        /**
+         * Create a builder copying all data from existing {@link CaptivePortalData}.
+         */
+        public Builder(@Nullable CaptivePortalData data) {
+            if (data == null) return;
+            setRefreshTime(data.mRefreshTimeMillis)
+                    .setUserPortalUrl(data.mUserPortalUrl)
+                    .setVenueInfoUrl(data.mVenueInfoUrl)
+                    .setSessionExtendable(data.mIsSessionExtendable)
+                    .setBytesRemaining(data.mByteLimit)
+                    .setExpiryTime(data.mExpiryTimeMillis)
+                    .setCaptive(data.mCaptive);
+        }
+
+        /**
+         * Set the time at which data was last refreshed, as per {@link System#currentTimeMillis()}.
+         */
+        @NonNull
+        public Builder setRefreshTime(long refreshTime) {
+            mRefreshTime = refreshTime;
+            return this;
+        }
+
+        /**
+         * Set the URL to be used for users to login to the portal, if captive.
+         */
+        @NonNull
+        public Builder setUserPortalUrl(@Nullable Uri userPortalUrl) {
+            mUserPortalUrl = userPortalUrl;
+            return this;
+        }
+
+        /**
+         * Set the URL that can be used by users to view information about the network venue.
+         */
+        @NonNull
+        public Builder setVenueInfoUrl(@Nullable Uri venueInfoUrl) {
+            mVenueInfoUrl = venueInfoUrl;
+            return this;
+        }
+
+        /**
+         * Set whether the portal supports extending a user session on the portal URL page.
+         */
+        @NonNull
+        public Builder setSessionExtendable(boolean sessionExtendable) {
+            mIsSessionExtendable = sessionExtendable;
+            return this;
+        }
+
+        /**
+         * Set the number of bytes remaining on the network before the portal closes.
+         */
+        @NonNull
+        public Builder setBytesRemaining(long bytesRemaining) {
+            mBytesRemaining = bytesRemaining;
+            return this;
+        }
+
+        /**
+         * Set the time at the session will expire, as per {@link System#currentTimeMillis()}.
+         */
+        @NonNull
+        public Builder setExpiryTime(long expiryTime) {
+            mExpiryTime = expiryTime;
+            return this;
+        }
+
+        /**
+         * Set whether the network is captive (portal closed).
+         */
+        @NonNull
+        public Builder setCaptive(boolean captive) {
+            mCaptive = captive;
+            return this;
+        }
+
+        /**
+         * Create a new {@link CaptivePortalData}.
+         */
+        @NonNull
+        public CaptivePortalData build() {
+            return new CaptivePortalData(mRefreshTime, mUserPortalUrl, mVenueInfoUrl,
+                    mIsSessionExtendable, mBytesRemaining, mExpiryTime, mCaptive);
+        }
+    }
+
+    /**
+     * Get the time at which data was last refreshed, as per {@link System#currentTimeMillis()}.
+     */
+    public long getRefreshTimeMillis() {
+        return mRefreshTimeMillis;
+    }
+
+    /**
+     * Get the URL to be used for users to login to the portal, or extend their session if
+     * {@link #isSessionExtendable()} is true.
+     */
+    @Nullable
+    public Uri getUserPortalUrl() {
+        return mUserPortalUrl;
+    }
+
+    /**
+     * Get the URL that can be used by users to view information about the network venue.
+     */
+    @Nullable
+    public Uri getVenueInfoUrl() {
+        return mVenueInfoUrl;
+    }
+
+    /**
+     * Indicates whether the user portal URL can be used to extend sessions, when the user is logged
+     * in and the session has a time or byte limit.
+     */
+    public boolean isSessionExtendable() {
+        return mIsSessionExtendable;
+    }
+
+    /**
+     * Get the remaining bytes on the captive portal session, at the time {@link CaptivePortalData}
+     * was refreshed. This may be different from the limit currently enforced by the portal.
+     * @return The byte limit, or -1 if not set.
+     */
+    public long getByteLimit() {
+        return mByteLimit;
+    }
+
+    /**
+     * Get the time at the session will expire, as per {@link System#currentTimeMillis()}.
+     * @return The expiry time, or -1 if unset.
+     */
+    public long getExpiryTimeMillis() {
+        return mExpiryTimeMillis;
+    }
+
+    /**
+     * Get whether the network is captive (portal closed).
+     */
+    public boolean isCaptive() {
+        return mCaptive;
+    }
+
+    @NonNull
+    public static final Creator<CaptivePortalData> CREATOR = new Creator<CaptivePortalData>() {
+        @Override
+        public CaptivePortalData createFromParcel(Parcel source) {
+            return new CaptivePortalData(source);
+        }
+
+        @Override
+        public CaptivePortalData[] newArray(int size) {
+            return new CaptivePortalData[size];
+        }
+    };
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mRefreshTimeMillis, mUserPortalUrl, mVenueInfoUrl,
+                mIsSessionExtendable, mByteLimit, mExpiryTimeMillis, mCaptive);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof CaptivePortalData)) return false;
+        final CaptivePortalData other = (CaptivePortalData) obj;
+        return mRefreshTimeMillis == other.mRefreshTimeMillis
+                && Objects.equals(mUserPortalUrl, other.mUserPortalUrl)
+                && Objects.equals(mVenueInfoUrl, other.mVenueInfoUrl)
+                && mIsSessionExtendable == other.mIsSessionExtendable
+                && mByteLimit == other.mByteLimit
+                && mExpiryTimeMillis == other.mExpiryTimeMillis
+                && mCaptive == other.mCaptive;
+    }
+
+    @Override
+    public String toString() {
+        return "CaptivePortalData {"
+                + "refreshTime: " + mRefreshTimeMillis
+                + ", userPortalUrl: " + mUserPortalUrl
+                + ", venueInfoUrl: " + mVenueInfoUrl
+                + ", isSessionExtendable: " + mIsSessionExtendable
+                + ", byteLimit: " + mByteLimit
+                + ", expiryTime: " + mExpiryTimeMillis
+                + ", captive: " + mCaptive
+                + "}";
+    }
+}
diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.aidl b/core/java/android/net/ConnectivityDiagnosticsManager.aidl
new file mode 100644
index 0000000..82ba0ca
--- /dev/null
+++ b/core/java/android/net/ConnectivityDiagnosticsManager.aidl
@@ -0,0 +1,21 @@
+/**
+ *
+ * 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;
+
+parcelable ConnectivityDiagnosticsManager.ConnectivityReport;
+parcelable ConnectivityDiagnosticsManager.DataStallReport;
\ No newline at end of file
diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.java b/core/java/android/net/ConnectivityDiagnosticsManager.java
new file mode 100644
index 0000000..a6f9b96
--- /dev/null
+++ b/core/java/android/net/ConnectivityDiagnosticsManager.java
@@ -0,0 +1,467 @@
+/*
+ * 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.net;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Class that provides utilities for collecting network connectivity diagnostics information.
+ * Connectivity information is made available through triggerable diagnostics tools and by listening
+ * to System validations. Some diagnostics information may be permissions-restricted.
+ *
+ * <p>ConnectivityDiagnosticsManager is intended for use by applications offering network
+ * connectivity on a user device. These tools will provide several mechanisms for these applications
+ * to be alerted to network conditions as well as diagnose potential network issues themselves.
+ *
+ * <p>The primary responsibilities of this class are to:
+ *
+ * <ul>
+ *   <li>Allow permissioned applications to register and unregister callbacks for network event
+ *       notifications
+ *   <li>Invoke callbacks for network event notifications, including:
+ *       <ul>
+ *         <li>Network validations
+ *         <li>Data stalls
+ *         <li>Connectivity reports from applications
+ *       </ul>
+ * </ul>
+ */
+public class ConnectivityDiagnosticsManager {
+    private final Context mContext;
+    private final IConnectivityManager mService;
+
+    /** @hide */
+    public ConnectivityDiagnosticsManager(Context context, IConnectivityManager service) {
+        mContext = Preconditions.checkNotNull(context, "missing context");
+        mService = Preconditions.checkNotNull(service, "missing IConnectivityManager");
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    public static boolean persistableBundleEquals(
+            @Nullable PersistableBundle a, @Nullable PersistableBundle b) {
+        if (a == b) return true;
+        if (a == null || b == null) return false;
+        if (!Objects.equals(a.keySet(), b.keySet())) return false;
+        for (String key : a.keySet()) {
+            if (!Objects.equals(a.get(key), b.get(key))) return false;
+        }
+        return true;
+    }
+
+    /** Class that includes connectivity information for a specific Network at a specific time. */
+    public static final class ConnectivityReport implements Parcelable {
+        /** The Network for which this ConnectivityReport applied */
+        @NonNull private final Network mNetwork;
+
+        /**
+         * The timestamp for the report. The timestamp is taken from {@link
+         * System#currentTimeMillis}.
+         */
+        private final long mReportTimestamp;
+
+        /** LinkProperties available on the Network at the reported timestamp */
+        @NonNull private final LinkProperties mLinkProperties;
+
+        /** NetworkCapabilities available on the Network at the reported timestamp */
+        @NonNull private final NetworkCapabilities mNetworkCapabilities;
+
+        /** PersistableBundle that may contain additional info about the report */
+        @NonNull private final PersistableBundle mAdditionalInfo;
+
+        /**
+         * Constructor for ConnectivityReport.
+         *
+         * <p>Apps should obtain instances through {@link
+         * ConnectivityDiagnosticsCallback#onConnectivityReport} instead of instantiating their own
+         * instances (unless for testing purposes).
+         *
+         * @param network The Network for which this ConnectivityReport applies
+         * @param reportTimestamp The timestamp for the report
+         * @param linkProperties The LinkProperties available on network at reportTimestamp
+         * @param networkCapabilities The NetworkCapabilities available on network at
+         *     reportTimestamp
+         * @param additionalInfo A PersistableBundle that may contain additional info about the
+         *     report
+         */
+        public ConnectivityReport(
+                @NonNull Network network,
+                long reportTimestamp,
+                @NonNull LinkProperties linkProperties,
+                @NonNull NetworkCapabilities networkCapabilities,
+                @NonNull PersistableBundle additionalInfo) {
+            mNetwork = network;
+            mReportTimestamp = reportTimestamp;
+            mLinkProperties = linkProperties;
+            mNetworkCapabilities = networkCapabilities;
+            mAdditionalInfo = additionalInfo;
+        }
+
+        /**
+         * Returns the Network for this ConnectivityReport.
+         *
+         * @return The Network for which this ConnectivityReport applied
+         */
+        @NonNull
+        public Network getNetwork() {
+            return mNetwork;
+        }
+
+        /**
+         * Returns the epoch timestamp (milliseconds) for when this report was taken.
+         *
+         * @return The timestamp for the report. Taken from {@link System#currentTimeMillis}.
+         */
+        public long getReportTimestamp() {
+            return mReportTimestamp;
+        }
+
+        /**
+         * Returns the LinkProperties available when this report was taken.
+         *
+         * @return LinkProperties available on the Network at the reported timestamp
+         */
+        @NonNull
+        public LinkProperties getLinkProperties() {
+            return new LinkProperties(mLinkProperties);
+        }
+
+        /**
+         * Returns the NetworkCapabilities when this report was taken.
+         *
+         * @return NetworkCapabilities available on the Network at the reported timestamp
+         */
+        @NonNull
+        public NetworkCapabilities getNetworkCapabilities() {
+            return new NetworkCapabilities(mNetworkCapabilities);
+        }
+
+        /**
+         * Returns a PersistableBundle with additional info for this report.
+         *
+         * @return PersistableBundle that may contain additional info about the report
+         */
+        @NonNull
+        public PersistableBundle getAdditionalInfo() {
+            return new PersistableBundle(mAdditionalInfo);
+        }
+
+        @Override
+        public boolean equals(@Nullable Object o) {
+            if (this == o) return true;
+            if (!(o instanceof ConnectivityReport)) return false;
+            final ConnectivityReport that = (ConnectivityReport) o;
+
+            // PersistableBundle is optimized to avoid unparcelling data unless fields are
+            // referenced. Because of this, use {@link ConnectivityDiagnosticsManager#equals} over
+            // {@link PersistableBundle#kindofEquals}.
+            return mReportTimestamp == that.mReportTimestamp
+                    && mNetwork.equals(that.mNetwork)
+                    && mLinkProperties.equals(that.mLinkProperties)
+                    && mNetworkCapabilities.equals(that.mNetworkCapabilities)
+                    && persistableBundleEquals(mAdditionalInfo, that.mAdditionalInfo);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(
+                    mNetwork,
+                    mReportTimestamp,
+                    mLinkProperties,
+                    mNetworkCapabilities,
+                    mAdditionalInfo);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeParcelable(mNetwork, flags);
+            dest.writeLong(mReportTimestamp);
+            dest.writeParcelable(mLinkProperties, flags);
+            dest.writeParcelable(mNetworkCapabilities, flags);
+            dest.writeParcelable(mAdditionalInfo, flags);
+        }
+
+        /** Implement the Parcelable interface */
+        public static final @NonNull Creator<ConnectivityReport> CREATOR =
+                new Creator<>() {
+                    public ConnectivityReport createFromParcel(Parcel in) {
+                        return new ConnectivityReport(
+                                in.readParcelable(null),
+                                in.readLong(),
+                                in.readParcelable(null),
+                                in.readParcelable(null),
+                                in.readParcelable(null));
+                    }
+
+                    public ConnectivityReport[] newArray(int size) {
+                        return new ConnectivityReport[size];
+                    }
+                };
+    }
+
+    /** Class that includes information for a suspected data stall on a specific Network */
+    public static final class DataStallReport implements Parcelable {
+        public static final int DETECTION_METHOD_DNS_EVENTS = 1;
+        public static final int DETECTION_METHOD_TCP_METRICS = 2;
+
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(
+                prefix = {"DETECTION_METHOD_"},
+                value = {DETECTION_METHOD_DNS_EVENTS, DETECTION_METHOD_TCP_METRICS})
+        public @interface DetectionMethod {}
+
+        /** The Network for which this DataStallReport applied */
+        @NonNull private final Network mNetwork;
+
+        /**
+         * The timestamp for the report. The timestamp is taken from {@link
+         * System#currentTimeMillis}.
+         */
+        private long mReportTimestamp;
+
+        /** The detection method used to identify the suspected data stall */
+        @DetectionMethod private final int mDetectionMethod;
+
+        /** PersistableBundle that may contain additional information on the suspected data stall */
+        @NonNull private final PersistableBundle mStallDetails;
+
+        /**
+         * Constructor for DataStallReport.
+         *
+         * <p>Apps should obtain instances through {@link
+         * ConnectivityDiagnosticsCallback#onDataStallSuspected} instead of instantiating their own
+         * instances (unless for testing purposes).
+         *
+         * @param network The Network for which this DataStallReport applies
+         * @param reportTimestamp The timestamp for the report
+         * @param detectionMethod The detection method used to identify this data stall
+         * @param stallDetails A PersistableBundle that may contain additional info about the report
+         */
+        public DataStallReport(
+                @NonNull Network network,
+                long reportTimestamp,
+                @DetectionMethod int detectionMethod,
+                @NonNull PersistableBundle stallDetails) {
+            mNetwork = network;
+            mReportTimestamp = reportTimestamp;
+            mDetectionMethod = detectionMethod;
+            mStallDetails = stallDetails;
+        }
+
+        /**
+         * Returns the Network for this DataStallReport.
+         *
+         * @return The Network for which this DataStallReport applied
+         */
+        @NonNull
+        public Network getNetwork() {
+            return mNetwork;
+        }
+
+        /**
+         * Returns the epoch timestamp (milliseconds) for when this report was taken.
+         *
+         * @return The timestamp for the report. Taken from {@link System#currentTimeMillis}.
+         */
+        public long getReportTimestamp() {
+            return mReportTimestamp;
+        }
+
+        /**
+         * Returns the detection method used to identify this suspected data stall.
+         *
+         * @return The detection method used to identify the suspected data stall
+         */
+        public int getDetectionMethod() {
+            return mDetectionMethod;
+        }
+
+        /**
+         * Returns a PersistableBundle with additional info for this report.
+         *
+         * @return PersistableBundle that may contain additional information on the suspected data
+         *     stall
+         */
+        @NonNull
+        public PersistableBundle getStallDetails() {
+            return new PersistableBundle(mStallDetails);
+        }
+
+        @Override
+        public boolean equals(@Nullable Object o) {
+            if (this == o) return true;
+            if (!(o instanceof DataStallReport)) return false;
+            final DataStallReport that = (DataStallReport) o;
+
+            // PersistableBundle is optimized to avoid unparcelling data unless fields are
+            // referenced. Because of this, use {@link ConnectivityDiagnosticsManager#equals} over
+            // {@link PersistableBundle#kindofEquals}.
+            return mReportTimestamp == that.mReportTimestamp
+                    && mDetectionMethod == that.mDetectionMethod
+                    && mNetwork.equals(that.mNetwork)
+                    && persistableBundleEquals(mStallDetails, that.mStallDetails);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mNetwork, mReportTimestamp, mDetectionMethod, mStallDetails);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeParcelable(mNetwork, flags);
+            dest.writeLong(mReportTimestamp);
+            dest.writeInt(mDetectionMethod);
+            dest.writeParcelable(mStallDetails, flags);
+        }
+
+        /** Implement the Parcelable interface */
+        public static final @NonNull Creator<DataStallReport> CREATOR =
+                new Creator<DataStallReport>() {
+                    public DataStallReport createFromParcel(Parcel in) {
+                        return new DataStallReport(
+                                in.readParcelable(null),
+                                in.readLong(),
+                                in.readInt(),
+                                in.readParcelable(null));
+                    }
+
+                    public DataStallReport[] newArray(int size) {
+                        return new DataStallReport[size];
+                    }
+                };
+    }
+
+    /**
+     * Abstract base class for Connectivity Diagnostics callbacks. Used for notifications about
+     * network connectivity events. Must be extended by applications wanting notifications.
+     */
+    public abstract static class ConnectivityDiagnosticsCallback {
+        /**
+         * Called when the platform completes a data connectivity check. This will also be invoked
+         * upon registration with the latest report.
+         *
+         * <p>The Network specified in the ConnectivityReport may not be active any more when this
+         * method is invoked.
+         *
+         * @param report The ConnectivityReport containing information about a connectivity check
+         */
+        public void onConnectivityReport(@NonNull ConnectivityReport report) {}
+
+        /**
+         * Called when the platform suspects a data stall on some Network.
+         *
+         * <p>The Network specified in the DataStallReport may not be active any more when this
+         * method is invoked.
+         *
+         * @param report The DataStallReport containing information about the suspected data stall
+         */
+        public void onDataStallSuspected(@NonNull DataStallReport report) {}
+
+        /**
+         * Called when any app reports connectivity to the System.
+         *
+         * @param network The Network for which connectivity has been reported
+         * @param hasConnectivity The connectivity reported to the System
+         */
+        public void onNetworkConnectivityReported(
+                @NonNull Network network, boolean hasConnectivity) {}
+    }
+
+    /**
+     * Registers a ConnectivityDiagnosticsCallback with the System.
+     *
+     * <p>Only apps that offer network connectivity to the user are allowed to register callbacks.
+     * This includes:
+     *
+     * <ul>
+     *   <li>Carrier apps with active subscriptions
+     *   <li>Active VPNs
+     *   <li>WiFi Suggesters
+     * </ul>
+     *
+     * <p>Callbacks will be limited to receiving notifications for networks over which apps provide
+     * connectivity.
+     *
+     * <p>If a registering app loses its relevant permissions, any callbacks it registered will
+     * silently stop receiving callbacks.
+     *
+     * <p>Each register() call <b>MUST</b> use a unique ConnectivityDiagnosticsCallback instance. If
+     * a single instance is registered with multiple NetworkRequests, an IllegalArgumentException
+     * will be thrown.
+     *
+     * @param request The NetworkRequest that will be used to match with Networks for which
+     *     callbacks will be fired
+     * @param e The Executor to be used for running the callback method invocations
+     * @param callback The ConnectivityDiagnosticsCallback that the caller wants registered with the
+     *     System
+     * @throws IllegalArgumentException if the same callback instance is registered with multiple
+     *     NetworkRequests
+     * @throws SecurityException if the caller does not have appropriate permissions to register a
+     *     callback
+     */
+    public void registerConnectivityDiagnosticsCallback(
+            @NonNull NetworkRequest request,
+            @NonNull Executor e,
+            @NonNull ConnectivityDiagnosticsCallback callback) {
+        // TODO(b/143187964): implement ConnectivityDiagnostics functionality
+        throw new UnsupportedOperationException("registerCallback() not supported yet");
+    }
+
+    /**
+     * Unregisters a ConnectivityDiagnosticsCallback with the System.
+     *
+     * <p>If the given callback is not currently registered with the System, this operation will be
+     * a no-op.
+     *
+     * @param callback The ConnectivityDiagnosticsCallback to be unregistered from the System.
+     */
+    public void unregisterConnectivityDiagnosticsCallback(
+            @NonNull ConnectivityDiagnosticsCallback callback) {
+        // TODO(b/143187964): implement ConnectivityDiagnostics functionality
+        throw new UnsupportedOperationException("registerCallback() not supported yet");
+    }
+}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 6da16a8..753e754 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -33,6 +33,7 @@
 import android.content.Intent;
 import android.net.IpSecManager.UdpEncapsulationSocket;
 import android.net.SocketKeepalive.Callback;
+import android.net.TetheringManager.TetheringEventCallback;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Build.VERSION_CODES;
@@ -58,6 +59,7 @@
 import android.util.Log;
 import android.util.SparseIntArray;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.Protocol;
 
@@ -75,6 +77,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -363,7 +366,7 @@
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     @UnsupportedAppUsage
     public static final String ACTION_TETHER_STATE_CHANGED =
-            "android.net.conn.TETHER_STATE_CHANGED";
+            TetheringManager.ACTION_TETHER_STATE_CHANGED;
 
     /**
      * @hide
@@ -371,14 +374,14 @@
      * tethering and currently available for tethering.
      */
     @UnsupportedAppUsage
-    public static final String EXTRA_AVAILABLE_TETHER = "availableArray";
+    public static final String EXTRA_AVAILABLE_TETHER = TetheringManager.EXTRA_AVAILABLE_TETHER;
 
     /**
      * @hide
      * gives a String[] listing all the interfaces currently in local-only
      * mode (ie, has DHCPv4+IPv6-ULA support and no packet forwarding)
      */
-    public static final String EXTRA_ACTIVE_LOCAL_ONLY = "localOnlyArray";
+    public static final String EXTRA_ACTIVE_LOCAL_ONLY = TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY;
 
     /**
      * @hide
@@ -386,7 +389,7 @@
      * (ie, has DHCPv4 support and packets potentially forwarded/NATed)
      */
     @UnsupportedAppUsage
-    public static final String EXTRA_ACTIVE_TETHER = "tetherArray";
+    public static final String EXTRA_ACTIVE_TETHER = TetheringManager.EXTRA_ACTIVE_TETHER;
 
     /**
      * @hide
@@ -395,7 +398,7 @@
      * for any interfaces listed here.
      */
     @UnsupportedAppUsage
-    public static final String EXTRA_ERRORED_TETHER = "erroredArray";
+    public static final String EXTRA_ERRORED_TETHER = TetheringManager.EXTRA_ERRORED_TETHER;
 
     /**
      * Broadcast Action: The captive portal tracker has finished its test.
@@ -445,7 +448,7 @@
      * @see #startTethering(int, boolean, OnStartTetheringCallback)
      * @hide
      */
-    public static final int TETHERING_INVALID   = -1;
+    public static final int TETHERING_INVALID   = TetheringManager.TETHERING_INVALID;
 
     /**
      * Wifi tethering type.
@@ -453,7 +456,7 @@
      * @hide
      */
     @SystemApi
-    public static final int TETHERING_WIFI      = 0;
+    public static final int TETHERING_WIFI      = TetheringManager.TETHERING_WIFI;
 
     /**
      * USB tethering type.
@@ -461,7 +464,7 @@
      * @hide
      */
     @SystemApi
-    public static final int TETHERING_USB       = 1;
+    public static final int TETHERING_USB       = TetheringManager.TETHERING_USB;
 
     /**
      * Bluetooth tethering type.
@@ -469,7 +472,7 @@
      * @hide
      */
     @SystemApi
-    public static final int TETHERING_BLUETOOTH = 2;
+    public static final int TETHERING_BLUETOOTH = TetheringManager.TETHERING_BLUETOOTH;
 
     /**
      * Wifi P2p tethering type.
@@ -477,47 +480,48 @@
      * need to start from #startTethering(int, boolean, OnStartTetheringCallback).
      * @hide
      */
-    public static final int TETHERING_WIFI_P2P = 3;
+    public static final int TETHERING_WIFI_P2P = TetheringManager.TETHERING_WIFI_P2P;
 
     /**
      * Extra used for communicating with the TetherService. Includes the type of tethering to
      * enable if any.
      * @hide
      */
-    public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType";
+    public static final String EXTRA_ADD_TETHER_TYPE = TetheringConstants.EXTRA_ADD_TETHER_TYPE;
 
     /**
      * Extra used for communicating with the TetherService. Includes the type of tethering for
      * which to cancel provisioning.
      * @hide
      */
-    public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType";
+    public static final String EXTRA_REM_TETHER_TYPE = TetheringConstants.EXTRA_REM_TETHER_TYPE;
 
     /**
      * Extra used for communicating with the TetherService. True to schedule a recheck of tether
      * provisioning.
      * @hide
      */
-    public static final String EXTRA_SET_ALARM = "extraSetAlarm";
+    public static final String EXTRA_SET_ALARM = TetheringConstants.EXTRA_SET_ALARM;
 
     /**
      * Tells the TetherService to run a provision check now.
      * @hide
      */
-    public static final String EXTRA_RUN_PROVISION = "extraRunProvision";
+    public static final String EXTRA_RUN_PROVISION = TetheringConstants.EXTRA_RUN_PROVISION;
 
     /**
      * Extra used for communicating with the TetherService. Contains the {@link ResultReceiver}
      * which will receive provisioning results. Can be left empty.
      * @hide
      */
-    public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback";
+    public static final String EXTRA_PROVISION_CALLBACK =
+            TetheringConstants.EXTRA_PROVISION_CALLBACK;
 
     /**
      * The absence of a connection type.
      * @hide
      */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
+    @SystemApi
     public static final int TYPE_NONE        = -1;
 
     /**
@@ -662,7 +666,7 @@
      * {@hide}
      */
     @Deprecated
-    @UnsupportedAppUsage
+    @SystemApi
     public static final int TYPE_WIFI_P2P    = 13;
 
     /**
@@ -2368,10 +2372,12 @@
      *
      * @return an array of 0 or more Strings of tetherable interface names.
      *
+     * @deprecated Use {@link TetheringEventCallback#onTetherableInterfacesChanged(List)} instead.
      * {@hide}
      */
     @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     @UnsupportedAppUsage
+    @Deprecated
     public String[] getTetherableIfaces() {
         return getTetheringManager().getTetherableIfaces();
     }
@@ -2381,10 +2387,12 @@
      *
      * @return an array of 0 or more String of currently tethered interface names.
      *
+     * @deprecated Use {@link TetheringEventCallback#onTetherableInterfacesChanged(List)} instead.
      * {@hide}
      */
     @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     @UnsupportedAppUsage
+    @Deprecated
     public String[] getTetheredIfaces() {
         return getTetheringManager().getTetheredIfaces();
     }
@@ -2400,10 +2408,12 @@
      * @return an array of 0 or more String indicating the interface names
      *        which failed to tether.
      *
+     * @deprecated Use {@link TetheringEventCallback#onError(String, int)} instead.
      * {@hide}
      */
     @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     @UnsupportedAppUsage
+    @Deprecated
     public String[] getTetheringErroredIfaces() {
         return getTetheringManager().getTetheringErroredIfaces();
     }
@@ -2412,9 +2422,11 @@
      * Get the set of tethered dhcp ranges.
      *
      * @return an array of 0 or more {@code String} of tethered dhcp ranges.
+     * @deprecated This API just return the default value which is not used in DhcpServer.
      * {@hide}
      */
     @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+    @Deprecated
     public String[] getTetheredDhcpRanges() {
         return getTetheringManager().getTetheredDhcpRanges();
     }
@@ -2467,6 +2479,7 @@
      * {@hide}
      */
     @UnsupportedAppUsage
+    @Deprecated
     public int untether(String iface) {
         return getTetheringManager().untether(iface);
     }
@@ -2487,6 +2500,7 @@
      *
      * @return a boolean - {@code true} indicating Tethering is supported.
      *
+     * @deprecated Use {@link TetheringEventCallback#onTetheringSupported(boolean)} instead.
      * {@hide}
      */
     @SystemApi
@@ -2573,9 +2587,12 @@
      *         {@link ConnectivityManager.TETHERING_WIFI},
      *         {@link ConnectivityManager.TETHERING_USB}, or
      *         {@link ConnectivityManager.TETHERING_BLUETOOTH}.
+     *
+     * @deprecated Use {@link TetheringManager#stopTethering} instead.
      * @hide
      */
     @SystemApi
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
     public void stopTethering(int type) {
         getTetheringManager().stopTethering(type);
@@ -2585,9 +2602,11 @@
      * Callback for use with {@link registerTetheringEventCallback} to find out tethering
      * upstream status.
      *
-     *@hide
+     * @deprecated Use {@line TetheringManager#OnTetheringEventCallback} instead.
+     * @hide
      */
     @SystemApi
+    @Deprecated
     public abstract static class OnTetheringEventCallback {
 
         /**
@@ -2600,6 +2619,10 @@
         public void onUpstreamChanged(@Nullable Network network) {}
     }
 
+    @GuardedBy("mTetheringEventCallbacks")
+    private final ArrayMap<OnTetheringEventCallback, TetheringEventCallback>
+            mTetheringEventCallbacks = new ArrayMap<>();
+
     /**
      * Start listening to tethering change events. Any new added callback will receive the last
      * tethering status right away. If callback is registered when tethering has no upstream or
@@ -2608,16 +2631,30 @@
      *
      * @param executor the executor on which callback will be invoked.
      * @param callback the callback to be called when tethering has change events.
+     *
+     * @deprecated Use {@line TetheringManager#registerTetheringEventCallback} instead.
      * @hide
      */
     @SystemApi
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
     public void registerTetheringEventCallback(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull final OnTetheringEventCallback callback) {
         Preconditions.checkNotNull(callback, "OnTetheringEventCallback cannot be null.");
 
-        getTetheringManager().registerTetheringEventCallback(executor, callback);
+        final TetheringEventCallback tetherCallback =
+                new TetheringEventCallback() {
+                    @Override
+                    public void onUpstreamChanged(@Nullable Network network) {
+                        callback.onUpstreamChanged(network);
+                    }
+                };
+
+        synchronized (mTetheringEventCallbacks) {
+            mTetheringEventCallbacks.put(callback, tetherCallback);
+            getTetheringManager().registerTetheringEventCallback(executor, tetherCallback);
+        }
     }
 
     /**
@@ -2625,13 +2662,21 @@
      * {@link #registerTetheringEventCallback}.
      *
      * @param callback previously registered callback.
+     *
+     * @deprecated Use {@link TetheringManager#unregisterTetheringEventCallback} instead.
      * @hide
      */
     @SystemApi
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
     public void unregisterTetheringEventCallback(
             @NonNull final OnTetheringEventCallback callback) {
-        getTetheringManager().unregisterTetheringEventCallback(callback);
+        Objects.requireNonNull(callback, "The callback must be non-null");
+        synchronized (mTetheringEventCallbacks) {
+            final TetheringEventCallback tetherCallback =
+                    mTetheringEventCallbacks.remove(callback);
+            getTetheringManager().unregisterTetheringEventCallback(tetherCallback);
+        }
     }
 
 
@@ -2643,10 +2688,12 @@
      * @return an array of 0 or more regular expression Strings defining
      *        what interfaces are considered tetherable usb interfaces.
      *
+     * @deprecated Use {@link TetheringEventCallback#onTetherableInterfaceRegexpsChanged} instead.
      * {@hide}
      */
     @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     @UnsupportedAppUsage
+    @Deprecated
     public String[] getTetherableUsbRegexs() {
         return getTetheringManager().getTetherableUsbRegexs();
     }
@@ -2659,10 +2706,12 @@
      * @return an array of 0 or more regular expression Strings defining
      *        what interfaces are considered tetherable wifi interfaces.
      *
+     * @deprecated Use {@link TetheringEventCallback#onTetherableInterfaceRegexpsChanged} instead.
      * {@hide}
      */
     @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     @UnsupportedAppUsage
+    @Deprecated
     public String[] getTetherableWifiRegexs() {
         return getTetheringManager().getTetherableWifiRegexs();
     }
@@ -2675,10 +2724,13 @@
      * @return an array of 0 or more regular expression Strings defining
      *        what interfaces are considered tetherable bluetooth interfaces.
      *
+     * @deprecated Use {@link TetheringEventCallback#onTetherableInterfaceRegexpsChanged(
+     *TetheringManager.TetheringInterfaceRegexps)} instead.
      * {@hide}
      */
     @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     @UnsupportedAppUsage
+    @Deprecated
     public String[] getTetherableBluetoothRegexs() {
         return getTetheringManager().getTetherableBluetoothRegexs();
     }
@@ -2705,37 +2757,104 @@
         return getTetheringManager().setUsbTethering(enable);
     }
 
-    /** {@hide} */
+    /**
+     * @deprecated Use {@link TetheringManager#TETHER_ERROR_NO_ERROR}.
+     * {@hide}
+     */
     @SystemApi
-    public static final int TETHER_ERROR_NO_ERROR           = 0;
-    /** {@hide} */
-    public static final int TETHER_ERROR_UNKNOWN_IFACE      = 1;
-    /** {@hide} */
-    public static final int TETHER_ERROR_SERVICE_UNAVAIL    = 2;
-    /** {@hide} */
-    public static final int TETHER_ERROR_UNSUPPORTED        = 3;
-    /** {@hide} */
-    public static final int TETHER_ERROR_UNAVAIL_IFACE      = 4;
-    /** {@hide} */
-    public static final int TETHER_ERROR_MASTER_ERROR       = 5;
-    /** {@hide} */
-    public static final int TETHER_ERROR_TETHER_IFACE_ERROR = 6;
-    /** {@hide} */
-    public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7;
-    /** {@hide} */
-    public static final int TETHER_ERROR_ENABLE_NAT_ERROR     = 8;
-    /** {@hide} */
-    public static final int TETHER_ERROR_DISABLE_NAT_ERROR    = 9;
-    /** {@hide} */
-    public static final int TETHER_ERROR_IFACE_CFG_ERROR      = 10;
-    /** {@hide} */
+    @Deprecated
+    public static final int TETHER_ERROR_NO_ERROR = TetheringManager.TETHER_ERROR_NO_ERROR;
+    /**
+     * @deprecated Use {@link TetheringManager#TETHER_ERROR_UNKNOWN_IFACE}.
+     * {@hide}
+     */
+    @Deprecated
+    public static final int TETHER_ERROR_UNKNOWN_IFACE =
+            TetheringManager.TETHER_ERROR_UNKNOWN_IFACE;
+    /**
+     * @deprecated Use {@link TetheringManager#TETHER_ERROR_SERVICE_UNAVAIL}.
+     * {@hide}
+     */
+    @Deprecated
+    public static final int TETHER_ERROR_SERVICE_UNAVAIL =
+            TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL;
+    /**
+     * @deprecated Use {@link TetheringManager#TETHER_ERROR_UNSUPPORTED}.
+     * {@hide}
+     */
+    @Deprecated
+    public static final int TETHER_ERROR_UNSUPPORTED = TetheringManager.TETHER_ERROR_UNSUPPORTED;
+    /**
+     * @deprecated Use {@link TetheringManager#TETHER_ERROR_UNAVAIL_IFACE}.
+     * {@hide}
+     */
+    @Deprecated
+    public static final int TETHER_ERROR_UNAVAIL_IFACE =
+            TetheringManager.TETHER_ERROR_UNAVAIL_IFACE;
+    /**
+     * @deprecated Use {@link TetheringManager#TETHER_ERROR_MASTER_ERROR}.
+     * {@hide}
+     */
+    @Deprecated
+    public static final int TETHER_ERROR_MASTER_ERROR = TetheringManager.TETHER_ERROR_MASTER_ERROR;
+    /**
+     * @deprecated Use {@link TetheringManager#TETHER_ERROR_TETHER_IFACE_ERROR}.
+     * {@hide}
+     */
+    @Deprecated
+    public static final int TETHER_ERROR_TETHER_IFACE_ERROR =
+            TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR;
+    /**
+     * @deprecated Use {@link TetheringManager#TETHER_ERROR_UNTETHER_IFACE_ERROR}.
+     * {@hide}
+     */
+    @Deprecated
+    public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR =
+            TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
+    /**
+     * @deprecated Use {@link TetheringManager#TETHER_ERROR_ENABLE_NAT_ERROR}.
+     * {@hide}
+     */
+    @Deprecated
+    public static final int TETHER_ERROR_ENABLE_NAT_ERROR =
+            TetheringManager.TETHER_ERROR_ENABLE_NAT_ERROR;
+    /**
+     * @deprecated Use {@link TetheringManager#TETHER_ERROR_DISABLE_NAT_ERROR}.
+     * {@hide}
+     */
+    @Deprecated
+    public static final int TETHER_ERROR_DISABLE_NAT_ERROR =
+            TetheringManager.TETHER_ERROR_DISABLE_NAT_ERROR;
+    /**
+     * @deprecated Use {@link TetheringManager#TETHER_ERROR_IFACE_CFG_ERROR}.
+     * {@hide}
+     */
+    @Deprecated
+    public static final int TETHER_ERROR_IFACE_CFG_ERROR =
+            TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
+    /**
+     * @deprecated Use {@link TetheringManager#TETHER_ERROR_PROVISION_FAILED}.
+     * {@hide}
+     */
     @SystemApi
-    public static final int TETHER_ERROR_PROVISION_FAILED     = 11;
-    /** {@hide} */
-    public static final int TETHER_ERROR_DHCPSERVER_ERROR     = 12;
-    /** {@hide} */
+    @Deprecated
+    public static final int TETHER_ERROR_PROVISION_FAILED =
+            TetheringManager.TETHER_ERROR_PROVISION_FAILED;
+    /**
+     * @deprecated Use {@link TetheringManager#TETHER_ERROR_DHCPSERVER_ERROR}.
+     * {@hide}
+     */
+    @Deprecated
+    public static final int TETHER_ERROR_DHCPSERVER_ERROR =
+            TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR;
+    /**
+     * @deprecated Use {@link TetheringManager#TETHER_ERROR_ENTITLEMENT_UNKNOWN}.
+     * {@hide}
+     */
     @SystemApi
-    public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN  = 13;
+    @Deprecated
+    public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN =
+            TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN;
 
     /**
      * Get a more detailed error code after a Tethering or Untethering
@@ -2745,10 +2864,12 @@
      * @return error The error code of the last error tethering or untethering the named
      *               interface
      *
+     * @deprecated Use {@link TetheringEventCallback#onError(String, int)} instead.
      * {@hide}
      */
     @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     @UnsupportedAppUsage
+    @Deprecated
     public int getLastTetherError(String iface) {
         return getTetheringManager().getLastTetherError(iface);
     }
@@ -2766,9 +2887,12 @@
     /**
      * Callback for use with {@link #getLatestTetheringEntitlementResult} to find out whether
      * entitlement succeeded.
+     *
+     * @deprecated Use {@link TetheringManager#OnTetheringEntitlementResultListener} instead.
      * @hide
      */
     @SystemApi
+    @Deprecated
     public interface OnTetheringEntitlementResultListener  {
         /**
          * Called to notify entitlement result.
@@ -2798,9 +2922,11 @@
      * @param listener an {@link OnTetheringEntitlementResultListener} which will be called to
      *         notify the caller of the result of entitlement check. The listener may be called zero
      *         or one time.
+     * @deprecated Use {@link TetheringManager#requestLatestTetheringEntitlementResult} instead.
      * {@hide}
      */
     @SystemApi
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
     public void getLatestTetheringEntitlementResult(int type, boolean showEntitlementUi,
             @NonNull @CallbackExecutor Executor executor,
@@ -3107,6 +3233,61 @@
         }
     }
 
+    /**
+     * Registers the specified {@link NetworkProvider}.
+     * Each listener must only be registered once. The listener can be unregistered with
+     * {@link #unregisterNetworkProvider}.
+     *
+     * @param provider the provider to register
+     * @return the ID of the provider. This ID must be used by the provider when registering
+     *         {@link android.net.NetworkAgent}s.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+    public int registerNetworkProvider(@NonNull NetworkProvider provider) {
+        if (provider.getProviderId() != NetworkProvider.ID_NONE) {
+            throw new IllegalStateException("NetworkProviders can only be registered once");
+        }
+
+        try {
+            int providerId = mService.registerNetworkProvider(provider.getMessenger(),
+                    provider.getName());
+            provider.setProviderId(providerId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        return provider.getProviderId();
+    }
+
+    /**
+     * Unregisters the specified NetworkProvider.
+     *
+     * @param provider the provider to unregister
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+    public void unregisterNetworkProvider(@NonNull NetworkProvider provider) {
+        try {
+            mService.unregisterNetworkProvider(provider.getMessenger());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        provider.setProviderId(NetworkProvider.ID_NONE);
+    }
+
+
+    /** @hide exposed via the NetworkProvider class. */
+    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+    public void declareNetworkRequestUnfulfillable(@NonNull NetworkRequest request) {
+        try {
+            mService.declareNetworkRequestUnfulfillable(request);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     // TODO : remove this method. It is a stopgap measure to help sheperding a number
     // of dependent changes that would conflict throughout the automerger graph. Having this
     // temporarily helps with the process of going through with all these dependent changes across
@@ -3114,26 +3295,24 @@
     /**
      * @hide
      * Register a NetworkAgent with ConnectivityService.
-     * @return NetID corresponding to NetworkAgent.
+     * @return Network corresponding to NetworkAgent.
      */
     @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
-    public int registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp,
-            NetworkCapabilities nc, int score, NetworkMisc misc) {
-        return registerNetworkAgent(messenger, ni, lp, nc, score, misc,
-                NetworkFactory.SerialNumber.NONE);
+    public Network registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp,
+            NetworkCapabilities nc, int score, NetworkAgentConfig config) {
+        return registerNetworkAgent(messenger, ni, lp, nc, score, config, NetworkProvider.ID_NONE);
     }
 
     /**
      * @hide
      * Register a NetworkAgent with ConnectivityService.
-     * @return NetID corresponding to NetworkAgent.
+     * @return Network corresponding to NetworkAgent.
      */
     @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
-    public int registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp,
-            NetworkCapabilities nc, int score, NetworkMisc misc, int factorySerialNumber) {
+    public Network registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp,
+            NetworkCapabilities nc, int score, NetworkAgentConfig config, int providerId) {
         try {
-            return mService.registerNetworkAgent(messenger, ni, lp, nc, score, misc,
-                    factorySerialNumber);
+            return mService.registerNetworkAgent(messenger, ni, lp, nc, score, config, providerId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -3569,14 +3748,26 @@
     /**
      * Helper function to request a network with a particular legacy type.
      *
-     * This is temporarily public @hide so it can be called by system code that uses the
-     * NetworkRequest API to request networks but relies on CONNECTIVITY_ACTION broadcasts for
-     * instead network notifications.
+     * @deprecated This is temporarily public for tethering to backwards compatibility that uses
+     * the NetworkRequest API to request networks with legacy type and relies on
+     * CONNECTIVITY_ACTION broadcasts instead of NetworkCallbacks. New caller should use
+     * {@link #requestNetwork(NetworkRequest, NetworkCallback, Handler)} instead.
      *
      * TODO: update said system code to rely on NetworkCallbacks and make this method private.
+
+     * @param request {@link NetworkRequest} describing this request.
+     * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note
+     *                        the callback must not be shared - it uniquely specifies this request.
+     * @param timeoutMs The time in milliseconds to attempt looking for a suitable network
+     *                  before {@link NetworkCallback#onUnavailable()} is called. The timeout must
+     *                  be a positive value (i.e. >0).
+     * @param legacyType to specify the network type(#TYPE_*).
+     * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
      *
      * @hide
      */
+    @SystemApi
+    @Deprecated
     public void requestNetwork(@NonNull NetworkRequest request,
             @NonNull NetworkCallback networkCallback, int timeoutMs, int legacyType,
             @NonNull Handler handler) {
diff --git a/core/java/android/net/DhcpInfo.java b/core/java/android/net/DhcpInfo.java
index 98bab44..912df67 100644
--- a/core/java/android/net/DhcpInfo.java
+++ b/core/java/android/net/DhcpInfo.java
@@ -16,8 +16,8 @@
 
 package android.net;
 
-import android.os.Parcelable;
 import android.os.Parcel;
+import android.os.Parcelable;
 
 /**
  * A simple object for retrieving the results of a DHCP request.
@@ -67,12 +67,12 @@
         buf.append(NetworkUtils.intToInetAddress(addr).getHostAddress());
     }
 
-    /** Implement the Parcelable interface {@hide} */
+    /** Implement the Parcelable interface */
     public int describeContents() {
         return 0;
     }
 
-    /** Implement the Parcelable interface {@hide} */
+    /** Implement the Parcelable interface */
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(ipAddress);
         dest.writeInt(gateway);
@@ -83,7 +83,7 @@
         dest.writeInt(leaseDuration);
     }
 
-    /** Implement the Parcelable interface {@hide} */
+    /** Implement the Parcelable interface */
     public static final @android.annotation.NonNull Creator<DhcpInfo> CREATOR =
         new Creator<DhcpInfo>() {
             public DhcpInfo createFromParcel(Parcel in) {
diff --git a/core/java/android/net/ICaptivePortal.aidl b/core/java/android/net/ICaptivePortal.aidl
index 707b4f6..fe21905 100644
--- a/core/java/android/net/ICaptivePortal.aidl
+++ b/core/java/android/net/ICaptivePortal.aidl
@@ -21,6 +21,7 @@
  * @hide
  */
 oneway interface ICaptivePortal {
+    void appRequest(int request);
     void appResponse(int response);
     void logEvent(int eventId, String packageName);
 }
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 09c02ef..186196bd 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -20,9 +20,9 @@
 import android.net.ConnectionInfo;
 import android.net.LinkProperties;
 import android.net.Network;
+import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
-import android.net.NetworkMisc;
 import android.net.NetworkQuotaInfo;
 import android.net.NetworkRequest;
 import android.net.NetworkState;
@@ -142,14 +142,19 @@
 
     void setAirplaneMode(boolean enable);
 
-    int registerNetworkFactory(in Messenger messenger, in String name);
-
     boolean requestBandwidthUpdate(in Network network);
 
+    int registerNetworkFactory(in Messenger messenger, in String name);
     void unregisterNetworkFactory(in Messenger messenger);
 
-    int registerNetworkAgent(in Messenger messenger, in NetworkInfo ni, in LinkProperties lp,
-            in NetworkCapabilities nc, int score, in NetworkMisc misc, in int factorySerialNumber);
+    int registerNetworkProvider(in Messenger messenger, in String name);
+    void unregisterNetworkProvider(in Messenger messenger);
+
+    void declareNetworkRequestUnfulfillable(in NetworkRequest request);
+
+    Network registerNetworkAgent(in Messenger messenger, in NetworkInfo ni, in LinkProperties lp,
+            in NetworkCapabilities nc, int score, in NetworkAgentConfig config,
+            in int factorySerialNumber);
 
     NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities,
             in Messenger messenger, int timeoutSec, in IBinder binder, int legacy);
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index 385cb1d..72a6b39 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -57,9 +57,6 @@
     @UnsupportedAppUsage
     boolean getRestrictBackground();
 
-    /** Callback used to change internal state on tethering */
-    void onTetheringChanged(String iface, boolean tethering);
-
     /** Gets the restrict background status based on the caller's UID:
         1 - disabled
         2 - whitelisted
diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl
index 9994f9f..5fa515a 100644
--- a/core/java/android/net/INetworkStatsService.aidl
+++ b/core/java/android/net/INetworkStatsService.aidl
@@ -23,6 +23,8 @@
 import android.net.NetworkStats;
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
+import android.net.netstats.provider.INetworkStatsProvider;
+import android.net.netstats.provider.INetworkStatsProviderCallback;
 import android.os.IBinder;
 import android.os.Messenger;
 import com.android.internal.net.VpnInfo;
@@ -89,4 +91,7 @@
     /** Get the total network stats information since boot */
     long getTotalStats(int type);
 
+    /** Registers a network stats provider */
+    INetworkStatsProviderCallback registerNetworkStatsProvider(String tag,
+            in INetworkStatsProvider provider);
 }
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index 45d0c73..09ec6c3 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -17,7 +17,6 @@
 
 import static com.android.internal.util.Preconditions.checkNotNull;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
@@ -26,6 +25,7 @@
 import android.annotation.TestApi;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.net.annotations.PolicyDirection;
 import android.os.Binder;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
@@ -41,8 +41,6 @@
 
 import java.io.FileDescriptor;
 import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.net.DatagramSocket;
 import java.net.InetAddress;
 import java.net.Socket;
@@ -78,11 +76,6 @@
      */
     public static final int DIRECTION_OUT = 1;
 
-    /** @hide */
-    @IntDef(value = {DIRECTION_IN, DIRECTION_OUT})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface PolicyDirection {}
-
     /**
      * The Security Parameter Index (SPI) 0 indicates an unknown or invalid index.
      *
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 2792c56..e83f5e4 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -21,6 +21,8 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.net.util.LinkPropertiesUtils;
+import android.net.util.LinkPropertiesUtils.CompareResult;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -70,6 +72,14 @@
     private String mTcpBufferSizes;
     private IpPrefix mNat64Prefix;
     private boolean mWakeOnLanSupported;
+    private Uri mCaptivePortalApiUrl;
+    private CaptivePortalData mCaptivePortalData;
+
+    /**
+     * Indicates whether parceling should preserve fields that are set based on permissions of
+     * the process receiving the {@link LinkProperties}.
+     */
+    private final transient boolean mParcelSensitiveFields;
 
     private static final int MIN_MTU    = 68;
     private static final int MIN_MTU_V6 = 1280;
@@ -84,36 +94,6 @@
     /**
      * @hide
      */
-    public static class CompareResult<T> {
-        public final List<T> removed = new ArrayList<>();
-        public final List<T> added = new ArrayList<>();
-
-        public CompareResult() {}
-
-        public CompareResult(Collection<T> oldItems, Collection<T> newItems) {
-            if (oldItems != null) {
-                removed.addAll(oldItems);
-            }
-            if (newItems != null) {
-                for (T newItem : newItems) {
-                    if (!removed.remove(newItem)) {
-                        added.add(newItem);
-                    }
-                }
-            }
-        }
-
-        @Override
-        public String toString() {
-            return "removed=[" + TextUtils.join(",", removed)
-                    + "] added=[" + TextUtils.join(",", added)
-                    + "]";
-        }
-    }
-
-    /**
-     * @hide
-     */
     @UnsupportedAppUsage(implicitMember =
             "values()[Landroid/net/LinkProperties$ProvisioningChange;")
     public enum ProvisioningChange {
@@ -174,6 +154,7 @@
      * Constructs a new {@code LinkProperties} with default values.
      */
     public LinkProperties() {
+        mParcelSensitiveFields = false;
     }
 
     /**
@@ -182,26 +163,32 @@
     @SystemApi
     @TestApi
     public LinkProperties(@Nullable LinkProperties source) {
-        if (source != null) {
-            mIfaceName = source.mIfaceName;
-            mLinkAddresses.addAll(source.mLinkAddresses);
-            mDnses.addAll(source.mDnses);
-            mValidatedPrivateDnses.addAll(source.mValidatedPrivateDnses);
-            mUsePrivateDns = source.mUsePrivateDns;
-            mPrivateDnsServerName = source.mPrivateDnsServerName;
-            mPcscfs.addAll(source.mPcscfs);
-            mDomains = source.mDomains;
-            mRoutes.addAll(source.mRoutes);
-            mHttpProxy = (source.mHttpProxy == null) ? null : new ProxyInfo(source.mHttpProxy);
-            for (LinkProperties l: source.mStackedLinks.values()) {
-                addStackedLink(l);
-            }
-            setMtu(source.mMtu);
-            setDhcpServerAddress(source.getDhcpServerAddress());
-            mTcpBufferSizes = source.mTcpBufferSizes;
-            mNat64Prefix = source.mNat64Prefix;
-            mWakeOnLanSupported = source.mWakeOnLanSupported;
+        this(source, false /* parcelSensitiveFields */);
+    }
+
+    private LinkProperties(@Nullable LinkProperties source, boolean parcelSensitiveFields) {
+        mParcelSensitiveFields = parcelSensitiveFields;
+        if (source == null) return;
+        mIfaceName = source.mIfaceName;
+        mLinkAddresses.addAll(source.mLinkAddresses);
+        mDnses.addAll(source.mDnses);
+        mValidatedPrivateDnses.addAll(source.mValidatedPrivateDnses);
+        mUsePrivateDns = source.mUsePrivateDns;
+        mPrivateDnsServerName = source.mPrivateDnsServerName;
+        mPcscfs.addAll(source.mPcscfs);
+        mDomains = source.mDomains;
+        mRoutes.addAll(source.mRoutes);
+        mHttpProxy = (source.mHttpProxy == null) ? null : new ProxyInfo(source.mHttpProxy);
+        for (LinkProperties l: source.mStackedLinks.values()) {
+            addStackedLink(l);
         }
+        setMtu(source.mMtu);
+        setDhcpServerAddress(source.getDhcpServerAddress());
+        mTcpBufferSizes = source.mTcpBufferSizes;
+        mNat64Prefix = source.mNat64Prefix;
+        mWakeOnLanSupported = source.mWakeOnLanSupported;
+        mCaptivePortalApiUrl = source.mCaptivePortalApiUrl;
+        mCaptivePortalData = source.mCaptivePortalData;
     }
 
     /**
@@ -860,6 +847,11 @@
      * Clears this object to its initial state.
      */
     public void clear() {
+        if (mParcelSensitiveFields) {
+            throw new UnsupportedOperationException(
+                    "Cannot clear LinkProperties when parcelSensitiveFields is set");
+        }
+
         mIfaceName = null;
         mLinkAddresses.clear();
         mDnses.clear();
@@ -875,6 +867,8 @@
         mTcpBufferSizes = null;
         mNat64Prefix = null;
         mWakeOnLanSupported = false;
+        mCaptivePortalApiUrl = null;
+        mCaptivePortalData = null;
     }
 
     /**
@@ -945,6 +939,14 @@
             resultJoiner.add(mDhcpServerAddress.toString());
         }
 
+        if (mCaptivePortalApiUrl != null) {
+            resultJoiner.add("CaptivePortalApiUrl: " + mCaptivePortalApiUrl);
+        }
+
+        if (mCaptivePortalData != null) {
+            resultJoiner.add("CaptivePortalData: " + mCaptivePortalData);
+        }
+
         if (mTcpBufferSizes != null) {
             resultJoiner.add("TcpBufferSizes:");
             resultJoiner.add(mTcpBufferSizes);
@@ -1295,7 +1297,7 @@
      */
     @UnsupportedAppUsage
     public boolean isIdenticalInterfaceName(@NonNull LinkProperties target) {
-        return TextUtils.equals(getInterfaceName(), target.getInterfaceName());
+        return LinkPropertiesUtils.isIdenticalInterfaceName(target, this);
     }
 
     /**
@@ -1318,10 +1320,7 @@
      */
     @UnsupportedAppUsage
     public boolean isIdenticalAddresses(@NonNull LinkProperties target) {
-        Collection<InetAddress> targetAddresses = target.getAddresses();
-        Collection<InetAddress> sourceAddresses = getAddresses();
-        return (sourceAddresses.size() == targetAddresses.size()) ?
-                    sourceAddresses.containsAll(targetAddresses) : false;
+        return LinkPropertiesUtils.isIdenticalAddresses(target, this);
     }
 
     /**
@@ -1333,15 +1332,7 @@
      */
     @UnsupportedAppUsage
     public boolean isIdenticalDnses(@NonNull LinkProperties target) {
-        Collection<InetAddress> targetDnses = target.getDnsServers();
-        String targetDomains = target.getDomains();
-        if (mDomains == null) {
-            if (targetDomains != null) return false;
-        } else {
-            if (!mDomains.equals(targetDomains)) return false;
-        }
-        return (mDnses.size() == targetDnses.size()) ?
-                mDnses.containsAll(targetDnses) : false;
+        return LinkPropertiesUtils.isIdenticalDnses(target, this);
     }
 
     /**
@@ -1394,9 +1385,7 @@
      */
     @UnsupportedAppUsage
     public boolean isIdenticalRoutes(@NonNull LinkProperties target) {
-        Collection<RouteInfo> targetRoutes = target.getRoutes();
-        return (mRoutes.size() == targetRoutes.size()) ?
-                mRoutes.containsAll(targetRoutes) : false;
+        return LinkPropertiesUtils.isIdenticalRoutes(target, this);
     }
 
     /**
@@ -1408,8 +1397,7 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public boolean isIdenticalHttpProxy(@NonNull LinkProperties target) {
-        return getHttpProxy() == null ? target.getHttpProxy() == null :
-                getHttpProxy().equals(target.getHttpProxy());
+        return LinkPropertiesUtils.isIdenticalHttpProxy(target, this);
     }
 
     /**
@@ -1479,6 +1467,28 @@
     }
 
     /**
+     * Compares this {@code LinkProperties}'s CaptivePortalApiUrl against the target.
+     *
+     * @param target LinkProperties to compare.
+     * @return {@code true} if both are identical, {@code false} otherwise.
+     * @hide
+     */
+    public boolean isIdenticalCaptivePortalApiUrl(LinkProperties target) {
+        return Objects.equals(mCaptivePortalApiUrl, target.mCaptivePortalApiUrl);
+    }
+
+    /**
+     * Compares this {@code LinkProperties}'s CaptivePortalData against the target.
+     *
+     * @param target LinkProperties to compare.
+     * @return {@code true} if both are identical, {@code false} otherwise.
+     * @hide
+     */
+    public boolean isIdenticalCaptivePortalData(LinkProperties target) {
+        return Objects.equals(mCaptivePortalData, target.mCaptivePortalData);
+    }
+
+    /**
      * Set whether the network interface supports WakeOnLAN
      *
      * @param supported WakeOnLAN supported value
@@ -1499,6 +1509,73 @@
     }
 
     /**
+     * Set the URL of the captive portal API endpoint to get more information about the network.
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public void setCaptivePortalApiUrl(@Nullable Uri url) {
+        mCaptivePortalApiUrl = url;
+    }
+
+    /**
+     * Get the URL of the captive portal API endpoint to get more information about the network.
+     *
+     * <p>This is null unless the application has
+     * {@link android.Manifest.permission.NETWORK_SETTINGS} or
+     * {@link NetworkStack#PERMISSION_MAINLINE_NETWORK_STACK} permissions, and the network provided
+     * the URL.
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    @Nullable
+    public Uri getCaptivePortalApiUrl() {
+        return mCaptivePortalApiUrl;
+    }
+
+    /**
+     * Set the CaptivePortalData obtained from the captive portal API (RFC7710bis).
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public void setCaptivePortalData(@Nullable CaptivePortalData data) {
+        mCaptivePortalData = data;
+    }
+
+    /**
+     * Get the CaptivePortalData obtained from the captive portal API (RFC7710bis).
+     *
+     * <p>This is null unless the application has
+     * {@link android.Manifest.permission.NETWORK_SETTINGS} or
+     * {@link NetworkStack#PERMISSION_MAINLINE_NETWORK_STACK} permissions.
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    @Nullable
+    public CaptivePortalData getCaptivePortalData() {
+        return mCaptivePortalData;
+    }
+
+    /**
+     * Create a copy of this {@link LinkProperties} that will preserve fields that were set
+     * based on the permissions of the process that received this {@link LinkProperties}.
+     *
+     * <p>By default {@link LinkProperties} does not preserve such fields during parceling, as
+     * they should not be shared outside of the process that receives them without appropriate
+     * checks.
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    @NonNull
+    public LinkProperties makeSensitiveFieldsParcelingCopy() {
+        return new LinkProperties(this, true /* parcelSensitiveFields */);
+    }
+
+    /**
      * Compares this {@code LinkProperties} instance against the target
      * LinkProperties in {@code obj}. Two LinkPropertieses are equal if
      * all their fields are equal in values.
@@ -1537,27 +1614,9 @@
                 && isIdenticalMtu(target)
                 && isIdenticalTcpBufferSizes(target)
                 && isIdenticalNat64Prefix(target)
-                && isIdenticalWakeOnLan(target);
-    }
-
-    /**
-     * Compares the addresses in this LinkProperties with another
-     * LinkProperties, examining only addresses on the base link.
-     *
-     * @param target a LinkProperties with the new list of addresses
-     * @return the differences between the addresses.
-     * @hide
-     */
-    public @NonNull CompareResult<LinkAddress> compareAddresses(@Nullable LinkProperties target) {
-        /*
-         * Duplicate the LinkAddresses into removed, we will be removing
-         * address which are common between mLinkAddresses and target
-         * leaving the addresses that are different. And address which
-         * are in target but not in mLinkAddresses are placed in the
-         * addedAddresses.
-         */
-        return new CompareResult<>(mLinkAddresses,
-                target != null ? target.getLinkAddresses() : null);
+                && isIdenticalWakeOnLan(target)
+                && isIdenticalCaptivePortalApiUrl(target)
+                && isIdenticalCaptivePortalData(target);
     }
 
     /**
@@ -1655,7 +1714,8 @@
                 + mPcscfs.size() * 67
                 + ((null == mPrivateDnsServerName) ? 0 : mPrivateDnsServerName.hashCode())
                 + Objects.hash(mNat64Prefix)
-                + (mWakeOnLanSupported ? 71 : 0);
+                + (mWakeOnLanSupported ? 71 : 0)
+                + Objects.hash(mCaptivePortalApiUrl, mCaptivePortalData);
     }
 
     /**
@@ -1694,6 +1754,8 @@
         dest.writeList(stackedLinks);
 
         dest.writeBoolean(mWakeOnLanSupported);
+        dest.writeParcelable(mParcelSensitiveFields ? mCaptivePortalApiUrl : null, 0);
+        dest.writeParcelable(mParcelSensitiveFields ? mCaptivePortalData : null, 0);
     }
 
     private static void writeAddresses(@NonNull Parcel dest, @NonNull List<InetAddress> list) {
@@ -1785,6 +1847,9 @@
                     netProp.addStackedLink(stackedLink);
                 }
                 netProp.setWakeOnLanSupported(in.readBoolean());
+
+                netProp.setCaptivePortalApiUrl(in.readParcelable(null));
+                netProp.setCaptivePortalData(in.readParcelable(null));
                 return netProp;
             }
 
diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java
index 74c9aac..0e10c42 100644
--- a/core/java/android/net/MacAddress.java
+++ b/core/java/android/net/MacAddress.java
@@ -20,11 +20,11 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.net.util.MacAddressUtils;
 import android.net.wifi.WifiInfo;
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import com.android.internal.util.BitUtils;
 import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
@@ -33,7 +33,6 @@
 import java.net.UnknownHostException;
 import java.security.SecureRandom;
 import java.util.Arrays;
-import java.util.Random;
 
 /**
  * Representation of a MAC address.
@@ -109,21 +108,13 @@
         if (equals(BROADCAST_ADDRESS)) {
             return TYPE_BROADCAST;
         }
-        if (isMulticastAddress()) {
+        if ((mAddr & MULTICAST_MASK) != 0) {
             return TYPE_MULTICAST;
         }
         return TYPE_UNICAST;
     }
 
     /**
-     * @return true if this MacAddress is a multicast address.
-     * @hide
-     */
-    public boolean isMulticastAddress() {
-        return (mAddr & MULTICAST_MASK) != 0;
-    }
-
-    /**
      * @return true if this MacAddress is a locally assigned address.
      */
     public boolean isLocallyAssigned() {
@@ -192,7 +183,7 @@
      * @hide
      */
     public static boolean isMacAddress(byte[] addr) {
-        return addr != null && addr.length == ETHER_ADDR_LEN;
+        return MacAddressUtils.isMacAddress(addr);
     }
 
     /**
@@ -261,26 +252,11 @@
     }
 
     private static byte[] byteAddrFromLongAddr(long addr) {
-        byte[] bytes = new byte[ETHER_ADDR_LEN];
-        int index = ETHER_ADDR_LEN;
-        while (index-- > 0) {
-            bytes[index] = (byte) addr;
-            addr = addr >> 8;
-        }
-        return bytes;
+        return MacAddressUtils.byteAddrFromLongAddr(addr);
     }
 
     private static long longAddrFromByteAddr(byte[] addr) {
-        Preconditions.checkNotNull(addr);
-        if (!isMacAddress(addr)) {
-            throw new IllegalArgumentException(
-                    Arrays.toString(addr) + " was not a valid MAC address");
-        }
-        long longAddr = 0;
-        for (byte b : addr) {
-            longAddr = (longAddr << 8) + BitUtils.uint8(b);
-        }
-        return longAddr;
+        return MacAddressUtils.longAddrFromByteAddr(addr);
     }
 
     // Internal conversion function equivalent to longAddrFromByteAddr(byteAddrFromStringAddr(addr))
@@ -350,50 +326,7 @@
      * @hide
      */
     public static @NonNull MacAddress createRandomUnicastAddressWithGoogleBase() {
-        return createRandomUnicastAddress(BASE_GOOGLE_MAC, new SecureRandom());
-    }
-
-    /**
-     * Returns a generated MAC address whose 46 bits, excluding the locally assigned bit and the
-     * unicast bit, are randomly selected.
-     *
-     * The locally assigned bit is always set to 1. The multicast bit is always set to 0.
-     *
-     * @return a random locally assigned, unicast MacAddress.
-     *
-     * @hide
-     */
-    public static @NonNull MacAddress createRandomUnicastAddress() {
-        return createRandomUnicastAddress(null, new SecureRandom());
-    }
-
-    /**
-     * Returns a randomly generated MAC address using the given Random object and the same
-     * OUI values as the given MacAddress.
-     *
-     * The locally assigned bit is always set to 1. The multicast bit is always set to 0.
-     *
-     * @param base a base MacAddress whose OUI is used for generating the random address.
-     *             If base == null then the OUI will also be randomized.
-     * @param r a standard Java Random object used for generating the random address.
-     * @return a random locally assigned MacAddress.
-     *
-     * @hide
-     */
-    public static @NonNull MacAddress createRandomUnicastAddress(MacAddress base, Random r) {
-        long addr;
-        if (base == null) {
-            addr = r.nextLong() & VALID_LONG_MASK;
-        } else {
-            addr = (base.mAddr & OUI_MASK) | (NIC_MASK & r.nextLong());
-        }
-        addr |= LOCALLY_ASSIGNED_MASK;
-        addr &= ~MULTICAST_MASK;
-        MacAddress mac = new MacAddress(addr);
-        if (mac.equals(DEFAULT_MAC_ADDRESS)) {
-            return createRandomUnicastAddress(base, r);
-        }
-        return mac;
+        return MacAddressUtils.createRandomUnicastAddress(BASE_GOOGLE_MAC, new SecureRandom());
     }
 
     // Convenience function for working around the lack of byte literals.
diff --git a/core/java/android/net/NattKeepalivePacketData.java b/core/java/android/net/NattKeepalivePacketData.java
index 3fb52f1..bd39c13 100644
--- a/core/java/android/net/NattKeepalivePacketData.java
+++ b/core/java/android/net/NattKeepalivePacketData.java
@@ -16,9 +16,11 @@
 
 package android.net;
 
-import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
-import static android.net.SocketKeepalive.ERROR_INVALID_PORT;
+import static android.net.InvalidPacketException.ERROR_INVALID_IP_ADDRESS;
+import static android.net.InvalidPacketException.ERROR_INVALID_PORT;
 
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.net.util.IpUtils;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -30,20 +32,22 @@
 import java.nio.ByteOrder;
 
 /** @hide */
+@SystemApi
 public final class NattKeepalivePacketData extends KeepalivePacketData implements Parcelable {
     private static final int IPV4_HEADER_LENGTH = 20;
     private static final int UDP_HEADER_LENGTH = 8;
 
     // This should only be constructed via static factory methods, such as
     // nattKeepalivePacket
-    private NattKeepalivePacketData(InetAddress srcAddress, int srcPort,
-            InetAddress dstAddress, int dstPort, byte[] data) throws
+    public NattKeepalivePacketData(@NonNull InetAddress srcAddress, int srcPort,
+            @NonNull InetAddress dstAddress, int dstPort, @NonNull byte[] data) throws
             InvalidPacketException {
         super(srcAddress, srcPort, dstAddress, dstPort, data);
     }
 
     /**
      * Factory method to create Nat-T keepalive packet structure.
+     * @hide
      */
     public static NattKeepalivePacketData nattKeepalivePacket(
             InetAddress srcAddress, int srcPort, InetAddress dstAddress, int dstPort)
@@ -87,7 +91,7 @@
     }
 
     /** Write to parcel */
-    public void writeToParcel(Parcel out, int flags) {
+    public void writeToParcel(@NonNull Parcel out, int flags) {
         out.writeString(srcAddress.getHostAddress());
         out.writeString(dstAddress.getHostAddress());
         out.writeInt(srcPort);
@@ -95,7 +99,7 @@
     }
 
     /** Parcelable Creator */
-    public static final Parcelable.Creator<NattKeepalivePacketData> CREATOR =
+    public static final @NonNull Parcelable.Creator<NattKeepalivePacketData> CREATOR =
             new Parcelable.Creator<NattKeepalivePacketData>() {
                 public NattKeepalivePacketData createFromParcel(Parcel in) {
                     final InetAddress srcAddress =
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index 60bd573..7cc569a 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -17,6 +17,8 @@
 package android.net;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.Build;
@@ -31,6 +33,7 @@
 import com.android.internal.util.Protocol;
 
 import java.util.ArrayList;
+import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -43,22 +46,40 @@
  *
  * @hide
  */
-public abstract class NetworkAgent extends Handler {
-    // Guaranteed to be valid (not NETID_UNSET), otherwise registerNetworkAgent() would have thrown
-    // an exception.
-    public final int netId;
+@SystemApi
+public abstract class NetworkAgent {
+    /**
+     * The {@link Network} corresponding to this object.
+     */
+    @Nullable
+    private volatile Network mNetwork;
 
+    // Whether this NetworkAgent is using the legacy (never unhidden) API. The difference is
+    // that the legacy API uses NetworkInfo to convey the state, while the current API is
+    // exposing methods to manage it and generate it internally instead.
+    // TODO : remove this as soon as all agents have been converted.
+    private final boolean mIsLegacy;
+
+    private final Handler mHandler;
     private volatile AsyncChannel mAsyncChannel;
     private final String LOG_TAG;
     private static final boolean DBG = true;
     private static final boolean VDBG = false;
-    private final Context mContext;
     private final ArrayList<Message>mPreConnectedQueue = new ArrayList<Message>();
     private volatile long mLastBwRefreshTime = 0;
     private static final long BW_REFRESH_MIN_WIN_MS = 500;
-    private boolean mPollLceScheduled = false;
-    private AtomicBoolean mPollLcePending = new AtomicBoolean(false);
-    public final int mFactorySerialNumber;
+    private boolean mBandwidthUpdateScheduled = false;
+    private AtomicBoolean mBandwidthUpdatePending = new AtomicBoolean(false);
+    // Not used by legacy agents. Non-legacy agents use this to convert the NetworkAgent system API
+    // into the internal API of ConnectivityService.
+    @NonNull
+    private NetworkInfo mNetworkInfo;
+
+    /**
+     * The ID of the {@link NetworkProvider} that created this object, or
+     * {@link NetworkProvider#ID_NONE} if unknown.
+     */
+    public final int providerId;
 
     private static final int BASE = Protocol.BASE_NETWORK_AGENT;
 
@@ -66,6 +87,7 @@
      * Sent by ConnectivityService to the NetworkAgent to inform it of
      * suspected connectivity problems on its network.  The NetworkAgent
      * should take steps to verify and correct connectivity.
+     * @hide
      */
     public static final int CMD_SUSPECT_BAD = BASE;
 
@@ -74,6 +96,7 @@
      * ConnectivityService to pass the current NetworkInfo (connection state).
      * Sent when the NetworkInfo changes, mainly due to change of state.
      * obj = NetworkInfo
+     * @hide
      */
     public static final int EVENT_NETWORK_INFO_CHANGED = BASE + 1;
 
@@ -81,6 +104,7 @@
      * Sent by the NetworkAgent to ConnectivityService to pass the current
      * NetworkCapabilties.
      * obj = NetworkCapabilities
+     * @hide
      */
     public static final int EVENT_NETWORK_CAPABILITIES_CHANGED = BASE + 2;
 
@@ -88,11 +112,14 @@
      * Sent by the NetworkAgent to ConnectivityService to pass the current
      * NetworkProperties.
      * obj = NetworkProperties
+     * @hide
      */
     public static final int EVENT_NETWORK_PROPERTIES_CHANGED = BASE + 3;
 
-    /* centralize place where base network score, and network score scaling, will be
+    /**
+     * Centralize the place where base network score, and network score scaling, will be
      * stored, so as we can consistently compare apple and oranges, or wifi, ethernet and LTE
+     * @hide
      */
     public static final int WIFI_BASE_SCORE = 60;
 
@@ -100,6 +127,7 @@
      * Sent by the NetworkAgent to ConnectivityService to pass the current
      * network score.
      * obj = network score Integer
+     * @hide
      */
     public static final int EVENT_NETWORK_SCORE_CHANGED = BASE + 4;
 
@@ -112,12 +140,33 @@
      * obj = Bundle containing map from {@code REDIRECT_URL_KEY} to {@code String}
      *       representing URL that Internet probe was redirect to, if it was redirected,
      *       or mapping to {@code null} otherwise.
+     * @hide
      */
     public static final int CMD_REPORT_NETWORK_STATUS = BASE + 7;
 
+
+    /**
+     * Network validation suceeded.
+     * Corresponds to {@link NetworkCapabilities.NET_CAPABILITY_VALIDATED}.
+     */
+    public static final int VALIDATION_STATUS_VALID = 1;
+
+    /**
+     * Network validation was attempted and failed. This may be received more than once as
+     * subsequent validation attempts are made.
+     */
+    public static final int VALIDATION_STATUS_NOT_VALID = 2;
+
+    // TODO: remove.
+    /** @hide */
     public static final int VALID_NETWORK = 1;
+    /** @hide */
     public static final int INVALID_NETWORK = 2;
 
+    /**
+     * The key for the redirect URL in the Bundle argument of {@code CMD_REPORT_NETWORK_STATUS}.
+     * @hide
+     */
     public static String REDIRECT_URL_KEY = "redirect URL";
 
      /**
@@ -126,6 +175,7 @@
      * CONNECTED so it can be given special treatment at that time.
      *
      * obj = boolean indicating whether to use this network even if unvalidated
+     * @hide
      */
     public static final int EVENT_SET_EXPLICITLY_SELECTED = BASE + 8;
 
@@ -136,12 +186,14 @@
      * responsibility to remember it.
      *
      * arg1 = 1 if true, 0 if false
+     * @hide
      */
     public static final int CMD_SAVE_ACCEPT_UNVALIDATED = BASE + 9;
 
     /**
      * Sent by ConnectivityService to the NetworkAgent to inform the agent to pull
      * the underlying network connection for updated bandwidth information.
+     * @hide
      */
     public static final int CMD_REQUEST_BANDWIDTH_UPDATE = BASE + 10;
 
@@ -154,6 +206,7 @@
      *   obj = KeepalivePacketData object describing the data to be sent
      *
      * Also used internally by ConnectivityService / KeepaliveTracker, with different semantics.
+     * @hide
      */
     public static final int CMD_START_SOCKET_KEEPALIVE = BASE + 11;
 
@@ -163,6 +216,7 @@
      * arg1 = slot number of the keepalive to stop.
      *
      * Also used internally by ConnectivityService / KeepaliveTracker, with different semantics.
+     * @hide
      */
     public static final int CMD_STOP_SOCKET_KEEPALIVE = BASE + 12;
 
@@ -176,6 +230,7 @@
      *
      * arg1 = slot number of the keepalive
      * arg2 = error code
+     * @hide
      */
     public static final int EVENT_SOCKET_KEEPALIVE = BASE + 13;
 
@@ -184,6 +239,7 @@
      * that when crossed should trigger a system wakeup and a NetworkCapabilities update.
      *
      *   obj = int[] describing signal strength thresholds.
+     * @hide
      */
     public static final int CMD_SET_SIGNAL_STRENGTH_THRESHOLDS = BASE + 14;
 
@@ -191,6 +247,7 @@
      * Sent by ConnectivityService to the NeworkAgent to inform the agent to avoid
      * automatically reconnecting to this network (e.g. via autojoin).  Happens
      * when user selects "No" option on the "Stay connected?" dialog box.
+     * @hide
      */
     public static final int CMD_PREVENT_AUTOMATIC_RECONNECT = BASE + 15;
 
@@ -203,6 +260,7 @@
      * This does not happen with UDP, so this message is TCP-specific.
      * arg1 = slot number of the keepalive to filter for.
      * obj = the keepalive packet to send repeatedly.
+     * @hide
      */
     public static final int CMD_ADD_KEEPALIVE_PACKET_FILTER = BASE + 16;
 
@@ -210,156 +268,250 @@
      * Sent by the KeepaliveTracker to NetworkAgent to remove a packet filter. See
      * {@link #CMD_ADD_KEEPALIVE_PACKET_FILTER}.
      * arg1 = slot number of the keepalive packet filter to remove.
+     * @hide
      */
     public static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER = BASE + 17;
 
-    // TODO : remove these two constructors. They are a stopgap measure to help sheperding a number
-    // of dependent changes that would conflict throughout the automerger graph. Having these
-    // temporarily helps with the process of going through with all these dependent changes across
-    // the entire tree.
+    /** @hide TODO: remove and replace usage with the public constructor. */
     public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
             NetworkCapabilities nc, LinkProperties lp, int score) {
-        this(looper, context, logTag, ni, nc, lp, score, null, NetworkFactory.SerialNumber.NONE);
-    }
-    public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
-            NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc) {
-        this(looper, context, logTag, ni, nc, lp, score, misc, NetworkFactory.SerialNumber.NONE);
+        this(looper, context, logTag, ni, nc, lp, score, null, NetworkProvider.ID_NONE);
+        // Register done by the constructor called in the previous line
     }
 
+    /** @hide TODO: remove and replace usage with the public constructor. */
     public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
-            NetworkCapabilities nc, LinkProperties lp, int score, int factorySerialNumber) {
-        this(looper, context, logTag, ni, nc, lp, score, null, factorySerialNumber);
+            NetworkCapabilities nc, LinkProperties lp, int score, NetworkAgentConfig config) {
+        this(looper, context, logTag, ni, nc, lp, score, config, NetworkProvider.ID_NONE);
+        // Register done by the constructor called in the previous line
     }
 
+    /** @hide TODO: remove and replace usage with the public constructor. */
     public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
-            NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc,
-            int factorySerialNumber) {
-        super(looper);
+            NetworkCapabilities nc, LinkProperties lp, int score, int providerId) {
+        this(looper, context, logTag, ni, nc, lp, score, null, providerId);
+        // Register done by the constructor called in the previous line
+    }
+
+    /** @hide TODO: remove and replace usage with the public constructor. */
+    public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
+            NetworkCapabilities nc, LinkProperties lp, int score, NetworkAgentConfig config,
+            int providerId) {
+        this(looper, context, logTag, nc, lp, score, config, providerId, ni, true /* legacy */);
+        register();
+    }
+
+    private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) {
+        // The subtype can be changed with (TODO) setLegacySubtype, but it starts
+        // with the type and an empty description.
+        final NetworkInfo ni = new NetworkInfo(config.legacyType, config.legacyType,
+                config.legacyTypeName, "");
+        ni.setIsAvailable(true);
+        return ni;
+    }
+
+    /**
+     * Create a new network agent.
+     * @param context a {@link Context} to get system services from.
+     * @param looper the {@link Looper} on which to invoke the callbacks.
+     * @param logTag the tag for logs
+     * @param nc the initial {@link NetworkCapabilities} of this network. Update with
+     *           sendNetworkCapabilities.
+     * @param lp the initial {@link LinkProperties} of this network. Update with sendLinkProperties.
+     * @param score the initial score of this network. Update with sendNetworkScore.
+     * @param config an immutable {@link NetworkAgentConfig} for this agent.
+     * @param provider the {@link NetworkProvider} managing this agent.
+     */
+    public NetworkAgent(@NonNull Context context, @NonNull Looper looper, @NonNull String logTag,
+            @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp, int score,
+            @NonNull NetworkAgentConfig config, @Nullable NetworkProvider provider) {
+        this(looper, context, logTag, nc, lp, score, config,
+                provider == null ? NetworkProvider.ID_NONE : provider.getProviderId(),
+                getLegacyNetworkInfo(config), false /* legacy */);
+    }
+
+    private static class InitialConfiguration {
+        public final Context context;
+        public final NetworkCapabilities capabilities;
+        public final LinkProperties properties;
+        public final int score;
+        public final NetworkAgentConfig config;
+        public final NetworkInfo info;
+        InitialConfiguration(@NonNull Context context, @NonNull NetworkCapabilities capabilities,
+                @NonNull LinkProperties properties, int score, @NonNull NetworkAgentConfig config,
+                @NonNull NetworkInfo info) {
+            this.context = context;
+            this.capabilities = capabilities;
+            this.properties = properties;
+            this.score = score;
+            this.config = config;
+            this.info = info;
+        }
+    }
+    private volatile InitialConfiguration mInitialConfiguration;
+
+    private NetworkAgent(@NonNull Looper looper, @NonNull Context context, @NonNull String logTag,
+            @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp, int score,
+            @NonNull NetworkAgentConfig config, int providerId, @NonNull NetworkInfo ni,
+            boolean legacy) {
+        mHandler = new NetworkAgentHandler(looper);
         LOG_TAG = logTag;
-        mContext = context;
-        mFactorySerialNumber = factorySerialNumber;
+        mIsLegacy = legacy;
+        mNetworkInfo = new NetworkInfo(ni);
+        this.providerId = providerId;
         if (ni == null || nc == null || lp == null) {
             throw new IllegalArgumentException();
         }
 
-        if (VDBG) log("Registering NetworkAgent");
-        ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(
-                Context.CONNECTIVITY_SERVICE);
-        netId = cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(ni),
-                new LinkProperties(lp), new NetworkCapabilities(nc), score, misc,
-                factorySerialNumber);
+        mInitialConfiguration = new InitialConfiguration(context, new NetworkCapabilities(nc),
+                new LinkProperties(lp), score, config, ni);
     }
 
-    @Override
-    public void handleMessage(Message msg) {
-        switch (msg.what) {
-            case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
-                if (mAsyncChannel != null) {
-                    log("Received new connection while already connected!");
-                } else {
-                    if (VDBG) log("NetworkAgent fully connected");
-                    AsyncChannel ac = new AsyncChannel();
-                    ac.connected(null, this, msg.replyTo);
-                    ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
-                            AsyncChannel.STATUS_SUCCESSFUL);
-                    synchronized (mPreConnectedQueue) {
-                        mAsyncChannel = ac;
-                        for (Message m : mPreConnectedQueue) {
-                            ac.sendMessage(m);
-                        }
-                        mPreConnectedQueue.clear();
-                    }
-                }
-                break;
-            }
-            case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
-                if (VDBG) log("CMD_CHANNEL_DISCONNECT");
-                if (mAsyncChannel != null) mAsyncChannel.disconnect();
-                break;
-            }
-            case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
-                if (DBG) log("NetworkAgent channel lost");
-                // let the client know CS is done with us.
-                unwanted();
-                synchronized (mPreConnectedQueue) {
-                    mAsyncChannel = null;
-                }
-                break;
-            }
-            case CMD_SUSPECT_BAD: {
-                log("Unhandled Message " + msg);
-                break;
-            }
-            case CMD_REQUEST_BANDWIDTH_UPDATE: {
-                long currentTimeMs = System.currentTimeMillis();
-                if (VDBG) {
-                    log("CMD_REQUEST_BANDWIDTH_UPDATE request received.");
-                }
-                if (currentTimeMs >= (mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS)) {
-                    mPollLceScheduled = false;
-                    if (mPollLcePending.getAndSet(true) == false) {
-                        pollLceData();
-                    }
-                } else {
-                    // deliver the request at a later time rather than discard it completely.
-                    if (!mPollLceScheduled) {
-                        long waitTime = mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS -
-                                currentTimeMs + 1;
-                        mPollLceScheduled = sendEmptyMessageDelayed(
-                                CMD_REQUEST_BANDWIDTH_UPDATE, waitTime);
-                    }
-                }
-                break;
-            }
-            case CMD_REPORT_NETWORK_STATUS: {
-                String redirectUrl = ((Bundle)msg.obj).getString(REDIRECT_URL_KEY);
-                if (VDBG) {
-                    log("CMD_REPORT_NETWORK_STATUS(" +
-                            (msg.arg1 == VALID_NETWORK ? "VALID, " : "INVALID, ") + redirectUrl);
-                }
-                networkStatus(msg.arg1, redirectUrl);
-                break;
-            }
-            case CMD_SAVE_ACCEPT_UNVALIDATED: {
-                saveAcceptUnvalidated(msg.arg1 != 0);
-                break;
-            }
-            case CMD_START_SOCKET_KEEPALIVE: {
-                startSocketKeepalive(msg);
-                break;
-            }
-            case CMD_STOP_SOCKET_KEEPALIVE: {
-                stopSocketKeepalive(msg);
-                break;
-            }
+    private class NetworkAgentHandler extends Handler {
+        NetworkAgentHandler(Looper looper) {
+            super(looper);
+        }
 
-            case CMD_SET_SIGNAL_STRENGTH_THRESHOLDS: {
-                ArrayList<Integer> thresholds =
-                        ((Bundle) msg.obj).getIntegerArrayList("thresholds");
-                // TODO: Change signal strength thresholds API to use an ArrayList<Integer>
-                // rather than convert to int[].
-                int[] intThresholds = new int[(thresholds != null) ? thresholds.size() : 0];
-                for (int i = 0; i < intThresholds.length; i++) {
-                    intThresholds[i] = thresholds.get(i);
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
+                    if (mAsyncChannel != null) {
+                        log("Received new connection while already connected!");
+                    } else {
+                        if (VDBG) log("NetworkAgent fully connected");
+                        AsyncChannel ac = new AsyncChannel();
+                        ac.connected(null, this, msg.replyTo);
+                        ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
+                                AsyncChannel.STATUS_SUCCESSFUL);
+                        synchronized (mPreConnectedQueue) {
+                            mAsyncChannel = ac;
+                            for (Message m : mPreConnectedQueue) {
+                                ac.sendMessage(m);
+                            }
+                            mPreConnectedQueue.clear();
+                        }
+                    }
+                    break;
                 }
-                setSignalStrengthThresholds(intThresholds);
-                break;
-            }
-            case CMD_PREVENT_AUTOMATIC_RECONNECT: {
-                preventAutomaticReconnect();
-                break;
-            }
-            case CMD_ADD_KEEPALIVE_PACKET_FILTER: {
-                addKeepalivePacketFilter(msg);
-                break;
-            }
-            case CMD_REMOVE_KEEPALIVE_PACKET_FILTER: {
-                removeKeepalivePacketFilter(msg);
-                break;
+                case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
+                    if (VDBG) log("CMD_CHANNEL_DISCONNECT");
+                    if (mAsyncChannel != null) mAsyncChannel.disconnect();
+                    break;
+                }
+                case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
+                    if (DBG) log("NetworkAgent channel lost");
+                    // let the client know CS is done with us.
+                    onNetworkUnwanted();
+                    synchronized (mPreConnectedQueue) {
+                        mAsyncChannel = null;
+                    }
+                    break;
+                }
+                case CMD_SUSPECT_BAD: {
+                    log("Unhandled Message " + msg);
+                    break;
+                }
+                case CMD_REQUEST_BANDWIDTH_UPDATE: {
+                    long currentTimeMs = System.currentTimeMillis();
+                    if (VDBG) {
+                        log("CMD_REQUEST_BANDWIDTH_UPDATE request received.");
+                    }
+                    if (currentTimeMs >= (mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS)) {
+                        mBandwidthUpdateScheduled = false;
+                        if (!mBandwidthUpdatePending.getAndSet(true)) {
+                            onBandwidthUpdateRequested();
+                        }
+                    } else {
+                        // deliver the request at a later time rather than discard it completely.
+                        if (!mBandwidthUpdateScheduled) {
+                            long waitTime = mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS
+                                    - currentTimeMs + 1;
+                            mBandwidthUpdateScheduled = sendEmptyMessageDelayed(
+                                    CMD_REQUEST_BANDWIDTH_UPDATE, waitTime);
+                        }
+                    }
+                    break;
+                }
+                case CMD_REPORT_NETWORK_STATUS: {
+                    String redirectUrl = ((Bundle) msg.obj).getString(REDIRECT_URL_KEY);
+                    if (VDBG) {
+                        log("CMD_REPORT_NETWORK_STATUS("
+                                + (msg.arg1 == VALID_NETWORK ? "VALID, " : "INVALID, ")
+                                + redirectUrl);
+                    }
+                    onValidationStatus(msg.arg1 /* status */, redirectUrl);
+                    break;
+                }
+                case CMD_SAVE_ACCEPT_UNVALIDATED: {
+                    onSaveAcceptUnvalidated(msg.arg1 != 0);
+                    break;
+                }
+                case CMD_START_SOCKET_KEEPALIVE: {
+                    onStartSocketKeepalive(msg.arg1 /* slot */, msg.arg2 /* interval */,
+                            (KeepalivePacketData) msg.obj /* packet */);
+                    break;
+                }
+                case CMD_STOP_SOCKET_KEEPALIVE: {
+                    onStopSocketKeepalive(msg.arg1 /* slot */);
+                    break;
+                }
+
+                case CMD_SET_SIGNAL_STRENGTH_THRESHOLDS: {
+                    ArrayList<Integer> thresholds =
+                            ((Bundle) msg.obj).getIntegerArrayList("thresholds");
+                    // TODO: Change signal strength thresholds API to use an ArrayList<Integer>
+                    // rather than convert to int[].
+                    int[] intThresholds = new int[(thresholds != null) ? thresholds.size() : 0];
+                    for (int i = 0; i < intThresholds.length; i++) {
+                        intThresholds[i] = thresholds.get(i);
+                    }
+                    onSignalStrengthThresholdsUpdated(intThresholds);
+                    break;
+                }
+                case CMD_PREVENT_AUTOMATIC_RECONNECT: {
+                    onAutomaticReconnectDisabled();
+                    break;
+                }
+                case CMD_ADD_KEEPALIVE_PACKET_FILTER: {
+                    onAddKeepalivePacketFilter(msg.arg1 /* slot */,
+                            (KeepalivePacketData) msg.obj /* packet */);
+                    break;
+                }
+                case CMD_REMOVE_KEEPALIVE_PACKET_FILTER: {
+                    onRemoveKeepalivePacketFilter(msg.arg1 /* slot */);
+                    break;
+                }
             }
         }
     }
 
+    /**
+     * Register this network agent with ConnectivityService.
+     * @return the Network associated with this network agent (which can also be obtained later
+     *         by calling getNetwork() on this agent).
+     */
+    @NonNull
+    public Network register() {
+        if (VDBG) log("Registering NetworkAgent");
+        final ConnectivityManager cm = (ConnectivityManager) mInitialConfiguration.context
+                .getSystemService(Context.CONNECTIVITY_SERVICE);
+        mNetwork = cm.registerNetworkAgent(new Messenger(mHandler),
+                new NetworkInfo(mInitialConfiguration.info),
+                mInitialConfiguration.properties, mInitialConfiguration.capabilities,
+                mInitialConfiguration.score, mInitialConfiguration.config, providerId);
+        mInitialConfiguration = null; // All this memory can now be GC'd
+        return mNetwork;
+    }
+
+    /**
+     * @return The Network associated with this agent, or null if it's not registered yet.
+     */
+    @Nullable
+    public Network getNetwork() {
+        return mNetwork;
+    }
+
     private void queueOrSendMessage(int what, Object obj) {
         queueOrSendMessage(what, 0, 0, obj);
     }
@@ -388,32 +540,111 @@
     }
 
     /**
-     * Called by the bearer code when it has new LinkProperties data.
+     * Must be called by the agent when the network's {@link LinkProperties} change.
+     * @param linkProperties the new LinkProperties.
      */
-    public void sendLinkProperties(LinkProperties linkProperties) {
+    public void sendLinkProperties(@NonNull LinkProperties linkProperties) {
+        Objects.requireNonNull(linkProperties);
         queueOrSendMessage(EVENT_NETWORK_PROPERTIES_CHANGED, new LinkProperties(linkProperties));
     }
 
     /**
-     * Called by the bearer code when it has new NetworkInfo data.
+     * Inform ConnectivityService that this agent has now connected.
+     */
+    public void setConnected() {
+        if (mIsLegacy) {
+            throw new UnsupportedOperationException(
+                    "Legacy agents can't call setConnected.");
+        }
+        mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null, null);
+        queueOrSendMessage(EVENT_NETWORK_INFO_CHANGED, mNetworkInfo);
+    }
+
+    /**
+     * Unregister this network agent.
+     *
+     * This signals the network has disconnected and ends its lifecycle. After this is called,
+     * the network is torn down and this agent can no longer be used.
+     */
+    public void unregister() {
+        if (mIsLegacy) {
+            throw new UnsupportedOperationException(
+                    "Legacy agents can't call unregister.");
+        }
+        mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, null);
+        queueOrSendMessage(EVENT_NETWORK_INFO_CHANGED, mNetworkInfo);
+    }
+
+    /**
+     * Change the legacy subtype of this network agent.
+     *
+     * This is only for backward compatibility and should not be used by non-legacy network agents,
+     * or agents that did not use to set a subtype. As such, only TYPE_MOBILE type agents can use
+     * this and others will be thrown an exception if they try.
+     *
+     * @deprecated this is for backward compatibility only.
+     * @param legacySubtype the legacy subtype.
+     */
+    @Deprecated
+    public void setLegacySubtype(final int legacySubtype, @NonNull final String legacySubtypeName) {
+        if (mIsLegacy) {
+            throw new UnsupportedOperationException("Legacy agents can't call setLegacySubtype.");
+        }
+        mNetworkInfo.setSubtype(legacySubtype, legacySubtypeName);
+        queueOrSendMessage(EVENT_NETWORK_INFO_CHANGED, mNetworkInfo);
+    }
+
+    /**
+     * Set the ExtraInfo of this network agent.
+     *
+     * This sets the ExtraInfo field inside the NetworkInfo returned by legacy public API and the
+     * broadcasts about the corresponding Network.
+     * This is only for backward compatibility and should not be used by non-legacy network agents,
+     * who will be thrown an exception if they try. The extra info should only be :
+     * <ul>
+     *   <li>For cellular agents, the APN name.</li>
+     *   <li>For ethernet agents, the interface name.</li>
+     * </ul>
+     *
+     * @deprecated this is for backward compatibility only.
+     * @param extraInfo the ExtraInfo.
+     */
+    @Deprecated
+    public void setLegacyExtraInfo(@Nullable final String extraInfo) {
+        if (mIsLegacy) {
+            throw new UnsupportedOperationException("Legacy agents can't call setLegacyExtraInfo.");
+        }
+        mNetworkInfo.setExtraInfo(extraInfo);
+        queueOrSendMessage(EVENT_NETWORK_INFO_CHANGED, mNetworkInfo);
+    }
+
+    /**
+     * Must be called by the agent when it has a new NetworkInfo object.
+     * @hide TODO: expose something better.
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public void sendNetworkInfo(NetworkInfo networkInfo) {
+        if (!mIsLegacy) {
+            throw new UnsupportedOperationException("Only legacy agents can call sendNetworkInfo.");
+        }
         queueOrSendMessage(EVENT_NETWORK_INFO_CHANGED, new NetworkInfo(networkInfo));
     }
 
     /**
-     * Called by the bearer code when it has new NetworkCapabilities data.
+     * Must be called by the agent when the network's {@link NetworkCapabilities} change.
+     * @param networkCapabilities the new NetworkCapabilities.
      */
-    public void sendNetworkCapabilities(NetworkCapabilities networkCapabilities) {
-        mPollLcePending.set(false);
+    public void sendNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) {
+        Objects.requireNonNull(networkCapabilities);
+        mBandwidthUpdatePending.set(false);
         mLastBwRefreshTime = System.currentTimeMillis();
         queueOrSendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED,
                 new NetworkCapabilities(networkCapabilities));
     }
 
     /**
-     * Called by the bearer code when it has a new score for this network.
+     * Must be called by the agent to update the score of this network.
+     * @param score the new score.
      */
     public void sendNetworkScore(int score) {
         if (score < 0) {
@@ -425,14 +656,16 @@
     }
 
     /**
-     * Called by the bearer code when it has a new NetworkScore for this network.
+     * Must be called by the agent when it has a new {@link NetworkScore} for this network.
+     * @param ns the new score.
+     * @hide TODO: unhide the NetworkScore class, and rename to sendNetworkScore.
      */
     public void updateScore(@NonNull NetworkScore ns) {
         queueOrSendMessage(EVENT_NETWORK_SCORE_CHANGED, new NetworkScore(ns));
     }
 
     /**
-     * Called by the bearer to indicate this network was manually selected by the user.
+     * Must be called by the agent to indicate this network was manually selected by the user.
      * This should be called before the NetworkInfo is marked CONNECTED so that this
      * Network can be given special treatment at that time. If {@code acceptUnvalidated} is
      * {@code true}, then the system will switch to this network. If it is {@code false} and the
@@ -441,15 +674,16 @@
      * {@link #saveAcceptUnvalidated} to persist the user's choice. Thus, if the transport ever
      * calls this method with {@code acceptUnvalidated} set to {@code false}, it must also implement
      * {@link #saveAcceptUnvalidated} to respect the user's choice.
+     * @hide should move to NetworkAgentConfig.
      */
     public void explicitlySelected(boolean acceptUnvalidated) {
         explicitlySelected(true /* explicitlySelected */, acceptUnvalidated);
     }
 
     /**
-     * Called by the bearer to indicate whether the network was manually selected by the user.
-     * This should be called before the NetworkInfo is marked CONNECTED so that this
-     * Network can be given special treatment at that time.
+     * Must be called by the agent to indicate whether the network was manually selected by the
+     * user. This should be called before the network becomes connected, so it can be given
+     * special treatment when it does.
      *
      * If {@code explicitlySelected} is {@code true}, and {@code acceptUnvalidated} is {@code true},
      * then the system will switch to this network. If {@code explicitlySelected} is {@code true}
@@ -464,6 +698,7 @@
      * {@code true}, the system will interpret this as the user having accepted partial connectivity
      * on this network. Thus, the system will switch to the network and consider it validated even
      * if it only provides partial connectivity, but the network is not otherwise treated specially.
+     * @hide should move to NetworkAgentConfig.
      */
     public void explicitlySelected(boolean explicitlySelected, boolean acceptUnvalidated) {
         queueOrSendMessage(EVENT_SET_EXPLICITLY_SELECTED,
@@ -477,73 +712,126 @@
      * as well, either canceling NetworkRequests or altering their score such that this
      * network won't be immediately requested again.
      */
-    abstract protected void unwanted();
+    public void onNetworkUnwanted() {
+        unwanted();
+    }
+    /** @hide TODO delete once subclasses have moved to onNetworkUnwanted. */
+    protected void unwanted() {
+    }
 
     /**
      * Called when ConnectivityService request a bandwidth update. The parent factory
      * shall try to overwrite this method and produce a bandwidth update if capable.
      */
+    public void onBandwidthUpdateRequested() {
+        pollLceData();
+    }
+    /** @hide TODO delete once subclasses have moved to onBandwidthUpdateRequested. */
     protected void pollLceData() {
     }
 
     /**
      * Called when the system determines the usefulness of this network.
      *
-     * Networks claiming internet connectivity will have their internet
-     * connectivity verified.
+     * The system attempts to validate Internet connectivity on networks that provide the
+     * {@link NetworkCapabilities#NET_CAPABILITY_INTERNET} capability.
      *
      * Currently there are two possible values:
-     * {@code VALID_NETWORK} if the system is happy with the connection,
-     * {@code INVALID_NETWORK} if the system is not happy.
-     * TODO - add indications of captive portal-ness and related success/failure,
-     * ie, CAPTIVE_SUCCESS_NETWORK, CAPTIVE_NETWORK for successful login and detection
+     * {@code VALIDATION_STATUS_VALID} if Internet connectivity was validated,
+     * {@code VALIDATION_STATUS_NOT_VALID} if Internet connectivity was not validated.
      *
-     * This may be called multiple times as the network status changes and may
-     * generate false negatives if we lose ip connectivity before the link is torn down.
+     * This may be called multiple times as network status changes, or if there are multiple
+     * subsequent attempts to validate connectivity that fail.
      *
-     * @param status one of {@code VALID_NETWORK} or {@code INVALID_NETWORK}.
-     * @param redirectUrl If the Internet probe was redirected, this is the destination it was
-     *         redirected to, otherwise {@code null}.
+     * @param status one of {@code VALIDATION_STATUS_VALID} or {@code VALIDATION_STATUS_NOT_VALID}.
+     * @param redirectUrl If Internet connectivity is being redirected (e.g., on a captive portal),
+     *        this is the destination the probes are being redirected to, otherwise {@code null}.
      */
+    public void onValidationStatus(int status, @Nullable String redirectUrl) {
+        networkStatus(status, redirectUrl);
+    }
+    /** @hide TODO delete once subclasses have moved to onValidationStatus */
     protected void networkStatus(int status, String redirectUrl) {
     }
 
+
     /**
      * Called when the user asks to remember the choice to use this network even if unvalidated.
      * The transport is responsible for remembering the choice, and the next time the user connects
      * to the network, should explicitlySelected with {@code acceptUnvalidated} set to {@code true}.
      * This method will only be called if {@link #explicitlySelected} was called with
      * {@code acceptUnvalidated} set to {@code false}.
+     * @param accept whether the user wants to use the network even if unvalidated.
      */
+    public void onSaveAcceptUnvalidated(boolean accept) {
+        saveAcceptUnvalidated(accept);
+    }
+    /** @hide TODO delete once subclasses have moved to onSaveAcceptUnvalidated */
     protected void saveAcceptUnvalidated(boolean accept) {
     }
 
     /**
      * Requests that the network hardware send the specified packet at the specified interval.
+     *
+     * @param slot the hardware slot on which to start the keepalive.
+     * @param intervalSeconds the interval between packets
+     * @param packet the packet to send.
      */
+    public void onStartSocketKeepalive(int slot, int intervalSeconds,
+            @NonNull KeepalivePacketData packet) {
+        Message msg = mHandler.obtainMessage(CMD_START_SOCKET_KEEPALIVE, slot, intervalSeconds,
+                packet);
+        startSocketKeepalive(msg);
+        msg.recycle();
+    }
+    /** @hide TODO delete once subclasses have moved to onStartSocketKeepalive */
     protected void startSocketKeepalive(Message msg) {
         onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED);
     }
 
     /**
-     * Requests that the network hardware send the specified packet at the specified interval.
+     * Requests that the network hardware stop a previously-started keepalive.
+     *
+     * @param slot the hardware slot on which to stop the keepalive.
      */
+    public void onStopSocketKeepalive(int slot) {
+        Message msg = mHandler.obtainMessage(CMD_STOP_SOCKET_KEEPALIVE, slot, 0, null);
+        stopSocketKeepalive(msg);
+        msg.recycle();
+    }
+    /** @hide TODO delete once subclasses have moved to onStopSocketKeepalive */
     protected void stopSocketKeepalive(Message msg) {
         onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED);
     }
 
     /**
-     * Called by the network when a socket keepalive event occurs.
+     * Must be called by the agent when a socket keepalive event occurs.
+     *
+     * @param slot the hardware slot on which the event occurred.
+     * @param event the event that occurred.
      */
+    public void sendSocketKeepaliveEvent(int slot, int event) {
+        queueOrSendMessage(EVENT_SOCKET_KEEPALIVE, slot, event);
+    }
+    /** @hide TODO delete once callers have moved to sendSocketKeepaliveEvent */
     public void onSocketKeepaliveEvent(int slot, int reason) {
-        queueOrSendMessage(EVENT_SOCKET_KEEPALIVE, slot, reason);
+        sendSocketKeepaliveEvent(slot, reason);
     }
 
     /**
      * Called by ConnectivityService to add specific packet filter to network hardware to block
-     * ACKs matching the sent keepalive packets. Implementations that support this feature must
-     * override this method.
+     * replies (e.g., TCP ACKs) matching the sent keepalive packets. Implementations that support
+     * this feature must override this method.
+     *
+     * @param slot the hardware slot on which the keepalive should be sent.
+     * @param packet the packet that is being sent.
      */
+    public void onAddKeepalivePacketFilter(int slot, @NonNull KeepalivePacketData packet) {
+        Message msg = mHandler.obtainMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER, slot, 0, packet);
+        addKeepalivePacketFilter(msg);
+        msg.recycle();
+    }
+    /** @hide TODO delete once subclasses have moved to onAddKeepalivePacketFilter */
     protected void addKeepalivePacketFilter(Message msg) {
     }
 
@@ -551,14 +839,28 @@
      * Called by ConnectivityService to remove a packet filter installed with
      * {@link #addKeepalivePacketFilter(Message)}. Implementations that support this feature
      * must override this method.
+     *
+     * @param slot the hardware slot on which the keepalive is being sent.
      */
+    public void onRemoveKeepalivePacketFilter(int slot) {
+        Message msg = mHandler.obtainMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER, slot, 0, null);
+        removeKeepalivePacketFilter(msg);
+        msg.recycle();
+    }
+    /** @hide TODO delete once subclasses have moved to onRemoveKeepalivePacketFilter */
     protected void removeKeepalivePacketFilter(Message msg) {
     }
 
     /**
      * Called by ConnectivityService to inform this network transport of signal strength thresholds
      * that when crossed should trigger a system wakeup and a NetworkCapabilities update.
+     *
+     * @param thresholds the array of thresholds that should trigger wakeups.
      */
+    public void onSignalStrengthThresholdsUpdated(@NonNull int[] thresholds) {
+        setSignalStrengthThresholds(thresholds);
+    }
+    /** @hide TODO delete once subclasses have moved to onSetSignalStrengthThresholds */
     protected void setSignalStrengthThresholds(int[] thresholds) {
     }
 
@@ -568,9 +870,14 @@
      * responsible for making sure the device does not automatically reconnect to the same network
      * after the {@code unwanted} call.
      */
+    public void onAutomaticReconnectDisabled() {
+        preventAutomaticReconnect();
+    }
+    /** @hide TODO delete once subclasses have moved to onAutomaticReconnectDisabled */
     protected void preventAutomaticReconnect() {
     }
 
+    /** @hide */
     protected void log(String s) {
         Log.d(LOG_TAG, "NetworkAgent: " + s);
     }
diff --git a/core/java/android/net/NetworkAgentConfig.aidl b/core/java/android/net/NetworkAgentConfig.aidl
new file mode 100644
index 0000000..cb70bdd
--- /dev/null
+++ b/core/java/android/net/NetworkAgentConfig.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2014 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;
+
+parcelable NetworkAgentConfig;
diff --git a/core/java/android/net/NetworkAgentConfig.java b/core/java/android/net/NetworkAgentConfig.java
new file mode 100644
index 0000000..7e2db4a
--- /dev/null
+++ b/core/java/android/net/NetworkAgentConfig.java
@@ -0,0 +1,392 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Allows a network transport to provide the system with policy and configuration information about
+ * a particular network when registering a {@link NetworkAgent}. This information cannot change once the agent is registered.
+ *
+ * @hide
+ */
+@SystemApi
+public final class NetworkAgentConfig implements Parcelable {
+
+    /**
+     * If the {@link Network} is a VPN, whether apps are allowed to bypass the
+     * VPN. This is set by a {@link VpnService} and used by
+     * {@link ConnectivityManager} when creating a VPN.
+     *
+     * @hide
+     */
+    public boolean allowBypass;
+
+    /**
+     * Set if the network was manually/explicitly connected to by the user either from settings
+     * or a 3rd party app.  For example, turning on cell data is not explicit but tapping on a wifi
+     * ap in the wifi settings to trigger a connection is explicit.  A 3rd party app asking to
+     * connect to a particular access point is also explicit, though this may change in the future
+     * as we want apps to use the multinetwork apis.
+     *
+     * @hide
+     */
+    public boolean explicitlySelected;
+
+    /**
+     * @return whether this network was explicitly selected by the user.
+     */
+    public boolean isExplicitlySelected() {
+        return explicitlySelected;
+    }
+
+    /**
+     * Set if the user desires to use this network even if it is unvalidated. This field has meaning
+     * only if {@link explicitlySelected} is true. If it is, this field must also be set to the
+     * appropriate value based on previous user choice.
+     *
+     * TODO : rename this field to match its accessor
+     * @hide
+     */
+    public boolean acceptUnvalidated;
+
+    /**
+     * @return whether the system should accept this network even if it doesn't validate.
+     */
+    public boolean isUnvalidatedConnectivityAcceptable() {
+        return acceptUnvalidated;
+    }
+
+    /**
+     * Whether the user explicitly set that this network should be validated even if presence of
+     * only partial internet connectivity.
+     *
+     * TODO : rename this field to match its accessor
+     * @hide
+     */
+    public boolean acceptPartialConnectivity;
+
+    /**
+     * @return whether the system should validate this network even if it only offers partial
+     *     Internet connectivity.
+     */
+    public boolean isPartialConnectivityAcceptable() {
+        return acceptPartialConnectivity;
+    }
+
+    /**
+     * Set to avoid surfacing the "Sign in to network" notification.
+     * if carrier receivers/apps are registered to handle the carrier-specific provisioning
+     * procedure, a carrier specific provisioning notification will be placed.
+     * only one notification should be displayed. This field is set based on
+     * which notification should be used for provisioning.
+     *
+     * @hide
+     */
+    public boolean provisioningNotificationDisabled;
+
+    /**
+     *
+     * @return whether the sign in to network notification is enabled by this configuration.
+     */
+    public boolean isProvisioningNotificationEnabled() {
+        return !provisioningNotificationDisabled;
+    }
+
+    /**
+     * For mobile networks, this is the subscriber ID (such as IMSI).
+     *
+     * @hide
+     */
+    public String subscriberId;
+
+    /**
+     * @return the subscriber ID, or null if none.
+     */
+    @Nullable
+    public String getSubscriberId() {
+        return subscriberId;
+    }
+
+    /**
+     * Set to skip 464xlat. This means the device will treat the network as IPv6-only and
+     * will not attempt to detect a NAT64 via RFC 7050 DNS lookups.
+     *
+     * @hide
+     */
+    public boolean skip464xlat;
+
+    /**
+     * @return whether NAT64 prefix detection is enabled.
+     */
+    public boolean isNat64DetectionEnabled() {
+        return !skip464xlat;
+    }
+
+    /**
+     * The legacy type of this network agent, or TYPE_NONE if unset.
+     * @hide
+     */
+    public int legacyType = ConnectivityManager.TYPE_NONE;
+
+    /**
+     * @return the legacy type
+     */
+    public int getLegacyType() {
+        return legacyType;
+    }
+
+    /**
+     * Set to true if the PRIVATE_DNS_BROKEN notification has shown for this network.
+     * Reset this bit when private DNS mode is changed from strict mode to opportunistic/off mode.
+     *
+     * This is not parceled, because it would not make sense.
+     *
+     * @hide
+     */
+    public transient boolean hasShownBroken;
+
+    /**
+     * The name of the legacy network type. It's a free-form string used in logging.
+     * @hide
+     */
+    @NonNull
+    public String legacyTypeName = "";
+
+    /**
+     * @return the name of the legacy network type. It's a free-form string used in logging.
+     */
+    @NonNull
+    public String getLegacyTypeName() {
+        return legacyTypeName;
+    }
+
+    /** @hide */
+    public NetworkAgentConfig() {
+    }
+
+    /** @hide */
+    public NetworkAgentConfig(@Nullable NetworkAgentConfig nac) {
+        if (nac != null) {
+            allowBypass = nac.allowBypass;
+            explicitlySelected = nac.explicitlySelected;
+            acceptUnvalidated = nac.acceptUnvalidated;
+            acceptPartialConnectivity = nac.acceptPartialConnectivity;
+            subscriberId = nac.subscriberId;
+            provisioningNotificationDisabled = nac.provisioningNotificationDisabled;
+            skip464xlat = nac.skip464xlat;
+            legacyType = nac.legacyType;
+            legacyTypeName = nac.legacyTypeName;
+        }
+    }
+
+    /**
+     * Builder class to facilitate constructing {@link NetworkAgentConfig} objects.
+     */
+    public static class Builder {
+        private final NetworkAgentConfig mConfig = new NetworkAgentConfig();
+
+        /**
+         * Sets whether the network was explicitly selected by the user.
+         *
+         * @return this builder, to facilitate chaining.
+         */
+        @NonNull
+        public Builder setExplicitlySelected(final boolean explicitlySelected) {
+            mConfig.explicitlySelected = explicitlySelected;
+            return this;
+        }
+
+        /**
+         * Sets whether the system should validate this network even if it is found not to offer
+         * Internet connectivity.
+         *
+         * @return this builder, to facilitate chaining.
+         */
+        @NonNull
+        public Builder setUnvalidatedConnectivityAcceptable(
+                final boolean unvalidatedConnectivityAcceptable) {
+            mConfig.acceptUnvalidated = unvalidatedConnectivityAcceptable;
+            return this;
+        }
+
+        /**
+         * Sets whether the system should validate this network even if it is found to only offer
+         * partial Internet connectivity.
+         *
+         * @return this builder, to facilitate chaining.
+         */
+        @NonNull
+        public Builder setPartialConnectivityAcceptable(
+                final boolean partialConnectivityAcceptable) {
+            mConfig.acceptPartialConnectivity = partialConnectivityAcceptable;
+            return this;
+        }
+
+        /**
+         * Sets the subscriber ID for this network.
+         *
+         * @return this builder, to facilitate chaining.
+         */
+        @NonNull
+        public Builder setSubscriberId(@Nullable String subscriberId) {
+            mConfig.subscriberId = subscriberId;
+            return this;
+        }
+
+        /**
+         * Disables active detection of NAT64 (e.g., via RFC 7050 DNS lookups). Used to save power
+         * and reduce idle traffic on networks that are known to be IPv6-only without a NAT64.
+         *
+         * @return this builder, to facilitate chaining.
+         */
+        @NonNull
+        public Builder disableNat64Detection() {
+            mConfig.skip464xlat = true;
+            return this;
+        }
+
+        /**
+         * Disables the "Sign in to network" notification. Used if the network transport will
+         * perform its own carrier-specific provisioning procedure.
+         *
+         * @return this builder, to facilitate chaining.
+         */
+        @NonNull
+        public Builder disableProvisioningNotification() {
+            mConfig.provisioningNotificationDisabled = true;
+            return this;
+        }
+
+        /**
+         * Sets the legacy type for this network.
+         *
+         * @param legacyType the type
+         * @return this builder, to facilitate chaining.
+         */
+        @NonNull
+        public Builder setLegacyType(int legacyType) {
+            mConfig.legacyType = legacyType;
+            return this;
+        }
+
+        /**
+         * Sets the name of the legacy type of the agent. It's a free-form string used in logging.
+         * @param legacyTypeName the name
+         * @return this builder, to facilitate chaining.
+         */
+        @NonNull
+        public Builder setLegacyTypeName(@NonNull String legacyTypeName) {
+            mConfig.legacyTypeName = legacyTypeName;
+            return this;
+        }
+
+        /**
+         * Returns the constructed {@link NetworkAgentConfig} object.
+         */
+        @NonNull
+        public NetworkAgentConfig build() {
+            return mConfig;
+        }
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        final NetworkAgentConfig that = (NetworkAgentConfig) o;
+        return allowBypass == that.allowBypass
+                && explicitlySelected == that.explicitlySelected
+                && acceptUnvalidated == that.acceptUnvalidated
+                && acceptPartialConnectivity == that.acceptPartialConnectivity
+                && provisioningNotificationDisabled == that.provisioningNotificationDisabled
+                && skip464xlat == that.skip464xlat
+                && legacyType == that.legacyType
+                && Objects.equals(subscriberId, that.subscriberId)
+                && Objects.equals(legacyTypeName, that.legacyTypeName);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(allowBypass, explicitlySelected, acceptUnvalidated,
+                acceptPartialConnectivity, provisioningNotificationDisabled, subscriberId,
+                skip464xlat, legacyType, legacyTypeName);
+    }
+
+    @Override
+    public String toString() {
+        return "NetworkAgentConfig {"
+                + " allowBypass = " + allowBypass
+                + ", explicitlySelected = " + explicitlySelected
+                + ", acceptUnvalidated = " + acceptUnvalidated
+                + ", acceptPartialConnectivity = " + acceptPartialConnectivity
+                + ", provisioningNotificationDisabled = " + provisioningNotificationDisabled
+                + ", subscriberId = '" + subscriberId + '\''
+                + ", skip464xlat = " + skip464xlat
+                + ", legacyType = " + legacyType
+                + ", hasShownBroken = " + hasShownBroken
+                + ", legacyTypeName = '" + legacyTypeName + '\''
+                + "}";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeInt(allowBypass ? 1 : 0);
+        out.writeInt(explicitlySelected ? 1 : 0);
+        out.writeInt(acceptUnvalidated ? 1 : 0);
+        out.writeInt(acceptPartialConnectivity ? 1 : 0);
+        out.writeString(subscriberId);
+        out.writeInt(provisioningNotificationDisabled ? 1 : 0);
+        out.writeInt(skip464xlat ? 1 : 0);
+        out.writeInt(legacyType);
+        out.writeString(legacyTypeName);
+    }
+
+    public static final @NonNull Creator<NetworkAgentConfig> CREATOR =
+            new Creator<NetworkAgentConfig>() {
+        @Override
+        public NetworkAgentConfig createFromParcel(Parcel in) {
+            NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig();
+            networkAgentConfig.allowBypass = in.readInt() != 0;
+            networkAgentConfig.explicitlySelected = in.readInt() != 0;
+            networkAgentConfig.acceptUnvalidated = in.readInt() != 0;
+            networkAgentConfig.acceptPartialConnectivity = in.readInt() != 0;
+            networkAgentConfig.subscriberId = in.readString();
+            networkAgentConfig.provisioningNotificationDisabled = in.readInt() != 0;
+            networkAgentConfig.skip464xlat = in.readInt() != 0;
+            networkAgentConfig.legacyType = in.readInt();
+            networkAgentConfig.legacyTypeName = in.readString();
+            return networkAgentConfig;
+        }
+
+        @Override
+        public NetworkAgentConfig[] newArray(int size) {
+            return new NetworkAgentConfig[size];
+        }
+    };
+}
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 33c39d4..738070b 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -587,15 +587,14 @@
     }
 
     /**
-     * Removes the NET_CAPABILITY_NOT_RESTRICTED capability if all the capabilities it provides are
-     * typically provided by restricted networks.
+     * Deduces that all the capabilities it provides are typically provided by restricted networks
+     * or not.
      *
-     * TODO: consider:
-     * - Renaming it to guessRestrictedCapability and make it set the
-     *   restricted capability bit in addition to clearing it.
+     * @return {@code true} if the network should be restricted.
      * @hide
      */
-    public void maybeMarkCapabilitiesRestricted() {
+    @SystemApi
+    public boolean deduceRestrictedCapability() {
         // Check if we have any capability that forces the network to be restricted.
         final boolean forceRestrictedCapability =
                 (mNetworkCapabilities & FORCE_RESTRICTED_CAPABILITIES) != 0;
@@ -609,8 +608,17 @@
         final boolean hasRestrictedCapabilities =
                 (mNetworkCapabilities & RESTRICTED_CAPABILITIES) != 0;
 
-        if (forceRestrictedCapability
-                || (hasRestrictedCapabilities && !hasUnrestrictedCapabilities)) {
+        return forceRestrictedCapability
+                || (hasRestrictedCapabilities && !hasUnrestrictedCapabilities);
+    }
+
+    /**
+     * Removes the NET_CAPABILITY_NOT_RESTRICTED capability if deducing the network is restricted.
+     *
+     * @hide
+     */
+    public void maybeMarkCapabilitiesRestricted() {
+        if (deduceRestrictedCapability()) {
             removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
         }
     }
@@ -1275,6 +1283,7 @@
      * Gets the SSID of this network, or null if none or unknown.
      * @hide
      */
+    @SystemApi
     public @Nullable String getSSID() {
         return mSSID;
     }
diff --git a/core/java/android/net/NetworkFactory.java b/core/java/android/net/NetworkFactory.java
deleted file mode 100644
index 42ab925..0000000
--- a/core/java/android/net/NetworkFactory.java
+++ /dev/null
@@ -1,475 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net;
-
-import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Context;
-import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
-import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.AsyncChannel;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.Protocol;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * A NetworkFactory is an entity that creates NetworkAgent objects.
- * The bearers register with ConnectivityService using {@link #register} and
- * their factory will start receiving scored NetworkRequests.  NetworkRequests
- * can be filtered 3 ways: by NetworkCapabilities, by score and more complexly by
- * overridden function.  All of these can be dynamic - changing NetworkCapabilities
- * or score forces re-evaluation of all current requests.
- *
- * If any requests pass the filter some overrideable functions will be called.
- * If the bearer only cares about very simple start/stopNetwork callbacks, those
- * functions can be overridden.  If the bearer needs more interaction, it can
- * override addNetworkRequest and removeNetworkRequest which will give it each
- * request that passes their current filters.
- * @hide
- **/
-public class NetworkFactory extends Handler {
-    /** @hide */
-    public static class SerialNumber {
-        // Guard used by no network factory.
-        public static final int NONE = -1;
-        // A hardcoded serial number for NetworkAgents representing VPNs. These agents are
-        // not created by any factory, so they use this constant for clarity instead of NONE.
-        public static final int VPN = -2;
-        private static final AtomicInteger sNetworkFactorySerialNumber = new AtomicInteger(1);
-        /** Returns a unique serial number for a factory. */
-        public static final int nextSerialNumber() {
-            return sNetworkFactorySerialNumber.getAndIncrement();
-        }
-    }
-
-    private static final boolean DBG = true;
-    private static final boolean VDBG = false;
-
-    private static final int BASE = Protocol.BASE_NETWORK_FACTORY;
-    /**
-     * Pass a network request to the bearer.  If the bearer believes it can
-     * satisfy the request it should connect to the network and create a
-     * NetworkAgent.  Once the NetworkAgent is fully functional it will
-     * register itself with ConnectivityService using registerNetworkAgent.
-     * If the bearer cannot immediately satisfy the request (no network,
-     * user disabled the radio, lower-scored network) it should remember
-     * any NetworkRequests it may be able to satisfy in the future.  It may
-     * disregard any that it will never be able to service, for example
-     * those requiring a different bearer.
-     * msg.obj = NetworkRequest
-     * msg.arg1 = score - the score of the network currently satisfying this
-     *            request.  If this bearer knows in advance it cannot
-     *            exceed this score it should not try to connect, holding the request
-     *            for the future.
-     *            Note that subsequent events may give a different (lower
-     *            or higher) score for this request, transmitted to each
-     *            NetworkFactory through additional CMD_REQUEST_NETWORK msgs
-     *            with the same NetworkRequest but an updated score.
-     *            Also, network conditions may change for this bearer
-     *            allowing for a better score in the future.
-     * msg.arg2 = the serial number of the factory currently responsible for the
-     *            NetworkAgent handling this request, or SerialNumber.NONE if none.
-     */
-    public static final int CMD_REQUEST_NETWORK = BASE;
-
-    /**
-     * Cancel a network request
-     * msg.obj = NetworkRequest
-     */
-    public static final int CMD_CANCEL_REQUEST = BASE + 1;
-
-    /**
-     * Internally used to set our best-guess score.
-     * msg.arg1 = new score
-     */
-    private static final int CMD_SET_SCORE = BASE + 2;
-
-    /**
-     * Internally used to set our current filter for coarse bandwidth changes with
-     * technology changes.
-     * msg.obj = new filter
-     */
-    private static final int CMD_SET_FILTER = BASE + 3;
-
-    /**
-     * Sent by NetworkFactory to ConnectivityService to indicate that a request is
-     * unfulfillable.
-     * @see #releaseRequestAsUnfulfillableByAnyFactory(NetworkRequest).
-     */
-    public static final int EVENT_UNFULFILLABLE_REQUEST = BASE + 4;
-
-    private final Context mContext;
-    private final ArrayList<Message> mPreConnectedQueue = new ArrayList<Message>();
-    private AsyncChannel mAsyncChannel;
-    private final String LOG_TAG;
-
-    private final SparseArray<NetworkRequestInfo> mNetworkRequests =
-            new SparseArray<NetworkRequestInfo>();
-
-    private int mScore;
-    private NetworkCapabilities mCapabilityFilter;
-
-    private int mRefCount = 0;
-    private Messenger mMessenger = null;
-    private int mSerialNumber;
-
-    @UnsupportedAppUsage
-    public NetworkFactory(Looper looper, Context context, String logTag,
-            NetworkCapabilities filter) {
-        super(looper);
-        LOG_TAG = logTag;
-        mContext = context;
-        mCapabilityFilter = filter;
-    }
-
-    public void register() {
-        if (DBG) log("Registering NetworkFactory");
-        if (mMessenger == null) {
-            mMessenger = new Messenger(this);
-            mSerialNumber = ConnectivityManager.from(mContext).registerNetworkFactory(mMessenger,
-                    LOG_TAG);
-        }
-    }
-
-    public void unregister() {
-        if (DBG) log("Unregistering NetworkFactory");
-        if (mMessenger != null) {
-            ConnectivityManager.from(mContext).unregisterNetworkFactory(mMessenger);
-            mMessenger = null;
-        }
-    }
-
-    @Override
-    public void handleMessage(Message msg) {
-        switch (msg.what) {
-            case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
-                if (mAsyncChannel != null) {
-                    log("Received new connection while already connected!");
-                    break;
-                }
-                if (VDBG) log("NetworkFactory fully connected");
-                AsyncChannel ac = new AsyncChannel();
-                ac.connected(null, this, msg.replyTo);
-                ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
-                        AsyncChannel.STATUS_SUCCESSFUL);
-                mAsyncChannel = ac;
-                for (Message m : mPreConnectedQueue) {
-                    ac.sendMessage(m);
-                }
-                mPreConnectedQueue.clear();
-                break;
-            }
-            case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
-                if (VDBG) log("CMD_CHANNEL_DISCONNECT");
-                if (mAsyncChannel != null) {
-                    mAsyncChannel.disconnect();
-                    mAsyncChannel = null;
-                }
-                break;
-            }
-            case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
-                if (DBG) log("NetworkFactory channel lost");
-                mAsyncChannel = null;
-                break;
-            }
-            case CMD_REQUEST_NETWORK: {
-                handleAddRequest((NetworkRequest) msg.obj, msg.arg1, msg.arg2);
-                break;
-            }
-            case CMD_CANCEL_REQUEST: {
-                handleRemoveRequest((NetworkRequest) msg.obj);
-                break;
-            }
-            case CMD_SET_SCORE: {
-                handleSetScore(msg.arg1);
-                break;
-            }
-            case CMD_SET_FILTER: {
-                handleSetFilter((NetworkCapabilities) msg.obj);
-                break;
-            }
-        }
-    }
-
-    private class NetworkRequestInfo {
-        public final NetworkRequest request;
-        public int score;
-        public boolean requested; // do we have a request outstanding, limited by score
-        public int factorySerialNumber;
-
-        NetworkRequestInfo(NetworkRequest request, int score, int factorySerialNumber) {
-            this.request = request;
-            this.score = score;
-            this.requested = false;
-            this.factorySerialNumber = factorySerialNumber;
-        }
-
-        @Override
-        public String toString() {
-            return "{" + request + ", score=" + score + ", requested=" + requested + "}";
-        }
-    }
-
-    /**
-     * Add a NetworkRequest that the bearer may want to attempt to satisfy.
-     * @see #CMD_REQUEST_NETWORK
-     *
-     * @param request the request to handle.
-     * @param score the score of the NetworkAgent currently satisfying this request.
-     * @param servingFactorySerialNumber the serial number of the NetworkFactory that
-     *         created the NetworkAgent currently satisfying this request.
-     */
-    // TODO : remove this method. It is a stopgap measure to help sheperding a number
-    // of dependent changes that would conflict throughout the automerger graph. Having this
-    // temporarily helps with the process of going through with all these dependent changes across
-    // the entire tree.
-    @VisibleForTesting
-    protected void handleAddRequest(NetworkRequest request, int score) {
-        handleAddRequest(request, score, SerialNumber.NONE);
-    }
-
-    /**
-     * Add a NetworkRequest that the bearer may want to attempt to satisfy.
-     * @see #CMD_REQUEST_NETWORK
-     *
-     * @param request the request to handle.
-     * @param score the score of the NetworkAgent currently satisfying this request.
-     * @param servingFactorySerialNumber the serial number of the NetworkFactory that
-     *         created the NetworkAgent currently satisfying this request.
-     */
-    @VisibleForTesting
-    protected void handleAddRequest(NetworkRequest request, int score,
-            int servingFactorySerialNumber) {
-        NetworkRequestInfo n = mNetworkRequests.get(request.requestId);
-        if (n == null) {
-            if (DBG) {
-                log("got request " + request + " with score " + score
-                        + " and serial " + servingFactorySerialNumber);
-            }
-            n = new NetworkRequestInfo(request, score, servingFactorySerialNumber);
-            mNetworkRequests.put(n.request.requestId, n);
-        } else {
-            if (VDBG) {
-                log("new score " + score + " for exisiting request " + request
-                        + " with serial " + servingFactorySerialNumber);
-            }
-            n.score = score;
-            n.factorySerialNumber = servingFactorySerialNumber;
-        }
-        if (VDBG) log("  my score=" + mScore + ", my filter=" + mCapabilityFilter);
-
-        evalRequest(n);
-    }
-
-    @VisibleForTesting
-    protected void handleRemoveRequest(NetworkRequest request) {
-        NetworkRequestInfo n = mNetworkRequests.get(request.requestId);
-        if (n != null) {
-            mNetworkRequests.remove(request.requestId);
-            if (n.requested) releaseNetworkFor(n.request);
-        }
-    }
-
-    private void handleSetScore(int score) {
-        mScore = score;
-        evalRequests();
-    }
-
-    private void handleSetFilter(NetworkCapabilities netCap) {
-        mCapabilityFilter = netCap;
-        evalRequests();
-    }
-
-    /**
-     * Overridable function to provide complex filtering.
-     * Called for every request every time a new NetworkRequest is seen
-     * and whenever the filterScore or filterNetworkCapabilities change.
-     *
-     * acceptRequest can be overridden to provide complex filter behavior
-     * for the incoming requests
-     *
-     * For output, this class will call {@link #needNetworkFor} and
-     * {@link #releaseNetworkFor} for every request that passes the filters.
-     * If you don't need to see every request, you can leave the base
-     * implementations of those two functions and instead override
-     * {@link #startNetwork} and {@link #stopNetwork}.
-     *
-     * If you want to see every score fluctuation on every request, set
-     * your score filter to a very high number and watch {@link #needNetworkFor}.
-     *
-     * @return {@code true} to accept the request.
-     */
-    public boolean acceptRequest(NetworkRequest request, int score) {
-        return true;
-    }
-
-    private void evalRequest(NetworkRequestInfo n) {
-        if (VDBG) {
-            log("evalRequest");
-            log(" n.requests = " + n.requested);
-            log(" n.score = " + n.score);
-            log(" mScore = " + mScore);
-            log(" n.factorySerialNumber = " + n.factorySerialNumber);
-            log(" mSerialNumber = " + mSerialNumber);
-        }
-        if (shouldNeedNetworkFor(n)) {
-            if (VDBG) log("  needNetworkFor");
-            needNetworkFor(n.request, n.score);
-            n.requested = true;
-        } else if (shouldReleaseNetworkFor(n)) {
-            if (VDBG) log("  releaseNetworkFor");
-            releaseNetworkFor(n.request);
-            n.requested = false;
-        } else {
-            if (VDBG) log("  done");
-        }
-    }
-
-    private boolean shouldNeedNetworkFor(NetworkRequestInfo n) {
-        // If this request is already tracked, it doesn't qualify for need
-        return !n.requested
-            // If the score of this request is higher or equal to that of this factory and some
-            // other factory is responsible for it, then this factory should not track the request
-            // because it has no hope of satisfying it.
-            && (n.score < mScore || n.factorySerialNumber == mSerialNumber)
-            // If this factory can't satisfy the capability needs of this request, then it
-            // should not be tracked.
-            && n.request.networkCapabilities.satisfiedByNetworkCapabilities(mCapabilityFilter)
-            // Finally if the concrete implementation of the factory rejects the request, then
-            // don't track it.
-            && acceptRequest(n.request, n.score);
-    }
-
-    private boolean shouldReleaseNetworkFor(NetworkRequestInfo n) {
-        // Don't release a request that's not tracked.
-        return n.requested
-            // The request should be released if it can't be satisfied by this factory. That
-            // means either of the following conditions are met :
-            // - Its score is too high to be satisfied by this factory and it's not already
-            //   assigned to the factory
-            // - This factory can't satisfy the capability needs of the request
-            // - The concrete implementation of the factory rejects the request
-            && ((n.score > mScore && n.factorySerialNumber != mSerialNumber)
-                    || !n.request.networkCapabilities.satisfiedByNetworkCapabilities(
-                            mCapabilityFilter)
-                    || !acceptRequest(n.request, n.score));
-    }
-
-    private void evalRequests() {
-        for (int i = 0; i < mNetworkRequests.size(); i++) {
-            NetworkRequestInfo n = mNetworkRequests.valueAt(i);
-            evalRequest(n);
-        }
-    }
-
-    /**
-     * Post a command, on this NetworkFactory Handler, to re-evaluate all
-     * oustanding requests. Can be called from a factory implementation.
-     */
-    protected void reevaluateAllRequests() {
-        post(() -> {
-            evalRequests();
-        });
-    }
-
-    /**
-     * Can be called by a factory to release a request as unfulfillable: the request will be
-     * removed, and the caller will get a
-     * {@link ConnectivityManager.NetworkCallback#onUnavailable()} callback after this function
-     * returns.
-     *
-     * Note: this should only be called by factory which KNOWS that it is the ONLY factory which
-     * is able to fulfill this request!
-     */
-    protected void releaseRequestAsUnfulfillableByAnyFactory(NetworkRequest r) {
-        post(() -> {
-            if (DBG) log("releaseRequestAsUnfulfillableByAnyFactory: " + r);
-            Message msg = obtainMessage(EVENT_UNFULFILLABLE_REQUEST, r);
-            if (mAsyncChannel != null) {
-                mAsyncChannel.sendMessage(msg);
-            } else {
-                mPreConnectedQueue.add(msg);
-            }
-        });
-    }
-
-    // override to do simple mode (request independent)
-    protected void startNetwork() { }
-    protected void stopNetwork() { }
-
-    // override to do fancier stuff
-    protected void needNetworkFor(NetworkRequest networkRequest, int score) {
-        if (++mRefCount == 1) startNetwork();
-    }
-
-    protected void releaseNetworkFor(NetworkRequest networkRequest) {
-        if (--mRefCount == 0) stopNetwork();
-    }
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    public void setScoreFilter(int score) {
-        sendMessage(obtainMessage(CMD_SET_SCORE, score, 0));
-    }
-
-    public void setCapabilityFilter(NetworkCapabilities netCap) {
-        sendMessage(obtainMessage(CMD_SET_FILTER, new NetworkCapabilities(netCap)));
-    }
-
-    @VisibleForTesting
-    protected int getRequestCount() {
-        return mNetworkRequests.size();
-    }
-
-    public int getSerialNumber() {
-        return mSerialNumber;
-    }
-
-    protected void log(String s) {
-        Log.d(LOG_TAG, s);
-    }
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
-        final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
-        pw.println(toString());
-        pw.increaseIndent();
-        for (int i = 0; i < mNetworkRequests.size(); i++) {
-            pw.println(mNetworkRequests.valueAt(i));
-        }
-        pw.decreaseIndent();
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder("{").append(LOG_TAG).append(" - mSerialNumber=")
-                .append(mSerialNumber).append(", ScoreFilter=")
-                .append(mScore).append(", Filter=").append(mCapabilityFilter).append(", requests=")
-                .append(mNetworkRequests.size()).append(", refCount=").append(mRefCount)
-                .append("}");
-        return sb.toString();
-    }
-}
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index d0c5363..08fe159 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -17,9 +17,11 @@
 package android.net;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.telephony.Annotation.NetworkType;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -150,10 +152,19 @@
     private boolean mIsRoaming;
 
     /**
-     * @hide
+     * Create a new instance of NetworkInfo.
+     *
+     * This may be useful for apps to write unit tests.
+     *
+     * @param type the legacy type of the network, as one of the ConnectivityManager.TYPE_*
+     *             constants.
+     * @param subtype the subtype if applicable, as one of the TelephonyManager.NETWORK_TYPE_*
+     *                constants.
+     * @param typeName a human-readable string for the network type, or an empty string or null.
+     * @param subtypeName a human-readable string for the subtype, or an empty string or null.
      */
-    @UnsupportedAppUsage
-    public NetworkInfo(int type, int subtype, String typeName, String subtypeName) {
+    public NetworkInfo(int type, @NetworkType int subtype,
+            @Nullable String typeName, @Nullable String subtypeName) {
         if (!ConnectivityManager.isNetworkTypeValid(type)
                 && type != ConnectivityManager.TYPE_NONE) {
             throw new IllegalArgumentException("Invalid network type: " + type);
@@ -462,17 +473,19 @@
 
     /**
      * Sets the fine-grained state of the network.
+     *
+     * This is only useful for testing.
+     *
      * @param detailedState the {@link DetailedState}.
      * @param reason a {@code String} indicating the reason for the state change,
      * if one was supplied. May be {@code null}.
      * @param extraInfo an optional {@code String} providing addditional network state
      * information passed up from the lower networking layers.
      * @deprecated Use {@link NetworkCapabilities} instead.
-     * @hide
      */
     @Deprecated
-    @UnsupportedAppUsage
-    public void setDetailedState(DetailedState detailedState, String reason, String extraInfo) {
+    public void setDetailedState(@NonNull DetailedState detailedState, @Nullable String reason,
+            @Nullable String extraInfo) {
         synchronized (this) {
             this.mDetailedState = detailedState;
             this.mState = stateMap.get(detailedState);
diff --git a/core/java/android/net/NetworkKey.java b/core/java/android/net/NetworkKey.java
index 47c08a4..4469d7d 100644
--- a/core/java/android/net/NetworkKey.java
+++ b/core/java/android/net/NetworkKey.java
@@ -73,13 +73,14 @@
     /**
      * Constructs a new NetworkKey for the given wifi {@link ScanResult}.
      *
-     * @return  A new {@link NetworkKey} instance or <code>null</code> if the given
-     *          {@link ScanResult} instance is malformed.
+     * @return A new {@link NetworkKey} instance or <code>null</code> if the given
+     *         {@link ScanResult} instance is malformed.
+     * @throws IllegalArgumentException
      */
     @Nullable
-    public static NetworkKey createFromScanResult(@Nullable ScanResult result) {
+    public static NetworkKey createFromScanResult(@NonNull ScanResult result) {
         if (result == null) {
-            return null;
+            throw new IllegalArgumentException("ScanResult cannot be null");
         }
         final String ssid = result.SSID;
         if (TextUtils.isEmpty(ssid) || ssid.equals(WifiManager.UNKNOWN_SSID)) {
diff --git a/core/java/android/net/NetworkMisc.aidl b/core/java/android/net/NetworkMisc.aidl
deleted file mode 100644
index c65583f..0000000
--- a/core/java/android/net/NetworkMisc.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2014 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;
-
-parcelable NetworkMisc;
diff --git a/core/java/android/net/NetworkMisc.java b/core/java/android/net/NetworkMisc.java
deleted file mode 100644
index 4ad52d5..0000000
--- a/core/java/android/net/NetworkMisc.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * A grab-bag of information (metadata, policies, properties, etc) about a
- * {@link Network}. Since this contains PII, it should not be sent outside the
- * system.
- *
- * @hide
- */
-public class NetworkMisc implements Parcelable {
-
-    /**
-     * If the {@link Network} is a VPN, whether apps are allowed to bypass the
-     * VPN. This is set by a {@link VpnService} and used by
-     * {@link ConnectivityManager} when creating a VPN.
-     */
-    public boolean allowBypass;
-
-    /**
-     * Set if the network was manually/explicitly connected to by the user either from settings
-     * or a 3rd party app.  For example, turning on cell data is not explicit but tapping on a wifi
-     * ap in the wifi settings to trigger a connection is explicit.  A 3rd party app asking to
-     * connect to a particular access point is also explicit, though this may change in the future
-     * as we want apps to use the multinetwork apis.
-     */
-    public boolean explicitlySelected;
-
-    /**
-     * Set if the user desires to use this network even if it is unvalidated. This field has meaning
-     * only if {@link explicitlySelected} is true. If it is, this field must also be set to the
-     * appropriate value based on previous user choice.
-     */
-    public boolean acceptUnvalidated;
-
-    /**
-     * Whether the user explicitly set that this network should be validated even if presence of
-     * only partial internet connectivity.
-     */
-    public boolean acceptPartialConnectivity;
-
-    /**
-     * Set to avoid surfacing the "Sign in to network" notification.
-     * if carrier receivers/apps are registered to handle the carrier-specific provisioning
-     * procedure, a carrier specific provisioning notification will be placed.
-     * only one notification should be displayed. This field is set based on
-     * which notification should be used for provisioning.
-     */
-    public boolean provisioningNotificationDisabled;
-
-    /**
-     * For mobile networks, this is the subscriber ID (such as IMSI).
-     */
-    public String subscriberId;
-
-    /**
-     * Set to skip 464xlat. This means the device will treat the network as IPv6-only and
-     * will not attempt to detect a NAT64 via RFC 7050 DNS lookups.
-     */
-    public boolean skip464xlat;
-
-    /**
-     * Set to true if the PRIVATE_DNS_BROKEN notification has shown for this network.
-     * Reset this bit when private DNS mode is changed from strict mode to opportunistic/off mode.
-     */
-    public boolean hasShownBroken;
-
-    public NetworkMisc() {
-    }
-
-    public NetworkMisc(NetworkMisc nm) {
-        if (nm != null) {
-            allowBypass = nm.allowBypass;
-            explicitlySelected = nm.explicitlySelected;
-            acceptUnvalidated = nm.acceptUnvalidated;
-            subscriberId = nm.subscriberId;
-            provisioningNotificationDisabled = nm.provisioningNotificationDisabled;
-            skip464xlat = nm.skip464xlat;
-        }
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel out, int flags) {
-        out.writeInt(allowBypass ? 1 : 0);
-        out.writeInt(explicitlySelected ? 1 : 0);
-        out.writeInt(acceptUnvalidated ? 1 : 0);
-        out.writeString(subscriberId);
-        out.writeInt(provisioningNotificationDisabled ? 1 : 0);
-        out.writeInt(skip464xlat ? 1 : 0);
-    }
-
-    public static final @android.annotation.NonNull Creator<NetworkMisc> CREATOR = new Creator<NetworkMisc>() {
-        @Override
-        public NetworkMisc createFromParcel(Parcel in) {
-            NetworkMisc networkMisc = new NetworkMisc();
-            networkMisc.allowBypass = in.readInt() != 0;
-            networkMisc.explicitlySelected = in.readInt() != 0;
-            networkMisc.acceptUnvalidated = in.readInt() != 0;
-            networkMisc.subscriberId = in.readString();
-            networkMisc.provisioningNotificationDisabled = in.readInt() != 0;
-            networkMisc.skip464xlat = in.readInt() != 0;
-            return networkMisc;
-        }
-
-        @Override
-        public NetworkMisc[] newArray(int size) {
-            return new NetworkMisc[size];
-        }
-    };
-}
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index de962f8..01800c6 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -18,6 +18,10 @@
 
 import static android.content.pm.PackageManager.GET_SIGNATURES;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.app.ActivityManager;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -38,24 +42,38 @@
 
 import com.google.android.collect.Sets;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.time.ZonedDateTime;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * Manager for creating and modifying network policy rules.
  *
- * {@hide}
+ * @hide
  */
 @SystemService(Context.NETWORK_POLICY_SERVICE)
+@SystemApi
 public class NetworkPolicyManager {
 
     /* POLICY_* are masks and can be ORed, although currently they are not.*/
-    /** No specific network policy, use system default. */
+    /**
+     * No specific network policy, use system default.
+     * @hide
+     */
     public static final int POLICY_NONE = 0x0;
-    /** Reject network usage on metered networks when application in background. */
+    /**
+     * Reject network usage on metered networks when application in background.
+     * @hide
+     */
     public static final int POLICY_REJECT_METERED_BACKGROUND = 0x1;
-    /** Allow metered network use in the background even when in data usage save mode. */
+    /**
+     * Allow metered network use in the background even when in data usage save mode.
+     * @hide
+     */
     public static final int POLICY_ALLOW_METERED_BACKGROUND = 0x4;
 
     /*
@@ -74,49 +92,102 @@
      *
      * See network-policy-restrictions.md for more info.
      */
-    /** No specific rule was set */
+
+    /**
+     * No specific rule was set
+     * @hide
+     */
     public static final int RULE_NONE = 0;
-    /** Allow traffic on metered networks. */
+    /**
+     * Allow traffic on metered networks.
+     * @hide
+     */
     public static final int RULE_ALLOW_METERED = 1 << 0;
-    /** Temporarily allow traffic on metered networks because app is on foreground. */
+    /**
+     * Temporarily allow traffic on metered networks because app is on foreground.
+     * @hide
+     */
     public static final int RULE_TEMPORARY_ALLOW_METERED = 1 << 1;
-    /** Reject traffic on metered networks. */
+    /**
+     * Reject traffic on metered networks.
+     * @hide
+     */
     public static final int RULE_REJECT_METERED = 1 << 2;
-    /** Network traffic should be allowed on all networks (metered or non-metered), although
-     * metered-network restrictions could still apply. */
+    /**
+     * Network traffic should be allowed on all networks (metered or non-metered), although
+     * metered-network restrictions could still apply.
+     * @hide
+     */
     public static final int RULE_ALLOW_ALL = 1 << 5;
-    /** Reject traffic on all networks. */
+    /**
+     * Reject traffic on all networks.
+     * @hide
+     */
     public static final int RULE_REJECT_ALL = 1 << 6;
-    /** Mask used to get the {@code RULE_xxx_METERED} rules */
+
+    /**
+     * Mask used to get the {@code RULE_xxx_METERED} rules
+     * @hide
+     */
     public static final int MASK_METERED_NETWORKS = 0b00001111;
-    /** Mask used to get the {@code RULE_xxx_ALL} rules */
+    /**
+     * Mask used to get the {@code RULE_xxx_ALL} rules
+     * @hide
+     */
     public static final int MASK_ALL_NETWORKS     = 0b11110000;
 
+    /** @hide */
     public static final int FIREWALL_RULE_DEFAULT = 0;
-
+    /** @hide */
     public static final String FIREWALL_CHAIN_NAME_NONE = "none";
+    /** @hide */
     public static final String FIREWALL_CHAIN_NAME_DOZABLE = "dozable";
+    /** @hide */
     public static final String FIREWALL_CHAIN_NAME_STANDBY = "standby";
+    /** @hide */
     public static final String FIREWALL_CHAIN_NAME_POWERSAVE = "powersave";
 
     private static final boolean ALLOW_PLATFORM_APP_POLICY = true;
 
+    /** @hide */
     public static final int FOREGROUND_THRESHOLD_STATE =
             ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
 
     /**
      * {@link Intent} extra that indicates which {@link NetworkTemplate} rule it
      * applies to.
+     * @hide
      */
     public static final String EXTRA_NETWORK_TEMPLATE = "android.net.NETWORK_TEMPLATE";
 
-    public static final int OVERRIDE_UNMETERED = 1 << 0;
-    public static final int OVERRIDE_CONGESTED = 1 << 1;
+    /**
+     * Mask used to check if an override value is marked as unmetered.
+     */
+    public static final int SUBSCRIPTION_OVERRIDE_UNMETERED = 1 << 0;
+
+    /**
+     * Mask used to check if an override value is marked as congested.
+     */
+    public static final int SUBSCRIPTION_OVERRIDE_CONGESTED = 1 << 1;
+
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "SUBSCRIPTION_OVERRIDE_" }, value = {
+        SUBSCRIPTION_OVERRIDE_UNMETERED,
+        SUBSCRIPTION_OVERRIDE_CONGESTED
+    })
+    public @interface SubscriptionOverrideMask {}
 
     private final Context mContext;
     @UnsupportedAppUsage
     private INetworkPolicyManager mService;
 
+    private final Map<SubscriptionCallback, SubscriptionCallbackProxy>
+            mCallbackMap = new ConcurrentHashMap<>();
+
+    /** @hide */
     public NetworkPolicyManager(Context context, INetworkPolicyManager service) {
         if (service == null) {
             throw new IllegalArgumentException("missing INetworkPolicyManager");
@@ -125,6 +196,7 @@
         mService = service;
     }
 
+    /** @hide */
     @UnsupportedAppUsage
     public static NetworkPolicyManager from(Context context) {
         return (NetworkPolicyManager) context.getSystemService(Context.NETWORK_POLICY_SERVICE);
@@ -135,6 +207,7 @@
      *
      * @param policy should be {@link #POLICY_NONE} or any combination of {@code POLICY_} flags,
      *     although it is not validated.
+     * @hide
      */
     @UnsupportedAppUsage
     public void setUidPolicy(int uid, int policy) {
@@ -152,6 +225,7 @@
      *
      * @param policy should be {@link #POLICY_NONE} or any combination of {@code POLICY_} flags,
      *     although it is not validated.
+     * @hide
      */
     public void addUidPolicy(int uid, int policy) {
         try {
@@ -168,6 +242,7 @@
      *
      * @param policy should be {@link #POLICY_NONE} or any combination of {@code POLICY_} flags,
      *     although it is not validated.
+     * @hide
      */
     public void removeUidPolicy(int uid, int policy) {
         try {
@@ -177,6 +252,7 @@
         }
     }
 
+    /** @hide */
     @UnsupportedAppUsage
     public int getUidPolicy(int uid) {
         try {
@@ -186,6 +262,7 @@
         }
     }
 
+    /** @hide */
     @UnsupportedAppUsage
     public int[] getUidsWithPolicy(int policy) {
         try {
@@ -195,6 +272,7 @@
         }
     }
 
+    /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public void registerListener(INetworkPolicyListener listener) {
         try {
@@ -204,6 +282,7 @@
         }
     }
 
+    /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public void unregisterListener(INetworkPolicyListener listener) {
         try {
@@ -213,6 +292,36 @@
         }
     }
 
+    /** @hide */
+    @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY)
+    @SystemApi
+    public void registerSubscriptionCallback(@NonNull SubscriptionCallback callback) {
+        if (callback == null) {
+            throw new NullPointerException("Callback cannot be null.");
+        }
+
+        final SubscriptionCallbackProxy callbackProxy = new SubscriptionCallbackProxy(callback);
+        if (null != mCallbackMap.putIfAbsent(callback, callbackProxy)) {
+            throw new IllegalArgumentException("Callback is already registered.");
+        }
+        registerListener(callbackProxy);
+    }
+
+    /** @hide */
+    @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY)
+    @SystemApi
+    public void unregisterSubscriptionCallback(@NonNull SubscriptionCallback callback) {
+        if (callback == null) {
+            throw new NullPointerException("Callback cannot be null.");
+        }
+
+        final SubscriptionCallbackProxy callbackProxy = mCallbackMap.remove(callback);
+        if (callbackProxy == null) return;
+
+        unregisterListener(callbackProxy);
+    }
+
+    /** @hide */
     public void setNetworkPolicies(NetworkPolicy[] policies) {
         try {
             mService.setNetworkPolicies(policies);
@@ -221,6 +330,7 @@
         }
     }
 
+    /** @hide */
     @UnsupportedAppUsage
     public NetworkPolicy[] getNetworkPolicies() {
         try {
@@ -230,6 +340,7 @@
         }
     }
 
+    /** @hide */
     @UnsupportedAppUsage
     public void setRestrictBackground(boolean restrictBackground) {
         try {
@@ -239,6 +350,7 @@
         }
     }
 
+    /** @hide */
     @UnsupportedAppUsage
     public boolean getRestrictBackground() {
         try {
@@ -249,6 +361,62 @@
     }
 
     /**
+     * Override connections to be temporarily marked as either unmetered or congested,
+     * along with automatic timeouts if desired.
+     *
+     * @param subId the subscriber ID this override applies to.
+     * @param overrideMask the bitmask that specifies which of the overrides is being
+     *            set or cleared.
+     * @param overrideValue the override values to set or clear.
+     * @param timeoutMillis the timeout after which the requested override will
+     *            be automatically cleared, or {@code 0} to leave in the
+     *            requested state until explicitly cleared, or the next reboot,
+     *            whichever happens first
+     * @param callingPackage the name of the package making the call.
+     */
+    public void setSubscriptionOverride(int subId, @SubscriptionOverrideMask int overrideMask,
+            @SubscriptionOverrideMask int overrideValue, long timeoutMillis,
+                    @NonNull String callingPackage) {
+        try {
+            mService.setSubscriptionOverride(subId, overrideMask, overrideValue, timeoutMillis,
+                    callingPackage);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Set the subscription plans for a specific subscriber.
+     *
+     * @param subId the subscriber this relationship applies to.
+     * @param plans the list of plans.
+     * @param callingPackage the name of the package making the call
+     */
+    public void setSubscriptionPlans(int subId, @NonNull SubscriptionPlan[] plans,
+            @NonNull String callingPackage) {
+        try {
+            mService.setSubscriptionPlans(subId, plans, callingPackage);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get subscription plans for the given subscription id.
+     *
+     * @param subId the subscriber to get the subscription plans for.
+     * @param callingPackage the name of the package making the call.
+     */
+    @NonNull
+    public SubscriptionPlan[] getSubscriptionPlans(int subId, @NonNull String callingPackage) {
+        try {
+            return mService.getSubscriptionPlans(subId, callingPackage);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Resets network policy settings back to factory defaults.
      *
      * @hide
@@ -286,6 +454,7 @@
     /**
      * Check if given UID can have a {@link #setUidPolicy(int, int)} defined,
      * usually to protect critical system services.
+     * @hide
      */
     @Deprecated
     public static boolean isUidValidForPolicy(Context context, int uid) {
@@ -353,6 +522,7 @@
     /**
      * Returns true if {@param procState} is considered foreground and as such will be allowed
      * to access network when the device is idle or in battery saver mode. Otherwise, false.
+     * @hide
      */
     public static boolean isProcStateAllowedWhileIdleOrPowerSaveMode(int procState) {
         return procState <= FOREGROUND_THRESHOLD_STATE;
@@ -361,20 +531,68 @@
     /**
      * Returns true if {@param procState} is considered foreground and as such will be allowed
      * to access network when the device is in data saver mode. Otherwise, false.
+     * @hide
      */
     public static boolean isProcStateAllowedWhileOnRestrictBackground(int procState) {
         return procState <= FOREGROUND_THRESHOLD_STATE;
     }
 
+    /** @hide */
     public static String resolveNetworkId(WifiConfiguration config) {
         return WifiInfo.removeDoubleQuotes(config.isPasspoint()
                 ? config.providerFriendlyName : config.SSID);
     }
 
+    /** @hide */
     public static String resolveNetworkId(String ssid) {
         return WifiInfo.removeDoubleQuotes(ssid);
     }
 
+    /** @hide */
+    @SystemApi
+    public static class SubscriptionCallback {
+        /**
+         * Notify clients of a new override about a given subscription.
+         *
+         * @param subId the subscriber this override applies to.
+         * @param overrideMask a bitmask that specifies which of the overrides is set.
+         * @param overrideValue a bitmask that specifies the override values.
+         */
+        public void onSubscriptionOverride(int subId, @SubscriptionOverrideMask int overrideMask,
+                @SubscriptionOverrideMask int overrideValue) {}
+
+        /**
+         * Notify of subscription plans change about a given subscription.
+         *
+         * @param subId the subscriber id that got subscription plans change.
+         * @param plans the list of subscription plans.
+         */
+        public void onSubscriptionPlansChanged(int subId, @NonNull SubscriptionPlan[] plans) {}
+    }
+
+    /**
+     * SubscriptionCallback proxy for SubscriptionCallback object.
+     * @hide
+     */
+    public class SubscriptionCallbackProxy extends Listener {
+        private final SubscriptionCallback mCallback;
+
+        SubscriptionCallbackProxy(SubscriptionCallback callback) {
+            mCallback = callback;
+        }
+
+        @Override
+        public void onSubscriptionOverride(int subId, @SubscriptionOverrideMask int overrideMask,
+                @SubscriptionOverrideMask int overrideValue) {
+            mCallback.onSubscriptionOverride(subId, overrideMask, overrideValue);
+        }
+
+        @Override
+        public void onSubscriptionPlansChanged(int subId, SubscriptionPlan[] plans) {
+            mCallback.onSubscriptionPlansChanged(subId, plans);
+        }
+    }
+
     /** {@hide} */
     public static class Listener extends INetworkPolicyListener.Stub {
         @Override public void onUidRulesChanged(int uid, int uidRules) { }
diff --git a/core/java/android/net/NetworkProvider.java b/core/java/android/net/NetworkProvider.java
new file mode 100644
index 0000000..2c0e4aa
--- /dev/null
+++ b/core/java/android/net/NetworkProvider.java
@@ -0,0 +1,166 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.util.Log;
+
+/**
+ * Base class for network providers such as telephony or Wi-Fi. NetworkProviders connect the device
+ * to networks and makes them available to to the core network stack by creating
+ * {@link NetworkAgent}s. The networks can then provide connectivity to apps and can be interacted
+ * with via networking APIs such as {@link ConnectivityManager}.
+ *
+ * Subclasses should implement {@link #onNetworkRequested} and {@link #onRequestWithdrawn} to
+ * receive {@link NetworkRequest}s sent by the system and by apps. A network that is not the
+ * best (highest-scoring) network for any request is generally not used by the system, and torn
+ * down.
+ *
+ * @hide
+ */
+@SystemApi
+public class NetworkProvider {
+    /**
+     * {@code providerId} value that indicates the absence of a provider. It is the providerId of
+     * any NetworkProvider that is not currently registered, and of any NetworkRequest that is not
+     * currently being satisfied by a network.
+     */
+    public static final int ID_NONE = -1;
+
+    /**
+     * A hardcoded ID for NetworkAgents representing VPNs. These agents are not created by any
+     * provider, so they use this constant for clarity instead of NONE.
+     * @hide only used by ConnectivityService.
+     */
+    public static final int ID_VPN = -2;
+
+    /**
+     * The first providerId value that will be allocated.
+     * @hide only used by ConnectivityService.
+     */
+    public static final int FIRST_PROVIDER_ID = 1;
+
+    /** @hide only used by ConnectivityService */
+    public static final int CMD_REQUEST_NETWORK = 1;
+    /** @hide only used by ConnectivityService */
+    public static final int CMD_CANCEL_REQUEST = 2;
+
+    private final Messenger mMessenger;
+    private final String mName;
+    private final ConnectivityManager mCm;
+
+    private int mProviderId = ID_NONE;
+
+    /**
+     * Constructs a new NetworkProvider.
+     *
+     * @param looper the Looper on which to run {@link #onNetworkRequested} and
+     *               {@link #onRequestWithdrawn}.
+     * @param name the name of the listener, used only for debugging.
+     *
+     * @hide
+     */
+    @SystemApi
+    public NetworkProvider(@NonNull Context context, @NonNull Looper looper, @NonNull String name) {
+        mCm = ConnectivityManager.from(context);
+
+        Handler handler = new Handler(looper) {
+            @Override
+            public void handleMessage(Message m) {
+                switch (m.what) {
+                    case CMD_REQUEST_NETWORK:
+                        onNetworkRequested((NetworkRequest) m.obj, m.arg1, m.arg2);
+                        break;
+                    case CMD_CANCEL_REQUEST:
+                        onRequestWithdrawn((NetworkRequest) m.obj);
+                        break;
+                    default:
+                        Log.e(mName, "Unhandled message: " + m.what);
+                }
+            }
+        };
+        mMessenger = new Messenger(handler);
+        mName = name;
+    }
+
+    // TODO: consider adding a register() method so ConnectivityManager does not need to call this.
+    public @Nullable Messenger getMessenger() {
+        return mMessenger;
+    }
+
+    public @NonNull String getName() {
+        return mName;
+    }
+
+    /**
+     * Returns the ID of this provider. This is known only once the provider is registered via
+     * {@link ConnectivityManager#registerNetworkProvider()}, otherwise the ID is {@link #ID_NONE}.
+     * This ID must be used when registering any {@link NetworkAgent}s.
+     */
+    public int getProviderId() {
+        return mProviderId;
+    }
+
+    /** @hide */
+    public void setProviderId(int providerId) {
+        mProviderId = providerId;
+    }
+
+    /**
+     *  Called when a NetworkRequest is received. The request may be a new request or an existing
+     *  request with a different score.
+     *
+     * @param request the NetworkRequest being received
+     * @param score the score of the network currently satisfying the request, or 0 if none.
+     * @param providerId the ID of the provider that created the network currently satisfying this
+     *                   request, or {@link #ID_NONE} if none.
+     *
+     *  @hide
+     */
+    @SystemApi
+    public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) {}
+
+    /**
+     *  Called when a NetworkRequest is withdrawn.
+     *  @hide
+     */
+    @SystemApi
+    public void onRequestWithdrawn(@NonNull NetworkRequest request) {}
+
+    /**
+     * Asserts that no provider will ever be able to satisfy the specified request. The provider
+     * must only call this method if it knows that it is the only provider on the system capable of
+     * satisfying this request, and that the request cannot be satisfied. The application filing the
+     * request will receive an {@link NetworkCallback#onUnavailable()} callback.
+     *
+     * @param request the request that cannot be fulfilled
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+    public void declareNetworkRequestUnfulfillable(@NonNull NetworkRequest request) {
+        mCm.declareNetworkRequestUnfulfillable(request);
+    }
+}
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 9731f3c..301d203 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -300,22 +300,34 @@
          * this without a single transport set will generate an exception, as will
          * subsequently adding or removing transports after this is set.
          * </p>
-         * The interpretation of this {@code String} is bearer specific and bearers that use
-         * it should document their particulars.  For example, Bluetooth may use some sort of
-         * device id while WiFi could used ssid and/or bssid.  Cellular may use carrier spn.
+         * If the {@code networkSpecifier} is provided, it shall be interpreted as follows:
+         * <ul>
+         * <li>If the specifier can be parsed as an integer, it will be treated as a
+         * {@link android.net TelephonyNetworkSpecifier}, and the provided integer will be
+         * interpreted as a SubscriptionId.
+         * <li>If the value is an ethernet interface name, it will be treated as such.
+         * <li>For all other cases, the behavior is undefined.
+         * </ul>
          *
-         * @param networkSpecifier An {@code String} of opaque format used to specify the bearer
-         *                         specific network specifier where the bearer has a choice of
-         *                         networks.
+         * @param networkSpecifier A {@code String} of either a SubscriptionId in cellular
+         *                         network request or an ethernet interface name in ethernet
+         *                         network request.
+         *
+         * @deprecated Use {@link #setNetworkSpecifier(NetworkSpecifier)} instead.
          */
+        @Deprecated
         public Builder setNetworkSpecifier(String networkSpecifier) {
-            /*
-             * A StringNetworkSpecifier does not accept null or empty ("") strings. When network
-             * specifiers were strings a null string and an empty string were considered equivalent.
-             * Hence no meaning is attached to a null or empty ("") string.
-             */
-            return setNetworkSpecifier(TextUtils.isEmpty(networkSpecifier) ? null
-                    : new StringNetworkSpecifier(networkSpecifier));
+            try {
+                int subId = Integer.parseInt(networkSpecifier);
+                return setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+                        .setSubscriptionId(subId).build());
+            } catch (NumberFormatException nfe) {
+                // A StringNetworkSpecifier does not accept null or empty ("") strings. When network
+                // specifiers were strings a null string and an empty string were considered
+                // equivalent. Hence no meaning is attached to a null or empty ("") string.
+                return setNetworkSpecifier(TextUtils.isEmpty(networkSpecifier) ? null
+                        : new StringNetworkSpecifier(networkSpecifier));
+            }
         }
 
         /**
@@ -455,6 +467,19 @@
     }
 
     /**
+     * Returns true iff. the capabilities requested in this NetworkRequest are satisfied by the
+     * provided {@link NetworkCapabilities}.
+     *
+     * @param nc Capabilities that should satisfy this NetworkRequest. null capabilities do not
+     *           satisfy any request.
+     * @hide
+     */
+    @SystemApi
+    public boolean satisfiedBy(@Nullable NetworkCapabilities nc) {
+        return networkCapabilities.satisfiedByNetworkCapabilities(nc);
+    }
+
+    /**
      * @see Builder#addTransportType(int)
      */
     public boolean hasTransport(@Transport int transportType) {
diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java
index f6dc525..a190c47 100644
--- a/core/java/android/net/NetworkScoreManager.java
+++ b/core/java/android/net/NetworkScoreManager.java
@@ -35,6 +35,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.Executor;
 
@@ -163,27 +164,26 @@
     public static final String EXTRA_NEW_SCORER = "newScorer";
 
     /** @hide */
-    @IntDef({CACHE_FILTER_NONE, CACHE_FILTER_CURRENT_NETWORK, CACHE_FILTER_SCAN_RESULTS})
+    @IntDef({SCORE_FILTER_NONE, SCORE_FILTER_CURRENT_NETWORK, SCORE_FILTER_SCAN_RESULTS})
     @Retention(RetentionPolicy.SOURCE)
-    public @interface CacheUpdateFilter {}
+    public @interface ScoreUpdateFilter {}
 
     /**
-     * Do not filter updates sent to the cache.
-     * @hide
+     * Do not filter updates sent to the {@link NetworkScoreCallback}].
      */
-    public static final int CACHE_FILTER_NONE = 0;
+    public static final int SCORE_FILTER_NONE = 0;
 
     /**
-     * Only send cache updates when the network matches the connected network.
-     * @hide
+     * Only send updates to the {@link NetworkScoreCallback} when the network matches the connected
+     * network.
      */
-    public static final int CACHE_FILTER_CURRENT_NETWORK = 1;
+    public static final int SCORE_FILTER_CURRENT_NETWORK = 1;
 
     /**
-     * Only send cache updates when the network is part of the current scan result set.
-     * @hide
+     * Only send updates to the {@link NetworkScoreCallback} when the network is part of the
+     * current scan result set.
      */
-    public static final int CACHE_FILTER_SCAN_RESULTS = 2;
+    public static final int SCORE_FILTER_SCAN_RESULTS = 2;
 
     /** @hide */
     @IntDef({RECOMMENDATIONS_ENABLED_FORCED_OFF, RECOMMENDATIONS_ENABLED_OFF,
@@ -386,7 +386,6 @@
      *
      * @hide
      */
-    @SystemApi
     @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES)
     public boolean requestScores(@NonNull NetworkKey[] networks) throws SecurityException {
         try {
@@ -397,6 +396,28 @@
     }
 
     /**
+     * Request scoring for networks.
+     *
+     * <p>
+     * Note: The results (i.e scores) for these networks, when available will be provided via the
+     * callback registered with {@link #registerNetworkScoreCallback(int, int, Executor,
+     * NetworkScoreCallback)}. The calling module is responsible for registering a callback to
+     * receive the results before requesting new scores via this API.
+     *
+     * @return true if the request was successfully sent, or false if there is no active scorer.
+     * @throws SecurityException if the caller does not hold the
+     *         {@link permission#REQUEST_NETWORK_SCORES} permission.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES)
+    public boolean requestScores(@NonNull Collection<NetworkKey> networks)
+            throws SecurityException {
+        return requestScores(networks.toArray(new NetworkKey[0]));
+    }
+
+    /**
      * Register a network score cache.
      *
      * @param networkType the type of network this cache can handle. See {@link NetworkKey#type}.
@@ -404,13 +425,13 @@
      * @throws SecurityException if the caller does not hold the
      *         {@link permission#REQUEST_NETWORK_SCORES} permission.
      * @throws IllegalArgumentException if a score cache is already registered for this type.
-     * @deprecated equivalent to registering for cache updates with CACHE_FILTER_NONE.
+     * @deprecated equivalent to registering for cache updates with {@link #SCORE_FILTER_NONE}.
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES)
     @Deprecated // migrate to registerNetworkScoreCache(int, INetworkScoreCache, int)
     public void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
-        registerNetworkScoreCache(networkType, scoreCache, CACHE_FILTER_NONE);
+        registerNetworkScoreCache(networkType, scoreCache, SCORE_FILTER_NONE);
     }
 
     /**
@@ -418,7 +439,7 @@
      *
      * @param networkType the type of network this cache can handle. See {@link NetworkKey#type}
      * @param scoreCache implementation of {@link INetworkScoreCache} to store the scores
-     * @param filterType the {@link CacheUpdateFilter} to apply
+     * @param filterType the {@link ScoreUpdateFilter} to apply
      * @throws SecurityException if the caller does not hold the
      *         {@link permission#REQUEST_NETWORK_SCORES} permission.
      * @throws IllegalArgumentException if a score cache is already registered for this type.
@@ -426,7 +447,7 @@
      */
     @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES)
     public void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache,
-            @CacheUpdateFilter int filterType) {
+            @ScoreUpdateFilter int filterType) {
         try {
             mService.registerNetworkScoreCache(networkType, scoreCache, filterType);
         } catch (RemoteException e) {
@@ -455,26 +476,25 @@
 
     /**
      * Base class for network score cache callback. Should be extended by applications and set
-     * when calling {@link #registerNetworkScoreCallback(int, int, NetworkScoreCallback,
-     * Executor)}
+     * when calling {@link #registerNetworkScoreCallback(int, int, Executor, NetworkScoreCallback)}.
      *
      * @hide
      */
     @SystemApi
-    public interface NetworkScoreCallback {
+    public abstract static class NetworkScoreCallback {
         /**
          * Called when a new set of network scores are available.
          * This is triggered in response when the client invokes
-         * {@link #requestScores(NetworkKey[])} to score a new set of networks.
+         * {@link #requestScores(Collection)} to score a new set of networks.
          *
          * @param networks List of {@link ScoredNetwork} containing updated scores.
          */
-        void updateScores(@NonNull List<ScoredNetwork> networks);
+        public abstract void onScoresUpdated(@NonNull Collection<ScoredNetwork> networks);
 
         /**
          * Invokes when all the previously provided scores are no longer valid.
          */
-        void clearScores();
+        public abstract void onScoresInvalidated();
     }
 
     /**
@@ -493,7 +513,7 @@
         public void updateScores(@NonNull List<ScoredNetwork> networks) {
             Binder.clearCallingIdentity();
             mExecutor.execute(() -> {
-                mCallback.updateScores(networks);
+                mCallback.onScoresUpdated(networks);
             });
         }
 
@@ -501,7 +521,7 @@
         public void clearScores() {
             Binder.clearCallingIdentity();
             mExecutor.execute(() -> {
-                mCallback.clearScores();
+                mCallback.onScoresInvalidated();
             });
         }
     }
@@ -510,7 +530,7 @@
      * Register a network score callback.
      *
      * @param networkType the type of network this cache can handle. See {@link NetworkKey#type}
-     * @param filterType the {@link CacheUpdateFilter} to apply
+     * @param filterType the {@link ScoreUpdateFilter} to apply
      * @param callback implementation of {@link NetworkScoreCallback} that will be invoked when the
      *                 scores change.
      * @param executor The executor on which to execute the callbacks.
@@ -522,7 +542,7 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES)
     public void registerNetworkScoreCallback(@NetworkKey.NetworkType int networkType,
-            @CacheUpdateFilter int filterType,
+            @ScoreUpdateFilter int filterType,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull NetworkScoreCallback callback) throws SecurityException {
         if (callback == null || executor == null) {
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index 45bf4d2..96d7a80 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -19,6 +19,9 @@
 import static android.os.Process.CLAT_UID;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -48,59 +51,104 @@
  * @hide
  */
 // @NotThreadSafe
-public class NetworkStats implements Parcelable {
+@SystemApi
+public final class NetworkStats implements Parcelable {
     private static final String TAG = "NetworkStats";
+
     /** {@link #iface} value when interface details unavailable. */
-    public static final String IFACE_ALL = null;
+    @SuppressLint("CompileTimeConstant")
+    @Nullable public static final String IFACE_ALL = null;
+    /**
+     * Virtual network interface for video telephony. This is for VT data usage counting
+     * purpose.
+     */
+    public static final String IFACE_VT = "vt_data0";
+
     /** {@link #uid} value when UID details unavailable. */
     public static final int UID_ALL = -1;
-    /** {@link #tag} value matching any tag. */
+    /** Special UID value for data usage by tethering. */
+    public static final int UID_TETHERING = -5;
+
+    /**
+     * {@link #tag} value matching any tag.
+     * @hide
+     */
     // TODO: Rename TAG_ALL to TAG_ANY.
     public static final int TAG_ALL = -1;
-    /** {@link #set} value for all sets combined, not including debug sets. */
+    /**
+     * {@link #set} value for all sets combined, not including debug sets.
+     * @hide
+     */
     public static final int SET_ALL = -1;
     /** {@link #set} value where background data is accounted. */
     public static final int SET_DEFAULT = 0;
     /** {@link #set} value where foreground data is accounted. */
     public static final int SET_FOREGROUND = 1;
-    /** All {@link #set} value greater than SET_DEBUG_START are debug {@link #set} values. */
+    /**
+     * All {@link #set} value greater than SET_DEBUG_START are debug {@link #set} values.
+     * @hide
+     */
     public static final int SET_DEBUG_START = 1000;
-    /** Debug {@link #set} value when the VPN stats are moved in. */
+    /**
+     * Debug {@link #set} value when the VPN stats are moved in.
+     * @hide
+     */
     public static final int SET_DBG_VPN_IN = 1001;
-    /** Debug {@link #set} value when the VPN stats are moved out of a vpn UID. */
+    /**
+     * Debug {@link #set} value when the VPN stats are moved out of a vpn UID.
+     * @hide
+     */
     public static final int SET_DBG_VPN_OUT = 1002;
 
-    /** Include all interfaces when filtering */
-    public static final String[] INTERFACES_ALL = null;
+    /**
+     * Include all interfaces when filtering
+     * @hide
+     */
+    public @Nullable static final String[] INTERFACES_ALL = null;
 
     /** {@link #tag} value for total data across all tags. */
     // TODO: Rename TAG_NONE to TAG_ALL.
     public static final int TAG_NONE = 0;
 
-    /** {@link #metered} value to account for all metered states. */
+    /**
+     * {@link #metered} value to account for all metered states.
+     * @hide
+     */
     public static final int METERED_ALL = -1;
     /** {@link #metered} value where native, unmetered data is accounted. */
     public static final int METERED_NO = 0;
     /** {@link #metered} value where metered data is accounted. */
     public static final int METERED_YES = 1;
 
-    /** {@link #roaming} value to account for all roaming states. */
+    /**
+     * {@link #roaming} value to account for all roaming states.
+     * @hide
+     */
     public static final int ROAMING_ALL = -1;
     /** {@link #roaming} value where native, non-roaming data is accounted. */
     public static final int ROAMING_NO = 0;
     /** {@link #roaming} value where roaming data is accounted. */
     public static final int ROAMING_YES = 1;
 
-    /** {@link #onDefaultNetwork} value to account for all default network states. */
+    /**
+     * {@link #onDefaultNetwork} value to account for all default network states.
+     * @hide
+     */
     public static final int DEFAULT_NETWORK_ALL = -1;
     /** {@link #onDefaultNetwork} value to account for usage while not the default network. */
     public static final int DEFAULT_NETWORK_NO = 0;
     /** {@link #onDefaultNetwork} value to account for usage while the default network. */
     public static final int DEFAULT_NETWORK_YES = 1;
 
-    /** Denotes a request for stats at the interface level. */
+    /**
+     * Denotes a request for stats at the interface level.
+     * @hide
+     */
     public static final int STATS_PER_IFACE = 0;
-    /** Denotes a request for stats at the interface and UID level. */
+    /**
+     * Denotes a request for stats at the interface and UID level.
+     * @hide
+     */
     public static final int STATS_PER_UID = 1;
 
     private static final String CLATD_INTERFACE_PREFIX = "v4-";
@@ -144,60 +192,78 @@
     @UnsupportedAppUsage
     private long[] operations;
 
+    /** @hide */
+    @SystemApi
     public static class Entry {
+        /** @hide */
         @UnsupportedAppUsage
         public String iface;
+        /** @hide */
         @UnsupportedAppUsage
         public int uid;
+        /** @hide */
         @UnsupportedAppUsage
         public int set;
+        /** @hide */
         @UnsupportedAppUsage
         public int tag;
         /**
          * Note that this is only populated w/ the default value when read from /proc or written
          * to disk. We merge in the correct value when reporting this value to clients of
          * getSummary().
+         * @hide
          */
         public int metered;
         /**
          * Note that this is only populated w/ the default value when read from /proc or written
          * to disk. We merge in the correct value when reporting this value to clients of
          * getSummary().
+         * @hide
          */
         public int roaming;
         /**
          * Note that this is only populated w/ the default value when read from /proc or written
          * to disk. We merge in the correct value when reporting this value to clients of
          * getSummary().
+         * @hide
          */
         public int defaultNetwork;
+        /** @hide */
         @UnsupportedAppUsage
         public long rxBytes;
+        /** @hide */
         @UnsupportedAppUsage
         public long rxPackets;
+        /** @hide */
         @UnsupportedAppUsage
         public long txBytes;
+        /** @hide */
         @UnsupportedAppUsage
         public long txPackets;
+        /** @hide */
+        @UnsupportedAppUsage
         public long operations;
 
+        /** @hide */
         @UnsupportedAppUsage
         public Entry() {
             this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
         }
 
+        /** @hide */
         public Entry(long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
             this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets,
                     operations);
         }
 
+        /** @hide */
         public Entry(String iface, int uid, int set, int tag, long rxBytes, long rxPackets,
                 long txBytes, long txPackets, long operations) {
             this(iface, uid, set, tag, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO,
                     rxBytes, rxPackets, txBytes, txPackets, operations);
         }
 
-        public Entry(String iface, int uid, int set, int tag, int metered, int roaming,
+        public Entry(@Nullable String iface, int uid, int set, int tag, int metered, int roaming,
                  int defaultNetwork, long rxBytes, long rxPackets, long txBytes, long txPackets,
                  long operations) {
             this.iface = iface;
@@ -214,15 +280,18 @@
             this.operations = operations;
         }
 
+        /** @hide */
         public boolean isNegative() {
             return rxBytes < 0 || rxPackets < 0 || txBytes < 0 || txPackets < 0 || operations < 0;
         }
 
+        /** @hide */
         public boolean isEmpty() {
             return rxBytes == 0 && rxPackets == 0 && txBytes == 0 && txPackets == 0
                     && operations == 0;
         }
 
+        /** @hide */
         public void add(Entry another) {
             this.rxBytes += another.rxBytes;
             this.rxPackets += another.rxPackets;
@@ -249,6 +318,7 @@
             return builder.toString();
         }
 
+        /** @hide */
         @Override
         public boolean equals(Object o) {
             if (o instanceof Entry) {
@@ -262,13 +332,13 @@
             return false;
         }
 
+        /** @hide */
         @Override
         public int hashCode() {
             return Objects.hash(uid, set, tag, metered, roaming, defaultNetwork, iface);
         }
     }
 
-    @UnsupportedAppUsage
     public NetworkStats(long elapsedRealtime, int initialSize) {
         this.elapsedRealtime = elapsedRealtime;
         this.size = 0;
@@ -292,6 +362,7 @@
         }
     }
 
+    /** @hide */
     @UnsupportedAppUsage
     public NetworkStats(Parcel parcel) {
         elapsedRealtime = parcel.readLong();
@@ -312,7 +383,7 @@
     }
 
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeLong(elapsedRealtime);
         dest.writeInt(size);
         dest.writeInt(capacity);
@@ -330,19 +401,23 @@
         dest.writeLongArray(operations);
     }
 
+    /**
+     * @hide
+     */
     @Override
     public NetworkStats clone() {
         final NetworkStats clone = new NetworkStats(elapsedRealtime, size);
         NetworkStats.Entry entry = null;
         for (int i = 0; i < size; i++) {
             entry = getValues(i, entry);
-            clone.addValues(entry);
+            clone.addEntry(entry);
         }
         return clone;
     }
 
     /**
      * Clear all data stored in this object.
+     * @hide
      */
     public void clear() {
         this.capacity = 0;
@@ -360,25 +435,28 @@
         this.operations = EmptyArray.LONG;
     }
 
+    /** @hide */
     @VisibleForTesting
     public NetworkStats addIfaceValues(
             String iface, long rxBytes, long rxPackets, long txBytes, long txPackets) {
-        return addValues(
+        return addEntry(
                 iface, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets, 0L);
     }
 
+    /** @hide */
     @VisibleForTesting
-    public NetworkStats addValues(String iface, int uid, int set, int tag, long rxBytes,
+    public NetworkStats addEntry(String iface, int uid, int set, int tag, long rxBytes,
             long rxPackets, long txBytes, long txPackets, long operations) {
-        return addValues(new Entry(
+        return addEntry(new Entry(
                 iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations));
     }
 
+    /** @hide */
     @VisibleForTesting
-    public NetworkStats addValues(String iface, int uid, int set, int tag, int metered, int roaming,
+    public NetworkStats addEntry(String iface, int uid, int set, int tag, int metered, int roaming,
             int defaultNetwork, long rxBytes, long rxPackets, long txBytes, long txPackets,
             long operations) {
-        return addValues(new Entry(
+        return addEntry(new Entry(
                 iface, uid, set, tag, metered, roaming, defaultNetwork, rxBytes, rxPackets,
                 txBytes, txPackets, operations));
     }
@@ -386,8 +464,9 @@
     /**
      * Add new stats entry, copying from given {@link Entry}. The {@link Entry}
      * object can be recycled across multiple calls.
+     * @hide
      */
-    public NetworkStats addValues(Entry entry) {
+    public NetworkStats addEntry(Entry entry) {
         if (size >= capacity) {
             final int newLength = Math.max(size, 10) * 3 / 2;
             iface = Arrays.copyOf(iface, newLength);
@@ -428,6 +507,7 @@
 
     /**
      * Return specific stats entry.
+     * @hide
      */
     @UnsupportedAppUsage
     public Entry getValues(int i, Entry recycle) {
@@ -467,10 +547,12 @@
         operations[dest] = operations[src];
     }
 
+    /** @hide */
     public long getElapsedRealtime() {
         return elapsedRealtime;
     }
 
+    /** @hide */
     public void setElapsedRealtime(long time) {
         elapsedRealtime = time;
     }
@@ -478,21 +560,25 @@
     /**
      * Return age of this {@link NetworkStats} object with respect to
      * {@link SystemClock#elapsedRealtime()}.
+     * @hide
      */
     public long getElapsedRealtimeAge() {
         return SystemClock.elapsedRealtime() - elapsedRealtime;
     }
 
+    /** @hide */
     @UnsupportedAppUsage
     public int size() {
         return size;
     }
 
+    /** @hide */
     @VisibleForTesting
     public int internalSize() {
         return capacity;
     }
 
+    /** @hide */
     @Deprecated
     public NetworkStats combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets,
             long txBytes, long txPackets, long operations) {
@@ -501,6 +587,7 @@
                 txPackets, operations);
     }
 
+    /** @hide */
     public NetworkStats combineValues(String iface, int uid, int set, int tag,
             long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
         return combineValues(new Entry(
@@ -509,16 +596,20 @@
 
     /**
      * Combine given values with an existing row, or create a new row if
-     * {@link #findIndex(String, int, int, int, int)} is unable to find match. Can
-     * also be used to subtract values from existing rows.
+     * {@link #findIndex(String, int, int, int, int, int, int)} is unable to find match. Can
+     * also be used to subtract values from existing rows. This method mutates the referencing
+     * {@link NetworkStats} object.
+     *
+     * @param entry the {@link Entry} to combine.
+     * @return a reference to this mutated {@link NetworkStats} object.
+     * @hide
      */
-    @UnsupportedAppUsage
-    public NetworkStats combineValues(Entry entry) {
+    public @NonNull NetworkStats combineValues(@NonNull Entry entry) {
         final int i = findIndex(entry.iface, entry.uid, entry.set, entry.tag, entry.metered,
                 entry.roaming, entry.defaultNetwork);
         if (i == -1) {
             // only create new entry when positive contribution
-            addValues(entry);
+            addEntry(entry);
         } else {
             rxBytes[i] += entry.rxBytes;
             rxPackets[i] += entry.rxPackets;
@@ -530,10 +621,33 @@
     }
 
     /**
-     * Combine all values from another {@link NetworkStats} into this object.
+     * Add given values with an existing row, or create a new row if
+     * {@link #findIndex(String, int, int, int, int, int, int)} is unable to find match. Can
+     * also be used to subtract values from existing rows.
+     *
+     * @param entry the {@link Entry} to add.
+     * @return a new constructed {@link NetworkStats} object that contains the result.
      */
-    @UnsupportedAppUsage
-    public void combineAllValues(NetworkStats another) {
+    public @NonNull NetworkStats addValues(@NonNull Entry entry) {
+        return this.clone().combineValues(entry);
+    }
+
+    /**
+     * Add the given {@link NetworkStats} objects.
+     *
+     * @return the sum of two objects.
+     */
+    public @NonNull NetworkStats add(@NonNull NetworkStats another) {
+        final NetworkStats ret = this.clone();
+        ret.combineAllValues(another);
+        return ret;
+    }
+
+    /**
+     * Combine all values from another {@link NetworkStats} into this object.
+     * @hide
+     */
+    public void combineAllValues(@NonNull NetworkStats another) {
         NetworkStats.Entry entry = null;
         for (int i = 0; i < another.size; i++) {
             entry = another.getValues(i, entry);
@@ -543,6 +657,7 @@
 
     /**
      * Find first stats index that matches the requested parameters.
+     * @hide
      */
     public int findIndex(String iface, int uid, int set, int tag, int metered, int roaming,
             int defaultNetwork) {
@@ -560,6 +675,7 @@
     /**
      * Find first stats index that matches the requested parameters, starting
      * search around the hinted index as an optimization.
+     * @hide
      */
     @VisibleForTesting
     public int findIndexHinted(String iface, int uid, int set, int tag, int metered, int roaming,
@@ -589,6 +705,7 @@
      * Splice in {@link #operations} from the given {@link NetworkStats} based
      * on matching {@link #uid} and {@link #tag} rows. Ignores {@link #iface},
      * since operation counts are at data layer.
+     * @hide
      */
     public void spliceOperationsFrom(NetworkStats stats) {
         for (int i = 0; i < size; i++) {
@@ -604,6 +721,7 @@
 
     /**
      * Return list of unique interfaces known by this data structure.
+     * @hide
      */
     public String[] getUniqueIfaces() {
         final HashSet<String> ifaces = new HashSet<String>();
@@ -617,6 +735,7 @@
 
     /**
      * Return list of unique UIDs known by this data structure.
+     * @hide
      */
     @UnsupportedAppUsage
     public int[] getUniqueUids() {
@@ -636,6 +755,7 @@
     /**
      * Return total bytes represented by this snapshot object, usually used when
      * checking if a {@link #subtract(NetworkStats)} delta passes a threshold.
+     * @hide
      */
     @UnsupportedAppUsage
     public long getTotalBytes() {
@@ -645,6 +765,7 @@
 
     /**
      * Return total of all fields represented by this snapshot object.
+     * @hide
      */
     @UnsupportedAppUsage
     public Entry getTotal(Entry recycle) {
@@ -654,6 +775,7 @@
     /**
      * Return total of all fields represented by this snapshot object matching
      * the requested {@link #uid}.
+     * @hide
      */
     @UnsupportedAppUsage
     public Entry getTotal(Entry recycle, int limitUid) {
@@ -663,11 +785,13 @@
     /**
      * Return total of all fields represented by this snapshot object matching
      * the requested {@link #iface}.
+     * @hide
      */
     public Entry getTotal(Entry recycle, HashSet<String> limitIface) {
         return getTotal(recycle, limitIface, UID_ALL, false);
     }
 
+    /** @hide */
     @UnsupportedAppUsage
     public Entry getTotalIncludingTags(Entry recycle) {
         return getTotal(recycle, null, UID_ALL, true);
@@ -717,6 +841,7 @@
 
     /**
      * Fast path for battery stats.
+     * @hide
      */
     public long getTotalPackets() {
         long total = 0;
@@ -729,9 +854,12 @@
     /**
      * Subtract the given {@link NetworkStats}, effectively leaving the delta
      * between two snapshots in time. Assumes that statistics rows collect over
-     * time, and that none of them have disappeared.
+     * time, and that none of them have disappeared. This method does not mutate
+     * the referencing object.
+     *
+     * @return the delta between two objects.
      */
-    public NetworkStats subtract(NetworkStats right) {
+    public @NonNull NetworkStats subtract(@NonNull NetworkStats right) {
         return subtract(this, right, null, null);
     }
 
@@ -742,6 +870,7 @@
      * <p>
      * If counters have rolled backwards, they are clamped to {@code 0} and
      * reported to the given {@link NonMonotonicObserver}.
+     * @hide
      */
     public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right,
             NonMonotonicObserver<C> observer, C cookie) {
@@ -759,6 +888,7 @@
      * If <var>recycle</var> is supplied, this NetworkStats object will be
      * reused (and returned) as the result if it is large enough to contain
      * the data.
+     * @hide
      */
     public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right,
             NonMonotonicObserver<C> observer, C cookie, NetworkStats recycle) {
@@ -817,7 +947,7 @@
                 entry.operations = Math.max(entry.operations, 0);
             }
 
-            result.addValues(entry);
+            result.addEntry(entry);
         }
 
         return result;
@@ -847,6 +977,7 @@
      * @param stackedTraffic Stats with traffic stacked on top of our ifaces. Will also be mutated.
      * @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both.
      * @param useBpfStats True if eBPF is in use.
+     * @hide
      */
     public static void apply464xlatAdjustments(NetworkStats baseTraffic,
             NetworkStats stackedTraffic, Map<String, String> stackedIfaces, boolean useBpfStats) {
@@ -899,6 +1030,7 @@
      * {@link #apply464xlatAdjustments(NetworkStats, NetworkStats, Map)} with {@code this} as
      * base and stacked traffic.
      * @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both.
+     * @hide
      */
     public void apply464xlatAdjustments(Map<String, String> stackedIfaces, boolean useBpfStats) {
         apply464xlatAdjustments(this, this, stackedIfaces, useBpfStats);
@@ -907,6 +1039,7 @@
     /**
      * Return total statistics grouped by {@link #iface}; doesn't mutate the
      * original structure.
+     * @hide
      */
     public NetworkStats groupedByIface() {
         final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
@@ -938,6 +1071,7 @@
     /**
      * Return total statistics grouped by {@link #uid}; doesn't mutate the
      * original structure.
+     * @hide
      */
     public NetworkStats groupedByUid() {
         final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
@@ -968,6 +1102,7 @@
 
     /**
      * Remove all rows that match one of specified UIDs.
+     * @hide
      */
     public void removeUids(int[] uids) {
         int nextOutputEntry = 0;
@@ -989,6 +1124,7 @@
      * @param limitUid UID to filter for, or {@link #UID_ALL}.
      * @param limitIfaces Interfaces to filter for, or {@link #INTERFACES_ALL}.
      * @param limitTag Tag to filter for, or {@link #TAG_ALL}.
+     * @hide
      */
     public void filter(int limitUid, String[] limitIfaces, int limitTag) {
         if (limitUid == UID_ALL && limitTag == TAG_ALL && limitIfaces == INTERFACES_ALL) {
@@ -1004,6 +1140,7 @@
      * Only keep entries with {@link #set} value less than {@link #SET_DEBUG_START}.
      *
      * <p>This mutates the original structure in place.
+     * @hide
      */
     public void filterDebugEntries() {
         filter(e -> e.set < SET_DEBUG_START);
@@ -1024,6 +1161,7 @@
         size = nextOutputEntry;
     }
 
+    /** @hide */
     public void dump(String prefix, PrintWriter pw) {
         pw.print(prefix);
         pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime);
@@ -1047,6 +1185,7 @@
 
     /**
      * Return text description of {@link #set} value.
+     * @hide
      */
     public static String setToString(int set) {
         switch (set) {
@@ -1067,6 +1206,7 @@
 
     /**
      * Return text description of {@link #set} value.
+     * @hide
      */
     public static String setToCheckinString(int set) {
         switch (set) {
@@ -1087,6 +1227,7 @@
 
     /**
      * @return true if the querySet matches the dataSet.
+     * @hide
      */
     public static boolean setMatches(int querySet, int dataSet) {
         if (querySet == dataSet) {
@@ -1098,6 +1239,7 @@
 
     /**
      * Return text description of {@link #tag} value.
+     * @hide
      */
     public static String tagToString(int tag) {
         return "0x" + Integer.toHexString(tag);
@@ -1105,6 +1247,7 @@
 
     /**
      * Return text description of {@link #metered} value.
+     * @hide
      */
     public static String meteredToString(int metered) {
         switch (metered) {
@@ -1121,6 +1264,7 @@
 
     /**
      * Return text description of {@link #roaming} value.
+     * @hide
      */
     public static String roamingToString(int roaming) {
         switch (roaming) {
@@ -1137,6 +1281,7 @@
 
     /**
      * Return text description of {@link #defaultNetwork} value.
+     * @hide
      */
     public static String defaultNetworkToString(int defaultNetwork) {
         switch (defaultNetwork) {
@@ -1151,6 +1296,7 @@
         }
     }
 
+    /** @hide */
     @Override
     public String toString() {
         final CharArrayWriter writer = new CharArrayWriter();
@@ -1163,8 +1309,7 @@
         return 0;
     }
 
-    @UnsupportedAppUsage
-    public static final @android.annotation.NonNull Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() {
+    public static final @NonNull Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() {
         @Override
         public NetworkStats createFromParcel(Parcel in) {
             return new NetworkStats(in);
@@ -1176,6 +1321,7 @@
         }
     };
 
+    /** @hide */
     public interface NonMonotonicObserver<C> {
         public void foundNonMonotonic(
                 NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie);
@@ -1195,6 +1341,7 @@
      * @param tunUid uid of the VPN application
      * @param tunIface iface of the vpn tunnel
      * @param underlyingIfaces underlying network ifaces used by the VPN application
+     * @hide
      */
     public void migrateTun(int tunUid, @NonNull String tunIface,
             @NonNull String[] underlyingIfaces) {
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index 08cc4e2..779f7bc 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -31,7 +31,6 @@
 import java.io.FileDescriptor;
 import java.math.BigInteger;
 import java.net.Inet4Address;
-import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.SocketException;
 import java.net.UnknownHostException;
@@ -313,15 +312,6 @@
     }
 
     /**
-     * Check if IP address type is consistent between two InetAddress.
-     * @return true if both are the same type.  False otherwise.
-     */
-    public static boolean addressTypeMatches(InetAddress left, InetAddress right) {
-        return (((left instanceof Inet4Address) && (right instanceof Inet4Address)) ||
-                ((left instanceof Inet6Address) && (right instanceof Inet6Address)));
-    }
-
-    /**
      * Convert a 32 char hex string into a Inet6Address.
      * throws a runtime exception if the string isn't 32 chars, isn't hex or can't be
      * made into an Inet6Address
diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java
index ea6002c..2b9e9fe 100644
--- a/core/java/android/net/RouteInfo.java
+++ b/core/java/android/net/RouteInfo.java
@@ -22,6 +22,7 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.net.util.NetUtils;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -104,6 +105,11 @@
      */
     private final int mType;
 
+    /**
+     * The maximum transmission unit size for this route.
+     */
+    private final int mMtu;
+
     // Derived data members.
     // TODO: remove these.
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -132,6 +138,31 @@
     @TestApi
     public RouteInfo(@Nullable IpPrefix destination, @Nullable InetAddress gateway,
             @Nullable String iface, @RouteType int type) {
+        this(destination, gateway, iface, type, 0);
+    }
+
+    /**
+     * Constructs a RouteInfo object.
+     *
+     * If destination is null, then gateway must be specified and the
+     * constructed route is either the IPv4 default route <code>0.0.0.0</code>
+     * if the gateway is an instance of {@link Inet4Address}, or the IPv6 default
+     * route <code>::/0</code> if gateway is an instance of
+     * {@link Inet6Address}.
+     * <p>
+     * destination and gateway may not both be null.
+     *
+     * @param destination the destination prefix
+     * @param gateway the IP address to route packets through
+     * @param iface the interface name to send packets on
+     * @param type the type of this route
+     * @param mtu the maximum transmission unit size for this route
+     *
+     * @hide
+     */
+    @SystemApi
+    public RouteInfo(@Nullable IpPrefix destination, @Nullable InetAddress gateway,
+            @Nullable String iface, @RouteType int type, int mtu) {
         switch (type) {
             case RTN_UNICAST:
             case RTN_UNREACHABLE:
@@ -161,7 +192,7 @@
             } else {
                 // no destination, no gateway. invalid.
                 throw new IllegalArgumentException("Invalid arguments passed in: " + gateway + "," +
-                                                   destination);
+                        destination);
             }
         }
         // TODO: set mGateway to null if there is no gateway. This is more correct, saves space, and
@@ -176,10 +207,10 @@
         }
         mHasGateway = (!gateway.isAnyLocalAddress());
 
-        if ((destination.getAddress() instanceof Inet4Address &&
-                 (gateway instanceof Inet4Address == false)) ||
-                (destination.getAddress() instanceof Inet6Address &&
-                 (gateway instanceof Inet6Address == false))) {
+        if ((destination.getAddress() instanceof Inet4Address
+                && !(gateway instanceof Inet4Address))
+                || (destination.getAddress() instanceof Inet6Address
+                && !(gateway instanceof Inet6Address))) {
             throw new IllegalArgumentException("address family mismatch in RouteInfo constructor");
         }
         mDestination = destination;  // IpPrefix objects are immutable.
@@ -187,6 +218,7 @@
         mInterface = iface;          // Strings are immutable.
         mType = type;
         mIsHost = isHost();
+        mMtu = mtu;
     }
 
     /**
@@ -373,6 +405,17 @@
     }
 
     /**
+     * Retrieves the MTU size for this route.
+     *
+     * @return The MTU size, or 0 if it has not been set.
+     * @hide
+     */
+    @SystemApi
+    public int getMtu() {
+        return mMtu;
+    }
+
+    /**
      * Indicates if this route is a default route (ie, has no destination specified).
      *
      * @return {@code true} if the destination has a prefix length of 0.
@@ -441,21 +484,7 @@
     @UnsupportedAppUsage
     @Nullable
     public static RouteInfo selectBestRoute(Collection<RouteInfo> routes, InetAddress dest) {
-        if ((routes == null) || (dest == null)) return null;
-
-        RouteInfo bestRoute = null;
-        // pick a longest prefix match under same address type
-        for (RouteInfo route : routes) {
-            if (NetworkUtils.addressTypeMatches(route.mDestination.getAddress(), dest)) {
-                if ((bestRoute != null) &&
-                        (bestRoute.mDestination.getPrefixLength() >=
-                        route.mDestination.getPrefixLength())) {
-                    continue;
-                }
-                if (route.matches(dest)) bestRoute = route;
-            }
-        }
-        return bestRoute;
+        return NetUtils.selectBestRoute(routes, dest);
     }
 
     /**
@@ -476,6 +505,7 @@
                 val += " unknown type " + mType;
             }
         }
+        val += " mtu " + mMtu;
         return val;
     }
 
@@ -493,7 +523,7 @@
         return Objects.equals(mDestination, target.getDestination()) &&
                 Objects.equals(mGateway, target.getGateway()) &&
                 Objects.equals(mInterface, target.getInterface()) &&
-                mType == target.getType();
+                mType == target.getType() && mMtu == target.getMtu();
     }
 
     /**
@@ -503,7 +533,7 @@
         return (mDestination.hashCode() * 41)
                 + (mGateway == null ? 0 :mGateway.hashCode() * 47)
                 + (mInterface == null ? 0 :mInterface.hashCode() * 67)
-                + (mType * 71);
+                + (mType * 71) + (mMtu * 89);
     }
 
     /**
@@ -522,6 +552,7 @@
         dest.writeByteArray(gatewayBytes);
         dest.writeString(mInterface);
         dest.writeInt(mType);
+        dest.writeInt(mMtu);
     }
 
     /**
@@ -540,8 +571,9 @@
 
             String iface = in.readString();
             int type = in.readInt();
+            int mtu = in.readInt();
 
-            return new RouteInfo(dest, gateway, iface, type);
+            return new RouteInfo(dest, gateway, iface, type, mtu);
         }
 
         public RouteInfo[] newArray(int size) {
diff --git a/core/java/android/net/SocketKeepalive.java b/core/java/android/net/SocketKeepalive.java
index fb224fb..fc9a8f6 100644
--- a/core/java/android/net/SocketKeepalive.java
+++ b/core/java/android/net/SocketKeepalive.java
@@ -20,6 +20,7 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.os.Binder;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
@@ -53,7 +54,11 @@
 public abstract class SocketKeepalive implements AutoCloseable {
     static final String TAG = "SocketKeepalive";
 
-    /** @hide */
+    /**
+     * No errors.
+     * @hide
+     */
+    @SystemApi
     public static final int SUCCESS = 0;
 
     /** @hide */
diff --git a/core/java/android/net/StringNetworkSpecifier.java b/core/java/android/net/StringNetworkSpecifier.java
index 83dbc63..6ae5971 100644
--- a/core/java/android/net/StringNetworkSpecifier.java
+++ b/core/java/android/net/StringNetworkSpecifier.java
@@ -17,7 +17,6 @@
 package android.net;
 
 import android.annotation.NonNull;
-import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -27,7 +26,6 @@
 import java.util.Objects;
 
 /** @hide */
-@SystemApi
 public final class StringNetworkSpecifier extends NetworkSpecifier implements Parcelable {
     /**
      * Arbitrary string used to pass (additional) information to the network factory.
diff --git a/core/java/android/net/TelephonyNetworkSpecifier.java b/core/java/android/net/TelephonyNetworkSpecifier.java
new file mode 100644
index 0000000..726f770
--- /dev/null
+++ b/core/java/android/net/TelephonyNetworkSpecifier.java
@@ -0,0 +1,146 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * NetworkSpecifier object for cellular network request. Apps should use the
+ * {@link TelephonyNetworkSpecifier.Builder} class to create an instance.
+ */
+public final class TelephonyNetworkSpecifier extends NetworkSpecifier implements Parcelable {
+
+    private final int mSubId;
+
+    /**
+     * Return the subscription Id of current TelephonyNetworkSpecifier object.
+     *
+     * @return The subscription id.
+     */
+    public int getSubscriptionId() {
+        return mSubId;
+    }
+
+    /**
+     * @hide
+     */
+    public TelephonyNetworkSpecifier(int subId) {
+        this.mSubId = subId;
+    }
+
+    public static final @NonNull Creator<TelephonyNetworkSpecifier> CREATOR =
+            new Creator<TelephonyNetworkSpecifier>() {
+                @Override
+                public TelephonyNetworkSpecifier createFromParcel(Parcel in) {
+                    int subId = in.readInt();
+                    return new TelephonyNetworkSpecifier(subId);
+                }
+
+                @Override
+                public TelephonyNetworkSpecifier[] newArray(int size) {
+                    return new TelephonyNetworkSpecifier[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mSubId);
+    }
+
+    @Override
+    public int hashCode() {
+        return mSubId;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof TelephonyNetworkSpecifier)) {
+            return false;
+        }
+
+        TelephonyNetworkSpecifier lhs = (TelephonyNetworkSpecifier) obj;
+        return mSubId == lhs.mSubId;
+    }
+
+    @Override
+    public String toString() {
+        return new StringBuilder()
+                .append("TelephonyNetworkSpecifier [")
+                .append("mSubId = ").append(mSubId)
+                .append("]")
+                .toString();
+    }
+
+    /** @hide */
+    @Override
+    public boolean satisfiedBy(NetworkSpecifier other) {
+        // Any generic requests should be satisfied by a specific telephony network.
+        // For simplicity, we treat null same as MatchAllNetworkSpecifier
+        return equals(other) || other == null || other instanceof MatchAllNetworkSpecifier;
+    }
+
+
+    /**
+     * Builder to create {@link TelephonyNetworkSpecifier} object.
+     */
+    public static final class Builder {
+        // Integer.MIN_VALUE which is not a valid subId, services as the sentinel to check if
+        // subId was set
+        private static final int SENTINEL_SUB_ID = Integer.MIN_VALUE;
+
+        private int mSubId;
+
+        public Builder() {
+            mSubId = SENTINEL_SUB_ID;
+        }
+
+        /**
+         * Set the subscription id.
+         *
+         * @param subId The subscription Id.
+         * @return Instance of {@link Builder} to enable the chaining of the builder method.
+         */
+        public @NonNull Builder setSubscriptionId(int subId) {
+            mSubId = subId;
+            return this;
+        }
+
+        /**
+         * Create a NetworkSpecifier for the cellular network request.
+         *
+         * @return TelephonyNetworkSpecifier object.
+         * @throws IllegalArgumentException when subscription Id is not provided through
+         *         {@link #setSubscriptionId(int)}.
+         */
+        public @NonNull TelephonyNetworkSpecifier build() {
+            if (mSubId == SENTINEL_SUB_ID) {
+                throw new IllegalArgumentException("Subscription Id is not provided.");
+            }
+            return new TelephonyNetworkSpecifier(mSubId);
+        }
+    }
+}
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index 162d6e2..8108cf0 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -89,7 +89,7 @@
      *
      * @hide
      */
-    public static final int UID_TETHERING = -5;
+    public static final int UID_TETHERING = NetworkStats.UID_TETHERING;
 
     /**
      * Tag values in this range are reserved for the network stack. The network stack is
diff --git a/core/java/android/net/Uri.aidl b/core/java/android/net/Uri.aidl
index 6bd3be5..b85f63b 100644
--- a/core/java/android/net/Uri.aidl
+++ b/core/java/android/net/Uri.aidl
@@ -16,4 +16,4 @@
 
 package android.net;
 
-parcelable Uri;
+@JavaOnlyStableParcelable parcelable Uri;
diff --git a/core/java/android/net/annotations/PolicyDirection.java b/core/java/android/net/annotations/PolicyDirection.java
new file mode 100644
index 0000000..febd9b4
--- /dev/null
+++ b/core/java/android/net/annotations/PolicyDirection.java
@@ -0,0 +1,35 @@
+/*
+ * 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.net.annotations;
+
+import android.annotation.IntDef;
+import android.net.IpSecManager;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * IPsec traffic direction.
+ *
+ * <p>Mainline modules cannot reference hidden @IntDef. Moving this annotation to a separate class
+ * to allow others to statically include it.
+ *
+ * @hide
+ */
+@IntDef(value = {IpSecManager.DIRECTION_IN, IpSecManager.DIRECTION_OUT})
+@Retention(RetentionPolicy.SOURCE)
+public @interface PolicyDirection {}
diff --git a/core/java/android/net/netstats/provider/AbstractNetworkStatsProvider.java b/core/java/android/net/netstats/provider/AbstractNetworkStatsProvider.java
new file mode 100644
index 0000000..740aa92
--- /dev/null
+++ b/core/java/android/net/netstats/provider/AbstractNetworkStatsProvider.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.netstats.provider;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.net.NetworkStats;
+
+/**
+ * A base class that allows external modules to implement a custom network statistics provider.
+ * @hide
+ */
+@SystemApi
+public abstract class AbstractNetworkStatsProvider {
+    /**
+     * A value used by {@link #setLimit} and {@link #setAlert} indicates there is no limit.
+     */
+    public static final int QUOTA_UNLIMITED = -1;
+
+    /**
+     * Called by {@code NetworkStatsService} when global polling is needed. Custom
+     * implementation of providers MUST respond to it by calling
+     * {@link NetworkStatsProviderCallback#onStatsUpdated} within one minute. Responding
+     * later than this may cause the stats to be dropped.
+     *
+     * @param token a positive number identifying the new state of the system under which
+     *              {@link NetworkStats} have to be gathered from now on. When this is called,
+     *              custom implementations of providers MUST report the latest stats with the
+     *              previous token, under which stats were being gathered so far.
+     */
+    public abstract void requestStatsUpdate(int token);
+
+    /**
+     * Called by {@code NetworkStatsService} when setting the interface quota for the specified
+     * upstream interface. When this is called, the custom implementation should block all egress
+     * packets on the {@code iface} associated with the provider when {@code quotaBytes} bytes have
+     * been reached, and MUST respond to it by calling
+     * {@link NetworkStatsProviderCallback#onLimitReached()}.
+     *
+     * @param iface the interface requiring the operation.
+     * @param quotaBytes the quota defined as the number of bytes, starting from zero and counting
+     *                   from now. A value of {@link #QUOTA_UNLIMITED} indicates there is no limit.
+     */
+    public abstract void setLimit(@NonNull String iface, long quotaBytes);
+
+    /**
+     * Called by {@code NetworkStatsService} when setting the alert bytes. Custom implementations
+     * MUST call {@link NetworkStatsProviderCallback#onAlertReached()} when {@code quotaBytes} bytes
+     * have been reached. Unlike {@link #setLimit(String, long)}, the custom implementation should
+     * not block all egress packets.
+     *
+     * @param quotaBytes the quota defined as the number of bytes, starting from zero and counting
+     *                   from now. A value of {@link #QUOTA_UNLIMITED} indicates there is no alert.
+     */
+    public abstract void setAlert(long quotaBytes);
+}
diff --git a/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl b/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl
new file mode 100644
index 0000000..55b3d4e
--- /dev/null
+++ b/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.netstats.provider;
+
+/**
+ * Interface for NetworkStatsService to query network statistics and set data limits.
+ *
+ * @hide
+ */
+oneway interface INetworkStatsProvider {
+    void requestStatsUpdate(int token);
+    void setLimit(String iface, long quotaBytes);
+    void setAlert(long quotaBytes);
+}
diff --git a/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl b/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl
new file mode 100644
index 0000000..3ea9318
--- /dev/null
+++ b/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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.netstats.provider;
+
+import android.net.NetworkStats;
+
+/**
+ * Interface for implementor of {@link INetworkStatsProviderCallback} to push events
+ * such as network statistics update or notify limit reached.
+ * @hide
+ */
+oneway interface INetworkStatsProviderCallback {
+    void onStatsUpdated(int token, in NetworkStats ifaceStats, in NetworkStats uidStats);
+    void onAlertReached();
+    void onLimitReached();
+    void unregister();
+}
diff --git a/core/java/android/net/netstats/provider/NetworkStatsProviderCallback.java b/core/java/android/net/netstats/provider/NetworkStatsProviderCallback.java
new file mode 100644
index 0000000..e17a8ee
--- /dev/null
+++ b/core/java/android/net/netstats/provider/NetworkStatsProviderCallback.java
@@ -0,0 +1,98 @@
+/*
+ * 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.netstats.provider;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.net.NetworkStats;
+import android.os.RemoteException;
+
+/**
+ * A callback class that allows callers to report events to the system.
+ * @hide
+ */
+@SystemApi
+@SuppressLint("CallbackMethodName")
+public class NetworkStatsProviderCallback {
+    @NonNull private final INetworkStatsProviderCallback mBinder;
+
+    /** @hide */
+    public NetworkStatsProviderCallback(@NonNull INetworkStatsProviderCallback binder) {
+        mBinder = binder;
+    }
+
+    /**
+     * Notify the system of new network statistics.
+     *
+     * Send the network statistics recorded since the last call to {@link #onStatsUpdated}. Must be
+     * called within one minute of {@link AbstractNetworkStatsProvider#requestStatsUpdate(int)}
+     * being called. The provider can also call this whenever it wants to reports new stats for any
+     * reason. Note that the system will not necessarily immediately propagate the statistics to
+     * reflect the update.
+     *
+     * @param token the token under which these stats were gathered. Providers can call this method
+     *              with the current token as often as they want, until the token changes.
+     *              {@see AbstractNetworkStatsProvider#requestStatsUpdate()}
+     * @param ifaceStats the {@link NetworkStats} per interface to be reported.
+     *                   The provider should not include any traffic that is already counted by
+     *                   kernel interface counters.
+     * @param uidStats the same stats as above, but counts {@link NetworkStats}
+     *                 per uid.
+     */
+    public void onStatsUpdated(int token, @NonNull NetworkStats ifaceStats,
+            @NonNull NetworkStats uidStats) {
+        try {
+            mBinder.onStatsUpdated(token, ifaceStats, uidStats);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Notify system that the quota set by {@code setAlert} has been reached.
+     */
+    public void onAlertReached() {
+        try {
+            mBinder.onAlertReached();
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Notify system that the quota set by {@code setLimit} has been reached.
+     */
+    public void onLimitReached() {
+        try {
+            mBinder.onLimitReached();
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Unregister the provider and the referencing callback.
+     */
+    public void unregister() {
+        try {
+            mBinder.unregister();
+        } catch (RemoteException e) {
+            // Ignore error.
+        }
+    }
+}
diff --git a/core/java/android/net/netstats/provider/NetworkStatsProviderWrapper.java b/core/java/android/net/netstats/provider/NetworkStatsProviderWrapper.java
new file mode 100644
index 0000000..4bf7c9b
--- /dev/null
+++ b/core/java/android/net/netstats/provider/NetworkStatsProviderWrapper.java
@@ -0,0 +1,48 @@
+/*
+ * 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.netstats.provider;
+
+import android.annotation.NonNull;
+
+/**
+ * A wrapper class of {@link INetworkStatsProvider} that hides the binder interface from exposing
+ * to outer world.
+ *
+ * @hide
+ */
+public class NetworkStatsProviderWrapper extends INetworkStatsProvider.Stub {
+    @NonNull final AbstractNetworkStatsProvider mProvider;
+
+    public NetworkStatsProviderWrapper(AbstractNetworkStatsProvider provider) {
+        mProvider = provider;
+    }
+
+    @Override
+    public void requestStatsUpdate(int token) {
+        mProvider.requestStatsUpdate(token);
+    }
+
+    @Override
+    public void setLimit(@NonNull String iface, long quotaBytes) {
+        mProvider.setLimit(iface, quotaBytes);
+    }
+
+    @Override
+    public void setAlert(long quotaBytes) {
+        mProvider.setAlert(quotaBytes);
+    }
+}
diff --git a/core/java/android/net/nsd/DnsSdTxtRecord.java b/core/java/android/net/nsd/DnsSdTxtRecord.java
deleted file mode 100644
index e4a91c5..0000000
--- a/core/java/android/net/nsd/DnsSdTxtRecord.java
+++ /dev/null
@@ -1,325 +0,0 @@
-/* -*- Mode: Java; tab-width: 4 -*-
- *
- * Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
- *
- * 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.
-
- To do:
- - implement remove()
- - fix set() to replace existing values
- */
-
-package android.net.nsd;
-
-import android.os.Parcelable;
-import android.os.Parcel;
-
-import java.util.Arrays;
-
-/**
- * This class handles TXT record data for DNS based service discovery as specified at
- * http://tools.ietf.org/html/draft-cheshire-dnsext-dns-sd-11
- *
- * DNS-SD specifies that a TXT record corresponding to an SRV record consist of
- * a packed array of bytes, each preceded by a length byte. Each string
- * is an attribute-value pair.
- *
- * The DnsSdTxtRecord object stores the entire TXT data as a single byte array, traversing it
- * as need be to implement its various methods.
- * @hide
- *
- */
-public class DnsSdTxtRecord implements Parcelable {
-    private static final byte mSeperator = '=';
-
-    private byte[] mData;
-
-    /** Constructs a new, empty TXT record. */
-    public DnsSdTxtRecord()  {
-        mData = new byte[0];
-    }
-
-    /** Constructs a new TXT record from a byte array in the standard format. */
-    public DnsSdTxtRecord(byte[] data) {
-        mData = (byte[]) data.clone();
-    }
-
-    /** Copy constructor */
-    public DnsSdTxtRecord(DnsSdTxtRecord src) {
-        if (src != null && src.mData != null) {
-            mData = (byte[]) src.mData.clone();
-        }
-    }
-
-    /**
-     * Set a key/value pair. Setting an existing key will replace its value.
-     * @param key Must be ascii with no '='
-     * @param value matching value to key
-     */
-    public void set(String key, String value) {
-        byte[] keyBytes;
-        byte[] valBytes;
-        int valLen;
-
-        if (value != null) {
-            valBytes = value.getBytes();
-            valLen = valBytes.length;
-        } else {
-            valBytes = null;
-            valLen = 0;
-        }
-
-        try {
-            keyBytes = key.getBytes("US-ASCII");
-        }
-        catch (java.io.UnsupportedEncodingException e) {
-            throw new IllegalArgumentException("key should be US-ASCII");
-        }
-
-        for (int i = 0; i < keyBytes.length; i++) {
-            if (keyBytes[i] == '=') {
-                throw new IllegalArgumentException("= is not a valid character in key");
-            }
-        }
-
-        if (keyBytes.length + valLen >= 255) {
-            throw new IllegalArgumentException("Key and Value length cannot exceed 255 bytes");
-        }
-
-        int currentLoc = remove(key);
-        if (currentLoc == -1)
-            currentLoc = keyCount();
-
-        insert(keyBytes, valBytes, currentLoc);
-    }
-
-    /**
-     * Get a value for a key
-     *
-     * @param key
-     * @return The value associated with the key
-     */
-    public String get(String key) {
-        byte[] val = this.getValue(key);
-        return val != null ? new String(val) : null;
-    }
-
-    /** Remove a key/value pair. If found, returns the index or -1 if not found */
-    public int remove(String key) {
-        int avStart = 0;
-
-        for (int i=0; avStart < mData.length; i++) {
-            int avLen = mData[avStart];
-            if (key.length() <= avLen &&
-                    (key.length() == avLen || mData[avStart + key.length() + 1] == mSeperator)) {
-                String s = new String(mData, avStart + 1, key.length());
-                if (0 == key.compareToIgnoreCase(s)) {
-                    byte[] oldBytes = mData;
-                    mData = new byte[oldBytes.length - avLen - 1];
-                    System.arraycopy(oldBytes, 0, mData, 0, avStart);
-                    System.arraycopy(oldBytes, avStart + avLen + 1, mData, avStart,
-                            oldBytes.length - avStart - avLen - 1);
-                    return i;
-                }
-            }
-            avStart += (0xFF & (avLen + 1));
-        }
-        return -1;
-    }
-
-    /** Return the count of keys */
-    public int keyCount() {
-        int count = 0, nextKey;
-        for (nextKey = 0; nextKey < mData.length; count++) {
-            nextKey += (0xFF & (mData[nextKey] + 1));
-        }
-        return count;
-    }
-
-    /** Return true if key is present, false if not. */
-    public boolean contains(String key) {
-        String s = null;
-        for (int i = 0; null != (s = this.getKey(i)); i++) {
-            if (0 == key.compareToIgnoreCase(s)) return true;
-        }
-        return false;
-    }
-
-    /* Gets the size in bytes */
-    public int size() {
-        return mData.length;
-    }
-
-    /* Gets the raw data in bytes */
-    public byte[] getRawData() {
-        return (byte[]) mData.clone();
-    }
-
-    private void insert(byte[] keyBytes, byte[] value, int index) {
-        byte[] oldBytes = mData;
-        int valLen = (value != null) ? value.length : 0;
-        int insertion = 0;
-        int newLen, avLen;
-
-        for (int i = 0; i < index && insertion < mData.length; i++) {
-            insertion += (0xFF & (mData[insertion] + 1));
-        }
-
-        avLen = keyBytes.length + valLen + (value != null ? 1 : 0);
-        newLen = avLen + oldBytes.length + 1;
-
-        mData = new byte[newLen];
-        System.arraycopy(oldBytes, 0, mData, 0, insertion);
-        int secondHalfLen = oldBytes.length - insertion;
-        System.arraycopy(oldBytes, insertion, mData, newLen - secondHalfLen, secondHalfLen);
-        mData[insertion] = (byte) avLen;
-        System.arraycopy(keyBytes, 0, mData, insertion + 1, keyBytes.length);
-        if (value != null) {
-            mData[insertion + 1 + keyBytes.length] = mSeperator;
-            System.arraycopy(value, 0, mData, insertion + keyBytes.length + 2, valLen);
-        }
-    }
-
-    /** Return a key in the TXT record by zero-based index. Returns null if index exceeds the total number of keys. */
-    private String getKey(int index) {
-        int avStart = 0;
-
-        for (int i=0; i < index && avStart < mData.length; i++) {
-            avStart += mData[avStart] + 1;
-        }
-
-        if (avStart < mData.length) {
-            int avLen = mData[avStart];
-            int aLen = 0;
-
-            for (aLen=0; aLen < avLen; aLen++) {
-                if (mData[avStart + aLen + 1] == mSeperator) break;
-            }
-            return new String(mData, avStart + 1, aLen);
-        }
-        return null;
-    }
-
-    /**
-     * Look up a key in the TXT record by zero-based index and return its value.
-     * Returns null if index exceeds the total number of keys.
-     * Returns null if the key is present with no value.
-     */
-    private byte[] getValue(int index) {
-        int avStart = 0;
-        byte[] value = null;
-
-        for (int i=0; i < index && avStart < mData.length; i++) {
-            avStart += mData[avStart] + 1;
-        }
-
-        if (avStart < mData.length) {
-            int avLen = mData[avStart];
-            int aLen = 0;
-
-            for (aLen=0; aLen < avLen; aLen++) {
-                if (mData[avStart + aLen + 1] == mSeperator) {
-                    value = new byte[avLen - aLen - 1];
-                    System.arraycopy(mData, avStart + aLen + 2, value, 0, avLen - aLen - 1);
-                    break;
-                }
-            }
-        }
-        return value;
-    }
-
-    private String getValueAsString(int index) {
-        byte[] value = this.getValue(index);
-        return value != null ? new String(value) : null;
-    }
-
-    private byte[] getValue(String forKey) {
-        String s = null;
-        int i;
-
-        for (i = 0; null != (s = this.getKey(i)); i++) {
-            if (0 == forKey.compareToIgnoreCase(s)) {
-                return this.getValue(i);
-            }
-        }
-
-        return null;
-    }
-
-    /**
-     * Return a string representation.
-     * Example : {key1=value1},{key2=value2}..
-     *
-     * For a key say like "key3" with null value
-     * {key1=value1},{key2=value2}{key3}
-     */
-    public String toString() {
-        String a, result = null;
-
-        for (int i = 0; null != (a = this.getKey(i)); i++) {
-            String av =  "{" + a;
-            String val = this.getValueAsString(i);
-            if (val != null)
-                av += "=" + val + "}";
-            else
-                av += "}";
-            if (result == null)
-                result = av;
-            else
-                result = result + ", " + av;
-        }
-        return result != null ? result : "";
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (o == this) {
-            return true;
-        }
-        if (!(o instanceof DnsSdTxtRecord)) {
-            return false;
-        }
-
-        DnsSdTxtRecord record = (DnsSdTxtRecord)o;
-        return  Arrays.equals(record.mData, mData);
-    }
-
-    @Override
-    public int hashCode() {
-        return Arrays.hashCode(mData);
-    }
-
-    /** Implement the Parcelable interface */
-    public int describeContents() {
-        return 0;
-    }
-
-    /** Implement the Parcelable interface */
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeByteArray(mData);
-    }
-
-    /** Implement the Parcelable interface */
-    public static final @android.annotation.NonNull Creator<DnsSdTxtRecord> CREATOR =
-        new Creator<DnsSdTxtRecord>() {
-            public DnsSdTxtRecord createFromParcel(Parcel in) {
-                DnsSdTxtRecord info = new DnsSdTxtRecord();
-                in.readByteArray(info.mData);
-                return info;
-            }
-
-            public DnsSdTxtRecord[] newArray(int size) {
-                return new DnsSdTxtRecord[size];
-            }
-        };
-}
diff --git a/core/java/android/nfc/Tag.java b/core/java/android/nfc/Tag.java
index 8bb2df0..b9e6ff4 100644
--- a/core/java/android/nfc/Tag.java
+++ b/core/java/android/nfc/Tag.java
@@ -455,12 +455,12 @@
      *
      * @hide
      */
-    public synchronized void setConnectedTechnology(int technology) {
-        if (mConnectedTechnology == -1) {
-            mConnectedTechnology = technology;
-        } else {
-            throw new IllegalStateException("Close other technology first!");
+    public synchronized boolean setConnectedTechnology(int technology) {
+        if (mConnectedTechnology != -1) {
+            return false;
         }
+        mConnectedTechnology = technology;
+        return true;
     }
 
     /**
diff --git a/core/java/android/nfc/tech/BasicTagTechnology.java b/core/java/android/nfc/tech/BasicTagTechnology.java
index b6b347c..ae468fe 100644
--- a/core/java/android/nfc/tech/BasicTagTechnology.java
+++ b/core/java/android/nfc/tech/BasicTagTechnology.java
@@ -75,7 +75,10 @@
 
             if (errorCode == ErrorCodes.SUCCESS) {
                 // Store this in the tag object
-                mTag.setConnectedTechnology(mSelectedTechnology);
+                if (!mTag.setConnectedTechnology(mSelectedTechnology)) {
+                    Log.e(TAG, "Close other technology first!");
+                    throw new IOException("Only one TagTechnology can be connected at a time.");
+                }
                 mIsConnected = true;
             } else if (errorCode == ErrorCodes.ERROR_NOT_SUPPORTED) {
                 throw new UnsupportedOperationException("Connecting to " +
diff --git a/core/java/android/os/AppZygote.java b/core/java/android/os/AppZygote.java
index 6daa5b4..9257496 100644
--- a/core/java/android/os/AppZygote.java
+++ b/core/java/android/os/AppZygote.java
@@ -21,6 +21,8 @@
 
 import com.android.internal.annotations.GuardedBy;
 
+import dalvik.system.VMRuntime;
+
 /**
  * AppZygote is responsible for interfacing with an application-specific zygote.
  *
@@ -113,7 +115,7 @@
                     "app_zygote",  // seInfo
                     abi,  // abi
                     abi, // acceptedAbiList
-                    null, // instructionSet
+                    VMRuntime.getInstructionSet(abi), // instructionSet
                     mZygoteUidGidMin,
                     mZygoteUidGidMax);
 
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index b0c2546..ac70b52 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -631,10 +631,12 @@
         }
     }
 
-    private static void sendDeathNotice(DeathRecipient recipient) {
-        if (false) Log.v("JavaBinder", "sendDeathNotice to " + recipient);
+    private static void sendDeathNotice(DeathRecipient recipient, IBinder binderProxy) {
+        if (false) {
+            Log.v("JavaBinder", "sendDeathNotice to " + recipient + " for " + binderProxy);
+        }
         try {
-            recipient.binderDied();
+            recipient.binderDied(binderProxy);
         } catch (RuntimeException exc) {
             Log.w("BinderNative", "Uncaught exception from death notification",
                     exc);
diff --git a/core/java/android/os/HidlMemory.java b/core/java/android/os/HidlMemory.java
index aeb6589..02d1e0c 100644
--- a/core/java/android/os/HidlMemory.java
+++ b/core/java/android/os/HidlMemory.java
@@ -85,11 +85,11 @@
     }
 
     /**
-     * Disowns the underlying handle and returns it. This object becomes invalid.
+     * Disowns the underlying handle and returns it. The underlying handle becomes null.
      *
      * @return The underlying handle.
      */
-    @NonNull
+    @Nullable
     public NativeHandle releaseHandle() {
         NativeHandle handle = mHandle;
         mHandle = null;
diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java
index f336fda..f5fe9c3 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -285,6 +285,13 @@
      */
     public interface DeathRecipient {
         public void binderDied();
+
+        /**
+         * @hide
+         */
+        default void binderDied(IBinder who) {
+            binderDied();
+        }
     }
 
     /**
diff --git a/core/java/android/os/IIncidentDumpCallback.aidl b/core/java/android/os/IIncidentDumpCallback.aidl
new file mode 100644
index 0000000..09b5b01
--- /dev/null
+++ b/core/java/android/os/IIncidentDumpCallback.aidl
@@ -0,0 +1,31 @@
+/**
+ * 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.os.ParcelFileDescriptor;
+
+/**
+ * Callback from IIncidentManager to dump an extended section.
+ *
+ * @hide
+ */
+oneway interface IIncidentDumpCallback {
+    /**
+     * Dumps section data to the given ParcelFileDescriptor.
+     */
+    void onDumpSection(in ParcelFileDescriptor fd);
+}
diff --git a/core/java/android/os/IIncidentManager.aidl b/core/java/android/os/IIncidentManager.aidl
index 7e1b1e0..923234e 100644
--- a/core/java/android/os/IIncidentManager.aidl
+++ b/core/java/android/os/IIncidentManager.aidl
@@ -17,6 +17,7 @@
 package android.os;
 
 import android.os.IIncidentReportStatusListener;
+import android.os.IIncidentDumpCallback;
 import android.os.IncidentManager;
 import android.os.IncidentReportArgs;
 
@@ -52,6 +53,19 @@
             @nullable IIncidentReportStatusListener listener);
 
     /**
+     * Register a section callback with the given id and name. The callback function
+     * will be invoked when an incident report with all sections or sections matching
+     * the given id is being taken.
+     */
+    oneway void registerSection(int id, String name, IIncidentDumpCallback callback);
+
+    /**
+     * Unregister a section callback associated with the given id. The section must be
+     * previously registered with registerSection(int, String, IIncidentDumpCallback).
+     */
+    oneway void unregisterSection(int id);
+
+    /**
      * Tell the incident daemon that the android system server is up and running.
      */
     oneway void systemRunning();
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index 185693e..3ae5700 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -80,6 +80,14 @@
 
     // controls whether PowerManager should doze after the screen turns off or not
     void setDozeAfterScreenOff(boolean on);
+    // returns whether ambient display is available on the device.
+    boolean isAmbientDisplayAvailable();
+    // suppresses the current ambient display configuration and disables ambient display.
+    void suppressAmbientDisplay(String token, boolean suppress);
+    // returns whether ambient display is suppressed by the calling app with the given token.
+    boolean isAmbientDisplaySuppressedForToken(String token);
+    // returns whether ambient display is suppressed by any app with any token.
+    boolean isAmbientDisplaySuppressed();
 
     // Forces the system to suspend even if there are held wakelocks.
     boolean forceSuspend();
diff --git a/core/java/android/os/ISystemConfig.aidl b/core/java/android/os/ISystemConfig.aidl
new file mode 100644
index 0000000..d3b0298
--- /dev/null
+++ b/core/java/android/os/ISystemConfig.aidl
@@ -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.
+ */
+
+package android.os;
+
+/**
+  * Binder interface to query SystemConfig in the system server.
+  * {@hide}
+  */
+interface ISystemConfig {
+    /**
+     * @see SystemConfigManager#getDisabledUntilUsedPreinstalledCarrierApps
+     */
+    List<String> getDisabledUntilUsedPreinstalledCarrierApps();
+
+    /**
+     * @see SystemConfigManager#getDisabledUntilUsedPreinstalledCarrierAssociatedApps
+     */
+    Map getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
+}
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index edaaf81..b10abe7 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -41,11 +41,11 @@
      * END OF DO NOT MOVE
      */
 
-    UserInfo createUser(in String name, in String userType, int flags);
-    UserInfo preCreateUser(in String userType);
-    UserInfo createProfileForUser(in String name, in String userType, int flags, int userId,
+    UserInfo createUserWithThrow(in String name, in String userType, int flags);
+    UserInfo preCreateUserWithThrow(in String userType);
+    UserInfo createProfileForUserWithThrow(in String name, in String userType, int flags, int userId,
             in String[] disallowedPackages);
-    UserInfo createRestrictedProfile(String name, int parentUserHandle);
+    UserInfo createRestrictedProfileWithThrow(String name, int parentUserHandle);
     void setUserEnabled(int userId);
     void setUserAdmin(int userId);
     void evictCredentialEncryptionKey(int userId);
@@ -100,7 +100,7 @@
     boolean isManagedProfile(int userId);
     boolean isDemoUser(int userId);
     boolean isPreCreated(int userId);
-    UserInfo createProfileForUserEvenWhenDisallowed(in String name, in String userType, int flags,
+    UserInfo createProfileForUserEvenWhenDisallowedWithThrow(in String name, in String userType, int flags,
             int userId, in String[] disallowedPackages);
     boolean isUserUnlockingOrUnlocked(int userId);
     int getUserIconBadgeResId(int userId);
@@ -113,7 +113,7 @@
     boolean isUserRunning(int userId);
     boolean isUserNameSet(int userId);
     boolean hasRestrictedProfiles();
-    boolean requestQuietModeEnabled(String callingPackage, boolean enableQuietMode, int userId, in IntentSender target);
+    boolean requestQuietModeEnabled(String callingPackage, boolean enableQuietMode, int userId, in IntentSender target, int flags);
     String getUserName();
     long getUserStartRealtime();
     long getUserUnlockRealtime();
diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl
index 7b2d148..416d692 100644
--- a/core/java/android/os/IVibratorService.aidl
+++ b/core/java/android/os/IVibratorService.aidl
@@ -24,7 +24,7 @@
 {
     boolean hasVibrator();
     boolean hasAmplitudeControl();
-    boolean setAlwaysOnEffect(int id, in VibrationEffect effect,
+    boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, in VibrationEffect effect,
             in VibrationAttributes attributes);
     void vibrate(int uid, String opPkg, in VibrationEffect effect,
             in VibrationAttributes attributes, String reason, IBinder token);
diff --git a/core/java/android/os/IncidentManager.java b/core/java/android/os/IncidentManager.java
index 09e1c0f..8dde117 100644
--- a/core/java/android/os/IncidentManager.java
+++ b/core/java/android/os/IncidentManager.java
@@ -31,10 +31,12 @@
 import java.io.Closeable;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
@@ -421,6 +423,43 @@
     }
 
     /**
+     * Callback for dumping an extended (usually vendor-supplied) incident report section
+     *
+     * @see #registerSection
+     * @see #unregisterSection
+     */
+    public static class DumpCallback {
+        private int mId;
+        private Executor mExecutor;
+
+        IIncidentDumpCallback.Stub mBinder = new IIncidentDumpCallback.Stub() {
+            @Override
+            public void onDumpSection(ParcelFileDescriptor pfd) {
+                if (mExecutor != null) {
+                    mExecutor.execute(() -> {
+                        DumpCallback.this.onDumpSection(mId,
+                                new ParcelFileDescriptor.AutoCloseOutputStream(pfd));
+                    });
+                } else {
+                    DumpCallback.this.onDumpSection(mId,
+                            new ParcelFileDescriptor.AutoCloseOutputStream(pfd));
+                }
+            }
+        };
+
+        /**
+         * Dump the registered section as a protobuf message to the given OutputStream. Called when
+         * incidentd requests to dump this section.
+         *
+         * @param id  the id of the registered section. The same id used in calling
+         *            {@link #registerSection(int, String, DumpCallback)} will be passed in here.
+         * @param out the OutputStream to write the protobuf message
+         */
+        public void onDumpSection(int id, @NonNull OutputStream out) {
+        }
+    }
+
+    /**
      * @hide
      */
     public IncidentManager(Context context) {
@@ -528,6 +567,78 @@
     }
 
     /**
+     * Register a callback to dump an extended incident report section with the given id and name.
+     *
+     * Calling <code>registerSection</code> with a duplicate id will override previous registration.
+     * However, the request must come from the same calling uid.
+     *
+     * @param id       the ID of the extended section. It should be unique system-wide, and be
+     *                 different from IDs of all existing section in
+     *                 frameworks/base/core/proto/android/os/incident.proto.
+     *                 Also see incident.proto for other rules about the ID.
+     * @param name     the name to display in logs and/or stderr when taking an incident report
+     *                 containing this section, mainly for debugging purpose
+     * @param callback the callback function to be invoked when an incident report with all sections
+     *                 or sections matching the given id is being taken
+     */
+    public void registerSection(int id, @NonNull String name, @NonNull DumpCallback callback) {
+        registerSection(id, name, mContext.getMainExecutor(), callback);
+    }
+
+    /**
+     * Register a callback to dump an extended incident report section with the given id and name,
+     * running on the supplied executor.
+     *
+     * @param id       the ID of the extended section. It should be unique system-wide, and be
+     *                 different from IDs of all existing section in
+     *                 frameworks/base/core/proto/android/os/incident.proto.
+     *                 Also see incident.proto for other rules about the ID.
+     * @param name     the name to display in logs and/or stderr when taking an incident report
+     *                 containing this section, mainly for debugging purpose
+     * @param executor the executor used to run the callback
+     * @param callback the callback function to be invoked when an incident report with all sections
+     *                 or sections matching the given id is being taken
+     */
+    public void registerSection(int id, @NonNull String name,
+                @NonNull @CallbackExecutor Executor executor, @NonNull DumpCallback callback) {
+        Objects.requireNonNull(executor, "executor cannot be null");
+        Objects.requireNonNull(callback, "callback cannot be null");
+        try {
+            if (callback.mExecutor != null) {
+                throw new RuntimeException("Do not reuse DumpCallback objects when calling"
+                        + " registerSection");
+            }
+            callback.mExecutor = executor;
+            callback.mId = id;
+            final IIncidentManager service = getIIncidentManagerLocked();
+            if (service == null) {
+                Slog.e(TAG, "registerSection can't find incident binder service");
+                return;
+            }
+            service.registerSection(id, name, callback.mBinder);
+        } catch (RemoteException ex) {
+            Slog.e(TAG, "registerSection failed", ex);
+        }
+    }
+
+    /**
+     * Unregister an extended section dump function. The section must be previously registered with
+     * {@link #registerSection(int, String, DumpCallback)} by the same calling uid.
+     */
+    public void unregisterSection(int id) {
+        try {
+            final IIncidentManager service = getIIncidentManagerLocked();
+            if (service == null) {
+                Slog.e(TAG, "unregisterSection can't find incident binder service");
+                return;
+            }
+            service.unregisterSection(id);
+        } catch (RemoteException ex) {
+            Slog.e(TAG, "unregisterSection failed", ex);
+        }
+    }
+
+    /**
      * Get the incident reports that are available for upload for the supplied
      * broadcast recevier.
      *
@@ -630,7 +741,7 @@
                 throw new RuntimeException("Invalid URI: No "
                         + URI_PARAM_REPORT_ID + " parameter. " + uri);
             }
-        
+
             try {
                 getCompanionServiceLocked().deleteIncidentReports(pkg, cls, id);
             } catch (RemoteException ex) {
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index c9ebed3..1a4dac7 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -1886,6 +1886,43 @@
     public final void writeException(@NonNull Exception e) {
         AppOpsManager.prefixParcelWithAppOpsIfNeeded(this);
 
+        int code = getExceptionCode(e);
+        writeInt(code);
+        StrictMode.clearGatheredViolations();
+        if (code == 0) {
+            if (e instanceof RuntimeException) {
+                throw (RuntimeException) e;
+            }
+            throw new RuntimeException(e);
+        }
+        writeString(e.getMessage());
+        final long timeNow = sParcelExceptionStackTrace ? SystemClock.elapsedRealtime() : 0;
+        if (sParcelExceptionStackTrace && (timeNow - sLastWriteExceptionStackTrace
+                > WRITE_EXCEPTION_STACK_TRACE_THRESHOLD_MS)) {
+            sLastWriteExceptionStackTrace = timeNow;
+            writeStackTrace(e);
+        } else {
+            writeInt(0);
+        }
+        switch (code) {
+            case EX_SERVICE_SPECIFIC:
+                writeInt(((ServiceSpecificException) e).errorCode);
+                break;
+            case EX_PARCELABLE:
+                // Write parceled exception prefixed by length
+                final int sizePosition = dataPosition();
+                writeInt(0);
+                writeParcelable((Parcelable) e, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+                final int payloadPosition = dataPosition();
+                setDataPosition(sizePosition);
+                writeInt(payloadPosition - sizePosition);
+                setDataPosition(payloadPosition);
+                break;
+        }
+    }
+
+    /** @hide */
+    public static int getExceptionCode(@NonNull Throwable e) {
         int code = 0;
         if (e instanceof Parcelable
                 && (e.getClass().getClassLoader() == Parcelable.class.getClassLoader())) {
@@ -1909,51 +1946,25 @@
         } else if (e instanceof ServiceSpecificException) {
             code = EX_SERVICE_SPECIFIC;
         }
-        writeInt(code);
-        StrictMode.clearGatheredViolations();
-        if (code == 0) {
-            if (e instanceof RuntimeException) {
-                throw (RuntimeException) e;
-            }
-            throw new RuntimeException(e);
+        return code;
+    }
+
+    /** @hide */
+    public void writeStackTrace(@NonNull Throwable e) {
+        final int sizePosition = dataPosition();
+        writeInt(0); // Header size will be filled in later
+        StackTraceElement[] stackTrace = e.getStackTrace();
+        final int truncatedSize = Math.min(stackTrace.length, 5);
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < truncatedSize; i++) {
+            sb.append("\tat ").append(stackTrace[i]).append('\n');
         }
-        writeString(e.getMessage());
-        final long timeNow = sParcelExceptionStackTrace ? SystemClock.elapsedRealtime() : 0;
-        if (sParcelExceptionStackTrace && (timeNow - sLastWriteExceptionStackTrace
-                > WRITE_EXCEPTION_STACK_TRACE_THRESHOLD_MS)) {
-            sLastWriteExceptionStackTrace = timeNow;
-            final int sizePosition = dataPosition();
-            writeInt(0); // Header size will be filled in later
-            StackTraceElement[] stackTrace = e.getStackTrace();
-            final int truncatedSize = Math.min(stackTrace.length, 5);
-            StringBuilder sb = new StringBuilder();
-            for (int i = 0; i < truncatedSize; i++) {
-                sb.append("\tat ").append(stackTrace[i]).append('\n');
-            }
-            writeString(sb.toString());
-            final int payloadPosition = dataPosition();
-            setDataPosition(sizePosition);
-            // Write stack trace header size. Used in native side to skip the header
-            writeInt(payloadPosition - sizePosition);
-            setDataPosition(payloadPosition);
-        } else {
-            writeInt(0);
-        }
-        switch (code) {
-            case EX_SERVICE_SPECIFIC:
-                writeInt(((ServiceSpecificException) e).errorCode);
-                break;
-            case EX_PARCELABLE:
-                // Write parceled exception prefixed by length
-                final int sizePosition = dataPosition();
-                writeInt(0);
-                writeParcelable((Parcelable) e, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
-                final int payloadPosition = dataPosition();
-                setDataPosition(sizePosition);
-                writeInt(payloadPosition - sizePosition);
-                setDataPosition(payloadPosition);
-                break;
-        }
+        writeString(sb.toString());
+        final int payloadPosition = dataPosition();
+        setDataPosition(sizePosition);
+        // Write stack trace header size. Used in native side to skip the header
+        writeInt(payloadPosition - sizePosition);
+        setDataPosition(payloadPosition);
     }
 
     /**
@@ -2069,14 +2080,7 @@
         if (remoteStackTrace != null) {
             RemoteException cause = new RemoteException(
                     "Remote stack trace:\n" + remoteStackTrace, null, false, false);
-            try {
-                Throwable rootCause = ExceptionUtils.getRootCause(e);
-                if (rootCause != null) {
-                    rootCause.initCause(cause);
-                }
-            } catch (RuntimeException ex) {
-                Log.e(TAG, "Cannot set cause " + cause + " for " + e, ex);
-            }
+            ExceptionUtils.appendCause(e, cause);
         }
         SneakyThrow.sneakyThrow(e);
     }
@@ -2088,6 +2092,14 @@
      * @param msg The exception message.
      */
     private Exception createException(int code, String msg) {
+        Exception exception = createExceptionOrNull(code, msg);
+        return exception != null
+                ? exception
+                : new RuntimeException("Unknown exception code: " + code + " msg " + msg);
+    }
+
+    /** @hide */
+    public Exception createExceptionOrNull(int code, String msg) {
         switch (code) {
             case EX_PARCELABLE:
                 if (readInt() > 0) {
@@ -2111,9 +2123,9 @@
                 return new UnsupportedOperationException(msg);
             case EX_SERVICE_SPECIFIC:
                 return new ServiceSpecificException(readInt(), msg);
+            default:
+                return null;
         }
-        return new RuntimeException("Unknown exception code: " + code
-                + " msg " + msg);
     }
 
     /**
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 983053b..89ddf8c 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -31,6 +31,9 @@
 import static android.system.OsConstants.S_ISREG;
 import static android.system.OsConstants.S_IWOTH;
 
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
@@ -253,6 +256,9 @@
      *             be opened with the requested mode.
      * @see #parseMode(String)
      */
+    // We can't accept a generic Executor here, since we need to use
+    // MessageQueue.addOnFileDescriptorEventListener()
+    @SuppressLint("ExecutorRegistration")
     public static ParcelFileDescriptor open(File file, int mode, Handler handler,
             final OnCloseListener listener) throws IOException {
         if (handler == null) {
@@ -268,9 +274,22 @@
         return fromFd(fd, handler, listener);
     }
 
-    /** {@hide} */
-    public static ParcelFileDescriptor fromPfd(ParcelFileDescriptor pfd, Handler handler,
-            final OnCloseListener listener) throws IOException {
+    /**
+     * Create a new ParcelFileDescriptor wrapping an already-opened file.
+     *
+     * @param pfd The already-opened file.
+     * @param handler to call listener from.
+     * @param listener to be invoked when the returned descriptor has been
+     *            closed.
+     * @return a new ParcelFileDescriptor pointing to the given file.
+     * @hide
+     */
+    @SystemApi
+    // We can't accept a generic Executor here, since we need to use
+    // MessageQueue.addOnFileDescriptorEventListener()
+    @SuppressLint("ExecutorRegistration")
+    public static @NonNull ParcelFileDescriptor wrap(@NonNull ParcelFileDescriptor pfd,
+            @NonNull Handler handler, @NonNull OnCloseListener listener) throws IOException {
         final FileDescriptor original = new FileDescriptor();
         original.setInt$(pfd.detachFd());
         return fromFd(original, handler, listener);
diff --git a/core/java/android/os/PersistableBundle.java b/core/java/android/os/PersistableBundle.java
index b40283f..7a837e1 100644
--- a/core/java/android/os/PersistableBundle.java
+++ b/core/java/android/os/PersistableBundle.java
@@ -16,17 +16,24 @@
 
 package android.os;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.util.ArrayMap;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.XmlUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
 import org.xmlpull.v1.XmlSerializer;
 
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.ArrayList;
 
 /**
@@ -339,4 +346,44 @@
 
         proto.end(token);
     }
+
+    /**
+     * Writes the content of the {@link PersistableBundle} to a {@link OutputStream}.
+     *
+     * <p>The content can be read by a {@link #readFromStream}.
+     *
+     * @see #readFromStream
+     */
+    public void writeToStream(@NonNull OutputStream outputStream) throws IOException {
+        FastXmlSerializer serializer = new FastXmlSerializer();
+        serializer.setOutput(outputStream, UTF_8.name());
+        serializer.startTag(null, "bundle");
+        try {
+            saveToXml(serializer);
+        } catch (XmlPullParserException e) {
+            throw new IOException(e);
+        }
+        serializer.endTag(null, "bundle");
+        serializer.flush();
+    }
+
+    /**
+     * Reads a {@link PersistableBundle} from an {@link InputStream}.
+     *
+     * <p>The stream must be generated by {@link #writeToStream}.
+     *
+     * @see #writeToStream
+     */
+    @NonNull
+    public static PersistableBundle readFromStream(@NonNull InputStream inputStream)
+            throws IOException {
+        try {
+            XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
+            parser.setInput(inputStream, UTF_8.name());
+            parser.next();
+            return PersistableBundle.restoreFromXml(parser);
+        } catch (XmlPullParserException e) {
+            throw new IOException(e);
+        }
+    }
 }
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 82b04a6..6a80788 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -247,6 +247,19 @@
      */
     public static final int BRIGHTNESS_DEFAULT = -1;
 
+    /**
+     * Brightness value for fully off in float.
+     * TODO: rename this to BRIGHTNES_OFF and remove the integer-based constant.
+     * @hide
+     */
+    public static final float BRIGHTNESS_OFF_FLOAT = -1.0f;
+
+    /**
+     * Invalid brightness value.
+     * @hide
+     */
+    public static final float BRIGHTNESS_INVALID_FLOAT = Float.NaN;
+
     // Note: Be sure to update android.os.BatteryStats and PowerManager.h
     // if adding or modifying user activity event constants.
 
@@ -373,9 +386,15 @@
     public static final int GO_TO_SLEEP_REASON_INATTENTIVE = 9;
 
     /**
+     * Go to sleep reason code: Going to sleep due to quiescent boot.
      * @hide
      */
-    public static final int GO_TO_SLEEP_REASON_MAX = GO_TO_SLEEP_REASON_INATTENTIVE;
+    public static final int GO_TO_SLEEP_REASON_QUIESCENT = 10;
+
+    /**
+     * @hide
+     */
+    public static final int GO_TO_SLEEP_REASON_MAX = GO_TO_SLEEP_REASON_QUIESCENT;
 
     /**
      * @hide
@@ -1864,6 +1883,77 @@
     }
 
     /**
+     * Returns true if ambient display is available on the device.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE)
+    public boolean isAmbientDisplayAvailable() {
+        try {
+            return mService.isAmbientDisplayAvailable();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * If true, suppresses the current ambient display configuration and disables ambient display.
+     *
+     * <p>This method has no effect if {@link #isAmbientDisplayAvailable()} is false.
+     *
+     * @param token A persistable identifier for the ambient display suppression that is unique
+     *              within the calling application.
+     * @param suppress If set to {@code true}, ambient display will be suppressed. If set to
+     *                 {@code false}, ambient display will no longer be suppressed for the given
+     *                 token.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE)
+    public void suppressAmbientDisplay(@NonNull String token, boolean suppress) {
+        try {
+            mService.suppressAmbientDisplay(token, suppress);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns true if ambient display is suppressed by the calling app with the given
+     * {@code token}.
+     *
+     * <p>This method will return false if {@link #isAmbientDisplayAvailable()} is false.
+     *
+     * @param token The identifier of the ambient display suppression.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE)
+    public boolean isAmbientDisplaySuppressedForToken(@NonNull String token) {
+        try {
+            return mService.isAmbientDisplaySuppressedForToken(token);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns true if ambient display is suppressed by <em>any</em> app with <em>any</em> token.
+     *
+     * <p>This method will return false if {@link #isAmbientDisplayAvailable()} is false.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE)
+    public boolean isAmbientDisplaySuppressed() {
+        try {
+            return mService.isAmbientDisplaySuppressed();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns the reason the phone was last shutdown. Calling app must have the
      * {@link android.Manifest.permission#DEVICE_POWER} permission to request this information.
      * @return Reason for shutdown as an int, {@link #SHUTDOWN_REASON_UNKNOWN} if the file could
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 94623bc..c7a8474 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -20,14 +20,20 @@
 import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
+import android.system.StructPollfd;
 import android.util.Pair;
 import android.webkit.WebViewZygote;
 
 import dalvik.system.VMRuntime;
 
+import libcore.io.IoUtils;
+
+import java.io.FileDescriptor;
 import java.util.Map;
+import java.util.concurrent.TimeoutException;
 
 /**
  * Tools for managing OS processes.
@@ -68,10 +74,9 @@
     public static final int LOG_UID = 1007;
 
     /**
-     * Defines the UID/GID for the WIFI supplicant process.
-     * @hide
+     * Defines the UID/GID for the WIFI native processes like wificond, supplicant, hostapd,
+     * vendor HAL, etc.
      */
-    @UnsupportedAppUsage
     public static final int WIFI_UID = 1010;
 
     /**
@@ -89,6 +94,12 @@
     public static final int DRM_UID = 1019;
 
     /**
+     * Defines the GID for the group that allows write access to the internal media storage.
+     * @hide
+     */
+    public static final int SDCARD_RW_GID = 1015;
+
+    /**
      * Defines the UID/GID for the group that controls VPN services.
      * @hide
      */
@@ -482,6 +493,15 @@
     private static long sStartElapsedRealtime;
     private static long sStartUptimeMillis;
 
+    private static final int PIDFD_UNKNOWN = 0;
+    private static final int PIDFD_SUPPORTED = 1;
+    private static final int PIDFD_UNSUPPORTED = 2;
+
+    /**
+     * Whether or not the underlying OS supports pidfd
+     */
+    private static int sPidFdSupported = PIDFD_UNKNOWN;
+
     /**
      * State associated with the zygote process.
      * @hide
@@ -752,11 +772,12 @@
 
     /**
      * Set the priority of a thread, based on Linux priorities.
-     * 
-     * @param tid The identifier of the thread/process to change.
+     *
+     * @param tid The identifier of the thread/process to change. It should be
+     * the native thread id but not the managed id of {@link java.lang.Thread}.
      * @param priority A Linux priority level, from -20 for highest scheduling
      * priority to 19 for lowest scheduling priority.
-     * 
+     *
      * @throws IllegalArgumentException Throws IllegalArgumentException if
      * <var>tid</var> does not exist.
      * @throws SecurityException Throws SecurityException if your process does
@@ -1183,4 +1204,90 @@
         }
 
     }
+
+    /**
+     * Wait for the death of the given process.
+     *
+     * @param pid The process ID to be waited on
+     * @param timeout The maximum time to wait in milliseconds, or -1 to wait forever
+     * @hide
+     */
+    public static void waitForProcessDeath(int pid, int timeout)
+            throws InterruptedException, TimeoutException {
+        FileDescriptor pidfd = null;
+        if (sPidFdSupported == PIDFD_UNKNOWN) {
+            int fd = -1;
+            try {
+                fd = nativePidFdOpen(pid, 0);
+                sPidFdSupported = PIDFD_SUPPORTED;
+            } catch (ErrnoException e) {
+                sPidFdSupported = e.errno != OsConstants.ENOSYS
+                    ? PIDFD_SUPPORTED : PIDFD_UNSUPPORTED;
+            } finally {
+                if (fd >= 0) {
+                    pidfd = new FileDescriptor();
+                    pidfd.setInt$(fd);
+                }
+            }
+        }
+        boolean fallback = sPidFdSupported == PIDFD_UNSUPPORTED;
+        if (!fallback) {
+            try {
+                if (pidfd == null) {
+                    int fd = nativePidFdOpen(pid, 0);
+                    if (fd >= 0) {
+                        pidfd = new FileDescriptor();
+                        pidfd.setInt$(fd);
+                    } else {
+                        fallback = true;
+                    }
+                }
+                if (pidfd != null) {
+                    StructPollfd[] fds = new StructPollfd[] {
+                        new StructPollfd()
+                    };
+                    fds[0].fd = pidfd;
+                    fds[0].events = (short) OsConstants.POLLIN;
+                    fds[0].revents = 0;
+                    fds[0].userData = null;
+                    int res = Os.poll(fds, timeout);
+                    if (res > 0) {
+                        return;
+                    } else if (res == 0) {
+                        throw new TimeoutException();
+                    } else {
+                        // We should get an ErrnoException now
+                    }
+                }
+            } catch (ErrnoException e) {
+                if (e.errno == OsConstants.EINTR) {
+                    throw new InterruptedException();
+                }
+                fallback = true;
+            } finally {
+                if (pidfd != null) {
+                    IoUtils.closeQuietly(pidfd);
+                }
+            }
+        }
+        if (fallback) {
+            boolean infinity = timeout < 0;
+            long now = System.currentTimeMillis();
+            final long end = now + timeout;
+            while (infinity || now < end) {
+                try {
+                    Os.kill(pid, 0);
+                } catch (ErrnoException e) {
+                    if (e.errno == OsConstants.ESRCH) {
+                        return;
+                    }
+                }
+                Thread.sleep(1);
+                now = System.currentTimeMillis();
+            }
+        }
+        throw new TimeoutException();
+    }
+
+    private static native int nativePidFdOpen(int pid, int flags) throws ErrnoException;
 }
diff --git a/core/java/android/os/RevocableFileDescriptor.java b/core/java/android/os/RevocableFileDescriptor.java
index a750ce6..ac2cd60 100644
--- a/core/java/android/os/RevocableFileDescriptor.java
+++ b/core/java/android/os/RevocableFileDescriptor.java
@@ -48,6 +48,8 @@
 
     private volatile boolean mRevoked;
 
+    private ParcelFileDescriptor.OnCloseListener mOnCloseListener;
+
     /** {@hide} */
     public RevocableFileDescriptor() {
     }
@@ -97,6 +99,14 @@
         IoUtils.closeQuietly(mInner);
     }
 
+    /**
+     * Callback for indicating that {@link ParcelFileDescriptor} passed to the client
+     * process ({@link #getRevocableFileDescriptor()}) has been closed.
+     */
+    public void addOnCloseListener(ParcelFileDescriptor.OnCloseListener onCloseListener) {
+        mOnCloseListener = onCloseListener;
+    }
+
     public boolean isRevoked() {
         return mRevoked;
     }
@@ -156,6 +166,9 @@
             if (DEBUG) Slog.v(TAG, "onRelease()");
             mRevoked = true;
             IoUtils.closeQuietly(mInner);
+            if (mOnCloseListener != null) {
+                mOnCloseListener.onClose(null);
+            }
         }
     };
 }
diff --git a/core/java/android/os/SystemConfigManager.java b/core/java/android/os/SystemConfigManager.java
new file mode 100644
index 0000000..3a9ce2f
--- /dev/null
+++ b/core/java/android/os/SystemConfigManager.java
@@ -0,0 +1,91 @@
+/*
+ * 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.os;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.content.Context;
+import android.util.ArraySet;
+import android.util.Log;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * Allows apps outside the system process to access various bits of configuration defined in
+ * /etc/sysconfig and its counterparts on OEM and vendor partitions.
+ *
+ * TODO: Intended for access by system mainline modules only. Marking as SystemApi until the
+ * module-only API surface is available.
+ * @hide
+ */
+@SystemApi
+@TestApi
+@SystemService(Context.SYSTEM_CONFIG_SERVICE)
+public class SystemConfigManager {
+    private static final String TAG = SystemConfigManager.class.getSimpleName();
+
+    private final ISystemConfig mInterface;
+
+    /** @hide **/
+    public SystemConfigManager() {
+        mInterface = ISystemConfig.Stub.asInterface(
+                ServiceManager.getService(Context.SYSTEM_CONFIG_SERVICE));
+    }
+
+    /**
+     * Returns a set of package names for carrier apps that are preinstalled on the device but
+     * should be disabled until the matching carrier's SIM is inserted into the device.
+     * @return A set of package names.
+     */
+    @RequiresPermission(Manifest.permission.READ_CARRIER_APP_INFO)
+    public @NonNull Set<String> getDisabledUntilUsedPreinstalledCarrierApps() {
+        try {
+            List<String> apps = mInterface.getDisabledUntilUsedPreinstalledCarrierApps();
+            return new ArraySet<>(apps);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Caught remote exception");
+            return Collections.emptySet();
+        }
+    }
+
+    /**
+     * Returns a map that describes helper apps associated with carrier apps that, like the apps
+     * returned by {@link #getDisabledUntilUsedPreinstalledCarrierApps()}, should be disabled until
+     * the correct SIM is inserted into the device.
+     * @return A map with keys corresponding to package names returned by
+     *         {@link #getDisabledUntilUsedPreinstalledCarrierApps()} and values as lists of package
+     *         names of helper apps.
+     */
+    @RequiresPermission(Manifest.permission.READ_CARRIER_APP_INFO)
+    public @NonNull Map<String, List<String>>
+            getDisabledUntilUsedPreinstalledCarrierAssociatedApps() {
+        try {
+            return (Map<String, List<String>>)
+                    mInterface.getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Caught remote exception");
+            return Collections.emptyMap();
+        }
+    }
+}
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index c1542c7..8050454 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -70,14 +70,15 @@
     }
 
     @Override
-    public boolean setAlwaysOnEffect(int id, VibrationEffect effect, AudioAttributes attributes) {
+    public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect,
+            AudioAttributes attributes) {
         if (mService == null) {
             Log.w(TAG, "Failed to set always-on effect; no vibrator service.");
             return false;
         }
         try {
             VibrationAttributes atr = new VibrationAttributes.Builder(attributes, effect).build();
-            return mService.setAlwaysOnEffect(id, effect, atr);
+            return mService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, effect, atr);
         } catch (RemoteException e) {
             Log.w(TAG, "Failed to set always-on effect.", e);
         }
diff --git a/core/java/android/os/TelephonyServiceManager.java b/core/java/android/os/TelephonyServiceManager.java
index 1211dd6..4f5f3d6 100644
--- a/core/java/android/os/TelephonyServiceManager.java
+++ b/core/java/android/os/TelephonyServiceManager.java
@@ -18,6 +18,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.content.Context;
 
 /**
  * Provides a way to register and obtain the system service binder objects managed by the telephony
@@ -51,8 +52,8 @@
         /**
          * Register a system server binding object for a service.
          */
-        public void register(@NonNull IBinder binder) {
-            ServiceManager.addService(mServiceName, binder);
+        public void register(@NonNull IBinder service) {
+            ServiceManager.addService(mServiceName, service);
         }
 
         /**
@@ -114,25 +115,115 @@
      */
     @NonNull
     public ServiceRegisterer getTelephonyServiceRegisterer() {
-        return new ServiceRegisterer("phone");
+        return new ServiceRegisterer(Context.TELEPHONY_SERVICE);
     }
 
+    /**
+     * Returns {@link ServiceRegisterer} for the telephony registry service.
+     */
+    @NonNull
+    public ServiceRegisterer getTelephonyRegistryServiceRegisterer() {
+        return new ServiceRegisterer("telephony.registry");
+    }
 
-// TODO: Add more services...
-//
-//    /**
-//     * Returns {@link ServiceRegisterer} for the "subscription" service.
-//     */
-//    @NonNull
-//    public ServiceRegisterer getSubscriptionServiceRegisterer() {
-//        return new ServiceRegisterer("isub");
-//    }
-//
-//    /**
-//     * Returns {@link ServiceRegisterer} for the "SMS" service.
-//     */
-//    @NonNull
-//    public ServiceRegisterer getSmsServiceRegisterer() {
-//        return new ServiceRegisterer("isms");
-//    }
+    /**
+     * Returns {@link ServiceRegisterer} for the telephony IMS service.
+     */
+    @NonNull
+    public ServiceRegisterer getTelephonyImsServiceRegisterer() {
+        return new ServiceRegisterer(Context.TELEPHONY_IMS_SERVICE);
+    }
+
+    /**
+     * Returns {@link ServiceRegisterer} for the telephony RCS message service.
+     */
+    @NonNull
+    public ServiceRegisterer getTelephonyRcsMessageServiceRegisterer() {
+        return new ServiceRegisterer(Context.TELEPHONY_RCS_MESSAGE_SERVICE);
+    }
+
+    /**
+     * Returns {@link ServiceRegisterer} for the subscription service.
+     */
+    @NonNull
+    public ServiceRegisterer getSubscriptionServiceRegisterer() {
+        return new ServiceRegisterer("isub");
+    }
+
+    /**
+     * Returns {@link ServiceRegisterer} for the network policy service.
+     */
+    @NonNull
+    public ServiceRegisterer getNetworkPolicyServiceRegisterer() {
+        return new ServiceRegisterer(Context.NETWORK_POLICY_SERVICE);
+    }
+
+    /**
+     * Returns {@link ServiceRegisterer} for the phone sub service.
+     */
+    @NonNull
+    public ServiceRegisterer getPhoneSubServiceRegisterer() {
+        return new ServiceRegisterer("iphonesubinfo");
+    }
+
+    /**
+     * Returns {@link ServiceRegisterer} for the opportunistic network service.
+     */
+    @NonNull
+    public ServiceRegisterer getOpportunisticNetworkServiceRegisterer() {
+        return new ServiceRegisterer("ions");
+    }
+
+    /**
+     * Returns {@link ServiceRegisterer} for the carrier config service.
+     */
+    @NonNull
+    public ServiceRegisterer getCarrierConfigServiceRegisterer() {
+        return new ServiceRegisterer(Context.CARRIER_CONFIG_SERVICE);
+    }
+
+    /**
+     * Returns {@link ServiceRegisterer} for the "SMS" service.
+     */
+    @NonNull
+    public ServiceRegisterer getSmsServiceRegisterer() {
+        return new ServiceRegisterer("isms");
+    }
+
+    /**
+     * Returns {@link ServiceRegisterer} for the eUICC controller service.
+     */
+    @NonNull
+    public ServiceRegisterer getEuiccControllerService() {
+        return new ServiceRegisterer("econtroller");
+    }
+
+    @NonNull
+    public ServiceRegisterer getEuiccCardControllerServiceRegisterer() {
+        return new ServiceRegisterer("euicc_card_controller");
+    }
+
+    /**
+     * Returns {@link ServiceRegisterer} for the package manager service.
+     */
+    @NonNull
+    public ServiceRegisterer getPackageManagerServiceRegisterer() {
+        return new ServiceRegisterer("package");
+    }
+
+    /**
+     * Returns {@link ServiceRegisterer} for the permission manager service.
+     */
+    @NonNull
+    public ServiceRegisterer getPermissionManagerServiceRegisterer() {
+        return new ServiceRegisterer("permissionmgr");
+    }
+
+    /**
+     * Returns {@link ServiceRegisterer} for the ICC phone book service.
+     */
+    @NonNull
+    public ServiceRegisterer getIccPhoneBookServiceRegisterer() {
+        return new ServiceRegisterer("simphonebook");
+    }
 }
diff --git a/core/java/android/os/TimestampedValue.java b/core/java/android/os/TimestampedValue.java
index 348574e..f4c87ac 100644
--- a/core/java/android/os/TimestampedValue.java
+++ b/core/java/android/os/TimestampedValue.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 
 import java.util.Objects;
 
@@ -35,19 +36,27 @@
  * @param <T> the type of the value with an associated timestamp
  * @hide
  */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 public final class TimestampedValue<T> implements Parcelable {
     private final long mReferenceTimeMillis;
+    @Nullable
     private final T mValue;
 
-    public TimestampedValue(long referenceTimeMillis, T value) {
+    public TimestampedValue(long referenceTimeMillis, @Nullable T value) {
         mReferenceTimeMillis = referenceTimeMillis;
         mValue = value;
     }
 
+    /** Returns the reference time value. See {@link TimestampedValue} for more information. */
     public long getReferenceTimeMillis() {
         return mReferenceTimeMillis;
     }
 
+    /**
+     * Returns the value associated with the timestamp. See {@link TimestampedValue} for more
+     * information.
+     */
+    @Nullable
     public T getValue() {
         return mValue;
     }
@@ -86,6 +95,8 @@
         return one.mReferenceTimeMillis - two.mReferenceTimeMillis;
     }
 
+    /** @hide */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final @NonNull Parcelable.Creator<TimestampedValue<?>> CREATOR =
             new Parcelable.ClassLoaderCreator<TimestampedValue<?>>() {
 
diff --git a/core/java/android/os/UpdateEngine.java b/core/java/android/os/UpdateEngine.java
index 16f7b39..73e1adf 100644
--- a/core/java/android/os/UpdateEngine.java
+++ b/core/java/android/os/UpdateEngine.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.content.res.AssetFileDescriptor;
 import android.os.IUpdateEngine;
 import android.os.IUpdateEngineCallback;
 import android.os.RemoteException;
@@ -349,16 +350,17 @@
     }
 
     /**
-     * Applies the payload passed as ParcelFileDescriptor {@code pfd} instead of
-     * using the {@code file://} scheme.
+     * Applies the payload passed as AssetFileDescriptor {@code assetFd}
+     * instead of using the {@code file://} scheme.
      *
      * <p>See {@link #applyPayload(String)} for {@code offset}, {@code size} and
      * {@code headerKeyValuePairs} parameters.
      */
-    public void applyPayload(@NonNull ParcelFileDescriptor pfd, long offset, long size,
+    public void applyPayload(@NonNull AssetFileDescriptor assetFd,
             @NonNull String[] headerKeyValuePairs) {
         try {
-            mUpdateEngine.applyPayloadFd(pfd, offset, size, headerKeyValuePairs);
+            mUpdateEngine.applyPayloadFd(assetFd.getParcelFileDescriptor(),
+                    assetFd.getStartOffset(), assetFd.getLength(), headerKeyValuePairs);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 6e199ce3..08e4c3a 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -28,6 +28,7 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.annotation.UserHandleAware;
 import android.annotation.UserIdInt;
 import android.annotation.WorkerThread;
 import android.app.Activity;
@@ -50,6 +51,7 @@
 import android.location.LocationManager;
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
+import android.util.AndroidException;
 import android.view.WindowManager.LayoutParams;
 
 import com.android.internal.R;
@@ -77,8 +79,12 @@
     private static final String TAG = "UserManager";
     @UnsupportedAppUsage
     private final IUserManager mService;
+    /** Holding the Application context (not constructor param context). */
     private final Context mContext;
 
+    /** The userId of the constructor param context. To be used instead of mContext.getUserId(). */
+    private final @UserIdInt int mUserId;
+
     private Boolean mIsManagedProfileCached;
     private Boolean mIsProfileCached;
 
@@ -87,6 +93,7 @@
      * This type of user cannot be created; it can only pre-exist on first boot.
      * @hide
      */
+    @SystemApi
     public static final String USER_TYPE_FULL_SYSTEM = "android.os.usertype.full.SYSTEM";
 
     /**
@@ -95,6 +102,7 @@
      * This is sometimes called an ordinary 'secondary user'.
      * @hide
      */
+    @SystemApi
     public static final String USER_TYPE_FULL_SECONDARY = "android.os.usertype.full.SECONDARY";
 
     /**
@@ -122,6 +130,7 @@
      * The intended purpose is for work profiles, which are managed by a corporate entity.
      * @hide
      */
+    @SystemApi
     public static final String USER_TYPE_PROFILE_MANAGED = "android.os.usertype.profile.MANAGED";
 
     /**
@@ -130,9 +139,26 @@
      * This type of user cannot be created; it can only pre-exist on first boot.
      * @hide
      */
+    @SystemApi
     public static final String USER_TYPE_SYSTEM_HEADLESS = "android.os.usertype.system.HEADLESS";
 
     /**
+     * Flag passed to {@link #requestQuietModeEnabled} to request disabling quiet mode only if
+     * there is no need to confirm the user credentials. If credentials are required to disable
+     * quiet mode, {@link #requestQuietModeEnabled} will do nothing and return {@code false}.
+     */
+    public static final int QUIET_MODE_DISABLE_ONLY_IF_CREDENTIAL_NOT_REQUIRED = 0x1;
+
+    /**
+     * List of flags available for the {@link #requestQuietModeEnabled} method.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "QUIET_MODE_" }, value = {
+            QUIET_MODE_DISABLE_ONLY_IF_CREDENTIAL_NOT_REQUIRED })
+    public @interface QuietModeFlag {}
+
+    /**
      * @hide
      * No user restriction.
      */
@@ -192,7 +218,11 @@
     /**
      * Specifies if a user is disallowed from changing Wi-Fi
      * access points. The default value is <code>false</code>.
-     * <p>This restriction has no effect in a managed profile.
+     * <p>
+     * Device owner and profile owner can set this restriction, although the restriction has no
+     * effect in a managed profile. When it is set by the profile owner of an organization-owned
+     * managed profile on the parent profile, it will disallow the personal user from changing
+     * Wi-Fi access points.
      *
      * <p>Key for user restrictions.
      * <p>Type: Boolean
@@ -242,8 +272,13 @@
     /**
      * Specifies if a user is disallowed from turning on location sharing.
      * The default value is <code>false</code>.
-     * <p>In a managed profile, location sharing always reflects the primary user's setting, but
+     * <p>
+     * In a managed profile, location sharing always reflects the primary user's setting, but
      * can be overridden and forced off by setting this restriction to true in the managed profile.
+     * <p>
+     * Device owner and profile owner can set this restriction. When it is set by the profile
+     * owner of an organization-owned managed profile on the parent profile, it will prevent the
+     * user from turning on location sharing in the personal profile.
      *
      * <p>Key for user restrictions.
      * <p>Type: Boolean
@@ -349,9 +384,14 @@
      * Specifies if a user is disallowed from configuring bluetooth.
      * This does <em>not</em> restrict the user from turning bluetooth on or off.
      * The default value is <code>false</code>.
-     * <p>This restriction doesn't prevent the user from using bluetooth. For disallowing usage of
+     * <p>
+     * This restriction doesn't prevent the user from using bluetooth. For disallowing usage of
      * bluetooth completely on the device, use {@link #DISALLOW_BLUETOOTH}.
-     * <p>This restriction has no effect in a managed profile.
+     * <p>
+     * Device owner and profile owner can set this restriction, although the restriction has no
+     * effect in a managed profile. When it is set by the profile owner of an organization-owned
+     * managed profile on the parent profile, it will disallow the personal user from configuring
+     * bluetooth.
      *
      * <p>Key for user restrictions.
      * <p>Type: Boolean
@@ -364,8 +404,10 @@
     /**
      * Specifies if bluetooth is disallowed on the device.
      *
-     * <p> This restriction can only be set by the device owner and the profile owner on the
-     * primary user and it applies globally - i.e. it disables bluetooth on the entire device.
+     * <p> This restriction can only be set by the device owner, the profile owner on the
+     * primary user or the profile owner of an organization-owned managed profile on the
+     * parent profile and it applies globally - i.e. it disables bluetooth on the entire
+     * device.
      * <p>The default value is <code>false</code>.
      * <p>Key for user restrictions.
      * <p>Type: Boolean
@@ -377,8 +419,9 @@
 
     /**
      * Specifies if outgoing bluetooth sharing is disallowed on the device. Device owner and profile
-     * owner can set this restriction. When it is set by device owner, all users on this device will
-     * be affected.
+     * owner can set this restriction. When it is set by device owner or the profile owner of an
+     * organization-owned managed profile on the parent profile, all users on this device will be
+     * affected.
      *
      * <p>Default is <code>true</code> for managed profiles and false for otherwise. When a device
      * upgrades to {@link android.os.Build.VERSION_CODES#O}, the system sets it for all existing
@@ -394,7 +437,8 @@
 
     /**
      * Specifies if a user is disallowed from transferring files over
-     * USB. This can only be set by device owners and profile owners on the primary user.
+     * USB. This can only be set by device owners, profile owners on the primary user or
+     * profile owners of organization-owned managed profiles on the parent profile.
      * The default value is <code>false</code>.
      *
      * <p>Key for user restrictions.
@@ -453,8 +497,9 @@
 
     /**
      * Specifies if a user is disallowed from enabling or accessing debugging features. When set on
-     * the primary user, disables debugging features altogether, including USB debugging. When set
-     * on a managed profile or a secondary user, blocks debugging for that user only, including
+     * the primary user or by the profile owner of an organization-owned managed profile on the
+     * parent profile, disables debugging features altogether, including USB debugging. When set on
+     * a managed profile or a secondary user, blocks debugging for that user only, including
      * starting activities, making service calls, accessing content providers, sending broadcasts,
      * installing/uninstalling packages, clearing user data, etc.
      * The default value is <code>false</code>.
@@ -485,18 +530,19 @@
 
     /**
      * Specifies if a user is disallowed from enabling or disabling location providers. As a
-     * result, user is disallowed from turning on or off location. Device owner and profile owners
-     * can set this restriction and it only applies on the managed user.
+     * result, user is disallowed from turning on or off location.
      *
-     * <p>In a managed profile, location sharing is forced off when it's off on primary user, so
-     * user can still turn off location sharing on managed profile when the restriction is set by
-     * profile owner on managed profile.
-     *
-     * <p>This user restriction is different from {@link #DISALLOW_SHARE_LOCATION},
+     * <p>
+     * In a managed profile, location sharing is forced off when it is turned off on the primary
+     * user or by the profile owner of an organization-owned managed profile on the parent profile.
+     * The user can still turn off location sharing on a managed profile when the restriction is
+     * set by the profile owner on a managed profile.
+     * <p>
+     * This user restriction is different from {@link #DISALLOW_SHARE_LOCATION},
      * as the device owner or profile owner can still enable or disable location mode via
      * {@link DevicePolicyManager#setLocationEnabled} when this restriction is on.
-     *
-     * <p>The default value is <code>false</code>.
+     * <p>
+     * The default value is <code>false</code>.
      *
      * <p>Key for user restrictions.
      * <p>Type: Boolean
@@ -510,7 +556,8 @@
     /**
      * Specifies if date, time and timezone configuring is disallowed.
      *
-     * <p>When restriction is set by device owners, it applies globally - i.e., it disables date,
+     * <p>When restriction is set by device owners or profile owners of organization-owned
+     * managed profiles on the parent profile, it applies globally - i.e., it disables date,
      * time and timezone setting on the entire device and all users will be affected. When it's set
      * by profile owners, it's only applied to the managed user.
      * <p>The default value is <code>false</code>.
@@ -526,8 +573,9 @@
 
     /**
      * Specifies if a user is disallowed from configuring Tethering
-     * & portable hotspots. This can only be set by device owners and profile owners on the
-     * primary user. The default value is <code>false</code>.
+     * & portable hotspots. This can only be set by device owners, profile owners on the
+     * primary user or profile owners of organization-owned managed profiles on the parent profile.
+     * The default value is <code>false</code>.
      * <p>In Android 9.0 or higher, if tethering is enabled when this restriction is set,
      * tethering will be automatically turned off.
      *
@@ -571,8 +619,8 @@
 
     /**
      * Specifies if a user is disallowed from adding new users. This can only be set by device
-     * owners and profile owners on the primary user.
-     * The default value is <code>false</code>.
+     * owners, profile owners on the primary user or profile owners of organization-owned managed
+     * profiles on the parent profile. The default value is <code>false</code>.
      * <p>This restriction has no effect on secondary users and managed profiles since only the
      * primary user can add other users.
      *
@@ -621,7 +669,8 @@
 
     /**
      * Specifies if a user is disallowed from configuring cell
-     * broadcasts. This can only be set by device owners and profile owners on the primary user.
+     * broadcasts. This can only be set by device owners, profile owners on the primary user or
+     * profile owners of organization-owned managed profiles on the parent profile.
      * The default value is <code>false</code>.
      * <p>This restriction has no effect on secondary users and managed profiles since only the
      * primary user can configure cell broadcasts.
@@ -636,7 +685,8 @@
 
     /**
      * Specifies if a user is disallowed from configuring mobile
-     * networks. This can only be set by device owners and profile owners on the primary user.
+     * networks. This can only be set by device owners, profile owners on the primary user or
+     * profile owners of organization-owned managed profiles on the parent profile.
      * The default value is <code>false</code>.
      * <p>This restriction has no effect on secondary users and managed profiles since only the
      * primary user can configure mobile networks.
@@ -739,6 +789,10 @@
     /**
      * Specifies that the user is not allowed to send or receive
      * SMS messages. The default value is <code>false</code>.
+     * <p>
+     * Device owner and profile owner can set this restriction. When it is set by the
+     * profile owner of an organization-owned managed profile on the parent profile,
+     * it will disable SMS in the personal profile.
      *
      * <p>Key for user restrictions.
      * <p>Type: Boolean
@@ -857,7 +911,8 @@
 
     /**
      * Specifies if the user is not allowed to reboot the device into safe boot mode.
-     * This can only be set by device owners and profile owners on the primary user.
+     * This can only be set by device owners, profile owners on the primary user or profile
+     * owners of organization-owned managed profiles on the parent profile.
      * The default value is <code>false</code>.
      *
      * <p>Key for user restrictions.
@@ -896,6 +951,12 @@
 
     /**
      * Specifies if a user is not allowed to use the camera.
+     * <p>
+     * Device owner and profile owner can set this restriction. When the restriction is set by
+     * the device owner or the profile owner of an organization-owned managed profile on the
+     * parent profile, it is applied globally.
+     * <p>
+     * The default value is <code>false</code>.
      *
      * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
      * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
@@ -916,7 +977,8 @@
 
     /**
      * Specifies if a user is not allowed to use cellular data when roaming. This can only be set by
-     * device owners. The default value is <code>false</code>.
+     * device owners or profile owners of organization-owned managed profiles on the parent profile.
+     * The default value is <code>false</code>.
      *
      * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
      * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
@@ -1011,8 +1073,9 @@
      * Specifies if the contents of a user's screen is not allowed to be captured for artificial
      * intelligence purposes.
      *
-     * <p>Device owner and profile owner can set this restriction. When it is set by device owner,
-     * only the target user will be affected.
+     * <p>Device owner and profile owner can set this restriction. When it is set by the
+     * device owner or the profile owner of an organization-owned managed profile on the parent
+     * profile, only the target user will be affected.
      *
      * <p>The default value is <code>false</code>.
      *
@@ -1026,8 +1089,9 @@
      * Specifies if the current user is able to receive content suggestions for selections based on
      * the contents of their screen.
      *
-     * <p>Device owner and profile owner can set this restriction. When it is set by device owner,
-     * only the target user will be affected.
+     * <p>Device owner and profile owner can set this restriction. When it is set by the
+     * device owner or the profile owner of an organization-owned managed profile on the parent
+     * profile, only the target user will be affected.
      *
      * <p>The default value is <code>false</code>.
      *
@@ -1093,7 +1157,9 @@
      *
      * <p>The default value is <code>false</code>.
      *
-     * <p>This user restriction can only be applied by the Device Owner.
+     * <p>This user restriction can only be applied by the device owner or the profile owner
+     * of an organization-owned managed profile on the parent profile.
+     *
      * <p>Key for user restrictions.
      * <p>Type: Boolean
      * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -1379,6 +1445,62 @@
         public @UserOperationResult int getUserOperationResult() {
             return mUserOperationResult;
         }
+
+        /**
+         * Returns a UserOperationException containing the same message and error code.
+         * @hide
+         */
+        public static UserOperationException from(ServiceSpecificException exception) {
+            return new UserOperationException(exception.getMessage(), exception.errorCode);
+        }
+    }
+
+    /**
+     * Converts the ServiceSpecificException into a UserOperationException or throws null;
+     *
+     * @param exception exception to convert.
+     * @param throwInsteadOfNull if an exception should be thrown or null returned.
+     * @return null if chosen not to throw exception.
+     * @throws UserOperationException
+     */
+    private <T> T returnNullOrThrowUserOperationException(ServiceSpecificException exception,
+            boolean throwInsteadOfNull) throws UserOperationException {
+        if (throwInsteadOfNull) {
+            throw UserOperationException.from(exception);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Thrown to indicate user operation failed. (Checked exception)
+     * @hide
+     */
+    public static class CheckedUserOperationException extends AndroidException {
+        private final @UserOperationResult int mUserOperationResult;
+
+        /**
+         * Constructs a CheckedUserOperationException with specific result code.
+         *
+         * @param message the detail message
+         * @param userOperationResult the result code
+         * @hide
+         */
+        public CheckedUserOperationException(String message,
+                @UserOperationResult int userOperationResult) {
+            super(message);
+            mUserOperationResult = userOperationResult;
+        }
+
+        /** Returns the operation result code. */
+        public @UserOperationResult int getUserOperationResult() {
+            return mUserOperationResult;
+        }
+
+        /** Return a ServiceSpecificException containing the same message and error code. */
+        public ServiceSpecificException toServiceSpecificException() {
+            return new ServiceSpecificException(mUserOperationResult, getMessage());
+        }
     }
 
     /** @hide */
@@ -1391,6 +1513,7 @@
     public UserManager(Context context, IUserManager service) {
         mService = service;
         mContext = context.getApplicationContext();
+        mUserId = context.getUserId();
     }
 
     /**
@@ -1530,17 +1653,25 @@
     }
 
     /**
-     * Returns the user name of the user making this call.  This call is only
-     * available to applications on the system image; it requires the
-     * {@code android.permission.MANAGE_USERS} or {@code android.permission.GET_ACCOUNTS_PRIVILEGED}
-     * permissions.
+     * Returns the user name of the context user. This call is only available to applications on
+     * the system image; it requires the {@code android.permission.MANAGE_USERS} or {@code
+     * android.permission.GET_ACCOUNTS_PRIVILEGED} permissions.
+     *
      * @return the user name
      */
-    public String getUserName() {
-        try {
-            return mService.getUserName();
-        } catch (RemoteException re) {
-            throw re.rethrowFromSystemServer();
+    @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}, conditional = true)
+    @UserHandleAware
+    public @NonNull String getUserName() {
+        if (UserHandle.myUserId() == mUserId) {
+            try {
+                return mService.getUserName();
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
+        } else {
+            UserInfo userInfo = getUserInfo(mUserId);
+            return userInfo == null ? "" : userInfo.name;
         }
     }
 
@@ -1583,7 +1714,8 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS})
     public boolean isPrimaryUser() {
         UserInfo user = getUserInfo(UserHandle.myUserId());
         return user != null && user.isPrimary();
@@ -1620,22 +1752,26 @@
      * user.
      */
     @UnsupportedAppUsage
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS})
     public boolean isUserAdmin(@UserIdInt int userId) {
         UserInfo user = getUserInfo(userId);
         return user != null && user.isAdmin();
     }
 
     /**
-     * Returns whether the current user is of the given user type, such as
+     * Returns whether the context user's user is of the given user type, such as
      * {@link UserManager#USER_TYPE_FULL_GUEST}.
      *
      * @return true if the user is of the given user type.
      * @hide
      */
+    @SystemApi
+    @UserHandleAware
     @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public boolean isUserOfType(@NonNull String userType) {
         try {
-            return mService.isUserOfType(UserHandle.myUserId(), userType);
+            return mService.isUserOfType(mUserId, userType);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -1651,6 +1787,7 @@
      * @return true if the userHandle user is of type userType
      * @hide
      */
+    @SystemApi
     @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public boolean isUserOfType(@NonNull UserHandle userHandle, @NonNull String userType) {
         try {
@@ -1730,7 +1867,8 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS})
     public boolean isRestrictedProfile(@NonNull UserHandle user) {
         try {
             return mService.getUserInfo(user.getIdentifier()).isRestricted();
@@ -1743,6 +1881,7 @@
      * Checks if specified user can have restricted profile.
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public boolean canHaveRestrictedProfile(@UserIdInt int userId) {
         try {
             return mService.canHaveRestrictedProfile(userId);
@@ -1757,6 +1896,7 @@
      * @hide
      */
     @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public boolean hasRestrictedProfiles() {
         try {
             return mService.hasRestrictedProfiles();
@@ -1771,6 +1911,8 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS})
     public boolean isGuestUser(@UserIdInt int userId) {
         UserInfo user = getUserInfo(userId);
         return user != null && user.isGuest();
@@ -1783,7 +1925,8 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS})
     public boolean isGuestUser() {
         UserInfo user = getUserInfo(UserHandle.myUserId());
         return user != null && user.isGuest();
@@ -1805,55 +1948,50 @@
     }
 
     /**
-     * Checks if the calling app is running in a profile.
+     * Checks if the calling context user is running in a profile.
+     *
+     * Requires {@link android.Manifest.permission#MANAGE_USERS} or
+     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission, otherwise the
+     * caller must be in the same profile group of specified user.
      *
      * @return whether the caller is in a profile.
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+    @UserHandleAware
     public boolean isProfile() {
-        // No need for synchronization.  Once it becomes non-null, it'll be non-null forever.
-        // Worst case we might end up calling the AIDL method multiple times but that's fine.
-        if (mIsProfileCached != null) {
-            return mIsProfileCached;
-        }
-        try {
-            mIsProfileCached = mService.isProfile(UserHandle.myUserId());
-            return mIsProfileCached;
-        } catch (RemoteException re) {
-            throw re.rethrowFromSystemServer();
+        return isProfile(mUserId);
+    }
+
+    private boolean isProfile(@UserIdInt int userId) {
+        if (userId == UserHandle.myUserId()) {
+            // No need for synchronization.  Once it becomes non-null, it'll be non-null forever.
+            // Worst case we might end up calling the AIDL method multiple times but that's fine.
+            if (mIsProfileCached != null) {
+                return mIsProfileCached;
+            }
+            try {
+                mIsProfileCached = mService.isProfile(userId);
+                return mIsProfileCached;
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
+        } else {
+            try {
+                return mService.isProfile(userId);
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
         }
     }
 
     /**
-     * Checks if the specified user is a profile.
-     *
-     * Requires {@link android.Manifest.permission#MANAGE_USERS} or
-     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission, otherwise the caller
-     * must be in the same profile group of specified user.
-     *
-     * @return whether the specified user is a profile.
-     * @hide
-     */
-    @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
-            android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
-    public boolean isProfile(@UserIdInt int userId) {
-        if (userId == UserHandle.myUserId()) {
-            return isProfile();
-        }
-        try {
-            return mService.isProfile(userId);
-        } catch (RemoteException re) {
-            throw re.rethrowFromSystemServer();
-        }
-    }
-    /**
      * Checks if the calling app is running in a managed profile.
      *
      * @return whether the caller is in a managed profile.
-     * @hide
      */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public boolean isManagedProfile() {
         // No need for synchronization.  Once it becomes non-null, it'll be non-null forever.
         // Worst case we might end up calling the AIDL method multiple times but that's fine.
@@ -1878,7 +2016,8 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+            Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
     public boolean isManagedProfile(@UserIdInt int userId) {
         if (userId == UserHandle.myUserId()) {
             return isManagedProfile();
@@ -1896,6 +2035,8 @@
      * @return whether the caller is an ephemeral user.
      * @hide
      */
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS})
     public boolean isEphemeralUser() {
         return isUserEphemeral(UserHandle.myUserId());
     }
@@ -1904,6 +2045,8 @@
      * Returns whether the specified user is ephemeral.
      * @hide
      */
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS})
     public boolean isUserEphemeral(@UserIdInt int userId) {
         final UserInfo user = getUserInfo(userId);
         return user != null && user.isEphemeral();
@@ -1925,12 +2068,15 @@
      *
      * @param user The user to retrieve the running state for.
      */
-    // Note this requires either INTERACT_ACROSS_USERS or MANAGE_USERS.
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
     public boolean isUserRunning(UserHandle user) {
         return isUserRunning(user.getIdentifier());
     }
 
     /** {@hide} */
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
     public boolean isUserRunning(@UserIdInt int userId) {
         try {
             return mService.isUserRunning(userId);
@@ -1954,7 +2100,8 @@
      *
      * @param user The user to retrieve the running state for.
      */
-    // Note this requires either INTERACT_ACROSS_USERS or MANAGE_USERS.
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
     public boolean isUserRunningOrStopping(UserHandle user) {
         try {
             // TODO: reconcile stopped vs stopping?
@@ -2001,12 +2148,16 @@
      * @see Intent#ACTION_USER_UNLOCKED
      * @see Context#createDeviceProtectedStorageContext()
      */
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
     public boolean isUserUnlocked(UserHandle user) {
         return isUserUnlocked(user.getIdentifier());
     }
 
     /** {@hide} */
     @UnsupportedAppUsage
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
     public boolean isUserUnlocked(@UserIdInt int userId) {
         try {
             return mService.isUserUnlocked(userId);
@@ -2034,15 +2185,15 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(anyOf = {
-            Manifest.permission.MANAGE_USERS,
-            Manifest.permission.INTERACT_ACROSS_USERS
-    })
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
     public boolean isUserUnlockingOrUnlocked(@NonNull UserHandle user) {
         return isUserUnlockingOrUnlocked(user.getIdentifier());
     }
 
     /** {@hide} */
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
     public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) {
         try {
             return mService.isUserUnlockingOrUnlocked(userId);
@@ -2083,12 +2234,13 @@
 
     /**
      * Returns the UserInfo object describing a specific user.
-     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
      * @param userId the user handle of the user whose information is being requested.
      * @return the UserInfo object for a specific user.
      * @hide
      */
     @UnsupportedAppUsage
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS})
     public UserInfo getUserInfo(@UserIdInt int userId) {
         try {
             return mService.getUserInfo(userId);
@@ -2178,6 +2330,7 @@
      * @param userHandle the UserHandle of the user for whom to retrieve the restrictions.
      */
     @UnsupportedAppUsage
+    @RequiresPermission(Manifest.permission.MANAGE_USERS)
     public boolean hasBaseUserRestriction(@UserRestrictionKey String restrictionKey,
             UserHandle userHandle) {
         try {
@@ -2218,6 +2371,7 @@
      * android.content.ComponentName, String)} instead.
      */
     @Deprecated
+    @RequiresPermission(Manifest.permission.MANAGE_USERS)
     public void setUserRestriction(String key, boolean value) {
         setUserRestriction(key, value, Process.myUserHandle());
     }
@@ -2225,7 +2379,6 @@
     /**
      * @hide
      * Sets the value of a specific restriction on a specific user.
-     * Requires the MANAGE_USERS permission.
      * @param key the key of the restriction
      * @param value the value for the restriction
      * @param userHandle the user whose restriction is to be changed.
@@ -2235,6 +2388,7 @@
      * android.content.ComponentName, String)} instead.
      */
     @Deprecated
+    @RequiresPermission(Manifest.permission.MANAGE_USERS)
     public void setUserRestriction(String key, boolean value, UserHandle userHandle) {
         try {
             mService.setUserRestriction(key, value, userHandle.getIdentifier());
@@ -2387,21 +2541,32 @@
      * Creates a user with the specified name and options. For non-admin users, default user
      * restrictions will be applied.
      *
-     * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+     * <p>Requires {@link android.Manifest.permission#MANAGE_USERS}.
+     * {@link android.Manifest.permission#CREATE_USERS} suffices if flags are in
+     * com.android.server.pm.UserManagerService#ALLOWED_FLAGS_FOR_CREATE_USERS_PERMISSION}.
      *
-     * @param name the user's name
+     * @param name     the user's name
      * @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_GUEST}.
-     * @param flags UserInfo flags that specify user properties.
-     * @see UserInfo
+     * @param flags    UserInfo flags that specify user properties.
+     * @return the {@link UserInfo} object for the created user,
+     *         or throws {@link UserOperationException} if the user could not be created
+     *         and calling app is targeting {@link android.os.Build.VERSION_CODES#R} or above
+     *         (otherwise returns {@code null}).
      *
-     * @return the UserInfo object for the created user, or {@code null} if the user could not be
-     * created.
+     * @throws UserOperationException if the user could not be created and the calling app is
+     *         targeting {@link android.os.Build.VERSION_CODES#R} or above.
      * @hide
+     * @see UserInfo
      */
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS})
     public @Nullable UserInfo createUser(@Nullable String name, @NonNull String userType,
             @UserInfoFlag int flags) {
         try {
-            return mService.createUser(name, userType, flags);
+            return mService.createUserWithThrow(name, userType, flags);
+        } catch (ServiceSpecificException e) {
+            return returnNullOrThrowUserOperationException(e,
+                    mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2416,20 +2581,37 @@
      * by {@link #createUser(String, String, int)} or {@link #createGuest(Context, String)}), it
      * takes less time.
      *
-     * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+     * <p>This method completes the majority of work necessary for user creation: it
+     * creates user data, CE and DE encryption keys, app data directories, initializes the user and
+     * grants default permissions. When pre-created users become "real" users, only then are
+     * components notified of new user creation by firing user creation broadcasts.
+     *
+     * <p>All pre-created users are removed during system upgrade.
+     *
+     * <p>Requires {@link android.Manifest.permission#MANAGE_USERS}.
+     * {@link android.Manifest.permission#CREATE_USERS} suffices if flags are in
+     * com.android.server.pm.UserManagerService#ALLOWED_FLAGS_FOR_CREATE_USERS_PERMISSION}.
      *
      * @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_GUEST}.
-     * @return the UserInfo object for the created user, or {@code null} if the user could not be
-     * created.
+     * @return the {@link UserInfo} object for the created user,
+     *         or throws {@link UserOperationException} if the user could not be created
+     *         and calling app is targeting {@link android.os.Build.VERSION_CODES#R} or above
+     *         (otherwise returns {@code null}).
      *
-     * @throw {@link IllegalArgumentException} if {@code flags} contains
-     * {@link UserInfo#FLAG_MANAGED_PROFILE}.
+     * @throws UserOperationException if the user could not be created and the calling app is
+     *         targeting {@link android.os.Build.VERSION_CODES#R} or above.
      *
      * @hide
      */
-    public @Nullable UserInfo preCreateUser(@NonNull String userType) {
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS})
+    public @Nullable UserInfo preCreateUser(@NonNull String userType)
+            throws UserOperationException {
         try {
-            return mService.preCreateUser(userType);
+            return mService.preCreateUserWithThrow(userType);
+        } catch (ServiceSpecificException e) {
+            return returnNullOrThrowUserOperationException(e,
+                    mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2439,16 +2621,28 @@
      * Creates a guest user and configures it.
      * @param context an application context
      * @param name the name to set for the user
+     * @return the {@link UserInfo} object for the created user,
+     *         or throws {@link UserOperationException} if the user could not be created
+     *         and calling app is targeting {@link android.os.Build.VERSION_CODES#R} or above
+     *         (otherwise returns {@code null}).
+     *
+     * @throws UserOperationException if the user could not be created and the calling app is
+     *         targeting {@link android.os.Build.VERSION_CODES#R} or above.
      * @hide
      */
-    public UserInfo createGuest(Context context, String name) {
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS})
+    public UserInfo createGuest(Context context, String name) throws UserOperationException {
         UserInfo guest = null;
         try {
-            guest = mService.createUser(name, USER_TYPE_FULL_GUEST, 0);
+            guest = mService.createUserWithThrow(name, USER_TYPE_FULL_GUEST, 0);
             if (guest != null) {
                 Settings.Secure.putStringForUser(context.getContentResolver(),
                         Settings.Secure.SKIP_FIRST_USE_HINTS, "1", guest.id);
             }
+        } catch (ServiceSpecificException e) {
+            return returnNullOrThrowUserOperationException(e,
+                    context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2470,9 +2664,42 @@
     }
 
     /**
+     * Creates a user with the specified name and options as a profile of the context's user.
+     *
+     * @param name the user's name.
+     * @param userType the type of user, such as {@link UserManager#USER_TYPE_PROFILE_MANAGED}.
+     * @param disallowedPackages packages to not install for this profile.
+     *
+     * @return the {@link android.os.UserHandle} object for the created user,
+     *         or throws {@link UserOperationException} if the user could not be created
+     *         and calling app is targeting {@link android.os.Build.VERSION_CODES#R} or above
+     *         (otherwise returns {@code null}).
+     *
+     * @throws UserOperationException if the user could not be created and the calling app is
+     *         targeting {@link android.os.Build.VERSION_CODES#R} or above.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS})
+    @UserHandleAware
+    public @Nullable UserHandle createProfile(@NonNull String name, @NonNull String userType,
+            @Nullable String[] disallowedPackages) throws UserOperationException {
+        try {
+            return mService.createProfileForUserWithThrow(name, userType, 0,
+                    mUserId, disallowedPackages).getUserHandle();
+        } catch (ServiceSpecificException e) {
+            return returnNullOrThrowUserOperationException(e,
+                    mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Creates a user with the specified name and options as a profile of another user.
-     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
-     * The type of profile must be specified using the given flags.
+     * <p>Requires MANAGE_USERS. CREATE_USERS suffices for ALLOWED_FLAGS_FOR_CREATE_USERS_PERMISSION
      *
      * @param name the user's name
      * @param flags flags that identify the type of user and other properties.
@@ -2485,6 +2712,8 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS})
     @Deprecated
     public UserInfo createProfileForUser(String name, @UserInfoFlag int flags,
             @UserIdInt int userId) {
@@ -2494,7 +2723,6 @@
 
     /**
      * Creates a user with the specified name and options as a profile of another user.
-     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
      *
      * @param name the user's name
      * @param userType the type of user, such as {@link UserManager#USER_TYPE_PROFILE_MANAGED}.
@@ -2505,6 +2733,8 @@
      *         could not be created.
      * @hide
      */
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS})
     public UserInfo createProfileForUser(String name, @NonNull String userType,
             @UserInfoFlag int flags, @UserIdInt int userId) {
         return createProfileForUser(name, userType, flags, userId, null);
@@ -2521,14 +2751,26 @@
      * @param userId new user will be a profile of this user.
      * @param disallowedPackages packages that will not be installed in the profile being created.
      *
-     * @return the {@link UserInfo} object for the created user, or null if the user
-     *         could not be created.
+     * @return the {@link UserInfo} object for the created user,
+     *         or throws {@link UserOperationException} if the user could not be created
+     *         and calling app is targeting {@link android.os.Build.VERSION_CODES#R} or above
+     *         (otherwise returns {@code null}).
+     *
+     * @throws UserOperationException if the user could not be created and the calling app is
+     *         targeting {@link android.os.Build.VERSION_CODES#R} or above.
      * @hide
      */
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS})
     public UserInfo createProfileForUser(String name, @NonNull String userType,
-            @UserInfoFlag int flags, @UserIdInt int userId, String[] disallowedPackages) {
+            @UserInfoFlag int flags, @UserIdInt int userId, String[] disallowedPackages)
+            throws UserOperationException {
         try {
-            return mService.createProfileForUser(name, userType, flags, userId, disallowedPackages);
+            return mService.createProfileForUserWithThrow(name, userType, flags, userId,
+                    disallowedPackages);
+        } catch (ServiceSpecificException e) {
+            return returnNullOrThrowUserOperationException(e,
+                    mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2537,17 +2779,21 @@
     /**
      * Similar to {@link #createProfileForUser(String, String, int, int, String[])}
      * except bypassing the checking of {@link UserManager#DISALLOW_ADD_MANAGED_PROFILE}.
-     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
      *
      * @see #createProfileForUser(String, String, int, int, String[])
      * @hide
      */
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS})
     public UserInfo createProfileForUserEvenWhenDisallowed(String name,
             @NonNull String userType, @UserInfoFlag int flags, @UserIdInt int userId,
-            String[] disallowedPackages) {
+            String[] disallowedPackages) throws UserOperationException {
         try {
-            return mService.createProfileForUserEvenWhenDisallowed(name, userType, flags,
+            return mService.createProfileForUserEvenWhenDisallowedWithThrow(name, userType, flags,
                     userId, disallowedPackages);
+        } catch (ServiceSpecificException e) {
+            return returnNullOrThrowUserOperationException(e,
+                    mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2558,19 +2804,30 @@
      * restrictions and adds shared accounts.
      *
      * @param name profile's name
-     * @return UserInfo object for the created user, or null if the user could not be created.
+     * @return the {@link UserInfo} object for the created user,
+     *         or throws {@link UserOperationException} if the user could not be created
+     *         and calling app is targeting {@link android.os.Build.VERSION_CODES#R} or above
+     *         (otherwise returns {@code null}).
+     *
+     * @throws UserOperationException if the user could not be created and the calling app is
+     *         targeting {@link android.os.Build.VERSION_CODES#R} or above.
      * @hide
      */
-    public UserInfo createRestrictedProfile(String name) {
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS})
+    public UserInfo createRestrictedProfile(String name) throws UserOperationException {
         try {
             UserHandle parentUserHandle = Process.myUserHandle();
-            UserInfo user = mService.createRestrictedProfile(name,
+            UserInfo user = mService.createRestrictedProfileWithThrow(name,
                     parentUserHandle.getIdentifier());
             if (user != null) {
                 AccountManager.get(mContext).addSharedAccountsFromParentUser(parentUserHandle,
                         UserHandle.of(user.id));
             }
             return user;
+        } catch (ServiceSpecificException e) {
+            return returnNullOrThrowUserOperationException(e,
+                    mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2687,6 +2944,7 @@
      * @param accountOptions
      * @see #createUserCreationIntent(String, String, String, PersistableBundle)
      */
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public void setSeedAccountData(int userId, String accountName, String accountType,
             PersistableBundle accountOptions) {
         try {
@@ -2718,6 +2976,7 @@
      * @param userId
      * @return
      */
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public boolean markGuestForDeletion(@UserIdInt int userId) {
         try {
             return mService.markGuestForDeletion(userId);
@@ -2729,8 +2988,6 @@
     /**
      * Sets the user as enabled, if such an user exists.
      *
-     * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
-     *
      * <p>Note that the default is true, it's only that managed profiles might not be enabled.
      * Also ephemeral users can be disabled to indicate that their removal is in progress and they
      * shouldn't be re-entered. Therefore ephemeral users should not be re-enabled once disabled.
@@ -2738,6 +2995,7 @@
      * @param userId the id of the profile to enable
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public void setUserEnabled(@UserIdInt int userId) {
         try {
             mService.setUserEnabled(userId);
@@ -2771,6 +3029,7 @@
      *
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public void evictCredentialEncryptionKey(@UserIdInt int userId) {
         try {
             mService.evictCredentialEncryptionKey(userId);
@@ -2784,6 +3043,7 @@
      * <p>This API is not for use by third-party apps. It requires the {@code MANAGE_USERS}
      * permission.</p>
      */
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public int getUserCount() {
         List<UserInfo> users = getUsers();
         return users != null ? users.size() : 1;
@@ -2902,11 +3162,11 @@
 
     /**
      * Returns information for Primary user.
-     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
      *
      * @return the Primary user, null if not found.
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public @Nullable UserInfo getPrimaryUser() {
         try {
             return mService.getPrimaryUser();
@@ -2922,6 +3182,7 @@
      * @return true if more users can be added, false if limit has been reached.
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public boolean canAddMoreUsers() {
         // TODO(b/142482943): UMS has different logic, excluding Demo and Profile from counting. Why
         //                    not here? The logic is inconsistent. See UMS.canAddMoreManagedProfiles
@@ -2946,6 +3207,7 @@
      * @return true if more managed profiles can be added, false if limit has been reached.
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public boolean canAddMoreManagedProfiles(@UserIdInt int userId, boolean allowedToRemoveOne) {
         try {
             return mService.canAddMoreManagedProfiles(userId, allowedToRemoveOne);
@@ -2975,12 +3237,15 @@
      * Note that this returns both enabled and not enabled profiles. See
      * {@link #getEnabledProfiles(int)} if you need only the enabled ones.
      *
-     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+     * <p>Requires {@link android.Manifest.permission#MANAGE_USERS}.
+     * {@link android.Manifest.permission#CREATE_USERS} suffices if userId is the calling user.
      * @param userId profiles of this user will be returned.
      * @return the list of profiles.
      * @hide
      */
     @UnsupportedAppUsage
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS}, conditional = true)
     public List<UserInfo> getProfiles(@UserIdInt int userId) {
         try {
             return mService.getProfiles(userId, false /* enabledOnly */);
@@ -2996,7 +3261,6 @@
      * @param otherUser one of the two user handles to check.
      * @return true if the two users are in the same profile group.
      *
-     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
      * @hide
      */
     @SystemApi
@@ -3006,12 +3270,13 @@
     }
 
     /**
-     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+     * Checks if the 2 provided user ids belong to the same profile group.
      * @param userId one of the two user ids to check.
      * @param otherUserId one of the two user ids to check.
      * @return true if the two user ids are in the same profile group.
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public boolean isSameProfileGroup(@UserIdInt int userId, int otherUserId) {
         try {
             return mService.isSameProfileGroup(userId, otherUserId);
@@ -3024,12 +3289,15 @@
      * Returns list of the profiles of userId including userId itself.
      * Note that this returns only enabled.
      *
-     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+     * <p>Requires {@link android.Manifest.permission#MANAGE_USERS}.
+     * {@link android.Manifest.permission#CREATE_USERS} suffices if userId is the calling user.
      * @param userId profiles of this user will be returned.
      * @return the list of profiles.
      * @hide
      */
     @UnsupportedAppUsage
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS}, conditional = true)
     public List<UserInfo> getEnabledProfiles(@UserIdInt int userId) {
         try {
             return mService.getProfiles(userId, true /* enabledOnly */);
@@ -3054,6 +3322,28 @@
     }
 
     /**
+     * Returns a list of ids for profiles associated with the context user including the user
+     * itself.
+     *
+     * @param enabledOnly whether to return only {@link UserInfo#isEnabled() enabled} profiles
+     * @return A non-empty list of UserHandles associated with the calling user.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS}, conditional = true)
+    @UserHandleAware
+    public @NonNull List<UserHandle> getUserProfiles(boolean enabledOnly) {
+        final int[] userIds = getProfileIds(mUserId, enabledOnly);
+        final List<UserHandle> result = new ArrayList<>(userIds.length);
+        for (int userId : userIds) {
+            result.add(UserHandle.of(userId));
+        }
+        return result;
+    }
+
+    /**
      * Returns a list of ids for profiles associated with the specified user including the user
      * itself.
      *
@@ -3078,6 +3368,8 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS}, conditional = true)
     public int[] getProfileIdsWithDisabled(@UserIdInt int userId) {
         return getProfileIds(userId, false /* enabledOnly */);
     }
@@ -3086,6 +3378,8 @@
      * @see #getProfileIds(int, boolean)
      * @hide
      */
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS}, conditional = true)
     public int[] getEnabledProfileIds(@UserIdInt int userId) {
         return getProfileIds(userId, true /* enabledOnly */);
     }
@@ -3097,6 +3391,7 @@
      *
      * @hide
      */
+    @RequiresPermission(Manifest.permission.MANAGE_USERS)
     public int getCredentialOwnerProfile(@UserIdInt int userId) {
         try {
             return mService.getCredentialOwnerProfile(userId);
@@ -3112,6 +3407,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public UserInfo getProfileParent(@UserIdInt int userId) {
         try {
             return mService.getProfileParent(userId);
@@ -3166,11 +3462,32 @@
      *
      * @see #isQuietModeEnabled(UserHandle)
      */
+    @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+            Manifest.permission.MODIFY_QUIET_MODE}, conditional = true)
     public boolean requestQuietModeEnabled(boolean enableQuietMode, @NonNull UserHandle userHandle) {
         return requestQuietModeEnabled(enableQuietMode, userHandle, null);
     }
 
     /**
+     * Perform the same operation as {@link #requestQuietModeEnabled(boolean, UserHandle)}, but
+     * with a flag to tweak the behavior of the request.
+     *
+     * @param enableQuietMode whether quiet mode should be enabled or disabled
+     * @param userHandle user handle of the profile
+     * @param flags Can be 0 or {@link #QUIET_MODE_DISABLE_ONLY_IF_CREDENTIAL_NOT_REQUIRED}.
+     * @return {@code false} if user's credential is needed in order to turn off quiet mode,
+     *         {@code true} otherwise
+     * @throws SecurityException if the caller is invalid
+     * @throws IllegalArgumentException if {@code userHandle} is not a managed profile
+     *
+     * @see #isQuietModeEnabled(UserHandle)
+     */
+    public boolean requestQuietModeEnabled(boolean enableQuietMode, @NonNull UserHandle userHandle,
+            @QuietModeFlag int flags) {
+        return requestQuietModeEnabled(enableQuietMode, userHandle, null, flags);
+    }
+
+    /**
      * Similar to {@link #requestQuietModeEnabled(boolean, UserHandle)}, except you can specify
      * a target to start when user is unlocked. If {@code target} is specified, caller must have
      * the {@link android.Manifest.permission#MANAGE_USERS} permission.
@@ -3178,11 +3495,26 @@
      * @see {@link #requestQuietModeEnabled(boolean, UserHandle)}
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public boolean requestQuietModeEnabled(
             boolean enableQuietMode, @NonNull UserHandle userHandle, IntentSender target) {
+        return requestQuietModeEnabled(enableQuietMode, userHandle, target, 0);
+    }
+    /**
+     * Similar to {@link #requestQuietModeEnabled(boolean, UserHandle)}, except you can specify
+     * a target to start when user is unlocked. If {@code target} is specified, caller must have
+     * the {@link android.Manifest.permission#MANAGE_USERS} permission.
+     *
+     * @see {@link #requestQuietModeEnabled(boolean, UserHandle)}
+     * @hide
+     */
+    public boolean requestQuietModeEnabled(
+            boolean enableQuietMode, @NonNull UserHandle userHandle, IntentSender target,
+            int flags) {
         try {
             return mService.requestQuietModeEnabled(
-                    mContext.getPackageName(), enableQuietMode, userHandle.getIdentifier(), target);
+                    mContext.getPackageName(), enableQuietMode, userHandle.getIdentifier(), target,
+                    flags);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -3225,15 +3557,16 @@
     }
 
     /**
-     * Returns whether the calling app's user has a badge (generally to put on profiles' icons).
+     * Returns whether the user associated with the context has a badge (generally to put on
+     * profiles' icons).
      *
      * @return true if the user's icons should display a badge; false otherwise.
-     *
      * @see #getBadgedIconForUser more information about badging in general
      * @hide
      */
+    @UserHandleAware
     public boolean hasBadge() {
-        return hasBadge(UserHandle.myUserId());
+        return hasBadge(mUserId);
     }
 
     /**
@@ -3391,11 +3724,12 @@
 
     /**
      * Removes a user and all associated data.
-     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
      * @param userId the integer handle of the user.
      * @hide
      */
     @UnsupportedAppUsage
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS})
     public boolean removeUser(@UserIdInt int userId) {
         try {
             return mService.removeUser(userId);
@@ -3413,7 +3747,8 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS})
     public boolean removeUser(@NonNull UserHandle user) {
         if (user == null) {
             throw new IllegalArgumentException("user cannot be null");
@@ -3430,6 +3765,8 @@
      * @see {@link #removeUser(int)}
      * @hide
      */
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS})
     public boolean removeUserEvenWhenDisallowed(@UserIdInt int userId) {
         try {
             return mService.removeUserEvenWhenDisallowed(userId);
@@ -3440,12 +3777,12 @@
 
     /**
      * Updates the user's name.
-     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
      *
      * @param userId the user's integer id
      * @param name the new name for the user
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public void setUserName(@UserIdInt int userId, String name) {
         try {
             mService.setUserName(userId, name);
@@ -3455,16 +3792,16 @@
     }
 
     /**
-     * Updates the calling user's name.
-     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+     * Updates the context user's name.
      *
      * @param name the new name for the user
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    @UserHandleAware
     public void setUserName(@Nullable String name) {
-        setUserName(getUserHandle(), name);
+        setUserName(mUserId, name);
     }
 
     /**
@@ -3473,35 +3810,41 @@
      * @param icon the bitmap to set as the photo.
      * @hide
      */
-    public void setUserIcon(@UserIdInt int userId, Bitmap icon) {
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public void setUserIcon(@UserIdInt int userId, @NonNull Bitmap icon)
+            throws UserOperationException {
         try {
             mService.setUserIcon(userId, icon);
+        } catch (ServiceSpecificException e) {
+            throw UserOperationException.from(e);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
     }
 
     /**
-     * Sets the calling user's photo.
-     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+     * Sets the context user's photo.
      *
      * @param icon the bitmap to set as the photo.
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
-    public void setUserIcon(@NonNull Bitmap icon) {
-        setUserIcon(getUserHandle(), icon);
+    @UserHandleAware
+    public void setUserIcon(@NonNull Bitmap icon) throws UserOperationException {
+        setUserIcon(mUserId, icon);
     }
 
     /**
-     * Returns a file descriptor for the user's photo. PNG data can be read from this file.
+     * Returns a bitmap of the user's photo
      * @param userId the user whose photo we want to read.
      * @return a {@link Bitmap} of the user's photo, or null if there's no photo.
      * @see com.android.internal.util.UserIcons#getDefaultUserIcon for a default.
      * @hide
      */
     @UnsupportedAppUsage
+    @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED})
     public Bitmap getUserIcon(@UserIdInt int userId) {
         try {
             ParcelFileDescriptor fd = mService.getUserIcon(userId);
@@ -3522,9 +3865,7 @@
     }
 
     /**
-     * Returns a Bitmap for the calling user's photo.
-     * Requires {@link android.Manifest.permission#MANAGE_USERS}
-     * or {@link android.Manifest.permission#GET_ACCOUNTS_PRIVILEGED} permissions.
+     * Returns a Bitmap for the context user's photo.
      *
      * @return a {@link Bitmap} of the user's photo, or null if there's no photo.
      * @see com.android.internal.util.UserIcons#getDefaultUserIcon for a default.
@@ -3533,8 +3874,9 @@
     @SystemApi
     @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
             android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED})
+    @UserHandleAware
     public @Nullable Bitmap getUserIcon() {
-        return getUserIcon(getUserHandle());
+        return getUserIcon(mUserId);
     }
 
     /**
@@ -3711,6 +4053,7 @@
      * @hide
      * Set restrictions that should apply to any future guest user that's created.
      */
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public void setDefaultGuestRestrictions(Bundle restrictions) {
         try {
             mService.setDefaultGuestRestrictions(restrictions);
@@ -3723,6 +4066,7 @@
      * @hide
      * Gets the default guest restrictions.
      */
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public Bundle getDefaultGuestRestrictions() {
         try {
             return mService.getDefaultGuestRestrictions();
@@ -3753,6 +4097,7 @@
      * @param accountType The account type of the account to check for
      * @return whether the seed account was found
      */
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public boolean someUserHasSeedAccount(String accountName, String accountType) {
         try {
             return mService.someUserHasSeedAccount(accountName, accountType);
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index ccbb0f1..ae75f3d 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -155,7 +155,7 @@
     /**
      * Configure an always-on haptics effect.
      *
-     * @param id The board-specific always-on ID to configure.
+     * @param alwaysOnId The board-specific always-on ID to configure.
      * @param effect Vibration effect to assign to always-on id. Passing null will disable it.
      * @param attributes {@link AudioAttributes} corresponding to the vibration. For example,
      *        specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or
@@ -164,8 +164,17 @@
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)
-    public boolean setAlwaysOnEffect(int id, @Nullable VibrationEffect effect,
-                                  @Nullable AudioAttributes attributes) {
+    public boolean setAlwaysOnEffect(int alwaysOnId, @Nullable VibrationEffect effect,
+            @Nullable AudioAttributes attributes) {
+        return setAlwaysOnEffect(Process.myUid(), mPackageName, alwaysOnId, effect, attributes);
+    }
+
+    /**
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)
+    public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
+            @Nullable VibrationEffect effect, @Nullable AudioAttributes attributes) {
         Log.w(TAG, "Always-on effects aren't supported");
         return false;
     }
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 62b8953..3846f89 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -594,6 +594,8 @@
             argsForZygote.add("--mount-external-legacy");
         } else if (mountExternal == Zygote.MOUNT_EXTERNAL_PASS_THROUGH) {
             argsForZygote.add("--mount-external-pass-through");
+        } else if (mountExternal == Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE) {
+            argsForZygote.add("--mount-external-android-writable");
         }
 
         argsForZygote.add("--target-sdk-version=" + targetSdkVersion);
diff --git a/core/java/android/os/image/DynamicSystemClient.java b/core/java/android/os/image/DynamicSystemClient.java
index 921f0f2..5cb3361 100644
--- a/core/java/android/os/image/DynamicSystemClient.java
+++ b/core/java/android/os/image/DynamicSystemClient.java
@@ -256,9 +256,13 @@
                 mService.send(msg);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Unable to get status from installation service");
-                mExecutor.execute(() -> {
+                if (mExecutor != null) {
+                    mExecutor.execute(() -> {
+                        mListener.onStatusChanged(STATUS_UNKNOWN, CAUSE_ERROR_IPC, 0, e);
+                    });
+                } else {
                     mListener.onStatusChanged(STATUS_UNKNOWN, CAUSE_ERROR_IPC, 0, e);
-                });
+                }
             }
         }
 
diff --git a/core/java/android/os/image/DynamicSystemManager.java b/core/java/android/os/image/DynamicSystemManager.java
index 4c92c28..cbf531c 100644
--- a/core/java/android/os/image/DynamicSystemManager.java
+++ b/core/java/android/os/image/DynamicSystemManager.java
@@ -106,9 +106,9 @@
      * @return true if the call succeeds
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
-    public boolean startInstallation() {
+    public boolean startInstallation(String dsuSlot) {
         try {
-            return mService.startInstallation();
+            return mService.startInstallation(dsuSlot);
         } catch (RemoteException e) {
             throw new RuntimeException(e.toString());
         }
diff --git a/core/java/android/os/image/IDynamicSystemService.aidl b/core/java/android/os/image/IDynamicSystemService.aidl
index 69cbab2..cc32f99 100644
--- a/core/java/android/os/image/IDynamicSystemService.aidl
+++ b/core/java/android/os/image/IDynamicSystemService.aidl
@@ -22,9 +22,10 @@
 {
     /**
      * Start DynamicSystem installation.
+     * @param dsuSlot Name used to identify this installation
      * @return true if the call succeeds
      */
-    boolean startInstallation();
+    boolean startInstallation(@utf8InCpp String dsuSlot);
 
     /**
      * Create a DSU partition. This call may take 60~90 seconds. The caller
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index 92fecad..bbc936d 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -193,4 +193,5 @@
     void startCheckpoint(int numTries) = 85;
     boolean needsCheckpoint() = 86;
     void abortChanges(in String message, boolean retry) = 87;
+    void clearUserKeyAuth(int userId, int serialNumber, in byte[] token, in byte[] secret) = 88;
 }
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 8959fcf..3ea64f1 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -31,6 +31,7 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
 import android.annotation.BytesLong;
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -72,6 +73,7 @@
 import android.os.ServiceManager;
 import android.os.ServiceManager.ServiceNotFoundException;
 import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.provider.MediaStore;
 import android.provider.Settings;
 import android.sysprop.VoldProperties;
@@ -93,7 +95,6 @@
 import com.android.internal.os.FuseAppLoop;
 import com.android.internal.os.FuseUnavailableMountException;
 import com.android.internal.os.RoSystemProperties;
-import com.android.internal.os.SomeArgs;
 import com.android.internal.util.Preconditions;
 
 import dalvik.system.BlockGuard;
@@ -114,6 +115,7 @@
 import java.util.Objects;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -305,109 +307,85 @@
     private final Looper mLooper;
     private final AtomicInteger mNextNonce = new AtomicInteger(0);
 
+    @GuardedBy("mDelegates")
     private final ArrayList<StorageEventListenerDelegate> mDelegates = new ArrayList<>();
 
-    private static class StorageEventListenerDelegate extends IStorageEventListener.Stub implements
-            Handler.Callback {
-        private static final int MSG_STORAGE_STATE_CHANGED = 1;
-        private static final int MSG_VOLUME_STATE_CHANGED = 2;
-        private static final int MSG_VOLUME_RECORD_CHANGED = 3;
-        private static final int MSG_VOLUME_FORGOTTEN = 4;
-        private static final int MSG_DISK_SCANNED = 5;
-        private static final int MSG_DISK_DESTROYED = 6;
+    private class StorageEventListenerDelegate extends IStorageEventListener.Stub {
+        final Executor mExecutor;
+        final StorageEventListener mListener;
+        final StorageVolumeCallback mCallback;
 
-        final StorageEventListener mCallback;
-        final Handler mHandler;
-
-        public StorageEventListenerDelegate(StorageEventListener callback, Looper looper) {
+        public StorageEventListenerDelegate(@NonNull Executor executor,
+                @NonNull StorageEventListener listener, @NonNull StorageVolumeCallback callback) {
+            mExecutor = executor;
+            mListener = listener;
             mCallback = callback;
-            mHandler = new Handler(looper, this);
-        }
-
-        @Override
-        public boolean handleMessage(Message msg) {
-            final SomeArgs args = (SomeArgs) msg.obj;
-            switch (msg.what) {
-                case MSG_STORAGE_STATE_CHANGED:
-                    mCallback.onStorageStateChanged((String) args.arg1, (String) args.arg2,
-                            (String) args.arg3);
-                    args.recycle();
-                    return true;
-                case MSG_VOLUME_STATE_CHANGED:
-                    mCallback.onVolumeStateChanged((VolumeInfo) args.arg1, args.argi2, args.argi3);
-                    args.recycle();
-                    return true;
-                case MSG_VOLUME_RECORD_CHANGED:
-                    mCallback.onVolumeRecordChanged((VolumeRecord) args.arg1);
-                    args.recycle();
-                    return true;
-                case MSG_VOLUME_FORGOTTEN:
-                    mCallback.onVolumeForgotten((String) args.arg1);
-                    args.recycle();
-                    return true;
-                case MSG_DISK_SCANNED:
-                    mCallback.onDiskScanned((DiskInfo) args.arg1, args.argi2);
-                    args.recycle();
-                    return true;
-                case MSG_DISK_DESTROYED:
-                    mCallback.onDiskDestroyed((DiskInfo) args.arg1);
-                    args.recycle();
-                    return true;
-            }
-            args.recycle();
-            return false;
         }
 
         @Override
         public void onUsbMassStorageConnectionChanged(boolean connected) throws RemoteException {
-            // Ignored
+            mExecutor.execute(() -> {
+                mListener.onUsbMassStorageConnectionChanged(connected);
+            });
         }
 
         @Override
         public void onStorageStateChanged(String path, String oldState, String newState) {
-            final SomeArgs args = SomeArgs.obtain();
-            args.arg1 = path;
-            args.arg2 = oldState;
-            args.arg3 = newState;
-            mHandler.obtainMessage(MSG_STORAGE_STATE_CHANGED, args).sendToTarget();
+            mExecutor.execute(() -> {
+                mListener.onStorageStateChanged(path, oldState, newState);
+
+                if (path != null) {
+                    for (StorageVolume sv : getStorageVolumes()) {
+                        if (Objects.equals(path, sv.getPath())) {
+                            mCallback.onStateChanged(sv);
+                        }
+                    }
+                }
+            });
         }
 
         @Override
         public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
-            final SomeArgs args = SomeArgs.obtain();
-            args.arg1 = vol;
-            args.argi2 = oldState;
-            args.argi3 = newState;
-            mHandler.obtainMessage(MSG_VOLUME_STATE_CHANGED, args).sendToTarget();
+            mExecutor.execute(() -> {
+                mListener.onVolumeStateChanged(vol, oldState, newState);
+
+                final File path = vol.getPathForUser(UserHandle.myUserId());
+                if (path != null) {
+                    for (StorageVolume sv : getStorageVolumes()) {
+                        if (Objects.equals(path.getAbsolutePath(), sv.getPath())) {
+                            mCallback.onStateChanged(sv);
+                        }
+                    }
+                }
+            });
         }
 
         @Override
         public void onVolumeRecordChanged(VolumeRecord rec) {
-            final SomeArgs args = SomeArgs.obtain();
-            args.arg1 = rec;
-            mHandler.obtainMessage(MSG_VOLUME_RECORD_CHANGED, args).sendToTarget();
+            mExecutor.execute(() -> {
+                mListener.onVolumeRecordChanged(rec);
+            });
         }
 
         @Override
         public void onVolumeForgotten(String fsUuid) {
-            final SomeArgs args = SomeArgs.obtain();
-            args.arg1 = fsUuid;
-            mHandler.obtainMessage(MSG_VOLUME_FORGOTTEN, args).sendToTarget();
+            mExecutor.execute(() -> {
+                mListener.onVolumeForgotten(fsUuid);
+            });
         }
 
         @Override
         public void onDiskScanned(DiskInfo disk, int volumeCount) {
-            final SomeArgs args = SomeArgs.obtain();
-            args.arg1 = disk;
-            args.argi2 = volumeCount;
-            mHandler.obtainMessage(MSG_DISK_SCANNED, args).sendToTarget();
+            mExecutor.execute(() -> {
+                mListener.onDiskScanned(disk, volumeCount);
+            });
         }
 
         @Override
         public void onDiskDestroyed(DiskInfo disk) throws RemoteException {
-            final SomeArgs args = SomeArgs.obtain();
-            args.arg1 = disk;
-            mHandler.obtainMessage(MSG_DISK_DESTROYED, args).sendToTarget();
+            mExecutor.execute(() -> {
+                mListener.onDiskDestroyed(disk);
+            });
         }
     }
 
@@ -525,8 +503,8 @@
     @UnsupportedAppUsage
     public void registerListener(StorageEventListener listener) {
         synchronized (mDelegates) {
-            final StorageEventListenerDelegate delegate = new StorageEventListenerDelegate(listener,
-                    mLooper);
+            final StorageEventListenerDelegate delegate = new StorageEventListenerDelegate(
+                    mContext.getMainExecutor(), listener, new StorageVolumeCallback());
             try {
                 mStorageManager.registerListener(delegate);
             } catch (RemoteException e) {
@@ -548,7 +526,76 @@
         synchronized (mDelegates) {
             for (Iterator<StorageEventListenerDelegate> i = mDelegates.iterator(); i.hasNext();) {
                 final StorageEventListenerDelegate delegate = i.next();
-                if (delegate.mCallback == listener) {
+                if (delegate.mListener == listener) {
+                    try {
+                        mStorageManager.unregisterListener(delegate);
+                    } catch (RemoteException e) {
+                        throw e.rethrowFromSystemServer();
+                    }
+                    i.remove();
+                }
+            }
+        }
+    }
+
+    /**
+     * Callback that delivers {@link StorageVolume} related events.
+     * <p>
+     * For example, this can be used to detect when a volume changes to the
+     * {@link Environment#MEDIA_MOUNTED} or {@link Environment#MEDIA_UNMOUNTED}
+     * states.
+     *
+     * @see StorageManager#registerStorageVolumeCallback
+     * @see StorageManager#unregisterStorageVolumeCallback
+     */
+    public static class StorageVolumeCallback {
+        /**
+         * Called when {@link StorageVolume#getState()} changes, such as
+         * changing to the {@link Environment#MEDIA_MOUNTED} or
+         * {@link Environment#MEDIA_UNMOUNTED} states.
+         * <p>
+         * The given argument is a snapshot in time and can be used to process
+         * events in the order they occurred, or you can call
+         * {@link StorageManager#getStorageVolumes()} to observe the latest
+         * value.
+         */
+        public void onStateChanged(@NonNull StorageVolume volume) { }
+    }
+
+    /**
+     * Registers the given callback to listen for {@link StorageVolume} changes.
+     * <p>
+     * For example, this can be used to detect when a volume changes to the
+     * {@link Environment#MEDIA_MOUNTED} or {@link Environment#MEDIA_UNMOUNTED}
+     * states.
+     *
+     * @see StorageManager#unregisterStorageVolumeCallback
+     */
+    public void registerStorageVolumeCallback(@CallbackExecutor @NonNull Executor executor,
+            @NonNull StorageVolumeCallback callback) {
+        synchronized (mDelegates) {
+            final StorageEventListenerDelegate delegate = new StorageEventListenerDelegate(
+                    executor, new StorageEventListener(), callback);
+            try {
+                mStorageManager.registerListener(delegate);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+            mDelegates.add(delegate);
+        }
+    }
+
+    /**
+     * Unregisters the given callback from listening for {@link StorageVolume}
+     * changes.
+     *
+     * @see StorageManager#registerStorageVolumeCallback
+     */
+    public void unregisterStorageVolumeCallback(@NonNull StorageVolumeCallback callback) {
+        synchronized (mDelegates) {
+            for (Iterator<StorageEventListenerDelegate> i = mDelegates.iterator(); i.hasNext();) {
+                final StorageEventListenerDelegate delegate = i.next();
+                if (delegate.mCallback == callback) {
                     try {
                         mStorageManager.unregisterListener(delegate);
                     } catch (RemoteException e) {
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 2ab226f..e251f80 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -162,9 +163,13 @@
         mState = in.readString();
     }
 
-    /** {@hide} */
-    @UnsupportedAppUsage
-    public String getId() {
+    /**
+     * Return an opaque ID that can be used to identify this volume.
+     *
+     * @hide
+     */
+    @SystemApi
+    public @NonNull String getId() {
         return mId;
     }
 
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 2583292..5a1ba7f 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -160,6 +160,7 @@
      * Grant default permissions to currently active LUI app
      * @param packageName The package name for the LUI app
      * @param user The user handle
+     * @param executor The executor for the callback
      * @param callback The callback provided by caller to be notified when grant completes
      * @hide
      */
@@ -181,6 +182,7 @@
      * Revoke default permissions to currently active LUI app
      * @param packageNames The package names for the LUI apps
      * @param user The user handle
+     * @param executor The executor for the callback
      * @param callback The callback provided by caller to be notified when grant completes
      * @hide
      */
@@ -198,6 +200,72 @@
         }
     }
 
+    /**
+     * Grant default permissions to currently active Ims services
+     * @param packageNames The package names for the Ims services
+     * @param user The user handle
+     * @param executor The executor for the callback
+     * @param callback The callback provided by caller to be notified when grant completes
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS)
+    public void grantDefaultPermissionsToEnabledImsServices(
+            @NonNull String[] packageNames, @NonNull UserHandle user,
+            @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
+        try {
+            mPermissionManager.grantDefaultPermissionsToEnabledImsServices(
+                    packageNames, user.getIdentifier());
+            executor.execute(() -> callback.accept(true));
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Grant default permissions to currently enabled telephony data services
+     * @param packageNames The package name for the services
+     * @param user The user handle
+     * @param executor The executor for the callback
+     * @param callback The callback provided by caller to be notified when grant completes
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS)
+    public void grantDefaultPermissionsToEnabledTelephonyDataServices(
+            @NonNull String[] packageNames, @NonNull UserHandle user,
+            @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
+        try {
+            mPermissionManager.grantDefaultPermissionsToEnabledTelephonyDataServices(
+                    packageNames, user.getIdentifier());
+            executor.execute(() -> callback.accept(true));
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Revoke default permissions to currently active telephony data services
+     * @param packageNames The package name for the services
+     * @param user The user handle
+     * @param executor The executor for the callback
+     * @param callback The callback provided by caller to be notified when revoke completes
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS)
+    public void revokeDefaultPermissionsFromDisabledTelephonyDataServices(
+            @NonNull String[] packageNames, @NonNull UserHandle user,
+            @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
+        try {
+            mPermissionManager.revokeDefaultPermissionsFromDisabledTelephonyDataServices(
+                    packageNames, user.getIdentifier());
+            executor.execute(() -> callback.accept(true));
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
     private List<SplitPermissionInfo> splitPermissionInfoListToNonParcelableList(
             List<SplitPermissionInfoParcelable> parcelableList) {
         final int size = parcelableList.size();
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 6650cf2..091d78e 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -333,6 +333,15 @@
     public static final String NAMESPACE_WINDOW_MANAGER = "window_manager";
 
     /**
+     * Namespace for window manager features accessible by native code and
+     * loaded once per boot.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT = "window_manager_native_boot";
+
+    /**
      * List of namespaces which can be read without READ_DEVICE_CONFIG permission
      *
      * @hide
@@ -424,6 +433,7 @@
      * @hide
      */
     @SystemApi
+    @TestApi
     @NonNull
     @RequiresPermission(READ_DEVICE_CONFIG)
     public static Properties getProperties(@NonNull String namespace, @NonNull String ... names) {
@@ -593,6 +603,7 @@
      * @hide
      */
     @SystemApi
+    @TestApi
     @RequiresPermission(WRITE_DEVICE_CONFIG)
     public static boolean setProperties(@NonNull Properties properties) throws BadConfigException {
         ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
@@ -785,6 +796,14 @@
     }
 
     /**
+     * Returns list of namespaces that can be read without READ_DEVICE_CONFIG_PERMISSION;
+     * @hide
+     */
+    public static @NonNull List<String> getPublicNamespaces() {
+        return PUBLIC_NAMESPACES;
+    }
+
+    /**
      * Interface for monitoring changes to properties. Implementations will receive callbacks when
      * properties change, including a {@link Properties} object which contains a single namespace
      * and all of the properties which changed for that namespace. This includes properties which
@@ -817,6 +836,7 @@
      * @hide
      */
     @SystemApi
+    @TestApi
     public static class BadConfigException extends Exception {}
 
     /**
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 0a3c333..bb1c8ed 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -16,6 +16,8 @@
 
 package android.provider;
 
+import static android.annotation.SystemApi.Client.MODULE_APPS;
+
 import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
 import static com.android.internal.util.Preconditions.checkCollectionNotEmpty;
 
@@ -24,6 +26,7 @@
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ContentInterface;
+import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -47,6 +50,7 @@
 import android.os.Parcelable;
 import android.os.ParcelableException;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.util.Log;
 
 import com.android.internal.util.Preconditions;
@@ -363,7 +367,7 @@
          * <p>
          * Type: INTEGER (int)
          *
-         * @see #FLAG_DIR_BLOCKS_TREE
+         * @see #FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE
          * @see #FLAG_DIR_PREFERS_GRID
          * @see #FLAG_DIR_PREFERS_LAST_MODIFIED
          * @see #FLAG_DIR_SUPPORTS_CREATE
@@ -553,7 +557,9 @@
         /**
          * Flag indicating that a document is a directory that wants to block itself
          * from being selected when the user launches an {@link Intent#ACTION_OPEN_DOCUMENT_TREE}
-         * intent. Only valid when {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}.
+         * intent. Individual files can still be selected when launched via other intents
+         * like {@link Intent#ACTION_OPEN_DOCUMENT} and {@link Intent#ACTION_GET_CONTENT}.
+         * Only valid when {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}.
          * <p>
          * Note that this flag <em>only</em> applies to the single directory to which it is
          * applied. It does <em>not</em> block the user from selecting either a parent or
@@ -565,7 +571,7 @@
          * @see Intent#ACTION_OPEN_DOCUMENT_TREE
          * @see #COLUMN_FLAGS
          */
-        public static final int FLAG_DIR_BLOCKS_TREE = 1 << 15;
+        public static final int FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE = 1 << 15;
     }
 
     /**
@@ -943,6 +949,20 @@
         return getBaseDocumentUriBuilder(authority).appendPath(documentId).build();
     }
 
+    /**
+     * Builds URI as described in {@link #buildDocumentUri(String, String)}, but such that it will
+     * be associated with the given user.
+     *
+     * @hide
+     */
+    @SystemApi(client = MODULE_APPS)
+    @NonNull
+    public static Uri buildDocumentUriAsUser(
+            @NonNull String authority, @NonNull String documentId, @NonNull UserHandle user) {
+        return ContentProvider.maybeAddUserId(
+                buildDocumentUri(authority, documentId), user.getIdentifier());
+    }
+
     /** {@hide} */
     public static Uri buildBaseDocumentUri(String authority) {
         return getBaseDocumentUriBuilder(authority).build();
diff --git a/core/java/android/provider/SearchIndexablesContract.java b/core/java/android/provider/SearchIndexablesContract.java
index 298628e..8fc13b7 100644
--- a/core/java/android/provider/SearchIndexablesContract.java
+++ b/core/java/android/provider/SearchIndexablesContract.java
@@ -98,16 +98,14 @@
 
 
     /**
-     * Dynamic indexable raw data names.
-     *
-     * @hide
+     * The raw data name of dynamic index. This is used to compose the index path of provider
+     * for dynamic index.
      */
     public static final String DYNAMIC_INDEXABLES_RAW = "dynamic_indexables_raw";
 
     /**
-     * ContentProvider path for dynamic indexable raw data.
-     *
-     * @hide
+     * ContentProvider path for dynamic index. This is used to get the raw data of dynamic index
+     * from provider.
      */
     public static final String DYNAMIC_INDEXABLES_RAW_PATH =
             SETTINGS + "/" + DYNAMIC_INDEXABLES_RAW;
diff --git a/core/java/android/provider/SearchIndexablesProvider.java b/core/java/android/provider/SearchIndexablesProvider.java
index 68284b4..f4d0cb4 100644
--- a/core/java/android/provider/SearchIndexablesProvider.java
+++ b/core/java/android/provider/SearchIndexablesProvider.java
@@ -204,11 +204,9 @@
      * @param projection list of {@link android.provider.SearchIndexablesContract.RawData} columns
      *                   to put into the cursor. If {@code null} all supported columns should be
      *                   included.
-     *
-     * @hide
      */
     @Nullable
-    public Cursor queryDynamicRawData(String[] projection) {
+    public Cursor queryDynamicRawData(@Nullable String[] projection) {
         // By default no-op;
         return null;
     }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index bbaf94a..00b2feb 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -63,12 +63,12 @@
 import android.os.LocaleList;
 import android.os.PowerManager.AutoPowerSaveModeTriggers;
 import android.os.Process;
+import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.speech.tts.TextToSpeech;
-import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
 import android.util.AndroidException;
 import android.util.ArrayMap;
@@ -96,6 +96,7 @@
  * The Settings provider contains global system-level device preferences.
  */
 public final class Settings {
+    private static final boolean DEFAULT_OVERRIDEABLE_BY_RESTORE = false;
 
     // Intent actions for Settings
 
@@ -219,7 +220,9 @@
      * @hide
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
-    public static final String ACTION_TETHER_PROVISIONING =
+    @SystemApi
+    @TestApi
+    public static final String ACTION_TETHER_PROVISIONING_UI =
             "android.settings.TETHER_PROVISIONING_UI";
 
     /**
@@ -388,6 +391,21 @@
             "android.settings.MANAGE_UNKNOWN_APP_SOURCES";
 
     /**
+     * Activity Action: Show settings to allow configuration of cross-profile access for apps
+     *
+     * Input: Optionally, the Intent's data URI can specify the application package name to
+     * directly invoke the management GUI specific to the package name. For example
+     * "package:com.my.app".
+     * <p>
+     * Output: Nothing.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_MANAGE_CROSS_PROFILE_ACCESS =
+            "android.settings.MANAGE_CROSS_PROFILE_ACCESS";
+
+    /**
      * Activity Action: Show the "Open by Default" page in a particular application's details page.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you safeguard against this.
@@ -494,28 +512,33 @@
             "android.settings.WIFI_IP_SETTINGS";
 
     /**
-     * Activity Action: Show setting page to process an Easy Connect (Wi-Fi DPP) QR code and start
+     * Activity Action: Show setting page to process a Wi-Fi Easy Connect (aka DPP) URI and start
      * configuration. This intent should be used when you want to use this device to take on the
-     * configurator role for an IoT/other device. When provided with a valid DPP URI string Settings
-     * will open a wifi selection screen for the user to indicate which network they would like to
-     * configure the device specified in the DPP URI string for and carry them through the rest of
-     * the flow for provisioning the device.
+     * configurator role for an IoT/other device. When provided with a valid DPP URI
+     * string, Settings will open a Wi-Fi selection screen for the user to indicate which network
+     * they would like to configure the device specified in the DPP URI string and
+     * carry them through the rest of the flow for provisioning the device.
      * <p>
-     * In some cases, a matching Activity may not exist, so ensure you safeguard against this by
-     * checking WifiManager.isEasyConnectSupported();
+     * In some cases, a matching Activity may not exist, so ensure to safeguard against this by
+     * checking {@link WifiManager#isEasyConnectSupported()}.
      * <p>
      * Input: The Intent's data URI specifies bootstrapping information for authenticating and
-     * provisioning the peer, with the "DPP" scheme.
+     * provisioning the peer, and uses a "DPP" scheme. The URI should be attached to the intent
+     * using {@link Intent#setData(Uri)}. The calling app can obtain a DPP URI in any
+     * way, e.g. by scanning a QR code or other out-of-band methods. The calling app may also
+     * attach the {@link #EXTRA_EASY_CONNECT_BAND_LIST} extra to provide information
+     * about the bands supported by the enrollee device.
      * <p>
-     * Output: After {@code startActivityForResult}, the callback {@code onActivityResult} will have
-     * resultCode {@link android.app.Activity#RESULT_OK} if Wi-Fi Easy Connect configuration succeeded
-     * and the user tapped 'Done' button, or {@link android.app.Activity#RESULT_CANCELED} if operation
-     * failed and user tapped 'Cancel'. In case the operation has failed, a status code from {@link
-     * android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode} will be returned as
-     * Extra {@link #EXTRA_EASY_CONNECT_ERROR_CODE}. Easy Connect R2 Enrollees report additional
-     * details about the error they encountered, which will be provided in the {@link
-     * #EXTRA_EASY_CONNECT_ATTEMPTED_SSID}, {@link #EXTRA_EASY_CONNECT_CHANNEL_LIST}, and {@link
-     * #EXTRA_EASY_CONNECT_BAND_LIST}.
+     * Output: After calling {@link android.app.Activity#startActivityForResult}, the callback
+     * {@code onActivityResult} will have resultCode {@link android.app.Activity#RESULT_OK} if
+     * the Wi-Fi Easy Connect configuration succeeded and the user tapped the 'Done' button, or
+     * {@link android.app.Activity#RESULT_CANCELED} if the operation failed and user tapped the
+     * 'Cancel' button. In case the operation has failed, a status code from
+     * {@link android.net.wifi.EasyConnectStatusCallback} {@code EASY_CONNECT_EVENT_FAILURE_*} will
+     * be returned as an Extra {@link #EXTRA_EASY_CONNECT_ERROR_CODE}. Easy Connect R2
+     * Enrollees report additional details about the error they encountered, which will be
+     * provided in the {@link #EXTRA_EASY_CONNECT_ATTEMPTED_SSID},
+     * {@link #EXTRA_EASY_CONNECT_CHANNEL_LIST}, and {@link #EXTRA_EASY_CONNECT_BAND_LIST}.
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_PROCESS_WIFI_EASY_CONNECT_URI =
@@ -524,12 +547,15 @@
     /**
      * Activity Extra: The Easy Connect operation error code
      * <p>
-     * An extra returned on the result intent received when using the {@link
-     * #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation. This
-     * extra contains the error code of the operation - one of
-     * {@link android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode}.
-     * If there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK},
+     * An extra returned on the result intent received when using the
+     * {@link #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation.
+     * This extra contains the integer error code of the operation - one of
+     * {@link android.net.wifi.EasyConnectStatusCallback} {@code EASY_CONNECT_EVENT_FAILURE_*}. If
+     * there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK},
      * then this extra is not attached to the result intent.
+     * <p>
+     * Use the {@link Intent#hasExtra(String)} to determine whether the extra is attached and
+     * {@link Intent#getIntExtra(String, int)} to obtain the error code data.
      */
     public static final String EXTRA_EASY_CONNECT_ERROR_CODE =
             "android.provider.extra.EASY_CONNECT_ERROR_CODE";
@@ -541,11 +567,13 @@
      * #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation. This
      * extra contains the SSID of the Access Point that the remote Enrollee tried to connect to.
      * This value is populated only by remote R2 devices, and only for the following error codes:
-     * {@link android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK}
-     * {@link android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION}.
+     * {@link android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK}
+     * {@link android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION}.
      * Therefore, always check if this extra is available using {@link Intent#hasExtra(String)}. If
      * there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK}, then
      * this extra is not attached to the result intent.
+     * <p>
+     * Use the {@link Intent#getStringExtra(String)} to obtain the SSID.
      */
     public static final String EXTRA_EASY_CONNECT_ATTEMPTED_SSID =
             "android.provider.extra.EASY_CONNECT_ATTEMPTED_SSID";
@@ -555,13 +583,15 @@
      * <p>
      * An extra returned on the result intent received when using the {@link
      * #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation. This
-     * extra contains the list channels the Enrollee used to scan for a network. This value is
+     * extra contains the channel list that the Enrollee scanned for a network. This value is
      * populated only by remote R2 devices, and only for the following error code: {@link
-     * android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK}.
+     * android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK}.
      * Therefore, always check if this extra is available using {@link Intent#hasExtra(String)}. If
      * there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK}, then
      * this extra is not attached to the result intent. The list is JSON formatted, as an array
      * (Wi-Fi global operating classes) of arrays (Wi-Fi channels).
+     * <p>
+     * Use the {@link Intent#getStringExtra(String)} to obtain the list.
      */
     public static final String EXTRA_EASY_CONNECT_CHANNEL_LIST =
             "android.provider.extra.EASY_CONNECT_CHANNEL_LIST";
@@ -569,17 +599,31 @@
     /**
      * Activity Extra: The Band List that the Enrollee supports.
      * <p>
-     * An extra returned on the result intent received when using the {@link
-     * #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation. This
-     * extra contains the bands the Enrollee supports, expressed as the Global Operating Class,
-     * see Table E-4 in IEEE Std 802.11-2016 -Global operating classes. This value is populated only
-     * by remote R2 devices, and only for the following error codes: {@link
-     * android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK}
-     * {@link android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION}
-     * {@link android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION}.
-     * Therefore, always check if this extra is available using {@link Intent#hasExtra(String)}. If
-     * there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK}, then
-     * this extra is not attached to the result intent.
+     * This extra contains the bands the Enrollee supports, expressed as the Global Operating
+     * Class, see Table E-4 in IEEE Std 802.11-2016 Global operating classes. It is used both as
+     * input, to configure the Easy Connect operation and as output of the operation.
+     * <p>
+     * As input: an optional extra to be attached to the
+     * {@link #ACTION_PROCESS_WIFI_EASY_CONNECT_URI}. If attached, it indicates the bands which
+     * the remote device (enrollee, device-to-be-configured) supports. The Settings operation
+     * may take this into account when presenting the user with list of networks configurations
+     * to be used. The calling app may obtain this information in any out-of-band method. The
+     * information should be attached as an array of raw integers - using the
+     * {@link Intent#putExtra(String, int[])}.
+     * <p>
+     * As output: an extra returned on the result intent received when using the
+     * {@link #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation
+     * . This value is populated only by remote R2 devices, and only for the following error
+     * codes:
+     * {@link android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK},
+     * {@link android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION},
+     * or
+     * {@link android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION}.
+     * Therefore, always check if this extra is available using {@link Intent#hasExtra(String)}.
+     * If there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK}
+     * , then this extra is not attached to the result intent.
+     * <p>
+     * Use the {@link Intent#getIntArrayExtra(String)} to obtain the list.
      */
     public static final String EXTRA_EASY_CONNECT_BAND_LIST =
             "android.provider.extra.EASY_CONNECT_BAND_LIST";
@@ -1926,6 +1970,21 @@
             "android.settings.REQUEST_SET_AUTOFILL_SERVICE";
 
     /**
+     * Activity Action: Show screen for controlling the Quick Access Wallet.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: The Intent's data URI specifies the application package name
+     * to be shown, with the "package" scheme.  That is "package:com.my.app".
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_QUICK_ACCESS_WALLET_SETTINGS =
+            "android.settings.QUICK_ACCESS_WALLET_SETTINGS";
+
+    /**
      * Activity Action: Show screen for controlling which apps have access on volume directories.
      * <p>
      * Input: Nothing.
@@ -2103,10 +2162,20 @@
     public static final String CALL_METHOD_PREFIX_KEY = "_prefix";
 
     /**
+     * @hide - RemoteCallback monitor callback argument extra to the fast-path call()-based requests
+     */
+    public static final String CALL_METHOD_MONITOR_CALLBACK_KEY = "_monitor_callback_key";
+
+    /**
      * @hide - String argument extra to the fast-path call()-based requests
      */
     public static final String CALL_METHOD_FLAGS_KEY = "_flags";
 
+    /**
+     * @hide - String argument extra to the fast-path call()-based requests
+     */
+    public static final String CALL_METHOD_OVERRIDEABLE_BY_RESTORE_KEY = "_overrideable_by_restore";
+
     /** @hide - Private call() method to write to 'system' table */
     public static final String CALL_METHOD_PUT_SYSTEM = "PUT_system";
 
@@ -2155,6 +2224,26 @@
     /** @hide - Private call() method to reset to defaults the 'configuration' table */
     public static final String CALL_METHOD_LIST_CONFIG = "LIST_config";
 
+    /** @hide - Private call() method to register monitor callback for 'configuration' table */
+    public static final String CALL_METHOD_REGISTER_MONITOR_CALLBACK_CONFIG =
+            "REGISTER_MONITOR_CALLBACK_config";
+
+    /** @hide - String argument extra to the config monitor callback */
+    public static final String EXTRA_MONITOR_CALLBACK_TYPE = "monitor_callback_type";
+
+    /** @hide - String argument extra to the config monitor callback */
+    public static final String EXTRA_ACCESS_CALLBACK = "access_callback";
+
+    /** @hide - String argument extra to the config monitor callback */
+    public static final String EXTRA_NAMESPACE_UPDATED_CALLBACK =
+            "namespace_updated_callback";
+
+    /** @hide - String argument extra to the config monitor callback */
+    public static final String EXTRA_NAMESPACE = "namespace";
+
+    /** @hide - String argument extra to the config monitor callback */
+    public static final String EXTRA_CALLING_PACKAGE = "calling_package";
+
     /**
      * Activity Extra: Limit available options in launched activity based on the given authority.
      * <p>
@@ -2475,7 +2564,8 @@
         }
 
         public boolean putStringForUser(ContentResolver cr, String name, String value,
-                String tag, boolean makeDefault, final int userHandle) {
+                String tag, boolean makeDefault, final int userHandle,
+                boolean overrideableByRestore) {
             try {
                 Bundle arg = new Bundle();
                 arg.putString(Settings.NameValueTable.VALUE, value);
@@ -2486,6 +2576,9 @@
                 if (makeDefault) {
                     arg.putBoolean(CALL_METHOD_MAKE_DEFAULT_KEY, true);
                 }
+                if (overrideableByRestore) {
+                    arg.putBoolean(CALL_METHOD_OVERRIDEABLE_BY_RESTORE_KEY, true);
+                }
                 IContentProvider cp = mProviderHolder.getProvider(cr);
                 cp.call(cr.getPackageName(), cr.getFeatureId(),
                         mProviderHolder.mUri.getAuthority(), mCallSetCommand, name, arg);
@@ -2694,6 +2787,8 @@
 
         public ArrayMap<String, String> getStringsForPrefix(ContentResolver cr, String prefix,
                 List<String> names) {
+            String namespace = prefix.substring(0, prefix.length() - 1);
+            DeviceConfig.enforceReadPermission(ActivityThread.currentApplication(), namespace);
             ArrayMap<String, String> keyValues = new ArrayMap<>();
             int currentGeneration = -1;
 
@@ -3036,10 +3131,36 @@
             return putStringForUser(resolver, name, value, resolver.getUserId());
         }
 
+        /**
+         * Store a name/value pair into the database. Values written by this method will be
+         * overridden if a restore happens in the future.
+         *
+         * @param resolver to access the database with
+         * @param name to store
+         * @param value to associate with the name
+         *
+         * @return true if the value was set, false on database errors
+         *
+         * @hide
+         */
+        @RequiresPermission(Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE)
+        @SystemApi
+        public static boolean putString(@NonNull ContentResolver resolver,
+                @NonNull String name, @Nullable String value, boolean overrideableByRestore) {
+            return putStringForUser(resolver, name, value, resolver.getUserId(),
+                   overrideableByRestore);
+        }
+
         /** @hide */
         @UnsupportedAppUsage
         public static boolean putStringForUser(ContentResolver resolver, String name, String value,
                 int userHandle) {
+            return putStringForUser(resolver, name, value, userHandle,
+                    DEFAULT_OVERRIDEABLE_BY_RESTORE);
+        }
+
+        private static boolean putStringForUser(ContentResolver resolver, String name, String value,
+                int userHandle, boolean overrideableByRestore) {
             if (MOVED_TO_SECURE.contains(name)) {
                 Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System"
                         + " to android.provider.Settings.Secure, value is unchanged.");
@@ -3050,7 +3171,8 @@
                         + " to android.provider.Settings.Global, value is unchanged.");
                 return false;
             }
-            return sNameValueCache.putStringForUser(resolver, name, value, null, false, userHandle);
+            return sNameValueCache.putStringForUser(resolver, name, value, null, false, userHandle,
+                    overrideableByRestore);
         }
 
         /**
@@ -3374,7 +3496,7 @@
                     // need to store the adjusted configuration as the initial settings.
                     Settings.System.putStringForUser(
                             cr, SYSTEM_LOCALES, outConfig.getLocales().toLanguageTags(),
-                            userHandle);
+                            userHandle, DEFAULT_OVERRIDEABLE_BY_RESTORE);
                 }
             }
         }
@@ -3407,7 +3529,8 @@
                 int userHandle) {
             return Settings.System.putFloatForUser(cr, FONT_SCALE, config.fontScale, userHandle) &&
                     Settings.System.putStringForUser(
-                            cr, SYSTEM_LOCALES, config.getLocales().toLanguageTags(), userHandle);
+                            cr, SYSTEM_LOCALES, config.getLocales().toLanguageTags(), userHandle,
+                            DEFAULT_OVERRIDEABLE_BY_RESTORE);
         }
 
         /** @hide */
@@ -5095,7 +5218,6 @@
             MOVED_TO_GLOBAL.add(Settings.Global.WIFI_P2P_DEVICE_NAME);
             MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SAVED_STATE);
             MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SUPPLICANT_SCAN_INTERVAL_MS);
-            MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED);
             MOVED_TO_GLOBAL.add(Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED);
             MOVED_TO_GLOBAL.add(Settings.Global.WIFI_ENHANCED_AUTO_JOIN);
             MOVED_TO_GLOBAL.add(Settings.Global.WIFI_NETWORK_SHOW_RSSI);
@@ -5210,6 +5332,24 @@
         }
 
         /**
+         * Store a name/value pair into the database. Values written by this method will be
+         * overridden if a restore happens in the future.
+         *
+         * @param resolver to access the database with
+         * @param name to store
+         * @param value to associate with the name
+         * @return true if the value was set, false on database errors
+         *
+         * @hide
+         */
+        @RequiresPermission(Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE)
+        public static boolean putString(ContentResolver resolver, String name,
+                String value, boolean overrideableByRestore) {
+            return putStringForUser(resolver, name, value, /* tag */ null, /* makeDefault */ false,
+                    resolver.getUserId(), overrideableByRestore);
+        }
+
+        /**
          * Store a name/value pair into the database.
          * @param resolver to access the database with
          * @param name to store
@@ -5224,22 +5364,23 @@
         @UnsupportedAppUsage
         public static boolean putStringForUser(ContentResolver resolver, String name, String value,
                 int userHandle) {
-            return putStringForUser(resolver, name, value, null, false, userHandle);
+            return putStringForUser(resolver, name, value, null, false, userHandle,
+                    DEFAULT_OVERRIDEABLE_BY_RESTORE);
         }
 
         /** @hide */
         @UnsupportedAppUsage
         public static boolean putStringForUser(@NonNull ContentResolver resolver,
                 @NonNull String name, @Nullable String value, @Nullable String tag,
-                boolean makeDefault, @UserIdInt int userHandle) {
+                boolean makeDefault, @UserIdInt int userHandle, boolean overrideableByRestore) {
             if (MOVED_TO_GLOBAL.contains(name)) {
                 Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Secure"
                         + " to android.provider.Settings.Global");
                 return Global.putStringForUser(resolver, name, value,
-                        tag, makeDefault, userHandle);
+                        tag, makeDefault, userHandle, DEFAULT_OVERRIDEABLE_BY_RESTORE);
             }
             return sNameValueCache.putStringForUser(resolver, name, value, tag,
-                    makeDefault, userHandle);
+                    makeDefault, userHandle, overrideableByRestore);
         }
 
         /**
@@ -5288,7 +5429,7 @@
                 @NonNull String name, @Nullable String value, @Nullable String tag,
                 boolean makeDefault) {
             return putStringForUser(resolver, name, value, tag, makeDefault,
-                    resolver.getUserId());
+                    resolver.getUserId(), DEFAULT_OVERRIDEABLE_BY_RESTORE);
         }
 
         /**
@@ -5783,6 +5924,22 @@
                 "dark_mode_dialog_seen";
 
         /**
+         * Custom time when Dark theme is scheduled to activate.
+         * Represented as milliseconds from midnight (e.g. 79200000 == 10pm).
+         * @hide
+         */
+        public static final String DARK_THEME_CUSTOM_START_TIME =
+                "dark_theme_custom_start_time";
+
+        /**
+         * Custom time when Dark theme is scheduled to deactivate.
+         * Represented as milliseconds from midnight (e.g. 79200000 == 10pm).
+         * @hide
+         */
+        public static final String DARK_THEME_CUSTOM_END_TIME =
+                "dark_theme_custom_end_time";
+
+        /**
          * Defines value returned by {@link android.service.autofill.UserData#getMaxUserDataSize()}.
          *
          * @hide
@@ -6378,6 +6535,15 @@
                 "accessibility_button_target_component";
 
         /**
+         * The system class name of magnification controller which is a target to be toggled via
+         * accessibility shortcut or accessibility button.
+         *
+         * @hide
+         */
+        public static final String ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER =
+                "com.android.server.accessibility.MagnificationController";
+
+        /**
          * If touch exploration is enabled.
          */
         public static final String TOUCH_EXPLORATION_ENABLED = "touch_exploration_enabled";
@@ -7379,6 +7545,21 @@
         public static final String INCALL_POWER_BUTTON_BEHAVIOR = "incall_power_button_behavior";
 
         /**
+         * Whether the user allows minimal post processing or not.
+         *
+         * <p>Values:
+         * 0 - Not allowed. Any preferences set through the Window.setPreferMinimalPostProcessing
+         *     API will be ignored.
+         * 1 - Allowed. Any preferences set through the Window.setPreferMinimalPostProcessing API
+         *     will be respected and the appropriate signals will be sent to display.
+         *     (Default behaviour)
+         *
+         * @hide
+         */
+        public static final String MINIMAL_POST_PROCESSING_ALLOWED =
+                "minimal_post_processing_allowed";
+
+        /**
          * INCALL_POWER_BUTTON_BEHAVIOR value for "turn off screen".
          * @hide
          */
@@ -7489,6 +7670,12 @@
         public static final String DOZE_WAKE_DISPLAY_GESTURE = "doze_wake_display_gesture";
 
         /**
+         * Whether the device should suppress the current doze configuration and disable dozing.
+         * @hide
+         */
+        public static final String SUPPRESS_DOZE = "suppress_doze";
+
+        /**
          * Gesture that skips media.
          * @hide
          */
@@ -7576,6 +7763,14 @@
         public static final String UI_NIGHT_MODE = "ui_night_mode";
 
         /**
+         * The current night mode that has been overrided by the system.  Owned
+         * and controlled by UiModeManagerService.  Constants are as per
+         * UiModeManager.
+         * @hide
+         */
+        public static final String UI_NIGHT_MODE_OVERRIDE = "ui_night_mode_override";
+
+        /**
          * Whether screensavers are enabled.
          * @hide
          */
@@ -8769,9 +8964,9 @@
          * added to both AIRPLANE_MODE_RADIOS and AIRPLANE_MODE_TOGGLEABLE_RADIOS, then Wifi
          * will be turned off when entering airplane mode, but the user will be able to reenable
          * Wifi in the Settings app.
-         *
-         * {@hide}
+         * @hide
          */
+        @SystemApi
         public static final String AIRPLANE_MODE_TOGGLEABLE_RADIOS = "airplane_mode_toggleable_radios";
 
         /**
@@ -9698,6 +9893,8 @@
          * is interpreted as |false|.
          * @hide
          */
+        @SystemApi
+        @TestApi
         public static final String TETHER_OFFLOAD_DISABLED = "tether_offload_disabled";
 
         /**
@@ -9941,24 +10138,17 @@
         * Setting to allow scans to be enabled even wifi is turned off for connectivity.
         * @hide
         */
+       @SystemApi
        public static final String WIFI_SCAN_ALWAYS_AVAILABLE =
                 "wifi_scan_always_enabled";
 
         /**
-         * The interval in milliseconds at which wifi rtt ranging requests will be throttled when
-         * they are coming from the background.
-         *
-         * @hide
-         */
-        public static final String WIFI_RTT_BACKGROUND_EXEC_GAP_MS =
-                "wifi_rtt_background_exec_gap_ms";
-
-        /**
          * Indicate whether factory reset request is pending.
          *
          * Type: int (0 for false, 1 for true)
          * @hide
          */
+        @SystemApi
         public static final String WIFI_P2P_PENDING_FACTORY_RESET =
                 "wifi_p2p_pending_factory_reset";
 
@@ -9968,6 +10158,7 @@
          * Type: int (0 for false, 1 for true)
          * @hide
          */
+        @SystemApi
         public static final String SOFT_AP_TIMEOUT_ENABLED = "soft_ap_timeout_enabled";
 
         /**
@@ -10011,10 +10202,10 @@
          * enabled state.
          * @hide
          */
+        @SystemApi
         public static final String NETWORK_RECOMMENDATIONS_ENABLED =
                 "network_recommendations_enabled";
 
-
         /**
          * Which package name to use for network recommendations. If null, network recommendations
          * will neither be requested nor accepted.
@@ -10039,17 +10230,6 @@
         public static final String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package";
 
         /**
-         * The number of milliseconds the {@link com.android.server.NetworkScoreService}
-         * will give a recommendation request to complete before returning a default response.
-         *
-         * Type: long
-         * @hide
-         * @deprecated to be removed
-         */
-        public static final String NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS =
-                "network_recommendation_request_timeout_ms";
-
-        /**
          * The expiration time in milliseconds for the {@link android.net.WifiKey} request cache in
          * {@link com.android.server.wifi.RecommendedNetworkEvaluator}.
          *
@@ -10067,6 +10247,7 @@
          * Type: int (0 for false, 1 for true)
          * @hide
          */
+        @SystemApi
         public static final String WIFI_SCAN_THROTTLE_ENABLED = "wifi_scan_throttle_enabled";
 
         /**
@@ -10175,18 +10356,11 @@
                "wifi_watchdog_poor_network_test_enabled";
 
        /**
-        * Setting to turn on suspend optimizations at screen off on Wi-Fi. Enabled by default and
-        * needs to be set to 0 to disable it.
-        * @hide
-        */
-       public static final String WIFI_SUSPEND_OPTIMIZATIONS_ENABLED =
-               "wifi_suspend_optimizations_enabled";
-
-       /**
         * Setting to enable verbose logging in Wi-Fi; disabled by default, and setting to 1
         * will enable it. In the future, additional values may be supported.
         * @hide
         */
+       @SystemApi
        public static final String WIFI_VERBOSE_LOGGING_ENABLED =
                "wifi_verbose_logging_enabled";
 
@@ -10212,69 +10386,10 @@
          * Errors in the parameters will cause the entire setting to be ignored.
          * @hide
          */
+        @SystemApi
         public static final String WIFI_SCORE_PARAMS =
                 "wifi_score_params";
 
-        /**
-         * Setting to enable logging WifiIsUnusableEvent in metrics
-         * which gets triggered when wifi becomes unusable.
-         * Disabled by default, and setting it to 1 will enable it.
-         * @hide
-         */
-        public static final String WIFI_IS_UNUSABLE_EVENT_METRICS_ENABLED =
-                "wifi_is_unusable_event_metrics_enabled";
-
-        /**
-         * The minimum number of txBad the framework has to observe
-         * to trigger a wifi data stall.
-         * @hide
-         */
-        public static final String WIFI_DATA_STALL_MIN_TX_BAD =
-                "wifi_data_stall_min_tx_bad";
-
-        /**
-         * The minimum number of txSuccess the framework has to observe
-         * to trigger a wifi data stall when rxSuccess is 0.
-         * @hide
-         */
-        public static final String WIFI_DATA_STALL_MIN_TX_SUCCESS_WITHOUT_RX =
-                "wifi_data_stall_min_tx_success_without_rx";
-
-        /**
-         * Setting to enable logging Wifi LinkSpeedCounts in metrics.
-         * Disabled by default, and setting it to 1 will enable it.
-         * @hide
-         */
-        public static final String WIFI_LINK_SPEED_METRICS_ENABLED =
-                "wifi_link_speed_metrics_enabled";
-
-        /**
-         * Setting to enable the PNO frequency culling optimization.
-         * Disabled by default, and setting it to 1 will enable it.
-         * The value is boolean (0 or 1).
-         * @hide
-         */
-        public static final String WIFI_PNO_FREQUENCY_CULLING_ENABLED =
-                "wifi_pno_frequency_culling_enabled";
-
-        /**
-         * Setting to enable including recency information when determining pno network priorities.
-         * Disabled by default, and setting it to 1 will enable it.
-         * The value is boolean (0 or 1).
-         * @hide
-         */
-        public static final String WIFI_PNO_RECENCY_SORTING_ENABLED =
-                "wifi_pno_recency_sorting_enabled";
-
-        /**
-         * Setting to enable the Wi-Fi link probing.
-         * Enabled by default, and setting it to 0 will disable it.
-         * The value is boolean (0 or 1).
-         * @hide
-         */
-        public static final String WIFI_LINK_PROBING_ENABLED =
-                "wifi_link_probing_enabled";
-
        /**
         * The maximum number of times we will retry a connection to an access
         * point for which we have failed in acquiring an IP address from DHCP.
@@ -10314,6 +10429,7 @@
         * The Wi-Fi peer-to-peer device name
         * @hide
         */
+       @SystemApi
        public static final String WIFI_P2P_DEVICE_NAME = "wifi_p2p_device_name";
 
        /**
@@ -12459,16 +12575,17 @@
 
         /**
          * Whether the Volte is enabled. If this setting is not set then we use the Carrier Config
-         * value {@link CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}.
+         * value
+         * {@link android.telephony.CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}.
          * <p>
          * Type: int (0 for false, 1 for true)
          * @hide
-         * @deprecated Use {@link android.telephony.SubscriptionManager#ENHANCED_4G_MODE_ENABLED}
+         * @deprecated Use {@link android.provider.Telephony.SimInfo#ENHANCED_4G_MODE_ENABLED}
          * instead.
          */
         @Deprecated
         public static final String ENHANCED_4G_MODE_ENABLED =
-                SubscriptionManager.ENHANCED_4G_MODE_ENABLED;
+                Telephony.SimInfo.ENHANCED_4G_MODE_ENABLED;
 
         /**
          * Whether VT (Video Telephony over IMS) is enabled
@@ -12476,10 +12593,10 @@
          * Type: int (0 for false, 1 for true)
          *
          * @hide
-         * @deprecated Use {@link android.telephony.SubscriptionManager#VT_IMS_ENABLED} instead.
+         * @deprecated Use {@link android.provider.Telephony.SimInfo#VT_IMS_ENABLED} instead.
          */
         @Deprecated
-        public static final String VT_IMS_ENABLED = SubscriptionManager.VT_IMS_ENABLED;
+        public static final String VT_IMS_ENABLED = Telephony.SimInfo.VT_IMS_ENABLED;
 
         /**
          * Whether WFC is enabled
@@ -12487,10 +12604,10 @@
          * Type: int (0 for false, 1 for true)
          *
          * @hide
-         * @deprecated Use {@link android.telephony.SubscriptionManager#WFC_IMS_ENABLED} instead.
+         * @deprecated Use {@link android.provider.Telephony.SimInfo#WFC_IMS_ENABLED} instead.
          */
         @Deprecated
-        public static final String WFC_IMS_ENABLED = SubscriptionManager.WFC_IMS_ENABLED;
+        public static final String WFC_IMS_ENABLED = Telephony.SimInfo.WFC_IMS_ENABLED;
 
         /**
          * WFC mode on home/non-roaming network.
@@ -12498,10 +12615,10 @@
          * Type: int - 2=Wi-Fi preferred, 1=Cellular preferred, 0=Wi-Fi only
          *
          * @hide
-         * @deprecated Use {@link android.telephony.SubscriptionManager#WFC_IMS_MODE} instead.
+         * @deprecated Use {@link android.provider.Telephony.SimInfo#WFC_IMS_MODE} instead.
          */
         @Deprecated
-        public static final String WFC_IMS_MODE = SubscriptionManager.WFC_IMS_MODE;
+        public static final String WFC_IMS_MODE = Telephony.SimInfo.WFC_IMS_MODE;
 
         /**
          * WFC mode on roaming network.
@@ -12509,11 +12626,11 @@
          * Type: int - see {@link #WFC_IMS_MODE} for values
          *
          * @hide
-         * @deprecated Use {@link android.telephony.SubscriptionManager#WFC_IMS_ROAMING_MODE}
+         * @deprecated Use {@link android.provider.Telephony.SimInfo#WFC_IMS_ROAMING_MODE}
          * instead.
          */
         @Deprecated
-        public static final String WFC_IMS_ROAMING_MODE = SubscriptionManager.WFC_IMS_ROAMING_MODE;
+        public static final String WFC_IMS_ROAMING_MODE = Telephony.SimInfo.WFC_IMS_ROAMING_MODE;
 
         /**
          * Whether WFC roaming is enabled
@@ -12521,12 +12638,12 @@
          * Type: int (0 for false, 1 for true)
          *
          * @hide
-         * @deprecated Use {@link android.telephony.SubscriptionManager#WFC_IMS_ROAMING_ENABLED}
+         * @deprecated Use {@link android.provider.Telephony.SimInfo#WFC_IMS_ROAMING_ENABLED}
          * instead
          */
         @Deprecated
         public static final String WFC_IMS_ROAMING_ENABLED =
-                SubscriptionManager.WFC_IMS_ROAMING_ENABLED;
+                Telephony.SimInfo.WFC_IMS_ROAMING_ENABLED;
 
         /**
          * Whether user can enable/disable LTE as a preferred network. A carrier might control
@@ -12968,7 +13085,29 @@
          */
         public static boolean putString(ContentResolver resolver,
                 String name, String value) {
-            return putStringForUser(resolver, name, value, null, false, resolver.getUserId());
+            return putStringForUser(resolver, name, value, null, false, resolver.getUserId(),
+                    DEFAULT_OVERRIDEABLE_BY_RESTORE);
+        }
+
+        /**
+         * Store a name/value pair into the database.
+         *
+         * @param resolver to access the database with
+         * @param name to store
+         * @param value to associate with the name
+         * @param tag to associated with the setting.
+         * @param makeDefault whether to make the value the default one.
+         * @param overrideableByRestore whether restore can override this value
+         * @return true if the value was set, false on database errors
+         *
+         * @hide
+         */
+        @RequiresPermission(Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE)
+        public static boolean putString(@NonNull ContentResolver resolver,
+                @NonNull String name, @Nullable String value, @Nullable String tag,
+                boolean makeDefault, boolean overrideableByRestore) {
+            return putStringForUser(resolver, name, value, tag, makeDefault,
+                    resolver.getUserId(), overrideableByRestore);
         }
 
         /**
@@ -13017,7 +13156,7 @@
                 @NonNull String name, @Nullable String value, @Nullable String tag,
                 boolean makeDefault) {
             return putStringForUser(resolver, name, value, tag, makeDefault,
-                    resolver.getUserId());
+                    resolver.getUserId(), DEFAULT_OVERRIDEABLE_BY_RESTORE);
         }
 
         /**
@@ -13079,13 +13218,14 @@
         @UnsupportedAppUsage
         public static boolean putStringForUser(ContentResolver resolver,
                 String name, String value, int userHandle) {
-            return putStringForUser(resolver, name, value, null, false, userHandle);
+            return putStringForUser(resolver, name, value, null, false, userHandle,
+                    DEFAULT_OVERRIDEABLE_BY_RESTORE);
         }
 
         /** @hide */
         public static boolean putStringForUser(@NonNull ContentResolver resolver,
                 @NonNull String name, @Nullable String value, @Nullable String tag,
-                boolean makeDefault, @UserIdInt int userHandle) {
+                boolean makeDefault, @UserIdInt int userHandle, boolean overrideableByRestore) {
             if (LOCAL_LOGV) {
                 Log.v(TAG, "Global.putString(name=" + name + ", value=" + value
                         + " for " + userHandle);
@@ -13095,10 +13235,10 @@
                 Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Global"
                         + " to android.provider.Settings.Secure, value is unchanged.");
                 return Secure.putStringForUser(resolver, name, value, tag,
-                        makeDefault, userHandle);
+                        makeDefault, userHandle, overrideableByRestore);
             }
             return sNameValueCache.putStringForUser(resolver, name, value, tag,
-                    makeDefault, userHandle);
+                    makeDefault, userHandle, overrideableByRestore);
         }
 
         /**
@@ -13875,6 +14015,19 @@
          */
         public static final String POWER_BUTTON_SUPPRESSION_DELAY_AFTER_GESTURE_WAKE =
                 "power_button_suppression_delay_after_gesture_wake";
+
+        /**
+         * An integer indicating whether the device is in Common Criteria mode. When enabled,
+         * certain device functionalities are tuned to meet the higher security level required
+         * by Common Criteria certification. Examples include:
+         *   Bluetooth long term key material is additionally integrity-protected with AES-GCM.
+         *   WiFi configuration store is additionally integrity-protected with AES-GCM.
+         * A value of 0 means Common Criteria mode is not enabled (default), a value of non-zero
+         * means Common Criteria mode is enabled.
+         * @hide
+         */
+        @SystemApi(client = SystemApi.Client.MODULE_APPS)
+        public static final String COMMON_CRITERIA_MODE = "common_criteria_mode";
     }
 
     /**
@@ -13965,7 +14118,8 @@
         static boolean putString(@NonNull ContentResolver resolver, @NonNull String namespace,
                 @NonNull String name, @Nullable String value, boolean makeDefault) {
             return sNameValueCache.putStringForUser(resolver, createCompositeName(namespace, name),
-                    value, null, makeDefault, resolver.getUserId());
+                    value, null, makeDefault, resolver.getUserId(),
+                    DEFAULT_OVERRIDEABLE_BY_RESTORE);
         }
 
         /**
@@ -14027,6 +14181,37 @@
             }
         }
 
+        /**
+         * Register callback for monitoring Config table.
+         *
+         * @param resolver Handle to the content resolver.
+         * @param callback callback to register
+         *
+         * @hide
+         */
+        @SystemApi
+        @RequiresPermission(Manifest.permission.MONITOR_DEVICE_CONFIG_ACCESS)
+        public static void registerMonitorCallback(@NonNull ContentResolver resolver,
+                @NonNull RemoteCallback callback) {
+            registerMonitorCallbackAsUser(resolver, resolver.getUserId(), callback);
+        }
+
+        private static void registerMonitorCallbackAsUser(
+                @NonNull ContentResolver resolver, @UserIdInt int userHandle,
+                @NonNull RemoteCallback callback) {
+            try {
+                Bundle arg = new Bundle();
+                arg.putInt(CALL_METHOD_USER_KEY, userHandle);
+                arg.putParcelable(CALL_METHOD_MONITOR_CALLBACK_KEY, callback);
+                IContentProvider cp = sProviderHolder.getProvider(resolver);
+                cp.call(resolver.getPackageName(), resolver.getFeatureId(),
+                        sProviderHolder.mUri.getAuthority(),
+                        CALL_METHOD_REGISTER_MONITOR_CALLBACK_CONFIG, null, arg);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Can't register config monitor callback", e);
+            }
+        }
+
         private static String createCompositeName(@NonNull String namespace, @NonNull String name) {
             Preconditions.checkNotNull(namespace);
             Preconditions.checkNotNull(name);
@@ -14305,46 +14490,74 @@
 
     /**
      * Activity Action: Show setting page to process the addition of Wi-Fi networks to the user's
-     * saved network list. The app should send a new intent with an extra that holds a maximum of
-     * five {@link android.net.wifi.WifiConfiguration} that specify credentials for the networks to
-     * be added to the user's database. The Intent should be sent via the {@link
-     * android.app.Activity#startActivityForResult(Intent, int)} API.
+     * saved network list. The app should send a new intent with an extra that holds a maximum
+     * of five {@link android.net.wifi.WifiNetworkSuggestion} that specify credentials for the
+     * networks to be added to the user's database. The Intent should be sent via the
+     * {@link android.app.Activity#startActivityForResult(Intent, int)} API.
      * <p>
      * Note: The app sending the Intent to add the credentials doesn't get any ownership over the
      * newly added network(s). For the Wi-Fi stack, these networks will look like the user
      * manually added them from the Settings UI.
      * <p>
-     * Input: The app should put parcelable array list to
-     * {@link android.net.wifi.WifiConfiguration} into the
-     * {@link #EXTRA_WIFI_CONFIGURATION_LIST} extra.
+     * Input: The app should put parcelable array list of
+     * {@link android.net.wifi.WifiNetworkSuggestion} into the {@link #EXTRA_WIFI_NETWORK_LIST}
+     * extra.
      * <p>
      * Output: After {@link android.app.Activity#startActivityForResult(Intent, int)}, the
      * callback {@link android.app.Activity#onActivityResult(int, int, Intent)} will have a
      * result code {@link android.app.Activity#RESULT_OK} to indicate user pressed the save
      * button to save the networks or {@link android.app.Activity#RESULT_CANCELED} to indicate
      * that the user rejected the request. Additionally, an integer array list, stored in
-     * {@link #EXTRA_WIFI_CONFIGURATION_RESULT_LIST}, will indicate the process result of
-     * each network.
+     * {@link #EXTRA_WIFI_NETWORK_RESULT_LIST}, will indicate the process result of each network.
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_WIFI_ADD_NETWORKS =
             "android.settings.WIFI_ADD_NETWORKS";
 
     /**
-     * A bundle extra of {@link #ACTION_WIFI_ADD_NETWORKS} intent action that indicates all the
-     * {@link android.net.wifi.WifiConfiguration} that would be saved.
+     * A bundle extra of {@link #ACTION_WIFI_ADD_NETWORKS} intent action that indicates the list
+     * of the {@link android.net.wifi.WifiNetworkSuggestion} elements. The maximum count of the
+     * {@link android.net.wifi.WifiNetworkSuggestion} elements in the list will be five.
+     * <p>
+     * For example:
+     * To provide credentials for one open and one WPA2 networks:
+     *
+     * <pre>{@code
+     * final WifiNetworkSuggestion suggestion1 =
+     *       new WifiNetworkSuggestion.Builder()
+     *       .setSsid("test111111")
+     *       .build();
+     * final WifiNetworkSuggestion suggestion2 =
+     *       new WifiNetworkSuggestion.Builder()
+     *       .setSsid("test222222")
+     *       .setWpa2Passphrase("test123456")
+     *       .build();
+     * final List<WifiNetworkSuggestion> suggestionsList = new ArrayList<>;
+     * suggestionsList.add(suggestion1);
+     * suggestionsList.add(suggestion2);
+     * Bundle bundle = new Bundle();
+     * bundle.putParcelableArrayList(Settings.EXTRA_WIFI_NETWORK_LIST,(ArrayList<? extends
+     * Parcelable>) suggestionsList);
+     * final Intent intent = new Intent(Settings.ACTION_WIFI_ADD_NETWORKS);
+     * intent.putExtras(bundle);
+     * startActivityForResult(intent, 0);
+     * }</pre>
      */
-    public static final String EXTRA_WIFI_CONFIGURATION_LIST =
-            "android.provider.extra.WIFI_CONFIGURATION_LIST";
+    public static final String EXTRA_WIFI_NETWORK_LIST =
+            "android.provider.extra.WIFI_NETWORK_LIST";
 
     /**
      * A bundle extra of the result of {@link #ACTION_WIFI_ADD_NETWORKS} intent action that
-     * indicates the action result of the saved {@link android.net.wifi.WifiConfiguration}. It's
-     * value of AddWifiResult interface, and will be 1:1 mapping to the element in {@link
-     * #EXTRA_WIFI_CONFIGURATION_LIST}.
+     * indicates the action result of the saved {@link android.net.wifi.WifiNetworkSuggestion}.
+     * Its value is a list of integers, and all the elements will be 1:1 mapping to the elements
+     * in {@link #EXTRA_WIFI_NETWORK_LIST}, if user press cancel to cancel the add networks
+     * request, then its value will be null.
+     * <p>
+     * Note: The integer value will be one of the {@link #ADD_WIFI_RESULT_SUCCESS},
+     * {@link #ADD_WIFI_RESULT_ADD_OR_UPDATE_FAILED}, or {@link #ADD_WIFI_RESULT_ALREADY_EXISTS}}.
      */
-    public static final String EXTRA_WIFI_CONFIGURATION_RESULT_LIST =
-            "android.provider.extra.WIFI_CONFIGURATION_RESULT_LIST";
+    public static final String EXTRA_WIFI_NETWORK_RESULT_LIST =
+            "android.provider.extra.WIFI_NETWORK_RESULT_LIST";
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -14400,6 +14613,42 @@
     };
 
     /**
+     * Activity Action: Show screen for controlling which apps have access to manage external
+     * storage.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you safeguard against this.
+     * <p>
+     * If you want to control a specific app's access to manage external storage, use
+     * {@link #ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION} instead.
+     * <p>
+     * Output: Nothing.
+     * @see #ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION =
+            "android.settings.MANAGE_ALL_FILES_ACCESS_PERMISSION";
+
+    /**
+     * Activity Action: Show screen for controlling if the app specified in the data URI of the
+     * intent can manage external storage.
+     * <p>
+     * Launching the corresponding activity requires the permission
+     * {@link Manifest.permission#MANAGE_EXTERNAL_STORAGE}.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you safeguard against this.
+     * <p>
+     * Input: The Intent's data URI MUST specify the application package name whose ability of
+     * managing external storage you want to control.
+     * For example "package:com.my.app".
+     * <p>
+     * Output: Nothing.
+     * @see #ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION =
+            "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION";
+
+    /**
      * Performs a strict and comprehensive check of whether a calling package is allowed to
      * write/modify system settings, as the condition differs for pre-M, M+, and
      * privileged/preinstalled apps. If the provided uid does not match the
@@ -14425,8 +14674,9 @@
      * current time.
      * @hide
      */
-    public static boolean checkAndNoteWriteSettingsOperation(Context context, int uid,
-            String callingPackage, boolean throwException) {
+    @SystemApi
+    public static boolean checkAndNoteWriteSettingsOperation(@NonNull Context context, int uid,
+            @NonNull String callingPackage, boolean throwException) {
         return isCallingPackageAllowedToPerformAppOpsProtectedOperation(context, uid,
                 callingPackage, throwException, AppOpsManager.OP_WRITE_SETTINGS,
                 PM_WRITE_SETTINGS, true);
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 1d9bdb8..ee9d8f5 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -37,12 +37,13 @@
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.Parcel;
+import android.telephony.CarrierConfigManager;
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.SmsMessage;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.UiccAccessRule;
 import android.text.TextUtils;
 import android.util.Patterns;
 
@@ -4140,7 +4141,6 @@
          *     <li>{@link #ENABLE_CMAS_PRESIDENTIAL_PREF}</li>
          *     <li>{@link #ENABLE_ALERT_VIBRATION_PREF}</li>
          *     <li>{@link #ENABLE_EMERGENCY_PERF}</li>
-         *     <li>{@link #ENABLE_FULL_VOLUME_PREF}</li>
          *     <li>{@link #ENABLE_CMAS_IN_SECOND_LANGUAGE_PREF}</li>
          * </ul>
          * @hide
@@ -4203,10 +4203,6 @@
             public static final @NonNull String ENABLE_EMERGENCY_PERF =
                     "enable_emergency_alerts";
 
-            /** Preference to enable volume for alerts */
-            public static final @NonNull String ENABLE_FULL_VOLUME_PREF =
-                    "use_full_volume";
-
             /** Preference to enable receive alerts in second language */
             public static final @NonNull String ENABLE_CMAS_IN_SECOND_LANGUAGE_PREF =
                     "receive_cmas_in_second_language";
@@ -4562,32 +4558,6 @@
         }
 
         /**
-         * Used to insert a ServiceState into the ServiceStateProvider as a ContentValues instance.
-         *
-         * @param state the ServiceState to convert into ContentValues
-         * @return the convertedContentValues instance
-         * @hide
-         */
-        public static ContentValues getContentValuesForServiceState(ServiceState state) {
-            ContentValues values = new ContentValues();
-            final Parcel p = Parcel.obtain();
-            state.writeToParcel(p, 0);
-            // Turn the parcel to byte array. Safe to do this because the content values were never
-            // written into a persistent storage. ServiceStateProvider keeps values in the memory.
-            values.put(SERVICE_STATE, p.marshall());
-            return values;
-        }
-
-        /**
-         * The current service state.
-         *
-         * This is the entire {@link ServiceState} object in byte array.
-         *
-         * @hide
-         */
-        public static final String SERVICE_STATE = "service_state";
-
-        /**
          * An integer value indicating the current voice service state.
          * <p>
          * Valid values: {@link ServiceState#STATE_IN_SERVICE},
@@ -4599,53 +4569,6 @@
         public static final String VOICE_REG_STATE = "voice_reg_state";
 
         /**
-         * An integer value indicating the current data service state.
-         * <p>
-         * Valid values: {@link ServiceState#STATE_IN_SERVICE},
-         * {@link ServiceState#STATE_OUT_OF_SERVICE}, {@link ServiceState#STATE_EMERGENCY_ONLY},
-         * {@link ServiceState#STATE_POWER_OFF}.
-         * <p>
-         * This is the same as {@link ServiceState#getDataRegState()}.
-         * @hide
-         */
-        public static final String DATA_REG_STATE = "data_reg_state";
-
-        /**
-         * An integer value indicating the current voice roaming type.
-         * <p>
-         * This is the same as {@link ServiceState#getVoiceRoamingType()}.
-         * @hide
-         */
-        public static final String VOICE_ROAMING_TYPE = "voice_roaming_type";
-
-        /**
-         * An integer value indicating the current data roaming type.
-         * <p>
-         * This is the same as {@link ServiceState#getDataRoamingType()}.
-         * @hide
-         */
-        public static final String DATA_ROAMING_TYPE = "data_roaming_type";
-
-        /**
-         * The current registered voice network operator name in long alphanumeric format.
-         * <p>
-         * This is the same as {@link ServiceState#getOperatorAlphaLong()}.
-         * @hide
-         */
-        public static final String VOICE_OPERATOR_ALPHA_LONG = "voice_operator_alpha_long";
-
-        /**
-         * The current registered operator name in short alphanumeric format.
-         * <p>
-         * In GSM/UMTS, short format can be up to 8 characters long. The current registered voice
-         * network operator name in long alphanumeric format.
-         * <p>
-         * This is the same as {@link ServiceState#getOperatorAlphaShort()}.
-         * @hide
-         */
-        public static final String VOICE_OPERATOR_ALPHA_SHORT = "voice_operator_alpha_short";
-
-        /**
          * The current registered operator numeric id.
          * <p>
          * In GSM/UMTS, numeric format is 3 digit country code plus 2 or 3 digit
@@ -4656,125 +4579,11 @@
         public static final String VOICE_OPERATOR_NUMERIC = "voice_operator_numeric";
 
         /**
-         * The current registered data network operator name in long alphanumeric format.
-         * <p>
-         * This is the same as {@link ServiceState#getOperatorAlphaLong()}.
-         * @hide
-         */
-        public static final String DATA_OPERATOR_ALPHA_LONG = "data_operator_alpha_long";
-
-        /**
-         * The current registered data network operator name in short alphanumeric format.
-         * <p>
-         * This is the same as {@link ServiceState#getOperatorAlphaShort()}.
-         * @hide
-         */
-        public static final String DATA_OPERATOR_ALPHA_SHORT = "data_operator_alpha_short";
-
-        /**
-         * The current registered data network operator numeric id.
-         * <p>
-         * This is the same as {@link ServiceState#getOperatorNumeric()}.
-         * @hide
-         */
-        public static final String DATA_OPERATOR_NUMERIC = "data_operator_numeric";
-
-        /**
          * The current network selection mode.
          * <p>
          * This is the same as {@link ServiceState#getIsManualSelection()}.
          */
         public static final String IS_MANUAL_NETWORK_SELECTION = "is_manual_network_selection";
-
-        /**
-         * This is the same as {@link ServiceState#getRilVoiceRadioTechnology()}.
-         * @hide
-         */
-        public static final String RIL_VOICE_RADIO_TECHNOLOGY = "ril_voice_radio_technology";
-
-        /**
-         * This is the same as {@link ServiceState#getRilDataRadioTechnology()}.
-         * @hide
-         */
-        public static final String RIL_DATA_RADIO_TECHNOLOGY = "ril_data_radio_technology";
-
-        /**
-         * This is the same as {@link ServiceState#getCssIndicator()}.
-         * @hide
-         */
-        public static final String CSS_INDICATOR = "css_indicator";
-
-        /**
-         * This is the same as {@link ServiceState#getCdmaNetworkId()}.
-         * @hide
-         */
-        public static final String NETWORK_ID = "network_id";
-
-        /**
-         * This is the same as {@link ServiceState#getCdmaSystemId()}.
-         * @hide
-         */
-        public static final String SYSTEM_ID = "system_id";
-
-        /**
-         * This is the same as {@link ServiceState#getCdmaRoamingIndicator()}.
-         * @hide
-         */
-        public static final String CDMA_ROAMING_INDICATOR = "cdma_roaming_indicator";
-
-        /**
-         * This is the same as {@link ServiceState#getCdmaDefaultRoamingIndicator()}.
-         * @hide
-         */
-        public static final String CDMA_DEFAULT_ROAMING_INDICATOR =
-                "cdma_default_roaming_indicator";
-
-        /**
-         * This is the same as {@link ServiceState#getCdmaEriIconIndex()}.
-         * @hide
-         */
-        public static final String CDMA_ERI_ICON_INDEX = "cdma_eri_icon_index";
-
-        /**
-         * This is the same as {@link ServiceState#getCdmaEriIconMode()}.
-         * @hide
-         */
-        public static final String CDMA_ERI_ICON_MODE = "cdma_eri_icon_mode";
-
-        /**
-         * This is the same as {@link ServiceState#isEmergencyOnly()}.
-         * @hide
-         */
-        public static final String IS_EMERGENCY_ONLY = "is_emergency_only";
-
-        /**
-         * This is the same as {@link ServiceState#getDataRoamingFromRegistration()}.
-         * @hide
-         */
-        public static final String IS_DATA_ROAMING_FROM_REGISTRATION =
-                "is_data_roaming_from_registration";
-
-        /**
-         * This is the same as {@link ServiceState#isUsingCarrierAggregation()}.
-         * @hide
-         */
-        public static final String IS_USING_CARRIER_AGGREGATION = "is_using_carrier_aggregation";
-
-        /**
-         * The current registered raw data network operator name in long alphanumeric format.
-         * <p>
-         * This is the same as {@link ServiceState#getOperatorAlphaLongRaw()}.
-         * @hide
-         */
-        public static final String OPERATOR_ALPHA_LONG_RAW = "operator_alpha_long_raw";
-
-        /**
-         * The current registered raw data network operator name in short alphanumeric format.
-         * <p>
-         * This is the same as {@link ServiceState#getOperatorAlphaShortRaw()}.
-         * @hide
-         */
-        public static final String OPERATOR_ALPHA_SHORT_RAW = "operator_alpha_short_raw";
     }
 
     /**
@@ -4980,5 +4789,408 @@
          */
         @NonNull
         public static final Uri CONTENT_URI = Uri.parse("content://telephony/siminfo");
+
+        /**
+         * TelephonyProvider unique key column name is the subscription id.
+         * <P>Type: TEXT (String)</P>
+         */
+        public static final String UNIQUE_KEY_SUBSCRIPTION_ID = "_id";
+
+        /**
+         * TelephonyProvider column name for a unique identifier for the subscription within the
+         * specific subscription type. For example, it contains SIM ICC Identifier subscriptions
+         * on Local SIMs. and Mac-address for Remote-SIM Subscriptions for Bluetooth devices.
+         * <P>Type: TEXT (String)</P>
+         */
+        public static final String ICC_ID = "icc_id";
+
+        /**
+         * TelephonyProvider column name for user SIM_SlOT_INDEX
+         * <P>Type: INTEGER (int)</P>
+         */
+        public static final String SIM_SLOT_INDEX = "sim_id";
+
+        /**
+         * SIM is not inserted
+         */
+        public static final int SIM_NOT_INSERTED = -1;
+
+        /**
+         * TelephonyProvider column name Subscription-type.
+         * <P>Type: INTEGER (int)</P> {@link #SUBSCRIPTION_TYPE_LOCAL_SIM} for Local-SIM
+         * Subscriptions, {@link #SUBSCRIPTION_TYPE_REMOTE_SIM} for Remote-SIM Subscriptions.
+         * Default value is 0.
+         */
+        public static final String SUBSCRIPTION_TYPE = "subscription_type";
+
+        /**
+         * This constant is to designate a subscription as a Local-SIM Subscription.
+         * <p> A Local-SIM can be a physical SIM inserted into a sim-slot in the device, or eSIM on
+         * the device.
+         * </p>
+         */
+        public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0;
+
+        /**
+         * This constant is to designate a subscription as a Remote-SIM Subscription.
+         * <p>
+         * A Remote-SIM subscription is for a SIM on a phone connected to this device via some
+         * connectivity mechanism, for example bluetooth. Similar to Local SIM, this subscription
+         * can be used for SMS, Voice and data by proxying data through the connected device.
+         * Certain data of the SIM, such as IMEI, are not accessible for Remote SIMs.
+         * </p>
+         *
+         * <p>
+         * A Remote-SIM is available only as long the phone stays connected to this device.
+         * When the phone disconnects, Remote-SIM subscription is removed from this device and is
+         * no longer known. All data associated with the subscription, such as stored SMS, call
+         * logs, contacts etc, are removed from this device.
+         * </p>
+         *
+         * <p>
+         * If the phone re-connects to this device, a new Remote-SIM subscription is created for
+         * the phone. The Subscription Id associated with the new subscription is different from
+         * the Subscription Id of the previous Remote-SIM subscription created (and removed) for the
+         * phone; i.e., new Remote-SIM subscription treats the reconnected phone as a Remote-SIM
+         * that was never seen before.
+         * </p>
+         */
+        public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1;
+
+        /**
+         * TelephonyProvider column name data_enabled_override_rules.
+         * It's a list of rules for overriding data enabled settings. The syntax is
+         * For example, "mms=nonDefault" indicates enabling data for mms in non-default
+         * subscription.
+         * "default=nonDefault&inVoiceCall" indicates enabling data for internet in non-default
+         * subscription and while is in voice call.
+         *
+         * Default value is empty string.
+         */
+        public static final String DATA_ENABLED_OVERRIDE_RULES = "data_enabled_override_rules";
+
+        /**
+         * TelephonyProvider column name for user displayed name.
+         * <P>Type: TEXT (String)</P>
+         */
+        public static final String DISPLAY_NAME = "display_name";
+
+        /**
+         * TelephonyProvider column name for the service provider name for the SIM.
+         * <P>Type: TEXT (String)</P>
+         */
+        public static final String CARRIER_NAME = "carrier_name";
+
+        /**
+         * TelephonyProvider column name for source of the user displayed name.
+         * <P>Type: INT (int)</P> with one of the NAME_SOURCE_XXXX values below
+         */
+        public static final String NAME_SOURCE = "name_source";
+
+        /** The name_source is the default, which is from the carrier id. */
+        public static final int NAME_SOURCE_DEFAULT = 0;
+
+        /**
+         * The name_source is from SIM EF_SPN.
+         */
+        public static final int NAME_SOURCE_SIM_SPN = 1;
+
+        /**
+         * The name_source is from user input
+         */
+        public static final int NAME_SOURCE_USER_INPUT = 2;
+
+        /**
+         * The name_source is carrier (carrier app, carrier config, etc.)
+         */
+        public static final int NAME_SOURCE_CARRIER = 3;
+
+        /**
+         * The name_source is from SIM EF_PNN.
+         */
+        public static final int NAME_SOURCE_SIM_PNN = 4;
+
+        /**
+         * TelephonyProvider column name for the color of a SIM.
+         * <P>Type: INTEGER (int)</P>
+         */
+        public static final String COLOR = "color";
+
+        /** TelephonyProvider column name for the default color of a SIM {@hide} */
+        public static final int COLOR_DEFAULT = 0;
+
+        /**
+         * TelephonyProvider column name for the phone number of a SIM.
+         * <P>Type: TEXT (String)</P>
+         */
+        public static final String NUMBER = "number";
+
+        /**
+         * TelephonyProvider column name for the number display format of a SIM.
+         * <P>Type: INTEGER (int)</P>
+         * @hide
+         */
+        public static final String DISPLAY_NUMBER_FORMAT = "display_number_format";
+
+        /**
+         * TelephonyProvider column name for the default display format of a SIM
+         * @hide
+         */
+        public static final int DISPLAY_NUMBER_DEFAULT = 1;
+
+        /**
+         * TelephonyProvider column name for whether data roaming is enabled.
+         * <P>Type: INTEGER (int)</P>
+         */
+        public static final String DATA_ROAMING = "data_roaming";
+
+        /** Indicates that data roaming is enabled for a subscription */
+        public static final int DATA_ROAMING_ENABLE = 1;
+
+        /** Indicates that data roaming is disabled for a subscription */
+        public static final int DATA_ROAMING_DISABLE = 0;
+
+        /** TelephonyProvider column name for default data roaming setting: disable */
+        public static final int DATA_ROAMING_DEFAULT = DATA_ROAMING_DISABLE;
+
+        /**
+         * TelephonyProvider column name for subscription carrier id.
+         * @see TelephonyManager#getSimCarrierId()
+         * <p>Type: INTEGER (int) </p>
+         */
+        public static final String CARRIER_ID = "carrier_id";
+
+        /**
+         * A comma-separated list of EHPLMNs associated with the subscription
+         * <P>Type: TEXT (String)</P>
+         */
+        public static final String EHPLMNS = "ehplmns";
+
+        /**
+         * A comma-separated list of HPLMNs associated with the subscription
+         * <P>Type: TEXT (String)</P>
+         */
+        public static final String HPLMNS = "hplmns";
+
+        /**
+         * TelephonyProvider column name for the MCC associated with a SIM, stored as a string.
+         * <P>Type: TEXT (String)</P>
+         */
+        public static final String MCC_STRING = "mcc_string";
+
+        /**
+         * TelephonyProvider column name for the MNC associated with a SIM, stored as a string.
+         * <P>Type: TEXT (String)</P>
+         */
+        public static final String MNC_STRING = "mnc_string";
+
+        /**
+         * TelephonyProvider column name for the MCC associated with a SIM.
+         * <P>Type: INTEGER (int)</P>
+         */
+        public static final String MCC = "mcc";
+
+        /**
+         * TelephonyProvider column name for the MNC associated with a SIM.
+         * <P>Type: INTEGER (int)</P>
+         */
+        public static final String MNC = "mnc";
+
+        /**
+         * TelephonyProvider column name for the iso country code associated with a SIM.
+         * <P>Type: TEXT (String)</P>
+         */
+        public static final String ISO_COUNTRY_CODE = "iso_country_code";
+
+        /**
+         * TelephonyProvider column name for the sim provisioning status associated with a SIM.
+         * <P>Type: INTEGER (int)</P>
+         * @hide
+         */
+        public static final String SIM_PROVISIONING_STATUS = "sim_provisioning_status";
+
+        /** The sim is provisioned {@hide} */
+        public static final int SIM_PROVISIONED = 0;
+
+        /**
+         * TelephonyProvider column name for whether a subscription is embedded (that is, present on
+         * an eSIM).
+         * <p>Type: INTEGER (int), 1 for embedded or 0 for non-embedded.
+         */
+        public static final String IS_EMBEDDED = "is_embedded";
+
+        /**
+         * TelephonyProvider column name for SIM card identifier. For UICC card it is the ICCID of
+         * the current enabled profile on the card, while for eUICC card it is the EID of the card.
+         * <P>Type: TEXT (String)</P>
+         */
+        public static final String CARD_ID = "card_id";
+
+        /**
+         * TelephonyProvider column name for the encoded {@link UiccAccessRule}s from
+         * {@link UiccAccessRule#encodeRules}. Only present if {@link #IS_EMBEDDED} is 1.
+         * <p>TYPE: BLOB
+         */
+        public static final String ACCESS_RULES = "access_rules";
+
+        /**
+         * TelephonyProvider column name for the encoded {@link UiccAccessRule}s from
+         * {@link UiccAccessRule#encodeRules} but for the rules that come from CarrierConfigs.
+         * Only present if there are access rules in CarrierConfigs
+         * <p>TYPE: BLOB
+         */
+        public static final String ACCESS_RULES_FROM_CARRIER_CONFIGS =
+                "access_rules_from_carrier_configs";
+
+        /**
+         * TelephonyProvider column name identifying whether an embedded subscription is on a
+         * removable card. Such subscriptions are marked inaccessible as soon as the current card
+         * is removed. Otherwise, they will remain accessible unless explicitly deleted. Only
+         * present if {@link #IS_EMBEDDED} is 1.
+         * <p>TYPE: INTEGER (int), 1 for removable or 0 for non-removable.
+         */
+        public static final String IS_REMOVABLE = "is_removable";
+
+        /** TelephonyProvider column name for extreme threat in CB settings */
+        public static final String CB_EXTREME_THREAT_ALERT = "enable_cmas_extreme_threat_alerts";
+
+        /** TelephonyProvider column name for severe threat in CB settings */
+        public static final String CB_SEVERE_THREAT_ALERT = "enable_cmas_severe_threat_alerts";
+
+        /** TelephonyProvider column name for amber alert in CB settings */
+        public static final String CB_AMBER_ALERT = "enable_cmas_amber_alerts";
+
+        /** TelephonyProvider column name for emergency alert in CB settings */
+        public static final String CB_EMERGENCY_ALERT = "enable_emergency_alerts";
+
+        /** TelephonyProvider column name for alert sound duration in CB settings */
+        public static final String CB_ALERT_SOUND_DURATION = "alert_sound_duration";
+
+        /** TelephonyProvider column name for alert reminder interval in CB settings */
+        public static final String CB_ALERT_REMINDER_INTERVAL = "alert_reminder_interval";
+
+        /** TelephonyProvider column name for enabling vibrate in CB settings */
+        public static final String CB_ALERT_VIBRATE = "enable_alert_vibrate";
+
+        /** TelephonyProvider column name for enabling alert speech in CB settings */
+        public static final String CB_ALERT_SPEECH = "enable_alert_speech";
+
+        /** TelephonyProvider column name for ETWS test alert in CB settings */
+        public static final String CB_ETWS_TEST_ALERT = "enable_etws_test_alerts";
+
+        /** TelephonyProvider column name for enable channel50 alert in CB settings */
+        public static final String CB_CHANNEL_50_ALERT = "enable_channel_50_alerts";
+
+        /** TelephonyProvider column name for CMAS test alert in CB settings */
+        public static final String CB_CMAS_TEST_ALERT = "enable_cmas_test_alerts";
+
+        /** TelephonyProvider column name for Opt out dialog in CB settings */
+        public static final String CB_OPT_OUT_DIALOG = "show_cmas_opt_out_dialog";
+
+        /**
+         * TelephonyProvider column name for enable Volte.
+         *
+         * If this setting is not initialized (set to -1)  then we use the Carrier Config value
+         * {@link CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}.
+         */
+        public static final String ENHANCED_4G_MODE_ENABLED = "volte_vt_enabled";
+
+        /** TelephonyProvider column name for enable VT (Video Telephony over IMS) */
+        public static final String VT_IMS_ENABLED = "vt_ims_enabled";
+
+        /** TelephonyProvider column name for enable Wifi calling */
+        public static final String WFC_IMS_ENABLED = "wfc_ims_enabled";
+
+        /** TelephonyProvider column name for Wifi calling mode */
+        public static final String WFC_IMS_MODE = "wfc_ims_mode";
+
+        /** TelephonyProvider column name for Wifi calling mode in roaming */
+        public static final String WFC_IMS_ROAMING_MODE = "wfc_ims_roaming_mode";
+
+        /** TelephonyProvider column name for enable Wifi calling in roaming */
+        public static final String WFC_IMS_ROAMING_ENABLED = "wfc_ims_roaming_enabled";
+
+        /**
+         * TelephonyProvider column name for whether a subscription is opportunistic, that is,
+         * whether the network it connects to is limited in functionality or coverage.
+         * For example, CBRS.
+         * <p>Type: INTEGER (int), 1 for opportunistic or 0 for non-opportunistic.
+         */
+        public static final String IS_OPPORTUNISTIC = "is_opportunistic";
+
+        /**
+         * TelephonyProvider column name for group ID. Subscriptions with same group ID
+         * are considered bundled together, and should behave as a single subscription at
+         * certain scenarios.
+         */
+        public static final String GROUP_UUID = "group_uuid";
+
+        /**
+         * TelephonyProvider column name for group owner. It's the package name who created
+         * the subscription group.
+         */
+        public static final String GROUP_OWNER = "group_owner";
+
+        /**
+         * TelephonyProvider column name for whether a subscription is metered or not, that is,
+         * whether the network it connects to charges for subscription or not. For example, paid
+         * CBRS or unpaid.
+         * @hide
+         */
+        public static final String IS_METERED = "is_metered";
+
+        /**
+         * TelephonyProvider column name for the profile class of a subscription
+         * Only present if {@link #IS_EMBEDDED} is 1.
+         * <P>Type: INTEGER (int)</P>
+         */
+        public static final String PROFILE_CLASS = "profile_class";
+
+        /**
+         * A testing profile can be pre-loaded or downloaded onto
+         * the eUICC and provides connectivity to test equipment
+         * for the purpose of testing the device and the eUICC. It
+         * is not intended to store any operator credentials.
+         */
+        public static final int PROFILE_CLASS_TESTING = 0;
+
+        /**
+         * A provisioning profile is pre-loaded onto the eUICC and
+         * provides connectivity to a mobile network solely for the
+         * purpose of provisioning profiles.
+         */
+        public static final int PROFILE_CLASS_PROVISIONING = 1;
+
+        /**
+         * An operational profile can be pre-loaded or downloaded
+         * onto the eUICC and provides services provided by the
+         * operator.
+         */
+        public static final int PROFILE_CLASS_OPERATIONAL = 2;
+
+        /**
+         * The profile class is unset. This occurs when profile class
+         * info is not available. The subscription either has no profile
+         * metadata or the profile metadata did not encode profile class.
+         */
+        public static final int PROFILE_CLASS_UNSET = -1;
+
+        /** Default profile class */
+        public static final int PROFILE_CLASS_DEFAULT = PROFILE_CLASS_UNSET;
+
+        /**
+         * IMSI (International Mobile Subscriber Identity).
+         * <P>Type: TEXT </P>
+         */
+        public static final String IMSI = "imsi";
+
+        /** Whether uicc applications is set to be enabled or disabled. By default it's enabled. */
+        public static final String UICC_APPLICATIONS_ENABLED = "uicc_applications_enabled";
+
+        /**
+         * TelephonyProvider column name for allowed network types. Indicate which network types
+         * are allowed. Default is -1.
+         */
+        public static final String ALLOWED_NETWORK_TYPES = "allowed_network_types";
     }
 }
diff --git a/core/java/android/se/omapi/SEService.java b/core/java/android/se/omapi/SEService.java
index d646e23..a5c5c61 100644
--- a/core/java/android/se/omapi/SEService.java
+++ b/core/java/android/se/omapi/SEService.java
@@ -22,14 +22,11 @@
 
 package android.se.omapi;
 
-import android.app.ActivityThread;
 import android.annotation.NonNull;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
@@ -101,6 +98,8 @@
 
     private static final String TAG = "OMAPI.SEService";
 
+    private static final String UICC_TERMINAL = "SIM";
+
     private final Object mLock = new Object();
 
     /** The client context (e.g. activity). */
@@ -143,10 +142,6 @@
             throw new NullPointerException("Arguments must not be null");
         }
 
-        if (!hasOMAPIReaders()) {
-            throw new UnsupportedOperationException("Device does not support any OMAPI reader");
-        }
-
         mContext = context;
         mSEListener.mListener = listener;
         mSEListener.mExecutor = executor;
@@ -197,32 +192,33 @@
      * is of length 0.
      */
     public @NonNull Reader[] getReaders() {
-        if (mSecureElementService == null) {
-            throw new IllegalStateException("service not connected to system");
-        }
-        String[] readerNames;
-        try {
-            readerNames = mSecureElementService.getReaders();
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
-        }
+        loadReaders();
 
-        Reader[] readers = new Reader[readerNames.length];
-        int i = 0;
-        for (String readerName : readerNames) {
-            if (mReaders.get(readerName) == null) {
-                try {
-                    mReaders.put(readerName, new Reader(this, readerName,
-                            getReader(readerName)));
-                    readers[i++] = mReaders.get(readerName);
-                } catch (Exception e) {
-                    Log.e(TAG, "Error adding Reader: " + readerName, e);
-                }
-            } else {
-                readers[i++] = mReaders.get(readerName);
-            }
-        }
-        return readers;
+        return mReaders.values().toArray(new Reader[0]);
+    }
+
+    /**
+      * Obtain a UICC Reader instance with specific slot number from the SecureElementService
+      *
+      * @param slotNumber The index of the uicc slot. The index starts from 1.
+      * @throws IllegalArgumentException if the reader object corresponding to the uiccSlotNumber
+      *         is not exist.
+      * @return A Reader object for this uicc slot.
+      */
+     public @NonNull Reader getUiccReader(int slotNumber) {
+         if (slotNumber < 1) {
+             throw new IllegalArgumentException("slotNumber should be larger than 0");
+         }
+         loadReaders();
+
+         String readerName = UICC_TERMINAL + slotNumber;
+         Reader reader = mReaders.get(readerName);
+
+         if (reader == null) {
+            throw new IllegalArgumentException("Reader:" + readerName + " doesn't exist");
+         }
+
+         return reader;
     }
 
     /**
@@ -279,21 +275,28 @@
     }
 
     /**
-     * Helper to check if this device support any OMAPI readers
+     * Load available Secure Element Readers
      */
-    private static boolean hasOMAPIReaders() {
-        IPackageManager pm = ActivityThread.getPackageManager();
-        if (pm == null) {
-            Log.e(TAG, "Cannot get package manager, assuming OMAPI readers supported");
-            return true;
+    private void loadReaders() {
+        if (mSecureElementService == null) {
+            throw new IllegalStateException("service not connected to system");
         }
+        String[] readerNames;
         try {
-            return pm.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_UICC, 0)
-                || pm.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_ESE, 0)
-                || pm.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_SD, 0);
+            readerNames = mSecureElementService.getReaders();
         } catch (RemoteException e) {
-            Log.e(TAG, "Package manager query failed, assuming OMAPI readers supported", e);
-            return true;
+            throw e.rethrowAsRuntimeException();
+        }
+
+        for (String readerName : readerNames) {
+            if (mReaders.get(readerName) == null) {
+                try {
+                    mReaders.put(readerName, new Reader(this, readerName,
+                            getReader(readerName)));
+                } catch (Exception e) {
+                    Log.e(TAG, "Error adding Reader: " + readerName, e);
+                }
+            }
         }
     }
 }
diff --git a/core/java/android/security/ConfirmationPrompt.java b/core/java/android/security/ConfirmationPrompt.java
index 5330cff..f67af85 100644
--- a/core/java/android/security/ConfirmationPrompt.java
+++ b/core/java/android/security/ConfirmationPrompt.java
@@ -212,20 +212,16 @@
 
     private int getUiOptionsAsFlags() {
         int uiOptionsAsFlags = 0;
-        try {
-            ContentResolver contentResolver = mContext.getContentResolver();
-            int inversionEnabled = Settings.Secure.getInt(contentResolver,
-                    Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
-            if (inversionEnabled == 1) {
-                uiOptionsAsFlags |= UI_OPTION_ACCESSIBILITY_INVERTED_FLAG;
-            }
-            float fontScale = Settings.System.getFloat(contentResolver,
-                    Settings.System.FONT_SCALE);
-            if (fontScale > 1.0) {
-                uiOptionsAsFlags |= UI_OPTION_ACCESSIBILITY_MAGNIFIED_FLAG;
-            }
-        } catch (SettingNotFoundException e) {
-            Log.w(TAG, "Unexpected SettingNotFoundException");
+        ContentResolver contentResolver = mContext.getContentResolver();
+        int inversionEnabled = Settings.Secure.getInt(contentResolver,
+                Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, 0);
+        if (inversionEnabled == 1) {
+            uiOptionsAsFlags |= UI_OPTION_ACCESSIBILITY_INVERTED_FLAG;
+        }
+        float fontScale = Settings.System.getFloat(contentResolver,
+                Settings.System.FONT_SCALE, (float) 1.0);
+        if (fontScale > 1.0) {
+            uiOptionsAsFlags |= UI_OPTION_ACCESSIBILITY_MAGNIFIED_FLAG;
         }
         return uiOptionsAsFlags;
     }
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index d827b30..262d989 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.content.IntentSender;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -238,6 +239,7 @@
         public Builder(@NonNull RemoteViews presentation,
                 @NonNull InlinePresentation inlinePresentation) {
             Preconditions.checkNotNull(presentation, "presentation must be non-null");
+            Preconditions.checkNotNull(inlinePresentation, "inlinePresentation must be non-null");
             mPresentation = presentation;
             mInlinePresentation = inlinePresentation;
         }
@@ -248,7 +250,8 @@
          * @param presentation The presentation used to visualize this dataset.
          */
         public Builder(@NonNull RemoteViews presentation) {
-            this(presentation, null);
+            Preconditions.checkNotNull(presentation, "presentation must be non-null");
+            mPresentation = presentation;
         }
 
         /**
@@ -262,7 +265,9 @@
          * @hide
          */
         @SystemApi
+        @TestApi
         public Builder(@NonNull InlinePresentation inlinePresentation) {
+            Preconditions.checkNotNull(inlinePresentation, "inlinePresentation must be non-null");
             mInlinePresentation = inlinePresentation;
         }
 
@@ -576,6 +581,7 @@
          * @hide
          */
         @SystemApi
+        @TestApi
         public @NonNull Builder setInlinePresentation(@NonNull AutofillId id,
                 @Nullable AutofillValue value, @Nullable Pattern filter,
                 @NonNull InlinePresentation inlinePresentation) {
@@ -672,11 +678,13 @@
             // using specially crafted parcels.
             final RemoteViews presentation = parcel.readParcelable(null);
             final InlinePresentation inlinePresentation = parcel.readParcelable(null);
-            final Builder builder = presentation == null
-                    ? new Builder(inlinePresentation)
-                    : inlinePresentation == null
+            final Builder builder = presentation != null
+                    ? inlinePresentation == null
                             ? new Builder(presentation)
-                            : new Builder(presentation, inlinePresentation);
+                            : new Builder(presentation, inlinePresentation)
+                    : inlinePresentation == null
+                            ? new Builder()
+                            : new Builder(inlinePresentation);
             final ArrayList<AutofillId> ids =
                     parcel.createTypedArrayList(AutofillId.CREATOR);
             final ArrayList<AutofillValue> values =
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index d51e4ca..dc0f562 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -559,8 +559,8 @@
         }
 
         /**
-         * Sets targets with the resources IDs of the child view of
-         * {@link RemoteViews Presentation Template} which will cancel the session when clicked.
+         * Sets target resource IDs of the child view in {@link RemoteViews Presentation Template}
+         * which will cancel the session when clicked.
          * Those targets will be respectively applied to a child of the header, footer and
          * each {@link Dataset}.
          *
@@ -571,7 +571,7 @@
          * @throws IllegalStateException if {@link #build()} was already called.
          */
         @NonNull
-        public Builder setCancelTargetIds(@Nullable int[] ids) {
+        public Builder setPresentationCancelIds(@Nullable int[] ids) {
             throwIfDestroyed();
             mCancelIds = ids;
             return this;
@@ -609,10 +609,12 @@
                         "must add at least 1 dataset when using header or footer");
             }
 
-            for (final Dataset dataset : mDatasets) {
-                if (dataset.getFieldInlinePresentation(0) != null) {
-                    mSupportsInlineSuggestions = true;
-                    break;
+            if (mDatasets != null) {
+                for (final Dataset dataset : mDatasets) {
+                    if (dataset.getFieldInlinePresentation(0) != null) {
+                        mSupportsInlineSuggestions = true;
+                        break;
+                    }
                 }
             }
 
@@ -767,7 +769,7 @@
             }
             builder.setFlags(parcel.readInt());
             final int[] cancelIds = parcel.createIntArray();
-            builder.setCancelTargetIds(cancelIds);
+            builder.setPresentationCancelIds(cancelIds);
 
             final FillResponse response = builder.build();
             response.setRequestId(parcel.readInt());
diff --git a/core/java/android/service/dataloader/DataLoaderService.java b/core/java/android/service/dataloader/DataLoaderService.java
index 75f252e..c215778 100644
--- a/core/java/android/service/dataloader/DataLoaderService.java
+++ b/core/java/android/service/dataloader/DataLoaderService.java
@@ -58,6 +58,7 @@
      * Managed DataLoader interface. Each instance corresponds to a single installation session.
      * @hide
      */
+    @SystemApi
     public interface DataLoader {
         /**
          * A virtual constructor.
@@ -78,8 +79,8 @@
          * @param removedFiles list of files removed in this installation session.
          * @return false if unable to create and populate all addedFiles.
          */
-        boolean onPrepareImage(Collection<InstallationFile> addedFiles,
-                Collection<String> removedFiles);
+        boolean onPrepareImage(@NonNull Collection<InstallationFile> addedFiles,
+                @NonNull Collection<String> removedFiles);
     }
 
     /**
@@ -88,6 +89,7 @@
      * @return An instance of a DataLoader.
      * @hide
      */
+    @SystemApi
     public @Nullable DataLoader onCreateDataLoader() {
         return null;
     }
@@ -188,6 +190,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final class FileSystemConnector {
         /**
          * Create a wrapper for a native instance.
@@ -211,8 +214,8 @@
          * @throws IOException if trouble opening the file for writing, such as lack of disk space
          *                     or unavailable media.
          */
-        public void writeData(String name, long offsetBytes, long lengthBytes,
-                ParcelFileDescriptor incomingFd) throws IOException {
+        public void writeData(@NonNull String name, long offsetBytes, long lengthBytes,
+                @NonNull ParcelFileDescriptor incomingFd) throws IOException {
             try {
                 nativeWriteData(mNativeInstance, name, offsetBytes, lengthBytes, incomingFd);
             } catch (RuntimeException e) {
diff --git a/core/java/android/service/euicc/IEuiccServiceDumpResultCallback.aidl b/core/java/android/service/euicc/IEuiccServiceDumpResultCallback.aidl
new file mode 100644
index 0000000..ea55ebb
--- /dev/null
+++ b/core/java/android/service/euicc/IEuiccServiceDumpResultCallback.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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 android.service.euicc;
+
+/** @hide */
+oneway interface IEuiccServiceDumpResultCallback {
+    void onComplete(in String logs);
+}
\ No newline at end of file
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index c84fbc7..8464c6d 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -131,6 +131,13 @@
     public static final String KEY_RANKING_SCORE = "key_ranking_score";
 
     /**
+     * Data type: boolean, when true it suggests this is NOT a conversation notification.
+     * @hide
+     */
+    @SystemApi
+    public static final String KEY_NOT_CONVERSATION = "key_not_conversation";
+
+    /**
      * Create a notification adjustment.
      *
      * @param pkg The package of the notification.
diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl
index 5977baf..4ead3fc 100644
--- a/core/java/android/service/notification/INotificationListener.aidl
+++ b/core/java/android/service/notification/INotificationListener.aidl
@@ -49,6 +49,9 @@
     void onNotificationEnqueuedWithChannel(in IStatusBarNotificationHolder notificationHolder, in NotificationChannel channel);
     void onNotificationSnoozedUntilContext(in IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId);
     void onNotificationsSeen(in List<String> keys);
+    void onPanelRevealed(int items);
+    void onPanelHidden();
+    void onNotificationVisibilityChanged(String key, boolean isVisible);
     void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded);
     void onNotificationDirectReply(String key);
     void onSuggestedReplySent(String key, in CharSequence reply, int source);
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index da40254..e976e18 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -182,6 +182,32 @@
     }
 
     /**
+     * Implement this to know when the notification panel is revealed
+     *
+     * @param items Number of items on the panel at time of opening
+     */
+    public void onPanelRevealed(int items) {
+
+    }
+
+    /**
+     * Implement this to know when the notification panel is hidden
+     */
+    public void onPanelHidden() {
+
+    }
+
+    /**
+     * Implement this to know when a notification becomes visible or hidden from the user.
+     *
+     * @param key the notification key
+     * @param isVisible whether the notification is visible.
+     */
+    public void onNotificationVisibilityChanged(@NonNull String key, boolean isVisible) {
+
+    }
+
+    /**
      * Implement this to know when a notification change (expanded / collapsed) is visible to user.
      *
      * @param key the notification key
@@ -337,6 +363,30 @@
         }
 
         @Override
+        public void onPanelRevealed(int items) {
+            SomeArgs args = SomeArgs.obtain();
+            args.argi1 = items;
+            mHandler.obtainMessage(MyHandler.MSG_ON_PANEL_REVEALED,
+                    args).sendToTarget();
+        }
+
+        @Override
+        public void onPanelHidden() {
+            SomeArgs args = SomeArgs.obtain();
+            mHandler.obtainMessage(MyHandler.MSG_ON_PANEL_HIDDEN,
+                    args).sendToTarget();
+        }
+
+        @Override
+        public void onNotificationVisibilityChanged(String key, boolean isVisible) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = key;
+            args.argi1 = isVisible ? 1 : 0;
+            mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_VISIBILITY_CHANGED,
+                    args).sendToTarget();
+        }
+
+        @Override
         public void onNotificationExpansionChanged(String key, boolean isUserAction,
                 boolean isExpanded) {
             SomeArgs args = SomeArgs.obtain();
@@ -394,6 +444,9 @@
         public static final int MSG_ON_SUGGESTED_REPLY_SENT = 6;
         public static final int MSG_ON_ACTION_INVOKED = 7;
         public static final int MSG_ON_ALLOWED_ADJUSTMENTS_CHANGED = 8;
+        public static final int MSG_ON_PANEL_REVEALED = 9;
+        public static final int MSG_ON_PANEL_HIDDEN = 10;
+        public static final int MSG_ON_NOTIFICATION_VISIBILITY_CHANGED = 11;
 
         public MyHandler(Looper looper) {
             super(looper, null, false);
@@ -480,6 +533,25 @@
                     onAllowedAdjustmentsChanged();
                     break;
                 }
+                case MSG_ON_PANEL_REVEALED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    int items = args.argi1;
+                    args.recycle();
+                    onPanelRevealed(items);
+                    break;
+                }
+                case MSG_ON_PANEL_HIDDEN: {
+                    onPanelHidden();
+                    break;
+                }
+                case MSG_ON_NOTIFICATION_VISIBILITY_CHANGED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    String key = (String) args.arg1;
+                    boolean isVisible = args.argi1 == 1;
+                    args.recycle();
+                    onNotificationVisibilityChanged(key, isVisible);
+                    break;
+                }
             }
         }
     }
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 80d054b..e053ed5 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1383,6 +1383,22 @@
         }
 
         @Override
+        public void onPanelRevealed(int items) throws RemoteException {
+            // no-op in the listener
+        }
+
+        @Override
+        public void onPanelHidden() throws RemoteException {
+            // no-op in the listener
+        }
+
+        @Override
+        public void onNotificationVisibilityChanged(
+                String key, boolean isVisible) {
+            // no-op in the listener
+        }
+
+        @Override
         public void onNotificationSnoozedUntilContext(
                 IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId)
                 throws RemoteException {
@@ -1523,6 +1539,7 @@
         private ArrayList<CharSequence> mSmartReplies;
         private boolean mCanBubble;
         private boolean mVisuallyInterruptive;
+        private boolean mIsConversation;
 
         private static final int PARCEL_VERSION = 2;
 
@@ -1555,6 +1572,7 @@
             out.writeCharSequenceList(mSmartReplies);
             out.writeBoolean(mCanBubble);
             out.writeBoolean(mVisuallyInterruptive);
+            out.writeBoolean(mIsConversation);
         }
 
         /** @hide */
@@ -1588,6 +1606,7 @@
             mSmartReplies = in.readCharSequenceList();
             mCanBubble = in.readBoolean();
             mVisuallyInterruptive = in.readBoolean();
+            mIsConversation = in.readBoolean();
         }
 
 
@@ -1785,6 +1804,14 @@
         }
 
         /**
+         * Returns whether this notification is a conversation notification.
+         * @hide
+         */
+        public boolean isConversation() {
+            return mIsConversation;
+        }
+
+        /**
          * @hide
          */
         @VisibleForTesting
@@ -1796,7 +1823,7 @@
                 int userSentiment, boolean hidden, long lastAudiblyAlertedMs,
                 boolean noisy, ArrayList<Notification.Action> smartActions,
                 ArrayList<CharSequence> smartReplies, boolean canBubble,
-                boolean visuallyInterruptive) {
+                boolean visuallyInterruptive, boolean isConversation) {
             mKey = key;
             mRank = rank;
             mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW;
@@ -1818,6 +1845,7 @@
             mSmartReplies = smartReplies;
             mCanBubble = canBubble;
             mVisuallyInterruptive = visuallyInterruptive;
+            mIsConversation = isConversation;
         }
 
         /**
@@ -1843,7 +1871,8 @@
                     other.mSmartActions,
                     other.mSmartReplies,
                     other.mCanBubble,
-                    other.mVisuallyInterruptive);
+                    other.mVisuallyInterruptive,
+                    other.mIsConversation);
         }
 
         /**
@@ -1896,7 +1925,8 @@
                         == (other.mSmartActions == null ? 0 : other.mSmartActions.size()))
                     && Objects.equals(mSmartReplies, other.mSmartReplies)
                     && Objects.equals(mCanBubble, other.mCanBubble)
-                    && Objects.equals(mVisuallyInterruptive, other.mVisuallyInterruptive);
+                    && Objects.equals(mVisuallyInterruptive, other.mVisuallyInterruptive)
+                    && Objects.equals(mIsConversation, other.mIsConversation);
         }
     }
 
diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java
index 389040c..4f400a8 100644
--- a/core/java/android/service/notification/StatusBarNotification.java
+++ b/core/java/android/service/notification/StatusBarNotification.java
@@ -16,6 +16,9 @@
 
 package android.service.notification;
 
+import static android.app.NotificationChannel.PLACEHOLDER_CONVERSATION_ID;
+import static android.util.FeatureFlagUtils.*;
+
 import android.annotation.NonNull;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -29,6 +32,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.FeatureFlagUtils;
 
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -436,6 +441,19 @@
         return logMaker;
     }
 
+    /**
+     * @hide
+     */
+    public String getShortcutId(Context context) {
+        String conversationId = getNotification().getShortcutId();
+        if (isEnabled(context,  NOTIF_CONVO_BYPASS_SHORTCUT_REQ)
+                && getNotification().getNotificationStyle() == Notification.MessagingStyle.class
+                && TextUtils.isEmpty(conversationId)) {
+            conversationId = getId() + getTag() + PLACEHOLDER_CONVERSATION_ID;
+        }
+        return conversationId;
+    }
+
     private String getGroupLogTag() {
         return shortenTag(getGroup());
     }
diff --git a/core/java/android/service/quickaccesswallet/GetWalletCardsCallback.java b/core/java/android/service/quickaccesswallet/GetWalletCardsCallback.java
new file mode 100644
index 0000000..9d210cd
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/GetWalletCardsCallback.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.quickaccesswallet;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Handles response from the {@link QuickAccessWalletService} for {@link GetWalletCardsRequest}
+ */
+public final class GetWalletCardsCallback {
+
+    private static final String TAG = "QAWalletCallback";
+
+    private final IQuickAccessWalletServiceCallbacks mCallback;
+    private final Handler mHandler;
+    private boolean mCalled;
+
+    /**
+     * @hide
+     */
+    GetWalletCardsCallback(IQuickAccessWalletServiceCallbacks callback, Handler handler) {
+        mCallback = callback;
+        mHandler = handler;
+    }
+
+    /**
+     * Notifies the Android System that an {@link QuickAccessWalletService#onWalletCardsRequested}
+     * was successfully handled by the service.
+     *
+     * @param response The response contains the list of {@link WalletCard walletCards} to be shown
+     *                 to the user as well as the index of the card that should initially be
+     *                 presented as the selected card.
+     */
+    public void onSuccess(@NonNull GetWalletCardsResponse response) {
+        mHandler.post(() -> onSuccessInternal(response));
+    }
+
+    /**
+     * Notifies the Android System that an {@link QuickAccessWalletService#onWalletCardsRequested}
+     * could not be handled by the service.
+     *
+     * @param error The error message. <b>Note: </b> this message should <b>not</b> contain PII
+     *              (Personally Identifiable Information, such as username or email address).
+     * @throws IllegalStateException if this method or {@link #onSuccess} was already called.
+     */
+    public void onFailure(@NonNull GetWalletCardsError error) {
+        mHandler.post(() -> onFailureInternal(error));
+    }
+
+    private void onSuccessInternal(GetWalletCardsResponse response) {
+        if (mCalled) {
+            Log.w(TAG, "already called");
+            return;
+        }
+        mCalled = true;
+        try {
+            mCallback.onGetWalletCardsSuccess(response);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error returning wallet cards", e);
+        }
+    }
+
+    private void onFailureInternal(GetWalletCardsError error) {
+        if (mCalled) {
+            Log.w(TAG, "already called");
+            return;
+        }
+        mCalled = true;
+        try {
+            mCallback.onGetWalletCardsFailure(error);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error returning failure message", e);
+        }
+    }
+}
diff --git a/core/java/android/service/quickaccesswallet/GetWalletCardsError.aidl b/core/java/android/service/quickaccesswallet/GetWalletCardsError.aidl
new file mode 100644
index 0000000..847f5ac
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/GetWalletCardsError.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.service.quickaccesswallet;
+
+parcelable GetWalletCardsError;
\ No newline at end of file
diff --git a/core/java/android/service/quickaccesswallet/GetWalletCardsError.java b/core/java/android/service/quickaccesswallet/GetWalletCardsError.java
new file mode 100644
index 0000000..527d2b7
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/GetWalletCardsError.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 android.service.quickaccesswallet;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.drawable.Icon;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Error response for an {@link GetWalletCardsRequest}.
+ */
+public final class GetWalletCardsError implements Parcelable {
+
+    private final Icon mIcon;
+    private final CharSequence mMessage;
+
+    /**
+     * Construct a new error response. If provided, the icon and message will be displayed to the
+     * user.
+     *
+     * @param icon    an icon to be shown to the user next to the message. Optional.
+     * @param message message to be shown to the user. Optional.
+     */
+    public GetWalletCardsError(@Nullable Icon icon, @Nullable CharSequence message) {
+        mIcon = icon;
+        mMessage = message;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        if (mIcon == null) {
+            dest.writeByte((byte) 0);
+        } else {
+            dest.writeByte((byte) 1);
+            mIcon.writeToParcel(dest, flags);
+        }
+        TextUtils.writeToParcel(mMessage, dest, flags);
+    }
+
+    private static GetWalletCardsError readFromParcel(Parcel source) {
+        Icon icon = source.readByte() == 0 ? null : Icon.CREATOR.createFromParcel(source);
+        CharSequence message = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+        return new GetWalletCardsError(icon, message);
+    }
+
+    @NonNull
+    public static final Creator<GetWalletCardsError> CREATOR =
+            new Creator<GetWalletCardsError>() {
+                @Override
+                public GetWalletCardsError createFromParcel(Parcel source) {
+                    return readFromParcel(source);
+                }
+
+                @Override
+                public GetWalletCardsError[] newArray(int size) {
+                    return new GetWalletCardsError[size];
+                }
+            };
+
+    /**
+     * An icon that may be displayed with the message to provide a visual indication of why cards
+     * could not be provided in the Quick Access Wallet.
+     */
+    @Nullable
+    public Icon getIcon() {
+        return mIcon;
+    }
+
+    /**
+     * A localized message that may be shown to the user in the event that the wallet cards cannot
+     * be retrieved. <b>Note: </b> this message should <b>not</b> contain PII (Personally
+     * Identifiable Information, such as username or email address).
+     */
+    @Nullable
+    public CharSequence getMessage() {
+        return mMessage;
+    }
+}
diff --git a/core/java/android/service/quickaccesswallet/GetWalletCardsRequest.aidl b/core/java/android/service/quickaccesswallet/GetWalletCardsRequest.aidl
new file mode 100644
index 0000000..e70a982
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/GetWalletCardsRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.service.quickaccesswallet;
+
+parcelable GetWalletCardsRequest;
\ No newline at end of file
diff --git a/core/java/android/service/quickaccesswallet/GetWalletCardsRequest.java b/core/java/android/service/quickaccesswallet/GetWalletCardsRequest.java
new file mode 100644
index 0000000..2ba448f
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/GetWalletCardsRequest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.service.quickaccesswallet;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents a request to a {@link QuickAccessWalletService} for {@link WalletCard walletCards}.
+ * Wallet cards may represent anything that a user might carry in their wallet -- a credit card,
+ * library card, a transit pass, etc. This request contains the desired size of the card images and
+ * icons as well as the maximum number of cards that may be returned in the {@link
+ * GetWalletCardsResponse}.
+ *
+ * <p>Cards may be displayed with an optional icon and label. The icon and label should communicate
+ * the same idea. For example, if a card can be used at an NFC terminal, the icon could be an NFC
+ * icon and the label could inform the user how to interact with the NFC terminal.
+ *
+ * <p>The maximum number of cards that may be displayed in the wallet is provided in {@link
+ * #getMaxCards()}. The {@link QuickAccessWalletService} may provide up to this many cards in the
+ * {@link GetWalletCardsResponse#getWalletCards()}. If the list of cards provided exceeds this
+ * number, some of the cards may not be shown to the user.
+ */
+public final class GetWalletCardsRequest implements Parcelable {
+
+    private final int mCardWidthPx;
+    private final int mCardHeightPx;
+    private final int mIconSizePx;
+    private final int mMaxCards;
+
+    /**
+     * Creates a new GetWalletCardsRequest.
+     *
+     * @param cardWidthPx  The width of the card image in pixels.
+     * @param cardHeightPx The height of the card image in pixels.
+     * @param iconSizePx   The width and height of the optional card icon in pixels.
+     * @param maxCards     The maximum number of cards that may be provided in the response.
+     */
+    public GetWalletCardsRequest(int cardWidthPx, int cardHeightPx, int iconSizePx, int maxCards) {
+        this.mCardWidthPx = cardWidthPx;
+        this.mCardHeightPx = cardHeightPx;
+        this.mIconSizePx = iconSizePx;
+        this.mMaxCards = maxCards;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mCardWidthPx);
+        dest.writeInt(mCardHeightPx);
+        dest.writeInt(mIconSizePx);
+        dest.writeInt(mMaxCards);
+    }
+
+    @NonNull
+    public static final Creator<GetWalletCardsRequest> CREATOR =
+            new Creator<GetWalletCardsRequest>() {
+                @Override
+                public GetWalletCardsRequest createFromParcel(Parcel source) {
+                    int cardWidthPx = source.readInt();
+                    int cardHeightPx = source.readInt();
+                    int iconSizePx = source.readInt();
+                    int maxCards = source.readInt();
+                    return new GetWalletCardsRequest(cardWidthPx,
+                            cardHeightPx,
+                            iconSizePx,
+                            maxCards);
+                }
+
+                @Override
+                public GetWalletCardsRequest[] newArray(int size) {
+                    return new GetWalletCardsRequest[size];
+                }
+            };
+
+    /**
+     * The desired width of the {@link WalletCard#getCardImage()}, in pixels. The dimensions of the
+     * card image are requested so that it may be rendered without scaling.
+     * <p>
+     * The {@code cardWidthPx} and {@code cardHeightPx} should be applied to the size of the {@link
+     * WalletCard#getCardImage()}. The size of the card image is specified so that it may be
+     * rendered accurately and without distortion caused by scaling.
+     */
+    public int getCardWidthPx() {
+        return mCardWidthPx;
+    }
+
+    /**
+     * The desired height of the {@link WalletCard#getCardImage()}, in pixels. The dimensions of the
+     * card image are requested so that it may be rendered without scaling.
+     */
+    public int getCardHeightPx() {
+        return mCardHeightPx;
+    }
+
+    /**
+     * Wallet cards may be displayed next to an icon. The icon can help to convey additional
+     * information about the state of the card. If the provided icon is a bitmap, its width and
+     * height should equal iconSizePx so that it is rendered without distortion caused by scaling.
+     */
+    public int getIconSizePx() {
+        return mIconSizePx;
+    }
+
+    /**
+     * The maximum size of the {@link GetWalletCardsResponse#getWalletCards()}. If the list of cards
+     * exceeds this number, not all cards may be displayed.
+     */
+    public int getMaxCards() {
+        return mMaxCards;
+    }
+}
diff --git a/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.aidl b/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.aidl
new file mode 100644
index 0000000..b0f25b3
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.service.quickaccesswallet;
+
+parcelable GetWalletCardsResponse;
\ No newline at end of file
diff --git a/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.java b/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.java
new file mode 100644
index 0000000..996622a
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.java
@@ -0,0 +1,100 @@
+/*
+ * 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.service.quickaccesswallet;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The response for an {@link GetWalletCardsRequest} contains a list of wallet cards and the index
+ * of the card that should initially be displayed in the 'selected' position.
+ */
+public final class GetWalletCardsResponse implements Parcelable {
+
+    private final List<WalletCard> mWalletCards;
+    private final int mSelectedIndex;
+
+    /**
+     * Construct a new response.
+     *
+     * @param walletCards   The list of wallet cards.
+     * @param selectedIndex The index of the card that should be presented as the initially
+     *                      'selected' card
+     */
+    public GetWalletCardsResponse(@NonNull List<WalletCard> walletCards, int selectedIndex) {
+        this.mWalletCards = walletCards;
+        this.mSelectedIndex = selectedIndex;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mWalletCards.size());
+        dest.writeParcelableList(mWalletCards, flags);
+        dest.writeInt(mSelectedIndex);
+    }
+
+    private static GetWalletCardsResponse readFromParcel(Parcel source) {
+        int size = source.readInt();
+        List<WalletCard> walletCards =
+                source.readParcelableList(new ArrayList<>(size), WalletCard.class.getClassLoader());
+        int selectedIndex = source.readInt();
+        return new GetWalletCardsResponse(walletCards, selectedIndex);
+    }
+
+    @NonNull
+    public static final Creator<GetWalletCardsResponse> CREATOR =
+            new Creator<GetWalletCardsResponse>() {
+                @Override
+                public GetWalletCardsResponse createFromParcel(Parcel source) {
+                    return readFromParcel(source);
+                }
+
+                @Override
+                public GetWalletCardsResponse[] newArray(int size) {
+                    return new GetWalletCardsResponse[size];
+                }
+            };
+
+    /**
+     * The list of {@link WalletCard}s. The size of this list should not exceed {@link
+     * GetWalletCardsRequest#getMaxCards()}.
+     */
+    @NonNull
+    public List<WalletCard> getWalletCards() {
+        return mWalletCards;
+    }
+
+    /**
+     * The {@code selectedIndex} represents the index of the card that should be presented in the
+     * 'selected' position when the cards are initially displayed in the quick access wallet.  The
+     * {@code selectedIndex} should be greater than or equal to zero and less than the size of the
+     * list of {@link WalletCard walletCards}, unless the list is empty in which case the {@code
+     * selectedIndex} can take any value. 0 is a nice round number for such cases.
+     */
+    public int getSelectedIndex() {
+        return mSelectedIndex;
+    }
+}
diff --git a/core/java/android/service/quickaccesswallet/IQuickAccessWalletService.aidl b/core/java/android/service/quickaccesswallet/IQuickAccessWalletService.aidl
new file mode 100644
index 0000000..ee70be4
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/IQuickAccessWalletService.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.service.quickaccesswallet;
+
+import android.service.quickaccesswallet.GetWalletCardsRequest;
+import android.service.quickaccesswallet.IQuickAccessWalletServiceCallbacks;
+import android.service.quickaccesswallet.SelectWalletCardRequest;
+import android.service.quickaccesswallet.WalletServiceEvent;
+import android.service.quickaccesswallet.WalletServiceEventListenerRequest;
+
+/**
+ * Implemented by QuickAccessWalletService in the payment application
+ *
+ * @hide
+ */
+interface IQuickAccessWalletService {
+    // Request to get cards, which should be provided using the callback.
+    oneway void onWalletCardsRequested(
+        in GetWalletCardsRequest request, in IQuickAccessWalletServiceCallbacks callback);
+    // Indicates that a card has been selected.
+    oneway void onWalletCardSelected(in SelectWalletCardRequest request);
+    // Sent when the wallet is dismissed or closed.
+    oneway void onWalletDismissed();
+    // Register an event listener
+    oneway void registerWalletServiceEventListener(
+        in WalletServiceEventListenerRequest request,
+        in IQuickAccessWalletServiceCallbacks callback);
+    // Unregister an event listener
+    oneway void unregisterWalletServiceEventListener(in WalletServiceEventListenerRequest request);
+}
\ No newline at end of file
diff --git a/core/java/android/service/quickaccesswallet/IQuickAccessWalletServiceCallbacks.aidl b/core/java/android/service/quickaccesswallet/IQuickAccessWalletServiceCallbacks.aidl
new file mode 100644
index 0000000..f37b930
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/IQuickAccessWalletServiceCallbacks.aidl
@@ -0,0 +1,37 @@
+/*
+ * 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.service.quickaccesswallet;
+
+import android.service.quickaccesswallet.GetWalletCardsError;
+import android.service.quickaccesswallet.GetWalletCardsResponse;
+import android.service.quickaccesswallet.WalletServiceEvent;
+
+/**
+ * Interface to receive the result of requests to the wallet application.
+ *
+ * @hide
+ */
+interface IQuickAccessWalletServiceCallbacks {
+    // Called in response to onWalletCardsRequested on success. May only be called once per request.
+    oneway void onGetWalletCardsSuccess(in GetWalletCardsResponse response);
+    // Called in response to onWalletCardsRequested when an error occurs. May only be called once
+    // per request.
+    oneway void onGetWalletCardsFailure(in GetWalletCardsError error);
+    // Called in response to registerWalletServiceEventListener. May be called multiple times as
+    // long as the event listener is registered.
+    oneway void onWalletServiceEvent(in WalletServiceEvent event);
+}
\ No newline at end of file
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
new file mode 100644
index 0000000..cfc6d57
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
@@ -0,0 +1,89 @@
+/*
+ * 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.service.quickaccesswallet;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.Intent;
+
+import java.util.function.Consumer;
+
+/**
+ * Facilitates accessing cards from the {@link QuickAccessWalletService}.
+ *
+ * @hide
+ */
+public interface QuickAccessWalletClient {
+
+    /**
+     * Create a client for accessing wallet cards from the {@link QuickAccessWalletService}. If the
+     * service is unavailable, {@link #isWalletServiceAvailable()} will return false.
+     */
+    @NonNull
+    static QuickAccessWalletClient create(@NonNull Context context) {
+        return new QuickAccessWalletClientImpl(context);
+    }
+
+    /**
+     * @return true if the {@link QuickAccessWalletService} is available.
+     */
+    boolean isWalletServiceAvailable();
+
+    /**
+     * Get wallet cards from the {@link QuickAccessWalletService}.
+     */
+    void getWalletCards(
+            @NonNull GetWalletCardsRequest request,
+            @NonNull Consumer<GetWalletCardsResponse> onSuccessListener,
+            @NonNull Consumer<GetWalletCardsError> onFailureListener);
+
+    /**
+     * Notify the {@link QuickAccessWalletService} service that a wallet card was selected.
+     */
+    void selectWalletCard(@NonNull SelectWalletCardRequest request);
+
+    /**
+     * Notify the {@link QuickAccessWalletService} service that the Wallet was dismissed.
+     */
+    void notifyWalletDismissed();
+
+    /**
+     * Unregister event listener.
+     */
+    void registerWalletServiceEventListener(Consumer<WalletServiceEvent> listener);
+
+    /**
+     * Unregister event listener
+     */
+    void unregisterWalletServiceEventListener(Consumer<WalletServiceEvent> listener);
+
+    /**
+     * The manifest entry for the QuickAccessWalletService may also publish information about the
+     * activity that hosts the Wallet view. This is typically the home screen of the Wallet
+     * application.
+     */
+    @Nullable
+    Intent getWalletActivity();
+
+    /**
+     * The manifest entry for the {@link QuickAccessWalletService} may publish the activity that
+     * hosts the settings
+     */
+    @Nullable
+    Intent getSettingsActivity();
+}
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
new file mode 100644
index 0000000..17c287f
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
@@ -0,0 +1,342 @@
+/*
+ * 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.service.quickaccesswallet;
+
+import static android.service.quickaccesswallet.QuickAccessWalletService.SERVICE_INTERFACE;
+
+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.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Queue;
+import java.util.UUID;
+import java.util.function.Consumer;
+
+/**
+ * @hide
+ */
+@SuppressWarnings("AndroidJdkLibsChecker")
+class QuickAccessWalletClientImpl implements QuickAccessWalletClient, Handler.Callback,
+        ServiceConnection {
+
+    private static final String TAG = "QAWalletSClient";
+    private final Handler mHandler;
+    private final Context mContext;
+    private final Queue<ApiCaller> mRequestQueue;
+    private final Map<Consumer<WalletServiceEvent>, String> mEventListeners;
+    private boolean mIsConnected;
+    @Nullable
+    private IQuickAccessWalletService mService;
+
+
+    @Nullable
+    private final QuickAccessWalletServiceInfo mServiceInfo;
+
+    private static final int MSG_CONNECT = 1;
+    private static final int MSG_CONNECTED = 2;
+    private static final int MSG_EXECUTE = 3;
+    private static final int MSG_DISCONNECT = 4;
+
+    QuickAccessWalletClientImpl(@NonNull Context context) {
+        mContext = context.getApplicationContext();
+        mServiceInfo = QuickAccessWalletServiceInfo.tryCreate(context);
+        mHandler = new Handler(Looper.getMainLooper(), this);
+        mRequestQueue = new LinkedList<>();
+        mEventListeners = new HashMap<>(1);
+    }
+
+    @Override
+    public boolean handleMessage(Message msg) {
+        switch (msg.what) {
+            case MSG_CONNECT:
+                connectInternal();
+                break;
+            case MSG_CONNECTED:
+                onConnectedInternal((IQuickAccessWalletService) msg.obj);
+                break;
+            case MSG_EXECUTE:
+                executeInternal((ApiCaller) msg.obj);
+                break;
+            case MSG_DISCONNECT:
+                disconnectInternal();
+                break;
+            default:
+                Log.w(TAG, "Unknown what: " + msg.what);
+                return false;
+        }
+        return true;
+    }
+
+    private void connect() {
+        mHandler.sendMessage(mHandler.obtainMessage(MSG_CONNECT));
+    }
+
+    private void connectInternal() {
+        if (mServiceInfo == null) {
+            Log.w(TAG, "Wallet service unavailable");
+            return;
+        }
+        if (mIsConnected) {
+            Log.w(TAG, "already connected");
+            return;
+        }
+        mIsConnected = true;
+        Intent intent = new Intent(SERVICE_INTERFACE);
+        intent.setComponent(mServiceInfo.getComponentName());
+        int flags = Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY;
+        mContext.bindService(intent, this, flags);
+    }
+
+    private void onConnectedInternal(IQuickAccessWalletService service) {
+        if (!mIsConnected) {
+            Log.w(TAG, "onConnectInternal but connection closed");
+            mService = null;
+            return;
+        }
+        mService = service;
+        for (ApiCaller apiCaller : new ArrayList<>(mRequestQueue)) {
+            try {
+                apiCaller.performApiCall(mService);
+            } catch (RemoteException e) {
+                Log.e(TAG, "onConnectedInternal error", e);
+                apiCaller.onApiError();
+                disconnect();
+                break;
+            }
+            mRequestQueue.remove(apiCaller);
+        }
+    }
+
+    private void disconnect() {
+        mHandler.sendMessage(mHandler.obtainMessage(MSG_DISCONNECT));
+    }
+
+    private void disconnectInternal() {
+        if (!mIsConnected) {
+            Log.w(TAG, "already disconnected");
+            return;
+        }
+        mIsConnected = false;
+        mContext.unbindService(/*conn=*/this);
+        mService = null;
+        mEventListeners.clear();
+        mRequestQueue.clear();
+    }
+
+    private void execute(ApiCaller apiCaller) {
+        mHandler.sendMessage(mHandler.obtainMessage(MSG_EXECUTE, apiCaller));
+    }
+
+    private void executeInternal(ApiCaller apiCall) {
+        if (mIsConnected && mService != null) {
+            try {
+                apiCall.performApiCall(mService);
+            } catch (RemoteException e) {
+                Log.w(TAG, "executeInternal error", e);
+                apiCall.onApiError();
+                disconnect();
+            }
+        } else {
+            mRequestQueue.add(apiCall);
+            connect();
+        }
+    }
+
+    public boolean isWalletServiceAvailable() {
+        return mServiceInfo != null;
+    }
+
+    private abstract static class ApiCaller {
+        abstract void performApiCall(IQuickAccessWalletService service) throws RemoteException;
+
+        void onApiError() {
+            Log.w(TAG, "api error");
+        }
+    }
+
+    public void getWalletCards(
+            @NonNull GetWalletCardsRequest request,
+            @NonNull Consumer<GetWalletCardsResponse> onSuccessListener,
+            @NonNull Consumer<GetWalletCardsError> onFailureListener) {
+
+        BaseCallbacks callback = new BaseCallbacks() {
+            @Override
+            public void onGetWalletCardsSuccess(GetWalletCardsResponse response) {
+                mHandler.post(() -> onSuccessListener.accept(response));
+            }
+
+            @Override
+            public void onGetWalletCardsFailure(GetWalletCardsError error) {
+                mHandler.post(() -> onFailureListener.accept(error));
+            }
+        };
+
+        execute(new ApiCaller() {
+            @Override
+            public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
+                service.onWalletCardsRequested(request, callback);
+            }
+
+            @Override
+            public void onApiError() {
+                callback.onGetWalletCardsFailure(new GetWalletCardsError(null, null));
+            }
+        });
+    }
+
+    public void selectWalletCard(@NonNull SelectWalletCardRequest request) {
+        execute(new ApiCaller() {
+            @Override
+            public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
+                service.onWalletCardSelected(request);
+            }
+        });
+    }
+
+    public void notifyWalletDismissed() {
+        execute(new ApiCaller() {
+            @Override
+            public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
+                service.onWalletDismissed();
+                mHandler.sendMessage(mHandler.obtainMessage(MSG_DISCONNECT));
+            }
+        });
+    }
+
+    @Override
+    public void registerWalletServiceEventListener(Consumer<WalletServiceEvent> listener) {
+
+        BaseCallbacks callback = new BaseCallbacks() {
+            @Override
+            public void onWalletServiceEvent(WalletServiceEvent event) {
+                Log.i(TAG, "onWalletServiceEvent");
+                mHandler.post(() -> listener.accept(event));
+            }
+        };
+
+        execute(new ApiCaller() {
+            @Override
+            public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
+                String listenerId = UUID.randomUUID().toString();
+                WalletServiceEventListenerRequest request =
+                        new WalletServiceEventListenerRequest(listenerId);
+                mEventListeners.put(listener, listenerId);
+                service.registerWalletServiceEventListener(request, callback);
+            }
+        });
+    }
+
+    @Override
+    public void unregisterWalletServiceEventListener(Consumer<WalletServiceEvent> listener) {
+        execute(new ApiCaller() {
+            @Override
+            public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
+                String listenerId = mEventListeners.get(listener);
+                if (listenerId == null) {
+                    return;
+                }
+                WalletServiceEventListenerRequest request =
+                        new WalletServiceEventListenerRequest(listenerId);
+                service.unregisterWalletServiceEventListener(request);
+            }
+        });
+    }
+
+    @Override
+    @Nullable
+    public Intent getWalletActivity() {
+        if (mServiceInfo == null || TextUtils.isEmpty(mServiceInfo.getWalletActivity())) {
+            return null;
+        }
+        return new Intent(QuickAccessWalletService.ACTION_VIEW_WALLET)
+                .setComponent(
+                        new ComponentName(
+                                mServiceInfo.getComponentName().getPackageName(),
+                                mServiceInfo.getWalletActivity()));
+    }
+
+    @Override
+    @Nullable
+    public Intent getSettingsActivity() {
+        if (mServiceInfo == null || TextUtils.isEmpty(mServiceInfo.getSettingsActivity())) {
+            return null;
+        }
+        return new Intent(QuickAccessWalletService.ACTION_VIEW_WALLET_SETTINGS)
+                .setComponent(
+                        new ComponentName(
+                                mServiceInfo.getComponentName().getPackageName(),
+                                mServiceInfo.getSettingsActivity()));
+    }
+
+    /**
+     * Connection to the {@link QuickAccessWalletService}
+     */
+
+
+    @Override
+    public void onServiceConnected(ComponentName name, IBinder binder) {
+        IQuickAccessWalletService service = IQuickAccessWalletService.Stub.asInterface(binder);
+        mHandler.sendMessage(mHandler.obtainMessage(MSG_CONNECTED, service));
+    }
+
+    @Override
+    public void onServiceDisconnected(ComponentName name) {
+        // Do not disconnect, as we may later be re-connected
+        Log.w(TAG, "onServiceDisconnected");
+    }
+
+    @Override
+    public void onBindingDied(ComponentName name) {
+        // This is a recoverable error but the client will need to reconnect.
+        Log.w(TAG, "onBindingDied");
+        disconnect();
+    }
+
+    @Override
+    public void onNullBinding(ComponentName name) {
+        Log.w(TAG, "onNullBinding");
+        disconnect();
+    }
+
+    private static class BaseCallbacks extends IQuickAccessWalletServiceCallbacks.Stub {
+        public void onGetWalletCardsSuccess(GetWalletCardsResponse response) {
+            throw new IllegalStateException();
+        }
+
+        public void onGetWalletCardsFailure(GetWalletCardsError error) {
+            throw new IllegalStateException();
+        }
+
+        public void onWalletServiceEvent(WalletServiceEvent event) {
+            throw new IllegalStateException();
+        }
+    }
+}
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
new file mode 100644
index 0000000..d968405
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
@@ -0,0 +1,337 @@
+/*
+ * 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.service.quickaccesswallet;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.util.Log;
+
+/**
+ * A {@code QuickAccessWalletService} provides a list of {@code WalletCard}s shown in the Quick
+ * Access Wallet. The Quick Access Wallet allows the user to change their selected payment method
+ * and access other important passes, such as tickets and transit passes, without leaving the
+ * context of their current app.
+ *
+ * <p>An {@code QuickAccessWalletService} is only bound to the Android System for the purposes of
+ * showing wallet cards if:
+ * <ol>
+ *   <li>The application hosting the QuickAccessWalletService is also the default NFC payment
+ *   application. This means that the same application must also have a
+ *   {@link android.nfc.cardemulation.HostApduService} or
+ *   {@link android.nfc.cardemulation.OffHostApduService} that requires the
+ *   android.permission.BIND_NFC_SERVICE permission.
+ *   <li>The user explicitly selected the application as the default payment application in
+ *   the Tap &amp; pay settings screen.
+ *   <li>The application requires the {@code android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE}
+ *   permission in its manifest.
+ *   <li>The user explicitly enables it using Android Settings (the
+ *       {@link Settings#ACTION_QUICK_ACCESS_WALLET_SETTINGS} intent can be used to launch it).
+ * </ol>
+ *
+ * <a name="BasicUsage"></a>
+ * <h3>Basic usage</h3>
+ *
+ * <p>The basic Quick Access Wallet process is defined by the workflow below:
+ * <ol>
+ *   <li>User performs a gesture to bring up the Quick Access Wallet, which is displayed by the
+ *   Android System.
+ *   <li>The Android System creates a {@link GetWalletCardsRequest}, binds to the
+ *   {@link QuickAccessWalletService}, and delivers the request.
+ *   <li>The service receives the request through {@link #onWalletCardsRequested}
+ *   <li>The service responds by calling {@link GetWalletCardsCallback#onSuccess} with a
+ *   {@link GetWalletCardsResponse response} that contains between 1 and
+ *   {@link GetWalletCardsRequest#getMaxCards() maxCards} cards.
+ *   <li>The Android System displays the Quick Access Wallet containing the provided cards. The
+ *   card at the {@link GetWalletCardsResponse#getSelectedIndex() selectedIndex} will initially
+ *   be presented as the 'selected' card.
+ *   <li>As soon as the cards are displayed, the Android System will notify the service that the
+ *   card at the selected index has been selected through {@link #onWalletCardSelected}.
+ *   <li>The user interacts with the wallet and may select one or more cards in sequence. Each time
+ *   a new card is selected, the Android System will notify the service through
+ *   {@link #onWalletCardSelected} and will provide the {@link WalletCard#getCardId() cardId} of the
+ *   card that is now selected.
+ *   <li>When the wallet is dismissed, the Android System will notify the service through
+ *   {@link #onWalletDismissed}.
+ * </ol>
+ *
+ * <p>The workflow is designed to minimize the time that the Android System is bound to the
+ * service, but connections may be cached and reused to improve performance and conserve memory.
+ * All calls should be considered stateless: if the service needs to keep state between calls, it
+ * must do its own state management (keeping in mind that the service's process might be killed
+ * by the Android System when unbound; for example, if the device is running low in memory).
+ *
+ * <p>
+ * <a name="ErrorHandling"></a>
+ * <h3>Error handling</h3>
+ * <p>If the service encountered an error processing the request, it should call
+ * {@link GetWalletCardsCallback#onFailure}.
+ * For performance reasons, it's paramount that the service calls either
+ * {@link GetWalletCardsCallback#onSuccess} or
+ * {@link GetWalletCardsCallback#onFailure} for each
+ * {@link #onWalletCardsRequested} received - if it doesn't, the request will eventually time out
+ * and be discarded by the Android System.
+ *
+ * <p>
+ * <a name="ManifestEntry"></a>
+ * <h3>Manifest entry</h3>
+ *
+ * <p>QuickAccessWalletService must require the permission
+ * "android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE".
+ *
+ * <pre class="prettyprint">
+ * {@literal
+ * <service
+ *     android:name=".MyQuickAccessWalletService"
+ *     android:label="@string/my_default_tile_label"
+ *     android:icon="@drawable/my_default_icon_label"
+ *     android:permission="android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE">
+ *     <intent-filter>
+ *         <action android:name="android.service.quickaccesswallet.QuickAccessWalletService" />
+ *     </intent-filter>
+ *     <meta-data android:name="android.quickaccesswallet"
+ *          android:resource="@xml/quickaccesswallet_configuration" />;
+ * </service>}
+ * </pre>
+ * <p>
+ * The {@literal <meta-data>} element includes an android:resource attribute that points to an
+ * XML resource with further details about the service. The {@code quickaccesswallet_configuration}
+ * in the example above specifies an activity that allows the users to view the entire wallet.
+ * The following example shows the quickaccesswallet_configuration XML resource:
+ * <p>
+ * <pre class="prettyprint">
+ * {@literal
+ * <quickaccesswallet-service
+ *   xmlns:android="http://schemas.android.com/apk/res/android"
+ *   android:settingsActivity="com.example.android.SettingsActivity"
+ *   android:targetActivity="com.example.android.WalletActivity"/>
+ * }
+ * </pre>
+ *
+ * <p>The entry for {@code settingsActivity} should contain the fully qualified class name of an
+ * activity that allows the user to modify the settings for this service. The {@code targetActivity}
+ * entry should contain the fully qualified class name of an activity that allows the user to view
+ * their entire wallet. If specified, the wallet activity will be started with the Intent action
+ * {@link #ACTION_VIEW_WALLET} and the settings activity will be started with the Intent action
+ * {@link #ACTION_VIEW_WALLET_SETTINGS}.
+ */
+public abstract class QuickAccessWalletService extends Service {
+
+    private static final String TAG = "QAWalletService";
+
+    /**
+     * The {@link Intent} that must be declared as handled by the service. To be supported, the
+     * service must also require the
+     * {@link android.Manifest.permission#BIND_QUICK_ACCESS_WALLET_SERVICE}
+     * permission so that other applications can not abuse it.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+    public static final String SERVICE_INTERFACE =
+            "android.service.quickaccesswallet.QuickAccessWalletService";
+
+    /**
+     * Intent action to launch an activity to display the wallet.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_VIEW_WALLET =
+            "android.service.quickaccesswallet.action.VIEW_WALLET";
+
+    /**
+     * Intent action to launch an activity to display quick access wallet settings.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_VIEW_WALLET_SETTINGS =
+            "android.service.quickaccesswallet.action.VIEW_WALLET_SETTINGS";
+
+    /**
+     * Broadcast Action: Sent by the wallet application to dismiss the Quick Access Wallet.
+     * <p>
+     * The Quick Access Wallet may be shown in a system window on top of other Activities. If the
+     * user selects a payment card from the Quick Access Wallet and then holds their phone to an NFC
+     * terminal, the wallet application will need to show a payment Activity. But if the Quick
+     * Access Wallet is still being shown, it may obscure the payment Activity. To avoid this, the
+     * wallet application can send a broadcast to the Android System with this action to request
+     * that the Quick Access Wallet be dismissed.
+     * <p>
+     * This broadcast must use the {@code android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE}
+     * permission to ensure that it is only delivered to System UI. Furthermore, your application
+     * must require the {@code android.permission.DISMISS_QUICK_ACCESS_WALLET}
+     * <p>
+     * <pre class="prettyprint">
+     * context.sendBroadcast(
+     *     new Intent(ACTION_DISMISS_WALLET), Manifest.permission.BIND_QUICK_ACCESS_WALLET_SERVICE);
+     * </pre>
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_DISMISS_WALLET =
+            "android.service.quickaccesswallet.action.DISMISS_WALLET";
+
+    /**
+     * Name under which a QuickAccessWalletService component publishes information about itself.
+     * This meta-data should reference an XML resource containing a
+     * <code>&lt;{@link
+     * android.R.styleable#QuickAccessWalletService quickaccesswallet-service}&gt;</code> tag. This
+     * is a a sample XML file configuring an QuickAccessWalletService:
+     * <pre> &lt;quickaccesswallet-service
+     *     android:walletActivity="foo.bar.WalletActivity"
+     *     . . .
+     * /&gt;</pre>
+     */
+    public static final String SERVICE_META_DATA = "android.quickaccesswallet";
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    @Nullable
+    private String mEventListenerId;
+    @Nullable
+    private IQuickAccessWalletServiceCallbacks mEventListener;
+
+    private final IQuickAccessWalletService mInterface = new IQuickAccessWalletService.Stub() {
+        @Override
+        public void onWalletCardsRequested(
+                @NonNull GetWalletCardsRequest request,
+                @NonNull IQuickAccessWalletServiceCallbacks callback) {
+            mHandler.post(() -> onWalletCardsRequestedInternal(request, callback));
+        }
+
+        @Override
+        public void onWalletCardSelected(@NonNull SelectWalletCardRequest request) {
+            mHandler.post(() -> QuickAccessWalletService.this.onWalletCardSelected(request));
+        }
+
+        @Override
+        public void onWalletDismissed() {
+            mHandler.post(QuickAccessWalletService.this::onWalletDismissed);
+        }
+
+        public void registerWalletServiceEventListener(
+                @NonNull WalletServiceEventListenerRequest request,
+                @NonNull IQuickAccessWalletServiceCallbacks callback) {
+            mHandler.post(() -> registerDismissWalletListenerInternal(request, callback));
+        }
+
+        public void unregisterWalletServiceEventListener(
+                @NonNull WalletServiceEventListenerRequest request) {
+            mHandler.post(() -> unregisterDismissWalletListenerInternal(request));
+        }
+    };
+
+    private void onWalletCardsRequestedInternal(
+            GetWalletCardsRequest request,
+            IQuickAccessWalletServiceCallbacks callback) {
+        onWalletCardsRequested(request, new GetWalletCardsCallback(callback, mHandler));
+    }
+
+    @Override
+    @Nullable
+    public IBinder onBind(@NonNull Intent intent) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+            // Binding to the QuickAccessWalletService is protected by the
+            // android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE permission, which is defined in
+            // R. Pre-R devices can have other side-loaded applications that claim this permission.
+            // This ensures that the service is only available when properly permission protected.
+            Log.w(TAG, "Warning: binding on pre-R device");
+        }
+        if (SERVICE_INTERFACE.equals(intent.getAction())) {
+            return mInterface.asBinder();
+        }
+        Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
+        return null;
+    }
+
+    /**
+     * Called when the user requests the service to provide wallet cards.
+     *
+     * <p>This method will be called on the main thread, but the callback may be called from any
+     * thread. The callback should be called as quickly as possible. The service must always call
+     * either {@link GetWalletCardsCallback#onSuccess(GetWalletCardsResponse)} or {@link
+     * GetWalletCardsCallback#onFailure(GetWalletCardsError)}. Calling multiple times or calling
+     * both methods will cause an exception to be thrown.
+     */
+    public abstract void onWalletCardsRequested(
+            @NonNull GetWalletCardsRequest request,
+            @NonNull GetWalletCardsCallback callback);
+
+    /**
+     * A wallet card was selected. Sent when the user selects a wallet card from the list of cards.
+     * Selection may indicate that the card is now in the center of the screen, or highlighted in
+     * some other fashion. It does not mean that the user clicked on the card -- clicking on the
+     * card will cause the {@link WalletCard#getPendingIntent()} to be sent.
+     *
+     * <p>Card selection events are especially important to NFC payment applications because
+     * many NFC terminals can only accept one payment card at a time. If the user has several NFC
+     * cards in their wallet, selecting different cards can change which payment method is presented
+     * to the terminal.
+     */
+    public abstract void onWalletCardSelected(@NonNull SelectWalletCardRequest request);
+
+    /**
+     * Indicates that the wallet was dismissed. This is received when the Quick Access Wallet is no
+     * longer visible.
+     */
+    public abstract void onWalletDismissed();
+
+    /**
+     * Send a {@link WalletServiceEvent} to the Quick Access Wallet.
+     * <p>
+     * Background events may require that the Quick Access Wallet view be updated. For example, if
+     * the wallet application hosting this service starts to handle an NFC payment while the Quick
+     * Access Wallet is being shown, the Quick Access Wallet will need to be dismissed so that the
+     * Activity showing the payment can be displayed to the user.
+     */
+    public final void sendWalletServiceEvent(@NonNull WalletServiceEvent serviceEvent) {
+        mHandler.post(() -> sendWalletServiceEventInternal(serviceEvent));
+    }
+
+    private void sendWalletServiceEventInternal(WalletServiceEvent serviceEvent) {
+        if (mEventListener == null) {
+            Log.i(TAG, "No dismiss listener registered");
+            return;
+        }
+        try {
+            mEventListener.onWalletServiceEvent(serviceEvent);
+        } catch (RemoteException e) {
+            Log.w(TAG, "onWalletServiceEvent error", e);
+            mEventListenerId = null;
+            mEventListener = null;
+        }
+    }
+
+    private void registerDismissWalletListenerInternal(
+            @NonNull WalletServiceEventListenerRequest request,
+            @NonNull IQuickAccessWalletServiceCallbacks callback) {
+        mEventListenerId = request.getListenerId();
+        mEventListener = callback;
+    }
+
+    private void unregisterDismissWalletListenerInternal(
+            @NonNull WalletServiceEventListenerRequest request) {
+        if (mEventListenerId != null && mEventListenerId.equals(request.getListenerId())) {
+            mEventListenerId = null;
+            mEventListener = null;
+        } else {
+            Log.w(TAG, "dismiss listener missing or replaced");
+        }
+    }
+}
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
new file mode 100644
index 0000000..8793f28
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.quickaccesswallet;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.provider.Settings;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * {@link ServiceInfo} and meta-data about a {@link QuickAccessWalletService}.
+ *
+ * @hide
+ */
+class QuickAccessWalletServiceInfo {
+
+    private static final String TAG = "QAWalletSInfo";
+    private static final String TAG_WALLET_SERVICE = "quickaccesswallet-service";
+
+    private final ServiceInfo mServiceInfo;
+    private final ServiceMetadata mServiceMetadata;
+
+    private QuickAccessWalletServiceInfo(
+            @NonNull ServiceInfo serviceInfo,
+            @NonNull ServiceMetadata metadata) {
+        mServiceInfo = serviceInfo;
+        mServiceMetadata = metadata;
+    }
+
+    @Nullable
+    static QuickAccessWalletServiceInfo tryCreate(@NonNull Context context) {
+        ComponentName defaultPaymentApp = getDefaultPaymentApp(context);
+        if (defaultPaymentApp == null) {
+            Log.d(TAG, "create: default payment app not set");
+            return null;
+        }
+
+        ServiceInfo serviceInfo = getWalletServiceInfo(context, defaultPaymentApp.getPackageName());
+        if (serviceInfo == null) {
+            Log.d(TAG, "create: unable to resolve service intent");
+            return null;
+        }
+
+        if (!Manifest.permission.BIND_QUICK_ACCESS_WALLET_SERVICE.equals(serviceInfo.permission)) {
+            Log.w(TAG, String.format("QuickAccessWalletService from %s does not have permission %s",
+                    serviceInfo.packageName, Manifest.permission.BIND_QUICK_ACCESS_WALLET_SERVICE));
+            return null;
+        }
+
+        ServiceMetadata metadata = parseServiceMetadata(context, serviceInfo);
+        return new QuickAccessWalletServiceInfo(serviceInfo, metadata);
+    }
+
+    private static ComponentName getDefaultPaymentApp(Context context) {
+        ContentResolver cr = context.getContentResolver();
+        String comp = Settings.Secure.getString(cr, Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT);
+        return comp == null ? null : ComponentName.unflattenFromString(comp);
+    }
+
+    private static ServiceInfo getWalletServiceInfo(Context context, String packageName) {
+        Intent intent = new Intent(QuickAccessWalletService.SERVICE_INTERFACE);
+        intent.setPackage(packageName);
+        List<ResolveInfo> resolveInfos =
+                context.getPackageManager().queryIntentServices(intent,
+                        PackageManager.MATCH_DEFAULT_ONLY);
+        return resolveInfos.isEmpty() ? null : resolveInfos.get(0).serviceInfo;
+    }
+
+    private static class ServiceMetadata {
+        @Nullable
+        private final String mSettingsActivity;
+        @Nullable
+        private final String mWalletActivity;
+
+        private ServiceMetadata(String settingsActivity, String walletActivity) {
+            this.mSettingsActivity = settingsActivity;
+            this.mWalletActivity = walletActivity;
+        }
+    }
+
+    private static ServiceMetadata parseServiceMetadata(Context context, ServiceInfo serviceInfo) {
+        PackageManager pm = context.getPackageManager();
+        final XmlResourceParser parser =
+                serviceInfo.loadXmlMetaData(pm, QuickAccessWalletService.SERVICE_META_DATA);
+
+        if (parser == null) {
+            return new ServiceMetadata(null, null);
+        }
+
+        try {
+            Resources resources = pm.getResourcesForApplication(serviceInfo.applicationInfo);
+            int type = 0;
+            while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) {
+                type = parser.next();
+            }
+
+            if (TAG_WALLET_SERVICE.equals(parser.getName())) {
+                final AttributeSet allAttributes = Xml.asAttributeSet(parser);
+                TypedArray afsAttributes = null;
+                try {
+                    afsAttributes = resources.obtainAttributes(allAttributes,
+                            R.styleable.QuickAccessWalletService);
+                    String settingsActivity = afsAttributes.getString(
+                            R.styleable.QuickAccessWalletService_settingsActivity);
+                    String walletActivity = afsAttributes.getString(
+                            R.styleable.QuickAccessWalletService_targetActivity);
+                    return new ServiceMetadata(settingsActivity, walletActivity);
+                } finally {
+                    if (afsAttributes != null) {
+                        afsAttributes.recycle();
+                    }
+                }
+            } else {
+                Log.e(TAG, "Meta-data does not start with quickaccesswallet-service tag");
+            }
+
+        } catch (PackageManager.NameNotFoundException
+                | IOException
+                | XmlPullParserException e) {
+            Log.e(TAG, "Error parsing quickaccesswallet service meta-data", e);
+        }
+        return new ServiceMetadata(null, null);
+    }
+
+    /**
+     * @return the component name of the {@link QuickAccessWalletService}
+     */
+    @NonNull
+    ComponentName getComponentName() {
+        return mServiceInfo.getComponentName();
+    }
+
+    /**
+     * @return the fully qualified name of the activity that hosts the full wallet. If available,
+     * this intent should be started with the action
+     * {@link QuickAccessWalletService#ACTION_VIEW_WALLET}
+     */
+    @Nullable
+    String getWalletActivity() {
+        return mServiceMetadata.mWalletActivity;
+    }
+
+    /**
+     * @return the fully qualified name of the activity that allows the user to change quick access
+     * wallet settings. If available, this intent should be started with the action {@link
+     * QuickAccessWalletService#ACTION_VIEW_WALLET_SETTINGS}
+     */
+    @Nullable
+    String getSettingsActivity() {
+        return mServiceMetadata.mSettingsActivity;
+    }
+}
diff --git a/core/java/android/service/quickaccesswallet/SelectWalletCardRequest.aidl b/core/java/android/service/quickaccesswallet/SelectWalletCardRequest.aidl
new file mode 100644
index 0000000..97a0d41
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/SelectWalletCardRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.service.quickaccesswallet;
+
+parcelable SelectWalletCardRequest;
\ No newline at end of file
diff --git a/core/java/android/service/quickaccesswallet/SelectWalletCardRequest.java b/core/java/android/service/quickaccesswallet/SelectWalletCardRequest.java
new file mode 100644
index 0000000..cb69eee
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/SelectWalletCardRequest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.quickaccesswallet;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents a request to a {@link QuickAccessWalletService} to select a particular {@link
+ * WalletCard walletCard}. Card selection events are transmitted to the WalletService so that the
+ * selected card may be used by the NFC payment service.
+ */
+public final class SelectWalletCardRequest implements Parcelable {
+
+    private final String mCardId;
+
+    /**
+     * Creates a new GetWalletCardsRequest.
+     *
+     * @param cardId The {@link WalletCard#getCardId() cardId} of the wallet card that is currently
+     *               selected.
+     */
+    public SelectWalletCardRequest(@NonNull String cardId) {
+        this.mCardId = cardId;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mCardId);
+    }
+
+    @NonNull
+    public static final Creator<SelectWalletCardRequest> CREATOR =
+            new Creator<SelectWalletCardRequest>() {
+                @Override
+                public SelectWalletCardRequest createFromParcel(Parcel source) {
+                    String cardId = source.readString();
+                    return new SelectWalletCardRequest(cardId);
+                }
+
+                @Override
+                public SelectWalletCardRequest[] newArray(int size) {
+                    return new SelectWalletCardRequest[size];
+                }
+            };
+
+    /**
+     * The {@link WalletCard#getCardId() cardId} of the wallet card that is currently selected.
+     */
+    @NonNull
+    public String getCardId() {
+        return mCardId;
+    }
+}
diff --git a/core/java/android/service/quickaccesswallet/WalletCard.aidl b/core/java/android/service/quickaccesswallet/WalletCard.aidl
new file mode 100644
index 0000000..115213d
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/WalletCard.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.service.quickaccesswallet;
+
+parcelable WalletCard;
\ No newline at end of file
diff --git a/core/java/android/service/quickaccesswallet/WalletCard.java b/core/java/android/service/quickaccesswallet/WalletCard.java
new file mode 100644
index 0000000..c3b1a4b
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/WalletCard.java
@@ -0,0 +1,245 @@
+/*
+ * 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.service.quickaccesswallet;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.graphics.drawable.Icon;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * A {@link WalletCard} can represent anything that a user might carry in their wallet -- a credit
+ * card, library card, transit pass, etc. Cards are identified by a String identifier and contain a
+ * card image, card image content description, and a {@link PendingIntent} to be used if the user
+ * clicks on the card. Cards may be displayed with an icon and label, though these are optional.
+ */
+public final class WalletCard implements Parcelable {
+
+    private final String mCardId;
+    private final Icon mCardImage;
+    private final CharSequence mContentDescription;
+    private final PendingIntent mPendingIntent;
+    private final Icon mCardIcon;
+    private final CharSequence mCardLabel;
+
+    private WalletCard(Builder builder) {
+        this.mCardId = builder.mCardId;
+        this.mCardImage = builder.mCardImage;
+        this.mContentDescription = builder.mContentDescription;
+        this.mPendingIntent = builder.mPendingIntent;
+        this.mCardIcon = builder.mCardIcon;
+        this.mCardLabel = builder.mCardLabel;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mCardId);
+        mCardImage.writeToParcel(dest, flags);
+        TextUtils.writeToParcel(mContentDescription, dest, flags);
+        PendingIntent.writePendingIntentOrNullToParcel(mPendingIntent, dest);
+        if (mCardIcon == null) {
+            dest.writeByte((byte) 0);
+        } else {
+            dest.writeByte((byte) 1);
+            mCardIcon.writeToParcel(dest, flags);
+        }
+        TextUtils.writeToParcel(mCardLabel, dest, flags);
+    }
+
+    private static WalletCard readFromParcel(Parcel source) {
+        String cardId = source.readString();
+        Icon cardImage = Icon.CREATOR.createFromParcel(source);
+        CharSequence contentDesc = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+        PendingIntent pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(source);
+        Icon cardIcon = source.readByte() == 0 ? null : Icon.CREATOR.createFromParcel(source);
+        CharSequence cardLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+        return new Builder(cardId, cardImage, contentDesc, pendingIntent)
+                .setCardIcon(cardIcon)
+                .setCardLabel(cardLabel)
+                .build();
+    }
+
+    @NonNull
+    public static final Creator<WalletCard> CREATOR =
+            new Creator<WalletCard>() {
+                @Override
+                public WalletCard createFromParcel(Parcel source) {
+                    return readFromParcel(source);
+                }
+
+                @Override
+                public WalletCard[] newArray(int size) {
+                    return new WalletCard[size];
+                }
+            };
+
+    /**
+     * The card id must be unique within the list of cards returned.
+     */
+    @NonNull
+    public String getCardId() {
+        return mCardId;
+    }
+
+    /**
+     * The visual representation of the card. If the card image Icon is a bitmap, it should have a
+     * width of {@link GetWalletCardsRequest#getCardWidthPx()} and a height of {@link
+     * GetWalletCardsRequest#getCardHeightPx()}.
+     */
+    @NonNull
+    public Icon getCardImage() {
+        return mCardImage;
+    }
+
+    /**
+     * The content description of the card image.
+     */
+    @NonNull
+    public CharSequence getContentDescription() {
+        return mContentDescription;
+    }
+
+    /**
+     * If the user performs a click on the card, this PendingIntent will be sent. If the device is
+     * locked, the wallet will first request device unlock before sending the pending intent.
+     */
+    @NonNull
+    public PendingIntent getPendingIntent() {
+        return mPendingIntent;
+    }
+
+    /**
+     * An icon may be shown alongside the card image to convey information about how the card can be
+     * used, or if some other action must be taken before using the card. For example, an NFC logo
+     * could indicate that the card is NFC-enabled and will be provided to an NFC terminal if the
+     * phone is held in close proximity to the NFC reader.
+     *
+     * <p>If the supplied Icon is backed by a bitmap, it should have width and height
+     * {@link GetWalletCardsRequest#getIconSizePx()}.
+     */
+    @Nullable
+    public Icon getCardIcon() {
+        return mCardIcon;
+    }
+
+    /**
+     * A card label may be shown alongside the card image to convey information about how the card
+     * can be used, or if some other action must be taken before using the card. For example, an
+     * NFC-enabled card could be labeled "Hold near reader" to inform the user of how to use NFC
+     * cards when interacting with an NFC reader.
+     *
+     * <p>If the provided label is too long to fit on one line, it may be truncated and ellipsized.
+     */
+    @Nullable
+    public CharSequence getCardLabel() {
+        return mCardLabel;
+    }
+
+    /**
+     * Builder for {@link WalletCard} objects. You must to provide cardId, cardImage,
+     * contentDescription, and pendingIntent. If the card is opaque and should be shown with
+     * elevation, set hasShadow to true. cardIcon and cardLabel are optional.
+     */
+    public static final class Builder {
+        private String mCardId;
+        private Icon mCardImage;
+        private CharSequence mContentDescription;
+        private PendingIntent mPendingIntent;
+        private Icon mCardIcon;
+        private CharSequence mCardLabel;
+
+        /**
+         * @param cardId             The card id must be non-null and unique within the list of
+         *                           cards returned. <b>Note:
+         *                           </b> this card ID should <b>not</b> contain PII (Personally
+         *                           Identifiable Information, * such as username or email
+         *                           address).
+         * @param cardImage          The visual representation of the card. If the card image Icon
+         *                           is a bitmap, it should have a width of {@link
+         *                           GetWalletCardsRequest#getCardWidthPx()} and a height of {@link
+         *                           GetWalletCardsRequest#getCardHeightPx()}. If the card image
+         *                           does not have these dimensions, it may appear distorted when it
+         *                           is scaled to fit these dimensions on screen.
+         * @param contentDescription The content description of the card image. This field is
+         *                           required.
+         *                           <b>Note: </b> this message should <b>not</b> contain PII
+         *                           (Personally Identifiable Information, such as username or email
+         *                           address).
+         * @param pendingIntent      If the user performs a click on the card, this PendingIntent
+         *                           will be sent. If the device is locked, the wallet will first
+         *                           request device unlock before sending the pending intent.
+         */
+        public Builder(@NonNull String cardId,
+                @NonNull Icon cardImage,
+                @NonNull CharSequence contentDescription,
+                @NonNull PendingIntent pendingIntent) {
+            mCardId = cardId;
+            mCardImage = cardImage;
+            mContentDescription = contentDescription;
+            mPendingIntent = pendingIntent;
+        }
+
+        /**
+         * An icon may be shown alongside the card image to convey information about how the card
+         * can be used, or if some other action must be taken before using the card. For example, an
+         * NFC logo could indicate that the card is NFC-enabled and will be provided to an NFC
+         * terminal if the phone is held in close proximity to the NFC reader. This field is
+         * optional.
+         *
+         * <p>If the supplied Icon is backed by a bitmap, it should have width and height
+         * {@link GetWalletCardsRequest#getIconSizePx()}.
+         */
+        @NonNull
+        public Builder setCardIcon(@Nullable Icon cardIcon) {
+            mCardIcon = cardIcon;
+            return this;
+        }
+
+        /**
+         * A card label may be shown alongside the card image to convey information about how the
+         * card can be used, or if some other action must be taken before using the card. For
+         * example, an NFC-enabled card could be labeled "Hold near reader" to inform the user of
+         * how to use NFC cards when interacting with an NFC reader. This field is optional.
+         * <b>Note: </b> this card label should <b>not</b> contain PII (Personally Identifiable
+         * Information, such as username or email address). If the provided label is too long to fit
+         * on one line, it may be truncated and ellipsized.
+         */
+        @NonNull
+        public Builder setCardLabel(@Nullable CharSequence cardLabel) {
+            mCardLabel = cardLabel;
+            return this;
+        }
+
+        /**
+         * Builds a new {@link WalletCard} instance.
+         *
+         * @return A built response.
+         */
+        @NonNull
+        public WalletCard build() {
+            return new WalletCard(this);
+        }
+    }
+}
diff --git a/core/java/android/service/quickaccesswallet/WalletServiceEvent.aidl b/core/java/android/service/quickaccesswallet/WalletServiceEvent.aidl
new file mode 100644
index 0000000..891cf1d
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/WalletServiceEvent.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.service.quickaccesswallet;
+
+parcelable WalletServiceEvent;
\ No newline at end of file
diff --git a/core/java/android/service/quickaccesswallet/WalletServiceEvent.java b/core/java/android/service/quickaccesswallet/WalletServiceEvent.java
new file mode 100644
index 0000000..fb524be
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/WalletServiceEvent.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.quickaccesswallet;
+
+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;
+
+/**
+ * Represents a request from the {@link QuickAccessWalletService wallet app} to the Quick Access
+ * Wallet in System UI. Background events may necessitate that the Quick Access Wallet update its
+ * view. For example, if the wallet application handles an NFC payment while the Quick Access Wallet
+ * is being shown, it needs to tell the Quick Access Wallet so that the wallet can be dismissed and
+ * Activity showing the payment can be displayed to the user.
+ */
+public final class WalletServiceEvent implements Parcelable {
+
+    /**
+     * An NFC payment has started. If the Quick Access Wallet is in a system window, it will need to
+     * be dismissed so that an Activity showing the payment can be displayed.
+     */
+    public static final int TYPE_NFC_PAYMENT_STARTED = 1;
+
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({TYPE_NFC_PAYMENT_STARTED})
+    public @interface EventType {
+    }
+
+    @EventType
+    private final int mEventType;
+
+    /**
+     * Creates a new DismissWalletRequest.
+     */
+    public WalletServiceEvent(@EventType int eventType) {
+        this.mEventType = eventType;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mEventType);
+    }
+
+    @NonNull
+    public static final Creator<WalletServiceEvent> CREATOR =
+            new Creator<WalletServiceEvent>() {
+                @Override
+                public WalletServiceEvent createFromParcel(Parcel source) {
+                    int eventType = source.readInt();
+                    return new WalletServiceEvent(eventType);
+                }
+
+                @Override
+                public WalletServiceEvent[] newArray(int size) {
+                    return new WalletServiceEvent[size];
+                }
+            };
+
+    /**
+     * @return the event type
+     */
+    @EventType
+    public int getEventType() {
+        return mEventType;
+    }
+}
diff --git a/core/java/android/service/quickaccesswallet/WalletServiceEventListenerRequest.aidl b/core/java/android/service/quickaccesswallet/WalletServiceEventListenerRequest.aidl
new file mode 100644
index 0000000..155f92e
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/WalletServiceEventListenerRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.service.quickaccesswallet;
+
+parcelable WalletServiceEventListenerRequest;
\ No newline at end of file
diff --git a/core/java/android/service/quickaccesswallet/WalletServiceEventListenerRequest.java b/core/java/android/service/quickaccesswallet/WalletServiceEventListenerRequest.java
new file mode 100644
index 0000000..223110e
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/WalletServiceEventListenerRequest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.quickaccesswallet;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Register a dismiss request listener with the QuickAccessWalletService. This allows the service to
+ * dismiss the wallet if it needs to show a payment activity in response to an NFC event.
+ *
+ * @hide
+ */
+public final class WalletServiceEventListenerRequest implements Parcelable {
+
+    private final String mListenerId;
+
+    /**
+     * Construct a new {@code DismissWalletListenerRequest}.
+     *
+     * @param listenerKey A unique key that identifies the listener.
+     */
+    public WalletServiceEventListenerRequest(@NonNull String listenerKey) {
+        mListenerId = listenerKey;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mListenerId);
+    }
+
+    private static WalletServiceEventListenerRequest readFromParcel(Parcel source) {
+        String listenerId = source.readString();
+        return new WalletServiceEventListenerRequest(listenerId);
+    }
+
+    @NonNull
+    public static final Creator<WalletServiceEventListenerRequest> CREATOR =
+            new Creator<WalletServiceEventListenerRequest>() {
+                @Override
+                public WalletServiceEventListenerRequest createFromParcel(Parcel source) {
+                    return readFromParcel(source);
+                }
+
+                @Override
+                public WalletServiceEventListenerRequest[] newArray(int size) {
+                    return new WalletServiceEventListenerRequest[size];
+                }
+            };
+
+    /**
+     * Returns the unique key that identifies the wallet dismiss request listener.
+     */
+    @NonNull
+    public String getListenerId() {
+        return mListenerId;
+    }
+}
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 67925bf..1966f17 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -29,7 +29,6 @@
 import android.hardware.soundtrigger.SoundTrigger.ConfidenceLevel;
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent;
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra;
-import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
 import android.media.AudioFormat;
@@ -67,7 +66,12 @@
     /**
      * Indicates that recognition for the given keyphrase is not supported.
      * No further interaction should be performed with the detector that returns this availability.
+     *
+     * @deprecated This is no longer a valid state. Enrollment can occur outside of
+     * {@link KeyphraseEnrollmentInfo} through another privileged application. We can no longer
+     * determine ahead of time if the keyphrase and locale are unsupported by the system.
      */
+    @Deprecated
     public static final int STATE_KEYPHRASE_UNSUPPORTED = -1;
     /**
      * Indicates that the given keyphrase is not enrolled.
@@ -85,41 +89,15 @@
      */
     private static final int STATE_NOT_READY = 0;
 
-    // Keyphrase management actions. Used in getManageIntent() ----//
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = { "MANAGE_ACTION_" }, value = {
-            MANAGE_ACTION_ENROLL,
-            MANAGE_ACTION_RE_ENROLL,
-            MANAGE_ACTION_UN_ENROLL
-    })
-    private @interface ManageActions {}
-
-    /**
-     * Indicates that we need to enroll.
-     *
-     * @hide
-     */
-    public static final int MANAGE_ACTION_ENROLL = 0;
-    /**
-     * Indicates that we need to re-enroll.
-     *
-     * @hide
-     */
-    public static final int MANAGE_ACTION_RE_ENROLL = 1;
-    /**
-     * Indicates that we need to un-enroll.
-     *
-     * @hide
-     */
-    public static final int MANAGE_ACTION_UN_ENROLL = 2;
-
     //-- Flags for startRecognition    ----//
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, prefix = { "RECOGNITION_FLAG_" }, value = {
             RECOGNITION_FLAG_NONE,
             RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO,
-            RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS
+            RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS,
+            RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION,
+            RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION,
     })
     public @interface RecognitionFlags {}
 
@@ -144,6 +122,26 @@
      */
     public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 0x2;
 
+    /**
+     * Audio capabilities flag for {@link #startRecognition(int)} that indicates
+     * if the underlying recognition should use AEC.
+     * This capability may or may not be supported by the system, and support can be queried
+     * by calling {@link #getSupportedAudioCapabilities()}. The corresponding capabilities field for
+     * this flag is {@link #AUDIO_CAPABILITY_ECHO_CANCELLATION}. If this flag is passed without the
+     * audio capability supported, there will be no audio effect applied.
+     */
+    public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 0x4;
+
+    /**
+     * Audio capabilities flag for {@link #startRecognition(int)} that indicates
+     * if the underlying recognition should use noise suppression.
+     * This capability may or may not be supported by the system, and support can be queried
+     * by calling {@link #getSupportedAudioCapabilities()}. The corresponding capabilities field for
+     * this flag is {@link #AUDIO_CAPABILITY_NOISE_SUPPRESSION}. If this flag is passed without the
+     * audio capability supported, there will be no audio effect applied.
+     */
+    public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 0x8;
+
     //---- Recognition mode flags. Return codes for getSupportedRecognitionModes() ----//
     // Must be kept in sync with the related attribute defined as searchKeyphraseRecognitionFlags.
 
@@ -168,6 +166,46 @@
     public static final int RECOGNITION_MODE_USER_IDENTIFICATION
             = SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION;
 
+    //-- Audio capabilities. Values in returned bit field for getSupportedAudioCapabilities() --//
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "AUDIO_CAPABILITY_" }, value = {
+            AUDIO_CAPABILITY_ECHO_CANCELLATION,
+            AUDIO_CAPABILITY_NOISE_SUPPRESSION,
+    })
+    public @interface AudioCapabilities {}
+
+    /**
+     * If set the underlying module supports AEC.
+     * Returned by {@link #getSupportedAudioCapabilities()}
+     */
+    public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION =
+            SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION;
+
+    /**
+     * If set, the underlying module supports noise suppression.
+     * Returned by {@link #getSupportedAudioCapabilities()}
+     */
+    public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION =
+            SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "MODEL_PARAM_" }, value = {
+            MODEL_PARAM_THRESHOLD_FACTOR,
+    })
+    public @interface ModelParams {}
+
+    /**
+     * Controls the sensitivity threshold adjustment factor for a given model.
+     * Negative value corresponds to less sensitive model (high threshold) and
+     * a positive value corresponds to a more sensitive model (low threshold).
+     * Default value is 0.
+     */
+    public static final int MODEL_PARAM_THRESHOLD_FACTOR =
+            android.hardware.soundtrigger.ModelParams.THRESHOLD_FACTOR;
+
     static final String TAG = "AlwaysOnHotwordDetector";
     static final boolean DBG = false;
 
@@ -186,7 +224,8 @@
      * The metadata of the Keyphrase, derived from the enrollment application.
      * This may be null if this keyphrase isn't supported by the enrollment application.
      */
-    private final KeyphraseMetadata mKeyphraseMetadata;
+    @Nullable
+    private KeyphraseMetadata mKeyphraseMetadata;
     private final KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo;
     private final IVoiceInteractionService mVoiceInteractionService;
     private final IVoiceInteractionManagerService mModelManagementService;
@@ -198,6 +237,53 @@
     private int mAvailability = STATE_NOT_READY;
 
     /**
+     *  A ModelParamRange is a representation of supported parameter range for a
+     *  given loaded model.
+     */
+    public static final class ModelParamRange {
+        private final SoundTrigger.ModelParamRange mModelParamRange;
+
+        /** @hide */
+        ModelParamRange(SoundTrigger.ModelParamRange modelParamRange) {
+            mModelParamRange = modelParamRange;
+        }
+
+        /**
+         * The inclusive start of supported range.
+         *
+         * @return start of range
+         */
+        public int start() {
+            return mModelParamRange.start;
+        }
+
+        /**
+         * The inclusive end of supported range.
+         *
+         * @return end of range
+         */
+        public int end() {
+            return mModelParamRange.end;
+        }
+
+        @Override
+        @NonNull
+        public String toString() {
+            return mModelParamRange.toString();
+        }
+
+        @Override
+        public boolean equals(@Nullable Object obj) {
+            return mModelParamRange.equals(obj);
+        }
+
+        @Override
+        public int hashCode() {
+            return mModelParamRange.hashCode();
+        }
+    }
+
+    /**
      * Additional payload for {@link Callback#onDetected}.
      */
     public static class EventPayload {
@@ -339,7 +425,6 @@
         mText = text;
         mLocale = locale;
         mKeyphraseEnrollmentInfo = keyphraseEnrollmentInfo;
-        mKeyphraseMetadata = mKeyphraseEnrollmentInfo.getKeyphraseMetadata(text, locale);
         mExternalCallback = callback;
         mHandler = new MyHandler();
         mInternalCallback = new SoundTriggerListener(mHandler);
@@ -375,8 +460,7 @@
         }
 
         // This method only makes sense if we can actually support a recognition.
-        if (mAvailability != STATE_KEYPHRASE_ENROLLED
-                && mAvailability != STATE_KEYPHRASE_UNENROLLED) {
+        if (mAvailability != STATE_KEYPHRASE_ENROLLED || mKeyphraseMetadata == null) {
             throw new UnsupportedOperationException(
                     "Getting supported recognition modes for the keyphrase is not supported");
         }
@@ -385,6 +469,37 @@
     }
 
     /**
+     * Get the audio capabilities supported by the platform which can be enabled when
+     * starting a recognition.
+     *
+     * @see #AUDIO_CAPABILITY_ECHO_CANCELLATION
+     * @see #AUDIO_CAPABILITY_NOISE_SUPPRESSION
+     *
+     * @return Bit field encoding of the AudioCapabilities supported.
+     */
+    @AudioCapabilities
+    public int getSupportedAudioCapabilities() {
+        if (DBG) Slog.d(TAG, "getSupportedAudioCapabilities()");
+        synchronized (mLock) {
+            return getSupportedAudioCapabilitiesLocked();
+        }
+    }
+
+    private int getSupportedAudioCapabilitiesLocked() {
+        try {
+            ModuleProperties properties =
+                    mModelManagementService.getDspModuleProperties(mVoiceInteractionService);
+            if (properties != null) {
+                return properties.audioCapabilities;
+            }
+
+            return 0;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Starts recognition for the associated keyphrase.
      *
      * @see #RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
@@ -445,6 +560,83 @@
     }
 
     /**
+     * Set a model specific {@link ModelParams} with the given value. This
+     * parameter will keep its value for the duration the model is loaded regardless of starting and
+     * stopping recognition. Once the model is unloaded, the value will be lost.
+     * {@link AlwaysOnHotwordDetector#queryParameter} should be checked first before calling this
+     * method.
+     *
+     * @param modelParam   {@link ModelParams}
+     * @param value        Value to set
+     * @return - {@link SoundTrigger#STATUS_OK} in case of success
+     *         - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
+     *         - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
+     *         - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
+     *           if API is not supported by HAL
+     */
+    public int setParameter(@ModelParams int modelParam, int value) {
+        if (DBG) {
+            Slog.d(TAG, "setParameter(" + modelParam + ", " + value + ")");
+        }
+
+        synchronized (mLock) {
+            if (mAvailability == STATE_INVALID) {
+                throw new IllegalStateException("setParameter called on an invalid detector");
+            }
+
+            return setParameterLocked(modelParam, value);
+        }
+    }
+
+    /**
+     * Get a model specific {@link ModelParams}. This parameter will keep its value
+     * for the duration the model is loaded regardless of starting and stopping recognition.
+     * Once the model is unloaded, the value will be lost. If the value is not set, a default
+     * value is returned. See {@link ModelParams} for parameter default values.
+     * {@link AlwaysOnHotwordDetector#queryParameter} should be checked first before
+     * calling this method.
+     *
+     * @param modelParam   {@link ModelParams}
+     * @return value of parameter
+     */
+    public int getParameter(@ModelParams int modelParam) {
+        if (DBG) {
+            Slog.d(TAG, "getParameter(" + modelParam + ")");
+        }
+
+        synchronized (mLock) {
+            if (mAvailability == STATE_INVALID) {
+                throw new IllegalStateException("getParameter called on an invalid detector");
+            }
+
+            return getParameterLocked(modelParam);
+        }
+    }
+
+    /**
+     * Determine if parameter control is supported for the given model handle.
+     * This method should be checked prior to calling {@link AlwaysOnHotwordDetector#setParameter}
+     * or {@link AlwaysOnHotwordDetector#getParameter}.
+     *
+     * @param modelParam {@link ModelParams}
+     * @return supported range of parameter, null if not supported
+     */
+    @Nullable
+    public ModelParamRange queryParameter(@ModelParams int modelParam) {
+        if (DBG) {
+            Slog.d(TAG, "queryParameter(" + modelParam + ")");
+        }
+
+        synchronized (mLock) {
+            if (mAvailability == STATE_INVALID) {
+                throw new IllegalStateException("queryParameter called on an invalid detector");
+            }
+
+            return queryParameterLocked(modelParam);
+        }
+    }
+
+    /**
      * Creates an intent to start the enrollment for the associated keyphrase.
      * This intent must be invoked using {@link Context#startForegroundService(Intent)}.
      * Starting re-enrollment is only valid if the keyphrase is un-enrolled,
@@ -462,7 +654,7 @@
     public Intent createEnrollIntent() {
         if (DBG) Slog.d(TAG, "createEnrollIntent");
         synchronized (mLock) {
-            return getManageIntentLocked(MANAGE_ACTION_ENROLL);
+            return getManageIntentLocked(KeyphraseEnrollmentInfo.MANAGE_ACTION_ENROLL);
         }
     }
 
@@ -483,7 +675,7 @@
     public Intent createUnEnrollIntent() {
         if (DBG) Slog.d(TAG, "createUnEnrollIntent");
         synchronized (mLock) {
-            return getManageIntentLocked(MANAGE_ACTION_UN_ENROLL);
+            return getManageIntentLocked(KeyphraseEnrollmentInfo.MANAGE_ACTION_UN_ENROLL);
         }
     }
 
@@ -504,11 +696,11 @@
     public Intent createReEnrollIntent() {
         if (DBG) Slog.d(TAG, "createReEnrollIntent");
         synchronized (mLock) {
-            return getManageIntentLocked(MANAGE_ACTION_RE_ENROLL);
+            return getManageIntentLocked(KeyphraseEnrollmentInfo.MANAGE_ACTION_RE_ENROLL);
         }
     }
 
-    private Intent getManageIntentLocked(int action) {
+    private Intent getManageIntentLocked(@KeyphraseEnrollmentInfo.ManageActions int action) {
         if (mAvailability == STATE_INVALID) {
             throw new IllegalStateException("getManageIntent called on an invalid detector");
         }
@@ -544,8 +736,7 @@
     void onSoundModelsChanged() {
         synchronized (mLock) {
             if (mAvailability == STATE_INVALID
-                    || mAvailability == STATE_HARDWARE_UNAVAILABLE
-                    || mAvailability == STATE_KEYPHRASE_UNSUPPORTED) {
+                    || mAvailability == STATE_HARDWARE_UNAVAILABLE) {
                 Slog.w(TAG, "Received onSoundModelsChanged for an unsupported keyphrase/config");
                 return;
             }
@@ -555,7 +746,9 @@
             // or was deleted.
             // The availability change callback should ensure that the client starts recognition
             // again if needed.
-            stopRecognitionLocked();
+            if (mAvailability == STATE_KEYPHRASE_ENROLLED) {
+                stopRecognitionLocked();
+            }
 
             // Execute a refresh availability task - which should then notify of a change.
             new RefreshAvailabiltyTask().execute();
@@ -571,12 +764,21 @@
                 (recognitionFlags&RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO) != 0;
         boolean allowMultipleTriggers =
                 (recognitionFlags&RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS) != 0;
+
+        int audioCapabilities = 0;
+        if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION) != 0) {
+            audioCapabilities |= AUDIO_CAPABILITY_ECHO_CANCELLATION;
+        }
+        if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION) != 0) {
+            audioCapabilities |= AUDIO_CAPABILITY_NOISE_SUPPRESSION;
+        }
+
         int code = STATUS_ERROR;
         try {
             code = mModelManagementService.startRecognition(mVoiceInteractionService,
                     mKeyphraseMetadata.id, mLocale.toLanguageTag(), mInternalCallback,
                     new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers,
-                            recognitionExtra, null /* additional data */));
+                            recognitionExtra, null /* additional data */, audioCapabilities));
         } catch (RemoteException e) {
             Slog.w(TAG, "RemoteException in startRecognition!", e);
         }
@@ -601,6 +803,47 @@
         return code;
     }
 
+    private int setParameterLocked(@ModelParams int modelParam, int value) {
+        try {
+            int code = mModelManagementService.setParameter(mVoiceInteractionService,
+                    mKeyphraseMetadata.id, modelParam, value);
+
+            if (code != STATUS_OK) {
+                Slog.w(TAG, "setParameter failed with error code " + code);
+            }
+
+            return code;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private int getParameterLocked(@ModelParams int modelParam) {
+        try {
+            return mModelManagementService.getParameter(mVoiceInteractionService,
+                    mKeyphraseMetadata.id, modelParam);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Nullable
+    private ModelParamRange queryParameterLocked(@ModelParams int modelParam) {
+        try {
+            SoundTrigger.ModelParamRange modelParamRange =
+                    mModelManagementService.queryParameter(mVoiceInteractionService,
+                            mKeyphraseMetadata.id, modelParam);
+
+            if (modelParamRange == null) {
+                return null;
+            }
+
+            return new ModelParamRange(modelParamRange);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     private void notifyStateChangedLocked() {
         Message message = Message.obtain(mHandler, MSG_AVAILABILITY_CHANGED);
         message.arg1 = mAvailability;
@@ -688,20 +931,17 @@
         @Override
         public Void doInBackground(Void... params) {
             int availability = internalGetInitialAvailability();
-            boolean enrolled = false;
-            // Fetch the sound model if the availability is one of the supported ones.
-            if (availability == STATE_NOT_READY
-                    || availability == STATE_KEYPHRASE_UNENROLLED
-                    || availability == STATE_KEYPHRASE_ENROLLED) {
-                enrolled = internalGetIsEnrolled(mKeyphraseMetadata.id, mLocale);
-                if (!enrolled) {
-                    availability = STATE_KEYPHRASE_UNENROLLED;
-                } else {
-                    availability = STATE_KEYPHRASE_ENROLLED;
-                }
-            }
 
             synchronized (mLock) {
+                if (availability == STATE_NOT_READY) {
+                    internalUpdateEnrolledKeyphraseMetadata();
+                    if (mKeyphraseMetadata != null) {
+                        availability = STATE_KEYPHRASE_ENROLLED;
+                    } else {
+                        availability = STATE_KEYPHRASE_UNENROLLED;
+                    }
+                }
+
                 if (DBG) {
                     Slog.d(TAG, "Hotword availability changed from " + mAvailability
                             + " -> " + availability);
@@ -730,28 +970,22 @@
             } catch (RemoteException e) {
                 Slog.w(TAG, "RemoteException in getDspProperties!", e);
             }
+
             // No DSP available
             if (dspModuleProperties == null) {
                 return STATE_HARDWARE_UNAVAILABLE;
             }
-            // No enrollment application supports this keyphrase/locale
-            if (mKeyphraseMetadata == null) {
-                return STATE_KEYPHRASE_UNSUPPORTED;
-            }
+
             return STATE_NOT_READY;
         }
 
-        /**
-         * @return The corresponding {@link KeyphraseSoundModel} or null if none is found.
-         */
-        private boolean internalGetIsEnrolled(int keyphraseId, Locale locale) {
+        private void internalUpdateEnrolledKeyphraseMetadata() {
             try {
-                return mModelManagementService.isEnrolledForKeyphrase(
-                        mVoiceInteractionService, keyphraseId, locale.toLanguageTag());
+                mKeyphraseMetadata = mModelManagementService.getEnrolledKeyphraseMetadata(
+                        mVoiceInteractionService, mText, mLocale.toLanguageTag());
             } catch (RemoteException e) {
-                Slog.w(TAG, "RemoteException in listRegisteredKeyphraseSoundModels!", e);
+                Slog.w(TAG, "RemoteException in internalUpdateEnrolledKeyphraseMetadata", e);
             }
-            return false;
         }
     }
 
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 36e057f..fc99836 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -16,14 +16,18 @@
 
 package android.service.voice;
 
+import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
 import android.app.Service;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.hardware.soundtrigger.KeyphraseEnrollmentInfo;
+import android.media.voice.KeyphraseModelManager;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -304,6 +308,23 @@
     }
 
     /**
+     * Creates an {@link KeyphraseModelManager} to use for enrolling voice models outside of the
+     * pre-bundled system voice models.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES)
+    @NonNull
+    public final KeyphraseModelManager createKeyphraseModelManager() {
+        if (mSystemService == null) {
+            throw new IllegalStateException("Not available until onReady() is called");
+        }
+        synchronized (mLock) {
+            return new KeyphraseModelManager(mSystemService);
+        }
+    }
+
+    /**
      * @return Details of keyphrases available for enrollment.
      * @hide
      */
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index 52b7294..36f2c62 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -1137,7 +1137,7 @@
         mWindow = new SoftInputWindow(mContext, "VoiceInteractionSession", mTheme,
                 mCallbacks, this, mDispatcherState,
                 WindowManager.LayoutParams.TYPE_VOICE_INTERACTION, Gravity.BOTTOM, true);
-        mWindow.getWindow().setFitWindowInsetsTypes(0 /* types */);
+        mWindow.getWindow().getAttributes().setFitInsetsTypes(0 /* types */);
         mWindow.getWindow().addFlags(
                 WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED |
                         WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
index 00e0b7c..84b6869 100644
--- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
@@ -37,4 +37,5 @@
     void requestWallpaperColors();
     @UnsupportedAppUsage
     void destroy();
+    void setZoomOut(float scale);
 }
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 9a76a1b..dd78c78 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -16,6 +16,7 @@
 
 package android.service.wallpaper;
 
+import android.annotation.FloatRange;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
@@ -31,6 +32,7 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.PixelFormat;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.hardware.display.DisplayManager;
@@ -121,6 +123,9 @@
     private static final int MSG_WINDOW_MOVED = 10035;
     private static final int MSG_TOUCH_EVENT = 10040;
     private static final int MSG_REQUEST_WALLPAPER_COLORS = 10050;
+    private static final int MSG_SCALE = 10100;
+
+    private static final float MAX_SCALE = 1.15f;
 
     private static final int NOTIFY_COLORS_RATE_LIMIT_MS = 1000;
 
@@ -169,6 +174,7 @@
         int mType;
         int mCurWidth;
         int mCurHeight;
+        float mZoom = 0f;
         int mWindowFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
         int mWindowPrivateFlags =
                 WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS;
@@ -188,6 +194,7 @@
         DisplayCutout mDispatchedDisplayCutout = DisplayCutout.NO_CUTOUT;
         final InsetsState mInsetsState = new InsetsState();
         final MergedConfiguration mMergedConfiguration = new MergedConfiguration();
+        private final Point mSurfaceSize = new Point();
 
         final WindowManager.LayoutParams mLayout
                 = new WindowManager.LayoutParams();
@@ -494,6 +501,15 @@
         }
 
         /**
+         * Returns the current scale of the surface
+         * @hide
+         */
+        @VisibleForTesting
+        public float getZoom() {
+            return mZoom;
+        }
+
+        /**
          * Called once to initialize the engine.  After returning, the
          * engine's surface will be created by the framework.
          */
@@ -621,6 +637,16 @@
         }
 
         /**
+         * Called when the zoom level of the wallpaper changed.
+         * This method will be called with the initial zoom level when the surface is created.
+         *
+         * @param zoom the zoom level, between 0 indicating fully zoomed in and 1 indicating fully
+         *             zoomed out.
+         */
+        public void onZoomChanged(@FloatRange(from = 0f, to = 1f) float zoom) {
+        }
+
+        /**
          * Notifies the engine that wallpaper colors changed significantly.
          * This will trigger a {@link #onComputeColors()} call.
          */
@@ -704,6 +730,7 @@
             out.print(prefix); out.print("mConfiguration=");
                     out.println(mMergedConfiguration.getMergedConfiguration());
             out.print(prefix); out.print("mLayout="); out.println(mLayout);
+            out.print(prefix); out.print("mZoom="); out.println(mZoom);
             synchronized (mLock) {
                 out.print(prefix); out.print("mPendingXOffset="); out.print(mPendingXOffset);
                         out.print(" mPendingXOffset="); out.println(mPendingXOffset);
@@ -719,6 +746,37 @@
             }
         }
 
+        /**
+         * Set the wallpaper zoom to the given value. This value will be ignored when in ambient
+         * mode (and zoom will be reset to 0).
+         * @hide
+         * @param zoom between 0 and 1 (inclusive) indicating fully zoomed in to fully zoomed out
+         *              respectively.
+         */
+        @VisibleForTesting
+        public void setZoom(float zoom) {
+            if (DEBUG) {
+                Log.v(TAG, "set zoom received: " + zoom);
+            }
+            boolean updated = false;
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Log.v(TAG, "mZoom: " + mZoom + " updated: " + zoom);
+                }
+                if (mIsInAmbientMode) {
+                    mZoom = 0;
+                }
+                if (Float.compare(zoom, mZoom) != 0) {
+                    mZoom = zoom;
+                    updated = true;
+                }
+            }
+            if (DEBUG) Log.v(TAG, "setZoom updated? " + updated);
+            if (updated && !mDestroyed) {
+                onZoomChanged(mZoom);
+            }
+        }
+
         private void dispatchPointer(MotionEvent event) {
             if (event.isTouchEvent()) {
                 synchronized (mLock) {
@@ -811,7 +869,7 @@
                         // Add window
                         mLayout.type = mIWallpaperEngine.mWindowType;
                         mLayout.gravity = Gravity.START|Gravity.TOP;
-                        mLayout.setFitWindowInsetsTypes(0 /* types */);
+                        mLayout.setFitInsetsTypes(0 /* types */);
                         mLayout.setTitle(WallpaperService.this.getClass().getName());
                         mLayout.windowAnimations =
                                 com.android.internal.R.style.Animation_Wallpaper;
@@ -838,12 +896,13 @@
                     } else {
                         mLayout.surfaceInsets.set(0, 0, 0, 0);
                     }
+
                     final int relayoutResult = mSession.relayout(
                         mWindow, mWindow.mSeq, mLayout, mWidth, mHeight,
                             View.VISIBLE, 0, -1, mWinFrame, mContentInsets,
                             mVisibleInsets, mStableInsets, mBackdropFrame,
                             mDisplayCutout, mMergedConfiguration, mSurfaceControl,
-                            mInsetsState);
+                            mInsetsState, mSurfaceSize);
                     if (mSurfaceControl.isValid()) {
                         mSurfaceHolder.mSurface.copyFrom(mSurfaceControl);
                         mSurfaceControl.release();
@@ -917,6 +976,7 @@
                                     c.surfaceCreated(mSurfaceHolder);
                                 }
                             }
+                            onZoomChanged(0f);
                         }
 
                         redrawNeeded |= creating || (relayoutResult
@@ -1077,6 +1137,7 @@
                 mIsInAmbientMode = inAmbientMode;
                 if (mCreated) {
                     onAmbientModeChanged(inAmbientMode, animationDuration);
+                    setZoom(0);
                 }
             }
         }
@@ -1351,6 +1412,11 @@
             }
         }
 
+        public void setZoomOut(float scale) {
+            Message msg = mCaller.obtainMessageI(MSG_SCALE, Float.floatToIntBits(scale));
+            mCaller.sendMessage(msg);
+        }
+
         public void reportShown() {
             if (!mShownReported) {
                 mShownReported = true;
@@ -1423,6 +1489,9 @@
                 case MSG_UPDATE_SURFACE:
                     mEngine.updateSurface(true, false, false);
                     break;
+                case MSG_SCALE:
+                    mEngine.setZoom(Float.intBitsToFloat(message.arg1));
+                    break;
                 case MSG_VISIBILITY_CHANGED:
                     if (DEBUG) Log.v(TAG, "Visibility change in " + mEngine
                             + ": " + message.arg1);
diff --git a/core/java/android/service/watchdog/ExplicitHealthCheckService.java b/core/java/android/service/watchdog/ExplicitHealthCheckService.java
index 619c507..9950143 100644
--- a/core/java/android/service/watchdog/ExplicitHealthCheckService.java
+++ b/core/java/android/service/watchdog/ExplicitHealthCheckService.java
@@ -24,6 +24,7 @@
 import android.annotation.SystemApi;
 import android.app.Service;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -48,7 +49,8 @@
  * <p>To extend this class, you must declare the service in your manifest file with the
  * {@link android.Manifest.permission#BIND_EXPLICIT_HEALTH_CHECK_SERVICE} permission,
  * and include an intent filter with the {@link #SERVICE_INTERFACE} action. In adddition,
- * your implementation must live in {@link PackageManger#SYSTEM_SHARED_LIBRARY_SERVICES}.
+ * your implementation must live in
+ * {@link PackageManager#getServicesSystemSharedLibraryPackageName()}.
  * For example:</p>
  * <pre>
  *     &lt;service android:name=".FooExplicitHealthCheckService"
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index e08a06a..67cac0e 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -168,6 +168,16 @@
     public static final int LISTEN_SIGNAL_STRENGTHS                         = 0x00000100;
 
     /**
+     * Listen for always reported changes of the network signal strengths (cellular),
+     * even in some situations such as the screen of the device is off.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH)
+    @SystemApi
+    public static final int LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH          = 0x00000200;
+
+    /**
      * Listen for changes to observed cell info.
      *
      * @see #onCellInfoChanged
@@ -178,6 +188,10 @@
      * Listen 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}).
+     *
      * @hide
      */
     @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
@@ -187,13 +201,13 @@
     /**
      * Listen for {@link PreciseDataConnectionState} on the data connection (cellular).
      *
-     * <p>Requires permission {@link android.Manifest.permission#MODIFY_PHONE_STATE}
+     * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}
      * or the calling app has carrier privileges
      * (see {@link TelephonyManager#hasCarrierPrivileges}).
      *
      * @see #onPreciseDataConnectionStateChanged
      */
-    @RequiresPermission((android.Manifest.permission.MODIFY_PHONE_STATE))
+    @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
     public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE            = 0x00001000;
 
     /**
@@ -318,26 +332,36 @@
      * Listen 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}).
+     *
      */
     @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
     public static final int LISTEN_CALL_DISCONNECT_CAUSES                  = 0x02000000;
 
     /**
      * Listen for changes to the call attributes of a currently active call.
-     * {@more}
-     * Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE
-     * READ_PRECISE_PHONE_STATE}
+     *
+     * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}
+     * or the calling app has carrier privileges
+     * (see {@link TelephonyManager#hasCarrierPrivileges}).
      *
      * @see #onCallAttributesChanged
      * @hide
      */
     @SystemApi
+    @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
     public static final int LISTEN_CALL_ATTRIBUTES_CHANGED                 = 0x04000000;
 
     /**
      * Listen 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 #onImsCallDisconnectCauseChanged(ImsReasonInfo)
      */
     @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
@@ -384,6 +408,17 @@
     @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
     public static final int LISTEN_REGISTRATION_FAILURE = 0x40000000;
 
+    /**
+     * Listen for Barring Information for the current registered / camped cell.
+     *
+     * <p>Requires permission {@link android.Manifest.permission#READ_PHONE_STATE} or the calling
+     * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
+     *
+     * @see #onBarringInfoChanged()
+     */
+    @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
+    public static final int LISTEN_BARRING_INFO = 0x80000000;
+
     /*
      * Subscription used to listen to the phone state changes
      * @hide
@@ -974,7 +1009,21 @@
      *        TS 24.301 9.9.4.4. Integer.MAX_VALUE if this value is unused.
      */
     public void onRegistrationFailed(@NonNull CellIdentity cellIdentity, @NonNull String chosenPlmn,
-            @NetworkRegistrationInfo.Domain int domain, int causeCode, int additionalCauseCode) {
+            int domain, int causeCode, int additionalCauseCode) {
+        // default implementation empty
+    }
+
+    /**
+     * 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
+     */
+    public void onBarringInfoChanged(@NonNull BarringInfo barringInfo) {
         // default implementation empty
     }
 
@@ -1252,7 +1301,7 @@
         }
 
         public void onRegistrationFailed(@NonNull CellIdentity cellIdentity,
-                @NonNull String chosenPlmn, @NetworkRegistrationInfo.Domain int domain,
+                @NonNull String chosenPlmn, int domain,
                 int causeCode, int additionalCauseCode) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
@@ -1262,6 +1311,14 @@
                             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)));
+        }
     }
 
 
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 9387a2c..5737591 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -28,8 +28,10 @@
 import android.telephony.Annotation.CallState;
 import android.telephony.Annotation.DataActivityType;
 import android.telephony.Annotation.DataFailureCause;
+import android.telephony.Annotation.DisconnectCauses;
 import android.telephony.Annotation.NetworkType;
 import android.telephony.Annotation.PreciseCallStates;
+import android.telephony.Annotation.PreciseDisconnectCauses;
 import android.telephony.Annotation.RadioPowerState;
 import android.telephony.Annotation.SimActivationState;
 import android.telephony.Annotation.SrvccState;
@@ -56,6 +58,7 @@
  * @hide
  */
 @SystemApi
+@TestApi
 public class TelephonyRegistryManager {
 
     private static final String TAG = "TelephonyRegistryManager";
@@ -225,11 +228,9 @@
      * invalid.
      * @param state latest call state. e.g, offhook, ringing
      * @param incomingNumber incoming phone number.
-     *
-     * @hide
      */
     public void notifyCallStateChanged(int subId, int slotIndex, @CallState int state,
-            String incomingNumber) {
+            @Nullable String incomingNumber) {
         try {
             sRegistry.notifyCallState(slotIndex, subId, state, incomingNumber);
         } catch (RemoteException ex) {
@@ -263,10 +264,8 @@
      * @param slotIndex for which the service state changed. Can be derived from subId except
      * subId is invalid.
      * @param state service state e.g, in service, out of service or roaming status.
-     *
-     * @hide
      */
-    public void notifyServiceStateChanged(int subId, int slotIndex, ServiceState state) {
+    public void notifyServiceStateChanged(int subId, int slotIndex, @NonNull ServiceState state) {
         try {
             sRegistry.notifyServiceStateForPhoneId(slotIndex, subId, state);
         } catch (RemoteException ex) {
@@ -281,11 +280,9 @@
      * @param slotIndex for which the signalstrength changed. Can be derived from subId except when
      * subId is invalid.
      * @param signalStrength e.g, signalstrength level {@see SignalStrength#getLevel()}
-     *
-     * @hide
      */
     public void notifySignalStrengthChanged(int subId, int slotIndex,
-        SignalStrength signalStrength) {
+            @NonNull SignalStrength signalStrength) {
         try {
             sRegistry.notifySignalStrengthForPhoneId(slotIndex, subId, signalStrength);
         } catch (RemoteException ex) {
@@ -302,8 +299,6 @@
      * except when subId is invalid.
      * @param msgWaitingInd {@code true} indicates there is message-waiting indicator, {@code false}
      * otherwise.
-     *
-     * @hide
      */
     public void notifyMessageWaitingChanged(int subId, int slotIndex, boolean msgWaitingInd) {
         try {
@@ -319,8 +314,6 @@
      * @param subId for which call forwarding status changed.
      * @param callForwardInd {@code true} indicates there is call forwarding, {@code false}
      * otherwise.
-     *
-     * @hide
      */
     public void notifyCallForwardingChanged(int subId, boolean callForwardInd) {
         try {
@@ -336,8 +329,6 @@
      * @param subId for which data activity state changed.
      * @param dataActivityType indicates the latest data activity type e.g, {@link
      * TelephonyManager#DATA_ACTIVITY_IN}
-     *
-     * @hide
      */
     public void notifyDataActivityChanged(int subId, @DataActivityType int dataActivityType) {
         try {
@@ -358,10 +349,9 @@
      *
      * @see android.telephony.PreciseDataConnection
      * @see TelephonyManager#DATA_DISCONNECTED
-     * @hide
      */
     public void notifyDataConnectionForSubscriber(int slotIndex, int subId,
-            @ApnType int apnType, PreciseDataConnectionState preciseState) {
+            @ApnType int apnType, @Nullable PreciseDataConnectionState preciseState) {
         try {
             sRegistry.notifyDataConnectionForSubscriber(
                     slotIndex, subId, apnType, preciseState);
@@ -378,10 +368,8 @@
      * subId is invalid.
      * @param callQuality Information about call quality e.g, call quality level
      * @param networkType associated with this data connection. e.g, LTE
-     *
-     * @hide
      */
-    public void notifyCallQualityChanged(int subId, int slotIndex, CallQuality callQuality,
+    public void notifyCallQualityChanged(int subId, int slotIndex, @NonNull CallQuality callQuality,
         @NetworkType int networkType) {
         try {
             sRegistry.notifyCallQualityChanged(callQuality, slotIndex, subId, networkType);
@@ -396,8 +384,6 @@
      * @param subId for which emergency number list changed.
      * @param slotIndex for which emergency number list changed. Can be derived from subId except
      * when subId is invalid.
-     *
-     * @hide
      */
     public void notifyEmergencyNumberList(int subId, int slotIndex) {
         try {
@@ -414,8 +400,6 @@
      * @param slotIndex for which radio power state changed. Can be derived from subId except when
      * subId is invalid.
      * @param radioPowerState the current modem radio state.
-     *
-     * @hide
      */
     public void notifyRadioPowerStateChanged(int subId, int slotIndex,
         @RadioPowerState int radioPowerState) {
@@ -430,10 +414,8 @@
      * Notify {@link PhoneCapability} changed.
      *
      * @param phoneCapability the capability of the modem group.
-     *
-     * @hide
      */
-    public void notifyPhoneCapabilityChanged(PhoneCapability phoneCapability) {
+    public void notifyPhoneCapabilityChanged(@NonNull PhoneCapability phoneCapability) {
         try {
             sRegistry.notifyPhoneCapabilityChanged(phoneCapability);
         } catch (RemoteException ex) {
@@ -462,8 +444,6 @@
      * @param slotIndex for which data activation state changed. Can be derived from subId except
      * when subId is invalid.
      * @param activationState sim activation state e.g, activated.
-     *
-     * @hide
      */
     public void notifyDataActivationStateChanged(int subId, int slotIndex,
         @SimActivationState int activationState) {
@@ -483,8 +463,6 @@
      * @param slotIndex for which voice activation state changed. Can be derived from subId except
      * subId is invalid.
      * @param activationState sim activation state e.g, activated.
-     *
-     * @hide
      */
     public void notifyVoiceActivationStateChanged(int subId, int slotIndex,
         @SimActivationState int activationState) {
@@ -504,8 +482,6 @@
      * @param slotIndex for which mobile data state has changed. Can be derived from subId except
      * when subId is invalid.
      * @param state {@code true} indicates mobile data is enabled/on. {@code false} otherwise.
-     *
-     * @hide
      */
     public void notifyUserMobileDataStateChanged(int slotIndex, int subId, boolean state) {
         try {
@@ -516,31 +492,12 @@
     }
 
     /**
-     * TODO: this is marked as deprecated, can we move this one safely?
-     *
-     * @param subId
-     * @param slotIndex
-     * @param rawData
-     *
-     * @hide
-     */
-    public void notifyOemHookRawEventForSubscriber(int subId, int slotIndex, byte[] rawData) {
-        try {
-            sRegistry.notifyOemHookRawEventForSubscriber(slotIndex, subId, rawData);
-        } catch (RemoteException ex) {
-            // system process is dead
-        }
-    }
-
-    /**
      * Notify IMS call disconnect causes which contains {@link android.telephony.ims.ImsReasonInfo}.
      *
      * @param subId for which ims call disconnect.
      * @param imsReasonInfo the reason for ims call disconnect.
-     *
-     * @hide
      */
-    public void notifyImsDisconnectCause(int subId, ImsReasonInfo imsReasonInfo) {
+    public void notifyImsDisconnectCause(int subId, @NonNull ImsReasonInfo imsReasonInfo) {
         try {
             sRegistry.notifyImsDisconnectCause(subId, imsReasonInfo);
         } catch (RemoteException ex) {
@@ -557,11 +514,9 @@
      * @param apnType the apn type bitmask, defined with {@code ApnSetting#TYPE_*} flags.
      * @param apn the APN {@link ApnSetting#getApnName()} of this data connection.
      * @param failCause data fail cause.
-     *
-     * @hide
      */
     public void notifyPreciseDataConnectionFailed(int subId, int slotIndex, @ApnType int apnType,
-        String apn, @DataFailureCause int failCause) {
+            @Nullable String apn, @DataFailureCause int failCause) {
         try {
             sRegistry.notifyPreciseDataConnectionFailed(slotIndex, subId, apnType, apn, failCause);
         } catch (RemoteException ex) {
@@ -575,8 +530,6 @@
      *
      * @param subId for which srvcc state changed.
      * @param state srvcc state
-     *
-     * @hide
      */
     public void notifySrvccStateChanged(int subId, @SrvccState int state) {
         try {
@@ -596,8 +549,6 @@
      * @param ringCallPreciseState ringCall state.
      * @param foregroundCallPreciseState foreground call state.
      * @param backgroundCallPreciseState background call state.
-     *
-     * @hide
      */
     public void notifyPreciseCallState(int subId, int slotIndex,
             @PreciseCallStates int ringCallPreciseState,
@@ -621,10 +572,9 @@
      * @param cause {@link DisconnectCause} for the disconnected call.
      * @param preciseCause {@link android.telephony.PreciseDisconnectCause} for the disconnected
      * call.
-     *
-     * @hide
      */
-    public void notifyDisconnectCause(int slotIndex, int subId, int cause, int preciseCause) {
+    public void notifyDisconnectCause(int slotIndex, int subId, @DisconnectCauses int cause,
+            @PreciseDisconnectCauses int preciseCause) {
         try {
             sRegistry.notifyDisconnectCause(slotIndex, subId, cause, preciseCause);
         } catch (RemoteException ex) {
@@ -637,10 +587,8 @@
      *
      * <p>To be compatible with {@link TelephonyRegistry}, use {@link CellIdentity} which is
      * parcelable, and convert to CellLocation in client code.
-     *
-     * @hide
      */
-    public void notifyCellLocation(int subId, CellIdentity cellLocation) {
+    public void notifyCellLocation(int subId, @NonNull CellIdentity cellLocation) {
         try {
             sRegistry.notifyCellLocationForSubscriber(subId, cellLocation);
         } catch (RemoteException ex) {
@@ -654,10 +602,8 @@
      *
      * @param subId for which cellinfo changed.
      * @param cellInfo A list of cellInfo associated with the given subscription.
-     *
-     * @hide
      */
-    public void notifyCellInfoChanged(int subId, List<CellInfo> cellInfo) {
+    public void notifyCellInfoChanged(int subId, @NonNull List<CellInfo> cellInfo) {
         try {
             sRegistry.notifyCellInfoForSubscriber(subId, cellInfo);
         } catch (RemoteException ex) {
@@ -666,8 +612,8 @@
     }
 
     /**
-     * @param activeDataSubId
-     * @hide
+     * Notify that the active data subscription ID has changed.
+     * @param activeDataSubId The new subscription ID for active data
      */
     public void notifyActiveDataSubIdChanged(int activeDataSubId) {
         try {
@@ -701,11 +647,28 @@
      */
     public void notifyRegistrationFailed(int slotIndex, int subId,
             @NonNull CellIdentity cellIdentity, @NonNull String chosenPlmn,
-            @NetworkRegistrationInfo.Domain int domain, int causeCode, int additionalCauseCode) {
+            int domain, int causeCode, int additionalCauseCode) {
         try {
             sRegistry.notifyRegistrationFailed(slotIndex, subId, cellIdentity,
                     chosenPlmn, domain, causeCode, additionalCauseCode);
         } catch (RemoteException ex) {
         }
     }
+
+    /**
+     * Notify {@link BarringInfo} has changed for a specific subscription.
+     *
+     * @param slotIndex for the phone object that got updated barring info.
+     * @param subId for which the BarringInfo changed.
+     * @param barringInfo updated BarringInfo.
+     */
+    public void notifyBarringInfoChanged(
+            int slotIndex, int subId, @NonNull BarringInfo barringInfo) {
+        try {
+            sRegistry.notifyBarringInfoChanged(slotIndex, subId, barringInfo);
+        } catch (RemoteException ex) {
+            // system server crash
+        }
+    }
+
 }
diff --git a/core/java/android/telephony/WapPushManagerConnector.java b/core/java/android/telephony/WapPushManagerConnector.java
new file mode 100644
index 0000000..a9df506
--- /dev/null
+++ b/core/java/android/telephony/WapPushManagerConnector.java
@@ -0,0 +1,178 @@
+/*
+ * 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.telephony;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.internal.telephony.IWapPushManager;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * APIs for platform to connect to the WAP push manager service.
+ *
+ * <p>To start connection, {@link #bindToWapPushManagerService} should be called.
+ *
+ * <p>Upon completion {@link #unbindWapPushManagerService} should be called to unbind the service.
+ *
+ * @hide
+ */
+@SystemApi
+public final class WapPushManagerConnector {
+    private final Context mContext;
+
+    private volatile WapPushManagerConnection mConnection;
+    private volatile IWapPushManager mWapPushManager;
+    private String mWapPushManagerPackage;
+
+    /**
+     * The {@link android.content.Intent} that must be declared as handled by the
+     * WAP push manager service.
+     * @hide
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+    public static final String SERVICE_INTERFACE =
+            "com.android.internal.telephony.IWapPushManager";
+
+    /** @hide */
+    @IntDef(flag = true, prefix = {"RESULT_"}, value = {
+            RESULT_MESSAGE_HANDLED,
+            RESULT_APP_QUERY_FAILED,
+            RESULT_SIGNATURE_NO_MATCH,
+            RESULT_INVALID_RECEIVER_NAME,
+            RESULT_EXCEPTION_CAUGHT,
+            RESULT_FURTHER_PROCESSING,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ProcessMessageResult{}
+
+    /** {@link #processMessage} return value: Message is handled. */
+    public static final int RESULT_MESSAGE_HANDLED = 0x1;
+    /** {@link #processMessage} return value: Application ID or content type was not found. */
+    public static final int RESULT_APP_QUERY_FAILED = 0x2;
+    /** {@link #processMessage} return value: Receiver application signature check failed. */
+    public static final int RESULT_SIGNATURE_NO_MATCH = 0x4;
+    /** {@link #processMessage} return value: Receiver application was not found. */
+    public static final int RESULT_INVALID_RECEIVER_NAME = 0x8;
+    /** {@link #processMessage} return value: Unknown exception. */
+    public static final int RESULT_EXCEPTION_CAUGHT = 0x10;
+    /** {@link #processMessage} return value: further processing needed. */
+    public static final int RESULT_FURTHER_PROCESSING = 0x8000;
+
+    /** The application package name of the WAP push manager service. */
+    private static final String SERVICE_PACKAGE = "com.android.smspush";
+
+    public WapPushManagerConnector(@NonNull Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Binds to the WAP push manager service. This method should be called exactly once.
+     *
+     * @return {@code true} upon successfully binding to a service, {@code false} otherwise
+     */
+    public boolean bindToWapPushManagerService() {
+        Preconditions.checkState(mConnection == null);
+
+        Intent intent = new Intent(SERVICE_INTERFACE);
+        ComponentName component = intent.resolveSystemService(mContext.getPackageManager(), 0);
+        intent.setComponent(component);
+        mConnection = new WapPushManagerConnection();
+        if (component != null
+                && mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE)) {
+            mWapPushManagerPackage = component.getPackageName();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns the package name of WAP push manager service application connected to,
+     * or {@code null} if not connected.
+     */
+    @Nullable
+    public String getConnectedWapPushManagerServicePackage() {
+        return mWapPushManagerPackage;
+    }
+
+    /**
+     * Processes WAP push message and triggers the {@code intent}.
+     *
+     * @see RESULT_MESSAGE_HANDLED
+     * @see RESULT_APP_QUERY_FAILED
+     * @see RESULT_SIGNATURE_NO_MATCH
+     * @see RESULT_INVALID_RECEIVER_NAME
+     * @see RESULT_EXCEPTION_CAUGHT
+     * @see RESULT_FURTHER_PROCESSING
+     */
+    @ProcessMessageResult
+    public int processMessage(
+            @NonNull String applicationId, @NonNull String contentType, @NonNull Intent intent) {
+        try {
+            return mWapPushManager.processMessage(applicationId, contentType, intent);
+        } catch (NullPointerException | RemoteException e) {
+            return RESULT_EXCEPTION_CAUGHT;
+        }
+    }
+
+    /**
+     * Unbinds the WAP push manager service. This method should be called exactly once.
+     */
+    public void unbindWapPushManagerService() {
+        Preconditions.checkNotNull(mConnection);
+
+        mContext.unbindService(mConnection);
+        mConnection = null;
+    }
+
+    private class WapPushManagerConnection implements ServiceConnection {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            // Because we have bound to an explicit
+            // service that is running in our own process, we can
+            // cast its IBinder to a concrete class and directly access it.
+            mWapPushManager = IWapPushManager.Stub.asInterface(service);
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            mWapPushManager = null;
+        }
+
+        @Override
+        public void onNullBinding(ComponentName name) {
+            onServiceDisconnected(name);
+        }
+
+        @Override
+        public void onBindingDied(ComponentName name) {
+            onServiceDisconnected(name);
+        }
+    }
+}
diff --git a/core/java/android/text/SpannableStringInternal.java b/core/java/android/text/SpannableStringInternal.java
index b85ef76..4c9328a 100644
--- a/core/java/android/text/SpannableStringInternal.java
+++ b/core/java/android/text/SpannableStringInternal.java
@@ -40,9 +40,10 @@
 
         if (source instanceof Spanned) {
             if (source instanceof SpannableStringInternal) {
-                copySpans((SpannableStringInternal) source, start, end, ignoreNoCopySpan);
+                copySpansFromInternal(
+                        (SpannableStringInternal) source, start, end, ignoreNoCopySpan);
             } else {
-                copySpans((Spanned) source, start, end, ignoreNoCopySpan);
+                copySpansFromSpanned((Spanned) source, start, end, ignoreNoCopySpan);
             }
         }
     }
@@ -65,7 +66,7 @@
      * @param end End index in the source object.
      * @param ignoreNoCopySpan whether to copy NoCopySpans in the {@code source}
      */
-    private void copySpans(Spanned src, int start, int end, boolean ignoreNoCopySpan) {
+    private void copySpansFromSpanned(Spanned src, int start, int end, boolean ignoreNoCopySpan) {
         Object[] spans = src.getSpans(start, end, Object.class);
 
         for (int i = 0; i < spans.length; i++) {
@@ -94,7 +95,7 @@
      * @param end End index in the source object.
      * @param ignoreNoCopySpan copy NoCopySpan for backward compatible reasons.
      */
-    private void copySpans(SpannableStringInternal src, int start, int end,
+    private void copySpansFromInternal(SpannableStringInternal src, int start, int end,
             boolean ignoreNoCopySpan) {
         int count = 0;
         final int[] srcData = src.mSpanData;
@@ -555,12 +556,12 @@
      */
     @UnsupportedAppUsage
     private void copySpans(Spanned src, int start, int end) {
-        copySpans(src, start, end, false);
+        copySpansFromSpanned(src, start, end, false);
     }
 
     @UnsupportedAppUsage
     private void copySpans(SpannableStringInternal src, int start, int end) {
-        copySpans(src, start, end, false);
+        copySpansFromInternal(src, start, end, false);
     }
 
 
diff --git a/core/java/android/timezone/CountryTimeZones.java b/core/java/android/timezone/CountryTimeZones.java
new file mode 100644
index 0000000..e5bbdf4
--- /dev/null
+++ b/core/java/android/timezone/CountryTimeZones.java
@@ -0,0 +1,266 @@
+/*
+ * 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.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.icu.util.TimeZone;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Information about a country's time zones.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class CountryTimeZones {
+
+    /**
+     * A mapping to a time zone ID with some associated metadata.
+     *
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final class TimeZoneMapping {
+
+        @NonNull
+        private libcore.timezone.CountryTimeZones.TimeZoneMapping mDelegate;
+
+        TimeZoneMapping(libcore.timezone.CountryTimeZones.TimeZoneMapping delegate) {
+            this.mDelegate = Objects.requireNonNull(delegate);
+        }
+
+        /**
+         * Returns the ID for this mapping. See also {@link #getTimeZone()} which handles when the
+         * ID is unrecognized.
+         */
+        @NonNull
+        public String getTimeZoneId() {
+            return mDelegate.timeZoneId;
+        }
+
+        /**
+         * Returns a {@link TimeZone} object for this mapping, or {@code null} if the ID is
+         * unrecognized.
+         */
+        @Nullable
+        public TimeZone getTimeZone() {
+            return mDelegate.getTimeZone();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            TimeZoneMapping that = (TimeZoneMapping) o;
+            return this.mDelegate.equals(that.mDelegate);
+        }
+
+        @Override
+        public int hashCode() {
+            return this.mDelegate.hashCode();
+        }
+
+        @Override
+        public String toString() {
+            return mDelegate.toString();
+        }
+    }
+
+    /**
+     * The result of lookup up a time zone using offset information (and possibly more).
+     *
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final class OffsetResult {
+
+        private final TimeZone mTimeZone;
+        private final boolean mIsOnlyMatch;
+
+        /** Creates an instance with the supplied information. */
+        public OffsetResult(@NonNull TimeZone timeZone, boolean isOnlyMatch) {
+            mTimeZone = Objects.requireNonNull(timeZone);
+            mIsOnlyMatch = isOnlyMatch;
+        }
+
+        /**
+         * Returns a time zone that matches the supplied criteria.
+         */
+        @NonNull
+        public TimeZone getTimeZone() {
+            return mTimeZone;
+        }
+
+        /**
+         * Returns {@code true} if there is only one matching time zone for the supplied criteria.
+         */
+        public boolean isOnlyMatch() {
+            return mIsOnlyMatch;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            OffsetResult that = (OffsetResult) o;
+            return mIsOnlyMatch == that.mIsOnlyMatch
+                    && mTimeZone.getID().equals(that.mTimeZone.getID());
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mTimeZone, mIsOnlyMatch);
+        }
+
+        @Override
+        public String toString() {
+            return "OffsetResult{"
+                    + "mTimeZone=" + mTimeZone
+                    + ", mIsOnlyMatch=" + mIsOnlyMatch
+                    + '}';
+        }
+    }
+
+    @NonNull
+    private final libcore.timezone.CountryTimeZones mDelegate;
+
+    CountryTimeZones(libcore.timezone.CountryTimeZones delegate) {
+        mDelegate = delegate;
+    }
+
+    /**
+     * Returns true if the ISO code for the country is a match for the one specified.
+     */
+    public boolean isForCountryCode(@NonNull String countryIso) {
+        return mDelegate.isForCountryCode(countryIso);
+    }
+
+    /**
+     * Returns the default time zone ID for the country. Can return {@code null} in cases when no
+     * data is available or the time zone ID was not recognized.
+     */
+    @Nullable
+    public String getDefaultTimeZoneId() {
+        return mDelegate.getDefaultTimeZoneId();
+    }
+
+    /**
+     * Returns the default time zone for the country. Can return {@code null} in cases when no data
+     * is available or the time zone ID was not recognized.
+     */
+    @Nullable
+    public TimeZone getDefaultTimeZone() {
+        return mDelegate.getDefaultTimeZone();
+    }
+
+    /**
+     * Qualifier for a country's default time zone. {@code true} indicates whether the default
+     * would be a good choice <em>generally</em> when there's no other information available.
+     */
+    public boolean isDefaultTimeZoneBoosted() {
+        return mDelegate.getDefaultTimeZoneBoost();
+    }
+
+    /**
+     * Returns true if the country has at least one zone that is the same as UTC at the given time.
+     */
+    public boolean hasUtcZone(long whenMillis) {
+        return mDelegate.hasUtcZone(whenMillis);
+    }
+
+    /**
+     * Returns a time zone for the country, if there is one, that matches the desired properties. If
+     * there are multiple matches and the {@code bias} is one of them then it is returned, otherwise
+     * an arbitrary match is returned based on the {@link #getEffectiveTimeZoneMappingsAt(long)}
+     * ordering.
+     *
+     * @param totalOffsetMillis the offset from UTC at {@code whenMillis}
+     * @param isDst the Daylight Savings Time state at {@code whenMillis}. {@code true} means DST,
+     *     {@code false} means not DST, {@code null} means unknown
+     * @param dstOffsetMillis the part of {@code totalOffsetMillis} contributed by DST, only used if
+     *     {@code isDst} is {@code true}. The value can be {@code null} if the DST offset is
+     *     unknown
+     * @param whenMillis the UTC time to match against
+     * @param bias the time zone to prefer, can be {@code null}
+     */
+    @Nullable
+    public OffsetResult lookupByOffsetWithBias(int totalOffsetMillis, @Nullable Boolean isDst,
+            @SuppressLint("AutoBoxing") @Nullable Integer dstOffsetMillis, long whenMillis,
+            @Nullable TimeZone bias) {
+        libcore.timezone.CountryTimeZones.OffsetResult delegateOffsetResult =
+                mDelegate.lookupByOffsetWithBias(
+                        totalOffsetMillis, isDst, dstOffsetMillis, whenMillis, bias);
+        return delegateOffsetResult == null ? null :
+                new OffsetResult(delegateOffsetResult.mTimeZone, delegateOffsetResult.mOneMatch);
+    }
+
+    /**
+     * Returns an immutable, ordered list of time zone mappings for the country in an undefined but
+     * "priority" order, filtered so that only "effective" time zone IDs are returned. An
+     * "effective" time zone is one that differs from another time zone used in the country after
+     * {@code whenMillis}. The list can be empty if there were no zones configured or the configured
+     * zone IDs were not recognized.
+     */
+    @NonNull
+    public List<TimeZoneMapping> getEffectiveTimeZoneMappingsAt(long whenMillis) {
+        List<libcore.timezone.CountryTimeZones.TimeZoneMapping> delegateList =
+                mDelegate.getEffectiveTimeZoneMappingsAt(whenMillis);
+
+        List<TimeZoneMapping> toReturn = new ArrayList<>(delegateList.size());
+        for (libcore.timezone.CountryTimeZones.TimeZoneMapping delegateMapping : delegateList) {
+            toReturn.add(new TimeZoneMapping(delegateMapping));
+        }
+        return Collections.unmodifiableList(toReturn);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        CountryTimeZones that = (CountryTimeZones) o;
+        return mDelegate.equals(that.mDelegate);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mDelegate);
+    }
+
+    @Override
+    public String toString() {
+        return mDelegate.toString();
+    }
+}
diff --git a/core/java/android/timezone/TelephonyLookup.java b/core/java/android/timezone/TelephonyLookup.java
new file mode 100644
index 0000000..eebccf4
--- /dev/null
+++ b/core/java/android/timezone/TelephonyLookup.java
@@ -0,0 +1,70 @@
+/*
+ * 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.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Objects;
+
+/**
+ * A class that can find time zone-related information about telephony networks.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class TelephonyLookup {
+
+    private static Object sLock = new Object();
+    @GuardedBy("sLock")
+    private static TelephonyLookup sInstance;
+
+    /**
+     * Obtains an instance for use when resolving telephony time zone information.
+     */
+    @NonNull
+    public static TelephonyLookup getInstance() {
+        synchronized (sLock) {
+            if (sInstance == null) {
+                sInstance = new TelephonyLookup(libcore.timezone.TelephonyLookup.getInstance());
+            }
+            return sInstance;
+        }
+    }
+
+    @NonNull
+    private final libcore.timezone.TelephonyLookup mDelegate;
+
+    private TelephonyLookup(@NonNull libcore.timezone.TelephonyLookup delegate) {
+        mDelegate = Objects.requireNonNull(delegate);
+    }
+
+    /**
+     * Returns an object capable of querying telephony network information. This method can return
+     * {@code null} in the event of an error while reading the underlying data files.
+     */
+    @Nullable
+    public TelephonyNetworkFinder getTelephonyNetworkFinder() {
+        libcore.timezone.TelephonyNetworkFinder telephonyNetworkFinderDelegate =
+                mDelegate.getTelephonyNetworkFinder();
+        return telephonyNetworkFinderDelegate != null
+                ? new TelephonyNetworkFinder(telephonyNetworkFinderDelegate) : null;
+    }
+}
diff --git a/core/java/android/timezone/TelephonyNetwork.java b/core/java/android/timezone/TelephonyNetwork.java
new file mode 100644
index 0000000..ae39fbd
--- /dev/null
+++ b/core/java/android/timezone/TelephonyNetwork.java
@@ -0,0 +1,86 @@
+/*
+ * 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.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import java.util.Objects;
+
+/**
+ * Information about a telephony network.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class TelephonyNetwork {
+
+    @NonNull
+    private final libcore.timezone.TelephonyNetwork mDelegate;
+
+    TelephonyNetwork(@NonNull libcore.timezone.TelephonyNetwork delegate) {
+        mDelegate = Objects.requireNonNull(delegate);
+    }
+
+    /**
+     * Returns the Mobile Country Code of the network.
+     */
+    @NonNull
+    public String getMcc() {
+        return mDelegate.getMcc();
+    }
+
+    /**
+     * Returns the Mobile Network Code of the network.
+     */
+    @NonNull
+    public String getMnc() {
+        return mDelegate.getMnc();
+    }
+
+    /**
+     * Returns the country in which the network operates as an ISO 3166 alpha-2 (lower case).
+     */
+    @NonNull
+    public String getCountryIsoCode() {
+        return mDelegate.getCountryIsoCode();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        TelephonyNetwork that = (TelephonyNetwork) o;
+        return mDelegate.equals(that.mDelegate);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mDelegate);
+    }
+
+    @Override
+    public String toString() {
+        return "TelephonyNetwork{"
+                + "mDelegate=" + mDelegate
+                + '}';
+    }
+}
diff --git a/core/java/android/timezone/TelephonyNetworkFinder.java b/core/java/android/timezone/TelephonyNetworkFinder.java
new file mode 100644
index 0000000..079d088
--- /dev/null
+++ b/core/java/android/timezone/TelephonyNetworkFinder.java
@@ -0,0 +1,55 @@
+/*
+ * 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.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import java.util.Objects;
+
+/**
+ * A class that can find telephony network information loaded via {@link TelephonyLookup}.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class TelephonyNetworkFinder {
+
+    @NonNull
+    private final libcore.timezone.TelephonyNetworkFinder mDelegate;
+
+    TelephonyNetworkFinder(libcore.timezone.TelephonyNetworkFinder delegate) {
+        mDelegate = Objects.requireNonNull(delegate);
+    }
+
+    /**
+     * Returns information held about a specific MCC + MNC combination. It is expected for this
+     * method to return {@code null}. Only known, unusual networks will typically have information
+     * returned, e.g. if they operate in countries other than the one suggested by their MCC.
+     */
+    @Nullable
+    public TelephonyNetwork findNetworkByMccMnc(@NonNull String mcc, @NonNull String mnc) {
+        Objects.requireNonNull(mcc);
+        Objects.requireNonNull(mnc);
+
+        libcore.timezone.TelephonyNetwork telephonyNetworkDelegate =
+                mDelegate.findNetworkByMccMnc(mcc, mnc);
+        return telephonyNetworkDelegate != null
+                ? new TelephonyNetwork(telephonyNetworkDelegate) : null;
+    }
+}
diff --git a/core/java/android/timezone/TimeZoneFinder.java b/core/java/android/timezone/TimeZoneFinder.java
new file mode 100644
index 0000000..9327b00
--- /dev/null
+++ b/core/java/android/timezone/TimeZoneFinder.java
@@ -0,0 +1,78 @@
+/*
+ * 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.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Objects;
+
+/**
+ * A class that can be used to find time zones using information like country and offset.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class TimeZoneFinder {
+
+    private static Object sLock = new Object();
+    @GuardedBy("sLock")
+    private static TimeZoneFinder sInstance;
+
+    /**
+     * Obtains the singleton instance.
+     */
+    @NonNull
+    public static TimeZoneFinder getInstance() {
+        synchronized (sLock) {
+            if (sInstance == null) {
+                sInstance = new TimeZoneFinder(libcore.timezone.TimeZoneFinder.getInstance());
+            }
+        }
+        return sInstance;
+    }
+
+    @NonNull
+    private final libcore.timezone.TimeZoneFinder mDelegate;
+
+    private TimeZoneFinder(@NonNull libcore.timezone.TimeZoneFinder delegate) {
+        mDelegate = Objects.requireNonNull(delegate);
+    }
+
+    /**
+     * Returns the IANA rules version associated with the data. If there is no version information
+     * or there is a problem reading the file then {@code null} is returned.
+     */
+    @Nullable
+    public String getIanaVersion() {
+        return mDelegate.getIanaVersion();
+    }
+
+    /**
+     * Returns a {@link CountryTimeZones} object associated with the specified country code.
+     * Caching is handled as needed. If the country code is not recognized or there is an error
+     * during lookup this method can return null.
+     */
+    @Nullable
+    public CountryTimeZones lookupCountryTimeZones(@NonNull String countryIso) {
+        libcore.timezone.CountryTimeZones delegate = mDelegate.lookupCountryTimeZones(countryIso);
+        return delegate == null ? null : new CountryTimeZones(delegate);
+    }
+}
diff --git a/core/java/android/timezone/TzDataSetVersion.java b/core/java/android/timezone/TzDataSetVersion.java
new file mode 100644
index 0000000..aba7c4c
--- /dev/null
+++ b/core/java/android/timezone/TzDataSetVersion.java
@@ -0,0 +1,154 @@
+/*
+ * 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.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * Version information associated with the set of time zone data on a device.
+ *
+ * <p>Time Zone Data Sets have a major ({@link #getFormatMajorVersion()}) and minor
+ * ({@link #currentFormatMinorVersion()}) version number:
+ * <ul>
+ *   <li>Major version numbers are mutually incompatible. e.g. v2 is not compatible with a v1 or a
+ *   v3 device.</li>
+ *   <li>Minor version numbers are backwards compatible. e.g. a v2.2 data set will work
+ *   on a v2.1 device but not a v2.3 device. The minor version is reset to 1 when the major version
+ *   is incremented.</li>
+ * </ul>
+ *
+ * <p>Data sets contain time zone rules and other data associated wtih a tzdb release
+ * ({@link #getRulesVersion()}) and an additional Android-specific revision number
+ * ({@link #getRevision()}).
+ *
+ * <p>See platform/system/timezone/README.android for more information.
+ * @hide
+ */
+@VisibleForTesting
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class TzDataSetVersion {
+
+    /**
+     * Returns the major tz data format version supported by this device.
+     */
+    public static int currentFormatMajorVersion() {
+        return libcore.timezone.TzDataSetVersion.currentFormatMajorVersion();
+    }
+
+    /**
+     * Returns the minor tz data format version supported by this device.
+     */
+    public static int currentFormatMinorVersion() {
+        return libcore.timezone.TzDataSetVersion.currentFormatMinorVersion();
+    }
+
+    /**
+     * Returns true if the version information provided would be compatible with this device, i.e.
+     * with the current system image, and set of active modules.
+     */
+    public static boolean isCompatibleWithThisDevice(TzDataSetVersion tzDataSetVersion) {
+        return libcore.timezone.TzDataSetVersion.isCompatibleWithThisDevice(
+                tzDataSetVersion.mDelegate);
+    }
+
+    /**
+     * Reads the current Android time zone data set version file.
+     */
+    @NonNull
+    public static TzDataSetVersion read() throws IOException, TzDataSetException {
+        try {
+            return new TzDataSetVersion(
+                    libcore.timezone.TzDataSetVersion.readTimeZoneModuleVersion());
+        } catch (libcore.timezone.TzDataSetVersion.TzDataSetException e) {
+            throw new TzDataSetException(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * A checked exception used in connection with time zone data sets.
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static class TzDataSetException extends Exception {
+
+        /** Creates an instance with a message. */
+        public TzDataSetException(String message) {
+            super(message);
+        }
+
+        /** Creates an instance with a message and a cause. */
+        public TzDataSetException(String message, Throwable cause) {
+            super(message, cause);
+        }
+    }
+
+    @NonNull
+    private final libcore.timezone.TzDataSetVersion mDelegate;
+
+    private TzDataSetVersion(@NonNull libcore.timezone.TzDataSetVersion delegate) {
+        mDelegate = Objects.requireNonNull(delegate);
+    }
+
+    /** Returns the major version number. See {@link TzDataSetVersion}. */
+    public int getFormatMajorVersion() {
+        return mDelegate.formatMajorVersion;
+    }
+
+    /** Returns the minor version number. See {@link TzDataSetVersion}. */
+    public int getFormatMinorVersion() {
+        return mDelegate.formatMinorVersion;
+    }
+
+    /** Returns the tzdb version string. See {@link TzDataSetVersion}. */
+    @NonNull
+    public String getRulesVersion() {
+        return mDelegate.rulesVersion;
+    }
+
+    /** Returns the Android revision. See {@link TzDataSetVersion}. */
+    public int getRevision() {
+        return mDelegate.revision;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        TzDataSetVersion that = (TzDataSetVersion) o;
+        return mDelegate.equals(that.mDelegate);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mDelegate);
+    }
+
+    @Override
+    public String toString() {
+        return mDelegate.toString();
+    }
+}
diff --git a/core/java/android/timezone/ZoneInfoDb.java b/core/java/android/timezone/ZoneInfoDb.java
new file mode 100644
index 0000000..eb191e8
--- /dev/null
+++ b/core/java/android/timezone/ZoneInfoDb.java
@@ -0,0 +1,66 @@
+/*
+ * 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.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Objects;
+
+/**
+ * Android's internal factory for java.util.TimeZone objects. Provides access to core library time
+ * zone metadata not available via {@link java.util.TimeZone}.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class ZoneInfoDb {
+
+    private static Object sLock = new Object();
+    @GuardedBy("sLock")
+    private static ZoneInfoDb sInstance;
+
+    /**
+     * Obtains the singleton instance.
+     */
+    @NonNull
+    public static ZoneInfoDb getInstance() {
+        synchronized (sLock) {
+            if (sInstance == null) {
+                sInstance = new ZoneInfoDb(libcore.timezone.ZoneInfoDB.getInstance());
+            }
+        }
+        return sInstance;
+    }
+
+    @NonNull
+    private final libcore.timezone.ZoneInfoDB mDelegate;
+
+    private ZoneInfoDb(libcore.timezone.ZoneInfoDB delegate) {
+        mDelegate = Objects.requireNonNull(delegate);
+    }
+
+    /**
+     * Returns the tzdb version in use.
+     */
+    @NonNull
+    public String getVersion() {
+        return mDelegate.getVersion();
+    }
+}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 1b2db36..06fccaf8 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -42,6 +42,11 @@
     public static final String DYNAMIC_SYSTEM = "settings_dynamic_system";
     public static final String SETTINGS_WIFITRACKER2 = "settings_wifitracker2";
     public static final String SETTINGS_FUSE_FLAG = "settings_fuse";
+    public static final String NOTIF_CONVO_BYPASS_SHORTCUT_REQ =
+            "settings_notif_convo_bypass_shortcut_req";
+    /** @hide */
+    public static final String BACKUP_NO_KV_DATA_CHANGE_CALLS =
+            "backup_enable_no_data_notification_calls";
 
     private static final Map<String, String> DEFAULT_FLAGS;
 
@@ -57,9 +62,12 @@
         DEFAULT_FLAGS.put("settings_wifi_details_datausage_header", "false");
         DEFAULT_FLAGS.put("settings_skip_direction_mutable", "true");
         DEFAULT_FLAGS.put(SETTINGS_WIFITRACKER2, "false");
-        DEFAULT_FLAGS.put("settings_work_profile", "true");
         DEFAULT_FLAGS.put("settings_controller_loading_enhancement", "false");
         DEFAULT_FLAGS.put("settings_conditionals", "false");
+        DEFAULT_FLAGS.put(NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "true");
+
+        // Disabled until backup transports support it.
+        DEFAULT_FLAGS.put(BACKUP_NO_KV_DATA_CHANGE_CALLS, "false");
     }
 
     /**
diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java
index f324113..9921bf0 100644
--- a/core/java/android/util/Log.java
+++ b/core/java/android/util/Log.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.DeadSystemException;
 
@@ -400,7 +401,7 @@
      * @param message The message you would like logged.
      * @hide
      */
-    // @SystemApi(client= SystemApi.Client.MODULE_LIBRARIES) // TODO Uncomment once http://ag/9956147 is in.
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static int logToRadioBuffer(@Level int priority, @Nullable String tag,
             @Nullable String message) {
         return println_native(LOG_ID_RADIO, priority, tag, message);
diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java
index fa994ba..0892c94 100644
--- a/core/java/android/util/NtpTrustedTime.java
+++ b/core/java/android/util/NtpTrustedTime.java
@@ -16,6 +16,8 @@
 
 package android.util;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -25,172 +27,270 @@
 import android.net.NetworkInfo;
 import android.net.SntpClient;
 import android.os.SystemClock;
-import android.os.TimestampedValue;
 import android.provider.Settings;
 import android.text.TextUtils;
 
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Objects;
+import java.util.function.Supplier;
+
 /**
- * {@link TrustedTime} that connects with a remote NTP server as its trusted
- * time source.
+ * A singleton that connects with a remote NTP server as its trusted time source. This class
+ * is thread-safe. The {@link #forceRefresh()} method is synchronous, i.e. it may occupy the
+ * current thread while performing an NTP request. All other threads calling {@link #forceRefresh()}
+ * will block during that request.
  *
  * @hide
  */
 public class NtpTrustedTime implements TrustedTime {
+
+    /**
+     * The result of a successful NTP query.
+     *
+     * @hide
+     */
+    public static class TimeResult {
+        private final long mTimeMillis;
+        private final long mElapsedRealtimeMillis;
+        private final long mCertaintyMillis;
+
+        public TimeResult(long timeMillis, long elapsedRealtimeMillis, long certaintyMillis) {
+            mTimeMillis = timeMillis;
+            mElapsedRealtimeMillis = elapsedRealtimeMillis;
+            mCertaintyMillis = certaintyMillis;
+        }
+
+        public long getTimeMillis() {
+            return mTimeMillis;
+        }
+
+        public long getElapsedRealtimeMillis() {
+            return mElapsedRealtimeMillis;
+        }
+
+        public long getCertaintyMillis() {
+            return mCertaintyMillis;
+        }
+
+        /** Calculates and returns the current time accounting for the age of this result. */
+        public long currentTimeMillis() {
+            return mTimeMillis + getAgeMillis();
+        }
+
+        /** Calculates and returns the age of this result. */
+        public long getAgeMillis() {
+            return SystemClock.elapsedRealtime() - mElapsedRealtimeMillis;
+        }
+
+        @Override
+        public String toString() {
+            return "TimeResult{"
+                    + "mTimeMillis=" + mTimeMillis
+                    + ", mElapsedRealtimeMillis=" + mElapsedRealtimeMillis
+                    + ", mCertaintyMillis=" + mCertaintyMillis
+                    + '}';
+        }
+    }
+
     private static final String TAG = "NtpTrustedTime";
     private static final boolean LOGD = false;
 
     private static NtpTrustedTime sSingleton;
-    private static Context sContext;
 
-    private final String mServer;
-    private final long mTimeout;
+    @NonNull
+    private final Context mContext;
 
-    private ConnectivityManager mCM;
+    /**
+     * A supplier that returns the ConnectivityManager. The Supplier can return null if
+     * ConnectivityService isn't running yet.
+     */
+    private final Supplier<ConnectivityManager> mConnectivityManagerSupplier =
+            new Supplier<ConnectivityManager>() {
+        private ConnectivityManager mConnectivityManager;
 
-    private boolean mHasCache;
-    private long mCachedNtpTime;
-    private long mCachedNtpElapsedRealtime;
-    private long mCachedNtpCertainty;
+        @Nullable
+        @Override
+        public synchronized ConnectivityManager get() {
+            // We can't do this at initialization time: ConnectivityService might not be running
+            // yet.
+            if (mConnectivityManager == null) {
+                mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
+            }
+            return mConnectivityManager;
+        }
+    };
 
-    private NtpTrustedTime(String server, long timeout) {
-        if (LOGD) Log.d(TAG, "creating NtpTrustedTime using " + server);
-        mServer = server;
-        mTimeout = timeout;
+    // Declared volatile and accessed outside of synchronized blocks to avoid blocking reads during
+    // forceRefresh().
+    private volatile TimeResult mTimeResult;
+
+    private NtpTrustedTime(Context context) {
+        mContext = Objects.requireNonNull(context);
     }
 
     @UnsupportedAppUsage
     public static synchronized NtpTrustedTime getInstance(Context context) {
         if (sSingleton == null) {
-            final Resources res = context.getResources();
-            final ContentResolver resolver = context.getContentResolver();
-
-            final String defaultServer = res.getString(
-                    com.android.internal.R.string.config_ntpServer);
-            final long defaultTimeout = res.getInteger(
-                    com.android.internal.R.integer.config_ntpTimeout);
-
-            final String secureServer = Settings.Global.getString(
-                    resolver, Settings.Global.NTP_SERVER);
-            final long timeout = Settings.Global.getLong(
-                    resolver, Settings.Global.NTP_TIMEOUT, defaultTimeout);
-
-            final String server = secureServer != null ? secureServer : defaultServer;
-            sSingleton = new NtpTrustedTime(server, timeout);
-            sContext = context;
+            Context appContext = context.getApplicationContext();
+            sSingleton = new NtpTrustedTime(appContext);
         }
-
         return sSingleton;
     }
 
-    @Override
     @UnsupportedAppUsage
     public boolean forceRefresh() {
-        // We can't do this at initialization time: ConnectivityService might not be running yet.
         synchronized (this) {
-            if (mCM == null) {
-                mCM = sContext.getSystemService(ConnectivityManager.class);
+            NtpConnectionInfo connectionInfo = getNtpConnectionInfo();
+            if (connectionInfo == null) {
+                // missing server config, so no trusted time available
+                if (LOGD) Log.d(TAG, "forceRefresh: invalid server config");
+                return false;
             }
-        }
 
-        final Network network = mCM == null ? null : mCM.getActiveNetwork();
-        return forceRefresh(network);
-    }
-
-    public boolean forceRefresh(Network network) {
-        if (TextUtils.isEmpty(mServer)) {
-            // missing server, so no trusted time available
-            return false;
-        }
-
-        // We can't do this at initialization time: ConnectivityService might not be running yet.
-        synchronized (this) {
-            if (mCM == null) {
-                mCM = sContext.getSystemService(ConnectivityManager.class);
+            ConnectivityManager connectivityManager = mConnectivityManagerSupplier.get();
+            if (connectivityManager == null) {
+                if (LOGD) Log.d(TAG, "forceRefresh: no ConnectivityManager");
+                return false;
             }
-        }
+            final Network network = connectivityManager.getActiveNetwork();
+            final NetworkInfo ni = connectivityManager.getNetworkInfo(network);
+            if (ni == null || !ni.isConnected()) {
+                if (LOGD) Log.d(TAG, "forceRefresh: no connectivity");
+                return false;
+            }
 
-        final NetworkInfo ni = mCM == null ? null : mCM.getNetworkInfo(network);
-        if (ni == null || !ni.isConnected()) {
-            if (LOGD) Log.d(TAG, "forceRefresh: no connectivity");
-            return false;
-        }
-
-
-        if (LOGD) Log.d(TAG, "forceRefresh() from cache miss");
-        final SntpClient client = new SntpClient();
-        if (client.requestTime(mServer, (int) mTimeout, network)) {
-            mHasCache = true;
-            mCachedNtpTime = client.getNtpTime();
-            mCachedNtpElapsedRealtime = client.getNtpTimeReference();
-            mCachedNtpCertainty = client.getRoundTripTime() / 2;
-            return true;
-        } else {
-            return false;
+            if (LOGD) Log.d(TAG, "forceRefresh() from cache miss");
+            final SntpClient client = new SntpClient();
+            final String serverName = connectionInfo.getServer();
+            final int timeoutMillis = connectionInfo.getTimeoutMillis();
+            if (client.requestTime(serverName, timeoutMillis, network)) {
+                long ntpCertainty = client.getRoundTripTime() / 2;
+                mTimeResult = new TimeResult(
+                        client.getNtpTime(), client.getNtpTimeReference(), ntpCertainty);
+                return true;
+            } else {
+                return false;
+            }
         }
     }
 
-    @Override
+    /**
+     * Only kept for UnsupportedAppUsage.
+     *
+     * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically.
+     */
+    @Deprecated
     @UnsupportedAppUsage
     public boolean hasCache() {
-        return mHasCache;
+        return mTimeResult != null;
     }
 
+    /**
+     * Only kept for UnsupportedAppUsage.
+     *
+     * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically.
+     */
+    @Deprecated
     @Override
     public long getCacheAge() {
-        if (mHasCache) {
-            return SystemClock.elapsedRealtime() - mCachedNtpElapsedRealtime;
+        TimeResult timeResult = mTimeResult;
+        if (timeResult != null) {
+            return SystemClock.elapsedRealtime() - timeResult.getElapsedRealtimeMillis();
         } else {
             return Long.MAX_VALUE;
         }
     }
 
-    @Override
-    public long getCacheCertainty() {
-        if (mHasCache) {
-            return mCachedNtpCertainty;
-        } else {
-            return Long.MAX_VALUE;
-        }
-    }
-
-    @Override
+    /**
+     * Only kept for UnsupportedAppUsage.
+     *
+     * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically.
+     */
+    @Deprecated
     @UnsupportedAppUsage
     public long currentTimeMillis() {
-        if (!mHasCache) {
+        TimeResult timeResult = mTimeResult;
+        if (timeResult == null) {
             throw new IllegalStateException("Missing authoritative time source");
         }
         if (LOGD) Log.d(TAG, "currentTimeMillis() cache hit");
 
         // current time is age after the last ntp cache; callers who
-        // want fresh values will hit makeAuthoritative() first.
-        return mCachedNtpTime + getCacheAge();
-    }
-
-    @UnsupportedAppUsage
-    public long getCachedNtpTime() {
-        if (LOGD) Log.d(TAG, "getCachedNtpTime() cache hit");
-        return mCachedNtpTime;
-    }
-
-    @UnsupportedAppUsage
-    public long getCachedNtpTimeReference() {
-        return mCachedNtpElapsedRealtime;
+        // want fresh values will hit forceRefresh() first.
+        return timeResult.currentTimeMillis();
     }
 
     /**
-     * Returns the combination of {@link #getCachedNtpTime()} and {@link
-     * #getCachedNtpTimeReference()} as a {@link TimestampedValue}. This method is useful when
-     * passing the time to another component that will adjust for elapsed time.
+     * Only kept for UnsupportedAppUsage.
      *
-     * @throws IllegalStateException if there is no cached value
+     * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically.
      */
-    public TimestampedValue<Long> getCachedNtpTimeSignal() {
-        if (!mHasCache) {
-            throw new IllegalStateException("Missing authoritative time source");
-        }
-        if (LOGD) Log.d(TAG, "getCachedNtpTimeSignal() cache hit");
-
-        return new TimestampedValue<>(mCachedNtpElapsedRealtime, mCachedNtpTime);
+    @Deprecated
+    @UnsupportedAppUsage
+    public long getCachedNtpTime() {
+        if (LOGD) Log.d(TAG, "getCachedNtpTime() cache hit");
+        TimeResult timeResult = mTimeResult;
+        return timeResult == null ? 0 : timeResult.getTimeMillis();
     }
 
+    /**
+     * Only kept for UnsupportedAppUsage.
+     *
+     * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically.
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public long getCachedNtpTimeReference() {
+        TimeResult timeResult = mTimeResult;
+        return timeResult == null ? 0 : timeResult.getElapsedRealtimeMillis();
+    }
+
+    /**
+     * Returns an object containing the latest NTP information available. Can return {@code null} if
+     * no information is available.
+     */
+    @Nullable
+    public TimeResult getCachedTimeResult() {
+        return mTimeResult;
+    }
+
+    private static class NtpConnectionInfo {
+
+        @NonNull private final String mServer;
+        private final int mTimeoutMillis;
+
+        NtpConnectionInfo(@NonNull String server, int timeoutMillis) {
+            mServer = Objects.requireNonNull(server);
+            mTimeoutMillis = timeoutMillis;
+        }
+
+        @NonNull
+        public String getServer() {
+            return mServer;
+        }
+
+        int getTimeoutMillis() {
+            return mTimeoutMillis;
+        }
+    }
+
+    @GuardedBy("this")
+    private NtpConnectionInfo getNtpConnectionInfo() {
+        final ContentResolver resolver = mContext.getContentResolver();
+
+        final Resources res = mContext.getResources();
+        final String defaultServer = res.getString(
+                com.android.internal.R.string.config_ntpServer);
+        final int defaultTimeoutMillis = res.getInteger(
+                com.android.internal.R.integer.config_ntpTimeout);
+
+        final String secureServer = Settings.Global.getString(
+                resolver, Settings.Global.NTP_SERVER);
+        final int timeoutMillis = Settings.Global.getInt(
+                resolver, Settings.Global.NTP_TIMEOUT, defaultTimeoutMillis);
+
+        final String server = secureServer != null ? secureServer : defaultServer;
+        return TextUtils.isEmpty(server) ? null : new NtpConnectionInfo(server, timeoutMillis);
+    }
 }
diff --git a/core/java/android/util/SparseSetArray.java b/core/java/android/util/SparseSetArray.java
index 9f0f246..f5025f7 100644
--- a/core/java/android/util/SparseSetArray.java
+++ b/core/java/android/util/SparseSetArray.java
@@ -37,10 +37,10 @@
             mData.put(n, set);
         }
         if (set.contains(value)) {
-            return true;
+            return false;
         }
         set.add(value);
-        return false;
+        return true;
     }
 
     /**
diff --git a/core/java/android/util/StatsLog.java b/core/java/android/util/StatsLog.java
index 952d7cb..8635340 100644
--- a/core/java/android/util/StatsLog.java
+++ b/core/java/android/util/StatsLog.java
@@ -254,6 +254,7 @@
      * @param statsEvent    The StatsEvent object containing the encoded buffer of data to write.
      * @hide
      */
+    @SystemApi
     public static void write(@NonNull final StatsEvent statsEvent) {
         writeImpl(statsEvent.getBytes(), statsEvent.getNumBytes(), statsEvent.getAtomId());
         statsEvent.release();
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index 8439f5a..37dd781 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -30,6 +30,7 @@
 
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
+import java.time.LocalTime;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Collections;
@@ -88,7 +89,7 @@
      *
      * <p>The list returned may be different from other on-device sources like
      * {@link android.icu.util.TimeZone#getRegion(String)} as it can be curated to avoid
-     * contentious mappings.
+     * contentious or obsolete mappings.
      *
      * @param countryCode the ISO 3166-1 alpha-2 code for the country as can be obtained using
      *     {@link java.util.Locale#getCountry()}
@@ -382,6 +383,28 @@
     }
 
     /**
+     * This method is used to find if a clock time is inclusively between two other clock times
+     * @param reference The time of the day we want check if it is between start and end
+     * @param start The start time reference
+     * @param end The end time
+     * @return true if the reference time is between the two clock times, and false otherwise.
+     */
+    public static boolean isTimeBetween(@NonNull LocalTime reference,
+                                        @NonNull LocalTime start,
+                                        @NonNull LocalTime end) {
+        //    ////////E----+-----S////////
+        if ((reference.isBefore(start) && reference.isAfter(end)
+                //    -----+----S//////////E------
+                || (reference.isBefore(end) && reference.isBefore(start) && start.isBefore(end))
+                //    ---------S//////////E---+---
+                || (reference.isAfter(end) && reference.isAfter(start)) && start.isBefore(end))) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    /**
      * Dump a currentTimeMillis style timestamp for dumpsys, with the delta time from now.
      *
      * @hide
diff --git a/core/java/android/util/TrustedTime.java b/core/java/android/util/TrustedTime.java
index 1360f87..f41fe85 100644
--- a/core/java/android/util/TrustedTime.java
+++ b/core/java/android/util/TrustedTime.java
@@ -20,42 +20,48 @@
 
 /**
  * Interface that provides trusted time information, possibly coming from an NTP
- * server. Implementations may cache answers until {@link #forceRefresh()}.
+ * server.
  *
  * @hide
+ * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime}
  */
 public interface TrustedTime {
     /**
      * Force update with an external trusted time source, returning {@code true}
      * when successful.
+     *
+     * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime}
      */
+    @Deprecated
     @UnsupportedAppUsage
     public boolean forceRefresh();
 
     /**
      * Check if this instance has cached a response from a trusted time source.
+     *
+     * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime}
      */
+    @Deprecated
     @UnsupportedAppUsage
-    public boolean hasCache();
+    boolean hasCache();
 
     /**
      * Return time since last trusted time source contact, or
      * {@link Long#MAX_VALUE} if never contacted.
+     *
+     * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime}
      */
+    @Deprecated
     @UnsupportedAppUsage
     public long getCacheAge();
 
     /**
-     * Return certainty of cached trusted time in milliseconds, or
-     * {@link Long#MAX_VALUE} if never contacted. Smaller values are more
-     * precise.
-     */
-    public long getCacheCertainty();
-
-    /**
      * Return current time similar to {@link System#currentTimeMillis()},
      * possibly using a cached authoritative time source.
+     *
+     * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime}
      */
+    @Deprecated
     @UnsupportedAppUsage
-    public long currentTimeMillis();
+    long currentTimeMillis();
 }
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 4368115..0304328 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.CONFIGURE_DISPLAY_COLOR_MODE;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
@@ -422,10 +423,14 @@
     /**
      * Internal method to create a display.
      * The display created with this method will have a static {@link DisplayAdjustments} applied.
-     * Applications should use {@link android.view.WindowManager#getDefaultDisplay()}
-     * or {@link android.hardware.display.DisplayManager#getDisplay}
-     * to get a display object.
+     * Applications should use {@link android.content.Context#getDisplay} with
+     * {@link android.app.Activity} or a context associated with a {@link Display} via
+     * {@link android.content.Context#createDisplayContext(Display)}
+     * to get a display object associated with a {@link android.app.Context}, or
+     * {@link android.hardware.display.DisplayManager#getDisplay} to get a display object by id.
      *
+     * @see android.content.Context#getDisplay()
+     * @see android.content.Context#createDisplayContext(Display)
      * @hide
      */
     public Display(DisplayManagerGlobal global, int displayId, /*@NotNull*/ DisplayInfo displayInfo,
@@ -453,7 +458,7 @@
         mResources = res;
         mDisplayAdjustments = mResources != null
             ? new DisplayAdjustments(mResources.getConfiguration())
-            : daj != null ? new DisplayAdjustments(daj) : null;
+            : daj != null ? new DisplayAdjustments(daj) : new DisplayAdjustments();
         mIsValid = true;
 
         // Cache properties that cannot change as long as the display is valid.
@@ -1010,6 +1015,9 @@
      * @return Supported WCG color spaces.
      * @hide
      */
+    @SuppressLint("VisiblySynchronized")
+    @NonNull
+    @TestApi
     public @ColorMode ColorSpace[] getSupportedWideColorGamut() {
         synchronized (this) {
             final ColorSpace[] defaultColorSpaces = new ColorSpace[0];
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index 615dab0..d433591 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -86,7 +86,7 @@
      * @hide
      */
     public static final DisplayCutout NO_CUTOUT = new DisplayCutout(
-            ZERO_RECT, ZERO_RECT, ZERO_RECT, ZERO_RECT, ZERO_RECT,
+            ZERO_RECT, Insets.NONE, ZERO_RECT, ZERO_RECT, ZERO_RECT, ZERO_RECT,
             false /* copyArguments */);
 
 
@@ -103,8 +103,12 @@
     private static float sCachedDensity;
     @GuardedBy("CACHE_LOCK")
     private static Pair<Path, DisplayCutout> sCachedCutout = NULL_PAIR;
+    @GuardedBy("CACHE_LOCK")
+    private static Insets sCachedWaterfallInsets;
 
     private final Rect mSafeInsets;
+    @NonNull
+    private final Insets mWaterfallInsets;
 
 
     /**
@@ -251,7 +255,32 @@
     // TODO(b/73953958): @VisibleForTesting(visibility = PRIVATE)
     public DisplayCutout(@NonNull Insets safeInsets, @Nullable Rect boundLeft,
             @Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom) {
-        this(safeInsets.toRect(), boundLeft, boundTop, boundRight, boundBottom, true);
+        this(safeInsets.toRect(), Insets.NONE, boundLeft, boundTop, boundRight, boundBottom, true);
+    }
+
+    /**
+     * Creates a DisplayCutout instance.
+     *
+     * <p>Note that this is only useful for tests. For production code, developers should always
+     * use a {@link DisplayCutout} obtained from the system.</p>
+     *
+     * @param safeInsets the insets from each edge which avoid the display cutout as returned by
+     *                   {@link #getSafeInsetTop()} etc.
+     * @param boundLeft the left bounding rect of the display cutout in pixels. If null is passed,
+     *                  it's treated as an empty rectangle (0,0)-(0,0).
+     * @param boundTop the top bounding rect of the display cutout in pixels.  If null is passed,
+     *                  it's treated as an empty rectangle (0,0)-(0,0).
+     * @param boundRight the right bounding rect of the display cutout in pixels.  If null is
+     *                  passed, it's treated as an empty rectangle (0,0)-(0,0).
+     * @param boundBottom the bottom bounding rect of the display cutout in pixels.  If null is
+     *                   passed, it's treated as an empty rectangle (0,0)-(0,0).
+     * @param waterfallInsets the insets for the curved areas in waterfall display.
+     */
+    public DisplayCutout(@NonNull Insets safeInsets, @Nullable Rect boundLeft,
+            @Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom,
+            @NonNull Insets waterfallInsets) {
+        this(safeInsets.toRect(), waterfallInsets, boundLeft, boundTop, boundRight, boundBottom,
+                true);
     }
 
     /**
@@ -269,7 +298,7 @@
     // TODO(b/73953958): @VisibleForTesting(visibility = PRIVATE)
     @Deprecated
     public DisplayCutout(@Nullable Rect safeInsets, @Nullable List<Rect> boundingRects) {
-        this(safeInsets, extractBoundsFromList(safeInsets, boundingRects),
+        this(safeInsets, Insets.NONE, extractBoundsFromList(safeInsets, boundingRects),
                 true /* copyArguments */);
     }
 
@@ -281,19 +310,23 @@
      * @param copyArguments if true, create a copy of the arguments. If false, the passed arguments
      *                      are not copied and MUST remain unchanged forever.
      */
-    private DisplayCutout(Rect safeInsets, Rect boundLeft, Rect boundTop, Rect boundRight,
-                         Rect boundBottom, boolean copyArguments) {
+    private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Rect boundLeft,
+                        Rect boundTop, Rect boundRight, Rect boundBottom, boolean copyArguments) {
         mSafeInsets = getCopyOrRef(safeInsets, copyArguments);
+        mWaterfallInsets = waterfallInsets == null ? Insets.NONE : waterfallInsets;
         mBounds = new Bounds(boundLeft, boundTop, boundRight, boundBottom, copyArguments);
     }
 
-    private DisplayCutout(Rect safeInsets, Rect[] bounds, boolean copyArguments) {
+    private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Rect[] bounds,
+                        boolean copyArguments) {
         mSafeInsets = getCopyOrRef(safeInsets, copyArguments);
+        mWaterfallInsets = waterfallInsets == null ? Insets.NONE : waterfallInsets;
         mBounds = new Bounds(bounds, copyArguments);
     }
 
-    private DisplayCutout(Rect safeInsets, Bounds bounds) {
+    private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Bounds bounds) {
         mSafeInsets = safeInsets;
+        mWaterfallInsets = waterfallInsets == null ? Insets.NONE : waterfallInsets;
         mBounds = bounds;
 
     }
@@ -309,6 +342,14 @@
     }
 
     /**
+     * Return the waterfall insets.
+     */
+    public @NonNull Insets getWaterfallInsets() {
+        return mWaterfallInsets;
+    }
+
+
+    /**
      * Find the position of the bounding rect, and create an array of Rect whose index represents
      * the position (= BoundsPosition).
      *
@@ -476,7 +517,8 @@
 
     @Override
     public int hashCode() {
-        return mSafeInsets.hashCode() * 48271 + mBounds.hashCode();
+        return (mSafeInsets.hashCode() * 48271 + mBounds.hashCode()) * 48271
+                + mWaterfallInsets.hashCode();
     }
 
     @Override
@@ -486,7 +528,8 @@
         }
         if (o instanceof DisplayCutout) {
             DisplayCutout c = (DisplayCutout) o;
-            return mSafeInsets.equals(c.mSafeInsets) && mBounds.equals(c.mBounds);
+            return mSafeInsets.equals(c.mSafeInsets) && mBounds.equals(c.mBounds)
+                    && mWaterfallInsets.equals(c.mWaterfallInsets);
         }
         return false;
     }
@@ -494,6 +537,7 @@
     @Override
     public String toString() {
         return "DisplayCutout{insets=" + mSafeInsets
+                + " waterfall=" + mWaterfallInsets
                 + " boundingRect={" + mBounds + "}"
                 + "}";
     }
@@ -508,6 +552,7 @@
         mBounds.getRect(BOUNDS_POSITION_TOP).dumpDebug(proto, BOUND_TOP);
         mBounds.getRect(BOUNDS_POSITION_RIGHT).dumpDebug(proto, BOUND_RIGHT);
         mBounds.getRect(BOUNDS_POSITION_BOTTOM).dumpDebug(proto, BOUND_BOTTOM);
+        mWaterfallInsets.toRect().dumpDebug(proto, INSETS);
         proto.end(token);
     }
 
@@ -553,7 +598,7 @@
             }
         }
 
-        return new DisplayCutout(safeInsets, bounds, false /* copyArguments */);
+        return new DisplayCutout(safeInsets, mWaterfallInsets, bounds, false /* copyArguments */);
     }
 
     /**
@@ -565,7 +610,7 @@
      * @hide
      */
     public DisplayCutout replaceSafeInsets(Rect safeInsets) {
-        return new DisplayCutout(new Rect(safeInsets), mBounds);
+        return new DisplayCutout(new Rect(safeInsets), mWaterfallInsets, mBounds);
     }
 
     private static int atLeastZero(int value) {
@@ -585,7 +630,16 @@
         for (int i = 0; i < BOUNDS_POSITION_LENGTH; ++i) {
             bounds[i] = (pos == i) ? new Rect(left, top, right, bottom) : new Rect();
         }
-        return new DisplayCutout(ZERO_RECT, bounds, false /* copyArguments */);
+        return new DisplayCutout(ZERO_RECT, Insets.NONE, bounds, false /* copyArguments */);
+    }
+
+    /**
+     * Creates an instance from a bounding and waterfall insets.
+     *
+     * @hide
+     */
+    public static DisplayCutout fromBoundsAndWaterfall(Rect[] bounds, Insets waterfallInsets) {
+        return new DisplayCutout(ZERO_RECT, waterfallInsets, bounds, false /* copyArguments */);
     }
 
     /**
@@ -594,7 +648,7 @@
      * @hide
      */
     public static DisplayCutout fromBounds(Rect[] bounds) {
-        return new DisplayCutout(ZERO_RECT, bounds, false /* copyArguments */);
+        return new DisplayCutout(ZERO_RECT, Insets.NONE, bounds, false /* copyArguments */);
     }
 
     /**
@@ -606,7 +660,8 @@
      */
     public static DisplayCutout fromResourcesRectApproximation(Resources res, int displayWidth, int displayHeight) {
         return fromSpec(res.getString(R.string.config_mainBuiltInDisplayCutoutRectApproximation),
-                displayWidth, displayHeight, DENSITY_DEVICE_STABLE / (float) DENSITY_DEFAULT);
+                displayWidth, displayHeight, DENSITY_DEVICE_STABLE / (float) DENSITY_DEFAULT,
+                loadWaterfallInset(res));
     }
 
     /**
@@ -617,7 +672,8 @@
     public static Path pathFromResources(Resources res, int displayWidth, int displayHeight) {
         return pathAndDisplayCutoutFromSpec(
                 res.getString(R.string.config_mainBuiltInDisplayCutout),
-                displayWidth, displayHeight, DENSITY_DEVICE_STABLE / (float) DENSITY_DEFAULT).first;
+                displayWidth, displayHeight, DENSITY_DEVICE_STABLE / (float) DENSITY_DEFAULT,
+                loadWaterfallInset(res)).first;
     }
 
     /**
@@ -627,91 +683,109 @@
      */
     @VisibleForTesting(visibility = PRIVATE)
     public static DisplayCutout fromSpec(String spec, int displayWidth, int displayHeight,
-            float density) {
-        return pathAndDisplayCutoutFromSpec(spec, displayWidth, displayHeight, density).second;
+            float density, Insets waterfallInsets) {
+        return pathAndDisplayCutoutFromSpec(
+                spec, displayWidth, displayHeight, density, waterfallInsets).second;
     }
 
     private static Pair<Path, DisplayCutout> pathAndDisplayCutoutFromSpec(String spec,
-            int displayWidth, int displayHeight, float density) {
-        if (TextUtils.isEmpty(spec)) {
+            int displayWidth, int displayHeight, float density, Insets waterfallInsets) {
+        if (TextUtils.isEmpty(spec) && waterfallInsets.equals(Insets.NONE)) {
             return NULL_PAIR;
         }
+
         synchronized (CACHE_LOCK) {
             if (spec.equals(sCachedSpec) && sCachedDisplayWidth == displayWidth
                     && sCachedDisplayHeight == displayHeight
-                    && sCachedDensity == density) {
+                    && sCachedDensity == density
+                    && waterfallInsets.equals(sCachedWaterfallInsets)) {
                 return sCachedCutout;
             }
         }
-        spec = spec.trim();
-        final float offsetX;
-        if (spec.endsWith(RIGHT_MARKER)) {
-            offsetX = displayWidth;
-            spec = spec.substring(0, spec.length() - RIGHT_MARKER.length()).trim();
-        } else if (spec.endsWith(LEFT_MARKER)) {
-            offsetX = 0;
-            spec = spec.substring(0, spec.length() - LEFT_MARKER.length()).trim();
-        } else {
-            offsetX = displayWidth / 2f;
-        }
-        final boolean inDp = spec.endsWith(DP_MARKER);
-        if (inDp) {
-            spec = spec.substring(0, spec.length() - DP_MARKER.length());
-        }
 
-        String bottomSpec = null;
-        if (spec.contains(BOTTOM_MARKER)) {
-            String[] splits = spec.split(BOTTOM_MARKER, 2);
-            spec = splits[0].trim();
-            bottomSpec = splits[1].trim();
-        }
-
-        final Path p;
-        final Region r = Region.obtain();
-        try {
-            p = PathParser.createPathFromPathData(spec);
-        } catch (Throwable e) {
-            Log.wtf(TAG, "Could not inflate cutout: ", e);
-            return NULL_PAIR;
-        }
-
-        final Matrix m = new Matrix();
-        if (inDp) {
-            m.postScale(density, density);
-        }
-        m.postTranslate(offsetX, 0);
-        p.transform(m);
-
-        Rect boundTop = new Rect();
-        toRectAndAddToRegion(p, r, boundTop);
-        final int topInset = boundTop.bottom;
-
+        Path p = null;
+        Rect boundTop = null;
         Rect boundBottom = null;
-        final int bottomInset;
-        if (bottomSpec != null) {
-            final Path bottomPath;
-            try {
-                bottomPath = PathParser.createPathFromPathData(bottomSpec);
-            } catch (Throwable e) {
-                Log.wtf(TAG, "Could not inflate bottom cutout: ", e);
-                return NULL_PAIR;
+        Rect safeInset = new Rect();
+        String bottomSpec = null;
+        if (!TextUtils.isEmpty(spec)) {
+            spec = spec.trim();
+            final float offsetX;
+            if (spec.endsWith(RIGHT_MARKER)) {
+                offsetX = displayWidth;
+                spec = spec.substring(0, spec.length() - RIGHT_MARKER.length()).trim();
+            } else if (spec.endsWith(LEFT_MARKER)) {
+                offsetX = 0;
+                spec = spec.substring(0, spec.length() - LEFT_MARKER.length()).trim();
+            } else {
+                offsetX = displayWidth / 2f;
             }
-            // Keep top transform
-            m.postTranslate(0, displayHeight);
-            bottomPath.transform(m);
-            p.addPath(bottomPath);
-            boundBottom = new Rect();
-            toRectAndAddToRegion(bottomPath, r, boundBottom);
-            bottomInset = displayHeight - boundBottom.top;
-        } else {
-            bottomInset = 0;
+            final boolean inDp = spec.endsWith(DP_MARKER);
+            if (inDp) {
+                spec = spec.substring(0, spec.length() - DP_MARKER.length());
+            }
+
+            if (spec.contains(BOTTOM_MARKER)) {
+                String[] splits = spec.split(BOTTOM_MARKER, 2);
+                spec = splits[0].trim();
+                bottomSpec = splits[1].trim();
+            }
+
+            final Matrix m = new Matrix();
+            final Region r = Region.obtain();
+            if (!spec.isEmpty()) {
+                try {
+                    p = PathParser.createPathFromPathData(spec);
+                } catch (Throwable e) {
+                    Log.wtf(TAG, "Could not inflate cutout: ", e);
+                }
+
+                if (p != null) {
+                    if (inDp) {
+                        m.postScale(density, density);
+                    }
+                    m.postTranslate(offsetX, 0);
+                    p.transform(m);
+
+                    boundTop = new Rect();
+                    toRectAndAddToRegion(p, r, boundTop);
+                    safeInset.top = boundTop.bottom;
+                }
+            }
+
+            if (bottomSpec != null) {
+                int bottomInset = 0;
+                Path bottomPath = null;
+                try {
+                    bottomPath = PathParser.createPathFromPathData(bottomSpec);
+                } catch (Throwable e) {
+                    Log.wtf(TAG, "Could not inflate bottom cutout: ", e);
+                }
+
+                if (bottomPath != null) {
+                    // Keep top transform
+                    m.postTranslate(0, displayHeight);
+                    bottomPath.transform(m);
+                    p.addPath(bottomPath);
+                    boundBottom = new Rect();
+                    toRectAndAddToRegion(bottomPath, r, boundBottom);
+                    bottomInset = displayHeight - boundBottom.top;
+                }
+                safeInset.bottom = bottomInset;
+            }
         }
 
-        Rect safeInset = new Rect(0, topInset, 0, bottomInset);
-        final DisplayCutout cutout = new DisplayCutout(
-                safeInset, null /* boundLeft */, boundTop, null /* boundRight */, boundBottom,
-                false /* copyArguments */);
+        if (!waterfallInsets.equals(Insets.NONE)) {
+            safeInset.set(
+                    Math.max(waterfallInsets.left, safeInset.left),
+                    Math.max(waterfallInsets.top, safeInset.top),
+                    Math.max(waterfallInsets.right, safeInset.right),
+                    Math.max(waterfallInsets.bottom, safeInset.bottom));
+        }
 
+        final DisplayCutout cutout = new DisplayCutout(
+                safeInset, waterfallInsets, null /* boundLeft */, boundTop,
+                null /* boundRight */, boundBottom, false /* copyArguments */);
         final Pair<Path, DisplayCutout> result = new Pair<>(p, cutout);
         synchronized (CACHE_LOCK) {
             sCachedSpec = spec;
@@ -719,6 +793,7 @@
             sCachedDisplayHeight = displayHeight;
             sCachedDensity = density;
             sCachedCutout = result;
+            sCachedWaterfallInsets = waterfallInsets;
         }
         return result;
     }
@@ -730,6 +805,15 @@
         inoutRegion.op(inoutRect, Op.UNION);
     }
 
+
+    private static Insets loadWaterfallInset(Resources res) {
+        return Insets.of(
+                res.getDimensionPixelSize(R.dimen.waterfall_display_left_edge_size),
+                res.getDimensionPixelSize(R.dimen.waterfall_display_top_edge_size),
+                res.getDimensionPixelSize(R.dimen.waterfall_display_right_edge_size),
+                res.getDimensionPixelSize(R.dimen.waterfall_display_bottom_edge_size));
+    }
+
     /**
      * Helper class for passing {@link DisplayCutout} through binder.
      *
@@ -773,6 +857,7 @@
                 out.writeInt(1);
                 out.writeTypedObject(cutout.mSafeInsets, flags);
                 out.writeTypedArray(cutout.mBounds.getRects(), flags);
+                out.writeTypedObject(cutout.mWaterfallInsets, flags);
             }
         }
 
@@ -815,8 +900,10 @@
             Rect safeInsets = in.readTypedObject(Rect.CREATOR);
             Rect[] bounds = new Rect[BOUNDS_POSITION_LENGTH];
             in.readTypedArray(bounds, Rect.CREATOR);
+            Insets waterfallInsets = in.readTypedObject(Insets.CREATOR);
 
-            return new DisplayCutout(safeInsets, bounds, false /* copyArguments */);
+            return new DisplayCutout(
+                    safeInsets, waterfallInsets, bounds, false /* copyArguments */);
         }
 
         public DisplayCutout get() {
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 7d455c9..b9868a7 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -624,6 +624,8 @@
         sb.append(Arrays.toString(supportedColorModes));
         sb.append(", hdrCapabilities ");
         sb.append(hdrCapabilities);
+        sb.append(", minimalPostProcessingSupported ");
+        sb.append(minimalPostProcessingSupported);
         sb.append(", rotation ");
         sb.append(rotation);
         sb.append(", density ");
diff --git a/core/java/android/view/IDisplayWindowInsetsController.aidl b/core/java/android/view/IDisplayWindowInsetsController.aidl
new file mode 100644
index 0000000..429c3ae
--- /dev/null
+++ b/core/java/android/view/IDisplayWindowInsetsController.aidl
@@ -0,0 +1,48 @@
+/**
+ * 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.view;
+
+import android.view.InsetsSourceControl;
+import android.view.InsetsState;
+
+/**
+ * Singular controller of insets to use when there isn't another obvious controller available.
+ * Specifically, this will take over insets control in multi-window.
+ * @hide
+ */
+oneway interface IDisplayWindowInsetsController {
+
+    /**
+     * @see IWindow#insetsChanged
+     */
+    void insetsChanged(in InsetsState insetsState);
+
+    /**
+     * @see IWindow#insetsControlChanged
+     */
+    void insetsControlChanged(in InsetsState insetsState, in InsetsSourceControl[] activeControls);
+
+    /**
+     * @see IWindow#showInsets
+     */
+    void showInsets(int types, boolean fromIme);
+
+    /**
+     * @see IWindow#hideInsets
+     */
+    void hideInsets(int types, boolean fromIme);
+}
diff --git a/core/java/android/view/ITaskOrganizer.aidl b/core/java/android/view/ITaskOrganizer.aidl
new file mode 100644
index 0000000..e92aafe
--- /dev/null
+++ b/core/java/android/view/ITaskOrganizer.aidl
@@ -0,0 +1,38 @@
+/* //device/java/android/android/view/ITaskOrganizer.aidl
+**
+** Copyright 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.view;
+
+import android.view.IWindowContainer;
+import android.view.SurfaceControl;
+import android.app.ActivityManager;
+
+/**
+ * Interface for ActivityTaskManager/WindowManager to delegate control of tasks.
+ * {@hide}
+ */
+oneway interface ITaskOrganizer {
+    void taskAppeared(in IWindowContainer container,
+        in ActivityManager.RunningTaskInfo taskInfo);
+    void taskVanished(in IWindowContainer container);
+
+    /**
+     * Called upon completion of
+     * ActivityTaskManagerService#applyTaskOrganizerTransaction
+     */
+    void transactionReady(int id, in SurfaceControl.Transaction t);
+}
\ No newline at end of file
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 9496827..d9c502e 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -32,9 +32,11 @@
 import android.os.Bundle;
 import android.os.IRemoteCallback;
 import android.os.ParcelFileDescriptor;
+import android.view.DisplayCutout;
 import android.view.IApplicationToken;
 import android.view.IAppTransitionAnimationSpecsFuture;
 import android.view.IDockedStackListener;
+import android.view.IDisplayWindowInsetsController;
 import android.view.IDisplayWindowListener;
 import android.view.IDisplayFoldListener;
 import android.view.IDisplayWindowRotationController;
@@ -49,6 +51,7 @@
 import android.view.IWindowSessionCallback;
 import android.view.KeyEvent;
 import android.view.InputEvent;
+import android.view.InsetsState;
 import android.view.MagnificationSpec;
 import android.view.MotionEvent;
 import android.view.InputChannel;
@@ -109,6 +112,20 @@
 
     // These can only be called when holding the MANAGE_APP_TOKENS permission.
     void setEventDispatching(boolean enabled);
+
+    /** @return {@code true} if this binder is a registered window token. */
+    boolean isWindowToken(in IBinder binder);
+    /**
+     * Adds window token for a given type.
+     *
+     * @param token Token to be registered.
+     * @param type Window type to be used with this token.
+     * @param displayId The ID of the display where this token should be added.
+     * @param packageName The name of package to request to add window token.
+     * @return {@link WindowManagerGlobal#ADD_OKAY} if the addition was successful, an error code
+     *         otherwise.
+     */
+    int addWindowContextToken(IBinder token, int type, int displayId, String packageName);
     void addWindowToken(IBinder token, int type, int displayId);
     void removeWindowToken(IBinder token, int displayId);
     void prepareAppTransition(int transit, boolean alwaysKeepCurrent);
@@ -711,4 +728,24 @@
      * @return true if the display was successfully mirrored.
      */
     boolean mirrorDisplay(int displayId, out SurfaceControl outSurfaceControl);
+
+    /**
+     * When in multi-window mode, the provided displayWindowInsetsController will control insets
+     * animations.
+     */
+    void setDisplayWindowInsetsController(
+            int displayId, in IDisplayWindowInsetsController displayWindowInsetsController);
+
+    /**
+     * Called when a remote process modifies insets on a display window container.
+     */
+    void modifyDisplayWindowInsets(int displayId, in InsetsState state);
+
+    /**
+     * Called to get the expected window insets.
+     * TODO(window-context): Remove when new insets flag is available.
+     */
+    void getWindowInsets(in WindowManager.LayoutParams attrs, int displayId,
+            out Rect outContentInsets, out Rect outStableInsets,
+            out DisplayCutout.ParcelableWrapper displayCutout);
 }
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 8f25d89..e3446e1 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -18,6 +18,7 @@
 package android.view;
 
 import android.content.ClipData;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Bundle;
@@ -90,6 +91,7 @@
      * since it was last displayed.
      * @param outSurface Object in which is placed the new display surface.
      * @param insetsState The current insets state in the system.
+     * @param outSurfaceSize The width and height of the surface control
      *
      * @return int Result flags: {@link WindowManagerGlobal#RELAYOUT_SHOW_FOCUS},
      * {@link WindowManagerGlobal#RELAYOUT_FIRST_TIME}.
@@ -101,7 +103,7 @@
             out Rect outBackdropFrame,
             out DisplayCutout.ParcelableWrapper displayCutout,
             out MergedConfiguration outMergedConfiguration, out SurfaceControl outSurfaceControl,
-            out InsetsState insetsState);
+            out InsetsState insetsState, out Point outSurfaceSize);
 
     /*
      * Notify the window manager that an application is relaunching and
diff --git a/core/java/android/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java
new file mode 100644
index 0000000..8d58ee8
--- /dev/null
+++ b/core/java/android/view/ImeFocusController.java
@@ -0,0 +1,256 @@
+/*
+ * 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.view;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.UiThread;
+import android.util.Log;
+import android.view.inputmethod.InputMethodManager;
+
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.internal.inputmethod.StartInputFlags;
+import com.android.internal.inputmethod.StartInputReason;
+
+/**
+ * Responsible for IME focus handling inside {@link ViewRootImpl}.
+ * @hide
+ */
+public final class ImeFocusController {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "ImeFocusController";
+
+    private final ViewRootImpl mViewRootImpl;
+    private boolean mHasImeFocus = false;
+    private View mServedView;
+    private View mNextServedView;
+    private InputMethodManagerDelegate mDelegate;
+
+    @UiThread
+    ImeFocusController(@NonNull ViewRootImpl viewRootImpl) {
+        mViewRootImpl = viewRootImpl;
+    }
+
+    @NonNull
+    private InputMethodManagerDelegate getImmDelegate() {
+        InputMethodManagerDelegate delegate = mDelegate;
+        if (delegate != null) {
+            return delegate;
+        }
+        delegate = mViewRootImpl.mContext.getSystemService(InputMethodManager.class).getDelegate();
+        mDelegate = delegate;
+        return delegate;
+    }
+
+    /** Called when the view root is moved to a different display. */
+    @UiThread
+    void onMovedToDisplay() {
+        // InputMethodManager managed its instances for different displays. So if the associated
+        // display is changed, the delegate also needs to be refreshed (by getImmDelegate).
+        // See the comment in {@link android.app.SystemServiceRegistry} for InputMethodManager
+        // and {@link android.view.inputmethod.InputMethodManager#forContext}.
+        mDelegate = null;
+    }
+
+    @UiThread
+    void onTraversal(boolean hasWindowFocus, WindowManager.LayoutParams windowAttribute) {
+        final boolean hasImeFocus = updateImeFocusable(windowAttribute, false /* force */);
+        if (!hasWindowFocus || isInLocalFocusMode(windowAttribute)) {
+            return;
+        }
+        if (hasImeFocus == mHasImeFocus) {
+            return;
+        }
+        mHasImeFocus = hasImeFocus;
+        if (mHasImeFocus) {
+            onPreWindowFocus(true /* hasWindowFocus */, windowAttribute);
+            onPostWindowFocus(mViewRootImpl.mView.findFocus(), true /* hasWindowFocus */,
+                    windowAttribute);
+        }
+    }
+
+    @UiThread
+    void onPreWindowFocus(boolean hasWindowFocus, WindowManager.LayoutParams windowAttribute) {
+        if (!mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
+            return;
+        }
+        if (hasWindowFocus) {
+            getImmDelegate().setCurrentRootView(mViewRootImpl);
+        }
+    }
+
+    @UiThread
+    boolean updateImeFocusable(WindowManager.LayoutParams windowAttribute, boolean force) {
+        final boolean hasImeFocus = WindowManager.LayoutParams.mayUseInputMethod(
+                windowAttribute.flags);
+        if (force) {
+            mHasImeFocus = hasImeFocus;
+        }
+        return hasImeFocus;
+    }
+
+    @UiThread
+    void onPostWindowFocus(View focusedView, boolean hasWindowFocus,
+            WindowManager.LayoutParams windowAttribute) {
+        if (!hasWindowFocus || !mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
+            return;
+        }
+        if (DEBUG) {
+            Log.v(TAG, "onWindowFocus: " + focusedView
+                    + " softInputMode=" + InputMethodDebug.softInputModeToString(
+                    windowAttribute.softInputMode));
+        }
+
+        boolean forceFocus = false;
+        final InputMethodManagerDelegate immDelegate = getImmDelegate();
+        if (immDelegate.isRestartOnNextWindowFocus(true /* reset */)) {
+            if (DEBUG) Log.v(TAG, "Restarting due to isRestartOnNextWindowFocus as true");
+            forceFocus = true;
+        }
+        // Update mNextServedView when focusedView changed.
+        final View viewForWindowFocus = focusedView != null ? focusedView : mViewRootImpl.mView;
+        onViewFocusChanged(viewForWindowFocus, true);
+
+        immDelegate.startInputAsyncOnWindowFocusGain(viewForWindowFocus,
+                windowAttribute.softInputMode, windowAttribute.flags, forceFocus);
+    }
+
+    public boolean checkFocus(boolean forceNewFocus, boolean startInput) {
+        final InputMethodManagerDelegate immDelegate = getImmDelegate();
+        if (!immDelegate.isCurrentRootView(mViewRootImpl)
+                || (mServedView == mNextServedView && !forceNewFocus)) {
+            return false;
+        }
+        if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView
+                + " next=" + mNextServedView
+                + " force=" + forceNewFocus
+                + " package="
+                + (mServedView != null ? mServedView.getContext().getPackageName() : "<none>"));
+
+        // Close the connection when no next served view coming.
+        if (mNextServedView == null) {
+            immDelegate.finishInput();
+            immDelegate.closeCurrentIme();
+            return false;
+        }
+        mServedView = mNextServedView;
+        immDelegate.finishComposingText();
+
+        if (startInput) {
+            immDelegate.startInput(StartInputReason.CHECK_FOCUS, null /* focusedView */,
+                    0 /* startInputFlags */, 0 /* softInputMode */, 0 /* windowFlags */);
+        }
+        return true;
+    }
+
+    @UiThread
+    void onViewFocusChanged(View view, boolean hasFocus) {
+        if (view == null || view.isTemporarilyDetached()) {
+            return;
+        }
+        if (!getImmDelegate().isCurrentRootView(view.getViewRootImpl())) {
+            return;
+        }
+        if (mServedView == view || !view.hasImeFocus() || !view.hasWindowFocus()) {
+            return;
+        }
+        mNextServedView = hasFocus ? view : null;
+        mViewRootImpl.dispatchCheckFocus();
+    }
+
+    @UiThread
+    void onViewDetachedFromWindow(View view) {
+        if (!getImmDelegate().isCurrentRootView(view.getViewRootImpl())) {
+            return;
+        }
+        if (mServedView == view) {
+            mNextServedView = null;
+            mViewRootImpl.dispatchCheckFocus();
+        }
+    }
+
+    @UiThread
+    void onWindowDismissed() {
+        final InputMethodManagerDelegate immDelegate = getImmDelegate();
+        if (!immDelegate.isCurrentRootView(mViewRootImpl)) {
+            return;
+        }
+        if (mServedView != null) {
+            immDelegate.finishInput();
+        }
+        immDelegate.setCurrentRootView(null);
+        mHasImeFocus = false;
+    }
+
+    /**
+     * @param windowAttribute {@link WindowManager.LayoutParams} to be checked.
+     * @return Whether the window is in local focus mode or not.
+     */
+    @AnyThread
+    private static boolean isInLocalFocusMode(WindowManager.LayoutParams windowAttribute) {
+        return (windowAttribute.flags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0;
+    }
+
+    int onProcessImeInputStage(Object token, InputEvent event,
+            WindowManager.LayoutParams windowAttribute,
+            InputMethodManager.FinishedInputEventCallback callback) {
+        if (!mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
+            return InputMethodManager.DISPATCH_NOT_HANDLED;
+        }
+        final InputMethodManager imm =
+                mViewRootImpl.mContext.getSystemService(InputMethodManager.class);
+        if (imm == null) {
+            return InputMethodManager.DISPATCH_NOT_HANDLED;
+        }
+        return imm.dispatchInputEvent(event, token, callback, mViewRootImpl.mHandler);
+    }
+
+    /**
+     * A delegate implementing some basic {@link InputMethodManager} APIs.
+     * @hide
+     */
+    public interface InputMethodManagerDelegate {
+        boolean startInput(@StartInputReason int startInputReason, View focusedView,
+                @StartInputFlags int startInputFlags,
+                @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags);
+        void startInputAsyncOnWindowFocusGain(View rootView,
+                @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags,
+                boolean forceNewFocus);
+        void finishInput();
+        void closeCurrentIme();
+        void finishComposingText();
+        void setCurrentRootView(ViewRootImpl rootView);
+        boolean isCurrentRootView(ViewRootImpl rootView);
+        boolean isRestartOnNextWindowFocus(boolean reset);
+    }
+
+    public View getServedView() {
+        return mServedView;
+    }
+
+    public View getNextServedView() {
+        return mNextServedView;
+    }
+
+    public void setServedView(View view) {
+        mServedView = view;
+    }
+
+    public void setNextServedView(View view) {
+        mNextServedView = view;
+    }
+}
diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java
index c67ff6e..7986ceb 100644
--- a/core/java/android/view/InputEventReceiver.java
+++ b/core/java/android/view/InputEventReceiver.java
@@ -128,6 +128,19 @@
     }
 
     /**
+     * Called when a focus event is received.
+     *
+     * @param hasFocus if true, the window associated with this input channel has just received
+     *                 focus
+     *                 if false, the window associated with this input channel has just lost focus
+     * @param inTouchMode if true, the device is in touch mode
+     *                    if false, the device is not in touch mode
+     */
+    // Called from native code.
+    public void onFocusEvent(boolean hasFocus, boolean inTouchMode) {
+    }
+
+    /**
      * Called when a batched input event is pending.
      *
      * The batched input event will continue to accumulate additional movement
@@ -213,8 +226,13 @@
         onBatchedInputEventPending();
     }
 
-    public static interface Factory {
-        public InputEventReceiver createInputEventReceiver(
-                InputChannel inputChannel, Looper looper);
+    /**
+     * Factory for InputEventReceiver
+     */
+    public interface Factory {
+        /**
+         * Create a new InputReceiver for a given inputChannel
+         */
+        InputEventReceiver createInputEventReceiver(InputChannel inputChannel, Looper looper);
     }
 }
diff --git a/core/java/android/view/InsetsAnimationControlCallbacks.java b/core/java/android/view/InsetsAnimationControlCallbacks.java
index 6fdadc6..27edb0b 100644
--- a/core/java/android/view/InsetsAnimationControlCallbacks.java
+++ b/core/java/android/view/InsetsAnimationControlCallbacks.java
@@ -16,17 +16,28 @@
 
 package android.view;
 
+import android.view.InsetsController.LayoutInsetsDuringAnimation;
+import android.view.WindowInsetsAnimationCallback.AnimationBounds;
+import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
+
 /**
  * Provide an interface to let InsetsAnimationControlImpl call back into its owner.
  * @hide
  */
 public interface InsetsAnimationControlCallbacks {
+
     /**
-     * Dispatch the animation started event to all listeners.
-     * @param animation
+     * Executes the necessary code to start the animation in the correct order, including:
+     * <ul>
+     *     <li>Dispatch {@link WindowInsetsAnimationCallback#onPrepare}</li>
+     *     <li>Update insets state and run layout according to {@code layoutDuringAnimation}</li>
+     *     <li>Dispatch {@link WindowInsetsAnimationCallback#onStart}</li>
+     *     <li>Dispatch {@link WindowInsetsAnimationControlListener#onReady}</li>
+     * </ul>
      */
-    void dispatchAnimationStarted(WindowInsetsAnimationCallback.InsetsAnimation animation,
-            WindowInsetsAnimationCallback.AnimationBounds bounds);
+    void startAnimation(InsetsAnimationControlImpl controller,
+            WindowInsetsAnimationControlListener listener, int types, InsetsAnimation animation,
+            AnimationBounds bounds, @LayoutInsetsDuringAnimation int layoutDuringAnimation);
 
     /**
      * Schedule the apply by posting the animation callback.
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 771695c..405eccd 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
+import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
 import static android.view.InsetsState.ISIDE_BOTTOM;
 import static android.view.InsetsState.ISIDE_FLOATING;
 import static android.view.InsetsState.ISIDE_LEFT;
@@ -30,12 +32,15 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.SparseSetArray;
+import android.view.InsetsController.LayoutInsetsDuringAnimation;
 import android.view.InsetsState.InternalInsetsSide;
+import android.view.InsetsState.InternalInsetsType;
 import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsAnimationCallback.AnimationBounds;
 import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
 import android.view.WindowManager.LayoutParams;
+import android.view.animation.Interpolator;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -80,7 +85,8 @@
     public InsetsAnimationControlImpl(SparseArray<InsetsSourceControl> controls, Rect frame,
             InsetsState state, WindowInsetsAnimationControlListener listener,
             @InsetsType int types,
-            InsetsAnimationControlCallbacks controller, long durationMs, boolean fade) {
+            InsetsAnimationControlCallbacks controller, long durationMs, Interpolator interpolator,
+            boolean fade, @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) {
         mControls = controls;
         mListener = listener;
         mTypes = types;
@@ -88,6 +94,7 @@
         mController = controller;
         mInitialInsetsState = new InsetsState(state, true /* copySources */);
         mCurrentInsets = getInsetsFromState(mInitialInsetsState, frame, null /* typeSideMap */);
+        mPendingInsets = mCurrentInsets;
         mHiddenInsets = calculateInsets(mInitialInsetsState, frame, controls, false /* shown */,
                 null /* typeSideMap */);
         mShownInsets = calculateInsets(mInitialInsetsState, frame, controls, true /* shown */,
@@ -95,14 +102,11 @@
         mFrame = new Rect(frame);
         buildTypeSourcesMap(mTypeSideMap, mSideSourceMap, mControls);
 
-        // TODO: Check for controllability first and wait for IME if needed.
-        listener.onReady(this, types);
-
-        mAnimation = new WindowInsetsAnimationCallback.InsetsAnimation(mTypes,
-                InsetsController.INTERPOLATOR, durationMs);
+        mAnimation = new WindowInsetsAnimationCallback.InsetsAnimation(mTypes, interpolator,
+                durationMs);
         mAnimation.setAlpha(getCurrentAlpha());
-        mController.dispatchAnimationStarted(mAnimation,
-                new AnimationBounds(mHiddenInsets, mShownInsets));
+        mController.startAnimation(this, listener, types, mAnimation,
+                new AnimationBounds(mHiddenInsets, mShownInsets), layoutInsetsDuringAnimation);
     }
 
     @Override
@@ -130,6 +134,10 @@
         return mTypes;
     }
 
+    boolean controlsInternalType(@InternalInsetsType int type) {
+        return InsetsState.toInternalType(mTypes).contains(type);
+    }
+
     @Override
     public void setInsetsAndAlpha(Insets insets, float alpha, float fraction) {
         if (mFinished) {
@@ -189,7 +197,7 @@
             state.getSource(control.getType()).setVisible(shown);
         }
         Insets insets = getInsetsFromState(state, mFrame, null /* typeSideMap */);
-        setInsetsAndAlpha(insets, 1f /* alpha */, shown ? 1f : 0f /* fraction */);
+        setInsetsAndAlpha(insets, 1f /* alpha */, 1f /* fraction */);
         mFinished = true;
         mShownOnFinish = shown;
     }
@@ -232,7 +240,8 @@
         return state.calculateInsets(frame, false /* isScreenRound */,
                 false /* alwaysConsumeSystemBars */, null /* displayCutout */,
                 null /* legacyContentInsets */, null /* legacyStableInsets */,
-                LayoutParams.SOFT_INPUT_ADJUST_RESIZE /* legacySoftInputMode*/, typeSideMap)
+                LayoutParams.SOFT_INPUT_ADJUST_RESIZE /* legacySoftInputMode*/,
+                0 /* legacySystemUiFlags */, typeSideMap)
                .getInsets(mTypes);
     }
 
@@ -257,10 +266,6 @@
         for (int i = items.size() - 1; i >= 0; i--) {
             final InsetsSourceControl control = items.valueAt(i);
             final InsetsSource source = mInitialInsetsState.getSource(control.getType());
-            if (control == null) {
-                // TODO: remove this check when we ensure the elements will not be null.
-                continue;
-            }
             final SurfaceControl leash = control.getLeash();
 
             mTmpMatrix.setTranslate(control.getSurfacePosition().x, control.getSurfacePosition().y);
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 4d4ace27c..c6e3835 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -19,6 +19,8 @@
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.toPublicType;
 import static android.view.WindowInsets.Type.all;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_APPEARANCE_CONTROLLED;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -26,6 +28,7 @@
 import android.animation.TypeEvaluator;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.os.RemoteException;
@@ -37,16 +40,20 @@
 import android.view.InsetsSourceConsumer.ShowResult;
 import android.view.InsetsState.InternalInsetsType;
 import android.view.SurfaceControl.Transaction;
+import android.view.ViewTreeObserver.OnPreDrawListener;
 import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsAnimationCallback.AnimationBounds;
 import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
+import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
 
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 
 /**
@@ -57,14 +64,61 @@
 
     private static final int ANIMATION_DURATION_SHOW_MS = 275;
     private static final int ANIMATION_DURATION_HIDE_MS = 340;
-    private static final int DIRECTION_NONE = 0;
-    private static final int DIRECTION_SHOW = 1;
-    private static final int DIRECTION_HIDE = 2;
 
     static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
 
-    @IntDef ({DIRECTION_NONE, DIRECTION_SHOW, DIRECTION_HIDE})
-    private @interface AnimationDirection{}
+    /**
+     * Layout mode during insets animation: The views should be laid out as if the changing inset
+     * types are fully shown. Before starting the animation, {@link View#onApplyWindowInsets} will
+     * be called as if the changing insets types are shown, which will result in the views being
+     * laid out as if the insets are fully shown.
+     */
+    static final int LAYOUT_INSETS_DURING_ANIMATION_SHOWN = 0;
+
+    /**
+     * Layout mode during insets animation: The views should be laid out as if the changing inset
+     * types are fully hidden. Before starting the animation, {@link View#onApplyWindowInsets} will
+     * be called as if the changing insets types are hidden, which will result in the views being
+     * laid out as if the insets are fully hidden.
+     */
+    static final int LAYOUT_INSETS_DURING_ANIMATION_HIDDEN = 1;
+
+    /**
+     * Determines the behavior of how the views should be laid out during an insets animation that
+     * is controlled by the application by calling {@link #controlWindowInsetsAnimation}.
+     * <p>
+     * When the animation is system-initiated, the layout mode is always chosen such that the
+     * pre-animation layout will represent the opposite of the starting state, i.e. when insets
+     * are appearing, {@link #LAYOUT_INSETS_DURING_ANIMATION_SHOWN} will be used. When insets
+     * are disappearing, {@link #LAYOUT_INSETS_DURING_ANIMATION_HIDDEN} will be used.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {LAYOUT_INSETS_DURING_ANIMATION_SHOWN,
+            LAYOUT_INSETS_DURING_ANIMATION_HIDDEN})
+    @interface LayoutInsetsDuringAnimation {
+    }
+
+    /** Not running an animation. */
+    @VisibleForTesting
+    public static final int ANIMATION_TYPE_NONE = -1;
+
+    /** Running animation will show insets */
+    @VisibleForTesting
+    public static final int ANIMATION_TYPE_SHOW = 0;
+
+    /** Running animation will hide insets */
+    @VisibleForTesting
+    public static final int ANIMATION_TYPE_HIDE = 1;
+
+    /** Running animation is controlled by user via {@link #controlWindowInsetsAnimation} */
+    @VisibleForTesting
+    public static final int ANIMATION_TYPE_USER = 2;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {ANIMATION_TYPE_NONE, ANIMATION_TYPE_SHOW, ANIMATION_TYPE_HIDE,
+            ANIMATION_TYPE_USER})
+    @interface AnimationType {
+    }
 
     /**
      * Translation animation evaluator.
@@ -92,29 +146,29 @@
             controller.setInsetsAndAlpha(
                     value, 1f /* alpha */, (((DefaultAnimationControlListener)
                             ((InsetsAnimationControlImpl) controller).getListener())
-                                    .getRawProgress()));
+                                    .getRawFraction()));
         }
     }
 
-    private class DefaultAnimationControlListener implements WindowInsetsAnimationControlListener {
+    /**
+     * The default implementation of listener, to be used by InsetsController and InsetsPolicy to
+     * animate insets.
+     */
+    public static class InternalAnimationControlListener
+            implements WindowInsetsAnimationControlListener {
 
         private WindowInsetsAnimationController mController;
         private ObjectAnimator mAnimator;
-        private boolean mShow;
+        protected boolean mShow;
 
-        DefaultAnimationControlListener(boolean show) {
+        InternalAnimationControlListener(boolean show) {
             mShow = show;
         }
 
         @Override
         public void onReady(WindowInsetsAnimationController controller, int types) {
             mController = controller;
-            if (mShow) {
-                showDirectly(types);
-            } else {
-                hideDirectly(types);
-            }
-            mAnimationDirection = mShow ? DIRECTION_SHOW : DIRECTION_HIDE;
+
             mAnimator = ObjectAnimator.ofObject(
                     controller,
                     new InsetsProperty(),
@@ -131,7 +185,9 @@
                     onAnimationFinish();
                 }
             });
+            setStartingAnimation(true);
             mAnimator.start();
+            setStartingAnimation(false);
         }
 
         @Override
@@ -142,17 +198,18 @@
             }
         }
 
-        private void onAnimationFinish() {
-            mAnimationDirection = DIRECTION_NONE;
+        protected void setStartingAnimation(boolean startingAnimation) {
+        }
+
+        protected void onAnimationFinish() {
             mController.finish(mShow);
         }
 
-        private float getRawProgress() {
-            float fraction = (float) mAnimator.getCurrentPlayTime() / mAnimator.getDuration();
-            return mShow ? fraction : 1 - fraction;
+        protected float getRawFraction() {
+            return (float) mAnimator.getCurrentPlayTime() / mAnimator.getDuration();
         }
 
-        private long getDurationMs() {
+        protected long getDurationMs() {
             if (mAnimator != null) {
                 return mAnimator.getDuration();
             }
@@ -160,6 +217,31 @@
         }
     }
 
+    /**
+     * Represents a running animation
+     */
+    private static class RunningAnimation {
+
+        RunningAnimation(InsetsAnimationControlImpl control, int type) {
+            this.control = control;
+            this.type = type;
+        }
+
+        final InsetsAnimationControlImpl control;
+        final @AnimationType int type;
+    }
+
+    private class DefaultAnimationControlListener extends InternalAnimationControlListener {
+        DefaultAnimationControlListener(boolean show) {
+            super(show);
+        }
+
+        @Override
+        protected void setStartingAnimation(boolean startingAnimation) {
+            mStartingAnimation = startingAnimation;
+        }
+    }
+
     private final String TAG = "InsetsControllerImpl";
 
     private final InsetsState mState = new InsetsState();
@@ -170,7 +252,7 @@
     private final ViewRootImpl mViewRoot;
 
     private final SparseArray<InsetsSourceControl> mTmpControlArray = new SparseArray<>();
-    private final ArrayList<InsetsAnimationControlImpl> mAnimationControls = new ArrayList<>();
+    private final ArrayList<RunningAnimation> mRunningAnimations = new ArrayList<>();
     private final ArrayList<InsetsAnimationControlImpl> mTmpFinishedControls = new ArrayList<>();
     private WindowInsets mLastInsets;
 
@@ -180,11 +262,12 @@
 
     private final Rect mLastLegacyContentInsets = new Rect();
     private final Rect mLastLegacyStableInsets = new Rect();
-    private @AnimationDirection int mAnimationDirection;
 
     private int mPendingTypesToShow;
 
     private int mLastLegacySoftInputMode;
+    private int mLastLegacySystemUiFlags;
+    private boolean mStartingAnimation;
 
     private SyncRtSurfaceTransactionApplier mApplier;
 
@@ -192,7 +275,7 @@
         mViewRoot = viewRoot;
         mAnimCallback = () -> {
             mAnimCallbackScheduled = false;
-            if (mAnimationControls.isEmpty()) {
+            if (mRunningAnimations.isEmpty()) {
                 return;
             }
             if (mViewRoot.mView == null) {
@@ -202,9 +285,9 @@
 
             mTmpFinishedControls.clear();
             InsetsState state = new InsetsState(mState, true /* copySources */);
-            for (int i = mAnimationControls.size() - 1; i >= 0; i--) {
-                InsetsAnimationControlImpl control = mAnimationControls.get(i);
-                if (mAnimationControls.get(i).applyChangeInsets(state)) {
+            for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
+                InsetsAnimationControlImpl control = mRunningAnimations.get(i).control;
+                if (control.applyChangeInsets(state)) {
                     mTmpFinishedControls.add(control);
                 }
             }
@@ -212,7 +295,7 @@
             WindowInsets insets = state.calculateInsets(mFrame, mLastInsets.isRound(),
                     mLastInsets.shouldAlwaysConsumeSystemBars(), mLastInsets.getDisplayCutout(),
                     mLastLegacyContentInsets, mLastLegacyStableInsets, mLastLegacySoftInputMode,
-                    null /* typeSideMap */);
+                    mLastLegacySystemUiFlags, null /* typeSideMap */);
             mViewRoot.mView.dispatchWindowInsetsAnimationProgress(insets);
 
             for (int i = mTmpFinishedControls.size() - 1; i >= 0; i--) {
@@ -255,17 +338,26 @@
     @VisibleForTesting
     public WindowInsets calculateInsets(boolean isScreenRound,
             boolean alwaysConsumeSystemBars, DisplayCutout cutout, Rect legacyContentInsets,
-            Rect legacyStableInsets, int legacySoftInputMode) {
+            Rect legacyStableInsets, int legacySoftInputMode, int legacySystemUiFlags) {
         mLastLegacyContentInsets.set(legacyContentInsets);
         mLastLegacyStableInsets.set(legacyStableInsets);
         mLastLegacySoftInputMode = legacySoftInputMode;
+        mLastLegacySystemUiFlags = legacySystemUiFlags;
         mLastInsets = mState.calculateInsets(mFrame, isScreenRound, alwaysConsumeSystemBars, cutout,
-                legacyContentInsets, legacyStableInsets, legacySoftInputMode,
+                legacyContentInsets, legacyStableInsets, legacySoftInputMode, legacySystemUiFlags,
                 null /* typeSideMap */);
         return mLastInsets;
     }
 
     /**
+     * @see InsetsState#calculateVisibleInsets(Rect, Rect, int)
+     */
+    public Rect calculateVisibleInsets(Rect legacyVisibleInsets,
+            @SoftInputModeFlags int softInputMode) {
+        return mState.calculateVisibleInsets(mFrame, legacyVisibleInsets, softInputMode);
+    }
+
+    /**
      * Called when the server has dispatched us a new set of inset controls.
      */
     public void onControlsChanged(InsetsSourceControl[] activeControls) {
@@ -307,18 +399,13 @@
         int typesReady = 0;
         final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
         for (int i = internalTypes.size() - 1; i >= 0; i--) {
-            InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
-            if (mAnimationDirection == DIRECTION_HIDE) {
-                // Only one animator (with multiple InsetsType) can run at a time.
-                // previous one should be cancelled for simplicity.
-                cancelExistingAnimation();
-            } else if (consumer.isVisible()
-                    && (mAnimationDirection == DIRECTION_NONE
-                    || mAnimationDirection == DIRECTION_HIDE)) {
+            @InternalInsetsType int internalType = internalTypes.valueAt(i);
+            @AnimationType int animationType = getAnimationType(internalType);
+            InsetsSourceConsumer consumer = getSourceConsumer(internalType);
+            if (mState.getSource(internalType).isVisible() && animationType == ANIMATION_TYPE_NONE
+                    || animationType == ANIMATION_TYPE_SHOW) {
                 // no-op: already shown or animating in (because window visibility is
                 // applied before starting animation).
-                // TODO: When we have more than one types: handle specific case when
-                // show animation is going on, but the current type is not becoming visible.
                 continue;
             }
             typesReady |= InsetsState.toPublicType(consumer.getType());
@@ -335,12 +422,11 @@
         int typesReady = 0;
         final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
         for (int i = internalTypes.size() - 1; i >= 0; i--) {
-            InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
-            if (mAnimationDirection == DIRECTION_SHOW) {
-                cancelExistingAnimation();
-            } else if (!consumer.isVisible()
-                    && (mAnimationDirection == DIRECTION_NONE
-                    || mAnimationDirection == DIRECTION_HIDE)) {
+            @InternalInsetsType int internalType = internalTypes.valueAt(i);
+            @AnimationType int animationType = getAnimationType(internalType);
+            InsetsSourceConsumer consumer = getSourceConsumer(internalType);
+            if (!mState.getSource(internalType).isVisible() && animationType == ANIMATION_TYPE_NONE
+                    || animationType == ANIMATION_TYPE_HIDE) {
                 // no-op: already hidden or animating out.
                 continue;
             }
@@ -351,32 +437,37 @@
 
     @Override
     public void controlWindowInsetsAnimation(@InsetsType int types, long durationMs,
-            WindowInsetsAnimationControlListener listener) {
-        controlWindowInsetsAnimation(types, listener, false /* fromIme */, durationMs);
+            @Nullable Interpolator interpolator,
+            @NonNull WindowInsetsAnimationControlListener listener) {
+        controlWindowInsetsAnimation(types, listener, false /* fromIme */, durationMs, interpolator,
+                ANIMATION_TYPE_USER);
     }
 
     private void controlWindowInsetsAnimation(@InsetsType int types,
-            WindowInsetsAnimationControlListener listener, boolean fromIme, long durationMs) {
+            WindowInsetsAnimationControlListener listener, boolean fromIme, long durationMs,
+            @Nullable Interpolator interpolator, @AnimationType int animationType) {
         // If the frame of our window doesn't span the entire display, the control API makes very
         // little sense, as we don't deal with negative insets. So just cancel immediately.
         if (!mState.getDisplayFrame().equals(mFrame)) {
             listener.onCancelled();
             return;
         }
-        controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, false /* fade */);
+        controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, interpolator,
+                false /* fade */, animationType, getLayoutInsetsDuringAnimationMode(types));
     }
 
     private void controlAnimationUnchecked(@InsetsType int types,
             WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme,
-            long durationMs, boolean fade) {
+            long durationMs, Interpolator interpolator, boolean fade,
+            @AnimationType int animationType,
+            @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) {
         if (types == 0) {
             // nothing to animate.
             return;
         }
         cancelExistingControllers(types);
 
-        final ArraySet<Integer> internalTypes = mState.toInternalType(types);
-        final SparseArray<InsetsSourceConsumer> consumers = new SparseArray<>();
+        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
         final SparseArray<InsetsSourceControl> controls = new SparseArray<>();
 
         Pair<Integer, Boolean> typesReadyPair = collectSourceControls(
@@ -399,8 +490,9 @@
         }
 
         final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(controls,
-                frame, mState, listener, typesReady, this, durationMs, fade);
-        mAnimationControls.add(controller);
+                frame, mState, listener, typesReady, this, durationMs, interpolator, fade,
+                layoutInsetsDuringAnimation);
+        mRunningAnimations.add(new RunningAnimation(controller, animationType));
     }
 
     /**
@@ -413,7 +505,7 @@
         boolean isReady = true;
         for (int i = internalTypes.size() - 1; i >= 0; i--) {
             InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
-            boolean setVisible = !consumer.isVisible();
+            boolean setVisible = !consumer.isRequestedVisible();
             if (setVisible) {
                 // Show request
                 switch(consumer.requestShow(fromIme)) {
@@ -441,7 +533,10 @@
                 }
                 typesReady |= InsetsState.toPublicType(consumer.getType());
             }
-            controls.put(consumer.getType(), consumer.getControl());
+            final InsetsSourceControl control = consumer.getControl();
+            if (control != null) {
+                controls.put(consumer.getType(), control);
+            }
         }
         return new Pair<>(typesReady, isReady);
     }
@@ -452,11 +547,34 @@
         return typesReady;
     }
 
+    private @LayoutInsetsDuringAnimation int getLayoutInsetsDuringAnimationMode(
+            @InsetsType int types) {
+
+        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
+
+        // Generally, we want to layout the opposite of the current state. This is to make animation
+        // callbacks easy to use: The can capture the layout values and then treat that as end-state
+        // during the animation.
+        //
+        // However, if controlling multiple sources, we want to treat it as shown if any of the
+        // types is currently hidden.
+        for (int i = internalTypes.size() - 1; i >= 0; i--) {
+            InsetsSourceConsumer consumer = mSourceConsumers.get(internalTypes.valueAt(i));
+            if (consumer == null) {
+                continue;
+            }
+            if (!consumer.isRequestedVisible()) {
+                return LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
+            }
+        }
+        return LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
+    }
+
     private void cancelExistingControllers(@InsetsType int types) {
-        for (int i = mAnimationControls.size() - 1; i >= 0; i--) {
-            InsetsAnimationControlImpl control = mAnimationControls.get(i);
+        for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
+            InsetsAnimationControlImpl control = mRunningAnimations.get(i).control;
             if ((control.getTypes() & types) != 0) {
-                cancelAnimation(control);
+                cancelAnimation(control, true /* invokeCallback */);
             }
         }
     }
@@ -464,7 +582,7 @@
     @VisibleForTesting
     @Override
     public void notifyFinished(InsetsAnimationControlImpl controller, boolean shown) {
-        mAnimationControls.remove(controller);
+        cancelAnimation(controller, false /* invokeCallback */);
         if (shown) {
             showDirectly(controller.getTypes());
         } else {
@@ -484,17 +602,24 @@
     }
 
     void notifyControlRevoked(InsetsSourceConsumer consumer) {
-        for (int i = mAnimationControls.size() - 1; i >= 0; i--) {
-            InsetsAnimationControlImpl control = mAnimationControls.get(i);
+        for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
+            InsetsAnimationControlImpl control = mRunningAnimations.get(i).control;
             if ((control.getTypes() & toPublicType(consumer.getType())) != 0) {
-                cancelAnimation(control);
+                cancelAnimation(control, true /* invokeCallback */);
             }
         }
     }
 
-    private void cancelAnimation(InsetsAnimationControlImpl control) {
-        control.onCancelled();
-        mAnimationControls.remove(control);
+    private void cancelAnimation(InsetsAnimationControlImpl control, boolean invokeCallback) {
+        if (invokeCallback) {
+            control.onCancelled();
+        }
+        for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
+            if (mRunningAnimations.get(i).control == control) {
+                mRunningAnimations.remove(i);
+                break;
+            }
+        }
     }
 
     private void applyLocalVisibilityOverride() {
@@ -522,6 +647,14 @@
     }
 
     /**
+     * @see ViewRootImpl#updateCompatSysUiVisibility(int, boolean, boolean)
+     */
+    public void updateCompatSysUiVisibility(@InternalInsetsType int type, boolean visible,
+            boolean hasControl) {
+        mViewRoot.updateCompatSysUiVisibility(type, visible, hasControl);
+    }
+
+    /**
      * Called when current window gains focus.
      */
     public void onWindowFocusGained() {
@@ -552,8 +685,15 @@
         }
     }
 
-    boolean isAnimating() {
-        return mAnimationDirection != DIRECTION_NONE;
+    @VisibleForTesting
+    public @AnimationType int getAnimationType(@InternalInsetsType int type) {
+        for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
+            InsetsAnimationControlImpl control = mRunningAnimations.get(i).control;
+            if (control.controlsInternalType(type)) {
+                return mRunningAnimations.get(i).type;
+            }
+        }
+        return ANIMATION_TYPE_NONE;
     }
 
     private InsetsSourceConsumer createConsumerOfType(int type) {
@@ -595,7 +735,9 @@
         // and hidden state insets are correct.
         controlAnimationUnchecked(
                 types, listener, mState.getDisplayFrame(), fromIme, listener.getDurationMs(),
-                true /* fade */);
+                INTERPOLATOR, true /* fade */, show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE,
+                show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN
+                        : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN);
     }
 
     private void hideDirectly(@InsetsType int types) {
@@ -627,18 +769,40 @@
 
     @VisibleForTesting
     @Override
-    public void dispatchAnimationStarted(InsetsAnimation animation, AnimationBounds bounds) {
-        mViewRoot.mView.dispatchWindowInsetsAnimationStarted(animation, bounds);
+    public void startAnimation(InsetsAnimationControlImpl controller,
+            WindowInsetsAnimationControlListener listener, int types, InsetsAnimation animation,
+            AnimationBounds bounds, int layoutDuringAnimation) {
+        if (layoutDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN) {
+            showDirectly(types);
+        } else {
+            hideDirectly(types);
+        }
+        mViewRoot.mView.dispatchWindowInsetsAnimationPrepare(animation);
+        mViewRoot.mView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
+            @Override
+            public boolean onPreDraw() {
+                mViewRoot.mView.getViewTreeObserver().removeOnPreDrawListener(this);
+                mViewRoot.mView.dispatchWindowInsetsAnimationStart(animation, bounds);
+                listener.onReady(controller, types);
+                return true;
+            }
+        });
+        mViewRoot.mView.invalidate();
     }
 
     @VisibleForTesting
     public void dispatchAnimationFinished(InsetsAnimation animation) {
-        mViewRoot.mView.dispatchWindowInsetsAnimationFinished(animation);
+        mViewRoot.mView.dispatchWindowInsetsAnimationFinish(animation);
     }
 
     @VisibleForTesting
     @Override
     public void scheduleApplyChangeInsets() {
+        if (mStartingAnimation) {
+            mAnimCallback.run();
+            mAnimCallbackScheduled = false;
+            return;
+        }
         if (!mAnimCallbackScheduled) {
             mViewRoot.mChoreographer.postCallback(Choreographer.CALLBACK_INSETS_ANIMATION,
                     mAnimCallback, null /* token*/);
@@ -647,20 +811,41 @@
     }
 
     @Override
-    public void setSystemBarsAppearance(@Appearance int appearance) {
-        if (mViewRoot.mWindowAttributes.insetsFlags.appearance != appearance) {
-            mViewRoot.mWindowAttributes.insetsFlags.appearance = appearance;
+    public void setSystemBarsAppearance(@Appearance int appearance, @Appearance int mask) {
+        mViewRoot.mWindowAttributes.privateFlags |= PRIVATE_FLAG_APPEARANCE_CONTROLLED;
+        final InsetsFlags insetsFlags = mViewRoot.mWindowAttributes.insetsFlags;
+        if (insetsFlags.appearance != appearance) {
+            insetsFlags.appearance = (insetsFlags.appearance & ~mask) | (appearance & mask);
             mViewRoot.mWindowAttributesChanged = true;
             mViewRoot.scheduleTraversals();
         }
     }
 
     @Override
+    public @Appearance int getSystemBarsAppearance() {
+        if ((mViewRoot.mWindowAttributes.privateFlags & PRIVATE_FLAG_APPEARANCE_CONTROLLED) == 0) {
+            // We only return the requested appearance, not the implied one.
+            return 0;
+        }
+        return mViewRoot.mWindowAttributes.insetsFlags.appearance;
+    }
+
+    @Override
     public void setSystemBarsBehavior(@Behavior int behavior) {
+        mViewRoot.mWindowAttributes.privateFlags |= PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
         if (mViewRoot.mWindowAttributes.insetsFlags.behavior != behavior) {
             mViewRoot.mWindowAttributes.insetsFlags.behavior = behavior;
             mViewRoot.mWindowAttributesChanged = true;
             mViewRoot.scheduleTraversals();
         }
     }
+
+    @Override
+    public @Appearance int getSystemBarsBehavior() {
+        if ((mViewRoot.mWindowAttributes.privateFlags & PRIVATE_FLAG_BEHAVIOR_CONTROLLED) == 0) {
+            // We only return the requested behavior, not the implied one.
+            return 0;
+        }
+        return mViewRoot.mWindowAttributes.insetsFlags.behavior;
+    }
 }
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 324d562..67ccfd6 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.annotation.Nullable;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.os.Parcel;
@@ -23,6 +24,7 @@
 import android.view.InsetsState.InternalInsetsType;
 
 import java.io.PrintWriter;
+import java.util.Objects;
 
 /**
  * Represents the state of a single window generating insets for clients.
@@ -34,6 +36,7 @@
 
     /** Frame of the source in screen coordinate space */
     private final Rect mFrame;
+    private @Nullable Rect mVisibleFrame;
     private boolean mVisible;
 
     private final Rect mTmpFrame = new Rect();
@@ -54,6 +57,10 @@
         mFrame.set(frame);
     }
 
+    public void setVisibleFrame(@Nullable Rect visibleFrame) {
+        mVisibleFrame = visibleFrame != null ? new Rect(visibleFrame) : visibleFrame;
+    }
+
     public void setVisible(boolean visible) {
         mVisible = visible;
     }
@@ -66,6 +73,10 @@
         return mFrame;
     }
 
+    public @Nullable Rect getVisibleFrame() {
+        return mVisibleFrame;
+    }
+
     public boolean isVisible() {
         return mVisible;
     }
@@ -79,10 +90,22 @@
      *         source.
      */
     public Insets calculateInsets(Rect relativeFrame, boolean ignoreVisibility) {
+        return calculateInsets(relativeFrame, mFrame, ignoreVisibility);
+    }
+
+    /**
+     * Like {@link #calculateInsets(Rect, boolean)}, but will return visible insets.
+     */
+    public Insets calculateVisibleInsets(Rect relativeFrame) {
+        return calculateInsets(relativeFrame, mVisibleFrame != null ? mVisibleFrame : mFrame,
+                false /* ignoreVisibility */);
+    }
+
+    private Insets calculateInsets(Rect relativeFrame, Rect frame, boolean ignoreVisibility) {
         if (!ignoreVisibility && !mVisible) {
             return Insets.NONE;
         }
-        if (!mTmpFrame.setIntersect(mFrame, relativeFrame)) {
+        if (!mTmpFrame.setIntersect(frame, relativeFrame)) {
             return Insets.NONE;
         }
 
@@ -110,6 +133,9 @@
         pw.print(prefix);
         pw.print("InsetsSource type="); pw.print(InsetsState.typeToString(mType));
         pw.print(" frame="); pw.print(mFrame.toShortString());
+        if (mVisibleFrame != null) {
+            pw.print(" visibleFrmae="); pw.print(mVisibleFrame.toShortString());
+        }
         pw.print(" visible="); pw.print(mVisible);
         pw.println();
     }
@@ -123,6 +149,7 @@
 
         if (mType != that.mType) return false;
         if (mVisible != that.mVisible) return false;
+        if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false;
         return mFrame.equals(that.mFrame);
     }
 
@@ -137,6 +164,7 @@
     public InsetsSource(Parcel in) {
         mType = in.readInt();
         mFrame = in.readParcelable(null /* loader */);
+        mVisibleFrame = in.readParcelable(null /* loader */);
         mVisible = in.readBoolean();
     }
 
@@ -149,6 +177,7 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mType);
         dest.writeParcelable(mFrame, 0 /* flags*/);
+        dest.writeParcelable(mVisibleFrame, 0 /* flags */);
         dest.writeBoolean(mVisible);
     }
 
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index c6d9898..35a82b8 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import static android.view.InsetsController.ANIMATION_TYPE_NONE;
+
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.view.InsetsState.InternalInsetsType;
@@ -53,7 +55,7 @@
     }
 
     protected final InsetsController mController;
-    protected boolean mVisible;
+    protected boolean mRequestedVisible;
     private final Supplier<Transaction> mTransactionSupplier;
     private final @InternalInsetsType int mType;
     private final InsetsState mState;
@@ -66,7 +68,7 @@
         mState = state;
         mTransactionSupplier = transactionSupplier;
         mController = controller;
-        mVisible = InsetsState.getDefaultVisibility(type);
+        mRequestedVisible = InsetsState.getDefaultVisibility(type);
     }
 
     public void setControl(@Nullable InsetsSourceControl control) {
@@ -94,12 +96,12 @@
 
     @VisibleForTesting
     public void show() {
-        setVisible(true);
+        setRequestedVisible(true);
     }
 
     @VisibleForTesting
     public void hide() {
-        setVisible(false);
+        setRequestedVisible(false);
     }
 
     /**
@@ -121,21 +123,28 @@
     }
 
     boolean applyLocalVisibilityOverride() {
+        final boolean isVisible = mState.getSource(mType).isVisible();
+        final boolean hasControl = mSourceControl != null;
+
+        // We still need to let the legacy app know the visibility change even if we don't have the
+        // control.
+        mController.updateCompatSysUiVisibility(
+                mType, hasControl ? mRequestedVisible : isVisible, hasControl);
 
         // If we don't have control, we are not able to change the visibility.
-        if (mSourceControl == null) {
+        if (!hasControl) {
             return false;
         }
-        if (mState.getSource(mType).isVisible() == mVisible) {
+        if (isVisible == mRequestedVisible) {
             return false;
         }
-        mState.getSource(mType).setVisible(mVisible);
+        mState.getSource(mType).setVisible(mRequestedVisible);
         return true;
     }
 
     @VisibleForTesting
-    public boolean isVisible() {
-        return mVisible;
+    public boolean isRequestedVisible() {
+        return mRequestedVisible;
     }
 
     /**
@@ -157,23 +166,27 @@
         // no-op for types that always return ShowResult#SHOW_IMMEDIATELY.
     }
 
-    private void setVisible(boolean visible) {
-        if (mVisible == visible) {
+    /**
+     * Sets requested visibility from the client, regardless of whether we are able to control it at
+     * the moment.
+     */
+    private void setRequestedVisible(boolean requestedVisible) {
+        if (mRequestedVisible == requestedVisible) {
             return;
         }
-        mVisible = visible;
+        mRequestedVisible = requestedVisible;
         applyLocalVisibilityOverride();
         mController.notifyVisibilityChanged();
     }
 
     private void applyHiddenToControl() {
         if (mSourceControl == null || mSourceControl.getLeash() == null
-                || mController.isAnimating()) {
+                || mController.getAnimationType(mType) != ANIMATION_TYPE_NONE) {
             return;
         }
 
         final Transaction t = mTransactionSupplier.get();
-        if (mVisible) {
+        if (mRequestedVisible) {
             t.show(mSourceControl.getLeash());
         } else {
             t.hide(mSourceControl.getLeash());
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index ae1e579..bd19799 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -16,15 +16,18 @@
 
 package android.view;
 
+import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME;
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
+import static android.view.ViewRootImpl.sNewInsetsMode;
 import static android.view.WindowInsets.Type.IME;
 import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES;
 import static android.view.WindowInsets.Type.SIZE;
 import static android.view.WindowInsets.Type.SYSTEM_GESTURES;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.indexOf;
+import static android.view.WindowInsets.Type.isVisibleInsetsType;
 import static android.view.WindowInsets.Type.systemBars;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
@@ -41,6 +44,7 @@
 import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowManager.LayoutParams;
+import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -140,7 +144,8 @@
     public WindowInsets calculateInsets(Rect frame, boolean isScreenRound,
             boolean alwaysConsumeSystemBars, DisplayCutout cutout,
             @Nullable Rect legacyContentInsets, @Nullable Rect legacyStableInsets,
-            int legacySoftInputMode, @Nullable @InternalInsetsSide SparseIntArray typeSideMap) {
+            int legacySoftInputMode, int legacySystemUiFlags,
+            @Nullable @InternalInsetsSide SparseIntArray typeSideMap) {
         Insets[] typeInsetsMap = new Insets[Type.SIZE];
         Insets[] typeMaxInsetsMap = new Insets[Type.SIZE];
         boolean[] typeVisibilityMap = new boolean[SIZE];
@@ -183,7 +188,35 @@
         return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound,
                 alwaysConsumeSystemBars, cutout, softInputAdjustMode == SOFT_INPUT_ADJUST_RESIZE
                         ? systemBars() | ime()
-                        : systemBars());
+                        : systemBars(),
+                sNewInsetsMode == NEW_INSETS_MODE_FULL
+                        && (legacySystemUiFlags & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0);
+    }
+
+    public Rect calculateVisibleInsets(Rect frame, Rect legacyVisibleInsets,
+            @SoftInputModeFlags int softInputMode) {
+        if (sNewInsetsMode == NEW_INSETS_MODE_NONE) {
+            return legacyVisibleInsets;
+        }
+
+        Insets insets = Insets.NONE;
+        for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
+            InsetsSource source = mSources.get(type);
+            if (source == null) {
+                continue;
+            }
+            if (sNewInsetsMode != NEW_INSETS_MODE_FULL && type != ITYPE_IME) {
+                continue;
+            }
+
+            // Ignore everything that's not a system bar or IME.
+            int publicType = InsetsState.toPublicType(type);
+            if (!isVisibleInsetsType(publicType, softInputMode)) {
+                continue;
+            }
+            insets = Insets.max(source.calculateVisibleInsets(frame), insets);
+        }
+        return insets.toRect();
     }
 
     private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility,
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 7707ad1..a6b7c33 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -88,6 +89,8 @@
     private static native int nativeSetSharedBufferModeEnabled(long nativeObject, boolean enabled);
     private static native int nativeSetAutoRefreshEnabled(long nativeObject, boolean enabled);
 
+    private static native int nativeSetFrameRate(long nativeObject, float frameRate);
+
     public static final @android.annotation.NonNull Parcelable.Creator<Surface> CREATOR =
             new Parcelable.Creator<Surface>() {
         @Override
@@ -841,6 +844,34 @@
     }
 
     /**
+     * Sets the intended frame rate for this surface.
+     *
+     * On devices that are capable of running the display at different refresh rates, the
+     * system may choose a display refresh rate to better match this surface's frame
+     * rate. Usage of this API won't introduce frame rate throttling, or affect other
+     * aspects of the application's frame production pipeline. However, because the system
+     * may change the display refresh rate, calls to this function may result in changes
+     * to Choreographer callback timings, and changes to the time interval at which the
+     * system releases buffers back to the application.
+     *
+     * Note that this only has an effect for surfaces presented on the display. If this
+     * surface is consumed by something other than the system compositor, e.g. a media
+     * codec, this call has no effect.
+     *
+     * @param frameRate The intended frame rate of this surface. 0 is a special value that
+     * indicates the app will accept the system's choice for the display frame rate, which
+     * is the default behavior if this function isn't called. The frameRate param does
+     * *not* need to be a valid refresh rate for this device's display - e.g., it's fine
+     * to pass 30fps to a device that can only run the display at 60fps.
+     */
+    public void setFrameRate(@FloatRange(from = 0.0) float frameRate) {
+        int error = nativeSetFrameRate(mNativeObject, frameRate);
+        if (error != 0) {
+            throw new RuntimeException("Failed to set frame rate on Surface");
+        }
+    }
+
+    /**
      * Exception thrown when a Canvas couldn't be locked with {@link Surface#lockCanvas}, or
      * when a SurfaceTexture could not successfully be allocated.
      */
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index f6d6522..f7b87cc 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -122,10 +122,14 @@
     private static native void nativeSetColor(long transactionObj, long nativeObject, float[] color);
     private static native void nativeSetFlags(long transactionObj, long nativeObject,
             int flags, int mask);
+    private static native void nativeSetFrameRateSelectionPriority(long transactionObj,
+            long nativeObject, int priority);
     private static native void nativeSetWindowCrop(long transactionObj, long nativeObject,
             int l, int t, int r, int b);
     private static native void nativeSetCornerRadius(long transactionObj, long nativeObject,
             float cornerRadius);
+    private static native void nativeSetBackgroundBlurRadius(long transactionObj, long nativeObject,
+            int blurRadius);
     private static native void nativeSetLayerStack(long transactionObj, long nativeObject,
             int layerStack);
 
@@ -157,7 +161,6 @@
     private static native DisplayedContentSample nativeGetDisplayedContentSample(
             IBinder displayToken, long numFrames, long timestamp);
     private static native int nativeGetActiveConfig(IBinder displayToken);
-    private static native boolean nativeSetActiveConfig(IBinder displayToken, int id);
     private static native boolean nativeSetDesiredDisplayConfigSpecs(IBinder displayToken,
             SurfaceControl.DesiredDisplayConfigSpecs desiredDisplayConfigSpecs);
     private static native SurfaceControl.DesiredDisplayConfigSpecs
@@ -208,6 +211,9 @@
     private static native void nativeSetGlobalShadowSettings(@Size(4) float[] ambientColor,
             @Size(4) float[] spotColor, float lightPosY, float lightPosZ, float lightRadius);
 
+    private static native void nativeSetFrameRate(
+            long transactionObj, long nativeObject, float frameRate);
+
     private final CloseGuard mCloseGuard = CloseGuard.get();
     private String mName;
     /**
@@ -677,6 +683,22 @@
         }
 
         /**
+         * Set the initial visibility for the SurfaceControl.
+         *
+         * @param hidden Whether the Surface is initially HIDDEN.
+         * @hide
+         */
+        @NonNull
+        public Builder setHidden(boolean hidden) {
+            if (hidden) {
+                mFlags |= HIDDEN;
+            } else {
+                mFlags &= ~HIDDEN;
+            }
+            return this;
+        }
+
+        /**
          * Set a parent surface for our new SurfaceControl.
          *
          * Child surfaces are constrained to the onscreen region of their parent.
@@ -789,8 +811,7 @@
      * @param name     The surface name, must not be null.
      * @param w        The surface initial width.
      * @param h        The surface initial height.
-     * @param flags    The surface creation flags.  Should always include {@link #HIDDEN}
-     *                 in the creation flags.
+     * @param flags    The surface creation flags.
      * @param metadata Initial metadata.
      * @throws throws OutOfResourcesException If the SurfaceControl cannot be created.
      */
@@ -801,15 +822,6 @@
             throw new IllegalArgumentException("name must not be null");
         }
 
-        if ((flags & SurfaceControl.HIDDEN) == 0) {
-            Log.w(TAG, "Surfaces should always be created with the HIDDEN flag set "
-                    + "to ensure that they are not made visible prematurely before "
-                    + "all of the surface's properties have been configured.  "
-                    + "Set the other properties and make the surface visible within "
-                    + "a transaction.  New surface name: " + name,
-                    new Throwable());
-        }
-
         mName = name;
         mWidth = w;
         mHeight = h;
@@ -1474,16 +1486,6 @@
 
 
     /**
-     * @hide
-     */
-    public static boolean setActiveConfig(IBinder displayToken, int id) {
-        if (displayToken == null) {
-            throw new IllegalArgumentException("displayToken must not be null");
-        }
-        return nativeSetActiveConfig(displayToken, id);
-    }
-
-    /**
      * Contains information about desired display configuration.
      *
      * @hide
@@ -2239,6 +2241,19 @@
         }
 
         /**
+         * This information is passed to SurfaceFlinger to decide which window should have a
+         * priority when deciding about the refresh rate of the display. All windows have the
+         * lowest priority by default.
+         * @hide
+         */
+        @NonNull
+        public Transaction setFrameRateSelectionPriority(@NonNull SurfaceControl sc, int priority) {
+            sc.checkNotReleased();
+            nativeSetFrameRateSelectionPriority(mNativeObject, sc.mNativeObject, priority);
+            return this;
+        }
+
+        /**
          * Request that a given surface and it's sub-tree be shown.
          *
          * @param sc The surface to show.
@@ -2495,6 +2510,20 @@
         }
 
         /**
+         * Sets the background blur radius of the {@link SurfaceControl}.
+         *
+         * @param sc SurfaceControl.
+         * @param radius Blur radius in pixels.
+         * @return itself.
+         * @hide
+         */
+        public Transaction setBackgroundBlurRadius(SurfaceControl sc, int radius) {
+            checkPreconditions(sc);
+            nativeSetBackgroundBlurRadius(mNativeObject, sc.mNativeObject, radius);
+            return this;
+        }
+
+        /**
          * @hide
          */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O)
@@ -2761,6 +2790,33 @@
         }
 
         /**
+         * Sets the intended frame rate for the surface {@link SurfaceControl}.
+         *
+         * On devices that are capable of running the display at different refresh rates, the system
+         * may choose a display refresh rate to better match this surface's frame rate. Usage of
+         * this API won't directly affect the application's frame production pipeline. However,
+         * because the system may change the display refresh rate, calls to this function may result
+         * in changes to Choreographer callback timings, and changes to the time interval at which
+         * the system releases buffers back to the application.
+         *
+         * @param sc The SurfaceControl to specify the frame rate of.
+         * @param frameRate The intended frame rate for this surface. 0 is a special value that
+         *                  indicates the app will accept the system's choice for the display frame
+         *                  rate, which is the default behavior if this function isn't called. The
+         *                  frameRate param does *not* need to be a valid refresh rate for this
+         *                  device's display - e.g., it's fine to pass 30fps to a device that can
+         *                  only run the display at 60fps.
+         * @return This transaction object.
+         */
+        @NonNull
+        public Transaction setFrameRate(
+                @NonNull SurfaceControl sc, @FloatRange(from = 0.0) float frameRate) {
+            checkPreconditions(sc);
+            nativeSetFrameRate(mNativeObject, sc.mNativeObject, frameRate);
+            return this;
+        }
+
+        /**
          * Merge the other transaction into this transaction, clearing the
          * other transaction as if it had been applied.
          *
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 4f8aecd..71cf051 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -20,15 +20,21 @@
 import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.content.Context;
+import android.graphics.PixelFormat;
 import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
 
 /**
- * Utility class for adding a view hierarchy to a SurfaceControl.
- *
- * See WindowlessWmTest for example usage.
- * @hide
+ * Utility class for adding a View hierarchy to a {@link SurfaceControl}. The View hierarchy
+ * will render in to a root SurfaceControl, and receive input based on the SurfaceControl's
+ * placement on-screen. The primary usage of this class is to embed a View hierarchy from
+ * one process in to another. After the SurfaceControlViewHost has been set up in the embedded
+ * content provider, we can send the {@link SurfaceControlViewHost.SurfacePackage}
+ * to the host process. The host process can then attach the hierarchy to a SurfaceView within
+ * its own by calling
+ * {@link SurfaceView#setChildSurfacePackage}.
  */
-@TestApi
 public class SurfaceControlViewHost {
     private ViewRootImpl mViewRoot;
     private WindowlessWindowManager mWm;
@@ -36,20 +42,52 @@
     private SurfaceControl mSurfaceControl;
 
     /**
-     * @hide
+     * Package encapsulating a Surface hierarchy which contains interactive view
+     * elements. It's expected to get this object from
+     * {@link SurfaceControlViewHost#getSurfacePackage} afterwards it can be embedded within
+     * a SurfaceView by calling {@link SurfaceView#setChildSurfacePackage}.
      */
-    @TestApi
-    public class SurfacePackage {
-        final SurfaceControl mSurfaceControl;
+    public static final class SurfacePackage implements Parcelable {
+        private final SurfaceControl mSurfaceControl;
         // TODO: Accessibility ID goes here
 
         SurfacePackage(SurfaceControl sc) {
             mSurfaceControl = sc;
         }
 
+        private SurfacePackage(Parcel in) {
+            mSurfaceControl = new SurfaceControl();
+            mSurfaceControl.readFromParcel(in);
+        }
+
+        /**
+         * Use {@link SurfaceView#setChildSurfacePackage} or manually fix
+         * accessibility (see SurfaceView implementation).
+         * @hide
+         */
         public @NonNull SurfaceControl getSurfaceControl() {
             return mSurfaceControl;
         }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel out, int flags) {
+            mSurfaceControl.writeToParcel(out, flags);
+        }
+
+        public static final @NonNull Creator<SurfacePackage> CREATOR
+             = new Creator<SurfacePackage>() {
+                     public SurfacePackage createFromParcel(Parcel in) {
+                         return new SurfacePackage(in);
+                     }
+                     public SurfacePackage[] newArray(int size) {
+                         return new SurfacePackage[size];
+                     }
+             };
     }
 
     /** @hide */
@@ -59,17 +97,36 @@
         mViewRoot = new ViewRootImpl(c, d, mWm);
     }
 
-    public SurfaceControlViewHost(@NonNull Context c, @NonNull Display d,
-            @Nullable IBinder hostInputToken) {
+    /**
+     * Construct a new SurfaceControlViewHost. The root Surface will be
+     * allocated internally and is accessible via getSurfacePackage().
+     *
+     * The {@param hostToken} parameter, primarily used for ANR reporting,
+     * must be obtained from whomever will be hosting the embedded hierarchy.
+     * It's accessible from {@link SurfaceView#getHostToken}.
+     *
+     * @param context The Context object for your activity or application.
+     * @param display The Display the hierarchy will be placed on.
+     * @param hostToken The host token, as discussed above.
+     */
+    public SurfaceControlViewHost(@NonNull Context context, @NonNull Display display,
+            @Nullable IBinder hostToken) {
         mSurfaceControl = new SurfaceControl.Builder()
             .setContainerLayer()
             .setName("SurfaceControlViewHost")
             .build();
-        mWm = new WindowlessWindowManager(c.getResources().getConfiguration(), mSurfaceControl,
-                hostInputToken);
-        mViewRoot = new ViewRootImpl(c, d, mWm);
+        mWm = new WindowlessWindowManager(context.getResources().getConfiguration(),
+                mSurfaceControl, hostToken);
+        mViewRoot = new ViewRootImpl(context, display, mWm);
     }
 
+    /**
+     * Return a SurfacePackage for the root SurfaceControl of the embedded hierarchy.
+     * Rather than be directly reparented using {@link SurfaceControl.Transaction} this
+     * SurfacePackage should be passed to {@link SurfaceView#setChildSurfacePackage}
+     * which will not only reparent the Surface, but ensure the accessibility hierarchies
+     * are linked.
+     */
     public @Nullable SurfacePackage getSurfacePackage() {
         if (mSurfaceControl != null) {
             return new SurfacePackage(mSurfaceControl);
@@ -78,10 +135,32 @@
         }
     }
 
-    public void addView(View view, WindowManager.LayoutParams attrs) {
+    /**
+     * @hide
+     */
+    public void addView(@NonNull View view, WindowManager.LayoutParams attrs) {
         mViewRoot.setView(view, attrs, null);
     }
 
+    /**
+     * Set the root view of the SurfaceControlViewHost. This view will render in to
+     * the SurfaceControl, and receive input based on the SurfaceControls positioning on
+     * screen. It will be laid as if it were in a window of the passed in width and height.
+     *
+     * @param view The View to add
+     * @param width The width to layout the View within, in pixels.
+     * @param height The height to layout the View within, in pixels.
+     */
+    public void addView(@NonNull View view, int width, int height) {
+        final WindowManager.LayoutParams lp =
+                new WindowManager.LayoutParams(width, height,
+                        WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.TRANSPARENT);
+        addView(view, lp);
+    }
+
+    /**
+     * @hide
+     */
     public void relayout(WindowManager.LayoutParams attrs) {
         mViewRoot.setLayoutParams(attrs, false);
         mViewRoot.setReportNextDraw();
@@ -90,8 +169,27 @@
         });
     }
 
-    public void dispose() {
+    /**
+     * Modify the size of the root view.
+     *
+     * @param width Width in pixels
+     * @param height Height in pixels
+     */
+    public void relayout(int width, int height) {
+        final WindowManager.LayoutParams lp =
+                new WindowManager.LayoutParams(width, height,
+                        WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.TRANSPARENT);
+        relayout(width, height);
+    }
+
+    /**
+     * Trigger the tear down of the embedded view hierarchy and release the SurfaceControl.
+     * This will result in onDispatchedFromWindow being dispatched to the embedded view hierarchy
+     * and render the object unusable.
+     */
+    public void release() {
         mViewRoot.dispatchDetachedFromWindow();
+        mSurfaceControl.release();
     }
 
     /**
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 1782544..1981bdd 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -20,6 +20,7 @@
 import static android.view.WindowManagerPolicyConstants.APPLICATION_MEDIA_SUBLAYER;
 import static android.view.WindowManagerPolicyConstants.APPLICATION_PANEL_SUBLAYER;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -42,6 +43,8 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.SurfaceControl.Transaction;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.SurfaceControlViewHost;
 
 import com.android.internal.view.SurfaceCallbackHelper;
 
@@ -201,6 +204,10 @@
     private SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
     private int mParentSurfaceGenerationId;
 
+    // The token of embedded windowless view hierarchy.
+    private IBinder mEmbeddedViewHierarchy;
+    SurfaceControlViewHost.SurfacePackage mSurfacePackage;
+
     public SurfaceView(Context context) {
         this(context, null);
     }
@@ -873,6 +880,11 @@
                     } else {
                         mTmpTransaction.hide(mSurfaceControl);
                     }
+
+                    if (mSurfacePackage != null) {
+                        reparentSurfacePackage(mTmpTransaction, mSurfacePackage);
+                    }
+
                     updateBackgroundVisibility(mTmpTransaction);
                     if (mUseAlpha) {
                         mTmpTransaction.setAlpha(mSurfaceControl, alpha);
@@ -1467,11 +1479,12 @@
     }
 
     /**
-     * @return The token used to identify the windows input channel.
-     * @hide
+     * A token used for constructing {@link SurfaceControlViewHost}. This token should
+     * be passed from the host process to the client process.
+     *
+     * @return The token
      */
-    @TestApi
-    public @Nullable IBinder getInputToken() {
+    public @Nullable IBinder getHostToken() {
         final ViewRootImpl viewRoot = getViewRootImpl();
         if (viewRoot == null) {
             return null;
@@ -1531,4 +1544,54 @@
         if (viewRoot == null) return;
         viewRoot.setUseBLASTSyncTransaction();
     }
+
+    /**
+     * Display the view-hierarchy embedded within a {@link SurfaceControlViewHost.SurfacePackage}
+     * within this SurfaceView. If this SurfaceView is above it's host Surface (see
+     * {@link #setZOrderOnTop} then the embedded Surface hierarchy will be able to receive
+     * input.
+     *
+     * @param p The SurfacePackage to embed.
+     */
+    public void setChildSurfacePackage(@NonNull SurfaceControlViewHost.SurfacePackage p) {
+        final SurfaceControl sc = p != null ? p.getSurfaceControl() : null;
+        final SurfaceControl lastSc = mSurfacePackage != null ?
+            mSurfacePackage.getSurfaceControl() : null;
+        if (mSurfaceControl != null && lastSc != null) {
+            mTmpTransaction.reparent(lastSc, null).apply();
+        } else if (mSurfaceControl != null) {
+            reparentSurfacePackage(mTmpTransaction, p);
+            mTmpTransaction.apply();
+        }
+        mSurfacePackage = p;
+    }
+
+    private void reparentSurfacePackage(SurfaceControl.Transaction t,
+            SurfaceControlViewHost.SurfacePackage p) {
+        // TODO: Link accessibility IDs here.
+        t.reparent(p.getSurfaceControl(), mSurfaceControl);
+    }
+
+    /**
+     * Add the token of embedded view hierarchy. Set {@code null} to clear the embedded view
+     * hierarchy.
+     *
+     * @param token IBinder token.
+     * @hide
+     */
+    public void setEmbeddedViewHierarchy(IBinder token) {
+        mEmbeddedViewHierarchy = token;
+    }
+
+    /** @hide */
+    @Override
+    public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfoInternal(info);
+        if (mEmbeddedViewHierarchy == null) {
+            return;
+        }
+        // Add a leashed child when this SurfaceView embeds another view hierarchy. Getting this
+        // leashed child would return the root node in the embedded hierarchy
+        info.addChild(mEmbeddedViewHierarchy);
+    }
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 0db80e2..0f2d2c2 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -22,6 +22,10 @@
 import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP;
 import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION;
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
+import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.systemBars;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
 import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
 
 import static java.lang.Math.max;
@@ -96,6 +100,7 @@
 import android.util.LayoutDirection;
 import android.util.Log;
 import android.util.LongSparseLongArray;
+import android.util.Pair;
 import android.util.Pools.SynchronizedPool;
 import android.util.Property;
 import android.util.SparseArray;
@@ -109,8 +114,11 @@
 import android.view.AccessibilityIterators.TextSegmentIterator;
 import android.view.AccessibilityIterators.WordTextSegmentIterator;
 import android.view.ContextMenu.ContextMenuInfo;
+import android.view.Window.OnContentApplyWindowInsetsListener;
+import android.view.WindowInsets.Type;
 import android.view.WindowInsetsAnimationCallback.AnimationBounds;
 import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
+import android.view.WindowManager.LayoutParams;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityEventSource;
 import android.view.accessibility.AccessibilityManager;
@@ -130,7 +138,6 @@
 import android.view.contentcapture.ContentCaptureSession;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.InputMethodManager;
 import android.view.inspector.InspectableProperty;
 import android.view.inspector.InspectableProperty.EnumEntry;
 import android.view.inspector.InspectableProperty.FlagEntry;
@@ -139,6 +146,7 @@
 import android.widget.ScrollBarDrawable;
 
 import com.android.internal.R;
+import com.android.internal.policy.DecorView;
 import com.android.internal.view.TooltipPopup;
 import com.android.internal.view.menu.MenuBuilder;
 import com.android.internal.widget.ScrollBarUtils;
@@ -950,22 +958,14 @@
     private static boolean sAcceptZeroSizeDragShadow;
 
     /**
-     * Prior to Q, {@link #dispatchApplyWindowInsets} had some issues:
-     * <ul>
-     *     <li>The modified insets changed by {@link #onApplyWindowInsets} were passed to the
-     *     entire view hierarchy in prefix order, including siblings as well as siblings of parents
-     *     further down the hierarchy. This violates the basic concepts of the view hierarchy, and
-     *     thus, the hierarchical dispatching mechanism was hard to use for apps.</li>
-     *
-     *     <li>Dispatch was stopped after the insets were fully consumed. This is somewhat confusing
-     *     for developers, but more importantly, by adding more granular information to
-     *     {@link WindowInsets} it becomes really cumbersome to define what consumed actually means
-     *     </li>
-     * </ul>
-     *
+     * Prior to R, {@link #dispatchApplyWindowInsets} had an issue:
+     * <p>The modified insets changed by {@link #onApplyWindowInsets} were passed to the
+     * entire view hierarchy in prefix order, including siblings as well as siblings of parents
+     * further down the hierarchy. This violates the basic concepts of the view hierarchy, and
+     * thus, the hierarchical dispatching mechanism was hard to use for apps.
+     * <p>
      * In order to make window inset dispatching work properly, we dispatch window insets
-     * in the view hierarchy in a proper hierarchical manner and don't stop dispatching if the
-     * insets are consumed if this flag is set to {@code false}.
+     * in the view hierarchy in a proper hierarchical manner if this flag is set to {@code false}.
      */
     static boolean sBrokenInsetsDispatch;
 
@@ -1517,6 +1517,10 @@
      * Set for framework elements that use FITS_SYSTEM_WINDOWS, to indicate
      * that they are optional and should be skipped if the window has
      * requested system UI flags that ignore those insets for layout.
+     * <p>
+     * This is only used for support library as of Android R. The framework now uses
+     * {@link #PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS} such that it can skip the legacy
+     * insets path that loses insets information.
      */
     static final int OPTIONAL_FITS_SYSTEM_WINDOWS = 0x00000800;
 
@@ -2265,7 +2269,7 @@
      * be extended in the future to hold our own class with more than just
      * a Rect. :)
      */
-    static final ThreadLocal<Rect> sThreadLocal = new ThreadLocal<Rect>();
+    static final ThreadLocal<Rect> sThreadLocal = ThreadLocal.withInitial(Rect::new);
 
     /**
      * Map used to store views' tags.
@@ -3427,6 +3431,7 @@
      *                          1       PFLAG4_CONTENT_CAPTURE_IMPORTANCE_IS_CACHED
      *                         1        PFLAG4_CONTENT_CAPTURE_IMPORTANCE_CACHED_VALUE
      *                         11       PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK
+     *                        1         PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS
      * |-------|-------|-------|-------|
      */
 
@@ -3464,6 +3469,11 @@
             PFLAG4_CONTENT_CAPTURE_IMPORTANCE_IS_CACHED
             | PFLAG4_CONTENT_CAPTURE_IMPORTANCE_CACHED_VALUE;
 
+    /**
+     * @see #OPTIONAL_FITS_SYSTEM_WINDOWS
+     */
+    static final int PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS = 0x000000100;
+
     /* End of masks for mPrivateFlags4 */
 
     /** @hide */
@@ -3513,7 +3523,10 @@
      * requested the system UI (status bar) to be visible (the default).
      *
      * @see #setSystemUiVisibility(int)
+     * @deprecated SystemUiVisibility flags are deprecated. Use {@link WindowInsetsController}
+     * instead.
      */
+    @Deprecated
     public static final int SYSTEM_UI_FLAG_VISIBLE = 0;
 
     /**
@@ -3526,7 +3539,11 @@
      * <p>In low profile mode, the status bar and/or navigation icons may dim.
      *
      * @see #setSystemUiVisibility(int)
+     * @deprecated Low profile mode is deprecated. Hide the system bars instead if the application
+     * needs to be in a unobtrusive mode. Use {@link WindowInsetsController#hide(int)} with
+     * {@link Type#systemBars()}.
      */
+    @Deprecated
     public static final int SYSTEM_UI_FLAG_LOW_PROFILE = 0x00000001;
 
     /**
@@ -3547,7 +3564,10 @@
      * so that both elements reappear at the same time.
      *
      * @see #setSystemUiVisibility(int)
+     * @deprecated Use {@link WindowInsetsController#hide(int)} with {@link Type#navigationBars()}
+     * instead.
      */
+    @Deprecated
     public static final int SYSTEM_UI_FLAG_HIDE_NAVIGATION = 0x00000002;
 
     /**
@@ -3583,7 +3603,10 @@
      * the book.
      *
      * @see #setSystemUiVisibility(int)
+     * @deprecated Use {@link WindowInsetsController#hide(int)} with {@link Type#statusBars()}
+     * instead.
      */
+    @Deprecated
     public static final int SYSTEM_UI_FLAG_FULLSCREEN = 0x00000004;
 
     /**
@@ -3617,7 +3640,11 @@
      * overlay mode with {@link Window#FEATURE_ACTION_BAR_OVERLAY
      * Window.FEATURE_ACTION_BAR_OVERLAY}, this flag will also impact the
      * insets it adds to those given to the application.
+     *
+     * @deprecated Use {@link WindowInsets#getInsetsIgnoringVisibility(int)} instead to retrieve
+     * insets that don't change when system bars change visibility state.
      */
+    @Deprecated
     public static final int SYSTEM_UI_FLAG_LAYOUT_STABLE = 0x00000100;
 
     /**
@@ -3629,6 +3656,11 @@
      * decorations when they are shown.  You can perform layout of your inner
      * UI elements to account for the navigation system UI through the
      * {@link #fitSystemWindows(Rect)} method.
+     *
+     * @deprecated For floating windows, use {@link LayoutParams#setFitInsetsTypes(int)} with
+     * {@link Type#navigationBars()}. For non-floating windows that fill the screen, call
+     * {@link Window#setOnContentApplyWindowInsets} with {@code null} or a listener that doesn't
+     * fit the navigation bar on the window content level.
      */
     public static final int SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION = 0x00000200;
 
@@ -3653,7 +3685,13 @@
      * @see WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
      * @see WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
      * @see WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
+     *
+     * @deprecated For floating windows, use {@link LayoutParams#setFitInsetsTypes(int)} with
+     * {@link Type#statusBars()} ()}. For non-floating windows that fill the screen, call
+     * {@link Window#setOnContentApplyWindowInsets} with {@code null} or a listener that doesn't
+     * fit the status bar on the window content level.
      */
+    @Deprecated
     public static final int SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = 0x00000400;
 
     /**
@@ -3663,7 +3701,10 @@
      * user interaction.
      * <p>Since this flag is a modifier for {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}, it only
      * has an effect when used in combination with that flag.</p>
+     *
+     * @deprecated Use {@link WindowInsetsController#BEHAVIOR_SHOW_BARS_BY_SWIPE} instead.
      */
+    @Deprecated
     public static final int SYSTEM_UI_FLAG_IMMERSIVE = 0x00000800;
 
     /**
@@ -3681,7 +3722,10 @@
      * </p><p>Since this flag is a modifier for {@link #SYSTEM_UI_FLAG_FULLSCREEN} and
      * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}, it only has an effect when used in combination
      * with one or both of those flags.</p>
+     *
+     * @deprecated Use {@link WindowInsetsController#BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE} instead.
      */
+    @Deprecated
     public static final int SYSTEM_UI_FLAG_IMMERSIVE_STICKY = 0x00001000;
 
     /**
@@ -3695,7 +3739,9 @@
      *         FLAG_TRANSLUCENT_STATUS}.
      *
      * @see android.R.attr#windowLightStatusBar
+     * @deprecated Use {@link WindowInsetsController#APPEARANCE_LIGHT_STATUS_BARS} instead.
      */
+    @Deprecated
     public static final int SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = 0x00002000;
 
     /**
@@ -3721,7 +3767,9 @@
      *         FLAG_TRANSLUCENT_NAVIGATION}.
      *
      * @see android.R.attr#windowLightNavigationBar
+     * @deprecated Use {@link WindowInsetsController#APPEARANCE_LIGHT_NAVIGATION_BARS} instead.
      */
+    @Deprecated
     public static final int SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR = 0x00000010;
 
     /**
@@ -3949,7 +3997,10 @@
 
     /**
      * Flags that can impact the layout in relation to system UI.
+     *
+     * @deprecated System UI layout flags are deprecated.
      */
+    @Deprecated
     public static final int SYSTEM_UI_LAYOUT_FLAGS =
             SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
             | SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
@@ -4785,7 +4836,7 @@
     private CheckForTap mPendingCheckForTap = null;
     private PerformClick mPerformClick;
     private SendViewScrolledAccessibilityEvent mSendViewScrolledAccessibilityEvent;
-
+    private SendAccessibilityEventThrottle mSendStateChangedAccessibilityEvent;
     private UnsetPressedState mUnsetPressedState;
 
     /**
@@ -5232,7 +5283,7 @@
             sAcceptZeroSizeDragShadow = targetSdkVersion < Build.VERSION_CODES.P;
 
             sBrokenInsetsDispatch = ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL
-                    || targetSdkVersion < Build.VERSION_CODES.Q;
+                    || targetSdkVersion < Build.VERSION_CODES.R;
 
             sBrokenWindowBackground = targetSdkVersion < Build.VERSION_CODES.Q;
 
@@ -7942,12 +7993,12 @@
             if (isPressed()) {
                 setPressed(false);
             }
-            if (mAttachInfo != null && mAttachInfo.mHasWindowFocus) {
-                notifyFocusChangeToInputMethodManager(false /* hasFocus */);
+            if (hasWindowFocus()) {
+                notifyFocusChangeToImeFocusController(false /* hasFocus */);
             }
             onFocusLost();
-        } else if (mAttachInfo != null && mAttachInfo.mHasWindowFocus) {
-            notifyFocusChangeToInputMethodManager(true /* hasFocus */);
+        } else if (hasWindowFocus()) {
+            notifyFocusChangeToImeFocusController(true /* hasFocus */);
         }
 
         invalidate(true);
@@ -7964,23 +8015,15 @@
     }
 
     /**
-     * Notify {@link InputMethodManager} about the focus change of the {@link View}.
-     *
-     * <p>Does nothing when {@link InputMethodManager} is not available.</p>
+     * Notify {@link ImeFocusController} about the focus change of the {@link View}.
      *
      * @param hasFocus {@code true} when the {@link View} is being focused.
      */
-    private void notifyFocusChangeToInputMethodManager(boolean hasFocus) {
-        final InputMethodManager imm =
-                getContext().getSystemService(InputMethodManager.class);
-        if (imm == null) {
+    private void notifyFocusChangeToImeFocusController(boolean hasFocus) {
+        if (mAttachInfo == null) {
             return;
         }
-        if (hasFocus) {
-            imm.focusIn(this);
-        } else {
-            imm.focusOut(this);
-        }
+        mAttachInfo.mViewRootImpl.getImeFocusController().onViewFocusChanged(this, hasFocus);
     }
 
     /** @hide */
@@ -8163,13 +8206,46 @@
         if ((event.getEventType() & POPULATING_ACCESSIBILITY_EVENT_TYPES) != 0) {
             dispatchPopulateAccessibilityEvent(event);
         }
-        // In the beginning we called #isShown(), so we know that getParent() is not null.
+        SendAccessibilityEventThrottle throttle = getThrottleForAccessibilityEvent(event);
+        if (throttle != null) {
+            throttle.post(event);
+        } else {
+            requestParentSendAccessibilityEvent(event);
+        }
+    }
+
+    private void requestParentSendAccessibilityEvent(AccessibilityEvent event) {
         ViewParent parent = getParent();
         if (parent != null) {
             getParent().requestSendAccessibilityEvent(this, event);
         }
     }
 
+    private SendAccessibilityEventThrottle getThrottleForAccessibilityEvent(
+            AccessibilityEvent event) {
+        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
+            if (mSendViewScrolledAccessibilityEvent == null) {
+                mSendViewScrolledAccessibilityEvent = new SendViewScrolledAccessibilityEvent();
+            }
+            return mSendViewScrolledAccessibilityEvent;
+        }
+        boolean isStateContentChanged = (event.getContentChangeTypes()
+                & AccessibilityEvent.CONTENT_CHANGE_TYPE_STATE_DESCRIPTION) != 0;
+        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
+                && isStateContentChanged) {
+            if (mSendStateChangedAccessibilityEvent == null) {
+                mSendStateChangedAccessibilityEvent = new SendAccessibilityEventThrottle();
+            }
+            return mSendStateChangedAccessibilityEvent;
+        }
+        return null;
+    }
+
+    private void clearAccessibilityThrottles() {
+        cancel(mSendViewScrolledAccessibilityEvent);
+        cancel(mSendStateChangedAccessibilityEvent);
+    }
+
     /**
      * Dispatches an {@link AccessibilityEvent} to the {@link View} first and then
      * to its children for adding their text content to the event. Note that the
@@ -10390,8 +10466,12 @@
                 && getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
             setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
         }
-        notifyViewAccessibilityStateChangedIfNeeded(
-                AccessibilityEvent.CONTENT_CHANGE_TYPE_STATE_DESCRIPTION);
+        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+            AccessibilityEvent event = AccessibilityEvent.obtain();
+            event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+            event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_STATE_DESCRIPTION);
+            sendAccessibilityEventUnchecked(event);
+        }
     }
 
     /**
@@ -10998,23 +11078,22 @@
 
     private boolean fitSystemWindowsInt(Rect insets) {
         if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) {
-            mUserPaddingStart = UNDEFINED_PADDING;
-            mUserPaddingEnd = UNDEFINED_PADDING;
             Rect localInsets = sThreadLocal.get();
-            if (localInsets == null) {
-                localInsets = new Rect();
-                sThreadLocal.set(localInsets);
-            }
             boolean res = computeFitSystemWindows(insets, localInsets);
-            mUserPaddingLeftInitial = localInsets.left;
-            mUserPaddingRightInitial = localInsets.right;
-            internalSetPadding(localInsets.left, localInsets.top,
-                    localInsets.right, localInsets.bottom);
+            applyInsets(localInsets);
             return res;
         }
         return false;
     }
 
+    private void applyInsets(Rect insets) {
+        mUserPaddingStart = UNDEFINED_PADDING;
+        mUserPaddingEnd = UNDEFINED_PADDING;
+        mUserPaddingLeftInitial = insets.left;
+        mUserPaddingRightInitial = insets.right;
+        internalSetPadding(insets.left, insets.top, insets.right, insets.bottom);
+    }
+
     /**
      * Called when the view should apply {@link WindowInsets} according to its internal policy.
      *
@@ -11041,6 +11120,10 @@
      * @return The supplied insets with any applied insets consumed
      */
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+        if ((mPrivateFlags4 & PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS) != 0
+                && (mViewFlags & FITS_SYSTEM_WINDOWS) != 0) {
+            return onApplyFrameworkOptionalFitSystemWindows(insets);
+        }
         if ((mPrivateFlags3 & PFLAG3_FITTING_SYSTEM_WINDOWS) == 0) {
             // We weren't called from within a direct call to fitSystemWindows,
             // call into it as a fallback in case we're in a class that overrides it
@@ -11057,6 +11140,13 @@
         return insets;
     }
 
+    private WindowInsets onApplyFrameworkOptionalFitSystemWindows(WindowInsets insets) {
+        Rect localInsets = sThreadLocal.get();
+        WindowInsets result = computeSystemWindowInsets(insets, localInsets);
+        applyInsets(localInsets);
+        return result;
+    }
+
     /**
      * Set an {@link OnApplyWindowInsetsListener} to take over the policy for applying
      * window insets to this view. The listener's
@@ -11109,7 +11199,10 @@
     /**
      * Sets a {@link WindowInsetsAnimationCallback} to be notified about animations of windows that
      * cause insets.
-     *
+     * <p>
+     * When setting a listener, it's {@link WindowInsetsAnimationCallback#getDispatchMode() dispatch
+     * mode} will be retrieved and recorded until another listener will be set.
+     * </p>
      * @param listener The listener to set.
      */
     public void setWindowInsetsAnimationCallback(@Nullable WindowInsetsAnimationCallback listener) {
@@ -11117,7 +11210,21 @@
     }
 
     /**
-     * Dispatches {@link WindowInsetsAnimationCallback#onStarted(InsetsAnimation, AnimationBounds)}
+     * Dispatches {@link WindowInsetsAnimationCallback#onPrepare(InsetsAnimation)}
+     * when Window Insets animation is being prepared.
+     * @param animation current animation
+     *
+     * @see WindowInsetsAnimationCallback#onPrepare(InsetsAnimation)
+     */
+    public void dispatchWindowInsetsAnimationPrepare(
+            @NonNull InsetsAnimation animation) {
+        if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
+            mListenerInfo.mWindowInsetsAnimationCallback.onPrepare(animation);
+        }
+    }
+
+    /**
+     * Dispatches {@link WindowInsetsAnimationCallback#onStart(InsetsAnimation, AnimationBounds)}
      * when Window Insets animation is started.
      * @param animation current animation
      * @param bounds the upper and lower {@link AnimationBounds} that provides range of
@@ -11125,10 +11232,10 @@
      * @return the upper and lower {@link AnimationBounds}.
      */
     @NonNull
-    public AnimationBounds dispatchWindowInsetsAnimationStarted(
+    public AnimationBounds dispatchWindowInsetsAnimationStart(
             @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
         if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
-            return mListenerInfo.mWindowInsetsAnimationCallback.onStarted(animation, bounds);
+            return mListenerInfo.mWindowInsetsAnimationCallback.onStart(animation, bounds);
         }
         return bounds;
     }
@@ -11149,13 +11256,13 @@
     }
 
     /**
-     * Dispatches {@link WindowInsetsAnimationCallback#onFinished(InsetsAnimation)}
+     * Dispatches {@link WindowInsetsAnimationCallback#onFinish(InsetsAnimation)}
      * when Window Insets animation finishes.
      * @param animation The current ongoing {@link InsetsAnimation}.
      */
-    public void dispatchWindowInsetsAnimationFinished(@NonNull InsetsAnimation animation) {
+    public void dispatchWindowInsetsAnimationFinish(@NonNull InsetsAnimation animation) {
         if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
-            mListenerInfo.mWindowInsetsAnimationCallback.onFinished(animation);
+            mListenerInfo.mWindowInsetsAnimationCallback.onFinish(animation);
         }
     }
 
@@ -11330,16 +11437,23 @@
      * @return Insets that should be passed along to views under this one
      */
     public WindowInsets computeSystemWindowInsets(WindowInsets in, Rect outLocalInsets) {
-        if ((mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS) == 0
-                || mAttachInfo == null
-                || ((mAttachInfo.mSystemUiVisibility & SYSTEM_UI_LAYOUT_FLAGS) == 0)) {
+        boolean isOptionalFitSystemWindows = (mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS) != 0
+                || (mPrivateFlags4 & PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS) != 0;
+        if (isOptionalFitSystemWindows && mAttachInfo != null) {
+            OnContentApplyWindowInsetsListener listener =
+                    mAttachInfo.mContentOnApplyWindowInsetsListener;
+            if (listener == null) {
+                // The application wants to take care of fitting system window for
+                // the content.
+                outLocalInsets.setEmpty();
+                return in;
+            }
+            Pair<Insets, WindowInsets> result = listener.onContentApplyWindowInsets(in);
+            outLocalInsets.set(result.first.toRect());
+            return result.second;
+        } else {
             outLocalInsets.set(in.getSystemWindowInsetsAsRect());
             return in.consumeSystemWindowInsets().inset(outLocalInsets);
-        } else {
-            // The application wants to take care of fitting system window for
-            // the content.
-            outLocalInsets.setEmpty();
-            return in;
         }
     }
 
@@ -11410,7 +11524,7 @@
     }
 
     /**
-     * For use by PhoneWindow to make its own system window fitting optional.
+     * @see #OPTIONAL_FITS_SYSTEM_WINDOWS
      * @hide
      */
     @UnsupportedAppUsage
@@ -11419,6 +11533,14 @@
     }
 
     /**
+     * @see #PFLAG4_OPTIONAL_FITS_SYSTEM_WINDOWS
+     * @hide
+     */
+    public void makeFrameworkOptionalFitsSystemWindows() {
+        mPrivateFlags4 |= PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS;
+    }
+
+    /**
      * Returns the visibility status for this view.
      *
      * @return One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
@@ -12597,11 +12719,10 @@
                 return findViewInsideOutShouldExist(root, mNextFocusForwardId);
             case FOCUS_BACKWARD: {
                 if (mID == View.NO_ID) return null;
-                final int id = mID;
                 return root.findViewByPredicateInsideOut(this, new Predicate<View>() {
                     @Override
                     public boolean test(View t) {
-                        return t.mNextFocusForwardId == id;
+                        return t.findViewById(t.mNextFocusForwardId) == View.this;
                     }
                 });
             }
@@ -13904,7 +14025,7 @@
         mPrivateFlags3 &= ~PFLAG3_TEMPORARY_DETACH;
         onFinishTemporaryDetach();
         if (hasWindowFocus() && hasFocus()) {
-            notifyFocusChangeToInputMethodManager(true /* hasFocus */);
+            notifyFocusChangeToImeFocusController(true /* hasFocus */);
         }
         notifyEnterOrExitForAutoFillIfNeeded(true);
         notifyAppearedOrDisappearedForContentCaptureIfNeeded(true);
@@ -14312,13 +14433,13 @@
             }
             mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
             if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
-                notifyFocusChangeToInputMethodManager(false /* hasFocus */);
+                notifyFocusChangeToImeFocusController(false /* hasFocus */);
             }
             removeLongPressCallback();
             removeTapCallback();
             onFocusLost();
         } else if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
-            notifyFocusChangeToInputMethodManager(true /* hasFocus */);
+            notifyFocusChangeToImeFocusController(true /* hasFocus */);
         }
 
         refreshDrawableState();
@@ -14335,6 +14456,14 @@
     }
 
     /**
+     * @return {@code true} if this view is in a window that currently has IME focusable state.
+     * @hide
+     */
+    public boolean hasImeFocus() {
+        return mAttachInfo != null && mAttachInfo.mHasImeFocus;
+    }
+
+    /**
      * Dispatch a view visibility change down the view hierarchy.
      * ViewGroups should override to route to their children.
      * @param changedView The view whose visibility changed. Could be 'this' or
@@ -15974,10 +16103,7 @@
      */
     protected void onScrollChanged(int l, int t, int oldl, int oldt) {
         notifySubtreeAccessibilityStateChangedIfNeeded();
-
-        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
-            postSendViewScrolledAccessibilityEventCallback(l - oldl, t - oldt);
-        }
+        postSendViewScrolledAccessibilityEventCallback(l - oldl, t - oldt);
 
         mBackgroundSizeChanged = true;
         mDefaultFocusHighlightSizeChanged = true;
@@ -18700,10 +18826,13 @@
      * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}.
      */
     private void postSendViewScrolledAccessibilityEventCallback(int dx, int dy) {
-        if (mSendViewScrolledAccessibilityEvent == null) {
-            mSendViewScrolledAccessibilityEvent = new SendViewScrolledAccessibilityEvent();
+        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+            AccessibilityEvent event =
+                    AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED);
+            event.setScrollDeltaX(dx);
+            event.setScrollDeltaY(dy);
+            sendAccessibilityEventUnchecked(event);
         }
-        mSendViewScrolledAccessibilityEvent.post(dx, dy);
     }
 
     /**
@@ -19630,7 +19759,7 @@
         rebuildOutline();
 
         if (isFocused()) {
-            notifyFocusChangeToInputMethodManager(true /* hasFocus */);
+            notifyFocusChangeToImeFocusController(true /* hasFocus */);
         }
     }
 
@@ -20001,7 +20130,7 @@
         removeUnsetPressCallback();
         removeLongPressCallback();
         removePerformClickCallback();
-        cancel(mSendViewScrolledAccessibilityEvent);
+        clearAccessibilityThrottles();
         stopNestedScroll();
 
         // Anything that started animating right before detach should already
@@ -20213,9 +20342,8 @@
         onDetachedFromWindow();
         onDetachedFromWindowInternal();
 
-        InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
-        if (imm != null) {
-            imm.onViewDetachedFromWindow(this);
+        if (info != null) {
+            info.mViewRootImpl.getImeFocusController().onViewDetachedFromWindow(this);
         }
 
         ListenerInfo li = mListenerInfo;
@@ -25698,7 +25826,11 @@
      * {@link #SYSTEM_UI_FLAG_LAYOUT_STABLE}, {@link #SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION},
      * {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}, {@link #SYSTEM_UI_FLAG_IMMERSIVE},
      * and {@link #SYSTEM_UI_FLAG_IMMERSIVE_STICKY}.
+     *
+     * @deprecated SystemUiVisibility flags are deprecated. Use {@link WindowInsetsController}
+     * instead.
      */
+    @Deprecated
     public void setSystemUiVisibility(int visibility) {
         if (visibility != mSystemUiVisibility) {
             mSystemUiVisibility = visibility;
@@ -25715,7 +25847,11 @@
      * {@link #SYSTEM_UI_FLAG_LAYOUT_STABLE}, {@link #SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION},
      * {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}, {@link #SYSTEM_UI_FLAG_IMMERSIVE},
      * and {@link #SYSTEM_UI_FLAG_IMMERSIVE_STICKY}.
+     *
+     * @deprecated SystemUiVisibility flags are deprecated. Use {@link WindowInsetsController}
+     * instead.
      */
+    @Deprecated
     public int getSystemUiVisibility() {
         return mSystemUiVisibility;
     }
@@ -25725,7 +25861,11 @@
      * the entire window.  This is the combination of the
      * {@link #setSystemUiVisibility(int)} values supplied by all of the
      * views in the window.
+     *
+     * @deprecated SystemUiVisibility flags are deprecated. Use {@link WindowInsetsController}
+     * instead.
      */
+    @Deprecated
     public int getWindowSystemUiVisibility() {
         return mAttachInfo != null ? mAttachInfo.mSystemUiVisibility : 0;
     }
@@ -25737,14 +25877,22 @@
      * {@link #setOnSystemUiVisibilityChangeListener(OnSystemUiVisibilityChangeListener)}
      * in that this is only telling you about the local request of the window,
      * not the actual values applied by the system.
+     *
+     * @deprecated SystemUiVisibility flags are deprecated. Use {@link WindowInsetsController}
+     * instead.
      */
+    @Deprecated
     public void onWindowSystemUiVisibilityChanged(int visible) {
     }
 
     /**
      * Dispatch callbacks to {@link #onWindowSystemUiVisibilityChanged(int)} down
      * the view hierarchy.
+     *
+     * @deprecated SystemUiVisibility flags are deprecated. Use {@link WindowInsetsController}
+     * instead.
      */
+    @Deprecated
     public void dispatchWindowSystemUiVisiblityChanged(int visible) {
         onWindowSystemUiVisibilityChanged(visible);
     }
@@ -25752,7 +25900,11 @@
     /**
      * Set a listener to receive callbacks when the visibility of the system bar changes.
      * @param l  The {@link OnSystemUiVisibilityChangeListener} to receive callbacks.
+     *
+     * @deprecated Use {@link WindowInsets#isVisible(int)} to find out about system bar visibilities
+     * by setting a {@link OnApplyWindowInsetsListener} on this view.
      */
+    @Deprecated
     public void setOnSystemUiVisibilityChangeListener(OnSystemUiVisibilityChangeListener l) {
         getListenerInfo().mOnSystemUiVisibilityChangeListener = l;
         if (mParent != null && mAttachInfo != null && !mAttachInfo.mRecomputeGlobalAttributes) {
@@ -25763,7 +25915,11 @@
     /**
      * Dispatch callbacks to {@link #setOnSystemUiVisibilityChangeListener} down
      * the view hierarchy.
+     *
+     * @deprecated Use {@link WindowInsets#isVisible(int)} to find out about system bar visibilities
+     * by setting a {@link OnApplyWindowInsetsListener} on this view.
      */
+    @Deprecated
     public void dispatchSystemUiVisibilityChanged(int visibility) {
         ListenerInfo li = mListenerInfo;
         if (li != null && li.mOnSystemUiVisibilityChangeListener != null) {
@@ -28204,7 +28360,11 @@
      * state, not what the application is requesting.
      *
      * @see View#setOnSystemUiVisibilityChangeListener(android.view.View.OnSystemUiVisibilityChangeListener)
+     *
+     * @deprecated Use {@link WindowInsets#isVisible(int)} to find out about system bar visibilities
+     * by setting a {@link OnApplyWindowInsetsListener} on this view.
      */
+    @Deprecated
     public interface OnSystemUiVisibilityChangeListener {
         /**
          * Called when the status bar changes visibility because of a call to
@@ -28370,6 +28530,7 @@
      * window.
      */
     final static class AttachInfo {
+
         interface Callbacks {
             void playSoundEffect(int effectId);
             boolean performHapticFeedback(int effectId, boolean always);
@@ -28551,6 +28712,11 @@
         boolean mHasWindowFocus;
 
         /**
+         * Indicates whether the view's window has IME focused.
+         */
+        boolean mHasImeFocus;
+
+        /**
          * The current visibility of the window.
          */
         int mWindowVisibility;
@@ -28804,6 +28970,11 @@
         ContentCaptureManager mContentCaptureManager;
 
         /**
+         * Listener used to fit content on window level.
+         */
+        OnContentApplyWindowInsetsListener mContentOnApplyWindowInsetsListener;
+
+        /**
          * Creates a new set of attachment information with the specified
          * events handler and thread.
          *
@@ -28981,49 +29152,67 @@
         }
     }
 
-    /**
-     * Resuable callback for sending
-     * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event.
-     */
-    private class SendViewScrolledAccessibilityEvent implements Runnable {
+    private class SendAccessibilityEventThrottle implements Runnable {
         public volatile boolean mIsPending;
-        public int mDeltaX;
-        public int mDeltaY;
+        private AccessibilityEvent mAccessibilityEvent;
 
-        public void post(int dx, int dy) {
-            mDeltaX += dx;
-            mDeltaY += dy;
+        public void post(AccessibilityEvent accessibilityEvent) {
+            updateWithAccessibilityEvent(accessibilityEvent);
             if (!mIsPending) {
                 mIsPending = true;
-                postDelayed(this, ViewConfiguration.getSendRecurringAccessibilityEventsInterval());
+                postDelayed(this,
+                        ViewConfiguration.getSendRecurringAccessibilityEventsInterval());
             }
         }
 
         @Override
         public void run() {
             if (AccessibilityManager.getInstance(mContext).isEnabled()) {
-                AccessibilityEvent event = AccessibilityEvent.obtain(
-                        AccessibilityEvent.TYPE_VIEW_SCROLLED);
-                event.setScrollDeltaX(mDeltaX);
-                event.setScrollDeltaY(mDeltaY);
-                sendAccessibilityEventUnchecked(event);
+                requestParentSendAccessibilityEvent(mAccessibilityEvent);
             }
             reset();
         }
 
-        private void reset() {
+        public void updateWithAccessibilityEvent(AccessibilityEvent accessibilityEvent) {
+            mAccessibilityEvent = accessibilityEvent;
+        }
+
+        public void reset() {
             mIsPending = false;
+            mAccessibilityEvent = null;
+        }
+
+    }
+
+    /**
+     * Resuable callback for sending
+     * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event.
+     */
+    private class SendViewScrolledAccessibilityEvent extends SendAccessibilityEventThrottle {
+        public int mDeltaX;
+        public int mDeltaY;
+
+        @Override
+        public void updateWithAccessibilityEvent(AccessibilityEvent accessibilityEvent) {
+            super.updateWithAccessibilityEvent(accessibilityEvent);
+            mDeltaX += accessibilityEvent.getScrollDeltaX();
+            mDeltaY += accessibilityEvent.getScrollDeltaY();
+            accessibilityEvent.setScrollDeltaX(mDeltaX);
+            accessibilityEvent.setScrollDeltaY(mDeltaY);
+        }
+
+        @Override
+        public void reset() {
+            super.reset();
             mDeltaX = 0;
             mDeltaY = 0;
         }
     }
-
     /**
-     * Remove the pending callback for sending a
-     * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event.
+     * Remove the pending callback for sending a throttled accessibility event.
      */
     @UnsupportedAppUsage
-    private void cancel(@Nullable SendViewScrolledAccessibilityEvent callback) {
+    private void cancel(@Nullable SendAccessibilityEventThrottle callback) {
         if (callback == null || !callback.mIsPending) return;
         removeCallbacks(callback);
         callback.reset();
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 5fb7177..4f03ca1 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -17,11 +17,14 @@
 package android.view;
 
 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+import static android.view.WindowInsetsAnimationCallback.DISPATCH_MODE_CONTINUE_ON_SUBTREE;
+import static android.view.WindowInsetsAnimationCallback.DISPATCH_MODE_STOP;
 
 import android.animation.LayoutTransition;
 import android.annotation.CallSuper;
 import android.annotation.IdRes;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.annotation.UiThread;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -45,13 +48,16 @@
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.os.SystemClock;
+import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Pools;
 import android.util.Pools.SynchronizedPool;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
+import android.view.Window.OnContentApplyWindowInsetsListener;
 import android.view.WindowInsetsAnimationCallback.AnimationBounds;
+import android.view.WindowInsetsAnimationCallback.DispatchMode;
 import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
@@ -607,6 +613,13 @@
     int mChildUnhandledKeyListeners = 0;
 
     /**
+     * Current dispatch mode of animation events
+     *
+     * @see WindowInsetsAnimationCallback#getDispatchMode()
+     */
+    private @DispatchMode int mInsetsAnimationDispatchMode = DISPATCH_MODE_CONTINUE_ON_SUBTREE;
+
+    /**
      * Empty ActionMode used as a sentinel in recursive entries to startActionModeForChild.
      *
      * @see #startActionModeForChild(View, android.view.ActionMode.Callback)
@@ -1515,6 +1528,19 @@
         }
     }
 
+    /**
+     * @hide
+     */
+    @Override
+    public void makeFrameworkOptionalFitsSystemWindows() {
+        super.makeFrameworkOptionalFitsSystemWindows();
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < count; i++) {
+            children[i].makeFrameworkOptionalFitsSystemWindows();
+        }
+    }
+
     @Override
     public void dispatchDisplayHint(int hint) {
         super.dispatchDisplayHint(hint);
@@ -1859,6 +1885,7 @@
     }
 
     @Override
+    @Deprecated
     public void dispatchWindowSystemUiVisiblityChanged(int visible) {
         super.dispatchWindowSystemUiVisiblityChanged(visible);
 
@@ -1871,6 +1898,7 @@
     }
 
     @Override
+    @Deprecated
     public void dispatchSystemUiVisibilityChanged(int visible) {
         super.dispatchSystemUiVisibilityChanged(visible);
 
@@ -7170,6 +7198,9 @@
     @Override
     public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
         insets = super.dispatchApplyWindowInsets(insets);
+        if (insets.isConsumed()) {
+            return insets;
+        }
         if (View.sBrokenInsetsDispatch) {
             return brokenDispatchApplyWindowInsets(insets);
         } else {
@@ -7178,13 +7209,11 @@
     }
 
     private WindowInsets brokenDispatchApplyWindowInsets(WindowInsets insets) {
-        if (!insets.isConsumed()) {
-            final int count = getChildCount();
-            for (int i = 0; i < count; i++) {
-                insets = getChildAt(i).dispatchApplyWindowInsets(insets);
-                if (insets.isConsumed()) {
-                    break;
-                }
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            insets = getChildAt(i).dispatchApplyWindowInsets(insets);
+            if (insets.isConsumed()) {
+                break;
             }
         }
         return insets;
@@ -7199,13 +7228,37 @@
     }
 
     @Override
-    @NonNull
-    public AnimationBounds dispatchWindowInsetsAnimationStarted(
-            @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
-        super.dispatchWindowInsetsAnimationStarted(animation, bounds);
+    public void setWindowInsetsAnimationCallback(@Nullable WindowInsetsAnimationCallback listener) {
+        super.setWindowInsetsAnimationCallback(listener);
+        mInsetsAnimationDispatchMode = listener != null
+                ? listener.getDispatchMode()
+                : DISPATCH_MODE_CONTINUE_ON_SUBTREE;
+    }
+
+    @Override
+    public void dispatchWindowInsetsAnimationPrepare(
+            @NonNull InsetsAnimation animation) {
+        super.dispatchWindowInsetsAnimationPrepare(animation);
+        if (mInsetsAnimationDispatchMode == DISPATCH_MODE_STOP) {
+            return;
+        }
         final int count = getChildCount();
         for (int i = 0; i < count; i++) {
-            getChildAt(i).dispatchWindowInsetsAnimationStarted(animation, bounds);
+            getChildAt(i).dispatchWindowInsetsAnimationPrepare(animation);
+        }
+    }
+
+    @Override
+    @NonNull
+    public AnimationBounds dispatchWindowInsetsAnimationStart(
+            @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
+        bounds = super.dispatchWindowInsetsAnimationStart(animation, bounds);
+        if (mInsetsAnimationDispatchMode == DISPATCH_MODE_STOP) {
+            return bounds;
+        }
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            getChildAt(i).dispatchWindowInsetsAnimationStart(animation, bounds);
         }
         return bounds;
     }
@@ -7214,6 +7267,9 @@
     @NonNull
     public WindowInsets dispatchWindowInsetsAnimationProgress(@NonNull WindowInsets insets) {
         insets = super.dispatchWindowInsetsAnimationProgress(insets);
+        if (mInsetsAnimationDispatchMode == DISPATCH_MODE_STOP) {
+            return insets;
+        }
         final int count = getChildCount();
         for (int i = 0; i < count; i++) {
             getChildAt(i).dispatchWindowInsetsAnimationProgress(insets);
@@ -7222,11 +7278,14 @@
     }
 
     @Override
-    public void dispatchWindowInsetsAnimationFinished(@NonNull InsetsAnimation animation) {
-        super.dispatchWindowInsetsAnimationFinished(animation);
+    public void dispatchWindowInsetsAnimationFinish(@NonNull InsetsAnimation animation) {
+        super.dispatchWindowInsetsAnimationFinish(animation);
+        if (mInsetsAnimationDispatchMode == DISPATCH_MODE_STOP) {
+            return;
+        }
         final int count = getChildCount();
         for (int i = 0; i < count; i++) {
-            getChildAt(i).dispatchWindowInsetsAnimationFinished(animation);
+            getChildAt(i).dispatchWindowInsetsAnimationFinish(animation);
         }
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index bf8dc65..f5cfbec 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -18,6 +18,8 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
+import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
+import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.View.PFLAG_DRAW_ANIMATION;
 import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
 import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
@@ -25,15 +27,24 @@
 import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
 import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
 import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+import static android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+import static android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE;
 import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER;
 import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM;
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE;
+import static android.view.WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_TOUCH;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
 import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_APPEARANCE_CONTROLLED;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FIT_INSETS_CONTROLLED;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
@@ -105,11 +116,13 @@
 import android.util.SparseArray;
 import android.util.TimeUtils;
 import android.util.TypedValue;
+import android.view.InsetsState.InternalInsetsType;
 import android.view.Surface.OutOfResourcesException;
 import android.view.SurfaceControl.Transaction;
 import android.view.View.AttachInfo;
 import android.view.View.FocusDirection;
 import android.view.View.MeasureSpec;
+import android.view.Window.OnContentApplyWindowInsetsListener;
 import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
@@ -417,12 +430,17 @@
 
     @UnsupportedAppUsage
     final View.AttachInfo mAttachInfo;
+    final SystemUiVisibilityInfo mCompatibleVisibilityInfo;
     InputQueue.Callback mInputQueueCallback;
     InputQueue mInputQueue;
     @UnsupportedAppUsage
     FallbackEventHandler mFallbackEventHandler;
     Choreographer mChoreographer;
 
+    // used in relayout to get SurfaceControl size
+    // for BLAST adapter surface setup
+    private final Point mSurfaceSize = new Point();
+
     final Rect mTempRect; // used in the transaction to not thrash the heap.
     final Rect mVisRect; // used to retrieve visible rect of focused view.
     private final Rect mTempBoundsRect = new Rect(); // used to set the size of the bounds surface.
@@ -453,7 +471,6 @@
     boolean mReportNextDraw;
     boolean mFullRedrawNeeded;
     boolean mNewSurfaceNeeded;
-    boolean mLastWasImTarget;
     boolean mForceNextWindowRelayout;
     CountDownLatch mWindowDrawCountDown;
 
@@ -619,6 +636,16 @@
             InputEventConsistencyVerifier.isInstrumentationEnabled() ?
                     new InputEventConsistencyVerifier(this, 0) : null;
 
+    private final ImeFocusController mImeFocusController;
+
+    /**
+     * @return {@link ImeFocusController} for this instance.
+     */
+    @NonNull
+    public ImeFocusController getImeFocusController() {
+        return mImeFocusController;
+    }
+
     private final InsetsController mInsetsController = new InsetsController(this);
 
     private final GestureExclusionTracker mGestureExclusionTracker = new GestureExclusionTracker();
@@ -665,6 +692,7 @@
         mAdded = false;
         mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
                 context);
+        mCompatibleVisibilityInfo = new SystemUiVisibilityInfo();
         mAccessibilityManager = AccessibilityManager.getInstance(context);
         mAccessibilityManager.addAccessibilityStateChangeListener(
                 mAccessibilityInteractionConnectionManager, mHandler);
@@ -704,6 +732,7 @@
         }
 
         loadSystemProperties();
+        mImeFocusController = new ImeFocusController(this);
     }
 
     public static void addFirstDrawHandler(Runnable callback) {
@@ -727,6 +756,11 @@
         mActivityConfigCallback = callback;
     }
 
+    public void setOnContentApplyWindowInsetsListener(OnContentApplyWindowInsetsListener listener) {
+        mAttachInfo.mContentOnApplyWindowInsetsListener = listener;
+        requestFitSystemWindows();
+    }
+
     public void addWindowCallbacks(WindowCallbacks callback) {
         synchronized (mWindowCallbacks) {
             mWindowCallbacks.add(callback);
@@ -826,6 +860,10 @@
                 if (mWindowAttributes.packageName == null) {
                     mWindowAttributes.packageName = mBasePackageName;
                 }
+                if (WindowManagerGlobal.USE_BLAST_ADAPTER) {
+                    mWindowAttributes.privateFlags |=
+                        WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
+                }
                 attrs = mWindowAttributes;
                 setTag();
 
@@ -1050,11 +1088,6 @@
         }
     }
 
-    /** Whether the window is in local focus mode or not */
-    private boolean isInLocalFocusMode() {
-        return (mWindowAttributes.flags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0;
-    }
-
     @UnsupportedAppUsage
     public int getWindowFlags() {
         return mWindowAttributes.flags;
@@ -1308,7 +1341,7 @@
             mWindowAttributes.privateFlags |= compatibleWindowFlag;
 
             if (WindowManagerGlobal.USE_BLAST_ADAPTER) {
-                mWindowAttributes.privateFlags =
+                mWindowAttributes.privateFlags |=
                     WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
             }
 
@@ -1415,6 +1448,7 @@
         // Get new instance of display based on current display adjustments. It may be updated later
         // if moving between the displays also involved a configuration change.
         updateInternalDisplay(displayId, mView.getResources());
+        mImeFocusController.onMovedToDisplay();
         mAttachInfo.mDisplayState = mDisplay.getState();
         // Internal state updated, now notify the view hierarchy.
         mView.dispatchMovedToDisplay(mDisplay, config);
@@ -1464,6 +1498,7 @@
             return;
         }
         mApplyInsetsRequested = true;
+        requestLayout();
 
         // If this changes during traversal, no need to schedule another one as it will dispatch it
         // during the current traversal.
@@ -1852,6 +1887,9 @@
             mAttachInfo.mSystemUiVisibility &= ~mAttachInfo.mDisabledSystemUiVisibility;
             WindowManager.LayoutParams params = mWindowAttributes;
             mAttachInfo.mSystemUiVisibility |= getImpliedSystemUiVisibility(params);
+            mCompatibleVisibilityInfo.globalVisibility =
+                    (mCompatibleVisibilityInfo.globalVisibility & ~View.SYSTEM_UI_FLAG_LOW_PROFILE)
+                            | (mAttachInfo.mSystemUiVisibility & View.SYSTEM_UI_FLAG_LOW_PROFILE);
             if (mAttachInfo.mKeepScreenOn != oldScreenOn
                     || mAttachInfo.mSystemUiVisibility != params.subtreeSystemUiVisibility
                     || mAttachInfo.mHasSystemUiListeners != params.hasSystemUiListeners) {
@@ -1877,6 +1915,38 @@
         return vis;
     }
 
+    /**
+     * Update the compatible system UI visibility for dispatching it to the legacy app.
+     *
+     * @param type Indicates which type of the insets source we are handling.
+     * @param visible True if the insets source is visible.
+     * @param hasControl True if we can control the insets source.
+     */
+    void updateCompatSysUiVisibility(@InternalInsetsType int type, boolean visible,
+            boolean hasControl) {
+        if ((type != ITYPE_STATUS_BAR && type != ITYPE_NAVIGATION_BAR)
+                || ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL) {
+            return;
+        }
+        final SystemUiVisibilityInfo info = mCompatibleVisibilityInfo;
+        final int systemUiFlag = type == ITYPE_STATUS_BAR
+                ? View.SYSTEM_UI_FLAG_FULLSCREEN
+                : View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+        final boolean wasVisible = (info.globalVisibility & systemUiFlag) == 0;
+        if (visible) {
+            info.globalVisibility &= ~systemUiFlag;
+            if (!wasVisible && hasControl) {
+                // The local system UI visibility can only be cleared while we have the control.
+                info.localChanges |= systemUiFlag;
+            }
+        } else {
+            info.globalVisibility |= systemUiFlag;
+        }
+        if (mAttachInfo.mGlobalSystemUiVisibility != info.globalVisibility) {
+            scheduleTraversals();
+        }
+    }
+
     @VisibleForTesting
     public static void adjustLayoutParamsForCompatibility(WindowManager.LayoutParams inOutParams) {
         if (sNewInsetsMode != NEW_INSETS_MODE_FULL) {
@@ -1887,19 +1957,37 @@
         final int type = inOutParams.type;
         final int adjust = inOutParams.softInputMode & SOFT_INPUT_MASK_ADJUST;
 
-        if ((sysUiVis & SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0) {
-            inOutParams.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
-        } else if ((sysUiVis & SYSTEM_UI_FLAG_IMMERSIVE) != 0) {
-            inOutParams.insetsFlags.behavior = BEHAVIOR_SHOW_BARS_BY_SWIPE;
+        if ((inOutParams.privateFlags & PRIVATE_FLAG_APPEARANCE_CONTROLLED) == 0) {
+            inOutParams.insetsFlags.appearance = 0;
+            if ((sysUiVis & SYSTEM_UI_FLAG_LOW_PROFILE) != 0) {
+                inOutParams.insetsFlags.appearance |= APPEARANCE_LOW_PROFILE_BARS;
+            }
+            if ((sysUiVis & SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0) {
+                inOutParams.insetsFlags.appearance |= APPEARANCE_LIGHT_STATUS_BARS;
+            }
+            if ((sysUiVis & SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) != 0) {
+                inOutParams.insetsFlags.appearance |= APPEARANCE_LIGHT_NAVIGATION_BARS;
+            }
+        }
+
+        if ((inOutParams.privateFlags & PRIVATE_FLAG_BEHAVIOR_CONTROLLED) == 0) {
+            if ((sysUiVis & SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0
+                    || (flags & FLAG_FULLSCREEN) != 0) {
+                inOutParams.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
+            } else if ((sysUiVis & SYSTEM_UI_FLAG_IMMERSIVE) != 0) {
+                inOutParams.insetsFlags.behavior = BEHAVIOR_SHOW_BARS_BY_SWIPE;
+            } else {
+                inOutParams.insetsFlags.behavior = BEHAVIOR_SHOW_BARS_BY_TOUCH;
+            }
         }
 
         if ((inOutParams.privateFlags & PRIVATE_FLAG_FIT_INSETS_CONTROLLED) != 0) {
             return;
         }
 
-        int types = inOutParams.getFitWindowInsetsTypes();
-        int sides = inOutParams.getFitWindowInsetsSides();
-        boolean ignoreVis = inOutParams.getFitIgnoreVisibility();
+        int types = inOutParams.getFitInsetsTypes();
+        int sides = inOutParams.getFitInsetsSides();
+        boolean ignoreVis = inOutParams.isFitInsetsIgnoringVisibility();
 
         if (((sysUiVis & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0
                 || (flags & FLAG_LAYOUT_IN_SCREEN) != 0)
@@ -1916,9 +2004,9 @@
                 && adjust == SOFT_INPUT_ADJUST_RESIZE) {
             types |= Type.ime();
         }
-        inOutParams.setFitWindowInsetsTypes(types);
-        inOutParams.setFitWindowInsetsSides(sides);
-        inOutParams.setFitIgnoreVisibility(ignoreVis);
+        inOutParams.setFitInsetsTypes(types);
+        inOutParams.setFitInsetsSides(sides);
+        inOutParams.setFitInsetsIgnoringVisibility(ignoreVis);
 
         // The fitting of insets are not really controlled by the clients, so we remove the flag.
         inOutParams.privateFlags &= ~PRIVATE_FLAG_FIT_INSETS_CONTROLLED;
@@ -2072,7 +2160,9 @@
             mLastWindowInsets = mInsetsController.calculateInsets(
                     mContext.getResources().getConfiguration().isScreenRound(),
                     mAttachInfo.mAlwaysConsumeSystemBars, displayCutout,
-                    contentInsets, stableInsets, mWindowAttributes.softInputMode);
+                    contentInsets, stableInsets, mWindowAttributes.softInputMode,
+                    (mWindowAttributes.systemUiVisibility
+                            | mWindowAttributes.subtreeSystemUiVisibility));
         }
         return mLastWindowInsets;
     }
@@ -2102,6 +2192,12 @@
         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
     }
 
+    private void updateVisibleInsets() {
+        Rect visibleInsets = mInsetsController.calculateVisibleInsets(mPendingVisibleInsets,
+                mWindowAttributes.softInputMode);
+        mAttachInfo.mVisibleInsets.set(visibleInsets);
+    }
+
     InsetsController getInsetsController() {
         return mInsetsController;
     }
@@ -2250,7 +2346,7 @@
                     insetsChanged = true;
                 }
                 if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) {
-                    mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
+                    updateVisibleInsets();
                     if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: "
                             + mAttachInfo.mVisibleInsets);
                 }
@@ -2287,6 +2383,11 @@
             mAttachInfo.mForceReportNewAttributes = false;
             params = lp;
         }
+        if (sNewInsetsMode == NEW_INSETS_MODE_FULL) {
+            adjustLayoutParamsForCompatibility(lp);
+            controlInsetsForCompatibility(lp);
+            handleDispatchSystemUiVisibilityChanged(mCompatibleVisibilityInfo);
+        }
 
         if (mFirst || mAttachInfo.mViewVisibilityChanged) {
             mAttachInfo.mViewVisibilityChanged = false;
@@ -2316,6 +2417,7 @@
 
         if (mApplyInsetsRequested) {
             mApplyInsetsRequested = false;
+            updateVisibleInsets();
             dispatchApplyInsets(host);
             if (mLayoutRequested) {
                 // Short-circuit catching a new layout request here, so
@@ -2379,8 +2481,6 @@
                     && !PixelFormat.formatHasAlpha(params.format)) {
                 params.format = PixelFormat.TRANSLUCENT;
             }
-            adjustLayoutParamsForCompatibility(params);
-            controlInsetsForCompatibility(params);
         }
 
         if (mFirst || windowShouldResize || insetsChanged ||
@@ -2500,7 +2600,7 @@
                     contentInsetsChanged = true;
                 }
                 if (visibleInsetsChanged) {
-                    mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
+                    updateVisibleInsets();
                     if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: "
                             + mAttachInfo.mVisibleInsets);
                 }
@@ -2880,19 +2980,7 @@
         mViewVisibility = viewVisibility;
         mHadWindowFocus = hasWindowFocus;
 
-        if (hasWindowFocus && !isInLocalFocusMode()) {
-            final boolean imTarget = WindowManager.LayoutParams
-                    .mayUseInputMethod(mWindowAttributes.flags);
-            if (imTarget != mLastWasImTarget) {
-                mLastWasImTarget = imTarget;
-                InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
-                if (imm != null && imTarget) {
-                    imm.onPreWindowFocus(mView, hasWindowFocus);
-                    imm.onPostWindowFocus(mView, mView.findFocus(),
-                            mWindowAttributes.softInputMode, mWindowAttributes.flags);
-                }
-            }
-        }
+        mImeFocusController.onTraversal(hasWindowFocus, mWindowAttributes);
 
         // Remember if we must report the next draw.
         if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
@@ -3060,14 +3148,10 @@
             }
 
             mAttachInfo.mHasWindowFocus = hasWindowFocus;
+            mAttachInfo.mHasImeFocus = mImeFocusController.updateImeFocusable(
+                    mWindowAttributes, true /* force */);
+            mImeFocusController.onPreWindowFocus(hasWindowFocus, mWindowAttributes);
 
-            mLastWasImTarget = WindowManager.LayoutParams
-                    .mayUseInputMethod(mWindowAttributes.flags);
-
-            InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
-            if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
-                imm.onPreWindowFocus(mView, hasWindowFocus);
-            }
             if (mView != null) {
                 mAttachInfo.mKeyDispatchState.reset();
                 mView.dispatchWindowFocusChanged(hasWindowFocus);
@@ -3079,11 +3163,10 @@
 
             // Note: must be done after the focus change callbacks,
             // so all of the view state is set up correctly.
+            mImeFocusController.onPostWindowFocus(mView.findFocus(), hasWindowFocus,
+                    mWindowAttributes);
+
             if (hasWindowFocus) {
-                if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
-                    imm.onPostWindowFocus(mView, mView.findFocus(),
-                            mWindowAttributes.softInputMode, mWindowAttributes.flags);
-                }
                 // Clear the forward bit.  We can just do this directly, since
                 // the window manager doesn't care about it.
                 mWindowAttributes.softInputMode &=
@@ -4879,10 +4962,7 @@
                     enqueueInputEvent(event, null, 0, true);
                 } break;
                 case MSG_CHECK_FOCUS: {
-                    InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
-                    if (imm != null) {
-                        imm.checkFocus();
-                    }
+                    getImeFocusController().checkFocus(false, true);
                 } break;
                 case MSG_CLOSE_SYSTEM_DIALOGS: {
                     if (mView != null) {
@@ -5446,23 +5526,20 @@
 
         @Override
         protected int onProcess(QueuedInputEvent q) {
-            if (mLastWasImTarget && !isInLocalFocusMode()) {
-                InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
-                if (imm != null) {
-                    final InputEvent event = q.mEvent;
-                    if (DEBUG_IMF) Log.v(mTag, "Sending input event to IME: " + event);
-                    int result = imm.dispatchInputEvent(event, q, this, mHandler);
-                    if (result == InputMethodManager.DISPATCH_HANDLED) {
-                        return FINISH_HANDLED;
-                    } else if (result == InputMethodManager.DISPATCH_NOT_HANDLED) {
-                        // The IME could not handle it, so skip along to the next InputStage
-                        return FORWARD;
-                    } else {
-                        return DEFER; // callback will be invoked later
-                    }
-                }
+            final int result = mImeFocusController.onProcessImeInputStage(
+                    q, q.mEvent, mWindowAttributes, this);
+            switch (result) {
+                case InputMethodManager.DISPATCH_IN_PROGRESS:
+                    // callback will be invoked later
+                    return DEFER;
+                case InputMethodManager.DISPATCH_NOT_HANDLED:
+                    // The IME could not handle it, so skip along to the next InputStage
+                    return FORWARD;
+                case InputMethodManager.DISPATCH_HANDLED:
+                    return FINISH_HANDLED;
+                default:
+                    throw new IllegalStateException("Unexpected result=" + result);
             }
-            return FORWARD;
         }
 
         @Override
@@ -7102,7 +7179,7 @@
     }
 
     public void handleDispatchSystemUiVisibilityChanged(SystemUiVisibilityInfo args) {
-        if (mSeq != args.seq) {
+        if (mSeq != args.seq && sNewInsetsMode != NEW_INSETS_MODE_FULL) {
             // The sequence has changed, so we need to update our value and make
             // sure to do a traversal afterward so the window manager is given our
             // most recent data.
@@ -7113,6 +7190,7 @@
         if (mView == null) return;
         if (args.localChanges != 0) {
             mView.updateLocalSystemUiVisibility(args.localValue, args.localChanges);
+            args.localChanges = 0;
         }
 
         int visibility = args.globalVisibility&View.SYSTEM_UI_CLEARABLE_FLAGS;
@@ -7266,14 +7344,13 @@
                 insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
                 mTmpFrame, mPendingContentInsets, mPendingVisibleInsets,
                 mPendingStableInsets, mPendingBackDropFrame, mPendingDisplayCutout,
-                mPendingMergedConfiguration, mSurfaceControl, mTempInsets);
+                mPendingMergedConfiguration, mSurfaceControl, mTempInsets, mSurfaceSize);
         if (mSurfaceControl.isValid()) {
             if (!WindowManagerGlobal.USE_BLAST_ADAPTER) {
                 mSurface.copyFrom(mSurfaceControl);
             } else {
-                mSurface.transferFrom(getOrCreateBLASTSurface(
-                    (int) (mView.getMeasuredWidth() * appScale + 0.5f),
-                    (int) (mView.getMeasuredHeight() * appScale + 0.5f)));
+                mSurface.transferFrom(getOrCreateBLASTSurface(mSurfaceSize.x,
+                        mSurfaceSize.y));
             }
         } else {
             destroySurface();
@@ -8032,6 +8109,11 @@
         }
 
         @Override
+        public void onFocusEvent(boolean hasFocus, boolean inTouchMode) {
+            windowFocusChanged(hasFocus, inTouchMode);
+        }
+
+        @Override
         public void dispose() {
             unscheduleConsumeBatchedInput();
             super.dispose();
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index a1894f3..0ef4e33 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -33,6 +33,7 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.graphics.Insets;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -46,6 +47,9 @@
 import android.transition.Scene;
 import android.transition.Transition;
 import android.transition.TransitionManager;
+import android.util.Pair;
+import android.view.View.OnApplyWindowInsetsListener;
+import android.view.ViewGroup.LayoutParams;
 import android.view.WindowInsets.Side.InsetsSide;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.accessibility.AccessibilityEvent;
@@ -692,6 +696,32 @@
                 int dropCountSinceLastInvocation);
     }
 
+    /**
+     * Listener for applying window insets on the content of a window in a custom way.
+     *
+     * <p>Apps may choose to implement this interface if they want to apply custom policy
+     * to the way that window insets are treated for fitting root-level content views.
+     *
+     * @see Window#setOnContentApplyWindowInsetsListener(OnContentApplyWindowInsetsListener)
+     */
+    public interface OnContentApplyWindowInsetsListener {
+
+        /**
+         * Called when the window needs to apply insets on the container of its content view which
+         * are set by calling {@link #setContentView}. The method should determine what insets to
+         * apply on the container of the root level content view and what should be dispatched to
+         * the content view's
+         * {@link View#setOnApplyWindowInsetsListener(OnApplyWindowInsetsListener)} through the view
+         * hierarchy.
+         *
+         * @param insets The root level insets that are about to be dispatched
+         * @return A pair, with the first element containing the insets to apply as margin to the
+         *         root-level content views, and the second element determining what should be
+         *         dispatched to the content view.
+         */
+        @NonNull Pair<Insets, WindowInsets> onContentApplyWindowInsets(
+                @NonNull WindowInsets insets);
+    }
 
     public Window(Context context) {
         mContext = context;
@@ -1281,57 +1311,33 @@
     }
 
     /**
-     * A shortcut for {@link WindowManager.LayoutParams#setFitWindowInsetsTypes(int)}
-     * @hide pending unhide
+     * Sets the listener to be invoked when fitting root-level content views.
+     * <p>
+     * By default, a listener that inspects the now deprecated {@link View#SYSTEM_UI_LAYOUT_FLAGS}
+     * as well the {@link WindowManager.LayoutParams#SOFT_INPUT_ADJUST_RESIZE} flag is installed and
+     * fits content according to these flags.
+     * </p>
+     * @param contentOnApplyWindowInsetsListener The listener to use for fitting root-level content
+     *                                           views, or {@code null} to disable any kind of
+     *                                           content fitting on the window level and letting the
+     *                                           {@link WindowInsets} pass through to the content
+     *                                           view.
+     * @see OnContentApplyWindowInsetsListener
      */
-    public void setFitWindowInsetsTypes(@InsetsType int types) {
-        final WindowManager.LayoutParams attrs = getAttributes();
-        attrs.setFitWindowInsetsTypes(types);
-        dispatchWindowAttributesChanged(attrs);
+    public void setOnContentApplyWindowInsetsListener(
+            @Nullable OnContentApplyWindowInsetsListener contentOnApplyWindowInsetsListener) {
     }
 
     /**
-     * A shortcut for {@link WindowManager.LayoutParams#setFitWindowInsetsSides(int)}
-     * @hide pending unhide
+     * Resets the listener set via {@link #setOnContentApplyWindowInsetsListener} to the default
+     * state.
+     * <p>
+     * By default, a listener that inspects the now deprecated {@link View#SYSTEM_UI_LAYOUT_FLAGS}
+     * as well the {@link WindowManager.LayoutParams#SOFT_INPUT_ADJUST_RESIZE} flag is installed and
+     * fits content according to these flags.
+     * </p>
      */
-    public void setFitWindowInsetsSides(@InsetsSide int sides) {
-        final WindowManager.LayoutParams attrs = getAttributes();
-        attrs.setFitWindowInsetsSides(sides);
-        dispatchWindowAttributesChanged(attrs);
-    }
-
-    /**
-     * A shortcut for {@link WindowManager.LayoutParams#setFitIgnoreVisibility(boolean)}
-     * @hide pending unhide
-     */
-    public void setFitIgnoreVisibility(boolean ignore) {
-        final WindowManager.LayoutParams attrs = getAttributes();
-        attrs.setFitIgnoreVisibility(ignore);
-        dispatchWindowAttributesChanged(attrs);
-    }
-
-    /**
-     * A shortcut for {@link WindowManager.LayoutParams#getFitWindowInsetsTypes}
-     * @hide pending unhide
-     */
-    public @InsetsType int getFitWindowInsetsTypes() {
-        return getAttributes().getFitWindowInsetsTypes();
-    }
-
-    /**
-     * A shortcut for {@link WindowManager.LayoutParams#getFitWindowInsetsSides()}
-     * @hide pending unhide
-     */
-    public @InsetsSide int getFitWindowInsetsSides() {
-        return getAttributes().getFitWindowInsetsSides();
-    }
-
-    /**
-     * A shortcut for {@link WindowManager.LayoutParams#getFitIgnoreVisibility()}
-     * @hide pending unhide
-     */
-    public boolean getFitIgnoreVisibility() {
-        return getAttributes().getFitIgnoreVisibility();
+    public void resetOnContentApplyWindowInsetsListener() {
     }
 
     /**
diff --git a/core/java/android/view/WindowContainerTransaction.java b/core/java/android/view/WindowContainerTransaction.java
index 607a870..c55caa3 100644
--- a/core/java/android/view/WindowContainerTransaction.java
+++ b/core/java/android/view/WindowContainerTransaction.java
@@ -62,6 +62,30 @@
         return this;
     }
 
+    /**
+     * Notify activies within the hiearchy of a container that they have entered picture-in-picture
+     * mode with the given bounds.
+     */
+    public WindowContainerTransaction scheduleFinishEnterPip(IWindowContainer container,
+            Rect bounds) {
+        Change chg = getOrCreateChange(container.asBinder());
+        chg.mSchedulePipCallback = true;
+        chg.mPinnedBounds = new Rect(bounds);
+        return this;
+    }
+
+    /**
+     * Sets whether a container or any of its children can be focusable. When {@code false}, no
+     * child can be focused; however, when {@code true}, it is still possible for children to be
+     * non-focusable due to WM policy.
+     */
+    public WindowContainerTransaction setFocusable(IWindowContainer container, boolean focusable) {
+        Change chg = getOrCreateChange(container.asBinder());
+        chg.mFocusable = focusable;
+        chg.mChangeMask |= Change.CHANGE_FOCUSABLE;
+        return this;
+    }
+
     public Map<IBinder, Change> getChanges() {
         return mChanges;
     }
@@ -100,22 +124,48 @@
      * @hide
      */
     public static class Change implements Parcelable {
+        public static final int CHANGE_FOCUSABLE = 1;
+
         private final Configuration mConfiguration = new Configuration();
+        private boolean mFocusable = true;
+        private int mChangeMask = 0;
         private @ActivityInfo.Config int mConfigSetMask = 0;
         private @WindowConfiguration.WindowConfig int mWindowSetMask = 0;
 
+        private boolean mSchedulePipCallback = false;
+        private Rect mPinnedBounds = null;
+
         public Change() {}
 
         protected Change(Parcel in) {
             mConfiguration.readFromParcel(in);
+            mFocusable = in.readBoolean();
+            mChangeMask = in.readInt();
             mConfigSetMask = in.readInt();
             mWindowSetMask = in.readInt();
+            mSchedulePipCallback = (in.readInt() != 0);
+            if (mSchedulePipCallback ) {
+                mPinnedBounds = new Rect();
+                mPinnedBounds.readFromParcel(in);
+            }
         }
 
         public Configuration getConfiguration() {
             return mConfiguration;
         }
 
+        /** Gets the requested focusable value */
+        public boolean getFocusable() {
+            if ((mChangeMask & CHANGE_FOCUSABLE) == 0) {
+                throw new RuntimeException("Focusable not set. check CHANGE_FOCUSABLE first");
+            }
+            return mFocusable;
+        }
+
+        public int getChangeMask() {
+            return mChangeMask;
+        }
+
         @ActivityInfo.Config
         public int getConfigSetMask() {
             return mConfigSetMask;
@@ -126,6 +176,14 @@
             return mWindowSetMask;
         }
 
+        /**
+         * Returns the bounds to be used for scheduling the enter pip callback
+         * or null if no callback is to be scheduled.
+         */
+        public Rect getEnterPipBounds() {
+            return mPinnedBounds;
+        }
+
         @Override
         public String toString() {
             final boolean changesBounds =
@@ -142,6 +200,9 @@
             if (changesSss) {
                 sb.append("ssw:" + mConfiguration.smallestScreenWidthDp + ",");
             }
+            if ((mChangeMask & CHANGE_FOCUSABLE) != 0) {
+                sb.append("focusable:" + mFocusable + ",");
+            }
             sb.append("}");
             return sb.toString();
         }
@@ -149,8 +210,15 @@
         @Override
         public void writeToParcel(Parcel dest, int flags) {
             mConfiguration.writeToParcel(dest, flags);
+            dest.writeBoolean(mFocusable);
+            dest.writeInt(mChangeMask);
             dest.writeInt(mConfigSetMask);
             dest.writeInt(mWindowSetMask);
+
+            dest.writeInt(mSchedulePipCallback ? 1 : 0);
+            if (mSchedulePipCallback ) {
+                mPinnedBounds.writeToParcel(dest, flags);
+            }
         }
 
         @Override
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 9df131d..a6c311e1 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -27,8 +27,11 @@
 import static android.view.WindowInsets.Type.SYSTEM_GESTURES;
 import static android.view.WindowInsets.Type.TAPPABLE_ELEMENT;
 import static android.view.WindowInsets.Type.all;
+import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.indexOf;
 import static android.view.WindowInsets.Type.systemBars;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
 
 import android.annotation.IntDef;
 import android.annotation.IntRange;
@@ -39,10 +42,13 @@
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.util.SparseArray;
+import android.view.View.OnApplyWindowInsetsListener;
 import android.view.WindowInsets.Type.InsetsType;
+import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethod;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
@@ -88,15 +94,23 @@
     private final boolean mDisplayCutoutConsumed;
 
     private final int mCompatInsetTypes;
+    private final boolean mCompatIgnoreVisibility;
 
     /**
-     * Since new insets may be added in the future that existing apps couldn't
-     * know about, this fully empty constant shouldn't be made available to apps
-     * since it would allow them to inadvertently consume unknown insets by returning it.
-     * @hide
+     * A {@link WindowInsets} instance for which {@link #isConsumed()} returns {@code true}.
+     * <p>
+     * This can be used during insets dispatch in the view hierarchy by returning this value from
+     * {@link View#onApplyWindowInsets(WindowInsets)} or
+     * {@link OnApplyWindowInsetsListener#onApplyWindowInsets(View, WindowInsets)} to stop dispatch
+     * the insets to its children to avoid traversing the entire view hierarchy.
+     * <p>
+     * The application should return this instance once it has taken care of all insets on a certain
+     * level in the view hierarchy, and doesn't need to dispatch to its children anymore for better
+     * performance.
+     *
+     * @see #isConsumed()
      */
-    @UnsupportedAppUsage
-    public static final WindowInsets CONSUMED;
+    public static final @NonNull WindowInsets CONSUMED;
 
     static {
         CONSUMED = new WindowInsets((Rect) null, null, false, false, null);
@@ -114,7 +128,8 @@
             boolean isRound, boolean alwaysConsumeSystemBars, DisplayCutout displayCutout) {
         this(createCompatTypeMap(systemWindowInsetsRect), createCompatTypeMap(stableInsetsRect),
                 createCompatVisibilityMap(createCompatTypeMap(systemWindowInsetsRect)),
-                isRound, alwaysConsumeSystemBars, displayCutout, systemBars());
+                isRound, alwaysConsumeSystemBars, displayCutout, systemBars(),
+                false /* compatIgnoreVisibility */);
     }
 
     /**
@@ -133,7 +148,8 @@
             @Nullable Insets[] typeMaxInsetsMap,
             boolean[] typeVisibilityMap,
             boolean isRound,
-            boolean alwaysConsumeSystemBars, DisplayCutout displayCutout, int compatInsetTypes) {
+            boolean alwaysConsumeSystemBars, DisplayCutout displayCutout, int compatInsetTypes,
+            boolean compatIgnoreVisibility) {
         mSystemWindowInsetsConsumed = typeInsetsMap == null;
         mTypeInsetsMap = mSystemWindowInsetsConsumed
                 ? new Insets[SIZE]
@@ -148,6 +164,7 @@
         mIsRound = isRound;
         mAlwaysConsumeSystemBars = alwaysConsumeSystemBars;
         mCompatInsetTypes = compatInsetTypes;
+        mCompatIgnoreVisibility = compatIgnoreVisibility;
 
         mDisplayCutoutConsumed = displayCutout == null;
         mDisplayCutout = (mDisplayCutoutConsumed || displayCutout.isEmpty())
@@ -164,7 +181,8 @@
                 src.mStableInsetsConsumed ? null : src.mTypeMaxInsetsMap,
                 src.mTypeVisibilityMap, src.mIsRound,
                 src.mAlwaysConsumeSystemBars, displayCutoutCopyConstructorArgument(src),
-                src.mCompatInsetTypes);
+                src.mCompatInsetTypes,
+                src.mCompatIgnoreVisibility);
     }
 
     private static DisplayCutout displayCutoutCopyConstructorArgument(WindowInsets w) {
@@ -216,7 +234,7 @@
     @UnsupportedAppUsage
     public WindowInsets(Rect systemWindowInsets) {
         this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, false, null,
-                systemBars());
+                systemBars(), false /* compatIgnoreVisibility */);
     }
 
     /**
@@ -236,7 +254,8 @@
     /**
      * @hide
      */
-    static void assignCompatInsets(Insets[] typeInsetsMap, Rect insets) {
+    @VisibleForTesting
+    public static void assignCompatInsets(Insets[] typeInsetsMap, Rect insets) {
         typeInsetsMap[indexOf(STATUS_BARS)] = Insets.of(0, insets.top, 0, 0);
         typeInsetsMap[indexOf(NAVIGATION_BARS)] =
                 Insets.of(insets.left, 0, insets.right, insets.bottom);
@@ -282,10 +301,21 @@
      * </p>
      *
      * @return The system window insets
+     * @deprecated Use {@link #getInsets(int)} with {@link Type#systemBars()}
+     * instead.
      */
+    @Deprecated
     @NonNull
     public Insets getSystemWindowInsets() {
-        return getInsets(mTypeInsetsMap, mCompatInsetTypes);
+        Insets result = mCompatIgnoreVisibility
+                ? getInsetsIgnoringVisibility(mCompatInsetTypes & ~ime())
+                : getInsets(mCompatInsetTypes);
+
+        // We can't query max insets for IME, so we need to add it manually after.
+        if ((mCompatInsetTypes & ime()) != 0 && mCompatIgnoreVisibility) {
+            result = Insets.max(result, getInsets(ime()));
+        }
+        return result;
     }
 
     /**
@@ -301,25 +331,26 @@
     }
 
     /**
-     * Returns the maximum amount of insets a specific set of windows can cause, denoted by the
-     * {@code typeMask} bit mask of {@link InsetsType}s.
+     * Returns the insets a specific set of windows can cause, denoted by the
+     * {@code typeMask} bit mask of {@link InsetsType}s, regardless of whether that type is
+     * currently visible or not.
      *
-     * <p>The maximum insets represents the area of a a window that that <b>may</b> be partially
+     * <p>The insets represents the area of a a window that that <b>may</b> be partially
      * or fully obscured by the system window identified by {@code type}. This value does not
-     * change based on the visibility state of those elements. for example, if the status bar is
-     * normally shown, but temporarily hidden, the maximum inset will still provide the inset
+     * change based on the visibility state of those elements. For example, if the status bar is
+     * normally shown, but temporarily hidden, the inset returned here will still provide the inset
      * associated with the status bar being shown.</p>
      *
      * @param typeMask Bit mask of {@link InsetsType}s to query the insets for.
      * @return The insets.
      *
-     * @throws IllegalArgumentException If the caller tries to query {@link Type#ime()}. Maximum
-     *                                  insets are not available for this type as the height of the
+     * @throws IllegalArgumentException If the caller tries to query {@link Type#ime()}. Insets are
+     *                                  not available if the IME isn't visible as the height of the
      *                                  IME is dynamic depending on the {@link EditorInfo} of the
      *                                  currently focused view, as well as the UI state of the IME.
      */
     @NonNull
-    public Insets getMaxInsets(@InsetsType int typeMask) throws IllegalArgumentException {
+    public Insets getInsetsIgnoringVisibility(@InsetsType int typeMask) {
         if ((typeMask & IME) != 0) {
             throw new IllegalArgumentException("Unable to query the maximum insets for IME");
         }
@@ -354,7 +385,10 @@
      * </p>
      *
      * @return The left system window inset
+     * @deprecated Use {@link #getInsets(int)} with {@link Type#systemBars()}
+     * instead.
      */
+    @Deprecated
     public int getSystemWindowInsetLeft() {
         return getSystemWindowInsets().left;
     }
@@ -367,7 +401,10 @@
      * </p>
      *
      * @return The top system window inset
+     * @deprecated Use {@link #getInsets(int)} with {@link Type#systemBars()}
+     * instead.
      */
+    @Deprecated
     public int getSystemWindowInsetTop() {
         return getSystemWindowInsets().top;
     }
@@ -380,7 +417,10 @@
      * </p>
      *
      * @return The right system window inset
+     * @deprecated Use {@link #getInsets(int)} with {@link Type#systemBars()}
+     * instead.
      */
+    @Deprecated
     public int getSystemWindowInsetRight() {
         return getSystemWindowInsets().right;
     }
@@ -393,7 +433,10 @@
      * </p>
      *
      * @return The bottom system window inset
+     * @deprecated Use {@link #getInsets(int)} with {@link Type#systemBars()}
+     * instead.
      */
+    @Deprecated
     public int getSystemWindowInsetBottom() {
         return getSystemWindowInsets().bottom;
     }
@@ -406,7 +449,10 @@
      * </p>
      *
      * @return true if any of the system window inset values are nonzero
+     * @deprecated Use {@link #getInsets(int)} with {@link Type#systemBars()}
+     * instead.
      */
+    @Deprecated
     public boolean hasSystemWindowInsets() {
         return !getSystemWindowInsets().equals(Insets.NONE);
     }
@@ -437,7 +483,11 @@
      * Returns a copy of this WindowInsets with the cutout fully consumed.
      *
      * @return A modified copy of this WindowInsets
+     * @deprecated Consuming of different parts individually of a {@link WindowInsets} instance is
+     * deprecated, since {@link WindowInsets} contains many different insets. Use {@link #CONSUMED}
+     * instead to stop dispatching insets.
      */
+    @Deprecated
     @NonNull
     public WindowInsets consumeDisplayCutout() {
         return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap,
@@ -445,7 +495,7 @@
                 mTypeVisibilityMap,
                 mIsRound, mAlwaysConsumeSystemBars,
                 null /* displayCutout */,
-                mCompatInsetTypes);
+                mCompatInsetTypes, mCompatIgnoreVisibility);
     }
 
 
@@ -485,14 +535,18 @@
      * Returns a copy of this WindowInsets with the system window insets fully consumed.
      *
      * @return A modified copy of this WindowInsets
+     * @deprecated Consuming of different parts individually of a {@link WindowInsets} instance is
+     * deprecated, since {@link WindowInsets} contains many different insets. Use {@link #CONSUMED}
+     * instead to stop dispatching insets.
      */
+    @Deprecated
     @NonNull
     public WindowInsets consumeSystemWindowInsets() {
-        return new WindowInsets(null, mStableInsetsConsumed ? null : mTypeMaxInsetsMap,
+        return new WindowInsets(null, null,
                 mTypeVisibilityMap,
                 mIsRound, mAlwaysConsumeSystemBars,
                 displayCutoutCopyConstructorArgument(this),
-                mCompatInsetTypes);
+                mCompatInsetTypes, mCompatIgnoreVisibility);
     }
 
     // TODO(b/119190588): replace @code with @link below
@@ -559,7 +613,10 @@
      * associated with the status bar being shown.</p>
      *
      * @return The stable insets
+     * @deprecated Use {@link #getInsetsIgnoringVisibility(int)} with {@link Type#systemBars()}
+     * instead.
      */
+    @Deprecated
     @NonNull
     public Insets getStableInsets() {
         return getInsets(mTypeMaxInsetsMap, mCompatInsetTypes);
@@ -575,7 +632,10 @@
      * associated with the status bar being shown.</p>
      *
      * @return The top stable inset
+     * @deprecated Use {@link #getInsetsIgnoringVisibility(int)} with {@link Type#systemBars()}
+     * instead.
      */
+    @Deprecated
     public int getStableInsetTop() {
         return getStableInsets().top;
     }
@@ -590,7 +650,10 @@
      * associated with the status bar being shown.</p>
      *
      * @return The left stable inset
+     * @deprecated Use {@link #getInsetsIgnoringVisibility(int)} with {@link Type#systemBars()}
+     * instead.
      */
+    @Deprecated
     public int getStableInsetLeft() {
         return getStableInsets().left;
     }
@@ -605,7 +668,10 @@
      * associated with the status bar being shown.</p>
      *
      * @return The right stable inset
+     * @deprecated Use {@link #getInsetsIgnoringVisibility(int)} with {@link Type#systemBars()}
+     * instead.
      */
+    @Deprecated
     public int getStableInsetRight() {
         return getStableInsets().right;
     }
@@ -620,7 +686,10 @@
      * associated with the status bar being shown.</p>
      *
      * @return The bottom stable inset
+     * @deprecated Use {@link #getInsetsIgnoringVisibility(int)} with {@link Type#systemBars()}
+     * instead.
      */
+    @Deprecated
     public int getStableInsetBottom() {
         return getStableInsets().bottom;
     }
@@ -635,7 +704,10 @@
      * associated with the status bar being shown.</p>
      *
      * @return true if any of the stable inset values are nonzero
+     * @deprecated Use {@link #getInsetsIgnoringVisibility(int)} with {@link Type#systemBars()}
+     * instead.
      */
+    @Deprecated
     public boolean hasStableInsets() {
         return !getStableInsets().equals(Insets.NONE);
     }
@@ -671,7 +743,9 @@
      * system window insets} by {@link #consumeSystemWindowInsets()}.
      *
      * @see #getMandatorySystemGestureInsets
+     * @deprecated Use {@link #getInsets(int)} with {@link Type#systemGestures()} instead.
      */
+    @Deprecated
     @NonNull
     public Insets getSystemGestureInsets() {
         return getInsets(mTypeInsetsMap, SYSTEM_GESTURES);
@@ -699,7 +773,9 @@
      * system window insets} by {@link #consumeSystemWindowInsets()}.
      *
      * @see #getSystemGestureInsets
+     * @deprecated Use {@link #getInsets(int)} with {@link Type#mandatorySystemGestures()} instead.
      */
+    @Deprecated
     @NonNull
     public Insets getMandatorySystemGestureInsets() {
         return getInsets(mTypeInsetsMap, MANDATORY_SYSTEM_GESTURES);
@@ -725,7 +801,10 @@
      *
      * <p>This inset is consumed together with the {@link #getSystemWindowInsets()
      * system window insets} by {@link #consumeSystemWindowInsets()}.
+     *
+     * @deprecated Use {@link #getInsets(int)} with {@link Type#tappableElement()} instead.
      */
+    @Deprecated
     @NonNull
     public Insets getTappableElementInsets() {
         return getInsets(mTypeInsetsMap, TAPPABLE_ELEMENT);
@@ -735,13 +814,14 @@
      * Returns a copy of this WindowInsets with the stable insets fully consumed.
      *
      * @return A modified copy of this WindowInsets
+     * @deprecated Consuming of different parts individually of a {@link WindowInsets} instance is
+     * deprecated, since {@link WindowInsets} contains many different insets. Use {@link #CONSUMED}
+     * instead to stop dispatching insets.
      */
+    @Deprecated
     @NonNull
     public WindowInsets consumeStableInsets() {
-        return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap, null,
-                mTypeVisibilityMap, mIsRound, mAlwaysConsumeSystemBars,
-                displayCutoutCopyConstructorArgument(this),
-                mCompatInsetTypes);
+        return consumeSystemWindowInsets();
     }
 
     /**
@@ -826,7 +906,7 @@
                         : mDisplayCutout == null
                                 ? DisplayCutout.NO_CUTOUT
                                 : mDisplayCutout.inset(left, top, right, bottom),
-                mCompatInsetTypes);
+                mCompatInsetTypes, mCompatIgnoreVisibility);
     }
 
     @Override
@@ -949,7 +1029,9 @@
          *
          * @see #getSystemWindowInsets()
          * @return itself
+         * @deprecated Use {@link #setInsets(int, Insets)} with {@link Type#systemBars()}.
          */
+        @Deprecated
         @NonNull
         public Builder setSystemWindowInsets(@NonNull Insets systemWindowInsets) {
             Preconditions.checkNotNull(systemWindowInsets);
@@ -967,7 +1049,9 @@
          *
          * @see #getSystemGestureInsets()
          * @return itself
+         * @deprecated Use {@link #setInsets(int, Insets)} with {@link Type#systemGestures()}.
          */
+        @Deprecated
         @NonNull
         public Builder setSystemGestureInsets(@NonNull Insets insets) {
             WindowInsets.setInsets(mTypeInsetsMap, SYSTEM_GESTURES, insets);
@@ -987,7 +1071,10 @@
          *
          * @see #getMandatorySystemGestureInsets()
          * @return itself
+         * @deprecated Use {@link #setInsets(int, Insets)} with
+         *             {@link Type#mandatorySystemGestures()}.
          */
+        @Deprecated
         @NonNull
         public Builder setMandatorySystemGestureInsets(@NonNull Insets insets) {
             WindowInsets.setInsets(mTypeInsetsMap, MANDATORY_SYSTEM_GESTURES, insets);
@@ -1002,7 +1089,9 @@
          *
          * @see #getTappableElementInsets()
          * @return itself
+         * @deprecated Use {@link #setInsets(int, Insets)} with {@link Type#tappableElement()}.
          */
+        @Deprecated
         @NonNull
         public Builder setTappableElementInsets(@NonNull Insets insets) {
             WindowInsets.setInsets(mTypeInsetsMap, TAPPABLE_ELEMENT, insets);
@@ -1032,15 +1121,15 @@
         }
 
         /**
-         * Sets the maximum amount of insets a specific window type in pixels.
+         * Sets the insets a specific window type in pixels, while ignoring its visibility state.
          *
-         * <p>The maximum insets represents the area of a a window that that <b>may</b> be partially
-         * or fully obscured by the system windows identified by {@code typeMask}. This value does
-         * not change based on the visibility state of those elements. for example, if the status
-         * bar is normally shown, but temporarily hidden, the maximum inset will still provide the
+         * <p>The insets represents the area of a a window that that <b>may</b> be partially
+         * or fully obscured by the system window identified by {@code type}. This value does not
+         * change based on the visibility state of those elements. For example, if the status bar is
+         * normally shown, but temporarily hidden, the inset returned here will still provide the
          * inset associated with the status bar being shown.</p>
          *
-         * @see #getMaxInsets(int)
+         * @see #getInsetsIgnoringVisibility(int)
          *
          * @param typeMask The bitmask of {@link InsetsType} to set the insets for.
          * @param insets The insets to set.
@@ -1054,7 +1143,7 @@
          *                                  state of the IME.
          */
         @NonNull
-        public Builder setMaxInsets(@InsetsType int typeMask, @NonNull Insets insets)
+        public Builder setInsetsIgnoringVisibility(@InsetsType int typeMask, @NonNull Insets insets)
                 throws IllegalArgumentException{
             if (typeMask == IME) {
                 throw new IllegalArgumentException("Maximum inset not available for IME");
@@ -1098,7 +1187,10 @@
          *
          * @see #getStableInsets()
          * @return itself
+         * @deprecated Use {@link #setInsetsIgnoringVisibility(int, Insets)} with
+         *             {@link Type#systemBars()}.
          */
+        @Deprecated
         @NonNull
         public Builder setStableInsets(@NonNull Insets stableInsets) {
             Preconditions.checkNotNull(stableInsets);
@@ -1144,7 +1236,7 @@
             return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap,
                     mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap,
                     mIsRound, mAlwaysConsumeSystemBars, mDisplayCutout,
-                    systemBars());
+                    systemBars(), false /* compatIgnoreVisibility */);
         }
     }
 
@@ -1231,13 +1323,6 @@
         }
 
         /**
-         * @return An insets type representing decor that is being app-controlled.
-         */
-        public static @InsetsType int windowDecor() {
-            return WINDOW_DECOR;
-        }
-
-        /**
          * Returns an insets type representing the system gesture insets.
          *
          * <p>The system gesture insets represent the area of a window where system gestures have
@@ -1273,27 +1358,36 @@
         }
 
         /**
-         * @return All system bars. Includes {@link #statusBars()} as well as
+         * @return All system bars. Includes {@link #statusBars()}, {@link #captionBar()} as well as
          *         {@link #navigationBars()}, but not {@link #ime()}.
          */
         public static @InsetsType int systemBars() {
-            return STATUS_BARS | NAVIGATION_BARS;
+            return STATUS_BARS | NAVIGATION_BARS | CAPTION_BAR;
         }
 
         /**
          * @return All inset types combined.
          *
-         * TODO: Figure out if this makes sense at all, mixing e.g {@link #systemGestures()} and
-         *       {@link #ime()} does not seem very useful.
+         * @hide
          */
         public static @InsetsType int all() {
             return 0xFFFFFFFF;
         }
+
+        /**
+         * Checks whether the specified type is considered to be part of visible insets.
+         * @hide
+         */
+        public static boolean isVisibleInsetsType(int type,
+                @SoftInputModeFlags int softInputModeFlags) {
+            int softInputMode = softInputModeFlags & SOFT_INPUT_MASK_ADJUST;
+            return (type & Type.systemBars()) != 0
+                    || (softInputMode != SOFT_INPUT_ADJUST_NOTHING && (type & Type.ime()) != 0);
+        }
     }
 
     /**
      * Class that defines different sides for insets.
-     * @hide pending unhide
      */
     public static final class Side {
 
diff --git a/core/java/android/view/WindowInsetsAnimationCallback.java b/core/java/android/view/WindowInsetsAnimationCallback.java
index 5e71f27..1e04d02 100644
--- a/core/java/android/view/WindowInsetsAnimationCallback.java
+++ b/core/java/android/view/WindowInsetsAnimationCallback.java
@@ -17,12 +17,17 @@
 package android.view;
 
 import android.annotation.FloatRange;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.Insets;
+import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.animation.Interpolator;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Interface that allows the application to listen to animation events for windows that cause
  * insets.
@@ -30,14 +35,101 @@
 public interface WindowInsetsAnimationCallback {
 
     /**
-     * Called when an inset animation gets started.
+     * Return value for {@link #getDispatchMode()}: Dispatching of animation events should
+     * stop at this level in the view hierarchy, and no animation events should be dispatch to the
+     * subtree of the view hierarchy.
+     */
+    int DISPATCH_MODE_STOP = 0;
+
+    /**
+     * Return value for {@link #getDispatchMode()}: Dispatching of animation events should
+     * continue in the view hierarchy.
+     */
+    int DISPATCH_MODE_CONTINUE_ON_SUBTREE = 1;
+
+    /** @hide */
+    @IntDef(prefix = { "DISPATCH_MODE_" }, value = {
+            DISPATCH_MODE_STOP,
+            DISPATCH_MODE_CONTINUE_ON_SUBTREE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface DispatchMode {}
+
+    /**
+     * Retrieves the dispatch mode of this listener. Dispatch of the all animation events is
+     * hierarchical: It will starts at the root of the view hierarchy and then traverse it and
+     * invoke the callback of the specific {@link View} that is being traversed.
+     * The method may return either {@link #DISPATCH_MODE_CONTINUE_ON_SUBTREE} to indicate that
+     * animation events should be propagated to the subtree of the view hierarchy, or
+     * {@link #DISPATCH_MODE_STOP} to stop dispatching. In that case, all animation callbacks
+     * related to the animation passed in will be stopped from propagating to the subtree of the
+     * hierarchy.
+     * <p>
+     * Note that this method will only be invoked once when
+     * {@link View#setWindowInsetsAnimationCallback setting the listener} and then the framework
+     * will use the recorded result.
+     * <p>
+     * Also note that returning {@link #DISPATCH_MODE_STOP} here behaves the same way as returning
+     * {@link WindowInsets#CONSUMED} during the regular insets dispatch in
+     * {@link View#onApplyWindowInsets}.
+     *
+     * @return Either {@link #DISPATCH_MODE_CONTINUE_ON_SUBTREE} to indicate that dispatching of
+     *         animation events will continue to the subtree of the view hierarchy, or
+     *         {@link #DISPATCH_MODE_STOP} to indicate that animation events will stop dispatching.
+     */
+    @DispatchMode
+    int getDispatchMode();
+
+    /**
+     * Called when an insets animation is about to start and before the views have been laid out in
+     * the end state of the animation. The ordering of events during an insets animation is the
+     * following:
+     * <p>
+     * <ul>
+     *     <li>Application calls {@link WindowInsetsController#hideInputMethod()},
+     *     {@link WindowInsetsController#showInputMethod()},
+     *     {@link WindowInsetsController#controlInputMethodAnimation}</li>
+     *     <li>onPrepare is called on the view hierarchy listeners</li>
+     *     <li>{@link View#onApplyWindowInsets} will be called with the end state of the
+     *     animation</li>
+     *     <li>View hierarchy gets laid out according to the changes the application has requested
+     *     due to the new insets being dispatched</li>
+     *     <li>{@link #onStart} is called <em>before</em> the view
+     *     hierarchy gets drawn in the new laid out state</li>
+     *     <li>{@link #onProgress} is called immediately after with the animation start state</li>
+     *     <li>The frame gets drawn.</li>
+     * </ul>
+     * <p>
+     * This ordering allows the application to inspect the end state after the animation has
+     * finished, and then revert to the starting state of the animation in the first
+     * {@link #onProgress} callback by using post-layout view properties like {@link View#setX} and
+     * related methods.
+     * <p>
+     * Note: If the animation is application controlled by using
+     * {@link WindowInsetsController#controlInputMethodAnimation}, the end state of the animation
+     * is undefined as the application may decide on the end state only by passing in the
+     * {@code shown} parameter when calling {@link WindowInsetsAnimationController#finish}. In this
+     * situation, the system will dispatch the insets in the opposite visibility state before the
+     * animation starts. Example: When controlling the input method with
+     * {@link WindowInsetsController#controlInputMethodAnimation} and the input method is currently
+     * showing, {@link View#onApplyWindowInsets} will receive a {@link WindowInsets} instance for
+     * which {@link WindowInsets#isVisible} will return {@code false} for {@link Type#ime}.
+     *
+     * @param animation The animation that is about to start.
+     */
+    default void onPrepare(@NonNull InsetsAnimation animation) {
+    }
+
+    /**
+     * Called when an insets animation gets started.
      * <p>
      * Note that, like {@link #onProgress}, dispatch of the animation start event is hierarchical:
      * It will starts at the root of the view hierarchy and then traverse it and invoke the callback
-     * of the specific {@link View} that is being traversed. The method my return a modified
+     * of the specific {@link View} that is being traversed. The method may return a modified
      * instance of the bounds by calling {@link AnimationBounds#inset} to indicate that a part of
      * the insets have been used to offset or clip its children, and the children shouldn't worry
-     * about that part anymore.
+     * about that part anymore. Furthermore, if {@link #getDispatchMode()} returns
+     * {@link #DISPATCH_MODE_STOP}, children of this view will not receive the callback anymore.
      *
      * @param animation The animation that is about to start.
      * @param bounds The bounds in which animation happens.
@@ -45,7 +137,7 @@
      *         subtree of the hierarchy.
      */
     @NonNull
-    default AnimationBounds onStarted(
+    default AnimationBounds onStart(
             @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
         return bounds;
     }
@@ -61,7 +153,9 @@
      * The method may return a modified instance by calling
      * {@link WindowInsets#inset(int, int, int, int)} to indicate that a part of the insets have
      * been used to offset or clip its children, and the children shouldn't worry about that part
-     * anymore.
+     * anymore. Furthermore, if {@link #getDispatchMode()} returns
+     * {@link #DISPATCH_MODE_STOP}, children of this view will not receive the callback anymore.
+     *
      * TODO: Introduce a way to map (type -> InsetAnimation) so app developer can query animation
      *  for a given type e.g. callback.getAnimation(type) OR controller.getAnimation(type).
      *  Or on the controller directly?
@@ -72,12 +166,12 @@
     WindowInsets onProgress(@NonNull WindowInsets insets);
 
     /**
-     * Called when an inset animation has finished.
+     * Called when an insets animation has finished.
      *
      * @param animation The animation that has finished running. This will be the same instance as
-     *                  passed into {@link #onStarted}
+     *                  passed into {@link #onStart}
      */
-    default void onFinished(@NonNull InsetsAnimation animation) {
+    default void onFinish(@NonNull InsetsAnimation animation) {
     }
 
     /**
@@ -88,14 +182,26 @@
         private final @InsetsType int mTypeMask;
         private float mFraction;
         @Nullable private final Interpolator mInterpolator;
-        private long mDurationMs;
+        private final long mDurationMillis;
         private float mAlpha;
 
+        /**
+         * Creates a new {@link InsetsAnimation} object.
+         * <p>
+         * This should only be used for testing, as usually the system creates this object for the
+         * application to listen to with {@link WindowInsetsAnimationCallback}.
+         * </p>
+         * @param typeMask The bitmask of {@link WindowInsets.Type}s that are animating.
+         * @param interpolator The interpolator of the animation.
+         * @param durationMillis The duration of the animation in
+         *                   {@link java.util.concurrent.TimeUnit#MILLISECONDS}.
+         */
         public InsetsAnimation(
-                @InsetsType int typeMask, @Nullable Interpolator interpolator, long durationMs) {
+                @InsetsType int typeMask, @Nullable Interpolator interpolator,
+                long durationMillis) {
             mTypeMask = typeMask;
             mInterpolator = interpolator;
-            mDurationMs = durationMs;
+            mDurationMillis = durationMillis;
         }
 
         /**
@@ -107,14 +213,18 @@
 
         /**
          * Returns the raw fractional progress of this animation between
-         * {@link AnimationBounds#getLowerBound()} and {@link AnimationBounds#getUpperBound()}. Note
+         * start state of the animation and the end state of the animation. Note
          * that this progress is the global progress of the animation, whereas
          * {@link WindowInsetsAnimationCallback#onProgress} will only dispatch the insets that may
          * be inset with {@link WindowInsets#inset} by parents of views in the hierarchy.
          * Progress per insets animation is global for the entire animation. One animation animates
          * all things together (in, out, ...). If they don't animate together, we'd have
          * multiple animations.
-         *
+         * <p>
+         * Note: In case the application is controlling the animation, the valued returned here will
+         * be the same as the application passed into
+         * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)}.
+         * </p>
          * @return The current progress of this animation.
          */
         @FloatRange(from = 0f, to = 1f)
@@ -124,16 +234,27 @@
 
         /**
          * Returns the interpolated fractional progress of this animation between
-         * {@link AnimationBounds#getLowerBound()} and {@link AnimationBounds#getUpperBound()}. Note
+         * start state of the animation and the end state of the animation. Note
          * that this progress is the global progress of the animation, whereas
          * {@link WindowInsetsAnimationCallback#onProgress} will only dispatch the insets that may
          * be inset with {@link WindowInsets#inset} by parents of views in the hierarchy.
          * Progress per insets animation is global for the entire animation. One animation animates
          * all things together (in, out, ...). If they don't animate together, we'd have
          * multiple animations.
+         * <p>
+         * Note: In case the application is controlling the animation, the valued returned here will
+         * be the same as the application passed into
+         * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)},
+         * interpolated with the interpolator passed into
+         * {@link WindowInsetsController#controlInputMethodAnimation}.
+         * </p>
+         * <p>
+         * Note: For system-initiated animations, this will always return a valid value between 0
+         * and 1.
+         * </p>
          * @see #getFraction() for raw fraction.
          * @return The current interpolated progress of this animation. -1 if interpolator isn't
-         * specified.
+         *         specified.
          */
         public float getInterpolatedFraction() {
             if (mInterpolator != null) {
@@ -142,52 +263,66 @@
             return -1;
         }
 
+        /**
+         * Retrieves the interpolator used for this animation, or {@code null} if this animation
+         * doesn't follow an interpolation curved. For system-initiated animations, this will never
+         * return {@code null}.
+         *
+         * @return The interpolator used for this animation.
+         */
         @Nullable
         public Interpolator getInterpolator() {
             return mInterpolator;
         }
 
         /**
-         * @return duration of animation in {@link java.util.concurrent.TimeUnit#MILLISECONDS}.
+         * @return duration of animation in {@link java.util.concurrent.TimeUnit#MILLISECONDS}, or
+         *         -1 if the animation doesn't have a fixed duration.
          */
         public long getDurationMillis() {
-            return mDurationMs;
+            return mDurationMillis;
         }
 
         /**
          * Set fraction of the progress if {@link WindowInsets.Type.InsetsType} animation is
-         * controlled by the app {@see #getCurrentFraction}.
-         * <p>Note: If app didn't create {@link InsetsAnimation}, it shouldn't set progress either.
-         * Progress would be set by system with the system-default animation.
+         * controlled by the app.
+         * <p>
+         * Note: This should only be used for testing, as the system fills in the fraction for the
+         * application or the fraction that was passed into
+         * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)} is being
+         * used.
          * </p>
          * @param fraction fractional progress between 0 and 1 where 0 represents hidden and
          *                zero progress and 1 represent fully shown final state.
+         * @see #getFraction()
          */
         public void setFraction(@FloatRange(from = 0f, to = 1f) float fraction) {
             mFraction = fraction;
         }
 
         /**
-         * Set duration of the animation if {@link WindowInsets.Type.InsetsType} animation is
-         * controlled by the app.
-         * <p>Note: If app didn't create {@link InsetsAnimation}, it shouldn't set duration either.
-         * Duration would be set by system with the system-default animation.
-         * </p>
-         * @param durationMs in {@link java.util.concurrent.TimeUnit#MILLISECONDS}
-         */
-        public void setDuration(long durationMs) {
-            mDurationMs = durationMs;
-        }
-
-        /**
-         * @return alpha of {@link WindowInsets.Type.InsetsType}.
+         * Retrieves the translucency of the windows that are animating.
+         *
+         * @return Alpha of windows that cause insets of type {@link WindowInsets.Type.InsetsType}.
          */
         @FloatRange(from = 0f, to = 1f)
         public float getAlpha() {
             return mAlpha;
         }
 
-        void setAlpha(@FloatRange(from = 0f, to = 1f) float alpha) {
+        /**
+         * Sets the translucency of the windows that are animating.
+         * <p>
+         * Note: This should only be used for testing, as the system fills in the alpha for the
+         * application or the alpha that was passed into
+         * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)} is being
+         * used.
+         * </p>
+         * @param alpha Alpha of windows that cause insets of type
+         *              {@link WindowInsets.Type.InsetsType}.
+         * @see #getAlpha()
+         */
+        public void setAlpha(@FloatRange(from = 0f, to = 1f) float alpha) {
             mAlpha = alpha;
         }
     }
@@ -196,6 +331,7 @@
      * Class representing the range of an {@link InsetsAnimation}
      */
     final class AnimationBounds {
+
         private final Insets mLowerBound;
         private final Insets mUpperBound;
 
@@ -253,14 +389,14 @@
 
         /**
          * Insets both the lower and upper bound by the specified insets. This is to be used in
-         * {@link WindowInsetsAnimationCallback#onStarted} to indicate that a part of the insets has
+         * {@link WindowInsetsAnimationCallback#onStart} to indicate that a part of the insets has
          * been used to offset or clip its children, and the children shouldn't worry about that
          * part anymore.
          *
          * @param insets The amount to inset.
          * @return A copy of this instance inset in the given directions.
          * @see WindowInsets#inset
-         * @see WindowInsetsAnimationCallback#onStarted
+         * @see WindowInsetsAnimationCallback#onStart
          */
         @NonNull
         public AnimationBounds inset(@NonNull Insets insets) {
diff --git a/core/java/android/view/WindowInsetsAnimationControlListener.java b/core/java/android/view/WindowInsetsAnimationControlListener.java
index 8a226c1..f91254d 100644
--- a/core/java/android/view/WindowInsetsAnimationControlListener.java
+++ b/core/java/android/view/WindowInsetsAnimationControlListener.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.annotation.Hide;
 import android.annotation.NonNull;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.inputmethod.EditorInfo;
@@ -26,6 +27,12 @@
 public interface WindowInsetsAnimationControlListener {
 
     /**
+     * @hide
+     */
+    default void onPrepare(int types) {
+    }
+
+    /**
      * Called when the animation is ready to be controlled. This may be delayed when the IME needs
      * to redraw because of an {@link EditorInfo} change, or when the window is starting up.
      *
diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index 6de56be..02323cf 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -20,8 +20,11 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.graphics.Insets;
 import android.view.WindowInsets.Type.InsetsType;
+import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
+import android.view.animation.Interpolator;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -29,7 +32,7 @@
 /**
  * Interface to control windows that generate insets.
  *
- * TODO Needs more information and examples once the API is more baked.
+ * TODO(118118435): Needs more information and examples once the API is more baked.
  */
 public interface WindowInsetsController {
 
@@ -79,7 +82,6 @@
      * shown on any user interaction on the corresponding display if navigation bars are hidden by
      * {@link #hide(int)} or
      * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)}.
-     * @hide
      */
     int BEHAVIOR_SHOW_BARS_BY_TOUCH = 0;
 
@@ -90,7 +92,6 @@
      *
      * <p>When system bars are hidden in this mode, they can be revealed with system gestures, such
      * as swiping from the edge of the screen where the bar is hidden from.</p>
-     * @hide
      */
     int BEHAVIOR_SHOW_BARS_BY_SWIPE = 1;
 
@@ -103,7 +104,6 @@
      * gestures, such as swiping from the edge of the screen where the bar is hidden from. These
      * transient system bars will overlay app’s content, may have some degree of transparency, and
      * will automatically hide after a short timeout.</p>
-     * @hide
      */
     int BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE = 2;
 
@@ -126,7 +126,6 @@
      *
      * @param types A bitmask of {@link InsetsType} specifying what windows the app
      *              would like to make appear on screen.
-     * @hide
      */
     void show(@InsetsType int types);
 
@@ -139,7 +138,6 @@
      *
      * @param types A bitmask of {@link InsetsType} specifying what windows the app
      *              would like to make disappear.
-     * @hide
      */
     void hide(@InsetsType int types);
 
@@ -148,27 +146,50 @@
      * the position of the windows in the system causing insets directly.
      *
      * @param types The {@link InsetsType}s the application has requested to control.
-     * @param durationMillis duration of animation in
-     *                       {@link java.util.concurrent.TimeUnit#MILLISECONDS}
+     * @param durationMillis Duration of animation in
+     *                       {@link java.util.concurrent.TimeUnit#MILLISECONDS}, or -1 if the
+     *                       animation doesn't have a predetermined duration.T his value will be
+     *                       passed to {@link InsetsAnimation#getDurationMillis()}
+     * @param interpolator The interpolator used for this animation, or {@code null} if this
+     *                     animation doesn't follow an interpolation curve. This value will be
+     *                     passed to {@link InsetsAnimation#getInterpolator()} and used to calculate
+     *                     {@link InsetsAnimation#getInterpolatedFraction()}.
      * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the
      *                 windows are ready to be controlled, among other callbacks.
-     * @hide
+     *
+     * @see InsetsAnimation#getFraction()
+     * @see InsetsAnimation#getInterpolatedFraction()
+     * @see InsetsAnimation#getInterpolator()
+     * @see InsetsAnimation#getDurationMillis()
      */
     void controlWindowInsetsAnimation(@InsetsType int types, long durationMillis,
+            @Nullable Interpolator interpolator,
             @NonNull WindowInsetsAnimationControlListener listener);
 
     /**
      * Lets the application control the animation for showing the IME in a frame-by-frame manner by
      * modifying the position of the IME when it's causing insets.
      *
-     * @param durationMillis duration of the animation in
-     *                       {@link java.util.concurrent.TimeUnit#MILLISECONDS}
+     * @param durationMillis Duration of the animation in
+     *                       {@link java.util.concurrent.TimeUnit#MILLISECONDS}, or -1 if the
+     *                       animation doesn't have a predetermined duration. This value will be
+     *                       passed to {@link InsetsAnimation#getDurationMillis()}
+     * @param interpolator The interpolator used for this animation, or {@code null} if this
+     *                     animation doesn't follow an interpolation curve. This value will be
+     *                     passed to {@link InsetsAnimation#getInterpolator()} and used to calculate
+     *                     {@link InsetsAnimation#getInterpolatedFraction()}.
      * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the
      *                 IME are ready to be controlled, among other callbacks.
+     *
+     * @see InsetsAnimation#getFraction()
+     * @see InsetsAnimation#getInterpolatedFraction()
+     * @see InsetsAnimation#getInterpolator()
+     * @see InsetsAnimation#getDurationMillis()
      */
     default void controlInputMethodAnimation(long durationMillis,
+            @Nullable Interpolator interpolator,
             @NonNull WindowInsetsAnimationControlListener listener) {
-        controlWindowInsetsAnimation(ime(), durationMillis, listener);
+        controlWindowInsetsAnimation(ime(), durationMillis, interpolator, listener);
     }
 
     /**
@@ -179,7 +200,7 @@
      * the event by observing {@link View#onApplyWindowInsets} and checking visibility with
      * {@link WindowInsets#isVisible}.
      *
-     * @see #controlInputMethodAnimation(long, WindowInsetsAnimationControlListener)
+     * @see #controlInputMethodAnimation
      * @see #hideInputMethod()
      */
     default void showInputMethod() {
@@ -194,7 +215,7 @@
      * the event by observing {@link View#onApplyWindowInsets} and checking visibility with
      * {@link WindowInsets#isVisible}.
      *
-     * @see #controlInputMethodAnimation(long, WindowInsetsAnimationControlListener)
+     * @see #controlInputMethodAnimation
      * @see #showInputMethod()
      */
     default void hideInputMethod() {
@@ -203,21 +224,47 @@
 
     /**
      * Controls the appearance of system bars.
+     * <p>
+     * For example, the following statement adds {@link #APPEARANCE_LIGHT_STATUS_BARS}:
+     * <pre>
+     * setSystemBarsAppearance(APPEARANCE_LIGHT_STATUS_BARS, APPEARANCE_LIGHT_STATUS_BARS)
+     * </pre>
+     * And the following statement clears it:
+     * <pre>
+     * setSystemBarsAppearance(0, APPEARANCE_LIGHT_STATUS_BARS)
+     * </pre>
      *
      * @param appearance Bitmask of {@link Appearance} flags.
-     * @see Appearance
+     * @param mask Specifies which flags of appearance should be changed.
+     * @see #getSystemBarsAppearance
      */
-    void setSystemBarsAppearance(@Appearance int appearance);
+    void setSystemBarsAppearance(@Appearance int appearance, @Appearance int mask);
+
+    /**
+     * Retrieves the requested appearance of system bars.
+     *
+     * @return The requested bitmask of system bar appearance controlled by this window.
+     * @see #setSystemBarsAppearance(int, int)
+     */
+    @Appearance int getSystemBarsAppearance();
 
     /**
      * Controls the behavior of system bars.
      *
      * @param behavior Determines how the bars behave when being hidden by the application.
-     * @see Behavior
+     * @see #getSystemBarsBehavior
      */
     void setSystemBarsBehavior(@Behavior int behavior);
 
     /**
+     * Retrieves the requested behavior of system bars.
+     *
+     * @return the system bar behavior controlled by this window.
+     * @see #setSystemBarsBehavior(int)
+     */
+    @Behavior int getSystemBarsBehavior();
+
+    /**
      * @hide
      */
     InsetsState getState();
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index f7d9706..c8dea43 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -421,7 +421,9 @@
      * </p>
      *
      * @return The display that this window manager is managing.
+     * @deprecated Use {@link Context#getDisplay()} instead.
      */
+    @Deprecated
     public Display getDefaultDisplay();
 
     /**
@@ -435,6 +437,49 @@
     public void removeViewImmediate(View view);
 
     /**
+     * Returns the {@link WindowMetrics} according to the current system state.
+     * <p>
+     * The metrics describe the size of the area the window would occupy with
+     * {@link LayoutParams#MATCH_PARENT MATCH_PARENT} width and height, and the {@link WindowInsets}
+     * such a window would have.
+     * <p>
+     * The value of this is based on the <b>current</b> windowing state of the system.
+     *
+     * For example, for activities in multi-window mode, the metrics returned are based on the
+     * current bounds that the user has selected for the {@link android.app.Activity Activity}'s
+     * task.
+     *
+     * @see #getMaximumWindowMetrics()
+     * @see WindowMetrics
+     */
+    default @NonNull WindowMetrics getCurrentWindowMetrics() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Returns the largets {@link WindowMetrics} an app may expect in the current system state.
+     * <p>
+     * The metrics describe the size of the largest potential area the window might occupy with
+     * {@link LayoutParams#MATCH_PARENT MATCH_PARENT} width and height, and the {@link WindowInsets}
+     * such a window would have.
+     * <p>
+     * The value of this is based on the largest <b>potential</b> windowing state of the system.
+     *
+     * For example, for activities in multi-window mode, the metrics returned are based on the
+     * what the bounds would be if the user expanded the {@link android.app.Activity Activity}'s
+     * task to cover the entire screen.
+     *
+     * Note that this might still be smaller than the size of the physical display if certain areas
+     * of the display are not available to windows created in this {@link Context}.
+     *
+     * @see #getMaximumWindowMetrics()
+     * @see WindowMetrics
+     */
+    default @NonNull WindowMetrics getMaximumWindowMetrics() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
      * Used to asynchronously request Keyboard Shortcuts from the focused window.
      *
      * @hide
@@ -1131,6 +1176,15 @@
         public static final int TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 39;
 
         /**
+         * Window type: the notification shade and keyguard. There can be only one status bar
+         * window; it is placed at the top of the screen, and all other
+         * windows are shifted down so they are below it.
+         * In multiuser systems shows on all users' windows.
+         * @hide
+         */
+        public static final int TYPE_NOTIFICATION_SHADE = FIRST_SYSTEM_WINDOW + 40;
+
+        /**
          * End of types of system windows.
          */
         public static final int LAST_SYSTEM_WINDOW      = 2999;
@@ -1234,11 +1288,9 @@
          *  the device's screen turned on and bright. */
         public static final int FLAG_KEEP_SCREEN_ON     = 0x00000080;
 
-        /** Window flag: place the window within the entire screen, ignoring
-         *  decorations around the border (such as the status bar).  The
-         *  window must correctly position its contents to take the screen
-         *  decoration into account.  This flag is normally set for you
-         *  by Window as described in {@link Window#setFlags}.
+        /**
+         * Window flag for attached windows: Place the window within the entire screen, ignoring
+         * any constraints from the parent window.
          *
          *  <p>Note: on displays that have a {@link DisplayCutout}, the window may be placed
          *  such that it avoids the {@link DisplayCutout} area if necessary according to the
@@ -1268,11 +1320,21 @@
          * {@link android.R.style#Theme_Holo_Light_NoActionBar_Fullscreen},
          * {@link android.R.style#Theme_DeviceDefault_NoActionBar_Fullscreen}, and
          * {@link android.R.style#Theme_DeviceDefault_Light_NoActionBar_Fullscreen}.</p>
+         *
+         * @deprecated Use {@link WindowInsetsController#hide(int)} with {@link Type#statusBars()}
+         * instead.
          */
+        @Deprecated
         public static final int FLAG_FULLSCREEN      = 0x00000400;
 
-        /** Window flag: override {@link #FLAG_FULLSCREEN} and force the
-         *  screen decorations (such as the status bar) to be shown. */
+        /**
+         * Window flag: override {@link #FLAG_FULLSCREEN} and force the
+         * screen decorations (such as the status bar) to be shown.
+         *
+         * @deprecated This value became API "by accident", and shouldn't be used by 3rd party
+         * applications.
+         */
+        @Deprecated
         public static final int FLAG_FORCE_NOT_FULLSCREEN   = 0x00000800;
 
         /** Window flag: turn on dithering when compositing this window to
@@ -1304,13 +1366,18 @@
          * until the finger is released. */
         public static final int FLAG_IGNORE_CHEEK_PRESSES    = 0x00008000;
 
-        /** Window flag: a special option only for use in combination with
+        /**
+         * Window flag: a special option only for use in combination with
          * {@link #FLAG_LAYOUT_IN_SCREEN}.  When requesting layout in the
          * screen your window may appear on top of or behind screen decorations
          * such as the status bar.  By also including this flag, the window
          * manager will report the inset rectangle needed to ensure your
          * content is not covered by screen decorations.  This flag is normally
-         * set for you by Window as described in {@link Window#setFlags}.*/
+         * set for you by Window as described in {@link Window#setFlags}
+         *
+         * @deprecated Insets will always be delivered to your application.
+         */
+        @Deprecated
         public static final int FLAG_LAYOUT_INSET_DECOR = 0x00010000;
 
         /** Window flag: When set, input method can't interact with the focusable window
@@ -1496,7 +1563,11 @@
          *
          * <p>Note: For devices that support
          * {@link android.content.pm.PackageManager#FEATURE_AUTOMOTIVE} this flag may be ignored.
+         *
+         * @deprecated Use {@link Window#setStatusBarColor(int)} with a half-translucent color
+         * instead.
          */
+        @Deprecated
         public static final int FLAG_TRANSLUCENT_STATUS = 0x04000000;
 
         /**
@@ -1519,7 +1590,11 @@
          * <p>Note: For devices that support
          * {@link android.content.pm.PackageManager#FEATURE_AUTOMOTIVE} this flag can be disabled
          * by the car manufacturers.
+         *
+         * @deprecated Use {@link Window#setNavigationBarColor(int)} with a half-translucent color
+         * instead.
          */
+        @Deprecated
         public static final int FLAG_TRANSLUCENT_NAVIGATION = 0x08000000;
 
         /**
@@ -1549,7 +1624,11 @@
          * overlap with the screen decorations of the parent window such as the navigation bar. By
          * including this flag, the window manager will layout the attached window within the decor
          * frame of the parent window such that it doesn't overlap with screen decorations.
+         *
+         * @deprecated Use {@link #setFitInsetsTypes(int)} to determine whether the attached
+         * window will overlap with system bars.
          */
+        @Deprecated
         public static final int FLAG_LAYOUT_ATTACHED_IN_DECOR = 0x40000000;
 
         /**
@@ -1732,14 +1811,6 @@
         public static final int PRIVATE_FLAG_SYSTEM_ERROR = 0x00000100;
 
         /**
-         * Flag whether the current window is a keyguard window, meaning that it will hide all other
-         * windows behind it except for windows with flag {@link #FLAG_SHOW_WHEN_LOCKED} set.
-         * Further, this can only be set by {@link LayoutParams#TYPE_STATUS_BAR}.
-         * {@hide}
-         */
-        public static final int PRIVATE_FLAG_KEYGUARD = 0x00000400;
-
-        /**
          * Flag that prevents the wallpaper behind the current window from receiving touch events.
          *
          * {@hide}
@@ -1748,12 +1819,12 @@
 
         /**
          * Flag to force the status bar window to be visible all the time. If the bar is hidden when
-         * this flag is set it will be shown again and the bar will have a transparent background.
+         * this flag is set it will be shown again.
          * This can only be set by {@link LayoutParams#TYPE_STATUS_BAR}.
          *
          * {@hide}
          */
-        public static final int PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT = 0x00001000;
+        public static final int PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR = 0x00001000;
 
         /**
          * Flag indicating that the x, y, width, and height members should be
@@ -1856,18 +1927,25 @@
         public static final int PRIVATE_FLAG_USE_BLAST = 0x02000000;
 
         /**
+         * Flag to indicate that the window is controlling the appearance of system bars. So we
+         * don't need to adjust it by reading its system UI flags for compatibility.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_APPEARANCE_CONTROLLED = 0x04000000;
+
+        /**
+         * Flag to indicate that the window is controlling the behavior of system bars. So we don't
+         * need to adjust it by reading its window flags or system UI flags for compatibility.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_BEHAVIOR_CONTROLLED = 0x08000000;
+
+        /**
          * Flag to indicate that the window is controlling how it fits window insets on its own.
          * So we don't need to adjust its attributes for fitting window insets.
          * @hide
          */
-        public static final int PRIVATE_FLAG_FIT_INSETS_CONTROLLED = 0x04000000;
-
-        /**
-         * Flag to indicate that the window only draws the bottom bar background so that we don't
-         * extend it to system bar areas at other sides.
-         * @hide
-         */
-        public static final int PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND = 0x08000000;
+        public static final int PRIVATE_FLAG_FIT_INSETS_CONTROLLED = 0x10000000;
 
         /**
          * An internal annotation for flags that can be specified to {@link #softInputMode}.
@@ -1917,17 +1995,13 @@
                         equals = PRIVATE_FLAG_SYSTEM_ERROR,
                         name = "SYSTEM_ERROR"),
                 @ViewDebug.FlagToString(
-                        mask = PRIVATE_FLAG_KEYGUARD,
-                        equals = PRIVATE_FLAG_KEYGUARD,
-                        name = "KEYGUARD"),
-                @ViewDebug.FlagToString(
                         mask = PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
                         equals = PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
                         name = "DISABLE_WALLPAPER_TOUCH_EVENTS"),
                 @ViewDebug.FlagToString(
-                        mask = PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT,
-                        equals = PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT,
-                        name = "FORCE_STATUS_BAR_VISIBLE_TRANSPARENT"),
+                        mask = PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
+                        equals = PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
+                        name = "FORCE_STATUS_BAR_VISIBLE"),
                 @ViewDebug.FlagToString(
                         mask = PRIVATE_FLAG_PRESERVE_GEOMETRY,
                         equals = PRIVATE_FLAG_PRESERVE_GEOMETRY,
@@ -1973,13 +2047,17 @@
                         equals = PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
                         name = "COLOR_SPACE_AGNOSTIC"),
                 @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_APPEARANCE_CONTROLLED,
+                        equals = PRIVATE_FLAG_APPEARANCE_CONTROLLED,
+                        name = "APPEARANCE_CONTROLLED"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_BEHAVIOR_CONTROLLED,
+                        equals = PRIVATE_FLAG_BEHAVIOR_CONTROLLED,
+                        name = "BEHAVIOR_CONTROLLED"),
+                @ViewDebug.FlagToString(
                         mask = PRIVATE_FLAG_FIT_INSETS_CONTROLLED,
                         equals = PRIVATE_FLAG_FIT_INSETS_CONTROLLED,
-                        name = "FIT_INSETS_CONTROLLED"),
-                @ViewDebug.FlagToString(
-                        mask = PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND,
-                        equals = PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND,
-                        name = "ONLY_DRAW_BOTTOM_BAR_BACKGROUND")
+                        name = "FIT_INSETS_CONTROLLED")
         })
         @TestApi
         public int privateFlags;
@@ -2079,7 +2157,11 @@
          * layout parameter flags include {@link #FLAG_FULLSCREEN}, this
          * value for {@link #softInputMode} will be ignored; the window will
          * not resize, but will stay fullscreen.
+         *
+         * @deprecated Use {@link Window#setOnContentApplyWindowInsetsListener} instead with a
+         * listener that fits {@link Type#ime()} instead.
          */
+        @Deprecated
         public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;
 
         /** Adjustment option for {@link #softInputMode}: set to have a window
@@ -2374,7 +2456,11 @@
          *
          * @see View#STATUS_BAR_VISIBLE
          * @see View#STATUS_BAR_HIDDEN
+         *
+         * @deprecated SystemUiVisibility flags are deprecated. Use {@link WindowInsetsController}
+         * instead.
          */
+        @Deprecated
         public int systemUiVisibility;
 
         /**
@@ -2402,7 +2488,8 @@
                 flag = true,
                 value = {LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT,
                         LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES,
-                        LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER})
+                        LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER,
+                        LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS})
         @interface LayoutInDisplayCutoutMode {}
 
         /**
@@ -2414,6 +2501,7 @@
          * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
          * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
          * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
+         * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
          * @see DisplayCutout
          * @see android.R.attr#windowLayoutInDisplayCutoutMode
          *         android:windowLayoutInDisplayCutoutMode
@@ -2447,13 +2535,6 @@
         public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT = 0;
 
         /**
-         * @deprecated use {@link #LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES}
-         * @hide
-         */
-        @Deprecated
-        public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS = 1;
-
-        /**
          * The window is always allowed to extend into the {@link DisplayCutout} areas on the short
          * edges of the screen.
          *
@@ -2516,6 +2597,25 @@
          */
         public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER = 2;
 
+        /**
+         * The window is always allowed to extend into the {@link DisplayCutout} areas on the all
+         * edges of the screen.
+         *
+         * <p>
+         * The window must make sure that no important content overlaps with the
+         * {@link DisplayCutout}.
+         *
+         * <p>
+         * In this mode, the window extends under cutouts on the all edges of the display in both
+         * portrait and landscape, regardless of whether the window is hiding the system bars.
+         *
+         * @see DisplayCutout
+         * @see WindowInsets#getDisplayCutout()
+         * @see #layoutInDisplayCutoutMode
+         * @see android.R.attr#windowLayoutInDisplayCutoutMode
+         *         android:windowLayoutInDisplayCutoutMode
+         */
+        public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS = 3;
 
         /**
          * When this window has focus, disable touch pad pointer gesture processing.
@@ -2686,7 +2786,7 @@
                         equals = WINDOW_DECOR,
                         name = "WINDOW_DECOR")
         })
-        private @InsetsType int mFitWindowInsetsTypes = Type.systemBars();
+        private @InsetsType int mFitInsetsTypes = Type.systemBars();
 
         @ViewDebug.ExportedProperty(flagMapping = {
                 @ViewDebug.FlagToString(
@@ -2706,19 +2806,18 @@
                         equals = BOTTOM,
                         name = "BOTTOM")
         })
-        private @InsetsSide int mFitWindowInsetsSides = Side.all();
+        private @InsetsSide int mFitInsetsSides = Side.all();
 
-        private boolean mFitIgnoreVisibility = false;
+        private boolean mFitInsetsIgnoringVisibility = false;
 
         /**
          * Specifies types of insets that this window should avoid overlapping during layout.
          *
          * @param types which types of insets that this window should avoid. The initial value of
          *              this object includes all system bars.
-         * @hide pending unhide
          */
-        public void setFitWindowInsetsTypes(@InsetsType int types) {
-            mFitWindowInsetsTypes = types;
+        public void setFitInsetsTypes(@InsetsType int types) {
+            mFitInsetsTypes = types;
             privateFlags |= PRIVATE_FLAG_FIT_INSETS_CONTROLLED;
         }
 
@@ -2727,10 +2826,9 @@
          *
          * @param sides which sides that this window should avoid overlapping with the types
          *              specified. The initial value of this object includes all sides.
-         * @hide pending unhide
          */
-        public void setFitWindowInsetsSides(@InsetsSide int sides) {
-            mFitWindowInsetsSides = sides;
+        public void setFitInsetsSides(@InsetsSide int sides) {
+            mFitInsetsSides = sides;
             privateFlags |= PRIVATE_FLAG_FIT_INSETS_CONTROLLED;
         }
 
@@ -2738,36 +2836,32 @@
          * Specifies if this window should fit the window insets no matter they are visible or not.
          *
          * @param ignore if true, this window will fit the given types even if they are not visible.
-         * @hide pending unhide
          */
-        public void setFitIgnoreVisibility(boolean ignore) {
-            mFitIgnoreVisibility = ignore;
+        public void setFitInsetsIgnoringVisibility(boolean ignore) {
+            mFitInsetsIgnoringVisibility = ignore;
             privateFlags |= PRIVATE_FLAG_FIT_INSETS_CONTROLLED;
         }
 
         /**
          * @return the insets types that this window is avoiding overlapping.
-         * @hide pending unhide
          */
-        public @InsetsType int getFitWindowInsetsTypes() {
-            return mFitWindowInsetsTypes;
+        public @InsetsType int getFitInsetsTypes() {
+            return mFitInsetsTypes;
         }
 
         /**
          * @return the sides that this window is avoiding overlapping.
-         * @hide pending unhide
          */
-        public @InsetsSide int getFitWindowInsetsSides() {
-            return mFitWindowInsetsSides;
+        public @InsetsSide int getFitInsetsSides() {
+            return mFitInsetsSides;
         }
 
         /**
          * @return {@code true} if this window fits the window insets no matter they are visible or
          *         not.
-         * @hide pending unhide
          */
-        public boolean getFitIgnoreVisibility() {
-            return mFitIgnoreVisibility;
+        public boolean isFitInsetsIgnoringVisibility() {
+            return mFitInsetsIgnoringVisibility;
         }
 
         public LayoutParams() {
@@ -2933,9 +3027,9 @@
             out.writeLong(hideTimeoutMilliseconds);
             out.writeInt(insetsFlags.appearance);
             out.writeInt(insetsFlags.behavior);
-            out.writeInt(mFitWindowInsetsTypes);
-            out.writeInt(mFitWindowInsetsSides);
-            out.writeBoolean(mFitIgnoreVisibility);
+            out.writeInt(mFitInsetsTypes);
+            out.writeInt(mFitInsetsSides);
+            out.writeBoolean(mFitInsetsIgnoringVisibility);
             out.writeBoolean(preferMinimalPostProcessing);
         }
 
@@ -2994,9 +3088,9 @@
             hideTimeoutMilliseconds = in.readLong();
             insetsFlags.appearance = in.readInt();
             insetsFlags.behavior = in.readInt();
-            mFitWindowInsetsTypes = in.readInt();
-            mFitWindowInsetsSides = in.readInt();
-            mFitIgnoreVisibility = in.readBoolean();
+            mFitInsetsTypes = in.readInt();
+            mFitInsetsSides = in.readInt();
+            mFitInsetsIgnoringVisibility = in.readBoolean();
             preferMinimalPostProcessing = in.readBoolean();
         }
 
@@ -3243,18 +3337,18 @@
                 changes |= INSET_FLAGS_CHANGED;
             }
 
-            if (mFitWindowInsetsTypes != o.mFitWindowInsetsTypes) {
-                mFitWindowInsetsTypes = o.mFitWindowInsetsTypes;
+            if (mFitInsetsTypes != o.mFitInsetsTypes) {
+                mFitInsetsTypes = o.mFitInsetsTypes;
                 changes |= LAYOUT_CHANGED;
             }
 
-            if (mFitWindowInsetsSides != o.mFitWindowInsetsSides) {
-                mFitWindowInsetsSides = o.mFitWindowInsetsSides;
+            if (mFitInsetsSides != o.mFitInsetsSides) {
+                mFitInsetsSides = o.mFitInsetsSides;
                 changes |= LAYOUT_CHANGED;
             }
 
-            if (mFitIgnoreVisibility != o.mFitIgnoreVisibility) {
-                mFitIgnoreVisibility = o.mFitIgnoreVisibility;
+            if (mFitInsetsIgnoringVisibility != o.mFitInsetsIgnoringVisibility) {
+                mFitInsetsIgnoringVisibility = o.mFitInsetsIgnoringVisibility;
                 changes |= LAYOUT_CHANGED;
             }
 
@@ -3416,17 +3510,17 @@
                 sb.append(prefix).append("  bhv=").append(ViewDebug.flagsToString(
                         InsetsFlags.class, "behavior", insetsFlags.behavior));
             }
-            if (mFitWindowInsetsTypes != 0) {
+            if (mFitInsetsTypes != 0) {
                 sb.append(System.lineSeparator());
                 sb.append(prefix).append("  fitTypes=").append(ViewDebug.flagsToString(
-                        LayoutParams.class, "mFitWindowInsetsTypes", mFitWindowInsetsTypes));
+                        LayoutParams.class, "mFitInsetsTypes", mFitInsetsTypes));
             }
-            if (mFitWindowInsetsSides != Side.all()) {
+            if (mFitInsetsSides != Side.all()) {
                 sb.append(System.lineSeparator());
                 sb.append(prefix).append("  fitSides=").append(ViewDebug.flagsToString(
-                        LayoutParams.class, "mFitWindowInsetsSides", mFitWindowInsetsSides));
+                        LayoutParams.class, "mFitInsetsSides", mFitInsetsSides));
             }
-            if (mFitIgnoreVisibility) {
+            if (mFitInsetsIgnoringVisibility) {
                 sb.append(System.lineSeparator());
                 sb.append(prefix).append("  fitIgnoreVis");
             }
@@ -3467,9 +3561,9 @@
             proto.write(SUBTREE_SYSTEM_UI_VISIBILITY_FLAGS, subtreeSystemUiVisibility);
             proto.write(APPEARANCE, insetsFlags.appearance);
             proto.write(BEHAVIOR, insetsFlags.behavior);
-            proto.write(FIT_INSETS_TYPES, mFitWindowInsetsTypes);
-            proto.write(FIT_INSETS_SIDES, mFitWindowInsetsSides);
-            proto.write(FIT_IGNORE_VISIBILITY, mFitIgnoreVisibility);
+            proto.write(FIT_INSETS_TYPES, mFitInsetsTypes);
+            proto.write(FIT_INSETS_SIDES, mFitInsetsSides);
+            proto.write(FIT_IGNORE_VISIBILITY, mFitInsetsIgnoringVisibility);
             proto.end(token);
         }
 
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 7d5564e..d39c3c0 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -29,6 +29,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
+import android.provider.DeviceConfig;
 import android.util.AndroidRuntimeException;
 import android.util.ArraySet;
 import android.util.Log;
@@ -56,11 +57,16 @@
 public final class WindowManagerGlobal {
     private static final String TAG = "WindowManager";
 
+    private static final String WM_USE_BLAST_ADAPTER_FLAG = "wm_use_blast_adapter";
+
     /**
      * This flag controls whether ViewRootImpl will utilize the Blast Adapter
      * to send buffer updates to SurfaceFlinger
      */
-    public static final boolean USE_BLAST_ADAPTER = false;
+    public static final boolean USE_BLAST_ADAPTER =
+            SystemProperties.getBoolean(String.join(".", "persist.device_config",
+                  DeviceConfig.NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT,
+                  WM_USE_BLAST_ADAPTER_FLAG), false);
 
     /**
      * The user is navigating with keys (not the touch screen), so
@@ -487,11 +493,8 @@
         ViewRootImpl root = mRoots.get(index);
         View view = root.getView();
 
-        if (view != null) {
-            InputMethodManager imm = view.getContext().getSystemService(InputMethodManager.class);
-            if (imm != null) {
-                imm.windowDismissed(mViews.get(index).getWindowToken());
-            }
+        if (root != null) {
+            root.getImeFocusController().onWindowDismissed();
         }
         boolean deferred = root.die(immediate);
         if (view != null) {
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index cdeeaa4..4365d1f 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -17,12 +17,17 @@
 package android.view;
 
 import android.annotation.NonNull;
+import android.app.ResourcesManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
+import android.graphics.Insets;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.Size;
 
 import com.android.internal.os.IResultReceiver;
 
@@ -62,6 +67,10 @@
 
     private IBinder mDefaultToken;
 
+    private boolean mIsViewAdded;
+    private View mLastView;
+    private WindowManager.LayoutParams mLastParams;
+
     public WindowManagerImpl(Context context) {
         this(context, null);
     }
@@ -93,6 +102,9 @@
     public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
         applyDefaultToken(params);
         mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
+        mIsViewAdded = true;
+        mLastView = view;
+        mLastParams = (WindowManager.LayoutParams) params;
     }
 
     @Override
@@ -201,4 +213,71 @@
         }
         return false;
     }
+
+    @Override
+    public WindowMetrics getCurrentWindowMetrics() {
+        final Context context = mParentWindow != null ? mParentWindow.getContext() : mContext;
+        final Rect bound = getCurrentBounds(context);
+
+        return new WindowMetrics(toSize(bound), computeWindowInsets());
+    }
+
+    private static Rect getCurrentBounds(Context context) {
+        synchronized (ResourcesManager.getInstance()) {
+            return context.getResources().getConfiguration().windowConfiguration.getBounds();
+        }
+    }
+
+    @Override
+    public WindowMetrics getMaximumWindowMetrics() {
+        return new WindowMetrics(toSize(getMaximumBounds()), computeWindowInsets());
+    }
+
+    private Size toSize(Rect frame) {
+        return new Size(frame.width(), frame.height());
+    }
+
+    private Rect getMaximumBounds() {
+        // TODO(b/128338354): Current maximum bound is display size, but it should be displayArea
+        //  bound after displayArea feature is finished.
+        final Display display = mContext.getDisplay();
+        final Point displaySize = new Point();
+        display.getRealSize(displaySize);
+        return new Rect(0, 0, displaySize.x, displaySize.y);
+    }
+
+    private WindowInsets computeWindowInsets() {
+        // TODO(window-context): This can only be properly implemented
+        //  once we flip the new insets mode flag.
+        if (mParentWindow != null) {
+            if (mParentWindow.getDecorView().isAttachedToWindow()) {
+                return mParentWindow.getDecorView().getViewRootImpl()
+                        .getWindowInsets(true /* forceConstruct */);
+            }
+            return getWindowInsetsFromServer(mParentWindow.getAttributes());
+        }
+        if (mIsViewAdded) {
+            return mLastView.getViewRootImpl().getWindowInsets(true /* forceConstruct */);
+        } else {
+            return getWindowInsetsFromServer(new WindowManager.LayoutParams());
+        }
+
+    }
+
+    private WindowInsets getWindowInsetsFromServer(WindowManager.LayoutParams attrs) {
+        try {
+            final Rect systemWindowInsets = new Rect();
+            final Rect stableInsets = new Rect();
+            final DisplayCutout.ParcelableWrapper displayCutout =
+                    new DisplayCutout.ParcelableWrapper();
+            WindowManagerGlobal.getWindowManagerService().getWindowInsets(attrs,
+                    mContext.getDisplayId(), systemWindowInsets, stableInsets, displayCutout);
+            return new WindowInsets.Builder()
+                    .setSystemWindowInsets(Insets.of(systemWindowInsets))
+                    .setStableInsets(Insets.of(stableInsets))
+                    .setDisplayCutout(displayCutout.get()).build();
+        } catch (RemoteException e) {
+        }
+        return null;
+    }
 }
diff --git a/core/java/android/view/WindowMetrics.java b/core/java/android/view/WindowMetrics.java
new file mode 100644
index 0000000..8caf5b7
--- /dev/null
+++ b/core/java/android/view/WindowMetrics.java
@@ -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 android.view;
+
+import android.annotation.NonNull;
+import android.util.Size;
+
+/**
+ * Metrics about a Window, consisting of the size and {@link WindowInsets}.
+ * <p>
+ * This is usually obtained from {@link WindowManager#getCurrentWindowMetrics()} and
+ * {@link WindowManager#getMaximumWindowMetrics()}.
+ *
+ * @see WindowInsets#getInsets(int)
+ * @see WindowManager#getCurrentWindowMetrics()
+ * @see WindowManager#getMaximumWindowMetrics()
+ */
+public final class WindowMetrics {
+    private final @NonNull Size mSize;
+    private final @NonNull WindowInsets mWindowInsets;
+
+    public WindowMetrics(@NonNull Size size, @NonNull WindowInsets windowInsets) {
+        mSize = size;
+        mWindowInsets = windowInsets;
+    }
+
+    /**
+     * Returns the size of the window.
+     *
+     * @return window size in pixel.
+     */
+    public @NonNull Size getSize() {
+        return mSize;
+    }
+
+    /**
+     * Returns the {@link WindowInsets} of the window.
+     *
+     * @return the {@link WindowInsets} of the window.
+     */
+    public @NonNull WindowInsets getWindowInsets() {
+        return mWindowInsets;
+    }
+}
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 1312a9b..9f27848 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -18,6 +18,7 @@
 
 import android.content.res.Configuration;
 import android.graphics.PixelFormat;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -159,7 +160,8 @@
             Rect outFrame, Rect outContentInsets, Rect outVisibleInsets,
             Rect outStableInsets, Rect outBackdropFrame,
             DisplayCutout.ParcelableWrapper cutout, MergedConfiguration mergedConfiguration,
-            SurfaceControl outSurfaceControl, InsetsState outInsetsState) {
+            SurfaceControl outSurfaceControl, InsetsState outInsetsState,
+            Point outSurfaceSize) {
         State state = null;
         synchronized (this) {
             state = mStateForWindow.get(window.asBinder());
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 914ff18..b9f08ad 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -17,10 +17,14 @@
 package android.view.accessibility;
 
 import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.Bitmap;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
@@ -337,6 +341,48 @@
         return emptyWindows;
     }
 
+
+    /**
+     * Finds an {@link AccessibilityNodeInfo} by accessibility id and given leash token instead of
+     * window id. This method is used to find the leashed node on the embedded view hierarchy.
+     *
+     * @param connectionId The id of a connection for interacting with the system.
+     * @param leashToken The token of the embedded hierarchy.
+     * @param accessibilityNodeId A unique view id or virtual descendant id from
+     *     where to start the search. Use
+     *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
+     *     to start from the root.
+     * @param bypassCache Whether to bypass the cache while looking for the node.
+     * @param prefetchFlags flags to guide prefetching.
+     * @param arguments Optional action arguments.
+     * @return An {@link AccessibilityNodeInfo} if found, null otherwise.
+     */
+    public @Nullable AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(
+            int connectionId, @NonNull IBinder leashToken, long accessibilityNodeId,
+            boolean bypassCache, int prefetchFlags, Bundle arguments) {
+        if (leashToken == null) {
+            return null;
+        }
+        int windowId = -1;
+        try {
+            IAccessibilityServiceConnection connection = getConnection(connectionId);
+            if (connection != null) {
+                windowId = connection.getWindowIdForLeashToken(leashToken);
+            } else {
+                if (DEBUG) {
+                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+                }
+            }
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while calling remote getWindowIdForLeashToken", re);
+        }
+        if (windowId == -1) {
+            return null;
+        }
+        return findAccessibilityNodeInfoByAccessibilityId(connectionId, windowId,
+                accessibilityNodeId, bypassCache, prefetchFlags, arguments);
+    }
+
     /**
      * Finds an {@link AccessibilityNodeInfo} by accessibility id.
      *
@@ -783,6 +829,31 @@
     }
 
     /**
+     * Takes the screenshot of the specified display and returns it by bitmap format.
+     *
+     * @param connectionId The id of a connection for interacting with the system.
+     * @param displayId The logic display id, use {@link Display#DEFAULT_DISPLAY} for
+     *                  default display.
+     * @return The screenshot bitmap on success, null otherwise.
+     */
+    public Bitmap takeScreenshot(int connectionId, int displayId) {
+        Bitmap screenShot = null;
+        try {
+            IAccessibilityServiceConnection connection = getConnection(connectionId);
+            if (connection != null) {
+                screenShot = connection.takeScreenshot(displayId);
+            } else {
+                if (DEBUG) {
+                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+                }
+            }
+        } catch (RemoteException re) {
+            Log.w(LOG_TAG, "Error while calling remote takeScreenshot", re);
+        }
+        return screenShot;
+    }
+
+    /**
      * Clears the result state.
      */
     private void clearResultLocked() {
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index ff31bcc..9cbba87 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -31,6 +31,7 @@
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.annotation.UserIdInt;
+import android.app.RemoteAction;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
@@ -1194,6 +1195,19 @@
     @TestApi
     @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
     public void performAccessibilityShortcut() {
+        performAccessibilityShortcut(null);
+    }
+
+    /**
+     * Perform the accessibility shortcut for the given target which is assigned to the shortcut.
+     *
+     * @param targetName The flattened {@link ComponentName} string or the class name of a system
+     *        class implementing a supported accessibility feature, or {@code null} if there's no
+     *        specified target.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
+    public void performAccessibilityShortcut(@Nullable String targetName) {
         final IAccessibilityManager service;
         synchronized (mLock) {
             service = getServiceLocked();
@@ -1202,19 +1216,89 @@
             }
         }
         try {
-            service.performAccessibilityShortcut();
+            service.performAccessibilityShortcut(targetName);
         } catch (RemoteException re) {
             Log.e(LOG_TAG, "Error performing accessibility shortcut. ", re);
         }
     }
 
     /**
+     * Register the provided {@link RemoteAction} with the given actionId
+     *
+     * @param action The remote action to be registered with the given actionId as system action.
+     * @param actionId The id uniquely identify the system action.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
+    public void registerSystemAction(@NonNull RemoteAction action, int actionId) {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return;
+            }
+        }
+        try {
+            service.registerSystemAction(action, actionId);
+
+            if (DEBUG) {
+                Log.i(LOG_TAG, "System action " + action.getTitle() + " is registered.");
+            }
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error registering system action " + action.getTitle() + " ", re);
+        }
+    }
+
+   /**
+     * Unregister a system action with the given actionId
+     *
+     * @param actionId The id uniquely identify the system action to be unregistered.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
+    public void unregisterSystemAction(int actionId) {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return;
+            }
+        }
+        try {
+            service.unregisterSystemAction(actionId);
+
+            if (DEBUG) {
+                Log.i(LOG_TAG, "System action with actionId " + actionId + " is unregistered.");
+            }
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error unregistering system action with actionId " + actionId + " ", re);
+        }
+    }
+
+    /**
      * Notifies that the accessibility button in the system's navigation area has been clicked
      *
      * @param displayId The logical display id.
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
     public void notifyAccessibilityButtonClicked(int displayId) {
+        notifyAccessibilityButtonClicked(displayId, null);
+    }
+
+    /**
+     * Perform the accessibility button for the given target which is assigned to the button.
+     *
+     * @param displayId displayId The logical display id.
+     * @param targetName The flattened {@link ComponentName} string or the class name of a system
+     *        class implementing a supported accessibility feature, or {@code null} if there's no
+     *        specified target.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
+    public void notifyAccessibilityButtonClicked(int displayId, @Nullable String targetName) {
         final IAccessibilityManager service;
         synchronized (mLock) {
             service = getServiceLocked();
@@ -1223,7 +1307,7 @@
             }
         }
         try {
-            service.notifyAccessibilityButtonClicked(displayId);
+            service.notifyAccessibilityButtonClicked(displayId, targetName);
         } catch (RemoteException re) {
             Log.e(LOG_TAG, "Error while dispatching accessibility button click", re);
         }
@@ -1360,6 +1444,29 @@
         return null;
     }
 
+    /**
+     *
+     * Sets an {@link IWindowMagnificationConnection} that manipulates window magnification.
+     *
+     * @param connection The connection that manipulates window magnification.
+     * @hide
+     */
+    public void setWindowMagnificationConnection(@Nullable
+            IWindowMagnificationConnection connection) {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return;
+            }
+        }
+        try {
+            service.setWindowMagnificationConnection(connection);
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error setting window magnfication connection", re);
+        }
+    }
+
     private IAccessibilityManager getServiceLocked() {
         if (mService == null) {
             tryConnectToServiceLocked(null);
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 92aa7d5..184f3302 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -31,6 +31,7 @@
 import android.graphics.Region;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.InputType;
@@ -110,6 +111,9 @@
     public static final int ROOT_ITEM_ID = Integer.MAX_VALUE - 1;
 
     /** @hide */
+    public static final int LEASHED_ITEM_ID = Integer.MAX_VALUE - 2;
+
+    /** @hide */
     public static final long UNDEFINED_NODE_ID = makeNodeId(UNDEFINED_ITEM_ID, UNDEFINED_ITEM_ID);
 
     /** @hide */
@@ -117,6 +121,10 @@
             AccessibilityNodeProvider.HOST_VIEW_ID);
 
     /** @hide */
+    public static final long LEASHED_NODE_ID = makeNodeId(LEASHED_ITEM_ID,
+            AccessibilityNodeProvider.HOST_VIEW_ID);
+
+    /** @hide */
     public static final int FLAG_PREFETCH_PREDECESSORS = 0x00000001;
 
     /** @hide */
@@ -788,6 +796,10 @@
 
     private TouchDelegateInfo mTouchDelegateInfo;
 
+    private IBinder mLeashedChild;
+    private IBinder mLeashedParent;
+    private long mLeashedParentNodeId = UNDEFINED_NODE_ID;
+
     /**
      * Creates a new {@link AccessibilityNodeInfo}.
      */
@@ -1039,7 +1051,12 @@
             return null;
         }
         final long childId = mChildNodeIds.get(index);
-        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+        final AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+        if (mLeashedChild != null && childId == LEASHED_NODE_ID) {
+            return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mLeashedChild,
+                    ROOT_NODE_ID, false, FLAG_PREFETCH_DESCENDANTS, null);
+        }
+
         return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mWindowId,
                 childId, false, FLAG_PREFETCH_DESCENDANTS, null);
     }
@@ -1062,6 +1079,43 @@
     }
 
     /**
+     * Adds a view root from leashed content as a child. This method is used to embedded another
+     * view hierarchy.
+     * <p>
+     * <strong>Note:</strong> Only one leashed child is permitted.
+     * </p>
+     * <p>
+     * <strong>Note:</strong> Cannot be called from an
+     * {@link android.accessibilityservice.AccessibilityService}.
+     * This class is made immutable before being delivered to an AccessibilityService.
+     * Note that a view cannot be made its own child.
+     * </p>
+     *
+     * @param token The token to which a view root is added.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     * @hide
+     */
+    @TestApi
+    public void addChild(@NonNull IBinder token) {
+        enforceNotSealed();
+        if (token == null) {
+            return;
+        }
+        if (mChildNodeIds == null) {
+            mChildNodeIds = new LongArray();
+        }
+
+        mLeashedChild = token;
+        // Checking uniqueness.
+        // Since only one leashed child is permitted, skip adding ID if the ID already exists.
+        if (mChildNodeIds.indexOf(LEASHED_NODE_ID) >= 0) {
+            return;
+        }
+        mChildNodeIds.add(LEASHED_NODE_ID);
+    }
+
+    /**
      * Unchecked version of {@link #addChild(View)} that does not verify
      * uniqueness. For framework use only.
      *
@@ -1090,6 +1144,38 @@
     }
 
     /**
+     * Removes a leashed child. If the child was not previously added to the node,
+     * calling this method has no effect.
+     * <p>
+     * <strong>Note:</strong> Cannot be called from an
+     * {@link android.accessibilityservice.AccessibilityService}.
+     * This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param token The token of the leashed child
+     * @return true if the child was present
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     * @hide
+     */
+    public boolean removeChild(IBinder token) {
+        enforceNotSealed();
+        if (mChildNodeIds == null || mLeashedChild == null) {
+            return false;
+        }
+        if (!mLeashedChild.equals(token)) {
+            return false;
+        }
+        final int index = mChildNodeIds.indexOf(LEASHED_NODE_ID);
+        mLeashedChild = null;
+        if (index < 0) {
+            return false;
+        }
+        mChildNodeIds.remove(index);
+        return true;
+    }
+
+    /**
      * Adds a virtual child which is a descendant of the given <code>root</code>.
      * If <code>virtualDescendantId</code> is {@link View#NO_ID} the root
      * is added as a child.
@@ -1668,6 +1754,9 @@
      */
     public AccessibilityNodeInfo getParent() {
         enforceSealed();
+        if (mLeashedParent != null && mLeashedParentNodeId != UNDEFINED_NODE_ID) {
+            return getNodeForAccessibilityId(mConnectionId, mLeashedParent, mLeashedParentNodeId);
+        }
         return getNodeForAccessibilityId(mConnectionId, mWindowId, mParentNodeId);
     }
 
@@ -3257,6 +3346,40 @@
     }
 
     /**
+     * Sets the token and node id of the leashed parent.
+     *
+     * @param token The token.
+     * @param viewId The accessibility view id.
+     * @hide
+     */
+    @TestApi
+    public void setLeashedParent(@Nullable IBinder token, int viewId) {
+        enforceNotSealed();
+        mLeashedParent = token;
+        mLeashedParentNodeId = makeNodeId(viewId, AccessibilityNodeProvider.HOST_VIEW_ID);
+    }
+
+    /**
+     * Gets the token of the leashed parent.
+     *
+     * @return The token.
+     * @hide
+     */
+    public @Nullable IBinder getLeashedParent() {
+        return mLeashedParent;
+    }
+
+    /**
+     * Gets the node id of the leashed parent.
+     *
+     * @return The accessibility node id.
+     * @hide
+     */
+    public long getLeashedParentNodeId() {
+        return mLeashedParentNodeId;
+    }
+
+    /**
      * Sets if this instance is sealed.
      *
      * @param sealed Whether is sealed.
@@ -3559,6 +3682,18 @@
         if (!Objects.equals(mTouchDelegateInfo, DEFAULT.mTouchDelegateInfo)) {
             nonDefaultFields |= bitAt(fieldIndex);
         }
+        fieldIndex++;
+        if (mLeashedChild != DEFAULT.mLeashedChild) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
+        if (mLeashedParent != DEFAULT.mLeashedParent) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
+        if (mLeashedParentNodeId != DEFAULT.mLeashedParentNodeId) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
         int totalFields = fieldIndex;
         parcel.writeLong(nonDefaultFields);
 
@@ -3685,6 +3820,16 @@
             mTouchDelegateInfo.writeToParcel(parcel, flags);
         }
 
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            parcel.writeStrongBinder(mLeashedChild);
+        }
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            parcel.writeStrongBinder(mLeashedParent);
+        }
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            parcel.writeLong(mLeashedParentNodeId);
+        }
+
         if (DEBUG) {
             fieldIndex--;
             if (totalFields != fieldIndex) {
@@ -3768,6 +3913,10 @@
         final TouchDelegateInfo otherInfo = other.mTouchDelegateInfo;
         mTouchDelegateInfo = (otherInfo != null)
                 ? new TouchDelegateInfo(otherInfo.mTargetMap, true) : null;
+
+        mLeashedChild = other.mLeashedChild;
+        mLeashedParent = other.mLeashedParent;
+        mLeashedParentNodeId = other.mLeashedParentNodeId;
     }
 
     private void initPoolingInfos(AccessibilityNodeInfo other) {
@@ -3921,6 +4070,16 @@
             mTouchDelegateInfo = TouchDelegateInfo.CREATOR.createFromParcel(parcel);
         }
 
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            mLeashedChild = parcel.readStrongBinder();
+        }
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            mLeashedParent = parcel.readStrongBinder();
+        }
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            mLeashedParentNodeId = parcel.readLong();
+        }
+
         mSealed = sealed;
     }
 
@@ -4200,6 +4359,19 @@
                         | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS, null);
     }
 
+    private static AccessibilityNodeInfo getNodeForAccessibilityId(int connectionId,
+            IBinder leashToken, long accessibilityId) {
+        if (!((leashToken != null)
+                && (getAccessibilityViewId(accessibilityId) != UNDEFINED_ITEM_ID)
+                && (connectionId != UNDEFINED_CONNECTION_ID))) {
+            return null;
+        }
+        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+        return client.findAccessibilityNodeInfoByAccessibilityId(connectionId,
+                leashToken, accessibilityId, false, FLAG_PREFETCH_PREDECESSORS
+                        | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS, null);
+    }
+
     /** @hide */
     public static String idToString(long accessibilityId) {
         int accessibilityViewId = getAccessibilityViewId(accessibilityId);
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 36515b3..7f8fdf8 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -16,6 +16,7 @@
 
 package android.view.accessibility;
 
+import android.app.RemoteAction;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.IAccessibilityServiceConnection;
 import android.accessibilityservice.IAccessibilityServiceClient;
@@ -24,6 +25,7 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.IAccessibilityInteractionConnection;
 import android.view.accessibility.IAccessibilityManagerClient;
+import android.view.accessibility.IWindowMagnificationConnection;
 import android.view.IWindow;
 
 /**
@@ -65,12 +67,12 @@
     // Used by UiAutomation
     IBinder getWindowToken(int windowId, int userId);
 
-    void notifyAccessibilityButtonClicked(int displayId);
+    void notifyAccessibilityButtonClicked(int displayId, String targetName);
 
     void notifyAccessibilityButtonVisibilityChanged(boolean available);
 
     // Requires Manifest.permission.MANAGE_ACCESSIBILITY
-    void performAccessibilityShortcut();
+    void performAccessibilityShortcut(String targetName);
 
     // Requires Manifest.permission.MANAGE_ACCESSIBILITY
     List<String> getAccessibilityShortcutTargets(int shortcutType);
@@ -82,4 +84,8 @@
     int getAccessibilityWindowId(IBinder windowToken);
 
     long getRecommendedTimeoutMillis();
+
+    oneway void registerSystemAction(in RemoteAction action, int actionId);
+    oneway void unregisterSystemAction(int actionId);
+    oneway void setWindowMagnificationConnection(in IWindowMagnificationConnection connection);
 }
diff --git a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
new file mode 100644
index 0000000..0b45c6b
--- /dev/null
+++ b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
@@ -0,0 +1,75 @@
+/*
+ * 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.view.accessibility;
+
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.view.accessibility.IWindowMagnificationConnectionCallback;
+
+/**
+ * Interface for interaction between {@link AccessibilityManagerService}
+ * and {@link WindowMagnification} in SystemUI.
+ *
+ * @hide
+ */
+oneway interface IWindowMagnificationConnection {
+
+    /**
+     * Enables window magnification on specifed display with specified center and scale.
+     *
+     * @param displayId The logical display id.
+     * @param scale magnification scale.
+     * @param centerX the screen-relative X coordinate around which to center,
+     *                or {@link Float#NaN} to leave unchanged.
+     * @param centerY the screen-relative Y coordinate around which to center,
+     *                or {@link Float#NaN} to leave unchanged.
+     */
+    void enableWindowMagnification(int displayId, float scale, float centerX, float centerY);
+
+    /**
+     * Sets the scale of the window magnifier on specifed display.
+     *
+     * @param displayId The logical display id.
+     * @param scale magnification scale.
+     */
+    void setScale(int displayId, float scale);
+
+     /**
+     * Disables window magnification on specifed display.
+     *
+     * @param displayId The logical display id.
+     */
+    void disableWindowMagnification(int displayId);
+
+    /**
+     * Moves the window magnifier on the specifed display.
+     *
+     * @param offsetX the amount in pixels to offset the window magnifier in the X direction, in
+     *                current screen pixels.
+     * @param offsetY the amount in pixels to offset the window magnifier in the Y direction, in
+     *                current screen pixels.
+     */
+    void moveWindowMagnifier(int displayId, float offsetX, float offsetY);
+
+    /**
+     * Sets {@link IWindowMagnificationConnectionCallback} to receive the request or the callback.
+     *
+     *
+     * @param callback the interface to be called.
+     */
+    void setConnectionCallback(in IWindowMagnificationConnectionCallback callback);
+}
diff --git a/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl b/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl
new file mode 100644
index 0000000..7327bb5
--- /dev/null
+++ b/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl
@@ -0,0 +1,44 @@
+/*
+ * 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.view.accessibility;
+
+import android.graphics.Rect;
+
+/**
+ * interface to notify the change of the window magnifier bounds and request to change
+ * the magnification mode.
+ *
+ * @hide
+ */
+ oneway interface IWindowMagnificationConnectionCallback {
+
+    /**
+     * Called when the bounds of the window magnifier is changed.
+     *
+     * @param displayId The logical display id.
+     * @param bounds The window magnifier bounds in screen coordinates.
+     */
+    void onWindowMagnifierBoundsChanged(int display, in Rect bounds);
+    /**
+     * Changes the magnification mode on specified display. It is invoked by System UI when the
+     *  switch button is toggled.
+     *
+     * @param displayId The logical display id.
+     * @param magnificationMode new magnification mode.
+     */
+    void onChangeMagnificationMode(int display, int magnificationMode);
+}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 9c04b39..c159f89 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -230,6 +230,7 @@
     /** @hide */ public static final int ACTION_VIEW_ENTERED =  2;
     /** @hide */ public static final int ACTION_VIEW_EXITED = 3;
     /** @hide */ public static final int ACTION_VALUE_CHANGED = 4;
+    /** @hide */ public static final int ACTION_RESPONSE_EXPIRED = 5;
 
     /** @hide */ public static final int NO_LOGGING = 0;
     /** @hide */ public static final int FLAG_ADD_CLIENT_ENABLED = 0x1;
@@ -776,11 +777,19 @@
      *
      * @see AutofillClient#autofillClientIsVisibleForAutofill()
      *
+     * @param isExpiredResponse The response has expired or not
+     *
      * {@hide}
      */
-    public void onInvisibleForAutofill() {
+    public void onInvisibleForAutofill(boolean isExpiredResponse) {
         synchronized (mLock) {
             mOnInvisibleCalled = true;
+
+            if (isExpiredResponse) {
+                // Notify service the response has expired.
+                updateSessionLocked(/* id= */ null, /* bounds= */ null, /* value= */ null,
+                        ACTION_RESPONSE_EXPIRED, /* flags= */ 0);
+            }
         }
     }
 
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index ae2fb8e..d5d631a 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -758,8 +758,9 @@
             Context context;
             if (mTargetView != null) {
                 context = mTargetView.getContext();
-            } else if (mIMM.mServedView != null) {
-                context = mIMM.mServedView.getContext();
+            } else if (mIMM.mCurRootView != null) {
+                final View servedView = mIMM.mCurRootView.getImeFocusController().getServedView();
+                context = servedView != null ? servedView.getContext() : null;
             } else {
                 context = null;
             }
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index a26243c..c80a1ae 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -18,6 +18,8 @@
 
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.os.Bundle;
@@ -28,7 +30,13 @@
 import android.text.InputType;
 import android.text.TextUtils;
 import android.util.Printer;
+import android.view.View;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
 
 /**
@@ -491,6 +499,238 @@
     @Nullable
     public UserHandle targetInputMethodUser = null;
 
+    @IntDef({TrimPolicy.HEAD, TrimPolicy.TAIL})
+    @Retention(RetentionPolicy.SOURCE)
+    @interface TrimPolicy {
+        int HEAD = 0;
+        int TAIL = 1;
+    }
+
+    /**
+     * The maximum length of initialSurroundingText. When the input text from
+     * {@code setInitialSurroundingText(CharSequence)} is longer than this, trimming shall be
+     * performed to keep memory efficiency.
+     */
+    @VisibleForTesting
+    static final int MEMORY_EFFICIENT_TEXT_LENGTH = 2048;
+    /**
+     * When the input text is longer than {@code #MEMORY_EFFICIENT_TEXT_LENGTH}, we start trimming
+     * the input text into three parts: BeforeCursor, Selection, and AfterCursor. We don't want to
+     * trim the Selection but we also don't want it consumes all available space. Therefore, the
+     * maximum acceptable Selection length is half of {@code #MEMORY_EFFICIENT_TEXT_LENGTH}.
+     */
+    @VisibleForTesting
+    static final int MAX_INITIAL_SELECTION_LENGTH =  MEMORY_EFFICIENT_TEXT_LENGTH / 2;
+
+    @NonNull
+    private InitialSurroundingText mInitialSurroundingText = new InitialSurroundingText();
+
+    /**
+     * Editors may use this method to provide initial input text to IMEs. As the surrounding text
+     * could be used to provide various input assistance, we recommend editors to provide the
+     * complete initial input text in its {@link View#onCreateInputConnection(EditorInfo)} callback.
+     * The supplied text will then be processed to serve {@code #getInitialTextBeforeCursor},
+     * {@code #getInitialSelectedText}, and {@code #getInitialTextBeforeCursor}. System is allowed
+     * to trim {@code sourceText} for various reasons while keeping the most valuable data to IMEs.
+     *
+     * <p><strong>Editor authors: </strong>Providing the initial input text helps reducing IPC calls
+     * for IMEs to provide many modern features right after the connection setup. We recommend
+     * calling this method in your implementation.
+     *
+     * @param sourceText The complete input text.
+     */
+    public void setInitialSurroundingText(@NonNull CharSequence sourceText) {
+        setInitialSurroundingSubText(sourceText, /* subTextStart = */ 0);
+    }
+
+    /**
+     * Editors may use this method to provide initial input text to IMEs. As the surrounding text
+     * could be used to provide various input assistance, we recommend editors to provide the
+     * complete initial input text in its {@link View#onCreateInputConnection(EditorInfo)} callback.
+     * When trimming the input text is needed, call this method instead of
+     * {@code setInitialSurroundingText(CharSequence)} and provide the trimmed position info. Always
+     * try to include the selected text within {@code subText} to give the system best flexibility
+     * to choose where and how to trim {@code subText} when necessary.
+     *
+     * @param subText The input text. When it was trimmed, {@code subTextStart} must be provided
+     *                correctly.
+     * @param subTextStart  The position that the input text got trimmed. For example, when the
+     *                      editor wants to trim out the first 10 chars, subTextStart should be 10.
+     */
+    public void setInitialSurroundingSubText(@NonNull CharSequence subText, int subTextStart) {
+        Preconditions.checkNotNull(subText);
+
+        // Swap selection start and end if necessary.
+        final int subTextSelStart = initialSelStart > initialSelEnd
+                ? initialSelEnd - subTextStart : initialSelStart - subTextStart;
+        final int subTextSelEnd = initialSelStart > initialSelEnd
+                ? initialSelStart - subTextStart : initialSelEnd - subTextStart;
+
+        final int subTextLength = subText.length();
+        // Unknown or invalid selection.
+        if (subTextStart < 0 || subTextSelStart < 0 || subTextSelEnd > subTextLength) {
+            mInitialSurroundingText = new InitialSurroundingText();
+            return;
+        }
+
+        // For privacy protection reason, we don't carry password inputs to IMEs.
+        if (isPasswordInputType(inputType)) {
+            mInitialSurroundingText = new InitialSurroundingText();
+            return;
+        }
+
+        if (subTextLength <= MEMORY_EFFICIENT_TEXT_LENGTH) {
+            mInitialSurroundingText = new InitialSurroundingText(subText, subTextSelStart,
+                    subTextSelEnd);
+            return;
+        }
+
+        // The input text is too long. Let's try to trim it reasonably. Fundamental rules are:
+        // 1. Text before the cursor is the most important information to IMEs.
+        // 2. Text after the cursor is the second important information to IMEs.
+        // 3. Selected text is the least important information but it shall NEVER be truncated.
+        //    When it is too long, just drop it.
+        //
+        // Source: <TextBeforeCursor><Selection><TextAfterCursor>
+        // Possible results:
+        // 1. <(maybeTrimmedAtHead)TextBeforeCursor><Selection><TextAfterCursor(maybeTrimmedAtTail)>
+        // 2. <(maybeTrimmedAtHead)TextBeforeCursor><TextAfterCursor(maybeTrimmedAtTail)>
+        //
+        final int sourceSelLength = subTextSelEnd - subTextSelStart;
+        // When the selected text is too long, drop it.
+        final int newSelLength = (sourceSelLength > MAX_INITIAL_SELECTION_LENGTH)
+                ? 0 : sourceSelLength;
+
+        // Distribute rest of length quota to TextBeforeCursor and TextAfterCursor in 4:1 ratio.
+        final int subTextBeforeCursorLength = subTextSelStart;
+        final int subTextAfterCursorLength = subTextLength - subTextSelEnd;
+        final int maxLengthMinusSelection = MEMORY_EFFICIENT_TEXT_LENGTH - newSelLength;
+        final int possibleMaxBeforeCursorLength =
+                Math.min(subTextBeforeCursorLength, (int) (0.8 * maxLengthMinusSelection));
+        int newAfterCursorLength = Math.min(subTextAfterCursorLength,
+                maxLengthMinusSelection - possibleMaxBeforeCursorLength);
+        int newBeforeCursorLength = Math.min(subTextBeforeCursorLength,
+                maxLengthMinusSelection - newAfterCursorLength);
+
+        // As trimming may happen at the head of TextBeforeCursor, calculate new starting position.
+        int newBeforeCursorHead = subTextBeforeCursorLength - newBeforeCursorLength;
+
+        // We don't want to cut surrogate pairs in the middle. Exam that at the new head and tail.
+        if (isCutOnSurrogate(subText,
+                subTextSelStart - newBeforeCursorLength, TrimPolicy.HEAD)) {
+            newBeforeCursorHead = newBeforeCursorHead + 1;
+            newBeforeCursorLength = newBeforeCursorLength - 1;
+        }
+        if (isCutOnSurrogate(subText,
+                subTextSelEnd + newAfterCursorLength - 1, TrimPolicy.TAIL)) {
+            newAfterCursorLength = newAfterCursorLength - 1;
+        }
+
+        // Now we know where to trim, compose the initialSurroundingText.
+        final int newTextLength = newBeforeCursorLength + newSelLength + newAfterCursorLength;
+        CharSequence newInitialSurroundingText;
+        if (newSelLength != sourceSelLength) {
+            final CharSequence beforeCursor = subText.subSequence(newBeforeCursorHead,
+                    newBeforeCursorHead + newBeforeCursorLength);
+
+            final CharSequence afterCursor = subText.subSequence(subTextSelEnd,
+                    subTextSelEnd + newAfterCursorLength);
+
+            newInitialSurroundingText = TextUtils.concat(beforeCursor, afterCursor);
+        } else {
+            newInitialSurroundingText = subText
+                    .subSequence(newBeforeCursorHead, newBeforeCursorHead + newTextLength);
+        }
+
+        // As trimming may happen at the head, adjust cursor position in the initialSurroundingText
+        // obj.
+        newBeforeCursorHead = 0;
+        final int newSelHead = newBeforeCursorHead + newBeforeCursorLength;
+        mInitialSurroundingText = new InitialSurroundingText(
+                newInitialSurroundingText, newSelHead, newSelHead + newSelLength);
+    }
+
+    /**
+     * Get <var>n</var> characters of text before the current cursor position. May be {@code null}
+     * when the protocol is not supported.
+     *
+     * @param length The expected length of the text.
+     * @param flags Supplies additional options controlling how the text is returned. May be
+     * either 0 or {@link InputConnection#GET_TEXT_WITH_STYLES}.
+     * @return the text before the cursor position; the length of the returned text might be less
+     * than <var>n</var>. When there is no text before the cursor, an empty string will be returned.
+     * It could also be {@code null} when the editor or system could not support this protocol.
+     */
+    @Nullable
+    public CharSequence getInitialTextBeforeCursor(int length, int flags) {
+        return mInitialSurroundingText.getInitialTextBeforeCursor(length, flags);
+    }
+
+    /**
+     * Gets the selected text, if any. May be {@code null} when no text is selected or the selected
+     * text is way too long.
+     *
+     * @param flags Supplies additional options controlling how the text is returned. May be
+     * either 0 or {@link InputConnection#GET_TEXT_WITH_STYLES}.
+     * @return the text that is currently selected, if any. It could be an empty string when there
+     * is no text selected. When {@code null} is returned, the selected text might be too long or
+     * this protocol is not supported.
+     */
+    @Nullable
+    public CharSequence getInitialSelectedText(int flags) {
+        // Swap selection start and end if necessary.
+        final int correctedTextSelStart = initialSelStart > initialSelEnd
+                ? initialSelEnd : initialSelStart;
+        final int correctedTextSelEnd = initialSelStart > initialSelEnd
+                ? initialSelStart : initialSelEnd;
+
+        final int sourceSelLength = correctedTextSelEnd - correctedTextSelStart;
+        if (initialSelStart < 0 || initialSelEnd < 0
+                || mInitialSurroundingText.getSelectionLength() != sourceSelLength) {
+            return null;
+        }
+        return mInitialSurroundingText.getInitialSelectedText(flags);
+    }
+
+    /**
+     * Get <var>n</var> characters of text after the current cursor position. May be {@code null}
+     * when the protocol is not supported.
+     *
+     * @param length The expected length of the text.
+     * @param flags Supplies additional options controlling how the text is returned. May be
+     * either 0 or {@link InputConnection#GET_TEXT_WITH_STYLES}.
+     * @return the text after the cursor position; the length of the returned text might be less
+     * than <var>n</var>. When there is no text after the cursor, an empty string will be returned.
+     * It could also be {@code null} when the editor or system could not support this protocol.
+     */
+    @Nullable
+    public CharSequence getInitialTextAfterCursor(int length, int flags) {
+        return mInitialSurroundingText.getInitialTextAfterCursor(length, flags);
+    }
+
+    private static boolean isCutOnSurrogate(CharSequence sourceText, int cutPosition,
+            @TrimPolicy int policy) {
+        switch (policy) {
+            case TrimPolicy.HEAD:
+                return Character.isLowSurrogate(sourceText.charAt(cutPosition));
+            case TrimPolicy.TAIL:
+                return Character.isHighSurrogate(sourceText.charAt(cutPosition));
+            default:
+                return false;
+        }
+    }
+
+    private static boolean isPasswordInputType(int inputType) {
+        final int variation =
+                inputType & (TYPE_MASK_CLASS | TYPE_MASK_VARIATION);
+        return variation
+                == (TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_PASSWORD)
+                || variation
+                == (TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_WEB_PASSWORD)
+                || variation
+                == (TYPE_CLASS_NUMBER | TYPE_NUMBER_VARIATION_PASSWORD);
+    }
+
     /**
      * Ensure that the data in this EditorInfo is compatible with an application
      * that was developed against the given target API version.  This can
@@ -573,6 +813,7 @@
         dest.writeInt(fieldId);
         dest.writeString(fieldName);
         dest.writeBundle(extras);
+        mInitialSurroundingText.writeToParcel(dest, flags);
         if (hintLocales != null) {
             hintLocales.writeToParcel(dest, flags);
         } else {
@@ -603,6 +844,9 @@
                     res.fieldId = source.readInt();
                     res.fieldName = source.readString();
                     res.extras = source.readBundle();
+                    InitialSurroundingText initialSurroundingText =
+                            InitialSurroundingText.CREATOR.createFromParcel(source);
+                    res.mInitialSurroundingText = initialSurroundingText;
                     LocaleList hintLocales = LocaleList.CREATOR.createFromParcel(source);
                     res.hintLocales = hintLocales.isEmpty() ? null : hintLocales;
                     res.contentMimeTypes = source.readStringArray();
@@ -619,4 +863,93 @@
         return 0;
     }
 
+    // TODO(b/148035211): Unit tests for this class
+    static final class InitialSurroundingText implements Parcelable {
+        @Nullable final CharSequence mSurroundingText;
+        final int mSelectionHead;
+        final int mSelectionEnd;
+
+        InitialSurroundingText() {
+            mSurroundingText = null;
+            mSelectionHead = 0;
+            mSelectionEnd = 0;
+        }
+
+        InitialSurroundingText(@Nullable CharSequence surroundingText, int selectionHead,
+                int selectionEnd) {
+            mSurroundingText = surroundingText;
+            mSelectionHead = selectionHead;
+            mSelectionEnd = selectionEnd;
+        }
+
+        @Nullable
+        private CharSequence getInitialTextBeforeCursor(int n, int flags) {
+            if (mSurroundingText == null) {
+                return null;
+            }
+
+            final int length = Math.min(n, mSelectionHead);
+            return ((flags & InputConnection.GET_TEXT_WITH_STYLES) != 0)
+                    ? mSurroundingText.subSequence(mSelectionHead - length, mSelectionHead)
+                    : TextUtils.substring(mSurroundingText, mSelectionHead - length,
+                            mSelectionHead);
+        }
+
+        @Nullable
+        private CharSequence getInitialSelectedText(int flags) {
+            if (mSurroundingText == null) {
+                return null;
+            }
+
+            return ((flags & InputConnection.GET_TEXT_WITH_STYLES) != 0)
+                    ? mSurroundingText.subSequence(mSelectionHead, mSelectionEnd)
+                    : TextUtils.substring(mSurroundingText, mSelectionHead, mSelectionEnd);
+        }
+
+        @Nullable
+        private CharSequence getInitialTextAfterCursor(int n, int flags) {
+            if (mSurroundingText == null) {
+                return null;
+            }
+
+            final int length = Math.min(n, mSurroundingText.length() - mSelectionEnd);
+            return ((flags & InputConnection.GET_TEXT_WITH_STYLES) != 0)
+                    ? mSurroundingText.subSequence(mSelectionEnd, mSelectionEnd + length)
+                    : TextUtils.substring(mSurroundingText, mSelectionEnd, mSelectionEnd + length);
+        }
+
+        private int getSelectionLength() {
+            return mSelectionEnd - mSelectionHead;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            TextUtils.writeToParcel(mSurroundingText, dest, flags);
+            dest.writeInt(mSelectionHead);
+            dest.writeInt(mSelectionEnd);
+        }
+
+        public static final @android.annotation.NonNull Parcelable.Creator<InitialSurroundingText>
+                CREATOR = new Parcelable.Creator<InitialSurroundingText>() {
+                    @Override
+                    public InitialSurroundingText createFromParcel(Parcel source) {
+                        final CharSequence initialText =
+                                TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+                        final int selectionHead = source.readInt();
+                        final int selectionEnd = source.readInt();
+
+                        return new InitialSurroundingText(initialText, selectionHead, selectionEnd);
+                    }
+
+                    @Override
+                    public InitialSurroundingText[] newArray(int size) {
+                        return new InitialSurroundingText[size];
+                    }
+                };
+    }
 }
diff --git a/core/java/android/view/inputmethod/InlineSuggestion.java b/core/java/android/view/inputmethod/InlineSuggestion.java
index c10144e..a32ea4b 100644
--- a/core/java/android/view/inputmethod/InlineSuggestion.java
+++ b/core/java/android/view/inputmethod/InlineSuggestion.java
@@ -19,6 +19,7 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.content.Context;
 import android.os.AsyncTask;
 import android.os.Parcelable;
@@ -61,6 +62,20 @@
     private final @Nullable IInlineContentProvider mContentProvider;
 
     /**
+     * Creates a new {@link InlineSuggestion}, for testing purpose.
+     *
+     * @hide
+     */
+    @TestApi
+    @NonNull
+    public static InlineSuggestion newInlineSuggestion(@NonNull InlineSuggestionInfo info) {
+        return new InlineSuggestion(info, null);
+    }
+
+
+
+
+    /**
      * Inflates a view with the content of this suggestion at a specific size.
      * The size must be between the {@link InlinePresentationSpec#getMinSize() min size}
      * and the {@link InlinePresentationSpec#getMaxSize() max size} of the presentation
@@ -271,10 +286,10 @@
     };
 
     @DataClass.Generated(
-            time = 1575933636929L,
+            time = 1578972138081L,
             codegenVersion = "1.0.14",
             sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestion.java",
-            inputSignatures = "private static final  java.lang.String TAG\nprivate final @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo mInfo\nprivate final @android.annotation.Nullable com.android.internal.view.inline.IInlineContentProvider mContentProvider\npublic  void inflate(android.content.Context,android.util.Size,java.util.concurrent.Executor,java.util.function.Consumer<android.view.View>)\nclass InlineSuggestion extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
+            inputSignatures = "private static final  java.lang.String TAG\nprivate final @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo mInfo\nprivate final @android.annotation.Nullable com.android.internal.view.inline.IInlineContentProvider mContentProvider\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestion newInlineSuggestion(android.view.inputmethod.InlineSuggestionInfo)\npublic  void inflate(android.content.Context,android.util.Size,java.util.concurrent.Executor,java.util.function.Consumer<android.view.View>)\nclass InlineSuggestion extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/view/inputmethod/InlineSuggestionInfo.java b/core/java/android/view/inputmethod/InlineSuggestionInfo.java
index 07fce31..195b63a 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionInfo.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionInfo.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.os.Parcelable;
 import android.view.inline.InlinePresentationSpec;
 
@@ -53,6 +54,19 @@
     /** Hints for the type of data being suggested. */
     private final @Nullable String[] mAutofillHints;
 
+    /**
+     * Creates a new {@link InlineSuggestionInfo}, for testing purpose.
+     *
+     * @hide
+     */
+    @TestApi
+    @NonNull
+    public static InlineSuggestionInfo newInlineSuggestionInfo(
+            @NonNull InlinePresentationSpec presentationSpec,
+            @NonNull @Source String source,
+            @Nullable String[] autofillHints) {
+        return new InlineSuggestionInfo(presentationSpec, source, autofillHints);
+    }
 
 
 
@@ -247,10 +261,10 @@
     };
 
     @DataClass.Generated(
-            time = 1574406074120L,
+            time = 1578972121865L,
             codegenVersion = "1.0.14",
             sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionInfo.java",
-            inputSignatures = "public static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_AUTOFILL\npublic static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_PLATFORM\nprivate final @android.annotation.NonNull android.view.inline.InlinePresentationSpec mPresentationSpec\nprivate final @android.annotation.NonNull @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String mSource\nprivate final @android.annotation.Nullable java.lang.String[] mAutofillHints\nclass InlineSuggestionInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
+            inputSignatures = "public static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_AUTOFILL\npublic static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_PLATFORM\nprivate final @android.annotation.NonNull android.view.inline.InlinePresentationSpec mPresentationSpec\nprivate final @android.annotation.NonNull @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String mSource\nprivate final @android.annotation.Nullable java.lang.String[] mAutofillHints\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo newInlineSuggestionInfo(android.view.inline.InlinePresentationSpec,java.lang.String,java.lang.String[])\nclass InlineSuggestionInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
index 386c9cb..860ce90 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
@@ -17,6 +17,8 @@
 package android.view.inputmethod;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityThread;
 import android.os.Parcelable;
 import android.view.inline.InlinePresentationSpec;
 
@@ -49,6 +51,21 @@
      */
     private final @NonNull List<InlinePresentationSpec> mPresentationSpecs;
 
+    /**
+     * The package name of the app that requests for the inline suggestions and will host the
+     * embedded suggestion views. The app does not have to set the value for the field because
+     * it'll be set by the system for safety reasons.
+     */
+    private @NonNull String mHostPackageName;
+
+    /**
+     * @hide
+     * @see {@link #mHostPackageName}.
+     */
+    public void setHostPackageName(@NonNull String hostPackageName) {
+        mHostPackageName = hostPackageName;
+    }
+
     private void onConstructed() {
         Preconditions.checkState(mMaxSuggestionCount >= mPresentationSpecs.size());
     }
@@ -57,9 +74,15 @@
         return SUGGESTION_COUNT_UNLIMITED;
     }
 
+    private static String defaultHostPackageName() {
+        return ActivityThread.currentPackageName();
+    }
+
     /** @hide */
     abstract static class BaseBuilder {
         abstract Builder setPresentationSpecs(@NonNull List<InlinePresentationSpec> value);
+
+        abstract Builder setHostPackageName(@Nullable String value);
     }
 
 
@@ -80,11 +103,15 @@
     @DataClass.Generated.Member
     /* package-private */ InlineSuggestionsRequest(
             int maxSuggestionCount,
-            @NonNull List<InlinePresentationSpec> presentationSpecs) {
+            @NonNull List<InlinePresentationSpec> presentationSpecs,
+            @NonNull String hostPackageName) {
         this.mMaxSuggestionCount = maxSuggestionCount;
         this.mPresentationSpecs = presentationSpecs;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mPresentationSpecs);
+        this.mHostPackageName = hostPackageName;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mHostPackageName);
 
         onConstructed();
     }
@@ -108,6 +135,16 @@
         return mPresentationSpecs;
     }
 
+    /**
+     * The package name of the app that requests for the inline suggestions and will host the
+     * embedded suggestion views. The app does not have to set the value for the field because
+     * it'll be set by the system for safety reasons.
+     */
+    @DataClass.Generated.Member
+    public @NonNull String getHostPackageName() {
+        return mHostPackageName;
+    }
+
     @Override
     @DataClass.Generated.Member
     public String toString() {
@@ -116,13 +153,14 @@
 
         return "InlineSuggestionsRequest { " +
                 "maxSuggestionCount = " + mMaxSuggestionCount + ", " +
-                "presentationSpecs = " + mPresentationSpecs +
+                "presentationSpecs = " + mPresentationSpecs + ", " +
+                "hostPackageName = " + mHostPackageName +
         " }";
     }
 
     @Override
     @DataClass.Generated.Member
-    public boolean equals(@android.annotation.Nullable Object o) {
+    public boolean equals(@Nullable Object o) {
         // You can override field equality logic by defining either of the methods like:
         // boolean fieldNameEquals(InlineSuggestionsRequest other) { ... }
         // boolean fieldNameEquals(FieldType otherValue) { ... }
@@ -134,7 +172,8 @@
         //noinspection PointlessBooleanExpression
         return true
                 && mMaxSuggestionCount == that.mMaxSuggestionCount
-                && java.util.Objects.equals(mPresentationSpecs, that.mPresentationSpecs);
+                && java.util.Objects.equals(mPresentationSpecs, that.mPresentationSpecs)
+                && java.util.Objects.equals(mHostPackageName, that.mHostPackageName);
     }
 
     @Override
@@ -146,6 +185,7 @@
         int _hash = 1;
         _hash = 31 * _hash + mMaxSuggestionCount;
         _hash = 31 * _hash + java.util.Objects.hashCode(mPresentationSpecs);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mHostPackageName);
         return _hash;
     }
 
@@ -157,6 +197,7 @@
 
         dest.writeInt(mMaxSuggestionCount);
         dest.writeParcelableList(mPresentationSpecs, flags);
+        dest.writeString(mHostPackageName);
     }
 
     @Override
@@ -173,11 +214,15 @@
         int maxSuggestionCount = in.readInt();
         List<InlinePresentationSpec> presentationSpecs = new ArrayList<>();
         in.readParcelableList(presentationSpecs, InlinePresentationSpec.class.getClassLoader());
+        String hostPackageName = in.readString();
 
         this.mMaxSuggestionCount = maxSuggestionCount;
         this.mPresentationSpecs = presentationSpecs;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mPresentationSpecs);
+        this.mHostPackageName = hostPackageName;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mHostPackageName);
 
         onConstructed();
     }
@@ -205,6 +250,7 @@
 
         private int mMaxSuggestionCount;
         private @NonNull List<InlinePresentationSpec> mPresentationSpecs;
+        private @NonNull String mHostPackageName;
 
         private long mBuilderFieldsSet = 0L;
 
@@ -260,22 +306,40 @@
             return this;
         }
 
+        /**
+         * The package name of the app that requests for the inline suggestions and will host the
+         * embedded suggestion views. The app does not have to set the value for the field because
+         * it'll be set by the system for safety reasons.
+         */
+        @DataClass.Generated.Member
+        @Override
+        @NonNull Builder setHostPackageName(@NonNull String value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x4;
+            mHostPackageName = value;
+            return this;
+        }
+
         /** Builds the instance. This builder should not be touched after calling this! */
         public @NonNull InlineSuggestionsRequest build() {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x4; // Mark builder used
+            mBuilderFieldsSet |= 0x8; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x1) == 0) {
                 mMaxSuggestionCount = defaultMaxSuggestionCount();
             }
+            if ((mBuilderFieldsSet & 0x4) == 0) {
+                mHostPackageName = defaultHostPackageName();
+            }
             InlineSuggestionsRequest o = new InlineSuggestionsRequest(
                     mMaxSuggestionCount,
-                    mPresentationSpecs);
+                    mPresentationSpecs,
+                    mHostPackageName);
             return o;
         }
 
         private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x4) != 0) {
+            if ((mBuilderFieldsSet & 0x8) != 0) {
                 throw new IllegalStateException(
                         "This Builder should not be reused. Use a new Builder instance instead");
             }
@@ -283,10 +347,10 @@
     }
 
     @DataClass.Generated(
-            time = 1576637222199L,
+            time = 1578948035951L,
             codegenVersion = "1.0.14",
             sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java",
-            inputSignatures = "public static final  int SUGGESTION_COUNT_UNLIMITED\nprivate final  int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.view.inline.InlinePresentationSpec> mPresentationSpecs\nprivate  void onConstructed()\nprivate static  int defaultMaxSuggestionCount()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setPresentationSpecs(java.util.List<android.view.inline.InlinePresentationSpec>)\nclass BaseBuilder extends java.lang.Object implements []")
+            inputSignatures = "public static final  int SUGGESTION_COUNT_UNLIMITED\nprivate final  int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.view.inline.InlinePresentationSpec> mPresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\npublic  void setHostPackageName(java.lang.String)\nprivate  void onConstructed()\nprivate static  int defaultMaxSuggestionCount()\nprivate static  java.lang.String defaultHostPackageName()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setPresentationSpecs(java.util.List<android.view.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nclass BaseBuilder extends java.lang.Object implements []")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/view/inputmethod/InlineSuggestionsResponse.java b/core/java/android/view/inputmethod/InlineSuggestionsResponse.java
index 924a5ee..be833df 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionsResponse.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionsResponse.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.os.Parcelable;
 
 import com.android.internal.util.DataClass;
@@ -33,6 +34,18 @@
 public final class InlineSuggestionsResponse implements Parcelable {
     private final @NonNull List<InlineSuggestion> mInlineSuggestions;
 
+    /**
+     * Creates a new {@link InlineSuggestionsResponse}, for testing purpose.
+     *
+     * @hide
+     */
+    @TestApi
+    @NonNull
+    public static InlineSuggestionsResponse newInlineSuggestionsResponse(
+            @NonNull List<InlineSuggestion> inlineSuggestions) {
+        return new InlineSuggestionsResponse(inlineSuggestions);
+    }
+
 
 
     // Code below generated by codegen v1.0.14.
@@ -151,10 +164,10 @@
     };
 
     @DataClass.Generated(
-            time = 1574406147911L,
+            time = 1578972149519L,
             codegenVersion = "1.0.14",
             sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsResponse.java",
-            inputSignatures = "private final @android.annotation.NonNull java.util.List<android.view.inputmethod.InlineSuggestion> mInlineSuggestions\nclass InlineSuggestionsResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstructor=true)")
+            inputSignatures = "private final @android.annotation.NonNull java.util.List<android.view.inputmethod.InlineSuggestion> mInlineSuggestions\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestionsResponse newInlineSuggestionsResponse(java.util.List<android.view.inputmethod.InlineSuggestion>)\nclass InlineSuggestionsResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstructor=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index e554540..4337ed5 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -158,7 +158,11 @@
      * trigger an IPC round-trip that will take some time. Assume this
      * method consumes a lot of time. Also, please keep in mind the
      * Editor may choose to return less characters than requested even
-     * if they are available for performance reasons.</p>
+     * if they are available for performance reasons. If you are using
+     * this to get the initial text around the cursor, you may consider
+     * using {@link EditorInfo#getInitialTextBeforeCursor(int, int)},
+     * {@link EditorInfo#getInitialSelectedText(int)}, and
+     * {@link EditorInfo#getInitialTextAfterCursor(int, int)} to prevent IPC costs.</p>
      *
      * <p><strong>Editor authors:</strong> please be careful of race
      * conditions in implementing this call. An IME can make a change
@@ -196,7 +200,11 @@
      *
      * <p><strong>IME authors:</strong> please consider this will
      * trigger an IPC round-trip that will take some time. Assume this
-     * method consumes a lot of time.</p>
+     * method consumes a lot of time. If you are using this to get the
+     * initial text around the cursor, you may consider using
+     * {@link EditorInfo#getInitialTextBeforeCursor(int, int)},
+     * {@link EditorInfo#getInitialSelectedText(int)}, and
+     * {@link EditorInfo#getInitialTextAfterCursor(int, int)} to prevent IPC costs.</p>
      *
      * <p><strong>Editor authors:</strong> please be careful of race
      * conditions in implementing this call. An IME can make a change
@@ -234,7 +242,11 @@
      *
      * <p><strong>IME authors:</strong> please consider this will
      * trigger an IPC round-trip that will take some time. Assume this
-     * method consumes a lot of time.</p>
+     * method consumes a lot of time. If you are using this to get the
+     * initial text around the cursor, you may consider using
+     * {@link EditorInfo#getInitialTextBeforeCursor(int, int)},
+     * {@link EditorInfo#getInitialSelectedText(int)}, and
+     * {@link EditorInfo#getInitialTextAfterCursor(int, int)} to prevent IPC costs.</p>
      *
      * <p><strong>Editor authors:</strong> please be careful of race
      * conditions in implementing this call. An IME can make a change
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index cf494ae..91e15c1 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -27,6 +27,7 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.util.Log;
+import android.view.View;
 import android.view.autofill.AutofillId;
 
 import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
@@ -298,7 +299,30 @@
      * until deliberated dismissed by the user in its UI.
      */
     public static final int SHOW_FORCED = 0x00002;
-    
+
+    /**
+     * Request that any soft input part of the input method be shown to the user.
+     *
+     * @param flags Provides additional information about the show request.
+     * Currently may be 0 or have the bit {@link #SHOW_EXPLICIT} set.
+     * @param resultReceiver The client requesting the show may wish to
+     * be told the impact of their request, which should be supplied here.
+     * The result code should be
+     * {@link InputMethodManager#RESULT_UNCHANGED_SHOWN InputMethodManager.RESULT_UNCHANGED_SHOWN},
+     * {@link InputMethodManager#RESULT_UNCHANGED_HIDDEN InputMethodManager.RESULT_UNCHANGED_HIDDEN},
+     * {@link InputMethodManager#RESULT_SHOWN InputMethodManager.RESULT_SHOWN}, or
+     * {@link InputMethodManager#RESULT_HIDDEN InputMethodManager.RESULT_HIDDEN}.
+     * @param showInputToken an opaque {@link android.os.Binder} token to identify which API call
+     *        of {@link InputMethodManager#showSoftInput(View, int)} is associated with
+     *        this callback.
+     * @hide
+     */
+    @MainThread
+    default public void showSoftInputWithToken(int flags, ResultReceiver resultReceiver,
+            IBinder showInputToken) {
+        showSoftInput(flags, resultReceiver);
+    }
+
     /**
      * Request that any soft input part of the input method be shown to the user.
      * 
@@ -314,7 +338,7 @@
      */
     @MainThread
     public void showSoftInput(int flags, ResultReceiver resultReceiver);
-    
+
     /**
      * Request that any soft input part of the input method be hidden from the user.
      * @param flags Provides additional information about the show request.
@@ -336,4 +360,12 @@
      */
     @MainThread
     public void changeInputMethodSubtype(InputMethodSubtype subtype);
+
+    /**
+     * Update token of the client window requesting {@link #showSoftInput(int, ResultReceiver)}
+     * @param showInputToken dummy app window token for window requesting
+     *        {@link InputMethodManager#showSoftInput(View, int)}
+     * @hide
+     */
+    public void setCurrentShowInputToken(IBinder showInputToken);
 }
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 67ce8d2..307abd2 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -59,6 +59,7 @@
 import android.util.Printer;
 import android.util.SparseArray;
 import android.view.Display;
+import android.view.ImeFocusController;
 import android.view.ImeInsetsSourceConsumer;
 import android.view.InputChannel;
 import android.view.InputEvent;
@@ -93,9 +94,12 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.CancellationException;
-import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -361,10 +365,10 @@
     boolean mActive = false;
 
     /**
-     * {@code true} if next {@link #onPostWindowFocus(View, View, int, int)} needs to
+     * {@code true} if next {@link ImeFocusController#onPostWindowFocus} needs to
      * restart input.
      */
-    boolean mRestartOnNextWindowFocus = true;
+    private boolean mRestartOnNextWindowFocus = true;
 
     /**
      * As reported by IME through InputConnection.
@@ -377,22 +381,8 @@
      * This is the root view of the overall window that currently has input
      * method focus.
      */
-    @UnsupportedAppUsage
-    View mCurRootView;
-    /**
-     * This is the view that should currently be served by an input method,
-     * regardless of the state of setting that up.
-     */
-    // See comment to mH field in regard to @UnsupportedAppUsage
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
-    View mServedView;
-    /**
-     * This is then next view that will be served by the input method, when
-     * we get around to updating things.
-     */
-    // See comment to mH field in regard to @UnsupportedAppUsage
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
-    View mNextServedView;
+    @GuardedBy("mH")
+    ViewRootImpl mCurRootView;
     /**
      * This is set when we are in the process of connecting, to determine
      * when we have actually finished.
@@ -429,7 +419,10 @@
      * in a background thread. Later, if there is an actual startInput it will wait on
      * main thread till the background thread completes.
      */
-    private CompletableFuture<Void> mWindowFocusGainFuture;
+    private Future<?> mWindowFocusGainFuture;
+
+    private ExecutorService mStartInputWorker = Executors.newSingleThreadExecutor(
+            new ImeThreadFactory("StartInputWorker"));
 
     /**
      * The instance that has previously been sent to the input method.
@@ -483,6 +476,8 @@
     final Pool<PendingEvent> mPendingEventPool = new SimplePool<>(20);
     final SparseArray<PendingEvent> mPendingEvents = new SparseArray<>(20);
 
+    final DelegateImpl mDelegate = new DelegateImpl();
+
     // -----------------------------------------------------------
 
     static final int MSG_DUMP = 1;
@@ -558,6 +553,197 @@
         return servedView.hasWindowFocus() || isAutofillUIShowing(servedView);
     }
 
+    private final class DelegateImpl implements
+            ImeFocusController.InputMethodManagerDelegate {
+        /**
+         * Used by {@link ImeFocusController} to start input connection.
+         */
+        @Override
+        public boolean startInput(@StartInputReason int startInputReason, View focusedView,
+                @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
+                int windowFlags) {
+            final View servedView;
+            synchronized (mH) {
+                mCurrentTextBoxAttribute = null;
+                mCompletions = null;
+                mServedConnecting = true;
+                servedView = getServedViewLocked();
+            }
+            if (servedView != null && servedView.getHandler() != null) {
+                // Make sure View checks should be on the UI thread.
+                servedView.getHandler().post(() -> {
+                    if (!servedView.onCheckIsTextEditor()) {
+                        // servedView has changed and it's not editable.
+                        synchronized (mH) {
+                            maybeCallServedViewChangedLocked(null);
+                        }
+                    }
+                });
+            }
+            return startInputInner(startInputReason,
+                    focusedView != null ? focusedView.getWindowToken() : null, startInputFlags,
+                    softInputMode, windowFlags);
+        }
+
+        /**
+         * Used by {@link ImeFocusController} to finish input connection.
+         */
+        @Override
+        public void finishInput() {
+            synchronized (mH) {
+                finishInputLocked();
+            }
+        }
+
+        /**
+         * Used by {@link ImeFocusController} to hide current input method editor.
+         */
+        @Override
+        public void closeCurrentIme() {
+            closeCurrentInput();
+        }
+
+        /**
+         * For {@link ImeFocusController} to start input asynchronously when focus gain.
+         */
+        @Override
+        public void startInputAsyncOnWindowFocusGain(View focusedView,
+                @SoftInputModeFlags int softInputMode, int windowFlags, boolean forceNewFocus) {
+            final boolean forceNewFocus1 = forceNewFocus;
+            final int startInputFlags = getStartInputFlags(focusedView, 0);
+
+            if (mWindowFocusGainFuture != null) {
+                mWindowFocusGainFuture.cancel(false /* mayInterruptIfRunning */);
+            }
+            mWindowFocusGainFuture = mStartInputWorker.submit(() -> {
+                final ImeFocusController controller = getFocusController();
+                if (controller == null) {
+                    return;
+                }
+                if (controller.checkFocus(forceNewFocus1, false)) {
+                    // We need to restart input on the current focus view.  This
+                    // should be done in conjunction with telling the system service
+                    // about the window gaining focus, to help make the transition
+                    // smooth.
+                    if (startInput(StartInputReason.WINDOW_FOCUS_GAIN,
+                            focusedView, startInputFlags, softInputMode, windowFlags)) {
+                        return;
+                    }
+                }
+
+                synchronized (mH) {
+                    // For some reason we didn't do a startInput + windowFocusGain, so
+                    // we'll just do a window focus gain and call it a day.
+                    try {
+                        if (DEBUG) Log.v(TAG, "Reporting focus gain, without startInput");
+                        mService.startInputOrWindowGainedFocus(
+                                StartInputReason.WINDOW_FOCUS_GAIN_REPORT_ONLY, mClient,
+                                focusedView.getWindowToken(), startInputFlags, softInputMode,
+                                windowFlags,
+                                null, null, 0 /* missingMethodFlags */,
+                                mCurRootView.mContext.getApplicationInfo().targetSdkVersion);
+                    } catch (RemoteException e) {
+                        throw e.rethrowFromSystemServer();
+                    }
+                }
+            });
+        }
+
+        /**
+         * Used by {@link ImeFocusController} to finish current composing text.
+         */
+        @Override
+        public void finishComposingText() {
+            if (mServedInputConnectionWrapper != null) {
+                mServedInputConnectionWrapper.finishComposingText();
+            }
+        }
+
+        /**
+         * Used for {@link ImeFocusController} to set the current focused root view.
+         */
+        @Override
+        public void setCurrentRootView(ViewRootImpl rootView) {
+            // If the mCurRootView is losing window focus, release the strong reference to it
+            // so as not to prevent it from being garbage-collected.
+            if (mWindowFocusGainFuture != null) {
+                mWindowFocusGainFuture.cancel(false /* mayInterruptIfRunning */);
+                mWindowFocusGainFuture = null;
+            }
+            synchronized (mH) {
+                mCurRootView = rootView;
+            }
+        }
+
+        /**
+         * Used for {@link ImeFocusController} to return if the root view from the
+         * controller is this {@link InputMethodManager} currently focused.
+         * TODO: Address event-order problem when get current root view in multi-threads.
+         */
+        @Override
+        public boolean isCurrentRootView(ViewRootImpl rootView) {
+            synchronized (mH) {
+                return mCurRootView == rootView;
+            }
+        }
+
+        /**
+         * For {@link ImeFocusController#checkFocus} if needed to force check new focus.
+         */
+        @Override
+        public boolean isRestartOnNextWindowFocus(boolean reset) {
+            final boolean result = mRestartOnNextWindowFocus;
+            if (reset) {
+                mRestartOnNextWindowFocus = false;
+            }
+            return result;
+        }
+    }
+
+    /** @hide */
+    public DelegateImpl getDelegate() {
+        return mDelegate;
+    }
+
+    private View getServedViewLocked() {
+        return mCurRootView != null ? mCurRootView.getImeFocusController().getServedView() : null;
+    }
+
+    private View getNextServedViewLocked() {
+        return mCurRootView != null ? mCurRootView.getImeFocusController().getNextServedView()
+                : null;
+    }
+
+    private void setServedViewLocked(View view) {
+        if (mCurRootView != null) {
+            mCurRootView.getImeFocusController().setServedView(view);
+        }
+    }
+
+    private void setNextServedViewLocked(View view) {
+        if (mCurRootView != null) {
+            mCurRootView.getImeFocusController().setNextServedView(view);
+        }
+    }
+
+    private ImeFocusController getFocusController() {
+        synchronized (mH) {
+            if (mCurRootView != null) {
+                return mCurRootView.getImeFocusController();
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Returns {@code true} when the given view has been served by Input Method.
+     */
+    private boolean hasServedByInputMethodLocked(View view) {
+        final View servedView = getServedViewLocked();
+        return (servedView == view
+                || (servedView != null && servedView.checkInputConnectionProxy(view)));
+    }
+
     class H extends Handler {
         H(Looper looper) {
             super(looper, null, true);
@@ -623,7 +809,8 @@
                         clearBindingLocked();
                         // If we were actively using the last input method, then
                         // we would like to re-connect to the next input method.
-                        if (mServedView != null && mServedView.isFocused()) {
+                        final View servedView = getServedViewLocked();
+                        if (servedView != null && servedView.isFocused()) {
                             mServedConnecting = true;
                         }
                         startInput = mActive;
@@ -658,11 +845,16 @@
                     }
                     // Check focus again in case that "onWindowFocus" is called before
                     // handling this message.
-                    if (mServedView != null && canStartInput(mServedView)) {
-                        if (checkFocusNoStartInput(mRestartOnNextWindowFocus)) {
+                    final View servedView;
+                    synchronized (mH) {
+                        servedView = getServedViewLocked();
+                    }
+                    if (servedView != null && canStartInput(servedView)) {
+                        if (mCurRootView != null && mCurRootView.getImeFocusController()
+                                .checkFocus(mRestartOnNextWindowFocus, false)) {
                             final int reason = active ? StartInputReason.ACTIVATED_BY_IMMS
                                     : StartInputReason.DEACTIVATED_BY_IMMS;
-                            startInputInner(reason, null, 0, 0, 0);
+                            mDelegate.startInput(reason, null, 0, 0, 0);
                         }
                     }
                     return;
@@ -790,6 +982,19 @@
         }
     }
 
+    private static class ImeThreadFactory implements ThreadFactory {
+        private final String mThreadName;
+
+        ImeThreadFactory(String name) {
+            mThreadName = name;
+        }
+
+        @Override
+        public Thread newThread(Runnable r) {
+            return new Thread(r, mThreadName);
+        }
+    }
+
     final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() {
         @Override
         protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
@@ -1193,10 +1398,7 @@
 
         checkFocus();
         synchronized (mH) {
-            return (mServedView == view
-                    || (mServedView != null
-                            && mServedView.checkInputConnectionProxy(view)))
-                    && mCurrentTextBoxAttribute != null;
+            return hasServedByInputMethodLocked(view) && mCurrentTextBoxAttribute != null;
         }
     }
 
@@ -1206,7 +1408,7 @@
     public boolean isActive() {
         checkFocus();
         synchronized (mH) {
-            return mServedView != null && mCurrentTextBoxAttribute != null;
+            return getServedViewLocked() != null && mCurrentTextBoxAttribute != null;
         }
     }
 
@@ -1267,11 +1469,14 @@
      */
     @UnsupportedAppUsage
     void finishInputLocked() {
-        mNextServedView = null;
         mActivityViewToScreenMatrix = null;
-        if (mServedView != null) {
-            if (DEBUG) Log.v(TAG, "FINISH INPUT: mServedView=" + dumpViewInfo(mServedView));
-            mServedView = null;
+        setNextServedViewLocked(null);
+        if (getServedViewLocked() != null) {
+            if (DEBUG) {
+                Log.v(TAG, "FINISH INPUT: mServedView="
+                        + dumpViewInfo(getServedViewLocked()));
+            }
+            setServedViewLocked(null);
             mCompletions = null;
             mServedConnecting = false;
             clearConnectionLocked();
@@ -1288,8 +1493,7 @@
 
         checkFocus();
         synchronized (mH) {
-            if (mServedView != view && (mServedView == null
-                            || !mServedView.checkInputConnectionProxy(view))) {
+            if (!hasServedByInputMethodLocked(view)) {
                 return;
             }
 
@@ -1313,8 +1517,7 @@
 
         checkFocus();
         synchronized (mH) {
-            if (mServedView != view && (mServedView == null
-                    || !mServedView.checkInputConnectionProxy(view))) {
+            if (!hasServedByInputMethodLocked(view)) {
                 return;
             }
 
@@ -1428,13 +1631,13 @@
 
         checkFocus();
         synchronized (mH) {
-            if (mServedView != view && (mServedView == null
-                    || !mServedView.checkInputConnectionProxy(view))) {
+            if (!hasServedByInputMethodLocked(view)) {
                 return false;
             }
 
             try {
-                return mService.showSoftInput(mClient, flags, resultReceiver);
+                return mService.showSoftInput(
+                        mClient, view.getWindowToken(), flags, resultReceiver);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -1456,7 +1659,8 @@
             Log.w(TAG, "showSoftInputUnchecked() is a hidden method, which will be removed "
                     + "soon. If you are using android.support.v7.widget.SearchView, please update "
                     + "to version 26.0 or newer version.");
-            mService.showSoftInput(mClient, flags, resultReceiver);
+            mService.showSoftInput(
+                    mClient, mCurRootView.getView().getWindowToken(), flags, resultReceiver);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1520,7 +1724,8 @@
             ResultReceiver resultReceiver) {
         checkFocus();
         synchronized (mH) {
-            if (mServedView == null || mServedView.getWindowToken() != windowToken) {
+            final View servedView = getServedViewLocked();
+            if (servedView == null || servedView.getWindowToken() != windowToken) {
                 return false;
             }
 
@@ -1547,7 +1752,8 @@
      **/
     public void toggleSoftInputFromWindow(IBinder windowToken, int showFlags, int hideFlags) {
         synchronized (mH) {
-            if (mServedView == null || mServedView.getWindowToken() != windowToken) {
+            final View servedView = getServedViewLocked();
+            if (servedView == null || servedView.getWindowToken() != windowToken) {
                 return;
             }
             if (mCurMethod != null) {
@@ -1598,8 +1804,7 @@
 
         checkFocus();
         synchronized (mH) {
-            if (mServedView != view && (mServedView == null
-                    || !mServedView.checkInputConnectionProxy(view))) {
+            if (!hasServedByInputMethodLocked(view)) {
                 return;
             }
 
@@ -1609,6 +1814,12 @@
         startInputInner(StartInputReason.APP_CALLED_RESTART_INPUT_API, null, 0, 0, 0);
     }
 
+    /**
+     * Called when {@link DelegateImpl#startInput}, {@link #restartInput(View)},
+     * {@link #MSG_BIND} or {@link #MSG_UNBIND}.
+     * Note that this method should *NOT* be called inside of {@code mH} lock to prevent start input
+     * background thread may blocked by other methods which already inside {@code mH} lock.
+     */
     boolean startInputInner(@StartInputReason int startInputReason,
             @Nullable IBinder windowGainingFocus, @StartInputFlags int startInputFlags,
             @SoftInputModeFlags int softInputMode, int windowFlags) {
@@ -1626,7 +1837,7 @@
 
         final View view;
         synchronized (mH) {
-            view = mServedView;
+            view = getServedViewLocked();
 
             // Make sure we have a window token for the served view.
             if (DEBUG) {
@@ -1645,10 +1856,7 @@
                 Log.e(TAG, "ABORT input: ServedView must be attached to a Window");
                 return false;
             }
-            startInputFlags |= StartInputFlags.VIEW_HAS_FOCUS;
-            if (view.onCheckIsTextEditor()) {
-                startInputFlags |= StartInputFlags.IS_TEXT_EDITOR;
-            }
+            startInputFlags = getStartInputFlags(view, startInputFlags);
             softInputMode = view.getViewRootImpl().mWindowAttributes.softInputMode;
             windowFlags = view.getViewRootImpl().mWindowAttributes.flags;
         }
@@ -1671,7 +1879,7 @@
             // The view is running on a different thread than our own, so
             // we need to reschedule our work for over there.
             if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread");
-            vh.post(() -> startInputInner(startInputReason, null, 0, 0, 0));
+            vh.post(() -> mDelegate.startInput(startInputReason, null, 0, 0, 0));
             return false;
         }
 
@@ -1690,11 +1898,12 @@
         synchronized (mH) {
             // Now that we are locked again, validate that our state hasn't
             // changed.
-            if (mServedView != view || !mServedConnecting) {
+            final View servedView = getServedViewLocked();
+            if (servedView != view || !mServedConnecting) {
                 // Something else happened, so abort.
                 if (DEBUG) Log.v(TAG,
                         "Starting input: finished by someone else. view=" + dumpViewInfo(view)
-                        + " mServedView=" + dumpViewInfo(mServedView)
+                        + " servedView=" + dumpViewInfo(servedView)
                         + " mServedConnecting=" + mServedConnecting);
                 return false;
             }
@@ -1785,158 +1994,28 @@
         return true;
     }
 
-    /**
-     * When the focused window is dismissed, this method is called to finish the
-     * input method started before.
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public void windowDismissed(IBinder appWindowToken) {
-        checkFocus();
-        synchronized (mH) {
-            if (mServedView != null &&
-                    mServedView.getWindowToken() == appWindowToken) {
-                finishInputLocked();
-            }
-            if (mCurRootView != null &&
-                    mCurRootView.getWindowToken() == appWindowToken) {
-                mCurRootView = null;
-            }
+    private int getStartInputFlags(View focusedView, int startInputFlags) {
+        startInputFlags |= StartInputFlags.VIEW_HAS_FOCUS;
+        if (focusedView.onCheckIsTextEditor()) {
+            startInputFlags |= StartInputFlags.IS_TEXT_EDITOR;
         }
+        return startInputFlags;
     }
 
     /**
-     * Call this when a view receives focus.
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public void focusIn(View view) {
-        synchronized (mH) {
-            focusInLocked(view);
-        }
-    }
-
-    void focusInLocked(View view) {
-        if (DEBUG) Log.v(TAG, "focusIn: " + dumpViewInfo(view));
-
-        if (view != null && view.isTemporarilyDetached()) {
-            // This is a request from a view that is temporarily detached from a window.
-            if (DEBUG) Log.v(TAG, "Temporarily detached view, ignoring");
-            return;
-        }
-
-        if (mCurRootView != view.getRootView()) {
-            // This is a request from a window that isn't in the window with
-            // IME focus, so ignore it.
-            if (DEBUG) Log.v(TAG, "Not IME target window, ignoring");
-            return;
-        }
-
-        mNextServedView = view;
-        scheduleCheckFocusLocked(view);
-    }
-
-    /**
-     * Call this when a view loses focus.
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public void focusOut(View view) {
-        synchronized (mH) {
-            if (DEBUG) Log.v(TAG, "focusOut: view=" + dumpViewInfo(view)
-                    + " mServedView=" + dumpViewInfo(mServedView));
-            if (mServedView != view) {
-                // The following code would auto-hide the IME if we end up
-                // with no more views with focus.  This can happen, however,
-                // whenever we go into touch mode, so it ends up hiding
-                // at times when we don't really want it to.  For now it
-                // seems better to just turn it all off.
-                // TODO: Check view.isTemporarilyDetached() when re-enable the following code.
-                if (false && canStartInput(view)) {
-                    mNextServedView = null;
-                    scheduleCheckFocusLocked(view);
-                }
-            }
-        }
-    }
-
-    /**
-     * Call this when a view is being detached from a {@link android.view.Window}.
-     * @hide
-     */
-    public void onViewDetachedFromWindow(View view) {
-        synchronized (mH) {
-            if (DEBUG) Log.v(TAG, "onViewDetachedFromWindow: view=" + dumpViewInfo(view)
-                    + " mServedView=" + dumpViewInfo(mServedView));
-            if (mServedView == view) {
-                mNextServedView = null;
-                scheduleCheckFocusLocked(view);
-            }
-        }
-    }
-
-    static void scheduleCheckFocusLocked(View view) {
-        ViewRootImpl viewRootImpl = view.getViewRootImpl();
-        if (viewRootImpl != null) {
-            viewRootImpl.dispatchCheckFocus();
-        }
-    }
-
-    /**
+     * Check the next served view from {@link ImeFocusController} if needs to start input.
+     * Note that this method should *NOT* be called inside of {@code mH} lock to prevent start input
+     * background thread may blocked by other methods which already inside {@code mH} lock.
      * @hide
      */
     @UnsupportedAppUsage
     public void checkFocus() {
-        if (checkFocusNoStartInput(false)) {
-            startInputInner(StartInputReason.CHECK_FOCUS, null, 0, 0, 0);
+        final ImeFocusController controller = getFocusController();
+        if (controller != null) {
+            controller.checkFocus(false /* forceNewFocus */, true /* startInput */);
         }
     }
 
-    private boolean checkFocusNoStartInput(boolean forceNewFocus) {
-        // This is called a lot, so short-circuit before locking.
-        if (mServedView == mNextServedView && !forceNewFocus) {
-            return false;
-        }
-
-        final ControlledInputConnectionWrapper ic;
-        synchronized (mH) {
-            if (mServedView == mNextServedView && !forceNewFocus) {
-                return false;
-            }
-            if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView
-                    + " next=" + mNextServedView
-                    + " forceNewFocus=" + forceNewFocus
-                    + " package="
-                    + (mServedView != null ? mServedView.getContext().getPackageName() : "<none>"));
-
-            if (mNextServedView == null) {
-                finishInputLocked();
-                // In this case, we used to have a focused view on the window,
-                // but no longer do.  We should make sure the input method is
-                // no longer shown, since it serves no purpose.
-                closeCurrentInput();
-                return false;
-            }
-
-            ic = mServedInputConnectionWrapper;
-
-            mServedView = mNextServedView;
-            mCurrentTextBoxAttribute = null;
-            mCompletions = null;
-            mServedConnecting = true;
-            // servedView has changed and it's not editable.
-            if (!mServedView.onCheckIsTextEditor()) {
-                maybeCallServedViewChangedLocked(null);
-            }
-        }
-
-        if (ic != null) {
-            ic.finishComposingText();
-        }
-
-        return true;
-    }
-
     @UnsupportedAppUsage
     void closeCurrentInput() {
         try {
@@ -1947,92 +2026,6 @@
     }
 
     /**
-     * Called by ViewAncestor when its window gets input focus.
-     * @hide
-     */
-    public void onPostWindowFocus(View rootView, View focusedView,
-            @SoftInputModeFlags int softInputMode, int windowFlags) {
-        boolean forceNewFocus = false;
-        synchronized (mH) {
-            if (DEBUG) Log.v(TAG, "onWindowFocus: " + focusedView
-                    + " softInputMode=" + InputMethodDebug.softInputModeToString(softInputMode)
-                    + " flags=#" + Integer.toHexString(windowFlags));
-            if (mRestartOnNextWindowFocus) {
-                if (DEBUG) Log.v(TAG, "Restarting due to mRestartOnNextWindowFocus");
-                mRestartOnNextWindowFocus = false;
-                forceNewFocus = true;
-            }
-            focusInLocked(focusedView != null ? focusedView : rootView);
-        }
-
-        int startInputFlags = 0;
-        if (focusedView != null) {
-            startInputFlags |= StartInputFlags.VIEW_HAS_FOCUS;
-            if (focusedView.onCheckIsTextEditor()) {
-                startInputFlags |= StartInputFlags.IS_TEXT_EDITOR;
-            }
-        }
-
-        final boolean forceNewFocus1 = forceNewFocus;
-        final int startInputFlags1 = startInputFlags;
-        if (mWindowFocusGainFuture != null) {
-            mWindowFocusGainFuture.cancel(false/* mayInterruptIfRunning */);
-        }
-        mWindowFocusGainFuture = CompletableFuture.runAsync(() -> {
-            if (checkFocusNoStartInput(forceNewFocus1)) {
-                // We need to restart input on the current focus view.  This
-                // should be done in conjunction with telling the system service
-                // about the window gaining focus, to help make the transition
-                // smooth.
-                if (startInputInner(StartInputReason.WINDOW_FOCUS_GAIN, rootView.getWindowToken(),
-                        startInputFlags1, softInputMode, windowFlags)) {
-                    return;
-                }
-            }
-
-            // For some reason we didn't do a startInput + windowFocusGain, so
-            // we'll just do a window focus gain and call it a day.
-            synchronized (mH) {
-                try {
-                    if (DEBUG) Log.v(TAG, "Reporting focus gain, without startInput");
-                    mService.startInputOrWindowGainedFocus(
-                            StartInputReason.WINDOW_FOCUS_GAIN_REPORT_ONLY, mClient,
-                            rootView.getWindowToken(), startInputFlags1, softInputMode, windowFlags,
-                            null, null, 0 /* missingMethodFlags */,
-                            rootView.getContext().getApplicationInfo().targetSdkVersion);
-                } catch (RemoteException e) {
-                    throw e.rethrowFromSystemServer();
-                }
-            }
-        });
-    }
-
-    /** @hide */
-    @UnsupportedAppUsage
-    public void onPreWindowFocus(View rootView, boolean hasWindowFocus) {
-        synchronized (mH) {
-            if (rootView == null) {
-                mCurRootView = null;
-            } if (hasWindowFocus) {
-                mCurRootView = rootView;
-            } else if (rootView == mCurRootView) {
-                // If the mCurRootView is losing window focus, release the strong reference to it
-                // so as not to prevent it from being garbage-collected.
-                mCurRootView = null;
-                if (mWindowFocusGainFuture != null) {
-                    mWindowFocusGainFuture.cancel(false /* mayInterruptIfRunning */);
-                    mWindowFocusGainFuture = null;
-                }
-            } else {
-                if (DEBUG) {
-                    Log.v(TAG, "Ignoring onPreWindowFocus()."
-                            + " mCurRootView=" + mCurRootView + " rootView=" + rootView);
-                }
-            }
-        }
-    }
-
-    /**
      * Register for IME state callbacks and applying visibility in
      * {@link android.view.ImeInsetsSourceConsumer}.
      * @hide
@@ -2071,10 +2064,11 @@
      */
     public boolean requestImeShow(ResultReceiver resultReceiver) {
         synchronized (mH) {
-            if (mServedView == null) {
+            final View servedView = getServedViewLocked();
+            if (servedView == null) {
                 return false;
             }
-            showSoftInput(mServedView, 0 /* flags */, resultReceiver);
+            showSoftInput(servedView, 0 /* flags */, resultReceiver);
             return true;
         }
     }
@@ -2116,9 +2110,8 @@
 
         checkFocus();
         synchronized (mH) {
-            if ((mServedView != view && (mServedView == null
-                        || !mServedView.checkInputConnectionProxy(view)))
-                    || mCurrentTextBoxAttribute == null || mCurMethod == null) {
+            if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null
+                    || mCurMethod == null) {
                 return;
             }
 
@@ -2166,12 +2159,17 @@
             return;
         }
 
-        final boolean focusChanged = mServedView != mNextServedView;
+        final View servedView;
+        final View nextServedView;
+        synchronized (mH) {
+            servedView = getServedViewLocked();
+            nextServedView = getNextServedViewLocked();
+        }
+        final boolean focusChanged = servedView != nextServedView;
         checkFocus();
         synchronized (mH) {
-            if ((mServedView != view && (mServedView == null
-                    || !mServedView.checkInputConnectionProxy(view)))
-                    || mCurrentTextBoxAttribute == null || mCurMethod == null) {
+            if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null
+                    || mCurMethod == null) {
                 return;
             }
             try {
@@ -2239,9 +2237,8 @@
 
         checkFocus();
         synchronized (mH) {
-            if ((mServedView != view && (mServedView == null
-                        || !mServedView.checkInputConnectionProxy(view)))
-                    || mCurrentTextBoxAttribute == null || mCurMethod == null) {
+            if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null
+                    || mCurMethod == null) {
                 return;
             }
 
@@ -2277,9 +2274,8 @@
 
         checkFocus();
         synchronized (mH) {
-            if ((mServedView != view &&
-                    (mServedView == null || !mServedView.checkInputConnectionProxy(view)))
-                    || mCurrentTextBoxAttribute == null || mCurMethod == null) {
+            if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null
+                    || mCurMethod == null) {
                 return;
             }
             // If immediate bit is set, we will call updateCursorAnchorInfo() even when the data has
@@ -2335,9 +2331,8 @@
 
         checkFocus();
         synchronized (mH) {
-            if ((mServedView != view && (mServedView == null
-                        || !mServedView.checkInputConnectionProxy(view)))
-                    || mCurrentTextBoxAttribute == null || mCurMethod == null) {
+            if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null
+                    || mCurMethod == null) {
                 return;
             }
             try {
@@ -2558,8 +2553,9 @@
         synchronized (mH) {
             ViewRootImpl viewRootImpl = targetView != null ? targetView.getViewRootImpl() : null;
             if (viewRootImpl == null) {
-                if (mServedView != null) {
-                    viewRootImpl = mServedView.getViewRootImpl();
+                final View servedView = getServedViewLocked();
+                if (servedView != null) {
+                    viewRootImpl = servedView.getViewRootImpl();
                 }
             }
             if (viewRootImpl != null) {
@@ -2740,6 +2736,7 @@
 
     /**
      * Show the settings for enabling subtypes of the specified input method.
+     *
      * @param imiId An input method, whose subtypes settings will be shown. If imiId is null,
      * subtypes of all input methods will be shown.
      */
@@ -3038,8 +3035,8 @@
         p.println("  mFullscreenMode=" + mFullscreenMode);
         p.println("  mCurMethod=" + mCurMethod);
         p.println("  mCurRootView=" + mCurRootView);
-        p.println("  mServedView=" + mServedView);
-        p.println("  mNextServedView=" + mNextServedView);
+        p.println("  mServedView=" + getServedViewLocked());
+        p.println("  mNextServedView=" + getNextServedViewLocked());
         p.println("  mServedConnecting=" + mServedConnecting);
         if (mCurrentTextBoxAttribute != null) {
             p.println("  mCurrentTextBoxAttribute:");
@@ -3115,6 +3112,8 @@
         sb.append(",window=" + view.getWindowToken());
         sb.append(",displayId=" + view.getContext().getDisplayId());
         sb.append(",temporaryDetach=" + view.isTemporarilyDetached());
+        sb.append(",hasImeFocus=" + view.hasImeFocus());
+
         return sb.toString();
     }
 }
diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java
index 1aa2aec..bda12b0 100644
--- a/core/java/android/view/textclassifier/TextLinks.java
+++ b/core/java/android/view/textclassifier/TextLinks.java
@@ -113,7 +113,7 @@
      * Returns the text that was used to generate these links.
      */
     @NonNull
-    public String getText() {
+    public CharSequence getText() {
         return mFullText;
     }
 
@@ -370,8 +370,8 @@
         }
 
         /**
-         * @return ordered list of locale preferences that can be used to disambiguate
-         *      the provided text
+         * Returns an ordered list of locale preferences that can be used to disambiguate the
+         * provided text.
          */
         @Nullable
         public LocaleList getDefaultLocales() {
@@ -379,7 +379,8 @@
         }
 
         /**
-         * @return The config representing the set of entities to look for
+         * Returns the config representing the set of entities to look for
+         *
          * @see Builder#setEntityConfig(EntityConfig)
          */
         @Nullable
@@ -398,8 +399,8 @@
         }
 
         /**
-         * @return reference time based on which relative dates (e.g. "tomorrow") should be
-         *      interpreted.
+         * Returns reference time based on which relative dates (e.g. "tomorrow") should be
+         * interpreted.
          */
         @Nullable
         public ZonedDateTime getReferenceTime() {
@@ -473,6 +474,9 @@
             }
 
             /**
+             * Sets ordered list of locale preferences that may be used to disambiguate the
+             * provided text.
+             *
              * @param defaultLocales ordered list of locale preferences that may be used to
              *                       disambiguate the provided text. If no locale preferences exist,
              *                       set this to null or an empty locale list.
@@ -524,9 +528,11 @@
             }
 
             /**
-             * @param referenceTime reference time based on which relative dates (e.g. "tomorrow"
-             *      should be interpreted. This should usually be the time when the text was
-             *      originally composed.
+             * Sets the reference time based on which relative dates (e.g.
+             * "tomorrow") should be interpreted.
+             *
+             * @param referenceTime reference time based on which relative dates. This should
+             *                      usually be the time when the text was originally composed.
              *
              * @return this builder
              */
@@ -716,6 +722,8 @@
         }
 
         /**
+         * Adds a TextLink.
+         *
          * @see #addLink(int, int, Map)
          * @param urlSpan An optional URLSpan to delegate to. NOTE: Not parcelled.
          */
diff --git a/core/java/android/view/textclassifier/TextLinksParams.java b/core/java/android/view/textclassifier/TextLinksParams.java
index b7d63bf..f12b0d7 100644
--- a/core/java/android/view/textclassifier/TextLinksParams.java
+++ b/core/java/android/view/textclassifier/TextLinksParams.java
@@ -113,7 +113,7 @@
             return TextLinks.STATUS_UNSUPPORTED_CHARACTER;
         }
 
-        if (!textString.startsWith(textLinks.getText())) {
+        if (!textString.startsWith(textLinks.getText().toString())) {
             return TextLinks.STATUS_DIFFERENT_TEXT;
         }
         if (textLinks.getLinks().isEmpty()) {
diff --git a/core/java/android/webkit/PacProcessor.java b/core/java/android/webkit/PacProcessor.java
new file mode 100644
index 0000000..5ef450f
--- /dev/null
+++ b/core/java/android/webkit/PacProcessor.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 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.webkit;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+
+/**
+ * Class to evaluate PAC scripts.
+ * @hide
+ */
+
+@SystemApi
+public interface PacProcessor {
+
+    /**
+     * Returns the default PacProcessor instance.
+     *
+     * @return the default PacProcessor instance.
+     */
+    @NonNull
+    static PacProcessor getInstance() {
+        return WebViewFactory.getProvider().getPacProcessor();
+    }
+
+    /**
+     * Set PAC script to use.
+     *
+     * @param script PAC script.
+     * @return true if PAC script is successfully set.
+     */
+    boolean setProxyScript(@NonNull String script);
+
+    /**
+     * Gets a list of proxy servers to use.
+     * @param url The URL being accessed.
+     * @return a PAC-style semicolon-separated list of valid proxy servers.
+     *         For example: "PROXY xxx.xxx.xxx.xxx:xx; SOCKS yyy.yyy.yyy:yy".
+     */
+    @Nullable
+    String findProxyForUrl(@NonNull String url);
+}
diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java
index 6a1ed39..f7c3ec0 100644
--- a/core/java/android/webkit/WebViewFactoryProvider.java
+++ b/core/java/android/webkit/WebViewFactoryProvider.java
@@ -175,6 +175,15 @@
     WebViewDatabase getWebViewDatabase(Context context);
 
     /**
+     * Gets the singleton PacProcessor instance.
+     * @return the PacProcessor instance
+     */
+    @NonNull
+    default PacProcessor getPacProcessor() {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    /**
      * Gets the classloader used to load internal WebView implementation classes. This interface
      * should only be used by the WebView Support Library.
      */
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index a0cf534..cdf8c68 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -21,6 +21,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.AppGlobals;
 import android.app.PendingIntent;
 import android.app.PendingIntent.CanceledException;
 import android.app.RemoteAction;
@@ -152,7 +153,7 @@
 
     // Specifies whether to allow starting a cursor drag by dragging anywhere over the text.
     @VisibleForTesting
-    public static boolean FLAG_ENABLE_CURSOR_DRAG = false;
+    public static boolean FLAG_ENABLE_CURSOR_DRAG = true;
     // Specifies whether to use the magnifier when pressing the insertion or selection handles.
     private static final boolean FLAG_USE_MAGNIFIER = true;
 
@@ -219,7 +220,9 @@
 
     private final boolean mHapticTextHandleEnabled;
 
-    private final MagnifierMotionAnimator mMagnifierAnimator;
+    @Nullable
+    private MagnifierMotionAnimator mMagnifierAnimator;
+
     private final Runnable mUpdateMagnifierRunnable = new Runnable() {
         @Override
         public void run() {
@@ -384,6 +387,13 @@
 
     private final SuggestionHelper mSuggestionHelper = new SuggestionHelper();
 
+    // Specifies whether the cursor control feature set is enabled.
+    // This can only be true if the text view is editable.
+    private boolean mCursorControlEnabled;
+
+    // Specifies whether the new magnifier (with fish-eye effect) is enabled.
+    private final boolean mNewMagnifierEnabled;
+
     Editor(TextView textView) {
         mTextView = textView;
         // Synchronize the filter list, which places the undo input filter at the end.
@@ -392,13 +402,89 @@
         mHapticTextHandleEnabled = mTextView.getContext().getResources().getBoolean(
                 com.android.internal.R.bool.config_enableHapticTextHandle);
 
-        if (FLAG_USE_MAGNIFIER) {
-            final Magnifier magnifier =
-                    Magnifier.createBuilderWithOldMagnifierDefaults(mTextView).build();
-            mMagnifierAnimator = new MagnifierMotionAnimator(magnifier);
+        mCursorControlEnabled = AppGlobals.getIntCoreSetting(
+                WidgetFlags.KEY_ENABLE_CURSOR_CONTROL , 0) != 0;
+        mNewMagnifierEnabled = AppGlobals.getIntCoreSetting(
+                WidgetFlags.KEY_ENABLE_NEW_MAGNIFIER, 0) != 0;
+        if (TextView.DEBUG_CURSOR) {
+            logCursor("Editor", "Cursor control is %s.",
+                    mCursorControlEnabled ? "enabled" : "disabled");
+            logCursor("Editor", "New magnifier is %s.",
+                    mNewMagnifierEnabled ? "enabled" : "disabled");
         }
     }
 
+    @VisibleForTesting
+    public void setCursorControlEnabled(boolean enabled) {
+        mCursorControlEnabled = enabled;
+    }
+
+    @VisibleForTesting
+    public boolean getCursorControlEnabled() {
+        return mCursorControlEnabled;
+    }
+
+    // Lazy creates the magnifier animator.
+    private MagnifierMotionAnimator getMagnifierAnimator() {
+        if (FLAG_USE_MAGNIFIER && mMagnifierAnimator == null) {
+            // Lazy creates the magnifier instance because it requires the text height which cannot
+            // be measured at the time of Editor instance being created.
+            final Magnifier.Builder builder = mNewMagnifierEnabled
+                    ? createBuilderWithInlineMagnifierDefaults()
+                    : Magnifier.createBuilderWithOldMagnifierDefaults(mTextView);
+            mMagnifierAnimator = new MagnifierMotionAnimator(builder.build());
+        }
+        return mMagnifierAnimator;
+    }
+
+    private Magnifier.Builder createBuilderWithInlineMagnifierDefaults() {
+        final Magnifier.Builder params = new Magnifier.Builder(mTextView);
+
+        // TODO: supports changing the height/width dynamically because the text height can be
+        // dynamically changed.
+        float zoom = AppGlobals.getFloatCoreSetting(
+                WidgetFlags.KEY_MAGNIFIER_ZOOM_FACTOR, 1.5f);
+        float aspectRatio = AppGlobals.getFloatCoreSetting(
+                WidgetFlags.KEY_MAGNIFIER_ASPECT_RATIO, 5.5f);
+        // Avoid invalid/unsupported values.
+        if (zoom < 1.2f || zoom > 1.8f) {
+            zoom = 1.5f;
+        }
+        if (aspectRatio < 3 || aspectRatio > 8) {
+            aspectRatio = 5.5f;
+        }
+
+        final Paint.FontMetrics fontMetrics = mTextView.getPaint().getFontMetrics();
+        final float sourceHeight = fontMetrics.descent - fontMetrics.ascent;
+        // Slightly increase the height to avoid tooLargeTextForMagnifier() returns true.
+        int height = (int)(sourceHeight * zoom) + 2;
+        int width = (int)(aspectRatio * height);
+
+        params.setFishEyeStyle()
+                .setSize(width, height)
+                .setSourceSize(width, Math.round(sourceHeight))
+                .setElevation(0)
+                .setInitialZoom(zoom)
+                .setClippingEnabled(false);
+
+        final Context context = mTextView.getContext();
+        final TypedArray a = context.obtainStyledAttributes(
+                null, com.android.internal.R.styleable.Magnifier,
+                com.android.internal.R.attr.magnifierStyle, 0);
+        params.setDefaultSourceToMagnifierOffset(
+                a.getDimensionPixelSize(
+                        com.android.internal.R.styleable.Magnifier_magnifierHorizontalOffset, 0),
+                a.getDimensionPixelSize(
+                        com.android.internal.R.styleable.Magnifier_magnifierVerticalOffset, 0));
+        a.recycle();
+
+        return params.setSourceBounds(
+                Magnifier.SOURCE_BOUND_MAX_VISIBLE,
+                Magnifier.SOURCE_BOUND_MAX_IN_SURFACE,
+                Magnifier.SOURCE_BOUND_MAX_VISIBLE,
+                Magnifier.SOURCE_BOUND_MAX_IN_SURFACE);
+    }
+
     ParcelableParcel saveInstanceState() {
         ParcelableParcel state = new ParcelableParcel(getClass().getClassLoader());
         Parcel parcel = state.getParcel();
@@ -1192,7 +1278,7 @@
         }
         // Long press in empty space moves cursor and starts the insertion action mode.
         if (!handled && !isPositionOnText(mTouchState.getLastDownX(), mTouchState.getLastDownY())
-                && mInsertionControllerEnabled) {
+                && !mTouchState.isOnHandle() && mInsertionControllerEnabled) {
             final int offset = mTextView.getOffsetForPosition(mTouchState.getLastDownX(),
                     mTouchState.getLastDownY());
             Selection.setSelection((Spannable) mTextView.getText(), offset);
@@ -4605,11 +4691,11 @@
             }
         };
 
-        private int getPreferredWidth() {
+        protected final int getPreferredWidth() {
             return Math.max(mDrawable.getIntrinsicWidth(), mMinSize);
         }
 
-        private int getPreferredHeight() {
+        protected final int getPreferredHeight() {
             return Math.max(mDrawable.getIntrinsicHeight(), mMinSize);
         }
 
@@ -5000,7 +5086,7 @@
         }
 
         protected final void updateMagnifier(@NonNull final MotionEvent event) {
-            if (mMagnifierAnimator == null) {
+            if (getMagnifierAnimator() == null) {
                 return;
             }
 
@@ -5014,6 +5100,16 @@
                 mTextView.invalidateCursorPath();
                 suspendBlink();
 
+                if (mNewMagnifierEnabled) {
+                    // Calculates the line bounds as the content source bounds to the magnifier.
+                    Layout layout = mTextView.getLayout();
+                    int line = layout.getLineForOffset(getCurrentCursorOffset());
+                    int lineLeft = (int) layout.getLineLeft(line);
+                    lineLeft += mTextView.getTotalPaddingLeft() - mTextView.getScrollX();
+                    int lineRight = (int) layout.getLineRight(line);
+                    lineRight -= mTextView.getTotalPaddingRight() + mTextView.getScrollX();
+                    mMagnifierAnimator.mMagnifier.setSourceHorizontalBounds(lineLeft, lineRight);
+                }
                 mMagnifierAnimator.show(showPosInView.x, showPosInView.y);
                 updateHandlesVisibility();
             } else {
@@ -5123,8 +5219,60 @@
         private float mLastDownRawX, mLastDownRawY;
         private Runnable mHider;
 
-        public InsertionHandleView(Drawable drawable) {
+        // Members for fake-dismiss effect in touch through mode.
+        // It is to make InsertionHandleView can receive the MOVE/UP events after calling dismiss(),
+        // which could happen in case of long-press (making selection will dismiss the insertion
+        // handle).
+
+        // Whether the finger is down and hasn't been up yet.
+        private boolean mIsTouchDown = false;
+        // Whether the popup window is in the invisible state and will be dismissed when finger up.
+        private boolean mPendingDismissOnUp = false;
+        // The alpha value of the drawable.
+        private final int mDrawableOpacity;
+
+        // Members for toggling the insertion menu in touch through mode.
+
+        // The coordinate for the touch down event, which is used for transforming the coordinates
+        // of the events to the text view.
+        private float mTouchDownX;
+        private float mTouchDownY;
+        // The cursor offset when touch down. This is to detect whether the cursor is moved when
+        // finger move/up.
+        private int mOffsetDown;
+        // Whether the cursor offset has been changed on the move/up events.
+        private boolean mOffsetChanged;
+        // Whether it is in insertion action mode when finger down.
+        private boolean mIsInActionMode;
+        // The timestamp for the last up event, which is used for double tap detection.
+        private long mLastUpTime;
+        // The text height of the font of the text view, which is used to calculate the Y coordinate
+        // of the touch through events.
+        private float mTextHeight;
+
+        // The delta height applied to the insertion handle view.
+        private final int mDeltaHeight;
+
+        InsertionHandleView(Drawable drawable) {
             super(drawable, drawable, com.android.internal.R.id.insertion_handle);
+
+            int deltaHeight = 0;
+            int opacity = 255;
+            if (mCursorControlEnabled) {
+                deltaHeight = AppGlobals.getIntCoreSetting(
+                        WidgetFlags.KEY_INSERTION_HANDLE_DELTA_HEIGHT, 25);
+                opacity = AppGlobals.getIntCoreSetting(
+                        WidgetFlags.KEY_INSERTION_HANDLE_OPACITY, 50);
+                // Avoid invalid/unsupported values.
+                if (deltaHeight < -25 || deltaHeight > 50) {
+                    deltaHeight = 25;
+                }
+                if (opacity < 10 || opacity > 100) {
+                    opacity = 50;
+                }
+            }
+            mDeltaHeight = deltaHeight;
+            mDrawableOpacity = opacity;
         }
 
         private void hideAfterDelay() {
@@ -5177,7 +5325,23 @@
         }
 
         @Override
+        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+            if (mCursorControlEnabled) {
+                final int height = Math.max(
+                        getPreferredHeight() + mDeltaHeight, mDrawable.getIntrinsicHeight());
+                setMeasuredDimension(getPreferredWidth(), height);
+                return;
+            }
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+
+        @Override
         public boolean onTouchEvent(MotionEvent ev) {
+            if (mCursorControlEnabled && FLAG_ENABLE_CURSOR_DRAG) {
+                // Should only enable touch through when cursor drag is enabled.
+                // Otherwise the insertion handle view cannot be moved.
+                return touchThrough(ev);
+            }
             final boolean result = super.onTouchEvent(ev);
 
             switch (ev.getActionMasked()) {
@@ -5223,6 +5387,115 @@
             return result;
         }
 
+        // Handles the touch events in touch through mode.
+        private boolean touchThrough(MotionEvent ev) {
+            final int actionType = ev.getActionMasked();
+            switch (actionType) {
+                case MotionEvent.ACTION_DOWN:
+                    mIsTouchDown = true;
+                    mOffsetChanged = false;
+                    mOffsetDown = mTextView.getSelectionStart();
+                    mTouchDownX = ev.getX();
+                    mTouchDownY = ev.getY();
+                    mIsInActionMode = mTextActionMode != null;
+                    if (ev.getEventTime() - mLastUpTime < ViewConfiguration.getDoubleTapTimeout()) {
+                        stopTextActionMode();  // Avoid crash when double tap and drag backwards.
+                    }
+                    final Paint.FontMetrics fontMetrics = mTextView.getPaint().getFontMetrics();
+                    mTextHeight = fontMetrics.descent - fontMetrics.ascent;
+                    mTouchState.setIsOnHandle(true);
+                    break;
+                case MotionEvent.ACTION_UP:
+                    mLastUpTime = ev.getEventTime();
+                    break;
+            }
+            // Performs the touch through by forward the events to the text view.
+            boolean ret = mTextView.onTouchEvent(transformEventForTouchThrough(ev));
+
+            if (actionType == MotionEvent.ACTION_UP || actionType == MotionEvent.ACTION_CANCEL) {
+                mIsTouchDown = false;
+                if (mPendingDismissOnUp) {
+                    dismiss();
+                }
+                mTouchState.setIsOnHandle(false);
+            }
+
+            // Checks for cursor offset change.
+            if (!mOffsetChanged) {
+                int start = mTextView.getSelectionStart();
+                int end = mTextView.getSelectionEnd();
+                if (start != end || mOffsetDown != start) {
+                    mOffsetChanged = true;
+                }
+            }
+
+            // Toggling the insertion action mode on finger up.
+            if (!mOffsetChanged && actionType == MotionEvent.ACTION_UP) {
+                if (mIsInActionMode) {
+                    stopTextActionMode();
+                } else {
+                    startInsertionActionMode();
+                }
+            }
+            return ret;
+        }
+
+        private MotionEvent transformEventForTouchThrough(MotionEvent ev) {
+            // Transforms the touch events to screen coordinates.
+            // And also shift up to make the hit point is on the text.
+            // Note:
+            //  - The revised X should reflect the distance to the horizontal center of touch down.
+            //  - The revised Y should be at the top of the text.
+            Matrix m = new Matrix();
+            m.setTranslate(ev.getRawX() - ev.getX() + (getMeasuredWidth() >> 1) - mTouchDownX,
+                    ev.getRawY() - ev.getY() - mTouchDownY - mTextHeight);
+            ev.transform(m);
+            // Transforms the touch events to text view coordinates.
+            mTextView.toLocalMotionEvent(ev);
+            if (TextView.DEBUG_CURSOR) {
+                logCursor("InsertionHandleView#transformEventForTouchThrough",
+                        "Touch through: %d, (%f, %f)",
+                        ev.getAction(), ev.getX(), ev.getY());
+            }
+            return ev;
+        }
+
+        @Override
+        public boolean isShowing() {
+            if (mPendingDismissOnUp) {
+                return false;
+            }
+            return super.isShowing();
+        }
+
+        @Override
+        public void show() {
+            super.show();
+            mPendingDismissOnUp = false;
+            mDrawable.setAlpha(mDrawableOpacity);
+        }
+
+        @Override
+        public void dismiss() {
+            if (mIsTouchDown) {
+                if (TextView.DEBUG_CURSOR) {
+                    logCursor("InsertionHandleView#dismiss",
+                            "Suppressed the real dismiss, only become invisible");
+                }
+                mPendingDismissOnUp = true;
+                mDrawable.setAlpha(0);
+            } else {
+                super.dismiss();
+                mPendingDismissOnUp = false;
+            }
+        }
+
+        @Override
+        protected void updateDrawable(final boolean updateDrawableWhenDragging) {
+            super.updateDrawable(updateDrawableWhenDragging);
+            mDrawable.setAlpha(mDrawableOpacity);
+        }
+
         @Override
         public int getCurrentCursorOffset() {
             return mTextView.getSelectionStart();
@@ -5737,14 +6010,14 @@
         private boolean mIsDraggingCursor;
 
         public void onTouchEvent(MotionEvent event) {
-            if (getSelectionController().isCursorBeingModified()) {
+            if (hasSelectionController() && getSelectionController().isCursorBeingModified()) {
                 return;
             }
             switch (event.getActionMasked()) {
-                case MotionEvent.ACTION_DOWN:
-                    mIsDraggingCursor = false;
-                    break;
                 case MotionEvent.ACTION_MOVE:
+                    if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+                        break;
+                    }
                     if (mIsDraggingCursor) {
                         performCursorDrag(event);
                     } else if (FLAG_ENABLE_CURSOR_DRAG
@@ -5921,8 +6194,6 @@
         // The offsets of that last touch down event. Remembered to start selection there.
         private int mMinTouchOffset, mMaxTouchOffset;
 
-        private boolean mGestureStayedInTapRegion;
-
         // Where the user first starts the drag motion.
         private int mStartOffset = -1;
 
@@ -6029,9 +6300,8 @@
                                 eventX, eventY);
 
                         // Double tap detection
-                        if (mGestureStayedInTapRegion
-                                && mTouchState.isMultiTapInSameArea()
-                                && (isMouse || isPositionOnText(eventX, eventY))) {
+                        if (mTouchState.isMultiTapInSameArea() && (isMouse
+                                || mTouchState.isOnHandle() || isPositionOnText(eventX, eventY))) {
                             if (TextView.DEBUG_CURSOR) {
                                 logCursor("SelectionModifierCursorController: onTouchEvent",
                                         "ACTION_DOWN: select and start drag");
@@ -6043,7 +6313,6 @@
                             }
                             mDiscardNextActionUp = true;
                         }
-                        mGestureStayedInTapRegion = true;
                         mHaventMovedEnoughToStartDrag = true;
                     }
                     break;
@@ -6059,25 +6328,8 @@
                     break;
 
                 case MotionEvent.ACTION_MOVE:
-                    final ViewConfiguration viewConfig = ViewConfiguration.get(
-                            mTextView.getContext());
-
-                    if (mGestureStayedInTapRegion || mHaventMovedEnoughToStartDrag) {
-                        final float deltaX = eventX - mTouchState.getLastDownX();
-                        final float deltaY = eventY - mTouchState.getLastDownY();
-                        final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
-
-                        if (mGestureStayedInTapRegion) {
-                            int doubleTapTouchSlop = viewConfig.getScaledDoubleTapTouchSlop();
-                            mGestureStayedInTapRegion =
-                                    distanceSquared <= doubleTapTouchSlop * doubleTapTouchSlop;
-                        }
-                        if (mHaventMovedEnoughToStartDrag) {
-                            // We don't start dragging until the user has moved enough.
-                            int touchSlop = viewConfig.getScaledTouchSlop();
-                            mHaventMovedEnoughToStartDrag =
-                                    distanceSquared <= touchSlop * touchSlop;
-                        }
+                    if (mHaventMovedEnoughToStartDrag) {
+                        mHaventMovedEnoughToStartDrag = !mTouchState.isMovedEnoughForDrag();
                     }
 
                     if (isMouse && !isDragAcceleratorActive()) {
diff --git a/core/java/android/widget/EditorTouchState.java b/core/java/android/widget/EditorTouchState.java
index d53099d..ff3ac073 100644
--- a/core/java/android/widget/EditorTouchState.java
+++ b/core/java/android/widget/EditorTouchState.java
@@ -31,15 +31,18 @@
 import java.lang.annotation.RetentionPolicy;
 
 /**
- * Helper class used by {@link Editor} to track state for touch events.
+ * Helper class used by {@link Editor} to track state for touch events. Ideally the logic here
+ * should be replaced with {@link android.view.GestureDetector}.
  *
  * @hide
  */
 @VisibleForTesting(visibility = PACKAGE)
 public class EditorTouchState {
     private float mLastDownX, mLastDownY;
+    private long mLastDownMillis;
     private float mLastUpX, mLastUpY;
     private long mLastUpMillis;
+    private boolean mIsOnHandle;
 
     @IntDef({MultiTapStatus.NONE, MultiTapStatus.FIRST_TAP, MultiTapStatus.DOUBLE_TAP,
             MultiTapStatus.TRIPLE_CLICK})
@@ -96,7 +99,15 @@
     }
 
     public boolean isDragCloseToVertical() {
-        return mIsDragCloseToVertical;
+        return mIsDragCloseToVertical && !mIsOnHandle;
+    }
+
+    public void setIsOnHandle(boolean onHandle) {
+        mIsOnHandle = onHandle;
+    }
+
+    public boolean isOnHandle() {
+        return mIsOnHandle;
     }
 
     /**
@@ -106,9 +117,18 @@
         final int action = event.getActionMasked();
         if (action == MotionEvent.ACTION_DOWN) {
             final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE);
+
+            // We check both the time between the last up and current down event, as well as the
+            // time between the first down and up events. The latter check is necessary to handle
+            // the case when the user taps, drags/holds for some time, and then lifts up and
+            // quickly taps in the same area. This scenario should not be treated as a double-tap.
+            // This follows the behavior in GestureDetector.
             final long millisSinceLastUp = event.getEventTime() - mLastUpMillis;
+            final long millisBetweenLastDownAndLastUp = mLastUpMillis - mLastDownMillis;
+
             // Detect double tap and triple click.
             if (millisSinceLastUp <= ViewConfiguration.getDoubleTapTimeout()
+                    && millisBetweenLastDownAndLastUp <= ViewConfiguration.getDoubleTapTimeout()
                     && (mMultiTapStatus == MultiTapStatus.FIRST_TAP
                     || (mMultiTapStatus == MultiTapStatus.DOUBLE_TAP && isMouse))) {
                 if (mMultiTapStatus == MultiTapStatus.FIRST_TAP) {
@@ -133,6 +153,7 @@
             }
             mLastDownX = event.getX();
             mLastDownY = event.getY();
+            mLastDownMillis = event.getEventTime();
             mMovedEnoughForDrag = false;
             mIsDragCloseToVertical = false;
         } else if (action == MotionEvent.ACTION_UP) {
@@ -161,6 +182,13 @@
                     mIsDragCloseToVertical = (4 * deltaXSquared) <= distanceSquared;
                 }
             }
+        } else if (action == MotionEvent.ACTION_CANCEL) {
+            mLastDownMillis = 0;
+            mLastUpMillis = 0;
+            mMultiTapStatus = MultiTapStatus.NONE;
+            mMultiTapInSameArea = false;
+            mMovedEnoughForDrag = false;
+            mIsDragCloseToVertical = false;
         }
     }
 
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index 2924dd9..57b63a7 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -140,6 +140,18 @@
     // The lock used to synchronize the UI and render threads when a #dismiss is performed.
     private final Object mDestroyLock = new Object();
 
+    // Members for new styled magnifier (Eloquent style).
+
+    // Whether the magnifier is in new style.
+    private boolean mIsFishEyeStyle;
+    // The width of the cut region on the left edge of the pixel copy source rect.
+    private int mLeftCutWidth = 0;
+    // The width of the cut region on the right edge of the pixel copy source rect.
+    private int mRightCutWidth = 0;
+    // The horizontal bounds of the content source in pixels, relative to the view.
+    private int mLeftBound = Integer.MIN_VALUE;
+    private int mRightBound = Integer.MAX_VALUE;
+
     /**
      * Initializes a magnifier.
      *
@@ -198,8 +210,14 @@
         mWindowWidth = params.mWidth;
         mWindowHeight = params.mHeight;
         mZoom = params.mZoom;
-        mSourceWidth = Math.round(mWindowWidth / mZoom);
-        mSourceHeight = Math.round(mWindowHeight / mZoom);
+        mIsFishEyeStyle = params.mIsFishEyeStyle;
+        if (params.mSourceWidth > 0 && params.mSourceHeight > 0) {
+            mSourceWidth = params.mSourceWidth;
+            mSourceHeight = params.mSourceHeight;
+        } else {
+            mSourceWidth = Math.round(mWindowWidth / mZoom);
+            mSourceHeight = Math.round(mWindowHeight / mZoom);
+        }
         mWindowElevation = params.mElevation;
         mWindowCornerRadius = params.mCornerRadius;
         mOverlay = params.mOverlay;
@@ -221,6 +239,18 @@
     }
 
     /**
+     * Sets the horizontal bounds of the source when showing the magnifier.
+     * This is used for new style magnifier. e.g. limit the source bounds by the text line bounds.
+     *
+     * @param left the left of the bounds, relative to the view.
+     * @param right the right of the bounds, relative to the view.
+     */
+    void setSourceHorizontalBounds(int left, int right) {
+        mLeftBound = left;
+        mRightBound = right;
+    }
+
+    /**
      * Shows the magnifier on the screen. The method takes the coordinates of the center
      * of the content source going to be magnified and copied to the magnifier. The coordinates
      * are relative to the top left corner of the magnified view. The magnifier will be
@@ -265,20 +295,37 @@
 
         obtainSurfaces();
         obtainContentCoordinates(sourceCenterX, sourceCenterY);
+
+        int startX = mClampedCenterZoomCoords.x - mSourceWidth / 2;
+        final int startY = mClampedCenterZoomCoords.y - mSourceHeight / 2;
+
+        if (mIsFishEyeStyle) {
+            // The magnifier center is the same as source center in new style.
+            magnifierCenterX = mClampedCenterZoomCoords.x - mViewCoordinatesInSurface[0];
+            magnifierCenterY = mClampedCenterZoomCoords.y - mViewCoordinatesInSurface[1];
+            // Gets the startX for new style, which should be bounded by the horizontal bounds.
+            // Also calculates the left/right cut width for pixel copy.
+            final int left = startX;
+            final int right = startX + mSourceWidth;
+            final int leftBound = mViewCoordinatesInSurface[0] + Math.max(0, mLeftBound);
+            final int rightBound =
+                    mViewCoordinatesInSurface[0] + Math.min(mView.getWidth(), mRightBound);
+            startX = Math.max(left, leftBound);
+            mLeftCutWidth = Math.max(0, leftBound - left);
+            mRightCutWidth = Math.max(0, right - rightBound);
+        }
         obtainWindowCoordinates(magnifierCenterX, magnifierCenterY);
 
-        final int startX = mClampedCenterZoomCoords.x - mSourceWidth / 2;
-        final int startY = mClampedCenterZoomCoords.y - mSourceHeight / 2;
         if (sourceCenterX != mPrevShowSourceCoords.x || sourceCenterY != mPrevShowSourceCoords.y
                 || mDirtyState) {
             if (mWindow == null) {
                 synchronized (mLock) {
                     mWindow = new InternalPopupWindow(mView.getContext(), mView.getDisplay(),
-                            mParentSurface.mSurfaceControl, mWindowWidth, mWindowHeight,
+                            mParentSurface.mSurfaceControl, mWindowWidth, mWindowHeight, mZoom,
                             mWindowElevation, mWindowCornerRadius,
                             mOverlay != null ? mOverlay : new ColorDrawable(Color.TRANSPARENT),
                             Handler.getMain() /* draw the magnifier on the UI thread */, mLock,
-                            mCallback);
+                            mCallback, mIsFishEyeStyle);
                 }
             }
             performPixelCopy(startX, startY, true /* update window position */);
@@ -387,7 +434,7 @@
     public void setZoom(@FloatRange(from = 0f) float zoom) {
         Preconditions.checkArgumentPositive(zoom, "Zoom should be positive");
         mZoom = zoom;
-        mSourceWidth = Math.round(mWindowWidth / mZoom);
+        mSourceWidth = mIsFishEyeStyle ? mWindowWidth : Math.round(mWindowWidth / mZoom);
         mSourceHeight = Math.round(mWindowHeight / mZoom);
         mDirtyState = true;
     }
@@ -634,8 +681,10 @@
         resolvedBottom = Math.max(resolvedBottom, resolvedTop + mSourceHeight);
 
         // Finally compute the coordinates of the source center.
-        mClampedCenterZoomCoords.x = Math.max(resolvedLeft + mSourceWidth / 2, Math.min(
-                zoomCenterX, resolvedRight - mSourceWidth / 2));
+        mClampedCenterZoomCoords.x = mIsFishEyeStyle
+                ? Math.max(resolvedLeft, Math.min(zoomCenterX, resolvedRight))
+                : Math.max(resolvedLeft + mSourceWidth / 2, Math.min(
+                        zoomCenterX, resolvedRight - mSourceWidth / 2));
         mClampedCenterZoomCoords.y = Math.max(resolvedTop + mSourceHeight / 2, Math.min(
                 zoomCenterY, resolvedBottom - mSourceHeight / 2));
     }
@@ -678,11 +727,22 @@
         // Perform the pixel copy.
         mPixelCopyRequestRect.set(startXInSurface,
                 startYInSurface,
-                startXInSurface + mSourceWidth,
+                startXInSurface + mSourceWidth - mLeftCutWidth - mRightCutWidth,
                 startYInSurface + mSourceHeight);
+        mPrevStartCoordsInSurface.x = startXInSurface;
+        mPrevStartCoordsInSurface.y = startYInSurface;
+        mDirtyState = false;
+
         final InternalPopupWindow currentWindowInstance = mWindow;
+        if (mPixelCopyRequestRect.width() == 0) {
+            // If the copy rect is empty, updates an empty bitmap to the window.
+            mWindow.updateContent(
+                    Bitmap.createBitmap(mSourceWidth, mSourceHeight, Bitmap.Config.ALPHA_8));
+            return;
+        }
         final Bitmap bitmap =
-                Bitmap.createBitmap(mSourceWidth, mSourceHeight, Bitmap.Config.ARGB_8888);
+                Bitmap.createBitmap(mSourceWidth - mLeftCutWidth - mRightCutWidth,
+                        mSourceHeight, Bitmap.Config.ARGB_8888);
         PixelCopy.request(mContentCopySurface.mSurface, mPixelCopyRequestRect, bitmap,
                 result -> {
                     if (result != PixelCopy.SUCCESS) {
@@ -696,15 +756,25 @@
                         }
                         if (updateWindowPosition) {
                             // TODO: pull the position update outside #performPixelCopy
-                            mWindow.setContentPositionForNextDraw(windowCoords.x, windowCoords.y);
+                            mWindow.setContentPositionForNextDraw(windowCoords.x,
+                                    windowCoords.y);
                         }
-                        mWindow.updateContent(bitmap);
+                        if (bitmap.getWidth() < mSourceWidth) {
+                            // When bitmap width has been cut, re-fills it with full width bitmap.
+                            // This only happens in new styled magnifier.
+                            final Bitmap newBitmap = Bitmap.createBitmap(
+                                    mSourceWidth, bitmap.getHeight(), bitmap.getConfig());
+                            final Canvas can = new Canvas(newBitmap);
+                            final Rect dstRect = new Rect(mLeftCutWidth, 0,
+                                    mSourceWidth - mRightCutWidth, bitmap.getHeight());
+                            can.drawBitmap(bitmap, null, dstRect, null);
+                            mWindow.updateContent(newBitmap);
+                        } else {
+                            mWindow.updateContent(bitmap);
+                        }
                     }
                 },
                 sPixelCopyHandlerThread.getThreadHandler());
-        mPrevStartCoordsInSurface.x = startXInSurface;
-        mPrevStartCoordsInSurface.y = startYInSurface;
-        mDirtyState = false;
     }
 
     private void onPixelCopyFailed() {
@@ -790,9 +860,6 @@
         // The size of the content of the magnifier.
         private final int mContentWidth;
         private final int mContentHeight;
-        // The size of the allocated surface.
-        private final int mSurfaceWidth;
-        private final int mSurfaceHeight;
         // The insets of the content inside the allocated surface.
         private final int mOffsetX;
         private final int mOffsetY;
@@ -815,9 +882,6 @@
         private final Handler mHandler;
         // The callback to be run after the next draw.
         private Callback mCallback;
-        // The position of the magnifier content when the last draw was requested.
-        private int mLastDrawContentPositionX;
-        private int mLastDrawContentPositionY;
 
         // Members below describe the state of the magnifier. Reads/writes to them
         // have to be synchronized between the UI thread and the thread that handles
@@ -838,10 +902,19 @@
         // The current content of the magnifier. It is mBitmap + mOverlay, only used for testing.
         private Bitmap mCurrentContent;
 
+        private final float mZoom;
+        // Whether is in the new magnifier style.
+        private boolean mIsFishEyeStyle;
+        // The mesh matrix for the fish-eye effect.
+        private float[] mMesh;
+        private int mMeshWidth;
+        private int mMeshHeight;
+
         InternalPopupWindow(final Context context, final Display display,
                 final SurfaceControl parentSurfaceControl, final int width, final int height,
-                final float elevation, final float cornerRadius, final Drawable overlay,
-                final Handler handler, final Object lock, final Callback callback) {
+                final float zoom, final float elevation, final float cornerRadius,
+                final Drawable overlay, final Handler handler, final Object lock,
+                final Callback callback, final boolean isFishEyeStyle) {
             mDisplay = display;
             mOverlay = overlay;
             mLock = lock;
@@ -849,15 +922,16 @@
 
             mContentWidth = width;
             mContentHeight = height;
+            mZoom = zoom;
             mOffsetX = (int) (1.05f * elevation);
             mOffsetY = (int) (1.05f * elevation);
             // Setup the surface we will use for drawing the content and shadow.
-            mSurfaceWidth = mContentWidth + 2 * mOffsetX;
-            mSurfaceHeight = mContentHeight + 2 * mOffsetY;
+            final int surfaceWidth = mContentWidth + 2 * mOffsetX;
+            final int surfaceHeight = mContentHeight + 2 * mOffsetY;
             mSurfaceSession = new SurfaceSession();
             mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
                     .setFormat(PixelFormat.TRANSLUCENT)
-                    .setBufferSize(mSurfaceWidth, mSurfaceHeight)
+                    .setBufferSize(surfaceWidth, surfaceHeight)
                     .setName("magnifier surface")
                     .setFlags(SurfaceControl.HIDDEN)
                     .setParent(parentSurfaceControl)
@@ -904,6 +978,38 @@
             mHandler = handler;
             mMagnifierUpdater = this::doDraw;
             mFrameDrawScheduled = false;
+            mIsFishEyeStyle = isFishEyeStyle;
+
+            if (mIsFishEyeStyle) {
+                createMeshMatrixForFishEyeEffect();
+            }
+        }
+
+        private void createMeshMatrixForFishEyeEffect() {
+            mMeshWidth = mZoom < 1.5f ? 5 : 4;
+            mMeshHeight = 6;
+            final float w = mContentWidth;
+            final float h = mContentHeight;
+            final float dx = (w - mZoom * w * (mMeshWidth - 2) / mMeshWidth) / 2;
+            mMesh = new float[2 * (mMeshWidth + 1) * (mMeshHeight + 1)];
+            for (int i = 0; i < 2 * (mMeshWidth + 1) * (mMeshHeight + 1); i += 2) {
+                // Calculates X value.
+                final int colIndex = i % (2 * (mMeshWidth + 1)) / 2;
+                if (colIndex == 0) {
+                    mMesh[i] = 0;
+                } else if (colIndex == mMeshWidth) {
+                    mMesh[i] = w;
+                } else {
+                    mMesh[i] = (colIndex - 1) * (w - 2 * dx) / (mMeshWidth - 2) + dx;
+                }
+                // Calculates Y value.
+                final int rowIndex = i / 2 / (mMeshWidth + 1);
+                final float y0 = colIndex == 0 || colIndex == mMeshWidth
+                        ? (h - h / mZoom) / 2 : 0;
+                final float dy = colIndex == 0 || colIndex == mMeshWidth
+                        ? h / mZoom / mMeshHeight : h / mMeshHeight;
+                mMesh[i + 1] = y0 + rowIndex * dy;
+            }
         }
 
         private RenderNode createRenderNodeForBitmap(final String name,
@@ -1060,15 +1166,19 @@
                 final RecordingCanvas canvas =
                         mBitmapRenderNode.beginRecording(mContentWidth, mContentHeight);
                 try {
-                    final Rect srcRect = new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
-                    final Rect dstRect = new Rect(0, 0, mContentWidth, mContentHeight);
-                    final Paint paint = new Paint();
-                    paint.setFilterBitmap(true);
-                    canvas.drawBitmap(mBitmap, srcRect, dstRect, paint);
+                    if (mIsFishEyeStyle) {
+                        canvas.drawBitmapMesh(
+                                mBitmap, mMeshWidth, mMeshHeight, mMesh, 0, null, 0, null);
+                    } else {
+                        final Rect srcRect = new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
+                        final Rect dstRect = new Rect(0, 0, mContentWidth, mContentHeight);
+                        final Paint paint = new Paint();
+                        paint.setFilterBitmap(true);
+                        canvas.drawBitmap(mBitmap, srcRect, dstRect, paint);
+                    }
                 } finally {
                     mBitmapRenderNode.endRecording();
                 }
-
                 if (mPendingWindowPositionUpdate || mFirstDraw) {
                     // If the window has to be shown or moved, defer this until the next draw.
                     final boolean firstDraw = mFirstDraw;
@@ -1094,13 +1204,14 @@
                         }
                         mTransaction.apply();
                     };
-                    mRenderer.setLightCenter(mDisplay, pendingX, pendingY);
+                    if (!mIsFishEyeStyle) {
+                        // The new style magnifier doesn't need the light/shadow.
+                        mRenderer.setLightCenter(mDisplay, pendingX, pendingY);
+                    }
                 } else {
                     callback = null;
                 }
 
-                mLastDrawContentPositionX = mWindowPositionX + mOffsetX;
-                mLastDrawContentPositionY = mWindowPositionY + mOffsetY;
                 mFrameDrawScheduled = false;
             }
 
@@ -1149,6 +1260,9 @@
         private @SourceBound int mTopContentBound;
         private @SourceBound int mRightContentBound;
         private @SourceBound int  mBottomContentBound;
+        private boolean mIsFishEyeStyle;
+        private int mSourceWidth;
+        private int mSourceHeight;
 
         /**
          * Construct a new builder for {@link Magnifier} objects.
@@ -1177,6 +1291,7 @@
             mTopContentBound = SOURCE_BOUND_MAX_VISIBLE;
             mRightContentBound = SOURCE_BOUND_MAX_VISIBLE;
             mBottomContentBound = SOURCE_BOUND_MAX_VISIBLE;
+            mIsFishEyeStyle = false;
         }
 
         /**
@@ -1339,6 +1454,25 @@
         }
 
         /**
+         * Sets the source width/height.
+         */
+        @NonNull
+        Builder setSourceSize(int width, int height) {
+            mSourceWidth = width;
+            mSourceHeight = height;
+            return this;
+        }
+
+        /**
+         * Sets the magnifier as the new fish-eye style.
+         */
+        @NonNull
+        Builder setFishEyeStyle() {
+            mIsFishEyeStyle = true;
+            return this;
+        }
+
+        /**
          * Builds a {@link Magnifier} instance based on the configuration of this {@link Builder}.
          */
         public @NonNull Magnifier build() {
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 733a775..970d70c 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -52,7 +52,6 @@
 import android.view.ViewDebug;
 import android.view.ViewHierarchyEncoder;
 import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
@@ -307,6 +306,9 @@
         setMax(a.getInt(R.styleable.ProgressBar_max, mMax));
 
         setProgress(a.getInt(R.styleable.ProgressBar_progress, mProgress));
+        // onProgressRefresh() is only called when the progress changes. So we should set
+        // stateDescription during initialization here.
+        super.setStateDescription(formatStateDescription(mProgress));
 
         setSecondaryProgress(a.getInt(
                 R.styleable.ProgressBar_secondaryProgress, mSecondaryProgress));
@@ -1599,8 +1601,7 @@
     }
 
     void onProgressRefresh(float scale, boolean fromUser, int progress) {
-        if (AccessibilityManager.getInstance(mContext).isEnabled()
-                && mCustomStateDescription == null) {
+        if (mCustomStateDescription == null) {
             super.setStateDescription(formatStateDescription(mProgress));
         }
     }
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index d86766e..01a0e9b 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -565,7 +565,8 @@
     }
 
     private static void visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor) {
-        if (icon != null && icon.getType() == Icon.TYPE_URI) {
+        if (icon != null && (icon.getType() == Icon.TYPE_URI
+                || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) {
             visitor.accept(icon.getUri());
         }
     }
diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java
index 5731e50..8565493 100644
--- a/core/java/android/widget/TextClock.java
+++ b/core/java/android/widget/TextClock.java
@@ -422,7 +422,7 @@
     /**
      * Update the displayed time if necessary and invalidate the view.
      */
-    public void refresh() {
+    public void refreshTime() {
         onTimeChanged();
         invalidate();
     }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 32d3fef..8ce6ee5 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -8668,6 +8668,7 @@
                 outAttrs.initialSelStart = getSelectionStart();
                 outAttrs.initialSelEnd = getSelectionEnd();
                 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
+                outAttrs.setInitialSurroundingText(mText);
                 return ic;
             }
         }
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index 9bdb4c1..42d7892 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -16,6 +16,8 @@
 
 package android.widget;
 
+import static com.android.internal.util.Preconditions.checkNotNull;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -27,6 +29,7 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.PixelFormat;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
@@ -42,8 +45,12 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * A toast is a view containing a quick little message for the user.  The toast class
@@ -60,6 +67,10 @@
  * <p>
  * The easiest way to use this class is to call one of the static methods that constructs
  * everything you need and returns a new Toast object.
+ * <p>
+ * Note that
+ * <a href="{@docRoot}reference/com/google/android/material/snackbar/Snackbar">Snackbars</a> are
+ * preferred for brief messages while the app is in the foreground.
  *
  * <div class="special reference">
  * <h3>Developer Guides</h3>
@@ -94,7 +105,8 @@
      */
     public static final int LENGTH_LONG = 1;
 
-    final Context mContext;
+    private final Binder mToken;
+    private final Context mContext;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     final TN mTN;
     @UnsupportedAppUsage
@@ -120,7 +132,8 @@
      */
     public Toast(@NonNull Context context, @Nullable Looper looper) {
         mContext = context;
-        mTN = new TN(context.getPackageName(), looper);
+        mToken = new Binder();
+        mTN = new TN(context.getPackageName(), mToken, looper);
         mTN.mY = context.getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.toast_y_offset);
         mTN.mGravity = context.getResources().getInteger(
@@ -143,9 +156,9 @@
 
         try {
             if (mIsCustomToast) {
-                service.enqueueToast(pkg, tn, mDuration, displayId);
+                service.enqueueToast(pkg, mToken, tn, mDuration, displayId);
             } else {
-                service.enqueueTextToast(pkg, tn, mDuration, displayId);
+                service.enqueueTextToast(pkg, mToken, tn, mDuration, displayId);
             }
         } catch (RemoteException e) {
             // Empty
@@ -163,8 +176,16 @@
 
     /**
      * Set the view to show.
+     *
      * @see #getView
+     * @deprecated Custom toast views are deprecated. Apps can create a standard text toast with the
+     *      {@link #makeText(Context, CharSequence, int)} method, or use a
+     *      <a href="{@docRoot}reference/com/google/android/material/snackbar/Snackbar">Snackbar</a>
+     *      when in the foreground. Starting from Android {@link Build.VERSION_CODES#R}, apps
+     *      targeting API level {@link Build.VERSION_CODES#R} or higher that are in the background
+     *      will not have custom toast views displayed.
      */
+    @Deprecated
     public void setView(View view) {
         mIsCustomToast = true;
         mNextView = view;
@@ -172,7 +193,14 @@
 
     /**
      * Return the view.
+     *
      * @see #setView
+     * @deprecated Custom toast views are deprecated. Apps can create a standard text toast with the
+     *      {@link #makeText(Context, CharSequence, int)} method, or use a
+     *      <a href="{@docRoot}reference/com/google/android/material/snackbar/Snackbar">Snackbar</a>
+     *      when in the foreground. Starting from Android {@link Build.VERSION_CODES#R}, apps
+     *      targeting API level {@link Build.VERSION_CODES#R} or higher that are in the background
+     *      will not have custom toast views displayed.
      */
     public View getView() {
         mIsCustomToast = true;
@@ -262,6 +290,29 @@
     }
 
     /**
+     * Adds a callback to be notified when the toast is shown or hidden.
+     *
+     * Note that if the toast is blocked for some reason you won't get a call back.
+     *
+     * @see #removeCallback(Callback)
+     */
+    public void addCallback(@NonNull Callback callback) {
+        checkNotNull(callback);
+        synchronized (mTN.mCallbacks) {
+            mTN.mCallbacks.add(callback);
+        }
+    }
+
+    /**
+     * Removes a callback previously added with {@link #addCallback(Callback)}.
+     */
+    public void removeCallback(@NonNull Callback callback) {
+        synchronized (mTN.mCallbacks) {
+            mTN.mCallbacks.remove(callback);
+        }
+    }
+
+    /**
      * Gets the LayoutParams for the Toast window.
      * @hide
      */
@@ -387,12 +438,16 @@
 
         WindowManager mWM;
 
-        String mPackageName;
+        final String mPackageName;
+        final Binder mToken;
+
+        @GuardedBy("mCallbacks")
+        private final List<Callback> mCallbacks = new ArrayList<>();
 
         static final long SHORT_DURATION_TIMEOUT = 4000;
         static final long LONG_DURATION_TIMEOUT = 7000;
 
-        TN(String packageName, @Nullable Looper looper) {
+        TN(String packageName, Binder token, @Nullable Looper looper) {
             // XXX This should be changed to use a Dialog, with a Theme.Toast
             // defined that sets up the layout params appropriately.
             final WindowManager.LayoutParams params = mParams;
@@ -401,13 +456,14 @@
             params.format = PixelFormat.TRANSLUCENT;
             params.windowAnimations = com.android.internal.R.style.Animation_Toast;
             params.type = WindowManager.LayoutParams.TYPE_TOAST;
-            params.setFitIgnoreVisibility(true);
+            params.setFitInsetsIgnoringVisibility(true);
             params.setTitle("Toast");
             params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                     | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                     | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
 
             mPackageName = packageName;
+            mToken = token;
 
             if (looper == null) {
                 // Use Looper.myLooper() if looper is not specified.
@@ -439,7 +495,7 @@
                             // handleShow()
                             mNextView = null;
                             try {
-                                getService().cancelToast(mPackageName, TN.this);
+                                getService().cancelToast(mPackageName, mToken);
                             } catch (RemoteException e) {
                             }
                             break;
@@ -449,6 +505,12 @@
             };
         }
 
+        private List<Callback> getCallbacks() {
+            synchronized (mCallbacks) {
+                return new ArrayList<>(mCallbacks);
+            }
+        }
+
         /**
          * schedule handleShow into the right thread
          */
@@ -522,6 +584,9 @@
                 try {
                     mWM.addView(mView, mParams);
                     trySendAccessibilityEvent();
+                    for (Callback callback : getCallbacks()) {
+                        callback.onToastShown();
+                    }
                 } catch (WindowManager.BadTokenException e) {
                     /* ignore */
                 }
@@ -560,12 +625,34 @@
                 // Now that we've removed the view it's safe for the server to release
                 // the resources.
                 try {
-                    getService().finishToken(mPackageName, this);
+                    getService().finishToken(mPackageName, mToken);
                 } catch (RemoteException e) {
                 }
 
+                for (Callback callback : getCallbacks()) {
+                    callback.onToastHidden();
+                }
                 mView = null;
             }
         }
     }
+
+    /**
+     * Callback object to be called when the toast is shown or hidden.
+     *
+     * Callback methods will be called on the looper thread provided on construction.
+     *
+     * @see #addCallback(Callback)
+     */
+    public abstract static class Callback {
+        /**
+         * Called when the toast is displayed on the screen.
+         */
+        public void onToastShown() {}
+
+        /**
+         * Called when the toast is hidden.
+         */
+        public void onToastHidden() {}
+    }
 }
diff --git a/core/java/android/widget/WidgetFlags.java b/core/java/android/widget/WidgetFlags.java
new file mode 100644
index 0000000..1a8e7a7
--- /dev/null
+++ b/core/java/android/widget/WidgetFlags.java
@@ -0,0 +1,100 @@
+/*
+  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.widget;
+
+/**
+ * Keeps the flags related to the Widget namespace in {@link DeviceConfig}.
+ *
+ * @hide
+ */
+public final class WidgetFlags {
+
+    /**
+     * Whether the cursor control feature set is enabled.
+     * TODO: Makes this flag key visible to webview/chrome.
+     */
+    public static final String ENABLE_CURSOR_CONTROL =
+            "CursorControlFeature__enable_cursor_control";
+
+    /**
+     * The key name used in app core settings for enable cursor control.
+     */
+    public static final String KEY_ENABLE_CURSOR_CONTROL = "widget__enable_cursor_control";
+
+    /**
+     * The flag of delta height applies to the insertion handle when cursor control flag is enabled.
+     * The default value is 25.
+     */
+    public static final String INSERTION_HANDLE_DELTA_HEIGHT =
+            "CursorControlFeature__insertion_handle_delta_height";
+
+    /**
+     * The key name used in app core settings for {@link #INSERTION_HANDLE_DELTA_HEIGHT}.
+     */
+    public static final String KEY_INSERTION_HANDLE_DELTA_HEIGHT =
+            "widget__insertion_handle_delta_height";
+
+    /**
+     * The flag of opacity applies to the insertion handle when cursor control flag is enabled.
+     * The opacity value is in the range of {0..100}. The default value is 50.
+     */
+    public static final String INSERTION_HANDLE_OPACITY =
+            "CursorControlFeature__insertion_handle_opacity";
+
+    /**
+     * The key name used in app core settings for {@link #INSERTION_HANDLE_OPACITY}.
+     */
+    public static final String KEY_INSERTION_HANDLE_OPACITY =
+            "widget__insertion_handle_opacity";
+
+    /**
+     * The flag of enabling the new magnifier.
+     */
+    public static final String ENABLE_NEW_MAGNIFIER = "CursorControlFeature__enable_new_magnifier";
+
+    /**
+     * The key name used in app core settings for {@link #ENABLE_NEW_MAGNIFIER}.
+     */
+    public static final String KEY_ENABLE_NEW_MAGNIFIER = "widget__enable_new_magnifier";
+
+    /**
+     * The flag of zoom factor applies to the new magnifier.
+     * The default value is 1.5f.
+     */
+    public static final String MAGNIFIER_ZOOM_FACTOR =
+            "CursorControlFeature__magnifier_zoom_factor";
+
+    /**
+     * The key name used in app core settings for {@link #MAGNIFIER_ZOOM_FACTOR}.
+     */
+    public static final String KEY_MAGNIFIER_ZOOM_FACTOR = "widget__magnifier_zoom_factor";
+
+    /**
+     * The flag of aspect ratio (width/height) applies to the new magnifier.
+     * The default value is 5.5f.
+     */
+    public static final String MAGNIFIER_ASPECT_RATIO =
+            "CursorControlFeature__magnifier_aspect_ratio";
+
+    /**
+     * The key name used in app core settings for {@link #MAGNIFIER_ASPECT_RATIO}.
+     */
+    public static final String KEY_MAGNIFIER_ASPECT_RATIO = "widget__magnifier_aspect_ratio";
+
+    private WidgetFlags() {
+    }
+}
diff --git a/core/java/com/android/ims/internal/uce/common/CapInfo.java b/core/java/com/android/ims/internal/uce/common/CapInfo.java
index c45a3a4..2bb3f1f 100644
--- a/core/java/com/android/ims/internal/uce/common/CapInfo.java
+++ b/core/java/com/android/ims/internal/uce/common/CapInfo.java
@@ -64,6 +64,20 @@
     private boolean mRcsIpVideoCallSupported = false;
     /** RCS IP Video call support .  */
     private boolean mRcsIpVideoOnlyCallSupported = false;
+    /** IP Geo location Push using SMS. */
+    private boolean mGeoSmsSupported = false;
+    /** RCS call composer support. */
+    private boolean mCallComposerSupported = false;
+    /** RCS post-call support. */
+    private boolean mPostCallSupported = false;
+    /** Shared map support. */
+    private boolean mSharedMapSupported = false;
+    /** Shared Sketch supported. */
+    private boolean mSharedSketchSupported = false;
+    /** Chatbot communication support. */
+    private boolean mChatbotSupported = false;
+    /** Chatbot role support. */
+    private boolean mChatbotRoleSupported = false;
     /** List of supported extensions. */
     private String[] mExts = new String[10];
     /** Time used to compute when to query again. */
@@ -386,6 +400,104 @@
         this.mRcsIpVideoOnlyCallSupported = rcsIpVideoOnlyCallSupported;
     }
 
+    /**
+     * Checks whether Geo Push via SMS is supported.
+     */
+    public boolean isGeoSmsSupported() {
+        return mGeoSmsSupported;
+    }
+
+    /**
+     * Sets Geolocation Push via SMS as supported or not supported.
+     */
+    public void setGeoSmsSupported(boolean geoSmsSupported) {
+         this.mGeoSmsSupported = geoSmsSupported;
+    }
+
+    /**
+     * Checks whether RCS call composer is supported.
+     */
+    public boolean isCallComposerSupported() {
+        return mCallComposerSupported;
+    }
+
+    /**
+     * Sets call composer as supported or not supported.
+     */
+    public void setCallComposerSupported(boolean callComposerSupported) {
+        this.mCallComposerSupported = callComposerSupported;
+    }
+
+    /**
+     * Checks whether post call is supported.
+     */
+    public boolean isPostCallSupported(){
+        return mPostCallSupported;
+    }
+
+    /**
+     * Sets post call as supported or not supported.
+     */
+     public void setPostCallSupported(boolean postCallSupported) {
+         this.mPostCallSupported = postCallSupported;
+     }
+
+    /**
+     * Checks whether shared map is supported.
+     */
+    public boolean isSharedMapSupported() {
+        return mSharedMapSupported;
+    }
+
+    /**
+     * Sets shared map as supported or not supported.
+     */
+    public void setSharedMapSupported(boolean sharedMapSupported) {
+        this.mSharedMapSupported = sharedMapSupported;
+    }
+
+    /**
+     * Checks whether shared sketch is supported.
+     */
+    public boolean isSharedSketchSupported() {
+        return mSharedSketchSupported;
+    }
+
+    /**
+     * Sets shared sketch as supported or not supported.
+     */
+    public void setSharedSketchSupported(boolean sharedSketchSupported) {
+        this.mSharedSketchSupported = sharedSketchSupported;
+    }
+
+    /**
+     * Checks whether chatbot communication is supported.
+     */
+    public boolean isChatbotSupported() {
+        return mChatbotSupported;
+    }
+
+    /**
+     * Sets chatbot communication as supported or not supported.
+     */
+    public void setChatbotSupported(boolean chatbotSupported) {
+        this.mChatbotSupported = chatbotSupported;
+    }
+
+    /**
+     * Checks whether chatbot role is supported.
+     */
+    public boolean isChatbotRoleSupported() {
+        return mChatbotRoleSupported;
+    }
+
+    /**
+     * Sets chatbot role as supported or not supported.
+     */
+    public void setChatbotRoleSupported(boolean chatbotRoleSupported) {
+        this.mChatbotRoleSupported = chatbotRoleSupported;
+    }
+
     /** Gets the list of supported extensions. */
     public String[] getExts() {
         return mExts;
@@ -434,6 +546,13 @@
         dest.writeInt(mGeoPushSupported ? 1 : 0);
         dest.writeInt(mSmSupported ? 1 : 0);
         dest.writeInt(mFullSnFGroupChatSupported ? 1 : 0);
+        dest.writeInt(mGeoSmsSupported ? 1 : 0);
+        dest.writeInt(mCallComposerSupported ? 1 : 0);
+        dest.writeInt(mPostCallSupported ? 1 : 0);
+        dest.writeInt(mSharedMapSupported ? 1 : 0);
+        dest.writeInt(mSharedSketchSupported ? 1 : 0);
+        dest.writeInt(mChatbotSupported ? 1 : 0);
+        dest.writeInt(mChatbotRoleSupported ? 1 : 0);
 
         dest.writeInt(mRcsIpVoiceCallSupported ? 1 : 0);
         dest.writeInt(mRcsIpVideoCallSupported ? 1 : 0);
@@ -476,6 +595,13 @@
         mGeoPushSupported = (source.readInt() == 0) ? false : true;
         mSmSupported = (source.readInt() == 0) ? false : true;
         mFullSnFGroupChatSupported = (source.readInt() == 0) ? false : true;
+        mGeoSmsSupported = (source.readInt() == 0) ? false : true;
+        mCallComposerSupported = (source.readInt() == 0) ? false : true;
+        mPostCallSupported = (source.readInt() == 0) ? false : true;
+        mSharedMapSupported = (source.readInt() == 0) ? false : true;
+        mSharedSketchSupported = (source.readInt() == 0) ? false : true;
+        mChatbotSupported = (source.readInt() == 0) ? false : true;
+        mChatbotRoleSupported = (source.readInt() == 0) ? false : true;
 
         mRcsIpVoiceCallSupported = (source.readInt() == 0) ? false : true;
         mRcsIpVideoCallSupported = (source.readInt() == 0) ? false : true;
diff --git a/core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java b/core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java
index a50a22f..fdff86f 100644
--- a/core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java
+++ b/core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java
@@ -47,6 +47,10 @@
     public static final int UCE_PRES_PUBLISH_TRIGGER_MOVE_TO_IWLAN = 8;
     /** Trigger is unknown. */
     public static final int UCE_PRES_PUBLISH_TRIGGER_UNKNOWN = 9;
+    /** Move to 5G NR with VoPS disabled. */
+    public static final int UCE_PRES_PUBLISH_TRIGGER_MOVE_TO_NR5G_VOPS_DISABLED = 10;
+    /** Move to 5G NR with VoPS enabled. */
+    public static final int UCE_PRES_PUBLISH_TRIGGER_MOVE_TO_NR5G_VOPS_ENABLED = 11;
 
 
 
@@ -113,4 +117,4 @@
     public void readFromParcel(Parcel source) {
         mPublishTriggerType = source.readInt();
     }
-}
\ No newline at end of file
+}
diff --git a/core/java/com/android/ims/internal/uce/uceservice/ImsUceManager.java b/core/java/com/android/ims/internal/uce/uceservice/ImsUceManager.java
index 4b0b098..9aee879f 100644
--- a/core/java/com/android/ims/internal/uce/uceservice/ImsUceManager.java
+++ b/core/java/com/android/ims/internal/uce/uceservice/ImsUceManager.java
@@ -18,16 +18,9 @@
 
 import android.content.Context;
 import android.content.Intent;
-
-import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.IBinder;
-import android.os.Message;
-import android.os.ServiceManager;
 import android.os.RemoteException;
-
-import java.util.HashMap;
-import android.util.Log;
+import android.os.ServiceManager;
 
 /**
  * ImsUceManager Declaration
@@ -49,55 +42,25 @@
     private IUceService mUceService = null;
     private UceServiceDeathRecipient mDeathReceipient = new UceServiceDeathRecipient();
     private Context mContext;
-    private int mPhoneId;
-    /**
-     * Stores the UceManager instaces of Clients identified by
-     * phoneId
-     * @hide
-     */
-    private static HashMap<Integer, ImsUceManager> sUceManagerInstances =
-                                                   new HashMap<Integer, ImsUceManager>();
+    private static final Object sLock = new Object();
+    private static ImsUceManager sUceManager;
 
     public static final String ACTION_UCE_SERVICE_UP =
                                        "com.android.ims.internal.uce.UCE_SERVICE_UP";
     public static final String ACTION_UCE_SERVICE_DOWN =
                                         "com.android.ims.internal.uce.UCE_SERVICE_DOWN";
 
-    /** Uce Service status received in IUceListener.setStatus()
-     *  callback
-     *  @hide
-     */
-    public static final int UCE_SERVICE_STATUS_FAILURE = 0;
-    /** indicate UI to call Presence/Options API.   */
-    public static final int UCE_SERVICE_STATUS_ON = 1;
-    /** Indicate UI destroy Presence/Options   */
-    public static final int UCE_SERVICE_STATUS_CLOSED = 2;
-    /** Service up and trying to register for network events  */
-    public static final int UCE_SERVICE_STATUS_READY = 3;
-
-    /**
-     * Part of the ACTION_UCE_SERVICE_UP or _DOWN intents. A long
-     * value; the phone ID corresponding to the IMS service coming up or down.
-     * Internal use only.
-     * @hide
-     */
-    public static final String EXTRA_PHONE_ID = "android:phone_id";
-
 
     /**
      * Gets the instance of UCE Manager
      * @hide
      */
-    public static ImsUceManager getInstance(Context context, int phoneId) {
-        //if (DBG) Log.d (LOG_TAG, "GetInstance Called");
-        synchronized (sUceManagerInstances) {
-            if (sUceManagerInstances.containsKey(phoneId)) {
-                return sUceManagerInstances.get(phoneId);
-            } else {
-                ImsUceManager uceMgr =  new ImsUceManager(context, phoneId);
-                sUceManagerInstances.put(phoneId, uceMgr);
-                return uceMgr;
+    public static ImsUceManager getInstance(Context context) {
+        synchronized (sLock) {
+            if (sUceManager == null && context != null) {
+                sUceManager =  new ImsUceManager(context);
             }
+            return sUceManager;
         }
     }
 
@@ -105,10 +68,9 @@
      * Constructor
      * @hide
      */
-    private ImsUceManager(Context context, int phoneId) {
+    private ImsUceManager(Context context) {
         //if (DBG) Log.d (LOG_TAG, "Constructor");
         mContext = context;
-        mPhoneId = phoneId;
         createUceService(true);
     }
 
@@ -129,7 +91,7 @@
      * Gets the UCE service name
      * @hide
      */
-    private String getUceServiceName(int phoneId) {
+    private String getUceServiceName() {
         return UCE_SERVICE;
     }
 
@@ -143,14 +105,14 @@
     public void createUceService(boolean checkService) {
         //if (DBG) Log.d (LOG_TAG, "CreateUceService Called");
         if (checkService) {
-            IBinder binder = ServiceManager.checkService(getUceServiceName(mPhoneId));
+            IBinder binder = ServiceManager.checkService(getUceServiceName());
 
             if (binder == null) {
                 //if (DBG)Log.d (LOG_TAG, "Unable to find IBinder");
                 return;
             }
         }
-        IBinder b = ServiceManager.getService(getUceServiceName(mPhoneId));
+        IBinder b = ServiceManager.getService(getUceServiceName());
 
         if (b != null) {
             try {
@@ -174,12 +136,10 @@
     private class UceServiceDeathRecipient implements IBinder.DeathRecipient {
         @Override
         public void binderDied() {
-            //if (DBG) Log.d (LOG_TAG, "found IBinder/IUceService Service Died");
             mUceService = null;
 
             if (mContext != null) {
                 Intent intent = new Intent(ACTION_UCE_SERVICE_DOWN);
-                intent.putExtra(EXTRA_PHONE_ID, mPhoneId);
                 mContext.sendBroadcast(new Intent(intent));
             }
         }
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index 3fdedc8..93659a4 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -371,7 +371,9 @@
         // targets during boot. Needs to read settings directly here.
         String shortcutTargets = Settings.Secure.getStringForUser(mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, mUserId);
-        if (TextUtils.isEmpty(shortcutTargets)) {
+        // A11y warning dialog updates settings to empty string, when user disables a11y shortcut.
+        // Only fallback to default a11y service, when setting is never updated.
+        if (shortcutTargets == null) {
             shortcutTargets = mContext.getString(R.string.config_defaultAccessibilityService);
         }
         return !TextUtils.isEmpty(shortcutTargets);
diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
index 08022e9..b2aa043 100644
--- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
@@ -26,7 +26,9 @@
 import com.android.internal.widget.PagerAdapter;
 import com.android.internal.widget.ViewPager;
 
+import java.util.HashSet;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * Skeletal {@link PagerAdapter} implementation of a work or personal profile page for
@@ -34,6 +36,7 @@
  */
 public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
 
+    private static final String TAG = "AbstractMultiProfilePagerAdapter";
     static final int PROFILE_PERSONAL = 0;
     static final int PROFILE_WORK = 1;
     @IntDef({PROFILE_PERSONAL, PROFILE_WORK})
@@ -41,10 +44,17 @@
 
     private final Context mContext;
     private int mCurrentPage;
+    private OnProfileSelectedListener mOnProfileSelectedListener;
+    private Set<Integer> mLoadedPages;
 
     AbstractMultiProfilePagerAdapter(Context context, int currentPage) {
         mContext = Objects.requireNonNull(context);
         mCurrentPage = currentPage;
+        mLoadedPages = new HashSet<>();
+    }
+
+    void setOnProfileSelectedListener(OnProfileSelectedListener listener) {
+        mOnProfileSelectedListener = listener;
     }
 
     Context getContext() {
@@ -57,15 +67,22 @@
      * page and rebuilds the list.
      */
     void setupViewPager(ViewPager viewPager) {
-        viewPager.setCurrentItem(mCurrentPage);
         viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
             @Override
             public void onPageSelected(int position) {
                 mCurrentPage = position;
-                getActiveListAdapter().rebuildList();
+                if (!mLoadedPages.contains(position)) {
+                    getActiveListAdapter().rebuildList();
+                    mLoadedPages.add(position);
+                }
+                if (mOnProfileSelectedListener != null) {
+                    mOnProfileSelectedListener.onProfileSelected(position);
+                }
             }
         });
         viewPager.setAdapter(this);
+        viewPager.setCurrentItem(mCurrentPage);
+        mLoadedPages.add(mCurrentPage);
     }
 
     @Override
@@ -90,7 +107,8 @@
         return mCurrentPage;
     }
 
-    UserHandle getCurrentUserHandle() {
+    @VisibleForTesting
+    public UserHandle getCurrentUserHandle() {
         return getActiveListAdapter().mResolverListController.getUserHandle();
     }
 
@@ -135,7 +153,8 @@
      * <p>This method is meant to be implemented with an implementation-specific return type
      * depending on the adapter type.
      */
-    abstract Object getAdapterForIndex(int pageIndex);
+    @VisibleForTesting
+    public abstract Object getAdapterForIndex(int pageIndex);
 
     @VisibleForTesting
     public abstract ResolverListAdapter getActiveListAdapter();
@@ -152,7 +171,9 @@
 
     abstract Object getCurrentRootAdapter();
 
-    abstract ViewGroup getCurrentAdapterView();
+    abstract ViewGroup getActiveAdapterView();
+
+    abstract @Nullable ViewGroup getInactiveAdapterView();
 
     protected class ProfileDescriptor {
         final ViewGroup rootView;
@@ -160,4 +181,15 @@
             this.rootView = rootView;
         }
     }
+
+    public interface OnProfileSelectedListener {
+        /**
+         * Callback for when the user changes the active tab from personal to work or vice versa.
+         * <p>This callback is only called when the intent resolver or share sheet shows
+         * the work and personal profiles.
+         * @param profileIndex {@link #PROFILE_PERSONAL} if the personal profile was selected or
+         * {@link #PROFILE_WORK} if the work profile was selected.
+         */
+        void onProfileSelected(int profileIndex);
+    }
 }
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
index f9ba34e..384275f 100644
--- a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
+++ b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
@@ -16,21 +16,36 @@
 package com.android.internal.app;
 
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
 import static android.view.accessibility.AccessibilityManager.ShortcutType;
 
+import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
+import static com.android.internal.app.AccessibilityButtonChooserActivity.WhiteListingFeatureElementIndex.COMPONENT_ID;
+import static com.android.internal.app.AccessibilityButtonChooserActivity.WhiteListingFeatureElementIndex.FRAGMENT_TYPE;
+import static com.android.internal.app.AccessibilityButtonChooserActivity.WhiteListingFeatureElementIndex.ICON_ID;
+import static com.android.internal.app.AccessibilityButtonChooserActivity.WhiteListingFeatureElementIndex.LABEL_ID;
+import static com.android.internal.app.AccessibilityButtonChooserActivity.WhiteListingFeatureElementIndex.SETTINGS_KEY;
+import static com.android.internal.util.Preconditions.checkArgument;
+
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.AlertDialog;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.UserHandle;
 import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArraySet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -49,23 +64,53 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
+import java.util.StringJoiner;
 
 /**
  * Activity used to display and persist a service or feature target for the Accessibility button.
  */
 public class AccessibilityButtonChooserActivity extends Activity {
-
-    private static final String MAGNIFICATION_COMPONENT_ID =
-            "com.android.server.accessibility.MagnificationController";
-
+    private static final char SERVICES_SEPARATOR = ':';
+    private static final TextUtils.SimpleStringSplitter sStringColonSplitter =
+            new TextUtils.SimpleStringSplitter(SERVICES_SEPARATOR);
+    @ShortcutType
     private int mShortcutType;
-    private List<AccessibilityButtonTarget> mTargets = new ArrayList<>();
-    private List<AccessibilityButtonTarget> mReadyToBeDisabledTargets = new ArrayList<>();
+    @UserShortcutType
+    private int mShortcutUserType;
+    private final List<AccessibilityButtonTarget> mTargets = new ArrayList<>();
     private AlertDialog mAlertDialog;
     private TargetAdapter mTargetAdapter;
 
     /**
+     * Annotation for different user shortcut type UI type.
+     *
+     * {@code DEFAULT} for displaying default value.
+     * {@code SOFTWARE} for displaying specifying the accessibility services or features which
+     * choose accessibility button in the navigation bar as preferred shortcut.
+     * {@code HARDWARE} for displaying specifying the accessibility services or features which
+     * choose accessibility shortcut as preferred shortcut.
+     * {@code TRIPLETAP} for displaying specifying magnification to be toggled via quickly
+     * tapping screen 3 times as preferred shortcut.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            UserShortcutType.DEFAULT,
+            UserShortcutType.SOFTWARE,
+            UserShortcutType.HARDWARE,
+            UserShortcutType.TRIPLETAP,
+    })
+    /** Denotes the user shortcut type. */
+    private @interface UserShortcutType {
+        int DEFAULT = 0;
+        int SOFTWARE = 1; // 1 << 0
+        int HARDWARE = 2; // 1 << 1
+        int TRIPLETAP = 4; // 1 << 2
+    }
+
+    /**
      * Annotation for different accessibilityService fragment UI type.
      *
      * {@code LEGACY} for displaying appearance aligned with sdk version Q accessibility service
@@ -81,7 +126,7 @@
             AccessibilityServiceFragmentType.INTUITIVE,
             AccessibilityServiceFragmentType.BOUNCE,
     })
-    public @interface AccessibilityServiceFragmentType {
+    private @interface AccessibilityServiceFragmentType {
         int LEGACY = 0;
         int INVISIBLE = 1;
         int INTUITIVE = 2;
@@ -99,11 +144,61 @@
             ShortcutMenuMode.LAUNCH,
             ShortcutMenuMode.EDIT,
     })
-    public @interface ShortcutMenuMode {
+    private @interface ShortcutMenuMode {
         int LAUNCH = 0;
         int EDIT = 1;
     }
 
+    /**
+     * Annotation for align the element index of white listing feature
+     * {@code WHITE_LISTING_FEATURES}.
+     *
+     * {@code COMPONENT_ID} is to get the service component name.
+     * {@code LABEL_ID} is to get the service label text.
+     * {@code ICON_ID} is to get the service icon.
+     * {@code FRAGMENT_TYPE} is to get the service fragment type.
+     * {@code SETTINGS_KEY} is to get the service settings key.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            WhiteListingFeatureElementIndex.COMPONENT_ID,
+            WhiteListingFeatureElementIndex.LABEL_ID,
+            WhiteListingFeatureElementIndex.ICON_ID,
+            WhiteListingFeatureElementIndex.FRAGMENT_TYPE,
+            WhiteListingFeatureElementIndex.SETTINGS_KEY,
+    })
+    @interface WhiteListingFeatureElementIndex {
+        int COMPONENT_ID = 0;
+        int LABEL_ID = 1;
+        int ICON_ID = 2;
+        int FRAGMENT_TYPE = 3;
+        int SETTINGS_KEY = 4;
+    }
+
+    private static final String[][] WHITE_LISTING_FEATURES = {
+            {
+                    COLOR_INVERSION_COMPONENT_NAME.flattenToString(),
+                    String.valueOf(R.string.color_inversion_feature_name),
+                    String.valueOf(R.drawable.ic_accessibility_color_inversion),
+                    String.valueOf(AccessibilityServiceFragmentType.INTUITIVE),
+                    Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
+            },
+            {
+                    DALTONIZER_COMPONENT_NAME.flattenToString(),
+                    String.valueOf(R.string.color_correction_feature_name),
+                    String.valueOf(R.drawable.ic_accessibility_color_correction),
+                    String.valueOf(AccessibilityServiceFragmentType.INTUITIVE),
+                    Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
+            },
+            {
+                    MAGNIFICATION_CONTROLLER_NAME,
+                    String.valueOf(R.string.accessibility_magnification_chooser_text),
+                    String.valueOf(R.drawable.ic_accessibility_magnification),
+                    String.valueOf(AccessibilityServiceFragmentType.INVISIBLE),
+                    Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
+            },
+    };
+
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -114,11 +209,16 @@
         }
 
         mShortcutType = getIntent().getIntExtra(AccessibilityManager.EXTRA_SHORTCUT_TYPE,
-                ACCESSIBILITY_BUTTON);
+                /* unexpectedShortcutType */ -1);
+        final boolean existInShortcutType = (mShortcutType == ACCESSIBILITY_BUTTON)
+                || (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY);
+        checkArgument(existInShortcutType, "Unexpected shortcut type: " + mShortcutType);
+
+        mShortcutUserType = convertToUserType(mShortcutType);
+
         mTargets.addAll(getServiceTargets(this, mShortcutType));
 
-        // TODO(b/146815548): Will add title to separate which one type
-        mTargetAdapter = new TargetAdapter(mTargets);
+        mTargetAdapter = new TargetAdapter(mTargets, mShortcutType);
         mAlertDialog = new AlertDialog.Builder(this)
                 .setAdapter(mTargetAdapter, /* listener= */ null)
                 .setPositiveButton(
@@ -159,6 +259,20 @@
 
     private static List<AccessibilityButtonTarget> getServiceTargets(@NonNull Context context,
             @ShortcutType int shortcutType) {
+        final List<AccessibilityButtonTarget> targets = new ArrayList<>();
+        targets.addAll(getAccessibilityServiceTargets(context));
+        targets.addAll(getWhiteListingServiceTargets(context));
+
+        final AccessibilityManager ams = (AccessibilityManager) context.getSystemService(
+                Context.ACCESSIBILITY_SERVICE);
+        final List<String> requiredTargets = ams.getAccessibilityShortcutTargets(shortcutType);
+        targets.removeIf(target -> !requiredTargets.contains(target.getId()));
+
+        return targets;
+    }
+
+    private static List<AccessibilityButtonTarget> getAccessibilityServiceTargets(
+            @NonNull Context context) {
         final AccessibilityManager ams = (AccessibilityManager) context.getSystemService(
                 Context.ACCESSIBILITY_SERVICE);
         final List<AccessibilityServiceInfo> installedServices =
@@ -169,29 +283,72 @@
 
         final List<AccessibilityButtonTarget> targets = new ArrayList<>(installedServices.size());
         for (AccessibilityServiceInfo info : installedServices) {
-            if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) {
-                targets.add(new AccessibilityButtonTarget(context, info));
-            }
-        }
-
-        final List<String> requiredTargets = ams.getAccessibilityShortcutTargets(shortcutType);
-        targets.removeIf(target -> !requiredTargets.contains(target.getId()));
-
-        // TODO(b/146815874): Will replace it with white list services.
-        if (Settings.Secure.getInt(context.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, 0) == 1) {
-            final AccessibilityButtonTarget magnificationTarget = new AccessibilityButtonTarget(
-                    context,
-                    MAGNIFICATION_COMPONENT_ID,
-                    R.string.accessibility_magnification_chooser_text,
-                    R.drawable.ic_accessibility_magnification,
-                    AccessibilityServiceFragmentType.INTUITIVE);
-            targets.add(magnificationTarget);
+            targets.add(new AccessibilityButtonTarget(context, info));
         }
 
         return targets;
     }
 
+    private static List<AccessibilityButtonTarget> getWhiteListingServiceTargets(
+            @NonNull Context context) {
+        final List<AccessibilityButtonTarget> targets = new ArrayList<>();
+
+        for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) {
+            final AccessibilityButtonTarget target = new AccessibilityButtonTarget(
+                    context,
+                    WHITE_LISTING_FEATURES[i][COMPONENT_ID],
+                    Integer.parseInt(WHITE_LISTING_FEATURES[i][LABEL_ID]),
+                    Integer.parseInt(WHITE_LISTING_FEATURES[i][ICON_ID]),
+                    Integer.parseInt(WHITE_LISTING_FEATURES[i][FRAGMENT_TYPE]));
+            targets.add(target);
+        }
+
+        return targets;
+    }
+
+    private static boolean isWhiteListingServiceEnabled(@NonNull Context context,
+            AccessibilityButtonTarget target) {
+
+        for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) {
+            if (WHITE_LISTING_FEATURES[i][COMPONENT_ID].equals(target.getId())) {
+                return Settings.Secure.getInt(context.getContentResolver(),
+                        WHITE_LISTING_FEATURES[i][SETTINGS_KEY],
+                        /* settingsValueOff */ 0) == /* settingsValueOn */ 1;
+            }
+        }
+
+        return false;
+    }
+
+    private static boolean isWhiteListingService(String componentId) {
+        for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) {
+            if (WHITE_LISTING_FEATURES[i][COMPONENT_ID].equals(componentId)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private void setWhiteListingServiceEnabled(String componentId, int settingsValue) {
+        for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) {
+            if (WHITE_LISTING_FEATURES[i][COMPONENT_ID].equals(componentId)) {
+                Settings.Secure.putInt(getContentResolver(),
+                        WHITE_LISTING_FEATURES[i][SETTINGS_KEY], settingsValue);
+                return;
+            }
+        }
+    }
+
+    private void disableService(String componentId) {
+        if (isWhiteListingService(componentId)) {
+            setWhiteListingServiceEnabled(componentId, /* settingsValueOff */ 0);
+        } else {
+            final ComponentName componentName = ComponentName.unflattenFromString(componentId);
+            setAccessibilityServiceState(this, componentName, /* enabled= */ false);
+        }
+    }
+
     private static class ViewHolder {
         ImageView mIconView;
         TextView mLabelView;
@@ -203,16 +360,21 @@
     private static class TargetAdapter extends BaseAdapter {
         @ShortcutMenuMode
         private int mShortcutMenuMode = ShortcutMenuMode.LAUNCH;
+        @ShortcutType
+        private int mShortcutButtonType;
         private List<AccessibilityButtonTarget> mButtonTargets;
 
-        TargetAdapter(List<AccessibilityButtonTarget> targets) {
+        TargetAdapter(List<AccessibilityButtonTarget> targets,
+                @ShortcutType int shortcutButtonType) {
             this.mButtonTargets = targets;
+            this.mShortcutButtonType = shortcutButtonType;
         }
 
-        void setShortcutMenuMode(int shortcutMenuMode) {
+        void setShortcutMenuMode(@ShortcutMenuMode int shortcutMenuMode) {
             mShortcutMenuMode = shortcutMenuMode;
         }
 
+        @ShortcutMenuMode
         int getShortcutMenuMode() {
             return mShortcutMenuMode;
         }
@@ -269,8 +431,10 @@
 
             switch (target.getFragmentType()) {
                 case AccessibilityServiceFragmentType.LEGACY:
+                    updateLegacyActionItemVisibility(context, holder);
+                    break;
                 case AccessibilityServiceFragmentType.INVISIBLE:
-                    updateLegacyOrInvisibleActionItemVisibility(context, holder);
+                    updateInvisibleActionItemVisibility(context, holder);
                     break;
                 case AccessibilityServiceFragmentType.INTUITIVE:
                     updateIntuitiveActionItemVisibility(context, holder, target);
@@ -283,9 +447,23 @@
             }
         }
 
-        private void updateLegacyOrInvisibleActionItemVisibility(@NonNull Context context,
+        private void updateLegacyActionItemVisibility(@NonNull Context context,
                 @NonNull ViewHolder holder) {
-            final boolean isEditMenuMode = mShortcutMenuMode == ShortcutMenuMode.EDIT;
+            final boolean isLaunchMenuMode = (mShortcutMenuMode == ShortcutMenuMode.LAUNCH);
+            final boolean isHardwareButtonTriggered =
+                    (mShortcutButtonType == ACCESSIBILITY_SHORTCUT_KEY);
+
+            holder.mLabelView.setEnabled(isLaunchMenuMode || isHardwareButtonTriggered);
+            holder.mViewItem.setEnabled(isLaunchMenuMode || isHardwareButtonTriggered);
+            holder.mViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item));
+            holder.mViewItem.setVisibility(View.VISIBLE);
+            holder.mSwitchItem.setVisibility(View.GONE);
+            holder.mItemContainer.setVisibility(isLaunchMenuMode ? View.GONE : View.VISIBLE);
+        }
+
+        private void updateInvisibleActionItemVisibility(@NonNull Context context,
+                @NonNull ViewHolder holder) {
+            final boolean isEditMenuMode = (mShortcutMenuMode == ShortcutMenuMode.EDIT);
 
             holder.mViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item));
             holder.mViewItem.setVisibility(View.VISIBLE);
@@ -295,18 +473,21 @@
 
         private void updateIntuitiveActionItemVisibility(@NonNull Context context,
                 @NonNull ViewHolder holder, AccessibilityButtonTarget target) {
-            final boolean isEditMenuMode = mShortcutMenuMode == ShortcutMenuMode.EDIT;
+            final boolean isEditMenuMode = (mShortcutMenuMode == ShortcutMenuMode.EDIT);
+            final boolean isServiceEnabled = isWhiteListingService(target.getId())
+                    ? isWhiteListingServiceEnabled(context, target)
+                    : isAccessibilityServiceEnabled(context, target);
 
             holder.mViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item));
             holder.mViewItem.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE);
             holder.mSwitchItem.setVisibility(isEditMenuMode ? View.GONE : View.VISIBLE);
-            holder.mSwitchItem.setChecked(!isEditMenuMode && isServiceEnabled(context, target));
+            holder.mSwitchItem.setChecked(!isEditMenuMode && isServiceEnabled);
             holder.mItemContainer.setVisibility(View.VISIBLE);
         }
 
         private void updateBounceActionItemVisibility(@NonNull Context context,
                 @NonNull ViewHolder holder) {
-            final boolean isEditMenuMode = mShortcutMenuMode == ShortcutMenuMode.EDIT;
+            final boolean isEditMenuMode = (mShortcutMenuMode == ShortcutMenuMode.EDIT);
 
             holder.mViewItem.setImageDrawable(
                     isEditMenuMode ? context.getDrawable(R.drawable.ic_delete_item)
@@ -357,7 +538,7 @@
         }
     }
 
-    private static boolean isServiceEnabled(@NonNull Context context,
+    private static boolean isAccessibilityServiceEnabled(@NonNull Context context,
             AccessibilityButtonTarget target) {
         final AccessibilityManager ams = (AccessibilityManager) context.getSystemService(
                 Context.ACCESSIBILITY_SERVICE);
@@ -375,28 +556,120 @@
     }
 
     private void onTargetSelected(AdapterView<?> parent, View view, int position, long id) {
-        Settings.Secure.putString(getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
-                mTargets.get(position).getId());
-        // TODO(b/146969684): notify accessibility button clicked.
+        final AccessibilityButtonTarget target = mTargets.get(position);
+        switch (target.getFragmentType()) {
+            case AccessibilityServiceFragmentType.LEGACY:
+                onLegacyTargetSelected(target);
+                break;
+            case AccessibilityServiceFragmentType.INVISIBLE:
+                onInvisibleTargetSelected(target);
+                break;
+            case AccessibilityServiceFragmentType.INTUITIVE:
+                onIntuitiveTargetSelected(target);
+                break;
+            case AccessibilityServiceFragmentType.BOUNCE:
+                // Do nothing
+                break;
+            default:
+                throw new IllegalStateException("Unexpected fragment type");
+        }
+
         mAlertDialog.dismiss();
     }
 
+    private void onLegacyTargetSelected(AccessibilityButtonTarget target) {
+        if (mShortcutType == ACCESSIBILITY_BUTTON) {
+            final AccessibilityManager ams = (AccessibilityManager) getSystemService(
+                    Context.ACCESSIBILITY_SERVICE);
+            ams.notifyAccessibilityButtonClicked(getDisplayId(), target.getId());
+        } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
+            switchServiceState(target);
+        }
+    }
+
+    private void onInvisibleTargetSelected(AccessibilityButtonTarget target) {
+        final AccessibilityManager ams = (AccessibilityManager) getSystemService(
+                Context.ACCESSIBILITY_SERVICE);
+        ams.notifyAccessibilityButtonClicked(getDisplayId(), target.getId());
+    }
+
+    private void onIntuitiveTargetSelected(AccessibilityButtonTarget target) {
+        switchServiceState(target);
+    }
+
+    private void switchServiceState(AccessibilityButtonTarget target) {
+        final ComponentName componentName =
+                ComponentName.unflattenFromString(target.getId());
+        final String componentId = componentName.flattenToString();
+
+        if (isWhiteListingService(componentId)) {
+            setWhiteListingServiceEnabled(componentId,
+                    isWhiteListingServiceEnabled(this, target)
+                            ? /* settingsValueOff */ 0
+                            : /* settingsValueOn */ 1);
+        } else {
+            setAccessibilityServiceState(this, componentName,
+                    /* enabled= */!isAccessibilityServiceEnabled(this, target));
+        }
+    }
+
     private void onTargetDeleted(AdapterView<?> parent, View view, int position, long id) {
-        // TODO(b/147027236): Will discuss with UX designer what UX behavior about deleting item
-        //  is good for user.
-        mReadyToBeDisabledTargets.add(mTargets.get(position));
+        final AccessibilityButtonTarget target = mTargets.get(position);
+        final String componentId = target.getId();
+
+        switch (target.getFragmentType()) {
+            case AccessibilityServiceFragmentType.LEGACY:
+                onLegacyTargetDeleted(position, componentId);
+                break;
+            case AccessibilityServiceFragmentType.INVISIBLE:
+                onInvisibleTargetDeleted(position, componentId);
+                break;
+            case AccessibilityServiceFragmentType.INTUITIVE:
+                onIntuitiveTargetDeleted(position, componentId);
+                break;
+            case AccessibilityServiceFragmentType.BOUNCE:
+                // Do nothing
+                break;
+            default:
+                throw new IllegalStateException("Unexpected fragment type");
+        }
+
+        if (mTargets.isEmpty()) {
+            mAlertDialog.dismiss();
+        }
+    }
+
+    private void onLegacyTargetDeleted(int position, String componentId) {
+        if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
+            optOutValueFromSettings(this, mShortcutUserType, componentId);
+
+            mTargets.remove(position);
+            mTargetAdapter.notifyDataSetChanged();
+        }
+    }
+
+    private void onInvisibleTargetDeleted(int position, String componentId) {
+        optOutValueFromSettings(this, mShortcutUserType, componentId);
+
+        final int shortcutTypes = UserShortcutType.SOFTWARE | UserShortcutType.HARDWARE;
+        if (!hasValuesInSettings(this, shortcutTypes, componentId)) {
+            disableService(componentId);
+        }
+
+        mTargets.remove(position);
+        mTargetAdapter.notifyDataSetChanged();
+    }
+
+    private void onIntuitiveTargetDeleted(int position, String componentId) {
+        optOutValueFromSettings(this, mShortcutUserType, componentId);
         mTargets.remove(position);
         mTargetAdapter.notifyDataSetChanged();
     }
 
     private void onCancelButtonClicked() {
-        resetAndUpdateTargets();
-
         mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.LAUNCH);
         mTargetAdapter.notifyDataSetChanged();
 
-        mAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE);
         mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(
                 getString(R.string.edit_accessibility_shortcut_menu_button));
 
@@ -407,49 +680,202 @@
         mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.EDIT);
         mTargetAdapter.notifyDataSetChanged();
 
-        mAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setText(
+        mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(
                 getString(R.string.cancel_accessibility_shortcut_menu_button));
-        mAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.VISIBLE);
-        mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(
-                getString(R.string.save_accessibility_shortcut_menu_button));
-
-        updateDialogListeners();
-    }
-
-    private void onSaveButtonClicked() {
-        disableTargets();
-        resetAndUpdateTargets();
-
-        mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.LAUNCH);
-        mTargetAdapter.notifyDataSetChanged();
-
-        mAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE);
-        mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(
-                getString(R.string.edit_accessibility_shortcut_menu_button));
 
         updateDialogListeners();
     }
 
     private void updateDialogListeners() {
         final boolean isEditMenuMode =
-                mTargetAdapter.getShortcutMenuMode() == ShortcutMenuMode.EDIT;
+                (mTargetAdapter.getShortcutMenuMode() == ShortcutMenuMode.EDIT);
 
-        mAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener(
-                view -> onCancelButtonClicked());
         mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(
-                isEditMenuMode ? view -> onSaveButtonClicked() : view -> onEditButtonClicked());
+                isEditMenuMode ? view -> onCancelButtonClicked() : view -> onEditButtonClicked());
         mAlertDialog.getListView().setOnItemClickListener(
                 isEditMenuMode ? this::onTargetDeleted : this::onTargetSelected);
     }
 
-    private void disableTargets() {
-        for (AccessibilityButtonTarget service : mReadyToBeDisabledTargets) {
-            // TODO(b/146967898): disable services.
+    /**
+     * @return the set of enabled accessibility services for {@param userId}. If there are no
+     * services, it returns the unmodifiable {@link Collections#emptySet()}.
+     */
+    private Set<ComponentName> getEnabledServicesFromSettings(Context context, int userId) {
+        final String enabledServicesSetting = Settings.Secure.getStringForUser(
+                context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                userId);
+        if (TextUtils.isEmpty(enabledServicesSetting)) {
+            return Collections.emptySet();
+        }
+
+        final Set<ComponentName> enabledServices = new HashSet<>();
+        final TextUtils.StringSplitter colonSplitter =
+                new TextUtils.SimpleStringSplitter(SERVICES_SEPARATOR);
+        colonSplitter.setString(enabledServicesSetting);
+
+        for (String componentNameString : colonSplitter) {
+            final ComponentName enabledService = ComponentName.unflattenFromString(
+                    componentNameString);
+            if (enabledService != null) {
+                enabledServices.add(enabledService);
+            }
+        }
+
+        return enabledServices;
+    }
+
+    /**
+     * Changes an accessibility component's state.
+     */
+    private void setAccessibilityServiceState(Context context, ComponentName componentName,
+            boolean enabled) {
+        setAccessibilityServiceState(context, componentName, enabled, UserHandle.myUserId());
+    }
+
+    /**
+     * Changes an accessibility component's state for {@param userId}.
+     */
+    private void setAccessibilityServiceState(Context context, ComponentName componentName,
+            boolean enabled, int userId) {
+        Set<ComponentName> enabledServices = getEnabledServicesFromSettings(
+                context, userId);
+
+        if (enabledServices.isEmpty()) {
+            enabledServices = new ArraySet<>(/* capacity= */ 1);
+        }
+
+        if (enabled) {
+            enabledServices.add(componentName);
+        } else {
+            enabledServices.remove(componentName);
+        }
+
+        final StringBuilder enabledServicesBuilder = new StringBuilder();
+        for (ComponentName enabledService : enabledServices) {
+            enabledServicesBuilder.append(enabledService.flattenToString());
+            enabledServicesBuilder.append(
+                    SERVICES_SEPARATOR);
+        }
+
+        final int enabledServicesBuilderLength = enabledServicesBuilder.length();
+        if (enabledServicesBuilderLength > 0) {
+            enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1);
+        }
+
+        Settings.Secure.putStringForUser(context.getContentResolver(),
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                enabledServicesBuilder.toString(), userId);
+    }
+
+    /**
+     * Opts out component name into colon-separated {@code shortcutType} key's string in Settings.
+     *
+     * @param context The current context.
+     * @param shortcutType The preferred shortcut type user selected.
+     * @param componentId The component id that need to be opted out from Settings.
+     */
+    private void optOutValueFromSettings(
+            Context context, @UserShortcutType int shortcutType, String componentId) {
+        final StringJoiner joiner = new StringJoiner(String.valueOf(SERVICES_SEPARATOR));
+        final String targetsKey = convertToKey(shortcutType);
+        final String targetsValue = Settings.Secure.getString(context.getContentResolver(),
+                targetsKey);
+
+        if (TextUtils.isEmpty(targetsValue)) {
+            return;
+        }
+
+        sStringColonSplitter.setString(targetsValue);
+        while (sStringColonSplitter.hasNext()) {
+            final String id = sStringColonSplitter.next();
+            if (TextUtils.isEmpty(id) || componentId.equals(id)) {
+                continue;
+            }
+            joiner.add(id);
+        }
+
+        Settings.Secure.putString(context.getContentResolver(), targetsKey, joiner.toString());
+    }
+
+    /**
+     * Returns if component name existed in one of {@code shortcutTypes} string in Settings.
+     *
+     * @param context The current context.
+     * @param shortcutTypes A combination of {@link UserShortcutType}.
+     * @param componentId The component name that need to be checked existed in Settings.
+     * @return {@code true} if componentName existed in Settings.
+     */
+    private boolean hasValuesInSettings(Context context, int shortcutTypes,
+            @NonNull String componentId) {
+        boolean exist = false;
+        if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) {
+            exist = hasValueInSettings(context, UserShortcutType.SOFTWARE, componentId);
+        }
+        if (((shortcutTypes & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE)) {
+            exist |= hasValueInSettings(context, UserShortcutType.HARDWARE, componentId);
+        }
+        return exist;
+    }
+
+
+    /**
+     * Returns if component name existed in Settings.
+     *
+     * @param context The current context.
+     * @param shortcutType The preferred shortcut type user selected.
+     * @param componentId The component id that need to be checked existed in Settings.
+     * @return {@code true} if componentName existed in Settings.
+     */
+    private boolean hasValueInSettings(Context context, @UserShortcutType int shortcutType,
+            @NonNull String componentId) {
+        final String targetKey = convertToKey(shortcutType);
+        final String targetString = Settings.Secure.getString(context.getContentResolver(),
+                targetKey);
+
+        if (TextUtils.isEmpty(targetString)) {
+            return false;
+        }
+
+        sStringColonSplitter.setString(targetString);
+        while (sStringColonSplitter.hasNext()) {
+            final String id = sStringColonSplitter.next();
+            if (componentId.equals(id)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Converts {@link UserShortcutType} to key in Settings.
+     *
+     * @param type The shortcut type.
+     * @return Mapping key in Settings.
+     */
+    private String convertToKey(@UserShortcutType int type) {
+        switch (type) {
+            case UserShortcutType.SOFTWARE:
+                return Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT;
+            case UserShortcutType.HARDWARE:
+                return Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
+            case UserShortcutType.TRIPLETAP:
+                return Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED;
+            default:
+                throw new IllegalArgumentException(
+                        "Unsupported user shortcut type: " + type);
         }
     }
 
-    private void resetAndUpdateTargets() {
-        mTargets.clear();
-        mTargets.addAll(getServiceTargets(this, mShortcutType));
+    private static @UserShortcutType int convertToUserType(@ShortcutType int type) {
+        switch (type) {
+            case ACCESSIBILITY_BUTTON:
+                return UserShortcutType.SOFTWARE;
+            case ACCESSIBILITY_SHORTCUT_KEY:
+                return UserShortcutType.HARDWARE;
+            default:
+                throw new IllegalArgumentException(
+                        "Unsupported shortcut type:" + type);
+        }
     }
 }
diff --git a/core/java/com/android/internal/app/BlockedAppActivity.java b/core/java/com/android/internal/app/BlockedAppActivity.java
new file mode 100644
index 0000000..fbdbbfb
--- /dev/null
+++ b/core/java/com/android/internal/app/BlockedAppActivity.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 com.android.internal.app;
+
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.R;
+
+/**
+ * A dialog shown to the user when they try to launch an app that is not allowed in lock task
+ * mode. The intent to start this activity must be created with the static factory method provided
+ * below.
+ */
+public class BlockedAppActivity extends AlertActivity {
+
+    private static final String TAG = "BlockedAppActivity";
+    private static final String PACKAGE_NAME = "com.android.internal.app";
+    private static final String EXTRA_BLOCKED_PACKAGE = PACKAGE_NAME + ".extra.BLOCKED_PACKAGE";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Intent intent = getIntent();
+        int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, /* defaultValue= */ -1);
+        if (userId < 0) {
+            Slog.wtf(TAG, "Invalid user: " + userId);
+            finish();
+            return;
+        }
+
+        String packageName = intent.getStringExtra(EXTRA_BLOCKED_PACKAGE);
+        if (TextUtils.isEmpty(packageName)) {
+            Slog.wtf(TAG, "Invalid package: " + packageName);
+            finish();
+            return;
+        }
+
+        CharSequence appLabel = getAppLabel(userId, packageName);
+
+        mAlertParams.mTitle = getString(R.string.app_blocked_title);
+        mAlertParams.mMessage = getString(R.string.app_blocked_message, appLabel);
+        mAlertParams.mPositiveButtonText = getString(android.R.string.ok);
+        setupAlert();
+    }
+
+    private CharSequence getAppLabel(int userId, String packageName) {
+        PackageManager pm = getPackageManager();
+        try {
+            ApplicationInfo aInfo =
+                    pm.getApplicationInfoAsUser(packageName, /* flags= */ 0, userId);
+            return aInfo.loadLabel(pm);
+        } catch (PackageManager.NameNotFoundException ne) {
+            Slog.e(TAG, "Package " + packageName + " not found", ne);
+        }
+        return packageName;
+    }
+
+
+    /** Creates an intent that launches {@link BlockedAppActivity}. */
+    public static Intent createIntent(int userId, String packageName) {
+        return new Intent()
+                .setClassName("android", BlockedAppActivity.class.getName())
+                .putExtra(Intent.EXTRA_USER_ID, userId)
+                .putExtra(EXTRA_BLOCKED_PACKAGE, packageName);
+    }
+}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index b6b548c..8bbc343 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -71,6 +71,7 @@
 import android.os.IBinder;
 import android.os.Message;
 import android.os.Parcelable;
+import android.os.PatternMatcher;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
@@ -507,7 +508,6 @@
     protected void onCreate(Bundle savedInstanceState) {
         final long intentReceivedTime = System.currentTimeMillis();
         // This is the only place this value is being set. Effectively final.
-        //TODO(arangelov) - should there be a mIsAppPredictorComponentAvailable flag for work tab?
         mIsAppPredictorComponentAvailable = isAppPredictionServiceAvailable();
 
         mIsSuccessfullySelected = false;
@@ -688,29 +688,6 @@
                 mResolverDrawerLayout.setOnScrollChangeListener(this::handleScroll);
             }
 
-            final View chooserHeader = mResolverDrawerLayout.findViewById(R.id.chooser_header);
-            final float defaultElevation = chooserHeader.getElevation();
-            final float chooserHeaderScrollElevation =
-                    getResources().getDimensionPixelSize(R.dimen.chooser_header_scroll_elevation);
-
-            mChooserMultiProfilePagerAdapter.getCurrentAdapterView().addOnScrollListener(
-                    new RecyclerView.OnScrollListener() {
-                        public void onScrollStateChanged(RecyclerView view, int scrollState) {
-                        }
-
-                        public void onScrolled(RecyclerView view, int dx, int dy) {
-                            if (view.getChildCount() > 0) {
-                                View child = view.getLayoutManager().findViewByPosition(0);
-                                if (child == null || child.getTop() < 0) {
-                                    chooserHeader.setElevation(chooserHeaderScrollElevation);
-                                    return;
-                                }
-                            }
-
-                            chooserHeader.setElevation(defaultElevation);
-                        }
-            });
-
             mResolverDrawerLayout.setOnCollapsedChangedListener(
                     new ResolverDrawerLayout.OnCollapsedChangedListener() {
 
@@ -1329,8 +1306,8 @@
     }
 
     @Override
-    public void onPrepareAdapterView(ResolverListAdapter adapter) {
-        mChooserMultiProfilePagerAdapter.getCurrentAdapterView().setVisibility(View.VISIBLE);
+    public void addUseDifferentAppLabelIfNecessary(ResolverListAdapter adapter) {
+        mChooserMultiProfilePagerAdapter.getActiveAdapterView().setVisibility(View.VISIBLE);
         if (mCallerChooserTargets != null && mCallerChooserTargets.length > 0) {
             mChooserMultiProfilePagerAdapter.getActiveListAdapter().addServiceResults(
                     /* origTarget */ null,
@@ -1650,10 +1627,28 @@
         try {
             final Intent intent = getTargetIntent();
             String dataString = intent.getDataString();
-            if (TextUtils.isEmpty(dataString)) {
-                dataString = intent.getType();
+            if (!TextUtils.isEmpty(dataString)) {
+                return new IntentFilter(intent.getAction(), dataString);
             }
-            return new IntentFilter(intent.getAction(), dataString);
+            IntentFilter intentFilter = new IntentFilter(intent.getAction(), intent.getType());
+            List<Uri> contentUris = new ArrayList<>();
+            if (Intent.ACTION_SEND.equals(intent.getAction())) {
+                Uri uri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
+                if (uri != null) {
+                    contentUris.add(uri);
+                }
+            } else {
+                List<Uri> uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
+                if (uris != null) {
+                    contentUris.addAll(uris);
+                }
+            }
+            for (Uri uri : contentUris) {
+                intentFilter.addDataScheme(uri.getScheme());
+                intentFilter.addDataAuthority(uri.getAuthority(), null);
+                intentFilter.addDataPath(uri.getPath(), PatternMatcher.PATTERN_LITERAL);
+            }
+            return intentFilter;
         } catch (Exception e) {
             Log.e(TAG, "failed to get target intent filter " + e);
             return null;
@@ -2183,7 +2178,7 @@
         if (mChooserMultiProfilePagerAdapter == null) {
             return;
         }
-        RecyclerView recyclerView = mChooserMultiProfilePagerAdapter.getCurrentAdapterView();
+        RecyclerView recyclerView = mChooserMultiProfilePagerAdapter.getActiveAdapterView();
         ChooserGridAdapter gridAdapter = mChooserMultiProfilePagerAdapter.getCurrentRootAdapter();
         if (gridAdapter == null || recyclerView == null) {
             return;
@@ -2309,6 +2304,8 @@
 
     @Override
     public void onListRebuilt(ResolverListAdapter listAdapter) {
+        setupScrollListener();
+
         ChooserListAdapter chooserListAdapter = (ChooserListAdapter) listAdapter;
         if (chooserListAdapter.mDisplayList == null
                 || chooserListAdapter.mDisplayList.isEmpty()) {
@@ -2349,6 +2346,34 @@
         }
     }
 
+    private void setupScrollListener() {
+        if (mResolverDrawerLayout == null) {
+            return;
+        }
+        final View chooserHeader = mResolverDrawerLayout.findViewById(R.id.chooser_header);
+        final float defaultElevation = chooserHeader.getElevation();
+        final float chooserHeaderScrollElevation =
+                getResources().getDimensionPixelSize(R.dimen.chooser_header_scroll_elevation);
+
+        mChooserMultiProfilePagerAdapter.getActiveAdapterView().addOnScrollListener(
+                new RecyclerView.OnScrollListener() {
+                    public void onScrollStateChanged(RecyclerView view, int scrollState) {
+                    }
+
+                    public void onScrolled(RecyclerView view, int dx, int dy) {
+                        if (view.getChildCount() > 0) {
+                            View child = view.getLayoutManager().findViewByPosition(0);
+                            if (child == null || child.getTop() < 0) {
+                                chooserHeader.setElevation(chooserHeaderScrollElevation);
+                                return;
+                            }
+                        }
+
+                        chooserHeader.setElevation(defaultElevation);
+                    }
+                });
+    }
+
     @Override // ChooserListCommunicator
     public boolean isSendAction(Intent targetIntent) {
         if (targetIntent == null) {
@@ -2456,7 +2481,8 @@
      * row level by this adapter but not on the item level. Individual targets within the row are
      * handled by {@link ChooserListAdapter}
      */
-    final class ChooserGridAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
+    @VisibleForTesting
+    public final class ChooserGridAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
         private ChooserListAdapter mChooserListAdapter;
         private final LayoutInflater mLayoutInflater;
 
@@ -2886,7 +2912,7 @@
 
             if (mDirectShareViewHolder != null && canExpandDirectShare) {
                 mDirectShareViewHolder.handleScroll(
-                        mChooserMultiProfilePagerAdapter.getCurrentAdapterView(), y, oldy,
+                        mChooserMultiProfilePagerAdapter.getActiveAdapterView(), y, oldy,
                         getMaxTargetsPerRow());
             }
         }
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index a8a676d..6ff844a 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -246,11 +246,6 @@
     }
 
     @Override
-    public boolean shouldGetResolvedFilter() {
-        return true;
-    }
-
-    @Override
     public int getCount() {
         return getRankedTargetCount() + getAlphaTargetCount()
                 + getSelectableServiceTargetCount() + getCallerTargetCount();
diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
index 7d856e1..1c52d0e 100644
--- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.app;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.view.LayoutInflater;
 import android.view.ViewGroup;
@@ -77,7 +78,8 @@
     }
 
     @Override
-    ChooserActivity.ChooserGridAdapter getAdapterForIndex(int pageIndex) {
+    @VisibleForTesting
+    public ChooserActivity.ChooserGridAdapter getAdapterForIndex(int pageIndex) {
         return mItems[pageIndex].chooserGridAdapter;
     }
 
@@ -121,10 +123,19 @@
     }
 
     @Override
-    RecyclerView getCurrentAdapterView() {
+    RecyclerView getActiveAdapterView() {
         return getListViewForIndex(getCurrentPage());
     }
 
+    @Override
+    @Nullable
+    RecyclerView getInactiveAdapterView() {
+        if (getCount() == 1) {
+            return null;
+        }
+        return getListViewForIndex(1 - getCurrentPage());
+    }
+
     class ChooserProfileDescriptor extends ProfileDescriptor {
         private ChooserActivity.ChooserGridAdapter chooserGridAdapter;
         private RecyclerView recyclerView;
diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl
index d94294f..74bfb96 100644
--- a/core/java/com/android/internal/app/ISoundTriggerService.aidl
+++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl
@@ -61,10 +61,6 @@
     int setParameter(in ParcelUuid soundModelId, in ModelParams modelParam,
         int value);
 
-    /**
-     * @throws UnsupportedOperationException if hal or model do not support this API.
-     * @throws IllegalArgumentException if invalid model handle or parameter is passed.
-     */
     int getParameter(in ParcelUuid soundModelId, in ModelParams modelParam);
 
     @nullable SoundTrigger.ModelParamRange queryParameter(in ParcelUuid soundModelId,
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index f462f5d..f3b6d29 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -26,6 +26,8 @@
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.app.IVoiceInteractionSessionListener;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.KeyphraseMetadata;
+import android.hardware.soundtrigger.ModelParams;
 import android.hardware.soundtrigger.SoundTrigger;
 import android.service.voice.IVoiceInteractionService;
 import android.service.voice.IVoiceInteractionSession;
@@ -71,8 +73,8 @@
      */
     SoundTrigger.ModuleProperties getDspModuleProperties(in IVoiceInteractionService service);
     /**
-     * Indicates if there's a keyphrase sound model available for the given keyphrase ID.
-     * This performs the check for the current user.
+     * Indicates if there's a keyphrase sound model available for the given keyphrase ID and the
+     * user ID of the caller.
      *
      * @param service The current VoiceInteractionService.
      * @param keyphraseId The unique identifier for the keyphrase.
@@ -81,6 +83,18 @@
     boolean isEnrolledForKeyphrase(IVoiceInteractionService service, int keyphraseId,
             String bcp47Locale);
     /**
+     * Generates KeyphraseMetadata for an enrolled sound model based on keyphrase string, locale,
+     * and the user ID of the caller.
+     *
+     * @param service The current VoiceInteractionService
+     * @param keyphrase Keyphrase text associated with the enrolled model
+     * @param bcp47Locale The BCP47 language tag for the keyphrase's locale.
+     * @return The metadata for the enrolled voice model bassed on the passed in parameters. Null if
+     *         no matching voice model exists.
+     */
+    KeyphraseMetadata getEnrolledKeyphraseMetadata(IVoiceInteractionService service,
+            String keyphrase, String bcp47Locale);
+    /**
      * Starts a recognition for the given keyphrase.
      */
     int startRecognition(in IVoiceInteractionService service, int keyphraseId,
@@ -91,6 +105,49 @@
      */
     int stopRecognition(in IVoiceInteractionService service, int keyphraseId,
             in IRecognitionStatusCallback callback);
+    /**
+     * Set a model specific ModelParams with the given value. This
+     * parameter will keep its value for the duration the model is loaded regardless of starting and
+     * stopping recognition. Once the model is unloaded, the value will be lost.
+     * queryParameter should be checked first before calling this method.
+     *
+     * @param service The current VoiceInteractionService.
+     * @param keyphraseId The unique identifier for the keyphrase.
+     * @param modelParam   ModelParams
+     * @param value        Value to set
+     * @return - {@link SoundTrigger#STATUS_OK} in case of success
+     *         - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
+     *         - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
+     *         - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
+     *           if API is not supported by HAL
+     */
+    int setParameter(in IVoiceInteractionService service, int keyphraseId,
+            in ModelParams modelParam, int value);
+    /**
+     * Get a model specific ModelParams. This parameter will keep its value
+     * for the duration the model is loaded regardless of starting and stopping recognition.
+     * Once the model is unloaded, the value will be lost. If the value is not set, a default
+     * value is returned. See ModelParams for parameter default values.
+     * queryParameter should be checked first before calling this method.
+     *
+     * @param service The current VoiceInteractionService.
+     * @param keyphraseId The unique identifier for the keyphrase.
+     * @param modelParam   ModelParams
+     * @return value of parameter
+     */
+    int getParameter(in IVoiceInteractionService service, int keyphraseId,
+            in ModelParams modelParam);
+    /**
+     * Determine if parameter control is supported for the given model handle.
+     * This method should be checked prior to calling setParameter or getParameter.
+     *
+     * @param service The current VoiceInteractionService.
+     * @param keyphraseId The unique identifier for the keyphrase.
+     * @param modelParam ModelParams
+     * @return supported range of parameter, null if not supported
+     */
+    @nullable SoundTrigger.ModelParamRange queryParameter(in IVoiceInteractionService service,
+            int keyphraseId, in ModelParams modelParam);
 
     /**
      * @return the component name for the currently active voice interaction service
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index b2417b2..68d6e03 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -59,6 +59,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
+import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -67,9 +68,12 @@
 import android.widget.AbsListView;
 import android.widget.AdapterView;
 import android.widget.Button;
+import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.ListView;
 import android.widget.Space;
+import android.widget.TabHost;
+import android.widget.TabWidget;
 import android.widget.TextView;
 import android.widget.Toast;
 
@@ -82,6 +86,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.internal.widget.ResolverDrawerLayout;
+import com.android.internal.widget.ViewPager;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -147,7 +152,10 @@
     /**
      * TODO(arangelov): Remove a couple of weeks after work/personal tabs are finalized.
      */
-    static final boolean ENABLE_TABBED_VIEW = false;
+    @VisibleForTesting
+    public static boolean ENABLE_TABBED_VIEW = false;
+    private static final String TAB_TAG_PERSONAL = "personal";
+    private static final String TAB_TAG_WORK = "work";
 
     private final PackageMonitor mPackageMonitor = createPackageMonitor();
 
@@ -418,12 +426,16 @@
             Intent[] initialIntents,
             List<ResolveInfo> rList,
             boolean filterLastUsed) {
+        // We only show the default app for the profile of the current user. The filterLastUsed
+        // flag determines whether to show a default app and that app is not shown in the
+        // resolver list. So filterLastUsed should be false for the other profile.
         ResolverListAdapter personalAdapter = createResolverListAdapter(
                 /* context */ this,
                 /* payloadIntents */ mIntents,
                 initialIntents,
                 rList,
-                filterLastUsed,
+                (filterLastUsed && UserHandle.myUserId()
+                        == getPersonalProfileUserHandle().getIdentifier()),
                 mUseLayoutForBrowsables,
                 /* userHandle */ getPersonalProfileUserHandle());
         ResolverListAdapter workAdapter = createResolverListAdapter(
@@ -431,7 +443,8 @@
                 /* payloadIntents */ mIntents,
                 initialIntents,
                 rList,
-                filterLastUsed,
+                (filterLastUsed && UserHandle.myUserId()
+                        == getWorkProfileUserHandle().getIdentifier()),
                 mUseLayoutForBrowsables,
                 /* userHandle */ getWorkProfileUserHandle());
         return new ResolverMultiProfilePagerAdapter(
@@ -495,12 +508,12 @@
             mFooterSpacer = new Space(getApplicationContext());
         } else {
             ((ResolverMultiProfilePagerAdapter) mMultiProfilePagerAdapter)
-                .getCurrentAdapterView().removeFooterView(mFooterSpacer);
+                .getActiveAdapterView().removeFooterView(mFooterSpacer);
         }
         mFooterSpacer.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,
                                                                    mSystemWindowInsets.bottom));
         ((ResolverMultiProfilePagerAdapter) mMultiProfilePagerAdapter)
-            .getCurrentAdapterView().addFooterView(mFooterSpacer);
+            .getActiveAdapterView().addFooterView(mFooterSpacer);
     }
 
     protected WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
@@ -817,7 +830,7 @@
 
     public void onButtonClick(View v) {
         final int id = v.getId();
-        ListView listView = (ListView) mMultiProfilePagerAdapter.getCurrentAdapterView();
+        ListView listView = (ListView) mMultiProfilePagerAdapter.getActiveAdapterView();
         ResolverListAdapter currentListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter();
         int which = currentListAdapter.hasFilteredItem()
                 ? currentListAdapter.getFilteredPosition()
@@ -898,7 +911,10 @@
 
     @Override // ResolverListCommunicator
     public void onPostListReady(ResolverListAdapter listAdapter) {
-        setHeader();
+        if (mMultiProfilePagerAdapter.getCurrentUserHandle().getIdentifier()
+                == UserHandle.myUserId()) {
+            setHeader();
+        }
         resetButtonBar();
         onListRebuilt(listAdapter);
     }
@@ -913,6 +929,9 @@
                 finish();
             }
         }
+
+        final ItemClickListener listener = new ItemClickListener();
+        setupAdapterListView((ListView) mMultiProfilePagerAdapter.getActiveAdapterView(), listener);
     }
 
     protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
@@ -1094,6 +1113,7 @@
         return true;
     }
 
+    @VisibleForTesting
     public void safelyStartActivity(TargetInfo cti) {
         // We're dispatching intents that might be coming from legacy apps, so
         // don't kill ourselves.
@@ -1222,9 +1242,6 @@
                     + "cannot be null.");
         }
         boolean rebuildCompleted = mMultiProfilePagerAdapter.getActiveListAdapter().rebuildList();
-        if (mMultiProfilePagerAdapter.getInactiveListAdapter() != null) {
-            mMultiProfilePagerAdapter.getInactiveListAdapter().rebuildList();
-        }
         if (useLayoutWithDefault()) {
             mLayoutId = R.layout.resolver_list_with_default;
         } else {
@@ -1272,45 +1289,99 @@
             }
         }
 
-        setupViewVisibilities(count);
+        setupViewVisibilities();
+
+        if (hasWorkProfile() && ENABLE_TABBED_VIEW) {
+            setupProfileTabs();
+        }
+
         return false;
     }
 
-    private void setupViewVisibilities(int count) {
-        if (count == 0
-                && mMultiProfilePagerAdapter.getActiveListAdapter().getPlaceholderCount() == 0) {
-            final TextView emptyView = findViewById(R.id.empty);
-            emptyView.setVisibility(View.VISIBLE);
-            findViewById(R.id.profile_pager).setVisibility(View.GONE);
-        } else {
-            onPrepareAdapterView(mMultiProfilePagerAdapter.getActiveListAdapter());
+    private void setupProfileTabs() {
+        TabHost tabHost = findViewById(R.id.profile_tabhost);
+        tabHost.setup();
+        ViewPager viewPager = findViewById(R.id.profile_pager);
+        TabHost.TabSpec tabSpec = tabHost.newTabSpec(TAB_TAG_PERSONAL)
+                .setContent(R.id.profile_pager)
+                .setIndicator(getString(R.string.resolver_personal_tab));
+        tabHost.addTab(tabSpec);
+
+        tabSpec = tabHost.newTabSpec(TAB_TAG_WORK)
+                .setContent(R.id.profile_pager)
+                .setIndicator(getString(R.string.resolver_work_tab));
+        tabHost.addTab(tabSpec);
+
+        TabWidget tabWidget = tabHost.getTabWidget();
+        tabWidget.setVisibility(View.VISIBLE);
+        resetTabsHeaderStyle(tabWidget);
+        updateActiveTabStyle(tabHost);
+
+        tabHost.setOnTabChangedListener(tabId -> {
+            resetTabsHeaderStyle(tabWidget);
+            updateActiveTabStyle(tabHost);
+            if (TAB_TAG_PERSONAL.equals(tabId)) {
+                viewPager.setCurrentItem(0);
+            } else {
+                viewPager.setCurrentItem(1);
+            }
+            setupViewVisibilities();
+        });
+
+        viewPager.setVisibility(View.VISIBLE);
+        tabHost.setCurrentTab(mMultiProfilePagerAdapter.getCurrentPage());
+        mMultiProfilePagerAdapter.setOnProfileSelectedListener(tabHost::setCurrentTab);
+    }
+
+    private void resetTabsHeaderStyle(TabWidget tabWidget) {
+        for (int i = 0; i < tabWidget.getChildCount(); i++) {
+            TextView title = tabWidget.getChildAt(i).findViewById(android.R.id.title);
+            title.setTextColor(getColor(R.color.resolver_tabs_inactive_color));
+            title.setAllCaps(false);
+        }
+    }
+
+    private void updateActiveTabStyle(TabHost tabHost) {
+        TextView title = tabHost.getTabWidget().getChildAt(tabHost.getCurrentTab())
+                .findViewById(android.R.id.title);
+        title.setTextColor(getColor(R.color.resolver_tabs_active_color));
+    }
+
+    private void setupViewVisibilities() {
+        int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount();
+        boolean shouldShowEmptyState = count == 0
+                && mMultiProfilePagerAdapter.getActiveListAdapter().getPlaceholderCount() == 0;
+        //TODO(arangelov): Handle empty state
+        if (!shouldShowEmptyState) {
+            addUseDifferentAppLabelIfNecessary(mMultiProfilePagerAdapter.getActiveListAdapter());
         }
     }
 
     /**
-     * Prepare the scrollable view which consumes data in the list adapter.
+     * Add a label to signify that the user can pick a different app.
      * @param adapter The adapter used to provide data to item views.
      */
-    public void onPrepareAdapterView(ResolverListAdapter adapter) {
-        mMultiProfilePagerAdapter.getCurrentAdapterView().setVisibility(View.VISIBLE);
+    public void addUseDifferentAppLabelIfNecessary(ResolverListAdapter adapter) {
         final boolean useHeader = adapter.hasFilteredItem();
-        final ListView listView = (ListView) mMultiProfilePagerAdapter.getCurrentAdapterView();
-        final ItemClickListener listener = new ItemClickListener();
+        if (useHeader) {
+            FrameLayout stub = findViewById(R.id.stub);
+            stub.setVisibility(View.VISIBLE);
+            TextView textView = (TextView) LayoutInflater.from(this).inflate(
+                    R.layout.resolver_different_item_header, null, false);
+            if (ENABLE_TABBED_VIEW) {
+                textView.setGravity(Gravity.CENTER);
+            }
+            stub.addView(textView);
+        }
+    }
+
+    private void setupAdapterListView(ListView listView, ItemClickListener listener) {
         listView.setOnItemClickListener(listener);
         listView.setOnItemLongClickListener(listener);
 
         if (mSupportsAlwaysUseOption || mUseLayoutForBrowsables) {
             listView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
         }
-
-        // In case this method is called again (due to activity recreation), avoid adding a new
-        // header if one is already present.
-        if (useHeader && listView.getHeaderViewsCount() == 0) {
-            listView.setHeaderDividersEnabled(true);
-            listView.addHeaderView(LayoutInflater.from(this).inflate(
-                    R.layout.resolver_different_item_header, listView, false),
-                    null, false);
-        }
     }
 
     /**
@@ -1378,7 +1449,7 @@
         }
 
         // When the items load in, if an item was already selected, enable the buttons
-        ListView currentAdapterView = (ListView) mMultiProfilePagerAdapter.getCurrentAdapterView();
+        ListView currentAdapterView = (ListView) mMultiProfilePagerAdapter.getActiveAdapterView();
         if (currentAdapterView != null
                 && currentAdapterView.getCheckedItemPosition() != ListView.INVALID_POSITION) {
             setAlwaysButtonEnabled(true, currentAdapterView.getCheckedItemPosition(), true);
@@ -1388,8 +1459,18 @@
 
     @Override // ResolverListCommunicator
     public boolean useLayoutWithDefault() {
-        return mSupportsAlwaysUseOption
-                && mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem();
+        // We only use the default app layout when the profile of the active user has a
+        // filtered item. We always show the same default app even in the inactive user profile.
+        boolean currentUserAdapterHasFilteredItem;
+        if (mMultiProfilePagerAdapter.getCurrentUserHandle().getIdentifier()
+                == UserHandle.myUserId()) {
+            currentUserAdapterHasFilteredItem =
+                    mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem();
+        } else {
+            currentUserAdapterHasFilteredItem =
+                    mMultiProfilePagerAdapter.getInactiveListAdapter().hasFilteredItem();
+        }
+        return mSupportsAlwaysUseOption && currentUserAdapterHasFilteredItem;
     }
 
     /**
@@ -1494,9 +1575,8 @@
                     .resolveInfoForPosition(position, true) == null) {
                 return;
             }
-
             ListView currentAdapterView =
-                    (ListView) mMultiProfilePagerAdapter.getCurrentAdapterView();
+                    (ListView) mMultiProfilePagerAdapter.getActiveAdapterView();
             final int checkedPos = currentAdapterView.getCheckedItemPosition();
             final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION;
             if (!useLayoutWithDefault()
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index ef7a347..d227ec5 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -193,7 +193,8 @@
                     mBaseResolveList);
         } else {
             currentResolveList = mUnfilteredResolveList =
-                    mResolverListController.getResolversForIntent(shouldGetResolvedFilter(),
+                    mResolverListController.getResolversForIntent(
+                            /* shouldGetResolvedFilter= */ true,
                             mResolverListCommunicator.shouldGetActivityMetadata(),
                             mIntents);
             if (currentResolveList == null) {
@@ -363,10 +364,6 @@
         }
     }
 
-    public boolean shouldGetResolvedFilter() {
-        return mFilterLastUsed;
-    }
-
     private void addResolveInfoWithAlternates(ResolvedComponentInfo rci) {
         final int count = rci.getCount();
         final Intent intent = rci.getIntentAt(0);
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index abd3eb2..0bfe9eb 100644
--- a/core/java/com/android/internal/app/ResolverListController.java
+++ b/core/java/com/android/internal/app/ResolverListController.java
@@ -133,7 +133,8 @@
         return resolvedComponents;
     }
 
-    UserHandle getUserHandle() {
+    @VisibleForTesting
+    public UserHandle getUserHandle() {
         return mUserHandle;
     }
 
diff --git a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
index d72c52b..567ed74 100644
--- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.app;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.view.LayoutInflater;
 import android.view.ViewGroup;
@@ -81,7 +82,8 @@
     }
 
     @Override
-    ResolverListAdapter getAdapterForIndex(int pageIndex) {
+    @VisibleForTesting
+    public ResolverListAdapter getAdapterForIndex(int pageIndex) {
         return mItems[pageIndex].resolverListAdapter;
     }
 
@@ -106,10 +108,19 @@
     }
 
     @Override
-    ListView getCurrentAdapterView() {
+    ListView getActiveAdapterView() {
         return getListViewForIndex(getCurrentPage());
     }
 
+    @Override
+    @Nullable
+    ViewGroup getInactiveAdapterView() {
+        if (getCount() == 1) {
+            return null;
+        }
+        return getListViewForIndex(1 - getCurrentPage());
+    }
+
     class ResolverProfileDescriptor extends ProfileDescriptor {
         private ResolverListAdapter resolverListAdapter;
         final ListView listView;
diff --git a/core/java/com/android/internal/app/ResolverViewPager.java b/core/java/com/android/internal/app/ResolverViewPager.java
new file mode 100644
index 0000000..8ec94f1
--- /dev/null
+++ b/core/java/com/android/internal/app/ResolverViewPager.java
@@ -0,0 +1,78 @@
+/*
+ * 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.internal.app;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.internal.widget.ViewPager;
+
+/**
+ * A {@link ViewPager} which wraps around its first child's height and has swiping disabled.
+ * <p>Normally {@link ViewPager} instances expand their height to cover all remaining space in
+ * the layout.
+ * <p>This class is used for the intent resolver and share sheet's tabbed view.
+ */
+public class ResolverViewPager extends ViewPager {
+
+    public ResolverViewPager(Context context) {
+        super(context);
+    }
+
+    public ResolverViewPager(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public ResolverViewPager(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public ResolverViewPager(Context context, AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        if (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.AT_MOST) {
+            return;
+        }
+        widthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY);
+        int height = getMeasuredHeight();
+        if (getChildCount() > 0) {
+            View firstChild = getChildAt(0);
+            firstChild.measure(widthMeasureSpec,
+                    MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
+            height = firstChild.getMeasuredHeight();
+        }
+        heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        return false;
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        return false;
+    }
+}
diff --git a/core/java/com/android/internal/app/WrapHeightViewPager.java b/core/java/com/android/internal/app/WrapHeightViewPager.java
deleted file mode 100644
index b017bb4..0000000
--- a/core/java/com/android/internal/app/WrapHeightViewPager.java
+++ /dev/null
@@ -1,71 +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.internal.app;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-
-import com.android.internal.widget.ViewPager;
-
-/**
- * A {@link ViewPager} which wraps around its first child's height.
- * <p>Normally {@link ViewPager} instances expand their height to cover all remaining space in
- * the layout.
- * <p>This class is used for the intent resolver picker's tabbed view to maintain
- * consistency with the previous behavior.
- */
-public class WrapHeightViewPager extends ViewPager {
-
-    public WrapHeightViewPager(Context context) {
-        super(context);
-    }
-
-    public WrapHeightViewPager(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public WrapHeightViewPager(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    public WrapHeightViewPager(Context context, AttributeSet attrs,
-            int defStyleAttr, int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-
-    // TODO(arangelov): When we have multiple pages, the height should wrap to the currently
-    // displayed page. Investigate whether onMeasure is called when changing a page, and instead
-    // of getChildAt(0), use the currently displayed one.
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        if (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.AT_MOST) {
-            return;
-        }
-        widthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY);
-        int height = getMeasuredHeight();
-        if (getChildCount() > 0) {
-            View firstChild = getChildAt(0);
-            firstChild.measure(widthMeasureSpec,
-                    MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
-            height = firstChild.getMeasuredHeight();
-        }
-        heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-    }
-}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 9e23c28..de64573 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -350,7 +350,19 @@
      * (boolean) Whether screenshot flow going to the corner (instead of shown in a notification)
      * is enabled.
      */
-    public static final String SCREENSHOT_CORNER_FLOW = "screenshot_corner_flow";
+    public static final String SCREENSHOT_CORNER_FLOW = "enable_screenshot_corner_flow";
+
+    /**
+     * (boolean) Whether scrolling screenshots are enabled.
+     */
+    public static final String SCREENSHOT_SCROLLING_ENABLED = "enable_screenshot_scrolling";
+
+    // Flags related to Nav Bar
+
+    /**
+     * (boolean) Whether to force the Nav Bar handle to remain opaque.
+     */
+    public static final String NAV_BAR_HANDLE_FORCE_OPAQUE = "nav_bar_handle_force_opaque";
 
     private SystemUiDeviceConfigFlags() {
     }
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index 221cd6d..ef9b3d10 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -561,7 +561,7 @@
                     flags |= Document.FLAG_SUPPORTS_MOVE;
 
                     if (shouldBlockFromTree(docId)) {
-                        flags |= Document.FLAG_DIR_BLOCKS_TREE;
+                        flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE;
                     }
 
                 } else {
diff --git a/core/java/com/android/internal/infra/AndroidFuture.java b/core/java/com/android/internal/infra/AndroidFuture.java
index b250578..9f15d89 100644
--- a/core/java/com/android/internal/infra/AndroidFuture.java
+++ b/core/java/com/android/internal/infra/AndroidFuture.java
@@ -75,6 +75,7 @@
 
     private static final boolean DEBUG = false;
     private static final String LOG_TAG = AndroidFuture.class.getSimpleName();
+    private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0];
 
     private final @NonNull Object mLock = new Object();
     @GuardedBy("mLock")
@@ -95,15 +96,7 @@
             // Done
             if (in.readBoolean()) {
                 // Failed
-                try {
-                    in.readException();
-                } catch (Throwable e) {
-                    completeExceptionally(e);
-                }
-                if (!isCompletedExceptionally()) {
-                    throw new IllegalStateException(
-                            "Error unparceling AndroidFuture: exception expected");
-                }
+                completeExceptionally(unparcelException(in));
             } else {
                 // Success
                 complete((T) in.readValue(null));
@@ -512,14 +505,9 @@
             T result;
             try {
                 result = get();
-            } catch (Exception t) {
-                // Exceptions coming out of get() are wrapped in ExecutionException, which is not
-                // handled by Parcel.
-                if (t instanceof ExecutionException && t.getCause() instanceof Exception) {
-                    t = (Exception) t.getCause();
-                }
+            } catch (Throwable t) {
                 dest.writeBoolean(true);
-                dest.writeException(t);
+                parcelException(dest, unwrapExecutionException(t));
                 return;
             }
             dest.writeBoolean(false);
@@ -528,22 +516,76 @@
             dest.writeStrongBinder(new IAndroidFuture.Stub() {
                 @Override
                 public void complete(AndroidFuture resultContainer) {
+                    boolean changed;
                     try {
-                        AndroidFuture.this.complete((T) resultContainer.get());
+                        changed = AndroidFuture.this.complete((T) resultContainer.get());
                     } catch (Throwable t) {
-                        // If resultContainer was completed exceptionally, get() wraps the
-                        // underlying exception in an ExecutionException. Unwrap it now to avoid
-                        // double-layering ExecutionExceptions.
-                        if (t instanceof ExecutionException && t.getCause() != null) {
-                            t = t.getCause();
-                        }
-                        completeExceptionally(t);
+                        changed = completeExceptionally(unwrapExecutionException(t));
+                    }
+                    if (!changed) {
+                        Log.w(LOG_TAG, "Remote result " + resultContainer
+                                + " ignored, as local future is already completed: "
+                                + AndroidFuture.this);
                     }
                 }
             }.asBinder());
         }
     }
 
+    /**
+     * Exceptions coming out of {@link #get} are wrapped in {@link ExecutionException}
+     */
+    Throwable unwrapExecutionException(Throwable t) {
+        return t instanceof ExecutionException
+                ? t.getCause()
+                : t;
+    }
+
+    /**
+     * Alternative to {@link Parcel#writeException} that stores the stack trace, in a
+     * way consistent with the binder IPC exception propagation behavior.
+     */
+    private static void parcelException(Parcel p, @Nullable Throwable t) {
+        p.writeBoolean(t == null);
+        if (t == null) {
+            return;
+        }
+
+        p.writeInt(Parcel.getExceptionCode(t));
+        p.writeString(t.getClass().getName());
+        p.writeString(t.getMessage());
+        p.writeStackTrace(t);
+        parcelException(p, t.getCause());
+    }
+
+    /**
+     * @see #parcelException
+     */
+    private static @Nullable Throwable unparcelException(Parcel p) {
+        if (p.readBoolean()) {
+            return null;
+        }
+
+        int exCode = p.readInt();
+        String cls = p.readString();
+        String msg = p.readString();
+        String stackTrace = p.readInt() > 0 ? p.readString() : "\t<stack trace unavailable>";
+        msg += "\n" + stackTrace;
+
+        Exception ex = p.createExceptionOrNull(exCode, msg);
+        if (ex == null) {
+            ex = new RuntimeException(cls + ": " + msg);
+        }
+        ex.setStackTrace(EMPTY_STACK_TRACE);
+
+        Throwable cause = unparcelException(p);
+        if (cause != null) {
+            ex.initCause(ex);
+        }
+
+        return ex;
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
index e27ff00..20cd7c2 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
@@ -41,5 +41,5 @@
     boolean shouldOfferSwitchingToNextInputMethod();
     void notifyUserAction();
     void reportPreRendered(in EditorInfo info);
-    void applyImeVisibility(boolean setVisible);
+    void applyImeVisibility(IBinder showInputToken, boolean setVisible);
 }
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index d42c607..9eeef96 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -23,6 +23,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
+import android.view.View;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
@@ -368,18 +369,20 @@
     }
 
     /**
-     * Calls {@link IInputMethodPrivilegedOperations#applyImeVisibility(boolean)}.
+     * Calls {@link IInputMethodPrivilegedOperations#applyImeVisibility(IBinder, boolean)}.
      *
+     * @param showInputToken dummy token that maps to window requesting
+     *        {@link android.view.inputmethod.InputMethodManager#showSoftInput(View, int)}
      * @param setVisible {@code true} to set IME visible, else hidden.
      */
     @AnyThread
-    public void applyImeVisibility(boolean setVisible) {
+    public void applyImeVisibility(IBinder showInputToken, boolean setVisible) {
         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
         if (ops == null) {
             return;
         }
         try {
-            ops.applyImeVisibility(setVisible);
+            ops.applyImeVisibility(showInputToken, setVisible);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/com/android/internal/logging/InstanceId.java b/core/java/com/android/internal/logging/InstanceId.java
new file mode 100644
index 0000000..85dbac3
--- /dev/null
+++ b/core/java/com/android/internal/logging/InstanceId.java
@@ -0,0 +1,50 @@
+/*
+ * 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.logging;
+
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * An opaque identifier used to disambiguate which logs refer to a particular instance of some
+ * UI element. Useful when there might be multiple instances simultaneously active.
+ * Obtain from InstanceIdSequence.
+ */
+public class InstanceId {
+    private int mId;
+    protected InstanceId(int id) {
+        mId = id;
+    }
+    @VisibleForTesting
+    public int getId() {
+        return mId;
+    }
+
+    @Override
+    public int hashCode() {
+        return mId;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (!(obj instanceof InstanceId)) {
+            return false;
+        }
+        return mId == ((InstanceId) obj).mId;
+    }
+}
diff --git a/core/java/com/android/internal/logging/InstanceIdSequence.java b/core/java/com/android/internal/logging/InstanceIdSequence.java
new file mode 100644
index 0000000..2e78ed8
--- /dev/null
+++ b/core/java/com/android/internal/logging/InstanceIdSequence.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.logging;
+
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+
+import java.security.SecureRandom;
+import java.util.Random;
+
+/**
+ * Generates random InstanceIds in range [0, instanceIdMax) for passing to
+ * UiEventLogger.logWithInstanceId(). Holds a SecureRandom, which self-seeds on
+ * first use; try to give it a long lifetime. Safe for concurrent use.
+ */
+public class InstanceIdSequence {
+    // At most 20 bits: ~1m possibilities, ~0.5% probability of collision in 100 values
+    private static final int INSTANCE_ID_MAX = 1 << 20;
+    protected final int mInstanceIdMax;
+    private final Random mRandom = new SecureRandom();
+
+    /**
+     * Constructs a sequence with identifiers [0, instanceIdMax).  Capped at INSTANCE_ID_MAX.
+     * @param instanceIdMax Limiting value of identifiers. Normally positive: otherwise you get
+     *                      an all-zero sequence.
+     */
+    public InstanceIdSequence(int instanceIdMax) {
+        mInstanceIdMax = min(max(0, instanceIdMax), INSTANCE_ID_MAX);
+    }
+
+    /**
+     * Gets the next instance from the sequence.  Safe for concurrent use.
+     * @return new InstanceId
+     */
+    public InstanceId newInstanceId() {
+        return new InstanceId(mRandom.nextInt(mInstanceIdMax));
+    }
+}
diff --git a/core/java/com/android/internal/logging/UiEventLogger.java b/core/java/com/android/internal/logging/UiEventLogger.java
index cca97f6..48d2bc2 100644
--- a/core/java/com/android/internal/logging/UiEventLogger.java
+++ b/core/java/com/android/internal/logging/UiEventLogger.java
@@ -16,6 +16,9 @@
 
 package com.android.internal.logging;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 /**
  * Logging interface for UI events. Normal implementation is UiEventLoggerImpl.
  * For testing, use fake implementation UiEventLoggerFake.
@@ -26,13 +29,35 @@
     /** Put your Event IDs in enums that implement this interface, and document them using the
      * UiEventEnum annotation.
      * Event IDs must be globally unique. This will be enforced by tooling (forthcoming).
-     * OEMs should use event IDs above 100000.
+     * OEMs should use event IDs above 100000 and below 1000000 (1 million).
      */
     interface UiEventEnum {
         int getId();
     }
+
     /**
-     * Log a simple event, with no package or instance ID.
+     * Log a simple event, with no package information. Does nothing if event.getId() <= 0.
+     * @param event an enum implementing UiEventEnum interface.
      */
-    void log(UiEventEnum eventID);
+    void log(@NonNull UiEventEnum event);
+
+    /**
+     * Log an event with package information. Does nothing if event.getId() <= 0.
+     * Give both uid and packageName if both are known, but one may be omitted if unknown.
+     * @param event an enum implementing UiEventEnum interface.
+     * @param uid the uid of the relevant app, if known (0 otherwise).
+     * @param packageName the package name of the relevant app, if known (null otherwise).
+     */
+    void log(@NonNull UiEventEnum event, int uid, @Nullable String packageName);
+
+    /**
+     * Log an event with package information and an instance ID.
+     * Does nothing if event.getId() <= 0.
+     * @param event an enum implementing UiEventEnum interface.
+     * @param uid the uid of the relevant app, if known (0 otherwise).
+     * @param packageName the package name of the relevant app, if known (null otherwise).
+     * @param instance An identifier obtained from an InstanceIdSequence.
+     */
+    void logWithInstanceId(@NonNull UiEventEnum event, int uid, @Nullable String packageName,
+            @NonNull InstanceId instance);
 }
diff --git a/core/java/com/android/internal/logging/UiEventLoggerImpl.java b/core/java/com/android/internal/logging/UiEventLoggerImpl.java
index e64fba2..fe758a8 100644
--- a/core/java/com/android/internal/logging/UiEventLoggerImpl.java
+++ b/core/java/com/android/internal/logging/UiEventLoggerImpl.java
@@ -24,14 +24,26 @@
  * See UiEventReported atom in atoms.proto for more context.
  */
 public class UiEventLoggerImpl implements UiEventLogger {
-    /**
-     * Log a simple event, with no package or instance ID.
-     */
     @Override
     public void log(UiEventEnum event) {
+        log(event, 0, null);
+    }
+
+    @Override
+    public void log(UiEventEnum event, int uid, String packageName) {
         final int eventID = event.getId();
         if (eventID > 0) {
-            StatsLog.write(StatsLog.UI_EVENT_REPORTED, eventID, 0, null);
+            StatsLog.write(StatsLog.UI_EVENT_REPORTED, eventID, uid, packageName);
+        }
+    }
+
+    @Override
+    public void logWithInstanceId(UiEventEnum event, int uid, String packageName,
+            InstanceId instance) {
+        final int eventID = event.getId();
+        if (eventID > 0) {
+            StatsLog.write(StatsLog.UI_EVENT_REPORTED, eventID, uid, packageName,
+                    instance.getId());
         }
     }
 }
diff --git a/core/java/com/android/internal/logging/testing/InstanceIdSequenceFake.java b/core/java/com/android/internal/logging/testing/InstanceIdSequenceFake.java
new file mode 100644
index 0000000..0fd40b9
--- /dev/null
+++ b/core/java/com/android/internal/logging/testing/InstanceIdSequenceFake.java
@@ -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.
+ */
+
+package com.android.internal.logging.testing;
+
+import android.annotation.SuppressLint;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.InstanceIdSequence;
+
+/**
+ * A fake implementation of InstanceIdSequence that returns 0, 1, 2, ...
+ */
+public class InstanceIdSequenceFake extends InstanceIdSequence {
+
+    public InstanceIdSequenceFake(int instanceIdMax) {
+        super(instanceIdMax);
+    }
+
+    /**
+     * Extend InstanceId to add a constructor we can call, strictly for testing purposes.
+     * Public so that tests can check whether the InstanceIds they see are fake.
+     */
+    public static class InstanceIdFake extends InstanceId {
+        @SuppressLint("VisibleForTests")  // This is test infrastructure, which ought to count
+        InstanceIdFake(int id) {
+            super(id);
+        }
+    }
+
+    private int mNextId = 0;
+
+    @Override
+    public InstanceId newInstanceId() {
+        synchronized (this) {
+            ++mNextId;
+            if (mNextId >= mInstanceIdMax) {
+                mNextId = 0;
+            }
+            return new InstanceIdFake(mNextId);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
index 92e9bbb..130ee64 100644
--- a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
+++ b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.logging.testing;
 
+import com.android.internal.logging.InstanceId;
 import com.android.internal.logging.UiEventLogger;
 
 import java.util.LinkedList;
@@ -30,29 +31,52 @@
     /**
      * Immutable data class used to record fake log events.
      */
-    public class FakeUiEvent {
+    public static class FakeUiEvent {
         public final int eventId;
         public final int uid;
         public final String packageName;
+        public final InstanceId instanceId;  // Used only for WithInstanceId variant
 
-        public FakeUiEvent(int eventId, int uid, String packageName) {
+        FakeUiEvent(int eventId, int uid, String packageName) {
             this.eventId = eventId;
             this.uid = uid;
             this.packageName = packageName;
+            this.instanceId = null;
+        }
+
+        FakeUiEvent(int eventId, int uid, String packageName, InstanceId instanceId) {
+            this.eventId = eventId;
+            this.uid = uid;
+            this.packageName = packageName;
+            this.instanceId = instanceId;
         }
     }
 
-    private Queue<FakeUiEvent> mLogs = new LinkedList<FakeUiEvent>();
-
-    @Override
-    public void log(UiEventEnum event) {
-        final int eventId = event.getId();
-        if (eventId > 0) {
-            mLogs.offer(new FakeUiEvent(eventId, 0, null));
-        }
-    }
+    private Queue<FakeUiEvent> mLogs = new LinkedList<>();
 
     public Queue<FakeUiEvent> getLogs() {
         return mLogs;
     }
+
+    @Override
+    public void log(UiEventEnum event) {
+        log(event, 0, null);
+    }
+
+    @Override
+    public void log(UiEventEnum event, int uid, String packageName) {
+        final int eventId = event.getId();
+        if (eventId > 0) {
+            mLogs.offer(new FakeUiEvent(eventId, uid, packageName));
+        }
+    }
+
+    @Override
+    public void logWithInstanceId(UiEventLogger.UiEventEnum event, int uid, String packageName,
+            InstanceId instance) {
+        final int eventId = event.getId();
+        if (eventId > 0) {
+            mLogs.offer(new FakeUiEvent(eventId, uid, packageName, instance));
+        }
+    }
 }
diff --git a/core/java/com/android/internal/os/ProcessCpuTracker.java b/core/java/com/android/internal/os/ProcessCpuTracker.java
index 5e3c5df..7a9b659 100644
--- a/core/java/com/android/internal/os/ProcessCpuTracker.java
+++ b/core/java/com/android/internal/os/ProcessCpuTracker.java
@@ -753,6 +753,8 @@
         proto.write(CpuUsageProto.Load.LOAD15, mLoad15);
         proto.end(currentLoadToken);
 
+        buildWorkingProcs();
+
         proto.write(CpuUsageProto.NOW, now);
         proto.write(CpuUsageProto.LAST_SAMPLE_TIME, mLastSampleTime);
         proto.write(CpuUsageProto.CURRENT_SAMPLE_TIME, mCurrentSampleTime);
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 179828c..13d0c5c 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -35,6 +35,7 @@
 import com.android.server.NetworkManagementSocketTagger;
 
 import dalvik.system.RuntimeHooks;
+import dalvik.system.ThreadPrioritySetter;
 import dalvik.system.VMRuntime;
 
 import libcore.content.type.MimeMap;
@@ -204,6 +205,7 @@
      */
     public static void preForkInit() {
         if (DEBUG) Slog.d(TAG, "Entered preForkInit.");
+        RuntimeHooks.setThreadPrioritySetter(new RuntimeThreadPrioritySetter());
         RuntimeInit.enableDdms();
         // TODO(b/142019040#comment13): Decide whether to load the default instance eagerly, i.e.
         // MimeMap.setDefault(DefaultMimeMapFactory.create());
@@ -216,6 +218,35 @@
         MimeMap.setDefaultSupplier(DefaultMimeMapFactory::create);
     }
 
+    private static class RuntimeThreadPrioritySetter implements ThreadPrioritySetter {
+        // Should remain consistent with kNiceValues[] in system/libartpalette/palette_android.cc
+        private static final int[] NICE_VALUES = {
+            Process.THREAD_PRIORITY_LOWEST,  // 1 (MIN_PRIORITY)
+            Process.THREAD_PRIORITY_BACKGROUND + 6,
+            Process.THREAD_PRIORITY_BACKGROUND + 3,
+            Process.THREAD_PRIORITY_BACKGROUND,
+            Process.THREAD_PRIORITY_DEFAULT,  // 5 (NORM_PRIORITY)
+            Process.THREAD_PRIORITY_DEFAULT - 2,
+            Process.THREAD_PRIORITY_DEFAULT - 4,
+            Process.THREAD_PRIORITY_URGENT_DISPLAY + 3,
+            Process.THREAD_PRIORITY_URGENT_DISPLAY + 2,
+            Process.THREAD_PRIORITY_URGENT_DISPLAY  // 10 (MAX_PRIORITY)
+        };
+
+        @Override
+        public void setPriority(int nativeTid, int priority) {
+            // Check NICE_VALUES[] length first.
+            if (NICE_VALUES.length != (1 + Thread.MAX_PRIORITY - Thread.MIN_PRIORITY)) {
+                throw new AssertionError("Unexpected NICE_VALUES.length=" + NICE_VALUES.length);
+            }
+            // Priority should be in the range of MIN_PRIORITY (1) to MAX_PRIORITY (10).
+            if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
+                throw new IllegalArgumentException("Priority out of range: " + priority);
+            }
+            Process.setThreadPriority(nativeTid, NICE_VALUES[priority - Thread.MIN_PRIORITY]);
+        }
+    }
+
     @UnsupportedAppUsage
     protected static final void commonInit() {
         if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 2248b88..e2985566 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -35,6 +35,7 @@
 import android.system.Os;
 import android.util.Log;
 
+import dalvik.annotation.optimization.FastNative;
 import dalvik.system.ZygoteHooks;
 
 import libcore.io.IoUtils;
@@ -145,6 +146,11 @@
     /** The lower file system should be bind mounted directly on external storage */
     public static final int MOUNT_EXTERNAL_PASS_THROUGH = IVold.REMOUNT_MODE_PASS_THROUGH;
 
+    /** Use the regular scoped storage filesystem, but Android/ should be writable.
+     * Used to support the applications hosting DownloadManager and the MTP server.
+     */
+    public static final int MOUNT_EXTERNAL_ANDROID_WRITABLE = IVold.REMOUNT_MODE_ANDROID_WRITABLE;
+
     /** Number of bytes sent to the Zygote over USAP pipes or the pool event FD */
     static final int USAP_MANAGEMENT_MESSAGE_BYTES = 8;
 
@@ -1013,4 +1019,19 @@
             command.append(" '").append(arg.replace("'", "'\\''")).append("'");
         }
     }
+
+    /**
+     * Parse the given unsolicited zygote message as type SIGCHLD,
+     * extract the payload information into the given output buffer.
+     *
+     * @param in The unsolicited zygote message to be parsed
+     * @param length The number of bytes in the message
+     * @param out The output buffer where the payload information will be placed
+     * @return Number of elements being place into output buffer, or -1 if
+     *         either the message is malformed or not the type as expected here.
+     *
+     * @hide
+     */
+    @FastNative
+    public static native int nativeParseSigChld(byte[] in, int length, int[] out);
 }
diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java
index d349954..37f570b 100644
--- a/core/java/com/android/internal/os/ZygoteArguments.java
+++ b/core/java/com/android/internal/os/ZygoteArguments.java
@@ -376,6 +376,8 @@
                 mMountExternal = Zygote.MOUNT_EXTERNAL_LEGACY;
             } else if (arg.equals("--mount-external-pass-through")) {
                 mMountExternal = Zygote.MOUNT_EXTERNAL_PASS_THROUGH;
+            } else if (arg.equals("--mount-external-android-writable")) {
+                mMountExternal = Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE;
             } else if (arg.equals("--query-abi-list")) {
                 mAbiListQuery = true;
             } else if (arg.equals("--get-pid")) {
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 0170978..8412b84 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -68,6 +68,7 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.InsetDrawable;
 import android.graphics.drawable.LayerDrawable;
+import android.os.Build.VERSION_CODES;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.Pair;
@@ -282,6 +283,11 @@
     private Insets mLastBackgroundInsets = Insets.NONE;
     private boolean mDrawLegacyNavigationBarBackground;
 
+    /**
+     * Whether the app targets an SDK that uses the new insets APIs.
+     */
+    private boolean mUseNewInsetsApi;
+
     DecorView(Context context, int featureId, PhoneWindow window,
             WindowManager.LayoutParams params) {
         super(context);
@@ -311,6 +317,7 @@
         initResizingPaints();
 
         mLegacyNavigationBarBackgroundPaint.setColor(Color.BLACK);
+        mUseNewInsetsApi = context.getApplicationInfo().targetSdkVersion >= VERSION_CODES.R;
     }
 
     void setBackgroundFallback(@Nullable Drawable fallbackDrawable) {
@@ -1174,20 +1181,24 @@
         // Note: We don't need to check for IN_SCREEN or INSET_DECOR because unlike the status bar,
         // these flags wouldn't make the window draw behind the navigation bar, unless
         // LAYOUT_HIDE_NAVIGATION was set.
+        //
+        // Note: Once the app targets R+, we no longer do this logic because we can't rely on
+        // SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION to indicate whether the app wants to handle it by
+        // themselves.
         boolean hideNavigation = (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0
                 || !(state == null || state.getSource(ITYPE_NAVIGATION_BAR).isVisible());
         boolean forceConsumingNavBar = (mForceWindowDrawsBarBackgrounds
                         && (attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0
                         && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
-                        && (attrs.getFitWindowInsetsTypes() & Type.navigationBars()) != 0
                         && !hideNavigation)
                 || (mLastShouldAlwaysConsumeSystemBars && hideNavigation);
 
         boolean consumingNavBar =
                 ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
                         && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
-                        && (attrs.getFitWindowInsetsTypes() & Type.navigationBars()) != 0
-                        && !hideNavigation)
+                        && !hideNavigation
+                        // TODO IME wrap_content windows need to have margin to work properly
+                        && (!mUseNewInsetsApi || isImeWindow))
                 || forceConsumingNavBar;
 
         // If we didn't request fullscreen layout, but we still got it because of the
@@ -1200,16 +1211,14 @@
         boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0
                 && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0
                 && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0
-                && (attrs.getFitWindowInsetsTypes() & Type.statusBars()) != 0
                 && mForceWindowDrawsBarBackgrounds
                 && mLastTopInset != 0
                 || (mLastShouldAlwaysConsumeSystemBars && fullscreen);
 
-        int sides = attrs.getFitWindowInsetsSides();
-        int consumedTop = consumingStatusBar && (sides & Side.TOP) != 0 ? mLastTopInset : 0;
-        int consumedRight = consumingNavBar && (sides & Side.RIGHT) != 0 ? mLastRightInset : 0;
-        int consumedBottom = consumingNavBar && (sides & Side.BOTTOM) != 0 ? mLastBottomInset : 0;
-        int consumedLeft = consumingNavBar && (sides & Side.LEFT) != 0 ? mLastLeftInset : 0;
+        int consumedTop = consumingStatusBar ? mLastTopInset : 0;
+        int consumedRight = consumingNavBar ? mLastRightInset : 0;
+        int consumedBottom = consumingNavBar ? mLastBottomInset : 0;
+        int consumedLeft = consumingNavBar ? mLastLeftInset : 0;
 
         if (mContentRoot != null
                 && mContentRoot.getLayoutParams() instanceof MarginLayoutParams) {
@@ -1240,9 +1249,6 @@
         }
         updateBackgroundDrawable();
 
-        if (insets != null) {
-            insets = insets.consumeStableInsets();
-        }
         return insets;
     }
 
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 88a9cb0..95558c3 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -17,8 +17,11 @@
 package com.android.internal.policy;
 
 import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES;
+import static android.view.View.SYSTEM_UI_LAYOUT_FLAGS;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.systemBars;
 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
@@ -27,9 +30,11 @@
 import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
-import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
@@ -43,6 +48,7 @@
 import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
 import android.graphics.Color;
+import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
@@ -66,6 +72,7 @@
 import android.util.AndroidRuntimeException;
 import android.util.EventLog;
 import android.util.Log;
+import android.util.Pair;
 import android.util.SparseArray;
 import android.util.TypedValue;
 import android.view.ContextThemeWrapper;
@@ -307,6 +314,8 @@
     /** @see ViewRootImpl#mActivityConfigCallback */
     private ActivityConfigCallback mActivityConfigCallback;
 
+    private OnContentApplyWindowInsetsListener mPendingOnContentApplyWindowInsetsListener;
+
     static class WindowManagerHolder {
         static final IWindowManager sWindowManager = IWindowManager.Stub.asInterface(
                 ServiceManager.getService("window"));
@@ -2092,6 +2101,34 @@
     /** Notify when decor view is attached to window and {@link ViewRootImpl} is available. */
     void onViewRootImplSet(ViewRootImpl viewRoot) {
         viewRoot.setActivityConfigCallback(mActivityConfigCallback);
+        if (mPendingOnContentApplyWindowInsetsListener != null) {
+            viewRoot.setOnContentApplyWindowInsetsListener(
+                    mPendingOnContentApplyWindowInsetsListener);
+            mPendingOnContentApplyWindowInsetsListener = null;
+        } else {
+            viewRoot.setOnContentApplyWindowInsetsListener(
+                    createDefaultContentWindowInsetsListener());
+        }
+    }
+
+    private OnContentApplyWindowInsetsListener createDefaultContentWindowInsetsListener() {
+        return insets -> {
+            if ((getDecorView().getWindowSystemUiVisibility() & SYSTEM_UI_LAYOUT_FLAGS) != 0) {
+                return new Pair<>(Insets.NONE, insets);
+            }
+
+            boolean includeIme = (getAttributes().softInputMode & SOFT_INPUT_MASK_ADJUST)
+                    == SOFT_INPUT_ADJUST_RESIZE;
+            Insets insetsToApply;
+            if (ViewRootImpl.sNewInsetsMode == 0) {
+                insetsToApply = insets.getSystemWindowInsets();
+            } else {
+                insetsToApply = insets.getInsets(systemBars() | (includeIme ? ime() : 0));
+            }
+            insets = insets.inset(insetsToApply);
+            return new Pair<>(insetsToApply,
+                    insets.inset(insetsToApply).consumeSystemWindowInsets());
+        };
     }
 
     static private final String FOCUSED_ID_TAG = "android:focusedViewId";
@@ -2344,6 +2381,8 @@
             setFlags(0, flagsToUpdate);
         } else {
             setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
+            getAttributes().setFitInsetsSides(0);
+            getAttributes().setFitInsetsTypes(0);
         }
 
         if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
@@ -2466,7 +2505,7 @@
         if (a.hasValue(R.styleable.Window_windowLayoutInDisplayCutoutMode)) {
             int mode = a.getInt(R.styleable.Window_windowLayoutInDisplayCutoutMode, -1);
             if (mode < LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
-                    || mode > LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER) {
+                    || mode > LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
                 throw new UnsupportedOperationException("Unknown windowLayoutInDisplayCutoutMode: "
                         + a.getString(R.styleable.Window_windowLayoutInDisplayCutoutMode));
             }
@@ -2656,7 +2695,7 @@
             mContentParent = generateLayout(mDecor);
 
             // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
-            mDecor.makeOptionalFitsSystemWindows();
+            mDecor.makeFrameworkOptionalFitsSystemWindows();
 
             final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                     R.id.decor_content_parent);
@@ -3853,4 +3892,19 @@
     public List<Rect> getSystemGestureExclusionRects() {
         return getViewRootImpl().getRootSystemGestureExclusionRects();
     }
+
+    @Override
+    public void setOnContentApplyWindowInsetsListener(OnContentApplyWindowInsetsListener listener) {
+        ViewRootImpl impl = getViewRootImpl();
+        if (impl != null) {
+            impl.setOnContentApplyWindowInsetsListener(listener);
+        } else {
+            mPendingOnContentApplyWindowInsetsListener = listener;
+        }
+    }
+
+    @Override
+    public void resetOnContentApplyWindowInsetsListener() {
+        setOnContentApplyWindowInsetsListener(createDefaultContentWindowInsetsListener());
+    }
 }
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 6fd271c..0f50596f 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony;
 
+import android.telephony.BarringInfo;
 import android.telephony.CallAttributes;
 import android.telephony.CellIdentity;
 import android.telephony.CellInfo;
@@ -64,4 +65,5 @@
     void onImsCallDisconnectCauseChanged(in ImsReasonInfo imsReasonInfo);
     void onRegistrationFailed(in CellIdentity cellIdentity,
              String chosenPlmn, int domain, int causeCode, int additionalCauseCode);
+    void onBarringInfoChanged(in BarringInfo barringInfo);
 }
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 8e97ae1..47752c5 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -19,6 +19,7 @@
 import android.content.Intent;
 import android.net.LinkProperties;
 import android.net.NetworkCapabilities;
+import android.telephony.BarringInfo;
 import android.telephony.CallQuality;
 import android.telephony.CellIdentity;
 import android.telephony.CellInfo;
@@ -99,4 +100,5 @@
     void notifyImsDisconnectCause(int subId, in ImsReasonInfo imsReasonInfo);
     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);
 }
diff --git a/core/java/com/android/internal/telephony/IWapPushManager.aidl b/core/java/com/android/internal/telephony/IWapPushManager.aidl
new file mode 100644
index 0000000..9f6851b
--- /dev/null
+++ b/core/java/com/android/internal/telephony/IWapPushManager.aidl
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2010 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.telephony;
+
+import android.content.Intent;
+
+/** @hide */
+interface IWapPushManager {
+    /**
+     * Processes WAP push message and triggers the receiver application registered
+     * in the application ID table.
+     */
+    int processMessage(String app_id, String content_type, in Intent intent);
+
+    /**
+     * Adds receiver application into the application ID table.
+     * Returns true if inserting the information is successful. Inserting duplicated
+     * record in the application ID table is not allowed. Use update/delete method.
+     */
+    boolean addPackage(String x_app_id, String content_type,
+            String package_name, String class_name,
+            int app_type, boolean need_signature, boolean further_processing);
+
+    /**
+     * Updates receiver application that is last added.
+     * Returns true if updating the information is successfull.
+     */
+    boolean updatePackage(String x_app_id, String content_type,
+            String package_name, String class_name,
+            int app_type, boolean need_signature, boolean further_processing);
+
+    /**
+     * Deletes receiver application information.
+     * Returns true if deleting is successfull.
+     */
+    boolean deletePackage(String x_app_id, String content_type,
+            String package_name, String class_name);
+}
diff --git a/core/java/com/android/internal/telephony/WapPushManagerParams.java b/core/java/com/android/internal/telephony/WapPushManagerParams.java
new file mode 100644
index 0000000..eafb8f1
--- /dev/null
+++ b/core/java/com/android/internal/telephony/WapPushManagerParams.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2010 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.telephony;
+
+import android.telephony.WapPushManagerConnector;
+
+/**
+ * WapPushManager constant value definitions.
+ * @hide
+ */
+public class WapPushManagerParams {
+    /**
+     * Application type activity
+     */
+    public static final int APP_TYPE_ACTIVITY = 0;
+
+    /**
+     * Application type service
+     */
+    public static final int APP_TYPE_SERVICE = 1;
+
+    /**
+     * Process Message return value
+     * Message is handled
+     */
+    public static final int MESSAGE_HANDLED = WapPushManagerConnector.RESULT_MESSAGE_HANDLED;
+
+    /**
+     * Process Message return value
+     * Application ID or content type was not found in the application ID table
+     */
+    public static final int APP_QUERY_FAILED = WapPushManagerConnector.RESULT_APP_QUERY_FAILED;
+
+    /**
+     * Process Message return value
+     * Receiver application signature check failed
+     */
+    public static final int SIGNATURE_NO_MATCH = WapPushManagerConnector.RESULT_SIGNATURE_NO_MATCH;
+
+    /**
+     * Process Message return value
+     * Receiver application was not found
+     */
+    public static final int INVALID_RECEIVER_NAME =
+            WapPushManagerConnector.RESULT_INVALID_RECEIVER_NAME;
+
+    /**
+     * Process Message return value
+     * Unknown exception
+     */
+    public static final int EXCEPTION_CAUGHT = WapPushManagerConnector.RESULT_EXCEPTION_CAUGHT;
+
+    /**
+     * Process Message return value
+     * Need further processing after WapPushManager message processing
+     */
+    public static final int FURTHER_PROCESSING = WapPushManagerConnector.RESULT_FURTHER_PROCESSING;
+}
diff --git a/core/java/com/android/internal/util/ConnectivityUtil.java b/core/java/com/android/internal/util/ConnectivityUtil.java
new file mode 100644
index 0000000..799352b
--- /dev/null
+++ b/core/java/com/android/internal/util/ConnectivityUtil.java
@@ -0,0 +1,201 @@
+/*
+ * 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.util;
+
+import android.Manifest;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.location.LocationManager;
+import android.os.Binder;
+import android.os.Build;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+
+/**
+ * Utility methods for common functionality using by different networks.
+ *
+ * @hide
+ */
+public class ConnectivityUtil {
+
+    private static final String TAG = "ConnectivityUtil";
+
+    private final Context mContext;
+    private final AppOpsManager mAppOps;
+    private final UserManager mUserManager;
+
+    public ConnectivityUtil(Context context) {
+        mContext = context;
+        mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+    }
+
+    /**
+     * API to determine if the caller has fine/coarse location permission (depending on
+     * config/targetSDK level) and the location mode is enabled for the user. SecurityException is
+     * thrown if the caller has no permission or the location mode is disabled.
+     * @param pkgName package name of the application requesting access
+     * @param featureId The feature in the package
+     * @param uid The uid of the package
+     * @param message A message describing why the permission was checked. Only needed if this is
+     *                not inside of a two-way binder call from the data receiver
+     */
+    public void enforceLocationPermission(String pkgName, @Nullable String featureId, int uid,
+            @Nullable String message)
+            throws SecurityException {
+        checkPackage(uid, pkgName);
+
+        // Location mode must be enabled
+        if (!isLocationModeEnabled()) {
+            // Location mode is disabled, scan results cannot be returned
+            throw new SecurityException("Location mode is disabled for the device");
+        }
+
+        // LocationAccess by App: caller must have Coarse/Fine Location permission to have access to
+        // location information.
+        boolean canAppPackageUseLocation = checkCallersLocationPermission(pkgName, featureId,
+                uid, /* coarseForTargetSdkLessThanQ */ true, message);
+
+        // If neither caller or app has location access, there is no need to check
+        // any other permissions. Deny access to scan results.
+        if (!canAppPackageUseLocation) {
+            throw new SecurityException("UID " + uid + " has no location permission");
+        }
+        // If the User or profile is current, permission is granted
+        // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission.
+        if (!isCurrentProfile(uid) && !checkInteractAcrossUsersFull(uid)) {
+            throw new SecurityException("UID " + uid + " profile not permitted");
+        }
+    }
+
+    /**
+     * Checks that calling process has android.Manifest.permission.ACCESS_FINE_LOCATION or
+     * android.Manifest.permission.ACCESS_COARSE_LOCATION (depending on config/targetSDK level)
+     * and a corresponding app op is allowed for this package and uid.
+     *
+     * @param pkgName PackageName of the application requesting access
+     * @param featureId The feature in the package
+     * @param uid The uid of the package
+     * @param coarseForTargetSdkLessThanQ If true and the targetSDK < Q then will check for COARSE
+     *                                    else (false or targetSDK >= Q) then will check for FINE
+     * @param message A message describing why the permission was checked. Only needed if this is
+     *                not inside of a two-way binder call from the data receiver
+     */
+    public boolean checkCallersLocationPermission(String pkgName, @Nullable String featureId,
+            int uid, boolean coarseForTargetSdkLessThanQ, @Nullable String message) {
+        boolean isTargetSdkLessThanQ = isTargetSdkLessThan(pkgName, Build.VERSION_CODES.Q, uid);
+
+        String permissionType = Manifest.permission.ACCESS_FINE_LOCATION;
+        if (coarseForTargetSdkLessThanQ && isTargetSdkLessThanQ) {
+            // Having FINE permission implies having COARSE permission (but not the reverse)
+            permissionType = Manifest.permission.ACCESS_COARSE_LOCATION;
+        }
+        if (getUidPermission(permissionType, uid)
+                == PackageManager.PERMISSION_DENIED) {
+            return false;
+        }
+
+        // Always checking FINE - even if will not enforce. This will record the request for FINE
+        // so that a location request by the app is surfaced to the user.
+        boolean isFineLocationAllowed = noteAppOpAllowed(
+                AppOpsManager.OPSTR_FINE_LOCATION, pkgName, featureId, uid, message);
+        if (isFineLocationAllowed) {
+            return true;
+        }
+        if (coarseForTargetSdkLessThanQ && isTargetSdkLessThanQ) {
+            return noteAppOpAllowed(AppOpsManager.OPSTR_COARSE_LOCATION, pkgName, featureId, uid,
+                    message);
+        }
+        return false;
+    }
+
+    /**
+     * Retrieves a handle to LocationManager (if not already done) and check if location is enabled.
+     */
+    public boolean isLocationModeEnabled() {
+        LocationManager locationManager =
+                (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
+        try {
+            return locationManager.isLocationEnabledForUser(UserHandle.of(
+                    getCurrentUser()));
+        } catch (Exception e) {
+            Log.e(TAG, "Failure to get location mode via API, falling back to settings", e);
+            return false;
+        }
+    }
+
+    private boolean isTargetSdkLessThan(String packageName, int versionCode, int callingUid) {
+        long ident = Binder.clearCallingIdentity();
+        try {
+            if (mContext.getPackageManager().getApplicationInfoAsUser(
+                    packageName, 0,
+                    UserHandle.getUserHandleForUid(callingUid)).targetSdkVersion
+                    < versionCode) {
+                return true;
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            // In case of exception, assume unknown app (more strict checking)
+            // Note: This case will never happen since checkPackage is
+            // called to verify validity before checking App's version.
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+        return false;
+    }
+
+    private boolean noteAppOpAllowed(String op, String pkgName, @Nullable String featureId,
+            int uid, @Nullable String message) {
+        return mAppOps.noteOp(op, uid, pkgName, featureId, message) == AppOpsManager.MODE_ALLOWED;
+    }
+
+    private void checkPackage(int uid, String pkgName) throws SecurityException {
+        if (pkgName == null) {
+            throw new SecurityException("Checking UID " + uid + " but Package Name is Null");
+        }
+        mAppOps.checkPackage(uid, pkgName);
+    }
+
+    private boolean isCurrentProfile(int uid) {
+        UserHandle currentUser = UserHandle.of(getCurrentUser());
+        UserHandle callingUser = UserHandle.getUserHandleForUid(uid);
+        return currentUser.equals(callingUser)
+                || mUserManager.isSameProfileGroup(currentUser, callingUser);
+    }
+
+    private boolean checkInteractAcrossUsersFull(int uid) {
+        return getUidPermission(
+                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, uid)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    @VisibleForTesting
+    protected int getCurrentUser() {
+        return ActivityManager.getCurrentUser();
+    }
+
+    private int getUidPermission(String permissionType, int uid) {
+        // We don't care about pid, pass in -1
+        return mContext.checkPermission(permissionType, -1, uid);
+    }
+}
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index 27c2478..67cfc3a 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -121,7 +121,11 @@
     }
 
     public static boolean isEnabled(Context ctx) {
-        return Build.IS_DEBUGGABLE && getInstance(ctx).mEnabled;
+        return getInstance(ctx).isEnabled();
+    }
+
+    public boolean isEnabled() {
+        return Build.IS_DEBUGGABLE && mEnabled;
     }
 
     /**
diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java
index 5bc96d8..408a7a8 100644
--- a/core/java/com/android/internal/util/Preconditions.java
+++ b/core/java/com/android/internal/util/Preconditions.java
@@ -126,7 +126,9 @@
      * @param reference an object reference
      * @return the non-null reference that was validated
      * @throws NullPointerException if {@code reference} is null
+     * @deprecated - use {@link java.util.Objects.requireNonNull} instead.
      */
+    @Deprecated
     @UnsupportedAppUsage
     public static @NonNull <T> T checkNotNull(final T reference) {
         if (reference == null) {
@@ -144,7 +146,9 @@
      *     be converted to a string using {@link String#valueOf(Object)}
      * @return the non-null reference that was validated
      * @throws NullPointerException if {@code reference} is null
+     * @deprecated - use {@link java.util.Objects.requireNonNull} instead.
      */
+    @Deprecated
     @UnsupportedAppUsage
     public static @NonNull <T> T checkNotNull(final T reference, final Object errorMessage) {
         if (reference == null) {
@@ -154,26 +158,6 @@
     }
 
     /**
-     * Ensures that an object reference passed as a parameter to the calling
-     * method is not null.
-     *
-     * @param reference an object reference
-     * @param messageTemplate a printf-style message template to use if the check fails; will
-     *     be converted to a string using {@link String#format(String, Object...)}
-     * @param messageArgs arguments for {@code messageTemplate}
-     * @return the non-null reference that was validated
-     * @throws NullPointerException if {@code reference} is null
-     */
-    public static @NonNull <T> T checkNotNull(final T reference,
-            final String messageTemplate,
-            final Object... messageArgs) {
-        if (reference == null) {
-            throw new NullPointerException(String.format(messageTemplate, messageArgs));
-        }
-        return reference;
-    }
-
-    /**
      * Ensures the truth of an expression involving the state of the calling
      * instance, but not involving any parameters to the calling method.
      *
diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl
index 58aaa80..475a321 100644
--- a/core/java/com/android/internal/view/IInputMethod.aidl
+++ b/core/java/com/android/internal/view/IInputMethod.aidl
@@ -54,7 +54,7 @@
 
     void revokeSession(IInputMethodSession session);
 
-    void showSoftInput(int flags, in ResultReceiver resultReceiver);
+    void showSoftInput(in IBinder showInputToken, int flags, in ResultReceiver resultReceiver);
 
     void hideSoftInput(int flags, in ResultReceiver resultReceiver);
 
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index c29e823..0337ddd 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -41,7 +41,7 @@
             boolean allowsImplicitlySelectedSubtypes);
     InputMethodSubtype getLastInputMethodSubtype();
 
-    boolean showSoftInput(in IInputMethodClient client, int flags,
+    boolean showSoftInput(in IInputMethodClient client, IBinder windowToken, int flags,
             in ResultReceiver resultReceiver);
     boolean hideSoftInput(in IInputMethodClient client, int flags,
             in ResultReceiver resultReceiver);
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index 07d0d7d..f608958 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -22,6 +22,7 @@
 import android.annotation.StyleRes;
 import android.app.Notification;
 import android.app.Person;
+import android.app.RemoteInputHistoryItem;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -161,8 +162,9 @@
         if (headerText != null) {
             mConversationTitle = headerText.getText();
         }
-        addRemoteInputHistoryToMessages(newMessages,
-                extras.getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY));
+        RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[])
+                extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+        addRemoteInputHistoryToMessages(newMessages, history);
         boolean showSpinner =
                 extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false);
         bind(newMessages, newHistoricMessages, showSpinner);
@@ -175,14 +177,18 @@
 
     private void addRemoteInputHistoryToMessages(
             List<Notification.MessagingStyle.Message> newMessages,
-            CharSequence[] remoteInputHistory) {
+            RemoteInputHistoryItem[] remoteInputHistory) {
         if (remoteInputHistory == null || remoteInputHistory.length == 0) {
             return;
         }
         for (int i = remoteInputHistory.length - 1; i >= 0; i--) {
-            CharSequence message = remoteInputHistory[i];
-            newMessages.add(new Notification.MessagingStyle.Message(
-                    message, 0, (Person) null, true /* remoteHistory */));
+            RemoteInputHistoryItem historyMessage = remoteInputHistory[i];
+            Notification.MessagingStyle.Message message = new Notification.MessagingStyle.Message(
+                    historyMessage.getText(), 0, (Person) null, true /* remoteHistory */);
+            if (historyMessage.getUri() != null) {
+                message.setData(historyMessage.getMimeType(), historyMessage.getUri());
+            }
+            newMessages.add(message);
         }
     }
 
diff --git a/core/java/com/android/internal/widget/RecyclerView.java b/core/java/com/android/internal/widget/RecyclerView.java
index 43a227a..d7a01c4 100644
--- a/core/java/com/android/internal/widget/RecyclerView.java
+++ b/core/java/com/android/internal/widget/RecyclerView.java
@@ -2011,13 +2011,27 @@
         }
 
         if (!dispatchNestedPreFling(velocityX, velocityY)) {
-            final boolean canScroll = canScrollHorizontal || canScrollVertical;
-            dispatchNestedFling(velocityX, velocityY, canScroll);
+            final View firstChild = mLayout.getChildAt(0);
+            final View lastChild = mLayout.getChildAt(mLayout.getChildCount() - 1);
+            boolean consumed = false;
+            if (velocityY < 0) {
+                consumed = getChildAdapterPosition(firstChild) > 0
+                        || firstChild.getTop() < getPaddingTop();
+            }
+
+            if (velocityY > 0) {
+                consumed = getChildAdapterPosition(lastChild) < mAdapter.getItemCount() - 1
+                        || lastChild.getBottom() > getHeight() - getPaddingBottom();
+            }
+
+            dispatchNestedFling(velocityX, velocityY, consumed);
 
             if (mOnFlingListener != null && mOnFlingListener.onFling(velocityX, velocityY)) {
                 return true;
             }
 
+            final boolean canScroll = canScrollHorizontal || canScrollVertical;
+
             if (canScroll) {
                 velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity));
                 velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity));
diff --git a/core/java/com/android/server/BootReceiver.java b/core/java/com/android/server/BootReceiver.java
index 13bfc1b..31b5e49 100644
--- a/core/java/com/android/server/BootReceiver.java
+++ b/core/java/com/android/server/BootReceiver.java
@@ -44,21 +44,21 @@
 import com.android.internal.util.XmlUtils;
 import com.android.server.DropboxLogTags;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.FileNotFoundException;
 import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
 /**
  * Performs a number of miscellaneous, non-system-critical actions
  * after the system has finished booting.
@@ -424,7 +424,23 @@
         for (String propPostfix : MOUNT_DURATION_PROPS_POSTFIX) {
             int duration = SystemProperties.getInt("ro.boottime.init.mount_all." + propPostfix, 0);
             if (duration != 0) {
-                MetricsLogger.histogram(null, "boot_mount_all_duration_" + propPostfix, duration);
+                int eventType;
+                switch (propPostfix) {
+                    case "early":
+                        eventType = StatsLog.BOOT_TIME_EVENT_DURATION__EVENT__MOUNT_EARLY_DURATION;
+                        break;
+                    case "default":
+                        eventType =
+                                StatsLog.BOOT_TIME_EVENT_DURATION__EVENT__MOUNT_DEFAULT_DURATION;
+                        break;
+                    case "late":
+                        eventType = StatsLog.BOOT_TIME_EVENT_DURATION__EVENT__MOUNT_LATE_DURATION;
+                        break;
+                    default:
+                        continue;
+                }
+                StatsLog.write(StatsLog.BOOT_TIME_EVENT_DURATION_REPORTED, eventType,
+                        duration);
             }
         }
     }
@@ -555,16 +571,19 @@
         Pattern pattern = Pattern.compile(LAST_SHUTDOWN_TIME_PATTERN, Pattern.MULTILINE);
         Matcher matcher = pattern.matcher(lines);
         if (matcher.find()) {
-            MetricsLogger.histogram(null, "boot_fs_shutdown_duration",
+            StatsLog.write(StatsLog.BOOT_TIME_EVENT_DURATION_REPORTED,
+                    StatsLog.BOOT_TIME_EVENT_DURATION__EVENT__SHUTDOWN_DURATION,
                     Integer.parseInt(matcher.group(1)));
-            MetricsLogger.histogram(null, "boot_fs_shutdown_umount_stat",
+            StatsLog.write(StatsLog.BOOT_TIME_EVENT_ERROR_CODE_REPORTED,
+                    StatsLog.BOOT_TIME_EVENT_ERROR_CODE__EVENT__SHUTDOWN_UMOUNT_STAT,
                     Integer.parseInt(matcher.group(2)));
             Slog.i(TAG, "boot_fs_shutdown," + matcher.group(1) + "," + matcher.group(2));
         } else { // not found
             // This can happen when a device has too much kernel log after file system unmount
             // ,exceeding maxReadSize. And having that much kernel logging can affect overall
             // performance as well. So it is better to fix the kernel to reduce the amount of log.
-            MetricsLogger.histogram(null, "boot_fs_shutdown_umount_stat",
+            StatsLog.write(StatsLog.BOOT_TIME_EVENT_ERROR_CODE_REPORTED,
+                    StatsLog.BOOT_TIME_EVENT_ERROR_CODE__EVENT__SHUTDOWN_UMOUNT_STAT,
                     UMOUNT_STATUS_NOT_AVAILABLE);
             Slog.w(TAG, "boot_fs_shutdown, string not found");
         }
@@ -674,7 +693,11 @@
             return;
         }
         stat = fixFsckFsStat(partition, stat, lines, startLineNumber, endLineNumber);
-        MetricsLogger.histogram(null, "boot_fs_stat_" + partition, stat);
+        if ("userdata".equals(partition) || "data".equals(partition)) {
+            StatsLog.write(StatsLog.BOOT_TIME_EVENT_ERROR_CODE_REPORTED,
+                    StatsLog.BOOT_TIME_EVENT_ERROR_CODE__EVENT__FS_MGR_FS_STAT_DATA_PARTITION,
+                    stat);
+        }
         Slog.i(TAG, "fs_stat, partition:" + partition + " stat:0x" + Integer.toHexString(stat));
     }
 
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 8a59c99..74b481c 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -218,6 +218,7 @@
     final ArrayMap<String, ArraySet<String>> mAllowedAssociations = new ArrayMap<>();
 
     private final ArraySet<String> mBugreportWhitelistedPackages = new ArraySet<>();
+    private final ArraySet<String> mAppDataIsolationWhitelistedApps = new ArraySet<>();
 
     // Map of packagesNames to userTypes. Stored temporarily until cleared by UserManagerService().
     private ArrayMap<String, Set<String>> mPackageToUserTypeWhitelist = new ArrayMap<>();
@@ -389,6 +390,10 @@
         return mRollbackWhitelistedPackages;
     }
 
+    public ArraySet<String> getAppDataIsolationWhitelistedApps() {
+        return mAppDataIsolationWhitelistedApps;
+    }
+
     /**
      * Gets map of packagesNames to userTypes, dictating on which user types each package should be
      * initially installed, and then removes this map from SystemConfig.
@@ -1045,6 +1050,16 @@
                         }
                         XmlUtils.skipCurrentTag(parser);
                     } break;
+                    case "app-data-isolation-whitelisted-app": {
+                        String pkgname = parser.getAttributeValue(null, "package");
+                        if (pkgname == null) {
+                            Slog.w(TAG, "<" + name + "> without package in " + permFile
+                                    + " at " + parser.getPositionDescription());
+                        } else {
+                            mAppDataIsolationWhitelistedApps.add(pkgname);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
                     case "bugreport-whitelisted": {
                         String pkgname = parser.getAttributeValue(null, "package");
                         if (pkgname == null) {
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 6e6746f..a2f514a 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -344,7 +344,6 @@
     cppflags: ["-Wno-conversion-null"],
 
     srcs: [
-        "android/graphics/apex/android_bitmap.cpp",
         "android/graphics/apex/android_matrix.cpp",
         "android/graphics/apex/android_paint.cpp",
         "android/graphics/apex/android_region.cpp",
@@ -430,6 +429,7 @@
         android: {
             srcs: [ // sources that depend on android only libraries
                 "android/graphics/apex/android_canvas.cpp",
+                "android/graphics/apex/android_bitmap.cpp",
                 "android/graphics/apex/renderthread.cpp",
                 "android/graphics/apex/jni_runtime.cpp",
 
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 984f93c..32a7cf3 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -243,7 +243,8 @@
     return localBitmap.valid() ? &localBitmap->bitmap() : nullptr;
 }
 
-SkImageInfo GraphicsJNI::getBitmapInfo(JNIEnv* env, jobject bitmap, uint32_t* outRowBytes) {
+SkImageInfo GraphicsJNI::getBitmapInfo(JNIEnv* env, jobject bitmap, uint32_t* outRowBytes,
+                                       bool* isHardware) {
     SkASSERT(env);
     SkASSERT(bitmap);
     SkASSERT(env->IsInstanceOf(bitmap, gBitmap_class));
@@ -252,6 +253,9 @@
     if (outRowBytes) {
         *outRowBytes = localBitmap->rowBytes();
     }
+    if (isHardware) {
+        *isHardware = localBitmap->isHardware();
+    }
     return localBitmap->info();
 }
 
@@ -457,15 +461,6 @@
             sk_ref_sp(bitmap->info().colorSpace())));
 }
 
-// These must match the int values in Bitmap.java
-enum JavaEncodeFormat {
-    kJPEG_JavaEncodeFormat = 0,
-    kPNG_JavaEncodeFormat = 1,
-    kWEBP_JavaEncodeFormat = 2,
-    kWEBP_LOSSY_JavaEncodeFormat = 3,
-    kWEBP_LOSSLESS_JavaEncodeFormat = 4,
-};
-
 static jboolean Bitmap_compress(JNIEnv* env, jobject clazz, jlong bitmapHandle,
                                 jint format, jint quality,
                                 jobject jstream, jbyteArray jstorage) {
@@ -479,51 +474,9 @@
         return JNI_FALSE;
     }
 
-    SkBitmap skbitmap;
-    bitmap->getSkBitmap(&skbitmap);
-    if (skbitmap.colorType() == kRGBA_F16_SkColorType) {
-        // Convert to P3 before encoding. This matches SkAndroidCodec::computeOutputColorSpace
-        // for wide gamuts.
-        auto cs = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3);
-        auto info = skbitmap.info().makeColorType(kRGBA_8888_SkColorType)
-                                   .makeColorSpace(std::move(cs));
-        SkBitmap p3;
-        if (!p3.tryAllocPixels(info)) {
-            return JNI_FALSE;
-        }
-
-        SkPixmap pm;
-        SkAssertResult(p3.peekPixels(&pm));  // should always work if tryAllocPixels() did.
-        if (!skbitmap.readPixels(pm)) {
-            return JNI_FALSE;
-        }
-        skbitmap = p3;
-    }
-    SkEncodedImageFormat fm;
-    switch (format) {
-        case kJPEG_JavaEncodeFormat:
-            fm = SkEncodedImageFormat::kJPEG;
-            break;
-        case kPNG_JavaEncodeFormat:
-            fm = SkEncodedImageFormat::kPNG;
-            break;
-        case kWEBP_JavaEncodeFormat:
-            fm = SkEncodedImageFormat::kWEBP;
-            break;
-        case kWEBP_LOSSY_JavaEncodeFormat:
-        case kWEBP_LOSSLESS_JavaEncodeFormat: {
-            SkWebpEncoder::Options options;
-            options.fQuality = quality;
-            options.fCompression = format == kWEBP_LOSSY_JavaEncodeFormat ?
-                    SkWebpEncoder::Compression::kLossy : SkWebpEncoder::Compression::kLossless;
-            return SkWebpEncoder::Encode(strm.get(), skbitmap.pixmap(), options) ?
-                    JNI_TRUE : JNI_FALSE;
-        }
-        default:
-            return JNI_FALSE;
-    }
-
-    return SkEncodeImage(strm.get(), skbitmap, fm, quality) ? JNI_TRUE : JNI_FALSE;
+    auto fm = static_cast<Bitmap::JavaCompressFormat>(format);
+    auto result = bitmap->bitmap().compress(fm, quality, strm.get());
+    return result == Bitmap::CompressResult::Success ? JNI_TRUE : JNI_FALSE;
 }
 
 static inline void bitmapErase(SkBitmap bitmap, const SkColor4f& color,
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index fa64fd1..adedffd 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -52,42 +52,34 @@
 
 using namespace android;
 
-jstring encodedFormatToString(JNIEnv* env, SkEncodedImageFormat format) {
-    const char* mimeType;
+const char* getMimeType(SkEncodedImageFormat format) {
     switch (format) {
         case SkEncodedImageFormat::kBMP:
-            mimeType = "image/bmp";
-            break;
+            return "image/bmp";
         case SkEncodedImageFormat::kGIF:
-            mimeType = "image/gif";
-            break;
+            return "image/gif";
         case SkEncodedImageFormat::kICO:
-            mimeType = "image/x-ico";
-            break;
+            return "image/x-ico";
         case SkEncodedImageFormat::kJPEG:
-            mimeType = "image/jpeg";
-            break;
+            return "image/jpeg";
         case SkEncodedImageFormat::kPNG:
-            mimeType = "image/png";
-            break;
+            return "image/png";
         case SkEncodedImageFormat::kWEBP:
-            mimeType = "image/webp";
-            break;
+            return "image/webp";
         case SkEncodedImageFormat::kHEIF:
-            mimeType = "image/heif";
-            break;
+            return "image/heif";
         case SkEncodedImageFormat::kWBMP:
-            mimeType = "image/vnd.wap.wbmp";
-            break;
+            return "image/vnd.wap.wbmp";
         case SkEncodedImageFormat::kDNG:
-            mimeType = "image/x-adobe-dng";
-            break;
+            return "image/x-adobe-dng";
         default:
-            mimeType = nullptr;
-            break;
+            return nullptr;
     }
+}
 
+jstring getMimeTypeAsJavaString(JNIEnv* env, SkEncodedImageFormat format) {
     jstring jstr = nullptr;
+    const char* mimeType = getMimeType(format);
     if (mimeType) {
         // NOTE: Caller should env->ExceptionCheck() for OOM
         // (can't check for nullptr as it's a valid return value)
@@ -289,10 +281,9 @@
 
     // Set the options and return if the client only wants the size.
     if (options != NULL) {
-        jstring mimeType = encodedFormatToString(
-                env, (SkEncodedImageFormat)codec->getEncodedFormat());
+        jstring mimeType = getMimeTypeAsJavaString(env, codec->getEncodedFormat());
         if (env->ExceptionCheck()) {
-            return nullObjectReturn("OOM in encodedFormatToString()");
+            return nullObjectReturn("OOM in getMimeTypeAsJavaString()");
         }
         env->SetIntField(options, gOptions_widthFieldID, scaledWidth);
         env->SetIntField(options, gOptions_heightFieldID, scaledHeight);
diff --git a/core/jni/android/graphics/BitmapFactory.h b/core/jni/android/graphics/BitmapFactory.h
index e37c98d..45bffc4 100644
--- a/core/jni/android/graphics/BitmapFactory.h
+++ b/core/jni/android/graphics/BitmapFactory.h
@@ -26,6 +26,6 @@
 extern jclass gBitmapConfig_class;
 extern jmethodID gBitmapConfig_nativeToConfigMethodID;
 
-jstring encodedFormatToString(JNIEnv* env, SkEncodedImageFormat format);
+jstring getMimeTypeAsJavaString(JNIEnv*, SkEncodedImageFormat);
 
 #endif  // _ANDROID_GRAPHICS_BITMAP_FACTORY_H_
diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp
index f18632d..06b4ff8 100644
--- a/core/jni/android/graphics/BitmapRegionDecoder.cpp
+++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp
@@ -197,7 +197,7 @@
         env->SetIntField(options, gOptions_heightFieldID, bitmap.height());
 
         env->SetObjectField(options, gOptions_mimeFieldID,
-                encodedFormatToString(env, (SkEncodedImageFormat)brd->getEncodedFormat()));
+                getMimeTypeAsJavaString(env, brd->getEncodedFormat()));
         if (env->ExceptionCheck()) {
             return nullObjectReturn("OOM in encodedFormatToString()");
         }
diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h
index 99034ed..1e49765 100644
--- a/core/jni/android/graphics/GraphicsJNI.h
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -62,7 +62,8 @@
 
     static android::Canvas* getNativeCanvas(JNIEnv*, jobject canvas);
     static android::Bitmap* getNativeBitmap(JNIEnv*, jobject bitmap);
-    static SkImageInfo getBitmapInfo(JNIEnv*, jobject bitmap, uint32_t* outRowBytes);
+    static SkImageInfo getBitmapInfo(JNIEnv*, jobject bitmap, uint32_t* outRowBytes,
+                                     bool* isHardware);
     static SkRegion* getNativeRegion(JNIEnv*, jobject region);
 
     /*
diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp
index 627f8f5..e17e057 100644
--- a/core/jni/android/graphics/ImageDecoder.cpp
+++ b/core/jni/android/graphics/ImageDecoder.cpp
@@ -241,7 +241,7 @@
         doThrowISE(env, "Could not scale to target size!");
         return nullptr;
     }
-    if (requireUnpremul && !decoder->setOutAlphaType(kUnpremul_SkAlphaType)) {
+    if (requireUnpremul && !decoder->setUnpremultipliedRequired(true)) {
         doThrowISE(env, "Cannot scale unpremultiplied pixels!");
         return nullptr;
     }
@@ -301,11 +301,15 @@
         }
     }
 
-    SkBitmap bm;
     SkImageInfo bitmapInfo = decoder->getOutputInfo();
+    if (decoder->opaque()) {
+        bitmapInfo = bitmapInfo.makeAlphaType(kOpaque_SkAlphaType);
+    }
     if (asAlphaMask && colorType == kGray_8_SkColorType) {
         bitmapInfo = bitmapInfo.makeColorType(kAlpha_8_SkColorType);
     }
+
+    SkBitmap bm;
     if (!bm.setInfo(bitmapInfo)) {
         doThrowIOE(env, "Failed to setInfo properly");
         return nullptr;
@@ -475,7 +479,7 @@
 
 static jstring ImageDecoder_nGetMimeType(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
     auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
-    return encodedFormatToString(env, decoder->mCodec->getEncodedFormat());
+    return getMimeTypeAsJavaString(env, decoder->mCodec->getEncodedFormat());
 }
 
 static jobject ImageDecoder_nGetColorSpace(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
diff --git a/core/jni/android/graphics/MimeType.h b/core/jni/android/graphics/MimeType.h
new file mode 100644
index 0000000..38a579c
--- /dev/null
+++ b/core/jni/android/graphics/MimeType.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright 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.
+ */
+
+#pragma once
+
+#include "SkEncodedImageFormat.h"
+
+const char* getMimeType(SkEncodedImageFormat);
diff --git a/core/jni/android/graphics/SurfaceTexture.cpp b/core/jni/android/graphics/SurfaceTexture.cpp
index 1a9e8d0..2aca317 100644
--- a/core/jni/android/graphics/SurfaceTexture.cpp
+++ b/core/jni/android/graphics/SurfaceTexture.cpp
@@ -23,10 +23,10 @@
 #include <GLES2/gl2.h>
 #include <GLES2/gl2ext.h>
 
-#include <gui/Surface.h>
-#include <gui/surfacetexture/SurfaceTexture.h>
 #include <gui/BufferQueue.h>
-#include <gui/surfacetexture/surface_texture_platform.h>
+#include <gui/Surface.h>
+#include <surfacetexture/SurfaceTexture.h>
+#include <surfacetexture/surface_texture_platform.h>
 
 #include "core_jni_helpers.h"
 
diff --git a/core/jni/android/graphics/apex/android_bitmap.cpp b/core/jni/android/graphics/apex/android_bitmap.cpp
index 90cc986..6628529 100644
--- a/core/jni/android/graphics/apex/android_bitmap.cpp
+++ b/core/jni/android/graphics/apex/android_bitmap.cpp
@@ -23,6 +23,7 @@
 
 #include <GraphicsJNI.h>
 #include <hwui/Bitmap.h>
+#include <utils/Color.h>
 
 using namespace android;
 
@@ -77,7 +78,7 @@
     }
 }
 
-static uint32_t getInfoFlags(const SkImageInfo& info) {
+static uint32_t getAlphaFlags(const SkImageInfo& info) {
     switch (info.alphaType()) {
         case kUnknown_SkAlphaType:
             LOG_ALWAYS_FATAL("Bitmap has no alpha type");
@@ -91,6 +92,14 @@
     }
 }
 
+static uint32_t getInfoFlags(const SkImageInfo& info, bool isHardware) {
+    uint32_t flags = getAlphaFlags(info);
+    if (isHardware) {
+        flags |= ANDROID_BITMAP_FLAGS_IS_HARDWARE;
+    }
+    return flags;
+}
+
 ABitmap* ABitmap_copy(ABitmap* srcBitmapHandle, AndroidBitmapFormat dstFormat) {
     SkColorType dstColorType = getColorType(dstFormat);
     if (srcBitmapHandle && dstColorType != kUnknown_SkColorType) {
@@ -107,25 +116,32 @@
     return nullptr;
 }
 
-static AndroidBitmapInfo getInfo(const SkImageInfo& imageInfo, uint32_t rowBytes) {
+static AndroidBitmapInfo getInfo(const SkImageInfo& imageInfo, uint32_t rowBytes, bool isHardware) {
     AndroidBitmapInfo info;
     info.width = imageInfo.width();
     info.height = imageInfo.height();
     info.stride = rowBytes;
     info.format = getFormat(imageInfo);
-    info.flags = getInfoFlags(imageInfo);
+    info.flags = getInfoFlags(imageInfo, isHardware);
     return info;
 }
 
 AndroidBitmapInfo ABitmap_getInfo(ABitmap* bitmapHandle) {
     Bitmap* bitmap = TypeCast::toBitmap(bitmapHandle);
-    return getInfo(bitmap->info(), bitmap->rowBytes());
+    return getInfo(bitmap->info(), bitmap->rowBytes(), bitmap->isHardware());
+}
+
+ADataSpace ABitmap_getDataSpace(ABitmap* bitmapHandle) {
+    Bitmap* bitmap = TypeCast::toBitmap(bitmapHandle);
+    const SkImageInfo& info = bitmap->info();
+    return (ADataSpace)uirenderer::ColorSpaceToADataSpace(info.colorSpace(), info.colorType());
 }
 
 AndroidBitmapInfo ABitmap_getInfoFromJava(JNIEnv* env, jobject bitmapObj) {
     uint32_t rowBytes = 0;
-    SkImageInfo imageInfo = GraphicsJNI::getBitmapInfo(env, bitmapObj, &rowBytes);
-    return getInfo(imageInfo, rowBytes);
+    bool isHardware = false;
+    SkImageInfo imageInfo = GraphicsJNI::getBitmapInfo(env, bitmapObj, &rowBytes, &isHardware);
+    return getInfo(imageInfo, rowBytes, isHardware);
 }
 
 void* ABitmap_getPixels(ABitmap* bitmapHandle) {
@@ -151,3 +167,144 @@
     }
     return bitmap->notifyPixelsChanged();
 }
+
+namespace {
+SkAlphaType getAlphaType(const AndroidBitmapInfo* info) {
+    switch (info->flags & ANDROID_BITMAP_FLAGS_ALPHA_MASK) {
+        case ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE:
+            return kOpaque_SkAlphaType;
+        case ANDROID_BITMAP_FLAGS_ALPHA_PREMUL:
+            return kPremul_SkAlphaType;
+        case ANDROID_BITMAP_FLAGS_ALPHA_UNPREMUL:
+            return kUnpremul_SkAlphaType;
+        default:
+            return kUnknown_SkAlphaType;
+    }
+}
+
+class CompressWriter : public SkWStream {
+public:
+    CompressWriter(void* userContext, AndroidBitmap_CompressWriteFunc fn)
+          : mUserContext(userContext), mFn(fn), mBytesWritten(0) {}
+
+    bool write(const void* buffer, size_t size) override {
+        if (mFn(mUserContext, buffer, size)) {
+            mBytesWritten += size;
+            return true;
+        }
+        return false;
+    }
+
+    size_t bytesWritten() const override { return mBytesWritten; }
+
+private:
+    void* mUserContext;
+    AndroidBitmap_CompressWriteFunc mFn;
+    size_t mBytesWritten;
+};
+
+} // anonymous namespace
+
+int ABitmap_compress(const AndroidBitmapInfo* info, ADataSpace dataSpace, const void* pixels,
+                     AndroidBitmapCompressFormat inFormat, int32_t quality, void* userContext,
+                     AndroidBitmap_CompressWriteFunc fn) {
+    Bitmap::JavaCompressFormat format;
+    switch (inFormat) {
+        case ANDROID_BITMAP_COMPRESS_FORMAT_JPEG:
+            format = Bitmap::JavaCompressFormat::Jpeg;
+            break;
+        case ANDROID_BITMAP_COMPRESS_FORMAT_PNG:
+            format = Bitmap::JavaCompressFormat::Png;
+            break;
+        case ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSY:
+            format = Bitmap::JavaCompressFormat::WebpLossy;
+            break;
+        case ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSLESS:
+            format = Bitmap::JavaCompressFormat::WebpLossless;
+            break;
+        default:
+            // kWEBP_JavaEncodeFormat is a valid parameter for Bitmap::compress,
+            // for the deprecated Bitmap.CompressFormat.WEBP, but it should not
+            // be provided via the NDK. Other integers are likewise invalid.
+            return ANDROID_BITMAP_RESULT_BAD_PARAMETER;
+    }
+
+    SkColorType colorType;
+    switch (info->format) {
+        case ANDROID_BITMAP_FORMAT_RGBA_8888:
+            colorType = kN32_SkColorType;
+            break;
+        case ANDROID_BITMAP_FORMAT_RGB_565:
+            colorType = kRGB_565_SkColorType;
+            break;
+        case ANDROID_BITMAP_FORMAT_A_8:
+            // FIXME b/146637821: Should this encode as grayscale? We should
+            // make the same decision as for encoding an android.graphics.Bitmap.
+            // Note that encoding kAlpha_8 as WebP or JPEG will fail. Encoding
+            // it to PNG encodes as GRAY+ALPHA with a secret handshake that we
+            // only care about the alpha. I'm not sure whether Android decoding
+            // APIs respect that handshake.
+            colorType = kAlpha_8_SkColorType;
+            break;
+        case ANDROID_BITMAP_FORMAT_RGBA_F16:
+            colorType = kRGBA_F16_SkColorType;
+            break;
+        default:
+            return ANDROID_BITMAP_RESULT_BAD_PARAMETER;
+    }
+
+    auto alphaType = getAlphaType(info);
+    if (alphaType == kUnknown_SkAlphaType) {
+        return ANDROID_BITMAP_RESULT_BAD_PARAMETER;
+    }
+
+    sk_sp<SkColorSpace> cs;
+    if (info->format == ANDROID_BITMAP_FORMAT_A_8) {
+        // FIXME: A Java Bitmap with ALPHA_8 never has a ColorSpace. So should
+        // we force that here (as I'm doing now) or should we treat anything
+        // besides ADATASPACE_UNKNOWN as an error?
+        cs = nullptr;
+    } else {
+        cs = uirenderer::DataSpaceToColorSpace((android_dataspace) dataSpace);
+        // DataSpaceToColorSpace treats UNKNOWN as SRGB, but compress forces the
+        // client to specify SRGB if that is what they want.
+        if (!cs || dataSpace == ADATASPACE_UNKNOWN) {
+            return ANDROID_BITMAP_RESULT_BAD_PARAMETER;
+        }
+    }
+
+    {
+        size_t size;
+        if (!Bitmap::computeAllocationSize(info->stride, info->height, &size)) {
+            return ANDROID_BITMAP_RESULT_BAD_PARAMETER;
+        }
+    }
+
+    auto imageInfo =
+            SkImageInfo::Make(info->width, info->height, colorType, alphaType, std::move(cs));
+    SkBitmap bitmap;
+    // We are not going to modify the pixels, but installPixels expects them to
+    // not be const, since for all it knows we might want to draw to the SkBitmap.
+    if (!bitmap.installPixels(imageInfo, const_cast<void*>(pixels), info->stride)) {
+        return ANDROID_BITMAP_RESULT_BAD_PARAMETER;
+    }
+
+    CompressWriter stream(userContext, fn);
+    switch (Bitmap::compress(bitmap, format, quality, &stream)) {
+        case Bitmap::CompressResult::Success:
+            return ANDROID_BITMAP_RESULT_SUCCESS;
+        case Bitmap::CompressResult::AllocationFailed:
+            return ANDROID_BITMAP_RESULT_ALLOCATION_FAILED;
+        case Bitmap::CompressResult::Error:
+            return ANDROID_BITMAP_RESULT_JNI_EXCEPTION;
+    }
+}
+
+AHardwareBuffer* ABitmap_getHardwareBuffer(ABitmap* bitmapHandle) {
+    Bitmap* bitmap = TypeCast::toBitmap(bitmapHandle);
+    AHardwareBuffer* buffer = bitmap->hardwareBuffer();
+    if (buffer) {
+        AHardwareBuffer_acquire(buffer);
+    }
+    return buffer;
+}
diff --git a/core/jni/android/graphics/apex/include/android/graphics/bitmap.h b/core/jni/android/graphics/apex/include/android/graphics/bitmap.h
index f231eed..45fec2ab 100644
--- a/core/jni/android/graphics/apex/include/android/graphics/bitmap.h
+++ b/core/jni/android/graphics/apex/include/android/graphics/bitmap.h
@@ -17,9 +17,12 @@
 #define ANDROID_GRAPHICS_BITMAP_H
 
 #include <android/bitmap.h>
+#include <android/data_space.h>
 #include <jni.h>
 #include <sys/cdefs.h>
 
+struct AHardwareBuffer;
+
 __BEGIN_DECLS
 
 /**
@@ -49,6 +52,7 @@
 void ABitmap_releaseRef(ABitmap* bitmap);
 
 AndroidBitmapInfo ABitmap_getInfo(ABitmap* bitmap);
+ADataSpace ABitmap_getDataSpace(ABitmap* bitmap);
 
 void* ABitmap_getPixels(ABitmap* bitmap);
 void ABitmap_notifyPixelsChanged(ABitmap* bitmap);
@@ -56,6 +60,23 @@
 AndroidBitmapFormat ABitmapConfig_getFormatFromConfig(JNIEnv* env, jobject bitmapConfigObj);
 jobject ABitmapConfig_getConfigFromFormat(JNIEnv* env, AndroidBitmapFormat format);
 
+// NDK access
+int ABitmap_compress(const AndroidBitmapInfo* info, ADataSpace dataSpace, const void* pixels,
+                     AndroidBitmapCompressFormat format, int32_t quality, void* userContext,
+                     AndroidBitmap_CompressWriteFunc);
+/**
+ *  Retrieve the native object associated with a HARDWARE Bitmap.
+ *
+ *  Client must not modify it while a Bitmap is wrapping it.
+ *
+ *  @param bitmap Handle to an android.graphics.Bitmap.
+ *  @return on success, a pointer to the
+ *         AHardwareBuffer associated with bitmap. This acquires
+ *         a reference on the buffer, and the client must call
+ *         AHardwareBuffer_release when finished with it.
+ */
+AHardwareBuffer* ABitmap_getHardwareBuffer(ABitmap* bitmap);
+
 __END_DECLS
 
 #ifdef	__cplusplus
@@ -106,8 +127,10 @@
         ABitmap* get() const { return mBitmap; }
 
         AndroidBitmapInfo getInfo() const { return ABitmap_getInfo(mBitmap); }
+        ADataSpace getDataSpace() const { return ABitmap_getDataSpace(mBitmap); }
         void* getPixels() const { return ABitmap_getPixels(mBitmap); }
         void notifyPixelsChanged() const { ABitmap_notifyPixelsChanged(mBitmap); }
+        AHardwareBuffer* getHardwareBuffer() const { return ABitmap_getHardwareBuffer(mBitmap); }
 
     private:
         // takes ownership of the provided ABitmap
@@ -119,4 +142,4 @@
 }; // namespace android
 #endif // __cplusplus
 
-#endif // ANDROID_GRAPHICS_BITMAP_H
\ No newline at end of file
+#endif // ANDROID_GRAPHICS_BITMAP_H
diff --git a/core/jni/android_database_SQLiteConnection.cpp b/core/jni/android_database_SQLiteConnection.cpp
index d9ed07e..c80f1dc 100644
--- a/core/jni/android_database_SQLiteConnection.cpp
+++ b/core/jni/android_database_SQLiteConnection.cpp
@@ -59,14 +59,12 @@
 static const int BUSY_TIMEOUT_MS = 2500;
 
 static struct {
-    jfieldID name;
-    jfieldID numArgs;
-    jmethodID dispatchCallback;
-} gSQLiteCustomFunctionClassInfo;
+    jmethodID apply;
+} gUnaryOperator;
 
 static struct {
-    jclass clazz;
-} gStringClassInfo;
+    jmethodID apply;
+} gBinaryOperator;
 
 struct SQLiteConnection {
     // Open flags.
@@ -203,74 +201,146 @@
     }
 }
 
-// Called each time a custom function is evaluated.
-static void sqliteCustomFunctionCallback(sqlite3_context *context,
+static void sqliteCustomScalarFunctionCallback(sqlite3_context *context,
         int argc, sqlite3_value **argv) {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
-
-    // Get the callback function object.
-    // Create a new local reference to it in case the callback tries to do something
-    // dumb like unregister the function (thereby destroying the global ref) while it is running.
     jobject functionObjGlobal = reinterpret_cast<jobject>(sqlite3_user_data(context));
-    jobject functionObj = env->NewLocalRef(functionObjGlobal);
-
-    jobjectArray argsArray = env->NewObjectArray(argc, gStringClassInfo.clazz, NULL);
-    if (argsArray) {
-        for (int i = 0; i < argc; i++) {
-            const jchar* arg = static_cast<const jchar*>(sqlite3_value_text16(argv[i]));
-            if (!arg) {
-                ALOGW("NULL argument in custom_function_callback.  This should not happen.");
-            } else {
-                size_t argLen = sqlite3_value_bytes16(argv[i]) / sizeof(jchar);
-                jstring argStr = env->NewString(arg, argLen);
-                if (!argStr) {
-                    goto error; // out of memory error
-                }
-                env->SetObjectArrayElement(argsArray, i, argStr);
-                env->DeleteLocalRef(argStr);
-            }
-        }
-
-        // TODO: Support functions that return values.
-        env->CallVoidMethod(functionObj,
-                gSQLiteCustomFunctionClassInfo.dispatchCallback, argsArray);
-
-error:
-        env->DeleteLocalRef(argsArray);
-    }
-
-    env->DeleteLocalRef(functionObj);
+    ScopedLocalRef<jobject> functionObj(env, env->NewLocalRef(functionObjGlobal));
+    ScopedLocalRef<jstring> argString(env,
+            env->NewStringUTF(reinterpret_cast<const char*>(sqlite3_value_text(argv[0]))));
+    ScopedLocalRef<jstring> resString(env,
+            (jstring) env->CallObjectMethod(functionObj.get(), gUnaryOperator.apply, argString.get()));
 
     if (env->ExceptionCheck()) {
-        ALOGE("An exception was thrown by custom SQLite function.");
-        LOGE_EX(env);
+        ALOGE("Exception thrown by custom scalar function");
+        sqlite3_result_error(context, "Exception thrown by custom scalar function", -1);
+        env->ExceptionDescribe();
         env->ExceptionClear();
+        return;
+    }
+
+    if (resString.get() == nullptr) {
+        sqlite3_result_null(context);
+    } else {
+        ScopedUtfChars res(env, resString.get());
+        sqlite3_result_text(context, res.c_str(), -1, SQLITE_TRANSIENT);
     }
 }
 
-// Called when a custom function is destroyed.
-static void sqliteCustomFunctionDestructor(void* data) {
+static void sqliteCustomScalarFunctionDestructor(void* data) {
     jobject functionObjGlobal = reinterpret_cast<jobject>(data);
 
     JNIEnv* env = AndroidRuntime::getJNIEnv();
     env->DeleteGlobalRef(functionObjGlobal);
 }
 
-static void nativeRegisterCustomFunction(JNIEnv* env, jclass clazz, jlong connectionPtr,
-        jobject functionObj) {
+static void nativeRegisterCustomScalarFunction(JNIEnv* env, jclass clazz, jlong connectionPtr,
+        jstring functionName, jobject functionObj) {
     SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
 
-    jstring nameStr = jstring(env->GetObjectField(
-            functionObj, gSQLiteCustomFunctionClassInfo.name));
-    jint numArgs = env->GetIntField(functionObj, gSQLiteCustomFunctionClassInfo.numArgs);
+    jobject functionObjGlobal = env->NewGlobalRef(functionObj);
+    ScopedUtfChars functionNameChars(env, functionName);
+    int err = sqlite3_create_function_v2(connection->db,
+            functionNameChars.c_str(), 1, SQLITE_UTF8,
+            reinterpret_cast<void*>(functionObjGlobal),
+            &sqliteCustomScalarFunctionCallback,
+            nullptr,
+            nullptr,
+            &sqliteCustomScalarFunctionDestructor);
+
+    if (err != SQLITE_OK) {
+        ALOGE("sqlite3_create_function returned %d", err);
+        env->DeleteGlobalRef(functionObjGlobal);
+        throw_sqlite3_exception(env, connection->db);
+        return;
+    }
+}
+
+static void sqliteCustomAggregateFunctionStep(sqlite3_context *context,
+        int argc, sqlite3_value **argv) {
+    char** agg = reinterpret_cast<char**>(
+            sqlite3_aggregate_context(context, sizeof(const char**)));
+    if (agg == nullptr) {
+        return;
+    } else if (*agg == nullptr) {
+        // During our first call the best we can do is allocate our result
+        // holder and populate it with our first value; we'll reduce it
+        // against any additional values in future calls
+        const char* res = reinterpret_cast<const char*>(sqlite3_value_text(argv[0]));
+        if (res == nullptr) {
+            *agg = nullptr;
+        } else {
+            *agg = strdup(res);
+        }
+        return;
+    }
+
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    jobject functionObjGlobal = reinterpret_cast<jobject>(sqlite3_user_data(context));
+    ScopedLocalRef<jobject> functionObj(env, env->NewLocalRef(functionObjGlobal));
+    ScopedLocalRef<jstring> arg0String(env,
+            env->NewStringUTF(reinterpret_cast<const char*>(*agg)));
+    ScopedLocalRef<jstring> arg1String(env,
+            env->NewStringUTF(reinterpret_cast<const char*>(sqlite3_value_text(argv[0]))));
+    ScopedLocalRef<jstring> resString(env,
+            (jstring) env->CallObjectMethod(functionObj.get(), gBinaryOperator.apply,
+                    arg0String.get(), arg1String.get()));
+
+    if (env->ExceptionCheck()) {
+        ALOGE("Exception thrown by custom aggregate function");
+        sqlite3_result_error(context, "Exception thrown by custom aggregate function", -1);
+        env->ExceptionDescribe();
+        env->ExceptionClear();
+        return;
+    }
+
+    // One way or another, we have a new value to collect, and we need to
+    // free our previous value
+    if (*agg != nullptr) {
+        free(*agg);
+    }
+    if (resString.get() == nullptr) {
+        *agg = nullptr;
+    } else {
+        ScopedUtfChars res(env, resString.get());
+        *agg = strdup(res.c_str());
+    }
+}
+
+static void sqliteCustomAggregateFunctionFinal(sqlite3_context *context) {
+    // We pass zero size here to avoid allocating for empty sets
+    char** agg = reinterpret_cast<char**>(
+            sqlite3_aggregate_context(context, 0));
+    if (agg == nullptr) {
+        return;
+    } else if (*agg == nullptr) {
+        sqlite3_result_null(context);
+    } else {
+        sqlite3_result_text(context, *agg, -1, SQLITE_TRANSIENT);
+        free(*agg);
+    }
+}
+
+static void sqliteCustomAggregateFunctionDestructor(void* data) {
+    jobject functionObjGlobal = reinterpret_cast<jobject>(data);
+
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    env->DeleteGlobalRef(functionObjGlobal);
+}
+
+static void nativeRegisterCustomAggregateFunction(JNIEnv* env, jclass clazz, jlong connectionPtr,
+        jstring functionName, jobject functionObj) {
+    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
 
     jobject functionObjGlobal = env->NewGlobalRef(functionObj);
-
-    const char* name = env->GetStringUTFChars(nameStr, NULL);
-    int err = sqlite3_create_function_v2(connection->db, name, numArgs, SQLITE_UTF16,
+    ScopedUtfChars functionNameChars(env, functionName);
+    int err = sqlite3_create_function_v2(connection->db,
+            functionNameChars.c_str(), 1, SQLITE_UTF8,
             reinterpret_cast<void*>(functionObjGlobal),
-            &sqliteCustomFunctionCallback, NULL, NULL, &sqliteCustomFunctionDestructor);
-    env->ReleaseStringUTFChars(nameStr, name);
+            nullptr,
+            &sqliteCustomAggregateFunctionStep,
+            &sqliteCustomAggregateFunctionFinal,
+            &sqliteCustomAggregateFunctionDestructor);
 
     if (err != SQLITE_OK) {
         ALOGE("sqlite3_create_function returned %d", err);
@@ -812,8 +882,10 @@
             (void*)nativeOpen },
     { "nativeClose", "(J)V",
             (void*)nativeClose },
-    { "nativeRegisterCustomFunction", "(JLandroid/database/sqlite/SQLiteCustomFunction;)V",
-            (void*)nativeRegisterCustomFunction },
+    { "nativeRegisterCustomScalarFunction", "(JLjava/lang/String;Ljava/util/function/UnaryOperator;)V",
+            (void*)nativeRegisterCustomScalarFunction },
+    { "nativeRegisterCustomAggregateFunction", "(JLjava/lang/String;Ljava/util/function/BinaryOperator;)V",
+            (void*)nativeRegisterCustomAggregateFunction },
     { "nativeRegisterLocalizedCollators", "(JLjava/lang/String;)V",
             (void*)nativeRegisterLocalizedCollators },
     { "nativePrepareStatement", "(JLjava/lang/String;)J",
@@ -864,15 +936,13 @@
 
 int register_android_database_SQLiteConnection(JNIEnv *env)
 {
-    jclass clazz = FindClassOrDie(env, "android/database/sqlite/SQLiteCustomFunction");
+    jclass unaryClazz = FindClassOrDie(env, "java/util/function/UnaryOperator");
+    gUnaryOperator.apply = GetMethodIDOrDie(env, unaryClazz,
+            "apply", "(Ljava/lang/Object;)Ljava/lang/Object;");
 
-    gSQLiteCustomFunctionClassInfo.name = GetFieldIDOrDie(env, clazz, "name", "Ljava/lang/String;");
-    gSQLiteCustomFunctionClassInfo.numArgs = GetFieldIDOrDie(env, clazz, "numArgs", "I");
-    gSQLiteCustomFunctionClassInfo.dispatchCallback = GetMethodIDOrDie(env, clazz,
-            "dispatchCallback", "([Ljava/lang/String;)V");
-
-    clazz = FindClassOrDie(env, "java/lang/String");
-    gStringClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+    jclass binaryClazz = FindClassOrDie(env, "java/util/function/BinaryOperator");
+    gBinaryOperator.apply = GetMethodIDOrDie(env, binaryClazz,
+            "apply", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
 
     return RegisterMethodsOrDie(env, "android/database/sqlite/SQLiteConnection", sMethods,
                                 NELEM(sMethods));
diff --git a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
index f2a51ad..8cf1d2c 100644
--- a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
+++ b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
@@ -27,14 +27,14 @@
 #include "android_runtime/android_view_Surface.h"
 #include "android_runtime/android_graphics_SurfaceTexture.h"
 
-#include <gui/Surface.h>
-#include <gui/surfacetexture/SurfaceTexture.h>
 #include <gui/IGraphicBufferProducer.h>
 #include <gui/IProducerListener.h>
-#include <ui/GraphicBuffer.h>
-#include <system/window.h>
+#include <gui/Surface.h>
 #include <hardware/camera3.h>
+#include <surfacetexture/SurfaceTexture.h>
 #include <system/camera_metadata.h>
+#include <system/window.h>
+#include <ui/GraphicBuffer.h>
 
 #include <stdint.h>
 #include <inttypes.h>
diff --git a/core/jni/android_media_AudioAttributes.cpp b/core/jni/android_media_AudioAttributes.cpp
index b87a34d..b616ffc 100644
--- a/core/jni/android_media_AudioAttributes.cpp
+++ b/core/jni/android_media_AudioAttributes.cpp
@@ -48,11 +48,14 @@
     jfieldID    mFormattedTags; // AudioAttributes.mFormattedTags
 } gAudioAttributesFields;
 
+static struct { jmethodID isSystemUsage; } gAudioAttributesClassMethods;
+
 static jclass gAudioAttributesBuilderClass;
 static jmethodID gAudioAttributesBuilderCstor;
 static struct {
     jmethodID build;
     jmethodID setUsage;
+    jmethodID setSystemUsage;
     jmethodID setInternalCapturePreset;
     jmethodID setContentType;
     jmethodID setFlags;
@@ -109,9 +112,17 @@
     if (jAttributeBuilder.get() == nullptr) {
         return (jint)AUDIO_JAVA_ERROR;
     }
-    env->CallObjectMethod(jAttributeBuilder.get(),
-                          gAudioAttributesBuilderMethods.setUsage,
-                          attributes.usage);
+
+    const bool isSystemUsage = env->CallStaticBooleanMethod(gAudioAttributesClass,
+                                                      gAudioAttributesClassMethods.isSystemUsage,
+                                                      attributes.usage);
+    if (isSystemUsage) {
+        env->CallObjectMethod(jAttributeBuilder.get(),
+                              gAudioAttributesBuilderMethods.setSystemUsage, attributes.usage);
+    } else {
+        env->CallObjectMethod(jAttributeBuilder.get(), gAudioAttributesBuilderMethods.setUsage,
+                              attributes.usage);
+    }
     env->CallObjectMethod(jAttributeBuilder.get(),
                           gAudioAttributesBuilderMethods.setInternalCapturePreset,
                           attributes.source);
@@ -168,6 +179,9 @@
 {
     jclass audioAttributesClass = FindClassOrDie(env, kClassPathName);
     gAudioAttributesClass = MakeGlobalRefOrDie(env, audioAttributesClass);
+    gAudioAttributesClassMethods.isSystemUsage =
+            GetStaticMethodIDOrDie(env, gAudioAttributesClass, "isSystemUsage", "(I)Z");
+
     gAudioAttributesFields.mUsage = GetFieldIDOrDie(env, audioAttributesClass, "mUsage", "I");
     gAudioAttributesFields.mSource = GetFieldIDOrDie(env, audioAttributesClass, "mSource", "I");
     gAudioAttributesFields.mContentType =
@@ -186,6 +200,9 @@
     gAudioAttributesBuilderMethods.setUsage = GetMethodIDOrDie(
                 env, audioAttributesBuilderClass, "setUsage",
                 "(I)Landroid/media/AudioAttributes$Builder;");
+    gAudioAttributesBuilderMethods.setSystemUsage =
+            GetMethodIDOrDie(env, audioAttributesBuilderClass, "setSystemUsage",
+                             "(I)Landroid/media/AudioAttributes$Builder;");
     gAudioAttributesBuilderMethods.setInternalCapturePreset = GetMethodIDOrDie(
                 env, audioAttributesBuilderClass, "setInternalCapturePreset",
                 "(I)Landroid/media/AudioAttributes$Builder;");
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 79cf019..0156e23 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -307,6 +307,43 @@
     return kAudioStatusError;
 }
 
+static jint getVectorOfAudioDeviceTypeAddr(JNIEnv *env, jintArray deviceTypes,
+                                           jobjectArray deviceAddresses,
+                                           Vector<AudioDeviceTypeAddr> &audioDeviceTypeAddrVector) {
+    if (deviceTypes == nullptr || deviceAddresses == nullptr) {
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+    jsize deviceCount = env->GetArrayLength(deviceTypes);
+    if (deviceCount == 0 || deviceCount != env->GetArrayLength(deviceAddresses)) {
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+    // retrieve all device types
+    std::vector<audio_devices_t> deviceTypesVector;
+    jint *typesPtr = nullptr;
+    typesPtr = env->GetIntArrayElements(deviceTypes, 0);
+    if (typesPtr == nullptr) {
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+    for (jint i = 0; i < deviceCount; i++) {
+        deviceTypesVector.push_back((audio_devices_t)typesPtr[i]);
+    }
+    // check each address is a string and add device type/address to list
+    jclass stringClass = FindClassOrDie(env, "java/lang/String");
+    for (jint i = 0; i < deviceCount; i++) {
+        jobject addrJobj = env->GetObjectArrayElement(deviceAddresses, i);
+        if (!env->IsInstanceOf(addrJobj, stringClass)) {
+            return (jint)AUDIO_JAVA_BAD_VALUE;
+        }
+        const char *address = env->GetStringUTFChars((jstring)addrJobj, NULL);
+        AudioDeviceTypeAddr dev = AudioDeviceTypeAddr(typesPtr[i], address);
+        audioDeviceTypeAddrVector.add(dev);
+        env->ReleaseStringUTFChars((jstring)addrJobj, address);
+    }
+    env->ReleaseIntArrayElements(deviceTypes, typesPtr, 0);
+
+    return (jint)NO_ERROR;
+}
+
 static jint
 android_media_AudioSystem_muteMicrophone(JNIEnv *env, jobject thiz, jboolean on)
 {
@@ -1905,6 +1942,10 @@
             nCriterion.mValue.mUid = env->GetIntField(jCriterion,
                     gAudioMixMatchCriterionFields.mIntProp);
             break;
+        case RULE_MATCH_USERID:
+            nCriterion.mValue.mUserId =
+                    env->GetIntField(jCriterion, gAudioMixMatchCriterionFields.mIntProp);
+            break;
         case RULE_MATCH_ATTRIBUTE_USAGE:
         case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: {
             jobject jAttributes = env->GetObjectField(jCriterion, gAudioMixMatchCriterionFields.mAttr);
@@ -1990,39 +2031,11 @@
 
 static jint android_media_AudioSystem_setUidDeviceAffinities(JNIEnv *env, jobject clazz,
         jint uid, jintArray deviceTypes, jobjectArray deviceAddresses) {
-    if (deviceTypes == nullptr || deviceAddresses == nullptr) {
-        return (jint) AUDIO_JAVA_BAD_VALUE;
-    }
-    jsize nb = env->GetArrayLength(deviceTypes);
-    if (nb == 0 || nb != env->GetArrayLength(deviceAddresses)) {
-        return (jint) AUDIO_JAVA_BAD_VALUE;
-    }
-    // retrieve all device types
-    std::vector<audio_devices_t> deviceTypesVector;
-    jint* typesPtr = nullptr;
-    typesPtr = env->GetIntArrayElements(deviceTypes, 0);
-    if (typesPtr == nullptr) {
-        return (jint) AUDIO_JAVA_BAD_VALUE;
-    }
-    for (jint i = 0; i < nb; i++) {
-        deviceTypesVector.push_back((audio_devices_t) typesPtr[i]);
-    }
-
-    // check each address is a string and add device type/address to list for device affinity
     Vector<AudioDeviceTypeAddr> deviceVector;
-    jclass stringClass = FindClassOrDie(env, "java/lang/String");
-    for (jint i = 0; i < nb; i++) {
-        jobject addrJobj = env->GetObjectArrayElement(deviceAddresses, i);
-        if (!env->IsInstanceOf(addrJobj, stringClass)) {
-            return (jint) AUDIO_JAVA_BAD_VALUE;
-        }
-        const char* address = env->GetStringUTFChars((jstring) addrJobj, NULL);
-        AudioDeviceTypeAddr dev = AudioDeviceTypeAddr(typesPtr[i], address);
-        deviceVector.add(dev);
-        env->ReleaseStringUTFChars((jstring) addrJobj, address);
+    jint results = getVectorOfAudioDeviceTypeAddr(env, deviceTypes, deviceAddresses, deviceVector);
+    if (results != NO_ERROR) {
+        return results;
     }
-    env->ReleaseIntArrayElements(deviceTypes, typesPtr, 0);
-
     status_t status = AudioSystem::setUidDeviceAffinities((uid_t) uid, deviceVector);
     return (jint) nativeToJavaStatus(status);
 }
@@ -2033,6 +2046,23 @@
     return (jint) nativeToJavaStatus(status);
 }
 
+static jint android_media_AudioSystem_setUserIdDeviceAffinities(JNIEnv *env, jobject clazz,
+                                                                jint userId, jintArray deviceTypes,
+                                                                jobjectArray deviceAddresses) {
+    Vector<AudioDeviceTypeAddr> deviceVector;
+    jint results = getVectorOfAudioDeviceTypeAddr(env, deviceTypes, deviceAddresses, deviceVector);
+    if (results != NO_ERROR) {
+        return results;
+    }
+    status_t status = AudioSystem::setUserIdDeviceAffinities((int)userId, deviceVector);
+    return (jint)nativeToJavaStatus(status);
+}
+
+static jint android_media_AudioSystem_removeUserIdDeviceAffinities(JNIEnv *env, jobject clazz,
+                                                                   jint userId) {
+    status_t status = AudioSystem::removeUserIdDeviceAffinities((int)userId);
+    return (jint)nativeToJavaStatus(status);
+}
 
 static jint
 android_media_AudioSystem_systemReady(JNIEnv *env, jobject thiz)
@@ -2239,6 +2269,31 @@
     return AudioSystem::isHapticPlaybackSupported();
 }
 
+static jint android_media_AudioSystem_setSupportedSystemUsages(JNIEnv *env, jobject thiz,
+                                                               jintArray systemUsages) {
+    std::vector<audio_usage_t> nativeSystemUsagesVector;
+
+    if (systemUsages == nullptr) {
+        return (jint) AUDIO_JAVA_BAD_VALUE;
+    }
+
+    int *nativeSystemUsages = nullptr;
+    nativeSystemUsages = env->GetIntArrayElements(systemUsages, 0);
+
+    if (nativeSystemUsages != nullptr) {
+        jsize len = env->GetArrayLength(systemUsages);
+        for (size_t i = 0; i < len; i++) {
+            audio_usage_t nativeAudioUsage =
+                    static_cast<audio_usage_t>(nativeSystemUsages[i]);
+            nativeSystemUsagesVector.push_back(nativeAudioUsage);
+        }
+        env->ReleaseIntArrayElements(systemUsages, nativeSystemUsages, 0);
+    }
+
+    status_t status = AudioSystem::setSupportedSystemUsages(nativeSystemUsagesVector);
+    return (jint)nativeToJavaStatus(status);
+}
+
 static jint
 android_media_AudioSystem_setAllowedCapturePolicy(JNIEnv *env, jobject thiz, jint uid, jint flags) {
     return AudioSystem::setAllowedCapturePolicy(uid, flags);
@@ -2312,6 +2367,48 @@
     return jStatus;
 }
 
+static jint
+android_media_AudioSystem_getDevicesForAttributes(JNIEnv *env, jobject thiz,
+        jobject jaa, jobjectArray jDeviceArray)
+{
+    const jsize maxResultSize = env->GetArrayLength(jDeviceArray);
+    // the JNI is always expected to provide us with an array capable of holding enough
+    // devices i.e. the most we ever route a track to. This is preferred over receiving an ArrayList
+    // with reverse JNI to make the array grow as need as this would be less efficient, and some
+    // components call this method often
+    if (jDeviceArray == nullptr || maxResultSize == 0) {
+        ALOGE("%s invalid array to store AudioDeviceAddress", __FUNCTION__);
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+
+    JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
+    jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get());
+    if (jStatus != (jint) AUDIO_JAVA_SUCCESS) {
+        return jStatus;
+    }
+
+    AudioDeviceTypeAddrVector devices;
+    jStatus = check_AudioSystem_Command(
+            AudioSystem::getDevicesForAttributes(*(paa.get()), &devices));
+    if (jStatus != NO_ERROR) {
+        return jStatus;
+    }
+
+    if (devices.size() > maxResultSize) {
+        return AUDIO_JAVA_INVALID_OPERATION;
+    }
+    size_t index = 0;
+    jobject jAudioDeviceAddress = NULL;
+    for (const auto& device : devices) {
+        jStatus = createAudioDeviceAddressFromNative(env, &jAudioDeviceAddress, &device);
+        if (jStatus != AUDIO_JAVA_SUCCESS) {
+            return jStatus;
+        }
+        env->SetObjectArrayElement(jDeviceArray, index++, jAudioDeviceAddress);
+    }
+    return jStatus;
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gMethods[] = {
@@ -2388,6 +2485,7 @@
     {"isHapticPlaybackSupported", "()Z", (void *)android_media_AudioSystem_isHapticPlaybackSupported},
     {"getHwOffloadEncodingFormatsSupportedForA2DP", "(Ljava/util/ArrayList;)I",
                     (void*)android_media_AudioSystem_getHwOffloadEncodingFormatsSupportedForA2DP},
+    {"setSupportedSystemUsages", "([I)I", (void *)android_media_AudioSystem_setSupportedSystemUsages},
     {"setAllowedCapturePolicy", "(II)I", (void *)android_media_AudioSystem_setAllowedCapturePolicy},
     {"setRttEnabled",       "(Z)I",     (void *)android_media_AudioSystem_setRttEnabled},
     {"setAudioHalPids",  "([I)I", (void *)android_media_AudioSystem_setAudioHalPids},
@@ -2395,6 +2493,9 @@
     {"setPreferredDeviceForStrategy", "(IILjava/lang/String;)I", (void *)android_media_AudioSystem_setPreferredDeviceForStrategy},
     {"removePreferredDeviceForStrategy", "(I)I", (void *)android_media_AudioSystem_removePreferredDeviceForStrategy},
     {"getPreferredDeviceForStrategy", "(I[Landroid/media/AudioDeviceAddress;)I", (void *)android_media_AudioSystem_getPreferredDeviceForStrategy},
+    {"getDevicesForAttributes", "(Landroid/media/AudioAttributes;[Landroid/media/AudioDeviceAddress;)I", (void *)android_media_AudioSystem_getDevicesForAttributes},
+    {"setUserIdDeviceAffinities", "(I[I[Ljava/lang/String;)I", (void *)android_media_AudioSystem_setUserIdDeviceAffinities},
+    {"removeUserIdDeviceAffinities", "(I)I", (void *)android_media_AudioSystem_removeUserIdDeviceAffinities}
 };
 
 static const JNINativeMethod gEventHandlerMethods[] = {
@@ -2584,7 +2685,7 @@
     gMidAudioRecordRoutingProxy_release =
             android::GetMethodIDOrDie(env, gClsAudioRecordRoutingProxy, "native_release", "()V");
 
-    AudioSystem::setErrorCallback(android_media_AudioSystem_error_callback);
+    AudioSystem::addErrorCallback(android_media_AudioSystem_error_callback);
 
     RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
     return RegisterMethodsOrDie(env, kEventHandlerClassPathName, gEventHandlerMethods,
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index c979133..a888b43 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -102,6 +102,47 @@
     }
 };
 
+class TunerConfigurationHelper {
+    JNIEnv *const mEnv;
+    jobject const mTunerConfiguration;
+
+    struct Ids {
+        Ids(JNIEnv *env)
+              : mClass(FindClassOrDie(env, "android/media/AudioTrack$TunerConfiguration")),
+                mContentId(GetFieldIDOrDie(env, mClass, "mContentId", "I")),
+                mSyncId(GetFieldIDOrDie(env, mClass, "mSyncId", "I")) {}
+        const jclass mClass;
+        const jfieldID mContentId;
+        const jfieldID mSyncId;
+    };
+
+    static const Ids &getIds(JNIEnv *env) {
+        // Meyer's singleton, initializes first time control passes through
+        // declaration in a block and is thread-safe per ISO/IEC 14882:2011 6.7.4.
+        static Ids ids(env);
+        return ids;
+    }
+
+public:
+    TunerConfigurationHelper(JNIEnv *env, jobject tunerConfiguration)
+          : mEnv(env), mTunerConfiguration(tunerConfiguration) {}
+
+    int32_t getContentId() const {
+        if (mEnv == nullptr || mTunerConfiguration == nullptr) return 0;
+        const Ids &ids = getIds(mEnv);
+        return (int32_t)mEnv->GetIntField(mTunerConfiguration, ids.mContentId);
+    }
+
+    int32_t getSyncId() const {
+        if (mEnv == nullptr || mTunerConfiguration == nullptr) return 0;
+        const Ids &ids = getIds(mEnv);
+        return (int32_t)mEnv->GetIntField(mTunerConfiguration, ids.mSyncId);
+    }
+
+    // optional check to confirm class and field ids can be found.
+    static void initCheckOrDie(JNIEnv *env) { (void)getIds(env); }
+};
+
 static Mutex sLock;
 static SortedVector <audiotrack_callback_cookie *> sAudioTrackCallBackCookies;
 
@@ -213,24 +254,36 @@
 }
 
 // ----------------------------------------------------------------------------
-static jint
-android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this, jobject jaa,
-        jintArray jSampleRate, jint channelPositionMask, jint channelIndexMask,
-        jint audioFormat, jint buffSizeInBytes, jint memoryMode, jintArray jSession,
-        jlong nativeAudioTrack, jboolean offload) {
-
+static jint android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this,
+                                           jobject jaa, jintArray jSampleRate,
+                                           jint channelPositionMask, jint channelIndexMask,
+                                           jint audioFormat, jint buffSizeInBytes, jint memoryMode,
+                                           jintArray jSession, jlong nativeAudioTrack,
+                                           jboolean offload, jint encapsulationMode,
+                                           jobject tunerConfiguration) {
     ALOGV("sampleRates=%p, channel mask=%x, index mask=%x, audioFormat(Java)=%d, buffSize=%d,"
-        " nativeAudioTrack=0x%" PRIX64 ", offload=%d",
-        jSampleRate, channelPositionMask, channelIndexMask, audioFormat, buffSizeInBytes,
-        nativeAudioTrack, offload);
-
-    sp<AudioTrack> lpTrack = 0;
+          " nativeAudioTrack=0x%" PRIX64 ", offload=%d encapsulationMode=%d tuner=%p",
+          jSampleRate, channelPositionMask, channelIndexMask, audioFormat, buffSizeInBytes,
+          nativeAudioTrack, offload, encapsulationMode, tunerConfiguration);
 
     if (jSession == NULL) {
         ALOGE("Error creating AudioTrack: invalid session ID pointer");
         return (jint) AUDIO_JAVA_ERROR;
     }
 
+    // TODO: replace when we land matching AudioTrack::set() in frameworks/av in r or r-tv-dev.
+    if (tunerConfiguration != nullptr) {
+        const TunerConfigurationHelper tunerHelper(env, tunerConfiguration);
+        ALOGE("Error creating AudioTrack: unsupported tuner contentId:%d syncId:%d",
+              tunerHelper.getContentId(), tunerHelper.getSyncId());
+        return (jint)AUDIOTRACK_ERROR_SETUP_NATIVEINITFAILED;
+    }
+    // TODO: replace when we land matching AudioTrack::set() in frameworks/av in r or r-tv-dev.
+    if (encapsulationMode != 0 /* ENCAPSULATION_MODE_NONE */) {
+        ALOGE("Error creating AudioTrack: unsupported encapsulationMode %d", encapsulationMode);
+        return (jint)AUDIOTRACK_ERROR_SETUP_NATIVEINITFAILED;
+    }
+
     jint* nSession = (jint *) env->GetPrimitiveArrayCritical(jSession, NULL);
     if (nSession == NULL) {
         ALOGE("Error creating AudioTrack: Error retrieving session id pointer");
@@ -249,6 +302,7 @@
     }
 
     // if we pass in an existing *Native* AudioTrack, we don't need to create/initialize one.
+    sp<AudioTrack> lpTrack;
     if (nativeAudioTrack == 0) {
         if (jaa == 0) {
             ALOGE("Error creating AudioTrack: invalid audio attributes");
@@ -1301,85 +1355,149 @@
     lpTrack->setParameters(param.toString());
 }
 
+static jint android_media_AudioTrack_setAudioDescriptionMixLeveldB(JNIEnv *env, jobject thiz,
+                                                                   jfloat level) {
+    sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
+    if (lpTrack == nullptr) {
+        jniThrowException(env, "java/lang/IllegalStateException", "AudioTrack not initialized");
+        return (jint)AUDIO_JAVA_ERROR;
+    }
+
+    // TODO: replace in r-dev or r-tv-dev with code if HW is able to set audio mix level.
+    return (jint)AUDIO_JAVA_ERROR;
+}
+
+static jint android_media_AudioTrack_getAudioDescriptionMixLeveldB(JNIEnv *env, jobject thiz,
+                                                                   jfloatArray level) {
+    sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
+    if (lpTrack == nullptr) {
+        ALOGE("%s: AudioTrack not initialized", __func__);
+        return (jint)AUDIO_JAVA_ERROR;
+    }
+    jfloat *nativeLevel = (jfloat *)env->GetPrimitiveArrayCritical(level, NULL);
+    if (nativeLevel == nullptr) {
+        ALOGE("%s: Cannot retrieve level pointer", __func__);
+        return (jint)AUDIO_JAVA_ERROR;
+    }
+
+    // TODO: replace in r-dev or r-tv-dev with code if HW is able to set audio mix level.
+    // By contract we can return -infinity if unsupported.
+    *nativeLevel = -std::numeric_limits<float>::infinity();
+    env->ReleasePrimitiveArrayCritical(level, nativeLevel, 0 /* mode */);
+    nativeLevel = nullptr;
+    return (jint)AUDIO_JAVA_SUCCESS;
+}
+
+static jint android_media_AudioTrack_setDualMonoMode(JNIEnv *env, jobject thiz, jint dualMonoMode) {
+    sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
+    if (lpTrack == nullptr) {
+        jniThrowException(env, "java/lang/IllegalStateException", "AudioTrack not initialized");
+        return (jint)AUDIO_JAVA_ERROR;
+    }
+
+    // TODO: replace in r-dev or r-tv-dev with code if HW is able to set audio mix level.
+    return (jint)AUDIO_JAVA_ERROR;
+}
+
+static jint android_media_AudioTrack_getDualMonoMode(JNIEnv *env, jobject thiz,
+                                                     jintArray dualMonoMode) {
+    sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
+    if (lpTrack == nullptr) {
+        ALOGE("%s: AudioTrack not initialized", __func__);
+        return (jint)AUDIO_JAVA_ERROR;
+    }
+    jfloat *nativeDualMonoMode = (jfloat *)env->GetPrimitiveArrayCritical(dualMonoMode, NULL);
+    if (nativeDualMonoMode == nullptr) {
+        ALOGE("%s: Cannot retrieve dualMonoMode pointer", __func__);
+        return (jint)AUDIO_JAVA_ERROR;
+    }
+
+    // TODO: replace in r-dev or r-tv-dev with code if HW is able to select dual mono mode.
+    // By contract we can return DUAL_MONO_MODE_OFF if unsupported.
+    *nativeDualMonoMode = 0; // DUAL_MONO_MODE_OFF for now.
+    env->ReleasePrimitiveArrayCritical(dualMonoMode, nativeDualMonoMode, 0 /* mode */);
+    nativeDualMonoMode = nullptr;
+    return (jint)AUDIO_JAVA_SUCCESS;
+}
+
 // ----------------------------------------------------------------------------
 // ----------------------------------------------------------------------------
 static const JNINativeMethod gMethods[] = {
-    // name,              signature,     funcPtr
-    {"native_is_direct_output_supported",
-                             "(IIIIIII)Z",
-                                         (void *)android_media_AudioTrack_is_direct_output_supported},
-    {"native_start",         "()V",      (void *)android_media_AudioTrack_start},
-    {"native_stop",          "()V",      (void *)android_media_AudioTrack_stop},
-    {"native_pause",         "()V",      (void *)android_media_AudioTrack_pause},
-    {"native_flush",         "()V",      (void *)android_media_AudioTrack_flush},
-    {"native_setup",     "(Ljava/lang/Object;Ljava/lang/Object;[IIIIII[IJZ)I",
-                                         (void *)android_media_AudioTrack_setup},
-    {"native_finalize",      "()V",      (void *)android_media_AudioTrack_finalize},
-    {"native_release",       "()V",      (void *)android_media_AudioTrack_release},
-    {"native_write_byte",    "([BIIIZ)I",(void *)android_media_AudioTrack_writeArray<jbyteArray>},
-    {"native_write_native_bytes",
-                             "(Ljava/nio/ByteBuffer;IIIZ)I",
-                                         (void *)android_media_AudioTrack_write_native_bytes},
-    {"native_write_short",   "([SIIIZ)I",(void *)android_media_AudioTrack_writeArray<jshortArray>},
-    {"native_write_float",   "([FIIIZ)I",(void *)android_media_AudioTrack_writeArray<jfloatArray>},
-    {"native_setVolume",     "(FF)V",    (void *)android_media_AudioTrack_set_volume},
-    {"native_get_buffer_size_frames",
-                             "()I",      (void *)android_media_AudioTrack_get_buffer_size_frames},
-    {"native_set_buffer_size_frames",
-                             "(I)I",     (void *)android_media_AudioTrack_set_buffer_size_frames},
-    {"native_get_buffer_capacity_frames",
-                             "()I",      (void *)android_media_AudioTrack_get_buffer_capacity_frames},
-    {"native_set_playback_rate",
-                             "(I)I",     (void *)android_media_AudioTrack_set_playback_rate},
-    {"native_get_playback_rate",
-                             "()I",      (void *)android_media_AudioTrack_get_playback_rate},
-    {"native_set_playback_params",
-                             "(Landroid/media/PlaybackParams;)V",
-                                         (void *)android_media_AudioTrack_set_playback_params},
-    {"native_get_playback_params",
-                             "()Landroid/media/PlaybackParams;",
-                                         (void *)android_media_AudioTrack_get_playback_params},
-    {"native_set_marker_pos","(I)I",     (void *)android_media_AudioTrack_set_marker_pos},
-    {"native_get_marker_pos","()I",      (void *)android_media_AudioTrack_get_marker_pos},
-    {"native_set_pos_update_period",
-                             "(I)I",     (void *)android_media_AudioTrack_set_pos_update_period},
-    {"native_get_pos_update_period",
-                             "()I",      (void *)android_media_AudioTrack_get_pos_update_period},
-    {"native_set_position",  "(I)I",     (void *)android_media_AudioTrack_set_position},
-    {"native_get_position",  "()I",      (void *)android_media_AudioTrack_get_position},
-    {"native_get_latency",   "()I",      (void *)android_media_AudioTrack_get_latency},
-    {"native_get_underrun_count", "()I",      (void *)android_media_AudioTrack_get_underrun_count},
-    {"native_get_flags",     "()I",      (void *)android_media_AudioTrack_get_flags},
-    {"native_get_timestamp", "([J)I",    (void *)android_media_AudioTrack_get_timestamp},
-    {"native_getMetrics",    "()Landroid/os/PersistableBundle;",
-                                         (void *)android_media_AudioTrack_native_getMetrics},
-    {"native_set_loop",      "(III)I",   (void *)android_media_AudioTrack_set_loop},
-    {"native_reload_static", "()I",      (void *)android_media_AudioTrack_reload},
-    {"native_get_output_sample_rate",
-                             "(I)I",      (void *)android_media_AudioTrack_get_output_sample_rate},
-    {"native_get_min_buff_size",
-                             "(III)I",   (void *)android_media_AudioTrack_get_min_buff_size},
-    {"native_setAuxEffectSendLevel",
-                             "(F)I",     (void *)android_media_AudioTrack_setAuxEffectSendLevel},
-    {"native_attachAuxEffect",
-                             "(I)I",     (void *)android_media_AudioTrack_attachAuxEffect},
-    {"native_setOutputDevice", "(I)Z",
-                             (void *)android_media_AudioTrack_setOutputDevice},
-    {"native_getRoutedDeviceId", "()I", (void *)android_media_AudioTrack_getRoutedDeviceId},
-    {"native_enableDeviceCallback", "()V", (void *)android_media_AudioTrack_enableDeviceCallback},
-    {"native_disableDeviceCallback", "()V", (void *)android_media_AudioTrack_disableDeviceCallback},
-    {"native_applyVolumeShaper",
-            "(Landroid/media/VolumeShaper$Configuration;Landroid/media/VolumeShaper$Operation;)I",
-                                         (void *)android_media_AudioTrack_apply_volume_shaper},
-    {"native_getVolumeShaperState",
-            "(I)Landroid/media/VolumeShaper$State;",
-                                        (void *)android_media_AudioTrack_get_volume_shaper_state},
-    {"native_setPresentation", "(II)I", (void *)android_media_AudioTrack_setPresentation},
-    {"native_getPortId", "()I", (void *)android_media_AudioTrack_get_port_id},
-    {"native_set_delay_padding", "(II)V", (void *)android_media_AudioTrack_set_delay_padding},
+        // name,              signature,     funcPtr
+        {"native_is_direct_output_supported", "(IIIIIII)Z",
+         (void *)android_media_AudioTrack_is_direct_output_supported},
+        {"native_start", "()V", (void *)android_media_AudioTrack_start},
+        {"native_stop", "()V", (void *)android_media_AudioTrack_stop},
+        {"native_pause", "()V", (void *)android_media_AudioTrack_pause},
+        {"native_flush", "()V", (void *)android_media_AudioTrack_flush},
+        {"native_setup", "(Ljava/lang/Object;Ljava/lang/Object;[IIIIII[IJZILjava/lang/Object;)I",
+         (void *)android_media_AudioTrack_setup},
+        {"native_finalize", "()V", (void *)android_media_AudioTrack_finalize},
+        {"native_release", "()V", (void *)android_media_AudioTrack_release},
+        {"native_write_byte", "([BIIIZ)I", (void *)android_media_AudioTrack_writeArray<jbyteArray>},
+        {"native_write_native_bytes", "(Ljava/nio/ByteBuffer;IIIZ)I",
+         (void *)android_media_AudioTrack_write_native_bytes},
+        {"native_write_short", "([SIIIZ)I",
+         (void *)android_media_AudioTrack_writeArray<jshortArray>},
+        {"native_write_float", "([FIIIZ)I",
+         (void *)android_media_AudioTrack_writeArray<jfloatArray>},
+        {"native_setVolume", "(FF)V", (void *)android_media_AudioTrack_set_volume},
+        {"native_get_buffer_size_frames", "()I",
+         (void *)android_media_AudioTrack_get_buffer_size_frames},
+        {"native_set_buffer_size_frames", "(I)I",
+         (void *)android_media_AudioTrack_set_buffer_size_frames},
+        {"native_get_buffer_capacity_frames", "()I",
+         (void *)android_media_AudioTrack_get_buffer_capacity_frames},
+        {"native_set_playback_rate", "(I)I", (void *)android_media_AudioTrack_set_playback_rate},
+        {"native_get_playback_rate", "()I", (void *)android_media_AudioTrack_get_playback_rate},
+        {"native_set_playback_params", "(Landroid/media/PlaybackParams;)V",
+         (void *)android_media_AudioTrack_set_playback_params},
+        {"native_get_playback_params", "()Landroid/media/PlaybackParams;",
+         (void *)android_media_AudioTrack_get_playback_params},
+        {"native_set_marker_pos", "(I)I", (void *)android_media_AudioTrack_set_marker_pos},
+        {"native_get_marker_pos", "()I", (void *)android_media_AudioTrack_get_marker_pos},
+        {"native_set_pos_update_period", "(I)I",
+         (void *)android_media_AudioTrack_set_pos_update_period},
+        {"native_get_pos_update_period", "()I",
+         (void *)android_media_AudioTrack_get_pos_update_period},
+        {"native_set_position", "(I)I", (void *)android_media_AudioTrack_set_position},
+        {"native_get_position", "()I", (void *)android_media_AudioTrack_get_position},
+        {"native_get_latency", "()I", (void *)android_media_AudioTrack_get_latency},
+        {"native_get_underrun_count", "()I", (void *)android_media_AudioTrack_get_underrun_count},
+        {"native_get_flags", "()I", (void *)android_media_AudioTrack_get_flags},
+        {"native_get_timestamp", "([J)I", (void *)android_media_AudioTrack_get_timestamp},
+        {"native_getMetrics", "()Landroid/os/PersistableBundle;",
+         (void *)android_media_AudioTrack_native_getMetrics},
+        {"native_set_loop", "(III)I", (void *)android_media_AudioTrack_set_loop},
+        {"native_reload_static", "()I", (void *)android_media_AudioTrack_reload},
+        {"native_get_output_sample_rate", "(I)I",
+         (void *)android_media_AudioTrack_get_output_sample_rate},
+        {"native_get_min_buff_size", "(III)I", (void *)android_media_AudioTrack_get_min_buff_size},
+        {"native_setAuxEffectSendLevel", "(F)I",
+         (void *)android_media_AudioTrack_setAuxEffectSendLevel},
+        {"native_attachAuxEffect", "(I)I", (void *)android_media_AudioTrack_attachAuxEffect},
+        {"native_setOutputDevice", "(I)Z", (void *)android_media_AudioTrack_setOutputDevice},
+        {"native_getRoutedDeviceId", "()I", (void *)android_media_AudioTrack_getRoutedDeviceId},
+        {"native_enableDeviceCallback", "()V",
+         (void *)android_media_AudioTrack_enableDeviceCallback},
+        {"native_disableDeviceCallback", "()V",
+         (void *)android_media_AudioTrack_disableDeviceCallback},
+        {"native_applyVolumeShaper",
+         "(Landroid/media/VolumeShaper$Configuration;Landroid/media/VolumeShaper$Operation;)I",
+         (void *)android_media_AudioTrack_apply_volume_shaper},
+        {"native_getVolumeShaperState", "(I)Landroid/media/VolumeShaper$State;",
+         (void *)android_media_AudioTrack_get_volume_shaper_state},
+        {"native_setPresentation", "(II)I", (void *)android_media_AudioTrack_setPresentation},
+        {"native_getPortId", "()I", (void *)android_media_AudioTrack_get_port_id},
+        {"native_set_delay_padding", "(II)V", (void *)android_media_AudioTrack_set_delay_padding},
+        {"native_set_audio_description_mix_level_db", "(F)I",
+         (void *)android_media_AudioTrack_setAudioDescriptionMixLeveldB},
+        {"native_get_audio_description_mix_level_db", "([F)I",
+         (void *)android_media_AudioTrack_getAudioDescriptionMixLeveldB},
+        {"native_set_dual_mono_mode", "(I)I", (void *)android_media_AudioTrack_setDualMonoMode},
+        {"native_get_dual_mono_mode", "([I)I", (void *)android_media_AudioTrack_getDualMonoMode},
 };
 
-
 // field names found in android/media/AudioTrack.java
 #define JAVA_POSTEVENT_CALLBACK_NAME                    "postEventFromNative"
 #define JAVA_NATIVETRACKINJAVAOBJ_FIELD_NAME            "mNativeTrackInJavaObj"
@@ -1436,6 +1554,10 @@
     gPlaybackParamsFields.init(env);
 
     gVolumeShaperFields.init(env);
+
+    // optional check that the TunerConfiguration class and fields exist.
+    TunerConfigurationHelper::initCheckOrDie(env);
+
     return res;
 }
 
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 566c385..b523811 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -193,7 +193,8 @@
 {
     int err = memtrack_proc_get(p, pid);
     if (err != 0) {
-        ALOGW("failed to get memory consumption info: %d", err);
+        // The memtrack HAL may not be available, do not log to avoid flooding
+        // logcat.
         return err;
     }
 
diff --git a/core/jni/android_service_DataLoaderService.cpp b/core/jni/android_service_DataLoaderService.cpp
index a62d127..b307a73 100644
--- a/core/jni/android_service_DataLoaderService.cpp
+++ b/core/jni/android_service_DataLoaderService.cpp
@@ -35,24 +35,24 @@
 static jboolean nativeStartDataLoader(JNIEnv* env,
                                       jobject thiz,
                                       jint storageId) {
-    return DataLoaderService_OnStart(storageId);
+    return DataLoaderService_OnStart(env, storageId);
 }
 
 static jboolean nativeStopDataLoader(JNIEnv* env,
                                      jobject thiz,
                                      jint storageId) {
-    return DataLoaderService_OnStop(storageId);
+    return DataLoaderService_OnStop(env, storageId);
 }
 
 static jboolean nativeDestroyDataLoader(JNIEnv* env,
                                         jobject thiz,
                                         jint storageId) {
-    return DataLoaderService_OnDestroy(storageId);
+    return DataLoaderService_OnDestroy(env, storageId);
 }
 
 
 static jboolean nativePrepareImage(JNIEnv* env, jobject thiz, jint storageId, jobject addedFiles, jobject removedFiles) {
-    return DataLoaderService_OnPrepareImage(storageId, addedFiles, removedFiles);
+    return DataLoaderService_OnPrepareImage(env, storageId, addedFiles, removedFiles);
 }
 
 static void nativeWriteData(JNIEnv* env,
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index ff596b4..062b886 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -220,8 +220,12 @@
 
 // ----------------------------------------------------------------------------
 
+static std::unique_ptr<DynamicLibManager> sDynamicLibManager =
+    std::make_unique<DynamicLibManager>();
+
 // Let the opaque type AAssetManager refer to a guarded AssetManager2 instance.
 struct GuardedAssetManager : public ::AAssetManager {
+  GuardedAssetManager() : guarded_assetmanager(sDynamicLibManager.get()) {}
   Guarded<AssetManager2> guarded_assetmanager;
 };
 
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 0992beb..fb8e633 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -537,9 +537,9 @@
         LOGDEATH("Receiving binderDied() on JavaDeathRecipient %p\n", this);
         if (mObject != NULL) {
             JNIEnv* env = javavm_to_jnienv(mVM);
-
+            jobject jBinderProxy = javaObjectForIBinder(env, who.promote());
             env->CallStaticVoidMethod(gBinderProxyOffsets.mClass,
-                    gBinderProxyOffsets.mSendDeathNotice, mObject);
+                                      gBinderProxyOffsets.mSendDeathNotice, mObject, jBinderProxy);
             if (env->ExceptionCheck()) {
                 jthrowable excep = env->ExceptionOccurred();
                 report_exception(env, excep,
@@ -1532,8 +1532,9 @@
     gBinderProxyOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
     gBinderProxyOffsets.mGetInstance = GetStaticMethodIDOrDie(env, clazz, "getInstance",
             "(JJ)Landroid/os/BinderProxy;");
-    gBinderProxyOffsets.mSendDeathNotice = GetStaticMethodIDOrDie(env, clazz, "sendDeathNotice",
-            "(Landroid/os/IBinder$DeathRecipient;)V");
+    gBinderProxyOffsets.mSendDeathNotice =
+            GetStaticMethodIDOrDie(env, clazz, "sendDeathNotice",
+                                   "(Landroid/os/IBinder$DeathRecipient;Landroid/os/IBinder;)V");
     gBinderProxyOffsets.mNativeData = GetFieldIDOrDie(env, clazz, "mNativeData", "J");
 
     clazz = FindClassOrDie(env, "java/lang/Class");
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index ee6e8c4..89dbca8 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -50,11 +50,14 @@
 #include <pwd.h>
 #include <signal.h>
 #include <string.h>
+#include <sys/epoll.h>
 #include <sys/errno.h>
 #include <sys/resource.h>
 #include <sys/stat.h>
+#include <sys/syscall.h>
 #include <sys/sysinfo.h>
 #include <sys/types.h>
+#include <time.h>
 #include <unistd.h>
 
 #define GUARD_THREAD_PRIORITY 0
@@ -1271,39 +1274,78 @@
     return removeAllProcessGroups();
 }
 
+static void throwErrnoException(JNIEnv* env, const char* functionName, int error) {
+    ScopedLocalRef<jstring> detailMessage(env, env->NewStringUTF(functionName));
+    if (detailMessage.get() == NULL) {
+        // Not really much we can do here. We're probably dead in the water,
+        // but let's try to stumble on...
+        env->ExceptionClear();
+    }
+    static jclass errnoExceptionClass =
+            MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/system/ErrnoException"));
+
+    static jmethodID errnoExceptionCtor =
+            GetMethodIDOrDie(env, errnoExceptionClass, "<init>", "(Ljava/lang/String;I)V");
+
+    jobject exception =
+            env->NewObject(errnoExceptionClass, errnoExceptionCtor, detailMessage.get(), error);
+    env->Throw(reinterpret_cast<jthrowable>(exception));
+}
+
+// Wrapper function to the syscall pidfd_open, which creates a file
+// descriptor that refers to the process whose PID is specified in pid.
+static inline int sys_pidfd_open(pid_t pid, unsigned int flags) {
+    return syscall(__NR_pidfd_open, pid, flags);
+}
+
+static jboolean android_os_Process_nativePidFdOpen(JNIEnv* env, jobject, jint pid, jint flags) {
+    int fd = sys_pidfd_open(pid, flags);
+    if (fd < 0) {
+        throwErrnoException(env, "nativePidFdOpen", errno);
+        return -1;
+    }
+    return fd;
+}
+
 static const JNINativeMethod methods[] = {
-    {"getUidForName",       "(Ljava/lang/String;)I", (void*)android_os_Process_getUidForName},
-    {"getGidForName",       "(Ljava/lang/String;)I", (void*)android_os_Process_getGidForName},
-    {"setThreadPriority",   "(II)V", (void*)android_os_Process_setThreadPriority},
-    {"setThreadScheduler",  "(III)V", (void*)android_os_Process_setThreadScheduler},
-    {"setCanSelfBackground", "(Z)V", (void*)android_os_Process_setCanSelfBackground},
-    {"setThreadPriority",   "(I)V", (void*)android_os_Process_setCallingThreadPriority},
-    {"getThreadPriority",   "(I)I", (void*)android_os_Process_getThreadPriority},
-    {"getThreadScheduler",   "(I)I", (void*)android_os_Process_getThreadScheduler},
-    {"setThreadGroup",      "(II)V", (void*)android_os_Process_setThreadGroup},
-    {"setThreadGroupAndCpuset", "(II)V", (void*)android_os_Process_setThreadGroupAndCpuset},
-    {"setProcessGroup",     "(II)V", (void*)android_os_Process_setProcessGroup},
-    {"getProcessGroup",     "(I)I", (void*)android_os_Process_getProcessGroup},
-    {"getExclusiveCores",   "()[I", (void*)android_os_Process_getExclusiveCores},
-    {"setSwappiness",   "(IZ)Z", (void*)android_os_Process_setSwappiness},
-    {"setArgV0",    "(Ljava/lang/String;)V", (void*)android_os_Process_setArgV0},
-    {"setUid", "(I)I", (void*)android_os_Process_setUid},
-    {"setGid", "(I)I", (void*)android_os_Process_setGid},
-    {"sendSignal", "(II)V", (void*)android_os_Process_sendSignal},
-    {"sendSignalQuiet", "(II)V", (void*)android_os_Process_sendSignalQuiet},
-    {"getFreeMemory", "()J", (void*)android_os_Process_getFreeMemory},
-    {"getTotalMemory", "()J", (void*)android_os_Process_getTotalMemory},
-    {"readProcLines", "(Ljava/lang/String;[Ljava/lang/String;[J)V", (void*)android_os_Process_readProcLines},
-    {"getPids", "(Ljava/lang/String;[I)[I", (void*)android_os_Process_getPids},
-    {"readProcFile", "(Ljava/lang/String;[I[Ljava/lang/String;[J[F)Z", (void*)android_os_Process_readProcFile},
-    {"parseProcLine", "([BII[I[Ljava/lang/String;[J[F)Z", (void*)android_os_Process_parseProcLine},
-    {"getElapsedCpuTime", "()J", (void*)android_os_Process_getElapsedCpuTime},
-    {"getPss", "(I)J", (void*)android_os_Process_getPss},
-    {"getRss", "(I)[J", (void*)android_os_Process_getRss},
-    {"getPidsForCommands", "([Ljava/lang/String;)[I", (void*)android_os_Process_getPidsForCommands},
-    //{"setApplicationObject", "(Landroid/os/IBinder;)V", (void*)android_os_Process_setApplicationObject},
-    {"killProcessGroup", "(II)I", (void*)android_os_Process_killProcessGroup},
-    {"removeAllProcessGroups", "()V", (void*)android_os_Process_removeAllProcessGroups},
+        {"getUidForName", "(Ljava/lang/String;)I", (void*)android_os_Process_getUidForName},
+        {"getGidForName", "(Ljava/lang/String;)I", (void*)android_os_Process_getGidForName},
+        {"setThreadPriority", "(II)V", (void*)android_os_Process_setThreadPriority},
+        {"setThreadScheduler", "(III)V", (void*)android_os_Process_setThreadScheduler},
+        {"setCanSelfBackground", "(Z)V", (void*)android_os_Process_setCanSelfBackground},
+        {"setThreadPriority", "(I)V", (void*)android_os_Process_setCallingThreadPriority},
+        {"getThreadPriority", "(I)I", (void*)android_os_Process_getThreadPriority},
+        {"getThreadScheduler", "(I)I", (void*)android_os_Process_getThreadScheduler},
+        {"setThreadGroup", "(II)V", (void*)android_os_Process_setThreadGroup},
+        {"setThreadGroupAndCpuset", "(II)V", (void*)android_os_Process_setThreadGroupAndCpuset},
+        {"setProcessGroup", "(II)V", (void*)android_os_Process_setProcessGroup},
+        {"getProcessGroup", "(I)I", (void*)android_os_Process_getProcessGroup},
+        {"getExclusiveCores", "()[I", (void*)android_os_Process_getExclusiveCores},
+        {"setSwappiness", "(IZ)Z", (void*)android_os_Process_setSwappiness},
+        {"setArgV0", "(Ljava/lang/String;)V", (void*)android_os_Process_setArgV0},
+        {"setUid", "(I)I", (void*)android_os_Process_setUid},
+        {"setGid", "(I)I", (void*)android_os_Process_setGid},
+        {"sendSignal", "(II)V", (void*)android_os_Process_sendSignal},
+        {"sendSignalQuiet", "(II)V", (void*)android_os_Process_sendSignalQuiet},
+        {"getFreeMemory", "()J", (void*)android_os_Process_getFreeMemory},
+        {"getTotalMemory", "()J", (void*)android_os_Process_getTotalMemory},
+        {"readProcLines", "(Ljava/lang/String;[Ljava/lang/String;[J)V",
+         (void*)android_os_Process_readProcLines},
+        {"getPids", "(Ljava/lang/String;[I)[I", (void*)android_os_Process_getPids},
+        {"readProcFile", "(Ljava/lang/String;[I[Ljava/lang/String;[J[F)Z",
+         (void*)android_os_Process_readProcFile},
+        {"parseProcLine", "([BII[I[Ljava/lang/String;[J[F)Z",
+         (void*)android_os_Process_parseProcLine},
+        {"getElapsedCpuTime", "()J", (void*)android_os_Process_getElapsedCpuTime},
+        {"getPss", "(I)J", (void*)android_os_Process_getPss},
+        {"getRss", "(I)[J", (void*)android_os_Process_getRss},
+        {"getPidsForCommands", "([Ljava/lang/String;)[I",
+         (void*)android_os_Process_getPidsForCommands},
+        //{"setApplicationObject", "(Landroid/os/IBinder;)V",
+        //(void*)android_os_Process_setApplicationObject},
+        {"killProcessGroup", "(II)I", (void*)android_os_Process_killProcessGroup},
+        {"removeAllProcessGroups", "()V", (void*)android_os_Process_removeAllProcessGroups},
+        {"nativePidFdOpen", "(II)I", (void*)android_os_Process_nativePidFdOpen},
 };
 
 int register_android_os_Process(JNIEnv* env)
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 59dab0c8..649e5d2 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -40,10 +40,15 @@
 
 static const bool kDebugDispatchCycle = false;
 
+static const char* toString(bool value) {
+    return value ? "true" : "false";
+}
+
 static struct {
     jclass clazz;
 
     jmethodID dispatchInputEvent;
+    jmethodID onFocusEvent;
     jmethodID dispatchBatchedInputEventPending;
 } gInputEventReceiverClassInfo;
 
@@ -219,8 +224,7 @@
         bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
     if (kDebugDispatchCycle) {
         ALOGD("channel '%s' ~ Consuming input events, consumeBatches=%s, frameTime=%" PRId64,
-                getInputChannelName().c_str(),
-                consumeBatches ? "true" : "false", frameTime);
+              getInputChannelName().c_str(), toString(consumeBatches), frameTime);
     }
 
     if (consumeBatches) {
@@ -235,6 +239,7 @@
     for (;;) {
         uint32_t seq;
         InputEvent* inputEvent;
+
         status_t status = mInputConsumer.consume(&mInputEventFactory,
                 consumeBatches, frameTime, &seq, &inputEvent);
         if (status) {
@@ -302,6 +307,19 @@
                 inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
                 break;
             }
+            case AINPUT_EVENT_TYPE_FOCUS: {
+                FocusEvent* focusEvent = static_cast<FocusEvent*>(inputEvent);
+                if (kDebugDispatchCycle) {
+                    ALOGD("channel '%s' ~ Received focus event: hasFocus=%s, inTouchMode=%s.",
+                          getInputChannelName().c_str(), toString(focusEvent->getHasFocus()),
+                          toString(focusEvent->getInTouchMode()));
+                }
+                env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.onFocusEvent,
+                                    jboolean(focusEvent->getHasFocus()),
+                                    jboolean(focusEvent->getInTouchMode()));
+                finishInputEvent(seq, true /* handled */);
+                return OK;
+            }
 
             default:
                 assert(false); // InputConsumer should prevent this from ever happening
@@ -421,6 +439,8 @@
     gInputEventReceiverClassInfo.dispatchInputEvent = GetMethodIDOrDie(env,
             gInputEventReceiverClassInfo.clazz,
             "dispatchInputEvent", "(ILandroid/view/InputEvent;)V");
+    gInputEventReceiverClassInfo.onFocusEvent =
+            GetMethodIDOrDie(env, gInputEventReceiverClassInfo.clazz, "onFocusEvent", "(ZZ)V");
     gInputEventReceiverClassInfo.dispatchBatchedInputEventPending = GetMethodIDOrDie(env,
             gInputEventReceiverClassInfo.clazz, "dispatchBatchedInputEventPending", "()V");
 
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 01b5920..b01083b 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -413,6 +413,12 @@
     return anw->perform(surface, NATIVE_WINDOW_SET_AUTO_REFRESH, int(enabled));
 }
 
+static jint nativeSetFrameRate(JNIEnv* env, jclass clazz, jlong nativeObject, jfloat frameRate) {
+    Surface* surface = reinterpret_cast<Surface*>(nativeObject);
+    ANativeWindow* anw = static_cast<ANativeWindow*>(surface);
+    return anw->perform(surface, NATIVE_WINDOW_SET_FRAME_RATE, float(frameRate));
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gSurfaceMethods[] = {
@@ -447,6 +453,7 @@
             (void*)nativeAttachAndQueueBufferWithColorSpace},
     {"nativeSetSharedBufferModeEnabled", "(JZ)I", (void*)nativeSetSharedBufferModeEnabled},
     {"nativeSetAutoRefreshEnabled", "(JZ)I", (void*)nativeSetAutoRefreshEnabled},
+    {"nativeSetFrameRate", "(JF)I", (void*)nativeSetFrameRate},
 };
 
 int register_android_view_Surface(JNIEnv* env)
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 4a7276c..2b9d454 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -263,7 +263,8 @@
     status_t res = ScreenshotClient::capture(displayToken, dataspace,
             ui::PixelFormat::RGBA_8888,
             sourceCrop, width, height,
-            useIdentityTransform, rotation, captureSecureLayers, &buffer, capturedSecureLayers);
+            useIdentityTransform, ui::toRotation(rotation),
+            captureSecureLayers, &buffer, capturedSecureLayers);
     if (res != NO_ERROR) {
         return NULL;
     }
@@ -422,6 +423,14 @@
     transaction->setFlags(ctrl, flags, mask);
 }
 
+static void nativeSetFrameRateSelectionPriority(JNIEnv* env, jclass clazz, jlong transactionObj,
+        jlong nativeObject, jint priority) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+    SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
+    transaction->setFrameRateSelectionPriority(ctrl, priority);
+}
+
 static void nativeSetTransparentRegionHint(JNIEnv* env, jclass clazz, jlong transactionObj,
         jlong nativeObject, jobject regionObj) {
     SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
@@ -551,6 +560,14 @@
     transaction->setCornerRadius(ctrl, cornerRadius);
 }
 
+static void nativeSetBackgroundBlurRadius(JNIEnv* env, jclass clazz, jlong transactionObj,
+         jlong nativeObject, jint blurRadius) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+    SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
+    transaction->setBackgroundBlurRadius(ctrl, blurRadius);
+}
+
 static void nativeSetLayerStack(JNIEnv* env, jclass clazz, jlong transactionObj,
         jlong nativeObject, jint layerStack) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -567,6 +584,14 @@
     transaction->setShadowRadius(ctrl, shadowRadius);
 }
 
+static void nativeSetFrameRate(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject,
+                               jfloat frameRate) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+    const auto ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+    transaction->setFrameRate(ctrl, frameRate);
+}
+
 static jlongArray nativeGetPhysicalDisplayIds(JNIEnv* env, jclass clazz) {
     const auto displayIds = SurfaceComposerClient::getPhysicalDisplayIds();
     jlongArray array = env->NewLongArray(displayIds.size());
@@ -724,7 +749,8 @@
 
     {
         auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
-        transaction->setDisplayProjection(token, orientation, layerStackRect, displayRect);
+        transaction->setDisplayProjection(token, static_cast<ui::Rotation>(orientation),
+                                          layerStackRect, displayRect);
     }
 }
 
@@ -816,13 +842,6 @@
     return static_cast<jint>(SurfaceComposerClient::getActiveConfig(token));
 }
 
-static jboolean nativeSetActiveConfig(JNIEnv* env, jclass clazz, jobject tokenObj, jint id) {
-    sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
-    if (token == NULL) return JNI_FALSE;
-    status_t err = SurfaceComposerClient::setActiveConfig(token, static_cast<int>(id));
-    return err == NO_ERROR ? JNI_TRUE : JNI_FALSE;
-}
-
 static jintArray nativeGetDisplayColorModes(JNIEnv* env, jclass, jobject tokenObj) {
     sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
     if (token == NULL) return NULL;
@@ -1360,14 +1379,20 @@
             (void*)nativeSetColorSpaceAgnostic },
     {"nativeSetFlags", "(JJII)V",
             (void*)nativeSetFlags },
+    {"nativeSetFrameRateSelectionPriority", "(JJI)V",
+            (void*)nativeSetFrameRateSelectionPriority },
     {"nativeSetWindowCrop", "(JJIIII)V",
             (void*)nativeSetWindowCrop },
     {"nativeSetCornerRadius", "(JJF)V",
             (void*)nativeSetCornerRadius },
+    {"nativeSetBackgroundBlurRadius", "(JJI)V",
+            (void*)nativeSetBackgroundBlurRadius },
     {"nativeSetLayerStack", "(JJI)V",
             (void*)nativeSetLayerStack },
     {"nativeSetShadowRadius", "(JJF)V",
             (void*)nativeSetShadowRadius },
+    {"nativeSetFrameRate", "(JJF)V",
+            (void*)nativeSetFrameRate },
     {"nativeGetPhysicalDisplayIds", "()[J",
             (void*)nativeGetPhysicalDisplayIds },
     {"nativeGetPhysicalDisplayToken", "(J)Landroid/os/IBinder;",
@@ -1388,8 +1413,6 @@
             (void*)nativeGetDisplayConfigs },
     {"nativeGetActiveConfig", "(Landroid/os/IBinder;)I",
             (void*)nativeGetActiveConfig },
-    {"nativeSetActiveConfig", "(Landroid/os/IBinder;I)Z",
-            (void*)nativeSetActiveConfig },
     {"nativeSetDesiredDisplayConfigSpecs",
             "(Landroid/os/IBinder;Landroid/view/SurfaceControl$DesiredDisplayConfigSpecs;)Z",
             (void*)nativeSetDesiredDisplayConfigSpecs },
diff --git a/core/jni/android_view_TextureLayer.cpp b/core/jni/android_view_TextureLayer.cpp
index 5491a33..40f6180 100644
--- a/core/jni/android_view_TextureLayer.cpp
+++ b/core/jni/android_view_TextureLayer.cpp
@@ -19,12 +19,9 @@
 #include "jni.h"
 #include <nativehelper/JNIHelp.h>
 
+#include <android/surface_texture_jni.h>
 #include "core_jni_helpers.h"
-#include <android_runtime/android_graphics_SurfaceTexture.h>
 
-#include <gui/IGraphicBufferProducer.h>
-#include <gui/surfacetexture/surface_texture_platform.h>
-#include <gui/surfacetexture/SurfaceTexture.h>
 #include <hwui/Paint.h>
 #include <SkMatrix.h>
 #include <DeferredLayerUpdater.h>
@@ -61,10 +58,8 @@
 static void TextureLayer_setSurfaceTexture(JNIEnv* env, jobject clazz,
         jlong layerUpdaterPtr, jobject surface) {
     DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr);
-    auto consumer = SurfaceTexture_getSurfaceTexture(env, surface);
-    auto producer = SurfaceTexture_getProducer(env, surface);
-    layer->setSurfaceTexture(AutoTextureRelease(
-            ASurfaceTexture_create(consumer, producer)));
+    ASurfaceTexture* surfaceTexture = ASurfaceTexture_fromSurfaceTexture(env, surface);
+    layer->setSurfaceTexture(AutoTextureRelease(surfaceTexture, &ASurfaceTexture_release));
 }
 
 static void TextureLayer_updateSurfaceTexture(JNIEnv* env, jobject clazz,
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index d17d0a4..4b969ec 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -67,6 +67,7 @@
 #include <sys/stat.h>
 #include <sys/time.h>
 #include <sys/types.h>
+#include <sys/un.h>
 #include <sys/utsname.h>
 #include <sys/wait.h>
 #include <unistd.h>
@@ -163,6 +164,12 @@
  */
 static int gUsapPoolEventFD = -1;
 
+/**
+ * The socket file descriptor used to send notifications to the
+ * system_server.
+ */
+static int gSystemServerSocketFd = -1;
+
 static constexpr int DEFAULT_DATA_DIR_PERMISSION = 0751;
 
 /**
@@ -174,6 +181,8 @@
 static constexpr const uint64_t UPPER_HALF_WORD_MASK = 0xFFFF'FFFF'0000'0000;
 static constexpr const uint64_t LOWER_HALF_WORD_MASK = 0x0000'0000'FFFF'FFFF;
 
+static constexpr const char* kCurProfileDirPath = "/data/misc/profiles/cur";
+
 /**
  * The maximum value that the gUSAPPoolSizeMax variable may take.  This value
  * is a mirror of ZygoteServer.USAP_POOL_SIZE_MAX_LIMIT
@@ -319,7 +328,8 @@
   MOUNT_EXTERNAL_INSTALLER = 5,
   MOUNT_EXTERNAL_FULL = 6,
   MOUNT_EXTERNAL_PASS_THROUGH = 7,
-  MOUNT_EXTERNAL_COUNT = 8
+  MOUNT_EXTERNAL_ANDROID_WRITABLE = 8,
+  MOUNT_EXTERNAL_COUNT = 9
 };
 
 // The order of entries here must be kept in sync with MountExternalKind enum values.
@@ -331,6 +341,8 @@
   "/mnt/runtime/write",   // MOUNT_EXTERNAL_LEGACY
   "/mnt/runtime/write",   // MOUNT_EXTERNAL_INSTALLER
   "/mnt/runtime/full",    // MOUNT_EXTERNAL_FULL
+  "/mnt/runtime/full",    // MOUNT_EXTERNAL_PASS_THROUGH (only used w/ FUSE)
+  "/mnt/runtime/full",    // MOUNT_EXTERNAL_ANDROID_WRITABLE (only used w/ FUSE)
 };
 
 // Must match values in com.android.internal.os.Zygote.
@@ -339,6 +351,26 @@
   PROFILE_FROM_SHELL = 1 << 15,
 };
 
+enum UnsolicitedZygoteMessageTypes : uint32_t {
+    UNSOLICITED_ZYGOTE_MESSAGE_TYPE_RESERVED = 0,
+    UNSOLICITED_ZYGOTE_MESSAGE_TYPE_SIGCHLD = 1,
+};
+
+struct UnsolicitedZygoteMessageSigChld {
+    struct {
+        UnsolicitedZygoteMessageTypes type;
+    } header;
+    struct {
+        pid_t pid;
+        uid_t uid;
+        int status;
+    } payload;
+};
+
+// Keep sync with services/core/java/com/android/server/am/ProcessList.java
+static constexpr struct sockaddr_un kSystemServerSockAddr =
+        {.sun_family = AF_LOCAL, .sun_path = "/data/system/unsolzygotesocket"};
+
 // Forward declaration so we don't have to move the signal handler.
 static bool RemoveUsapTableEntry(pid_t usap_pid);
 
@@ -348,74 +380,107 @@
   env->FatalError(oss.str().c_str());
 }
 
+// Create the socket which is going to be used to send unsolicited message
+// to system_server, the socket will be closed post forking a child process.
+// It's expected to be called at each zygote's initialization.
+static void initUnsolSocketToSystemServer() {
+    gSystemServerSocketFd = socket(AF_LOCAL, SOCK_DGRAM | SOCK_NONBLOCK, 0);
+    if (gSystemServerSocketFd >= 0) {
+        ALOGV("Zygote:systemServerSocketFD = %d", gSystemServerSocketFd);
+    } else {
+        ALOGE("Unable to create socket file descriptor to connect to system_server");
+    }
+}
+
+static void sendSigChildStatus(const pid_t pid, const uid_t uid, const int status) {
+    int socketFd = gSystemServerSocketFd;
+    if (socketFd >= 0) {
+        // fill the message buffer
+        struct UnsolicitedZygoteMessageSigChld data =
+                {.header = {.type = UNSOLICITED_ZYGOTE_MESSAGE_TYPE_SIGCHLD},
+                 .payload = {.pid = pid, .uid = uid, .status = status}};
+        if (TEMP_FAILURE_RETRY(
+                    sendto(socketFd, &data, sizeof(data), 0,
+                           reinterpret_cast<const struct sockaddr*>(&kSystemServerSockAddr),
+                           sizeof(kSystemServerSockAddr))) == -1) {
+            async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG,
+                                  "Zygote failed to write to system_server FD: %s",
+                                  strerror(errno));
+        }
+    }
+}
+
 // This signal handler is for zygote mode, since the zygote must reap its children
-static void SigChldHandler(int /*signal_number*/) {
-  pid_t pid;
-  int status;
-  int64_t usaps_removed = 0;
+static void SigChldHandler(int /*signal_number*/, siginfo_t* info, void* /*ucontext*/) {
+    pid_t pid;
+    int status;
+    int64_t usaps_removed = 0;
 
-  // It's necessary to save and restore the errno during this function.
-  // Since errno is stored per thread, changing it here modifies the errno
-  // on the thread on which this signal handler executes. If a signal occurs
-  // between a call and an errno check, it's possible to get the errno set
-  // here.
-  // See b/23572286 for extra information.
-  int saved_errno = errno;
+    // It's necessary to save and restore the errno during this function.
+    // Since errno is stored per thread, changing it here modifies the errno
+    // on the thread on which this signal handler executes. If a signal occurs
+    // between a call and an errno check, it's possible to get the errno set
+    // here.
+    // See b/23572286 for extra information.
+    int saved_errno = errno;
 
-  while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
-     // Log process-death status that we care about.
-    if (WIFEXITED(status)) {
-      async_safe_format_log(ANDROID_LOG_INFO, LOG_TAG,
-                            "Process %d exited cleanly (%d)", pid, WEXITSTATUS(status));
+    while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
+        // Notify system_server that we received a SIGCHLD
+        sendSigChildStatus(pid, info->si_uid, status);
+        // Log process-death status that we care about.
+        if (WIFEXITED(status)) {
+            async_safe_format_log(ANDROID_LOG_INFO, LOG_TAG, "Process %d exited cleanly (%d)", pid,
+                                  WEXITSTATUS(status));
 
-      // Check to see if the PID is in the USAP pool and remove it if it is.
-      if (RemoveUsapTableEntry(pid)) {
-        ++usaps_removed;
-      }
-    } else if (WIFSIGNALED(status)) {
-      async_safe_format_log(ANDROID_LOG_INFO, LOG_TAG,
-                            "Process %d exited due to signal %d (%s)%s", pid,
-                            WTERMSIG(status), strsignal(WTERMSIG(status)),
-                            WCOREDUMP(status) ? "; core dumped" : "");
+            // Check to see if the PID is in the USAP pool and remove it if it is.
+            if (RemoveUsapTableEntry(pid)) {
+                ++usaps_removed;
+            }
+        } else if (WIFSIGNALED(status)) {
+            async_safe_format_log(ANDROID_LOG_INFO, LOG_TAG,
+                                  "Process %d exited due to signal %d (%s)%s", pid,
+                                  WTERMSIG(status), strsignal(WTERMSIG(status)),
+                                  WCOREDUMP(status) ? "; core dumped" : "");
 
-      // If the process exited due to a signal other than SIGTERM, check to see
-      // if the PID is in the USAP pool and remove it if it is.  If the process
-      // was closed by the Zygote using SIGTERM then the USAP pool entry will
-      // have already been removed (see nativeEmptyUsapPool()).
-      if (WTERMSIG(status) != SIGTERM && RemoveUsapTableEntry(pid)) {
-        ++usaps_removed;
-      }
+            // If the process exited due to a signal other than SIGTERM, check to see
+            // if the PID is in the USAP pool and remove it if it is.  If the process
+            // was closed by the Zygote using SIGTERM then the USAP pool entry will
+            // have already been removed (see nativeEmptyUsapPool()).
+            if (WTERMSIG(status) != SIGTERM && RemoveUsapTableEntry(pid)) {
+                ++usaps_removed;
+            }
+        }
+
+        // If the just-crashed process is the system_server, bring down zygote
+        // so that it is restarted by init and system server will be restarted
+        // from there.
+        if (pid == gSystemServerPid) {
+            async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG,
+                                  "Exit zygote because system server (pid %d) has terminated", pid);
+            kill(getpid(), SIGKILL);
+        }
     }
 
-    // If the just-crashed process is the system_server, bring down zygote
-    // so that it is restarted by init and system server will be restarted
-    // from there.
-    if (pid == gSystemServerPid) {
-      async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG,
-                            "Exit zygote because system server (pid %d) has terminated", pid);
-      kill(getpid(), SIGKILL);
+    // Note that we shouldn't consider ECHILD an error because
+    // the secondary zygote might have no children left to wait for.
+    if (pid < 0 && errno != ECHILD) {
+        async_safe_format_log(ANDROID_LOG_WARN, LOG_TAG, "Zygote SIGCHLD error in waitpid: %s",
+                              strerror(errno));
     }
-  }
 
-  // Note that we shouldn't consider ECHILD an error because
-  // the secondary zygote might have no children left to wait for.
-  if (pid < 0 && errno != ECHILD) {
-    async_safe_format_log(ANDROID_LOG_WARN, LOG_TAG,
-                          "Zygote SIGCHLD error in waitpid: %s", strerror(errno));
-  }
-
-  if (usaps_removed > 0) {
-    if (TEMP_FAILURE_RETRY(write(gUsapPoolEventFD, &usaps_removed, sizeof(usaps_removed))) == -1) {
-      // If this write fails something went terribly wrong.  We will now kill
-      // the zygote and let the system bring it back up.
-      async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG,
-                            "Zygote failed to write to USAP pool event FD: %s",
-                            strerror(errno));
-      kill(getpid(), SIGKILL);
+    if (usaps_removed > 0) {
+        if (TEMP_FAILURE_RETRY(write(gUsapPoolEventFD, &usaps_removed, sizeof(usaps_removed))) ==
+            -1) {
+            // If this write fails something went terribly wrong.  We will now kill
+            // the zygote and let the system bring it back up.
+            async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG,
+                                  "Zygote failed to write to USAP pool event FD: %s",
+                                  strerror(errno));
+            kill(getpid(), SIGKILL);
+        }
     }
-  }
 
-  errno = saved_errno;
+    errno = saved_errno;
 }
 
 // Configures the SIGCHLD/SIGHUP handlers for the zygote process. This is
@@ -436,12 +501,11 @@
 // This ends up being called repeatedly before each fork(), but there's
 // no real harm in that.
 static void SetSignalHandlers() {
-  struct sigaction sig_chld = {};
-  sig_chld.sa_handler = SigChldHandler;
+    struct sigaction sig_chld = {.sa_flags = SA_SIGINFO, .sa_sigaction = SigChldHandler};
 
-  if (sigaction(SIGCHLD, &sig_chld, nullptr) < 0) {
-    ALOGW("Error setting SIGCHLD handler: %s", strerror(errno));
-  }
+    if (sigaction(SIGCHLD, &sig_chld, nullptr) < 0) {
+        ALOGW("Error setting SIGCHLD handler: %s", strerror(errno));
+    }
 
   struct sigaction sig_hup = {};
   sig_hup.sa_handler = SIG_IGN;
@@ -743,9 +807,6 @@
 
   const userid_t user_id = multiuser_get_user_id(uid);
   const std::string user_source = StringPrintf("/mnt/user/%d", user_id);
-  const std::string pass_through_source = StringPrintf("/mnt/pass_through/%d", user_id);
-  bool isFuse = GetBoolProperty(kPropFuse, false);
-
   // Shell is neither AID_ROOT nor AID_EVERYBODY. Since it equally needs 'execute' access to
   // /mnt/user/0 to 'adb shell ls /sdcard' for instance, we set the uid bit of /mnt/user/0 to
   // AID_SHELL. This gives shell access along with apps running as group everybody (user 0 apps)
@@ -754,14 +815,15 @@
   PrepareDir(user_source, 0710, user_id ? AID_ROOT : AID_SHELL,
              multiuser_get_uid(user_id, AID_EVERYBODY), fail_fn);
 
+  bool isFuse = GetBoolProperty(kPropFuse, false);
+
   if (isFuse) {
-    if (mount_mode == MOUNT_EXTERNAL_PASS_THROUGH || mount_mode ==
-        MOUNT_EXTERNAL_INSTALLER || mount_mode == MOUNT_EXTERNAL_FULL) {
-      // For now, MediaProvider, installers and "full" get the pass_through mount
-      // view, which is currently identical to the sdcardfs write view.
-      //
-      // TODO(b/146189163): scope down MOUNT_EXTERNAL_INSTALLER
+    if (mount_mode == MOUNT_EXTERNAL_PASS_THROUGH) {
+      const std::string pass_through_source = StringPrintf("/mnt/pass_through/%d", user_id);
       BindMount(pass_through_source, "/storage", fail_fn);
+    } else if (mount_mode == MOUNT_EXTERNAL_INSTALLER) {
+      const std::string installer_source = StringPrintf("/mnt/installer/%d", user_id);
+      BindMount(installer_source, "/storage", fail_fn);
     } else {
       BindMount(user_source, "/storage", fail_fn);
     }
@@ -1046,6 +1108,9 @@
 
     // Turn fdsan back on.
     android_fdsan_set_error_level(fdsan_error_level);
+
+    // Reset the fd to the unsolicited zygote socket
+    gSystemServerSocketFd = -1;
   } else {
     ALOGD("Forked child process %d", pid);
   }
@@ -1091,19 +1156,19 @@
       fail_fn(CREATE_ERROR("Unexpected error in getAppDataDirName: %s", strerror(errno)));
       return nullptr;
     }
-    // Directory doesn't exist, try to search the name from inode
-    DIR* dir = opendir(parent_path.data());
-    if (dir == nullptr) {
-      fail_fn(CREATE_ERROR("Failed to opendir %s", parent_path.data()));
-    }
-    struct dirent* ent;
-    while ((ent = readdir(dir))) {
-      if (ent->d_ino == ce_data_inode) {
-        closedir(dir);
-        return ent->d_name;
+    {
+      // Directory doesn't exist, try to search the name from inode
+      std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(parent_path.data()), closedir);
+      if (dir == nullptr) {
+        fail_fn(CREATE_ERROR("Failed to opendir %s", parent_path.data()));
+      }
+      struct dirent* ent;
+      while ((ent = readdir(dir.get()))) {
+        if (ent->d_ino == ce_data_inode) {
+          return ent->d_name;
+        }
       }
     }
-    closedir(dir);
 
     // Fallback due to b/145989852, ce_data_inode stored in package manager may be corrupted
     // if ino_t is 32 bits.
@@ -1114,19 +1179,18 @@
       fixed_ce_data_inode = ((ce_data_inode >> 32) & LOWER_HALF_WORD_MASK);
     }
     if (fixed_ce_data_inode != 0) {
-      dir = opendir(parent_path.data());
+      std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(parent_path.data()), closedir);
       if (dir == nullptr) {
         fail_fn(CREATE_ERROR("Failed to opendir %s", parent_path.data()));
       }
-      while ((ent = readdir(dir))) {
+      struct dirent* ent;
+      while ((ent = readdir(dir.get()))) {
         if (ent->d_ino == fixed_ce_data_inode) {
           long long d_ino = ent->d_ino;
           ALOGW("Fallback success inode %lld -> %lld", ce_data_inode, d_ino);
-          closedir(dir);
           return ent->d_name;
         }
       }
-      closedir(dir);
     }
     // Fallback done
 
@@ -1155,6 +1219,36 @@
   createAndMountAppData(package_name, ce_data_path, mirrorCePath, actualCePath, fail_fn);
 }
 
+// Relabel directory
+static void relabelDir(const char* path, security_context_t context, fail_fn_t fail_fn) {
+  if (setfilecon(path, context) != 0) {
+    fail_fn(CREATE_ERROR("Failed to setfilecon %s %s", path, strerror(errno)));
+  }
+}
+
+// Relabel all directories under a path non-recursively.
+static void relabelAllDirs(const char* path, security_context_t context, fail_fn_t fail_fn) {
+  DIR* dir = opendir(path);
+  if (dir == nullptr) {
+    fail_fn(CREATE_ERROR("Failed to opendir %s", path));
+  }
+  struct dirent* ent;
+  while ((ent = readdir(dir))) {
+    if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue;
+    auto filePath = StringPrintf("%s/%s", path, ent->d_name);
+    if (ent->d_type == DT_DIR) {
+      relabelDir(filePath.c_str(), context, fail_fn);
+    } else if (ent->d_type == DT_LNK) {
+      if (lsetfilecon(filePath.c_str(), context) != 0) {
+        fail_fn(CREATE_ERROR("Failed to lsetfilecon %s %s", filePath.c_str(), strerror(errno)));
+      }
+    } else {
+      fail_fn(CREATE_ERROR("Unexpected type: %d %s", ent->d_type, filePath.c_str()));
+    }
+  }
+  closedir(dir);
+}
+
 /**
  * Make other apps data directory not visible in CE, DE storage.
  *
@@ -1214,10 +1308,36 @@
   snprintf(internalDePath, PATH_MAX, "/data/user_de");
   snprintf(externalPrivateMountPath, PATH_MAX, "/mnt/expand");
 
+  security_context_t dataDataContext = nullptr;
+  if (getfilecon(internalDePath, &dataDataContext) < 0) {
+    fail_fn(CREATE_ERROR("Unable to getfilecon on %s %s", internalDePath,
+        strerror(errno)));
+  }
+
   MountAppDataTmpFs(internalLegacyCePath, fail_fn);
   MountAppDataTmpFs(internalCePath, fail_fn);
   MountAppDataTmpFs(internalDePath, fail_fn);
-  MountAppDataTmpFs(externalPrivateMountPath, fail_fn);
+
+  // Mount tmpfs on all external vols DE and CE storage
+  DIR* dir = opendir(externalPrivateMountPath);
+  if (dir == nullptr) {
+    fail_fn(CREATE_ERROR("Failed to opendir %s", externalPrivateMountPath));
+  }
+  struct dirent* ent;
+  while ((ent = readdir(dir))) {
+    if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue;
+    if (ent->d_type != DT_DIR) {
+      fail_fn(CREATE_ERROR("Unexpected type: %d %s", ent->d_type, ent->d_name));
+    }
+    auto volPath = StringPrintf("%s/%s", externalPrivateMountPath, ent->d_name);
+    auto cePath = StringPrintf("%s/user", volPath.c_str());
+    auto dePath = StringPrintf("%s/user_de", volPath.c_str());
+    MountAppDataTmpFs(cePath.c_str(), fail_fn);
+    MountAppDataTmpFs(dePath.c_str(), fail_fn);
+  }
+  closedir(dir);
+
+  bool legacySymlinkCreated = false;
 
   for (int i = 0; i < size; i += 3) {
     jstring package_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i));
@@ -1265,7 +1385,14 @@
       // If it's user 0, create a symlink /data/user/0 -> /data/data,
       // otherwise create /data/user/$USER
       if (userId == 0) {
-        symlink(internalLegacyCePath, internalCeUserPath);
+        if (!legacySymlinkCreated) {
+          legacySymlinkCreated = true;
+          int result = symlink(internalLegacyCePath, internalCeUserPath);
+          if (result != 0) {
+             fail_fn(CREATE_ERROR("Failed to create symlink %s %s", internalCeUserPath,
+              strerror(errno)));
+          }
+        }
         actualCePath = internalLegacyCePath;
       } else {
         PrepareDirIfNotPresent(internalCeUserPath, DEFAULT_DATA_DIR_PERMISSION,
@@ -1279,6 +1406,85 @@
     isolateAppDataPerPackage(userId, packageName, volUuid, ceDataInode,
         actualCePath, actualDePath, fail_fn);
   }
+  // We set the label AFTER everything is done, as we are applying
+  // the file operations on tmpfs. If we set the label when we mount
+  // tmpfs, SELinux will not happy as we are changing system_data_files.
+  // Relabel dir under /data/user, including /data/user/0
+  relabelAllDirs(internalCePath, dataDataContext, fail_fn);
+
+  // Relabel /data/user
+  relabelDir(internalCePath, dataDataContext, fail_fn);
+
+  // Relabel /data/data
+  relabelDir(internalLegacyCePath, dataDataContext, fail_fn);
+
+  // Relabel dir under /data/user_de
+  relabelAllDirs(internalDePath, dataDataContext, fail_fn);
+
+  // Relabel /data/user_de
+  relabelDir(internalDePath, dataDataContext, fail_fn);
+
+  // Relabel CE and DE dirs under /mnt/expand
+  dir = opendir(externalPrivateMountPath);
+  if (dir == nullptr) {
+    fail_fn(CREATE_ERROR("Failed to opendir %s", externalPrivateMountPath));
+  }
+  while ((ent = readdir(dir))) {
+    if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue;
+    auto volPath = StringPrintf("%s/%s", externalPrivateMountPath, ent->d_name);
+    auto cePath = StringPrintf("%s/user", volPath.c_str());
+    auto dePath = StringPrintf("%s/user_de", volPath.c_str());
+
+    relabelAllDirs(cePath.c_str(), dataDataContext, fail_fn);
+    relabelDir(cePath.c_str(), dataDataContext, fail_fn);
+    relabelAllDirs(dePath.c_str(), dataDataContext, fail_fn);
+    relabelDir(dePath.c_str(), dataDataContext, fail_fn);
+  }
+  closedir(dir);
+
+  freecon(dataDataContext);
+}
+
+/**
+ * Like isolateAppData(), isolate jit profile directories, so apps don't see what
+ * other apps are installed by reading content inside /data/misc/profiles/cur.
+ *
+ * The implementation is similar to isolateAppData(), it creates a tmpfs
+ * on /data/misc/profiles/cur, and bind mounts related package profiles to it.
+ */
+static void isolateJitProfile(JNIEnv* env, jobjectArray pkg_data_info_list,
+    uid_t uid, const char* process_name, jstring managed_nice_name,
+    fail_fn_t fail_fn) {
+
+  auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
+  const userid_t user_id = multiuser_get_user_id(uid);
+
+  int size = (pkg_data_info_list != nullptr) ? env->GetArrayLength(pkg_data_info_list) : 0;
+  // Size should be a multiple of 3, as it contains list of <package_name, volume_uuid, inode>
+  if ((size % 3) != 0) {
+    fail_fn(CREATE_ERROR("Wrong pkg_inode_list size %d", size));
+  }
+
+  // Mount (namespace) tmpfs on profile directory, so apps no longer access
+  // the original profile directory anymore.
+  MountAppDataTmpFs(kCurProfileDirPath, fail_fn);
+
+  // Create profile directory for this user.
+  std::string actualCurUserProfile = StringPrintf("%s/%d", kCurProfileDirPath, user_id);
+  PrepareDir(actualCurUserProfile, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn);
+
+  for (int i = 0; i < size; i += 3) {
+    jstring package_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i));
+    std::string packageName = extract_fn(package_str).value();
+
+    std::string actualCurPackageProfile = StringPrintf("%s/%s", actualCurUserProfile.c_str(),
+        packageName.c_str());
+    std::string mirrorCurPackageProfile = StringPrintf("/data_mirror/cur_profiles/%d/%s",
+        user_id, packageName.c_str());
+
+    PrepareDir(actualCurPackageProfile, DEFAULT_DATA_DIR_PERMISSION, uid, uid, fail_fn);
+    BindMount(mirrorCurPackageProfile, actualCurPackageProfile, fail_fn);
+  }
 }
 
 // Utility routine to specialize a zygote child process.
@@ -1308,31 +1514,25 @@
 
   DropCapabilitiesBoundingSet(fail_fn);
 
-  bool use_native_bridge = !is_system_server &&
-                           instruction_set.has_value() &&
-                           android::NativeBridgeAvailable() &&
-                           android::NeedsNativeBridge(instruction_set.value().c_str());
+  bool need_pre_initialize_native_bridge =
+      !is_system_server &&
+      instruction_set.has_value() &&
+      android::NativeBridgeAvailable() &&
+      // Native bridge may be already initialized if this
+      // is an app forked from app-zygote.
+      !android::NativeBridgeInitialized() &&
+      android::NeedsNativeBridge(instruction_set.value().c_str());
 
-  if (use_native_bridge && !app_data_dir.has_value()) {
-    // The app_data_dir variable should never be empty if we need to use a
-    // native bridge.  In general, app_data_dir will never be empty for normal
-    // applications.  It can only happen in special cases (for isolated
-    // processes which are not associated with any app).  These are launched by
-    // the framework and should not be emulated anyway.
-    use_native_bridge = false;
-    ALOGW("Native bridge will not be used because managed_app_data_dir == nullptr.");
-  }
-
-  MountEmulatedStorage(uid, mount_external, use_native_bridge, fail_fn);
+  MountEmulatedStorage(uid, mount_external, need_pre_initialize_native_bridge, fail_fn);
 
   // System services, isolated process, webview/app zygote, old target sdk app, should
   // give a null in same_uid_pkgs and private_volumes so they don't need app data isolation.
   // Isolated process / webview / app zygote should be gated by SELinux and file permission
   // so they can't even traverse CE / DE directories.
   if (pkg_data_info_list != nullptr
-      && GetBoolProperty(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, false)) {
-    isolateAppData(env, pkg_data_info_list, uid, process_name, managed_nice_name,
-        fail_fn);
+      && GetBoolProperty(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, true)) {
+    isolateAppData(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn);
+    isolateJitProfile(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn);
   }
 
   // If this zygote isn't root, it won't be able to create a process group,
@@ -1349,11 +1549,12 @@
   SetGids(env, gids, fail_fn);
   SetRLimits(env, rlimits, fail_fn);
 
-  if (use_native_bridge) {
-    // Due to the logic behind use_native_bridge we know that both app_data_dir
-    // and instruction_set contain values.
-    android::PreInitializeNativeBridge(app_data_dir.value().c_str(),
-                                       instruction_set.value().c_str());
+  if (need_pre_initialize_native_bridge) {
+    // Due to the logic behind need_pre_initialize_native_bridge we know that
+    // instruction_set contains a value.
+    android::PreInitializeNativeBridge(
+        app_data_dir.has_value() ? app_data_dir.value().c_str() : nullptr,
+        instruction_set.value().c_str());
   }
 
   if (setresgid(gid, gid, gid) == -1) {
@@ -1462,6 +1663,10 @@
     }
   }
 
+  if (is_child_zygote) {
+      initUnsolSocketToSystemServer();
+  }
+
   env->CallStaticVoidMethod(gZygoteClass, gCallPostForkChildHooks, runtime_flags,
                             is_system_server, is_child_zygote, managed_instruction_set);
 
@@ -1727,6 +1932,11 @@
       fds_to_ignore.push_back(gUsapPoolEventFD);
     }
 
+    if (gSystemServerSocketFd != -1) {
+        fds_to_close.push_back(gSystemServerSocketFd);
+        fds_to_ignore.push_back(gSystemServerSocketFd);
+    }
+
     pid_t pid = ForkCommon(env, false, fds_to_close, fds_to_ignore, true);
 
     if (pid == 0) {
@@ -1753,6 +1963,11 @@
     fds_to_ignore.push_back(gUsapPoolEventFD);
   }
 
+  if (gSystemServerSocketFd != -1) {
+      fds_to_close.push_back(gSystemServerSocketFd);
+      fds_to_ignore.push_back(gSystemServerSocketFd);
+  }
+
   pid_t pid = ForkCommon(env, true,
                          fds_to_close,
                          fds_to_ignore,
@@ -1823,6 +2038,9 @@
   fds_to_close.push_back(gZygoteSocketFD);
   fds_to_close.push_back(gUsapPoolEventFD);
   fds_to_close.insert(fds_to_close.end(), session_socket_fds.begin(), session_socket_fds.end());
+  if (gSystemServerSocketFd != -1) {
+      fds_to_close.push_back(gSystemServerSocketFd);
+  }
 
   fds_to_ignore.push_back(gZygoteSocketFD);
   fds_to_ignore.push_back(gUsapPoolSocketFD);
@@ -1830,6 +2048,9 @@
   fds_to_ignore.push_back(read_pipe_fd);
   fds_to_ignore.push_back(write_pipe_fd);
   fds_to_ignore.insert(fds_to_ignore.end(), session_socket_fds.begin(), session_socket_fds.end());
+  if (gSystemServerSocketFd != -1) {
+      fds_to_ignore.push_back(gSystemServerSocketFd);
+  }
 
   pid_t usap_pid = ForkCommon(env, /* is_system_server= */ false, fds_to_close, fds_to_ignore,
                               is_priority_fork == JNI_TRUE);
@@ -1928,6 +2149,8 @@
     ALOGE("Unable to fetch USAP pool socket file descriptor");
   }
 
+  initUnsolSocketToSystemServer();
+
   /*
    * Security Initialization
    */
@@ -2054,43 +2277,81 @@
   setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MAX);
 }
 
+static jint com_android_internal_os_Zygote_nativeParseSigChld(JNIEnv* env, jclass, jbyteArray in,
+                                                              jint length, jintArray out) {
+    if (length != sizeof(struct UnsolicitedZygoteMessageSigChld)) {
+        // Apparently it's not the message we are expecting.
+        return -1;
+    }
+    if (in == nullptr || out == nullptr) {
+        // Invalid parameter
+        jniThrowException(env, "java/lang/IllegalArgumentException", nullptr);
+        return -1;
+    }
+    ScopedByteArrayRO source(env, in);
+    if (source.size() < length) {
+        // Invalid parameter
+        jniThrowException(env, "java/lang/IllegalArgumentException", nullptr);
+        return -1;
+    }
+    const struct UnsolicitedZygoteMessageSigChld* msg =
+            reinterpret_cast<const struct UnsolicitedZygoteMessageSigChld*>(source.get());
+
+    switch (msg->header.type) {
+        case UNSOLICITED_ZYGOTE_MESSAGE_TYPE_SIGCHLD: {
+            ScopedIntArrayRW buf(env, out);
+            if (buf.size() != 3) {
+                jniThrowException(env, "java/lang/IllegalArgumentException", nullptr);
+                return UNSOLICITED_ZYGOTE_MESSAGE_TYPE_RESERVED;
+            }
+            buf[0] = msg->payload.pid;
+            buf[1] = msg->payload.uid;
+            buf[2] = msg->payload.status;
+            return 3;
+        }
+        default:
+            break;
+    }
+    return -1;
+}
+
 static const JNINativeMethod gMethods[] = {
-    { "nativeForkAndSpecialize",
-      "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;)I",
-      (void *) com_android_internal_os_Zygote_nativeForkAndSpecialize },
-    { "nativeForkSystemServer", "(II[II[[IJJ)I",
-      (void *) com_android_internal_os_Zygote_nativeForkSystemServer },
-    { "nativeAllowFileAcrossFork", "(Ljava/lang/String;)V",
-      (void *) com_android_internal_os_Zygote_nativeAllowFileAcrossFork },
-    { "nativePreApplicationInit", "()V",
-      (void *) com_android_internal_os_Zygote_nativePreApplicationInit },
-    { "nativeInstallSeccompUidGidFilter", "(II)V",
-      (void *) com_android_internal_os_Zygote_nativeInstallSeccompUidGidFilter },
-    { "nativeForkUsap", "(II[IZ)I",
-      (void *) com_android_internal_os_Zygote_nativeForkUsap },
-    { "nativeSpecializeAppProcess",
-      "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;)V",
-      (void *) com_android_internal_os_Zygote_nativeSpecializeAppProcess },
-    { "nativeInitNativeState", "(Z)V",
-      (void *) com_android_internal_os_Zygote_nativeInitNativeState },
-    { "nativeGetUsapPipeFDs", "()[I",
-      (void *) com_android_internal_os_Zygote_nativeGetUsapPipeFDs },
-    { "nativeRemoveUsapTableEntry", "(I)Z",
-      (void *) com_android_internal_os_Zygote_nativeRemoveUsapTableEntry },
-    { "nativeGetUsapPoolEventFD", "()I",
-      (void *) com_android_internal_os_Zygote_nativeGetUsapPoolEventFD },
-    { "nativeGetUsapPoolCount", "()I",
-      (void *) com_android_internal_os_Zygote_nativeGetUsapPoolCount },
-    { "nativeEmptyUsapPool", "()V",
-      (void *) com_android_internal_os_Zygote_nativeEmptyUsapPool },
-    { "nativeDisableExecuteOnly", "()Z",
-      (void *) com_android_internal_os_Zygote_nativeDisableExecuteOnly },
-    { "nativeBlockSigTerm", "()V",
-      (void* ) com_android_internal_os_Zygote_nativeBlockSigTerm },
-    { "nativeUnblockSigTerm", "()V",
-      (void* ) com_android_internal_os_Zygote_nativeUnblockSigTerm },
-    { "nativeBoostUsapPriority", "()V",
-      (void* ) com_android_internal_os_Zygote_nativeBoostUsapPriority }
+        {"nativeForkAndSpecialize",
+         "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/"
+         "String;Z[Ljava/lang/String;)I",
+         (void*)com_android_internal_os_Zygote_nativeForkAndSpecialize},
+        {"nativeForkSystemServer", "(II[II[[IJJ)I",
+         (void*)com_android_internal_os_Zygote_nativeForkSystemServer},
+        {"nativeAllowFileAcrossFork", "(Ljava/lang/String;)V",
+         (void*)com_android_internal_os_Zygote_nativeAllowFileAcrossFork},
+        {"nativePreApplicationInit", "()V",
+         (void*)com_android_internal_os_Zygote_nativePreApplicationInit},
+        {"nativeInstallSeccompUidGidFilter", "(II)V",
+         (void*)com_android_internal_os_Zygote_nativeInstallSeccompUidGidFilter},
+        {"nativeForkUsap", "(II[IZ)I", (void*)com_android_internal_os_Zygote_nativeForkUsap},
+        {"nativeSpecializeAppProcess",
+         "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/"
+         "String;Z[Ljava/lang/String;)V",
+         (void*)com_android_internal_os_Zygote_nativeSpecializeAppProcess},
+        {"nativeInitNativeState", "(Z)V",
+         (void*)com_android_internal_os_Zygote_nativeInitNativeState},
+        {"nativeGetUsapPipeFDs", "()[I",
+         (void*)com_android_internal_os_Zygote_nativeGetUsapPipeFDs},
+        {"nativeRemoveUsapTableEntry", "(I)Z",
+         (void*)com_android_internal_os_Zygote_nativeRemoveUsapTableEntry},
+        {"nativeGetUsapPoolEventFD", "()I",
+         (void*)com_android_internal_os_Zygote_nativeGetUsapPoolEventFD},
+        {"nativeGetUsapPoolCount", "()I",
+         (void*)com_android_internal_os_Zygote_nativeGetUsapPoolCount},
+        {"nativeEmptyUsapPool", "()V", (void*)com_android_internal_os_Zygote_nativeEmptyUsapPool},
+        {"nativeDisableExecuteOnly", "()Z",
+         (void*)com_android_internal_os_Zygote_nativeDisableExecuteOnly},
+        {"nativeBlockSigTerm", "()V", (void*)com_android_internal_os_Zygote_nativeBlockSigTerm},
+        {"nativeUnblockSigTerm", "()V", (void*)com_android_internal_os_Zygote_nativeUnblockSigTerm},
+        {"nativeBoostUsapPriority", "()V",
+         (void*)com_android_internal_os_Zygote_nativeBoostUsapPriority},
+        {"nativeParseSigChld", "([BI[I)I",
+         (void*)com_android_internal_os_Zygote_nativeParseSigChld},
 };
 
 int register_com_android_internal_os_Zygote(JNIEnv* env) {
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index 082a289..1fcc8ac 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -39,6 +39,7 @@
         "/apex/com.android.media/javalib/updatable-media.jar",
         "/apex/com.android.mediaprovider/javalib/framework-mediaprovider.jar",
         "/apex/com.android.os.statsd/javalib/framework-statsd.jar",
+        "/apex/com.android.permission/javalib/framework-permission.jar",
         "/apex/com.android.sdkext/javalib/framework-sdkextensions.jar",
         "/apex/com.android.wifi/javalib/framework-wifi.jar",
         "/apex/com.android.tethering/javalib/framework-tethering.jar",
diff --git a/core/proto/android/app/appexitinfo.proto b/core/proto/android/app/appexitinfo.proto
new file mode 100644
index 0000000..e23f150
--- /dev/null
+++ b/core/proto/android/app/appexitinfo.proto
@@ -0,0 +1,185 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+option java_multiple_files = true;
+
+package android.app;
+
+import "frameworks/base/core/proto/android/privacy.proto";
+
+/**
+ * An android.app.ApplicationExitInfo object.
+ */
+message ApplicationExitInfoProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    optional int32 pid = 1;
+    optional int32 real_uid = 2;
+    optional int32 package_uid = 3;
+    optional int32 defining_uid = 4;
+    optional string process_name = 5;
+    optional int32 connection_group = 6;
+
+    enum ReasonCode {
+        /**
+         * Application process died due to unknown reason.
+         */
+        REASON_UNKNOWN = 0;
+
+        /**
+         * Application process exit normally by itself, for example,
+         * via {@link android.os.Process#exit}; {@link #status} will specify the exit code.
+         *
+         * <p>Applications should normally not do this, as the system has a better knowledge
+         * in terms of process management.</p>
+         */
+        REASON_EXIT_SELF = 1;
+
+        /**
+         * Application process died due to the result of an OS signal; for example,
+         * {@link android.os.Process#SIGNAL_KILL}; {@link #status} will specify the signum.
+         */
+        REASON_SIGNALED = 2;
+
+        /**
+         * Application process was killed by the system low memory killer, meaning the system was
+         * under memory pressure at the time of kill.
+         */
+        REASON_LOW_MEMORY = 3;
+
+        /**
+         * Application process died because of an unhandled exception in Java code.
+         */
+        REASON_CRASH = 4;
+
+        /**
+         * Application process died because it's crashed due to a native code crash.
+         */
+        REASON_CRASH_NATIVE = 5;
+
+        /**
+         * Application process was killed due to being unresponsive (ANR).
+         */
+        REASON_ANR = 6;
+
+        /**
+         * Application process was killed because it took too long to attach to the system
+         * during the start.
+         */
+        REASON_INITIALIZATION_FAILURE = 7;
+
+        /**
+         * Application process was killed because of initialization failure,
+         * for example, it took too long to attach to the system during the start,
+         * or there was an error during initialization.
+         */
+        REASON_PERMISSION_CHANGE = 8;
+
+        /**
+         * Application process was killed by the activity manager due to excessive resource usage.
+         */
+        REASON_EXCESSIVE_RESOURCE_USAGE = 9;
+
+        /**
+         * Application process was killed by the system for various other reasons,
+         * for example, the application package got disabled by the user;
+         * {@link #description} will specify the cause given by the system.
+         */
+        REASON_OTHER = 10;
+
+    }
+    optional ReasonCode reason = 7;
+
+    enum SubReason {
+        /**
+         * Application process kills subReason is unknown.
+         */
+        SUBREASON_UNKNOWN = 0;
+
+        /**
+         * Application process was killed because user quit it on the "wait for debugger" dialog.
+         */
+        SUBREASON_WAIT_FOR_DEBUGGER = 1;
+
+        /**
+         * Application process was killed by the activity manager because there were too many cached
+         * processes.
+         */
+        SUBREASON_TOO_MANY_CACHED = 2;
+
+        /**
+         * Application process was killed by the activity manager because there were too many empty
+         * processes.
+         */
+        SUBREASON_TOO_MANY_EMPTY = 3;
+
+        /**
+         * Application process was killed by the activity manager because there were too many cached
+         * processes and this process had been in empty state for a long time.
+         */
+        SUBREASON_TRIM_EMPTY = 4;
+
+        /**
+         * Application process was killed by the activity manager because system was on
+         * memory pressure and this process took large amount of cached memory.
+         */
+        SUBREASON_LARGE_CACHED = 5;
+
+        /**
+         * Application process was killed by the activity manager because the system was on
+         * low memory pressure for a significant amount of time since last idle.
+         */
+        SUBREASON_MEMORY_PRESSURE = 6;
+
+        /**
+         * Application process was killed by the activity manager due to excessive CPU usage.
+         */
+        SUBREASON_EXCESSIVE_CPU = 7;
+    }
+
+    optional SubReason sub_reason = 8;
+
+    optional int32 status = 9;
+
+    enum Importance {
+        option allow_alias = true;
+        /**
+         * Keep sync with the definitions in
+         * {@link android.app.ActivityManager.RunningAppProcessInfo}
+         */
+        IMPORTANCE_FOREGROUND = 100;
+        IMPORTANCE_FOREGROUND_SERVICE = 125;
+        IMPORTANCE_TOP_SLEEPING_PRE_28 = 150;
+        IMPORTANCE_VISIBLE = 200;
+        IMPORTANCE_PERCEPTIBLE_PRE_26 = 130;
+        IMPORTANCE_PERCEPTIBLE = 230;
+        IMPORTANCE_CANT_SAVE_STATE_PRE_26 = 170;
+        IMPORTANCE_SERVICE = 300;
+        IMPORTANCE_TOP_SLEEPING = 325;
+        IMPORTANCE_CANT_SAVE_STATE = 350;
+        IMPORTANCE_CACHED = 400;
+        IMPORTANCE_BACKGROUND = 400;
+        IMPORTANCE_EMPTY = 500;
+        IMPORTANCE_GONE = 1000;
+    }
+
+    optional Importance importance = 10;
+    optional int32 pss = 11;
+    optional int32 rss = 12;
+    optional int64 timestamp = 13;
+    optional string description = 14;
+}
diff --git a/core/proto/android/app/job/enums.proto b/core/proto/android/app/job/enums.proto
index f702b3e..d2bf205 100644
--- a/core/proto/android/app/job/enums.proto
+++ b/core/proto/android/app/job/enums.proto
@@ -34,4 +34,5 @@
     STOP_REASON_TIMEOUT = 3;
     STOP_REASON_DEVICE_IDLE = 4;
     STOP_REASON_DEVICE_THERMAL = 5;
+    STOP_REASON_RESTRAINED = 6;
 }
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index b83b31c..ce03727 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2553,4 +2553,19 @@
     // OS: R
     MANAGE_EXTERNAL_STORAGE = 1822;
 
+    // Open: Settings > DND > People
+    // OS: R
+    DND_PEOPLE = 1823;
+
+    // OPEN: Settings > Apps and notifications > App info > one of any app > Open by default
+    //       > Open supported links
+    // CATEGORY: SETTINGS
+    // OS: R
+    OPEN_SUPPORTED_LINKS = 1824;
+    
+    // OPEN: Settings > Display > Dark theme > Set start time dialog
+    DIALOG_DARK_THEME_SET_START_TIME = 1825;
+
+    // OPEN: Settings > Display > Dark theme > Set end time dialog
+    DIALOG_DARK_THEME_SET_END_TIME = 1826;
 }
diff --git a/core/proto/android/hardware/location/context_hub_info.proto b/core/proto/android/hardware/location/context_hub_info.proto
new file mode 100644
index 0000000..de5cd55
--- /dev/null
+++ b/core/proto/android/hardware/location/context_hub_info.proto
@@ -0,0 +1,49 @@
+/*
+ * 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.hardware.location;
+
+import "frameworks/base/core/proto/android/privacy.proto";
+
+option java_multiple_files = true;
+
+message ContextHubInfoProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+    // Context hub unique identifier
+    optional int32 id = 1;
+    // A name for the hub
+    optional string name = 2;
+    // A name for the vendor
+    optional string vendor = 3;
+    // Description of the tool chain
+    optional string toolchain = 4;
+    optional int32 platform_version = 5;
+    optional int32 static_sw_version = 6;
+    optional int32 toolchain_version = 7;
+    // The CHRE platform ID as defined in chre/version.h
+    optional int64 chre_platform_id = 8;
+    // Peak MIPS that this hub can deliver
+    optional float peak_mips = 9;
+    // Power draw in stopped state in milli watts
+    optional float stopped_power_draw_mw = 10;
+    // Power draw in sleep state in milli watts
+    optional float sleep_power_draw_mw = 11;
+    // Peak power draw in milli watts
+    optional float peak_power_draw_mw = 12;
+    // The maximum number of bytes that can be sent per message to the hub
+    optional int32 max_packet_length_bytes = 13;
+}
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 8f9c041..8adcc9e 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -36,6 +36,7 @@
 import "frameworks/base/core/proto/android/server/alarmmanagerservice.proto";
 import "frameworks/base/core/proto/android/server/fingerprint.proto";
 import "frameworks/base/core/proto/android/server/jobscheduler.proto";
+import "frameworks/base/core/proto/android/server/location/context_hub.proto";
 import "frameworks/base/core/proto/android/server/powermanagerservice.proto";
 import "frameworks/base/core/proto/android/server/rolemanagerservice.proto";
 import "frameworks/base/core/proto/android/server/windowmanagerservice.proto";
@@ -56,6 +57,7 @@
 import "frameworks/base/core/proto/android/util/log.proto";
 import "frameworks/base/core/proto/android/privacy.proto";
 import "frameworks/base/core/proto/android/section.proto";
+import "frameworks/base/proto/src/ipconnectivity.proto";
 
 package android.os;
 
@@ -165,6 +167,11 @@
         (section).args = "security -L"
     ];
 
+    optional android.util.PersistedLogProto persisted_logs = 1116 [
+        (section).type = SECTION_COMMAND,
+        (section).args = "/system/bin/sh /system/bin/incident-helper-cmd -l run persisted_logs --limit 10MB"
+    ];
+
     // Stack dumps
     optional android.os.BackTraceProto native_traces = 1200 [
         (section).type = SECTION_TOMBSTONE,
@@ -475,6 +482,16 @@
         (section).args = "cpuinfo --proto"
     ];
 
+    optional .clearcut.connectivity.IpConnectivityLog ip_connectivity_metrics = 3049 [
+        (section).type = SECTION_DUMPSYS,
+        (section).args = "connmetrics --proto"
+    ];
+
+    optional com.android.server.location.ContextHubServiceProto context_hub = 3051 [
+        (section).type = SECTION_DUMPSYS,
+        (section).args = "contexthub --proto"
+    ];
+
     // Reserved for OEMs.
     extensions 50000 to 100000;
 }
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 7835016..77d4e87 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -188,6 +188,7 @@
         optional SettingProto pulse_on_long_press = 4 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto pulse_on_double_tap = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto pulse_on_tap = 6 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        optional SettingProto suppress = 7 [ (android.privacy).dest = DEST_AUTOMATIC ];
     }
     optional Doze doze = 21;
 
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index c79f314..126d445 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -19,6 +19,7 @@
 package com.android.server.am;
 
 import "frameworks/base/core/proto/android/app/activitymanager.proto";
+import "frameworks/base/core/proto/android/app/appexitinfo.proto";
 import "frameworks/base/core/proto/android/app/enums.proto";
 import "frameworks/base/core/proto/android/app/notification.proto";
 import "frameworks/base/core/proto/android/app/profilerinfo.proto";
@@ -1082,3 +1083,23 @@
     optional .android.util.Duration started_time = 4;
     optional string started_package = 5;
 }
+
+// sync with com.android.server.am.am.ProcessList.java
+message AppsExitInfoProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    optional int64 last_update_timestamp = 1;
+    message Package {
+        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+        optional string package_name = 1;
+        message User {
+            option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+            optional int32 uid = 1;
+            repeated .android.app.ApplicationExitInfoProto app_exit_info = 2;
+        }
+        repeated User users = 2;
+    }
+    repeated Package packages = 2;
+}
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index 06040a5..303d62d 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -32,8 +32,8 @@
 import "frameworks/base/core/proto/android/server/job/enums.proto";
 import "frameworks/base/core/proto/android/server/statlogger.proto";
 import "frameworks/base/core/proto/android/privacy.proto";
+import "frameworks/base/core/proto/android/util/quotatracker.proto";
 
-// Next tag: 21
 message JobSchedulerServiceDumpProto {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
 
@@ -160,10 +160,13 @@
     optional JobConcurrencyManagerProto concurrency_manager = 20;
 
     optional JobStorePersistStatsProto persist_stats = 21;
+
+    optional .android.util.quota.CountQuotaTrackerProto quota_tracker = 22;
+
+    // Next tag: 23
 }
 
 // A com.android.server.job.JobSchedulerService.Constants object.
-// Next tag: 29
 message ConstantsProto {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
 
@@ -246,6 +249,14 @@
     // Whether to use heartbeats or rolling window for quota management. True
     // will use heartbeats, false will use a rolling window.
     reserved 23; // use_heartbeats
+    // Whether to enable quota limits on APIs.
+    optional bool enable_api_quotas = 31;
+    // The maximum number of schedule() calls an app can make in a set amount of time.
+    optional int32 api_quota_schedule_count = 32;
+    // The time window that {@link #API_QUOTA_SCHEDULE_COUNT} should be evaluated over.
+    optional int64 api_quota_schedule_window_ms = 33;
+    // Whether or not to throw an exception when an app hits its schedule quota limit.
+    optional bool api_quota_schedule_throw_exception = 34;
 
     message QuotaController {
         option (.android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -271,6 +282,10 @@
         // expected to run only {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past
         // WINDOW_SIZE_MS.
         optional int64 rare_window_size_ms = 6;
+        // The quota window size of the particular standby bucket. Apps in this standby bucket are
+        // expected to run only {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past
+        // WINDOW_SIZE_MS.
+        optional int64 restricted_window_size_ms = 20;
         // The maximum amount of time an app can have its jobs running within a 24 hour window.
         optional int64 max_execution_time_ms = 7;
         // The maximum number of jobs an app can run within this particular standby bucket's
@@ -285,6 +300,9 @@
         // The maximum number of jobs an app can run within this particular standby bucket's
         // window size.
         optional int32 max_job_count_rare = 11;
+        // The maximum number of jobs an app can run within this particular standby bucket's
+        // window size.
+        optional int32 max_job_count_restricted = 21;
         // The period of time used to rate limit recently run jobs.
         optional int32 rate_limiting_window_ms = 19;
         // The maximum number of jobs that should be allowed to run in the past
@@ -302,12 +320,17 @@
         // The maximum number of timing sessions an app can run within this particular standby
         // bucket's window size.
         optional int32 max_session_count_rare = 16;
+        // The maximum number of timing sessions an app can run within this particular standby
+        // bucket's window size.
+        optional int32 max_session_count_restricted = 22;
         // The maximum number of timing sessions that should be allowed to run in the past
         // rate_limiting_window_ms.
         optional int32 max_session_count_per_rate_limiting_window = 17;
         // Treat two distinct {@link TimingSession}s as the same if they start and end within this
         // amount of time of each other.
         optional int64 timing_session_coalescing_duration_ms = 18;
+
+        // Next tag: 23
     }
     optional QuotaController quota_controller = 24;
 
@@ -331,7 +354,7 @@
     // In this time after screen turns on, we increase job concurrency.
     optional int32 screen_off_job_concurrency_increase_delay_ms = 28;
 
-    // Next tag: 31
+    // Next tag: 35
 }
 
 // Next tag: 4
@@ -651,8 +674,7 @@
             optional bool is_active = 2;
             // The time this timer last became active. Only valid if is_active is true.
             optional int64 start_time_elapsed = 3;
-            // How many background jobs are currently running. Valid only if the device is_active
-            // is true.
+            // How many background jobs are currently running. Valid only if is_active is true.
             optional int32 bg_job_count = 4;
             // All of the jobs that the Timer is currently tracking.
             repeated JobStatusShortInfoProto running_jobs = 5;
@@ -933,6 +955,9 @@
     optional JobInfo job_info = 6;
 
     repeated ConstraintEnum required_constraints = 7;
+    // Dynamic constraints are additional constraints imposed by the system that MUST be met before
+    // the app can run if the app does not have quota.
+    repeated ConstraintEnum dynamic_constraints = 31;
     repeated ConstraintEnum satisfied_constraints = 8;
     repeated ConstraintEnum unsatisfied_constraints = 9;
     optional bool is_doze_whitelisted = 10;
@@ -946,6 +971,8 @@
         // Battery Saver). This implicit constraint must be satisfied for the
         // job to run.
         optional bool is_not_restricted_in_bg = 2;
+        // True if dynamic constraints have been satisfied.
+        optional bool is_dynamic_satisfied = 3;
     }
     optional ImplicitConstraints implicit_constraints = 25;
 
@@ -988,6 +1015,7 @@
         FREQUENT = 2;
         RARE = 3;
         NEVER = 4;
+        RESTRICTED = 5;
     }
     optional Bucket standby_bucket = 17;
     optional bool is_exempted_from_app_standby = 27;
@@ -1018,7 +1046,7 @@
     // was no attempt.
     optional int64 time_since_first_force_batch_attempt_ms = 29;
 
-    // Next tag: 31
+    // Next tag: 32
 }
 
 // Dump from com.android.server.job.JobConcurrencyManager.
diff --git a/core/proto/android/server/location/context_hub.proto b/core/proto/android/server/location/context_hub.proto
new file mode 100644
index 0000000..c87f791
--- /dev/null
+++ b/core/proto/android/server/location/context_hub.proto
@@ -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.
+ */
+
+syntax = "proto2";
+package com.android.server.location;
+
+import "frameworks/base/core/proto/android/hardware/location/context_hub_info.proto";
+import "frameworks/base/core/proto/android/privacy.proto";
+
+option java_multiple_files = true;
+
+message ContextHubServiceProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    repeated .android.hardware.location.ContextHubInfoProto context_hub_info = 1;
+    optional ClientManagerProto client_manager = 2;
+}
+
+message ClientManagerProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    message RegistrationRecord {
+        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+        optional int64 timestamp_ms = 1;
+        optional int32 action = 2;
+        // ClientBroker endpoint id, contexthub id and package name
+        optional string broker = 3;
+    }
+
+    repeated ClientBrokerProto client_brokers = 1;
+    repeated RegistrationRecord registration_records = 2;
+}
+
+message ClientBrokerProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    optional int32 endpoint_id = 1;
+    optional int32 attached_context_hub_id = 2;
+    optional string package = 3;
+    optional int64 nano_app_id = 4;
+    optional bool pending_intent_request_valid = 5;
+    optional bool has_pending_intent = 6;
+    optional bool pending_intent_cancelled = 7;
+    optional bool registered = 8;
+
+}
diff --git a/core/proto/android/service/graphicsstats.proto b/core/proto/android/service/graphicsstats.proto
index 557075c..2de5b7f 100644
--- a/core/proto/android/service/graphicsstats.proto
+++ b/core/proto/android/service/graphicsstats.proto
@@ -32,6 +32,10 @@
 }
 
 message GraphicsStatsProto {
+    enum PipelineType {
+        GL = 0;
+        VULKAN = 1;
+    }
     option (android.msg_privacy).dest = DEST_AUTOMATIC;
 
     // The package name of the app
@@ -54,6 +58,9 @@
 
     // The gpu frame time histogram for the package
     repeated GraphicsStatsHistogramBucketProto gpu_histogram = 7;
+
+    // HWUI renders pipeline type: GL or Vulkan
+    optional PipelineType pipeline = 8;
 }
 
 message GraphicsStatsJankSummaryProto {
diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
index 9054d54..0f03e69 100644
--- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto
+++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
@@ -154,4 +154,7 @@
   SET_AUTO_TIME = 127;
   SET_AUTO_TIME_ZONE = 128;
   SET_PACKAGES_PROTECTED = 129;
+  SET_FACTORY_RESET_PROTECTION = 130;
+  SET_COMMON_CRITERIA_MODE = 131;
+  ALLOW_MODIFICATION_OF_ADMIN_CONFIGURED_NETWORKS = 132;
 }
diff --git a/core/proto/android/stats/launcher/Android.bp b/core/proto/android/stats/launcher/Android.bp
index b8fb6ff..976a0b8 100644
--- a/core/proto/android/stats/launcher/Android.bp
+++ b/core/proto/android/stats/launcher/Android.bp
@@ -25,3 +25,16 @@
         "*.proto",
     ],
 }
+
+java_library {
+    name: "launcherprotoslite",
+    proto: {
+        type: "lite",
+        include_dirs: ["external/protobuf/src"],
+    },
+
+    sdk_version: "current",
+    srcs: [
+        "*.proto",
+    ],
+}
diff --git a/core/proto/android/stats/sysui/notification_enums.proto b/core/proto/android/stats/sysui/notification_enums.proto
new file mode 100644
index 0000000..0983702
--- /dev/null
+++ b/core/proto/android/stats/sysui/notification_enums.proto
@@ -0,0 +1,29 @@
+/*
+ * 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.sysui;
+
+// Enum used in NotificationReported and NotificationChannelModified atoms
+enum NotificationImportance {  // Constants from NotificationManager.java
+  IMPORTANCE_UNSPECIFIED = -1000;  // Should not occur for real notifications.
+  IMPORTANCE_NONE = 0;  // No importance: does not show in the shade.
+  IMPORTANCE_MIN = 1;  // Minimum to show in the shade.
+  IMPORTANCE_LOW = 2;  // Shows in shade, maybe status bar, no buzz/beep.
+  IMPORTANCE_DEFAULT = 3;  // Shows everywhere, makes noise, no heads-up.
+  IMPORTANCE_HIGH = 4;  // Shows everywhere, makes noise, heads-up, may full-screen.
+}
diff --git a/core/proto/android/util/log.proto b/core/proto/android/util/log.proto
index 09870ae..a214a1a 100644
--- a/core/proto/android/util/log.proto
+++ b/core/proto/android/util/log.proto
@@ -94,3 +94,16 @@
     repeated BinaryLogEntry binary_logs = 2;
 }
 
+message PersistedLogProto {
+    option (android.msg_privacy).dest = DEST_EXPLICIT;
+
+    repeated TextLogEntry main_logs = 1;
+    repeated TextLogEntry radio_logs = 2;
+    repeated TextLogEntry events_logs = 3;
+    repeated TextLogEntry system_logs = 4;
+    repeated TextLogEntry crash_logs = 5;
+    repeated TextLogEntry stats_logs = 6;
+    repeated TextLogEntry security_logs = 7;
+    repeated TextLogEntry kernel_logs = 8;
+}
+
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 31faff6..4595bab 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -131,7 +131,6 @@
 
     <protected-broadcast android:name="android.os.action.SETTING_RESTORED" />
 
-    <protected-broadcast android:name="android.app.backup.intent.RUN" />
     <protected-broadcast android:name="android.app.backup.intent.CLEAR" />
     <protected-broadcast android:name="android.app.backup.intent.INIT" />
 
@@ -362,7 +361,6 @@
     <protected-broadcast android:name="android.net.wifi.WIFI_STATE_CHANGED" />
     <protected-broadcast android:name="android.net.wifi.WIFI_AP_STATE_CHANGED" />
     <protected-broadcast android:name="android.net.wifi.WIFI_CREDENTIAL_CHANGED" />
-    <protected-broadcast android:name="android.net.wifi.WIFI_SCAN_AVAILABLE" />
     <protected-broadcast android:name="android.net.wifi.aware.action.WIFI_AWARE_STATE_CHANGED" />
     <protected-broadcast android:name="android.net.wifi.rtt.action.WIFI_RTT_STATE_CHANGED" />
     <protected-broadcast android:name="android.net.wifi.SCAN_RESULTS" />
@@ -370,12 +368,14 @@
     <protected-broadcast android:name="android.net.wifi.STATE_CHANGE" />
     <protected-broadcast android:name="android.net.wifi.LINK_CONFIGURATION_CHANGED" />
     <protected-broadcast android:name="android.net.wifi.CONFIGURED_NETWORKS_CHANGE" />
+    <protected-broadcast android:name="android.net.wifi.action.NETWORK_SETTINGS_RESET" />
     <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_DEAUTH_IMMINENT" />
     <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_ICON" />
     <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST" />
     <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_SUBSCRIPTION_REMEDIATION" />
     <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_LAUNCH_OSU_VIEW" />
     <protected-broadcast android:name="android.net.wifi.action.WIFI_NETWORK_SUGGESTION_POST_CONNECTION" />
+    <protected-broadcast android:name="android.net.wifi.action.WIFI_SCAN_AVAILABLE" />
     <protected-broadcast android:name="android.net.wifi.supplicant.CONNECTION_CHANGE" />
     <protected-broadcast android:name="android.net.wifi.supplicant.STATE_CHANGE" />
     <protected-broadcast android:name="android.net.wifi.p2p.STATE_CHANGED" />
@@ -444,6 +444,7 @@
 
     <protected-broadcast android:name="android.internal.policy.action.BURN_IN_PROTECTION" />
     <protected-broadcast android:name="android.app.action.SYSTEM_UPDATE_POLICY_CHANGED" />
+    <protected-broadcast android:name="android.app.action.RESET_PROTECTION_POLICY_CHANGED" />
     <protected-broadcast android:name="android.app.action.DEVICE_OWNER_CHANGED" />
     <protected-broadcast android:name="android.app.action.MANAGED_USER_CREATED" />
 
@@ -456,7 +457,6 @@
     <protected-broadcast android:name="android.intent.action.internal_sim_state_changed" />
     <protected-broadcast android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
     <protected-broadcast android:name="android.intent.action.PRECISE_CALL_STATE" />
-    <protected-broadcast android:name="android.intent.action.PRECISE_DATA_CONNECTION_STATE_CHANGED" />
     <protected-broadcast android:name="android.intent.action.SUBSCRIPTION_PHONE_STATE" />
     <protected-broadcast android:name="android.intent.action.USER_INFO_CHANGED" />
     <protected-broadcast android:name="android.intent.action.USER_UNLOCKED" />
@@ -563,10 +563,12 @@
 
     <protected-broadcast android:name="com.android.sync.SYNC_CONN_STATUS_CHANGED" />
 
-    <protected-broadcast android:name="com.android.phone.SIP_INCOMING_CALL" />
+    <protected-broadcast android:name="android.net.sip.action.SIP_INCOMING_CALL" />
     <protected-broadcast android:name="com.android.phone.SIP_ADD_PHONE" />
-    <protected-broadcast android:name="com.android.phone.SIP_REMOVE_PHONE" />
-    <protected-broadcast android:name="com.android.phone.SIP_CALL_OPTION_CHANGED" />
+    <protected-broadcast android:name="android.net.sip.action.SIP_REMOVE_PROFILE" />
+    <protected-broadcast android:name="android.net.sip.action.SIP_SERVICE_UP" />
+    <protected-broadcast android:name="android.net.sip.action.SIP_CALL_OPTION_CHANGED" />
+    <protected-broadcast android:name="android.net.sip.action.START_SIP" />
 
     <protected-broadcast android:name="android.bluetooth.adapter.action.BLE_ACL_CONNECTED" />
     <protected-broadcast android:name="android.bluetooth.adapter.action.BLE_ACL_DISCONNECTED" />
@@ -640,10 +642,6 @@
 
     <protected-broadcast android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY" />
 
-    <!-- NETWORK_SET_TIME moved from com.android.phone to system server. It should ultimately be
-         removed. -->
-    <protected-broadcast android:name="android.telephony.action.NETWORK_SET_TIME" />
-
     <!-- For tether entitlement recheck-->
     <protected-broadcast
         android:name="com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM" />
@@ -934,11 +932,10 @@
 
     <!-- Allows an application a broad access to external storage in scoped storage.
          Intended to be used by few apps that need to manage files on behalf of the users.
-         <p>Protection level: signature|appop
-         <p>This protection level is temporary and will most likely be changed to |preinstalled -->
+         <p>Protection level: signature|appop|preinstalled -->
     <permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
         android:permissionGroup="android.permission-group.UNDEFINED"
-        android:protectionLevel="signature|appop" />
+        android:protectionLevel="signature|appop|preinstalled" />
 
     <!-- ====================================================================== -->
     <!-- Permissions for accessing the device location                          -->
@@ -1619,6 +1616,7 @@
 
     <!-- Allows applications to request network
          recommendations and scores from the NetworkScoreService.
+         @SystemApi
          <p>Not for use by third-party applications. @hide -->
     <permission android:name="android.permission.REQUEST_NETWORK_SCORES"
         android:protectionLevel="signature|setup" />
@@ -1656,6 +1654,7 @@
     <!-- Allows holder to request bluetooth/wifi scan bypassing global "use location" setting and
          location permissions.
          <p>Not for use by third-party or privileged applications.
+         @SystemApi
          @hide
     -->
     <permission android:name="android.permission.RADIO_SCAN_WITHOUT_LOCATION"
@@ -1791,6 +1790,11 @@
         android:label="@string/permlab_preferredPaymentInfo"
         android:protectionLevel="normal" />
 
+    <!-- @SystemApi Allows an internal user to use privileged SecureElement APIs.
+         @hide -->
+    <permission android:name="android.permission.SECURE_ELEMENT_PRIVILEGED"
+        android:protectionLevel="signature|privileged" />
+
     <!-- @deprecated This permission used to allow too broad access to sensitive methods and all its
          uses have been replaced by a more appropriate permission. Most uses have been replaced with
          a NETWORK_STACK or NETWORK_SETTINGS check. Please look up the documentation of the
@@ -2071,8 +2075,10 @@
         android:protectionLevel="signature|privileged" />
 
     <!-- Allows read only access to precise phone state.
-         @hide Pending API council approval -->
+         Allows reading of detailed information about phone state for special-use applications
+         such as dialers, carrier applications, or ims applications. -->
     <permission android:name="android.permission.READ_PRECISE_PHONE_STATE"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:protectionLevel="signature|privileged" />
 
     <!-- @SystemApi Allows read access to privileged phone state.
@@ -2086,6 +2092,11 @@
     <permission android:name="android.permission.READ_ACTIVE_EMERGENCY_SESSION"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows listen permission to always reported signal strength.
+         @hide Used internally. -->
+    <permission android:name="android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH"
+        android:protectionLevel="signature|telephony" />
+
     <!-- @SystemApi Protects the ability to register any PhoneAccount with
          PhoneAccount#CAPABILITY_SIM_SUBSCRIPTION. This capability indicates that the PhoneAccount
          corresponds to a device SIM.
@@ -2206,12 +2217,12 @@
 
     <!-- Must be required by a NetworkService to ensure that only the
          system can bind to it.
-         <p>Protection level: signature
+         <p>Protection level: signature|telephony
          @SystemApi
          @hide
     -->
     <permission android:name="android.permission.BIND_TELEPHONY_NETWORK_SERVICE"
-                android:protectionLevel="signature" />
+                android:protectionLevel="signature|telephony" />
 
     <!-- @SystemApi Allows an application to manage embedded subscriptions (those on a eUICC)
          through EuiccManager APIs.
@@ -2229,12 +2240,23 @@
     <permission android:name="android.permission.BIND_EUICC_SERVICE"
                 android:protectionLevel="signature|telephony" />
 
+    <!-- Required for reading information about carrier apps from SystemConfigManager.
+         <p>Protection level: signature|telephony
+         @SystemApi
+         @hide
+    -->
+    <permission android:name="android.permission.READ_CARRIER_APP_INFO"
+        android:protectionLevel="signature|telephony" />
+
     <!-- ================================== -->
     <!-- Permissions for sdcard interaction -->
     <!-- ================================== -->
     <eat-comment />
 
     <!-- @SystemApi @TestApi Allows an application to write to internal media storage
+         @deprecated This permission is no longer honored in the system and no longer adds
+         the media_rw gid as a supplementary gid to the holder. Use the
+         android.permission.MANAGE_EXTERNAL_STORAGE instead.
          @hide  -->
     <permission android:name="android.permission.WRITE_MEDIA_STORAGE"
         android:protectionLevel="signature|privileged" />
@@ -2356,7 +2378,7 @@
     <permission android:name="android.permission.MANAGE_USERS"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @hide Allows an application to create, remove users and get the list of
+    <!-- @SystemApi @hide Allows an application to create, remove users and get the list of
          users on the device. Applications holding this permission can only create restricted,
          guest, managed, demo, and ephemeral users. For creating other kind of users,
          {@link android.Manifest.permission#MANAGE_USERS} is needed.
@@ -2513,6 +2535,14 @@
                 android:description="@string/permdesc_useDataInBackground"
                 android:protectionLevel="normal" />
 
+    <!-- Allows a companion app to associate to Wi-Fi.
+         <p>Only for use by a single pre-approved app.
+         @hide
+         @SystemApi
+    -->
+    <permission android:name="android.permission.COMPANION_APPROVE_WIFI_CONNECTIONS"
+                android:protectionLevel="signature|privileged" />
+
 
     <!-- ================================== -->
     <!-- Permissions affecting the system wallpaper -->
@@ -2564,7 +2594,7 @@
 
     <!-- Allows telephony to suggest the time / time zone.
          <p>Not for use by third-party applications.
-         @hide
+         @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @hide
      -->
     <permission android:name="android.permission.SUGGEST_PHONE_TIME_AND_ZONE"
         android:protectionLevel="signature|telephony" />
@@ -2691,6 +2721,11 @@
     <permission android:name="android.permission.READ_DEVICE_CONFIG"
         android:protectionLevel="signature|preinstalled" />
 
+    <!-- @SystemApi @hide Allows an application to monitor config settings access.
+    <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS"
+        android:protectionLevel="signature"/>
+
     <!-- @SystemApi @TestApi Allows an application to call
         {@link android.app.ActivityManager#forceStopPackage}.
         @hide -->
@@ -3213,6 +3248,13 @@
     <permission android:name="android.permission.BIND_NFC_SERVICE"
         android:protectionLevel="signature" />
 
+    <!-- Must be required by a {@link android.service.quickaccesswallet.QuickAccessWalletService}
+         to ensure that only the system can bind to it.
+         <p>Protection level: signature
+    -->
+    <permission android:name="android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE"
+                android:protectionLevel="signature" />
+
     <!-- Must be required by the PrintSpooler to ensure that only the system can bind to it.
          @hide -->
     <permission android:name="android.permission.BIND_PRINT_SPOOLER_SERVICE"
@@ -3382,6 +3424,14 @@
     <permission android:name="android.permission.NOTIFY_TV_INPUTS"
          android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Allows an application to interact with tuner resources through
+         Tuner Resource Manager.
+         <p>Protection level: signature|privileged
+         <p>Not for use by third-party applications.
+         @hide -->
+    <permission android:name="android.permission.TUNER_RESOURCE_ACCESS"
+         android:protectionLevel="signature|privileged" />
+
     <!-- Must be required by a {@link android.media.routing.MediaRouteService}
          to ensure that only the system can interact with it.
          @hide -->
@@ -3682,6 +3732,13 @@
     <permission android:name="android.permission.CONFIGURE_DISPLAY_COLOR_MODE"
         android:protectionLevel="signature" />
 
+    <!-- Allows an application to control the lights on the device.
+         @hide
+         @SystemApi
+         @TestApi -->
+    <permission android:name="android.permission.CONTROL_DEVICE_LIGHTS"
+        android:protectionLevel="signature|privileged" />
+
     <!-- Allows an application to control the color saturation of the display.
          @hide
          @SystemApi -->
@@ -3760,6 +3817,24 @@
     <permission android:name="android.permission.CAPTURE_MEDIA_OUTPUT"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Allows an application to capture the audio played by other apps
+        with the {@code USAGE_VOICE_COMMUNICATION} usage.
+
+        The application may opt out of capturing by setting an allow capture policy of
+        {@link android.media.AudioAttributes#ALLOW_CAPTURE_BY_NONE}.
+
+        There are strong restriction listed at
+        {@link android.media.AudioAttributes#ALLOW_CAPTURE_BY_SYSTEM}
+        on what an app can do with the captured audio.
+
+        See {@code CAPTURE_AUDIO_OUTPUT} and {@code CAPTURE_MEDIA_OUTPUT} for capturing
+        audio use cases other than voice communication playback.
+
+        <p>Not for use by third-party applications.</p>
+        @hide -->
+    <permission android:name="android.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT"
+        android:protectionLevel="signature|privileged" />
+
     <!-- @SystemApi Allows an application to capture audio for hotword detection.
          <p>Not for use by third-party applications.</p>
          @hide -->
@@ -3968,6 +4043,13 @@
     <permission android:name="android.permission.BACKUP"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Allows an application to make modifications to device settings such that these
+         modifications will be overridden by settings restore..
+    <p>Not for use by third-party applications.
+         @hide -->
+    <permission android:name="android.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE"
+                android:protectionLevel="signature|setup" />
+
     <!-- @SystemApi Allows application to manage
          {@link android.security.keystore.recovery.RecoveryController}.
          <p>Not for use by third-party applications.
@@ -4757,6 +4839,10 @@
     <!-- Allows input events to be monitored. Very dangerous!  @hide -->
     <permission android:name="android.permission.MONITOR_INPUT"
                 android:protectionLevel="signature" />
+    <!--  Allows the caller to change the associations between input devices and displays.
+        Very dangerous! @hide -->
+    <permission android:name="android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT"
+                android:protectionLevel="signature" />
 
     <!-- Allows query of any normal app on the device, regardless of manifest declarations. -->
     <permission android:name="android.permission.QUERY_ALL_PACKAGES"
@@ -4960,6 +5046,12 @@
                 android:process=":ui">
         </activity>
 
+        <activity android:name="com.android.internal.app.BlockedAppActivity"
+                android:theme="@style/Theme.Dialog.Confirmation"
+                android:excludeFromRecents="true"
+                android:process=":ui">
+        </activity>
+
         <activity android:name="com.android.settings.notification.NotificationAccessConfirmationActivity"
                   android:theme="@style/Theme.Dialog.Confirmation"
                   android:excludeFromRecents="true">
diff --git a/core/res/res/drawable-hdpi/ic_accessibility_magnification.png b/core/res/res/drawable-hdpi/ic_accessibility_magnification.png
deleted file mode 100755
index a91bc6e..0000000
--- a/core/res/res/drawable-hdpi/ic_accessibility_magnification.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_accessibility_magnification.png b/core/res/res/drawable-mdpi/ic_accessibility_magnification.png
deleted file mode 100755
index 9ec5107..0000000
--- a/core/res/res/drawable-mdpi/ic_accessibility_magnification.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml
index 19a296a..46e8f64 100644
--- a/core/res/res/drawable-nodpi/platlogo.xml
+++ b/core/res/res/drawable-nodpi/platlogo.xml
@@ -1,5 +1,5 @@
 <!--
-Copyright (C) 2015 The Android Open Source Project
+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.
@@ -14,14 +14,18 @@
     limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+    android:width="240dp"
+    android:height="240dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
     <path
-        android:fillColor="#FF000000"
-        android:pathData="M19.45,22.89l-10.250001,-10.249999l-2.6599998,2.6599998l-1.77,-1.7600002l4.43,-4.4300003l12.0199995,12.0199995l-1.7699986,1.7600002z"/>
+        android:fillColor="#000"
+        android:pathData="M16 4c-2.2 0-4 1.8-4 4v4H4V8c0-2.2 1.8-4 4-4h8z"/>
     <path
-        android:fillColor="#FF000000"
-        android:pathData="M12,6a6,6 0,1 1,-6 6,6 6,0 0,1 6,-6m0,-2.5A8.5,8.5 0,1 0,20.5 12,8.51 8.51,0 0,0 12,3.5Z"/>
+        android:fillColor="#000"
+        android:pathData="M8 20c2.2 0 4-1.8 4-4v-4H4v8h4z"/>
+    <path
+        android:fillColor="#80000000"
+        android:pathData="M16 12c2.2 0 4-1.8 4-4V4h-8v8h4z"/>
 </vector>
+
diff --git a/core/res/res/drawable-nodpi/stat_sys_adb.xml b/core/res/res/drawable-nodpi/stat_sys_adb.xml
index 08e9fad..0e9aab2 100644
--- a/core/res/res/drawable-nodpi/stat_sys_adb.xml
+++ b/core/res/res/drawable-nodpi/stat_sys_adb.xml
@@ -1,5 +1,5 @@
 <!--
-Copyright (C) 2019 The Android Open Source Project
+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.
@@ -14,17 +14,17 @@
     limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
     <path
-        android:fillColor="#FF000000"
-        android:pathData="M16.28,16.19A6,6 0,1 0,6 12c0,0.1 0,0.19 0,0.29L9.2,9.11Z"/>
+        android:fillColor="#000"
+        android:pathData="M16 4c-2.2 0-4 1.8-4 4v4H4V8c0-2.2 1.8-4 4-4h8z"/>
     <path
-        android:fillColor="#FF000000"
-        android:pathData="M16,19.48a8.57,8.57 0,1 1,2 -1.52l1.77,1.77a11.07,11.07 0,1 0,-2 1.57Z"/>
+        android:fillColor="#000"
+        android:pathData="M8 20c2.2 0 4-1.8 4-4v-4H4v8h4z"/>
     <path
-        android:fillColor="#FF000000"
-        android:pathData="M12,18a5.77,5.77 0,0 0,2 -0.34l0.19,-0.07 -0.87,-0.87L9.2,12.64 6.82,15A6,6 0,0 0,12 18Z"/>
+        android:fillColor="#80000000"
+        android:pathData="M16 12c2.2 0 4-1.8 4-4V4h-8v8h4z"/>
 </vector>
diff --git a/core/res/res/drawable-xhdpi/ic_accessibility_magnification.png b/core/res/res/drawable-xhdpi/ic_accessibility_magnification.png
deleted file mode 100755
index 0b3a32e..0000000
--- a/core/res/res/drawable-xhdpi/ic_accessibility_magnification.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_accessibility_magnification.png b/core/res/res/drawable-xxhdpi/ic_accessibility_magnification.png
deleted file mode 100755
index 3eeb1c9..0000000
--- a/core/res/res/drawable-xxhdpi/ic_accessibility_magnification.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xxxhdpi/ic_accessibility_magnification.png b/core/res/res/drawable-xxxhdpi/ic_accessibility_magnification.png
deleted file mode 100755
index 7d37612..0000000
--- a/core/res/res/drawable-xxxhdpi/ic_accessibility_magnification.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable/ic_accessibility_color_correction.xml b/core/res/res/drawable/ic_accessibility_color_correction.xml
new file mode 100644
index 0000000..ed09d57
--- /dev/null
+++ b/core/res/res/drawable/ic_accessibility_color_correction.xml
@@ -0,0 +1,52 @@
+<!--
+    Copyright (C) 2020 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector android:height="24dp" android:viewportHeight="192"
+    android:viewportWidth="192" android:width="24dp"
+    xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#00BCD4" android:pathData="M37.14,173.74L8.61,83.77c-1.72,-5.75 0.26,-11.97 4.97,-15.63L87.15,11c5.2,-4.04 12.46,-3.99 17.61,0.12l73.81,58.94c4.63,3.7 6.53,9.88 4.8,15.57l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84C44.53,184 38.97,179.83 37.14,173.74z"/>
+    <path android:fillAlpha="0.2" android:fillColor="#FFFFFF"
+        android:pathData="M13.58,69.14L87.15,12c5.2,-4.04 12.46,-3.99 17.61,0.12l73.81,58.94c3.36,2.68 5.27,6.67 5.41,10.82c0.15,-4.51 -1.79,-8.93 -5.41,-11.82l-73.81,-58.94C99.61,7.01 92.35,6.96 87.15,11L13.58,68.14c-3.71,2.88 -5.72,7.36 -5.56,11.94C8.17,75.85 10.14,71.81 13.58,69.14z" android:strokeAlpha="0.2"/>
+    <path android:fillAlpha="0.2" android:fillColor="#263238"
+        android:pathData="M183.35,84.63l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84c-6.31,0 -11.87,-4.17 -13.7,-10.26L8.61,82.77c-0.36,-1.22 -0.55,-2.46 -0.59,-3.69c-0.06,1.56 0.13,3.14 0.59,4.69l28.53,89.97c1.83,6.09 7.39,10.26 13.7,10.26h90.37c6.28,0 11.82,-4.13 13.67,-10.19l28.48,-88.18c0.48,-1.57 0.67,-3.17 0.61,-4.75C183.92,82.13 183.73,83.39 183.35,84.63z" android:strokeAlpha="0.2"/>
+    <path android:pathData="M60.01,135L60.01,135H60l48.04,49h33.17c6.28,0 11.82,-4.13 13.67,-10.19l18.68,-57.84L129.51,72.2l-0.01,0c0.27,0.27 0.52,0.5 0.77,0.77l0.55,0.55c1.57,1.56 1.57,4.08 -0.04,5.68l-12.56,12.49l6.36,6.3l1.38,1.37l-5.67,5.64l-5.71,-5.68L79.15,135H60.42H60.01z">
+        <aapt:attr name="android:fillColor">
+            <gradient android:endX="155.9627" android:endY="165.1493"
+                android:startX="98.4649" android:startY="107.6516" android:type="linear">
+                <item android:color="#19263238" android:offset="0"/>
+                <item android:color="#00212121" android:offset="1"/>
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path android:fillColor="#0097A7" android:pathData="M68.55,120.35l32.173,-32.173l7.658,7.658l-32.173,32.173z"/>
+    <path android:fillColor="#FFFFFF" android:pathData="M130.83,73.52l-9.42,-9.36c-1.57,-1.56 -4.1,-1.56 -5.67,0l-12.56,12.48L95.42,69l-5.67,5.64l5.71,5.68L60,116.97V135h19.15l35.42,-35.68l5.71,5.68l5.67,-5.64l-7.73,-7.68l12.56,-12.48C132.4,77.6 132.4,75.08 130.83,73.52zM74.98,126.77l-6.43,-6.43l32.17,-32.17l6.43,6.43L74.98,126.77z"/>
+    <path android:fillAlpha="0.1" android:fillColor="#263238"
+        android:pathData="M120.28,105l-5.71,-5.68l-35.42,35.68l-19.15,0l1,1l19.15,0l35.42,-35.68l5.71,5.68l5.68,-5.64l-1,-1z" android:strokeAlpha="0.1"/>
+    <path android:fillAlpha="0.1" android:fillColor="#263238"
+        android:pathData="M90.75,75.64l-0.01,0l4.71,4.68l0.01,0z" android:strokeAlpha="0.1"/>
+    <path android:fillAlpha="0.1" android:fillColor="#263238"
+        android:pathData="M131.83,74.52l-0.97,-0.97c1.54,1.56 1.53,4.06 -0.07,5.65l-12.56,12.48l1,1l12.55,-12.48C133.4,78.6 133.4,76.08 131.83,74.52z" android:strokeAlpha="0.1"/>
+    <path android:fillAlpha="0.1" android:fillColor="#263238"
+        android:pathData="M101.72,89.17l6.67,6.66l0,0l-7.67,-7.66l-32.17,32.17l1,1z" android:strokeAlpha="0.1"/>
+    <path android:pathData="M37.13,173.7L8.62,83.69c-1.7,-5.8 0.3,-12 5,-15.6l73.52,-57.1c5.2,-4 12.5,-4 17.6,0.1l73.82,58.9c4.6,3.7 6.5,9.9 4.8,15.6l-28.51,88.21c-1.8,6.1 -7.4,10.2 -13.7,10.2H50.83C44.53,184 38.93,179.8 37.13,173.7z">
+        <aapt:attr name="android:fillColor">
+            <gradient android:centerX="21.977" android:centerY="23.8809"
+                android:gradientRadius="158.0384" android:type="radial">
+                <item android:color="#19FFFFFF" android:offset="0"/>
+                <item android:color="#00FFFFFF" android:offset="1"/>
+            </gradient>
+        </aapt:attr>
+    </path>
+</vector>
diff --git a/core/res/res/drawable/ic_accessibility_color_inversion.xml b/core/res/res/drawable/ic_accessibility_color_inversion.xml
new file mode 100644
index 0000000..d69a169
--- /dev/null
+++ b/core/res/res/drawable/ic_accessibility_color_inversion.xml
@@ -0,0 +1,62 @@
+<!--
+    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.
+-->
+<vector android:height="24dp" android:viewportHeight="192"
+    android:viewportWidth="192" android:width="24dp"
+    xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#546E7A" android:pathData="M37.14,173.74L8.61,83.77c-1.72,-5.75 0.26,-11.97 4.97,-15.63L87.15,11c5.2,-4.04 12.46,-3.99 17.61,0.12l73.81,58.94c4.63,3.7 6.53,9.88 4.8,15.57l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84C44.53,184 38.97,179.83 37.14,173.74z"/>
+    <path android:fillAlpha="0.2" android:fillColor="#263238"
+        android:pathData="M183.37,84.63l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84c-6.31,0 -11.87,-4.17 -13.7,-10.26L8.61,82.77c-0.36,-1.22 -0.55,-2.46 -0.59,-3.69c-0.06,1.56 0.13,3.14 0.59,4.69l28.53,89.97c1.83,6.09 7.39,10.26 13.7,10.26h90.38c6.28,0 11.82,-4.13 13.67,-10.19l28.48,-88.18c0.48,-1.57 0.67,-3.17 0.61,-4.75C183.94,82.13 183.74,83.39 183.37,84.63z" android:strokeAlpha="0.2"/>
+    <path android:fillAlpha="0.2" android:fillColor="#FFFFFF"
+        android:pathData="M13.58,69.14L87.15,12c5.2,-4.04 12.46,-3.99 17.61,0.12l73.81,58.94c3.36,2.68 5.27,6.67 5.41,10.82c0.15,-4.51 -1.79,-8.93 -5.41,-11.82l-73.81,-58.94C99.61,7.01 92.35,6.96 87.15,11L13.58,68.14c-3.71,2.88 -5.72,7.36 -5.56,11.94C8.17,75.85 10.14,71.81 13.58,69.14z" android:strokeAlpha="0.2"/>
+    <path android:fillAlpha="0.2" android:fillColor="#FFFFFF"
+        android:pathData="M53,130.05l5.03,-4.79L44.16,112c-0.05,0.78 -0.14,1.52 -0.15,2.34C43.62,136.61 61.27,154.56 84,156v-5.31C70.13,149.64 58.56,141.54 53,130.05z" android:strokeAlpha="0.2"/>
+    <path android:fillAlpha="0.2" android:fillColor="#FFFFFF"
+        android:pathData="M109,52v5.31c13.65,1.05 24.67,9.15 30.11,20.64l-4.92,4.79L147.81,96c0.09,-0.78 0.17,-1.53 0.19,-2.34C148.38,71.39 131.36,53.44 109,52z" android:strokeAlpha="0.2"/>
+    <path android:pathData="M154.89,173.81l13.57,-42.02l-46.53,-46.56C125.75,90.5 128,96.98 128,104c0,17.7 -14.3,32 -32,32c-8.64,0 -16.47,-3.42 -22.22,-8.97l0.9,0.91L130.73,184h10.49C147.5,184 153.04,179.87 154.89,173.81z">
+        <aapt:attr name="android:fillColor">
+            <gradient android:endX="153.3523" android:endY="161.6371"
+                android:startX="90.6075" android:startY="98.8923" android:type="linear">
+                <item android:color="#33263238" android:offset="0"/>
+                <item android:color="#05263238" android:offset="1"/>
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path android:pathData="M96,129.6V78.4c-14.11,0 -25.6,11.49 -25.6,25.6S81.89,129.6 96,129.6z">
+        <aapt:attr name="android:fillColor">
+            <gradient android:endX="150.8492" android:endY="164.1401"
+                android:startX="88.1044" android:startY="101.3954" android:type="linear">
+                <item android:color="#33263238" android:offset="0"/>
+                <item android:color="#05263238" android:offset="1"/>
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path android:fillAlpha="0.2" android:fillColor="#263238"
+        android:pathData="M96,136c-17.53,0 -31.72,-14.04 -31.99,-31.5c0,0.17 -0.01,0.33 -0.01,0.5c0,17.7 14.3,32 32,32s32,-14.3 32,-32c0,-0.17 -0.01,-0.33 -0.01,-0.5C127.72,121.96 113.53,136 96,136z" android:strokeAlpha="0.2"/>
+    <path android:fillAlpha="0.2" android:fillColor="#263238"
+        android:pathData="M70.4,104c0,0.17 0.01,0.33 0.01,0.5C70.68,90.62 82.06,79.4 96,79.4v-1C81.89,78.4 70.4,89.88 70.4,104z" android:strokeAlpha="0.2"/>
+    <path android:fillAlpha="0.2" android:fillColor="#FFFFFF"
+        android:pathData="M96,72c-17.7,0 -32,14.3 -32,32s14.3,32 32,32s32,-14.3 32,-32S113.7,72 96,72zM70.4,104c0,-14.11 11.49,-25.6 25.6,-25.6v51.2C81.89,129.6 70.4,118.11 70.4,104z" android:strokeAlpha="0.2"/>
+    <path android:fillColor="#FFFFFF" android:pathData="M96,72c-17.7,0 -32,14.3 -32,32s14.3,32 32,32s32,-14.3 32,-32S113.7,72 96,72zM70.4,104c0,-14.11 11.49,-25.6 25.6,-25.6v51.2C81.89,129.6 70.4,118.11 70.4,104z"/>
+    <path android:pathData="M37.14,173.74L8.61,83.77c-1.72,-5.75 0.26,-11.97 4.97,-15.63L87.15,11c5.2,-4.04 12.46,-3.99 17.61,0.12l73.81,58.94c4.63,3.7 6.53,9.88 4.8,15.57l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84C44.53,184 38.97,179.83 37.14,173.74z">
+        <aapt:attr name="android:fillColor">
+            <gradient android:endX="156.2451" android:endY="171.4516"
+                android:startX="37.0633" android:startY="52.269802" android:type="linear">
+                <item android:color="#19FFFFFF" android:offset="0"/>
+                <item android:color="#00FFFFFF" android:offset="1"/>
+            </gradient>
+        </aapt:attr>
+    </path>
+</vector>
diff --git a/core/res/res/drawable/ic_accessibility_magnification.xml b/core/res/res/drawable/ic_accessibility_magnification.xml
new file mode 100644
index 0000000..5dab479
--- /dev/null
+++ b/core/res/res/drawable/ic_accessibility_magnification.xml
@@ -0,0 +1,114 @@
+<!--
+    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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="192"
+    android:viewportHeight="192">
+  <path
+      android:pathData="M96,104m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0"
+      android:fillColor="#F50057"/>
+  <path
+      android:pathData="M178.57,70.06l-73.81,-58.94C99.6,7.01 92.35,6.96 87.15,11L13.59,68.14C8.87,71.8 6.9,78.02 8.61,83.77l28.53,89.97c1.83,6.09 7.39,10.26 13.7,10.26h90.38c6.28,0 11.82,-4.13 13.67,-10.19l0.69,-2.13l-34.94,-34.88v-4.7l-0.96,-0.99c-6.33,5.54 -14.61,8.9 -23.68,8.9c-19.89,0 -36.02,-16.12 -36.02,-36.01S76.11,68 96,68s36.01,16.12 36.01,36.01c0,8.68 -3.08,16.65 -8.2,22.87l1.05,1.01h4.7l30.34,30.39l23.47,-72.65C185.1,79.94 183.2,73.76 178.57,70.06z"
+      android:fillColor="#F50057"/>
+  <path
+      android:pathData="M65.25,73c0,0 -16.75,31.96 -9.25,45.1s21.02,29.15 40.01,32.65s32.99,10.5 39.99,14s19.58,6.93 19.58,6.93s-5.34,-9.49 4.32,-13.4c0.73,-3.53 -11.9,-42.35 -11.9,-42.35L127.92,73L81.79,62.25L65.25,73z"
+      android:fillColor="#F50057"/>
+  <path
+      android:pathData="M155.33,171.43l-0.44,1.37c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84c-6.31,0 -11.87,-4.17 -13.7,-10.26L8.61,82.77c-0.36,-1.22 -0.55,-2.46 -0.59,-3.69c-0.06,1.56 0.13,3.14 0.59,4.69l28.53,89.97c1.83,6.09 7.39,10.26 13.7,10.26h90.38c6.28,0 11.82,-4.13 13.67,-10.19l0.69,-2.13L155.33,171.43z"
+      android:strokeAlpha="0.2"
+      android:fillColor="#3E2723"
+      android:fillAlpha="0.2"/>
+  <path
+      android:pathData="M183.37,84.63l-23.71,73.41l0.24,0.24l23.47,-72.66c0.48,-1.57 0.67,-3.17 0.61,-4.75C183.94,82.13 183.74,83.39 183.37,84.63z"
+      android:strokeAlpha="0.2"
+      android:fillColor="#3E2723"
+      android:fillAlpha="0.2"/>
+  <path
+      android:pathData="M155.57,171.67l-34.93,-34.87v-4.7l-0.96,-0.99c-6.33,5.54 -14.61,8.9 -23.68,8.9c-9.81,0 -18.71,-3.93 -25.2,-10.29L125.07,184h16.14c6.28,0 11.81,-4.13 13.67,-10.19L155.57,171.67z">
+    <aapt:attr name="android:fillColor">
+      <gradient
+          android:startY="104.215"
+          android:startX="69.035"
+          android:endY="173.8946"
+          android:endX="138.7146"
+          android:type="linear">
+        <item android:offset="0" android:color="#333E2723"/>
+        <item android:offset="1" android:color="#003E2723"/>
+      </gradient>
+    </aapt:attr>
+  </path>
+  <path
+      android:pathData="M132.01,104.01c0,8.68 -3.08,16.65 -8.2,22.87l1.05,1.01h4.7l30.34,30.39L170,127l-49,-49.03l-0.19,-0.04C127.71,84.49 132.01,93.74 132.01,104.01z">
+    <aapt:attr name="android:fillColor">
+      <gradient
+          android:startY="83.635"
+          android:startX="103.615"
+          android:endY="137.0219"
+          android:endX="157.0018"
+          android:type="linear">
+        <item android:offset="0" android:color="#333E2723"/>
+        <item android:offset="1" android:color="#003E2723"/>
+      </gradient>
+    </aapt:attr>
+  </path>
+  <path
+      android:pathData="M124.27,127.32c4.85,-6.13 7.75,-13.88 7.75,-22.3c0,-0.16 -0.01,-0.31 -0.01,-0.47c-0.12,8.47 -3.17,16.24 -8.19,22.34L124.27,127.32z"
+      android:strokeAlpha="0.2"
+      android:fillColor="#3E2723"
+      android:fillAlpha="0.2"/>
+  <path
+      android:pathData="M96.01,80.01c-13.25,0 -24,10.75 -24,24c0,0.17 0.01,0.33 0.01,0.5c0.27,-13.02 10.91,-23.5 23.99,-23.5s23.72,10.48 23.99,23.5c0,-0.17 0.01,-0.33 0.01,-0.5C120.01,90.76 109.26,80.01 96.01,80.01z"
+      android:strokeAlpha="0.2"
+      android:fillColor="#3E2723"
+      android:fillAlpha="0.2"/>
+  <path
+      android:pathData="M155.58,171.68l-34.93,-34.87l0,1l34.68,34.62z"
+      android:strokeAlpha="0.2"
+      android:fillColor="#3E2723"
+      android:fillAlpha="0.2"/>
+  <path
+      android:pathData="M119.69,131.12c-6.33,5.54 -14.61,8.9 -23.68,8.9c-9.97,0 -19,-4.06 -25.52,-10.61h-0.01l5.59,5.59c5.71,3.8 12.57,6.03 19.94,6.03c9.07,0 17.35,-3.36 23.68,-8.9l0.96,0.99v-1L119.69,131.12z"
+      android:strokeAlpha="0.2"
+      android:fillColor="#3E2723"
+      android:fillAlpha="0.2"/>
+  <path
+      android:pathData="M13.59,69.14L87.15,12c5.2,-4.04 12.45,-3.99 17.61,0.12l73.81,58.94c3.36,2.68 5.27,6.67 5.41,10.82c0.15,-4.51 -1.79,-8.93 -5.41,-11.82l-73.81,-58.94C99.6,7.01 92.35,6.96 87.15,11L13.59,68.14c-3.72,2.88 -5.72,7.36 -5.57,11.94C8.17,75.85 10.14,71.81 13.59,69.14z"
+      android:strokeAlpha="0.2"
+      android:fillColor="#FFFFFF"
+      android:fillAlpha="0.2"/>
+  <path
+      android:pathData="M112,108h-12v12h-8v-12H80v-8h12V88h8v12h12V108z"
+      android:fillColor="#F8BBD0"/>
+  <path
+      android:pathData="M129.57,127.9h-4.7l-1.05,-1.01c5.12,-6.22 8.2,-14.19 8.2,-22.87c0,-19.89 -16.12,-36.01 -36.01,-36.01s-36.02,16.11 -36.02,36s16.13,36.01 36.02,36.01c9.07,0 17.35,-3.36 23.68,-8.9l0.96,0.99v4.7l34.93,34.87l4.33,-13.39L129.57,127.9zM96.01,128.01c-13.25,0 -24,-10.75 -24,-24s10.75,-24 24,-24s24,10.75 24,24S109.26,128.01 96.01,128.01z"
+      android:fillColor="#FFFFFF"/>
+  <path
+      android:pathData="M37.14,173.74L8.61,83.77C6.9,78.02 8.87,71.8 13.59,68.14L87.15,11c5.2,-4.04 12.45,-3.99 17.61,0.12l73.81,58.94c4.63,3.7 6.53,9.88 4.8,15.57l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84C44.53,184 38.97,179.83 37.14,173.74z">
+    <aapt:attr name="android:fillColor">
+      <gradient
+          android:startY="53.3534"
+          android:startX="38.1466"
+          android:endY="178.712"
+          android:endX="163.5051"
+          android:type="linear">
+        <item android:offset="0" android:color="#19FFFFFF"/>
+        <item android:offset="1" android:color="#00FFFFFF"/>
+      </gradient>
+    </aapt:attr>
+  </path>
+</vector>
diff --git a/core/res/res/layout/accessibility_button_chooser_item.xml b/core/res/res/layout/accessibility_button_chooser_item.xml
index 1edd2cd..d19e313 100644
--- a/core/res/res/layout/accessibility_button_chooser_item.xml
+++ b/core/res/res/layout/accessibility_button_chooser_item.xml
@@ -36,7 +36,7 @@
         android:id="@+id/accessibility_button_target_label"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
-        android:layout_marginStart="8dp"
+        android:layout_marginStart="14dp"
         android:layout_weight="1"
         android:textColor="?attr/textColorPrimary"/>
 
diff --git a/core/res/res/layout/autofill_inline_suggestion.xml b/core/res/res/layout/autofill_inline_suggestion.xml
new file mode 100644
index 0000000..f7ac164
--- /dev/null
+++ b/core/res/res/layout/autofill_inline_suggestion.xml
@@ -0,0 +1,67 @@
+<?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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="wrap_content"
+    android:layout_height="56dp"
+    android:background="@color/white"
+    android:orientation="horizontal"
+    android:paddingStart="12dp"
+    android:paddingEnd="12dp">
+
+    <ImageView
+        android:id="@+id/autofill_inline_suggestion_start_icon"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_gravity="center"
+        android:contentDescription="autofill_inline_suggestion_start_icon" />
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_gravity="center"
+        android:layout_weight="1"
+        android:layout_marginStart="12dp"
+        android:layout_marginEnd="12dp"
+        android:orientation="vertical"
+        android:gravity="center_vertical">
+
+        <TextView
+            android:id="@+id/autofill_inline_suggestion_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:ellipsize="end"
+            android:maxLines="1"
+            tools:text="username1"/>
+
+        <TextView
+            android:id="@+id/autofill_inline_suggestion_subtitle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:ellipsize="end"
+            android:maxLines="1"
+            tools:text="inline fill service"/>
+    </LinearLayout>
+
+    <ImageView
+        android:id="@+id/autofill_inline_suggestion_end_icon"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_gravity="center"
+        android:contentDescription="autofill_inline_suggestion_end_icon" />
+</LinearLayout>
diff --git a/core/res/res/layout/chooser_grid.xml b/core/res/res/layout/chooser_grid.xml
index 0c45e45..4e8a41f 100644
--- a/core/res/res/layout/chooser_grid.xml
+++ b/core/res/res/layout/chooser_grid.xml
@@ -61,10 +61,33 @@
         android:layout_height="wrap_content"
         android:visibility="gone" />
 
-    <com.android.internal.widget.ViewPager
-        android:id="@+id/profile_pager"
+    <TabHost
+        android:id="@+id/profile_tabhost"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"/>
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:layout_centerHorizontal="true"
+        android:background="?attr/colorBackgroundFloating">
+        <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+            <TabWidget
+                android:id="@android:id/tabs"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+            </TabWidget>
+            <FrameLayout
+                android:id="@android:id/tabcontent"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+                <com.android.internal.app.ResolverViewPager
+                    android:id="@+id/profile_pager"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"/>
+            </FrameLayout>
+        </LinearLayout>
+    </TabHost>
 
     <TextView android:id="@+id/empty"
               android:layout_width="match_parent"
diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml
index c5d8912..757cd53 100644
--- a/core/res/res/layout/resolver_list.xml
+++ b/core/res/res/layout/resolver_list.xml
@@ -70,14 +70,44 @@
         android:background="?attr/colorBackgroundFloating"
         android:foreground="?attr/dividerVertical" />
 
-    <com.android.internal.app.WrapHeightViewPager
-        android:id="@+id/profile_pager"
+    <FrameLayout
+        android:id="@+id/stub"
+        android:visibility="gone"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:divider="?attr/dividerVertical"
-        android:footerDividersEnabled="false"
-        android:headerDividersEnabled="false"
-        android:dividerHeight="1dp"/>
+        android:background="?attr/colorBackgroundFloating"/>
+
+    <TabHost
+        android:id="@+id/profile_tabhost"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:layout_centerHorizontal="true"
+        android:background="?attr/colorBackgroundFloating">
+        <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+            <TabWidget
+                android:id="@android:id/tabs"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+            </TabWidget>
+            <FrameLayout
+                android:id="@android:id/tabcontent"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+                <com.android.internal.app.ResolverViewPager
+                    android:id="@+id/profile_pager"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:divider="?attr/dividerVertical"
+                    android:footerDividersEnabled="false"
+                    android:headerDividersEnabled="false"
+                    android:dividerHeight="1dp"/>
+            </FrameLayout>
+        </LinearLayout>
+    </TabHost>
 
     <View
         android:layout_alwaysShow="true"
diff --git a/core/res/res/layout/resolver_list_per_profile.xml b/core/res/res/layout/resolver_list_per_profile.xml
index 68b9917..6d8d348 100644
--- a/core/res/res/layout/resolver_list_per_profile.xml
+++ b/core/res/res/layout/resolver_list_per_profile.xml
@@ -25,7 +25,7 @@
     android:nestedScrollingEnabled="true"
     android:scrollbarStyle="outsideOverlay"
     android:scrollIndicators="top|bottom"
-    android:divider="?attr/dividerVertical"
+    android:divider="@null"
     android:footerDividersEnabled="false"
     android:headerDividersEnabled="false"
-    android:dividerHeight="1dp" />
\ No newline at end of file
+    android:dividerHeight="0dp" />
\ No newline at end of file
diff --git a/core/res/res/layout/resolver_list_with_default.xml b/core/res/res/layout/resolver_list_with_default.xml
index 5b3d929..b546738 100644
--- a/core/res/res/layout/resolver_list_with_default.xml
+++ b/core/res/res/layout/resolver_list_with_default.xml
@@ -151,14 +151,46 @@
         android:background="?attr/colorBackgroundFloating"
         android:foreground="?attr/dividerVertical" />
 
-    <com.android.internal.app.WrapHeightViewPager
-        android:id="@+id/profile_pager"
+    <FrameLayout
+        android:id="@+id/stub"
+        android:visibility="gone"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:dividerHeight="1dp"
-        android:divider="?attr/dividerVertical"
-        android:footerDividersEnabled="false"
-        android:headerDividersEnabled="false"/>
+        android:background="?attr/colorBackgroundFloating"/>
+
+    <TabHost
+        android:id="@+id/profile_tabhost"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:layout_centerHorizontal="true"
+        android:background="?attr/colorBackgroundFloating">
+        <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+            <TabWidget
+                android:id="@android:id/tabs"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:visibility="gone">
+            </TabWidget>
+            <FrameLayout
+                android:id="@android:id/tabcontent"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+                <com.android.internal.app.ResolverViewPager
+                    android:id="@+id/profile_pager"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:dividerHeight="1dp"
+                    android:divider="?attr/dividerVertical"
+                    android:footerDividersEnabled="false"
+                    android:headerDividersEnabled="false"/>
+            </FrameLayout>
+        </LinearLayout>
+    </TabHost>
+
     <View
         android:layout_alwaysShow="true"
         android:layout_width="match_parent"
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 3dd5651..be0e588 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"by jou kalender in te gaan"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS-boodskappe te stuur en te bekyk"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Berging"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"toegang te verkry tot foto\'s, media en lêers op jou toestel"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofoon"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"oudio op te neem"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Kan tik, swiep, knyp en ander gebare uitvoer."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Vingerafdrukgebare"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Kan gebare vasvang wat op die toestel se vingerafdruksensor uitgevoer word."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"deaktiveer of verander statusbalk"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Laat die program toe om die statusbalk te deaktiveer en stelselikone by te voeg of te verwyder."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"wees die statusbalk"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Verhoog volume bo aanbevole vlak?\n\nOm lang tydperke teen hoë volume te luister, kan jou gehoor beskadig."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Gebruik toeganklikheidkortpad?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Wanneer die kortpad aan is, sal \'n toeganklikheidkenmerk begin word as albei volumeknoppies 3 sekondes lank gedruk word.\n\n Bestaande toeganklikheidkenmerk:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Jy kan die kenmerk in Instellings &gt; Toeganklikheid verander."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Maak leeg"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Wysig kortpaaie"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Kanselleer"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Skakel kortpad af"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Gebruik kortpad"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Kleuromkering"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Skakel werkprofiel aan?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Jou werkprogramme, kennisgewings, data en ander werkprofielkenmerke sal aangeskakel word"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Skakel aan"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Hierdie program is vir \'n ouer weergawe van Android gebou en sal dalk nie behoorlik werk nie. Probeer kyk vir opdaterings, of kontak die ontwikkelaar."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Kyk vir opdatering"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Jy het nuwe boodskappe"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Battery kan afloop voordat dit normaalweg gelaai word"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Batterybespaarder is geaktiveer om batterylewe te verleng"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Batterybespaarder"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Batterybespaarder sal nie weer aanskakel voordat battery amper pap is nie"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Battery is tot \'n voldoende vlak gelaai. Batterybespaarder sal nie weer aanskakel voordat battery amper pap is nie"</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Foon <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> gelaai"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Tablet <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> gelaai"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Toestel <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> gelaai"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Batterybespaarder is af. Kenmerke is nie meer beperk nie."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Batterybespaarder is afgeskakel. Kenmerke is nie meer beperk nie."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"Vouer"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android-program"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Lêer"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 9b9dbb0..c92d3b0 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -287,7 +287,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"የእርስዎን ቀን መቁጠሪያ ይድረሱበት"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"ኤስኤምኤስ"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"የኤስኤምኤስ መልዕክቶችን ይላኩና ይመልከቱ"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"ማከማቻ"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"ፋይሎች እና ሚዲያ"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"በመሳሪያዎ ላይ ያሉ ፎቶዎችን፣ ማህደረመረጃን እና ፋይሎችን ይድረሱ"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"ማይክሮፎን"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"ኦዲዮ ይቅዱ"</string>
@@ -313,6 +313,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"መታ ማድረግ፣ ማንሸራተት፣ መቆንጠጥ እና ሌሎች የጣት ምልክቶችን ማከናወን ይችላል።"</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"የጣት አሻራ ምልክቶች"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"በመሣሪያው የጣት አሻራ ዳሳሽ ላይ የተከናወኑ የጣት ምልክቶችን መያዝ ይችላል።"</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"ቅጽበታዊ ገጽ እይታን ያነሳል"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"የማሳያው ቅጽበታዊ ገጽ እይታን ማንሳት ይችላል።"</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"የሁኔቴ አሞሌ አቦዝን ወይም ቀይር"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"የስርዓት አዶዎችን ወደ ሁኔታ አሞሌ ላለማስቻል ወይም ለማከል እና ለማስወገድ ለመተግበሪያው ይፈቅዳሉ፡፡"</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"የሁኔታ አሞሌ መሆን"</string>
@@ -1610,6 +1612,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ድምጹ ከሚመከረው መጠን በላይ ከፍ ይበል?\n\nበከፍተኛ ድምጽ ለረጅም ጊዜ ማዳመጥ ጆሮዎን ሊጎዳው ይችላል።"</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"የተደራሽነት አቋራጭ ጥቅም ላይ ይዋል?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"አቋራጩ ሲበራ ሁለቱንም የድምፅ አዝራሮች ለ3 ሰከንዶች ተጭኖ መቆየት የተደራሽነት ባህሪን ያስጀምረዋል።\n\n አሁን ያለ የተደራሽነት ባህሪ፦\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n ባህሪውን በቅንብሮች &gt; ተደራሽነት ውስጥ ሊለውጡት ይችላሉ።"</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"ባዶ"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"አቋራጮችን አርትዕ ያድርጉ"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"ይቅር"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"አቋራጩን አጥፋ"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"አቋራጭ ይጠቀሙ"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"ተቃራኒ ቀለም"</string>
@@ -1845,6 +1850,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"የስራ መገለጫ ይብራ?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"የእርስዎ የስራ መተግበሪያዎች፣ ማሳወቂያዎች፣ ውሂብ እና ሌሎች የስራ መገለጫ ባህሪያት ይበራሉ"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"አብራ"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"መተግበሪያ አይገኝም"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> አሁን አይገኝም።"</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ይህ መተግበሪያ ለቆየ የAndroid ስሪት ነው የተገነባው፣ እና በአግባቡ ላይሰራ ይችላል። ዝማኔዎች ካሉ ለመመልከት ይሞክሩ፣ ወይም ደግሞ ገንቢውን ያነጋግሩ።"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"ዝማኔ ካለ አረጋግጥ"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"አዲስ መልዕክቶች አለዎት"</string>
@@ -1955,13 +1962,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"ባትሪ ከተለመደው ኃይል መሙላት በፊት ሊያልቅ ይችላል"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"የባትሪ ቆጣቢ የባትሪ ዕድሜን ለማራዘም ገብሯል።"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"ባትሪ ቆጣቢ"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"ባትሪ ቆጣቢ ባትሪ እንደገና ዝቅ እስከሚል ድረስ ዳግም አይገብርም።"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"ባትሪ በቂ ወደ ሆነ ደረጃ ኃይል ተሞልቷል። የባትሪ ቆጣቢ ባቲትው እንደገና ዝቅ እስከሚል ድረስ ዳግም አይገብርም።"</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"ስልክ <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ኃይል ተሞልቷል"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"ጡባዊ <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ኃይል ተሞልቷል"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"መሣሪያ <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ኃይል ተሞልቷል"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"የባትሪ ኃይል ቆጣቢ ጠፍቷል። ባህሪያት ከእንግዲህ የተገደቡ አይደሉም።"</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"የባትሪ ቆጣቢ ጠፍቷል። ባህሪያት ከእንግዲህ የተገደቡ አይደሉም።"</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"የባትሪ ቆጣቢ ጠፍቷል"</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"ስልክ በቂ የባትሪ ኃይል አለው። ባህሪያት ከእንግዲህ የተገደቡ አይደሉም።"</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"ጡባዊ በቂ የባትሪ ኃይል አለው። ባህሪያት ከእንግዲህ የተገደቡ አይደሉም።"</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"መሣሪያ በቂ የባትሪ ኃይል የለውም። ባህሪያት ከእንግዲህ የተገደቡ አይደሉም።"</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"አቃፊ"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"የAndroid መተግበሪያ"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"ፋይል"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 68708d35..1a6bbd8 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -299,7 +299,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"الوصول تقويمك"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"‏إرسال رسائل قصيرة SMS وعرضها"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"التخزين"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"الوصول إلى الصور والوسائط والملفات على جهازك"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"الميكروفون"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"تسجيل الصوت"</string>
@@ -322,9 +323,13 @@
     <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"التحكم في تكبير الشاشة"</string>
     <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"يمكنك التحكّم في مستوى تكبير/تصغير الشاشة وتحديد الموضع."</string>
     <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"تنفيذ إيماءات"</string>
-    <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"يمكن النقر والتمرير بسرعة والتصغير وتنفيذ إيماءات أخرى."</string>
+    <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"يمكن النقر والتمرير بسرعة والتصغير أو التكبير بإصبعين وتنفيذ إيماءات أخرى."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"إيماءات بصمات الإصبع"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"يمكن أن تلتقط الإيماءات التي تم تنفيذها على جهاز استشعار بصمات الإصبع في الجهاز."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"إيقاف شريط الحالة أو تعديله"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"للسماح للتطبيق بإيقاف شريط الحالة أو إضافة رموز نظام وإزالتها."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"العمل كشريط للحالة"</string>
@@ -658,13 +663,13 @@
     <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"للسماح للمالك ببدء استخدام الإذن لأحد التطبيقات. ولن تكون هناك حاجة إليه مطلقًا مع التطبيقات العادية."</string>
     <string name="policylab_limitPassword" msgid="4851829918814422199">"تعيين قواعد كلمة المرور"</string>
     <string name="policydesc_limitPassword" msgid="4105491021115793793">"للتحكم في الطول والأحرف المسموح بها في كلمات المرور وأرقام التعريف الشخصي في قفل الشاشة."</string>
-    <string name="policylab_watchLogin" msgid="7599669460083719504">"مراقبة محاولات إلغاء قفل الشاشة"</string>
-    <string name="policydesc_watchLogin" product="tablet" msgid="2388436408621909298">"لمراقبة عدد مرات كتابة كلمات المرور غير الصحيحة عند إلغاء تأمين الشاشة وتأمين الجهاز اللوحي أو مسح جميع بياناته في حالة كتابة الكثير من كلمات المرور غير الصحيحة."</string>
-    <string name="policydesc_watchLogin" product="tv" msgid="2140588224468517507">"‏يمكنك مراقبة عدد كلمات المرور غير الصحيحة التي تمت كتابتها عند إلغاء قفل الشاشة، وقفل جهاز Android TV أو محو جميع بيانات جهاز Android TV إذا تمت كتابة عدد أكبر من اللازم من كلمات المرور غير الصحيحة."</string>
-    <string name="policydesc_watchLogin" product="default" msgid="4885030206253600299">"مراقبة عدد كلمات المرور غير الصحيحة التي تمت كتابتها عند إلغاء تأمين الشاشة، وتأمين الهاتف ومحو جميع بياناته إذا تمت كتابة عدد أكبر من اللازم من كلمات المرور غير الصحيحة."</string>
-    <string name="policydesc_watchLogin_secondaryUser" product="tablet" msgid="2049038943004297474">"يمكنك مراقبة عدد كلمات المرور غير الصحيحة التي تمت كتابتها عند إلغاء تأمين الشاشة، وتأمين الجهاز اللوحي ومحو جميع بيانات هذا المستخدم إذا تمت كتابة عدد أكبر من اللازم من كلمات المرور غير الصحيحة."</string>
-    <string name="policydesc_watchLogin_secondaryUser" product="tv" msgid="8965224107449407052">"‏يمكنك مراقبة عدد كلمات المرور غير الصحيحة التي تمت كتابتها عند إلغاء قفل الشاشة، وقفل جهاز Android TV أو محو جميع بيانات هذا المستخدم إذا تمت كتابة عدد أكبر من اللازم من كلمات المرور غير الصحيحة."</string>
-    <string name="policydesc_watchLogin_secondaryUser" product="default" msgid="9177645136475155924">"يمكنك مراقبة عدد كلمات المرور غير الصحيحة التي تمت كتابتها عند إلغاء تأمين الشاشة، وتأمين الهاتف ومحو جميع بيانات هذا المستخدم إذا تمت كتابة عدد أكبر من اللازم من كلمات المرور غير الصحيحة."</string>
+    <string name="policylab_watchLogin" msgid="7599669460083719504">"مراقبة محاولات فتح قفل الشاشة"</string>
+    <string name="policydesc_watchLogin" product="tablet" msgid="2388436408621909298">"لمراقبة عدد مرات كتابة كلمات المرور غير الصحيحة عند فتح قفل الشاشة وتأمين الجهاز اللوحي أو مسح جميع بياناته في حالة كتابة الكثير من كلمات المرور غير الصحيحة."</string>
+    <string name="policydesc_watchLogin" product="tv" msgid="2140588224468517507">"‏يمكنك مراقبة عدد كلمات المرور غير الصحيحة التي تمت كتابتها عند فتح قفل الشاشة، وقفل جهاز Android TV أو محو جميع بيانات جهاز Android TV إذا تمت كتابة عدد أكبر من اللازم من كلمات المرور غير الصحيحة."</string>
+    <string name="policydesc_watchLogin" product="default" msgid="4885030206253600299">"مراقبة عدد كلمات المرور غير الصحيحة التي تمت كتابتها عند فتح قفل الشاشة، وتأمين الهاتف ومحو جميع بياناته إذا تمت كتابة عدد أكبر من اللازم من كلمات المرور غير الصحيحة."</string>
+    <string name="policydesc_watchLogin_secondaryUser" product="tablet" msgid="2049038943004297474">"يمكنك مراقبة عدد كلمات المرور غير الصحيحة التي تمت كتابتها عند فتح قفل الشاشة، وتأمين الجهاز اللوحي ومحو جميع بيانات هذا المستخدم إذا تمت كتابة عدد أكبر من اللازم من كلمات المرور غير الصحيحة."</string>
+    <string name="policydesc_watchLogin_secondaryUser" product="tv" msgid="8965224107449407052">"‏يمكنك مراقبة عدد كلمات المرور غير الصحيحة التي تمت كتابتها عند فتح قفل الشاشة، وقفل جهاز Android TV أو محو جميع بيانات هذا المستخدم إذا تمت كتابة عدد أكبر من اللازم من كلمات المرور غير الصحيحة."</string>
+    <string name="policydesc_watchLogin_secondaryUser" product="default" msgid="9177645136475155924">"يمكنك مراقبة عدد كلمات المرور غير الصحيحة التي تمت كتابتها عند فتح قفل الشاشة، وتأمين الهاتف ومحو جميع بيانات هذا المستخدم إذا تمت كتابة عدد أكبر من اللازم من كلمات المرور غير الصحيحة."</string>
     <string name="policylab_resetPassword" msgid="214556238645096520">"تغيير قفل الشاشة"</string>
     <string name="policydesc_resetPassword" msgid="4626419138439341851">"إمكانية تغيير قفل الشاشة"</string>
     <string name="policylab_forceLock" msgid="7360335502968476434">"قفل الشاشة"</string>
@@ -821,7 +826,7 @@
     <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"صحيح!"</string>
     <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"أعد المحاولة"</string>
     <string name="lockscreen_password_wrong" msgid="8605355913868947490">"أعد المحاولة"</string>
-    <string name="lockscreen_storage_locked" msgid="634993789186443380">"إلغاء قفل جميع الميزات والبيانات"</string>
+    <string name="lockscreen_storage_locked" msgid="634993789186443380">"فتح قفل جميع الميزات والبيانات"</string>
     <string name="faceunlock_multiple_failures" msgid="681991538434031708">"تم تجاوز الحد الأقصى لعدد محاولات تأمين الجهاز بالوجه"</string>
     <string name="lockscreen_missing_sim_message_short" msgid="1248431165144893792">"‏ليست هناك شريحة SIM"</string>
     <string name="lockscreen_missing_sim_message" product="tablet" msgid="8596805728510570760">"‏ليس هناك شريحة SIM في الجهاز اللوحي."</string>
@@ -843,22 +848,22 @@
     <string name="lockscreen_sim_puk_locked_message" msgid="6618356415831082174">"‏شريحة SIM مؤمّنة بكود PUK."</string>
     <string name="lockscreen_sim_puk_locked_instructions" msgid="5307979043730860995">"راجع دليل المستخدم أو اتصل بخدمة العملاء."</string>
     <string name="lockscreen_sim_locked_message" msgid="3160196135801185938">"‏شريحة SIM مؤمّنة."</string>
-    <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="2286497117428409709">"‏جارٍ إلغاء تأمين شريحة SIM…"</string>
+    <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="2286497117428409709">"‏جارٍ فتح قفل شريحة SIM…"</string>
     <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6458790975898594240">"لقد رسمت نقش فتح القفل بطريقة غير صحيحة <xliff:g id="NUMBER_0">%1$d</xliff:g> مرة.\n\nيُرجى إعادة المحاولة خلال <xliff:g id="NUMBER_1">%2$d</xliff:g> ثانية."</string>
     <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="3118353451602377380">"لقد كتبت كلمة المرور <xliff:g id="NUMBER_0">%1$d</xliff:g> مرة بشكل غير صحيح. \n\nأعد المحاولة خلال <xliff:g id="NUMBER_1">%2$d</xliff:g> ثانية."</string>
     <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="2874278239714821984">"‏لقد كتبت رمز PIN <xliff:g id="NUMBER_0">%1$d</xliff:g> مرة بشكل غير صحيح. \n\nأعد المحاولة خلال <xliff:g id="NUMBER_1">%2$d</xliff:g> ثانية."</string>
     <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="3069635524964070596">"‏لقد رسمت نقش فتح القفل بشكل غير صحيح <xliff:g id="NUMBER_0">%1$d</xliff:g> مرة. بعد <xliff:g id="NUMBER_1">%2$d</xliff:g> من المحاولات غير الناجحة الأخرى، ستُطالب بإلغاء تأمين الجهاز اللوحي باستخدام معلومات تسجيل الدخول إلى Google.\n\n أعد المحاولة خلال <xliff:g id="NUMBER_2">%3$d</xliff:g> ثانية."</string>
     <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="6399092175942158529">"‏لقد رسمت نقش فتح القفل بشكل غير صحيح <xliff:g id="NUMBER_0">%1$d</xliff:g> مرة. بعد إجراء<xliff:g id="NUMBER_1">%2$d</xliff:g> محاولة أخرى غير ناجحة، ستُطالب بإلغاء قفل جهاز Android TV باستخدام معلومات تسجيل الدخول إلى Google.\n\n يُرجى إعادة المحاولة بعد <xliff:g id="NUMBER_2">%3$d</xliff:g> ثانية."</string>
     <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="5691623136957148335">"‏لقد رسمت نقش فتح القفل بشكل غير صحيح <xliff:g id="NUMBER_0">%1$d</xliff:g> مرة. بعد <xliff:g id="NUMBER_1">%2$d</xliff:g> من المحاولات غير الناجحة الأخرى، ستُطالب بإلغاء تأمين الهاتف باستخدام معلومات تسجيل الدخول إلى Google.\n\n أعد المحاولة خلال <xliff:g id="NUMBER_2">%3$d</xliff:g> ثانية."</string>
-    <string name="lockscreen_failed_attempts_almost_at_wipe" product="tablet" msgid="7914445759242151426">"لقد حاولت إلغاء تأمين الجهاز اللوحي <xliff:g id="NUMBER_0">%1$d</xliff:g> من المرات. بعد <xliff:g id="NUMBER_1">%2$d</xliff:g> من المحاولات غير الناجحة، ستتم إعادة تعيين الجهاز اللوحي إلى الإعدادات الأساسية وسيتم فقد جميع بيانات المستخدم."</string>
-    <string name="lockscreen_failed_attempts_almost_at_wipe" product="tv" msgid="4275591249631864248">"‏لقد حاولت إلغاء قفل جهاز Android TV بشكل غير صحيح <xliff:g id="NUMBER_0">%1$d</xliff:g> مرة. بعد إجراء <xliff:g id="NUMBER_1">%2$d</xliff:g> محاولة أخرى غير ناجحة، ستتم إعادة ضبط جهاز Android TV على الإعداد التلقائي للمصنع وسيتم فقدان جميع بيانات المستخدمين."</string>
-    <string name="lockscreen_failed_attempts_almost_at_wipe" product="default" msgid="1166532464798446579">"لقد حاولت إلغاء تأمين الهاتف <xliff:g id="NUMBER_0">%1$d</xliff:g> من المرات. بعد <xliff:g id="NUMBER_1">%2$d</xliff:g> من المحاولات غير الناجحة، ستتم إعادة تعيين الهاتف إلى الإعدادات الأساسية وسيتم فقد جميع بيانات المستخدم."</string>
-    <string name="lockscreen_failed_attempts_now_wiping" product="tablet" msgid="8682445539263683414">"لقد حاولت إلغاء تأمين الجهاز اللوحي <xliff:g id="NUMBER">%d</xliff:g> من المرات بشكل غير صحيح. سيتم الآن إعادة تعيين الجهاز اللوحي إلى الإعدادات الأساسية."</string>
-    <string name="lockscreen_failed_attempts_now_wiping" product="tv" msgid="2205435033340091883">"‏لقد حاولت إلغاء قفل جهاز Android TV بشكل غير صحيح <xliff:g id="NUMBER">%d</xliff:g> مرة. ستتم الآن إعادة ضبط جهاز Android TV على الإعداد التلقائي للمصنع."</string>
-    <string name="lockscreen_failed_attempts_now_wiping" product="default" msgid="2203704707679895487">"لقد حاولت إلغاء تأمين الهاتف <xliff:g id="NUMBER">%d</xliff:g> من المرات بشكل غير صحيح. سيتم الآن إعادة تعيين الهاتف إلى الإعدادات الأساسية."</string>
+    <string name="lockscreen_failed_attempts_almost_at_wipe" product="tablet" msgid="7914445759242151426">"لقد حاولت فتح قفل الجهاز اللوحي <xliff:g id="NUMBER_0">%1$d</xliff:g> من المرات. بعد <xliff:g id="NUMBER_1">%2$d</xliff:g> من المحاولات غير الناجحة، ستتم إعادة تعيين الجهاز اللوحي إلى الإعدادات الأساسية وسيتم فقد جميع بيانات المستخدم."</string>
+    <string name="lockscreen_failed_attempts_almost_at_wipe" product="tv" msgid="4275591249631864248">"‏لقد حاولت فتح قفل جهاز Android TV بشكل غير صحيح <xliff:g id="NUMBER_0">%1$d</xliff:g> مرة. بعد إجراء <xliff:g id="NUMBER_1">%2$d</xliff:g> محاولة أخرى غير ناجحة، ستتم إعادة ضبط جهاز Android TV على الإعداد التلقائي للمصنع وسيتم فقدان جميع بيانات المستخدمين."</string>
+    <string name="lockscreen_failed_attempts_almost_at_wipe" product="default" msgid="1166532464798446579">"لقد حاولت فتح قفل الهاتف <xliff:g id="NUMBER_0">%1$d</xliff:g> من المرات. بعد <xliff:g id="NUMBER_1">%2$d</xliff:g> من المحاولات غير الناجحة، ستتم إعادة تعيين الهاتف إلى الإعدادات الأساسية وسيتم فقد جميع بيانات المستخدم."</string>
+    <string name="lockscreen_failed_attempts_now_wiping" product="tablet" msgid="8682445539263683414">"لقد حاولت فتح قفل الجهاز اللوحي <xliff:g id="NUMBER">%d</xliff:g> من المرات بشكل غير صحيح. سيتم الآن إعادة تعيين الجهاز اللوحي إلى الإعدادات الأساسية."</string>
+    <string name="lockscreen_failed_attempts_now_wiping" product="tv" msgid="2205435033340091883">"‏لقد حاولت فتح قفل جهاز Android TV بشكل غير صحيح <xliff:g id="NUMBER">%d</xliff:g> مرة. ستتم الآن إعادة ضبط جهاز Android TV على الإعداد التلقائي للمصنع."</string>
+    <string name="lockscreen_failed_attempts_now_wiping" product="default" msgid="2203704707679895487">"لقد حاولت فتح قفل الهاتف <xliff:g id="NUMBER">%d</xliff:g> من المرات بشكل غير صحيح. سيتم الآن إعادة تعيين الهاتف إلى الإعدادات الأساسية."</string>
     <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6807200118164539589">"حاول مرة أخرى خلال <xliff:g id="NUMBER">%d</xliff:g> ثانية."</string>
     <string name="lockscreen_forgot_pattern_button_text" msgid="8362442730606839031">"هل نسيت النمط؟"</string>
-    <string name="lockscreen_glogin_forgot_pattern" msgid="9218940117797602518">"إلغاء تأمين الحساب"</string>
+    <string name="lockscreen_glogin_forgot_pattern" msgid="9218940117797602518">"فتح قفل الحساب"</string>
     <string name="lockscreen_glogin_too_many_attempts" msgid="3775904917743034195">"محاولات النقش كثيرة جدًا"</string>
     <string name="lockscreen_glogin_instructions" msgid="4695162942525531700">"‏لإلغاء التأمين، سجّل الدخول بحسابك في Google."</string>
     <string name="lockscreen_glogin_username_hint" msgid="6916101478673157045">"اسم المستخدم (البريد إلكتروني)"</string>
@@ -867,7 +872,7 @@
     <string name="lockscreen_glogin_invalid_input" msgid="4369219936865697679">"اسم المستخدم غير صحيح أو كلمة المرور غير صحيحة."</string>
     <string name="lockscreen_glogin_account_recovery_hint" msgid="1683405808525090649">"‏هل نسيت اسم المستخدم أو كلمة المرور؟\nانتقل إلى "<b>"google.com/accounts/recovery"</b></string>
     <string name="lockscreen_glogin_checking_password" msgid="2607271802803381645">"جارٍ التحقق..."</string>
-    <string name="lockscreen_unlock_label" msgid="4648257878373307582">"إلغاء تأمين"</string>
+    <string name="lockscreen_unlock_label" msgid="4648257878373307582">"فتح قفل"</string>
     <string name="lockscreen_sound_on_label" msgid="1660281470535492430">"تشغيل الصوت"</string>
     <string name="lockscreen_sound_off_label" msgid="2331496559245450053">"الصوت متوقف"</string>
     <string name="lockscreen_access_pattern_start" msgid="3778502525702613399">"بدأ النمط"</string>
@@ -879,8 +884,8 @@
     <string name="keyguard_accessibility_widget_changed" msgid="7298011259508200234">"‏%1$s. الأداة %2$d من %3$d."</string>
     <string name="keyguard_accessibility_add_widget" msgid="8245795023551343672">"إضافة أداة."</string>
     <string name="keyguard_accessibility_widget_empty_slot" msgid="544239307077644480">"فارغة"</string>
-    <string name="keyguard_accessibility_unlock_area_expanded" msgid="7768634718706488951">"تم توسيع منطقة إلغاء القفل."</string>
-    <string name="keyguard_accessibility_unlock_area_collapsed" msgid="4729922043778400434">"تم تصغير منطقة إلغاء القفل."</string>
+    <string name="keyguard_accessibility_unlock_area_expanded" msgid="7768634718706488951">"تم توسيع منطقة فتح القفل."</string>
+    <string name="keyguard_accessibility_unlock_area_collapsed" msgid="4729922043778400434">"تم تصغير منطقة فتح القفل."</string>
     <string name="keyguard_accessibility_widget" msgid="6776892679715699875">"أداة <xliff:g id="WIDGET_INDEX">%1$s</xliff:g>."</string>
     <string name="keyguard_accessibility_user_selector" msgid="1466067610235696600">"محدد المستخدم"</string>
     <string name="keyguard_accessibility_status" msgid="6792745049712397237">"الحالة"</string>
@@ -889,14 +894,14 @@
     <string name="keyguard_accessibility_widget_reorder_start" msgid="7066213328912939191">"بدأت إعادة ترتيب الأدوات."</string>
     <string name="keyguard_accessibility_widget_reorder_end" msgid="1083806817600593490">"انتهت إعادة ترتيب الأدوات."</string>
     <string name="keyguard_accessibility_widget_deleted" msgid="1509738950119878705">"تم حذف أداة <xliff:g id="WIDGET_INDEX">%1$s</xliff:g>."</string>
-    <string name="keyguard_accessibility_expand_lock_area" msgid="4215280881346033434">"توسيع منطقة إلغاء القفل."</string>
-    <string name="keyguard_accessibility_slide_unlock" msgid="2968195219692413046">"إلغاء القفل باستخدام التمرير."</string>
-    <string name="keyguard_accessibility_pattern_unlock" msgid="8669128146589233293">"إلغاء القفل باستخدام النقش."</string>
+    <string name="keyguard_accessibility_expand_lock_area" msgid="4215280881346033434">"توسيع منطقة فتح القفل."</string>
+    <string name="keyguard_accessibility_slide_unlock" msgid="2968195219692413046">"فتح القفل باستخدام التمرير."</string>
+    <string name="keyguard_accessibility_pattern_unlock" msgid="8669128146589233293">"فتح القفل باستخدام النقش."</string>
     <string name="keyguard_accessibility_face_unlock" msgid="632407612842329815">"تأمين الجهاز بالوجه."</string>
-    <string name="keyguard_accessibility_pin_unlock" msgid="4020864007967340068">"‏إلغاء القفل باستخدام رمز PIN."</string>
-    <string name="keyguard_accessibility_sim_pin_unlock" msgid="4895939120871890557">"‏إلغاء قفل رقم التعريف الشخصي لشريحة SIM."</string>
-    <string name="keyguard_accessibility_sim_puk_unlock" msgid="3459003464041899101">"‏إلغاء قفل مفتاح PUK لشريحة SIM."</string>
-    <string name="keyguard_accessibility_password_unlock" msgid="6130186108581153265">"إلغاء القفل باستخدام كلمة المرور."</string>
+    <string name="keyguard_accessibility_pin_unlock" msgid="4020864007967340068">"‏فتح القفل باستخدام رمز PIN."</string>
+    <string name="keyguard_accessibility_sim_pin_unlock" msgid="4895939120871890557">"‏فتح قفل رقم التعريف الشخصي لشريحة SIM."</string>
+    <string name="keyguard_accessibility_sim_puk_unlock" msgid="3459003464041899101">"‏فتح قفل مفتاح PUK لشريحة SIM."</string>
+    <string name="keyguard_accessibility_password_unlock" msgid="6130186108581153265">"فتح القفل باستخدام كلمة المرور."</string>
     <string name="keyguard_accessibility_pattern_area" msgid="1419570880512350689">"منطقة النقش."</string>
     <string name="keyguard_accessibility_slide_area" msgid="4331399051142520176">"منطقة التمرير."</string>
     <string name="password_keyboard_label_symbol_key" msgid="2716255580853511949">"?123"</string>
@@ -1388,7 +1393,7 @@
     <string name="adb_active_notification_message" msgid="5617264033476778211">"‏انقر لإيقاف تصحيح أخطاء الجهاز عبر USB."</string>
     <string name="adb_active_notification_message" product="tv" msgid="6624498401272780855">"‏اختيار إيقاف تصحيح أخطاء USB."</string>
     <string name="test_harness_mode_notification_title" msgid="2282785860014142511">"تم تفعيل وضع \"مفعّل الاختبار\""</string>
-    <string name="test_harness_mode_notification_message" msgid="3039123743127958420">"يمكنك إجراء إعادة ضبط على إعدادات المصنع لإيقاف وضع \"مفعِّل اختبار\"."</string>
+    <string name="test_harness_mode_notification_message" msgid="3039123743127958420">"يمكنك إجراء إعادة ضبط على الإعدادات الأصلية لإيقاف وضع \"مفعِّل اختبار\"."</string>
     <string name="console_running_notification_title" msgid="6087888939261635904">"وحدة التحكّم التسلسلية مفعّلة"</string>
     <string name="console_running_notification_message" msgid="7892751888125174039">"الأداء متأثر. لإيقاف وحدة التحكّم، تحقّق من برنامج الإقلاع."</string>
     <string name="usb_contaminant_detected_title" msgid="4359048603069159678">"‏السوائل والشوائب في منفذ USB"</string>
@@ -1398,7 +1403,7 @@
     <string name="taking_remote_bugreport_notification_title" msgid="1582531382166919850">"جارٍ الحصول على تقرير الخطأ…"</string>
     <string name="share_remote_bugreport_notification_title" msgid="6708897723753334999">"هل تريد مشاركة تقرير الخطأ؟"</string>
     <string name="sharing_remote_bugreport_notification_title" msgid="3077385149217638550">"جارٍ مشاركة تقرير الخطأ…"</string>
-    <string name="share_remote_bugreport_notification_message_finished" msgid="7325635795739260135">"طلب المشرف الحصول على تقرير خطأ للمساعدة في تحرِّي مشكلة هذا الجهاز وإصلاحها؛ ويمكن أن تتم مشاركة التطبيقات والبيانات."</string>
+    <string name="share_remote_bugreport_notification_message_finished" msgid="7325635795739260135">"طلب المشرف الحصول على تقرير خطأ للمساعدة في تحديد مشكلة هذا الجهاز وحلّها؛ ويمكن أن تتم مشاركة التطبيقات والبيانات."</string>
     <string name="share_remote_bugreport_action" msgid="7630880678785123682">"مشاركة"</string>
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"رفض"</string>
     <string name="select_input_method" msgid="3971267998568587025">"اختيار أسلوب الإدخال"</string>
@@ -1464,7 +1469,7 @@
     <string name="permdesc_requestDeletePackages" msgid="6133633516423860381">"للسماح لتطبيق ما بطلب حذف الحِزم."</string>
     <string name="permlab_requestIgnoreBatteryOptimizations" msgid="7646611326036631439">"طلب تجاهل تحسينات البطارية"</string>
     <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"للسماح للتطبيق بطلب الإذن لتجاهل تحسينات البطارية في هذا التطبيق."</string>
-    <string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"اضغط مرتين للتحكم في التكبير/التصغير"</string>
+    <string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"اضغط مرتين للتحكم في التكبير أو التصغير"</string>
     <string name="gadget_host_error_inflating" msgid="2449961590495198720">"تعذرت إضافة أداة."</string>
     <string name="ime_action_go" msgid="5536744546326495436">"تنفيذ"</string>
     <string name="ime_action_search" msgid="4501435960587287668">"بحث"</string>
@@ -1667,7 +1672,7 @@
     <string name="kg_puk_enter_puk_hint" msgid="6696187482616360994">"‏شريحة SIM غير مفعّلة الآن. أدخل رمز PUK للمتابعة. اتصل بمشغل شبكة الجوال للاطلاع على التفاصيل."</string>
     <string name="kg_puk_enter_pin_hint" msgid="8190982314659429770">"‏إدخال رمز رمز PIN المراد"</string>
     <string name="kg_enter_confirm_pin_hint" msgid="6372557107414074580">"‏تأكيد رمز رمز PIN المراد"</string>
-    <string name="kg_sim_unlock_progress_dialog_message" msgid="8871937892678885545">"‏جارٍ إلغاء تأمين شريحة SIM…"</string>
+    <string name="kg_sim_unlock_progress_dialog_message" msgid="8871937892678885545">"‏جارٍ فتح قفل شريحة SIM…"</string>
     <string name="kg_password_wrong_pin_code" msgid="9013856346870572451">"‏رمز PIN غير صحيح."</string>
     <string name="kg_invalid_sim_pin_hint" msgid="4821601451222564077">"اكتب  رقم التعريف الشخصي المكون من ٤ إلى ٨ أرقام."</string>
     <string name="kg_invalid_sim_puk_hint" msgid="2539364558870734339">"‏يجب أن يتكون رمز PUK من ۸ أرقام."</string>
@@ -1684,12 +1689,12 @@
     <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="23741434207544038">"‏لقد كتبت رمز PIN بشكل غير صحيح <xliff:g id="NUMBER_0">%1$d</xliff:g> مرة. \n\nأعد المحاولة خلال <xliff:g id="NUMBER_1">%2$d</xliff:g> ثانية."</string>
     <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="3328686432962224215">"لقد كتبت كلمة المرور بشكل غير صحيح <xliff:g id="NUMBER_0">%1$d</xliff:g> مرة. \n\nأعد المحاولة خلال <xliff:g id="NUMBER_1">%2$d</xliff:g> ثانية."</string>
     <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="7357404233979139075">"لقد رسمت نقش فتح القفل بطريقة غير صحيحة <xliff:g id="NUMBER_0">%1$d</xliff:g> مرة. \n\nأعد المحاولة خلال <xliff:g id="NUMBER_1">%2$d</xliff:g> ثانية."</string>
-    <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="3479940221343361587">"لقد حاولت إلغاء تأمين الجهاز اللوحي بشكل غير صحيح <xliff:g id="NUMBER_0">%1$d</xliff:g> مرة. بعد إجراء <xliff:g id="NUMBER_1">%2$d</xliff:g> من المحاولات غير الناجحة الأخرى، ستتم إعادة تعيين الجهاز اللوحي على الإعدادات الأساسية وسيتم فقد جميع بيانات المستخدم."</string>
-    <string name="kg_failed_attempts_almost_at_wipe" product="tv" msgid="9064457748587850217">"‏لقد حاولت إلغاء قفل جهاز Android TV بشكل غير صحيح <xliff:g id="NUMBER_0">%1$d</xliff:g> مرة. بعد إجراء <xliff:g id="NUMBER_1">%2$d</xliff:g> محاولة أخرى غير ناجحة، ستتم إعادة ضبط جهاز Android TV على الإعداد التلقائي للمصنع وسيتم فقدان جميع بيانات المستخدمين."</string>
-    <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="5955398963754432548">"لقد حاولت إلغاء تأمين الهاتف بشكل غير صحيح <xliff:g id="NUMBER_0">%1$d</xliff:g> مرة. بعد إجراء <xliff:g id="NUMBER_1">%2$d</xliff:g> من المحاولات غير الناجحة الأخرى، ستتم إعادة تعيين الهاتف على الإعدادات الأساسية وسيتم فقد جميع بيانات المستخدم."</string>
-    <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="2299099385175083308">"لقد حاولت إلغاء تأمين الجهاز اللوحي بشكل غير صحيح <xliff:g id="NUMBER">%d</xliff:g> مرة. سيتم الآن إعادة تعيين الجهاز اللوحي على الإعدادات الأساسية."</string>
-    <string name="kg_failed_attempts_now_wiping" product="tv" msgid="5045460916106267585">"‏لقد حاولت إلغاء قفل جهاز Android TV بشكل غير صحيح <xliff:g id="NUMBER">%d</xliff:g> مرة. ستتم الآن إعادة ضبط جهاز Android TV على الإعداد التلقائي للمصنع."</string>
-    <string name="kg_failed_attempts_now_wiping" product="default" msgid="5043730590446071189">"لقد حاولت إلغاء تأمين الهاتف بشكل غير صحيح <xliff:g id="NUMBER">%d</xliff:g> مرة. سيتم الآن إعادة تعيين الهاتف على الإعدادات الأساسية."</string>
+    <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="3479940221343361587">"لقد حاولت فتح قفل الجهاز اللوحي بشكل غير صحيح <xliff:g id="NUMBER_0">%1$d</xliff:g> مرة. بعد إجراء <xliff:g id="NUMBER_1">%2$d</xliff:g> من المحاولات غير الناجحة الأخرى، ستتم إعادة تعيين الجهاز اللوحي على الإعدادات الأساسية وسيتم فقد جميع بيانات المستخدم."</string>
+    <string name="kg_failed_attempts_almost_at_wipe" product="tv" msgid="9064457748587850217">"‏لقد حاولت فتح قفل جهاز Android TV بشكل غير صحيح <xliff:g id="NUMBER_0">%1$d</xliff:g> مرة. بعد إجراء <xliff:g id="NUMBER_1">%2$d</xliff:g> محاولة أخرى غير ناجحة، ستتم إعادة ضبط جهاز Android TV على الإعداد التلقائي للمصنع وسيتم فقدان جميع بيانات المستخدمين."</string>
+    <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="5955398963754432548">"لقد حاولت فتح قفل الهاتف بشكل غير صحيح <xliff:g id="NUMBER_0">%1$d</xliff:g> مرة. بعد إجراء <xliff:g id="NUMBER_1">%2$d</xliff:g> من المحاولات غير الناجحة الأخرى، ستتم إعادة تعيين الهاتف على الإعدادات الأساسية وسيتم فقد جميع بيانات المستخدم."</string>
+    <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="2299099385175083308">"لقد حاولت فتح قفل الجهاز اللوحي بشكل غير صحيح <xliff:g id="NUMBER">%d</xliff:g> مرة. سيتم الآن إعادة تعيين الجهاز اللوحي على الإعدادات الأساسية."</string>
+    <string name="kg_failed_attempts_now_wiping" product="tv" msgid="5045460916106267585">"‏لقد حاولت فتح قفل جهاز Android TV بشكل غير صحيح <xliff:g id="NUMBER">%d</xliff:g> مرة. ستتم الآن إعادة ضبط جهاز Android TV على الإعداد التلقائي للمصنع."</string>
+    <string name="kg_failed_attempts_now_wiping" product="default" msgid="5043730590446071189">"لقد حاولت فتح قفل الهاتف بشكل غير صحيح <xliff:g id="NUMBER">%d</xliff:g> مرة. سيتم الآن إعادة تعيين الهاتف على الإعدادات الأساسية."</string>
     <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="7086799295109717623">"لقد رسمت نقش فتح القفل بشكل غير صحيح <xliff:g id="NUMBER_0">%1$d</xliff:g> مرة. بعد إجراء <xliff:g id="NUMBER_1">%2$d</xliff:g> من المحاولات غير الناجحة الأخرى، ستطالَب بإلغاء تأمين الجهاز اللوحي باستخدام معلومات حساب بريد إلكتروني.\n\n أعد المحاولة خلال <xliff:g id="NUMBER_2">%3$d</xliff:g> ثانية."</string>
     <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4670840383567106114">"‏لقد رسمت نقش فتح القفل بشكل غير صحيح <xliff:g id="NUMBER_0">%1$d</xliff:g> مرة. بعد إجراء <xliff:g id="NUMBER_1">%2$d</xliff:g> محاولة أخرى غير ناجحة، ستُطالب بإلغاء قفل جهاز Android TV باستخدام حساب بريد إلكتروني.\n\n يُرجى إعادة المحاولة بعد <xliff:g id="NUMBER_2">%3$d</xliff:g> ثانية."</string>
     <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"لقد رسمت نقش فتح القفل بشكل غير صحيح <xliff:g id="NUMBER_0">%1$d</xliff:g> مرة. بعد إجراء <xliff:g id="NUMBER_1">%2$d</xliff:g> من المحاولات غير الناجحة الأخرى، ستُطالب بإلغاء تأمين الهاتف باستخدام حساب بريد إلكتروني لإلغاء تأمين الهاتف.\n\n أعد المحاولة خلال <xliff:g id="NUMBER_2">%3$d</xliff:g> ثانية."</string>
@@ -1698,6 +1703,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"هل تريد رفع مستوى الصوت فوق المستوى الموصى به؟\n\nقد يضر سماع صوت عالٍ لفترات طويلة بسمعك."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"هل تريد استخدام اختصار \"سهولة الاستخدام\"؟"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"عند تشغيل الاختصار، يؤدي الضغط على زرّي مستوى الصوت لمدة 3 ثوانٍ إلى تفعيل ميزة \"سهولة الاستخدام\".\n\n ميزة \"سهولة الاستخدام\" الحالية:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n يمكنك تغيير الميزة من \"الإعدادات\" &gt; \"سهولة الاستخدام\"."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"فارغ"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"تعديل الاختصارات"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"إلغاء"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"إيقاف الاختصار"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"استخدام الاختصار"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"عكس الألوان"</string>
@@ -1973,6 +1981,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"تفعيل الملف الشخصي للعمل؟"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"سيتم تفعيل تطبيقات العمل التي تستخدمها والإشعارات والبيانات وغيرها من ميزات الملف الشخصي للعمل"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"تشغيل"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"‏تمّ إنشاء هذا التطبيق لإصدار قديم من Android وقد لا يعمل بشكل صحيح. جرِّب البحث عن تحديثات أو الاتصال بمطوّر البرامج."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"البحث عن تحديث"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"لديك رسائل جديدة"</string>
@@ -2035,7 +2047,7 @@
     <string name="autofill_save_notnow" msgid="2853932672029024195">"ليس الآن"</string>
     <string name="autofill_save_never" msgid="6821841919831402526">"أبدًا"</string>
     <string name="autofill_update_yes" msgid="4608662968996874445">"تعديل"</string>
-    <string name="autofill_continue_yes" msgid="7914985605534510385">"متابعة"</string>
+    <string name="autofill_continue_yes" msgid="7914985605534510385">"مواصلة"</string>
     <string name="autofill_save_type_password" msgid="5624528786144539944">"كلمة مرور"</string>
     <string name="autofill_save_type_address" msgid="3111006395818252885">"عنوان"</string>
     <string name="autofill_save_type_credit_card" msgid="3583795235862046693">"بطاقة ائتمان"</string>
@@ -2087,13 +2099,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"قد تنفد طاقة البطارية قبل الشحن المعتاد"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"تم تفعيل \"توفير شحن البطارية\" لإطالة عمرها."</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"توفير شحن البطارية"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"لن يتم تفعيل ميزة \"توفير شحن البطارية\" مرة أخرى حتى تنخفض طاقة البطارية مرة أخرى."</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"وصل شحن البطارية إلى مستوى كافٍ. لن يتم تفعيل ميزة \"توفير شحن البطارية\" حتى تنخفض طاقة البطارية مرة أخرى."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"وصل مستوى شحن الهاتف إلى <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"وصل مستوى شحن الجهاز اللوحي إلى <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"وصل مستوى شحن الجهاز إلى <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"توفير شحن البطارية لا يعمل. لم تعُد الميزات مقيّدة."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"تم تفعيل \"توفير شحن البطارية\". لم تعُد الميزات مقيّدة."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"مجلّد"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"‏تطبيق Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"ملف"</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index 149ac6b..faa99a2 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"আপোনাৰ কেলেণ্ডাৰ ব্যৱহাৰ কৰিব পাৰে"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"এছএমএছ"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"এছএমএছ বার্তা পঠিয়াব আৰু চাব পাৰে"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"সঞ্চয়াগাৰ"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"আপোনাৰ ডিভাইচৰ ফট\', মিডিয়া আৰু ফাইলসমূহ ব্যৱহাৰ কৰিব পাৰে"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"মাইক্ৰ\'ফ\'ন"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"অডিঅ\' ৰেকর্ড কৰিব পাৰে"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"টেপ কৰা, ছোৱাইপ কৰা, পিঞ্চ কৰা আৰু আঙুলিৰ স্পৰ্শেৰে নিৰ্দেশ কৰা অন্যান্য কাৰ্যসমূহ কৰিব পাৰে।"</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"ফিংগাৰপ্ৰিণ্ট নিৰ্দেশ"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"ডিভাইচটোৰ ফিংগাৰপ্ৰিণ্ট ছেন্সৰত দিয়া নিৰ্দেশ বুজিব পাৰে।"</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"স্থিতি দণ্ড অক্ষম কৰক বা সলনি কৰক"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"স্থিতি দণ্ড অক্ষম কৰিবলৈ বা ছিষ্টেম আইকন আঁতৰাবলৈ এপটোক অনুমতি দিয়ে।"</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"স্থিতি দণ্ড হ\'ব পাৰে"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"অনুমোদিত স্তৰতকৈ ওপৰলৈ ভলিউম বঢ়াব নেকি?\n\nদীৰ্ঘ সময়ৰ বাবে উচ্চ ভলিউমত শুনাৰ ফলত শ্ৰৱণ ক্ষমতাৰ ক্ষতি হ\'ব পাৰে।"</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"দিব্যাংগসকলৰ সুবিধাৰ শ্বৰ্টকাট ব্যৱহাৰ কৰেনে?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"শ্বৰ্টকাট অন হৈ থকাৰ সময়ত দুয়োটা ভলিউম বুটামত ৩ ছেকেণ্ডৰ বাবে ছাপ দি থাকিলে দিব্যাংগসকলৰ বাবে থকা সুবিধা এটা আৰম্ভ হ\'ব। \n\n চলিত দিব্যাংগসকলৰ সুবিধা:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n আপুনি এই সুবিধাটো ছেটিংসমূহ &gt; দিব্যাংগসকলৰ বাবে সুবিধা-লৈ গৈ সলনি কৰিব পাৰে।"</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"খালী কৰক"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"শ্বৰ্টকাটসমূহ সম্পাদনা কৰক"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"বাতিল কৰক"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"শ্বৰ্টকাট অফ কৰক"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"শ্বৰ্টকাট ব্যৱহাৰ কৰক"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"ৰং বিপৰীতকৰণ"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"কৰ্মস্থানৰ প্ৰ\'ফাইল অন কৰিবনে?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"আপোনাৰ কৰ্মস্থানৰ এপসমূহ, জাননীসমূহ, ডেটা আৰু কৰ্মস্থানৰ প্ৰ\'ফাইলৰ অইন সুবিধাসমূহ অন কৰা হ\'ব"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"অন কৰক"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"এই এপটো Androidৰ এটা পুৰণা সংস্কৰণৰ বাবে প্ৰস্তুত কৰা হৈছিল, আৰু ই বিচৰাধৰণে কাম নকৰিবও পাৰে। ইয়াৰ আপডে’ট আছে নেকি চাওক, বা বিকাশকৰ্তাৰ সৈতে যোগাযোগ কৰক।"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"আপডে’ট আছে নেকি চাওক"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"আপুনি নতুন বার্তা লাভ কৰিছে"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"চ্চাৰ্জ কৰাৰ সচৰাচৰ সময়ৰ আগতেই বেটাৰি শেষ হ’ব পাৰে"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"বেটাৰিৰ খৰচ কমাবলৈ বেটাৰি সঞ্চয়কাৰী অন কৰা হৈছে"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"বেটাৰি সঞ্চয়কাৰী"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"বেটাৰিৰ চাৰ্জ নকমালৈকে বেটাৰি সঞ্চয়কাৰী পুনৰ সক্ৰিয় নহয়"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"বেটাৰি পৰ্যাপ্ত পৰিমাণে চাৰ্জ হৈছে। বেটাৰিৰ চাৰ্জ নকমালৈকে বেটাৰি সঞ্চয়কাৰী পুনৰ সক্ৰিয় নহয়।"</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"ফ’ন <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> চাৰ্জ হ’ল"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"টেবলেট <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> চাৰ্জ হ’ল"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"ডিভাইচটো <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> চাৰ্জ হ’ল"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"বেটাৰি সঞ্চয়কাৰী অফ আছে। সুবিধাবোৰ ব্যৱহাৰ কৰাত এতিয়া বাধা নাই।"</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"বেটাৰি সঞ্চয়কাৰী অফ কৰা হ’ল। সুবিধাবোৰ ব্যৱহাৰ কৰাত এতিয়া বাধা নাই।"</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"ফ’ল্ডাৰ"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android এপ্লিকেশ্বন"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"ফাইল"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 829b8a0..d97b73f 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"təqvimə daxil olun"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"göndərin və SMS mesajlarına baxın"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Yaddaş"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"cihazınızda foto, media və fayllara daxil olun"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofon"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"səsi qeydə alın"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Digər jestlərə tıklaya, sürüşdürə və əməliyyat apara bilərsiniz."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Barmaq izi işarələri"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Cihazların barmaq izi sensorunda olan işarələri əldə edə bilər."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"status panelini deaktivləşdir və ya dəyişdir"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Tətbiqə status panelini deaktiv etməyə və ya sistem ikonalarını əlavə etmək və ya silmək imkanı verir."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"status paneli edin"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Səsin həcmi tövsiyə olunan səviyyədən artıq olsun?\n\nYüksək səsi uzun zaman dinləmək eşitmə qabiliyyətinizə zərər vura bilər."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Əlçatımlılıq Qısayolu istifadə edilsin?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Qısayol aktiv olduqda hər iki səs düyməsinə 3 saniyə basıb saxlamaqla əlçatımlılıq funksiyası işə başlayacaq.\n\n Cari əlçatımlılıq funksiyası:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Funksiyanı Ayarlar və Əçatımlılıq bölməsində dəyişə bilərsiniz."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Boşaldın"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Qısayolları redaktə edin"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Ləğv edin"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Qısayolu Deaktiv edin"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Qısayol İstifadə edin"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Rəng İnversiyası"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"İş profili aktiv edilsin?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"İş tətbiqləri, bildirişləri, data və digər iş profili funksiyaları aktiv ediləcək"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Aktivləşdirin"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Bu tətbiq köhnə Android versiyası üçün hazırlanıb və düzgün işləməyə bilər. Güncəlləməni yoxlayın və ya developer ilə əlaqə saxlayın."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Güncəlləməni yoxlayın"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Yeni mesajlarınız var"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Batareya həmişəki vaxtdan əvvəl bitə bilər"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Enerjiyə Qənaət rejimi batareya istifadəsinin müddətini artırmaq üçün aktiv edilir"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Enerjiyə qənaət"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Enerjiyə qənaət batareya az olana qədər aktiv edilməyəcək"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Batareya kifayət qədər dolub. Enerjiyə qənaət batareya az olana qədər aktiv edilməyəcək."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Batareya <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> dolub"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Batareya <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> dolub"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Batareya <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> dolub"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Enerjiyə qənaət deaktivdir. Funksiyalar artıq məhdud deyil."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Enerjiyə qənaət deaktivdir. Funksiyalar artıq məhdud deyil."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"Qovluq"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android tətbiqi"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Fayl"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 7909872..c37e2b4 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -290,7 +290,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"pristupi kalendaru"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"šalje i pregleda SMS poruke"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Memorijski prostor"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"Datoteke i mediji"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"pristupa slikama, medijima i datotekama na uređaju"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofon"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"snima zvuk"</string>
@@ -316,6 +316,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Može da dodiruje, lista, skuplja prikaz i obavlja druge pokrete."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Pokreti za otisak prsta"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Može da registruje pokrete na senzoru za otisak prsta na uređaju."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Napravi snimak ekrana"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Može da napravi snimak ekrana."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"onemogućavanje ili izmena statusne trake"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Dozvoljava aplikaciji da onemogući statusnu traku ili da dodaje i uklanja sistemske ikone."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"funkcionisanje kao statusna traka"</string>
@@ -1632,6 +1634,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Želite da pojačate zvuk iznad preporučenog nivoa?\n\nSlušanje glasne muzike duže vreme može da vam ošteti sluh."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Želite li da koristite prečicu za pristupačnost?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Kada je prečica uključena, pritisnite oba dugmeta za jačinu zvuka da biste pokrenuli funkciju pristupačnosti.\n\n Aktuelna funkcija pristupačnosti:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Možete da promenite funkciju u odeljku Podešavanja &gt; Pristupačnost."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Prazno"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Izmenite prečice"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Otkaži"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Isključi prečicu"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Koristi prečicu"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Inverzija boja"</string>
@@ -1877,6 +1882,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Da uključimo profil za Work?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Uključiće se poslovne aplikacije, obaveštenja, podaci i druge funkcije profila za Work"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Uključi"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacija nije dostupna"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> trenutno nije dostupna."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ova aplikacija je napravljena za stariju verziju Android-a, pa možda neće raditi ispravno. Potražite ažuriranja ili kontaktirajte programera."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Potraži ažuriranje"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Imate nove poruke"</string>
@@ -1988,13 +1995,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Baterija će se možda isprazniti pre uobičajenog punjenja"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Ušteda baterije je aktivirana da bi se produžilo trajanje baterije"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Ušteda baterije"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Ušteda baterije se neće ponovo aktivirati dok baterija ne bude skoro prazna"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Baterija je napunjena do zadovoljavajućeg nivoa. Ušteda baterije se neće ponovo aktivirati dok baterija ne bude skoro prazna."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Telefon je napunjen <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Tablet je napunjen <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Uređaj je napunjen <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Ušteda baterije je isključena. Funkcije više nisu ograničene."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Ušteda baterije je isključena. Funkcije više nisu ograničene."</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Ušteda baterije je isključena"</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Baterija telefona je dovoljno napunjena. Funkcije više nisu ograničene."</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"Baterija tableta je dovoljno napunjena. Funkcije više nisu ograničene."</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"Baterija uređaja je dovoljno napunjena. Funkcije više nisu ograničene."</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"Direktorijum"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android aplikacija"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Datoteka"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 306934b..855ce25 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -293,7 +293,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"атрымліваць доступ да вашага календара"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"адпраўляць і праглядаць SMS-паведамленні"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Сховішча"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"атрымліваць доступ да фатаграфій, медыяфайлаў і файлаў на вашай прыладзе"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Мікрафон"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"запісваць аўдыя"</string>
@@ -319,6 +320,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Можна кранаць, праводзіць пальцам, маштабаваць шчыпком, а таксама выконваць іншыя жэсты."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Жэсты на сканеры адбіткаў пальцаў"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Можа распазнаваць жэсты на сканеры адбіткаў пальцаў прылады."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"адключаць ці змяняць радок стану"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Дазваляе прыкладанням адключаць радок стану або дадаваць і выдаляць сістэмныя значкі."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"быць панэллю стану"</string>
@@ -1654,6 +1659,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Павялiчыць гук вышэй рэкамендаванага ўзроўню?\n\nДоўгае праслухоўванне музыкi на вялiкай гучнасцi можа пашкодзiць ваш слых."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Выкарыстоўваць камбінацыю хуткага доступу для спецыяльных магчымасцей?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Калі камбінацыя хуткага доступу ўключана, вы можаце націснуць абедзве кнопкі гучнасці і ўтрымліваць іх 3 секунды, каб уключыць функцыю спецыяльных магчымасцей.\n\n Бягучая функцыя спецыяльных магчымасцей:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Вы можаце змяніць гэту функцыю ў меню \"Налады &gt; Спецыяльныя магчымасці\"."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Пуста"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Змяніць ярлыкі"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Скасаваць"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Дэактываваць камбінацыю хуткага доступу"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Выкарыстоўваць камбінацыю хуткага доступу"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Інверсія колеру"</string>
@@ -1909,6 +1917,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Уключыць працоўны профіль?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Будуць уключаны вашы рабочыя праграмы, апавяшчэнні, даныя і іншыя функцыі працоўнага профілю"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Уключыць"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Гэта праграма была створана для больш старой версіі Android і можа не працаваць належным чынам. Праверце наяўнасць абнаўленняў або звярніцеся да распрацоўшчыка."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Праверыць на наяўнасць абнаўленняў"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"У вас ёсць новыя паведамленні"</string>
@@ -2021,13 +2033,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Акумулятар можа разрадзіцца хутчэй, чым прыйдзе час звычайнай зарадкі"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Каб павялічыць тэрмін работы акумулятара, уключаны рэжым эканоміі зараду"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Эканомія зараду"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Рэжым эканоміі зараду ўключыцца зноў, калі ў акумулятара будзе нізкі зарад"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Акумулятар зараджаны да дастатковага ўзроўню. Рэжым эканоміі зараду ўключыцца зноў, калі ў акумулятара будзе нізкі зарад."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Узровень зараду тэлефона: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Узровень зараду планшэта: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Узровень зараду прылады: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Рэжым эканоміі зараду выключаны. Функцыі больш не абмежаваны."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Рэжым эканоміі зараду выключаны. Функцыі больш не абмежаваны."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"Папка"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Праграма Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Файл"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index dfa4a2f..40e20d3 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -287,7 +287,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"има достъп до календара ви"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"да изпраща и преглежда SMS съобщения"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Хранилище"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"Файлове и мултимедия"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"да има достъп до снимките, мултимедията и файловете на устройството ви"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Микрофон"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"записва звук"</string>
@@ -313,6 +313,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Можете да докосвате, да прекарвате пръст, да събирате пръсти и да извършвате други жестове."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Жестове за отпечатък"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Може да улавя жестовете, извършени върху сензора за отпечатъци на устройството."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Създаване на екранна снимка"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Може да създава екранни снимки."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"деактивиране или промяна на лентата на състоянието"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Разрешава на приложението да деактивира лентата на състоянието или да добавя и премахва системни икони."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"изпълняване на ролята на лента на състоянието"</string>
@@ -1610,6 +1612,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Да се увеличи ли силата на звука над препоръчителното ниво?\n\nПродължителното слушане при висока сила на звука може да увреди слуха ви."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Искате ли да използвате пряк път към функцията за достъпност?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Когато прекият път е включен, можете да стартирате дадена функция за достъпност, като натиснете двата бутона за промяна на силата на звука и ги задържите 3 секунди.\n\n Текущата функция за достъпност е:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Можете да промените функцията от „Настройки“ &gt; „Достъпност“."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Празно"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Редактиране на преките пътища"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Отказ"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Изключване на прекия път"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Използване на пряк път"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Инвертиране на цветовете"</string>
@@ -1845,6 +1850,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Вкл. на служ. потр. профил?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Служебните ви приложения, известия и данни, както и другите функции на служебния потребителски профил ще бъдат включени"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Включване"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Приложението не е достъпно"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"В момента няма достъп до <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Това приложение бе създадено за по-стара версия на Android и може да не работи правилно. Опитайте да проверите за актуализации или се свържете с програмиста."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Проверка за актуализация"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Имате нови съобщения"</string>
@@ -1955,13 +1962,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Батерията може да се изтощи преди обичайното зареждане"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Режимът за запазване на батерията е активиран с цел удължаване на живота на батерията"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Режим за запазване на батерията"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Режимът за запазване на батерията няма да се активира отново, докато тя пак не се изтощи"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Батерията е достатъчно заредена. Режимът за запазването й няма да се активира отново, докато тя пак не се изтощи."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Телефонът е зареден на <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Таблетът е зареден на <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Устройството е заредено на <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Режимът за запазване на батерията е изключен. Функциите вече не са ограничени."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Режимът за запазване на батерията е изключен. Функциите вече не са ограничени."</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Режимът за запазване на батерията е изключен"</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Батерията на телефона има достатъчно заряд. Функциите вече не са ограничени."</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"Батерията на таблета има достатъчно заряд. Функциите вече не са ограничени."</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"Батерията на устройството има достатъчно заряд. Функциите вече не са ограничени."</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"Папка"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Приложение за Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Файл"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index 150fc7d..3164bb6 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"আপনার ক্যালেন্ডারে অ্যাক্সেস"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"এসএমএসগুলি পাঠাতে এবং দেখতে"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"স্টোরেজ"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"আপনার ডিভাইসে ফটো, মিডিয়া এবং ফাইলগুলিতে অ্যাক্সেস"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"মাইক্রোফোন"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"অডিও রেকর্ড"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"আলতো চাপ দেওয়া, সোয়াইপ, পিঞ্চ করা এবং অন্যান্য ইঙ্গিতের কাজগুলি সম্পাদন করতে পারবেন৷"</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"আঙ্গুলের ছাপ সেন্সরের উপর করা জেসচার"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"ডিভাইসের আঙ্গুলের ছাপের সেন্সরের উপরে ইঙ্গিত করলে বুঝতে পারে।"</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"স্ট্যাটাস বার নিষ্ক্রিয় অথবা সংশোধন করে"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"অ্যাপ্লিকেশনকে স্ট্যাটাস বার অক্ষম করতে এবং সিস্টেম আইকনগুলি সরাতে দেয়৷"</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"স্থিতি দন্ডে থাকুন"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"প্রস্তাবিত স্তরের চেয়ে বেশি উঁচুতে ভলিউম বাড়াবেন?\n\nউঁচু ভলিউমে বেশি সময় ধরে কিছু শুনলে আপনার শ্রবনশক্তির ক্ষতি হতে পারে।"</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"অ্যাক্সেসযোগ্যতা শর্টকাট ব্যবহার করবেন?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"শর্টকাটটি চালু থাকলে দুটি ভলিউম বোতাম একসাথে ৩ সেকেন্ড টিপে ধরে রাখলে একটি অ্যাকসেসিবিলিটি বৈশিষ্ট্য চালু হবে।\n\n বর্তমান অ্যাকসেসিবিলিটি বৈশিষ্ট্য:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n আপনি এই বৈশিষ্ট্যটি সেটিংস &gt; অ্যাকসেসিবিলিটিতে গিয়ে পরিবর্তন করতে পারবেন।"</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"খালি"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"শর্টকাট এডিট করুন"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"বাতিল করুন"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"শর্টকাট বন্ধ করুন"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"শর্টকাট ব্যবহার করুন"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"রঙ উল্টানো"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"কাজের প্রোফাইল চালু করবেন?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"আপনার কাজের অ্যাপ, বিজ্ঞপ্তি, ডেটা এবং কাজের প্রোফাইলের অন্যান্য বৈশিষ্ট্য চালু করা হবে"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"চালু করুন"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"এই অ্যাপটি Android এর একটি পুরনো ভার্সনের জন্য তৈরি করা হয়েছিল, তাই এখানে সেটি ঠিকমতো কাজ নাও করতে পারে। আপডেট পাওয়া যাচ্ছে কিনা দেখুন বা ডেভেলপারের সাথে যোগাযোগ করুন।"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"আপডেট পাওয়া যাচ্ছে কিনা দেখুন"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"আপনার নতুন মেসেজ আছে"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"সাধারণত যখন চার্জ দেন, তার আগে চার্জ শেষ হয়ে যেতে পারে"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"ডিভাইস বেশিক্ষণ চালু রাখতে ব্যাটারি সেভার চালু করা হয়েছে"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"ব্যাটারি সেভার"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"চার্জ কম না হওয়া পর্যন্ত ব্যাটারি সেভার আবার চালু হবে না"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"ব্যাটারি পর্যাপ্ত পরিমাণ চার্জ করা হয়েছে। চার্জ কম না হওয়া পর্যন্ত ব্যাটারি সেভার আবার চালু হবে না।"</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"ফোনে <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> চার্জ আছে"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"ট্যাবলেটে <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> চার্জ আছে"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"ডিভাইসে <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> চার্জ আছে"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"ব্যাটারি সেভার বন্ধ আছে। ফিচারগুলি আর সীমাবদ্ধ নেই।"</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"ব্যাটারি সেভার বন্ধ করে দেওয়া হয়েছে। ফিচারগুলি আর সীমাবদ্ধ নেই।"</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"ফোল্ডার"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android অ্যাপ্লিকেশন"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"ফাইল"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 3660824..e0a4886 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -290,7 +290,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"pristupa vašem kalendaru"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"šalje i pregleda SMS poruke"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Pohrana"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"Fajlovi i mediji"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"pristupa slikama, medijskim fajlovima i fajlovima na vašem uređaju"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofon"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"snima zvuk"</string>
@@ -316,6 +316,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Može dodirivati, prevlačiti, hvatati prstima i praviti druge pokrete."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Pokreti otiska prsta"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Moguće je zabilježiti pokrete na senzoru za otisak prsta uređaja."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Snimanje ekrana"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Moguće je snimiti ekran."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"onemogućavanje ili mijenjanje statusne trake"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Dozvoljava aplikaciji onemogućavanje statusne trake ili dodavanje i uklanjanje sistemskih ikona."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"funkcioniranje u vidu statusne trake"</string>
@@ -1634,6 +1636,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Želite li pojačati zvuk iznad preporučenog nivoa?\n\nDužim slušanjem glasnog zvuka možete oštetiti sluh."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Želite li koristiti Prečicu za pristupačnost?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Kada je prečica uključena, pritiskom na oba dugmeta za podešavanje jačine zvuka u trajanju od 3 sekunde pokrenut će se funkcija za pristupačnost.\n\n Trenutna funkcija za pristupačnost je:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Funkciju možete promijeniti ako odete u Postavke &gt; Pristupačnost."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Prazno"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Uredi prečice"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Otkaži"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Isključi prečicu"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Koristi prečicu"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Inverzija boja"</string>
@@ -1879,6 +1884,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Uključiti radni profil?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Uključit će se poslovne aplikacije, obavještenja, podaci i druge funkcije radnog profila"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Uključi"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacija nije dostupna"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> trenutno nije dostupna."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ova aplikacija je pravljena za stariju verziju Androida i možda neće ispravno raditi. Provjerite jesu li dostupna ažuriranja ili kontaktirajte programera."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Provjeri je li dostupno ažuriranje"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Imate nove poruke"</string>
@@ -1990,13 +1997,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Moguće je da će se baterija isprazniti prije uobičajenog punjenja"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Ušteda baterije je aktivirana da bi se produžio vijek trajanja baterije"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Ušteda baterije"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Ušteda baterije se neće ponovo aktivirati dok se baterija ponovo ne isprazni"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Baterija je dovoljno napunjena. Ušteda baterije se neće ponovo aktivirati dok se baterija ponovo ne isprazni."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Telefon je napunjen <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Tablet je napunjen <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Uređaj je napunjen <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Ušteda baterije je isključena. Funkcije nisu više ograničene."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Ušteda baterije je isključena. Funkcije nisu više ograničene."</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Ušteda baterije je isključena"</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefon se dovoljno napunio. Funkcije nisu više ograničene."</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"Tablet se dovoljno napunio. Funkcije nisu više ograničene."</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"Uređaj se dovoljno napunio. Funkcije nisu više ograničene."</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"Folder"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android aplikacija"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Fajl"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index b265f66..93a60d8 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -287,7 +287,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"accedir al calendari"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"enviar i llegir missatges SMS"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Emmagatzematge"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"Fitxers i multimèdia"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"accedir a fotos, contingut multimèdia i fitxers del dispositiu"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Micròfon"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"gravar àudio"</string>
@@ -313,6 +313,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Permet tocar, lliscar, pinçar i fer altres gestos."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Gestos d\'empremtes dactilars"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Captura gestos realitzats en el sensor d\'empremtes dactilars del dispositiu."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Fes una captura de pantalla"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Pots fer una captura de la pantalla."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"desactivar o modificar la barra d\'estat"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Permet que l\'aplicació desactivi la barra d\'estat o afegeixi i elimini icones del sistema."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"aparèixer a la barra d\'estat"</string>
@@ -1610,6 +1612,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Vols apujar el volum per sobre del nivell recomanat?\n\nSi escoltes música a un volum alt durant períodes llargs, pots danyar-te l\'oïda."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Vols fer servir la drecera d\'accessibilitat?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Si la drecera està activada, prem els dos botons de volum durant 3 segons, per iniciar una funció d\'accessibilitat.\n\n Funció d\'accessibilitat actual:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Pots canviar la funció a Configuració &gt; Accessibilitat."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Buida"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Edita les dreceres"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Cancel·la"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Desactiva la drecera"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Utilitza la drecera"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Inversió dels colors"</string>
@@ -1845,6 +1850,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Activar el perfil professional?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"S\'activaran les teves aplicacions per a la feina, les notificacions, les dades i altres funcions del perfil professional"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Activa"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"L\'aplicació no està disponible"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"Ara mateix, <xliff:g id="APP_NAME">%1$s</xliff:g> no està disponible."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Aquesta aplicació es va crear per a una versió antiga d\'Android i pot ser que no funcioni correctament. Prova de cercar actualitzacions o contacta amb el desenvolupador."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Cerca actualitzacions"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Tens missatges nous"</string>
@@ -1955,13 +1962,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"És possible que la bateria s\'esgoti abans de la càrrega habitual"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"S\'ha activat l\'estalvi de bateria per prolongar-ne la durada"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Estalvi de bateria"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"L\'estalvi de bateria no es tornarà a activar fins que no tornis a tenir poca bateria"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"La bateria ja està suficientment carregada. L\'estalvi de bateria no es tornarà a activar fins que no tornis a tenir poca bateria."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"El telèfon està carregat un <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Tauleta carregada un <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"El dispositiu està carregat un <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"L\'estalvi de bateria s\'ha desactivat. Les funcions ja no estan restringides."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"L\'estalvi de bateria s\'ha desactivat. Les funcions ja no estan restringides."</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"L\'estalvi de bateria s\'ha desactivat"</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"El telèfon té prou bateria. Les funcions ja no estan restringides."</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"La tauleta té prou bateria. Les funcions ja no estan restringides."</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"El dispositiu té prou bateria. Les funcions ja no estan restringides."</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"Carpeta"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Aplicació d\'Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Fitxer"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 806cb7e..81b1ebb 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -293,7 +293,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"přístup ke kalendáři"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"odesílání a zobrazování zpráv SMS"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Úložiště"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"Soubory a média"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"přístup k fotkám, médiím a souborům v zařízení"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofon"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"nahrávání zvuku"</string>
@@ -319,6 +319,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Může provádět gesta klepnutí, přejetí, stažení prstů a další."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Gesta otiskem prstu"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Dokáže rozpoznat gesta zadaná na snímači otisků prstů."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Pořídit snímek obrazovky"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Může pořídit snímek obrazovky."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"zakázání či změny stavového řádku"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Umožňuje aplikaci zakázat stavový řádek nebo přidat či odebrat systémové ikony."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"vydávání se za stavový řádek"</string>
@@ -1654,6 +1656,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Zvýšit hlasitost nad doporučenou úroveň?\n\nDlouhodobý poslech hlasitého zvuku může poškodit sluch."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Použít zkratku přístupnosti?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Když je tato zkratka zapnutá, můžete funkci přístupnosti spustit tím, že na tři sekundy podržíte obě tlačítka hlasitosti.\n\n Aktuální funkce přístupnosti:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Funkci můžete změnit v Nastavení &gt; Přístupnost."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Prázdné"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Upravit zkratky"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Zrušit"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Vypnout zkratku"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Použít zkratku"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Převrácení barev"</string>
@@ -1909,6 +1914,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Zapnout pracovní profil?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Vaše pracovní aplikace, oznámení, data a ostatní funkce pracovního účtu budou zapnuty"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Zapnout"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Aplikace není k dispozici"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> v tuto chvíli není k dispozici."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Tato aplikace byla vytvořena pro starší verzi systému Android a nemusí fungovat správně. Zkuste vyhledat aktualizace, případně kontaktujte vývojáře."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Zkontrolovat aktualizace"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Máte nové zprávy"</string>
@@ -2021,13 +2028,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Baterie se možná vybije před obvyklým časem nabití"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Byl aktivován spořič baterie za účelem prodloužení výdrže"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Spořič baterie"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Spořič baterie se znovu aktivuje, až bude stav baterie opět nízký"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Baterie byla nabita na dostatečnou úroveň. Spořič baterie se znovu aktivuje, až bude baterie opět téměř vybitá."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Telefon je nabitý na <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Tablet je nabitý na <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Zařízení je nabito na <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Spořič baterie je vypnutý. Funkce už nejsou omezeny."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Spořič baterie je vypnutý. Funkce už nejsou omezeny."</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Spořič baterie je vypnutý"</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefon je dostatečně nabitý. Funkce už nejsou omezeny."</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"Tablet je dostatečně nabitý. Funkce už nejsou omezeny."</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"Zařízení je dostatečně nabité. Funkce už nejsou omezeny."</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"Složka"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Aplikace pro Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Soubor"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 81449ad..4374b22 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"have adgang til din kalender"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"Sms"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"sende og se sms-beskeder"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Lagerplads"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"få adgang til billeder, medier og filer på din enhed"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofon"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"optage lyd"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Kan trykke, stryge, knibe sammen og udføre andre bevægelser."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Fingeraftryksbevægelser"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Kan registrere bevægelser, der foretages på enhedens fingeraftrykslæser."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"deaktivere eller redigere statuslinje"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Tillader, at appen kan deaktivere statusbjælken eller tilføje og fjerne systemikoner."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"vær statusbjælken"</string>
@@ -358,7 +363,7 @@
     <string name="permlab_systemAlertWindow" msgid="5757218350944719065">"Denne app kan vises oven på andre apps"</string>
     <string name="permdesc_systemAlertWindow" msgid="1145660714855738308">"Denne app kan vises oven på andre apps eller andre dele af skærmen. Dette kan forstyrre den normale brug af appen og ændre visningen af andre apps."</string>
     <string name="permlab_runInBackground" msgid="541863968571682785">"kør i baggrunden"</string>
-    <string name="permdesc_runInBackground" msgid="4344539472115495141">"Denne app kan køre i baggrunden. Dette kan aflade batteriet hurtigere."</string>
+    <string name="permdesc_runInBackground" msgid="4344539472115495141">"Denne app kan køre i baggrunden. Dette kan dræne batteriet hurtigere."</string>
     <string name="permlab_useDataInBackground" msgid="783415807623038947">"brug data i baggrunden"</string>
     <string name="permdesc_useDataInBackground" msgid="1230753883865891987">"Denne app kan bruge data i baggrunden. Dette kan øge dataforbruget."</string>
     <string name="permlab_persistentActivity" msgid="464970041740567970">"sørge for, at appen altid kører"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Vil du skrue højere op end det anbefalede lydstyrkeniveau?\n\nDu kan skade hørelsen ved at lytte til meget høj musik over længere tid."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Vil du bruge genvejen til Hjælpefunktioner?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Når genvejen er slået til, kan du starte en hjælpefunktion ved at trykke på begge lydstyrkeknapper i tre sekunder.\n\n Nuværende hjælpefunktion:\n<xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Du kan skifte funktion i Indstillinger &gt; Hjælpefunktioner."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Tom"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Rediger genveje"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Annuller"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Deaktiver genvej"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Brug genvej"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Ombytning af farver"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Skal arbejdsprofilen slås til?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Dine arbejdsapps, notifikationer, data og andre funktioner til din arbejdsprofil deaktiveres"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Slå til"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Denne app er lavet til en ældre version af Android og fungerer muligvis ikke korrekt. Prøv at søge efter opdateringer, eller kontakt udvikleren."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Søg efter opdatering"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Du har nye beskeder"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Enheden løber muligvis tør for batteri, inden du normalt oplader den"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Batterisparefunktion er aktiveret for at forlænge batteritiden"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Batterisparefunktion"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Batterisparefunktion genaktiveres ikke, før batteriet er lavt igen"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Batteriet er tilstrækkeligt opladet. Batterisparefunktion genaktiveres ikke, før batteriet er lavt igen."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Telefonen er <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> opladet"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Denne tablet er <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> opladet"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Enheden er <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> opladet"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Batterisparefunktion er slået fra. Funktionerne er ikke længere begrænsede."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Batterisparefunktion er slået fra. Funktionerne er ikke længere begrænsede."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"Mappe"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android-app"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Fil"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 857cdbd..7804e9e 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"auf deinen Kalender zugreifen"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS senden und abrufen"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Speicher"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"auf Fotos, Medien und Dateien auf deinem Gerät zugreifen"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofon"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"Audio aufnehmen"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Tippen, Wischen, Zusammenziehen und andere Touch-Gesten möglich."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Fingerabdrucksensor-Gesten"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Erfasst Touch-Gesten auf dem Fingerabdrucksensor des Geräts."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"Statusleiste deaktivieren oder ändern"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Ermöglicht der App, die Statusleiste zu deaktivieren oder Systemsymbole hinzuzufügen oder zu entfernen"</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"Statusleiste darstellen"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Lautstärke über den Schwellenwert anheben?\n\nWenn du über einen längeren Zeitraum Musik in hoher Lautstärke hörst, kann dies dein Gehör schädigen."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Verknüpfung für Bedienungshilfen verwenden?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Wenn die Verknüpfung aktiviert ist, kannst du die beiden Lautstärketasten drei Sekunden lang gedrückt halten, um eine Bedienungshilfe zu starten.\n\n Aktuelle Bedienungshilfe:\n<xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Du kannst die Bedienungshilfe unter \"Einstellungen\" &gt; \"Bedienungshilfen\" ändern."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Leeren"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Verknüpfungen bearbeiten"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Abbrechen"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Verknüpfung deaktivieren"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Verknüpfung verwenden"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Farbumkehr"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Arbeitsprofil aktivieren?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Deine geschäftlichen Apps, Benachrichtigungen, Daten und andere Funktionen des Arbeitsprofils werden aktiviert"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Aktivieren"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Diese App wurde für eine ältere Android-Version entwickelt und funktioniert möglicherweise nicht mehr richtig. Prüfe, ob Updates verfügbar sind oder kontaktiere den Entwickler."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Auf Updates prüfen"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Du hast neue Nachrichten"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Dein Akku könnte vor der gewöhnlichen Ladezeit leer sein"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Energiesparmodus aktiviert, um die Akkulaufzeit zu verlängern"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Energiesparmodus"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Energiesparmodus wird erst bei niedrigem Ladestand wieder aktiviert"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Der Akku ist jetzt ausreichend aufgeladen. Der Energiesparmodus wird erst wieder bei schwachem Ladestand aktiviert."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Ladestand deines Smartphones: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Ladestand Tablet: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Ladestand des Geräts: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Energiesparmodus ist deaktiviert. Es sind keine Funktionen mehr beschränkt."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Energiesparmodus deaktiviert. Es sind keine Funktionen mehr beschränkt."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"Ordner"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android-App"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Datei"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 817df16b..a5977ee 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -287,7 +287,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"έχει πρόσβαση στο ημερολόγιό σας"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"στέλνει και να διαβάζει μηνύματα SMS"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Αποθηκευτικός χώρος"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"Αρχεία και μέσα"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"έχει πρόσβαση στις φωτογραφίες/πολυμέσα/αρχεία στη συσκευή σας"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Μικρόφωνο"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"ηχογραφεί"</string>
@@ -313,6 +313,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Επιτρέπει το πάτημα, την ολίσθηση, το πλησίασμα και άλλες κινήσεις."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Κινήσεις δακτυλικών αποτυπωμάτων"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Μπορεί να αναγνωρίσει κινήσεις που εκτελούνται στον αισθητήρα δακτυλικού αποτυπώματος της συσκευής."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Λήψη στιγμιότυπου οθόνης"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Μπορεί να τραβήξει στιγμιότυπο της οθόνης."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"απενεργοποιεί ή να τροποποιεί την γραμμή κατάστασης"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Επιτρέπει στην εφαρμογή να απενεργοποιεί τη γραμμή κατάστασης ή να προσθέτει και να αφαιρεί εικονίδια συστήματος."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"ορίζεται ως γραμμή κατάστασης"</string>
@@ -1610,6 +1612,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Αυξάνετε την ένταση ήχου πάνω από το επίπεδο ασφαλείας;\n\nΑν ακούτε μουσική σε υψηλή ένταση για μεγάλο χρονικό διάστημα ενδέχεται να προκληθεί βλάβη στην ακοή σας."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Να χρησιμοποιείται η συντόμευση προσβασιμότητας;"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Όταν η συντόμευση είναι ενεργοποιημένη, το πάτημα και των δύο κουμπιών έντασης ήχου για 3 δευτερόλεπτα θα ξεκινήσει μια λειτουργία προσβασιμότητας.\n\n Τρέχουσα λειτουργία προσβασιμότητας:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Μπορείτε να αλλάξετε τη λειτουργία από τις Ρυθμίσεις &gt; Προσβασιμότητα."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Κενό"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Επεξεργασία συντομεύσεων"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Άκυρο"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Απενεργοποίηση συντόμευσης"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Χρήση συντόμευσης"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Αντιστροφή χρωμάτων"</string>
@@ -1845,6 +1850,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Ενεργοποίηση προφίλ εργασίας;"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Οι εφαρμογές, οι ειδοποιήσεις και τα δεδομένα εργασίας σας, καθώς και άλλες λειτουργίες του προφίλ εργασίας, θα ενεργοποιηθούν"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Ενεργοποίηση"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Η εφαρμογή δεν είναι διαθέσιμη"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> δεν είναι διαθέσιμη αυτήν τη στιγμή."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Αυτή η εφαρμογή δημιουργήθηκε για παλαιότερη έκδοση του Android και μπορεί να μην λειτουργεί σωστά. Δοκιμάστε να ελέγξετε εάν υπάρχουν ενημερώσεις ή επικοινωνήστε με τον προγραμματιστή."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Έλεγχος για ενημέρωση"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Έχετε νέα μηνύματα"</string>
@@ -1955,13 +1962,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Η μπαταρία μπορεί να εξαντληθεί πριν από τη συνηθισμένη φόρτιση"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Η Εξοικονόμηση μπαταρίας ενεργοποιήθηκε για την επέκταση της διάρκειας ζωής της μπαταρίας"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Εξοικονόμηση μπαταρίας"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Η Εξοικονόμηση μπαταρίας δεν θα ενεργοποιηθεί ξανά προτού η στάθμη της μπαταρίας είναι ξανά χαμηλή"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Η μπαταρία φορτίστηκε σε επαρκές επίπεδο. Η Εξοικονόμηση μπαταρίας δεν θα ενεργοποιηθεί ξανά προτού η στάθμη της μπαταρίας είναι ξανά χαμηλή."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Το τηλέφωνο <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> φορτίστηκε"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Επίπεδο φόρτισης tablet <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Η συσκευή <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> φορτίστηκε"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Η Εξοικονόμηση μπαταρίας είναι ανενεργή. Οι λειτουργίες δεν περιορίζονται πλέον."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Η Εξοικονόμηση μπαταρίας απενεργοποιήθηκε. Οι λειτουργίες δεν περιορίζονται πλέον."</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Η Εξοικονόμηση μπαταρίας απενεργοποιήθηκε"</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Το τηλέφωνο είναι αρκετά φορτισμένο. Οι λειτουργίες δεν περιορίζονται πλέον."</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"Το tablet είναι αρκετά φορτισμένο. Οι λειτουργίες δεν περιορίζονται πλέον."</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"Η συσκευή είναι αρκετά φορτισμένη. Οι λειτουργίες δεν περιορίζονται πλέον."</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"Φάκελος"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Εφαρμογή Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Αρχείο"</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 33a8488..da89793 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -287,7 +287,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"access your calendar"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"send and view SMS messages"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Storage"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"Files and media"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"access photos, media and files on your device"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Microphone"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"record audio"</string>
@@ -313,6 +313,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Can tap, swipe, pinch and perform other gestures."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Fingerprint gestures"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Can capture gestures performed on the device\'s fingerprint sensor."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Take screenshot"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Can take a screenshot of the display."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"disable or modify status bar"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Allows the app to disable the status bar or add and remove system icons."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"be the status bar"</string>
@@ -1610,6 +1612,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Raise volume above recommended level?\n\nListening at high volume for long periods may damage your hearing."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Use Accessibility Shortcut?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature.\n\n Current accessibility feature:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n You can change the feature in Settings &gt; Accessibility."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Empty"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Edit shortcuts"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Cancel"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Turn off Shortcut"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Use Shortcut"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Colour Inversion"</string>
@@ -1845,6 +1850,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Turn on work profile?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Your work apps, notifications, data and other work profile features will be turned on"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Turn on"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"App is not available"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not available right now."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"This app was built for an older version of Android and may not work properly. Try checking for updates or contact the developer."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Check for update"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"You have new messages"</string>
@@ -1955,13 +1962,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Battery may run out before usual charge"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Battery Saver activated to extend battery life"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Battery Saver"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Battery Saver won’t reactivate until battery low again"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Battery has been charged to a sufficient level. Battery Saver won’t reactivate until the battery is low again."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Phone <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> charged"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Tablet <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> charged"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Device <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> charged"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Battery Saver is off. Features no longer restricted."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Battery Saver turned off. Features no longer restricted."</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Battery Saver turned off"</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Phone has enough charge. Features no longer restricted."</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"Tablet has enough charge. Features no longer restricted."</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"Device has enough charge. Features no longer restricted."</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"Folder"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android application"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"File"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index b2ba419..560c4cd 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -287,7 +287,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"access your calendar"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"send and view SMS messages"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Storage"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"Files and media"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"access photos, media and files on your device"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Microphone"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"record audio"</string>
@@ -313,6 +313,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Can tap, swipe, pinch and perform other gestures."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Fingerprint gestures"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Can capture gestures performed on the device\'s fingerprint sensor."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Take screenshot"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Can take a screenshot of the display."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"disable or modify status bar"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Allows the app to disable the status bar or add and remove system icons."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"be the status bar"</string>
@@ -1610,6 +1612,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Raise volume above recommended level?\n\nListening at high volume for long periods may damage your hearing."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Use Accessibility Shortcut?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature.\n\n Current accessibility feature:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n You can change the feature in Settings &gt; Accessibility."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Empty"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Edit shortcuts"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Cancel"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Turn off Shortcut"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Use Shortcut"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Colour Inversion"</string>
@@ -1845,6 +1850,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Turn on work profile?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Your work apps, notifications, data and other work profile features will be turned on"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Turn on"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"App is not available"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not available right now."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"This app was built for an older version of Android and may not work properly. Try checking for updates or contact the developer."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Check for update"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"You have new messages"</string>
@@ -1955,13 +1962,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Battery may run out before usual charge"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Battery Saver activated to extend battery life"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Battery Saver"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Battery Saver won’t reactivate until battery low again"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Battery has been charged to a sufficient level. Battery Saver won’t reactivate until the battery is low again."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Phone <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> charged"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Tablet <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> charged"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Device <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> charged"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Battery Saver is off. Features no longer restricted."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Battery Saver turned off. Features no longer restricted."</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Battery Saver turned off"</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Phone has enough charge. Features no longer restricted."</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"Tablet has enough charge. Features no longer restricted."</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"Device has enough charge. Features no longer restricted."</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"Folder"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android application"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"File"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 33a8488..da89793 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -287,7 +287,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"access your calendar"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"send and view SMS messages"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Storage"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"Files and media"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"access photos, media and files on your device"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Microphone"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"record audio"</string>
@@ -313,6 +313,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Can tap, swipe, pinch and perform other gestures."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Fingerprint gestures"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Can capture gestures performed on the device\'s fingerprint sensor."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Take screenshot"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Can take a screenshot of the display."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"disable or modify status bar"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Allows the app to disable the status bar or add and remove system icons."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"be the status bar"</string>
@@ -1610,6 +1612,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Raise volume above recommended level?\n\nListening at high volume for long periods may damage your hearing."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Use Accessibility Shortcut?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature.\n\n Current accessibility feature:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n You can change the feature in Settings &gt; Accessibility."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Empty"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Edit shortcuts"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Cancel"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Turn off Shortcut"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Use Shortcut"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Colour Inversion"</string>
@@ -1845,6 +1850,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Turn on work profile?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Your work apps, notifications, data and other work profile features will be turned on"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Turn on"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"App is not available"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not available right now."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"This app was built for an older version of Android and may not work properly. Try checking for updates or contact the developer."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Check for update"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"You have new messages"</string>
@@ -1955,13 +1962,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Battery may run out before usual charge"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Battery Saver activated to extend battery life"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Battery Saver"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Battery Saver won’t reactivate until battery low again"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Battery has been charged to a sufficient level. Battery Saver won’t reactivate until the battery is low again."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Phone <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> charged"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Tablet <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> charged"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Device <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> charged"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Battery Saver is off. Features no longer restricted."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Battery Saver turned off. Features no longer restricted."</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Battery Saver turned off"</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Phone has enough charge. Features no longer restricted."</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"Tablet has enough charge. Features no longer restricted."</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"Device has enough charge. Features no longer restricted."</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"Folder"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android application"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"File"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 33a8488..da89793 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -287,7 +287,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"access your calendar"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"send and view SMS messages"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Storage"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"Files and media"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"access photos, media and files on your device"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Microphone"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"record audio"</string>
@@ -313,6 +313,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Can tap, swipe, pinch and perform other gestures."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Fingerprint gestures"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Can capture gestures performed on the device\'s fingerprint sensor."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Take screenshot"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Can take a screenshot of the display."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"disable or modify status bar"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Allows the app to disable the status bar or add and remove system icons."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"be the status bar"</string>
@@ -1610,6 +1612,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Raise volume above recommended level?\n\nListening at high volume for long periods may damage your hearing."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Use Accessibility Shortcut?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature.\n\n Current accessibility feature:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n You can change the feature in Settings &gt; Accessibility."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Empty"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Edit shortcuts"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Cancel"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Turn off Shortcut"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Use Shortcut"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Colour Inversion"</string>
@@ -1845,6 +1850,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Turn on work profile?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Your work apps, notifications, data and other work profile features will be turned on"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Turn on"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"App is not available"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not available right now."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"This app was built for an older version of Android and may not work properly. Try checking for updates or contact the developer."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Check for update"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"You have new messages"</string>
@@ -1955,13 +1962,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Battery may run out before usual charge"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Battery Saver activated to extend battery life"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Battery Saver"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Battery Saver won’t reactivate until battery low again"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Battery has been charged to a sufficient level. Battery Saver won’t reactivate until the battery is low again."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Phone <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> charged"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Tablet <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> charged"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Device <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> charged"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Battery Saver is off. Features no longer restricted."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Battery Saver turned off. Features no longer restricted."</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Battery Saver turned off"</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Phone has enough charge. Features no longer restricted."</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"Tablet has enough charge. Features no longer restricted."</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"Device has enough charge. Features no longer restricted."</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"Folder"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android application"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"File"</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index e256605..f7da05e 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -287,7 +287,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‏‎‏‏‎‏‎‎‎‎‏‏‏‏‎‎‏‎‎‎‎‏‎‏‏‏‏‎‏‎‎‎‎‎‎‏‏‏‎‏‎‎‎‏‎‎‏‏‏‏‎‎‎‏‏‎access your calendar‎‏‎‎‏‎"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‎‏‎‏‏‎‎‎‎‎‏‏‏‎‏‎‎‎‎‎‏‎‎‎‎‏‏‎‎‏‎‎‏‎‎‎‎‎‏‏‎‏‏‎‏‎‎‎‎‏‎‏‎‎SMS‎‏‎‎‏‎"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‏‏‏‏‎‎‎‎‏‏‏‏‎‏‏‎‎‏‏‎‏‏‎‎‎‎‎‎‎‎‎‏‎‎‏‏‎‎‏‎‎‎‎‎‏‎‏‏‎‎‏‏‏‏‎‎send and view SMS messages‎‏‎‎‏‎"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‏‎‎‏‎‎‎‏‎‎‎‎‏‏‏‏‏‏‏‎‏‎‎‏‎‏‏‎‏‎‏‎‏‏‎‎‎‎‎‎‏‏‏‎‎‏‏‏‏‎‎‎‎‎‎‏‎Storage‎‏‎‎‏‎"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‏‏‏‎‎‏‏‎‏‎‏‎‎‎‏‏‎‎‏‏‎‏‎‎‎‏‏‏‎‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‎‎‎‎‏‎‎‏‏‏‎‏‎Files and media‎‏‎‎‏‎"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‎‏‎‏‎‎‎‏‎‎‏‏‎‎‎‎‎‎‎‎‎‎‎‏‏‎‎‎‏‏‎‎‏‏‎‏‏‏‏‎‎‎‏‏‎‎‏‎‏‎‎‎‎access photos, media, and files on your device‎‏‎‎‏‎"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‎‎‏‏‎‏‏‎‎‏‏‎‏‏‎‏‎‎‎‏‏‎‏‏‏‏‎‏‏‏‎‏‎‏‏‎‏‏‏‎‏‎‏‎‎‎‎‏‏‏‎‏‎‏‏‎‎‎Microphone‎‏‎‎‏‎"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‎‎‏‎‏‎‎‏‏‏‏‏‎‎‎‏‏‏‏‎‏‏‎‏‏‎‏‎‎‎‎‏‏‏‎‏‏‎‏‎‎‏‏‏‏‏‎‎‏‎‏‎‏‎‎record audio‎‏‎‎‏‎"</string>
@@ -313,6 +313,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‏‏‏‎‏‎‎‎‎‏‎‎‏‎‏‎‎‏‏‎‎‎‏‏‎‎‏‏‏‏‏‎‎‏‎‎‏‏‎‏‎‎‏‎‎‎‏‏‏‏‏‏‎‎Can tap, swipe, pinch, and perform other gestures.‎‏‎‎‏‎"</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‏‎‎‎‎‎‎‎‎‏‎‏‏‏‎‏‎‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‎‏‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‏‎‏‏‎Fingerprint gestures‎‏‎‎‏‎"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‎‏‏‏‎‏‎‎‏‎‎‎‎‎‏‏‏‎‎‏‏‏‎‏‎‎‎‎‎‏‎‎‏‎‎‏‏‎‎‎‎‎‎‏‎‎‎‎‎‎‏‏‎‏‎‎Can capture gestures performed on the device\'s fingerprint sensor.‎‏‎‎‏‎"</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‎‎‎‏‎‎‎‎‏‎‏‏‎‏‎‏‎‎‏‎‏‏‏‏‎‏‏‏‎‏‏‎‎‏‏‎‎‏‏‏‏‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‎Take screenshot‎‏‎‎‏‎"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‏‏‎‎‏‎‎‏‏‏‎‎‎‎‏‎‏‎‏‎‏‏‏‏‎‎‏‎‏‎‎‏‎‏‎‏‎‏‏‏‏‎‏‎‏‏‏‏‎‎‏‎‎‎Can take a screenshot of the display.‎‏‎‎‏‎"</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‏‎‎‏‏‎‏‏‏‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‎‎‏‎disable or modify status bar‎‏‎‎‏‎"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‎‏‏‏‏‎‎‏‎‎‏‎‏‎‏‏‎‎‎‎‏‎‏‎‎‎‎‎‏‏‏‏‎‏‏‏‏‎‎‏‏‎‎‏‎‏‎‏‏‏‏‎‏‎‎Allows the app to disable the status bar or add and remove system icons.‎‏‎‎‏‎"</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‏‎‎‎‎‎‏‎‎‏‏‏‏‏‏‏‎‎‎‎‎‏‎‏‏‏‏‎‏‏‎‎‏‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‎‏‏‏‎‏‎be the status bar‎‏‎‎‏‎"</string>
@@ -1610,6 +1612,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‎‎‎‏‎‎‎‎‏‎‏‎‎‎‏‎‎‎‏‏‎‎‎‏‏‏‎‎‏‏‏‏‏‏‎‎‏‏‎‏‎‎‏‎‏‎‏‏‎‎‎‎‏‎‏‎‎Raise volume above recommended level?‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎Listening at high volume for long periods may damage your hearing.‎‏‎‎‏‎"</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‏‏‎‎‎‎‏‎‏‏‎‎‏‎‎‏‏‏‏‎‏‏‎‏‎‎‎‏‎‏‏‎‏‎‏‎‏‎‏‎‏‎‏‏‏‎‎‎‎‏‎‏‎‏‎‏‎Use Accessibility Shortcut?‎‏‎‎‏‎"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‎‎‎‏‎‏‏‎‏‏‎‎‎‎‏‏‎‏‏‏‎‏‎‎‎‏‏‎‎‏‎‏‎‎‎‎‏‏‏‎‎‏‎‏‎‏‎‎‏‎‏‏‎‎‎When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎ Current accessibility feature:‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎ ‎‏‎‎‏‏‎<xliff:g id="SERVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎ You can change the feature in Settings &gt; Accessibility.‎‏‎‎‏‎"</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‏‎‎‏‏‎‏‏‎‏‎‎‎‎‎‎‏‎‏‎‎‏‎‎‎‎‏‎‏‎‏‏‎‎‏‎‏‏‏‎‏‏‏‏‏‎‎‎‎‏‎‏‎‎‎Empty‎‏‎‎‏‎"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‎‎‎‎‏‎‎‎‏‎‎‏‎‎‎‎‏‎‎‏‏‎‎‎‎‎‏‎‎‎‏‎‏‏‏‎‎‏‎‏‎‎‎‎‏‎‎‎‎‏‏‏‎Edit shortcuts‎‏‎‎‏‎"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‏‎‎‏‏‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‏‎‏‏‎‎‎‎‏‏‏‎‏‏‏‏‎‎‎‎‎‎‏‎‏‏‎‏‎‎‏‏‏‎‏‎‎Cancel‎‏‎‎‏‎"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‎‏‎‎‏‏‎‏‏‎‎‎‎‏‎‏‎‏‏‎‎‎‏‏‎‏‎‎‏‏‏‎‎‏‎‎‎‏‎‏‎‎‏‎‏‏‏‏‏‏‏‏‏‏‎Turn off Shortcut‎‏‎‎‏‎"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‎‏‏‏‎‏‎‏‏‎‎‎‏‎‎‏‎‎‎‎‎‏‎‏‎‎‎‏‎‎‏‎‏‏‏‎‏‎‎‎‏‏‏‏‎‎‏‎‏‏‏‏‎‎Use Shortcut‎‏‎‎‏‎"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‎‏‎‎‎‎‏‏‎‎‏‎‏‏‏‎‎‏‏‎‎‏‎‏‎‏‏‎‎‎‎‎‏‎‏‎‏‎‏‏‎‏‏‏‎‏‏‏‏‏‏‎‏‎‏‎‎‎Color Inversion‎‏‎‎‏‎"</string>
@@ -1845,6 +1850,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‏‎‏‏‏‏‏‏‎‎‏‏‏‏‎‏‏‏‎‎‏‎‏‎‎‏‎‎‎‏‏‎‏‎‏‏‏‎‎‎‎‏‎‎‏‏‎‎‏‏‏‎‎‎‎‎Turn on work profile?‎‏‎‎‏‎"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‎‎‎‎‏‏‏‎‏‎‎‏‎‏‏‏‏‎‏‏‏‎‏‏‎‏‎‏‏‏‏‏‎‏‏‎‏‏‏‎‏‎‎‎‏‏‎‏‏‎‏‏‎Your work apps, notifications, data, and other work profile features will be turned on‎‏‎‎‏‎"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‎‏‎‏‎‎‎‎‎‎‏‎‎‎‎‏‏‎‏‏‎‏‎‏‎‎‏‎‏‏‏‏‎‏‏‎‎‎‎‎‎‎‎‏‏‏‏‎‏‎‏‏‎‏‎Turn on‎‏‎‎‏‎"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‎‏‏‎‎‎‎‎‎‏‎‎‎‏‏‏‏‏‎‎‏‏‏‎‎‎‏‏‎‎‎‏‏‏‏‏‎‎‏‎‎‏‏‎‏‏‏‎‎‎‎‎‎‎App is not available‎‏‎‎‏‎"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‏‏‎‎‎‏‎‎‏‎‎‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‎‎‎‏‏‎‎‏‎‏‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ is not available right now.‎‏‎‎‏‎"</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‎‏‏‎‏‎‏‏‎‎‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‎‎‏‎‏‎‏‏‎‏‏‏‏‎‏‏‎‏‏‎‎‎‏‎This app was built for an older version of Android and may not work properly. Try checking for updates, or contact the developer.‎‏‎‎‏‎"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‏‎‎‏‎‎‎‏‎‎‎‎‏‎‎‏‎‎‏‎‏‎‎‎‏‎‎‏‎‎‎‏‎‎‏‏‎‎‏‏‏‏‏‎‏‎‎‏‏‎‏‎Check for update‎‏‎‎‏‎"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‎‎‏‏‎‏‎‏‏‎‎‏‏‏‏‎‎‎‎‎‎‏‎‏‏‏‎‏‏‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‏‎‏‎‏‎‎‎‎‏‏‎You have new messages‎‏‎‎‏‎"</string>
@@ -1955,13 +1962,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‏‎‎‎‏‎‏‎‏‏‏‏‎‎‏‎‏‏‏‎‏‎‎‏‏‎‎‎‏‏‏‏‎‏‏‎‎‎‏‏‎‎‏‎‎‎‎‎Battery may run out before usual charge‎‏‎‎‏‎"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‎‏‏‏‏‎‎‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‏‎‎‎‎‏‏‏‏‎‎‎‎‎‏‏‏‏‎‎‏‏‏‏‎‎‏‏‏‏‏‏‎‏‎Battery Saver activated to extend battery life‎‏‎‎‏‎"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‎‏‏‎‎‎‎‎‎‏‏‎‎‏‎‏‏‎‏‎‏‎‏‎‎‏‎‏‎‏‎‏‏‎‏‎‎‎‏‎‏‏‏‎‏‎‎‎‎‏‏‏‎‎‎‏‎Battery Saver‎‏‎‎‏‎"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‎‎‎‏‏‏‏‎‏‎‏‎‎‏‏‏‏‎‏‎‏‎‏‎‎‏‎‎‏‎‏‎‎‎‎‎‎‏‏‎‏‎‎‏‏‎‎‎‎‎‎‎‏‏‎‎Battery Saver won’t reactivate until battery low again‎‏‎‎‏‎"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‏‎‎‎‏‎‏‏‎‎‎‏‎‎‏‏‏‎‏‎‏‎‎‎‎‎‏‎‎‎‎‏‏‏‏‏‎‎‎‎‏‏‎‎‎‎‎‎‏‏‎Battery has been charged to a sufficient level. Battery Saver won’t reactivate until the battery is low again.‎‏‎‎‏‎"</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‏‎‏‏‏‎‎‎‏‏‎‎‏‏‎‎‎‏‎‏‎‏‏‏‏‎‏‎‎‏‎‏‎‎‏‏‎‏‎‏‏‎‎‎‎‏‎Phone ‎‏‎‎‏‏‎<xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>‎‏‎‎‏‏‏‎ charged‎‏‎‎‏‎"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‎‎‎‏‎‏‏‏‎‏‎‏‏‏‎‏‎‎‏‏‎‏‎‏‎‏‏‎‎‎‎‏‎‏‎‎‏‎‏‏‏‏‎‏‎‎‏‎‏‏‎‎‎Tablet ‎‏‎‎‏‏‎<xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>‎‏‎‎‏‏‏‎ charged‎‏‎‎‏‎"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‏‎‏‎‏‎‎‎‏‎‎‎‎‏‏‏‏‎‏‏‎‎‎‏‎‏‎‎‎‎‏‎‏‎‏‎‏‎‏‏‎‏‏‎‏‎‏‎‏‏‎‏‎‏‎Device ‎‏‎‎‏‏‎<xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>‎‏‎‎‏‏‏‎ charged‎‏‎‎‏‎"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‎‎‎‏‏‎‎‏‏‏‎‎‎‏‏‏‏‏‎‎‎‏‎‎‏‏‎‏‏‎‎‏‎‎‎‎‏‎‎‎‏‎‏‎‎‎‎‎‎‏‏‎‎Battery Saver is off. Features no longer restricted.‎‏‎‎‏‎"</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎‏‏‎‎‏‏‎‎‎‎‎‏‎‏‎‎‏‏‎‏‎‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‏‎‎‏‎‏‏‏‏‎Battery Saver turned off. Features no longer restricted.‎‏‎‎‏‎"</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‏‏‏‏‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‏‎‏‎‎‎‎‎‏‏‎‎‏‏‎‏‏‏‎‏‎‎‎‏‏‎‎‎‎‎‎‎‎‏‏‎Battery Saver turned off‎‏‎‎‏‎"</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‏‏‏‎‎‎‏‏‏‎‏‏‏‏‎‏‎‎‎‏‏‎‏‏‏‏‎‏‏‎‏‎‎‎‎‏‏‎‏‎‏‏‎‎‏‎‏‎‏‏‎‏‏‏‏‎Phone has enough charge. Features no longer restricted.‎‏‎‎‏‎"</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‏‎‏‏‎‏‎‏‏‎‏‏‏‏‏‏‏‎‎‏‏‏‎‏‎‏‎‏‎‏‏‎‏‎‎‎‏‏‏‏‎‏‏‏‏‎‏‏‎‏‏‎‎‎‎Tablet has enough charge. Features no longer restricted.‎‏‎‎‏‎"</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‎‏‎‏‎‎‎‎‏‏‎‏‏‎‎‎‏‎‎‎‏‏‎‏‏‎‏‏‏‎‎‎‏‎‏‎‎‏‎‎‏‏‏‎‎‏‏‎‏‎‏‏‏‎‎‏‎Device has enough charge. Features no longer restricted.‎‏‎‎‏‎"</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‏‎‎‏‎‏‎‎‏‎‎‎‏‎‎‎‏‏‎‎‎‏‎‏‎‏‏‏‎‏‎‏‎‏‎‎‏‎‏‎‏‏‎‏‎‎‎‏‏‏‎‎‎‎‏‎‎Folder‎‏‎‎‏‎"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‏‏‏‏‏‎‎‏‏‏‎‎‏‎‎‎‏‏‏‏‎‎‎‎‏‏‎‏‎‎‏‏‏‎‎‏‏‎‎‏‏‎‏‎‏‎‎‏‏‏‎‏‏‏‏‎‎Android application‎‏‎‎‏‎"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‎‏‎‎‎‏‏‎‎‎‏‏‏‎‎‎‏‏‏‎‏‎‏‎‏‏‏‎‎‎‏‎‏‏‎‏‎‏‏‎‎‏‎‎‎File‎‏‎‎‏‎"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index ad861f2..aeec9f8 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -287,7 +287,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"acceder al calendario"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"enviar y ver mensajes SMS"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Almacenamiento"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"Archivos y contenido multimedia"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"acceder a las fotos, el contenido multimedia y los archivos"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Micrófono"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"grabar audio"</string>
@@ -313,6 +313,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Permite presionar, deslizar, pellizcar y usar otros gestos."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Gestos del sensor de huellas digitales"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Captura los gestos que se hacen en el sensor de huellas digitales del dispositivo."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Tomar captura de pantalla"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Puede tomar una captura de la pantalla."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"desactivar o modificar la barra de estado"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Permite que la aplicación inhabilite la barra de estado o que agregue y elimine íconos del sistema."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"aparecer en la barra de estado"</string>
@@ -1610,6 +1612,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"¿Quieres subir el volumen por encima del nivel recomendado?\n\nEscuchar a un alto volumen durante largos períodos puede dañar tu audición."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"¿Usar acceso directo de accesibilidad?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Cuando el acceso directo está activado, puedes presionar los botones de volumen durante 3 segundos para iniciar una función de accesibilidad.\n\n Función de accesibilidad actual:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Puedes cambiar la función en Configuración &gt; Accesibilidad."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Vacío"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Editar accesos directos"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Cancelar"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Desactivar acceso directo"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Usar acceso directo"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Inversión de color"</string>
@@ -1845,6 +1850,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"¿Activar el perfil de trabajo?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Se activaran las apps de trabajo, los datos, las notificaciones y otras funciones del perfil de trabajo"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Activar"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"La app no está disponible"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> no está disponible en este momento."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Esta app se creó para una versión anterior de Android y es posible que no funcione correctamente. Busca actualizaciones o comunícate con el programador."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Buscar actualización"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Tienes mensajes nuevos"</string>
@@ -1955,13 +1962,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Es posible que la batería se agote antes de la carga habitual"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Se activó el Ahorro de batería para extender la duración de la batería"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Ahorro de batería"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"El Ahorro de batería no se volverá a activar hasta que la batería esté baja otra vez"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"La batería tiene suficiente carga. El Ahorro de batería no se volverá a activar hasta que la batería esté baja otra vez."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Nivel de batería: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Nivel de batería de la tablet: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Nivel de batería del dispositivo: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"El Ahorro de batería está desactivado. Ya no se restringen las funciones."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Se desactivó el Ahorro de batería. Ya no se restringen las funciones."</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Se desactivó el Ahorro de batería"</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"El teléfono tiene suficiente carga. Ya no se restringen las funciones."</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"La tablet tiene suficiente carga. Ya no se restringen las funciones."</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"El dispositivo tiene suficiente carga. Ya no se restringen las funciones."</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"Carpeta"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Aplicación de Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Archivo"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 9c64369..1ef891a 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -287,7 +287,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"acceder a tu calendario"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"enviar y ver mensajes SMS"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Almacenamiento"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"Archivos y contenido multimedia"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"acceder a fotos, contenido multimedia y archivos de tu dispositivo"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Micrófono"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"grabar audio"</string>
@@ -313,6 +313,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Puedes tocar y pellizcar la pantalla, deslizar el dedo y hacer otros gestos."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Gestos de huellas digitales"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Puede capturar los gestos realizados en el sensor de huellas digitales del dispositivo."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Hacer captura de pantalla"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Puede hacer capturas de la pantalla."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"inhabilitar o modificar la barra de estado"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Permite que la aplicación inhabilite la barra de estado o añada y elimine iconos del sistema."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"aparecer en la barra de estado"</string>
@@ -1610,6 +1612,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"¿Quieres subir el volumen por encima del nivel recomendado?\n\nEscuchar sonidos fuertes durante mucho tiempo puede dañar los oídos."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"¿Utilizar acceso directo de accesibilidad?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Si el acceso directo está activado, pulsa los dos botones de volumen durante tres segundos para iniciar una función de accesibilidad.\n\n Función de accesibilidad actual:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Puedes cambiar la función en Ajustes &gt; Accesibilidad."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Vacío"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Editar accesos directos"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Cancelar"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Desactivar acceso directo"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Utilizar acceso directo"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Inversión de color"</string>
@@ -1845,6 +1850,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"¿Activar el perfil de trabajo?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Tus aplicaciones, notificaciones, datos y otras funciones del perfil de trabajo se activarán"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Activar"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"La aplicación no está disponible"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"En estos momentos, <xliff:g id="APP_NAME">%1$s</xliff:g> no está disponible."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Esta aplicación se ha diseñado para una versión anterior de Android y es posible que no funcione correctamente. Busca actualizaciones o ponte en contacto con el desarrollador."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Buscar actualizaciones"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Tienes mensajes nuevos"</string>
@@ -1955,13 +1962,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Es posible que te quedes sin batería antes de lo habitual"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Se ha activado el ahorro de batería para aumentar la duración de la batería"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Ahorro de batería"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"El modo Ahorro de batería no se activará hasta que la batería vuelva a estar baja"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"La batería se ha cargado lo suficiente. El modo Ahorro de batería no se activará hasta que la batería vuelva a estar baja."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Nivel de la batería del teléfono: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Nivel de batería del tablet: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Nivel de la batería del dispositivo: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Ahorro de batería desactivado. Las funciones ya no están restringidas."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Ahorro de batería desactivado. Las funciones ya no están restringidas."</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Ahorro de batería desactivado"</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"El teléfono tiene suficiente batería. Las funciones ya no están restringidas."</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"El tablet tiene suficiente batería. Las funciones ya no están restringidas."</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"El dispositivo tiene suficiente batería. Las funciones ya no están restringidas."</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"Carpeta"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Aplicación de Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Archivo"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index c9e74b3..9a4b6d3 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"juurdepääs kalendrile"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"saata ja vaadata SMS-sõnumeid"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Mäluruum"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"juurdepääs seadmesse salvestatud fotodele, meediasisule ja failidele"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofon"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"heli salvestamine"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Saate puudutada, pühkida, sõrmi kokku-lahku liigutada ja teisi liigutusi teha."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Sõrmejälje liigutused"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Teil on võimalik jäädvustada seadme sõrmejäljeanduril tehtud liigutused."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"keela või muuda olekuriba"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Võimaldab rakendusel keelata olekuriba või lisada ja eemaldada süsteemiikoone."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"olekuribana kuvamine"</string>
@@ -1454,7 +1459,7 @@
     <string name="sync_really_delete" msgid="5657871730315579051">"Kustuta üksused"</string>
     <string name="sync_undo_deletes" msgid="5786033331266418896">"Võtke kustutamised tagasi"</string>
     <string name="sync_do_nothing" msgid="4528734662446469646">"Ära tee praegu midagi"</string>
-    <string name="choose_account_label" msgid="5557833752759831548">"Konto valimine"</string>
+    <string name="choose_account_label" msgid="5557833752759831548">"Valige konto"</string>
     <string name="add_account_label" msgid="4067610644298737417">"Konto lisamine"</string>
     <string name="add_account_button_label" msgid="322390749416414097">"Lisa konto"</string>
     <string name="number_picker_increment_button" msgid="7621013714795186298">"Suurendamine"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Kas suurendada helitugevuse taset üle soovitatud taseme?\n\nPikaajaline valju helitugevusega kuulamine võib kuulmist kahjustada."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Kas kasutada juurdepääsetavuse otseteed?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Kui otsetee on sisse lülitatud, käivitab mõlema helitugevuse nupu kolm sekundit all hoidmine juurdepääsetavuse funktsiooni.\n\n Praegune juurdepääsetavuse funktsioon:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Saate seda funktsiooni muuta valikutega Seaded &gt; Juurdepääsetavus."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Tühi"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Muuda otseteid"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Tühista"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Lülita otsetee välja"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Kasuta otseteed"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Värvide ümberpööramine"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Kas lülitada tööprofiil sisse?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Teie töörakendused, märguanded, andmed ja muud tööprofiili funktsioonid lülitatakse sisse"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Lülita sisse"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"See rakendus on loodud Androidi vanema versiooni jaoks ega pruugi õigesti töötada. Otsige värskendusi või võtke ühendust arendajaga."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Otsi värskendust"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Teile on uusi sõnumeid"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Aku võib enne tavapärast laadimist tühjaks saada"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Akusäästja aktiveeriti aku tööea pikendamiseks"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Akusäästja"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Akusäästja lülitatakse uuesti sisse, kui aku hakkab tühjaks saama"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Aku on piisavalt laetud. Akusäästja lülitatakse uuesti sisse, kui aku hakkab tühjaks saama."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Telefon on <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> laetud"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Tahvelarvuti on <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> laetud"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Seade on <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> laetud"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Akusäästja on välja lülitatud. Funktsioonid ei ole enam piiratud."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Akusäästja on välja lülitatud. Funktsioonid ei ole enam piiratud."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"Kaust"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Androidi rakendus"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Fail"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index cae6b99..f0a0010c 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"atzitu egutegia"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS mezuak"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"bidali eta ikusi SMS mezuak"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Memoria"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"atzitu gailuko argazkiak, multimedia-edukia eta fitxategiak"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofonoa"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"grabatu audioa"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Sakatu, lerratu, atximurkatu eta beste hainbat keinu egin ditzake."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Hatz-marken keinuak"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Gailuaren hatz-marken sentsorean egindako keinuak atzeman ditzake."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"desgaitu edo aldatu egoera-barra"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Egoera-barra desgaitzea edo sistema-ikonoak gehitzea edo kentzea baimentzen die aplikazioei."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"bihurtu egoera-barra"</string>
@@ -1306,7 +1311,7 @@
     <string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"Erantsitako gailua ez da telefono honekin bateragarria. Sakatu informazio gehiago lortzeko."</string>
     <string name="adb_active_notification_title" msgid="408390247354560331">"USB bidezko arazketa konektatuta"</string>
     <string name="adb_active_notification_message" msgid="5617264033476778211">"Sakatu USB bidezko arazketa desaktibatzeko"</string>
-    <string name="adb_active_notification_message" product="tv" msgid="6624498401272780855">"Hautatu USB arazketa desgaitzeko."</string>
+    <string name="adb_active_notification_message" product="tv" msgid="6624498401272780855">"Hautatu USB bidezko arazketa desgaitzeko."</string>
     <string name="test_harness_mode_notification_title" msgid="2282785860014142511">"Proba-materialeko modua gaitu da"</string>
     <string name="test_harness_mode_notification_message" msgid="3039123743127958420">"Proba-materialaren modua desgaitzeko, berrezarri jatorrizko datuak."</string>
     <string name="console_running_notification_title" msgid="6087888939261635904">"Serie-kontsola gaituta dago"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Bolumena gomendatutako mailatik gora igo nahi duzu?\n\nMusika bolumen handian eta denbora luzez entzuteak entzumena kalte diezazuke."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Erabilerraztasun-lasterbidea erabili nahi duzu?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Lasterbidea aktibatuta dagoenean, bi bolumen-botoiak hiru segundoz sakatuta abiaraziko da erabilerraztasun-eginbidea.\n\n Uneko erabilerraztasun-eginbidea:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Eginbidea aldatzeko, joan Ezarpenak &gt; Erabilerraztasuna atalera."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Hustu"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Editatu lasterbideak"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Utzi"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Desaktibatu lasterbidea"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Erabili lasterbidea"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Koloreen alderantzikatzea"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Laneko profila aktibatu?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Laneko aplikazioak, jakinarazpenak, datuak eta laneko profileko bestelako eginbideak aktibatuko dira"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Aktibatu"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Aplikazioa Android-en bertsio zaharrago baterako sortu zenez, baliteke behar bezala ez funtzionatzea. Bilatu eguneratzerik baden, edo jarri garatzailearekin harremanetan."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Bilatu eguneratzeak"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Mezu berriak dituzu"</string>
@@ -1874,7 +1886,7 @@
     <string name="app_category_maps" msgid="6395725487922533156">"Mapak eta nabigazioa"</string>
     <string name="app_category_productivity" msgid="1844422703029557883">"Produktibitatea"</string>
     <string name="device_storage_monitor_notification_channel" msgid="5164244565844470758">"Gailuaren memoria"</string>
-    <string name="adb_debugging_notification_channel_tv" msgid="4764046459631031496">"USB arazketa"</string>
+    <string name="adb_debugging_notification_channel_tv" msgid="4764046459631031496">"USB bidezko arazketa"</string>
     <string name="time_picker_hour_label" msgid="4208590187662336864">"ordu"</string>
     <string name="time_picker_minute_label" msgid="8307452311269824553">"minutu"</string>
     <string name="time_picker_header_text" msgid="9073802285051516688">"Ezarri ordua"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Baliteke bateria ohi baino lehenago agortzea"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Bateria-aurrezlea aktibatuta dago bateriaren iraupena luzatzeko"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Bateria-aurrezlea"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Bateria-aurrezlea ez da aktibatuko berriro bateria gutxi gelditzen den arte"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Kargatu da behar adina bateria. Bateria-aurrezlea ez da aktibatuko berriro bateria gutxi gelditzen den arte."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Telefonoaren bateria: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Tabletaren bateria: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Gailuaren bateria: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Desaktibatu egin da bateria-aurrezlea. Jada ez dago eginbiderik murriztuta."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Desaktibatu egin da bateria-aurrezlea. Jada ez dago eginbiderik murriztuta."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"Karpeta"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android-erako aplikazioa"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Fitxategia"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index b45c6f9..7d12221 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"دسترسی به تقویم شما"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"پیامک"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"ارسال و مشاهده پیامک‌ها"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"حافظه"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"دسترسی به عکس‌ها، رسانه‌ها و فایل‌های روی دستگاهتان"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"میکروفن"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"ضبط صدا"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"می‌توانید ضربه بزنید، انگشتتان را تند بکشید، انگشتانتان را به هم نزدیک یا از هم دور کنید و اشاره‌های دیگری اجرا کنید."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"اشاره‌های اثر انگشت"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"می‌تواند اشاره‌های اجرا‌شده روی حسگر اثرانگشت دستگاه را ثبت کند."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"غیرفعال کردن یا تغییر نوار وضعیت"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"‏به برنامه اجازه می‎دهد تا نوار وضعیت را غیرفعال کند یا نمادهای سیستم را اضافه یا حذف کند."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"نوار وضعیت باشد"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"میزان صدا را به بالاتر از حد توصیه شده افزایش می‌دهید؟\n\nگوش دادن به صداهای بلند برای مدت طولانی می‌تواند به شنوایی‌تان آسیب وارد کند."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"از میان‌بر دسترس‌پذیری استفاده شود؟"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"وقتی میان‌بر روشن است،‌ اگر هر دو دکمه صدا را ۳ ثانیه فشار دهید یکی از قابلیت‌های دسترس‌پذیری شروع می‌شود.\n\n قابلیت دسترس‌پذیری کنونی:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n می‌توانید در «تنظیمات &gt; دسترس‌پذیری»، قابلیت را تغییر دهید."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"خالی"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"ویرایش میان‌برها"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"لغو"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"خاموش کردن میان‌بر"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"استفاده از میان‌بر"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"وارونگی رنگ"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"نمایه کاری روشن شود؟"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"برنامه‌ها، اعلان‌ها، داده‌ها و سایر قابلیت‌های نمایه کاری شما روشن خواهد شد"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"روشن کردن"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"‏این برنامه برای نسخه قدیمی‌تری از Android ساخته شده است و ممکن است درست کار نکند. وجود به‌روزرسانی را بررسی کنید یا با برنامه‌نویس تماس بگیرید."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"بررسی وجود به‌روزرسانی"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"پیام‌های جدیدی دارید"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"ممکن است شارژ باتری قبل از شارژ معمول تمام شود"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"جهت افزایش عمر باتری، «بهینه‌سازی باتری» فعال شد"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"بهینه‌سازی باتری"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"تا وقتی شارژ باتری دوباره به سطح پایین نرسد، «بهینه‌سازی باتری» مجدداً فعال نخواهد شد"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"باتری درحد کافی شارژ شده است. تا وقتی شارژ باتری دوباره به سطح پایین نرسد، «بهینه‌سازی باتری» مجدداً فعال نخواهد شد."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"تلفن <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> شارژ شد"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"رایانه لوحی <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> شارژ شد"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"دستگاه <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> شارژ شد"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"«بهینه‌سازی باتری» خاموش است. ویژگی‌ها دیگر محدود نمی‌شوند."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"«بهینه‌سازی باتری» خاموش شد. ویژگی‌ها دیگر محدود نمی‌شوند."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"پوشه"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"‏برنامه Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"فایل"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index c1df18e..ba39b8c 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"käyttää kalenteria"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"Tekstiviestit"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"lähettää ja tarkastella tekstiviestejä"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Tallennustila"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"käyttää laitteellesi tallennettuja valokuvia, mediatiedostoja ja muita tiedostoja"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofoni"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"tallentaa ääntä"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Lupa napauttaa, pyyhkäistä, nipistää ja käyttää muita eleitä."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Sormenjälkieleet"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Voi tallentaa laitteen sormenjälkitunnistimelle tehtyjä eleitä."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"poista tilapalkki käytöstä tai muokkaa tilapalkkia"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Antaa sovelluksen poistaa tilapalkin käytöstä ja lisätä tai poistaa järjestelmäkuvakkeita."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"sijaita tilapalkissa"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Nostetaanko äänenvoimakkuus suositellun tason yläpuolelle?\n\nPitkäkestoinen kova äänenvoimakkuus saattaa heikentää kuuloa."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Käytetäänkö esteettömyyden pikanäppäintä?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Kun pikanäppäin on käytössä, voit käynnistää esteettömyystoiminnon pitämällä molempia äänenvoimakkuuspainikkeita painettuna kolmen sekunnin ajan.\n\n Tällä hetkellä valittu esteettömyystoiminto:\n<xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Voit vaihtaa toimintoa valitsemalla Asetukset &gt; Esteettömyys."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Tyhjennä"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Muokkaa pikakuvakkeita"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Peruuta"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Poista pikanäppäin käytöstä"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Käytä pikanäppäintä"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Käänteiset värit"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Otetaanko työprofiili käyttöön?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Työsovellukset, ‑ilmoitukset, ‑tiedot ja muut työprofiiliominaisuudet otetaan käyttöön"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Ota käyttöön"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Tämä sovellus on suunniteltu vanhemmalle Android-versiolle eikä välttämättä toimi oikein. Kokeile tarkistaa päivitykset tai ottaa yhteyttä kehittäjään."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Tarkista päivitykset"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Sinulle on uusia viestejä"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Akku saattaa loppua ennen normaalia latausaikaa"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Virransäästö otettu käyttöön akunkeston pidentämiseksi"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Virransäästö"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Virransäästö aktivoituu uudelleen vasta, kun akku on lähes tyhjä"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Akun varaus on riittävä. Virransäästö aktivoituu uudelleen vasta, kun akku on lähes tyhjä."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Puhelin <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ladattu"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Tabletti <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ladattu"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Laite <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ladattu"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Virransäästö ei ole käytössä. Ominaisuuksia ei enää rajoiteta."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Virransäästö poistettiin käytöstä. Ominaisuuksia ei enää rajoiteta."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"Kansio"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android-sovellus"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Tiedosto"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 9f2647d..8df6ad6 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"accéder à votre agenda"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"Messagerie texte"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"envoyer et afficher des messages texte"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Stockage"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"accéder aux photos, aux contenus multimédias et aux fichiers sur votre appareil"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Microphone"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"enregistrer des fichiers audio"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Peut toucher, balayer, pincer et effectuer d\'autres gestes."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Gestes sur le capteur d\'empreintes digitales"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Peut capturer des gestes effectués sur le capteur d\'empreintes digitales de l\'appareil."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"désactiver ou modifier la barre d\'état"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Permet à l\'application de désactiver la barre d\'état, ou d\'ajouter et de supprimer des icônes système."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"servir de barre d\'état"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Augmenter le volume au-dessus du niveau recommandé?\n\nL\'écoute prolongée à un volume élevé peut endommager vos facultés auditives."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Utiliser le raccourci d\'accessibilité?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Quand le raccourci est activé, appuyez sur les deux boutons de volume pendant trois secondes pour lancer une fonctionnalité d\'accessibilité.\n\n Fonctionnalité d\'accessibilité utilisée actuellement :\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Vous pouvez changer de fonctionnalité sous Paramètres &gt; Accessibilité."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Vide"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Modifier les raccourcis"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Annuler"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Désactiver le raccourci"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Utiliser le raccourci"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Inversion des couleurs"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Activer le profil professionnel?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Vos applications professionnelles, vos notifications, vos données et les autres fonctionnalités de profil professionnel seront activées"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Activer"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Cette application a été conçue pour une ancienne version d\'Android et pourrait ne pas fonctionner correctement. Essayez de vérifier les mises à jour ou communiquez avec son concepteur."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Vérifier la présence de mises à jour"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Vous avez de nouveaux messages"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"La pile pourrait s\'épuiser avant la charge habituelle"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Le mode Économiseur de pile est activé afin de prolonger l\'autonomie"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Économiseur de pile"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Le mode Économiseur de pile ne se réactivera seulement lorsque la pile deviendra faible à nouveau"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"La pile a été suffisamment chargée. Le mode Économiseur de pile ne se réactivera seulement lorsque la pile deviendra faible à nouveau."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Téléphone chargé à <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Tablette chargée à <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Appareil chargé à <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Le mode Économiseur de pile est désactivé. Ces fonctionnalités ne sont plus restreintes."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Le mode Économiseur de pile est désactivé. Ces fonctionnalités ne sont plus restreintes."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"Dossier"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Application Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Fichier"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 7d80653..a64066a 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -287,7 +287,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"accéder à votre agenda"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"envoyer et consulter des SMS"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Stockage"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"Fichiers et contenus multimédias"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"accéder aux photos, contenus multimédias et fichiers sur votre appareil"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Microphone"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"enregistrer des fichiers audio"</string>
@@ -313,6 +313,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Permet d\'appuyer sur l\'écran, de le balayer, de le pincer et d\'effectuer d\'autres gestes."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Gestes avec l\'empreinte digitale"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Peut enregistrer des gestes effectués sur le lecteur d\'empreinte digitale de l\'appareil."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Prendre une capture d\'écran"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Peut prendre des captures d\'écran."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"Désactivation ou modification de la barre d\'état"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Permet à l\'application de désactiver la barre d\'état, ou d\'ajouter et de supprimer des icônes système."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"remplacer la barre d\'état"</string>
@@ -1610,6 +1612,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Augmenter le volume au dessus du niveau recommandé ?\n\nL\'écoute prolongée à un volume élevé peut endommager vos facultés auditives."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Utiliser le raccourci d\'accessibilité ?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Quand le raccourci est activé, appuyez sur les deux boutons de volume pendant trois secondes pour lancer une fonctionnalité d\'accessibilité.\n\n Fonctionnalité d\'accessibilité utilisée actuellement :\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Vous pouvez changer de fonctionnalité dans Paramètres &gt; Accessibilité."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Vider"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Modifier les raccourcis"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Annuler"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Désactiver le raccourci"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Utiliser le raccourci"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Inversion des couleurs"</string>
@@ -1845,6 +1850,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Activer profil professionnel ?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Vos applications professionnelles, notifications, données et d\'autres fonctionnalités de votre profil professionnel seront activées"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Activer"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Application non disponible"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> n\'est pas disponible pour le moment."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Cette application a été conçue pour une ancienne version d\'Android et risque de ne pas fonctionner correctement. Recherchez des mises à jour ou contactez le développeur."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Rechercher une mise à jour"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Vous avez de nouveaux messages"</string>
@@ -1955,13 +1962,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Vous risquez d\'être à court de batterie plus tôt que prévu"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Économiseur de batterie activé pour prolonger l\'autonomie"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Économiseur de batterie"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"L\'économiseur d\'écran ne se réactivera que lorsque le niveau de la batterie sera à nouveau faible"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Le niveau de charge de la batterie est suffisant. L\'économiseur de batterie ne se réactivera que lorsque le niveau de la batterie sera à nouveau faible."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Téléphone chargé à <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Tablette chargée à <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Appareil chargé à <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Économiseur de batterie désactivé. Les fonctionnalités ne sont plus restreintes."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Économiseur de batterie désactivé. Les fonctionnalités ne sont plus restreintes."</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Économiseur de batterie désactivé"</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Téléphone suffisamment chargé. Les fonctionnalités ne sont plus restreintes."</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"Tablette suffisamment chargée. Les fonctionnalités ne sont plus restreintes."</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"Appareil suffisamment chargé. Les fonctionnalités ne sont plus restreintes."</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"Dossier"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Application Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Fichier"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index eebe5ab..df08abc 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -287,7 +287,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"acceder ao teu calendario"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"enviar e consultar mensaxes de SMS"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Almacenamento"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"Ficheiros e contido multimedia"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"acceder a fotos, contido multimedia e ficheiros no teu dispositivo"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Micrófono"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"gravar audio"</string>
@@ -313,6 +313,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Podes tocar, pasar o dedo, beliscar e realizar outros xestos."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Xestos de impresión dixital"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Pode rexistrar os xestos realizados no sensor de impresión dixital do dispositivo."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Facer captura de pantalla"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Pode facer capturas de pantalla."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"desactivar ou modificar a barra de estado"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Permite á aplicación desactivar a barra de estado ou engadir e eliminar as iconas do sistema."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"actuar como a barra de estado"</string>
@@ -1610,6 +1612,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Queres subir o volume máis do nivel recomendado?\n\nA reprodución de son a un volume elevado durante moito tempo pode provocar danos nos oídos."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Queres utilizar o atallo de accesibilidade?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Cando o atallo está activado, podes premer os dous botóns de volume durante 3 segundos para iniciar unha función de accesibilidade.\n\n Función de accesibilidade actual:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Podes cambiar a función en Configuración &gt; Accesibilidade."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Baleirar"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Editar atallos"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Cancelar"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Desactivar atallo"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Utilizar atallo"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Inversión de cor"</string>
@@ -1845,6 +1850,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Activar o perfil de traballo?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Activaranse as túas aplicacións de traballo, as notificacións, os datos e outras funcións do perfil de traballo"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Activar"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"A aplicación non está dispoñible"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"A aplicación <xliff:g id="APP_NAME">%1$s</xliff:g> non está dispoñible neste momento."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Esta aplicación deseñouse para unha versión anterior de Android e quizais non funcione correctamente. Proba a buscar actualizacións ou contacta co programador."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Buscar actualización"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Tes mensaxes novas"</string>
@@ -1955,13 +1962,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"A batería pode esgotarse antes do habitual"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Para ampliar a duración da batería activouse a función Aforro de batería"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Aforro de batería"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"A función Aforro de batería non se activará de novo ata que volva quedar pouca batería"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"A batería cargouse ata un nivel suficiente. A función Aforro de batería non se activará de novo ata que volva quedar pouca batería."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Nivel de batería do teléfono: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Nivel de batería da tableta: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Nivel de batería do dispositivo: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"A función Aforro de batería está desactivada. Xa non se restrinxirá ningunha función."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Desactivouse a función Aforro de batería. Xa non se restrinxirá ningunha función."</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Desactivouse a función Aforro de batería."</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"O teléfono non ten suficiente batería. Xa non se restrinxirán as funcións."</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"A tableta non ten suficiente batería. Xa non se restrinxirán as funcións."</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"O dispositivo non ten suficiente batería. Xa non se restrinxirán as funcións."</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"Cartafol"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Aplicación Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Ficheiro"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 869a752..f663b87 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -247,7 +247,7 @@
     <string name="global_actions_toggle_airplane_mode" msgid="6911684460146916206">"એરપ્લેન મોડ"</string>
     <string name="global_actions_airplane_mode_on_status" msgid="5508025516695361936">"એરપ્લેન મોડ ચાલુ છે."</string>
     <string name="global_actions_airplane_mode_off_status" msgid="8522219771500505475">"એરપ્લેન મોડ બંધ છે."</string>
-    <string name="global_action_settings" msgid="4671878836947494217">"સેટિંગ્સ"</string>
+    <string name="global_action_settings" msgid="4671878836947494217">"સેટિંગ"</string>
     <string name="global_action_assist" msgid="2517047220311505805">"સહાય"</string>
     <string name="global_action_voice_assist" msgid="6655788068555086695">"વૉઇસ સહાય"</string>
     <string name="global_action_lockdown" msgid="2475471405907902963">"લૉકડાઉન"</string>
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"તમારા કેલેન્ડરને ઍક્સેસ કરવાની"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS સંદેશા મોકલવાની અને જોવાની"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"સ્ટોરેજ"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"તમારા ઉપકરણ પર ફોટો, મીડિયા અને ફાઇલો ઍક્સેસ કરવાની"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"માઇક્રોફોન"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"ઑડિઓ રેકોર્ડ કરવાની"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"ટૅપ, સ્વાઇપ, પિંચ કરી અને અન્ય હાવભાવ કરી શકે છે."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"ફિંગરપ્રિન્ટ સંકેતો"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"ડિવાઇસના ફિંગરપ્રિન્ટ સેન્સર પર કરવામાં આવેલા સંકેતો કૅપ્ચર કરી શકે છે."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"સ્ટેટસ બારને અક્ષમ કરો અથવા તેમાં ફેરફાર કરો"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"ઍપ્લિકેશનને સ્ટેટસ બાર અક્ષમ કરવાની અથવા સિસ્ટમ આયકન્સ ઉમેરવા અને દૂર કરવાની મંજૂરી આપે છે."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"સ્ટેટસ બારમાં બતાવો"</string>
@@ -1552,7 +1557,7 @@
     <string name="media_route_chooser_title" msgid="6646594924991269208">"ઉપકરણ સાથે કનેક્ટ કરો"</string>
     <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"ઉપકરણ પર સ્ક્રીન કાસ્ટ કરો"</string>
     <string name="media_route_chooser_searching" msgid="6119673534251329535">"ઉપકરણો માટે શોધી રહ્યું છે…"</string>
-    <string name="media_route_chooser_extended_settings" msgid="2506352159381327741">"સેટિંગ્સ"</string>
+    <string name="media_route_chooser_extended_settings" msgid="2506352159381327741">"સેટિંગ"</string>
     <string name="media_route_controller_disconnect" msgid="7362617572732576959">"ડિસ્કનેક્ટ કરો"</string>
     <string name="media_route_status_scanning" msgid="8045156315309594482">"સ્કેન કરી રહ્યું છે..."</string>
     <string name="media_route_status_connecting" msgid="5845597961412010540">"કનેક્ટ કરી રહ્યું છે..."</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ભલામણ કરેલ સ્તરની ઉપર વૉલ્યૂમ વધાર્યો?\n\nલાંબા સમય સુધી ઊંચા અવાજે સાંભળવું તમારી શ્રવણક્ષમતાને નુકસાન પહોંચાડી શકે છે."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ઍક્સેસિબિલિટી શૉર્ટકટનો ઉપયોગ કરીએ?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"જ્યારે શૉર્ટકટ ચાલુ હોય, ત્યારે બન્ને વૉલ્યૂમ બટનને 3 સેકન્ડ સુધી દબાવી રાખવાથી ઍક્સેસિબિલિટી સુવિધા શરૂ થઈ જશે.\n\n વર્તમાન ઍક્સેસિબિલિટી સુવિધા:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n તમે સેટિંગ્સ &gt; ઍક્સેસિબિલિટીમાં જઈને આ સુવિધા બદલી શકો છો."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"ખાલી કરો"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"શૉર્ટકટમાં ફેરફાર કરો"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"રદ કરો"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"શૉર્ટકટ બંધ કરો"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"શૉર્ટકટનો ઉપયોગ કરો"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"રંગનો વ્યુત્ક્રમ"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"કાર્યાલયની પ્રોફાઇલ ચાલુ કરીએ?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"તમારી કાર્યાલયની ઍપ, નોટિફિકેશન, ડેટા અને અન્ય કાર્યાલયની પ્રોફાઇલ સુવિધાઓ ચાલુ કરવામાં આવશે"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"ચાલુ કરો"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"આ ઍપ Androidના જૂના વર્ઝન માટે બનાવવામાં આવ્યું હતું અને તે કદાચ તે યોગ્ય રીતે કાર્ય કરી શકશે નહીં. અપડેટ માટે તપાસવાનો પ્રયાસ કરો અથવા ડેવલપરનો સંપર્ક કરો."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"અપડેટ માટે તપાસો"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"તમારી પાસે નવા સંદેશા છે"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"સામાન્ય રીતે ચાર્જ કરવાના સમય પહેલાં બૅટરી સમાપ્ત થઈ શકે છે"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"બૅટરી આવરદા વધારવા માટે બૅટરી સેવર ચાલુ કર્યું"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"બૅટરી સેવર"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"બૅટરી ફરીથી ઓછી નહીં થાય ત્યાં સુધી બૅટરી સેવર ફરીથી સક્રિય નહીં થાય"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"બૅટરી પૂરતા લેવલ સુધી ચાર્જ થઈ ગઈ છે. બૅટરી ફરીથી ઓછી નહીં થાય ત્યાં સુધી બૅટરી સેવર ફરીથી સક્રિય નહીં થાય"</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"ફોન <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ચાર્જ થયો છે"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"ટૅબ્લેટ <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ચાર્જ થયું છે"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"ડિવાઇસ <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ચાર્જ થયું છે"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"બૅટરી સેવર બંધ છે. સુવિધાઓ હવે મર્યાદિત નથી."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"બૅટરી સેવર બંધ કર્યું. સુવિધાઓ હવે મર્યાદિત નથી."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"ફોલ્ડર"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android ઍપ"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"ફાઇલ"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 612854c..8105441 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"अपने कैलेंडर को ऐक्सेस करने"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"मैसेज (एसएमएस)"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"मैसेज (एसएमएस) भेजें और देखें"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"मेमोरी"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"अपने डिवाइस पर मौजूद फ़ोटो, मीडिया और फ़ाइलें ऐक्सेस करने की"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"माइक्रोफ़ोन"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"ऑडियो रिकॉर्ड करें"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"इस सेवा के ज़रिए टैप, स्वाइप, पिंच और बाकी जेस्चर किए जा सकते हैं."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"फ़िंगरप्रिंट जेस्चर"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"डिवाइस के फ़िंगरप्रिंट सेंसर पर किए गए हाथ के जेस्चर (स्पर्श) कैप्चर किए जा सकते हैं."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"स्टेटस बार को अक्षम करें या बदलें"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"ऐप को, स्टेटस बार को बंद करने या सिस्‍टम आइकॉन को जोड़ने और निकालने की अनुमति देता है."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"स्टेटस बार को रहने दें"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"वॉल्यूम को सुझाए गए स्तर से ऊपर बढ़ाएं?\n\nअत्यधिक वॉल्यूम पर ज़्यादा समय तक सुनने से आपकी सुनने की क्षमता को नुकसान हो सकता है."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"सुलभता शॉर्टकट का इस्तेमाल करना चाहते हैं?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"इस शॉर्टकट के चालू होने पर, दोनों वॉल्यूम बटनों को 3 सेकंड तक दबाने से सुलभता सुविधा शुरू हो जाएगी.\n\n मौजूदा सुलभता सुविधा:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n आप इस सुविधा को सेटिंग &gt; सुलभता पर जाकर बदल सकते हैं."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"खाली करें"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"शॉर्टकट में बदलाव करें"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"अभी नहीं"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"शॉर्टकट बंद करें"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"शॉर्टकट का उपयोग करें"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"रंग बदलने की सुविधा"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"वर्क प्रोफ़ाइल चालू करें?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"आपके काम से जुड़े ऐप्लिकेशन, सूचनाएं, डेटा और वर्क प्रोफ़ाइल से जुड़ी दूसरी सुविधाएं चालू हो जाएंगी"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"चालू करें"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"यह ऐप्लिकेशन Android के पुराने वर्शन के लिए बनाया गया था, इसलिए हो सकता है कि यह सही से काम न करे. देखें कि अपडेट मौजूद हैं या नहीं, या फिर डेवलपर से संपर्क करें."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"देखें कि अपडेट मौजूद है या नहीं"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"आपके पास नए संदेश हैं"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"बैटरी आम तौर पर जितने समय चलती है, उससे पहले खत्म हो सकती है"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"बैटरी लाइफ़ बढ़ाने के लिए \'बैटरी सेवर\' चालू हो गया है"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"बैटरी सेवर"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"जब तक कि बैटरी फिर से कम नहीं हो जाती, तब तक बैटरी सेवर फिर से चालू नहीं होगा"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"बैटरी ज़रूरत भर की चार्ज हो गई है. जब तक कि बैटरी फिर से कम नहीं हो जाती, तब तक बैटरी सेवर फिर से चालू नहीं होगा."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"फ़ोन <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> चार्ज हो गया"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"टैबलेट <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> प्रतिशत चार्ज हो गया है"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"डिवाइस <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> चार्ज हो गया"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"बैटरी सेवर बंद है. सुविधाओं पर अब पाबंदी नहीं है."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"बैटरी सेवर बंद कर दिया गया है. सुविधाओं पर अब पाबंदी नहीं है."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"फ़ोल्डर"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android ऐप्लिकेशन"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"फ़ाइल"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 0b7a357..9737187 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -290,7 +290,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"pristupati kalendaru"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"slati i pregledavati SMS poruke"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Prostor za pohranu"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"Datoteke i mediji"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"pristupiti fotografijama, medijima i datotekama na vašem uređaju"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofon"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"snimati zvuk"</string>
@@ -316,6 +316,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Može dodirnuti, prijeći prstom, spojiti prste i izvoditi druge pokrete."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Pokreti za otisak prsta"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Može snimati pokrete izvršene na senzoru otiska prsta na uređaju."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Snimi zaslon"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Možete napraviti snimku zaslona."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"onemogućavanje ili izmjena trake statusa"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Aplikaciji omogućuje onemogućavanje trake statusa ili dodavanje i uklanjanje sistemskih ikona."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"biti traka statusa"</string>
@@ -1632,6 +1634,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Želite li pojačati zvuk iznad preporučene razine?\n\nDugotrajno slušanje glasne glazbe može vam oštetiti sluh."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Želite li upotrebljavati prečac za pristupačnost?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Kada je taj prečac uključen, pritiskom na obje tipke za glasnoću na 3 sekunde pokrenut će se značajka pristupačnosti.\n\n Trenutačna značajka pristupačnosti:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Značajku možete promijeniti u Postavkama &gt; Pristupačnost."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Prazno"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Uredi prečace"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Otkaži"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Isključi prečac"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Upotrijebi prečac"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Inverzija boja"</string>
@@ -1877,6 +1882,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Želite uključiti radni profil?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Uključit će se vaše radne aplikacije, obavijesti, podaci i druge značajke radnog profila"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Uključi"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacija nije dostupna"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> trenutačno nije dostupna."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ova je aplikacija razvijena za stariju verziju Androida i možda neće funkcionirati pravilno. Potražite ažuriranja ili se obratite razvojnom programeru."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Provjeri ažuriranja"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Imate nove poruke"</string>
@@ -1988,13 +1995,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Baterija se može isprazniti prije uobičajenog vremena punjenja"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Štednja baterije aktivirana je kako bi se produljilo trajanje baterije"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Štednja baterije"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Štednja baterije neće se ponovo aktivirati dok baterija opet ne postane slaba"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Baterija je napunjena do zadovoljavajuće razine. Štednja baterije neće se ponovo aktivirati dok baterija opet ne postane slaba."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Telefon je napunjen <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Tablet je napunjen <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Uređaj je napunjen <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Isključena je Štednja baterije. Značajke više nisu ograničene."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Isključena je Štednja baterije. Značajke više nisu ograničene."</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Isključena je Štednja baterije"</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Baterija mobilnog telefona dovoljno je napunjena. Značajke više nisu ograničene."</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"Baterija tableta dovoljno je napunjena. Značajke više nisu ograničene."</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"Baterija uređaja dovoljno je napunjena. Značajke više nisu ograničene."</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"Mapa"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android aplikacija"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Datoteka"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index cc53c7b..4884cf7 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -287,7 +287,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"hozzáférés a naptárhoz"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS-ek küldése és megtekintése"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Tárhely"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"Fájlok és média"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"az eszközön lévő fotók, médiatartalmak és fájlok elérése"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofon"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"hanganyag rögzítése"</string>
@@ -313,6 +313,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Koppintás, ujjak gyors csúsztatása és összehúzása, illetve egyéb kézmozdulatok végrehajtása."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Kézmozdulatok az ujjlenyomat-érzékelőn"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Érzékeli az eszköz ujjlenyomat-érzékelőjén végzett kézmozdulatokat."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Képernyőkép készítése"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Készíthet képernyőképet a kijelzőről."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"állapotsor kikapcsolása vagy módosítása"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Lehetővé teszi az alkalmazás számára az állapotsor kikapcsolását, illetve rendszerikonok hozzáadását és eltávolítását."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"az állapotsor szerepének átvétele"</string>
@@ -1610,6 +1612,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Az ajánlott szint fölé szeretné emelni a hangerőt?\n\nHa hosszú időn át teszi ki magát nagy hangerőnek, azzal károsíthatja a hallását."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Szeretné használni a Kisegítő lehetőségek billentyűparancsot?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Ha be van kapcsolva a billentyűparancs, a két hangerőgomb 3 másodpercig tartó lenyomásával elindíthatja a kisegítő lehetőségek egyik funkcióját.\n\n A kisegítő lehetőségek jelenleg beállított funkciója:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n A funkciót a Beállítások &gt; Kisegítő lehetőségek menüpontban módosíthatja."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Üres"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Gyorsparancsszerkesztés"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Mégse"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Billentyűparancs kikapcsolása"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Billentyűparancs használata"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Színek invertálása"</string>
@@ -1845,6 +1850,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Bekapcsolja a munkaprofilt?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"A munkahelyi alkalmazások, értesítések, adatok és a munkaprofilhoz tartozó egyéb funkciók be lesznek kapcsolva"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Bekapcsolás"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Az alkalmazás nem hozzáférhető"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> jelenleg nem hozzáférhető."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ez az alkalmazás az Android egyik korábbi verziójához készült, így elképzelhető, hogy nem működik majd megfelelően ezen a rendszeren. Keressen frissítéseket, vagy vegye fel a kapcsolatot a fejlesztővel."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Frissítés keresése"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Új üzenetei érkeztek"</string>
@@ -1955,13 +1962,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Előfordulhat, hogy az akkumulátor lemerül a szokásos töltési időszak előtt"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Akkumulátorkímélő mód aktiválva az akkumulátor üzemidejének növelése érdekében"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Akkumulátorkímélő mód"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Az Akkumulátorkímélő mód nem aktiválódik újra addig, amíg az akkumulátor közel nem kerül a lemerüléshez"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Az akkumulátor töltöttségi szintje elégséges. Az Akkumulátorkímélő mód nem aktiválódik újra addig, amíg az akkumulátor közel nem kerül a lemerüléshez."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"A telefon töltöttségi szintje: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"A táblagép töltöttségi szintje: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Az eszköz töltöttségi szintje: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Akkumulátorkímélő mód kikapcsolva. A funkciók használata most már nincs korlátozva."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Akkumulátorkímélő mód kikapcsolva. A funkciók használata most már nincs korlátozva."</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Akkumulátorkímélő mód kikapcsolva"</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"A telefon töltöttsége elegendő. A funkciók használata már nincs korlátozva."</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"A táblagép töltöttsége elegendő. A funkciók használata már nincs korlátozva."</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"Az eszköz töltöttsége elegendő. A funkciók használata már nincs korlátozva."</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"Mappa"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android-alkalmazás"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Fájl"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index e693298..7491539 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"օգտագործել օրացույցը"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"ուղարկել և դիտել SMS-ները"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Տարածք"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"օգտագործել լուսանկարները, մեդիա ֆայլերը և ձեր սարքում պահվող մյուս ֆայլերը"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Խոսափող"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"ձայնագրել"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Կարող է հպել, թերթել, պտղունցել և կատարել այլ ժեստեր:"</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Մատնահետքերի սկաների ժեստեր"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Կարող է արձանագրել մատնահետքերի սկաների վրա կատարվող ժեստերը"</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"անջատել կամ փոփոխել կարգավիճակի գոտին"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Թույլ է տալիս հավելվածին անջատել կարգավիճակի գոտին կամ ավելացնել ու հեռացնել համակարգի պատկերակները:"</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"լինել կարգավիճակի գոտի"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Ձայնը բարձրացնե՞լ խորհուրդ տրվող մակարդակից ավել:\n\nԵրկարատև բարձրաձայն լսելը կարող է վնասել ձեր լսողությունը:"</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Օգտագործե՞լ Մատչելիության դյուրանցումը։"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Հատուկ գործառույթն օգտագործելու համար սեղմեք և 3 վայրկյան սեղմած պահեք ձայնի ուժգնության երկու կոճակները, երբ գործառույթը միացված է։\n\n Մատչելիության ակտիվ գործառույթը՝\n<xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Գործառույթը կարող եք փոփոխել՝ անցնելով Կարգավորումներ &gt; Հատուկ գործառույթներ։"</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Դատարկ"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Փոփոխել դյուրանցումները"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Չեղարկել"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Անջատել դյուրանցումը"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Օգտագործել դյուրանցումը"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Գունաշրջում"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Միացնե՞լ աշխատանքային պրոֆիլը"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Ձեր աշխատանքային հավելվածները, ծանուցումները, տվյալները և աշխատանքային պրոֆիլի մյուս գործառույթները կմիանան"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Միացնել"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Այս հավելվածը ստեղծվել է Android-ի ավելի հին տարբերակի համար և կարող է պատշաճ չաշխատել: Ստուգեք թարմացումների առկայությունը կամ դիմեք մշակողին:"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Ստուգել նոր տարբերակի առկայությունը"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Դուք ունեք նոր հաղորդագրություններ"</string>
@@ -1858,7 +1870,7 @@
     <string name="pin_specific_target" msgid="7824671240625957415">"Ամրացնել <xliff:g id="LABEL">%1$s</xliff:g> հավելվածը"</string>
     <string name="unpin_target" msgid="3963318576590204447">"Ապամրացնել"</string>
     <string name="unpin_specific_target" msgid="3859828252160908146">"Ապամրացնել <xliff:g id="LABEL">%1$s</xliff:g> հավելվածը"</string>
-    <string name="app_info" msgid="6113278084877079851">"Հավելվածի տվյալներ"</string>
+    <string name="app_info" msgid="6113278084877079851">"Հավելվածի մասին"</string>
     <string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="demo_starting_message" msgid="6577581216125805905">"Ցուցադրական օգտատերը գործարկվում է…"</string>
     <string name="demo_restarting_message" msgid="1160053183701746766">"Սարաքը վերակայվում է…"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Մարտկոցի լիցքը կարող է սովորականից շուտ սպառվել"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Մարտկոցի կյանքը երկարացնելու համար ակտիվացվել է մարտկոցի տնտեսման ռեժիմը"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Մարտկոցի տնտեսում"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Մարտկոցի տնտեսումը նորից կմիանա, երբ մարտկոցի լիցքը ցածր լինի"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Մարտկոցը բավարար լիցքավորված է։ Մարտկոցի տնտեսման ռեժիմը նորից կմիանա, երբ մարտկոցի լիցքը ցածր լինի։"</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Հեռախոսի լիցքը՝ <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Պլանշետի լիցքը՝ <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Սարքի լիցքը՝ <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Մարտկոցի տնտեսման ռեժիմն անջատված է: Գործառույթներն այլևս չեն սահմանափակվում։"</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Մարտկոցի տնտեսումն անջատված է։ Գործառույթներն այլևս չեն սահմանափակվում։"</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"Պանակ"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android հավելված"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Ֆայլ"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 299f23b..9cfe6d9 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -287,7 +287,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"mengakses kalender"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"mengirim dan melihat pesan SMS"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Penyimpanan"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"File dan media"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"mengakses foto, media, dan file di perangkat"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofon"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"merekam audio"</string>
@@ -313,6 +313,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Dapat mengetuk, menggeser, mencubit, dan melakukan isyarat lainnya."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Gestur sidik jari"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Dapat merekam gestur yang dilakukan di sensor sidik jari perangkat."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Ambil screenshot"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Dapat mengambil screenshot tampilan."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"nonaktifkan atau ubah bilah status"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Mengizinkan apl menonaktifkan bilah status atau menambah dan menghapus ikon sistem."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"jadikan bilah status"</string>
@@ -1610,6 +1612,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Mengeraskan volume di atas tingkat yang disarankan?\n\nMendengarkan dengan volume keras dalam waktu yang lama dapat merusak pendengaran Anda."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Gunakan Pintasan Aksesibilitas?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Saat pintasan aktif, menekan kedua tombol volume selama 3 detik akan memulai fitur aksesibilitas.\n\n Fitur aksesibilitas saat ini:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Anda dapat mengubah fitur di Setelan &gt; Aksesibilitas."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Kosong"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Edit pintasan"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Batal"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Nonaktifkan Pintasan"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Gunakan Pintasan"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Inversi Warna"</string>
@@ -1845,6 +1850,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Aktifkan profil kerja?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Aplikasi kerja, notifikasi, data, dan fitur profil kerja lainnya akan diaktifkan"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Aktifkan"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Aplikasi tidak tersedia"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> tidak tersedia saat ini."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Aplikasi ini dibuat untuk Android versi lama dan mungkin tidak berfungsi sebagaimana mestinya. Coba periksa apakah ada update, atau hubungi developer."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Periksa apakah ada update"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Ada pesan baru"</string>
@@ -1955,13 +1962,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Baterai mungkin habis sebelum pengisian daya biasanya"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Penghemat Baterai diaktifkan untuk memperpanjang masa pakai baterai"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Penghemat Baterai"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Penghemat Baterai tidak akan aktif lagi sampai baterai lemah kembali"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Baterai telah terisi ke tingkat yang memadai. Penghemat Baterai tidak akan aktif lagi sampai baterai lemah kembali."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Ponsel terisi <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Tablet terisi <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Perangkat terisi <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Penghemat Baterai nonaktif. Fitur tidak lagi dibatasi."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Penghemat Baterai dinonaktifkan. Fitur tidak lagi dibatasi."</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Penghemat Baterai dinonaktifkan"</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Baterai ponsel cukup terisi. Fitur tidak lagi dibatasi."</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"Baterai tablet cukup terisi. Fitur tidak lagi dibatasi."</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"Baterai perangkat cukup terisi. Fitur tidak lagi dibatasi."</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"Folder"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Aplikasi Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"File"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index fe55b4c..f7c3b5c 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -287,7 +287,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"fá aðgang að dagatalinu þínu"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"senda og skoða SMS-skilaboð"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Geymslurými"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"Skrár og margmiðlunarefni"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"fá aðgang að myndum, efni og skrám í tækinu"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Hljóðnemi"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"taka upp hljóð"</string>
@@ -313,6 +313,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Getur ýtt, strokið, fært fingur saman og gert ýmsar aðrar bendingar."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Fingrafarabendingar"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Getur fangað bendingar sem eru gerðar á fingrafaralesara tækisins."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Taka skjámynd"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Getur tekið skjámynd af skjánum."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"slökkva á eða breyta stöðustiku"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Leyfir forriti að slökkva á stöðustikunni eða bæta við og fjarlægja kerfistákn."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"vera stöðustikan"</string>
@@ -1610,6 +1612,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Hækka hljóðstyrk umfram ráðlagðan styrk?\n\nEf hlustað er á háum hljóðstyrk í langan tíma kann það að skaða heyrnina."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Viltu nota aðgengisflýtileið?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Þegar flýtileiðin er virk er kveikt á aðgengiseiginleikanum með því að halda báðum hljóðstyrkshnöppunum inni í þrjár sekúndur.\n\n Virkur aðgengiseiginleiki:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Hægt er að skipta um eiginleika í Stillingar &gt; Aðgengi."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Autt"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Breyta flýtileiðum"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Hætta við"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Slökkva á flýtileið"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Nota flýtileið"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Umsnúningur lita"</string>
@@ -1845,6 +1850,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Kveikja á vinnusniði?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Kveikt verður á vinnuforritum, tilkynningum, gögnum og öðrum eiginleikum vinnusniðsins"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Kveikja"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Forrit er ekki tiltækt"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> er ekki tiltækt núna."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Þetta forrit var hannað fyrir eldri útgáfu af Android og ekki er víst að það virki eðlilega. Athugaðu hvort uppfærslur séu í boði eða hafðu samband við þróunaraðilann."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Leita að uppfærslu"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Þú ert með ný skilaboð"</string>
@@ -1955,13 +1962,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Rafhlaðan kann að tæmast áður en hún kemst í hleðslu"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Kveikt á rafhlöðusparnaði til að lengja endingu rafhlöðunnar"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Rafhlöðusparnaður"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Rafhlöðusparnaður verður ekki virkur þar til næst þegar lítil hleðsla er á rafhlöðunni"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Rafhlaðan hefur fengið nægilega hleðslu. Rafhlöðusparnaður verður ekki virkur aftur fyrr en hleðslan verður lítil aftur."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Síminn er með <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> hleðslu"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Hleðsla spjaldtölvunnar er <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Tækið er með <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> hleðslu"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Slökkt er á rafhlöðusparnaði. Eiginleikar eru ekki lengur takmarkaðir."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Slökkt á rafhlöðusparnaði. Eiginleikar eru ekki lengur takmarkaðir."</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Slökkt á rafhlöðusparnaði"</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Síminn er með næga hleðslu. Eiginleikar eru ekki lengur takmarkaðir."</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"Spjaldtölva er með næga hleðslu. Eiginleikar eru ekki lengur takmarkaðir."</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"Tæki er með næga hleðslu. Eiginleikar eru ekki lengur takmarkaðir."</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"Mappa"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android forrit"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Skrá"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 87a1191..387d52e 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -287,7 +287,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"accedere al calendario"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"inviare e visualizzare SMS"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Archiviazione"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"File e contenuti multimediali"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"accedere a foto, contenuti multimediali e file sul dispositivo"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Microfono"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"registrare audio"</string>
@@ -313,6 +313,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Consente di toccare, far scorrere, pizzicare ed eseguire altri gesti."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Gesti con sensore di impronte"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"È in grado di rilevare i gesti compiuti con il sensore di impronte dei dispositivi."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Acquisire screenshot"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Può acquisire uno screenshot del display."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"disattivazione o modifica della barra di stato"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Consente all\'applicazione di disattivare la barra di stato o di aggiungere e rimuovere icone di sistema."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"ruolo di barra di stato"</string>
@@ -1610,6 +1612,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Vuoi aumentare il volume oltre il livello consigliato?\n\nL\'ascolto ad alto volume per lunghi periodi di tempo potrebbe danneggiare l\'udito."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Usare la scorciatoia Accessibilità?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Quando la scorciatoia è attiva, puoi premere entrambi i pulsanti del volume per tre secondi per avviare una funzione di accessibilità.\n\n Funzione di accessibilità corrente:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Puoi cambiare la funzione in Impostazioni &gt; Accessibilità."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Svuota"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Modifica scorciatoie"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Annulla"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Disattiva scorciatoia"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Usa scorciatoia"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Inversione colori"</string>
@@ -1845,6 +1850,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Attivare il profilo di lavoro?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Le tue app di lavoro, le notifiche, i dati e altri elementi del profilo di lavoro saranno attivati."</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Attiva"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"L\'app non è disponibile"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"L\'app <xliff:g id="APP_NAME">%1$s</xliff:g> non è al momento disponibile."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Questa app è stata realizzata per una versione precedente di Android e potrebbe non funzionare correttamente. Prova a verificare la disponibilità di aggiornamenti o contatta lo sviluppatore."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Verifica la presenza di aggiornamenti"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Hai nuovi messaggi"</string>
@@ -1955,13 +1962,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"La batteria potrebbe esaurirsi prima della ricarica abituale"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Risparmio energetico attivo per far durare di più la batteria"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Risparmio energetico"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Risparmio energetico non si riattiverà finché la batteria non raggiungerà nuovamente un livello di carica basso"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"La batteria ha raggiunto un livello di carica sufficiente. Risparmio energetico non si riattiverà finché la batteria non raggiungerà nuovamente un livello di carica basso"</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Livello di carica del telefono: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Livello di carica del tablet: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Livello di carica del dispositivo: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Il risparmio energetico è disattivato. Funzionalità non più limitate."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Risparmio energetico disattivato. Funzionalità non più limitate."</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Risparmio energetico disattivato"</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Il telefono ha carica sufficiente. Funzionalità non più limitate."</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"Il tablet ha carica sufficiente. Funzionalità non più limitate."</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"Il dispositivo ha carica sufficiente. Funzionalità non più limitate."</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"Cartella"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Applicazione Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"File"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index f4624ba..cf6fa22 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -293,7 +293,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"גישה אל היומן"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"‏שליחה והצגה של הודעות SMS"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"אחסון"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"גישה לתמונות, למדיה ולקבצים במכשיר שלך"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"מיקרופון"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"הקלטת אודיו"</string>
@@ -319,6 +320,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"יכול להקיש, להחליק, לעשות תנועת צביטה ולבצע תנועות אחרות."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"תנועות של טביעות אצבעות"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"אפשרות לזהות תנועות בזמן נגיעה בחיישן טביעות האצבע של המכשיר."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"השבת או שנה את שורת המצב"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"מאפשר לאפליקציה להשבית את שורת המצב או להוסיף ולהסיר סמלי מערכת."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"להיות שורת הסטטוס"</string>
@@ -1654,6 +1659,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"האם להעלות את עוצמת הקול מעל לרמה המומלצת?\n\nהאזנה בעוצמת קול גבוהה למשכי זמן ממושכים עלולה לפגוע בשמיעה."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"להשתמש בקיצור הדרך לתכונת הנגישות?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"כשקיצור הדרך מופעל, לחיצה על שני לחצני עוצמת השמע למשך שלוש שניות מפעילה את תכונת הנגישות.\n\n תכונת הנגישות המוגדרת כרגע:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n אפשר לשנות את התכונה בקטע \'הגדרות ונגישות\'."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"ריק"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"עריכת קיצורי הדרך"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"ביטול"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"כבה את קיצור הדרך"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"השתמש בקיצור הדרך"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"היפוך צבעים"</string>
@@ -1909,6 +1917,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"להפעיל את פרופיל העבודה?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"אפליקציות העבודה, התראות, נתונים ותכונות נוספות של פרופיל העבודה יופעלו"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"הפעל"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"‏האפליקציה הזו עוצבה לגרסה ישנה יותר של Android וייתכן שלא תפעל כראוי. ניתן לבדוק אם יש עדכונים או ליצור קשר עם המפתח."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"האם יש עדכון חדש?"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"יש לך הודעות חדשות"</string>
@@ -2021,13 +2033,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"הסוללה עלולה להתרוקן לפני המועד הרגיל של הטעינה"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"תכונת החיסכון בסוללה הופעלה כדי להאריך את חיי הסוללה"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"חיסכון בסוללה"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"\'חיסכון בסוללה\' יופעל שוב רק כשהסוללה תהיה שוב חלשה"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"רמת הטעינה של הסוללה מספיקה. \'חיסכון בסוללה\' יופעל מחדש רק כשהסוללה תהיה שוב חלשה."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"הטלפון טעון בשיעור <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"הטאבלט טעון בשיעור <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"המכשיר טעון בשיעור <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"\'חיסכון בסוללה\' כבוי. התכונות כבר לא מוגבלות."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"\'חיסכון בסוללה\' כבוי. התכונות כבר לא מוגבלות."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"תיקייה"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"‏אפליקציית Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"קובץ"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 79d9faa..e7f5e85 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"カレンダーへのアクセス"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"SMSメッセージの送信と表示"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"ストレージ"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"デバイス内の写真、メディア、ファイルへのアクセス"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"マイク"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"音声の録音"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"タップ、スワイプ、ピンチ、その他の操作を行えます。"</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"指紋認証センサーでの操作"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"デバイスの指紋認証センサーで行われた操作をキャプチャできます。"</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"ステータスバーの無効化や変更"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"ステータスバーの無効化、システムアイコンの追加や削除をアプリに許可します。"</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"ステータスバーへの表示"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"推奨レベルを超えるまで音量を上げますか?\n\n大音量で長時間聞き続けると、聴力を損なう恐れがあります。"</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ユーザー補助機能のショートカットの使用"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"ショートカットが ON の場合、両方の音量ボタンを 3 秒間押し続けるとユーザー補助機能が起動します。\n\n現在のユーザー補助機能:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\nユーザー補助機能は [設定] &gt; [ユーザー補助] で変更できます。"</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"空"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"ショートカットの編集"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"キャンセル"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"ショートカットを OFF にする"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"ショートカットを使用"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"色反転"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"仕事用プロファイルの有効化"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"仕事用のアプリ、通知、データなど、仕事用プロファイルの機能が ON になります"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"ON にする"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"このアプリは以前のバージョンの Android 用に作成されており、正常に動作しない可能性があります。アップデートを確認するか、デベロッパーにお問い合わせください。"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"アップデートを確認"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"新着メッセージがあります"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"通常の充電を行う前に電池が切れる可能性があります"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"電池を長持ちさせるため、バッテリー セーバーが有効になりました"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"バッテリー セーバー"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"バッテリー セーバーは、電池残量が再び低下するまで有効に戻りません。"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"電池が十分なレベルまで充電されました。バッテリー セーバーは、電池残量が再び低下するまで有効に戻りません。"</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"スマートフォンの電池残量 <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"タブレットの電池残量 <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"デバイスの電池残量 <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"バッテリー セーバーが無効になりました。機能は制限されなくなりました。"</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"バッテリー セーバーが無効になりました。機能は制限されなくなりました。"</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"フォルダ"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android アプリ"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"ファイル"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index a0f631e4..5d4a560 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"თქვენს კალენდარზე წვდომა"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS შეტყობინებების გაგზავნა და ნახვა"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"შესანახი სივრცე"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"თქვენს მოწყობილობაზე არსებულ ფოტოებზე, მედიასა და ფაილებზე წვდომა"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"მიკროფონი"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"აუდიოს ჩაწერა"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"შეუძლია შეხება, გადაფურცვლა, მასშტაბირება და სხვა ჟესტების შესრულება."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"თითის ანაბეჭდის ჟესტები"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"შეუძლია აღბეჭდოს მოწყობილობის თითის ანაბეჭდის სენსორზე განხორციელებული ჟესტები."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"სტატუსის ზოლის გათიშვა ან ცვლილება"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"აპს შეეძლება სტატუსების ზოლის გათიშვა და სისტემის ხატულების დამატება/წაშლა."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"სტატუსის ზოლის ჩანაცვლება"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"გსურთ ხმის რეკომენდებულ დონეზე მაღლა აწევა?\n\nხანგრძლივად ხმამაღლა მოსმენით შესაძლოა სმენადობა დაიზიანოთ."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"გსურთ მარტივი წვდომის მალსახმობის გამოყენება?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"მალსახმობის ჩართვის შემთხვევაში, ხმის ორივე ღილაკზე 3 წამის განმავლობაში დაჭერით მარტივი წვდომის ფუნქცია ჩაირთვება.\n\n მარტივი წვდომის ამჟამინდელი ფუნქციაა:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n ამ ფუნქციის შეცვლა შეგიძლიათ აქ: პარამეტრები &gt; მარტივი წვდომა."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"ცარიელი"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"მალსახმობების რედაქტირება"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"გაუქმება"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"მალსახმობის გამორთვა"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"მალსახმობის გამოყენება"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"ფერთა ინვერსია"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"ჩაირთოს სამსახურის პროფილი?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"თქვენი სამსახურის აპები, შეტყობინებები, მონაცემები და სამსახურის პროფილის ყველა სხვა ფუნქცია ჩაირთვება"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"ჩართვა"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ეს აპი Android-ის ძველი ვერსიისთვის შეიქმნა და შესაძლოა სათანადოდ არ მუშაობდეს. გადაამოწმეთ განახლებები ან დაუკავშირდით დეველოპერს."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"განახლების შემოწმება"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"თქვენ ახალი შეტყობინებები გაქვთ"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"ბატარეა შეიძლება დაჯდეს დატენის ჩვეულ დრომდე"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"ბატარეის დამზოგი გააქტიურდა ბატარეის მუშაობის გასახანგრძლივლებლად"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"ბატარეის დამზოგი"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"ბატარეის დამზოგი ხელახლა არ გააქტიურდება, სანამ ბატარეა ისევ არ დაიცლება"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"ბატარეა საკმარისად არის დატენილი. ბატარეის დამზოგი ხელახლა არ გააქტიურდება, სანამ ბატარეა ისევ არ დაიცლება."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"ტელეფონი <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> დატენილია"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"ტაბლეტი <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> დატენილია"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"მოწყობილობა <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> დატენილია"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"ბატარეის დამზოგი გამორთულია. ფუნქციები შეზღუდული აღარ არის."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"ბატარეის დამზოგი გამორთულია. ფუნქციები შეზღუდული აღარ არის."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"საქაღალდე"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android-ის აპლიკაცია"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"ფაილი"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index e6562c4..2acbb17 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"күнтізбеге кіру"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS хабарларын жіберу және көру"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Жад"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"құрылғыдағы фотосуреттерге, мультимедиаға және файлдарға қол жеткізу"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Микрофон"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"аудио жазу"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Түртуге, сырғытуға, қысуға және басқа қимылдарды орындауға болады."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Саусақ ізі сканеріндегі қимылдар"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Құрылғының саусақ ізі сенсорында орындалған қимылдарды сақтайды."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"күйін көрсету тақтасын өшіру немесе өзгерту"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Қолданбаға күй жолағын өшіруге немесе жүйелік белгішелерді қосуға және жоюға рұқсат береді."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"күй жолағы болу"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Дыбыс деңгейін ұсынылған деңгейден көтеру керек пе?\n\nЖоғары дыбыс деңгейінде ұзақ кезеңдер бойы тыңдау есту қабілетіңізге зиян тигізуі мүмкін."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Арнайы мүмкіндік төте жолын пайдалану керек пе?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Бұл төте жол қосулы кезде дыбыс деңгейі түймелерінің екеуін де 3 секунд бойы басқанда арнайы мүмкіндік іске қосылады.\n\n Ағымдағы арнайы мүмкіндік:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Бұл мүмкіндікті \"Параметрлер\" &gt; \"Арнайы мүмкіндіктер\" тармағында өзгертуге болады."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Бос"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Таңбашаларды өзгерту"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Бас тарту"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Төте жолды өшіру"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Төте жолды пайдалану"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Түстер инверсиясы"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Жұмыс профилі қосылсын ба?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Жұмыс қолданбалары, хабарландырулар, деректер және басқа да жұмыс профильдерінің мүмкіндіктері қосылады"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Қосу"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Қолданба Android жүйесінің ескі нұсқасына арналған және дұрыс жұмыс істемеуі мүмкін. Жаңартылған нұсқаны тексеріңіз немесе әзірлеушіге хабарласыңыз."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Жаңартылған нұсқаны тексеру"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Сізде жаңа хабарлар бар"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Батарея заряды азаюы мүмкін"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Батарея ұзаққа жетуі үшін, Battery Saver іске қосылды"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Battery Saver"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Батарея қуаты азаймайынша, Battery Saver қайта қосылмайды."</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Батарея сәйкес деңгейге дейін зарядталды. Батарея қуаты азаймайынша, Battery Saver қайта қосылмайды."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Телефон батареясының қуат деңгейі: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Планшет батареясының қуат деңгейі: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Құрылғының батарея қуатының деңгейі: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Battery Saver өшірулі. Функцияларға енді шектеу қойылмайды."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Battery Saver өшірілді. Функцияларға енді шектеу қойылмайды."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"Қалта"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android қолданбасы"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Файл"</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index e6397ad..0e277ef 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"ចូលប្រើប្រិតិទិនរបស់អ្នក"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"សារ SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"ផ្ញើ និងមើលសារ SMS"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"ទំហំ​ផ្ទុក"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"ចូលដំណើការរូបភាព មេឌៀ និងឯកសារនៅលើឧបករណ៍របស់អ្នក"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"មីក្រូ​ហ្វូន"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"ថតសំឡេង"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"អាចប៉ះ អូស ច្បិច និងធ្វើកាយវិការផ្សេងទៀត"</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"ចលនា​ស្នាមម្រាមដៃ"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"អាចចាប់យក​ចលនា​ដែលធ្វើនៅលើ​នៅលើ​ឧបករណ៍​ចាប់​ស្នាម​ម្រាមដៃ​របស់ឧបករណ៍បាន។"</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"បិទ ឬ​កែ​របារ​ស្ថានភាព"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"ឲ្យ​កម្មវិធី​បិទ​របារ​ស្ថានភាព ឬ​បន្ថែម និង​លុប​រូប​តំណាង​ប្រព័ន្ធ។"</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"ធ្វើជារបារស្ថានភាព"</string>
@@ -1612,6 +1617,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"បង្កើន​កម្រិត​សំឡេង​លើស​ពី​កម្រិត​បាន​ផ្ដល់​យោបល់?\n\nការ​ស្ដាប់​នៅ​កម្រិត​សំឡេង​ខ្លាំង​យូរ​អាច​ធ្វើឲ្យ​ខូច​ត្រចៀក។"</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ប្រើប្រាស់​ផ្លូវកាត់​ភាព​ងាយស្រួល?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"នៅពេល​ផ្លូវកាត់​នេះបើក ការ​ចុច​ប៊ូតុង​កម្រិត​សំឡេង​ទាំង​ពីរ​ឲ្យ​ជាប់​រយៈពេល​ 3 វិនាទីនឹង​ចាប់ផ្តើម​មុខងារ​ភាពងាយស្រួល។\n\n មុខងារ​ភាពងាយស្រួល​បច្ចុប្បន្ន៖\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n អ្នក​អាច​ផ្លាស់​ប្តូរ​មុខងារ​នេះ​បាន​នៅក្នុង​ការ កំណត់ &gt; ភាព​ងាយស្រួល។"</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"លុបទាំងអស់"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"កែ​ផ្លូវកាត់"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"បោះបង់"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"បិទ​ផ្លូវកាត់"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"ប្រើប្រាស់​ផ្លូវកាត់"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"បញ្ច្រាស​ពណ៌"</string>
@@ -1847,6 +1855,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"បើក​កម្រង​ព័ត៌មាន​ការ​ងារ?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"កម្មវិធី​ការងារ ការ​ជូនដំណឹង ទិន្នន័យ និង​មុខងារ​កម្រង​ព័ត៌មាន​ការងារ​ផ្សេង​ទៀត​របស់អ្នក​នឹង​ត្រូវ​បាន​បើក"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"បើក"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"កម្មវិធី​នេះ​ត្រូវបាន​បង្កើត​ឡើង​សម្រាប់​កំណែ​ប្រព័ន្ធ​ប្រតិបត្តិការ Android ចាស់ ហើយ​វាអាច​ដំណើរការ​ខុសប្រក្រតី។ សូម​សាកល្បង​ពិនិត្យមើល​កំណែ​ថ្មី ឬ​ទាក់ទង​ទៅអ្នក​អភិវឌ្ឍន៍។"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"រក​មើល​កំណែ​ថ្មី"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"អ្នកមានសារថ្មី"</string>
@@ -1957,13 +1969,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"ថ្ម​អាច​នឹង​អស់ មុនពេល​សាកថ្មធម្មតា"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"បាន​បើក​ដំណើរការកម្មវិធី​សន្សំ​ថ្ម ដើម្បីបង្កើនកម្រិត​ថាមពល​​ថ្ម"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"កម្មវិធីសន្សំថ្ម"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"កម្មវិធី​សន្សំ​ថ្មនឹង​មិន​បើក​ដំណើរការឡើងវិញ​ទេ រហូតទាល់តែ​ថ្មមានកម្រិតទាប​ម្ដងទៀត"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"ថ្ម​បាន​សាក​ដល់​កម្រិត​គ្រប់គ្រាន់ហើយ។ កម្មវិធី​សន្សំ​ថ្មនឹង​មិន​បើក​ដំណើរការ​ឡើងវិញទេ រហូតទាល់តែ​ថ្មមានកម្រិតទាប​ម្ដងទៀត។"</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"បាន​សាកថ្មទូរសព្ទ <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"បាន​សាកថ្មថេប្លេត <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"បានសាកថ្មឧបករណ៍ <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"កម្មវិធី​សន្សំ​ថ្ម​បាន​បិទ។ មុខងារ​ផ្សេងៗ​មិន​ត្រូវបាន​រឹតបន្តឹងទៀត​ទេ។"</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"បានបិទកម្មវិធី​សន្សំ​ថ្ម។ មុខងារ​ផ្សេងៗ​មិន​ត្រូវបាន​រឹតបន្តឹងទៀត​ទេ។"</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"ថត"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"​កម្មវិធី Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"ឯកសារ"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 22edcae..50b1bba 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"ನಿಮ್ಮ ಕ್ಯಾಲೆಂಡರ್ ಪ್ರವೇಶಿಸಲು"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS ಸಂದೇಶಗಳನ್ನು ಕಳುಹಿಸಲು ಮತ್ತು ನಿರ್ವಹಿಸಲು"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"ಸಂಗ್ರಹಣೆ"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"ಸಾಧನದಲ್ಲಿ ಫೋಟೋಗಳು, ಮಾಧ್ಯಮ ಮತ್ತು ಫೈಲ್‌ಗಳನ್ನು ಪ್ರವೇಶಿಸಲು"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"ಮೈಕ್ರೋಫೋನ್‌"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"ಆಡಿಯೊ ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"ಟ್ಯಾಪ್ ಮಾಡಬಹುದು, ಸ್ವೈಪ್ ಮಾಡಬಹುದು, ಪಿಂಚ್ ಮಾಡಬಹುದು ಮತ್ತು ಇತರ ಗೆಸ್ಚರ್‌ಗಳನ್ನು ಮಾಡಬಹುದು."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"ಫಿಂಗರ್‌ಪ್ರಿಂಟ್‌ ಗೆಶ್ಚರ್‌ಗಳು"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"ಸಾಧನದ ಫಿಂಗರ್‌ಪ್ರಿಂಟ್ ಸೆನ್ಸರ್‌ನಲ್ಲಿ ನಡೆಸಿದ ಗೆಶ್ಚರ್‌ಗಳನ್ನು ಕ್ಯಾಪ್ಚರ್ ಮಾಡಿ."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"ಸ್ಥಿತಿ ಪಟ್ಟಿಯನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ ಇಲ್ಲವೇ ಮಾರ್ಪಡಿಸಿ"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"ಸ್ಥಿತಿ ಪಟ್ಟಿಯನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲು ಅಥವಾ ಸೇರಿಸಲು ಮತ್ತು ಸಿಸ್ಟಂ ಐಕಾನ್‌ಗಳನ್ನು ತೆಗೆದುಹಾಕಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅವಕಾಶ ನೀಡುತ್ತದೆ."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"ಸ್ಥಿತಿ ಪಟ್ಟಿಯಾಗಿರಲು"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ವಾಲ್ಯೂಮ್‌ ಅನ್ನು ಶಿಫಾರಸು ಮಾಡಲಾದ ಮಟ್ಟಕ್ಕಿಂತಲೂ ಹೆಚ್ಚು ಮಾಡುವುದೇ?\n\nದೀರ್ಘ ಅವಧಿಯವರೆಗೆ ಹೆಚ್ಚಿನ ವಾಲ್ಯೂಮ್‌ನಲ್ಲಿ ಆಲಿಸುವುದರಿಂದ ನಿಮ್ಮ ಆಲಿಸುವಿಕೆ ಸಾಮರ್ಥ್ಯಕ್ಕೆ ಹಾನಿಯುಂಟು ಮಾಡಬಹುದು."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ಪ್ರವೇಶಿಸುವಿಕೆ ಶಾರ್ಟ್‌ಕಟ್ ಬಳಸುವುದೇ?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"ಶಾರ್ಟ್‌ಕಟ್ ಆನ್ ಆಗಿರುವಾಗ ಪ್ರವೇಶಿಸುವಿಕೆ ವೈಶಿಷ್ಟ್ಯ ಆನ್ ಮಾಡಲು, ಎರಡೂ ವಾಲ್ಯೂಮ್ ಬಟನ್‌ಗಳನ್ನು ನೀವು 3 ಸೆಕೆಂಡುಗಳ ಕಾಲ ಒತ್ತಬೇಕು.\n\nಪ್ರಸ್ತುತ ಪ್ರವೇಶಿಸುವಿಕೆ ವೈಶಿಷ್ಟ್ಯ: \n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n ಸೆಟ್ಟಿಂಗ್‌ಗಳು ಮತ್ತು ಪ್ರವೇಶಿಸುವಿಕೆಯಲ್ಲಿ ನೀವು ವೈಶಿಷ್ಟ್ಯವನ್ನು ಬದಲಾಯಿಸಬಹುದು."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"ಖಾಲಿ"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"ಶಾರ್ಟ್‌ಕಟ್‌‍ಗಳನ್ನು ಎಡಿಟ್ ಮಾಡಿ"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"ರದ್ದುಗೊಳಿಸಿ"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"ಶಾರ್ಟ್‌ಕಟ್‌ ಆಫ್ ಮಾಡಿ"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"ಶಾರ್ಟ್‌ಕಟ್ ಬಳಸಿ"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"ಬಣ್ಣ ವಿಲೋಮ"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್‌ ಆನ್ ಮಾಡುವುದೇ?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"ನಿಮ್ಮ ಕೆಲಸದ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು, ಅಧಿಸೂಚನೆಗಳು, ಡೇಟಾ ಮತ್ತು ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್‌ನ ಇತರ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ಆನ್ ಮಾಡಲಾಗುತ್ತದೆ"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"ಆನ್‌ ಮಾಡಿ"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ಈ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು Android ನ ಹಳೆಯ ಆವೃತ್ತಿಗೆ ರಚಿಸಲಾಗಿದೆ ಮತ್ತು ಸರಿಯಾಗಿ ಕೆಲಸ ಮಾಡದಿರಬಹುದು. ಅಪ್‌ಡೇಟ್‌ಗಳನ್ನು ಪರಿಶೀಲಿಸಲು ಪ್ರಯತ್ನಿಸಿ ಅಥವಾ ಡೆವಲಪರ್ ಅನ್ನು ಸಂಪರ್ಕಿಸಿ."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"ಅಪ್‌ಡೇಟ್‌ಗಾಗಿ ಪರಿಶೀಲಿಸಿ"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"ನೀವು ಹೊಸ ಸಂದೇಶಗಳನ್ನು ಹೊಂದಿರುವಿರಿ"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"ಚಾರ್ಜ್‌ಗೆ ಮೊದಲೆ ಬ್ಯಾಟರಿ ಮುಗಿದು ಬಿಡಬಹುದು"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"ಬ್ಯಾಟರಿ ಅವಧಿ ಹೆಚ್ಚಿಸಲು ಬ್ಯಾಟರಿ ಸೇವರ್ ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"ಬ್ಯಾಟರಿ ಸೇವರ್"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"ಇನ್ನೊಮ್ಮೆ ಬ್ಯಾಟರಿ ಕಡಿಮೆಯಾಗುವವರೆಗೂ ಬ್ಯಾಟರಿ ಸೇವರ್ ಮರುಸಕ್ರಿಯವಾಗುವುದಿಲ್ಲ"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"ಬ್ಯಾಟರಿಯನ್ನು ಬೇಕಾಗಿರುವಷ್ಟು ಮಟ್ಟಕ್ಕೆ ಚಾರ್ಜ್ ಮಾಡಲಾಗಿದೆ. ಇನ್ನೊಮ್ಮೆ ಬ್ಯಾಟರಿ ಕಡಿಮೆಯಾಗುವವರೆಗೂ ಬ್ಯಾಟರಿ ಸೇವರ್ ಮರುಸಕ್ರಿಯವಾಗುವುದಿಲ್ಲ."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"ಫೋನ್ <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ಚಾರ್ಜ್ ಆಗಿದೆ"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"ಟ್ಯಾಬ್ಲೆಟ್ <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ಚಾರ್ಜ್ ಆಗಿದೆ"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"ಸಾಧನ <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ಚಾರ್ಜ್ ಆಗಿದೆ"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"ಬ್ಯಾಟರಿ ಸೇವರ್ ಆಫ್ ಆಗಿದೆ. ಇನ್ನು ಮುಂದೆ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗುವುದಿಲ್ಲ."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"ಬ್ಯಾಟರಿ ಸೇವರ್ ಅನ್ನು ಆಫ್ ಮಾಡಲಾಗಿದೆ. ಇನ್ನು ಮುಂದೆ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗುವುದಿಲ್ಲ."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"ಫೋಲ್ಡರ್"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android ಆ್ಯಪ್‌"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"ಫೈಲ್"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index df26c4e..7e12490 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"캘린더에 액세스"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS 메시지 전송 및 보기"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"저장용량"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"기기 사진, 미디어, 파일 액세스"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"마이크"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"오디오 녹음"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"탭, 스와이프, 확대/축소 및 기타 동작을 실행할 수 있습니다."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"지문 동작"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"기기 지문 센서에서 동작을 캡처합니다."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"상태 표시줄 사용 중지 또는 수정"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"앱이 상태 표시줄을 사용중지하거나 시스템 아이콘을 추가 및 제거할 수 있도록 허용합니다."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"상태 표시줄에 위치"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"권장 수준 이상으로 볼륨을 높이시겠습니까?\n\n높은 볼륨으로 장시간 청취하면 청력에 손상이 올 수 있습니다."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"접근성 단축키를 사용하시겠습니까?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"단축키가 사용 설정된 경우 두 개의 볼륨 버튼을 3초간 누르면 접근성 기능이 시작됩니다.\n\n 현재 접근성 기능:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n \'설정 &gt; 접근성\'에서 기능을 변경할 수 있습니다."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"비우기"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"단축키 수정"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"취소"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"단축키 사용 중지"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"단축키 사용"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"색상 반전"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"직장 프로필을 사용 설정하시겠어요?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"업무용 앱, 알림, 데이터 및 기타 직장 프로필 기능이 사용 설정됩니다."</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"사용 설정"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"이 앱은 Android 이전 버전에 맞게 개발되었기 때문에 제대로 작동하지 않을 수 있습니다. 업데이트를 확인하거나 개발자에게 문의하세요."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"업데이트 확인"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"새 메시지 있음"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"평소에 충전하는 시간 전에 배터리가 소진될 수 있습니다."</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"배터리 수명을 연장하기 위해 배터리 세이버가 활성화되었습니다."</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"절전 모드"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"배터리가 다시 부족해지기 전까지 절전 모드가 재활성화되지 않음"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"배터리가 충분히 충전되었습니다. 배터리가 다시 부족해지기 전까지는 절전 모드가 재활성화되지 않습니다."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"휴대전화 <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> 충전됨"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"태블릿 <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> 충전됨"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"기기 <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> 충전됨"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"절전 모드가 꺼져 있습니다. 기능이 더 이상 제한되지 않습니다."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"절전 모드가 사용 중지되었습니다. 기능이 더 이상 제한되지 않습니다."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"폴더"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android 애플리케이션"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"파일"</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index adcc4c1..c887c34 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"жылнаамаңызды пайдалануу"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS билдирүүлөрдү жиберүү жана көрсөтүү"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Сактагыч"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"түзмөгүңүздөгү сүрөттөрдү жана башка мультимедиа файлдарын пайдаланууга"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Микрофон"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"аудио жаздыруу"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Таптап, серпип, чымчып жана башка жаңсоолорду аткара алат."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Манжа изинин жаңсоолору"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Түзмөктөгү манжа изинин сенсорунда жасалган жаңсоолорду жаздырып алат."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"абал тилкесин өчүрүү же өзгөртүү"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Колдонмого абал тилкесин өчүрүү же тутум сүрөтчөлөрүн кошуу же алып салуу мүмкүнчүлүгүн берет."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"абал тилкесинин милдетин аткаруу"</string>
@@ -1395,7 +1400,7 @@
     <string name="ime_action_default" msgid="8265027027659800121">"Аткаруу"</string>
     <string name="dial_number_using" msgid="6060769078933953531">"<xliff:g id="NUMBER">%s</xliff:g> менен\nномерди терүү"</string>
     <string name="create_contact_using" msgid="6200708808003692594">"<xliff:g id="NUMBER">%s</xliff:g> менен\nбайланыш түзүү"</string>
-    <string name="grant_credentials_permission_message_header" msgid="5365733888842570481">"Төмөнкү бир же бир нече колдонмо каттоо эсебиңизге азыр жана кийинчерээк кирүү мүмкүнчүлүгүн сурап жатат."</string>
+    <string name="grant_credentials_permission_message_header" msgid="5365733888842570481">"Төмөнкү бир же бир нече колдонмо аккаунтуңузга азыр жана кийинчерээк кирүү мүмкүнчүлүгүн сурап жатат."</string>
     <string name="grant_credentials_permission_message_footer" msgid="1886710210516246461">"Бул өтүнүчкө уруксат бересизби?"</string>
     <string name="grant_permissions_header_text" msgid="3420736827804657201">"Жетки талабы"</string>
     <string name="allow" msgid="6195617008611933762">"Уруксат берүү"</string>
@@ -1602,14 +1607,17 @@
     <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="2299099385175083308">"Сиз планшетиңизди <xliff:g id="NUMBER">%d</xliff:g> жолу ийгиликсиз бөгөттөн чыгаруу аракетин кылдыңыз. Планшет баштапкы абалына кайтарылат."</string>
     <string name="kg_failed_attempts_now_wiping" product="tv" msgid="5045460916106267585">"Android TV түзмөгүңүздүн кулпусун <xliff:g id="NUMBER">%d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Android TV түзмөгүңүз эми демейки жөндөөлөргө кайтарылат."</string>
     <string name="kg_failed_attempts_now_wiping" product="default" msgid="5043730590446071189">"Сиз телефонуңузду <xliff:g id="NUMBER">%d</xliff:g> жолу ийгиликсиз бөгөттөн чыгаруу аракетин кылдыңыз. Телефон баштапкы абалына кайтарылат."</string>
-    <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="7086799295109717623">"Графикалык ачкычты <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес көрсөттүңүз. <xliff:g id="NUMBER_1">%2$d</xliff:g> жолу туура эмес көрсөтүлгөндөн кийин, планшетиңиздин кулпусун ачуу үчүн Google каттоо эсебиңизге кирүүгө туура келет.\n\n<xliff:g id="NUMBER_2">%3$d</xliff:g> секундадан кийин кайталап көрсөңүз болот."</string>
+    <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="7086799295109717623">"Графикалык ачкычты <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес көрсөттүңүз. <xliff:g id="NUMBER_1">%2$d</xliff:g> жолу туура эмес көрсөтүлгөндөн кийин, планшетиңиздин кулпусун ачуу үчүн Google аккаунтуңузга кирүүгө туура келет.\n\n<xliff:g id="NUMBER_2">%3$d</xliff:g> секундадан кийин кайталап көрсөңүз болот."</string>
     <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4670840383567106114">"Графикалык ачкычыңызды <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес чийдиңиз. Дагы <xliff:g id="NUMBER_1">%2$d</xliff:g> ийгиликсиз аракеттен кийин, Android TV түзмөгүңүздүн кулпусун электрондук почта аккаунтуңуз менен ачышыңыз керек болот.\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> секунддан кийин кайра аракет кылыңыз."</string>
-    <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Графикалык ачкычты <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес көрсөттүңүз. <xliff:g id="NUMBER_1">%2$d</xliff:g> жолу туура эмес көрсөтүлгөндөн кийин, телефондун кулпусун ачуу үчүн Google каттоо эсебиңизге кирүүгө туура келет.\n\n<xliff:g id="NUMBER_2">%3$d</xliff:g> секундадан кийин кайталап көрсөңүз болот."</string>
+    <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Графикалык ачкычты <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес көрсөттүңүз. <xliff:g id="NUMBER_1">%2$d</xliff:g> жолу туура эмес көрсөтүлгөндөн кийин, телефондун кулпусун ачуу үчүн Google аккаунтуңузга кирүүгө туура келет.\n\n<xliff:g id="NUMBER_2">%3$d</xliff:g> секундадан кийин кайталап көрсөңүз болот."</string>
     <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string>
     <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Алып салуу"</string>
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Сунушталган деңгээлден да катуулатып уккуңуз келеби?\n\nМузыканы узакка чейин катуу уксаңыз, угууңуз начарлап кетиши мүмкүн."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Ыкчам иштетесизби?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Атайын мүмкүнчүлүктөр функциясын пайдалануу үчүн, ал күйгүзүлгөндө, үндү катуулатып/акырындаткан эки баскычты тең үч секунддай кое бербей басып туруңуз.\n\n Учурдагы атайын мүмкүнчүлүктөрдүн жөндөөлөрү:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\nЖөндөөлөр &gt; Атайын мүмкүнчүлүктөр бөлүмүнөн өзгөртө аласыз."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Бошотуу"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Кыска жолдорду түзөтүү"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Жокко чыгаруу"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Кыска жолду өчүрүү"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Кыска жолду колдонуу"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Түстү инверсиялоо"</string>
@@ -1843,8 +1851,12 @@
     <string name="app_suspended_default_message" msgid="6451215678552004172">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> колдонмосу учурда жеткиликсиз. Анын жеткиликтүүлүгү <xliff:g id="APP_NAME_1">%2$s</xliff:g> тарабынан башкарылат."</string>
     <string name="app_suspended_more_details" msgid="211260942831587014">"Кеңири маалымат"</string>
     <string name="work_mode_off_title" msgid="5503291976647976560">"Жумуш профили күйгүзүлсүнбү?"</string>
-    <string name="work_mode_off_message" msgid="8417484421098563803">"Жумуш колдонмолоруңуз, эскертмелериңиз, дайындарыңыз жана жумуш профилинин башка функциялары күйгүзүлөт."</string>
+    <string name="work_mode_off_message" msgid="8417484421098563803">"Жумуш колдонмолоруңуз, эскертмелериңиз, дайын-даректериңиз жана жумуш профилинин башка функциялары күйгүзүлөт."</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Күйгүзүү"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Бул колдонмо Android\'дин эски версиясы үчүн иштеп чыгарылган, андыктан туура эмес иштеши мүмкүн. Жаңыртууларды издеп көрүңүз же иштеп чыгуучуга кайрылыңыз."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Жаңыртууну издөө"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Сизге жаңы билдирүүлөр келди"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Батарея кубаттоого чейин отуруп калышы мүмкүн"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Батареянын отуруп калбашы үчүн Батареяны үнөмдөгүч режими иштетилди"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Батареяны үнөмдөгүч"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Батареяны үнөмдөгүч батарея кайра азаймайынча күйгүзүлбөйт"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Батарея жетиштүү деңгээлге чейин кубатталды. Батареяны үнөмдөгүч батарея кайра азайганча чейин күйгүзүлбөйт."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Телефон <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> кубатталды"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Планшет <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> кубатталды"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Түзмөк <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> кубатталды"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Батареяны үнөмдөгүч өчүк. Функцияны колдоно берсеңиз болот."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Батареяны үнөмдөгүч режими өчүрүлдү. Функцияны колдоно берсеңиз болот."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"Папка"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android колдонмосу"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Файл"</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 230f2b5..52674c0 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"ເຂົ້າ​ຫາ​ປະ​ຕິ​ທິນ​ຂອງ​ທ່ານ"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"ສົ່ງ ແລະ​ເບິ່ງ​ຂໍ້​ຄວາມ SMS"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"ພື້ນທີ່ຈັດເກັບຂໍ້ມູນ"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"ເຂົ້າເຖິງຮູບຖ່າຍ, ສື່ ແລະໄຟລ໌ຢູ່ເທິງອຸປະກອນຂອງທ່ານ"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"ໄມໂຄຣໂຟນ"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"ບັນທຶກສຽງ"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"ສາມາດແຕະ, ປັດນີ້ວມື, ຢິບນິ້ວມື ແລະ ດຳເນີນທ່າທາງອື່ນ."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"ທ່າທາງລາຍນິ້ວມື"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"ສາມາດບັນທຶກທ່າທາງທີ່ເກີດຂຶ້ນໃນອຸປະກອນເຊັນເຊີລາຍນິ້ວມືໄດ້."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"ປິດການນນຳໃຊ້ ຫຼື ແກ້ໄຂແຖບສະຖານະ"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"ອະນຸຍາດໃຫ້ແອັບຯປິດການເຮັດວຽກຂອງແຖບສະຖານະ ຫຼືເພີ່ມ ແລະລຶບໄອຄອນລະບົບອອກໄດ້."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"ເປັນ​ແຖບ​ສະ​ຖາ​ນະ"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ເພີ່ມ​ລະ​ດັບ​ສຽງ​ໃຫ້​ເກີນກວ່າ​ລະ​ດັບ​ທີ່​ແນະ​ນຳ​ບໍ?\n\n​ການ​ຮັບ​ຟັງ​ສຽງ​ໃນ​ລະ​ດັບ​ທີ່​ສູງ​ເປັນ​ໄລ​ຍະ​ເວ​ລາ​ດົນ​​ອາດ​ເຮັດ​ໃຫ້​ການ​ຟັງ​ຂອງ​ທ່ານ​ມີ​ບັນ​ຫາ​ໄດ້."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ໃຊ້ປຸ່ມລັດການຊ່ວຍເຂົ້າເຖິງບໍ?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"ເມື່ອເປີດໃຊ້ປຸ່ມລັດແລ້ວ, ໃຫ້ກົດປຸ່ມສຽງທັງສອງຄ້າງໄວ້ 3 ວິນາທີເພື່ອເລີ່ມຄຸນສົມບັດການຊ່ວຍເຂົ້າເຖິງ.\n\n ຄຸນສົມບັດການຊ່ວຍເຂົ້າເຖິງປັດຈຸບັນ:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n ທ່ານສາມາດປ່ຽນຄຸນສົມບັດໄດ້ໃນການຕັ້ງຄ່າ &gt; ການຊ່ວຍເຂົ້າເຖິງ."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"ຫວ່າງເປົ່າ"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"ແກ້ໄຂທາງລັດ"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"ຍົກເລີກ"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"ປິດປຸ່ມລັດ"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"ໃຊ້ປຸ່ມລັດ"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"ການປີ້ນສີ"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"ເປີດໃຊ້ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກບໍ?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"ແອັບວຽກຂອງທ່ານ, ການແຈ້ງເຕືອນ, ຂໍ້ມູນ ແລະ ຄຸນສົມບັດໂປຣໄຟລ໌ວຽກຈະຖືກເປີດໃຊ້"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"ເປີດ​"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ແອັບນີ້ຖືກສ້າງຂຶ້ນສຳລັບ Android ເວີຊັນທີ່ເກົ່າກວ່າ ແລະ ອາດເຮັດວຽກໄດ້ບໍ່ປົກກະຕິ. ໃຫ້ລອງກວດສອບເບິ່ງອັບເດດ ຫຼື ຕິດຕໍ່ຜູ້ພັດທະນາ."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"ກວດເບິ່ງອັບເດດ"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"ທ່ານມີຂໍ້ຄວາມໃໝ່"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"ແບັດເຕີຣີອາດໝົດກ່ອນການສາກຕາມປົກກະຕິ"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"ເປີດຕົວປະຢັດແບັດເຕີຣີເພື່ອຂະຫຍາຍອາຍຸແບັດເຕີຣີ"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"ຕົວປະຢັດແບັດເຕີຣີ"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"ຈະບໍ່ມີການເປີດໃຊ້ຕົວປະຢັດແບັດເຕີຣີຄືນໃໝ່ຈົນກວ່າແບັດເຕີຣີຈະເຫຼືອໜ້ອຍອີກເທື່ອໜຶ່ງ"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"ສາກແບັດເຕີຣີຮອດລະດັບທີ່ພຽງພໍແລ້ວ. ຕົວປະຢັດແບັດເຕີຣີຈະບໍ່ເປີດໃຊ້ຄືນໃໝ່ຈົນກວ່າແບັດເຕີຣີຈະເຫຼືອໜ້ອຍກີເທື່ອໜຶ່ງ."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"ສາກໂທລະສັບແລ້ວ <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"ສາກແທັບເລັດແລ້ວ <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"ສາກອຸປະກອນແລ້ວ <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"ຕົວປະຢັດແບັດເຕີຣີປິດຢູ່. ບໍ່ມີການຈຳກັດຄຸນສົມບັດອີກຕໍ່ໄປແລ້ວ."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"ປິດຕົວປະຢັດແບັດເຕີຣີແລ້ວ. ບໍ່ມີການຈຳກັດຄຸນສົມບັດອີກຕໍ່ໄປແລ້ວ."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"ໂຟນເດີ"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"ແອັບພລິເຄຊັນ Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"ໄຟລ໌"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 5a85f48..5097d45 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -293,7 +293,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"pasiekti kalendorių"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"siųsti ir peržiūrėti SMS pranešimus"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Saugykla"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"Failai ir medija"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"pasiekti nuotraukas, mediją ir failus įrenginyje"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofonas"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"įrašyti garso įrašą"</string>
@@ -319,6 +319,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Galima paliesti, perbraukti, suimti ir atlikti kitus veiksmus gestais."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Piršto antspaudo gestai"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Gali užfiksuoti gestus, atliktus naudojant įrenginio piršto antspaudo jutiklį."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Ekrano kopijos kūrimas"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Galima sukurti vaizdo ekrano kopiją."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"išjungti ar keisti būsenos juostą"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Leidžiama programai neleisti būsenos juostos arba pridėti ir pašalinti sistemos piktogramas."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"būti būsenos juosta"</string>
@@ -1654,6 +1656,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Padidinti garsą daugiau nei rekomenduojamas lygis?\n\nIlgai klausydami dideliu garsu galite pažeisti klausą."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Naudoti spartųjį pritaikymo neįgaliesiems klavišą?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Kai spartusis klavišas įjungtas, spaudžiant abu garsumo mygtukus 3 sekundes bus paleista pritaikymo neįgaliesiems funkcija.\n\n Dabartinė pritaikymo neįgaliesiems funkcija:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>„\n“\n Funkciją galite pakeisti skiltyje „Nustatymai“ &gt; „Pritaikymas neįgaliesiems“."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Išvalyti"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Redaguoti sparčiuosius klavišus"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Atšaukti"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Išjungti spartųjį klavišą"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Naudoti spartųjį klavišą"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Spalvų inversija"</string>
@@ -1909,6 +1914,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Įjungti darbo profilį?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Darbo programos, pranešimai, duomenys ir kitos darbo profilio funkcijos bus išjungtos"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Įjungti"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Programa nepasiekiama."</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"Programa „<xliff:g id="APP_NAME">%1$s</xliff:g>“ šiuo metu nepasiekiama."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ši programa sukurta naudoti senesnės versijos sistemoje „Android“ ir gali tinkamai neveikti. Pabandykite patikrinti, ar yra naujinių, arba susisiekite su kūrėju."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Tikrinti, ar yra naujinių"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Turite naujų pranešimų"</string>
@@ -2021,13 +2028,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Akumuliatoriaus energija gali išsekti prieš įprastą įkrovimą"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Akumuliatoriaus tausojimo priemonė suaktyvinta, kad akumuliatorius veiktų ilgiau"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Akumuliatoriaus tausojimo priemonė"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Akumuliatoriaus tausojimo priemonė nebus iš naujo suaktyvinta, kol akumuliatorius vėl beveik išseks"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Pasiektas tinkamas akumuliatoriaus įkrovimo lygis. Akumuliatoriaus tausojimo priemonė nebus iš naujo suaktyvinta, kol akumuliatorius vėl beveik išseks."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Telefono įkrovimo lygis: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Planšetinio kompiuterio įkrovimo lygis: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Įrenginio įkrovimo lygis: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Akumuliatoriaus tausojimo priemonė išjungta. Funkcijos neberibojamos."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Akumuliatoriaus tausojimo priemonė išjungta. Funkcijos neberibojamos."</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Akumuliatoriaus tausojimo priemonė išjungta"</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefonas pakankamai įkrautas. Funkcijos neberibojamos."</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"Planšetinis kompiuteris pakankamai įkrautas. Funkcijos neberibojamos."</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"Įrenginys pakankamai įkrautas. Funkcijos neberibojamos."</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"Aplankas"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"„Android“ programa"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Failas"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 17c0c17..7d11634f 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -290,7 +290,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"piekļūt jūsu kalendāram"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"Īsziņas"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"sūtīt un skatīt īsziņas"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Krātuve"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"piekļūt fotoattēliem, multividei un failiem jūsu ierīcē"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofons"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"ierakstīt audio"</string>
@@ -316,6 +317,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Atbalsta pieskaršanos, vilkšanu, savilkšanu un citus žestus."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Pirksta nospieduma žesti"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Var uztvert žestus ierīces pirksta nospieduma sensorā."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"atspējot vai pārveidot statusa joslu"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Ļauj lietotnei atspējot statusa joslu vai pievienot un noņemt sistēmas ikonas."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"Būt par statusa joslu"</string>
@@ -1632,6 +1637,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Vai palielināt skaļumu virs ieteicamā līmeņa?\n\nIlgstoši klausoties skaņu lielā skaļumā, var tikt bojāta dzirde."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Vai izmantot pieejamības saīsni?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Ja saīsne ir iespējota, vienlaikus nospiežot abas skaļuma regulēšanas pogas un trīs sekundes turot tās, tiks palaista pieejamības funkcija.\n\n Pašreiz iestatītā pieejamības funkcija:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Šo funkciju var mainīt sadaļā Iestatījumi &gt; Pieejamība."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Notīrīt"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Rediģēt īsinājumtaustiņus"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Atcelt"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Izslēgt saīsni"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Izmantot saīsni"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Krāsu inversija"</string>
@@ -1877,6 +1885,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Vai ieslēgt darba profilu?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Tiks ieslēgtas jūsu darba lietotnes, paziņojumi, dati un citas darba profila funkcijas."</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Ieslēgt"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Šī lietotne tika izstrādāta vecākai Android versijai un var nedarboties pareizi. Meklējiet atjauninājumus vai sazinieties ar izstrādātāju."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Meklēt atjauninājumu"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Jums ir jaunas īsziņas."</string>
@@ -1988,13 +2000,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Akumulators var izlādēties pirms parastā uzlādes laika"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Aktivizēts akumulatora jaudas taupīšanas režīms, lai palielinātu akumulatora darbības ilgumu"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Akumulatora jaudas taupīšanas režīms"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Akumulatora jaudas taupīšanas režīms tiks atkārtoti aktivizēts tikai tad, kad akumulatora uzlādes līmenis atkal būs zems"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Akumulatora uzlādes līmenis ir pietiekams. Akumulatora jaudas taupīšanas režīms tiks atkārtoti aktivizēts tikai tad, kad akumulatora uzlādes līmenis atkal būs zems."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Tālruņa uzlādes līmenis ir <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Planšetdatora uzlādes līmenis ir <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Ierīces uzlādes līmenis ir <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Akumulatora jaudas taupīšanas režīms ir izslēgts. Funkcijas vairs netiek ierobežotas."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Akumulatora jaudas taupīšanas režīms ir izslēgts. Funkcijas vairs netiek ierobežotas."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"Mape"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android lietojumprogramma"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Fails"</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 2898129..72048b5 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -287,7 +287,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"пристапува до календарот"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"испраќа и прикажува SMS-пораки"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Капацитет"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"Датотеки и аудиовизуелни содржини"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"пристапува до фотографии, аудио-видео и датотеки на уредот"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Микрофон"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"снима аудио"</string>
@@ -313,6 +313,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Може да допрете, повлечете, штипнете и да користите други движења."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Движења за отпечатоци"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Може да сними движења што се направени на сензорот за отпечатоци на уредот."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Правење слика од екранот"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Може да направи слика од екранот."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"оневозможи или измени статусна лента"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Дозволува апликацијата да ја оневозможи статусната лента или да додава или отстранува системски икони."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"да стане статусна лента"</string>
@@ -1454,7 +1456,7 @@
     <string name="sync_really_delete" msgid="5657871730315579051">"Избриши ги ставките"</string>
     <string name="sync_undo_deletes" msgid="5786033331266418896">"Врати ги избришаните"</string>
     <string name="sync_do_nothing" msgid="4528734662446469646">"Не прави ништо засега"</string>
-    <string name="choose_account_label" msgid="5557833752759831548">"Избери сметка"</string>
+    <string name="choose_account_label" msgid="5557833752759831548">"Изберете сметка"</string>
     <string name="add_account_label" msgid="4067610644298737417">"Додај сметка"</string>
     <string name="add_account_button_label" msgid="322390749416414097">"Додај сметка"</string>
     <string name="number_picker_increment_button" msgid="7621013714795186298">"Зголеми"</string>
@@ -1612,6 +1614,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Да го зголемиме звукот над препорачаното ниво?\n\nСлушањето звуци со голема јачина подолги периоди може да ви го оштети сетилото за слух."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Да се користи кратенка за „Пристапност“?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Кога е вклучена кратенката, ако ги притиснете двете копчиња за јачина на звук во времетраење од 3 секунди, ќе се стартува функција на пристапност.\n\n Тековна функција на пристапност:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Функцијата може да ја промените во „Поставки“ &gt; „Пристапност“."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Испразни"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Изменете ги кратенките"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Откажи"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Исклучи ја кратенката"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Користи кратенка"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Инверзија на бои"</string>
@@ -1847,6 +1852,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Да се вклучи работниот профил?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Вашите работни апликации, известувања, податоци и други функции на работниот профил ќе бидат вклучени"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Вклучи"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Апликацијата не е достапна"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> не е достапна во моментов."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Апликацијава е создадена за постара верзија на Android и може да не функционира правилно. Проверете за ажурирања или контактирајте со програмерот."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Проверка за ажурирање"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Имате нови пораки"</string>
@@ -1957,13 +1964,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Батеријата може да се потроши пред вообичаеното време за полнење"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Активиран е „Штедачот на батерија“ за да се продолжи траењето на батеријата"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Штедач на батерија"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Штедачот на батерија нема да се реактивира додека батеријата не се потроши до одредено ниво повторно"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Батеријата е доволно полна. Штедачот на батерија нема да се реактивира додека батеријата не се потроши до одредено ниво повторно."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Телефонот е наполнет <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Таблетот е наполнет <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Уредот е наполнет <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Штедачот на батерија е исклучен. Функциите веќе не се ограничени."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Штедачот на батерија е исклучен. Функциите веќе не се ограничени."</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Штедачот на батерија е исклучен"</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Телефонот е доволно полн. Функциите веќе не се ограничени."</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"Таблетот е доволно полн. Функциите веќе не се ограничени."</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"Уредот е доволно полн. Функциите веќе не се ограничени."</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"Папка"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Апликација за Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Датотека"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index 1abf88d..b0bbb67 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"നിങ്ങളുടെ കലണ്ടർ ആക്‌സസ്സ് ചെയ്യുക"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS സന്ദേശങ്ങൾ അയയ്‌ക്കുകയും കാണുകയും ചെയ്യുക"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"സ്റ്റോറേജ്"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"നിങ്ങളുടെ ഉപകരണത്തിലെ ഫോട്ടോകളും മീഡിയയും ഫയലുകളും ആക്സസ് ചെയ്യുക"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"മൈക്രോഫോണ്‍"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"ഓഡിയോ റെക്കോർഡ് ചെയ്യുക"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"ടാപ്പുചെയ്യാനോ സ്വൈപ്പുചെയ്യാനോ പിഞ്ചുചെയ്യാനോ മറ്റ് ജെസ്‌റ്ററുകൾ നിർവഹിക്കാനോ കഴിയും."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"ഫിംഗർപ്രിന്റ് ജെസ്‌റ്ററുകൾ"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"ഉപകരണത്തിന്റെ ഫിംഗർപ്രിന്റ് സെൻസറിൽ ചെയ്‌ത ജെസ്‌റ്ററുകൾ ക്യാപ്‌ചർ ചെയ്യാനാകും."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"സ്റ്റാറ്റസ് ബാർ പ്രവർത്തനരഹിതമാക്കുക അല്ലെങ്കിൽ പരിഷ്‌ക്കരിക്കുക"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"നില ബാർ പ്രവർത്തരഹിതമാക്കുന്നതിന് അല്ലെങ്കിൽ സിസ്‌റ്റം ഐക്കണുകൾ ചേർക്കുന്നതിനും നീക്കംചെയ്യുന്നതിനും അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"സ്റ്റാറ്റസ് ബാർ ആയിരിക്കുക"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"മുകളിൽക്കൊടുത്തിരിക്കുന്ന ശുപാർശചെയ്‌ത ലെവലിലേക്ക് വോളിയം വർദ്ധിപ്പിക്കണോ?\n\nഉയർന്ന വോളിയത്തിൽ ദീർഘനേരം കേൾക്കുന്നത് നിങ്ങളുടെ ശ്രവണ ശേഷിയെ ദോഷകരമായി ബാധിക്കാം."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ഉപയോഗസഹായി കുറുക്കുവഴി ഉപയോഗിക്കണോ?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"കുറുക്കുവഴി ഓണാണെങ്കിൽ, രണ്ട് വോളിയം ബട്ടണുകളും 3 സെക്കൻഡ് നേരത്തേക്ക് അമർത്തുന്നത് ഉപയോഗസഹായി ഫീച്ചർ ആരംഭിക്കും.\n\n നിലവിലെ  ഉപയോഗസഹായി ഫീച്ചർ:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n ക്രമീകരണം &gt; ഉപയോഗസഹായി എന്നതിൽ ഏത് സമയത്തും നിങ്ങൾക്ക് ഫീച്ചർ മാറ്റാവുന്നതാണ്."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"ശൂന്യം"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"കുറുക്കുവഴികൾ തിരുത്തുക"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"റദ്ദാക്കുക"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"കുറുക്കുവഴി ‌ഓഫാക്കുക"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"കുറുക്കുവഴി ഉപയോഗിക്കുക"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"വർണ്ണ വിപര്യയം"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"ഔദ്യോഗിക പ്രൊഫൈൽ ഓണാക്കണോ?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"നിങ്ങളുടെ ഔദ്യോഗിക ആപ്പുകൾ, അറിയിപ്പുകൾ, ഡാറ്റ, മറ്റ് ഔദ്യോഗിക പ്രൊഫൈൽ ഫീച്ചറുകൾ എന്നിവ ഓണാക്കും"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"ഓണാക്കുക"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ഈ ആപ്പ് Android-ന്റെ പഴയ പതിപ്പിനായി നിർമ്മിച്ചിരിക്കുന്നതിനാൽ ശരിയായി പ്രവർത്തിച്ചേക്കില്ല. അപ്‌ഡേറ്റിനായി പരിശോധിക്കുക, അല്ലെങ്കിൽ ഡെവലപ്പറുമായി ബന്ധപ്പെടുക."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"അപ്‌ഡേറ്റിനായി പരിശോധിക്കുക"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"നിങ്ങൾക്ക് പുതിയ സന്ദേശങ്ങൾ ഉണ്ട്"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"സാധാരണയുള്ളതിലും നേരത്തെ ബാറ്ററിയുടെ ചാർജ് തീർന്നേക്കാം"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"ബാറ്ററി ലൈഫ് വര്‍ദ്ധിപ്പിക്കാൻ, ബാറ്ററി ലാഭിക്കൽ സജീവമാക്കി"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"ബാറ്ററി ലാഭിക്കൽ"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"ബാറ്ററി ചാർജ് വീണ്ടും കുറയുന്നത് വരെ ബാറ്ററി ലാഭിക്കൽ പിന്നെയും സജീവമാവുകയില്ല"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"മതിയായ നില വരെ ബാറ്ററി ചാർജായി. വീണ്ടും ബാറ്ററി ചാർജ് കുറയുന്നത് വരെ ബാറ്ററി ലാഭിക്കൽ പിന്നെയും സജീവമാവുകയില്ല."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"ഫോൺ <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ചാർജായി"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"ടാബ്‌ലെറ്റ് <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ചാർജായി"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"ഉപകരണം <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ചാർജായി"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"ബാറ്ററി ലാഭിക്കൽ ഓഫാണ്. ഫീച്ചറുകൾക്ക് ഇനിമുതൽ നിയന്ത്രണമില്ല."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"ബാറ്ററി ലാഭിക്കൽ ഓഫാക്കി. ഫീച്ചറുകൾക്ക് ഇനിമുതൽ നിയന്ത്രണമില്ല."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"ഫോള്‍ഡര്‍"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android ആപ്പ്"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"ഫയൽ"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 0f17c00..205df27 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"Хуанли руу хандах"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"Мессеж"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS мессежийг илгээх, харах"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Хадгалах сан"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"Tөхөөрөмж дээрх зураг, медиа болон файлд хандалт хийх"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Микрофон"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"дуу хураах"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Товших, шудрах, жижигрүүлэх болон бусад зангааг хийх боломжтой."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Хурууны хээний зангаа"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Төхөөрөмжийн хурууны хээ мэдрэгчид зангасан зангааг танина."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"статус самбарыг идэвхгүй болгох болон өөрчлөх"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Апп нь статус самбарыг идэвхгүй болгох эсвэл систем дүрсийг нэмэх, хасах боломжтой."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"статусын хэсэг болох"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Дууг санал болгосноос чанга болгож өсгөх үү?\n\nУрт хугацаанд чанга хөгжим сонсох нь таны сонсголыг муутгаж болно."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Хүртээмжийн товчлолыг ашиглах уу?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Товчлолыг асаасан үед дуун товчлуурыг 3 секунд дарснаар хүртээмжийн онцлогийг эхлүүлнэ.\n\n Одоогийн хүртээмжийн онцлог:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Онцлогийг Тохиргоо &gt; Хүртээмж хэсэгт өөрчлөх боломжтой."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Хоосон"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Товчлолуудыг засах"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Болих"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Товчлолыг унтраах"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Товчлол ашиглах"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Өнгө хувиргалт"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Ажлын профайлыг асаах уу?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Таны ажлын апп, мэдэгдэл, өгөгдөл болон бусад ажлын профайлын онцлогийг асаана"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Асаах"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Энэ аппыг Андройдын хуучин хувилбарт зориулсан бөгөөд буруу ажиллаж болзошгүй. Шинэчлэлтийг шалгаж эсвэл хөгжүүлэгчтэй холбогдоно уу."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Шинэчлэлтийг шалгах"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Танд шинэ зурвасууд байна"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Батарей ихэвчлэн цэнэглэдэг хугацаанаас өмнө дуусаж болзошгүй"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Батарейн ажиллах хугацааг уртасгахын тулд Батарей хэмнэгчийг идэвхжүүллээ"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Батарей хэмнэгч"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Батарей хэмнэгч батарейг дахин багасах хүртэл дахин идэвхжихгүй"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Батарейг хангалттай түвшинд цэнэглэлээ. Батарей хэмнэгч батарейг дахин багасах хүртэл дахин идэвхжихгүй."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Утсыг <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> цэнэглэсэн байна"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Таблетыг <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> цэнэглэсэн"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Төхөөрөмжийг <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> цэнэглэсэн байна"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Батарей хэмнэгч унтраалттай байна. Онцлогуудыг хязгаарлахаа больсон."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Батарей хэмнэгчийг унтраасан байна. Онцлогуудыг хязгаарлахаа больсон."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"Фолдер"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Андройд апп"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Файл"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index fa8cbb0..b27e1c9 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"आपल्या कॅलेंडरवर प्रवेश"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS मेसेज पाठवणे आणि पाहणे हे"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"स्टोरेज"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"तुमच्या डिव्हाइस वरील फोटो, मीडिया आणि फायलींमध्‍ये अ‍ॅक्सेस"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"मायक्रोफोन"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"ऑडिओ रेकॉर्ड"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"टॅप, स्वाइप, पिंच आणि इतर जेश्चर करू शकते."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"फिंगरप्रिंट जेश्चर"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"डिव्‍हाइसच्‍या फिंगरप्रिंट सेंन्सरवरील जेश्चर कॅप्‍चर करू शकते."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"स्टेटस बार अक्षम करा किंवा सुधारित करा"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"स्टेटस बार अक्षम करण्यासाठी किंवा सिस्टम चिन्हे जोडण्यासाठी आणि काढण्यासाठी अ‍ॅप ला अनुमती देते."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"स्टेटस बार होऊ द्या"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"शिफारस केलेल्‍या पातळीच्या वर आवाज वाढवायचा?\n\nउच्च आवाजात दीर्घ काळ ऐकण्‍याने आपल्‍या श्रवणशक्तीची हानी होऊ शकते."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"प्रवेशयोग्यता शॉर्टकट वापरायचा?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"शॉर्टकट चालू असताना, दोन्ही आवाज बटणे 3 सेकंद दाबल्याने प्रवेशयोग्यता वैशिष्ट्य सुरू होईल.\n\n वर्तमान प्रवेशयोग्यता वैशिष्ट्य:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n तुम्ही सेटिंग्ज &gt; प्रवेशयोग्यता मध्ये वैशिष्ट्य बदलू शकता."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"रिकामे करा"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"शॉर्टकट संपादित करा"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"रद्द करा"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"शॉर्टकट बंद करा"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"शॉर्टकट वापरा"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"रंगांची उलटापालट"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"कार्य प्रोफाइल चालू ठेवायची?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"तुमची कार्य अ‍ॅप्स, सूचना, डेटा आणि अन्य कार्य प्रोफाइल वैशिष्ट्ये चालू केली जातील"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"चालू करा"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"हे अ‍ॅप Android च्या जुन्या आवृत्ती साठी तयार करण्यात आले होते आणि योग्यरितीने कार्य करू शकणार नाही. अपडेट आहेत का ते तपासून पाहा, किंवा डेव्हलपरशी संपर्क साधण्याचा प्रयत्न करा."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"अपडेट आहे का ते तपासा"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"आपल्याकडे नवीन मेसेज आहेत"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"चार्जिंगची सामान्य पातळी गाठेपर्यंत कदाचित बॅटरी संपू शकते"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"बॅटरी लाइफ वाढवण्यासाठी बॅटरी सेव्हर सुरू केला आहे"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"बॅटरी सेव्‍हर"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"बॅटरी पुन्हा कमी होईपर्यंत बॅटरी सेव्हर पुन्हा अ‍ॅक्टिव्हेट होणार नाही"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"बॅटरी पुरेशा पातळीवर चार्ज झालेली आहे. बॅटरी पुन्हा कमी होईपर्यंत बॅटरी सेव्हर पुन्हा अ‍ॅक्टिव्हेट होणार नाही."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"फोन <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> चार्ज झाला"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"टॅबलेट <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> चार्ज झाले"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"डिव्हाइस <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> चार्ज झाले"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"बॅटरी सेव्हर बंद आहे. वैशिष्ट्ये मर्यादित नाहीत."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"बॅटरी सेव्हर बंद केलेला आहे. वैशिष्ट्ये मर्यादित नाहीत."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"फोल्डर"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android अ‍ॅप्लिकेशन"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"फाइल"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 1386e7a..a29e363 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"mengakses kalendar"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"menghantar dan melihat mesej SMS"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Storan"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"mengakses foto, media dan fail pada peranti anda"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofon"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"rakam audio"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Boleh ketik, leret, cubit dan laksanakan gerak isyarat lain."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Gerak isyarat cap jari"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Boleh menangkap gerak isyarat yang dilakukan pada penderia cap jari peranti."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"lumpuhkan atau ubah suai bar status"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Membenarkan apl melumpuhkan bar status atau menambah dan mengalih keluar ikon sistem."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"jadi bar status"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Naikkan kelantangan melebihi paras yang disyokorkan?\n\nMendengar pada kelantangan yang tinggi untuk tempoh yang lama boleh merosakkan pendengaran anda."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Gunakan Pintasan Kebolehaksesan?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Apabila pintasan dihidupkan, tindakan menekan kedua-dua butang kelantangan selama 3 saat akan memulakan ciri kebolehaksesan.\n\n Ciri kebolehaksesan semasa:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Anda boleh menukar ciri itu dalam Tetapan &gt; Kebolehaksesan."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Kosong"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Edit pintasan"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Batal"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Matikan pintasan"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Gunakan Pintasan"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Penyongsangan Warna"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Hidupkan profil kerja?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Apl kerja, pemberitahuan, data dan ciri profil kerja anda yang lain akan dihidupkan"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Hidupkan"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Apl ini dibina untuk versi Android yang lebih lama dan mungkin tidak berfungsi dengan betul. Cuba semak kemas kini atau hubungi pembangun."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Semak kemas kini"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Anda mempunyai mesej baharu"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Bateri mungkin habis sebelum pengecasan biasa"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Penjimat Bateri diaktifkan untuk memanjangkan hayat bateri"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Penjimat Bateri"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Penjmat Bateri tidak akan diaktifkan semula sehingga bateri rendah sekali lagi"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Bateri sudah dicas pada aras yang mencukupi Penjimat Bateri tidak akan diaktifkan semula sehingga bateri rendah sekali lagi."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Telefon dicas <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Tablet dicas <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Peranti dicas <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Penjimat Bateri dimatikan. Ciri tidak lagi dihadkan."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Penjimat Bateri dimatikan. Ciri tidak lagi dihadkan."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"Folder"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Aplikasi Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Fail"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index ff08a59..a53deaf 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"သင့်ပြက္ခဒိန်အား ဝင်ရောက်သုံးရန်"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS စာတိုစနစ်"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS စာများကို ပို့ကာ ကြည့်မည်"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"သိုလှောင်မှုများ"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"သင့်ဖုန်းရှိ ဓာတ်ပုံများ၊ မီဒီယာနှင့် ဖိုင်များအား ဝင်သုံးပါ"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"မိုက်ခရိုဖုန်း"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"အသံဖမ်းခြင်း"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"တို့ခြင်း၊ ပွတ်ဆွဲခြင်း၊ နှင့် အခြား လက်ဟန်များကို အသုံးပြုနိုင်ပါသည်။"</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"လက်ဗွေဟန်များ"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"စက်ပစ္စည်း၏ လက်ဗွေအာရုံခံကိရိယာတွင် လုပ်ဆောင်ထားသည့် လက်ဟန်များကို မှတ်သားထားနိုင်သည်။"</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"အခြေအနေပြဘားအား အလုပ်မလုပ်ခိုင်းရန်သို့မဟုတ် မွမ်းမံရန်"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"အက်ပ်အား အခြေအနေပြ ဘားကို ပိတ်ခွင့် သို့မဟတ် စနစ် အိုင်ကွန်များကို ထည့်ခြင်း ဖယ်ရှားခြင်း ပြုလုပ်ခွင့် ပြုသည်။"</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"အခြေအနေပြ ဘားဖြစ်ပါစေ"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"အသံကို အကြံပြုထားသည့် ပမာဏထက် မြှင့်ပေးရမလား?\n\nအသံကို မြင့်သည့် အဆင့်မှာ ကြာရှည်စွာ နားထောင်ခြင်းက သင်၏ နားကို ထိခိုက်စေနိုင်သည်။"</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"အများသုံးစွဲနိုင်မှု ဖြတ်လမ်းလင့်ခ်ကို အသုံးပြုလိုပါသလား။"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"ဖြတ်လမ်းလင့်ခ်ကို ဖွင့်ထားစဉ် အသံအတိုးအလျှော့ခလုတ် နှစ်ခုစလုံးကို ၃ စက္ကန့်ခန့် ဖိထားခြင်းဖြင့် အများသုံးစွဲနိုင်မှုဆိုင်ရာ ဝန်ဆောင်မှုကို ဖွင့်နိုင်သည်။\n\n လက်ရှိ အများသုံးစွဲနိုင်မှုဆိုင်ရာ ဝန်ဆောင်မှု−\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n ဝန်ဆောင်မှုကို ဆက်တင်များ &gt; အများသုံးစွဲနိုင်မှုတွင် ပြောင်းလဲနိုင်ပါသည်။"</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"အလွတ်"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"ဖြတ်လမ်းများကို တည်းဖြတ်ရန်"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"မလုပ်တော့"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"ဖြတ်လမ်းလင့်ခ်ကို ပိတ်ရန်"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"ဖြတ်လမ်းလင့်ခ်ကို သုံးရန်"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"အရောင် ပြောင်းပြန်လှန်ခြင်း"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"အလုပ်ပရိုဖိုင် ဖွင့်လိုသလား။"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"သင်၏ အလုပ်အက်ပ်၊ အကြောင်းကြားချက်၊ ဒေတာနှင့် အခြားအလုပ်ပရိုဖိုင် ဝန်ဆောင်မှုများကို ဖွင့်လိုက်ပါမည်"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"ဖွင့်ပါ"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ဤအက်ပ်ကို Android ဗားရှင်းဟောင်းအတွက် ပြုလုပ်ထားခြင်းဖြစ်ပြီး ပုံမှန်အလုပ်မလုပ်နိုင်ပါ။ အပ်ဒိတ်များအတွက် ရှာကြည့်ပါ သို့မဟုတ် ဆော့ဖ်ဝဲအင်ဂျင်နီယာကို ဆက်သွယ်ပါ။"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"အပ်ဒိတ်စစ်ရန်"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"သင့်ထံတွင် စာအသစ်များရောက်နေသည်"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"ပုံမှန်အားသွင်းမှုမပြုလုပ်မီ ဘက်ထရီကုန်သွားနိုင်သည်"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"ဘက်ထရီသက်တမ်းကို တိုးမြှင့်ရန် \'ဘက်ထရီအားထိန်း\' စတင်ပြီးပါပြီ"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"ဘက်ထရီ အားထိန်း"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"နောက်တစ်ကြိမ် ဘက်ထရီအားနည်းသွားသည်အထိ \'ဘက်ထရီအားထိန်း\' ပြန်ပွင့်မည် မဟုတ်ပါ"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"ဘက်ထရီကို လုံလောက်သည့်ပမာဏသို့ အားသွင်းထားပါသည်။ နောက်တစ်ကြိမ် ဘက်ထရီအားနည်းသွားသည်အထိ \'ဘက်ထရီအားထိန်း\' ပြန်ပွင့်မည် မဟုတ်ပါ။"</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"ဖုန်း <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> အားသွင်းထားသည်"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"တက်ဘလက် <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> အားသွင်းထားသည်"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"စက်ပစ္စည်း <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> အားသွင်းထားသည်"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"\'ဘက်ထရီအားထိန်း\' ကိုပိတ်ထားသည်။ ဝန်ဆောင်မှုများကို ကန့်သတ်မထားတော့ပါ။"</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"\'ဘက်ထရီ အားထိန်း\' ပိတ်ထားသည်။ ဝန်ဆောင်မှုများကို ကန့်သတ်မထားတော့ပါ။"</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"ဖိုင်တွဲ"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android အပလီကေးရှင်း"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"ဖိုင်"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 3e23679..ee16f90 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"åpne kalenderen din"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"sende og lese SMS-meldinger"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Lagring"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"åpne bilder, medieinnhold og filer på enheten din"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofon"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"ta opp lyd"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Kan trykke, sveipe, klype og gjøre andre bevegelser."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Bevegelser på fingeravtrykkssensor"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Kan fange inn bevegelser som utføres på enhetens fingeravtrykkssensor."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"deaktivere eller endre statusfeltet"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Lar appen deaktivere statusfeltet eller legge til og fjerne systemikoner."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"vise appen i statusfeltet"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Vil du øke volumet til over anbefalt nivå?\n\nHvis du hører på et høyt volum over lengre perioder, kan det skade hørselen din."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Vil du bruke tilgjengelighetssnarveien?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Når snarveien er på, starter en tilgjengelighetsfunksjon når du trykker inn begge volumknappene i tre sekunder.\n\n Nåværende tilgjengelighetsfunksjon:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Du kan endre funksjonen i Innstillinger &gt; Tilgjengelighet."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Tøm"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Endre snarveier"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Avbryt"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Slå av snarveien"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Bruk snarveien"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Fargeinvertering"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Vil du slå på jobbprofilen?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Jobbappene dine samt varsler, data og andre funksjoner i jobbprofilen din blir slått på"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Slå på"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Denne appen er utviklet for en eldre versjon av Android og fungerer kanskje ikke som den skal. Prøv å se etter oppdateringer, eller kontakt utvikleren."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Se etter oppdateringer"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Du har nye meldinger"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Batteriet kan gå tomt før den vanlige ladingen"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Batterisparing er aktivert for å forlenge batterilevetiden"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Batterisparing"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Batterisparing blir ikke aktivert igjen før batteriet er lavt"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Batteriet er blitt ladet til et tilstrekkelig nivå. Batterisparing blir ikke aktivert igjen før batteriet er lavt."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Telefonen er <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ladet"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Nettbrettet er <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ladet"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Enheten er <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ladet"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Batterisparing er av. Funksjoner begrenses ikke lenger."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Batterisparing er slått av. Funksjoner begrenses ikke lenger."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"Mappe"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android-app"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Fil"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index d7e121c..1b85b28 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"तपाईंको पात्रोमाथि पहुँच गर्नुहोस्"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS सन्देशहरू पठाउनुहोस् र हेर्नुहोस्"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"भण्डारण"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"तपाईंको यन्त्रमा तस्बिर, मिडिया, र फाइलहरूमाथि पहुँच गर्नुहोस्"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"माइक्रोफोन"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"अडियो रेकर्ड गर्नुहोस्"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"ट्याप, स्वाइप गर्न, थिच्न र अन्य इसाराहरू सम्बन्धी कार्य गर्न सक्छ"</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"फिंगरप्रिन्टका इसाराहरू"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"यसले यन्त्रक‍ो फिंगरप्रिन्टसम्बन्धी सेन्सरमा गरिएका इसाराहरूलाई खिच्‍न सक्छ।"</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"स्थिति पट्टिलाई अक्षम वा संशोधित गर्नुहोस्"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"स्थिति पट्टि असक्षम पार्न वा प्रणाली आइकनहरू थप्न र हटाउन अनुप्रयोगलाई अनुमति दिन्छ।"</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"वस्तुस्थिति पट्टी हुन दिनुहोस्"</string>
@@ -1616,6 +1621,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"सिफारिस तहभन्दा आवाज ठुलो गर्नुहुन्छ?\n\nलामो समय सम्म उच्च आवाजमा सुन्दा तपाईँको सुन्ने शक्तिलाई हानी गर्न सक्छ।"</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"पहुँच सम्बन्धी सर्टकट प्रयोग गर्ने हो?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"सर्टकट सक्रिय हुँदा, भोल्युमका दुवै बटनहरूलाई ३ सेकेन्डसम्म थिची राख्नाले पहुँच सम्बन्धी कुनै सुविधा सुरु हुनेछ।\n\n हाल व्यवहारमा रहेको पहुँच सम्बन्धी सुविधा:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n तपाईं सेटिङहरू अन्तर्गतको पहुँच सम्बन्धी विकल्पमा गई उक्त सुविधालाई बदल्न सक्नुहुन्छ।"</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"खाली"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"सर्टकटहरू सम्पादन गर्नुहोस्"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"रद्द गर्नुहोस्"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"सर्टकटलाई निष्क्रिय पार्नुहोस्"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"सर्टकट प्रयोग गर्नुहोस्"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"रङ्ग उल्टाउने सुविधा"</string>
@@ -1851,6 +1859,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"कार्य प्रोफाइल सक्रिय गर्ने?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"तपाईंका कार्यसम्बन्धी अनुप्रयोग, सूचना, डेटा र कार्य प्रोफाइलका अन्य सुविधाहरू सक्रिय गरिने छन्‌"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"सक्रिय गर्नुहोस्"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"यो अनुप्रयोग Android को पुरानो संस्करणका लागि बनाइएको हुनाले यसले सही ढङ्गले काम नगर्न सक्छ। अद्यावधिकहरू उपलब्ध छन् वा छैनन् भनी जाँच गरी हेर्नुहोस् वा यसको विकासकर्तालाई सम्पर्क गर्नुहोस्।"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"अद्यावधिक उपलब्ध छ वा छैन भनी जाँच गर्नुहोस्"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"तपाईंलाई नयाँ सन्देश आएको छ"</string>
@@ -1961,13 +1973,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"प्रायः चार्ज गर्ने समय हुनुभन्दा पहिले नै ब्याट्री सकिन सक्छ"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"ब्याट्रीको आयु बढाउन ब्याट्री सेभर सक्रिय गरियो"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"ब्याट्री सेभर"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"फेरि ब्याट्री कम नभएसम्म ब्याट्री सेभर पुनः सक्रिय हुने छैन"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"ब्याट्री पर्याप्त हुने गरी चार्ज गरिएको छ। ब्याट्रीको चार्ज कम नभएसम्म ब्याट्री सेभर पुनः सक्रिय हुने छैन।"</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"फोन <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> चार्ज भयो"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"ट्याब्लेट <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> चार्ज भयो"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"यन्त्र <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> चार्ज गरियो"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"ब्याट्री सेभर निष्क्रिय छ। सुविधाहरूलाई अब उप्रान्त प्रतिबन्ध लगाइँदैन।"</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"ब्याट्री सेभर निष्क्रिय पारियो। सुविधाहरूलाई अब उप्रान्त प्रतिबन्ध लगाइँदैन।"</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"फोल्डर"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android अनुप्रयोग"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"फाइल"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index b342941..dece9ae 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -287,7 +287,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"toegang krijgen tot je agenda"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"Sms"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"sms\'jes verzenden en bekijken"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Opslagruimte"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"Bestanden en media"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"toegang krijgen tot foto\'s, media en bestanden op je apparaat"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Microfoon"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"audio opnemen"</string>
@@ -313,6 +313,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Kan tikken, vegen, samenknijpen en andere gebaren uitvoeren."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Vingerafdrukgebaren"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Kan gebaren registreren die op de vingerafdruksensor van het apparaat worden getekend."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Screenshot maken"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Kan een screenshot van het scherm maken."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"statusbalk uitschakelen of wijzigen"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Hiermee kan de app de statusbalk uitschakelen of systeempictogrammen toevoegen en verwijderen."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"de statusbalk zijn"</string>
@@ -1610,6 +1612,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Volume verhogen tot boven het aanbevolen niveau?\n\nAls je langere tijd op hoog volume naar muziek luistert, raakt je gehoor mogelijk beschadigd."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Snelkoppeling toegankelijkheid gebruiken?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Wanneer de snelkoppeling is ingeschakeld, kun je drie seconden op beide volumeknoppen drukken om een toegankelijkheidsfunctie te starten.\n\n Huidige toegankelijkheidsfunctie:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Je kunt de functie wijzigen in Instellingen &gt; Toegankelijkheid."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Leeg"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Snelkoppelingen bewerken"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Annuleren"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Sneltoets uitschakelen"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Sneltoets gebruiken"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Kleurinversie"</string>
@@ -1845,6 +1850,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Werkprofiel inschakelen?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Je werk-apps, meldingen, gegevens en andere functies van je werkprofiel worden uitgeschakeld"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Inschakelen"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"App is niet beschikbaar"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is momenteel niet beschikbaar."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Deze app is ontwikkeld voor een oudere versie van Android en werkt mogelijk niet op de juiste manier. Controleer op updates of neem contact op met de ontwikkelaar."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Controleren op update"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Je hebt nieuwe berichten"</string>
@@ -1955,13 +1962,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"De batterij raakt mogelijk leeg voordat deze normaal gesproken wordt opgeladen"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Batterijbesparing is geactiveerd om de batterijduur te verlengen"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Batterijbesparing"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Batterijbesparing kan pas opnieuw worden geactiveerd zodra de batterij bijna leeg is"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Batterij is voldoende opgeladen. Batterijbesparing kan pas opnieuw worden geactiveerd zodra de batterij bijna leeg is."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Telefoon is <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> opgeladen"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Tablet is <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> opgeladen"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Apparaat is <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> opgeladen"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Batterijbesparing staat uit. Functies worden niet meer beperkt."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Batterijbesparing is uitgeschakeld. Functies worden niet meer beperkt."</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Batterijbesparing is uitgeschakeld"</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefoon is voldoende opgeladen. Functies worden niet meer beperkt."</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"Tablet is voldoende opgeladen. Functies worden niet meer beperkt."</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"Apparaat is voldoende opgeladen. Functies worden niet meer beperkt."</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"Map"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android-app"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Bestand"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index b12c655..2018a7f 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"ଆପଣଙ୍କ କ୍ୟାଲେଣ୍ଡର୍‍ ଆକ୍ସେସ୍‍ କରେ"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS ମେସେଜ୍‍ ପଠାନ୍ତୁ ଓ ଦେଖନ୍ତୁ"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"ଷ୍ଟୋରେଜ୍‌"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"ଆପଣଙ୍କ ଡିଭାଇସ୍‌ରେ ଥିବା ଫଟୋ, ମିଡିଆ ଓ ଫାଇଲ୍‍ ଆକ୍ସେସ୍‍ କରେ"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"ମାଇକ୍ରୋଫୋନ୍"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"ଅଡିଓ ରେକର୍ଡ କରେ"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"ଟାପ୍‍, ସ୍ୱାଇପ୍‍, ପିଞ୍ଚ ଓ ଅନ୍ୟାନ୍ୟ ଜେଶ୍ଚର୍‍ ସମ୍ପାଦନ କରିପାରିବ।"</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"ଟିପଚିହ୍ନ ଜେଶ୍ଚର"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"ଡିଭାଇସ୍‌ର ଟିପଚିହ୍ନ ସେନସର୍ ଉପରେ ଜେଶ୍ଚର୍‍ କ୍ୟାପଚର୍‍ କାର୍ଯ୍ୟ କରାଯାଇପାରିବ।"</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"ଷ୍ଟାଟସ୍‌ ବାର୍‌କୁ ଅକ୍ଷମ କିମ୍ୱା ସଂଶୋଧନ କରନ୍ତୁ"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"ଆପ୍‍କୁ, ସ୍ଥିତି ବାର୍‍ ଅକ୍ଷମ କରିବାକୁ କିମ୍ବା ସିଷ୍ଟମ୍‍ ଆଇକନ୍‍ ଯୋଡ଼ିବା କିମ୍ବା ବାହାର କରିବାକୁ ଦେଇଥାଏ।"</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"ଷ୍ଟାଟସ୍‍ ବାର୍‍ ରହିବାକୁ ଦିଅନ୍ତୁ"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ମାତ୍ରା ବଢ଼ାଇ ସୁପାରିଶ ସ୍ତର ବଢ଼ାଉଛନ୍ତି? \n\n ଲମ୍ବା ସମୟ ପର୍ଯ୍ୟନ୍ତ ଉଚ୍ଚ ଶବ୍ଦରେ ଶୁଣିଲେ ଆପଣଙ୍କ ଶ୍ରବଣ ଶକ୍ତି ଖରାପ ହୋଇପାରେ।"</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ଆକ୍ସେସବିଲିଟି ଶର୍ଟକଟ୍‍ ବ୍ୟବହାର କରିବେ?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"ସର୍ଟକଟ୍‌ ଅନ୍‌ ଥିବା ବେଳେ, ଉଭୟ ଭଲ୍ୟୁମ୍‍ ବଟନ୍‍ 3 ସେକେଣ୍ଡ ପାଇଁ ଦବାଇବା ଦ୍ୱାରା ଆକ୍ସେସବିଲିଟି ବୈଶିଷ୍ଟ ଆରମ୍ଭ ହେବ।\n\n ସମ୍ପ୍ରତି ଆକ୍ସେସବିଲିଟି ବୈଶିଷ୍ଟ୍ୟ:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n ସେଟିଙ୍ଗ ଓ ଆକ୍ସେସବିଲିଟିରେ ଆପଣ ବୈଶିଷ୍ଟ୍ୟ ବଦଳାଇ ପାରିବେ।"</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"ଖାଲି କରନ୍ତୁ"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"ସର୍ଟକଟଗୁଡ଼ିକୁ ସମ୍ପାଦନ କରନ୍ତୁ"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"ବାତିଲ୍ କରନ୍ତୁ"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"ଶର୍ଟକଟ୍‍ ବନ୍ଦ କରନ୍ତୁ"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"ଶର୍ଟକଟ୍‍ ବ୍ୟବହାର କରନ୍ତୁ"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"ରଙ୍ଗ ବଦଳାଇବାର ସୁବିଧା"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"ୱର୍କ ପ୍ରୋଫାଇଲ୍‌କୁ ଚାଲୁ କରିବେ?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"ଆପଣଙ୍କର କାର୍ଯ୍ୟକାରୀ ଆପ୍‌, ବିଜ୍ଞପ୍ତି, ଡାଟା ଓ ଅନ୍ୟ ୱର୍କ ପ୍ରୋଫାଇଲ୍‌ଗୁଡ଼ିକ ଚାଲୁ ହୋଇଯିବ"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"ଅନ୍ କରନ୍ତୁ"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ଏହି ଆପ୍‌କୁ Androidର ପୁରୁଣା ଭର୍ସନ୍ ପାଇଁ ନିର୍ମାଣ କରାଯାଇଥିଲା ଏବଂ ଠିକ୍ ଭାବେ କାମ କରିନପାରେ। ଏହାପାଇଁ ଅପଡେଟ୍‌ ଅଛି କି ନାହିଁ ଯାଞ୍ଚ କରନ୍ତୁ କିମ୍ବା ଡେଭେଲପର୍‌ଙ୍କ ସହିତ ସମ୍ପର୍କ କରନ୍ତୁ।"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"ଅପଡେଟ୍‌ ପାଇଁ ଯାଞ୍ଚ କରନ୍ତୁ"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"ଆପଣଙ୍କ ପାଖରେ ନୂଆ ମେସେଜ୍‍ ରହିଛି"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"ସାଧାରଣ ଭାବରେ ଚାର୍ଜ୍ କରିବା ପୂର୍ବରୁ ବ୍ୟାଟେରୀ ସରିଯାଇପାରେ"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"ବ୍ୟାଟେରୀର ସମୟକୁ ବଢ଼ାଇବା ପାଇଁ ବ୍ୟଟେରୀ ସେଭର୍‍କୁ କାର୍ଯ୍ୟକାରୀ କରାଯାଇଛି"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"ବ୍ୟାଟେରୀ ସେଭର୍"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"ବ୍ୟାଟେରୀ ଟାର୍ଜ କମ୍ ନହେବା ପର୍ଯ୍ୟନ୍ତ ବ୍ୟାଟେରୀ ସେଭର୍‌ ପୁନଃସକ୍ରିୟ ହୁଏ ନାହିଁ"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"ବ୍ୟାଟେରୀ ଏକ ପର୍ଯ୍ୟାପ୍ତ ସ୍ତର ପର୍ଯ୍ୟନ୍ତ ଚାର୍ଜ ହୋଇଛି। ବ୍ୟାଟେରୀ ପୁଣି କମ୍ ନହେବା ପର୍ଯ୍ୟନ୍ତ ବ୍ୟାଟେରୀ ସେଭର୍‌ ପୁନଃସକ୍ରିୟ ହେବନାହିଁ।"</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"ଫୋନ୍ <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ଚାର୍ଜ ହୋଇଛି"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"ଟାବ୍‌ଲେଟ୍‌ <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ଚାର୍ଜ ହୋଇଛି"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"ଡିଭାଇସ୍ <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ଚାର୍ଜ ହୋଇଛି"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"ବ୍ୟାଟେରୀ ସେଭର୍ ବନ୍ଦ ଅଛି ବୈଶିଷ୍ଟ୍ୟ ଆଉ ପ୍ରତିବନ୍ଧିତ ନୁହେଁ।"</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"ବ୍ୟାଟେରୀ ସେଭର୍‌ ବନ୍ଦ ଅଛି। ବୈଶିଷ୍ଟ୍ୟ ଆଉ ପ୍ରତିବନ୍ଧିତ ନୁହେଁ।"</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"ଫୋଲ୍ଡର୍"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android ଆପ୍ଲିକେସନ୍"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"ଫାଇଲ୍"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 72e695e..552f77d 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"ਤੁਹਾਡੇ ਕੈਲੰਡਰ ਤੱਕ ਪਹੁੰਚ ਕਰਨ"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS ਸੁਨੇਹੇ ਭੇਜੋ ਅਤੇ ਦੇਖੋ"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"ਸਟੋਰੇਜ"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"ਆਪਣੇ ਡੀਵਾਈਸ \'ਤੇ ਫ਼ੋਟੋਆਂ, ਮੀਡੀਆ ਅਤੇ ਫ਼ਾਈਲਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨਾ"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">" ਆਡੀਓ  ਰਿਕਾਰਡ ਕਰਨ"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"ਟੈਪ ਕਰ ਸਕਦੀ ਹੈ, ਸਵਾਈਪ ਕਰ ਸਕਦੀ ਹੈ, ਚੂੰਢੀ ਭਰ ਸਕਦੀ ਹੈ, ਅਤੇ ਹੋਰ ਸੰਕੇਤ ਕਰ ਸਕਦੀ ਹੈ।"</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"ਫਿੰਗਰਪ੍ਰਿੰਟ ਸੰਕੇਤ"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"ਡੀਵਾਈਸਾਂ ਦੇ ਫਿੰਗਰਪ੍ਰਿੰਟ ਸੈਂਸਰ \'ਤੇ ਕੀਤੇ ਗਏ ਸੰਕੇਤਾਂ ਨੂੰ ਕੈਪਚਰ ਕਰ ਸਕਦੇ ਹਨ।"</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"ਸਥਿਤੀ ਪੱਟੀ ਬੰਦ ਕਰੋ ਜਾਂ ਸੰਸ਼ੋਧਿਤ ਕਰੋ"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"ਐਪ ਨੂੰ ਸਥਿਤੀ ਪੱਟੀ ਨੂੰ ਚਾਲੂ ਕਰਨ ਜਾਂ ਸਿਸਟਮ ਪ੍ਰਤੀਕਾਂ ਨੂੰ ਜੋੜਨ ਅਤੇ ਹਟਾਉਣ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।"</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"ਸਥਿਤੀ ਪੱਟੀ ਬਣਨ ਦਿਓ"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ਕੀ ਵੌਲਿਊਮ  ਸਿਫ਼ਾਰਸ਼  ਕੀਤੇ ਪੱਧਰ ਤੋਂ ਵਧਾਉਣੀ ਹੈ?\n\nਲੰਮੇ ਸਮੇਂ ਤੱਕ ਉੱਚ ਵੌਲਿਊਮ ਤੇ ਸੁਣਨ ਨਾਲ ਤੁਹਾਡੀ ਸੁਣਨ ਸ਼ਕਤੀ ਨੂੰ ਨੁਕਸਾਨ ਪਹੁੰਚ ਸਕਦਾ ਹੈ।"</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ਕੀ ਪਹੁੰਚਯੋਗਤਾ ਸ਼ਾਰਟਕੱਟ ਵਰਤਣਾ ਹੈ?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"ਸ਼ਾਰਟਕੱਟ ਚਾਲੂ ਹੋਣ \'ਤੇ, ਕਿਸੇ ਪਹੁੰਚਯੋਗਤਾ ਵਿਸ਼ੇਸ਼ਤਾ ਨੂੰ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਅਵਾਜ਼ ਬਟਨਾਂ ਨੂੰ 3 ਸਕਿੰਟ ਲਈ ਦਬਾ ਕੇ ਰੱਖੋ।\n\n ਵਰਤਮਾਨ ਪਹੁੰਚਯੋਗਤਾ ਵਿਸ਼ੇਸ਼ਤਾ:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n ਤੁਸੀਂ ਸੈਟਿੰਗਾਂ &gt; ਪਹੁੰਚਯੋਗਤਾ ਵਿੱਚ ਵਿਸ਼ੇਸ਼ਤਾ ਨੂੰ ਬਦਲ ਸਕਦੇ ਹੋ।"</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"ਖਾਲੀ"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"ਸ਼ਾਰਟਕੱਟਾਂ ਦਾ ਸੰਪਾਦਨ ਕਰੋ"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"ਰੱਦ ਕਰੋ"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"ਸ਼ਾਰਟਕੱਟ ਬੰਦ ਕਰੋ"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"ਸ਼ਾਰਟਕੱਟ ਦੀ ਵਰਤੋਂ ਕਰੋ"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"ਰੰਗ ਦੀ ਉਲਟੀ ਤਰਤੀਬ"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"ਕੀ ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ ਚਾਲੂ ਕਰਨੀ ਹੈ?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"ਤੁਹਾਡੀਆਂ ਕਾਰਜ-ਸਥਾਨ ਐਪਾਂ, ਸੂਚਨਾਵਾਂ, ਡਾਟਾ ਅਤੇ ਹੋਰ ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਚਾਲੂ ਕੀਤੀਆਂ ਜਾਣਗੀਆਂ"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"ਚਾਲੂ ਕਰੋ"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ਇਹ ਐਪ Android ਦੇ ਕਿਸੇ ਵਧੇਰੇ ਪੁਰਾਣੇ ਵਰਜਨ ਲਈ ਬਣਾਈ ਗਈ ਸੀ ਅਤੇ ਸ਼ਾਇਦ ਸਹੀ ਢੰਗ ਨਾਲ ਕੰਮ ਨਾ ਕਰੇ। ਅੱਪਡੇਟਾਂ ਲਈ ਜਾਂਚ ਕਰੋ ਜਾਂ ਵਿਕਾਸਕਾਰ ਨਾਲ ਸੰਪਰਕ ਕਰੋ।"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"ਅੱਪਡੇਟ ਦੀ ਜਾਂਚ ਕਰੋ"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"ਤੁਹਾਨੂੰ ਨਵੇਂ ਸੁਨੇਹੇ ਪ੍ਰਾਪਤ ਹੋਏ ਹਨ"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"ਬੈਟਰੀ ਚਾਰਜ ਕਰਨ ਦੇ ਮਿੱਥੇ ਸਮੇਂ ਤੋਂ ਪਹਿਲਾਂ ਸ਼ਾਇਦ ਬੈਟਰੀ ਖਤਮ ਹੋ ਜਾਵੇ"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"ਬੈਟਰੀ ਲਾਈਫ਼ ਵਧਾਉਣ ਲਈ ਬੈਟਰੀ ਸੇਵਰ ਚਾਲੂ ਕੀਤਾ ਗਿਆ"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"ਬੈਟਰੀ ਸੇਵਰ"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"ਬੈਟਰੀ ਸੇਵਰ ਦੁਬਾਰਾ ਬੈਟਰੀ ਘਟਣ ਤੱਕ ਮੁੜ-ਕਿਰਿਆਸ਼ੀਲ ਨਹੀਂ ਹੁੰਦਾ ਹੈ"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"ਬੈਟਰੀ ਲੋੜੀਂਦੇ ਪੱਧਰ ਤੱਕ ਚਾਰਜ ਹੋ ਗਈ ਹੈ। ਬੈਟਰੀ ਸੇਵਰ ਦੁਬਾਰਾ ਬੈਟਰੀ ਘਟਣ ਤੱਕ ਮੁੜ-ਕਿਰਿਆਸ਼ੀਲ ਨਹੀਂ ਹੁੰਦਾ।"</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"ਫ਼ੋਨ <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ਚਾਰਜ ਹੋਇਆ"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"ਟੈਬਲੈੱਟ <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ਚਾਰਜ ਹੋਇਆ"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"ਡੀਵਾਈਸ <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ਚਾਰਜ ਹੋਇਆ"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"ਬੈਟਰੀ ਸੇਵਰ ਬੰਦ ਹੈ। ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਹੁਣ ਪ੍ਰਤਿਬੰਧਿਤ ਨਹੀਂ ਹਨ।"</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"ਬੈਟਰੀ ਸੇਵਰ ਬੰਦ ਕੀਤਾ ਗਿਆ। ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਹੁਣ ਪ੍ਰਤਿਬੰਧਿਤ ਨਹੀਂ ਹਨ।"</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"ਫੋਲਡਰ"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android ਐਪਲੀਕੇਸ਼ਨ"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"ਫ਼ਾਈਲ"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 4f6e748..5f94b18 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -293,7 +293,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"dostęp do kalendarza"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"wysyłanie i wyświetlanie SMS‑ów"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Pamięć"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"dostęp do zdjęć, multimediów i plików na Twoim urządzeniu"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofon"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"nagrywanie dźwięku"</string>
@@ -319,6 +320,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Obsługuje kliknięcia, przesunięcia, ściągnięcia palców i inne gesty."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Gesty związane z odciskiem palca"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Może przechwytywać gesty wykonywane na czytniku linii papilarnych w urządzeniu."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"wyłączanie lub zmienianie paska stanu"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Pozwala aplikacji na wyłączanie paska stanu oraz dodawanie i usuwanie ikon systemowych."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"działanie jako pasek stanu"</string>
@@ -1654,6 +1659,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Zwiększyć głośność ponad zalecany poziom?\n\nSłuchanie głośno przez długi czas może uszkodzić Twój słuch."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Użyć skrótu do ułatwień dostępu?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Gdy skrót jest włączony, jednoczesne naciśnięcie przez trzy sekundy obu klawiszy sterowania głośnością uruchomi funkcję ułatwień dostępu.\n\nBieżąca funkcja ułatwień dostępu:\n<xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\nFunkcję możesz zmienić, wybierając Ustawienia &gt; Ułatwienia dostępu."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Puste"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Edytuj skróty"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Anuluj"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Wyłącz skrót"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Użyj skrótu"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Odwrócenie kolorów"</string>
@@ -1909,6 +1917,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Włączyć profil służbowy?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Aplikacje do pracy, powiadomienia, dane i inne funkcje profilu do pracy zostaną włączone"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Włącz"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ta aplikacja jest na starszą wersję Androida i może nie działać prawidłowo. Sprawdź dostępność aktualizacji lub skontaktuj się z programistą."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Sprawdź dostępność aktualizacji"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Masz nowe wiadomości"</string>
@@ -2021,13 +2033,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Bateria może się wyczerpać przed zwykłą porą ładowania"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Włączono Oszczędzanie baterii, by wydłużyć czas pracy na baterii"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Oszczędzanie baterii"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Oszczędzanie baterii nie włączy się ponownie do czasu rozładowania baterii"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Poziom naładowania baterii jest wystarczający. Oszczędzanie baterii nie włączy się ponownie do czasu rozładowania baterii."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Poziom naładowania telefonu: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Poziom naładowania tabletu: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Poziom naładowania urządzenia: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Oszczędzanie baterii jest wyłączone. Funkcje nie są już ograniczone."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Oszczędzanie baterii zostało wyłączone. Funkcje nie są już ograniczone."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"Folder"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Aplikacja na Androida"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Plik"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index f4c9e34..567d6d4 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -287,7 +287,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"acesse sua agenda"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"envie e veja mensagens SMS"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Armazenamento"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"Arquivos e mídia"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"acesse fotos, mídia e arquivos do dispositivo"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Microfone"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"grave áudio"</string>
@@ -313,6 +313,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Toque, deslize, faça gestos de pinça e faça outros gestos."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Gestos de impressão digital"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Pode captar gestos realizados no sensor de impressão digital do dispositivo."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Fazer uma captura de tela"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Pode fazer uma captura de tela."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"desativar ou modificar a barra de status"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Permite que o app desative a barra de status ou adicione e remova ícones do sistema."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"ser a barra de status"</string>
@@ -1610,6 +1612,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Aumentar o volume acima do nível recomendado?\n\nOuvir em volume alto por longos períodos pode danificar sua audição."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Usar atalho de Acessibilidade?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Quando o atalho está ativado, pressione os dois botões de volume por três segundos para iniciar um recurso de acessibilidade.\n\n Recurso de acessibilidade atual:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n É possível alterar o recurso em Configurações &gt; Acessibilidade."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Limpar"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Editar atalhos"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Cancelar"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Desativar atalho"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Usar atalho"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Inversão de cores"</string>
@@ -1845,6 +1850,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Ativar o perfil de trabalho?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Seus apps, notificações, dados e outros recursos do perfil de trabalho serão ativados"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Ativar"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"O app não está disponível"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> não está disponível no momento."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Este app foi criado para uma versão mais antiga do Android e pode não funcionar corretamente. Tente verificar se há atualizações ou entre em contato com o desenvolvedor."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Procurar atualizações"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Você tem mensagens novas"</string>
@@ -1955,13 +1962,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"A bateria pode acabar antes da recarga normal"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"A Economia de bateria foi ativada para aumentar a duração da carga"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Economia de bateria"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"A Economia de bateria só será reativada quando a bateria estiver acabando novamente"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"A bateria foi carregada até o nível suficiente. A Economia de bateria só será reativada quando a bateria estiver acabando novamente."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Smartphone <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> carregado"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Tablet <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> carregado"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Dispositivo <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> carregado"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Economia de bateria desativada. Os recursos não estão mais restritos."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Economia de bateria desativada. Os recursos não estão mais restritos."</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"\"Economia de bateria\" desativada"</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Não há carga suficiente no smartphone. Os recursos não estão mais restritos."</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"Não há carga suficiente no tablet. Os recursos não estão mais restritos."</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"Não há carga suficiente no dispositivo. Os recursos não estão mais restritos."</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"Pasta"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Aplicativo Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Arquivo"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 65913ce..b746c92 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -287,7 +287,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"aceder ao calendário"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"enviar e ver mensagens SMS"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Armazenamento"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"Ficheiros e multimédia"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"aceder a fotos, multimédia e ficheiros no dispositivo"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Microfone"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"gravar áudio"</string>
@@ -313,6 +313,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"É possível tocar, deslizar rapidamente, juntar os dedos e realizar outros gestos"</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Gestos de impressão digital"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Pode capturar gestos realizados no sensor de impressões digitais do dispositivo."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Tirar captura de ecrã"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"É possível tirar uma captura de ecrã."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"desativar ou modificar barra de estado"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Permite à aplicação desativar a barra de estado ou adicionar e remover ícones do sistema."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"ser apresentada na barra de estado"</string>
@@ -1610,6 +1612,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Aumentar o volume acima do nível recomendado?\n\nOuvir com um volume elevado durante longos períodos poderá ser prejudicial para a sua audição."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Pretende utilizar o atalho de acessibilidade?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Quando o atalho está ativado, premir ambos os botões de volume durante 3 segundos inicia uma funcionalidade de acessibilidade.\n\n Funcionalidade de acessibilidade atual:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Pode alterar a funcionalidade em Definições &gt; Acessibilidade."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Esvaziar"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Editar atalhos"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Cancelar"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Desativar atalho"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Utilizar atalho"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Inversão de cores"</string>
@@ -1731,7 +1736,7 @@
     <string name="restr_pin_try_later" msgid="5897719962541636727">"Tente novamente mais tarde"</string>
     <string name="immersive_cling_title" msgid="2307034298721541791">"Visualização de ecrã inteiro"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Para sair, deslize rapidamente para baixo a partir da parte superior."</string>
-    <string name="immersive_cling_positive" msgid="7047498036346489883">"Compreendi"</string>
+    <string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string>
     <string name="done_label" msgid="7283767013231718521">"Concluído"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Controlo de deslize circular das horas"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Controlo de deslize circular dos minutos"</string>
@@ -1845,6 +1850,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Ativar o perfil de trabalho?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"As aplicações de trabalho, as notificações, os dados e outras funcionalidades do perfil de trabalho serão desativados"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Ativar"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"A app não está disponível"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"De momento, a app <xliff:g id="APP_NAME">%1$s</xliff:g> não está disponível."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Esta aplicação foi concebida para uma versão mais antiga do Android e pode não funcionar corretamente. Experimente verificar se existem atualizações ou contacte o programador."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Verificar se existem atualizações"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Tem mensagens novas"</string>
@@ -1955,13 +1962,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Pode ficar sem bateria antes do carregamento habitual"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Poupança de bateria ativada para prolongar a duração da bateria"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Poupança de bateria"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"A Poupança de bateria não será reativada até a bateria estar novamente fraca"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"A bateria foi carregada até um nível suficiente. A Poupança de bateria não será reativada até a bateria estar novamente fraca."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"O telemóvel tem um nível de carga de <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"O tablet tem um nível de carga de <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"O dispositivo tem um nível de carga de <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"A Poupança de bateria está desativada. As funcionalidades já não estão restritas."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"A Poupança de bateria está desativada. As funcionalidades já não estão restritas."</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"A Poupança de bateria está desativada"</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"O telemóvel tem carga suficiente. As funcionalidades já não estão restritas."</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"O tablet tem carga suficiente. As funcionalidades já não estão restritas."</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"O dispositivo tem carga suficiente. As funcionalidades já não estão restritas."</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"Pasta"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Aplicação para Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Ficheiro"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index f4c9e34..567d6d4 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -287,7 +287,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"acesse sua agenda"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"envie e veja mensagens SMS"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Armazenamento"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"Arquivos e mídia"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"acesse fotos, mídia e arquivos do dispositivo"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Microfone"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"grave áudio"</string>
@@ -313,6 +313,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Toque, deslize, faça gestos de pinça e faça outros gestos."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Gestos de impressão digital"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Pode captar gestos realizados no sensor de impressão digital do dispositivo."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Fazer uma captura de tela"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Pode fazer uma captura de tela."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"desativar ou modificar a barra de status"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Permite que o app desative a barra de status ou adicione e remova ícones do sistema."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"ser a barra de status"</string>
@@ -1610,6 +1612,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Aumentar o volume acima do nível recomendado?\n\nOuvir em volume alto por longos períodos pode danificar sua audição."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Usar atalho de Acessibilidade?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Quando o atalho está ativado, pressione os dois botões de volume por três segundos para iniciar um recurso de acessibilidade.\n\n Recurso de acessibilidade atual:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n É possível alterar o recurso em Configurações &gt; Acessibilidade."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Limpar"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Editar atalhos"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Cancelar"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Desativar atalho"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Usar atalho"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Inversão de cores"</string>
@@ -1845,6 +1850,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Ativar o perfil de trabalho?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Seus apps, notificações, dados e outros recursos do perfil de trabalho serão ativados"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Ativar"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"O app não está disponível"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> não está disponível no momento."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Este app foi criado para uma versão mais antiga do Android e pode não funcionar corretamente. Tente verificar se há atualizações ou entre em contato com o desenvolvedor."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Procurar atualizações"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Você tem mensagens novas"</string>
@@ -1955,13 +1962,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"A bateria pode acabar antes da recarga normal"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"A Economia de bateria foi ativada para aumentar a duração da carga"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Economia de bateria"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"A Economia de bateria só será reativada quando a bateria estiver acabando novamente"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"A bateria foi carregada até o nível suficiente. A Economia de bateria só será reativada quando a bateria estiver acabando novamente."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Smartphone <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> carregado"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Tablet <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> carregado"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Dispositivo <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> carregado"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Economia de bateria desativada. Os recursos não estão mais restritos."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Economia de bateria desativada. Os recursos não estão mais restritos."</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"\"Economia de bateria\" desativada"</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Não há carga suficiente no smartphone. Os recursos não estão mais restritos."</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"Não há carga suficiente no tablet. Os recursos não estão mais restritos."</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"Não há carga suficiente no dispositivo. Os recursos não estão mais restritos."</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"Pasta"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Aplicativo Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Arquivo"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 5b50ef7..5b115375 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -290,7 +290,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"acceseze calendarul"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"trimită și să vadă mesajele SMS"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Stocare"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"acceseze fotografiile, conținutul media și fișierele de pe dispozitiv"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Microfon"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"înregistreze sunet"</string>
@@ -316,6 +317,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Poate atinge, glisa, ciupi sau folosi alte gesturi."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Gesturi ce implică amprente"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Poate reda gesturile făcute pe senzorul de amprentă al dispozitivului."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"dezactivare sau modificare bare de stare"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Permite aplicației să dezactiveze bara de stare sau să adauge și să elimine pictograme de sistem."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"să fie bara de stare"</string>
@@ -1632,6 +1637,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Ridicați volumul mai sus de nivelul recomandat?\n\nAscultarea la volum ridicat pe perioade lungi de timp vă poate afecta auzul."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Utilizați comanda rapidă pentru accesibilitate?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Când comanda rapidă este activată, dacă apăsați ambele butoane de volum timp de 3 secunde, veți lansa o funcție de accesibilitate.\n\n Funcția actuală de accesibilitate:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Puteți schimba funcția în Setări &gt; Accesibilitate."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Goliți"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Editați comenzile rapide"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Anulați"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Dezactivați comanda rapidă"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Utilizați comanda rapidă"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Inversarea culorilor"</string>
@@ -1877,6 +1885,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Activați profilul de serviciu?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Se vor activa aplicațiile dvs. de serviciu, notificările, datele și alte funcții ale profilului de serviciu"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Activați"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Această aplicație a fost creată pentru o versiune Android mai veche și este posibil să nu funcționeze corect. Încercați să căutați actualizări sau contactați dezvoltatorul."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Căutați actualizări"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Aveți mesaje noi"</string>
@@ -1988,13 +2000,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Bateria se poate descărca înainte de încărcarea obișnuită"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Economisirea bateriei este activată pentru a prelungi durata de funcționare a bateriei"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Economisirea bateriei"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Economisirea bateriei se va reactiva când bateria va fi descărcată din nou"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Bateria s-a încărcat la un nivel suficient. Economisirea bateriei se va reactiva când bateria va fi descărcată din nou."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Telefonul este încărcat <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Tableta este încărcată <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Dispozitivul este încărcat <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Economisirea bateriei este dezactivată. Funcțiile nu mai sunt limitate."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Economisirea bateriei a fost dezactivată. Funcțiile nu mai sunt limitate."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"Dosar"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Aplicație Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Fișier"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 9e8e13a..fa283d8 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -293,7 +293,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"доступ к календарю"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"отправлять и просматривать SMS-сообщения"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Хранилище"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"доступ к фото, мультимедиа и файлам на вашем устройстве"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Микрофон"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"записывать аудио"</string>
@@ -319,6 +320,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Может выполнять жесты нажатия, пролистывания, масштабирования и т. д."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Регистрировать жесты на сканере отпечатков пальцев"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Использовать сканер отпечатков пальцев для дополнительных жестов."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"Отключение/изменение строки состояния"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Приложение сможет отключать строку состояния, а также добавлять и удалять системные значки."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"Замена строки состояния"</string>
@@ -1654,6 +1659,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Установить громкость выше рекомендуемого уровня?\n\nВоздействие громкого звука в течение долгого времени может привести к повреждению слуха."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Использовать быстрое включение?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Чтобы использовать функцию специальных возможностей, когда она включена, нажмите и удерживайте три секунды обе кнопки регулировки громкости.\n\nТекущая функция специальных возможностей:\n<xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\nВы можете изменить ее в разделе \"Настройки &gt; Специальные возможности\"."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Очистить"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Изменить быстрые клавиши"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Отмена"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Деактивировать быстрое включение"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Использовать быстрое включение"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Инверсия цветов"</string>
@@ -1909,6 +1917,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Включить рабочий профиль?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Будут включены корпоративные приложения, уведомления, данные и другие функции рабочего профиля."</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Включить"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Это приложение было создано для более ранней версии Android и может работать со сбоями. Проверьте наличие обновлений или свяжитесь с разработчиком."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Проверить обновления"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Новые сообщения"</string>
@@ -2021,13 +2033,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Батарея может разрядиться"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Чтобы увеличить время работы от батареи, был включен режим энергосбережения."</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Режим энергосбережения"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Режим энергосбережения будет включен снова при низком уровне заряда батареи."</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Батарея заряжена достаточно. Режим энергосбережения будет включен снова при низком уровне заряда батареи."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Телефон заряжен на <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Планшет заряжен на <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Устройство заряжено на <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Режим энергосбережения выключен. Функции больше не ограничены."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Режим энергосбережения выключен. Функции больше не ограничены."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"Папка"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Приложение Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Файл"</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index a28e036..3899cd7 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"ඔබේ දින දර්ශනයට පිවිසෙන්න"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"කෙටි පණිවිඩ"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS පණිවිඩ යැවීම සහ බැලීම"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"ආචයනය"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"ඔබේ උපාංගයේ ඇති ඡායාරූප, මාධ්‍ය සහ ගොනුවලට පිවිසීම"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"මයික්‍රොෆෝනය"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"ශ්‍රව්‍ය පටිගත කරන්න"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"තට්ටු කිරීමට, ස්වයිප් කිරීමට, පින්ච් කිරීමට, සහ වෙනත් අභින සිදු කිරීමට හැකිය."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"ඇඟිලි සලකුණු ඉංගිත"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"උපාංගයෙහි ඇඟිලි සලකුණු සංවේදකය මත සිදු කරන ඉංගිත ග්‍රහණය කළ හැකිය."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"තත්ව තීරුව අබල කරන්න හෝ වෙනස් කරන්න"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"තත්ව තීරුව අක්‍රිය කිරීමට හෝ පද්ධති නිරූපක එකතු හෝ ඉවත් කිරීමට යෙදුමට අවසර දේ."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"තත්ත්ව තීරුව බවට පත්වීම"</string>
@@ -1612,6 +1617,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"නිර්දේශිතයි මට්ටමට වඩා ශබ්දය වැඩිද?\n\nදිගු කාලයක් සඳහා ඉහළ ශබ්දයක් ඇසීමෙන් ඇතැම් විට ඔබගේ ඇසීමට හානි විය හැක."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ප්‍රවේශ්‍යතා කෙටිමඟ භාවිතා කරන්නද?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"කෙටිමඟ සක්‍රිය විට, හඬ බොත්තම් දෙකම තත්පර 3ක් අල්ලාගෙන සිටීමෙන් ප්‍රවේශ්‍යත අංගයක් ඇරඹේ.\n\n වත්මන් ප්‍රවේශ්‍යතා අංගය:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n සැකසීම් &gt; ප්‍රවේශ්‍යතාව තුළ ඔබට අංගය වෙනස් කළ හැක."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"හිස්"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"කෙටිමං සංස්කරණ කරන්න"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"අවලංගු කරන්න"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"කෙටිමඟ ක්‍රියාවිරහිත කරන්න"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"කෙටිමඟ භාවිතා කරන්න"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"වර්ණ අපවර්තනය"</string>
@@ -1847,6 +1855,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"කාර්යාල පැතිකඩ ක්‍රියාත්මක කරන්නද?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"ඔබගේ වැඩ යෙදුම්, දැනුම්දීම්, දත්ත සහ වෙනත් කාර්යාල පැතිකඩ විශේෂාංග ක්‍රියාත්මක කරනු ඇත"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"ක්‍රියාත්මක කරන්න"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"මෙම යෙදුම Android හි පැරණි අනුවාදයක් සඳහා තනා ඇති අතර නිසියාකාරව ක්‍රියා නොකරනු ඇත. යාවත්කාලීන සඳහා පරික්ෂා කිරීම උත්සාහ කරන්න, නැතහොත් සංවර්ධක අමතන්න."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"යාවත්කාලීන සඳහා පරික්ෂා කරන්න"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"ඔබට නව පණිවිඩ තිබේ"</string>
@@ -1957,13 +1969,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"බැටරිය සුපුරුදු ආරෝපණයට පෙර ඉවර විය හැක"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"බැටරි සුරැකුම බැටරි ආයු කාලය දීර්ඝ කිරීමට සක්‍රිය කෙරිණි"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"බැටරි සුරැකුම"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"බැටරිය නැවතත් අඩු වන තෙක් බැටරි සුරැකුම යළි ක්‍රියාත්මක නොවේ"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"බැටරිය ප්‍රමාණවත් මට්ටමකට ආරෝපණ කර ඇත. බැටරිය නැවතත් අඩු වන තෙක් බැටරි සුරැකුම යළි ක්‍රියාත්මක නොවේ"</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"<xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> දුරකථනය ආරෝපණ විය"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"<xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ටැබ්ලටය ආරෝපණ කළා"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"<xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> උපාංගය ආරෝපණ විය"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"බැටරි සුරැකුම අක්‍රියයි. විශේෂාංග තවදුරටත් සීමිත නොවේ."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"බැටරි සුරැකුම අක්‍රියයි. විශේෂාංග තවදුරටත් සීමිත නොවේ."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"ෆෝල්ඩරය"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android යෙදුම"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"ගොනුව"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 6bf54b0..f48368f 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -293,7 +293,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"prístup ku kalendáru"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"posielanie a zobrazovanie SMS"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Úložisko"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"prístup k fotkám, médiám a súborom v zariadení"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofón"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"nahrávanie zvuku"</string>
@@ -319,6 +320,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Je možné použiť klepnutie, prejdenie, stiahnutie prstami a ďalšie gestá."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Gestá odtlačkom prsta"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Dokáže zaznamenať gestá na senzore odtlačkov prstov zariadenia."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"zakázanie alebo zmeny stavového riadka"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Umožňuje aplikácii vypnúť stavový riadok alebo pridať a odstrániť systémové ikony."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"vydávanie sa za stavový riadok"</string>
@@ -1654,6 +1659,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Zvýšiť hlasitosť nad odporúčanú úroveň?\n\nDlhodobé počúvanie pri vysokej hlasitosti môže poškodiť váš sluch."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Použiť skratku dostupnosti?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Keď je skratka zapnutá, stlačením obidvoch tlačidiel hlasitosti na tri sekundy spustíte funkciu dostupnosti.\n\n Aktuálna funkcia dostupnosti:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Funkciu môžete zmeniť v časti Nastavenia &gt; Dostupnosť."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Prázdne"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Upraviť skratky"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Zrušiť"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Vypnúť skratku"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Použiť skratku"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Inverzia farieb"</string>
@@ -1909,6 +1917,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Zapnúť pracovný profil?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Pracovné aplikácie, upozornenia, dáta a ďalšie funkcie pracovného profilu sa zapnú"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Zapnúť"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Táto aplikácia bola zostavená pre staršiu verziu Androidu a nemusí správne fungovať. Skúste skontrolovať dostupnosť aktualizácií alebo kontaktovať vývojára."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Skontrolovať dostupnosť aktualizácie"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Máte nové správy."</string>
@@ -2021,13 +2033,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Batéria sa môže vybiť pred obvyklým nabitím"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Bol aktivovaný šetrič batérie na predĺženie výdrže batérie"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Šetrič batérie"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Šetrič batérie sa znova aktivuje až pri nízkej úrovni nabitia batérie"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Batéria bola nabitá na dostatočnú úroveň. Šetrič batérie sa znova aktivuje až pri nízkej úrovni nabitia batérie."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Úroveň nabitia telefónu: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Úroveň nabitia tabletu: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Úroveň nabitia zariadenia: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Šetrič batérie je vypnutý. Funkcie už nie sú obmedzené."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Šetrič batérie bol vypnutý. Funkcie už nie sú obmedzené."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"Priečinok"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Aplikácia pre Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Súbor"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 7aa91f7..6343e0e 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -293,7 +293,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"dostop do koledarja"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"pošiljanje in ogled sporočil SMS"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Shramba"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"dostop do fotografij, predstavnosti in datotek v napravi"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofon"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"snemanje zvoka"</string>
@@ -319,6 +320,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Mogoče je izvajanje dotikov, vlečenja, primikanja in razmikanja prstov ter drugih potez."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Poteze po tipalu prstnih odtisov"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Prepoznava poteze, narejene po tipalu prstnih odtisov naprave."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"onemogočanje ali spreminjanje vrstice stanja"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Aplikacijam omogoča onemogočenje vrstice stanja ali dodajanje in odstranjevanje ikon sistema."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"postane vrstica stanja"</string>
@@ -1654,6 +1659,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Ali želite povečati glasnost nad priporočeno raven?\n\nDolgotrajno poslušanje pri veliki glasnosti lahko poškoduje sluh."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Želite uporabljati bližnjico funkcij za ljudi s posebnimi potrebami?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Ko je bližnjica vklopljena, pritisnite gumba za glasnost in ju pridržite tri sekunde, če želite zagnati funkcijo za ljudi s posebnimi potrebami.\n\n Trenutna funkcija za ljudi s posebnimi potrebami:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Funkcijo lahko spremenite v »Nastavitve &gt; Funkcije za ljudi s posebnimi potrebami«."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Prazno"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Uredi bližnjice"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Prekliči"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Izklopi bližnjico"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Uporabi bližnjico"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Inverzija barv"</string>
@@ -1909,6 +1917,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Želite vklopiti delovni profil?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Vklopili boste svoje delovne aplikacije, obvestila, podatke in druge funkcije delovnega profila"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Vklop"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ta aplikacija je bila zasnovana za starejšo različico Androida in morda ne bo delovala pravilno. Preverite, ali so na voljo posodobitve, ali pa se obrnite na razvijalca."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Preveri, ali je na voljo posodobitev"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Imate nova sporočila."</string>
@@ -2021,13 +2033,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Akumulator se bo morda izpraznil, preden ga običajno priključite na polnjenje"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Vklopilo se je varčevanje z energijo akumulatorja za podaljšanje časa delovanja akumulatorja"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Varčevanje z energijo akumulatorja"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Varčevanje z energijo akumulatorja se ne bo znova aktiviralo, dokler akumulator ne bo znova skoraj prazen"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Akumulator je dovolj napolnjen. Varčevanje z energijo akumulatorja se ne bo znova aktiviralo, dokler akumulator ne bo znova skoraj prazen."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Napolnjenost telefona: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Napolnjenost tabličnega računalnika: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Napolnjenost naprave: <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Varčevanje z energijo akumulatorja je izklopljeno. Funkcije niso več omejene."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Varčevanje z energijo akumulatorja je izklopljeno. Funkcije niso več omejene."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"Mapa"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Aplikacija za Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Datoteka"</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index f38d354..ceba0be 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"qasje te kalendari yt"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"dërgo dhe shiko mesazhet SMS"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Hapësira e ruajtjes"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"qasjen te fotografitë, përmbajtjet audio-vizuale dhe skedarët në pajisje"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofoni"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"regjistro audio"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Mund të trokasë, rrëshqasë, bashkojë gishtat dhe kryejë gjeste të tjera."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Gjestet e gjurmës së gishtit"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Mund të regjistrojë gjestet e kryera në sensorin e gjurmës së gishtit të pajisjes."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"çaktivizo ose modifiko shiritin e statusit"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Lejon aplikacionin të çaktivizojë shiritin e statusit dhe të heqë ikonat e sistemit."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"të bëhet shiriti i statusit"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Të ngrihet volumi mbi nivelin e rekomanduar?\n\nDëgjimi me volum të lartë për periudha të gjata mund të dëmtojë dëgjimin."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Të përdoret shkurtorja e qasshmërisë?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Kur shkurtorja është e aktivizuar, shtypja e të dy butonave për 3 sekonda do të nisë një funksion qasshmërie.\n\n Funksioni aktual i qasshmërisë:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Mund ta ndryshosh funksionin te Cilësimet &gt; Qasshmëria."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Boshatis"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Redakto shkurtoret"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Anulo"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Çaktivizo shkurtoren"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Përdor shkurtoren"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Kthimi i ngjyrës"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Të aktivizohet profili i punës?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Aplikacionet e punës, njoftimet, të dhënat e tua dhe funksionet e tjera të profilit të punës do të aktivizohen"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Aktivizo"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ky aplikacion është ndërtuar për një version më të vjetër të Android dhe mund të mos funksionojë mirë. Provo të kontrollosh për përditësime ose kontakto me zhvilluesin."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Kontrollo për përditësim"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Ke mesazhe të reja"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Bateria mund të mbarojë përpara ngarkimit të zakonshëm"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"\"Kursyesi i baterisë\" u aktivizua për të zgjatur jetëgjatësinë e baterisë"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Kursyesi i baterisë"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"\"Kursyesi i baterisë\" nuk do të aktivizohet përsëri derisa bateria të jetë përsëri në nivel të ulët"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Bateria është karikuar në një nivel të mjaftueshëm. \"Kursyesi i baterisë\" nuk do të aktivizohet përsëri deri sa bateria të jetë përsëri në nivel të ulët."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Telefoni është karikuar në <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Tableti është karikuar në <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Pajisja është karikuar në <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"\"Kursyesi i baterisë\" është joaktiv. Funksionet nuk janë më të kufizuara."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"\"Kursyesi i baterisë\" është çaktivizuar. Funksionet nuk janë më të kufizuara."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"Dosje"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Aplikacion i Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Skedar"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index c3881cb..f3a2ce5 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -290,7 +290,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"приступи календару"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"шаље и прегледа SMS поруке"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Меморијски простор"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"Датотеке и медији"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"приступа сликама, медијима и датотекама на уређају"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Микрофон"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"снима звук"</string>
@@ -316,6 +316,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Може да додирује, листа, скупља приказ и обавља друге покрете."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Покрети за отисак прста"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Може да региструје покрете на сензору за отисак прста на уређају."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Направи снимак екрана"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Може да направи снимак екрана."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"онемогућавање или измена статусне траке"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Дозвољава апликацији да онемогући статусну траку или да додаје и уклања системске иконе."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"функционисање као статусна трака"</string>
@@ -1632,6 +1634,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Желите да појачате звук изнад препорученог нивоа?\n\nСлушање гласне музике дуже време може да вам оштети слух."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Желите ли да користите пречицу за приступачност?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Када је пречица укључена, притисните оба дугмета за јачину звука да бисте покренули функцију приступачности.\n\n Актуелна функција приступачности:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Можете да промените функцију у одељку Подешавања &gt; Приступачност."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Празно"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Измените пречице"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Откажи"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Искључи пречицу"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Користи пречицу"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Инверзија боја"</string>
@@ -1877,6 +1882,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Да укључимо профил за Work?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Укључиће се пословне апликације, обавештења, подаци и друге функције профила за Work"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Укључи"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Апликација није доступна"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"Апликација <xliff:g id="APP_NAME">%1$s</xliff:g> тренутно није доступна."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ова апликација је направљена за старију верзију Android-а, па можда неће радити исправно. Потражите ажурирања или контактирајте програмера."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Потражи ажурирање"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Имате нове поруке"</string>
@@ -1988,13 +1995,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Батерија ће се можда испразнити пре уобичајеног пуњења"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Уштеда батерије је активирана да би се продужило трајање батерије"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Уштеда батерије"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Уштеда батерије се неће поново активирати док батерија не буде скоро празна"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Батерија је напуњена до задовољавајућег нивоа. Уштеда батерије се неће поново активирати док батерија не буде скоро празна."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Телефон је напуњен <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Таблет је напуњен <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Уређај је напуњен <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Уштеда батерије је искључена. Функције више нису ограничене."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Уштеда батерије је искључена. Функције више нису ограничене."</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Уштеда батерије је искључена"</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Батерија телефона је довољно напуњена. Функције више нису ограничене."</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"Батерија таблета је довољно напуњена. Функције више нису ограничене."</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"Батерија уређаја је довољно напуњена. Функције више нису ограничене."</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"Директоријум"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android апликација"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Датотека"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index feef35b..ade5e6a 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -287,7 +287,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"få tillgång till din kalender"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"Sms"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"skicka och visa sms"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Lagring"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"Filer och media"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"få åtkomst till foton, media och filer på din enhet"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofon"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"spela in ljud"</string>
@@ -313,6 +313,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Kan trycka, svepa, nypa och göra andra rörelser."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Fingeravtrycksrörelser"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Kan registrera rörelser som utförs med hjälp av enhetens fingeravtryckssensor."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Ta skärmdump"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Du kan ta en skärmdump av skärmen."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"inaktivera eller ändra statusfält"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Tillåter att appen inaktiverar statusfältet eller lägger till och tar bort systemikoner."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"visas i statusfältet"</string>
@@ -1610,6 +1612,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Vill du höja volymen över den rekommenderade nivån?\n\nAtt lyssna med stark volym långa stunder åt gången kan skada hörseln."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Vill du använda Aktivera tillgänglighet snabbt?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"När kortkommandot har aktiverats startar du en tillgänglighetsfunktion genom att trycka ned båda volymknapparna i tre sekunder.\n\n Aktuell tillgänglighetsfunktion:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Du kan ändra funktionen i Inställningar &gt; Tillgänglighet."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Töm"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Redigera genvägar"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Avbryt"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Inaktivera kortkommandot"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Använd kortkommandot"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Inverterade färger"</string>
@@ -1845,6 +1850,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Vill du aktivera jobbprofilen?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Jobbappar, aviseringar, data och andra funktioner i jobbprofilen aktiveras"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Aktivera"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Appen är inte tillgänglig"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> är inte tillgängligt just nu."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Appen har utvecklats för en äldre version av Android och kanske inte fungerar som den ska. Testa att söka efter uppdateringar eller kontakta utvecklaren."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Sök efter uppdateringar"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Du har nya meddelanden"</string>
@@ -1955,13 +1962,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Batteriet kan ta slut innan du brukar ladda det"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Batterisparläget har aktiverats för att utöka batteritiden"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Batterisparläge"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Batterisparläget aktiveras inte igen förrän batterinivån är låg"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Batteriet har laddats tillräckligt. Batterisparläget aktiveras inte igen förrän batterinivån är låg."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Mobilen är laddad till <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Surfplattan är laddad till <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Enheten är laddad till <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Batterisparläget har inaktiverats. Funktioner begränsas inte längre."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Batterisparläget har inaktiverats. Funktioner begränsas inte längre."</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Batterisparläget har inaktiverats"</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefonen har laddats tillräckligt. Funktioner begränsas inte längre."</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"Surfplattan har laddats tillräckligt. Funktioner begränsas inte längre."</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"Enheten har laddats tillräckligt. Funktioner begränsas inte längre."</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"Mapp"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android-app"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Fil"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 094e324..f893af3 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"ifikie kalenda yako"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"itume na iangalie SMS"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Hifadhi"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"ifikie picha, maudhui na faili kwenye kifaa chako"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Kipokea sauti"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"irekodi sauti"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Unaweza kugusa, kutelezesha kidole, kubana na kutekeleza ishara zingine."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Ishara za alama ya kidole"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Inaweza kurekodi ishara zinazotekelezwa kwenye kitambua alama ya kidole."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"zima au rekebisha mwambaa hali"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Inaruhusu programu kulemaza upau wa hali au kuongeza na kutoa aikoni za mfumo."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"kuwa sehemu ya arifa"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Ungependa kupandisha sauti zaidi ya kiwango kinachopendekezwa?\n\nKusikiliza kwa sauti ya juu kwa muda mrefu kunaweza kuharibu uwezo wako wa kusikia."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Ungependa kutumia njia ya mkato ya ufikivu?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Unapowasha kipengele cha njia ya mkato, hatua ya kubonyeza vitufe vyote viwili vya sauti kwa dakika 3 itafungua kipengele cha ufikivu.\n\n Kipengele cha ufikivu kilichopo kwa sasa:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Unaweza kubadilisha kipengele hiki katika Mipangilio &gt; Zana za ufikivu."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Tupu"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Kubadilisha njia za mkato"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Ghairi"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Zima kipengele cha Njia ya Mkato"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Tumia Njia ya Mkato"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Ugeuzaji rangi"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Ungependa kuwasha wasifu wa kazini?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Hatua hii itawasha data, arifa, programu za kazini, arifa na vipengele vingine vya wasifu wa kazini"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Washa"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Programu hii iliundwa kwa ajili ya toleo la zamani la Android na huenda isifanye kazi vizuri. Jaribu kuangalia masasisho au uwasiliane na msanidi programu."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Angalia masasisho"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Una ujumbe mpya"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Huenda betri itakwisha chaji mapema"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Imewasha Kiokoa Betri ili kurefusha muda wa matumizi ya betri"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Kiokoa betri"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Kiokoa betri hakitawashwa tena hadi chaji ipungue tena"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Betri imejazwa chaji hadi kiwango cha kutosha. Kiokoa betri hakitawashwa tena hadi chaji ya betri ipungue tena."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Simu ina <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ya chaji"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Kompyuta kibao ina <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ya chaji"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Kifaa kina <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ya chaji"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Kiokoa betri kimezimwa. Vipengele havizuiliwi tena."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Kiokoa betri kimezimwa. Vipengele havizuiliwi tena."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"Folda"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Programu ya Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Faili"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 08b32b9..d3a4613 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"கேலெண்டரை அணுகலாம்"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS அனுப்பலாம், வந்த SMSகளைப் பார்க்கலாம்"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"சேமிப்பிடம்"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"உங்கள் சாதனத்தில் உள்ள படங்கள், மீடியா மற்றும் கோப்புகளை அணுக வேண்டும்"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"மைக்ரோஃபோன்"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"ஒலிப் பதிவு செய்யலாம்"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"தட்டலாம், ஸ்வைப் செய்யலாம், பின்ச் செய்யலாம் மற்றும் பிற சைகைகளைச் செயல்படுத்தலாம்."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"கைரேகை சைகைகள்"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"சாதனத்தின் கைரேகை சென்சார்மேல் செய்யப்படும் சைகைகளைக் கேப்ட்சர் செய்ய முடியும்."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"நிலைப் பட்டியை முடக்குதல் அல்லது மாற்றுதல்"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"நிலைப் பட்டியை முடக்க அல்லது முறைமையில் ஐகான்களைச் சேர்க்க மற்றும் அகற்ற ஆப்ஸை அனுமதிக்கிறது."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"நிலைப் பட்டியில் இருக்கும்"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"பரிந்துரைத்த அளவை விட ஒலியை அதிகரிக்கவா?\n\nநீண்ட நேரத்திற்கு அதிகளவில் ஒலி கேட்பது கேட்கும் திறனைப் பாதிக்கலாம்."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"அணுகல்தன்மை ஷார்ட்கட்டைப் பயன்படுத்தவா?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"ஷார்ட்கட் இயக்கத்தில் இருந்தால், இரண்டு ஒலியளவு பொத்தான்களையும் 3 வினாடிகள் அழுத்தி, அணுகல்தன்மை அம்சத்தை இயக்கலாம்.\n\n தற்போதைய அணுகல்தன்மை அம்சம்:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n அமைப்புகள் &gt; அணுகல்தன்மை என்பதற்குச் சென்று, அம்சத்தை மாற்றலாம்."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"காலியானது"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"ஷார்ட்கட்களை மாற்று"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"ரத்துசெய்"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"ஷார்ட்கட்டை முடக்கு"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"ஷார்ட்கட்டைப் பயன்படுத்து"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"வண்ணத்தை நேர் எதிராக மாற்றுதல்"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"பணிச் சுயவிவரத்தை ஆன் செய்யவா?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"பணி ஆப்ஸ், அறிவிப்புகள், தரவு மற்றும் பிற பணிச் சுயவிவர அம்சங்கள் ஆன் செய்யப்படும்"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"இயக்கு"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"இந்த ஆப்ஸ் Android இன் பழைய பதிப்புக்காக உருவாக்கப்பட்டதால், சரியாக வேலை செய்யாமல் போகலாம். புதுப்பிப்புகள் ஏதேனும் உள்ளதா எனப் பார்க்கவும் அல்லது டெவெலப்பரைத் தொடர்புகொள்ளவும்."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"புதுப்பிப்பு உள்ளதா எனப் பார்"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"புதிய செய்திகள் வந்துள்ளன"</string>
@@ -1855,11 +1867,9 @@
     <string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"<xliff:g id="PRODUCT_NAME">%1$s</xliff:g> உடன் இணைக்கப்பட்டது"</string>
     <string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"கோப்புகளைப் பார்க்க, தட்டவும்"</string>
     <string name="pin_target" msgid="8036028973110156895">"பின் செய்"</string>
-    <!-- no translation found for pin_specific_target (7824671240625957415) -->
-    <skip />
+    <string name="pin_specific_target" msgid="7824671240625957415">"<xliff:g id="LABEL">%1$s</xliff:g> ஐப் பின் செய்"</string>
     <string name="unpin_target" msgid="3963318576590204447">"பின்னை அகற்று"</string>
-    <!-- no translation found for unpin_specific_target (3859828252160908146) -->
-    <skip />
+    <string name="unpin_specific_target" msgid="3859828252160908146">"<xliff:g id="LABEL">%1$s</xliff:g> ஐப் பின் நீக்கு"</string>
     <string name="app_info" msgid="6113278084877079851">"ஆப்ஸ் தகவல்"</string>
     <string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="demo_starting_message" msgid="6577581216125805905">"டெமோவைத் தொடங்குகிறது…"</string>
@@ -1957,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"வழக்கமாகச் சார்ஜ் செய்வதற்கு முன்பே பேட்டரி தீர்ந்துபோகக்கூடும்"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"பேட்டரி நிலையை நீட்டிக்க பேட்டரி சேமிப்பான் இயக்கப்பட்டுள்ளது"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"பேட்டரி சேமிப்பான்"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"பேட்டரி மறுபடியும் குறையும் வரை பேட்டரி சேமிப்பானை இயக்க இயலாது"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"பேட்டரி போதுமான அளவு சார்ஜ் செய்யப்பட்டுள்ளது. பேட்டரி குறையும் வரை பேட்டரி சேமிப்பானை மீண்டும் இயக்க இயலாது."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"மொபைல் <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> சார்ஜ் செய்யப்பட்டது"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"டேப்லெட் <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> சார்ஜ் செய்யப்பட்டது"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"சாதனம் <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> சார்ஜ் செய்யப்பட்டது"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"பேட்டரி சேமிப்பான் ஆஃப் செய்யப்பட்டுள்ளது. அம்சங்கள் இனி தடையின்றி இயங்கும்."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"பேட்டரி சேமிப்பான் ஆஃப் செய்யப்பட்டுள்ளது. அம்சங்கள் இனி தடையின்றி இயங்கும்."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"கோப்புறை"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android ஆப்ஸ்"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"ஃபைல்"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index e1b88ac..04a4d41 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"మీ క్యాలెండర్‌ను యాక్సెస్ చేయడానికి"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS సందేశాలను పంపడం మరియు వీక్షించడం"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"నిల్వ"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"మీ పరికరంలోని ఫోటోలు, మీడియా మరియు ఫైల్‌లను యాక్సెస్ చేయడానికి"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"మైక్రోఫోన్"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"ఆడియోను రికార్డ్ చేయడానికి"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"నొక్కగలరు, స్వైప్ చేయగలరు, స్క్రీన్‌పై రెండు వేళ్లను ఉంచి ఆ వేళ్లను దగ్గరకు లేదా దూరానికి లాగగలరు మరియు ఇతర సంజ్ఞలను చేయగలరు."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"వేలిముద్ర సంజ్ఞలు"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"పరికర వేలిముద్ర సెన్సార్‌లో ఉపయోగించిన సంజ్ఞలను క్యాప్చర్ చేయవచ్చు."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"స్థితి బార్‌ను నిలిపివేయడం లేదా సవరించడం"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"స్థితి బార్‌ను నిలిపివేయడానికి లేదా సిస్టమ్ చిహ్నాలను జోడించడానికి మరియు తీసివేయడానికి యాప్‌ను అనుమతిస్తుంది."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"స్థితి పట్టీగా ఉండటం"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"వాల్యూమ్‌ను సిఫార్సు చేయబడిన స్థాయి కంటే ఎక్కువగా పెంచాలా?\n\nసుదీర్ఘ వ్యవధుల పాటు అధిక వాల్యూమ్‌లో వినడం వలన మీ వినికిడి శక్తి దెబ్బ తినవచ్చు."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"యాక్సెస్ సామర్థ్యం షార్ట్‌కట్‌ను ఉపయోగించాలా?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"షార్ట్‌కట్ ఆన్‌లో ఉన్నప్పుడు, రెండు వాల్యూమ్ బటన్‌లను 3 సెకన్ల పాటు నొక్కితే యాక్సెస్ సామర్థ్య ఫీచర్ ప్రారంభం అవుతుంది.\n\n ప్రస్తుత యాక్సెస్ సామర్థ్య ఫీచర్:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n సెట్టింగ్‌లు &gt; యాక్సెస్ సామర్థ్యంలో మీరు ఫీచర్‌ను మార్చవచ్చు."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"ఖాళీ"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"షార్ట్‌కట్‌లను ఎడిట్ చేయి"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"రద్దు చేయి"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"సత్వరమార్గాన్ని ఆఫ్ చేయి"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"సత్వరమార్గాన్ని ఉపయోగించు"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"రంగుల మార్పిడి"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"కార్యాలయ ప్రొఫైల్‌ని ఆన్ చేయాలా?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"మీ కార్యాలయ యాప్‌లు, నోటిఫికేషన్‌లు, డేటా మరియు ఇతర కార్యాలయ ప్రొఫైల్ ఫీచర్‌లు ఆన్ చేయబడతాయి"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"ఆన్ చేయి"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ఈ యాప్ పాత వెర్షన్ Android కోసం రూపొందించబడింది మరియు అది సరిగ్గా పని చేయకపోవచ్చు. అప్‌డేట్‌ల కోసం తనిఖీ చేయడానికి ప్రయత్నించండి లేదా డెవలపర్‌ని సంప్రదించండి."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"అప్‌డేట్ కోసం తనిఖీ చేయండి"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"మీకు కొత్త సందేశాలు ఉన్నాయి"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"మామూలుగా ఛార్జ్ చేసేలోపు బ్యాటరీ ఖాళీ కావచ్చు"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"బ్యాటరీ జీవితకాలాన్ని పెంచడానికి బ్యాటరీ సేవర్ యాక్టివేట్ చేయబడింది"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"బ్యాటరీ సేవర్"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"బ్యాటరీ మళ్లీ తగ్గిపోయే వరకు బ్యాటరీ సేవర్ తిరిగి యాక్టివేట్ చేయబడదు"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"బ్యాటరీ సరిపడే స్థాయికి ఛార్జ్ చేయబడింది. బ్యాటరీ మళ్లీ తగ్గిపోయే వరకు బ్యాటరీ సేవర్ తిరిగి యాక్టివేట్ చేయబడదు."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"ఫోన్ <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ఛార్జ్ అయింది"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"టాబ్లెట్ <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ఛార్జ్ అయింది"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"పరికరం <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ఛార్జ్ అయింది"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"బ్యాటరీ సేవర్ ఆఫ్‌లో ఉంది. ఫీచర్‌లు ఇప్పటి నుండి పరిమితం చేయబడవు."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"బ్యాటరీ సేవర్ ఆఫ్ చేయబడింది. ఫీచర్‌లు ఇప్పటి నుండి పరిమితం చేయబడవు."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"ఫోల్డర్"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android అప్లికేషన్"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"ఫైల్"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index bd02a73..11aa407 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"เข้าถึงปฏิทิน"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"ส่งและดูข้อความ SMS"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"พื้นที่เก็บข้อมูล"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"เข้าถึงรูปภาพ สื่อ และไฟล์บนอุปกรณ์ของคุณ"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"ไมโครโฟน"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"บันทึกเสียง"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"สามารถแตะ เลื่อน บีบ และทำท่าทางสัมผัสอื่นๆ"</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"ท่าทางสัมผัสลายนิ้วมือ"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"สามารถจับท่าทางสัมผัสที่เกิดขึ้นบนเซ็นเซอร์ลายนิ้วมือของอุปกรณ์"</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"ปิดการใช้งานหรือแก้ไขแถบสถานะ"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"อนุญาตให้แอปพลิเคชันปิดใช้งานแถบสถานะหรือเพิ่มและนำไอคอนระบบออก"</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"เป็นแถบสถานะ"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"นี่เป็นการเพิ่มระดับเสียงเกินระดับที่แนะนำ\n\nการฟังเสียงดังเป็นเวลานานอาจทำให้การได้ยินของคุณบกพร่องได้"</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ใช้ทางลัดการช่วยเหลือพิเศษไหม"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"เมื่อทางลัดเปิดอยู่ การกดปุ่มปรับระดับเสียงทั้ง 2 ปุ่มเป็นเวลา 3 วินาทีจะเริ่มฟีเจอร์การช่วยเหลือพิเศษ\n\n ฟีเจอร์การช่วยเหลือพิเศษปัจจุบัน:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n คุณสามารถเปลี่ยนฟีเจอร์ในการตั้งค่า &gt; การช่วยเหลือพิเศษ"</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"ล้าง"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"แก้ไขทางลัด"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"ยกเลิก"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"ปิดทางลัด"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"ใช้ทางลัด"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"การกลับสี"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"เปิดโปรไฟล์งานไหม"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"ระบบจะเปิดแอปงาน การแจ้งเตือน ข้อมูล และฟีเจอร์อื่นๆ ในโปรไฟล์งาน"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"เปิด"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"แอปนี้สร้างขึ้นเพื่อ Android เวอร์ชันเก่าและอาจทำงานผิดปกติ โปรดลองตรวจหาการอัปเดตหรือติดต่อนักพัฒนาซอฟต์แวร์"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"ตรวจสอบอัปเดต"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"คุณมีข้อความใหม่"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"แบตเตอรี่อาจหมดก่อนการชาร์จปกติ"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"เปิดใช้งานโหมดประหยัดแบตเตอรี่แล้วเพื่อยืดอายุการใช้งานแบตเตอรี่"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"โหมดประหยัดแบตเตอรี่"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"โหมดประหยัดแบตเตอรี่จะไม่เปิดใช้งานอีกจนกว่าแบตเตอรี่จะเหลือน้อยอีกครั้ง"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"ชาร์จแบตเตอรี่จนถึงระดับที่เพียงพอแล้ว โหมดประหยัดแบตเตอรี่จะไม่เปิดใช้งานอีกจนกว่าแบตเตอรี่จะเหลือน้อยอีกครั้ง"</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"โทรศัพท์ชาร์จ <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"แท็บเล็ตชาร์จ <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"อุปกรณ์ชาร์จ <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"โหมดประหยัดแบตเตอรี่ปิดอยู่ ไม่มีการจำกัดฟีเจอร์แล้ว"</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"ปิดโหมดประหยัดแบตเตอรี่แล้ว ไม่มีการจำกัดฟีเจอร์แล้ว"</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"โฟลเดอร์"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"ไฟล์แอปพลิเคชัน Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"ไฟล์"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index c6fb52d..718e170 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"i-access ang iyong kalendaryo"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"magpadala at tumingin ng mga mensaheng SMS"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Storage"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"i-access ang mga larawan, media at file sa iyong device"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikropono"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"mag-record ng audio"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"May kakayahang mag-tap, mag-swipe, mag-pinch at magsagawa ng iba pang mga galaw."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Mga galaw gamit ang fingerprint"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Makukunan ang mga galaw na ginawa sa sensor para sa fingerprint ng device."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"i-disable o baguhin ang status bar"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Pinapayagan ang app na i-disable ang status bar o magdagdag at mag-alis ng mga icon ng system."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"maging status bar"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Lakasan ang volume nang lagpas sa inirerekomendang antas?\n\nMaaaring mapinsala ng pakikinig sa malakas na volume sa loob ng mahahabang panahon ang iyong pandinig."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Gagamitin ang Shortcut sa Pagiging Accessible?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Kapag naka-on ang shortcut, magsisimula ang isang feature ng pagiging naa-access kapag pinindot ang parehong button ng volume sa loob ng 3 segundo.\n\n Kasalukuyang feature ng pagiging naa-access:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Maaari mong baguhin ang feature sa Mga Setting &gt; Pagiging Accessible."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Bakantehin"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"I-edit ang mga shortcut"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Kanselahin"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"I-off ang Shortcut"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Gamitin ang Shortcut"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Pag-invert ng Kulay"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"I-on ang profile sa trabaho?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Mao-on ang iyong mga app sa trabaho, notification, data, at iba pang feature sa profile sa trabaho"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"I-on"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ang app na ito ay ginawa para sa mas lumang bersyon ng Android at maaaring hindi gumana nang maayos. Subukang tingnan kung may mga update, o makipag-ugnayan sa developer."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Tingnan kung may update"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Mayroon kang mga bagong mensahe"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Maaaring maubos ang baterya bago ang karaniwang pag-charge"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Na-activate ang Pangtipid sa Baterya para patagalin ang buhay ng baterya"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Pangtipid sa Baterya"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Hindi muling maa-activate ang Pangtipid sa Baterya hangga\'t hindi pa ulit mababa ang baterya"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Na-charge na ang baterya sa sapat na antas. Hindi muling maa-activate ang Pangtipid sa Baterya hangga\'t hindi pa ulit mababa ang baterya."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"<xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ang charge ng telepono"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"<xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ang charge ng tablet"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"<xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> ang charge ng baterya"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Na-off ang Pangtipid sa Baterya. Hindi na pinaghihigpitan ang mga feature."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Na-off ang Pangtipid sa Baterya. Hindi na pinaghihigpitan ang mga feature."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"Folder"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android application"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"File"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 25d0674..93d9af1 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"takviminize erişme"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS mesajları gönderme ve görüntüleme"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Depolama"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"cihazınızdaki fotoğraflara, medyaya ve dosyalara erişme"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofon"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"ses kaydetme"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Dokunabilir, hızlıca kaydırabilir, sıkıştırabilir ve diğer hareketleri yapabilirsiniz."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Parmak izi hareketleri"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Cihazın parmak izi sensörlerinde gerçekleştirilen hareketleri yakalayabilir."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"durum çubuğunu devre dışı bırak veya değiştir"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Uygulamaya, durum çubuğunu devre dışı bırakma ve sistem simgelerini ekleyip kaldırma izni verir."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"durum çubuğunda olma"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Ses seviyesi önerilen düzeyin üzerine yükseltilsin mi?\n\nUzun süre yüksek ses seviyesinde dinlemek işitme duyunuza zarar verebilir."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Erişilebilirlik Kısayolu Kullanılsın mı?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Kısayol açık olduğunda, ses düğmelerinin ikisini birden 3 saniyeliğine basılı tutmanız bir erişilebilirlik özelliğini başlatır.\n\n Geçerli erişilebilirlik özelliği:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Özelliği, Ayarlar &gt; Erişilebilirlik seçeneğinden değiştirebilirsiniz."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Boş"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Kısayolları düzenle"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"İptal"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Kısayolu Kapat"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Kısayolu Kullan"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Rengi Ters Çevirme"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"İş profili açılsın mı?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"İş uygulamalarınız, bildirimleriniz, verileriniz ve diğer iş profili özellikleriniz açılacak"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Aç"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Bu uygulama Android\'in daha eski bir sürümü için oluşturuldu ve düzgün çalışmayabilir. Güncellemeleri kontrol etmeyi deneyin veya geliştiriciyle iletişime geçin."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Güncellemeleri denetle"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Yeni mesajlarınız var"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Pil normal şarjdan önce bitebilir"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Pilin ömrünü uzatmak için Pil Tasarrufu etkinleştirildi"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Pil Tasarrufu"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Pil seviyesi tekrar azalıncaya kadar Pil Tasarrufu yeniden etkinleştirilmeyecek"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Pil yeterli bir seviyede şarj edildi. Pil seviyesi tekrar azalıncaya kadar Pil Tasarrufu yeniden etkinleştirilmeyecek."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Telefon <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> şarja sahip"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Tablet <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> şarja sahip"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Cihaz <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> şarja sahip"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Pil Tasarrufu kapalı. Özellikler artık kısıtlanmış değil."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Pil Tasarrufu kapalı. Özellikler artık kısıtlanmış değil."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"Klasör"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android uygulaması"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Dosya"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index ca0146b..0f8b368 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -293,7 +293,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"отримувати доступ до календаря"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"надсилати та переглядати SMS-повідомлення"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Пам’ять"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"отримувати доступ до фотографій, мультимедійного вмісту та файлів на вашому пристрої"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Мікрофон"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"записувати аудіо"</string>
@@ -319,6 +320,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Можна торкатися, проводити пальцем, стискати пальці та виконувати інші жести."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Жести на сканері відбитків пальців"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Може фіксувати жести на сканері відбитків пальців."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"вимикати чи змін. рядок стану"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Дозволяє програмі вимикати рядок стану чи додавати та видаляти піктограми системи."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"відображатися як рядок стану"</string>
@@ -1654,6 +1659,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Збільшити гучність понад рекомендований рівень?\n\nЯкщо слухати надто гучну музику тривалий час, можна пошкодити слух."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Використовувати швидке ввімкнення?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Коли ярлик увімкнено, після натискання обох клавіш гучності й утримування їх протягом 3 секунд увімкнеться функція спеціальних можливостей.\n\n Поточна функція спеціальних можливостей:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Цю функцію можна змінити в меню \"Налаштування\" &gt; \"Спеціальні можливості\"."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Очистити"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Редагувати засоби"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Скасувати"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Вимкнути ярлик"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Використовувати ярлик"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Інверсія кольорів"</string>
@@ -1909,6 +1917,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Увімкнути робочий профіль?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Додатки, сповіщення, дані й інші функції робочого профілю буде ввімкнено"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Увімкнути"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Цей додаток створений для старішої версії Android і може працювати неналежним чином. Спробуйте знайти оновлення або зв’яжіться з розробником."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Шукати оновлення"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"У вас є нові повідомлення"</string>
@@ -2021,13 +2033,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Акумулятор може розрядитися раніше ніж зазвичай"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Режим енергозбереження активовано для збільшення часу роботи акумулятора"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Режим енергозбереження"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Режим енергозбереження не ввімкнеться, доки рівень заряду знову не знизиться"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Акумулятор заряджено достатньо. Режим енергозбереження буде знову ввімкнено, коли рівень заряду знизиться."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Телефон заряджено на <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Планшет заряджено на <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Пристрій заряджено на <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Режим енергозбереження вимкнено. Функції вже не обмежено."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Режим енергозбереження вимкнено. Функції вже не обмежено."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"Папка"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Додаток Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Файл"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 1d79c6e..5b957159 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"اپنے کیلنڈر تک رسائی حاصل کریں"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"‏SMS پیغامات بھیجیں اور دیکھیں"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"اسٹوریج"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"آپ کے آلہ پر تصاویر، میڈیا اور فائلوں تک رسائی حاصل کر سکتی ہیں"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"مائکروفون"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"آڈیو ریکارڈ کریں"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"تھپتھپانا، سوائپ کرنا، چٹکی بھرنا اور دیگر اشارے انجام دے سکتی ہے"</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"فنگر پرنٹ کے اشارے"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"آلہ کے فنگر پرنٹ سینسر پر کیے گئے اشاروں کو کیپچر کر سکتا ہے۔"</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"اسٹیٹس بار کو غیر فعال یا اس میں ترمیم کریں"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"ایپ کو اسٹیٹس بار غیر فعال کرنے یا سسٹم آئیکنز شامل کرنے اور ہٹانے کی اجازت دیتا ہے۔"</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"بطور اسٹیٹس بار کام لیں"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"والیوم کو تجویز کردہ سطح سے زیادہ کریں؟\n\nزیادہ وقت تک اونچی آواز میں سننے سے آپ کی سماعت کو نقصان پہنچ سکتا ہے۔"</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ایکسیسبیلٹی شارٹ کٹ استعمال کریں؟"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"شارٹ کٹ آن ہونے پر، 3 سیکنڈ تک دونوں والیوم بٹنز کو دبانے سے ایک ایکسیسبیلٹی خصوصیت شروع ہو جائے گی۔\n\n موجودہ ایکسیسبیلٹی خصوصیت:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n آپ خصوصیت کو ترتیبات &gt; ایکسیسبیلٹی میں جا کر تبدیل کر سکتے ہیں۔"</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"خالی"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"شارٹ کٹس میں ترمیم کریں"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"منسوخ کریں"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"شارٹ کٹ آف کریں"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"شارٹ کٹ استعمال کریں"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"رنگوں کی تقلیب"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"دفتری پروفائل آن کریں؟"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"آپ کی دفتری ایپس، اطلاعات، ڈیٹا اور دفتری پروفائل کی دیگر خصوصیات آن کر دی جائیں گی"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"آن کریں"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"‏یہ ایپ Android کے پرانے ورژن کے لئے بنائی گئی ہے اور ہو سکتا ہے صحیح طور پر کام نہ کرے۔ اپ ڈیٹس چیک کر کے آزمائیں یا ڈیولپر سے رابطہ کریں۔"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"اپ ڈیٹ چیک کریں"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"آپ کے پاس نئے پیغامات ہیں"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"معمول چارج سے پہلے بیٹری ختم ہو سکتی ہے"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"بیٹری لائف کو بڑھانے کے لیے بیٹری سیور کو فعال کر دیا گیا ہے"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"بیٹری سیور"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"بیٹری سیور اس وقت تک دوبارہ فعال نہیں ہوگی جب تک پھر سے بیٹری کم نہیں ہو جاتی ہے"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"بیٹری کافی سطح تک چارج ہو گئی ہے۔ بیٹری سیور اس وقت تک دوبارہ فعال نہیں ہوگی جب تک پھر سے بیٹری کم نہیں ہو جاتی ہے۔"</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"فون <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> چارج ہو گیا"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"ٹیبلیٹ <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> چارج ہو گیا"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"آلہ <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> چارج ہو گیا"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"بیٹری سیور آف ہے۔ خصوصیات پر اب پابندی نہیں ہے۔"</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"بیٹری سیور کو آف کر دیا گیا۔ خصوصیات پر اب پابندی نہیں ہے۔"</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"فولڈر"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"‏Android ایپلیکیشن"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"فائل"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 744f54d..c837414 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -287,7 +287,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"taqvimingizga kirish"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS xabarlarni yuborish va ko‘rish"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Xotira"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"Fayllar va media"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"qurilmangizdagi surat, multimedia va fayllarga kirish"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofon"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"ovoz yozib olish"</string>
@@ -313,6 +313,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Bosish, surish; jipslashtirish va boshqa imo-ishoralarni amalga oshirish mumkin."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Barmoq izi ishoralari"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Barmoq izi skanerida kiritilgan ishoralarni taniy oladi."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Skrinshot olish"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Ekrandan skrinshot olishi mumkin."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"holat panelini o‘zgartirish yoki o‘chirish"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Ilova holat panelini o‘chirib qo‘yishi hamda tizim ikonkalarini qo‘shishi yoki olib tashlashi mumkin."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"holat qatorida ko‘rinishi"</string>
@@ -1610,6 +1612,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Tovush balandligi tavsiya etilgan darajadan ham yuqori qilinsinmi?\n\nUzoq vaqt davomida baland ovozda tinglash eshitish qobiliyatingizga salbiy ta’sir ko‘rsatishi mumkin."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Tezkor ishga tushirishdan foydalanilsinmi?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Maxsus imkoniyatlar funksiyasidan foydalanish uchun u yoniqligida ikkala ovoz balandligini boshqarish tugmasini 3 soniya bosib turing.\n\n Joriy maxsus imkoniyatlar funksiyasi:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Bu funksiyani Sozlamalar &gt; Maxsus imkoniyatlar orqali o‘zgartirish mumkin."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Boʻshatish"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Tezkor tugmalarni tahrirlash"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Bekor qilish"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Tezkor ishga tushirishni o‘chirib qo‘yish"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Tezkor ishga tushirishdan foydalanish"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Ranglarni akslantirish"</string>
@@ -1845,6 +1850,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Ishchi profil yoqilsinmi?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Ishchi ilovalar, bildirishnomalar, ma’lumotlar va boshqa ishchi profil imkoniyatlari yoqiladi"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Yoqish"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Ilova ishlamayapti"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"Ayni vaqtda <xliff:g id="APP_NAME">%1$s</xliff:g> ilovasi ishlamayapti."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Bu ilova eskiroq Android versiyalariga chiqarilgan va xato ishlashi mumkin. Yangilanishlarini tekshiring yoki dasturchi bilan bog‘laning."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Yangilanish borligini tekshirish"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Sizga yangi SMS keldi"</string>
@@ -1955,13 +1962,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Batareya quvvati odatdagidan ertaroq tugashi mumkin"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Batareya quvvati uzoqroq ishlashi uchun Tejamkor rejim yoqildi"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Quvvat tejash"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Quvvat tejash rejimi qatareya quvvati kamaymaguncha qayta yoqilmaydi"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Batareya yetarli darajada quvvatlandi. Quvvat tejash rejimi qatareya quvvati kamaymaguncha qayta yoqilmaydi."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Telefon <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> quvvat oldi"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Planshet <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> quvvat oldi"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Qurilma <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> quvvat oldi"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Quvvat tejash rejimi faolsizlantirildi. Funksiyalar endi cheklovlarsiz ishlaydi."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Quvvat tejash rejimi faolsizlantirildi. Funksiyalar endi cheklovlarsiz ishlaydi."</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Quvvat tejash rejimi faolsizlantirildi"</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefon yetarli quvvatlandi. Funksiyalar endi cheklovlarsiz ishlaydi."</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"Planshet yetarli quvvatlandi. Funksiyalar endi cheklovlarsiz ishlaydi."</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"Qurilma yetarli quvvatlandi. Funksiyalar endi cheklovlarsiz ishlaydi."</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"Jild"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android ilova"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Fayl"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 7e4e264..82662ab 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"truy cập lịch của bạn"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"Tin nhắn SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"gửi và xem tin nhắn SMS"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Bộ nhớ"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"truy cập ảnh, phương tiện và tệp trên thiết bị của bạn"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"Micrô"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"ghi âm"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Có thể nhấn, vuốt, chụm và thực hiện các cử chỉ khác."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Cử chỉ vân tay"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Có thể ghi lại các cử chỉ được thực hiện trên cảm biến vân tay của thiết bị."</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"vô hiệu hóa hoặc sửa đổi thanh trạng thái"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Cho phép ứng dụng vô hiệu hóa thanh trạng thái hoặc thêm và xóa biểu tượng hệ thống."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"trở thành thanh trạng thái"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Bạn tăng âm lượng lên quá mức khuyên dùng?\n\nViệc nghe ở mức âm lượng cao trong thời gian dài có thể gây tổn thương thính giác của bạn."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Sử dụng phím tắt Hỗ trợ tiếp cận?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Khi phím tắt được bật, nhấn cả hai nút âm lượng trong 3 giây sẽ bắt đầu một tính năng trợ năng.\n\n Tính năng trợ năng hiện tại:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Bạn có thể thay đổi tính năng trong Cài đặt &gt; Trợ năng."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Xóa sạch"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Chỉnh sửa phím tắt"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Hủy"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Tắt phím tắt"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Sử dụng phím tắt"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Đảo màu"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Bạn muốn bật hồ sơ công việc?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Ứng dụng công việc, thông báo, dữ liệu và các tính năng khác của hồ sơ công việc sẽ được bật"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Bật"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ứng dụng này được xây dựng cho một phiên bản Android cũ hơn và có thể hoạt động không bình thường. Hãy thử kiểm tra các bản cập nhật hoặc liên hệ với nhà phát triển."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Kiểm tra bản cập nhật"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Bạn có tin nhắn mới"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Pin có thể hết trước khi sạc bình thường"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Trình tiết kiệm pin được kích hoạt để kéo dài thời lượng pin"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Trình tiết kiệm pin"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Trình tiết kiệm pin sẽ chỉ kích hoạt lại khi sắp hết pin"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Pin đã sạc đủ. Trình tiết kiệm pin sẽ chỉ kích hoạt lại khi pin sắp hết."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Điện thoại đã sạc được <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Máy tính bảng đã sạc được <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Thiết bị đã sạc được <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Trình tiết kiệm pin đã tắt. Các tính năng không bị hạn chế nữa."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Trình tiết kiệm pin đã tắt. Các tính năng không bị hạn chế nữa."</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"Thư mục"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Ứng dụng Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Tệp"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 6abfda6..101a2ec 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"访问您的日历"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"短信"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"发送和查看短信"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"存储空间"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"访问您设备上的照片、媒体内容和文件"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"麦克风"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"录制音频"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"可执行点按、滑动、双指张合等手势。"</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"指纹手势"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"可以捕获在设备指纹传感器上执行的手势。"</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"停用或修改状态栏"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"允许应用停用状态栏或者增删系统图标。"</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"用作状态栏"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"要将音量调高到建议的音量以上吗?\n\n长时间保持高音量可能会损伤听力。"</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"要使用无障碍快捷方式吗?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"开启快捷方式后,同时按下两个音量按钮 3 秒钟即可启动所设定的无障碍功能。\n\n当前设定的无障碍功能:\n<xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n如需更改设定的功能,请依次转到“设置”&gt;“无障碍”。"</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"清空"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"修改快捷方式"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"取消"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"关闭快捷方式"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"使用快捷方式"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"颜色反转"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"要开启工作资料吗?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"您的工作应用、通知、数据及其他工作资料功能将会开启"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"开启"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"此应用专为旧版 Android 打造,因此可能无法正常运行。请尝试检查更新或与开发者联系。"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"检查更新"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"您有新消息"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"电池电量可能会在您平时的充电时间之前耗尽"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"已启用省电模式以延长电池续航时间"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"省电模式"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"电池电量不足时才会再次启用省电模式"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"电池电量已充足。电池电量不足时才会再次启用省电模式。"</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"手机已充电到 <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"平板电脑已充电到 <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"设备已充电到 <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"省电模式已关闭。各项功能不再受限。"</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"省电模式已关闭,各项功能不再受限。"</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"文件夹"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android 应用"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"文件"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 2cc42d7..2929869 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"存取您的日曆"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"短訊"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"傳送和查看短訊"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"儲存空間"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"存取裝置上的相片、媒體和檔案"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"麥克風"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"錄音"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"可以輕按、滑動和兩指縮放,並執行其他手勢。"</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"指紋手勢"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"可以擷取在裝置指紋感應器上執行的手勢。"</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"停用或修改狀態列"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"允許應用程式停用狀態列,並可新增或移除系統圖示。"</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"成為狀態列"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"要調高音量 (比建議的音量更大聲) 嗎?\n\n長時間聆聽高分貝音量可能會導致您的聽力受損。"</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"要使用無障礙功能快速鍵嗎?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"快速鍵開啟後,同時按住音量按鈕 3 秒,無障礙功能便會啟用。\n\n目前的無障礙功能:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n如要變更功能,請前往「設定」&gt;「無障礙功能」。"</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"空白"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"編輯捷徑"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"取消"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"關閉快速鍵"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"使用快速鍵"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"色彩反轉"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"要開啟工作設定檔嗎?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"系統將開啟您的工作應用程式、通知、資料和其他工作設定檔功能"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"開啟"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"此應用程式專為舊版 Android 打造,因此可能無法正常運作。請嘗試檢查更新,或與開發人員聯絡。"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"檢查更新"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"您有新的訊息"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"電量可能會在日常充電前耗盡"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"「省電模式」已啟用,以便延長電池壽命"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"省電模式"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"當再次出現低電量時,省電模式才會重新啟用"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"電池已充至足夠電量。當再次出現低電量時,省電模式才會重新啟用。"</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"手機已充電至 <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"平板電腦已充電至 <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"裝置已充電至 <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"已關閉省電模式。各項功能已不再受限。"</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"已關閉省電模式。各項功能已不再受限。"</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"資料夾"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android 應用程式"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"檔案"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 15aaec1..48ff3f6 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -287,7 +287,8 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"存取你的日曆"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"簡訊"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"傳送及查看簡訊"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"儲存空間"</string>
+    <!-- no translation found for permgrouplab_storage (1938416135375282333) -->
+    <skip />
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"存取裝置中的相片、媒體和檔案"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"麥克風"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"錄音"</string>
@@ -313,6 +314,10 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"可使用輕觸、滑動和雙指撥動等手勢。"</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"指紋手勢"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"可以擷取使用者對裝置的指紋感應器執行的手勢。"</string>
+    <!-- no translation found for capability_title_canTakeScreenshot (3895812893130071930) -->
+    <skip />
+    <!-- no translation found for capability_desc_canTakeScreenshot (7762297374317934052) -->
+    <skip />
     <string name="permlab_statusBar" msgid="8798267849526214017">"停用或變更狀態列"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"允許應用程式停用狀態列,並可新增或移除系統圖示。"</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"以狀態列顯示"</string>
@@ -1610,6 +1615,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"要調高音量,比建議的音量更大聲嗎?\n\n長時間聆聽高分貝音量可能會使你的聽力受損。"</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"要使用無障礙捷徑嗎?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"啟用捷徑功能後,只要同時按下兩個音量鍵 3 秒,就能啟動無障礙功能。\n\n 目前設定的無障礙功能為:\n<xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n 如要變更設定的功能,請依序輕觸 [設定] &gt; [無障礙設定]。"</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"空白"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"編輯捷徑"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"取消"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"停用捷徑"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"使用捷徑"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"色彩反轉"</string>
@@ -1845,6 +1853,10 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"要開啟工作資料夾嗎?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"系統將開啟你的辦公應用程式、通知、資料和其他工作資料夾功能"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"開啟"</string>
+    <!-- no translation found for app_blocked_title (7353262160455028160) -->
+    <skip />
+    <!-- no translation found for app_blocked_message (542972921087873023) -->
+    <skip />
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"這個應用程式是專為舊版 Android 所打造,因此可能無法正常運作。請嘗試檢查更新,或是與開發人員聯絡。"</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"檢查更新"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"你有新訊息"</string>
@@ -1955,13 +1967,14 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"電池電力可能會在你平常的充電時間前耗盡"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"已啟用節約耗電量模式以延長電池續航力"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"節約耗電量"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"電池電量不足時,節約耗電量模式才會再次開啟"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"電池電量已足夠。電池電量不足時,節約耗電量模式才會再次開啟。"</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"手機已充電至 <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"平板電腦已充電至 <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"裝置已充電至 <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"節約耗電量模式已關閉,各項功能不再受到限制。"</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"節約耗電量模式已關閉,各項功能不再受到限制。"</string>
+    <!-- no translation found for battery_saver_off_notification_title (7637255960468032515) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (5544457317418624367) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (4426317048139996888) -->
+    <skip />
+    <!-- no translation found for battery_saver_charged_notification_summary (1031562417867646649) -->
+    <skip />
     <string name="mime_type_folder" msgid="2203536499348787650">"資料夾"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Android 應用程式"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"檔案"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index ddaf38c..d736562 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -287,7 +287,7 @@
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"finyelela kukhalenda yakho"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"I-SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"thumela uphinde ubuke imilayezo ye-SMS"</string>
-    <string name="permgrouplab_storage" msgid="1121695277384787841">"Isitoreji"</string>
+    <string name="permgrouplab_storage" msgid="1938416135375282333">"Amafayela nemidiya"</string>
     <string name="permgroupdesc_storage" msgid="6351503740613026600">"finyelela kuzithombe, imidiya, namafayela kudivayisi yakho"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"I-Microphone"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"rekhoda ividiyo"</string>
@@ -313,6 +313,8 @@
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Ingathepha, iswayiphe, incinze, futhi yenze okunye ukuthintwa."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Ukuthinta kwezigxivizo zeminwe"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Ingathatha ukuthinta okwenziwe kunzwa yezigxivizo zeminwe zedivayisi."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Thatha isithombe-skrini"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Ingathatha isithombe-skrini sesiboniso"</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"khubaza noma guqula ibha yomumo"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Ivumela uhlelo lokusebenza ukuthi yenze umudwa ochaza ngesimo ukuthi ungasebenzi noma ukufaka noma ukukhipha izithonjana zohlelo."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"yiba yibha yesimo"</string>
@@ -1610,6 +1612,9 @@
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Khuphukisa ivolumu ngaphezu kweleveli enconyiwe?\n\nUkulalela ngevolumu ephezulu izikhathi ezide kungahle kulimaze ukuzwa kwakho."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Sebenzisa isinqamuleli sokufinyelela?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Uma kuvulwe isinqamuleli, ukucindezela zombili izinkinobho zevolumu amasekhondi angu-3 kuzoqala isici sokufinyelela.\n\n Isici samanje sokufinyelela:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Ungashintsha isici kuzilungiselelo &gt; Ukufinyelela."</string>
+    <string name="accessibility_shortcut_menu_button" msgid="3019329474891857428">"Akunalutho"</string>
+    <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Hlela izinqamuleli"</string>
+    <string name="cancel_accessibility_shortcut_menu_button" msgid="1817413122335452474">"Khansela"</string>
     <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Vala isinqamuleli"</string>
     <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Sebenzisa isinqamuleli"</string>
     <string name="color_inversion_feature_name" msgid="326050048927789012">"Ukuguqulwa kombala"</string>
@@ -1845,6 +1850,8 @@
     <string name="work_mode_off_title" msgid="5503291976647976560">"Vula iphrofayela yomsebenzi?"</string>
     <string name="work_mode_off_message" msgid="8417484421098563803">"Izinhlelo zakho zokusebenza zomsebenzi, izaziso, idatha, nezinye izici zephrofayela yomsebenzi kuzovulwa"</string>
     <string name="work_mode_turn_on" msgid="3662561662475962285">"Vula"</string>
+    <string name="app_blocked_title" msgid="7353262160455028160">"Uhlelo lokusebenza alutholakali"</string>
+    <string name="app_blocked_message" msgid="542972921087873023">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> ayitholakali khona manje."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Lolu hlelo lokusebenza belakhelwe inguqulo endala ye-Android futhi kungenzeka lungasebenzi kahle. Zama ukuhlolela izibuyekezo, noma uxhumane nonjiniyela."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Hlola izibuyekezo"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Unemilayezo emisha"</string>
@@ -1955,13 +1962,10 @@
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Ibhethri lingaphela ngaphambi kokushaja okuvamile"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Isilondolozi sebhethri siyasebenza ngaphandle kwempilo yebhethri"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Isilondolozi sebhethri"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Isilondolozi sebhethri ngeke siphinde senziwe sisebenze lize libe phansi ibhethri futhi"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Ibhethri lishajwe kufikela kuleveli elanele. Isilondolozi sebhethri ngeke siphinde senziwe sisebenze ibhethri lize liphinde libe phansi futhi."</string>
-    <string name="battery_saver_charged_notification_title" product="default" msgid="3323003634503470433">"Ifoni engu-<xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> igcwalisiwe"</string>
-    <string name="battery_saver_charged_notification_title" product="tablet" msgid="7917739170436791596">"Ithebulethi ishajwe ngokungu-<xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_charged_notification_title" product="device" msgid="6316898726189914805">"Idivayisi ishajwe ngokungu-<xliff:g id="CHARGE_LEVEL">%1$s</xliff:g>"</string>
-    <string name="battery_saver_off_notification_summary" msgid="8489738830261291526">"Isilondolozi sebhethri sivaliwe. Izici azisakhawulelwe."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="8875240624827898671">"Isilondolozi sebhethri sivaliwe. Izici azisakhawulelwe."</string>
+    <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Isilondolozi sebhethri sivaliwe"</string>
+    <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Ifoni inokushajwa okwanele. Izici azisakhawulelwe."</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"Ithebulethi inokushajwa okwanele. Izici azisakhawulelwe."</string>
+    <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"Idivayisi inokushajwa okwanele. Izici azisakhawulelwe."</string>
     <string name="mime_type_folder" msgid="2203536499348787650">"Ifolda"</string>
     <string name="mime_type_apk" msgid="3168784749499623902">"Uhlelo lokusebenza lwe-Android"</string>
     <string name="mime_type_generic" msgid="4606589110116560228">"Ifayela"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 9c08728..940e9f1 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2217,6 +2217,16 @@
             Corresponds to <code>LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER</code>.
             -->
             <enum name="never" value="2" />
+            <!-- <p>
+            The window is always allowed to extend into the <code>DisplayCutout</code> areas on the
+            all edges of the screen.
+            <p>
+            The window must make sure that no important content overlaps with the
+            <code>DisplayCutout</code>.
+            <p>
+            Corresponds to <code>LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS</code>.
+            -->
+            <enum name="always" value="3" />
         </attr>
     </declare-styleable>
 
@@ -3752,6 +3762,8 @@
              </p>
          -->
         <attr name="canRequestFingerprintGestures" format="boolean" />
+        <!-- Attribute whether the accessibility service wants to be able to take screenshot. -->
+        <attr name="canTakeScreenshot" format="boolean" />
 
         <!-- Animated image of the accessibility service purpose or behavior, to help users
              understand how the service can help them.-->
@@ -8317,6 +8329,26 @@
         <attr name="successColor" format="color|reference"/>
     </declare-styleable>
 
+    <!-- =============================== -->
+    <!-- QuickAccessWallet attributes -->
+    <!-- =============================== -->
+    <eat-comment />
+
+    <!-- Use <code>quickaccesswallet-service</code> as the root tag of the XML resource
+         that describes a {@link android.service.quickaccesswallet.QuickAccessWalletService},
+         which is referenced from its
+         {@link android.service.quickaccesswallet.QuickAccessWalletService#SERVICE_META_DATA}
+         meta-data entry.
+    -->
+    <declare-styleable name="QuickAccessWalletService">
+        <!-- Fully qualified class name of an activity that allows the user to modify
+             the settings for this service. -->
+        <attr name="settingsActivity"/>
+        <!-- Fully qualified class name of an activity that allows the user to view
+             their entire wallet -->
+        <attr name="targetActivity"/>
+    </declare-styleable>
+
     <!-- Use <code>recognition-service</code> as the root tag of the XML resource that
          describes a {@link android.speech.RecognitionService}, which is referenced from
          its {@link android.speech.RecognitionService#SERVICE_META_DATA} meta-data entry.
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 6435cdd..cfed805 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1520,6 +1520,22 @@
         <!-- Managing a media projection session, e.g, for screen recording or taking
              screenshots.-->
         <flag name="mediaProjection" value="0x20" />
+        <!-- Use the camera device or record video.
+
+            <p>For apps with <code>targetSdkVersion</code> {@link android.os.Build.VERSION_CODES#R}
+            and above, a foreground service will not be able to access the camera if this type is
+            not specified in the manifest and in
+            {@link android.app.Service#startForeground(int, android.app.Notification, int)}.
+            -->
+        <flag name="camera" value="0x40" />
+        <!--Use the microphone device or record audio.
+
+            <p>For apps with <code>targetSdkVersion</code> {@link android.os.Build.VERSION_CODES#R}
+            and above, a foreground service will not be able to access the microphone if this type
+            is not specified in the manifest and in
+            {@link android.app.Service#startForeground(int, android.app.Notification, int)}.
+            -->
+        <flag name="microphone" value="0x80" />
     </attr>
 
 
@@ -2005,6 +2021,15 @@
         <attr name="maxSdkVersion" />
     </declare-styleable>
 
+    <!-- The <code>extension-sdk</code> tag is a child of the <uses-sdk> tag,
+         and specifies required extension sdk features. -->
+    <declare-styleable name="AndroidManifestExtensionSdk">
+        <!-- The extension SDK version that this tag refers to. -->
+        <attr name="sdkVersion" format="integer" />
+        <!-- The minimum version of the extension SDK this application requires.-->
+        <attr name="minExtensionVersion" format="integer" />
+    </declare-styleable>
+
     <!-- The <code>library</code> tag declares that this apk is providing itself
          as a shared library for other applications to use.  It can only be used
          with apks that are built in to the system image.  Other apks can link to
@@ -2235,6 +2260,55 @@
         <attr name="name" />
     </declare-styleable>
 
+    <!-- The <code>processes</code> tag specifies the processes the application will run code in
+         and optionally characteristics of those processes.  This tag is optional; if not
+         specified, components will simply run in the processes they specify.  If supplied,
+         they can only specify processes that are enumerated here, and if they don't this
+         will be treated as a corrupt apk and result in an install failure.
+
+         <p>This appears as a child tag of the
+         {@link #AndroidManifestApplication application} tag. -->
+    <declare-styleable name="AndroidManifestProcesses" parent="AndroidManifestApplication">
+    </declare-styleable>
+
+    <!-- The <code>process</code> tag enumerates one of the available processes under its
+         containing <code>processes</code> tag.
+
+         <p>This appears as a child tag of the
+         {@link #AndroidManifestProcesses processes} tag. -->
+    <declare-styleable name="AndroidManifestProcess" parent="AndroidManifestProcesses">
+        <!-- Required name of the process that is allowed -->
+        <attr name="process" />
+    </declare-styleable>
+
+    <!-- The <code>deny-permission</code> tag specifies that a permission is to be denied
+         for a particular process (if specified under the
+         {@link #AndroidManifestProcess process} tag) or by default for all
+         processes {if specified under the
+         @link #AndroidManifestProcesses processes} tag).
+
+         <p>This appears as a child tag of the
+         {@link #AndroidManifestProcesses processes} and
+         {@link #AndroidManifestProcess process} tags. -->
+    <declare-styleable name="AndroidManifestDenyPermission"
+            parent="AndroidManifestProcesses">
+        <!-- Required name of the permission that is to be denied -->
+        <attr name="name" />
+    </declare-styleable>
+
+    <!-- The <code>allow-permission</code> tag specifies that a permission is to be allowed
+         for a particular process, when it was previously denied for all processes through
+         {@link #AndroidManifestDenyPermission deny-permission}
+
+         <p>This appears as a child tag of the
+         {@link #AndroidManifestProcesses processes} and
+         {@link #AndroidManifestProcess process} tags. -->
+    <declare-styleable name="AndroidManifestAllowPermission"
+            parent="AndroidManifestProcesses">
+        <!-- Required name of the permission that is to be allowed. -->
+        <attr name="name" />
+    </declare-styleable>
+
     <!-- The <code>provider</code> tag declares a
          {@link android.content.ContentProvider} class that is available
          as part of the package's application components, supplying structured
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 1dcd389..731e2ec 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -224,4 +224,6 @@
 
     <!-- Resolver/Chooser -->
     <color name="resolver_text_color_secondary_dark">#ffC4C6C6</color>
+    <color name="resolver_tabs_active_color">#FF1A73E8</color>
+    <color name="resolver_tabs_inactive_color">#FF80868B</color>
 </resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 245aed1..42127e7 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -281,6 +281,10 @@
          before automatically restore the default connection.  Set -1 if the connection
          does not require auto-restore. -->
     <!-- the 6th element indicates boot-time dependency-met value. -->
+    <!-- NOTE: The telephony module is no longer reading the configuration below for available
+         APN types.  The set of APN types and relevant settings are specified within the telephony
+         module and are non-configurable.  Whether or not data connectivity over a cellular network
+         is available at all is controlled by the flag: config_moble_data_capable. -->
     <string-array translatable="false" name="networkAttributes">
         <item>"wifi,1,1,1,-1,true"</item>
         <item>"mobile,0,0,0,-1,true"</item>
@@ -1624,29 +1628,21 @@
          config_timeZoneRulesUpdateTrackingEnabled are true.] -->
     <integer name="config_timeZoneRulesCheckRetryCount">5</integer>
 
-    <!-- Whether to enable network location overlay which allows network
-         location provider to be replaced by an app at run-time. When disabled,
-         only the config_networkLocationProviderPackageName package will be
-         searched for network location provider, otherwise packages whose
-         signature matches the signatures of config_locationProviderPackageNames
-         will be searched, and the service with the highest version number will
-         be picked. Anyone who wants to disable the overlay mechanism can set it
-         to false.
-         -->
+    <!-- Whether to enable network location overlay which allows network location provider to be
+         replaced by an app at run-time. When disabled, only the
+         config_networkLocationProviderPackageName package will be searched for network location
+         provider, otherwise any system package is eligible. Anyone who wants to disable the overlay
+         mechanism can set it to false. -->
     <bool name="config_enableNetworkLocationOverlay" translatable="false">true</bool>
     <!-- Package name providing network location support. Used only when
          config_enableNetworkLocationOverlay is false. -->
     <string name="config_networkLocationProviderPackageName" translatable="false">@null</string>
 
-    <!-- Whether to enable fused location provider overlay which allows fused
-         location provider to be replaced by an app at run-time. When disabled,
-         only the config_fusedLocationProviderPackageName package will be
-         searched for fused location provider, otherwise packages whose
-         signature matches the signatures of config_locationProviderPackageNames
-         will be searched, and the service with the highest version number will
-         be picked. Anyone who wants to disable the overlay mechanism can set it
-         to false.
-         -->
+    <!-- Whether to enable fused location provider overlay which allows fused location provider to
+         be replaced by an app at run-time. When disabled, only the
+         config_fusedLocationProviderPackageName package will be searched for fused location
+         provider, otherwise any system package is eligible. Anyone who wants to disable the overlay
+         mechanism can set it to false. -->
     <bool name="config_enableFusedLocationOverlay" translatable="false">true</bool>
     <!-- Package name providing fused location support. Used only when
          config_enableFusedLocationOverlay is false. -->
@@ -1665,25 +1661,10 @@
      -->
     <string name="config_defaultNetworkRecommendationProviderPackage" translatable="false"></string>
 
-    <!-- Whether to enable Hardware FLP overlay which allows Hardware FLP to be
-         replaced by an app at run-time. When disabled, only the
-         config_hardwareFlpPackageName package will be searched for Hardware Flp,
-         otherwise packages whose signature matches the signatures of
-         config_locationProviderPackageNames will be searched, and the service
-         with the highest version number will be picked. Anyone who wants to
-         disable the overlay mechanism can set it to false.
-         -->
-    <bool name="config_enableHardwareFlpOverlay" translatable="false">true</bool>
-    <!-- Package name providing Hardware Flp. Used only when
-         config_enableHardwareFlpOverlay is false. -->
-    <string name="config_hardwareFlpPackageName" translatable="false">com.android.location.fused</string>
-
     <!-- Whether to enable geocoder overlay which allows geocoder to be replaced
          by an app at run-time. When disabled, only the
          config_geocoderProviderPackageName package will be searched for
-         geocoder, otherwise packages whose signature matches the signatures of
-         config_locationProviderPackageNames will be searched, and the service
-         with the highest version number will be picked. Anyone who wants to
+         geocoder, otherwise any system package is eligible. Anyone who wants to
          disable the overlay mechanism can set it to false.
          -->
     <bool name="config_enableGeocoderOverlay" translatable="false">true</bool>
@@ -1694,9 +1675,7 @@
     <!-- Whether to enable geofence overlay which allows geofence to be replaced
          by an app at run-time. When disabled, only the
          config_geofenceProviderPackageName package will be searched for
-         geofence implementation, otherwise packages whose signature matches the
-         signatures of config_locationProviderPackageNames will be searched, and
-         the service with the highest version number will be picked. Anyone who
+         geofence implementation, otherwise any system package is eligible. Anyone who
          wants to disable the overlay mechanism can set it to false.
          -->
     <bool name="config_enableGeofenceOverlay" translatable="false">true</bool>
@@ -1707,9 +1686,7 @@
     <!-- Whether to enable Hardware Activity-Recognition overlay which allows Hardware
          Activity-Recognition to be replaced by an app at run-time. When disabled, only the
          config_activityRecognitionHardwarePackageName package will be searched for
-         its implementation, otherwise packages whose signature matches the
-         signatures of config_locationProviderPackageNames will be searched, and
-         the service with the highest version number will be picked. Anyone who
+         its implementation, otherwise any system package is eligible. Anyone who
          wants to disable the overlay mechanism can set it to false.
          -->
     <bool name="config_enableActivityRecognitionHardwareOverlay" translatable="false">true</bool>
@@ -1717,19 +1694,8 @@
          config_enableActivityRecognitionHardwareOverlay is false. -->
     <string name="config_activityRecognitionHardwarePackageName" translatable="false">@null</string>
 
-    <!-- Package name(s) containing location provider support.
-         These packages can contain services implementing location providers,
-         such as the Geocode Provider, Network Location Provider, and
-         Fused Location Provider. They will each be searched for
-         service components implementing these providers.
-         It is strongly recommended that the packages explicitly named
-         below are on the system image, so that they will not map to
-         a 3rd party application.
-         The location framework also has support for installation
-         of new location providers at run-time. The new package does not
-         have to be explicitly listed here, however it must have a signature
-         that matches the signature of at least one package on this list.
-         -->
+    <!-- Package name(s) containing location provider support. These packages will be auto-granted
+         several permissions by the system, and should be system packages. -->
     <string-array name="config_locationProviderPackageNames" translatable="false">
         <!-- The standard AOSP fused location provider -->
         <item>com.android.location.fused</item>
@@ -1867,6 +1833,12 @@
          Note: This config is deprecated, please use config_defaultSms instead. -->
     <string name="default_sms_application" translatable="false">com.android.messaging</string>
 
+    <!-- Flag indicating whether the current device allows data.
+         If true, this means that the device supports data connectivity through
+         the telephony network.
+         This can be overridden to false for devices that support voice and/or sms . -->
+    <bool name="config_mobile_data_capable">true</bool>
+
     <!-- Default web browser.  This is the package name of the application that will
          be the default browser when the device first boots.  Afterwards the user
          can select whatever browser app they wish to use as the default.
@@ -1895,6 +1867,8 @@
     <string name="config_defaultCallRedirection" translatable="false"></string>
     <!-- The name of the package that will hold the call screening role by default. -->
     <string name="config_defaultCallScreening" translatable="false"></string>
+    <!-- The name of the package that will hold the system gallery role. -->
+    <string name="config_systemGallery" translatable="false">com.android.gallery</string>
 
     <!-- Enable/disable default bluetooth profiles:
         HSP_AG, ObexObjectPush, Audio, NAP -->
@@ -3561,6 +3535,12 @@
     <!-- Whether the device supports quick settings and its associated APIs -->
     <bool name="config_quickSettingsSupported">true</bool>
 
+    <!-- Comma separated list of extra quick settings tiles to be added to the default set as
+         defined in SystemUi (com.android.systemui.R.string.quick_settings_tiles_default).
+         Custom tiles (TileService) must be specified as "custom(pkg_name/class_in_package)"
+         (without the quotes, both absolute and relative class works). -->
+    <string name="config_defaultExtraQuickSettingsTiles" translatable="false"></string>
+
     <!-- The component name, flattened to a string, for the default autofill service
          to  enabled for an user. This service must be trusted, as it can be activated
          without explicit consent of the user. If no autofill service with the
@@ -4308,6 +4288,31 @@
          generation). -->
     <bool name="config_customBugreport">false</bool>
 
+    <!-- Names of packages that should not be suspended when personal use is blocked by policy. -->
+    <string-array name="config_packagesExemptFromSuspension" translatable="false">
+        <!-- Add packages here, example: -->
+        <!-- <item>com.android.settings</item> -->
+    </string-array>
+
+
     <!-- Class name of the custom country detector to be used. -->
     <string name="config_customCountryDetector" translatable="false">com.android.server.location.ComprehensiveCountryDetector</string>
+
+    <!-- Package name of the required service extension package. -->
+    <string name="config_servicesExtensionPackage" translatable="false">android.ext.services</string>
+
+    <!-- Retention policy: number of records to kept for the historical exit info per package. -->
+    <integer name="config_app_exit_info_history_list_size">16</integer>
+
+    <!-- Packages that can't be killed even if it's requested to be killed on imperceptible -->
+    <string-array name="config_defaultImperceptibleKillingExemptionPkgs" translatable="false" />
+
+    <!-- Proc States that can't be killed even if it's requested to be killed on imperceptible -->
+    <integer-array name="config_defaultImperceptibleKillingExemptionProcStates">
+      <item>0</item> <!-- PROCESS_STATE_PERSISTENT -->
+      <item>1</item> <!-- PROCESS_STATE_PERSISTENT_UI -->
+      <item>2</item> <!-- PROCESS_STATE_TOP -->
+      <item>4</item> <!-- PROCESS_STATE_FOREGROUND_SERVICE -->
+      <item>12</item> <!-- PROCESS_STATE_TOP_SLEEPING -->
+    </integer-array>
 </resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index bf7558c..d437aa1 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -777,4 +777,9 @@
     <!-- Assistant handles -->
     <dimen name="assist_handle_shadow_radius">2dp</dimen>
 
+    <!-- For Waterfall Display -->
+    <dimen name="waterfall_display_left_edge_size">0px</dimen>
+    <dimen name="waterfall_display_top_edge_size">0px</dimen>
+    <dimen name="waterfall_display_right_edge_size">0px</dimen>
+    <dimen name="waterfall_display_bottom_edge_size">0px</dimen>
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 817ccde..36dbcbd 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3000,7 +3000,6 @@
     <public-group type="attr" first-id="0x01010607">
       <public name="importantForContentCapture" />
       <public name="forceQueryable" />
-      <!-- @hide @SystemApi -->
       <public name="resourcesMap" />
       <public name="animatedImageDrawable"/>
       <public name="htmlDescription"/>
@@ -3008,6 +3007,11 @@
       <public name="featureId" />
       <public name="supportsInlineSuggestions" />
       <public name="crossProfile" />
+      <public name="canTakeScreenshot"/>
+      <!-- @hide @SystemApi -->
+      <public name="sdkVersion" />
+      <!-- @hide @SystemApi -->
+      <public name="minExtensionVersion" />
     </public-group>
 
     <public-group type="drawable" first-id="0x010800b5">
@@ -3031,6 +3035,8 @@
       <public name="config_defaultCallRedirection" />
       <!-- @hide @SystemApi -->
       <public name="config_defaultCallScreening" />
+      <!-- @hide @SystemApi @TestApi -->
+      <public name="config_systemGallery" />
     </public-group>
 
     <public-group type="bool" first-id="0x01110005">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 66267d1..be2b678 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -425,6 +425,12 @@
     <!-- A toast message displayed when printing is attempted but disabled by policy. -->
     <string name="printing_disabled_by">Printing disabled by <xliff:g id="owner_app">%s</xliff:g>.</string>
 
+    <!-- Content title for a notification that personal apps are suspended [CHAR LIMIT=NONE] -->
+    <string name="personal_apps_suspended_notification_title">Personal apps have been suspended by an admin</string>
+
+    <!-- Message for a notification about personal apps suspension when work profile is off. [CHAR LIMIT=NONE] -->
+    <string name="personal_apps_suspended_notification_text">Tap here to check policy compliance.</string>
+
     <!-- Display name for any time a piece of data refers to the owner of the phone. For example, this could be used in place of the phone's phone number. -->
     <string name="me">Me</string>
 
@@ -723,11 +729,11 @@
     <string name="permgroupdesc_sms">send and view SMS messages</string>
 
     <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permgrouplab_storage">Storage</string>
+    <string name="permgrouplab_storage">Files and media</string>
     <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permgroupdesc_storage">access photos, media, and files on your device</string>
 
-    <!-- Title of a category of application permissioncds, listed so the user can choose whether they want to allow the application to do this. -->
+    <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permgrouplab_microphone">Microphone</string>
     <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permgroupdesc_microphone">record audio</string>
@@ -794,6 +800,11 @@
     <string name="capability_desc_canCaptureFingerprintGestures">Can capture gestures performed on
         the device\'s fingerprint sensor.</string>
 
+    <!-- Title for the capability of an accessibility service to take screenshot. [CHAR LIMIT=32] -->
+    <string name="capability_title_canTakeScreenshot">Take screenshot</string>
+    <!-- Description for the capability of an accessibility service to take screenshot. [CHAR LIMIT=NONE] -->
+    <string name="capability_desc_canTakeScreenshot">Can take a screenshot of the display.</string>
+
     <!--  Permissions -->
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -4354,14 +4365,9 @@
         You can change the feature in Settings > Accessibility.
     </string>
 
-    <!-- Text in button that edit the accessibility shortcut menu. [CHAR LIMIT=100] -->
-    <string name="accessibility_shortcut_menu_button">Empty</string>
-
-    <!-- Text in button that edit the accessibility shortcut menu. [CHAR LIMIT=100] -->
-    <string name="edit_accessibility_shortcut_menu_button">Edit</string>
-
-    <!-- Text in button that save the accessibility shortcut menu changed status. [CHAR LIMIT=100] -->
-    <string name="save_accessibility_shortcut_menu_button">Save</string>
+    <!-- Text in button that edit the accessibility shortcut menu, user can delete
+    any service item in the menu list. [CHAR LIMIT=100] -->
+    <string name="edit_accessibility_shortcut_menu_button">Edit shortcuts</string>
 
     <!-- Text in button that cancel the accessibility shortcut menu changed status. [CHAR LIMIT=100] -->
     <string name="cancel_accessibility_shortcut_menu_button">Cancel</string>
@@ -4925,6 +4931,13 @@
     <!-- Title for button to turn on work profile. [CHAR LIMIT=NONE] -->
     <string name="work_mode_turn_on">Turn on</string>
 
+    <!-- Title of the dialog that is shown when the user tries to launch a suspended application [CHAR LIMIT=50] -->
+    <string name="app_blocked_title">App is not available</string>
+    <!-- Default message shown in the dialog that is shown when the user tries to launch a suspended application [CHAR LIMIT=NONE] -->
+    <string name="app_blocked_message">
+        <xliff:g id="app_name" example="Gmail">%1$s</xliff:g> is not available right now.
+    </string>
+
     <!-- Message displayed in dialog when app is too old to run on this verison of android. [CHAR LIMIT=NONE] -->
     <string name="deprecated_target_sdk_message">This app was built for an older version of Android and may not work properly. Try checking for updates, or contact the developer.</string>
     <!-- Title for button to see application detail in app store which it came from - it may allow user to update to newer version. [CHAR LIMIT=50] -->
@@ -5210,25 +5223,14 @@
     <!-- Battery saver strings -->
     <!-- The user visible name of the notification channel for battery saver notifications [CHAR_LIMIT=80] -->
     <string name="battery_saver_notification_channel_name">Battery Saver</string>
-    <!-- Title of notification letting users know why battery saver didn't turn back on automatically after the device was unplugged [CHAR_LIMIT=NONE] -->
-    <string name="battery_saver_sticky_disabled_notification_title">Battery Saver won\u2019t reactivate until battery low again</string>
-    <!-- Summary of notification letting users know why battery saver didn't turn back on automatically after the device was unplugged [CHAR_LIMIT=NONE] -->
-    <string name="battery_saver_sticky_disabled_notification_summary">Battery has been charged to a sufficient level. Battery Saver won\u2019t reactivate until the battery is low again.</string>
+    <!-- Title of notification letting users know that battery saver is now off [CHAR_LIMIT=80] -->
+    <string name="battery_saver_off_notification_title">Battery Saver turned off</string>
     <!-- Title of notification letting users know the battery level at the time the notification was posted [CHAR_LIMIT=80] -->
-    <string name="battery_saver_charged_notification_title" product="default">Phone <xliff:g id="charge level" example="90%">%1$s</xliff:g> charged</string>
+    <string name="battery_saver_charged_notification_summary" product="default">Phone has enough charge. Features no longer restricted.</string>
     <!-- Title of notification letting users know the battery level at the time the notification was posted [CHAR_LIMIT=80] -->
-    <string name="battery_saver_charged_notification_title" product="tablet">Tablet <xliff:g id="charge level" example="90%">%1$s</xliff:g> charged</string>
+    <string name="battery_saver_charged_notification_summary" product="tablet">Tablet has enough charge. Features no longer restricted.</string>
     <!-- Title of notification letting users know the battery level at the time the notification was posted [CHAR_LIMIT=80] -->
-    <string name="battery_saver_charged_notification_title" product="device">Device <xliff:g id="charge level" example="90%">%1$s</xliff:g> charged</string>
-    <!-- Summary of notification letting users know that battery saver is now off [CHAR_LIMIT=NONE] -->
-    <string name="battery_saver_off_notification_summary">Battery Saver is off. Features no longer restricted.</string>
-    <!-- Alternative summary of notification letting users know that battery saver has been turned off.
-     If it's easy to translate the difference between "Battery Saver turned off. Features no longer restricted."
-     and "Battery Saver is off. Features no longer restricted." into the target language,
-     then translate "Battery Saver turned off. Features no longer restricted."
-     If the translation doesn't make a difference or the difference is hard to capture in the target language,
-     then translate "Battery Saver is off. Features no longer restricted." instead. [CHAR_LIMIT=NONE] -->
-    <string name="battery_saver_off_alternative_notification_summary">Battery Saver turned off. Features no longer restricted.</string>
+    <string name="battery_saver_charged_notification_summary" product="device">Device has enough charge. Features no longer restricted.</string>
 
     <!-- Description of media type: folder or directory that contains additional files. [CHAR LIMIT=32] -->
     <string name="mime_type_folder">Folder</string>
@@ -5275,6 +5277,10 @@
     <!-- Description of media type: presentation file, such as PPT. The 'extension' variable is the file name extension. [CHAR LIMIT=32] -->
     <string name="mime_type_presentation_ext"><xliff:g id="extension" example="PDF">%1$s</xliff:g> presentation</string>
 
+    <!-- Strings for Bluetooth service -->
+    <!-- toast message informing user that Bluetooth stays on after airplane mode is turned on. [CHAR LIMIT=NONE] -->
+    <string name="bluetooth_airplane_mode_toast">Bluetooth will stay on during airplane mode</string>
+
     <!-- Strings for car -->
     <!-- String displayed when loading a user in the car [CHAR LIMIT=30] -->
     <string name="car_loading_profile">Loading</string>
@@ -5315,4 +5321,12 @@
 
     <!-- Accessibility description of caption view -->
     <string name="accessibility_freeform_caption">Caption bar of <xliff:g id="app_name">%1$s</xliff:g>.</string>
+
+    <!-- Text to tell the user that a package has been forced by themselves in the RESTRICTED bucket. [CHAR LIMIT=NONE] -->
+    <string name="as_app_forced_to_restricted_bucket">
+        <xliff:g id="package_name" example="com.android.example">%1$s</xliff:g> has been put into the RESTRICTED bucket</string>
+
+    <!-- ResolverActivity - profile tabs -->
+    <string name="resolver_personal_tab">Personal</string>
+    <string name="resolver_work_tab">Work</string>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index cdbf14b..ea8ca9a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -249,6 +249,9 @@
   <java-symbol type="id" name="app_ops" />
   <java-symbol type="id" name="profile_pager" />
   <java-symbol type="id" name="content_preview_container" />
+  <java-symbol type="id" name="profile_tabhost" />
+  <java-symbol type="id" name="tabs" />
+  <java-symbol type="id" name="tabcontent" />
 
   <java-symbol type="attr" name="actionModeShareDrawable" />
   <java-symbol type="attr" name="alertDialogCenterButtons" />
@@ -310,6 +313,7 @@
   <java-symbol type="bool" name="config_sip_wifi_only" />
   <java-symbol type="bool" name="config_sms_capable" />
   <java-symbol type="bool" name="config_sms_utf8_support" />
+  <java-symbol type="bool" name="config_mobile_data_capable" />
   <java-symbol type="bool" name="config_suspendWhenScreenOffDueToProximity" />
   <java-symbol type="bool" name="config_swipeDisambiguation" />
   <java-symbol type="bool" name="config_syncstorageengine_masterSyncAutomatically" />
@@ -1208,6 +1212,8 @@
   <java-symbol type="string" name="device_ownership_relinquished" />
   <java-symbol type="string" name="network_logging_notification_title" />
   <java-symbol type="string" name="network_logging_notification_text" />
+  <java-symbol type="string" name="personal_apps_suspended_notification_title" />
+  <java-symbol type="string" name="personal_apps_suspended_notification_text" />
   <java-symbol type="string" name="factory_reset_warning" />
   <java-symbol type="string" name="factory_reset_message" />
   <java-symbol type="string" name="lockscreen_transport_play_description" />
@@ -1868,7 +1874,6 @@
   <java-symbol type="bool" name="config_dozeAfterScreenOffByDefault" />
   <java-symbol type="bool" name="config_enableActivityRecognitionHardwareOverlay" />
   <java-symbol type="bool" name="config_enableFusedLocationOverlay" />
-  <java-symbol type="bool" name="config_enableHardwareFlpOverlay" />
   <java-symbol type="bool" name="config_enableGeocoderOverlay" />
   <java-symbol type="bool" name="config_enableGeofenceOverlay" />
   <java-symbol type="bool" name="config_enableNetworkLocationOverlay" />
@@ -2018,7 +2023,6 @@
   <java-symbol type="string" name="config_datause_iface" />
   <java-symbol type="string" name="config_activityRecognitionHardwarePackageName" />
   <java-symbol type="string" name="config_fusedLocationProviderPackageName" />
-  <java-symbol type="string" name="config_hardwareFlpPackageName" />
   <java-symbol type="string" name="config_geocoderProviderPackageName" />
   <java-symbol type="string" name="config_geofenceProviderPackageName" />
   <java-symbol type="string" name="config_networkLocationProviderPackageName" />
@@ -3036,6 +3040,9 @@
   <java-symbol type="string" name="app_suspended_more_details" />
   <java-symbol type="string" name="app_suspended_default_message" />
 
+  <java-symbol type="string" name="app_blocked_title" />
+  <java-symbol type="string" name="app_blocked_message" />
+
   <!-- Used internally for assistant to launch activity transitions -->
   <java-symbol type="id" name="cross_task_transition" />
 
@@ -3212,9 +3219,10 @@
   <java-symbol type="id" name="accessibility_button_target_switch_item" />
   <java-symbol type="string" name="accessibility_magnification_chooser_text" />
   <java-symbol type="string" name="edit_accessibility_shortcut_menu_button" />
-  <java-symbol type="string" name="save_accessibility_shortcut_menu_button" />
   <java-symbol type="string" name="cancel_accessibility_shortcut_menu_button" />
 
+  <java-symbol type="drawable" name="ic_accessibility_color_inversion" />
+  <java-symbol type="drawable" name="ic_accessibility_color_correction" />
   <java-symbol type="drawable" name="ic_accessibility_magnification" />
 
   <java-symbol type="drawable" name="ic_delete_item" />
@@ -3231,6 +3239,7 @@
   <java-symbol type="layout" name="autofill_dataset_picker"/>
   <java-symbol type="layout" name="autofill_dataset_picker_fullscreen"/>
   <java-symbol type="layout" name="autofill_dataset_picker_header_footer"/>
+  <java-symbol type="layout" name="autofill_inline_suggestion" />
   <java-symbol type="id" name="autofill" />
   <java-symbol type="id" name="autofill_dataset_footer"/>
   <java-symbol type="id" name="autofill_dataset_header"/>
@@ -3238,6 +3247,10 @@
   <java-symbol type="id" name="autofill_dataset_list"/>
   <java-symbol type="id" name="autofill_dataset_picker"/>
   <java-symbol type="id" name="autofill_dataset_title" />
+  <java-symbol type="id" name="autofill_inline_suggestion_end_icon" />
+  <java-symbol type="id" name="autofill_inline_suggestion_start_icon" />
+  <java-symbol type="id" name="autofill_inline_suggestion_subtitle" />
+  <java-symbol type="id" name="autofill_inline_suggestion_title" />
   <java-symbol type="id" name="autofill_save_custom_subtitle" />
   <java-symbol type="id" name="autofill_save_icon" />
   <java-symbol type="id" name="autofill_save_no" />
@@ -3400,6 +3413,7 @@
   <java-symbol type="string" name="etws_primary_default_message_others" />
 
   <java-symbol type="bool" name="config_quickSettingsSupported" />
+  <java-symbol type="string" name="config_defaultExtraQuickSettingsTiles" />
 
   <java-symbol type="style" name="Theme.DeviceDefault.QuickSettings" />
 
@@ -3645,11 +3659,8 @@
   <java-symbol type="bool" name="config_useSystemProvidedLauncherForSecondary" />
 
   <java-symbol type="string" name="battery_saver_notification_channel_name" />
-  <java-symbol type="string" name="battery_saver_sticky_disabled_notification_title" />
-  <java-symbol type="string" name="battery_saver_sticky_disabled_notification_summary" />
-  <java-symbol type="string" name="battery_saver_charged_notification_title" />
-  <java-symbol type="string" name="battery_saver_off_notification_summary" />
-  <java-symbol type="string" name="battery_saver_off_alternative_notification_summary" />
+  <java-symbol type="string" name="battery_saver_off_notification_title" />
+  <java-symbol type="string" name="battery_saver_charged_notification_summary" />
   <java-symbol type="string" name="dynamic_mode_notification_channel_name" />
   <java-symbol type="string" name="dynamic_mode_notification_title" />
   <java-symbol type="string" name="dynamic_mode_notification_summary" />
@@ -3715,6 +3726,9 @@
   <java-symbol type="string" name="mime_type_presentation" />
   <java-symbol type="string" name="mime_type_presentation_ext" />
 
+  <!-- For Bluetooth service -->
+  <java-symbol type="string" name="bluetooth_airplane_mode_toast" />
+
   <!-- For high refresh rate displays -->
   <java-symbol type="integer" name="config_defaultPeakRefreshRate" />
   <java-symbol type="integer" name="config_defaultRefreshRateInZone" />
@@ -3799,8 +3813,38 @@
   <java-symbol type="string" name="config_rawContactsLocalAccountName" />
   <java-symbol type="string" name="config_rawContactsLocalAccountType" />
 
+  <!-- For App Standby -->
+  <java-symbol type="string" name="as_app_forced_to_restricted_bucket" />
+
   <!-- Assistant handles -->
   <java-symbol type="dimen" name="assist_handle_shadow_radius" />
 
+  <!-- For Waterfall Display -->
+  <java-symbol type="dimen" name="waterfall_display_left_edge_size" />
+  <java-symbol type="dimen" name="waterfall_display_top_edge_size" />
+  <java-symbol type="dimen" name="waterfall_display_right_edge_size" />
+  <java-symbol type="dimen" name="waterfall_display_bottom_edge_size" />
+
+  <!-- For device policy -->
+  <java-symbol type="array" name="config_packagesExemptFromSuspension" />
+
+  <!-- Accessibility take screenshot -->
+  <java-symbol type="string" name="capability_desc_canTakeScreenshot" />
+  <java-symbol type="string" name="capability_title_canTakeScreenshot" />
+
+  <java-symbol type="string" name="config_servicesExtensionPackage" />
+
+  <!-- For app process exit info tracking -->
+  <java-symbol type="integer" name="config_app_exit_info_history_list_size" />
+
+  <java-symbol type="array" name="config_defaultImperceptibleKillingExemptionPkgs" />
+  <java-symbol type="array" name="config_defaultImperceptibleKillingExemptionProcStates" />
+
+  <!-- Intent resolver and share sheet -->
+  <java-symbol type="color" name="resolver_tabs_active_color" />
+  <java-symbol type="color" name="resolver_tabs_inactive_color" />
+  <java-symbol type="string" name="resolver_personal_tab" />
+  <java-symbol type="string" name="resolver_work_tab" />
+  <java-symbol type="id" name="stub" />
 
 </resources>
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/WifiConfigurationHelper.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/WifiConfigurationHelper.java
index f0a8367..a296ca2 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/WifiConfigurationHelper.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/WifiConfigurationHelper.java
@@ -16,6 +16,7 @@
 
 package com.android.connectivitymanagertest;
 
+import android.net.IpConfiguration;
 import android.net.IpConfiguration.IpAssignment;
 import android.net.IpConfiguration.ProxySettings;
 import android.net.LinkAddress;
@@ -136,7 +137,7 @@
         config.enterpriseConfig.setPhase2Method(phase2);
         config.enterpriseConfig.setIdentity(identity);
         config.enterpriseConfig.setAnonymousIdentity(anonymousIdentity);
-        config.enterpriseConfig.setCaCertificateAlias(caCert);
+        config.enterpriseConfig.setCaCertificateAliases(new String[] {caCert});
         config.enterpriseConfig.setClientCertificateAlias(clientCert);
         return config;
     }
@@ -147,8 +148,12 @@
     private static WifiConfiguration createGenericConfig(String ssid) {
         WifiConfiguration config = new WifiConfiguration();
         config.SSID = quotedString(ssid);
-        config.setIpAssignment(IpAssignment.DHCP);
-        config.setProxySettings(ProxySettings.NONE);
+
+        IpConfiguration ipConfiguration = config.getIpConfiguration();
+        ipConfiguration.setIpAssignment(IpAssignment.DHCP);
+        ipConfiguration.setProxySettings(ProxySettings.NONE);
+        config.setIpConfiguration(ipConfiguration);
+
         return config;
     }
 
@@ -237,6 +242,7 @@
                 throw new IllegalArgumentException();
         }
 
+        IpConfiguration ipConfiguration = config.getIpConfiguration();
         if (jsonConfig.has("ip")) {
             StaticIpConfiguration staticIpConfig = new StaticIpConfiguration();
 
@@ -247,13 +253,14 @@
             staticIpConfig.dnsServers.add(getInetAddress(jsonConfig.getString("dns1")));
             staticIpConfig.dnsServers.add(getInetAddress(jsonConfig.getString("dns2")));
 
-            config.setIpAssignment(IpAssignment.STATIC);
-            config.setStaticIpConfiguration(staticIpConfig);
+            ipConfiguration.setIpAssignment(IpAssignment.STATIC);
+            ipConfiguration.setStaticIpConfiguration(staticIpConfig);
         } else {
-            config.setIpAssignment(IpAssignment.DHCP);
+            ipConfiguration.setIpAssignment(IpAssignment.DHCP);
         }
+        ipConfiguration.setProxySettings(ProxySettings.NONE);
+        config.setIpConfiguration(ipConfiguration);
 
-        config.setProxySettings(ProxySettings.NONE);
         return config;
     }
 
diff --git a/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java b/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java
index 2989df8..5d42915 100644
--- a/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java
+++ b/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java
@@ -268,7 +268,7 @@
         File snd_stat = new File (root_filepath + "tcp_snd");
         int tx = BandwidthTestUtil.parseIntValueFromFile(snd_stat);
         NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1);
-        stats.addValues(NetworkStats.IFACE_ALL, uid, NetworkStats.SET_DEFAULT,
+        stats.addEntry(NetworkStats.IFACE_ALL, uid, NetworkStats.SET_DEFAULT,
                 NetworkStats.TAG_NONE, rx, 0, tx, 0, 0);
         return stats;
     }
diff --git a/core/tests/benchmarks/src/android/net/NetworkStatsBenchmark.java b/core/tests/benchmarks/src/android/net/NetworkStatsBenchmark.java
index 707d7b3..239f971 100644
--- a/core/tests/benchmarks/src/android/net/NetworkStatsBenchmark.java
+++ b/core/tests/benchmarks/src/android/net/NetworkStatsBenchmark.java
@@ -54,7 +54,7 @@
             recycle.txBytes = 150000;
             recycle.txPackets = 1500;
             recycle.operations = 0;
-            mNetworkStats.addValues(recycle);
+            mNetworkStats.addEntry(recycle);
             if (recycle.set == 1) {
                 uid++;
             }
@@ -70,7 +70,7 @@
             recycle.txBytes = 180000 * mSize;
             recycle.txPackets = 1200 * mSize;
             recycle.operations = 0;
-            mNetworkStats.addValues(recycle);
+            mNetworkStats.addEntry(recycle);
         }
     }
 
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index caae908..3836d6f 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -8,6 +8,7 @@
         "EnabledTestApp/src/**/*.java",
         "BinderProxyCountingTestApp/src/**/*.java",
         "BinderProxyCountingTestService/src/**/*.java",
+        "BinderDeathRecipientHelperApp/src/**/*.java",
         "aidl/**/I*.aidl",
     ],
 
@@ -59,7 +60,11 @@
     resource_dirs: ["res"],
     resource_zips: [":FrameworksCoreTests_apks_as_resources"],
 
-    data: [":BstatsTestApp"],
+    data: [
+        ":BstatsTestApp",
+        ":BinderDeathRecipientHelperApp1",
+        ":BinderDeathRecipientHelperApp2",
+    ],
 }
 
 // Rules to copy all the test apks to the intermediate raw resource directory
@@ -79,6 +84,11 @@
         ":FrameworksCoreTests_install_split_feature_a",
         ":FrameworksCoreTests_install_use_perm_good",
         ":FrameworksCoreTests_install_uses_feature",
+        ":FrameworksCoreTests_install_uses_sdk_0",
+        ":FrameworksCoreTests_install_uses_sdk_q0",
+        ":FrameworksCoreTests_install_uses_sdk_r",
+        ":FrameworksCoreTests_install_uses_sdk_r0",
+        ":FrameworksCoreTests_install_uses_sdk_r5",
         ":FrameworksCoreTests_install_verifier_bad",
         ":FrameworksCoreTests_install_verifier_good",
         ":FrameworksCoreTests_keyset_permdef_sa_unone",
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 1aea98a..b85a332 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -95,6 +95,7 @@
 
     <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
     <uses-permission android:name="android.permission.KILL_UID" />
+    <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES" />
 
     <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
 
diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml
index b40aa87..ed9d3f5 100644
--- a/core/tests/coretests/AndroidTest.xml
+++ b/core/tests/coretests/AndroidTest.xml
@@ -21,6 +21,8 @@
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="FrameworksCoreTests.apk" />
         <option name="test-file-name" value="BstatsTestApp.apk" />
+        <option name="test-file-name" value="BinderDeathRecipientHelperApp1.apk" />
+        <option name="test-file-name" value="BinderDeathRecipientHelperApp2.apk" />
     </target_preparer>
     <option name="test-tag" value="FrameworksCoreTests" />
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/core/tests/coretests/BinderDeathRecipientHelperApp/Android.bp b/core/tests/coretests/BinderDeathRecipientHelperApp/Android.bp
new file mode 100644
index 0000000..25e4fc3
--- /dev/null
+++ b/core/tests/coretests/BinderDeathRecipientHelperApp/Android.bp
@@ -0,0 +1,19 @@
+android_test_helper_app {
+    name: "BinderDeathRecipientHelperApp1",
+
+    srcs: ["**/*.java"],
+
+    sdk_version: "current",
+}
+
+android_test_helper_app {
+    name: "BinderDeathRecipientHelperApp2",
+
+    srcs: ["**/*.java"],
+
+    sdk_version: "current",
+
+    aaptflags: [
+            "--rename-manifest-package com.android.frameworks.coretests.bdr_helper_app2",
+        ],
+}
diff --git a/core/tests/coretests/BinderDeathRecipientHelperApp/AndroidManifest.xml b/core/tests/coretests/BinderDeathRecipientHelperApp/AndroidManifest.xml
new file mode 100644
index 0000000..dbd1774
--- /dev/null
+++ b/core/tests/coretests/BinderDeathRecipientHelperApp/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?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.coretests.bdr_helper_app1">
+
+    <application>
+        <receiver android:name="com.android.frameworks.coretests.bdr_helper_app.TestCommsReceiver"
+                  android:exported="true"/>
+
+    </application>
+</manifest>
diff --git a/core/tests/coretests/BinderDeathRecipientHelperApp/src/com/android/frameworks/coretests/bdr_helper_app/TestCommsReceiver.java b/core/tests/coretests/BinderDeathRecipientHelperApp/src/com/android/frameworks/coretests/bdr_helper_app/TestCommsReceiver.java
new file mode 100644
index 0000000..ab79e69
--- /dev/null
+++ b/core/tests/coretests/BinderDeathRecipientHelperApp/src/com/android/frameworks/coretests/bdr_helper_app/TestCommsReceiver.java
@@ -0,0 +1,51 @@
+/*
+ * 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.frameworks.coretests.bdr_helper_app;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * Receiver used to hand off a binder owned by this process to
+ * {@link android.os.BinderDeathRecipientTest}.
+ */
+public class TestCommsReceiver extends BroadcastReceiver {
+    private static final String TAG = TestCommsReceiver.class.getSimpleName();
+    private  static final String PACKAGE_NAME = "com.android.frameworks.coretests.bdr_helper_app";
+
+    public static final String ACTION_GET_BINDER = PACKAGE_NAME + ".action.GET_BINDER";
+    public static final String EXTRA_KEY_BINDER = PACKAGE_NAME + ".EXTRA_BINDER";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        switch (intent.getAction()) {
+            case ACTION_GET_BINDER:
+                final Bundle resultExtras = new Bundle();
+                resultExtras.putBinder(EXTRA_KEY_BINDER, new Binder());
+                setResult(Activity.RESULT_OK, null, resultExtras);
+                break;
+            default:
+                Log.e(TAG, "Unknown action " + intent.getAction());
+                break;
+        }
+    }
+}
diff --git a/core/tests/coretests/apks/install_uses_sdk/Android.bp b/core/tests/coretests/apks/install_uses_sdk/Android.bp
new file mode 100644
index 0000000..92b09ed
--- /dev/null
+++ b/core/tests/coretests/apks/install_uses_sdk/Android.bp
@@ -0,0 +1,39 @@
+android_test_helper_app {
+    name: "FrameworksCoreTests_install_uses_sdk_r0",
+    defaults: ["FrameworksCoreTests_apks_defaults"],
+    manifest: "AndroidManifest-r0.xml",
+
+    srcs: ["**/*.java"],
+}
+
+android_test_helper_app {
+    name: "FrameworksCoreTests_install_uses_sdk_r5",
+    defaults: ["FrameworksCoreTests_apks_defaults"],
+    manifest: "AndroidManifest-r5.xml",
+
+    srcs: ["**/*.java"],
+}
+
+android_test_helper_app {
+    name: "FrameworksCoreTests_install_uses_sdk_q0",
+    defaults: ["FrameworksCoreTests_apks_defaults"],
+    manifest: "AndroidManifest-q0.xml",
+
+    srcs: ["**/*.java"],
+}
+
+android_test_helper_app {
+    name: "FrameworksCoreTests_install_uses_sdk_r",
+    defaults: ["FrameworksCoreTests_apks_defaults"],
+    manifest: "AndroidManifest-r.xml",
+
+    srcs: ["**/*.java"],
+}
+
+android_test_helper_app {
+    name: "FrameworksCoreTests_install_uses_sdk_0",
+    defaults: ["FrameworksCoreTests_apks_defaults"],
+    manifest: "AndroidManifest-0.xml",
+
+    srcs: ["**/*.java"],
+}
diff --git a/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-0.xml b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-0.xml
new file mode 100644
index 0000000..634228b
--- /dev/null
+++ b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-0.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.coretests.install_uses_sdk">
+
+    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
+        <!-- This is invalid, because there is no sdk version specified -->
+        <extension-sdk android:minExtensionVersion="5" />
+    </uses-sdk>
+
+    <application>
+    </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-q0.xml b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-q0.xml
new file mode 100644
index 0000000..8994966
--- /dev/null
+++ b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-q0.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.coretests.install_uses_sdk">
+
+    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
+        <!-- This fails because 29 doesn't have an extension sdk -->
+        <extension-sdk android:sdkVersion="29" android:minExtensionVersion="0" />
+    </uses-sdk>
+
+    <application>
+    </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r.xml b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r.xml
new file mode 100644
index 0000000..0d0d8b9
--- /dev/null
+++ b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.coretests.install_uses_sdk">
+
+    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
+        <!-- This is invalid, because there is no minimum extension version specified -->
+        <extension-sdk android:sdkVersion="10000" />
+    </uses-sdk>
+
+    <application>
+    </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r0.xml b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r0.xml
new file mode 100644
index 0000000..a987afa
--- /dev/null
+++ b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r0.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.coretests.install_uses_sdk">
+
+    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
+        <extension-sdk android:sdkVersion="10000" android:minExtensionVersion="0" />
+    </uses-sdk>
+
+    <application>
+    </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r5.xml b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r5.xml
new file mode 100644
index 0000000..9860096
--- /dev/null
+++ b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r5.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.coretests.install_uses_sdk">
+
+    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
+        <!-- This will fail to install, because minExtensionVersion is not met -->
+        <extension-sdk android:sdkVersion="10000" android:minExtensionVersion="5" />
+    </uses-sdk>
+
+    <application>
+    </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install_uses_sdk/res/values/strings.xml b/core/tests/coretests/apks/install_uses_sdk/res/values/strings.xml
new file mode 100644
index 0000000..3b8b3b1
--- /dev/null
+++ b/core/tests/coretests/apks/install_uses_sdk/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Just need this dummy file to have something to build. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string name="dummy">dummy</string>
+</resources>
diff --git a/core/tests/coretests/res/values/styles.xml b/core/tests/coretests/res/values/styles.xml
index 0bf4b92..352b4dc 100644
--- a/core/tests/coretests/res/values/styles.xml
+++ b/core/tests/coretests/res/values/styles.xml
@@ -31,6 +31,9 @@
     <style name="LayoutInDisplayCutoutModeNever">
         <item name="android:windowLayoutInDisplayCutoutMode">never</item>
     </style>
+    <style name="LayoutInDisplayCutoutModeAlways">
+        <item name="android:windowLayoutInDisplayCutoutMode">always</item>
+    </style>
     <style name="WindowBackgroundColorLiteral">
         <item name="android:windowBackground">#00FF00</item>
     </style>
diff --git a/core/tests/coretests/src/android/accessibilityservice/AccessibilityServiceTest.java b/core/tests/coretests/src/android/accessibilityservice/AccessibilityServiceTest.java
new file mode 100644
index 0000000..c65ef9a
--- /dev/null
+++ b/core/tests/coretests/src/android/accessibilityservice/AccessibilityServiceTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.accessibilityservice;
+
+import static org.mockito.Mockito.verify;
+
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.view.accessibility.AccessibilityEvent;
+
+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;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for AccessibilityService.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityServiceTest {
+    private static final String TAG = "AccessibilityServiceTest";
+    private static final int CONNECTION_ID = 1;
+
+    private static class AccessibilityServiceTestClass extends AccessibilityService {
+        private IAccessibilityServiceClient mCallback;
+        private Looper mLooper;
+
+        AccessibilityServiceTestClass() {
+            super();
+            attachBaseContext(InstrumentationRegistry.getContext());
+            mLooper = InstrumentationRegistry.getContext().getMainLooper();
+        }
+
+        public void setupCallback(IAccessibilityServiceClient callback) {
+            mCallback = callback;
+        }
+
+        public Looper getMainLooper() {
+            return mLooper;
+        }
+
+        public void onAccessibilityEvent(AccessibilityEvent event) { }
+        public void onInterrupt() { }
+
+        @Override
+        public void onSystemActionsChanged() {
+            try {
+                if (mCallback != null) mCallback.onSystemActionsChanged();
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    private @Mock IAccessibilityServiceClient  mMockClientForCallback;
+    private @Mock IAccessibilityServiceConnection mMockConnection;
+    private @Mock IBinder mMockIBinder;
+    private IAccessibilityServiceClient mServiceInterface;
+    private AccessibilityServiceTestClass mService;
+
+    @Before
+    public void setUp() throws RemoteException {
+        MockitoAnnotations.initMocks(this);
+        mService = new AccessibilityServiceTestClass();
+        mService.setupCallback(mMockClientForCallback);
+        mServiceInterface = (IAccessibilityServiceClient) mService.onBind(new Intent());
+        mServiceInterface.init(mMockConnection, CONNECTION_ID, mMockIBinder);
+    }
+
+    @Test
+    public void testOnSystemActionsChanged() throws RemoteException {
+        mServiceInterface.onSystemActionsChanged();
+
+        verify(mMockClientForCallback).onSystemActionsChanged();
+    }
+
+    @Test
+    public void testGetSystemActions() throws RemoteException {
+        mService.getSystemActions();
+
+        verify(mMockConnection).getSystemActions();
+    }
+}
diff --git a/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java b/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java
index abaeb0a..ae6d8df 100644
--- a/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java
+++ b/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java
@@ -17,8 +17,10 @@
 package android.accessibilityservice;
 
 import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
 
 import android.content.ComponentName;
 import android.content.Context;
@@ -48,6 +50,7 @@
 public class AccessibilityShortcutInfoTest {
     private Context mTargetContext;
     private PackageManager mPackageManager;
+    private ComponentName mComponentName;
     private AccessibilityShortcutInfo mShortcutInfo;
 
     @Before
@@ -55,25 +58,9 @@
         mTargetContext = InstrumentationRegistry.getInstrumentation()
                 .getTargetContext();
         mPackageManager = mTargetContext.getPackageManager();
-
-        final ComponentName testShortcutName = new ComponentName(mTargetContext,
-                AccessibilityTestActivity.class);
-        final AccessibilityManager accessibilityManager = (AccessibilityManager) mTargetContext
-                .getSystemService(Context.ACCESSIBILITY_SERVICE);
-        final List<AccessibilityShortcutInfo> infoList = accessibilityManager
-                .getInstalledAccessibilityShortcutListAsUser(
-                        mTargetContext, mTargetContext.getUserId());
-        for (AccessibilityShortcutInfo info : infoList) {
-            final ActivityInfo activityInfo = info.getActivityInfo();
-            final ComponentName name = new ComponentName(
-                    activityInfo.packageName, activityInfo.name);
-            if (name.equals(testShortcutName)) {
-                mShortcutInfo = info;
-                break;
-            }
-        }
-
-        assertNotNull("Can't find " + testShortcutName, mShortcutInfo);
+        mComponentName = new ComponentName(mTargetContext, AccessibilityTestActivity.class);
+        mShortcutInfo = getAccessibilityShortcutInfo(mComponentName);
+        assertNotNull("Can't find " + mComponentName, mShortcutInfo);
     }
 
     @Test
@@ -95,4 +82,38 @@
         assertThat("Summary is not correct",
                 mShortcutInfo.loadSummary(mPackageManager), is(summary));
     }
+
+    @Test
+    public void testEquals() {
+        assertTrue(mShortcutInfo.equals(mShortcutInfo));
+        assertFalse(mShortcutInfo.equals(null));
+        assertFalse(mShortcutInfo.equals(new Object()));
+
+        final AccessibilityShortcutInfo sameCopy = getAccessibilityShortcutInfo(
+                mComponentName);
+        assertTrue(mShortcutInfo != sameCopy);
+        assertTrue(mShortcutInfo.hashCode() == sameCopy.hashCode());
+        assertTrue(mShortcutInfo.getComponentName().equals(sameCopy.getComponentName()));
+        assertTrue(mShortcutInfo.equals(sameCopy));
+    }
+
+    @Test
+    public void testToString() {
+        assertNotNull(mShortcutInfo.toString());
+    }
+
+    private AccessibilityShortcutInfo getAccessibilityShortcutInfo(ComponentName componentName) {
+        final AccessibilityManager accessibilityManager = (AccessibilityManager) mTargetContext
+                .getSystemService(Context.ACCESSIBILITY_SERVICE);
+        final List<AccessibilityShortcutInfo> infoList = accessibilityManager
+                .getInstalledAccessibilityShortcutListAsUser(
+                        mTargetContext, mTargetContext.getUserId());
+        for (AccessibilityShortcutInfo info : infoList) {
+            final ActivityInfo activityInfo = info.getActivityInfo();
+            if (componentName.equals(activityInfo.getComponentName())) {
+                return info;
+            }
+        }
+        return null;
+    }
 }
diff --git a/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java b/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java
index 68b9b00..f4709ff 100644
--- a/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java
+++ b/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java
@@ -33,14 +33,15 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.support.test.uiautomator.UiDevice;
 import android.test.InstrumentationTestCase;
 import android.util.Log;
 
-import libcore.io.Streams;
-
 import com.google.mockwebserver.MockResponse;
 import com.google.mockwebserver.MockWebServer;
 
+import libcore.io.Streams;
+
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
 import java.io.File;
@@ -63,6 +64,7 @@
     private static final String TAG = "DownloadManagerBaseTest";
     protected DownloadManager mDownloadManager = null;
     private MockWebServer mServer = null;
+    private UiDevice mUiDevice = null;
     protected String mFileType = "text/plain";
     protected Context mContext = null;
     protected MultipleDownloadsCompletedReceiver mReceiver = null;
@@ -234,6 +236,7 @@
     @Override
     public void setUp() throws Exception {
         mContext = getInstrumentation().getContext();
+        mUiDevice = UiDevice.getInstance(getInstrumentation());
         mDownloadManager = (DownloadManager)mContext.getSystemService(Context.DOWNLOAD_SERVICE);
         mServer = new MockWebServer();
         mServer.play();
@@ -512,7 +515,7 @@
         Log.i(LOG_TAG, "Setting WiFi State to: " + enable);
         WifiManager manager = (WifiManager)mContext.getSystemService(Context.WIFI_SERVICE);
 
-        manager.setWifiEnabled(enable);
+        mUiDevice.executeShellCommand("svc wifi " + (enable ? "enable" : "disable"));
 
         String timeoutMessage = "Timed out waiting for Wifi to be "
             + (enable ? "enabled!" : "disabled!");
diff --git a/core/tests/coretests/src/android/app/NotificationHistoryTest.java b/core/tests/coretests/src/android/app/NotificationHistoryTest.java
index f9a6a5c..0a21875 100644
--- a/core/tests/coretests/src/android/app/NotificationHistoryTest.java
+++ b/core/tests/coretests/src/android/app/NotificationHistoryTest.java
@@ -116,25 +116,28 @@
     @Test
     public void testAddNotificationsToWrite() {
         NotificationHistory history = new NotificationHistory();
-        HistoricalNotification n = getHistoricalNotification(0);
+        HistoricalNotification n = getHistoricalNotification(3);
         HistoricalNotification n2 = getHistoricalNotification(1);
+        HistoricalNotification n5 = getHistoricalNotification(0);
         history.addNotificationToWrite(n2);
         history.addNotificationToWrite(n);
+        history.addNotificationToWrite(n5);
 
         NotificationHistory secondHistory = new NotificationHistory();
-        HistoricalNotification n3 = getHistoricalNotification(2);
-        HistoricalNotification n4 = getHistoricalNotification(3);
+        HistoricalNotification n3 = getHistoricalNotification(4);
+        HistoricalNotification n4 = getHistoricalNotification(2);
         secondHistory.addNotificationToWrite(n4);
         secondHistory.addNotificationToWrite(n3);
 
         history.addNotificationsToWrite(secondHistory);
 
-        assertThat(history.getNotificationsToWrite().size()).isEqualTo(4);
-        assertThat(history.getNotificationsToWrite().get(0)).isSameAs(n2);
+        assertThat(history.getNotificationsToWrite().size()).isEqualTo(5);
+        assertThat(history.getNotificationsToWrite().get(0)).isSameAs(n3);
         assertThat(history.getNotificationsToWrite().get(1)).isSameAs(n);
         assertThat(history.getNotificationsToWrite().get(2)).isSameAs(n4);
-        assertThat(history.getNotificationsToWrite().get(3)).isSameAs(n3);
-        assertThat(history.getHistoryCount()).isEqualTo(4);
+        assertThat(history.getNotificationsToWrite().get(3)).isSameAs(n2);
+        assertThat(history.getNotificationsToWrite().get(4)).isSameAs(n5);
+        assertThat(history.getHistoryCount()).isEqualTo(5);
 
         assertThat(history.getPooledStringsToWrite()).asList().contains(n2.getChannelName());
         assertThat(history.getPooledStringsToWrite()).asList().contains(n4.getPackage());
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index beaaa37..d8b527c 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -438,8 +438,7 @@
     }
 
     private static ClientTransaction newStopTransaction(Activity activity) {
-        final StopActivityItem stopStateRequest =
-                StopActivityItem.obtain(false /* showWindow */, 0 /* configChanges */);
+        final StopActivityItem stopStateRequest = StopActivityItem.obtain(0 /* configChanges */);
 
         final ClientTransaction transaction = newTransaction(activity);
         transaction.setLifecycleStateRequest(stopStateRequest);
diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchDocumentTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchDocumentTest.java
new file mode 100644
index 0000000..abba7fc
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/AppSearchDocumentTest.java
@@ -0,0 +1,218 @@
+/*
+ * 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.appsearch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.appsearch.AppSearch.Document;
+
+import androidx.test.filters.SmallTest;
+
+import com.google.android.icing.proto.DocumentProto;
+import com.google.android.icing.proto.PropertyProto;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+@SmallTest
+public class AppSearchDocumentTest {
+
+    @Test
+    public void testDocumentEquals_Identical() {
+        Document document1 = Document.newBuilder("uri1", "schemaType1")
+                .setCreationTimestampMillis(5L)
+                .setProperty("longKey1", 1L, 2L, 3L)
+                .setProperty("doubleKey1", 1.0, 2.0, 3.0)
+                .setProperty("booleanKey1", true, false, true)
+                .setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
+                .build();
+        Document document2 = Document.newBuilder("uri1", "schemaType1")
+                .setCreationTimestampMillis(5L)
+                .setProperty("longKey1", 1L, 2L, 3L)
+                .setProperty("doubleKey1", 1.0, 2.0, 3.0)
+                .setProperty("booleanKey1", true, false, true)
+                .setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
+                .build();
+        assertThat(document1).isEqualTo(document2);
+        assertThat(document1.hashCode()).isEqualTo(document2.hashCode());
+    }
+
+    @Test
+    public void testDocumentEquals_DifferentOrder() {
+        Document document1 = Document.newBuilder("uri1", "schemaType1")
+                .setCreationTimestampMillis(5L)
+                .setProperty("longKey1", 1L, 2L, 3L)
+                .setProperty("doubleKey1", 1.0, 2.0, 3.0)
+                .setProperty("booleanKey1", true, false, true)
+                .setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
+                .build();
+
+        // Create second document with same parameter but different order.
+        Document document2 = Document.newBuilder("uri1", "schemaType1")
+                .setCreationTimestampMillis(5L)
+                .setProperty("booleanKey1", true, false, true)
+                .setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
+                .setProperty("doubleKey1", 1.0, 2.0, 3.0)
+                .setProperty("longKey1", 1L, 2L, 3L)
+                .build();
+        assertThat(document1).isEqualTo(document2);
+        assertThat(document1.hashCode()).isEqualTo(document2.hashCode());
+    }
+
+    @Test
+    public void testDocumentEquals_Failure() {
+        Document document1 = Document.newBuilder("uri1", "schemaType1")
+                .setCreationTimestampMillis(5L)
+                .setProperty("longKey1", 1L, 2L, 3L)
+                .build();
+
+        // Create second document with same order but different value.
+        Document document2 = Document.newBuilder("uri1", "schemaType1")
+                .setCreationTimestampMillis(5L)
+                .setProperty("longKey1", 1L, 2L, 4L) // Different
+                .build();
+        assertThat(document1).isNotEqualTo(document2);
+        assertThat(document1.hashCode()).isNotEqualTo(document2.hashCode());
+    }
+
+    @Test
+    public void testDocumentEquals_Failure_RepeatedFieldOrder() {
+        Document document1 = Document.newBuilder("uri1", "schemaType1")
+                .setCreationTimestampMillis(5L)
+                .setProperty("booleanKey1", true, false, true)
+                .build();
+
+        // Create second document with same order but different value.
+        Document document2 = Document.newBuilder("uri1", "schemaType1")
+                .setCreationTimestampMillis(5L)
+                .setProperty("booleanKey1", true, true, false) // Different
+                .build();
+        assertThat(document1).isNotEqualTo(document2);
+        assertThat(document1.hashCode()).isNotEqualTo(document2.hashCode());
+    }
+
+    @Test
+    public void testDocumentGetSingleValue() {
+        Document document = Document.newBuilder("uri1", "schemaType1")
+                .setCreationTimestampMillis(5L)
+                .setScore(1)
+                .setProperty("longKey1", 1L)
+                .setProperty("doubleKey1", 1.0)
+                .setProperty("booleanKey1", true)
+                .setProperty("stringKey1", "test-value1").build();
+        assertThat(document.getUri()).isEqualTo("uri1");
+        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");
+    }
+
+    @Test
+    public void testDocumentGetArrayValues() {
+        Document document = Document.newBuilder("uri1", "schemaType1")
+                .setCreationTimestampMillis(5L)
+                .setProperty("longKey1", 1L, 2L, 3L)
+                .setProperty("doubleKey1", 1.0, 2.0, 3.0)
+                .setProperty("booleanKey1", true, false, true)
+                .setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
+                .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");
+    }
+
+    @Test
+    public void testDocumentGetValues_DifferentTypes() {
+        Document document = Document.newBuilder("uri1", "schemaType1")
+                .setScore(1)
+                .setProperty("longKey1", 1L)
+                .setProperty("booleanKey1", true, false, true)
+                .setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
+                .build();
+
+        // Get a value for a key that doesn't exist
+        assertThat(document.getPropertyDouble("doubleKey1")).isNull();
+        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")).isNull();
+        assertThat(document.getPropertyDoubleArray("longKey1")).isNull();
+    }
+
+    @Test
+    public void testDocumentInvalid() {
+        Document.Builder builder = Document.newBuilder("uri1", "schemaType1");
+        assertThrows(
+                IllegalArgumentException.class, () -> builder.setProperty("test", new boolean[]{}));
+    }
+
+    @Test
+    public void testDocumentProtoPopulation() {
+        Document document = Document.newBuilder("uri1", "schemaType1")
+                .setCreationTimestampMillis(5L)
+                .setScore(1)
+                .setProperty("longKey1", 1L)
+                .setProperty("doubleKey1", 1.0)
+                .setProperty("booleanKey1", true)
+                .setProperty("stringKey1", "test-value1")
+                .build();
+
+        // Create the Document proto. Need to sort the property order by key.
+        DocumentProto.Builder documentProtoBuilder = DocumentProto.newBuilder()
+                .setUri("uri1").setSchema("schemaType1").setScore(1).setCreationTimestampMs(5L);
+        HashMap<String, PropertyProto.Builder> propertyProtoMap = new HashMap<>();
+        propertyProtoMap.put("longKey1",
+                PropertyProto.newBuilder().setName("longKey1").addInt64Values(1L));
+        propertyProtoMap.put("doubleKey1",
+                PropertyProto.newBuilder().setName("doubleKey1").addDoubleValues(1.0));
+        propertyProtoMap.put("booleanKey1",
+                PropertyProto.newBuilder().setName("booleanKey1").addBooleanValues(true));
+        propertyProtoMap.put("stringKey1",
+                PropertyProto.newBuilder().setName("stringKey1").addStringValues("test-value1"));
+        List<String> sortedKey = new ArrayList<>(propertyProtoMap.keySet());
+        Collections.sort(sortedKey);
+        for (String key : sortedKey) {
+            documentProtoBuilder.addProperties(propertyProtoMap.get(key));
+        }
+        assertThat(document.getProto()).isEqualTo(documentProtoBuilder.build());
+    }
+}
diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchEmailTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchEmailTest.java
new file mode 100644
index 0000000..c50b1da
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/AppSearchEmailTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.appsearch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.appsearch.AppSearch.Email;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+@SmallTest
+public class AppSearchEmailTest {
+
+    @Test
+    public void testBuildEmailAndGetValue() {
+        Email email = Email.newBuilder("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)
+                .setProperty("propertyKey", "propertyValue1", "propertyValue2")
+                .setSubject("subject")
+                .setBody("EmailBody")
+                .build();
+
+        assertThat(email.getUri()).isEqualTo("uri");
+        assertThat(email.getFrom()).isEqualTo("FakeFromAddress");
+        assertThat(email.getTo()).isNull();
+        assertThat(email.getCc()).asList().containsExactly("CC1", "CC2");
+        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.getSubject()).isEqualTo("subject");
+        assertThat(email.getBody()).isEqualTo("EmailBody");
+    }
+}
diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchSchemaTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchSchemaTest.java
new file mode 100644
index 0000000..08ec2d0
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/AppSearchSchemaTest.java
@@ -0,0 +1,166 @@
+/*
+ * 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.app.appsearch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.expectThrows;
+
+import android.app.appsearch.AppSearchSchema.PropertyConfig;
+
+import androidx.test.filters.SmallTest;
+
+import com.google.android.icing.proto.IndexingConfig.TokenizerType;
+import com.google.android.icing.proto.PropertyConfigProto;
+import com.google.android.icing.proto.SchemaTypeConfigProto;
+import com.google.android.icing.proto.TermMatchType;
+
+import org.junit.Test;
+
+@SmallTest
+public class AppSearchSchemaTest {
+    @Test
+    public void testGetProto_Email() {
+        AppSearchSchema emailSchema = AppSearchSchema.newBuilder("Email")
+                .addProperty(AppSearchSchema.newPropertyBuilder("subject")
+                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .build()
+                ).addProperty(AppSearchSchema.newPropertyBuilder("body")
+                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .setTokenizerType(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.google.android.icing.proto.IndexingConfig.newBuilder()
+                                        .setTokenizerType(TokenizerType.Code.PLAIN)
+                                        .setTermMatchType(TermMatchType.Code.PREFIX)
+                        )
+                ).addProperties(PropertyConfigProto.newBuilder()
+                        .setPropertyName("body")
+                        .setDataType(PropertyConfigProto.DataType.Code.STRING)
+                        .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+                        .setIndexingConfig(
+                                com.google.android.icing.proto.IndexingConfig.newBuilder()
+                                        .setTokenizerType(TokenizerType.Code.PLAIN)
+                                        .setTermMatchType(TermMatchType.Code.PREFIX)
+                        )
+                ).build();
+
+        assertThat(emailSchema.getProto()).isEqualTo(expectedEmailProto);
+    }
+
+    @Test
+    public void testGetProto_MusicRecording() {
+        AppSearchSchema musicRecordingSchema = AppSearchSchema.newBuilder("MusicRecording")
+                .addProperty(AppSearchSchema.newPropertyBuilder("artist")
+                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                        .setCardinality(PropertyConfig.CARDINALITY_REPEATED)
+                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .build()
+                ).addProperty(AppSearchSchema.newPropertyBuilder("pubDate")
+                        .setDataType(PropertyConfig.DATA_TYPE_INT64)
+                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setIndexingType(PropertyConfig.INDEXING_TYPE_NONE)
+                        .setTokenizerType(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.google.android.icing.proto.IndexingConfig.newBuilder()
+                                        .setTokenizerType(TokenizerType.Code.PLAIN)
+                                        .setTermMatchType(TermMatchType.Code.PREFIX)
+                        )
+                ).addProperties(PropertyConfigProto.newBuilder()
+                        .setPropertyName("pubDate")
+                        .setDataType(PropertyConfigProto.DataType.Code.INT64)
+                        .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+                        .setIndexingConfig(
+                                com.google.android.icing.proto.IndexingConfig.newBuilder()
+                                        .setTokenizerType(TokenizerType.Code.NONE)
+                                        .setTermMatchType(TermMatchType.Code.UNKNOWN)
+                        )
+                ).build();
+
+        assertThat(musicRecordingSchema.getProto()).isEqualTo(expectedMusicRecordingProto);
+    }
+
+    @Test
+    public void testInvalidEnums() {
+        PropertyConfig.Builder builder = AppSearchSchema.newPropertyBuilder("test");
+        assertThrows(IllegalArgumentException.class, () -> builder.setDataType(99));
+        assertThrows(IllegalArgumentException.class, () -> builder.setCardinality(99));
+    }
+
+    @Test
+    public void testMissingFields() {
+        PropertyConfig.Builder builder = AppSearchSchema.newPropertyBuilder("test");
+        Exception 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 = AppSearchSchema.newBuilder("Email")
+                .addProperty(AppSearchSchema.newPropertyBuilder("subject")
+                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .build()
+                ).addProperty(AppSearchSchema.newPropertyBuilder("subject")
+                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .build()
+                );
+
+        Exception e = expectThrows(IllegalSchemaException.class, builder::build);
+        assertThat(e).hasMessageThat().contains("Property defined more than once: subject");
+    }
+}
diff --git a/core/tests/coretests/src/android/app/appsearch/SearchResultsTest.java b/core/tests/coretests/src/android/app/appsearch/SearchResultsTest.java
new file mode 100644
index 0000000..21259cc
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/SearchResultsTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.appsearch;
+
+import static  com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import androidx.test.filters.SmallTest;
+
+import com.google.android.icing.proto.DocumentProto;
+import com.google.android.icing.proto.SearchResultProto;
+
+import org.junit.Test;
+
+@SmallTest
+public class SearchResultsTest {
+
+    @Test
+    public void testSearchResultsEqual() {
+        final String uri = "testUri";
+        final String schemaType = "testSchema";
+        SearchResultProto.ResultProto result1 = SearchResultProto.ResultProto.newBuilder()
+                .setDocument(DocumentProto.newBuilder()
+                        .setUri(uri)
+                        .setSchema(schemaType)
+                        .build())
+                .build();
+        SearchResultProto searchResults1 = SearchResultProto.newBuilder()
+                .addResults(result1)
+                .build();
+        SearchResults res1 = new SearchResults(searchResults1);
+        SearchResultProto.ResultProto result2 = SearchResultProto.ResultProto.newBuilder()
+                .setDocument(DocumentProto.newBuilder()
+                        .setUri(uri)
+                        .setSchema(schemaType)
+                        .build())
+                .build();
+        SearchResultProto searchResults2 = SearchResultProto.newBuilder()
+                .addResults(result2)
+                .build();
+        SearchResults res2 = new SearchResults(searchResults2);
+        assertThat(res1.toString()).isEqualTo(res2.toString());
+    }
+
+    @Test
+    public void buildSearchSpecWithoutTermMatchType() {
+        assertThrows(RuntimeException.class, () -> SearchSpec.newBuilder()
+                .setSchemaTypes("testSchemaType")
+                .build());
+    }
+}
diff --git a/core/tests/coretests/src/android/app/appsearch/SnippetTest.java b/core/tests/coretests/src/android/app/appsearch/SnippetTest.java
new file mode 100644
index 0000000..3103708
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/SnippetTest.java
@@ -0,0 +1,200 @@
+/*
+ * 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.app.appsearch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+
+import com.google.android.icing.proto.DocumentProto;
+import com.google.android.icing.proto.PropertyProto;
+import com.google.android.icing.proto.SearchResultProto;
+import com.google.android.icing.proto.SnippetMatchProto;
+import com.google.android.icing.proto.SnippetProto;
+
+import org.junit.Test;
+
+@SmallTest
+public class SnippetTest {
+
+    // TODO(sidchhabra): Add tests for Double and Long Snippets.
+    @Test
+    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 uri = "uri1";
+        final String schemaType = "schema1";
+        final String searchWord = "foo";
+        final String exactMatch = "foo";
+        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();
+        SearchResults searchResults = new SearchResults(searchResultProto);
+
+        // Making ResultReader and getting Snippet values.
+        while (searchResults.hasNext()) {
+            SearchResults.Result result = searchResults.next();
+            MatchInfo match = result.getMatchInfo().get(0);
+            assertThat(match.getPropertyPath()).isEqualTo(propertyKeyString);
+            assertThat(match.getFullText()).isEqualTo(propertyValueString);
+            assertThat(match.getExactMatch()).isEqualTo(exactMatch);
+            assertThat(match.getSnippet()).isEqualTo(window);
+        }
+    }
+
+    // TODO(sidchhabra): Add tests for Double and Long Snippets.
+    @Test
+    public void testNoSnippets() {
+
+        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 uri = "uri1";
+        final String schemaType = "schema1";
+        final String searchWord = "foo";
+        final String exactMatch = "foo";
+        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();
+        SearchResults searchResults = new SearchResults(searchResultProto);
+
+        while (searchResults.hasNext()) {
+            SearchResults.Result result = searchResults.next();
+            assertThat(result.getMatchInfo()).isEqualTo(null);
+        }
+    }
+
+    @Test
+    public void testMultipleStringSnippet() {
+        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();
+        SearchResults searchResults = new SearchResults(searchResultProto);
+
+        // Making ResultReader and getting Snippet values.
+        while (searchResults.hasNext()) {
+            SearchResults.Result result = searchResults.next();
+
+            MatchInfo match1 = result.getMatchInfo().get(0);
+            assertThat(match1.getPropertyPath()).isEqualTo("sender.name");
+            assertThat(match1.getFullText()).isEqualTo("Test Name Jr.");
+            assertThat(match1.getExactMatch()).isEqualTo("Test");
+            assertThat(match1.getSnippet()).isEqualTo("Test Name");
+
+            MatchInfo match2 = result.getMatchInfo().get(1);
+            assertThat(match2.getPropertyPath()).isEqualTo("sender.email");
+            assertThat(match2.getFullText()).isEqualTo("TestNameJr@gmail.com");
+            assertThat(match2.getExactMatch()).isEqualTo("TestNameJr@gmail.com");
+            assertThat(match2.getSnippet()).isEqualTo("TestNameJr@gmail.com");
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/app/appsearch/impl/CustomerDocumentTest.java b/core/tests/coretests/src/android/app/appsearch/impl/CustomerDocumentTest.java
new file mode 100644
index 0000000..c5986bb
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/impl/CustomerDocumentTest.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 android.app.appsearch.impl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearch.Document;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/** Tests that {@link Document} and {@link Document.Builder} are extendable by developers.
+ *
+ * <p>This class is intentionally in a different package than {@link Document} to make sure there
+ * are no package-private methods required for external developers to add custom types.
+ */
+@SmallTest
+public class CustomerDocumentTest {
+    @Test
+    public void testBuildCustomerDocument() {
+        CustomerDocument customerDocument = CustomerDocument.newBuilder("uri1")
+                .setScore(1)
+                .setCreationTimestampMillis(0)
+                .setProperty("longKey1", 1L, 2L, 3L)
+                .setProperty("doubleKey1", 1.0, 2.0, 3.0)
+                .setProperty("booleanKey1", true, false, true)
+                .setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
+                .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");
+    }
+
+    /**
+     * An example document type for test purposes, defined outside of
+     * {@link android.app.appsearch.AppSearch} (the way an external developer would define it).
+     */
+    private static class CustomerDocument extends Document {
+        private CustomerDocument(Document document) {
+            super(document);
+        }
+
+        public static CustomerDocument.Builder newBuilder(String uri) {
+            return new CustomerDocument.Builder(uri);
+        }
+
+        public static class Builder extends Document.Builder<CustomerDocument.Builder> {
+            private Builder(@NonNull String uri) {
+                super(uri, "customerDocument");
+            }
+
+            @Override
+            public CustomerDocument build() {
+                return new CustomerDocument(super.build());
+            }
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 37d21f0..4b29d59 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -274,30 +274,15 @@
 
     @Test
     public void testRecycleStopItem() {
-        StopActivityItem emptyItem = StopActivityItem.obtain(false, 0);
-        StopActivityItem item = StopActivityItem.obtain(true, 4);
+        StopActivityItem emptyItem = StopActivityItem.obtain(0);
+        StopActivityItem item = StopActivityItem.obtain(4);
         assertNotSame(item, emptyItem);
         assertFalse(item.equals(emptyItem));
 
         item.recycle();
         assertEquals(item, emptyItem);
 
-        StopActivityItem item2 = StopActivityItem.obtain(true, 3);
-        assertSame(item, item2);
-        assertFalse(item2.equals(emptyItem));
-    }
-
-    @Test
-    public void testRecycleWindowVisibleItem() {
-        WindowVisibilityItem emptyItem = WindowVisibilityItem.obtain(false);
-        WindowVisibilityItem item = WindowVisibilityItem.obtain(true);
-        assertNotSame(item, emptyItem);
-        assertFalse(item.equals(emptyItem));
-
-        item.recycle();
-        assertEquals(item, emptyItem);
-
-        WindowVisibilityItem item2 = WindowVisibilityItem.obtain(true);
+        StopActivityItem item2 = StopActivityItem.obtain(3);
         assertSame(item, item2);
         assertFalse(item2.equals(emptyItem));
     }
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index 1410f4f..09ea1b1 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -124,7 +124,7 @@
         assertArrayEquals(new int[] {}, path(ON_START));
         assertArrayEquals(new int[] {ON_RESUME}, path(ON_RESUME));
         assertArrayEquals(new int[] {ON_RESUME, ON_PAUSE}, path(ON_PAUSE));
-        assertArrayEquals(new int[] {ON_RESUME, ON_PAUSE, ON_STOP}, path(ON_STOP));
+        assertArrayEquals(new int[] {ON_STOP}, path(ON_STOP));
         assertArrayEquals(new int[] {ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY}, path(ON_DESTROY));
     }
 
@@ -362,7 +362,9 @@
     public void testClosestStateResolutionFromOnStart() {
         mClientRecord.setState(ON_START);
         assertEquals(ON_RESUME, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray(
-                new int[] {ON_CREATE, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY})));
+                new int[] {ON_CREATE, ON_RESUME, ON_PAUSE, ON_DESTROY})));
+        assertEquals(ON_STOP, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray(
+                new int[] {ON_STOP})));
         assertEquals(ON_CREATE, mExecutorHelper.getClosestOfStates(mClientRecord, shuffledArray(
                 new int[] {ON_CREATE})));
     }
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 39bf742..ecea901 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -61,6 +61,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
@@ -180,31 +181,6 @@
     }
 
     @Test
-    public void testWindowVisibilityChange() {
-        // Write to parcel
-        WindowVisibilityItem item = WindowVisibilityItem.obtain(true /* showWindow */);
-        writeAndPrepareForReading(item);
-
-        // Read from parcel and assert
-        WindowVisibilityItem result = WindowVisibilityItem.CREATOR.createFromParcel(mParcel);
-
-        assertEquals(item.hashCode(), result.hashCode());
-        assertTrue(item.equals(result));
-
-        // Check different value
-        item = WindowVisibilityItem.obtain(false);
-
-        mParcel = Parcel.obtain();
-        writeAndPrepareForReading(item);
-
-        // Read from parcel and assert
-        result = WindowVisibilityItem.CREATOR.createFromParcel(mParcel);
-
-        assertEquals(item.hashCode(), result.hashCode());
-        assertTrue(item.equals(result));
-    }
-
-    @Test
     public void testDestroy() {
         DestroyActivityItem item = DestroyActivityItem.obtain(true /* finished */,
                 135 /* configChanges */);
@@ -299,8 +275,7 @@
     @Test
     public void testStop() {
         // Write to parcel
-        StopActivityItem item = StopActivityItem.obtain(true /* showWindow */,
-                14 /* configChanges */);
+        StopActivityItem item = StopActivityItem.obtain(14 /* configChanges */);
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
@@ -311,14 +286,26 @@
     }
 
     @Test
+    public void testStart() {
+        // Write to parcel
+        StartActivityItem item = StartActivityItem.obtain();
+        writeAndPrepareForReading(item);
+
+        // Read from parcel and assert
+        StartActivityItem result = StartActivityItem.CREATOR.createFromParcel(mParcel);
+
+        assertEquals(item.hashCode(), result.hashCode());
+        assertEquals(item, result);
+    }
+
+    @Test
     public void testClientTransaction() {
         // Write to parcel
-        WindowVisibilityItem callback1 = WindowVisibilityItem.obtain(true);
+        NewIntentItem callback1 = NewIntentItem.obtain(new ArrayList<>(), true);
         ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain(
                 config());
 
-        StopActivityItem lifecycleRequest = StopActivityItem.obtain(true /* showWindow */,
-                78 /* configChanges */);
+        StopActivityItem lifecycleRequest = StopActivityItem.obtain(78 /* configChanges */);
 
         IApplicationThread appThread = new StubAppThread();
         Binder activityToken = new Binder();
@@ -340,7 +327,7 @@
     @Test
     public void testClientTransactionCallbacksOnly() {
         // Write to parcel
-        WindowVisibilityItem callback1 = WindowVisibilityItem.obtain(true);
+        NewIntentItem callback1 = NewIntentItem.obtain(new ArrayList<>(), true);
         ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain(
                 config());
 
@@ -363,8 +350,7 @@
     @Test
     public void testClientTransactionLifecycleOnly() {
         // Write to parcel
-        StopActivityItem lifecycleRequest = StopActivityItem.obtain(true /* showWindow */,
-                78 /* configChanges */);
+        StopActivityItem lifecycleRequest = StopActivityItem.obtain(78 /* configChanges */);
 
         IApplicationThread appThread = new StubAppThread();
         Binder activityToken = new Binder();
diff --git a/core/tests/coretests/src/android/content/ApexContextTest.java b/core/tests/coretests/src/android/content/ApexContextTest.java
new file mode 100644
index 0000000..d15c64d
--- /dev/null
+++ b/core/tests/coretests/src/android/content/ApexContextTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.UserHandle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ApexContextTest {
+
+    @Test
+    public void dataDirectoryPathsAreAsExpected() {
+        ApexContext apexContext = ApexContext.getApexContext("my.apex");
+
+        assertEquals("/data/misc/apexdata/my.apex",
+                apexContext.getDeviceProtectedDataDir().getAbsolutePath());
+
+        assertEquals("/data/misc_de/5/apexdata/my.apex",
+                apexContext.getDeviceProtectedDataDirForUser(UserHandle.of(5)).getAbsolutePath());
+
+        assertEquals("/data/misc_ce/16/apexdata/my.apex",
+                apexContext.getCredentialProtectedDataDirForUser(
+                        UserHandle.of(16)).getAbsolutePath());
+    }
+}
diff --git a/core/tests/coretests/src/android/content/PermissionCheckerTest.java b/core/tests/coretests/src/android/content/PermissionCheckerTest.java
new file mode 100644
index 0000000..cb04a74
--- /dev/null
+++ b/core/tests/coretests/src/android/content/PermissionCheckerTest.java
@@ -0,0 +1,195 @@
+/*
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.AppOpsManager;
+import android.app.UiAutomation;
+import android.os.Binder;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+// TODO(b/147877945): Add missing tests.
+@RunWith(AndroidJUnit4.class)
+public class PermissionCheckerTest {
+    private static final String INTERACT_ACROSS_PROFILES_PERMISSION =
+            "android.permission.INTERACT_ACROSS_PROFILES";
+    private static final String MANAGE_APP_OPS_MODE = "android.permission.MANAGE_APP_OPS_MODES";
+
+    private  final Context mContext = InstrumentationRegistry.getContext();;
+    private final AppOpsManager mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
+    private UiAutomation mUiAutomation =
+            InstrumentationRegistry.getInstrumentation().getUiAutomation();
+
+    @After
+    public void tearDown() {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    @Test
+    public void testCheckPermissionForPreflight_appOpPermission_modeDefaultAndPermissionGranted_returnsGranted() {
+        mUiAutomation.adoptShellPermissionIdentity(
+                INTERACT_ACROSS_PROFILES_PERMISSION, MANAGE_APP_OPS_MODE);
+        mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION),
+                Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_DEFAULT);
+
+        assertThat(PermissionChecker.checkPermissionForPreflight(
+                    mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(),
+                    Binder.getCallingUid(), mContext.getPackageName()))
+                .isEqualTo(PermissionChecker.PERMISSION_GRANTED);
+    }
+
+    @Test
+    public void testCheckPermissionForPreflight_appOpPermission_modeDefaultAndPermissionNotGranted_returnsHardDenied() {
+        mUiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE);
+        mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION),
+                Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_DEFAULT);
+
+        assertThat(PermissionChecker.checkPermissionForPreflight(
+                    mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(),
+                    Binder.getCallingUid(), mContext.getPackageName()))
+                .isEqualTo(PermissionChecker.PERMISSION_HARD_DENIED);
+    }
+
+    @Test
+    public void testCheckPermissionForPreflight_appOpPermission_modeAllowed_returnsGranted() {
+        mUiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE);
+        mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION),
+                Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_ALLOWED);
+
+        assertThat(PermissionChecker.checkPermissionForPreflight(
+                    mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(),
+                    Binder.getCallingUid(), mContext.getPackageName()))
+                .isEqualTo(PermissionChecker.PERMISSION_GRANTED);
+    }
+
+    @Test
+    public void testCheckPermissionForPreflight_appOpPermission_packageNameIsNull_returnsGranted() {
+        mUiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE);
+        mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION),
+                Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_ALLOWED);
+
+        assertThat(PermissionChecker.checkPermissionForPreflight(
+                    mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(),
+                    Binder.getCallingUid(), /* packageName= */ null))
+                .isEqualTo(PermissionChecker.PERMISSION_GRANTED);
+    }
+
+    @Test
+    public void testCheckPermissionForPreflight_appOpPermission_modeIgnored_returnsHardDenied() {
+        mUiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE);
+        mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION),
+                Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_IGNORED);
+
+        assertThat(PermissionChecker.checkPermissionForPreflight(
+                    mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(),
+                    Binder.getCallingUid(), mContext.getPackageName()))
+                .isEqualTo(PermissionChecker.PERMISSION_HARD_DENIED);
+    }
+
+    @Test
+    public void testCheckPermissionForPreflight_appOpPermission_modeErrored_returnsHardDenied() {
+        mUiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE);
+        mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION),
+                Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_ERRORED);
+
+        assertThat(PermissionChecker.checkPermissionForPreflight(
+                mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(),
+                Binder.getCallingUid(), mContext.getPackageName()))
+                .isEqualTo(PermissionChecker.PERMISSION_HARD_DENIED);
+    }
+
+    @Test
+    public void testCheckPermissionForDataDelivery_appOpPermission_modeDefaultAndPermissionGranted_returnsGranted() {
+        mUiAutomation.adoptShellPermissionIdentity(
+                INTERACT_ACROSS_PROFILES_PERMISSION, MANAGE_APP_OPS_MODE);
+        mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION),
+                Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_DEFAULT);
+
+        assertThat(PermissionChecker.checkPermissionForDataDelivery(
+                mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(),
+                Binder.getCallingUid(), mContext.getPackageName(), /* featureId= */null,
+                /* message= */null)).isEqualTo(PermissionChecker.PERMISSION_GRANTED);
+    }
+
+    @Test
+    public void testCheckPermissionForDataDelivery_appOpPermission_modeDefaultAndPermissionNotGranted_returnsHardDenied() {
+        mUiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE);
+        mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION),
+                Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_DEFAULT);
+
+        assertThat(PermissionChecker.checkPermissionForDataDelivery(
+                mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(),
+                Binder.getCallingUid(), mContext.getPackageName(), /* featureId= */null,
+                /* message= */null)).isEqualTo(PermissionChecker.PERMISSION_HARD_DENIED);
+    }
+
+    @Test
+    public void testCheckPermissionForDataDelivery_appOpPermission_modeAllowed_returnsGranted() {
+        mUiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE);
+        mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION),
+                Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_ALLOWED);
+
+        assertThat(PermissionChecker.checkPermissionForDataDelivery(
+                mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(),
+                Binder.getCallingUid(), mContext.getPackageName(), /* featureId= */null,
+                /* message= */null)).isEqualTo(PermissionChecker.PERMISSION_GRANTED);
+    }
+
+    @Test
+    public void testCheckPermissionForDataDelivery_appOpPermission_packageNameIsNull_returnsGranted() {
+        mUiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE);
+        mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION),
+                Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_ALLOWED);
+
+        assertThat(PermissionChecker.checkPermissionForDataDelivery(
+                mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(),
+                Binder.getCallingUid(), /* packageName= */ null, /* featureId= */null,
+                /* message= */null)).isEqualTo(PermissionChecker.PERMISSION_GRANTED);
+    }
+
+    @Test
+    public void testCheckPermissionForDataDelivery_appOpPermission_modeIgnored_returnsHardDenied() {
+        mUiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE);
+        mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION),
+                Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_IGNORED);
+
+        assertThat(PermissionChecker.checkPermissionForDataDelivery(
+                mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(),
+                Binder.getCallingUid(), mContext.getPackageName(), /* featureId= */null,
+                /* message= */null)).isEqualTo(PermissionChecker.PERMISSION_HARD_DENIED);
+    }
+
+    @Test
+    public void testCheckPermissionForDataDelivery_appOpPermission_modeErrored_returnsHardDenied() {
+        mUiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE);
+        mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION),
+                Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_ERRORED);
+
+        assertThat(PermissionChecker.checkPermissionForDataDelivery(
+                mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(),
+                Binder.getCallingUid(), mContext.getPackageName(), /* featureId= */null,
+                /* message= */null)).isEqualTo(PermissionChecker.PERMISSION_HARD_DENIED);
+    }
+}
diff --git a/core/tests/coretests/src/android/content/pm/PackageParserTest.java b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
index 4402190..47c4286 100644
--- a/core/tests/coretests/src/android/content/pm/PackageParserTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.apex.ApexInfo;
 import android.content.Context;
@@ -469,7 +470,7 @@
         PackageParser.collectCertificates(p, false);
         PackageInfo pi = PackageParser.generatePackageInfo(p, apexInfo, flags);
 
-        assertEquals("com.google.android.tzdata", pi.applicationInfo.packageName);
+        assertEquals("com.google.android.tzdata2", pi.applicationInfo.packageName);
         assertTrue(pi.applicationInfo.enabled);
         assertEquals(28, pi.applicationInfo.targetSdkVersion);
         assertEquals(191000070, pi.applicationInfo.longVersionCode);
@@ -478,7 +479,7 @@
         assertEquals("Bundle[{com.android.vending.derived.apk.id=1}]",
                 pi.applicationInfo.metaData.toString());
 
-        assertEquals("com.google.android.tzdata", pi.packageName);
+        assertEquals("com.google.android.tzdata2", pi.packageName);
         assertEquals(191000070, pi.getLongVersionCode());
         assertNotNull(pi.signingInfo);
         assertTrue(pi.signingInfo.getApkContentsSigners().length > 0);
@@ -486,4 +487,34 @@
         assertTrue((pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0);
         assertTrue((pi.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0);
     }
+
+    @Test
+    public void testUsesSdk() throws Exception {
+        parsePackage("install_uses_sdk.apk_r0", R.raw.install_uses_sdk_r0, x -> x);
+        try {
+            parsePackage("install_uses_sdk.apk_r5", R.raw.install_uses_sdk_r5, x -> x);
+            fail("Expected parsing exception due to incompatible extension SDK version");
+        } catch (PackageParser.PackageParserException expected) {
+            assertEquals(PackageManager.INSTALL_FAILED_OLDER_SDK, expected.error);
+        }
+        try {
+            parsePackage("install_uses_sdk.apk_q0", R.raw.install_uses_sdk_q0, x -> x);
+            fail("Expected parsing exception due to non-existent extension SDK");
+        } catch (PackageParser.PackageParserException expected) {
+            assertEquals(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, expected.error);
+        }
+        try {
+            parsePackage("install_uses_sdk.apk_r", R.raw.install_uses_sdk_r, x -> x);
+            fail("Expected parsing exception due to unspecified extension SDK version");
+        } catch (PackageParser.PackageParserException expected) {
+            assertEquals(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, expected.error);
+        }
+        try {
+            parsePackage("install_uses_sdk.apk_0", R.raw.install_uses_sdk_0, x -> x);
+            fail("Expected parsing exception due to unspecified extension SDK");
+        } catch (PackageParser.PackageParserException expected) {
+            assertEquals(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, expected.error);
+        }
+
+    }
 }
diff --git a/core/tests/coretests/src/android/net/NetworkKeyTest.java b/core/tests/coretests/src/android/net/NetworkKeyTest.java
index c6c0b46..b13bcd1 100644
--- a/core/tests/coretests/src/android/net/NetworkKeyTest.java
+++ b/core/tests/coretests/src/android/net/NetworkKeyTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import android.net.wifi.ScanResult;
@@ -107,7 +108,7 @@
 
     @Test
     public void createFromScanResult_nullSsid() {
-        ScanResult scanResult = new ScanResult();
+        ScanResult scanResult = mock(ScanResult.class);
         scanResult.BSSID = VALID_BSSID;
 
         assertNull(NetworkKey.createFromScanResult(scanResult));
@@ -115,7 +116,7 @@
 
     @Test
     public void createFromScanResult_emptySsid() {
-        ScanResult scanResult = new ScanResult();
+        ScanResult scanResult = mock(ScanResult.class);
         scanResult.SSID = "";
         scanResult.BSSID = VALID_BSSID;
 
@@ -124,7 +125,7 @@
 
     @Test
     public void createFromScanResult_noneSsid() {
-        ScanResult scanResult = new ScanResult();
+        ScanResult scanResult = mock(ScanResult.class);
         scanResult.SSID = WifiManager.UNKNOWN_SSID;
         scanResult.BSSID = VALID_BSSID;
 
@@ -133,7 +134,7 @@
 
     @Test
     public void createFromScanResult_nullBssid() {
-        ScanResult scanResult = new ScanResult();
+        ScanResult scanResult = mock(ScanResult.class);
         scanResult.SSID = VALID_UNQUOTED_SSID;
 
         assertNull(NetworkKey.createFromScanResult(scanResult));
@@ -141,7 +142,7 @@
 
     @Test
     public void createFromScanResult_emptyBssid() {
-        ScanResult scanResult = new ScanResult();
+        ScanResult scanResult = mock(ScanResult.class);
         scanResult.SSID = VALID_UNQUOTED_SSID;
         scanResult.BSSID = "";
 
@@ -150,7 +151,7 @@
 
     @Test
     public void createFromScanResult_invalidBssid() {
-        ScanResult scanResult = new ScanResult();
+        ScanResult scanResult = mock(ScanResult.class);
         scanResult.SSID = VALID_UNQUOTED_SSID;
         scanResult.BSSID = INVALID_BSSID;
 
@@ -159,7 +160,7 @@
 
     @Test
     public void createFromScanResult_validSsid() {
-        ScanResult scanResult = new ScanResult();
+        ScanResult scanResult = mock(ScanResult.class);
         scanResult.SSID = VALID_UNQUOTED_SSID;
         scanResult.BSSID = VALID_BSSID;
 
diff --git a/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java b/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java
new file mode 100644
index 0000000..2cce43f
--- /dev/null
+++ b/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java
@@ -0,0 +1,162 @@
+/*
+ * 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.os;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.frameworks.coretests.bdr_helper_app.TestCommsReceiver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Tests functionality of {@link android.os.IBinder.DeathRecipient} callbacks.
+ */
+@RunWith(AndroidJUnit4.class)
+public class BinderDeathRecipientTest {
+    private static final String TAG = BinderDeathRecipientTest.class.getSimpleName();
+    private static final String TEST_PACKAGE_NAME_1 =
+            "com.android.frameworks.coretests.bdr_helper_app1";
+    private static final String TEST_PACKAGE_NAME_2 =
+            "com.android.frameworks.coretests.bdr_helper_app2";
+
+    private Context mContext;
+    private Handler mHandler;
+    private ActivityManager mActivityManager;
+    private Set<Pair<IBinder, IBinder.DeathRecipient>> mLinkedDeathRecipients = new ArraySet<>();
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mActivityManager = mContext.getSystemService(ActivityManager.class);
+        mHandler = new Handler(Looper.getMainLooper());
+    }
+
+    private IBinder getNewRemoteBinder(String testPackage) throws InterruptedException {
+        final CountDownLatch resultLatch = new CountDownLatch(1);
+        final AtomicInteger resultCode = new AtomicInteger(Activity.RESULT_CANCELED);
+        final AtomicReference<Bundle> resultExtras = new AtomicReference<>();
+
+        final Intent intent = new Intent(TestCommsReceiver.ACTION_GET_BINDER)
+                .setClassName(testPackage, TestCommsReceiver.class.getName());
+        mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                resultCode.set(getResultCode());
+                resultExtras.set(getResultExtras(true));
+                resultLatch.countDown();
+            }
+        }, mHandler, Activity.RESULT_CANCELED, null, null);
+
+        assertTrue("Request for binder timed out", resultLatch.await(5, TimeUnit.SECONDS));
+        assertEquals(Activity.RESULT_OK, resultCode.get());
+        return resultExtras.get().getBinder(TestCommsReceiver.EXTRA_KEY_BINDER);
+    }
+
+    @Test
+    public void binderDied_noArgs() throws Exception {
+        final IBinder testAppBinder = getNewRemoteBinder(TEST_PACKAGE_NAME_1);
+        final CountDownLatch deathNotificationLatch = new CountDownLatch(1);
+        final IBinder.DeathRecipient simpleDeathRecipient =
+                () -> deathNotificationLatch.countDown();
+        testAppBinder.linkToDeath(simpleDeathRecipient, 0);
+        mLinkedDeathRecipients.add(Pair.create(testAppBinder, simpleDeathRecipient));
+
+        mActivityManager.forceStopPackage(TEST_PACKAGE_NAME_1);
+        assertTrue("Death notification did not arrive",
+                deathNotificationLatch.await(10, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void binderDied_iBinderArg() throws Exception {
+        final IBinder testApp1Binder = getNewRemoteBinder(TEST_PACKAGE_NAME_1);
+        final IBinder testApp2Binder = getNewRemoteBinder(TEST_PACKAGE_NAME_2);
+        final CyclicBarrier barrier = new CyclicBarrier(2);
+
+        final AtomicReference<IBinder> binderThatDied = new AtomicReference<>();
+        final IBinder.DeathRecipient sameDeathRecipient = new IBinder.DeathRecipient() {
+            @Override
+            public void binderDied() {
+                Log.e(TAG, "Should not have been called!");
+            }
+
+            @Override
+            public void binderDied(IBinder who) {
+                binderThatDied.set(who);
+                try {
+                    barrier.await();
+                } catch (InterruptedException | BrokenBarrierException e) {
+                    Log.e(TAG, "Unexpected exception while waiting on CyclicBarrier", e);
+                }
+            }
+        };
+        testApp1Binder.linkToDeath(sameDeathRecipient, 0);
+        mLinkedDeathRecipients.add(Pair.create(testApp1Binder, sameDeathRecipient));
+
+        testApp2Binder.linkToDeath(sameDeathRecipient, 0);
+        mLinkedDeathRecipients.add(Pair.create(testApp2Binder, sameDeathRecipient));
+
+        mActivityManager.forceStopPackage(TEST_PACKAGE_NAME_1);
+        try {
+            barrier.await(10, TimeUnit.SECONDS);
+        } catch (TimeoutException e) {
+            fail("Timed out while waiting for 1st death notification: " + e.getMessage());
+        }
+        assertEquals("Different binder received", testApp1Binder, binderThatDied.get());
+
+        barrier.reset();
+        mActivityManager.forceStopPackage(TEST_PACKAGE_NAME_2);
+        try {
+            barrier.await(10, TimeUnit.SECONDS);
+        } catch (TimeoutException e) {
+            fail("Timed out while waiting for 2nd death notification: " + e.getMessage());
+        }
+        assertEquals("Different binder received", testApp2Binder, binderThatDied.get());
+    }
+
+    @After
+    public void tearDown() {
+        for (Pair<IBinder, IBinder.DeathRecipient> linkedPair : mLinkedDeathRecipients) {
+            linkedPair.first.unlinkToDeath(linkedPair.second, 0);
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/view/DisplayCutoutTest.java b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
index d5a0dfa..7c2b98f 100644
--- a/core/tests/coretests/src/android/view/DisplayCutoutTest.java
+++ b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
@@ -69,6 +69,8 @@
             null /* boundBottom */);
 
     final DisplayCutout mCutoutTop = createCutoutTop();
+    final DisplayCutout mCutoutWithWaterfall = createCutoutWithWaterfall();
+    final DisplayCutout mWaterfallOnly = createCutoutWaterfallOnly();
 
     @Test
     public void testExtractBoundsFromList_left() {
@@ -126,9 +128,23 @@
     }
 
     @Test
-    public void hasCutout() throws Exception {
-        assertTrue(NO_CUTOUT.isEmpty());
-        assertFalse(mCutoutTop.isEmpty());
+    public void testHasCutout_noCutout() throws Exception {
+        assertTrue(NO_CUTOUT.isBoundsEmpty());
+    }
+
+    @Test
+    public void testHasCutout_cutoutOnly() {
+        assertFalse(mCutoutTop.isBoundsEmpty());
+    }
+
+    @Test
+    public void testHasCutout_cutoutWithWaterfall() {
+        assertFalse(mCutoutWithWaterfall.isBoundsEmpty());
+    }
+
+    @Test
+    public void testHasCutout_waterfallOnly() {
+        assertTrue(mWaterfallOnly.isBoundsEmpty());
     }
 
     @Test
@@ -142,20 +158,27 @@
     }
 
     @Test
+    public void testGetWaterfallInsets() throws Exception {
+        DisplayCutout cutout =
+                createCutoutWaterfallOnly(Insets.of(5, 6, 7, 8));
+        assertEquals(Insets.of(5, 6, 7, 8), cutout.getWaterfallInsets());
+    }
+
+    @Test
     public void testHashCode() throws Exception {
-        assertEquals(mCutoutTop.hashCode(), createCutoutTop().hashCode());
-        assertNotEquals(mCutoutTop.hashCode(), mCutoutNumbers.hashCode());
+        assertEquals(mCutoutWithWaterfall.hashCode(), createCutoutWithWaterfall().hashCode());
+        assertNotEquals(mCutoutWithWaterfall.hashCode(), mCutoutNumbers.hashCode());
     }
 
     @Test
     public void testEquals() throws Exception {
-        assertEquals(mCutoutTop, createCutoutTop());
-        assertNotEquals(mCutoutTop, mCutoutNumbers);
+        assertEquals(mCutoutWithWaterfall, createCutoutWithWaterfall());
+        assertNotEquals(mCutoutWithWaterfall, mCutoutNumbers);
     }
 
     @Test
     public void testToString() throws Exception {
-        assertFalse(mCutoutTop.toString().isEmpty());
+        assertFalse(mCutoutWithWaterfall.toString().isEmpty());
         assertFalse(mCutoutNumbers.toString().isEmpty());
     }
 
@@ -240,12 +263,12 @@
     public void parcel_unparcel_regular() {
         Parcel p = Parcel.obtain();
 
-        new ParcelableWrapper(mCutoutTop).writeToParcel(p, 0);
+        new ParcelableWrapper(mCutoutWithWaterfall).writeToParcel(p, 0);
         int posAfterWrite = p.dataPosition();
 
         p.setDataPosition(0);
 
-        assertEquals(mCutoutTop, ParcelableWrapper.CREATOR.createFromParcel(p).get());
+        assertEquals(mCutoutWithWaterfall, ParcelableWrapper.CREATOR.createFromParcel(p).get());
         assertEquals(posAfterWrite, p.dataPosition());
     }
 
@@ -264,44 +287,64 @@
 
     @Test
     public void fromSpec_caches() {
-        DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f);
-        assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f), sameInstance(cached));
+        Insets waterfallInsets = Insets.of(0, 20, 0, 20);
+        DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f, waterfallInsets);
+        assertThat(
+                fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f, waterfallInsets),
+                sameInstance(cached));
     }
 
     @Test
     public void fromSpec_wontCacheIfSpecChanges() {
-        DisplayCutout cached = fromSpec("L1,0 L1000,1000 L0,1 z", 200, 400, 1f);
-        assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f), not(sameInstance(cached)));
+        DisplayCutout cached = fromSpec("L1,0 L1000,1000 L0,1 z", 200, 400, 1f, Insets.NONE);
+        assertThat(
+                fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f, Insets.NONE),
+                not(sameInstance(cached)));
     }
 
     @Test
     public void fromSpec_wontCacheIfScreenWidthChanges() {
-        DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 2000, 400, 1f);
-        assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f), not(sameInstance(cached)));
+        DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 2000, 400, 1f, Insets.NONE);
+        assertThat(
+                fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f, Insets.NONE),
+                not(sameInstance(cached)));
     }
 
     @Test
     public void fromSpec_wontCacheIfScreenHeightChanges() {
-        DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 200, 4000, 1f);
-        assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f), not(sameInstance(cached)));
+        DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 200, 4000, 1f, Insets.NONE);
+        assertThat(
+                fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f, Insets.NONE),
+                not(sameInstance(cached)));
     }
 
     @Test
     public void fromSpec_wontCacheIfDensityChanges() {
-        DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 2f);
-        assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f), not(sameInstance(cached)));
+        DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 2f, Insets.NONE);
+        assertThat(
+                fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f, Insets.NONE),
+                not(sameInstance(cached)));
+    }
+
+    @Test
+    public void fromSpec_wontCacheIfWaterfallInsetsChange() {
+        Insets waterfallInsets = Insets.of(0, 20, 0, 20);
+        DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 2f, Insets.NONE);
+        assertThat(
+                fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 2f, waterfallInsets),
+                not(sameInstance(cached)));
     }
 
     @Test
     public void fromSpec_setsSafeInsets_top() {
-        DisplayCutout cutout = fromSpec("M -50,0 v 20 h 100 v -20 z", 200, 400, 2f);
+        DisplayCutout cutout = fromSpec("M -50,0 v 20 h 100 v -20 z", 200, 400, 2f, Insets.NONE);
         assertThat(cutout.getSafeInsets(), equalTo(new Rect(0, 20, 0, 0)));
     }
 
     @Test
     public void fromSpec_setsSafeInsets_top_and_bottom() {
         DisplayCutout cutout = fromSpec("M -50,0 v 20 h 100 v -20 z"
-                + "@bottom M -50,0 v -10,0 h 100 v 20 z", 200, 400, 2f);
+                + "@bottom M -50,0 v -10,0 h 100 v 20 z", 200, 400, 2f, Insets.NONE);
         assertThat(cutout.getSafeInsets(), equalTo(new Rect(0, 20, 0, 10)));
         assertThat(cutout.getBoundingRectsAll(), equalTo(new Rect[]{
                 ZERO_RECT, new Rect(50, 0, 150, 20),
@@ -310,6 +353,38 @@
     }
 
     @Test
+    public void fromSpec_setsSafeInsets_waterfallTopBottom() {
+        DisplayCutout cutout = fromSpec("", 200, 400, 2f, Insets.of(0, 30, 0, 30));
+        assertThat(cutout.getSafeInsets(), equalTo(new Rect(0, 30, 0, 30)));
+    }
+
+    @Test
+    public void fromSpec_setsSafeInsets_waterfallLeftRight() {
+        DisplayCutout cutout = fromSpec("", 200, 400, 2f, Insets.of(30, 0, 30, 0));
+        assertThat(cutout.getSafeInsets(), equalTo(new Rect(30, 0, 30, 0)));
+    }
+
+    @Test
+    public void fromSpec_setsSafeInsets_waterfall_allEdges() {
+        DisplayCutout cutout = fromSpec("", 200, 400, 2f, Insets.of(30, 30, 30, 30));
+        assertThat(cutout.getSafeInsets(), equalTo(new Rect(30, 30, 30, 30)));
+    }
+
+    @Test
+    public void fromSpec_setsSafeInsets_cutoutTopBottom_waterfallTopBottom() {
+        DisplayCutout cutout = fromSpec("M -50,0 v 20 h 100 v -20 z"
+                + "@bottom M -50,0 v -20,0 h 100 v 20 z", 200, 400, 2f, Insets.of(0, 30, 0, 30));
+        assertThat(cutout.getSafeInsets(), equalTo(new Rect(0, 30, 0, 30)));
+    }
+
+    @Test
+    public void fromSpec_setsSafeInsets_cutoutTopBottom_waterfallLeftRight() {
+        DisplayCutout cutout = fromSpec("M -50,0 v 20 h 100 v -20 z"
+                + "@bottom M -50,0 v -20,0 h 100 v 20 z", 200, 400, 2f, Insets.of(30, 0, 30, 0));
+        assertThat(cutout.getSafeInsets(), equalTo(new Rect(30, 20, 30, 20)));
+    }
+
+    @Test
     public void parcel_unparcel_nocutout() {
         Parcel p = Parcel.obtain();
 
@@ -326,7 +401,7 @@
     public void parcel_unparcel_inplace() {
         Parcel p = Parcel.obtain();
 
-        new ParcelableWrapper(mCutoutTop).writeToParcel(p, 0);
+        new ParcelableWrapper(mCutoutWithWaterfall).writeToParcel(p, 0);
         int posAfterWrite = p.dataPosition();
 
         p.setDataPosition(0);
@@ -334,22 +409,24 @@
         ParcelableWrapper wrapper = new ParcelableWrapper();
         wrapper.readFromParcel(p);
 
-        assertEquals(mCutoutTop, wrapper.get());
+        assertEquals(mCutoutWithWaterfall, wrapper.get());
         assertEquals(posAfterWrite, p.dataPosition());
     }
 
     @Test
     public void wrapper_hashcode() throws Exception {
-        assertEquals(new ParcelableWrapper(mCutoutTop).hashCode(),
-                new ParcelableWrapper(createCutoutTop()).hashCode());
-        assertNotEquals(new ParcelableWrapper(mCutoutTop).hashCode(),
+        assertEquals(new ParcelableWrapper(mCutoutWithWaterfall).hashCode(),
+                new ParcelableWrapper(createCutoutWithWaterfall()).hashCode());
+        assertNotEquals(new ParcelableWrapper(mCutoutWithWaterfall).hashCode(),
                 new ParcelableWrapper(mCutoutNumbers).hashCode());
     }
 
     @Test
     public void wrapper_equals() throws Exception {
-        assertEquals(new ParcelableWrapper(mCutoutTop), new ParcelableWrapper(createCutoutTop()));
-        assertNotEquals(new ParcelableWrapper(mCutoutTop), new ParcelableWrapper(mCutoutNumbers));
+        assertEquals(new ParcelableWrapper(mCutoutWithWaterfall),
+                new ParcelableWrapper(createCutoutWithWaterfall()));
+        assertNotEquals(new ParcelableWrapper(mCutoutWithWaterfall),
+                new ParcelableWrapper(mCutoutNumbers));
     }
 
     private static DisplayCutout createCutoutTop() {
@@ -363,4 +440,28 @@
                 safeInset, null /* boundLeft */, boundTop, null /* boundRight */,
                 null /* boundBottom */);
     }
+
+    private static DisplayCutout createCutoutWithWaterfall() {
+        return new DisplayCutout(
+                Insets.of(20, 100, 20, 0),
+                ZERO_RECT,
+                new Rect(50, 0, 75, 100),
+                ZERO_RECT,
+                ZERO_RECT,
+                Insets.of(20, 0, 20, 0));
+    }
+
+    private static DisplayCutout createCutoutWaterfallOnly() {
+        return createCutoutWaterfallOnly(Insets.of(20, 0, 20, 0));
+    }
+
+    private static DisplayCutout createCutoutWaterfallOnly(Insets waterfallInsets) {
+        return new DisplayCutout(
+                Insets.of(20, 0, 20, 0),
+                ZERO_RECT,
+                ZERO_RECT,
+                ZERO_RECT,
+                ZERO_RECT,
+                waterfallInsets);
+    }
 }
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index 0e19ca8..7c78bce 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -74,7 +74,7 @@
                     false,
                     new DisplayCutout(
                             Insets.of(10, 10, 10, 10), rect, rect, rect, rect),
-                    rect, rect, SOFT_INPUT_ADJUST_RESIZE);
+                    rect, rect, SOFT_INPUT_ADJUST_RESIZE, 0);
             mImeConsumer = new ImeInsetsSourceConsumer(
                     new InsetsState(), Transaction::new, mController);
         });
@@ -90,12 +90,12 @@
             mImeConsumer.onWindowFocusGained();
             mImeConsumer.applyImeVisibility(true);
             mController.cancelExistingAnimation();
-            assertTrue(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             // test if setVisibility can hide IME
             mImeConsumer.applyImeVisibility(false);
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
         });
     }
 
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index 179929f..bcf0b8c 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
@@ -24,7 +25,10 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -37,6 +41,7 @@
 import android.util.SparseArray;
 import android.view.SurfaceControl.Transaction;
 import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
+import android.view.animation.LinearInterpolator;
 import android.view.test.InsetsModeSession;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -90,6 +95,8 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+        doNothing().when(mMockController).updateCompatSysUiVisibility(
+                anyInt(), anyBoolean(), anyBoolean());
         mTopLeash = new SurfaceControl.Builder(mSession)
                 .setName("testSurface")
                 .build();
@@ -115,8 +122,8 @@
         controls.put(ITYPE_NAVIGATION_BAR, navConsumer.getControl());
         mController = new InsetsAnimationControlImpl(controls,
                 new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(),
-                mMockController, 10 /* durationMs */,
-                false /* fade */);
+                mMockController, 10 /* durationMs */, new LinearInterpolator(),
+                false /* fade */, LAYOUT_INSETS_DURING_ANIMATION_SHOWN);
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index a89fc1e..f720c98 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -16,9 +16,13 @@
 
 package android.view;
 
+import static android.view.InsetsController.ANIMATION_TYPE_HIDE;
+import static android.view.InsetsController.ANIMATION_TYPE_NONE;
+import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
 import static android.view.WindowInsets.Type.statusBars;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 
@@ -40,12 +44,16 @@
 import android.view.WindowInsets.Type;
 import android.view.WindowManager.BadTokenException;
 import android.view.WindowManager.LayoutParams;
+import android.view.animation.LinearInterpolator;
+import android.view.test.InsetsModeSession;
 import android.widget.TextView;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.AfterClass;
 import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -68,6 +76,18 @@
     private InsetsController mController;
     private SurfaceSession mSession = new SurfaceSession();
     private SurfaceControl mLeash;
+    private ViewRootImpl mViewRoot;
+    private static InsetsModeSession sInsetsModeSession;
+
+    @BeforeClass
+    public static void setupOnce() {
+        sInsetsModeSession = new InsetsModeSession(NEW_INSETS_MODE_FULL);
+    }
+
+    @AfterClass
+    public static void tearDownOnce() {
+        sInsetsModeSession.close();
+    }
 
     @Before
     public void setup() {
@@ -77,22 +97,26 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             Context context = InstrumentationRegistry.getTargetContext();
             // cannot mock ViewRootImpl since it's final.
-            ViewRootImpl viewRootImpl = new ViewRootImpl(context, context.getDisplay());
+            mViewRoot = new ViewRootImpl(context, context.getDisplay());
             try {
-                viewRootImpl.setView(new TextView(context), new LayoutParams(), null);
+                mViewRoot.setView(new TextView(context), new LayoutParams(), null);
             } catch (BadTokenException e) {
                 // activity isn't running, we will ignore BadTokenException.
             }
-            mController = new InsetsController(viewRootImpl);
+            mController = new InsetsController(mViewRoot);
             final Rect rect = new Rect(5, 5, 5, 5);
+            mController.getState().getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 10));
+            mController.getState().getSource(ITYPE_NAVIGATION_BAR).setFrame(
+                    new Rect(0, 90, 100, 100));
+            mController.getState().getSource(ITYPE_IME).setFrame(new Rect(0, 50, 100, 100));
+            mController.getState().setDisplayFrame(new Rect(0, 0, 100, 100));
             mController.calculateInsets(
                     false,
                     false,
                     new DisplayCutout(
                             Insets.of(10, 10, 10, 10), rect, rect, rect, rect),
-                    rect, rect, SOFT_INPUT_ADJUST_RESIZE);
+                    rect, rect, SOFT_INPUT_ADJUST_RESIZE, 0);
             mController.onFrameChanged(new Rect(0, 0, 100, 100));
-            mController.getState().setDisplayFrame(new Rect(0, 0, 100, 100));
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -117,16 +141,22 @@
 
     @Test
     public void testControlsRevoked_duringAnim() {
-        InsetsSourceControl control =
-                new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point());
-        mController.onControlsChanged(new InsetsSourceControl[] { control });
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            InsetsSourceControl control =
+                    new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point());
+            mController.onControlsChanged(new InsetsSourceControl[] { control });
 
-        WindowInsetsAnimationControlListener mockListener =
-                mock(WindowInsetsAnimationControlListener.class);
-        mController.controlWindowInsetsAnimation(statusBars(), 10 /* durationMs */, mockListener);
-        verify(mockListener).onReady(any(), anyInt());
-        mController.onControlsChanged(new InsetsSourceControl[0]);
-        verify(mockListener).onCancelled();
+            WindowInsetsAnimationControlListener mockListener =
+                    mock(WindowInsetsAnimationControlListener.class);
+            mController.controlWindowInsetsAnimation(statusBars(), 10 /* durationMs */,
+                    new LinearInterpolator(), mockListener);
+
+            // Ready gets deferred until next predraw
+            mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
+            verify(mockListener).onReady(any(), anyInt());
+            mController.onControlsChanged(new InsetsSourceControl[0]);
+            verify(mockListener).onCancelled();
+        });
     }
 
     @Test
@@ -135,7 +165,8 @@
         mController.getState().setDisplayFrame(new Rect(0, 0, 200, 200));
         WindowInsetsAnimationControlListener controlListener =
                 mock(WindowInsetsAnimationControlListener.class);
-        mController.controlWindowInsetsAnimation(0, 0 /* durationMs */, controlListener);
+        mController.controlWindowInsetsAnimation(0, 0 /* durationMs */, new LinearInterpolator(),
+                controlListener);
         verify(controlListener).onCancelled();
         verify(controlListener, never()).onReady(any(), anyInt());
     }
@@ -154,16 +185,16 @@
             mController.show(Type.all());
             // quickly jump to final state by cancelling it.
             mController.cancelExistingAnimation();
-            assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertTrue(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             mController.applyImeVisibility(false /* setVisible */);
             mController.hide(Type.all());
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
             mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost();
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -180,10 +211,10 @@
             mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained();
             mController.applyImeVisibility(true);
             mController.cancelExistingAnimation();
-            assertTrue(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
             mController.applyImeVisibility(false);
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
             mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost();
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -198,19 +229,25 @@
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             int types = Type.navigationBars() | Type.systemBars();
-            // test show select types.
-            mController.show(types);
+            // test hide select types.
+            mController.hide(types);
+            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
+            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
             mController.cancelExistingAnimation();
-            assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
+            assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_STATUS_BAR));
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             // test hide all
-            mController.hide(types);
+            mController.show(types);
+            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
+            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_STATUS_BAR));
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -227,29 +264,29 @@
             // test show select types.
             mController.show(types);
             mController.cancelExistingAnimation();
-            assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             // test hide all
             mController.hide(Type.all());
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             // test single show
             mController.show(Type.navigationBars());
             mController.cancelExistingAnimation();
-            assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             // test single hide
             mController.hide(Type.navigationBars());
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -264,34 +301,42 @@
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             // start two animations and see if previous is cancelled and final state is reached.
-            mController.show(Type.navigationBars());
-            mController.show(Type.systemBars());
-            mController.cancelExistingAnimation();
-            assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
-
             mController.hide(Type.navigationBars());
             mController.hide(Type.systemBars());
+            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
+            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+
+            mController.show(Type.navigationBars());
+            mController.show(Type.systemBars());
+            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
+            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_STATUS_BAR));
+            mController.cancelExistingAnimation();
+            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             int types = Type.navigationBars() | Type.systemBars();
             // show two at a time and hide one by one.
             mController.show(types);
             mController.hide(Type.navigationBars());
+            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
+            assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_STATUS_BAR));
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             mController.hide(Type.systemBars());
+            assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
+            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -309,15 +354,15 @@
             mController.show(types);
             mController.hide(Type.navigationBars());
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             mController.hide(Type.systemBars());
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -332,16 +377,20 @@
             WindowInsetsAnimationControlListener mockListener =
                     mock(WindowInsetsAnimationControlListener.class);
             mController.controlWindowInsetsAnimation(statusBars(), 0 /* durationMs */,
-                    mockListener);
+                    new LinearInterpolator(), mockListener);
 
             ArgumentCaptor<WindowInsetsAnimationController> controllerCaptor =
                     ArgumentCaptor.forClass(WindowInsetsAnimationController.class);
+
+            // Ready gets deferred until next predraw
+            mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
+
             verify(mockListener).onReady(controllerCaptor.capture(), anyInt());
             controllerCaptor.getValue().finish(false /* shown */);
         });
         waitUntilNextFrame();
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isVisible());
+            assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible());
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index 7af833b..492c036 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -96,7 +96,7 @@
     @Test
     public void testHide() {
         mConsumer.hide();
-        assertFalse("Consumer should not be visible", mConsumer.isVisible());
+        assertFalse("Consumer should not be visible", mConsumer.isRequestedVisible());
         verify(mSpyInsetsSource).setVisible(eq(false));
     }
 
@@ -106,7 +106,7 @@
         // Insets source starts out visible
         mConsumer.hide();
         mConsumer.show();
-        assertTrue("Consumer should be visible", mConsumer.isVisible());
+        assertTrue("Consumer should be visible", mConsumer.isRequestedVisible());
         verify(mSpyInsetsSource).setVisible(eq(false));
         verify(mSpyInsetsSource).setVisible(eq(true));
     }
diff --git a/core/tests/coretests/src/android/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java
index d7f50ba..e3b08bb 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java
@@ -108,5 +108,30 @@
         assertEquals(Insets.of(0, 100, 0, 0), insets);
     }
 
+    @Test
+    public void testCalculateVisibleInsets_default() {
+        mSource.setFrame(new Rect(0, 0, 500, 100));
+        Insets insets = mSource.calculateVisibleInsets(new Rect(100, 0, 500, 500));
+        assertEquals(Insets.of(0, 100, 0, 0), insets);
+    }
+
+    @Test
+    public void testCalculateVisibleInsets_override() {
+        mSource.setFrame(new Rect(0, 0, 500, 100));
+        mSource.setVisibleFrame(new Rect(0, 0, 500, 200));
+        Insets insets = mSource.calculateVisibleInsets(new Rect(100, 0, 500, 500));
+        assertEquals(Insets.of(0, 200, 0, 0), insets);
+    }
+
+    @Test
+    public void testCalculateVisibleInsets_invisible() {
+        mSource.setFrame(new Rect(0, 0, 500, 100));
+        mSource.setVisibleFrame(new Rect(0, 0, 500, 200));
+        mSource.setVisible(false);
+        Insets insets = mSource.calculateVisibleInsets(new Rect(100, 0, 500, 500));
+        assertEquals(Insets.of(0, 0, 0, 0), insets);
+    }
+
+
     // Parcel and equals already tested via InsetsStateTest
 }
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index fa2ffcca..20be8c2 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -18,12 +18,15 @@
 
 import static android.view.InsetsState.ISIDE_BOTTOM;
 import static android.view.InsetsState.ISIDE_TOP;
+import static android.view.InsetsState.ITYPE_BOTTOM_GESTURES;
 import static android.view.InsetsState.ITYPE_CAPTION_BAR;
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 
 import static org.junit.Assert.assertEquals;
@@ -70,7 +73,7 @@
             mState.getSource(ITYPE_IME).setVisible(true);
             SparseIntArray typeSideMap = new SparseIntArray();
             WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false,
-                    DisplayCutout.NO_CUTOUT, null, null, SOFT_INPUT_ADJUST_RESIZE, typeSideMap);
+                    DisplayCutout.NO_CUTOUT, null, null, SOFT_INPUT_ADJUST_RESIZE, 0, typeSideMap);
             assertEquals(Insets.of(0, 100, 0, 100), insets.getSystemWindowInsets());
             assertEquals(Insets.of(0, 100, 0, 100), insets.getInsets(Type.all()));
             assertEquals(ISIDE_TOP, typeSideMap.get(ITYPE_STATUS_BAR));
@@ -89,9 +92,9 @@
             mState.getSource(ITYPE_IME).setFrame(new Rect(0, 100, 100, 300));
             mState.getSource(ITYPE_IME).setVisible(true);
             WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false,
-                    DisplayCutout.NO_CUTOUT, null, null, SOFT_INPUT_ADJUST_RESIZE, null);
+                    DisplayCutout.NO_CUTOUT, null, null, SOFT_INPUT_ADJUST_RESIZE, 0, null);
             assertEquals(100, insets.getStableInsetBottom());
-            assertEquals(Insets.of(0, 0, 0, 100), insets.getMaxInsets(Type.systemBars()));
+            assertEquals(Insets.of(0, 0, 0, 100), insets.getInsetsIgnoringVisibility(Type.systemBars()));
             assertEquals(Insets.of(0, 0, 0, 200), insets.getSystemWindowInsets());
             assertEquals(Insets.of(0, 0, 0, 200), insets.getInsets(Type.all()));
             assertEquals(Insets.of(0, 0, 0, 100), insets.getInsets(Type.navigationBars()));
@@ -108,7 +111,7 @@
             mState.getSource(ITYPE_NAVIGATION_BAR).setFrame(new Rect(80, 0, 100, 300));
             mState.getSource(ITYPE_NAVIGATION_BAR).setVisible(true);
             WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false,
-                    DisplayCutout.NO_CUTOUT, null, null, 0, null);
+                    DisplayCutout.NO_CUTOUT, null, null, 0, 0, null);
             assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets());
             assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(Type.statusBars()));
             assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(Type.navigationBars()));
@@ -124,7 +127,7 @@
             mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
             mState.getSource(ITYPE_IME).setVisible(true);
             WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false,
-                    DisplayCutout.NO_CUTOUT, null, null, SOFT_INPUT_ADJUST_NOTHING, null);
+                    DisplayCutout.NO_CUTOUT, null, null, SOFT_INPUT_ADJUST_NOTHING, 0, null);
             assertEquals(0, insets.getSystemWindowInsetBottom());
             assertEquals(100, insets.getInsets(ime()).bottom);
             assertTrue(insets.isVisible(ime()));
@@ -132,6 +135,25 @@
     }
 
     @Test
+    public void testCalculateInsets_systemUiFlagLayoutStable() {
+        try (final InsetsModeSession session =
+                     new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
+            mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
+            mState.getSource(ITYPE_STATUS_BAR).setVisible(false);
+            mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
+            mState.getSource(ITYPE_IME).setVisible(true);
+            WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false,
+                    DisplayCutout.NO_CUTOUT, null, null, SOFT_INPUT_ADJUST_NOTHING,
+                    SYSTEM_UI_FLAG_LAYOUT_STABLE, null);
+            assertEquals(100, insets.getSystemWindowInsetTop());
+            insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false,
+                    DisplayCutout.NO_CUTOUT, null, null, SOFT_INPUT_ADJUST_NOTHING,
+                    0 /* legacySystemUiFlags */, null);
+            assertEquals(0, insets.getSystemWindowInsetTop());
+        }
+    }
+
+    @Test
     public void testStripForDispatch() {
         mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
         mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
@@ -139,7 +161,7 @@
         mState.getSource(ITYPE_IME).setVisible(true);
         mState.removeSource(ITYPE_IME);
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false,
-                DisplayCutout.NO_CUTOUT, null, null, SOFT_INPUT_ADJUST_RESIZE, null);
+                DisplayCutout.NO_CUTOUT, null, null, SOFT_INPUT_ADJUST_RESIZE, 0, null);
         assertEquals(0, insets.getSystemWindowInsetBottom());
     }
 
@@ -209,6 +231,42 @@
         assertFalse(InsetsState.getDefaultVisibility(ITYPE_IME));
     }
 
+    @Test
+    public void testCalculateVisibleInsets() throws Exception {
+        try (final InsetsModeSession session =
+                     new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
+            mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
+            mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
+            mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
+            mState.getSource(ITYPE_IME).setVisible(true);
+
+            // Make sure bottom gestures are ignored
+            mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300));
+            mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true);
+            Rect visibleInsets = mState.calculateVisibleInsets(
+                    new Rect(0, 0, 100, 300), new Rect(), SOFT_INPUT_ADJUST_PAN);
+            assertEquals(new Rect(0, 100, 0, 100), visibleInsets);
+        }
+    }
+
+    @Test
+    public void testCalculateVisibleInsets_adjustNothing() throws Exception {
+        try (final InsetsModeSession session =
+                     new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
+            mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
+            mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
+            mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
+            mState.getSource(ITYPE_IME).setVisible(true);
+
+            // Make sure bottom gestures are ignored
+            mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300));
+            mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true);
+            Rect visibleInsets = mState.calculateVisibleInsets(
+                    new Rect(0, 0, 100, 300), new Rect(), SOFT_INPUT_ADJUST_NOTHING);
+            assertEquals(new Rect(0, 100, 0, 0), visibleInsets);
+        }
+    }
+
     private void assertEqualsAndHashCode() {
         assertEquals(mState, mState2);
         assertEquals(mState.hashCode(), mState2.hashCode());
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index cf5d079..df6ed8c 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -109,7 +109,7 @@
         attrs.systemUiVisibility = SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
         ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
 
-        assertEquals(0, attrs.getFitWindowInsetsTypes() & Type.statusBars());
+        assertEquals(0, attrs.getFitInsetsTypes() & Type.statusBars());
     }
 
     @Test
@@ -120,7 +120,7 @@
         attrs.flags = FLAG_LAYOUT_IN_SCREEN;
         ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
 
-        assertEquals(0, attrs.getFitWindowInsetsTypes() & Type.statusBars());
+        assertEquals(0, attrs.getFitInsetsTypes() & Type.statusBars());
     }
 
     @Test
@@ -131,7 +131,7 @@
         attrs.systemUiVisibility = SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
         ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
 
-        assertEquals(0, attrs.getFitWindowInsetsTypes() & Type.systemBars());
+        assertEquals(0, attrs.getFitInsetsTypes() & Type.systemBars());
     }
 
     @Test
@@ -141,8 +141,8 @@
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_TOAST);
         ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
 
-        assertEquals(Type.systemBars(), attrs.getFitWindowInsetsTypes() & Type.systemBars());
-        assertEquals(true, attrs.getFitIgnoreVisibility());
+        assertEquals(Type.systemBars(), attrs.getFitInsetsTypes() & Type.systemBars());
+        assertEquals(true, attrs.isFitInsetsIgnoringVisibility());
     }
 
     @Test
@@ -152,8 +152,8 @@
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_SYSTEM_ALERT);
         ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
 
-        assertEquals(Type.systemBars(), attrs.getFitWindowInsetsTypes() & Type.systemBars());
-        assertEquals(true, attrs.getFitIgnoreVisibility());
+        assertEquals(Type.systemBars(), attrs.getFitInsetsTypes() & Type.systemBars());
+        assertEquals(true, attrs.isFitInsetsIgnoringVisibility());
     }
 
     @Test
@@ -165,14 +165,14 @@
         final int sides = Side.TOP | Side.LEFT;
         final boolean fitMaxInsets = true;
         attrs.flags = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
-        attrs.setFitWindowInsetsTypes(types);
-        attrs.setFitWindowInsetsSides(sides);
-        attrs.setFitIgnoreVisibility(fitMaxInsets);
+        attrs.setFitInsetsTypes(types);
+        attrs.setFitInsetsSides(sides);
+        attrs.setFitInsetsIgnoringVisibility(fitMaxInsets);
         ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
 
-        assertEquals(types, attrs.getFitWindowInsetsTypes());
-        assertEquals(sides, attrs.getFitWindowInsetsSides());
-        assertEquals(fitMaxInsets, attrs.getFitIgnoreVisibility());
+        assertEquals(types, attrs.getFitInsetsTypes());
+        assertEquals(sides, attrs.getFitInsetsSides());
+        assertEquals(fitMaxInsets, attrs.isFitInsetsIgnoringVisibility());
     }
 
     private static class ViewRootImplAccessor {
diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java
index e5a4f6d..fce2ebd 100644
--- a/core/tests/coretests/src/android/view/WindowInsetsTest.java
+++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java
@@ -16,10 +16,12 @@
 
 package android.view;
 
+import static android.view.WindowInsets.Type.SIZE;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.navigationBars;
 import static android.view.WindowInsets.Type.statusBars;
 
+import static android.view.WindowInsets.Type.systemBars;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -28,6 +30,7 @@
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.view.WindowInsets.Builder;
+import android.view.WindowInsets.Type;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -56,6 +59,18 @@
         assertTrue(new WindowInsets((Rect) null).isConsumed());
     }
 
+    @Test
+    public void compatInsets_layoutStable() {
+        Insets[] insets = new Insets[SIZE];
+        Insets[] maxInsets = new Insets[SIZE];
+        boolean[] visible = new boolean[SIZE];
+        WindowInsets.assignCompatInsets(maxInsets, new Rect(0, 10, 0, 0));
+        WindowInsets.assignCompatInsets(insets, new Rect(0, 0, 0, 0));
+        WindowInsets windowInsets = new WindowInsets(insets, maxInsets, visible, false, false, null,
+                systemBars(), true /* compatIgnoreVisibility */);
+        assertEquals(Insets.of(0, 10, 0, 0), windowInsets.getSystemWindowInsets());
+    }
+
     // TODO: Move this to CTS once API made public
     @Test
     public void typeMap() {
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
new file mode 100644
index 0000000..8e24907
--- /dev/null
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2010 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.view.accessibility;
+
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertSame;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.Instrumentation;
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.os.UserHandle;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.IntPair;
+import com.android.server.accessibility.test.MessageCapturingHandler;
+
+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;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for the AccessibilityManager by mocking the backing service.
+ */
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityManagerTest {
+    private static final boolean WITH_A11Y_ENABLED = true;
+    private static final boolean WITH_A11Y_DISABLED = false;
+    private static final String LABEL = "label";
+    private static final String INTENT_ACTION = "TESTACTION";
+    private static final String DESCRIPTION = "description";
+    private static final PendingIntent TEST_PENDING_INTENT = PendingIntent.getBroadcast(
+            InstrumentationRegistry.getTargetContext(), 0, new Intent(INTENT_ACTION), 0);
+    private static final RemoteAction TEST_ACTION = new RemoteAction(
+            Icon.createWithContentUri("content://test"),
+            LABEL,
+            DESCRIPTION,
+            TEST_PENDING_INTENT);
+
+    @Mock private IAccessibilityManager mMockService;
+    private MessageCapturingHandler mHandler;
+    private Instrumentation mInstrumentation;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mHandler = new MessageCapturingHandler(null);
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+    }
+
+    @After
+    public void tearDown() {
+        mHandler.removeAllMessages();
+    }
+
+
+    private AccessibilityManager createManager(boolean enabled) throws Exception {
+        long serviceReturnValue = IntPair.of(
+                (enabled) ? AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED : 0,
+                AccessibilityEvent.TYPES_ALL_MASK);
+        when(mMockService.addClient(any(IAccessibilityManagerClient.class), anyInt()))
+                .thenReturn(serviceReturnValue);
+
+        AccessibilityManager manager =
+                new AccessibilityManager(mHandler, mMockService, UserHandle.USER_CURRENT);
+
+        verify(mMockService).addClient(any(IAccessibilityManagerClient.class), anyInt());
+        mHandler.setCallback(manager.getCallback());
+        mHandler.sendAllMessages();
+        return manager;
+    }
+
+    @Test
+    public void testGetAccessibilityServiceList() throws Exception {
+        // create a list of installed accessibility services the mock service returns
+        List<AccessibilityServiceInfo> expectedServices = new ArrayList<>();
+        AccessibilityServiceInfo accessibilityServiceInfo = new AccessibilityServiceInfo();
+        accessibilityServiceInfo.packageNames = new String[] { "foo.bar" };
+        expectedServices.add(accessibilityServiceInfo);
+
+        // configure the mock service behavior
+        when(mMockService.getInstalledAccessibilityServiceList(anyInt()))
+                .thenReturn(expectedServices);
+
+        // invoke the method under test
+        AccessibilityManager manager = createManager(true);
+        List<AccessibilityServiceInfo> receivedServices =
+                manager.getInstalledAccessibilityServiceList();
+
+        verify(mMockService).getInstalledAccessibilityServiceList(UserHandle.USER_CURRENT);
+        // check expected result (list equals() compares it contents as well)
+        assertEquals("All expected services must be returned", expectedServices, receivedServices);
+    }
+
+    @Test
+    public void testInterrupt() throws Exception {
+        AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
+        manager.interrupt();
+
+        verify(mMockService).interrupt(UserHandle.USER_CURRENT);
+    }
+
+    @Test
+    public void testRegisterSystemAction() throws Exception {
+        AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
+        RemoteAction action = new RemoteAction(
+                Icon.createWithContentUri("content://test"),
+                LABEL,
+                DESCRIPTION,
+                TEST_PENDING_INTENT);
+        final int actionId = 0;
+        manager.registerSystemAction(TEST_ACTION, actionId);
+
+        verify(mMockService).registerSystemAction(TEST_ACTION, actionId);
+    }
+
+    @Test
+    public void testUnregisterSystemAction() throws Exception {
+        AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
+        final int actionId = 0;
+        manager.unregisterSystemAction(actionId);
+
+        verify(mMockService).unregisterSystemAction(actionId);
+    }
+
+    @Test
+    public void testIsEnabled() throws Exception {
+        // Create manager with a11y enabled
+        AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
+        assertTrue("Must be enabled since the mock service is enabled", manager.isEnabled());
+
+        // Disable accessibility
+        manager.getClient().setState(0);
+        mHandler.sendAllMessages();
+        assertFalse("Must be disabled since the mock service is disabled", manager.isEnabled());
+    }
+
+    @Test
+    public void testSendAccessibilityEvent_AccessibilityEnabled() throws Exception {
+        AccessibilityEvent sentEvent = AccessibilityEvent.obtain(
+                AccessibilityEvent.TYPE_ANNOUNCEMENT);
+
+        AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
+        manager.sendAccessibilityEvent(sentEvent);
+
+        assertSame("The event should be recycled.", sentEvent, AccessibilityEvent.obtain());
+    }
+
+    @Test
+    public void testSendAccessibilityEvent_AccessibilityDisabled() throws Exception {
+        AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
+
+        AccessibilityManager manager = createManager(WITH_A11Y_DISABLED);
+        mInstrumentation.runOnMainSync(() -> {
+            try {
+                manager.sendAccessibilityEvent(sentEvent);
+                fail("No accessibility events are sent if accessibility is disabled");
+            } catch (IllegalStateException ise) {
+                // check expected result
+                assertEquals("Accessibility off. Did you forget to check that?", ise.getMessage());
+            }
+        });
+    }
+
+    @Test
+    public void testSetWindowMagnificationConnection() throws Exception {
+        AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
+        IWindowMagnificationConnection connection = Mockito.mock(
+                IWindowMagnificationConnection.class);
+
+        manager.setWindowMagnificationConnection(connection);
+
+        verify(mMockService).setWindowMagnificationConnection(connection);
+    }
+}
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 1bd52af..ade1e0d 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -46,7 +46,7 @@
     // The number of fields tested in the corresponding CTS AccessibilityNodeInfoTest:
     // See fullyPopulateAccessibilityNodeInfo, assertEqualsAccessibilityNodeInfo,
     // and assertAccessibilityNodeInfoCleared in that class.
-    private static final int NUM_MARSHALLED_PROPERTIES = 35;
+    private static final int NUM_MARSHALLED_PROPERTIES = 38;
 
     /**
      * The number of properties that are purposely not marshalled
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
index 3586216a..8b8e9ea 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -19,9 +19,14 @@
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.IAccessibilityServiceConnection;
 import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
 import android.graphics.Region;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.RemoteCallback;
+
+import java.util.Collections;
+import java.util.List;
 
 /**
  * Stub implementation of IAccessibilityServiceConnection so each test doesn't need to implement
@@ -83,6 +88,10 @@
         return false;
     }
 
+    public List<AccessibilityNodeInfo.AccessibilityAction> getSystemActions() {
+        return Collections.emptyList();
+    }
+
     public void disableSelf() {}
 
     public void setOnKeyEventResult(boolean handled, int sequence) {}
@@ -124,6 +133,10 @@
 
     public void setSoftKeyboardCallbackEnabled(boolean enabled) {}
 
+    public boolean switchToInputMethod(String imeId) {
+        return false;
+    }
+
     public boolean isAccessibilityButtonAvailable() {
         return false;
     }
@@ -139,4 +152,14 @@
     public IBinder getOverlayWindowToken(int displayId) {
         return null;
     }
+
+    public int getWindowIdForLeashToken(IBinder token) {
+        return -1;
+    }
+
+    public Bitmap takeScreenshot(int displayId) {
+        return null;
+    }
+
+    public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {}
 }
diff --git a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
index 68099fe..12c057f 100644
--- a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
+++ b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
@@ -18,9 +18,15 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
+import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.UserHandle;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -71,4 +77,227 @@
             }
         }
     }
+
+    @Test
+    public void testNullTextInputComposeInitialSurroundingText() {
+        final Spannable testText = null;
+        final EditorInfo editorInfo = new EditorInfo();
+
+        try {
+            editorInfo.setInitialSurroundingText(testText);
+            fail("Shall not take null input");
+        } catch (NullPointerException expected) {
+            // Expected behavior, nothing to do.
+        }
+    }
+
+    @Test
+    public void testNonNullTextInputComposeInitialSurroundingText() {
+        final Spannable testText = createTestText(/* prependLength= */ 0,
+                EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH);
+        final EditorInfo editorInfo = new EditorInfo();
+
+        // Cursor at position 0.
+        int selectionLength = 0;
+        editorInfo.initialSelStart = 0;
+        editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength;
+        int expectedTextBeforeCursorLength = 0;
+        int expectedTextAfterCursorLength = testText.length();
+
+        editorInfo.setInitialSurroundingText(testText);
+
+        assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength,
+                expectedTextAfterCursorLength);
+
+        // Cursor at the end.
+        editorInfo.initialSelStart = testText.length() - selectionLength;
+        editorInfo.initialSelEnd = testText.length();
+        expectedTextBeforeCursorLength = testText.length();
+        expectedTextAfterCursorLength = 0;
+
+        editorInfo.setInitialSurroundingText(testText);
+
+        assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength,
+                expectedTextAfterCursorLength);
+
+        // Cursor at the middle.
+        selectionLength = 2;
+        editorInfo.initialSelStart = testText.length() / 2;
+        editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength;
+        expectedTextBeforeCursorLength = editorInfo.initialSelStart;
+        expectedTextAfterCursorLength = testText.length() - editorInfo.initialSelEnd;
+
+        editorInfo.setInitialSurroundingText(testText);
+
+        assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength,
+                expectedTextAfterCursorLength);
+
+        // Accidentally swap selection start and end.
+        editorInfo.initialSelEnd = testText.length() / 2;
+        editorInfo.initialSelStart = editorInfo.initialSelEnd + selectionLength;
+
+        editorInfo.setInitialSurroundingText(testText);
+
+        assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength,
+                expectedTextAfterCursorLength);
+
+        // Invalid cursor position.
+        editorInfo.initialSelStart = -1;
+
+        editorInfo.setInitialSurroundingText(testText);
+
+        assertExpectedTextLength(editorInfo,
+                /* expectBeforeCursorLength= */null,
+                /* expectSelectionLength= */null,
+                /* expectAfterCursorLength= */null);
+    }
+
+    @Test
+    public void testTooLongTextInputComposeInitialSurroundingText() {
+        final Spannable testText = createTestText(/* prependLength= */ 0,
+                EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH + 2);
+        final EditorInfo editorInfo = new EditorInfo();
+
+        // Cursor at position 0.
+        int selectionLength = 0;
+        editorInfo.initialSelStart = 0;
+        editorInfo.initialSelEnd = 0 + selectionLength;
+        int expectedTextBeforeCursorLength = 0;
+        int expectedTextAfterCursorLength = editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH;
+
+        editorInfo.setInitialSurroundingText(testText);
+
+        assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength,
+                expectedTextAfterCursorLength);
+
+        // Cursor at the end.
+        editorInfo.initialSelStart = testText.length() - selectionLength;
+        editorInfo.initialSelEnd = testText.length();
+        expectedTextBeforeCursorLength = editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH;
+        expectedTextAfterCursorLength = 0;
+
+        editorInfo.setInitialSurroundingText(testText);
+
+        assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength,
+                expectedTextAfterCursorLength);
+
+        // Cursor at the middle.
+        selectionLength = 2;
+        editorInfo.initialSelStart = testText.length() / 2;
+        editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength;
+        expectedTextBeforeCursorLength = Math.min(editorInfo.initialSelStart,
+                (int) (0.8 * (EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH - selectionLength)));
+        expectedTextAfterCursorLength = EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH
+                - expectedTextBeforeCursorLength - selectionLength;
+
+        editorInfo.setInitialSurroundingText(testText);
+
+        assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength,
+                expectedTextAfterCursorLength);
+
+        // Accidentally swap selection start and end.
+        editorInfo.initialSelEnd = testText.length() / 2;
+        editorInfo.initialSelStart = editorInfo.initialSelEnd + selectionLength;
+
+        editorInfo.setInitialSurroundingText(testText);
+
+        assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength,
+                expectedTextAfterCursorLength);
+
+        // Selection too long, selected text should be dropped.
+        selectionLength = EditorInfo.MAX_INITIAL_SELECTION_LENGTH + 1;
+        editorInfo.initialSelStart = testText.length() / 2;
+        editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength;
+        expectedTextBeforeCursorLength = Math.min(editorInfo.initialSelStart,
+                (int) (0.8 * EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH));
+        expectedTextAfterCursorLength = testText.length() - editorInfo.initialSelEnd;
+
+        editorInfo.setInitialSurroundingText(testText);
+
+        assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength,
+                /* expectSelectionLength= */null, expectedTextAfterCursorLength);
+    }
+
+    @Test
+    public void testTooLongSubTextInputComposeInitialSurroundingText() {
+        final int prependLength = 5;
+        final int subTextLength = EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH;
+        final Spannable fullText = createTestText(prependLength, subTextLength);
+        final EditorInfo editorInfo = new EditorInfo();
+        // Cursor at the middle.
+        final int selectionLength = 2;
+        editorInfo.initialSelStart = fullText.length() / 2;
+        editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength;
+        // #prependLength characters will be trimmed out.
+        final Spannable expectedTextBeforeCursor = createExpectedText(/* startNumber= */0,
+                editorInfo.initialSelStart - prependLength);
+        final Spannable expectedSelectedText = createExpectedText(
+                editorInfo.initialSelStart - prependLength, selectionLength);
+        final Spannable expectedTextAfterCursor = createExpectedText(
+                editorInfo.initialSelEnd - prependLength,
+                fullText.length() - editorInfo.initialSelEnd);
+
+        editorInfo.setInitialSurroundingSubText(fullText.subSequence(prependLength,
+                fullText.length()), prependLength);
+
+        assertTrue(TextUtils.equals(expectedTextBeforeCursor,
+                editorInfo.getInitialTextBeforeCursor(editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH,
+                        InputConnection.GET_TEXT_WITH_STYLES)));
+        assertTrue(TextUtils.equals(expectedSelectedText,
+                editorInfo.getInitialSelectedText(InputConnection.GET_TEXT_WITH_STYLES)));
+        assertTrue(TextUtils.equals(expectedTextAfterCursor,
+                editorInfo.getInitialTextAfterCursor(editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH,
+                        InputConnection.GET_TEXT_WITH_STYLES)));
+    }
+
+    private static void assertExpectedTextLength(EditorInfo editorInfo,
+            @Nullable Integer expectBeforeCursorLength, @Nullable Integer expectSelectionLength,
+            @Nullable Integer expectAfterCursorLength) {
+        final CharSequence textBeforeCursor =
+                editorInfo.getInitialTextBeforeCursor(editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH,
+                        InputConnection.GET_TEXT_WITH_STYLES);
+        final CharSequence selectedText =
+                editorInfo.getInitialSelectedText(InputConnection.GET_TEXT_WITH_STYLES);
+        final CharSequence textAfterCursor =
+                editorInfo.getInitialTextAfterCursor(editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH,
+                        InputConnection.GET_TEXT_WITH_STYLES);
+
+        if (expectBeforeCursorLength == null) {
+            assertNull(textBeforeCursor);
+        } else {
+            assertEquals(expectBeforeCursorLength.intValue(), textBeforeCursor.length());
+        }
+
+        if (expectSelectionLength == null) {
+            assertNull(selectedText);
+        } else {
+            assertEquals(expectSelectionLength.intValue(), selectedText.length());
+        }
+
+        if (expectAfterCursorLength == null) {
+            assertNull(textAfterCursor);
+        } else {
+            assertEquals(expectAfterCursorLength.intValue(), textAfterCursor.length());
+        }
+    }
+
+    private static Spannable createTestText(int prependLength, int surroundingLength) {
+        final SpannableStringBuilder builder = new SpannableStringBuilder();
+        for (int i = 0; i < prependLength; i++) {
+            builder.append("a");
+        }
+
+        for (int i = 0; i < surroundingLength; i++) {
+            builder.append(Integer.toString(i % 10));
+        }
+        return builder;
+    }
+
+    private static Spannable createExpectedText(int startNumber, int length) {
+        final SpannableStringBuilder builder = new SpannableStringBuilder();
+        for (int i = startNumber; i < startNumber + length; i++) {
+            builder.append(Integer.toString(i % 10));
+        }
+        return builder;
+    }
 }
diff --git a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
index f497db2..a602fa3 100644
--- a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
+++ b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
@@ -32,6 +32,7 @@
 
 import android.app.Activity;
 import android.app.Instrumentation;
+import android.view.InputDevice;
 import android.view.MotionEvent;
 
 import androidx.test.InstrumentationRegistry;
@@ -67,6 +68,7 @@
         mOriginalFlagValue = Editor.FLAG_ENABLE_CURSOR_DRAG;
         Editor.FLAG_ENABLE_CURSOR_DRAG = true;
     }
+
     @After
     public void after() throws Throwable {
         Editor.FLAG_ENABLE_CURSOR_DRAG = mOriginalFlagValue;
@@ -226,6 +228,90 @@
     }
 
     @Test
+    public void testEditor_onTouchEvent_quickTapAfterDrag() throws Throwable {
+        String text = "Hi world!";
+        onView(withId(R.id.textview)).perform(replaceText(text));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
+
+        TextView tv = mActivity.findViewById(R.id.textview);
+        Editor editor = tv.getEditorForTesting();
+
+        // Simulate a tap-and-drag gesture.
+        long event1Time = 1001;
+        MotionEvent event1 = downEvent(event1Time, event1Time, 5f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        long event2Time = 1002;
+        MotionEvent event2 = moveEvent(event1Time, event2Time, 50f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2));
+        assertTrue(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        long event3Time = 1003;
+        MotionEvent event3 = moveEvent(event1Time, event3Time, 100f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3));
+        assertTrue(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        long event4Time = 2004;
+        MotionEvent event4 = upEvent(event1Time, event4Time, 100f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event4));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        // Simulate a quick tap after the drag, near the location where the drag ended.
+        long event5Time = 2005;
+        MotionEvent event5 = downEvent(event5Time, event5Time, 90f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event5));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        long event6Time = 2006;
+        MotionEvent event6 = upEvent(event5Time, event6Time, 90f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event6));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        // Simulate another quick tap in the same location; now selection should be triggered.
+        long event7Time = 2007;
+        MotionEvent event7 = downEvent(event7Time, event7Time, 90f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event7));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertTrue(editor.getSelectionController().isCursorBeingModified());
+    }
+
+    @Test
+    public void testEditor_onTouchEvent_mouseDrag() throws Throwable {
+        String text = "testEditor_onTouchEvent_mouseDrag";
+        onView(withId(R.id.textview)).perform(replaceText(text));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
+
+        TextView tv = mActivity.findViewById(R.id.textview);
+        Editor editor = tv.getEditorForTesting();
+
+        // Simulate a mouse click and drag. This should NOT trigger a cursor drag.
+        long event1Time = 1001;
+        MotionEvent event1 = mouseDownEvent(event1Time, event1Time, 20f, 30f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        long event2Time = 1002;
+        MotionEvent event2 = mouseMoveEvent(event1Time, event2Time, 120f, 30f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertTrue(editor.getSelectionController().isCursorBeingModified());
+
+        long event3Time = 1003;
+        MotionEvent event3 = mouseUpEvent(event1Time, event3Time, 120f, 30f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+    }
+
+    @Test
     public void testEditor_onTouchEvent_cursorDrag() throws Throwable {
         String text = "testEditor_onTouchEvent_cursorDrag";
         onView(withId(R.id.textview)).perform(replaceText(text));
@@ -237,29 +323,25 @@
         // Simulate a tap-and-drag gesture. This should trigger a cursor drag.
         long event1Time = 1001;
         MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event1));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
 
         long event2Time = 1002;
         MotionEvent event2 = moveEvent(event1Time, event2Time, 21f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event2));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
 
         long event3Time = 1003;
-        MotionEvent event3 = moveEvent(event3Time, event3Time, 120f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event3));
-        mInstrumentation.waitForIdleSync();
+        MotionEvent event3 = moveEvent(event1Time, event3Time, 120f, 30f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3));
         assertTrue(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
 
         long event4Time = 1004;
-        MotionEvent event4 = upEvent(event3Time, event4Time, 120f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event4));
-        mInstrumentation.waitForIdleSync();
+        MotionEvent event4 = upEvent(event1Time, event4Time, 120f, 30f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event4));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
     }
@@ -276,40 +358,52 @@
         // Simulate a double-tap followed by a drag. This should trigger a selection drag.
         long event1Time = 1001;
         MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event1));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
 
         long event2Time = 1002;
         MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event2));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
 
         long event3Time = 1003;
         MotionEvent event3 = downEvent(event3Time, event3Time, 20f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event3));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertTrue(editor.getSelectionController().isCursorBeingModified());
 
         long event4Time = 1004;
         MotionEvent event4 = moveEvent(event3Time, event4Time, 120f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event4));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event4));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertTrue(editor.getSelectionController().isCursorBeingModified());
 
         long event5Time = 1005;
         MotionEvent event5 = upEvent(event3Time, event5Time, 120f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event5));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event5));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
     }
 
+    @Test // Reproduces b/147366705
+    public void testCursorDrag_nonSelectableTextView() throws Throwable {
+        String text = "Hello world!";
+        TextView tv = mActivity.findViewById(R.id.nonselectable_textview);
+        tv.setText(text);
+        Editor editor = tv.getEditorForTesting();
+
+        // Simulate a tap. No error should be thrown.
+        long event1Time = 1001;
+        MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1));
+
+        // Swipe left to right. No error should be thrown.
+        onView(withId(R.id.nonselectable_textview)).perform(
+                dragOnText(text.indexOf("llo"), text.indexOf("!")));
+    }
+
     private static MotionEvent downEvent(long downTime, long eventTime, float x, float y) {
         return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0);
     }
@@ -321,4 +415,25 @@
     private static MotionEvent moveEvent(long downTime, long eventTime, float x, float y) {
         return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
     }
+
+    private static MotionEvent mouseDownEvent(long downTime, long eventTime, float x, float y) {
+        MotionEvent event = downEvent(downTime, eventTime, x, y);
+        event.setSource(InputDevice.SOURCE_MOUSE);
+        event.setButtonState(MotionEvent.BUTTON_PRIMARY);
+        return event;
+    }
+
+    private static MotionEvent mouseUpEvent(long downTime, long eventTime, float x, float y) {
+        MotionEvent event = upEvent(downTime, eventTime, x, y);
+        event.setSource(InputDevice.SOURCE_MOUSE);
+        event.setButtonState(0);
+        return event;
+    }
+
+    private static MotionEvent mouseMoveEvent(long downTime, long eventTime, float x, float y) {
+        MotionEvent event = moveEvent(downTime, eventTime, x, y);
+        event.setSource(InputDevice.SOURCE_MOUSE);
+        event.setButtonState(MotionEvent.BUTTON_PRIMARY);
+        return event;
+    }
 }
diff --git a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
index 6adb1b8..3dc001d 100644
--- a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
+++ b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
@@ -48,24 +48,32 @@
     }
 
     @Test
+    public void testIsDistanceWithin() throws Exception {
+        assertTrue(EditorTouchState.isDistanceWithin(0, 0, 0, 0, 8));
+        assertTrue(EditorTouchState.isDistanceWithin(3, 9, 5, 11, 8));
+        assertTrue(EditorTouchState.isDistanceWithin(5, 11, 3, 9, 8));
+        assertFalse(EditorTouchState.isDistanceWithin(5, 10, 5, 20, 8));
+    }
+
+    @Test
     public void testUpdate_singleTap() throws Exception {
         // Simulate an ACTION_DOWN event.
         long event1Time = 1000;
         MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
         mTouchState.update(event1, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0);
 
         // Simulate an ACTION_UP event.
         long event2Time = 1001;
         MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
         mTouchState.update(event2, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
 
         // Generate an ACTION_DOWN event whose time is after the double-tap timeout.
         long event3Time = event2Time + ViewConfiguration.getDoubleTapTimeout() + 1;
         MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f);
         mTouchState.update(event3, mConfig);
-        assertSingleTap(mTouchState, 22f, 33f, 20f, 30f, false);
+        assertSingleTap(mTouchState, 22f, 33f, 20f, 30f);
     }
 
     @Test
@@ -74,13 +82,13 @@
         long event1Time = 1000;
         MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
         mTouchState.update(event1, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0);
 
         // Simulate an ACTION_UP event.
         long event2Time = 1001;
         MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
         mTouchState.update(event2, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
 
         // Generate an ACTION_DOWN event whose time is within the double-tap timeout.
         long event3Time = 1002;
@@ -96,13 +104,13 @@
         long event1Time = 1000;
         MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
         mTouchState.update(event1, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0);
 
         // Simulate an ACTION_UP event.
         long event2Time = 1001;
         MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
         mTouchState.update(event2, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
 
         // Generate an ACTION_DOWN event whose time is within the double-tap timeout.
         long event3Time = 1002;
@@ -120,20 +128,74 @@
     }
 
     @Test
+    public void testUpdate_doubleTap_delayAfterFirstDownEvent() throws Exception {
+        // Simulate an ACTION_DOWN event.
+        long event1Time = 1000;
+        MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+        mTouchState.update(event1, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+
+        // Simulate an ACTION_UP event with a delay that's longer than the double-tap timeout.
+        long event2Time = 1000 + ViewConfiguration.getDoubleTapTimeout() + 1;
+        MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
+        mTouchState.update(event2, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
+
+        // Generate an ACTION_DOWN event whose time is within the double-tap timeout when
+        // calculated from the last ACTION_UP event time. Even though the time between the last up
+        // and this down event is within the double-tap timeout, this should not be considered a
+        // double-tap (since the first down event had a longer delay).
+        long event3Time = event2Time + 1;
+        MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f);
+        mTouchState.update(event3, mConfig);
+        assertSingleTap(mTouchState, 22f, 33f, 20f, 30f);
+    }
+
+    @Test
+    public void testUpdate_quickTapAfterDrag() throws Exception {
+        // Simulate an ACTION_DOWN event.
+        long event1Time = 1000;
+        MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+        mTouchState.update(event1, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+
+        // Simulate an ACTION_MOVE event.
+        long event2Time = 1001;
+        MotionEvent event2 = moveEvent(event1Time, event2Time, 200f, 31f);
+        mTouchState.update(event2, mConfig);
+        assertDrag(mTouchState, 20f, 30f, 0, 0, false);
+
+        // Simulate an ACTION_UP event with a delay that's longer than the double-tap timeout.
+        long event3Time = 5000;
+        MotionEvent event3 = upEvent(event1Time, event3Time, 200f, 31f);
+        mTouchState.update(event3, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 200f, 31f);
+
+        // Generate an ACTION_DOWN event whose time is within the double-tap timeout when
+        // calculated from the last ACTION_UP event time. Even though the time between the last up
+        // and this down event is within the double-tap timeout, this should not be considered a
+        // double-tap (since the first down event had a longer delay).
+        long event4Time = event3Time + 1;
+        MotionEvent event4 = downEvent(event4Time, event4Time, 200f, 31f);
+        mTouchState.update(event4, mConfig);
+        assertSingleTap(mTouchState, 200f, 31f, 200f, 31f);
+    }
+
+    @Test
     public void testUpdate_tripleClick_mouse() throws Exception {
         // Simulate an ACTION_DOWN event.
         long event1Time = 1000;
         MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
         event1.setSource(InputDevice.SOURCE_MOUSE);
         mTouchState.update(event1, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0);
 
         // Simulate an ACTION_UP event.
         long event2Time = 1001;
         MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
         event2.setSource(InputDevice.SOURCE_MOUSE);
         mTouchState.update(event2, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
 
         // Generate a second ACTION_DOWN event whose time is within the double-tap timeout.
         long event3Time = 1002;
@@ -166,13 +228,13 @@
         long event1Time = 1000;
         MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
         mTouchState.update(event1, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0);
 
         // Simulate an ACTION_UP event.
         long event2Time = 1001;
         MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
         mTouchState.update(event2, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
 
         // Generate a second ACTION_DOWN event whose time is within the double-tap timeout.
         long event3Time = 1002;
@@ -192,7 +254,7 @@
         long event5Time = 1004;
         MotionEvent event5 = downEvent(event5Time, event5Time, 22f, 32f);
         mTouchState.update(event5, mConfig);
-        assertSingleTap(mTouchState, 22f, 32f, 21f, 31f, false);
+        assertSingleTap(mTouchState, 22f, 32f, 21f, 31f);
     }
 
     @Test
@@ -201,13 +263,13 @@
         long event1Time = 1000;
         MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
         mTouchState.update(event1, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0);
 
         // Simulate an ACTION_MOVE event whose location is not far enough to start a drag.
         long event2Time = 1001;
         MotionEvent event2 = moveEvent(event1Time, event2Time, 21f, 30f);
         mTouchState.update(event2, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0);
 
         // Simulate another ACTION_MOVE event whose location is far enough to start a drag.
         int touchSlop = mConfig.getScaledTouchSlop();
@@ -216,21 +278,135 @@
         long event3Time = 1002;
         MotionEvent event3 = moveEvent(event3Time, event3Time, newX, newY);
         mTouchState.update(event3, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 0, 0, true);
+        assertDrag(mTouchState, 20f, 30f, 0, 0, false);
 
         // Simulate an ACTION_UP event.
         long event4Time = 1003;
         MotionEvent event4 = upEvent(event3Time, event4Time, 200f, 300f);
         mTouchState.update(event4, mConfig);
-        assertSingleTap(mTouchState, 20f, 30f, 200f, 300f, false);
+        assertSingleTap(mTouchState, 20f, 30f, 200f, 300f);
     }
 
     @Test
-    public void testIsDistanceWithin() throws Exception {
-        assertTrue(EditorTouchState.isDistanceWithin(0, 0, 0, 0, 8));
-        assertTrue(EditorTouchState.isDistanceWithin(3, 9, 5, 11, 8));
-        assertTrue(EditorTouchState.isDistanceWithin(5, 11, 3, 9, 8));
-        assertFalse(EditorTouchState.isDistanceWithin(5, 10, 5, 20, 8));
+    public void testUpdate_drag_startsCloseToVerticalThenHorizontal() throws Exception {
+        // Simulate an ACTION_DOWN event.
+        long event1Time = 1001;
+        MotionEvent event1 = downEvent(event1Time, event1Time, 0f, 0f);
+        mTouchState.update(event1, mConfig);
+        assertSingleTap(mTouchState, 0f, 0f, 0, 0);
+
+        // Simulate an ACTION_MOVE event that is < 30 deg from vertical.
+        long event2Time = 1002;
+        MotionEvent event2 = moveEvent(event1Time, event2Time, 100f, 174f);
+        mTouchState.update(event2, mConfig);
+        assertDrag(mTouchState, 0f, 0f, 0, 0, true);
+
+        // Simulate another ACTION_MOVE event that is horizontal from the original down event.
+        // The value of `isDragCloseToVertical` should NOT change since it should only reflect the
+        // initial direction of movement.
+        long event3Time = 1003;
+        MotionEvent event3 = moveEvent(event1Time, event3Time, 200f, 0f);
+        mTouchState.update(event3, mConfig);
+        assertDrag(mTouchState, 0f, 0f, 0, 0, true);
+
+        // Simulate an ACTION_UP event.
+        long event4Time = 1004;
+        MotionEvent event4 = upEvent(event1Time, event4Time, 200f, 0f);
+        mTouchState.update(event4, mConfig);
+        assertSingleTap(mTouchState, 0f, 0f, 200f, 0f);
+    }
+
+    @Test
+    public void testUpdate_drag_startsHorizontalThenVertical() throws Exception {
+        // Simulate an ACTION_DOWN event.
+        long event1Time = 1001;
+        MotionEvent event1 = downEvent(event1Time, event1Time, 0f, 0f);
+        mTouchState.update(event1, mConfig);
+        assertSingleTap(mTouchState, 0f, 0f, 0, 0);
+
+        // Simulate an ACTION_MOVE event that is > 30 deg from vertical.
+        long event2Time = 1002;
+        MotionEvent event2 = moveEvent(event1Time, event2Time, 100f, 173f);
+        mTouchState.update(event2, mConfig);
+        assertDrag(mTouchState, 0f, 0f, 0, 0, false);
+
+        // Simulate another ACTION_MOVE event that is vertical from the original down event.
+        // The value of `isDragCloseToVertical` should NOT change since it should only reflect the
+        // initial direction of movement.
+        long event3Time = 1003;
+        MotionEvent event3 = moveEvent(event1Time, event3Time, 0f, 200f);
+        mTouchState.update(event3, mConfig);
+        assertDrag(mTouchState, 0f, 0f, 0, 0, false);
+
+        // Simulate an ACTION_UP event.
+        long event4Time = 1004;
+        MotionEvent event4 = upEvent(event1Time, event4Time, 0f, 200f);
+        mTouchState.update(event4, mConfig);
+        assertSingleTap(mTouchState, 0f, 0f, 0f, 200f);
+    }
+
+    @Test
+    public void testUpdate_cancelAfterDown() throws Exception {
+        // Simulate an ACTION_DOWN event.
+        long event1Time = 1001;
+        MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+        mTouchState.update(event1, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+
+        // Simulate an ACTION_CANCEL event.
+        long event2Time = 1002;
+        MotionEvent event2 = cancelEvent(event1Time, event2Time, 20f, 30f);
+        mTouchState.update(event2, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+    }
+
+    @Test
+    public void testUpdate_cancelAfterDrag() throws Exception {
+        // Simulate an ACTION_DOWN event.
+        long event1Time = 1001;
+        MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+        mTouchState.update(event1, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+
+        // Simulate another ACTION_MOVE event whose location is far enough to start a drag.
+        long event2Time = 1002;
+        MotionEvent event2 = moveEvent(event2Time, event2Time, 200f, 30f);
+        mTouchState.update(event2, mConfig);
+        assertDrag(mTouchState, 20f, 30f, 0, 0, false);
+
+        // Simulate an ACTION_CANCEL event.
+        long event3Time = 1003;
+        MotionEvent event3 = cancelEvent(event1Time, event3Time, 200f, 30f);
+        mTouchState.update(event3, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+    }
+
+    @Test
+    public void testUpdate_cancelAfterMultitap() throws Exception {
+        // Simulate an ACTION_DOWN event.
+        long event1Time = 1001;
+        MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+        mTouchState.update(event1, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+
+        // Simulate an ACTION_UP event.
+        long event2Time = 1002;
+        MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
+        mTouchState.update(event2, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
+
+        // Generate an ACTION_DOWN event whose time is within the double-tap timeout.
+        long event3Time = 1003;
+        MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f);
+        mTouchState.update(event3, mConfig);
+        assertMultiTap(mTouchState, 22f, 33f, 20f, 30f,
+                MultiTapStatus.DOUBLE_TAP, true);
+
+        // Simulate an ACTION_CANCEL event.
+        long event4Time = 1004;
+        MotionEvent event4 = cancelEvent(event3Time, event4Time, 20f, 30f);
+        mTouchState.update(event4, mConfig);
+        assertSingleTap(mTouchState, 22f, 33f, 20f, 30f);
     }
 
     private static MotionEvent downEvent(long downTime, long eventTime, float x, float y) {
@@ -245,8 +421,12 @@
         return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
     }
 
+    private static MotionEvent cancelEvent(long downTime, long eventTime, float x, float y) {
+        return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_CANCEL, x, y, 0);
+    }
+
     private static void assertSingleTap(EditorTouchState touchState, float lastDownX,
-            float lastDownY, float lastUpX, float lastUpY, boolean isMovedEnoughForDrag) {
+            float lastDownY, float lastUpX, float lastUpY) {
         assertThat(touchState.getLastDownX(), is(lastDownX));
         assertThat(touchState.getLastDownY(), is(lastDownY));
         assertThat(touchState.getLastUpX(), is(lastUpX));
@@ -255,7 +435,21 @@
         assertThat(touchState.isTripleClick(), is(false));
         assertThat(touchState.isMultiTap(), is(false));
         assertThat(touchState.isMultiTapInSameArea(), is(false));
-        assertThat(touchState.isMovedEnoughForDrag(), is(isMovedEnoughForDrag));
+        assertThat(touchState.isMovedEnoughForDrag(), is(false));
+    }
+
+    private static void assertDrag(EditorTouchState touchState, float lastDownX,
+            float lastDownY, float lastUpX, float lastUpY, boolean isDragCloseToVertical) {
+        assertThat(touchState.getLastDownX(), is(lastDownX));
+        assertThat(touchState.getLastDownY(), is(lastDownY));
+        assertThat(touchState.getLastUpX(), is(lastUpX));
+        assertThat(touchState.getLastUpY(), is(lastUpY));
+        assertThat(touchState.isDoubleTap(), is(false));
+        assertThat(touchState.isTripleClick(), is(false));
+        assertThat(touchState.isMultiTap(), is(false));
+        assertThat(touchState.isMultiTapInSameArea(), is(false));
+        assertThat(touchState.isMovedEnoughForDrag(), is(true));
+        assertThat(touchState.isDragCloseToVertical(), is(isDragCloseToVertical));
     }
 
     private static void assertMultiTap(EditorTouchState touchState,
@@ -271,5 +465,6 @@
                 || multiTapStatus == MultiTapStatus.TRIPLE_CLICK));
         assertThat(touchState.isMultiTapInSameArea(), is(isMultiTapInSameArea));
         assertThat(touchState.isMovedEnoughForDrag(), is(false));
+        assertThat(touchState.isDragCloseToVertical(), is(false));
     }
 }
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
index b411668..a0cfb31 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
@@ -48,7 +48,6 @@
 import android.view.textclassifier.TextClassifier;
 
 import androidx.test.filters.MediumTest;
-import androidx.test.filters.Suppress;
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -64,7 +63,6 @@
  */
 @RunWith(AndroidJUnit4.class)
 @MediumTest
-@Suppress // Consistently failing. b/29591177
 public class TextViewActivityMouseTest {
 
     @Rule
@@ -86,22 +84,12 @@
         onView(withId(R.id.textview)).perform(mouseClick());
         onView(withId(R.id.textview)).perform(replaceText(helloWorld));
 
-        assertNoSelectionHandles();
-
         onView(withId(R.id.textview)).perform(
                 mouseDragOnText(helloWorld.indexOf("llo"), helloWorld.indexOf("ld!")));
-
         onView(withId(R.id.textview)).check(hasSelection("llo wor"));
 
-        onHandleView(com.android.internal.R.id.selection_start_handle)
-                .check(matches(isDisplayed()));
-        onHandleView(com.android.internal.R.id.selection_end_handle)
-                .check(matches(isDisplayed()));
-
         onView(withId(R.id.textview)).perform(mouseClickOnTextAtIndex(helloWorld.indexOf("w")));
         onView(withId(R.id.textview)).check(hasSelection(""));
-
-        assertNoSelectionHandles();
     }
 
     @Test
@@ -196,7 +184,6 @@
 
         onView(withId(R.id.textview)).check(matches(withText("abc ghi.def")));
         onView(withId(R.id.textview)).check(hasSelection(""));
-        assertNoSelectionHandles();
         onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex("abc ghi.def".length()));
     }
 
@@ -213,7 +200,6 @@
 
         onView(withId(R.id.textview)).check(matches(withText("abc ghi.def")));
         onView(withId(R.id.textview)).check(hasSelection(""));
-        assertNoSelectionHandles();
         onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex("abc ghi.def".length()));
     }
 
@@ -403,4 +389,29 @@
                 mouseTripleClickAndDragOnText(text.indexOf("ird"), text.indexOf("First")));
         onView(withId(R.id.textview)).check(hasSelection(text));
     }
+
+    @Test
+    public void testSelectionHandlesDisplay() {
+        final String helloWorld = "Hello world!";
+        onView(withId(R.id.textview)).perform(mouseClick());
+        onView(withId(R.id.textview)).perform(replaceText(helloWorld));
+
+        onView(withId(R.id.textview)).perform(
+                mouseDragOnText(helloWorld.indexOf("llo"), helloWorld.indexOf("ld!")));
+        onView(withId(R.id.textview)).check(hasSelection("llo wor"));
+
+        // Confirm that selection handles are shown when there is a selection.
+        onHandleView(com.android.internal.R.id.selection_start_handle)
+                .check(matches(isDisplayed()));
+        onHandleView(com.android.internal.R.id.selection_end_handle)
+                .check(matches(isDisplayed()));
+
+        onView(withId(R.id.textview)).perform(mouseClickOnTextAtIndex(helloWorld.indexOf("w")));
+        onView(withId(R.id.textview)).check(hasSelection(""));
+
+        // Confirm that the selection handles are not shown when there is no selection. This
+        // assertion is slow so we only do it in this one test case. The rest of the tests just
+        // assert via `hasSelection("")`.
+        assertNoSelectionHandles();
+    }
 }
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index fbe4c1a..0c38e71 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -27,9 +27,13 @@
 import static android.widget.espresso.TextViewActions.Handle;
 import static android.widget.espresso.TextViewActions.clickOnTextAtIndex;
 import static android.widget.espresso.TextViewActions.doubleClickOnTextAtIndex;
+import static android.widget.espresso.TextViewActions.doubleTapAndDragHandle;
 import static android.widget.espresso.TextViewActions.doubleTapAndDragOnText;
+import static android.widget.espresso.TextViewActions.doubleTapHandle;
 import static android.widget.espresso.TextViewActions.dragHandle;
 import static android.widget.espresso.TextViewActions.longPressAndDragOnText;
+import static android.widget.espresso.TextViewActions.longPressAndDragHandle;
+import static android.widget.espresso.TextViewActions.longPressHandle;
 import static android.widget.espresso.TextViewActions.longPressOnTextAtIndex;
 import static android.widget.espresso.TextViewAssertions.doesNotHaveStyledText;
 import static android.widget.espresso.TextViewAssertions.hasInsertionPointerAtIndex;
@@ -511,6 +515,111 @@
     }
 
     @Test
+    public void testInsertionHandle_touchThrough() {
+        final TextView textView = mActivity.findViewById(R.id.textview);
+        boolean cursorControlEnabled = textView.getEditorForTesting().getCursorControlEnabled();
+        boolean cursorDragEnabled = Editor.FLAG_ENABLE_CURSOR_DRAG;
+        textView.getEditorForTesting().setCursorControlEnabled(true);
+        Editor.FLAG_ENABLE_CURSOR_DRAG = true;
+
+        testInsertionHandle();
+        testInsertionHandle_multiLine();
+
+        textView.getEditorForTesting().setCursorControlEnabled(cursorControlEnabled);
+        Editor.FLAG_ENABLE_CURSOR_DRAG = cursorDragEnabled;
+    }
+
+    @Test
+    public void testInsertionHandle_longPressToSelect() {
+        // This test only makes sense when Cursor Control flag is enabled.
+        final TextView textView = mActivity.findViewById(R.id.textview);
+        boolean cursorControlEnabled = textView.getEditorForTesting().getCursorControlEnabled();
+        boolean cursorDragEnabled = Editor.FLAG_ENABLE_CURSOR_DRAG;
+        textView.getEditorForTesting().setCursorControlEnabled(true);
+        Editor.FLAG_ENABLE_CURSOR_DRAG = true;
+
+        final String text = "hello the world";
+        onView(withId(R.id.textview)).perform(replaceText(text));
+
+        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length()));
+
+        onHandleView(com.android.internal.R.id.insertion_handle).perform(longPressHandle(textView));
+        onView(withId(R.id.textview)).check(hasSelection("world"));
+
+        textView.getEditorForTesting().setCursorControlEnabled(cursorControlEnabled);
+        Editor.FLAG_ENABLE_CURSOR_DRAG = cursorDragEnabled;
+    }
+
+    @Test
+    public void testInsertionHandle_longPressAndDragToSelect() {
+        // This test only makes sense when Cursor Control flag is enabled.
+        final TextView textView = mActivity.findViewById(R.id.textview);
+        boolean cursorControlEnabled = textView.getEditorForTesting().getCursorControlEnabled();
+        boolean cursorDragEnabled = Editor.FLAG_ENABLE_CURSOR_DRAG;
+        textView.getEditorForTesting().setCursorControlEnabled(true);
+        Editor.FLAG_ENABLE_CURSOR_DRAG = true;
+
+        final String text = "hello the world";
+        onView(withId(R.id.textview)).perform(replaceText(text));
+
+        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length()));
+
+        onHandleView(com.android.internal.R.id.insertion_handle)
+                .perform(longPressAndDragHandle(textView, Handle.INSERTION, text.indexOf('t')));
+        onView(withId(R.id.textview)).check(hasSelection("the world"));
+
+        textView.getEditorForTesting().setCursorControlEnabled(cursorControlEnabled);
+        Editor.FLAG_ENABLE_CURSOR_DRAG = cursorDragEnabled;
+    }
+
+    @Test
+    public void testInsertionHandle_doubleTapToSelect() {
+        // This test only makes sense when Cursor Control flag is enabled.
+        final TextView textView = mActivity.findViewById(R.id.textview);
+        boolean cursorControlEnabled = textView.getEditorForTesting().getCursorControlEnabled();
+        boolean cursorDragEnabled = Editor.FLAG_ENABLE_CURSOR_DRAG;
+        textView.getEditorForTesting().setCursorControlEnabled(true);
+        Editor.FLAG_ENABLE_CURSOR_DRAG = true;
+
+        final String text = "hello the world";
+        onView(withId(R.id.textview)).perform(replaceText(text));
+
+        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length()));
+
+        onHandleView(com.android.internal.R.id.insertion_handle).perform(doubleTapHandle(textView));
+        onView(withId(R.id.textview)).check(hasSelection("world"));
+
+        textView.getEditorForTesting().setCursorControlEnabled(cursorControlEnabled);
+        Editor.FLAG_ENABLE_CURSOR_DRAG = cursorDragEnabled;
+    }
+
+    @Test
+    public void testInsertionHandle_doubleTapAndDragToSelect() {
+        // This test only makes sense when Cursor Control flag is enabled.
+        final TextView textView = mActivity.findViewById(R.id.textview);
+        boolean cursorControlEnabled = textView.getEditorForTesting().getCursorControlEnabled();
+        boolean cursorDragEnabled = Editor.FLAG_ENABLE_CURSOR_DRAG;
+        textView.getEditorForTesting().setCursorControlEnabled(true);
+        Editor.FLAG_ENABLE_CURSOR_DRAG = true;
+
+        final String text = "hello the world";
+        onView(withId(R.id.textview)).perform(replaceText(text));
+
+        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length()));
+
+        onHandleView(com.android.internal.R.id.insertion_handle)
+                .perform(doubleTapAndDragHandle(textView, Handle.INSERTION, text.indexOf('t')));
+        onView(withId(R.id.textview)).check(hasSelection("the world"));
+
+        textView.getEditorForTesting().setCursorControlEnabled(cursorControlEnabled);
+        Editor.FLAG_ENABLE_CURSOR_DRAG = cursorDragEnabled;
+    }
+
+    @Test
     public void testSelectionHandles() {
         final String text = "abcd efg hijk lmn";
         onView(withId(R.id.textview)).perform(replaceText(text));
diff --git a/core/tests/coretests/src/android/widget/espresso/MouseUiController.java b/core/tests/coretests/src/android/widget/espresso/MouseUiController.java
index abee736..1928d25 100644
--- a/core/tests/coretests/src/android/widget/espresso/MouseUiController.java
+++ b/core/tests/coretests/src/android/widget/espresso/MouseUiController.java
@@ -70,6 +70,8 @@
         event.setSource(InputDevice.SOURCE_MOUSE);
         if (event.getActionMasked() != MotionEvent.ACTION_UP) {
             event.setButtonState(mButton);
+        } else {
+            event.setButtonState(0);
         }
         return mUiController.injectMotionEvent(event);
     }
diff --git a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
index 4808a0b..d4c9971 100644
--- a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
+++ b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
@@ -199,6 +199,86 @@
     }
 
     /**
+     * Returns an action that long presses then drags on handle from the current position to
+     * endIndex on the TextView.<br>
+     * <br>
+     * View constraints:
+     * <ul>
+     * <li>must be a TextView's drag-handle displayed on screen
+     * <ul>
+     *
+     * @param textView TextView the handle is on
+     * @param handleType Type of the handle
+     * @param endIndex The index of the TextView's text to end the drag at
+     */
+    public static ViewAction longPressAndDragHandle(TextView textView, Handle handleType,
+            int endIndex) {
+        return actionWithAssertions(
+                new DragAction(
+                        DragAction.Drag.LONG_PRESS,
+                        new CurrentHandleCoordinates(textView),
+                        new HandleCoordinates(textView, handleType, endIndex, true),
+                        Press.FINGER,
+                        Editor.HandleView.class));
+    }
+
+    /**
+     * Returns an action that long presses on the current handle.<br>
+     * <br>
+     * View constraints:
+     * <ul>
+     * <li>must be a TextView's drag-handle displayed on screen
+     * <ul>
+     *
+     * @param textView TextView the handle is on
+     */
+    public static ViewAction longPressHandle(TextView textView) {
+        return actionWithAssertions(
+                new ViewClickAction(Tap.LONG, new CurrentHandleCoordinates(textView),
+                        Press.FINGER));
+    }
+
+    /**
+     * Returns an action that double tap then drags on handle from the current position to
+     * endIndex on the TextView.<br>
+     * <br>
+     * View constraints:
+     * <ul>
+     * <li>must be a TextView's drag-handle displayed on screen
+     * <ul>
+     *
+     * @param textView TextView the handle is on
+     * @param handleType Type of the handle
+     * @param endIndex The index of the TextView's text to end the drag at
+     */
+    public static ViewAction doubleTapAndDragHandle(TextView textView, Handle handleType,
+            int endIndex) {
+        return actionWithAssertions(
+                new DragAction(
+                        DragAction.Drag.DOUBLE_TAP,
+                        new CurrentHandleCoordinates(textView),
+                        new HandleCoordinates(textView, handleType, endIndex, true),
+                        Press.FINGER,
+                        Editor.HandleView.class));
+    }
+
+    /**
+     * Returns an action that double tap on the current handle.<br>
+     * <br>
+     * View constraints:
+     * <ul>
+     * <li>must be a TextView's drag-handle displayed on screen
+     * <ul>
+     *
+     * @param textView TextView the handle is on
+     */
+    public static ViewAction doubleTapHandle(TextView textView) {
+        return actionWithAssertions(
+                new ViewClickAction(Tap.DOUBLE, new CurrentHandleCoordinates(textView),
+                        Press.FINGER));
+    }
+
+    /**
      * Returns an action that double taps then drags on text from startIndex to endIndex on the
      * TextView.<br>
      * <br>
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
index 82854e5..6784ede 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -348,7 +348,7 @@
         verify(mAlertDialog).show();
         verify(mAccessibilityManagerService, atLeastOnce()).getInstalledAccessibilityServiceList(
                 anyInt());
-        verify(mAccessibilityManagerService, times(0)).performAccessibilityShortcut();
+        verify(mAccessibilityManagerService, times(0)).performAccessibilityShortcut(null);
         verify(mFrameworkObjectProvider, times(0)).getTextToSpeech(any(), any());
     }
 
@@ -365,7 +365,7 @@
         assertEquals(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
                 mLayoutParams.privateFlags
                         & WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
-        verify(mAccessibilityManagerService, times(1)).performAccessibilityShortcut();
+        verify(mAccessibilityManagerService, times(1)).performAccessibilityShortcut(null);
     }
 
     @Test
@@ -433,7 +433,7 @@
 
         verifyZeroInteractions(mAlertDialogBuilder, mAlertDialog);
         verify(mToast).show();
-        verify(mAccessibilityManagerService).performAccessibilityShortcut();
+        verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
     }
 
     @Test
@@ -459,7 +459,7 @@
         when(mServiceInfo.loadSummary(any())).thenReturn(null);
         Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
         getController().performAccessibilityShortcut();
-        verify(mAccessibilityManagerService).performAccessibilityShortcut();
+        verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
     }
 
     @Test
@@ -471,7 +471,7 @@
         getController().performAccessibilityShortcut();
 
         verifyZeroInteractions(mToast);
-        verify(mAccessibilityManagerService).performAccessibilityShortcut();
+        verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
     }
 
     @Test
@@ -485,7 +485,7 @@
         getController().performAccessibilityShortcut();
 
         verifyZeroInteractions(mToast);
-        verify(mAccessibilityManagerService).performAccessibilityShortcut();
+        verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
     }
 
     @Test
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index c086421..411868d 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -31,6 +31,7 @@
 import static com.android.internal.app.ChooserListAdapter.CALLER_TARGET_SCORE_BOOST;
 import static com.android.internal.app.ChooserListAdapter.SHORTCUT_TARGET_SCORE_BOOST;
 import static com.android.internal.app.ChooserWrapperActivity.sOverrides;
+import static com.android.internal.app.MatcherUtils.first;
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.not;
@@ -63,6 +64,8 @@
 import android.graphics.drawable.Icon;
 import android.metrics.LogMaker;
 import android.net.Uri;
+import android.os.Bundle;
+import android.os.UserHandle;
 import android.service.chooser.ChooserTarget;
 
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -74,7 +77,11 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -302,6 +309,7 @@
         assertThat(activity.getIsSelected(), is(true));
     }
 
+    @Ignore // b/148158199
     @Test
     public void noResultsFromPackageManager() {
         when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
@@ -346,6 +354,9 @@
 
     @Test
     public void hasOtherProfileOneOption() throws Exception {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+
         Intent sendIntent = createSendTextIntent();
         List<ResolvedComponentInfo> resolvedComponentInfos =
                 createResolvedComponentsForTestWithOtherProfile(2);
@@ -372,9 +383,7 @@
         // Make a stable copy of the components as the original list may be modified
         List<ResolvedComponentInfo> stableCopy =
                 createResolvedComponentsForTestWithOtherProfile(2);
-        // Check that the "Other Profile" activity is put in the right spot
-        onView(withId(R.id.profile_button)).check(matches(
-                withText(stableCopy.get(0).getResolveInfoAt(0).activityInfo.name)));
+        waitForIdle();
         onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
                 .perform(click());
         waitForIdle();
@@ -383,6 +392,9 @@
 
     @Test
     public void hasOtherProfileTwoOptionsAndUserSelectsOne() throws Exception {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+
         Intent sendIntent = createSendTextIntent();
         List<ResolvedComponentInfo> resolvedComponentInfos =
                 createResolvedComponentsForTestWithOtherProfile(3);
@@ -411,9 +423,6 @@
         // Make a stable copy of the components as the original list may be modified
         List<ResolvedComponentInfo> stableCopy =
                 createResolvedComponentsForTestWithOtherProfile(3);
-        // Check that the "Other Profile" activity is put in the right spot
-        onView(withId(R.id.profile_button)).check(matches(
-                withText(stableCopy.get(0).getResolveInfoAt(0).activityInfo.name)));
         onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
                 .perform(click());
         waitForIdle();
@@ -422,6 +431,9 @@
 
     @Test
     public void hasLastChosenActivityAndOtherProfile() throws Exception {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+
         Intent sendIntent = createSendTextIntent();
         List<ResolvedComponentInfo> resolvedComponentInfos =
                 createResolvedComponentsForTestWithOtherProfile(3);
@@ -448,9 +460,6 @@
         // Make a stable copy of the components as the original list may be modified
         List<ResolvedComponentInfo> stableCopy =
                 createResolvedComponentsForTestWithOtherProfile(3);
-        // Check that the "Other Profile" activity is put in the right spot
-        onView(withId(R.id.profile_button)).check(matches(
-                withText(stableCopy.get(0).getResolveInfoAt(0).activityInfo.name)));
         onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
                 .perform(click());
         waitForIdle();
@@ -1161,6 +1170,123 @@
                 .getAllValues().get(2).getTaggedData(MetricsEvent.FIELD_RANKED_POSITION), is(-1));
     }
 
+    @Test
+    public void testWorkTab_displayedWhenWorkProfileUserAvailable() {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+        Intent sendIntent = createSendTextIntent();
+        sendIntent.setType("TestType");
+        markWorkProfileUserAvailable();
+
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+        waitForIdle();
+
+        onView(withId(R.id.tabs)).check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testWorkTab_hiddenWhenWorkProfileUserNotAvailable() {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+        Intent sendIntent = createSendTextIntent();
+        sendIntent.setType("TestType");
+
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+        waitForIdle();
+
+        onView(withId(R.id.tabs)).check(matches(not(isDisplayed())));
+    }
+
+    @Test
+    public void testWorkTab_eachTabUsesExpectedAdapter() {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+        int personalProfileTargets = 3;
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTest(personalProfileTargets);
+        int workProfileTargets = 4;
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(
+                workProfileTargets);
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos);
+        when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+        Intent sendIntent = createSendTextIntent();
+        sendIntent.setType("TestType");
+        markWorkProfileUserAvailable();
+
+        final ChooserWrapperActivity activity =
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+        waitForIdle();
+
+        assertThat(activity.getCurrentUserHandle().getIdentifier(), is(0));
+        // The work list adapter must only be filled when we open the work tab
+        assertThat(activity.getWorkListAdapter().getCount(), is(0));
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+        assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10));
+        assertThat(activity.getPersonalListAdapter().getCount(), is(personalProfileTargets));
+        assertThat(activity.getWorkListAdapter().getCount(), is(workProfileTargets));
+    }
+
+    @Test
+    public void testWorkTab_workProfileHasExpectedNumberOfTargets() {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+        markWorkProfileUserAvailable();
+        int workProfileTargets = 4;
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(workProfileTargets);
+        when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+        Intent sendIntent = createSendTextIntent();
+        sendIntent.setType("TestType");
+
+        final ChooserWrapperActivity activity =
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+        waitForIdle();
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+        waitForIdle();
+
+        assertThat(activity.getWorkListAdapter().getCount(), is(workProfileTargets));
+    }
+
+    @Ignore // b/148156663
+    @Test
+    public void testWorkTab_selectingWorkTabAppOpensAppInWorkProfile() throws InterruptedException {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+        markWorkProfileUserAvailable();
+        int workProfileTargets = 4;
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(workProfileTargets);
+        when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+        Intent sendIntent = createSendTextIntent();
+        sendIntent.setType("TestType");
+        ResolveInfo[] chosen = new ResolveInfo[1];
+        sOverrides.onSafelyStartCallback = targetInfo -> {
+            chosen[0] = targetInfo.getResolveInfo();
+            return true;
+        };
+
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+        waitForIdle();
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+        waitForIdle();
+        // wait for the share sheet to expand
+        Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+
+        onView(first(withText(workResolvedComponentInfos.get(0)
+                .getResolveInfoAt(0).activityInfo.applicationInfo.name)))
+                .perform(click());
+        waitForIdle();
+        assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0)));
+    }
+
     private Intent createSendTextIntent() {
         Intent sendIntent = new Intent();
         sendIntent.setAction(Intent.ACTION_SEND);
@@ -1224,6 +1350,15 @@
         return infoList;
     }
 
+    private List<ResolvedComponentInfo> createResolvedComponentsForTestWithUserId(
+            int numberOfResults, int userId) {
+        List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+        for (int i = 0; i < numberOfResults; i++) {
+            infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId));
+        }
+        return infoList;
+    }
+
     private List<ChooserTarget> createDirectShareTargets(int numberOfResults, String packageName) {
         Icon icon = Icon.createWithBitmap(createBitmap());
         String testTitle = "testTitle";
@@ -1308,4 +1443,8 @@
             assertEquals(cn.flattenToString(), ct.getComponentName().flattenToString());
         }
     }
+
+    private void markWorkProfileUserAvailable() {
+        sOverrides.workProfileUserHandle = UserHandle.of(10);
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index 2a10443..eee62bb 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -17,6 +17,7 @@
 package com.android.internal.app;
 
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import android.annotation.Nullable;
 import android.app.usage.UsageStatsManager;
@@ -29,6 +30,7 @@
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.UserHandle;
 import android.util.Size;
 
@@ -51,6 +53,19 @@
         return mChooserMultiProfilePagerAdapter.getActiveListAdapter();
     }
 
+    ChooserListAdapter getPersonalListAdapter() {
+        return ((ChooserGridAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(0))
+                .getListAdapter();
+    }
+
+    ChooserListAdapter getWorkListAdapter() {
+        if (mMultiProfilePagerAdapter.getInactiveListAdapter() == null) {
+            return null;
+        }
+        return ((ChooserGridAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(1))
+                .getListAdapter();
+    }
+
     boolean getIsSelected() { return mIsSuccessfullySelected; }
 
     UsageStatsManager getUsageStatsManager() {
@@ -79,7 +94,12 @@
 
     @Override
     protected ResolverListController createListController(UserHandle userHandle) {
-        return sOverrides.resolverListController;
+        if (userHandle == UserHandle.SYSTEM) {
+            when(sOverrides.resolverListController.getUserHandle()).thenReturn(UserHandle.SYSTEM);
+            return sOverrides.resolverListController;
+        }
+        when(sOverrides.workResolverListController.getUserHandle()).thenReturn(userHandle);
+        return sOverrides.workResolverListController;
     }
 
     @Override
@@ -144,6 +164,15 @@
                 resolveInfoPresentationGetter);
     }
 
+    @Override
+    protected UserHandle getWorkProfileUserHandle() {
+        return sOverrides.workProfileUserHandle;
+    }
+
+    protected UserHandle getCurrentUserHandle() {
+        return mMultiProfilePagerAdapter.getCurrentUserHandle();
+    }
+
     /**
      * We cannot directly mock the activity created since instrumentation creates it.
      * <p>
@@ -154,6 +183,7 @@
         public Function<PackageManager, PackageManager> createPackageManager;
         public Function<TargetInfo, Boolean> onSafelyStartCallback;
         public ResolverListController resolverListController;
+        public ResolverListController workResolverListController;
         public Boolean isVoiceInteraction;
         public boolean isImageType;
         public Cursor resolverCursor;
@@ -162,6 +192,7 @@
         public MetricsLogger metricsLogger;
         public int alternateProfileSetting;
         public Resources resources;
+        public UserHandle workProfileUserHandle;
 
         public void reset() {
             onSafelyStartCallback = null;
@@ -172,9 +203,11 @@
             resolverCursor = null;
             resolverForceException = false;
             resolverListController = mock(ResolverListController.class);
+            workResolverListController = mock(ResolverListController.class);
             metricsLogger = mock(MetricsLogger.class);
             alternateProfileSetting = 0;
             resources = null;
+            workProfileUserHandle = null;
         }
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/app/MatcherUtils.java b/core/tests/coretests/src/com/android/internal/app/MatcherUtils.java
new file mode 100644
index 0000000..a476631
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/MatcherUtils.java
@@ -0,0 +1,51 @@
+/*
+ * 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.app;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+
+/**
+ * Utils for helping with more customized matching options, for example matching the first
+ * occurrence of a set criteria.
+ */
+public class MatcherUtils {
+
+    /**
+     * Returns a {@link Matcher} which only matches the first occurrence of a set criteria.
+     */
+    static <T> Matcher<T> first(final Matcher<T> matcher) {
+        return new BaseMatcher<T>() {
+            boolean isFirstMatch = true;
+
+            @Override
+            public boolean matches(final Object item) {
+                if (isFirstMatch && matcher.matches(item)) {
+                    isFirstMatch = false;
+                    return true;
+                }
+                return false;
+            }
+
+            @Override
+            public void describeTo(final Description description) {
+                description.appendText("Returns the first matching item");
+            }
+        };
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index 923ce3e..42f7736 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -19,13 +19,17 @@
 import static androidx.test.espresso.Espresso.onView;
 import static androidx.test.espresso.action.ViewActions.click;
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed;
 import static androidx.test.espresso.matcher.ViewMatchers.isEnabled;
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
+import static com.android.internal.app.MatcherUtils.first;
 import static com.android.internal.app.ResolverDataProvider.createPackageManagerMockedInfo;
 import static com.android.internal.app.ResolverWrapperActivity.sOverrides;
 
+import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -33,6 +37,7 @@
 
 import android.content.Intent;
 import android.content.pm.ResolveInfo;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.view.View;
 import android.widget.RelativeLayout;
@@ -49,6 +54,9 @@
 import com.android.internal.app.ResolverListAdapter.ResolveInfoPresentationGetter;
 import com.android.internal.widget.ResolverDrawerLayout;
 
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Rule;
@@ -212,6 +220,9 @@
 
     @Test
     public void hasOtherProfileOneOption() throws Exception {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+
         Intent sendIntent = createSendImageIntent();
         List<ResolvedComponentInfo> resolvedComponentInfos =
                 createResolvedComponentsForTestWithOtherProfile(2);
@@ -237,9 +248,6 @@
         // Make a stable copy of the components as the original list may be modified
         List<ResolvedComponentInfo> stableCopy =
                 createResolvedComponentsForTestWithOtherProfile(2);
-        // Check that the "Other Profile" activity is put in the right spot
-        onView(withId(R.id.profile_button)).check(matches(
-                withText(stableCopy.get(0).getResolveInfoAt(0).activityInfo.name)));
         onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
                 .perform(click());
         onView(withId(R.id.button_once))
@@ -250,6 +258,9 @@
 
     @Test
     public void hasOtherProfileTwoOptionsAndUserSelectsOne() throws Exception {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+
         Intent sendIntent = createSendImageIntent();
         List<ResolvedComponentInfo> resolvedComponentInfos =
                 createResolvedComponentsForTestWithOtherProfile(3);
@@ -279,9 +290,6 @@
         List<ResolvedComponentInfo> stableCopy =
                 createResolvedComponentsForTestWithOtherProfile(2);
 
-        // Check that the "Other Profile" activity is put in the right spot
-        onView(withId(R.id.profile_button)).check(matches(
-                withText(stableCopy.get(0).getResolveInfoAt(0).activityInfo.name)));
         onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
                 .perform(click());
         onView(withId(R.id.button_once)).perform(click());
@@ -292,6 +300,9 @@
 
     @Test
     public void hasLastChosenActivityAndOtherProfile() throws Exception {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+
         // In this case we prefer the other profile and don't display anything about the last
         // chosen activity.
         Intent sendIntent = createSendImageIntent();
@@ -325,9 +336,6 @@
         List<ResolvedComponentInfo> stableCopy =
                 createResolvedComponentsForTestWithOtherProfile(2);
 
-        // Check that the "Other Profile" activity is put in the right spot
-        onView(withId(R.id.profile_button)).check(matches(
-                withText(stableCopy.get(0).getResolveInfoAt(0).activityInfo.name)));
         onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
                 .perform(click());
         onView(withId(R.id.button_once)).perform(click());
@@ -379,6 +387,222 @@
                 TextUtils.isEmpty(pg.getSubLabel()));
     }
 
+    @Test
+    public void testWorkTab_displayedWhenWorkProfileUserAvailable() {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+        Intent sendIntent = createSendImageIntent();
+        markWorkProfileUserAvailable();
+
+        mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+
+        onView(withId(R.id.tabs)).check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testWorkTab_hiddenWhenWorkProfileUserNotAvailable() {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+        Intent sendIntent = createSendImageIntent();
+
+        mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+
+        onView(withId(R.id.tabs)).check(matches(not(isDisplayed())));
+    }
+
+    @Test
+    public void testWorkTab_workTabListEmptyBeforeGoingToTab() {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTest(3);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos);
+        when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+        markWorkProfileUserAvailable();
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+
+        assertThat(activity.getCurrentUserHandle().getIdentifier(), is(0));
+        // The work list adapter must only be filled when we open the work tab
+        assertThat(activity.getWorkListAdapter().getCount(), is(0));
+    }
+
+    @Test
+    public void testWorkTab_workTabUsesExpectedAdapter() {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTest(3);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos);
+        when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+        markWorkProfileUserAvailable();
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+
+        assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10));
+        assertThat(activity.getWorkListAdapter().getCount(), is(4));
+    }
+
+    @Test
+    public void testWorkTab_personalTabUsesExpectedAdapter() {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTest(3);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos);
+        when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+        markWorkProfileUserAvailable();
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+
+        assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10));
+        assertThat(activity.getPersonalListAdapter().getCount(), is(3));
+    }
+
+    @Test
+    public void testWorkTab_workProfileHasExpectedNumberOfTargets() throws InterruptedException {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+        markWorkProfileUserAvailable();
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTest(3);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos);
+        when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        onView(withText(R.string.resolver_work_tab))
+                .perform(click());
+
+        waitForIdle();
+        assertThat(activity.getWorkListAdapter().getCount(), is(4));
+    }
+
+    @Test
+    public void testWorkTab_selectingWorkTabAppOpensAppInWorkProfile() throws InterruptedException {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+        markWorkProfileUserAvailable();
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTest(3);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos);
+        when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+        ResolveInfo[] chosen = new ResolveInfo[1];
+        sOverrides.onSafelyStartCallback = targetInfo -> {
+            chosen[0] = targetInfo.getResolveInfo();
+            return true;
+        };
+
+        mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        onView(withText(R.string.resolver_work_tab))
+                .perform(click());
+        waitForIdle();
+        // wait for the share sheet to expand
+        Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+        onView(first(allOf(withText(workResolvedComponentInfos.get(0)
+                .getResolveInfoAt(0).activityInfo.applicationInfo.name), isCompletelyDisplayed())))
+                .perform(click());
+        onView(withId(R.id.button_once))
+                .perform(click());
+
+        waitForIdle();
+        assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0)));
+    }
+
+    @Test
+    public void testWorkTab_noPersonalApps_workTabHasExpectedNumberOfTargets()
+            throws InterruptedException {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+        markWorkProfileUserAvailable();
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+        when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        onView(withText(R.string.resolver_work_tab))
+                .perform(click());
+
+        waitForIdle();
+        assertThat(activity.getWorkListAdapter().getCount(), is(4));
+    }
+
+    @Ignore // b/148156663
+    @Test
+    public void testWorkTab_noPersonalApps_canStartWorkApps()
+            throws InterruptedException {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+        markWorkProfileUserAvailable();
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+        when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+        ResolveInfo[] chosen = new ResolveInfo[1];
+        sOverrides.onSafelyStartCallback = targetInfo -> {
+            chosen[0] = targetInfo.getResolveInfo();
+            return true;
+        };
+
+        mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        onView(withText(R.string.resolver_work_tab))
+                .perform(click());
+        waitForIdle();
+        // wait for the share sheet to expand
+        Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+        onView(first(allOf(withText(workResolvedComponentInfos.get(0)
+                .getResolveInfoAt(0).activityInfo.applicationInfo.name), isCompletelyDisplayed())))
+                .perform(click());
+        onView(withId(R.id.button_once))
+                .perform(click());
+        waitForIdle();
+
+        assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0)));
+    }
+
     private Intent createSendImageIntent() {
         Intent sendIntent = new Intent();
         sendIntent.setAction(Intent.ACTION_SEND);
@@ -411,4 +635,8 @@
     private void waitForIdle() {
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
+
+    private void markWorkProfileUserAvailable() {
+        ResolverWrapperActivity.sOverrides.workProfileUserHandle = UserHandle.of(10);
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java b/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java
index 59634f6..d7db5f8 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java
@@ -46,6 +46,12 @@
                 createResolverIntent(i), createResolveInfo(i, USER_SOMEONE_ELSE));
     }
 
+    static ResolverActivity.ResolvedComponentInfo createResolvedComponentInfoWithOtherId(int i,
+            int userId) {
+        return new ResolverActivity.ResolvedComponentInfo(createComponentName(i),
+                createResolverIntent(i), createResolveInfo(i, userId));
+    }
+
     static ComponentName createComponentName(int i) {
         final String name = "component" + i;
         return new ComponentName("foo.bar." + name, name);
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
index c5d2cfa..36c8724 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
@@ -17,12 +17,14 @@
 package com.android.internal.app;
 
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import android.app.usage.UsageStatsManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.os.Bundle;
 import android.os.UserHandle;
 
 import com.android.internal.app.chooser.TargetInfo;
@@ -49,6 +51,17 @@
         return (ResolverWrapperAdapter) mMultiProfilePagerAdapter.getActiveListAdapter();
     }
 
+    ResolverListAdapter getPersonalListAdapter() {
+        return ((ResolverListAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(0));
+    }
+
+    ResolverListAdapter getWorkListAdapter() {
+        if (mMultiProfilePagerAdapter.getInactiveListAdapter() == null) {
+            return null;
+        }
+        return ((ResolverListAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(1));
+    }
+
     @Override
     public boolean isVoiceInteraction() {
         if (sOverrides.isVoiceInteraction != null) {
@@ -68,7 +81,12 @@
 
     @Override
     protected ResolverListController createListController(UserHandle userHandle) {
-        return sOverrides.resolverListController;
+        if (userHandle == UserHandle.SYSTEM) {
+            when(sOverrides.resolverListController.getUserHandle()).thenReturn(UserHandle.SYSTEM);
+            return sOverrides.resolverListController;
+        }
+        when(sOverrides.workResolverListController.getUserHandle()).thenReturn(userHandle);
+        return sOverrides.workResolverListController;
     }
 
     @Override
@@ -79,6 +97,20 @@
         return super.getPackageManager();
     }
 
+    protected UserHandle getCurrentUserHandle() {
+        return mMultiProfilePagerAdapter.getCurrentUserHandle();
+    }
+
+    @Override
+    protected UserHandle getWorkProfileUserHandle() {
+        return sOverrides.workProfileUserHandle;
+    }
+
+    @Override
+    public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
+        super.startActivityAsUser(intent, options, user);
+    }
+
     /**
      * We cannot directly mock the activity created since instrumentation creates it.
      * <p>
@@ -89,13 +121,17 @@
         public Function<PackageManager, PackageManager> createPackageManager;
         public Function<TargetInfo, Boolean> onSafelyStartCallback;
         public ResolverListController resolverListController;
+        public ResolverListController workResolverListController;
         public Boolean isVoiceInteraction;
+        public UserHandle workProfileUserHandle;
 
         public void reset() {
             onSafelyStartCallback = null;
             isVoiceInteraction = null;
             createPackageManager = null;
             resolverListController = mock(ResolverListController.class);
+            workResolverListController = mock(ResolverListController.class);
+            workProfileUserHandle = null;
         }
     }
 }
\ No newline at end of file
diff --git a/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java b/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java
index ffc925f..f108eb8 100644
--- a/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java
+++ b/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java
@@ -121,7 +121,12 @@
         AndroidFuture future2 = AndroidFuture.CREATOR.createFromParcel(parcel);
         ExecutionException executionException =
                 expectThrows(ExecutionException.class, future2::get);
-        assertThat(executionException.getCause()).isInstanceOf(UnsupportedOperationException.class);
+
+        Throwable cause = executionException.getCause();
+        String msg = cause.getMessage();
+        assertThat(cause).isInstanceOf(UnsupportedOperationException.class);
+        assertThat(msg).contains(getClass().getName());
+        assertThat(msg).contains("testWriteToParcel_Exception");
     }
 
     @Test
diff --git a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
index 6c2d630..916e2b5 100644
--- a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
+++ b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.policy;
 
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
@@ -94,6 +95,15 @@
     }
 
     @Test
+    public void layoutInDisplayCutoutMode_always() throws Exception {
+        createPhoneWindowWithTheme(R.style.LayoutInDisplayCutoutModeAlways);
+        installDecor();
+
+        assertThat(mPhoneWindow.getAttributes().layoutInDisplayCutoutMode,
+                is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS));
+    }
+
+    @Test
     public void testWindowBackground_colorLiteral() {
         createPhoneWindowWithTheme(R.style.WindowBackgroundColorLiteral);
         installDecor();
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index 66d84aa..9018320 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -79,138 +79,6 @@
 
     @Test
     @UiThreadTest
-    public void testWindowVisibilityChange_OnCreate() throws Exception {
-        try (ClientMockSession clientSession = new ClientMockSession()) {
-            ActivityClientRecord r = clientSession.stubActivityRecord();
-
-            clientSession.launchActivity(r);
-            assertEquals(ON_CREATE, r.getLifecycleState());
-
-            clientSession.changeVisibility(r, true);
-            assertEquals(ON_CREATE, r.getLifecycleState());
-
-            clientSession.changeVisibility(r, false);
-            assertEquals(ON_CREATE, r.getLifecycleState());
-        }
-    }
-
-    @Test
-    @UiThreadTest
-    public void testWindowVisibilityChange_OnCreate_Finished() throws Exception {
-        try (ClientMockSession clientSession = new ClientMockSession()) {
-            ActivityClientRecord r = clientSession.stubActivityRecord();
-
-            Activity activity = clientSession.launchActivity(r);
-            activity.finish();
-            assertEquals(ON_CREATE, r.getLifecycleState());
-
-            clientSession.changeVisibility(r, true);
-            assertEquals(ON_CREATE, r.getLifecycleState());
-
-            clientSession.changeVisibility(r, false);
-            assertEquals(ON_CREATE, r.getLifecycleState());
-        }
-    }
-
-    @Test
-    @UiThreadTest
-    public void testWindowVisibilityChange_OnStart() throws Exception {
-        try (ClientMockSession clientSession = new ClientMockSession()) {
-            ActivityClientRecord r = clientSession.stubActivityRecord();
-
-            clientSession.launchActivity(r);
-            clientSession.startActivity(r);
-            assertEquals(ON_START, r.getLifecycleState());
-
-            clientSession.changeVisibility(r, false);
-            assertEquals(ON_STOP, r.getLifecycleState());
-
-            clientSession.changeVisibility(r, true);
-            assertEquals(ON_START, r.getLifecycleState());
-        }
-    }
-
-    @Test
-    @UiThreadTest
-    public void testWindowVisibilityChange_OnStart_Finished() throws Exception {
-        try (ClientMockSession clientSession = new ClientMockSession()) {
-            ActivityClientRecord r = clientSession.stubActivityRecord();
-
-            Activity activity = clientSession.launchActivity(r);
-            clientSession.startActivity(r);
-            activity.finish();
-            assertEquals(ON_START, r.getLifecycleState());
-
-            clientSession.changeVisibility(r, false);
-            assertEquals(ON_STOP, r.getLifecycleState());
-
-            clientSession.changeVisibility(r, true);
-            assertEquals(ON_START, r.getLifecycleState());
-        }
-    }
-
-    @Test
-    @UiThreadTest
-    public void testWindowVisibilityChange_OnResume() throws Exception {
-        try (ClientMockSession clientSession = new ClientMockSession()) {
-            ActivityClientRecord r = clientSession.stubActivityRecord();
-
-            clientSession.launchActivity(r);
-            clientSession.startActivity(r);
-            clientSession.resumeActivity(r);
-            assertEquals(ON_RESUME, r.getLifecycleState());
-
-            clientSession.changeVisibility(r, false);
-            assertEquals(ON_STOP, r.getLifecycleState());
-
-            clientSession.changeVisibility(r, true);
-            assertEquals(ON_START, r.getLifecycleState());
-        }
-    }
-
-    @Test
-    @UiThreadTest
-    public void testWindowVisibilityChange_OnPause() throws Exception {
-        try (ClientMockSession clientSession = new ClientMockSession()) {
-            ActivityClientRecord r = clientSession.stubActivityRecord();
-
-            clientSession.launchActivity(r);
-            clientSession.startActivity(r);
-            clientSession.resumeActivity(r);
-            clientSession.pauseActivity(r);
-            assertEquals(ON_PAUSE, r.getLifecycleState());
-
-            clientSession.changeVisibility(r, false);
-            assertEquals(ON_STOP, r.getLifecycleState());
-
-            clientSession.changeVisibility(r, true);
-            assertEquals(ON_START, r.getLifecycleState());
-        }
-    }
-
-    @Test
-    @UiThreadTest
-    public void testWindowVisibilityChange_OnStop() throws Exception {
-        try (ClientMockSession clientSession = new ClientMockSession()) {
-            ActivityClientRecord r = clientSession.stubActivityRecord();
-
-            clientSession.launchActivity(r);
-            clientSession.startActivity(r);
-            clientSession.resumeActivity(r);
-            clientSession.pauseActivity(r);
-            clientSession.stopActivity(r);
-            assertEquals(ON_STOP, r.getLifecycleState());
-
-            clientSession.changeVisibility(r, true);
-            assertEquals(ON_START, r.getLifecycleState());
-
-            clientSession.changeVisibility(r, false);
-            assertEquals(ON_STOP, r.getLifecycleState());
-        }
-    }
-
-    @Test
-    @UiThreadTest
     public void testLifecycleAfterFinished_OnCreate() throws Exception {
         try (ClientMockSession clientSession = new ClientMockSession()) {
             ActivityClientRecord r = clientSession.stubActivityRecord();
@@ -308,7 +176,7 @@
         }
 
         private void startActivity(ActivityClientRecord r) {
-            mThread.handleStartActivity(r, null /* pendingActions */);
+            mThread.handleStartActivity(r.token, null /* pendingActions */);
         }
 
         private void resumeActivity(ActivityClientRecord r) {
@@ -323,7 +191,7 @@
         }
 
         private void stopActivity(ActivityClientRecord r) {
-            mThread.handleStopActivity(r.token, false /* show */, 0 /* configChanges */,
+            mThread.handleStopActivity(r.token, 0 /* configChanges */,
                     new PendingTransactionActions(), false /* finalStateRequest */, "test");
         }
 
@@ -332,10 +200,6 @@
                     false /* getNonConfigInstance */, "test");
         }
 
-        private void changeVisibility(ActivityClientRecord r, boolean show) {
-            mThread.handleWindowVisibility(r.token, show);
-        }
-
         private ActivityClientRecord stubActivityRecord() {
             ComponentName component = new ComponentName(
                     InstrumentationRegistry.getInstrumentation().getContext(), TestActivity.class);
diff --git a/core/tests/overlaytests/host/Android.bp b/core/tests/overlaytests/host/Android.bp
new file mode 100644
index 0000000..a2fcef5
--- /dev/null
+++ b/core/tests/overlaytests/host/Android.bp
@@ -0,0 +1,30 @@
+// 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.
+
+java_test_host {
+    name: "OverlayHostTests",
+    srcs: ["src/**/*.java"],
+    libs: ["tradefed"],
+    test_suites: ["general-tests"],
+    target_required: [
+        "OverlayHostTests_NonPlatformSignatureOverlay",
+        "OverlayHostTests_PlatformSignatureStaticOverlay",
+        "OverlayHostTests_PlatformSignatureOverlay",
+        "OverlayHostTests_UpdateOverlay",
+        "OverlayHostTests_FrameworkOverlayV1",
+        "OverlayHostTests_FrameworkOverlayV2",
+        "OverlayHostTests_AppOverlayV1",
+        "OverlayHostTests_AppOverlayV2",
+    ],
+}
diff --git a/core/tests/overlaytests/host/Android.mk b/core/tests/overlaytests/host/Android.mk
index e7348d5..d58d939 100644
--- a/core/tests/overlaytests/host/Android.mk
+++ b/core/tests/overlaytests/host/Android.mk
@@ -14,23 +14,6 @@
 
 LOCAL_PATH := $(call my-dir)
 
-include $(CLEAR_VARS)
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_MODULE_TAGS := tests
-LOCAL_MODULE := OverlayHostTests
-LOCAL_JAVA_LIBRARIES := tradefed
-LOCAL_COMPATIBILITY_SUITE := general-tests
-LOCAL_TARGET_REQUIRED_MODULES := \
-    OverlayHostTests_NonPlatformSignatureOverlay \
-    OverlayHostTests_PlatformSignatureStaticOverlay \
-    OverlayHostTests_PlatformSignatureOverlay \
-    OverlayHostTests_UpdateOverlay \
-    OverlayHostTests_FrameworkOverlayV1 \
-    OverlayHostTests_FrameworkOverlayV2 \
-    OverlayHostTests_AppOverlayV1 \
-    OverlayHostTests_AppOverlayV2
-include $(BUILD_HOST_JAVA_LIBRARY)
-
 # Include to build test-apps.
 include $(call all-makefiles-under,$(LOCAL_PATH))
 
diff --git a/core/tests/overlaytests/remount/TEST_MAPPING b/core/tests/overlaytests/remount/TEST_MAPPING
new file mode 100644
index 0000000..54dd431
--- /dev/null
+++ b/core/tests/overlaytests/remount/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name" : "OverlayRemountedTest"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/core/tests/overlaytests/remount/host/Android.bp b/core/tests/overlaytests/remount/host/Android.bp
new file mode 100644
index 0000000..3825c55
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/Android.bp
@@ -0,0 +1,28 @@
+// 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.
+
+java_test_host {
+    name: "OverlayRemountedTest",
+    srcs: ["src/**/*.java"],
+    libs: [
+        "tradefed",
+        "junit",
+    ],
+    test_suites: ["general-tests"],
+    java_resources: [
+        ":OverlayRemountedTest_SharedLibrary",
+        ":OverlayRemountedTest_SharedLibraryOverlay",
+        ":OverlayRemountedTest_Target",
+    ],
+}
diff --git a/core/tests/overlaytests/remount/host/AndroidTest.xml b/core/tests/overlaytests/remount/host/AndroidTest.xml
new file mode 100644
index 0000000..087b731
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?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.
+  -->
+
+<configuration description="Test module config for OverlayRemountedTest">
+    <option name="test-tag" value="OverlayRemountedTest" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+
+    <test class="com.android.tradefed.testtype.HostTest">
+        <option name="jar" value="OverlayRemountedTest.jar" />
+    </test>
+</configuration>
diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java
new file mode 100644
index 0000000..06b2ac8
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.overlaytest.remounted;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class OverlaySharedLibraryTest extends BaseHostJUnit4Test {
+    private static final String TARGET_APK = "OverlayRemountedTest_Target.apk";
+    private static final String TARGET_PACKAGE = "com.android.overlaytest.remounted.target";
+    private static final String SHARED_LIBRARY_APK =
+            "OverlayRemountedTest_SharedLibrary.apk";
+    private static final String SHARED_LIBRARY_PACKAGE =
+            "com.android.overlaytest.remounted.shared_library";
+    private static final String SHARED_LIBRARY_OVERLAY_APK =
+            "OverlayRemountedTest_SharedLibraryOverlay.apk";
+    private static final String SHARED_LIBRARY_OVERLAY_PACKAGE =
+            "com.android.overlaytest.remounted.shared_library.overlay";
+
+    public final TemporaryFolder temporaryFolder = new TemporaryFolder();
+    public final SystemPreparer preparer = new SystemPreparer(temporaryFolder, this::getDevice);
+
+    @Rule
+    public final RuleChain ruleChain = RuleChain.outerRule(temporaryFolder).around(preparer);
+
+    @Before
+    public void startBefore() throws DeviceNotAvailableException {
+        getDevice().waitForDeviceAvailable();
+    }
+
+    @Test
+    public void testSharedLibrary() throws Exception {
+        final String targetResource = resourceName(TARGET_PACKAGE, "bool",
+                "uses_shared_library_overlaid");
+        final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool",
+                "shared_library_overlaid");
+
+        preparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk")
+                .installResourceApk(SHARED_LIBRARY_OVERLAY_APK, SHARED_LIBRARY_OVERLAY_PACKAGE)
+                .reboot()
+                .setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, false)
+                .installResourceApk(TARGET_APK, TARGET_PACKAGE);
+
+        // The shared library resource is not currently overlaid.
+        assertResource(targetResource, "false");
+        assertResource(libraryResource, "false");
+
+        // Overlay the shared library resource.
+        preparer.setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true);
+        assertResource(targetResource, "true");
+        assertResource(libraryResource, "true");
+    }
+
+    @Test
+    public void testSharedLibraryPreEnabled() throws Exception {
+        final String targetResource = resourceName(TARGET_PACKAGE, "bool",
+                "uses_shared_library_overlaid");
+        final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool",
+                "shared_library_overlaid");
+
+        preparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk")
+                .installResourceApk(SHARED_LIBRARY_OVERLAY_APK, SHARED_LIBRARY_OVERLAY_PACKAGE)
+                .setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true)
+                .reboot()
+                .installResourceApk(TARGET_APK, TARGET_PACKAGE);
+
+        assertResource(targetResource, "true");
+        assertResource(libraryResource, "true");
+    }
+
+    /** Builds the full name of a resource in the form package:type/entry. */
+    String resourceName(String pkg, String type, String entry) {
+        return String.format("%s:%s/%s", pkg, type, entry);
+    }
+
+    void assertResource(String resourceName, String expectedValue)
+            throws DeviceNotAvailableException {
+        final String result = getDevice().executeShellCommand(
+                String.format("cmd overlay lookup %s %s", TARGET_PACKAGE, resourceName));
+        assertTrue(String.format("expected: <[%s]> in: <[%s]>", expectedValue, result),
+                result.equals(expectedValue + "\n") ||
+                result.endsWith("-> " + expectedValue + "\n"));
+    }
+}
diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java
new file mode 100644
index 0000000..8696091
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java
@@ -0,0 +1,157 @@
+/*
+ * 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.overlaytest.remounted;
+
+import static org.junit.Assert.assertTrue;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+
+import org.junit.Assert;
+import org.junit.rules.ExternalResource;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeoutException;
+
+class SystemPreparer extends ExternalResource {
+    private static final long OVERLAY_ENABLE_TIMEOUT_MS = 30000;
+
+    // The paths of the files pushed onto the device through this rule.
+    private ArrayList<String> mPushedFiles = new ArrayList<>();
+
+    // The package names of packages installed through this rule.
+    private ArrayList<String> mInstalledPackages = new ArrayList<>();
+
+    private final TemporaryFolder mHostTempFolder;
+    private final DeviceProvider mDeviceProvider;
+
+    SystemPreparer(TemporaryFolder hostTempFolder, DeviceProvider deviceProvider) {
+        mHostTempFolder = hostTempFolder;
+        mDeviceProvider = deviceProvider;
+    }
+
+    /** Copies a file within the host test jar to a path on device. */
+    SystemPreparer pushResourceFile(String resourcePath,
+            String outputPath) throws DeviceNotAvailableException, IOException {
+        final ITestDevice device = mDeviceProvider.getDevice();
+        device.executeAdbCommand("remount");
+        assertTrue(device.pushFile(copyResourceToTemp(resourcePath), outputPath));
+        mPushedFiles.add(outputPath);
+        return this;
+    }
+
+    /** Installs an APK within the host test jar onto the device. */
+    SystemPreparer installResourceApk(String resourcePath, String packageName)
+            throws DeviceNotAvailableException, IOException {
+        final ITestDevice device = mDeviceProvider.getDevice();
+        final File tmpFile = copyResourceToTemp(resourcePath);
+        final String result = device.installPackage(tmpFile, true);
+        Assert.assertNull(result);
+        mInstalledPackages.add(packageName);
+        return this;
+    }
+
+    /** Sets the enable state of an overlay pacakage. */
+    SystemPreparer setOverlayEnabled(String packageName, boolean enabled)
+            throws ExecutionException, DeviceNotAvailableException {
+        final ITestDevice device = mDeviceProvider.getDevice();
+
+        // Wait for the overlay to change its enabled state.
+        final FutureTask<Boolean> enabledListener = new FutureTask<>(() -> {
+            while (true) {
+                device.executeShellCommand(String.format("cmd overlay %s %s",
+                        enabled ? "enable" : "disable", packageName));
+
+                final String result = device.executeShellCommand("cmd overlay dump " + packageName);
+                final int startIndex = result.indexOf("mIsEnabled");
+                final int endIndex = result.indexOf('\n', startIndex);
+                if (result.substring(startIndex, endIndex).contains((enabled) ? "true" : "false")) {
+                    return true;
+                }
+            }
+        });
+
+        final Executor executor = (cmd) -> new Thread(cmd).start();
+        executor.execute(enabledListener);
+        try {
+            enabledListener.get(OVERLAY_ENABLE_TIMEOUT_MS, MILLISECONDS);
+        } catch (InterruptedException ignored) {
+        } catch (TimeoutException e) {
+            throw new IllegalStateException(device.executeShellCommand("cmd overlay list"));
+        }
+
+        return this;
+    }
+
+    /** Restarts the device and waits until after boot is completed. */
+    SystemPreparer reboot() throws DeviceNotAvailableException {
+        final ITestDevice device = mDeviceProvider.getDevice();
+        device.reboot();
+        return this;
+    }
+
+    /** Copies a file within the host test jar to a temporary file on the host machine. */
+    private File copyResourceToTemp(String resourcePath) throws IOException {
+        final File tempFile = mHostTempFolder.newFile(resourcePath);
+        final ClassLoader classLoader = getClass().getClassLoader();
+        try (InputStream assetIs = classLoader.getResource(resourcePath).openStream();
+             FileOutputStream assetOs = new FileOutputStream(tempFile)) {
+            if (assetIs == null) {
+                throw new IllegalStateException("Failed to find resource " + resourcePath);
+            }
+
+            int b;
+            while ((b = assetIs.read()) >= 0) {
+                assetOs.write(b);
+            }
+        }
+
+        return tempFile;
+    }
+
+    /** Removes installed packages and files that were pushed to the device. */
+    @Override
+    protected void after() {
+        final ITestDevice device = mDeviceProvider.getDevice();
+        try {
+            device.executeAdbCommand("remount");
+            for (final String file : mPushedFiles) {
+                device.deleteFile(file);
+            }
+            for (final String packageName : mInstalledPackages) {
+                device.uninstallPackage(packageName);
+            }
+            device.reboot();
+        } catch (DeviceNotAvailableException e) {
+            Assert.fail(e.toString());
+        }
+    }
+
+    interface DeviceProvider {
+        ITestDevice getDevice();
+    }
+}
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp
new file mode 100644
index 0000000..ffb0572
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp
@@ -0,0 +1,19 @@
+// 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.
+
+android_test_helper_app {
+    name: "OverlayRemountedTest_SharedLibrary",
+    sdk_version: "current",
+    aaptflags: ["--shared-lib"],
+}
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml
new file mode 100644
index 0000000..06e3f6a
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/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.overlaytest.remounted.shared_library">
+    <application>
+        <library android:name="com.android.overlaytest.remounted.shared_library" />
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml
new file mode 100644
index 0000000..1b06f6d
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml
@@ -0,0 +1,24 @@
+<?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.
+  -->
+
+<resources>
+    <overlayable name="TestResources">
+        <policy type="public">
+            <item type="bool" name="shared_library_overlaid" />
+        </policy>
+    </overlayable>
+</resources>
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml
new file mode 100644
index 0000000..5b9db16
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml
@@ -0,0 +1,20 @@
+<?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.
+  -->
+
+<resources>
+    <public type="bool" name="shared_library_overlaid" id="0x00050001"/>
+</resources>
\ No newline at end of file
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml
new file mode 100644
index 0000000..2dc47a7
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml
@@ -0,0 +1,20 @@
+<?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.
+  -->
+
+<resources>
+    <bool name="shared_library_overlaid">false</bool>
+</resources>
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp
new file mode 100644
index 0000000..0d29aec
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp
@@ -0,0 +1,18 @@
+// 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.
+
+android_test_helper_app {
+    name: "OverlayRemountedTest_SharedLibraryOverlay",
+    sdk_version: "current",
+}
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..53a4e61
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/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.overlaytest.remounted.shared_library.overlay">
+    <application android:hasCode="false" />
+    <overlay android:targetPackage="com.android.overlaytest.remounted.shared_library"
+             android:targetName="TestResources" />
+</manifest>
\ No newline at end of file
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml
new file mode 100644
index 0000000..f66448a
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml
@@ -0,0 +1,20 @@
+<?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.
+  -->
+
+<resources>
+    <bool name="shared_library_overlaid">true</bool>
+</resources>
diff --git a/core/tests/overlaytests/remount/target/Android.bp b/core/tests/overlaytests/remount/target/Android.bp
new file mode 100644
index 0000000..83f9f28
--- /dev/null
+++ b/core/tests/overlaytests/remount/target/Android.bp
@@ -0,0 +1,20 @@
+// 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.
+
+android_test_helper_app {
+    name: "OverlayRemountedTest_Target",
+    srcs: ["src/**/*.java"],
+    sdk_version: "test_current",
+    libs: ["OverlayRemountedTest_SharedLibrary"],
+}
diff --git a/core/tests/overlaytests/remount/target/AndroidManifest.xml b/core/tests/overlaytests/remount/target/AndroidManifest.xml
new file mode 100644
index 0000000..dc07dca
--- /dev/null
+++ b/core/tests/overlaytests/remount/target/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?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.overlaytest.remounted.target">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="com.android.overlaytest.remounted.shared_library"
+                      android:required="true" />
+    </application>
+</manifest>
diff --git a/core/tests/overlaytests/remount/target/res/values/values.xml b/core/tests/overlaytests/remount/target/res/values/values.xml
new file mode 100644
index 0000000..b5f444a
--- /dev/null
+++ b/core/tests/overlaytests/remount/target/res/values/values.xml
@@ -0,0 +1,20 @@
+<?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.
+  -->
+
+<resources xmlns:sharedlib="http://schemas.android.com/apk/res/com.android.overlaytest.remounted.shared_library">
+    <bool name="uses_shared_library_overlaid">@sharedlib:bool/shared_library_overlaid</bool>
+</resources>
diff --git a/core/tests/utiltests/src/com/android/internal/util/ConnectivityUtilTest.java b/core/tests/utiltests/src/com/android/internal/util/ConnectivityUtilTest.java
new file mode 100644
index 0000000..0809f69
--- /dev/null
+++ b/core/tests/utiltests/src/com/android/internal/util/ConnectivityUtilTest.java
@@ -0,0 +1,287 @@
+/*
+ * 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.util;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.location.LocationManager;
+import android.os.Binder;
+import android.os.Build;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.HashMap;
+
+/** Unit tests for {@link ConnectivityUtil}. */
+public class ConnectivityUtilTest {
+
+    public static final String TAG = "ConnectivityUtilTest";
+
+    // Mock objects for testing
+    @Mock private Context mMockContext;
+    @Mock private PackageManager mMockPkgMgr;
+    @Mock private ApplicationInfo mMockApplInfo;
+    @Mock private AppOpsManager mMockAppOps;
+    @Mock private UserManager mMockUserManager;
+    @Mock private LocationManager mLocationManager;
+
+    private static final String TEST_PKG_NAME = "com.google.somePackage";
+    private static final String TEST_FEATURE_ID = "com.google.someFeature";
+    private static final int MANAGED_PROFILE_UID = 1100000;
+    private static final int OTHER_USER_UID = 1200000;
+
+    private final String mInteractAcrossUsersFullPermission =
+            "android.permission.INTERACT_ACROSS_USERS_FULL";
+    private final String mManifestStringCoarse =
+            Manifest.permission.ACCESS_COARSE_LOCATION;
+    private final String mManifestStringFine =
+            Manifest.permission.ACCESS_FINE_LOCATION;
+
+    // Test variables
+    private int mWifiScanAllowApps;
+    private int mUid;
+    private int mCoarseLocationPermission;
+    private int mAllowCoarseLocationApps;
+    private int mFineLocationPermission;
+    private int mAllowFineLocationApps;
+    private int mCurrentUser;
+    private boolean mIsLocationEnabled;
+    private boolean mThrowSecurityException;
+    private Answer<Integer> mReturnPermission;
+    private HashMap<String, Integer> mPermissionsList = new HashMap<String, Integer>();
+
+    private class TestConnectivityUtil extends ConnectivityUtil {
+
+        TestConnectivityUtil(Context context) {
+            super(context);
+        }
+
+        @Override
+        protected int getCurrentUser() {
+            return mCurrentUser;
+        }
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        initTestVars();
+    }
+
+    private void setupMocks() throws Exception {
+        when(mMockPkgMgr.getApplicationInfoAsUser(eq(TEST_PKG_NAME), eq(0), any()))
+                .thenReturn(mMockApplInfo);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPkgMgr);
+        when(mMockAppOps.noteOp(AppOpsManager.OPSTR_WIFI_SCAN, mUid, TEST_PKG_NAME,
+                TEST_FEATURE_ID, null)).thenReturn(mWifiScanAllowApps);
+        when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_COARSE_LOCATION), eq(mUid),
+                eq(TEST_PKG_NAME), eq(TEST_FEATURE_ID), nullable(String.class)))
+                .thenReturn(mAllowCoarseLocationApps);
+        when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_FINE_LOCATION), eq(mUid),
+                eq(TEST_PKG_NAME), eq(TEST_FEATURE_ID), nullable(String.class)))
+                .thenReturn(mAllowFineLocationApps);
+        if (mThrowSecurityException) {
+            doThrow(new SecurityException("Package " + TEST_PKG_NAME + " doesn't belong"
+                    + " to application bound to user " + mUid))
+                    .when(mMockAppOps).checkPackage(mUid, TEST_PKG_NAME);
+        }
+        when(mMockContext.getSystemService(Context.APP_OPS_SERVICE))
+                .thenReturn(mMockAppOps);
+        when(mMockContext.getSystemService(Context.USER_SERVICE))
+                .thenReturn(mMockUserManager);
+        when(mMockContext.getSystemService(Context.LOCATION_SERVICE)).thenReturn(mLocationManager);
+    }
+
+    private void setupTestCase() throws Exception {
+        setupMocks();
+        setupMockInterface();
+    }
+
+    private void initTestVars() {
+        mPermissionsList.clear();
+        mReturnPermission = createPermissionAnswer();
+        mWifiScanAllowApps = AppOpsManager.MODE_ERRORED;
+        mUid = OTHER_USER_UID;
+        mThrowSecurityException = true;
+        mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.M;
+        mIsLocationEnabled = false;
+        mCurrentUser = UserHandle.USER_SYSTEM;
+        mCoarseLocationPermission = PackageManager.PERMISSION_DENIED;
+        mFineLocationPermission = PackageManager.PERMISSION_DENIED;
+        mAllowCoarseLocationApps = AppOpsManager.MODE_ERRORED;
+        mAllowFineLocationApps = AppOpsManager.MODE_ERRORED;
+    }
+
+    private void setupMockInterface() {
+        Binder.restoreCallingIdentity((((long) mUid) << 32) | Binder.getCallingPid());
+        doAnswer(mReturnPermission).when(mMockContext).checkPermission(
+                anyString(), anyInt(), anyInt());
+        when(mMockUserManager.isSameProfileGroup(UserHandle.SYSTEM,
+                UserHandle.getUserHandleForUid(MANAGED_PROFILE_UID)))
+                .thenReturn(true);
+        when(mMockContext.checkPermission(mManifestStringCoarse, -1, mUid))
+                .thenReturn(mCoarseLocationPermission);
+        when(mMockContext.checkPermission(mManifestStringFine, -1, mUid))
+                .thenReturn(mFineLocationPermission);
+        when(mLocationManager.isLocationEnabledForUser(any())).thenReturn(mIsLocationEnabled);
+    }
+
+    private Answer<Integer> createPermissionAnswer() {
+        return new Answer<Integer>() {
+            @Override
+            public Integer answer(InvocationOnMock invocation) {
+                int myUid = (int) invocation.getArguments()[1];
+                String myPermission = (String) invocation.getArguments()[0];
+                mPermissionsList.get(myPermission);
+                if (mPermissionsList.containsKey(myPermission)) {
+                    int uid = mPermissionsList.get(myPermission);
+                    if (myUid == uid) {
+                        return PackageManager.PERMISSION_GRANTED;
+                    }
+                }
+                return PackageManager.PERMISSION_DENIED;
+            }
+        };
+    }
+
+    @Test
+    public void testEnforceLocationPermission_HasAllPermissions_BeforeQ() throws Exception {
+        mIsLocationEnabled = true;
+        mThrowSecurityException = false;
+        mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED;
+        mAllowCoarseLocationApps = AppOpsManager.MODE_ALLOWED;
+        mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+        mUid = mCurrentUser;
+        setupTestCase();
+        new TestConnectivityUtil(mMockContext)
+                .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
+    }
+
+    @Test
+    public void testEnforceLocationPermission_HasAllPermissions_AfterQ() throws Exception {
+        mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.Q;
+        mIsLocationEnabled = true;
+        mThrowSecurityException = false;
+        mUid = mCurrentUser;
+        mFineLocationPermission = PackageManager.PERMISSION_GRANTED;
+        mAllowFineLocationApps = AppOpsManager.MODE_ALLOWED;
+        mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+        setupTestCase();
+        new TestConnectivityUtil(mMockContext)
+                .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
+    }
+
+    @Test
+    public void testEnforceLocationPermission_PkgNameAndUidMismatch() throws Exception {
+        mThrowSecurityException = true;
+        mIsLocationEnabled = true;
+        mFineLocationPermission = PackageManager.PERMISSION_GRANTED;
+        mAllowFineLocationApps = AppOpsManager.MODE_ALLOWED;
+        mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+        setupTestCase();
+
+        assertThrows(SecurityException.class,
+                () -> new TestConnectivityUtil(mMockContext)
+                        .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
+    }
+
+    @Test
+    public void testenforceCanAccessScanResults_UserOrProfileNotCurrent() throws Exception {
+        mIsLocationEnabled = true;
+        mThrowSecurityException = false;
+        mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED;
+        mAllowCoarseLocationApps = AppOpsManager.MODE_ALLOWED;
+        mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+        setupTestCase();
+
+        assertThrows(SecurityException.class,
+                () -> new TestConnectivityUtil(mMockContext)
+                        .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
+    }
+
+    @Test
+    public void testenforceCanAccessScanResults_NoCoarseLocationPermission() throws Exception {
+        mThrowSecurityException = false;
+        mIsLocationEnabled = true;
+        setupTestCase();
+        assertThrows(SecurityException.class,
+                () -> new TestConnectivityUtil(mMockContext)
+                        .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
+    }
+
+    @Test
+    public void testenforceCanAccessScanResults_NoFineLocationPermission() throws Exception {
+        mThrowSecurityException = false;
+        mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.Q;
+        mIsLocationEnabled = true;
+        mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED;
+        mAllowFineLocationApps = AppOpsManager.MODE_ERRORED;
+        mUid = MANAGED_PROFILE_UID;
+        setupTestCase();
+
+        assertThrows(SecurityException.class,
+                () -> new TestConnectivityUtil(mMockContext)
+                        .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
+        verify(mMockAppOps, never()).noteOp(anyInt(), anyInt(), anyString());
+    }
+
+    @Test
+    public void testenforceCanAccessScanResults_LocationModeDisabled() throws Exception {
+        mThrowSecurityException = false;
+        mUid = MANAGED_PROFILE_UID;
+        mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+        mPermissionsList.put(mInteractAcrossUsersFullPermission, mUid);
+        mIsLocationEnabled = false;
+
+        setupTestCase();
+
+        assertThrows(SecurityException.class,
+                () -> new TestConnectivityUtil(mMockContext)
+                        .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
+    }
+
+    private static void assertThrows(Class<? extends Exception> exceptionClass, Runnable r) {
+        try {
+            r.run();
+            Assert.fail("Expected " + exceptionClass + " to be thrown.");
+        } catch (Exception exception) {
+            assertTrue(exceptionClass.isInstance(exception));
+        }
+    }
+}
diff --git a/core/xsd/permission.xsd b/core/xsd/permission.xsd
index cc01a31..5435047 100644
--- a/core/xsd/permission.xsd
+++ b/core/xsd/permission.xsd
@@ -46,6 +46,7 @@
                 <xs:element name="hidden-api-whitelisted-app" type="hidden-api-whitelisted-app"/>
                 <xs:element name="allow-association" type="allow-association"/>
                 <xs:element name="bugreport-whitelisted" type="bugreport-whitelisted"/>
+                <xs:element name="app-data-isolation-whitelisted-app" type="app-data-isolation-whitelisted-app"/>
             </xs:choice>
         </xs:complexType>
     </xs:element>
@@ -161,6 +162,9 @@
         <xs:attribute name="target" type="xs:string"/>
         <xs:attribute name="allowed" type="xs:string"/>
     </xs:complexType>
+    <xs:complexType name="app-data-isolation-whitelisted-app">
+        <xs:attribute name="package" type="xs:string"/>
+    </xs:complexType>
     <xs:complexType name="bugreport-whitelisted">
         <xs:attribute name="package" type="xs:string"/>
     </xs:complexType>
diff --git a/core/xsd/schema/current.txt b/core/xsd/schema/current.txt
index 771c1df..c36c422 100644
--- a/core/xsd/schema/current.txt
+++ b/core/xsd/schema/current.txt
@@ -45,6 +45,12 @@
     method public void set_package(String);
   }
 
+  public class AppDataIsolationWhitelistedApp {
+    ctor public AppDataIsolationWhitelistedApp();
+    method public String get_package();
+    method public void set_package(String);
+  }
+
   public class AppLink {
     ctor public AppLink();
     method public String get_package();
@@ -160,6 +166,7 @@
     method public java.util.List<com.android.xml.permission.configfile.AllowInPowerSaveExceptIdle> getAllowInPowerSaveExceptIdle_optional();
     method public java.util.List<com.android.xml.permission.configfile.AllowInPowerSave> getAllowInPowerSave_optional();
     method public java.util.List<com.android.xml.permission.configfile.AllowUnthrottledLocation> getAllowUnthrottledLocation_optional();
+    method public java.util.List<com.android.xml.permission.configfile.AppDataIsolationWhitelistedApp> getAppDataIsolationWhitelistedApp_optional();
     method public java.util.List<com.android.xml.permission.configfile.AppLink> getAppLink_optional();
     method public java.util.List<com.android.xml.permission.configfile.AssignPermission> getAssignPermission_optional();
     method public java.util.List<com.android.xml.permission.configfile.BackupTransportWhitelistedService> getBackupTransportWhitelistedService_optional();
diff --git a/data/etc/OWNERS b/data/etc/OWNERS
index ea66ee3..70d4678 100644
--- a/data/etc/OWNERS
+++ b/data/etc/OWNERS
@@ -1 +1 @@
-per-file privapp-permissions-platform.xml = hackbod@android.com, jsharkey@android.com, svetoslavganov@google.com, toddke@google.com, yamasani@google.com, cbrubaker@google.com, jeffv@google.com, moltmann@google.com
+per-file privapp-permissions-platform.xml = hackbod@android.com, jsharkey@android.com, svetoslavganov@google.com, toddke@google.com, yamasani@google.com, cbrubaker@google.com, jeffv@google.com, moltmann@google.com, lorenzo@google.com
diff --git a/data/etc/car/Android.bp b/data/etc/car/Android.bp
index 26a40d3..dfb7a16 100644
--- a/data/etc/car/Android.bp
+++ b/data/etc/car/Android.bp
@@ -135,3 +135,10 @@
     src: "com.android.car.floatingcardslauncher.xml",
     filename_from_src: true,
 }
+
+prebuilt_etc {
+    name: "privapp_whitelist_com.android.car.ui.paintbooth",
+    sub_dir: "permissions",
+    src: "com.android.car.ui.paintbooth.xml",
+    filename_from_src: true,
+}
diff --git a/data/etc/car/com.android.car.developeroptions.xml b/data/etc/car/com.android.car.developeroptions.xml
index 5f5e908..7204898 100644
--- a/data/etc/car/com.android.car.developeroptions.xml
+++ b/data/etc/car/com.android.car.developeroptions.xml
@@ -40,6 +40,7 @@
         <permission name="android.permission.MOVE_PACKAGE"/>
         <permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
         <permission name="android.permission.PACKAGE_USAGE_STATS"/>
+        <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
         <permission name="android.permission.READ_SEARCH_INDEXABLES"/>
         <permission name="android.permission.REBOOT"/>
         <permission name="android.permission.REQUEST_NETWORK_SCORES"/>
diff --git a/data/etc/car/com.android.car.ui.paintbooth.xml b/data/etc/car/com.android.car.ui.paintbooth.xml
new file mode 100644
index 0000000..11bf304
--- /dev/null
+++ b/data/etc/car/com.android.car.ui.paintbooth.xml
@@ -0,0 +1,29 @@
+<?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.ui.paintbooth">
+        <!-- For enabling/disabling, and getting list of RROs -->
+        <permission name="android.permission.CHANGE_OVERLAY_PACKAGES"/>
+        <!-- For showing the current active activity -->
+        <permission name="android.permission.REAL_GET_TASKS"/>
+        <!-- For getting list of RROs for current user -->
+        <permission name="android.permission.MANAGE_USERS"/>
+        <!-- For getting list of RROs for current user-->
+        <permission name="android.permission.INTERACT_ACROSS_USERS"/>
+    </privapp-permissions>
+</permissions>
diff --git a/data/etc/com.android.documentsui.xml b/data/etc/com.android.documentsui.xml
index 36b282c..4d98603 100644
--- a/data/etc/com.android.documentsui.xml
+++ b/data/etc/com.android.documentsui.xml
@@ -17,5 +17,6 @@
 <permissions>
     <privapp-permissions package="com.android.documentsui">
         <permission name="android.permission.CHANGE_OVERLAY_PACKAGES"/>
+        <permission name="android.permission.INTERACT_ACROSS_USERS"/>
     </privapp-permissions>
 </permissions>
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index 1d735af..40de83a 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -22,7 +22,6 @@
         <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
         <permission name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"/>
         <permission name="android.permission.CHANGE_OVERLAY_PACKAGES"/>
-        <permission name="android.permission.CONNECTIVITY_INTERNAL"/>
         <permission name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"/>
         <permission name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"/>
         <permission name="android.permission.CONTROL_VPN"/>
@@ -38,6 +37,7 @@
         <permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
         <permission name="android.permission.MODIFY_PHONE_STATE"/>
         <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
+        <permission name="android.permission.OBSERVE_NETWORK_POLICY"/>
         <permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
         <permission name="android.permission.READ_DREAM_STATE"/>
         <permission name="android.permission.READ_FRAME_BUFFER"/>
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 0574775..0541db1 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -60,10 +60,6 @@
         <group gid="log" />
     </permission>
 
-    <permission name="android.permission.WRITE_MEDIA_STORAGE" >
-        <group gid="media_rw" />
-    </permission>
-
     <permission name="android.permission.ACCESS_MTP" >
         <group gid="mtp" />
     </permission>
@@ -180,7 +176,6 @@
     <assign-permission name="android.permission.ACCESS_LOWPAN_STATE" uid="lowpan" />
     <assign-permission name="android.permission.MANAGE_LOWPAN_INTERFACES" uid="lowpan" />
 
-    <assign-permission name="android.permission.BATTERY_STATS" uid="statsd" />
     <assign-permission name="android.permission.DUMP" uid="statsd" />
     <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="statsd" />
     <assign-permission name="android.permission.STATSCOMPANION" uid="statsd" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index eb1d1ab..3677b8f 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -63,12 +63,12 @@
 
     <privapp-permissions package="com.android.location.fused">
         <permission name="android.permission.INSTALL_LOCATION_PROVIDER"/>
+        <permission name="android.permission.UPDATE_DEVICE_STATS"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.managedprovisioning">
         <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
         <permission name="android.permission.CHANGE_CONFIGURATION"/>
-        <permission name="android.permission.CONNECTIVITY_INTERNAL"/>
         <permission name="android.permission.CRYPT_KEEPER"/>
         <permission name="android.permission.DELETE_PACKAGES"/>
         <permission name="android.permission.INSTALL_PACKAGES"/>
@@ -153,6 +153,7 @@
         <permission name="android.permission.MODIFY_PHONE_STATE"/>
         <permission name="android.permission.PACKAGE_USAGE_STATS"/>
         <permission name="android.permission.PERFORM_CDMA_PROVISIONING"/>
+        <permission name="android.permission.READ_CARRIER_APP_INFO"/>
         <permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
         <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
         <permission name="android.permission.READ_SEARCH_INDEXABLES"/>
@@ -243,7 +244,9 @@
         <permission name="android.permission.MANAGE_USB"/>
         <permission name="android.permission.MODIFY_PHONE_STATE"/>
         <permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
+        <permission name="android.permission.TETHER_PRIVILEGED"/>
         <permission name="android.permission.UPDATE_APP_OPS_STATS"/>
+        <permission name="android.permission.UPDATE_DEVICE_STATS"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.server.telecom">
@@ -309,6 +312,7 @@
         <!-- Needed for test only -->
         <permission name="android.permission.PACKET_KEEPALIVE_OFFLOAD" />
         <permission name="android.permission.POWER_SAVER" />
+        <permission name="android.permission.READ_CARRIER_APP_INFO"/>
         <permission name="android.permission.READ_FRAME_BUFFER"/>
         <permission name="android.permission.READ_LOWPAN_CREDENTIAL"/>
         <!-- Needed for test only -->
@@ -371,7 +375,6 @@
     </privapp-permissions>
 
     <privapp-permissions package="com.android.vpndialogs">
-        <permission name="android.permission.CONNECTIVITY_INTERNAL"/>
         <permission name="android.permission.CONTROL_VPN"/>
     </privapp-permissions>
 
diff --git a/drm/java/android/drm/DrmConvertedStatus.java b/drm/java/android/drm/DrmConvertedStatus.java
index f6e570a..0f7ceb4 100644
--- a/drm/java/android/drm/DrmConvertedStatus.java
+++ b/drm/java/android/drm/DrmConvertedStatus.java
@@ -25,7 +25,9 @@
  * An valid offset value is provided only from a success call to
  * {@link DrmManagerClient#closeConvertSession DrmManagerClient.closeConvertSession()}.
  *
+ * @deprecated Please use {@link android.media.MediaDrm}
  */
+@Deprecated
 public class DrmConvertedStatus {
     // The following status code constants must be in sync with
     // DrmConvertedStatus.cpp. Please also update isValidStatusCode()
diff --git a/drm/java/android/drm/DrmErrorEvent.java b/drm/java/android/drm/DrmErrorEvent.java
index c61819d..f37c8ac 100644
--- a/drm/java/android/drm/DrmErrorEvent.java
+++ b/drm/java/android/drm/DrmErrorEvent.java
@@ -22,7 +22,9 @@
  * An entity class that is passed to the
  * {@link DrmManagerClient.OnErrorListener#onError onError()} callback.
  *
+ * @deprecated Please use {@link android.media.MediaDrm}
  */
+@Deprecated
 public class DrmErrorEvent extends DrmEvent {
 
     // Please add newly defined type constants to the end of the list,
diff --git a/drm/java/android/drm/DrmEvent.java b/drm/java/android/drm/DrmEvent.java
index 1a19f5c..e2fe87b5 100644
--- a/drm/java/android/drm/DrmEvent.java
+++ b/drm/java/android/drm/DrmEvent.java
@@ -21,7 +21,9 @@
 /**
  * A base class that is used to send asynchronous event information from the DRM framework.
  *
+ * @deprecated Please use {@link android.media.MediaDrm}
  */
+@Deprecated
 public class DrmEvent {
 
     // Please do not add type constants in this class. More event type constants
diff --git a/drm/java/android/drm/DrmInfo.java b/drm/java/android/drm/DrmInfo.java
index 8c43252..3240893 100644
--- a/drm/java/android/drm/DrmInfo.java
+++ b/drm/java/android/drm/DrmInfo.java
@@ -30,7 +30,9 @@
  * The caller can retrieve the {@link DrmInfo} instance by passing a {@link DrmInfoRequest}
  * instance to {@link DrmManagerClient#acquireDrmInfo}.
  *
+ * @deprecated Please use {@link android.media.MediaDrm}
  */
+@Deprecated
 public class DrmInfo {
     private byte[] mData;
     private final String mMimeType;
diff --git a/drm/java/android/drm/DrmInfoEvent.java b/drm/java/android/drm/DrmInfoEvent.java
index 2826dce..853f566c 100644
--- a/drm/java/android/drm/DrmInfoEvent.java
+++ b/drm/java/android/drm/DrmInfoEvent.java
@@ -22,7 +22,9 @@
  * An entity class that is passed to the 
  * {@link DrmManagerClient.OnInfoListener#onInfo onInfo()} callback.
  *
+ * @deprecated Please use {@link android.media.MediaDrm}
  */
+@Deprecated
 public class DrmInfoEvent extends DrmEvent {
 
     // Please add newly defined type constants to the end of the list,
diff --git a/drm/java/android/drm/DrmInfoRequest.java b/drm/java/android/drm/DrmInfoRequest.java
index 621da41..135bbc0 100644
--- a/drm/java/android/drm/DrmInfoRequest.java
+++ b/drm/java/android/drm/DrmInfoRequest.java
@@ -24,7 +24,9 @@
  * class is passed to the {@link DrmManagerClient#acquireDrmInfo acquireDrmInfo()} method to get an
  * instance of a {@link DrmInfo}.
  *
+ * @deprecated Please use {@link android.media.MediaDrm}
  */
+@Deprecated
 public class DrmInfoRequest {
     // Changes in following constants should be in sync with DrmInfoRequest.h
     /**
diff --git a/drm/java/android/drm/DrmInfoStatus.java b/drm/java/android/drm/DrmInfoStatus.java
index 9a3a7df..0fa1a70 100644
--- a/drm/java/android/drm/DrmInfoStatus.java
+++ b/drm/java/android/drm/DrmInfoStatus.java
@@ -25,7 +25,9 @@
  * This class contains the {@link ProcessedData} object, which can be used
  * to instantiate a {@link DrmRights} object during license acquisition.
  *
+ * @deprecated Please use {@link android.media.MediaDrm}
  */
+@Deprecated
 public class DrmInfoStatus {
     // The following status code constants must be in sync with DrmInfoStatus.cpp
     // Please update isValidStatusCode() if more status codes are added.
diff --git a/drm/java/android/drm/DrmManagerClient.java b/drm/java/android/drm/DrmManagerClient.java
index 041300c..ba3ebdd 100644
--- a/drm/java/android/drm/DrmManagerClient.java
+++ b/drm/java/android/drm/DrmManagerClient.java
@@ -47,7 +47,9 @@
  * The main programming interface for the DRM framework. An application must instantiate this class
  * to access DRM agents through the DRM framework.
  *
+ * @deprecated Please use {@link android.media.MediaDrm}
  */
+@Deprecated
 public class DrmManagerClient implements AutoCloseable {
     /**
      * Indicates that a request was successful or that no error occurred.
diff --git a/drm/java/android/drm/DrmOutputStream.java b/drm/java/android/drm/DrmOutputStream.java
index 9c23834..73e7f23 100644
--- a/drm/java/android/drm/DrmOutputStream.java
+++ b/drm/java/android/drm/DrmOutputStream.java
@@ -40,7 +40,9 @@
  * writing to disk, similar to a {@link FilterOutputStream}.
  *
  * @hide
+ * @deprecated Please use {@link android.media.MediaDrm}
  */
+@Deprecated
 public class DrmOutputStream extends OutputStream {
     private static final String TAG = "DrmOutputStream";
 
diff --git a/drm/java/android/drm/DrmRights.java b/drm/java/android/drm/DrmRights.java
index 8747f77..0a8df09 100644
--- a/drm/java/android/drm/DrmRights.java
+++ b/drm/java/android/drm/DrmRights.java
@@ -37,7 +37,9 @@
  * agent or plugin, they can be either null, or an empty string, or any other don't-care
  * string value.
  *
+ * @deprecated Please use {@link android.media.MediaDrm}
  */
+@Deprecated
 public class DrmRights {
     private byte[] mData;
     private String mMimeType;
diff --git a/drm/java/android/drm/DrmStore.java b/drm/java/android/drm/DrmStore.java
index 3a77ea1..98d4449 100644
--- a/drm/java/android/drm/DrmStore.java
+++ b/drm/java/android/drm/DrmStore.java
@@ -19,7 +19,9 @@
 /**
  * Defines constants that are used by the DRM framework.
  *
+ * @deprecated Please use {@link android.media.MediaDrm}
  */
+@Deprecated
 public class DrmStore {
     /**
      * Interface definition for the columns that represent DRM constraints.
diff --git a/drm/java/android/drm/DrmSupportInfo.java b/drm/java/android/drm/DrmSupportInfo.java
index 3694ff4..f7e4fbd 100644
--- a/drm/java/android/drm/DrmSupportInfo.java
+++ b/drm/java/android/drm/DrmSupportInfo.java
@@ -26,7 +26,9 @@
  * Plug-in developers can expose the capability of their plug-in by passing an instance of this
  * class to an application.
  *
+ * @deprecated Please use {@link android.media.MediaDrm}
  */
+@Deprecated
 public class DrmSupportInfo {
     private final ArrayList<String> mFileSuffixList = new ArrayList<String>();
     private final ArrayList<String> mMimeTypeList = new ArrayList<String>();
diff --git a/drm/java/android/drm/DrmUtils.java b/drm/java/android/drm/DrmUtils.java
index 60ee6d9..66a60cf 100644
--- a/drm/java/android/drm/DrmUtils.java
+++ b/drm/java/android/drm/DrmUtils.java
@@ -33,7 +33,9 @@
  * constraints, the constraints will show up in the
  * {@link DrmStore.ConstraintsColumns#EXTENDED_METADATA} key. You can use
  * {@link DrmUtils.ExtendedMetadataParser} to iterate over those values.
+ * @deprecated Please use {@link android.media.MediaDrm}
  */
+@Deprecated
 public class DrmUtils {
     /* Should be used when we need to read from local file */
     /* package */ static byte[] readBytes(String path) throws IOException {
diff --git a/drm/java/android/drm/ProcessedData.java b/drm/java/android/drm/ProcessedData.java
index 06e03e7..35b7288 100644
--- a/drm/java/android/drm/ProcessedData.java
+++ b/drm/java/android/drm/ProcessedData.java
@@ -23,7 +23,9 @@
  *
  * In a license acquisition scenario this class holds the rights information in binary form.
  *
+ * @deprecated Please use {@link android.media.MediaDrm}
  */
+@Deprecated
 public class ProcessedData {
     private final byte[] mData;
     private String mAccountId = "_NO_USER";
diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java
index ae90995..447f043 100644
--- a/graphics/java/android/graphics/FontFamily.java
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -20,6 +20,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.AssetManager;
 import android.graphics.fonts.FontVariationAxis;
+import android.os.Build;
 import android.text.TextUtils;
 
 import dalvik.annotation.optimization.CriticalNative;
@@ -58,7 +59,8 @@
      *
      * This cannot be deleted because it's in use by AndroidX.
      */
-    @UnsupportedAppUsage(trackingBug = 123768928)
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+            publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
     public long mNativePtr;
 
     // Points native font family builder. Must be zero after freezing this family.
@@ -67,7 +69,8 @@
     /**
      * This cannot be deleted because it's in use by AndroidX.
      */
-    @UnsupportedAppUsage(trackingBug = 123768928)
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+            publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
     public FontFamily() {
         mBuilderPtr = nInitBuilder(null, 0);
         mNativeBuilderCleaner = sBuilderRegistry.registerNativeAllocation(this, mBuilderPtr);
@@ -76,7 +79,8 @@
     /**
      * This cannot be deleted because it's in use by AndroidX.
      */
-    @UnsupportedAppUsage(trackingBug = 123768928)
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+            publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
     public FontFamily(@Nullable String[] langs, int variant) {
         final String langsString;
         if (langs == null || langs.length == 0) {
@@ -98,7 +102,8 @@
      *
      * This cannot be deleted because it's in use by AndroidX.
      */
-    @UnsupportedAppUsage(trackingBug = 123768928)
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+            publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
     public boolean freeze() {
         if (mBuilderPtr == 0) {
             throw new IllegalStateException("This FontFamily is already frozen");
@@ -115,7 +120,8 @@
     /**
      * This cannot be deleted because it's in use by AndroidX.
      */
-    @UnsupportedAppUsage(trackingBug = 123768928)
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+            publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
     public void abortCreation() {
         if (mBuilderPtr == 0) {
             throw new IllegalStateException("This FontFamily is already frozen or abandoned");
@@ -127,7 +133,8 @@
     /**
      * This cannot be deleted because it's in use by AndroidX.
      */
-    @UnsupportedAppUsage(trackingBug = 123768928)
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+            publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
     public boolean addFont(String path, int ttcIndex, FontVariationAxis[] axes, int weight,
             int italic) {
         if (mBuilderPtr == 0) {
@@ -151,7 +158,8 @@
     /**
      * This cannot be deleted because it's in use by AndroidX.
      */
-    @UnsupportedAppUsage(trackingBug = 123768928)
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+            publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
     public boolean addFontFromBuffer(ByteBuffer font, int ttcIndex, FontVariationAxis[] axes,
             int weight, int italic) {
         if (mBuilderPtr == 0) {
@@ -179,7 +187,8 @@
      *
      * This cannot be deleted because it's in use by AndroidX.
      */
-    @UnsupportedAppUsage(trackingBug = 123768928)
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+            publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
     public boolean addFontFromAssetManager(AssetManager mgr, String path, int cookie,
             boolean isAsset, int ttcIndex, int weight, int isItalic,
             FontVariationAxis[] axes) {
diff --git a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
index fab96a1..928e607 100644
--- a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
+++ b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
@@ -16,6 +16,7 @@
 
 package android.graphics.drawable;
 
+import android.annotation.DrawableRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
@@ -223,6 +224,7 @@
         final int deviceDensity = Drawable.resolveDensity(r, 0);
         state.setDensity(deviceDensity);
         state.mSrcDensityOverride = mSrcDensityOverride;
+        state.mSourceDrawableId = Resources.getAttributeSetSourceResId(attrs);
 
         final ChildDrawable[] array = state.mChildren;
         for (int i = 0; i < state.mChildren.length; i++) {
@@ -446,6 +448,17 @@
     }
 
     /**
+     * If the drawable was inflated from XML, this returns the resource ID for the drawable
+     *
+     * @hide
+     */
+    @DrawableRes
+    public int getSourceDrawableResId() {
+        final LayerState state = mLayerState;
+        return state == null ? Resources.ID_NULL : state.mSourceDrawableId;
+    }
+
+    /**
      * Inflates child layers using the specified parser.
      */
     private void inflateLayers(@NonNull Resources r, @NonNull XmlPullParser parser,
@@ -944,6 +957,8 @@
         @Config int mChangingConfigurations;
         @Config int mChildrenChangingConfigurations;
 
+        @DrawableRes int mSourceDrawableId = Resources.ID_NULL;
+
         private boolean mCheckedOpacity;
         private int mOpacity;
 
diff --git a/identity/MODULE_LICENSE_APACHE2 b/identity/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/identity/MODULE_LICENSE_APACHE2
diff --git a/identity/NOTICE b/identity/NOTICE
new file mode 100644
index 0000000..64aaa8d
--- /dev/null
+++ b/identity/NOTICE
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2009, 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.
+
+   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.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/identity/OWNERS b/identity/OWNERS
new file mode 100644
index 0000000..533d90b
--- /dev/null
+++ b/identity/OWNERS
@@ -0,0 +1,3 @@
+swillden@google.com
+zeuthen@google.com
+
diff --git a/identity/java/android/security/identity/AccessControlProfile.java b/identity/java/android/security/identity/AccessControlProfile.java
new file mode 100644
index 0000000..10e451c
--- /dev/null
+++ b/identity/java/android/security/identity/AccessControlProfile.java
@@ -0,0 +1,131 @@
+/*
+ * 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.security.identity;
+
+import android.annotation.NonNull;
+
+import java.security.cert.X509Certificate;
+
+/**
+ * A class used to specify access controls.
+ */
+public class AccessControlProfile {
+    private AccessControlProfileId mAccessControlProfileId = new AccessControlProfileId(0);
+    private X509Certificate mReaderCertificate = null;
+    private boolean mUserAuthenticationRequired = true;
+    private long mUserAuthenticationTimeout = 0;
+
+    private AccessControlProfile() {
+    }
+
+    AccessControlProfileId getAccessControlProfileId() {
+        return mAccessControlProfileId;
+    }
+
+    long getUserAuthenticationTimeout() {
+        return mUserAuthenticationTimeout;
+    }
+
+    boolean isUserAuthenticationRequired() {
+        return mUserAuthenticationRequired;
+    }
+
+    X509Certificate getReaderCertificate() {
+        return mReaderCertificate;
+    }
+
+    /**
+     * A builder for {@link AccessControlProfile}.
+     */
+    public static final class Builder {
+        private AccessControlProfile mProfile;
+
+        /**
+         * Each access control profile has numeric identifier that must be unique within the
+         * context of a Credential and may be used to reference the profile.
+         *
+         * <p>By default, the resulting {@link AccessControlProfile} will require user
+         * authentication with a timeout of zero, thus requiring the holder to authenticate for
+         * every presentation where data elements using this access control profile is used.</p>
+         *
+         * @param accessControlProfileId the access control profile identifier.
+         */
+        public Builder(@NonNull AccessControlProfileId accessControlProfileId) {
+            mProfile = new AccessControlProfile();
+            mProfile.mAccessControlProfileId = accessControlProfileId;
+        }
+
+        /**
+         * Set whether user authentication is required.
+         *
+         * <p>This should be used sparingly since disabling user authentication on just a single
+         * data element can easily create a
+         * <a href="https://en.wikipedia.org/wiki/Relay_attack">Relay Attack</a> if the device
+         * on which the credential is stored is compromised.</p>
+         *
+         * @param userAuthenticationRequired Set to true if user authentication is required,
+         *                                   false otherwise.
+         * @return The builder.
+         */
+        public @NonNull Builder setUserAuthenticationRequired(boolean userAuthenticationRequired) {
+            mProfile.mUserAuthenticationRequired = userAuthenticationRequired;
+            return this;
+        }
+
+        /**
+         * Sets the authentication timeout to use.
+         *
+         * <p>The authentication timeout specifies the amount of time, in milliseconds, for which a
+         * user authentication is valid, if user authentication is required (see
+         * {@link #setUserAuthenticationRequired(boolean)}).</p>
+         *
+         * <p>If the timeout is zero, then authentication is always required for each reader
+         * session.</p>
+         *
+         * @param userAuthenticationTimeoutMillis the authentication timeout, in milliseconds.
+         * @return The builder.
+         */
+        public @NonNull Builder setUserAuthenticationTimeout(long userAuthenticationTimeoutMillis) {
+            mProfile.mUserAuthenticationTimeout = userAuthenticationTimeoutMillis;
+            return this;
+        }
+
+        /**
+         * Sets the reader certificate to use when checking access control.
+         *
+         * <p>If set, this is checked against the certificate chain presented by
+         * reader. The access check is fulfilled only if one of the certificates
+         * in the chain, matches the certificate set by this method.</p>
+         *
+         * @param readerCertificate the certificate to use for the access control check.
+         * @return The builder.
+         */
+        public @NonNull Builder setReaderCertificate(@NonNull X509Certificate readerCertificate) {
+            mProfile.mReaderCertificate = readerCertificate;
+            return this;
+        }
+
+        /**
+         * Creates a new {@link AccessControlProfile} from the data supplied to the builder.
+         *
+         * @return The created {@link AccessControlProfile} object.
+         */
+        public @NonNull AccessControlProfile build() {
+            return mProfile;
+        }
+    }
+}
diff --git a/identity/java/android/security/identity/AccessControlProfileId.java b/identity/java/android/security/identity/AccessControlProfileId.java
new file mode 100644
index 0000000..3d59450
--- /dev/null
+++ b/identity/java/android/security/identity/AccessControlProfileId.java
@@ -0,0 +1,42 @@
+/*
+ * 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.security.identity;
+
+/**
+ * A class used to wrap an access control profile identifiers.
+ */
+public class AccessControlProfileId {
+    private int mId = 0;
+
+    /**
+     * Constructs a new object holding a numerical identifier.
+     *
+     * @param id the identifier.
+     */
+    public AccessControlProfileId(int id) {
+        this.mId = id;
+    }
+
+    /**
+     * Gets the numerical identifier wrapped by this object.
+     *
+     * @return the identifier.
+     */
+    public int getId() {
+        return this.mId;
+    }
+}
diff --git a/identity/java/android/security/identity/AlreadyPersonalizedException.java b/identity/java/android/security/identity/AlreadyPersonalizedException.java
new file mode 100644
index 0000000..1933882
--- /dev/null
+++ b/identity/java/android/security/identity/AlreadyPersonalizedException.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 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.security.identity;
+
+import android.annotation.NonNull;
+
+/**
+ * Thrown if trying to create a credential which already exists.
+ */
+public class AlreadyPersonalizedException extends IdentityCredentialException {
+    /**
+     * Constructs a new {@link AlreadyPersonalizedException} exception.
+     *
+     * @param message the detail message.
+     */
+    public AlreadyPersonalizedException(@NonNull String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new {@link AlreadyPersonalizedException} exception.
+     *
+     * @param message the detail message.
+     * @param cause   the cause.
+     */
+    public AlreadyPersonalizedException(@NonNull String message, @NonNull Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/identity/java/android/security/identity/CipherSuiteNotSupportedException.java b/identity/java/android/security/identity/CipherSuiteNotSupportedException.java
new file mode 100644
index 0000000..e7a6c89
--- /dev/null
+++ b/identity/java/android/security/identity/CipherSuiteNotSupportedException.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 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.security.identity;
+
+import android.annotation.NonNull;
+
+/**
+ * Thrown if trying to use a cipher suite which isn't supported.
+ */
+public class CipherSuiteNotSupportedException extends IdentityCredentialException {
+    /**
+     * Constructs a new {@link CipherSuiteNotSupportedException} exception.
+     *
+     * @param message the detail message.
+     */
+    public CipherSuiteNotSupportedException(@NonNull String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new {@link CipherSuiteNotSupportedException} exception.
+     *
+     * @param message the detail message.
+     * @param cause   the cause.
+     */
+    public CipherSuiteNotSupportedException(@NonNull String message, @NonNull Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/identity/java/android/security/identity/CredstoreIdentityCredential.java b/identity/java/android/security/identity/CredstoreIdentityCredential.java
new file mode 100644
index 0000000..c520331
--- /dev/null
+++ b/identity/java/android/security/identity/CredstoreIdentityCredential.java
@@ -0,0 +1,424 @@
+/*
+ * Copyright 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.security.identity;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.Map;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyAgreement;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+class CredstoreIdentityCredential extends IdentityCredential {
+
+    private static final String TAG = "CredstoreIdentityCredential";
+    private String mCredentialName;
+    private @IdentityCredentialStore.Ciphersuite int mCipherSuite;
+    private Context mContext;
+    private ICredential mBinder;
+
+    CredstoreIdentityCredential(Context context, String credentialName,
+            @IdentityCredentialStore.Ciphersuite int cipherSuite,
+            ICredential binder) {
+        mContext = context;
+        mCredentialName = credentialName;
+        mCipherSuite = cipherSuite;
+        mBinder = binder;
+    }
+
+    private KeyPair mEphemeralKeyPair = null;
+    private SecretKey mSecretKey = null;
+    private SecretKey mReaderSecretKey = null;
+    private int mEphemeralCounter;
+    private int mReadersExpectedEphemeralCounter;
+
+    private void ensureEphemeralKeyPair() {
+        if (mEphemeralKeyPair != null) {
+            return;
+        }
+        try {
+            // This PKCS#12 blob is generated in credstore, using BoringSSL.
+            //
+            // The main reason for this convoluted approach and not just sending the decomposed
+            // key-pair is that this would require directly using (device-side) BouncyCastle which
+            // is tricky due to various API hiding efforts. So instead we have credstore generate
+            // this PKCS#12 blob. The blob is encrypted with no password (sadly, also, BoringSSL
+            // doesn't support not using encryption when building a PKCS#12 blob).
+            //
+            byte[] pkcs12 = mBinder.createEphemeralKeyPair();
+            String alias = "ephemeralKey";
+            char[] password = {};
+
+            KeyStore ks = KeyStore.getInstance("PKCS12");
+            ByteArrayInputStream bais = new ByteArrayInputStream(pkcs12);
+            ks.load(bais, password);
+            PrivateKey privKey = (PrivateKey) ks.getKey(alias, password);
+
+            Certificate cert = ks.getCertificate(alias);
+            PublicKey pubKey = cert.getPublicKey();
+
+            mEphemeralKeyPair = new KeyPair(pubKey, privKey);
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                    + e.errorCode, e);
+        } catch (KeyStoreException
+                | CertificateException
+                | UnrecoverableKeyException
+                | NoSuchAlgorithmException
+                | IOException e) {
+            throw new RuntimeException("Unexpected exception ", e);
+        }
+    }
+
+    @Override
+    public @NonNull KeyPair createEphemeralKeyPair() {
+        ensureEphemeralKeyPair();
+        return mEphemeralKeyPair;
+    }
+
+    @Override
+    public void setReaderEphemeralPublicKey(@NonNull PublicKey readerEphemeralPublicKey)
+            throws InvalidKeyException {
+        try {
+            byte[] uncompressedForm =
+                    Util.publicKeyEncodeUncompressedForm(readerEphemeralPublicKey);
+            mBinder.setReaderEphemeralPublicKey(uncompressedForm);
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                    + e.errorCode, e);
+        }
+
+        ensureEphemeralKeyPair();
+
+        try {
+            KeyAgreement ka = KeyAgreement.getInstance("ECDH");
+            ka.init(mEphemeralKeyPair.getPrivate());
+            ka.doPhase(readerEphemeralPublicKey, true);
+            byte[] sharedSecret = ka.generateSecret();
+
+            byte[] salt = new byte[1];
+            byte[] info = new byte[0];
+
+            salt[0] = 0x01;
+            byte[] derivedKey = Util.computeHkdf("HmacSha256", sharedSecret, salt, info, 32);
+            mSecretKey = new SecretKeySpec(derivedKey, "AES");
+
+            salt[0] = 0x00;
+            derivedKey = Util.computeHkdf("HmacSha256", sharedSecret, salt, info, 32);
+            mReaderSecretKey = new SecretKeySpec(derivedKey, "AES");
+
+            mEphemeralCounter = 0;
+            mReadersExpectedEphemeralCounter = 0;
+
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("Error performing key agreement", e);
+        }
+    }
+
+    @Override
+    public @NonNull byte[] encryptMessageToReader(@NonNull byte[] messagePlaintext) {
+        byte[] messageCiphertextAndAuthTag = null;
+        try {
+            ByteBuffer iv = ByteBuffer.allocate(12);
+            iv.putInt(0, 0x00000000);
+            iv.putInt(4, 0x00000001);
+            iv.putInt(8, mEphemeralCounter);
+            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
+            GCMParameterSpec encryptionParameterSpec = new GCMParameterSpec(128, iv.array());
+            cipher.init(Cipher.ENCRYPT_MODE, mSecretKey, encryptionParameterSpec);
+            messageCiphertextAndAuthTag = cipher.doFinal(messagePlaintext);
+        } catch (BadPaddingException
+                | IllegalBlockSizeException
+                | NoSuchPaddingException
+                | InvalidKeyException
+                | NoSuchAlgorithmException
+                | InvalidAlgorithmParameterException e) {
+            throw new RuntimeException("Error encrypting message", e);
+        }
+        mEphemeralCounter += 1;
+        return messageCiphertextAndAuthTag;
+    }
+
+    @Override
+    public @NonNull byte[] decryptMessageFromReader(@NonNull byte[] messageCiphertext)
+            throws MessageDecryptionException {
+        ByteBuffer iv = ByteBuffer.allocate(12);
+        iv.putInt(0, 0x00000000);
+        iv.putInt(4, 0x00000000);
+        iv.putInt(8, mReadersExpectedEphemeralCounter);
+        byte[] plainText = null;
+        try {
+            final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
+            cipher.init(Cipher.DECRYPT_MODE, mReaderSecretKey,
+                    new GCMParameterSpec(128, iv.array()));
+            plainText = cipher.doFinal(messageCiphertext);
+        } catch (BadPaddingException
+                | IllegalBlockSizeException
+                | InvalidAlgorithmParameterException
+                | InvalidKeyException
+                | NoSuchAlgorithmException
+                | NoSuchPaddingException e) {
+            throw new MessageDecryptionException("Error decrypting message", e);
+        }
+        mReadersExpectedEphemeralCounter += 1;
+        return plainText;
+    }
+
+    @Override
+    public @NonNull Collection<X509Certificate> getCredentialKeyCertificateChain() {
+        try {
+            byte[] certsBlob = mBinder.getCredentialKeyCertificateChain();
+            ByteArrayInputStream bais = new ByteArrayInputStream(certsBlob);
+
+            Collection<? extends Certificate> certs = null;
+            try {
+                CertificateFactory factory = CertificateFactory.getInstance("X.509");
+                certs = factory.generateCertificates(bais);
+            } catch (CertificateException e) {
+                throw new RuntimeException("Error decoding certificates", e);
+            }
+
+            LinkedList<X509Certificate> x509Certs = new LinkedList<>();
+            for (Certificate cert : certs) {
+                x509Certs.add((X509Certificate) cert);
+            }
+            return x509Certs;
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                    + e.errorCode, e);
+        }
+    }
+
+    private boolean mAllowUsingExhaustedKeys = true;
+
+    @Override
+    public void setAllowUsingExhaustedKeys(boolean allowUsingExhaustedKeys) {
+        mAllowUsingExhaustedKeys = allowUsingExhaustedKeys;
+    }
+
+    private boolean mOperationHandleSet = false;
+    private long mOperationHandle = 0;
+
+    /**
+     * Called by android.hardware.biometrics.CryptoObject#getOpId() to get an
+     * operation handle.
+     *
+     * @hide
+     */
+    @Override
+    public long getCredstoreOperationHandle() {
+        if (!mOperationHandleSet) {
+            try {
+                mOperationHandle = mBinder.selectAuthKey(mAllowUsingExhaustedKeys);
+                mOperationHandleSet = true;
+            } catch (android.os.RemoteException e) {
+                throw new RuntimeException("Unexpected RemoteException ", e);
+            } catch (android.os.ServiceSpecificException e) {
+                if (e.errorCode == ICredentialStore.ERROR_NO_AUTHENTICATION_KEY_AVAILABLE) {
+                    // The NoAuthenticationKeyAvailableException will be thrown when
+                    // the caller proceeds to call getEntries().
+                }
+                throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                        + e.errorCode, e);
+            }
+        }
+        return mOperationHandle;
+    }
+
+    @NonNull
+    @Override
+    public ResultData getEntries(
+            @Nullable byte[] requestMessage,
+            @NonNull Map<String, Collection<String>> entriesToRequest,
+            @Nullable byte[] sessionTranscript,
+            @Nullable byte[] readerSignature)
+            throws SessionTranscriptMismatchException, NoAuthenticationKeyAvailableException,
+            InvalidReaderSignatureException, EphemeralPublicKeyNotFoundException,
+            InvalidRequestMessageException {
+
+        RequestNamespaceParcel[] rnsParcels = new RequestNamespaceParcel[entriesToRequest.size()];
+        int n = 0;
+        for (String namespaceName : entriesToRequest.keySet()) {
+            Collection<String> entryNames = entriesToRequest.get(namespaceName);
+            rnsParcels[n] = new RequestNamespaceParcel();
+            rnsParcels[n].namespaceName = namespaceName;
+            rnsParcels[n].entries = new RequestEntryParcel[entryNames.size()];
+            int m = 0;
+            for (String entryName : entryNames) {
+                rnsParcels[n].entries[m] = new RequestEntryParcel();
+                rnsParcels[n].entries[m].name = entryName;
+                m++;
+            }
+            n++;
+        }
+
+        GetEntriesResultParcel resultParcel = null;
+        try {
+            resultParcel = mBinder.getEntries(
+                requestMessage != null ? requestMessage : new byte[0],
+                rnsParcels,
+                sessionTranscript != null ? sessionTranscript : new byte[0],
+                readerSignature != null ? readerSignature : new byte[0],
+                mAllowUsingExhaustedKeys);
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            if (e.errorCode == ICredentialStore.ERROR_EPHEMERAL_PUBLIC_KEY_NOT_FOUND) {
+                throw new EphemeralPublicKeyNotFoundException(e.getMessage(), e);
+            } else if (e.errorCode == ICredentialStore.ERROR_INVALID_READER_SIGNATURE) {
+                throw new InvalidReaderSignatureException(e.getMessage(), e);
+            } else if (e.errorCode == ICredentialStore.ERROR_NO_AUTHENTICATION_KEY_AVAILABLE) {
+                throw new NoAuthenticationKeyAvailableException(e.getMessage(), e);
+            } else if (e.errorCode == ICredentialStore.ERROR_INVALID_ITEMS_REQUEST_MESSAGE) {
+                throw new InvalidRequestMessageException(e.getMessage(), e);
+            } else if (e.errorCode == ICredentialStore.ERROR_SESSION_TRANSCRIPT_MISMATCH) {
+                throw new SessionTranscriptMismatchException(e.getMessage(), e);
+            } else {
+                throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                        + e.errorCode, e);
+            }
+        }
+
+        byte[] mac = resultParcel.mac;
+        if (mac != null && mac.length == 0) {
+            mac = null;
+        }
+        CredstoreResultData.Builder resultDataBuilder = new CredstoreResultData.Builder(
+                resultParcel.staticAuthenticationData, resultParcel.deviceNameSpaces, mac);
+
+        for (ResultNamespaceParcel resultNamespaceParcel : resultParcel.resultNamespaces) {
+            for (ResultEntryParcel resultEntryParcel : resultNamespaceParcel.entries) {
+                if (resultEntryParcel.status == ICredential.STATUS_OK) {
+                    resultDataBuilder.addEntry(resultNamespaceParcel.namespaceName,
+                            resultEntryParcel.name, resultEntryParcel.value);
+                } else {
+                    resultDataBuilder.addErrorStatus(resultNamespaceParcel.namespaceName,
+                            resultEntryParcel.name,
+                            resultEntryParcel.status);
+                }
+            }
+        }
+        return resultDataBuilder.build();
+    }
+
+    @Override
+    public void setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey) {
+        try {
+            mBinder.setAvailableAuthenticationKeys(keyCount, maxUsesPerKey);
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                    + e.errorCode, e);
+        }
+    }
+
+    @Override
+    public @NonNull Collection<X509Certificate> getAuthKeysNeedingCertification() {
+        try {
+            AuthKeyParcel[] authKeyParcels = mBinder.getAuthKeysNeedingCertification();
+            LinkedList<X509Certificate> x509Certs = new LinkedList<>();
+            CertificateFactory factory = CertificateFactory.getInstance("X.509");
+            for (AuthKeyParcel authKeyParcel : authKeyParcels) {
+                Collection<? extends Certificate> certs = null;
+                ByteArrayInputStream bais = new ByteArrayInputStream(authKeyParcel.x509cert);
+                certs = factory.generateCertificates(bais);
+                if (certs.size() != 1) {
+                    throw new RuntimeException("Returned blob yields more than one X509 cert");
+                }
+                X509Certificate authKeyCert = (X509Certificate) certs.iterator().next();
+                x509Certs.add(authKeyCert);
+            }
+            return x509Certs;
+        } catch (CertificateException e) {
+            throw new RuntimeException("Error decoding authenticationKey", e);
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                    + e.errorCode, e);
+        }
+    }
+
+    @Override
+    public void storeStaticAuthenticationData(X509Certificate authenticationKey,
+            byte[] staticAuthData)
+            throws UnknownAuthenticationKeyException {
+        try {
+            AuthKeyParcel authKeyParcel = new AuthKeyParcel();
+            authKeyParcel.x509cert = authenticationKey.getEncoded();
+            mBinder.storeStaticAuthenticationData(authKeyParcel, staticAuthData);
+        } catch (CertificateEncodingException e) {
+            throw new RuntimeException("Error encoding authenticationKey", e);
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            if (e.errorCode == ICredentialStore.ERROR_AUTHENTICATION_KEY_NOT_FOUND) {
+                throw new UnknownAuthenticationKeyException(e.getMessage(), e);
+            } else {
+                throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                        + e.errorCode, e);
+            }
+        }
+    }
+
+    @Override
+    public @NonNull int[] getAuthenticationDataUsageCount() {
+        try {
+            int[] usageCount = mBinder.getAuthenticationDataUsageCount();
+            return usageCount;
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                    + e.errorCode, e);
+        }
+    }
+}
diff --git a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
new file mode 100644
index 0000000..dcc6b95
--- /dev/null
+++ b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 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.security.identity;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.ServiceManager;
+
+class CredstoreIdentityCredentialStore extends IdentityCredentialStore {
+
+    private static final String TAG = "CredstoreIdentityCredentialStore";
+
+    private Context mContext = null;
+    private ICredentialStore mStore = null;
+
+    private CredstoreIdentityCredentialStore(@NonNull Context context, ICredentialStore store) {
+        mContext = context;
+        mStore = store;
+    }
+
+    static CredstoreIdentityCredentialStore getInstanceForType(@NonNull Context context,
+            int credentialStoreType) {
+        ICredentialStoreFactory storeFactory =
+                ICredentialStoreFactory.Stub.asInterface(
+                    ServiceManager.getService("android.security.identity"));
+
+        ICredentialStore credStore = null;
+        try {
+            credStore = storeFactory.getCredentialStore(credentialStoreType);
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            if (e.errorCode == ICredentialStore.ERROR_GENERIC) {
+                return null;
+            } else {
+                throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                        + e.errorCode, e);
+            }
+        }
+        if (credStore == null) {
+            return null;
+        }
+
+        return new CredstoreIdentityCredentialStore(context, credStore);
+    }
+
+    private static CredstoreIdentityCredentialStore sInstanceDefault = null;
+    private static CredstoreIdentityCredentialStore sInstanceDirectAccess = null;
+
+    public static @Nullable IdentityCredentialStore getInstance(@NonNull Context context) {
+        if (sInstanceDefault == null) {
+            sInstanceDefault = getInstanceForType(context,
+                    ICredentialStoreFactory.CREDENTIAL_STORE_TYPE_DEFAULT);
+        }
+        return sInstanceDefault;
+    }
+
+    public static @Nullable IdentityCredentialStore getDirectAccessInstance(@NonNull
+            Context context) {
+        if (sInstanceDirectAccess == null) {
+            sInstanceDirectAccess = getInstanceForType(context,
+                    ICredentialStoreFactory.CREDENTIAL_STORE_TYPE_DIRECT_ACCESS);
+        }
+        return sInstanceDirectAccess;
+    }
+
+    @Override
+    public @NonNull String[] getSupportedDocTypes() {
+        try {
+            SecurityHardwareInfoParcel info;
+            info = mStore.getSecurityHardwareInfo();
+            return info.supportedDocTypes;
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                    + e.errorCode, e);
+        }
+    }
+
+    @Override public @NonNull WritableIdentityCredential createCredential(
+            @NonNull String credentialName,
+            @NonNull String docType) throws AlreadyPersonalizedException,
+            DocTypeNotSupportedException {
+        try {
+            IWritableCredential wc;
+            wc = mStore.createCredential(credentialName, docType);
+            return new CredstoreWritableIdentityCredential(mContext, credentialName, docType, wc);
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            if (e.errorCode == ICredentialStore.ERROR_ALREADY_PERSONALIZED) {
+                throw new AlreadyPersonalizedException(e.getMessage(), e);
+            } else if (e.errorCode == ICredentialStore.ERROR_DOCUMENT_TYPE_NOT_SUPPORTED) {
+                throw new DocTypeNotSupportedException(e.getMessage(), e);
+            } else {
+                throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                        + e.errorCode, e);
+            }
+        }
+    }
+
+    @Override public @Nullable IdentityCredential getCredentialByName(
+            @NonNull String credentialName,
+            @Ciphersuite int cipherSuite) throws CipherSuiteNotSupportedException {
+        try {
+            ICredential credstoreCredential;
+            credstoreCredential = mStore.getCredentialByName(credentialName, cipherSuite);
+            return new CredstoreIdentityCredential(mContext, credentialName, cipherSuite,
+                    credstoreCredential);
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            if (e.errorCode == ICredentialStore.ERROR_NO_SUCH_CREDENTIAL) {
+                return null;
+            } else if (e.errorCode == ICredentialStore.ERROR_CIPHER_SUITE_NOT_SUPPORTED) {
+                throw new CipherSuiteNotSupportedException(e.getMessage(), e);
+            } else {
+                throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                        + e.errorCode, e);
+            }
+        }
+    }
+
+    @Override
+    public @Nullable byte[] deleteCredentialByName(@NonNull String credentialName) {
+        ICredential credstoreCredential = null;
+        try {
+            try {
+                credstoreCredential = mStore.getCredentialByName(credentialName,
+                        CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
+            } catch (android.os.ServiceSpecificException e) {
+                if (e.errorCode == ICredentialStore.ERROR_NO_SUCH_CREDENTIAL) {
+                    return null;
+                }
+            }
+            byte[] proofOfDeletion = credstoreCredential.deleteCredential();
+            return proofOfDeletion;
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                    + e.errorCode, e);
+        }
+    }
+
+}
diff --git a/identity/java/android/security/identity/CredstoreResultData.java b/identity/java/android/security/identity/CredstoreResultData.java
new file mode 100644
index 0000000..ef7afca
--- /dev/null
+++ b/identity/java/android/security/identity/CredstoreResultData.java
@@ -0,0 +1,162 @@
+/*
+ * 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.security.identity;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.Map;
+
+/**
+ * An object that contains the result of retrieving data from a credential. This is used to return
+ * data requested from a {@link IdentityCredential}.
+ */
+class CredstoreResultData extends ResultData {
+
+    byte[] mStaticAuthenticationData = null;
+    byte[] mAuthenticatedData = null;
+    byte[] mMessageAuthenticationCode = null;
+
+    private Map<String, Map<String, EntryData>> mData = new LinkedHashMap<>();
+
+    private static class EntryData {
+        @Status
+        int mStatus;
+        byte[] mValue;
+
+        EntryData(byte[] value, @Status int status) {
+            this.mValue = value;
+            this.mStatus = status;
+        }
+    }
+
+    CredstoreResultData() {}
+
+    @Override
+    public @NonNull byte[] getAuthenticatedData() {
+        return mAuthenticatedData;
+    }
+
+    @Override
+    public @Nullable byte[] getMessageAuthenticationCode() {
+        return mMessageAuthenticationCode;
+    }
+
+    @Override
+    public @NonNull byte[] getStaticAuthenticationData() {
+        return mStaticAuthenticationData;
+    }
+
+    @Override
+    public @NonNull Collection<String> getNamespaceNames() {
+        return Collections.unmodifiableCollection(mData.keySet());
+    }
+
+    @Override
+    public @Nullable Collection<String> getEntryNames(@NonNull String namespaceName) {
+        Map<String, EntryData> innerMap = mData.get(namespaceName);
+        if (innerMap == null) {
+            return null;
+        }
+        return Collections.unmodifiableCollection(innerMap.keySet());
+    }
+
+    @Override
+    public @Nullable Collection<String> getRetrievedEntryNames(@NonNull String namespaceName) {
+        Map<String, EntryData> innerMap = mData.get(namespaceName);
+        if (innerMap == null) {
+            return null;
+        }
+        LinkedList<String> result = new LinkedList<String>();
+        for (Map.Entry<String, EntryData> entry : innerMap.entrySet()) {
+            if (entry.getValue().mStatus == STATUS_OK) {
+                result.add(entry.getKey());
+            }
+        }
+        return result;
+    }
+
+    private EntryData getEntryData(@NonNull String namespaceName, @NonNull String name) {
+        Map<String, EntryData> innerMap = mData.get(namespaceName);
+        if (innerMap == null) {
+            return null;
+        }
+        return innerMap.get(name);
+    }
+
+    @Override
+    @Status
+    public int getStatus(@NonNull String namespaceName, @NonNull String name) {
+        EntryData value = getEntryData(namespaceName, name);
+        if (value == null) {
+            return STATUS_NOT_REQUESTED;
+        }
+        return value.mStatus;
+    }
+
+    @Override
+    public @Nullable byte[] getEntry(@NonNull String namespaceName, @NonNull String name) {
+        EntryData value = getEntryData(namespaceName, name);
+        if (value == null) {
+            return null;
+        }
+        return value.mValue;
+    }
+
+    static class Builder {
+        private CredstoreResultData mResultData;
+
+        Builder(byte[] staticAuthenticationData,
+                byte[] authenticatedData,
+                byte[] messageAuthenticationCode) {
+            this.mResultData = new CredstoreResultData();
+            this.mResultData.mStaticAuthenticationData = staticAuthenticationData;
+            this.mResultData.mAuthenticatedData = authenticatedData;
+            this.mResultData.mMessageAuthenticationCode = messageAuthenticationCode;
+        }
+
+        private Map<String, EntryData> getOrCreateInnerMap(String namespaceName) {
+            Map<String, EntryData> innerMap = mResultData.mData.get(namespaceName);
+            if (innerMap == null) {
+                innerMap = new LinkedHashMap<>();
+                mResultData.mData.put(namespaceName, innerMap);
+            }
+            return innerMap;
+        }
+
+        Builder addEntry(String namespaceName, String name, byte[] value) {
+            Map<String, EntryData> innerMap = getOrCreateInnerMap(namespaceName);
+            innerMap.put(name, new EntryData(value, STATUS_OK));
+            return this;
+        }
+
+        Builder addErrorStatus(String namespaceName, String name, @Status int status) {
+            Map<String, EntryData> innerMap = getOrCreateInnerMap(namespaceName);
+            innerMap.put(name, new EntryData(null, status));
+            return this;
+        }
+
+        CredstoreResultData build() {
+            return mResultData;
+        }
+    }
+
+}
diff --git a/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java b/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java
new file mode 100644
index 0000000..335636c
--- /dev/null
+++ b/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 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.security.identity;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.security.GateKeeper;
+
+import java.io.ByteArrayInputStream;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.LinkedList;
+
+class CredstoreWritableIdentityCredential extends WritableIdentityCredential {
+
+    private static final String TAG = "CredstoreWritableIdentityCredential";
+
+    private String mDocType;
+    private String mCredentialName;
+    private Context mContext;
+    private IWritableCredential mBinder;
+
+    CredstoreWritableIdentityCredential(Context context,
+            @NonNull String credentialName,
+            @NonNull String docType,
+            IWritableCredential binder) {
+        mContext = context;
+        mDocType = docType;
+        mCredentialName = credentialName;
+        mBinder = binder;
+    }
+
+    @NonNull @Override
+    public Collection<X509Certificate> getCredentialKeyCertificateChain(@NonNull byte[] challenge) {
+        try {
+            byte[] certsBlob = mBinder.getCredentialKeyCertificateChain(challenge);
+            ByteArrayInputStream bais = new ByteArrayInputStream(certsBlob);
+
+            Collection<? extends Certificate> certs = null;
+            try {
+                CertificateFactory factory = CertificateFactory.getInstance("X.509");
+                certs = factory.generateCertificates(bais);
+            } catch (CertificateException e) {
+                throw new RuntimeException("Error decoding certificates", e);
+            }
+
+            LinkedList<X509Certificate> x509Certs = new LinkedList<>();
+            for (Certificate cert : certs) {
+                x509Certs.add((X509Certificate) cert);
+            }
+            return x509Certs;
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                    + e.errorCode, e);
+        }
+    }
+
+    @NonNull @Override
+    public byte[] personalize(@NonNull PersonalizationData personalizationData) {
+
+        Collection<AccessControlProfile> accessControlProfiles =
+                personalizationData.getAccessControlProfiles();
+
+        AccessControlProfileParcel[] acpParcels =
+                new AccessControlProfileParcel[accessControlProfiles.size()];
+        boolean usingUserAuthentication = false;
+        int n = 0;
+        for (AccessControlProfile profile : accessControlProfiles) {
+            acpParcels[n] = new AccessControlProfileParcel();
+            acpParcels[n].id = profile.getAccessControlProfileId().getId();
+            X509Certificate cert = profile.getReaderCertificate();
+            if (cert != null) {
+                try {
+                    acpParcels[n].readerCertificate = cert.getEncoded();
+                } catch (CertificateException e) {
+                    throw new RuntimeException("Error encoding reader certificate", e);
+                }
+            } else {
+                acpParcels[n].readerCertificate = new byte[0];
+            }
+            acpParcels[n].userAuthenticationRequired = profile.isUserAuthenticationRequired();
+            acpParcels[n].userAuthenticationTimeoutMillis = profile.getUserAuthenticationTimeout();
+            if (profile.isUserAuthenticationRequired()) {
+                usingUserAuthentication = true;
+            }
+            n++;
+        }
+
+        Collection<String> namespaceNames = personalizationData.getNamespaceNames();
+
+        EntryNamespaceParcel[] ensParcels  = new EntryNamespaceParcel[namespaceNames.size()];
+        n = 0;
+        for (String namespaceName : namespaceNames) {
+            PersonalizationData.NamespaceData nsd =
+                    personalizationData.getNamespaceData(namespaceName);
+
+            ensParcels[n] = new EntryNamespaceParcel();
+            ensParcels[n].namespaceName = namespaceName;
+
+            Collection<String> entryNames = nsd.getEntryNames();
+            EntryParcel[] eParcels = new EntryParcel[entryNames.size()];
+            int m = 0;
+            for (String entryName : entryNames) {
+                eParcels[m] = new EntryParcel();
+                eParcels[m].name = entryName;
+                eParcels[m].value = nsd.getEntryValue(entryName);
+                Collection<AccessControlProfileId> acpIds =
+                        nsd.getAccessControlProfileIds(entryName);
+                eParcels[m].accessControlProfileIds = new int[acpIds.size()];
+                int o = 0;
+                for (AccessControlProfileId acpId : acpIds) {
+                    eParcels[m].accessControlProfileIds[o++] = acpId.getId();
+                }
+                m++;
+            }
+            ensParcels[n].entries = eParcels;
+            n++;
+        }
+
+        // Note: The value 0 is used to convey that no user-authentication is needed for this
+        // credential. This is to allow creating credentials w/o user authentication on devices
+        // where Secure lock screen is not enabled.
+        long secureUserId = 0;
+        if (usingUserAuthentication) {
+            secureUserId = getRootSid();
+        }
+        try {
+            byte[] personalizationReceipt = mBinder.personalize(acpParcels, ensParcels,
+                    secureUserId);
+            return personalizationReceipt;
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                    + e.errorCode, e);
+        }
+    }
+
+    private static long getRootSid() {
+        long rootSid = GateKeeper.getSecureUserId();
+        if (rootSid == 0) {
+            throw new IllegalStateException("Secure lock screen must be enabled"
+                    + " to create credentials requiring user authentication");
+        }
+        return rootSid;
+    }
+
+
+}
diff --git a/identity/java/android/security/identity/DocTypeNotSupportedException.java b/identity/java/android/security/identity/DocTypeNotSupportedException.java
new file mode 100644
index 0000000..754e44a
--- /dev/null
+++ b/identity/java/android/security/identity/DocTypeNotSupportedException.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 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.security.identity;
+
+import android.annotation.NonNull;
+
+/**
+ * Thrown if trying to create a credential with an unsupported document type.
+ */
+public class DocTypeNotSupportedException extends IdentityCredentialException {
+    /**
+     * Constructs a new {@link DocTypeNotSupportedException} exception.
+     *
+     * @param message the detail message.
+     */
+    public DocTypeNotSupportedException(@NonNull String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new {@link DocTypeNotSupportedException} exception.
+     *
+     * @param message the detail message.
+     * @param cause   the cause.
+     */
+    public DocTypeNotSupportedException(@NonNull String message, @NonNull Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/identity/java/android/security/identity/EphemeralPublicKeyNotFoundException.java b/identity/java/android/security/identity/EphemeralPublicKeyNotFoundException.java
new file mode 100644
index 0000000..265f271
--- /dev/null
+++ b/identity/java/android/security/identity/EphemeralPublicKeyNotFoundException.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 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.security.identity;
+
+import android.annotation.NonNull;
+
+/**
+ * Thrown if the ephemeral public key was not found in the session transcript
+ * passed to {@link IdentityCredential#getEntries(byte[], Map, byte[], byte[])}.
+ */
+public class EphemeralPublicKeyNotFoundException extends IdentityCredentialException {
+    /**
+     * Constructs a new {@link EphemeralPublicKeyNotFoundException} exception.
+     *
+     * @param message the detail message.
+     */
+    public EphemeralPublicKeyNotFoundException(@NonNull String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new {@link EphemeralPublicKeyNotFoundException} exception.
+     *
+     * @param message the detail message.
+     * @param cause   the cause.
+     */
+    public EphemeralPublicKeyNotFoundException(@NonNull String message, @NonNull Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/identity/java/android/security/identity/IdentityCredential.java b/identity/java/android/security/identity/IdentityCredential.java
new file mode 100644
index 0000000..bd43919
--- /dev/null
+++ b/identity/java/android/security/identity/IdentityCredential.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright 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.security.identity;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Class used to read data from a previously provisioned credential.
+ *
+ * Use {@link IdentityCredentialStore#getCredentialByName(String, int)} to get a
+ * {@link IdentityCredential} instance.
+ */
+public abstract class IdentityCredential {
+    /**
+     * @hide
+     */
+    protected IdentityCredential() {}
+
+    /**
+     * Create an ephemeral key pair to use to establish a secure channel with a reader.
+     *
+     * <p>Most applications will use only the public key, and only to send it to the reader,
+     * allowing the private key to be used internally for {@link #encryptMessageToReader(byte[])}
+     * and {@link #decryptMessageFromReader(byte[])}. The private key is also provided for
+     * applications that wish to use a cipher suite that is not supported by
+     * {@link IdentityCredentialStore}.
+     *
+     * @return ephemeral key pair to use to establish a secure channel with a reader.
+     */
+    public @NonNull abstract KeyPair createEphemeralKeyPair();
+
+    /**
+     * Set the ephemeral public key provided by the reader. This must be called before
+     * {@link #encryptMessageToReader} or {@link #decryptMessageFromReader} can be called.
+     *
+     * @param readerEphemeralPublicKey The ephemeral public key provided by the reader to
+     *                                 establish a secure session.
+     * @throws InvalidKeyException if the given key is invalid.
+     */
+    public abstract void setReaderEphemeralPublicKey(@NonNull PublicKey readerEphemeralPublicKey)
+            throws InvalidKeyException;
+
+    /**
+     * Encrypt a message for transmission to the reader.
+     *
+     * @param messagePlaintext unencrypted message to encrypt.
+     * @return encrypted message.
+     */
+    public @NonNull abstract byte[] encryptMessageToReader(@NonNull byte[] messagePlaintext);
+
+    /**
+     * Decrypt a message received from the reader.
+     *
+     * @param messageCiphertext encrypted message to decrypt.
+     * @return decrypted message.
+     * @throws MessageDecryptionException if the ciphertext couldn't be decrypted.
+     */
+    public @NonNull abstract byte[] decryptMessageFromReader(@NonNull byte[] messageCiphertext)
+            throws MessageDecryptionException;
+
+    /**
+     * Gets the X.509 certificate chain for the CredentialKey which identifies this
+     * credential to the issuing authority. This is the same certificate chain that
+     * was returned by {@link WritableIdentityCredential#getCredentialKeyCertificateChain(byte[])}
+     * when the credential was first created and its Android Keystore extension will
+     * contain the <code>challenge</code> data set at that time. See the documentation
+     * for that method for important information about this certificate chain.
+     *
+     * @return the certificate chain for this credential's CredentialKey.
+     */
+    public @NonNull abstract Collection<X509Certificate> getCredentialKeyCertificateChain();
+
+    /**
+     * Sets whether to allow using an authentication key which use count has been exceeded if no
+     * other key is available. This must be called prior to calling
+     * {@link #getEntries(byte[], Map, byte[], byte[])} or using a
+     * {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} which references this
+     * object.
+     *
+     * By default this is set to true.
+     *
+     * @param allowUsingExhaustedKeys whether to allow using an authentication key which use count
+     *                                has been exceeded if no other key is available.
+     */
+    public abstract void setAllowUsingExhaustedKeys(boolean allowUsingExhaustedKeys);
+
+    /**
+     * Called by android.hardware.biometrics.CryptoObject#getOpId() to get an
+     * operation handle.
+     *
+     * @hide
+     */
+    public abstract long getCredstoreOperationHandle();
+
+    /**
+     * Retrieve data entries and associated data from this {@code IdentityCredential}.
+     *
+     * <p>If an access control check fails for one of the requested entries or if the entry
+     * doesn't exist, the entry is simply not returned. The application can detect this
+     * by using the {@link ResultData#getStatus(String, String)} method on each of the requested
+     * entries.
+     *
+     * <p>It is the responsibility of the calling application to know if authentication is needed
+     * and use e.g. {@link android.hardware.biometrics.BiometricPrompt}) to make the user
+     * authenticate using a {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} which
+     * references this object. If needed, this must be done before calling
+     * {@link #getEntries(byte[], Map, byte[], byte[])}.
+     *
+     * <p>If this method returns successfully (i.e. without throwing an exception), it must not be
+     * called again on this instance.
+     *
+     * <p>If not {@code null} the {@code requestMessage} parameter must contain data for the request
+     * from the verifier. The content can be defined in the way appropriate for the credential, byt
+     * there are three requirements that must be met to work with this API:
+     * <ul>
+     * <li>The content must be a CBOR-encoded structure.</li>
+     * <li>The CBOR structure must be a map.</li>
+     * <li>The map must contain a tstr key "nameSpaces" whose value contains a map, as described in
+     *     the example below.</li>
+     * </ul>
+     *
+     * <p>Here's an example of CBOR which conforms to this requirement:
+     * <pre>
+     *   ItemsRequest = {
+     *     ? "docType" : DocType,
+     *     "nameSpaces" : NameSpaces,
+     *     ? "RequestInfo" : {* tstr => any} ; Additional info the reader wants to provide
+     *   }
+     *
+     *   NameSpaces = {
+     *     + NameSpace => DataElements    ; Requested data elements for each NameSpace
+     *   }
+     *
+     *   NameSpace = tstr
+     *
+     *   DataElements = {
+     *     + DataElement => IntentToRetain
+     *   }
+     *
+     *   DataElement = tstr
+     *   IntentToRetain = bool
+     * </pre>
+     *
+     * <p>If the {@code sessionTranscript} parameter is not {@code null}, it must contain CBOR
+     * data conforming to the following CDDL schema:
+     *
+     * <pre>
+     *   SessionTranscript = [
+     *     DeviceEngagementBytes,
+     *     EReaderKeyBytes
+     *   ]
+     *
+     *   DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement)
+     *   EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub)
+     * </pre>
+     *
+     * <p>If the SessionTranscript is not empty, a COSE_Key structure for the public part
+     * of the key-pair previously generated by {@link #createEphemeralKeyPair()} must appear
+     * somewhere in {@code DeviceEngagement} and the X and Y coordinates must both be present
+     * in uncompressed form.
+     *
+     * <p>If {@code readerAuth} is not {@code null} it must be the bytes of a COSE_Sign1
+     * structure as defined in RFC 8152. For the payload nil shall be used and the
+     * detached payload is the ReaderAuthentication CBOR described below.
+     * <pre>
+     *     ReaderAuthentication = [
+     *       "ReaderAuthentication",
+     *       SessionTranscript,
+     *       ItemsRequestBytes
+     *     ]
+     *
+     *     ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest)   ; Bytes of ItemsRequest
+     * </pre>
+     *
+     * <p>The public key corresponding to the key used to made signature, can be
+     * found in the {@code x5chain} unprotected header element of the COSE_Sign1
+     * structure (as as described in 'draft-ietf-cose-x509-04'). There will be at
+     * least one certificate in said element and there may be more (and if so,
+     * each certificate must be signed by its successor).
+     *
+     * <p>Data elements protected by reader authentication is returned if, and only if, they are
+     * mentioned in {@code requestMessage}, {@code requestMessage} is signed by the top-most
+     * certificate in {@code readerCertificateChain}, and the data element is configured
+     * with an {@link AccessControlProfile} with a {@link X509Certificate} in
+     * {@code readerCertificateChain}.
+     *
+     * <p>Note that only items referenced in {@code entriesToRequest} are returned - the
+     * {@code requestMessage} parameter is only used to for enforcing reader authentication.
+     *
+     * @param requestMessage         If not {@code null}, must contain CBOR data conforming to
+     *                               the schema mentioned above.
+     * @param entriesToRequest       The entries to request, organized as a map of namespace
+     *                               names with each value being a collection of data elements
+     *                               in the given namespace.
+     * @param readerSignature        COSE_Sign1 structure as described above or {@code null}
+     *                               if reader authentication is not being used.
+     * @return A {@link ResultData} object containing entry data organized by namespace and a
+     *         cryptographically authenticated representation of the same data.
+     * @throws SessionTranscriptMismatchException     Thrown when trying use multiple different
+     *                                                session transcripts in the same presentation
+     *                                                session.
+     * @throws NoAuthenticationKeyAvailableException  if authentication keys were never
+     *                                                provisioned, the method
+     *                                             {@link #setAvailableAuthenticationKeys(int, int)}
+     *                                                was called with {@code keyCount} set to 0,
+     *                                                the method
+     *                                                {@link #setAllowUsingExhaustedKeys(boolean)}
+     *                                                was called with {@code false} and all
+     *                                                available authentication keys have been
+     *                                                exhausted.
+     * @throws InvalidReaderSignatureException        if the reader signature is invalid, or it
+     *                                                doesn't contain a certificate chain, or if
+     *                                                the signature failed to validate.
+     * @throws InvalidRequestMessageException         if the requestMessage is malformed.
+     * @throws EphemeralPublicKeyNotFoundException    if the ephemeral public key was not found in
+     *                                                the session transcript.
+     */
+    public abstract @NonNull ResultData getEntries(
+            @Nullable byte[] requestMessage,
+            @NonNull Map<String, Collection<String>> entriesToRequest,
+            @Nullable byte[] sessionTranscript,
+            @Nullable byte[] readerSignature)
+            throws SessionTranscriptMismatchException, NoAuthenticationKeyAvailableException,
+            InvalidReaderSignatureException, EphemeralPublicKeyNotFoundException,
+            InvalidRequestMessageException;
+
+    /**
+     * Sets the number of dynamic authentication keys the {@code IdentityCredential} will maintain,
+     * and the number of times each should be used.
+     *
+     * <p>{@code IdentityCredential}s will select the least-used dynamic authentication key each
+     * time {@link #getEntries(byte[], Map, byte[], byte[])} is called. {@code IdentityCredential}s
+     * for which this method has not been called behave as though it had been called wit
+     * {@code keyCount} 0 and {@code maxUsesPerKey} 1.
+     *
+     * @param keyCount      The number of active, certified dynamic authentication keys the
+     *                      {@code IdentityCredential} will try to keep available. This value
+     *                      must be non-negative.
+     * @param maxUsesPerKey The maximum number of times each of the keys will be used before it's
+     *                      eligible for replacement. This value must be greater than zero.
+     */
+    public abstract void setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey);
+
+    /**
+     * Gets a collection of dynamic authentication keys that need certification.
+     *
+     * <p>When there aren't enough certified dynamic authentication keys, either because the key
+     * count has been increased or because one or more keys have reached their usage count, this
+     * method will generate replacement keys and certificates and return them for issuer
+     * certification. The issuer certificates and associated static authentication data must then
+     * be provided back to the {@code IdentityCredential} using
+     * {@link #storeStaticAuthenticationData(X509Certificate, byte[])}.
+     *
+     * <p>Each X.509 certificate is signed by CredentialKey. The certificate chain for CredentialKey
+     * can be obtained using the {@link #getCredentialKeyCertificateChain()} method.
+     *
+     * @return A collection of X.509 certificates for dynamic authentication keys that need issuer
+     * certification.
+     */
+    public @NonNull abstract Collection<X509Certificate> getAuthKeysNeedingCertification();
+
+    /**
+     * Store authentication data associated with a dynamic authentication key.
+     *
+     * This should only be called for an authenticated key returned by
+     * {@link #getAuthKeysNeedingCertification()}.
+     *
+     * @param authenticationKey The dynamic authentication key for which certification and
+     *                          associated static
+     *                          authentication data is being provided.
+     * @param staticAuthData    Static authentication data provided by the issuer that validates
+     *                          the authenticity
+     *                          and integrity of the credential data fields.
+     * @throws UnknownAuthenticationKeyException If the given authentication key is not recognized.
+     */
+    public abstract void storeStaticAuthenticationData(
+            @NonNull X509Certificate authenticationKey,
+            @NonNull byte[] staticAuthData)
+            throws UnknownAuthenticationKeyException;
+
+    /**
+     * Get the number of times the dynamic authentication keys have been used.
+     *
+     * @return int array of dynamic authentication key usage counts.
+     */
+    public @NonNull abstract int[] getAuthenticationDataUsageCount();
+}
diff --git a/identity/java/android/security/identity/IdentityCredentialException.java b/identity/java/android/security/identity/IdentityCredentialException.java
new file mode 100644
index 0000000..c811380
--- /dev/null
+++ b/identity/java/android/security/identity/IdentityCredentialException.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 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.security.identity;
+
+import android.annotation.NonNull;
+
+/**
+ * Base class for all Identity Credential exceptions.
+ */
+public class IdentityCredentialException extends Exception {
+    /**
+     * Constructs a new {@link IdentityCredentialException} exception.
+     *
+     * @param message the detail message.
+     */
+    public IdentityCredentialException(@NonNull String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new {@link IdentityCredentialException} exception.
+     *
+     * @param message the detail message.
+     * @param cause   the cause.
+     */
+    public IdentityCredentialException(@NonNull String message, @NonNull Throwable cause) {
+        super(message, cause);
+    }
+
+}
diff --git a/identity/java/android/security/identity/IdentityCredentialStore.java b/identity/java/android/security/identity/IdentityCredentialStore.java
new file mode 100644
index 0000000..a1dfc77
--- /dev/null
+++ b/identity/java/android/security/identity/IdentityCredentialStore.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 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.security.identity;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * An interface to a secure store for user identity documents.
+ *
+ * <p>This interface is deliberately fairly general and abstract.  To the extent possible,
+ * specification of the message formats and semantics of communication with credential
+ * verification devices and issuing authorities (IAs) is out of scope. It provides the
+ * interface with secure storage but a credential-specific Android application will be
+ * required to implement the presentation and verification protocols and processes
+ * appropriate for the specific credential type.
+ *
+ * <p>Multiple credentials can be created.  Each credential comprises:</p>
+ * <ul>
+ * <li>A document type, which is a string.</li>
+ *
+ * <li>A set of namespaces, which serve to disambiguate value names. It is recommended
+ * that namespaces be structured as reverse domain names so that IANA effectively serves
+ * as the namespace registrar.</li>
+ *
+ * <li>For each namespace, a set of name/value pairs, each with an associated set of
+ * access control profile IDs.  Names are strings and values are typed and can be any
+ * value supported by <a href="http://cbor.io/">CBOR</a>.</li>
+ *
+ * <li>A set of access control profiles, each with a profile ID and a specification
+ * of the conditions which satisfy the profile's requirements.</li>
+ *
+ * <li>An asymmetric key pair which is used to authenticate the credential to the Issuing
+ * Authority, called the <em>CredentialKey</em>.</li>
+ *
+ * <li>A set of zero or more named reader authentication public keys, which are used to
+ * authenticate an authorized reader to the credential.</li>
+ *
+ * <li>A set of named signing keys, which are used to sign collections of values and session
+ * transcripts.</li>
+ * </ul>
+ *
+ * <p>Implementing support for user identity documents in secure storage requires dedicated
+ * hardware-backed support and may not always be available.
+ *
+ * <p>Two different credential stores exist - the <em>default</em> store and the
+ * <em>direct access</em> store. Most often credentials will be accessed through the default
+ * store but that requires that the Android device be powered up and fully functional.
+ * It is desirable to allow identity credential usage when the Android device's battery is too
+ * low to boot the Android operating system, so direct access to the secure hardware via NFC
+ * may allow data retrieval, if the secure hardware chooses to implement it.
+ *
+ * <p>Credentials provisioned to the direct access store should <strong>always</strong> use reader
+ * authentication to protect data elements. The reason for this is user authentication or user
+ * approval of data release is not possible when the device is off.
+ */
+public abstract class IdentityCredentialStore {
+    IdentityCredentialStore() {}
+
+    /**
+     * Specifies that the cipher suite that will be used to secure communications between the reader
+     * is:
+     *
+     * <ul>
+     * <li>ECDHE with HKDF-SHA-256 for key agreement.</li>
+     * <li>AES-256 with GCM block mode for authenticated encryption (nonces are incremented by one
+     * for every message).</li>
+     * <li>ECDSA with SHA-256 for signing (used for signing session transcripts to defeat
+     * man-in-the-middle attacks), signing keys are not ephemeral. See {@link IdentityCredential}
+     * for details on reader and prover signing keys.</li>
+     * </ul>
+     *
+     * <p>
+     * At present this is the only supported cipher suite.
+     */
+    public static final int CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256 = 1;
+
+    /**
+     * Gets the default {@link IdentityCredentialStore}.
+     *
+     * @param context the application context.
+     * @return the {@link IdentityCredentialStore} or {@code null} if the device doesn't
+     *     have hardware-backed support for secure storage of user identity documents.
+     */
+    public static @Nullable IdentityCredentialStore getInstance(@NonNull Context context) {
+        return CredstoreIdentityCredentialStore.getInstance(context);
+    }
+
+    /**
+     * Gets the {@link IdentityCredentialStore} for direct access.
+     *
+     * <p>Direct access requires specialized NFC hardware and may not be supported on all
+     * devices even if default store is available. Credentials provisioned to the direct
+     * access store should <strong>always</strong> use reader authentication to protect
+     * data elements.
+     *
+     * @param context the application context.
+     * @return the {@link IdentityCredentialStore} or {@code null} if direct access is not
+     *     supported on this device.
+     */
+    public static @Nullable IdentityCredentialStore getDirectAccessInstance(@NonNull
+            Context context) {
+        return CredstoreIdentityCredentialStore.getDirectAccessInstance(context);
+    }
+
+    /**
+     * Gets a list of supported document types.
+     *
+     * <p>Only the direct-access store may restrict the kind of document types that can be used for
+     * credentials. The default store always supports any document type.
+     *
+     * @return The supported document types or the empty array if any document type is supported.
+     */
+    public abstract @NonNull String[] getSupportedDocTypes();
+
+    /**
+     * Creates a new credential.
+     *
+     * @param credentialName The name used to identify the credential.
+     * @param docType        The document type for the credential.
+     * @return A @{link WritableIdentityCredential} that can be used to create a new credential.
+     * @throws AlreadyPersonalizedException if a credential with the given name already exists.
+     * @throws DocTypeNotSupportedException if the given document type isn't supported by the store.
+     */
+    public abstract @NonNull WritableIdentityCredential createCredential(
+            @NonNull String credentialName, @NonNull String docType)
+            throws AlreadyPersonalizedException, DocTypeNotSupportedException;
+
+    /**
+     * Retrieve a named credential.
+     *
+     * @param credentialName the name of the credential to retrieve.
+     * @param cipherSuite    the cipher suite to use for communicating with the verifier.
+     * @return The named credential, or null if not found.
+     */
+    public abstract @Nullable IdentityCredential getCredentialByName(@NonNull String credentialName,
+            @Ciphersuite int cipherSuite)
+            throws CipherSuiteNotSupportedException;
+
+    /**
+     * Delete a named credential.
+     *
+     * <p>This method returns a COSE_Sign1 data structure signed by the CredentialKey
+     * with payload set to {@code ProofOfDeletion} as defined below:
+     *
+     * <pre>
+     *     ProofOfDeletion = [
+     *          "ProofOfDeletion",            ; tstr
+     *          tstr,                         ; DocType
+     *          bool                          ; true if this is a test credential, should
+     *                                        ; always be false.
+     *      ]
+     * </pre>
+     *
+     * @param credentialName the name of the credential to delete.
+     * @return {@code null} if the credential was not found, the COSE_Sign1 data structure above
+     *     if the credential was found and deleted.
+     */
+    public abstract @Nullable byte[] deleteCredentialByName(@NonNull String credentialName);
+
+    /** @hide */
+    @IntDef(value = {CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Ciphersuite {
+    }
+
+}
diff --git a/identity/java/android/security/identity/InvalidReaderSignatureException.java b/identity/java/android/security/identity/InvalidReaderSignatureException.java
new file mode 100644
index 0000000..3f70270
--- /dev/null
+++ b/identity/java/android/security/identity/InvalidReaderSignatureException.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 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.security.identity;
+
+import android.annotation.NonNull;
+
+/**
+ * Thrown if the reader signature is invalid, or it doesn't contain a certificate chain, or if the
+ * signature failed to validate.
+ */
+public class InvalidReaderSignatureException extends IdentityCredentialException {
+    /**
+     * Constructs a new {@link InvalidReaderSignatureException} exception.
+     *
+     * @param message the detail message.
+     */
+    public InvalidReaderSignatureException(@NonNull String message) {
+        super(message);
+    }
+
+
+    /**
+     * Constructs a new {@link InvalidReaderSignatureException} exception.
+     *
+     * @param message the detail message.
+     * @param cause   the cause.
+     */
+    public InvalidReaderSignatureException(@NonNull String message,
+            @NonNull Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/identity/java/android/security/identity/InvalidRequestMessageException.java b/identity/java/android/security/identity/InvalidRequestMessageException.java
new file mode 100644
index 0000000..b0c073c
--- /dev/null
+++ b/identity/java/android/security/identity/InvalidRequestMessageException.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 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.security.identity;
+
+import android.annotation.NonNull;
+
+/**
+ * Thrown if message with the request doesn't satisfy the requirements documented in
+ * {@link IdentityCredential#getEntries(byte[], Map, byte[], byte[])}.
+ */
+public class InvalidRequestMessageException extends IdentityCredentialException {
+    /**
+     * Constructs a new {@link InvalidRequestMessageException} exception.
+     *
+     * @param message the detail message.
+     */
+    public InvalidRequestMessageException(@NonNull String message) {
+        super(message);
+    }
+
+
+    /**
+     * Constructs a new {@link InvalidRequestMessageException} exception.
+     *
+     * @param message the detail message.
+     * @param cause   the cause.
+     */
+    public InvalidRequestMessageException(@NonNull String message,
+            @NonNull Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/identity/java/android/security/identity/MessageDecryptionException.java b/identity/java/android/security/identity/MessageDecryptionException.java
new file mode 100644
index 0000000..7a6169e
--- /dev/null
+++ b/identity/java/android/security/identity/MessageDecryptionException.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 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.security.identity;
+
+import android.annotation.NonNull;
+
+/**
+ * Thrown when failing to decrypt a message from the reader device.
+ */
+public class MessageDecryptionException extends IdentityCredentialException {
+
+    /**
+     * Constructs a new {@link MessageDecryptionException} exception.
+     *
+     * @param message the detail message.
+     */
+    public MessageDecryptionException(@NonNull String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new {@link MessageDecryptionException} exception.
+     *
+     * @param message the detail message.
+     * @param cause   the cause.
+     */
+    public MessageDecryptionException(@NonNull String message, @NonNull Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/identity/java/android/security/identity/NoAuthenticationKeyAvailableException.java b/identity/java/android/security/identity/NoAuthenticationKeyAvailableException.java
new file mode 100644
index 0000000..7f40403
--- /dev/null
+++ b/identity/java/android/security/identity/NoAuthenticationKeyAvailableException.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 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.security.identity;
+
+import android.annotation.NonNull;
+
+/**
+ * Thrown if no dynamic authentication keys are available.
+ */
+public class NoAuthenticationKeyAvailableException extends IdentityCredentialException {
+
+    /**
+     * Constructs a new {@link NoAuthenticationKeyAvailableException} exception.
+     *
+     * @param message the detail message.
+     */
+    public NoAuthenticationKeyAvailableException(@NonNull String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new {@link NoAuthenticationKeyAvailableException} exception.
+     *
+     * @param message the detail message.
+     * @param cause   the cause.
+     */
+    public NoAuthenticationKeyAvailableException(@NonNull String message,
+            @NonNull Throwable cause) {
+        super(message, cause);
+    }
+
+}
diff --git a/identity/java/android/security/identity/PersonalizationData.java b/identity/java/android/security/identity/PersonalizationData.java
new file mode 100644
index 0000000..44370a1
--- /dev/null
+++ b/identity/java/android/security/identity/PersonalizationData.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 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.security.identity;
+
+import android.annotation.NonNull;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+
+/**
+ * An object that holds personalization data.
+ *
+ * This data includes access control profiles and a set of data entries and values, grouped by
+ * namespace.
+ *
+ * This is used to provision data into a {@link WritableIdentityCredential}.
+ *
+ * @see WritableIdentityCredential#personalize
+ */
+public class PersonalizationData {
+
+    private PersonalizationData() {
+    }
+
+    private LinkedList<AccessControlProfile> mProfiles = new LinkedList<>();
+
+    private LinkedHashMap<String, NamespaceData> mNamespaces = new LinkedHashMap<>();
+
+    Collection<AccessControlProfile> getAccessControlProfiles() {
+        return Collections.unmodifiableCollection(mProfiles);
+    }
+
+    Collection<String> getNamespaceNames() {
+        return Collections.unmodifiableCollection(mNamespaces.keySet());
+    }
+
+    NamespaceData getNamespaceData(String namespace) {
+        return mNamespaces.get(namespace);
+    }
+
+    static class NamespaceData {
+
+        private String mNamespace;
+        private LinkedHashMap<String, EntryData> mEntries = new LinkedHashMap<>();
+
+        private NamespaceData(String namespace) {
+            this.mNamespace = namespace;
+        }
+
+        String getNamespaceName() {
+            return mNamespace;
+        }
+
+        Collection<String> getEntryNames() {
+            return Collections.unmodifiableCollection(mEntries.keySet());
+        }
+
+        Collection<AccessControlProfileId> getAccessControlProfileIds(String name) {
+            EntryData value = mEntries.get(name);
+            if (value != null) {
+                return value.mAccessControlProfileIds;
+            }
+            return null;
+        }
+
+        byte[] getEntryValue(String name) {
+            EntryData value = mEntries.get(name);
+            if (value != null) {
+                return value.mValue;
+            }
+            return null;
+        }
+    }
+
+    private static class EntryData {
+        byte[] mValue;
+        Collection<AccessControlProfileId> mAccessControlProfileIds;
+
+        EntryData(byte[] value, Collection<AccessControlProfileId> accessControlProfileIds) {
+            this.mValue = value;
+            this.mAccessControlProfileIds = accessControlProfileIds;
+        }
+    }
+
+    /**
+     * A builder for {@link PersonalizationData}.
+     */
+    public static final class Builder {
+        private PersonalizationData mData;
+
+        /**
+         * Creates a new builder for a given namespace.
+         */
+        public Builder() {
+            this.mData = new PersonalizationData();
+        }
+
+        /**
+         * Adds a new entry to the builder.
+         *
+         * @param namespace               The namespace to use, e.g. {@code org.iso.18013-5.2019}.
+         * @param name                    The name of the entry, e.g. {@code height}.
+         * @param accessControlProfileIds A set of access control profiles to use.
+         * @param value                   The value to add, in CBOR encoding.
+         * @return The builder.
+         */
+        public @NonNull Builder setEntry(@NonNull String namespace, @NonNull String name,
+                @NonNull Collection<AccessControlProfileId> accessControlProfileIds,
+                @NonNull byte[] value) {
+            NamespaceData namespaceData = mData.mNamespaces.get(namespace);
+            if (namespaceData == null) {
+                namespaceData = new NamespaceData(namespace);
+                mData.mNamespaces.put(namespace, namespaceData);
+            }
+            // TODO: validate/verify that value is proper CBOR.
+            namespaceData.mEntries.put(name, new EntryData(value, accessControlProfileIds));
+            return this;
+        }
+
+        /**
+         * Adds a new access control profile to the builder.
+         *
+         * @param profile The access control profile.
+         * @return The builder.
+         */
+        public @NonNull Builder addAccessControlProfile(@NonNull AccessControlProfile profile) {
+            mData.mProfiles.add(profile);
+            return this;
+        }
+
+        /**
+         * Creates a new {@link PersonalizationData} with all the entries added to the builder.
+         *
+         * @return A new {@link PersonalizationData} instance.
+         */
+        public @NonNull PersonalizationData build() {
+            return mData;
+        }
+    }
+
+}
diff --git a/identity/java/android/security/identity/ResultData.java b/identity/java/android/security/identity/ResultData.java
new file mode 100644
index 0000000..0982c8a
--- /dev/null
+++ b/identity/java/android/security/identity/ResultData.java
@@ -0,0 +1,224 @@
+/*
+ * 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.security.identity;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.lang.annotation.Retention;
+import java.util.Collection;
+
+/**
+ * An object that contains the result of retrieving data from a credential. This is used to return
+ * data requested from a {@link IdentityCredential}.
+ */
+public abstract class ResultData {
+
+    /** Value was successfully retrieved. */
+    public static final int STATUS_OK = 0;
+
+    /** Requested entry does not exist. */
+    public static final int STATUS_NO_SUCH_ENTRY = 1;
+
+    /** Requested entry was not requested. */
+    public static final int STATUS_NOT_REQUESTED = 2;
+
+    /** Requested entry wasn't in the request message. */
+    public static final int STATUS_NOT_IN_REQUEST_MESSAGE = 3;
+
+    /** The requested entry was not retrieved because user authentication wasn't performed. */
+    public static final int STATUS_USER_AUTHENTICATION_FAILED = 4;
+
+    /** The requested entry was not retrieved because reader authentication wasn't performed. */
+    public static final int STATUS_READER_AUTHENTICATION_FAILED = 5;
+
+    /**
+     * The requested entry was not retrieved because it was configured without any access
+     * control profile.
+     */
+    public static final int STATUS_NO_ACCESS_CONTROL_PROFILES = 6;
+
+    /**
+     * @hide
+     */
+    protected ResultData() {}
+
+    /**
+     * Returns a CBOR structure containing the retrieved data.
+     *
+     * <p>This structure - along with the session transcript - may be cryptographically
+     * authenticated to prove to the reader that the data is from a trusted credential and
+     * {@link #getMessageAuthenticationCode()} can be used to get a MAC.
+     *
+     * <p>The CBOR structure which is cryptographically authenticated is the
+     * {@code DeviceAuthentication} structure according to the following
+     * <a href="https://tools.ietf.org/html/draft-ietf-cbor-cddl-06">CDDL</a> schema:
+     *
+     * <pre>
+     *   DeviceAuthentication = [
+     *     "DeviceAuthentication",
+     *     SessionTranscript,
+     *     DocType,
+     *     DeviceNameSpacesBytes
+     *   ]
+     *
+     *   DocType = tstr
+     *
+     *   SessionTranscript = [
+     *     DeviceEngagementBytes,
+     *     EReaderKeyBytes
+     *   ]
+     *
+     *   DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement)
+     *   EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub)
+     *
+     *   DeviceNameSpacesBytes = #6.24(bstr .cbor DeviceNameSpaces)
+     * </pre>
+     *
+     * where
+     *
+     * <pre>
+     *   DeviceNameSpaces = {
+     *     * NameSpace => DeviceSignedItems
+     *   }
+     *
+     *   DeviceSignedItems = {
+     *     + DataItemName => DataItemValue
+     *   }
+     *
+     *   NameSpace = tstr
+     *   DataItemName = tstr
+     *   DataItemValue = any
+     * </pre>
+     *
+     * <p>The returned data is the binary encoding of the {@code DeviceNameSpaces} structure
+     * as defined above.
+     *
+     * @return The bytes of the {@code DeviceNameSpaces} CBOR structure.
+     */
+    public abstract @NonNull byte[] getAuthenticatedData();
+
+    /**
+     * Returns a message authentication code over the data returned by
+     * {@link #getAuthenticatedData}, to prove to the reader that the data is from a trusted
+     * credential.
+     *
+     * <p>The MAC proves to the reader that the data is from a trusted credential. This code is
+     * produced by using the key agreement and key derivation function from the ciphersuite
+     * with the authentication private key and the reader ephemeral public key to compute a
+     * shared message authentication code (MAC) key, then using the MAC function from the
+     * ciphersuite to compute a MAC of the authenticated data.
+     *
+     * <p>If the {@code sessionTranscript} parameter passed to
+     * {@link IdentityCredential#getEntries(byte[], Map, byte[], byte[])} was {@code null}
+     * or the reader ephmeral public key was never set using
+     * {@link IdentityCredential#setReaderEphemeralPublicKey(PublicKey)}, no message
+     * authencation code will be produced and this method will return {@code null}.
+     *
+     * @return A COSE_Mac0 structure with the message authentication code as described above
+     *         or {@code null} if the conditions specified above are not met.
+     */
+    public abstract @Nullable byte[] getMessageAuthenticationCode();
+
+    /**
+     * Returns the static authentication data associated with the dynamic authentication
+     * key used to sign or MAC the data returned by {@link #getAuthenticatedData()}.
+     *
+     * @return The static authentication data associated with dynamic authentication key used to
+     * MAC the data.
+     */
+    public abstract @NonNull byte[] getStaticAuthenticationData();
+
+    /**
+     * Gets the names of namespaces with retrieved entries.
+     *
+     * @return collection of name of namespaces containing retrieved entries. May be empty if no
+     *     data was retrieved.
+     */
+    public abstract @NonNull Collection<String> getNamespaceNames();
+
+    /**
+     * Get the names of all entries.
+     *
+     * This includes the name of entries that wasn't successfully retrieved.
+     *
+     * @param namespaceName the namespace name to get entries for.
+     * @return A collection of names or {@code null} if there are no entries for the given
+     *     namespace.
+     */
+    public abstract @Nullable Collection<String> getEntryNames(@NonNull String namespaceName);
+
+    /**
+     * Get the names of all entries that was successfully retrieved.
+     *
+     * This only return entries for which {@link #getStatus(String, String)} will return
+     * {@link #STATUS_OK}.
+     *
+     * @param namespaceName the namespace name to get entries for.
+     * @return A collection of names or {@code null} if there are no entries for the given
+     *     namespace.
+     */
+    public abstract @Nullable Collection<String> getRetrievedEntryNames(
+            @NonNull String namespaceName);
+
+    /**
+     * Gets the status of an entry.
+     *
+     * This returns {@link #STATUS_OK} if the value was retrieved, {@link #STATUS_NO_SUCH_ENTRY}
+     * if the given entry wasn't retrieved, {@link #STATUS_NOT_REQUESTED} if it wasn't requested,
+     * {@link #STATUS_NOT_IN_REQUEST_MESSAGE} if the request message was set but the entry wasn't
+     * present in the request message,
+     * {@link #STATUS_USER_AUTHENTICATION_FAILED} if the value
+     * wasn't retrieved because the necessary user authentication wasn't performed,
+     * {@link #STATUS_READER_AUTHENTICATION_FAILED} if the supplied reader certificate chain
+     * didn't match the set of certificates the entry was provisioned with, or
+     * {@link #STATUS_NO_ACCESS_CONTROL_PROFILES} if the entry was configured without any
+     * access control profiles.
+     *
+     * @param namespaceName the namespace name of the entry.
+     * @param name the name of the entry to get the value for.
+     * @return the status indicating whether the value was retrieved and if not, why.
+     */
+    @Status
+    public abstract int getStatus(@NonNull String namespaceName, @NonNull String name);
+
+    /**
+     * Gets the raw CBOR data for the value of an entry.
+     *
+     * This should only be called on an entry for which the {@link #getStatus(String, String)}
+     * method returns {@link #STATUS_OK}.
+     *
+     * @param namespaceName the namespace name of the entry.
+     * @param name the name of the entry to get the value for.
+     * @return the raw CBOR data or {@code null} if no entry with the given name exists.
+     */
+    public abstract @Nullable byte[] getEntry(@NonNull String namespaceName, @NonNull String name);
+
+    /**
+     * The type of the entry status.
+     * @hide
+     */
+    @Retention(SOURCE)
+    @IntDef({STATUS_OK, STATUS_NO_SUCH_ENTRY, STATUS_NOT_REQUESTED, STATUS_NOT_IN_REQUEST_MESSAGE,
+                        STATUS_USER_AUTHENTICATION_FAILED, STATUS_READER_AUTHENTICATION_FAILED,
+                        STATUS_NO_ACCESS_CONTROL_PROFILES})
+    public @interface Status {
+    }
+}
diff --git a/identity/java/android/security/identity/SessionTranscriptMismatchException.java b/identity/java/android/security/identity/SessionTranscriptMismatchException.java
new file mode 100644
index 0000000..8c24060
--- /dev/null
+++ b/identity/java/android/security/identity/SessionTranscriptMismatchException.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 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.security.identity;
+
+import android.annotation.NonNull;
+
+/**
+ * Thrown when trying use multiple different session transcripts in the same presentation session.
+ */
+public class SessionTranscriptMismatchException extends IdentityCredentialException {
+
+    /**
+     * Constructs a new {@link SessionTranscriptMismatchException} exception.
+     *
+     * @param message the detail message.
+     */
+    public SessionTranscriptMismatchException(@NonNull String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new {@link SessionTranscriptMismatchException} exception.
+     *
+     * @param message the detail message.
+     * @param cause   the cause.
+     */
+    public SessionTranscriptMismatchException(@NonNull String message, @NonNull Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/identity/java/android/security/identity/UnknownAuthenticationKeyException.java b/identity/java/android/security/identity/UnknownAuthenticationKeyException.java
new file mode 100644
index 0000000..f454b2c
--- /dev/null
+++ b/identity/java/android/security/identity/UnknownAuthenticationKeyException.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 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.security.identity;
+
+import android.annotation.NonNull;
+
+/**
+ * Thrown if trying to certify an unknown dynamic authentication key.
+ */
+public class UnknownAuthenticationKeyException extends IdentityCredentialException {
+    /**
+     * Constructs a new {@link UnknownAuthenticationKeyException} exception.
+     *
+     * @param message the detail message.
+     */
+    public UnknownAuthenticationKeyException(@NonNull String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new {@link UnknownAuthenticationKeyException} exception.
+     *
+     * @param message the detail message.
+     * @param cause   the cause.
+     */
+    public UnknownAuthenticationKeyException(@NonNull String message, @NonNull Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/identity/java/android/security/identity/Util.java b/identity/java/android/security/identity/Util.java
new file mode 100644
index 0000000..6eefeb8
--- /dev/null
+++ b/identity/java/android/security/identity/Util.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 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.security.identity;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECPoint;
+import java.util.Collection;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+class Util {
+    private static final String TAG = "Util";
+
+    static int[] integerCollectionToArray(Collection<Integer> collection) {
+        int[] result = new int[collection.size()];
+        int n = 0;
+        for (int item : collection) {
+            result[n++] = item;
+        }
+        return result;
+    }
+
+    static byte[] stripLeadingZeroes(byte[] value) {
+        int n = 0;
+        while (n < value.length && value[n] == 0) {
+            n++;
+        }
+        int newLen = value.length - n;
+        byte[] ret = new byte[newLen];
+        int m = 0;
+        while (n < value.length) {
+            ret[m++] = value[n++];
+        }
+        return ret;
+    }
+
+    static byte[] publicKeyEncodeUncompressedForm(PublicKey publicKey) {
+        ECPoint w = ((ECPublicKey) publicKey).getW();
+        // X and Y are always positive so for interop we remove any leading zeroes
+        // inserted by the BigInteger encoder.
+        byte[] x = stripLeadingZeroes(w.getAffineX().toByteArray());
+        byte[] y = stripLeadingZeroes(w.getAffineY().toByteArray());
+        try {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            baos.write(0x04);
+            baos.write(x);
+            baos.write(y);
+            return baos.toByteArray();
+        } catch (IOException e) {
+            throw new RuntimeException("Unexpected IOException", e);
+        }
+    }
+
+    /**
+     * Computes an HKDF.
+     *
+     * This is based on https://github.com/google/tink/blob/master/java/src/main/java/com/google
+     * /crypto/tink/subtle/Hkdf.java
+     * which is also Copyright (c) Google and also licensed under the Apache 2 license.
+     *
+     * @param macAlgorithm the MAC algorithm used for computing the Hkdf. I.e., "HMACSHA1" or
+     *                     "HMACSHA256".
+     * @param ikm          the input keying material.
+     * @param salt         optional salt. A possibly non-secret random value. If no salt is
+     *                     provided (i.e. if
+     *                     salt has length 0) then an array of 0s of the same size as the hash
+     *                     digest is used as salt.
+     * @param info         optional context and application specific information.
+     * @param size         The length of the generated pseudorandom string in bytes. The maximal
+     *                     size is
+     *                     255.DigestSize, where DigestSize is the size of the underlying HMAC.
+     * @return size pseudorandom bytes.
+     */
+    static byte[] computeHkdf(
+            String macAlgorithm, final byte[] ikm, final byte[] salt, final byte[] info, int size) {
+        Mac mac = null;
+        try {
+            mac = Mac.getInstance(macAlgorithm);
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("No such algorithm: " + macAlgorithm, e);
+        }
+        if (size > 255 * mac.getMacLength()) {
+            throw new RuntimeException("size too large");
+        }
+        try {
+            if (salt == null || salt.length == 0) {
+                // According to RFC 5869, Section 2.2 the salt is optional. If no salt is provided
+                // then HKDF uses a salt that is an array of zeros of the same length as the hash
+                // digest.
+                mac.init(new SecretKeySpec(new byte[mac.getMacLength()], macAlgorithm));
+            } else {
+                mac.init(new SecretKeySpec(salt, macAlgorithm));
+            }
+            byte[] prk = mac.doFinal(ikm);
+            byte[] result = new byte[size];
+            int ctr = 1;
+            int pos = 0;
+            mac.init(new SecretKeySpec(prk, macAlgorithm));
+            byte[] digest = new byte[0];
+            while (true) {
+                mac.update(digest);
+                mac.update(info);
+                mac.update((byte) ctr);
+                digest = mac.doFinal();
+                if (pos + digest.length < size) {
+                    System.arraycopy(digest, 0, result, pos, digest.length);
+                    pos += digest.length;
+                    ctr++;
+                } else {
+                    System.arraycopy(digest, 0, result, pos, size - pos);
+                    break;
+                }
+            }
+            return result;
+        } catch (InvalidKeyException e) {
+            throw new RuntimeException("Error MACing", e);
+        }
+    }
+
+}
diff --git a/identity/java/android/security/identity/WritableIdentityCredential.java b/identity/java/android/security/identity/WritableIdentityCredential.java
new file mode 100644
index 0000000..e2a389b
--- /dev/null
+++ b/identity/java/android/security/identity/WritableIdentityCredential.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 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.security.identity;
+
+import android.annotation.NonNull;
+
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+
+/**
+ * Class used to personalize a new identity credential.
+ *
+ * <p>Credentials cannot be updated or modified after creation; any changes require deletion and
+ * re-creation.
+ *
+ * Use {@link IdentityCredentialStore#createCredential(String, String)} to create a new credential.
+ */
+public abstract class WritableIdentityCredential {
+    /**
+     * @hide
+     */
+    protected WritableIdentityCredential() {}
+
+    /**
+     * Generates and returns an X.509 certificate chain for the CredentialKey which identifies this
+     * credential to the issuing authority. The certificate contains an
+     * <a href="https://source.android.com/security/keystore/attestation">Android Keystore</a>
+     * attestation extension which describes the key and the security hardware in which it lives.
+     *
+     * <p>Additionally, the attestation extension will contain the tag TODO_IC_KEY which indicates
+     * it is an Identity Credential key (which can only sign/MAC very specific messages) and not
+     * an Android Keystore key (which can be used to sign/MAC anything).
+     *
+     * <p>The issuer <b>MUST</b> carefully examine this certificate chain including (but not
+     * limited to) checking that the root certificate is well-known, the tag TODO_IC_KEY is
+     * present, the passed in challenge is present, the device has verified boot enabled, that each
+     * certificate in the chain is signed by its successor, that none of the certificates have been
+     * revoked and so on.
+     *
+     * <p>It is not strictly necessary to use this method to provision a credential if the issuing
+     * authority doesn't care about the nature of the security hardware. If called, however, this
+     * method must be called before {@link #personalize(PersonalizationData)}.
+     *
+     * @param challenge is a byte array whose contents should be unique, fresh and provided by
+     *                  the issuing authority. The value provided is embedded in the attestation
+     *                  extension and enables the issuing authority to verify that the attestation
+     *                  certificate is fresh.
+     * @return the X.509 certificate for this credential's CredentialKey.
+     */
+    public abstract @NonNull Collection<X509Certificate> getCredentialKeyCertificateChain(
+            @NonNull byte[] challenge);
+
+    /**
+     * Stores all of the data in the credential, with the specified access control profiles.
+     *
+     * <p>This method returns a COSE_Sign1 data structure signed by the CredentialKey with payload
+     * set to {@code ProofOfProvisioning} as defined below.
+     *
+     * <pre>
+     *     ProofOfProvisioning = [
+     *          "ProofOfProvisioning",        ; tstr
+     *          tstr,                         ; DocType
+     *          [ * AccessControlProfile ],
+     *          ProvisionedData,
+     *          bool                          ; true if this is a test credential, should
+     *                                        ; always be false.
+     *      ]
+     *
+     *      AccessControlProfile = {
+     *          "id": uint,
+     *          ? "readerCertificate" : bstr,
+     *          ? (
+     *               "userAuthenticationRequired" : bool,
+     *               "timeoutMillis" : uint,
+     *          )
+     *      }
+     *
+     *      ProvisionedData = {
+     *          * Namespace =&gt; [ + Entry ]
+     *      },
+     *
+     *      Namespace = tstr
+     *
+     *      Entry = {
+     *          "name" : tstr,
+     *          "value" : any,
+     *          "accessControlProfiles" : [ * uint ],
+     *      }
+     * </pre>
+     *
+     * <p>This data structure provides a guarantee to the issuer about the data which may be
+     * returned in the CBOR returned by
+     * {@link ResultData#getAuthenticatedData()} during a credential
+     * presentation.
+     *
+     * @param personalizationData   The data to provision, including access control profiles
+     *                              and data elements and their values, grouped into namespaces.
+     * @return A COSE_Sign1 data structure, see above.
+     */
+    public abstract @NonNull byte[] personalize(
+            @NonNull PersonalizationData personalizationData);
+}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
index 17aacb9..fedde42 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
@@ -308,6 +308,9 @@
         if (spec.isStrongBoxBacked()) {
             flags |= KeyStore.FLAG_STRONGBOX;
         }
+        if (spec.isCriticalToDeviceEncryption()) {
+            flags |= KeyStore.FLAG_CRITICAL_TO_DEVICE_ENCRYPTION;
+        }
         String keyAliasInKeystore = Credentials.USER_PRIVATE_KEY + spec.getKeystoreAlias();
         KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics();
         boolean success = false;
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
index 91aac83..c52fd48 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -18,10 +18,8 @@
 
 import android.annotation.Nullable;
 import android.security.Credentials;
-import android.security.GateKeeper;
 import android.security.KeyPairGeneratorSpec;
 import android.security.KeyStore;
-import android.security.KeyStoreException;
 import android.security.keymaster.KeyCharacteristics;
 import android.security.keymaster.KeymasterArguments;
 import android.security.keymaster.KeymasterCertificateChain;
@@ -458,6 +456,9 @@
         if (mSpec.isStrongBoxBacked()) {
             flags |= KeyStore.FLAG_STRONGBOX;
         }
+        if (mSpec.isCriticalToDeviceEncryption()) {
+            flags |= KeyStore.FLAG_CRITICAL_TO_DEVICE_ENCRYPTION;
+        }
 
         byte[] additionalEntropy =
                 KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 52ff9e0..450dd33 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -271,6 +271,7 @@
     private final boolean mIsStrongBoxBacked;
     private final boolean mUserConfirmationRequired;
     private final boolean mUnlockedDeviceRequired;
+    private final boolean mCriticalToDeviceEncryption;
     /*
      * ***NOTE***: All new fields MUST also be added to the following:
      * ParcelableKeyGenParameterSpec class.
@@ -307,7 +308,8 @@
             boolean invalidatedByBiometricEnrollment,
             boolean isStrongBoxBacked,
             boolean userConfirmationRequired,
-            boolean unlockedDeviceRequired) {
+            boolean unlockedDeviceRequired,
+            boolean criticalToDeviceEncryption) {
         if (TextUtils.isEmpty(keyStoreAlias)) {
             throw new IllegalArgumentException("keyStoreAlias must not be empty");
         }
@@ -357,6 +359,7 @@
         mIsStrongBoxBacked = isStrongBoxBacked;
         mUserConfirmationRequired = userConfirmationRequired;
         mUnlockedDeviceRequired = unlockedDeviceRequired;
+        mCriticalToDeviceEncryption = criticalToDeviceEncryption;
     }
 
     /**
@@ -710,6 +713,16 @@
     }
 
     /**
+     * Return whether this key is critical to the device encryption flow.
+     *
+     * @see android.security.KeyStore#FLAG_CRITICAL_TO_DEVICE_ENCRYPTION
+     * @hide
+     */
+    public boolean isCriticalToDeviceEncryption() {
+        return mCriticalToDeviceEncryption;
+    }
+
+    /**
      * Builder of {@link KeyGenParameterSpec} instances.
      */
     public final static class Builder {
@@ -741,6 +754,7 @@
         private boolean mIsStrongBoxBacked = false;
         private boolean mUserConfirmationRequired;
         private boolean mUnlockedDeviceRequired = false;
+        private boolean mCriticalToDeviceEncryption = false;
 
         /**
          * Creates a new instance of the {@code Builder}.
@@ -804,6 +818,7 @@
             mIsStrongBoxBacked = sourceSpec.isStrongBoxBacked();
             mUserConfirmationRequired = sourceSpec.isUserConfirmationRequired();
             mUnlockedDeviceRequired = sourceSpec.isUnlockedDeviceRequired();
+            mCriticalToDeviceEncryption = sourceSpec.isCriticalToDeviceEncryption();
         }
 
         /**
@@ -1339,6 +1354,20 @@
         }
 
         /**
+         * Set whether this key is critical to the device encryption flow
+         *
+         * This is a special flag only available to system servers to indicate the current key
+         * is part of the device encryption flow.
+         *
+         * @see android.security.KeyStore#FLAG_CRITICAL_TO_DEVICE_ENCRYPTION
+         * @hide
+         */
+        public Builder setCriticalToDeviceEncryption(boolean critical) {
+            mCriticalToDeviceEncryption = critical;
+            return this;
+        }
+
+        /**
          * Builds an instance of {@code KeyGenParameterSpec}.
          */
         @NonNull
@@ -1370,7 +1399,8 @@
                     mInvalidatedByBiometricEnrollment,
                     mIsStrongBoxBacked,
                     mUserConfirmationRequired,
-                    mUnlockedDeviceRequired);
+                    mUnlockedDeviceRequired,
+                    mCriticalToDeviceEncryption);
         }
     }
 }
diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
index d8030fb..98e4589 100644
--- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
@@ -16,8 +16,8 @@
 
 package android.security.keystore;
 
-import android.os.Parcelable;
 import android.os.Parcel;
+import android.os.Parcelable;
 
 import java.math.BigInteger;
 import java.security.spec.AlgorithmParameterSpec;
@@ -105,6 +105,7 @@
         out.writeBoolean(mSpec.isStrongBoxBacked());
         out.writeBoolean(mSpec.isUserConfirmationRequired());
         out.writeBoolean(mSpec.isUnlockedDeviceRequired());
+        out.writeBoolean(mSpec.isCriticalToDeviceEncryption());
     }
 
     private static Date readDateOrNull(Parcel in) {
@@ -160,6 +161,7 @@
         final boolean isStrongBoxBacked = in.readBoolean();
         final boolean userConfirmationRequired = in.readBoolean();
         final boolean unlockedDeviceRequired = in.readBoolean();
+        final boolean criticalToDeviceEncryption = in.readBoolean();
         // The KeyGenParameterSpec is intentionally not constructed using a Builder here:
         // The intention is for this class to break if new parameters are added to the
         // KeyGenParameterSpec constructor (whereas using a builder would silently drop them).
@@ -190,7 +192,8 @@
                 invalidatedByBiometricEnrollment,
                 isStrongBoxBacked,
                 userConfirmationRequired,
-                unlockedDeviceRequired);
+                unlockedDeviceRequired,
+                criticalToDeviceEncryption);
     }
 
     public static final @android.annotation.NonNull Creator<ParcelableKeyGenParameterSpec> CREATOR = new Creator<ParcelableKeyGenParameterSpec>() {
diff --git a/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java b/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java
index fca2775..b7d72fc 100644
--- a/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java
+++ b/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java
@@ -84,6 +84,7 @@
                 .setIsStrongBoxBacked(true)
                 .setUserConfirmationRequired(true)
                 .setUnlockedDeviceRequired(true)
+                .setCriticalToDeviceEncryption(true)
                 .build();
     }
 
@@ -115,6 +116,7 @@
         assertThat(spec.isStrongBoxBacked(), is(true));
         assertThat(spec.isUserConfirmationRequired(), is(true));
         assertThat(spec.isUnlockedDeviceRequired(), is(true));
+        assertThat(spec.isCriticalToDeviceEncryption(), is(true));
     }
 
     private Parcel parcelForReading(ParcelableKeyGenParameterSpec spec) {
diff --git a/packages/WindowManager/OWNERS b/libs/WindowManager/OWNERS
similarity index 100%
rename from packages/WindowManager/OWNERS
rename to libs/WindowManager/OWNERS
diff --git a/packages/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
similarity index 100%
rename from packages/WindowManager/Shell/Android.bp
rename to libs/WindowManager/Shell/Android.bp
diff --git a/packages/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml
similarity index 100%
rename from packages/WindowManager/Shell/AndroidManifest.xml
rename to libs/WindowManager/Shell/AndroidManifest.xml
diff --git a/packages/WindowManager/Shell/OWNERS b/libs/WindowManager/Shell/OWNERS
similarity index 100%
rename from packages/WindowManager/Shell/OWNERS
rename to libs/WindowManager/Shell/OWNERS
diff --git a/packages/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
similarity index 100%
rename from packages/WindowManager/Shell/res/values/config.xml
rename to libs/WindowManager/Shell/res/values/config.xml
diff --git a/packages/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShell.java b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShell.java
similarity index 100%
rename from packages/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShell.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShell.java
diff --git a/packages/WindowManager/Shell/tests/Android.bp b/libs/WindowManager/Shell/tests/Android.bp
similarity index 100%
rename from packages/WindowManager/Shell/tests/Android.bp
rename to libs/WindowManager/Shell/tests/Android.bp
diff --git a/packages/WindowManager/Shell/tests/AndroidManifest.xml b/libs/WindowManager/Shell/tests/AndroidManifest.xml
similarity index 100%
rename from packages/WindowManager/Shell/tests/AndroidManifest.xml
rename to libs/WindowManager/Shell/tests/AndroidManifest.xml
diff --git a/packages/WindowManager/Shell/tests/AndroidTest.xml b/libs/WindowManager/Shell/tests/AndroidTest.xml
similarity index 100%
rename from packages/WindowManager/Shell/tests/AndroidTest.xml
rename to libs/WindowManager/Shell/tests/AndroidTest.xml
diff --git a/packages/WindowManager/Shell/tests/res/values/config.xml b/libs/WindowManager/Shell/tests/res/values/config.xml
similarity index 100%
rename from packages/WindowManager/Shell/tests/res/values/config.xml
rename to libs/WindowManager/Shell/tests/res/values/config.xml
diff --git a/packages/WindowManager/Shell/tests/src/com/android/wm/shell/tests/WindowManagerShellTest.java b/libs/WindowManager/Shell/tests/src/com/android/wm/shell/tests/WindowManagerShellTest.java
similarity index 100%
rename from packages/WindowManager/Shell/tests/src/com/android/wm/shell/tests/WindowManagerShellTest.java
rename to libs/WindowManager/Shell/tests/src/com/android/wm/shell/tests/WindowManagerShellTest.java
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index 8765719..3f2f349 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -44,6 +44,7 @@
         "AttributeResolution.cpp",
         "ChunkIterator.cpp",
         "ConfigDescription.cpp",
+        "DynamicLibManager.cpp",
         "Idmap.cpp",
         "LoadedArsc.cpp",
         "Locale.cpp",
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index ca4143f..8cfd2d8 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -25,6 +25,7 @@
 
 #include "android-base/logging.h"
 #include "android-base/stringprintf.h"
+#include "androidfw/DynamicLibManager.h"
 #include "androidfw/ResourceUtils.h"
 #include "androidfw/Util.h"
 #include "utils/ByteOrder.h"
@@ -66,7 +67,12 @@
   StringPoolRef entry_string_ref;
 };
 
-AssetManager2::AssetManager2() {
+AssetManager2::AssetManager2() : dynamic_lib_manager_(std::make_unique<DynamicLibManager>()) {
+  memset(&configuration_, 0, sizeof(configuration_));
+}
+
+AssetManager2::AssetManager2(DynamicLibManager* dynamic_lib_manager)
+    : dynamic_lib_manager_(dynamic_lib_manager) {
   memset(&configuration_, 0, sizeof(configuration_));
 }
 
@@ -85,25 +91,45 @@
   package_groups_.clear();
   package_ids_.fill(0xff);
 
-  // A mapping from apk assets path to the runtime package id of its first loaded package.
+  // Overlay resources are not directly referenced by an application so their resource ids
+  // can change throughout the application's lifetime. Assign overlay package ids last.
+  std::vector<const ApkAssets*> sorted_apk_assets(apk_assets_);
+  std::stable_partition(sorted_apk_assets.begin(), sorted_apk_assets.end(), [](const ApkAssets* a) {
+    return !a->IsOverlay();
+  });
+
   std::unordered_map<std::string, uint8_t> apk_assets_package_ids;
+  std::unordered_map<std::string, uint8_t> package_name_package_ids;
 
-  // 0x01 is reserved for the android package.
-  int next_package_id = 0x02;
-  const size_t apk_assets_count = apk_assets_.size();
-  for (size_t i = 0; i < apk_assets_count; i++) {
-    const ApkAssets* apk_assets = apk_assets_[i];
-    const LoadedArsc* loaded_arsc = apk_assets->GetLoadedArsc();
-
-    for (const std::unique_ptr<const LoadedPackage>& package : loaded_arsc->GetPackages()) {
-      // Get the package ID or assign one if a shared library.
-      int package_id;
-      if (package->IsDynamic()) {
-        package_id = next_package_id++;
-      } else {
-        package_id = package->GetPackageId();
+  // Assign stable package ids to application packages.
+  uint8_t next_available_package_id = 0U;
+  for (const auto& apk_assets : sorted_apk_assets) {
+    for (const auto& package : apk_assets->GetLoadedArsc()->GetPackages()) {
+      uint8_t package_id = package->GetPackageId();
+      if (package->IsOverlay()) {
+        package_id = GetDynamicLibManager()->FindUnassignedId(next_available_package_id);
+        next_available_package_id = package_id + 1;
+      } else if (package->IsDynamic()) {
+        package_id = GetDynamicLibManager()->GetAssignedId(package->GetPackageName());
       }
 
+      // Map the path of the apk assets to the package id of its first loaded package.
+      apk_assets_package_ids[apk_assets->GetPath()] = package_id;
+
+      // Map the package name of the package to the first loaded package with that package id.
+      package_name_package_ids[package->GetPackageName()] = package_id;
+    }
+  }
+
+  const int apk_assets_count = apk_assets_.size();
+  for (int i = 0; i < apk_assets_count; i++) {
+    const auto& apk_assets = apk_assets_[i];
+    for (const auto& package : apk_assets->GetLoadedArsc()->GetPackages()) {
+      const auto package_id_entry = package_name_package_ids.find(package->GetPackageName());
+      CHECK(package_id_entry != package_name_package_ids.end())
+          << "no package id assgined to package " << package->GetPackageName();
+      const uint8_t package_id = package_id_entry->second;
+
       // Add the mapping for package ID to index if not present.
       uint8_t idx = package_ids_[package_id];
       if (idx == 0xff) {
@@ -115,7 +141,10 @@
           // to take effect.
           const auto& loaded_idmap = apk_assets->GetLoadedIdmap();
           auto target_package_iter = apk_assets_package_ids.find(loaded_idmap->TargetApkPath());
-          if (target_package_iter != apk_assets_package_ids.end()) {
+          if (target_package_iter == apk_assets_package_ids.end()) {
+             LOG(INFO) << "failed to find target package for overlay "
+                       << loaded_idmap->OverlayApkPath();
+          } else {
             const uint8_t target_package_id = target_package_iter->second;
             const uint8_t target_idx = package_ids_[target_package_id];
             CHECK(target_idx != 0xff) << "overlay added to apk_assets_package_ids but does not"
@@ -123,7 +152,7 @@
 
             PackageGroup& target_package_group = package_groups_[target_idx];
 
-            // Create a special dynamic reference table for the overlay to rewite references to
+            // Create a special dynamic reference table for the overlay to rewrite references to
             // overlay resources as references to the target resources they overlay.
             auto overlay_table = std::make_shared<OverlayDynamicRefTable>(
                 loaded_idmap->GetOverlayDynamicRefTable(target_package_id));
@@ -153,8 +182,6 @@
         package_group->dynamic_ref_table->mEntries.replaceValueFor(
             package_name, static_cast<uint8_t>(entry.package_id));
       }
-
-      apk_assets_package_ids.insert(std::make_pair(apk_assets->GetPath(), package_id));
     }
   }
 
@@ -567,7 +594,7 @@
       if (resource_resolution_logging_enabled_) {
         last_resolution_.steps.push_back(
             Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result.config.toString(),
-                             &package_group.packages_[0].loaded_package_->GetPackageName()});
+                             overlay_result.package_name});
       }
     }
   }
@@ -1279,6 +1306,16 @@
   return 0;
 }
 
+DynamicLibManager* AssetManager2::GetDynamicLibManager() const {
+  auto dynamic_lib_manager =
+      std::get_if<std::unique_ptr<DynamicLibManager>>(&dynamic_lib_manager_);
+  if (dynamic_lib_manager) {
+    return (*dynamic_lib_manager).get();
+  } else {
+    return *std::get_if<DynamicLibManager*>(&dynamic_lib_manager_);
+  }
+}
+
 std::unique_ptr<Theme> AssetManager2::NewTheme() {
   return std::unique_ptr<Theme>(new Theme(this));
 }
diff --git a/libs/androidfw/DynamicLibManager.cpp b/libs/androidfw/DynamicLibManager.cpp
new file mode 100644
index 0000000..895b769
--- /dev/null
+++ b/libs/androidfw/DynamicLibManager.cpp
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+#include "androidfw/DynamicLibManager.h"
+
+namespace android {
+
+uint8_t DynamicLibManager::GetAssignedId(const std::string& library_package_name) {
+  auto lib_entry = shared_lib_package_ids_.find(library_package_name);
+  if (lib_entry != shared_lib_package_ids_.end()) {
+    return lib_entry->second;
+  }
+
+  return shared_lib_package_ids_[library_package_name] = next_package_id_++;
+}
+
+uint8_t DynamicLibManager::FindUnassignedId(uint8_t start_package_id) {
+  return (start_package_id < next_package_id_) ? next_package_id_ : start_package_id;
+}
+
+} // namespace android
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index 00cbbca..b2cec2a 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -27,6 +27,7 @@
 #include "androidfw/ApkAssets.h"
 #include "androidfw/Asset.h"
 #include "androidfw/AssetManager.h"
+#include "androidfw/DynamicLibManager.h"
 #include "androidfw/ResourceTypes.h"
 #include "androidfw/Util.h"
 
@@ -94,6 +95,7 @@
   };
 
   AssetManager2();
+  explicit AssetManager2(DynamicLibManager* dynamic_lib_manager);
 
   // Sets/resets the underlying ApkAssets for this AssetManager. The ApkAssets
   // are not owned by the AssetManager, and must have a longer lifetime.
@@ -371,6 +373,8 @@
   // Retrieve the assigned package id of the package if loaded into this AssetManager
   uint8_t GetAssignedPackageId(const LoadedPackage* package) const;
 
+  DynamicLibManager* GetDynamicLibManager() const;
+
   // The ordered list of ApkAssets to search. These are not owned by the AssetManager, and must
   // have a longer lifetime.
   std::vector<const ApkAssets*> apk_assets_;
@@ -389,6 +393,9 @@
   // may need to be purged.
   ResTable_config configuration_;
 
+  // Component responsible for assigning package ids to shared libraries.
+  std::variant<std::unique_ptr<DynamicLibManager>, DynamicLibManager*> dynamic_lib_manager_;
+
   // Cached set of bags. These are cached because they can inherit keys from parent bags,
   // which involves some calculation.
   std::unordered_map<uint32_t, util::unique_cptr<ResolvedBag>> cached_bags_;
diff --git a/libs/androidfw/include/androidfw/DynamicLibManager.h b/libs/androidfw/include/androidfw/DynamicLibManager.h
new file mode 100644
index 0000000..1ff7079
--- /dev/null
+++ b/libs/androidfw/include/androidfw/DynamicLibManager.h
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROIDFW_DYNAMICLIBMANAGER_H
+#define ANDROIDFW_DYNAMICLIBMANAGER_H
+
+#include <string>
+#include <unordered_map>
+
+#include "android-base/macros.h"
+
+namespace android {
+
+// Manages assigning resource ids for dynamic resources.
+class DynamicLibManager {
+ public:
+  DynamicLibManager() = default;
+
+  // Retrieves the assigned package id for the library.
+  uint8_t GetAssignedId(const std::string& library_package_name);
+
+  // Queries in ascending order for the first available package id that is not currently assigned to
+  // a library.
+  uint8_t FindUnassignedId(uint8_t start_package_id);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DynamicLibManager);
+
+  uint8_t next_package_id_ = 0x02;
+  std::unordered_map<std::string, uint8_t> shared_lib_package_ids_;
+};
+
+} // namespace android
+
+#endif //ANDROIDFW_DYNAMICLIBMANAGER_H
diff --git a/libs/androidfw/include/androidfw/MutexGuard.h b/libs/androidfw/include/androidfw/MutexGuard.h
index 64924f4..8891512 100644
--- a/libs/androidfw/include/androidfw/MutexGuard.h
+++ b/libs/androidfw/include/androidfw/MutexGuard.h
@@ -47,7 +47,8 @@
   static_assert(!std::is_pointer<T>::value, "T must not be a raw pointer");
 
  public:
-  explicit Guarded() : guarded_() {
+  template <typename ...Args>
+  explicit Guarded(Args&& ...args) : guarded_(std::forward<Args>(args)...) {
   }
 
   template <typename U = T>
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index b3190be..2f6f3df 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -214,6 +214,25 @@
   EXPECT_EQ(fix_package_id(appaslib::R::array::integerArray1, 0x02), value.data);
 }
 
+TEST_F(AssetManager2Test, AssignsUnchangingPackageIdToSharedLibrary) {
+  DynamicLibManager lib_manager;
+  AssetManager2 assetmanager(&lib_manager);
+  assetmanager.SetApkAssets(
+      {lib_one_assets_.get(), lib_two_assets_.get(), libclient_assets_.get()});
+
+  AssetManager2 assetmanager2(&lib_manager);
+  assetmanager2.SetApkAssets(
+      {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()});
+
+  uint32_t res_id = assetmanager.GetResourceId("com.android.lib_one:string/foo");
+  ASSERT_NE(0U, res_id);
+
+  uint32_t res_id_2 = assetmanager2.GetResourceId("com.android.lib_one:string/foo");
+  ASSERT_NE(0U, res_id_2);
+
+  ASSERT_EQ(res_id, res_id_2);
+}
+
 TEST_F(AssetManager2Test, GetSharedLibraryResourceName) {
   AssetManager2 assetmanager;
   assetmanager.SetApkAssets({lib_one_assets_.get()});
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index d945fc4..51270f5 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -25,11 +25,6 @@
         // GCC false-positives on this warning, and since we -Werror that's
         // a problem
         "-Wno-free-nonheap-object",
-
-        // Clang is producing non-determistic binary when the new pass manager is
-        // enabled. Disable the new PM as a temporary workaround.
-        // b/142372146
-        "-fno-experimental-new-pass-manager",
     ],
 
     include_dirs: [
@@ -215,6 +210,7 @@
         android: {
 
             srcs: [
+                "pipeline/skia/ATraceMemoryDump.cpp",
                 "pipeline/skia/GLFunctorDrawable.cpp",
                 "pipeline/skia/LayerDrawable.cpp",
                 "pipeline/skia/ShaderCache.cpp",
@@ -244,7 +240,6 @@
                 "DeviceInfo.cpp",
                 "FrameInfo.cpp",
                 "FrameInfoVisualizer.cpp",
-                "GpuMemoryTracker.cpp",
                 "HardwareBitmapUploader.cpp",
                 "HWUIProperties.sysprop",
                 "JankTracker.cpp",
@@ -325,7 +320,6 @@
         "tests/unit/DamageAccumulatorTests.cpp",
         "tests/unit/DeferredLayerUpdaterTests.cpp",
         "tests/unit/FatVectorTests.cpp",
-        "tests/unit/GpuMemoryTrackerTests.cpp",
         "tests/unit/GraphicsStatsServiceTests.cpp",
         "tests/unit/LayerUpdateQueueTests.cpp",
         "tests/unit/LinearAllocatorTests.cpp",
diff --git a/libs/hwui/DeferredLayerUpdater.cpp b/libs/hwui/DeferredLayerUpdater.cpp
index d6b516f..5a50245 100644
--- a/libs/hwui/DeferredLayerUpdater.cpp
+++ b/libs/hwui/DeferredLayerUpdater.cpp
@@ -18,6 +18,8 @@
 #include <GLES2/gl2.h>
 #include <GLES2/gl2ext.h>
 
+// TODO: Use public SurfaceTexture APIs once available and include public NDK header file instead.
+#include <surfacetexture/surface_texture_platform.h>
 #include "AutoBackendTextureRelease.h"
 #include "Matrix.h"
 #include "Properties.h"
@@ -34,6 +36,7 @@
 DeferredLayerUpdater::DeferredLayerUpdater(RenderState& renderState)
         : mRenderState(renderState)
         , mBlend(false)
+        , mSurfaceTexture(nullptr, [](ASurfaceTexture*) {})
         , mTransform(nullptr)
         , mGLContextAttached(false)
         , mUpdateTexImage(false)
diff --git a/libs/hwui/DeferredLayerUpdater.h b/libs/hwui/DeferredLayerUpdater.h
index 289f65c..c44c0d5 100644
--- a/libs/hwui/DeferredLayerUpdater.h
+++ b/libs/hwui/DeferredLayerUpdater.h
@@ -21,8 +21,7 @@
 #include <SkMatrix.h>
 #include <android/hardware_buffer.h>
 #include <cutils/compiler.h>
-// TODO: Use public SurfaceTexture APIs once available and include public NDK header file instead.
-#include <gui/surfacetexture/surface_texture_platform.h>
+#include <android/surface_texture.h>
 
 #include <map>
 #include <memory>
@@ -37,7 +36,7 @@
 class AutoBackendTextureRelease;
 class RenderState;
 
-typedef std::unique_ptr<ASurfaceTexture> AutoTextureRelease;
+typedef std::unique_ptr<ASurfaceTexture, decltype(&ASurfaceTexture_release)> AutoTextureRelease;
 
 // Container to hold the properties a layer should be set to at the start
 // of a render pass
diff --git a/libs/hwui/DisplayListOps.in b/libs/hwui/DisplayListOps.in
index 4a252af..4981792 100644
--- a/libs/hwui/DisplayListOps.in
+++ b/libs/hwui/DisplayListOps.in
@@ -14,39 +14,41 @@
  * limitations under the License.
  */
 
-X(Flush) 
-X(Save) 
-X(Restore) 
+X(Flush)
+X(Save)
+X(Restore)
 X(SaveLayer)
 X(SaveBehind)
-X(Concat) 
-X(SetMatrix) 
+X(Concat44)
+X(Concat)
+X(SetMatrix)
+X(Scale)
 X(Translate)
-X(ClipPath) 
-X(ClipRect) 
-X(ClipRRect) 
+X(ClipPath)
+X(ClipRect)
+X(ClipRRect)
 X(ClipRegion)
 X(DrawPaint)
 X(DrawBehind)
-X(DrawPath) 
-X(DrawRect) 
-X(DrawRegion) 
-X(DrawOval) 
+X(DrawPath)
+X(DrawRect)
+X(DrawRegion)
+X(DrawOval)
 X(DrawArc)
-X(DrawRRect) 
-X(DrawDRRect) 
-X(DrawAnnotation) 
-X(DrawDrawable) 
+X(DrawRRect)
+X(DrawDRRect)
+X(DrawAnnotation)
+X(DrawDrawable)
 X(DrawPicture)
-X(DrawImage) 
-X(DrawImageNine) 
-X(DrawImageRect) 
+X(DrawImage)
+X(DrawImageNine)
+X(DrawImageRect)
 X(DrawImageLattice)
 X(DrawTextBlob)
-X(DrawPatch) 
-X(DrawPoints) 
-X(DrawVertices) 
-X(DrawAtlas) 
+X(DrawPatch)
+X(DrawPoints)
+X(DrawVertices)
+X(DrawAtlas)
 X(DrawShadowRec)
 X(DrawVectorDrawable)
 X(DrawWebView)
diff --git a/libs/hwui/GpuMemoryTracker.cpp b/libs/hwui/GpuMemoryTracker.cpp
deleted file mode 100644
index a9a7af8..0000000
--- a/libs/hwui/GpuMemoryTracker.cpp
+++ /dev/null
@@ -1,122 +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.
- */
-
-#include "utils/StringUtils.h"
-
-#include <GpuMemoryTracker.h>
-#include <cutils/compiler.h>
-#include <utils/Trace.h>
-#include <array>
-#include <sstream>
-#include <unordered_set>
-#include <vector>
-
-namespace android {
-namespace uirenderer {
-
-pthread_t gGpuThread = 0;
-
-#define NUM_TYPES static_cast<int>(GpuObjectType::TypeCount)
-
-const char* TYPE_NAMES[] = {
-        "Texture", "OffscreenBuffer", "Layer",
-};
-
-struct TypeStats {
-    int totalSize = 0;
-    int count = 0;
-};
-
-static std::array<TypeStats, NUM_TYPES> gObjectStats;
-static std::unordered_set<GpuMemoryTracker*> gObjectSet;
-
-void GpuMemoryTracker::notifySizeChanged(int newSize) {
-    int delta = newSize - mSize;
-    mSize = newSize;
-    gObjectStats[static_cast<int>(mType)].totalSize += delta;
-}
-
-void GpuMemoryTracker::startTrackingObject() {
-    auto result = gObjectSet.insert(this);
-    LOG_ALWAYS_FATAL_IF(!result.second,
-                        "startTrackingObject() on %p failed, already being tracked!", this);
-    gObjectStats[static_cast<int>(mType)].count++;
-}
-
-void GpuMemoryTracker::stopTrackingObject() {
-    size_t removed = gObjectSet.erase(this);
-    LOG_ALWAYS_FATAL_IF(removed != 1, "stopTrackingObject removed %zd, is %p not being tracked?",
-                        removed, this);
-    gObjectStats[static_cast<int>(mType)].count--;
-}
-
-void GpuMemoryTracker::onGpuContextCreated() {
-    LOG_ALWAYS_FATAL_IF(gGpuThread != 0,
-                        "We already have a gpu thread? "
-                        "current = %lu, gpu thread = %lu",
-                        pthread_self(), gGpuThread);
-    gGpuThread = pthread_self();
-}
-
-void GpuMemoryTracker::onGpuContextDestroyed() {
-    gGpuThread = 0;
-    if (CC_UNLIKELY(gObjectSet.size() > 0)) {
-        std::stringstream os;
-        dump(os);
-        ALOGE("%s", os.str().c_str());
-        LOG_ALWAYS_FATAL("Leaked %zd GPU objects!", gObjectSet.size());
-    }
-}
-
-void GpuMemoryTracker::dump() {
-    std::stringstream strout;
-    dump(strout);
-    ALOGD("%s", strout.str().c_str());
-}
-
-void GpuMemoryTracker::dump(std::ostream& stream) {
-    for (int type = 0; type < NUM_TYPES; type++) {
-        const TypeStats& stats = gObjectStats[type];
-        stream << TYPE_NAMES[type];
-        stream << " is using " << SizePrinter{stats.totalSize};
-        stream << ", count = " << stats.count;
-        stream << std::endl;
-    }
-}
-
-int GpuMemoryTracker::getInstanceCount(GpuObjectType type) {
-    return gObjectStats[static_cast<int>(type)].count;
-}
-
-int GpuMemoryTracker::getTotalSize(GpuObjectType type) {
-    return gObjectStats[static_cast<int>(type)].totalSize;
-}
-
-void GpuMemoryTracker::onFrameCompleted() {
-    if (ATRACE_ENABLED()) {
-        char buf[128];
-        for (int type = 0; type < NUM_TYPES; type++) {
-            snprintf(buf, 128, "hwui_%s", TYPE_NAMES[type]);
-            const TypeStats& stats = gObjectStats[type];
-            ATRACE_INT(buf, stats.totalSize);
-            snprintf(buf, 128, "hwui_%s_count", TYPE_NAMES[type]);
-            ATRACE_INT(buf, stats.count);
-        }
-    }
-}
-
-}  // namespace uirenderer
-}  // namespace android;
diff --git a/libs/hwui/GpuMemoryTracker.h b/libs/hwui/GpuMemoryTracker.h
deleted file mode 100644
index de3ca99..0000000
--- a/libs/hwui/GpuMemoryTracker.h
+++ /dev/null
@@ -1,77 +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.
- */
-#pragma once
-
-#include <pthread.h>
-#include <ostream>
-
-#include <log/log.h>
-
-namespace android {
-namespace uirenderer {
-
-extern pthread_t gGpuThread;
-
-#define ASSERT_GPU_THREAD()                                                                    \
-    LOG_ALWAYS_FATAL_IF(!pthread_equal(gGpuThread, pthread_self()),                            \
-                        "Error, %p of type %d (size=%d) used on wrong thread! cur thread %lu " \
-                        "!= gpu thread %lu",                                                   \
-                        this, static_cast<int>(mType), mSize, pthread_self(), gGpuThread)
-
-enum class GpuObjectType {
-    Texture = 0,
-    OffscreenBuffer,
-    Layer,
-
-    TypeCount,
-};
-
-class GpuMemoryTracker {
-public:
-    GpuObjectType objectType() { return mType; }
-    int objectSize() { return mSize; }
-
-    static void onGpuContextCreated();
-    static void onGpuContextDestroyed();
-    static void dump();
-    static void dump(std::ostream& stream);
-    static int getInstanceCount(GpuObjectType type);
-    static int getTotalSize(GpuObjectType type);
-    static void onFrameCompleted();
-
-protected:
-    explicit GpuMemoryTracker(GpuObjectType type) : mType(type) {
-        ASSERT_GPU_THREAD();
-        startTrackingObject();
-    }
-
-    ~GpuMemoryTracker() {
-        notifySizeChanged(0);
-        stopTrackingObject();
-    }
-
-    void notifySizeChanged(int newSize);
-
-private:
-    void startTrackingObject();
-    void stopTrackingObject();
-
-    int mSize = 0;
-    GpuObjectType mType;
-};
-
-}  // namespace uirenderer
-}  // namespace android;
diff --git a/libs/hwui/ProfileData.cpp b/libs/hwui/ProfileData.cpp
index 7921662..a8e36e3 100644
--- a/libs/hwui/ProfileData.cpp
+++ b/libs/hwui/ProfileData.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "ProfileData.h"
+#include "Properties.h"
 
 #include <cinttypes>
 
@@ -102,6 +103,7 @@
         mGPUFrameCounts[i] >>= divider;
         mGPUFrameCounts[i] += other.mGPUFrameCounts[i];
     }
+    mPipelineType = other.mPipelineType;
 }
 
 void ProfileData::dump(int fd) const {
@@ -157,6 +159,7 @@
     mTotalFrameCount = 0;
     mJankFrameCount = 0;
     mStatStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    mPipelineType = Properties::getRenderPipelineType();
 }
 
 void ProfileData::reportFrame(int64_t duration) {
diff --git a/libs/hwui/ProfileData.h b/libs/hwui/ProfileData.h
index ccbffc6..dd3ba66 100644
--- a/libs/hwui/ProfileData.h
+++ b/libs/hwui/ProfileData.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include "Properties.h"
 #include "utils/Macros.h"
 
 #include <utils/Timers.h>
@@ -65,6 +66,7 @@
     uint32_t jankFrameCount() const { return mJankFrameCount; }
     nsecs_t statsStartTime() const { return mStatStartTime; }
     uint32_t jankTypeCount(JankType type) const { return mJankTypeCounts[static_cast<int>(type)]; }
+    RenderPipelineType pipelineType() const { return mPipelineType; }
 
     struct HistogramEntry {
         uint32_t renderTimeMs;
@@ -103,6 +105,9 @@
     uint32_t mTotalFrameCount;
     uint32_t mJankFrameCount;
     nsecs_t mStatStartTime;
+
+    // true if HWUI renders with Vulkan pipeline
+    RenderPipelineType mPipelineType;
 };
 
 // For testing
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index c0df2fa..dc467c4 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -130,6 +130,12 @@
     }
 };
 
+struct Concat44 final : Op {
+    static const auto kType = Type::Concat44;
+    Concat44(const SkScalar m[16]) { memcpy(colMajor, m, sizeof(colMajor)); }
+    SkScalar colMajor[16];
+    void draw(SkCanvas* c, const SkMatrix&) const { c->experimental_concat44(colMajor); }
+};
 struct Concat final : Op {
     static const auto kType = Type::Concat;
     Concat(const SkMatrix& matrix) : matrix(matrix) {}
@@ -144,6 +150,12 @@
         c->setMatrix(SkMatrix::Concat(original, matrix));
     }
 };
+struct Scale final : Op {
+    static const auto kType = Type::Scale;
+    Scale(SkScalar sx, SkScalar sy) : sx(sx), sy(sy) {}
+    SkScalar sx, sy;
+    void draw(SkCanvas* c, const SkMatrix&) const { c->scale(sx, sy); }
+};
 struct Translate final : Op {
     static const auto kType = Type::Translate;
     Translate(SkScalar dx, SkScalar dy) : dx(dx), dy(dy) {}
@@ -562,12 +574,18 @@
     this->push<SaveBehind>(0, subset);
 }
 
+void DisplayListData::concat44(const SkScalar colMajor[16]) {
+    this->push<Concat44>(0, colMajor);
+}
 void DisplayListData::concat(const SkMatrix& matrix) {
     this->push<Concat>(0, matrix);
 }
 void DisplayListData::setMatrix(const SkMatrix& matrix) {
     this->push<SetMatrix>(0, matrix);
 }
+void DisplayListData::scale(SkScalar sx, SkScalar sy) {
+    this->push<Scale>(0, sx, sy);
+}
 void DisplayListData::translate(SkScalar dx, SkScalar dy) {
     this->push<Translate>(0, dx, dy);
 }
@@ -823,12 +841,18 @@
     return false;
 }
 
+void RecordingCanvas::didConcat44(const SkScalar colMajor[16]) {
+    fDL->concat44(colMajor);
+}
 void RecordingCanvas::didConcat(const SkMatrix& matrix) {
     fDL->concat(matrix);
 }
 void RecordingCanvas::didSetMatrix(const SkMatrix& matrix) {
     fDL->setMatrix(matrix);
 }
+void RecordingCanvas::didScale(SkScalar sx, SkScalar sy) {
+    fDL->scale(sx, sy);
+}
 void RecordingCanvas::didTranslate(SkScalar dx, SkScalar dy) {
     fDL->translate(dx, dy);
 }
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 322eff2..7eb1ce3 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -82,8 +82,10 @@
     void saveBehind(const SkRect*);
     void restore();
 
+    void concat44(const SkScalar colMajor[16]);
     void concat(const SkMatrix&);
     void setMatrix(const SkMatrix&);
+    void scale(SkScalar, SkScalar);
     void translate(SkScalar, SkScalar);
     void translateZ(SkScalar);
 
@@ -153,8 +155,10 @@
 
     void onFlush() override;
 
+    void didConcat44(const SkScalar[16]) override;
     void didConcat(const SkMatrix&) override;
     void didSetMatrix(const SkMatrix&) override;
+    void didScale(SkScalar, SkScalar) override;
     void didTranslate(SkScalar, SkScalar) override;
 
     void onClipRect(const SkRect&, SkClipOp, ClipEdgeStyle) override;
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 2314072..5790150 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -217,13 +217,16 @@
         canvas->setMatrix(mMatrix);
         switch (mType) {
             case Type::Rect:
-                canvas->clipRect(mRRect.rect(), mOp);
+                // Don't anti-alias rectangular clips
+                canvas->clipRect(mRRect.rect(), mOp, false);
                 break;
             case Type::RRect:
-                canvas->clipRRect(mRRect, mOp);
+                // Ensure rounded rectangular clips are anti-aliased
+                canvas->clipRRect(mRRect, mOp, true);
                 break;
             case Type::Path:
-                canvas->clipPath(mPath.value(), mOp);
+                // Ensure path clips are anti-aliased
+                canvas->clipPath(mPath.value(), mOp, true);
                 break;
         }
     }
@@ -392,7 +395,7 @@
 
 bool SkiaCanvas::clipPath(const SkPath* path, SkClipOp op) {
     this->recordClip(*path, op);
-    mCanvas->clipPath(*path, op);
+    mCanvas->clipPath(*path, op, true);
     return !mCanvas->isClipEmpty();
 }
 
@@ -746,7 +749,10 @@
     glyphFunc(buffer.glyphs, buffer.pos);
 
     sk_sp<SkTextBlob> textBlob(builder.make());
-    mCanvas->drawTextBlob(textBlob, 0, 0, paintCopy);
+
+    apply_looper(&paintCopy, [&](const SkPaint& p) {
+        mCanvas->drawTextBlob(textBlob, 0, 0, p);
+    });
     drawTextDecorations(x, y, totalAdvance, paintCopy);
 }
 
@@ -783,8 +789,10 @@
         xform[i - start].fTx = pos.x() - tan.y() * y - halfWidth * tan.x();
         xform[i - start].fTy = pos.y() + tan.x() * y - halfWidth * tan.y();
     }
-
-    this->asSkCanvas()->drawTextBlob(builder.make(), 0, 0, paintCopy);
+    auto* finalCanvas = this->asSkCanvas();
+    apply_looper(&paintCopy, [&](const SkPaint& p) {
+        finalCanvas->drawTextBlob(builder.make(), 0, 0, paintCopy);
+    });
 }
 
 // ----------------------------------------------------------------------------
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index f4149b9..3c402e9 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -38,7 +38,7 @@
 
 #include <SkCanvas.h>
 #include <SkImagePriv.h>
-
+#include <SkWebpEncoder.h>
 #include <SkHighContrastFilter.h>
 #include <limits>
 
@@ -152,7 +152,9 @@
     AHardwareBuffer_describe(hardwareBuffer, &bufferDesc);
     SkImageInfo info = uirenderer::BufferDescriptionToImageInfo(bufferDesc, colorSpace);
 
-    const size_t rowBytes = info.bytesPerPixel() * bufferDesc.stride;
+    // If the stride is 0 we have to use the width as an approximation (eg, compressed buffer)
+    const auto bufferStride = bufferDesc.stride > 0 ? bufferDesc.stride : bufferDesc.width;
+    const size_t rowBytes = info.bytesPerPixel() * bufferStride;
     return sk_sp<Bitmap>(new Bitmap(hardwareBuffer, info, rowBytes, palette));
 }
 
@@ -469,4 +471,59 @@
     return BitmapPalette::Unknown;
 }
 
+Bitmap::CompressResult Bitmap::compress(JavaCompressFormat format, int32_t quality,
+                                        SkWStream* stream) {
+    SkBitmap skbitmap;
+    getSkBitmap(&skbitmap);
+    return compress(skbitmap, format, quality, stream);
+}
+
+Bitmap::CompressResult Bitmap::compress(const SkBitmap& bitmap, JavaCompressFormat format,
+                                        int32_t quality, SkWStream* stream) {
+    SkBitmap skbitmap = bitmap;
+    if (skbitmap.colorType() == kRGBA_F16_SkColorType) {
+        // Convert to P3 before encoding. This matches
+        // SkAndroidCodec::computeOutputColorSpace for wide gamuts. Now that F16
+        // could already be P3, we still want to convert to 8888.
+        auto cs = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3);
+        auto info = skbitmap.info().makeColorType(kRGBA_8888_SkColorType)
+                                   .makeColorSpace(std::move(cs));
+        SkBitmap p3;
+        if (!p3.tryAllocPixels(info)) {
+            return CompressResult::AllocationFailed;
+        }
+
+        SkPixmap pm;
+        SkAssertResult(p3.peekPixels(&pm));  // should always work if tryAllocPixels() did.
+        if (!skbitmap.readPixels(pm)) {
+            return CompressResult::Error;
+        }
+        skbitmap = p3;
+    }
+
+    SkEncodedImageFormat fm;
+    switch (format) {
+        case JavaCompressFormat::Jpeg:
+            fm = SkEncodedImageFormat::kJPEG;
+            break;
+        case JavaCompressFormat::Png:
+            fm = SkEncodedImageFormat::kPNG;
+            break;
+        case JavaCompressFormat::Webp:
+            fm = SkEncodedImageFormat::kWEBP;
+            break;
+        case JavaCompressFormat::WebpLossy:
+        case JavaCompressFormat::WebpLossless: {
+            SkWebpEncoder::Options options;
+            options.fQuality = quality;
+            options.fCompression = format == JavaCompressFormat::WebpLossy ?
+                    SkWebpEncoder::Compression::kLossy : SkWebpEncoder::Compression::kLossless;
+            return SkWebpEncoder::Encode(stream, skbitmap.pixmap(), options)
+                    ? CompressResult::Success : CompressResult::Error;
+        }
+    }
+
+    return SkEncodeImage(stream, skbitmap, fm, quality)
+            ? CompressResult::Success : CompressResult::Error;
+}
 }  // namespace android
diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h
index 1cda046..ee365af 100644
--- a/libs/hwui/hwui/Bitmap.h
+++ b/libs/hwui/hwui/Bitmap.h
@@ -27,6 +27,8 @@
 #include <android/hardware_buffer.h>
 #endif
 
+class SkWStream;
+
 namespace android {
 
 enum class PixelStorageType {
@@ -142,6 +144,26 @@
   // and places that value in size.
   static bool computeAllocationSize(size_t rowBytes, int height, size_t* size);
 
+  // These must match the int values of CompressFormat in Bitmap.java, as well as
+  // AndroidBitmapCompressFormat.
+  enum class JavaCompressFormat {
+    Jpeg = 0,
+    Png = 1,
+    Webp = 2,
+    WebpLossy = 3,
+    WebpLossless = 4,
+  };
+
+  enum class CompressResult {
+    Success,
+    AllocationFailed,
+    Error,
+  };
+
+  CompressResult compress(JavaCompressFormat format, int32_t quality, SkWStream* stream);
+
+  static CompressResult compress(const SkBitmap& bitmap, JavaCompressFormat format,
+                                 int32_t quality, SkWStream* stream);
 private:
     static sk_sp<Bitmap> allocateAshmemBitmap(size_t size, const SkImageInfo& i, size_t rowBytes);
     static sk_sp<Bitmap> allocateHeapBitmap(size_t size, const SkImageInfo& i, size_t rowBytes);
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index 4f2027d..4b2857f 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -30,19 +30,25 @@
     , mTargetSize(mCodec->getInfo().dimensions())
     , mDecodeSize(mTargetSize)
     , mOutColorType(mCodec->computeOutputColorType(kN32_SkColorType))
-    , mOutAlphaType(mCodec->getInfo().isOpaque() ?
-                    kOpaque_SkAlphaType : kPremul_SkAlphaType)
-    , mOutColorSpace(mCodec->getInfo().refColorSpace())
+    , mUnpremultipliedRequired(false)
+    , mOutColorSpace(mCodec->computeOutputColorSpace(mOutColorType, nullptr))
     , mSampleSize(1)
 {
 }
 
+SkAlphaType ImageDecoder::getOutAlphaType() const {
+    // While an SkBitmap may want to use kOpaque_SkAlphaType for a performance
+    // optimization, this class just outputs raw pixels. Using either
+    // premultiplication choice has no effect on decoding an opaque encoded image.
+    return mUnpremultipliedRequired ? kUnpremul_SkAlphaType : kPremul_SkAlphaType;
+}
+
 bool ImageDecoder::setTargetSize(int width, int height) {
     if (width <= 0 || height <= 0) {
         return false;
     }
 
-    auto info = SkImageInfo::Make(width, height, mOutColorType, mOutAlphaType);
+    auto info = SkImageInfo::Make(width, height, mOutColorType, getOutAlphaType());
     size_t rowBytes = info.minRowBytes();
     if (rowBytes == 0) {
         // This would have overflowed.
@@ -63,7 +69,7 @@
     SkISize targetSize = { width, height }, decodeSize = targetSize;
     int sampleSize = mCodec->computeSampleSize(&decodeSize);
 
-    if (decodeSize != targetSize && mOutAlphaType == kUnpremul_SkAlphaType
+    if (decodeSize != targetSize && mUnpremultipliedRequired
             && !mCodec->getInfo().isOpaque()) {
         return false;
     }
@@ -105,7 +111,6 @@
             if (!gray()) {
                 return false;
             }
-            mOutColorSpace = nullptr;
             break;
         case kN32_SkColorType:
             break;
@@ -119,29 +124,11 @@
     return true;
 }
 
-bool ImageDecoder::setOutAlphaType(SkAlphaType alpha) {
-    switch (alpha) {
-        case kOpaque_SkAlphaType:
-            return opaque();
-        case kPremul_SkAlphaType:
-            if (opaque()) {
-                // Opaque can be treated as premul.
-                return true;
-            }
-            break;
-        case kUnpremul_SkAlphaType:
-            if (opaque()) {
-                // Opaque can be treated as unpremul.
-                return true;
-            }
-            if (mDecodeSize != mTargetSize) {
-                return false;
-            }
-            break;
-        default:
-            return false;
+bool ImageDecoder::setUnpremultipliedRequired(bool required) {
+    if (required && !opaque() && mDecodeSize != mTargetSize) {
+        return false;
     }
-    mOutAlphaType = alpha;
+    mUnpremultipliedRequired = required;
     return true;
 }
 
@@ -149,13 +136,19 @@
     mOutColorSpace = std::move(colorSpace);
 }
 
+sk_sp<SkColorSpace> ImageDecoder::getOutputColorSpace() const {
+    // kGray_8 is used for ALPHA_8, which ignores the color space.
+    return mOutColorType == kGray_8_SkColorType ? nullptr : mOutColorSpace;
+}
+
+
 SkImageInfo ImageDecoder::getOutputInfo() const {
     SkISize size = mCropRect ? mCropRect->size() : mTargetSize;
-    return SkImageInfo::Make(size, mOutColorType, mOutAlphaType, mOutColorSpace);
+    return SkImageInfo::Make(size, mOutColorType, getOutAlphaType(), getOutputColorSpace());
 }
 
 bool ImageDecoder::opaque() const {
-    return mOutAlphaType == kOpaque_SkAlphaType;
+    return mCodec->getInfo().alphaType() == kOpaque_SkAlphaType;
 }
 
 bool ImageDecoder::gray() const {
@@ -165,7 +158,8 @@
 SkCodec::Result ImageDecoder::decode(void* pixels, size_t rowBytes) {
     void* decodePixels = pixels;
     size_t decodeRowBytes = rowBytes;
-    auto decodeInfo = SkImageInfo::Make(mDecodeSize, mOutColorType, mOutAlphaType, mOutColorSpace);
+    auto decodeInfo = SkImageInfo::Make(mDecodeSize, mOutColorType, getOutAlphaType(),
+                                        getOutputColorSpace());
     // Used if we need a temporary before scaling or subsetting.
     // FIXME: Use scanline decoding on only a couple lines to save memory. b/70709380.
     SkBitmap tmp;
diff --git a/libs/hwui/hwui/ImageDecoder.h b/libs/hwui/hwui/ImageDecoder.h
index b956f4a..0c99f84 100644
--- a/libs/hwui/hwui/ImageDecoder.h
+++ b/libs/hwui/hwui/ImageDecoder.h
@@ -41,14 +41,12 @@
 
     bool setOutColorType(SkColorType outColorType);
 
-    bool setOutAlphaType(SkAlphaType outAlphaType);
+    bool setUnpremultipliedRequired(bool unpremultipliedRequired);
 
     void setOutColorSpace(sk_sp<SkColorSpace> cs);
 
     // The size is the final size after scaling and cropping.
     SkImageInfo getOutputInfo() const;
-    SkColorType getOutColorType() const { return mOutColorType; }
-    SkAlphaType getOutAlphaType() const { return mOutAlphaType; }
 
     bool opaque() const;
     bool gray() const;
@@ -59,13 +57,16 @@
     SkISize mTargetSize;
     SkISize mDecodeSize;
     SkColorType mOutColorType;
-    SkAlphaType mOutAlphaType;
+    bool mUnpremultipliedRequired;
     sk_sp<SkColorSpace> mOutColorSpace;
     int mSampleSize;
     std::optional<SkIRect> mCropRect;
 
     ImageDecoder(const ImageDecoder&) = delete;
     ImageDecoder& operator=(const ImageDecoder&) = delete;
+
+    SkAlphaType getOutAlphaType() const;
+    sk_sp<SkColorSpace> getOutputColorSpace() const;
 };
 
 } // namespace android
diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp
new file mode 100644
index 0000000..551bdc6
--- /dev/null
+++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp
@@ -0,0 +1,178 @@
+/*
+ * 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.
+ */
+
+#include "ATraceMemoryDump.h"
+
+#include <utils/Trace.h>
+
+#include <cstring>
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+// When purgeable is INVALID_MEMORY_SIZE it won't be logged at all.
+#define INVALID_MEMORY_SIZE -1
+
+/**
+ * Skia invokes the following SkTraceMemoryDump functions:
+ * 1. dumpNumericValue (dumpName, units="bytes", valueName="size")
+ * 2. dumpStringValue (dumpName, valueName="type") [optional -> for example CPU memory does not
+ * invoke dumpStringValue]
+ * 3. dumpNumericValue (dumpName, units="bytes", valueName="purgeable_size") [optional]
+ * 4. setMemoryBacking(dumpName, backingType) [optional -> for example Vulkan GPU resources do not
+ * invoke setMemoryBacking]
+ *
+ * ATraceMemoryDump calculates memory category first by looking at the "type" string passed to
+ * dumpStringValue and then by looking at "backingType" passed to setMemoryBacking.
+ * Only GPU Texture memory is tracked separately and everything else is grouped as one
+ * "GPU Memory" category.
+ */
+static std::unordered_map<const char*, const char*> sResourceMap = {
+        {"malloc", "HWUI CPU Memory"},          // taken from setMemoryBacking(backingType)
+        {"gl_texture", "HWUI Texture Memory"},  // taken from setMemoryBacking(backingType)
+        {"Texture",
+         "HWUI Texture Memory"},  // taken from dumpStringValue(value, valueName="type")
+        // Uncomment categories below to split "GPU Memory" into more brackets for debugging.
+        /*{"vk_buffer", "vk_buffer"},
+        {"gl_renderbuffer", "gl_renderbuffer"},
+        {"gl_buffer", "gl_buffer"},
+        {"RenderTarget", "RenderTarget"},
+        {"Stencil", "Stencil"},
+        {"Path Data", "Path Data"},
+        {"Buffer Object", "Buffer Object"},
+        {"Surface", "Surface"},*/
+};
+
+ATraceMemoryDump::ATraceMemoryDump() {
+    mLastDumpName.reserve(100);
+    mCategory.reserve(100);
+}
+
+void ATraceMemoryDump::dumpNumericValue(const char* dumpName, const char* valueName,
+                                        const char* units, uint64_t value) {
+    if (!strcmp(units, "bytes")) {
+        recordAndResetCountersIfNeeded(dumpName);
+        if (!strcmp(valueName, "size")) {
+            mLastDumpValue = value;
+        } else if (!strcmp(valueName, "purgeable_size")) {
+            mLastPurgeableDumpValue = value;
+        }
+    }
+}
+
+void ATraceMemoryDump::dumpStringValue(const char* dumpName, const char* valueName,
+                                       const char* value) {
+    if (!strcmp(valueName, "type")) {
+        recordAndResetCountersIfNeeded(dumpName);
+        auto categoryIt = sResourceMap.find(value);
+        if (categoryIt != sResourceMap.end()) {
+            mCategory = categoryIt->second;
+        }
+    }
+}
+
+void ATraceMemoryDump::setMemoryBacking(const char* dumpName, const char* backingType,
+                                        const char* backingObjectId) {
+    recordAndResetCountersIfNeeded(dumpName);
+    auto categoryIt = sResourceMap.find(backingType);
+    if (categoryIt != sResourceMap.end()) {
+        mCategory = categoryIt->second;
+    }
+}
+
+/**
+ * startFrame is invoked before dumping anything. It resets counters from the previous frame.
+ * This is important, because if there is no new data for a given category trace would assume
+ * usage has not changed (instead of reporting 0).
+ */
+void ATraceMemoryDump::startFrame() {
+    resetCurrentCounter("");
+    for (auto& it : mCurrentValues) {
+        // Once a category is observed in at least one frame, it is always reported in subsequent
+        // frames (even if it is 0). Not logging a category to ATRACE would mean its value has not
+        // changed since the previous frame, which is not what we want.
+        it.second.memory = 0;
+        // If purgeableMemory is INVALID_MEMORY_SIZE, then logTraces won't log it at all.
+        if (it.second.purgeableMemory != INVALID_MEMORY_SIZE) {
+            it.second.purgeableMemory = 0;
+        }
+    }
+}
+
+/**
+ * logTraces reads from mCurrentValues and logs the counters with ATRACE.
+ */
+void ATraceMemoryDump::logTraces() {
+    // Accumulate data from last dumpName
+    recordAndResetCountersIfNeeded("");
+    uint64_t hwui_all_frame_memory = 0;
+    for (auto& it : mCurrentValues) {
+        hwui_all_frame_memory += it.second.memory;
+        ATRACE_INT64(it.first.c_str(), it.second.memory);
+        if (it.second.purgeableMemory != INVALID_MEMORY_SIZE) {
+            ATRACE_INT64((std::string("Purgeable ") + it.first).c_str(), it.second.purgeableMemory);
+        }
+    }
+    ATRACE_INT64("HWUI All Memory", hwui_all_frame_memory);
+}
+
+/**
+ * recordAndResetCountersIfNeeded reads memory usage from mLastDumpValue/mLastPurgeableDumpValue and
+ * accumulates in mCurrentValues[category]. It makes provision to create a new category and track
+ * purgeable memory only if there is at least one observation.
+ * recordAndResetCountersIfNeeded won't do anything until all the information for a given dumpName
+ * is received.
+ */
+void ATraceMemoryDump::recordAndResetCountersIfNeeded(const char* dumpName) {
+    if (!mLastDumpName.compare(dumpName)) {
+        // Still waiting for more data for current dumpName.
+        return;
+    }
+
+    // First invocation will have an empty mLastDumpName.
+    if (!mLastDumpName.empty()) {
+        // A new dumpName observed -> store the data already collected.
+        auto memoryCounter = mCurrentValues.find(mCategory);
+        if (memoryCounter != mCurrentValues.end()) {
+            memoryCounter->second.memory += mLastDumpValue;
+            if (mLastPurgeableDumpValue != INVALID_MEMORY_SIZE) {
+                if (memoryCounter->second.purgeableMemory == INVALID_MEMORY_SIZE) {
+                    memoryCounter->second.purgeableMemory = mLastPurgeableDumpValue;
+                } else {
+                    memoryCounter->second.purgeableMemory += mLastPurgeableDumpValue;
+                }
+            }
+        } else {
+            mCurrentValues[mCategory] = {mLastDumpValue, mLastPurgeableDumpValue};
+        }
+    }
+
+    // Reset counters and default category for the newly observed "dumpName".
+    resetCurrentCounter(dumpName);
+}
+
+void ATraceMemoryDump::resetCurrentCounter(const char* dumpName) {
+    mLastDumpValue = 0;
+    mLastPurgeableDumpValue = INVALID_MEMORY_SIZE;
+    mLastDumpName = dumpName;
+    // Categories not listed in sResourceMap are reported as "GPU memory"
+    mCategory = "HWUI GPU Memory";
+}
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.h b/libs/hwui/pipeline/skia/ATraceMemoryDump.h
new file mode 100644
index 0000000..4592711
--- /dev/null
+++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.h
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <SkString.h>
+#include <SkTraceMemoryDump.h>
+
+#include <string>
+#include <unordered_map>
+#include <utility>
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+class ATraceMemoryDump : public SkTraceMemoryDump {
+public:
+    ATraceMemoryDump();
+    ~ATraceMemoryDump() override {}
+
+    void dumpNumericValue(const char* dumpName, const char* valueName, const char* units,
+                          uint64_t value) override;
+
+    void dumpStringValue(const char* dumpName, const char* valueName, const char* value) override;
+
+    LevelOfDetail getRequestedDetails() const override {
+        return SkTraceMemoryDump::kLight_LevelOfDetail;
+    }
+
+    bool shouldDumpWrappedObjects() const override { return false; }
+
+    void setMemoryBacking(const char* dumpName, const char* backingType,
+                          const char* backingObjectId) override;
+
+    void setDiscardableMemoryBacking(const char*, const SkDiscardableMemory&) override {}
+
+    void startFrame();
+
+    void logTraces();
+
+private:
+    std::string mLastDumpName;
+
+    uint64_t mLastDumpValue;
+
+    uint64_t mLastPurgeableDumpValue;
+
+    std::string mCategory;
+
+    struct TraceValue {
+        uint64_t memory;
+        uint64_t purgeableMemory;
+    };
+
+    // keys are define in sResourceMap
+    std::unordered_map<std::string, TraceValue> mCurrentValues;
+
+    void recordAndResetCountersIfNeeded(const char* dumpName);
+
+    void resetCurrentCounter(const char* dumpName);
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
\ No newline at end of file
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index a1b2b18..aa8849b 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -26,6 +26,7 @@
 #include "SkAndroidFrameworkUtils.h"
 #include "SkClipStack.h"
 #include "SkRect.h"
+#include "include/private/SkM44.h"
 
 namespace android {
 namespace uirenderer {
@@ -92,7 +93,7 @@
 
     SkIRect surfaceBounds = canvas->internal_private_getTopLayerBounds();
     SkIRect clipBounds = canvas->getDeviceClipBounds();
-    SkMatrix44 mat4(canvas->getTotalMatrix());
+    SkM44 mat4(canvas->experimental_getLocalToDevice());
     SkRegion clipRegion;
     canvas->temporary_internal_getRgnClip(&clipRegion);
 
@@ -118,7 +119,7 @@
 
         // update the matrix and clip that we pass to the WebView to match the coordinates of
         // the offscreen layer
-        mat4.preTranslate(-clipBounds.fLeft, -clipBounds.fTop, 0);
+        mat4.preTranslate(-clipBounds.fLeft, -clipBounds.fTop);
         clipBounds.offsetTo(0, 0);
         clipRegion.translate(-surfaceBounds.fLeft, -surfaceBounds.fTop);
 
@@ -126,7 +127,7 @@
         // we are drawing into a (clipped) offscreen layer so we must update the clip and matrix
         // from device coordinates to the layer's coordinates
         clipBounds.offset(-surfaceBounds.fLeft, -surfaceBounds.fTop);
-        mat4.preTranslate(-surfaceBounds.fLeft, -surfaceBounds.fTop, 0);
+        mat4.preTranslate(-surfaceBounds.fLeft, -surfaceBounds.fTop);
     }
 
     DrawGlInfo info;
@@ -137,7 +138,7 @@
     info.isLayer = fboID != 0;
     info.width = fboSize.width();
     info.height = fboSize.height();
-    mat4.asColMajorf(&info.transform[0]);
+    mat4.getColMajor(&info.transform[0]);
     info.color_space_ptr = canvas->imageInfo().colorSpace();
 
     // ensure that the framebuffer that the webview will render into is bound before we clear
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 11dc013..b940cff 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -457,7 +457,16 @@
                                    const Rect& contentDrawBounds, SkCanvas* canvas,
                                    const SkMatrix& preTransform) {
     SkAutoCanvasRestore saver(canvas, true);
-    canvas->androidFramework_setDeviceClipRestriction(preTransform.mapRect(clip).roundOut());
+    auto clipRestriction = preTransform.mapRect(clip).roundOut();
+    if (CC_UNLIKELY(mCaptureMode == CaptureMode::SingleFrameSKP
+         || mCaptureMode == CaptureMode::MultiFrameSKP)) {
+        canvas->drawAnnotation(SkRect::Make(clipRestriction), "AndroidDeviceClipRestriction",
+            nullptr);
+    } else {
+        // clip drawing to dirty region only when not recording SKP files (which should contain all
+        // draw ops on every frame)
+        canvas->androidFramework_setDeviceClipRestriction(clipRestriction);
+    }
     canvas->concat(preTransform);
 
     // STOPSHIP: Revert, temporary workaround to clear always F16 frame buffer for b/74976293
diff --git a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
index 1127926..68f1117 100644
--- a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
@@ -20,6 +20,7 @@
 #include <GrBackendDrawableInfo.h>
 #include <SkAndroidFrameworkUtils.h>
 #include <SkImage.h>
+#include "include/private/SkM44.h"
 #include <utils/Color.h>
 #include <utils/Trace.h>
 #include <utils/TraceUtils.h>
@@ -62,7 +63,7 @@
             renderthread::RenderThread::getInstance().vulkanManager();
     mFunctorHandle->initVk(vk_manager.getVkFunctorInitParams());
 
-    SkMatrix44 mat4(mMatrix);
+    SkM44 mat4(mMatrix);
     VkFunctorDrawParams params{
             .width = mImageInfo.width(),
             .height = mImageInfo.height(),
@@ -72,7 +73,7 @@
             .clip_right = mClip.fRight,
             .clip_bottom = mClip.fBottom,
     };
-    mat4.asColMajorf(&params.transform[0]);
+    mat4.getColMajor(&params.transform[0]);
     params.secondary_command_buffer = vulkan_info.fSecondaryCommandBuffer;
     params.color_attachment_index = vulkan_info.fColorAttachmentIndex;
     params.compatible_render_pass = vulkan_info.fCompatibleRenderPass;
diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
index 706325f..241d370 100644
--- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
@@ -121,7 +121,7 @@
         glBindTexture(GL_TEXTURE_2D, 0);
 
         DrawGlInfo info;
-        SkMatrix44 mat4(canvas->getTotalMatrix());
+        SkM44 mat4(canvas->experimental_getLocalToDevice());
         SkIRect clipBounds = canvas->getDeviceClipBounds();
 
         info.clipLeft = clipBounds.fLeft;
@@ -131,7 +131,7 @@
         info.isLayer = true;
         info.width = mFBInfo.width();
         info.height = mFBInfo.height();
-        mat4.asColMajorf(&info.transform[0]);
+        mat4.getColMajor(&info.transform[0]);
         info.color_space_ptr = canvas->imageInfo().colorSpace();
 
         glViewport(0, 0, info.width, info.height);
diff --git a/libs/hwui/protos/graphicsstats.proto b/libs/hwui/protos/graphicsstats.proto
index 0cd5c62..dd5676c 100644
--- a/libs/hwui/protos/graphicsstats.proto
+++ b/libs/hwui/protos/graphicsstats.proto
@@ -29,6 +29,11 @@
 }
 
 message GraphicsStatsProto {
+    enum PipelineType {
+        GL = 0;
+        VULKAN = 1;
+    }
+
     // The package name of the app
     optional string package_name = 1;
 
@@ -49,6 +54,9 @@
 
     // The gpu frame time histogram for the package
     repeated GraphicsStatsHistogramBucketProto gpu_histogram = 7;
+
+    // HWUI renders pipeline type: GL or Vulkan
+    optional PipelineType pipeline = 8;
 }
 
 message GraphicsStatsJankSummaryProto {
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index fad9440..7e8c96d 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -16,7 +16,6 @@
 #include "renderstate/RenderState.h"
 
 #include "renderthread/RenderThread.h"
-#include "GpuMemoryTracker.h"
 
 namespace android {
 namespace uirenderer {
@@ -25,15 +24,10 @@
     mThreadId = pthread_self();
 }
 
-void RenderState::onContextCreated() {
-    GpuMemoryTracker::onGpuContextCreated();
-}
-
 void RenderState::onContextDestroyed() {
     for(auto callback : mContextCallbacks) {
         callback->onContextDestroyed();
     }
-    GpuMemoryTracker::onGpuContextDestroyed();
 }
 
 void RenderState::postDecStrong(VirtualLightRefBase* object) {
diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h
index ff5d02f..e08d32a 100644
--- a/libs/hwui/renderstate/RenderState.h
+++ b/libs/hwui/renderstate/RenderState.h
@@ -62,7 +62,6 @@
     ~RenderState() {}
 
     // Context notifications are only to be triggered by renderthread::RenderThread
-    void onContextCreated();
     void onContextDestroyed();
 
     std::set<IGpuContextCallback*> mContextCallbacks;
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index eaed46c..d177855 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -20,10 +20,12 @@
 #include "Layer.h"
 #include "Properties.h"
 #include "RenderThread.h"
+#include "pipeline/skia/ATraceMemoryDump.h"
 #include "pipeline/skia/ShaderCache.h"
 #include "pipeline/skia/SkiaMemoryTracer.h"
 #include "renderstate/RenderState.h"
 #include "thread/CommonPool.h"
+#include <utils/Trace.h>
 
 #include <GrContextOptions.h>
 #include <SkExecutor.h>
@@ -184,6 +186,18 @@
     gpuTracer.logTotals(log);
 }
 
+void CacheManager::onFrameCompleted() {
+    if (ATRACE_ENABLED()) {
+        static skiapipeline::ATraceMemoryDump tracer;
+        tracer.startFrame();
+        SkGraphics::DumpMemoryStatistics(&tracer);
+        if (mGrContext) {
+            mGrContext->dumpMemoryStatistics(&tracer);
+        }
+        tracer.logTraces();
+    }
+}
+
 } /* namespace renderthread */
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h
index 968251e..b009cc4 100644
--- a/libs/hwui/renderthread/CacheManager.h
+++ b/libs/hwui/renderthread/CacheManager.h
@@ -50,6 +50,7 @@
 
     size_t getCacheSize() const { return mMaxResourceBytes; }
     size_t getBackgroundCacheSize() const { return mBackgroundResourceBytes; }
+    void onFrameCompleted();
 
 private:
     friend class RenderThread;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 8490221..5993e17 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -16,7 +16,6 @@
 
 #include "CanvasContext.h"
 
-#include <GpuMemoryTracker.h>
 #include <apex/window.h>
 #include <fcntl.h>
 #include <strings.h>
@@ -558,7 +557,7 @@
         mJankTracker.finishGpuDraw(*forthBehind);
     }
 
-    GpuMemoryTracker::onFrameCompleted();
+    mRenderThread.cacheManager().onFrameCompleted();
 }
 
 // Called by choreographer to do an RT-driven animation
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index d78f641..cae3e3b 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -270,7 +270,6 @@
     }
     mGrContext = std::move(context);
     if (mGrContext) {
-        mRenderState->onContextCreated();
         DeviceInfo::setMaxTextureSize(mGrContext->maxRenderTargetSize());
     }
 }
diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp
index 12c5b83..c418617 100644
--- a/libs/hwui/service/GraphicsStatsService.cpp
+++ b/libs/hwui/service/GraphicsStatsService.cpp
@@ -16,24 +16,28 @@
 
 #include "GraphicsStatsService.h"
 
-#include "JankTracker.h"
-#include "protos/graphicsstats.pb.h"
-
-#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
-#include <log/log.h>
-
 #include <errno.h>
 #include <fcntl.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
 #include <inttypes.h>
+#include <log/log.h>
 #include <sys/mman.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <algorithm>
+#include <map>
+#include <vector>
+
+#include "JankTracker.h"
+#include "protos/graphicsstats.pb.h"
+
 namespace android {
 namespace uirenderer {
 
 using namespace google::protobuf;
+using namespace uirenderer::protos;
 
 constexpr int32_t sCurrentFileVersion = 1;
 constexpr int32_t sHeaderSize = 4;
@@ -42,9 +46,9 @@
 constexpr int sHistogramSize = ProfileData::HistogramSize();
 constexpr int sGPUHistogramSize = ProfileData::GPUHistogramSize();
 
-static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto,
-                                      const std::string& package, int64_t versionCode,
-                                      int64_t startTime, int64_t endTime, const ProfileData* data);
+static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::string& package,
+                                      int64_t versionCode, int64_t startTime, int64_t endTime,
+                                      const ProfileData* data);
 static void dumpAsTextToFd(protos::GraphicsStatsProto* proto, int outFd);
 
 class FileDescriptor {
@@ -57,7 +61,7 @@
         }
     }
     bool valid() { return mFd != -1; }
-    operator int() { return mFd; } // NOLINT(google-explicit-constructor)
+    operator int() { return mFd; }  // NOLINT(google-explicit-constructor)
 
 private:
     int mFd;
@@ -167,6 +171,8 @@
     }
     proto->set_package_name(package);
     proto->set_version_code(versionCode);
+    proto->set_pipeline(data->pipelineType() == RenderPipelineType::SkiaGL ?
+            GraphicsStatsProto_PipelineType_GL : GraphicsStatsProto_PipelineType_VULKAN);
     auto summary = proto->mutable_summary();
     summary->set_total_frames(summary->total_frames() + data->totalFrameCount());
     summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount());
@@ -179,8 +185,8 @@
     summary->set_slow_bitmap_upload_count(summary->slow_bitmap_upload_count() +
                                           data->jankTypeCount(kSlowSync));
     summary->set_slow_draw_count(summary->slow_draw_count() + data->jankTypeCount(kSlowRT));
-    summary->set_missed_deadline_count(summary->missed_deadline_count()
-            + data->jankTypeCount(kMissedDeadline));
+    summary->set_missed_deadline_count(summary->missed_deadline_count() +
+                                       data->jankTypeCount(kMissedDeadline));
 
     bool creatingHistogram = false;
     if (proto->histogram_size() == 0) {
@@ -365,17 +371,69 @@
 
 class GraphicsStatsService::Dump {
 public:
-    Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {}
+    Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {
+        if (mFd == -1 && mType == DumpType::Protobuf) {
+            mType = DumpType::ProtobufStatsd;
+        }
+    }
     int fd() { return mFd; }
     DumpType type() { return mType; }
     protos::GraphicsStatsServiceDumpProto& proto() { return mProto; }
+    void mergeStat(const protos::GraphicsStatsProto& stat);
+    void updateProto();
 
 private:
+    // use package name and app version for a key
+    typedef std::pair<std::string, int64_t> DumpKey;
+
+    std::map<DumpKey, protos::GraphicsStatsProto> mStats;
     int mFd;
     DumpType mType;
     protos::GraphicsStatsServiceDumpProto mProto;
 };
 
+void GraphicsStatsService::Dump::mergeStat(const protos::GraphicsStatsProto& stat) {
+    auto dumpKey = std::make_pair(stat.package_name(), stat.version_code());
+    auto findIt = mStats.find(dumpKey);
+    if (findIt == mStats.end()) {
+        mStats[dumpKey] = stat;
+    } else {
+        auto summary = findIt->second.mutable_summary();
+        summary->set_total_frames(summary->total_frames() + stat.summary().total_frames());
+        summary->set_janky_frames(summary->janky_frames() + stat.summary().janky_frames());
+        summary->set_missed_vsync_count(summary->missed_vsync_count() +
+                                        stat.summary().missed_vsync_count());
+        summary->set_high_input_latency_count(summary->high_input_latency_count() +
+                                              stat.summary().high_input_latency_count());
+        summary->set_slow_ui_thread_count(summary->slow_ui_thread_count() +
+                                          stat.summary().slow_ui_thread_count());
+        summary->set_slow_bitmap_upload_count(summary->slow_bitmap_upload_count() +
+                                              stat.summary().slow_bitmap_upload_count());
+        summary->set_slow_draw_count(summary->slow_draw_count() + stat.summary().slow_draw_count());
+        summary->set_missed_deadline_count(summary->missed_deadline_count() +
+                                           stat.summary().missed_deadline_count());
+        for (int bucketIndex = 0; bucketIndex < findIt->second.histogram_size(); bucketIndex++) {
+            auto bucket = findIt->second.mutable_histogram(bucketIndex);
+            bucket->set_frame_count(bucket->frame_count() +
+                                    stat.histogram(bucketIndex).frame_count());
+        }
+        for (int bucketIndex = 0; bucketIndex < findIt->second.gpu_histogram_size();
+             bucketIndex++) {
+            auto bucket = findIt->second.mutable_gpu_histogram(bucketIndex);
+            bucket->set_frame_count(bucket->frame_count() +
+                                    stat.gpu_histogram(bucketIndex).frame_count());
+        }
+        findIt->second.set_stats_start(std::min(findIt->second.stats_start(), stat.stats_start()));
+        findIt->second.set_stats_end(std::max(findIt->second.stats_end(), stat.stats_end()));
+    }
+}
+
+void GraphicsStatsService::Dump::updateProto() {
+    for (auto& stat : mStats) {
+        mProto.add_stats()->CopyFrom(stat.second);
+    }
+}
+
 GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) {
     return new Dump(outFd, type);
 }
@@ -396,8 +454,9 @@
               path.empty() ? "<empty>" : path.c_str(), data);
         return;
     }
-
-    if (dump->type() == DumpType::Protobuf) {
+    if (dump->type() == DumpType::ProtobufStatsd) {
+        dump->mergeStat(statsProto);
+    } else if (dump->type() == DumpType::Protobuf) {
         dump->proto().add_stats()->CopyFrom(statsProto);
     } else {
         dumpAsTextToFd(&statsProto, dump->fd());
@@ -409,7 +468,9 @@
     if (!parseFromFile(path, &statsProto)) {
         return;
     }
-    if (dump->type() == DumpType::Protobuf) {
+    if (dump->type() == DumpType::ProtobufStatsd) {
+        dump->mergeStat(statsProto);
+    } else if (dump->type() == DumpType::Protobuf) {
         dump->proto().add_stats()->CopyFrom(statsProto);
     } else {
         dumpAsTextToFd(&statsProto, dump->fd());
@@ -424,5 +485,79 @@
     delete dump;
 }
 
+class MemOutputStreamLite : public io::ZeroCopyOutputStream {
+public:
+    explicit MemOutputStreamLite() : mCopyAdapter(), mImpl(&mCopyAdapter) {}
+    virtual ~MemOutputStreamLite() {}
+
+    virtual bool Next(void** data, int* size) override { return mImpl.Next(data, size); }
+
+    virtual void BackUp(int count) override { mImpl.BackUp(count); }
+
+    virtual int64 ByteCount() const override { return mImpl.ByteCount(); }
+
+    bool Flush() { return mImpl.Flush(); }
+
+    void copyData(const DumpMemoryFn& reader, void* param1, void* param2) {
+        int bufferOffset = 0;
+        int totalSize = mCopyAdapter.mBuffersSize - mCopyAdapter.mCurrentBufferUnusedSize;
+        int totalDataLeft = totalSize;
+        for (auto& it : mCopyAdapter.mBuffers) {
+            int bufferSize = std::min(totalDataLeft, (int)it.size());  // last buffer is not full
+            reader(it.data(), bufferOffset, bufferSize, totalSize, param1, param2);
+            bufferOffset += bufferSize;
+            totalDataLeft -= bufferSize;
+        }
+    }
+
+private:
+    struct MemAdapter : public io::CopyingOutputStream {
+        // Data is stored in an array of buffers.
+        // JNI SetByteArrayRegion assembles data in one continuous Java byte[] buffer.
+        std::vector<std::vector<unsigned char>> mBuffers;
+        int mBuffersSize = 0;                     // total bytes allocated in mBuffers
+        int mCurrentBufferUnusedSize = 0;         // unused bytes in the last buffer mBuffers.back()
+        unsigned char* mCurrentBuffer = nullptr;  // pointer to next free byte in mBuffers.back()
+
+        explicit MemAdapter() {}
+        virtual ~MemAdapter() {}
+
+        virtual bool Write(const void* buffer, int size) override {
+            while (size > 0) {
+                if (0 == mCurrentBufferUnusedSize) {
+                    mCurrentBufferUnusedSize =
+                            std::max(size, mBuffersSize ? 2 * mBuffersSize : 10000);
+                    mBuffers.emplace_back();
+                    mBuffers.back().resize(mCurrentBufferUnusedSize);
+                    mCurrentBuffer = mBuffers.back().data();
+                    mBuffersSize += mCurrentBufferUnusedSize;
+                }
+                int dataMoved = std::min(mCurrentBufferUnusedSize, size);
+                memcpy(mCurrentBuffer, buffer, dataMoved);
+                mCurrentBufferUnusedSize -= dataMoved;
+                mCurrentBuffer += dataMoved;
+                buffer = reinterpret_cast<const unsigned char*>(buffer) + dataMoved;
+                size -= dataMoved;
+            }
+            return true;
+        }
+    };
+
+    MemOutputStreamLite::MemAdapter mCopyAdapter;
+    io::CopyingOutputStreamAdaptor mImpl;
+};
+
+void GraphicsStatsService::finishDumpInMemory(Dump* dump, const DumpMemoryFn& reader, void* param1,
+                                              void* param2) {
+    MemOutputStreamLite stream;
+    dump->updateProto();
+    bool success = dump->proto().SerializeToZeroCopyStream(&stream) && stream.Flush();
+    delete dump;
+    if (!success) {
+        return;
+    }
+    stream.copyData(reader, param1, param2);
+}
+
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/service/GraphicsStatsService.h b/libs/hwui/service/GraphicsStatsService.h
index 389f599..4bed9633 100644
--- a/libs/hwui/service/GraphicsStatsService.h
+++ b/libs/hwui/service/GraphicsStatsService.h
@@ -27,6 +27,9 @@
 class GraphicsStatsProto;
 }
 
+typedef void (*DumpMemoryFn)(void* buffer, int bufferOffset, int bufferSize, int totalSize,
+                             void* param1, void* param2);
+
 /*
  * The exported entry points used by GraphicsStatsService.java in f/b/services/core
  *
@@ -40,6 +43,7 @@
     enum class DumpType {
         Text,
         Protobuf,
+        ProtobufStatsd,
     };
 
     ANDROID_API static void saveBuffer(const std::string& path, const std::string& package,
@@ -52,6 +56,8 @@
                                       int64_t startTime, int64_t endTime, const ProfileData* data);
     ANDROID_API static void addToDump(Dump* dump, const std::string& path);
     ANDROID_API static void finishDump(Dump* dump);
+    ANDROID_API static void finishDumpInMemory(Dump* dump, const DumpMemoryFn& reader, void* param1,
+                                               void* param2);
 
     // Visible for testing
     static bool parseFromFile(const std::string& path, protos::GraphicsStatsProto* output);
diff --git a/libs/hwui/tests/common/TestContext.cpp b/libs/hwui/tests/common/TestContext.cpp
index 0a54aca..e075d80 100644
--- a/libs/hwui/tests/common/TestContext.cpp
+++ b/libs/hwui/tests/common/TestContext.cpp
@@ -25,16 +25,16 @@
 static const int IDENT_DISPLAYEVENT = 1;
 
 static android::DisplayInfo DUMMY_DISPLAY{
-        1080,   // w
-        1920,   // h
-        320.0,  // xdpi
-        320.0,  // ydpi
-        60.0,   // fps
-        2.0,    // density
-        0,      // orientation
-        false,  // secure?
-        0,      // appVsyncOffset
-        0,      // presentationDeadline
+        1080,           // w
+        1920,           // h
+        320.0,          // xdpi
+        320.0,          // ydpi
+        60.0,           // fps
+        2.0,            // density
+        ui::ROTATION_0, // orientation
+        false,          // secure?
+        0,              // appVsyncOffset
+        0,              // presentationDeadline
 };
 
 DisplayInfo getInternalDisplay() {
diff --git a/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp b/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp
deleted file mode 100644
index dac888c..0000000
--- a/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp
+++ /dev/null
@@ -1,65 +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.
- */
-
-#include <GpuMemoryTracker.h>
-#include <gtest/gtest.h>
-
-#include "renderthread/EglManager.h"
-#include "renderthread/RenderThread.h"
-#include "tests/common/TestUtils.h"
-
-#include <utils/StrongPointer.h>
-
-using namespace android;
-using namespace android::uirenderer;
-using namespace android::uirenderer::renderthread;
-
-class TestGPUObject : public GpuMemoryTracker {
-public:
-    TestGPUObject() : GpuMemoryTracker(GpuObjectType::Texture) {}
-
-    void changeSize(int newSize) { notifySizeChanged(newSize); }
-};
-
-// Other tests may have created a renderthread and EGL context.
-// This will destroy the EGLContext on RenderThread if it exists so that the
-// current thread can spoof being a GPU thread
-static void destroyEglContext() {
-    if (TestUtils::isRenderThreadRunning()) {
-        TestUtils::runOnRenderThread([](RenderThread& thread) { thread.destroyRenderingContext(); });
-    }
-}
-
-TEST(GpuMemoryTracker, sizeCheck) {
-    destroyEglContext();
-
-    GpuMemoryTracker::onGpuContextCreated();
-    ASSERT_EQ(0, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
-    ASSERT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture));
-    {
-        TestGPUObject myObj;
-        ASSERT_EQ(1, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture));
-        myObj.changeSize(500);
-        ASSERT_EQ(500, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
-        myObj.changeSize(1000);
-        ASSERT_EQ(1000, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
-        myObj.changeSize(300);
-        ASSERT_EQ(300, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
-    }
-    ASSERT_EQ(0, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
-    ASSERT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture));
-    GpuMemoryTracker::onGpuContextDestroyed();
-}
diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp
index b93759f..71a27ce 100644
--- a/libs/hwui/utils/Color.cpp
+++ b/libs/hwui/utils/Color.cpp
@@ -108,10 +108,109 @@
     }
 }
 
+namespace {
+static constexpr skcms_TransferFunction k2Dot6 = {2.6f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
+
+// Skia's SkNamedGamut::kDCIP3 is based on a white point of D65. This gamut
+// matches the white point used by ColorSpace.Named.DCIP3.
+static constexpr skcms_Matrix3x3 kDCIP3 = {{
+        {0.486143, 0.323835, 0.154234},
+        {0.226676, 0.710327, 0.0629966},
+        {0.000800549, 0.0432385, 0.78275},
+}};
+
+static bool nearlyEqual(float a, float b) {
+    // By trial and error, this is close enough to match for the ADataSpaces we
+    // compare for.
+    return ::fabs(a - b) < .002f;
+}
+
+static bool nearlyEqual(const skcms_TransferFunction& x, const skcms_TransferFunction& y) {
+    return nearlyEqual(x.g, y.g)
+        && nearlyEqual(x.a, y.a)
+        && nearlyEqual(x.b, y.b)
+        && nearlyEqual(x.c, y.c)
+        && nearlyEqual(x.d, y.d)
+        && nearlyEqual(x.e, y.e)
+        && nearlyEqual(x.f, y.f);
+}
+
+static bool nearlyEqual(const skcms_Matrix3x3& x, const skcms_Matrix3x3& y) {
+    for (int i = 0; i < 3; i++) {
+        for (int j = 0; j < 3; j++) {
+            if (!nearlyEqual(x.vals[i][j], y.vals[i][j])) return false;
+        }
+    }
+    return true;
+}
+
+} // anonymous namespace
+
+android_dataspace ColorSpaceToADataSpace(SkColorSpace* colorSpace, SkColorType colorType) {
+    if (!colorSpace) {
+        return HAL_DATASPACE_UNKNOWN;
+    }
+
+    if (colorSpace->isSRGB()) {
+        if (colorType == kRGBA_F16_SkColorType) {
+            return HAL_DATASPACE_V0_SCRGB;
+        }
+        return HAL_DATASPACE_V0_SRGB;
+    }
+
+    skcms_TransferFunction fn;
+    LOG_ALWAYS_FATAL_IF(!colorSpace->isNumericalTransferFn(&fn));
+
+    skcms_Matrix3x3 gamut;
+    LOG_ALWAYS_FATAL_IF(!colorSpace->toXYZD50(&gamut));
+
+    if (nearlyEqual(gamut, SkNamedGamut::kSRGB)) {
+        if (nearlyEqual(fn, SkNamedTransferFn::kLinear)) {
+            // Skia doesn't differentiate amongst the RANGES. In Java, we associate
+            // LINEAR_EXTENDED_SRGB with F16, and LINEAR_SRGB with other Configs.
+            // Make the same association here.
+            if (colorType == kRGBA_F16_SkColorType) {
+                return HAL_DATASPACE_V0_SCRGB_LINEAR;
+            }
+            return HAL_DATASPACE_V0_SRGB_LINEAR;
+        }
+
+        if (nearlyEqual(fn, SkNamedTransferFn::kRec2020)) {
+            return HAL_DATASPACE_V0_BT709;
+        }
+    }
+
+    if (nearlyEqual(fn, SkNamedTransferFn::kSRGB) && nearlyEqual(gamut, SkNamedGamut::kDCIP3)) {
+        return HAL_DATASPACE_DISPLAY_P3;
+    }
+
+    if (nearlyEqual(fn, SkNamedTransferFn::k2Dot2) && nearlyEqual(gamut, SkNamedGamut::kAdobeRGB)) {
+        return HAL_DATASPACE_ADOBE_RGB;
+    }
+
+    if (nearlyEqual(fn, SkNamedTransferFn::kRec2020) &&
+        nearlyEqual(gamut, SkNamedGamut::kRec2020)) {
+        return HAL_DATASPACE_BT2020;
+    }
+
+    if (nearlyEqual(fn, k2Dot6) && nearlyEqual(gamut, kDCIP3)) {
+        return HAL_DATASPACE_DCI_P3;
+    }
+
+    return HAL_DATASPACE_UNKNOWN;
+}
+
 sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace) {
     if (dataspace == HAL_DATASPACE_UNKNOWN) {
         return SkColorSpace::MakeSRGB();
     }
+    if (dataspace == HAL_DATASPACE_DCI_P3) {
+        // This cannot be handled by the switch statements below because it
+        // needs to use the locally-defined kDCIP3 gamut, rather than the one in
+        // Skia (SkNamedGamut), which is used for other data spaces with
+        // HAL_DATASPACE_STANDARD_DCI_P3 (e.g. HAL_DATASPACE_DISPLAY_P3).
+        return SkColorSpace::MakeRGB(k2Dot6, kDCIP3);
+    }
 
     skcms_Matrix3x3 gamut;
     switch (dataspace & HAL_DATASPACE_STANDARD_MASK) {
@@ -149,13 +248,15 @@
         case HAL_DATASPACE_TRANSFER_GAMMA2_2:
             return SkColorSpace::MakeRGB({2.2f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, gamut);
         case HAL_DATASPACE_TRANSFER_GAMMA2_6:
-            return SkColorSpace::MakeRGB({2.6f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, gamut);
+            return SkColorSpace::MakeRGB(k2Dot6, gamut);
         case HAL_DATASPACE_TRANSFER_GAMMA2_8:
             return SkColorSpace::MakeRGB({2.8f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, gamut);
+        case HAL_DATASPACE_TRANSFER_ST2084:
+            return SkColorSpace::MakeRGB(SkNamedTransferFn::kPQ, gamut);
+        case HAL_DATASPACE_TRANSFER_SMPTE_170M:
+            return SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, gamut);
         case HAL_DATASPACE_TRANSFER_UNSPECIFIED:
             return nullptr;
-        case HAL_DATASPACE_TRANSFER_SMPTE_170M:
-        case HAL_DATASPACE_TRANSFER_ST2084:
         case HAL_DATASPACE_TRANSFER_HLG:
         default:
             ALOGV("Unsupported Gamma: %d", dataspace);
diff --git a/libs/hwui/utils/Color.h b/libs/hwui/utils/Color.h
index 07b5ec8..a76f7e4 100644
--- a/libs/hwui/utils/Color.h
+++ b/libs/hwui/utils/Color.h
@@ -105,6 +105,22 @@
 
 ANDROID_API sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace);
 
+/**
+ * Return the android_dataspace corresponding to colorSpace.
+ *
+ * Note: This currently only returns android_dataspaces with corresponding
+ * ADataSpaces. The NDK relies on this, so if you need to update it to return
+ * an android_dataspace *without* an ADataSpace, the NDK methods need to be
+ * updated.
+ *
+ * @param colorSpace May be null, in which case this will return
+ *                   HAL_DATASPACE_UNKNOWN.
+ * @param colorType Some SkColorSpaces are associated with more than one
+ *                  android_dataspace. In that case, the SkColorType is used to
+ *                  determine which one to return.
+ */
+ANDROID_API android_dataspace ColorSpaceToADataSpace(SkColorSpace*, SkColorType);
+
 struct Lab {
     float L;
     float a;
diff --git a/location/java/android/location/Criteria.java b/location/java/android/location/Criteria.java
index 1370b10..26f73f7 100644
--- a/location/java/android/location/Criteria.java
+++ b/location/java/android/location/Criteria.java
@@ -16,9 +16,16 @@
 
 package android.location;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * A class indicating the application criteria for selecting a
  * location provider. Providers may be ordered according to accuracy,
@@ -26,6 +33,25 @@
  * cost.
  */
 public class Criteria implements Parcelable {
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({NO_REQUIREMENT, POWER_LOW, POWER_MEDIUM, POWER_HIGH})
+    public @interface PowerRequirement {
+    }
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({NO_REQUIREMENT, ACCURACY_LOW, ACCURACY_MEDIUM, ACCURACY_HIGH})
+    public @interface AccuracyRequirement {
+    }
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({NO_REQUIREMENT, ACCURACY_FINE, ACCURACY_COARSE})
+    public @interface LocationAccuracyRequirement {
+    }
+
     /**
      * A constant indicating that the application does not choose to
      * place requirement on a particular feature.
@@ -81,15 +107,15 @@
      */
     public static final int ACCURACY_HIGH = 3;
 
-    private int mHorizontalAccuracy    = NO_REQUIREMENT;
-    private int mVerticalAccuracy      = NO_REQUIREMENT;
-    private int mSpeedAccuracy         = NO_REQUIREMENT;
-    private int mBearingAccuracy       = NO_REQUIREMENT;
-    private int mPowerRequirement      = NO_REQUIREMENT;
-    private boolean mAltitudeRequired  = false;
-    private boolean mBearingRequired   = false;
-    private boolean mSpeedRequired     = false;
-    private boolean mCostAllowed       = false;
+    private int mHorizontalAccuracy = NO_REQUIREMENT;
+    private int mVerticalAccuracy = NO_REQUIREMENT;
+    private int mSpeedAccuracy = NO_REQUIREMENT;
+    private int mBearingAccuracy = NO_REQUIREMENT;
+    private int mPowerRequirement = NO_REQUIREMENT;
+    private boolean mAltitudeRequired = false;
+    private boolean mBearingRequired = false;
+    private boolean mSpeedRequired = false;
+    private boolean mCostAllowed = false;
 
     /**
      * Constructs a new Criteria object.  The new object will have no
@@ -97,7 +123,8 @@
      * require altitude, speed, or bearing; and will not allow monetary
      * cost.
      */
-    public Criteria() {}
+    public Criteria() {
+    }
 
     /**
      * Constructs a new Criteria object that is a copy of the given criteria.
@@ -115,125 +142,121 @@
     }
 
     /**
-     * Indicates the desired horizontal accuracy (latitude and longitude).
-     * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM},
-     * {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}.
-     * More accurate location may consume more power and may take longer.
+     * Indicates the desired horizontal accuracy (latitude and longitude). Accuracy may be
+     * {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH} or
+     * {@link #NO_REQUIREMENT}. More accurate location may consume more power and may take longer.
      *
      * @throws IllegalArgumentException if accuracy is not one of the supported constants
      */
-    public void setHorizontalAccuracy(int accuracy) {
-        if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) {
-            throw new IllegalArgumentException("accuracy=" + accuracy);
-        }
-        mHorizontalAccuracy = accuracy;
+    public void setHorizontalAccuracy(@AccuracyRequirement int accuracy) {
+        mHorizontalAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT,
+                ACCURACY_HIGH, "accuracy");
     }
 
     /**
      * Returns a constant indicating the desired horizontal accuracy (latitude and longitude).
-     * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM},
-     * {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}.
+     *
+     * @see #setHorizontalAccuracy(int)
      */
+    @AccuracyRequirement
     public int getHorizontalAccuracy() {
         return mHorizontalAccuracy;
     }
 
     /**
-     * Indicates the desired vertical accuracy (altitude).
-     * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM},
-     * {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}.
-     * More accurate location may consume more power and may take longer.
+     * Indicates the desired vertical accuracy (altitude). Accuracy may be {@link #ACCURACY_LOW},
+     * {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}. More accurate
+     * location may consume more power and may take longer.
      *
      * @throws IllegalArgumentException if accuracy is not one of the supported constants
      */
-    public void setVerticalAccuracy(int accuracy) {
-        if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) {
-            throw new IllegalArgumentException("accuracy=" + accuracy);
-        }
-        mVerticalAccuracy = accuracy;
+    public void setVerticalAccuracy(@AccuracyRequirement int accuracy) {
+        mVerticalAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT,
+                ACCURACY_HIGH, "accuracy");
     }
 
     /**
      * Returns a constant indicating the desired vertical accuracy (altitude).
-     * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH},
-     * or {@link #NO_REQUIREMENT}.
+     *
+     * @see #setVerticalAccuracy(int)
      */
+    @AccuracyRequirement
     public int getVerticalAccuracy() {
         return mVerticalAccuracy;
     }
 
     /**
-     * Indicates the desired speed accuracy.
-     * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH},
-     * or {@link #NO_REQUIREMENT}.
-     * More accurate location may consume more power and may take longer.
+     * Indicates the desired speed accuracy. Accuracy may be {@link #ACCURACY_LOW},
+     * {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH}, or {@link #NO_REQUIREMENT}. More accurate
+     * location may consume more power and may take longer.
      *
      * @throws IllegalArgumentException if accuracy is not one of the supported constants
      */
-    public void setSpeedAccuracy(int accuracy) {
-        if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) {
-            throw new IllegalArgumentException("accuracy=" + accuracy);
-        }
-        mSpeedAccuracy = accuracy;
+    public void setSpeedAccuracy(@AccuracyRequirement int accuracy) {
+        mSpeedAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT, ACCURACY_HIGH,
+                "accuracy");
     }
 
     /**
-     * Returns a constant indicating the desired speed accuracy
-     * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH},
-     * or {@link #NO_REQUIREMENT}.
+     * Returns a constant indicating the desired speed accuracy.
+     *
+     * @see #setSpeedAccuracy(int)
      */
+    @AccuracyRequirement
     public int getSpeedAccuracy() {
         return mSpeedAccuracy;
     }
 
     /**
-     * Indicates the desired bearing accuracy.
-     * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH},
-     * or {@link #NO_REQUIREMENT}.
-     * More accurate location may consume more power and may take longer.
+     * Indicates the desired bearing accuracy. Accuracy may be {@link #ACCURACY_LOW},
+     * {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH}, or {@link #NO_REQUIREMENT}. More accurate
+     * location may consume more power and may take longer.
      *
      * @throws IllegalArgumentException if accuracy is not one of the supported constants
      */
-    public void setBearingAccuracy(int accuracy) {
-        if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) {
-            throw new IllegalArgumentException("accuracy=" + accuracy);
-        }
-        mBearingAccuracy = accuracy;
+    public void setBearingAccuracy(@AccuracyRequirement int accuracy) {
+        mBearingAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT,
+                ACCURACY_HIGH, "accuracy");
     }
 
     /**
      * Returns a constant indicating the desired bearing accuracy.
-     * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH},
-     * or {@link #NO_REQUIREMENT}.
+     *
+     * @see #setBearingAccuracy(int)
      */
+    @AccuracyRequirement
     public int getBearingAccuracy() {
         return mBearingAccuracy;
     }
 
     /**
-     * Indicates the desired accuracy for latitude and longitude. Accuracy
-     * may be {@link #ACCURACY_FINE} if desired location
-     * is fine, else it can be {@link #ACCURACY_COARSE}.
-     * More accurate location may consume more power and may take longer.
+     * Indicates the desired accuracy for latitude and longitude. Accuracy may be
+     * {@link #ACCURACY_FINE} or {@link #ACCURACY_COARSE}. More accurate location may consume more
+     * power and may take longer.
      *
      * @throws IllegalArgumentException if accuracy is not one of the supported constants
      */
-    public void setAccuracy(int accuracy) {
-        if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_COARSE) {
-            throw new IllegalArgumentException("accuracy=" + accuracy);
-        }
-        if (accuracy == ACCURACY_FINE) {
-            mHorizontalAccuracy = ACCURACY_HIGH;
-        } else {
-            mHorizontalAccuracy = ACCURACY_LOW;
+    public void setAccuracy(@LocationAccuracyRequirement int accuracy) {
+        Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT, ACCURACY_COARSE, "accuracy");
+        switch (accuracy) {
+            case NO_REQUIREMENT:
+                setHorizontalAccuracy(NO_REQUIREMENT);
+                break;
+            case ACCURACY_FINE:
+                setHorizontalAccuracy(ACCURACY_HIGH);
+                break;
+            case ACCURACY_COARSE:
+                setHorizontalAccuracy(ACCURACY_LOW);
+                break;
         }
     }
 
     /**
-     * Returns a constant indicating desired accuracy of location
-     * Accuracy may be {@link #ACCURACY_FINE} if desired location
-     * is fine, else it can be {@link #ACCURACY_COARSE}.
+     * Returns a constant indicating desired accuracy of location.
+     *
+     * @see #setAccuracy(int)
      */
+    @LocationAccuracyRequirement
     public int getAccuracy() {
         if (mHorizontalAccuracy >= ACCURACY_HIGH) {
             return ACCURACY_FINE;
@@ -243,21 +266,20 @@
     }
 
     /**
-     * Indicates the desired maximum power level.  The level parameter
-     * must be one of NO_REQUIREMENT, POWER_LOW, POWER_MEDIUM, or
-     * POWER_HIGH.
+     * Indicates the desired maximum power requirement. The power requirement parameter may be
+     * {@link #NO_REQUIREMENT}, {@link #POWER_LOW}, {@link #POWER_MEDIUM}, or {@link #POWER_HIGH}.
      */
-    public void setPowerRequirement(int level) {
-        if (level < NO_REQUIREMENT || level > POWER_HIGH) {
-            throw new IllegalArgumentException("level=" + level);
-        }
-        mPowerRequirement = level;
+    public void setPowerRequirement(@PowerRequirement int powerRequirement) {
+        mPowerRequirement = Preconditions.checkArgumentInRange(powerRequirement, NO_REQUIREMENT,
+                POWER_HIGH, "powerRequirement");
     }
 
     /**
-     * Returns a constant indicating the desired power requirement.  The
-     * returned
+     * Returns a constant indicating the desired maximum power requirement.
+     *
+     * @see #setPowerRequirement(int)
      */
+    @PowerRequirement
     public int getPowerRequirement() {
         return mPowerRequirement;
     }
@@ -277,8 +299,8 @@
     }
 
     /**
-     * Indicates whether the provider must provide altitude information.
-     * Not all fixes are guaranteed to contain such information.
+     * Indicates whether the provider must provide altitude information. Not all fixes are
+     * guaranteed to contain such information.
      */
     public void setAltitudeRequired(boolean altitudeRequired) {
         mAltitudeRequired = altitudeRequired;
@@ -286,15 +308,16 @@
 
     /**
      * Returns whether the provider must provide altitude information.
-     * Not all fixes are guaranteed to contain such information.
+     *
+     * @see #setAltitudeRequired(boolean)
      */
     public boolean isAltitudeRequired() {
         return mAltitudeRequired;
     }
 
     /**
-     * Indicates whether the provider must provide speed information.
-     * Not all fixes are guaranteed to contain such information.
+     * Indicates whether the provider must provide speed information. Not all fixes are guaranteed
+     * to contain such information.
      */
     public void setSpeedRequired(boolean speedRequired) {
         mSpeedRequired = speedRequired;
@@ -302,15 +325,16 @@
 
     /**
      * Returns whether the provider must provide speed information.
-     * Not all fixes are guaranteed to contain such information.
+     *
+     * @see #setSpeedRequired(boolean)
      */
     public boolean isSpeedRequired() {
         return mSpeedRequired;
     }
 
     /**
-     * Indicates whether the provider must provide bearing information.
-     * Not all fixes are guaranteed to contain such information.
+     * Indicates whether the provider must provide bearing information. Not all fixes are guaranteed
+     * to contain such information.
      */
     public void setBearingRequired(boolean bearingRequired) {
         mBearingRequired = bearingRequired;
@@ -318,34 +342,36 @@
 
     /**
      * Returns whether the provider must provide bearing information.
-     * Not all fixes are guaranteed to contain such information.
+     *
+     * @see #setBearingRequired(boolean)
      */
     public boolean isBearingRequired() {
         return mBearingRequired;
     }
 
-    public static final @android.annotation.NonNull Parcelable.Creator<Criteria> CREATOR =
-        new Parcelable.Creator<Criteria>() {
-        @Override
-        public Criteria createFromParcel(Parcel in) {
-            Criteria c = new Criteria();
-            c.mHorizontalAccuracy = in.readInt();
-            c.mVerticalAccuracy = in.readInt();
-            c.mSpeedAccuracy = in.readInt();
-            c.mBearingAccuracy = in.readInt();
-            c.mPowerRequirement = in.readInt();
-            c.mAltitudeRequired = in.readInt() != 0;
-            c.mBearingRequired = in.readInt() != 0;
-            c.mSpeedRequired = in.readInt() != 0;
-            c.mCostAllowed = in.readInt() != 0;
-            return c;
-        }
+    @NonNull
+    public static final Parcelable.Creator<Criteria> CREATOR =
+            new Parcelable.Creator<Criteria>() {
+                @Override
+                public Criteria createFromParcel(Parcel in) {
+                    Criteria c = new Criteria();
+                    c.mHorizontalAccuracy = in.readInt();
+                    c.mVerticalAccuracy = in.readInt();
+                    c.mSpeedAccuracy = in.readInt();
+                    c.mBearingAccuracy = in.readInt();
+                    c.mPowerRequirement = in.readInt();
+                    c.mAltitudeRequired = in.readInt() != 0;
+                    c.mBearingRequired = in.readInt() != 0;
+                    c.mSpeedRequired = in.readInt() != 0;
+                    c.mCostAllowed = in.readInt() != 0;
+                    return c;
+                }
 
-        @Override
-        public Criteria[] newArray(int size) {
-            return new Criteria[size];
-        }
-    };
+                @Override
+                public Criteria[] newArray(int size) {
+                    return new Criteria[size];
+                }
+            };
 
     @Override
     public int describeContents() {
@@ -365,42 +391,57 @@
         parcel.writeInt(mCostAllowed ? 1 : 0);
     }
 
-    private static String powerToString(int power) {
-        switch (power) {
-            case NO_REQUIREMENT:
-                return "NO_REQ";
-            case POWER_LOW:
-                return "LOW";
-            case POWER_MEDIUM:
-                return "MEDIUM";
-            case POWER_HIGH:
-                return "HIGH";
-            default:
-                return "???";
-        }
-    }
-
-    private static String accuracyToString(int accuracy) {
-        switch (accuracy) {
-            case NO_REQUIREMENT:
-                return "---";
-            case ACCURACY_HIGH:
-                return "HIGH";
-            case ACCURACY_MEDIUM:
-                return "MEDIUM";
-            case ACCURACY_LOW:
-                return "LOW";
-            default:
-                return "???";
-        }
-    }
-
     @Override
     public String toString() {
         StringBuilder s = new StringBuilder();
-        s.append("Criteria[power=").append(powerToString(mPowerRequirement));
-        s.append(" acc=").append(accuracyToString(mHorizontalAccuracy));
+        s.append("Criteria[");
+        s.append("power=").append(requirementToString(mPowerRequirement)).append(", ");
+        s.append("accuracy=").append(requirementToString(mHorizontalAccuracy));
+        if (mVerticalAccuracy != NO_REQUIREMENT) {
+            s.append(", verticalAccuracy=").append(requirementToString(mVerticalAccuracy));
+        }
+        if (mSpeedAccuracy != NO_REQUIREMENT) {
+            s.append(", speedAccuracy=").append(requirementToString(mSpeedAccuracy));
+        }
+        if (mBearingAccuracy != NO_REQUIREMENT) {
+            s.append(", bearingAccuracy=").append(requirementToString(mBearingAccuracy));
+        }
+        if (mAltitudeRequired || mBearingRequired || mSpeedRequired) {
+            s.append(", required=[");
+            if (mAltitudeRequired) {
+                s.append("altitude, ");
+            }
+            if (mBearingRequired) {
+                s.append("bearing, ");
+            }
+            if (mSpeedRequired) {
+                s.append("speed, ");
+            }
+            s.setLength(s.length() - 2);
+            s.append("]");
+        }
+        if (mCostAllowed) {
+            s.append(", costAllowed");
+        }
         s.append(']');
         return s.toString();
     }
+
+    private static String requirementToString(int power) {
+        switch (power) {
+            case NO_REQUIREMENT:
+                return "None";
+            //case ACCURACY_LOW:
+            case POWER_LOW:
+                return "Low";
+            //case ACCURACY_MEDIUM:
+            case POWER_MEDIUM:
+                return "Medium";
+            //case ACCURACY_HIGH:
+            case POWER_HIGH:
+                return "High";
+            default:
+                return "???";
+        }
+    }
 }
diff --git a/location/java/android/location/GnssMeasurement.java b/location/java/android/location/GnssMeasurement.java
index 70abbb3..3eeb3a2 100644
--- a/location/java/android/location/GnssMeasurement.java
+++ b/location/java/android/location/GnssMeasurement.java
@@ -778,10 +778,12 @@
     /**
      * Gets the Carrier-to-noise density in dB-Hz.
      *
-     * <p>Typical range: 10-50 db-Hz.
+     * <p>Typical range: 10-50 dB-Hz. The range of possible C/N0 values is 0-63 dB-Hz to handle
+     * some edge cases.
      *
      * <p>The value contains the measured C/N0 for the signal at the antenna input.
      */
+    @FloatRange(from = 0, to = 63)
     public double getCn0DbHz() {
         return mCn0DbHz;
     }
@@ -805,13 +807,14 @@
     /**
      * Gets the baseband carrier-to-noise density in dB-Hz.
      *
-     * <p>Typical range: 0-50 dB-Hz.
+     * <p>Typical range: 10-50 dB-Hz. The range of possible baseband C/N0 values is 0-63 dB-Hz to
+     * handle some edge cases.
      *
      * <p>The value contains the measured C/N0 for the signal at the baseband. This is typically
      * a few dB weaker than the value estimated for C/N0 at the antenna port, which is reported
      * in {@link #getCn0DbHz()}.
      */
-    @FloatRange(from = 0, to = 50)
+    @FloatRange(from = 0, to = 63)
     public double getBasebandCn0DbHz() {
         return mBasebandCn0DbHz;
     }
diff --git a/location/java/android/location/GnssNavigationMessage.java b/location/java/android/location/GnssNavigationMessage.java
index a83db3f..ca0bfb1 100644
--- a/location/java/android/location/GnssNavigationMessage.java
+++ b/location/java/android/location/GnssNavigationMessage.java
@@ -16,9 +16,9 @@
 
 package android.location;
 
-import android.annotation.TestApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -39,7 +39,8 @@
      */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({TYPE_UNKNOWN, TYPE_GPS_L1CA, TYPE_GPS_L2CNAV, TYPE_GPS_L5CNAV, TYPE_GPS_CNAV2,
-        TYPE_GLO_L1CA, TYPE_BDS_D1, TYPE_BDS_D2, TYPE_GAL_I, TYPE_GAL_F})
+            TYPE_SBS, TYPE_GLO_L1CA, TYPE_QZS_L1CA, TYPE_BDS_D1, TYPE_BDS_D2, TYPE_BDS_CNAV1,
+            TYPE_BDS_CNAV2, TYPE_GAL_I, TYPE_GAL_F, TYPE_IRN_L5CA})
     public @interface GnssNavigationMessageType {}
 
     // The following enumerations must be in sync with the values declared in gps.h
@@ -54,16 +55,26 @@
     public static final int TYPE_GPS_L5CNAV = 0x0103;
     /** GPS CNAV-2 message contained in the structure. */
     public static final int TYPE_GPS_CNAV2 = 0x0104;
+    /** SBAS message contained in the structure. */
+    public static final int TYPE_SBS = 0x0201;
     /** Glonass L1 CA message contained in the structure. */
     public static final int TYPE_GLO_L1CA = 0x0301;
+    /** QZSS L1 C/A message contained in the structure. */
+    public static final int TYPE_QZS_L1CA = 0x0401;
     /** Beidou D1 message contained in the structure. */
     public static final int TYPE_BDS_D1 = 0x0501;
     /** Beidou D2 message contained in the structure. */
     public static final int TYPE_BDS_D2 = 0x0502;
+    /** Beidou CNAV1 message contained in the structure. */
+    public static final int TYPE_BDS_CNAV1 = 0x0503;
+    /** Beidou CNAV2 message contained in the structure. */
+    public static final int TYPE_BDS_CNAV2 = 0x0504;
     /** Galileo I/NAV message contained in the structure. */
     public static final int TYPE_GAL_I = 0x0601;
     /** Galileo F/NAV message contained in the structure. */
     public static final int TYPE_GAL_F = 0x0602;
+    /** IRNSS L5 C/A message contained in the structure. */
+    public static final int TYPE_IRN_L5CA = 0x0701;
 
     /**
      * The Navigation Message Status is 'unknown'.
@@ -199,16 +210,26 @@
                 return "GPS L5-CNAV";
             case TYPE_GPS_CNAV2:
                 return "GPS CNAV2";
+            case TYPE_SBS:
+                return "SBS";
             case TYPE_GLO_L1CA:
                 return "Glonass L1 C/A";
+            case TYPE_QZS_L1CA:
+                return "QZSS L1 C/A";
             case TYPE_BDS_D1:
                 return "Beidou D1";
             case TYPE_BDS_D2:
                 return "Beidou D2";
+            case TYPE_BDS_CNAV1:
+                return "Beidou CNAV1";
+            case TYPE_BDS_CNAV2:
+                return "Beidou CNAV2";
             case TYPE_GAL_I:
                 return "Galileo I";
             case TYPE_GAL_F:
                 return "Galileo F";
+            case TYPE_IRN_L5CA:
+                return "IRNSS L5 C/A";
             default:
                 return "<Invalid:" + mType + ">";
         }
diff --git a/location/java/android/location/GnssStatus.java b/location/java/android/location/GnssStatus.java
index 89a3bc0..f17fa39 100644
--- a/location/java/android/location/GnssStatus.java
+++ b/location/java/android/location/GnssStatus.java
@@ -189,6 +189,7 @@
      * <li>QZSS: 193-200</li>
      * <li>Galileo: 1-36</li>
      * <li>Beidou: 1-37</li>
+     * <li>IRNSS: 1-14</li>
      * </ul>
      *
      * @param satelliteIndex An index from zero to {@link #getSatelliteCount()} - 1
diff --git a/location/java/com/android/internal/location/ProviderProperties.java b/location/java/com/android/internal/location/ProviderProperties.java
index def96f0..68f9ec3 100644
--- a/location/java/com/android/internal/location/ProviderProperties.java
+++ b/location/java/com/android/internal/location/ProviderProperties.java
@@ -16,15 +16,36 @@
 
 package com.android.internal.location;
 
+import android.annotation.IntDef;
+import android.location.Criteria;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * A Parcelable containing (legacy) location provider properties.
  * This object is just used inside the framework and system services.
+ *
  * @hide
  */
 public final class ProviderProperties implements Parcelable {
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({Criteria.POWER_LOW, Criteria.POWER_MEDIUM, Criteria.POWER_HIGH})
+    public @interface PowerRequirement {
+    }
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({Criteria.ACCURACY_FINE, Criteria.ACCURACY_COARSE})
+    public @interface Accuracy {
+    }
+
     /**
      * True if provider requires access to a
      * data network (e.g., the Internet), false otherwise.
@@ -79,58 +100,58 @@
 
     /**
      * Power requirement for this provider.
-     *
-     * @return the power requirement for this provider, as one of the
-     * constants Criteria.POWER_*.
      */
+    @PowerRequirement
     public final int mPowerRequirement;
 
     /**
      * Constant describing the horizontal accuracy returned
      * by this provider.
-     *
-     * @return the horizontal accuracy for this provider, as one of the
-     * constants Criteria.ACCURACY_COARSE or Criteria.ACCURACY_FINE
      */
+    @Accuracy
     public final int mAccuracy;
 
-    public ProviderProperties(boolean mRequiresNetwork,
-            boolean mRequiresSatellite, boolean mRequiresCell, boolean mHasMonetaryCost,
-            boolean mSupportsAltitude, boolean mSupportsSpeed, boolean mSupportsBearing,
-            int mPowerRequirement, int mAccuracy) {
-        this.mRequiresNetwork = mRequiresNetwork;
-        this.mRequiresSatellite = mRequiresSatellite;
-        this.mRequiresCell = mRequiresCell;
-        this.mHasMonetaryCost = mHasMonetaryCost;
-        this.mSupportsAltitude = mSupportsAltitude;
-        this.mSupportsSpeed = mSupportsSpeed;
-        this.mSupportsBearing = mSupportsBearing;
-        this.mPowerRequirement = mPowerRequirement;
-        this.mAccuracy = mAccuracy;
+    public ProviderProperties(boolean requiresNetwork, boolean requiresSatellite,
+            boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude,
+            boolean supportsSpeed, boolean supportsBearing, @PowerRequirement int powerRequirement,
+            @Accuracy int accuracy) {
+        mRequiresNetwork = requiresNetwork;
+        mRequiresSatellite = requiresSatellite;
+        mRequiresCell = requiresCell;
+        mHasMonetaryCost = hasMonetaryCost;
+        mSupportsAltitude = supportsAltitude;
+        mSupportsSpeed = supportsSpeed;
+        mSupportsBearing = supportsBearing;
+        mPowerRequirement = Preconditions.checkArgumentInRange(powerRequirement, Criteria.POWER_LOW,
+                Criteria.POWER_HIGH, "powerRequirement");
+        mAccuracy = Preconditions.checkArgumentInRange(accuracy, Criteria.ACCURACY_FINE,
+                Criteria.ACCURACY_COARSE, "accuracy");
     }
 
     public static final Parcelable.Creator<ProviderProperties> CREATOR =
             new Parcelable.Creator<ProviderProperties>() {
-        @Override
-        public ProviderProperties createFromParcel(Parcel in) {
-            boolean requiresNetwork = in.readInt() == 1;
-            boolean requiresSatellite = in.readInt() == 1;
-            boolean requiresCell = in.readInt() == 1;
-            boolean hasMonetaryCost = in.readInt() == 1;
-            boolean supportsAltitude = in.readInt() == 1;
-            boolean supportsSpeed = in.readInt() == 1;
-            boolean supportsBearing = in.readInt() == 1;
-            int powerRequirement = in.readInt();
-            int accuracy = in.readInt();
-            return new ProviderProperties(requiresNetwork, requiresSatellite,
-                    requiresCell, hasMonetaryCost, supportsAltitude, supportsSpeed, supportsBearing,
-                    powerRequirement, accuracy);
-        }
-        @Override
-        public ProviderProperties[] newArray(int size) {
-            return new ProviderProperties[size];
-        }
-    };
+                @Override
+                public ProviderProperties createFromParcel(Parcel in) {
+                    boolean requiresNetwork = in.readInt() == 1;
+                    boolean requiresSatellite = in.readInt() == 1;
+                    boolean requiresCell = in.readInt() == 1;
+                    boolean hasMonetaryCost = in.readInt() == 1;
+                    boolean supportsAltitude = in.readInt() == 1;
+                    boolean supportsSpeed = in.readInt() == 1;
+                    boolean supportsBearing = in.readInt() == 1;
+                    int powerRequirement = in.readInt();
+                    int accuracy = in.readInt();
+                    return new ProviderProperties(requiresNetwork, requiresSatellite,
+                            requiresCell, hasMonetaryCost, supportsAltitude, supportsSpeed,
+                            supportsBearing,
+                            powerRequirement, accuracy);
+                }
+
+                @Override
+                public ProviderProperties[] newArray(int size) {
+                    return new ProviderProperties[size];
+                }
+            };
 
     @Override
     public int describeContents() {
@@ -149,4 +170,67 @@
         parcel.writeInt(mPowerRequirement);
         parcel.writeInt(mAccuracy);
     }
+
+    @Override
+    public String toString() {
+        StringBuilder b = new StringBuilder("ProviderProperties[");
+        b.append("power=").append(powerToString(mPowerRequirement)).append(", ");
+        b.append("accuracy=").append(accuracyToString(mAccuracy));
+        if (mRequiresNetwork || mRequiresSatellite || mRequiresCell) {
+            b.append(", requires=");
+            if (mRequiresNetwork) {
+                b.append("network,");
+            }
+            if (mRequiresSatellite) {
+                b.append("satellite,");
+            }
+            if (mRequiresCell) {
+                b.append("cell,");
+            }
+            b.setLength(b.length() - 1);
+        }
+        if (mHasMonetaryCost) {
+            b.append(", hasMonetaryCost");
+        }
+        if (mSupportsBearing || mSupportsSpeed || mSupportsAltitude) {
+            b.append(", supports=[");
+            if (mSupportsBearing) {
+                b.append("bearing, ");
+            }
+            if (mSupportsSpeed) {
+                b.append("speed, ");
+            }
+            if (mSupportsAltitude) {
+                b.append("altitude, ");
+            }
+            b.setLength(b.length() - 2);
+            b.append("]");
+        }
+        b.append("]");
+        return b.toString();
+    }
+
+    private static String powerToString(@PowerRequirement int power) {
+        switch (power) {
+            case Criteria.POWER_LOW:
+                return "Low";
+            case Criteria.POWER_MEDIUM:
+                return "Medium";
+            case Criteria.POWER_HIGH:
+                return "High";
+            default:
+                return "???";
+        }
+    }
+
+    private static String accuracyToString(@Accuracy int accuracy) {
+        switch (accuracy) {
+            case Criteria.ACCURACY_COARSE:
+                return "Coarse";
+            case Criteria.ACCURACY_FINE:
+                return "Fine";
+            default:
+                return "???";
+        }
+    }
 }
diff --git a/location/java/com/android/internal/location/ProviderRequest.java b/location/java/com/android/internal/location/ProviderRequest.java
index c23f499..572fbc3 100644
--- a/location/java/com/android/internal/location/ProviderRequest.java
+++ b/location/java/com/android/internal/location/ProviderRequest.java
@@ -20,33 +20,41 @@
 import android.location.LocationRequest;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.WorkSource;
 import android.util.TimeUtils;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 
 /** @hide */
 public final class ProviderRequest implements Parcelable {
+
+    public static final ProviderRequest EMPTY_REQUEST = new ProviderRequest(false, Long.MAX_VALUE,
+            false, false,
+            Collections.emptyList(), new WorkSource());
+
     /** Location reporting is requested (true) */
     @UnsupportedAppUsage
-    public boolean reportLocation = false;
+    public final boolean reportLocation;
 
     /** The smallest requested interval */
     @UnsupportedAppUsage
-    public long interval = Long.MAX_VALUE;
+    public final long interval;
+
+    /**
+     * Whether provider shall make stronger than normal tradeoffs to substantially restrict power
+     * use.
+     */
+    public final boolean lowPowerMode;
 
     /**
      * When this flag is true, providers should ignore all location settings, user consents, power
      * restrictions or any other restricting factors and always satisfy this request to the best of
      * their ability. This flag should only be used in event of an emergency.
      */
-    public boolean locationSettingsIgnored = false;
-
-    /**
-     * Whether provider shall make stronger than normal tradeoffs to substantially restrict power
-     * use.
-     */
-    public boolean lowPowerMode = false;
+    public final boolean locationSettingsIgnored;
 
     /**
      * A more detailed set of requests.
@@ -56,26 +64,37 @@
      * low power fast interval request.
      */
     @UnsupportedAppUsage
-    public final List<LocationRequest> locationRequests = new ArrayList<>();
+    public final List<LocationRequest> locationRequests;
 
-    @UnsupportedAppUsage
-    public ProviderRequest() {
+    public final WorkSource workSource;
+
+    private ProviderRequest(boolean reportLocation, long interval, boolean lowPowerMode,
+            boolean locationSettingsIgnored, List<LocationRequest> locationRequests,
+            WorkSource workSource) {
+        this.reportLocation = reportLocation;
+        this.interval = interval;
+        this.lowPowerMode = lowPowerMode;
+        this.locationSettingsIgnored = locationSettingsIgnored;
+        this.locationRequests = Objects.requireNonNull(locationRequests);
+        this.workSource = Objects.requireNonNull(workSource);
     }
 
     public static final Parcelable.Creator<ProviderRequest> CREATOR =
             new Parcelable.Creator<ProviderRequest>() {
                 @Override
                 public ProviderRequest createFromParcel(Parcel in) {
-                    ProviderRequest request = new ProviderRequest();
-                    request.reportLocation = in.readInt() == 1;
-                    request.interval = in.readLong();
-                    request.lowPowerMode = in.readBoolean();
-                    request.locationSettingsIgnored = in.readBoolean();
+                    boolean reportLocation = in.readInt() == 1;
+                    long interval = in.readLong();
+                    boolean lowPowerMode = in.readBoolean();
+                    boolean locationSettingsIgnored = in.readBoolean();
                     int count = in.readInt();
+                    ArrayList<LocationRequest> locationRequests = new ArrayList<>(count);
                     for (int i = 0; i < count; i++) {
-                        request.locationRequests.add(LocationRequest.CREATOR.createFromParcel(in));
+                        locationRequests.add(LocationRequest.CREATOR.createFromParcel(in));
                     }
-                    return request;
+                    WorkSource workSource = in.readParcelable(null);
+                    return new ProviderRequest(reportLocation, interval, lowPowerMode,
+                            locationSettingsIgnored, locationRequests, workSource);
                 }
 
                 @Override
@@ -99,6 +118,7 @@
         for (LocationRequest request : locationRequests) {
             request.writeToParcel(parcel, flags);
         }
+        parcel.writeParcelable(workSource, flags);
     }
 
     @Override
@@ -106,14 +126,13 @@
         StringBuilder s = new StringBuilder();
         s.append("ProviderRequest[");
         if (reportLocation) {
-            s.append("ON");
-            s.append(" interval=");
+            s.append("interval=");
             TimeUtils.formatDuration(interval, s);
             if (lowPowerMode) {
-                s.append(" lowPowerMode");
+                s.append(", lowPowerMode");
             }
             if (locationSettingsIgnored) {
-                s.append(" locationSettingsIgnored");
+                s.append(", locationSettingsIgnored");
             }
         } else {
             s.append("OFF");
@@ -121,4 +140,77 @@
         s.append(']');
         return s.toString();
     }
+
+    /**
+     * A Builder for {@link ProviderRequest}s.
+     */
+    public static class Builder {
+        private long mInterval = Long.MAX_VALUE;
+        private boolean mLowPowerMode;
+        private boolean mLocationSettingsIgnored;
+        private List<LocationRequest> mLocationRequests = Collections.emptyList();
+        private WorkSource mWorkSource = new WorkSource();
+
+        public long getInterval() {
+            return mInterval;
+        }
+
+        /** Sets the request interval. */
+        public Builder setInterval(long interval) {
+            this.mInterval = interval;
+            return this;
+        }
+
+        public boolean isLowPowerMode() {
+            return mLowPowerMode;
+        }
+
+        /** Sets whether low power mode is enabled. */
+        public Builder setLowPowerMode(boolean lowPowerMode) {
+            this.mLowPowerMode = lowPowerMode;
+            return this;
+        }
+
+        public boolean isLocationSettingsIgnored() {
+            return mLocationSettingsIgnored;
+        }
+
+        /** Sets whether location settings should be ignored. */
+        public Builder setLocationSettingsIgnored(boolean locationSettingsIgnored) {
+            this.mLocationSettingsIgnored = locationSettingsIgnored;
+            return this;
+        }
+
+        public List<LocationRequest> getLocationRequests() {
+            return mLocationRequests;
+        }
+
+        /** Sets the {@link LocationRequest}s associated with this request. */
+        public Builder setLocationRequests(List<LocationRequest> locationRequests) {
+            this.mLocationRequests = Objects.requireNonNull(locationRequests);
+            return this;
+        }
+
+        public WorkSource getWorkSource() {
+            return mWorkSource;
+        }
+
+        /** Sets the work source. */
+        public Builder setWorkSource(WorkSource workSource) {
+            mWorkSource = Objects.requireNonNull(workSource);
+            return this;
+        }
+
+        /**
+         * Builds a ProviderRequest object with the set information.
+         */
+        public ProviderRequest build() {
+            if (mInterval == Long.MAX_VALUE) {
+                return EMPTY_REQUEST;
+            } else {
+                return new ProviderRequest(true, mInterval, mLowPowerMode,
+                        mLocationSettingsIgnored, mLocationRequests, mWorkSource);
+            }
+        }
+    }
 }
diff --git a/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java b/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
index 751bb6a..127d00c 100644
--- a/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
+++ b/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
@@ -110,7 +110,6 @@
      * Logs the status of a location report received from the HAL
      */
     public void logReceivedLocationStatus(boolean isSuccessful) {
-        StatsLog.write(StatsLog.GPS_LOCATION_STATUS_REPORTED, isSuccessful);
         if (!isSuccessful) {
             mLocationFailureStatistics.addItem(1.0);
             return;
@@ -127,7 +126,6 @@
                 DEFAULT_TIME_BETWEEN_FIXES_MILLISECS, desiredTimeBetweenFixesMilliSeconds)) - 1;
         if (numReportMissed > 0) {
             for (int i = 0; i < numReportMissed; i++) {
-                StatsLog.write(StatsLog.GPS_LOCATION_STATUS_REPORTED, false);
                 mLocationFailureStatistics.addItem(1.0);
             }
         }
@@ -138,7 +136,6 @@
      */
     public void logTimeToFirstFixMilliSecs(int timeToFirstFixMilliSeconds) {
         mTimeToFirstFixSecStatistics.addItem((double) (timeToFirstFixMilliSeconds / 1000));
-        StatsLog.write(StatsLog.GPS_TIME_TO_FIRST_FIX_REPORTED, timeToFirstFixMilliSeconds);
     }
 
     /**
diff --git a/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java b/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java
index d12d6b7..b650efc 100644
--- a/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java
+++ b/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java
@@ -34,6 +34,7 @@
  * of this package for more information.
  */
 public final class ProviderRequestUnbundled {
+
     private final ProviderRequest mRequest;
 
     /** @hide */
diff --git a/media/OWNERS b/media/OWNERS
index 8bd037a..be60583 100644
--- a/media/OWNERS
+++ b/media/OWNERS
@@ -5,6 +5,7 @@
 etalvala@google.com
 gkasten@google.com
 hdmoon@google.com
+hkuang@google.com
 hunga@google.com
 insun@google.com
 jaewan@google.com
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index ece5335..114c0f1 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -99,6 +99,10 @@
     public final static int CONTENT_TYPE_SONIFICATION = 4;
 
     /**
+     * Invalid value, only ever used for an uninitialized usage value
+     */
+    private static final int USAGE_INVALID = -1;
+    /**
      * Usage value to use when the usage is unknown.
      */
     public final static int USAGE_UNKNOWN = 0;
@@ -184,9 +188,43 @@
      * Usage value to use for assistant voice interaction with remote caller on Cell and VoIP calls.
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.MODIFY_PHONE_STATE,
+            android.Manifest.permission.MODIFY_AUDIO_ROUTING
+    })
     public static final int USAGE_CALL_ASSISTANT = 17;
 
+    private static final int SYSTEM_USAGE_OFFSET = 1000;
+
+    /**
+     * @hide
+     * Usage value to use when the usage is an emergency.
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public static final int USAGE_EMERGENCY = SYSTEM_USAGE_OFFSET;
+    /**
+     * @hide
+     * Usage value to use when the usage is a safety sound.
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public static final int USAGE_SAFETY = SYSTEM_USAGE_OFFSET + 1;
+    /**
+     * @hide
+     * Usage value to use when the usage is a vehicle status.
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public static final int USAGE_VEHICLE_STATUS = SYSTEM_USAGE_OFFSET + 2;
+    /**
+     * @hide
+     * Usage value to use when the usage is an announcement.
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public static final int USAGE_ANNOUNCEMENT = SYSTEM_USAGE_OFFSET + 3;
+
     /**
      * IMPORTANT: when adding new usage types, add them to SDK_USAGES and update SUPPRESSIBLE_USAGES
      *            if applicable, as well as audioattributes.proto.
@@ -489,6 +527,20 @@
      * @return one of the values that can be set in {@link Builder#setUsage(int)}
      */
     public int getUsage() {
+        if (isSystemUsage(mUsage)) {
+            return USAGE_UNKNOWN;
+        }
+        return mUsage;
+    }
+
+    /**
+     * @hide
+     * Return the system usage.
+     * @return one of the values that can be set in {@link Builder#setUsage(int)} or
+     * {@link Builder#setSystemUsage(int)}
+     */
+    @SystemApi
+    public int getSystemUsage() {
         return mUsage;
     }
 
@@ -591,13 +643,19 @@
      * {@link MediaPlayer} will use a default usage of {@link AudioAttributes#USAGE_MEDIA}.
      */
     public static class Builder {
-        private int mUsage = USAGE_UNKNOWN;
+        private int mUsage = USAGE_INVALID;
+        private int mSystemUsage = USAGE_INVALID;
         private int mContentType = CONTENT_TYPE_UNKNOWN;
         private int mSource = MediaRecorder.AudioSource.AUDIO_SOURCE_INVALID;
         private int mFlags = 0x0;
         private boolean mMuteHapticChannels = true;
         private HashSet<String> mTags = new HashSet<String>();
         private Bundle mBundle;
+        private int mPrivacySensitive = PRIVACY_SENSITIVE_DEFAULT;
+
+        private static final int PRIVACY_SENSITIVE_DEFAULT = -1;
+        private static final int PRIVACY_SENSITIVE_DISABLED = 0;
+        private static final int PRIVACY_SENSITIVE_ENABLED = 1;
 
         /**
          * Constructs a new Builder with the defaults.
@@ -632,17 +690,41 @@
         public AudioAttributes build() {
             AudioAttributes aa = new AudioAttributes();
             aa.mContentType = mContentType;
-            aa.mUsage = mUsage;
+
+            if (mUsage == USAGE_INVALID) {
+                if (mSystemUsage == USAGE_INVALID) {
+                    aa.mUsage = USAGE_UNKNOWN;
+                } else {
+                    aa.mUsage = mSystemUsage;
+                }
+            } else {
+                if (mSystemUsage == USAGE_INVALID) {
+                    aa.mUsage = mUsage;
+                } else {
+                    throw new IllegalArgumentException(
+                            "Cannot set both usage and system usage on same builder");
+                }
+            }
+
             aa.mSource = mSource;
             aa.mFlags = mFlags;
             if (mMuteHapticChannels) {
                 aa.mFlags |= FLAG_MUTE_HAPTIC;
             }
-            // capturing for camcorder of communication is private by default to
-            // reflect legacy behavior
-            if (aa.mSource == MediaRecorder.AudioSource.VOICE_COMMUNICATION
-                    || aa.mSource == MediaRecorder.AudioSource.CAMCORDER) {
+
+            if (mPrivacySensitive == PRIVACY_SENSITIVE_DEFAULT) {
+                // capturing for camcorder or communication is private by default to
+                // reflect legacy behavior
+                if (mSource == MediaRecorder.AudioSource.VOICE_COMMUNICATION
+                        || mSource == MediaRecorder.AudioSource.CAMCORDER) {
+                    aa.mFlags |= FLAG_CAPTURE_PRIVATE;
+                } else {
+                    aa.mFlags &= ~FLAG_CAPTURE_PRIVATE;
+                }
+            } else if (mPrivacySensitive == PRIVACY_SENSITIVE_ENABLED) {
                 aa.mFlags |= FLAG_CAPTURE_PRIVATE;
+            } else {
+                aa.mFlags &= ~FLAG_CAPTURE_PRIVATE;
             }
             aa.mTags = (HashSet<String>) mTags.clone();
             aa.mFormattedTags = TextUtils.join(";", mTags);
@@ -653,26 +735,26 @@
         }
 
         /**
-         * Sets the attribute describing what is the intended use of the the audio signal,
+         * Sets the attribute describing what is the intended use of the audio signal,
          * such as alarm or ringtone.
-         * @param usage one of {@link AudioAttributes#USAGE_UNKNOWN},
-         *     {@link AudioAttributes#USAGE_MEDIA},
-         *     {@link AudioAttributes#USAGE_VOICE_COMMUNICATION},
-         *     {@link AudioAttributes#USAGE_VOICE_COMMUNICATION_SIGNALLING},
-         *     {@link AudioAttributes#USAGE_ALARM}, {@link AudioAttributes#USAGE_NOTIFICATION},
-         *     {@link AudioAttributes#USAGE_NOTIFICATION_RINGTONE},
-         *     {@link AudioAttributes#USAGE_NOTIFICATION_COMMUNICATION_REQUEST},
-         *     {@link AudioAttributes#USAGE_NOTIFICATION_COMMUNICATION_INSTANT},
-         *     {@link AudioAttributes#USAGE_NOTIFICATION_COMMUNICATION_DELAYED},
-         *     {@link AudioAttributes#USAGE_NOTIFICATION_EVENT},
-         *     {@link AudioAttributes#USAGE_ASSISTANT},
-         *     {@link AudioAttributes#USAGE_ASSISTANCE_ACCESSIBILITY},
-         *     {@link AudioAttributes#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE},
-         *     {@link AudioAttributes#USAGE_ASSISTANCE_SONIFICATION},
-         *     {@link AudioAttributes#USAGE_GAME}.
+         * @param usage one of {@link AttributeSdkUsage#USAGE_UNKNOWN},
+         *     {@link AttributeSdkUsage#USAGE_MEDIA},
+         *     {@link AttributeSdkUsage#USAGE_VOICE_COMMUNICATION},
+         *     {@link AttributeSdkUsage#USAGE_VOICE_COMMUNICATION_SIGNALLING},
+         *     {@link AttributeSdkUsage#USAGE_ALARM}, {@link AudioAttributes#USAGE_NOTIFICATION},
+         *     {@link AttributeSdkUsage#USAGE_NOTIFICATION_RINGTONE},
+         *     {@link AttributeSdkUsage#USAGE_NOTIFICATION_COMMUNICATION_REQUEST},
+         *     {@link AttributeSdkUsage#USAGE_NOTIFICATION_COMMUNICATION_INSTANT},
+         *     {@link AttributeSdkUsage#USAGE_NOTIFICATION_COMMUNICATION_DELAYED},
+         *     {@link AttributeSdkUsage#USAGE_NOTIFICATION_EVENT},
+         *     {@link AttributeSdkUsage#USAGE_ASSISTANT},
+         *     {@link AttributeSdkUsage#USAGE_ASSISTANCE_ACCESSIBILITY},
+         *     {@link AttributeSdkUsage#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE},
+         *     {@link AttributeSdkUsage#USAGE_ASSISTANCE_SONIFICATION},
+         *     {@link AttributeSdkUsage#USAGE_GAME}.
          * @return the same Builder instance.
          */
-        public Builder setUsage(@AttributeUsage int usage) {
+        public Builder setUsage(@AttributeSdkUsage int usage) {
             switch (usage) {
                 case USAGE_UNKNOWN:
                 case USAGE_MEDIA:
@@ -691,7 +773,6 @@
                 case USAGE_GAME:
                 case USAGE_VIRTUAL_SOURCE:
                 case USAGE_ASSISTANT:
-                case USAGE_CALL_ASSISTANT:
                     mUsage = usage;
                     break;
                 default:
@@ -701,6 +782,28 @@
         }
 
         /**
+         * @hide
+         * Sets the attribute describing what is the intended use of the audio signal for categories
+         * of sounds restricted to the system, such as vehicle status or emergency.
+         *
+         * <p>Note that the AudioAttributes have a single usage value, therefore it is illegal to
+         * call both this method and {@link #setUsage(int)}.
+         * @param systemUsage the system-restricted usage.
+         * @return the same Builder instance.
+         */
+        @SystemApi
+        @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+        public @NonNull Builder setSystemUsage(@AttributeSystemUsage int systemUsage) {
+            if (isSystemUsage(systemUsage)) {
+                mSystemUsage = systemUsage;
+            } else {
+                throw new IllegalArgumentException("Invalid system usage " + systemUsage);
+            }
+
+            return this;
+        }
+
+        /**
          * Sets the attribute describing the content type of the audio signal, such as speech,
          * or music.
          * @param contentType the content type values, one of
@@ -967,6 +1070,20 @@
             mMuteHapticChannels = muted;
             return this;
         }
+
+        /**
+         * @hide
+         * Indicates if an AudioRecord build with this AudioAttributes is privacy sensitive or not.
+         * See {@link AudioRecord.Builder#setPrivacySensitive(boolean)}.
+         * @param privacySensitive True if capture must be marked as privacy sensitive,
+         * false otherwise.
+         * @return the same Builder instance.
+         */
+        public @NonNull Builder setPrivacySensitive(boolean privacySensitive) {
+            mPrivacySensitive =
+                privacySensitive ? PRIVACY_SENSITIVE_ENABLED : PRIVACY_SENSITIVE_DISABLED;
+            return this;
+        }
     };
 
     @Override
@@ -1147,6 +1264,14 @@
                 return new String("USAGE_ASSISTANT");
             case USAGE_CALL_ASSISTANT:
                 return new String("USAGE_CALL_ASSISTANT");
+            case USAGE_EMERGENCY:
+                return new String("USAGE_EMERGENCY");
+            case USAGE_SAFETY:
+                return new String("USAGE_SAFETY");
+            case USAGE_VEHICLE_STATUS:
+                return new String("USAGE_VEHICLE_STATUS");
+            case USAGE_ANNOUNCEMENT:
+                return new String("USAGE_ANNOUNCEMENT");
             default:
                 return new String("unknown usage " + usage);
         }
@@ -1193,6 +1318,25 @@
     }
 
     /**
+     * @param usage one of {@link AttributeSystemUsage},
+     *     {@link AttributeSystemUsage#USAGE_CALL_ASSISTANT},
+     *     {@link AttributeSystemUsage#USAGE_EMERGENCY},
+     *     {@link AttributeSystemUsage#USAGE_SAFETY},
+     *     {@link AttributeSystemUsage#USAGE_VEHICLE_STATUS},
+     *     {@link AttributeSystemUsage#USAGE_ANNOUNCEMENT}
+     * @return boolean indicating if the usage is a system usage or not
+     * @hide
+     */
+    @SystemApi
+    public static boolean isSystemUsage(@AttributeSystemUsage int usage) {
+        return (usage == USAGE_CALL_ASSISTANT
+                || usage == USAGE_EMERGENCY
+                || usage == USAGE_SAFETY
+                || usage == USAGE_VEHICLE_STATUS
+                || usage == USAGE_ANNOUNCEMENT);
+    }
+
+    /**
      * Returns the stream type matching this {@code AudioAttributes} instance for volume control.
      * Use this method to derive the stream type needed to configure the volume
      * control slider in an {@link android.app.Activity} with
@@ -1267,6 +1411,10 @@
                 return AudioSystem.STREAM_NOTIFICATION;
             case USAGE_ASSISTANCE_ACCESSIBILITY:
                 return AudioSystem.STREAM_ACCESSIBILITY;
+            case USAGE_EMERGENCY:
+            case USAGE_SAFETY:
+            case USAGE_VEHICLE_STATUS:
+            case USAGE_ANNOUNCEMENT:
             case USAGE_UNKNOWN:
                 return AudioSystem.STREAM_MUSIC;
             default:
@@ -1299,6 +1447,39 @@
 
     /** @hide */
     @IntDef({
+            USAGE_CALL_ASSISTANT,
+            USAGE_EMERGENCY,
+            USAGE_SAFETY,
+            USAGE_VEHICLE_STATUS,
+            USAGE_ANNOUNCEMENT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AttributeSystemUsage {}
+
+    /** @hide */
+    @IntDef({
+            USAGE_UNKNOWN,
+            USAGE_MEDIA,
+            USAGE_VOICE_COMMUNICATION,
+            USAGE_VOICE_COMMUNICATION_SIGNALLING,
+            USAGE_ALARM,
+            USAGE_NOTIFICATION,
+            USAGE_NOTIFICATION_RINGTONE,
+            USAGE_NOTIFICATION_COMMUNICATION_REQUEST,
+            USAGE_NOTIFICATION_COMMUNICATION_INSTANT,
+            USAGE_NOTIFICATION_COMMUNICATION_DELAYED,
+            USAGE_NOTIFICATION_EVENT,
+            USAGE_ASSISTANCE_ACCESSIBILITY,
+            USAGE_ASSISTANCE_NAVIGATION_GUIDANCE,
+            USAGE_ASSISTANCE_SONIFICATION,
+            USAGE_GAME,
+            USAGE_ASSISTANT,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AttributeSdkUsage {}
+
+    /** @hide */
+    @IntDef({
         USAGE_UNKNOWN,
         USAGE_MEDIA,
         USAGE_VOICE_COMMUNICATION,
@@ -1316,6 +1497,10 @@
         USAGE_GAME,
         USAGE_ASSISTANT,
         USAGE_CALL_ASSISTANT,
+        USAGE_EMERGENCY,
+        USAGE_SAFETY,
+        USAGE_VEHICLE_STATUS,
+        USAGE_ANNOUNCEMENT,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AttributeUsage {}
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index 8293b5f..6e63d17 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.util.SparseIntArray;
 
 import java.lang.annotation.Retention;
@@ -127,6 +128,23 @@
      * A device type describing a Hearing Aid.
      */
     public static final int TYPE_HEARING_AID   = 23;
+    /**
+     * A device type describing the speaker system (i.e. a mono speaker or stereo speakers) built
+     * in a device, that is specifically tuned for outputting sounds like notifications and alarms
+     * (i.e. sounds the user couldn't necessarily anticipate).
+     * <p>Note that this physical audio device may be the same as {@link #TYPE_BUILTIN_SPEAKER}
+     * but is driven differently to safely accommodate the different use case.</p>
+     */
+    public static final int TYPE_BUILTIN_SPEAKER_SAFE = 24;
+    /**
+     * @hide
+     * A device type for rerouting audio within the Android framework between mixes and
+     * system applications. Typically created when using
+     * {@link android.media.audiopolicy.AudioPolicy} for mixes created with the
+     * {@link android.media.audiopolicy.AudioMix#ROUTE_FLAG_RENDER} flag.
+     */
+    @SystemApi
+    public static final int TYPE_REMOTE_SUBMIX = 25;
 
     /** @hide */
     @IntDef(flag = false, prefix = "TYPE", value = {
@@ -228,6 +246,8 @@
             case TYPE_IP:
             case TYPE_BUS:
             case TYPE_HEARING_AID:
+            case TYPE_BUILTIN_SPEAKER_SAFE:
+            case TYPE_REMOTE_SUBMIX:
                 return true;
             default:
                 return false;
@@ -253,6 +273,7 @@
             case TYPE_LINE_DIGITAL:
             case TYPE_IP:
             case TYPE_BUS:
+            case TYPE_REMOTE_SUBMIX:
                 return true;
             default:
                 return false;
@@ -401,6 +422,40 @@
         return AudioFormat.filterPublicFormats(mPort.formats());
     }
 
+    /**
+     * Returns an array of supported encapsulation modes for the device.
+     *
+     * The array can include any of
+     * {@link AudioTrack#ENCAPSULATION_MODE_ELEMENTARY_STREAM},
+     * {@link AudioTrack#ENCAPSULATION_MODE_HANDLE}.
+     *
+     * @return An array of supported encapsulation modes for the device.  This
+     *     may be an empty array if no encapsulation modes are supported.
+     */
+    public @NonNull int[] getEncapsulationModes() {
+        // Implement a getter in r-dev or r-tv-dev as needed.
+        return new int[0];  // be careful of returning a copy of any internal data.
+    }
+
+    /**
+     * Returns an array of supported encapsulation metadata types for the device.
+     *
+     * The metadata type returned should be allowed for all encapsulation modes supported
+     * by the device.  Some metadata types may apply only to certain
+     * compressed stream formats, the returned list is the union of subsets.
+     *
+     * The array can include any of
+     * {@link AudioTrack#ENCAPSULATION_METADATA_TYPE_FRAMEWORK_TUNER},
+     * {@link AudioTrack#ENCAPSULATION_METADATA_TYPE_DVB_AD_DESCRIPTOR}.
+     *
+     * @return An array of supported encapsulation metadata types for the device.  This
+     *     may be an empty array if no metadata types are supported.
+     */
+    public @NonNull int[] getEncapsulationMetadataTypes() {
+        // Implement a getter in r-dev or r-tv-dev as needed.
+        return new int[0];  // be careful of returning a copy of any internal data.
+    }
+
    /**
      * @return The device type identifier of the audio device (i.e. TYPE_BUILTIN_SPEAKER).
      */
@@ -449,6 +504,9 @@
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_IP, TYPE_IP);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BUS, TYPE_BUS);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_HEARING_AID, TYPE_HEARING_AID);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_SPEAKER_SAFE,
+                TYPE_BUILTIN_SPEAKER_SAFE);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_REMOTE_SUBMIX, TYPE_REMOTE_SUBMIX);
 
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUILTIN_MIC, TYPE_BUILTIN_MIC);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET, TYPE_BLUETOOTH_SCO);
@@ -468,10 +526,7 @@
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, TYPE_BLUETOOTH_A2DP);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_IP, TYPE_IP);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUS, TYPE_BUS);
-
-        // not covered here, legacy
-        //AudioSystem.DEVICE_OUT_REMOTE_SUBMIX
-        //AudioSystem.DEVICE_IN_REMOTE_SUBMIX
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_REMOTE_SUBMIX, TYPE_REMOTE_SUBMIX);
 
         // privileges mapping to output device
         EXT_TO_INT_DEVICE_MAPPING = new SparseIntArray();
@@ -498,6 +553,9 @@
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_IP, AudioSystem.DEVICE_OUT_IP);
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BUS, AudioSystem.DEVICE_OUT_BUS);
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_HEARING_AID, AudioSystem.DEVICE_OUT_HEARING_AID);
+        EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BUILTIN_SPEAKER_SAFE,
+                AudioSystem.DEVICE_OUT_SPEAKER_SAFE);
+        EXT_TO_INT_DEVICE_MAPPING.put(TYPE_REMOTE_SUBMIX, AudioSystem.DEVICE_OUT_REMOTE_SUBMIX);
     }
 }
 
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index e410882..861b76d 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -37,6 +37,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.media.AudioAttributes.AttributeSystemUsage;
 import android.media.audiopolicy.AudioPolicy;
 import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener;
 import android.media.audiopolicy.AudioProductStrategy;
@@ -1268,6 +1269,39 @@
     }
 
     /**
+     * Set the system usages to be supported on this device.
+     * @param systemUsages array of system usages to support {@link AttributeSystemUsage}
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public void setSupportedSystemUsages(@NonNull @AttributeSystemUsage int[] systemUsages) {
+        Objects.requireNonNull(systemUsages, "systemUsages must not be null");
+        final IAudioService service = getService();
+        try {
+            service.setSupportedSystemUsages(systemUsages);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get the system usages supported on this device.
+     * @return array of supported system usages {@link AttributeSystemUsage}
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public @NonNull @AttributeSystemUsage int[] getSupportedSystemUsages() {
+        final IAudioService service = getService();
+        try {
+            return service.getSupportedSystemUsages();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Solo or unsolo a particular stream.
      * <p>
      * Do not use. This method has been deprecated and is now a no-op.
@@ -4331,6 +4365,26 @@
         }
     }
 
+    /**
+     * @hide
+     * Get the audio devices that would be used for the routing of the given audio attributes.
+     * @param attributes the {@link AudioAttributes} for which the routing is being queried
+     * @return an empty list if there was an issue with the request, a list of audio devices
+     *   otherwise (typically one device, except for duplicated paths).
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public @NonNull List<AudioDeviceAddress> getDevicesForAttributes(
+            @NonNull AudioAttributes attributes) {
+        Objects.requireNonNull(attributes);
+        final IAudioService service = getService();
+        try {
+            return service.getDevicesForAttributes(attributes);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
      /**
      * Indicate wired accessory connection state change.
      * @param device type of device connected/disconnected (AudioManager.DEVICE_OUT_xxx)
@@ -4513,6 +4567,70 @@
     }
 
     /**
+     * @hide
+     * Sets an additional audio output device delay in milliseconds.
+     *
+     * The additional output delay is a request to the output device to
+     * delay audio presentation (generally with respect to video presentation for better
+     * synchronization).
+     * It may not be supported by all output devices,
+     * and typically increases the audio latency by the amount of additional
+     * audio delay requested.
+     *
+     * If additional audio delay is supported by an audio output device,
+     * it is expected to be supported for all output streams (and configurations)
+     * opened on that device.
+     *
+     * @param device an instance of {@link AudioDeviceInfo} returned from {@link getDevices()}.
+     * @param delayMs delay in milliseconds desired.  This should be in range of {@code 0}
+     *     to the value returned by {@link #getMaxAdditionalOutputDeviceDelay()}.
+     * @return true if successful, false if the device does not support output device delay
+     *     or the delay is not in range of {@link #getMaxAdditionalOutputDeviceDelay()}.
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public boolean setAdditionalOutputDeviceDelay(
+            @NonNull AudioDeviceInfo device, @IntRange(from = 0) int delayMs) {
+        Objects.requireNonNull(device);
+        // Implement the setter in r-dev or r-tv-dev as needed.
+        return false;
+    }
+
+    /**
+     * @hide
+     * Returns the current additional audio output device delay in milliseconds.
+     *
+     * @param device an instance of {@link AudioDeviceInfo} returned from {@link getDevices()}.
+     * @return the additional output device delay. This is a non-negative number.
+     *     {@code 0} is returned if unsupported.
+     */
+    @SystemApi
+    @IntRange(from = 0)
+    public int getAdditionalOutputDeviceDelay(@NonNull AudioDeviceInfo device) {
+        Objects.requireNonNull(device);
+        // Implement the getter in r-dev or r-tv-dev as needed.
+        return 0;
+    }
+
+    /**
+     * @hide
+     * Returns the maximum additional audio output device delay in milliseconds.
+     *
+     * @param device an instance of {@link AudioDeviceInfo} returned from {@link getDevices()}.
+     * @return the maximum output device delay in milliseconds that can be set.
+     *     This is a non-negative number
+     *     representing the additional audio delay supported for the device.
+     *     {@code 0} is returned if unsupported.
+     */
+    @SystemApi
+    @IntRange(from = 0)
+    public int getMaxAdditionalOutputDeviceDelay(@NonNull AudioDeviceInfo device) {
+        Objects.requireNonNull(device);
+        // Implement the getter in r-dev or r-tv-dev as needed.
+        return 0;
+    }
+
+    /**
      * Returns the estimated latency for the given stream type in milliseconds.
      *
      * DO NOT UNHIDE. The existing approach for doing A/V sync has too many problems. We need
diff --git a/media/java/android/media/AudioMetadata.java b/media/java/android/media/AudioMetadata.java
new file mode 100644
index 0000000..7245aab
--- /dev/null
+++ b/media/java/android/media/AudioMetadata.java
@@ -0,0 +1,406 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Pair;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * AudioMetadata class is used to manage typed key-value pairs for
+ * configuration and capability requests within the Audio Framework.
+ */
+public final class AudioMetadata {
+    /**
+     * Key interface for the map.
+     *
+     * The presence of this {@code Key} interface on an object allows
+     * it to be used to reference metadata in the Audio Framework.
+     *
+     * @param <T> type of value associated with {@code Key}.
+     */
+    // Conceivably metadata keys exposing multiple interfaces
+    // could be eligible to work in multiple framework domains.
+    public interface Key<T> {
+        /**
+         * Returns the internal name of the key.
+         */
+        @NonNull
+        String getName();
+
+        /**
+         * Returns the class type of the associated value.
+         */
+        @NonNull
+        Class<T> getValueClass();
+
+        // TODO: consider adding bool isValid(@NonNull T value)
+
+        /**
+         * Do not allow non-framework apps to create their own keys
+         * by implementing this interface; keep a method hidden.
+         *
+         * @hide
+         */
+        boolean isFromFramework();
+    }
+
+    /**
+     * A read only {@code Map} interface of {@link Key} value pairs.
+     *
+     * Using a {@link Key} interface, look up the corresponding value.
+     */
+    public interface ReadMap {
+        /**
+         * Returns true if the key exists in the map.
+         *
+         * @param key interface for requesting the value.
+         * @param <T> type of value.
+         * @return true if key exists in the Map.
+         */
+        <T> boolean containsKey(@NonNull Key<T> key);
+
+        /**
+         * Returns a copy of the map.
+         *
+         * This is intended for safe conversion between a {@link ReadMap}
+         * interface and a {@link Map} interface.
+         * Currently only simple objects are used for key values which
+         * means a shallow copy is sufficient.
+         *
+         * @return a Map copied from the existing map.
+         */
+        @NonNull
+        Map dup(); // lint checker doesn't like clone().
+
+        /**
+         * Returns the value associated with the key.
+         *
+         * @param key interface for requesting the value.
+         * @param <T> type of value.
+         * @return returns the value of associated with key or null if it doesn't exist.
+         */
+        @Nullable
+        <T> T get(@NonNull Key<T> key);
+
+        /**
+         * Returns a {@code Set} of keys associated with the map.
+         * @hide
+         */
+        @NonNull
+        Set<Key<?>> keySet();
+
+        /**
+         * Returns the number of elements in the map.
+         */
+        int size();
+    }
+
+    /**
+     * A writeable {@link Map} interface of {@link Key} value pairs.
+     * This interface is not guaranteed to be thread-safe
+     * unless the supplier for the {@code Map} states it as thread safe.
+     */
+    // TODO: Create a wrapper like java.util.Collections.synchronizedMap?
+    public interface Map extends ReadMap {
+        /**
+         * Removes the value associated with the key.
+         * @param key interface for storing the value.
+         * @param <T> type of value.
+         * @return the value of the key, null if it doesn't exist.
+         */
+        @Nullable
+        <T> T remove(@NonNull Key<T> key);
+
+        /**
+         * Sets a value for the key.
+         *
+         * @param key interface for storing the value.
+         * @param <T> type of value.
+         * @param value a non-null value of type T.
+         * @return the previous value associated with key or null if it doesn't exist.
+         */
+        // See automatic Kotlin overloading for Java interoperability.
+        // https://kotlinlang.org/docs/reference/java-interop.html#operators
+        // See also Kotlin set for overloaded operator indexing.
+        // https://kotlinlang.org/docs/reference/operator-overloading.html#indexed
+        // Also the Kotlin mutable-list set.
+        // https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-mutable-list/set.html
+        @Nullable
+        <T> T set(@NonNull Key<T> key, @NonNull T value);
+    }
+
+    /**
+     * Creates a {@link Map} suitable for adding keys.
+     * @return an empty {@link Map} instance.
+     */
+    @NonNull
+    public static Map createMap() {
+        return new BaseMap();
+    }
+
+    /**
+     * A container class for AudioMetadata Format keys.
+     *
+     * @see AudioTrack.OnCodecFormatChangedListener
+     */
+    public static class Format {
+        // The key name strings used here must match that of the native framework, but are
+        // allowed to change between API releases.  This due to the Java specification
+        // on what is a compile time constant.
+        //
+        // Key<?> are final variables but not constant variables (per Java spec 4.12.4) because
+        // the keys are not a primitive type nor a String initialized by a constant expression.
+        // Hence (per Java spec 13.1.3), they are not resolved at compile time,
+        // rather are picked up by applications at run time.
+        //
+        // So the contractual API behavior of AudioMetadata.Key<> are different than Strings
+        // initialized by a constant expression (for example MediaFormat.KEY_*).
+
+        // See MediaFormat
+        /**
+         * A key representing the bitrate of the encoded stream used in
+         *
+         * If the stream is variable bitrate, this is the average bitrate of the stream.
+         * The unit is bits per second.
+         *
+         * An Integer value.
+         *
+         * @see MediaFormat#KEY_BIT_RATE
+         */
+        @NonNull public static final Key<Integer> KEY_BIT_RATE =
+                createKey("bitrate", Integer.class);
+
+        /**
+         * A key representing the audio channel mask of the stream.
+         *
+         * An Integer value.
+         *
+         * @see AudioTrack#getChannelConfiguration()
+         * @see MediaFormat#KEY_CHANNEL_MASK
+         */
+        @NonNull public static final Key<Integer> KEY_CHANNEL_MASK =
+                createKey("channel-mask", Integer.class);
+
+
+        /**
+         * A key representing the codec mime string.
+         *
+         * A String value.
+         *
+         * @see MediaFormat#KEY_MIME
+         */
+        @NonNull public static final Key<String> KEY_MIME = createKey("mime", String.class);
+
+        /**
+         * A key representing the audio sample rate in Hz of the stream.
+         *
+         * An Integer value.
+         *
+         * @see AudioFormat#getSampleRate()
+         * @see MediaFormat#KEY_SAMPLE_RATE
+         */
+        @NonNull public static final Key<Integer> KEY_SAMPLE_RATE =
+                createKey("sample-rate", Integer.class);
+
+        // Unique to Audio
+
+        /**
+         * A key representing the bit width of an element of decoded data.
+         *
+         * An Integer value.
+         */
+        @NonNull public static final Key<Integer> KEY_BIT_WIDTH =
+                createKey("bit-width", Integer.class);
+
+        /**
+         * A key representing the presence of Atmos in an E-AC3 stream.
+         *
+         * A Boolean value which is true if Atmos is present in an E-AC3 stream.
+         */
+        @NonNull public static final Key<Boolean> KEY_ATMOS_PRESENT =
+                createKey("atmos-present", Boolean.class);
+
+        /**
+         * A key representing the audio encoding used for the stream.
+         * This is the same encoding used in {@link AudioFormat#getEncoding()}.
+         *
+         * An Integer value.
+         *
+         * @see AudioFormat#getEncoding()
+         */
+        @NonNull public static final Key<Integer> KEY_AUDIO_ENCODING =
+                createKey("audio-encoding", Integer.class);
+
+        private Format() {} // delete constructor
+    }
+
+    /////////////////////////////////////////////////////////////////////////
+    // Hidden methods and functions.
+
+    /**
+     * Returns a Key object with the correct interface for the AudioMetadata.
+     *
+     * An interface with the same name and type will be treated as
+     * identical for the purposes of value storage, even though
+     * other methods or hidden parameters may return different values.
+     *
+     * @param name The name of the key.
+     * @param type The class type of the value represented by the key.
+     * @param <T> The type of value.
+     * @return a new key interface.
+     *
+     * Creating keys is currently only allowed by the Framework.
+     * @hide
+     */
+    @NonNull
+    public static <T> Key<T> createKey(String name, Class<T> type) {
+        // Implementation specific.
+        return new Key<T>() {
+            private final String mName = name;
+            private final Class<T> mType = type;
+
+            @Override
+            @NonNull
+            public String getName() {
+                return mName;
+            }
+
+            @Override
+            @NonNull
+            public Class<T> getValueClass() {
+                return mType;
+            }
+
+            // hidden interface method to prevent user class implements the of Key interface.
+            @Override
+            public boolean isFromFramework() {
+                return true;
+            }
+        };
+    }
+
+    /**
+     * @hide
+     *
+     * AudioMetadata is based on interfaces in order to allow multiple inheritance
+     * and maximum flexibility in implementation.
+     *
+     * Here, we provide a simple implementation of {@link Map} interface;
+     * Note that the Keys are not specific to this Map implementation.
+     *
+     * It is possible to require the keys to be of a certain class
+     * before allowing a set or get operation.
+     */
+    public static class BaseMap implements Map {
+        @Override
+        public <T> boolean containsKey(@NonNull Key<T> key) {
+            Pair<Key<?>, Object> valuePair = mHashMap.get(pairFromKey(key));
+            return valuePair != null;
+        }
+
+        @Override
+        @NonNull
+        public Map dup() {
+            BaseMap map = new BaseMap();
+            map.mHashMap.putAll(this.mHashMap);
+            return map;
+        }
+
+        @Override
+        @Nullable
+        public <T> T get(@NonNull Key<T> key) {
+            Pair<Key<?>, Object> valuePair = mHashMap.get(pairFromKey(key));
+            return (T) getValueFromValuePair(valuePair);
+        }
+
+        @Override
+        @NonNull
+        public Set<Key<?>> keySet() {
+            HashSet<Key<?>> set = new HashSet();
+            for (Pair<Key<?>, Object> pair : mHashMap.values()) {
+                set.add(pair.first);
+            }
+            return set;
+        }
+
+        @Override
+        @Nullable
+        public <T> T remove(@NonNull Key<T> key) {
+            Pair<Key<?>, Object> valuePair = mHashMap.remove(pairFromKey(key));
+            return (T) getValueFromValuePair(valuePair);
+        }
+
+        @Override
+        @Nullable
+        public <T> T set(@NonNull Key<T> key, @NonNull T value) {
+            Objects.requireNonNull(value);
+            Pair<Key<?>, Object> valuePair = mHashMap
+                    .put(pairFromKey(key), new Pair<Key<?>, Object>(key, value));
+            return (T) getValueFromValuePair(valuePair);
+        }
+
+        @Override
+        public int size() {
+            return mHashMap.size();
+        }
+
+        /*
+         * Implementation specific.
+         *
+         * To store the value in the HashMap we need to convert the Key interface
+         * to a hashcode() / equals() compliant Pair.
+         */
+        @NonNull
+        private static <T> Pair<String, Class<?>> pairFromKey(@NonNull Key<T> key) {
+            Objects.requireNonNull(key);
+            return new Pair<String, Class<?>>(key.getName(), key.getValueClass());
+        }
+
+        /*
+         * Implementation specific.
+         *
+         * We store in a Pair (valuePair) the key along with the Object value.
+         * This helper returns the Object value from the value pair.
+         */
+        @Nullable
+        private static Object getValueFromValuePair(@Nullable Pair<Key<?>, Object> valuePair) {
+            if (valuePair == null) {
+                return null;
+            }
+            return valuePair.second;
+        }
+
+        /*
+         * Implementation specific.
+         *
+         * We use a HashMap to back the AudioMetadata BaseMap object.
+         * This is not locked, so concurrent reads are permitted if all threads
+         * have a ReadMap; this is risky with a Map.
+         */
+        private final HashMap<Pair<String, Class<?>>, Pair<Key<?>, Object>> mHashMap =
+                new HashMap();
+    }
+
+    // Delete the constructor as there is nothing to implement here.
+    private AudioMetadata() {}
+}
diff --git a/media/java/android/media/AudioPlaybackCaptureConfiguration.java b/media/java/android/media/AudioPlaybackCaptureConfiguration.java
index 453704e..65f2f17 100644
--- a/media/java/android/media/AudioPlaybackCaptureConfiguration.java
+++ b/media/java/android/media/AudioPlaybackCaptureConfiguration.java
@@ -102,6 +102,12 @@
                                 criterion -> criterion.getIntProp());
     }
 
+    /** @return the userId's passed to {@link Builder#addMatchingUserId(int)}. */
+    public @NonNull int[] getMatchingUserIds() {
+        return getIntPredicates(AudioMixingRule.RULE_MATCH_USERID,
+                criterion -> criterion.getIntProp());
+    }
+
     /** @return the usages passed to {@link Builder#excludeUsage(int)}. */
     @AttributeUsage
     public @NonNull int[] getExcludeUsages() {
@@ -115,6 +121,12 @@
                                 criterion -> criterion.getIntProp());
     }
 
+    /** @return the userId's passed to {@link Builder#excludeUserId(int)}.  */
+    public @NonNull int[] getExcludeUserIds() {
+        return getIntPredicates(AudioMixingRule.RULE_EXCLUDE_USERID,
+                criterion -> criterion.getIntProp());
+    }
+
     private int[] getIntPredicates(int rule,
                                    ToIntFunction<AudioMixMatchCriterion> getPredicate) {
         return mAudioMixingRule.getCriteria().stream()
@@ -153,6 +165,7 @@
         private final MediaProjection mProjection;
         private int mUsageMatchType = MATCH_TYPE_UNSPECIFIED;
         private int mUidMatchType = MATCH_TYPE_UNSPECIFIED;
+        private int mUserIdMatchType = MATCH_TYPE_UNSPECIFIED;
 
         /** @param projection A MediaProjection that supports audio projection. */
         public Builder(@NonNull MediaProjection projection) {
@@ -202,6 +215,23 @@
         }
 
         /**
+         * Only capture audio output by app with the matching {@code userId}.
+         *
+         * <p>If called multiple times, will capture audio output by apps whose userId is any of the
+         * given userId's.
+         *
+         * @throws IllegalStateException if called in conjunction with {@link #excludeUserId(int)}.
+         */
+        public @NonNull Builder addMatchingUserId(int userId) {
+            Preconditions.checkState(
+                    mUserIdMatchType != MATCH_TYPE_EXCLUSIVE,
+                    ERROR_MESSAGE_MISMATCHED_RULES);
+            mAudioMixingRuleBuilder.addMixRule(AudioMixingRule.RULE_MATCH_USERID, userId);
+            mUserIdMatchType = MATCH_TYPE_INCLUSIVE;
+            return this;
+        }
+
+        /**
          * Only capture audio output that does not match the given {@link AudioAttributes}.
          *
          * <p>If called multiple times, will capture audio output that does not match any of the
@@ -238,6 +268,24 @@
         }
 
         /**
+         * Only capture audio output by apps that do not have the matching {@code userId}.
+         *
+         * <p>If called multiple times, will capture audio output by apps whose userId is not any of
+         * the given userId's.
+         *
+         * @throws IllegalStateException if called in conjunction with
+         * {@link #addMatchingUserId(int)}.
+         */
+        public @NonNull Builder excludeUserId(int userId) {
+            Preconditions.checkState(
+                    mUserIdMatchType != MATCH_TYPE_INCLUSIVE,
+                    ERROR_MESSAGE_MISMATCHED_RULES);
+            mAudioMixingRuleBuilder.excludeMixRule(AudioMixingRule.RULE_MATCH_USERID, userId);
+            mUserIdMatchType = MATCH_TYPE_EXCLUSIVE;
+            return this;
+        }
+
+        /**
          * Builds the configuration instance.
          *
          * @throws UnsupportedOperationException if the parameters set are incompatible.
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index fd3523d..4d26b8d 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -315,7 +315,7 @@
      * @hide
      * Class constructor with {@link AudioAttributes} and {@link AudioFormat}.
      * @param attributes a non-null {@link AudioAttributes} instance. Use
-     *     {@link AudioAttributes.Builder#setAudioSource(int)} for configuring the audio
+     *     {@link AudioAttributes.Builder#setCapturePreset(int)} for configuring the audio
      *     source for this instance.
      * @param format a non-null {@link AudioFormat} instance describing the format of the data
      *     that will be recorded through this AudioRecord. See {@link AudioFormat.Builder} for
@@ -754,17 +754,10 @@
                             "Cannot request private capture with source: " + source);
                 }
 
-                int flags = mAttributes.getAllFlags();
-                if (mPrivacySensitive == PRIVACY_SENSITIVE_DISABLED) {
-                    flags &= ~AudioAttributes.FLAG_CAPTURE_PRIVATE;
-                } else if (mPrivacySensitive == PRIVACY_SENSITIVE_ENABLED) {
-                    flags |= AudioAttributes.FLAG_CAPTURE_PRIVATE;
-                }
-                if (flags != mAttributes.getAllFlags()) {
-                    mAttributes = new AudioAttributes.Builder(mAttributes)
-                            .replaceFlags(flags)
-                            .build();
-                }
+                mAttributes = new AudioAttributes.Builder(mAttributes)
+                        .setInternalCapturePreset(source)
+                        .setPrivacySensitive(mPrivacySensitive == PRIVACY_SENSITIVE_ENABLED)
+                        .build();
             }
 
             try {
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index e584add..02cb8aa 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -33,6 +33,7 @@
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 /* IF YOU CHANGE ANY OF THE CONSTANTS IN THIS FILE, DO NOT FORGET
@@ -1077,6 +1078,41 @@
     @UnsupportedAppUsage
     public static native int getDevicesForStream(int stream);
 
+    /**
+     * Do not use directly, see {@link AudioManager#getDevicesForAttributes(AudioAttributes)}
+     * Get the audio devices that would be used for the routing of the given audio attributes.
+     * @param attributes the {@link AudioAttributes} for which the routing is being queried
+     * @return an empty list if there was an issue with the request, a list of audio devices
+     *   otherwise (typically one device, except for duplicated paths).
+     */
+    public static @NonNull ArrayList<AudioDeviceAddress> getDevicesForAttributes(
+            @NonNull AudioAttributes attributes) {
+        Objects.requireNonNull(attributes);
+        final AudioDeviceAddress[] devices = new AudioDeviceAddress[MAX_DEVICE_ROUTING];
+        final int res = getDevicesForAttributes(attributes, devices);
+        final ArrayList<AudioDeviceAddress> routeDevices = new ArrayList<>();
+        if (res != SUCCESS) {
+            Log.e(TAG, "error " + res + " in getDevicesForAttributes for " + attributes);
+            return routeDevices;
+        }
+
+        for (AudioDeviceAddress device : devices) {
+            if (device != null) {
+                routeDevices.add(device);
+            }
+        }
+        return routeDevices;
+    }
+
+    /**
+     * Maximum number of audio devices a track is ever routed to, determines the size of the
+     * array passed to {@link #getDevicesForAttributes(AudioAttributes, AudioDeviceAddress[])}
+     */
+    private static final int MAX_DEVICE_ROUTING = 4;
+
+    private static native int getDevicesForAttributes(@NonNull AudioAttributes aa,
+                                                      @NonNull AudioDeviceAddress[] devices);
+
     /** @hide returns true if master mono is enabled. */
     public static native boolean getMasterMono();
     /** @hide enables or disables the master mono mode. */
@@ -1133,11 +1169,23 @@
     /** see AudioPolicy.removeUidDeviceAffinities() */
     public static native int removeUidDeviceAffinities(int uid);
 
+    /** see AudioPolicy.setUserIdDeviceAffinities() */
+    public static native int setUserIdDeviceAffinities(int userId, @NonNull int[] types,
+            @NonNull String[] addresses);
+
+    /** see AudioPolicy.removeUserIdDeviceAffinities() */
+    public static native int removeUserIdDeviceAffinities(int userId);
+
     public static native int systemReady();
 
     public static native float getStreamVolumeDB(int stream, int index, int device);
 
     /**
+     * Communicate supported system usages to audio policy service.
+     */
+    public static native int setSupportedSystemUsages(int[] systemUsages);
+
+    /**
      * @see AudioManager#setAllowedCapturePolicy()
      */
     public static native int setAllowedCapturePolicy(int uid, int flags);
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 0ced68ef..81275f6 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -188,6 +188,10 @@
 
     // Events:
     // to keep in sync with frameworks/av/include/media/AudioTrack.h
+    // Note: To avoid collisions with other event constants,
+    // do not define an event here that is the same value as
+    // AudioSystem.NATIVE_EVENT_ROUTING_CHANGE.
+
     /**
      * Event id denotes when playback head has reached a previously set marker.
      */
@@ -210,9 +214,144 @@
      * back (after stop is called) for an offloaded track.
      */
     private static final int NATIVE_EVENT_STREAM_END = 7;
+    /**
+     * Event id denotes when the codec format changes.
+     *
+     * Note: Similar to a device routing change (AudioSystem.NATIVE_EVENT_ROUTING_CHANGE),
+     * this event comes from the AudioFlinger Thread / Output Stream management
+     * (not from buffer indications as above).
+     */
+    private static final int NATIVE_EVENT_CODEC_FORMAT_CHANGE = 100;
 
     private final static String TAG = "android.media.AudioTrack";
 
+    /** @hide */
+    @IntDef({
+        ENCAPSULATION_MODE_NONE,
+        ENCAPSULATION_MODE_ELEMENTARY_STREAM,
+        ENCAPSULATION_MODE_HANDLE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface EncapsulationMode {}
+
+    // Important: The ENCAPSULATION_MODE values must be kept in sync with native header files.
+    /**
+     * This mode indicates no metadata encapsulation,
+     * which is the default mode for sending audio data
+     * through {@code AudioTrack}.
+     */
+    public static final int ENCAPSULATION_MODE_NONE = 0;
+    /**
+     * This mode indicates metadata encapsulation with an elementary stream payload.
+     * Both compressed and PCM format is allowed.
+     *
+     * TODO(b/147778408) Link: See the Android developers guide for more information.
+     */
+    public static final int ENCAPSULATION_MODE_ELEMENTARY_STREAM = 1;
+    /**
+     * This mode indicates metadata encapsulation with a handle payload.
+     * The handle is a 64 bit long, provided by the Tuner API.
+     *
+     * TODO(b/147778408) Link: Fill in Tuner API to obtain the handle.
+     */
+    public static final int ENCAPSULATION_MODE_HANDLE = 2;
+
+    /* Enumeration of metadata types permitted for use by
+     * encapsulation mode audio streams.
+     */
+    /** @hide */
+    @IntDef(prefix = { "ENCAPSULATION_METADATA_TYPE_" }, value = {
+        ENCAPSULATION_METADATA_TYPE_NONE, /* reserved */
+        ENCAPSULATION_METADATA_TYPE_FRAMEWORK_TUNER,
+        ENCAPSULATION_METADATA_TYPE_DVB_AD_DESCRIPTOR,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface EncapsulationMetadataType {}
+
+    /**
+     * Reserved do not use.
+     * @hide
+     */
+    public static final int ENCAPSULATION_METADATA_TYPE_NONE = 0; // reserved
+
+    /**
+     * Encapsulation metadata type for framework tuner information.
+     *
+     * TODO(b/147778408) Link: Fill in Tuner API info.
+     */
+    public static final int ENCAPSULATION_METADATA_TYPE_FRAMEWORK_TUNER = 1;
+
+    /**
+     * Encapsulation metadata type for DVB AD descriptor.
+     *
+     * This metadata is formatted per ETSI TS 101 154 Table E.1: AD_descriptor.
+     */
+    public static final int ENCAPSULATION_METADATA_TYPE_DVB_AD_DESCRIPTOR = 2;
+
+    /* Dual Mono handling is used when a stereo audio stream
+     * contains separate audio content on the left and right channels.
+     * Such information about the content of the stream may be found, for example, in
+     * ITU T-REC-J.94-201610 A.6.2.3 Component descriptor.
+     */
+    /** @hide */
+    @IntDef({
+        DUAL_MONO_MODE_OFF,
+        DUAL_MONO_MODE_LR,
+        DUAL_MONO_MODE_LL,
+        DUAL_MONO_MODE_RR,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DualMonoMode {}
+    // Important: The DUAL_MONO_MODE values must be kept in sync with native header files.
+    /**
+     * This mode disables any Dual Mono presentation effect.
+     *
+     */
+    public static final int DUAL_MONO_MODE_OFF = 0;
+
+    /**
+     * This mode indicates that a stereo stream should be presented
+     * with the left and right audio channels blended together
+     * and delivered to both channels.
+     *
+     * Behavior for non-stereo streams is implementation defined.
+     * A suggested guideline is that the left-right stereo symmetric
+     * channels are pairwise blended;
+     * the other channels such as center are left alone.
+     *
+     * The Dual Mono effect occurs before volume scaling.
+     */
+    public static final int DUAL_MONO_MODE_LR = 1;
+
+    /**
+     * This mode indicates that a stereo stream should be presented
+     * with the left audio channel replicated into the right audio channel.
+     *
+     * Behavior for non-stereo streams is implementation defined.
+     * A suggested guideline is that all channels with left-right
+     * stereo symmetry will have the left channel position replicated
+     * into the right channel position.
+     * The center channels (with no left/right symmetry) or unbalanced
+     * channels are left alone.
+     *
+     * The Dual Mono effect occurs before volume scaling.
+     */
+    public static final int DUAL_MONO_MODE_LL = 2;
+
+    /**
+     * This mode indicates that a stereo stream should be presented
+     * with the right audio channel replicated into the left audio channel.
+     *
+     * Behavior for non-stereo streams is implementation defined.
+     * A suggested guideline is that all channels with left-right
+     * stereo symmetry will have the right channel position replicated
+     * into the left channel position.
+     * The center channels (with no left/right symmetry) or unbalanced
+     * channels are left alone.
+     *
+     * The Dual Mono effect occurs before volume scaling.
+     */
+    public static final int DUAL_MONO_MODE_RR = 3;
 
     /** @hide */
     @IntDef({
@@ -592,11 +731,13 @@
     public AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
             int mode, int sessionId)
                     throws IllegalArgumentException {
-        this(attributes, format, bufferSizeInBytes, mode, sessionId, false /*offload*/);
+        this(attributes, format, bufferSizeInBytes, mode, sessionId, false /*offload*/,
+                ENCAPSULATION_MODE_NONE, null /* tunerConfiguration */);
     }
 
     private AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
-            int mode, int sessionId, boolean offload)
+            int mode, int sessionId, boolean offload, int encapsulationMode,
+            @Nullable TunerConfiguration tunerConfiguration)
                     throws IllegalArgumentException {
         super(attributes, AudioPlaybackConfiguration.PLAYER_TYPE_JAM_AUDIOTRACK);
         // mState already == STATE_UNINITIALIZED
@@ -663,7 +804,7 @@
         int initResult = native_setup(new WeakReference<AudioTrack>(this), mAttributes,
                 sampleRate, mChannelMask, mChannelIndexMask, mAudioFormat,
                 mNativeBufferSizeInBytes, mDataLoadMode, session, 0 /*nativeTrackInJavaObj*/,
-                offload);
+                offload, encapsulationMode, tunerConfiguration);
         if (initResult != SUCCESS) {
             loge("Error code "+initResult+" when initializing AudioTrack.");
             return; // with mState == STATE_UNINITIALIZED
@@ -672,6 +813,8 @@
         mSampleRate = sampleRate[0];
         mSessionId = session[0];
 
+        // TODO: consider caching encapsulationMode and tunerConfiguration in the Java object.
+
         if ((mAttributes.getFlags() & AudioAttributes.FLAG_HW_AV_SYNC) != 0) {
             int frameSizeInBytes;
             if (AudioFormat.isEncodingLinearFrames(mAudioFormat)) {
@@ -745,7 +888,9 @@
                     0 /*mDataLoadMode - NA*/,
                     session,
                     nativeTrackInJavaObj,
-                    false /*offload*/);
+                    false /*offload*/,
+                    ENCAPSULATION_MODE_NONE,
+                    null /* tunerConfiguration */);
             if (initResult != SUCCESS) {
                 loge("Error code "+initResult+" when initializing AudioTrack.");
                 return; // with mState == STATE_UNINITIALIZED
@@ -758,6 +903,99 @@
     }
 
     /**
+     * TunerConfiguration is used to convey tuner information
+     * from the android.media.tv.Tuner API to AudioTrack construction.
+     *
+     * Use the Builder to construct the TunerConfiguration object,
+     * which is then used by the {@link AudioTrack.Builder} to create an AudioTrack.
+     */
+    public static class TunerConfiguration {
+        private final int mContentId;
+        private final int mSyncId;
+
+        private TunerConfiguration(int contentId, int syncId) {
+            mContentId = contentId;
+            mSyncId = syncId;
+        }
+
+        /**
+         * Returns the contentId.
+         */
+        public int getContentId() {
+            return mContentId;
+        }
+
+        /**
+         * Returns the syncId.
+         */
+        public int getSyncId() {
+            return mSyncId;
+        }
+
+        /**
+         * Builder class for {@link AudioTrack.TunerConfiguration} objects.
+         */
+        public static class Builder {
+            private int mContentId;
+            private int mSyncId;
+
+            /**
+             * Sets the contentId from the Tuner filter.
+             *
+             * @param contentId selects the audio stream to use.
+             *     See android.media.tv.tuner.filter.Filter#getId().
+             *     This is always a positive number.
+             *     TODO(b/147778408) Link to tuner filter doc when unhidden.
+             * @return the same Builder instance.
+             */
+            public @NonNull Builder setContentId(@IntRange(from = 1) int contentId) {
+                if (contentId < 1) {
+                    throw new IllegalArgumentException(
+                            "contentId " + contentId + " must be positive");
+                }
+                mContentId = contentId;
+                return this;
+            }
+
+            /**
+             * Sets the syncId from the Tuner filter.
+             *
+             * @param syncId selects the clock to use for synchronization
+             *     of audio with other streams such as video.
+             *     See android.media.tv.tuner.Tuner#getAvSyncHwId().
+             *     This is always a positive number.
+             *     TODO(b/147778408) Link to tuner filter doc when unhidden.
+             * @return the same Builder instance.
+             */
+            public @NonNull Builder setSyncId(@IntRange(from = 1) int syncId) {
+                if (syncId < 1) {
+                    throw new IllegalArgumentException("syncId " + syncId + " must be positive");
+                }
+                mSyncId = syncId;
+                return this;
+            }
+
+            /**
+             * Builds a {@link AudioTrack.TunerConfiguration} instance initialized with
+             * the parameters set on this {@code Builder}.
+             *
+             * @return a new successfully initialized {@link AudioTrack.TunerConfiguration}.
+             * @throws UnsupportedOperationException if the parameters set on the
+             *     {@code Builder} are incompatible.
+             */
+            public @NonNull TunerConfiguration build() {
+                if (mContentId < 1 || mSyncId < 1) {
+                    throw new UnsupportedOperationException(
+                            "contentId " + mContentId
+                            + " syncId " + mSyncId
+                            + " must be set");
+                }
+                return new TunerConfiguration(mContentId, mSyncId);
+            }
+        }
+    }
+
+    /**
      * Builder class for {@link AudioTrack} objects.
      * Use this class to configure and create an <code>AudioTrack</code> instance. By setting audio
      * attributes and audio format parameters, you indicate which of those vary from the default
@@ -799,10 +1037,12 @@
         private AudioAttributes mAttributes;
         private AudioFormat mFormat;
         private int mBufferSizeInBytes;
+        private int mEncapsulationMode = ENCAPSULATION_MODE_NONE;
         private int mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE;
         private int mMode = MODE_STREAM;
         private int mPerformanceMode = PERFORMANCE_MODE_NONE;
         private boolean mOffload = false;
+        private TunerConfiguration mTunerConfiguration;
 
         /**
          * Constructs a new Builder with the default values as described above.
@@ -869,6 +1109,34 @@
         }
 
         /**
+         * Sets the encapsulation mode.
+         *
+         * Encapsulation mode allows metadata to be sent together with
+         * the audio data payload in a {@code ByteBuffer}.
+         * The data format is specified in the Android developers site.
+         *
+         * TODO(b/147778408) Link to doc page.
+         *
+         * @param encapsulationMode one of {@link AudioTrack#ENCAPSULATION_MODE_NONE},
+         *        {@link AudioTrack#ENCAPSULATION_MODE_ELEMENTARY_STREAM},
+         *        {@link AudioTrack#ENCAPSULATION_MODE_HANDLE}.
+         * @return the same Builder instance.
+         */
+        public @NonNull Builder setEncapsulationMode(@EncapsulationMode int encapsulationMode) {
+            switch (encapsulationMode) {
+                case ENCAPSULATION_MODE_NONE:
+                case ENCAPSULATION_MODE_ELEMENTARY_STREAM:
+                case ENCAPSULATION_MODE_HANDLE:
+                    mEncapsulationMode = encapsulationMode;
+                    break;
+                default:
+                    throw new IllegalArgumentException(
+                            "Invalid encapsulation mode " + encapsulationMode);
+            }
+            return this;
+        }
+
+        /**
          * Sets the mode under which buffers of audio data are transferred from the
          * {@link AudioTrack} to the framework.
          * @param mode one of {@link AudioTrack#MODE_STREAM}, {@link AudioTrack#MODE_STATIC}.
@@ -949,6 +1217,25 @@
         }
 
         /**
+         * Sets the tuner configuration for the {@code AudioTrack}.
+         *
+         * The {@link AudioTrack.TunerConfiguration} consists of parameters obtained from
+         * the Android TV tuner API which indicate the audio content stream id and the
+         * synchronization id for the {@code AudioTrack}.
+         *
+         * @param tunerConfiguration obtained by {@link AudioTrack.TunerConfiguration.Builder}.
+         * @return the same Builder instance.
+         */
+        public @NonNull Builder setTunerConfiguration(
+                @NonNull TunerConfiguration tunerConfiguration) {
+            if (tunerConfiguration == null) {
+                throw new IllegalArgumentException("tunerConfiguration is null");
+            }
+            mTunerConfiguration = tunerConfiguration;
+            return this;
+        }
+
+        /**
          * Builds an {@link AudioTrack} instance initialized with all the parameters set
          * on this <code>Builder</code>.
          * @return a new successfully initialized {@link AudioTrack} instance.
@@ -1003,6 +1290,8 @@
                 }
             }
 
+            // TODO: Check mEncapsulationMode compatibility with MODE_STATIC, etc?
+
             try {
                 // If the buffer size is not specified in streaming mode,
                 // use a single frame for the buffer size and let the
@@ -1012,7 +1301,8 @@
                             * mFormat.getBytesPerSample(mFormat.getEncoding());
                 }
                 final AudioTrack track = new AudioTrack(
-                        mAttributes, mFormat, mBufferSizeInBytes, mMode, mSessionId, mOffload);
+                        mAttributes, mFormat, mBufferSizeInBytes, mMode, mSessionId, mOffload,
+                        mEncapsulationMode, mTunerConfiguration);
                 if (track.getState() == STATE_UNINITIALIZED) {
                     // release is not necessary
                     throw new UnsupportedOperationException("Cannot create AudioTrack");
@@ -1162,6 +1452,140 @@
                 attributes.getContentType(), attributes.getUsage(), attributes.getFlags());
     }
 
+    /*
+     * The MAX_LEVEL should be exactly representable by an IEEE 754-2008 base32 float.
+     * This means fractions must be divisible by a power of 2. For example,
+     * 10.25f is OK as 0.25 is 1/4, but 10.1f is NOT OK as 1/10 is not expressable by
+     * a finite binary fraction.
+     *
+     * 48.f is the nominal max for API level {@link android os.Build.VERSION_CODES#R}.
+     * We use this to suggest a baseline range for implementation.
+     *
+     * The API contract specification allows increasing this value in a future
+     * API release, but not decreasing this value.
+     */
+    private static final float MAX_AUDIO_DESCRIPTION_MIX_LEVEL = 48.f;
+
+    private static boolean isValidAudioDescriptionMixLevel(float level) {
+        return !(Float.isNaN(level) || level > MAX_AUDIO_DESCRIPTION_MIX_LEVEL);
+    }
+
+    /**
+     * Sets the Audio Description mix level in dB.
+     *
+     * For AudioTracks incorporating a secondary Audio Description stream
+     * (where such contents may be sent through an Encapsulation Mode
+     * {@link #ENCAPSULATION_MODE_ELEMENTARY_STREAM} or {@link #ENCAPSULATION_MODE_HANDLE}
+     * or internally by a HW channel),
+     * the level of mixing of the Audio Description to the Main Audio stream
+     * is controlled by this method.
+     *
+     * Such mixing occurs <strong>prior</strong> to overall volume scaling.
+     *
+     * @param level a floating point value between
+     *     {@code Float.NEGATIVE_INFINITY} to {@code +48.f},
+     *     where {@code Float.NEGATIVE_INFINITY} means the Audio Description is not mixed
+     *     and a level of {@code 0.f} means the Audio Description is mixed without scaling.
+     * @return true on success, false on failure.
+     */
+    public boolean setAudioDescriptionMixLeveldB(
+            @FloatRange(to = 48.f, toInclusive = true) float level) {
+        if (!isValidAudioDescriptionMixLevel(level)) {
+            throw new IllegalArgumentException("level is out of range" + level);
+        }
+        return native_set_audio_description_mix_level_db(level) == SUCCESS;
+    }
+
+    /**
+     * Returns the Audio Description mix level in dB.
+     *
+     * If Audio Description mixing is unavailable from the hardware device,
+     * a value of {@code Float.NEGATIVE_INFINITY} is returned.
+     *
+     * @return the current Audio Description Mix Level in dB.
+     *     A value of {@code Float.NEGATIVE_INFINITY} means
+     *     that the audio description is not mixed or
+     *     the hardware is not available.
+     *     This should reflect the <strong>true</strong> internal device mix level;
+     *     hence the application might receive any floating value
+     *     except {@code Float.NaN}.
+     */
+    public float getAudioDescriptionMixLeveldB() {
+        float[] level = { Float.NEGATIVE_INFINITY };
+        try {
+            final int status = native_get_audio_description_mix_level_db(level);
+            if (status != SUCCESS || Float.isNaN(level[0])) {
+                return Float.NEGATIVE_INFINITY;
+            }
+        } catch (Exception e) {
+            return Float.NEGATIVE_INFINITY;
+        }
+        return level[0];
+    }
+
+    private static boolean isValidDualMonoMode(@DualMonoMode int dualMonoMode) {
+        switch (dualMonoMode) {
+            case DUAL_MONO_MODE_OFF:
+            case DUAL_MONO_MODE_LR:
+            case DUAL_MONO_MODE_LL:
+            case DUAL_MONO_MODE_RR:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Sets the Dual Mono mode presentation on the output device.
+     *
+     * The Dual Mono mode is generally applied to stereo audio streams
+     * where the left and right channels come from separate sources.
+     *
+     * For compressed audio, where the decoding is done in hardware,
+     * Dual Mono presentation needs to be performed
+     * by the hardware output device
+     * as the PCM audio is not available to the framework.
+     *
+     * @param dualMonoMode one of {@link #DUAL_MONO_MODE_OFF},
+     *     {@link #DUAL_MONO_MODE_LR},
+     *     {@link #DUAL_MONO_MODE_LL},
+     *     {@link #DUAL_MONO_MODE_RR}.
+     *
+     * @return true on success, false on failure if the output device
+     *     does not support Dual Mono mode.
+     */
+    public boolean setDualMonoMode(@DualMonoMode int dualMonoMode) {
+        if (!isValidDualMonoMode(dualMonoMode)) {
+            throw new IllegalArgumentException(
+                    "Invalid Dual Mono mode " + dualMonoMode);
+        }
+        return native_set_dual_mono_mode(dualMonoMode) == SUCCESS;
+    }
+
+    /**
+     * Returns the Dual Mono mode presentation setting.
+     *
+     * If no Dual Mono presentation is available for the output device,
+     * then {@link #DUAL_MONO_MODE_OFF} is returned.
+     *
+     * @return one of {@link #DUAL_MONO_MODE_OFF},
+     *     {@link #DUAL_MONO_MODE_LR},
+     *     {@link #DUAL_MONO_MODE_LL},
+     *     {@link #DUAL_MONO_MODE_RR}.
+     */
+    public @DualMonoMode int getDualMonoMode() {
+        int[] dualMonoMode = { DUAL_MONO_MODE_OFF };
+        try {
+            final int status = native_get_dual_mono_mode(dualMonoMode);
+            if (status != SUCCESS || !isValidDualMonoMode(dualMonoMode[0])) {
+                return DUAL_MONO_MODE_OFF;
+            }
+        } catch (Exception e) {
+            return DUAL_MONO_MODE_OFF;
+        }
+        return dualMonoMode[0];
+    }
+
     // mask of all the positional channels supported, however the allowed combinations
     // are further restricted by the matching left/right rule and
     // AudioSystem.OUT_CHANNEL_COUNT_MAX
@@ -3228,6 +3652,67 @@
         }
     }
 
+    //--------------------------------------------------------------------------
+    // Codec notifications
+    //--------------------
+
+    // OnCodecFormatChangedListener notifications uses an instance
+    // of ListenerList to manage its listeners.
+
+    private final Utils.ListenerList<AudioMetadata.ReadMap> mCodecFormatChangedListeners =
+            new Utils.ListenerList();
+
+    /**
+     * Interface definition for a listener for codec format changes.
+     */
+    public interface OnCodecFormatChangedListener {
+        /**
+         * Called when the compressed codec format changes.
+         *
+         * @param audioTrack is the {@code AudioTrack} instance associated with the codec.
+         * @param info is a {@link AudioMetadata.ReadMap} of values which contains decoded format
+         *     changes reported by the codec.  Not all hardware
+         *     codecs indicate codec format changes. Acceptable keys are taken from
+         *     {@code AudioMetadata.Format.KEY_*} range, with the associated value type.
+         */
+        void onCodecFormatChanged(
+                @NonNull AudioTrack audioTrack, @Nullable AudioMetadata.ReadMap info);
+    }
+
+    /**
+     * Adds an {@link OnCodecFormatChangedListener} to receive notifications of
+     * codec format change events on this {@code AudioTrack}.
+     *
+     * @param executor  Specifies the {@link Executor} object to control execution.
+     *
+     * @param listener The {@link OnCodecFormatChangedListener} interface to receive
+     *     notifications of codec events.
+     */
+    public void addOnCodecFormatChangedListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnCodecFormatChangedListener listener) { // NPE checks done by ListenerList.
+        mCodecFormatChangedListeners.add(
+                listener, /* key for removal */
+                executor,
+                (int eventCode, AudioMetadata.ReadMap readMap) -> {
+                    // eventCode is unused by this implementation.
+                    listener.onCodecFormatChanged(this, readMap);
+                }
+        );
+    }
+
+    /**
+     * Removes an {@link OnCodecFormatChangedListener} which has been previously added
+     * to receive codec format change events.
+     *
+     * @param listener The previously added {@link OnCodecFormatChangedListener} interface
+     * to remove.
+     */
+    public void removeOnCodecFormatChangedListener(
+            @NonNull OnCodecFormatChangedListener listener) {
+        mCodecFormatChangedListeners.remove(listener);  // NPE checks done by ListenerList.
+    }
+
     //---------------------------------------------------------
     // Interface definitions
     //--------------------
@@ -3564,6 +4049,12 @@
             return;
         }
 
+        if (what == NATIVE_EVENT_CODEC_FORMAT_CHANGE) {
+            track.mCodecFormatChangedListeners.notify(
+                    0 /* eventCode, unused */, (AudioMetadata.ReadMap) obj);
+            return;
+        }
+
         if (what == NATIVE_EVENT_CAN_WRITE_MORE_DATA
                 || what == NATIVE_EVENT_NEW_IAUDIOTRACK
                 || what == NATIVE_EVENT_STREAM_END) {
@@ -3595,7 +4086,7 @@
             Object /*AudioAttributes*/ attributes,
             int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat,
             int buffSizeInBytes, int mode, int[] sessionId, long nativeAudioTrack,
-            boolean offload);
+            boolean offload, int encapsulationMode, Object tunerConfiguration);
 
     private native final void native_finalize();
 
@@ -3687,6 +4178,11 @@
 
     private native void native_set_delay_padding(int delayInFrames, int paddingInFrames);
 
+    private native int native_set_audio_description_mix_level_db(float level);
+    private native int native_get_audio_description_mix_level_db(float[] level);
+    private native int native_set_dual_mono_mode(int dualMonoMode);
+    private native int native_get_dual_mono_mode(int[] dualMonoMode);
+
     //---------------------------------------------------------
     // Utility methods
     //------------------
diff --git a/media/java/android/media/CamcorderProfile.java b/media/java/android/media/CamcorderProfile.java
index e4bab74..f898931 100644
--- a/media/java/android/media/CamcorderProfile.java
+++ b/media/java/android/media/CamcorderProfile.java
@@ -99,25 +99,21 @@
 
     /**
      * Quality level corresponding to the VGA (640 x 480) resolution.
-     * @hide
      */
     public static final int QUALITY_VGA = 9;
 
     /**
      * Quality level corresponding to 4k-DCI (4096 x 2160) resolution.
-     * @hide
      */
     public static final int QUALITY_4KDCI = 10;
 
     /**
      * Quality level corresponding to QHD (2560 x 1440) resolution
-     * @hide
      */
     public static final int QUALITY_QHD = 11;
 
     /**
      * Quality level corresponding to 2K (2048 x 1080) resolution
-     * @hide
      */
     public static final int QUALITY_2K = 12;
 
@@ -172,25 +168,21 @@
 
     /**
      * Time lapse quality level corresponding to the VGA (640 x 480) resolution.
-     * @hide
      */
     public static final int QUALITY_TIME_LAPSE_VGA = 1009;
 
     /**
      * Time lapse quality level corresponding to the 4k-DCI (4096 x 2160) resolution.
-     * @hide
      */
     public static final int QUALITY_TIME_LAPSE_4KDCI = 1010;
 
     /**
      * Time lapse quality level corresponding to the QHD (2560 x 1440) resolution.
-     * @hide
      */
     public static final int QUALITY_TIME_LAPSE_QHD = 1011;
 
     /**
      * Time lapse quality level corresponding to the 2K (2048 x 1080) resolution.
-     * @hide
      */
     public static final int QUALITY_TIME_LAPSE_2K = 1012;
 
@@ -255,19 +247,16 @@
 
     /**
      * High speed ( >= 100fps) quality level corresponding to the CIF (352 x 288)
-     * @hide
      */
     public static final int QUALITY_HIGH_SPEED_CIF = 2006;
 
     /**
      * High speed ( >= 100fps) quality level corresponding to the VGA (640 x 480)
-     * @hide
      */
     public static final int QUALITY_HIGH_SPEED_VGA = 2007;
 
     /**
      * High speed ( >= 100fps) quality level corresponding to the 4K-DCI (4096 x 2160)
-     * @hide
      */
     public static final int QUALITY_HIGH_SPEED_4KDCI = 2008;
 
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index ad7335e..1f97be5 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -105,6 +105,10 @@
 
     int getLastAudibleStreamVolume(int streamType);
 
+    void setSupportedSystemUsages(in int[] systemUsages);
+
+    int[] getSupportedSystemUsages();
+
     List<AudioProductStrategy> getAudioProductStrategies();
 
     boolean isMicrophoneMuted();
@@ -262,6 +266,10 @@
 
     int removeUidDeviceAffinity(in IAudioPolicyCallback pcb, in int uid);
 
+    int setUserIdDeviceAffinity(in IAudioPolicyCallback pcb, in int userId, in int[] deviceTypes,
+             in String[] deviceAddresses);
+    int removeUserIdDeviceAffinity(in IAudioPolicyCallback pcb, in int userId);
+
     boolean hasHapticChannels(in Uri uri);
 
     boolean isCallScreeningModeSupported();
@@ -272,6 +280,8 @@
 
     AudioDeviceAddress getPreferredDeviceForStrategy(in int strategy);
 
+    List<AudioDeviceAddress> getDevicesForAttributes(in AudioAttributes attributes);
+
     // WARNING: read warning at top of file, new methods that need to be used by native
     // code via IAudioManager.h need to be added to the top section.
 }
diff --git a/media/java/android/media/IMediaRoute2Provider.aidl b/media/java/android/media/IMediaRoute2Provider.aidl
index 02a3816..a25aff6 100644
--- a/media/java/android/media/IMediaRoute2Provider.aidl
+++ b/media/java/android/media/IMediaRoute2Provider.aidl
@@ -18,19 +18,20 @@
 
 import android.content.Intent;
 import android.media.IMediaRoute2ProviderClient;
+import android.os.Bundle;
 
 /**
  * {@hide}
  */
 oneway interface IMediaRoute2Provider {
     void setClient(IMediaRoute2ProviderClient client);
-    void requestCreateSession(String packageName, String routeId,
-            String controlCategory, long requestId);
-    void releaseSession(int sessionId);
+    void requestCreateSession(String packageName, String routeId, long requestId,
+            in @nullable Bundle sessionHints);
+    void releaseSession(String sessionId);
 
-    void selectRoute(int sessionId, String routeId);
-    void deselectRoute(int sessionId, String routeId);
-    void transferToRoute(int sessionId, String routeId);
+    void selectRoute(String sessionId, String routeId);
+    void deselectRoute(String sessionId, String routeId);
+    void transferToRoute(String sessionId, String routeId);
 
     void notifyControlRequestSent(String id, in Intent request);
     void requestSetVolume(String id, int volume);
diff --git a/media/java/android/media/IMediaRoute2ProviderClient.aidl b/media/java/android/media/IMediaRoute2ProviderClient.aidl
index bcb2336..0fccb3a 100644
--- a/media/java/android/media/IMediaRoute2ProviderClient.aidl
+++ b/media/java/android/media/IMediaRoute2ProviderClient.aidl
@@ -18,15 +18,17 @@
 
 import android.media.MediaRoute2ProviderInfo;
 import android.media.MediaRoute2Info;
-import android.media.RouteSessionInfo;
+import android.media.RoutingSessionInfo;
 import android.os.Bundle;
 
 /**
  * @hide
  */
 oneway interface IMediaRoute2ProviderClient {
-    void updateState(in MediaRoute2ProviderInfo providerInfo,
-            in List<RouteSessionInfo> sessionInfos);
-    void notifySessionCreated(in @nullable RouteSessionInfo sessionInfo, long requestId);
-    void notifySessionInfoChanged(in RouteSessionInfo sessionInfo);
+    // TODO: Change it to updateRoutes?
+    void updateState(in MediaRoute2ProviderInfo providerInfo);
+    void notifySessionCreated(in RoutingSessionInfo sessionInfo, long requestId);
+    void notifySessionCreationFailed(long requestId);
+    void notifySessionUpdated(in RoutingSessionInfo sessionInfo);
+    void notifySessionReleased(in RoutingSessionInfo sessionInfo);
 }
diff --git a/media/java/android/media/IMediaRouter2Client.aidl b/media/java/android/media/IMediaRouter2Client.aidl
index f90c7c5..bc7ebea 100644
--- a/media/java/android/media/IMediaRouter2Client.aidl
+++ b/media/java/android/media/IMediaRouter2Client.aidl
@@ -17,7 +17,7 @@
 package android.media;
 
 import android.media.MediaRoute2Info;
-import android.media.RouteSessionInfo;
+import android.media.RoutingSessionInfo;
 import android.os.Bundle;
 
 /**
@@ -28,7 +28,7 @@
     void notifyRoutesAdded(in List<MediaRoute2Info> routes);
     void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
     void notifyRoutesChanged(in List<MediaRoute2Info> routes);
-    void notifySessionCreated(in @nullable RouteSessionInfo sessionInfo, int requestId);
-    void notifySessionInfoChanged(in RouteSessionInfo sessionInfo);
-    void notifySessionReleased(in RouteSessionInfo sessionInfo);
+    void notifySessionCreated(in @nullable RoutingSessionInfo sessionInfo, int requestId);
+    void notifySessionInfoChanged(in RoutingSessionInfo sessionInfo);
+    void notifySessionReleased(in RoutingSessionInfo sessionInfo);
 }
diff --git a/media/java/android/media/IMediaRouter2Manager.aidl b/media/java/android/media/IMediaRouter2Manager.aidl
index b7cb705..ffad659 100644
--- a/media/java/android/media/IMediaRouter2Manager.aidl
+++ b/media/java/android/media/IMediaRouter2Manager.aidl
@@ -18,13 +18,15 @@
 
 import android.media.MediaRoute2ProviderInfo;
 import android.media.MediaRoute2Info;
+import android.media.RoutingSessionInfo;
 
 /**
  * {@hide}
  */
 oneway interface IMediaRouter2Manager {
-    void notifyRouteSelected(String packageName, in MediaRoute2Info route);
-    void notifyControlCategoriesChanged(String packageName, in List<String> categories);
+    void notifySessionCreated(in RoutingSessionInfo sessionInfo);
+    void notifySessionsUpdated();
+    void notifyPreferredFeaturesChanged(String packageName, in List<String> preferredFeatures);
     void notifyRoutesAdded(in List<MediaRoute2Info> routes);
     void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
     void notifyRoutesChanged(in List<MediaRoute2Info> routes);
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index e5b62ff..dac0fba 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -22,7 +22,9 @@
 import android.media.IMediaRouterClient;
 import android.media.MediaRoute2Info;
 import android.media.MediaRouterClientState;
-import android.media.RouteSessionInfo;
+import android.media.RouteDiscoveryPreference;
+import android.media.RoutingSessionInfo;
+import android.os.Bundle;
 
 /**
  * {@hide}
@@ -44,15 +46,17 @@
 
     // Methods for media router 2
     List<MediaRoute2Info> getSystemRoutes();
+    RoutingSessionInfo getSystemSessionInfo();
     void registerClient2(IMediaRouter2Client client, String packageName);
     void unregisterClient2(IMediaRouter2Client client);
-    void sendControlRequest(IMediaRouter2Client client, in MediaRoute2Info route, in Intent request);
+    void sendControlRequest(IMediaRouter2Client client, in MediaRoute2Info route,
+            in Intent request);
     void requestSetVolume2(IMediaRouter2Client client, in MediaRoute2Info route, int volume);
     void requestUpdateVolume2(IMediaRouter2Client client, in MediaRoute2Info route, int direction);
 
-    void requestCreateSession(IMediaRouter2Client client, in MediaRoute2Info route,
-            String controlCategory, int requestId);
-    void setControlCategories(IMediaRouter2Client client, in List<String> categories);
+    void requestCreateSession(IMediaRouter2Client client, in MediaRoute2Info route, int requestId,
+            in @nullable Bundle sessionHints);
+    void setDiscoveryRequest2(IMediaRouter2Client client, in RouteDiscoveryPreference preference);
     void selectRoute(IMediaRouter2Client client, String sessionId, in MediaRoute2Info route);
     void deselectRoute(IMediaRouter2Client client, String sessionId, in MediaRoute2Info route);
     void transferToRoute(IMediaRouter2Client client, String sessionId, in MediaRoute2Info route);
@@ -69,5 +73,13 @@
     void requestUpdateVolume2Manager(IMediaRouter2Manager manager,
             in MediaRoute2Info route, int direction);
 
-    List<RouteSessionInfo> getActiveSessions(IMediaRouter2Manager manager);
+    List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager);
+    void selectClientRoute(IMediaRouter2Manager manager,
+            String sessionId, in MediaRoute2Info route);
+    void deselectClientRoute(IMediaRouter2Manager manager,
+            String sessionId, in MediaRoute2Info route);
+    void transferToClientRoute(IMediaRouter2Manager manager,
+            String sessionId, in MediaRoute2Info route);
+    void releaseClientSession(IMediaRouter2Manager manager, String sessionId);
+
 }
diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java
index a9e33fd..cefc9db 100644
--- a/media/java/android/media/MediaCas.java
+++ b/media/java/android/media/MediaCas.java
@@ -16,13 +16,15 @@
 
 package android.media;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.hardware.cas.V1_0.HidlCasPluginDescriptor;
 import android.hardware.cas.V1_0.ICas;
 import android.hardware.cas.V1_0.IMediaCasService;
-import android.hardware.cas.V1_1.ICasListener;
+import android.hardware.cas.V1_2.ICasListener;
 import android.media.MediaCasException.*;
+import android.media.tv.TvInputService.PriorityHintUseCaseType;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -34,6 +36,8 @@
 import android.util.Log;
 import android.util.Singleton;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 
 /**
@@ -100,28 +104,185 @@
     private static final String TAG = "MediaCas";
     private ICas mICas;
     private android.hardware.cas.V1_1.ICas mICasV11;
+    private android.hardware.cas.V1_2.ICas mICasV12;
     private EventListener mListener;
     private HandlerThread mHandlerThread;
     private EventHandler mEventHandler;
+    private @PriorityHintUseCaseType int mPriorityHint;
+    private String mTvInputServiceSessionId;
+
+    /**
+     * Scrambling modes used to open cas sessions.
+     *
+     * @hide
+     */
+    @IntDef(prefix = "SCRAMBLING_MODE_",
+            value = {SCRAMBLING_MODE_RESERVED, SCRAMBLING_MODE_DVB_CSA1, SCRAMBLING_MODE_DVB_CSA2,
+            SCRAMBLING_MODE_DVB_CSA3_STANDARD,
+            SCRAMBLING_MODE_DVB_CSA3_MINIMAL, SCRAMBLING_MODE_DVB_CSA3_ENHANCE,
+            SCRAMBLING_MODE_DVB_CISSA_V1, SCRAMBLING_MODE_DVB_IDSA,
+            SCRAMBLING_MODE_MULTI2, SCRAMBLING_MODE_AES128, SCRAMBLING_MODE_AES_ECB,
+            SCRAMBLING_MODE_AES_SCTE52, SCRAMBLING_MODE_TDES_ECB, SCRAMBLING_MODE_TDES_SCTE52})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ScramblingMode {}
+
+    /**
+     * DVB (Digital Video Broadcasting) reserved mode.
+     */
+    public static final int SCRAMBLING_MODE_RESERVED =
+            android.hardware.cas.V1_2.ScramblingMode.RESERVED;
+    /**
+     * DVB (Digital Video Broadcasting) Common Scrambling Algorithm (CSA) 1.
+     */
+    public static final int SCRAMBLING_MODE_DVB_CSA1 =
+            android.hardware.cas.V1_2.ScramblingMode.DVB_CSA1;
+    /**
+     * DVB CSA 2.
+     */
+    public static final int SCRAMBLING_MODE_DVB_CSA2 =
+            android.hardware.cas.V1_2.ScramblingMode.DVB_CSA2;
+    /**
+     * DVB CSA 3 in standard mode.
+     */
+    public static final int SCRAMBLING_MODE_DVB_CSA3_STANDARD =
+            android.hardware.cas.V1_2.ScramblingMode.DVB_CSA3_STANDARD;
+    /**
+     * DVB CSA 3 in minimally enhanced mode.
+     */
+    public static final int SCRAMBLING_MODE_DVB_CSA3_MINIMAL =
+            android.hardware.cas.V1_2.ScramblingMode.DVB_CSA3_MINIMAL;
+    /**
+     * DVB CSA 3 in fully enhanced mode.
+     */
+    public static final int SCRAMBLING_MODE_DVB_CSA3_ENHANCE =
+            android.hardware.cas.V1_2.ScramblingMode.DVB_CSA3_ENHANCE;
+    /**
+     * DVB Common IPTV Software-oriented Scrambling Algorithm (CISSA) Version 1.
+     */
+    public static final int SCRAMBLING_MODE_DVB_CISSA_V1 =
+            android.hardware.cas.V1_2.ScramblingMode.DVB_CISSA_V1;
+    /**
+     * ATIS-0800006 IIF Default Scrambling Algorithm (IDSA).
+     */
+    public static final int SCRAMBLING_MODE_DVB_IDSA =
+            android.hardware.cas.V1_2.ScramblingMode.DVB_IDSA;
+    /**
+     * A symmetric key algorithm.
+     */
+    public static final int SCRAMBLING_MODE_MULTI2 =
+            android.hardware.cas.V1_2.ScramblingMode.MULTI2;
+    /**
+     * Advanced Encryption System (AES) 128-bit Encryption mode.
+     */
+    public static final int SCRAMBLING_MODE_AES128 =
+            android.hardware.cas.V1_2.ScramblingMode.AES128;
+    /**
+     * Advanced Encryption System (AES) Electronic Code Book (ECB) mode.
+     */
+    public static final int SCRAMBLING_MODE_AES_ECB =
+            android.hardware.cas.V1_2.ScramblingMode.AES_ECB;
+    /**
+     * Advanced Encryption System (AES) Society of Cable Telecommunications Engineers (SCTE) 52
+     * mode.
+     */
+    public static final int SCRAMBLING_MODE_AES_SCTE52 =
+            android.hardware.cas.V1_2.ScramblingMode.AES_SCTE52;
+    /**
+     * Triple Data Encryption Algorithm (TDES) Electronic Code Book (ECB) mode.
+     */
+    public static final int SCRAMBLING_MODE_TDES_ECB =
+            android.hardware.cas.V1_2.ScramblingMode.TDES_ECB;
+    /**
+     * Triple Data Encryption Algorithm (TDES) Society of Cable Telecommunications Engineers (SCTE)
+     * 52 mode.
+     */
+    public static final int SCRAMBLING_MODE_TDES_SCTE52 =
+            android.hardware.cas.V1_2.ScramblingMode.TDES_SCTE52;
+
+    /**
+     * Usages used to open cas sessions.
+     *
+     * @hide
+     */
+    @IntDef(prefix = "SESSION_USAGE_",
+            value = {SESSION_USAGE_LIVE, SESSION_USAGE_PLAYBACK, SESSION_USAGE_RECORD,
+            SESSION_USAGE_TIMESHIFT})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SessionUsage {}
+    /**
+     * Cas session is used to descramble live streams.
+     */
+    public static final int SESSION_USAGE_LIVE = android.hardware.cas.V1_2.SessionIntent.LIVE;
+    /**
+     * Cas session is used to descramble recoreded streams.
+     */
+    public static final int SESSION_USAGE_PLAYBACK =
+            android.hardware.cas.V1_2.SessionIntent.PLAYBACK;
+    /**
+     * Cas session is used to descramble live streams and encrypt local recorded content
+     */
+    public static final int SESSION_USAGE_RECORD = android.hardware.cas.V1_2.SessionIntent.RECORD;
+    /**
+     * Cas session is used to descramble live streams , encrypt local recorded content and playback
+     * local encrypted content.
+     */
+    public static final int SESSION_USAGE_TIMESHIFT =
+            android.hardware.cas.V1_2.SessionIntent.TIMESHIFT;
+
+    /**
+     * Plugin status events sent from cas system.
+     *
+     * @hide
+     */
+    @IntDef(prefix = "PLUGIN_STATUS_",
+            value = {PLUGIN_STATUS_PHYSICAL_MODULE_CHANGED, PLUGIN_STATUS_SESSION_NUMBER_CHANGED})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PluginStatus {}
+
+    /**
+     * The event to indicate that the status of CAS system is changed by the removal or insertion of
+     * physical CAS modules.
+     */
+    public static final int PLUGIN_STATUS_PHYSICAL_MODULE_CHANGED =
+            android.hardware.cas.V1_2.StatusEvent.PLUGIN_PHYSICAL_MODULE_CHANGED;
+    /**
+     * The event to indicate that the number of CAS system's session is changed.
+     */
+    public static final int PLUGIN_STATUS_SESSION_NUMBER_CHANGED =
+            android.hardware.cas.V1_2.StatusEvent.PLUGIN_SESSION_NUMBER_CHANGED;
 
     private static final Singleton<IMediaCasService> sService = new Singleton<IMediaCasService>() {
         @Override
         protected IMediaCasService create() {
             try {
-                Log.d(TAG, "Tried to get cas@1.1 service");
-                android.hardware.cas.V1_1.IMediaCasService serviceV11 =
-                        android.hardware.cas.V1_1.IMediaCasService.getService(true /*wait*/);
-                if (serviceV11 != null) {
-                    return serviceV11;
+                Log.d(TAG, "Trying to get cas@1.2 service");
+                android.hardware.cas.V1_2.IMediaCasService serviceV12 =
+                        android.hardware.cas.V1_2.IMediaCasService.getService(true /*wait*/);
+                if (serviceV12 != null) {
+                    return serviceV12;
                 }
-            } catch (Exception eV1_1) {
-                try {
-                    Log.d(TAG, "Tried to get cas@1.0 service");
-                    return IMediaCasService.getService(true /*wait*/);
-                } catch (Exception eV1_0) {
-                    Log.d(TAG, "Failed to get cas@1.0 service");
-                }
+            } catch (Exception eV1_2) {
+                Log.d(TAG, "Failed to get cas@1.2 service");
             }
+
+            try {
+                    Log.d(TAG, "Trying to get cas@1.1 service");
+                    android.hardware.cas.V1_1.IMediaCasService serviceV11 =
+                            android.hardware.cas.V1_1.IMediaCasService.getService(true /*wait*/);
+                    if (serviceV11 != null) {
+                        return serviceV11;
+                    }
+            } catch (Exception eV1_1) {
+                Log.d(TAG, "Failed to get cas@1.1 service");
+            }
+
+            try {
+                Log.d(TAG, "Trying to get cas@1.0 service");
+                return IMediaCasService.getService(true /*wait*/);
+            } catch (Exception eV1_0) {
+                Log.d(TAG, "Failed to get cas@1.0 service");
+            }
+
             return null;
         }
     };
@@ -139,6 +300,7 @@
     private void cleanupAndRethrowIllegalState() {
         mICas = null;
         mICasV11 = null;
+        mICasV12 = null;
         throw new IllegalStateException();
     }
 
@@ -146,6 +308,8 @@
 
         private static final int MSG_CAS_EVENT = 0;
         private static final int MSG_CAS_SESSION_EVENT = 1;
+        private static final int MSG_CAS_STATUS_EVENT = 2;
+        private static final int MSG_CAS_RESOURCE_LOST = 3;
         private static final String SESSION_KEY = "sessionId";
         private static final String DATA_KEY = "data";
 
@@ -164,6 +328,10 @@
                 mListener.onSessionEvent(MediaCas.this,
                         createFromSessionId(sessionId), msg.arg1, msg.arg2,
                         bundle.getByteArray(DATA_KEY));
+            } else if (msg.what == MSG_CAS_STATUS_EVENT) {
+                mListener.onPluginStatusUpdate(MediaCas.this, msg.arg1, msg.arg2);
+            } else if (msg.what == MSG_CAS_RESOURCE_LOST) {
+                mListener.onResourceLost(MediaCas.this);
             }
         }
     }
@@ -189,6 +357,12 @@
             msg.setData(bundle);
             mEventHandler.sendMessage(msg);
         }
+        @Override
+        public void onStatusUpdate(byte status, int arg)
+                throws RemoteException {
+            mEventHandler.sendMessage(mEventHandler.obtainMessage(
+                    EventHandler.MSG_CAS_STATUS_EVENT, status, arg));
+        }
     };
     /**
      * Describe a CAS plugin with its CA_system_ID and string name.
@@ -257,7 +431,7 @@
         final ArrayList<Byte> mSessionId;
 
         Session(@NonNull ArrayList<Byte> sessionId) {
-            mSessionId = sessionId;
+            mSessionId = new ArrayList<Byte>(sessionId);
         }
 
         /**
@@ -364,6 +538,19 @@
         }
 
         /**
+         * Get Session Id.
+         *
+         * @return session Id of the session.
+         *
+         * @throws IllegalStateException if the MediaCas instance is not valid.
+         */
+        @NonNull
+        public byte[] getSessionId() {
+            validateInternalStates();
+            return toBytes(mSessionId);
+        }
+
+        /**
          * Close the session.
          *
          * @throws IllegalStateException if the MediaCas instance is not valid.
@@ -445,14 +632,23 @@
     public MediaCas(int CA_system_id) throws UnsupportedCasException {
         try {
             IMediaCasService service = getService();
-            android.hardware.cas.V1_1.IMediaCasService serviceV11 =
+            android.hardware.cas.V1_2.IMediaCasService serviceV12 =
+                    android.hardware.cas.V1_2.IMediaCasService.castFrom(service);
+            if (serviceV12 == null) {
+                android.hardware.cas.V1_1.IMediaCasService serviceV11 =
                     android.hardware.cas.V1_1.IMediaCasService.castFrom(service);
-            if (serviceV11 == null) {
-                Log.d(TAG, "Used cas@1_0 interface to create plugin");
-                mICas = service.createPlugin(CA_system_id, mBinder);
+                if (serviceV11 == null) {
+                    Log.d(TAG, "Used cas@1_0 interface to create plugin");
+                    mICas = service.createPlugin(CA_system_id, mBinder);
+                } else {
+                    Log.d(TAG, "Used cas@1.1 interface to create plugin");
+                    mICas = mICasV11 = serviceV11.createPluginExt(CA_system_id, mBinder);
+                }
             } else {
-                Log.d(TAG, "Used cas@1.1 interface to create plugin");
-                mICas = mICasV11 = serviceV11.createPluginExt(CA_system_id, mBinder);
+                Log.d(TAG, "Used cas@1.2 interface to create plugin");
+                mICas = mICasV11 = mICasV12 =
+                    android.hardware.cas.V1_2.ICas
+                    .castFrom(serviceV12.createPluginExt(CA_system_id, mBinder));
             }
         } catch(Exception e) {
             Log.e(TAG, "Failed to create plugin: " + e);
@@ -465,6 +661,24 @@
         }
     }
 
+    /**
+     * Instantiate a CA system of the specified system id.
+     *
+     * @param casSystemId The system id of the CA system.
+     * @param tvInputServiceSessionId The Id of the session opened in TV Input Service (TIS)
+     *        {@link android.media.tv.TvInputService#onCreateSession(String, String)}
+     * @param priorityHint priority hint from the use case type for new created CAS system.
+     *
+     * @throws UnsupportedCasException if the device does not support the
+     * specified CA system.
+     */
+    public MediaCas(int casSystemId, @Nullable String tvInputServiceSessionId,
+            @PriorityHintUseCaseType int priorityHint)  throws UnsupportedCasException {
+        this(casSystemId);
+        mPriorityHint = priorityHint;
+        mTvInputServiceSessionId = tvInputServiceSessionId;
+    }
+
     IHwBinder getBinder() {
         validateInternalStates();
 
@@ -476,6 +690,7 @@
      * to receives scheme-specific notifications from a MediaCas instance.
      */
     public interface EventListener {
+
         /**
          * Notify the listener of a scheme-specific event from the CA system.
          *
@@ -501,6 +716,27 @@
                 int event, int arg, @Nullable byte[] data) {
             Log.d(TAG, "Received MediaCas Session event");
         }
+
+        /**
+         * Notify the listener that the cas plugin status is updated.
+         *
+         * @param mediaCas the MediaCas object to receive this event.
+         * @param status the plugin status which is updated.
+         * @param arg an integer whose meaning is specific to the status to be updated.
+         */
+        default void onPluginStatusUpdate(@NonNull MediaCas mediaCas, @PluginStatus int status,
+                int arg) {
+            Log.d(TAG, "Received MediaCas Plugin Status event");
+        }
+
+        /**
+         * Notify the listener that the session resources was lost.
+         *
+         * @param mediaCas the MediaCas object to receive this event.
+         */
+        default void onResourceLost(@NonNull MediaCas mediaCas) {
+            Log.d(TAG, "Received MediaCas Resource Reclaim event");
+        }
     }
 
     /**
@@ -563,6 +799,20 @@
             mSession = createFromSessionId(sessionId);
         }
     }
+
+    private class OpenSession_1_2_Callback implements
+            android.hardware.cas.V1_2.ICas.openSession_1_2Callback {
+
+        public Session mSession;
+        public int mStatus;
+
+        @Override
+        public void onValues(int status, ArrayList<Byte> sessionId) {
+            mStatus = status;
+            mSession = createFromSessionId(sessionId);
+        }
+    }
+
     /**
      * Open a session to descramble one or more streams scrambled by the
      * conditional access system.
@@ -588,6 +838,40 @@
     }
 
     /**
+     * Open a session with usage and scrambling information, so that descrambler can be configured
+     * to descramble one or more streams scrambled by the conditional access system.
+     *
+     * @param sessionUsage used for the created session.
+     * @param scramblingMode used for the created session.
+     *
+     * @return session the newly opened session.
+     *
+     * @throws IllegalStateException if the MediaCas instance is not valid.
+     * @throws MediaCasException for CAS-specific errors.
+     * @throws MediaCasStateException for CAS-specific state exceptions.
+     */
+    @Nullable
+    public Session openSession(@SessionUsage int sessionUsage, @ScramblingMode int scramblingMode)
+            throws MediaCasException {
+        validateInternalStates();
+
+        if (mICasV12 == null) {
+            Log.d(TAG, "Open Session with scrambling mode is only supported by cas@1.2+ interface");
+            throw new UnsupportedCasException("Open Session with scrambling mode is not supported");
+        }
+
+        try {
+            OpenSession_1_2_Callback cb = new OpenSession_1_2_Callback();
+            mICasV12.openSession_1_2(sessionUsage, scramblingMode, cb);
+            MediaCasException.throwExceptionIfNeeded(cb.mStatus);
+            return cb.mSession;
+        } catch (RemoteException e) {
+            cleanupAndRethrowIllegalState();
+        }
+        return null;
+    }
+
+    /**
      * Send a received EMM packet to the CA system.
      *
      * @param data byte array of the EMM data.
diff --git a/media/java/android/media/MediaCasException.java b/media/java/android/media/MediaCasException.java
index 35fb104..349e9b3 100644
--- a/media/java/android/media/MediaCasException.java
+++ b/media/java/android/media/MediaCasException.java
@@ -16,7 +16,7 @@
 
 package android.media;
 
-import android.hardware.cas.V1_0.Status;
+import android.hardware.cas.V1_2.Status;
 
 /**
  * Base class for MediaCas exceptions
@@ -85,4 +85,15 @@
             super(detailMessage);
         }
     }
+
+    /**
+     * Exception thrown when an operation on a MediaCas object is attempted
+     * and hardware resources are not sufficient to allocate, due to client's lower priority.
+     */
+    public static final class InsufficientResourceException extends MediaCasException {
+        /** @hide */
+        public InsufficientResourceException(String detailMessage) {
+            super(detailMessage);
+        }
+    }
 }
diff --git a/media/java/android/media/MediaCasStateException.java b/media/java/android/media/MediaCasStateException.java
index 26c5792..8dbc9f4 100644
--- a/media/java/android/media/MediaCasStateException.java
+++ b/media/java/android/media/MediaCasStateException.java
@@ -18,8 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-
-import android.hardware.cas.V1_0.Status;
+import android.hardware.cas.V1_2.Status;
 
 /**
  * Base class for MediaCas runtime exceptions
@@ -48,39 +47,60 @@
 
         String diagnosticInfo = "";
         switch (err) {
-        case Status.ERROR_CAS_UNKNOWN:
-            diagnosticInfo = "General CAS error";
-            break;
-        case Status.ERROR_CAS_NO_LICENSE:
-            diagnosticInfo = "No license";
-            break;
-        case Status.ERROR_CAS_LICENSE_EXPIRED:
-            diagnosticInfo = "License expired";
-            break;
-        case Status.ERROR_CAS_SESSION_NOT_OPENED:
-            diagnosticInfo = "Session not opened";
-            break;
-        case Status.ERROR_CAS_CANNOT_HANDLE:
-            diagnosticInfo = "Unsupported scheme or data format";
-            break;
-        case Status.ERROR_CAS_INVALID_STATE:
-            diagnosticInfo = "Invalid CAS state";
-            break;
-        case Status.ERROR_CAS_INSUFFICIENT_OUTPUT_PROTECTION:
-            diagnosticInfo = "Insufficient output protection";
-            break;
-        case Status.ERROR_CAS_TAMPER_DETECTED:
-            diagnosticInfo = "Tamper detected";
-            break;
-        case Status.ERROR_CAS_DECRYPT_UNIT_NOT_INITIALIZED:
-            diagnosticInfo = "Not initialized";
-            break;
-        case Status.ERROR_CAS_DECRYPT:
-            diagnosticInfo = "Decrypt error";
-            break;
-        default:
-            diagnosticInfo = "Unknown CAS state exception";
-            break;
+            case Status.ERROR_CAS_UNKNOWN:
+                diagnosticInfo = "General CAS error";
+                break;
+            case Status.ERROR_CAS_NO_LICENSE:
+                diagnosticInfo = "No license";
+                break;
+            case Status.ERROR_CAS_LICENSE_EXPIRED:
+                diagnosticInfo = "License expired";
+                break;
+            case Status.ERROR_CAS_SESSION_NOT_OPENED:
+                diagnosticInfo = "Session not opened";
+                break;
+            case Status.ERROR_CAS_CANNOT_HANDLE:
+                diagnosticInfo = "Unsupported scheme or data format";
+                break;
+            case Status.ERROR_CAS_INVALID_STATE:
+                diagnosticInfo = "Invalid CAS state";
+                break;
+            case Status.ERROR_CAS_INSUFFICIENT_OUTPUT_PROTECTION:
+                diagnosticInfo = "Insufficient output protection";
+                break;
+            case Status.ERROR_CAS_TAMPER_DETECTED:
+                diagnosticInfo = "Tamper detected";
+                break;
+            case Status.ERROR_CAS_DECRYPT_UNIT_NOT_INITIALIZED:
+                diagnosticInfo = "Not initialized";
+                break;
+            case Status.ERROR_CAS_DECRYPT:
+                diagnosticInfo = "Decrypt error";
+                break;
+            case Status.ERROR_CAS_NEED_ACTIVATION:
+                diagnosticInfo = "Need Activation";
+                break;
+            case Status.ERROR_CAS_NEED_PAIRING:
+                diagnosticInfo = "Need Pairing";
+                break;
+            case Status.ERROR_CAS_NO_CARD:
+                diagnosticInfo = "No Card";
+                break;
+            case Status.ERROR_CAS_CARD_MUTE:
+                diagnosticInfo = "Card Muted";
+                break;
+            case Status.ERROR_CAS_CARD_INVALID:
+                diagnosticInfo = "Card Invalid";
+                break;
+            case Status.ERROR_CAS_BLACKOUT:
+                diagnosticInfo = "Blackout";
+                break;
+            case Status.ERROR_CAS_REBOOTING:
+                diagnosticInfo = "Rebooting";
+                break;
+            default:
+                diagnosticInfo = "Unknown CAS state exception";
+                break;
         }
         throw new MediaCasStateException(err, msg,
                 String.format("%s (err=%d)", diagnosticInfo, err));
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index f780d40..abc7e0b 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -23,6 +23,7 @@
 import android.graphics.ImageFormat;
 import android.graphics.Rect;
 import android.graphics.SurfaceTexture;
+import android.hardware.HardwareBuffer;
 import android.media.MediaCodecInfo.CodecCapabilities;
 import android.os.Build;
 import android.os.Bundle;
@@ -39,9 +40,13 @@
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.ReadOnlyBufferException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
@@ -1736,7 +1741,25 @@
                 {
                     int index = msg.arg2;
                     synchronized(mBufferLock) {
-                        validateInputByteBuffer(mCachedInputBuffers, index);
+                        switch (mBufferMode) {
+                            case BUFFER_MODE_LEGACY:
+                                validateInputByteBuffer(mCachedInputBuffers, index);
+                                break;
+                            case BUFFER_MODE_BLOCK:
+                                while (mQueueRequests.size() <= index) {
+                                    mQueueRequests.add(null);
+                                }
+                                QueueRequest request = mQueueRequests.get(index);
+                                if (request == null) {
+                                    request = new QueueRequest(mCodec, index);
+                                    mQueueRequests.set(index, request);
+                                }
+                                request.setAccessible(true);
+                                break;
+                            default:
+                                throw new IllegalStateException(
+                                        "Unrecognized buffer mode: " + mBufferMode);
+                        }
                     }
                     mCallback.onInputBufferAvailable(mCodec, index);
                     break;
@@ -1747,7 +1770,26 @@
                     int index = msg.arg2;
                     BufferInfo info = (MediaCodec.BufferInfo) msg.obj;
                     synchronized(mBufferLock) {
-                        validateOutputByteBuffer(mCachedOutputBuffers, index, info);
+                        switch (mBufferMode) {
+                            case BUFFER_MODE_LEGACY:
+                                validateOutputByteBuffer(mCachedOutputBuffers, index, info);
+                                break;
+                            case BUFFER_MODE_BLOCK:
+                                while (mOutputFrames.size() <= index) {
+                                    mOutputFrames.add(null);
+                                }
+                                OutputFrame frame = mOutputFrames.get(index);
+                                if (frame == null) {
+                                    frame = new OutputFrame(index);
+                                    mOutputFrames.set(index, frame);
+                                }
+                                frame.setBufferInfo(info);
+                                frame.setAccessible(true);
+                                break;
+                            default:
+                                throw new IllegalStateException(
+                                        "Unrecognized buffer mode: " + mBufferMode);
+                        }
                     }
                     mCallback.onOutputBufferAvailable(
                             mCodec, index, info);
@@ -1913,8 +1955,33 @@
      */
     public static final int CONFIGURE_FLAG_ENCODE = 1;
 
+    /**
+     * If this codec is to be used with {@link LinearBlock} and/or {@link
+     * GraphicBlock}, pass this flag.
+     * <p>
+     * When this flag is set, the following APIs throw IllegalStateException.
+     * <ul>
+     * <li>{@link #getInputBuffer}
+     * <li>{@link #getInputImage}
+     * <li>{@link #getInputBuffers}
+     * <li>{@link #getOutputBuffer}
+     * <li>{@link #getOutputImage}
+     * <li>{@link #getOutputBuffers}
+     * <li>{@link #queueInputBuffer}
+     * <li>{@link #queueSecureInputBuffer}
+     * <li>{@link #dequeueInputBuffer}
+     * <li>{@link #dequeueOutputBuffer}
+     * </ul>
+     */
+    public static final int CONFIGURE_FLAG_USE_BLOCK_MODEL = 2;
+
     /** @hide */
-    @IntDef(flag = true, value = { CONFIGURE_FLAG_ENCODE })
+    @IntDef(
+        flag = true,
+        value = {
+            CONFIGURE_FLAG_ENCODE,
+            CONFIGURE_FLAG_USE_BLOCK_MODEL,
+    })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ConfigureFlag {}
 
@@ -1984,6 +2051,11 @@
                 descrambler != null ? descrambler.getBinder() : null, flags);
     }
 
+    private static final int BUFFER_MODE_INVALID = -1;
+    private static final int BUFFER_MODE_LEGACY = 0;
+    private static final int BUFFER_MODE_BLOCK = 1;
+    private int mBufferMode = BUFFER_MODE_INVALID;
+
     private void configure(
             @Nullable MediaFormat format, @Nullable Surface surface,
             @Nullable MediaCrypto crypto, @Nullable IHwBinder descramblerBinder,
@@ -2022,6 +2094,13 @@
 
         mHasSurface = surface != null;
         mCrypto = crypto;
+        synchronized (mBufferLock) {
+            if ((flags & CONFIGURE_FLAG_USE_BLOCK_MODEL) != 0) {
+                mBufferMode = BUFFER_MODE_BLOCK;
+            } else {
+                mBufferMode = BUFFER_MODE_LEGACY;
+            }
+        }
 
         native_configure(keys, values, surface, crypto, descramblerBinder, flags);
     }
@@ -2446,6 +2525,9 @@
             int offset, int size, long presentationTimeUs, int flags)
         throws CryptoException {
         synchronized(mBufferLock) {
+            if (mBufferMode != BUFFER_MODE_LEGACY) {
+                throw new IllegalStateException();
+            }
             invalidateByteBuffer(mCachedInputBuffers, index);
             mDequeuedInputBuffers.remove(index);
         }
@@ -2695,6 +2777,9 @@
             long presentationTimeUs,
             int flags) throws CryptoException {
         synchronized(mBufferLock) {
+            if (mBufferMode != BUFFER_MODE_LEGACY) {
+                throw new IllegalStateException();
+            }
             invalidateByteBuffer(mCachedInputBuffers, index);
             mDequeuedInputBuffers.remove(index);
         }
@@ -2726,6 +2811,11 @@
      * @throws MediaCodec.CodecException upon codec error.
      */
     public final int dequeueInputBuffer(long timeoutUs) {
+        synchronized (mBufferLock) {
+            if (mBufferMode != BUFFER_MODE_LEGACY) {
+                throw new IllegalStateException();
+            }
+        }
         int res = native_dequeueInputBuffer(timeoutUs);
         if (res >= 0) {
             synchronized(mBufferLock) {
@@ -2738,6 +2828,654 @@
     private native final int native_dequeueInputBuffer(long timeoutUs);
 
     /**
+     * Section of memory that represents a linear block. Applications may
+     * acquire a block via {@link LinearBlock#obtain} and queue all or part
+     * of the block as an input buffer to a codec, or get a block allocated by
+     * codec as an output buffer from {@link OutputFrame}.
+     *
+     * {@see QueueRequest#setLinearBlock}
+     * {@see QueueRequest#setEncryptedLinearBlock}
+     * {@see OutputFrame#getLinearBlock}
+     */
+    public static final class LinearBlock {
+        // No public constructors.
+        private LinearBlock() {}
+
+        /**
+         * Returns true if the buffer is mappable.
+         * @throws IllegalStateException if invalid
+         */
+        public boolean isMappable() {
+            synchronized (mLock) {
+                if (!mValid) {
+                    throw new IllegalStateException();
+                }
+                return mMappable;
+            }
+        }
+
+        /**
+         * Map the memory and return the mapped region.
+         * <p>
+         * The returned memory region becomes inaccessible after
+         * {@link #recycle}, or the buffer is queued to the codecs and not
+         * returned to the client yet.
+         *
+         * @return mapped memory region as {@link ByteBuffer} object
+         * @throws IllegalStateException if not mappable or invalid
+         */
+        public @NonNull ByteBuffer map() {
+            synchronized (mLock) {
+                if (!mValid) {
+                    throw new IllegalStateException();
+                }
+                if (!mMappable) {
+                    throw new IllegalStateException();
+                }
+                if (mMapped == null) {
+                    mMapped = native_map();
+                }
+                return mMapped;
+            }
+        }
+
+        private native ByteBuffer native_map();
+
+        /**
+         * Mark this block as ready to be recycled by the framework once it is
+         * no longer in use. All operations to this object after
+         * this call will cause exceptions, as well as attempt to access the
+         * previously mapped memory region. Caller should clear all references
+         * to this object after this call.
+         * <p>
+         * To avoid excessive memory consumption, it is recommended that callers
+         * recycle buffers as soon as they no longer need the buffers
+         *
+         * @throws IllegalStateException if invalid
+         */
+        public void recycle() {
+            synchronized (mLock) {
+                if (!mValid) {
+                    throw new IllegalStateException();
+                }
+                if (mMapped != null) {
+                    mMapped.setAccessible(false);
+                    mMapped = null;
+                }
+                native_recycle();
+                mValid = false;
+                mNativeContext = 0;
+            }
+            sPool.offer(this);
+        }
+
+        private native void native_recycle();
+
+        private native void native_obtain(int capacity, String[] codecNames);
+
+        @Override
+        protected void finalize() {
+            native_recycle();
+        }
+
+        /**
+         * Returns true if it is possible to allocate a linear block that can be
+         * passed to all listed codecs as input buffers without copying the
+         * content.
+         * <p>
+         * Note that even if this function returns true, {@link #obtain} may
+         * still throw due to invalid arguments or allocation failure.
+         *
+         * @param codecNames  list of codecs that the client wants to use a
+         *                    linear block without copying. Null entries are
+         *                    ignored.
+         */
+        public static boolean isCodecCopyFreeCompatible(@NonNull String[] codecNames) {
+            return native_checkCompatible(codecNames);
+        }
+
+        private static native boolean native_checkCompatible(@NonNull String[] codecNames);
+
+        /**
+         * Obtain a linear block object no smaller than {@code capacity}.
+         * If {@link #isCodecCopyFreeCompatible} with the same
+         * {@code codecNames} returned true, the returned
+         * {@link LinearBlock} object can be queued to the listed codecs without
+         * copying. The returned {@link LinearBlock} object is always
+         * read/write mappable.
+         *
+         * @param capacity requested capacity of the linear block in bytes
+         * @param codecNames  list of codecs that the client wants to use this
+         *                    linear block without copying. Null entries are
+         *                    ignored.
+         * @return  a linear block object.
+         * @throws IllegalArgumentException if the capacity is invalid or
+         *                                  codecNames contains invalid name
+         * @throws IOException if an error occurred while allocating a buffer
+         */
+        public static @Nullable LinearBlock obtain(
+                int capacity, @NonNull String[] codecNames) {
+            LinearBlock buffer = sPool.poll();
+            if (buffer == null) {
+                buffer = new LinearBlock();
+            }
+            synchronized (buffer.mLock) {
+                buffer.native_obtain(capacity, codecNames);
+            }
+            return buffer;
+        }
+
+        // Called from native
+        private void setInternalStateLocked(long context, boolean isMappable) {
+            mNativeContext = context;
+            mMappable = isMappable;
+            mValid = (context != 0);
+        }
+
+        private static final BlockingQueue<LinearBlock> sPool =
+                new LinkedBlockingQueue<>();
+
+        private final Object mLock = new Object();
+        private boolean mValid = false;
+        private boolean mMappable = false;
+        private ByteBuffer mMapped = null;
+        private long mNativeContext = 0;
+    }
+
+    /**
+     * Section of memory that represents a graphic block. Applications may
+     * acquire a block via {@link GraphicBlock#obtain} and queue
+     * the block as an input buffer to a codec, or get a block allocated by
+     * codec as an output buffer from {@link OutputFrame}.
+     *
+     * {@see QueueRequest#setGraphicBlock}
+     * {@see OutputFrame#getGraphicBlock}
+     */
+    public static final class GraphicBlock {
+        // No public constructors.
+        private GraphicBlock() {}
+
+        /**
+         * Returns true if the buffer is mappable.
+         * @throws IllegalStateException if invalid
+         */
+        public boolean isMappable() {
+            synchronized (mLock) {
+                if (!mValid) {
+                    throw new IllegalStateException();
+                }
+                return mMappable;
+            }
+        }
+
+        /**
+         * Map the memory and return the mapped region.
+         * <p>
+         * Calling {@link #recycle} or
+         * {@link QueueRequest#setGraphicBlock} causes the returned
+         * {@link Image} object to be closed, if not already.
+         *
+         * @return mapped memory region as {@link Image} object
+         * @throws IllegalStateException if not mappable or invalid
+         */
+        public @NonNull Image map() {
+            synchronized (mLock) {
+                if (!mValid) {
+                    throw new IllegalStateException();
+                }
+                if (!mMappable) {
+                    throw new IllegalStateException();
+                }
+                if (mMapped == null) {
+                    mMapped = native_map();
+                }
+                return mMapped;
+            }
+        }
+
+        private native Image native_map();
+
+        /**
+         * Mark this block as ready to be recycled by the framework once it is
+         * no longer in use. All operations to this object after
+         * this call will cause exceptions, as well as attempt to access the
+         * previously mapped memory region. Caller should clear all references
+         * to this object after this call.
+         * <p>
+         * To avoid excessive memory consumption, it is recommended that callers
+         * recycle buffers as soon as they no longer need the buffers.
+         *
+         * @throws IllegalStateException if invalid
+         */
+        public void recycle() {
+            synchronized (mLock) {
+                if (!mValid) {
+                    throw new IllegalStateException();
+                }
+                if (mMapped != null) {
+                    mMapped.close();
+                    mMapped = null;
+                }
+                native_recycle();
+                mValid = false;
+                mNativeContext = 0;
+            }
+            sPool.offer(this);
+        }
+
+        private native void native_recycle();
+
+        /**
+         * Returns true if it is possible to allocate a graphic block that
+         * can be passed to all listed codecs as an input buffer without
+         * copying.
+         * <p>
+         * Note that even if this function returns true, {@link #obtain}
+         * may still throw due to invalid arguments or allocation failure.
+         * In addition, choosing a format that is not natively supported by the
+         * codec may cause color conversion.
+         *
+         * @param codecNames  list of codecs that the client wants to use a
+         *                    graphic block without copying. Null entries are
+         *                    ignored.
+         */
+        public static boolean isCodecCopyFreeCompatible(@NonNull String[] codecNames) {
+            return native_checkCompatible(codecNames);
+        }
+
+        private static native boolean native_checkCompatible(@NonNull String[] codecNames);
+
+        // Called from native
+        private void setInternalStateLocked(long context, boolean isMappable) {
+            mNativeContext = context;
+            mMappable = isMappable;
+            mValid = (context != 0);
+        }
+
+        private static final BlockingQueue<GraphicBlock> sPool =
+                new LinkedBlockingQueue<>();
+
+        /**
+         * Obtain a graphic block object of dimension
+         * {@code width}x{@code height}.
+         * If {@link #isCodecCopyFreeCompatible} with the same
+         * {@code codecNames} returned true, the returned
+         * {@link GraphicBlock} object can be queued to the listed codecs
+         * without copying. The returned {@link GraphicBlock} object is always
+         * read/write mappable.
+         *
+         * @param width requested width of the graphic block
+         * @param height requested height of the graphic block
+         * @param format the format of pixels. One of the {@code COLOR_Format}
+         *               values from {@link MediaCodecInfo.CodecCapabilities}.
+         * @param usage the usage of the buffer. @HardwareBuffer.Usage
+         * @param codecNames  list of codecs that the client wants to use this
+         *                    graphic block without copying. Null entries are
+         *                    ignored.
+         * @return  a graphic block object.
+         * @throws IllegalArgumentException if the parameters are invalid or
+         *     not supported
+         * @throws IOException if an error occurred while allocating a buffer
+         */
+        public static @NonNull GraphicBlock obtain(
+                int width,
+                int height,
+                int format,
+                @HardwareBuffer.Usage long usage,
+                @NonNull String[] codecNames) {
+            GraphicBlock buffer = sPool.poll();
+            if (buffer == null) {
+                buffer = new GraphicBlock();
+            }
+            if (width < 0 || height < 0) {
+                throw new IllegalArgumentException();
+            }
+            synchronized (buffer.mLock) {
+                buffer.native_obtain(width, height, format, usage, codecNames);
+            }
+            return buffer;
+        }
+
+        private native void native_obtain(
+                int width,
+                int height,
+                int format,
+                @HardwareBuffer.Usage long usage,
+                @NonNull String[] codecNames);
+
+        @Override
+        protected void finalize() {
+            native_recycle();
+        }
+
+        private final Object mLock = new Object();
+        private boolean mValid = false;
+        private boolean mMappable = false;
+        private Image mMapped = null;
+        private long mNativeContext = 0;
+    }
+
+    /**
+     * Builder-like class for queue requests. Use this class to prepare a
+     * queue request and send it.
+     */
+    public final class QueueRequest {
+        // No public constructor
+        private QueueRequest(@NonNull MediaCodec codec, int index) {
+            mCodec = codec;
+            mIndex = index;
+        }
+
+        /**
+         * Set a linear block to this queue request. Exactly one buffer must be
+         * set for a queue request before calling {@link #queue}. It is possible
+         * to use the same {@link LinearBlock} object for multiple queue
+         * requests. The behavior is undefined if the range of the buffer
+         * overlaps for multiple requests, or the application writes into the
+         * region being processed by the codec.
+         *
+         * @param block The linear block object
+         * @param offset The byte offset into the input buffer at which the data starts.
+         * @param size The number of bytes of valid input data.
+         * @param presentationTimeUs The presentation timestamp in microseconds for this
+         *                           buffer. This is normally the media time at which this
+         *                           buffer should be presented (rendered). When using an output
+         *                           surface, this will be propagated as the {@link
+         *                           SurfaceTexture#getTimestamp timestamp} for the frame (after
+         *                           conversion to nanoseconds).
+         * @param flags A bitmask of flags
+         *              {@link #BUFFER_FLAG_CODEC_CONFIG} and {@link #BUFFER_FLAG_END_OF_STREAM}.
+         *              While not prohibited, most codecs do not use the
+         *              {@link #BUFFER_FLAG_KEY_FRAME} flag for input buffers.
+         * @return this object
+         * @throws IllegalStateException if a buffer is already set
+         */
+        public @NonNull QueueRequest setLinearBlock(
+                @NonNull LinearBlock block,
+                int offset,
+                int size,
+                long presentationTimeUs,
+                @BufferFlag int flags) {
+            if (!isAccessible()) {
+                throw new IllegalStateException();
+            }
+            if (mLinearBlock != null || mGraphicBlock != null) {
+                throw new IllegalStateException();
+            }
+            mLinearBlock = block;
+            mOffset = offset;
+            mSize = size;
+            mPresentationTimeUs = presentationTimeUs;
+            mFlags = flags;
+            return this;
+        }
+
+        /**
+         * Set an encrypted linear block to this queue request. Exactly one
+         * buffer must be set for a queue request before calling {@link #queue}.
+         *
+         * @param block The linear block object
+         * @param offset The byte offset into the input buffer at which the data starts.
+         * @param presentationTimeUs The presentation timestamp in microseconds for this
+         *                           buffer. This is normally the media time at which this
+         *                           buffer should be presented (rendered). When using an output
+         *                           surface, this will be propagated as the {@link
+         *                           SurfaceTexture#getTimestamp timestamp} for the frame (after
+         *                           conversion to nanoseconds).
+         * @param cryptoInfo Metadata describing the structure of the encrypted input sample.
+         * @param flags A bitmask of flags
+         *              {@link #BUFFER_FLAG_CODEC_CONFIG} and {@link #BUFFER_FLAG_END_OF_STREAM}.
+         *              While not prohibited, most codecs do not use the
+         *              {@link #BUFFER_FLAG_KEY_FRAME} flag for input buffers.
+         * @return this object
+         * @throws IllegalStateException if a buffer is already set
+         */
+        public @NonNull QueueRequest setEncryptedLinearBlock(
+                @NonNull LinearBlock block,
+                int offset,
+                @NonNull MediaCodec.CryptoInfo cryptoInfo,
+                long presentationTimeUs,
+                @BufferFlag int flags) {
+            if (!isAccessible()) {
+                throw new IllegalStateException();
+            }
+            if (mLinearBlock != null || mGraphicBlock != null) {
+                throw new IllegalStateException();
+            }
+            mLinearBlock = block;
+            mOffset = offset;
+            mCryptoInfo = cryptoInfo;
+            mPresentationTimeUs = presentationTimeUs;
+            mFlags = flags;
+            return this;
+        }
+
+        /**
+         * Set a graphic block to this queue request. Exactly one buffer must
+         * be set for a queue request before calling {@link #queue}.
+         *
+         * @param block The graphic block object
+         * @param presentationTimeUs The presentation timestamp in microseconds for this
+         *                           buffer. This is normally the media time at which this
+         *                           buffer should be presented (rendered). When using an output
+         *                           surface, this will be propagated as the {@link
+         *                           SurfaceTexture#getTimestamp timestamp} for the frame (after
+         *                           conversion to nanoseconds).
+         * @param flags A bitmask of flags
+         *              {@link #BUFFER_FLAG_CODEC_CONFIG} and {@link #BUFFER_FLAG_END_OF_STREAM}.
+         *              While not prohibited, most codecs do not use the
+         *              {@link #BUFFER_FLAG_KEY_FRAME} flag for input buffers.
+         * @return this object
+         * @throws IllegalStateException if a buffer is already set
+         */
+        public @NonNull QueueRequest setGraphicBlock(
+                @NonNull GraphicBlock block,
+                long presentationTimeUs,
+                @BufferFlag int flags) {
+            if (!isAccessible()) {
+                throw new IllegalStateException();
+            }
+            if (mLinearBlock != null || mGraphicBlock != null) {
+                throw new IllegalStateException();
+            }
+            mGraphicBlock = block;
+            mPresentationTimeUs = presentationTimeUs;
+            mFlags = flags;
+            return this;
+        }
+
+        /**
+         * Add a integer parameter. See {@link MediaFormat} for the list of
+         * supported tunings. If there was {@link MediaCodec#setParameters}
+         * call with the same key which is not processed by the codec yet, the
+         * value set from this method will override the unprocessed value.
+         */
+        public @NonNull QueueRequest setIntegerParameter(
+                @NonNull String key, int value) {
+            if (!isAccessible()) {
+                throw new IllegalStateException();
+            }
+            mTuningKeys.add(key);
+            mTuningValues.add(Integer.valueOf(value));
+            return this;
+        }
+
+        /**
+         * Add a long parameter. See {@link MediaFormat} for the list of
+         * supported tunings. If there was {@link MediaCodec#setParameters}
+         * call with the same key which is not processed by the codec yet, the
+         * value set from this method will override the unprocessed value.
+         */
+        public @NonNull QueueRequest setLongParameter(
+                @NonNull String key, long value) {
+            if (!isAccessible()) {
+                throw new IllegalStateException();
+            }
+            mTuningKeys.add(key);
+            mTuningValues.add(Long.valueOf(value));
+            return this;
+        }
+
+        /**
+         * Add a float parameter. See {@link MediaFormat} for the list of
+         * supported tunings. If there was {@link MediaCodec#setParameters}
+         * call with the same key which is not processed by the codec yet, the
+         * value set from this method will override the unprocessed value.
+         */
+        public @NonNull QueueRequest setFloatParameter(
+                @NonNull String key, float value) {
+            if (!isAccessible()) {
+                throw new IllegalStateException();
+            }
+            mTuningKeys.add(key);
+            mTuningValues.add(Float.valueOf(value));
+            return this;
+        }
+
+        /**
+         * Add a {@link ByteBuffer} parameter. See {@link MediaFormat} for the list of
+         * supported tunings. If there was {@link MediaCodec#setParameters}
+         * call with the same key which is not processed by the codec yet, the
+         * value set from this method will override the unprocessed value.
+         */
+        public @NonNull QueueRequest setByteBufferParameter(
+                @NonNull String key, @NonNull ByteBuffer value) {
+            if (!isAccessible()) {
+                throw new IllegalStateException();
+            }
+            mTuningKeys.add(key);
+            mTuningValues.add(value);
+            return this;
+        }
+
+        /**
+         * Add a string parameter. See {@link MediaFormat} for the list of
+         * supported tunings. If there was {@link MediaCodec#setParameters}
+         * call with the same key which is not processed by the codec yet, the
+         * value set from this method will override the unprocessed value.
+         */
+        public @NonNull QueueRequest setStringParameter(
+                @NonNull String key, @NonNull String value) {
+            if (!isAccessible()) {
+                throw new IllegalStateException();
+            }
+            mTuningKeys.add(key);
+            mTuningValues.add(value);
+            return this;
+        }
+
+        /**
+         * Finish building a queue request and queue the buffers with tunings.
+         */
+        public void queue() {
+            if (!isAccessible()) {
+                throw new IllegalStateException();
+            }
+            if (mLinearBlock == null && mGraphicBlock == null) {
+                throw new IllegalStateException();
+            }
+            setAccessible(false);
+            if (mLinearBlock != null) {
+                mCodec.native_queueLinearBlock(
+                        mIndex, mLinearBlock, mOffset, mSize, mCryptoInfo,
+                        mPresentationTimeUs, mFlags,
+                        mTuningKeys, mTuningValues);
+            } else if (mGraphicBlock != null) {
+                mCodec.native_queueGraphicBlock(
+                        mIndex, mGraphicBlock, mPresentationTimeUs, mFlags,
+                        mTuningKeys, mTuningValues);
+            }
+            clear();
+        }
+
+        @NonNull QueueRequest clear() {
+            mLinearBlock = null;
+            mOffset = 0;
+            mSize = 0;
+            mCryptoInfo = null;
+            mGraphicBlock = null;
+            mPresentationTimeUs = 0;
+            mFlags = 0;
+            mTuningKeys.clear();
+            mTuningValues.clear();
+            return this;
+        }
+
+        boolean isAccessible() {
+            return mAccessible;
+        }
+
+        @NonNull QueueRequest setAccessible(boolean accessible) {
+            mAccessible = accessible;
+            return this;
+        }
+
+        private final MediaCodec mCodec;
+        private final int mIndex;
+        private LinearBlock mLinearBlock = null;
+        private int mOffset = 0;
+        private int mSize = 0;
+        private MediaCodec.CryptoInfo mCryptoInfo = null;
+        private GraphicBlock mGraphicBlock = null;
+        private long mPresentationTimeUs = 0;
+        private @BufferFlag int mFlags = 0;
+        private final ArrayList<String> mTuningKeys = new ArrayList<>();
+        private final ArrayList<Object> mTuningValues = new ArrayList<>();
+
+        private boolean mAccessible = false;
+    }
+
+    private native void native_queueLinearBlock(
+            int index,
+            @NonNull LinearBlock block,
+            int offset,
+            int size,
+            @Nullable CryptoInfo cryptoInfo,
+            long presentationTimeUs,
+            int flags,
+            @NonNull ArrayList<String> keys,
+            @NonNull ArrayList<Object> values);
+
+    private native void native_queueGraphicBlock(
+            int index,
+            @NonNull GraphicBlock block,
+            long presentationTimeUs,
+            int flags,
+            @NonNull ArrayList<String> keys,
+            @NonNull ArrayList<Object> values);
+
+    private final ArrayList<QueueRequest> mQueueRequests = new ArrayList<>();
+
+    /**
+     * Return a clear {@link QueueRequest} object for an input slot index.
+     *
+     * @param index input slot index from
+     *              {@link Callback#onInputBufferAvailable}
+     * @return queue request object
+     * @throws IllegalStateException if not using block model
+     * @throws IllegalArgumentException if the input slot is not available or
+     *                                  the index is out of range
+     */
+    public @NonNull QueueRequest getQueueRequest(int index) {
+        synchronized (mBufferLock) {
+            if (mBufferMode != BUFFER_MODE_BLOCK) {
+                throw new IllegalStateException();
+            }
+            if (index < 0 || index >= mQueueRequests.size()) {
+                throw new IllegalArgumentException();
+            }
+            QueueRequest request = mQueueRequests.get(index);
+            if (request == null) {
+                throw new IllegalArgumentException();
+            }
+            if (!request.isAccessible()) {
+                throw new IllegalArgumentException();
+            }
+            return request.clear();
+        }
+    }
+
+    /**
      * If a non-negative timeout had been specified in the call
      * to {@link #dequeueOutputBuffer}, indicates that the call timed out.
      */
@@ -2789,8 +3527,13 @@
     @OutputBufferInfo
     public final int dequeueOutputBuffer(
             @NonNull BufferInfo info, long timeoutUs) {
+        synchronized (mBufferLock) {
+            if (mBufferMode != BUFFER_MODE_LEGACY) {
+                throw new IllegalStateException();
+            }
+        }
         int res = native_dequeueOutputBuffer(info, timeoutUs);
-        synchronized(mBufferLock) {
+        synchronized (mBufferLock) {
             if (res == INFO_OUTPUT_BUFFERS_CHANGED) {
                 cacheBuffers(false /* input */);
             } else if (res >= 0) {
@@ -2826,15 +3569,7 @@
      * @throws MediaCodec.CodecException upon codec error.
      */
     public final void releaseOutputBuffer(int index, boolean render) {
-        BufferInfo info = null;
-        synchronized(mBufferLock) {
-            invalidateByteBuffer(mCachedOutputBuffers, index);
-            mDequeuedOutputBuffers.remove(index);
-            if (mHasSurface) {
-                info = mDequeuedOutputInfos.remove(index);
-            }
-        }
-        releaseOutputBuffer(index, render, false /* updatePTS */, 0 /* dummy */);
+        releaseOutputBufferInternal(index, render, false /* updatePTS */, 0 /* dummy */);
     }
 
     /**
@@ -2887,16 +3622,33 @@
      * @throws MediaCodec.CodecException upon codec error.
      */
     public final void releaseOutputBuffer(int index, long renderTimestampNs) {
+        releaseOutputBufferInternal(
+                index, true /* render */, true /* updatePTS */, renderTimestampNs);
+    }
+
+    private void releaseOutputBufferInternal(
+            int index, boolean render, boolean updatePts, long renderTimestampNs) {
         BufferInfo info = null;
         synchronized(mBufferLock) {
-            invalidateByteBuffer(mCachedOutputBuffers, index);
-            mDequeuedOutputBuffers.remove(index);
-            if (mHasSurface) {
-                info = mDequeuedOutputInfos.remove(index);
+            switch (mBufferMode) {
+                case BUFFER_MODE_LEGACY:
+                    invalidateByteBuffer(mCachedOutputBuffers, index);
+                    mDequeuedOutputBuffers.remove(index);
+                    if (mHasSurface) {
+                        info = mDequeuedOutputInfos.remove(index);
+                    }
+                    break;
+                case BUFFER_MODE_BLOCK:
+                    OutputFrame frame = mOutputFrames.get(index);
+                    frame.setAccessible(false);
+                    frame.clear();
+                    break;
+                default:
+                    throw new IllegalStateException();
             }
         }
         releaseOutputBuffer(
-                index, true /* render */, true /* updatePTS */, renderTimestampNs);
+                index, render, updatePts, renderTimestampNs);
     }
 
     @UnsupportedAppUsage
@@ -3116,6 +3868,8 @@
             mCachedOutputBuffers = null;
             mDequeuedInputBuffers.clear();
             mDequeuedOutputBuffers.clear();
+            mQueueRequests.clear();
+            mOutputFrames.clear();
         }
     }
 
@@ -3154,11 +3908,16 @@
      */
     @NonNull
     public ByteBuffer[] getInputBuffers() {
-        if (mCachedInputBuffers == null) {
-            throw new IllegalStateException();
+        synchronized (mBufferLock) {
+            if (mBufferMode != BUFFER_MODE_LEGACY) {
+                throw new IllegalStateException();
+            }
+            if (mCachedInputBuffers == null) {
+                throw new IllegalStateException();
+            }
+            // FIXME: check codec status
+            return mCachedInputBuffers;
         }
-        // FIXME: check codec status
-        return mCachedInputBuffers;
     }
 
     /**
@@ -3185,11 +3944,16 @@
      */
     @NonNull
     public ByteBuffer[] getOutputBuffers() {
-        if (mCachedOutputBuffers == null) {
-            throw new IllegalStateException();
+        synchronized (mBufferLock) {
+            if (mBufferMode != BUFFER_MODE_LEGACY) {
+                throw new IllegalStateException();
+            }
+            if (mCachedOutputBuffers == null) {
+                throw new IllegalStateException();
+            }
+            // FIXME: check codec status
+            return mCachedOutputBuffers;
         }
-        // FIXME: check codec status
-        return mCachedOutputBuffers;
     }
 
     /**
@@ -3212,8 +3976,13 @@
      */
     @Nullable
     public ByteBuffer getInputBuffer(int index) {
+        synchronized (mBufferLock) {
+            if (mBufferMode != BUFFER_MODE_LEGACY) {
+                throw new IllegalStateException();
+            }
+        }
         ByteBuffer newBuffer = getBuffer(true /* input */, index);
-        synchronized(mBufferLock) {
+        synchronized (mBufferLock) {
             invalidateByteBuffer(mCachedInputBuffers, index);
             mDequeuedInputBuffers.put(index, newBuffer);
         }
@@ -3241,8 +4010,13 @@
      */
     @Nullable
     public Image getInputImage(int index) {
+        synchronized (mBufferLock) {
+            if (mBufferMode != BUFFER_MODE_LEGACY) {
+                throw new IllegalStateException();
+            }
+        }
         Image newImage = getImage(true /* input */, index);
-        synchronized(mBufferLock) {
+        synchronized (mBufferLock) {
             invalidateByteBuffer(mCachedInputBuffers, index);
             mDequeuedInputBuffers.put(index, newImage);
         }
@@ -3270,8 +4044,13 @@
      */
     @Nullable
     public ByteBuffer getOutputBuffer(int index) {
+        synchronized (mBufferLock) {
+            if (mBufferMode != BUFFER_MODE_LEGACY) {
+                throw new IllegalStateException();
+            }
+        }
         ByteBuffer newBuffer = getBuffer(false /* input */, index);
-        synchronized(mBufferLock) {
+        synchronized (mBufferLock) {
             invalidateByteBuffer(mCachedOutputBuffers, index);
             mDequeuedOutputBuffers.put(index, newBuffer);
         }
@@ -3298,8 +4077,13 @@
      */
     @Nullable
     public Image getOutputImage(int index) {
+        synchronized (mBufferLock) {
+            if (mBufferMode != BUFFER_MODE_LEGACY) {
+                throw new IllegalStateException();
+            }
+        }
         Image newImage = getImage(false /* input */, index);
-        synchronized(mBufferLock) {
+        synchronized (mBufferLock) {
             invalidateByteBuffer(mCachedOutputBuffers, index);
             mDequeuedOutputBuffers.put(index, newImage);
         }
@@ -3307,6 +4091,149 @@
     }
 
     /**
+     * A single output frame and its associated metadata.
+     */
+    public static final class OutputFrame {
+        // No public constructor
+        OutputFrame(int index) {
+            mIndex = index;
+        }
+
+        /**
+         * Returns the output linear block, or null if this frame is empty.
+         *
+         * @throws IllegalStateException if this output frame is not linear.
+         */
+        public @Nullable LinearBlock getLinearBlock() {
+            if (mGraphicBlock != null) {
+                throw new IllegalStateException();
+            }
+            return mLinearBlock;
+        }
+
+        /**
+         * Returns the output graphic block, or null if this frame is empty.
+         *
+         * @throws IllegalStateException if this output frame is not graphic.
+         */
+        public @Nullable GraphicBlock getGraphicBlock() {
+            if (mLinearBlock != null) {
+                throw new IllegalStateException();
+            }
+            return mGraphicBlock;
+        }
+
+        /**
+         * Returns the presentation timestamp in microseconds.
+         */
+        public long getPresentationTimeUs() {
+            return mPresentationTimeUs;
+        }
+
+        /**
+         * Returns the buffer flags.
+         */
+        public @BufferFlag int getFlags() {
+            return mFlags;
+        }
+
+        /**
+         * Returns a read-only {@link MediaFormat} for this frame. The returned
+         * object is valid only while the client is holding the output frame.
+         */
+        public @NonNull MediaFormat getFormat() {
+            return mFormat;
+        }
+
+        /**
+         * Populate {@code keys} with the name of entries that has changed from
+         * the previous frame. The entries may have been removed/changed/added.
+         * Client can find out what the change is by querying {@link MediaFormat}
+         * object returned from {@link #getFormat}.
+         */
+        public void getChangedKeys(@NonNull Set<String> keys) {
+            keys.clear();
+            keys.addAll(mChangedKeys);
+        }
+
+        void clear() {
+            mLinearBlock = null;
+            mGraphicBlock = null;
+            mFormat = null;
+            mChangedKeys.clear();
+            mLoaded = false;
+        }
+
+        boolean isAccessible() {
+            return mAccessible;
+        }
+
+        void setAccessible(boolean accessible) {
+            mAccessible = accessible;
+        }
+
+        void setBufferInfo(MediaCodec.BufferInfo info) {
+            mPresentationTimeUs = info.presentationTimeUs;
+            mFlags = info.flags;
+        }
+
+        boolean isLoaded() {
+            return mLoaded;
+        }
+
+        void setLoaded(boolean loaded) {
+            mLoaded = loaded;
+        }
+
+        private final int mIndex;
+        private LinearBlock mLinearBlock = null;
+        private GraphicBlock mGraphicBlock = null;
+        private long mPresentationTimeUs = 0;
+        private @BufferFlag int mFlags = 0;
+        private MediaFormat mFormat = null;
+        private final ArrayList<String> mChangedKeys = new ArrayList<>();
+        private boolean mAccessible = false;
+        private boolean mLoaded = false;
+    }
+
+    private final ArrayList<OutputFrame> mOutputFrames = new ArrayList<>();
+
+    /**
+     * Returns an {@link OutputFrame} object.
+     *
+     * @param index output buffer index from
+     *              {@link Callback#onOutputBufferAvailable}
+     * @return {@link OutputFrame} object describing the output buffer
+     * @throws IllegalStateException if not using block model
+     * @throws IllegalArgumentException if the output buffer is not available or
+     *                                  the index is out of range
+     */
+    public @NonNull OutputFrame getOutputFrame(int index) {
+        synchronized (mBufferLock) {
+            if (mBufferMode != BUFFER_MODE_BLOCK) {
+                throw new IllegalStateException();
+            }
+            if (index < 0 || index >= mOutputFrames.size()) {
+                throw new IllegalArgumentException();
+            }
+            OutputFrame frame = mOutputFrames.get(index);
+            if (frame == null) {
+                throw new IllegalArgumentException();
+            }
+            if (!frame.isAccessible()) {
+                throw new IllegalArgumentException();
+            }
+            if (!frame.isLoaded()) {
+                native_getOutputFrame(frame, index);
+                frame.setLoaded(true);
+            }
+            return frame;
+        }
+    }
+
+    private native void native_getOutputFrame(OutputFrame frame, int index);
+
+    /**
      * The content is scaled to the surface dimensions
      */
     public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT               = 1;
@@ -3889,7 +4816,9 @@
         @Override
         public void close() {
             if (mIsImageValid) {
-                java.nio.NioUtils.freeDirectBuffer(mBuffer);
+                if (mBuffer != null) {
+                    java.nio.NioUtils.freeDirectBuffer(mBuffer);
+                }
                 mIsImageValid = false;
             }
         }
@@ -3908,7 +4837,6 @@
             super.setCropRect(cropRect);
         }
 
-
         public MediaImage(
                 @NonNull ByteBuffer buffer, @NonNull ByteBuffer info, boolean readOnly,
                 long timestamp, int xOffset, int yOffset, @Nullable Rect cropRect) {
@@ -3963,7 +4891,6 @@
                         throw new UnsupportedOperationException("unexpected strides: "
                                 + colInc + " pixel, " + rowInc + " row on plane " + ix);
                     }
-
                     buffer.clear();
                     buffer.position(mBuffer.position() + planeOffset
                             + (xOffset / horiz) * colInc + (yOffset / vert) * rowInc);
@@ -3983,6 +4910,30 @@
             super.setCropRect(cropRect);
         }
 
+        public MediaImage(
+                @NonNull Image.Plane[] planes, int width, int height, int format, boolean readOnly,
+                long timestamp, int xOffset, int yOffset, @Nullable Rect cropRect) {
+            mWidth = width;
+            mHeight = height;
+            mFormat = format;
+            mTimestamp = timestamp;
+            mIsImageValid = true;
+            mIsReadOnly = readOnly;
+            mBuffer = null;
+            mInfo = null;
+            mPlanes = planes;
+
+            // save offsets and info
+            mXOffset = xOffset;
+            mYOffset = yOffset;
+
+            if (cropRect == null) {
+                cropRect = new Rect(0, 0, mWidth, mHeight);
+            }
+            cropRect.offset(-xOffset, -yOffset);
+            super.setCropRect(cropRect);
+        }
+
         private class MediaPlane extends Plane {
             public MediaPlane(@NonNull ByteBuffer buffer, int rowInc, int colInc) {
                 mData = buffer;
diff --git a/media/java/android/media/MediaFile.java b/media/java/android/media/MediaFile.java
index 9908e04..6689080 100644
--- a/media/java/android/media/MediaFile.java
+++ b/media/java/android/media/MediaFile.java
@@ -200,6 +200,84 @@
         return null;
     }
 
+    /**
+     * Check whether the mime type is document or not.
+     * @param mimeType the mime type to check
+     * @return true, if the mimeType is matched. Otherwise, false.
+     */
+    public static boolean isDocumentMimeType(@Nullable String mimeType) {
+        if (mimeType == null) {
+            return false;
+        }
+
+        final String normalizedMimeType = normalizeMimeType(mimeType);
+        if (normalizedMimeType.startsWith("text/")) {
+            return true;
+        }
+
+        switch (normalizedMimeType) {
+            case "application/epub+zip":
+            case "application/msword":
+            case "application/pdf":
+            case "application/rtf":
+            case "application/vnd.ms-excel":
+            case "application/vnd.ms-excel.addin.macroEnabled.12":
+            case "application/vnd.ms-excel.sheet.binary.macroEnabled.12":
+            case "application/vnd.ms-excel.sheet.macroEnabled.12":
+            case "application/vnd.ms-excel.template.macroEnabled.12":
+            case "application/vnd.ms-powerpoint":
+            case "application/vnd.ms-powerpoint.addin.macroEnabled.12":
+            case "application/vnd.ms-powerpoint.presentation.macroEnabled.12":
+            case "application/vnd.ms-powerpoint.slideshow.macroEnabled.12":
+            case "application/vnd.ms-powerpoint.template.macroEnabled.12":
+            case "application/vnd.ms-word.document.macroEnabled.12":
+            case "application/vnd.ms-word.template.macroEnabled.12":
+            case "application/vnd.oasis.opendocument.chart":
+            case "application/vnd.oasis.opendocument.database":
+            case "application/vnd.oasis.opendocument.formula":
+            case "application/vnd.oasis.opendocument.graphics":
+            case "application/vnd.oasis.opendocument.graphics-template":
+            case "application/vnd.oasis.opendocument.presentation":
+            case "application/vnd.oasis.opendocument.presentation-template":
+            case "application/vnd.oasis.opendocument.spreadsheet":
+            case "application/vnd.oasis.opendocument.spreadsheet-template":
+            case "application/vnd.oasis.opendocument.text":
+            case "application/vnd.oasis.opendocument.text-master":
+            case "application/vnd.oasis.opendocument.text-template":
+            case "application/vnd.oasis.opendocument.text-web":
+            case "application/vnd.openxmlformats-officedocument.presentationml.presentation":
+            case "application/vnd.openxmlformats-officedocument.presentationml.slideshow":
+            case "application/vnd.openxmlformats-officedocument.presentationml.template":
+            case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
+            case "application/vnd.openxmlformats-officedocument.spreadsheetml.template":
+            case "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
+            case "application/vnd.openxmlformats-officedocument.wordprocessingml.template":
+            case "application/vnd.stardivision.calc":
+            case "application/vnd.stardivision.chart":
+            case "application/vnd.stardivision.draw":
+            case "application/vnd.stardivision.impress":
+            case "application/vnd.stardivision.impress-packed":
+            case "application/vnd.stardivision.mail":
+            case "application/vnd.stardivision.math":
+            case "application/vnd.stardivision.writer":
+            case "application/vnd.stardivision.writer-global":
+            case "application/vnd.sun.xml.calc":
+            case "application/vnd.sun.xml.calc.template":
+            case "application/vnd.sun.xml.draw":
+            case "application/vnd.sun.xml.draw.template":
+            case "application/vnd.sun.xml.impress":
+            case "application/vnd.sun.xml.impress.template":
+            case "application/vnd.sun.xml.math":
+            case "application/vnd.sun.xml.writer":
+            case "application/vnd.sun.xml.writer.global":
+            case "application/vnd.sun.xml.writer.template":
+            case "application/x-mspublisher":
+                return true;
+            default:
+                return false;
+        }
+    }
+
     public static boolean isExifMimeType(@Nullable String mimeType) {
         // For simplicity, assume that all image files might have EXIF data
         return isImageMimeType(mimeType);
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 79b3886..f408ac3 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -100,6 +100,8 @@
  * <tr><td>{@link #KEY_AAC_DRC_HEAVY_COMPRESSION}</td><td>Integer</td><td><b>decoder-only</b>, optional, if content is AAC audio, specifies whether to use heavy compression.</td></tr>
  * <tr><td>{@link #KEY_AAC_MAX_OUTPUT_CHANNEL_COUNT}</td><td>Integer</td><td><b>decoder-only</b>, optional, if content is AAC audio, specifies the maximum number of channels the decoder outputs.</td></tr>
  * <tr><td>{@link #KEY_AAC_DRC_EFFECT_TYPE}</td><td>Integer</td><td><b>decoder-only</b>, optional, if content is AAC audio, specifies the MPEG-D DRC effect type to use.</td></tr>
+ * <tr><td>{@link #KEY_AAC_DRC_OUTPUT_LOUDNESS}</td><td>Integer</td><td><b>decoder-only</b>, optional, if content is AAC audio, returns the DRC output loudness.</td></tr>
+ * <tr><td>{@link #KEY_AAC_DRC_ALBUM_MODE}</td><td>Integer</td><td><b>decoder-only</b>, optional, if content is AAC audio, specifies the whether MPEG-D DRC Album Mode is active or not.</td></tr>
  * <tr><td>{@link #KEY_CHANNEL_MASK}</td><td>Integer</td><td>optional, a mask of audio channel assignments</td></tr>
  * <tr><td>{@link #KEY_ENCODER_DELAY}</td><td>Integer</td><td>optional, the number of frames to trim from the start of the decoded audio stream.</td></tr>
  * <tr><td>{@link #KEY_ENCODER_PADDING}</td><td>Integer</td><td>optional, the number of frames to trim from the end of the decoded audio stream.</td></tr>
@@ -292,6 +294,14 @@
     public static final String KEY_BIT_RATE = "bitrate";
 
     /**
+     * A key describing the hardware AV sync id.
+     * The associated value is an integer
+     *
+     * @see android.media.tv.tuner.Tuner#getAvSyncHwId
+     */
+    public static final String KEY_HARDWARE_AV_SYNC_ID = "hw-av-sync-id";
+
+    /**
      * A key describing the max bitrate in bits/sec.
      * This is usually over a one-second sliding window (e.g. over any window of one second).
      * The associated value is an integer
@@ -736,6 +746,37 @@
     public static final String KEY_AAC_DRC_HEAVY_COMPRESSION = "aac-drc-heavy-compression";
 
     /**
+     * A key to retrieve the output loudness of a decoded bitstream.
+     * <p>If loudness normalization is active, the value corresponds to the Target Reference Level
+     * (see {@link #KEY_AAC_DRC_TARGET_REFERENCE_LEVEL}).<br>
+     * If loudness normalization is not active, the value corresponds to the loudness metadata
+     * given in the bitstream.
+     * <p>The value is retrieved with getInteger() and is given as an integer value between 0 and
+     * 231. It is calculated as -4 * Output Loudness in LKFS. Therefore, it represents the range of
+     * 0 to -57.75 LKFS.
+     * <p>A value of -1 indicates that no loudness metadata is present in the bitstream.
+     * <p>Loudness metadata can originate from MPEG-4 DRC or MPEG-D DRC.
+     * <p>This key is only used during decoding.
+     */
+    public static final String KEY_AAC_DRC_OUTPUT_LOUDNESS = "aac-drc-output-loudness";
+
+    /**
+     * A key describing the album mode for MPEG-D DRC as defined in ISO/IEC 23003-4.
+     * <p>The associated value is an integer and can be set to following values:
+     * <table>
+     * <tr><th>Value</th><th>Album Mode</th></tr>
+     * <tr><th>0</th><th>disabled</th></tr>
+     * <tr><th>1</th><th>enabled</th></tr>
+     * </table>
+     * <p>Disabled album mode leads to application of gain sequences for fading in and out, if
+     * provided in the bitstream. Enabled album mode makes use of dedicated album loudness
+     * information, if provided in the bitstream.
+     * <p>The default value is 0 (album mode disabled).
+     * <p>This key is only used during decoding.
+     */
+    public static final String KEY_AAC_DRC_ALBUM_MODE = "aac-drc-album-mode";
+
+    /**
      * A key describing the FLAC compression level to be used (FLAC audio format only).
      * The associated value is an integer ranging from 0 (fastest, least compression)
      * to 8 (slowest, most compression).
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 71c97534..2d820e7 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -632,7 +632,6 @@
     private boolean mScreenOnWhilePlaying;
     private boolean mStayAwake;
     private int mStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE;
-    private int mUsage = -1;
 
     // Modular DRM
     private UUID mDrmUUID;
@@ -2220,7 +2219,6 @@
             throw new IllegalArgumentException(msg);
         }
         baseUpdateAudioAttributes(attributes);
-        mUsage = attributes.getUsage();
         Parcel pattributes = Parcel.obtain();
         attributes.writeToParcel(pattributes, AudioAttributes.FLATTEN_TAGS);
         setParameter(KEY_PARAMETER_AUDIO_ATTRIBUTES, pattributes);
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 4e6b4af..eae13d0 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -16,6 +16,8 @@
 
 package android.media;
 
+import static android.media.MediaRouter2Utils.toUniqueId;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -34,7 +36,6 @@
 
 /**
  * Describes the properties of a route.
- * @hide
  */
 public final class MediaRoute2Info implements Parcelable {
     @NonNull
@@ -54,7 +55,7 @@
     @IntDef({CONNECTION_STATE_DISCONNECTED, CONNECTION_STATE_CONNECTING,
             CONNECTION_STATE_CONNECTED})
     @Retention(RetentionPolicy.SOURCE)
-    private @interface ConnectionState {}
+    public @interface ConnectionState {}
 
     /**
      * The default connection state indicating the route is disconnected.
@@ -83,28 +84,29 @@
      * controlled from this object. An example of fixed playback volume is a remote player,
      * playing over HDMI where the user prefers to control the volume on the HDMI sink, rather
      * than attenuate at the source.
+     *
      * @see #getVolumeHandling()
      */
     public static final int PLAYBACK_VOLUME_FIXED = 0;
     /**
      * Playback information indicating the playback volume is variable and can be controlled
      * from this object.
+     *
      * @see #getVolumeHandling()
      */
     public static final int PLAYBACK_VOLUME_VARIABLE = 1;
 
     /** @hide */
     @IntDef({
-            DEVICE_TYPE_UNKNOWN, DEVICE_TYPE_TV,
-            DEVICE_TYPE_SPEAKER, DEVICE_TYPE_BLUETOOTH})
+            DEVICE_TYPE_UNKNOWN, DEVICE_TYPE_REMOTE_TV,
+            DEVICE_TYPE_REMOTE_SPEAKER, DEVICE_TYPE_BLUETOOTH})
     @Retention(RetentionPolicy.SOURCE)
-    private @interface DeviceType {}
+    public @interface DeviceType {}
 
     /**
      * The default receiver device type of the route indicating the type is unknown.
      *
      * @see #getDeviceType
-     * @hide
      */
     public static final int DEVICE_TYPE_UNKNOWN = 0;
 
@@ -114,7 +116,7 @@
      *
      * @see #getDeviceType
      */
-    public static final int DEVICE_TYPE_TV = 1;
+    public static final int DEVICE_TYPE_REMOTE_TV = 1;
 
     /**
      * A receiver device type of the route indicating the presentation of the media is happening
@@ -122,77 +124,266 @@
      *
      * @see #getDeviceType
      */
-    public static final int DEVICE_TYPE_SPEAKER = 2;
+    public static final int DEVICE_TYPE_REMOTE_SPEAKER = 2;
 
     /**
      * A receiver device type of the route indicating the presentation of the media is happening
      * on a bluetooth device such as a bluetooth speaker.
      *
      * @see #getDeviceType
-     * @hide
      */
     public static final int DEVICE_TYPE_BLUETOOTH = 3;
 
-    @NonNull
+    /**
+     * Media feature: Live audio.
+     * <p>
+     * A route that supports live audio routing will allow the media audio stream
+     * to be sent to supported destinations.  This can include internal speakers or
+     * audio jacks on the device itself, A2DP devices, and more.
+     * </p><p>
+     * When a live audio route is selected, audio routing is transparent to the application.
+     * All audio played on the media stream will be routed to the selected destination.
+     * </p><p>
+     * Refer to the class documentation for details about live audio routes.
+     * </p>
+     */
+    public static final String FEATURE_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO";
+
+    /**
+     * Media feature: Live video.
+     * <p>
+     * A route that supports live video routing will allow a mirrored version
+     * of the device's primary display or a customized
+     * {@link android.app.Presentation Presentation} to be sent to supported
+     * destinations.
+     * </p><p>
+     * When a live video route is selected, audio and video routing is transparent
+     * to the application.  By default, audio and video is routed to the selected
+     * destination.  For certain live video routes, the application may also use a
+     * {@link android.app.Presentation Presentation} to replace the mirrored view
+     * on the external display with different content.
+     * </p><p>
+     * Refer to the class documentation for details about live video routes.
+     * </p>
+     *
+     * @see android.app.Presentation
+     */
+    public static final String FEATURE_LIVE_VIDEO = "android.media.intent.category.LIVE_VIDEO";
+
+    /**
+     * Media feature: Remote playback.
+     * <p>
+     * A route that supports remote playback routing will allow an application to send
+     * requests to play content remotely to supported destinations.
+     * </p><p>
+     * Remote playback routes destinations operate independently of the local device.
+     * When a remote playback route is selected, the application can control the content
+     * playing on the destination using {@link MediaRouter2.RoutingController#getControlHints()}.
+     * The application may also receive status updates from the route regarding remote playback.
+     * </p><p>
+     * Refer to the class documentation for details about remote playback routes.
+     * </p>
+     */
+    public static final String FEATURE_REMOTE_PLAYBACK =
+            "android.media.intent.category.REMOTE_PLAYBACK";
+
     final String mId;
-    @Nullable
-    final String mProviderId;
-    @NonNull
     final CharSequence mName;
-    @Nullable
-    final CharSequence mDescription;
-    @Nullable
-    final @ConnectionState int mConnectionState;
-    @Nullable
+    final List<String> mFeatures;
+    @DeviceType
+    final int mDeviceType;
     final Uri mIconUri;
-    @Nullable
+    final CharSequence mDescription;
+    @ConnectionState
+    final int mConnectionState;
     final String mClientPackageName;
-    @NonNull
-    final List<String> mSupportedCategories;
     final int mVolume;
     final int mVolumeMax;
     final int mVolumeHandling;
-    final @DeviceType int mDeviceType;
-    @Nullable
     final Bundle mExtras;
+    final String mProviderId;
 
     MediaRoute2Info(@NonNull Builder builder) {
         mId = builder.mId;
-        mProviderId = builder.mProviderId;
         mName = builder.mName;
+        mFeatures = builder.mFeatures;
+        mDeviceType = builder.mDeviceType;
+        mIconUri = builder.mIconUri;
         mDescription = builder.mDescription;
         mConnectionState = builder.mConnectionState;
-        mIconUri = builder.mIconUri;
         mClientPackageName = builder.mClientPackageName;
-        mSupportedCategories = builder.mSupportedCategories;
-        mVolume = builder.mVolume;
-        mVolumeMax = builder.mVolumeMax;
         mVolumeHandling = builder.mVolumeHandling;
-        mDeviceType = builder.mDeviceType;
+        mVolumeMax = builder.mVolumeMax;
+        mVolume = builder.mVolume;
         mExtras = builder.mExtras;
+        mProviderId = builder.mProviderId;
     }
 
     MediaRoute2Info(@NonNull Parcel in) {
         mId = in.readString();
-        mProviderId = in.readString();
         mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mFeatures = in.createStringArrayList();
+        mDeviceType = in.readInt();
+        mIconUri = in.readParcelable(null);
         mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
         mConnectionState = in.readInt();
-        mIconUri = in.readParcelable(null);
         mClientPackageName = in.readString();
-        mSupportedCategories = in.createStringArrayList();
-        mVolume = in.readInt();
-        mVolumeMax = in.readInt();
         mVolumeHandling = in.readInt();
-        mDeviceType = in.readInt();
+        mVolumeMax = in.readInt();
+        mVolume = in.readInt();
         mExtras = in.readBundle();
+        mProviderId = in.readString();
     }
 
     /**
+     * Gets the id of the route. The routes which are given by {@link MediaRouter2} will have
+     * unique IDs.
+     * <p>
+     * In order to ensure uniqueness in {@link MediaRouter2} side, the value of this method
+     * can be different from what was set in {@link MediaRoute2ProviderService}.
+     *
+     * @see Builder#Builder(String, CharSequence)
+     */
+    @NonNull
+    public String getId() {
+        if (mProviderId != null) {
+            return toUniqueId(mProviderId, mId);
+        } else {
+            return mId;
+        }
+    }
+
+    /**
+     * Gets the user-visible name of the route.
+     */
+    @NonNull
+    public CharSequence getName() {
+        return mName;
+    }
+
+    /**
+     * Gets the supported features of the route.
+     */
+    @NonNull
+    public List<String> getFeatures() {
+        return mFeatures;
+    }
+
+    /**
+     * Gets the type of the receiver device associated with this route.
+     *
+     * @return The type of the receiver device associated with this route:
+     * {@link #DEVICE_TYPE_REMOTE_TV}, {@link #DEVICE_TYPE_REMOTE_SPEAKER},
+     * {@link #DEVICE_TYPE_BLUETOOTH}.
+     */
+    @DeviceType
+    public int getDeviceType() {
+        return mDeviceType;
+    }
+
+    /**
+     * Gets the URI of the icon representing this route.
+     * <p>
+     * This icon will be used in picker UIs if available.
+     *
+     * @return The URI of the icon representing this route, or null if none.
+     */
+    @Nullable
+    public Uri getIconUri() {
+        return mIconUri;
+    }
+
+    /**
+     * Gets the user-visible description of the route.
+     */
+    @Nullable
+    public CharSequence getDescription() {
+        return mDescription;
+    }
+
+    /**
+     * Gets the connection state of the route.
+     *
+     * @return The connection state of this route: {@link #CONNECTION_STATE_DISCONNECTED},
+     * {@link #CONNECTION_STATE_CONNECTING}, or {@link #CONNECTION_STATE_CONNECTED}.
+     */
+    @ConnectionState
+    public int getConnectionState() {
+        return mConnectionState;
+    }
+
+    /**
+     * Gets the package name of the app using the route.
+     * Returns null if no apps are using this route.
+     */
+    @Nullable
+    public String getClientPackageName() {
+        return mClientPackageName;
+    }
+
+    /**
+     * Gets information about how volume is handled on the route.
+     *
+     * @return {@link #PLAYBACK_VOLUME_FIXED} or {@link #PLAYBACK_VOLUME_VARIABLE}
+     */
+    public int getVolumeHandling() {
+        return mVolumeHandling;
+    }
+
+    /**
+     * Gets the maximum volume of the route.
+     */
+    public int getVolumeMax() {
+        return mVolumeMax;
+    }
+
+    /**
+     * Gets the current volume of the route. This may be invalid if the route is not selected.
+     */
+    public int getVolume() {
+        return mVolume;
+    }
+
+    @Nullable
+    public Bundle getExtras() {
+        return mExtras == null ? null : new Bundle(mExtras);
+    }
+
+    /**
+     * Gets the original id set by {@link Builder#Builder(String, CharSequence)}.
      * @hide
      */
-    public static String toUniqueId(String providerId, String routeId) {
-        return providerId + ":" + routeId;
+    @NonNull
+    public String getOriginalId() {
+        return mId;
+    }
+
+    /**
+     * Gets the provider id of the route. It is assigned automatically by
+     * {@link com.android.server.media.MediaRouterService}.
+     *
+     * @return provider id of the route or null if it's not set.
+     * @hide
+     */
+    @Nullable
+    public String getProviderId() {
+        return mProviderId;
+    }
+
+    /**
+     * Returns if the route has at least one of the specified route features.
+     *
+     * @param features the list of route features to consider
+     * @return true if the route has at least one feature in the list
+     */
+    public boolean hasAnyFeatures(@NonNull Collection<String> features) {
+        Objects.requireNonNull(features, "features must not be null");
+        for (String feature : features) {
+            if (getFeatures().contains(feature)) {
+                return true;
+            }
+        }
+        return false;
     }
 
     /**
@@ -219,189 +410,49 @@
             return false;
         }
         MediaRoute2Info other = (MediaRoute2Info) obj;
+
+        // Note: mExtras is not included.
         return Objects.equals(mId, other.mId)
-                && Objects.equals(mProviderId, other.mProviderId)
                 && Objects.equals(mName, other.mName)
+                && Objects.equals(mFeatures, other.mFeatures)
+                && (mDeviceType == other.mDeviceType)
+                && Objects.equals(mIconUri, other.mIconUri)
                 && Objects.equals(mDescription, other.mDescription)
                 && (mConnectionState == other.mConnectionState)
-                && Objects.equals(mIconUri, other.mIconUri)
                 && Objects.equals(mClientPackageName, other.mClientPackageName)
-                && Objects.equals(mSupportedCategories, other.mSupportedCategories)
-                && (mVolume == other.mVolume)
-                && (mVolumeMax == other.mVolumeMax)
                 && (mVolumeHandling == other.mVolumeHandling)
-                && (mDeviceType == other.mDeviceType)
-                //TODO: This will be evaluated as false in most cases. Try not to.
-                && Objects.equals(mExtras, other.mExtras);
+                && (mVolumeMax == other.mVolumeMax)
+                && (mVolume == other.mVolume)
+                && Objects.equals(mProviderId, other.mProviderId);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mId, mName, mDescription, mConnectionState, mIconUri,
-                mSupportedCategories, mVolume, mVolumeMax, mVolumeHandling, mDeviceType);
+        // Note: mExtras is not included.
+        return Objects.hash(mId, mName, mFeatures, mDeviceType, mIconUri, mDescription,
+                mConnectionState, mClientPackageName, mVolumeHandling, mVolumeMax, mVolume,
+                mProviderId);
     }
 
-    /**
-     * Gets the id of the route. The routes which are given by {@link MediaRouter2} will have
-     * unique IDs.
-     * <p>
-     * In order to ensure uniqueness in {@link MediaRouter2} side, the value of this method
-     * can be different from what was set in {@link MediaRoute2ProviderService}.
-     *
-     * @see Builder#setId(String)
-     */
-    @NonNull
-    public String getId() {
-        if (mProviderId != null) {
-            return toUniqueId(mProviderId, mId);
-        } else {
-            return mId;
-        }
-    }
-
-    /**
-     * Gets the original id set by {@link Builder#setId(String)}.
-     * @hide
-     */
-    @NonNull
-    public String getOriginalId() {
-        return mId;
-    }
-
-    /**
-     * Gets the provider id of the route. It is assigned automatically by
-     * {@link com.android.server.media.MediaRouterService}.
-     *
-     * @return provider id of the route or null if it's not set.
-     * @hide
-     */
-    @Nullable
-    public String getProviderId() {
-        return mProviderId;
-    }
-
-    @NonNull
-    public CharSequence getName() {
-        return mName;
-    }
-
-    @Nullable
-    public CharSequence getDescription() {
-        return mDescription;
-    }
-
-    /**
-     * Gets the connection state of the route.
-     *
-     * @return The connection state of this route: {@link #CONNECTION_STATE_DISCONNECTED},
-     * {@link #CONNECTION_STATE_CONNECTING}, or {@link #CONNECTION_STATE_CONNECTED}.
-     */
-    @ConnectionState
-    public int getConnectionState() {
-        return mConnectionState;
-    }
-
-    /**
-     * Gets the URI of the icon representing this route.
-     * <p>
-     * This icon will be used in picker UIs if available.
-     *
-     * @return The URI of the icon representing this route, or null if none.
-     */
-    @Nullable
-    public Uri getIconUri() {
-        return mIconUri;
-    }
-
-    /**
-     * Gets the package name of the client that uses the route.
-     * Returns null if no clients use this.
-     * @hide
-     */
-    @Nullable
-    public String getClientPackageName() {
-        return mClientPackageName;
-    }
-
-    /**
-     * Gets the supported categories of the route.
-     */
-    @NonNull
-    public List<String> getSupportedCategories() {
-        return mSupportedCategories;
-    }
-
-    //TODO: once device types are confirmed, reflect those into the comment.
-    /**
-     * Gets the type of the receiver device associated with this route.
-     *
-     * @return The type of the receiver device associated with this route:
-     * {@link #DEVICE_TYPE_TV} or {@link #DEVICE_TYPE_SPEAKER}.
-     */
-    @DeviceType
-    public int getDeviceType() {
-        return mDeviceType;
-    }
-
-    /**
-     * Gets the current volume of the route. This may be invalid if the route is not selected.
-     */
-    public int getVolume() {
-        return mVolume;
-    }
-
-    /**
-     * Gets the maximum volume of the route.
-     */
-    public int getVolumeMax() {
-        return mVolumeMax;
-    }
-
-    /**
-     * Gets information about how volume is handled on the route.
-     *
-     * @return {@link #PLAYBACK_VOLUME_FIXED} or {@link #PLAYBACK_VOLUME_VARIABLE}
-     */
-    public int getVolumeHandling() {
-        return mVolumeHandling;
-    }
-
-    @Nullable
-    public Bundle getExtras() {
-        return mExtras;
-    }
-
-    /**
-     * Returns if the route supports the specified control category
-     *
-     * @param controlCategory control category to consider
-     * @return true if the route supports at the category
-     */
-    public boolean supportsControlCategory(@NonNull String controlCategory) {
-        Objects.requireNonNull(controlCategory, "control category must not be null");
-        for (String supportedCategory : getSupportedCategories()) {
-            if (TextUtils.equals(controlCategory, supportedCategory)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    //TODO: Move this if we re-define control category / selector things.
-    /**
-     * Returns if the route supports at least one of the specified control categories
-     *
-     * @param controlCategories the list of control categories to consider
-     * @return true if the route supports at least one category
-     */
-    public boolean supportsControlCategories(@NonNull Collection<String> controlCategories) {
-        Objects.requireNonNull(controlCategories, "control categories must not be null");
-        for (String controlCategory : controlCategories) {
-            if (supportsControlCategory(controlCategory)) {
-                return true;
-            }
-        }
-        return false;
+    @Override
+    public String toString() {
+        // Note: mExtras is not printed here.
+        StringBuilder result = new StringBuilder()
+                .append("MediaRoute2Info{ ")
+                .append("id=").append(getId())
+                .append(", name=").append(getName())
+                .append(", features=").append(getFeatures())
+                .append(", deviceType=").append(getDeviceType())
+                .append(", iconUri=").append(getIconUri())
+                .append(", description=").append(getDescription())
+                .append(", connectionState=").append(getConnectionState())
+                .append(", clientPackageName=").append(getClientPackageName())
+                .append(", volumeHandling=").append(getVolumeHandling())
+                .append(", volumeMax=").append(getVolumeMax())
+                .append(", volume=").append(getVolume())
+                .append(", providerId=").append(getProviderId())
+                .append(" }");
+        return result.toString();
     }
 
     @Override
@@ -412,150 +463,128 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString(mId);
-        dest.writeString(mProviderId);
         TextUtils.writeToParcel(mName, dest, flags);
+        dest.writeStringList(mFeatures);
+        dest.writeInt(mDeviceType);
+        dest.writeParcelable(mIconUri, flags);
         TextUtils.writeToParcel(mDescription, dest, flags);
         dest.writeInt(mConnectionState);
-        dest.writeParcelable(mIconUri, flags);
         dest.writeString(mClientPackageName);
-        dest.writeStringList(mSupportedCategories);
-        dest.writeInt(mVolume);
-        dest.writeInt(mVolumeMax);
         dest.writeInt(mVolumeHandling);
-        dest.writeInt(mDeviceType);
+        dest.writeInt(mVolumeMax);
+        dest.writeInt(mVolume);
         dest.writeBundle(mExtras);
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder result = new StringBuilder()
-                .append("MediaRouteInfo{ ")
-                .append("id=").append(getId())
-                .append(", name=").append(getName())
-                .append(", description=").append(getDescription())
-                .append(", connectionState=").append(getConnectionState())
-                .append(", iconUri=").append(getIconUri())
-                .append(", volume=").append(getVolume())
-                .append(", volumeMax=").append(getVolumeMax())
-                .append(", volumeHandling=").append(getVolumeHandling())
-                .append(", deviceType=").append(getDeviceType())
-                .append(", providerId=").append(getProviderId())
-                .append(" }");
-        return result.toString();
+        dest.writeString(mProviderId);
     }
 
     /**
      * Builder for {@link MediaRoute2Info media route info}.
      */
     public static final class Builder {
-        String mId;
-        String mProviderId;
-        CharSequence mName;
+        final String mId;
+        final CharSequence mName;
+        final List<String> mFeatures;
+
+        @DeviceType
+        int mDeviceType = DEVICE_TYPE_UNKNOWN;
+        Uri mIconUri;
         CharSequence mDescription;
         @ConnectionState
         int mConnectionState;
-        Uri mIconUri;
         String mClientPackageName;
-        List<String> mSupportedCategories;
-        int mVolume;
-        int mVolumeMax;
         int mVolumeHandling = PLAYBACK_VOLUME_FIXED;
-        @DeviceType
-        int mDeviceType = DEVICE_TYPE_UNKNOWN;
+        int mVolumeMax;
+        int mVolume;
         Bundle mExtras;
+        String mProviderId;
 
+        /**
+         * Constructor for builder to create {@link MediaRoute2Info}.
+         * <p>
+         * In order to ensure ID uniqueness, the {@link MediaRoute2Info#getId() ID} of a route info
+         * obtained from {@link MediaRouter2} can be different from what was set in
+         * {@link MediaRoute2ProviderService}.
+         * </p>
+         * @param id The ID of the route. Must not be empty.
+         * @param name The user-visible name of the route.
+         */
         public Builder(@NonNull String id, @NonNull CharSequence name) {
-            setId(id);
-            setName(name);
-            mSupportedCategories = new ArrayList<>();
+            if (TextUtils.isEmpty(id)) {
+                throw new IllegalArgumentException("id must not be empty");
+            }
+            if (TextUtils.isEmpty(name)) {
+                throw new IllegalArgumentException("name must not be empty");
+            }
+            mId = id;
+            mName = name;
+            mFeatures = new ArrayList<>();
         }
 
+        /**
+         * Constructor for builder to create {@link MediaRoute2Info} with
+         * existing {@link MediaRoute2Info} instance.
+         *
+         * @param routeInfo the existing instance to copy data from.
+         */
         public Builder(@NonNull MediaRoute2Info routeInfo) {
-            if (routeInfo == null) {
-                throw new IllegalArgumentException("route info must not be null");
-            }
+            Objects.requireNonNull(routeInfo, "routeInfo must not be null");
 
-            setId(routeInfo.mId);
-            if (!TextUtils.isEmpty(routeInfo.mProviderId)) {
-                setProviderId(routeInfo.mProviderId);
-            }
-            setName(routeInfo.mName);
+            mId = routeInfo.mId;
+            mName = routeInfo.mName;
+            mFeatures = new ArrayList<>(routeInfo.mFeatures);
+            mDeviceType = routeInfo.mDeviceType;
+            mIconUri = routeInfo.mIconUri;
             mDescription = routeInfo.mDescription;
             mConnectionState = routeInfo.mConnectionState;
-            mIconUri = routeInfo.mIconUri;
-            setClientPackageName(routeInfo.mClientPackageName);
-            setSupportedCategories(routeInfo.mSupportedCategories);
-            setVolume(routeInfo.mVolume);
-            setVolumeMax(routeInfo.mVolumeMax);
-            setVolumeHandling(routeInfo.mVolumeHandling);
-            setDeviceType(routeInfo.mDeviceType);
+            mClientPackageName = routeInfo.mClientPackageName;
+            mVolumeHandling = routeInfo.mVolumeHandling;
+            mVolumeMax = routeInfo.mVolumeMax;
+            mVolume = routeInfo.mVolume;
             if (routeInfo.mExtras != null) {
                 mExtras = new Bundle(routeInfo.mExtras);
             }
+            mProviderId = routeInfo.mProviderId;
         }
 
         /**
-         * Sets the unique id of the route. The value given here must be unique for each of your
-         * route.
-         * <p>
-         * In order to ensure uniqueness in {@link MediaRouter2} side, the value of
-         * {@link MediaRoute2Info#getId()} can be different from what was set in
-         * {@link MediaRoute2ProviderService}.
-         * </p>
-         *
-         * @see MediaRoute2Info#getId()
+         * Adds a feature for the route.
          */
         @NonNull
-        public Builder setId(@NonNull String id) {
-            if (TextUtils.isEmpty(id)) {
-                throw new IllegalArgumentException("id must not be null or empty");
+        public Builder addFeature(@NonNull String feature) {
+            if (TextUtils.isEmpty(feature)) {
+                throw new IllegalArgumentException("feature must not be null or empty");
             }
-            mId = id;
+            mFeatures.add(feature);
             return this;
         }
 
         /**
-         * Sets the provider id of the route.
-         * @hide
+         * Adds features for the route. A route must support at least one route type.
          */
         @NonNull
-        public Builder setProviderId(@NonNull String providerId) {
-            if (TextUtils.isEmpty(providerId)) {
-                throw new IllegalArgumentException("providerId must not be null or empty");
+        public Builder addFeatures(@NonNull Collection<String> features) {
+            Objects.requireNonNull(features, "features must not be null");
+            for (String feature : features) {
+                addFeature(feature);
             }
-            mProviderId = providerId;
             return this;
         }
 
         /**
-         * Sets the user-visible name of the route.
+         * Clears the features of the route. A route must support at least one route type.
          */
         @NonNull
-        public Builder setName(@NonNull CharSequence name) {
-            Objects.requireNonNull(name, "name must not be null");
-            mName = name;
+        public Builder clearFeatures() {
+            mFeatures.clear();
             return this;
         }
 
         /**
-         * Sets the user-visible description of the route.
+         * Sets the route's device type.
          */
         @NonNull
-        public Builder setDescription(@Nullable String description) {
-            mDescription = description;
-            return this;
-        }
-
-        /**
-        * Sets the route's connection state.
-        *
-        * {@link #CONNECTION_STATE_DISCONNECTED},
-        * {@link #CONNECTION_STATE_CONNECTING}, or
-        * {@link #CONNECTION_STATE_CONNECTED}.
-        */
-        @NonNull
-        public Builder setConnectionState(@ConnectionState int connectionState) {
-            mConnectionState = connectionState;
+        public Builder setDeviceType(@DeviceType int deviceType) {
+            mDeviceType = deviceType;
             return this;
         }
 
@@ -580,6 +609,28 @@
         }
 
         /**
+         * Sets the user-visible description of the route.
+         */
+        @NonNull
+        public Builder setDescription(@Nullable CharSequence description) {
+            mDescription = description;
+            return this;
+        }
+
+        /**
+        * Sets the route's connection state.
+        *
+        * {@link #CONNECTION_STATE_DISCONNECTED},
+        * {@link #CONNECTION_STATE_CONNECTING}, or
+        * {@link #CONNECTION_STATE_CONNECTED}.
+        */
+        @NonNull
+        public Builder setConnectionState(@ConnectionState int connectionState) {
+            mConnectionState = connectionState;
+            return this;
+        }
+
+        /**
          * Sets the package name of the app using the route.
          */
         @NonNull
@@ -589,57 +640,6 @@
         }
 
         /**
-         * Sets the supported categories of the route.
-         */
-        @NonNull
-        public Builder setSupportedCategories(@NonNull Collection<String> categories) {
-            mSupportedCategories = new ArrayList<>();
-            return addSupportedCategories(categories);
-        }
-
-        /**
-         * Adds supported categories for the route.
-         */
-        @NonNull
-        public Builder addSupportedCategories(@NonNull Collection<String> categories) {
-            Objects.requireNonNull(categories, "categories must not be null");
-            for (String category: categories) {
-                addSupportedCategory(category);
-            }
-            return this;
-        }
-
-        /**
-         * Add a supported category for the route.
-         */
-        @NonNull
-        public Builder addSupportedCategory(@NonNull String category) {
-            if (TextUtils.isEmpty(category)) {
-                throw new IllegalArgumentException("category must not be null or empty");
-            }
-            mSupportedCategories.add(category);
-            return this;
-        }
-
-        /**
-         * Sets the route's current volume, or 0 if unknown.
-         */
-        @NonNull
-        public Builder setVolume(int volume) {
-            mVolume = volume;
-            return this;
-        }
-
-        /**
-         * Sets the route's maximum volume, or 0 if unknown.
-         */
-        @NonNull
-        public Builder setVolumeMax(int volumeMax) {
-            mVolumeMax = volumeMax;
-            return this;
-        }
-
-        /**
          * Sets the route's volume handling.
          */
         @NonNull
@@ -649,28 +649,61 @@
         }
 
         /**
-         * Sets the route's device type.
+         * Sets the route's maximum volume, or 0 if unknown.
          */
         @NonNull
-        public Builder setDeviceType(@DeviceType int deviceType) {
-            mDeviceType = deviceType;
+        public Builder setVolumeMax(int volumeMax) {
+            mVolumeMax = volumeMax;
+            return this;
+        }
+
+        /**
+         * Sets the route's current volume, or 0 if unknown.
+         */
+        @NonNull
+        public Builder setVolume(int volume) {
+            mVolume = volume;
             return this;
         }
 
         /**
          * Sets a bundle of extras for the route.
+         * <p>
+         * Note: The extras will not affect the result of {@link MediaRoute2Info#equals(Object)}.
          */
         @NonNull
         public Builder setExtras(@Nullable Bundle extras) {
-            mExtras = extras;
+            if (extras == null) {
+                mExtras = null;
+                return this;
+            }
+            mExtras = new Bundle(extras);
+            return this;
+        }
+
+        /**
+         * Sets the provider id of the route.
+         * @hide
+         */
+        @NonNull
+        public Builder setProviderId(@NonNull String providerId) {
+            if (TextUtils.isEmpty(providerId)) {
+                throw new IllegalArgumentException("providerId must not be null or empty");
+            }
+            mProviderId = providerId;
             return this;
         }
 
         /**
          * Builds the {@link MediaRoute2Info media route info}.
+         *
+         * @throws IllegalArgumentException if no features are added.
          */
         @NonNull
         public MediaRoute2Info build() {
+            if (mFeatures.isEmpty()) {
+                throw new IllegalArgumentException("features must not be empty!");
+            }
             return new MediaRoute2Info(this);
         }
     }
diff --git a/media/java/android/media/MediaRoute2ProviderInfo.java b/media/java/android/media/MediaRoute2ProviderInfo.java
index e2f246c..c9a2ec7 100644
--- a/media/java/android/media/MediaRoute2ProviderInfo.java
+++ b/media/java/android/media/MediaRoute2ProviderInfo.java
@@ -93,6 +93,9 @@
 
     /**
      * Gets the route for the given route id or null if no matching route exists.
+     * Please note that id should be original id.
+     *
+     * @see MediaRoute2Info#getOriginalId()
      */
     @Nullable
     public MediaRoute2Info getRoute(@NonNull String routeId) {
@@ -168,7 +171,7 @@
                 MediaRoute2Info routeWithProviderId = new MediaRoute2Info.Builder(entry.getValue())
                         .setProviderId(mUniqueId)
                         .build();
-                newRoutes.put(routeWithProviderId.getId(), routeWithProviderId);
+                newRoutes.put(routeWithProviderId.getOriginalId(), routeWithProviderId);
             }
 
             mRoutes.clear();
@@ -183,14 +186,14 @@
         public Builder addRoute(@NonNull MediaRoute2Info route) {
             Objects.requireNonNull(route, "route must not be null");
 
-            if (mRoutes.containsValue(route)) {
-                throw new IllegalArgumentException("route descriptor already added");
+            if (mRoutes.containsKey(route.getOriginalId())) {
+                throw new IllegalArgumentException("A route with the same id is already added");
             }
             if (mUniqueId != null) {
-                mRoutes.put(route.getId(),
+                mRoutes.put(route.getOriginalId(),
                         new MediaRoute2Info.Builder(route).setProviderId(mUniqueId).build());
             } else {
-                mRoutes.put(route.getId(), route);
+                mRoutes.put(route.getOriginalId(), route);
             }
             return this;
         }
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index 99bd1dc..1e8b188 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -18,6 +18,7 @@
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
+import android.annotation.CallSuper;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Service;
@@ -29,24 +30,44 @@
 import android.os.Looper;
 import android.os.Process;
 import android.os.RemoteException;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
- * @hide
+ * Base class for media route provider services.
+ * <p>
+ * The system media router service will bind to media route provider services when a
+ * {@link RouteDiscoveryPreference discovery preference} is registered via
+ * a {@link MediaRouter2 media router} by an application.
+ * </p><p>
+ * To implement your own media route provider service, extend this class and
+ * override {@link #onDiscoveryPreferenceChanged(RouteDiscoveryPreference)} to publish
+ * {@link MediaRoute2Info routes}.
+ * </p>
  */
 public abstract class MediaRoute2ProviderService extends Service {
     private static final String TAG = "MR2ProviderService";
 
     public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService";
 
+    /**
+     * The request ID to pass {@link #notifySessionCreated(RoutingSessionInfo, long)}
+     * when {@link MediaRoute2ProviderService} created a session although there was no creation
+     * request.
+     *
+     * @see #notifySessionCreated(RoutingSessionInfo, long)
+     */
+    public static final long REQUEST_ID_UNKNOWN = 0;
+
     private final Handler mHandler;
     private final Object mSessionLock = new Object();
     private final AtomicBoolean mStatePublishScheduled = new AtomicBoolean(false);
@@ -55,13 +76,20 @@
     private MediaRoute2ProviderInfo mProviderInfo;
 
     @GuardedBy("mSessionLock")
-    private ArrayMap<Integer, RouteSessionInfo> mSessionInfo = new ArrayMap<>();
+    private ArrayMap<String, RoutingSessionInfo> mSessionInfo = new ArrayMap<>();
 
     public MediaRoute2ProviderService() {
         mHandler = new Handler(Looper.getMainLooper());
     }
 
+    /**
+     * If overriding this method, call through to the super method for any unknown actions.
+     * <p>
+     * {@inheritDoc}
+     */
+    @CallSuper
     @Override
+    @Nullable
     public IBinder onBind(@NonNull Intent intent) {
         //TODO: Allow binding from media router service only?
         if (SERVICE_INTERFACE.equals(intent.getAction())) {
@@ -78,6 +106,7 @@
      *
      * @param routeId the id of the target route
      * @param request the media control request intent
+     * @hide
      */
     //TODO: Discuss what to use for request (e.g., Intent? Request class?)
     public abstract void onControlRequest(@NonNull String routeId, @NonNull Intent request);
@@ -103,112 +132,61 @@
      *
      * @param sessionId id of the session
      * @return information of the session with the given id.
-     *         null if the session is destroyed or id is not valid.
+     *         null if the session is released or ID is not valid.
      */
     @Nullable
-    public final RouteSessionInfo getSessionInfo(int sessionId) {
+    public final RoutingSessionInfo getSessionInfo(@NonNull String sessionId) {
+        if (TextUtils.isEmpty(sessionId)) {
+            throw new IllegalArgumentException("sessionId must not be empty");
+        }
         synchronized (mSessionLock) {
             return mSessionInfo.get(sessionId);
         }
     }
 
     /**
-     * Gets the list of {@link RouteSessionInfo session info} that the provider service maintains.
+     * Gets the list of {@link RoutingSessionInfo session info} that the provider service maintains.
      */
     @NonNull
-    public final List<RouteSessionInfo> getAllSessionInfo() {
+    public final List<RoutingSessionInfo> getAllSessionInfo() {
         synchronized (mSessionLock) {
             return new ArrayList<>(mSessionInfo.values());
         }
     }
 
     /**
-     * Updates the information of a session.
-     * If the session is destroyed or not created before, it will be ignored.
-     * A session will be destroyed if it has no selected route.
-     * Call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify clients of
-     * session info changes.
-     *
-     * @param sessionInfo new session information
-     * @see #notifySessionCreated(RouteSessionInfo, long)
-     */
-    public final void updateSessionInfo(@NonNull RouteSessionInfo sessionInfo) {
-        Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
-        int sessionId = sessionInfo.getSessionId();
-        if (sessionInfo.getSelectedRoutes().isEmpty()) {
-            releaseSession(sessionId);
-            return;
-        }
-
-        synchronized (mSessionLock) {
-            if (mSessionInfo.containsKey(sessionId)) {
-                mSessionInfo.put(sessionId, sessionInfo);
-                schedulePublishState();
-            } else {
-                Log.w(TAG, "Ignoring unknown session info.");
-                return;
-            }
-        }
-    }
-
-    /**
-     * Notifies the session is changed.
-     *
-     * TODO: This method is temporary, only created for tests. Remove when the alternative is ready.
-     * @hide
-     */
-    public final void notifySessionInfoChanged(@NonNull RouteSessionInfo sessionInfo) {
-        Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
-
-        int sessionId = sessionInfo.getSessionId();
-        synchronized (mSessionLock) {
-            if (mSessionInfo.containsKey(sessionId)) {
-                mSessionInfo.put(sessionId, sessionInfo);
-            } else {
-                Log.w(TAG, "Ignoring unknown session info.");
-                return;
-            }
-        }
-
-        if (mClient == null) {
-            return;
-        }
-        try {
-            mClient.notifySessionInfoChanged(sessionInfo);
-        } catch (RemoteException ex) {
-            Log.w(TAG, "Failed to notify session info changed.");
-        }
-    }
-
-    /**
-     * Notifies clients of that the session is created and ready for use. If the session can be
-     * controlled, pass a {@link Bundle} that contains how to control it.
+     * Notifies clients of that the session is created and ready for use.
+     * <p>
+     * If this session is created without any creation request, use {@link #REQUEST_ID_UNKNOWN}
+     * as the request ID.
      *
      * @param sessionInfo information of the new session.
-     *                    The {@link RouteSessionInfo#getSessionId() id} of the session must be
-     *                    unique. Pass {@code null} to reject the request or inform clients that
-     *                    session creation is failed.
-     * @param requestId id of the previous request to create this session
+     *                    The {@link RoutingSessionInfo#getId() id} of the session must be unique.
+     * @param requestId id of the previous request to create this session provided in
+     *                  {@link #onCreateSession(String, String, long, Bundle)}
+     * @see #onCreateSession(String, String, long, Bundle)
+     * @see #getSessionInfo(String)
      */
-    // TODO: fail reason?
-    // TODO: Maybe better to create notifySessionCreationFailed?
-    public final void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, long requestId) {
-        if (sessionInfo != null) {
-            int sessionId = sessionInfo.getSessionId();
-            synchronized (mSessionLock) {
-                if (mSessionInfo.containsKey(sessionId)) {
-                    Log.w(TAG, "Ignoring duplicate session id.");
-                    return;
-                }
-                mSessionInfo.put(sessionInfo.getSessionId(), sessionInfo);
+    public final void notifySessionCreated(@NonNull RoutingSessionInfo sessionInfo,
+            long requestId) {
+        Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
+
+        String sessionId = sessionInfo.getId();
+        synchronized (mSessionLock) {
+            if (mSessionInfo.containsKey(sessionId)) {
+                Log.w(TAG, "Ignoring duplicate session id.");
+                return;
             }
-            schedulePublishState();
+            mSessionInfo.put(sessionInfo.getId(), sessionInfo);
         }
 
         if (mClient == null) {
             return;
         }
         try {
+            // TODO: Calling binder calls in multiple thread may cause timing issue.
+            //       Consider to change implementations to avoid the problems.
+            //       For example, post binder calls, always send all sessions at once, etc.
             mClient.notifySessionCreated(sessionInfo, requestId);
         } catch (RemoteException ex) {
             Log.w(TAG, "Failed to notify session created.");
@@ -216,95 +194,187 @@
     }
 
     /**
-     * Releases a session with the given id.
-     * {@link #onDestroySession} is called if the session is released.
+     * Notifies clients of that the session could not be created.
      *
-     * @param sessionId id of the session to be released
-     * @see #onDestroySession(int, RouteSessionInfo)
+     * @param requestId id of the previous request to create the session provided in
+     *                  {@link #onCreateSession(String, String, long, Bundle)}.
+     * @see #onCreateSession(String, String, long, Bundle)
      */
-    public final void releaseSession(int sessionId) {
-        //TODO: notify media router service of release.
-        RouteSessionInfo sessionInfo;
-        synchronized (mSessionLock) {
-            sessionInfo = mSessionInfo.remove(sessionId);
+    public final void notifySessionCreationFailed(long requestId) {
+        if (mClient == null) {
+            return;
         }
-        if (sessionInfo != null) {
-            mHandler.sendMessage(obtainMessage(
-                    MediaRoute2ProviderService::onDestroySession, this, sessionId, sessionInfo));
-            schedulePublishState();
+        try {
+            mClient.notifySessionCreationFailed(requestId);
+        } catch (RemoteException ex) {
+            Log.w(TAG, "Failed to notify session creation failed.");
         }
     }
 
     /**
-     * Called when a session should be created.
+     * Notifies the existing session is updated. For example, when
+     * {@link RoutingSessionInfo#getSelectedRoutes() selected routes} are changed.
+     */
+    public final void notifySessionUpdated(@NonNull RoutingSessionInfo sessionInfo) {
+        Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
+
+        String sessionId = sessionInfo.getId();
+        synchronized (mSessionLock) {
+            if (mSessionInfo.containsKey(sessionId)) {
+                mSessionInfo.put(sessionId, sessionInfo);
+            } else {
+                Log.w(TAG, "Ignoring unknown session info.");
+                return;
+            }
+        }
+
+        if (mClient == null) {
+            return;
+        }
+        try {
+            mClient.notifySessionUpdated(sessionInfo);
+        } catch (RemoteException ex) {
+            Log.w(TAG, "Failed to notify session info changed.");
+        }
+    }
+
+    /**
+     * Notifies that the session is released.
+     *
+     * @param sessionId id of the released session.
+     * @see #onReleaseSession(String)
+     */
+    public final void notifySessionReleased(@NonNull String sessionId) {
+        if (TextUtils.isEmpty(sessionId)) {
+            throw new IllegalArgumentException("sessionId must not be empty");
+        }
+        RoutingSessionInfo sessionInfo;
+        synchronized (mSessionLock) {
+            sessionInfo = mSessionInfo.remove(sessionId);
+        }
+
+        if (sessionInfo == null) {
+            Log.w(TAG, "Ignoring unknown session info.");
+            return;
+        }
+
+        if (mClient == null) {
+            return;
+        }
+        try {
+            mClient.notifySessionReleased(sessionInfo);
+        } catch (RemoteException ex) {
+            Log.w(TAG, "Failed to notify session info changed.");
+        }
+    }
+
+    /**
+     * Called when the service receives a request to create a session.
+     * <p>
      * You should create and maintain your own session and notifies the client of
-     * session info. Call {@link #notifySessionCreated(RouteSessionInfo, long)}
+     * session info. Call {@link #notifySessionCreated(RoutingSessionInfo, long)}
      * with the given {@code requestId} to notify the information of a new session.
-     * If you can't create the session or want to reject the request, pass {@code null}
-     * as session info in {@link #notifySessionCreated(RouteSessionInfo, long)}
-     * with the given {@code requestId}.
+     * The created session must have the same route feature and must include the given route
+     * specified by {@code routeId}.
+     * <p>
+     * If the session can be controlled, you can optionally pass the control hints to
+     * {@link RoutingSessionInfo.Builder#setControlHints(Bundle)}. Control hints is a
+     * {@link Bundle} which contains how to control the session.
+     * <p>
+     * If you can't create the session or want to reject the request, call
+     * {@link #notifySessionCreationFailed(long)} with the given {@code requestId}.
      *
      * @param packageName the package name of the application that selected the route
      * @param routeId the id of the route initially being connected
-     * @param controlCategory the control category of the new session
      * @param requestId the id of this session creation request
+     * @param sessionHints an optional bundle of app-specific arguments sent by
+     *                     {@link MediaRouter2}, or null if none. The contents of this bundle
+     *                     may affect the result of session creation.
+     *
+     * @see RoutingSessionInfo.Builder#Builder(String, String)
+     * @see RoutingSessionInfo.Builder#addSelectedRoute(String)
+     * @see RoutingSessionInfo.Builder#setControlHints(Bundle)
      */
     public abstract void onCreateSession(@NonNull String packageName, @NonNull String routeId,
-            @NonNull String controlCategory, long requestId);
+            long requestId, @Nullable Bundle sessionHints);
 
     /**
-     * Called when a session is about to be destroyed.
-     * You can clean up your session here. This can happen by the
-     * client or provider itself.
+     * Called when the session should be released. A client of the session or system can request
+     * a session to be released.
+     * <p>
+     * After releasing the session, call {@link #notifySessionReleased(String)}
+     * with the ID of the released session.
      *
-     * @param sessionId id of the session being destroyed.
-     * @param lastSessionInfo information of the session being destroyed.
-     * @see #releaseSession(int)
+     * Note: Calling {@link #notifySessionReleased(String)} will <em>NOT</em> trigger
+     * this method to be called.
+     *
+     * @param sessionId id of the session being released.
+     * @see #notifySessionReleased(String)
+     * @see #getSessionInfo(String)
      */
-    public abstract void onDestroySession(int sessionId, @NonNull RouteSessionInfo lastSessionInfo);
+    public abstract void onReleaseSession(@NonNull String sessionId);
 
     //TODO: make a way to reject the request
     /**
      * Called when a client requests selecting a route for the session.
-     * After the route is selected, call {@link #updateSessionInfo(RouteSessionInfo)} to update
-     * session info and call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify
-     * clients of updated session info.
+     * After the route is selected, call {@link #notifySessionUpdated(RoutingSessionInfo)}
+     * to update session info.
      *
      * @param sessionId id of the session
      * @param routeId id of the route
-     * @see #updateSessionInfo(RouteSessionInfo)
      */
-    public abstract void onSelectRoute(int sessionId, @NonNull String routeId);
+    public abstract void onSelectRoute(@NonNull String sessionId, @NonNull String routeId);
 
     //TODO: make a way to reject the request
     /**
      * Called when a client requests deselecting a route from the session.
-     * After the route is deselected, call {@link #updateSessionInfo(RouteSessionInfo)} to update
-     * session info and call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify
-     * clients of updated session info.
+     * After the route is deselected, call {@link #notifySessionUpdated(RoutingSessionInfo)}
+     * to update session info.
      *
      * @param sessionId id of the session
      * @param routeId id of the route
      */
-    public abstract void onDeselectRoute(int sessionId, @NonNull String routeId);
+    public abstract void onDeselectRoute(@NonNull String sessionId, @NonNull String routeId);
 
     //TODO: make a way to reject the request
     /**
      * Called when a client requests transferring a session to a route.
-     * After the transfer is finished, call {@link #updateSessionInfo(RouteSessionInfo)} to update
-     * session info and call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify
-     * clients of updated session info.
+     * After the transfer is finished, call {@link #notifySessionUpdated(RoutingSessionInfo)}
+     * to update session info.
      *
      * @param sessionId id of the session
      * @param routeId id of the route
      */
-    public abstract void onTransferToRoute(int sessionId, @NonNull String routeId);
+    public abstract void onTransferToRoute(@NonNull String sessionId, @NonNull String routeId);
 
     /**
-     * Updates provider info and publishes routes and session info.
+     * Called when the {@link RouteDiscoveryPreference discovery preference} has changed.
+     * <p>
+     * Whenever an application registers a {@link MediaRouter2.RouteCallback callback},
+     * it also provides a discovery preference to specify features of routes that it is interested
+     * in. The media router combines all of these discovery request into a single discovery
+     * preference and notifies each provider.
+     * </p><p>
+     * The provider should examine {@link RouteDiscoveryPreference#getPreferredFeatures()
+     * preferred features} in the discovery preference to determine what kind of routes it should
+     * try to discover and whether it should perform active or passive scans. In many cases,
+     * the provider may be able to save power by not performing any scans when the request doesn't
+     * have any matching route features.
+     * </p>
+     *
+     * @param preference the new discovery preference
      */
-    public final void updateProviderInfo(@NonNull MediaRoute2ProviderInfo providerInfo) {
-        mProviderInfo = Objects.requireNonNull(providerInfo, "providerInfo must not be null");
+    // TODO: This method needs tests.
+    public void onDiscoveryPreferenceChanged(@NonNull RouteDiscoveryPreference preference) {}
+
+    /**
+     * Updates routes of the provider and notifies the system media router service.
+     */
+    public final void notifyRoutes(@NonNull Collection<MediaRoute2Info> routes) {
+        Objects.requireNonNull(routes, "routes must not be null");
+        mProviderInfo = new MediaRoute2ProviderInfo.Builder()
+                .addRoutes(routes)
+                .build();
         schedulePublishState();
     }
 
@@ -328,12 +398,12 @@
             return;
         }
 
-        List<RouteSessionInfo> sessionInfos;
+        List<RoutingSessionInfo> sessionInfos;
         synchronized (mSessionLock) {
             sessionInfos = new ArrayList<>(mSessionInfo.values());
         }
         try {
-            mClient.updateState(mProviderInfo, sessionInfos);
+            mClient.updateState(mProviderInfo);
         } catch (RemoteException ex) {
             Log.w(TAG, "Failed to send onProviderInfoUpdated");
         }
@@ -356,47 +426,64 @@
         }
 
         @Override
-        public void requestCreateSession(String packageName, String routeId,
-                String controlCategory, long requestId) {
+        public void requestCreateSession(String packageName, String routeId, long requestId,
+                @Nullable Bundle requestCreateSession) {
             if (!checkCallerisSystem()) {
                 return;
             }
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onCreateSession,
-                    MediaRoute2ProviderService.this, packageName, routeId, controlCategory,
-                    requestId));
+                    MediaRoute2ProviderService.this, packageName, routeId, requestId,
+                    requestCreateSession));
         }
+
         @Override
-        public void releaseSession(int sessionId) {
+        public void releaseSession(@NonNull String sessionId) {
             if (!checkCallerisSystem()) {
                 return;
             }
-            mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::releaseSession,
+            if (TextUtils.isEmpty(sessionId)) {
+                Log.w(TAG, "releaseSession: Ignoring empty sessionId from system service.");
+                return;
+            }
+            mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onReleaseSession,
                     MediaRoute2ProviderService.this, sessionId));
         }
 
         @Override
-        public void selectRoute(int sessionId, String routeId) {
+        public void selectRoute(@NonNull String sessionId, String routeId) {
             if (!checkCallerisSystem()) {
                 return;
             }
+            if (TextUtils.isEmpty(sessionId)) {
+                Log.w(TAG, "selectRoute: Ignoring empty sessionId from system service.");
+                return;
+            }
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSelectRoute,
                     MediaRoute2ProviderService.this, sessionId, routeId));
         }
 
         @Override
-        public void deselectRoute(int sessionId, String routeId) {
+        public void deselectRoute(@NonNull String sessionId, String routeId) {
             if (!checkCallerisSystem()) {
                 return;
             }
+            if (TextUtils.isEmpty(sessionId)) {
+                Log.w(TAG, "deselectRoute: Ignoring empty sessionId from system service.");
+                return;
+            }
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onDeselectRoute,
                     MediaRoute2ProviderService.this, sessionId, routeId));
         }
 
         @Override
-        public void transferToRoute(int sessionId, String routeId) {
+        public void transferToRoute(@NonNull String sessionId, String routeId) {
             if (!checkCallerisSystem()) {
                 return;
             }
+            if (TextUtils.isEmpty(sessionId)) {
+                Log.w(TAG, "transferToRoute: Ignoring empty sessionId from system service.");
+                return;
+            }
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onTransferToRoute,
                     MediaRoute2ProviderService.this, sessionId, routeId));
         }
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index bddfa69..18670e9 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -18,10 +18,7 @@
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
 import android.annotation.CallbackExecutor;
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -37,7 +34,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 
-import java.lang.annotation.Retention;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -48,57 +44,21 @@
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
 
 /**
- * A new Media Router
- * @hide
+ * Media Router 2 allows applications to control the routing of media channels
+ * and streams from the current device to remote speakers and devices.
  *
- * TODO: Add method names at the beginning of log messages. (e.g. changeSessionInfoOnHandler)
- *       Not only MediaRouter2, but also to service / manager / provider.
  */
+// TODO: Add method names at the beginning of log messages. (e.g. updateControllerOnHandler)
+//       Not only MediaRouter2, but also to service / manager / provider.
 public class MediaRouter2 {
-
-    /** @hide */
-    @Retention(SOURCE)
-    @IntDef(value = {
-            SELECT_REASON_UNKNOWN,
-            SELECT_REASON_USER_SELECTED,
-            SELECT_REASON_FALLBACK,
-            SELECT_REASON_SYSTEM_SELECTED})
-    public @interface SelectReason {}
-
-    /**
-     * Passed to {@link Callback#onRouteSelected(MediaRoute2Info, int, Bundle)} when the reason
-     * the route was selected is unknown.
-     */
-    public static final int SELECT_REASON_UNKNOWN = 0;
-
-    /**
-     * Passed to {@link Callback#onRouteSelected(MediaRoute2Info, int, Bundle)} when the route
-     * is selected in response to a user's request. For example, when a user has selected
-     * a different device to play media to.
-     */
-    public static final int SELECT_REASON_USER_SELECTED = 1;
-
-    /**
-     * Passed to {@link Callback#onRouteSelected(MediaRoute2Info, int, Bundle)} when the route
-     * is selected as a fallback route. For example, when Wi-Fi is disconnected, the device speaker
-     * may be selected as a fallback route.
-     */
-    public static final int SELECT_REASON_FALLBACK = 2;
-
-    /**
-     * This is passed from {@link com.android.server.media.MediaRouterService} when the route
-     * is selected in response to a request from other apps (e.g. System UI).
-     * @hide
-     */
-    public static final int SELECT_REASON_SYSTEM_SELECTED = 3;
-
     private static final String TAG = "MR2";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     private static final Object sRouterLock = new Object();
 
-    @GuardedBy("sLock")
+    @GuardedBy("sRouterLock")
     private static MediaRouter2 sInstance;
 
     private final Context mContext;
@@ -107,36 +67,40 @@
     private final CopyOnWriteArrayList<RouteCallbackRecord> mRouteCallbackRecords =
             new CopyOnWriteArrayList<>();
 
-    private final CopyOnWriteArrayList<SessionCallbackRecord> mSessionCallbackRecords =
+    private final CopyOnWriteArrayList<ControllerCallbackRecord> mControllerCallbackRecords =
             new CopyOnWriteArrayList<>();
 
-    private final CopyOnWriteArrayList<SessionCreationRequest> mSessionCreationRequests =
+    private final CopyOnWriteArrayList<ControllerCreationRequest> mControllerCreationRequests =
             new CopyOnWriteArrayList<>();
 
     private final String mPackageName;
-    @GuardedBy("sLock")
+    @GuardedBy("sRouterLock")
     final Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
 
-    @GuardedBy("sLock")
-    private List<String> mControlCategories = Collections.emptyList();
+    final RoutingController mSystemController;
+
+    @GuardedBy("sRouterLock")
+    private RouteDiscoveryPreference mDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
 
     // TODO: Make MediaRouter2 is always connected to the MediaRouterService.
-    @GuardedBy("sLock")
+    @GuardedBy("sRouterLock")
     Client2 mClient;
 
-    @GuardedBy("sLock")
-    private Map<String, RouteSessionController> mSessionControllers = new ArrayMap<>();
+    @GuardedBy("sRouterLock")
+    private Map<String, RoutingController> mRoutingControllers = new ArrayMap<>();
 
-    private AtomicInteger mSessionCreationRequestCnt = new AtomicInteger(1);
+    private AtomicInteger mControllerCreationRequestCnt = new AtomicInteger(1);
 
     final Handler mHandler;
-    @GuardedBy("sLock")
+    @GuardedBy("sRouterLock")
     private boolean mShouldUpdateRoutes;
     private volatile List<MediaRoute2Info> mFilteredRoutes = Collections.emptyList();
+    private volatile OnGetControllerHintsListener mOnGetControllerHintsListener;
 
     /**
      * Gets an instance of the media router associated with the context.
      */
+    @NonNull
     public static MediaRouter2 getInstance(@NonNull Context context) {
         Objects.requireNonNull(context, "context must not be null");
         synchronized (sRouterLock) {
@@ -152,23 +116,29 @@
         mMediaRouterService = IMediaRouterService.Stub.asInterface(
                 ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE));
         mPackageName = mContext.getPackageName();
-        //TODO: read control categories from the manifest
         mHandler = new Handler(Looper.getMainLooper());
 
         List<MediaRoute2Info> currentSystemRoutes = null;
+        RoutingSessionInfo currentSystemSessionInfo = null;
         try {
             currentSystemRoutes = mMediaRouterService.getSystemRoutes();
+            currentSystemSessionInfo = mMediaRouterService.getSystemSessionInfo();
         } catch (RemoteException ex) {
-            Log.e(TAG, "Unable to get current currentSystemRoutes", ex);
+            Log.e(TAG, "Unable to get current system's routes / session info", ex);
         }
 
         if (currentSystemRoutes == null || currentSystemRoutes.isEmpty()) {
             throw new RuntimeException("Null or empty currentSystemRoutes. Something is wrong.");
         }
 
+        if (currentSystemSessionInfo == null) {
+            throw new RuntimeException("Null currentSystemSessionInfo. Something is wrong.");
+        }
+
         for (MediaRoute2Info route : currentSystemRoutes) {
             mRoutes.put(route.getId(), route);
         }
+        mSystemController = new SystemRoutingController(currentSystemSessionInfo);
     }
 
     /**
@@ -188,24 +158,18 @@
 
     /**
      * Registers a callback to discover routes and to receive events when they change.
-     */
-    public void registerRouteCallback(@NonNull @CallbackExecutor Executor executor,
-            @NonNull RouteCallback routeCallback) {
-        registerRouteCallback(executor, routeCallback, 0);
-    }
-
-    /**
-     * Registers a callback to discover routes and to receive events when they change.
      * <p>
      * If you register the same callback twice or more, it will be ignored.
      * </p>
      */
     public void registerRouteCallback(@NonNull @CallbackExecutor Executor executor,
-            @NonNull RouteCallback routeCallback, int flags) {
+            @NonNull RouteCallback routeCallback,
+            @NonNull RouteDiscoveryPreference preference) {
         Objects.requireNonNull(executor, "executor must not be null");
         Objects.requireNonNull(routeCallback, "callback must not be null");
+        Objects.requireNonNull(preference, "preference must not be null");
 
-        RouteCallbackRecord record = new RouteCallbackRecord(executor, routeCallback, flags);
+        RouteCallbackRecord record = new RouteCallbackRecord(executor, routeCallback, preference);
         if (!mRouteCallbackRecords.addIfAbsent(record)) {
             Log.w(TAG, "Ignoring the same callback");
             return;
@@ -216,15 +180,14 @@
                 Client2 client = new Client2();
                 try {
                     mMediaRouterService.registerClient2(client, mPackageName);
-                    mMediaRouterService.setControlCategories(client, mControlCategories);
+                    updateDiscoveryRequestLocked();
+                    mMediaRouterService.setDiscoveryRequest2(client, mDiscoveryPreference);
                     mClient = client;
                 } catch (RemoteException ex) {
                     Log.e(TAG, "Unable to register media router.", ex);
                 }
             }
         }
-
-        //TODO: Update discovery request here.
     }
 
     /**
@@ -238,7 +201,7 @@
         Objects.requireNonNull(routeCallback, "callback must not be null");
 
         if (!mRouteCallbackRecords.remove(
-                new RouteCallbackRecord(null, routeCallback, 0))) {
+                new RouteCallbackRecord(null, routeCallback, null))) {
             Log.w(TAG, "Ignoring unknown callback");
             return;
         }
@@ -256,30 +219,10 @@
         }
     }
 
-    //TODO(b/139033746): Rename "Control Category" when it's finalized.
-    /**
-     * Sets the control categories of the application.
-     * Routes that support at least one of the given control categories are handled
-     * by the media router.
-     */
-    public void setControlCategories(@NonNull Collection<String> controlCategories) {
-        Objects.requireNonNull(controlCategories, "control categories must not be null");
-
-        List<String> newControlCategories = new ArrayList<>(controlCategories);
-
-        synchronized (sRouterLock) {
-            mShouldUpdateRoutes = true;
-
-            // invoke callbacks due to control categories change
-            handleControlCategoriesChangedLocked(newControlCategories);
-            if (mClient != null) {
-                try {
-                    mMediaRouterService.setControlCategories(mClient, mControlCategories);
-                } catch (RemoteException ex) {
-                    Log.e(TAG, "Unable to set control categories.", ex);
-                }
-            }
-        }
+    private void updateDiscoveryRequestLocked() {
+        mDiscoveryPreference = new RouteDiscoveryPreference.Builder(
+                mRouteCallbackRecords.stream().map(record -> record.mPreference).collect(
+                        Collectors.toList())).build();
     }
 
     /**
@@ -287,8 +230,8 @@
      * known to the media router.
      * Please note that the list can be changed before callbacks are invoked.
      *
-     * @return the list of routes that support at least one of the control categories set by
-     * the application
+     * @return the list of routes that contains at least one of the route features in discovery
+     * preferences registered by the application
      */
     @NonNull
     public List<MediaRoute2Info> getRoutes() {
@@ -298,7 +241,7 @@
 
                 List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
                 for (MediaRoute2Info route : mRoutes.values()) {
-                    if (route.supportsControlCategories(mControlCategories)) {
+                    if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
                         filteredRoutes.add(route);
                     }
                 }
@@ -309,22 +252,22 @@
     }
 
     /**
-     * Registers a callback to get updates on creations and changes of route sessions.
+     * Registers a callback to get updates on creations and changes of
+     * {@link RoutingController routing controllers}.
      * If you register the same callback twice or more, it will be ignored.
      *
      * @param executor the executor to execute the callback on
      * @param callback the callback to register
-     * @see #unregisterSessionCallback
+     * @see #unregisterControllerCallback
      */
-    @NonNull
-    public void registerSessionCallback(@CallbackExecutor Executor executor,
-            @NonNull SessionCallback callback) {
+    public void registerControllerCallback(@NonNull @CallbackExecutor Executor executor,
+            @NonNull RoutingControllerCallback callback) {
         Objects.requireNonNull(executor, "executor must not be null");
         Objects.requireNonNull(callback, "callback must not be null");
 
-        SessionCallbackRecord record = new SessionCallbackRecord(executor, callback);
-        if (!mSessionCallbackRecords.addIfAbsent(record)) {
-            Log.w(TAG, "Ignoring the same session callback");
+        ControllerCallbackRecord record = new ControllerCallbackRecord(executor, callback);
+        if (!mControllerCallbackRecords.addIfAbsent(record)) {
+            Log.w(TAG, "Ignoring the same controller callback");
             return;
         }
     }
@@ -334,43 +277,56 @@
      * If the callback has not been added or been removed already, it is ignored.
      *
      * @param callback the callback to unregister
-     * @see #registerSessionCallback
+     * @see #registerControllerCallback
      */
-    @NonNull
-    public void unregisterSessionCallback(@NonNull SessionCallback callback) {
+    public void unregisterControllerCallback(@NonNull RoutingControllerCallback callback) {
         Objects.requireNonNull(callback, "callback must not be null");
 
-        if (!mSessionCallbackRecords.remove(new SessionCallbackRecord(null, callback))) {
-            Log.w(TAG, "Ignoring unknown session callback");
+        if (!mControllerCallbackRecords.remove(new ControllerCallbackRecord(null, callback))) {
+            Log.w(TAG, "Ignoring unknown controller callback");
             return;
         }
     }
 
     /**
-     * Requests the media route provider service to create a session with the given route.
+     * Sets an {@link OnGetControllerHintsListener} to send hints when creating a
+     * {@link RoutingController}. To send the hints, listener should be set <em>BEFORE</em> calling
+     * {@link #requestCreateController(MediaRoute2Info)}.
      *
-     * @param route the route you want to create a session with.
-     * @param controlCategory the control category of the session. Should not be empty
-     *
-     * @see SessionCallback#onSessionCreated
-     * @see SessionCallback#onSessionCreationFailed
+     * @param listener A listener to send optional app-specific hints when creating a controller.
+     *                 {@code null} for unset.
      */
-    @NonNull
-    public void requestCreateSession(@NonNull MediaRoute2Info route,
-            @NonNull String controlCategory) {
+    public void setOnGetControllerHintsListener(@Nullable OnGetControllerHintsListener listener) {
+        mOnGetControllerHintsListener = listener;
+    }
+
+    /**
+     * Requests the media route provider service to create a {@link RoutingController}
+     * with the given route.
+     *
+     * @param route the route you want to create a controller with.
+     *
+     * @see RoutingControllerCallback#onControllerCreated
+     * @see RoutingControllerCallback#onControllerCreationFailed
+     */
+    public void requestCreateController(@NonNull MediaRoute2Info route) {
         Objects.requireNonNull(route, "route must not be null");
-        if (TextUtils.isEmpty(controlCategory)) {
-            throw new IllegalArgumentException("controlCategory must not be empty");
-        }
         // TODO: Check the given route exists
-        // TODO: Check the route supports the given controlCategory
 
         final int requestId;
-        requestId = mSessionCreationRequestCnt.getAndIncrement();
+        requestId = mControllerCreationRequestCnt.getAndIncrement();
 
-        SessionCreationRequest request = new SessionCreationRequest(
-                requestId, route, controlCategory);
-        mSessionCreationRequests.add(request);
+        ControllerCreationRequest request = new ControllerCreationRequest(requestId, route);
+        mControllerCreationRequests.add(request);
+
+        OnGetControllerHintsListener listener = mOnGetControllerHintsListener;
+        Bundle controllerHints = null;
+        if (listener != null) {
+            controllerHints = listener.onGetControllerHints(route);
+            if (controllerHints != null) {
+                controllerHints = new Bundle(controllerHints);
+            }
+        }
 
         Client2 client;
         synchronized (sRouterLock) {
@@ -378,10 +334,9 @@
         }
         if (client != null) {
             try {
-                mMediaRouterService.requestCreateSession(
-                        client, route, controlCategory, requestId);
+                mMediaRouterService.requestCreateSession(client, route, requestId, controllerHints);
             } catch (RemoteException ex) {
-                Log.e(TAG, "Unable to request to create session.", ex);
+                Log.e(TAG, "Unable to request to create controller.", ex);
                 mHandler.sendMessage(obtainMessage(MediaRouter2::createControllerOnHandler,
                         MediaRouter2.this, null, requestId));
             }
@@ -389,10 +344,46 @@
     }
 
     /**
+     * Gets a {@link RoutingController} which can control the routes provided by system.
+     * e.g. Phone speaker, wired headset, Bluetooth, etc.
+     * <p>
+     * Note: The system controller can't be released. Calling {@link RoutingController#release()}
+     * will be ignored.
+     * <p>
+     * This method will always return the same instance.
+     */
+    @NonNull
+    public RoutingController getSystemController() {
+        return mSystemController;
+    }
+
+    /**
+     * Gets the list of currently non-released {@link RoutingController routing controllers}.
+     * <p>
+     * Note: The list returned here will never be empty. The first element in the list is
+     * always the {@link #getSystemController() system controller}.
+     */
+    @NonNull
+    public List<RoutingController> getControllers() {
+        List<RoutingController> result = new ArrayList<>();
+        result.add(0, mSystemController);
+
+        Collection<RoutingController> controllers;
+        synchronized (sRouterLock) {
+            controllers = mRoutingControllers.values();
+            if (controllers != null) {
+                result.addAll(controllers);
+            }
+        }
+        return result;
+    }
+
+    /**
      * Sends a media control request to be performed asynchronously by the route's destination.
      *
      * @param route the route that will receive the control request
      * @param request the media control request
+     * @hide
      */
     //TODO: Discuss what to use for request (e.g., Intent? Request class?)
     //TODO: Provide a way to obtain the result
@@ -420,6 +411,7 @@
      * </p>
      *
      * @param volume The new volume value between 0 and {@link MediaRoute2Info#getVolumeMax}.
+     * @hide
      */
     public void requestSetVolume(@NonNull MediaRoute2Info route, int volume) {
         Objects.requireNonNull(route, "route must not be null");
@@ -444,6 +436,7 @@
      * </p>
      *
      * @param delta The delta to add to the current volume.
+     * @hide
      */
     public void requestUpdateVolume(@NonNull MediaRoute2Info route, int delta) {
         Objects.requireNonNull(route, "route must not be null");
@@ -461,36 +454,6 @@
         }
     }
 
-    private void handleControlCategoriesChangedLocked(List<String> newControlCategories) {
-        List<MediaRoute2Info> addedRoutes = new ArrayList<>();
-        List<MediaRoute2Info> removedRoutes = new ArrayList<>();
-
-        List<String> prevControlCategories = mControlCategories;
-        mControlCategories = newControlCategories;
-
-        for (MediaRoute2Info route : mRoutes.values()) {
-            boolean preSupported = route.supportsControlCategories(prevControlCategories);
-            boolean postSupported = route.supportsControlCategories(newControlCategories);
-            if (preSupported == postSupported) {
-                continue;
-            }
-            if (preSupported) {
-                removedRoutes.add(route);
-            } else {
-                addedRoutes.add(route);
-            }
-        }
-
-        if (removedRoutes.size() > 0) {
-            mHandler.sendMessage(obtainMessage(MediaRouter2::notifyRoutesRemoved,
-                    MediaRouter2.this, removedRoutes));
-        }
-        if (addedRoutes.size() > 0) {
-            mHandler.sendMessage(obtainMessage(MediaRouter2::notifyRoutesAdded,
-                    MediaRouter2.this, addedRoutes));
-        }
-    }
-
     void addRoutesOnHandler(List<MediaRoute2Info> routes) {
         // TODO: When onRoutesAdded is first called,
         //  1) clear mRoutes before adding the routes
@@ -500,7 +463,7 @@
         synchronized (sRouterLock) {
             for (MediaRoute2Info route : routes) {
                 mRoutes.put(route.getId(), route);
-                if (route.supportsControlCategories(mControlCategories)) {
+                if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
                     addedRoutes.add(route);
                 }
             }
@@ -516,7 +479,7 @@
         synchronized (sRouterLock) {
             for (MediaRoute2Info route : routes) {
                 mRoutes.remove(route.getId());
-                if (route.supportsControlCategories(mControlCategories)) {
+                if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
                     removedRoutes.add(route);
                 }
             }
@@ -532,7 +495,7 @@
         synchronized (sRouterLock) {
             for (MediaRoute2Info route : routes) {
                 mRoutes.put(route.getId(), route);
-                if (route.supportsControlCategories(mControlCategories)) {
+                if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
                     changedRoutes.add(route);
                 }
             }
@@ -543,15 +506,15 @@
     }
 
     /**
-     * Creates a controller and calls the {@link SessionCallback#onSessionCreated}.
-     * If session creation has failed, then it calls
-     * {@link SessionCallback#onSessionCreationFailed}.
+     * Creates a controller and calls the {@link RoutingControllerCallback#onControllerCreated}.
+     * If the controller creation has failed, then it calls
+     * {@link RoutingControllerCallback#onControllerCreationFailed}.
      * <p>
      * Pass {@code null} to sessionInfo for the failure case.
      */
-    void createControllerOnHandler(@Nullable RouteSessionInfo sessionInfo, int requestId) {
-        SessionCreationRequest matchingRequest = null;
-        for (SessionCreationRequest request : mSessionCreationRequests) {
+    void createControllerOnHandler(@Nullable RoutingSessionInfo sessionInfo, int requestId) {
+        ControllerCreationRequest matchingRequest = null;
+        for (ControllerCreationRequest request : mControllerCreationRequests) {
             if (request.mRequestId == requestId) {
                 matchingRequest = request;
                 break;
@@ -559,30 +522,21 @@
         }
 
         if (matchingRequest != null) {
-            mSessionCreationRequests.remove(matchingRequest);
+            mControllerCreationRequests.remove(matchingRequest);
 
             MediaRoute2Info requestedRoute = matchingRequest.mRoute;
-            String requestedControlCategory = matchingRequest.mControlCategory;
 
             if (sessionInfo == null) {
                 // TODO: We may need to distinguish between failure and rejection.
                 //       One way can be introducing 'reason'.
-                notifySessionCreationFailed(requestedRoute, requestedControlCategory);
-                return;
-            } else if (!TextUtils.equals(requestedControlCategory,
-                    sessionInfo.getControlCategory())) {
-                Log.w(TAG, "The session has different control category from what we requested. "
-                        + "(requested=" + requestedControlCategory
-                        + ", actual=" + sessionInfo.getControlCategory()
-                        + ")");
-                notifySessionCreationFailed(requestedRoute, requestedControlCategory);
+                notifyControllerCreationFailed(requestedRoute);
                 return;
             } else if (!sessionInfo.getSelectedRoutes().contains(requestedRoute.getId())) {
                 Log.w(TAG, "The session does not contain the requested route. "
                         + "(requestedRouteId=" + requestedRoute.getId()
                         + ", actualRoutes=" + sessionInfo.getSelectedRoutes()
                         + ")");
-                notifySessionCreationFailed(requestedRoute, requestedControlCategory);
+                notifyControllerCreationFailed(requestedRoute);
                 return;
             } else if (!TextUtils.equals(requestedRoute.getProviderId(),
                     sessionInfo.getProviderId())) {
@@ -590,130 +544,157 @@
                         + "(requested route's providerId=" + requestedRoute.getProviderId()
                         + ", actual providerId=" + sessionInfo.getProviderId()
                         + ")");
-                notifySessionCreationFailed(requestedRoute, requestedControlCategory);
+                notifyControllerCreationFailed(requestedRoute);
                 return;
             }
         }
 
         if (sessionInfo != null) {
-            RouteSessionController controller = new RouteSessionController(sessionInfo);
+            RoutingController controller = new RoutingController(sessionInfo);
             synchronized (sRouterLock) {
-                mSessionControllers.put(controller.getUniqueSessionId(), controller);
+                mRoutingControllers.put(controller.getId(), controller);
             }
-            notifySessionCreated(controller);
+            notifyControllerCreated(controller);
         }
     }
 
-    void changeSessionInfoOnHandler(RouteSessionInfo sessionInfo) {
+    void updateControllerOnHandler(RoutingSessionInfo sessionInfo) {
         if (sessionInfo == null) {
-            Log.w(TAG, "changeSessionInfoOnHandler: Ignoring null sessionInfo.");
+            Log.w(TAG, "updateControllerOnHandler: Ignoring null sessionInfo.");
             return;
         }
 
-        RouteSessionController matchingController;
+        if (sessionInfo.isSystemSession()) {
+            // The session info is sent from SystemMediaRoute2Provider.
+            RoutingController systemController = getSystemController();
+            systemController.setRoutingSessionInfo(sessionInfo);
+            notifyControllerUpdated(systemController);
+            return;
+        }
+
+        RoutingController matchingController;
         synchronized (sRouterLock) {
-            matchingController = mSessionControllers.get(sessionInfo.getUniqueSessionId());
+            matchingController = mRoutingControllers.get(sessionInfo.getId());
         }
 
         if (matchingController == null) {
-            Log.w(TAG, "changeSessionInfoOnHandler: Matching controller not found. uniqueSessionId="
-                    + sessionInfo.getUniqueSessionId());
+            Log.w(TAG, "updateControllerOnHandler: Matching controller not found. uniqueSessionId="
+                    + sessionInfo.getId());
             return;
         }
 
-        RouteSessionInfo oldInfo = matchingController.getRouteSessionInfo();
+        RoutingSessionInfo oldInfo = matchingController.getRoutingSessionInfo();
         if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) {
-            Log.w(TAG, "changeSessionInfoOnHandler: Provider IDs are not matched. old="
+            Log.w(TAG, "updateControllerOnHandler: Provider IDs are not matched. old="
                     + oldInfo.getProviderId() + ", new=" + sessionInfo.getProviderId());
             return;
         }
 
-        matchingController.setRouteSessionInfo(sessionInfo);
-        notifySessionInfoChanged(matchingController, oldInfo, sessionInfo);
+        matchingController.setRoutingSessionInfo(sessionInfo);
+        notifyControllerUpdated(matchingController);
     }
 
-    void releaseControllerOnHandler(RouteSessionInfo sessionInfo) {
+    void releaseControllerOnHandler(RoutingSessionInfo sessionInfo) {
         if (sessionInfo == null) {
             Log.w(TAG, "releaseControllerOnHandler: Ignoring null sessionInfo.");
             return;
         }
 
-        final String uniqueSessionId = sessionInfo.getUniqueSessionId();
-        RouteSessionController matchingController;
+        final String uniqueSessionId = sessionInfo.getId();
+        RoutingController matchingController;
         synchronized (sRouterLock) {
-            matchingController = mSessionControllers.get(uniqueSessionId);
+            matchingController = mRoutingControllers.get(uniqueSessionId);
         }
 
         if (matchingController == null) {
             if (DEBUG) {
                 Log.d(TAG, "releaseControllerOnHandler: Matching controller not found. "
-                        + "uniqueSessionId=" + sessionInfo.getUniqueSessionId());
+                        + "uniqueSessionId=" + sessionInfo.getId());
             }
             return;
         }
 
-        RouteSessionInfo oldInfo = matchingController.getRouteSessionInfo();
+        RoutingSessionInfo oldInfo = matchingController.getRoutingSessionInfo();
         if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) {
             Log.w(TAG, "releaseControllerOnHandler: Provider IDs are not matched. old="
                     + oldInfo.getProviderId() + ", new=" + sessionInfo.getProviderId());
             return;
         }
 
+        boolean removed;
         synchronized (sRouterLock) {
-            mSessionControllers.remove(uniqueSessionId, matchingController);
+            removed = mRoutingControllers.remove(uniqueSessionId, matchingController);
         }
-        matchingController.release();
-        notifyControllerReleased(matchingController);
+
+        if (removed) {
+            matchingController.release();
+            notifyControllerReleased(matchingController);
+        }
+    }
+
+    private List<MediaRoute2Info> filterRoutes(List<MediaRoute2Info> routes,
+            RouteDiscoveryPreference discoveryRequest) {
+        return routes.stream()
+                .filter(
+                        route -> route.hasAnyFeatures(discoveryRequest.getPreferredFeatures()))
+                .collect(Collectors.toList());
     }
 
     private void notifyRoutesAdded(List<MediaRoute2Info> routes) {
         for (RouteCallbackRecord record: mRouteCallbackRecords) {
-            record.mExecutor.execute(
-                    () -> record.mRouteCallback.onRoutesAdded(routes));
+            List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mPreference);
+            if (!filteredRoutes.isEmpty()) {
+                record.mExecutor.execute(
+                        () -> record.mRouteCallback.onRoutesAdded(filteredRoutes));
+            }
         }
     }
 
     private void notifyRoutesRemoved(List<MediaRoute2Info> routes) {
         for (RouteCallbackRecord record: mRouteCallbackRecords) {
-            record.mExecutor.execute(
-                    () -> record.mRouteCallback.onRoutesRemoved(routes));
+            List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mPreference);
+            if (!filteredRoutes.isEmpty()) {
+                record.mExecutor.execute(
+                        () -> record.mRouteCallback.onRoutesRemoved(filteredRoutes));
+            }
         }
     }
 
     private void notifyRoutesChanged(List<MediaRoute2Info> routes) {
         for (RouteCallbackRecord record: mRouteCallbackRecords) {
-            record.mExecutor.execute(
-                    () -> record.mRouteCallback.onRoutesChanged(routes));
+            List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mPreference);
+            if (!filteredRoutes.isEmpty()) {
+                record.mExecutor.execute(
+                        () -> record.mRouteCallback.onRoutesChanged(filteredRoutes));
+            }
         }
     }
 
-    private void notifySessionCreated(RouteSessionController controller) {
-        for (SessionCallbackRecord record: mSessionCallbackRecords) {
+    private void notifyControllerCreated(RoutingController controller) {
+        for (ControllerCallbackRecord record: mControllerCallbackRecords) {
             record.mExecutor.execute(
-                    () -> record.mSessionCallback.onSessionCreated(controller));
+                    () -> record.mControllerCallback.onControllerCreated(controller));
         }
     }
 
-    private void notifySessionCreationFailed(MediaRoute2Info route, String controlCategory) {
-        for (SessionCallbackRecord record: mSessionCallbackRecords) {
+    private void notifyControllerCreationFailed(MediaRoute2Info route) {
+        for (ControllerCallbackRecord record: mControllerCallbackRecords) {
             record.mExecutor.execute(
-                    () -> record.mSessionCallback.onSessionCreationFailed(route, controlCategory));
+                    () -> record.mControllerCallback.onControllerCreationFailed(route));
         }
     }
 
-    private void notifySessionInfoChanged(RouteSessionController controller,
-            RouteSessionInfo oldInfo, RouteSessionInfo newInfo) {
-        for (SessionCallbackRecord record: mSessionCallbackRecords) {
+    private void notifyControllerUpdated(RoutingController controller) {
+        for (ControllerCallbackRecord record: mControllerCallbackRecords) {
             record.mExecutor.execute(
-                    () -> record.mSessionCallback.onSessionInfoChanged(
-                            controller, oldInfo, newInfo));
+                    () -> record.mControllerCallback.onControllerUpdated(controller));
         }
     }
 
-    private void notifyControllerReleased(RouteSessionController controller) {
-        for (SessionCallbackRecord record: mSessionCallbackRecords) {
+    private void notifyControllerReleased(RoutingController controller) {
+        for (ControllerCallbackRecord record: mControllerCallbackRecords) {
             record.mExecutor.execute(
-                    () -> record.mSessionCallback.onSessionReleased(controller));
+                    () -> record.mControllerCallback.onControllerReleased(controller));
         }
     }
 
@@ -740,117 +721,110 @@
          * Called when routes are changed. For example, it is called when the route's name
          * or volume have been changed.
          *
-         * TODO: Write here what the developers should do when this method is called.
-         * How they can find the exact point how a route is changed?
-         * It can be a volume, name, client package name, ....
-         *
          * @param routes the list of routes that have been changed. It's never empty.
          */
         public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {}
     }
 
     /**
-     * Callback for receiving a result of session creation and session updates.
+     * Callback for receiving a result of {@link RoutingController} creation and updates.
      */
-    public static class SessionCallback {
+    public static class RoutingControllerCallback {
         /**
-         * Called when the route session is created by the route provider.
+         * Called when the {@link RoutingController} is created.
+         * A {@link RoutingController} can be created by calling
+         * {@link #requestCreateController(MediaRoute2Info)}, or by the system.
          *
-         * @param controller the controller to control the created session
+         * @param controller the controller to control routes
          */
-        public void onSessionCreated(@NonNull RouteSessionController controller) {}
+        public void onControllerCreated(@NonNull RoutingController controller) {}
 
         /**
-         * Called when the session creation request failed.
+         * Called when the controller creation request failed.
          *
-         * @param requestedRoute the route info which was used for the request
-         * @param requestedControlCategory the control category which was used for the request
+         * @param requestedRoute the route info which was used for the creation request
          */
-        public void onSessionCreationFailed(@NonNull MediaRoute2Info requestedRoute,
-                @NonNull String requestedControlCategory) {}
+        public void onControllerCreationFailed(@NonNull MediaRoute2Info requestedRoute) {}
 
         /**
-         * Called when the session info has changed.
+         * Called when the controller is updated.
          *
-         * @param oldInfo the session info before the session changed.
-         * @prarm newInfo the changed session info
-         *
-         * TODO: (Discussion) Do we really need newInfo? The controller has the newInfo.
-         *       However. there can be timing issue if there is no newInfo.
+         * @param controller the updated controller. Can be the system controller.
+         * @see #getSystemController()
          */
-        public void onSessionInfoChanged(@NonNull RouteSessionController controller,
-                @NonNull RouteSessionInfo oldInfo,
-                @NonNull RouteSessionInfo newInfo) {}
+        public void onControllerUpdated(@NonNull RoutingController controller) {}
 
         /**
-         * Called when the session is released by {@link MediaRoute2ProviderService}.
-         * Before this method is called, the controller would be released by the system,
-         * which means the {@link RouteSessionController#isReleased()} will always return true
+         * Called when a routing controller is released. It can be released in two cases:
+         * <ul>
+         *     <li>When {@link RoutingController#release()} is called.</li>
+         *     <li>When the remote session in the provider is destroyed.</li>
+         * </ul>
+         * {@link RoutingController#isReleased()} will always return {@code true}
          * for the {@code controller} here.
-         * <p>
-         * Note: Calling {@link RouteSessionController#release()} will <em>NOT</em> trigger
-         * this method to be called.
          *
-         * TODO: Add tests for checking whether this method is called.
-         * TODO: When service process dies, this should be called.
-         *
-         * @see RouteSessionController#isReleased()
+         * @see RoutingController#release()
+         * @see RoutingController#isReleased()
          */
-        public void onSessionReleased(@NonNull RouteSessionController controller) {}
+        // TODO: Add tests for checking whether this method is called.
+        // TODO: When service process dies, this should be called.
+        public void onControllerReleased(@NonNull RoutingController controller) {}
     }
 
     /**
-     * A class to control media route session in media route provider.
-     * For example, selecting/deselcting/transferring routes to session can be done through this
-     * class. Instances are created by {@link MediaRouter2}.
-     *
-     * TODO: Need to add toString()
+     * A listener interface to send an optional app-specific hints when creating the
+     * {@link RoutingController}.
      */
-    public final class RouteSessionController {
+    public interface OnGetControllerHintsListener {
+        /**
+         * Called when the {@link MediaRouter2} is about to request
+         * the media route provider service to create a controller with the given route.
+         * The {@link Bundle} returned here will be sent to media route provider service as a hint.
+         * <p>
+         * To send hints when creating the controller, set the listener before calling
+         * {@link #requestCreateController(MediaRoute2Info)}. The method will be called
+         * on the same thread which calls {@link #requestCreateController(MediaRoute2Info)}.
+         *
+         * @param route The route to create controller with
+         * @return An optional bundle of app-specific arguments to send to the provider,
+         *         or null if none. The contents of this bundle may affect the result of
+         *         controller creation.
+         * @see MediaRoute2ProviderService#onCreateSession(String, String, long, Bundle)
+         */
+        @Nullable
+        Bundle onGetControllerHints(@NonNull MediaRoute2Info route);
+    }
+
+    /**
+     * A class to control media routing session in media route provider.
+     * For example, selecting/deselcting/transferring routes to session can be done through this
+     * class. Instances are created by {@link #requestCreateController(MediaRoute2Info)}.
+     */
+    public class RoutingController {
         private final Object mControllerLock = new Object();
 
-        @GuardedBy("mLock")
-        private RouteSessionInfo mSessionInfo;
+        @GuardedBy("mControllerLock")
+        private RoutingSessionInfo mSessionInfo;
 
-        @GuardedBy("mLock")
+        @GuardedBy("mControllerLock")
         private volatile boolean mIsReleased;
 
-        RouteSessionController(@NonNull RouteSessionInfo sessionInfo) {
+        RoutingController(@NonNull RoutingSessionInfo sessionInfo) {
             mSessionInfo = sessionInfo;
         }
 
         /**
-         * @return the ID of the session
-         */
-        public int getSessionId() {
-            synchronized (mControllerLock) {
-                return mSessionInfo.getSessionId();
-            }
-        }
-
-        /**
-         * @return the unique ID of the session
-         * @hide
+         * @return the ID of the controller
          */
         @NonNull
-        public String getUniqueSessionId() {
+        public String getId() {
             synchronized (mControllerLock) {
-                return mSessionInfo.getUniqueSessionId();
+                return mSessionInfo.getId();
             }
         }
 
         /**
-         * @return the category of routes that the session includes.
-         */
-        @NonNull
-        public String getControlCategory() {
-            synchronized (mControllerLock) {
-                return mSessionInfo.getControlCategory();
-            }
-        }
-
-        /**
-         * @return the control hints used to control route session if available.
+         * @return the control hints used to control routing session if available.
          */
         @Nullable
         public Bundle getControlHints() {
@@ -900,7 +874,7 @@
         }
 
         /**
-         * Returns true if the session is released, false otherwise.
+         * Returns true if this controller is released, false otherwise.
          * If it is released, then all other getters from this instance may return invalid values.
          * Also, any operations to this instance will be ignored once released.
          *
@@ -923,7 +897,7 @@
          *
          * @see #getSelectedRoutes()
          * @see #getSelectableRoutes()
-         * @see SessionCallback#onSessionInfoChanged
+         * @see RoutingControllerCallback#onControllerUpdated
          */
         public void selectRoute(@NonNull MediaRoute2Info route) {
             Objects.requireNonNull(route, "route must not be null");
@@ -952,7 +926,7 @@
             }
             if (client != null) {
                 try {
-                    mMediaRouterService.selectRoute(client, getUniqueSessionId(), route);
+                    mMediaRouterService.selectRoute(client, getId(), route);
                 } catch (RemoteException ex) {
                     Log.e(TAG, "Unable to select route for session.", ex);
                 }
@@ -970,7 +944,7 @@
          *
          * @see #getSelectedRoutes()
          * @see #getDeselectableRoutes()
-         * @see SessionCallback#onSessionInfoChanged
+         * @see RoutingControllerCallback#onControllerUpdated
          */
         public void deselectRoute(@NonNull MediaRoute2Info route) {
             Objects.requireNonNull(route, "route must not be null");
@@ -999,7 +973,7 @@
             }
             if (client != null) {
                 try {
-                    mMediaRouterService.deselectRoute(client, getUniqueSessionId(), route);
+                    mMediaRouterService.deselectRoute(client, getId(), route);
                 } catch (RemoteException ex) {
                     Log.e(TAG, "Unable to remove route from session.", ex);
                 }
@@ -1017,7 +991,7 @@
          *
          * @see #getSelectedRoutes()
          * @see #getTransferrableRoutes()
-         * @see SessionCallback#onSessionInfoChanged
+         * @see RoutingControllerCallback#onControllerUpdated
          */
         public void transferToRoute(@NonNull MediaRoute2Info route) {
             Objects.requireNonNull(route, "route must not be null");
@@ -1047,7 +1021,7 @@
             }
             if (client != null) {
                 try {
-                    mMediaRouterService.transferToRoute(client, getUniqueSessionId(), route);
+                    mMediaRouterService.transferToRoute(client, getId(), route);
                 } catch (RemoteException ex) {
                     Log.e(TAG, "Unable to transfer to route for session.", ex);
                 }
@@ -1058,9 +1032,8 @@
          * Release this controller and corresponding session.
          * Any operations on this controller after calling this method will be ignored.
          * The devices that are playing media will stop playing it.
-         *
-         * TODO: Add tests using {@link MediaRouter2Manager#getActiveSessions()}.
          */
+        // TODO: Add tests using {@link MediaRouter2Manager#getActiveSessions()}.
         public void release() {
             synchronized (mControllerLock) {
                 if (mIsReleased) {
@@ -1071,31 +1044,68 @@
             }
 
             Client2 client;
+            boolean removed;
             synchronized (sRouterLock) {
-                mSessionControllers.remove(getUniqueSessionId(), this);
+                removed = mRoutingControllers.remove(getId(), this);
                 client = mClient;
             }
+
+            if (removed) {
+                mHandler.post(() -> notifyControllerReleased(RoutingController.this));
+            }
+
             if (client != null) {
                 try {
-                    mMediaRouterService.releaseSession(client, getUniqueSessionId());
+                    mMediaRouterService.releaseSession(client, getId());
                 } catch (RemoteException ex) {
                     Log.e(TAG, "Unable to notify of controller release", ex);
                 }
             }
         }
 
+        @Override
+        public String toString() {
+            // To prevent logging spam, we only print the ID of each route.
+            List<String> selectedRoutes = getSelectedRoutes().stream()
+                    .map(MediaRoute2Info::getId).collect(Collectors.toList());
+            List<String> selectableRoutes = getSelectableRoutes().stream()
+                    .map(MediaRoute2Info::getId).collect(Collectors.toList());
+            List<String> deselectableRoutes = getDeselectableRoutes().stream()
+                    .map(MediaRoute2Info::getId).collect(Collectors.toList());
+            List<String> transferrableRoutes = getTransferrableRoutes().stream()
+                    .map(MediaRoute2Info::getId).collect(Collectors.toList());
+
+            StringBuilder result = new StringBuilder()
+                    .append("RoutingController{ ")
+                    .append("id=").append(getId())
+                    .append(", selectedRoutes={")
+                    .append(selectedRoutes)
+                    .append("}")
+                    .append(", selectableRoutes={")
+                    .append(selectableRoutes)
+                    .append("}")
+                    .append(", deselectableRoutes={")
+                    .append(deselectableRoutes)
+                    .append("}")
+                    .append(", transferrableRoutes={")
+                    .append(transferrableRoutes)
+                    .append("}")
+                    .append(" }");
+            return result.toString();
+        }
+
         /**
          * TODO: Change this to package private. (Hidden for debugging purposes)
          * @hide
          */
         @NonNull
-        public RouteSessionInfo getRouteSessionInfo() {
+        public RoutingSessionInfo getRoutingSessionInfo() {
             synchronized (mControllerLock) {
                 return mSessionInfo;
             }
         }
 
-        void setRouteSessionInfo(@NonNull RouteSessionInfo info) {
+        void setRoutingSessionInfo(@NonNull RoutingSessionInfo info) {
             synchronized (mControllerLock) {
                 mSessionInfo = info;
             }
@@ -1106,9 +1116,9 @@
         private List<MediaRoute2Info> getRoutesWithIdsLocked(List<String> routeIds) {
             List<MediaRoute2Info> routes = new ArrayList<>();
             synchronized (sRouterLock) {
+                // TODO: Maybe able to change using Collection.stream()?
                 for (String routeId : routeIds) {
-                    MediaRoute2Info route = mRoutes.get(
-                            MediaRoute2Info.toUniqueId(mSessionInfo.mProviderId, routeId));
+                    MediaRoute2Info route = mRoutes.get(routeId);
                     if (route != null) {
                         routes.add(route);
                     }
@@ -1118,16 +1128,33 @@
         }
     }
 
+    class SystemRoutingController extends RoutingController {
+        SystemRoutingController(@NonNull RoutingSessionInfo sessionInfo) {
+            super(sessionInfo);
+        }
+
+        @Override
+        public void release() {
+            // Do nothing. SystemRoutingController will never be released
+        }
+
+        @Override
+        public boolean isReleased() {
+            // SystemRoutingController will never be released
+            return false;
+        }
+    }
+
     final class RouteCallbackRecord {
         public final Executor mExecutor;
         public final RouteCallback mRouteCallback;
-        public final int mFlags;
+        public final RouteDiscoveryPreference mPreference;
 
         RouteCallbackRecord(@Nullable Executor executor, @NonNull RouteCallback routeCallback,
-                int flags) {
+                @Nullable RouteDiscoveryPreference preference) {
             mRouteCallback = routeCallback;
             mExecutor = executor;
-            mFlags = flags;
+            mPreference = preference;
         }
 
         @Override
@@ -1147,13 +1174,13 @@
         }
     }
 
-    final class SessionCallbackRecord {
+    final class ControllerCallbackRecord {
         public final Executor mExecutor;
-        public final SessionCallback mSessionCallback;
+        public final RoutingControllerCallback mControllerCallback;
 
-        SessionCallbackRecord(@NonNull Executor executor,
-                @NonNull SessionCallback sessionCallback) {
-            mSessionCallback = sessionCallback;
+        ControllerCallbackRecord(@NonNull Executor executor,
+                @NonNull RoutingControllerCallback controllerCallback) {
+            mControllerCallback = controllerCallback;
             mExecutor = executor;
         }
 
@@ -1162,27 +1189,25 @@
             if (this == obj) {
                 return true;
             }
-            if (!(obj instanceof SessionCallbackRecord)) {
+            if (!(obj instanceof ControllerCallbackRecord)) {
                 return false;
             }
-            return mSessionCallback == ((SessionCallbackRecord) obj).mSessionCallback;
+            return mControllerCallback
+                    == ((ControllerCallbackRecord) obj).mControllerCallback;
         }
 
         @Override
         public int hashCode() {
-            return mSessionCallback.hashCode();
+            return mControllerCallback.hashCode();
         }
     }
 
-    final class SessionCreationRequest {
+    final class ControllerCreationRequest {
         public final MediaRoute2Info mRoute;
-        public final String mControlCategory;
         public final int mRequestId;
 
-        SessionCreationRequest(int requestId, @NonNull MediaRoute2Info route,
-                @NonNull String controlCategory) {
+        ControllerCreationRequest(int requestId, @NonNull MediaRoute2Info route) {
             mRoute = route;
-            mControlCategory = controlCategory;
             mRequestId = requestId;
         }
     }
@@ -1210,19 +1235,19 @@
         }
 
         @Override
-        public void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, int requestId) {
+        public void notifySessionCreated(@Nullable RoutingSessionInfo sessionInfo, int requestId) {
             mHandler.sendMessage(obtainMessage(MediaRouter2::createControllerOnHandler,
                     MediaRouter2.this, sessionInfo, requestId));
         }
 
         @Override
-        public void notifySessionInfoChanged(@Nullable RouteSessionInfo sessionInfo) {
-            mHandler.sendMessage(obtainMessage(MediaRouter2::changeSessionInfoOnHandler,
+        public void notifySessionInfoChanged(@Nullable RoutingSessionInfo sessionInfo) {
+            mHandler.sendMessage(obtainMessage(MediaRouter2::updateControllerOnHandler,
                     MediaRouter2.this, sessionInfo));
         }
 
         @Override
-        public void notifySessionReleased(RouteSessionInfo sessionInfo) {
+        public void notifySessionReleased(RoutingSessionInfo sessionInfo) {
             mHandler.sendMessage(obtainMessage(MediaRouter2::releaseControllerOnHandler,
                     MediaRouter2.this, sessionInfo));
         }
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 3cbbea1..61e2f77 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -22,6 +22,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -65,7 +66,7 @@
     @GuardedBy("mRoutesLock")
     private final Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
     @NonNull
-    final ConcurrentMap<String, List<String>> mControlCategoryMap = new ConcurrentHashMap<>();
+    final ConcurrentMap<String, List<String>> mPreferredFeaturesMap = new ConcurrentHashMap<>();
 
     private AtomicInteger mNextRequestId = new AtomicInteger(1);
 
@@ -131,7 +132,7 @@
         Objects.requireNonNull(callback, "callback must not be null");
 
         if (!mCallbackRecords.remove(new CallbackRecord(null, callback))) {
-            Log.w(TAG, "Ignore removing unknown callback. " + callback);
+            Log.w(TAG, "unregisterCallback: Ignore unknown callback. " + callback);
             return;
         }
 
@@ -144,7 +145,7 @@
                 }
                 //TODO: clear mRoutes?
                 mClient = null;
-                mControlCategoryMap.clear();
+                mPreferredFeaturesMap.clear();
             }
         }
     }
@@ -160,14 +161,14 @@
     public List<MediaRoute2Info> getAvailableRoutes(@NonNull String packageName) {
         Objects.requireNonNull(packageName, "packageName must not be null");
 
-        List<String> controlCategories = mControlCategoryMap.get(packageName);
-        if (controlCategories == null) {
+        List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
+        if (preferredFeatures == null) {
             return Collections.emptyList();
         }
         List<MediaRoute2Info> routes = new ArrayList<>();
         synchronized (mRoutesLock) {
             for (MediaRoute2Info route : mRoutes.values()) {
-                if (route.supportsControlCategories(controlCategories)) {
+                if (route.hasAnyFeatures(preferredFeatures)) {
                     routes.add(route);
                 }
             }
@@ -175,8 +176,36 @@
         return routes;
     }
 
+    /**
+     * Gets routing controllers of an application with the given package name.
+     * The first element of the returned list is the system routing controller.
+     *
+     * @see MediaRouter2#getSystemController()
+     */
     @NonNull
-    public List<RouteSessionInfo> getActiveSessions() {
+    public List<RoutingController> getRoutingControllers(@NonNull String packageName) {
+        Objects.requireNonNull(packageName, "packageName must not be null");
+
+        List<RoutingController> controllers = new ArrayList<>();
+
+        for (RoutingSessionInfo sessionInfo : getActiveSessions()) {
+            if (sessionInfo.isSystemSession()
+                    || TextUtils.equals(sessionInfo.getClientPackageName(), packageName)) {
+                controllers.add(new RoutingController(sessionInfo));
+            }
+        }
+        return controllers;
+    }
+
+    /**
+     * Gets the list of all active routing sessions.
+     * The first element of the list is the system routing session containing
+     * phone speakers, wired headset, Bluetooth devices.
+     * The system routing session is shared by apps such that controlling it will affect
+     * all apps.
+     */
+    @NonNull
+    public List<RoutingSessionInfo> getActiveSessions() {
         Client client;
         synchronized (sLock) {
             client = mClient;
@@ -192,23 +221,7 @@
     }
 
     /**
-     * Gets the list of routes that are actively used by {@link MediaRouter2}.
-     */
-    @NonNull
-    public List<MediaRoute2Info> getActiveRoutes() {
-        List<MediaRoute2Info> routes = new ArrayList<>();
-        synchronized (mRoutesLock) {
-            for (MediaRoute2Info route : mRoutes.values()) {
-                if (!TextUtils.isEmpty(route.getClientPackageName())) {
-                    routes.add(route);
-                }
-            }
-        }
-        return routes;
-    }
-
-    /**
-     * Gets the list of discovered routes
+     * Gets the list of all discovered routes
      */
     @NonNull
     public List<MediaRoute2Info> getAllRoutes() {
@@ -222,6 +235,10 @@
     /**
      * Selects media route for the specified package name.
      *
+     * If the given route is {@link RoutingController#getTransferrableRoutes() a transferrable
+     * route} of a routing session of the application, the session will be transferred to
+     * the route. If not, a new routing session will be created.
+     *
      * @param packageName the package name of the application that should change it's media route
      * @param route the route to be selected.
      */
@@ -229,6 +246,13 @@
         Objects.requireNonNull(packageName, "packageName must not be null");
         Objects.requireNonNull(route, "route must not be null");
 
+        for (RoutingController controller : getRoutingControllers(packageName)) {
+            if (controller.getSessionInfo().getTransferrableRoutes().contains(route.getId())) {
+                controller.transferToRoute(route);
+                return;
+            }
+        }
+
         Client client;
         synchronized (sLock) {
             client = mClient;
@@ -238,6 +262,7 @@
                 int requestId = mNextRequestId.getAndIncrement();
                 mMediaRouterService.requestCreateClientSession(
                         client, packageName, route, requestId);
+                //TODO: release the previous session?
             } catch (RemoteException ex) {
                 Log.e(TAG, "Unable to select media route", ex);
             }
@@ -245,7 +270,7 @@
     }
 
     /**
-     * Requests a volume change for the route asynchronously.
+     * Requests a volume change for a route asynchronously.
      * <p>
      * It may have no effect if the route is currently not selected.
      * </p>
@@ -346,21 +371,297 @@
         }
     }
 
-    void notifyRouteSelected(String packageName, MediaRoute2Info route) {
+    void notifySessionCreated(RoutingSessionInfo sessionInfo) {
         for (CallbackRecord record : mCallbackRecords) {
-            record.mExecutor.execute(() -> record.mCallback.onRouteSelected(packageName, route));
+            record.mExecutor.execute(() -> record.mCallback.onSessionCreated(
+                    new RoutingController(sessionInfo)));
         }
     }
 
-    void updateControlCategories(String packageName, List<String> categories) {
-        List<String> prevCategories = mControlCategoryMap.put(packageName, categories);
-        if ((prevCategories == null && categories.size() == 0)
-                || Objects.equals(categories, prevCategories)) {
+    void notifySessionInfosChanged() {
+        for (CallbackRecord record : mCallbackRecords) {
+            record.mExecutor.execute(() -> record.mCallback.onSessionsUpdated());
+        }
+    }
+
+    void updatePreferredFeatures(String packageName, List<String> preferredFeatures) {
+        List<String> prevFeatures = mPreferredFeaturesMap.put(packageName, preferredFeatures);
+        if ((prevFeatures == null && preferredFeatures.size() == 0)
+                || Objects.equals(preferredFeatures, prevFeatures)) {
             return;
         }
         for (CallbackRecord record : mCallbackRecords) {
-            record.mExecutor.execute(
-                    () -> record.mCallback.onControlCategoriesChanged(packageName, categories));
+            record.mExecutor.execute(() -> record.mCallback
+                    .onControlCategoriesChanged(packageName, preferredFeatures));
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public RoutingController getControllerForSession(@NonNull RoutingSessionInfo sessionInfo) {
+        return new RoutingController(sessionInfo);
+    }
+
+    /**
+     * A class to control media routing session in media route provider.
+     * With routing controller, an application can select a route into the session or deselect
+     * a route in the session.
+     */
+    public final class RoutingController {
+        private final Object mControllerLock = new Object();
+        @GuardedBy("mControllerLock")
+        private RoutingSessionInfo mSessionInfo;
+
+        RoutingController(@NonNull RoutingSessionInfo sessionInfo) {
+            mSessionInfo = sessionInfo;
+        }
+
+        /**
+         * Gets the ID of the session
+         */
+        @NonNull
+        public String getSessionId() {
+            synchronized (mControllerLock) {
+                return mSessionInfo.getId();
+            }
+        }
+
+        /**
+         * Gets the client package name of the session
+         */
+        @NonNull
+        public String getClientPackageName() {
+            synchronized (mControllerLock) {
+                return mSessionInfo.getClientPackageName();
+            }
+        }
+
+        /**
+         * @return the control hints used to control route session if available.
+         */
+        @Nullable
+        public Bundle getControlHints() {
+            synchronized (mControllerLock) {
+                return mSessionInfo.getControlHints();
+            }
+        }
+
+        /**
+         * @return the unmodifiable list of currently selected routes
+         */
+        @NonNull
+        public List<MediaRoute2Info> getSelectedRoutes() {
+            List<String> routeIds;
+            synchronized (mControllerLock) {
+                routeIds = mSessionInfo.getSelectedRoutes();
+            }
+            return getRoutesWithIds(routeIds);
+        }
+
+        /**
+         * @return the unmodifiable list of selectable routes for the session.
+         */
+        @NonNull
+        public List<MediaRoute2Info> getSelectableRoutes() {
+            List<String> routeIds;
+            synchronized (mControllerLock) {
+                routeIds = mSessionInfo.getSelectableRoutes();
+            }
+            return getRoutesWithIds(routeIds);
+        }
+
+        /**
+         * @return the unmodifiable list of deselectable routes for the session.
+         */
+        @NonNull
+        public List<MediaRoute2Info> getDeselectableRoutes() {
+            List<String> routeIds;
+            synchronized (mControllerLock) {
+                routeIds = mSessionInfo.getDeselectableRoutes();
+            }
+            return getRoutesWithIds(routeIds);
+        }
+
+        /**
+         * @return the unmodifiable list of transferrable routes for the session.
+         */
+        @NonNull
+        public List<MediaRoute2Info> getTransferrableRoutes() {
+            List<String> routeIds;
+            synchronized (mControllerLock) {
+                routeIds = mSessionInfo.getTransferrableRoutes();
+            }
+            return getRoutesWithIds(routeIds);
+        }
+
+        /**
+         * Selects a route for the remote session. The given route must satisfy all of the
+         * following conditions:
+         * <ul>
+         * <li>ID should not be included in {@link #getSelectedRoutes()}</li>
+         * <li>ID should be included in {@link #getSelectableRoutes()}</li>
+         * </ul>
+         * If the route doesn't meet any of above conditions, it will be ignored.
+         *
+         * @see #getSelectedRoutes()
+         * @see #getSelectableRoutes()
+         */
+        public void selectRoute(@NonNull MediaRoute2Info route) {
+            Objects.requireNonNull(route, "route must not be null");
+
+            RoutingSessionInfo sessionInfo;
+            synchronized (mControllerLock) {
+                sessionInfo = mSessionInfo;
+            }
+            if (sessionInfo.getSelectedRoutes().contains(route.getId())) {
+                Log.w(TAG, "Ignoring selecting a route that is already selected. route=" + route);
+                return;
+            }
+
+            if (!sessionInfo.getSelectableRoutes().contains(route.getId())) {
+                Log.w(TAG, "Ignoring selecting a non-selectable route=" + route);
+                return;
+            }
+
+            Client client;
+            synchronized (sLock) {
+                client = mClient;
+            }
+            if (client != null) {
+                try {
+                    mMediaRouterService.selectClientRoute(mClient, getSessionId(), route);
+                } catch (RemoteException ex) {
+                    Log.e(TAG, "Unable to select route for session.", ex);
+                }
+            }
+        }
+
+        /**
+         * Deselects a route from the remote session. The given route must satisfy all of the
+         * following conditions:
+         * <ul>
+         * <li>ID should be included in {@link #getSelectedRoutes()}</li>
+         * <li>ID should be included in {@link #getDeselectableRoutes()}</li>
+         * </ul>
+         * If the route doesn't meet any of above conditions, it will be ignored.
+         *
+         * @see #getSelectedRoutes()
+         * @see #getDeselectableRoutes()
+         */
+        public void deselectRoute(@NonNull MediaRoute2Info route) {
+            Objects.requireNonNull(route, "route must not be null");
+            RoutingSessionInfo sessionInfo;
+            synchronized (mControllerLock) {
+                sessionInfo = mSessionInfo;
+            }
+
+            if (!sessionInfo.getSelectedRoutes().contains(route.getId())) {
+                Log.w(TAG, "Ignoring deselecting a route that is not selected. route=" + route);
+                return;
+            }
+
+            if (!sessionInfo.getDeselectableRoutes().contains(route.getId())) {
+                Log.w(TAG, "Ignoring deselecting a non-deselectable route=" + route);
+                return;
+            }
+
+            Client client;
+            synchronized (sLock) {
+                client = mClient;
+            }
+            if (client != null) {
+                try {
+                    mMediaRouterService.deselectClientRoute(mClient, getSessionId(), route);
+                } catch (RemoteException ex) {
+                    Log.e(TAG, "Unable to remove route from session.", ex);
+                }
+            }
+        }
+
+        /**
+         * Transfers to a given route for the remote session. The given route must satisfy
+         * all of the following conditions:
+         * <ul>
+         * <li>ID should not be included in {@link #getSelectedRoutes()}</li>
+         * <li>ID should be included in {@link #getTransferrableRoutes()}</li>
+         * </ul>
+         * If the route doesn't meet any of above conditions, it will be ignored.
+         *
+         * @see #getSelectedRoutes()
+         * @see #getTransferrableRoutes()
+         */
+        public void transferToRoute(@NonNull MediaRoute2Info route) {
+            Objects.requireNonNull(route, "route must not be null");
+            RoutingSessionInfo sessionInfo;
+            synchronized (mControllerLock) {
+                sessionInfo = mSessionInfo;
+            }
+
+            if (sessionInfo.getSelectedRoutes().contains(route.getId())) {
+                Log.w(TAG, "Ignoring transferring to a route that is already added. route="
+                        + route);
+                return;
+            }
+
+            if (!sessionInfo.getTransferrableRoutes().contains(route.getId())) {
+                Log.w(TAG, "Ignoring transferring to a non-transferrable route=" + route);
+                return;
+            }
+
+            Client client;
+            synchronized (sLock) {
+                client = mClient;
+            }
+            if (client != null) {
+                try {
+                    mMediaRouterService.transferToClientRoute(mClient, getSessionId(), route);
+                } catch (RemoteException ex) {
+                    Log.e(TAG, "Unable to transfer to route for session.", ex);
+                }
+            }
+        }
+
+        /**
+         * Release this session.
+         * Any operation on this session after calling this method will be ignored.
+         */
+        public void release() {
+            Client client;
+            synchronized (sLock) {
+                client = mClient;
+            }
+            if (client != null) {
+                try {
+                    mMediaRouterService.releaseClientSession(mClient, getSessionId());
+                } catch (RemoteException ex) {
+                    Log.e(TAG, "Unable to notify of controller release", ex);
+                }
+            }
+        }
+
+        /**
+         * Gets the session info of the session
+         * @hide
+         */
+        @NonNull
+        public RoutingSessionInfo getSessionInfo() {
+            synchronized (mControllerLock) {
+                return mSessionInfo;
+            }
+        }
+
+        private List<MediaRoute2Info> getRoutesWithIds(List<String> routeIds) {
+            List<MediaRoute2Info> routes = new ArrayList<>();
+            synchronized (mRoutesLock) {
+                for (String routeId : routeIds) {
+                    MediaRoute2Info route = mRoutes.get(routeId);
+                    if (route != null) {
+                        routes.add(route);
+                    }
+                }
+            }
+            return Collections.unmodifiableList(routes);
         }
     }
 
@@ -388,23 +689,26 @@
         public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {}
 
         /**
-         * Called when a route is selected for an application.
+         * Called when a routing session is created.
          *
-         * @param packageName the package name of the application
-         * @param route the selected route of the application.
-         *              It is null if the application has no selected route.
+         * @param controller the controller to control the created session
          */
-        public void onRouteSelected(@NonNull String packageName, @Nullable MediaRoute2Info route) {}
-
+        public void onSessionCreated(@NonNull RoutingController controller) {}
 
         /**
-         * Called when the control categories of an app is changed.
+         * Called when at least one session info is changed.
+         * Call {@link #getActiveSessions()} to get current active session info.
+         */
+        public void onSessionsUpdated() {}
+
+        /**
+         * Called when the preferred route features of an app is changed.
          *
          * @param packageName the package name of the application
-         * @param controlCategories the list of control categories set by an application.
+         * @param preferredFeatures the list of preferred route features set by an application.
          */
         public void onControlCategoriesChanged(@NonNull String packageName,
-                @NonNull List<String> controlCategories) {}
+                @NonNull List<String> preferredFeatures) {}
     }
 
     final class CallbackRecord {
@@ -435,15 +739,22 @@
 
     class Client extends IMediaRouter2Manager.Stub {
         @Override
-        public void notifyRouteSelected(String packageName, MediaRoute2Info route) {
-            mHandler.sendMessage(obtainMessage(MediaRouter2Manager::notifyRouteSelected,
-                    MediaRouter2Manager.this, packageName, route));
+        public void notifySessionCreated(RoutingSessionInfo sessionInfo) {
+            mHandler.sendMessage(obtainMessage(MediaRouter2Manager::notifySessionCreated,
+                    MediaRouter2Manager.this, sessionInfo));
         }
 
         @Override
-        public void notifyControlCategoriesChanged(String packageName, List<String> categories) {
-            mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updateControlCategories,
-                    MediaRouter2Manager.this, packageName, categories));
+        public void notifySessionsUpdated() {
+            mHandler.sendMessage(obtainMessage(MediaRouter2Manager::notifySessionInfosChanged,
+                    MediaRouter2Manager.this));
+            // do nothing
+        }
+
+        @Override
+        public void notifyPreferredFeaturesChanged(String packageName, List<String> features) {
+            mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updatePreferredFeatures,
+                    MediaRouter2Manager.this, packageName, features));
         }
 
         @Override
diff --git a/media/java/android/media/MediaRouter2Utils.java b/media/java/android/media/MediaRouter2Utils.java
new file mode 100644
index 0000000..c15972d
--- /dev/null
+++ b/media/java/android/media/MediaRouter2Utils.java
@@ -0,0 +1,93 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * @hide
+ */
+public class MediaRouter2Utils {
+
+    static final String TAG = "MR2Utils";
+    static final String SEPARATOR = ":";
+
+    @NonNull
+    public static String toUniqueId(@NonNull String providerId, @NonNull String id) {
+        if (TextUtils.isEmpty(providerId)) {
+            Log.w(TAG, "toUniqueId: providerId shouldn't be empty");
+            return null;
+        }
+        if (TextUtils.isEmpty(id)) {
+            Log.w(TAG, "toUniqueId: id shouldn't be null");
+            return null;
+        }
+
+        return providerId + SEPARATOR + id;
+    }
+
+    /**
+     * Gets provider ID from unique ID.
+     * If the corresponding provider ID could not be generated, it will return null.
+     */
+    @Nullable
+    public static String getProviderId(@NonNull String uniqueId) {
+        if (TextUtils.isEmpty(uniqueId)) {
+            Log.w(TAG, "getProviderId: uniqueId shouldn't be empty");
+            return null;
+        }
+
+        int firstIndexOfSeparator = uniqueId.indexOf(SEPARATOR);
+        if (firstIndexOfSeparator == -1) {
+            return null;
+        }
+
+        String providerId = uniqueId.substring(0, firstIndexOfSeparator);
+        if (TextUtils.isEmpty(providerId)) {
+            return null;
+        }
+
+        return providerId;
+    }
+
+    /**
+     * Gets the original ID (i.e. non-unique route/session ID) from unique ID.
+     * If the corresponding ID could not be generated, it will return null.
+     */
+    @Nullable
+    public static String getOriginalId(@NonNull String uniqueId) {
+        if (TextUtils.isEmpty(uniqueId)) {
+            Log.w(TAG, "getOriginalId: uniqueId shouldn't be empty");
+            return null;
+        }
+
+        int firstIndexOfSeparator = uniqueId.indexOf(SEPARATOR);
+        if (firstIndexOfSeparator == -1 || firstIndexOfSeparator + 1 >= uniqueId.length()) {
+            return null;
+        }
+
+        String providerId = uniqueId.substring(firstIndexOfSeparator + 1);
+        if (TextUtils.isEmpty(providerId)) {
+            return null;
+        }
+
+        return providerId;
+    }
+}
diff --git a/media/java/android/media/MediaScannerConnection.java b/media/java/android/media/MediaScannerConnection.java
index 40e9073..05fa511 100644
--- a/media/java/android/media/MediaScannerConnection.java
+++ b/media/java/android/media/MediaScannerConnection.java
@@ -16,7 +16,7 @@
 
 package android.media;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.ContentProviderClient;
 import android.content.ContentResolver;
diff --git a/media/java/android/media/MediaTranscodeManager.java b/media/java/android/media/MediaTranscodeManager.java
new file mode 100644
index 0000000..2c431b9
--- /dev/null
+++ b/media/java/android/media/MediaTranscodeManager.java
@@ -0,0 +1,403 @@
+/*
+ * 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.media;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.Uri;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * MediaTranscodeManager provides an interface to the system's media transcode service.
+ * Transcode requests are put in a queue and processed in order. When a transcode operation is
+ * completed the caller is notified via its OnTranscodingFinishedListener. In the meantime the
+ * caller may use the returned TranscodingJob object to cancel or check the status of a specific
+ * transcode operation.
+ * The currently supported media types are video and still images.
+ *
+ * TODO(lnilsson): Add sample code when API is settled.
+ *
+ * @hide
+ */
+public final class MediaTranscodeManager {
+    private static final String TAG = "MediaTranscodeManager";
+
+    // Invalid ID passed from native means the request was never enqueued.
+    private static final long ID_INVALID = -1;
+
+    // Events passed from native.
+    private static final int EVENT_JOB_STARTED = 1;
+    private static final int EVENT_JOB_PROGRESSED = 2;
+    private static final int EVENT_JOB_FINISHED = 3;
+
+    @IntDef(prefix = { "EVENT_" }, value = {
+            EVENT_JOB_STARTED,
+            EVENT_JOB_PROGRESSED,
+            EVENT_JOB_FINISHED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Event {}
+
+    private static MediaTranscodeManager sMediaTranscodeManager;
+    private final ConcurrentMap<Long, TranscodingJob> mPendingTranscodingJobs =
+            new ConcurrentHashMap<>();
+    private final Context mContext;
+
+    /**
+     * Listener that gets notified when a transcoding operation has finished.
+     * This listener gets notified regardless of how the operation finished. It is up to the
+     * listener implementation to check the result and take appropriate action.
+     */
+    @FunctionalInterface
+    public interface OnTranscodingFinishedListener {
+        /**
+         * Called when the transcoding operation has finished. The receiver may use the
+         * TranscodingJob to check the result, i.e. whether the operation succeeded, was canceled or
+         * if an error occurred.
+         * @param transcodingJob The TranscodingJob instance for the finished transcoding operation.
+         */
+        void onTranscodingFinished(@NonNull TranscodingJob transcodingJob);
+    }
+
+    /**
+     * Class describing a transcode operation to be performed. The caller uses this class to
+     * configure a transcoding operation that can then be enqueued using MediaTranscodeManager.
+     */
+    public static final class TranscodingRequest {
+        private Uri mSrcUri;
+        private Uri mDstUri;
+        private MediaFormat mDstFormat;
+
+        private TranscodingRequest(Builder b) {
+            mSrcUri = b.mSrcUri;
+            mDstUri = b.mDstUri;
+            mDstFormat = b.mDstFormat;
+        }
+
+        /** TranscodingRequest builder class. */
+        public static class Builder {
+            private Uri mSrcUri;
+            private Uri mDstUri;
+            private MediaFormat mDstFormat;
+
+            /**
+             * Specifies the source media file.
+             * @param uri Content uri for the source media file.
+             * @return The builder instance.
+             */
+            public Builder setSourceUri(Uri uri) {
+                mSrcUri = uri;
+                return this;
+            }
+
+            /**
+             * Specifies the destination media file.
+             * @param uri Content uri for the destination media file.
+             * @return The builder instance.
+             */
+            public Builder setDestinationUri(Uri uri) {
+                mDstUri = uri;
+                return this;
+            }
+
+            /**
+             * Specifies the media format of the transcoded media file.
+             * @param dstFormat MediaFormat containing the desired destination format.
+             * @return The builder instance.
+             */
+            public Builder setDestinationFormat(MediaFormat dstFormat) {
+                mDstFormat = dstFormat;
+                return this;
+            }
+
+            /**
+             * Builds a new TranscodingRequest with the configuration set on this builder.
+             * @return A new TranscodingRequest.
+             */
+            public TranscodingRequest build() {
+                return new TranscodingRequest(this);
+            }
+        }
+    }
+
+    /**
+     * Handle to an enqueued transcoding operation. An instance of this class represents a single
+     * enqueued transcoding operation. The caller can use that instance to query the status or
+     * progress, and to get the result once the operation has completed.
+     */
+    public static final class TranscodingJob {
+        /** The job is enqueued but not yet running. */
+        public static final int STATUS_PENDING = 1;
+        /** The job is currently running. */
+        public static final int STATUS_RUNNING = 2;
+        /** The job is finished. */
+        public static final int STATUS_FINISHED = 3;
+
+        @IntDef(prefix = { "STATUS_" }, value = {
+                STATUS_PENDING,
+                STATUS_RUNNING,
+                STATUS_FINISHED,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface Status {}
+
+        /** The job does not have a result yet. */
+        public static final int RESULT_NONE = 1;
+        /** The job completed successfully. */
+        public static final int RESULT_SUCCESS = 2;
+        /** The job encountered an error while running. */
+        public static final int RESULT_ERROR = 3;
+        /** The job was canceled by the caller. */
+        public static final int RESULT_CANCELED = 4;
+
+        @IntDef(prefix = { "RESULT_" }, value = {
+                RESULT_NONE,
+                RESULT_SUCCESS,
+                RESULT_ERROR,
+                RESULT_CANCELED,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface Result {}
+
+        /** Listener that gets notified when the progress changes. */
+        @FunctionalInterface
+        public interface OnProgressChangedListener {
+
+            /**
+             * Called when the progress changes. The progress is between 0 and 1, where 0 means
+             * that the job has not yet started and 1 means that it has finished.
+             * @param progress The new progress.
+             */
+            void onProgressChanged(float progress);
+        }
+
+        private final Executor mExecutor;
+        private final OnTranscodingFinishedListener mListener;
+        private final ReentrantLock mStatusChangeLock = new ReentrantLock();
+        private Executor mProgressChangedExecutor;
+        private OnProgressChangedListener mProgressChangedListener;
+        private long mID;
+        private float mProgress = 0.0f;
+        private @Status int mStatus = STATUS_PENDING;
+        private @Result int mResult = RESULT_NONE;
+
+        private TranscodingJob(long id, @NonNull @CallbackExecutor Executor executor,
+                @NonNull OnTranscodingFinishedListener listener) {
+            mID = id;
+            mExecutor = executor;
+            mListener = listener;
+        }
+
+        /**
+         * Set a progress listener.
+         * @param listener The progress listener.
+         */
+        public void setOnProgressChangedListener(@NonNull @CallbackExecutor Executor executor,
+                @Nullable OnProgressChangedListener listener) {
+            mProgressChangedExecutor = executor;
+            mProgressChangedListener = listener;
+        }
+
+        /**
+         * Cancels the transcoding job and notify the listener. If the job happened to finish before
+         * being canceled this call is effectively a no-op and will not update the result in that
+         * case.
+         */
+        public void cancel() {
+            setJobFinished(RESULT_CANCELED);
+            sMediaTranscodeManager.native_cancelTranscodingRequest(mID);
+        }
+
+        /**
+         * Gets the progress of the transcoding job. The progress is between 0 and 1, where 0 means
+         * that the job has not yet started and 1 means that it is finished.
+         * @return The progress.
+         */
+        public float getProgress() {
+            return mProgress;
+        }
+
+        /**
+         * Gets the status of the transcoding job.
+         * @return The status.
+         */
+        public @Status int getStatus() {
+            return mStatus;
+        }
+
+        /**
+         * Gets the result of the transcoding job.
+         * @return The result.
+         */
+        public @Result int getResult() {
+            return mResult;
+        }
+
+        private void setJobStarted() {
+            mStatus = STATUS_RUNNING;
+        }
+
+        private void setJobProgress(float newProgress) {
+            mProgress = newProgress;
+
+            // Notify listener.
+            OnProgressChangedListener onProgressChangedListener = mProgressChangedListener;
+            if (onProgressChangedListener != null) {
+                mProgressChangedExecutor.execute(
+                        () -> onProgressChangedListener.onProgressChanged(mProgress));
+            }
+        }
+
+        private void setJobFinished(int result) {
+            boolean doNotifyListener = false;
+
+            // Prevent conflicting simultaneous status updates from native (finished) and from the
+            // caller (cancel).
+            try {
+                mStatusChangeLock.lock();
+                if (mStatus != STATUS_FINISHED) {
+                    mStatus = STATUS_FINISHED;
+                    mResult = result;
+                    doNotifyListener = true;
+                }
+            } finally {
+                mStatusChangeLock.unlock();
+            }
+
+            if (doNotifyListener) {
+                mExecutor.execute(() -> mListener.onTranscodingFinished(this));
+            }
+        }
+
+        private void processJobEvent(@Event int event, int arg) {
+            switch (event) {
+                case EVENT_JOB_STARTED:
+                    setJobStarted();
+                    break;
+                case EVENT_JOB_PROGRESSED:
+                    setJobProgress((float) arg / 100);
+                    break;
+                case EVENT_JOB_FINISHED:
+                    setJobFinished(arg);
+                    break;
+                default:
+                    Log.e(TAG, "Unsupported event: " + event);
+                    break;
+            }
+        }
+    }
+
+    // Initializes the native library.
+    private static native void native_init();
+    // Requests a new job ID from the native service.
+    private native long native_requestUniqueJobID();
+    // Enqueues a transcoding request to the native service.
+    private native boolean native_enqueueTranscodingRequest(
+            long id, @NonNull TranscodingRequest transcodingRequest, @NonNull Context context);
+    // Cancels an enqueued transcoding request.
+    private native void native_cancelTranscodingRequest(long id);
+
+    // Private constructor.
+    private MediaTranscodeManager(@NonNull Context context) {
+        mContext = context;
+    }
+
+    // Events posted from the native service.
+    @SuppressWarnings("unused")
+    private void postEventFromNative(@Event int event, long id, int arg) {
+        Log.d(TAG, String.format("postEventFromNative. Event %d, ID %d, arg %d", event, id, arg));
+
+        TranscodingJob transcodingJob = mPendingTranscodingJobs.get(id);
+
+        // Job IDs are added to the tracking set before the job is enqueued so it should never
+        // be null unless the service misbehaves.
+        if (transcodingJob == null) {
+            Log.e(TAG, "No matching transcode job found for id " + id);
+            return;
+        }
+
+        transcodingJob.processJobEvent(event, arg);
+    }
+
+    /**
+     * Gets the MediaTranscodeManager singleton instance.
+     * @param context The application context.
+     * @return the {@link MediaTranscodeManager} singleton instance.
+     */
+    public static MediaTranscodeManager getInstance(@NonNull Context context) {
+        Preconditions.checkNotNull(context);
+        synchronized (MediaTranscodeManager.class) {
+            if (sMediaTranscodeManager == null) {
+                sMediaTranscodeManager = new MediaTranscodeManager(context.getApplicationContext());
+            }
+            return sMediaTranscodeManager;
+        }
+    }
+
+    /**
+     * Enqueues a TranscodingRequest for execution.
+     * @param transcodingRequest The TranscodingRequest to enqueue.
+     * @param listenerExecutor Executor on which the listener is notified.
+     * @param listener Listener to get notified when the transcoding job is finished.
+     * @return A TranscodingJob for this operation.
+     */
+    public @Nullable TranscodingJob enqueueTranscodingRequest(
+            @NonNull TranscodingRequest transcodingRequest,
+            @NonNull @CallbackExecutor Executor listenerExecutor,
+            @NonNull OnTranscodingFinishedListener listener) {
+        Log.i(TAG, "enqueueTranscodingRequest called.");
+        Preconditions.checkNotNull(transcodingRequest);
+        Preconditions.checkNotNull(listenerExecutor);
+        Preconditions.checkNotNull(listener);
+
+        // Reserve a job ID.
+        long jobID = native_requestUniqueJobID();
+        if (jobID == ID_INVALID) {
+            return null;
+        }
+
+        // Add the job to the tracking set.
+        TranscodingJob transcodingJob = new TranscodingJob(jobID, listenerExecutor, listener);
+        mPendingTranscodingJobs.put(jobID, transcodingJob);
+
+        // Enqueue the request with the native service.
+        boolean enqueued = native_enqueueTranscodingRequest(jobID, transcodingRequest, mContext);
+        if (!enqueued) {
+            mPendingTranscodingJobs.remove(jobID);
+            return null;
+        }
+
+        return transcodingJob;
+    }
+
+    static {
+        System.loadLibrary("media_jni");
+        native_init();
+    }
+}
diff --git a/media/java/android/media/RouteDiscoveryPreference.aidl b/media/java/android/media/RouteDiscoveryPreference.aidl
new file mode 100644
index 0000000..898eb39
--- /dev/null
+++ b/media/java/android/media/RouteDiscoveryPreference.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 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.media;
+
+parcelable RouteDiscoveryPreference;
diff --git a/media/java/android/media/RouteDiscoveryPreference.java b/media/java/android/media/RouteDiscoveryPreference.java
new file mode 100644
index 0000000..7ec1123
--- /dev/null
+++ b/media/java/android/media/RouteDiscoveryPreference.java
@@ -0,0 +1,215 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A media route discovery preference  describing the kinds of routes that media router
+ * would like to discover and whether to perform active scanning.
+ *
+ * @see MediaRouter2#registerRouteCallback
+ */
+public final class RouteDiscoveryPreference implements Parcelable {
+    @NonNull
+    public static final Creator<RouteDiscoveryPreference> CREATOR =
+            new Creator<RouteDiscoveryPreference>() {
+                @Override
+                public RouteDiscoveryPreference createFromParcel(Parcel in) {
+                    return new RouteDiscoveryPreference(in);
+                }
+
+                @Override
+                public RouteDiscoveryPreference[] newArray(int size) {
+                    return new RouteDiscoveryPreference[size];
+                }
+            };
+
+    @NonNull
+    private final List<String> mPreferredFeatures;
+    private final boolean mActiveScan;
+    @Nullable
+    private final Bundle mExtras;
+
+    /**
+     * @hide
+     */
+    public static final RouteDiscoveryPreference EMPTY =
+            new Builder(Collections.emptyList(), false).build();
+
+    RouteDiscoveryPreference(@NonNull Builder builder) {
+        mPreferredFeatures = builder.mPreferredFeatures;
+        mActiveScan = builder.mActiveScan;
+        mExtras = builder.mExtras;
+    }
+
+    RouteDiscoveryPreference(@NonNull Parcel in) {
+        mPreferredFeatures = in.createStringArrayList();
+        mActiveScan = in.readBoolean();
+        mExtras = in.readBundle();
+    }
+
+    @NonNull
+    public List<String> getPreferredFeatures() {
+        return mPreferredFeatures;
+    }
+
+    public boolean isActiveScan() {
+        return mActiveScan;
+    }
+
+    /**
+     * @hide
+     */
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeStringList(mPreferredFeatures);
+        dest.writeBoolean(mActiveScan);
+        dest.writeBundle(mExtras);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder result = new StringBuilder()
+                .append("RouteDiscoveryRequest{ ")
+                .append("preferredFeatures={")
+                .append(String.join(", ", mPreferredFeatures))
+                .append("}")
+                .append(", activeScan=")
+                .append(mActiveScan)
+                .append(" }");
+
+        return result.toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof RouteDiscoveryPreference)) {
+            return false;
+        }
+        RouteDiscoveryPreference other = (RouteDiscoveryPreference) o;
+        return Objects.equals(mPreferredFeatures, other.mPreferredFeatures)
+                && mActiveScan == other.mActiveScan;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mPreferredFeatures, mActiveScan);
+    }
+
+    /**
+     * Builder for {@link RouteDiscoveryPreference}.
+     */
+    public static final class Builder {
+        List<String> mPreferredFeatures;
+        boolean mActiveScan;
+        Bundle mExtras;
+
+        public Builder(@NonNull List<String> preferredFeatures, boolean activeScan) {
+            mPreferredFeatures = new ArrayList<>(Objects.requireNonNull(preferredFeatures,
+                    "preferredFeatures must not be null"));
+            mActiveScan = activeScan;
+        }
+
+        public Builder(@NonNull RouteDiscoveryPreference preference) {
+            Objects.requireNonNull(preference, "preference must not be null");
+
+            mPreferredFeatures = preference.getPreferredFeatures();
+            mActiveScan = preference.isActiveScan();
+            mExtras = preference.getExtras();
+        }
+
+        /**
+         * A constructor to combine all of the preferences into a single preference .
+         * It ignores extras of preferences.
+         *
+         * @hide
+         */
+        public Builder(@NonNull Collection<RouteDiscoveryPreference> preferences) {
+            Objects.requireNonNull(preferences, "preferences must not be null");
+
+            Set<String> routeFeatureSet = new HashSet<>();
+            mActiveScan = false;
+            for (RouteDiscoveryPreference preference : preferences) {
+                routeFeatureSet.addAll(preference.mPreferredFeatures);
+                mActiveScan |= preference.mActiveScan;
+            }
+            mPreferredFeatures = new ArrayList<>(routeFeatureSet);
+        }
+
+        /**
+         * Sets preferred route features to discover.
+         */
+        @NonNull
+        public Builder setPreferredFeatures(@NonNull List<String> preferredFeatures) {
+            mPreferredFeatures = new ArrayList<>(Objects.requireNonNull(preferredFeatures,
+                            "preferredFeatures must not be null"));
+            return this;
+        }
+
+        /**
+         * Sets if active scanning should be performed.
+         */
+        @NonNull
+        public Builder setActiveScan(boolean activeScan) {
+            mActiveScan = activeScan;
+            return this;
+        }
+
+        /**
+         * Sets the extras of the route.
+         * @hide
+         */
+        @NonNull
+        public Builder setExtras(@Nullable Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /**
+         * Builds the {@link RouteDiscoveryPreference}.
+         */
+        @NonNull
+        public RouteDiscoveryPreference build() {
+            return new RouteDiscoveryPreference(this);
+        }
+    }
+}
diff --git a/media/java/android/media/RouteSessionInfo.aidl b/media/java/android/media/RouteSessionInfo.aidl
deleted file mode 100644
index fb5d836..0000000
--- a/media/java/android/media/RouteSessionInfo.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright 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.media;
-
-parcelable RouteSessionInfo;
diff --git a/media/java/android/media/RouteSessionInfo.java b/media/java/android/media/RouteSessionInfo.java
deleted file mode 100644
index 4a9298a..0000000
--- a/media/java/android/media/RouteSessionInfo.java
+++ /dev/null
@@ -1,480 +0,0 @@
-/*
- * Copyright 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.media;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Describes a route session that is made when a media route is selected.
- * @hide
- */
-public class RouteSessionInfo implements Parcelable {
-    @NonNull
-    public static final Creator<RouteSessionInfo> CREATOR =
-            new Creator<RouteSessionInfo>() {
-                @Override
-                public RouteSessionInfo createFromParcel(Parcel in) {
-                    return new RouteSessionInfo(in);
-                }
-                @Override
-                public RouteSessionInfo[] newArray(int size) {
-                    return new RouteSessionInfo[size];
-                }
-            };
-
-    final int mSessionId;
-    final String mPackageName;
-    final String mControlCategory;
-    @Nullable
-    final String mProviderId;
-    final List<String> mSelectedRoutes;
-    final List<String> mSelectableRoutes;
-    final List<String> mDeselectableRoutes;
-    final List<String> mTransferrableRoutes;
-    @Nullable
-    final Bundle mControlHints;
-
-    RouteSessionInfo(@NonNull Builder builder) {
-        Objects.requireNonNull(builder, "builder must not be null.");
-
-        mSessionId = builder.mSessionId;
-        mPackageName = builder.mPackageName;
-        mControlCategory = builder.mControlCategory;
-        mProviderId = builder.mProviderId;
-
-        mSelectedRoutes = Collections.unmodifiableList(builder.mSelectedRoutes);
-        mSelectableRoutes = Collections.unmodifiableList(builder.mSelectableRoutes);
-        mDeselectableRoutes = Collections.unmodifiableList(builder.mDeselectableRoutes);
-        mTransferrableRoutes = Collections.unmodifiableList(builder.mTransferrableRoutes);
-
-        mControlHints = builder.mControlHints;
-    }
-
-    RouteSessionInfo(@NonNull Parcel src) {
-        Objects.requireNonNull(src, "src must not be null.");
-
-        mSessionId = src.readInt();
-        mPackageName = ensureString(src.readString());
-        mControlCategory = ensureString(src.readString());
-        mProviderId = src.readString();
-
-        mSelectedRoutes = ensureList(src.createStringArrayList());
-        mSelectableRoutes = ensureList(src.createStringArrayList());
-        mDeselectableRoutes = ensureList(src.createStringArrayList());
-        mTransferrableRoutes = ensureList(src.createStringArrayList());
-
-        mControlHints = src.readBundle();
-    }
-
-    private static String ensureString(String str) {
-        if (str != null) {
-            return str;
-        }
-        return "";
-    }
-
-    private static <T> List<T> ensureList(List<? extends T> list) {
-        if (list != null) {
-            return Collections.unmodifiableList(list);
-        }
-        return Collections.emptyList();
-    }
-
-    /**
-     * Gets non-unique session id (int) from unique session id (string).
-     * If the corresponding session id could not be generated, it will return null.
-     * @hide
-     */
-    @Nullable
-    public static Integer getSessionId(@NonNull String uniqueSessionId) {
-        int lastIndexOfSeparator = uniqueSessionId.lastIndexOf("/");
-        if (lastIndexOfSeparator == -1 || lastIndexOfSeparator + 1 >= uniqueSessionId.length()) {
-            return null;
-        }
-
-        String integerString = uniqueSessionId.substring(lastIndexOfSeparator + 1);
-        if (TextUtils.isEmpty(integerString)) {
-            return null;
-        }
-
-        try {
-            return Integer.parseInt(integerString);
-        } catch (NumberFormatException ex) {
-            return null;
-        }
-    }
-
-    /**
-     * Gets provider ID (string) from unique session id (string).
-     * If the corresponding provider ID could not be generated, it will return null.
-     * @hide
-     *
-     * TODO: This logic seems error-prone. Consider to use long uniqueId.
-     */
-    @Nullable
-    public static String getProviderId(@NonNull String uniqueSessionId) {
-        int lastIndexOfSeparator = uniqueSessionId.lastIndexOf("/");
-        if (lastIndexOfSeparator == -1) {
-            return null;
-        }
-
-        String result = uniqueSessionId.substring(0, lastIndexOfSeparator);
-        if (TextUtils.isEmpty(result)) {
-            return null;
-        }
-        return result;
-    }
-
-    /**
-     * Returns whether the session info is valid or not
-     */
-    public boolean isValid() {
-        return !TextUtils.isEmpty(mPackageName)
-                && !TextUtils.isEmpty(mControlCategory)
-                && mSelectedRoutes.size() > 0;
-    }
-
-    /**
-     * Gets the id of the session
-     */
-    @NonNull
-    public int getSessionId() {
-        return mSessionId;
-    }
-
-    /**
-     * Gets the client package name of the session
-     */
-    @NonNull
-    public String getPackageName() {
-        return mPackageName;
-    }
-
-    /**
-     * Gets the control category of the session.
-     * Routes that don't support the category can't be added to the session.
-     */
-    @NonNull
-    public String getControlCategory() {
-        return mControlCategory;
-    }
-
-    /**
-     * Gets the provider id of the session.
-     * @hide
-     */
-    @Nullable
-    public String getProviderId() {
-        return mProviderId;
-    }
-
-    /**
-     * Gets the unique id of the session.
-     * @hide
-     */
-    @NonNull
-    public String getUniqueSessionId() {
-        StringBuilder sessionIdBuilder = new StringBuilder()
-                .append(mProviderId)
-                .append("/")
-                .append(mSessionId);
-        return sessionIdBuilder.toString();
-    }
-
-    /**
-     * Gets the list of ids of selected routes for the session. It shouldn't be empty.
-     */
-    @NonNull
-    public List<String> getSelectedRoutes() {
-        return mSelectedRoutes;
-    }
-
-    /**
-     * Gets the list of ids of selectable routes for the session.
-     */
-    @NonNull
-    public List<String> getSelectableRoutes() {
-        return mSelectableRoutes;
-    }
-
-    /**
-     * Gets the list of ids of deselectable routes for the session.
-     */
-    @NonNull
-    public List<String> getDeselectableRoutes() {
-        return mDeselectableRoutes;
-    }
-
-    /**
-     * Gets the list of ids of transferrable routes for the session.
-     */
-    @NonNull
-    public List<String> getTransferrableRoutes() {
-        return mTransferrableRoutes;
-    }
-
-    /**
-     * Gets the control hints
-     */
-    @Nullable
-    public Bundle getControlHints() {
-        return mControlHints;
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeInt(mSessionId);
-        dest.writeString(mPackageName);
-        dest.writeString(mControlCategory);
-        dest.writeString(mProviderId);
-        dest.writeStringList(mSelectedRoutes);
-        dest.writeStringList(mSelectableRoutes);
-        dest.writeStringList(mDeselectableRoutes);
-        dest.writeStringList(mTransferrableRoutes);
-        dest.writeBundle(mControlHints);
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder result = new StringBuilder()
-                .append("RouteSessionInfo{ ")
-                .append("sessionId=").append(mSessionId)
-                .append(", controlCategory=").append(mControlCategory)
-                .append(", selectedRoutes={")
-                .append(String.join(",", mSelectedRoutes))
-                .append("}")
-                .append(", selectableRoutes={")
-                .append(String.join(",", mSelectableRoutes))
-                .append("}")
-                .append(", deselectableRoutes={")
-                .append(String.join(",", mDeselectableRoutes))
-                .append("}")
-                .append(", transferrableRoutes={")
-                .append(String.join(",", mTransferrableRoutes))
-                .append("}")
-                .append(" }");
-        return result.toString();
-    }
-
-    /**
-     * Builder class for {@link RouteSessionInfo}.
-     */
-    public static final class Builder {
-        final String mPackageName;
-        final int mSessionId;
-        final String mControlCategory;
-        String mProviderId;
-        final List<String> mSelectedRoutes;
-        final List<String> mSelectableRoutes;
-        final List<String> mDeselectableRoutes;
-        final List<String> mTransferrableRoutes;
-        Bundle mControlHints;
-
-        public Builder(int sessionId, @NonNull String packageName,
-                @NonNull String controlCategory) {
-            mSessionId = sessionId;
-            mPackageName = Objects.requireNonNull(packageName, "packageName must not be null");
-            mControlCategory = Objects.requireNonNull(controlCategory,
-                    "controlCategory must not be null");
-
-            mSelectedRoutes = new ArrayList<>();
-            mSelectableRoutes = new ArrayList<>();
-            mDeselectableRoutes = new ArrayList<>();
-            mTransferrableRoutes = new ArrayList<>();
-        }
-
-        public Builder(RouteSessionInfo sessionInfo) {
-            mSessionId = sessionInfo.mSessionId;
-            mPackageName = sessionInfo.mPackageName;
-            mControlCategory = sessionInfo.mControlCategory;
-            mProviderId = sessionInfo.mProviderId;
-
-            mSelectedRoutes = new ArrayList<>(sessionInfo.mSelectedRoutes);
-            mSelectableRoutes = new ArrayList<>(sessionInfo.mSelectableRoutes);
-            mDeselectableRoutes = new ArrayList<>(sessionInfo.mDeselectableRoutes);
-            mTransferrableRoutes = new ArrayList<>(sessionInfo.mTransferrableRoutes);
-
-            mControlHints = sessionInfo.mControlHints;
-        }
-
-        /**
-         * Sets the provider ID of the session.
-         * Also, calling this method will make all type of route IDs be unique by adding
-         * {@code providerId:} as a prefix. So do NOT call this method twice on same instance.
-         *
-         * @hide
-         */
-        @NonNull
-        public Builder setProviderId(String providerId) {
-            mProviderId = providerId;
-            convertToUniqueRouteIds(providerId, mSelectedRoutes);
-            convertToUniqueRouteIds(providerId, mSelectableRoutes);
-            convertToUniqueRouteIds(providerId, mDeselectableRoutes);
-            convertToUniqueRouteIds(providerId, mTransferrableRoutes);
-            return this;
-        }
-
-        private void convertToUniqueRouteIds(@NonNull String providerId,
-                @NonNull List<String> routeIds) {
-            for (int i = 0; i < routeIds.size(); i++) {
-                String routeId = routeIds.get(i);
-                routeIds.set(i, MediaRoute2Info.toUniqueId(providerId, routeId));
-            }
-        }
-
-        /**
-         * Clears the selected routes.
-         */
-        @NonNull
-        public Builder clearSelectedRoutes() {
-            mSelectedRoutes.clear();
-            return this;
-        }
-
-        /**
-         * Adds a route to the selected routes.
-         */
-        @NonNull
-        public Builder addSelectedRoute(@NonNull String routeId) {
-            mSelectedRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
-            return this;
-        }
-
-        /**
-         * Removes a route from the selected routes.
-         */
-        @NonNull
-        public Builder removeSelectedRoute(@NonNull String routeId) {
-            mSelectedRoutes.remove(Objects.requireNonNull(routeId, "routeId must not be null"));
-            return this;
-        }
-
-        /**
-         * Clears the selectable routes.
-         */
-        @NonNull
-        public Builder clearSelectableRoutes() {
-            mSelectableRoutes.clear();
-            return this;
-        }
-
-        /**
-         * Adds a route to the selectable routes.
-         */
-        @NonNull
-        public Builder addSelectableRoute(@NonNull String routeId) {
-            mSelectableRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
-            return this;
-        }
-
-        /**
-         * Removes a route from the selectable routes.
-         */
-        @NonNull
-        public Builder removeSelectableRoute(@NonNull String routeId) {
-            mSelectableRoutes.remove(Objects.requireNonNull(routeId, "routeId must not be null"));
-            return this;
-        }
-
-        /**
-         * Clears the deselectable routes.
-         */
-        @NonNull
-        public Builder clearDeselectableRoutes() {
-            mDeselectableRoutes.clear();
-            return this;
-        }
-
-        /**
-         * Adds a route to the deselectable routes.
-         */
-        @NonNull
-        public Builder addDeselectableRoute(@NonNull String routeId) {
-            mDeselectableRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
-            return this;
-        }
-
-        /**
-         * Removes a route from the deselectable routes.
-         */
-        @NonNull
-        public Builder removeDeselectableRoute(@NonNull String routeId) {
-            mDeselectableRoutes.remove(Objects.requireNonNull(routeId, "routeId must not be null"));
-            return this;
-        }
-
-        /**
-         * Clears the transferrable routes.
-         */
-        @NonNull
-        public Builder clearTransferrableRoutes() {
-            mTransferrableRoutes.clear();
-            return this;
-        }
-
-        /**
-         * Adds a route to the transferrable routes.
-         */
-        @NonNull
-        public Builder addTransferrableRoute(@NonNull String routeId) {
-            mTransferrableRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
-            return this;
-        }
-
-        /**
-         * Removes a route from the transferrable routes.
-         */
-        @NonNull
-        public Builder removeTransferrableRoute(@NonNull String routeId) {
-            mTransferrableRoutes.remove(
-                    Objects.requireNonNull(routeId, "routeId must not be null"));
-            return this;
-        }
-
-        /**
-         * Sets control hints.
-         */
-        @NonNull
-        public Builder setControlHints(@Nullable Bundle controlHints) {
-            mControlHints = controlHints;
-            return this;
-        }
-
-        /**
-         * Builds a route session info.
-         */
-        @NonNull
-        public RouteSessionInfo build() {
-            return new RouteSessionInfo(this);
-        }
-    }
-}
diff --git a/media/java/android/media/RoutingSessionInfo.aidl b/media/java/android/media/RoutingSessionInfo.aidl
new file mode 100644
index 0000000..7b8e3d9
--- /dev/null
+++ b/media/java/android/media/RoutingSessionInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 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.media;
+
+parcelable RoutingSessionInfo;
diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java
new file mode 100644
index 0000000..5383ea2
--- /dev/null
+++ b/media/java/android/media/RoutingSessionInfo.java
@@ -0,0 +1,531 @@
+/*
+ * Copyright 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.media;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Describes a routing session which is created when a media route is selected.
+ */
+public final class RoutingSessionInfo implements Parcelable {
+    @NonNull
+    public static final Creator<RoutingSessionInfo> CREATOR =
+            new Creator<RoutingSessionInfo>() {
+                @Override
+                public RoutingSessionInfo createFromParcel(Parcel in) {
+                    return new RoutingSessionInfo(in);
+                }
+                @Override
+                public RoutingSessionInfo[] newArray(int size) {
+                    return new RoutingSessionInfo[size];
+                }
+            };
+
+    private static final String TAG = "RoutingSessionInfo";
+
+    final String mId;
+    final String mClientPackageName;
+    @Nullable
+    final String mProviderId;
+    final List<String> mSelectedRoutes;
+    final List<String> mSelectableRoutes;
+    final List<String> mDeselectableRoutes;
+    final List<String> mTransferrableRoutes;
+    @Nullable
+    final Bundle mControlHints;
+    final boolean mIsSystemSession;
+
+    RoutingSessionInfo(@NonNull Builder builder) {
+        Objects.requireNonNull(builder, "builder must not be null.");
+
+        mId = builder.mId;
+        mClientPackageName = builder.mClientPackageName;
+        mProviderId = builder.mProviderId;
+
+        // TODO: Needs to check that the routes already have unique IDs.
+        mSelectedRoutes = Collections.unmodifiableList(
+                convertToUniqueRouteIds(builder.mSelectedRoutes));
+        mSelectableRoutes = Collections.unmodifiableList(
+                convertToUniqueRouteIds(builder.mSelectableRoutes));
+        mDeselectableRoutes = Collections.unmodifiableList(
+                convertToUniqueRouteIds(builder.mDeselectableRoutes));
+        mTransferrableRoutes = Collections.unmodifiableList(
+                convertToUniqueRouteIds(builder.mTransferrableRoutes));
+
+        mControlHints = builder.mControlHints;
+        mIsSystemSession = builder.mIsSystemSession;
+    }
+
+    RoutingSessionInfo(@NonNull Parcel src) {
+        Objects.requireNonNull(src, "src must not be null.");
+
+        mId = ensureString(src.readString());
+        mClientPackageName = ensureString(src.readString());
+        mProviderId = src.readString();
+
+        mSelectedRoutes = ensureList(src.createStringArrayList());
+        mSelectableRoutes = ensureList(src.createStringArrayList());
+        mDeselectableRoutes = ensureList(src.createStringArrayList());
+        mTransferrableRoutes = ensureList(src.createStringArrayList());
+
+        mControlHints = src.readBundle();
+        mIsSystemSession = src.readBoolean();
+    }
+
+    private static String ensureString(String str) {
+        if (str != null) {
+            return str;
+        }
+        return "";
+    }
+
+    private static <T> List<T> ensureList(List<? extends T> list) {
+        if (list != null) {
+            return Collections.unmodifiableList(list);
+        }
+        return Collections.emptyList();
+    }
+
+    /**
+     * Gets the id of the session. The sessions which are given by {@link MediaRouter2} will have
+     * unique IDs.
+     * <p>
+     * In order to ensure uniqueness in {@link MediaRouter2} side, the value of this method
+     * can be different from what was set in {@link MediaRoute2ProviderService}.
+     *
+     * @see Builder#Builder(String, String)
+     */
+    @NonNull
+    public String getId() {
+        if (mProviderId != null) {
+            return MediaRouter2Utils.toUniqueId(mProviderId, mId);
+        } else {
+            return mId;
+        }
+    }
+
+    /**
+     * Gets the original id set by {@link Builder#Builder(String, String)}.
+     * @hide
+     */
+    @NonNull
+    public String getOriginalId() {
+        return mId;
+    }
+
+    /**
+     * Gets the client package name of the session
+     */
+    @NonNull
+    public String getClientPackageName() {
+        return mClientPackageName;
+    }
+
+    /**
+     * Gets the provider id of the session.
+     * @hide
+     */
+    @Nullable
+    public String getProviderId() {
+        return mProviderId;
+    }
+
+    /**
+     * Gets the list of ids of selected routes for the session. It shouldn't be empty.
+     */
+    @NonNull
+    public List<String> getSelectedRoutes() {
+        return mSelectedRoutes;
+    }
+
+    /**
+     * Gets the list of ids of selectable routes for the session.
+     */
+    @NonNull
+    public List<String> getSelectableRoutes() {
+        return mSelectableRoutes;
+    }
+
+    /**
+     * Gets the list of ids of deselectable routes for the session.
+     */
+    @NonNull
+    public List<String> getDeselectableRoutes() {
+        return mDeselectableRoutes;
+    }
+
+    /**
+     * Gets the list of ids of transferrable routes for the session.
+     */
+    @NonNull
+    public List<String> getTransferrableRoutes() {
+        return mTransferrableRoutes;
+    }
+
+    /**
+     * Gets the control hints
+     */
+    @Nullable
+    public Bundle getControlHints() {
+        return mControlHints;
+    }
+
+    /**
+     * Gets whether this session is in system media route provider.
+     * @hide
+     */
+    @Nullable
+    public boolean isSystemSession() {
+        return mIsSystemSession;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mId);
+        dest.writeString(mClientPackageName);
+        dest.writeString(mProviderId);
+        dest.writeStringList(mSelectedRoutes);
+        dest.writeStringList(mSelectableRoutes);
+        dest.writeStringList(mDeselectableRoutes);
+        dest.writeStringList(mTransferrableRoutes);
+        dest.writeBundle(mControlHints);
+        dest.writeBoolean(mIsSystemSession);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof RoutingSessionInfo)) {
+            return false;
+        }
+
+        RoutingSessionInfo other = (RoutingSessionInfo) obj;
+        return Objects.equals(mId, other.mId)
+                && Objects.equals(mClientPackageName, other.mClientPackageName)
+                && Objects.equals(mProviderId, other.mProviderId)
+                && Objects.equals(mSelectedRoutes, other.mSelectedRoutes)
+                && Objects.equals(mSelectableRoutes, other.mSelectableRoutes)
+                && Objects.equals(mDeselectableRoutes, other.mDeselectableRoutes)
+                && Objects.equals(mTransferrableRoutes, other.mTransferrableRoutes);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mId, mClientPackageName, mProviderId,
+                mSelectedRoutes, mSelectableRoutes, mDeselectableRoutes, mTransferrableRoutes);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder result = new StringBuilder()
+                .append("RoutingSessionInfo{ ")
+                .append("sessionId=").append(mId)
+                .append(", selectedRoutes={")
+                .append(String.join(",", mSelectedRoutes))
+                .append("}")
+                .append(", selectableRoutes={")
+                .append(String.join(",", mSelectableRoutes))
+                .append("}")
+                .append(", deselectableRoutes={")
+                .append(String.join(",", mDeselectableRoutes))
+                .append("}")
+                .append(", transferrableRoutes={")
+                .append(String.join(",", mTransferrableRoutes))
+                .append("}")
+                .append(" }");
+        return result.toString();
+    }
+
+    private List<String> convertToUniqueRouteIds(@NonNull List<String> routeIds) {
+        if (routeIds == null) {
+            Log.w(TAG, "routeIds is null. Returning an empty list");
+            return Collections.emptyList();
+        }
+
+        // mProviderId can be null if not set. Return the original list for this case.
+        if (mProviderId == null) {
+            return routeIds;
+        }
+
+        List<String> result = new ArrayList<>();
+        for (String routeId : routeIds) {
+            result.add(MediaRouter2Utils.toUniqueId(mProviderId, routeId));
+        }
+        return result;
+    }
+
+    /**
+     * Builder class for {@link RoutingSessionInfo}.
+     */
+    public static final class Builder {
+        // TODO: Reorder these (important ones first)
+        final String mId;
+        final String mClientPackageName;
+        String mProviderId;
+        final List<String> mSelectedRoutes;
+        final List<String> mSelectableRoutes;
+        final List<String> mDeselectableRoutes;
+        final List<String> mTransferrableRoutes;
+        Bundle mControlHints;
+        boolean mIsSystemSession;
+
+        /**
+         * Constructor for builder to create {@link RoutingSessionInfo}.
+         * <p>
+         * In order to ensure ID uniqueness in {@link MediaRouter2} side, the value of
+         * {@link RoutingSessionInfo#getId()} can be different from what was set in
+         * {@link MediaRoute2ProviderService}.
+         * </p>
+         *
+         * @param id ID of the session. Must not be empty.
+         * @param clientPackageName package name of the client app which uses this session.
+         *                          If is is unknown, then just use an empty string.
+         * @see MediaRoute2Info#getId()
+         */
+        public Builder(@NonNull String id, @NonNull String clientPackageName) {
+            if (TextUtils.isEmpty(id)) {
+                throw new IllegalArgumentException("id must not be empty");
+            }
+            Objects.requireNonNull(clientPackageName, "clientPackageName must not be null");
+
+            mId = id;
+            mClientPackageName = clientPackageName;
+            mSelectedRoutes = new ArrayList<>();
+            mSelectableRoutes = new ArrayList<>();
+            mDeselectableRoutes = new ArrayList<>();
+            mTransferrableRoutes = new ArrayList<>();
+        }
+
+        /**
+         * Constructor for builder to create {@link RoutingSessionInfo} with
+         * existing {@link RoutingSessionInfo} instance.
+         *
+         * @param sessionInfo the existing instance to copy data from.
+         */
+        public Builder(@NonNull RoutingSessionInfo sessionInfo) {
+            Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
+
+            mId = sessionInfo.mId;
+            mClientPackageName = sessionInfo.mClientPackageName;
+            mProviderId = sessionInfo.mProviderId;
+
+            mSelectedRoutes = new ArrayList<>(sessionInfo.mSelectedRoutes);
+            mSelectableRoutes = new ArrayList<>(sessionInfo.mSelectableRoutes);
+            mDeselectableRoutes = new ArrayList<>(sessionInfo.mDeselectableRoutes);
+            mTransferrableRoutes = new ArrayList<>(sessionInfo.mTransferrableRoutes);
+
+            mControlHints = sessionInfo.mControlHints;
+            mIsSystemSession = sessionInfo.mIsSystemSession;
+        }
+
+        /**
+         * Sets the provider ID of the session.
+         *
+         * @hide
+         */
+        @NonNull
+        public Builder setProviderId(@NonNull String providerId) {
+            if (TextUtils.isEmpty(providerId)) {
+                throw new IllegalArgumentException("providerId must not be empty");
+            }
+            mProviderId = providerId;
+            return this;
+        }
+
+        /**
+         * Clears the selected routes.
+         */
+        @NonNull
+        public Builder clearSelectedRoutes() {
+            mSelectedRoutes.clear();
+            return this;
+        }
+
+        /**
+         * Adds a route to the selected routes. The {@code routeId} must not be empty.
+         */
+        @NonNull
+        public Builder addSelectedRoute(@NonNull String routeId) {
+            if (TextUtils.isEmpty(routeId)) {
+                throw new IllegalArgumentException("routeId must not be empty");
+            }
+            mSelectedRoutes.add(routeId);
+            return this;
+        }
+
+        /**
+         * Removes a route from the selected routes. The {@code routeId} must not be empty.
+         */
+        @NonNull
+        public Builder removeSelectedRoute(@NonNull String routeId) {
+            if (TextUtils.isEmpty(routeId)) {
+                throw new IllegalArgumentException("routeId must not be empty");
+            }
+            mSelectedRoutes.remove(routeId);
+            return this;
+        }
+
+        /**
+         * Clears the selectable routes.
+         */
+        @NonNull
+        public Builder clearSelectableRoutes() {
+            mSelectableRoutes.clear();
+            return this;
+        }
+
+        /**
+         * Adds a route to the selectable routes. The {@code routeId} must not be empty.
+         */
+        @NonNull
+        public Builder addSelectableRoute(@NonNull String routeId) {
+            if (TextUtils.isEmpty(routeId)) {
+                throw new IllegalArgumentException("routeId must not be empty");
+            }
+            mSelectableRoutes.add(routeId);
+            return this;
+        }
+
+        /**
+         * Removes a route from the selectable routes. The {@code routeId} must not be empty.
+         */
+        @NonNull
+        public Builder removeSelectableRoute(@NonNull String routeId) {
+            if (TextUtils.isEmpty(routeId)) {
+                throw new IllegalArgumentException("routeId must not be empty");
+            }
+            mSelectableRoutes.remove(routeId);
+            return this;
+        }
+
+        /**
+         * Clears the deselectable routes.
+         */
+        @NonNull
+        public Builder clearDeselectableRoutes() {
+            mDeselectableRoutes.clear();
+            return this;
+        }
+
+        /**
+         * Adds a route to the deselectable routes. The {@code routeId} must not be empty.
+         */
+        @NonNull
+        public Builder addDeselectableRoute(@NonNull String routeId) {
+            if (TextUtils.isEmpty(routeId)) {
+                throw new IllegalArgumentException("routeId must not be empty");
+            }
+            mDeselectableRoutes.add(routeId);
+            return this;
+        }
+
+        /**
+         * Removes a route from the deselectable routes. The {@code routeId} must not be empty.
+         */
+        @NonNull
+        public Builder removeDeselectableRoute(@NonNull String routeId) {
+            if (TextUtils.isEmpty(routeId)) {
+                throw new IllegalArgumentException("routeId must not be empty");
+            }
+            mDeselectableRoutes.remove(routeId);
+            return this;
+        }
+
+        /**
+         * Clears the transferrable routes.
+         */
+        @NonNull
+        public Builder clearTransferrableRoutes() {
+            mTransferrableRoutes.clear();
+            return this;
+        }
+
+        /**
+         * Adds a route to the transferrable routes. The {@code routeId} must not be empty.
+         */
+        @NonNull
+        public Builder addTransferrableRoute(@NonNull String routeId) {
+            if (TextUtils.isEmpty(routeId)) {
+                throw new IllegalArgumentException("routeId must not be empty");
+            }
+            mTransferrableRoutes.add(routeId);
+            return this;
+        }
+
+        /**
+         * Removes a route from the transferrable routes. The {@code routeId} must not be empty.
+         */
+        @NonNull
+        public Builder removeTransferrableRoute(@NonNull String routeId) {
+            if (TextUtils.isEmpty(routeId)) {
+                throw new IllegalArgumentException("routeId must not be empty");
+            }
+            mTransferrableRoutes.remove(routeId);
+            return this;
+        }
+
+        /**
+         * Sets control hints.
+         */
+        @NonNull
+        public Builder setControlHints(@Nullable Bundle controlHints) {
+            mControlHints = controlHints;
+            return this;
+        }
+
+        /**
+         * Sets whether this session is in system media route provider.
+         * @hide
+         */
+        @NonNull
+        public Builder setSystemSession(boolean isSystemSession) {
+            mIsSystemSession = isSystemSession;
+            return this;
+        }
+
+        /**
+         * Builds a routing session info.
+         *
+         * @throws IllegalArgumentException if no selected routes are added.
+         */
+        @NonNull
+        public RoutingSessionInfo build() {
+            if (mSelectedRoutes.isEmpty()) {
+                throw new IllegalArgumentException("selectedRoutes must not be empty");
+            }
+            return new RoutingSessionInfo(this);
+        }
+    }
+}
diff --git a/media/java/android/media/Utils.java b/media/java/android/media/Utils.java
index d942bb6..7a4e7b8 100644
--- a/media/java/android/media/Utils.java
+++ b/media/java/android/media/Utils.java
@@ -16,12 +16,17 @@
 
 package android.media;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Environment;
 import android.os.FileUtils;
+import android.os.Handler;
 import android.provider.OpenableColumns;
 import android.util.Log;
 import android.util.Pair;
@@ -29,14 +34,26 @@
 import android.util.Rational;
 import android.util.Size;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.util.Arrays;
 import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Objects;
 import java.util.Vector;
+import java.util.concurrent.Executor;
 
-// package private
-class Utils {
+/**
+ * Media Utilities
+ *
+ * This class is hidden but public to allow CTS testing and verification
+ * of the static methods and classes.
+ *
+ * @hide
+ */
+public class Utils {
     private static final String TAG = "Utils";
 
     /**
@@ -381,4 +398,265 @@
         // it already represents the file's name.
         return uri.toString();
     }
+
+    /**
+     * {@code ListenerList} is a helper class that delivers events to listeners.
+     *
+     * It is written to isolate the <strong>mechanics</strong> of event delivery from the
+     * <strong>details</strong> of those events.
+     *
+     * The {@code ListenerList} is parameterized on the generic type {@code V}
+     * of the object delivered by {@code notify()}.
+     * This gives compile time type safety over run-time casting of a general {@code Object},
+     * much like {@code HashMap&lt;String, Object&gt;} does not give type safety of the
+     * stored {@code Object} value and may allow
+     * permissive storage of {@code Object}s that are not expected by users of the
+     * {@code HashMap}, later resulting in run-time cast exceptions that
+     * could have been caught by replacing
+     * {@code Object} with a more precise type to enforce a compile time contract.
+     *
+     * The {@code ListenerList} is implemented as a single method callback
+     * - or a "listener" according to Android style guidelines.
+     *
+     * The {@code ListenerList} can be trivially extended by a suitable lambda to implement
+     * a <strong> multiple method abstract class</strong> "callback",
+     * in which the generic type {@code V} could be an {@code Object}
+     * to encapsulate the details of the parameters of each callback method, and
+     * {@code instanceof} could be used to disambiguate which callback method to use.
+     * A {@link Bundle} could alternatively encapsulate those generic parameters,
+     * perhaps more conveniently.
+     * Again, this is a detail of the event, not the mechanics of the event delivery,
+     * which this class is concerned with.
+     *
+     * For details on how to use this class to implement a <strong>single listener</strong>
+     * {@code ListenerList}, see notes on {@link #add}.
+     *
+     * For details on how to optimize this class to implement
+     * a listener based on {@link Handler}s
+     * instead of {@link Executor}s, see{@link #ListenerList(boolean, boolean, boolean)}.
+     *
+     * This is a TestApi for CTS Unit Testing, not exposed for general Application use.
+     * @hide
+     *
+     * @param <V> The class of the object returned to the listener.
+     */
+    @TestApi
+    public static class ListenerList<V> {
+        /**
+         * The Listener interface for callback.
+         *
+         * @param <V> The class of the object returned to the listener
+         */
+        public interface Listener<V> {
+            /**
+             * General event listener interface which is managed by the {@code ListenerList}.
+             *
+             * @param eventCode is an integer representing the event type. This is an
+             *     implementation defined parameter.
+             * @param info is the object returned to the listener.  It is expected
+             *     that the listener makes a private copy of the {@code info} object before
+             *     modification, as it is the same instance passed to all listeners.
+             *     This is an implementation defined parameter that may be null.
+             */
+            void onEvent(int eventCode, @Nullable V info);
+        }
+
+        private interface ListenerWithCancellation<V> extends Listener<V> {
+            void cancel();
+        }
+
+        /**
+         * Default {@code ListenerList} constructor for {@link Executor} based implementation.
+         *
+         * TODO: consider adding a "name" for debugging if this is used for
+         * multiple listener implementations.
+         */
+        public ListenerList() {
+            this(true /* restrictSingleCallerOnEvent */,
+                true /* clearCallingIdentity */,
+                false /* forceRemoveConsistency*/);
+        }
+
+        /**
+         * Specific {@code ListenerList} constructor for customization.
+         *
+         * See the internal notes for the corresponding private variables on the behavior of
+         * the boolean configuration parameters.
+         *
+         * {@code ListenerList(true, true, false)} is the default and used for
+         * {@link Executor} based notification implementation.
+         *
+         * {@code ListenerList(false, false, false)} may be used for as an optimization
+         * where the {@link Executor} is actually a {@link Handler} post.
+         *
+         * @param restrictSingleCallerOnEvent whether the listener will only be called by
+         *     a single thread at a time.
+         * @param clearCallingIdentity whether the binder calling identity on
+         *     {@link #notify} is cleared.
+         * @param forceRemoveConsistency whether remove() guarantees no more callbacks to
+         *     the listener immediately after the call.
+         */
+        public ListenerList(boolean restrictSingleCallerOnEvent,
+                boolean clearCallingIdentity,
+                boolean forceRemoveConsistency) {
+            mRestrictSingleCallerOnEvent = restrictSingleCallerOnEvent;
+            mClearCallingIdentity = clearCallingIdentity;
+            mForceRemoveConsistency = forceRemoveConsistency;
+        }
+
+        /**
+         * Adds a listener to the {@code ListenerList}.
+         *
+         * The {@code ListenerList} is most often used to hold {@code multiple} listeners.
+         *
+         * Per Android style, for a single method Listener interface, the add and remove
+         * would be wrapped in "addSomeListener" or "removeSomeListener";
+         * or a lambda implemented abstract class callback, wrapped in
+         * "registerSomeCallback" or "unregisterSomeCallback".
+         *
+         * We allow a general {@code key} to be attached to add and remove that specific
+         * listener.  It could be the {@code listener} object itself.
+         *
+         * For some implementations, there may be only a {@code single} listener permitted.
+         *
+         * Per Android style, for a single listener {@code ListenerList},
+         * the naming of the wrapping call to {@link #add} would be
+         * "setSomeListener" with a nullable listener, which would be null
+         * to call {@link #remove}.
+         *
+         * In that case, the caller may use this {@link #add} with a single constant object for
+         * the {@code key} to enforce only one Listener in the {@code ListenerList}.
+         * Likewise on remove it would use that
+         * same single constant object to remove the listener.
+         * That {@code key} object could be the {@code ListenerList} itself for convenience.
+         *
+         * @param key is a unique object that is used to identify the listener
+         *     when {@code remove()} is called. It can be the listener itself.
+         * @param executor is used to execute the callback.
+         * @param listener is the {@link AudioTrack.ListenerList.Listener}
+         *     interface to be called upon {@link notify}.
+         */
+        public void add(
+                @NonNull Object key, @NonNull Executor executor, @NonNull Listener<V> listener) {
+            Objects.requireNonNull(key);
+            Objects.requireNonNull(executor);
+            Objects.requireNonNull(listener);
+
+            // construct wrapper outside of lock.
+            ListenerWithCancellation<V> listenerWithCancellation =
+                    new ListenerWithCancellation<V>() {
+                        private final Object mLock = new Object(); // our lock is per Listener.
+                        private volatile boolean mCancelled = false; // atomic rmw not needed.
+
+                        @Override
+                        public void onEvent(int eventCode, V info) {
+                            executor.execute(() -> {
+                                // Note deep execution of locking and cancellation
+                                // so this works after posting on different threads.
+                                if (mRestrictSingleCallerOnEvent || mForceRemoveConsistency) {
+                                    synchronized (mLock) {
+                                        if (mCancelled) return;
+                                        listener.onEvent(eventCode, info);
+                                    }
+                                } else {
+                                    if (mCancelled) return;
+                                    listener.onEvent(eventCode, info);
+                                }
+                            });
+                        }
+
+                        @Override
+                        public void cancel() {
+                            if (mForceRemoveConsistency) {
+                                synchronized (mLock) {
+                                    mCancelled = true;
+                                }
+                            } else {
+                                mCancelled = true;
+                            }
+                        }
+                    };
+
+            synchronized (mListeners) {
+                // TODO: consider an option to check the existence of the key
+                // and throw an ISE if it exists.
+                mListeners.put(key, listenerWithCancellation);  // replaces old value
+            }
+        }
+
+        /**
+         * Removes a listener from the {@code ListenerList}.
+         *
+         * @param key the unique object associated with the listener during {@link #add}.
+         */
+        public void remove(@NonNull Object key) {
+            Objects.requireNonNull(key);
+
+            ListenerWithCancellation<V> listener;
+            synchronized (mListeners) {
+                listener = mListeners.get(key);
+                if (listener == null) { // TODO: consider an option to throw ISE Here.
+                    return;
+                }
+                mListeners.remove(key);  // removes if exist
+            }
+
+            // cancel outside of lock
+            listener.cancel();
+        }
+
+        /**
+         * Notifies all listeners on the List.
+         *
+         * @param eventCode to pass to all listeners.
+         * @param info to pass to all listeners. This is an implemention defined parameter
+         *     which may be {@code null}.
+         */
+        public void notify(int eventCode, @Nullable V info) {
+            Object[] listeners; // note we can't cast an object array to a listener array
+            synchronized (mListeners) {
+                if (mListeners.size() == 0) {
+                    return;
+                }
+                listeners = mListeners.values().toArray(); // guarantees a copy.
+            }
+
+            // notify outside of lock.
+            final Long identity = mClearCallingIdentity ? Binder.clearCallingIdentity() : null;
+            try {
+                for (Object object : listeners) {
+                    final ListenerWithCancellation<V> listener =
+                            (ListenerWithCancellation<V>) object;
+                    listener.onEvent(eventCode, info);
+                }
+            } finally {
+                if (identity != null) {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            }
+        }
+
+        @GuardedBy("mListeners")
+        private HashMap<Object, ListenerWithCancellation<V>> mListeners = new HashMap<>();
+
+        // An Executor may run in multiple threads, whereas a Handler runs on a single Looper.
+        // Should be true for an Executor to avoid concurrent calling into the same listener,
+        // can be false for a Handler as a Handler forces single thread caller for each listener.
+        private final boolean mRestrictSingleCallerOnEvent; // default true
+
+        // An Executor may run in the calling thread, whereas a handler will post to the Looper.
+        // Should be true for an Executor to prevent privilege escalation,
+        // can be false for a Handler as its thread is not the calling binder thread.
+        private final boolean mClearCallingIdentity; // default true
+
+        // Guaranteeing no listener callbacks after removal requires taking the same lock for the
+        // remove as the callback; this is a reversal in calling layers,
+        // hence the risk of lock order inversion is great.
+        //
+        // Set to true only if you can control the caller's listen and remove methods and/or
+        // the threading of the Executor used for each listener.
+        // When set to false, we do not lock, but still do a best effort to cancel messages
+        // on the fly.
+        private final boolean mForceRemoveConsistency; // default false
+    }
 }
diff --git a/media/java/android/media/VolumeProvider.java b/media/java/android/media/VolumeProvider.java
index 8f68cbd..ed272d5 100644
--- a/media/java/android/media/VolumeProvider.java
+++ b/media/java/android/media/VolumeProvider.java
@@ -16,6 +16,7 @@
 package android.media;
 
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.media.session.MediaSession;
 
 import java.lang.annotation.Retention;
@@ -60,6 +61,7 @@
 
     private final int mControlType;
     private final int mMaxVolume;
+    private final String mControlId;
     private int mCurrentVolume;
     private Callback mCallback;
 
@@ -73,10 +75,28 @@
      * @param maxVolume The maximum allowed volume.
      * @param currentVolume The current volume on the output.
      */
+
     public VolumeProvider(@ControlType int volumeControl, int maxVolume, int currentVolume) {
+        this(volumeControl, maxVolume, currentVolume, null);
+    }
+
+    /**
+     * Create a new volume provider for handling volume events. You must specify
+     * the type of volume control, the maximum volume that can be used, and the
+     * current volume on the output.
+     *
+     * @param volumeControl The method for controlling volume that is used by
+     *            this provider.
+     * @param maxVolume The maximum allowed volume.
+     * @param currentVolume The current volume on the output.
+     * @param volumeControlId The volume control id of this provider.
+     */
+    public VolumeProvider(@ControlType int volumeControl, int maxVolume, int currentVolume,
+            @Nullable String volumeControlId) {
         mControlType = volumeControl;
         mMaxVolume = maxVolume;
         mCurrentVolume = currentVolume;
+        mControlId = volumeControlId;
     }
 
     /**
@@ -122,6 +142,17 @@
     }
 
     /**
+     * Gets the volume control id. It can be used to identify which volume provider is
+     * used by the session.
+     *
+     * @return the volume control id or {@code null} if it isn't set.
+     */
+    @Nullable
+    public final String getVolumeControlId() {
+        return mControlId;
+    }
+
+    /**
      * Override to handle requests to set the volume of the current output.
      * After the volume has been modified {@link #setCurrentVolume} must be
      * called to notify the system.
diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.java b/media/java/android/media/audiopolicy/AudioMixingRule.java
index 8c204d2..bca3fa7 100644
--- a/media/java/android/media/audiopolicy/AudioMixingRule.java
+++ b/media/java/android/media/audiopolicy/AudioMixingRule.java
@@ -73,6 +73,12 @@
      * parameter is an instance of {@link java.lang.Integer}.
      */
     public static final int RULE_MATCH_UID = 0x1 << 2;
+    /**
+     * A rule requiring the userId of the audio stream to match that specified.
+     * This mixing rule can be added with {@link Builder#addMixRule(int, Object)} where the Object
+     * parameter is an instance of {@link java.lang.Integer}.
+     */
+    public static final int RULE_MATCH_USERID = 0x1 << 3;
 
     private final static int RULE_EXCLUSION_MASK = 0x8000;
     /**
@@ -94,6 +100,13 @@
     public static final int RULE_EXCLUDE_UID =
             RULE_EXCLUSION_MASK | RULE_MATCH_UID;
 
+    /**
+     * @hide
+     * A rule requiring the userId information to differ.
+     */
+    public static final int RULE_EXCLUDE_USERID =
+            RULE_EXCLUSION_MASK | RULE_MATCH_USERID;
+
     /** @hide */
     public static final class AudioMixMatchCriterion {
         @UnsupportedAppUsage
@@ -125,19 +138,20 @@
             dest.writeInt(mRule);
             final int match_rule = mRule & ~RULE_EXCLUSION_MASK;
             switch (match_rule) {
-            case RULE_MATCH_ATTRIBUTE_USAGE:
-                dest.writeInt(mAttr.getUsage());
-                break;
-            case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
-                dest.writeInt(mAttr.getCapturePreset());
-                break;
-            case RULE_MATCH_UID:
-                dest.writeInt(mIntProp);
-                break;
-            default:
-                Log.e("AudioMixMatchCriterion", "Unknown match rule" + match_rule
-                        + " when writing to Parcel");
-                dest.writeInt(-1);
+                case RULE_MATCH_ATTRIBUTE_USAGE:
+                    dest.writeInt(mAttr.getUsage());
+                    break;
+                case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
+                    dest.writeInt(mAttr.getCapturePreset());
+                    break;
+                case RULE_MATCH_UID:
+                case RULE_MATCH_USERID:
+                    dest.writeInt(mIntProp);
+                    break;
+                default:
+                    Log.e("AudioMixMatchCriterion", "Unknown match rule" + match_rule
+                            + " when writing to Parcel");
+                    dest.writeInt(-1);
             }
         }
 
@@ -203,6 +217,7 @@
             case RULE_MATCH_ATTRIBUTE_USAGE:
             case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
             case RULE_MATCH_UID:
+            case RULE_MATCH_USERID:
                 return true;
             default:
                 return false;
@@ -225,6 +240,7 @@
             case RULE_MATCH_ATTRIBUTE_USAGE:
             case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
             case RULE_MATCH_UID:
+            case RULE_MATCH_USERID:
                 return true;
             default:
                 return false;
@@ -234,11 +250,12 @@
     private static boolean isPlayerRule(int rule) {
         final int match_rule = rule & ~RULE_EXCLUSION_MASK;
         switch (match_rule) {
-        case RULE_MATCH_ATTRIBUTE_USAGE:
-        case RULE_MATCH_UID:
-            return true;
-        default:
-            return false;
+            case RULE_MATCH_ATTRIBUTE_USAGE:
+            case RULE_MATCH_UID:
+            case RULE_MATCH_USERID:
+                return true;
+            default:
+                return false;
         }
     }
 
@@ -319,7 +336,8 @@
          * property to match against.
          * @param rule one of {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE},
          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or
-         *     {@link AudioMixingRule#RULE_MATCH_UID}.
+         *     {@link AudioMixingRule#RULE_MATCH_UID} or
+         *     {@link AudioMixingRule#RULE_MATCH_USERID}.
          * @param property see the definition of each rule for the type to use (either an
          *     {@link AudioAttributes} or an {@link java.lang.Integer}).
          * @return the same Builder instance.
@@ -349,7 +367,8 @@
          * coming from the specified UID.
          * @param rule one of {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE},
          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or
-         *     {@link AudioMixingRule#RULE_MATCH_UID}.
+         *     {@link AudioMixingRule#RULE_MATCH_UID} or
+         *     {@link AudioMixingRule#RULE_MATCH_USERID}.
          * @param property see the definition of each rule for the type to use (either an
          *     {@link AudioAttributes} or an {@link java.lang.Integer}).
          * @return the same Builder instance.
@@ -425,6 +444,8 @@
          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or
          *     {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET},
          *     {@link AudioMixingRule#RULE_MATCH_UID}, {@link AudioMixingRule#RULE_EXCLUDE_UID}.
+         *     {@link AudioMixingRule#RULE_MATCH_USERID},
+         *     {@link AudioMixingRule#RULE_EXCLUDE_USERID}.
          * @return the same Builder instance.
          * @throws IllegalArgumentException
          */
@@ -495,6 +516,20 @@
                                 }
                             }
                             break;
+                        case RULE_MATCH_USERID:
+                            // "userid"-based rule
+                            if (criterion.mIntProp == intProp.intValue()) {
+                                if (criterion.mRule == rule) {
+                                    // rule already exists, we're done
+                                    return this;
+                                } else {
+                                    // criterion already exists with a another rule,
+                                    // it is incompatible
+                                    throw new IllegalArgumentException("Contradictory rule exists"
+                                            + " for userId " + intProp);
+                                }
+                            }
+                            break;
                     }
                 }
                 // rule didn't exist, add it
@@ -504,6 +539,7 @@
                         mCriteria.add(new AudioMixMatchCriterion(attrToMatch, rule));
                         break;
                     case RULE_MATCH_UID:
+                    case RULE_MATCH_USERID:
                         mCriteria.add(new AudioMixMatchCriterion(intProp, rule));
                         break;
                     default:
@@ -530,6 +566,7 @@
                             .setInternalCapturePreset(preset).build();
                     break;
                 case RULE_MATCH_UID:
+                case RULE_MATCH_USERID:
                     intProp = new Integer(in.readInt());
                     break;
                 default:
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index 01f1250..32a4a4f 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -51,6 +51,7 @@
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * @hide
@@ -404,7 +405,7 @@
 
     /**
      * @hide
-     * Configures the audio framework so that all audio stream originating from the given UID
+     * Configures the audio framework so that all audio streams originating from the given UID
      * can only come from a set of audio devices.
      * For this routing to be operational, a number of {@link AudioMix} instances must have been
      * previously registered on this policy, and routed to a super-set of the given audio devices
@@ -476,6 +477,78 @@
         }
     }
 
+    /**
+     * @hide
+     * Removes audio device affinity previously set by
+     * {@link #setUserIdDeviceAffinity(int, java.util.List)}.
+     * @param userId userId of the application affected.
+     * @return true if the change was successful, false otherwise.
+     */
+    @TestApi
+    @SystemApi
+    public boolean removeUserIdDeviceAffinity(int userId) {
+        synchronized (mLock) {
+            if (mStatus != POLICY_STATUS_REGISTERED) {
+                throw new IllegalStateException("Cannot use unregistered AudioPolicy");
+            }
+            final IAudioService service = getService();
+            try {
+                final int status = service.removeUserIdDeviceAffinity(this.cb(), userId);
+                return (status == AudioManager.SUCCESS);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in removeUserIdDeviceAffinity", e);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * @hide
+     * Configures the audio framework so that all audio streams originating from the given user
+     * can only come from a set of audio devices.
+     * For this routing to be operational, a number of {@link AudioMix} instances must have been
+     * previously registered on this policy, and routed to a super-set of the given audio devices
+     * with {@link AudioMix.Builder#setDevice(android.media.AudioDeviceInfo)}. Note that having
+     * multiple devices in the list doesn't imply the signals will be duplicated on the different
+     * audio devices, final routing will depend on the {@link AudioAttributes} of the sounds being
+     * played.
+     * @param userId Android user id to affect.
+     * @param devices list of devices to which the audio stream of the application may be routed.
+     * @return true if the change was successful, false otherwise.
+     */
+    @TestApi
+    @SystemApi
+    public boolean setUserIdDeviceAffinity(int userId, @NonNull List<AudioDeviceInfo> devices) {
+        Objects.requireNonNull(devices, "Illegal null list of audio devices");
+        synchronized (mLock) {
+            if (mStatus != POLICY_STATUS_REGISTERED) {
+                throw new IllegalStateException("Cannot use unregistered AudioPolicy");
+            }
+            final int[] deviceTypes = new int[devices.size()];
+            final String[] deviceAddresses = new String[devices.size()];
+            int i = 0;
+            for (AudioDeviceInfo device : devices) {
+                if (device == null) {
+                    throw new IllegalArgumentException(
+                            "Illegal null AudioDeviceInfo in setUserIdDeviceAffinity");
+                }
+                deviceTypes[i] =
+                        AudioDeviceInfo.convertDeviceTypeToInternalDevice(device.getType());
+                deviceAddresses[i] = device.getAddress();
+                i++;
+            }
+            final IAudioService service = getService();
+            try {
+                final int status = service.setUserIdDeviceAffinity(this.cb(),
+                        userId, deviceTypes, deviceAddresses);
+                return (status == AudioManager.SUCCESS);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in setUserIdDeviceAffinity", e);
+                return false;
+            }
+        }
+    }
+
     public void setRegistration(String regId) {
         synchronized (mLock) {
             mRegistrationId = regId;
@@ -719,9 +792,14 @@
                     if (track == null) {
                         break;
                     }
-                    // TODO: add synchronous versions
-                    track.stop();
-                    track.flush();
+                    try {
+                        // TODO: add synchronous versions
+                        track.stop();
+                        track.flush();
+                    } catch (IllegalStateException e) {
+                        // ignore exception, AudioTrack could have already been stopped or
+                        // released by the user of the AudioPolicy
+                    }
                 }
             }
             if (mCaptors != null) {
@@ -730,8 +808,13 @@
                     if (record == null) {
                         break;
                     }
-                    // TODO: if needed: implement an invalidate method
-                    record.stop();
+                    try {
+                        // TODO: if needed: implement an invalidate method
+                        record.stop();
+                    } catch (IllegalStateException e) {
+                        // ignore exception, AudioRecord could have already been stopped or
+                        // released by the user of the AudioPolicy
+                    }
                 }
             }
         }
diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
index c4ba0c1..b048158 100644
--- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java
+++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
@@ -197,6 +197,14 @@
                         textDump += "  exclude UID ";
                         textDump += criterion.mIntProp;
                         break;
+                    case AudioMixingRule.RULE_MATCH_USERID:
+                        textDump += "  match userId ";
+                        textDump += criterion.mIntProp;
+                        break;
+                    case AudioMixingRule.RULE_EXCLUDE_USERID:
+                        textDump += "  exclude userId ";
+                        textDump += criterion.mIntProp;
+                        break;
                     default:
                         textDump += "invalid rule!";
                 }
diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl
index 4d68a6a..21378c8 100644
--- a/media/java/android/media/session/ISession.aidl
+++ b/media/java/android/media/session/ISession.aidl
@@ -48,6 +48,6 @@
 
     // These commands relate to volume handling
     void setPlaybackToLocal(in AudioAttributes attributes);
-    void setPlaybackToRemote(int control, int max);
+    void setPlaybackToRemote(int control, int max, @nullable String controlId);
     void setCurrentVolume(int currentVolume);
 }
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index 1812d9c..c2f6206 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -964,16 +964,26 @@
         private final int mMaxVolume;
         private final int mCurrentVolume;
         private final AudioAttributes mAudioAttrs;
+        private final String mVolumeControlId;
 
         /**
          * @hide
          */
         public PlaybackInfo(int type, int control, int max, int current, AudioAttributes attrs) {
+            this(type, control, max, current, attrs, null);
+        }
+
+        /**
+         * @hide
+         */
+        public PlaybackInfo(int type, int control, int max, int current, AudioAttributes attrs,
+                String volumeControlId) {
             mVolumeType = type;
             mVolumeControl = control;
             mMaxVolume = max;
             mCurrentVolume = current;
             mAudioAttrs = attrs;
+            mVolumeControlId = volumeControlId;
         }
 
         PlaybackInfo(Parcel in) {
@@ -982,6 +992,7 @@
             mMaxVolume = in.readInt();
             mCurrentVolume = in.readInt();
             mAudioAttrs = in.readParcelable(null);
+            mVolumeControlId = in.readString();
         }
 
         /**
@@ -1042,11 +1053,24 @@
             return mAudioAttrs;
         }
 
+        /**
+         * Gets the volume control ID for this session. It can be used to identify which
+         * volume provider is used by the session.
+         *
+         * @return the volume control ID for this session or {@code null} if it's local playback
+         * or not set.
+         * @see VolumeProvider#getVolumeControlId()
+         */
+        @Nullable
+        public String getVolumeControlId() {
+            return mVolumeControlId;
+        }
+
         @Override
         public String toString() {
             return "volumeType=" + mVolumeType + ", volumeControl=" + mVolumeControl
                     + ", maxVolume=" + mMaxVolume + ", currentVolume=" + mCurrentVolume
-                    + ", audioAttrs=" + mAudioAttrs;
+                    + ", audioAttrs=" + mAudioAttrs + ", volumeControlId=" + mVolumeControlId;
         }
 
         @Override
@@ -1061,6 +1085,7 @@
             dest.writeInt(mMaxVolume);
             dest.writeInt(mCurrentVolume);
             dest.writeParcelable(mAudioAttrs, flags);
+            dest.writeString(mVolumeControlId);
         }
 
         public static final @android.annotation.NonNull Parcelable.Creator<PlaybackInfo> CREATOR =
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 3adee59..870c1b4 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -146,6 +146,8 @@
      * the system but will not be published until {@link #setActive(boolean)
      * setActive(true)} is called. You must call {@link #release()} when
      * finished with the session.
+     * <p>
+     * Note that {@link RuntimeException} will be thrown if an app creates too many sessions.
      *
      * @param context The context to use to create the session.
      * @param tag A short name for debugging purposes.
@@ -163,6 +165,8 @@
      * The {@code sessionInfo} can include additional unchanging information about this session.
      * For example, it can include the version of the application, or the list of the custom
      * commands that this session supports.
+     * <p>
+     * Note that {@link RuntimeException} will be thrown if an app creates too many sessions.
      *
      * @param context The context to use to create the session.
      * @param tag A short name for debugging purposes.
@@ -335,7 +339,7 @@
 
         try {
             mBinder.setPlaybackToRemote(volumeProvider.getVolumeControl(),
-                    volumeProvider.getMaxVolume());
+                    volumeProvider.getMaxVolume(), volumeProvider.getVolumeControlId());
             mBinder.setCurrentVolume(volumeProvider.getCurrentVolume());
         } catch (RemoteException e) {
             Log.wtf(TAG, "Failure in setPlaybackToRemote.", e);
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index aff7257..aece39d 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -940,16 +940,15 @@
         /**
          * Called when a media key event is dispatched through the media session service. The
          * session token can be {@link null} if the framework has sent the media key event to the
-         * media button receiver to revive the media app's playback.
-         *
-         * the session is dead when , but the framework sent
+         * media button receiver to revive the media app's playback after the corresponding session
+         * is released.
          *
          * @param event Dispatched media key event.
          * @param packageName Package
          * @param sessionToken The media session's token. Can be {@code null}.
          */
         default void onMediaKeyEventDispatched(@NonNull KeyEvent event, @NonNull String packageName,
-                @NonNull MediaSession.Token sessionToken) { }
+                @Nullable MediaSession.Token sessionToken) { }
     }
 
     /**
diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
index 56e5566..118f65c 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
@@ -22,9 +22,10 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
 import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
 import android.media.AudioFormat;
 import android.os.Handler;
@@ -75,7 +76,9 @@
             value = {
                 RECOGNITION_FLAG_NONE,
                 RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO,
-                RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS
+                RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS,
+                RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION,
+                    RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION,
             })
     public @interface RecognitionFlags {}
 
@@ -100,11 +103,34 @@
      * triggers after a call to {@link #startRecognition(int)}, if the model
      * triggers multiple times.
      * When this isn't specified, the default behavior is to stop recognition once the
-     * trigger happenss, till the caller starts recognition again.
+     * trigger happens, till the caller starts recognition again.
      */
     public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 0x2;
 
     /**
+     * Audio capabilities flag for {@link #startRecognition(int)} that indicates
+     * if the underlying recognition should use AEC.
+     * This capability may or may not be supported by the system, and support can be queried
+     * by calling {@link SoundTriggerManager#getModuleProperties()} and checking
+     * {@link ModuleProperties#audioCapabilities}. The corresponding capabilities field for
+     * this flag is {@link SoundTrigger.ModuleProperties#CAPABILITY_ECHO_CANCELLATION}.
+     * If this flag is passed without the audio capability supported, there will be no audio effect
+     * applied.
+     */
+    public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 0x4;
+
+    /**
+     * Audio capabilities flag for {@link #startRecognition(int)} that indicates
+     * if the underlying recognition should use noise suppression.
+     * This capability may or may not be supported by the system, and support can be queried
+     * by calling {@link SoundTriggerManager#getModuleProperties()} and checking
+     * {@link ModuleProperties#audioCapabilities}. The corresponding capabilities field for
+     * this flag is {@link SoundTrigger.ModuleProperties#CAPABILITY_NOISE_SUPPRESSION}. If this flag
+     * is passed without the audio capability supported, there will be no audio effect applied.
+     */
+    public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 0x8;
+
+    /**
      * Additional payload for {@link Callback#onDetected}.
      */
     public static class EventPayload {
@@ -267,11 +293,20 @@
 
         boolean allowMultipleTriggers =
                 (recognitionFlags & RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS) != 0;
-        int status = STATUS_OK;
+
+        int audioCapabilities = 0;
+        if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION) != 0) {
+            audioCapabilities |= SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION;
+        }
+        if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION) != 0) {
+            audioCapabilities |= SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION;
+        }
+
+        int status;
         try {
             status = mSoundTriggerService.startRecognition(new ParcelUuid(mSoundModelId),
                     mRecognitionCallback, new RecognitionConfig(captureTriggerAudio,
-                        allowMultipleTriggers, null, null));
+                        allowMultipleTriggers, null, null, audioCapabilities));
         } catch (RemoteException e) {
             return false;
         }
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index 61b3e76..dd4dac2 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -23,7 +23,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
 import android.hardware.soundtrigger.ModelParams;
@@ -44,6 +44,7 @@
 import com.android.internal.util.Preconditions;
 
 import java.util.HashMap;
+import java.util.Objects;
 import java.util.UUID;
 
 /**
@@ -175,19 +176,40 @@
          * Factory constructor to create a SoundModel instance for use with methods in this
          * class.
          */
-        public static Model create(UUID modelUuid, UUID vendorUuid, byte[] data) {
-            return new Model(new SoundTrigger.GenericSoundModel(modelUuid,
-                        vendorUuid, data));
+        @NonNull
+        public static Model create(@NonNull UUID modelUuid, @NonNull UUID vendorUuid,
+                @Nullable byte[] data, int version) {
+            Objects.requireNonNull(modelUuid);
+            Objects.requireNonNull(vendorUuid);
+            return new Model(new SoundTrigger.GenericSoundModel(modelUuid, vendorUuid, data,
+                    version));
         }
 
+        /**
+         * Factory constructor to create a SoundModel instance for use with methods in this
+         * class.
+         */
+        @NonNull
+        public static Model create(@NonNull UUID modelUuid, @NonNull UUID vendorUuid,
+                @Nullable byte[] data) {
+            return create(modelUuid, vendorUuid, data, -1);
+        }
+
+        @NonNull
         public UUID getModelUuid() {
             return mGenericSoundModel.uuid;
         }
 
+        @NonNull
         public UUID getVendorUuid() {
             return mGenericSoundModel.vendorUuid;
         }
 
+        public int getVersion() {
+            return mGenericSoundModel.version;
+        }
+
+        @Nullable
         public byte[] getModelData() {
             return mGenericSoundModel.data;
         }
@@ -428,8 +450,7 @@
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
     public int setParameter(@Nullable UUID soundModelId,
-            @ModelParams int modelParam, int value)
-            throws UnsupportedOperationException, IllegalArgumentException {
+            @ModelParams int modelParam, int value) {
         try {
             return mSoundTriggerService.setParameter(new ParcelUuid(soundModelId), modelParam,
                     value);
@@ -449,15 +470,10 @@
      * @param soundModelId UUID of model to get parameter
      * @param modelParam   {@link ModelParams}
      * @return value of parameter
-     * @throws UnsupportedOperationException if hal or model do not support this API.
-     *         {@link SoundTriggerManager#queryParameter} should be checked first.
-     * @throws IllegalArgumentException if invalid model handle or parameter is passed.
-     *         {@link SoundTriggerManager#queryParameter} should be checked first.
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
     public int getParameter(@NonNull UUID soundModelId,
-            @ModelParams int modelParam)
-            throws UnsupportedOperationException, IllegalArgumentException {
+            @ModelParams int modelParam) {
         try {
             return mSoundTriggerService.getParameter(new ParcelUuid(soundModelId), modelParam);
         } catch (RemoteException e) {
@@ -479,8 +495,7 @@
     public ModelParamRange queryParameter(@Nullable UUID soundModelId,
             @ModelParams int modelParam) {
         try {
-            return mSoundTriggerService.queryParameter(new ParcelUuid(soundModelId),
-                    modelParam);
+            return mSoundTriggerService.queryParameter(new ParcelUuid(soundModelId), modelParam);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/media/java/android/media/soundtrigger_middleware/AudioCapabilities.aidl b/media/java/android/media/soundtrigger_middleware/AudioCapabilities.aidl
new file mode 100644
index 0000000..97a8849
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/AudioCapabilities.aidl
@@ -0,0 +1,32 @@
+/*
+ * 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.media.soundtrigger_middleware;
+
+/**
+ * AudioCapabilities supported by the implemented HAL driver.
+ * @hide
+ */
+@Backing(type="int")
+enum AudioCapabilities {
+    /**
+     * If set the underlying module supports AEC.
+     */
+    ECHO_CANCELLATION = 1 << 0,
+    /**
+     * If set, the underlying module supports noise suppression.
+     */
+    NOISE_SUPPRESSION = 1 << 1,
+}
diff --git a/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl b/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
index 149c1cd..726af76 100644
--- a/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
+++ b/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
@@ -44,4 +44,11 @@
      * and this event will be sent in addition to the abort event.
      */
     void onRecognitionAvailabilityChange(boolean available);
+    /**
+     * Notifies the client that the associated module has crashed and restarted. The module instance
+     * is no longer usable and will throw a ServiceSpecificException with a Status.DEAD_OBJECT code
+     * for every call. The client should detach, then re-attach to the module in order to get a new,
+     * usable instance. All state for this module has been lost.
+     */
+     void onModuleDied();
 }
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl b/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl
index c7642e8..5c0eeb1 100644
--- a/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl
+++ b/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl
@@ -28,6 +28,12 @@
     /* Configuration for each key phrase. */
     PhraseRecognitionExtra[] phraseRecognitionExtras;
 
+    /**
+     * Bit field encoding of the AudioCapabilities
+     * supported by the firmware.
+     */
+    int audioCapabilities;
+
     /** Opaque capture configuration data. */
     byte[] data;
 }
diff --git a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
index 1a3b402..9c56e7b 100644
--- a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
+++ b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
@@ -30,6 +30,14 @@
      * Unique implementation ID. The UUID must change with each version of
        the engine implementation */
     String     uuid;
+    /**
+     * String naming the architecture used for running the supported models.
+     * (eg. a platform running models on a DSP could implement this string to convey the DSP
+     * architecture used)
+     * This property is supported for soundtrigger HAL v2.3 and above.
+     * If running a previous version, the string will be empty.
+     */
+    String supportedModelArch;
     /** Maximum number of concurrent sound models loaded */
     int maxSoundModels;
     /** Maximum number of key phrases */
@@ -50,4 +58,11 @@
      * Rated power consumption when detection is active with TDB
      * silence/sound/speech ratio */
     int powerConsumptionMw;
+    /**
+     * Bit field encoding of the AudioCapabilities
+     * supported by the firmware.
+     * This property is supported for soundtrigger HAL v2.3 and above.
+     * If running a previous version, this value will be 0.
+     */
+    int audioCapabilities;
 }
diff --git a/media/java/android/media/soundtrigger_middleware/Status.aidl b/media/java/android/media/soundtrigger_middleware/Status.aidl
index d8f9d8f..85ccacf 100644
--- a/media/java/android/media/soundtrigger_middleware/Status.aidl
+++ b/media/java/android/media/soundtrigger_middleware/Status.aidl
@@ -26,4 +26,8 @@
     RESOURCE_CONTENTION = 1,
     /** Operation is not supported in this implementation. This is a permanent condition. */
     OPERATION_NOT_SUPPORTED = 2,
+    /** Temporary lack of permission. */
+    TEMPORARY_PERMISSION_DENIED = 3,
+    /** The object on which this method is called is dead and all of its state is lost. */
+    DEAD_OBJECT = 4,
 }
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index b076bb6..c199c6f 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -60,6 +60,7 @@
     void createSession(in ITvInputClient client, in String inputId, boolean isRecordingSession,
             int seq, int userId);
     void releaseSession(in IBinder sessionToken, int userId);
+    int getClientPid(in String sessionId);
 
     void setMainSession(in IBinder sessionToken, int userId);
     void setSurface(in IBinder sessionToken, in Surface surface, int userId);
@@ -94,7 +95,7 @@
     // For TV input hardware binding
     List<TvInputHardwareInfo> getHardwareList();
     ITvInputHardware acquireTvInputHardware(int deviceId, in ITvInputHardwareCallback callback,
-            in TvInputInfo info, int userId);
+            in TvInputInfo info, int userId, String tvInputSessionId, int priorityHint);
     void releaseTvInputHardware(int deviceId, in ITvInputHardware hardware, int userId);
 
     // For TV input capturing
diff --git a/media/java/android/media/tv/ITvInputService.aidl b/media/java/android/media/tv/ITvInputService.aidl
index f90c504..8ccf13a 100755
--- a/media/java/android/media/tv/ITvInputService.aidl
+++ b/media/java/android/media/tv/ITvInputService.aidl
@@ -30,8 +30,9 @@
     void registerCallback(in ITvInputServiceCallback callback);
     void unregisterCallback(in ITvInputServiceCallback callback);
     void createSession(in InputChannel channel, in ITvInputSessionCallback callback,
-            in String inputId);
-    void createRecordingSession(in ITvInputSessionCallback callback, in String inputId);
+            in String inputId, in String sessionId);
+    void createRecordingSession(in ITvInputSessionCallback callback, in String inputId,
+            in String sessionId);
 
     // For hardware TvInputService
     void notifyHardwareAdded(in TvInputHardwareInfo hardwareInfo);
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index 1b9cac0..377b2bc 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -20,7 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.StringRes;
 import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 854ea43..9cdfa2a 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -110,12 +110,20 @@
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({VIDEO_UNAVAILABLE_REASON_UNKNOWN, VIDEO_UNAVAILABLE_REASON_TUNING,
-            VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL, VIDEO_UNAVAILABLE_REASON_BUFFERING,
-            VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY})
+        VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL, VIDEO_UNAVAILABLE_REASON_BUFFERING,
+        VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY, VIDEO_UNAVAILABLE_REASON_INSUFFICIENT_RESOURCE,
+        VIDEO_UNAVAILABLE_REASON_CAS_INSUFFICIENT_OUTPUT_PROTECTION,
+        VIDEO_UNAVAILABLE_REASON_CAS_PVR_RECORDING_NOT_ALLOWED,
+        VIDEO_UNAVAILABLE_REASON_CAS_PVR_RECORDING_NOT_ALLOWED,
+        VIDEO_UNAVAILABLE_REASON_CAS_NO_LICENSE, VIDEO_UNAVAILABLE_REASON_CAS_LICENSE_EXPIRED,
+        VIDEO_UNAVAILABLE_REASON_CAS_NEED_ACTIVATION, VIDEO_UNAVAILABLE_REASON_CAS_NEED_PAIRING,
+        VIDEO_UNAVAILABLE_REASON_CAS_NO_CARD, VIDEO_UNAVAILABLE_REASON_CAS_CARD_MUTE,
+        VIDEO_UNAVAILABLE_REASON_CAS_CARD_INVALID, VIDEO_UNAVAILABLE_REASON_CAS_BLACKOUT,
+        VIDEO_UNAVAILABLE_REASON_CAS_REBOOTING, VIDEO_UNAVAILABLE_REASON_CAS_UNKNOWN})
     public @interface VideoUnavailableReason {}
 
     static final int VIDEO_UNAVAILABLE_REASON_START = 0;
-    static final int VIDEO_UNAVAILABLE_REASON_END = 5;
+    static final int VIDEO_UNAVAILABLE_REASON_END = 18;
 
     /**
      * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
@@ -151,9 +159,88 @@
      * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
      * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
      * the source is not physically connected, for example the HDMI cable is not connected.
+     */
+    public static final int VIDEO_UNAVAILABLE_REASON_NOT_CONNECTED = 5;
+    /**
+     * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
+     * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
+     * the resource is not enough to meet requirement.
+     */
+    public static final int VIDEO_UNAVAILABLE_REASON_INSUFFICIENT_RESOURCE = 6;
+    /**
+     * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
+     * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
+     * the output protection level enabled on the device is not sufficient to meet the requirements
+     * in the license policy.
+     */
+    public static final int VIDEO_UNAVAILABLE_REASON_CAS_INSUFFICIENT_OUTPUT_PROTECTION = 7;
+    /**
+     * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
+     * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
+     * the PVR record is not allowed by the license policy.
+     */
+    public static final int VIDEO_UNAVAILABLE_REASON_CAS_PVR_RECORDING_NOT_ALLOWED = 8;
+    /**
+     * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
+     * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
+     * no license keys have been provided.
      * @hide
      */
-    public static final int VIDEO_UNAVAILABLE_REASON_NOT_CONNECTED = VIDEO_UNAVAILABLE_REASON_END;
+    public static final int VIDEO_UNAVAILABLE_REASON_CAS_NO_LICENSE = 9;
+    /**
+     * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
+     * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
+     * Using a license in whhich the keys have expired.
+     */
+    public static final int VIDEO_UNAVAILABLE_REASON_CAS_LICENSE_EXPIRED = 10;
+    /**
+     * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
+     * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
+     * the device need be activated.
+     */
+    public static final int VIDEO_UNAVAILABLE_REASON_CAS_NEED_ACTIVATION = 11;
+    /**
+     * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
+     * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
+     * the device need be paired.
+     */
+    public static final int VIDEO_UNAVAILABLE_REASON_CAS_NEED_PAIRING = 12;
+    /**
+     * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
+     * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
+     * smart card is missed.
+     */
+    public static final int VIDEO_UNAVAILABLE_REASON_CAS_NO_CARD = 13;
+    /**
+     * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
+     * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
+     * smart card is muted.
+     */
+    public static final int VIDEO_UNAVAILABLE_REASON_CAS_CARD_MUTE = 14;
+    /**
+     * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
+     * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
+     * smart card is invalid.
+     */
+    public static final int VIDEO_UNAVAILABLE_REASON_CAS_CARD_INVALID = 15;
+    /**
+     * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
+     * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
+     * of a geographical blackout.
+     */
+    public static final int VIDEO_UNAVAILABLE_REASON_CAS_BLACKOUT = 16;
+    /**
+     * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
+     * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
+     * CAS system is rebooting.
+     */
+    public static final int VIDEO_UNAVAILABLE_REASON_CAS_REBOOTING = 17;
+    /**
+     * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
+     * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
+     * of unknown CAS error.
+     */
+    public static final int VIDEO_UNAVAILABLE_REASON_CAS_UNKNOWN = VIDEO_UNAVAILABLE_REASON_END;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -266,6 +353,15 @@
     public static final int INPUT_STATE_DISCONNECTED = 2;
 
     /**
+     * An unknown state of the client pid gets from the TvInputManager. Client gets this value when
+     * query through {@link getClientPid(String sessionId)} fails.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int UNKNOWN_CLIENT_PID = -1;
+
+    /**
      * Broadcast intent action when the user blocked content ratings change. For use with the
      * {@link #isRatingBlocked}.
      */
@@ -1484,6 +1580,21 @@
     }
 
     /**
+     * Get a the client pid when creating the session with the session id provided.
+     *
+     * @param sessionId a String of session id that is used to query the client pid.
+     * @return the client pid when created the session. Returns {@link #UNKNOWN_CLIENT_PID}
+     *         if the call fails.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
+    public int getClientPid(@NonNull String sessionId) {
+        return getClientPidInternal(sessionId);
+    };
+
+    /**
      * Creates a recording {@link Session} for a given TV input.
      *
      * <p>The number of sessions that can be created at the same time is limited by the capability
@@ -1516,6 +1627,17 @@
         }
     }
 
+    private int getClientPidInternal(String sessionId) {
+        Preconditions.checkNotNull(sessionId);
+        int clientPid = UNKNOWN_CLIENT_PID;
+        try {
+            clientPid = mService.getClientPid(sessionId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        return clientPid;
+    }
+
     /**
      * Returns the TvStreamConfig list of the given TV input.
      *
@@ -1613,8 +1735,14 @@
     /**
      * Acquires {@link Hardware} object for the given device ID.
      *
-     * <p>A subsequent call to this method on the same {@code deviceId} will release the currently
-     * acquired Hardware.
+     * <p>A subsequent call to this method on the same {@code deviceId} could release the currently
+     * acquired Hardware if TunerResourceManager(TRM) detects higher priority from the current
+     * request.
+     *
+     * <p>If the client would like to provide information for the TRM to compare, use
+     * {@link #acquireTvInputHardware(int, TvInputInfo, HardwareCallback, String, int)} instead.
+     *
+     * <p>Otherwise default priority will be applied.
      *
      * @param deviceId The device ID to acquire Hardware for.
      * @param callback A callback to receive updates on Hardware.
@@ -1625,8 +1753,49 @@
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
-    public Hardware acquireTvInputHardware(int deviceId, TvInputInfo info,
-            final HardwareCallback callback) {
+    public Hardware acquireTvInputHardware(int deviceId, @NonNull TvInputInfo info,
+            @NonNull final HardwareCallback callback) {
+        Preconditions.checkNotNull(info);
+        Preconditions.checkNotNull(callback);
+        return acquireTvInputHardwareInternal(deviceId, info, callback, null,
+                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE);
+    }
+
+    /**
+     * Acquires {@link Hardware} object for the given device ID.
+     *
+     * <p>A subsequent call to this method on the same {@code deviceId} could release the currently
+     * acquired Hardware if TunerResourceManager(TRM) detects higher priority from the current
+     * request.
+     *
+     * @param deviceId The device ID to acquire Hardware for.
+     * @param callback A callback to receive updates on Hardware.
+     * @param info The TV input which will use the acquired Hardware.
+     * @param tvInputSessionId a String returned to TIS when the session was created.
+     *        {@see TvInputService#onCreateSession(String, String)}. If null, the client will be
+     *        treated as a background app.
+     * @param priorityHint The use case of the client. {@see TvInputService#PriorityHintUseCaseType}
+     * @return Hardware on success, {@code null} otherwise. When the TRM decides to not grant
+     *         resource, null is returned and the {@link IllegalStateException} is thrown with
+     *         "No enough resources".
+     *
+     * @hide
+     */
+    @SystemApi
+    @Nullable
+    @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
+    public Hardware acquireTvInputHardware(int deviceId, @NonNull TvInputInfo info,
+            @NonNull final HardwareCallback callback,
+            @Nullable String tvInputSessionId,
+            @TvInputService.PriorityHintUseCaseType int priorityHint) {
+        Preconditions.checkNotNull(info);
+        Preconditions.checkNotNull(callback);
+        return acquireTvInputHardwareInternal(deviceId, info, callback,
+                tvInputSessionId, priorityHint);
+    }
+
+    private Hardware acquireTvInputHardwareInternal(int deviceId, TvInputInfo info,
+             final HardwareCallback callback, String tvInputSessionId, int priorityHint) {
         try {
             return new Hardware(
                     mService.acquireTvInputHardware(deviceId, new ITvInputHardwareCallback.Stub() {
@@ -1639,7 +1808,7 @@
                 public void onStreamConfigChanged(TvStreamConfig[] configs) {
                     callback.onStreamConfigChanged(configs);
                 }
-            }, info, mUserId));
+                    }, info, mUserId, tvInputSessionId, priorityHint));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 5c11ed9b..7e1f44c 100755
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -17,14 +17,15 @@
 package android.media.tv;
 
 import android.annotation.FloatRange;
+import android.annotation.IntDef;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
 import android.app.ActivityManager;
 import android.app.Service;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.PixelFormat;
@@ -58,6 +59,8 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.Preconditions;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -96,6 +99,53 @@
     public static final String SERVICE_META_DATA = "android.media.tv.input";
 
     /**
+     * Prioirity hint from use case types.
+     *
+     * @hide
+     */
+    @IntDef(prefix = "PRIORITY_HINT_USE_CASE_TYPE_",
+        value = {PRIORITY_HINT_USE_CASE_TYPE_BACKGROUND, PRIORITY_HINT_USE_CASE_TYPE_SCAN,
+            PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, PRIORITY_HINT_USE_CASE_TYPE_LIVE,
+            PRIORITY_HINT_USE_CASE_TYPE_RECORD})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PriorityHintUseCaseType {}
+
+    /**
+     * Use case of priority hint for {@link android.media.MediaCas#MediaCas(int, String , int)}:
+     * Background.
+     * TODO Link: Tuner#Tuner(Context, string, int).
+     */
+    public static final int PRIORITY_HINT_USE_CASE_TYPE_BACKGROUND = 100;
+
+    /**
+     * Use case of priority hint for {@link android.media.MediaCas#MediaCas(int, String , int)}:
+     * Scan.
+     * TODO Link: Tuner#Tuner(Context, string, int).
+     */
+    public static final int PRIORITY_HINT_USE_CASE_TYPE_SCAN = 200;
+
+    /**
+     * Use case of priority hint for {@link android.media.MediaCas#MediaCas(int, String , int)}:
+     * Playback.
+     * TODO Link: Tuner#Tuner(Context, string, int).
+     */
+    public static final int PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK = 300;
+
+    /**
+     * Use case of priority hint for {@link android.media.MediaCas#MediaCas(int, String , int)}:
+     * Live.
+     * TODO Link: Tuner#Tuner(Context, string, int).
+     */
+    public static final int PRIORITY_HINT_USE_CASE_TYPE_LIVE = 400;
+
+    /**
+     * Use case of priority hint for {@link android.media.MediaCas#MediaCas(int, String , int)}:
+     * Record.
+     * TODO Link: Tuner#Tuner(Context, string, int).
+     */
+    public static final int PRIORITY_HINT_USE_CASE_TYPE_RECORD = 500;
+
+    /**
      * Handler instance to handle request from TV Input Manager Service. Should be run in the main
      * looper to be synchronously run with {@code Session.mHandler}.
      */
@@ -124,7 +174,7 @@
 
             @Override
             public void createSession(InputChannel channel, ITvInputSessionCallback cb,
-                    String inputId) {
+                    String inputId, String sessionId) {
                 if (channel == null) {
                     Log.w(TAG, "Creating session without input channel");
                 }
@@ -135,17 +185,20 @@
                 args.arg1 = channel;
                 args.arg2 = cb;
                 args.arg3 = inputId;
+                args.arg4 = sessionId;
                 mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args).sendToTarget();
             }
 
             @Override
-            public void createRecordingSession(ITvInputSessionCallback cb, String inputId) {
+            public void createRecordingSession(ITvInputSessionCallback cb, String inputId,
+                    String sessionId) {
                 if (cb == null) {
                     return;
                 }
                 SomeArgs args = SomeArgs.obtain();
                 args.arg1 = cb;
                 args.arg2 = inputId;
+                args.arg3 = sessionId;
                 mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_RECORDING_SESSION, args)
                         .sendToTarget();
             }
@@ -208,6 +261,37 @@
     }
 
     /**
+     * Returns a concrete implementation of {@link Session}.
+     *
+     * <p>For any apps that needs sessionId to request tuner resources from TunerResourceManager,
+     * it needs to override this method to get the sessionId passed. When no overriding, this method
+     * calls {@link #onCreateSession(String)} defaultly.
+     *
+     * @param inputId The ID of the TV input associated with the session.
+     * @param sessionId the unique sessionId created by TIF when session is created.
+     */
+    @Nullable
+    public Session onCreateSession(@NonNull String inputId, @NonNull String sessionId) {
+        return onCreateSession(inputId);
+    }
+
+    /**
+     * Returns a concrete implementation of {@link RecordingSession}.
+     *
+     * <p>For any apps that needs sessionId to request tuner resources from TunerResourceManager,
+     * it needs to override this method to get the sessionId passed. When no overriding, this method
+     * calls {@link #onCreateRecordingSession(String)} defaultly.
+     *
+     * @param inputId The ID of the TV input associated with the recording session.
+     * @param sessionId the unique sessionId created by TIF when session is created.
+     */
+    @Nullable
+    public RecordingSession onCreateRecordingSession(
+            @NonNull String inputId, @NonNull String sessionId) {
+        return onCreateRecordingSession(inputId);
+    }
+
+    /**
      * Returns a new {@link TvInputInfo} object if this service is responsible for
      * {@code hardwareInfo}; otherwise, return {@code null}. Override to modify default behavior of
      * ignoring all hardware input.
@@ -2032,8 +2116,9 @@
                     InputChannel channel = (InputChannel) args.arg1;
                     ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2;
                     String inputId = (String) args.arg3;
+                    String sessionId = (String) args.arg4;
                     args.recycle();
-                    Session sessionImpl = onCreateSession(inputId);
+                    Session sessionImpl = onCreateSession(inputId, sessionId);
                     if (sessionImpl == null) {
                         try {
                             // Failed to create a session.
@@ -2103,8 +2188,10 @@
                     SomeArgs args = (SomeArgs) msg.obj;
                     ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg1;
                     String inputId = (String) args.arg2;
+                    String sessionId = (String) args.arg3;
                     args.recycle();
-                    RecordingSession recordingSessionImpl = onCreateRecordingSession(inputId);
+                    RecordingSession recordingSessionImpl =
+                            onCreateRecordingSession(inputId, sessionId);
                     if (recordingSessionImpl == null) {
                         try {
                             // Failed to create a recording session.
diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java
index c17beba..b40d43a 100644
--- a/media/java/android/media/tv/TvTrackInfo.java
+++ b/media/java/android/media/tv/TvTrackInfo.java
@@ -63,6 +63,7 @@
     private final int mAudioSampleRate;
     private final boolean mAudioDescription;
     private final boolean mHardOfHearing;
+    private final boolean mSpokenSubtitle;
     private final int mVideoWidth;
     private final int mVideoHeight;
     private final float mVideoFrameRate;
@@ -73,8 +74,9 @@
 
     private TvTrackInfo(int type, String id, String language, CharSequence description,
             boolean encrypted, int audioChannelCount, int audioSampleRate, boolean audioDescription,
-            boolean hardOfHearing, int videoWidth, int videoHeight, float videoFrameRate,
-            float videoPixelAspectRatio, byte videoActiveFormatDescription, Bundle extra) {
+            boolean hardOfHearing, boolean spokenSubtitle, int videoWidth, int videoHeight,
+            float videoFrameRate, float videoPixelAspectRatio, byte videoActiveFormatDescription,
+            Bundle extra) {
         mType = type;
         mId = id;
         mLanguage = language;
@@ -84,6 +86,7 @@
         mAudioSampleRate = audioSampleRate;
         mAudioDescription = audioDescription;
         mHardOfHearing = hardOfHearing;
+        mSpokenSubtitle = spokenSubtitle;
         mVideoWidth = videoWidth;
         mVideoHeight = videoHeight;
         mVideoFrameRate = videoFrameRate;
@@ -102,6 +105,7 @@
         mAudioSampleRate = in.readInt();
         mAudioDescription = in.readInt() != 0;
         mHardOfHearing = in.readInt() != 0;
+        mSpokenSubtitle = in.readInt() != 0;
         mVideoWidth = in.readInt();
         mVideoHeight = in.readInt();
         mVideoFrameRate = in.readFloat();
@@ -212,6 +216,22 @@
     }
 
     /**
+     * Returns {@code true} if the track is a spoken subtitle for people with visual impairment,
+     * {@code false} otherwise. Valid only for {@link #TYPE_AUDIO} tracks.
+     *
+     * <p>For example of broadcast, spoken subtitle information may be referred to broadcast
+     * standard (e.g. Supplementary Audio Language Descriptor of ETSI EN 300 468).
+     *
+     * @throws IllegalStateException if not called on an audio track
+     */
+    public boolean isSpokenSubtitle() {
+        if (mType != TYPE_AUDIO) {
+            throw new IllegalStateException("Not an audio track");
+        }
+        return mSpokenSubtitle;
+    }
+
+    /**
      * Returns the width of the video, in the unit of pixels. Valid only for {@link #TYPE_VIDEO}
      * tracks.
      *
@@ -298,7 +318,8 @@
      * @param flags The flags used for parceling.
      */
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        Preconditions.checkNotNull(dest);
         dest.writeInt(mType);
         dest.writeString(mId);
         dest.writeString(mLanguage);
@@ -308,6 +329,7 @@
         dest.writeInt(mAudioSampleRate);
         dest.writeInt(mAudioDescription ? 1 : 0);
         dest.writeInt(mHardOfHearing ? 1 : 0);
+        dest.writeInt(mSpokenSubtitle ? 1 : 0);
         dest.writeInt(mVideoWidth);
         dest.writeInt(mVideoHeight);
         dest.writeFloat(mVideoFrameRate);
@@ -331,7 +353,7 @@
         if (!TextUtils.equals(mId, obj.mId) || mType != obj.mType
                 || !TextUtils.equals(mLanguage, obj.mLanguage)
                 || !TextUtils.equals(mDescription, obj.mDescription)
-                || !Objects.equals(mExtra, obj.mExtra)) {
+                || mEncrypted != obj.mEncrypted) {
             return false;
         }
 
@@ -340,7 +362,8 @@
                 return mAudioChannelCount == obj.mAudioChannelCount
                         && mAudioSampleRate == obj.mAudioSampleRate
                         && mAudioDescription == obj.mAudioDescription
-                        && mHardOfHearing == obj.mHardOfHearing;
+                        && mHardOfHearing == obj.mHardOfHearing
+                        && mSpokenSubtitle == obj.mSpokenSubtitle;
 
             case TYPE_VIDEO:
                 return mVideoWidth == obj.mVideoWidth
@@ -358,17 +381,28 @@
 
     @Override
     public int hashCode() {
-        return Objects.hashCode(mId);
+        int result = Objects.hash(mId, mType, mLanguage, mDescription);
+
+        if (mType == TYPE_AUDIO) {
+            result = Objects.hash(result, mAudioChannelCount, mAudioSampleRate);
+        } else if (mType == TYPE_VIDEO) {
+            result = Objects.hash(result, mVideoWidth, mVideoHeight, mVideoFrameRate,
+                    mVideoPixelAspectRatio);
+        }
+
+        return result;
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<TvTrackInfo> CREATOR =
             new Parcelable.Creator<TvTrackInfo>() {
                 @Override
+                @NonNull
                 public TvTrackInfo createFromParcel(Parcel in) {
                     return new TvTrackInfo(in);
                 }
 
                 @Override
+                @NonNull
                 public TvTrackInfo[] newArray(int size) {
                     return new TvTrackInfo[size];
                 }
@@ -387,6 +421,7 @@
         private int mAudioSampleRate;
         private boolean mAudioDescription;
         private boolean mHardOfHearing;
+        private boolean mSpokenSubtitle;
         private int mVideoWidth;
         private int mVideoHeight;
         private float mVideoFrameRate;
@@ -420,7 +455,9 @@
          *
          * @param language The language string encoded by either ISO 639-1 or ISO 639-2/T.
          */
-        public final Builder setLanguage(String language) {
+        @NonNull
+        public  Builder setLanguage(@NonNull String language) {
+            Preconditions.checkNotNull(language);
             mLanguage = language;
             return this;
         }
@@ -430,7 +467,9 @@
          *
          * @param description The user readable description.
          */
-        public final Builder setDescription(CharSequence description) {
+        @NonNull
+        public  Builder setDescription(@NonNull CharSequence description) {
+            Preconditions.checkNotNull(description);
             mDescription = description;
             return this;
         }
@@ -455,7 +494,8 @@
          * @param audioChannelCount The audio channel count.
          * @throws IllegalStateException if not called on an audio track
          */
-        public final Builder setAudioChannelCount(int audioChannelCount) {
+        @NonNull
+        public Builder setAudioChannelCount(int audioChannelCount) {
             if (mType != TYPE_AUDIO) {
                 throw new IllegalStateException("Not an audio track");
             }
@@ -470,7 +510,8 @@
          * @param audioSampleRate The audio sample rate.
          * @throws IllegalStateException if not called on an audio track
          */
-        public final Builder setAudioSampleRate(int audioSampleRate) {
+        @NonNull
+        public Builder setAudioSampleRate(int audioSampleRate) {
             if (mType != TYPE_AUDIO) {
                 throw new IllegalStateException("Not an audio track");
             }
@@ -521,13 +562,33 @@
         }
 
         /**
+         * Sets the spoken subtitle attribute of the audio. Valid only for {@link #TYPE_AUDIO}
+         * tracks.
+         *
+         * <p>For example of broadcast, spoken subtitle information may be referred to broadcast
+         * standard (e.g. Supplementary Audio Language Descriptor of ETSI EN 300 468).
+         *
+         * @param spokenSubtitle The spoken subtitle attribute of the audio.
+         * @throws IllegalStateException if not called on an audio track
+         */
+        @NonNull
+        public Builder setSpokenSubtitle(boolean spokenSubtitle) {
+            if (mType != TYPE_AUDIO) {
+                throw new IllegalStateException("Not an audio track");
+            }
+            mSpokenSubtitle = spokenSubtitle;
+            return this;
+        }
+
+        /**
          * Sets the width of the video, in the unit of pixels. Valid only for {@link #TYPE_VIDEO}
          * tracks.
          *
          * @param videoWidth The width of the video.
          * @throws IllegalStateException if not called on a video track
          */
-        public final Builder setVideoWidth(int videoWidth) {
+        @NonNull
+        public Builder setVideoWidth(int videoWidth) {
             if (mType != TYPE_VIDEO) {
                 throw new IllegalStateException("Not a video track");
             }
@@ -542,7 +603,8 @@
          * @param videoHeight The height of the video.
          * @throws IllegalStateException if not called on a video track
          */
-        public final Builder setVideoHeight(int videoHeight) {
+        @NonNull
+        public Builder setVideoHeight(int videoHeight) {
             if (mType != TYPE_VIDEO) {
                 throw new IllegalStateException("Not a video track");
             }
@@ -557,7 +619,8 @@
          * @param videoFrameRate The frame rate of the video.
          * @throws IllegalStateException if not called on a video track
          */
-        public final Builder setVideoFrameRate(float videoFrameRate) {
+        @NonNull
+        public Builder setVideoFrameRate(float videoFrameRate) {
             if (mType != TYPE_VIDEO) {
                 throw new IllegalStateException("Not a video track");
             }
@@ -577,7 +640,8 @@
          * @param videoPixelAspectRatio The pixel aspect ratio of the video.
          * @throws IllegalStateException if not called on a video track
          */
-        public final Builder setVideoPixelAspectRatio(float videoPixelAspectRatio) {
+        @NonNull
+        public Builder setVideoPixelAspectRatio(float videoPixelAspectRatio) {
             if (mType != TYPE_VIDEO) {
                 throw new IllegalStateException("Not a video track");
             }
@@ -597,7 +661,8 @@
          * @param videoActiveFormatDescription The AFD code of the video.
          * @throws IllegalStateException if not called on a video track
          */
-        public final Builder setVideoActiveFormatDescription(byte videoActiveFormatDescription) {
+        @NonNull
+        public Builder setVideoActiveFormatDescription(byte videoActiveFormatDescription) {
             if (mType != TYPE_VIDEO) {
                 throw new IllegalStateException("Not a video track");
             }
@@ -610,7 +675,9 @@
          *
          * @param extra The extra information.
          */
-        public final Builder setExtra(Bundle extra) {
+        @NonNull
+        public Builder setExtra(@NonNull Bundle extra) {
+            Preconditions.checkNotNull(extra);
             mExtra = new Bundle(extra);
             return this;
         }
@@ -620,11 +687,12 @@
          *
          * @return The new {@link TvTrackInfo} instance
          */
+        @NonNull
         public TvTrackInfo build() {
             return new TvTrackInfo(mType, mId, mLanguage, mDescription, mEncrypted,
                     mAudioChannelCount, mAudioSampleRate, mAudioDescription, mHardOfHearing,
-                    mVideoWidth, mVideoHeight, mVideoFrameRate, mVideoPixelAspectRatio,
-                    mVideoActiveFormatDescription, mExtra);
+                    mSpokenSubtitle, mVideoWidth, mVideoHeight, mVideoFrameRate,
+                    mVideoPixelAspectRatio, mVideoActiveFormatDescription, mExtra);
         }
     }
 }
diff --git a/media/java/android/media/tv/tuner/DemuxCapabilities.java b/media/java/android/media/tv/tuner/DemuxCapabilities.java
index bda166e..364516c 100644
--- a/media/java/android/media/tv/tuner/DemuxCapabilities.java
+++ b/media/java/android/media/tv/tuner/DemuxCapabilities.java
@@ -16,87 +16,138 @@
 
 package android.media.tv.tuner;
 
+import android.annotation.BytesLong;
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.Size;
+import android.annotation.SystemApi;
+import android.media.tv.tuner.filter.Filter;
+import android.media.tv.tuner.filter.FilterConfiguration;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Capabilities info for Demux.
+ *
  * @hide
  */
+@SystemApi
 public class DemuxCapabilities {
-    private final int mNumDemux;
-    private final int mNumRecord;
-    private final int mNumPlayback;
-    private final int mNumTsFilter;
-    private final int mNumSectionFilter;
-    private final int mNumAudioFilter;
-    private final int mNumVideoFilter;
-    private final int mNumPesFilter;
-    private final int mNumPcrFilter;
-    private final int mNumBytesInSectionFilter;
+
+    /** @hide */
+    @IntDef(flag = true, value = {
+            Filter.TYPE_TS,
+            Filter.TYPE_MMTP,
+            Filter.TYPE_IP,
+            Filter.TYPE_TLV,
+            Filter.TYPE_ALP
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FilterCapabilities {}
+
+    private final int mDemuxCount;
+    private final int mRecordCount;
+    private final int mPlaybackCount;
+    private final int mTsFilterCount;
+    private final int mSectionFilterCount;
+    private final int mAudioFilterCount;
+    private final int mVideoFilterCount;
+    private final int mPesFilterCount;
+    private final int mPcrFilterCount;
+    private final long mSectionFilterLength;
     private final int mFilterCaps;
     private final int[] mLinkCaps;
+    private final boolean mSupportTimeFilter;
 
-    DemuxCapabilities(int numDemux, int numRecord, int numPlayback, int numTsFilter,
-            int numSectionFilter, int numAudioFilter, int numVideoFilter, int numPesFilter,
-            int numPcrFilter, int numBytesInSectionFilter, int filterCaps, int[] linkCaps) {
-        mNumDemux = numDemux;
-        mNumRecord = numRecord;
-        mNumPlayback = numPlayback;
-        mNumTsFilter = numTsFilter;
-        mNumSectionFilter = numSectionFilter;
-        mNumAudioFilter = numAudioFilter;
-        mNumVideoFilter = numVideoFilter;
-        mNumPesFilter = numPesFilter;
-        mNumPcrFilter = numPcrFilter;
-        mNumBytesInSectionFilter = numBytesInSectionFilter;
+    // Used by JNI
+    private DemuxCapabilities(int demuxCount, int recordCount, int playbackCount, int tsFilterCount,
+            int sectionFilterCount, int audioFilterCount, int videoFilterCount, int pesFilterCount,
+            int pcrFilterCount, long sectionFilterLength, int filterCaps, int[] linkCaps,
+            boolean timeFilter) {
+        mDemuxCount = demuxCount;
+        mRecordCount = recordCount;
+        mPlaybackCount = playbackCount;
+        mTsFilterCount = tsFilterCount;
+        mSectionFilterCount = sectionFilterCount;
+        mAudioFilterCount = audioFilterCount;
+        mVideoFilterCount = videoFilterCount;
+        mPesFilterCount = pesFilterCount;
+        mPcrFilterCount = pcrFilterCount;
+        mSectionFilterLength = sectionFilterLength;
         mFilterCaps = filterCaps;
         mLinkCaps = linkCaps;
+        mSupportTimeFilter = timeFilter;
     }
 
-    /** Gets total number of demuxes. */
-    public int getNumDemux() {
-        return mNumDemux;
+    /**
+     * Gets total number of demuxes.
+     */
+    public int getDemuxCount() {
+        return mDemuxCount;
     }
-    /** Gets max number of recordings at a time. */
-    public int getNumRecord() {
-        return mNumRecord;
+    /**
+     * Gets max number of recordings at a time.
+     */
+    public int getRecordCount() {
+        return mRecordCount;
     }
-    /** Gets max number of playbacks at a time. */
-    public int getNumPlayback() {
-        return mNumPlayback;
+    /**
+     * Gets max number of playbacks at a time.
+     */
+    public int getPlaybackCount() {
+        return mPlaybackCount;
     }
-    /** Gets number of TS filters. */
-    public int getNumTsFilter() {
-        return mNumTsFilter;
+    /**
+     * Gets number of TS filters.
+     */
+    public int getTsFilterCount() {
+        return mTsFilterCount;
     }
-    /** Gets number of section filters. */
-    public int getNumSectionFilter() {
-        return mNumSectionFilter;
+    /**
+     * Gets number of section filters.
+     */
+    public int getSectionFilterCount() {
+        return mSectionFilterCount;
     }
-    /** Gets number of audio filters. */
-    public int getNumAudioFilter() {
-        return mNumAudioFilter;
+    /**
+     * Gets number of audio filters.
+     */
+    public int getAudioFilterCount() {
+        return mAudioFilterCount;
     }
-    /** Gets number of video filters. */
-    public int getNumVideoFilter() {
-        return mNumVideoFilter;
+    /**
+     * Gets number of video filters.
+     */
+    public int getVideoFilterCount() {
+        return mVideoFilterCount;
     }
-    /** Gets number of PES filters. */
-    public int getNumPesFilter() {
-        return mNumPesFilter;
+    /**
+     * Gets number of PES filters.
+     */
+    public int getPesFilterCount() {
+        return mPesFilterCount;
     }
-    /** Gets number of PCR filters. */
-    public int getNumPcrFilter() {
-        return mNumPcrFilter;
+    /**
+     * Gets number of PCR filters.
+     */
+    public int getPcrFilterCount() {
+        return mPcrFilterCount;
     }
-    /** Gets number of bytes in the mask of a section filter. */
-    public int getNumBytesInSectionFilter() {
-        return mNumBytesInSectionFilter;
+    /**
+     * Gets number of bytes in the mask of a section filter.
+     */
+    @BytesLong
+    public long getSectionFilterLength() {
+        return mSectionFilterLength;
     }
     /**
      * Gets filter capabilities in bit field.
      *
-     * The bits of the returned value is corresponding to the types in
-     * {@link TunerConstants.FilterType}.
+     * <p>The bits of the returned value is corresponding to the types in
+     * {@link FilterConfiguration}.
      */
+    @FilterCapabilities
     public int getFilterCapabilities() {
         return mFilterCaps;
     }
@@ -104,11 +155,19 @@
     /**
      * Gets link capabilities.
      *
-     * The returned array contains the same elements as the number of types in
-     * {@link TunerConstants.FilterType}.
-     * The ith element represents the filter's capability as the source for the ith type
+     * <p>The returned array contains the same elements as the number of types in
+     * {@link FilterConfiguration}.
+     * <p>The ith element represents the filter's capability as the source for the ith type.
      */
+    @Nullable
+    @Size(5)
     public int[] getLinkCapabilities() {
         return mLinkCaps;
     }
+    /**
+     * Is {@link android.media.tv.tuner.filter.TimeFilter} supported.
+     */
+    public boolean isTimeFilterSupported() {
+        return mSupportTimeFilter;
+    }
 }
diff --git a/media/java/android/media/tv/tuner/Descrambler.java b/media/java/android/media/tv/tuner/Descrambler.java
new file mode 100644
index 0000000..f46d3ce
--- /dev/null
+++ b/media/java/android/media/tv/tuner/Descrambler.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 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.media.tv.tuner;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.media.tv.tuner.filter.Filter;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class is used to interact with descramblers.
+ *
+ * <p> Descrambler is a hardware component used to descramble data.
+ *
+ * <p> This class controls the TIS interaction with Tuner HAL.
+ *
+ * @hide
+ */
+@SystemApi
+public class Descrambler implements AutoCloseable {
+    /** @hide */
+    @IntDef(prefix = "PID_TYPE_", value = {PID_TYPE_T, PID_TYPE_MMTP})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PidType {}
+
+    /**
+     * Packet ID is used to specify packets in transport stream.
+     */
+    public static final int PID_TYPE_T = 1;
+    /**
+     * Packet ID is used to specify packets in MMTP.
+     */
+    public static final int PID_TYPE_MMTP = 2;
+
+
+    private long mNativeContext;
+
+    private native int nativeAddPid(int pidType, int pid, Filter filter);
+    private native int nativeRemovePid(int pidType, int pid, Filter filter);
+    private native int nativeSetKeyToken(byte[] keyToken);
+    private native int nativeClose();
+
+    // Called by JNI code
+    private Descrambler() {}
+
+    /**
+     * Add packets' PID to the descrambler for descrambling.
+     *
+     * The descrambler will start descrambling packets with this PID. Multiple PIDs can be added
+     * into one descrambler instance because descambling can happen simultaneously on packets
+     * from different PIDs.
+     *
+     * If the Descrambler previously contained a filter for the PID, the old filter is replaced
+     * by the specified filter.
+     *
+     * @param pidType the type of the PID.
+     * @param pid the PID of packets to start to be descrambled.
+     * @param filter an optional filter instance to identify upper stream.
+     * @return result status of the operation.
+     */
+    public int addPid(@PidType int pidType, int pid, @Nullable Filter filter) {
+        return nativeAddPid(pidType, pid, filter);
+    }
+
+    /**
+     * Remove packets' PID from the descrambler
+     *
+     * The descrambler will stop descrambling packets with this PID.
+     *
+     * @param pidType the type of the PID.
+     * @param pid the PID of packets to stop to be descrambled.
+     * @param filter an optional filter instance to identify upper stream.
+     * @return result status of the operation.
+     */
+    public int removePid(@PidType int pidType, int pid, @Nullable Filter filter) {
+        return nativeRemovePid(pidType, pid, filter);
+    }
+
+    /**
+     * Set a key token to link descrambler to a key slot
+     *
+     * A descrambler instance can have only one key slot to link, but a key slot can hold a few
+     * keys for different purposes.
+     *
+     * @param keyToken the token to be used to link the key slot.
+     * @return result status of the operation.
+     */
+    public int setKeyToken(@Nullable byte[] keyToken) {
+        return nativeSetKeyToken(keyToken);
+    }
+
+    /**
+     * Release the descrambler instance.
+     */
+    @Override
+    public void close() {
+        nativeClose();
+    }
+
+}
diff --git a/media/java/android/media/tv/tuner/DvrSettings.java b/media/java/android/media/tv/tuner/DvrSettings.java
deleted file mode 100644
index 76160dc..0000000
--- a/media/java/android/media/tv/tuner/DvrSettings.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright 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.media.tv.tuner;
-
-import android.media.tv.tuner.TunerConstants.DataFormat;
-import android.media.tv.tuner.TunerConstants.DvrSettingsType;
-
-/**
- * DVR settings.
- *
- * @hide
- */
-public class DvrSettings {
-    private int mStatusMask;
-    private int mLowThreshold;
-    private int mHighThreshold;
-    private int mPacketSize;
-
-    @DataFormat
-    private int mDataFormat;
-    @DvrSettingsType
-    private int mType;
-
-    private DvrSettings(int statusMask, int lowThreshold, int highThreshold, int packetSize,
-            @DataFormat int dataFormat, @DvrSettingsType int type) {
-        mStatusMask = statusMask;
-        mLowThreshold = lowThreshold;
-        mHighThreshold = highThreshold;
-        mPacketSize = packetSize;
-        mDataFormat = dataFormat;
-        mType = type;
-    }
-
-    /**
-     * Creates a new builder.
-     */
-    public static Builder newBuilder() {
-        return new Builder();
-    }
-
-    /**
-     * Builder for DvrSettings.
-     */
-    public static final class Builder {
-        private int mStatusMask;
-        private int mLowThreshold;
-        private int mHighThreshold;
-        private int mPacketSize;
-        @DataFormat
-        private int mDataFormat;
-        @DvrSettingsType
-        private int mType;
-
-        /**
-         * Sets status mask.
-         */
-        public Builder setStatusMask(int statusMask) {
-            this.mStatusMask = statusMask;
-            return this;
-        }
-
-        /**
-         * Sets low threshold.
-         */
-        public Builder setLowThreshold(int lowThreshold) {
-            this.mLowThreshold = lowThreshold;
-            return this;
-        }
-
-        /**
-         * Sets high threshold.
-         */
-        public Builder setHighThreshold(int highThreshold) {
-            this.mHighThreshold = highThreshold;
-            return this;
-        }
-
-        /**
-         * Sets packet size.
-         */
-        public Builder setPacketSize(int packetSize) {
-            this.mPacketSize = packetSize;
-            return this;
-        }
-
-        /**
-         * Sets data format.
-         */
-        public Builder setDataFormat(@DataFormat int dataFormat) {
-            this.mDataFormat = dataFormat;
-            return this;
-        }
-
-        /**
-         * Sets settings type.
-         */
-        public Builder setType(@DvrSettingsType int type) {
-            this.mType = type;
-            return this;
-        }
-
-        /**
-         * Builds a DvrSettings instance.
-         */
-        public DvrSettings build() {
-            return new DvrSettings(
-                    mStatusMask, mLowThreshold, mHighThreshold, mPacketSize, mDataFormat, mType);
-        }
-    }
-}
diff --git a/media/java/android/media/tv/tuner/FilterConfiguration.java b/media/java/android/media/tv/tuner/FilterConfiguration.java
deleted file mode 100644
index b80a82a..0000000
--- a/media/java/android/media/tv/tuner/FilterConfiguration.java
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * Copyright 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.media.tv.tuner;
-
-import android.annotation.Nullable;
-import android.media.tv.tuner.TunerConstants.FilterType;
-
-import java.util.List;
-
-/**
- * Demux Filter configuration.
- *
- * @hide
- */
-public abstract class FilterConfiguration {
-    @Nullable
-    protected final Settings mSettings;
-
-    protected FilterConfiguration(Settings settings) {
-        mSettings = settings;
-    }
-
-    /**
-     * Gets filter configuration type
-     */
-    @FilterType
-    public abstract int getType();
-
-    public Settings getSettings() {
-        return mSettings;
-    }
-
-    // TODO: more builders and getters
-
-    /**
-     *  Filter configuration for a TS filter.
-     */
-    public static class TsFilterConfiguration extends FilterConfiguration {
-        private int mTpid;
-
-        private TsFilterConfiguration(Settings settings, int tpid) {
-            super(settings);
-            mTpid = tpid;
-        }
-
-        @Override
-        public int getType() {
-            return TunerConstants.FILTER_TYPE_TS;
-        }
-
-        /**
-         * Creates a new builder.
-         */
-        public static Builder newBuilder() {
-            return new Builder();
-        }
-
-        /**
-         * Builder for TsFilterConfiguration.
-         */
-        public static class Builder {
-            private Settings mSettings;
-            private int mTpid;
-
-            /**
-             * Sets settings.
-             */
-            public Builder setSettings(Settings settings) {
-                mSettings = settings;
-                return this;
-            }
-
-            /**
-             * Sets TPID.
-             */
-            public Builder setTpid(int tpid) {
-                mTpid = tpid;
-                return this;
-            }
-
-            /**
-             * Builds a TsFilterConfiguration instance.
-             */
-            public TsFilterConfiguration build() {
-                return new TsFilterConfiguration(mSettings, mTpid);
-            }
-        }
-    }
-
-    /**
-     *  Filter configuration for a MMTP filter.
-     */
-    public static class MmtpFilterConfiguration extends FilterConfiguration {
-        private int mMmtpPid;
-
-        public MmtpFilterConfiguration(Settings settings) {
-            super(settings);
-        }
-
-        @Override
-        public int getType() {
-            return TunerConstants.FILTER_TYPE_MMTP;
-        }
-    }
-
-
-    /**
-     *  Filter configuration for a IP filter.
-     */
-    public static class IpFilterConfiguration extends FilterConfiguration {
-        private byte[] mSrcIpAddress;
-        private byte[] mDstIpAddress;
-        private int mSrcPort;
-        private int mDstPort;
-        private boolean mPassthrough;
-
-        public IpFilterConfiguration(Settings settings) {
-            super(settings);
-        }
-
-        @Override
-        public int getType() {
-            return TunerConstants.FILTER_TYPE_IP;
-        }
-    }
-
-
-    /**
-     *  Filter configuration for a TLV filter.
-     */
-    public static class TlvFilterConfiguration extends FilterConfiguration {
-        private int mPacketType;
-        private boolean mIsCompressedIpPacket;
-        private boolean mPassthrough;
-
-        public TlvFilterConfiguration(Settings settings) {
-            super(settings);
-        }
-
-        @Override
-        public int getType() {
-            return TunerConstants.FILTER_TYPE_TLV;
-        }
-    }
-
-
-    /**
-     *  Filter configuration for a ALP filter.
-     */
-    public static class AlpFilterConfiguration extends FilterConfiguration {
-        private int mPacketType;
-        private int mLengthType;
-
-        public AlpFilterConfiguration(Settings settings) {
-            super(settings);
-        }
-
-        @Override
-        public int getType() {
-            return TunerConstants.FILTER_TYPE_ALP;
-        }
-    }
-
-
-    /**
-     *  Settings for filters of different subtypes.
-     */
-    public abstract static class Settings {
-        protected final int mType;
-
-        protected Settings(int type) {
-            mType = type;
-        }
-
-        /**
-         * Gets filter settings type.
-         * @return
-         */
-        int getType() {
-            return mType;
-        }
-    }
-
-    /**
-     *  Filter Settings for Section data according to ISO/IEC 13818-1.
-     */
-    public static class SectionSettings extends Settings {
-
-        private SectionSettings(int mainType) {
-            super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_SECTION));
-        }
-    }
-
-    /**
-     *  Bits Settings for Section Filter.
-     */
-    public static class SectionSettingsWithSectionBits extends SectionSettings {
-        private List<Byte> mFilter;
-        private List<Byte> mMask;
-        private List<Byte> mMode;
-
-        private SectionSettingsWithSectionBits(int mainType) {
-            super(mainType);
-        }
-    }
-
-    /**
-     *  Table information for Section Filter.
-     */
-    public static class SectionSettingsWithTableInfo extends SectionSettings {
-        private int mTableId;
-        private int mVersion;
-
-        private SectionSettingsWithTableInfo(int mainType) {
-            super(mainType);
-        }
-    }
-
-    /**
-     *  Filter Settings for a PES Data.
-     */
-    public static class PesSettings extends Settings {
-        private int mStreamId;
-        private boolean mIsRaw;
-
-        private PesSettings(int mainType, int streamId, boolean isRaw) {
-            super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_PES));
-            mStreamId = streamId;
-            mIsRaw = isRaw;
-        }
-
-        /**
-         * Creates a builder for PesSettings.
-         */
-        public static Builder newBuilder(int mainType) {
-            return new Builder(mainType);
-        }
-
-        /**
-         * Builder for PesSettings.
-         */
-        public static class Builder {
-            private final int mMainType;
-            private int mStreamId;
-            private boolean mIsRaw;
-
-            public Builder(int mainType) {
-                mMainType = mainType;
-            }
-
-            /**
-             * Sets stream ID.
-             */
-            public Builder setStreamId(int streamId) {
-                mStreamId = streamId;
-                return this;
-            }
-
-            /**
-             * Sets whether it's raw.
-             * true if the filter send onFilterStatus instead of onFilterEvent.
-             */
-            public Builder setIsRaw(boolean isRaw) {
-                mIsRaw = isRaw;
-                return this;
-            }
-
-            /**
-             * Builds a PesSettings instance.
-             */
-            public PesSettings build() {
-                return new PesSettings(mMainType, mStreamId, mIsRaw);
-            }
-        }
-    }
-
-    /**
-     *  Filter Settings for a Video and Audio.
-     */
-    public static class AvSettings extends Settings {
-        private boolean mIsPassthrough;
-
-        private AvSettings(int mainType, boolean isAudio) {
-            super(TunerUtils.getFilterSubtype(
-                    mainType,
-                    isAudio
-                            ? TunerConstants.FILTER_SUBTYPE_AUDIO
-                            : TunerConstants.FILTER_SUBTYPE_VIDEO));
-        }
-    }
-
-    /**
-     *  Filter Settings for a Download.
-     */
-    public static class DownloadSettings extends Settings {
-        private int mDownloadId;
-
-        public DownloadSettings(int mainType) {
-            super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_DOWNLOAD));
-        }
-    }
-
-    /**
-     *  The Settings for the record in DVR.
-     */
-    public static class RecordSettings extends Settings {
-        private int mIndexType;
-        private int mIndexMask;
-
-        public RecordSettings(int mainType) {
-            super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_RECORD));
-        }
-    }
-
-}
diff --git a/media/java/android/media/tv/tuner/FrontendCapabilities.java b/media/java/android/media/tv/tuner/FrontendCapabilities.java
deleted file mode 100644
index fcfd7c8..0000000
--- a/media/java/android/media/tv/tuner/FrontendCapabilities.java
+++ /dev/null
@@ -1,291 +0,0 @@
-/*
- * Copyright 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.media.tv.tuner;
-
-/**
- * Frontend Capabilities.
- * @hide
- */
-public class FrontendCapabilities {
-    /** Analog Capabilities. */
-    public class Analog extends FrontendCapabilities {
-        private final int mTypeCap;
-        private final int mSifStandardCap;
-
-        Analog(int typeCap, int sifStandardCap) {
-            mTypeCap = typeCap;
-            mSifStandardCap = sifStandardCap;
-        }
-        /**
-         * Gets type capability.
-         */
-        public int getTypeCapability() {
-            return mTypeCap;
-        }
-        /** Gets SIF standard capability. */
-        public int getSifStandardCapability() {
-            return mSifStandardCap;
-        }
-    }
-
-    /** ATSC Capabilities. */
-    public class Atsc extends FrontendCapabilities {
-        private final int mModulationCap;
-
-        Atsc(int modulationCap) {
-            mModulationCap = modulationCap;
-        }
-        /** Gets modulation capability. */
-        public int getModulationCapability() {
-            return mModulationCap;
-        }
-    }
-
-    /** ATSC-3 Capabilities. */
-    public class Atsc3 extends FrontendCapabilities {
-        private final int mBandwidthCap;
-        private final int mModulationCap;
-        private final int mTimeInterleaveModeCap;
-        private final int mCodeRateCap;
-        private final int mFecCap;
-        private final int mDemodOutputFormatCap;
-
-        Atsc3(int bandwidthCap, int modulationCap, int timeInterleaveModeCap, int codeRateCap,
-                int fecCap, int demodOutputFormatCap) {
-            mBandwidthCap = bandwidthCap;
-            mModulationCap = modulationCap;
-            mTimeInterleaveModeCap = timeInterleaveModeCap;
-            mCodeRateCap = codeRateCap;
-            mFecCap = fecCap;
-            mDemodOutputFormatCap = demodOutputFormatCap;
-        }
-
-        /** Gets bandwidth capability. */
-        public int getBandwidthCapability() {
-            return mBandwidthCap;
-        }
-        /** Gets modulation capability. */
-        public int getModulationCapability() {
-            return mModulationCap;
-        }
-        /** Gets time interleave mod capability. */
-        public int getTimeInterleaveModeCapability() {
-            return mTimeInterleaveModeCap;
-        }
-        /** Gets code rate capability. */
-        public int getCodeRateCapability() {
-            return mCodeRateCap;
-        }
-        /** Gets FEC capability. */
-        public int getFecCapability() {
-            return mFecCap;
-        }
-        /** Gets demodulator output format capability. */
-        public int getDemodOutputFormatCapability() {
-            return mDemodOutputFormatCap;
-        }
-    }
-
-    /** DVBS Capabilities. */
-    public class Dvbs extends FrontendCapabilities {
-        private final int mModulationCap;
-        private final long mInnerFecCap;
-        private final int mStandard;
-
-        Dvbs(int modulationCap, long innerFecCap, int standard) {
-            mModulationCap = modulationCap;
-            mInnerFecCap = innerFecCap;
-            mStandard = standard;
-        }
-
-        /** Gets modulation capability. */
-        public int getModulationCapability() {
-            return mModulationCap;
-        }
-        /** Gets inner FEC capability. */
-        public long getInnerFecCapability() {
-            return mInnerFecCap;
-        }
-        /** Gets DVBS standard capability. */
-        public int getStandardCapability() {
-            return mStandard;
-        }
-    }
-
-    /** DVBC Capabilities. */
-    public class Dvbc extends FrontendCapabilities {
-        private final int mModulationCap;
-        private final int mFecCap;
-        private final int mAnnexCap;
-
-        Dvbc(int modulationCap, int fecCap, int annexCap) {
-            mModulationCap = modulationCap;
-            mFecCap = fecCap;
-            mAnnexCap = annexCap;
-        }
-
-        /** Gets modulation capability. */
-        public int getModulationCapability() {
-            return mModulationCap;
-        }
-        /** Gets FEC capability. */
-        public int getFecCapability() {
-            return mFecCap;
-        }
-        /** Gets annex capability. */
-        public int getAnnexCapability() {
-            return mAnnexCap;
-        }
-    }
-
-    /** DVBT Capabilities. */
-    public class Dvbt extends FrontendCapabilities {
-        private final int mTransmissionModeCap;
-        private final int mBandwidthCap;
-        private final int mConstellationCap;
-        private final int mCoderateCap;
-        private final int mHierarchyCap;
-        private final int mGuardIntervalCap;
-        private final boolean mIsT2Supported;
-        private final boolean mIsMisoSupported;
-
-        Dvbt(int transmissionModeCap, int bandwidthCap, int constellationCap, int coderateCap,
-                int hierarchyCap, int guardIntervalCap, boolean isT2Supported,
-                boolean isMisoSupported) {
-            mTransmissionModeCap = transmissionModeCap;
-            mBandwidthCap = bandwidthCap;
-            mConstellationCap = constellationCap;
-            mCoderateCap = coderateCap;
-            mHierarchyCap = hierarchyCap;
-            mGuardIntervalCap = guardIntervalCap;
-            mIsT2Supported = isT2Supported;
-            mIsMisoSupported = isMisoSupported;
-        }
-
-        /** Gets transmission mode capability. */
-        public int getTransmissionModeCapability() {
-            return mTransmissionModeCap;
-        }
-        /** Gets bandwidth capability. */
-        public int getBandwidthCapability() {
-            return mBandwidthCap;
-        }
-        /** Gets constellation capability. */
-        public int getConstellationCapability() {
-            return mConstellationCap;
-        }
-        /** Gets code rate capability. */
-        public int getCodeRateCapability() {
-            return mCoderateCap;
-        }
-        /** Gets hierarchy capability. */
-        public int getHierarchyCapability() {
-            return mHierarchyCap;
-        }
-        /** Gets guard interval capability. */
-        public int getGuardIntervalCapability() {
-            return mGuardIntervalCap;
-        }
-        /** Returns whether T2 is supported. */
-        public boolean getIsT2Supported() {
-            return mIsT2Supported;
-        }
-        /** Returns whether MISO is supported. */
-        public boolean getIsMisoSupported() {
-            return mIsMisoSupported;
-        }
-    }
-
-    /** ISDBS Capabilities. */
-    public class Isdbs extends FrontendCapabilities {
-        private final int mModulationCap;
-        private final int mCoderateCap;
-
-        Isdbs(int modulationCap, int coderateCap) {
-            mModulationCap = modulationCap;
-            mCoderateCap = coderateCap;
-        }
-
-        /** Gets modulation capability. */
-        public int getModulationCapability() {
-            return mModulationCap;
-        }
-        /** Gets code rate capability. */
-        public int getCodeRateCapability() {
-            return mCoderateCap;
-        }
-    }
-
-    /** ISDBS-3 Capabilities. */
-    public class Isdbs3 extends FrontendCapabilities {
-        private final int mModulationCap;
-        private final int mCoderateCap;
-
-        Isdbs3(int modulationCap, int coderateCap) {
-            mModulationCap = modulationCap;
-            mCoderateCap = coderateCap;
-        }
-
-        /** Gets modulation capability. */
-        public int getModulationCapability() {
-            return mModulationCap;
-        }
-        /** Gets code rate capability. */
-        public int getCodeRateCapability() {
-            return mCoderateCap;
-        }
-    }
-
-    /** ISDBC Capabilities. */
-    public class Isdbc extends FrontendCapabilities {
-        private final int mModeCap;
-        private final int mBandwidthCap;
-        private final int mModulationCap;
-        private final int mCoderateCap;
-        private final int mGuardIntervalCap;
-
-        Isdbc(int modeCap, int bandwidthCap, int modulationCap, int coderateCap,
-                int guardIntervalCap) {
-            mModeCap = modeCap;
-            mBandwidthCap = bandwidthCap;
-            mModulationCap = modulationCap;
-            mCoderateCap = coderateCap;
-            mGuardIntervalCap = guardIntervalCap;
-        }
-
-        /** Gets mode capability. */
-        public int getModeCapability() {
-            return mModeCap;
-        }
-        /** Gets bandwidth capability. */
-        public int getBandwidthCapability() {
-            return mBandwidthCap;
-        }
-        /** Gets modulation capability. */
-        public int getModulationCapability() {
-            return mModulationCap;
-        }
-        /** Gets code rate capability. */
-        public int getCodeRateCapability() {
-            return mCoderateCap;
-        }
-        /** Gets guard interval capability. */
-        public int getGuardIntervalCapability() {
-            return mGuardIntervalCap;
-        }
-    }
-}
diff --git a/media/java/android/media/tv/tuner/FrontendSettings.java b/media/java/android/media/tv/tuner/FrontendSettings.java
index e2e9910..7f9b982 100644
--- a/media/java/android/media/tv/tuner/FrontendSettings.java
+++ b/media/java/android/media/tv/tuner/FrontendSettings.java
@@ -16,35 +16,10 @@
 
 package android.media.tv.tuner;
 
-import android.annotation.SystemApi;
-import android.media.tv.tuner.TunerConstants.FrontendSettingsType;
-
 /**
  * Frontend settings for tune and scan operations.
+ * TODO: remove
  * @hide
  */
-@SystemApi
 public abstract class FrontendSettings {
-    private final int mFrequency;
-
-    /** @hide */
-    public FrontendSettings(int frequency) {
-        mFrequency = frequency;
-    }
-
-    /**
-     * Returns the frontend type.
-     */
-    @FrontendSettingsType
-    public abstract int getType();
-
-    /**
-     * Gets the frequency setting.
-     *
-     * @return the frequency in Hz.
-     */
-    public final int getFrequency() {
-        return mFrequency;
-    }
-
 }
diff --git a/media/java/android/media/tv/tuner/Lnb.java b/media/java/android/media/tv/tuner/Lnb.java
new file mode 100644
index 0000000..a8d2cba
--- /dev/null
+++ b/media/java/android/media/tv/tuner/Lnb.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 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.media.tv.tuner;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerConstants.Result;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * LNB (low-noise block downconverter) for satellite tuner.
+ *
+ * A Tuner LNB (low-noise block downconverter) is used by satellite frontend to receive the
+ * microwave signal from the satellite, amplify it, and downconvert the frequency to a lower
+ * frequency.
+ *
+ * @hide
+ */
+@SystemApi
+public class Lnb implements AutoCloseable {
+    /** @hide */
+    @IntDef(prefix = "VOLTAGE_",
+            value = {VOLTAGE_NONE, VOLTAGE_5V, VOLTAGE_11V, VOLTAGE_12V, VOLTAGE_13V, VOLTAGE_14V,
+            VOLTAGE_15V, VOLTAGE_18V, VOLTAGE_19V})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Voltage {}
+
+    /**
+     * LNB power voltage not set.
+     */
+    public static final int VOLTAGE_NONE = Constants.LnbVoltage.NONE;
+    /**
+     * LNB power voltage 5V.
+     */
+    public static final int VOLTAGE_5V = Constants.LnbVoltage.VOLTAGE_5V;
+    /**
+     * LNB power voltage 11V.
+     */
+    public static final int VOLTAGE_11V = Constants.LnbVoltage.VOLTAGE_11V;
+    /**
+     * LNB power voltage 12V.
+     */
+    public static final int VOLTAGE_12V = Constants.LnbVoltage.VOLTAGE_12V;
+    /**
+     * LNB power voltage 13V.
+     */
+    public static final int VOLTAGE_13V = Constants.LnbVoltage.VOLTAGE_13V;
+    /**
+     * LNB power voltage 14V.
+     */
+    public static final int VOLTAGE_14V = Constants.LnbVoltage.VOLTAGE_14V;
+    /**
+     * LNB power voltage 15V.
+     */
+    public static final int VOLTAGE_15V = Constants.LnbVoltage.VOLTAGE_15V;
+    /**
+     * LNB power voltage 18V.
+     */
+    public static final int VOLTAGE_18V = Constants.LnbVoltage.VOLTAGE_18V;
+    /**
+     * LNB power voltage 19V.
+     */
+    public static final int VOLTAGE_19V = Constants.LnbVoltage.VOLTAGE_19V;
+
+    /** @hide */
+    @IntDef(prefix = "TONE_",
+            value = {TONE_NONE, TONE_CONTINUOUS})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Tone {}
+
+    /**
+     * LNB tone mode not set.
+     */
+    public static final int TONE_NONE = Constants.LnbTone.NONE;
+    /**
+     * LNB continuous tone mode.
+     */
+    public static final int TONE_CONTINUOUS = Constants.LnbTone.CONTINUOUS;
+
+    /** @hide */
+    @IntDef(prefix = "POSITION_",
+            value = {POSITION_UNDEFINED, POSITION_A, POSITION_B})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Position {}
+
+    /**
+     * LNB position is not defined.
+     */
+    public static final int POSITION_UNDEFINED = Constants.LnbPosition.UNDEFINED;
+    /**
+     * Position A of two-band LNBs
+     */
+    public static final int POSITION_A = Constants.LnbPosition.POSITION_A;
+    /**
+     * Position B of two-band LNBs
+     */
+    public static final int POSITION_B = Constants.LnbPosition.POSITION_B;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "EVENT_TYPE_",
+            value = {EVENT_TYPE_DISEQC_RX_OVERFLOW, EVENT_TYPE_DISEQC_RX_TIMEOUT,
+            EVENT_TYPE_DISEQC_RX_PARITY_ERROR, EVENT_TYPE_LNB_OVERLOAD})
+    public @interface EventType {}
+
+    /**
+     * Outgoing Diseqc message overflow.
+     */
+    public static final int EVENT_TYPE_DISEQC_RX_OVERFLOW =
+            Constants.LnbEventType.DISEQC_RX_OVERFLOW;
+    /**
+     * Outgoing Diseqc message isn't delivered on time.
+     */
+    public static final int EVENT_TYPE_DISEQC_RX_TIMEOUT =
+            Constants.LnbEventType.DISEQC_RX_TIMEOUT;
+    /**
+     * Incoming Diseqc message has parity error.
+     */
+    public static final int EVENT_TYPE_DISEQC_RX_PARITY_ERROR =
+            Constants.LnbEventType.DISEQC_RX_PARITY_ERROR;
+    /**
+     * LNB is overload.
+     */
+    public static final int EVENT_TYPE_LNB_OVERLOAD = Constants.LnbEventType.LNB_OVERLOAD;
+
+    int mId;
+    LnbCallback mCallback;
+    Context mContext;
+
+    private native int nativeSetVoltage(int voltage);
+    private native int nativeSetTone(int tone);
+    private native int nativeSetSatellitePosition(int position);
+    private native int nativeSendDiseqcMessage(byte[] message);
+    private native int nativeClose();
+
+    Lnb(int id) {
+        mId = id;
+    }
+
+    void setCallback(@Nullable LnbCallback callback) {
+        mCallback = callback;
+        if (mCallback == null) {
+            return;
+        }
+    }
+
+    /**
+     * Sets the LNB's power voltage.
+     *
+     * @param voltage the power voltage constant the Lnb to use.
+     * @return result status of the operation.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @Result
+    public int setVoltage(@Voltage int voltage) {
+        TunerUtils.checkTunerPermission(mContext);
+        return nativeSetVoltage(voltage);
+    }
+
+    /**
+     * Sets the LNB's tone mode.
+     *
+     * @param tone the tone mode the Lnb to use.
+     * @return result status of the operation.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @Result
+    public int setTone(@Tone int tone) {
+        TunerUtils.checkTunerPermission(mContext);
+        return nativeSetTone(tone);
+    }
+
+    /**
+     * Selects the LNB's position.
+     *
+     * @param position the position the Lnb to use.
+     * @return result status of the operation.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @Result
+    public int setSatellitePosition(@Position int position) {
+        TunerUtils.checkTunerPermission(mContext);
+        return nativeSetSatellitePosition(position);
+    }
+
+    /**
+     * Sends DiSEqC (Digital Satellite Equipment Control) message.
+     *
+     * The response message from the device comes back through callback onDiseqcMessage.
+     *
+     * @param message a byte array of data for DiSEqC message which is specified by EUTELSAT Bus
+     *         Functional Specification Version 4.2.
+     *
+     * @return result status of the operation.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @Result
+    public int sendDiseqcMessage(@NonNull byte[] message) {
+        TunerUtils.checkTunerPermission(mContext);
+        return nativeSendDiseqcMessage(message);
+    }
+
+    /**
+     * Releases the LNB instance.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    public void close() {
+        TunerUtils.checkTunerPermission(mContext);
+        nativeClose();
+    }
+}
diff --git a/media/java/android/media/tv/tuner/LnbCallback.java b/media/java/android/media/tv/tuner/LnbCallback.java
new file mode 100644
index 0000000..7862470
--- /dev/null
+++ b/media/java/android/media/tv/tuner/LnbCallback.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 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.media.tv.tuner;
+
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.media.tv.tuner.Lnb.EventType;
+
+/**
+ * Callback interface for receiving information from LNBs.
+ *
+ * @hide
+ */
+@SystemApi
+public interface LnbCallback {
+    /**
+     * Invoked when there is a LNB event.
+     */
+    void onEvent(@EventType int lnbEventType);
+
+    /**
+     * Invoked when there is a new DiSEqC message.
+     *
+     * @param diseqcMessage a byte array of data for DiSEqC (Digital Satellite
+     * Equipment Control) message which is specified by EUTELSAT Bus Functional
+     * Specification Version 4.2.
+     */
+    void onDiseqcMessage(@NonNull byte[] diseqcMessage);
+}
diff --git a/media/java/android/media/tv/tuner/ScanMessage.java b/media/java/android/media/tv/tuner/ScanMessage.java
deleted file mode 100644
index 35f54f8..0000000
--- a/media/java/android/media/tv/tuner/ScanMessage.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright 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.media.tv.tuner;
-
-import android.media.tv.tuner.TunerConstants.ScanMessageType;
-
-/**
- * Message from frontend during scan operations.
- *
- * @hide
- */
-public class ScanMessage {
-    private final int mType;
-    private final Object mValue;
-
-    private ScanMessage(int type, Object value) {
-        mType = type;
-        mValue = value;
-    }
-
-    /** Gets scan message type. */
-    @ScanMessageType
-    public int getMessageType() {
-        return mType;
-    }
-    /** Message indicates whether frontend is locked or not. */
-    public boolean getIsLocked() {
-        if (mType != TunerConstants.SCAN_MESSAGE_TYPE_LOCKED) {
-            throw new IllegalStateException();
-        }
-        return (Boolean) mValue;
-    }
-    /** Message indicates whether the scan has reached the end or not. */
-    public boolean getIsEnd() {
-        if (mType != TunerConstants.SCAN_MESSAGE_TYPE_END) {
-            throw new IllegalStateException();
-        }
-        return (Boolean) mValue;
-    }
-    /** Progress message in percent. */
-    public int getProgressPercent() {
-        if (mType != TunerConstants.SCAN_MESSAGE_TYPE_PROGRESS_PERCENT) {
-            throw new IllegalStateException();
-        }
-        return (Integer) mValue;
-    }
-    /** Gets frequency. */
-    public int getFrequency() {
-        if (mType != TunerConstants.SCAN_MESSAGE_TYPE_FREQUENCY) {
-            throw new IllegalStateException();
-        }
-        return (Integer) mValue;
-    }
-    /** Gets symbol rate. */
-    public int getSymbolRate() {
-        if (mType != TunerConstants.SCAN_MESSAGE_TYPE_SYMBOL_RATE) {
-            throw new IllegalStateException();
-        }
-        return (Integer) mValue;
-    }
-    /** Gets PLP IDs. */
-    public int[] getPlpIds() {
-        if (mType != TunerConstants.SCAN_MESSAGE_TYPE_PLP_IDS) {
-            throw new IllegalStateException();
-        }
-        return (int[]) mValue;
-    }
-    /** Gets group IDs. */
-    public int[] getGroupIds() {
-        if (mType != TunerConstants.SCAN_MESSAGE_TYPE_GROUP_IDS) {
-            throw new IllegalStateException();
-        }
-        return (int[]) mValue;
-    }
-    /** Gets Input stream IDs. */
-    public int[] getInputStreamIds() {
-        if (mType != TunerConstants.SCAN_MESSAGE_TYPE_INPUT_STREAM_IDS) {
-            throw new IllegalStateException();
-        }
-        return (int[]) mValue;
-    }
-    /** Gets the DVB-T or DVB-S standard. */
-    public int getStandard() {
-        if (mType != TunerConstants.SCAN_MESSAGE_TYPE_STANDARD) {
-            throw new IllegalStateException();
-        }
-        return (int) mValue;
-    }
-
-    /** Gets PLP information for ATSC3. */
-    public Atsc3PlpInfo[] getAtsc3PlpInfos() {
-        if (mType != TunerConstants.SCAN_MESSAGE_TYPE_ATSC3_PLP_INFO) {
-            throw new IllegalStateException();
-        }
-        return (Atsc3PlpInfo[]) mValue;
-    }
-
-    /** PLP information for ATSC3. */
-    public static class Atsc3PlpInfo {
-        private final int mPlpId;
-        private final boolean mLlsFlag;
-
-        private Atsc3PlpInfo(int plpId, boolean llsFlag) {
-            mPlpId = plpId;
-            mLlsFlag = llsFlag;
-        }
-
-        /** Gets PLP IDs. */
-        public int getPlpId() {
-            return mPlpId;
-        }
-        /** Gets LLS flag. */
-        public boolean getLlsFlag() {
-            return mLlsFlag;
-        }
-    }
-}
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 43f9a89..5e01244 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -16,31 +16,35 @@
 
 package android.media.tv.tuner;
 
+import android.annotation.BytesLong;
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.content.Context;
-import android.media.tv.tuner.FilterConfiguration.Settings;
-import android.media.tv.tuner.TunerConstants.DemuxPidType;
-import android.media.tv.tuner.TunerConstants.FilterStatus;
-import android.media.tv.tuner.TunerConstants.FilterSubtype;
-import android.media.tv.tuner.TunerConstants.FilterType;
-import android.media.tv.tuner.TunerConstants.FrontendScanType;
-import android.media.tv.tuner.TunerConstants.LnbPosition;
-import android.media.tv.tuner.TunerConstants.LnbTone;
-import android.media.tv.tuner.TunerConstants.LnbVoltage;
+import android.media.tv.TvInputService;
 import android.media.tv.tuner.TunerConstants.Result;
-import android.media.tv.tuner.filter.FilterEvent;
-import android.media.tv.tuner.frontend.FrontendCallback;
+import android.media.tv.tuner.dvr.DvrPlayback;
+import android.media.tv.tuner.dvr.DvrRecorder;
+import android.media.tv.tuner.dvr.OnPlaybackStatusChangedListener;
+import android.media.tv.tuner.dvr.OnRecordStatusChangedListener;
+import android.media.tv.tuner.filter.Filter;
+import android.media.tv.tuner.filter.Filter.Subtype;
+import android.media.tv.tuner.filter.Filter.Type;
+import android.media.tv.tuner.filter.FilterCallback;
+import android.media.tv.tuner.filter.TimeFilter;
 import android.media.tv.tuner.frontend.FrontendInfo;
+import android.media.tv.tuner.frontend.FrontendSettings;
 import android.media.tv.tuner.frontend.FrontendStatus;
+import android.media.tv.tuner.frontend.OnTuneEventListener;
+import android.media.tv.tuner.frontend.ScanCallback;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 
-import java.io.FileDescriptor;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
  * This class is used to interact with hardware tuners devices.
@@ -52,11 +56,10 @@
  * @hide
  */
 @SystemApi
-public final class Tuner implements AutoCloseable  {
+public class Tuner implements AutoCloseable  {
     private static final String TAG = "MediaTvTuner";
     private static final boolean DEBUG = false;
 
-    private static final int MSG_ON_FRONTEND_EVENT = 1;
     private static final int MSG_ON_FILTER_EVENT = 2;
     private static final int MSG_ON_FILTER_STATUS = 3;
     private static final int MSG_ON_LNB_EVENT = 4;
@@ -74,22 +77,65 @@
 
     private List<Integer> mLnbIds;
     private Lnb mLnb;
+    @Nullable
+    private OnTuneEventListener mOnTuneEventListener;
+    @Nullable
+    private Executor mOnTunerEventExecutor;
+    @Nullable
+    private ScanCallback mScanCallback;
+    @Nullable
+    private Executor mScanCallbackExecutor;
 
     /**
      * Constructs a Tuner instance.
      *
-     * @param context context of the caller.
+     * @param context the context of the caller.
+     * @param tvInputSessionId the session ID of the TV input.
+     * @param useCase the use case of this Tuner instance.
      */
-    public Tuner(@NonNull Context context) {
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    public Tuner(@NonNull Context context, @NonNull String tvInputSessionId,
+            @TvInputService.PriorityHintUseCaseType int useCase,
+            @Nullable OnResourceLostListener listener) {
         mContext = context;
-        nativeSetup();
+    }
+
+    /**
+     * Shares the frontend resource with another Tuner instance
+     *
+     * @param tuner the Tuner instance to share frontend resource with.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    public void shareFrontendFromTuner(@NonNull Tuner tuner) {
+        // TODO: implementation.
+    }
+
+    /**
+     * Updates client priority with an arbitrary value along with a nice value.
+     *
+     * <p>Tuner resource manager (TRM) uses the client priority value to decide whether it is able
+     * to reclaim insufficient resources from another client.
+     * <p>The nice value represents how much the client intends to give up the resource when an
+     * insufficient resource situation happens.
+     *
+     * @param priority the new priority.
+     * @param niceValue the nice value.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    public void updateResourcePriority(int priority, int niceValue) {
+        // TODO: implementation.
     }
 
     private long mNativeContext; // used by native jMediaTuner
 
-    /** @hide */
+    /**
+     * Releases the Tuner instance.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
     @Override
-    public void close() {}
+    public void close() {
+        // TODO: implementation.
+    }
 
     /**
      * Native Initialization.
@@ -116,78 +162,38 @@
     private native int nativeStopScan();
     private native int nativeSetLnb(int lnbId);
     private native int nativeSetLna(boolean enable);
-    private native FrontendStatus[] nativeGetFrontendStatus(int[] statusTypes);
+    private native FrontendStatus nativeGetFrontendStatus(int[] statusTypes);
     private native int nativeGetAvSyncHwId(Filter filter);
     private native long nativeGetAvSyncTime(int avSyncId);
     private native int nativeConnectCiCam(int ciCamId);
     private native int nativeDisconnectCiCam();
     private native FrontendInfo nativeGetFrontendInfo(int id);
-    private native Filter nativeOpenFilter(int type, int subType, int bufferSize);
+    private native Filter nativeOpenFilter(int type, int subType, long bufferSize);
+    private native TimeFilter nativeOpenTimeFilter();
 
     private native List<Integer> nativeGetLnbIds();
     private native Lnb nativeOpenLnbById(int id);
 
     private native Descrambler nativeOpenDescrambler();
 
-    private native Dvr nativeOpenDvr(int type, int bufferSize);
+    private native DvrRecorder nativeOpenDvrRecorder(long bufferSize);
+    private native DvrPlayback nativeOpenDvrPlayback(long bufferSize);
 
     private static native DemuxCapabilities nativeGetDemuxCapabilities();
 
+
     /**
-     * LNB Callback.
+     * Listener for resource lost.
      *
-     * @hide
+     * <p>Resource is reclaimed and tuner instance is forced to close.
      */
-    public interface LnbCallback {
+    public interface OnResourceLostListener {
         /**
-         * Invoked when there is a LNB event.
-         */
-        void onEvent(int lnbEventType);
-
-        /**
-         * Invoked when there is a new DiSEqC message.
+         * Invoked when resource lost.
          *
-         * @param diseqcMessage a byte array of data for DiSEqC (Digital Satellite
-         * Equipment Control) message which is specified by EUTELSAT Bus Functional
-         * Specification Version 4.2.
+         * @param tuner the tuner instance whose resource is being reclaimed.
          */
-        void onDiseqcMessage(byte[] diseqcMessage);
-    }
-
-    /**
-     * Callback interface for receiving information from the corresponding filters.
-     */
-    public interface FilterCallback {
-        /**
-         * Invoked when there are filter events.
-         *
-         * @param filter the corresponding filter which sent the events.
-         * @param events the filter events sent from the filter.
-         */
-        void onFilterEvent(@NonNull Filter filter, @NonNull FilterEvent[] events);
-        /**
-         * Invoked when filter status changed.
-         *
-         * @param filter the corresponding filter whose status is changed.
-         * @param status the new status of the filter.
-         */
-        void onFilterStatusChanged(@NonNull Filter filter, @FilterStatus int status);
-    }
-
-    /**
-     * DVR Callback.
-     *
-     * @hide
-     */
-    public interface DvrCallback {
-        /**
-         * Invoked when record status changed.
-         */
-        void onRecordStatus(int status);
-        /**
-         * Invoked when playback status changed.
-         */
-        void onPlaybackStatus(int status);
+        void onResourceLost(@NonNull Tuner tuner);
     }
 
     @Nullable
@@ -209,23 +215,13 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case MSG_ON_FRONTEND_EVENT:
-                    if (mFrontend != null && mFrontend.mCallback != null) {
-                        mFrontend.mCallback.onEvent(msg.arg1);
-                    }
-                    break;
                 case MSG_ON_FILTER_STATUS: {
                     Filter filter = (Filter) msg.obj;
-                    if (filter.mCallback != null) {
-                        filter.mCallback.onFilterStatusChanged(filter, msg.arg1);
+                    if (filter.getCallback() != null) {
+                        filter.getCallback().onFilterStatusChanged(filter, msg.arg1);
                     }
                     break;
                 }
-                case MSG_ON_LNB_EVENT: {
-                    if (mLnb != null && mLnb.mCallback != null) {
-                        mLnb.mCallback.onEvent(msg.arg1);
-                    }
-                }
                 default:
                     // fall through
             }
@@ -234,44 +230,66 @@
 
     private class Frontend {
         private int mId;
-        private FrontendCallback mCallback;
 
         private Frontend(int id) {
             mId = id;
         }
-
-        public void setCallback(@Nullable FrontendCallback callback, @Nullable Handler handler) {
-            mCallback = callback;
-
-            if (mCallback == null) {
-                return;
-            }
-
-            if (handler == null) {
-                // use default looper if handler is null
-                if (mHandler == null) {
-                    mHandler = createEventHandler();
-                }
-                return;
-            }
-
-            Looper looper = handler.getLooper();
-            if (mHandler != null && mHandler.getLooper() == looper) {
-                // the same looper. reuse mHandler
-                return;
-            }
-            mHandler = new EventHandler(looper);
-        }
     }
 
     /**
-     * Tunes the frontend to the settings given.
+     * Listens for tune events.
      *
-     * @return result status of tune operation.
+     * <p>
+     * Tuner events are started when {@link #tune(FrontendSettings)} is called and end when {@link
+     * #stopTune()} is called.
+     *
+     * @param eventListener receives tune events.
      * @throws SecurityException if the caller does not have appropriate permissions.
-     * TODO: add result constants or throw exceptions.
+     * @see #tune(FrontendSettings)
      */
     @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    public void setOnTuneEventListener(@NonNull @CallbackExecutor Executor executor,
+            @NonNull OnTuneEventListener eventListener) {
+        TunerUtils.checkTunerPermission(mContext);
+        mOnTuneEventListener = eventListener;
+        mOnTunerEventExecutor = executor;
+    }
+
+    /**
+     * Clears the {@link OnTuneEventListener} and its associated {@link Executor}.
+     *
+     * @throws SecurityException if the caller does not have appropriate permissions.
+     * @see #setOnTuneEventListener(Executor, OnTuneEventListener)
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    public void clearOnTuneEventListener() {
+        TunerUtils.checkTunerPermission(mContext);
+        mOnTuneEventListener = null;
+        mOnTunerEventExecutor = null;
+
+    }
+
+    /**
+     * Tunes the frontend to using the settings given.
+     *
+     * <p>
+     * This locks the frontend to a frequency by providing signal
+     * delivery information. If previous tuning isn't completed, this stop the previous tuning, and
+     * start a new tuning.
+     *
+     * <p>
+     * Tune is an async call, with {@link OnTuneEventListener#SIGNAL_LOCKED} and {@link
+     * OnTuneEventListener#SIGNAL_NO_SIGNAL} events sent to the {@link OnTuneEventListener}
+     * specified in {@link #setOnTuneEventListener(Executor, OnTuneEventListener)}.
+     *
+     * @param settings Signal delivery information the frontend uses to
+     *                 search and lock the signal.
+     * @return result status of tune operation.
+     * @throws SecurityException if the caller does not have appropriate permissions.
+     * @see #setOnTuneEventListener(Executor, OnTuneEventListener)
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @Result
     public int tune(@NonNull FrontendSettings settings) {
         TunerUtils.checkTunerPermission(mContext);
         return nativeTune(settings.getType(), settings);
@@ -280,73 +298,104 @@
     /**
      * Stops a previous tuning.
      *
-     * If the method completes successfully the frontend is no longer tuned and no data
+     * <p>If the method completes successfully, the frontend is no longer tuned and no data
      * will be sent to attached filters.
      *
      * @return result status of the operation.
-     * @hide
      */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @Result
     public int stopTune() {
+        TunerUtils.checkTunerPermission(mContext);
         return nativeStopTune();
     }
 
     /**
-     * Scan channels.
-     * @hide
+     * Scan for channels.
+     *
+     * <p>Details for channels found are returned via {@link ScanCallback}.
+     *
+     * @param settings A {@link FrontendSettings} to configure the frontend.
+     * @param scanType The scan type.
+     * @throws SecurityException     if the caller does not have appropriate permissions.
+     * @throws IllegalStateException if {@code scan} is called again before {@link #stopScan()} is
+     *                               called.
      */
-    public int scan(@NonNull FrontendSettings settings, @FrontendScanType int scanType) {
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @Result
+    public int scan(@NonNull FrontendSettings settings, @TunerConstants.ScanType int scanType,
+            @NonNull @CallbackExecutor Executor executor, @NonNull ScanCallback scanCallback) {
+        TunerUtils.checkTunerPermission(mContext);
+        if (mScanCallback != null || mScanCallbackExecutor != null) {
+            throw new IllegalStateException(
+                    "Scan already in progress.  stopScan must be called before a new scan can be "
+                            + "started.");
+        }
+        mScanCallback = scanCallback;
+        mScanCallbackExecutor = executor;
         return nativeScan(settings.getType(), settings, scanType);
     }
 
     /**
      * Stops a previous scanning.
      *
-     * If the method completes successfully, the frontend stop previous scanning.
-     * @hide
+     * <p>
+     * The {@link ScanCallback} and it's {@link Executor} will be removed.
+     *
+     * <p>
+     * If the method completes successfully, the frontend stopped previous scanning.
+     *
+     * @throws SecurityException if the caller does not have appropriate permissions.
      */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @Result
     public int stopScan() {
-        return nativeStopScan();
+        TunerUtils.checkTunerPermission(mContext);
+        int retVal = nativeStopScan();
+        mScanCallback = null;
+        mScanCallbackExecutor = null;
+        return retVal;
     }
 
     /**
      * Sets Low-Noise Block downconverter (LNB) for satellite frontend.
      *
-     * This assigns a hardware LNB resource to the satellite tuner. It can be
+     * <p>This assigns a hardware LNB resource to the satellite tuner. It can be
      * called multiple times to update LNB assignment.
      *
      * @param lnb the LNB instance.
      *
      * @return result status of the operation.
-     * @hide
      */
-    public int setLnb(@NonNull Lnb lnb) {
+    @Result
+    private int setLnb(@NonNull Lnb lnb) {
         return nativeSetLnb(lnb.mId);
     }
 
     /**
      * Enable or Disable Low Noise Amplifier (LNA).
      *
-     * @param enable true to activate LNA module; false to deactivate LNA
+     * @param enable {@code true} to activate LNA module; {@code false} to deactivate LNA.
      *
      * @return result status of the operation.
-     * @hide
      */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @Result
     public int setLna(boolean enable) {
+        TunerUtils.checkTunerPermission(mContext);
         return nativeSetLna(enable);
     }
 
     /**
      * Gets the statuses of the frontend.
      *
-     * This retrieve the statuses of the frontend for given status types.
+     * <p>This retrieve the statuses of the frontend for given status types.
      *
-     * @param statusTypes an array of status type which the caller request.
-     *
-     * @return statuses an array of statuses which response the caller's
-     *         request.
-     * @hide
+     * @param statusTypes an array of status types which the caller requests.
+     * @return statuses which response the caller's requests.
      */
-    public FrontendStatus[] getFrontendStatus(int[] statusTypes) {
+    @Nullable
+    public FrontendStatus getFrontendStatus(@NonNull int[] statusTypes) {
         return nativeGetFrontendStatus(statusTypes);
     }
 
@@ -355,59 +404,67 @@
      *
      * @param filter the filter instance for the hardware sync ID.
      * @return the id of hardware A/V sync.
-     * @hide
      */
-    public int getAvSyncHwId(Filter filter) {
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    public int getAvSyncHwId(@NonNull Filter filter) {
+        TunerUtils.checkTunerPermission(mContext);
         return nativeGetAvSyncHwId(filter);
     }
+
     /**
-     * Gets the current timestamp for A/V sync
+     * Gets the current timestamp for Audio/Video sync
      *
-     * The timestamp is maintained by hardware. The timestamp based on 90KHz, and it's format is the
-     * same as PTS (Presentation Time Stamp).
+     * <p>The timestamp is maintained by hardware. The timestamp based on 90KHz, and it's format is
+     * the same as PTS (Presentation Time Stamp).
      *
      * @param avSyncHwId the hardware id of A/V sync.
      * @return the current timestamp of hardware A/V sync.
-     * @hide
      */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
     public long getAvSyncTime(int avSyncHwId) {
+        TunerUtils.checkTunerPermission(mContext);
         return nativeGetAvSyncTime(avSyncHwId);
     }
 
-
     /**
      * Connects Conditional Access Modules (CAM) through Common Interface (CI)
      *
-     * 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>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.
      *
      * @param ciCamId specify CI-CAM Id to connect.
      * @return result status of the operation.
-     * @hide
      */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
     @Result
     public int connectCiCam(int ciCamId) {
+        TunerUtils.checkTunerPermission(mContext);
         return nativeConnectCiCam(ciCamId);
     }
 
     /**
      * Disconnects Conditional Access Modules (CAM)
      *
-     * The demux will use the output from the frontend as the input after this call.
+     * <p>The demux will use the output from the frontend as the input after this call.
      *
      * @return result status of the operation.
-     * @hide
      */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
     @Result
     public int disconnectCiCam() {
+        TunerUtils.checkTunerPermission(mContext);
         return nativeDisconnectCiCam();
     }
 
     /**
-     * Retrieve the frontend information.
-     * @hide
+     * Gets the frontend information.
+     *
+     * @return The frontend information. {@code null} if the operation failed.
      */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @Nullable
     public FrontendInfo getFrontendInfo() {
+        TunerUtils.checkTunerPermission(mContext);
         if (mFrontend == null) {
             throw new IllegalStateException("frontend is not initialized");
         }
@@ -415,18 +472,12 @@
     }
 
     /**
-     * Gets frontend ID.
-     * @hide
+     * Gets Demux capabilities.
      */
-    public int getFrontendId() {
-        if (mFrontend == null) {
-            throw new IllegalStateException("frontend is not initialized");
-        }
-        return mFrontend.mId;
-    }
-
-    /** @hide */
-    private static DemuxCapabilities getDemuxCapabilities() {
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @Nullable
+    public static DemuxCapabilities getDemuxCapabilities(@NonNull Context context) {
+        TunerUtils.checkTunerPermission(context);
         return nativeGetDemuxCapabilities();
     }
 
@@ -447,139 +498,33 @@
     }
 
     private void onFrontendEvent(int eventType) {
-        if (mHandler != null) {
-            mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_FRONTEND_EVENT, eventType, 0));
+        if (mOnTunerEventExecutor != null && mOnTuneEventListener != null) {
+            mOnTunerEventExecutor.execute(() -> mOnTuneEventListener.onTuneEvent(eventType));
         }
     }
 
     /**
-     * Tuner data filter.
+     * Opens a filter object based on the given types and buffer size.
      *
-     * <p> This class is used to filter wanted data according to the filter's configuration.
+     * @param mainType the main type of the filter.
+     * @param subType the subtype of the filter.
+     * @param bufferSize the buffer size of the filter to be opened in bytes. The buffer holds the
+     * data output from the filter.
+     * @param executor the executor on which callback will be invoked. The default event handler
+     * executor is used if it's {@code null}.
+     * @param cb the callback to receive notifications from filter.
+     * @return the opened filter. {@code null} if the operation failed.
      */
-    public class Filter {
-        private long mNativeContext;
-        private FilterCallback mCallback;
-        int mId;
-
-        private native int nativeConfigureFilter(
-                int type, int subType, FilterConfiguration settings);
-        private native int nativeGetId();
-        private native int nativeSetDataSource(Filter source);
-        private native int nativeStartFilter();
-        private native int nativeStopFilter();
-        private native int nativeFlushFilter();
-        private native int nativeRead(byte[] buffer, int offset, int size);
-        private native int nativeClose();
-
-        private Filter(int id) {
-            mId = id;
-        }
-
-        private void onFilterStatus(int status) {
-            if (mHandler != null) {
-                mHandler.sendMessage(
-                        mHandler.obtainMessage(MSG_ON_FILTER_STATUS, status, 0, this));
-            }
-        }
-
-        /**
-         * Configures the filter.
-         *
-         * @param settings the settings of the filter.
-         * @return result status of the operation.
-         * @hide
-         */
-        public int configure(FilterConfiguration settings) {
-            int subType = -1;
-            Settings s = settings.getSettings();
-            if (s != null) {
-                subType = s.getType();
-            }
-            return nativeConfigureFilter(settings.getType(), subType, settings);
-        }
-
-        /**
-         * Gets the filter Id.
-         *
-         * @return the hardware resource Id for the filter.
-         * @hide
-         */
-        public int getId() {
-            return nativeGetId();
-        }
-
-        /**
-         * Sets the filter's data source.
-         *
-         * A filter uses demux as data source by default. If the data was packetized
-         * by multiple protocols, multiple filters may need to work together to
-         * extract all protocols' header. Then a filter's data source can be output
-         * from another filter.
-         *
-         * @param source the filter instance which provides data input. Switch to
-         * use demux as data source if the filter instance is NULL.
-         * @return result status of the operation.
-         * @hide
-         */
-        public int setDataSource(@Nullable Filter source) {
-            return nativeSetDataSource(source);
-        }
-
-        /**
-         * Starts the filter.
-         *
-         * @return result status of the operation.
-         * @hide
-         */
-        public int start() {
-            return nativeStartFilter();
-        }
-
-
-        /**
-         * Stops the filter.
-         *
-         * @return result status of the operation.
-         * @hide
-         */
-        public int stop() {
-            return nativeStopFilter();
-        }
-
-        /**
-         * Flushes the filter.
-         *
-         * @return result status of the operation.
-         * @hide
-         */
-        public int flush() {
-            return nativeFlushFilter();
-        }
-
-        /** @hide */
-        public int read(@NonNull byte[] buffer, int offset, int size) {
-            size = Math.min(size, buffer.length - offset);
-            return nativeRead(buffer, offset, size);
-        }
-
-        /**
-         * Release the Filter instance.
-         *
-         * @return result status of the operation.
-         * @hide
-         */
-        public int close() {
-            return nativeClose();
-        }
-    }
-
-    private Filter openFilter(@FilterType int mainType, @FilterSubtype int subType, int bufferSize,
-            FilterCallback cb) {
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @Nullable
+    public Filter openFilter(@Type int mainType, @Subtype int subType,
+            @BytesLong long bufferSize, @CallbackExecutor @Nullable Executor executor,
+            @Nullable FilterCallback cb) {
+        TunerUtils.checkTunerPermission(mContext);
         Filter filter = nativeOpenFilter(
                 mainType, TunerUtils.getFilterSubtype(mainType, subType), bufferSize);
         if (filter != null) {
-            filter.mCallback = cb;
+            filter.setCallback(cb);
             if (mHandler == null) {
                 mHandler = createEventHandler();
             }
@@ -587,88 +532,47 @@
         return filter;
     }
 
-    /** @hide */
-    public class Lnb {
-        private int mId;
-        private LnbCallback mCallback;
+    /**
+     * Opens an LNB (low-noise block downconverter) object.
+     *
+     * @param executor the executor on which callback will be invoked. The default event handler
+     * executor is used if it's {@code null}.
+     * @param cb the callback to receive notifications from LNB.
+     * @return the opened LNB object. {@code null} if the operation failed.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @Nullable
+    public Lnb openLnb(@CallbackExecutor @Nullable Executor executor, @Nullable LnbCallback cb) {
+        TunerUtils.checkTunerPermission(mContext);
+        return openLnbByName(null, executor, cb);
+    }
 
-        private native int nativeSetVoltage(int voltage);
-        private native int nativeSetTone(int tone);
-        private native int nativeSetSatellitePosition(int position);
-        private native int nativeSendDiseqcMessage(byte[] message);
-        private native int nativeClose();
+    /**
+     * Opens an LNB (low-noise block downconverter) object.
+     *
+     * @param name the LNB name.
+     * @param executor the executor on which callback will be invoked. The default event handler
+     * executor is used if it's {@code null}.
+     * @param cb the callback to receive notifications from LNB.
+     * @return the opened LNB object. {@code null} if the operation failed.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @Nullable
+    public Lnb openLnbByName(@Nullable String name, @CallbackExecutor @Nullable Executor executor,
+            @NonNull LnbCallback cb) {
+        TunerUtils.checkTunerPermission(mContext);
+        // TODO: use resource manager to get LNB ID.
+        return new Lnb(0);
+    }
 
-        private Lnb(int id) {
-            mId = id;
-        }
-
-        public void setCallback(@Nullable LnbCallback callback) {
-            mCallback = callback;
-            if (mCallback == null) {
-                return;
-            }
-            if (mHandler == null) {
-                mHandler = createEventHandler();
-            }
-        }
-
-        /**
-         * Sets the LNB's power voltage.
-         *
-         * @param voltage the power voltage the Lnb to use.
-         * @return result status of the operation.
-         */
-        @Result
-        public int setVoltage(@LnbVoltage int voltage) {
-            return nativeSetVoltage(voltage);
-        }
-
-        /**
-         * Sets the LNB's tone mode.
-         *
-         * @param tone the tone mode the Lnb to use.
-         * @return result status of the operation.
-         */
-        @Result
-        public int setTone(@LnbTone int tone) {
-            return nativeSetTone(tone);
-        }
-
-        /**
-         * Selects the LNB's position.
-         *
-         * @param position the position the Lnb to use.
-         * @return result status of the operation.
-         */
-        @Result
-        public int setSatellitePosition(@LnbPosition int position) {
-            return nativeSetSatellitePosition(position);
-        }
-
-        /**
-         * Sends DiSEqC (Digital Satellite Equipment Control) message.
-         *
-         * The response message from the device comes back through callback onDiseqcMessage.
-         *
-         * @param message a byte array of data for DiSEqC message which is specified by EUTELSAT Bus
-         *         Functional Specification Version 4.2.
-         *
-         * @return result status of the operation.
-         */
-        @Result
-        public int sendDiseqcMessage(byte[] message) {
-            return nativeSendDiseqcMessage(message);
-        }
-
-        /**
-         * Releases the LNB instance
-         *
-         * @return result status of the operation.
-         */
-        @Result
-        public int close() {
-            return nativeClose();
-        }
+    /**
+     * Open a time filter object.
+     *
+     * @return the opened time filter object. {@code null} if the operation failed.
+     */
+    @Nullable
+    public TimeFilter openTimeFilter() {
+        return nativeOpenTimeFilter();
     }
 
     private List<Integer> getLnbIds() {
@@ -694,89 +598,6 @@
     }
 
     /**
-     * This class is used to interact with descramblers.
-     *
-     * <p> Descrambler is a hardware component used to descramble data.
-     *
-     * <p> This class controls the TIS interaction with Tuner HAL.
-     * TODO: make it static and extends Closable.
-     */
-    public class Descrambler {
-        private long mNativeContext;
-
-        private native int nativeAddPid(int pidType, int pid, Filter filter);
-        private native int nativeRemovePid(int pidType, int pid, Filter filter);
-        private native int nativeSetKeyToken(byte[] keyToken);
-        private native int nativeClose();
-
-        private Descrambler() {}
-
-        /**
-         * Add packets' PID to the descrambler for descrambling.
-         *
-         * The descrambler will start descrambling packets with this PID. Multiple PIDs can be added
-         * into one descrambler instance because descambling can happen simultaneously on packets
-         * from different PIDs.
-         *
-         * If the Descrambler previously contained a filter for the PID, the old filter is replaced
-         * by the specified filter.
-         *
-         * @param pidType the type of the PID.
-         * @param pid the PID of packets to start to be descrambled.
-         * @param filter an optional filter instance to identify upper stream.
-         * @return result status of the operation.
-         *
-         * @hide
-         */
-        public int addPid(@DemuxPidType int pidType, int pid, @Nullable Filter filter) {
-            return nativeAddPid(pidType, pid, filter);
-        }
-
-        /**
-         * Remove packets' PID from the descrambler
-         *
-         * The descrambler will stop descrambling packets with this PID.
-         *
-         * @param pidType the type of the PID.
-         * @param pid the PID of packets to stop to be descrambled.
-         * @param filter an optional filter instance to identify upper stream.
-         * @return result status of the operation.
-         *
-         * @hide
-         */
-        public int removePid(@DemuxPidType int pidType, int pid, @Nullable Filter filter) {
-            return nativeRemovePid(pidType, pid, filter);
-        }
-
-        /**
-         * Set a key token to link descrambler to a key slot
-         *
-         * A descrambler instance can have only one key slot to link, but a key slot can hold a few
-         * keys for different purposes.
-         *
-         * @param keyToken the token to be used to link the key slot.
-         * @return result status of the operation.
-         *
-         * @hide
-         */
-        public int setKeyToken(byte[] keyToken) {
-            return nativeSetKeyToken(keyToken);
-        }
-
-        /**
-         * Release the descrambler instance.
-         *
-         * @return result status of the operation.
-         *
-         * @hide
-         */
-        public int close() {
-            return nativeClose();
-        }
-
-    }
-
-    /**
      * Opens a Descrambler in tuner.
      *
      * @return  a {@link Descrambler} object.
@@ -788,139 +609,45 @@
         return nativeOpenDescrambler();
     }
 
-    // TODO: consider splitting Dvr to Playback and Recording
-    /** @hide */
-    public class Dvr {
-        private long mNativeContext;
-        private DvrCallback mCallback;
-
-        private native int nativeAttachFilter(Filter filter);
-        private native int nativeDetachFilter(Filter filter);
-        private native int nativeConfigureDvr(DvrSettings settings);
-        private native int nativeStartDvr();
-        private native int nativeStopDvr();
-        private native int nativeFlushDvr();
-        private native int nativeClose();
-        private native void nativeSetFileDescriptor(FileDescriptor fd);
-        private native int nativeRead(int size);
-        private native int nativeRead(byte[] bytes, int offset, int size);
-        private native int nativeWrite(int size);
-        private native int nativeWrite(byte[] bytes, int offset, int size);
-
-        private Dvr() {}
-
-        /**
-         * Attaches a filter to DVR interface for recording.
-         *
-         * @param filter the filter to be attached.
-         * @return result status of the operation.
-         */
-        public int attachFilter(Filter filter) {
-            return nativeAttachFilter(filter);
-        }
-
-        /**
-         * Detaches a filter from DVR interface.
-         *
-         * @param filter the filter to be detached.
-         * @return result status of the operation.
-         */
-        public int detachFilter(Filter filter) {
-            return nativeDetachFilter(filter);
-        }
-
-        /**
-         * Configures the DVR.
-         *
-         * @param settings the settings of the DVR interface.
-         * @return result status of the operation.
-         */
-        public int configure(DvrSettings settings) {
-            return nativeConfigureDvr(settings);
-        }
-
-        /**
-         * Starts DVR.
-         *
-         * Starts consuming playback data or producing data for recording.
-         *
-         * @return result status of the operation.
-         */
-        public int start() {
-            return nativeStartDvr();
-        }
-
-        /**
-         * Stops DVR.
-         *
-         * Stops consuming playback data or producing data for recording.
-         *
-         * @return result status of the operation.
-         */
-        public int stop() {
-            return nativeStopDvr();
-        }
-
-        /**
-         * Flushed DVR data.
-         *
-         * @return result status of the operation.
-         */
-        public int flush() {
-            return nativeFlushDvr();
-        }
-
-        /**
-         * closes the DVR instance to release resources.
-         *
-         * @return result status of the operation.
-         */
-        public int close() {
-            return nativeClose();
-        }
-
-        /**
-         * Sets file descriptor to read/write data.
-         */
-        public void setFileDescriptor(FileDescriptor fd) {
-            nativeSetFileDescriptor(fd);
-        }
-
-        /**
-         * Reads data from the file for DVR playback.
-         */
-        public int read(int size) {
-            return nativeRead(size);
-        }
-
-        /**
-         * Reads data from the buffer for DVR playback.
-         */
-        public int read(@NonNull byte[] bytes, int offset, int size) {
-            if (size + offset > bytes.length) {
-                throw new ArrayIndexOutOfBoundsException(
-                        "Array length=" + bytes.length + ", offset=" + offset + ", size=" + size);
-            }
-            return nativeRead(bytes, offset, size);
-        }
-
-        /**
-         * Writes recording data to file.
-         */
-        public int write(int size) {
-            return nativeWrite(size);
-        }
-
-        /**
-         * Writes recording data to buffer.
-         */
-        public int write(@NonNull byte[] bytes, int offset, int size) {
-            return nativeWrite(bytes, offset, size);
-        }
+    /**
+     * Open a DVR (Digital Video Record) recorder instance.
+     *
+     * @param bufferSize the buffer size of the output in bytes. It's used to hold output data of
+     * the attached filters.
+     * @param executor the executor on which callback will be invoked. The default event handler
+     * executor is used if it's {@code null}.
+     * @param l the listener to receive notifications from DVR recorder.
+     * @return the opened DVR recorder object. {@code null} if the operation failed.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @Nullable
+    public DvrRecorder openDvrRecorder(
+            @BytesLong long bufferSize,
+            @CallbackExecutor @Nullable Executor executor,
+            @Nullable OnRecordStatusChangedListener l) {
+        TunerUtils.checkTunerPermission(mContext);
+        DvrRecorder dvr = nativeOpenDvrRecorder(bufferSize);
+        return dvr;
     }
 
-    private Dvr openDvr(int type, int bufferSize) {
-        Dvr dvr = nativeOpenDvr(type, bufferSize);
+    /**
+     * Open a DVR (Digital Video Record) playback instance.
+     *
+     * @param bufferSize the buffer size of the output in bytes. It's used to hold output data of
+     * the attached filters.
+     * @param executor the executor on which callback will be invoked. The default event handler
+     * executor is used if it's {@code null}.
+     * @param l the listener to receive notifications from DVR recorder.
+     * @return the opened DVR playback object. {@code null} if the operation failed.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @Nullable
+    public DvrPlayback openDvrPlayback(
+            @BytesLong long bufferSize,
+            @CallbackExecutor @Nullable Executor executor,
+            @Nullable OnPlaybackStatusChangedListener l) {
+        TunerUtils.checkTunerPermission(mContext);
+        DvrPlayback dvr = nativeOpenDvrPlayback(bufferSize);
         return dvr;
     }
 }
diff --git a/media/java/android/media/tv/tuner/TunerConstants.java b/media/java/android/media/tv/tuner/TunerConstants.java
index d24e582..f54808d 100644
--- a/media/java/android/media/tv/tuner/TunerConstants.java
+++ b/media/java/android/media/tv/tuner/TunerConstants.java
@@ -17,7 +17,6 @@
 package android.media.tv.tuner;
 
 import android.annotation.IntDef;
-import android.annotation.LongDef;
 import android.annotation.SystemApi;
 import android.hardware.tv.tuner.V1_0.Constants;
 
@@ -31,1350 +30,70 @@
  */
 @SystemApi
 public final class TunerConstants {
-    /** @hide */
+    /**
+     * Invalid TS packet ID.
+     */
     public static final int INVALID_TS_PID = Constants.Constant.INVALID_TS_PID;
-    /** @hide */
+    /**
+     * Invalid stream ID.
+     */
     public static final int INVALID_STREAM_ID = Constants.Constant.INVALID_STREAM_ID;
 
-
     /** @hide */
-    @IntDef({FRONTEND_TYPE_UNDEFINED, FRONTEND_TYPE_ANALOG, FRONTEND_TYPE_ATSC, FRONTEND_TYPE_ATSC3,
-            FRONTEND_TYPE_DVBC, FRONTEND_TYPE_DVBS, FRONTEND_TYPE_DVBT, FRONTEND_TYPE_ISDBS,
-            FRONTEND_TYPE_ISDBS3, FRONTEND_TYPE_ISDBT})
+    @IntDef(prefix = "SCAN_TYPE_", value = {SCAN_TYPE_UNDEFINED, SCAN_TYPE_AUTO, SCAN_TYPE_BLIND})
     @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendType {}
-
-    /** @hide */
-    public static final int FRONTEND_TYPE_UNDEFINED = Constants.FrontendType.UNDEFINED;
-    /** @hide */
-    public static final int FRONTEND_TYPE_ANALOG = Constants.FrontendType.ANALOG;
-    /** @hide */
-    public static final int FRONTEND_TYPE_ATSC = Constants.FrontendType.ATSC;
-    /** @hide */
-    public static final int FRONTEND_TYPE_ATSC3 = Constants.FrontendType.ATSC3;
-    /** @hide */
-    public static final int FRONTEND_TYPE_DVBC = Constants.FrontendType.DVBC;
-    /** @hide */
-    public static final int FRONTEND_TYPE_DVBS = Constants.FrontendType.DVBS;
-    /** @hide */
-    public static final int FRONTEND_TYPE_DVBT = Constants.FrontendType.DVBT;
-    /** @hide */
-    public static final int FRONTEND_TYPE_ISDBS = Constants.FrontendType.ISDBS;
-    /** @hide */
-    public static final int FRONTEND_TYPE_ISDBS3 = Constants.FrontendType.ISDBS3;
-    /** @hide */
-    public static final int FRONTEND_TYPE_ISDBT = Constants.FrontendType.ISDBT;
-
-
-    /** @hide */
-    @IntDef({FRONTEND_EVENT_TYPE_LOCKED, FRONTEND_EVENT_TYPE_NO_SIGNAL,
-            FRONTEND_EVENT_TYPE_LOST_LOCK})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendEventType {}
-    /** @hide */
-    public static final int FRONTEND_EVENT_TYPE_LOCKED = Constants.FrontendEventType.LOCKED;
-    /** @hide */
-    public static final int FRONTEND_EVENT_TYPE_NO_SIGNAL = Constants.FrontendEventType.NO_SIGNAL;
-    /** @hide */
-    public static final int FRONTEND_EVENT_TYPE_LOST_LOCK = Constants.FrontendEventType.LOST_LOCK;
-
-
-    /** @hide */
-    @IntDef({DATA_FORMAT_TS, DATA_FORMAT_PES, DATA_FORMAT_ES, DATA_FORMAT_SHV_TLV})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface DataFormat {}
-    /** @hide */
-    public static final int DATA_FORMAT_TS = Constants.DataFormat.TS;
-    /** @hide */
-    public static final int DATA_FORMAT_PES = Constants.DataFormat.PES;
-    /** @hide */
-    public static final int DATA_FORMAT_ES = Constants.DataFormat.ES;
-    /** @hide */
-    public static final int DATA_FORMAT_SHV_TLV = Constants.DataFormat.SHV_TLV;
-
-
-    /** @hide */
-    @IntDef({DEMUX_T_PID, DEMUX_MMPT_PID})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface DemuxPidType {}
-    /** @hide */
-    public static final int DEMUX_T_PID = 1;
-    /** @hide */
-    public static final int DEMUX_MMPT_PID = 2;
-
-    /** @hide */
-    @IntDef({FRONTEND_SETTINGS_ANALOG, FRONTEND_SETTINGS_ATSC, FRONTEND_SETTINGS_ATSC3,
-            FRONTEND_SETTINGS_DVBS, FRONTEND_SETTINGS_DVBC, FRONTEND_SETTINGS_DVBT,
-            FRONTEND_SETTINGS_ISDBS, FRONTEND_SETTINGS_ISDBS3, FRONTEND_SETTINGS_ISDBT})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendSettingsType {}
-    /** @hide */
-    public static final int FRONTEND_SETTINGS_ANALOG = 1;
-    /** @hide */
-    public static final int FRONTEND_SETTINGS_ATSC = 2;
-    /** @hide */
-    public static final int FRONTEND_SETTINGS_ATSC3 = 3;
-    /** @hide */
-    public static final int FRONTEND_SETTINGS_DVBS = 4;
-    /** @hide */
-    public static final int FRONTEND_SETTINGS_DVBC = 5;
-    /** @hide */
-    public static final int FRONTEND_SETTINGS_DVBT = 6;
-    /** @hide */
-    public static final int FRONTEND_SETTINGS_ISDBS = 7;
-    /** @hide */
-    public static final int FRONTEND_SETTINGS_ISDBS3 = 8;
-    /** @hide */
-    public static final int FRONTEND_SETTINGS_ISDBT = 9;
-
-    /** @hide */
-    @IntDef({FILTER_TYPE_TS, FILTER_TYPE_MMTP, FILTER_TYPE_IP, FILTER_TYPE_TLV, FILTER_TYPE_ALP})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FilterType {}
-    /** @hide */
-    public static final int FILTER_TYPE_TS = Constants.DemuxFilterMainType.TS;
-    /** @hide */
-    public static final int FILTER_TYPE_MMTP = Constants.DemuxFilterMainType.MMTP;
-    /** @hide */
-    public static final int FILTER_TYPE_IP = Constants.DemuxFilterMainType.IP;
-    /** @hide */
-    public static final int FILTER_TYPE_TLV = Constants.DemuxFilterMainType.TLV;
-    /** @hide */
-    public static final int FILTER_TYPE_ALP = Constants.DemuxFilterMainType.ALP;
-
-    /** @hide */
-    @IntDef({FILTER_SUBTYPE_UNDEFINED, FILTER_SUBTYPE_SECTION, FILTER_SUBTYPE_PES,
-            FILTER_SUBTYPE_AUDIO, FILTER_SUBTYPE_VIDEO, FILTER_SUBTYPE_DOWNLOAD,
-            FILTER_SUBTYPE_RECORD, FILTER_SUBTYPE_TS, FILTER_SUBTYPE_PCR, FILTER_SUBTYPE_TEMI,
-            FILTER_SUBTYPE_MMPT, FILTER_SUBTYPE_NTP, FILTER_SUBTYPE_IP_PAYLOAD, FILTER_SUBTYPE_IP,
-            FILTER_SUBTYPE_PAYLOAD_THROUGH, FILTER_SUBTYPE_TLV, FILTER_SUBTYPE_PTP, })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FilterSubtype {}
-    /** @hide */
-    public static final int FILTER_SUBTYPE_UNDEFINED = 0;
-    /** @hide */
-    public static final int FILTER_SUBTYPE_SECTION = 1;
-    /** @hide */
-    public static final int FILTER_SUBTYPE_PES = 2;
-    /** @hide */
-    public static final int FILTER_SUBTYPE_AUDIO = 3;
-    /** @hide */
-    public static final int FILTER_SUBTYPE_VIDEO = 4;
-    /** @hide */
-    public static final int FILTER_SUBTYPE_DOWNLOAD = 5;
-    /** @hide */
-    public static final int FILTER_SUBTYPE_RECORD = 6;
-    /** @hide */
-    public static final int FILTER_SUBTYPE_TS = 7;
-    /** @hide */
-    public static final int FILTER_SUBTYPE_PCR = 8;
-    /** @hide */
-    public static final int FILTER_SUBTYPE_TEMI = 9;
-    /** @hide */
-    public static final int FILTER_SUBTYPE_MMPT = 10;
-    /** @hide */
-    public static final int FILTER_SUBTYPE_NTP = 11;
-    /** @hide */
-    public static final int FILTER_SUBTYPE_IP_PAYLOAD = 12;
-    /** @hide */
-    public static final int FILTER_SUBTYPE_IP = 13;
-    /** @hide */
-    public static final int FILTER_SUBTYPE_PAYLOAD_THROUGH = 14;
-    /** @hide */
-    public static final int FILTER_SUBTYPE_TLV = 15;
-    /** @hide */
-    public static final int FILTER_SUBTYPE_PTP = 16;
-
-    /** @hide */
-    @IntDef({FILTER_STATUS_DATA_READY, FILTER_STATUS_LOW_WATER, FILTER_STATUS_HIGH_WATER,
-            FILTER_STATUS_OVERFLOW})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FilterStatus {}
-
+    public @interface ScanType {}
     /**
-     * The status of a filter that the data in the filter buffer is ready to be read.
+     * Scan type undefined.
      */
-    public static final int FILTER_STATUS_DATA_READY = Constants.DemuxFilterStatus.DATA_READY;
+    public static final int SCAN_TYPE_UNDEFINED = Constants.FrontendScanType.SCAN_UNDEFINED;
     /**
-     * The status of a filter that the amount of available data in the filter buffer is at low
-     * level.
+     * Scan type auto.
      *
-     * The value is set to 25 percent of the buffer size by default. It can be changed when
-     * configuring the filter.
+     * <p> Tuner will send {@link android.media.tv.tuner.frontend.ScanCallback#onLocked}
      */
-    public static final int FILTER_STATUS_LOW_WATER = Constants.DemuxFilterStatus.LOW_WATER;
+    public static final int SCAN_TYPE_AUTO = Constants.FrontendScanType.SCAN_AUTO;
     /**
-     * The status of a filter that the amount of available data in the filter buffer is at high
-     * level.
-     * The value is set to 75 percent of the buffer size by default. It can be changed when
-     * configuring the filter.
+     * Blind scan.
+     *
+     * <p>Frequency range is not specified. The {@link android.media.tv.tuner.Tuner} will scan an
+     * implementation specific range.
      */
-    public static final int FILTER_STATUS_HIGH_WATER = Constants.DemuxFilterStatus.HIGH_WATER;
-    /**
-     * The status of a filter that the filter buffer is full and newly filtered data is being
-     * discarded.
-     */
-    public static final int FILTER_STATUS_OVERFLOW = Constants.DemuxFilterStatus.OVERFLOW;
-
-    /** @hide */
-    @IntDef({FRONTEND_SCAN_UNDEFINED, FRONTEND_SCAN_AUTO, FRONTEND_SCAN_BLIND})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendScanType {}
-    /** @hide */
-    public static final int FRONTEND_SCAN_UNDEFINED = Constants.FrontendScanType.SCAN_UNDEFINED;
-    /** @hide */
-    public static final int FRONTEND_SCAN_AUTO = Constants.FrontendScanType.SCAN_AUTO;
-    /** @hide */
-    public static final int FRONTEND_SCAN_BLIND = Constants.FrontendScanType.SCAN_BLIND;
-
-    /** @hide */
-    @IntDef({SCAN_MESSAGE_TYPE_LOCKED, SCAN_MESSAGE_TYPE_END, SCAN_MESSAGE_TYPE_PROGRESS_PERCENT,
-            SCAN_MESSAGE_TYPE_FREQUENCY, SCAN_MESSAGE_TYPE_SYMBOL_RATE, SCAN_MESSAGE_TYPE_PLP_IDS,
-            SCAN_MESSAGE_TYPE_GROUP_IDS, SCAN_MESSAGE_TYPE_INPUT_STREAM_IDS,
-            SCAN_MESSAGE_TYPE_STANDARD, SCAN_MESSAGE_TYPE_ATSC3_PLP_INFO})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ScanMessageType {}
-    /** @hide */
-    public static final int SCAN_MESSAGE_TYPE_LOCKED = Constants.FrontendScanMessageType.LOCKED;
-    /** @hide */
-    public static final int SCAN_MESSAGE_TYPE_END = Constants.FrontendScanMessageType.END;
-    /** @hide */
-    public static final int SCAN_MESSAGE_TYPE_PROGRESS_PERCENT =
-            Constants.FrontendScanMessageType.PROGRESS_PERCENT;
-    /** @hide */
-    public static final int SCAN_MESSAGE_TYPE_FREQUENCY =
-            Constants.FrontendScanMessageType.FREQUENCY;
-    /** @hide */
-    public static final int SCAN_MESSAGE_TYPE_SYMBOL_RATE =
-            Constants.FrontendScanMessageType.SYMBOL_RATE;
-    /** @hide */
-    public static final int SCAN_MESSAGE_TYPE_PLP_IDS = Constants.FrontendScanMessageType.PLP_IDS;
-    /** @hide */
-    public static final int SCAN_MESSAGE_TYPE_GROUP_IDS =
-            Constants.FrontendScanMessageType.GROUP_IDS;
-    /** @hide */
-    public static final int SCAN_MESSAGE_TYPE_INPUT_STREAM_IDS =
-            Constants.FrontendScanMessageType.INPUT_STREAM_IDS;
-    /** @hide */
-    public static final int SCAN_MESSAGE_TYPE_STANDARD =
-            Constants.FrontendScanMessageType.STANDARD;
-    /** @hide */
-    public static final int SCAN_MESSAGE_TYPE_ATSC3_PLP_INFO =
-            Constants.FrontendScanMessageType.ATSC3_PLP_INFO;
-
-
-    /** @hide */
-    @IntDef({FRONTEND_STATUS_TYPE_DEMOD_LOCK, FRONTEND_STATUS_TYPE_SNR, FRONTEND_STATUS_TYPE_BER,
-            FRONTEND_STATUS_TYPE_PER, FRONTEND_STATUS_TYPE_PRE_BER,
-            FRONTEND_STATUS_TYPE_SIGNAL_QUALITY, FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH,
-            FRONTEND_STATUS_TYPE_SYMBOL_RATE, FRONTEND_STATUS_TYPE_FEC,
-            FRONTEND_STATUS_TYPE_MODULATION, FRONTEND_STATUS_TYPE_SPECTRAL,
-            FRONTEND_STATUS_TYPE_LNB_VOLTAGE, FRONTEND_STATUS_TYPE_PLP_ID,
-            FRONTEND_STATUS_TYPE_EWBS, FRONTEND_STATUS_TYPE_AGC, FRONTEND_STATUS_TYPE_LNA,
-            FRONTEND_STATUS_TYPE_LAYER_ERROR, FRONTEND_STATUS_TYPE_VBER_CN,
-            FRONTEND_STATUS_TYPE_LBER_CN, FRONTEND_STATUS_TYPE_XER_CN, FRONTEND_STATUS_TYPE_MER,
-            FRONTEND_STATUS_TYPE_FREQ_OFFSET, FRONTEND_STATUS_TYPE_HIERARCHY,
-            FRONTEND_STATUS_TYPE_RF_LOCK, FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendStatusType {}
-
-    /**
-     * Lock status for Demod.
-     * @hide
-     */
-    public static final int FRONTEND_STATUS_TYPE_DEMOD_LOCK =
-            Constants.FrontendStatusType.DEMOD_LOCK;
-    /**
-     * Signal to Noise Ratio.
-     * @hide
-     */
-    public static final int FRONTEND_STATUS_TYPE_SNR = Constants.FrontendStatusType.SNR;
-    /**
-     * Bit Error Ratio.
-     * @hide
-     */
-    public static final int FRONTEND_STATUS_TYPE_BER = Constants.FrontendStatusType.BER;
-    /**
-     * Packages Error Ratio.
-     * @hide
-     */
-    public static final int FRONTEND_STATUS_TYPE_PER = Constants.FrontendStatusType.PER;
-    /**
-     * Bit Error Ratio before FEC.
-     * @hide
-     */
-    public static final int FRONTEND_STATUS_TYPE_PRE_BER = Constants.FrontendStatusType.PRE_BER;
-    /**
-     * Signal Quality (0..100). Good data over total data in percent can be
-     * used as a way to present Signal Quality.
-     * @hide
-     */
-    public static final int FRONTEND_STATUS_TYPE_SIGNAL_QUALITY =
-            Constants.FrontendStatusType.SIGNAL_QUALITY;
-    /**
-     * Signal Strength.
-     * @hide
-     */
-    public static final int FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH =
-            Constants.FrontendStatusType.SIGNAL_STRENGTH;
-    /**
-     * Symbol Rate.
-     * @hide
-     */
-    public static final int FRONTEND_STATUS_TYPE_SYMBOL_RATE =
-            Constants.FrontendStatusType.SYMBOL_RATE;
-    /**
-     * Forward Error Correction Type.
-     * @hide
-     */
-    public static final int FRONTEND_STATUS_TYPE_FEC = Constants.FrontendStatusType.FEC;
-    /**
-     * Modulation Type.
-     * @hide
-     */
-    public static final int FRONTEND_STATUS_TYPE_MODULATION =
-            Constants.FrontendStatusType.MODULATION;
-    /**
-     * Spectral Inversion Type.
-     * @hide
-     */
-    public static final int FRONTEND_STATUS_TYPE_SPECTRAL = Constants.FrontendStatusType.SPECTRAL;
-    /**
-     * LNB Voltage.
-     * @hide
-     */
-    public static final int FRONTEND_STATUS_TYPE_LNB_VOLTAGE =
-            Constants.FrontendStatusType.LNB_VOLTAGE;
-    /**
-     * Physical Layer Pipe ID.
-     * @hide
-     */
-    public static final int FRONTEND_STATUS_TYPE_PLP_ID = Constants.FrontendStatusType.PLP_ID;
-    /**
-     * Status for Emergency Warning Broadcasting System.
-     * @hide
-     */
-    public static final int FRONTEND_STATUS_TYPE_EWBS = Constants.FrontendStatusType.EWBS;
-    /**
-     * Automatic Gain Control.
-     * @hide
-     */
-    public static final int FRONTEND_STATUS_TYPE_AGC = Constants.FrontendStatusType.AGC;
-    /**
-     * Low Noise Amplifier.
-     * @hide
-     */
-    public static final int FRONTEND_STATUS_TYPE_LNA = Constants.FrontendStatusType.LNA;
-    /**
-     * Error status by layer.
-     * @hide
-     */
-    public static final int FRONTEND_STATUS_TYPE_LAYER_ERROR =
-            Constants.FrontendStatusType.LAYER_ERROR;
-    /**
-     * CN value by VBER.
-     * @hide
-     */
-    public static final int FRONTEND_STATUS_TYPE_VBER_CN = Constants.FrontendStatusType.VBER_CN;
-    /**
-     * CN value by LBER.
-     * @hide
-     */
-    public static final int FRONTEND_STATUS_TYPE_LBER_CN = Constants.FrontendStatusType.LBER_CN;
-    /**
-     * CN value by XER.
-     * @hide
-     */
-    public static final int FRONTEND_STATUS_TYPE_XER_CN = Constants.FrontendStatusType.XER_CN;
-    /**
-     * Moduration Error Ratio.
-     * @hide
-     */
-    public static final int FRONTEND_STATUS_TYPE_MER = Constants.FrontendStatusType.MER;
-    /**
-     * Difference between tuning frequency and actual locked frequency.
-     * @hide
-     */
-    public static final int FRONTEND_STATUS_TYPE_FREQ_OFFSET =
-            Constants.FrontendStatusType.FREQ_OFFSET;
-    /**
-     * Hierarchy for DVBT.
-     * @hide
-     */
-    public static final int FRONTEND_STATUS_TYPE_HIERARCHY = Constants.FrontendStatusType.HIERARCHY;
-    /**
-     * Lock status for RF.
-     * @hide
-     */
-    public static final int FRONTEND_STATUS_TYPE_RF_LOCK = Constants.FrontendStatusType.RF_LOCK;
-    /**
-     * PLP information in a frequency band for ATSC3.0 frontend.
-     * @hide
-     */
-    public static final int FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO =
-            Constants.FrontendStatusType.ATSC3_PLP_INFO;
-
-    /** @hide */
-    @LongDef({FEC_UNDEFINED, FEC_AUTO, FEC_1_2, FEC_1_3, FEC_1_4, FEC_1_5, FEC_2_3, FEC_2_5,
-            FEC_2_9, FEC_3_4, FEC_3_5, FEC_4_5, FEC_4_15, FEC_5_6, FEC_5_9, FEC_6_7, FEC_7_8,
-            FEC_7_9, FEC_7_15, FEC_8_9, FEC_8_15, FEC_9_10, FEC_9_20, FEC_11_15, FEC_11_20,
-            FEC_11_45, FEC_13_18, FEC_13_45, FEC_14_45, FEC_23_36, FEC_25_36, FEC_26_45, FEC_28_45,
-            FEC_29_45, FEC_31_45, FEC_32_45, FEC_77_90})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendInnerFec {}
-
-    /**
-     * FEC not defined
-     * @hide
-     */
-    public static final long FEC_UNDEFINED = Constants.FrontendInnerFec.FEC_UNDEFINED;
-    /**
-     * hardware is able to detect and set FEC automatically
-     * @hide
-     */
-    public static final long FEC_AUTO = Constants.FrontendInnerFec.AUTO;
-    /**
-     * 1/2 conv. code rate
-     * @hide
-     */
-    public static final long FEC_1_2 = Constants.FrontendInnerFec.FEC_1_2;
-    /**
-     * 1/3 conv. code rate
-     * @hide
-     */
-    public static final long FEC_1_3 = Constants.FrontendInnerFec.FEC_1_3;
-    /**
-     * 1/4 conv. code rate
-     * @hide
-     */
-    public static final long FEC_1_4 = Constants.FrontendInnerFec.FEC_1_4;
-    /**
-     * 1/5 conv. code rate
-     * @hide
-     */
-    public static final long FEC_1_5 = Constants.FrontendInnerFec.FEC_1_5;
-    /**
-     * 2/3 conv. code rate
-     * @hide
-     */
-    public static final long FEC_2_3 = Constants.FrontendInnerFec.FEC_2_3;
-    /**
-     * 2/5 conv. code rate
-     * @hide
-     */
-    public static final long FEC_2_5 = Constants.FrontendInnerFec.FEC_2_5;
-    /**
-     * 2/9 conv. code rate
-     * @hide
-     */
-    public static final long FEC_2_9 = Constants.FrontendInnerFec.FEC_2_9;
-    /**
-     * 3/4 conv. code rate
-     * @hide
-     */
-    public static final long FEC_3_4 = Constants.FrontendInnerFec.FEC_3_4;
-    /**
-     * 3/5 conv. code rate
-     * @hide
-     */
-    public static final long FEC_3_5 = Constants.FrontendInnerFec.FEC_3_5;
-    /**
-     * 4/5 conv. code rate
-     * @hide
-     */
-    public static final long FEC_4_5 = Constants.FrontendInnerFec.FEC_4_5;
-    /**
-     * 4/15 conv. code rate
-     * @hide
-     */
-    public static final long FEC_4_15 = Constants.FrontendInnerFec.FEC_4_15;
-    /**
-     * 5/6 conv. code rate
-     * @hide
-     */
-    public static final long FEC_5_6 = Constants.FrontendInnerFec.FEC_5_6;
-    /**
-     * 5/9 conv. code rate
-     * @hide
-     */
-    public static final long FEC_5_9 = Constants.FrontendInnerFec.FEC_5_9;
-    /**
-     * 6/7 conv. code rate
-     * @hide
-     */
-    public static final long FEC_6_7 = Constants.FrontendInnerFec.FEC_6_7;
-    /**
-     * 7/8 conv. code rate
-     * @hide
-     */
-    public static final long FEC_7_8 = Constants.FrontendInnerFec.FEC_7_8;
-    /**
-     * 7/9 conv. code rate
-     * @hide
-     */
-    public static final long FEC_7_9 = Constants.FrontendInnerFec.FEC_7_9;
-    /**
-     * 7/15 conv. code rate
-     * @hide
-     */
-    public static final long FEC_7_15 = Constants.FrontendInnerFec.FEC_7_15;
-    /**
-     * 8/9 conv. code rate
-     * @hide
-     */
-    public static final long FEC_8_9 = Constants.FrontendInnerFec.FEC_8_9;
-    /**
-     * 8/15 conv. code rate
-     * @hide
-     */
-    public static final long FEC_8_15 = Constants.FrontendInnerFec.FEC_8_15;
-    /**
-     * 9/10 conv. code rate
-     * @hide
-     */
-    public static final long FEC_9_10 = Constants.FrontendInnerFec.FEC_9_10;
-    /**
-     * 9/20 conv. code rate
-     * @hide
-     */
-    public static final long FEC_9_20 = Constants.FrontendInnerFec.FEC_9_20;
-    /**
-     * 11/15 conv. code rate
-     * @hide
-     */
-    public static final long FEC_11_15 = Constants.FrontendInnerFec.FEC_11_15;
-    /**
-     * 11/20 conv. code rate
-     * @hide
-     */
-    public static final long FEC_11_20 = Constants.FrontendInnerFec.FEC_11_20;
-    /**
-     * 11/45 conv. code rate
-     * @hide
-     */
-    public static final long FEC_11_45 = Constants.FrontendInnerFec.FEC_11_45;
-    /**
-     * 13/18 conv. code rate
-     * @hide
-     */
-    public static final long FEC_13_18 = Constants.FrontendInnerFec.FEC_13_18;
-    /**
-     * 13/45 conv. code rate
-     * @hide
-     */
-    public static final long FEC_13_45 = Constants.FrontendInnerFec.FEC_13_45;
-    /**
-     * 14/45 conv. code rate
-     * @hide
-     */
-    public static final long FEC_14_45 = Constants.FrontendInnerFec.FEC_14_45;
-    /**
-     * 23/36 conv. code rate
-     * @hide
-     */
-    public static final long FEC_23_36 = Constants.FrontendInnerFec.FEC_23_36;
-    /**
-     * 25/36 conv. code rate
-     * @hide
-     */
-    public static final long FEC_25_36 = Constants.FrontendInnerFec.FEC_25_36;
-    /**
-     * 26/45 conv. code rate
-     * @hide
-     */
-    public static final long FEC_26_45 = Constants.FrontendInnerFec.FEC_26_45;
-    /**
-     * 28/45 conv. code rate
-     * @hide
-     */
-    public static final long FEC_28_45 = Constants.FrontendInnerFec.FEC_28_45;
-    /**
-     * 29/45 conv. code rate
-     * @hide
-     */
-    public static final long FEC_29_45 = Constants.FrontendInnerFec.FEC_29_45;
-    /**
-     * 31/45 conv. code rate
-     * @hide
-     */
-    public static final long FEC_31_45 = Constants.FrontendInnerFec.FEC_31_45;
-    /**
-     * 32/45 conv. code rate
-     * @hide
-     */
-    public static final long FEC_32_45 = Constants.FrontendInnerFec.FEC_32_45;
-    /**
-     * 77/90 conv. code rate
-     * @hide
-     */
-    public static final long FEC_77_90 = Constants.FrontendInnerFec.FEC_77_90;
-
-
-    /** @hide */
-    @IntDef({DVBC_MODULATION_UNDEFINED, DVBC_MODULATION_AUTO, DVBC_MODULATION_MOD_16QAM,
-            DVBC_MODULATION_MOD_32QAM, DVBC_MODULATION_MOD_64QAM, DVBC_MODULATION_MOD_128QAM,
-            DVBC_MODULATION_MOD_256QAM, DVBS_MODULATION_UNDEFINED, DVBS_MODULATION_AUTO,
-            DVBS_MODULATION_MOD_QPSK, DVBS_MODULATION_MOD_8PSK, DVBS_MODULATION_MOD_16QAM,
-            DVBS_MODULATION_MOD_16PSK, DVBS_MODULATION_MOD_32PSK, DVBS_MODULATION_MOD_ACM,
-            DVBS_MODULATION_MOD_8APSK, DVBS_MODULATION_MOD_16APSK, DVBS_MODULATION_MOD_32APSK,
-            DVBS_MODULATION_MOD_64APSK, DVBS_MODULATION_MOD_128APSK, DVBS_MODULATION_MOD_256APSK,
-            DVBS_MODULATION_MOD_RESERVED, ISDBS_MODULATION_UNDEFINED, ISDBS_MODULATION_AUTO,
-            ISDBS_MODULATION_MOD_BPSK, ISDBS_MODULATION_MOD_QPSK, ISDBS_MODULATION_MOD_TC8PSK,
-            ISDBS3_MODULATION_UNDEFINED, ISDBS3_MODULATION_AUTO, ISDBS3_MODULATION_MOD_BPSK,
-            ISDBS3_MODULATION_MOD_QPSK, ISDBS3_MODULATION_MOD_8PSK, ISDBS3_MODULATION_MOD_16APSK,
-            ISDBS3_MODULATION_MOD_32APSK, ISDBT_MODULATION_UNDEFINED, ISDBT_MODULATION_AUTO,
-            ISDBT_MODULATION_MOD_DQPSK, ISDBT_MODULATION_MOD_QPSK, ISDBT_MODULATION_MOD_16QAM,
-            ISDBT_MODULATION_MOD_64QAM})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendModulation {}
-    /** @hide */
-    public static final int DVBC_MODULATION_UNDEFINED = Constants.FrontendDvbcModulation.UNDEFINED;
-    /** @hide */
-    public static final int DVBC_MODULATION_AUTO = Constants.FrontendDvbcModulation.AUTO;
-    /** @hide */
-    public static final int DVBC_MODULATION_MOD_16QAM = Constants.FrontendDvbcModulation.MOD_16QAM;
-    /** @hide */
-    public static final int DVBC_MODULATION_MOD_32QAM = Constants.FrontendDvbcModulation.MOD_32QAM;
-    /** @hide */
-    public static final int DVBC_MODULATION_MOD_64QAM = Constants.FrontendDvbcModulation.MOD_64QAM;
-    /** @hide */
-    public static final int DVBC_MODULATION_MOD_128QAM =
-            Constants.FrontendDvbcModulation.MOD_128QAM;
-    /** @hide */
-    public static final int DVBC_MODULATION_MOD_256QAM =
-            Constants.FrontendDvbcModulation.MOD_256QAM;
-    /** @hide */
-    public static final int DVBS_MODULATION_UNDEFINED = Constants.FrontendDvbsModulation.UNDEFINED;
-    /** @hide */
-    public static final int DVBS_MODULATION_AUTO = Constants.FrontendDvbsModulation.AUTO;
-    /** @hide */
-    public static final int DVBS_MODULATION_MOD_QPSK = Constants.FrontendDvbsModulation.MOD_QPSK;
-    /** @hide */
-    public static final int DVBS_MODULATION_MOD_8PSK = Constants.FrontendDvbsModulation.MOD_8PSK;
-    /** @hide */
-    public static final int DVBS_MODULATION_MOD_16QAM = Constants.FrontendDvbsModulation.MOD_16QAM;
-    /** @hide */
-    public static final int DVBS_MODULATION_MOD_16PSK = Constants.FrontendDvbsModulation.MOD_16PSK;
-    /** @hide */
-    public static final int DVBS_MODULATION_MOD_32PSK = Constants.FrontendDvbsModulation.MOD_32PSK;
-    /** @hide */
-    public static final int DVBS_MODULATION_MOD_ACM = Constants.FrontendDvbsModulation.MOD_ACM;
-    /** @hide */
-    public static final int DVBS_MODULATION_MOD_8APSK = Constants.FrontendDvbsModulation.MOD_8APSK;
-    /** @hide */
-    public static final int DVBS_MODULATION_MOD_16APSK =
-            Constants.FrontendDvbsModulation.MOD_16APSK;
-    /** @hide */
-    public static final int DVBS_MODULATION_MOD_32APSK =
-            Constants.FrontendDvbsModulation.MOD_32APSK;
-    /** @hide */
-    public static final int DVBS_MODULATION_MOD_64APSK =
-            Constants.FrontendDvbsModulation.MOD_64APSK;
-    /** @hide */
-    public static final int DVBS_MODULATION_MOD_128APSK =
-            Constants.FrontendDvbsModulation.MOD_128APSK;
-    /** @hide */
-    public static final int DVBS_MODULATION_MOD_256APSK =
-            Constants.FrontendDvbsModulation.MOD_256APSK;
-    /** @hide */
-    public static final int DVBS_MODULATION_MOD_RESERVED =
-            Constants.FrontendDvbsModulation.MOD_RESERVED;
-    /** @hide */
-    public static final int ISDBS_MODULATION_UNDEFINED =
-            Constants.FrontendIsdbsModulation.UNDEFINED;
-    /** @hide */
-    public static final int ISDBS_MODULATION_AUTO = Constants.FrontendIsdbsModulation.AUTO;
-    /** @hide */
-    public static final int ISDBS_MODULATION_MOD_BPSK = Constants.FrontendIsdbsModulation.MOD_BPSK;
-    /** @hide */
-    public static final int ISDBS_MODULATION_MOD_QPSK = Constants.FrontendIsdbsModulation.MOD_QPSK;
-    /** @hide */
-    public static final int ISDBS_MODULATION_MOD_TC8PSK =
-            Constants.FrontendIsdbsModulation.MOD_TC8PSK;
-    /** @hide */
-    public static final int ISDBS3_MODULATION_UNDEFINED =
-            Constants.FrontendIsdbs3Modulation.UNDEFINED;
-    /** @hide */
-    public static final int ISDBS3_MODULATION_AUTO = Constants.FrontendIsdbs3Modulation.AUTO;
-    /** @hide */
-    public static final int ISDBS3_MODULATION_MOD_BPSK =
-            Constants.FrontendIsdbs3Modulation.MOD_BPSK;
-    /** @hide */
-    public static final int ISDBS3_MODULATION_MOD_QPSK =
-            Constants.FrontendIsdbs3Modulation.MOD_QPSK;
-    /** @hide */
-    public static final int ISDBS3_MODULATION_MOD_8PSK =
-            Constants.FrontendIsdbs3Modulation.MOD_8PSK;
-    /** @hide */
-    public static final int ISDBS3_MODULATION_MOD_16APSK =
-            Constants.FrontendIsdbs3Modulation.MOD_16APSK;
-    /** @hide */
-    public static final int ISDBS3_MODULATION_MOD_32APSK =
-            Constants.FrontendIsdbs3Modulation.MOD_32APSK;
-    /** @hide */
-    public static final int ISDBT_MODULATION_UNDEFINED =
-            Constants.FrontendIsdbtModulation.UNDEFINED;
-    /** @hide */
-    public static final int ISDBT_MODULATION_AUTO = Constants.FrontendIsdbtModulation.AUTO;
-    /** @hide */
-    public static final int ISDBT_MODULATION_MOD_DQPSK =
-            Constants.FrontendIsdbtModulation.MOD_DQPSK;
-    /** @hide */
-    public static final int ISDBT_MODULATION_MOD_QPSK = Constants.FrontendIsdbtModulation.MOD_QPSK;
-    /** @hide */
-    public static final int ISDBT_MODULATION_MOD_16QAM =
-            Constants.FrontendIsdbtModulation.MOD_16QAM;
-    /** @hide */
-    public static final int ISDBT_MODULATION_MOD_64QAM =
-            Constants.FrontendIsdbtModulation.MOD_64QAM;
-
-
-    /** @hide */
-    @IntDef({SPECTRAL_INVERSION_UNDEFINED, SPECTRAL_INVERSION_NORMAL, SPECTRAL_INVERSION_INVERTED})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendDvbcSpectralInversion {}
-    /** @hide */
-    public static final int SPECTRAL_INVERSION_UNDEFINED =
-            Constants.FrontendDvbcSpectralInversion.UNDEFINED;
-    /** @hide */
-    public static final int SPECTRAL_INVERSION_NORMAL =
-            Constants.FrontendDvbcSpectralInversion.NORMAL;
-    /** @hide */
-    public static final int SPECTRAL_INVERSION_INVERTED =
-            Constants.FrontendDvbcSpectralInversion.INVERTED;
-
-
-    /** @hide */
-    @IntDef({HIERARCHY_UNDEFINED, HIERARCHY_AUTO, HIERARCHY_NON_NATIVE, HIERARCHY_1_NATIVE,
-            HIERARCHY_2_NATIVE, HIERARCHY_4_NATIVE, HIERARCHY_NON_INDEPTH, HIERARCHY_1_INDEPTH,
-            HIERARCHY_2_INDEPTH, HIERARCHY_4_INDEPTH})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendDvbtHierarchy {}
-    /** @hide */
-    public static final int HIERARCHY_UNDEFINED = Constants.FrontendDvbtHierarchy.UNDEFINED;
-    /** @hide */
-    public static final int HIERARCHY_AUTO = Constants.FrontendDvbtHierarchy.AUTO;
-    /** @hide */
-    public static final int HIERARCHY_NON_NATIVE =
-            Constants.FrontendDvbtHierarchy.HIERARCHY_NON_NATIVE;
-    /** @hide */
-    public static final int HIERARCHY_1_NATIVE = Constants.FrontendDvbtHierarchy.HIERARCHY_1_NATIVE;
-    /** @hide */
-    public static final int HIERARCHY_2_NATIVE = Constants.FrontendDvbtHierarchy.HIERARCHY_2_NATIVE;
-    /** @hide */
-    public static final int HIERARCHY_4_NATIVE = Constants.FrontendDvbtHierarchy.HIERARCHY_4_NATIVE;
-    /** @hide */
-    public static final int HIERARCHY_NON_INDEPTH =
-            Constants.FrontendDvbtHierarchy.HIERARCHY_NON_INDEPTH;
-    /** @hide */
-    public static final int HIERARCHY_1_INDEPTH =
-            Constants.FrontendDvbtHierarchy.HIERARCHY_1_INDEPTH;
-    /** @hide */
-    public static final int HIERARCHY_2_INDEPTH =
-            Constants.FrontendDvbtHierarchy.HIERARCHY_2_INDEPTH;
-    /** @hide */
-    public static final int HIERARCHY_4_INDEPTH =
-            Constants.FrontendDvbtHierarchy.HIERARCHY_4_INDEPTH;
-
-    /** @hide */
-    @IntDef({FRONTEND_ANALOG_TYPE_UNDEFINED, FRONTEND_ANALOG_TYPE_PAL, FRONTEND_ANALOG_TYPE_SECAM,
-            FRONTEND_ANALOG_TYPE_NTSC})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendAnalogType {}
-    /** @hide */
-    public static final int FRONTEND_ANALOG_TYPE_UNDEFINED = Constants.FrontendAnalogType.UNDEFINED;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_TYPE_PAL = Constants.FrontendAnalogType.PAL;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_TYPE_SECAM = Constants.FrontendAnalogType.SECAM;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_TYPE_NTSC = Constants.FrontendAnalogType.NTSC;
-
-    /** @hide */
-    @IntDef({FRONTEND_ANALOG_SIF_UNDEFINED, FRONTEND_ANALOG_SIF_BG, FRONTEND_ANALOG_SIF_BG_A2,
-            FRONTEND_ANALOG_SIF_BG_NICAM, FRONTEND_ANALOG_SIF_I, FRONTEND_ANALOG_SIF_DK,
-            FRONTEND_ANALOG_SIF_DK1, FRONTEND_ANALOG_SIF_DK2, FRONTEND_ANALOG_SIF_DK3,
-            FRONTEND_ANALOG_SIF_DK_NICAM, FRONTEND_ANALOG_SIF_L, FRONTEND_ANALOG_SIF_M,
-            FRONTEND_ANALOG_SIF_M_BTSC, FRONTEND_ANALOG_SIF_M_A2, FRONTEND_ANALOG_SIF_M_EIA_J,
-            FRONTEND_ANALOG_SIF_I_NICAM, FRONTEND_ANALOG_SIF_L_NICAM, FRONTEND_ANALOG_SIF_L_PRIME})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendAnalogSifStandard {}
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_UNDEFINED =
-            Constants.FrontendAnalogSifStandard.UNDEFINED;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_BG = Constants.FrontendAnalogSifStandard.BG;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_BG_A2 = Constants.FrontendAnalogSifStandard.BG_A2;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_BG_NICAM =
-            Constants.FrontendAnalogSifStandard.BG_NICAM;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_I = Constants.FrontendAnalogSifStandard.I;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_DK = Constants.FrontendAnalogSifStandard.DK;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_DK1 = Constants.FrontendAnalogSifStandard.DK1;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_DK2 = Constants.FrontendAnalogSifStandard.DK2;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_DK3 = Constants.FrontendAnalogSifStandard.DK3;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_DK_NICAM =
-            Constants.FrontendAnalogSifStandard.DK_NICAM;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_L = Constants.FrontendAnalogSifStandard.L;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_M = Constants.FrontendAnalogSifStandard.M;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_M_BTSC = Constants.FrontendAnalogSifStandard.M_BTSC;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_M_A2 = Constants.FrontendAnalogSifStandard.M_A2;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_M_EIA_J =
-            Constants.FrontendAnalogSifStandard.M_EIA_J;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_I_NICAM =
-            Constants.FrontendAnalogSifStandard.I_NICAM;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_L_NICAM =
-            Constants.FrontendAnalogSifStandard.L_NICAM;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_L_PRIME =
-            Constants.FrontendAnalogSifStandard.L_PRIME;
-
-    /** @hide */
-    @IntDef({FRONTEND_ATSC_MODULATION_UNDEFINED, FRONTEND_ATSC_MODULATION_AUTO,
-            FRONTEND_ATSC_MODULATION_MOD_8VSB, FRONTEND_ATSC_MODULATION_MOD_16VSB})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendAtscModulation {}
-    /** @hide */
-    public static final int FRONTEND_ATSC_MODULATION_UNDEFINED =
-            Constants.FrontendAtscModulation.UNDEFINED;
-    /** @hide */
-    public static final int FRONTEND_ATSC_MODULATION_AUTO = Constants.FrontendAtscModulation.AUTO;
-    /** @hide */
-    public static final int FRONTEND_ATSC_MODULATION_MOD_8VSB =
-            Constants.FrontendAtscModulation.MOD_8VSB;
-    /** @hide */
-    public static final int FRONTEND_ATSC_MODULATION_MOD_16VSB =
-            Constants.FrontendAtscModulation.MOD_16VSB;
-
-    /** @hide */
-    @IntDef({FRONTEND_ATSC3_BANDWIDTH_UNDEFINED, FRONTEND_ATSC3_BANDWIDTH_AUTO,
-            FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_6MHZ, FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_7MHZ,
-            FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_8MHZ})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendAtsc3Bandwidth {}
-    /** @hide */
-    public static final int FRONTEND_ATSC3_BANDWIDTH_UNDEFINED =
-            Constants.FrontendAtsc3Bandwidth.UNDEFINED;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_BANDWIDTH_AUTO = Constants.FrontendAtsc3Bandwidth.AUTO;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_6MHZ =
-            Constants.FrontendAtsc3Bandwidth.BANDWIDTH_6MHZ;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_7MHZ =
-            Constants.FrontendAtsc3Bandwidth.BANDWIDTH_7MHZ;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_8MHZ =
-            Constants.FrontendAtsc3Bandwidth.BANDWIDTH_8MHZ;
-
-    /** @hide */
-    @IntDef({FRONTEND_ATSC3_MODULATION_UNDEFINED, FRONTEND_ATSC3_MODULATION_AUTO,
-            FRONTEND_ATSC3_MODULATION_MOD_QPSK, FRONTEND_ATSC3_MODULATION_MOD_16QAM,
-            FRONTEND_ATSC3_MODULATION_MOD_64QAM, FRONTEND_ATSC3_MODULATION_MOD_256QAM,
-            FRONTEND_ATSC3_MODULATION_MOD_1024QAM, FRONTEND_ATSC3_MODULATION_MOD_4096QAM})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendAtsc3Modulation {}
-    /** @hide */
-    public static final int FRONTEND_ATSC3_MODULATION_UNDEFINED =
-            Constants.FrontendAtsc3Modulation.UNDEFINED;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_MODULATION_AUTO = Constants.FrontendAtsc3Modulation.AUTO;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_MODULATION_MOD_QPSK =
-            Constants.FrontendAtsc3Modulation.MOD_QPSK;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_MODULATION_MOD_16QAM =
-            Constants.FrontendAtsc3Modulation.MOD_16QAM;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_MODULATION_MOD_64QAM =
-            Constants.FrontendAtsc3Modulation.MOD_64QAM;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_MODULATION_MOD_256QAM =
-            Constants.FrontendAtsc3Modulation.MOD_256QAM;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_MODULATION_MOD_1024QAM =
-            Constants.FrontendAtsc3Modulation.MOD_1024QAM;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_MODULATION_MOD_4096QAM =
-            Constants.FrontendAtsc3Modulation.MOD_4096QAM;
-
-    /** @hide */
-    @IntDef({FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_UNDEFINED,
-            FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_AUTO, FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_CTI,
-            FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_HTI})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendAtsc3TimeInterleaveMode {}
-    /** @hide */
-    public static final int FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_UNDEFINED =
-            Constants.FrontendAtsc3TimeInterleaveMode.UNDEFINED;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_AUTO =
-            Constants.FrontendAtsc3TimeInterleaveMode.AUTO;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_CTI =
-            Constants.FrontendAtsc3TimeInterleaveMode.CTI;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_HTI =
-            Constants.FrontendAtsc3TimeInterleaveMode.HTI;
-
-    /** @hide */
-    @IntDef({FRONTEND_ATSC3_CODERATE_UNDEFINED, FRONTEND_ATSC3_CODERATE_AUTO,
-            FRONTEND_ATSC3_CODERATE_2_15, FRONTEND_ATSC3_CODERATE_3_15,
-            FRONTEND_ATSC3_CODERATE_4_15, FRONTEND_ATSC3_CODERATE_5_15,
-            FRONTEND_ATSC3_CODERATE_6_15, FRONTEND_ATSC3_CODERATE_7_15,
-            FRONTEND_ATSC3_CODERATE_8_15, FRONTEND_ATSC3_CODERATE_9_15,
-            FRONTEND_ATSC3_CODERATE_10_15, FRONTEND_ATSC3_CODERATE_11_15,
-            FRONTEND_ATSC3_CODERATE_12_15, FRONTEND_ATSC3_CODERATE_13_15})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendAtsc3CodeRate {}
-    /** @hide */
-    public static final int FRONTEND_ATSC3_CODERATE_UNDEFINED =
-            Constants.FrontendAtsc3CodeRate.UNDEFINED;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_CODERATE_AUTO = Constants.FrontendAtsc3CodeRate.AUTO;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_CODERATE_2_15 =
-            Constants.FrontendAtsc3CodeRate.CODERATE_2_15;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_CODERATE_3_15 =
-            Constants.FrontendAtsc3CodeRate.CODERATE_3_15;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_CODERATE_4_15 =
-            Constants.FrontendAtsc3CodeRate.CODERATE_4_15;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_CODERATE_5_15 =
-            Constants.FrontendAtsc3CodeRate.CODERATE_5_15;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_CODERATE_6_15 =
-            Constants.FrontendAtsc3CodeRate.CODERATE_6_15;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_CODERATE_7_15 =
-            Constants.FrontendAtsc3CodeRate.CODERATE_7_15;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_CODERATE_8_15 =
-            Constants.FrontendAtsc3CodeRate.CODERATE_8_15;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_CODERATE_9_15 =
-            Constants.FrontendAtsc3CodeRate.CODERATE_9_15;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_CODERATE_10_15 =
-            Constants.FrontendAtsc3CodeRate.CODERATE_10_15;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_CODERATE_11_15 =
-            Constants.FrontendAtsc3CodeRate.CODERATE_11_15;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_CODERATE_12_15 =
-            Constants.FrontendAtsc3CodeRate.CODERATE_12_15;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_CODERATE_13_15 =
-            Constants.FrontendAtsc3CodeRate.CODERATE_13_15;
-
-    /** @hide */
-    @IntDef({FRONTEND_ATSC3_FEC_UNDEFINED, FRONTEND_ATSC3_FEC_AUTO, FRONTEND_ATSC3_FEC_BCH_LDPC_16K,
-            FRONTEND_ATSC3_FEC_BCH_LDPC_64K, FRONTEND_ATSC3_FEC_CRC_LDPC_16K,
-            FRONTEND_ATSC3_FEC_CRC_LDPC_64K, FRONTEND_ATSC3_FEC_LDPC_16K,
-            FRONTEND_ATSC3_FEC_LDPC_64K})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendAtsc3Fec {}
-    /** @hide */
-    public static final int FRONTEND_ATSC3_FEC_UNDEFINED = Constants.FrontendAtsc3Fec.UNDEFINED;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_FEC_AUTO = Constants.FrontendAtsc3Fec.AUTO;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_FEC_BCH_LDPC_16K =
-            Constants.FrontendAtsc3Fec.BCH_LDPC_16K;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_FEC_BCH_LDPC_64K =
-            Constants.FrontendAtsc3Fec.BCH_LDPC_64K;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_FEC_CRC_LDPC_16K =
-            Constants.FrontendAtsc3Fec.CRC_LDPC_16K;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_FEC_CRC_LDPC_64K =
-            Constants.FrontendAtsc3Fec.CRC_LDPC_64K;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_FEC_LDPC_16K = Constants.FrontendAtsc3Fec.LDPC_16K;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_FEC_LDPC_64K = Constants.FrontendAtsc3Fec.LDPC_64K;
-
-    /** @hide */
-    @IntDef({FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_UNDEFINED,
-            FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_ATSC3_LINKLAYER_PACKET,
-            FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_BASEBAND_PACKET})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendAtsc3DemodOutputFormat {}
-    /** @hide */
-    public static final int FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_UNDEFINED =
-            Constants.FrontendAtsc3DemodOutputFormat.UNDEFINED;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_ATSC3_LINKLAYER_PACKET =
-            Constants.FrontendAtsc3DemodOutputFormat.ATSC3_LINKLAYER_PACKET;
-    /** @hide */
-    public static final int FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_BASEBAND_PACKET =
-            Constants.FrontendAtsc3DemodOutputFormat.BASEBAND_PACKET;
-
-    /** @hide */
-    @IntDef({FRONTEND_DVBS_STANDARD_AUTO, FRONTEND_DVBS_STANDARD_S, FRONTEND_DVBS_STANDARD_S2,
-            FRONTEND_DVBS_STANDARD_S2X})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendDvbsStandard {}
-    /** @hide */
-    public static final int FRONTEND_DVBS_STANDARD_AUTO = Constants.FrontendDvbsStandard.AUTO;
-    /** @hide */
-    public static final int FRONTEND_DVBS_STANDARD_S = Constants.FrontendDvbsStandard.S;
-    /** @hide */
-    public static final int FRONTEND_DVBS_STANDARD_S2 = Constants.FrontendDvbsStandard.S2;
-    /** @hide */
-    public static final int FRONTEND_DVBS_STANDARD_S2X = Constants.FrontendDvbsStandard.S2X;
-
-    /** @hide */
-    @IntDef({FRONTEND_DVBC_ANNEX_UNDEFINED, FRONTEND_DVBC_ANNEX_A, FRONTEND_DVBC_ANNEX_B,
-            FRONTEND_DVBC_ANNEX_C})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendDvbcAnnex {}
-    /** @hide */
-    public static final int FRONTEND_DVBC_ANNEX_UNDEFINED = Constants.FrontendDvbcAnnex.UNDEFINED;
-    /** @hide */
-    public static final int FRONTEND_DVBC_ANNEX_A = Constants.FrontendDvbcAnnex.A;
-    /** @hide */
-    public static final int FRONTEND_DVBC_ANNEX_B = Constants.FrontendDvbcAnnex.B;
-    /** @hide */
-    public static final int FRONTEND_DVBC_ANNEX_C = Constants.FrontendDvbcAnnex.C;
-
-    /** @hide */
-    @IntDef({FRONTEND_DVBT_TRANSMISSION_MODE_UNDEFINED, FRONTEND_DVBT_TRANSMISSION_MODE_AUTO,
-            FRONTEND_DVBT_TRANSMISSION_MODE_2K, FRONTEND_DVBT_TRANSMISSION_MODE_8K,
-            FRONTEND_DVBT_TRANSMISSION_MODE_4K, FRONTEND_DVBT_TRANSMISSION_MODE_1K,
-            FRONTEND_DVBT_TRANSMISSION_MODE_16K, FRONTEND_DVBT_TRANSMISSION_MODE_32K})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendDvbtTransmissionMode {}
-    /** @hide */
-    public static final int FRONTEND_DVBT_TRANSMISSION_MODE_UNDEFINED =
-            Constants.FrontendDvbtTransmissionMode.UNDEFINED;
-    /** @hide */
-    public static final int FRONTEND_DVBT_TRANSMISSION_MODE_AUTO =
-            Constants.FrontendDvbtTransmissionMode.AUTO;
-    /** @hide */
-    public static final int FRONTEND_DVBT_TRANSMISSION_MODE_2K =
-            Constants.FrontendDvbtTransmissionMode.MODE_2K;
-    /** @hide */
-    public static final int FRONTEND_DVBT_TRANSMISSION_MODE_8K =
-            Constants.FrontendDvbtTransmissionMode.MODE_8K;
-    /** @hide */
-    public static final int FRONTEND_DVBT_TRANSMISSION_MODE_4K =
-            Constants.FrontendDvbtTransmissionMode.MODE_4K;
-    /** @hide */
-    public static final int FRONTEND_DVBT_TRANSMISSION_MODE_1K =
-            Constants.FrontendDvbtTransmissionMode.MODE_1K;
-    /** @hide */
-    public static final int FRONTEND_DVBT_TRANSMISSION_MODE_16K =
-            Constants.FrontendDvbtTransmissionMode.MODE_16K;
-    /** @hide */
-    public static final int FRONTEND_DVBT_TRANSMISSION_MODE_32K =
-            Constants.FrontendDvbtTransmissionMode.MODE_32K;
-
-    /** @hide */
-    @IntDef({FRONTEND_DVBT_BANDWIDTH_UNDEFINED, FRONTEND_DVBT_BANDWIDTH_AUTO,
-            FRONTEND_DVBT_BANDWIDTH_8MHZ, FRONTEND_DVBT_BANDWIDTH_7MHZ,
-            FRONTEND_DVBT_BANDWIDTH_6MHZ, FRONTEND_DVBT_BANDWIDTH_5MHZ,
-            FRONTEND_DVBT_BANDWIDTH_1_7MHZ, FRONTEND_DVBT_BANDWIDTH_10MHZ})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendDvbtBandwidth {}
-    /** @hide */
-    public static final int FRONTEND_DVBT_BANDWIDTH_UNDEFINED =
-            Constants.FrontendDvbtBandwidth.UNDEFINED;
-    /** @hide */
-    public static final int FRONTEND_DVBT_BANDWIDTH_AUTO = Constants.FrontendDvbtBandwidth.AUTO;
-    /** @hide */
-    public static final int FRONTEND_DVBT_BANDWIDTH_8MHZ =
-            Constants.FrontendDvbtBandwidth.BANDWIDTH_8MHZ;
-    /** @hide */
-    public static final int FRONTEND_DVBT_BANDWIDTH_7MHZ =
-            Constants.FrontendDvbtBandwidth.BANDWIDTH_7MHZ;
-    /** @hide */
-    public static final int FRONTEND_DVBT_BANDWIDTH_6MHZ =
-            Constants.FrontendDvbtBandwidth.BANDWIDTH_6MHZ;
-    /** @hide */
-    public static final int FRONTEND_DVBT_BANDWIDTH_5MHZ =
-            Constants.FrontendDvbtBandwidth.BANDWIDTH_5MHZ;
-    /** @hide */
-    public static final int FRONTEND_DVBT_BANDWIDTH_1_7MHZ =
-            Constants.FrontendDvbtBandwidth.BANDWIDTH_1_7MHZ;
-    /** @hide */
-    public static final int FRONTEND_DVBT_BANDWIDTH_10MHZ =
-            Constants.FrontendDvbtBandwidth.BANDWIDTH_10MHZ;
-
-    /** @hide */
-    @IntDef({FRONTEND_DVBT_CONSTELLATION_UNDEFINED, FRONTEND_DVBT_CONSTELLATION_AUTO,
-            FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_QPSK,
-            FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_16QAM,
-            FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_64QAM,
-            FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_256QAM})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendDvbtConstellation {}
-    /** @hide */
-    public static final int FRONTEND_DVBT_CONSTELLATION_UNDEFINED =
-            Constants.FrontendDvbtConstellation.UNDEFINED;
-    /** @hide */
-    public static final int FRONTEND_DVBT_CONSTELLATION_AUTO =
-            Constants.FrontendDvbtConstellation.AUTO;
-    /** @hide */
-    public static final int FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_QPSK =
-            Constants.FrontendDvbtConstellation.CONSTELLATION_QPSK;
-    /** @hide */
-    public static final int FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_16QAM =
-            Constants.FrontendDvbtConstellation.CONSTELLATION_16QAM;
-    /** @hide */
-    public static final int FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_64QAM =
-            Constants.FrontendDvbtConstellation.CONSTELLATION_64QAM;
-    /** @hide */
-    public static final int FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_256QAM =
-            Constants.FrontendDvbtConstellation.CONSTELLATION_256QAM;
-
-    /** @hide */
-    @IntDef({FRONTEND_DVBT_CODERATE_UNDEFINED, FRONTEND_DVBT_CODERATE_AUTO,
-            FRONTEND_DVBT_CODERATE_1_2, FRONTEND_DVBT_CODERATE_2_3, FRONTEND_DVBT_CODERATE_3_4,
-            FRONTEND_DVBT_CODERATE_5_6, FRONTEND_DVBT_CODERATE_7_8, FRONTEND_DVBT_CODERATE_3_5,
-            FRONTEND_DVBT_CODERATE_4_5, FRONTEND_DVBT_CODERATE_6_7, FRONTEND_DVBT_CODERATE_8_9})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendDvbtCoderate {}
-    /** @hide */
-    public static final int FRONTEND_DVBT_CODERATE_UNDEFINED =
-            Constants.FrontendDvbtCoderate.UNDEFINED;
-    /** @hide */
-    public static final int FRONTEND_DVBT_CODERATE_AUTO = Constants.FrontendDvbtCoderate.AUTO;
-    /** @hide */
-    public static final int FRONTEND_DVBT_CODERATE_1_2 =
-            Constants.FrontendDvbtCoderate.CODERATE_1_2;
-    /** @hide */
-    public static final int FRONTEND_DVBT_CODERATE_2_3 =
-            Constants.FrontendDvbtCoderate.CODERATE_2_3;
-    /** @hide */
-    public static final int FRONTEND_DVBT_CODERATE_3_4 =
-            Constants.FrontendDvbtCoderate.CODERATE_3_4;
-    /** @hide */
-    public static final int FRONTEND_DVBT_CODERATE_5_6 =
-            Constants.FrontendDvbtCoderate.CODERATE_5_6;
-    /** @hide */
-    public static final int FRONTEND_DVBT_CODERATE_7_8 =
-            Constants.FrontendDvbtCoderate.CODERATE_7_8;
-    /** @hide */
-    public static final int FRONTEND_DVBT_CODERATE_3_5 =
-            Constants.FrontendDvbtCoderate.CODERATE_3_5;
-    /** @hide */
-    public static final int FRONTEND_DVBT_CODERATE_4_5 =
-            Constants.FrontendDvbtCoderate.CODERATE_4_5;
-    /** @hide */
-    public static final int FRONTEND_DVBT_CODERATE_6_7 =
-            Constants.FrontendDvbtCoderate.CODERATE_6_7;
-    /** @hide */
-    public static final int FRONTEND_DVBT_CODERATE_8_9 =
-            Constants.FrontendDvbtCoderate.CODERATE_8_9;
-
-    /** @hide */
-    @IntDef({FRONTEND_DVBT_GUARD_INTERVAL_UNDEFINED, FRONTEND_DVBT_GUARD_INTERVAL_AUTO,
-            FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_32, FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_16,
-            FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_8, FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_4,
-            FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_128,
-            FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_19_128,
-            FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_19_256})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendDvbtGuardInterval {}
-    /** @hide */
-    public static final int FRONTEND_DVBT_GUARD_INTERVAL_UNDEFINED =
-            Constants.FrontendDvbtGuardInterval.UNDEFINED;
-    /** @hide */
-    public static final int FRONTEND_DVBT_GUARD_INTERVAL_AUTO =
-            Constants.FrontendDvbtGuardInterval.AUTO;
-    /** @hide */
-    public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_32 =
-            Constants.FrontendDvbtGuardInterval.INTERVAL_1_32;
-    /** @hide */
-    public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_16 =
-            Constants.FrontendDvbtGuardInterval.INTERVAL_1_16;
-    /** @hide */
-    public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_8 =
-            Constants.FrontendDvbtGuardInterval.INTERVAL_1_8;
-    /** @hide */
-    public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_4 =
-            Constants.FrontendDvbtGuardInterval.INTERVAL_1_4;
-    /** @hide */
-    public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_128 =
-            Constants.FrontendDvbtGuardInterval.INTERVAL_1_128;
-    /** @hide */
-    public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_19_128 =
-            Constants.FrontendDvbtGuardInterval.INTERVAL_19_128;
-    /** @hide */
-    public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_19_256 =
-            Constants.FrontendDvbtGuardInterval.INTERVAL_19_256;
-
-    /** @hide */
-    @IntDef({FRONTEND_ISDBS_CODERATE_UNDEFINED, FRONTEND_ISDBS_CODERATE_AUTO,
-            FRONTEND_ISDBS_CODERATE_1_2, FRONTEND_ISDBS_CODERATE_2_3, FRONTEND_ISDBS_CODERATE_3_4,
-            FRONTEND_ISDBS_CODERATE_5_6, FRONTEND_ISDBS_CODERATE_7_8})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendIsdbsCoderate {}
-    /** @hide */
-    public static final int FRONTEND_ISDBS_CODERATE_UNDEFINED =
-            Constants.FrontendIsdbsCoderate.UNDEFINED;
-    /** @hide */
-    public static final int FRONTEND_ISDBS_CODERATE_AUTO = Constants.FrontendIsdbsCoderate.AUTO;
-    /** @hide */
-    public static final int FRONTEND_ISDBS_CODERATE_1_2 =
-            Constants.FrontendIsdbsCoderate.CODERATE_1_2;
-    /** @hide */
-    public static final int FRONTEND_ISDBS_CODERATE_2_3 =
-            Constants.FrontendIsdbsCoderate.CODERATE_2_3;
-    /** @hide */
-    public static final int FRONTEND_ISDBS_CODERATE_3_4 =
-            Constants.FrontendIsdbsCoderate.CODERATE_3_4;
-    /** @hide */
-    public static final int FRONTEND_ISDBS_CODERATE_5_6 =
-            Constants.FrontendIsdbsCoderate.CODERATE_5_6;
-    /** @hide */
-    public static final int FRONTEND_ISDBS_CODERATE_7_8 =
-            Constants.FrontendIsdbsCoderate.CODERATE_7_8;
-
-    /** @hide */
-    @IntDef({FRONTEND_ISDBT_MODE_UNDEFINED, FRONTEND_ISDBT_MODE_AUTO, FRONTEND_ISDBT_MODE_1,
-            FRONTEND_ISDBT_MODE_2, FRONTEND_ISDBT_MODE_3})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendIsdbtMode {}
-    /** @hide */
-    public static final int FRONTEND_ISDBT_MODE_UNDEFINED = Constants.FrontendIsdbtMode.UNDEFINED;
-    /** @hide */
-    public static final int FRONTEND_ISDBT_MODE_AUTO = Constants.FrontendIsdbtMode.AUTO;
-    /** @hide */
-    public static final int FRONTEND_ISDBT_MODE_1 = Constants.FrontendIsdbtMode.MODE_1;
-    /** @hide */
-    public static final int FRONTEND_ISDBT_MODE_2 = Constants.FrontendIsdbtMode.MODE_2;
-    /** @hide */
-    public static final int FRONTEND_ISDBT_MODE_3 = Constants.FrontendIsdbtMode.MODE_3;
-
-    /** @hide */
-    @IntDef({FRONTEND_ISDBT_BANDWIDTH_UNDEFINED, FRONTEND_ISDBT_BANDWIDTH_AUTO,
-            FRONTEND_ISDBT_BANDWIDTH_8MHZ, FRONTEND_ISDBT_BANDWIDTH_7MHZ,
-            FRONTEND_ISDBT_BANDWIDTH_6MHZ})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendIsdbtBandwidth {}
-    /** @hide */
-    public static final int FRONTEND_ISDBT_BANDWIDTH_UNDEFINED =
-            Constants.FrontendIsdbtBandwidth.UNDEFINED;
-    /** @hide */
-    public static final int FRONTEND_ISDBT_BANDWIDTH_AUTO = Constants.FrontendIsdbtBandwidth.AUTO;
-    /** @hide */
-    public static final int FRONTEND_ISDBT_BANDWIDTH_8MHZ =
-            Constants.FrontendIsdbtBandwidth.BANDWIDTH_8MHZ;
-    /** @hide */
-    public static final int FRONTEND_ISDBT_BANDWIDTH_7MHZ =
-            Constants.FrontendIsdbtBandwidth.BANDWIDTH_7MHZ;
-    /** @hide */
-    public static final int FRONTEND_ISDBT_BANDWIDTH_6MHZ =
-            Constants.FrontendIsdbtBandwidth.BANDWIDTH_6MHZ;
-
-    /** @hide */
-    @IntDef({FILTER_SETTINGS_TS, FILTER_SETTINGS_MMTP, FILTER_SETTINGS_IP, FILTER_SETTINGS_TLV,
-            FILTER_SETTINGS_ALP})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FilterSettingsType {}
-    /** @hide */
-    public static final int FILTER_SETTINGS_TS = Constants.DemuxFilterMainType.TS;
-    /** @hide */
-    public static final int FILTER_SETTINGS_MMTP = Constants.DemuxFilterMainType.MMTP;
-    /** @hide */
-    public static final int FILTER_SETTINGS_IP = Constants.DemuxFilterMainType.IP;
-    /** @hide */
-    public static final int FILTER_SETTINGS_TLV = Constants.DemuxFilterMainType.TLV;
-    /** @hide */
-    public static final int FILTER_SETTINGS_ALP = Constants.DemuxFilterMainType.ALP;
-
-    /** @hide */
-    @IntDef({DVR_SETTINGS_RECORD, DVR_SETTINGS_PLAYBACK})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface DvrSettingsType {}
-    /** @hide */
-    public static final int DVR_SETTINGS_RECORD = Constants.DvrType.RECORD;
-    /** @hide */
-    public static final int DVR_SETTINGS_PLAYBACK = Constants.DvrType.PLAYBACK;
-
-
-    /** @hide */
-    @IntDef({LNB_VOLTAGE_NONE, LNB_VOLTAGE_5V, LNB_VOLTAGE_11V, LNB_VOLTAGE_12V, LNB_VOLTAGE_13V,
-            LNB_VOLTAGE_14V, LNB_VOLTAGE_15V, LNB_VOLTAGE_18V, LNB_VOLTAGE_19V})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface LnbVoltage {}
-    /** @hide */
-    public static final int LNB_VOLTAGE_NONE = Constants.LnbVoltage.NONE;
-    /** @hide */
-    public static final int LNB_VOLTAGE_5V = Constants.LnbVoltage.VOLTAGE_5V;
-    /** @hide */
-    public static final int LNB_VOLTAGE_11V = Constants.LnbVoltage.VOLTAGE_11V;
-    /** @hide */
-    public static final int LNB_VOLTAGE_12V = Constants.LnbVoltage.VOLTAGE_12V;
-    /** @hide */
-    public static final int LNB_VOLTAGE_13V = Constants.LnbVoltage.VOLTAGE_13V;
-    /** @hide */
-    public static final int LNB_VOLTAGE_14V = Constants.LnbVoltage.VOLTAGE_14V;
-    /** @hide */
-    public static final int LNB_VOLTAGE_15V = Constants.LnbVoltage.VOLTAGE_15V;
-    /** @hide */
-    public static final int LNB_VOLTAGE_18V = Constants.LnbVoltage.VOLTAGE_18V;
-    /** @hide */
-    public static final int LNB_VOLTAGE_19V = Constants.LnbVoltage.VOLTAGE_19V;
-
-    /** @hide */
-    @IntDef({LNB_TONE_NONE, LNB_TONE_CONTINUOUS})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface LnbTone {}
-    /** @hide */
-    public static final int LNB_TONE_NONE = Constants.LnbTone.NONE;
-    /** @hide */
-    public static final int LNB_TONE_CONTINUOUS = Constants.LnbTone.CONTINUOUS;
-
-    /** @hide */
-    @IntDef({LNB_POSITION_UNDEFINED, LNB_POSITION_A, LNB_POSITION_B})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface LnbPosition {}
-    /** @hide */
-    public static final int LNB_POSITION_UNDEFINED = Constants.LnbPosition.UNDEFINED;
-    /** @hide */
-    public static final int LNB_POSITION_A = Constants.LnbPosition.POSITION_A;
-    /** @hide */
-    public static final int LNB_POSITION_B = Constants.LnbPosition.POSITION_B;
-
+    public static final int SCAN_TYPE_BLIND = Constants.FrontendScanType.SCAN_BLIND;
 
     /** @hide */
     @IntDef({RESULT_SUCCESS, RESULT_UNAVAILABLE, RESULT_NOT_INITIALIZED, RESULT_INVALID_STATE,
             RESULT_INVALID_ARGUMENT, RESULT_OUT_OF_MEMORY, RESULT_UNKNOWN_ERROR})
     @Retention(RetentionPolicy.SOURCE)
     public @interface Result {}
-    /** @hide */
+
+    /**
+     * Operation succeeded.
+     */
     public static final int RESULT_SUCCESS = Constants.Result.SUCCESS;
-    /** @hide */
+    /**
+     * Operation failed because the corresponding resources are not available.
+     */
     public static final int RESULT_UNAVAILABLE = Constants.Result.UNAVAILABLE;
-    /** @hide */
+    /**
+     * Operation failed because the corresponding resources are not initialized.
+     */
     public static final int RESULT_NOT_INITIALIZED = Constants.Result.NOT_INITIALIZED;
-    /** @hide */
+    /**
+     * Operation failed because it's not in a valid state.
+     */
     public static final int RESULT_INVALID_STATE = Constants.Result.INVALID_STATE;
-    /** @hide */
+    /**
+     * Operation failed because there are invalid arguments.
+     */
     public static final int RESULT_INVALID_ARGUMENT = Constants.Result.INVALID_ARGUMENT;
-    /** @hide */
+    /**
+     * Memory allocation failed.
+     */
     public static final int RESULT_OUT_OF_MEMORY = Constants.Result.OUT_OF_MEMORY;
-    /** @hide */
+    /**
+     * Operation failed due to unknown errors.
+     */
     public static final int RESULT_UNKNOWN_ERROR = Constants.Result.UNKNOWN_ERROR;
 
     private TunerConstants() {
diff --git a/media/java/android/media/tv/tuner/TunerUtils.java b/media/java/android/media/tv/tuner/TunerUtils.java
index a7ccb02..30aaa02 100644
--- a/media/java/android/media/tv/tuner/TunerUtils.java
+++ b/media/java/android/media/tv/tuner/TunerUtils.java
@@ -19,8 +19,7 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.hardware.tv.tuner.V1_0.Constants;
-import android.media.tv.tuner.TunerConstants.FilterSubtype;
-import android.media.tv.tuner.TunerConstants.FilterType;
+import android.media.tv.tuner.filter.Filter;
 
 /**
  * Utility class for tuner framework.
@@ -49,91 +48,91 @@
      * @param mainType filter main type.
      * @param subtype filter subtype.
      */
-    public static int getFilterSubtype(@FilterType int mainType, @FilterSubtype int subtype) {
-        if (mainType == TunerConstants.FILTER_TYPE_TS) {
+    public static int getFilterSubtype(@Filter.Type int mainType, @Filter.Subtype int subtype) {
+        if (mainType == Filter.TYPE_TS) {
             switch (subtype) {
-                case TunerConstants.FILTER_SUBTYPE_UNDEFINED:
+                case Filter.SUBTYPE_UNDEFINED:
                     return Constants.DemuxTsFilterType.UNDEFINED;
-                case TunerConstants.FILTER_SUBTYPE_SECTION:
+                case Filter.SUBTYPE_SECTION:
                     return Constants.DemuxTsFilterType.SECTION;
-                case TunerConstants.FILTER_SUBTYPE_PES:
+                case Filter.SUBTYPE_PES:
                     return Constants.DemuxTsFilterType.PES;
-                case TunerConstants.FILTER_SUBTYPE_TS:
+                case Filter.SUBTYPE_TS:
                     return Constants.DemuxTsFilterType.TS;
-                case TunerConstants.FILTER_SUBTYPE_AUDIO:
+                case Filter.SUBTYPE_AUDIO:
                     return Constants.DemuxTsFilterType.AUDIO;
-                case TunerConstants.FILTER_SUBTYPE_VIDEO:
+                case Filter.SUBTYPE_VIDEO:
                     return Constants.DemuxTsFilterType.VIDEO;
-                case TunerConstants.FILTER_SUBTYPE_PCR:
+                case Filter.SUBTYPE_PCR:
                     return Constants.DemuxTsFilterType.PCR;
-                case TunerConstants.FILTER_SUBTYPE_RECORD:
+                case Filter.SUBTYPE_RECORD:
                     return Constants.DemuxTsFilterType.RECORD;
-                case TunerConstants.FILTER_SUBTYPE_TEMI:
+                case Filter.SUBTYPE_TEMI:
                     return Constants.DemuxTsFilterType.TEMI;
                 default:
                     break;
             }
-        } else if (mainType == TunerConstants.FILTER_TYPE_MMTP) {
+        } else if (mainType == Filter.TYPE_MMTP) {
             switch (subtype) {
-                case TunerConstants.FILTER_SUBTYPE_UNDEFINED:
+                case Filter.SUBTYPE_UNDEFINED:
                     return Constants.DemuxMmtpFilterType.UNDEFINED;
-                case TunerConstants.FILTER_SUBTYPE_SECTION:
+                case Filter.SUBTYPE_SECTION:
                     return Constants.DemuxMmtpFilterType.SECTION;
-                case TunerConstants.FILTER_SUBTYPE_PES:
+                case Filter.SUBTYPE_PES:
                     return Constants.DemuxMmtpFilterType.PES;
-                case TunerConstants.FILTER_SUBTYPE_MMPT:
+                case Filter.SUBTYPE_MMTP:
                     return Constants.DemuxMmtpFilterType.MMTP;
-                case TunerConstants.FILTER_SUBTYPE_AUDIO:
+                case Filter.SUBTYPE_AUDIO:
                     return Constants.DemuxMmtpFilterType.AUDIO;
-                case TunerConstants.FILTER_SUBTYPE_VIDEO:
+                case Filter.SUBTYPE_VIDEO:
                     return Constants.DemuxMmtpFilterType.VIDEO;
-                case TunerConstants.FILTER_SUBTYPE_RECORD:
+                case Filter.SUBTYPE_RECORD:
                     return Constants.DemuxMmtpFilterType.RECORD;
-                case TunerConstants.FILTER_SUBTYPE_DOWNLOAD:
+                case Filter.SUBTYPE_DOWNLOAD:
                     return Constants.DemuxMmtpFilterType.DOWNLOAD;
                 default:
                     break;
             }
 
-        } else if (mainType == TunerConstants.FILTER_TYPE_IP) {
+        } else if (mainType == Filter.TYPE_IP) {
             switch (subtype) {
-                case TunerConstants.FILTER_SUBTYPE_UNDEFINED:
+                case Filter.SUBTYPE_UNDEFINED:
                     return Constants.DemuxIpFilterType.UNDEFINED;
-                case TunerConstants.FILTER_SUBTYPE_SECTION:
+                case Filter.SUBTYPE_SECTION:
                     return Constants.DemuxIpFilterType.SECTION;
-                case TunerConstants.FILTER_SUBTYPE_NTP:
+                case Filter.SUBTYPE_NTP:
                     return Constants.DemuxIpFilterType.NTP;
-                case TunerConstants.FILTER_SUBTYPE_IP_PAYLOAD:
+                case Filter.SUBTYPE_IP_PAYLOAD:
                     return Constants.DemuxIpFilterType.IP_PAYLOAD;
-                case TunerConstants.FILTER_SUBTYPE_IP:
+                case Filter.SUBTYPE_IP:
                     return Constants.DemuxIpFilterType.IP;
-                case TunerConstants.FILTER_SUBTYPE_PAYLOAD_THROUGH:
+                case Filter.SUBTYPE_PAYLOAD_THROUGH:
                     return Constants.DemuxIpFilterType.PAYLOAD_THROUGH;
                 default:
                     break;
             }
-        } else if (mainType == TunerConstants.FILTER_TYPE_TLV) {
+        } else if (mainType == Filter.TYPE_TLV) {
             switch (subtype) {
-                case TunerConstants.FILTER_SUBTYPE_UNDEFINED:
+                case Filter.SUBTYPE_UNDEFINED:
                     return Constants.DemuxTlvFilterType.UNDEFINED;
-                case TunerConstants.FILTER_SUBTYPE_SECTION:
+                case Filter.SUBTYPE_SECTION:
                     return Constants.DemuxTlvFilterType.SECTION;
-                case TunerConstants.FILTER_SUBTYPE_TLV:
+                case Filter.SUBTYPE_TLV:
                     return Constants.DemuxTlvFilterType.TLV;
-                case TunerConstants.FILTER_SUBTYPE_PAYLOAD_THROUGH:
+                case Filter.SUBTYPE_PAYLOAD_THROUGH:
                     return Constants.DemuxTlvFilterType.PAYLOAD_THROUGH;
                 default:
                     break;
             }
-        } else if (mainType == TunerConstants.FILTER_TYPE_ALP) {
+        } else if (mainType == Filter.TYPE_ALP) {
             switch (subtype) {
-                case TunerConstants.FILTER_SUBTYPE_UNDEFINED:
+                case Filter.SUBTYPE_UNDEFINED:
                     return Constants.DemuxAlpFilterType.UNDEFINED;
-                case TunerConstants.FILTER_SUBTYPE_SECTION:
+                case Filter.SUBTYPE_SECTION:
                     return Constants.DemuxAlpFilterType.SECTION;
-                case TunerConstants.FILTER_SUBTYPE_PTP:
+                case Filter.SUBTYPE_PTP:
                     return Constants.DemuxAlpFilterType.PTP;
-                case TunerConstants.FILTER_SUBTYPE_PAYLOAD_THROUGH:
+                case Filter.SUBTYPE_PAYLOAD_THROUGH:
                     return Constants.DemuxAlpFilterType.PAYLOAD_THROUGH;
                 default:
                     break;
diff --git a/media/java/android/media/tv/tuner/dvr/Dvr.java b/media/java/android/media/tv/tuner/dvr/Dvr.java
new file mode 100644
index 0000000..4183e3b
--- /dev/null
+++ b/media/java/android/media/tv/tuner/dvr/Dvr.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 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.media.tv.tuner.dvr;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerConstants.Result;
+import android.media.tv.tuner.filter.Filter;
+import android.os.ParcelFileDescriptor;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Digital Video Record (DVR) interface provides record control on Demux's output buffer and
+ * playback control on Demux's input buffer.
+ *
+ * @hide
+ */
+@SystemApi
+public class Dvr implements AutoCloseable {
+
+    /** @hide */
+    @IntDef(prefix = "TYPE_", value = {TYPE_RECORD, TYPE_PLAYBACK})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Type {}
+
+    /**
+     * DVR for recording.
+     */
+    public static final int TYPE_RECORD = Constants.DvrType.RECORD;
+    /**
+     * DVR for playback of recorded programs.
+     */
+    public static final int TYPE_PLAYBACK = Constants.DvrType.PLAYBACK;
+
+
+    final int mType;
+    long mNativeContext;
+
+    private native int nativeAttachFilter(Filter filter);
+    private native int nativeDetachFilter(Filter filter);
+    private native int nativeConfigureDvr(DvrSettings settings);
+    private native int nativeStartDvr();
+    private native int nativeStopDvr();
+    private native int nativeFlushDvr();
+    private native int nativeClose();
+    private native void nativeSetFileDescriptor(int fd);
+
+    protected Dvr(int type) {
+        mType = type;
+    }
+
+    /**
+     * Attaches a filter to DVR interface for recording.
+     *
+     * @param filter the filter to be attached.
+     * @return result status of the operation.
+     */
+    @Result
+    public int attachFilter(@NonNull Filter filter) {
+        return nativeAttachFilter(filter);
+    }
+
+    /**
+     * Detaches a filter from DVR interface.
+     *
+     * @param filter the filter to be detached.
+     * @return result status of the operation.
+     */
+    @Result
+    public int detachFilter(@NonNull Filter filter) {
+        return nativeDetachFilter(filter);
+    }
+
+    /**
+     * Configures the DVR.
+     *
+     * @param settings the settings of the DVR interface.
+     * @return result status of the operation.
+     */
+    @Result
+    public int configure(@NonNull DvrSettings settings) {
+        return nativeConfigureDvr(settings);
+    }
+
+    /**
+     * Starts DVR.
+     *
+     * <p>Starts consuming playback data or producing data for recording.
+     *
+     * @return result status of the operation.
+     */
+    @Result
+    public int start() {
+        return nativeStartDvr();
+    }
+
+    /**
+     * Stops DVR.
+     *
+     * <p>Stops consuming playback data or producing data for recording.
+     *
+     * @return result status of the operation.
+     */
+    @Result
+    public int stop() {
+        return nativeStopDvr();
+    }
+
+    /**
+     * Flushed DVR data.
+     *
+     * <p>The data in DVR buffer is cleared.
+     *
+     * @return result status of the operation.
+     */
+    @Result
+    public int flush() {
+        return nativeFlushDvr();
+    }
+
+    /**
+     * Closes the DVR instance to release resources.
+     */
+    public void close() {
+        nativeClose();
+    }
+
+    /**
+     * Sets file descriptor to read/write data.
+     *
+     * @param fd the file descriptor to read/write data.
+     */
+    public void setFileDescriptor(@NonNull ParcelFileDescriptor fd) {
+        nativeSetFileDescriptor(fd.getFd());
+    }
+
+    @Type
+    int getType() {
+        return mType;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/dvr/DvrPlayback.java b/media/java/android/media/tv/tuner/dvr/DvrPlayback.java
new file mode 100644
index 0000000..eb31574
--- /dev/null
+++ b/media/java/android/media/tv/tuner/dvr/DvrPlayback.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 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.media.tv.tuner.dvr;
+
+import android.annotation.BytesLong;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.hardware.tv.tuner.V1_0.Constants;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Digital Video Record (DVR) class which provides playback control on Demux's input buffer.
+ *
+ * <p>It's used to play recorded programs.
+ *
+ * @hide
+ */
+@SystemApi
+public class DvrPlayback extends Dvr {
+
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "PLAYBACK_STATUS_",
+            value = {PLAYBACK_STATUS_EMPTY, PLAYBACK_STATUS_ALMOST_EMPTY,
+                    PLAYBACK_STATUS_ALMOST_FULL, PLAYBACK_STATUS_FULL})
+    @interface PlaybackStatus {}
+
+    /**
+     * The space of the playback is empty.
+     */
+    public static final int PLAYBACK_STATUS_EMPTY = Constants.PlaybackStatus.SPACE_EMPTY;
+    /**
+     * The space of the playback is almost empty.
+     *
+     * <p> the threshold is set in {@link DvrSettings}.
+     */
+    public static final int PLAYBACK_STATUS_ALMOST_EMPTY =
+            Constants.PlaybackStatus.SPACE_ALMOST_EMPTY;
+    /**
+     * The space of the playback is almost full.
+     *
+     * <p> the threshold is set in {@link DvrSettings}.
+     */
+    public static final int PLAYBACK_STATUS_ALMOST_FULL =
+            Constants.PlaybackStatus.SPACE_ALMOST_FULL;
+    /**
+     * The space of the playback is full.
+     */
+    public static final int PLAYBACK_STATUS_FULL = Constants.PlaybackStatus.SPACE_FULL;
+
+
+
+    private native long nativeRead(long size);
+    private native long nativeRead(byte[] bytes, long offset, long size);
+
+    private DvrPlayback() {
+        super(Dvr.TYPE_PLAYBACK);
+    }
+
+
+    /**
+     * Reads data from the file for DVR playback.
+     *
+     * @param size the maximum number of bytes to read.
+     * @return the number of bytes read.
+     */
+    @BytesLong
+    public long read(@BytesLong long size) {
+        return nativeRead(size);
+    }
+
+    /**
+     * Reads data from the buffer for DVR playback and copies to the given byte array.
+     *
+     * @param bytes the byte array to store the data.
+     * @param offset the index of the first byte in {@code bytes} to copy to.
+     * @param size the maximum number of bytes to read.
+     * @return the number of bytes read.
+     */
+    @BytesLong
+    public long read(@NonNull byte[] bytes, @BytesLong long offset, @BytesLong long size) {
+        if (size + offset > bytes.length) {
+            throw new ArrayIndexOutOfBoundsException(
+                    "Array length=" + bytes.length + ", offset=" + offset + ", size=" + size);
+        }
+        return nativeRead(bytes, offset, size);
+    }
+}
diff --git a/media/java/android/media/tv/tuner/dvr/DvrRecorder.java b/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
new file mode 100644
index 0000000..3128ca5
--- /dev/null
+++ b/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 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.media.tv.tuner.dvr;
+
+import android.annotation.BytesLong;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+/**
+ * Digital Video Record (DVR) recorder class which provides record control on Demux's output buffer.
+ *
+ * @hide
+ */
+@SystemApi
+public class DvrRecorder extends Dvr {
+    private native long nativeWrite(long size);
+    private native long nativeWrite(byte[] bytes, long offset, long size);
+
+    private DvrRecorder() {
+        super(Dvr.TYPE_RECORD);
+    }
+
+    /**
+     * Writes recording data to file.
+     *
+     * @param size the maximum number of bytes to write.
+     * @return the number of bytes written.
+     */
+    @BytesLong
+    public long write(@BytesLong long size) {
+        return nativeWrite(size);
+    }
+
+    /**
+     * Writes recording data to buffer.
+     *
+     * @param bytes the byte array stores the data to be written to DVR.
+     * @param offset the index of the first byte in {@code bytes} to be written to DVR.
+     * @param size the maximum number of bytes to write.
+     * @return the number of bytes written.
+     */
+    @BytesLong
+    public long write(@NonNull byte[] bytes, @BytesLong long offset, @BytesLong long size) {
+        return nativeWrite(bytes, offset, size);
+    }
+}
diff --git a/media/java/android/media/tv/tuner/dvr/DvrSettings.java b/media/java/android/media/tv/tuner/dvr/DvrSettings.java
new file mode 100644
index 0000000..f9dc682
--- /dev/null
+++ b/media/java/android/media/tv/tuner/dvr/DvrSettings.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright 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.media.tv.tuner.dvr;
+
+import android.annotation.BytesLong;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.filter.Filter;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * DVR settings used to configure {@link Dvr}.
+ *
+ * @hide
+ */
+@SystemApi
+public class DvrSettings {
+
+    /** @hide */
+    @IntDef(prefix = "DATA_FORMAT_",
+            value = {DATA_FORMAT_TS, DATA_FORMAT_PES, DATA_FORMAT_ES, DATA_FORMAT_SHV_TLV})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DataFormat {}
+
+    /**
+     * Transport Stream.
+     */
+    public static final int DATA_FORMAT_TS = Constants.DataFormat.TS;
+    /**
+     * Packetized Elementary Stream.
+     */
+    public static final int DATA_FORMAT_PES = Constants.DataFormat.PES;
+    /**
+     * Elementary Stream.
+     */
+    public static final int DATA_FORMAT_ES = Constants.DataFormat.ES;
+    /**
+     * TLV (type-length-value) Stream for SHV
+     */
+    public static final int DATA_FORMAT_SHV_TLV = Constants.DataFormat.SHV_TLV;
+
+
+    @Filter.Status
+    private final int mStatusMask;
+    @BytesLong
+    private final long mLowThreshold;
+    @BytesLong
+    private final long mHighThreshold;
+    @BytesLong
+    private final long mPacketSize;
+    @DataFormat
+    private final int mDataFormat;
+
+    private DvrSettings(@Filter.Status int statusMask, @BytesLong long lowThreshold,
+            @BytesLong long highThreshold, @BytesLong long packetSize, @DataFormat int dataFormat) {
+        mStatusMask = statusMask;
+        mLowThreshold = lowThreshold;
+        mHighThreshold = highThreshold;
+        mPacketSize = packetSize;
+        mDataFormat = dataFormat;
+    }
+
+    /**
+     * Gets status mask.
+     */
+    @Filter.Status
+    public int getStatusMask() {
+        return mStatusMask;
+    }
+
+    /**
+     * Gets low threshold in bytes.
+     */
+    @BytesLong
+    public long getLowThreshold() {
+        return mLowThreshold;
+    }
+
+    /**
+     * Sets high threshold in bytes.
+     */
+    @BytesLong
+    public long getHighThreshold() {
+        return mHighThreshold;
+    }
+
+    /**
+     * Gets packet size in bytes.
+     */
+    @BytesLong
+    public long getPacketSize() {
+        return mPacketSize;
+    }
+
+    /**
+     * Gets data format.
+     */
+    @DataFormat
+    public int getDataFormat() {
+        return mDataFormat;
+    }
+
+    /**
+     * Creates a builder for {@link DvrSettings}.
+     *
+     * @param context the context of the caller.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(@NonNull Context context) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder();
+    }
+
+    /**
+     * Builder for {@link DvrSettings}.
+     */
+    public static final class Builder {
+        private int mStatusMask;
+        private long mLowThreshold;
+        private long mHighThreshold;
+        private long mPacketSize;
+        @DataFormat
+        private int mDataFormat;
+
+        /**
+         * Sets status mask.
+         */
+        @NonNull
+        public Builder setStatusMask(@Filter.Status int statusMask) {
+            this.mStatusMask = statusMask;
+            return this;
+        }
+
+        /**
+         * Sets low threshold in bytes.
+         */
+        @NonNull
+        public Builder setLowThreshold(@BytesLong long lowThreshold) {
+            this.mLowThreshold = lowThreshold;
+            return this;
+        }
+
+        /**
+         * Sets high threshold in bytes.
+         */
+        @NonNull
+        public Builder setHighThreshold(@BytesLong long highThreshold) {
+            this.mHighThreshold = highThreshold;
+            return this;
+        }
+
+        /**
+         * Sets packet size in bytes.
+         */
+        @NonNull
+        public Builder setPacketSize(@BytesLong long packetSize) {
+            this.mPacketSize = packetSize;
+            return this;
+        }
+
+        /**
+         * Sets data format.
+         */
+        @NonNull
+        public Builder setDataFormat(@DataFormat int dataFormat) {
+            this.mDataFormat = dataFormat;
+            return this;
+        }
+
+        /**
+         * Builds a {@link DvrSettings} object.
+         */
+        @NonNull
+        public DvrSettings build() {
+            return new DvrSettings(
+                    mStatusMask, mLowThreshold, mHighThreshold, mPacketSize, mDataFormat);
+        }
+    }
+}
diff --git a/media/java/android/media/tv/tuner/dvr/OnPlaybackStatusChangedListener.java b/media/java/android/media/tv/tuner/dvr/OnPlaybackStatusChangedListener.java
new file mode 100644
index 0000000..a9a1779
--- /dev/null
+++ b/media/java/android/media/tv/tuner/dvr/OnPlaybackStatusChangedListener.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 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.media.tv.tuner.dvr;
+
+import android.annotation.SystemApi;
+
+/**
+ * Listener interface for receiving information from DVR playback.
+ *
+ * @hide
+ */
+@SystemApi
+public interface OnPlaybackStatusChangedListener {
+    /**
+     * Invoked when playback status changed.
+     */
+    void onPlaybackStatusChanged(@DvrPlayback.PlaybackStatus int status);
+}
diff --git a/media/java/android/media/tv/tuner/dvr/OnRecordStatusChangedListener.java b/media/java/android/media/tv/tuner/dvr/OnRecordStatusChangedListener.java
new file mode 100644
index 0000000..cb6ccab
--- /dev/null
+++ b/media/java/android/media/tv/tuner/dvr/OnRecordStatusChangedListener.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 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.media.tv.tuner.dvr;
+
+import android.annotation.SystemApi;
+import android.media.tv.tuner.filter.Filter;
+
+/**
+ * Listener interface for receiving information from DVR recorder.
+ *
+ * @hide
+ */
+@SystemApi
+public interface OnRecordStatusChangedListener {
+    /**
+     * Invoked when record status changed.
+     */
+    void onRecordStatusChanged(@Filter.Status int status);
+}
diff --git a/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java
new file mode 100644
index 0000000..064ab80
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 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.media.tv.tuner.filter;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Filter configuration for a ALP filter.
+ *
+ * @hide
+ */
+@SystemApi
+public class AlpFilterConfiguration extends FilterConfiguration {
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "LENGTH_TYPE_", value =
+            {LENGTH_TYPE_UNDEFINED, LENGTH_TYPE_WITHOUT_ADDITIONAL_HEADER,
+            LENGTH_TYPE_WITH_ADDITIONAL_HEADER})
+    public @interface LengthType {}
+
+    /**
+     * Length type not defined.
+     */
+    public static final int LENGTH_TYPE_UNDEFINED = Constants.DemuxAlpLengthType.UNDEFINED;
+    /**
+     * Length does NOT include additional header.
+     */
+    public static final int LENGTH_TYPE_WITHOUT_ADDITIONAL_HEADER =
+            Constants.DemuxAlpLengthType.WITHOUT_ADDITIONAL_HEADER;
+    /**
+     * Length includes additional header.
+     */
+    public static final int LENGTH_TYPE_WITH_ADDITIONAL_HEADER =
+            Constants.DemuxAlpLengthType.WITH_ADDITIONAL_HEADER;
+
+
+    private final int mPacketType;
+    private final int mLengthType;
+
+    private AlpFilterConfiguration(Settings settings, int packetType, int lengthType) {
+        super(settings);
+        mPacketType = packetType;
+        mLengthType = lengthType;
+    }
+
+    @Override
+    public int getType() {
+        return Filter.TYPE_ALP;
+    }
+
+    /**
+     * Gets packet type.
+     */
+    @FilterConfiguration.PacketType
+    public int getPacketType() {
+        return mPacketType;
+    }
+    /**
+     * Gets length type.
+     */
+    @LengthType
+    public int getLengthType() {
+        return mLengthType;
+    }
+
+    /**
+     * Creates a builder for {@link AlpFilterConfiguration}.
+     *
+     * @param context the context of the caller.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(@NonNull Context context) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder();
+    }
+
+    /**
+     * Builder for {@link AlpFilterConfiguration}.
+     */
+    public static class Builder extends FilterConfiguration.Builder<Builder> {
+        private int mPacketType;
+        private int mLengthType;
+
+        private Builder() {
+        }
+
+        /**
+         * Sets packet type.
+         */
+        @NonNull
+        public Builder setPacketType(@FilterConfiguration.PacketType int packetType) {
+            mPacketType = packetType;
+            return this;
+        }
+        /**
+         * Sets length type.
+         */
+        @NonNull
+        public Builder setLengthType(@LengthType int lengthType) {
+            mLengthType = lengthType;
+            return this;
+        }
+
+        /**
+         * Builds a {@link AlpFilterConfiguration} object.
+         */
+        @NonNull
+        public AlpFilterConfiguration build() {
+            return new AlpFilterConfiguration(mSettings, mPacketType, mLengthType);
+        }
+
+        @Override
+        Builder self() {
+            return this;
+        }
+    }
+}
diff --git a/media/java/android/media/tv/tuner/filter/AudioDescriptor.java b/media/java/android/media/tv/tuner/filter/AudioDescriptor.java
new file mode 100644
index 0000000..7b1576a
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/AudioDescriptor.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 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.media.tv.tuner.filter;
+
+import android.annotation.SystemApi;
+
+/**
+ * Meta data from AD (Audio Descriptor) according to ETSI TS 101 154 V2.1.1.
+ *
+ * @hide
+ */
+@SystemApi
+public class AudioDescriptor {
+    private final byte mAdFade;
+    private final byte mAdPan;
+    private final char mVersionTextTag;
+    private final byte mAdGainCenter;
+    private final byte mAdGainFront;
+    private final byte mAdGainSurround;
+
+    // This constructor is used by JNI code only
+    private AudioDescriptor(byte adFade, byte adPan, char versionTextTag, byte adGainCenter,
+            byte adGainFront, byte adGainSurround) {
+        mAdFade = adFade;
+        mAdPan = adPan;
+        mVersionTextTag = versionTextTag;
+        mAdGainCenter = adGainCenter;
+        mAdGainFront = adGainFront;
+        mAdGainSurround = adGainSurround;
+    }
+
+    /**
+     * Gets AD fade byte.
+     *
+     * <p>Takes values between 0x00 (representing no fade of the main programme sound) and 0xFF
+     * (representing a full fade). Over the range 0x00 to 0xFE one lsb represents a step in
+     * attenuation of the programme sound of 0.3 dB giving a range of 76.2 dB. The fade value of
+     * 0xFF represents no programme sound at all (i.e. mute).
+     */
+    public byte getAdFade() {
+        return mAdFade;
+    }
+
+    /**
+     * Gets AD pan byte.
+     *
+     * <p>Takes values between 0x00 representing a central forward presentation of the audio
+     * description and 0xFF, each increment representing a 360/256 degree step clockwise looking
+     * down on the listener (i.e. just over 1.4 degrees).
+     */
+    public byte getAdPan() {
+        return mAdPan;
+    }
+
+    /**
+     * Gets AD version tag. A single ASCII character version indicates the version.
+     *
+     * <p>A single ASCII character version designator (here "1" indicates revision 1).
+     */
+    public char getAdVersionTextTag() {
+        return mVersionTextTag;
+    }
+
+    /**
+     * Gets AD gain byte center in dB.
+     *
+     * <p>Represents a signed value in dB. Takes values between 0x7F (representing +76.2 dB boost of
+     * the main programme center) and 0x80 (representing a full fade). Over the range 0x00 to 0x7F
+     * one lsb represents a step in boost of the programme center of 0.6 dB giving a maximum boost
+     * of +76.2 dB. Over the range 0x81 to 0x00 one lsb represents a step in attenuation of the
+     * programme center of 0.6 dB giving a maximum attenuation of -76.2 dB. The gain value of 0x80
+     * represents no main center level at all (i.e. mute).
+     */
+    public byte getAdGainCenter() {
+        return mAdGainCenter;
+    }
+
+    /**
+     * Gets AD gain byte front in dB.
+     *
+     * <p>Same as {@link #getAdGainCenter()}, but applied to left and right front channel.
+     */
+    public byte getAdGainFront() {
+        return mAdGainFront;
+    }
+
+    /**
+     * Gets AD gain byte surround in dB.
+     *
+     * <p>Same as {@link #getAdGainCenter()}, but applied to all surround channels
+     */
+    public byte getAdGainSurround() {
+        return mAdGainSurround;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java b/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java
deleted file mode 100644
index 306de84..0000000
--- a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 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.media.tv.tuner.filter;
-
-/**
- * Extra Meta Data from AD (Audio Descriptor) according to
- * ETSI TS 101 154 V2.1.1.
- * @hide
- */
-public class AudioExtraMetaData {
-    private byte mAdFade;
-    private byte mAdPan;
-    private byte mVersionTextTag;
-    private byte mAdGainCenter;
-    private byte mAdGainFront;
-    private byte mAdGainSurround;
-}
diff --git a/media/java/android/media/tv/tuner/filter/AvSettings.java b/media/java/android/media/tv/tuner/filter/AvSettings.java
new file mode 100644
index 0000000..bf11893
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/AvSettings.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 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.media.tv.tuner.filter;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+
+/**
+ * Filter Settings for a Video and Audio.
+ *
+ * @hide
+ */
+@SystemApi
+public class AvSettings extends Settings {
+    private final boolean mIsPassthrough;
+
+    private AvSettings(int mainType, boolean isAudio, boolean isPassthrough) {
+        super(TunerUtils.getFilterSubtype(
+                mainType,
+                isAudio
+                        ? Filter.SUBTYPE_AUDIO
+                        : Filter.SUBTYPE_VIDEO));
+        mIsPassthrough = isPassthrough;
+    }
+
+    /**
+     * Checks whether it's passthrough.
+     */
+    public boolean isPassthrough() {
+        return mIsPassthrough;
+    }
+
+    /**
+     * Creates a builder for {@link AvSettings}.
+     *
+     * @param context the context of the caller.
+     * @param mainType the filter main type.
+     * @param isAudio {@code true} if it's audio settings; {@code false} if it's video settings.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(
+            @NonNull Context context, @Filter.Type int mainType, boolean isAudio) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder(mainType, isAudio);
+    }
+
+    /**
+     * Builder for {@link AvSettings}.
+     */
+    public static class Builder extends Settings.Builder<Builder> {
+        private final boolean mIsAudio;
+        private boolean mIsPassthrough;
+
+        private Builder(int mainType, boolean isAudio) {
+            super(mainType);
+            mIsAudio = isAudio;
+        }
+
+        /**
+         * Sets whether it's passthrough.
+         */
+        @NonNull
+        public Builder setPassthrough(boolean isPassthrough) {
+            mIsPassthrough = isPassthrough;
+            return this;
+        }
+
+        /**
+         * Builds a {@link AvSettings} object.
+         */
+        @NonNull
+        public AvSettings build() {
+            return new AvSettings(mMainType, mIsAudio, mIsPassthrough);
+        }
+
+        @Override
+        Builder self() {
+            return this;
+        }
+    }
+}
diff --git a/media/java/android/media/tv/tuner/filter/DownloadEvent.java b/media/java/android/media/tv/tuner/filter/DownloadEvent.java
index 548fa77..9f97b614 100644
--- a/media/java/android/media/tv/tuner/filter/DownloadEvent.java
+++ b/media/java/android/media/tv/tuner/filter/DownloadEvent.java
@@ -16,15 +16,66 @@
 
 package android.media.tv.tuner.filter;
 
+import android.annotation.SystemApi;
+
 /**
- * Download event.
+ * Filter event sent from {@link Filter} objects with download type.
+ *
  * @hide
  */
+@SystemApi
 public class DownloadEvent extends FilterEvent {
-    private int mItemId;
-    private int mMpuSequenceNumber;
-    private int mItemFragmentIndex;
-    private int mLastItemFragmentIndex;
-    private int mDataLength;
+    private final int mItemId;
+    private final int mMpuSequenceNumber;
+    private final int mItemFragmentIndex;
+    private final int mLastItemFragmentIndex;
+    private final int mDataLength;
+
+    // This constructor is used by JNI code only
+    private DownloadEvent(int itemId, int mpuSequenceNumber, int itemFragmentIndex,
+            int lastItemFragmentIndex, int dataLength) {
+        mItemId = itemId;
+        mMpuSequenceNumber = mpuSequenceNumber;
+        mItemFragmentIndex = itemFragmentIndex;
+        mLastItemFragmentIndex = lastItemFragmentIndex;
+        mDataLength = dataLength;
+    }
+
+    /**
+     * Gets item ID.
+     */
+    public int getItemId() {
+        return mItemId;
+    }
+
+    /**
+     * Gets MPU sequence number of filtered data.
+     */
+    public int getMpuSequenceNumber() {
+        return mMpuSequenceNumber;
+    }
+
+    /**
+     * Gets current index of the current item.
+     *
+     * An item can be stored in different fragments.
+     */
+    public int getItemFragmentIndex() {
+        return mItemFragmentIndex;
+    }
+
+    /**
+     * Gets last index of the current item.
+     */
+    public int getLastItemFragmentIndex() {
+        return mLastItemFragmentIndex;
+    }
+
+    /**
+     * Gets data size in bytes of filtered data.
+     */
+    public int getDataLength() {
+        return mDataLength;
+    }
 }
 
diff --git a/media/java/android/media/tv/tuner/filter/DownloadSettings.java b/media/java/android/media/tv/tuner/filter/DownloadSettings.java
new file mode 100644
index 0000000..915ad79
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/DownloadSettings.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 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.media.tv.tuner.filter;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+
+/**
+ * Filter Settings for a Download.
+ *
+ * @hide
+ */
+@SystemApi
+public class DownloadSettings extends Settings {
+    private final int mDownloadId;
+
+    private DownloadSettings(int mainType, int downloadId) {
+        super(TunerUtils.getFilterSubtype(mainType, Filter.SUBTYPE_DOWNLOAD));
+        mDownloadId = downloadId;
+    }
+
+    /**
+     * Gets download ID.
+     */
+    public int getDownloadId() {
+        return mDownloadId;
+    }
+
+    /**
+     * Creates a builder for {@link DownloadSettings}.
+     *
+     * @param context the context of the caller.
+     * @param mainType the filter main type.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(@NonNull Context context, @Filter.Type int mainType) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder(mainType);
+    }
+
+    /**
+     * Builder for {@link DownloadSettings}.
+     */
+    public static class Builder extends Settings.Builder<Builder> {
+        private int mDownloadId;
+
+        private Builder(int mainType) {
+            super(mainType);
+        }
+
+        /**
+         * Sets download ID.
+         */
+        @NonNull
+        public Builder setDownloadId(int downloadId) {
+            mDownloadId = downloadId;
+            return this;
+        }
+
+        /**
+         * Builds a {@link DownloadSettings} object.
+         */
+        @NonNull
+        public DownloadSettings build() {
+            return new DownloadSettings(mMainType, mDownloadId);
+        }
+
+        @Override
+        Builder self() {
+            return this;
+        }
+    }
+}
diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java
new file mode 100644
index 0000000..06de6e8
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/Filter.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright 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.media.tv.tuner.filter;
+
+import android.annotation.BytesLong;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerConstants.Result;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Tuner data filter.
+ *
+ * <p>This class is used to filter wanted data according to the filter's configuration.
+ *
+ * @hide
+ */
+@SystemApi
+public class Filter implements AutoCloseable {
+    /** @hide */
+    @IntDef(prefix = "TYPE_",
+            value = {TYPE_TS, TYPE_MMTP, TYPE_IP, TYPE_TLV, TYPE_ALP})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Type {}
+
+    /**
+     * TS filter type.
+     */
+    public static final int TYPE_TS = Constants.DemuxFilterMainType.TS;
+    /**
+     * MMTP filter type.
+     */
+    public static final int TYPE_MMTP = Constants.DemuxFilterMainType.MMTP;
+    /**
+     * IP filter type.
+     */
+    public static final int TYPE_IP = Constants.DemuxFilterMainType.IP;
+    /**
+     * TLV filter type.
+     */
+    public static final int TYPE_TLV = Constants.DemuxFilterMainType.TLV;
+    /**
+     * ALP filter type.
+     */
+    public static final int TYPE_ALP = Constants.DemuxFilterMainType.ALP;
+
+    /** @hide */
+    @IntDef(prefix = "SUBTYPE_",
+            value = {SUBTYPE_UNDEFINED, SUBTYPE_SECTION, SUBTYPE_PES, SUBTYPE_AUDIO, SUBTYPE_VIDEO,
+                    SUBTYPE_DOWNLOAD, SUBTYPE_RECORD, SUBTYPE_TS, SUBTYPE_PCR, SUBTYPE_TEMI,
+                    SUBTYPE_MMTP, SUBTYPE_NTP, SUBTYPE_IP_PAYLOAD, SUBTYPE_IP,
+                    SUBTYPE_PAYLOAD_THROUGH, SUBTYPE_TLV, SUBTYPE_PTP, })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Subtype {}
+    /**
+     * Filter subtype undefined.
+     */
+    public static final int SUBTYPE_UNDEFINED = 0;
+    /**
+     * Section filter subtype.
+     */
+    public static final int SUBTYPE_SECTION = 1;
+    /**
+     * PES filter subtype.
+     */
+    public static final int SUBTYPE_PES = 2;
+    /**
+     * Audio filter subtype.
+     */
+    public static final int SUBTYPE_AUDIO = 3;
+    /**
+     * Video filter subtype.
+     */
+    public static final int SUBTYPE_VIDEO = 4;
+    /**
+     * Download filter subtype.
+     */
+    public static final int SUBTYPE_DOWNLOAD = 5;
+    /**
+     * Record filter subtype.
+     */
+    public static final int SUBTYPE_RECORD = 6;
+    /**
+     * TS filter subtype.
+     */
+    public static final int SUBTYPE_TS = 7;
+    /**
+     * PCR filter subtype.
+     */
+    public static final int SUBTYPE_PCR = 8;
+    /**
+     * TEMI filter subtype.
+     */
+    public static final int SUBTYPE_TEMI = 9;
+    /**
+     * MMTP filter subtype.
+     */
+    public static final int SUBTYPE_MMTP = 10;
+    /**
+     * NTP filter subtype.
+     */
+    public static final int SUBTYPE_NTP = 11;
+    /**
+     * Payload filter subtype.
+     */
+    public static final int SUBTYPE_IP_PAYLOAD = 12;
+    /**
+     * IP filter subtype.
+     */
+    public static final int SUBTYPE_IP = 13;
+    /**
+     * Payload through filter subtype.
+     */
+    public static final int SUBTYPE_PAYLOAD_THROUGH = 14;
+    /**
+     * TLV filter subtype.
+     */
+    public static final int SUBTYPE_TLV = 15;
+    /**
+     * PTP filter subtype.
+     */
+    public static final int SUBTYPE_PTP = 16;
+
+
+
+    /** @hide */
+    @IntDef(flag = true, prefix = "STATUS_", value = {STATUS_DATA_READY, STATUS_LOW_WATER,
+            STATUS_HIGH_WATER, STATUS_OVERFLOW})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Status {}
+
+    /**
+     * The status of a filter that the data in the filter buffer is ready to be read.
+     */
+    public static final int STATUS_DATA_READY = Constants.DemuxFilterStatus.DATA_READY;
+    /**
+     * The status of a filter that the amount of available data in the filter buffer is at low
+     * level.
+     *
+     * The value is set to 25 percent of the buffer size by default. It can be changed when
+     * configuring the filter.
+     */
+    public static final int STATUS_LOW_WATER = Constants.DemuxFilterStatus.LOW_WATER;
+    /**
+     * The status of a filter that the amount of available data in the filter buffer is at high
+     * level.
+     * The value is set to 75 percent of the buffer size by default. It can be changed when
+     * configuring the filter.
+     */
+    public static final int STATUS_HIGH_WATER = Constants.DemuxFilterStatus.HIGH_WATER;
+    /**
+     * The status of a filter that the filter buffer is full and newly filtered data is being
+     * discarded.
+     */
+    public static final int STATUS_OVERFLOW = Constants.DemuxFilterStatus.OVERFLOW;
+
+
+    private long mNativeContext;
+    private FilterCallback mCallback;
+    private final int mId;
+
+    private native int nativeConfigureFilter(
+            int type, int subType, FilterConfiguration settings);
+    private native int nativeGetId();
+    private native int nativeSetDataSource(Filter source);
+    private native int nativeStartFilter();
+    private native int nativeStopFilter();
+    private native int nativeFlushFilter();
+    private native int nativeRead(byte[] buffer, long offset, long size);
+    private native int nativeClose();
+
+    // Called by JNI
+    private Filter(int id) {
+        mId = id;
+    }
+
+    private void onFilterStatus(int status) {
+    }
+
+    /** @hide */
+    public void setCallback(FilterCallback cb) {
+        mCallback = cb;
+    }
+    /** @hide */
+    public FilterCallback getCallback() {
+        return mCallback;
+    }
+
+    /**
+     * Configures the filter.
+     *
+     * @param config the configuration of the filter.
+     * @return result status of the operation.
+     */
+    @Result
+    public int configure(@NonNull FilterConfiguration config) {
+        int subType = -1;
+        Settings s = config.getSettings();
+        if (s != null) {
+            subType = s.getType();
+        }
+        return nativeConfigureFilter(config.getType(), subType, config);
+    }
+
+    /**
+     * Gets the filter Id.
+     */
+    @Result
+    public int getId() {
+        return nativeGetId();
+    }
+
+    /**
+     * Sets the filter's data source.
+     *
+     * A filter uses demux as data source by default. If the data was packetized
+     * by multiple protocols, multiple filters may need to work together to
+     * extract all protocols' header. Then a filter's data source can be output
+     * from another filter.
+     *
+     * @param source the filter instance which provides data input. Switch to
+     * use demux as data source if the filter instance is NULL.
+     * @return result status of the operation.
+     */
+    @Result
+    public int setDataSource(@Nullable Filter source) {
+        return nativeSetDataSource(source);
+    }
+
+    /**
+     * Starts filtering data.
+     *
+     * @return result status of the operation.
+     */
+    @Result
+    public int start() {
+        return nativeStartFilter();
+    }
+
+
+    /**
+     * Stops filtering data.
+     *
+     * @return result status of the operation.
+     */
+    @Result
+    public int stop() {
+        return nativeStopFilter();
+    }
+
+    /**
+     * Flushes the filter.
+     *
+     * <p>The data which is already produced by filter but not consumed yet will
+     * be cleared.
+     *
+     * @return result status of the operation.
+     */
+    @Result
+    public int flush() {
+        return nativeFlushFilter();
+    }
+
+    /**
+     * Copies filtered data from filter output to the given byte array.
+     *
+     * @param buffer the buffer to store the filtered data.
+     * @param offset the index of the first byte in {@code buffer} to write.
+     * @param size the maximum number of bytes to read.
+     * @return the number of bytes read.
+     */
+    @Result
+    public int read(@NonNull byte[] buffer, @BytesLong long offset, @BytesLong long size) {
+        size = Math.min(size, buffer.length - offset);
+        return nativeRead(buffer, offset, size);
+    }
+
+    /**
+     * Releases the Filter instance.
+     */
+    @Override
+    public void close() {
+        nativeClose();
+    }
+}
diff --git a/media/java/android/media/tv/tuner/filter/FilterCallback.java b/media/java/android/media/tv/tuner/filter/FilterCallback.java
new file mode 100644
index 0000000..2ad6bd1
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/FilterCallback.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 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.media.tv.tuner.filter;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+/**
+ * Callback interface for receiving information from the corresponding filters.
+ *
+ * @hide
+ */
+@SystemApi
+public interface FilterCallback {
+    /**
+     * Invoked when there are filter events.
+     *
+     * @param filter the corresponding filter which sent the events.
+     * @param events the filter events sent from the filter.
+     */
+    void onFilterEvent(@NonNull Filter filter, @NonNull FilterEvent[] events);
+    /**
+     * Invoked when filter status changed.
+     *
+     * @param filter the corresponding filter whose status is changed.
+     * @param status the new status of the filter.
+     */
+    void onFilterStatusChanged(@NonNull Filter filter, @Filter.Status int status);
+}
diff --git a/media/java/android/media/tv/tuner/filter/FilterConfiguration.java b/media/java/android/media/tv/tuner/filter/FilterConfiguration.java
new file mode 100644
index 0000000..c1d2275
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/FilterConfiguration.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 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.media.tv.tuner.filter;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Filter configuration used to configure filters.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class FilterConfiguration {
+
+    /** @hide */
+    @IntDef(prefix = "PACKET_TYPE_", value =
+            {PACKET_TYPE_IPV4, PACKET_TYPE_COMPRESSED, PACKET_TYPE_SIGNALING})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PacketType {}
+
+    /**
+     * IP v4 packet type.
+     */
+    public static final int PACKET_TYPE_IPV4 = 0;
+    /**
+     * Compressed packet type.
+     */
+    public static final int PACKET_TYPE_COMPRESSED = 2;
+    /**
+     * Signaling packet type.
+     */
+    public static final int PACKET_TYPE_SIGNALING = 4;
+
+
+    @Nullable
+    /* package */ final Settings mSettings;
+
+    /* package */ FilterConfiguration(Settings settings) {
+        mSettings = settings;
+    }
+
+    /**
+     * Gets filter configuration type.
+     */
+    @Filter.Type
+    public abstract int getType();
+
+    /**
+     * Gets filter Settings.
+     */
+    @Nullable
+    public Settings getSettings() {
+        return mSettings;
+    }
+
+    /**
+     * Builder for {@link FilterConfiguration}.
+     *
+     * @param <T> The subclass to be built.
+     */
+    public abstract static class Builder<T extends Builder<T>> {
+        /* package */ Settings mSettings;
+
+        /* package */ Builder() {
+        }
+
+        /**
+         * Sets filter settings.
+         */
+        @NonNull
+        public T setSettings(@Nullable Settings settings) {
+            mSettings = settings;
+            return self();
+        }
+        /* package */ abstract T self();
+    }
+}
diff --git a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
new file mode 100644
index 0000000..bf5aaed
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 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.media.tv.tuner.filter;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.Size;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+
+/**
+ * Filter configuration for a IP filter.
+ *
+ * @hide
+ */
+@SystemApi
+public class IpFilterConfiguration extends FilterConfiguration {
+    private final byte[] mSrcIpAddress;
+    private final byte[] mDstIpAddress;
+    private final int mSrcPort;
+    private final int mDstPort;
+    private final boolean mPassthrough;
+
+    private IpFilterConfiguration(Settings settings, byte[] srcAddr, byte[] dstAddr, int srcPort,
+            int dstPort, boolean passthrough) {
+        super(settings);
+        mSrcIpAddress = srcAddr;
+        mDstIpAddress = dstAddr;
+        mSrcPort = srcPort;
+        mDstPort = dstPort;
+        mPassthrough = passthrough;
+    }
+
+    @Override
+    public int getType() {
+        return Filter.TYPE_IP;
+    }
+
+    /**
+     * Gets source IP address.
+     */
+    @Size(min = 4, max = 16)
+    @NonNull
+    public byte[] getSrcIpAddress() {
+        return mSrcIpAddress;
+    }
+    /**
+     * Gets destination IP address.
+     */
+    @Size(min = 4, max = 16)
+    @NonNull
+    public byte[] getDstIpAddress() {
+        return mDstIpAddress;
+    }
+    /**
+     * Gets source port.
+     */
+    public int getSrcPort() {
+        return mSrcPort;
+    }
+    /**
+     * Gets destination port.
+     */
+    public int getDstPort() {
+        return mDstPort;
+    }
+    /**
+     * Checks whether the filter is passthrough.
+     *
+     * @return {@code true} if the data from IP subtype go to next filter directly;
+     *         {@code false} otherwise.
+     */
+    public boolean isPassthrough() {
+        return mPassthrough;
+    }
+
+    /**
+     * Creates a builder for {@link IpFilterConfiguration}.
+     *
+     * @param context the context of the caller.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(@NonNull Context context) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder();
+    }
+
+    /**
+     * Builder for {@link IpFilterConfiguration}.
+     */
+    public static class Builder extends FilterConfiguration.Builder<Builder> {
+        private byte[] mSrcIpAddress;
+        private byte[] mDstIpAddress;
+        private int mSrcPort;
+        private int mDstPort;
+        private boolean mPassthrough;
+
+        private Builder() {
+        }
+
+        /**
+         * Sets source IP address.
+         */
+        @NonNull
+        public Builder setSrcIpAddress(@NonNull byte[] srcIpAddress) {
+            mSrcIpAddress = srcIpAddress;
+            return this;
+        }
+        /**
+         * Sets destination IP address.
+         */
+        @NonNull
+        public Builder setDstIpAddress(@NonNull byte[] dstIpAddress) {
+            mDstIpAddress = dstIpAddress;
+            return this;
+        }
+        /**
+         * Sets source port.
+         */
+        @NonNull
+        public Builder setSrcPort(int srcPort) {
+            mSrcPort = srcPort;
+            return this;
+        }
+        /**
+         * Sets destination port.
+         */
+        @NonNull
+        public Builder setDstPort(int dstPort) {
+            mDstPort = dstPort;
+            return this;
+        }
+        /**
+         * Sets passthrough.
+         */
+        @NonNull
+        public Builder setPassthrough(boolean passthrough) {
+            mPassthrough = passthrough;
+            return this;
+        }
+
+        /**
+         * Builds a {@link IpFilterConfiguration} object.
+         */
+        @NonNull
+        public IpFilterConfiguration build() {
+            return new IpFilterConfiguration(
+                    mSettings, mSrcIpAddress, mDstIpAddress, mSrcPort, mDstPort, mPassthrough);
+        }
+
+        @Override
+        Builder self() {
+            return this;
+        }
+    }
+}
diff --git a/media/java/android/media/tv/tuner/filter/IpPayloadEvent.java b/media/java/android/media/tv/tuner/filter/IpPayloadEvent.java
index 4da1d21..42a124f 100644
--- a/media/java/android/media/tv/tuner/filter/IpPayloadEvent.java
+++ b/media/java/android/media/tv/tuner/filter/IpPayloadEvent.java
@@ -16,10 +16,26 @@
 
 package android.media.tv.tuner.filter;
 
+import android.annotation.SystemApi;
+
 /**
- * IP payload event.
+ * Filter event sent from {@link Filter} objects with IP payload type.
+ *
  * @hide
  */
+@SystemApi
 public class IpPayloadEvent extends FilterEvent {
-    private int mDataLength;
+    private final int mDataLength;
+
+    // This constructor is used by JNI code only
+    private IpPayloadEvent(int dataLength) {
+        mDataLength = dataLength;
+    }
+
+    /**
+     * Gets data size in bytes of filtered data.
+     */
+    public int getDataLength() {
+        return mDataLength;
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/MediaEvent.java b/media/java/android/media/tv/tuner/filter/MediaEvent.java
index 7703248..b6bd86b 100644
--- a/media/java/android/media/tv/tuner/filter/MediaEvent.java
+++ b/media/java/android/media/tv/tuner/filter/MediaEvent.java
@@ -16,20 +16,149 @@
 
 package android.media.tv.tuner.filter;
 
-import android.os.NativeHandle;
+import android.annotation.BytesLong;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.media.MediaCodec.LinearBlock;
 
 /**
- * Media event.
+ * Filter event sent from {@link Filter} objects with media type.
+ *
  * @hide
  */
-public class MediaEvent extends FilterEvent {
-    private int mStreamId;
-    private boolean mIsPtsPresent;
-    private long mPts;
-    private int mDataLength;
-    private NativeHandle mHandle;
-    private boolean mIsSecureMemory;
-    private int mMpuSequenceNumber;
-    private boolean mIsPrivateData;
-    private AudioExtraMetaData mExtraMetaData;
+@SystemApi
+public class MediaEvent extends FilterEvent{
+    private final int mStreamId;
+    private final boolean mIsPtsPresent;
+    private final long mPts;
+    private final long mDataLength;
+    private final long mOffset;
+    private final LinearBlock mLinearBlock;
+    private final boolean mIsSecureMemory;
+    private final long mDataId;
+    private final int mMpuSequenceNumber;
+    private final boolean mIsPrivateData;
+    private final AudioDescriptor mExtraMetaData;
+
+    // This constructor is used by JNI code only
+    private MediaEvent(int streamId, boolean isPtsPresent, long pts, long dataLength, long offset,
+            LinearBlock buffer, boolean isSecureMemory, long dataId, int mpuSequenceNumber,
+            boolean isPrivateData, AudioDescriptor extraMetaData) {
+        mStreamId = streamId;
+        mIsPtsPresent = isPtsPresent;
+        mPts = pts;
+        mDataLength = dataLength;
+        mOffset = offset;
+        mLinearBlock = buffer;
+        mIsSecureMemory = isSecureMemory;
+        mDataId = dataId;
+        mMpuSequenceNumber = mpuSequenceNumber;
+        mIsPrivateData = isPrivateData;
+        mExtraMetaData = extraMetaData;
+    }
+
+    /**
+     * Gets stream ID.
+     */
+    public int getStreamId() {
+        return mStreamId;
+    }
+
+    /**
+     * Returns whether PTS (Presentation Time Stamp) is present.
+     *
+     * @return {@code true} if PTS is present in PES header; {@code false} otherwise.
+     */
+    public boolean isPtsPresent() {
+        return mIsPtsPresent;
+    }
+
+    /**
+     * Gets PTS (Presentation Time Stamp) for audio or video frame.
+     */
+    public long getPts() {
+        return mPts;
+    }
+
+    /**
+     * Gets data size in bytes of audio or video frame.
+     */
+    @BytesLong
+    public long getDataLength() {
+        return mDataLength;
+    }
+
+    /**
+     * The offset in the memory block which is shared among multiple Media Events.
+     */
+    @BytesLong
+    public long getOffset() {
+        return mOffset;
+    }
+
+    /**
+     * Gets a linear block associated to the memory where audio or video data stays.
+     */
+    @Nullable
+    public LinearBlock getLinearBlock() {
+        return mLinearBlock;
+    }
+
+    /**
+     * Returns whether the data is secure.
+     *
+     * @return {@code true} if the data is in secure area, and isn't mappable;
+     *         {@code false} otherwise.
+     */
+    public boolean isSecureMemory() {
+        return mIsSecureMemory;
+    }
+
+    /**
+     * Gets the ID which is used by HAL to provide additional information for AV data.
+     *
+     * <p>For secure audio, it's the audio handle used by Audio Track.
+     */
+    public long getAvDataId() {
+        return mDataId;
+    }
+
+    /**
+     * Gets the audio handle.
+     *
+     * <p>Client gets audio handle from {@link MediaEvent}, and queues it to
+     * {@link android.media.AudioTrack} in
+     * {@link android.media.AudioTrack#ENCAPSULATION_MODE_HANDLE} format.
+     *
+     * @return the audio handle.
+     * @see android.media.AudioTrack#ENCAPSULATION_MODE_HANDLE
+     */
+    public long getAudioHandle() {
+        // TODO: implement
+        return mDataId;
+    }
+
+    /**
+     * Gets MPU sequence number of filtered data.
+     */
+    public int getMpuSequenceNumber() {
+        return mMpuSequenceNumber;
+    }
+
+    /**
+     * Returns whether the data is private.
+     *
+     * @return {@code true} if the data is in private; {@code false} otherwise.
+     */
+    public boolean isPrivateData() {
+        return mIsPrivateData;
+    }
+
+    /**
+     * Gets audio extra metadata.
+     */
+    @Nullable
+    public AudioDescriptor getExtraMetaData() {
+        return mExtraMetaData;
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java
new file mode 100644
index 0000000..0601829
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 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.media.tv.tuner.filter;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+
+/**
+ * Filter configuration for a MMTP filter.
+ *
+ * @hide
+ */
+@SystemApi
+public class MmtpFilterConfiguration extends FilterConfiguration {
+    private final int mMmtpPid;
+
+    private MmtpFilterConfiguration(Settings settings, int mmtpPid) {
+        super(settings);
+        mMmtpPid = mmtpPid;
+    }
+
+    @Override
+    public int getType() {
+        return Filter.TYPE_MMTP;
+    }
+
+    /**
+     * Gets MMTP Packet ID.
+     *
+     * <p>Packet ID is used to specify packets in MMTP.
+     */
+    public int getMmtpPacketId() {
+        return mMmtpPid;
+    }
+
+    /**
+     * Creates a builder for {@link IpFilterConfiguration}.
+     *
+     * @param context the context of the caller.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(@NonNull Context context) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder();
+    }
+
+    /**
+     * Builder for {@link IpFilterConfiguration}.
+     */
+    public static class Builder extends FilterConfiguration.Builder<Builder> {
+        private int mMmtpPid;
+
+        private Builder() {
+        }
+
+        /**
+         * Sets MMTP Packet ID.
+         */
+        @NonNull
+        public Builder setMmtpPacketId(int mmtpPid) {
+            mMmtpPid = mmtpPid;
+            return this;
+        }
+
+        /**
+         * Builds a {@link IpFilterConfiguration} object.
+         */
+        @NonNull
+        public MmtpFilterConfiguration build() {
+            return new MmtpFilterConfiguration(mSettings, mMmtpPid);
+        }
+
+        @Override
+        Builder self() {
+            return this;
+        }
+    }
+}
diff --git a/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java b/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
index dbd8c77..466fa3e 100644
--- a/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
+++ b/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
@@ -16,11 +16,39 @@
 
 package android.media.tv.tuner.filter;
 
+import android.annotation.BytesLong;
+import android.annotation.SystemApi;
+import android.media.tv.tuner.filter.RecordSettings.ScHevcIndex;
+
 /**
- * MMPT record event.
+ * Filter event sent from {@link Filter} objects with MMTP type.
+ *
  * @hide
  */
+@SystemApi
 public class MmtpRecordEvent extends FilterEvent {
-    private int mScHevcIndexMask;
-    private long mByteNumber;
+    private final int mScHevcIndexMask;
+    private final long mDataLength;
+
+    // This constructor is used by JNI code only
+    private MmtpRecordEvent(int scHevcIndexMask, long dataLength) {
+        mScHevcIndexMask = scHevcIndexMask;
+        mDataLength = dataLength;
+    }
+
+    /**
+     * Gets indexes which can be tagged by NAL unit group in HEVC according to ISO/IEC 23008-2.
+     */
+    @ScHevcIndex
+    public int getScHevcIndexMask() {
+        return mScHevcIndexMask;
+    }
+
+    /**
+     * Gets data size in bytes of filtered data.
+     */
+    @BytesLong
+    public long getDataLength() {
+        return mDataLength;
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/PesEvent.java b/media/java/android/media/tv/tuner/filter/PesEvent.java
index 16536e2..695e596 100644
--- a/media/java/android/media/tv/tuner/filter/PesEvent.java
+++ b/media/java/android/media/tv/tuner/filter/PesEvent.java
@@ -16,12 +16,44 @@
 
 package android.media.tv.tuner.filter;
 
+import android.annotation.SystemApi;
+
 /**
- * PES event.
+ * Filter event sent from {@link Filter} objects with PES type.
+ *
  * @hide
  */
+@SystemApi
 public class PesEvent extends FilterEvent {
-    private int mStreamId;
-    private int mDataLength;
-    private int mMpuSequenceNumber;
+    private final int mStreamId;
+    private final int mDataLength;
+    private final int mMpuSequenceNumber;
+
+    // This constructor is used by JNI code only
+    private PesEvent(int streamId, int dataLength, int mpuSequenceNumber) {
+        mStreamId = streamId;
+        mDataLength = dataLength;
+        mMpuSequenceNumber = mpuSequenceNumber;
+    }
+
+    /**
+     * Gets stream ID.
+     */
+    public int getStreamId() {
+        return mStreamId;
+    }
+
+    /**
+     * Gets data size in bytes of filtered data.
+     */
+    public int getDataLength() {
+        return mDataLength;
+    }
+
+    /**
+     * Gets MPU sequence number of filtered data.
+     */
+    public int getMpuSequenceNumber() {
+        return mMpuSequenceNumber;
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/PesSettings.java b/media/java/android/media/tv/tuner/filter/PesSettings.java
new file mode 100644
index 0000000..0f83597
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/PesSettings.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 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.media.tv.tuner.filter;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+
+/**
+ * Filter Settings for a PES Data.
+ *
+ * @hide
+ */
+@SystemApi
+public class PesSettings extends Settings {
+    private final int mStreamId;
+    private final boolean mIsRaw;
+
+    private PesSettings(@Filter.Type int mainType, int streamId, boolean isRaw) {
+        super(TunerUtils.getFilterSubtype(mainType, Filter.SUBTYPE_PES));
+        mStreamId = streamId;
+        mIsRaw = isRaw;
+    }
+
+    /**
+     * Gets stream ID.
+     */
+    public int getStreamId() {
+        return mStreamId;
+    }
+
+    /**
+     * Returns whether the data is raw.
+     *
+     * @return {@code true} if the data is raw. Filter sends onFilterStatus callback
+     * instead of onFilterEvent for raw data. {@code false} otherwise.
+     */
+    public boolean isRaw() {
+        return mIsRaw;
+    }
+
+    /**
+     * Creates a builder for {@link PesSettings}.
+     *
+     * @param mainType the filter main type of the settings.
+     * @param context the context of the caller.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(@NonNull Context context, @Filter.Type int mainType) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder(mainType);
+    }
+
+    /**
+     * Builder for {@link PesSettings}.
+     */
+    public static class Builder {
+        private final int mMainType;
+        private int mStreamId;
+        private boolean mIsRaw;
+
+        private Builder(int mainType) {
+            mMainType = mainType;
+        }
+
+        /**
+         * Sets stream ID.
+         *
+         * @param streamId the stream ID.
+         */
+        @NonNull
+        public Builder setStreamId(int streamId) {
+            mStreamId = streamId;
+            return this;
+        }
+
+        /**
+         * Sets whether the data is raw.
+         *
+         * @param isRaw {@code true} if the data is raw. Filter sends onFilterStatus callback
+         * instead of onFilterEvent for raw data. {@code false} otherwise.
+         */
+        @NonNull
+        public Builder setRaw(boolean isRaw) {
+            mIsRaw = isRaw;
+            return this;
+        }
+
+        /**
+         * Builds a {@link PesSettings} object.
+         */
+        @NonNull
+        public PesSettings build() {
+            return new PesSettings(mMainType, mStreamId, mIsRaw);
+        }
+    }
+}
diff --git a/media/java/android/media/tv/tuner/filter/RecordSettings.java b/media/java/android/media/tv/tuner/filter/RecordSettings.java
new file mode 100644
index 0000000..0c812ab
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/RecordSettings.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright 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.media.tv.tuner.filter;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * The Settings for the record in DVR.
+ *
+ * @hide
+ */
+@SystemApi
+public class RecordSettings extends Settings {
+    /**
+     * Indexes can be tagged through TS (Transport Stream) header.
+     *
+     * @hide
+     */
+    @IntDef(flag = true,
+            prefix = "TS_INDEX_",
+            value = {TS_INDEX_FIRST_PACKET, TS_INDEX_PAYLOAD_UNIT_START_INDICATOR,
+                    TS_INDEX_CHANGE_TO_NOT_SCRAMBLED, TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED,
+                    TS_INDEX_CHANGE_TO_ODD_SCRAMBLED, TS_INDEX_DISCONTINUITY_INDICATOR,
+                    TS_INDEX_RANDOM_ACCESS_INDICATOR, TS_INDEX_PRIORITY_INDICATOR,
+                    TS_INDEX_PCR_FLAG, TS_INDEX_OPCR_FLAG, TS_INDEX_SPLICING_POINT_FLAG,
+                    TS_INDEX_PRIVATE_DATA, TS_INDEX_ADAPTATION_EXTENSION_FLAG})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TsIndexMask {}
+
+    /**
+     * TS index FIRST_PACKET.
+     */
+    public static final int TS_INDEX_FIRST_PACKET = Constants.DemuxTsIndex.FIRST_PACKET;
+    /**
+     * TS index PAYLOAD_UNIT_START_INDICATOR.
+     */
+    public static final int TS_INDEX_PAYLOAD_UNIT_START_INDICATOR =
+            Constants.DemuxTsIndex.PAYLOAD_UNIT_START_INDICATOR;
+    /**
+     * TS index CHANGE_TO_NOT_SCRAMBLED.
+     */
+    public static final int TS_INDEX_CHANGE_TO_NOT_SCRAMBLED =
+            Constants.DemuxTsIndex.CHANGE_TO_NOT_SCRAMBLED;
+    /**
+     * TS index CHANGE_TO_EVEN_SCRAMBLED.
+     */
+    public static final int TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED =
+            Constants.DemuxTsIndex.CHANGE_TO_EVEN_SCRAMBLED;
+    /**
+     * TS index CHANGE_TO_ODD_SCRAMBLED.
+     */
+    public static final int TS_INDEX_CHANGE_TO_ODD_SCRAMBLED =
+            Constants.DemuxTsIndex.CHANGE_TO_ODD_SCRAMBLED;
+    /**
+     * TS index DISCONTINUITY_INDICATOR.
+     */
+    public static final int TS_INDEX_DISCONTINUITY_INDICATOR =
+            Constants.DemuxTsIndex.DISCONTINUITY_INDICATOR;
+    /**
+     * TS index RANDOM_ACCESS_INDICATOR.
+     */
+    public static final int TS_INDEX_RANDOM_ACCESS_INDICATOR =
+            Constants.DemuxTsIndex.RANDOM_ACCESS_INDICATOR;
+    /**
+     * TS index PRIORITY_INDICATOR.
+     */
+    public static final int TS_INDEX_PRIORITY_INDICATOR = Constants.DemuxTsIndex.PRIORITY_INDICATOR;
+    /**
+     * TS index PCR_FLAG.
+     */
+    public static final int TS_INDEX_PCR_FLAG = Constants.DemuxTsIndex.PCR_FLAG;
+    /**
+     * TS index OPCR_FLAG.
+     */
+    public static final int TS_INDEX_OPCR_FLAG = Constants.DemuxTsIndex.OPCR_FLAG;
+    /**
+     * TS index SPLICING_POINT_FLAG.
+     */
+    public static final int TS_INDEX_SPLICING_POINT_FLAG =
+            Constants.DemuxTsIndex.SPLICING_POINT_FLAG;
+    /**
+     * TS index PRIVATE_DATA.
+     */
+    public static final int TS_INDEX_PRIVATE_DATA = Constants.DemuxTsIndex.PRIVATE_DATA;
+    /**
+     * TS index ADAPTATION_EXTENSION_FLAG.
+     */
+    public static final int TS_INDEX_ADAPTATION_EXTENSION_FLAG =
+            Constants.DemuxTsIndex.ADAPTATION_EXTENSION_FLAG;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "INDEX_TYPE_", value =
+            {INDEX_TYPE_NONE, INDEX_TYPE_SC, INDEX_TYPE_SC_HEVC})
+    public @interface ScIndexType {}
+
+    /**
+     * Start Code Index is not used.
+     */
+    public static final int INDEX_TYPE_NONE = Constants.DemuxRecordScIndexType.NONE;
+    /**
+     * Start Code index.
+     */
+    public static final int INDEX_TYPE_SC = Constants.DemuxRecordScIndexType.SC;
+    /**
+     * Start Code index for HEVC.
+     */
+    public static final int INDEX_TYPE_SC_HEVC = Constants.DemuxRecordScIndexType.SC_HEVC;
+
+    /**
+     * Indexes can be tagged by Start Code in PES (Packetized Elementary Stream)
+     * according to ISO/IEC 13818-1.
+     * @hide
+     */
+    @IntDef(flag = true, value = {SC_INDEX_I_FRAME, SC_INDEX_P_FRAME, SC_INDEX_B_FRAME,
+            SC_INDEX_SEQUENCE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ScIndex {}
+
+    /**
+     * SC index for a new I-frame.
+     */
+    public static final int SC_INDEX_I_FRAME = Constants.DemuxScIndex.I_FRAME;
+    /**
+     * SC index for a new P-frame.
+     */
+    public static final int SC_INDEX_P_FRAME = Constants.DemuxScIndex.P_FRAME;
+    /**
+     * SC index for a new B-frame.
+     */
+    public static final int SC_INDEX_B_FRAME = Constants.DemuxScIndex.B_FRAME;
+    /**
+     * SC index for a new sequence.
+     */
+    public static final int SC_INDEX_SEQUENCE = Constants.DemuxScIndex.SEQUENCE;
+
+
+    /**
+     * Indexes can be tagged by NAL unit group in HEVC according to ISO/IEC 23008-2.
+     *
+     * @hide
+     */
+    @IntDef(flag = true,
+            value = {SC_HEVC_INDEX_SPS, SC_HEVC_INDEX_AUD, SC_HEVC_INDEX_SLICE_CE_BLA_W_LP,
+            SC_HEVC_INDEX_SLICE_BLA_W_RADL, SC_HEVC_INDEX_SLICE_BLA_N_LP,
+            SC_HEVC_INDEX_SLICE_IDR_W_RADL, SC_HEVC_INDEX_SLICE_IDR_N_LP,
+            SC_HEVC_INDEX_SLICE_TRAIL_CRA})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ScHevcIndex {}
+
+    /**
+     * SC HEVC index SPS.
+     */
+    public static final int SC_HEVC_INDEX_SPS = Constants.DemuxScHevcIndex.SPS;
+    /**
+     * SC HEVC index AUD.
+     */
+    public static final int SC_HEVC_INDEX_AUD = Constants.DemuxScHevcIndex.AUD;
+    /**
+     * SC HEVC index SLICE_CE_BLA_W_LP.
+     */
+    public static final int SC_HEVC_INDEX_SLICE_CE_BLA_W_LP =
+            Constants.DemuxScHevcIndex.SLICE_CE_BLA_W_LP;
+    /**
+     * SC HEVC index SLICE_BLA_W_RADL.
+     */
+    public static final int SC_HEVC_INDEX_SLICE_BLA_W_RADL =
+            Constants.DemuxScHevcIndex.SLICE_BLA_W_RADL;
+    /**
+     * SC HEVC index SLICE_BLA_N_LP.
+     */
+    public static final int SC_HEVC_INDEX_SLICE_BLA_N_LP =
+            Constants.DemuxScHevcIndex.SLICE_BLA_N_LP;
+    /**
+     * SC HEVC index SLICE_IDR_W_RADL.
+     */
+    public static final int SC_HEVC_INDEX_SLICE_IDR_W_RADL =
+            Constants.DemuxScHevcIndex.SLICE_IDR_W_RADL;
+    /**
+     * SC HEVC index SLICE_IDR_N_LP.
+     */
+    public static final int SC_HEVC_INDEX_SLICE_IDR_N_LP =
+            Constants.DemuxScHevcIndex.SLICE_IDR_N_LP;
+    /**
+     * SC HEVC index SLICE_TRAIL_CRA.
+     */
+    public static final int SC_HEVC_INDEX_SLICE_TRAIL_CRA =
+            Constants.DemuxScHevcIndex.SLICE_TRAIL_CRA;
+
+    /**
+     * @hide
+     */
+    @IntDef(flag = true,
+            prefix = "SC_",
+            value = {
+                SC_INDEX_I_FRAME,
+                SC_INDEX_P_FRAME,
+                SC_INDEX_B_FRAME,
+                SC_INDEX_SEQUENCE,
+                SC_HEVC_INDEX_SPS,
+                SC_HEVC_INDEX_AUD,
+                SC_HEVC_INDEX_SLICE_CE_BLA_W_LP,
+                SC_HEVC_INDEX_SLICE_BLA_W_RADL,
+                SC_HEVC_INDEX_SLICE_BLA_N_LP,
+                SC_HEVC_INDEX_SLICE_IDR_W_RADL,
+                SC_HEVC_INDEX_SLICE_IDR_N_LP,
+                SC_HEVC_INDEX_SLICE_TRAIL_CRA,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ScIndexMask {}
+
+
+
+    private final int mTsIndexMask;
+    private final int mScIndexType;
+    private final int mScIndexMask;
+
+    private RecordSettings(int mainType, int tsIndexType, int scIndexType, int scIndexMask) {
+        super(TunerUtils.getFilterSubtype(mainType, Filter.SUBTYPE_RECORD));
+        mTsIndexMask = tsIndexType;
+        mScIndexType = scIndexType;
+        mScIndexMask = scIndexMask;
+    }
+
+    /**
+     * Gets TS index mask.
+     */
+    @TsIndexMask
+    public int getTsIndexMask() {
+        return mTsIndexMask;
+    }
+    /**
+     * Gets Start Code index type.
+     */
+    @ScIndexType
+    public int getScIndexType() {
+        return mScIndexType;
+    }
+    /**
+     * Gets Start Code index mask.
+     */
+    @ScIndexMask
+    public int getScIndexMask() {
+        return mScIndexMask;
+    }
+
+    /**
+     * Creates a builder for {@link RecordSettings}.
+     *
+     * @param context the context of the caller.
+     * @param mainType the filter main type.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(@NonNull Context context, @Filter.Type int mainType) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder(mainType);
+    }
+
+    /**
+     * Builder for {@link RecordSettings}.
+     */
+    public static class Builder extends Settings.Builder<Builder> {
+        private int mTsIndexMask;
+        private int mScIndexType;
+        private int mScIndexMask;
+
+        private Builder(int mainType) {
+            super(mainType);
+        }
+
+        /**
+         * Sets TS index mask.
+         */
+        @NonNull
+        public Builder setTsIndexMask(@TsIndexMask int indexMask) {
+            mTsIndexMask = indexMask;
+            return this;
+        }
+        /**
+         * Sets index type.
+         */
+        @NonNull
+        public Builder setScIndexType(@ScIndexType int indexType) {
+            mScIndexType = indexType;
+            return this;
+        }
+        /**
+         * Sets Start Code index mask.
+         */
+        @NonNull
+        public Builder setScIndexMask(@ScIndexMask int indexMask) {
+            mScIndexMask = indexMask;
+            return this;
+        }
+
+        /**
+         * Builds a {@link RecordSettings} object.
+         */
+        @NonNull
+        public RecordSettings build() {
+            return new RecordSettings(mMainType, mTsIndexMask, mScIndexType, mScIndexMask);
+        }
+
+        @Override
+        Builder self() {
+            return this;
+        }
+    }
+
+}
diff --git a/media/java/android/media/tv/tuner/filter/SectionEvent.java b/media/java/android/media/tv/tuner/filter/SectionEvent.java
index e211dda..ff12492 100644
--- a/media/java/android/media/tv/tuner/filter/SectionEvent.java
+++ b/media/java/android/media/tv/tuner/filter/SectionEvent.java
@@ -17,7 +17,6 @@
 package android.media.tv.tuner.filter;
 
 import android.annotation.SystemApi;
-import android.media.tv.tuner.Tuner.Filter;
 
 /**
  * Filter event sent from {@link Filter} objects with section type.
diff --git a/media/java/android/media/tv/tuner/filter/SectionSettings.java b/media/java/android/media/tv/tuner/filter/SectionSettings.java
new file mode 100644
index 0000000..9470138
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/SectionSettings.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 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.media.tv.tuner.filter;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.media.tv.tuner.TunerUtils;
+
+/**
+ * Filter Settings for Section data according to ISO/IEC 13818-1.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class SectionSettings extends Settings {
+    final boolean mCrcEnabled;
+    final boolean mIsRepeat;
+    final boolean mIsRaw;
+
+    SectionSettings(int mainType, boolean crcEnabled, boolean isRepeat, boolean isRaw) {
+        super(TunerUtils.getFilterSubtype(mainType, Filter.SUBTYPE_SECTION));
+        mCrcEnabled = crcEnabled;
+        mIsRepeat = isRepeat;
+        mIsRaw = isRaw;
+    }
+
+    /**
+     * Returns whether the filter enables CRC (Cyclic redundancy check) and discards data which
+     * doesn't pass the check.
+     */
+    public boolean isCrcEnabled() {
+        return mCrcEnabled;
+    }
+    /**
+     * Returns whether the filter repeats the data with the same version.
+     */
+    public boolean isRepeat() {
+        return mIsRepeat;
+    }
+    /**
+     * Returns whether the filter sends {@link FilterCallback#onFilterStatusChanged} instead of
+     * {@link FilterCallback#onFilterEvent}.
+     */
+    public boolean isRaw() {
+        return mIsRaw;
+    }
+
+    /**
+     * Builder for {@link SectionSettings}.
+     *
+     * @param <T> The subclass to be built.
+     */
+    public abstract static class Builder<T extends Builder<T>>
+            extends Settings.Builder<Builder<T>> {
+        boolean mCrcEnabled;
+        boolean mIsRepeat;
+        boolean mIsRaw;
+
+        Builder(int mainType) {
+            super(mainType);
+        }
+
+        /**
+         * Sets whether the filter enables CRC (Cyclic redundancy check) and discards data which
+         * doesn't pass the check.
+         */
+        @NonNull
+        public T setCrcEnabled(boolean crcEnabled) {
+            mCrcEnabled = crcEnabled;
+            return self();
+        }
+        /**
+         * Sets whether the filter repeats the data with the same version.
+         */
+        @NonNull
+        public T setRepeat(boolean isRepeat) {
+            mIsRepeat = isRepeat;
+            return self();
+        }
+        /**
+         * Sets whether the filter send onFilterStatus instead of
+         * {@link FilterCallback#onFilterEvent}.
+         */
+        @NonNull
+        public T setRaw(boolean isRaw) {
+            mIsRaw = isRaw;
+            return self();
+        }
+
+        /* package */ abstract T self();
+    }
+}
diff --git a/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java b/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java
new file mode 100644
index 0000000..cb547ec
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 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.media.tv.tuner.filter;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+
+/**
+ * Bits Settings for Section Filters.
+ *
+ * @hide
+ */
+@SystemApi
+public class SectionSettingsWithSectionBits extends SectionSettings {
+    private final byte[] mFilter;
+    private final byte[] mMask;
+    private final byte[] mMode;
+
+
+    private SectionSettingsWithSectionBits(int mainType, boolean isCheckCrc, boolean isRepeat,
+            boolean isRaw, byte[] filter, byte[] mask, byte[] mode) {
+        super(mainType, isCheckCrc, isRepeat, isRaw);
+        mFilter = filter;
+        mMask = mask;
+        mMode = mode;
+    }
+
+    /**
+     * Gets the bytes configured for Section Filter
+     */
+    @NonNull
+    public byte[] getFilterBytes() {
+        return mFilter;
+    }
+    /**
+     * Gets bit mask.
+     *
+     * <p>The bits in the bytes are used for filtering.
+     */
+    @NonNull
+    public byte[] getMask() {
+        return mMask;
+    }
+    /**
+     * Gets mode.
+     *
+     * <p>Do positive match at the bit position of the configured bytes when the bit at same
+     * position of the mode is 0.
+     * <p>Do negative match at the bit position of the configured bytes when the bit at same
+     * position of the mode is 1.
+     */
+    @NonNull
+    public byte[] getMode() {
+        return mMode;
+    }
+
+    /**
+     * Creates a builder for {@link SectionSettingsWithSectionBits}.
+     *
+     * @param context the context of the caller.
+     * @param mainType the filter main type.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(@NonNull Context context, @Filter.Type int mainType) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder(mainType);
+    }
+
+    /**
+     * Builder for {@link SectionSettingsWithSectionBits}.
+     */
+    public static class Builder extends SectionSettings.Builder<Builder> {
+        private byte[] mFilter;
+        private byte[] mMask;
+        private byte[] mMode;
+
+        private Builder(int mainType) {
+            super(mainType);
+        }
+
+        /**
+         * Sets filter bytes.
+         */
+        @NonNull
+        public Builder setFilter(@NonNull byte[] filter) {
+            mFilter = filter;
+            return this;
+        }
+        /**
+         * Sets bit mask.
+         */
+        @NonNull
+        public Builder setMask(@NonNull byte[] mask) {
+            mMask = mask;
+            return this;
+        }
+        /**
+         * Sets mode.
+         */
+        @NonNull
+        public Builder setMode(@NonNull byte[] mode) {
+            mMode = mode;
+            return this;
+        }
+
+        /**
+         * Builds a {@link SectionSettingsWithSectionBits} object.
+         */
+        @NonNull
+        public SectionSettingsWithSectionBits build() {
+            return new SectionSettingsWithSectionBits(
+                    mMainType, mCrcEnabled, mIsRepeat, mIsRaw, mFilter, mMask, mMode);
+        }
+
+        @Override
+        Builder self() {
+            return this;
+        }
+    }
+}
diff --git a/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java b/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java
new file mode 100644
index 0000000..09d1dae9
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 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.media.tv.tuner.filter;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+
+/**
+ * Table information for Section Filter.
+ *
+ * @hide
+ */
+@SystemApi
+public class SectionSettingsWithTableInfo extends SectionSettings {
+    private final int mTableId;
+    private final int mVersion;
+
+    private SectionSettingsWithTableInfo(int mainType, boolean isCheckCrc, boolean isRepeat,
+            boolean isRaw, int tableId, int version) {
+        super(mainType, isCheckCrc, isRepeat, isRaw);
+        mTableId = tableId;
+        mVersion = version;
+    }
+
+    /**
+     * Gets table ID.
+     */
+    public int getTableId() {
+        return mTableId;
+    }
+    /**
+     * Gets version.
+     */
+    public int getVersion() {
+        return mVersion;
+    }
+
+    /**
+     * Creates a builder for {@link SectionSettingsWithTableInfo}.
+     *
+     * @param context the context of the caller.
+     * @param mainType the filter main type.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(@NonNull Context context, @Filter.Type int mainType) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder(mainType);
+    }
+
+    /**
+     * Builder for {@link SectionSettingsWithTableInfo}.
+     */
+    public static class Builder extends SectionSettings.Builder<Builder> {
+        private int mTableId;
+        private int mVersion;
+
+        private Builder(int mainType) {
+            super(mainType);
+        }
+
+        /**
+         * Sets table ID.
+         */
+        @NonNull
+        public Builder setTableId(int tableId) {
+            mTableId = tableId;
+            return this;
+        }
+        /**
+         * Sets version.
+         */
+        @NonNull
+        public Builder setVersion(int version) {
+            mVersion = version;
+            return this;
+        }
+
+        /**
+         * Builds a {@link SectionSettingsWithTableInfo} object.
+         */
+        @NonNull
+        public SectionSettingsWithTableInfo build() {
+            return new SectionSettingsWithTableInfo(
+                    mMainType, mCrcEnabled, mIsRepeat, mIsRaw, mTableId, mVersion);
+        }
+
+        @Override
+        Builder self() {
+            return this;
+        }
+    }
+
+}
diff --git a/media/java/android/media/tv/tuner/filter/Settings.java b/media/java/android/media/tv/tuner/filter/Settings.java
new file mode 100644
index 0000000..fee7f66
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/Settings.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 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.media.tv.tuner.filter;
+
+import android.annotation.SystemApi;
+
+/**
+ * Settings for filters of different subtypes.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class Settings {
+    private final int mType;
+
+    /* package */ Settings(int type) {
+        mType = type;
+    }
+
+    /**
+     * Gets filter settings type.
+     */
+    public int getType() {
+        return mType;
+    }
+
+
+    /**
+     * Builder for {@link Settings}.
+     *
+     * @param <T> The subclass to be built.
+     */
+    public abstract static class Builder<T extends Builder<T>> {
+        /* package */ final int mMainType;
+
+        /* package */ Builder(int mainType) {
+            mMainType = mainType;
+        }
+        /* package */ abstract T self();
+    }
+}
diff --git a/media/java/android/media/tv/tuner/filter/TemiEvent.java b/media/java/android/media/tv/tuner/filter/TemiEvent.java
index 3841604..9bee928 100644
--- a/media/java/android/media/tv/tuner/filter/TemiEvent.java
+++ b/media/java/android/media/tv/tuner/filter/TemiEvent.java
@@ -16,12 +16,47 @@
 
 package android.media.tv.tuner.filter;
 
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
 /**
- * TEMI event.
+ * Filter event sent from {@link Filter} objects for Timed External Media Information (TEMI) data.
+ *
  * @hide
  */
+@SystemApi
 public class TemiEvent extends FilterEvent {
-    private long mPts;
-    private byte mDescrTag;
-    private byte[] mDescrData;
+    private final long mPts;
+    private final byte mDescrTag;
+    private final byte[] mDescrData;
+
+    // This constructor is used by JNI code only
+    private TemiEvent(long pts, byte descrTag, byte[] descrData) {
+        mPts = pts;
+        mDescrTag = descrTag;
+        mDescrData = descrData;
+    }
+
+
+    /**
+     * Gets PTS (Presentation Time Stamp) for audio or video frame.
+     */
+    public long getPts() {
+        return mPts;
+    }
+
+    /**
+     * Gets TEMI (Timed External Media Information) descriptor tag.
+     */
+    public byte getDescriptorTag() {
+        return mDescrTag;
+    }
+
+    /**
+     * Gets TEMI (Timed External Media Information) descriptor.
+     */
+    @NonNull
+    public byte[] getDescriptorData() {
+        return mDescrData;
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/TimeFilter.java b/media/java/android/media/tv/tuner/filter/TimeFilter.java
new file mode 100644
index 0000000..a926d59
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/TimeFilter.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 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.media.tv.tuner.filter;
+
+import android.annotation.SystemApi;
+import android.media.tv.tuner.TunerConstants.Result;
+
+/**
+ *  A timer filter is used to filter data based on timestamps.
+ *
+ *  <p> If the timestamp is set, data is discarded if its timestamp is smaller than the
+ *  timestamp in this time filter.
+ *
+ *  <p> The format of the timestamps is the same as PTS defined in ISO/IEC 13818-1:2019. The
+ *  timestamps may or may not be related to PTS or DTS.
+ *
+ * @hide
+ */
+@SystemApi
+public class TimeFilter implements AutoCloseable {
+
+    /**
+     * Timestamp is unavailable.
+     *
+     * <p>Returned by {@link #getSourceTime()} or {@link #getTimeStamp()} when the requested
+     * timestamp is not available.
+     *
+     * @see #getSourceTime()
+     * @see #getTimeStamp()
+     */
+    public static final long TIMESTAMP_UNAVAILABLE = -1L;
+
+
+    private native int nativeSetTimestamp(long timestamp);
+    private native int nativeClearTimestamp();
+    private native Long nativeGetTimestamp();
+    private native Long nativeGetSourceTime();
+    private native int nativeClose();
+
+    private boolean mEnable = false;
+
+    // Called by JNI code
+    private TimeFilter() {
+    }
+
+    /**
+     * Set timestamp for time based filter.
+     *
+     * It is used to set initial timestamp and enable time filtering. Once set, the time will be
+     * increased automatically like a clock. Contents are discarded if their timestamps are
+     * older than the time in the time filter.
+     *
+     * This method can be called more than once to reset the initial timestamp.
+     *
+     * @param timestamp initial timestamp for the time filter before it's increased. It's
+     * based on the 90KHz counter, and it's the same format as PTS (Presentation Time Stamp)
+     * defined in ISO/IEC 13818-1:2019. The timestamps may or may not be related to PTS or DTS.
+     * @return result status of the operation.
+     */
+    @Result
+    public int setCurrentTimestamp(long timestamp) {
+        int res = nativeSetTimestamp(timestamp);
+        // TODO: use a constant for SUCCESS
+        if (res == 0) {
+            mEnable = true;
+        }
+        return res;
+    }
+
+    /**
+     * Clear the timestamp in the time filter.
+     *
+     * It is used to clear the time value of the time filter. Time filtering is disabled then.
+     *
+     * @return result status of the operation.
+     */
+    @Result
+    public int clearTimestamp() {
+        int res = nativeClearTimestamp();
+        if (res == 0) {
+            mEnable = false;
+        }
+        return res;
+    }
+
+    /**
+     * Get the current time in the time filter.
+     *
+     * It is used to inquiry current time in the time filter.
+     *
+     * @return current timestamp in the time filter. It's based on the 90KHz counter, and it's
+     * the same format as PTS (Presentation Time Stamp) defined in ISO/IEC 13818-1:2019. The
+     * timestamps may or may not be related to PTS or DTS. Returns {@link #TIMESTAMP_UNAVAILABLE}
+     * if the timestamp is never set.
+     */
+    public long getTimeStamp() {
+        if (!mEnable) {
+            return TIMESTAMP_UNAVAILABLE;
+        }
+        return nativeGetTimestamp();
+    }
+
+    /**
+     * Get the timestamp from the beginning of incoming data stream.
+     *
+     * It is used to inquiry the timestamp from the beginning of incoming data stream.
+     *
+     * @return first timestamp of incoming data stream. It's based on the 90KHz counter, and
+     * it's the same format as PTS (Presentation Time Stamp) defined in ISO/IEC 13818-1:2019.
+     * The timestamps may or may not be related to PTS or DTS. Returns
+     * {@link #TIMESTAMP_UNAVAILABLE} if the timestamp is not available.
+     */
+    public long getSourceTime() {
+        if (!mEnable) {
+            return TIMESTAMP_UNAVAILABLE;
+        }
+        return nativeGetSourceTime();
+    }
+
+    /**
+     * Close the Time Filter instance
+     *
+     * It is to release the TimeFilter instance. Resources are reclaimed so the instance must
+     * not be accessed after this method is called.
+     */
+    @Override
+    public void close() {
+        nativeClose();
+    }
+}
diff --git a/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java
new file mode 100644
index 0000000..3d83a74
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 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.media.tv.tuner.filter;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+
+/**
+ * Filter configuration for a TLV filter.
+ *
+ * @hide
+ */
+@SystemApi
+public class TlvFilterConfiguration extends FilterConfiguration {
+    private final int mPacketType;
+    private final boolean mIsCompressedIpPacket;
+    private final boolean mPassthrough;
+
+    private TlvFilterConfiguration(Settings settings, int packetType, boolean isCompressed,
+            boolean passthrough) {
+        super(settings);
+        mPacketType = packetType;
+        mIsCompressedIpPacket = isCompressed;
+        mPassthrough = passthrough;
+    }
+
+    @Override
+    public int getType() {
+        return Filter.TYPE_TLV;
+    }
+
+    /**
+     * Gets packet type.
+     */
+    @FilterConfiguration.PacketType
+    public int getPacketType() {
+        return mPacketType;
+    }
+    /**
+     * Checks whether the data is compressed IP packet.
+     *
+     * @return {@code true} if the filtered data is compressed IP packet; {@code false} otherwise.
+     */
+    public boolean isCompressedIpPacket() {
+        return mIsCompressedIpPacket;
+    }
+    /**
+     * Checks whether it's passthrough.
+     *
+     * @return {@code true} if the data from TLV subtype go to next filter directly;
+     *         {@code false} otherwise.
+     */
+    public boolean isPassthrough() {
+        return mPassthrough;
+    }
+
+    /**
+     * Creates a builder for {@link TlvFilterConfiguration}.
+     *
+     * @param context the context of the caller.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(@NonNull Context context) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder();
+    }
+
+    /**
+     * Builder for {@link TlvFilterConfiguration}.
+     */
+    public static class Builder extends FilterConfiguration.Builder<Builder> {
+        private int mPacketType;
+        private boolean mIsCompressedIpPacket;
+        private boolean mPassthrough;
+
+        private Builder() {
+        }
+
+        /**
+         * Sets packet type.
+         */
+        @NonNull
+        public Builder setPacketType(@FilterConfiguration.PacketType int packetType) {
+            mPacketType = packetType;
+            return this;
+        }
+        /**
+         * Sets whether the data is compressed IP packet.
+         */
+        @NonNull
+        public Builder setCompressedIpPacket(boolean isCompressedIpPacket) {
+            mIsCompressedIpPacket = isCompressedIpPacket;
+            return this;
+        }
+        /**
+         * Sets whether it's passthrough.
+         */
+        @NonNull
+        public Builder setPassthrough(boolean passthrough) {
+            mPassthrough = passthrough;
+            return this;
+        }
+
+        /**
+         * Builds a {@link TlvFilterConfiguration} object.
+         */
+        @NonNull
+        public TlvFilterConfiguration build() {
+            return new TlvFilterConfiguration(
+                    mSettings, mPacketType, mIsCompressedIpPacket, mPassthrough);
+        }
+
+        @Override
+        Builder self() {
+            return this;
+        }
+    }
+}
diff --git a/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java
new file mode 100644
index 0000000..f186de6
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 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.media.tv.tuner.filter;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+
+/**
+ * Filter configuration for a TS filter.
+ *
+ * @hide
+ */
+@SystemApi
+public class TsFilterConfiguration extends FilterConfiguration {
+    private final int mTpid;
+
+    private TsFilterConfiguration(Settings settings, int tpid) {
+        super(settings);
+        mTpid = tpid;
+    }
+
+    @Override
+    public int getType() {
+        return Filter.TYPE_TS;
+    }
+
+    /**
+     * Gets the {@link Settings} object of this filter configuration.
+     */
+    @Nullable
+    public Settings getSettings() {
+        return mSettings;
+    }
+    /**
+     * Gets Tag Protocol ID.
+     */
+    public int getTpid() {
+        return mTpid;
+    }
+
+    /**
+     * Creates a builder for {@link TsFilterConfiguration}.
+     *
+     * @param context the context of the caller.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(@NonNull Context context) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder();
+    }
+
+    /**
+     * Builder for {@link TsFilterConfiguration}.
+     */
+    public static class Builder {
+        private Settings mSettings;
+        private int mTpid;
+
+        private Builder() {
+        }
+
+        /**
+         * Sets filter settings.
+         *
+         * @param settings the filter settings.
+         */
+        @NonNull
+        public Builder setSettings(@NonNull Settings settings) {
+            mSettings = settings;
+            return this;
+        }
+
+        /**
+         * Sets Tag Protocol ID.
+         *
+         * @param tpid the Tag Protocol ID.
+         */
+        @NonNull
+        public Builder setTpid(int tpid) {
+            mTpid = tpid;
+            return this;
+        }
+
+        /**
+         * Builds a {@link TsFilterConfiguration} object.
+         */
+        @NonNull
+        public TsFilterConfiguration build() {
+            return new TsFilterConfiguration(mSettings, mTpid);
+        }
+    }
+}
diff --git a/media/java/android/media/tv/tuner/filter/TsRecordEvent.java b/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
index 875b5bd..7a14bb8 100644
--- a/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
+++ b/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
@@ -16,12 +16,60 @@
 
 package android.media.tv.tuner.filter;
 
+import android.annotation.BytesLong;
+import android.annotation.SystemApi;
+
+
 /**
- * TS record event.
+ * Filter event sent from {@link Filter} objects for TS record data.
+ *
  * @hide
  */
+@SystemApi
 public class TsRecordEvent extends FilterEvent {
-    private int mTpid;
-    private int mIndexMask;
-    private long mByteNumber;
+
+    private final int mPid;
+    private final int mTsIndexMask;
+    private final int mScIndexMask;
+    private final long mDataLength;
+
+    // This constructor is used by JNI code only
+    private TsRecordEvent(int pid, int tsIndexMask, int scIndexMask, long dataLength) {
+        mPid = pid;
+        mTsIndexMask = tsIndexMask;
+        mScIndexMask = scIndexMask;
+        mDataLength = dataLength;
+    }
+
+    /**
+     * Gets packet ID.
+     */
+    public int getPacketId() {
+        return mPid;
+    }
+
+    /**
+     * Gets TS (transport stream) index mask.
+     */
+    @RecordSettings.TsIndexMask
+    public int getTsIndexMask() {
+        return mTsIndexMask;
+    }
+    /**
+     * Gets SC (Start Code) index mask.
+     *
+     * <p>The index type is SC or SC-HEVC, and is set when configuring the filter.
+     */
+    @RecordSettings.ScIndexMask
+    public int getScIndexMask() {
+        return mScIndexMask;
+    }
+
+    /**
+     * Gets data size in bytes of filtered data.
+     */
+    @BytesLong
+    public long getDataLength() {
+        return mDataLength;
+    }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/AnalogFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/AnalogFrontendCapabilities.java
new file mode 100644
index 0000000..096bc67
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/AnalogFrontendCapabilities.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 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.media.tv.tuner.frontend;
+
+import android.annotation.SystemApi;
+
+/**
+ * Capabilities for analog tuners.
+ *
+ * @hide
+ */
+@SystemApi
+public class AnalogFrontendCapabilities extends FrontendCapabilities {
+    @AnalogFrontendSettings.SignalType
+    private final int mTypeCap;
+    @AnalogFrontendSettings.SifStandard
+    private final int mSifStandardCap;
+
+    // Called by JNI code.
+    private AnalogFrontendCapabilities(int typeCap, int sifStandardCap) {
+        mTypeCap = typeCap;
+        mSifStandardCap = sifStandardCap;
+    }
+
+    /**
+     * Gets analog signal type capability.
+     */
+    @AnalogFrontendSettings.SignalType
+    public int getSignalTypeCapability() {
+        return mTypeCap;
+    }
+    /**
+     * Gets Standard Interchange Format (SIF) capability.
+     */
+    @AnalogFrontendSettings.SifStandard
+    public int getSifStandardCapability() {
+        return mSifStandardCap;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java
index 16308ce..7b85fa8 100644
--- a/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java
@@ -16,82 +16,238 @@
 
 package android.media.tv.tuner.frontend;
 
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
- * Frontend settings for analog.
+ * Frontend settings for analog tuner.
+ *
  * @hide
  */
+@SystemApi
 public class AnalogFrontendSettings extends FrontendSettings {
-    private int mAnalogType;
-    private int mSifStandard;
+    /** @hide */
+    @IntDef(flag = true,
+            prefix = "SIGNAL_TYPE_",
+            value = {SIGNAL_TYPE_UNDEFINED, SIGNAL_TYPE_AUTO, SIGNAL_TYPE_PAL, SIGNAL_TYPE_PAL_M,
+              SIGNAL_TYPE_PAL_N, SIGNAL_TYPE_PAL_60, SIGNAL_TYPE_NTSC, SIGNAL_TYPE_NTSC_443,
+              SIGNAL_TYPE_SECAM})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SignalType {}
+
+    /**
+     * Undefined analog signal type.
+     */
+    public static final int SIGNAL_TYPE_UNDEFINED = Constants.FrontendAnalogType.UNDEFINED;
+    /**
+     * AUTO analog signal type.
+     */
+    public static final int SIGNAL_TYPE_AUTO = Constants.FrontendAnalogType.AUTO;
+    /**
+     * PAL analog signal type.
+     */
+    public static final int SIGNAL_TYPE_PAL = Constants.FrontendAnalogType.PAL;
+    /**
+     * PAL M analog signal type.
+     */
+    public static final int SIGNAL_TYPE_PAL_M = Constants.FrontendAnalogType.PAL_M;
+    /**
+     * PAL N analog signal type.
+     */
+    public static final int SIGNAL_TYPE_PAL_N = Constants.FrontendAnalogType.PAL_N;
+    /**
+     * PAL 60 analog signal type.
+     */
+    public static final int SIGNAL_TYPE_PAL_60 = Constants.FrontendAnalogType.PAL_60;
+    /**
+     * NTSC analog signal type.
+     */
+    public static final int SIGNAL_TYPE_NTSC = Constants.FrontendAnalogType.NTSC;
+    /**
+     * NTSC 443 analog signal type.
+     */
+    public static final int SIGNAL_TYPE_NTSC_443 = Constants.FrontendAnalogType.NTSC_443;
+    /**
+     * SECM analog signal type.
+     */
+    public static final int SIGNAL_TYPE_SECAM = Constants.FrontendAnalogType.SECAM;
+
+    /** @hide */
+    @IntDef(flag = true,
+            prefix = "SIF_",
+            value = {SIF_UNDEFINED, SIF_AUTO, SIF_BG, SIF_BG_A2, SIF_BG_NICAM, SIF_I, SIF_DK,
+            SIF_DK1_A2, SIF_DK2_A2, SIF_DK3_A2, SIF_DK_NICAM, SIF_L, SIF_M, SIF_M_BTSC, SIF_M_A2,
+            SIF_M_EIAJ, SIF_I_NICAM, SIF_L_NICAM, SIF_L_PRIME})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SifStandard {}
+
+    /**
+     * Undefined Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_UNDEFINED = Constants.FrontendAnalogSifStandard.UNDEFINED;
+    /**
+     * Audo Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_AUTO = Constants.FrontendAnalogSifStandard.AUTO;
+     /**
+     * BG Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_BG = Constants.FrontendAnalogSifStandard.BG;
+    /**
+     * BG-A2 Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_BG_A2 = Constants.FrontendAnalogSifStandard.BG_A2;
+    /**
+     * BG-NICAM Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_BG_NICAM = Constants.FrontendAnalogSifStandard.BG_NICAM;
+    /**
+     * I Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_I = Constants.FrontendAnalogSifStandard.I;
+    /**
+     * DK Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_DK = Constants.FrontendAnalogSifStandard.DK;
+    /**
+     * DK1 A2 Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_DK1_A2 = Constants.FrontendAnalogSifStandard.DK1_A2;
+    /**
+     * DK2 A2 Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_DK2_A2 = Constants.FrontendAnalogSifStandard.DK2_A2;
+    /**
+     * DK3 A2 Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_DK3_A2 = Constants.FrontendAnalogSifStandard.DK3_A2;
+    /**
+     * DK-NICAM Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_DK_NICAM = Constants.FrontendAnalogSifStandard.DK_NICAM;
+    /**
+     * L Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_L = Constants.FrontendAnalogSifStandard.L;
+    /**
+     * M Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_M = Constants.FrontendAnalogSifStandard.M;
+    /**
+     * M-BTSC Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_M_BTSC = Constants.FrontendAnalogSifStandard.M_BTSC;
+    /**
+     * M-A2 Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_M_A2 = Constants.FrontendAnalogSifStandard.M_A2;
+    /**
+     * M-EIAJ Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_M_EIAJ = Constants.FrontendAnalogSifStandard.M_EIAJ;
+    /**
+     * I-NICAM Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_I_NICAM = Constants.FrontendAnalogSifStandard.I_NICAM;
+    /**
+     * L-NICAM Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_L_NICAM = Constants.FrontendAnalogSifStandard.L_NICAM;
+    /**
+     * L-PRIME Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_L_PRIME = Constants.FrontendAnalogSifStandard.L_PRIME;
+
+
+    private final int mSignalType;
+    private final int mSifStandard;
 
     @Override
     public int getType() {
-        return TunerConstants.FRONTEND_TYPE_ANALOG;
+        return FrontendSettings.TYPE_ANALOG;
     }
 
-    public int getAnalogType() {
-        return mAnalogType;
+
+    /**
+     * Gets analog signal type.
+     */
+    @SignalType
+    public int getSignalType() {
+        return mSignalType;
     }
 
+    /**
+     * Gets Standard Interchange Format (SIF).
+     */
+    @SifStandard
     public int getSifStandard() {
         return mSifStandard;
     }
 
     /**
-     * Creates a new builder object.
+     * Creates a builder for {@link AnalogFrontendSettings}.
+     *
+     * @param context the context of the caller.
      */
-    public static Builder newBuilder() {
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(@NonNull Context context) {
+        TunerUtils.checkTunerPermission(context);
         return new Builder();
     }
 
-    private AnalogFrontendSettings(int frequency, int analogType, int sifStandard) {
+    private AnalogFrontendSettings(int frequency, int signalType, int sifStandard) {
         super(frequency);
-        mAnalogType = analogType;
+        mSignalType = signalType;
         mSifStandard = sifStandard;
     }
 
     /**
-     * Builder for FrontendAnalogSettings.
+     * Builder for {@link AnalogFrontendSettings}.
      */
-    public static class Builder {
-        private int mFrequency;
-        private int mAnalogType;
+    public static class Builder extends FrontendSettings.Builder<Builder> {
+        private int mSignalType;
         private int mSifStandard;
 
         private Builder() {}
 
         /**
-         * Sets frequency.
+         * Sets analog signal type.
          */
-        public Builder setFrequency(int frequency) {
-            mFrequency = frequency;
+        @NonNull
+        public Builder setSignalType(@SignalType int signalType) {
+            mSignalType = signalType;
             return this;
         }
 
         /**
-         * Sets analog type.
+         * Sets Standard Interchange Format (SIF).
          */
-        public Builder setAnalogType(int analogType) {
-            mAnalogType = analogType;
-            return this;
-        }
-
-        /**
-         * Sets sif standard.
-         */
-        public Builder setSifStandard(int sifStandard) {
+        @NonNull
+        public Builder setSifStandard(@SifStandard int sifStandard) {
             mSifStandard = sifStandard;
             return this;
         }
 
         /**
-         * Builds a FrontendAnalogSettings instance.
+         * Builds a {@link AnalogFrontendSettings} object.
          */
+        @NonNull
         public AnalogFrontendSettings build() {
-            return new AnalogFrontendSettings(mFrequency, mAnalogType, mSifStandard);
+            return new AnalogFrontendSettings(mFrequency, mSignalType, mSifStandard);
+        }
+
+        @Override
+        Builder self() {
+            return this;
         }
     }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendCapabilities.java
new file mode 100644
index 0000000..7730912
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendCapabilities.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 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.media.tv.tuner.frontend;
+
+import android.annotation.SystemApi;
+
+/**
+ * ATSC-3 Capabilities.
+ *
+ * @hide
+ */
+@SystemApi
+public class Atsc3FrontendCapabilities extends FrontendCapabilities {
+    private final int mBandwidthCap;
+    private final int mModulationCap;
+    private final int mTimeInterleaveModeCap;
+    private final int mCodeRateCap;
+    private final int mFecCap;
+    private final int mDemodOutputFormatCap;
+
+    private Atsc3FrontendCapabilities(int bandwidthCap, int modulationCap,
+            int timeInterleaveModeCap, int codeRateCap, int fecCap, int demodOutputFormatCap) {
+        mBandwidthCap = bandwidthCap;
+        mModulationCap = modulationCap;
+        mTimeInterleaveModeCap = timeInterleaveModeCap;
+        mCodeRateCap = codeRateCap;
+        mFecCap = fecCap;
+        mDemodOutputFormatCap = demodOutputFormatCap;
+    }
+
+    /**
+     * Gets bandwidth capability.
+     */
+    @Atsc3FrontendSettings.Bandwidth
+    public int getBandwidthCapability() {
+        return mBandwidthCap;
+    }
+    /**
+     * Gets modulation capability.
+     */
+    @Atsc3FrontendSettings.Modulation
+    public int getModulationCapability() {
+        return mModulationCap;
+    }
+    /**
+     * Gets time interleave mod capability.
+     */
+    @Atsc3FrontendSettings.TimeInterleaveMode
+    public int getTimeInterleaveModeCapability() {
+        return mTimeInterleaveModeCap;
+    }
+    /**
+     * Gets code rate capability.
+     */
+    @Atsc3FrontendSettings.CodeRate
+    public int getPlpCodeRateCapability() {
+        return mCodeRateCap;
+    }
+    /**
+     * Gets FEC capability.
+     */
+    @Atsc3FrontendSettings.Fec
+    public int getFecCapability() {
+        return mFecCap;
+    }
+    /**
+     * Gets demodulator output format capability.
+     */
+    @Atsc3FrontendSettings.DemodOutputFormat
+    public int getDemodOutputFormatCapability() {
+        return mDemodOutputFormatCap;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java
index bce8a64..85f3f72 100644
--- a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java
@@ -16,27 +16,365 @@
 
 package android.media.tv.tuner.frontend;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
 
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
-
-import java.util.List;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * Frontend settings for ATSC-3.
+ *
  * @hide
  */
+@SystemApi
 public class Atsc3FrontendSettings extends FrontendSettings {
-    public int bandwidth;
-    public byte demodOutputFormat;
-    public List<Atsc3PlpSettings> plpSettings;
 
-    Atsc3FrontendSettings(int frequency) {
+    /** @hide */
+    @IntDef(flag = true,
+            prefix = "BANDWIDTH_",
+            value = {BANDWIDTH_UNDEFINED, BANDWIDTH_AUTO, BANDWIDTH_BANDWIDTH_6MHZ,
+                    BANDWIDTH_BANDWIDTH_7MHZ, BANDWIDTH_BANDWIDTH_8MHZ})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Bandwidth {}
+
+    /**
+     * Bandwidth not defined.
+     */
+    public static final int BANDWIDTH_UNDEFINED =
+            Constants.FrontendAtsc3Bandwidth.UNDEFINED;
+    /**
+     * Hardware is able to detect and set bandwidth automatically
+     */
+    public static final int BANDWIDTH_AUTO = Constants.FrontendAtsc3Bandwidth.AUTO;
+    /**
+     * 6 MHz bandwidth.
+     */
+    public static final int BANDWIDTH_BANDWIDTH_6MHZ =
+            Constants.FrontendAtsc3Bandwidth.BANDWIDTH_6MHZ;
+    /**
+     * 7 MHz bandwidth.
+     */
+    public static final int BANDWIDTH_BANDWIDTH_7MHZ =
+            Constants.FrontendAtsc3Bandwidth.BANDWIDTH_7MHZ;
+    /**
+     * 8 MHz bandwidth.
+     */
+    public static final int BANDWIDTH_BANDWIDTH_8MHZ =
+            Constants.FrontendAtsc3Bandwidth.BANDWIDTH_8MHZ;
+
+
+    /** @hide */
+    @IntDef(flag = true,
+            prefix = "MODULATION_",
+            value = {MODULATION_UNDEFINED, MODULATION_AUTO,
+                    MODULATION_MOD_QPSK, MODULATION_MOD_16QAM,
+                    MODULATION_MOD_64QAM, MODULATION_MOD_256QAM,
+                    MODULATION_MOD_1024QAM, MODULATION_MOD_4096QAM})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Modulation {}
+
+    /**
+     * Modulation undefined.
+     */
+    public static final int MODULATION_UNDEFINED = Constants.FrontendAtsc3Modulation.UNDEFINED;
+    /**
+     * Hardware is able to detect and set modulation automatically.
+     */
+    public static final int MODULATION_AUTO = Constants.FrontendAtsc3Modulation.AUTO;
+    /**
+     * QPSK modulation.
+     */
+    public static final int MODULATION_MOD_QPSK = Constants.FrontendAtsc3Modulation.MOD_QPSK;
+    /**
+     * 16QAM modulation.
+     */
+    public static final int MODULATION_MOD_16QAM = Constants.FrontendAtsc3Modulation.MOD_16QAM;
+    /**
+     * 64QAM modulation.
+     */
+    public static final int MODULATION_MOD_64QAM = Constants.FrontendAtsc3Modulation.MOD_64QAM;
+    /**
+     * 256QAM modulation.
+     */
+    public static final int MODULATION_MOD_256QAM = Constants.FrontendAtsc3Modulation.MOD_256QAM;
+    /**
+     * 1024QAM modulation.
+     */
+    public static final int MODULATION_MOD_1024QAM = Constants.FrontendAtsc3Modulation.MOD_1024QAM;
+    /**
+     * 4096QAM modulation.
+     */
+    public static final int MODULATION_MOD_4096QAM = Constants.FrontendAtsc3Modulation.MOD_4096QAM;
+
+
+    /** @hide */
+    @IntDef(flag = true,
+            prefix = "TIME_INTERLEAVE_MODE_",
+            value = {TIME_INTERLEAVE_MODE_UNDEFINED, TIME_INTERLEAVE_MODE_AUTO,
+                    TIME_INTERLEAVE_MODE_CTI, TIME_INTERLEAVE_MODE_HTI})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TimeInterleaveMode {}
+
+    /**
+     * Time interleave mode undefined.
+     */
+    public static final int TIME_INTERLEAVE_MODE_UNDEFINED =
+            Constants.FrontendAtsc3TimeInterleaveMode.UNDEFINED;
+    /**
+     * Hardware is able to detect and set Time Interleave Mode automatically.
+     */
+    public static final int TIME_INTERLEAVE_MODE_AUTO =
+            Constants.FrontendAtsc3TimeInterleaveMode.AUTO;
+    /**
+     * CTI Time Interleave Mode.
+     */
+    public static final int TIME_INTERLEAVE_MODE_CTI =
+            Constants.FrontendAtsc3TimeInterleaveMode.CTI;
+    /**
+     * HTI Time Interleave Mode.
+     */
+    public static final int TIME_INTERLEAVE_MODE_HTI =
+            Constants.FrontendAtsc3TimeInterleaveMode.HTI;
+
+
+    /** @hide */
+    @IntDef(flag = true,
+            prefix = "CODERATE_",
+            value = {CODERATE_UNDEFINED, CODERATE_AUTO, CODERATE_2_15, CODERATE_3_15, CODERATE_4_15,
+                    CODERATE_5_15, CODERATE_6_15, CODERATE_7_15, CODERATE_8_15, CODERATE_9_15,
+                    CODERATE_10_15, CODERATE_11_15, CODERATE_12_15, CODERATE_13_15})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CodeRate {}
+
+    /**
+     * Code rate undefined.
+     */
+    public static final int CODERATE_UNDEFINED = Constants.FrontendAtsc3CodeRate.UNDEFINED;
+    /**
+     * Hardware is able to detect and set code rate automatically
+     */
+    public static final int CODERATE_AUTO = Constants.FrontendAtsc3CodeRate.AUTO;
+    /**
+     * 2/15 code rate.
+     */
+    public static final int CODERATE_2_15 = Constants.FrontendAtsc3CodeRate.CODERATE_2_15;
+    /**
+     * 3/15 code rate.
+     */
+    public static final int CODERATE_3_15 = Constants.FrontendAtsc3CodeRate.CODERATE_3_15;
+    /**
+     * 4/15 code rate.
+     */
+    public static final int CODERATE_4_15 = Constants.FrontendAtsc3CodeRate.CODERATE_4_15;
+    /**
+     * 5/15 code rate.
+     */
+    public static final int CODERATE_5_15 = Constants.FrontendAtsc3CodeRate.CODERATE_5_15;
+    /**
+     * 6/15 code rate.
+     */
+    public static final int CODERATE_6_15 = Constants.FrontendAtsc3CodeRate.CODERATE_6_15;
+    /**
+     * 7/15 code rate.
+     */
+    public static final int CODERATE_7_15 = Constants.FrontendAtsc3CodeRate.CODERATE_7_15;
+    /**
+     * 8/15 code rate.
+     */
+    public static final int CODERATE_8_15 = Constants.FrontendAtsc3CodeRate.CODERATE_8_15;
+    /**
+     * 9/15 code rate.
+     */
+    public static final int CODERATE_9_15 = Constants.FrontendAtsc3CodeRate.CODERATE_9_15;
+    /**
+     * 10/15 code rate.
+     */
+    public static final int CODERATE_10_15 = Constants.FrontendAtsc3CodeRate.CODERATE_10_15;
+    /**
+     * 11/15 code rate.
+     */
+    public static final int CODERATE_11_15 = Constants.FrontendAtsc3CodeRate.CODERATE_11_15;
+    /**
+     * 12/15 code rate.
+     */
+    public static final int CODERATE_12_15 = Constants.FrontendAtsc3CodeRate.CODERATE_12_15;
+    /**
+     * 13/15 code rate.
+     */
+    public static final int CODERATE_13_15 = Constants.FrontendAtsc3CodeRate.CODERATE_13_15;
+
+
+    /** @hide */
+    @IntDef(flag = true,
+            prefix = "FEC_",
+            value = {FEC_UNDEFINED, FEC_AUTO, FEC_BCH_LDPC_16K, FEC_BCH_LDPC_64K, FEC_CRC_LDPC_16K,
+                    FEC_CRC_LDPC_64K, FEC_LDPC_16K, FEC_LDPC_64K})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Fec {}
+
+    /**
+     * Forward Error Correction undefined.
+     */
+    public static final int FEC_UNDEFINED = Constants.FrontendAtsc3Fec.UNDEFINED;
+    /**
+     * Hardware is able to detect and set FEC automatically
+     */
+    public static final int FEC_AUTO = Constants.FrontendAtsc3Fec.AUTO;
+    /**
+     * BCH LDPC 16K Forward Error Correction
+     */
+    public static final int FEC_BCH_LDPC_16K = Constants.FrontendAtsc3Fec.BCH_LDPC_16K;
+    /**
+     * BCH LDPC 64K Forward Error Correction
+     */
+    public static final int FEC_BCH_LDPC_64K = Constants.FrontendAtsc3Fec.BCH_LDPC_64K;
+    /**
+     * CRC LDPC 16K Forward Error Correction
+     */
+    public static final int FEC_CRC_LDPC_16K = Constants.FrontendAtsc3Fec.CRC_LDPC_16K;
+    /**
+     * CRC LDPC 64K Forward Error Correction
+     */
+    public static final int FEC_CRC_LDPC_64K = Constants.FrontendAtsc3Fec.CRC_LDPC_64K;
+    /**
+     * LDPC 16K Forward Error Correction
+     */
+    public static final int FEC_LDPC_16K = Constants.FrontendAtsc3Fec.LDPC_16K;
+    /**
+     * LDPC 64K Forward Error Correction
+     */
+    public static final int FEC_LDPC_64K = Constants.FrontendAtsc3Fec.LDPC_64K;
+
+
+    /** @hide */
+    @IntDef(flag = true,
+            prefix = "DEMOD_OUTPUT_FORMAT_",
+            value = {DEMOD_OUTPUT_FORMAT_UNDEFINED, DEMOD_OUTPUT_FORMAT_ATSC3_LINKLAYER_PACKET,
+                    DEMOD_OUTPUT_FORMAT_BASEBAND_PACKET})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DemodOutputFormat {}
+
+    /**
+     * Demod output format undefined.
+     */
+    public static final int DEMOD_OUTPUT_FORMAT_UNDEFINED =
+            Constants.FrontendAtsc3DemodOutputFormat.UNDEFINED;
+    /**
+     * ALP format. Typically used in US region.
+     */
+    public static final int DEMOD_OUTPUT_FORMAT_ATSC3_LINKLAYER_PACKET =
+            Constants.FrontendAtsc3DemodOutputFormat.ATSC3_LINKLAYER_PACKET;
+    /**
+     * BaseBand packet format. Typically used in Korea region.
+     */
+    public static final int DEMOD_OUTPUT_FORMAT_BASEBAND_PACKET =
+            Constants.FrontendAtsc3DemodOutputFormat.BASEBAND_PACKET;
+
+    private final int mBandwidth;
+    private final int mDemodOutputFormat;
+    private final Atsc3PlpSettings[] mPlpSettings;
+
+    private Atsc3FrontendSettings(int frequency, int bandwidth, int demodOutputFormat,
+            Atsc3PlpSettings[] plpSettings) {
         super(frequency);
+        mBandwidth = bandwidth;
+        mDemodOutputFormat = demodOutputFormat;
+        mPlpSettings = plpSettings;
+    }
+
+    /**
+     * Gets bandwidth.
+     */
+    @Bandwidth
+    public int getBandwidth() {
+        return mBandwidth;
+    }
+    /**
+     * Gets Demod Output Format.
+     */
+    @DemodOutputFormat
+    public int getDemodOutputFormat() {
+        return mDemodOutputFormat;
+    }
+    /**
+     * Gets PLP Settings.
+     */
+    @NonNull
+    public Atsc3PlpSettings[] getPlpSettings() {
+        return mPlpSettings;
+    }
+
+    /**
+     * Creates a builder for {@link Atsc3FrontendSettings}.
+     *
+     * @param context the context of the caller.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(@NonNull Context context) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder();
+    }
+
+    /**
+     * Builder for {@link Atsc3FrontendSettings}.
+     */
+    public static class Builder extends FrontendSettings.Builder<Builder> {
+        private int mBandwidth;
+        private byte mDemodOutputFormat;
+        private Atsc3PlpSettings[] mPlpSettings;
+
+        private Builder() {
+        }
+
+        /**
+         * Sets bandwidth.
+         */
+        @NonNull
+        public Builder setBandwidth(int bandwidth) {
+            mBandwidth = bandwidth;
+            return this;
+        }
+        /**
+         * Sets Demod Output Format.
+         */
+        @NonNull
+        public Builder setDemodOutputFormat(byte demodOutputFormat) {
+            mDemodOutputFormat = demodOutputFormat;
+            return this;
+        }
+        /**
+         * Sets PLP Settings.
+         */
+        @NonNull
+        public Builder setPlpSettings(@NonNull Atsc3PlpSettings[] plpSettings) {
+            mPlpSettings = plpSettings;
+            return this;
+        }
+
+        /**
+         * Builds a {@link Atsc3FrontendSettings} object.
+         */
+        @NonNull
+        public Atsc3FrontendSettings build() {
+            return new Atsc3FrontendSettings(
+                mFrequency, mBandwidth, mDemodOutputFormat, mPlpSettings);
+        }
+
+        @Override
+        Builder self() {
+            return this;
+        }
     }
 
     @Override
     public int getType() {
-        return TunerConstants.FRONTEND_TYPE_ATSC3;
+        return FrontendSettings.TYPE_ATSC3;
     }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/Atsc3PlpInfo.java b/media/java/android/media/tv/tuner/frontend/Atsc3PlpInfo.java
new file mode 100644
index 0000000..9900fec
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/Atsc3PlpInfo.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+import android.annotation.SystemApi;
+
+/** PLP information for ATSC3.
+ * @hide
+ */
+@SystemApi
+public class Atsc3PlpInfo {
+    private final int mPlpId;
+    private final boolean mLlsFlag;
+
+    private Atsc3PlpInfo(int plpId, boolean llsFlag) {
+        mPlpId = plpId;
+        mLlsFlag = llsFlag;
+    }
+
+    /** Gets PLP IDs. */
+    public int getPlpId() {
+        return mPlpId;
+    }
+
+    /** Gets LLS flag. */
+    public boolean getLlsFlag() {
+        return mLlsFlag;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/Atsc3PlpSettings.java b/media/java/android/media/tv/tuner/frontend/Atsc3PlpSettings.java
index 61c6fec..fe61dbc 100644
--- a/media/java/android/media/tv/tuner/frontend/Atsc3PlpSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/Atsc3PlpSettings.java
@@ -16,14 +16,141 @@
 
 package android.media.tv.tuner.frontend;
 
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+
 /**
- * PLP settings for ATSC-3.
+ * Physical Layer Pipe (PLP) settings for ATSC-3.
+ *
  * @hide
  */
+@SystemApi
 public class Atsc3PlpSettings {
-    public byte plpId;
-    public int modulation;
-    public int interleaveMode;
-    public int codeRate;
-    public int fec;
+    private final int mPlpId;
+    private final int mModulation;
+    private final int mInterleaveMode;
+    private final int mCodeRate;
+    private final int mFec;
+
+    private Atsc3PlpSettings(int plpId, int modulation, int interleaveMode, int codeRate, int fec) {
+        mPlpId = plpId;
+        mModulation = modulation;
+        mInterleaveMode = interleaveMode;
+        mCodeRate = codeRate;
+        mFec = fec;
+    }
+
+    /**
+     * Gets Physical Layer Pipe (PLP) ID.
+     */
+    public int getPlpId() {
+        return mPlpId;
+    }
+    /**
+     * Gets Modulation.
+     */
+    @Atsc3FrontendSettings.Modulation
+    public int getModulation() {
+        return mModulation;
+    }
+    /**
+     * Gets Interleave Mode.
+     */
+    @Atsc3FrontendSettings.TimeInterleaveMode
+    public int getInterleaveMode() {
+        return mInterleaveMode;
+    }
+    /**
+     * Gets Code Rate.
+     */
+    @Atsc3FrontendSettings.CodeRate
+    public int getCodeRate() {
+        return mCodeRate;
+    }
+    /**
+     * Gets Forward Error Correction.
+     */
+    @Atsc3FrontendSettings.Fec
+    public int getFec() {
+        return mFec;
+    }
+
+    /**
+     * Creates a builder for {@link Atsc3PlpSettings}.
+     *
+     * @param context the context of the caller.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(@NonNull Context context) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder();
+    }
+
+    /**
+     * Builder for {@link Atsc3PlpSettings}.
+     */
+    public static class Builder {
+        private int mPlpId;
+        private int mModulation;
+        private int mInterleaveMode;
+        private int mCodeRate;
+        private int mFec;
+
+        private Builder() {
+        }
+
+        /**
+         * Sets Physical Layer Pipe (PLP) ID.
+         */
+        @NonNull
+        public Builder setPlpId(int plpId) {
+            mPlpId = plpId;
+            return this;
+        }
+        /**
+         * Sets Modulation.
+         */
+        @NonNull
+        public Builder setModulation(@Atsc3FrontendSettings.Modulation int modulation) {
+            mModulation = modulation;
+            return this;
+        }
+        /**
+         * Sets Interleave Mode.
+         */
+        @NonNull
+        public Builder setInterleaveMode(
+                @Atsc3FrontendSettings.TimeInterleaveMode int interleaveMode) {
+            mInterleaveMode = interleaveMode;
+            return this;
+        }
+        /**
+         * Sets Code Rate.
+         */
+        @NonNull
+        public Builder setCodeRate(@Atsc3FrontendSettings.CodeRate int codeRate) {
+            mCodeRate = codeRate;
+            return this;
+        }
+        /**
+         * Sets Forward Error Correction.
+         */
+        @NonNull
+        public Builder setFec(@Atsc3FrontendSettings.Fec int fec) {
+            mFec = fec;
+            return this;
+        }
+
+        /**
+         * Builds a {@link Atsc3PlpSettings} object.
+         */
+        @NonNull
+        public Atsc3PlpSettings build() {
+            return new Atsc3PlpSettings(mPlpId, mModulation, mInterleaveMode, mCodeRate, mFec);
+        }
+    }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/AtscFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/AtscFrontendCapabilities.java
new file mode 100644
index 0000000..eb21caa
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/AtscFrontendCapabilities.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 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.media.tv.tuner.frontend;
+
+import android.annotation.SystemApi;
+
+/**
+ * ATSC Capabilities.
+ *
+ * @hide
+ */
+@SystemApi
+public class AtscFrontendCapabilities extends FrontendCapabilities {
+    private final int mModulationCap;
+
+    private AtscFrontendCapabilities(int modulationCap) {
+        mModulationCap = modulationCap;
+    }
+
+    /**
+     * Gets modulation capability.
+     */
+    @AtscFrontendSettings.Modulation
+    public int getModulationCapability() {
+        return mModulationCap;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java
index 14c5cdd..fc82a1c 100644
--- a/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java
@@ -16,22 +16,112 @@
 
 package android.media.tv.tuner.frontend;
 
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * Frontend settings for ATSC.
+ *
  * @hide
  */
+@SystemApi
 public class AtscFrontendSettings extends FrontendSettings {
-    public int modulation;
 
-    AtscFrontendSettings(int frequency) {
+    /** @hide */
+    @IntDef(flag = true,
+            prefix = "MODULATION_",
+            value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_8VSB,
+                    MODULATION_MOD_16VSB})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Modulation {}
+
+    /**
+     * Modulation undefined.
+     */
+    public static final int MODULATION_UNDEFINED = Constants.FrontendAtscModulation.UNDEFINED;
+    /**
+     * Hardware is able to detect and set modulation automatically
+     */
+    public static final int MODULATION_AUTO = Constants.FrontendAtscModulation.AUTO;
+    /**
+     * 8VSB Modulation.
+     */
+    public static final int MODULATION_MOD_8VSB = Constants.FrontendAtscModulation.MOD_8VSB;
+    /**
+     * 16VSB Modulation.
+     */
+    public static final int MODULATION_MOD_16VSB = Constants.FrontendAtscModulation.MOD_16VSB;
+
+
+    private final int mModulation;
+
+    private AtscFrontendSettings(int frequency, int modulation) {
         super(frequency);
+        mModulation = modulation;
+    }
+
+    /**
+     * Gets Modulation.
+     */
+    @Modulation
+    public int getModulation() {
+        return mModulation;
+    }
+
+    /**
+     * Creates a builder for {@link AtscFrontendSettings}.
+     *
+     * @param context the context of the caller.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(@NonNull Context context) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder();
+    }
+
+    /**
+     * Builder for {@link AtscFrontendSettings}.
+     */
+    public static class Builder extends FrontendSettings.Builder<Builder> {
+        private int mModulation;
+
+        private Builder() {
+        }
+
+        /**
+         * Sets Modulation.
+         */
+        @NonNull
+        public Builder setModulation(@Modulation int modulation) {
+            mModulation = modulation;
+            return this;
+        }
+
+        /**
+         * Builds a {@link AtscFrontendSettings} object.
+         */
+        @NonNull
+        public AtscFrontendSettings build() {
+            return new AtscFrontendSettings(mFrequency, mModulation);
+        }
+
+        @Override
+        Builder self() {
+            return this;
+        }
     }
 
     @Override
     public int getType() {
-        return TunerConstants.FRONTEND_TYPE_ATSC;
+        return FrontendSettings.TYPE_ATSC;
     }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/DvbcFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/DvbcFrontendCapabilities.java
new file mode 100644
index 0000000..faa5434
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/DvbcFrontendCapabilities.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 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.media.tv.tuner.frontend;
+
+import android.annotation.SystemApi;
+
+/**
+ * DVBC Capabilities.
+ *
+ * @hide
+ */
+@SystemApi
+public class DvbcFrontendCapabilities extends FrontendCapabilities {
+    private final int mModulationCap;
+    private final int mFecCap;
+    private final int mAnnexCap;
+
+    private DvbcFrontendCapabilities(int modulationCap, int fecCap, int annexCap) {
+        mModulationCap = modulationCap;
+        mFecCap = fecCap;
+        mAnnexCap = annexCap;
+    }
+
+    /**
+     * Gets modulation capability.
+     */
+    @DvbcFrontendSettings.Modulation
+    public int getModulationCapability() {
+        return mModulationCap;
+    }
+    /**
+     * Gets inner FEC capability.
+     */
+    @FrontendSettings.InnerFec
+    public int getFecCapability() {
+        return mFecCap;
+    }
+    /**
+     * Gets annex capability.
+     */
+    @DvbcFrontendSettings.Annex
+    public int getAnnexCapability() {
+        return mAnnexCap;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java
index 07e49ff..bfa4f3f 100644
--- a/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java
@@ -16,27 +16,285 @@
 
 package android.media.tv.tuner.frontend;
 
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * Frontend settings for DVBC.
+ *
  * @hide
  */
+@SystemApi
 public class DvbcFrontendSettings extends FrontendSettings {
-    public int modulation;
-    public long fec;
-    public int symbolRate;
-    public int outerFec;
-    public byte annex;
-    public int spectralInversion;
 
-    DvbcFrontendSettings(int frequency) {
+    /** @hide */
+    @IntDef(flag = true,
+            prefix = "MODULATION_",
+            value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_16QAM,
+                    MODULATION_MOD_32QAM, MODULATION_MOD_64QAM, MODULATION_MOD_128QAM,
+                    MODULATION_MOD_256QAM})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Modulation {}
+
+    /**
+     * Modulation undefined.
+     */
+    public static final int MODULATION_UNDEFINED = Constants.FrontendDvbcModulation.UNDEFINED;
+    /**
+     * Hardware is able to detect and set modulation automatically
+     */
+    public static final int MODULATION_AUTO = Constants.FrontendDvbcModulation.AUTO;
+    /**
+     * 16QAM Modulation.
+     */
+    public static final int MODULATION_MOD_16QAM = Constants.FrontendDvbcModulation.MOD_16QAM;
+    /**
+     * 32QAM Modulation.
+     */
+    public static final int MODULATION_MOD_32QAM = Constants.FrontendDvbcModulation.MOD_32QAM;
+    /**
+     * 64QAM Modulation.
+     */
+    public static final int MODULATION_MOD_64QAM = Constants.FrontendDvbcModulation.MOD_64QAM;
+    /**
+     * 128QAM Modulation.
+     */
+    public static final int MODULATION_MOD_128QAM = Constants.FrontendDvbcModulation.MOD_128QAM;
+    /**
+     * 256QAM Modulation.
+     */
+    public static final int MODULATION_MOD_256QAM = Constants.FrontendDvbcModulation.MOD_256QAM;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "OUTER_FEC_",
+            value = {OUTER_FEC_UNDEFINED, OUTER_FEC_OUTER_FEC_NONE, OUTER_FEC_OUTER_FEC_RS})
+    public @interface OuterFec {}
+
+    /**
+     * Outer Forward Error Correction (FEC) Type undefined.
+     */
+    public static final int OUTER_FEC_UNDEFINED = Constants.FrontendDvbcOuterFec.UNDEFINED;
+    /**
+     * None Outer Forward Error Correction (FEC) Type.
+     */
+    public static final int OUTER_FEC_OUTER_FEC_NONE =
+            Constants.FrontendDvbcOuterFec.OUTER_FEC_NONE;
+    /**
+     * RS Outer Forward Error Correction (FEC) Type.
+     */
+    public static final int OUTER_FEC_OUTER_FEC_RS = Constants.FrontendDvbcOuterFec.OUTER_FEC_RS;
+
+
+    /** @hide */
+    @IntDef(flag = true,
+            prefix = "ANNEX_",
+            value = {ANNEX_UNDEFINED, ANNEX_A, ANNEX_B, ANNEX_C})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Annex {}
+
+    /**
+     * Annex Type undefined.
+     */
+    public static final int ANNEX_UNDEFINED = Constants.FrontendDvbcAnnex.UNDEFINED;
+    /**
+     * Annex Type A.
+     */
+    public static final int ANNEX_A = Constants.FrontendDvbcAnnex.A;
+    /**
+     * Annex Type B.
+     */
+    public static final int ANNEX_B = Constants.FrontendDvbcAnnex.B;
+    /**
+     * Annex Type C.
+     */
+    public static final int ANNEX_C = Constants.FrontendDvbcAnnex.C;
+
+
+    /** @hide */
+    @IntDef(prefix = "SPECTRAL_INVERSION_",
+            value = {SPECTRAL_INVERSION_UNDEFINED, SPECTRAL_INVERSION_NORMAL,
+                    SPECTRAL_INVERSION_INVERTED})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SpectralInversion {}
+
+    /**
+     * Spectral Inversion Type undefined.
+     */
+    public static final int SPECTRAL_INVERSION_UNDEFINED =
+            Constants.FrontendDvbcSpectralInversion.UNDEFINED;
+    /**
+     * Normal Spectral Inversion.
+     */
+    public static final int SPECTRAL_INVERSION_NORMAL =
+            Constants.FrontendDvbcSpectralInversion.NORMAL;
+    /**
+     * Inverted Spectral Inversion.
+     */
+    public static final int SPECTRAL_INVERSION_INVERTED =
+            Constants.FrontendDvbcSpectralInversion.INVERTED;
+
+
+    private final int mModulation;
+    private final long mFec;
+    private final int mSymbolRate;
+    private final int mOuterFec;
+    private final byte mAnnex;
+    private final int mSpectralInversion;
+
+    private DvbcFrontendSettings(int frequency, int modulation, long fec, int symbolRate,
+            int outerFec, byte annex, int spectralInversion) {
         super(frequency);
+        mModulation = modulation;
+        mFec = fec;
+        mSymbolRate = symbolRate;
+        mOuterFec = outerFec;
+        mAnnex = annex;
+        mSpectralInversion = spectralInversion;
+    }
+
+    /**
+     * Gets Modulation.
+     */
+    @Modulation
+    public int getModulation() {
+        return mModulation;
+    }
+    /**
+     * Gets Inner Forward Error Correction.
+     */
+    @InnerFec
+    public long getFec() {
+        return mFec;
+    }
+    /**
+     * Gets Symbol Rate in symbols per second.
+     */
+    public int getSymbolRate() {
+        return mSymbolRate;
+    }
+    /**
+     * Gets Outer Forward Error Correction.
+     */
+    @OuterFec
+    public int getOuterFec() {
+        return mOuterFec;
+    }
+    /**
+     * Gets Annex.
+     */
+    @Annex
+    public byte getAnnex() {
+        return mAnnex;
+    }
+    /**
+     * Gets Spectral Inversion.
+     */
+    @SpectralInversion
+    public int getSpectralInversion() {
+        return mSpectralInversion;
+    }
+
+    /**
+     * Creates a builder for {@link DvbcFrontendSettings}.
+     *
+     * @param context the context of the caller.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(@NonNull Context context) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder();
+    }
+
+    /**
+     * Builder for {@link DvbcFrontendSettings}.
+     */
+    public static class Builder extends FrontendSettings.Builder<Builder> {
+        private int mModulation;
+        private long mFec;
+        private int mSymbolRate;
+        private int mOuterFec;
+        private byte mAnnex;
+        private int mSpectralInversion;
+
+        private Builder() {
+        }
+
+        /**
+         * Sets Modulation.
+         */
+        @NonNull
+        public Builder setModulation(@Modulation int modulation) {
+            mModulation = modulation;
+            return this;
+        }
+        /**
+         * Sets Inner Forward Error Correction.
+         */
+        @NonNull
+        public Builder setFec(@InnerFec long fec) {
+            mFec = fec;
+            return this;
+        }
+        /**
+         * Sets Symbol Rate in symbols per second.
+         */
+        @NonNull
+        public Builder setSymbolRate(int symbolRate) {
+            mSymbolRate = symbolRate;
+            return this;
+        }
+        /**
+         * Sets Outer Forward Error Correction.
+         */
+        @NonNull
+        public Builder setOuterFec(@OuterFec int outerFec) {
+            mOuterFec = outerFec;
+            return this;
+        }
+        /**
+         * Sets Annex.
+         */
+        @NonNull
+        public Builder setAnnex(@Annex byte annex) {
+            mAnnex = annex;
+            return this;
+        }
+        /**
+         * Sets Spectral Inversion.
+         */
+        @NonNull
+        public Builder setSpectralInversion(@SpectralInversion int spectralInversion) {
+            mSpectralInversion = spectralInversion;
+            return this;
+        }
+
+        /**
+         * Builds a {@link DvbcFrontendSettings} object.
+         */
+        @NonNull
+        public DvbcFrontendSettings build() {
+            return new DvbcFrontendSettings(mFrequency, mModulation, mFec, mSymbolRate, mOuterFec,
+                    mAnnex, mSpectralInversion);
+        }
+
+        @Override
+        Builder self() {
+            return this;
+        }
     }
 
     @Override
     public int getType() {
-        return TunerConstants.FRONTEND_TYPE_DVBC;
+        return FrontendSettings.TYPE_DVBC;
     }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/DvbsCodeRate.java b/media/java/android/media/tv/tuner/frontend/DvbsCodeRate.java
index bfa4391..2bdd379 100644
--- a/media/java/android/media/tv/tuner/frontend/DvbsCodeRate.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbsCodeRate.java
@@ -16,13 +16,120 @@
 
 package android.media.tv.tuner.frontend;
 
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+
 /**
  * Code rate for DVBS.
+ *
  * @hide
  */
+@SystemApi
 public class DvbsCodeRate {
-    public long fec;
-    public boolean isLinear;
-    public boolean isShortFrames;
-    public int bitsPer1000Symbol;
+    private final long mInnerFec;
+    private final boolean mIsLinear;
+    private final boolean mIsShortFrames;
+    private final int mBitsPer1000Symbol;
+
+    private DvbsCodeRate(long fec, boolean isLinear, boolean isShortFrames, int bitsPer1000Symbol) {
+        mInnerFec = fec;
+        mIsLinear = isLinear;
+        mIsShortFrames = isShortFrames;
+        mBitsPer1000Symbol = bitsPer1000Symbol;
+    }
+
+    /**
+     * Gets inner FEC.
+     */
+    @FrontendSettings.InnerFec
+    public long getInnerFec() {
+        return mInnerFec;
+    }
+    /**
+     * Checks whether it's linear.
+     */
+    public boolean isLinear() {
+        return mIsLinear;
+    }
+    /**
+     * Checks whether short frame enabled.
+     */
+    public boolean isShortFrameEnabled() {
+        return mIsShortFrames;
+    }
+    /**
+     * Gets bits number in 1000 symbols. 0 by default.
+     */
+    public int getBitsPer1000Symbol() {
+        return mBitsPer1000Symbol;
+    }
+
+    /**
+     * Creates a builder for {@link DvbsCodeRate}.
+     *
+     * @param context the context of the caller.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(@NonNull Context context) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder();
+    }
+
+    /**
+     * Builder for {@link DvbsCodeRate}.
+     */
+    public static class Builder {
+        private long mFec;
+        private boolean mIsLinear;
+        private boolean mIsShortFrames;
+        private int mBitsPer1000Symbol;
+
+        private Builder() {
+        }
+
+        /**
+         * Sets inner FEC.
+         */
+        @NonNull
+        public Builder setInnerFec(@FrontendSettings.InnerFec long fec) {
+            mFec = fec;
+            return this;
+        }
+        /**
+         * Sets whether it's linear.
+         */
+        @NonNull
+        public Builder setLinear(boolean isLinear) {
+            mIsLinear = isLinear;
+            return this;
+        }
+        /**
+         * Sets whether short frame enabled.
+         */
+        @NonNull
+        public Builder setShortFrameEnabled(boolean isShortFrames) {
+            mIsShortFrames = isShortFrames;
+            return this;
+        }
+        /**
+         * Sets bits number in 1000 symbols.
+         */
+        @NonNull
+        public Builder setBitsPer1000Symbol(int bitsPer1000Symbol) {
+            mBitsPer1000Symbol = bitsPer1000Symbol;
+            return this;
+        }
+
+        /**
+         * Builds a {@link DvbsCodeRate} object.
+         */
+        @NonNull
+        public DvbsCodeRate build() {
+            return new DvbsCodeRate(mFec, mIsLinear, mIsShortFrames, mBitsPer1000Symbol);
+        }
+    }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/DvbsFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/DvbsFrontendCapabilities.java
new file mode 100644
index 0000000..1e25cf2
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/DvbsFrontendCapabilities.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 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.media.tv.tuner.frontend;
+
+import android.annotation.SystemApi;
+
+/**
+ * DVBS Capabilities.
+ *
+ * @hide
+ */
+@SystemApi
+public class DvbsFrontendCapabilities extends FrontendCapabilities {
+    private final int mModulationCap;
+    private final long mInnerFecCap;
+    private final int mStandard;
+
+    private DvbsFrontendCapabilities(int modulationCap, long innerFecCap, int standard) {
+        mModulationCap = modulationCap;
+        mInnerFecCap = innerFecCap;
+        mStandard = standard;
+    }
+
+    /**
+     * Gets modulation capability.
+     */
+    @DvbsFrontendSettings.Modulation
+    public int getModulationCapability() {
+        return mModulationCap;
+    }
+    /**
+     * Gets inner FEC capability.
+     */
+    @FrontendSettings.InnerFec
+    public long getInnerFecCapability() {
+        return mInnerFecCap;
+    }
+    /**
+     * Gets DVBS standard capability.
+     */
+    @DvbsFrontendSettings.Standard
+    public int getStandardCapability() {
+        return mStandard;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
index 23c0a7b1..4a4fed5 100644
--- a/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
@@ -16,28 +16,388 @@
 
 package android.media.tv.tuner.frontend;
 
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * Frontend settings for DVBS.
+ *
  * @hide
  */
+@SystemApi
 public class DvbsFrontendSettings extends FrontendSettings {
-    public int modulation;
-    public DvbsCodeRate coderate;
-    public int symbolRate;
-    public int rolloff;
-    public int pilot;
-    public int inputStreamId;
-    public byte standard;
+    /** @hide */
+    @IntDef(flag = true,
+            prefix = "MODULATION_",
+            value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_QPSK,
+                    MODULATION_MOD_8PSK, MODULATION_MOD_16QAM, MODULATION_MOD_16PSK,
+                    MODULATION_MOD_32PSK, MODULATION_MOD_ACM, MODULATION_MOD_8APSK,
+                    MODULATION_MOD_16APSK, MODULATION_MOD_32APSK, MODULATION_MOD_64APSK,
+                    MODULATION_MOD_128APSK, MODULATION_MOD_256APSK, MODULATION_MOD_RESERVED})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Modulation {}
 
-    DvbsFrontendSettings(int frequency) {
+    /**
+     * Modulation undefined.
+     */
+    public static final int MODULATION_UNDEFINED = Constants.FrontendDvbsModulation.UNDEFINED;
+    /**
+     * Hardware is able to detect and set modulation automatically
+     */
+    public static final int MODULATION_AUTO = Constants.FrontendDvbsModulation.AUTO;
+    /**
+     * QPSK Modulation.
+     */
+    public static final int MODULATION_MOD_QPSK = Constants.FrontendDvbsModulation.MOD_QPSK;
+    /**
+     * 8PSK Modulation.
+     */
+    public static final int MODULATION_MOD_8PSK = Constants.FrontendDvbsModulation.MOD_8PSK;
+    /**
+     * 16QAM Modulation.
+     */
+    public static final int MODULATION_MOD_16QAM = Constants.FrontendDvbsModulation.MOD_16QAM;
+    /**
+     * 16PSK Modulation.
+     */
+    public static final int MODULATION_MOD_16PSK = Constants.FrontendDvbsModulation.MOD_16PSK;
+    /**
+     * 32PSK Modulation.
+     */
+    public static final int MODULATION_MOD_32PSK = Constants.FrontendDvbsModulation.MOD_32PSK;
+    /**
+     * ACM Modulation.
+     */
+    public static final int MODULATION_MOD_ACM = Constants.FrontendDvbsModulation.MOD_ACM;
+    /**
+     * 8APSK Modulation.
+     */
+    public static final int MODULATION_MOD_8APSK = Constants.FrontendDvbsModulation.MOD_8APSK;
+    /**
+     * 16APSK Modulation.
+     */
+    public static final int MODULATION_MOD_16APSK = Constants.FrontendDvbsModulation.MOD_16APSK;
+    /**
+     * 32APSK Modulation.
+     */
+    public static final int MODULATION_MOD_32APSK = Constants.FrontendDvbsModulation.MOD_32APSK;
+    /**
+     * 64APSK Modulation.
+     */
+    public static final int MODULATION_MOD_64APSK = Constants.FrontendDvbsModulation.MOD_64APSK;
+    /**
+     * 128APSK Modulation.
+     */
+    public static final int MODULATION_MOD_128APSK = Constants.FrontendDvbsModulation.MOD_128APSK;
+    /**
+     * 256APSK Modulation.
+     */
+    public static final int MODULATION_MOD_256APSK = Constants.FrontendDvbsModulation.MOD_256APSK;
+    /**
+     * Reversed Modulation.
+     */
+    public static final int MODULATION_MOD_RESERVED = Constants.FrontendDvbsModulation.MOD_RESERVED;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "ROLLOFF_",
+            value = {ROLLOFF_UNDEFINED, ROLLOFF_0_35, ROLLOFF_0_25, ROLLOFF_0_20, ROLLOFF_0_15,
+                    ROLLOFF_0_10, ROLLOFF_0_5})
+    public @interface Rolloff {}
+
+    /**
+     * Rolloff range undefined.
+     */
+    public static final int ROLLOFF_UNDEFINED = Constants.FrontendDvbsRolloff.UNDEFINED;
+    /**
+     * Rolloff range 0,35.
+     */
+    public static final int ROLLOFF_0_35 = Constants.FrontendDvbsRolloff.ROLLOFF_0_35;
+    /**
+     * Rolloff range 0,25.
+     */
+    public static final int ROLLOFF_0_25 = Constants.FrontendDvbsRolloff.ROLLOFF_0_25;
+    /**
+     * Rolloff range 0,20.
+     */
+    public static final int ROLLOFF_0_20 = Constants.FrontendDvbsRolloff.ROLLOFF_0_20;
+    /**
+     * Rolloff range 0,15.
+     */
+    public static final int ROLLOFF_0_15 = Constants.FrontendDvbsRolloff.ROLLOFF_0_15;
+    /**
+     * Rolloff range 0,10.
+     */
+    public static final int ROLLOFF_0_10 = Constants.FrontendDvbsRolloff.ROLLOFF_0_10;
+    /**
+     * Rolloff range 0,5.
+     */
+    public static final int ROLLOFF_0_5 = Constants.FrontendDvbsRolloff.ROLLOFF_0_5;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "PILOT_",
+            value = {PILOT_UNDEFINED, PILOT_ON, PILOT_OFF, PILOT_AUTO})
+    public @interface Pilot {}
+
+    /**
+     * Pilot mode undefined.
+     */
+    public static final int PILOT_UNDEFINED = Constants.FrontendDvbsPilot.UNDEFINED;
+    /**
+     * Pilot mode on.
+     */
+    public static final int PILOT_ON = Constants.FrontendDvbsPilot.ON;
+    /**
+     * Pilot mode off.
+     */
+    public static final int PILOT_OFF = Constants.FrontendDvbsPilot.OFF;
+    /**
+     * Pilot mode auto.
+     */
+    public static final int PILOT_AUTO = Constants.FrontendDvbsPilot.AUTO;
+
+
+    /** @hide */
+    @IntDef(flag = true,
+            prefix = "STANDARD_",
+            value = {STANDARD_AUTO, STANDARD_S, STANDARD_S2, STANDARD_S2X})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Standard {}
+
+    /**
+     * Standard undefined.
+     */
+    public static final int STANDARD_AUTO = Constants.FrontendDvbsStandard.AUTO;
+    /**
+     * Standard S.
+     */
+    public static final int STANDARD_S = Constants.FrontendDvbsStandard.S;
+    /**
+     * Standard S2.
+     */
+    public static final int STANDARD_S2 = Constants.FrontendDvbsStandard.S2;
+    /**
+     * Standard S2X.
+     */
+    public static final int STANDARD_S2X = Constants.FrontendDvbsStandard.S2X;
+
+    /** @hide */
+    @IntDef(prefix = "VCM_MODE_",
+            value = {VCM_MODE_UNDEFINED, VCM_MODE_AUTO, VCM_MODE_MANUAL})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface VcmMode {}
+
+    /**
+     * VCM mode undefined.
+     */
+    public static final int VCM_MODE_UNDEFINED = Constants.FrontendDvbsVcmMode.UNDEFINED;
+    /**
+     * Auto VCM mode.
+     */
+    public static final int VCM_MODE_AUTO = Constants.FrontendDvbsVcmMode.AUTO;
+    /**
+     * Manual VCM mode.
+     */
+    public static final int VCM_MODE_MANUAL = Constants.FrontendDvbsVcmMode.MANUAL;
+
+
+    private final int mModulation;
+    private final DvbsCodeRate mCodeRate;
+    private final int mSymbolRate;
+    private final int mRolloff;
+    private final int mPilot;
+    private final int mInputStreamId;
+    private final int mStandard;
+    private final int mVcmMode;
+
+    private DvbsFrontendSettings(int frequency, int modulation, DvbsCodeRate codeRate,
+            int symbolRate, int rolloff, int pilot, int inputStreamId, int standard, int vcm) {
         super(frequency);
+        mModulation = modulation;
+        mCodeRate = codeRate;
+        mSymbolRate = symbolRate;
+        mRolloff = rolloff;
+        mPilot = pilot;
+        mInputStreamId = inputStreamId;
+        mStandard = standard;
+        mVcmMode = vcm;
+    }
+
+    /**
+     * Gets Modulation.
+     */
+    @Modulation
+    public int getModulation() {
+        return mModulation;
+    }
+    /**
+     * Gets Code rate.
+     */
+    @Nullable
+    public DvbsCodeRate getCodeRate() {
+        return mCodeRate;
+    }
+    /**
+     * Gets Symbol Rate in symbols per second.
+     */
+    public int getSymbolRate() {
+        return mSymbolRate;
+    }
+    /**
+     * Gets Rolloff.
+     */
+    @Rolloff
+    public int getRolloff() {
+        return mRolloff;
+    }
+    /**
+     * Gets Pilot mode.
+     */
+    @Pilot
+    public int getPilot() {
+        return mPilot;
+    }
+    /**
+     * Gets Input Stream ID.
+     */
+    public int getInputStreamId() {
+        return mInputStreamId;
+    }
+    /**
+     * Gets DVBS sub-standard.
+     */
+    @Standard
+    public int getStandard() {
+        return mStandard;
+    }
+    /**
+     * Gets VCM mode.
+     */
+    @VcmMode
+    public int getVcmMode() {
+        return mVcmMode;
+    }
+
+    /**
+     * Creates a builder for {@link DvbsFrontendSettings}.
+     *
+     * @param context the context of the caller.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(@NonNull Context context) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder();
+    }
+
+    /**
+     * Builder for {@link DvbsFrontendSettings}.
+     */
+    public static class Builder extends FrontendSettings.Builder<Builder> {
+        private int mModulation;
+        private DvbsCodeRate mCodeRate;
+        private int mSymbolRate;
+        private int mRolloff;
+        private int mPilot;
+        private int mInputStreamId;
+        private int mStandard;
+        private int mVcmMode;
+
+        private Builder() {
+        }
+
+        /**
+         * Sets Modulation.
+         */
+        @NonNull
+        public Builder setModulation(@Modulation int modulation) {
+            mModulation = modulation;
+            return this;
+        }
+        /**
+         * Sets Code rate.
+         */
+        @NonNull
+        public Builder setCodeRate(@Nullable DvbsCodeRate codeRate) {
+            mCodeRate = codeRate;
+            return this;
+        }
+        /**
+         * Sets Symbol Rate.
+         */
+        @NonNull
+        public Builder setSymbolRate(int symbolRate) {
+            mSymbolRate = symbolRate;
+            return this;
+        }
+        /**
+         * Sets Rolloff.
+         */
+        @NonNull
+        public Builder setRolloff(@Rolloff int rolloff) {
+            mRolloff = rolloff;
+            return this;
+        }
+        /**
+         * Sets Pilot mode.
+         */
+        @NonNull
+        public Builder setPilot(@Pilot int pilot) {
+            mPilot = pilot;
+            return this;
+        }
+        /**
+         * Sets Input Stream ID.
+         */
+        @NonNull
+        public Builder setInputStreamId(int inputStreamId) {
+            mInputStreamId = inputStreamId;
+            return this;
+        }
+        /**
+         * Sets Standard.
+         */
+        @NonNull
+        public Builder setStandard(@Standard int standard) {
+            mStandard = standard;
+            return this;
+        }
+        /**
+         * Sets VCM mode.
+         */
+        @NonNull
+        public Builder setVcmMode(@VcmMode int vcm) {
+            mVcmMode = vcm;
+            return this;
+        }
+
+        /**
+         * Builds a {@link DvbsFrontendSettings} object.
+         */
+        @NonNull
+        public DvbsFrontendSettings build() {
+            return new DvbsFrontendSettings(mFrequency, mModulation, mCodeRate, mSymbolRate,
+                    mRolloff, mPilot, mInputStreamId, mStandard, mVcmMode);
+        }
+
+        @Override
+        Builder self() {
+            return this;
+        }
     }
 
     @Override
     public int getType() {
-        return TunerConstants.FRONTEND_TYPE_DVBS;
+        return FrontendSettings.TYPE_DVBS;
     }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/DvbtFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/DvbtFrontendCapabilities.java
new file mode 100644
index 0000000..524952d
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/DvbtFrontendCapabilities.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 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.media.tv.tuner.frontend;
+
+import android.annotation.SystemApi;
+
+/**
+ * DVBT Capabilities.
+ *
+ * @hide
+ */
+@SystemApi
+public class DvbtFrontendCapabilities extends FrontendCapabilities {
+    private final int mTransmissionModeCap;
+    private final int mBandwidthCap;
+    private final int mConstellationCap;
+    private final int mCodeRateCap;
+    private final int mHierarchyCap;
+    private final int mGuardIntervalCap;
+    private final boolean mIsT2Supported;
+    private final boolean mIsMisoSupported;
+
+    private DvbtFrontendCapabilities(int transmissionModeCap, int bandwidthCap,
+            int constellationCap, int codeRateCap, int hierarchyCap, int guardIntervalCap,
+            boolean isT2Supported, boolean isMisoSupported) {
+        mTransmissionModeCap = transmissionModeCap;
+        mBandwidthCap = bandwidthCap;
+        mConstellationCap = constellationCap;
+        mCodeRateCap = codeRateCap;
+        mHierarchyCap = hierarchyCap;
+        mGuardIntervalCap = guardIntervalCap;
+        mIsT2Supported = isT2Supported;
+        mIsMisoSupported = isMisoSupported;
+    }
+
+    /**
+     * Gets transmission mode capability.
+     */
+    @DvbtFrontendSettings.TransmissionMode
+    public int getTransmissionModeCapability() {
+        return mTransmissionModeCap;
+    }
+    /**
+     * Gets bandwidth capability.
+     */
+    @DvbtFrontendSettings.Bandwidth
+    public int getBandwidthCapability() {
+        return mBandwidthCap;
+    }
+    /**
+     * Gets constellation capability.
+     */
+    @DvbtFrontendSettings.Constellation
+    public int getConstellationCapability() {
+        return mConstellationCap;
+    }
+    /**
+     * Gets code rate capability.
+     */
+    @DvbtFrontendSettings.CodeRate
+    public int getCodeRateCapability() {
+        return mCodeRateCap;
+    }
+    /**
+     * Gets hierarchy capability.
+     */
+    @DvbtFrontendSettings.Hierarchy
+    public int getHierarchyCapability() {
+        return mHierarchyCap;
+    }
+    /**
+     * Gets guard interval capability.
+     */
+    @DvbtFrontendSettings.GuardInterval
+    public int getGuardIntervalCapability() {
+        return mGuardIntervalCap;
+    }
+    /**
+     * Returns whether T2 is supported.
+     */
+    public boolean isT2Supported() {
+        return mIsT2Supported;
+    }
+    /**
+     * Returns whether MISO is supported.
+     */
+    public boolean isMisoSupported() {
+        return mIsMisoSupported;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java
index eec00f3..e99fd36f 100644
--- a/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java
@@ -16,35 +16,637 @@
 
 package android.media.tv.tuner.frontend;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
 
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * Frontend settings for DVBT.
+ *
  * @hide
  */
+@SystemApi
 public class DvbtFrontendSettings extends FrontendSettings {
-    public int transmissionMode;
-    public int bandwidth;
-    public int constellation;
-    public int hierarchy;
-    public int hpCoderate;
-    public int lpCoderate;
-    public int guardInterval;
-    public boolean isHighPriority;
-    public byte standard;
-    public boolean isMiso;
-    public int plpMode;
-    public byte plpId;
-    public byte plpGroupId;
 
-    DvbtFrontendSettings(int frequency) {
+    /** @hide */
+    @IntDef(flag = true,
+            prefix = "TRANSMISSION_MODE_",
+            value = {TRANSMISSION_MODE_UNDEFINED, TRANSMISSION_MODE_AUTO,
+                    TRANSMISSION_MODE_2K, TRANSMISSION_MODE_8K, TRANSMISSION_MODE_4K,
+                    TRANSMISSION_MODE_1K, TRANSMISSION_MODE_16K, TRANSMISSION_MODE_32K})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TransmissionMode {}
+
+    /**
+     * Transmission Mode undefined.
+     */
+    public static final int TRANSMISSION_MODE_UNDEFINED =
+            Constants.FrontendDvbtTransmissionMode.UNDEFINED;
+    /**
+     * Hardware is able to detect and set Transmission Mode automatically
+     */
+    public static final int TRANSMISSION_MODE_AUTO = Constants.FrontendDvbtTransmissionMode.AUTO;
+    /**
+     * 2K Transmission Mode.
+     */
+    public static final int TRANSMISSION_MODE_2K = Constants.FrontendDvbtTransmissionMode.MODE_2K;
+    /**
+     * 8K Transmission Mode.
+     */
+    public static final int TRANSMISSION_MODE_8K = Constants.FrontendDvbtTransmissionMode.MODE_8K;
+    /**
+     * 4K Transmission Mode.
+     */
+    public static final int TRANSMISSION_MODE_4K = Constants.FrontendDvbtTransmissionMode.MODE_4K;
+    /**
+     * 1K Transmission Mode.
+     */
+    public static final int TRANSMISSION_MODE_1K = Constants.FrontendDvbtTransmissionMode.MODE_1K;
+    /**
+     * 16K Transmission Mode.
+     */
+    public static final int TRANSMISSION_MODE_16K = Constants.FrontendDvbtTransmissionMode.MODE_16K;
+    /**
+     * 32K Transmission Mode.
+     */
+    public static final int TRANSMISSION_MODE_32K = Constants.FrontendDvbtTransmissionMode.MODE_32K;
+
+
+
+    /** @hide */
+    @IntDef(flag = true,
+            prefix = "BANDWIDTH_",
+            value = {BANDWIDTH_UNDEFINED, BANDWIDTH_AUTO, BANDWIDTH_8MHZ, BANDWIDTH_7MHZ,
+                    BANDWIDTH_6MHZ, BANDWIDTH_5MHZ, BANDWIDTH_1_7MHZ, BANDWIDTH_10MHZ})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Bandwidth {}
+
+    /**
+     * Bandwidth undefined.
+     */
+    public static final int BANDWIDTH_UNDEFINED = Constants.FrontendDvbtBandwidth.UNDEFINED;
+    /**
+     * Hardware is able to detect and set Bandwidth automatically.
+     */
+    public static final int BANDWIDTH_AUTO = Constants.FrontendDvbtBandwidth.AUTO;
+    /**
+     * 8 MHz bandwidth.
+     */
+    public static final int BANDWIDTH_8MHZ = Constants.FrontendDvbtBandwidth.BANDWIDTH_8MHZ;
+    /**
+     * 7 MHz bandwidth.
+     */
+    public static final int BANDWIDTH_7MHZ = Constants.FrontendDvbtBandwidth.BANDWIDTH_7MHZ;
+    /**
+     * 6 MHz bandwidth.
+     */
+    public static final int BANDWIDTH_6MHZ = Constants.FrontendDvbtBandwidth.BANDWIDTH_6MHZ;
+    /**
+     * 5 MHz bandwidth.
+     */
+    public static final int BANDWIDTH_5MHZ = Constants.FrontendDvbtBandwidth.BANDWIDTH_5MHZ;
+    /**
+     * 1,7 MHz bandwidth.
+     */
+    public static final int BANDWIDTH_1_7MHZ = Constants.FrontendDvbtBandwidth.BANDWIDTH_1_7MHZ;
+    /**
+     * 10 MHz bandwidth.
+     */
+    public static final int BANDWIDTH_10MHZ = Constants.FrontendDvbtBandwidth.BANDWIDTH_10MHZ;
+
+
+    /** @hide */
+    @IntDef(flag = true,
+            prefix = "CONSTELLATION_",
+            value = {CONSTELLATION_UNDEFINED, CONSTELLATION_AUTO, CONSTELLATION_CONSTELLATION_QPSK,
+                    CONSTELLATION_CONSTELLATION_16QAM, CONSTELLATION_CONSTELLATION_64QAM,
+                    CONSTELLATION_CONSTELLATION_256QAM})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Constellation {}
+
+    /**
+     * Constellation not defined.
+     */
+    public static final int CONSTELLATION_UNDEFINED = Constants.FrontendDvbtConstellation.UNDEFINED;
+    /**
+     * Hardware is able to detect and set Constellation automatically.
+     */
+    public static final int CONSTELLATION_AUTO = Constants.FrontendDvbtConstellation.AUTO;
+    /**
+     * QPSK Constellation.
+     */
+    public static final int CONSTELLATION_CONSTELLATION_QPSK =
+            Constants.FrontendDvbtConstellation.CONSTELLATION_QPSK;
+    /**
+     * 16QAM Constellation.
+     */
+    public static final int CONSTELLATION_CONSTELLATION_16QAM =
+            Constants.FrontendDvbtConstellation.CONSTELLATION_16QAM;
+    /**
+     * 64QAM Constellation.
+     */
+    public static final int CONSTELLATION_CONSTELLATION_64QAM =
+            Constants.FrontendDvbtConstellation.CONSTELLATION_64QAM;
+    /**
+     * 256QAM Constellation.
+     */
+    public static final int CONSTELLATION_CONSTELLATION_256QAM =
+            Constants.FrontendDvbtConstellation.CONSTELLATION_256QAM;
+
+
+    /** @hide */
+    @IntDef(flag = true,
+            prefix = "HIERARCHY_",
+            value = {HIERARCHY_UNDEFINED, HIERARCHY_AUTO, HIERARCHY_NON_NATIVE, HIERARCHY_1_NATIVE,
+            HIERARCHY_2_NATIVE, HIERARCHY_4_NATIVE, HIERARCHY_NON_INDEPTH, HIERARCHY_1_INDEPTH,
+            HIERARCHY_2_INDEPTH, HIERARCHY_4_INDEPTH})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Hierarchy {}
+
+    /**
+     * Hierarchy undefined.
+     */
+    public static final int HIERARCHY_UNDEFINED = Constants.FrontendDvbtHierarchy.UNDEFINED;
+    /**
+     * Hardware is able to detect and set Hierarchy automatically.
+     */
+    public static final int HIERARCHY_AUTO = Constants.FrontendDvbtHierarchy.AUTO;
+    /**
+     * Non-native Hierarchy
+     */
+    public static final int HIERARCHY_NON_NATIVE =
+            Constants.FrontendDvbtHierarchy.HIERARCHY_NON_NATIVE;
+    /**
+     * 1-native Hierarchy
+     */
+    public static final int HIERARCHY_1_NATIVE = Constants.FrontendDvbtHierarchy.HIERARCHY_1_NATIVE;
+    /**
+     * 2-native Hierarchy
+     */
+    public static final int HIERARCHY_2_NATIVE = Constants.FrontendDvbtHierarchy.HIERARCHY_2_NATIVE;
+    /**
+     * 4-native Hierarchy
+     */
+    public static final int HIERARCHY_4_NATIVE = Constants.FrontendDvbtHierarchy.HIERARCHY_4_NATIVE;
+    /**
+     * Non-indepth Hierarchy
+     */
+    public static final int HIERARCHY_NON_INDEPTH =
+            Constants.FrontendDvbtHierarchy.HIERARCHY_NON_INDEPTH;
+    /**
+     * 1-indepth Hierarchy
+     */
+    public static final int HIERARCHY_1_INDEPTH =
+            Constants.FrontendDvbtHierarchy.HIERARCHY_1_INDEPTH;
+    /**
+     * 2-indepth Hierarchy
+     */
+    public static final int HIERARCHY_2_INDEPTH =
+            Constants.FrontendDvbtHierarchy.HIERARCHY_2_INDEPTH;
+    /**
+     * 4-indepth Hierarchy
+     */
+    public static final int HIERARCHY_4_INDEPTH =
+            Constants.FrontendDvbtHierarchy.HIERARCHY_4_INDEPTH;
+
+
+    /** @hide */
+    @IntDef(flag = true,
+            prefix = "CODERATE_",
+            value = {CODERATE_UNDEFINED, CODERATE_AUTO, CODERATE_1_2, CODERATE_2_3, CODERATE_3_4,
+            CODERATE_5_6, CODERATE_7_8, CODERATE_3_5, CODERATE_4_5, CODERATE_6_7, CODERATE_8_9})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CodeRate {}
+
+    /**
+     * Code rate undefined.
+     */
+    public static final int CODERATE_UNDEFINED =
+            Constants.FrontendDvbtCoderate.UNDEFINED;
+    /**
+     * Hardware is able to detect and set code rate automatically.
+     */
+    public static final int CODERATE_AUTO = Constants.FrontendDvbtCoderate.AUTO;
+    /**
+     * 1/2 code rate.
+     */
+    public static final int CODERATE_1_2 = Constants.FrontendDvbtCoderate.CODERATE_1_2;
+    /**
+     * 2/3 code rate.
+     */
+    public static final int CODERATE_2_3 = Constants.FrontendDvbtCoderate.CODERATE_2_3;
+    /**
+     * 3/4 code rate.
+     */
+    public static final int CODERATE_3_4 = Constants.FrontendDvbtCoderate.CODERATE_3_4;
+    /**
+     * 5/6 code rate.
+     */
+    public static final int CODERATE_5_6 = Constants.FrontendDvbtCoderate.CODERATE_5_6;
+    /**
+     * 7/8 code rate.
+     */
+    public static final int CODERATE_7_8 = Constants.FrontendDvbtCoderate.CODERATE_7_8;
+    /**
+     * 4/5 code rate.
+     */
+    public static final int CODERATE_3_5 = Constants.FrontendDvbtCoderate.CODERATE_3_5;
+    /**
+     * 4/5 code rate.
+     */
+    public static final int CODERATE_4_5 = Constants.FrontendDvbtCoderate.CODERATE_4_5;
+    /**
+     * 6/7 code rate.
+     */
+    public static final int CODERATE_6_7 = Constants.FrontendDvbtCoderate.CODERATE_6_7;
+    /**
+     * 8/9 code rate.
+     */
+    public static final int CODERATE_8_9 = Constants.FrontendDvbtCoderate.CODERATE_8_9;
+
+    /** @hide */
+    @IntDef(flag = true,
+            prefix = "GUARD_INTERVAL_",
+            value = {GUARD_INTERVAL_UNDEFINED, GUARD_INTERVAL_AUTO,
+            GUARD_INTERVAL_INTERVAL_1_32, GUARD_INTERVAL_INTERVAL_1_16,
+            GUARD_INTERVAL_INTERVAL_1_8, GUARD_INTERVAL_INTERVAL_1_4,
+            GUARD_INTERVAL_INTERVAL_1_128,
+            GUARD_INTERVAL_INTERVAL_19_128,
+            GUARD_INTERVAL_INTERVAL_19_256})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface GuardInterval {}
+
+    /**
+     * Guard Interval undefined.
+     */
+    public static final int GUARD_INTERVAL_UNDEFINED =
+            Constants.FrontendDvbtGuardInterval.UNDEFINED;
+    /**
+     * Hardware is able to detect and set Guard Interval automatically.
+     */
+    public static final int GUARD_INTERVAL_AUTO = Constants.FrontendDvbtGuardInterval.AUTO;
+    /**
+     * 1/32 Guard Interval.
+     */
+    public static final int GUARD_INTERVAL_INTERVAL_1_32 =
+            Constants.FrontendDvbtGuardInterval.INTERVAL_1_32;
+    /**
+     * 1/16 Guard Interval.
+     */
+    public static final int GUARD_INTERVAL_INTERVAL_1_16 =
+            Constants.FrontendDvbtGuardInterval.INTERVAL_1_16;
+    /**
+     * 1/8 Guard Interval.
+     */
+    public static final int GUARD_INTERVAL_INTERVAL_1_8 =
+            Constants.FrontendDvbtGuardInterval.INTERVAL_1_8;
+    /**
+     * 1/4 Guard Interval.
+     */
+    public static final int GUARD_INTERVAL_INTERVAL_1_4 =
+            Constants.FrontendDvbtGuardInterval.INTERVAL_1_4;
+    /**
+     * 1/128 Guard Interval.
+     */
+    public static final int GUARD_INTERVAL_INTERVAL_1_128 =
+            Constants.FrontendDvbtGuardInterval.INTERVAL_1_128;
+    /**
+     * 19/128 Guard Interval.
+     */
+    public static final int GUARD_INTERVAL_INTERVAL_19_128 =
+            Constants.FrontendDvbtGuardInterval.INTERVAL_19_128;
+    /**
+     * 19/256 Guard Interval.
+     */
+    public static final int GUARD_INTERVAL_INTERVAL_19_256 =
+            Constants.FrontendDvbtGuardInterval.INTERVAL_19_256;
+
+    /** @hide */
+    @IntDef(flag = true,
+            prefix = "STANDARD",
+            value = {STANDARD_AUTO, STANDARD_T, STANDARD_T2}
+    )
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Standard {}
+
+    /**
+     * Hardware is able to detect and set Standard automatically.
+     */
+    public static final int STANDARD_AUTO = Constants.FrontendDvbtStandard.AUTO;
+    /**
+     * T standard.
+     */
+    public static final int STANDARD_T = Constants.FrontendDvbtStandard.T;
+    /**
+     * T2 standard.
+     */
+    public static final int STANDARD_T2 = Constants.FrontendDvbtStandard.T2;
+
+    /** @hide */
+    @IntDef(prefix = "PLP_MODE_",
+            value = {PLP_MODE_UNDEFINED, PLP_MODE_AUTO, PLP_MODE_MANUAL})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PlpMode {}
+
+    /**
+     * Physical Layer Pipe (PLP) Mode undefined.
+     */
+    public static final int PLP_MODE_UNDEFINED = Constants.FrontendDvbtPlpMode.UNDEFINED;
+    /**
+     * Hardware is able to detect and set Physical Layer Pipe (PLP) Mode automatically.
+     */
+    public static final int PLP_MODE_AUTO = Constants.FrontendDvbtPlpMode.AUTO;
+    /**
+     * Physical Layer Pipe (PLP) manual Mode.
+     */
+    public static final int PLP_MODE_MANUAL = Constants.FrontendDvbtPlpMode.MANUAL;
+
+
+    private final int mTransmissionMode;
+    private final int mBandwidth;
+    private final int mConstellation;
+    private final int mHierarchy;
+    private final int mHpCodeRate;
+    private final int mLpCodeRate;
+    private final int mGuardInterval;
+    private final boolean mIsHighPriority;
+    private final int mStandard;
+    private final boolean mIsMiso;
+    private final int mPlpMode;
+    private final int mPlpId;
+    private final int mPlpGroupId;
+
+    private DvbtFrontendSettings(int frequency, int transmissionMode, int bandwidth,
+            int constellation, int hierarchy, int hpCodeRate, int lpCodeRate, int guardInterval,
+            boolean isHighPriority, int standard, boolean isMiso, int plpMode, int plpId,
+            int plpGroupId) {
         super(frequency);
+        mTransmissionMode = transmissionMode;
+        mBandwidth = bandwidth;
+        mConstellation = constellation;
+        mHierarchy = hierarchy;
+        mHpCodeRate = hpCodeRate;
+        mLpCodeRate = lpCodeRate;
+        mGuardInterval = guardInterval;
+        mIsHighPriority = isHighPriority;
+        mStandard = standard;
+        mIsMiso = isMiso;
+        mPlpMode = plpMode;
+        mPlpId = plpId;
+        mPlpGroupId = plpGroupId;
+    }
+
+    /**
+     * Gets Transmission Mode.
+     */
+    @TransmissionMode
+    public int getTransmissionMode() {
+        return mTransmissionMode;
+    }
+    /**
+     * Gets Bandwidth.
+     */
+    @Bandwidth
+    public int getBandwidth() {
+        return mBandwidth;
+    }
+    /**
+     * Gets Constellation.
+     */
+    @Constellation
+    public int getConstellation() {
+        return mConstellation;
+    }
+    /**
+     * Gets Hierarchy.
+     */
+    @Hierarchy
+    public int getHierarchy() {
+        return mHierarchy;
+    }
+    /**
+     * Gets Code Rate for High Priority level.
+     */
+    @CodeRate
+    public int getHpCodeRate() {
+        return mHpCodeRate;
+    }
+    /**
+     * Gets Code Rate for Low Priority level.
+     */
+    @CodeRate
+    public int getLpCodeRate() {
+        return mLpCodeRate;
+    }
+    /**
+     * Gets Guard Interval.
+     */
+    @GuardInterval
+    public int getGuardInterval() {
+        return mGuardInterval;
+    }
+    /**
+     * Checks whether it's high priority.
+     */
+    public boolean isHighPriority() {
+        return mIsHighPriority;
+    }
+    /**
+     * Gets Standard.
+     */
+    @Standard
+    public int getStandard() {
+        return mStandard;
+    }
+    /**
+     * Gets whether it's MISO.
+     */
+    public boolean isMiso() {
+        return mIsMiso;
+    }
+    /**
+     * Gets Physical Layer Pipe (PLP) Mode.
+     */
+    @PlpMode
+    public int getPlpMode() {
+        return mPlpMode;
+    }
+    /**
+     * Gets Physical Layer Pipe (PLP) ID.
+     */
+    public int getPlpId() {
+        return mPlpId;
+    }
+    /**
+     * Gets Physical Layer Pipe (PLP) group ID.
+     */
+    public int getPlpGroupId() {
+        return mPlpGroupId;
+    }
+
+    /**
+     * Creates a builder for {@link DvbtFrontendSettings}.
+     *
+     * @param context the context of the caller.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(@NonNull Context context) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder();
+    }
+
+    /**
+     * Builder for {@link DvbtFrontendSettings}.
+     */
+    public static class Builder extends FrontendSettings.Builder<Builder> {
+        private int mTransmissionMode;
+        private int mBandwidth;
+        private int mConstellation;
+        private int mHierarchy;
+        private int mHpCodeRate;
+        private int mLpCodeRate;
+        private int mGuardInterval;
+        private boolean mIsHighPriority;
+        private int mStandard;
+        private boolean mIsMiso;
+        private int mPlpMode;
+        private int mPlpId;
+        private int mPlpGroupId;
+
+        private Builder() {
+        }
+
+        /**
+         * Sets Transmission Mode.
+         */
+        @NonNull
+        public Builder setTransmissionMode(@TransmissionMode int transmissionMode) {
+            mTransmissionMode = transmissionMode;
+            return this;
+        }
+        /**
+         * Sets Bandwidth.
+         */
+        @NonNull
+        public Builder setBandwidth(@Bandwidth int bandwidth) {
+            mBandwidth = bandwidth;
+            return this;
+        }
+        /**
+         * Sets Constellation.
+         */
+        @NonNull
+        public Builder setConstellation(@Constellation int constellation) {
+            mConstellation = constellation;
+            return this;
+        }
+        /**
+         * Sets Hierarchy.
+         */
+        @NonNull
+        public Builder setHierarchy(@Hierarchy int hierarchy) {
+            mHierarchy = hierarchy;
+            return this;
+        }
+        /**
+         * Sets Code Rate for High Priority level.
+         */
+        @NonNull
+        public Builder setHpCodeRate(@CodeRate int hpCodeRate) {
+            mHpCodeRate = hpCodeRate;
+            return this;
+        }
+        /**
+         * Sets Code Rate for Low Priority level.
+         */
+        @NonNull
+        public Builder setLpCodeRate(@CodeRate int lpCodeRate) {
+            mLpCodeRate = lpCodeRate;
+            return this;
+        }
+        /**
+         * Sets Guard Interval.
+         */
+        @NonNull
+        public Builder setGuardInterval(@GuardInterval int guardInterval) {
+            mGuardInterval = guardInterval;
+            return this;
+        }
+        /**
+         * Sets whether it's high priority.
+         */
+        @NonNull
+        public Builder setHighPriority(boolean isHighPriority) {
+            mIsHighPriority = isHighPriority;
+            return this;
+        }
+        /**
+         * Sets Standard.
+         */
+        @NonNull
+        public Builder setStandard(@Standard int standard) {
+            mStandard = standard;
+            return this;
+        }
+        /**
+         * Sets whether it's MISO.
+         */
+        @NonNull
+        public Builder setMiso(boolean isMiso) {
+            mIsMiso = isMiso;
+            return this;
+        }
+        /**
+         * Sets Physical Layer Pipe (PLP) Mode.
+         */
+        @NonNull
+        public Builder setPlpMode(@PlpMode int plpMode) {
+            mPlpMode = plpMode;
+            return this;
+        }
+        /**
+         * Sets Physical Layer Pipe (PLP) ID.
+         */
+        @NonNull
+        public Builder setPlpId(int plpId) {
+            mPlpId = plpId;
+            return this;
+        }
+        /**
+         * Sets Physical Layer Pipe (PLP) group ID.
+         */
+        @NonNull
+        public Builder setPlpGroupId(int plpGroupId) {
+            mPlpGroupId = plpGroupId;
+            return this;
+        }
+
+        /**
+         * Builds a {@link DvbtFrontendSettings} object.
+         */
+        @NonNull
+        public DvbtFrontendSettings build() {
+            return new DvbtFrontendSettings(mFrequency, mTransmissionMode, mBandwidth,
+                    mConstellation, mHierarchy, mHpCodeRate, mLpCodeRate, mGuardInterval,
+                    mIsHighPriority, mStandard, mIsMiso, mPlpMode, mPlpId, mPlpGroupId);
+        }
+
+        @Override
+        Builder self() {
+            return this;
+        }
     }
 
     @Override
     public int getType() {
-        return TunerConstants.FRONTEND_TYPE_DVBT;
+        return FrontendSettings.TYPE_DVBT;
     }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendCallback.java b/media/java/android/media/tv/tuner/frontend/FrontendCallback.java
deleted file mode 100644
index 91776e1..0000000
--- a/media/java/android/media/tv/tuner/frontend/FrontendCallback.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.tv.tuner.frontend;
-
-import android.media.tv.tuner.ScanMessage;
-
-/**
- * Frontend Callback.
- *
- * @hide
- */
-public interface FrontendCallback {
-
-    /**
-     * Invoked when there is a frontend event.
-     */
-    void onEvent(int frontendEventType);
-
-    /**
-     * Invoked when there is a scan message.
-     * @param msg
-     */
-    void onScanMessage(ScanMessage msg);
-}
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/FrontendCapabilities.java
new file mode 100644
index 0000000..c03133d
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/FrontendCapabilities.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 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.media.tv.tuner.frontend;
+
+import android.annotation.SystemApi;
+
+/**
+ * Frontend capabilities.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class FrontendCapabilities {
+}
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendInfo.java b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
index ef6c029..696d839 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
@@ -16,78 +16,102 @@
 
 package android.media.tv.tuner.frontend;
 
-import android.media.tv.tuner.FrontendCapabilities;
-import android.media.tv.tuner.TunerConstants.FrontendType;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.media.tv.tuner.frontend.FrontendSettings.Type;
+import android.media.tv.tuner.frontend.FrontendStatus.FrontendStatusType;
+import android.util.Range;
 
 /**
- * Frontend info.
+ * This class is used to specify meta information of a frontend.
+ *
  * @hide
  */
+@SystemApi
 public class FrontendInfo {
     private final int mId;
     private final int mType;
-    private final int mMinFrequency;
-    private final int mMaxFrequency;
-    private final int mMinSymbolRate;
-    private final int mMaxSymbolRate;
+    private final Range<Integer> mFrequencyRange;
+    private final Range<Integer> mSymbolRateRange;
     private final int mAcquireRange;
     private final int mExclusiveGroupId;
     private final int[] mStatusCaps;
     private final FrontendCapabilities mFrontendCap;
 
-    FrontendInfo(int id, int type, int minFrequency, int maxFrequency, int minSymbolRate,
+    private FrontendInfo(int id, int type, int minFrequency, int maxFrequency, int minSymbolRate,
             int maxSymbolRate, int acquireRange, int exclusiveGroupId, int[] statusCaps,
             FrontendCapabilities frontendCap) {
         mId = id;
         mType = type;
-        mMinFrequency = minFrequency;
-        mMaxFrequency = maxFrequency;
-        mMinSymbolRate = minSymbolRate;
-        mMaxSymbolRate = maxSymbolRate;
+        mFrequencyRange = new Range<>(minFrequency, maxFrequency);
+        mSymbolRateRange = new Range<>(minSymbolRate, maxSymbolRate);
         mAcquireRange = acquireRange;
         mExclusiveGroupId = exclusiveGroupId;
         mStatusCaps = statusCaps;
         mFrontendCap = frontendCap;
     }
 
-    /** Gets frontend ID. */
+    /**
+     * Gets frontend ID.
+     */
     public int getId() {
         return mId;
     }
-    /** Gets frontend type. */
-    @FrontendType
+    /**
+     * Gets frontend type.
+     */
+    @Type
     public int getType() {
         return mType;
     }
-    /** Gets min frequency. */
-    public int getMinFrequency() {
-        return mMinFrequency;
+
+    /**
+     * Gets supported frequency range in Hz.
+     */
+    @NonNull
+    public Range<Integer> getFrequencyRange() {
+        return mFrequencyRange;
     }
-    /** Gets max frequency. */
-    public int getMaxFrequency() {
-        return mMaxFrequency;
+
+    /**
+     * Gets symbol rate range in symbols per second.
+     */
+    @NonNull
+    public Range<Integer> getSymbolRateRange() {
+        return mSymbolRateRange;
     }
-    /** Gets min symbol rate. */
-    public int getMinSymbolRate() {
-        return mMinSymbolRate;
-    }
-    /** Gets max symbol rate. */
-    public int getMaxSymbolRate() {
-        return mMaxSymbolRate;
-    }
-    /** Gets acquire range. */
+
+    /**
+     * Gets acquire range in Hz.
+     *
+     * <p>The maximum frequency difference the frontend can detect.
+     */
     public int getAcquireRange() {
         return mAcquireRange;
     }
-    /** Gets exclusive group ID. */
+    /**
+     * Gets exclusive group ID.
+     *
+     * <p>Frontends with the same exclusive group ID indicates they can't function at same time. For
+     * instance, they share some hardware modules.
+     */
     public int getExclusiveGroupId() {
         return mExclusiveGroupId;
     }
-    /** Gets status capabilities. */
+    /**
+     * Gets status capabilities.
+     *
+     * @return An array of supported status types.
+     */
+    @FrontendStatusType
+    @NonNull
     public int[] getStatusCapabilities() {
         return mStatusCaps;
     }
-    /** Gets frontend capability. */
+    /**
+     * Gets frontend capabilities.
+     */
+    @NonNull
     public FrontendCapabilities getFrontendCapability() {
         return mFrontendCap;
     }
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/FrontendSettings.java
new file mode 100644
index 0000000..9071526
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/FrontendSettings.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright 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.media.tv.tuner.frontend;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.LongDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.hardware.tv.tuner.V1_0.Constants;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Frontend settings for tune and scan operations.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class FrontendSettings {
+    /** @hide */
+    @IntDef(prefix = "TYPE_",
+            value = {TYPE_UNDEFINED, TYPE_ANALOG, TYPE_ATSC, TYPE_ATSC3, TYPE_DVBC, TYPE_DVBS,
+                    TYPE_DVBT, TYPE_ISDBS, TYPE_ISDBS3, TYPE_ISDBT})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Type {}
+
+    /**
+     * Undefined frontend type.
+     */
+    public static final int TYPE_UNDEFINED = Constants.FrontendType.UNDEFINED;
+    /**
+     * Analog frontend type.
+     */
+    public static final int TYPE_ANALOG = Constants.FrontendType.ANALOG;
+    /**
+     * Advanced Television Systems Committee (ATSC) frontend type.
+     */
+    public static final int TYPE_ATSC = Constants.FrontendType.ATSC;
+    /**
+     * Advanced Television Systems Committee 3.0 (ATSC-3) frontend type.
+     */
+    public static final int TYPE_ATSC3 = Constants.FrontendType.ATSC3;
+    /**
+     * Digital Video Broadcasting-Cable (DVB-C) frontend type.
+     */
+    public static final int TYPE_DVBC = Constants.FrontendType.DVBC;
+    /**
+     * Digital Video Broadcasting-Satellite (DVB-S) frontend type.
+     */
+    public static final int TYPE_DVBS = Constants.FrontendType.DVBS;
+    /**
+     * Digital Video Broadcasting-Terrestrial (DVB-T) frontend type.
+     */
+    public static final int TYPE_DVBT = Constants.FrontendType.DVBT;
+    /**
+     * Integrated Services Digital Broadcasting-Satellite (ISDB-S) frontend type.
+     */
+    public static final int TYPE_ISDBS = Constants.FrontendType.ISDBS;
+    /**
+     * Integrated Services Digital Broadcasting-Satellite 3 (ISDB-S3) frontend type.
+     */
+    public static final int TYPE_ISDBS3 = Constants.FrontendType.ISDBS3;
+    /**
+     * Integrated Services Digital Broadcasting-Terrestrial (ISDB-T) frontend type.
+     */
+    public static final int TYPE_ISDBT = Constants.FrontendType.ISDBT;
+
+
+
+    /** @hide */
+    @LongDef(flag = true,
+            prefix = "FEC_",
+            value = {FEC_UNDEFINED, FEC_AUTO, FEC_1_2, FEC_1_3, FEC_1_4, FEC_1_5, FEC_2_3, FEC_2_5,
+            FEC_2_9, FEC_3_4, FEC_3_5, FEC_4_5, FEC_4_15, FEC_5_6, FEC_5_9, FEC_6_7, FEC_7_8,
+            FEC_7_9, FEC_7_15, FEC_8_9, FEC_8_15, FEC_9_10, FEC_9_20, FEC_11_15, FEC_11_20,
+            FEC_11_45, FEC_13_18, FEC_13_45, FEC_14_45, FEC_23_36, FEC_25_36, FEC_26_45, FEC_28_45,
+            FEC_29_45, FEC_31_45, FEC_32_45, FEC_77_90})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface InnerFec {}
+
+    /**
+     * FEC not defined.
+     */
+    public static final long FEC_UNDEFINED = Constants.FrontendInnerFec.FEC_UNDEFINED;
+    /**
+     * hardware is able to detect and set FEC automatically.
+     */
+    public static final long FEC_AUTO = Constants.FrontendInnerFec.AUTO;
+    /**
+     * 1/2 conv. code rate.
+     */
+    public static final long FEC_1_2 = Constants.FrontendInnerFec.FEC_1_2;
+    /**
+     * 1/3 conv. code rate.
+     */
+    public static final long FEC_1_3 = Constants.FrontendInnerFec.FEC_1_3;
+    /**
+     * 1/4 conv. code rate.
+     */
+    public static final long FEC_1_4 = Constants.FrontendInnerFec.FEC_1_4;
+    /**
+     * 1/5 conv. code rate.
+     */
+    public static final long FEC_1_5 = Constants.FrontendInnerFec.FEC_1_5;
+    /**
+     * 2/3 conv. code rate.
+     */
+    public static final long FEC_2_3 = Constants.FrontendInnerFec.FEC_2_3;
+    /**
+     * 2/5 conv. code rate.
+     */
+    public static final long FEC_2_5 = Constants.FrontendInnerFec.FEC_2_5;
+    /**
+     * 2/9 conv. code rate.
+     */
+    public static final long FEC_2_9 = Constants.FrontendInnerFec.FEC_2_9;
+    /**
+     * 3/4 conv. code rate.
+     */
+    public static final long FEC_3_4 = Constants.FrontendInnerFec.FEC_3_4;
+    /**
+     * 3/5 conv. code rate.
+     */
+    public static final long FEC_3_5 = Constants.FrontendInnerFec.FEC_3_5;
+    /**
+     * 4/5 conv. code rate.
+     */
+    public static final long FEC_4_5 = Constants.FrontendInnerFec.FEC_4_5;
+    /**
+     * 4/15 conv. code rate.
+     */
+    public static final long FEC_4_15 = Constants.FrontendInnerFec.FEC_4_15;
+    /**
+     * 5/6 conv. code rate.
+     */
+    public static final long FEC_5_6 = Constants.FrontendInnerFec.FEC_5_6;
+    /**
+     * 5/9 conv. code rate.
+     */
+    public static final long FEC_5_9 = Constants.FrontendInnerFec.FEC_5_9;
+    /**
+     * 6/7 conv. code rate.
+     */
+    public static final long FEC_6_7 = Constants.FrontendInnerFec.FEC_6_7;
+    /**
+     * 7/8 conv. code rate.
+     */
+    public static final long FEC_7_8 = Constants.FrontendInnerFec.FEC_7_8;
+    /**
+     * 7/9 conv. code rate.
+     */
+    public static final long FEC_7_9 = Constants.FrontendInnerFec.FEC_7_9;
+    /**
+     * 7/15 conv. code rate.
+     */
+    public static final long FEC_7_15 = Constants.FrontendInnerFec.FEC_7_15;
+    /**
+     * 8/9 conv. code rate.
+     */
+    public static final long FEC_8_9 = Constants.FrontendInnerFec.FEC_8_9;
+    /**
+     * 8/15 conv. code rate.
+     */
+    public static final long FEC_8_15 = Constants.FrontendInnerFec.FEC_8_15;
+    /**
+     * 9/10 conv. code rate.
+     */
+    public static final long FEC_9_10 = Constants.FrontendInnerFec.FEC_9_10;
+    /**
+     * 9/20 conv. code rate.
+     */
+    public static final long FEC_9_20 = Constants.FrontendInnerFec.FEC_9_20;
+    /**
+     * 11/15 conv. code rate.
+     */
+    public static final long FEC_11_15 = Constants.FrontendInnerFec.FEC_11_15;
+    /**
+     * 11/20 conv. code rate.
+     */
+    public static final long FEC_11_20 = Constants.FrontendInnerFec.FEC_11_20;
+    /**
+     * 11/45 conv. code rate.
+     */
+    public static final long FEC_11_45 = Constants.FrontendInnerFec.FEC_11_45;
+    /**
+     * 13/18 conv. code rate.
+     */
+    public static final long FEC_13_18 = Constants.FrontendInnerFec.FEC_13_18;
+    /**
+     * 13/45 conv. code rate.
+     */
+    public static final long FEC_13_45 = Constants.FrontendInnerFec.FEC_13_45;
+    /**
+     * 14/45 conv. code rate.
+     */
+    public static final long FEC_14_45 = Constants.FrontendInnerFec.FEC_14_45;
+    /**
+     * 23/36 conv. code rate.
+     */
+    public static final long FEC_23_36 = Constants.FrontendInnerFec.FEC_23_36;
+    /**
+     * 25/36 conv. code rate.
+     */
+    public static final long FEC_25_36 = Constants.FrontendInnerFec.FEC_25_36;
+    /**
+     * 26/45 conv. code rate.
+     */
+    public static final long FEC_26_45 = Constants.FrontendInnerFec.FEC_26_45;
+    /**
+     * 28/45 conv. code rate.
+     */
+    public static final long FEC_28_45 = Constants.FrontendInnerFec.FEC_28_45;
+    /**
+     * 29/45 conv. code rate.
+     */
+    public static final long FEC_29_45 = Constants.FrontendInnerFec.FEC_29_45;
+    /**
+     * 31/45 conv. code rate.
+     */
+    public static final long FEC_31_45 = Constants.FrontendInnerFec.FEC_31_45;
+    /**
+     * 32/45 conv. code rate.
+     */
+    public static final long FEC_32_45 = Constants.FrontendInnerFec.FEC_32_45;
+    /**
+     * 77/90 conv. code rate.
+     */
+    public static final long FEC_77_90 = Constants.FrontendInnerFec.FEC_77_90;
+
+
+
+    private final int mFrequency;
+
+    FrontendSettings(int frequency) {
+        mFrequency = frequency;
+    }
+
+    /**
+     * Returns the frontend type.
+     */
+    @Type
+    public abstract int getType();
+
+    /**
+     * Gets the frequency.
+     *
+     * @return the frequency in Hz.
+     */
+    public int getFrequency() {
+        return mFrequency;
+    }
+
+    /**
+     * Builder for {@link FrontendSettings}.
+     *
+     * @param <T> The subclass to be built.
+     */
+    public abstract static class Builder<T extends Builder<T>> {
+        /* package */ int mFrequency;
+
+        /* package */ Builder() {}
+
+        /**
+         * Sets frequency in Hz.
+         */
+        @NonNull
+        @IntRange(from = 1)
+        public T setFrequency(int frequency) {
+            mFrequency = frequency;
+            return self();
+        }
+
+        /* package */ abstract T self();
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
index 89ec536..c1b17d3 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
@@ -16,219 +16,472 @@
 
 package android.media.tv.tuner.frontend;
 
-import android.media.tv.tuner.TunerConstants;
-import android.media.tv.tuner.TunerConstants.FrontendDvbcSpectralInversion;
-import android.media.tv.tuner.TunerConstants.FrontendDvbtHierarchy;
-import android.media.tv.tuner.TunerConstants.FrontendInnerFec;
-import android.media.tv.tuner.TunerConstants.FrontendModulation;
-import android.media.tv.tuner.TunerConstants.FrontendStatusType;
-import android.media.tv.tuner.TunerConstants.LnbVoltage;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.Lnb;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
- * Frontend status
+ * Frontend status.
  *
  * @hide
  */
+@SystemApi
 public class FrontendStatus {
 
-    private final int mType;
-    private final Object mValue;
+    /** @hide */
+    @IntDef({FRONTEND_STATUS_TYPE_DEMOD_LOCK, FRONTEND_STATUS_TYPE_SNR, FRONTEND_STATUS_TYPE_BER,
+            FRONTEND_STATUS_TYPE_PER, FRONTEND_STATUS_TYPE_PRE_BER,
+            FRONTEND_STATUS_TYPE_SIGNAL_QUALITY, FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH,
+            FRONTEND_STATUS_TYPE_SYMBOL_RATE, FRONTEND_STATUS_TYPE_FEC,
+            FRONTEND_STATUS_TYPE_MODULATION, FRONTEND_STATUS_TYPE_SPECTRAL,
+            FRONTEND_STATUS_TYPE_LNB_VOLTAGE, FRONTEND_STATUS_TYPE_PLP_ID,
+            FRONTEND_STATUS_TYPE_EWBS, FRONTEND_STATUS_TYPE_AGC, FRONTEND_STATUS_TYPE_LNA,
+            FRONTEND_STATUS_TYPE_LAYER_ERROR, FRONTEND_STATUS_TYPE_VBER_CN,
+            FRONTEND_STATUS_TYPE_LBER_CN, FRONTEND_STATUS_TYPE_XER_CN, FRONTEND_STATUS_TYPE_MER,
+            FRONTEND_STATUS_TYPE_FREQ_OFFSET, FRONTEND_STATUS_TYPE_HIERARCHY,
+            FRONTEND_STATUS_TYPE_RF_LOCK, FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FrontendStatusType {}
 
-    private FrontendStatus(int type, Object value) {
-        mType = type;
-        mValue = value;
+    /**
+     * Lock status for Demod.
+     */
+    public static final int FRONTEND_STATUS_TYPE_DEMOD_LOCK =
+            Constants.FrontendStatusType.DEMOD_LOCK;
+    /**
+     * Signal to Noise Ratio.
+     */
+    public static final int FRONTEND_STATUS_TYPE_SNR = Constants.FrontendStatusType.SNR;
+    /**
+     * Bit Error Ratio.
+     */
+    public static final int FRONTEND_STATUS_TYPE_BER = Constants.FrontendStatusType.BER;
+    /**
+     * Packages Error Ratio.
+     */
+    public static final int FRONTEND_STATUS_TYPE_PER = Constants.FrontendStatusType.PER;
+    /**
+     * Bit Error Ratio before FEC.
+     */
+    public static final int FRONTEND_STATUS_TYPE_PRE_BER = Constants.FrontendStatusType.PRE_BER;
+    /**
+     * Signal Quality (0..100). Good data over total data in percent can be
+     * used as a way to present Signal Quality.
+     */
+    public static final int FRONTEND_STATUS_TYPE_SIGNAL_QUALITY =
+            Constants.FrontendStatusType.SIGNAL_QUALITY;
+    /**
+     * Signal Strength.
+     */
+    public static final int FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH =
+            Constants.FrontendStatusType.SIGNAL_STRENGTH;
+    /**
+     * Symbol Rate in symbols per second.
+     */
+    public static final int FRONTEND_STATUS_TYPE_SYMBOL_RATE =
+            Constants.FrontendStatusType.SYMBOL_RATE;
+    /**
+     * Forward Error Correction Type.
+     */
+    public static final int FRONTEND_STATUS_TYPE_FEC = Constants.FrontendStatusType.FEC;
+    /**
+     * Modulation Type.
+     */
+    public static final int FRONTEND_STATUS_TYPE_MODULATION =
+            Constants.FrontendStatusType.MODULATION;
+    /**
+     * Spectral Inversion Type.
+     */
+    public static final int FRONTEND_STATUS_TYPE_SPECTRAL = Constants.FrontendStatusType.SPECTRAL;
+    /**
+     * LNB Voltage.
+     */
+    public static final int FRONTEND_STATUS_TYPE_LNB_VOLTAGE =
+            Constants.FrontendStatusType.LNB_VOLTAGE;
+    /**
+     * Physical Layer Pipe ID.
+     */
+    public static final int FRONTEND_STATUS_TYPE_PLP_ID = Constants.FrontendStatusType.PLP_ID;
+    /**
+     * Status for Emergency Warning Broadcasting System.
+     */
+    public static final int FRONTEND_STATUS_TYPE_EWBS = Constants.FrontendStatusType.EWBS;
+    /**
+     * Automatic Gain Control.
+     */
+    public static final int FRONTEND_STATUS_TYPE_AGC = Constants.FrontendStatusType.AGC;
+    /**
+     * Low Noise Amplifier.
+     */
+    public static final int FRONTEND_STATUS_TYPE_LNA = Constants.FrontendStatusType.LNA;
+    /**
+     * Error status by layer.
+     */
+    public static final int FRONTEND_STATUS_TYPE_LAYER_ERROR =
+            Constants.FrontendStatusType.LAYER_ERROR;
+    /**
+     * CN value by VBER.
+     */
+    public static final int FRONTEND_STATUS_TYPE_VBER_CN = Constants.FrontendStatusType.VBER_CN;
+    /**
+     * CN value by LBER.
+     */
+    public static final int FRONTEND_STATUS_TYPE_LBER_CN = Constants.FrontendStatusType.LBER_CN;
+    /**
+     * CN value by XER.
+     */
+    public static final int FRONTEND_STATUS_TYPE_XER_CN = Constants.FrontendStatusType.XER_CN;
+    /**
+     * Modulation Error Ratio.
+     */
+    public static final int FRONTEND_STATUS_TYPE_MER = Constants.FrontendStatusType.MER;
+    /**
+     * Difference between tuning frequency and actual locked frequency.
+     */
+    public static final int FRONTEND_STATUS_TYPE_FREQ_OFFSET =
+            Constants.FrontendStatusType.FREQ_OFFSET;
+    /**
+     * Hierarchy for DVBT.
+     */
+    public static final int FRONTEND_STATUS_TYPE_HIERARCHY = Constants.FrontendStatusType.HIERARCHY;
+    /**
+     * Lock status for RF.
+     */
+    public static final int FRONTEND_STATUS_TYPE_RF_LOCK = Constants.FrontendStatusType.RF_LOCK;
+    /**
+     * PLP information in a frequency band for ATSC-3.0 frontend.
+     */
+    public static final int FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO =
+            Constants.FrontendStatusType.ATSC3_PLP_INFO;
+
+
+    /** @hide */
+    @IntDef(value = {
+            DvbcFrontendSettings.MODULATION_UNDEFINED,
+            DvbcFrontendSettings.MODULATION_AUTO,
+            DvbcFrontendSettings.MODULATION_MOD_16QAM,
+            DvbcFrontendSettings.MODULATION_MOD_32QAM,
+            DvbcFrontendSettings.MODULATION_MOD_64QAM,
+            DvbcFrontendSettings.MODULATION_MOD_128QAM,
+            DvbcFrontendSettings.MODULATION_MOD_256QAM,
+            DvbsFrontendSettings.MODULATION_UNDEFINED,
+            DvbsFrontendSettings.MODULATION_AUTO,
+            DvbsFrontendSettings.MODULATION_MOD_QPSK,
+            DvbsFrontendSettings.MODULATION_MOD_8PSK,
+            DvbsFrontendSettings.MODULATION_MOD_16QAM,
+            DvbsFrontendSettings.MODULATION_MOD_16PSK,
+            DvbsFrontendSettings.MODULATION_MOD_32PSK,
+            DvbsFrontendSettings.MODULATION_MOD_ACM,
+            DvbsFrontendSettings.MODULATION_MOD_8APSK,
+            DvbsFrontendSettings.MODULATION_MOD_16APSK,
+            DvbsFrontendSettings.MODULATION_MOD_32APSK,
+            DvbsFrontendSettings.MODULATION_MOD_64APSK,
+            DvbsFrontendSettings.MODULATION_MOD_128APSK,
+            DvbsFrontendSettings.MODULATION_MOD_256APSK,
+            DvbsFrontendSettings.MODULATION_MOD_RESERVED,
+            IsdbsFrontendSettings.MODULATION_UNDEFINED,
+            IsdbsFrontendSettings.MODULATION_AUTO,
+            IsdbsFrontendSettings.MODULATION_MOD_BPSK,
+            IsdbsFrontendSettings.MODULATION_MOD_QPSK,
+            IsdbsFrontendSettings.MODULATION_MOD_TC8PSK,
+            Isdbs3FrontendSettings.MODULATION_UNDEFINED,
+            Isdbs3FrontendSettings.MODULATION_AUTO,
+            Isdbs3FrontendSettings.MODULATION_MOD_BPSK,
+            Isdbs3FrontendSettings.MODULATION_MOD_QPSK,
+            Isdbs3FrontendSettings.MODULATION_MOD_8PSK,
+            Isdbs3FrontendSettings.MODULATION_MOD_16APSK,
+            Isdbs3FrontendSettings.MODULATION_MOD_32APSK,
+            IsdbtFrontendSettings.MODULATION_UNDEFINED,
+            IsdbtFrontendSettings.MODULATION_AUTO,
+            IsdbtFrontendSettings.MODULATION_MOD_DQPSK,
+            IsdbtFrontendSettings.MODULATION_MOD_QPSK,
+            IsdbtFrontendSettings.MODULATION_MOD_16QAM,
+            IsdbtFrontendSettings.MODULATION_MOD_64QAM})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FrontendModulation {}
+
+
+    private Boolean mIsDemodLocked;
+    private Integer mSnr;
+    private Integer mBer;
+    private Integer mPer;
+    private Integer mPerBer;
+    private Integer mSignalQuality;
+    private Integer mSignalStrength;
+    private Integer mSymbolRate;
+    private Long mInnerFec;
+    private Integer mModulation;
+    private Integer mInversion;
+    private Integer mLnbVoltage;
+    private Integer mPlpId;
+    private Boolean mIsEwbs;
+    private Integer mAgc;
+    private Boolean mIsLnaOn;
+    private boolean[] mIsLayerErrors;
+    private Integer mVberCn;
+    private Integer mLberCn;
+    private Integer mXerCn;
+    private Integer mMer;
+    private Integer mFreqOffset;
+    private Integer mHierarchy;
+    private Boolean mIsRfLocked;
+    private Atsc3PlpInfo[] mPlpInfo;
+
+    // Constructed and fields set by JNI code.
+    private FrontendStatus() {
     }
 
-    /** Gets frontend status type. */
-    @FrontendStatusType
-    public int getStatusType() {
-        return mType;
-    }
-    /** Lock status for Demod in True/False. */
-    public boolean getIsDemodLocked() {
-        if (mType != TunerConstants.FRONTEND_STATUS_TYPE_DEMOD_LOCK) {
+    /**
+     * Lock status for Demod.
+     */
+    public boolean isDemodLocked() {
+        if (mIsDemodLocked == null) {
             throw new IllegalStateException();
         }
-        return (Boolean) mValue;
-    }
-    /** SNR value measured by 0.001 dB. */
-    public int getSnr() {
-        if (mType != TunerConstants.FRONTEND_STATUS_TYPE_SNR) {
-            throw new IllegalStateException();
-        }
-        return (int) mValue;
-    }
-    /** The number of error bit per 1 billion bits. */
-    public int getBer() {
-        if (mType != TunerConstants.FRONTEND_STATUS_TYPE_BER) {
-            throw new IllegalStateException();
-        }
-        return (int) mValue;
-    }
-    /** The number of error package per 1 billion packages. */
-    public int getPer() {
-        if (mType != TunerConstants.FRONTEND_STATUS_TYPE_PER) {
-            throw new IllegalStateException();
-        }
-        return (int) mValue;
-    }
-    /** The number of error bit per 1 billion bits before FEC. */
-    public int getPerBer() {
-        if (mType != TunerConstants.FRONTEND_STATUS_TYPE_PRE_BER) {
-            throw new IllegalStateException();
-        }
-        return (int) mValue;
-    }
-    /** Signal Quality in percent. */
-    public int getSignalQuality() {
-        if (mType != TunerConstants.FRONTEND_STATUS_TYPE_SIGNAL_QUALITY) {
-            throw new IllegalStateException();
-        }
-        return (int) mValue;
-    }
-    /** Signal Strength measured by 0.001 dBm. */
-    public int getSignalStrength() {
-        if (mType != TunerConstants.FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH) {
-            throw new IllegalStateException();
-        }
-        return (int) mValue;
-    }
-    /**  Symbols per second. */
-    public int getSymbolRate() {
-        if (mType != TunerConstants.FRONTEND_STATUS_TYPE_SYMBOL_RATE) {
-            throw new IllegalStateException();
-        }
-        return (int) mValue;
+        return mIsDemodLocked;
     }
     /**
-     *  Inner Forward Error Correction type as specified in ETSI EN 300 468 V1.15.1
-     *  and ETSI EN 302 307-2 V1.1.1.
+     * Gets Signal to Noise Ratio in thousandths of a deciBel (0.001dB).
      */
-    @FrontendInnerFec
-    public long getFec() {
-        if (mType != TunerConstants.FRONTEND_STATUS_TYPE_FEC) {
+    public int getSnr() {
+        if (mSnr == null) {
             throw new IllegalStateException();
         }
-        return (long) mValue;
+        return mSnr;
     }
-    /** Modulation */
-    @FrontendModulation
-    public int getModulation() {
-        if (mType != TunerConstants.FRONTEND_STATUS_TYPE_MODULATION) {
+    /**
+     * Gets Bit Error Ratio.
+     *
+     * <p>The number of error bit per 1 billion bits.
+     */
+    public int getBer() {
+        if (mBer == null) {
             throw new IllegalStateException();
         }
-        return (int) mValue;
-    }
-    /** Spectral Inversion for DVBC. */
-    @FrontendDvbcSpectralInversion
-    public int getSpectralInversion() {
-        if (mType != TunerConstants.FRONTEND_STATUS_TYPE_SPECTRAL) {
-            throw new IllegalStateException();
-        }
-        return (int) mValue;
-    }
-    /** Power Voltage Type for LNB. */
-    @LnbVoltage
-    public int getLnbVoltage() {
-        if (mType != TunerConstants.FRONTEND_STATUS_TYPE_LNB_VOLTAGE) {
-            throw new IllegalStateException();
-        }
-        return (int) mValue;
-    }
-    /** PLP ID */
-    public byte getPlpId() {
-        if (mType != TunerConstants.FRONTEND_STATUS_TYPE_PLP_ID) {
-            throw new IllegalStateException();
-        }
-        return (byte) mValue;
-    }
-    /** Emergency Warning Broadcasting System */
-    public boolean getIsEwbs() {
-        if (mType != TunerConstants.FRONTEND_STATUS_TYPE_EWBS) {
-            throw new IllegalStateException();
-        }
-        return (Boolean) mValue;
-    }
-    /** AGC value is normalized from 0 to 255. */
-    public byte getAgc() {
-        if (mType != TunerConstants.FRONTEND_STATUS_TYPE_AGC) {
-            throw new IllegalStateException();
-        }
-        return (byte) mValue;
-    }
-    /** LNA(Low Noise Amplifier) is on or not. */
-    public boolean getLnaOn() {
-        if (mType != TunerConstants.FRONTEND_STATUS_TYPE_LNA) {
-            throw new IllegalStateException();
-        }
-        return (Boolean) mValue;
-    }
-    /** Error status by layer. */
-    public boolean[] getIsLayerError() {
-        if (mType != TunerConstants.FRONTEND_STATUS_TYPE_LAYER_ERROR) {
-            throw new IllegalStateException();
-        }
-        return (boolean[]) mValue;
-    }
-    /** CN value by VBER measured by 0.001 dB. */
-    public int getVberCn() {
-        if (mType != TunerConstants.FRONTEND_STATUS_TYPE_VBER_CN) {
-            throw new IllegalStateException();
-        }
-        return (int) mValue;
-    }
-    /** CN value by LBER measured by 0.001 dB. */
-    public int getLberCn() {
-        if (mType != TunerConstants.FRONTEND_STATUS_TYPE_LBER_CN) {
-            throw new IllegalStateException();
-        }
-        return (int) mValue;
-    }
-    /** CN value by XER measured by 0.001 dB. */
-    public int getXerCn() {
-        if (mType != TunerConstants.FRONTEND_STATUS_TYPE_XER_CN) {
-            throw new IllegalStateException();
-        }
-        return (int) mValue;
-    }
-    /** MER value measured by 0.001 dB. */
-    public int getMer() {
-        if (mType != TunerConstants.FRONTEND_STATUS_TYPE_MER) {
-            throw new IllegalStateException();
-        }
-        return (int) mValue;
-    }
-    /** Frequency difference in Hertz. */
-    public int getFreqOffset() {
-        if (mType != TunerConstants.FRONTEND_STATUS_TYPE_FREQ_OFFSET) {
-            throw new IllegalStateException();
-        }
-        return (int) mValue;
-    }
-    /** Hierarchy Type for DVBT. */
-    @FrontendDvbtHierarchy
-    public int getHierarchy() {
-        if (mType != TunerConstants.FRONTEND_STATUS_TYPE_HIERARCHY) {
-            throw new IllegalStateException();
-        }
-        return (int) mValue;
-    }
-    /** Lock status for RF. */
-    public boolean getIsRfLock() {
-        if (mType != TunerConstants.FRONTEND_STATUS_TYPE_RF_LOCK) {
-            throw new IllegalStateException();
-        }
-        return (Boolean) mValue;
-    }
-    /** A list of PLP status for tuned PLPs for ATSC3 frontend. */
-    public Atsc3PlpInfo[] getAtsc3PlpInfo() {
-        if (mType != TunerConstants.FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO) {
-            throw new IllegalStateException();
-        }
-        return (Atsc3PlpInfo[]) mValue;
+        return mBer;
     }
 
-    /** Status for each tuning PLPs. */
+    /**
+     * Gets Packages Error Ratio.
+     *
+     * <p>The number of error package per 1 billion packages.
+     */
+    public int getPer() {
+        if (mPer == null) {
+            throw new IllegalStateException();
+        }
+        return mPer;
+    }
+    /**
+     * Gets Bit Error Ratio before Forward Error Correction (FEC).
+     *
+     * <p>The number of error bit per 1 billion bits before FEC.
+     */
+    public int getPerBer() {
+        if (mPerBer == null) {
+            throw new IllegalStateException();
+        }
+        return mPerBer;
+    }
+    /**
+     * Gets Signal Quality in percent.
+     */
+    public int getSignalQuality() {
+        if (mSignalQuality == null) {
+            throw new IllegalStateException();
+        }
+        return mSignalQuality;
+    }
+    /**
+     * Gets Signal Strength in thousandths of a dBm (0.001dBm).
+     */
+    public int getSignalStrength() {
+        if (mSignalStrength == null) {
+            throw new IllegalStateException();
+        }
+        return mSignalStrength;
+    }
+    /**
+     * Gets symbol rate in symbols per second.
+     */
+    public int getSymbolRate() {
+        if (mSymbolRate == null) {
+            throw new IllegalStateException();
+        }
+        return mSymbolRate;
+    }
+    /**
+     *  Gets Inner Forward Error Correction type as specified in ETSI EN 300 468 V1.15.1
+     *  and ETSI EN 302 307-2 V1.1.1.
+     */
+    @FrontendSettings.InnerFec
+    public long getFec() {
+        if (mInnerFec == null) {
+            throw new IllegalStateException();
+        }
+        return mInnerFec;
+    }
+    /**
+     * Gets modulation.
+     */
+    @FrontendModulation
+    public int getModulation() {
+        if (mModulation == null) {
+            throw new IllegalStateException();
+        }
+        return mModulation;
+    }
+    /**
+     * Gets Spectral Inversion for DVBC.
+     */
+    @DvbcFrontendSettings.SpectralInversion
+    public int getSpectralInversion() {
+        if (mInversion == null) {
+            throw new IllegalStateException();
+        }
+        return mInversion;
+    }
+    /**
+     * Gets Power Voltage Type for LNB.
+     */
+    @Lnb.Voltage
+    public int getLnbVoltage() {
+        if (mLnbVoltage == null) {
+            throw new IllegalStateException();
+        }
+        return mLnbVoltage;
+    }
+    /**
+     * Gets Physical Layer Pipe ID.
+     */
+    public int getPlpId() {
+        if (mPlpId == null) {
+            throw new IllegalStateException();
+        }
+        return mPlpId;
+    }
+    /**
+     * Checks whether it's Emergency Warning Broadcasting System
+     */
+    public boolean isEwbs() {
+        if (mIsEwbs == null) {
+            throw new IllegalStateException();
+        }
+        return mIsEwbs;
+    }
+    /**
+     * Gets Automatic Gain Control value which is normalized from 0 to 255.
+     */
+    public int getAgc() {
+        if (mAgc == null) {
+            throw new IllegalStateException();
+        }
+        return mAgc;
+    }
+    /**
+     * Checks LNA (Low Noise Amplifier) is on or not.
+     */
+    public boolean isLnaOn() {
+        if (mIsLnaOn == null) {
+            throw new IllegalStateException();
+        }
+        return mIsLnaOn;
+    }
+    /**
+     * Gets Error status by layer.
+     */
+    @NonNull
+    public boolean[] getLayerErrors() {
+        if (mIsLayerErrors == null) {
+            throw new IllegalStateException();
+        }
+        return mIsLayerErrors;
+    }
+    /**
+     * Gets CN value by VBER in thousandths of a deciBel (0.001dB).
+     */
+    public int getVberCn() {
+        if (mVberCn == null) {
+            throw new IllegalStateException();
+        }
+        return mVberCn;
+    }
+    /**
+     * Gets CN value by LBER in thousandths of a deciBel (0.001dB).
+     */
+    public int getLberCn() {
+        if (mLberCn == null) {
+            throw new IllegalStateException();
+        }
+        return mLberCn;
+    }
+    /**
+     * Gets CN value by XER in thousandths of a deciBel (0.001dB).
+     */
+    public int getXerCn() {
+        if (mXerCn == null) {
+            throw new IllegalStateException();
+        }
+        return mXerCn;
+    }
+    /**
+     * Gets Modulation Error Ratio in thousandths of a deciBel (0.001dB).
+     */
+    public int getMer() {
+        if (mMer == null) {
+            throw new IllegalStateException();
+        }
+        return mMer;
+    }
+    /**
+     * Gets frequency difference in Hz.
+     *
+     * <p>Difference between tuning frequency and actual locked frequency.
+     */
+    public int getFreqOffset() {
+        if (mFreqOffset == null) {
+            throw new IllegalStateException();
+        }
+        return mFreqOffset;
+    }
+    /**
+     * Gets hierarchy Type for DVBT.
+     */
+    @DvbtFrontendSettings.Hierarchy
+    public int getHierarchy() {
+        if (mHierarchy == null) {
+            throw new IllegalStateException();
+        }
+        return mHierarchy;
+    }
+    /**
+     * Gets lock status for RF.
+     */
+    public boolean isRfLock() {
+        if (mIsRfLocked == null) {
+            throw new IllegalStateException();
+        }
+        return mIsRfLocked;
+    }
+    /**
+     * Gets an array of PLP status for tuned PLPs for ATSC3 frontend.
+     */
+    @NonNull
+    public Atsc3PlpInfo[] getAtsc3PlpInfo() {
+        if (mPlpInfo == null) {
+            throw new IllegalStateException();
+        }
+        return mPlpInfo;
+    }
+
+    /**
+     * Status for each tuning Physical Layer Pipes.
+     */
     public static class Atsc3PlpInfo {
         private final int mPlpId;
         private final boolean mIsLock;
@@ -240,17 +493,20 @@
             mUec = uec;
         }
 
-        /** Gets PLP IDs. */
+        /**
+         * Gets Physical Layer Pipe ID.
+         */
         public int getPlpId() {
             return mPlpId;
         }
-        /** Gets Demod Lock/Unlock status of this particular PLP. */
-        public boolean getIsLock() {
+        /**
+         * Gets Demod Lock/Unlock status of this particular PLP.
+         */
+        public boolean isLock() {
             return mIsLock;
         }
         /**
-         * Gets Uncorrectable Error Counts (UEC) of this particular PLP since last tune
-         * operation.
+         * Gets Uncorrectable Error Counts (UEC) of this particular PLP since last tune operation.
          */
         public int getUec() {
             return mUec;
diff --git a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities.java
new file mode 100644
index 0000000..573f379
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 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.media.tv.tuner.frontend;
+
+import android.annotation.SystemApi;
+
+/**
+ * ISDBS-3 Capabilities.
+ *
+ * @hide
+ */
+@SystemApi
+public class Isdbs3FrontendCapabilities extends FrontendCapabilities {
+    private final int mModulationCap;
+    private final int mCodeRateCap;
+
+    private Isdbs3FrontendCapabilities(int modulationCap, int codeRateCap) {
+        mModulationCap = modulationCap;
+        mCodeRateCap = codeRateCap;
+    }
+
+    /**
+     * Gets modulation capability.
+     */
+    @Isdbs3FrontendSettings.Modulation
+    public int getModulationCapability() {
+        return mModulationCap;
+    }
+    /**
+     * Gets code rate capability.
+     */
+    @Isdbs3FrontendSettings.CodeRate
+    public int getCodeRateCapability() {
+        return mCodeRateCap;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java
index 736d0b1..9b0e533 100644
--- a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java
@@ -16,27 +16,291 @@
 
 package android.media.tv.tuner.frontend;
 
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * Frontend settings for ISDBS-3.
+ *
  * @hide
  */
+@SystemApi
 public class Isdbs3FrontendSettings extends FrontendSettings {
-    public int streamId;
-    public int streamIdType;
-    public int modulation;
-    public int coderate;
-    public int symbolRate;
-    public int rolloff;
+    /** @hide */
+    @IntDef(flag = true,
+            prefix = "MODULATION_",
+            value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_BPSK,
+            MODULATION_MOD_QPSK, MODULATION_MOD_8PSK, MODULATION_MOD_16APSK,
+            MODULATION_MOD_32APSK})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Modulation {}
 
-    Isdbs3FrontendSettings(int frequency) {
+    /**
+     * Modulation undefined.
+     */
+    public static final int MODULATION_UNDEFINED = Constants.FrontendIsdbs3Modulation.UNDEFINED;
+    /**
+     * Hardware is able to detect and set modulation automatically.
+     */
+    public static final int MODULATION_AUTO = Constants.FrontendIsdbs3Modulation.AUTO;
+    /**
+     * BPSK Modulation.
+     */
+    public static final int MODULATION_MOD_BPSK = Constants.FrontendIsdbs3Modulation.MOD_BPSK;
+    /**
+     * QPSK Modulation.
+     */
+    public static final int MODULATION_MOD_QPSK = Constants.FrontendIsdbs3Modulation.MOD_QPSK;
+    /**
+     * 8PSK Modulation.
+     */
+    public static final int MODULATION_MOD_8PSK = Constants.FrontendIsdbs3Modulation.MOD_8PSK;
+    /**
+     * 16APSK Modulation.
+     */
+    public static final int MODULATION_MOD_16APSK = Constants.FrontendIsdbs3Modulation.MOD_16APSK;
+    /**
+     * 32APSK Modulation.
+     */
+    public static final int MODULATION_MOD_32APSK = Constants.FrontendIsdbs3Modulation.MOD_32APSK;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true,
+            prefix = "CODERATE_",
+            value = {CODERATE_UNDEFINED, CODERATE_AUTO, CODERATE_1_3, CODERATE_2_5, CODERATE_1_2,
+                    CODERATE_3_5, CODERATE_2_3, CODERATE_3_4, CODERATE_7_9, CODERATE_4_5,
+                    CODERATE_5_6, CODERATE_7_8, CODERATE_9_10})
+    public @interface CodeRate {}
+
+    /**
+     * Code rate undefined.
+     */
+    public static final int CODERATE_UNDEFINED = Constants.FrontendIsdbs3Coderate.UNDEFINED;
+    /**
+     * Hardware is able to detect and set code rate automatically.
+     */
+    public static final int CODERATE_AUTO = Constants.FrontendIsdbs3Coderate.AUTO;
+    /**
+     * 1/3 code rate.
+     */
+    public static final int CODERATE_1_3 = Constants.FrontendIsdbs3Coderate.CODERATE_1_3;
+    /**
+     * 2/5 code rate.
+     */
+    public static final int CODERATE_2_5 = Constants.FrontendIsdbs3Coderate.CODERATE_2_5;
+    /**
+     * 1/2 code rate.
+     */
+    public static final int CODERATE_1_2 = Constants.FrontendIsdbs3Coderate.CODERATE_1_2;
+    /**
+     * 3/5 code rate.
+     */
+    public static final int CODERATE_3_5 = Constants.FrontendIsdbs3Coderate.CODERATE_3_5;
+    /**
+     * 2/3 code rate.
+     */
+    public static final int CODERATE_2_3 = Constants.FrontendIsdbs3Coderate.CODERATE_2_3;
+    /**
+     * 3/4 code rate.
+     */
+    public static final int CODERATE_3_4 = Constants.FrontendIsdbs3Coderate.CODERATE_3_4;
+    /**
+     * 7/9 code rate.
+     */
+    public static final int CODERATE_7_9 = Constants.FrontendIsdbs3Coderate.CODERATE_7_9;
+    /**
+     * 4/5 code rate.
+     */
+    public static final int CODERATE_4_5 = Constants.FrontendIsdbs3Coderate.CODERATE_4_5;
+    /**
+     * 5/6 code rate.
+     */
+    public static final int CODERATE_5_6 = Constants.FrontendIsdbs3Coderate.CODERATE_5_6;
+    /**
+     * 7/8 code rate.
+     */
+    public static final int CODERATE_7_8 = Constants.FrontendIsdbs3Coderate.CODERATE_7_8;
+    /**
+     * 9/10 code rate.
+     */
+    public static final int CODERATE_9_10 = Constants.FrontendIsdbs3Coderate.CODERATE_9_10;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "ROLLOFF_",
+            value = {ROLLOFF_UNDEFINED, ROLLOFF_0_03})
+    public @interface Rolloff {}
+
+    /**
+     * Rolloff type undefined.
+     */
+    public static final int ROLLOFF_UNDEFINED = Constants.FrontendIsdbs3Rolloff.UNDEFINED;
+    /**
+     * 0,03 Rolloff.
+     */
+    public static final int ROLLOFF_0_03 = Constants.FrontendIsdbs3Rolloff.ROLLOFF_0_03;
+
+
+    private final int mStreamId;
+    private final int mStreamIdType;
+    private final int mModulation;
+    private final int mCodeRate;
+    private final int mSymbolRate;
+    private final int mRolloff;
+
+    private Isdbs3FrontendSettings(int frequency, int streamId, int streamIdType, int modulation,
+            int codeRate, int symbolRate, int rolloff) {
         super(frequency);
+        mStreamId = streamId;
+        mStreamIdType = streamIdType;
+        mModulation = modulation;
+        mCodeRate = codeRate;
+        mSymbolRate = symbolRate;
+        mRolloff = rolloff;
+    }
+
+    /**
+     * Gets Stream ID.
+     */
+    public int getStreamId() {
+        return mStreamId;
+    }
+    /**
+     * Gets Stream ID Type.
+     */
+    @IsdbsFrontendSettings.StreamIdType
+    public int getStreamIdType() {
+        return mStreamIdType;
+    }
+    /**
+     * Gets Modulation.
+     */
+    @Modulation
+    public int getModulation() {
+        return mModulation;
+    }
+    /**
+     * Gets Code rate.
+     */
+    @CodeRate
+    public int getCodeRate() {
+        return mCodeRate;
+    }
+    /**
+     * Gets Symbol Rate in symbols per second.
+     */
+    public int getSymbolRate() {
+        return mSymbolRate;
+    }
+    /**
+     * Gets Roll off type.
+     */
+    @Rolloff
+    public int getRolloff() {
+        return mRolloff;
+    }
+
+    /**
+     * Creates a builder for {@link Isdbs3FrontendSettings}.
+     *
+     * @param context the context of the caller.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(@NonNull Context context) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder();
+    }
+
+    /**
+     * Builder for {@link Isdbs3FrontendSettings}.
+     */
+    public static class Builder extends FrontendSettings.Builder<Builder> {
+        private int mStreamId;
+        private int mStreamIdType;
+        private int mModulation;
+        private int mCodeRate;
+        private int mSymbolRate;
+        private int mRolloff;
+
+        private Builder() {
+        }
+
+        /**
+         * Sets Stream ID.
+         */
+        @NonNull
+        public Builder setStreamId(int streamId) {
+            mStreamId = streamId;
+            return this;
+        }
+        /**
+         * Sets StreamIdType.
+         */
+        @NonNull
+        public Builder setStreamIdType(@IsdbsFrontendSettings.StreamIdType int streamIdType) {
+            mStreamIdType = streamIdType;
+            return this;
+        }
+        /**
+         * Sets Modulation.
+         */
+        @NonNull
+        public Builder setModulation(@Modulation int modulation) {
+            mModulation = modulation;
+            return this;
+        }
+        /**
+         * Sets Code rate.
+         */
+        @NonNull
+        public Builder setCodeRate(@CodeRate int codeRate) {
+            mCodeRate = codeRate;
+            return this;
+        }
+        /**
+         * Sets Symbol Rate in symbols per second.
+         */
+        @NonNull
+        public Builder setSymbolRate(int symbolRate) {
+            mSymbolRate = symbolRate;
+            return this;
+        }
+        /**
+         * Sets Roll off type.
+         */
+        @NonNull
+        public Builder setRolloff(@Rolloff int rolloff) {
+            mRolloff = rolloff;
+            return this;
+        }
+
+        /**
+         * Builds a {@link Isdbs3FrontendSettings} object.
+         */
+        @NonNull
+        public Isdbs3FrontendSettings build() {
+            return new Isdbs3FrontendSettings(mFrequency, mStreamId, mStreamIdType, mModulation,
+                    mCodeRate, mSymbolRate, mRolloff);
+        }
+
+        @Override
+        Builder self() {
+            return this;
+        }
     }
 
     @Override
     public int getType() {
-        return TunerConstants.FRONTEND_TYPE_ISDBS3;
+        return FrontendSettings.TYPE_ISDBS3;
     }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendCapabilities.java
new file mode 100644
index 0000000..38d2f1d
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendCapabilities.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 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.media.tv.tuner.frontend;
+
+import android.annotation.SystemApi;
+
+/**
+ * ISDBS Capabilities.
+ *
+ * @hide
+ */
+@SystemApi
+public class IsdbsFrontendCapabilities extends FrontendCapabilities {
+    private final int mModulationCap;
+    private final int mCodeRateCap;
+
+    private IsdbsFrontendCapabilities(int modulationCap, int codeRateCap) {
+        mModulationCap = modulationCap;
+        mCodeRateCap = codeRateCap;
+    }
+
+    /**
+     * Gets modulation capability.
+     */
+    @IsdbsFrontendSettings.Modulation
+    public int getModulationCapability() {
+        return mModulationCap;
+    }
+    /**
+     * Gets code rate capability.
+     */
+    @IsdbsFrontendSettings.CodeRate
+    public int getCodeRateCapability() {
+        return mCodeRateCap;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java
index 7fd5da7..14c08b1 100644
--- a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java
@@ -16,27 +16,275 @@
 
 package android.media.tv.tuner.frontend;
 
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * Frontend settings for ISDBS.
+ *
  * @hide
  */
+@SystemApi
 public class IsdbsFrontendSettings extends FrontendSettings {
-    public int streamId;
-    public int streamIdType;
-    public int modulation;
-    public int coderate;
-    public int symbolRate;
-    public int rolloff;
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "STREAM_ID_TYPE_",
+            value = {STREAM_ID_TYPE_ID, STREAM_ID_TYPE_RELATIVE_NUMBER})
+    public @interface StreamIdType {}
 
-    IsdbsFrontendSettings(int frequency) {
+    /**
+     * Uses stream ID.
+     */
+    public static final int STREAM_ID_TYPE_ID = Constants.FrontendIsdbsStreamIdType.STREAM_ID;
+    /**
+     * Uses relative number.
+     */
+    public static final int STREAM_ID_TYPE_RELATIVE_NUMBER =
+            Constants.FrontendIsdbsStreamIdType.RELATIVE_STREAM_NUMBER;
+
+
+    /** @hide */
+    @IntDef(flag = true,
+            prefix = "MODULATION_",
+            value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_BPSK,
+                    MODULATION_MOD_QPSK, MODULATION_MOD_TC8PSK})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Modulation {}
+
+    /**
+     * Modulation undefined.
+     */
+    public static final int MODULATION_UNDEFINED = Constants.FrontendIsdbsModulation.UNDEFINED;
+    /**
+     * Hardware is able to detect and set modulation automatically
+     */
+    public static final int MODULATION_AUTO = Constants.FrontendIsdbsModulation.AUTO;
+    /**
+     * BPSK Modulation.
+     */
+    public static final int MODULATION_MOD_BPSK = Constants.FrontendIsdbsModulation.MOD_BPSK;
+    /**
+     * QPSK Modulation.
+     */
+    public static final int MODULATION_MOD_QPSK = Constants.FrontendIsdbsModulation.MOD_QPSK;
+    /**
+     * TC8PSK Modulation.
+     */
+    public static final int MODULATION_MOD_TC8PSK = Constants.FrontendIsdbsModulation.MOD_TC8PSK;
+
+
+    /** @hide */
+    @IntDef(flag = true,
+            prefix = "CODERATE_",
+            value = {CODERATE_UNDEFINED, CODERATE_AUTO, CODERATE_1_2, CODERATE_2_3, CODERATE_3_4,
+                    CODERATE_5_6, CODERATE_7_8})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CodeRate {}
+
+    /**
+     * Code rate undefined.
+     */
+    public static final int CODERATE_UNDEFINED = Constants.FrontendIsdbsCoderate.UNDEFINED;
+    /**
+     * Hardware is able to detect and set code rate automatically.
+     */
+    public static final int CODERATE_AUTO = Constants.FrontendIsdbsCoderate.AUTO;
+    /**
+     * 1/2 code rate.
+     */
+    public static final int CODERATE_1_2 = Constants.FrontendIsdbsCoderate.CODERATE_1_2;
+    /**
+     * 2/3 code rate.
+     */
+    public static final int CODERATE_2_3 = Constants.FrontendIsdbsCoderate.CODERATE_2_3;
+    /**
+     * 3/4 code rate.
+     */
+    public static final int CODERATE_3_4 = Constants.FrontendIsdbsCoderate.CODERATE_3_4;
+    /**
+     * 5/6 code rate.
+     */
+    public static final int CODERATE_5_6 = Constants.FrontendIsdbsCoderate.CODERATE_5_6;
+    /**
+     * 7/8 code rate.
+     */
+    public static final int CODERATE_7_8 = Constants.FrontendIsdbsCoderate.CODERATE_7_8;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "ROLLOFF_",
+            value = {ROLLOFF_UNDEFINED, ROLLOFF_0_35})
+    public @interface Rolloff {}
+
+    /**
+     * Rolloff type undefined.
+     */
+    public static final int ROLLOFF_UNDEFINED = Constants.FrontendIsdbsRolloff.UNDEFINED;
+    /**
+     * 0,35 rolloff.
+     */
+    public static final int ROLLOFF_0_35 = Constants.FrontendIsdbsRolloff.ROLLOFF_0_35;
+
+
+    private final int mStreamId;
+    private final int mStreamIdType;
+    private final int mModulation;
+    private final int mCodeRate;
+    private final int mSymbolRate;
+    private final int mRolloff;
+
+    private IsdbsFrontendSettings(int frequency, int streamId, int streamIdType, int modulation,
+            int codeRate, int symbolRate, int rolloff) {
         super(frequency);
+        mStreamId = streamId;
+        mStreamIdType = streamIdType;
+        mModulation = modulation;
+        mCodeRate = codeRate;
+        mSymbolRate = symbolRate;
+        mRolloff = rolloff;
+    }
+
+    /**
+     * Gets Stream ID.
+     */
+    public int getStreamId() {
+        return mStreamId;
+    }
+    /**
+     * Gets Stream ID Type.
+     */
+    @StreamIdType
+    public int getStreamIdType() {
+        return mStreamIdType;
+    }
+    /**
+     * Gets Modulation.
+     */
+    @Modulation
+    public int getModulation() {
+        return mModulation;
+    }
+    /**
+     * Gets Code rate.
+     */
+    @CodeRate
+    public int getCodeRate() {
+        return mCodeRate;
+    }
+    /**
+     * Gets Symbol Rate in symbols per second.
+     */
+    public int getSymbolRate() {
+        return mSymbolRate;
+    }
+    /**
+     * Gets Roll off type.
+     */
+    @Rolloff
+    public int getRolloff() {
+        return mRolloff;
+    }
+
+    /**
+     * Creates a builder for {@link IsdbsFrontendSettings}.
+     *
+     * @param context the context of the caller.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(@NonNull Context context) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder();
+    }
+
+    /**
+     * Builder for {@link IsdbsFrontendSettings}.
+     */
+    public static class Builder extends FrontendSettings.Builder<Builder> {
+        private int mStreamId;
+        private int mStreamIdType;
+        private int mModulation;
+        private int mCodeRate;
+        private int mSymbolRate;
+        private int mRolloff;
+
+        private Builder() {
+        }
+
+        /**
+         * Sets Stream ID.
+         */
+        @NonNull
+        public Builder setStreamId(int streamId) {
+            mStreamId = streamId;
+            return this;
+        }
+        /**
+         * Sets StreamIdType.
+         */
+        @NonNull
+        public Builder setStreamIdType(@StreamIdType int streamIdType) {
+            mStreamIdType = streamIdType;
+            return this;
+        }
+        /**
+         * Sets Modulation.
+         */
+        @NonNull
+        public Builder setModulation(@Modulation int modulation) {
+            mModulation = modulation;
+            return this;
+        }
+        /**
+         * Sets Code rate.
+         */
+        @NonNull
+        public Builder setCodeRate(@CodeRate int codeRate) {
+            mCodeRate = codeRate;
+            return this;
+        }
+        /**
+         * Sets Symbol Rate in symbols per second.
+         */
+        @NonNull
+        public Builder setSymbolRate(int symbolRate) {
+            mSymbolRate = symbolRate;
+            return this;
+        }
+        /**
+         * Sets Roll off type.
+         */
+        @NonNull
+        public Builder setRolloff(@Rolloff int rolloff) {
+            mRolloff = rolloff;
+            return this;
+        }
+
+        /**
+         * Builds a {@link IsdbsFrontendSettings} object.
+         */
+        @NonNull
+        public IsdbsFrontendSettings build() {
+            return new IsdbsFrontendSettings(mFrequency, mStreamId, mStreamIdType, mModulation,
+                    mCodeRate, mSymbolRate, mRolloff);
+        }
+
+        @Override
+        Builder self() {
+            return this;
+        }
     }
 
     @Override
     public int getType() {
-        return TunerConstants.FRONTEND_TYPE_ISDBS;
+        return FrontendSettings.TYPE_ISDBS;
     }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendCapabilities.java
new file mode 100644
index 0000000..ffebc5a
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendCapabilities.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 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.media.tv.tuner.frontend;
+
+import android.annotation.SystemApi;
+
+/**
+ * ISDBT Capabilities.
+ *
+ * @hide
+ */
+@SystemApi
+public class IsdbtFrontendCapabilities extends FrontendCapabilities {
+    private final int mModeCap;
+    private final int mBandwidthCap;
+    private final int mModulationCap;
+    private final int mCodeRateCap;
+    private final int mGuardIntervalCap;
+
+    private IsdbtFrontendCapabilities(int modeCap, int bandwidthCap, int modulationCap,
+            int codeRateCap, int guardIntervalCap) {
+        mModeCap = modeCap;
+        mBandwidthCap = bandwidthCap;
+        mModulationCap = modulationCap;
+        mCodeRateCap = codeRateCap;
+        mGuardIntervalCap = guardIntervalCap;
+    }
+
+    /**
+     * Gets mode capability.
+     */
+    @IsdbtFrontendSettings.Mode
+    public int getModeCapability() {
+        return mModeCap;
+    }
+    /**
+     * Gets bandwidth capability.
+     */
+    @IsdbtFrontendSettings.Bandwidth
+    public int getBandwidthCapability() {
+        return mBandwidthCap;
+    }
+    /**
+     * Gets modulation capability.
+     */
+    @IsdbtFrontendSettings.Modulation
+    public int getModulationCapability() {
+        return mModulationCap;
+    }
+    /**
+     * Gets code rate capability.
+     */
+    @DvbtFrontendSettings.CodeRate
+    public int getCodeRateCapability() {
+        return mCodeRateCap;
+    }
+    /**
+     * Gets guard interval capability.
+     */
+    @DvbtFrontendSettings.GuardInterval
+    public int getGuardIntervalCapability() {
+        return mGuardIntervalCap;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
index 3f83267..de3c80d 100644
--- a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
@@ -16,27 +16,269 @@
 
 package android.media.tv.tuner.frontend;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.frontend.DvbtFrontendSettings.CodeRate;
 
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * Frontend settings for ISDBT.
+ *
  * @hide
  */
+@SystemApi
 public class IsdbtFrontendSettings extends FrontendSettings {
-    public int modulation;
-    public int bandwidth;
-    public int coderate;
-    public int guardInterval;
-    public int serviceAreaId;
+    /** @hide */
+    @IntDef(flag = true,
+            prefix = "MODULATION_",
+            value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_DQPSK,
+                    MODULATION_MOD_QPSK, MODULATION_MOD_16QAM, MODULATION_MOD_64QAM})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Modulation {}
 
-    IsdbtFrontendSettings(int frequency) {
+    /**
+     * Modulation undefined.
+     */
+    public static final int MODULATION_UNDEFINED = Constants.FrontendIsdbtModulation.UNDEFINED;
+    /**
+     * Hardware is able to detect and set modulation automatically
+     */
+    public static final int MODULATION_AUTO = Constants.FrontendIsdbtModulation.AUTO;
+    /**
+     * DQPSK Modulation.
+     */
+    public static final int MODULATION_MOD_DQPSK = Constants.FrontendIsdbtModulation.MOD_DQPSK;
+    /**
+     * QPSK Modulation.
+     */
+    public static final int MODULATION_MOD_QPSK = Constants.FrontendIsdbtModulation.MOD_QPSK;
+    /**
+     * 16QAM Modulation.
+     */
+    public static final int MODULATION_MOD_16QAM = Constants.FrontendIsdbtModulation.MOD_16QAM;
+    /**
+     * 64QAM Modulation.
+     */
+    public static final int MODULATION_MOD_64QAM = Constants.FrontendIsdbtModulation.MOD_64QAM;
+
+
+    /** @hide */
+    @IntDef(flag = true,
+            prefix = "MODE_",
+            value = {MODE_UNDEFINED, MODE_AUTO, MODE_1, MODE_2, MODE_3})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Mode {}
+
+    /**
+     * Mode undefined.
+     */
+    public static final int MODE_UNDEFINED = Constants.FrontendIsdbtMode.UNDEFINED;
+    /**
+     * Hardware is able to detect and set Mode automatically.
+     */
+    public static final int MODE_AUTO = Constants.FrontendIsdbtMode.AUTO;
+    /**
+     * Mode 1
+     */
+    public static final int MODE_1 = Constants.FrontendIsdbtMode.MODE_1;
+    /**
+     * Mode 2
+     */
+    public static final int MODE_2 = Constants.FrontendIsdbtMode.MODE_2;
+    /**
+     * Mode 3
+     */
+    public static final int MODE_3 = Constants.FrontendIsdbtMode.MODE_3;
+
+
+    /** @hide */
+    @IntDef(flag = true,
+            prefix = "BANDWIDTH_",
+            value = {BANDWIDTH_UNDEFINED, BANDWIDTH_AUTO, BANDWIDTH_8MHZ, BANDWIDTH_7MHZ,
+                    BANDWIDTH_6MHZ})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Bandwidth {}
+
+    /**
+     * Bandwidth undefined.
+     */
+    public static final int BANDWIDTH_UNDEFINED = Constants.FrontendIsdbtBandwidth.UNDEFINED;
+    /**
+     * Hardware is able to detect and set Bandwidth automatically.
+     */
+    public static final int BANDWIDTH_AUTO = Constants.FrontendIsdbtBandwidth.AUTO;
+    /**
+     * 8 MHz bandwidth.
+     */
+    public static final int BANDWIDTH_8MHZ = Constants.FrontendIsdbtBandwidth.BANDWIDTH_8MHZ;
+    /**
+     * 7 MHz bandwidth.
+     */
+    public static final int BANDWIDTH_7MHZ = Constants.FrontendIsdbtBandwidth.BANDWIDTH_7MHZ;
+    /**
+     * 6 MHz bandwidth.
+     */
+    public static final int BANDWIDTH_6MHZ = Constants.FrontendIsdbtBandwidth.BANDWIDTH_6MHZ;
+
+    private final int mModulation;
+    private final int mBandwidth;
+    private final int mMode;
+    private final int mCodeRate;
+    private final int mGuardInterval;
+    private final int mServiceAreaId;
+
+    private IsdbtFrontendSettings(int frequency, int modulation, int bandwidth, int mode,
+            int codeRate, int guardInterval, int serviceAreaId) {
         super(frequency);
+        mModulation = modulation;
+        mBandwidth = bandwidth;
+        mMode = mode;
+        mCodeRate = codeRate;
+        mGuardInterval = guardInterval;
+        mServiceAreaId = serviceAreaId;
+    }
+
+    /**
+     * Gets Modulation.
+     */
+    @Modulation
+    public int getModulation() {
+        return mModulation;
+    }
+    /**
+     * Gets Bandwidth.
+     */
+    @Bandwidth
+    public int getBandwidth() {
+        return mBandwidth;
+    }
+    /**
+     * Gets ISDBT mode.
+     */
+    @Mode
+    public int getMode() {
+        return mMode;
+    }
+    /**
+     * Gets Code rate.
+     */
+    @CodeRate
+    public int getCodeRate() {
+        return mCodeRate;
+    }
+    /**
+     * Gets Guard Interval.
+     */
+    @DvbtFrontendSettings.GuardInterval
+    public int getGuardInterval() {
+        return mGuardInterval;
+    }
+    /**
+     * Gets Service Area ID.
+     */
+    public int getServiceAreaId() {
+        return mServiceAreaId;
+    }
+
+    /**
+     * Creates a builder for {@link IsdbtFrontendSettings}.
+     *
+     * @param context the context of the caller.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @NonNull
+    public static Builder builder(@NonNull Context context) {
+        TunerUtils.checkTunerPermission(context);
+        return new Builder();
+    }
+
+    /**
+     * Builder for {@link IsdbtFrontendSettings}.
+     */
+    public static class Builder extends FrontendSettings.Builder<Builder> {
+        private int mModulation;
+        private int mBandwidth;
+        private int mMode;
+        private int mCodeRate;
+        private int mGuardInterval;
+        private int mServiceAreaId;
+
+        private Builder() {
+        }
+
+        /**
+         * Sets Modulation.
+         */
+        @NonNull
+        public Builder setModulation(@Modulation int modulation) {
+            mModulation = modulation;
+            return this;
+        }
+        /**
+         * Sets Bandwidth.
+         */
+        @NonNull
+        public Builder setBandwidth(@Bandwidth int bandwidth) {
+            mBandwidth = bandwidth;
+            return this;
+        }
+        /**
+         * Sets ISDBT mode.
+         */
+        @NonNull
+        public Builder setMode(@Mode int mode) {
+            mMode = mode;
+            return this;
+        }
+        /**
+         * Sets Code rate.
+         */
+        @NonNull
+        public Builder setCodeRate(@CodeRate int codeRate) {
+            mCodeRate = codeRate;
+            return this;
+        }
+        /**
+         * Sets Guard Interval.
+         */
+        @NonNull
+        public Builder setGuardInterval(@DvbtFrontendSettings.GuardInterval int guardInterval) {
+            mGuardInterval = guardInterval;
+            return this;
+        }
+        /**
+         * Sets Service Area ID.
+         */
+        @NonNull
+        public Builder setServiceAreaId(int serviceAreaId) {
+            mServiceAreaId = serviceAreaId;
+            return this;
+        }
+
+        /**
+         * Builds a {@link IsdbtFrontendSettings} object.
+         */
+        @NonNull
+        public IsdbtFrontendSettings build() {
+            return new IsdbtFrontendSettings(mFrequency, mModulation, mBandwidth, mMode, mCodeRate,
+                    mGuardInterval, mServiceAreaId);
+        }
+
+        @Override
+        Builder self() {
+            return this;
+        }
     }
 
     @Override
     public int getType() {
-        return TunerConstants.FRONTEND_TYPE_ISDBT;
+        return FrontendSettings.TYPE_ISDBT;
     }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/OnTuneEventListener.java b/media/java/android/media/tv/tuner/frontend/OnTuneEventListener.java
new file mode 100644
index 0000000..5cf0d31
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/OnTuneEventListener.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+import android.annotation.IntDef;
+import android.annotation.SystemApi;
+import android.hardware.tv.tuner.V1_0.Constants;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Listens for tune events.
+ *
+ * @hide
+ */
+@SystemApi
+public interface OnTuneEventListener {
+
+    /** @hide */
+    @IntDef(prefix = "SIGNAL_", value = {SIGNAL_LOCKED, SIGNAL_NO_SIGNAL, SIGNAL_LOST_LOCK})
+    @Retention(RetentionPolicy.SOURCE)
+    @interface TuneEvent {}
+
+    /** The frontend has locked to the signal specified by the tune method. */
+    int SIGNAL_LOCKED = Constants.FrontendEventType.LOCKED;
+    /** The frontend is unable to lock to the signal specified by the tune method. */
+    int SIGNAL_NO_SIGNAL = Constants.FrontendEventType.NO_SIGNAL;
+    /** The frontend has lost the lock to the signal specified by the tune method. */
+    int SIGNAL_LOST_LOCK = Constants.FrontendEventType.LOST_LOCK;
+
+    /** Tune Event from the frontend */
+    void onTuneEvent(@TuneEvent int tuneEvent);
+}
diff --git a/media/java/android/media/tv/tuner/frontend/ScanCallback.java b/media/java/android/media/tv/tuner/frontend/ScanCallback.java
new file mode 100644
index 0000000..f90144b
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/ScanCallback.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+/**
+ * Scan callback.
+ *
+ * @hide
+ */
+@SystemApi
+public interface ScanCallback {
+
+    /** Scan locked the signal. */
+    void onLocked();
+
+    /** Scan stopped. */
+    void onScanStopped();
+
+    /** scan progress percent (0..100) */
+    void onProgress(@IntRange(from = 0, to = 100) int percent);
+
+    /** Signal frequencies in Hertz */
+    void onFrequenciesReport(@NonNull int[] frequency);
+
+    /** Symbols per second */
+    void onSymbolRates(@NonNull int[] rate);
+
+    /** Locked Plp Ids for DVBT2 frontend. */
+    void onPlpIds(@NonNull int[] plpIds);
+
+    /** Locked group Ids for DVBT2 frontend. */
+    void onGroupIds(@NonNull int[] groupIds);
+
+    /** Stream Ids. */
+    void onInputStreamIds(@NonNull int[] inputStreamIds);
+
+    /** Locked signal standard for DVBS. */
+    void onDvbsStandard(@DvbsFrontendSettings.Standard int dvbsStandandard);
+
+    /** Locked signal standard. for DVBT */
+    void onDvbtStandard(@DvbtFrontendSettings.Standard int dvbtStandard);
+
+    /** Locked signal SIF standard for Analog. */
+    void onAnalogSifStandard(@AnalogFrontendSettings.SifStandard int sif);
+
+    /** PLP status in a tuned frequency band for ATSC3 frontend. */
+    void onAtsc3PlpInfos(@NonNull Atsc3PlpInfo[] atsc3PlpInfos);
+
+    /** Frontend hierarchy. */
+    void onHierarchy(@DvbtFrontendSettings.Hierarchy int hierarchy);
+
+    /** Frontend hierarchy. */
+    void onSignalType(@AnalogFrontendSettings.SignalType int signalType);
+
+}
diff --git a/media/java/android/media/voice/KeyphraseModelManager.java b/media/java/android/media/voice/KeyphraseModelManager.java
new file mode 100644
index 0000000..3fa38e0
--- /dev/null
+++ b/media/java/android/media/voice/KeyphraseModelManager.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.voice;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.hardware.soundtrigger.SoundTrigger;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Slog;
+
+import com.android.internal.app.IVoiceInteractionManagerService;
+
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * This class provides management of voice based sound recognition models. Usage of this class is
+ * restricted to system or signature applications only. This allows OEMs to write apps that can
+ * manage voice based sound trigger models.
+ * Callers of this class are expected to have whitelist manifest permission MANAGE_VOICE_KEYPHRASES.
+ * Callers of this class are expected to be the designated voice interaction service via
+ * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}.
+ * @hide
+ */
+@SystemApi
+public final class KeyphraseModelManager {
+    private static final boolean DBG = false;
+    private static final String TAG = "KeyphraseModelManager";
+
+    private final IVoiceInteractionManagerService mVoiceInteractionManagerService;
+
+    /**
+     * @hide
+     */
+    public KeyphraseModelManager(
+            IVoiceInteractionManagerService voiceInteractionManagerService) {
+        if (DBG) {
+            Slog.i(TAG, "KeyphraseModelManager created.");
+        }
+        mVoiceInteractionManagerService = voiceInteractionManagerService;
+    }
+
+
+    /**
+     * Gets the registered sound model for keyphrase detection for the current user.
+     * The keyphraseId and locale passed must match a supported model passed in via
+     * {@link #updateKeyphraseSoundModel}.
+     * If the active voice interaction service changes from the current user, all requests will be
+     * rejected, and any registered models will be unregistered.
+     *
+     * @param keyphraseId The unique identifier for the keyphrase.
+     * @param locale The locale language tag supported by the desired model.
+     * @return Registered keyphrase sound model matching the keyphrase ID and locale. May be null if
+     * no matching sound model exists.
+     * @throws SecurityException Thrown when caller does not have MANAGE_VOICE_KEYPHRASES permission
+     *                           or if the caller is not the active voice interaction service.
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES)
+    @Nullable
+    public SoundTrigger.KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId,
+            @NonNull Locale locale) {
+        Objects.requireNonNull(locale);
+        try {
+            return mVoiceInteractionManagerService.getKeyphraseSoundModel(keyphraseId,
+                    locale.toLanguageTag());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Add or update the given keyphrase sound model to the registered models pool for the current
+     * user.
+     * If a model exists with the same Keyphrase ID, locale, and user list. The registered model
+     * will be overwritten with the new model.
+     * If the active voice interaction service changes from the current user, all requests will be
+     * rejected, and any registered models will be unregistered.
+     *
+     * @param model Keyphrase sound model to be updated.
+     * @throws ServiceSpecificException Thrown with error code if failed to update the keyphrase
+     *                           sound model.
+     * @throws SecurityException Thrown when caller does not have MANAGE_VOICE_KEYPHRASES permission
+     *                           or if the caller is not the active voice interaction service.
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES)
+    public void updateKeyphraseSoundModel(@NonNull SoundTrigger.KeyphraseSoundModel model) {
+        Objects.requireNonNull(model);
+        try {
+            int status = mVoiceInteractionManagerService.updateKeyphraseSoundModel(model);
+            if (status != SoundTrigger.STATUS_OK) {
+                throw new ServiceSpecificException(status);
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Delete keyphrase sound model from the registered models pool for the current user matching\
+     * the keyphrase ID and locale.
+     * The keyphraseId and locale passed must match a supported model passed in via
+     * {@link #updateKeyphraseSoundModel}.
+     * If the active voice interaction service changes from the current user, all requests will be
+     * rejected, and any registered models will be unregistered.
+     *
+     * @param keyphraseId The unique identifier for the keyphrase.
+     * @param locale The locale language tag supported by the desired model.
+     * @throws ServiceSpecificException Thrown with error code if failed to delete the keyphrase
+     *                           sound model.
+     * @throws SecurityException Thrown when caller does not have MANAGE_VOICE_KEYPHRASES permission
+     *                           or if the caller is not the active voice interaction service.
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES)
+    public void deleteKeyphraseSoundModel(int keyphraseId, @NonNull Locale locale) {
+        Objects.requireNonNull(locale);
+        try {
+            int status = mVoiceInteractionManagerService.deleteKeyphraseSoundModel(keyphraseId,
+                    locale.toLanguageTag());
+            if (status != SoundTrigger.STATUS_OK) {
+                throw new ServiceSpecificException(status);
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/media/java/android/mtp/MtpPropertyList.java b/media/java/android/mtp/MtpPropertyList.java
index 557f099..53d838d 100644
--- a/media/java/android/mtp/MtpPropertyList.java
+++ b/media/java/android/mtp/MtpPropertyList.java
@@ -16,7 +16,8 @@
 
 package android.mtp;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
 import java.util.ArrayList;
 import java.util.List;
 
diff --git a/media/java/android/mtp/MtpStorage.java b/media/java/android/mtp/MtpStorage.java
index c7dbca6..ba75263 100644
--- a/media/java/android/mtp/MtpStorage.java
+++ b/media/java/android/mtp/MtpStorage.java
@@ -16,9 +16,8 @@
 
 package android.mtp;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.storage.StorageVolume;
-import android.provider.MediaStore;
 
 /**
  * This class represents a storage unit on an MTP device.
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java
index 86a1076..06adf30 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/java/android/service/media/MediaBrowserService.java
@@ -21,8 +21,8 @@
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
-import android.annotation.UnsupportedAppUsage;
 import android.app.Service;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index ee67613..c17b1b7 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -1,6 +1,8 @@
 cc_library_shared {
     name: "libmedia_jni",
 
+    defaults: ["libcodec2-internal-defaults"],
+
     srcs: [
         "android_media_ImageWriter.cpp",
         "android_media_ImageReader.cpp",
@@ -19,6 +21,7 @@
         "android_media_MediaProfiles.cpp",
         "android_media_MediaRecorder.cpp",
         "android_media_MediaSync.cpp",
+        "android_media_MediaTranscodeManager.cpp",
         "android_media_ResampleInputStream.cpp",
         "android_media_Streams.cpp",
         "android_media_SyncParams.cpp",
@@ -143,6 +146,10 @@
         "libutils",
     ],
 
+    header_libs: [
+        "libstagefright_foundation_headers",
+    ],
+
     export_include_dirs: ["."],
 
     cflags: [
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 20980a9..ab6966d 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -18,6 +18,8 @@
 #define LOG_TAG "MediaCodec-JNI"
 #include <utils/Log.h>
 
+#include <type_traits>
+
 #include "android_media_MediaCodec.h"
 
 #include "android_media_MediaCrypto.h"
@@ -31,13 +33,20 @@
 #include <nativehelper/JNIHelp.h>
 #include <nativehelper/ScopedLocalRef.h>
 
+#include <C2Buffer.h>
+
 #include <android/hardware/cas/native/1.0/IDescrambler.h>
 
+#include <binder/MemoryHeapBase.h>
+
 #include <cutils/compiler.h>
 
 #include <gui/Surface.h>
 
+#include <hidlmemory/FrameworkUtils.h>
+
 #include <media/MediaCodecBuffer.h>
+#include <media/hardware/VideoAPI.h>
 #include <media/stagefright/MediaCodec.h>
 #include <media/stagefright/foundation/ABuffer.h>
 #include <media/stagefright/foundation/ADebug.h>
@@ -47,7 +56,6 @@
 #include <media/stagefright/MediaErrors.h>
 #include <media/stagefright/PersistentSurface.h>
 #include <mediadrm/ICrypto.h>
-#include <nativehelper/ScopedLocalRef.h>
 
 #include <system/window.h>
 
@@ -110,6 +118,39 @@
     jfieldID levelField;
 } gCodecInfo;
 
+static struct {
+    jclass clazz;
+    jobject nativeByteOrder;
+    jmethodID orderId;
+    jmethodID asReadOnlyBufferId;
+    jmethodID positionId;
+    jmethodID limitId;
+} gByteBufferInfo;
+
+static struct {
+    jmethodID sizeId;
+    jmethodID getId;
+    jmethodID addId;
+} gArrayListInfo;
+
+static struct {
+    jclass clazz;
+    jmethodID ctorId;
+    jmethodID setInternalStateId;
+    jfieldID contextId;
+    jfieldID validId;
+    jfieldID lockId;
+} gLinearBlockInfo;
+
+static struct {
+    jclass clazz;
+    jmethodID ctorId;
+    jmethodID setInternalStateId;
+    jfieldID contextId;
+    jfieldID validId;
+    jfieldID lockId;
+} gGraphicBlockInfo;
+
 struct fields_t {
     jmethodID postEventFromNativeID;
     jmethodID lockAndGetContextID;
@@ -123,11 +164,65 @@
     jfieldID cryptoInfoPatternID;
     jfieldID patternEncryptBlocksID;
     jfieldID patternSkipBlocksID;
+    jfieldID queueRequestIndexID;
+    jfieldID outputFrameLinearBlockID;
+    jfieldID outputFrameGraphicBlockID;
+    jfieldID outputFrameChangedKeysID;
+    jfieldID outputFrameFormatID;
 };
 
 static fields_t gFields;
 static const void *sRefBaseOwner;
 
+struct JMediaCodecLinearBlock {
+    std::shared_ptr<C2Buffer> mBuffer;
+    std::shared_ptr<C2ReadView> mReadonlyMapping;
+
+    std::shared_ptr<C2LinearBlock> mBlock;
+    std::shared_ptr<C2WriteView> mReadWriteMapping;
+
+    sp<IMemoryHeap> mHeap;
+    sp<hardware::HidlMemory> mMemory;
+
+    sp<MediaCodecBuffer> mLegacyBuffer;
+
+    std::once_flag mCopyWarningFlag;
+
+    std::shared_ptr<C2Buffer> toC2Buffer(size_t offset, size_t size) {
+        if (mBuffer) {
+            if (mBuffer->data().type() != C2BufferData::LINEAR) {
+                return nullptr;
+            }
+            C2ConstLinearBlock block = mBuffer->data().linearBlocks().front();
+            if (offset == 0 && size == block.capacity()) {
+                return mBuffer;
+            }
+            return C2Buffer::CreateLinearBuffer(block.subBlock(offset, size));
+        }
+        if (mBlock) {
+            return C2Buffer::CreateLinearBuffer(mBlock->share(offset, size, C2Fence{}));
+        }
+        return nullptr;
+    }
+
+    sp<hardware::HidlMemory> toHidlMemory() {
+        if (mMemory) {
+            return mMemory;
+        }
+        return nullptr;
+    }
+};
+
+struct JMediaCodecGraphicBlock {
+    std::shared_ptr<C2Buffer> mBuffer;
+    std::shared_ptr<const C2GraphicView> mReadonlyMapping;
+
+    std::shared_ptr<C2GraphicBlock> mBlock;
+    std::shared_ptr<C2GraphicView> mReadWriteMapping;
+
+    sp<MediaCodecBuffer> mLegacyBuffer;
+};
+
 ////////////////////////////////////////////////////////////////////////////////
 
 JMediaCodec::JMediaCodec(
@@ -141,8 +236,6 @@
     mClass = (jclass)env->NewGlobalRef(clazz);
     mObject = env->NewWeakGlobalRef(thiz);
 
-    cacheJavaObjects(env);
-
     mLooper = new ALooper;
     mLooper->setName("MediaCodec_looper");
 
@@ -163,45 +256,6 @@
     CHECK((mCodec != NULL) != (mInitStatus != OK));
 }
 
-void JMediaCodec::cacheJavaObjects(JNIEnv *env) {
-    jclass clazz = (jclass)env->FindClass("java/nio/ByteBuffer");
-    mByteBufferClass = (jclass)env->NewGlobalRef(clazz);
-    CHECK(mByteBufferClass != NULL);
-
-    ScopedLocalRef<jclass> byteOrderClass(
-            env, env->FindClass("java/nio/ByteOrder"));
-    CHECK(byteOrderClass.get() != NULL);
-
-    jmethodID nativeOrderID = env->GetStaticMethodID(
-            byteOrderClass.get(), "nativeOrder", "()Ljava/nio/ByteOrder;");
-    CHECK(nativeOrderID != NULL);
-
-    jobject nativeByteOrderObj =
-        env->CallStaticObjectMethod(byteOrderClass.get(), nativeOrderID);
-    mNativeByteOrderObj = env->NewGlobalRef(nativeByteOrderObj);
-    CHECK(mNativeByteOrderObj != NULL);
-    env->DeleteLocalRef(nativeByteOrderObj);
-    nativeByteOrderObj = NULL;
-
-    mByteBufferOrderMethodID = env->GetMethodID(
-            mByteBufferClass,
-            "order",
-            "(Ljava/nio/ByteOrder;)Ljava/nio/ByteBuffer;");
-    CHECK(mByteBufferOrderMethodID != NULL);
-
-    mByteBufferAsReadOnlyBufferMethodID = env->GetMethodID(
-            mByteBufferClass, "asReadOnlyBuffer", "()Ljava/nio/ByteBuffer;");
-    CHECK(mByteBufferAsReadOnlyBufferMethodID != NULL);
-
-    mByteBufferPositionMethodID = env->GetMethodID(
-            mByteBufferClass, "position", "(I)Ljava/nio/Buffer;");
-    CHECK(mByteBufferPositionMethodID != NULL);
-
-    mByteBufferLimitMethodID = env->GetMethodID(
-            mByteBufferClass, "limit", "(I)Ljava/nio/Buffer;");
-    CHECK(mByteBufferLimitMethodID != NULL);
-}
-
 status_t JMediaCodec::initCheck() const {
     return mInitStatus;
 }
@@ -247,19 +301,6 @@
     mObject = NULL;
     env->DeleteGlobalRef(mClass);
     mClass = NULL;
-    deleteJavaObjects(env);
-}
-
-void JMediaCodec::deleteJavaObjects(JNIEnv *env) {
-    env->DeleteGlobalRef(mByteBufferClass);
-    mByteBufferClass = NULL;
-    env->DeleteGlobalRef(mNativeByteOrderObj);
-    mNativeByteOrderObj = NULL;
-
-    mByteBufferOrderMethodID = NULL;
-    mByteBufferAsReadOnlyBufferMethodID = NULL;
-    mByteBufferPositionMethodID = NULL;
-    mByteBufferLimitMethodID = NULL;
 }
 
 status_t JMediaCodec::enableOnFrameRenderedListener(jboolean enable) {
@@ -300,6 +341,12 @@
         mSurfaceTextureClient.clear();
     }
 
+    constexpr int32_t CONFIGURE_FLAG_ENCODE = 1;
+    AString mime;
+    CHECK(format->findString("mime", &mime));
+    mGraphicOutput = (mime.startsWithIgnoreCase("video/") || mime.startsWithIgnoreCase("image/"))
+            && !(flags & CONFIGURE_FLAG_ENCODE);
+
     return mCodec->configure(
             format, mSurfaceTextureClient, crypto, descrambler, flags);
 }
@@ -370,6 +417,32 @@
             presentationTimeUs, flags, errorDetailMsg);
 }
 
+status_t JMediaCodec::queueBuffer(
+        size_t index, const std::shared_ptr<C2Buffer> &buffer, int64_t timeUs,
+        uint32_t flags, const sp<AMessage> &tunings, AString *errorDetailMsg) {
+    return mCodec->queueBuffer(
+            index, buffer, timeUs, flags, tunings, errorDetailMsg);
+}
+
+status_t JMediaCodec::queueEncryptedLinearBlock(
+        size_t index,
+        const sp<hardware::HidlMemory> &buffer,
+        size_t offset,
+        const CryptoPlugin::SubSample *subSamples,
+        size_t numSubSamples,
+        const uint8_t key[16],
+        const uint8_t iv[16],
+        CryptoPlugin::Mode mode,
+        const CryptoPlugin::Pattern &pattern,
+        int64_t presentationTimeUs,
+        uint32_t flags,
+        const sp<AMessage> &tunings,
+        AString *errorDetailMsg) {
+    return mCodec->queueEncryptedBuffer(
+            index, buffer, offset, subSamples, numSubSamples, key, iv, mode, pattern,
+            presentationTimeUs, flags, tunings, errorDetailMsg);
+}
+
 status_t JMediaCodec::dequeueInputBuffer(size_t *index, int64_t timeoutUs) {
     return mCodec->dequeueInputBuffer(index, timeoutUs);
 }
@@ -444,7 +517,7 @@
     }
 
     *bufArray = (jobjectArray)env->NewObjectArray(
-            buffers.size(), mByteBufferClass, NULL);
+            buffers.size(), gByteBufferInfo.clazz, NULL);
     if (*bufArray == NULL) {
         return NO_MEMORY;
     }
@@ -470,6 +543,39 @@
     return OK;
 }
 
+template <typename T>
+static jobject CreateByteBuffer(
+        JNIEnv *env, T *base, size_t capacity, size_t offset, size_t size,
+        bool readOnly, bool clearBuffer) {
+    jobject byteBuffer =
+        env->NewDirectByteBuffer(
+                const_cast<typename std::remove_const<T>::type *>(base),
+                capacity);
+    if (readOnly && byteBuffer != NULL) {
+        jobject readOnlyBuffer = env->CallObjectMethod(
+                byteBuffer, gByteBufferInfo.asReadOnlyBufferId);
+        env->DeleteLocalRef(byteBuffer);
+        byteBuffer = readOnlyBuffer;
+    }
+    if (byteBuffer == NULL) {
+        return nullptr;
+    }
+    jobject me = env->CallObjectMethod(
+            byteBuffer, gByteBufferInfo.orderId, gByteBufferInfo.nativeByteOrder);
+    env->DeleteLocalRef(me);
+    me = env->CallObjectMethod(
+            byteBuffer, gByteBufferInfo.limitId,
+            clearBuffer ? capacity : offset + size);
+    env->DeleteLocalRef(me);
+    me = env->CallObjectMethod(
+            byteBuffer, gByteBufferInfo.positionId,
+            clearBuffer ? 0 : offset);
+    env->DeleteLocalRef(me);
+    me = NULL;
+    return byteBuffer;
+}
+
+
 // static
 template <typename T>
 status_t JMediaCodec::createByteBufferFromABuffer(
@@ -488,29 +594,9 @@
         return OK;
     }
 
-    jobject byteBuffer =
-        env->NewDirectByteBuffer(buffer->base(), buffer->capacity());
-    if (readOnly && byteBuffer != NULL) {
-        jobject readOnlyBuffer = env->CallObjectMethod(
-                byteBuffer, mByteBufferAsReadOnlyBufferMethodID);
-        env->DeleteLocalRef(byteBuffer);
-        byteBuffer = readOnlyBuffer;
-    }
-    if (byteBuffer == NULL) {
-        return NO_MEMORY;
-    }
-    jobject me = env->CallObjectMethod(
-            byteBuffer, mByteBufferOrderMethodID, mNativeByteOrderObj);
-    env->DeleteLocalRef(me);
-    me = env->CallObjectMethod(
-            byteBuffer, mByteBufferLimitMethodID,
-            clearBuffer ? buffer->capacity() : (buffer->offset() + buffer->size()));
-    env->DeleteLocalRef(me);
-    me = env->CallObjectMethod(
-            byteBuffer, mByteBufferPositionMethodID,
-            clearBuffer ? 0 : buffer->offset());
-    env->DeleteLocalRef(me);
-    me = NULL;
+    jobject byteBuffer = CreateByteBuffer(
+            env, buffer->base(), buffer->capacity(), buffer->offset(), buffer->size(),
+            readOnly, clearBuffer);
 
     *buf = byteBuffer;
     return OK;
@@ -628,6 +714,92 @@
     return OK;
 }
 
+status_t JMediaCodec::getOutputFrame(
+        JNIEnv *env, jobject frame, size_t index) const {
+    sp<MediaCodecBuffer> buffer;
+
+    status_t err = mCodec->getOutputBuffer(index, &buffer);
+    if (err != OK) {
+        return err;
+    }
+
+    if (buffer->size() > 0) {
+        // asC2Buffer clears internal reference, so set the reference again.
+        std::shared_ptr<C2Buffer> c2Buffer = buffer->asC2Buffer();
+        buffer->copy(c2Buffer);
+        if (c2Buffer) {
+            switch (c2Buffer->data().type()) {
+                case C2BufferData::LINEAR: {
+                    std::unique_ptr<JMediaCodecLinearBlock> context{new JMediaCodecLinearBlock};
+                    context->mBuffer = c2Buffer;
+                    ScopedLocalRef<jobject> linearBlock{env, env->NewObject(
+                            gLinearBlockInfo.clazz, gLinearBlockInfo.ctorId)};
+                    env->SetLongField(
+                            linearBlock.get(), gLinearBlockInfo.contextId, (jlong)context.release());
+                    env->SetObjectField(frame, gFields.outputFrameLinearBlockID, linearBlock.get());
+                    break;
+                }
+                case C2BufferData::GRAPHIC: {
+                    std::unique_ptr<JMediaCodecGraphicBlock> context{new JMediaCodecGraphicBlock};
+                    context->mBuffer = c2Buffer;
+                    ScopedLocalRef<jobject> graphicBlock{env, env->NewObject(
+                            gGraphicBlockInfo.clazz, gGraphicBlockInfo.ctorId)};
+                    env->SetLongField(
+                            graphicBlock.get(), gGraphicBlockInfo.contextId, (jlong)context.release());
+                    env->SetObjectField(frame, gFields.outputFrameGraphicBlockID, graphicBlock.get());
+                    break;
+                }
+                case C2BufferData::LINEAR_CHUNKS:  [[fallthrough]];
+                case C2BufferData::GRAPHIC_CHUNKS: [[fallthrough]];
+                case C2BufferData::INVALID:        [[fallthrough]];
+                default:
+                    return INVALID_OPERATION;
+            }
+        } else {
+            if (!mGraphicOutput) {
+                std::unique_ptr<JMediaCodecLinearBlock> context{new JMediaCodecLinearBlock};
+                context->mLegacyBuffer = buffer;
+                ScopedLocalRef<jobject> linearBlock{env, env->NewObject(
+                        gLinearBlockInfo.clazz, gLinearBlockInfo.ctorId)};
+                env->SetLongField(
+                        linearBlock.get(), gLinearBlockInfo.contextId, (jlong)context.release());
+                env->SetObjectField(frame, gFields.outputFrameLinearBlockID, linearBlock.get());
+            } else {
+                std::unique_ptr<JMediaCodecGraphicBlock> context{new JMediaCodecGraphicBlock};
+                context->mLegacyBuffer = buffer;
+                ScopedLocalRef<jobject> graphicBlock{env, env->NewObject(
+                        gGraphicBlockInfo.clazz, gGraphicBlockInfo.ctorId)};
+                env->SetLongField(
+                        graphicBlock.get(), gGraphicBlockInfo.contextId, (jlong)context.release());
+                env->SetObjectField(frame, gFields.outputFrameGraphicBlockID, graphicBlock.get());
+            }
+        }
+    }
+
+    jobject format;
+    err = getOutputFormat(env, index, &format);
+    if (err != OK) {
+        return err;
+    }
+    env->SetObjectField(frame, gFields.outputFrameFormatID, format);
+    env->DeleteLocalRef(format);
+    format = nullptr;
+
+    sp<RefBase> obj;
+    if (buffer->meta()->findObject("changedKeys", &obj) && obj) {
+        sp<MediaCodec::WrapperObject<std::set<std::string>>> changedKeys{
+            (decltype(changedKeys.get()))obj.get()};
+        ScopedLocalRef<jobject> changedKeysObj{env, env->GetObjectField(
+                frame, gFields.outputFrameChangedKeysID)};
+        for (const std::string &key : changedKeys->value) {
+            ScopedLocalRef<jstring> keyStr{env, env->NewStringUTF(key.c_str())};
+            (void)env->CallBooleanMethod(changedKeysObj.get(), gArrayListInfo.addId, keyStr.get());
+        }
+    }
+    return OK;
+}
+
+
 status_t JMediaCodec::getName(JNIEnv *env, jstring *nameStr) const {
     AString name;
 
@@ -1428,6 +1600,139 @@
             env, err, ACTION_CODE_FATAL, errorDetailMsg.empty() ? NULL : errorDetailMsg.c_str());
 }
 
+struct NativeCryptoInfo {
+    NativeCryptoInfo(JNIEnv *env, jobject cryptoInfoObj)
+        : mEnv{env},
+          mIvObj{env, (jbyteArray)env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoIVID)},
+          mKeyObj{env, (jbyteArray)env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoKeyID)} {
+        mNumSubSamples = env->GetIntField(cryptoInfoObj, gFields.cryptoInfoNumSubSamplesID);
+
+        ScopedLocalRef<jintArray> numBytesOfClearDataObj{env, (jintArray)env->GetObjectField(
+                cryptoInfoObj, gFields.cryptoInfoNumBytesOfClearDataID)};
+
+        ScopedLocalRef<jintArray> numBytesOfEncryptedDataObj{env, (jintArray)env->GetObjectField(
+                cryptoInfoObj, gFields.cryptoInfoNumBytesOfEncryptedDataID)};
+
+        jint jmode = env->GetIntField(cryptoInfoObj, gFields.cryptoInfoModeID);
+        if (jmode == gCryptoModes.Unencrypted) {
+            mMode = CryptoPlugin::kMode_Unencrypted;
+        } else if (jmode == gCryptoModes.AesCtr) {
+            mMode = CryptoPlugin::kMode_AES_CTR;
+        } else if (jmode == gCryptoModes.AesCbc) {
+            mMode = CryptoPlugin::kMode_AES_CBC;
+        }  else {
+            throwExceptionAsNecessary(env, INVALID_OPERATION);
+            return;
+        }
+
+        ScopedLocalRef<jobject> patternObj{
+            env, env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoPatternID)};
+
+        CryptoPlugin::Pattern pattern;
+        if (patternObj.get() == nullptr) {
+            pattern.mEncryptBlocks = 0;
+            pattern.mSkipBlocks = 0;
+        } else {
+            pattern.mEncryptBlocks = env->GetIntField(
+                    patternObj.get(), gFields.patternEncryptBlocksID);
+            pattern.mSkipBlocks = env->GetIntField(
+                    patternObj.get(), gFields.patternSkipBlocksID);
+        }
+
+        mErr = OK;
+        if (mNumSubSamples <= 0) {
+            mErr = -EINVAL;
+        } else if (numBytesOfClearDataObj == nullptr
+                && numBytesOfEncryptedDataObj == nullptr) {
+            mErr = -EINVAL;
+        } else if (numBytesOfEncryptedDataObj != nullptr
+                && env->GetArrayLength(numBytesOfEncryptedDataObj.get()) < mNumSubSamples) {
+            mErr = -ERANGE;
+        } else if (numBytesOfClearDataObj != nullptr
+                && env->GetArrayLength(numBytesOfClearDataObj.get()) < mNumSubSamples) {
+            mErr = -ERANGE;
+        // subSamples array may silently overflow if number of samples are too large.  Use
+        // INT32_MAX as maximum allocation size may be less than SIZE_MAX on some platforms
+        } else if (CC_UNLIKELY(mNumSubSamples >= (signed)(INT32_MAX / sizeof(*mSubSamples))) ) {
+            mErr = -EINVAL;
+        } else {
+            jint *numBytesOfClearData =
+                (numBytesOfClearDataObj == nullptr)
+                    ? nullptr
+                    : env->GetIntArrayElements(numBytesOfClearDataObj.get(), nullptr);
+
+            jint *numBytesOfEncryptedData =
+                (numBytesOfEncryptedDataObj == nullptr)
+                    ? nullptr
+                    : env->GetIntArrayElements(numBytesOfEncryptedDataObj.get(), nullptr);
+
+            mSubSamples = new CryptoPlugin::SubSample[mNumSubSamples];
+
+            for (jint i = 0; i < mNumSubSamples; ++i) {
+                mSubSamples[i].mNumBytesOfClearData =
+                    (numBytesOfClearData == nullptr) ? 0 : numBytesOfClearData[i];
+
+                mSubSamples[i].mNumBytesOfEncryptedData =
+                    (numBytesOfEncryptedData == nullptr) ? 0 : numBytesOfEncryptedData[i];
+            }
+
+            if (numBytesOfEncryptedData != nullptr) {
+                env->ReleaseIntArrayElements(
+                        numBytesOfEncryptedDataObj.get(), numBytesOfEncryptedData, 0);
+                numBytesOfEncryptedData = nullptr;
+            }
+
+            if (numBytesOfClearData != nullptr) {
+                env->ReleaseIntArrayElements(
+                        numBytesOfClearDataObj.get(), numBytesOfClearData, 0);
+                numBytesOfClearData = nullptr;
+            }
+        }
+
+        if (mErr == OK && mKeyObj.get() != nullptr) {
+            if (env->GetArrayLength(mKeyObj.get()) != 16) {
+                mErr = -EINVAL;
+            } else {
+                mKey = env->GetByteArrayElements(mKeyObj.get(), nullptr);
+            }
+        }
+
+        if (mErr == OK && mIvObj.get() != nullptr) {
+            if (env->GetArrayLength(mIvObj.get()) != 16) {
+                mErr = -EINVAL;
+            } else {
+                mIv = env->GetByteArrayElements(mIvObj.get(), nullptr);
+            }
+        }
+    }
+
+    ~NativeCryptoInfo() {
+        if (mIv != nullptr) {
+            mEnv->ReleaseByteArrayElements(mIvObj.get(), mIv, 0);
+        }
+
+        if (mKey != nullptr) {
+            mEnv->ReleaseByteArrayElements(mKeyObj.get(), mKey, 0);
+        }
+
+        if (mSubSamples != nullptr) {
+            delete[] mSubSamples;
+        }
+    }
+
+    JNIEnv *mEnv{nullptr};
+    ScopedLocalRef<jbyteArray> mIvObj;
+    ScopedLocalRef<jbyteArray> mKeyObj;
+    status_t mErr{OK};
+
+    CryptoPlugin::SubSample *mSubSamples{nullptr};
+    int32_t mNumSubSamples{0};
+    jbyte *mIv{nullptr};
+    jbyte *mKey{nullptr};
+    enum CryptoPlugin::Mode mMode;
+    CryptoPlugin::Pattern mPattern;
+};
+
 static void android_media_MediaCodec_queueSecureInputBuffer(
         JNIEnv *env,
         jobject thiz,
@@ -1445,154 +1750,276 @@
         return;
     }
 
-    jint numSubSamples =
-        env->GetIntField(cryptoInfoObj, gFields.cryptoInfoNumSubSamplesID);
-
-    jintArray numBytesOfClearDataObj =
-        (jintArray)env->GetObjectField(
-                cryptoInfoObj, gFields.cryptoInfoNumBytesOfClearDataID);
-
-    jintArray numBytesOfEncryptedDataObj =
-        (jintArray)env->GetObjectField(
-                cryptoInfoObj, gFields.cryptoInfoNumBytesOfEncryptedDataID);
-
-    jbyteArray keyObj =
-        (jbyteArray)env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoKeyID);
-
-    jbyteArray ivObj =
-        (jbyteArray)env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoIVID);
-
-    jint jmode = env->GetIntField(cryptoInfoObj, gFields.cryptoInfoModeID);
-    enum CryptoPlugin::Mode mode;
-    if (jmode == gCryptoModes.Unencrypted) {
-        mode = CryptoPlugin::kMode_Unencrypted;
-    } else if (jmode == gCryptoModes.AesCtr) {
-        mode = CryptoPlugin::kMode_AES_CTR;
-    } else if (jmode == gCryptoModes.AesCbc) {
-        mode = CryptoPlugin::kMode_AES_CBC;
-    }  else {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
-        return;
-    }
-
-    jobject patternObj = env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoPatternID);
-
-    CryptoPlugin::Pattern pattern;
-    if (patternObj == NULL) {
-        pattern.mEncryptBlocks = 0;
-        pattern.mSkipBlocks = 0;
-    } else {
-        pattern.mEncryptBlocks = env->GetIntField(patternObj, gFields.patternEncryptBlocksID);
-        pattern.mSkipBlocks = env->GetIntField(patternObj, gFields.patternSkipBlocksID);
-    }
-
-    status_t err = OK;
-
-    CryptoPlugin::SubSample *subSamples = NULL;
-    jbyte *key = NULL;
-    jbyte *iv = NULL;
-
-    if (numSubSamples <= 0) {
-        err = -EINVAL;
-    } else if (numBytesOfClearDataObj == NULL
-            && numBytesOfEncryptedDataObj == NULL) {
-        err = -EINVAL;
-    } else if (numBytesOfEncryptedDataObj != NULL
-            && env->GetArrayLength(numBytesOfEncryptedDataObj) < numSubSamples) {
-        err = -ERANGE;
-    } else if (numBytesOfClearDataObj != NULL
-            && env->GetArrayLength(numBytesOfClearDataObj) < numSubSamples) {
-        err = -ERANGE;
-    // subSamples array may silently overflow if number of samples are too large.  Use
-    // INT32_MAX as maximum allocation size may be less than SIZE_MAX on some platforms
-    } else if ( CC_UNLIKELY(numSubSamples >= (signed)(INT32_MAX / sizeof(*subSamples))) ) {
-        err = -EINVAL;
-    } else {
-        jboolean isCopy;
-
-        jint *numBytesOfClearData =
-            (numBytesOfClearDataObj == NULL)
-                ? NULL
-                : env->GetIntArrayElements(numBytesOfClearDataObj, &isCopy);
-
-        jint *numBytesOfEncryptedData =
-            (numBytesOfEncryptedDataObj == NULL)
-                ? NULL
-                : env->GetIntArrayElements(numBytesOfEncryptedDataObj, &isCopy);
-
-        subSamples = new CryptoPlugin::SubSample[numSubSamples];
-
-        for (jint i = 0; i < numSubSamples; ++i) {
-            subSamples[i].mNumBytesOfClearData =
-                (numBytesOfClearData == NULL) ? 0 : numBytesOfClearData[i];
-
-            subSamples[i].mNumBytesOfEncryptedData =
-                (numBytesOfEncryptedData == NULL)
-                    ? 0 : numBytesOfEncryptedData[i];
-        }
-
-        if (numBytesOfEncryptedData != NULL) {
-            env->ReleaseIntArrayElements(
-                    numBytesOfEncryptedDataObj, numBytesOfEncryptedData, 0);
-            numBytesOfEncryptedData = NULL;
-        }
-
-        if (numBytesOfClearData != NULL) {
-            env->ReleaseIntArrayElements(
-                    numBytesOfClearDataObj, numBytesOfClearData, 0);
-            numBytesOfClearData = NULL;
-        }
-    }
-
-    if (err == OK && keyObj != NULL) {
-        if (env->GetArrayLength(keyObj) != 16) {
-            err = -EINVAL;
-        } else {
-            jboolean isCopy;
-            key = env->GetByteArrayElements(keyObj, &isCopy);
-        }
-    }
-
-    if (err == OK && ivObj != NULL) {
-        if (env->GetArrayLength(ivObj) != 16) {
-            err = -EINVAL;
-        } else {
-            jboolean isCopy;
-            iv = env->GetByteArrayElements(ivObj, &isCopy);
-        }
-    }
-
+    NativeCryptoInfo cryptoInfo{env, cryptoInfoObj};
     AString errorDetailMsg;
 
+    status_t err = cryptoInfo.mErr;
     if (err == OK) {
         err = codec->queueSecureInputBuffer(
                 index, offset,
-                subSamples, numSubSamples,
-                (const uint8_t *)key, (const uint8_t *)iv,
-                mode,
-                pattern,
+                cryptoInfo.mSubSamples, cryptoInfo.mNumSubSamples,
+                (const uint8_t *)cryptoInfo.mKey, (const uint8_t *)cryptoInfo.mIv,
+                cryptoInfo.mMode,
+                cryptoInfo.mPattern,
                 timestampUs,
                 flags,
                 &errorDetailMsg);
     }
 
-    if (iv != NULL) {
-        env->ReleaseByteArrayElements(ivObj, iv, 0);
-        iv = NULL;
-    }
-
-    if (key != NULL) {
-        env->ReleaseByteArrayElements(keyObj, key, 0);
-        key = NULL;
-    }
-
-    delete[] subSamples;
-    subSamples = NULL;
-
     throwExceptionAsNecessary(
             env, err, ACTION_CODE_FATAL, errorDetailMsg.empty() ? NULL : errorDetailMsg.c_str());
 }
 
+static status_t ConvertKeyValueListsToAMessage(
+        JNIEnv *env, jobject keys, jobject values, sp<AMessage> *msg) {
+    static struct Fields {
+        explicit Fields(JNIEnv *env) {
+            ScopedLocalRef<jclass> clazz{env, env->FindClass("java/lang/String")};
+            CHECK(clazz.get() != NULL);
+            mStringClass = (jclass)env->NewGlobalRef(clazz.get());
+
+            clazz.reset(env->FindClass("java/lang/Integer"));
+            CHECK(clazz.get() != NULL);
+            mIntegerClass = (jclass)env->NewGlobalRef(clazz.get());
+
+            mIntegerValueId = env->GetMethodID(clazz.get(), "intValue", "()I");
+            CHECK(mIntegerValueId != NULL);
+
+            clazz.reset(env->FindClass("java/lang/Long"));
+            CHECK(clazz.get() != NULL);
+            mLongClass = (jclass)env->NewGlobalRef(clazz.get());
+
+            mLongValueId = env->GetMethodID(clazz.get(), "longValue", "()J");
+            CHECK(mLongValueId != NULL);
+
+            clazz.reset(env->FindClass("java/lang/Float"));
+            CHECK(clazz.get() != NULL);
+            mFloatClass = (jclass)env->NewGlobalRef(clazz.get());
+
+            mFloatValueId = env->GetMethodID(clazz.get(), "floatValue", "()F");
+            CHECK(mFloatValueId != NULL);
+
+            clazz.reset(env->FindClass("java/util/ArrayList"));
+            CHECK(clazz.get() != NULL);
+
+            mByteBufferArrayId = env->GetMethodID(gByteBufferInfo.clazz, "array", "()[B");
+            CHECK(mByteBufferArrayId != NULL);
+        }
+
+        jclass mStringClass;
+        jclass mIntegerClass;
+        jmethodID mIntegerValueId;
+        jclass mLongClass;
+        jmethodID mLongValueId;
+        jclass mFloatClass;
+        jmethodID mFloatValueId;
+        jmethodID mByteBufferArrayId;
+    } sFields{env};
+
+    jint size = env->CallIntMethod(keys, gArrayListInfo.sizeId);
+    if (size != env->CallIntMethod(values, gArrayListInfo.sizeId)) {
+        return BAD_VALUE;
+    }
+
+    sp<AMessage> result{new AMessage};
+    for (jint i = 0; i < size; ++i) {
+        ScopedLocalRef<jstring> jkey{
+            env, (jstring)env->CallObjectMethod(keys, gArrayListInfo.getId, i)};
+        const char *tmp = env->GetStringUTFChars(jkey.get(), nullptr);
+        AString key;
+        if (tmp) {
+            key.setTo(tmp);
+        }
+        env->ReleaseStringUTFChars(jkey.get(), tmp);
+        if (key.empty()) {
+            return NO_MEMORY;
+        }
+
+        ScopedLocalRef<jobject> jvalue{
+            env, env->CallObjectMethod(values, gArrayListInfo.getId, i)};
+
+        if (env->IsInstanceOf(jvalue.get(), sFields.mStringClass)) {
+            const char *tmp = env->GetStringUTFChars((jstring)jvalue.get(), nullptr);
+            AString value;
+            if (tmp) {
+                value.setTo(tmp);
+            }
+            env->ReleaseStringUTFChars((jstring)jvalue.get(), tmp);
+            if (value.empty()) {
+                return NO_MEMORY;
+            }
+            result->setString(key.c_str(), value);
+        } else if (env->IsInstanceOf(jvalue.get(), sFields.mIntegerClass)) {
+            jint value = env->CallIntMethod(jvalue.get(), sFields.mIntegerValueId);
+            result->setInt32(key.c_str(), value);
+        } else if (env->IsInstanceOf(jvalue.get(), sFields.mLongClass)) {
+            jlong value = env->CallLongMethod(jvalue.get(), sFields.mLongValueId);
+            result->setInt64(key.c_str(), value);
+        } else if (env->IsInstanceOf(jvalue.get(), sFields.mFloatClass)) {
+            jfloat value = env->CallFloatMethod(jvalue.get(), sFields.mFloatValueId);
+            result->setFloat(key.c_str(), value);
+        } else if (env->IsInstanceOf(jvalue.get(), gByteBufferInfo.clazz)) {
+            jint position = env->CallIntMethod(jvalue.get(), gByteBufferInfo.positionId);
+            jint limit = env->CallIntMethod(jvalue.get(), gByteBufferInfo.limitId);
+            sp<ABuffer> buffer{new ABuffer(limit - position)};
+            void *data = env->GetDirectBufferAddress(jvalue.get());
+            if (data != nullptr) {
+                memcpy(buffer->data(),
+                       static_cast<const uint8_t *>(data) + position,
+                       buffer->size());
+            } else {
+                ScopedLocalRef<jbyteArray> byteArray{env, (jbyteArray)env->CallObjectMethod(
+                        jvalue.get(), sFields.mByteBufferArrayId)};
+                env->GetByteArrayRegion(byteArray.get(), position, buffer->size(),
+                                        reinterpret_cast<jbyte *>(buffer->data()));
+            }
+            result->setBuffer(key.c_str(), buffer);
+        }
+    }
+
+    *msg = result;
+    return OK;
+}
+
+static void android_media_MediaCodec_native_queueLinearBlock(
+        JNIEnv *env, jobject thiz, jint index, jobject bufferObj,
+        jint offset, jint size, jobject cryptoInfoObj,
+        jlong presentationTimeUs, jint flags, jobject keys, jobject values) {
+    ALOGV("android_media_MediaCodec_native_queueLinearBlock");
+
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == nullptr) {
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        return;
+    }
+
+    sp<AMessage> tunings;
+    status_t err = ConvertKeyValueListsToAMessage(env, keys, values, &tunings);
+    if (err != OK) {
+        throwExceptionAsNecessary(env, err);
+        return;
+    }
+
+    std::shared_ptr<C2Buffer> buffer;
+    sp<hardware::HidlMemory> memory;
+    ScopedLocalRef<jobject> lock{env, env->GetObjectField(bufferObj, gLinearBlockInfo.lockId)};
+    if (env->MonitorEnter(lock.get()) == JNI_OK) {
+        if (env->GetBooleanField(bufferObj, gLinearBlockInfo.validId)) {
+            JMediaCodecLinearBlock *context =
+                (JMediaCodecLinearBlock *)env->GetLongField(bufferObj, gLinearBlockInfo.contextId);
+            if (cryptoInfoObj != nullptr) {
+                memory = context->toHidlMemory();
+            } else {
+                buffer = context->toC2Buffer(offset, size);
+            }
+        }
+        env->MonitorExit(lock.get());
+    } else {
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        return;
+    }
+
+    AString errorDetailMsg;
+    if (cryptoInfoObj != nullptr) {
+        if (!memory) {
+            throwExceptionAsNecessary(env, BAD_VALUE);
+            return;
+        }
+
+        NativeCryptoInfo cryptoInfo{env, cryptoInfoObj};
+        size_t cryptoSize = 0;
+        for (int i = 0; i < cryptoInfo.mNumSubSamples; ++i) {
+            cryptoSize += cryptoInfo.mSubSamples[i].mNumBytesOfClearData;
+            cryptoSize += cryptoInfo.mSubSamples[i].mNumBytesOfEncryptedData;
+        }
+        err = codec->queueEncryptedLinearBlock(
+                index,
+                memory,
+                offset,
+                cryptoInfo.mSubSamples, cryptoInfo.mNumSubSamples,
+                (const uint8_t *)cryptoInfo.mKey, (const uint8_t *)cryptoInfo.mIv,
+                cryptoInfo.mMode,
+                cryptoInfo.mPattern,
+                presentationTimeUs,
+                flags,
+                tunings,
+                &errorDetailMsg);
+    } else {
+        if (!buffer) {
+            throwExceptionAsNecessary(env, BAD_VALUE);
+            return;
+        }
+        err = codec->queueBuffer(
+                index, buffer, presentationTimeUs, flags, tunings, &errorDetailMsg);
+    }
+    throwExceptionAsNecessary(env, err, ACTION_CODE_FATAL, errorDetailMsg.c_str());
+}
+
+static void android_media_MediaCodec_native_queueGraphicBlock(
+        JNIEnv *env, jobject thiz, jint index, jobject bufferObj,
+        jlong presentationTimeUs, jint flags, jobject keys, jobject values) {
+    ALOGV("android_media_MediaCodec_native_queueGraphicBlock");
+
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        return;
+    }
+
+    sp<AMessage> tunings;
+    status_t err = ConvertKeyValueListsToAMessage(env, keys, values, &tunings);
+    if (err != OK) {
+        throwExceptionAsNecessary(env, err);
+        return;
+    }
+
+    std::shared_ptr<C2Buffer> buffer;
+    std::shared_ptr<C2GraphicBlock> block;
+    ScopedLocalRef<jobject> lock{env, env->GetObjectField(bufferObj, gGraphicBlockInfo.lockId)};
+    if (env->MonitorEnter(lock.get()) == JNI_OK) {
+        if (env->GetBooleanField(bufferObj, gGraphicBlockInfo.validId)) {
+            JMediaCodecGraphicBlock *context = (JMediaCodecGraphicBlock *)env->GetLongField(
+                    bufferObj, gGraphicBlockInfo.contextId);
+            buffer = context->mBuffer;
+            block = context->mBlock;
+        }
+        env->MonitorExit(lock.get());
+    } else {
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        return;
+    }
+
+    if (!block && !buffer) {
+        throwExceptionAsNecessary(env, BAD_VALUE);
+        return;
+    }
+    if (!buffer) {
+        buffer = C2Buffer::CreateGraphicBuffer(block->share(block->crop(), C2Fence{}));
+    }
+    AString errorDetailMsg;
+    err = codec->queueBuffer(index, buffer, presentationTimeUs, flags, tunings, &errorDetailMsg);
+    throwExceptionAsNecessary(env, err, ACTION_CODE_FATAL, errorDetailMsg.c_str());
+}
+
+static void android_media_MediaCodec_native_getOutputFrame(
+        JNIEnv *env, jobject thiz, jobject frame, jint index) {
+    ALOGV("android_media_MediaCodec_native_getOutputFrame");
+
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        return;
+    }
+
+    status_t err = codec->getOutputFrame(env, frame, index);
+    if (err != OK) {
+        throwExceptionAsNecessary(env, err);
+    }
+}
+
 static jint android_media_MediaCodec_dequeueInputBuffer(
         JNIEnv *env, jobject thiz, jlong timeoutUs) {
     ALOGV("android_media_MediaCodec_dequeueInputBuffer");
@@ -1991,6 +2418,31 @@
     gFields.patternSkipBlocksID = env->GetFieldID(clazz.get(), "mSkipBlocks", "I");
     CHECK(gFields.patternSkipBlocksID != NULL);
 
+    clazz.reset(env->FindClass("android/media/MediaCodec$QueueRequest"));
+    CHECK(clazz.get() != NULL);
+
+    gFields.queueRequestIndexID = env->GetFieldID(clazz.get(), "mIndex", "I");
+    CHECK(gFields.queueRequestIndexID != NULL);
+
+    clazz.reset(env->FindClass("android/media/MediaCodec$OutputFrame"));
+    CHECK(clazz.get() != NULL);
+
+    gFields.outputFrameLinearBlockID =
+        env->GetFieldID(clazz.get(), "mLinearBlock", "Landroid/media/MediaCodec$LinearBlock;");
+    CHECK(gFields.outputFrameLinearBlockID != NULL);
+
+    gFields.outputFrameGraphicBlockID =
+        env->GetFieldID(clazz.get(), "mGraphicBlock", "Landroid/media/MediaCodec$GraphicBlock;");
+    CHECK(gFields.outputFrameGraphicBlockID != NULL);
+
+    gFields.outputFrameChangedKeysID =
+        env->GetFieldID(clazz.get(), "mChangedKeys", "Ljava/util/ArrayList;");
+    CHECK(gFields.outputFrameChangedKeysID != NULL);
+
+    gFields.outputFrameFormatID =
+        env->GetFieldID(clazz.get(), "mFormat", "Landroid/media/MediaFormat;");
+    CHECK(gFields.outputFrameFormatID != NULL);
+
     clazz.reset(env->FindClass("android/media/MediaCodec$CryptoException"));
     CHECK(clazz.get() != NULL);
 
@@ -2105,6 +2557,96 @@
     field = env->GetFieldID(clazz.get(), "level", "I");
     CHECK(field != NULL);
     gCodecInfo.levelField = field;
+
+    clazz.reset(env->FindClass("java/nio/ByteBuffer"));
+    CHECK(clazz.get() != NULL);
+    gByteBufferInfo.clazz = (jclass)env->NewGlobalRef(clazz.get());
+
+    ScopedLocalRef<jclass> byteOrderClass(
+            env, env->FindClass("java/nio/ByteOrder"));
+    CHECK(byteOrderClass.get() != NULL);
+
+    jmethodID nativeOrderID = env->GetStaticMethodID(
+            byteOrderClass.get(), "nativeOrder", "()Ljava/nio/ByteOrder;");
+    CHECK(nativeOrderID != NULL);
+
+    ScopedLocalRef<jobject> nativeByteOrderObj{
+        env, env->CallStaticObjectMethod(byteOrderClass.get(), nativeOrderID)};
+    gByteBufferInfo.nativeByteOrder = env->NewGlobalRef(nativeByteOrderObj.get());
+    CHECK(gByteBufferInfo.nativeByteOrder != NULL);
+    nativeByteOrderObj.reset();
+
+    gByteBufferInfo.orderId = env->GetMethodID(
+            clazz.get(),
+            "order",
+            "(Ljava/nio/ByteOrder;)Ljava/nio/ByteBuffer;");
+    CHECK(gByteBufferInfo.orderId != NULL);
+
+    gByteBufferInfo.asReadOnlyBufferId = env->GetMethodID(
+            clazz.get(), "asReadOnlyBuffer", "()Ljava/nio/ByteBuffer;");
+    CHECK(gByteBufferInfo.asReadOnlyBufferId != NULL);
+
+    gByteBufferInfo.positionId = env->GetMethodID(
+            clazz.get(), "position", "(I)Ljava/nio/Buffer;");
+    CHECK(gByteBufferInfo.positionId != NULL);
+
+    gByteBufferInfo.limitId = env->GetMethodID(
+            clazz.get(), "limit", "(I)Ljava/nio/Buffer;");
+    CHECK(gByteBufferInfo.limitId != NULL);
+
+    clazz.reset(env->FindClass("java/util/ArrayList"));
+    CHECK(clazz.get() != NULL);
+
+    gArrayListInfo.sizeId = env->GetMethodID(clazz.get(), "size", "()I");
+    CHECK(gArrayListInfo.sizeId != NULL);
+
+    gArrayListInfo.getId = env->GetMethodID(clazz.get(), "get", "(I)Ljava/lang/Object;");
+    CHECK(gArrayListInfo.getId != NULL);
+
+    gArrayListInfo.addId = env->GetMethodID(clazz.get(), "add", "(Ljava/lang/Object;)Z");
+    CHECK(gArrayListInfo.addId != NULL);
+
+    clazz.reset(env->FindClass("android/media/MediaCodec$LinearBlock"));
+    CHECK(clazz.get() != NULL);
+
+    gLinearBlockInfo.clazz = (jclass)env->NewGlobalRef(clazz.get());
+
+    gLinearBlockInfo.ctorId = env->GetMethodID(clazz.get(), "<init>", "()V");
+    CHECK(gLinearBlockInfo.ctorId != NULL);
+
+    gLinearBlockInfo.setInternalStateId = env->GetMethodID(
+            clazz.get(), "setInternalStateLocked", "(JZ)V");
+    CHECK(gLinearBlockInfo.setInternalStateId != NULL);
+
+    gLinearBlockInfo.contextId = env->GetFieldID(clazz.get(), "mNativeContext", "J");
+    CHECK(gLinearBlockInfo.contextId != NULL);
+
+    gLinearBlockInfo.validId = env->GetFieldID(clazz.get(), "mValid", "Z");
+    CHECK(gLinearBlockInfo.validId != NULL);
+
+    gLinearBlockInfo.lockId = env->GetFieldID(clazz.get(), "mLock", "Ljava/lang/Object;");
+    CHECK(gLinearBlockInfo.lockId != NULL);
+
+    clazz.reset(env->FindClass("android/media/MediaCodec$GraphicBlock"));
+    CHECK(clazz.get() != NULL);
+
+    gGraphicBlockInfo.clazz = (jclass)env->NewGlobalRef(clazz.get());
+
+    gGraphicBlockInfo.ctorId = env->GetMethodID(clazz.get(), "<init>", "()V");
+    CHECK(gGraphicBlockInfo.ctorId != NULL);
+
+    gGraphicBlockInfo.setInternalStateId = env->GetMethodID(
+            clazz.get(), "setInternalStateLocked", "(JZ)V");
+    CHECK(gGraphicBlockInfo.setInternalStateId != NULL);
+
+    gGraphicBlockInfo.contextId = env->GetFieldID(clazz.get(), "mNativeContext", "J");
+    CHECK(gGraphicBlockInfo.contextId != NULL);
+
+    gGraphicBlockInfo.validId = env->GetFieldID(clazz.get(), "mValid", "Z");
+    CHECK(gGraphicBlockInfo.validId != NULL);
+
+    gGraphicBlockInfo.lockId = env->GetFieldID(clazz.get(), "mLock", "Ljava/lang/Object;");
+    CHECK(gGraphicBlockInfo.lockId != NULL);
 }
 
 static void android_media_MediaCodec_native_setup(
@@ -2155,6 +2697,345 @@
     android_media_MediaCodec_release(env, thiz);
 }
 
+// MediaCodec.LinearBlock
+
+static jobject android_media_MediaCodec_LinearBlock_native_map(
+        JNIEnv *env, jobject thiz) {
+    JMediaCodecLinearBlock *context =
+        (JMediaCodecLinearBlock *)env->GetLongField(thiz, gLinearBlockInfo.contextId);
+    if (context->mBuffer) {
+        std::shared_ptr<C2Buffer> buffer = context->mBuffer;
+        if (!context->mReadonlyMapping) {
+            const C2BufferData data = buffer->data();
+            if (data.type() != C2BufferData::LINEAR) {
+                throwExceptionAsNecessary(env, INVALID_OPERATION);
+                return nullptr;
+            }
+            if (data.linearBlocks().size() != 1u) {
+                throwExceptionAsNecessary(env, INVALID_OPERATION);
+                return nullptr;
+            }
+            C2ConstLinearBlock block = data.linearBlocks().front();
+            context->mReadonlyMapping =
+                std::make_shared<C2ReadView>(block.map().get());
+        }
+        return CreateByteBuffer(
+                env,
+                context->mReadonlyMapping->data(),  // base
+                context->mReadonlyMapping->capacity(),  // capacity
+                0u,  // offset
+                context->mReadonlyMapping->capacity(),  // size
+                true,  // readOnly
+                true /* clearBuffer */);
+    } else if (context->mBlock) {
+        std::shared_ptr<C2LinearBlock> block = context->mBlock;
+        if (!context->mReadWriteMapping) {
+            context->mReadWriteMapping =
+                std::make_shared<C2WriteView>(block->map().get());
+        }
+        return CreateByteBuffer(
+                env,
+                context->mReadWriteMapping->base(),
+                context->mReadWriteMapping->capacity(),
+                context->mReadWriteMapping->offset(),
+                context->mReadWriteMapping->size(),
+                false,  // readOnly
+                true /* clearBuffer */);
+    } else if (context->mLegacyBuffer) {
+        return CreateByteBuffer(
+                env,
+                context->mLegacyBuffer->base(),
+                context->mLegacyBuffer->capacity(),
+                context->mLegacyBuffer->offset(),
+                context->mLegacyBuffer->size(),
+                true,  // readOnly
+                true /* clearBuffer */);
+    } else if (context->mHeap) {
+        return CreateByteBuffer(
+                env,
+                static_cast<uint8_t *>(context->mHeap->getBase()) + context->mHeap->getOffset(),
+                context->mHeap->getSize(),
+                0,
+                context->mHeap->getSize(),
+                false,  // readOnly
+                true /* clearBuffer */);
+    }
+    throwExceptionAsNecessary(env, INVALID_OPERATION);
+    return nullptr;
+}
+
+static void android_media_MediaCodec_LinearBlock_native_recycle(
+        JNIEnv *env, jobject thiz) {
+    JMediaCodecLinearBlock *context =
+        (JMediaCodecLinearBlock *)env->GetLongField(thiz, gLinearBlockInfo.contextId);
+    env->CallVoidMethod(thiz, gLinearBlockInfo.setInternalStateId, 0, false);
+    delete context;
+}
+
+static void PopulateNamesVector(
+        JNIEnv *env, jobjectArray codecNames, std::vector<std::string> *names) {
+    jsize length = env->GetArrayLength(codecNames);
+    for (jsize i = 0; i < length; ++i) {
+        jstring jstr = static_cast<jstring>(env->GetObjectArrayElement(codecNames, i));
+        if (jstr == nullptr) {
+            // null entries are ignored
+            continue;
+        }
+        const char *cstr = env->GetStringUTFChars(jstr, nullptr);
+        if (cstr == nullptr) {
+            throwExceptionAsNecessary(env, BAD_VALUE);
+            return;
+        }
+        names->emplace_back(cstr);
+        env->ReleaseStringUTFChars(jstr, cstr);
+    }
+}
+
+static void android_media_MediaCodec_LinearBlock_native_obtain(
+        JNIEnv *env, jobject thiz, jint capacity, jobjectArray codecNames) {
+    std::unique_ptr<JMediaCodecLinearBlock> context{new JMediaCodecLinearBlock};
+    std::vector<std::string> names;
+    PopulateNamesVector(env, codecNames, &names);
+    bool hasSecure = false;
+    bool hasNonSecure = false;
+    for (const std::string &name : names) {
+        if (name.length() >= 7 && name.substr(name.length() - 7) == ".secure") {
+            hasSecure = true;
+        } else {
+            hasNonSecure = true;
+        }
+    }
+    if (hasSecure && !hasNonSecure) {
+        context->mHeap = new MemoryHeapBase(capacity);
+        context->mMemory = hardware::fromHeap(context->mHeap);
+    } else {
+        context->mBlock = MediaCodec::FetchLinearBlock(capacity, names);
+        if (!context->mBlock) {
+            jniThrowException(env, "java/io/IOException", nullptr);
+            return;
+        }
+    }
+    env->CallVoidMethod(
+            thiz,
+            gLinearBlockInfo.setInternalStateId,
+            (jlong)context.release(),
+            true /* isMappable */);
+}
+
+static jboolean android_media_MediaCodec_LinearBlock_checkCompatible(
+        JNIEnv *env, jobjectArray codecNames) {
+    std::vector<std::string> names;
+    PopulateNamesVector(env, codecNames, &names);
+    bool isCompatible = false;
+    bool hasSecure = false;
+    bool hasNonSecure = false;
+    for (const std::string &name : names) {
+        if (name.length() >= 7 && name.substr(name.length() - 7) == ".secure") {
+            hasSecure = true;
+        } else {
+            hasNonSecure = true;
+        }
+    }
+    if (hasSecure && hasNonSecure) {
+        return false;
+    }
+    status_t err = MediaCodec::CanFetchLinearBlock(names, &isCompatible);
+    if (err != OK) {
+        throwExceptionAsNecessary(env, err);
+    }
+    return isCompatible;
+}
+
+// MediaCodec.GraphicBlock
+
+template <class T>
+static jobject CreateImage(JNIEnv *env, const std::shared_ptr<T> &view) {
+    bool readOnly = std::is_const<T>::value;
+    const C2PlanarLayout layout = view->layout();
+    jint format = HAL_PIXEL_FORMAT_YCBCR_420_888;
+    switch (layout.type) {
+        case C2PlanarLayout::TYPE_YUV: {
+            if (layout.numPlanes != 3) {
+                throwExceptionAsNecessary(env, INVALID_OPERATION);
+                return nullptr;
+            }
+            const C2PlaneInfo & yPlane = layout.planes[C2PlanarLayout::PLANE_Y];
+            const C2PlaneInfo & uPlane = layout.planes[C2PlanarLayout::PLANE_U];
+            const C2PlaneInfo & vPlane = layout.planes[C2PlanarLayout::PLANE_V];
+            if (yPlane.rowSampling != 1 || yPlane.colSampling != 1) {
+                throwExceptionAsNecessary(env, INVALID_OPERATION);
+                return nullptr;
+            }
+            if (uPlane.rowSampling != vPlane.rowSampling
+                    || uPlane.colSampling != vPlane.colSampling) {
+                throwExceptionAsNecessary(env, INVALID_OPERATION);
+                return nullptr;
+            }
+            if (uPlane.rowSampling == 2 && uPlane.colSampling == 2) {
+                format = HAL_PIXEL_FORMAT_YCBCR_420_888;
+                break;
+            } else if (uPlane.rowSampling == 1 && uPlane.colSampling == 2) {
+                format = HAL_PIXEL_FORMAT_YCBCR_422_888;
+                break;
+            } else if (uPlane.rowSampling == 1 && uPlane.colSampling == 1) {
+                format = HAL_PIXEL_FORMAT_YCBCR_444_888;
+                break;
+            }
+            throwExceptionAsNecessary(env, INVALID_OPERATION);
+            return nullptr;
+        }
+        case C2PlanarLayout::TYPE_RGB: {
+            if (layout.numPlanes != 3) {
+                throwExceptionAsNecessary(env, INVALID_OPERATION);
+                return nullptr;
+            }
+            format = HAL_PIXEL_FORMAT_FLEX_RGB_888;
+            break;
+        }
+        case C2PlanarLayout::TYPE_RGBA: {
+            if (layout.numPlanes != 4) {
+                throwExceptionAsNecessary(env, INVALID_OPERATION);
+                return nullptr;
+            }
+            format = HAL_PIXEL_FORMAT_FLEX_RGBA_8888;
+            break;
+        }
+        case C2PlanarLayout::TYPE_YUVA:
+            [[fallthrough]];
+        default:
+            throwExceptionAsNecessary(env, INVALID_OPERATION);
+            return nullptr;
+    }
+
+    ScopedLocalRef<jclass> planeClazz(
+            env, env->FindClass("android/media/MediaCodec$MediaImage$MediaPlane"));
+    ScopedLocalRef<jobjectArray> planeArray{
+            env, env->NewObjectArray(layout.numPlanes, planeClazz.get(), NULL)};
+    CHECK(planeClazz.get() != NULL);
+    jmethodID planeConstructID = env->GetMethodID(planeClazz.get(), "<init>",
+            "([Ljava/nio/ByteBuffer;IIIII)V");
+
+    // plane indices are happened to be Y-U-V and R-G-B(-A) order.
+    for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+        const C2PlaneInfo &plane = layout.planes[i];
+        if (plane.rowInc < 0 || plane.colInc < 0) {
+            throwExceptionAsNecessary(env, INVALID_OPERATION);
+            return nullptr;
+        }
+        ssize_t minOffset = plane.minOffset(view->width(), view->height());
+        ssize_t maxOffset = plane.maxOffset(view->width(), view->height());
+        ScopedLocalRef<jobject> byteBuffer{env, CreateByteBuffer(
+                env,
+                view->data()[plane.rootIx] + plane.offset + minOffset,
+                maxOffset - minOffset + 1,
+                0,
+                maxOffset - minOffset + 1,
+                readOnly,
+                true)};
+
+        ScopedLocalRef<jobject> jPlane{env, env->NewObject(
+                planeClazz.get(), planeConstructID,
+                byteBuffer.get(), plane.rowInc, plane.colInc)};
+    }
+
+    ScopedLocalRef<jclass> imageClazz(
+            env, env->FindClass("android/media/MediaCodec$MediaImage"));
+    CHECK(imageClazz.get() != NULL);
+
+    jmethodID imageConstructID = env->GetMethodID(imageClazz.get(), "<init>",
+            "([Landroid/media/Image$Plane;IIIZJIILandroid/graphics/Rect;)V");
+
+    jobject img = env->NewObject(imageClazz.get(), imageConstructID,
+            planeArray.get(),
+            view->width(),
+            view->height(),
+            format,
+            (jboolean)readOnly /* readOnly */,
+            (jlong)0 /* timestamp */,
+            (jint)0 /* xOffset */, (jint)0 /* yOffset */, nullptr /* cropRect */);
+
+    // if MediaImage creation fails, return null
+    if (env->ExceptionCheck()) {
+        env->ExceptionDescribe();
+        env->ExceptionClear();
+        return nullptr;
+    }
+
+    return img;
+}
+
+static jobject android_media_MediaCodec_GraphicBlock_native_map(
+        JNIEnv *env, jobject thiz) {
+    JMediaCodecGraphicBlock *context =
+        (JMediaCodecGraphicBlock *)env->GetLongField(thiz, gGraphicBlockInfo.contextId);
+    if (context->mBuffer) {
+        std::shared_ptr<C2Buffer> buffer = context->mBuffer;
+        if (!context->mReadonlyMapping) {
+            const C2BufferData data = buffer->data();
+            if (data.type() != C2BufferData::GRAPHIC) {
+                throwExceptionAsNecessary(env, INVALID_OPERATION);
+                return nullptr;
+            }
+            if (data.graphicBlocks().size() != 1u) {
+                throwExceptionAsNecessary(env, INVALID_OPERATION);
+                return nullptr;
+            }
+            C2ConstGraphicBlock block = data.graphicBlocks().front();
+            context->mReadonlyMapping =
+                std::make_shared<const C2GraphicView>(block.map().get());
+        }
+        return CreateImage(env, context->mReadonlyMapping);
+    } else if (context->mBlock) {
+        std::shared_ptr<C2GraphicBlock> block = context->mBlock;
+        if (!context->mReadWriteMapping) {
+            context->mReadWriteMapping =
+                std::make_shared<C2GraphicView>(block->map().get());
+        }
+        return CreateImage(env, context->mReadWriteMapping);
+    } else if (context->mLegacyBuffer) {
+    }
+    throwExceptionAsNecessary(env, INVALID_OPERATION);
+    return nullptr;
+}
+
+static void android_media_MediaCodec_GraphicBlock_native_recycle(
+        JNIEnv *env, jobject thiz) {
+    JMediaCodecGraphicBlock *context =
+        (JMediaCodecGraphicBlock *)env->GetLongField(thiz, gGraphicBlockInfo.contextId);
+    env->CallVoidMethod(thiz, gGraphicBlockInfo.setInternalStateId, 0, false /* isMappable */);
+    delete context;
+}
+
+static void android_media_MediaCodec_GraphicBlock_native_obtain(
+        JNIEnv *env, jobject thiz,
+        jint width, jint height, jint format, jlong usage, jobjectArray codecNames) {
+    std::unique_ptr<JMediaCodecGraphicBlock> context{new JMediaCodecGraphicBlock};
+    std::vector<std::string> names;
+    PopulateNamesVector(env, codecNames, &names);
+    context->mBlock = MediaCodec::FetchGraphicBlock(width, height, format, usage, names);
+    if (!context->mBlock) {
+        jniThrowException(env, "java/io/IOException", nullptr);
+        return;
+    }
+    env->CallVoidMethod(
+            thiz,
+            gGraphicBlockInfo.setInternalStateId,
+            (jlong)context.release(),
+            true /*isMappable */);
+}
+
+static jboolean android_media_MediaCodec_GraphicBlock_checkCompatible(
+        JNIEnv *env, jobjectArray codecNames) {
+    std::vector<std::string> names;
+    PopulateNamesVector(env, codecNames, &names);
+    bool isCompatible = false;
+    status_t err = MediaCodec::CanFetchGraphicBlock(names, &isCompatible);
+    if (err != OK) {
+        throwExceptionAsNecessary(env, err);
+    }
+    return isCompatible;
+}
+
 static const JNINativeMethod gMethods[] = {
     { "native_release", "()V", (void *)android_media_MediaCodec_release },
 
@@ -2200,6 +3081,19 @@
     { "native_queueSecureInputBuffer", "(IILandroid/media/MediaCodec$CryptoInfo;JI)V",
       (void *)android_media_MediaCodec_queueSecureInputBuffer },
 
+    { "native_queueLinearBlock",
+      "(ILandroid/media/MediaCodec$LinearBlock;IILandroid/media/MediaCodec$CryptoInfo;JI"
+      "Ljava/util/ArrayList;Ljava/util/ArrayList;)V",
+      (void *)android_media_MediaCodec_native_queueLinearBlock },
+
+    { "native_queueGraphicBlock",
+      "(ILandroid/media/MediaCodec$GraphicBlock;JILjava/util/ArrayList;Ljava/util/ArrayList;)V",
+      (void *)android_media_MediaCodec_native_queueGraphicBlock },
+
+    { "native_getOutputFrame",
+      "(Landroid/media/MediaCodec$OutputFrame;I)V",
+      (void *)android_media_MediaCodec_native_getOutputFrame },
+
     { "native_dequeueInputBuffer", "(J)I",
       (void *)android_media_MediaCodec_dequeueInputBuffer },
 
@@ -2254,7 +3148,50 @@
       (void *)android_media_MediaCodec_native_finalize },
 };
 
+static const JNINativeMethod gLinearBlockMethods[] = {
+    { "native_map", "()Ljava/nio/ByteBuffer;",
+      (void *)android_media_MediaCodec_LinearBlock_native_map },
+
+    { "native_recycle", "()V",
+      (void *)android_media_MediaCodec_LinearBlock_native_recycle },
+
+    { "native_obtain", "(I[Ljava/lang/String;)V",
+      (void *)android_media_MediaCodec_LinearBlock_native_obtain },
+
+    { "native_checkCompatible", "([Ljava/lang/String;)Z",
+      (void *)android_media_MediaCodec_LinearBlock_checkCompatible },
+};
+
+static const JNINativeMethod gGraphicBlockMethods[] = {
+    { "native_map", "()Landroid/media/Image;",
+      (void *)android_media_MediaCodec_GraphicBlock_native_map },
+
+    { "native_recycle", "()V",
+      (void *)android_media_MediaCodec_GraphicBlock_native_recycle },
+
+    { "native_obtain", "(IIIJ[Ljava/lang/String;)V",
+      (void *)android_media_MediaCodec_GraphicBlock_native_obtain },
+
+    { "native_checkCompatible", "([Ljava/lang/String;)Z",
+      (void *)android_media_MediaCodec_GraphicBlock_checkCompatible },
+};
+
 int register_android_media_MediaCodec(JNIEnv *env) {
-    return AndroidRuntime::registerNativeMethods(env,
+    int result = AndroidRuntime::registerNativeMethods(env,
                 "android/media/MediaCodec", gMethods, NELEM(gMethods));
+    if (result != JNI_OK) {
+        return result;
+    }
+    result = AndroidRuntime::registerNativeMethods(env,
+                "android/media/MediaCodec$LinearBlock",
+                gLinearBlockMethods,
+                NELEM(gLinearBlockMethods));
+    if (result != JNI_OK) {
+        return result;
+    }
+    result = AndroidRuntime::registerNativeMethods(env,
+                "android/media/MediaCodec$GraphicBlock",
+                gGraphicBlockMethods,
+                NELEM(gGraphicBlockMethods));
+    return result;
 }
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index ce1c805..1d12e77 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -27,6 +27,8 @@
 #include <media/stagefright/foundation/AHandler.h>
 #include <utils/Errors.h>
 
+class C2Buffer;
+
 namespace android {
 
 struct ABuffer;
@@ -39,6 +41,7 @@
 struct PersistentSurface;
 class Surface;
 namespace hardware {
+class HidlMemory;
 namespace cas {
 namespace native {
 namespace V1_0 {
@@ -97,6 +100,26 @@
             uint32_t flags,
             AString *errorDetailMsg);
 
+    status_t queueBuffer(
+            size_t index, const std::shared_ptr<C2Buffer> &buffer,
+            int64_t timeUs, uint32_t flags, const sp<AMessage> &tunings,
+            AString *errorDetailMsg);
+
+    status_t queueEncryptedLinearBlock(
+            size_t index,
+            const sp<hardware::HidlMemory> &buffer,
+            size_t offset,
+            const CryptoPlugin::SubSample *subSamples,
+            size_t numSubSamples,
+            const uint8_t key[16],
+            const uint8_t iv[16],
+            CryptoPlugin::Mode mode,
+            const CryptoPlugin::Pattern &pattern,
+            int64_t presentationTimeUs,
+            uint32_t flags,
+            const sp<AMessage> &tunings,
+            AString *errorDetailMsg);
+
     status_t dequeueInputBuffer(size_t *index, int64_t timeoutUs);
 
     status_t dequeueOutputBuffer(
@@ -120,6 +143,9 @@
     status_t getImage(
             JNIEnv *env, bool input, size_t index, jobject *image) const;
 
+    status_t getOutputFrame(
+            JNIEnv *env, jobject frame, size_t index) const;
+
     status_t getName(JNIEnv *env, jstring *name) const;
 
     status_t getCodecInfo(JNIEnv *env, jobject *codecInfo) const;
@@ -147,17 +173,10 @@
     jweak mObject;
     sp<Surface> mSurfaceTextureClient;
 
-    // java objects cached
-    jclass mByteBufferClass;
-    jobject mNativeByteOrderObj;
-    jmethodID mByteBufferOrderMethodID;
-    jmethodID mByteBufferPositionMethodID;
-    jmethodID mByteBufferLimitMethodID;
-    jmethodID mByteBufferAsReadOnlyBufferMethodID;
-
     sp<ALooper> mLooper;
     sp<MediaCodec> mCodec;
     AString mNameAtCreation;
+    bool mGraphicOutput{false};
     std::once_flag mReleaseFlag;
 
     sp<AMessage> mCallbackNotification;
@@ -170,8 +189,6 @@
             JNIEnv *env, bool readOnly, bool clearBuffer, const sp<T> &buffer,
             jobject *buf) const;
 
-    void cacheJavaObjects(JNIEnv *env);
-    void deleteJavaObjects(JNIEnv *env);
     void handleCallback(const sp<AMessage> &msg);
     void handleFrameRenderedNotification(const sp<AMessage> &msg);
 
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 963b650..5cb42a9a 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -1453,6 +1453,7 @@
 extern int register_android_mtp_MtpDatabase(JNIEnv *env);
 extern int register_android_mtp_MtpDevice(JNIEnv *env);
 extern int register_android_mtp_MtpServer(JNIEnv *env);
+extern int register_android_media_MediaTranscodeManager(JNIEnv *env);
 
 jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
 {
@@ -1565,6 +1566,11 @@
         goto bail;
     }
 
+    if (register_android_media_MediaTranscodeManager(env) < 0) {
+        ALOGE("ERROR: MediaTranscodeManager native registration failed");
+        goto bail;
+    }
+
     /* success -- return valid version number */
     result = JNI_VERSION_1_4;
 
diff --git a/media/jni/android_media_MediaTranscodeManager.cpp b/media/jni/android_media_MediaTranscodeManager.cpp
new file mode 100644
index 0000000..0b4048c
--- /dev/null
+++ b/media/jni/android_media_MediaTranscodeManager.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright 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.
+ */
+//#define LOG_NDEBUG 0
+#define LOG_TAG "MediaTranscodeManager_JNI"
+
+#include "android_runtime/AndroidRuntime.h"
+#include "jni.h"
+
+#include <nativehelper/JNIHelp.h>
+#include <utils/Log.h>
+
+namespace {
+
+// NOTE: Keep these enums in sync with their equivalents in MediaTranscodeManager.java.
+enum {
+    ID_INVALID = -1
+};
+
+enum {
+    EVENT_JOB_STARTED = 1,
+    EVENT_JOB_PROGRESSED = 2,
+    EVENT_JOB_FINISHED = 3,
+};
+
+enum {
+    RESULT_NONE = 1,
+    RESULT_SUCCESS = 2,
+    RESULT_ERROR = 3,
+    RESULT_CANCELED = 4,
+};
+
+struct {
+    jmethodID postEventFromNative;
+} gMediaTranscodeManagerClassInfo;
+
+using namespace android;
+
+void android_media_MediaTranscodeManager_native_init(JNIEnv *env, jclass clazz) {
+    ALOGV("android_media_MediaTranscodeManager_native_init");
+
+    gMediaTranscodeManagerClassInfo.postEventFromNative = env->GetMethodID(
+            clazz, "postEventFromNative", "(IJI)V");
+    LOG_ALWAYS_FATAL_IF(gMediaTranscodeManagerClassInfo.postEventFromNative == NULL,
+                        "can't find android/media/MediaTranscodeManager.postEventFromNative");
+}
+
+jlong android_media_MediaTranscodeManager_requestUniqueJobID(
+        JNIEnv *env __unused, jobject thiz __unused) {
+    ALOGV("android_media_MediaTranscodeManager_reserveUniqueJobID");
+    static std::atomic_int32_t sJobIDCounter{0};
+    jlong id = (jlong)++sJobIDCounter;
+    return id;
+}
+
+jboolean android_media_MediaTranscodeManager_enqueueTranscodingRequest(
+        JNIEnv *env, jobject thiz, jlong id, jobject request, jobject context __unused) {
+    ALOGV("android_media_MediaTranscodeManager_enqueueTranscodingRequest");
+    if (!request) {
+        return ID_INVALID;
+    }
+
+    env->CallVoidMethod(thiz, gMediaTranscodeManagerClassInfo.postEventFromNative,
+                        EVENT_JOB_FINISHED, id, RESULT_ERROR);
+    return true;
+}
+
+void android_media_MediaTranscodeManager_cancelTranscodingRequest(
+        JNIEnv *env __unused, jobject thiz __unused, jlong jobID __unused) {
+    ALOGV("android_media_MediaTranscodeManager_cancelTranscodingRequest");
+}
+
+const JNINativeMethod gMethods[] = {
+    { "native_init", "()V",
+        (void *)android_media_MediaTranscodeManager_native_init },
+    { "native_requestUniqueJobID", "()J",
+        (void *)android_media_MediaTranscodeManager_requestUniqueJobID },
+    { "native_enqueueTranscodingRequest",
+        "(JLandroid/media/MediaTranscodeManager$TranscodingRequest;Landroid/content/Context;)Z",
+        (void *)android_media_MediaTranscodeManager_enqueueTranscodingRequest },
+    { "native_cancelTranscodingRequest", "(J)V",
+        (void *)android_media_MediaTranscodeManager_cancelTranscodingRequest },
+};
+
+} // namespace anonymous
+
+int register_android_media_MediaTranscodeManager(JNIEnv *env) {
+    return AndroidRuntime::registerNativeMethods(env,
+                "android/media/MediaTranscodeManager", gMethods, NELEM(gMethods));
+}
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index f0f3688..4f1125f 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -696,6 +696,10 @@
     return tuner->openFilter(filterType, bufferSize);
 }
 
+static jobject android_media_tv_Tuner_open_time_filter(JNIEnv, jobject) {
+    return NULL;
+}
+
 static DemuxFilterSettings getFilterSettings(
         JNIEnv *env, int type, int subtype, jobject filterSettingsObj) {
     DemuxFilterSettings filterSettings;
@@ -840,6 +844,28 @@
     return 0;
 }
 
+// TODO: implement TimeFilter functions
+static int android_media_tv_Tuner_time_filter_set_timestamp(
+        JNIEnv, jobject, jlong) {
+    return 0;
+}
+
+static int android_media_tv_Tuner_time_filter_clear_timestamp(JNIEnv, jobject) {
+    return 0;
+}
+
+static jobject android_media_tv_Tuner_time_filter_get_timestamp(JNIEnv, jobject) {
+    return NULL;
+}
+
+static jobject android_media_tv_Tuner_time_filter_get_source_time(JNIEnv, jobject) {
+    return NULL;
+}
+
+static int android_media_tv_Tuner_time_filter_close(JNIEnv, jobject) {
+    return 0;
+}
+
 static jobject android_media_tv_Tuner_open_descrambler(JNIEnv *env, jobject thiz) {
     sp<JTuner> tuner = getTuner(env, thiz);
     return tuner->openDescrambler();
@@ -1119,6 +1145,8 @@
             (void *)android_media_tv_Tuner_get_frontend_info },
     { "nativeOpenFilter", "(III)Landroid/media/tv/tuner/Tuner$Filter;",
             (void *)android_media_tv_Tuner_open_filter },
+    { "nativeOpenTimeFilter", "()Landroid/media/tv/tuner/Tuner$TimeFilter;",
+            (void *)android_media_tv_Tuner_open_time_filter },
     { "nativeGetLnbIds", "()Ljava/util/List;",
             (void *)android_media_tv_Tuner_get_lnb_ids },
     { "nativeOpenLnbById", "(I)Landroid/media/tv/tuner/Tuner$Lnb;",
@@ -1144,6 +1172,16 @@
     { "nativeClose", "()I", (void *)android_media_tv_Tuner_close_filter },
 };
 
+static const JNINativeMethod gTimeFilterMethods[] = {
+    { "nativeSetTimeStamp", "(J)I", (void *)android_media_tv_Tuner_time_filter_set_timestamp },
+    { "nativeClearTimeStamp", "()I", (void *)android_media_tv_Tuner_time_filter_clear_timestamp },
+    { "nativeGetTimeStamp", "()Ljava/lang/Long;",
+            (void *)android_media_tv_Tuner_time_filter_get_timestamp },
+    { "nativeGetSourceTime", "()Ljava/lang/Long;",
+            (void *)android_media_tv_Tuner_time_filter_get_source_time },
+    { "nativeClose", "()I", (void *)android_media_tv_Tuner_time_filter_close },
+};
+
 static const JNINativeMethod gDescramblerMethods[] = {
     { "nativeAddPid", "(IILandroid/media/tv/tuner/Tuner$Filter;)I",
             (void *)android_media_tv_Tuner_add_pid },
@@ -1194,6 +1232,13 @@
         return false;
     }
     if (AndroidRuntime::registerNativeMethods(
+            env, "android/media/tv/tuner/Tuner$TimeFilter",
+            gTimeFilterMethods,
+            NELEM(gTimeFilterMethods)) != JNI_OK) {
+        ALOGE("Failed to register time filter native methods");
+        return false;
+    }
+    if (AndroidRuntime::registerNativeMethods(
             env, "android/media/tv/tuner/Tuner$Descrambler",
             gDescramblerMethods,
             NELEM(gDescramblerMethods)) != JNI_OK) {
diff --git a/media/mca/effect/java/android/media/effect/SingleFilterEffect.java b/media/mca/effect/java/android/media/effect/SingleFilterEffect.java
index dfbf5d2..121443f 100644
--- a/media/mca/effect/java/android/media/effect/SingleFilterEffect.java
+++ b/media/mca/effect/java/android/media/effect/SingleFilterEffect.java
@@ -17,12 +17,11 @@
 
 package android.media.effect;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.filterfw.core.Filter;
 import android.filterfw.core.FilterFactory;
 import android.filterfw.core.FilterFunction;
 import android.filterfw.core.Frame;
-import android.media.effect.EffectContext;
 
 /**
  * Effect subclass for effects based on a single Filter. Subclasses need only invoke the
diff --git a/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java b/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java
index 52615bf..3a7f1ed 100644
--- a/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java
+++ b/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java
@@ -17,11 +17,11 @@
 
 package android.filterfw;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.filterfw.core.AsyncRunner;
-import android.filterfw.core.FilterGraph;
 import android.filterfw.core.FilterContext;
+import android.filterfw.core.FilterGraph;
 import android.filterfw.core.FrameManager;
 import android.filterfw.core.GraphRunner;
 import android.filterfw.core.RoundRobinScheduler;
diff --git a/media/mca/filterfw/java/android/filterfw/core/Filter.java b/media/mca/filterfw/java/android/filterfw/core/Filter.java
index 4f56b92..a608ef5 100644
--- a/media/mca/filterfw/java/android/filterfw/core/Filter.java
+++ b/media/mca/filterfw/java/android/filterfw/core/Filter.java
@@ -17,19 +17,15 @@
 
 package android.filterfw.core;
 
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.FilterContext;
-import android.filterfw.core.FilterPort;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.io.TextGraphReader;
-import android.filterfw.io.GraphIOException;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.filterfw.format.ObjectFormat;
+import android.filterfw.io.GraphIOException;
+import android.filterfw.io.TextGraphReader;
 import android.util.Log;
 
 import java.io.Serializable;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Field;
-import java.lang.Thread;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterContext.java b/media/mca/filterfw/java/android/filterfw/core/FilterContext.java
index a19220e..6b0a219 100644
--- a/media/mca/filterfw/java/android/filterfw/core/FilterContext.java
+++ b/media/mca/filterfw/java/android/filterfw/core/FilterContext.java
@@ -17,11 +17,7 @@
 
 package android.filterfw.core;
 
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.Filter;
-import android.filterfw.core.Frame;
-import android.filterfw.core.FrameManager;
-import android.filterfw.core.GLEnvironment;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import java.util.HashMap;
 import java.util.HashSet;
diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java b/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java
index e6ca11f..35a298f 100644
--- a/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java
+++ b/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java
@@ -17,6 +17,11 @@
 
 package android.filterfw.core;
 
+import android.compat.annotation.UnsupportedAppUsage;
+import android.filterpacks.base.FrameBranch;
+import android.filterpacks.base.NullFilter;
+import android.util.Log;
+
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -25,14 +30,6 @@
 import java.util.Set;
 import java.util.Stack;
 
-import android.filterfw.core.FilterContext;
-import android.filterfw.core.KeyValueMap;
-import android.filterpacks.base.FrameBranch;
-import android.filterpacks.base.NullFilter;
-
-import android.annotation.UnsupportedAppUsage;
-import android.util.Log;
-
 /**
  * @hide
  */
diff --git a/media/mca/filterfw/java/android/filterfw/core/Frame.java b/media/mca/filterfw/java/android/filterfw/core/Frame.java
index e880783..c4d935a 100644
--- a/media/mca/filterfw/java/android/filterfw/core/Frame.java
+++ b/media/mca/filterfw/java/android/filterfw/core/Frame.java
@@ -17,9 +17,7 @@
 
 package android.filterfw.core;
 
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.FrameFormat;
-import android.filterfw.core.FrameManager;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.Bitmap;
 
 import java.nio.ByteBuffer;
diff --git a/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java b/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java
index eb0ff0a..a87e9b9 100644
--- a/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java
+++ b/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java
@@ -17,9 +17,7 @@
 
 package android.filterfw.core;
 
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.MutableFrameFormat;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import java.util.Arrays;
 import java.util.Map.Entry;
diff --git a/media/mca/filterfw/java/android/filterfw/core/FrameManager.java b/media/mca/filterfw/java/android/filterfw/core/FrameManager.java
index 85c8fcd..e49aaf1 100644
--- a/media/mca/filterfw/java/android/filterfw/core/FrameManager.java
+++ b/media/mca/filterfw/java/android/filterfw/core/FrameManager.java
@@ -17,10 +17,7 @@
 
 package android.filterfw.core;
 
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.Frame;
-import android.filterfw.core.FrameFormat;
-import android.filterfw.core.MutableFrameFormat;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * @hide
diff --git a/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java b/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java
index e25d6a7..7e4e8a6 100644
--- a/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java
+++ b/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java
@@ -17,13 +17,12 @@
 
 package android.filterfw.core;
 
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.NativeAllocatorTag;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.SurfaceTexture;
+import android.media.MediaRecorder;
 import android.os.Looper;
 import android.util.Log;
 import android.view.Surface;
-import android.media.MediaRecorder;
 
 /**
  * @hide
diff --git a/media/mca/filterfw/java/android/filterfw/core/GLFrame.java b/media/mca/filterfw/java/android/filterfw/core/GLFrame.java
index 9e3025f..1ccd7fe 100644
--- a/media/mca/filterfw/java/android/filterfw/core/GLFrame.java
+++ b/media/mca/filterfw/java/android/filterfw/core/GLFrame.java
@@ -17,15 +17,10 @@
 
 package android.filterfw.core;
 
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.Frame;
-import android.filterfw.core.FrameFormat;
-import android.filterfw.core.FrameManager;
-import android.filterfw.core.NativeFrame;
-import android.filterfw.core.StopWatchMap;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.Bitmap;
-import android.opengl.GLES20;
 import android.graphics.Rect;
+import android.opengl.GLES20;
 
 import java.nio.ByteBuffer;
 
diff --git a/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java b/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java
index 250cfaa..b57e8bb 100644
--- a/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java
+++ b/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java
@@ -17,7 +17,7 @@
 
 package android.filterfw.core;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * @hide
diff --git a/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java b/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java
index ae2ad99..da00b1f 100644
--- a/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java
+++ b/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java
@@ -17,9 +17,7 @@
 
 package android.filterfw.core;
 
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.FrameFormat;
-import android.filterfw.core.KeyValueMap;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import java.util.Arrays;
 
diff --git a/media/mca/filterfw/java/android/filterfw/core/Program.java b/media/mca/filterfw/java/android/filterfw/core/Program.java
index 376c085..145388e 100644
--- a/media/mca/filterfw/java/android/filterfw/core/Program.java
+++ b/media/mca/filterfw/java/android/filterfw/core/Program.java
@@ -17,8 +17,7 @@
 
 package android.filterfw.core;
 
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.Frame;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * @hide
diff --git a/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java b/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java
index f41636e..e043be0 100644
--- a/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java
+++ b/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java
@@ -17,12 +17,7 @@
 
 package android.filterfw.core;
 
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.Frame;
-import android.filterfw.core.NativeAllocatorTag;
-import android.filterfw.core.Program;
-import android.filterfw.core.StopWatchMap;
-import android.filterfw.core.VertexFrame;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.filterfw.geometry.Quad;
 import android.opengl.GLES20;
 
diff --git a/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java b/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java
index ac08730..0e05092 100644
--- a/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java
+++ b/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java
@@ -17,7 +17,7 @@
 
 package android.filterfw.format;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.filterfw.core.FrameFormat;
 import android.filterfw.core.MutableFrameFormat;
 import android.graphics.Bitmap;
diff --git a/media/mca/filterfw/java/android/filterfw/geometry/Point.java b/media/mca/filterfw/java/android/filterfw/geometry/Point.java
index d7acf12..96d2d7b 100644
--- a/media/mca/filterfw/java/android/filterfw/geometry/Point.java
+++ b/media/mca/filterfw/java/android/filterfw/geometry/Point.java
@@ -17,8 +17,7 @@
 
 package android.filterfw.geometry;
 
-import android.annotation.UnsupportedAppUsage;
-import java.lang.Math;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * @hide
diff --git a/media/mca/filterfw/java/android/filterfw/geometry/Quad.java b/media/mca/filterfw/java/android/filterfw/geometry/Quad.java
index 610e5b8..2b308a9 100644
--- a/media/mca/filterfw/java/android/filterfw/geometry/Quad.java
+++ b/media/mca/filterfw/java/android/filterfw/geometry/Quad.java
@@ -17,10 +17,8 @@
 
 package android.filterfw.geometry;
 
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.geometry.Point;
+import android.compat.annotation.UnsupportedAppUsage;
 
-import java.lang.Float;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
diff --git a/media/tests/MediaFrameworkTest/Android.bp b/media/tests/MediaFrameworkTest/Android.bp
index f0fbc50..ecbe2b3 100644
--- a/media/tests/MediaFrameworkTest/Android.bp
+++ b/media/tests/MediaFrameworkTest/Android.bp
@@ -7,6 +7,7 @@
     ],
     static_libs: [
         "mockito-target-minus-junit4",
+        "androidx.test.ext.junit",
         "androidx.test.rules",
         "android-ex-camera2",
     ],
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediatranscodemanager/MediaTranscodeManagerTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediatranscodemanager/MediaTranscodeManagerTest.java
new file mode 100644
index 0000000..eeda50e
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediatranscodemanager/MediaTranscodeManagerTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.mediaframeworktest.functional.mediatranscodemanager;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.media.MediaTranscodeManager;
+import android.util.Log;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class MediaTranscodeManagerTest {
+    private static final String TAG = "MediaTranscodeManagerTest";
+
+    /**  The time to wait for the transcode operation to complete before failing the test. */
+    private static final int TRANSCODE_TIMEOUT_SECONDS = 2;
+
+    @Test
+    public void testMediaTranscodeManager() throws InterruptedException {
+        Log.d(TAG, "Starting: testMediaTranscodeManager");
+
+        Semaphore transcodeCompleteSemaphore = new Semaphore(0);
+        MediaTranscodeManager.TranscodingRequest request =
+                new MediaTranscodeManager.TranscodingRequest.Builder().build();
+        Executor listenerExecutor = Executors.newSingleThreadExecutor();
+
+        MediaTranscodeManager mediaTranscodeManager =
+                MediaTranscodeManager.getInstance(ApplicationProvider.getApplicationContext());
+        assertNotNull(mediaTranscodeManager);
+
+        MediaTranscodeManager.TranscodingJob job;
+        job = mediaTranscodeManager.enqueueTranscodingRequest(request, listenerExecutor,
+                transcodingJob -> {
+                Log.d(TAG, "Transcoding completed with result: " + transcodingJob.getResult());
+                transcodeCompleteSemaphore.release();
+            });
+        assertNotNull(job);
+
+        job.setOnProgressChangedListener(
+                listenerExecutor, progress -> Log.d(TAG, "Progress: " + progress));
+
+        if (job != null) {
+            Log.d(TAG, "testMediaTranscodeManager - Waiting for transcode to complete.");
+            boolean finishedOnTime = transcodeCompleteSemaphore.tryAcquire(
+                    TRANSCODE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+            assertTrue("Transcode failed to complete in time.", finishedOnTime);
+        }
+    }
+}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaFileTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaFileTest.java
index 481f479..507dd4a 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaFileTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaFileTest.java
@@ -19,6 +19,7 @@
 import static android.media.MediaFile.getFormatCode;
 import static android.media.MediaFile.getMimeType;
 import static android.media.MediaFile.isAudioMimeType;
+import static android.media.MediaFile.isDocumentMimeType;
 import static android.media.MediaFile.isImageMimeType;
 import static android.media.MediaFile.isPlayListMimeType;
 import static android.media.MediaFile.isVideoMimeType;
@@ -88,7 +89,15 @@
         assertTrue(isPlayListMimeType(mimeMap.guessMimeTypeFromExtension("wpl")));
         assertTrue(isPlayListMimeType(mimeMap.guessMimeTypeFromExtension("m3u")));
         assertTrue(isPlayListMimeType(mimeMap.guessMimeTypeFromExtension("m3u8")));
-        assertTrue(isPlayListMimeType(mimeMap.guessMimeTypeFromExtension("asf")));
+    }
+
+    @Test
+    public void testDocument() throws Exception {
+        assertTrue(isDocumentMimeType("text/csv"));
+        assertTrue(isDocumentMimeType("text/plain"));
+        assertTrue(isDocumentMimeType("application/pdf"));
+        assertTrue(isDocumentMimeType("application/msword"));
+        assertFalse(isDocumentMimeType("audio/mpeg"));
     }
 
     @Test
diff --git a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
index 04fccc7..6595cae 100644
--- a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
@@ -16,14 +16,15 @@
 
 package com.android.mediarouteprovider.example;
 
-import static android.media.MediaRoute2Info.DEVICE_TYPE_SPEAKER;
-import static android.media.MediaRoute2Info.DEVICE_TYPE_TV;
+import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_SPEAKER;
+import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_TV;
 
+import android.annotation.Nullable;
 import android.content.Intent;
 import android.media.MediaRoute2Info;
-import android.media.MediaRoute2ProviderInfo;
 import android.media.MediaRoute2ProviderService;
-import android.media.RouteSessionInfo;
+import android.media.RoutingSessionInfo;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.text.TextUtils;
 
@@ -46,8 +47,8 @@
     public static final String ROUTE_ID5_TO_TRANSFER_TO = "route_id5_to_transfer_to";
     public static final String ROUTE_NAME5 = "Sample Route 5 - Route to transfer to";
 
-    public static final String ROUTE_ID_SPECIAL_CATEGORY = "route_special_category";
-    public static final String ROUTE_NAME_SPECIAL_CATEGORY = "Special Category Route";
+    public static final String ROUTE_ID_SPECIAL_FEATURE = "route_special_feature";
+    public static final String ROUTE_NAME_SPECIAL_FEATURE = "Special Feature Route";
 
     public static final int VOLUME_MAX = 100;
     public static final String ROUTE_ID_FIXED_VOLUME = "route_fixed_volume";
@@ -58,49 +59,49 @@
     public static final String ACTION_REMOVE_ROUTE =
             "com.android.mediarouteprovider.action_remove_route";
 
-    public static final String CATEGORY_SAMPLE =
-            "com.android.mediarouteprovider.CATEGORY_SAMPLE";
-    public static final String CATEGORY_SPECIAL =
-            "com.android.mediarouteprovider.CATEGORY_SPECIAL";
+    public static final String FEATURE_SAMPLE =
+            "com.android.mediarouteprovider.FEATURE_SAMPLE";
+    public static final String FEATURE_SPECIAL =
+            "com.android.mediarouteprovider.FEATURE_SPECIAL";
 
     Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
-    Map<String, Integer> mRouteSessionMap = new HashMap<>();
+    Map<String, String> mRouteIdToSessionId = new HashMap<>();
     private int mNextSessionId = 1000;
 
     private void initializeRoutes() {
         MediaRoute2Info route1 = new MediaRoute2Info.Builder(ROUTE_ID1, ROUTE_NAME1)
-                .addSupportedCategory(CATEGORY_SAMPLE)
-                .setDeviceType(DEVICE_TYPE_TV)
+                .addFeature(FEATURE_SAMPLE)
+                .setDeviceType(DEVICE_TYPE_REMOTE_TV)
                 .build();
         MediaRoute2Info route2 = new MediaRoute2Info.Builder(ROUTE_ID2, ROUTE_NAME2)
-                .addSupportedCategory(CATEGORY_SAMPLE)
-                .setDeviceType(DEVICE_TYPE_SPEAKER)
+                .addFeature(FEATURE_SAMPLE)
+                .setDeviceType(DEVICE_TYPE_REMOTE_SPEAKER)
                 .build();
         MediaRoute2Info route3 = new MediaRoute2Info.Builder(
                 ROUTE_ID3_SESSION_CREATION_FAILED, ROUTE_NAME3)
-                .addSupportedCategory(CATEGORY_SAMPLE)
+                .addFeature(FEATURE_SAMPLE)
                 .build();
         MediaRoute2Info route4 = new MediaRoute2Info.Builder(
                 ROUTE_ID4_TO_SELECT_AND_DESELECT, ROUTE_NAME4)
-                .addSupportedCategory(CATEGORY_SAMPLE)
+                .addFeature(FEATURE_SAMPLE)
                 .build();
         MediaRoute2Info route5 = new MediaRoute2Info.Builder(
                 ROUTE_ID5_TO_TRANSFER_TO, ROUTE_NAME5)
-                .addSupportedCategory(CATEGORY_SAMPLE)
+                .addFeature(FEATURE_SAMPLE)
                 .build();
         MediaRoute2Info routeSpecial =
-                new MediaRoute2Info.Builder(ROUTE_ID_SPECIAL_CATEGORY, ROUTE_NAME_SPECIAL_CATEGORY)
-                        .addSupportedCategory(CATEGORY_SAMPLE)
-                        .addSupportedCategory(CATEGORY_SPECIAL)
+                new MediaRoute2Info.Builder(ROUTE_ID_SPECIAL_FEATURE, ROUTE_NAME_SPECIAL_FEATURE)
+                        .addFeature(FEATURE_SAMPLE)
+                        .addFeature(FEATURE_SPECIAL)
                         .build();
         MediaRoute2Info fixedVolumeRoute =
                 new MediaRoute2Info.Builder(ROUTE_ID_FIXED_VOLUME, ROUTE_NAME_FIXED_VOLUME)
-                        .addSupportedCategory(CATEGORY_SAMPLE)
+                        .addFeature(FEATURE_SAMPLE)
                         .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_FIXED)
                         .build();
         MediaRoute2Info variableVolumeRoute =
                 new MediaRoute2Info.Builder(ROUTE_ID_VARIABLE_VOLUME, ROUTE_NAME_VARIABLE_VOLUME)
-                        .addSupportedCategory(CATEGORY_SAMPLE)
+                        .addFeature(FEATURE_SAMPLE)
                         .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
                         .setVolumeMax(VOLUME_MAX)
                         .build();
@@ -154,6 +155,7 @@
 
     @Override
     public void onUpdateVolume(String routeId, int delta) {
+        android.util.Log.d(TAG, "onUpdateVolume routeId= " + routeId + "delta=" + delta);
         MediaRoute2Info route = mRoutes.get(routeId);
         if (route == null) {
             return;
@@ -167,38 +169,44 @@
     }
 
     @Override
-    public void onCreateSession(String packageName, String routeId, String controlCategory,
-            long requestId) {
+    public void onCreateSession(String packageName, String routeId, long requestId,
+            @Nullable Bundle sessionHints) {
         MediaRoute2Info route = mRoutes.get(routeId);
         if (route == null || TextUtils.equals(ROUTE_ID3_SESSION_CREATION_FAILED, routeId)) {
             // Tell the router that session cannot be created by passing null as sessionInfo.
-            notifySessionCreated(/* sessionInfo= */ null, requestId);
+            notifySessionCreationFailed(requestId);
             return;
         }
         maybeDeselectRoute(routeId);
 
-        final int sessionId = mNextSessionId;
+        final String sessionId = String.valueOf(mNextSessionId);
         mNextSessionId++;
 
         mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
                 .setClientPackageName(packageName)
                 .build());
-        mRouteSessionMap.put(routeId, sessionId);
+        mRouteIdToSessionId.put(routeId, sessionId);
 
-        RouteSessionInfo sessionInfo = new RouteSessionInfo.Builder(
-                sessionId, packageName, controlCategory)
+        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(sessionId, packageName)
                 .addSelectedRoute(routeId)
                 .addSelectableRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT)
                 .addTransferrableRoute(ROUTE_ID5_TO_TRANSFER_TO)
+                // Set control hints with given sessionHints
+                .setControlHints(sessionHints)
                 .build();
         notifySessionCreated(sessionInfo, requestId);
         publishRoutes();
     }
 
     @Override
-    public void onDestroySession(int sessionId, RouteSessionInfo lastSessionInfo) {
-        for (String routeId : lastSessionInfo.getSelectedRoutes()) {
-            mRouteSessionMap.remove(routeId);
+    public void onReleaseSession(String sessionId) {
+        RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
+        if (sessionInfo == null) {
+            return;
+        }
+
+        for (String routeId : sessionInfo.getSelectedRoutes()) {
+            mRouteIdToSessionId.remove(routeId);
             MediaRoute2Info route = mRoutes.get(routeId);
             if (route != null) {
                 mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
@@ -206,11 +214,13 @@
                         .build());
             }
         }
+        notifySessionReleased(sessionId);
+        publishRoutes();
     }
 
     @Override
-    public void onSelectRoute(int sessionId, String routeId) {
-        RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
+    public void onSelectRoute(String sessionId, String routeId) {
+        RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
         MediaRoute2Info route = mRoutes.get(routeId);
         if (route == null || sessionInfo == null) {
             return;
@@ -218,67 +228,90 @@
         maybeDeselectRoute(routeId);
 
         mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
-                .setClientPackageName(sessionInfo.getPackageName())
+                .setClientPackageName(sessionInfo.getClientPackageName())
                 .build());
-        mRouteSessionMap.put(routeId, sessionId);
+        mRouteIdToSessionId.put(routeId, sessionId);
 
-        RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
+        RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
                 .addSelectedRoute(routeId)
                 .removeSelectableRoute(routeId)
                 .addDeselectableRoute(routeId)
                 .build();
-        updateSessionInfo(newSessionInfo);
-        notifySessionInfoChanged(newSessionInfo);
+        notifySessionUpdated(newSessionInfo);
     }
 
     @Override
-    public void onDeselectRoute(int sessionId, String routeId) {
-        RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
+    public void onDeselectRoute(String sessionId, String routeId) {
+        RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
         MediaRoute2Info route = mRoutes.get(routeId);
 
-        mRouteSessionMap.remove(routeId);
-        if (sessionInfo == null || route == null) {
+        if (sessionInfo == null || route == null
+                || !sessionInfo.getSelectedRoutes().contains(routeId)) {
             return;
         }
+
+        mRouteIdToSessionId.remove(routeId);
         mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
                 .setClientPackageName(null)
                 .build());
 
-        RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
+        if (sessionInfo.getSelectedRoutes().size() == 1) {
+            notifySessionReleased(sessionId);
+            return;
+        }
+
+        RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
                 .removeSelectedRoute(routeId)
                 .addSelectableRoute(routeId)
                 .removeDeselectableRoute(routeId)
                 .build();
-        updateSessionInfo(newSessionInfo);
-        notifySessionInfoChanged(newSessionInfo);
+        notifySessionUpdated(newSessionInfo);
     }
 
     @Override
-    public void onTransferToRoute(int sessionId, String routeId) {
-        RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
-        RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
+    public void onTransferToRoute(String sessionId, String routeId) {
+        RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
+        MediaRoute2Info route = mRoutes.get(routeId);
+
+        if (sessionInfo == null || route == null) {
+            return;
+        }
+
+        for (String selectedRouteId : sessionInfo.getSelectedRoutes()) {
+            mRouteIdToSessionId.remove(selectedRouteId);
+            MediaRoute2Info selectedRoute = mRoutes.get(selectedRouteId);
+            if (selectedRoute != null) {
+                mRoutes.put(selectedRouteId, new MediaRoute2Info.Builder(selectedRoute)
+                        .setClientPackageName(null)
+                        .build());
+            }
+        }
+
+        mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
+                .setClientPackageName(sessionInfo.getClientPackageName())
+                .build());
+        mRouteIdToSessionId.put(routeId, sessionId);
+
+        RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
                 .clearSelectedRoutes()
                 .addSelectedRoute(routeId)
                 .removeDeselectableRoute(routeId)
                 .removeTransferrableRoute(routeId)
                 .build();
-        updateSessionInfo(newSessionInfo);
-        notifySessionInfoChanged(newSessionInfo);
+        notifySessionUpdated(newSessionInfo);
+        publishRoutes();
     }
 
     void maybeDeselectRoute(String routeId) {
-        if (!mRouteSessionMap.containsKey(routeId)) {
+        if (!mRouteIdToSessionId.containsKey(routeId)) {
             return;
         }
 
-        int sessionId = mRouteSessionMap.get(routeId);
+        String sessionId = mRouteIdToSessionId.get(routeId);
         onDeselectRoute(sessionId, routeId);
     }
 
     void publishRoutes() {
-        MediaRoute2ProviderInfo info = new MediaRoute2ProviderInfo.Builder()
-                .addRoutes(mRoutes.values())
-                .build();
-        updateProviderInfo(info);
+        notifyRoutes(mRoutes.values());
     }
 }
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRoute2InfoTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRoute2InfoTest.java
new file mode 100644
index 0000000..c46966f
--- /dev/null
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRoute2InfoTest.java
@@ -0,0 +1,363 @@
+/*
+ * 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.mediaroutertest;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertThrows;
+
+import android.media.MediaRoute2Info;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests {@link MediaRoute2Info} and its {@link MediaRoute2Info.Builder builder}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MediaRoute2InfoTest {
+
+    public static final String TEST_ID = "test_id";
+    public static final String TEST_NAME = "test_name";
+    public static final String TEST_ROUTE_TYPE_0 = "test_route_type_0";
+    public static final String TEST_ROUTE_TYPE_1 = "test_route_type_1";
+    public static final int TEST_DEVICE_TYPE = MediaRoute2Info.DEVICE_TYPE_REMOTE_SPEAKER;
+    public static final Uri TEST_ICON_URI = Uri.parse("https://developer.android.com");
+    public static final String TEST_DESCRIPTION = "test_description";
+    public static final int TEST_CONNECTION_STATE = MediaRoute2Info.CONNECTION_STATE_CONNECTING;
+    public static final String TEST_CLIENT_PACKAGE_NAME = "com.test.client.package.name";
+    public static final int TEST_VOLUME_HANDLING = MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE;
+    public static final int TEST_VOLUME_MAX = 100;
+    public static final int TEST_VOLUME = 65;
+
+    public static final String TEST_KEY = "test_key";
+    public static final String TEST_VALUE = "test_value";
+
+    @Test
+    public void testBuilderConstructorWithInvalidValues() {
+        final String nullId = null;
+        final String nullName = null;
+        final String emptyId = "";
+        final String emptyName = "";
+        final String validId = "valid_id";
+        final String validName = "valid_name";
+
+        // ID is invalid
+        assertThrows(IllegalArgumentException.class,
+                () -> new MediaRoute2Info.Builder(nullId, validName));
+        assertThrows(IllegalArgumentException.class,
+                () -> new MediaRoute2Info.Builder(emptyId, validName));
+
+        // name is invalid
+        assertThrows(IllegalArgumentException.class,
+                () -> new MediaRoute2Info.Builder(validId, nullName));
+        assertThrows(IllegalArgumentException.class,
+                () -> new MediaRoute2Info.Builder(validId, emptyName));
+
+        // Both are invalid
+        assertThrows(IllegalArgumentException.class,
+                () -> new MediaRoute2Info.Builder(nullId, nullName));
+        assertThrows(IllegalArgumentException.class,
+                () -> new MediaRoute2Info.Builder(nullId, emptyName));
+        assertThrows(IllegalArgumentException.class,
+                () -> new MediaRoute2Info.Builder(emptyId, nullName));
+        assertThrows(IllegalArgumentException.class,
+                () -> new MediaRoute2Info.Builder(emptyId, emptyName));
+
+
+        // Null RouteInfo (1-argument constructor)
+        final MediaRoute2Info nullRouteInfo = null;
+        assertThrows(NullPointerException.class,
+                () -> new MediaRoute2Info.Builder(nullRouteInfo));
+    }
+
+    @Test
+    public void testBuilderBuildWithEmptyRouteTypesShouldThrowIAE() {
+        MediaRoute2Info.Builder builder = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME);
+        assertThrows(IllegalArgumentException.class, () -> builder.build());
+    }
+
+    @Test
+    public void testBuilderAndGettersOfMediaRoute2Info() {
+        Bundle extras = new Bundle();
+        extras.putString(TEST_KEY, TEST_VALUE);
+
+        MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+                .addFeature(TEST_ROUTE_TYPE_0)
+                .addFeature(TEST_ROUTE_TYPE_1)
+                .setDeviceType(TEST_DEVICE_TYPE)
+                .setIconUri(TEST_ICON_URI)
+                .setDescription(TEST_DESCRIPTION)
+                .setConnectionState(TEST_CONNECTION_STATE)
+                .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
+                .setVolumeHandling(TEST_VOLUME_HANDLING)
+                .setVolumeMax(TEST_VOLUME_MAX)
+                .setVolume(TEST_VOLUME)
+                .setExtras(extras)
+                .build();
+
+        assertEquals(TEST_ID, routeInfo.getId());
+        assertEquals(TEST_NAME, routeInfo.getName());
+
+        assertEquals(2, routeInfo.getFeatures().size());
+        assertEquals(TEST_ROUTE_TYPE_0, routeInfo.getFeatures().get(0));
+        assertEquals(TEST_ROUTE_TYPE_1, routeInfo.getFeatures().get(1));
+
+        assertEquals(TEST_DEVICE_TYPE, routeInfo.getDeviceType());
+        assertEquals(TEST_ICON_URI, routeInfo.getIconUri());
+        assertEquals(TEST_DESCRIPTION, routeInfo.getDescription());
+        assertEquals(TEST_CONNECTION_STATE, routeInfo.getConnectionState());
+        assertEquals(TEST_CLIENT_PACKAGE_NAME, routeInfo.getClientPackageName());
+        assertEquals(TEST_VOLUME_HANDLING, routeInfo.getVolumeHandling());
+        assertEquals(TEST_VOLUME_MAX, routeInfo.getVolumeMax());
+        assertEquals(TEST_VOLUME, routeInfo.getVolume());
+
+        Bundle extrasOut = routeInfo.getExtras();
+        assertNotNull(extrasOut);
+        assertTrue(extrasOut.containsKey(TEST_KEY));
+        assertEquals(TEST_VALUE, extrasOut.getString(TEST_KEY));
+    }
+
+    @Test
+    public void testBuilderSetExtrasWithNull() {
+        MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+                .addFeature(TEST_ROUTE_TYPE_0)
+                .setExtras(null)
+                .build();
+
+        assertNull(routeInfo.getExtras());
+    }
+
+    @Test
+    public void testBuilderaddFeatures() {
+        List<String> routeTypes = new ArrayList<>();
+        routeTypes.add(TEST_ROUTE_TYPE_0);
+        routeTypes.add(TEST_ROUTE_TYPE_1);
+
+        MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+                .addFeatures(routeTypes)
+                .build();
+
+        assertEquals(routeTypes, routeInfo.getFeatures());
+    }
+
+    @Test
+    public void testBuilderclearFeatures() {
+        MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+                .addFeature(TEST_ROUTE_TYPE_0)
+                .addFeature(TEST_ROUTE_TYPE_1)
+                // clearFeatures should clear the route types.
+                .clearFeatures()
+                .addFeature(TEST_ROUTE_TYPE_1)
+                .build();
+
+        assertEquals(1, routeInfo.getFeatures().size());
+        assertEquals(TEST_ROUTE_TYPE_1, routeInfo.getFeatures().get(0));
+    }
+
+    @Test
+    public void testhasAnyFeaturesReturnsFalse() {
+        MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+                .addFeature(TEST_ROUTE_TYPE_0)
+                .addFeature(TEST_ROUTE_TYPE_1)
+                .build();
+
+        List<String> testRouteTypes = new ArrayList<>();
+        testRouteTypes.add("non_matching_route_type_1");
+        testRouteTypes.add("non_matching_route_type_2");
+        testRouteTypes.add("non_matching_route_type_3");
+        testRouteTypes.add("non_matching_route_type_4");
+
+        assertFalse(routeInfo.hasAnyFeatures(testRouteTypes));
+    }
+
+    @Test
+    public void testhasAnyFeaturesReturnsTrue() {
+        MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+                .addFeature(TEST_ROUTE_TYPE_0)
+                .addFeature(TEST_ROUTE_TYPE_1)
+                .build();
+
+        List<String> testRouteTypes = new ArrayList<>();
+        testRouteTypes.add("non_matching_route_type_1");
+        testRouteTypes.add("non_matching_route_type_2");
+        testRouteTypes.add("non_matching_route_type_3");
+        testRouteTypes.add(TEST_ROUTE_TYPE_1);
+
+        assertTrue(routeInfo.hasAnyFeatures(testRouteTypes));
+    }
+
+    @Test
+    public void testEqualsCreatedWithSameArguments() {
+        Bundle extras = new Bundle();
+        extras.putString(TEST_KEY, TEST_VALUE);
+
+        MediaRoute2Info routeInfo1 = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+                .addFeature(TEST_ROUTE_TYPE_0)
+                .addFeature(TEST_ROUTE_TYPE_1)
+                .setDeviceType(TEST_DEVICE_TYPE)
+                .setIconUri(TEST_ICON_URI)
+                .setDescription(TEST_DESCRIPTION)
+                .setConnectionState(TEST_CONNECTION_STATE)
+                .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
+                .setVolumeHandling(TEST_VOLUME_HANDLING)
+                .setVolumeMax(TEST_VOLUME_MAX)
+                .setVolume(TEST_VOLUME)
+                .setExtras(extras)
+                .build();
+
+        MediaRoute2Info routeInfo2 = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+                .addFeature(TEST_ROUTE_TYPE_0)
+                .addFeature(TEST_ROUTE_TYPE_1)
+                .setDeviceType(TEST_DEVICE_TYPE)
+                .setIconUri(TEST_ICON_URI)
+                .setDescription(TEST_DESCRIPTION)
+                .setConnectionState(TEST_CONNECTION_STATE)
+                .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
+                .setVolumeHandling(TEST_VOLUME_HANDLING)
+                .setVolumeMax(TEST_VOLUME_MAX)
+                .setVolume(TEST_VOLUME)
+                .setExtras(extras)
+                .build();
+
+        assertEquals(routeInfo1, routeInfo2);
+        assertEquals(routeInfo1.hashCode(), routeInfo2.hashCode());
+    }
+
+    @Test
+    public void testEqualsCreatedWithBuilderCopyConstructor() {
+        Bundle extras = new Bundle();
+        extras.putString(TEST_KEY, TEST_VALUE);
+
+        MediaRoute2Info routeInfo1 = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+                .addFeature(TEST_ROUTE_TYPE_0)
+                .addFeature(TEST_ROUTE_TYPE_1)
+                .setDeviceType(TEST_DEVICE_TYPE)
+                .setIconUri(TEST_ICON_URI)
+                .setDescription(TEST_DESCRIPTION)
+                .setConnectionState(TEST_CONNECTION_STATE)
+                .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
+                .setVolumeHandling(TEST_VOLUME_HANDLING)
+                .setVolumeMax(TEST_VOLUME_MAX)
+                .setVolume(TEST_VOLUME)
+                .setExtras(extras)
+                .build();
+
+        MediaRoute2Info routeInfo2 = new MediaRoute2Info.Builder(routeInfo1).build();
+
+        assertEquals(routeInfo1, routeInfo2);
+        assertEquals(routeInfo1.hashCode(), routeInfo2.hashCode());
+    }
+
+    @Test
+    public void testEqualsReturnFalse() {
+        Bundle extras = new Bundle();
+        extras.putString(TEST_KEY, TEST_VALUE);
+
+        MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+                .addFeature(TEST_ROUTE_TYPE_0)
+                .addFeature(TEST_ROUTE_TYPE_1)
+                .setDeviceType(TEST_DEVICE_TYPE)
+                .setIconUri(TEST_ICON_URI)
+                .setDescription(TEST_DESCRIPTION)
+                .setConnectionState(TEST_CONNECTION_STATE)
+                .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
+                .setVolumeHandling(TEST_VOLUME_HANDLING)
+                .setVolumeMax(TEST_VOLUME_MAX)
+                .setVolume(TEST_VOLUME)
+                .setExtras(extras)
+                .build();
+
+        // Now, we will use copy constructor
+        assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+                .addFeature("randomRouteType")
+                .build());
+        assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+                .setDeviceType(TEST_DEVICE_TYPE + 1)
+                .build());
+        assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+                .setIconUri(Uri.parse("randomUri"))
+                .build());
+        assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+                .setDescription("randomDescription")
+                .build());
+        assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+                .setConnectionState(TEST_CONNECTION_STATE + 1)
+                .build());
+        assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+                .setClientPackageName("randomPackageName")
+                .build());
+        assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+                .setVolumeHandling(TEST_VOLUME_HANDLING + 1)
+                .build());
+        assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+                .setVolumeMax(TEST_VOLUME_MAX + 100)
+                .build());
+        assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+                .setVolume(TEST_VOLUME + 10)
+                .build());
+        // Note: Extras will not affect the equals.
+    }
+
+    @Test
+    public void testParcelingAndUnParceling() {
+        Bundle extras = new Bundle();
+        extras.putString(TEST_KEY, TEST_VALUE);
+
+        MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+                .addFeature(TEST_ROUTE_TYPE_0)
+                .addFeature(TEST_ROUTE_TYPE_1)
+                .setDeviceType(TEST_DEVICE_TYPE)
+                .setIconUri(TEST_ICON_URI)
+                .setDescription(TEST_DESCRIPTION)
+                .setConnectionState(TEST_CONNECTION_STATE)
+                .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
+                .setVolumeHandling(TEST_VOLUME_HANDLING)
+                .setVolumeMax(TEST_VOLUME_MAX)
+                .setVolume(TEST_VOLUME)
+                .setExtras(extras)
+                .build();
+
+        Parcel parcel = Parcel.obtain();
+        routeInfo.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        MediaRoute2Info routeInfoFromParcel = MediaRoute2Info.CREATOR.createFromParcel(parcel);
+        assertEquals(routeInfo, routeInfoFromParcel);
+        assertEquals(routeInfo.hashCode(), routeInfoFromParcel.hashCode());
+
+        // Check extras
+        Bundle extrasOut = routeInfoFromParcel.getExtras();
+        assertNotNull(extrasOut);
+        assertTrue(extrasOut.containsKey(TEST_KEY));
+        assertEquals(TEST_VALUE, extrasOut.getString(TEST_KEY));
+    }
+}
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
index 86b9706..e0b4545 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
@@ -16,23 +16,19 @@
 
 package com.android.mediaroutertest;
 
-import static android.media.MediaRoute2Info.CONNECTION_STATE_CONNECTED;
 import static android.media.MediaRoute2Info.CONNECTION_STATE_CONNECTING;
-import static android.media.MediaRoute2Info.DEVICE_TYPE_SPEAKER;
-import static android.media.MediaRoute2Info.DEVICE_TYPE_TV;
-import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_FIXED;
+import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_SPEAKER;
 import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE;
 
-import static com.android.mediaroutertest.MediaRouterManagerTest.CATEGORIES_ALL;
-import static com.android.mediaroutertest.MediaRouterManagerTest.CATEGORIES_SPECIAL;
-import static com.android.mediaroutertest.MediaRouterManagerTest.CATEGORY_SAMPLE;
-import static com.android.mediaroutertest.MediaRouterManagerTest.CATEGORY_SPECIAL;
+import static com.android.mediaroutertest.MediaRouterManagerTest.FEATURES_ALL;
+import static com.android.mediaroutertest.MediaRouterManagerTest.FEATURES_SPECIAL;
+import static com.android.mediaroutertest.MediaRouterManagerTest.FEATURE_SAMPLE;
 import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID1;
 import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID2;
 import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID3_SESSION_CREATION_FAILED;
 import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID4_TO_SELECT_AND_DESELECT;
 import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID5_TO_TRANSFER_TO;
-import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_SPECIAL_CATEGORY;
+import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_SPECIAL_FEATURE;
 import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_VARIABLE_VOLUME;
 import static com.android.mediaroutertest.MediaRouterManagerTest.SYSTEM_PROVIDER_ID;
 
@@ -40,6 +36,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.testng.Assert.assertThrows;
 
@@ -47,11 +44,13 @@
 import android.content.Context;
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter2;
+import android.media.MediaRouter2.OnGetControllerHintsListener;
 import android.media.MediaRouter2.RouteCallback;
-import android.media.MediaRouter2.RouteSessionController;
-import android.media.MediaRouter2.SessionCallback;
-import android.media.RouteSessionInfo;
+import android.media.MediaRouter2.RoutingController;
+import android.media.MediaRouter2.RoutingControllerCallback;
+import android.media.RouteDiscoveryPreference;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
@@ -83,6 +82,9 @@
 
     private static final int TIMEOUT_MS = 5000;
 
+    private static final String TEST_KEY = "test_key";
+    private static final String TEST_VALUE = "test_value";
+
     @Before
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getTargetContext();
@@ -95,14 +97,14 @@
     }
 
     /**
-     * Tests if we get proper routes for application that has special control category.
+     * Tests if we get proper routes for application that has special route type.
      */
     @Test
     public void testGetRoutes() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_SPECIAL);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURES_SPECIAL);
 
         assertEquals(1, routes.size());
-        assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY));
+        assertNotNull(routes.get(ROUTE_ID_SPECIAL_FEATURE));
     }
 
     @Test
@@ -114,9 +116,9 @@
                 .setIconUri(new Uri.Builder().path("icon").build())
                 .setVolume(5)
                 .setVolumeMax(20)
-                .addSupportedCategory(CATEGORY_SAMPLE)
+                .addFeature(FEATURE_SAMPLE)
                 .setVolumeHandling(PLAYBACK_VOLUME_VARIABLE)
-                .setDeviceType(DEVICE_TYPE_SPEAKER)
+                .setDeviceType(DEVICE_TYPE_REMOTE_SPEAKER)
                 .build();
 
         MediaRoute2Info routeInfoRebuilt = new MediaRoute2Info.Builder(routeInfo).build();
@@ -131,67 +133,8 @@
     }
 
     @Test
-    public void testRouteInfoInequality() {
-        MediaRoute2Info route = new MediaRoute2Info.Builder("id", "name")
-                .setDescription("description")
-                .setClientPackageName("com.android.mediaroutertest")
-                .setConnectionState(CONNECTION_STATE_CONNECTING)
-                .setIconUri(new Uri.Builder().path("icon").build())
-                .addSupportedCategory(CATEGORY_SAMPLE)
-                .setVolume(5)
-                .setVolumeMax(20)
-                .setVolumeHandling(PLAYBACK_VOLUME_VARIABLE)
-                .setDeviceType(DEVICE_TYPE_SPEAKER)
-                .build();
-
-        MediaRoute2Info routeId = new MediaRoute2Info.Builder(route)
-                .setId("another id").build();
-        assertNotEquals(route, routeId);
-
-        MediaRoute2Info routeName = new MediaRoute2Info.Builder(route)
-                .setName("another name").build();
-        assertNotEquals(route, routeName);
-
-        MediaRoute2Info routeDescription = new MediaRoute2Info.Builder(route)
-                .setDescription("another description").build();
-        assertNotEquals(route, routeDescription);
-
-        MediaRoute2Info routeConnectionState = new MediaRoute2Info.Builder(route)
-                .setConnectionState(CONNECTION_STATE_CONNECTED).build();
-        assertNotEquals(route, routeConnectionState);
-
-        MediaRoute2Info routeIcon = new MediaRoute2Info.Builder(route)
-                .setIconUri(new Uri.Builder().path("new icon").build()).build();
-        assertNotEquals(route, routeIcon);
-
-        MediaRoute2Info routeClient = new MediaRoute2Info.Builder(route)
-                .setClientPackageName("another.client.package").build();
-        assertNotEquals(route, routeClient);
-
-        MediaRoute2Info routeCategory = new MediaRoute2Info.Builder(route)
-                .addSupportedCategory(CATEGORY_SPECIAL).build();
-        assertNotEquals(route, routeCategory);
-
-        MediaRoute2Info routeVolume = new MediaRoute2Info.Builder(route)
-                .setVolume(10).build();
-        assertNotEquals(route, routeVolume);
-
-        MediaRoute2Info routeVolumeMax = new MediaRoute2Info.Builder(route)
-                .setVolumeMax(30).build();
-        assertNotEquals(route, routeVolumeMax);
-
-        MediaRoute2Info routeVolumeHandling = new MediaRoute2Info.Builder(route)
-                .setVolumeHandling(PLAYBACK_VOLUME_FIXED).build();
-        assertNotEquals(route, routeVolumeHandling);
-
-        MediaRoute2Info routeDeviceType = new MediaRoute2Info.Builder(route)
-                .setVolume(DEVICE_TYPE_TV).build();
-        assertNotEquals(route, routeDeviceType);
-    }
-
-    @Test
     public void testControlVolumeWithRouter() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_ALL);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURES_ALL);
 
         MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
         assertNotNull(volRoute);
@@ -202,89 +145,79 @@
         awaitOnRouteChanged(
                 () -> mRouter2.requestUpdateVolume(volRoute, deltaVolume),
                 ROUTE_ID_VARIABLE_VOLUME,
-                (route -> route.getVolume() == originalVolume + deltaVolume));
+                (route -> route.getVolume() == originalVolume + deltaVolume),
+                FEATURES_ALL);
 
         awaitOnRouteChanged(
                 () -> mRouter2.requestSetVolume(volRoute, originalVolume),
                 ROUTE_ID_VARIABLE_VOLUME,
-                (route -> route.getVolume() == originalVolume));
+                (route -> route.getVolume() == originalVolume),
+                FEATURES_ALL);
     }
 
     @Test
-    public void testRegisterSessionCallbackWithInvalidArguments() {
+    public void testRegisterControllerCallbackWithInvalidArguments() {
         Executor executor = mExecutor;
-        SessionCallback callback = new SessionCallback();
+        RoutingControllerCallback callback = new RoutingControllerCallback();
 
         // Tests null executor
         assertThrows(NullPointerException.class,
-                () -> mRouter2.registerSessionCallback(null, callback));
+                () -> mRouter2.registerControllerCallback(null, callback));
 
         // Tests null callback
         assertThrows(NullPointerException.class,
-                () -> mRouter2.registerSessionCallback(executor, null));
+                () -> mRouter2.registerControllerCallback(executor, null));
     }
 
     @Test
-    public void testUnregisterSessionCallbackWithNullCallback() {
+    public void testUnregisterControllerCallbackWithNullCallback() {
         // Tests null callback
         assertThrows(NullPointerException.class,
-                () -> mRouter2.unregisterSessionCallback(null));
+                () -> mRouter2.unregisterControllerCallback(null));
     }
 
     @Test
-    public void testRequestCreateSessionWithInvalidArguments() {
-        MediaRoute2Info route = new MediaRoute2Info.Builder("id", "name").build();
-        String controlCategory = "controlCategory";
-
-        // Tests null route
+    public void testRequestCreateControllerWithNullRoute() {
         assertThrows(NullPointerException.class,
-                () -> mRouter2.requestCreateSession(null, controlCategory));
-
-        // Tests null or empty control category
-        assertThrows(IllegalArgumentException.class,
-                () -> mRouter2.requestCreateSession(route, null));
-        assertThrows(IllegalArgumentException.class,
-                () -> mRouter2.requestCreateSession(route, ""));
+                () -> mRouter2.requestCreateController(null));
     }
 
     @Test
-    public void testRequestCreateSessionSuccess() throws Exception {
-        final List<String> sampleControlCategory = new ArrayList<>();
-        sampleControlCategory.add(CATEGORY_SAMPLE);
+    public void testRequestCreateControllerSuccess() throws Exception {
+        final List<String> sampleRouteFeature = new ArrayList<>();
+        sampleRouteFeature.add(FEATURE_SAMPLE);
 
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteFeature);
         MediaRoute2Info route = routes.get(ROUTE_ID1);
         assertNotNull(route);
 
         final CountDownLatch successLatch = new CountDownLatch(1);
         final CountDownLatch failureLatch = new CountDownLatch(1);
-        final List<RouteSessionController> controllers = new ArrayList<>();
+        final List<RoutingController> controllers = new ArrayList<>();
 
         // Create session with this route
-        SessionCallback sessionCallback = new SessionCallback() {
+        RoutingControllerCallback controllerCallback = new RoutingControllerCallback() {
             @Override
-            public void onSessionCreated(RouteSessionController controller) {
+            public void onControllerCreated(RoutingController controller) {
                 assertNotNull(controller);
                 assertTrue(createRouteMap(controller.getSelectedRoutes()).containsKey(ROUTE_ID1));
-                assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller.getControlCategory()));
                 controllers.add(controller);
                 successLatch.countDown();
             }
 
             @Override
-            public void onSessionCreationFailed(MediaRoute2Info requestedRoute,
-                    String requestedControlCategory) {
+            public void onControllerCreationFailed(MediaRoute2Info requestedRoute) {
                 failureLatch.countDown();
             }
         };
 
         // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
         RouteCallback routeCallback = new RouteCallback();
-        mRouter2.registerRouteCallback(mExecutor, routeCallback);
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
 
         try {
-            mRouter2.registerSessionCallback(mExecutor, sessionCallback);
-            mRouter2.requestCreateSession(route, CATEGORY_SAMPLE);
+            mRouter2.registerControllerCallback(mExecutor, controllerCallback);
+            mRouter2.requestCreateController(route);
             assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
 
             // onSessionCreationFailed should not be called.
@@ -292,47 +225,45 @@
         } finally {
             releaseControllers(controllers);
             mRouter2.unregisterRouteCallback(routeCallback);
-            mRouter2.unregisterSessionCallback(sessionCallback);
+            mRouter2.unregisterControllerCallback(controllerCallback);
         }
     }
 
     @Test
-    public void testRequestCreateSessionFailure() throws Exception {
-        final List<String> sampleControlCategory = new ArrayList<>();
-        sampleControlCategory.add(CATEGORY_SAMPLE);
+    public void testRequestCreateControllerFailure() throws Exception {
+        final List<String> sampleRouteType = new ArrayList<>();
+        sampleRouteType.add(FEATURE_SAMPLE);
 
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
         MediaRoute2Info route = routes.get(ROUTE_ID3_SESSION_CREATION_FAILED);
         assertNotNull(route);
 
         final CountDownLatch successLatch = new CountDownLatch(1);
         final CountDownLatch failureLatch = new CountDownLatch(1);
-        final List<RouteSessionController> controllers = new ArrayList<>();
+        final List<RoutingController> controllers = new ArrayList<>();
 
         // Create session with this route
-        SessionCallback sessionCallback = new SessionCallback() {
+        RoutingControllerCallback controllerCallback = new RoutingControllerCallback() {
             @Override
-            public void onSessionCreated(RouteSessionController controller) {
+            public void onControllerCreated(RoutingController controller) {
                 controllers.add(controller);
                 successLatch.countDown();
             }
 
             @Override
-            public void onSessionCreationFailed(MediaRoute2Info requestedRoute,
-                    String requestedControlCategory) {
+            public void onControllerCreationFailed(MediaRoute2Info requestedRoute) {
                 assertEquals(route, requestedRoute);
-                assertTrue(TextUtils.equals(CATEGORY_SAMPLE, requestedControlCategory));
                 failureLatch.countDown();
             }
         };
 
         // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
         RouteCallback routeCallback = new RouteCallback();
-        mRouter2.registerRouteCallback(mExecutor, routeCallback);
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
 
         try {
-            mRouter2.registerSessionCallback(mExecutor, sessionCallback);
-            mRouter2.requestCreateSession(route, CATEGORY_SAMPLE);
+            mRouter2.registerControllerCallback(mExecutor, controllerCallback);
+            mRouter2.requestCreateController(route);
             assertTrue(failureLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
 
             // onSessionCreated should not be called.
@@ -340,35 +271,34 @@
         } finally {
             releaseControllers(controllers);
             mRouter2.unregisterRouteCallback(routeCallback);
-            mRouter2.unregisterSessionCallback(sessionCallback);
+            mRouter2.unregisterControllerCallback(controllerCallback);
         }
     }
 
     @Test
-    public void testRequestCreateSessionMultipleSessions() throws Exception {
-        final List<String> sampleControlCategory = new ArrayList<>();
-        sampleControlCategory.add(CATEGORY_SAMPLE);
+    public void testRequestCreateControllerMultipleSessions() throws Exception {
+        final List<String> sampleRouteType = new ArrayList<>();
+        sampleRouteType.add(FEATURE_SAMPLE);
 
         final CountDownLatch successLatch = new CountDownLatch(2);
         final CountDownLatch failureLatch = new CountDownLatch(1);
-        final List<RouteSessionController> createdControllers = new ArrayList<>();
+        final List<RoutingController> createdControllers = new ArrayList<>();
 
         // Create session with this route
-        SessionCallback sessionCallback = new SessionCallback() {
+        RoutingControllerCallback controllerCallback = new RoutingControllerCallback() {
             @Override
-            public void onSessionCreated(RouteSessionController controller) {
+            public void onControllerCreated(RoutingController controller) {
                 createdControllers.add(controller);
                 successLatch.countDown();
             }
 
             @Override
-            public void onSessionCreationFailed(MediaRoute2Info requestedRoute,
-                    String requestedControlCategory) {
+            public void onControllerCreationFailed(MediaRoute2Info requestedRoute) {
                 failureLatch.countDown();
             }
         };
 
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
         MediaRoute2Info route1 = routes.get(ROUTE_ID1);
         MediaRoute2Info route2 = routes.get(ROUTE_ID2);
         assertNotNull(route1);
@@ -376,12 +306,12 @@
 
         // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
         RouteCallback routeCallback = new RouteCallback();
-        mRouter2.registerRouteCallback(mExecutor, routeCallback);
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
 
         try {
-            mRouter2.registerSessionCallback(mExecutor, sessionCallback);
-            mRouter2.requestCreateSession(route1, CATEGORY_SAMPLE);
-            mRouter2.requestCreateSession(route2, CATEGORY_SAMPLE);
+            mRouter2.registerControllerCallback(mExecutor, controllerCallback);
+            mRouter2.requestCreateController(route1);
+            mRouter2.requestCreateController(route2);
             assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
 
             // onSessionCreationFailed should not be called.
@@ -389,59 +319,125 @@
 
             // Created controllers should have proper info
             assertEquals(2, createdControllers.size());
-            RouteSessionController controller1 = createdControllers.get(0);
-            RouteSessionController controller2 = createdControllers.get(1);
+            RoutingController controller1 = createdControllers.get(0);
+            RoutingController controller2 = createdControllers.get(1);
 
-            assertNotEquals(controller1.getSessionId(), controller2.getSessionId());
+            assertNotEquals(controller1.getId(), controller2.getId());
             assertTrue(createRouteMap(controller1.getSelectedRoutes()).containsKey(ROUTE_ID1));
             assertTrue(createRouteMap(controller2.getSelectedRoutes()).containsKey(ROUTE_ID2));
-            assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller1.getControlCategory()));
-            assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller2.getControlCategory()));
+
         } finally {
             releaseControllers(createdControllers);
             mRouter2.unregisterRouteCallback(routeCallback);
-            mRouter2.unregisterSessionCallback(sessionCallback);
+            mRouter2.unregisterControllerCallback(controllerCallback);
         }
     }
 
     @Test
-    public void testSessionCallbackIsNotCalledAfterUnregistered() throws Exception {
-        final List<String> sampleControlCategory = new ArrayList<>();
-        sampleControlCategory.add(CATEGORY_SAMPLE);
+    public void testSetOnGetControllerHintsListener() throws Exception {
+        final List<String> sampleRouteFeature = new ArrayList<>();
+        sampleRouteFeature.add(FEATURE_SAMPLE);
 
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteFeature);
         MediaRoute2Info route = routes.get(ROUTE_ID1);
         assertNotNull(route);
 
+        final Bundle createSessionHints = new Bundle();
+        createSessionHints.putString(TEST_KEY, TEST_VALUE);
+        final OnGetControllerHintsListener listener = new OnGetControllerHintsListener() {
+            @Override
+            public Bundle onGetControllerHints(MediaRoute2Info route) {
+                return createSessionHints;
+            }
+        };
+
         final CountDownLatch successLatch = new CountDownLatch(1);
         final CountDownLatch failureLatch = new CountDownLatch(1);
-        final List<RouteSessionController> controllers = new ArrayList<>();
+        final List<RoutingController> controllers = new ArrayList<>();
 
         // Create session with this route
-        SessionCallback sessionCallback = new SessionCallback() {
+        RoutingControllerCallback controllerCallback = new RoutingControllerCallback() {
             @Override
-            public void onSessionCreated(RouteSessionController controller) {
+            public void onControllerCreated(RoutingController controller) {
+                assertNotNull(controller);
+                assertTrue(createRouteMap(controller.getSelectedRoutes()).containsKey(ROUTE_ID1));
+
+                // The SampleMediaRoute2ProviderService supposed to set control hints
+                // with the given creationSessionHints.
+                Bundle controlHints = controller.getControlHints();
+                assertNotNull(controlHints);
+                assertTrue(controlHints.containsKey(TEST_KEY));
+                assertEquals(TEST_VALUE, controlHints.getString(TEST_KEY));
+
                 controllers.add(controller);
                 successLatch.countDown();
             }
 
             @Override
-            public void onSessionCreationFailed(MediaRoute2Info requestedRoute,
-                    String requestedControlCategory) {
+            public void onControllerCreationFailed(MediaRoute2Info requestedRoute) {
                 failureLatch.countDown();
             }
         };
 
         // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
         RouteCallback routeCallback = new RouteCallback();
-        mRouter2.registerRouteCallback(mExecutor, routeCallback);
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
 
         try {
-            mRouter2.registerSessionCallback(mExecutor, sessionCallback);
-            mRouter2.requestCreateSession(route, CATEGORY_SAMPLE);
+            mRouter2.registerControllerCallback(mExecutor, controllerCallback);
+
+            // The SampleMediaRoute2ProviderService supposed to set control hints
+            // with the given creationSessionHints.
+            mRouter2.setOnGetControllerHintsListener(listener);
+            mRouter2.requestCreateController(route);
+            assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            // onSessionCreationFailed should not be called.
+            assertFalse(failureLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            releaseControllers(controllers);
+            mRouter2.unregisterRouteCallback(routeCallback);
+            mRouter2.unregisterControllerCallback(controllerCallback);
+        }
+    }
+
+    @Test
+    public void testRoutingControllerCallbackIsNotCalledAfterUnregistered() throws Exception {
+        final List<String> sampleRouteType = new ArrayList<>();
+        sampleRouteType.add(FEATURE_SAMPLE);
+
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
+        MediaRoute2Info route = routes.get(ROUTE_ID1);
+        assertNotNull(route);
+
+        final CountDownLatch successLatch = new CountDownLatch(1);
+        final CountDownLatch failureLatch = new CountDownLatch(1);
+        final List<RoutingController> controllers = new ArrayList<>();
+
+        // Create session with this route
+        RoutingControllerCallback controllerCallback = new RoutingControllerCallback() {
+            @Override
+            public void onControllerCreated(RoutingController controller) {
+                controllers.add(controller);
+                successLatch.countDown();
+            }
+
+            @Override
+            public void onControllerCreationFailed(MediaRoute2Info requestedRoute) {
+                failureLatch.countDown();
+            }
+        };
+
+        // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
+        RouteCallback routeCallback = new RouteCallback();
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
+
+        try {
+            mRouter2.registerControllerCallback(mExecutor, controllerCallback);
+            mRouter2.requestCreateController(route);
 
             // Unregisters session callback
-            mRouter2.unregisterSessionCallback(sessionCallback);
+            mRouter2.unregisterControllerCallback(controllerCallback);
 
             // No session callback methods should be called.
             assertFalse(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
@@ -449,88 +445,78 @@
         } finally {
             releaseControllers(controllers);
             mRouter2.unregisterRouteCallback(routeCallback);
-            mRouter2.unregisterSessionCallback(sessionCallback);
+            mRouter2.unregisterControllerCallback(controllerCallback);
         }
     }
 
     // TODO: Add tests for illegal inputs if needed (e.g. selecting already selected route)
     @Test
-    public void testRouteSessionControllerSelectAndDeselectRoute() throws Exception {
-        final List<String> sampleControlCategory = new ArrayList<>();
-        sampleControlCategory.add(CATEGORY_SAMPLE);
+    public void testRoutingControllerSelectAndDeselectRoute() throws Exception {
+        final List<String> sampleRouteType = new ArrayList<>();
+        sampleRouteType.add(FEATURE_SAMPLE);
 
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
         MediaRoute2Info routeToCreateSessionWith = routes.get(ROUTE_ID1);
         assertNotNull(routeToCreateSessionWith);
 
-        final CountDownLatch onSessionCreatedLatch = new CountDownLatch(1);
-        final CountDownLatch onSessionInfoChangedLatchForSelect = new CountDownLatch(1);
-        final CountDownLatch onSessionInfoChangedLatchForDeselect = new CountDownLatch(1);
-        final List<RouteSessionController> controllers = new ArrayList<>();
+        final CountDownLatch onControllerCreatedLatch = new CountDownLatch(1);
+        final CountDownLatch onControllerUpdatedLatchForSelect = new CountDownLatch(1);
+        final CountDownLatch onControllerUpdatedLatchForDeselect = new CountDownLatch(1);
+        final List<RoutingController> controllers = new ArrayList<>();
 
         // Create session with ROUTE_ID1
-        SessionCallback sessionCallback = new SessionCallback() {
+        RoutingControllerCallback controllerCallback = new RoutingControllerCallback() {
             @Override
-            public void onSessionCreated(RouteSessionController controller) {
+            public void onControllerCreated(RoutingController controller) {
                 assertNotNull(controller);
                 assertTrue(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1));
-                assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller.getControlCategory()));
                 controllers.add(controller);
-                onSessionCreatedLatch.countDown();
+                onControllerCreatedLatch.countDown();
             }
 
             @Override
-            public void onSessionInfoChanged(RouteSessionController controller,
-                    RouteSessionInfo oldInfo, RouteSessionInfo newInfo) {
-                if (onSessionCreatedLatch.getCount() != 0
-                        || controllers.get(0).getSessionId() != controller.getSessionId()) {
+            public void onControllerUpdated(RoutingController controller) {
+                if (onControllerCreatedLatch.getCount() != 0
+                        || !TextUtils.equals(
+                                controllers.get(0).getId(), controller.getId())) {
                     return;
                 }
 
-                if (onSessionInfoChangedLatchForSelect.getCount() != 0) {
-                    // Check oldInfo
-                    assertEquals(controller.getSessionId(), oldInfo.getSessionId());
-                    assertEquals(1, oldInfo.getSelectedRoutes().size());
-                    assertTrue(oldInfo.getSelectedRoutes().contains(ROUTE_ID1));
-                    assertTrue(oldInfo.getSelectableRoutes().contains(
-                            ROUTE_ID4_TO_SELECT_AND_DESELECT));
+                if (onControllerUpdatedLatchForSelect.getCount() != 0) {
+                    assertEquals(2, controller.getSelectedRoutes().size());
+                    assertTrue(getRouteIds(controller.getSelectedRoutes())
+                            .contains(ROUTE_ID1));
+                    assertTrue(getRouteIds(controller.getSelectedRoutes())
+                            .contains(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+                    assertFalse(getRouteIds(controller.getSelectableRoutes())
+                            .contains(ROUTE_ID4_TO_SELECT_AND_DESELECT));
 
-                    // Check newInfo
-                    assertEquals(controller.getSessionId(), newInfo.getSessionId());
-                    assertEquals(2, newInfo.getSelectedRoutes().size());
-                    assertTrue(newInfo.getSelectedRoutes().contains(ROUTE_ID1));
-                    assertTrue(newInfo.getSelectedRoutes().contains(
-                            ROUTE_ID4_TO_SELECT_AND_DESELECT));
-                    assertFalse(newInfo.getSelectableRoutes().contains(
-                            ROUTE_ID4_TO_SELECT_AND_DESELECT));
-
-                    onSessionInfoChangedLatchForSelect.countDown();
+                    onControllerUpdatedLatchForSelect.countDown();
                 } else {
-                    // Check newInfo
-                    assertEquals(controller.getSessionId(), newInfo.getSessionId());
-                    assertEquals(1, newInfo.getSelectedRoutes().size());
-                    assertTrue(newInfo.getSelectedRoutes().contains(ROUTE_ID1));
-                    assertFalse(newInfo.getSelectedRoutes().contains(
-                            ROUTE_ID4_TO_SELECT_AND_DESELECT));
-                    assertTrue(newInfo.getSelectableRoutes().contains(
-                            ROUTE_ID4_TO_SELECT_AND_DESELECT));
+                    assertEquals(1, controller.getSelectedRoutes().size());
+                    assertTrue(getRouteIds(controller.getSelectedRoutes())
+                            .contains(ROUTE_ID1));
+                    assertFalse(getRouteIds(controller.getSelectedRoutes())
+                            .contains(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+                    assertTrue(getRouteIds(controller.getSelectableRoutes())
+                            .contains(ROUTE_ID4_TO_SELECT_AND_DESELECT));
 
-                    onSessionInfoChangedLatchForDeselect.countDown();
+                    onControllerUpdatedLatchForDeselect.countDown();
                 }
             }
         };
 
         // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
         RouteCallback routeCallback = new RouteCallback();
-        mRouter2.registerRouteCallback(mExecutor, routeCallback);
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
 
         try {
-            mRouter2.registerSessionCallback(mExecutor, sessionCallback);
-            mRouter2.requestCreateSession(routeToCreateSessionWith, CATEGORY_SAMPLE);
-            assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            mRouter2.registerControllerCallback(mExecutor, controllerCallback);
+            mRouter2.requestCreateController(routeToCreateSessionWith);
+            assertTrue(onControllerCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
 
             assertEquals(1, controllers.size());
-            RouteSessionController controller = controllers.get(0);
+            RoutingController controller = controllers.get(0);
             assertTrue(getRouteIds(controller.getSelectableRoutes())
                     .contains(ROUTE_ID4_TO_SELECT_AND_DESELECT));
 
@@ -540,78 +526,70 @@
             assertNotNull(routeToSelectAndDeselect);
 
             controller.selectRoute(routeToSelectAndDeselect);
-            assertTrue(onSessionInfoChangedLatchForSelect.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            assertTrue(onControllerUpdatedLatchForSelect.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
 
             controller.deselectRoute(routeToSelectAndDeselect);
-            assertTrue(onSessionInfoChangedLatchForDeselect.await(
+            assertTrue(onControllerUpdatedLatchForDeselect.await(
                     TIMEOUT_MS, TimeUnit.MILLISECONDS));
         } finally {
             releaseControllers(controllers);
             mRouter2.unregisterRouteCallback(routeCallback);
-            mRouter2.unregisterSessionCallback(sessionCallback);
+            mRouter2.unregisterControllerCallback(controllerCallback);
         }
     }
 
     @Test
-    public void testRouteSessionControllerTransferToRoute() throws Exception {
-        final List<String> sampleControlCategory = new ArrayList<>();
-        sampleControlCategory.add(CATEGORY_SAMPLE);
+    public void testRoutingControllerTransferToRoute() throws Exception {
+        final List<String> sampleRouteType = new ArrayList<>();
+        sampleRouteType.add(FEATURE_SAMPLE);
 
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
         MediaRoute2Info routeToCreateSessionWith = routes.get(ROUTE_ID1);
         assertNotNull(routeToCreateSessionWith);
 
-        final CountDownLatch onSessionCreatedLatch = new CountDownLatch(1);
-        final CountDownLatch onSessionInfoChangedLatch = new CountDownLatch(1);
-        final List<RouteSessionController> controllers = new ArrayList<>();
+        final CountDownLatch onControllerCreatedLatch = new CountDownLatch(1);
+        final CountDownLatch onControllerUpdatedLatch = new CountDownLatch(1);
+        final List<RoutingController> controllers = new ArrayList<>();
 
         // Create session with ROUTE_ID1
-        SessionCallback sessionCallback = new SessionCallback() {
+        RoutingControllerCallback controllerCallback = new RoutingControllerCallback() {
             @Override
-            public void onSessionCreated(RouteSessionController controller) {
+            public void onControllerCreated(RoutingController controller) {
                 assertNotNull(controller);
                 assertTrue(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1));
-                assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller.getControlCategory()));
                 controllers.add(controller);
-                onSessionCreatedLatch.countDown();
+                onControllerCreatedLatch.countDown();
             }
 
             @Override
-            public void onSessionInfoChanged(RouteSessionController controller,
-                    RouteSessionInfo oldInfo, RouteSessionInfo newInfo) {
-                if (onSessionCreatedLatch.getCount() != 0
-                        || controllers.get(0).getSessionId() != controller.getSessionId()) {
+            public void onControllerUpdated(RoutingController controller) {
+                if (onControllerCreatedLatch.getCount() != 0
+                        || !TextUtils.equals(
+                                controllers.get(0).getId(), controller.getId())) {
                     return;
                 }
+                assertEquals(1, controller.getSelectedRoutes().size());
+                assertFalse(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1));
+                assertTrue(getRouteIds(controller.getSelectedRoutes())
+                        .contains(ROUTE_ID5_TO_TRANSFER_TO));
+                assertFalse(getRouteIds(controller.getTransferrableRoutes())
+                        .contains(ROUTE_ID5_TO_TRANSFER_TO));
 
-                // Check oldInfo
-                assertEquals(controller.getSessionId(), oldInfo.getSessionId());
-                assertEquals(1, oldInfo.getSelectedRoutes().size());
-                assertTrue(oldInfo.getSelectedRoutes().contains(ROUTE_ID1));
-                assertTrue(oldInfo.getTransferrableRoutes().contains(ROUTE_ID5_TO_TRANSFER_TO));
-
-                // Check newInfo
-                assertEquals(controller.getSessionId(), newInfo.getSessionId());
-                assertEquals(1, newInfo.getSelectedRoutes().size());
-                assertFalse(newInfo.getSelectedRoutes().contains(ROUTE_ID1));
-                assertTrue(newInfo.getSelectedRoutes().contains(ROUTE_ID5_TO_TRANSFER_TO));
-                assertFalse(newInfo.getTransferrableRoutes().contains(ROUTE_ID5_TO_TRANSFER_TO));
-
-                onSessionInfoChangedLatch.countDown();
+                onControllerUpdatedLatch.countDown();
             }
         };
 
         // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
         RouteCallback routeCallback = new RouteCallback();
-        mRouter2.registerRouteCallback(mExecutor, routeCallback);
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
 
         try {
-            mRouter2.registerSessionCallback(mExecutor, sessionCallback);
-            mRouter2.requestCreateSession(routeToCreateSessionWith, CATEGORY_SAMPLE);
-            assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            mRouter2.registerControllerCallback(mExecutor, controllerCallback);
+            mRouter2.requestCreateController(routeToCreateSessionWith);
+            assertTrue(onControllerCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
 
             assertEquals(1, controllers.size());
-            RouteSessionController controller = controllers.get(0);
+            RoutingController controller = controllers.get(0);
             assertTrue(getRouteIds(controller.getTransferrableRoutes())
                     .contains(ROUTE_ID5_TO_TRANSFER_TO));
 
@@ -620,62 +598,70 @@
             assertNotNull(routeToTransferTo);
 
             controller.transferToRoute(routeToTransferTo);
-            assertTrue(onSessionInfoChangedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            assertTrue(onControllerUpdatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
         } finally {
             releaseControllers(controllers);
             mRouter2.unregisterRouteCallback(routeCallback);
-            mRouter2.unregisterSessionCallback(sessionCallback);
+            mRouter2.unregisterControllerCallback(controllerCallback);
         }
     }
 
-    // TODO: Add tests for onSessionReleased() call.
+    // TODO: Add tests for onSessionReleased() when provider releases the session.
 
     @Test
-    public void testRouteSessionControllerReleaseShouldIgnoreTransferTo() throws Exception {
-        final List<String> sampleControlCategory = new ArrayList<>();
-        sampleControlCategory.add(CATEGORY_SAMPLE);
+    public void testRoutingControllerRelease() throws Exception {
+        final List<String> sampleRouteType = new ArrayList<>();
+        sampleRouteType.add(FEATURE_SAMPLE);
 
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
         MediaRoute2Info routeToCreateSessionWith = routes.get(ROUTE_ID1);
         assertNotNull(routeToCreateSessionWith);
 
-        final CountDownLatch onSessionCreatedLatch = new CountDownLatch(1);
-        final CountDownLatch onSessionInfoChangedLatch = new CountDownLatch(1);
-        final List<RouteSessionController> controllers = new ArrayList<>();
+        final CountDownLatch onControllerCreatedLatch = new CountDownLatch(1);
+        final CountDownLatch onControllerUpdatedLatch = new CountDownLatch(1);
+        final CountDownLatch onControllerReleasedLatch = new CountDownLatch(1);
+        final List<RoutingController> controllers = new ArrayList<>();
 
         // Create session with ROUTE_ID1
-        SessionCallback sessionCallback = new SessionCallback() {
+        RoutingControllerCallback controllerCallback = new RoutingControllerCallback() {
             @Override
-            public void onSessionCreated(RouteSessionController controller) {
+            public void onControllerCreated(RoutingController controller) {
                 assertNotNull(controller);
                 assertTrue(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1));
-                assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller.getControlCategory()));
                 controllers.add(controller);
-                onSessionCreatedLatch.countDown();
+                onControllerCreatedLatch.countDown();
             }
 
             @Override
-            public void onSessionInfoChanged(RouteSessionController controller,
-                    RouteSessionInfo oldInfo, RouteSessionInfo newInfo) {
-                if (onSessionCreatedLatch.getCount() != 0
-                        || controllers.get(0).getSessionId() != controller.getSessionId()) {
+            public void onControllerUpdated(RoutingController controller) {
+                if (onControllerCreatedLatch.getCount() != 0
+                        || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
                     return;
                 }
-                onSessionInfoChangedLatch.countDown();
+                onControllerUpdatedLatch.countDown();
+            }
+
+            @Override
+            public void onControllerReleased(RoutingController controller) {
+                if (onControllerCreatedLatch.getCount() != 0
+                        || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
+                    return;
+                }
+                onControllerReleasedLatch.countDown();
             }
         };
 
         // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
         RouteCallback routeCallback = new RouteCallback();
-        mRouter2.registerRouteCallback(mExecutor, routeCallback);
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
 
         try {
-            mRouter2.registerSessionCallback(mExecutor, sessionCallback);
-            mRouter2.requestCreateSession(routeToCreateSessionWith, CATEGORY_SAMPLE);
-            assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            mRouter2.registerControllerCallback(mExecutor, controllerCallback);
+            mRouter2.requestCreateController(routeToCreateSessionWith);
+            assertTrue(onControllerCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
 
             assertEquals(1, controllers.size());
-            RouteSessionController controller = controllers.get(0);
+            RoutingController controller = controllers.get(0);
             assertTrue(getRouteIds(controller.getTransferrableRoutes())
                     .contains(ROUTE_ID5_TO_TRANSFER_TO));
 
@@ -689,14 +675,33 @@
             // This call should be ignored.
             // The onSessionInfoChanged() shouldn't be called.
             controller.transferToRoute(routeToTransferTo);
-            assertFalse(onSessionInfoChangedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            assertFalse(onControllerUpdatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            // onControllerReleased should be called.
+            assertTrue(onControllerReleasedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
         } finally {
             releaseControllers(controllers);
             mRouter2.unregisterRouteCallback(routeCallback);
-            mRouter2.unregisterSessionCallback(sessionCallback);
+            mRouter2.unregisterControllerCallback(controllerCallback);
         }
     }
 
+    // TODO: Consider adding tests with bluetooth connection/disconnection.
+    @Test
+    public void testGetSystemController() {
+        final RoutingController systemController = mRouter2.getSystemController();
+        assertNotNull(systemController);
+        assertFalse(systemController.isReleased());
+    }
+
+    @Test
+    public void testControllers() {
+        List<RoutingController> controllers = mRouter2.getControllers();
+        assertNotNull(controllers);
+        assertFalse(controllers.isEmpty());
+        assertSame(mRouter2.getSystemController(), controllers.get(0));
+    }
+
     // Helper for getting routes easily
     static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
         Map<String, MediaRoute2Info> routeMap = new HashMap<>();
@@ -706,11 +711,11 @@
         return routeMap;
     }
 
-    Map<String, MediaRoute2Info> waitAndGetRoutes(List<String> controlCategories)
+    Map<String, MediaRoute2Info> waitAndGetRoutes(List<String> routeTypes)
             throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
 
-        // A dummy callback is required to send control category info.
+        // A dummy callback is required to send route type info.
         RouteCallback routeCallback = new RouteCallback() {
             @Override
             public void onRoutesAdded(List<MediaRoute2Info> routes) {
@@ -723,8 +728,8 @@
             }
         };
 
-        mRouter2.setControlCategories(controlCategories);
-        mRouter2.registerRouteCallback(mExecutor, routeCallback);
+        mRouter2.registerRouteCallback(mExecutor, routeCallback,
+                new RouteDiscoveryPreference.Builder(routeTypes, true).build());
         try {
             latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
             return createRouteMap(mRouter2.getRoutes());
@@ -733,8 +738,8 @@
         }
     }
 
-    static void releaseControllers(@NonNull List<RouteSessionController> controllers) {
-        for (RouteSessionController controller : controllers) {
+    static void releaseControllers(@NonNull List<RoutingController> controllers) {
+        for (RoutingController controller : controllers) {
             controller.release();
         }
     }
@@ -751,7 +756,8 @@
     }
 
     void awaitOnRouteChanged(Runnable task, String routeId,
-            Predicate<MediaRoute2Info> predicate) throws Exception {
+            Predicate<MediaRoute2Info> predicate,
+            List<String> routeTypes) throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
         RouteCallback routeCallback = new RouteCallback() {
             @Override
@@ -762,7 +768,8 @@
                 }
             }
         };
-        mRouter2.registerRouteCallback(mExecutor, routeCallback);
+        mRouter2.registerRouteCallback(mExecutor, routeCallback,
+                new RouteDiscoveryPreference.Builder(routeTypes, true).build());
         try {
             task.run();
             assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
index 1fd0141..4a2044a 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
@@ -20,7 +20,6 @@
 import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
@@ -29,8 +28,10 @@
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter2;
 import android.media.MediaRouter2.RouteCallback;
-import android.media.MediaRouter2.SessionCallback;
+import android.media.MediaRouter2.RoutingControllerCallback;
 import android.media.MediaRouter2Manager;
+import android.media.RouteDiscoveryPreference;
+import android.media.RoutingSessionInfo;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -38,7 +39,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -75,9 +75,9 @@
             SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_id5_to_transfer_to";
     public static final String ROUTE_NAME5 = "Sample Route 5 - Route to transfer to";
 
-    public static final String ROUTE_ID_SPECIAL_CATEGORY =
-            SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_special_category";
-    public static final String ROUTE_NAME_SPECIAL_CATEGORY = "Special Category Route";
+    public static final String ROUTE_ID_SPECIAL_FEATURE =
+            SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_special_feature";
+    public static final String ROUTE_NAME_SPECIAL_FEATURE = "Special Feature Route";
 
     public static final String SYSTEM_PROVIDER_ID =
             "com.android.server.media/.SystemMediaRoute2Provider";
@@ -93,12 +93,12 @@
     public static final String ACTION_REMOVE_ROUTE =
             "com.android.mediarouteprovider.action_remove_route";
 
-    public static final String CATEGORY_SAMPLE =
-            "com.android.mediarouteprovider.CATEGORY_SAMPLE";
-    public static final String CATEGORY_SPECIAL =
-            "com.android.mediarouteprovider.CATEGORY_SPECIAL";
+    public static final String FEATURE_SAMPLE =
+            "com.android.mediarouteprovider.FEATURE_SAMPLE";
+    public static final String FEATURE_SPECIAL =
+            "com.android.mediarouteprovider.FEATURE_SPECIAL";
 
-    private static final String CATEGORY_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO";
+    private static final String FEATURE_LIVE_AUDIO = "android.media.intent.route.LIVE_AUDIO";
 
     private static final int TIMEOUT_MS = 5000;
 
@@ -110,20 +110,20 @@
 
     private final List<MediaRouter2Manager.Callback> mManagerCallbacks = new ArrayList<>();
     private final List<RouteCallback> mRouteCallbacks = new ArrayList<>();
-    private final List<SessionCallback> mSessionCallbacks = new ArrayList<>();
+    private final List<RoutingControllerCallback> mControllerCallbacks = new ArrayList<>();
 
-    public static final List<String> CATEGORIES_ALL = new ArrayList();
-    public static final List<String> CATEGORIES_SPECIAL = new ArrayList();
-    private static final List<String> CATEGORIES_LIVE_AUDIO = new ArrayList<>();
+    public static final List<String> FEATURES_ALL = new ArrayList();
+    public static final List<String> FEATURES_SPECIAL = new ArrayList();
+    private static final List<String> FEATURES_LIVE_AUDIO = new ArrayList<>();
 
     static {
-        CATEGORIES_ALL.add(CATEGORY_SAMPLE);
-        CATEGORIES_ALL.add(CATEGORY_SPECIAL);
-        CATEGORIES_ALL.add(CATEGORY_LIVE_AUDIO);
+        FEATURES_ALL.add(FEATURE_SAMPLE);
+        FEATURES_ALL.add(FEATURE_SPECIAL);
+        FEATURES_ALL.add(FEATURE_LIVE_AUDIO);
 
-        CATEGORIES_SPECIAL.add(CATEGORY_SPECIAL);
+        FEATURES_SPECIAL.add(FEATURE_SPECIAL);
 
-        CATEGORIES_LIVE_AUDIO.add(CATEGORY_LIVE_AUDIO);
+        FEATURES_LIVE_AUDIO.add(FEATURE_LIVE_AUDIO);
     }
 
     @Before
@@ -138,24 +138,12 @@
 
     @After
     public void tearDown() {
+        // order matters (callbacks should be cleared at the last)
+        releaseAllSessions();
         // unregister callbacks
         clearCallbacks();
     }
 
-    //TODO: Move to a separate file
-    @Test
-    public void testMediaRoute2Info() {
-        MediaRoute2Info routeInfo1 = new MediaRoute2Info.Builder("id", "name")
-                .build();
-        MediaRoute2Info routeInfo2 = new MediaRoute2Info.Builder(routeInfo1).build();
-
-        MediaRoute2Info routeInfo3 = new MediaRoute2Info.Builder(routeInfo1)
-                .setClientPackageName(mPackageName).build();
-
-        assertEquals(routeInfo1, routeInfo2);
-        assertNotEquals(routeInfo1, routeInfo3);
-    }
-
     /**
      * Tests if routes are added correctly when a new callback is registered.
      */
@@ -180,7 +168,7 @@
     @Test
     public void testOnRoutesRemoved() throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
 
         addRouterCallback(new RouteCallback());
         addManagerCallback(new MediaRouter2Manager.Callback() {
@@ -202,14 +190,14 @@
     }
 
     /**
-     * Tests if we get proper routes for application that has special control category.
+     * Tests if we get proper routes for application that has special route feature.
      */
     @Test
-    public void testControlCategory() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_SPECIAL);
+    public void testRouteFeatures() throws Exception {
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_SPECIAL);
 
         assertEquals(1, routes.size());
-        assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY));
+        assertNotNull(routes.get(ROUTE_ID_SPECIAL_FEATURE));
     }
 
     /**
@@ -218,16 +206,16 @@
      */
     @Test
     public void testRouterOnSessionCreated() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
 
         CountDownLatch latch = new CountDownLatch(1);
 
         addManagerCallback(new MediaRouter2Manager.Callback());
         //TODO: remove this when it's not necessary.
         addRouterCallback(new MediaRouter2.RouteCallback());
-        addSessionCallback(new SessionCallback() {
+        addSessionCallback(new RoutingControllerCallback() {
             @Override
-            public void onSessionCreated(MediaRouter2.RouteSessionController controller) {
+            public void onControllerCreated(MediaRouter2.RoutingController controller) {
                 if (createRouteMap(controller.getSelectedRoutes()).containsKey(ROUTE_ID1)) {
                     latch.countDown();
                 }
@@ -237,115 +225,88 @@
         MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1);
         assertNotNull(routeToSelect);
 
-        try {
-            mManager.selectRoute(mPackageName, routeToSelect);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            //TODO: release the session
-            //mManager.selectRoute(mPackageName, null);
-        }
+        mManager.selectRoute(mPackageName, routeToSelect);
+        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertEquals(2, mManager.getActiveSessions().size());
     }
 
-    /**
-     * Tests if MR2Manager.Callback.onRouteSelected is called
-     * when a route is selected by MR2Manager.
-     */
     @Test
-    @Ignore("TODO: test session created callback instead of onRouteSelected")
-    public void testManagerOnRouteSelected() throws Exception {
+    public void testGetRoutingControllers() throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
 
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
         addRouterCallback(new RouteCallback());
         addManagerCallback(new MediaRouter2Manager.Callback() {
             @Override
-            public void onRouteSelected(String packageName, MediaRoute2Info route) {
-                if (TextUtils.equals(mPackageName, packageName)
-                        && route != null && TextUtils.equals(route.getId(), ROUTE_ID1)) {
+            public void onSessionCreated(MediaRouter2Manager.RoutingController controller) {
+                if (TextUtils.equals(mPackageName, controller.getClientPackageName())
+                        && createRouteMap(controller.getSelectedRoutes()).containsKey(ROUTE_ID1)) {
                     latch.countDown();
                 }
             }
         });
 
-        MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1);
-        assertNotNull(routeToSelect);
-
-        try {
-            mManager.selectRoute(mPackageName, routeToSelect);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            //TODO: release the session
-            //mManager.selectRoute(mPackageName, null);
-        }
-    }
-
-    @Test
-    @Ignore("TODO: enable this when 'releasing session' is implemented")
-    public void testGetActiveRoutes() throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
-        addRouterCallback(new RouteCallback());
-        addManagerCallback(new MediaRouter2Manager.Callback() {
-            @Override
-            public void onRouteSelected(String packageName, MediaRoute2Info route) {
-                if (TextUtils.equals(mPackageName, packageName)
-                        && route != null && TextUtils.equals(route.getId(), ROUTE_ID1)) {
-                    latch.countDown();
-                }
-            }
-        });
-
-        //TODO: it fails due to not releasing session
-        assertEquals(0, mManager.getActiveSessions().size());
+        assertEquals(1, mManager.getRoutingControllers(mPackageName).size());
 
         mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1));
         latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
 
-        assertEquals(1, mManager.getActiveSessions().size());
+        List<MediaRouter2Manager.RoutingController> controllers =
+                mManager.getRoutingControllers(mPackageName);
 
-        //TODO: release the session
-        /*
+        assertEquals(2, controllers.size());
+
+        MediaRouter2Manager.RoutingController routingController = controllers.get(1);
         awaitOnRouteChangedManager(
-                () -> mManager.selectRoute(mPackageName, null),
+                () -> routingController.release(),
                 ROUTE_ID1,
                 route -> TextUtils.equals(route.getClientPackageName(), null));
-        assertEquals(0, mManager.getActiveRoutes().size());
-        */
+        assertEquals(1, mManager.getRoutingControllers(mPackageName).size());
     }
 
     /**
-     * Tests selecting and unselecting routes of a single provider.
+     * Tests select, transfer, release of routes of a provider
      */
     @Test
-    @Ignore("TODO: enable when session is released")
-    public void testSingleProviderSelect() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+    public void testSelectAndTransferAndRelease() throws Exception {
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
         addRouterCallback(new RouteCallback());
 
+        CountDownLatch onSessionCreatedLatch = new CountDownLatch(1);
+
+        addManagerCallback(new MediaRouter2Manager.Callback() {
+            @Override
+            public void onSessionCreated(MediaRouter2Manager.RoutingController controller) {
+                assertNotNull(controller);
+                onSessionCreatedLatch.countDown();
+            }
+        });
         awaitOnRouteChangedManager(
                 () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1)),
                 ROUTE_ID1,
                 route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
+        assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+        List<MediaRouter2Manager.RoutingController> controllers =
+                mManager.getRoutingControllers(mPackageName);
+
+        assertEquals(2, controllers.size());
+        MediaRouter2Manager.RoutingController routingController = controllers.get(1);
 
         awaitOnRouteChangedManager(
-                () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID2)),
-                ROUTE_ID2,
+                () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID5_TO_TRANSFER_TO)),
+                ROUTE_ID5_TO_TRANSFER_TO,
                 route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
 
-        //TODO: release the session
-        /*
         awaitOnRouteChangedManager(
-                () -> mManager.selectRoute(mPackageName, null),
-                ROUTE_ID2,
+                () -> routingController.release(),
+                ROUTE_ID5_TO_TRANSFER_TO,
                 route -> TextUtils.equals(route.getClientPackageName(), null));
-
-        */
     }
 
     @Test
     public void testControlVolumeWithManager() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
         MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
 
         int originalVolume = volRoute.getVolume();
@@ -364,7 +325,7 @@
 
     @Test
     public void testVolumeHandling() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
 
         MediaRoute2Info fixedVolumeRoute = routes.get(ROUTE_ID_FIXED_VOLUME);
         MediaRoute2Info variableVolumeRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
@@ -374,11 +335,11 @@
         assertEquals(VOLUME_MAX, variableVolumeRoute.getVolumeMax());
     }
 
-    Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> controlCategories)
+    Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> routeFeatures)
             throws Exception {
         CountDownLatch latch = new CountDownLatch(2);
 
-        // A dummy callback is required to send control category info.
+        // A dummy callback is required to send route feature info.
         RouteCallback routeCallback = new RouteCallback();
         MediaRouter2Manager.Callback managerCallback = new MediaRouter2Manager.Callback() {
             @Override
@@ -393,16 +354,17 @@
             }
 
             @Override
-            public void onControlCategoriesChanged(String packageName, List<String> categories) {
+            public void onControlCategoriesChanged(String packageName,
+                    List<String> preferredFeatures) {
                 if (TextUtils.equals(mPackageName, packageName)
-                        && controlCategories.equals(categories)) {
+                        && preferredFeatures.equals(preferredFeatures)) {
                     latch.countDown();
                 }
             }
         };
         mManager.registerCallback(mExecutor, managerCallback);
-        mRouter2.setControlCategories(controlCategories);
-        mRouter2.registerRouteCallback(mExecutor, routeCallback);
+        mRouter2.registerRouteCallback(mExecutor, routeCallback,
+                new RouteDiscoveryPreference.Builder(routeFeatures, true).build());
         try {
             latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
             return createRouteMap(mManager.getAvailableRoutes(mPackageName));
@@ -449,12 +411,12 @@
 
     private void addRouterCallback(RouteCallback routeCallback) {
         mRouteCallbacks.add(routeCallback);
-        mRouter2.registerRouteCallback(mExecutor, routeCallback);
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
     }
 
-    private void addSessionCallback(SessionCallback sessionCallback) {
-        mSessionCallbacks.add(sessionCallback);
-        mRouter2.registerSessionCallback(mExecutor, sessionCallback);
+    private void addSessionCallback(RoutingControllerCallback controllerCallback) {
+        mControllerCallbacks.add(controllerCallback);
+        mRouter2.registerControllerCallback(mExecutor, controllerCallback);
     }
 
     private void clearCallbacks() {
@@ -468,9 +430,18 @@
         }
         mRouteCallbacks.clear();
 
-        for (SessionCallback sessionCallback : mSessionCallbacks) {
-            mRouter2.unregisterSessionCallback(sessionCallback);
+        for (RoutingControllerCallback controllerCallback : mControllerCallbacks) {
+            mRouter2.unregisterControllerCallback(controllerCallback);
         }
-        mSessionCallbacks.clear();
+        mControllerCallbacks.clear();
+    }
+
+    private void releaseAllSessions() {
+        // ensure ManagerRecord in MediaRouter2ServiceImpl
+        addManagerCallback(new MediaRouter2Manager.Callback());
+
+        for (RoutingSessionInfo session : mManager.getActiveSessions()) {
+            mManager.getControllerForSession(session).release();
+        }
     }
 }
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryPreferenceTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryPreferenceTest.java
new file mode 100644
index 0000000..fa12935
--- /dev/null
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryPreferenceTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.mediaroutertest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.media.RouteDiscoveryPreference;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RouteDiscoveryPreferenceTest {
+    @Before
+    public void setUp() throws Exception { }
+
+    @After
+    public void tearDown() throws Exception { }
+
+    @Test
+    public void testEquality() {
+        List<String> testTypes = new ArrayList<>();
+        testTypes.add("TEST_TYPE_1");
+        testTypes.add("TEST_TYPE_2");
+        RouteDiscoveryPreference request = new RouteDiscoveryPreference.Builder(testTypes, true)
+                .build();
+
+        RouteDiscoveryPreference requestRebuilt = new RouteDiscoveryPreference.Builder(request)
+                .build();
+
+        assertEquals(request, requestRebuilt);
+
+        Parcel parcel = Parcel.obtain();
+        parcel.writeParcelable(request, 0);
+        parcel.setDataPosition(0);
+        RouteDiscoveryPreference requestFromParcel = parcel.readParcelable(null);
+
+        assertEquals(request, requestFromParcel);
+    }
+
+    @Test
+    public void testInequality() {
+        List<String> testTypes = new ArrayList<>();
+        testTypes.add("TEST_TYPE_1");
+        testTypes.add("TEST_TYPE_2");
+
+        List<String> testTypes2 = new ArrayList<>();
+        testTypes.add("TEST_TYPE_3");
+
+        RouteDiscoveryPreference request = new RouteDiscoveryPreference.Builder(testTypes, true)
+                .build();
+
+        RouteDiscoveryPreference requestTypes = new RouteDiscoveryPreference.Builder(request)
+                .setPreferredFeatures(testTypes2)
+                .build();
+        assertNotEquals(request, requestTypes);
+
+        RouteDiscoveryPreference requestActiveScan = new RouteDiscoveryPreference.Builder(request)
+                .setActiveScan(false)
+                .build();
+        assertNotEquals(request, requestActiveScan);
+    }
+}
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java
deleted file mode 100644
index 2e81a64..0000000
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java
+++ /dev/null
@@ -1,62 +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.mediaroutertest;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.media.RouteSessionInfo;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class RouteSessionTest {
-    private static final String TEST_PACKAGE_NAME = "com.android.mediaroutertest";
-    private static final String TEST_CONTROL_CATEGORY = "com.android.mediaroutertest.category";
-
-    private static final String TEST_ROUTE_ID1 = "route_id1";
-
-    @Test
-    public void testValidity() {
-        RouteSessionInfo emptyPackageSession = new RouteSessionInfo.Builder(1,
-                "",
-                TEST_CONTROL_CATEGORY)
-                .addSelectedRoute(TEST_ROUTE_ID1)
-                .build();
-        RouteSessionInfo emptyCategorySession = new RouteSessionInfo.Builder(1,
-                TEST_PACKAGE_NAME, "")
-                .addSelectedRoute(TEST_ROUTE_ID1)
-                .build();
-
-        RouteSessionInfo emptySelectedRouteSession = new RouteSessionInfo.Builder(1,
-                TEST_PACKAGE_NAME, TEST_CONTROL_CATEGORY)
-                .build();
-
-        RouteSessionInfo validSession = new RouteSessionInfo.Builder(emptySelectedRouteSession)
-                .addSelectedRoute(TEST_ROUTE_ID1)
-                .build();
-
-        assertFalse(emptySelectedRouteSession.isValid());
-        assertFalse(emptyPackageSession.isValid());
-        assertFalse(emptyCategorySession.isValid());
-        assertTrue(validSession.isValid());
-    }
-}
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java
new file mode 100644
index 0000000..704dca0
--- /dev/null
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java
@@ -0,0 +1,526 @@
+/*
+ * 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.mediaroutertest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertThrows;
+
+import android.media.RoutingSessionInfo;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests {@link RoutingSessionInfo} and its {@link RoutingSessionInfo.Builder builder}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RoutingSessionInfoTest {
+    public static final String TEST_ID = "test_id";
+    public static final String TEST_CLIENT_PACKAGE_NAME = "com.test.client.package.name";
+    public static final String TEST_ROUTE_FEATURE = "test_route_feature";
+
+    public static final String TEST_ROUTE_ID_0 = "test_route_type_0";
+    public static final String TEST_ROUTE_ID_1 = "test_route_type_1";
+    public static final String TEST_ROUTE_ID_2 = "test_route_type_2";
+    public static final String TEST_ROUTE_ID_3 = "test_route_type_3";
+    public static final String TEST_ROUTE_ID_4 = "test_route_type_4";
+    public static final String TEST_ROUTE_ID_5 = "test_route_type_5";
+    public static final String TEST_ROUTE_ID_6 = "test_route_type_6";
+    public static final String TEST_ROUTE_ID_7 = "test_route_type_7";
+
+    public static final String TEST_KEY = "test_key";
+    public static final String TEST_VALUE = "test_value";
+
+    @Test
+    public void testBuilderConstructorWithInvalidValues() {
+        final String nullId = null;
+        final String nullClientPackageName = null;
+
+        final String emptyId = "";
+        // Note: An empty string as client package name is valid.
+
+        final String validId = TEST_ID;
+        final String validClientPackageName = TEST_CLIENT_PACKAGE_NAME;
+
+        // ID is invalid
+        assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+                nullId, validClientPackageName));
+        assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+                emptyId, validClientPackageName));
+
+        // client package name is invalid (null)
+        assertThrows(NullPointerException.class, () -> new RoutingSessionInfo.Builder(
+                validId, nullClientPackageName));
+
+        // Both are invalid
+        assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+                nullId, nullClientPackageName));
+        assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+                emptyId, nullClientPackageName));
+    }
+
+    @Test
+    public void testBuilderCopyConstructorWithNull() {
+        // Null RouteInfo (1-argument constructor)
+        final RoutingSessionInfo nullRoutingSessionInfo = null;
+        assertThrows(NullPointerException.class,
+                () -> new RoutingSessionInfo.Builder(nullRoutingSessionInfo));
+    }
+
+    @Test
+    public void testBuilderConstructorWithEmptyClientPackageName() {
+        // An empty string for client package name is valid. (for unknown cases)
+        // Creating builder with it should not throw any exception.
+        RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
+                TEST_ID, "" /* clientPackageName*/);
+    }
+
+    @Test
+    public void testBuilderBuildWithEmptySelectedRoutesThrowsIAE() {
+        RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME);
+        // Note: Calling build() without adding any selected routes.
+        assertThrows(IllegalArgumentException.class, () -> builder.build());
+    }
+
+    @Test
+    public void testBuilderAddRouteMethodsWithIllegalArgumentsThrowsIAE() {
+        RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME);
+
+        final String nullRouteId = null;
+        final String emptyRouteId = "";
+
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addSelectedRoute(nullRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addSelectableRoute(nullRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addDeselectableRoute(nullRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addTransferrableRoute(nullRouteId));
+
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addSelectedRoute(emptyRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addSelectableRoute(emptyRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addDeselectableRoute(emptyRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addTransferrableRoute(emptyRouteId));
+    }
+
+    @Test
+    public void testBuilderRemoveRouteMethodsWithIllegalArgumentsThrowsIAE() {
+        RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME);
+
+        final String nullRouteId = null;
+        final String emptyRouteId = "";
+
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.removeSelectedRoute(nullRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.removeSelectableRoute(nullRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.removeDeselectableRoute(nullRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.removeTransferrableRoute(nullRouteId));
+
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.removeSelectedRoute(emptyRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.removeSelectableRoute(emptyRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.removeDeselectableRoute(emptyRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.removeTransferrableRoute(emptyRouteId));
+    }
+
+    @Test
+    public void testBuilderAndGettersOfRoutingSessionInfo() {
+        Bundle controlHints = new Bundle();
+        controlHints.putString(TEST_KEY, TEST_VALUE);
+
+        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .addTransferrableRoute(TEST_ROUTE_ID_6)
+                .addTransferrableRoute(TEST_ROUTE_ID_7)
+                .setControlHints(controlHints)
+                .build();
+
+        assertEquals(TEST_ID, sessionInfo.getId());
+        assertEquals(TEST_CLIENT_PACKAGE_NAME, sessionInfo.getClientPackageName());
+
+        assertEquals(2, sessionInfo.getSelectedRoutes().size());
+        assertEquals(TEST_ROUTE_ID_0, sessionInfo.getSelectedRoutes().get(0));
+        assertEquals(TEST_ROUTE_ID_1, sessionInfo.getSelectedRoutes().get(1));
+
+        assertEquals(2, sessionInfo.getSelectableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_2, sessionInfo.getSelectableRoutes().get(0));
+        assertEquals(TEST_ROUTE_ID_3, sessionInfo.getSelectableRoutes().get(1));
+
+        assertEquals(2, sessionInfo.getDeselectableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_4, sessionInfo.getDeselectableRoutes().get(0));
+        assertEquals(TEST_ROUTE_ID_5, sessionInfo.getDeselectableRoutes().get(1));
+
+        assertEquals(2, sessionInfo.getTransferrableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_6, sessionInfo.getTransferrableRoutes().get(0));
+        assertEquals(TEST_ROUTE_ID_7, sessionInfo.getTransferrableRoutes().get(1));
+
+        Bundle controlHintsOut = sessionInfo.getControlHints();
+        assertNotNull(controlHintsOut);
+        assertTrue(controlHintsOut.containsKey(TEST_KEY));
+        assertEquals(TEST_VALUE, controlHintsOut.getString(TEST_KEY));
+    }
+
+    @Test
+    public void testBuilderAddRouteMethodsWithBuilderCopyConstructor() {
+        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addTransferrableRoute(TEST_ROUTE_ID_6)
+                .build();
+
+        RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .addTransferrableRoute(TEST_ROUTE_ID_7)
+                .build();
+
+        assertEquals(2, newSessionInfo.getSelectedRoutes().size());
+        assertEquals(TEST_ROUTE_ID_0, newSessionInfo.getSelectedRoutes().get(0));
+        assertEquals(TEST_ROUTE_ID_1, newSessionInfo.getSelectedRoutes().get(1));
+
+        assertEquals(2, newSessionInfo.getSelectableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_2, newSessionInfo.getSelectableRoutes().get(0));
+        assertEquals(TEST_ROUTE_ID_3, newSessionInfo.getSelectableRoutes().get(1));
+
+        assertEquals(2, newSessionInfo.getDeselectableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_4, newSessionInfo.getDeselectableRoutes().get(0));
+        assertEquals(TEST_ROUTE_ID_5, newSessionInfo.getDeselectableRoutes().get(1));
+
+        assertEquals(2, newSessionInfo.getTransferrableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_6, newSessionInfo.getTransferrableRoutes().get(0));
+        assertEquals(TEST_ROUTE_ID_7, newSessionInfo.getTransferrableRoutes().get(1));
+    }
+
+    @Test
+    public void testBuilderRemoveRouteMethods() {
+        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .removeSelectedRoute(TEST_ROUTE_ID_1)
+
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .removeSelectableRoute(TEST_ROUTE_ID_3)
+
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .removeDeselectableRoute(TEST_ROUTE_ID_5)
+
+                .addTransferrableRoute(TEST_ROUTE_ID_6)
+                .addTransferrableRoute(TEST_ROUTE_ID_7)
+                .removeTransferrableRoute(TEST_ROUTE_ID_7)
+
+                .build();
+
+        assertEquals(1, sessionInfo.getSelectedRoutes().size());
+        assertEquals(TEST_ROUTE_ID_0, sessionInfo.getSelectedRoutes().get(0));
+
+        assertEquals(1, sessionInfo.getSelectableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_2, sessionInfo.getSelectableRoutes().get(0));
+
+        assertEquals(1, sessionInfo.getDeselectableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_4, sessionInfo.getDeselectableRoutes().get(0));
+
+        assertEquals(1, sessionInfo.getTransferrableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_6, sessionInfo.getTransferrableRoutes().get(0));
+    }
+
+    @Test
+    public void testBuilderRemoveRouteMethodsWithBuilderCopyConstructor() {
+        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .addTransferrableRoute(TEST_ROUTE_ID_6)
+                .addTransferrableRoute(TEST_ROUTE_ID_7)
+                .build();
+
+        RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+                .removeSelectedRoute(TEST_ROUTE_ID_1)
+                .removeSelectableRoute(TEST_ROUTE_ID_3)
+                .removeDeselectableRoute(TEST_ROUTE_ID_5)
+                .removeTransferrableRoute(TEST_ROUTE_ID_7)
+                .build();
+
+        assertEquals(1, newSessionInfo.getSelectedRoutes().size());
+        assertEquals(TEST_ROUTE_ID_0, newSessionInfo.getSelectedRoutes().get(0));
+
+        assertEquals(1, newSessionInfo.getSelectableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_2, newSessionInfo.getSelectableRoutes().get(0));
+
+        assertEquals(1, newSessionInfo.getDeselectableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_4, newSessionInfo.getDeselectableRoutes().get(0));
+
+        assertEquals(1, newSessionInfo.getTransferrableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_6, newSessionInfo.getTransferrableRoutes().get(0));
+    }
+
+    @Test
+    public void testBuilderClearRouteMethods() {
+        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .clearSelectedRoutes()
+
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .clearSelectableRoutes()
+
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .clearDeselectableRoutes()
+
+                .addTransferrableRoute(TEST_ROUTE_ID_6)
+                .addTransferrableRoute(TEST_ROUTE_ID_7)
+                .clearTransferrableRoutes()
+
+                // SelectedRoutes must not be empty
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .build();
+
+        assertEquals(1, sessionInfo.getSelectedRoutes().size());
+        assertEquals(TEST_ROUTE_ID_0, sessionInfo.getSelectedRoutes().get(0));
+
+        assertTrue(sessionInfo.getSelectableRoutes().isEmpty());
+        assertTrue(sessionInfo.getDeselectableRoutes().isEmpty());
+        assertTrue(sessionInfo.getTransferrableRoutes().isEmpty());
+    }
+
+    @Test
+    public void testBuilderClearRouteMethodsWithBuilderCopyConstructor() {
+        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .addTransferrableRoute(TEST_ROUTE_ID_6)
+                .addTransferrableRoute(TEST_ROUTE_ID_7)
+                .build();
+
+        RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+                .clearSelectedRoutes()
+                .clearSelectableRoutes()
+                .clearDeselectableRoutes()
+                .clearTransferrableRoutes()
+                // SelectedRoutes must not be empty
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .build();
+
+        assertEquals(1, newSessionInfo.getSelectedRoutes().size());
+        assertEquals(TEST_ROUTE_ID_0, newSessionInfo.getSelectedRoutes().get(0));
+
+        assertTrue(newSessionInfo.getSelectableRoutes().isEmpty());
+        assertTrue(newSessionInfo.getDeselectableRoutes().isEmpty());
+        assertTrue(newSessionInfo.getTransferrableRoutes().isEmpty());
+    }
+
+    @Test
+    public void testEqualsCreatedWithSameArguments() {
+        Bundle controlHints = new Bundle();
+        controlHints.putString(TEST_KEY, TEST_VALUE);
+
+        RoutingSessionInfo sessionInfo1 = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .addTransferrableRoute(TEST_ROUTE_ID_6)
+                .addTransferrableRoute(TEST_ROUTE_ID_7)
+                .setControlHints(controlHints)
+                .build();
+
+        RoutingSessionInfo sessionInfo2 = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .addTransferrableRoute(TEST_ROUTE_ID_6)
+                .addTransferrableRoute(TEST_ROUTE_ID_7)
+                .setControlHints(controlHints)
+                .build();
+
+        assertEquals(sessionInfo1, sessionInfo2);
+        assertEquals(sessionInfo1.hashCode(), sessionInfo2.hashCode());
+    }
+
+    @Test
+    public void testEqualsCreatedWithBuilderCopyConstructor() {
+        Bundle controlHints = new Bundle();
+        controlHints.putString(TEST_KEY, TEST_VALUE);
+
+        RoutingSessionInfo sessionInfo1 = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .addTransferrableRoute(TEST_ROUTE_ID_6)
+                .addTransferrableRoute(TEST_ROUTE_ID_7)
+                .setControlHints(controlHints)
+                .build();
+
+        RoutingSessionInfo sessionInfo2 = new RoutingSessionInfo.Builder(sessionInfo1).build();
+
+        assertEquals(sessionInfo1, sessionInfo2);
+        assertEquals(sessionInfo1.hashCode(), sessionInfo2.hashCode());
+    }
+
+    @Test
+    public void testEqualsReturnFalse() {
+        Bundle controlHints = new Bundle();
+        controlHints.putString(TEST_KEY, TEST_VALUE);
+
+        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .addTransferrableRoute(TEST_ROUTE_ID_6)
+                .addTransferrableRoute(TEST_ROUTE_ID_7)
+                .setControlHints(controlHints)
+                .build();
+
+        // Now, we will use copy constructor
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .addSelectedRoute("randomRoute")
+                .build());
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .addSelectableRoute("randomRoute")
+                .build());
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .addDeselectableRoute("randomRoute")
+                .build());
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .addTransferrableRoute("randomRoute")
+                .build());
+
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .removeSelectedRoute(TEST_ROUTE_ID_1)
+                .build());
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .removeSelectableRoute(TEST_ROUTE_ID_3)
+                .build());
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .removeDeselectableRoute(TEST_ROUTE_ID_5)
+                .build());
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .removeTransferrableRoute(TEST_ROUTE_ID_7)
+                .build());
+
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .clearSelectedRoutes()
+                // Note: Calling build() with empty selected routes will throw IAE.
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .build());
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .clearSelectableRoutes()
+                .build());
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .clearDeselectableRoutes()
+                .build());
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .clearTransferrableRoutes()
+                .build());
+
+        // Note: ControlHints will not affect the equals.
+    }
+
+    @Test
+    public void testParcelingAndUnParceling() {
+        Bundle controlHints = new Bundle();
+        controlHints.putString(TEST_KEY, TEST_VALUE);
+
+        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .addTransferrableRoute(TEST_ROUTE_ID_6)
+                .addTransferrableRoute(TEST_ROUTE_ID_7)
+                .setControlHints(controlHints)
+                .build();
+
+        Parcel parcel = Parcel.obtain();
+        sessionInfo.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        RoutingSessionInfo sessionInfoFromParcel =
+                RoutingSessionInfo.CREATOR.createFromParcel(parcel);
+        assertEquals(sessionInfo, sessionInfoFromParcel);
+        assertEquals(sessionInfo.hashCode(), sessionInfoFromParcel.hashCode());
+
+        // Check controlHints
+        Bundle controlHintsOut = sessionInfoFromParcel.getControlHints();
+        assertNotNull(controlHintsOut);
+        assertTrue(controlHintsOut.containsKey(TEST_KEY));
+        assertEquals(TEST_VALUE, controlHintsOut.getString(TEST_KEY));
+    }
+}
diff --git a/mime/java-res/android.mime.types b/mime/java-res/android.mime.types
index cb04d92..c1f8b3f 100644
--- a/mime/java-res/android.mime.types
+++ b/mime/java-res/android.mime.types
@@ -46,6 +46,7 @@
 # <extension1> and <extension2> are mapped (or remapped) to <mimeType>.
 
 ?application/epub+zip epub
+?application/lrc lrc
 ?application/pkix-cert cer
 ?application/rss+xml rss
 ?application/sdp sdp
@@ -65,6 +66,7 @@
 ?application/x-mpegurl m3u m3u8
 ?application/x-pem-file pem
 ?application/x-pkcs12 p12 pfx
+?application/x-subrip srt
 ?application/x-webarchive webarchive
 ?application/x-webarchive-xml webarchivexml
 ?application/x-x509-server-cert crt
diff --git a/mms/OWNERS b/mms/OWNERS
index ba00d5d..befc320 100644
--- a/mms/OWNERS
+++ b/mms/OWNERS
@@ -12,3 +12,5 @@
 shuoq@google.com
 refuhoo@google.com
 nazaninb@google.com
+sarahchin@google.com
+dbright@google.com
\ No newline at end of file
diff --git a/mms/java/android/telephony/MmsManager.java b/mms/java/android/telephony/MmsManager.java
index 6554267..cf55eba 100644
--- a/mms/java/android/telephony/MmsManager.java
+++ b/mms/java/android/telephony/MmsManager.java
@@ -16,8 +16,12 @@
 
 package android.telephony;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemService;
 import android.app.ActivityThread;
 import android.app.PendingIntent;
+import android.content.Context;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.RemoteException;
@@ -27,22 +31,18 @@
 
 /**
  * Manages MMS operations such as sending multimedia messages.
- * Get this object by calling the static method {@link #getInstance()}.
- * @hide
+ * Get this object by calling Context#getSystemService(Context#MMS_SERVICE).
  */
+@SystemService(Context.MMS_SERVICE)
 public class MmsManager {
     private static final String TAG = "MmsManager";
-
-    /** Singleton object constructed during class initialization. */
-    private static final MmsManager sInstance = new MmsManager();
+    private final Context mContext;
 
     /**
-     * Get the MmsManager singleton instance.
-     *
-     * @return the {@link MmsManager} singleton instance.
+     * @hide
      */
-    public static MmsManager getInstance() {
-        return sInstance;
+    public MmsManager(@NonNull Context context) {
+        mContext = context;
     }
 
     /**
@@ -56,8 +56,9 @@
      * @param sentIntent if not NULL this <code>PendingIntent</code> is broadcast when the message
      *                   is successfully sent, or failed
      */
-    public void sendMultimediaMessage(int subId, Uri contentUri, String locationUrl,
-            Bundle configOverrides, PendingIntent sentIntent) {
+    public void sendMultimediaMessage(int subId, @NonNull Uri contentUri,
+            @Nullable String locationUrl, @Nullable Bundle configOverrides,
+            @Nullable PendingIntent sentIntent) {
         try {
             final IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
             if (iMms == null) {
@@ -84,8 +85,9 @@
      *  broadcast when the message is downloaded, or the download is failed
      * @throws IllegalArgumentException if locationUrl or contentUri is empty
      */
-    public void downloadMultimediaMessage(int subId, String locationUrl, Uri contentUri,
-            Bundle configOverrides, PendingIntent downloadedIntent) {
+    public void downloadMultimediaMessage(int subId, @NonNull String locationUrl,
+            @NonNull Uri contentUri, @Nullable Bundle configOverrides,
+            @Nullable PendingIntent downloadedIntent) {
         try {
             final IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
             if (iMms == null) {
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 9d93c9b..0c6f507 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -47,7 +47,6 @@
         "sensor.cpp",
         "sharedmem.cpp",
         "storage_manager.cpp",
-        "surface_texture.cpp",
         "surface_control.cpp",
         "system_fonts.cpp",
         "trace.cpp",
@@ -70,6 +69,8 @@
         "libnetd_client",
         "libhwui",
         "libxml2",
+        "libEGL",
+        "libGLESv2",
         "android.hardware.configstore@1.0",
         "android.hardware.configstore-utils",
     ],
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 203adfc..97b861b 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -240,6 +240,7 @@
     ASurfaceTransaction_setColor; # introduced=29
     ASurfaceTransaction_setDamageRegion; # introduced=29
     ASurfaceTransaction_setDesiredPresentTime; # introduced=29
+    ASurfaceTransaction_setFrameRate; # introduced=30
     ASurfaceTransaction_setGeometry; # introduced=29
     ASurfaceTransaction_setHdrMetadata_cta861_3; # introduced=29
     ASurfaceTransaction_setHdrMetadata_smpte2086; # introduced=29
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index b34b31a..392c9f6 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -545,3 +545,18 @@
 
     transaction->setBackgroundColor(surfaceControl, color, alpha, static_cast<ui::Dataspace>(dataspace));
 }
+
+void ASurfaceTransaction_setFrameRate(ASurfaceTransaction* aSurfaceTransaction,
+                                      ASurfaceControl* aSurfaceControl, float frameRate) {
+    CHECK_NOT_NULL(aSurfaceTransaction);
+    CHECK_NOT_NULL(aSurfaceControl);
+
+    sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+    if (frameRate < 0) {
+        ALOGE("Failed to set frame ate - invalid frame rate");
+        return;
+    }
+
+    Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+    transaction->setFrameRate(surfaceControl, frameRate);
+}
diff --git a/native/android/surface_texture.cpp b/native/android/surface_texture.cpp
deleted file mode 100644
index 3049ec1..0000000
--- a/native/android/surface_texture.cpp
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.
- */
-
-#include <android/surface_texture.h>
-#include <android/surface_texture_jni.h>
-
-#define LOG_TAG "ASurfaceTexture"
-
-#include <utils/Log.h>
-
-#include <gui/Surface.h>
-
-#include <gui/surfacetexture/surface_texture_platform.h>
-
-#include <android_runtime/android_graphics_SurfaceTexture.h>
-
-using namespace android;
-
-ASurfaceTexture* ASurfaceTexture_fromSurfaceTexture(JNIEnv* env, jobject surfacetexture) {
-    if (!surfacetexture || !android_SurfaceTexture_isInstanceOf(env, surfacetexture)) {
-        return nullptr;
-    }
-    auto consumer = SurfaceTexture_getSurfaceTexture(env, surfacetexture);
-    auto producer = SurfaceTexture_getProducer(env, surfacetexture);
-    return ASurfaceTexture_create(consumer, producer);
-}
-
-ANativeWindow* ASurfaceTexture_acquireANativeWindow(ASurfaceTexture* st) {
-    sp<Surface> surface = new Surface(st->producer);
-    ANativeWindow* win(surface.get());
-    ANativeWindow_acquire(win);
-    return win;
-}
-
-void ASurfaceTexture_release(ASurfaceTexture* st) {
-    delete st;
-}
-
-int ASurfaceTexture_attachToGLContext(ASurfaceTexture* st, uint32_t tex) {
-    return st->consumer->attachToContext(tex);
-}
-
-int ASurfaceTexture_detachFromGLContext(ASurfaceTexture* st) {
-    return st->consumer->detachFromContext();
-}
-
-int ASurfaceTexture_updateTexImage(ASurfaceTexture* st) {
-    return st->consumer->updateTexImage();
-}
-
-void ASurfaceTexture_getTransformMatrix(ASurfaceTexture* st, float mtx[16]) {
-    st->consumer->getTransformMatrix(mtx);
-}
-
-int64_t ASurfaceTexture_getTimestamp(ASurfaceTexture* st) {
-    return st->consumer->getTimestamp();
-}
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 942eafd..376ea77 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -24,12 +24,21 @@
 
     // our source files
     //
-    srcs: ["bitmap.cpp"],
+    srcs: [
+        "aassetstreamadaptor.cpp",
+        "bitmap.cpp",
+        "imagedecoder.cpp",
+    ],
 
     shared_libs: [
+        "libandroid",
         "libandroid_runtime",
+        "libhwui",
+        "liblog",
     ],
 
+    static_libs: ["libarect"],
+
     arch: {
         arm: {
             // TODO: This is to work around b/24465209. Remove after root cause is fixed
diff --git a/native/graphics/jni/aassetstreamadaptor.cpp b/native/graphics/jni/aassetstreamadaptor.cpp
new file mode 100644
index 0000000..016008b
--- /dev/null
+++ b/native/graphics/jni/aassetstreamadaptor.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright 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.
+ */
+
+#include "aassetstreamadaptor.h"
+
+#include <log/log.h>
+
+AAssetStreamAdaptor::AAssetStreamAdaptor(AAsset* asset)
+    : mAAsset(asset)
+{
+}
+
+bool AAssetStreamAdaptor::rewind() {
+    off64_t pos = AAsset_seek64(mAAsset, 0, SEEK_SET);
+    if (pos == (off64_t)-1) {
+        ALOGE("rewind failed!");
+        return false;
+    }
+    return true;
+}
+
+size_t AAssetStreamAdaptor::getLength() const {
+    return AAsset_getLength64(mAAsset);
+}
+
+bool AAssetStreamAdaptor::isAtEnd() const {
+    return AAsset_getRemainingLength64(mAAsset) == 0;
+}
+
+SkStreamRewindable* AAssetStreamAdaptor::onDuplicate() const {
+    // Cannot sensibly create a duplicate, since each AAssetStreamAdaptor
+    // would be modifying the same AAsset.
+    //return new AAssetStreamAdaptor(mAAsset);
+    return nullptr;
+}
+
+bool AAssetStreamAdaptor::hasPosition() const {
+    return AAsset_seek64(mAAsset, 0, SEEK_CUR) != -1;
+}
+
+size_t AAssetStreamAdaptor::getPosition() const {
+    const off64_t offset = AAsset_seek64(mAAsset, 0, SEEK_CUR);
+    if (offset == -1) {
+        ALOGE("getPosition failed!");
+        return 0;
+    }
+
+    return offset;
+}
+
+bool AAssetStreamAdaptor::seek(size_t position) {
+    if (AAsset_seek64(mAAsset, position, SEEK_SET) == -1) {
+        ALOGE("seek failed!");
+        return false;
+    }
+
+    return true;
+}
+
+bool AAssetStreamAdaptor::move(long offset) {
+    if (AAsset_seek64(mAAsset, offset, SEEK_CUR) == -1) {
+        ALOGE("move failed!");
+        return false;
+    }
+
+    return true;
+}
+
+size_t AAssetStreamAdaptor::read(void* buffer, size_t size) {
+    ssize_t amount;
+
+    if (!buffer) {
+        if (!size) {
+            return 0;
+        }
+
+        // asset->seek returns new total offset
+        // we want to return amount that was skipped
+        const off64_t oldOffset = AAsset_seek64(mAAsset, 0, SEEK_CUR);
+        if (oldOffset == -1) {
+            ALOGE("seek(oldOffset) failed!");
+            return 0;
+        }
+
+        const off64_t newOffset = AAsset_seek64(mAAsset, size, SEEK_CUR);
+        if (-1 == newOffset) {
+            ALOGE("seek(%zu) failed!", size);
+            return 0;
+        }
+        amount = newOffset - oldOffset;
+    } else {
+        amount = AAsset_read(mAAsset, buffer, size);
+    }
+
+    if (amount < 0) {
+        amount = 0;
+    }
+    return amount;
+}
+
+const void* AAssetStreamAdaptor::getMemoryBase() {
+    return AAsset_getBuffer(mAAsset);
+}
+
diff --git a/native/graphics/jni/aassetstreamadaptor.h b/native/graphics/jni/aassetstreamadaptor.h
new file mode 100644
index 0000000..c1fddb0
--- /dev/null
+++ b/native/graphics/jni/aassetstreamadaptor.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright 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.
+ */
+
+#pragma once
+
+#include "SkStream.h"
+
+#include <android/asset_manager.h>
+
+// Like AssetStreamAdaptor, but operates on AAsset, a public NDK API.
+class AAssetStreamAdaptor : public SkStreamRewindable {
+public:
+    /**
+     * Create an SkStream that reads from an AAsset.
+     *
+     * The AAsset must remain open for the lifetime of the Adaptor. The Adaptor
+     * does *not* close the AAsset.
+     */
+    explicit AAssetStreamAdaptor(AAsset*);
+
+    bool rewind() override;
+    size_t read(void* buffer, size_t size) override;
+    bool hasLength() const override { return true; }
+    size_t getLength() const override;
+    bool hasPosition() const override;
+    size_t getPosition() const override;
+    bool seek(size_t position) override;
+    bool move(long offset) override;
+    bool isAtEnd() const override;
+    const void* getMemoryBase() override;
+protected:
+    SkStreamRewindable* onDuplicate() const override;
+
+private:
+    AAsset* mAAsset;
+};
+
diff --git a/native/graphics/jni/bitmap.cpp b/native/graphics/jni/bitmap.cpp
index 1aebeaf..b8eb543 100644
--- a/native/graphics/jni/bitmap.cpp
+++ b/native/graphics/jni/bitmap.cpp
@@ -15,7 +15,9 @@
  */
 
 #include <android/bitmap.h>
+#include <android/data_space.h>
 #include <android/graphics/bitmap.h>
+#include <android/data_space.h>
 
 int AndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap,
                           AndroidBitmapInfo* info) {
@@ -29,6 +31,19 @@
     return ANDROID_BITMAP_RESULT_SUCCESS;
 }
 
+int32_t AndroidBitmap_getDataSpace(JNIEnv* env, jobject jbitmap) {
+    if (NULL == env || NULL == jbitmap) {
+        return ADATASPACE_UNKNOWN;
+    }
+
+    android::graphics::Bitmap bitmap(env, jbitmap);
+    if (!bitmap.isValid()) {
+        return ADATASPACE_UNKNOWN;
+    }
+
+    return bitmap.getDataSpace();
+}
+
 int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr) {
     if (NULL == env || NULL == jbitmap) {
         return ANDROID_BITMAP_RESULT_BAD_PARAMETER;
@@ -64,3 +79,35 @@
     ABitmap_releaseRef(bitmap.get());
     return ANDROID_BITMAP_RESULT_SUCCESS;
 }
+
+int AndroidBitmap_getHardwareBuffer(JNIEnv* env, jobject jbitmap, AHardwareBuffer** outBuffer) {
+    if (NULL == env || NULL == jbitmap || NULL == outBuffer) {
+        return ANDROID_BITMAP_RESULT_BAD_PARAMETER;
+    }
+
+    android::graphics::Bitmap bitmap(env, jbitmap);
+
+    if (!bitmap.isValid()) {
+        return ANDROID_BITMAP_RESULT_JNI_EXCEPTION;
+    }
+
+    *outBuffer = bitmap.getHardwareBuffer();
+    return *outBuffer == NULL ? ANDROID_BITMAP_RESULT_BAD_PARAMETER : ANDROID_BITMAP_RESULT_SUCCESS;
+}
+
+int AndroidBitmap_compress(const AndroidBitmapInfo* info,
+                           int32_t dataSpace,
+                           const void* pixels,
+                           int32_t format, int32_t quality,
+                           void* userContext,
+                           AndroidBitmap_CompressWriteFunc fn) {
+    if (NULL == info || NULL == pixels || NULL == fn) {
+        return ANDROID_BITMAP_RESULT_BAD_PARAMETER;
+    }
+    if (quality < 0 || quality > 100) {
+        return ANDROID_BITMAP_RESULT_BAD_PARAMETER;
+    }
+
+    return ABitmap_compress(info, (ADataSpace) dataSpace, pixels,
+            (AndroidBitmapCompressFormat) format, quality, userContext, fn);
+}
diff --git a/native/graphics/jni/imagedecoder.cpp b/native/graphics/jni/imagedecoder.cpp
new file mode 100644
index 0000000..79bcc15
--- /dev/null
+++ b/native/graphics/jni/imagedecoder.cpp
@@ -0,0 +1,341 @@
+/*
+ * Copyright 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.
+ */
+
+#include "aassetstreamadaptor.h"
+
+#include <android/asset_manager.h>
+#include <android/bitmap.h>
+#include <android/data_space.h>
+#include <android/imagedecoder.h>
+#include <android/graphics/MimeType.h>
+#include <android/rect.h>
+#include <hwui/ImageDecoder.h>
+#include <log/log.h>
+#include <SkAndroidCodec.h>
+#include <utils/Color.h>
+
+#include <fcntl.h>
+#include <optional>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+using namespace android;
+
+int ResultToErrorCode(SkCodec::Result result) {
+    switch (result) {
+        case SkCodec::kIncompleteInput:
+            return ANDROID_IMAGE_DECODER_INCOMPLETE;
+        case SkCodec::kErrorInInput:
+            return ANDROID_IMAGE_DECODER_ERROR;
+        case SkCodec::kInvalidInput:
+            return ANDROID_IMAGE_DECODER_INVALID_INPUT;
+        case SkCodec::kCouldNotRewind:
+            return ANDROID_IMAGE_DECODER_SEEK_ERROR;
+        case SkCodec::kUnimplemented:
+            return ANDROID_IMAGE_DECODER_UNSUPPORTED_FORMAT;
+        case SkCodec::kInvalidConversion:
+            return ANDROID_IMAGE_DECODER_INVALID_CONVERSION;
+        case SkCodec::kInvalidParameters:
+            return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+        case SkCodec::kSuccess:
+            return ANDROID_IMAGE_DECODER_SUCCESS;
+        case SkCodec::kInvalidScale:
+            return ANDROID_IMAGE_DECODER_INVALID_SCALE;
+        case SkCodec::kInternalError:
+            return ANDROID_IMAGE_DECODER_INTERNAL_ERROR;
+    }
+}
+
+static int createFromStream(std::unique_ptr<SkStreamRewindable> stream, AImageDecoder** outDecoder) {
+    SkCodec::Result result;
+    auto codec = SkCodec::MakeFromStream(std::move(stream), &result, nullptr,
+                                         SkCodec::SelectionPolicy::kPreferAnimation);
+    auto androidCodec = SkAndroidCodec::MakeFromCodec(std::move(codec),
+            SkAndroidCodec::ExifOrientationBehavior::kRespect);
+    if (!androidCodec) {
+        return ResultToErrorCode(result);
+    }
+
+    *outDecoder = reinterpret_cast<AImageDecoder*>(new ImageDecoder(std::move(androidCodec)));
+    return ANDROID_IMAGE_DECODER_SUCCESS;
+}
+
+int AImageDecoder_createFromAAsset(AAsset* asset, AImageDecoder** outDecoder) {
+    if (!asset || !outDecoder) {
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+    *outDecoder = nullptr;
+
+    auto stream = std::make_unique<AAssetStreamAdaptor>(asset);
+    return createFromStream(std::move(stream), outDecoder);
+}
+
+static bool isSeekable(int descriptor) {
+    return ::lseek64(descriptor, 0, SEEK_CUR) != -1;
+}
+
+int AImageDecoder_createFromFd(int fd, AImageDecoder** outDecoder) {
+    if (fd <= 0 || !outDecoder) {
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+
+    struct stat fdStat;
+    if (fstat(fd, &fdStat) == -1) {
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+
+    if (!isSeekable(fd)) {
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+
+    // SkFILEStream will close its descriptor. Duplicate it so the client will
+    // still be responsible for closing the original.
+    int dupDescriptor = fcntl(fd, F_DUPFD_CLOEXEC, 0);
+    FILE* file = fdopen(dupDescriptor, "r");
+    if (!file) {
+        close(dupDescriptor);
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+
+    auto stream = std::unique_ptr<SkStreamRewindable>(new SkFILEStream(file));
+    return createFromStream(std::move(stream), outDecoder);
+}
+
+int AImageDecoder_createFromBuffer(const void* buffer, size_t length,
+                                   AImageDecoder** outDecoder) {
+    if (!buffer || !length  || !outDecoder) {
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+    *outDecoder = nullptr;
+
+    // The client is expected to keep the buffer alive as long as the
+    // AImageDecoder, so we do not need to copy the buffer.
+    auto stream = std::unique_ptr<SkStreamRewindable>(
+            new SkMemoryStream(buffer, length, false /* copyData */));
+    return createFromStream(std::move(stream), outDecoder);
+}
+
+static ImageDecoder* toDecoder(AImageDecoder* d) {
+    return reinterpret_cast<ImageDecoder*>(d);
+}
+
+static const ImageDecoder* toDecoder(const AImageDecoder* d) {
+    return reinterpret_cast<const ImageDecoder*>(d);
+}
+
+// Note: This differs from the version in android_bitmap.cpp in that this
+// version returns kGray_8_SkColorType for ANDROID_BITMAP_FORMAT_A_8. SkCodec
+// allows decoding single channel images to gray, which Android then treats
+// as A_8/ALPHA_8.
+static SkColorType getColorType(AndroidBitmapFormat format) {
+    switch (format) {
+        case ANDROID_BITMAP_FORMAT_RGBA_8888:
+            return kN32_SkColorType;
+        case ANDROID_BITMAP_FORMAT_RGB_565:
+            return kRGB_565_SkColorType;
+        case ANDROID_BITMAP_FORMAT_RGBA_4444:
+            return kARGB_4444_SkColorType;
+        case ANDROID_BITMAP_FORMAT_A_8:
+            return kGray_8_SkColorType;
+        case ANDROID_BITMAP_FORMAT_RGBA_F16:
+            return kRGBA_F16_SkColorType;
+        default:
+            return kUnknown_SkColorType;
+    }
+}
+
+int AImageDecoder_setAndroidBitmapFormat(AImageDecoder* decoder, int32_t format) {
+    if (!decoder || format < ANDROID_BITMAP_FORMAT_NONE
+            || format > ANDROID_BITMAP_FORMAT_RGBA_F16) {
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+    return toDecoder(decoder)->setOutColorType(getColorType((AndroidBitmapFormat) format))
+            ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_CONVERSION;
+}
+
+int AImageDecoder_setDataSpace(AImageDecoder* decoder, int32_t dataspace) {
+    sk_sp<SkColorSpace> cs = uirenderer::DataSpaceToColorSpace((android_dataspace)dataspace);
+    // 0 is ADATASPACE_UNKNOWN. We need an explicit request for an ADataSpace.
+    if (!decoder || !dataspace || !cs) {
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+
+    ImageDecoder* imageDecoder = toDecoder(decoder);
+    imageDecoder->setOutColorSpace(std::move(cs));
+    return ANDROID_IMAGE_DECODER_SUCCESS;
+}
+
+const AImageDecoderHeaderInfo* AImageDecoder_getHeaderInfo(const AImageDecoder* decoder) {
+    return reinterpret_cast<const AImageDecoderHeaderInfo*>(decoder);
+}
+
+static const ImageDecoder* toDecoder(const AImageDecoderHeaderInfo* info) {
+    return reinterpret_cast<const ImageDecoder*>(info);
+}
+
+int32_t AImageDecoderHeaderInfo_getWidth(const AImageDecoderHeaderInfo* info) {
+    if (!info) {
+        return 0;
+    }
+    return toDecoder(info)->mCodec->getInfo().width();
+}
+
+int32_t AImageDecoderHeaderInfo_getHeight(const AImageDecoderHeaderInfo* info) {
+    if (!info) {
+        return 0;
+    }
+    return toDecoder(info)->mCodec->getInfo().height();
+}
+
+const char* AImageDecoderHeaderInfo_getMimeType(const AImageDecoderHeaderInfo* info) {
+    if (!info) {
+        return nullptr;
+    }
+    return getMimeType(toDecoder(info)->mCodec->getEncodedFormat());
+}
+
+int32_t AImageDecoderHeaderInfo_getDataSpace(const AImageDecoderHeaderInfo* info) {
+    if (!info) {
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+
+    // Note: This recomputes the data space because it's possible the client has
+    // changed the output color space, so we cannot rely on it. Alternatively,
+    // we could store the ADataSpace in the ImageDecoder.
+    const ImageDecoder* imageDecoder = toDecoder(info);
+    SkColorType colorType = imageDecoder->mCodec->computeOutputColorType(kN32_SkColorType);
+    sk_sp<SkColorSpace> colorSpace = imageDecoder->mCodec->computeOutputColorSpace(colorType);
+    return uirenderer::ColorSpaceToADataSpace(colorSpace.get(), colorType);
+}
+
+// FIXME: Share with getFormat in android_bitmap.cpp?
+static AndroidBitmapFormat getFormat(SkColorType colorType) {
+    switch (colorType) {
+        case kN32_SkColorType:
+            return ANDROID_BITMAP_FORMAT_RGBA_8888;
+        case kRGB_565_SkColorType:
+            return ANDROID_BITMAP_FORMAT_RGB_565;
+        case kARGB_4444_SkColorType:
+            return ANDROID_BITMAP_FORMAT_RGBA_4444;
+        case kAlpha_8_SkColorType:
+            return ANDROID_BITMAP_FORMAT_A_8;
+        case kRGBA_F16_SkColorType:
+            return ANDROID_BITMAP_FORMAT_RGBA_F16;
+        default:
+            return ANDROID_BITMAP_FORMAT_NONE;
+    }
+}
+
+int32_t AImageDecoderHeaderInfo_getAndroidBitmapFormat(const AImageDecoderHeaderInfo* info) {
+    if (!info) {
+        return ANDROID_BITMAP_FORMAT_NONE;
+    }
+    return getFormat(toDecoder(info)->mCodec->computeOutputColorType(kN32_SkColorType));
+}
+
+int AImageDecoderHeaderInfo_getAlphaFlags(const AImageDecoderHeaderInfo* info) {
+    if (!info) {
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+    switch (toDecoder(info)->mCodec->getInfo().alphaType()) {
+        case kUnknown_SkAlphaType:
+            LOG_ALWAYS_FATAL("Invalid alpha type");
+            return ANDROID_IMAGE_DECODER_INTERNAL_ERROR;
+        case kUnpremul_SkAlphaType:
+            // fall through. premul is the default.
+        case kPremul_SkAlphaType:
+            return ANDROID_BITMAP_FLAGS_ALPHA_PREMUL;
+        case kOpaque_SkAlphaType:
+            return ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE;
+    }
+}
+
+int AImageDecoder_setUnpremultipliedRequired(AImageDecoder* decoder, bool required) {
+    if (!decoder) {
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+
+    return toDecoder(decoder)->setUnpremultipliedRequired(required)
+            ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_CONVERSION;
+}
+
+int AImageDecoder_setTargetSize(AImageDecoder* decoder, int32_t width, int32_t height) {
+    if (!decoder) {
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+
+    return toDecoder(decoder)->setTargetSize(width, height)
+            ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_SCALE;
+}
+
+int AImageDecoder_computeSampledSize(const AImageDecoder* decoder, int sampleSize,
+                                     int32_t* width, int32_t* height) {
+    if (!decoder || !width || !height || sampleSize < 1) {
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+
+    SkISize size = toDecoder(decoder)->mCodec->getSampledDimensions(sampleSize);
+    *width = size.width();
+    *height = size.height();
+    return ANDROID_IMAGE_DECODER_SUCCESS;
+}
+
+int AImageDecoder_setCrop(AImageDecoder* decoder, ARect crop) {
+    if (!decoder) {
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+
+    SkIRect cropIRect;
+    cropIRect.setLTRB(crop.left, crop.top, crop.right, crop.bottom);
+    SkIRect* cropPtr = cropIRect == SkIRect::MakeEmpty() ? nullptr : &cropIRect;
+    return toDecoder(decoder)->setCropRect(cropPtr)
+            ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+}
+
+
+size_t AImageDecoder_getMinimumStride(AImageDecoder* decoder) {
+    if (!decoder) {
+        return 0;
+    }
+
+    SkImageInfo info = toDecoder(decoder)->getOutputInfo();
+    return info.minRowBytes();
+}
+
+int AImageDecoder_decodeImage(AImageDecoder* decoder,
+                              void* pixels, size_t stride,
+                              size_t size) {
+    if (!decoder || !pixels || !stride) {
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+
+    ImageDecoder* imageDecoder = toDecoder(decoder);
+
+    const int height = imageDecoder->getOutputInfo().height();
+    const size_t minStride = AImageDecoder_getMinimumStride(decoder);
+    // If this calculation were to overflow, it would have been caught in
+    // setTargetSize.
+    if (stride < minStride || size < stride * (height - 1) + minStride) {
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+
+    return ResultToErrorCode(imageDecoder->decode(pixels, stride));
+}
+
+void AImageDecoder_delete(AImageDecoder* decoder) {
+    delete toDecoder(decoder);
+}
diff --git a/native/graphics/jni/libjnigraphics.map.txt b/native/graphics/jni/libjnigraphics.map.txt
index a601d8a..01c1477 100644
--- a/native/graphics/jni/libjnigraphics.map.txt
+++ b/native/graphics/jni/libjnigraphics.map.txt
@@ -1,8 +1,30 @@
 LIBJNIGRAPHICS {
   global:
+    AImageDecoder_createFromAAsset; # introduced=30
+    AImageDecoder_createFromFd; # introduced=30
+    AImageDecoder_createFromBuffer; # introduced=30
+    AImageDecoder_delete; # introduced=30
+    AImageDecoder_setAndroidBitmapFormat; # introduced=30
+    AImageDecoder_setUnpremultipliedRequired; # introduced=30
+    AImageDecoder_setDataSpace; # introduced=30
+    AImageDecoder_getHeaderInfo; # introduced=30
+    AImageDecoder_getMinimumStride; # introduced=30
+    AImageDecoder_decodeImage; # introduced=30
+    AImageDecoder_setTargetSize; # introduced=30
+    AImageDecoder_computeSampledSize; # introduced=30
+    AImageDecoder_setCrop; # introduced=30
+    AImageDecoderHeaderInfo_getWidth; # introduced=30
+    AImageDecoderHeaderInfo_getHeight; # introduced=30
+    AImageDecoderHeaderInfo_getMimeType; # introduced=30
+    AImageDecoderHeaderInfo_getAlphaFlags; # introduced=30
+    AImageDecoderHeaderInfo_getAndroidBitmapFormat; # introduced=30
+    AImageDecoderHeaderInfo_getDataSpace; # introduced=30
     AndroidBitmap_getInfo;
+    AndroidBitmap_getDataSpace;
     AndroidBitmap_lockPixels;
     AndroidBitmap_unlockPixels;
+    AndroidBitmap_compress; # introduced=30
+    AndroidBitmap_getHardwareBuffer; #introduced=30
   local:
     *;
 };
diff --git a/opengl/java/android/opengl/EGL14.java b/opengl/java/android/opengl/EGL14.java
index 728e6e1..90b46fd 100644
--- a/opengl/java/android/opengl/EGL14.java
+++ b/opengl/java/android/opengl/EGL14.java
@@ -18,11 +18,11 @@
 
 package android.opengl;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.SurfaceTexture;
 import android.view.Surface;
-import android.view.SurfaceView;
 import android.view.SurfaceHolder;
+import android.view.SurfaceView;
 
 /**
  * EGL 1.4
diff --git a/opengl/java/android/opengl/GLES20.java b/opengl/java/android/opengl/GLES20.java
index d66e7ac..e853e44 100644
--- a/opengl/java/android/opengl/GLES20.java
+++ b/opengl/java/android/opengl/GLES20.java
@@ -19,7 +19,7 @@
 
 package android.opengl;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /** OpenGL ES 2.0
  */
diff --git a/opengl/java/android/opengl/GLSurfaceView.java b/opengl/java/android/opengl/GLSurfaceView.java
index 8a3e6a0..75131b0 100644
--- a/opengl/java/android/opengl/GLSurfaceView.java
+++ b/opengl/java/android/opengl/GLSurfaceView.java
@@ -16,7 +16,7 @@
 
 package android.opengl;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.Trace;
 import android.util.AttributeSet;
diff --git a/opengl/java/com/google/android/gles_jni/EGLImpl.java b/opengl/java/com/google/android/gles_jni/EGLImpl.java
index f94f69f..b4ea0a6 100644
--- a/opengl/java/com/google/android/gles_jni/EGLImpl.java
+++ b/opengl/java/com/google/android/gles_jni/EGLImpl.java
@@ -16,13 +16,12 @@
 
 package com.google.android.gles_jni;
 
+import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.SurfaceTexture;
 import android.view.Surface;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
 import javax.microedition.khronos.egl.EGL10;
 import javax.microedition.khronos.egl.EGLConfig;
 import javax.microedition.khronos.egl.EGLContext;
diff --git a/opengl/java/com/google/android/gles_jni/GLImpl.java b/opengl/java/com/google/android/gles_jni/GLImpl.java
index 2a8d07f..3c808a6 100644
--- a/opengl/java/com/google/android/gles_jni/GLImpl.java
+++ b/opengl/java/com/google/android/gles_jni/GLImpl.java
@@ -20,14 +20,13 @@
 package com.google.android.gles_jni;
 
 import android.app.AppGlobals;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.os.Build;
 import android.os.UserHandle;
 import android.util.Log;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
 import java.nio.Buffer;
 
 import javax.microedition.khronos.opengles.GL10;
diff --git a/opengl/java/javax/microedition/khronos/egl/EGL10.java b/opengl/java/javax/microedition/khronos/egl/EGL10.java
index 8a25170..ea571c7 100644
--- a/opengl/java/javax/microedition/khronos/egl/EGL10.java
+++ b/opengl/java/javax/microedition/khronos/egl/EGL10.java
@@ -16,8 +16,7 @@
 
 package javax.microedition.khronos.egl;
 
-import android.annotation.UnsupportedAppUsage;
-import java.lang.String;
+import android.compat.annotation.UnsupportedAppUsage;
 
 public interface EGL10 extends EGL {
     int EGL_SUCCESS                     = 0x3000;
diff --git a/packages/CarSystemUI/res/layout/super_notification_shade.xml b/packages/CarSystemUI/res/layout/super_notification_shade.xml
new file mode 100644
index 0000000..cb65045
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/super_notification_shade.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** 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.
+*/
+-->
+
+<!-- This is the notification shade window. -->
+<com.android.systemui.statusbar.phone.NotificationShadeWindowView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:sysui="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true">
+
+    <com.android.systemui.statusbar.BackDropView
+        android:id="@+id/backdrop"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="gone"
+        sysui:ignoreRightInset="true"
+    >
+        <ImageView android:id="@+id/backdrop_back"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:scaleType="centerCrop"/>
+        <ImageView android:id="@+id/backdrop_front"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:scaleType="centerCrop"
+            android:visibility="invisible"/>
+    </com.android.systemui.statusbar.BackDropView>
+
+    <com.android.systemui.statusbar.ScrimView
+        android:id="@+id/scrim_for_bubble"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:importantForAccessibility="no"
+        sysui:ignoreRightInset="true"
+    />
+
+    <com.android.systemui.statusbar.ScrimView
+        android:id="@+id/scrim_behind"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:importantForAccessibility="no"
+        sysui:ignoreRightInset="true"
+    />
+
+    <include layout="@layout/brightness_mirror"/>
+
+    <ViewStub android:id="@+id/fullscreen_user_switcher_stub"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout="@layout/car_fullscreen_user_switcher"/>
+
+    <include layout="@layout/notification_center_activity"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginBottom="@dimen/navigation_bar_height"
+        android:visibility="invisible"/>
+
+    <include layout="@layout/headsup_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="invisible"/>
+
+    <include layout="@layout/status_bar_expanded"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="invisible"/>
+
+    <com.android.systemui.statusbar.ScrimView
+        android:id="@+id/scrim_in_front"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:importantForAccessibility="no"
+        sysui:ignoreRightInset="true"
+    />
+
+</com.android.systemui.statusbar.phone.NotificationShadeWindowView>
diff --git a/packages/CarSystemUI/res/layout/super_status_bar.xml b/packages/CarSystemUI/res/layout/super_status_bar.xml
index 37cd1d4..d93f62f 100644
--- a/packages/CarSystemUI/res/layout/super_status_bar.xml
+++ b/packages/CarSystemUI/res/layout/super_status_bar.xml
@@ -1,21 +1,23 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2018 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
+**
+** 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.
+*/
+-->
 
-<!-- This is the combined status bar / notification panel window. -->
+<!-- This is the status bar window. -->
 <com.android.systemui.statusbar.phone.StatusBarWindowView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:sysui="http://schemas.android.com/apk/res-auto"
@@ -23,43 +25,9 @@
     android:layout_height="match_parent"
     android:fitsSystemWindows="true">
 
-    <com.android.systemui.statusbar.BackDropView
-        android:id="@+id/backdrop"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:visibility="gone"
-        sysui:ignoreRightInset="true"
-    >
-        <ImageView android:id="@+id/backdrop_back"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:scaleType="centerCrop"/>
-        <ImageView android:id="@+id/backdrop_front"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:scaleType="centerCrop"
-            android:visibility="invisible"/>
-    </com.android.systemui.statusbar.BackDropView>
-
-    <com.android.systemui.statusbar.ScrimView
-        android:id="@+id/scrim_for_bubble"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:importantForAccessibility="no"
-        sysui:ignoreRightInset="true"
-    />
-
-    <com.android.systemui.statusbar.ScrimView
-        android:id="@+id/scrim_behind"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:importantForAccessibility="no"
-        sysui:ignoreRightInset="true"
-    />
-
     <LinearLayout
         android:layout_width="match_parent"
-        android:layout_height="@dimen/status_bar_height"
+        android:layout_height="wrap_content"
         android:orientation="vertical"
     >
         <FrameLayout
@@ -75,35 +43,4 @@
             android:layout_height="wrap_content"/>
     </LinearLayout>
 
-    <include layout="@layout/brightness_mirror"/>
-
-    <ViewStub android:id="@+id/fullscreen_user_switcher_stub"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout="@layout/car_fullscreen_user_switcher"/>
-
-    <include layout="@layout/notification_center_activity"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_marginBottom="@dimen/navigation_bar_height"
-        android:visibility="invisible"/>
-
-    <include layout="@layout/headsup_container"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:visibility="invisible"/>
-
-    <ViewStub android:id="@+id/status_bar_expanded"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:visibility="invisible"/>
-
-    <com.android.systemui.statusbar.ScrimView
-        android:id="@+id/scrim_in_front"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:importantForAccessibility="no"
-        sysui:ignoreRightInset="true"
-    />
-
 </com.android.systemui.statusbar.phone.StatusBarWindowView>
diff --git a/packages/CarSystemUI/res/layout/sysui_primary_window.xml b/packages/CarSystemUI/res/layout/sysui_primary_window.xml
new file mode 100644
index 0000000..cc36e87
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/sysui_primary_window.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.
+  -->
+
+<!-- Fullscreen views in sysui should be listed here in increasing Z order. -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:background="@android:color/transparent"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <ViewStub android:id="@+id/fullscreen_user_switcher_stub"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:layout="@layout/car_fullscreen_user_switcher"/>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/CarSystemUI/res/xml/overlayable.xml b/packages/CarSystemUI/res/xml/overlayable.xml
new file mode 100644
index 0000000..2b6e66e
--- /dev/null
+++ b/packages/CarSystemUI/res/xml/overlayable.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<resources>
+    <overlayable name="SystemBarsLayouts">
+        <policy type="product|signature">
+            <item type="layout" name="car_navigation_bar" />
+        </policy>
+    </overlayable>
+</resources>
\ No newline at end of file
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
index f7802d2..cfe1c70 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
@@ -19,14 +19,19 @@
 import android.service.notification.StatusBarNotification;
 
 import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.logging.NotifLog;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.util.leak.LeakDetector;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
+import dagger.Lazy;
+
 /**
  * Car specific notification entry manager that does nothing when adding a notification.
  *
@@ -42,8 +47,12 @@
             NotificationGroupManager groupManager,
             NotificationRankingManager rankingManager,
             KeyguardEnvironment keyguardEnvironment,
-            FeatureFlags featureFlags) {
-        super(notifLog, groupManager, rankingManager, keyguardEnvironment, featureFlags);
+            FeatureFlags featureFlags,
+            Lazy<NotificationRowBinder> notificationRowBinderLazy,
+            Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
+            LeakDetector leakDetector) {
+        super(notifLog, groupManager, rankingManager, keyguardEnvironment, featureFlags,
+                notificationRowBinderLazy, notificationRemoteInputManagerLazy, leakDetector);
     }
 
     @Override
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/SystemUIPrimaryWindowController.java b/packages/CarSystemUI/src/com/android/systemui/car/SystemUIPrimaryWindowController.java
new file mode 100644
index 0000000..3f55ac8
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/SystemUIPrimaryWindowController.java
@@ -0,0 +1,153 @@
+/*
+ * 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.car;
+
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.os.Binder;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Controls the expansion state of the primary window which will contain all of the fullscreen sysui
+ * behavior. This window still has a collapsed state in order to watch for swipe events to expand
+ * this window for the notification panel.
+ */
+@Singleton
+public class SystemUIPrimaryWindowController implements
+        ConfigurationController.ConfigurationListener {
+
+    private final Context mContext;
+    private final Resources mResources;
+    private final WindowManager mWindowManager;
+
+    private final int mStatusBarHeight;
+    private final int mNavBarHeight;
+    private final int mDisplayHeight;
+    private ViewGroup mBaseLayout;
+    private WindowManager.LayoutParams mLp;
+    private WindowManager.LayoutParams mLpChanged;
+    private boolean mIsAttached = false;
+
+    @Inject
+    public SystemUIPrimaryWindowController(
+            Context context,
+            @Main Resources resources,
+            WindowManager windowManager,
+            ConfigurationController configurationController
+    ) {
+        mContext = context;
+        mResources = resources;
+        mWindowManager = windowManager;
+
+        Point display = new Point();
+        mWindowManager.getDefaultDisplay().getSize(display);
+        mDisplayHeight = display.y;
+
+        mStatusBarHeight = mResources.getDimensionPixelSize(
+                com.android.internal.R.dimen.status_bar_height);
+        mNavBarHeight = mResources.getDimensionPixelSize(R.dimen.navigation_bar_height);
+
+        mLpChanged = new WindowManager.LayoutParams();
+        mBaseLayout = (ViewGroup) LayoutInflater.from(context)
+                .inflate(R.layout.sysui_primary_window, /* root= */ null, false);
+
+        configurationController.addCallback(this);
+    }
+
+    /** Returns the base view of the primary window. */
+    public ViewGroup getBaseLayout() {
+        return mBaseLayout;
+    }
+
+    /** Returns {@code true} if the window is already attached. */
+    public boolean isAttached() {
+        return mIsAttached;
+    }
+
+    /** Attaches the window to the window manager. */
+    public void attach() {
+        if (mIsAttached) {
+            return;
+        }
+        mIsAttached = true;
+        // Now that the status bar window encompasses the sliding panel and its
+        // translucent backdrop, the entire thing is made TRANSLUCENT and is
+        // hardware-accelerated.
+        mLp = new WindowManager.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                mStatusBarHeight,
+                WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL,
+                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+                        | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
+                PixelFormat.TRANSLUCENT);
+        mLp.token = new Binder();
+        mLp.gravity = Gravity.TOP;
+        mLp.setFitInsetsTypes(/* types= */ 0);
+        mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+        mLp.setTitle("SystemUIPrimaryWindow");
+        mLp.packageName = mContext.getPackageName();
+        mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+        mWindowManager.addView(mBaseLayout, mLp);
+        mLpChanged.copyFrom(mLp);
+    }
+
+    /** Sets the window to the expanded state. */
+    public void setWindowExpanded(boolean expanded) {
+        if (expanded) {
+            // TODO: Update this so that the windowing type gets the full height of the display
+            //  when we use MATCH_PARENT.
+            mLpChanged.height = mDisplayHeight + mNavBarHeight;
+            mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+        } else {
+            mLpChanged.height = mStatusBarHeight;
+            // TODO: Allow touches to go through to the status bar to handle notification panel.
+            mLpChanged.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+        }
+        updateWindow();
+    }
+
+    /** Returns {@code true} if the window is expanded */
+    public boolean isWindowExpanded() {
+        return mLp.height != mStatusBarHeight;
+    }
+
+    private void updateWindow() {
+        if (mLp != null && mLp.copyFrom(mLpChanged) != 0) {
+            if (isAttached()) {
+                mWindowManager.updateViewLayout(mBaseLayout, mLp);
+            }
+        }
+    }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
index d8c9d17..3a52015 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
@@ -296,7 +296,7 @@
             leftlp.windowAnimations = 0;
             leftlp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
             leftlp.gravity = Gravity.LEFT;
-            leftlp.setFitWindowInsetsTypes(0 /* types */);
+            leftlp.setFitInsetsTypes(0 /* types */);
             mWindowManager.addView(mLeftNavigationBarWindow, leftlp);
         }
         if (mRightNavigationBarWindow != null) {
@@ -314,7 +314,7 @@
             rightlp.windowAnimations = 0;
             rightlp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
             rightlp.gravity = Gravity.RIGHT;
-            rightlp.setFitWindowInsetsTypes(0 /* types */);
+            rightlp.setFitInsetsTypes(0 /* types */);
             mWindowManager.addView(mRightNavigationBarWindow, rightlp);
         }
     }
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarShadeControllerImpl.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarShadeControllerImpl.java
index d1d352a..755ed25 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarShadeControllerImpl.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarShadeControllerImpl.java
@@ -24,10 +24,10 @@
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.ShadeControllerImpl;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.StatusBarWindowController;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -41,13 +41,13 @@
     @Inject
     public CarShadeControllerImpl(CommandQueue commandQueue,
             StatusBarStateController statusBarStateController,
-            StatusBarWindowController statusBarWindowController,
+            NotificationShadeWindowController notificationShadeWindowController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             WindowManager windowManager,
             Lazy<StatusBar> statusBarLazy,
             Lazy<AssistManager> assistManagerLazy,
             Lazy<BubbleController> bubbleControllerLazy) {
-        super(commandQueue, statusBarStateController, statusBarWindowController,
+        super(commandQueue, statusBarStateController, notificationShadeWindowController,
                 statusBarKeyguardViewManager, windowManager,
                 statusBarLazy, assistManagerLazy, bubbleControllerLazy);
     }
@@ -61,14 +61,14 @@
             return;
         }
 
-        mStatusBarWindowController.setStatusBarFocusable(false);
-        getCarStatusBar().getStatusBarWindowViewController().cancelExpandHelper();
+        mNotificationShadeWindowController.setNotificationShadeFocusable(false);
+        getCarStatusBar().getNotificationShadeWindowViewController().cancelExpandHelper();
         getStatusBarView().collapsePanel(true /* animate */, delayed, speedUpFactor);
 
         getCarStatusBar().animateNotificationPanel(getCarStatusBar().getClosingVelocity(), true);
 
         if (!getCarStatusBar().isTracking()) {
-            mStatusBarWindowController.setPanelVisible(false);
+            mNotificationShadeWindowController.setPanelVisible(false);
             getCarNotificationView().setVisibility(View.INVISIBLE);
         }
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 77db54c..76e9ec6 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.car;
 
-import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
 
 import android.animation.Animator;
@@ -68,6 +67,7 @@
 import com.android.systemui.car.CarDeviceProvisionedController;
 import com.android.systemui.car.CarDeviceProvisionedListener;
 import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.car.SystemUIPrimaryWindowController;
 import com.android.systemui.classifier.FalsingLog;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -107,7 +107,8 @@
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.phone.AutoHideController;
@@ -125,14 +126,14 @@
 import com.android.systemui.statusbar.phone.LockscreenWallpaper;
 import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.phone.StatusBarComponent;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
-import com.android.systemui.statusbar.phone.StatusBarWindowController;
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -168,6 +169,7 @@
     // acceleration rate for the fling animation
     private static final float FLING_SPEED_UP_FACTOR = 0.6f;
 
+    private final UserSwitcherController mUserSwitcherController;
     private final ScrimController mScrimController;
     private final LockscreenLockIconController mLockscreenLockIconController;
 
@@ -177,17 +179,16 @@
     private float mBackgroundAlphaDiff;
     private float mInitialBackgroundAlpha;
 
-    private final Lazy<FullscreenUserSwitcher> mFullscreenUserSwitcherLazy;
-    private FullscreenUserSwitcher mFullscreenUserSwitcher;
-
     private CarBatteryController mCarBatteryController;
     private BatteryMeterView mBatteryMeterView;
     private Drawable mNotificationPanelBackground;
 
     private final Object mQueueLock = new Object();
+    private final SystemUIPrimaryWindowController mSystemUIPrimaryWindowController;
     private final CarNavigationBarController mCarNavigationBarController;
     private final FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
     private final Lazy<PowerManagerHelper> mPowerManagerHelperLazy;
+    private final FullscreenUserSwitcher mFullscreenUserSwitcher;
     private final ShadeController mShadeController;
     private final CarServiceProvider mCarServiceProvider;
     private final CarDeviceProvisionedController mCarDeviceProvisionedController;
@@ -268,8 +269,7 @@
             HeadsUpManagerPhone headsUpManagerPhone,
             DynamicPrivacyController dynamicPrivacyController,
             BypassHeadsUpNotifier bypassHeadsUpNotifier,
-            @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowNotificationLongPress,
-            Lazy<NewNotifPipeline> newNotifPipeline,
+            Lazy<NotifPipelineInitializer> notifPipelineInitializer,
             FalsingManager falsingManager,
             BroadcastDispatcher broadcastDispatcher,
             RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
@@ -303,7 +303,7 @@
             Lazy<AssistManager> assistManagerLazy,
             NotificationListener notificationListener,
             ConfigurationController configurationController,
-            StatusBarWindowController statusBarWindowController,
+            NotificationShadeWindowController notificationShadeWindowController,
             LockscreenLockIconController lockscreenLockIconController,
             DozeParameters dozeParameters,
             ScrimController scrimController,
@@ -334,11 +334,13 @@
             KeyguardDismissUtil keyguardDismissUtil,
             ExtensionController extensionController,
             UserInfoControllerImpl userInfoControllerImpl,
+            NotificationRowBinderImpl notificationRowBinder,
             DismissCallbackRegistry dismissCallbackRegistry,
             /* Car Settings injected components. */
             CarServiceProvider carServiceProvider,
             Lazy<PowerManagerHelper> powerManagerHelperLazy,
-            Lazy<FullscreenUserSwitcher> fullscreenUserSwitcherLazy,
+            FullscreenUserSwitcher fullscreenUserSwitcher,
+            SystemUIPrimaryWindowController systemUIPrimaryWindowController,
             CarNavigationBarController carNavigationBarController,
             FlingAnimationUtils.Builder flingAnimationUtilsBuilder) {
         super(
@@ -355,8 +357,7 @@
                 headsUpManagerPhone,
                 dynamicPrivacyController,
                 bypassHeadsUpNotifier,
-                allowNotificationLongPress,
-                newNotifPipeline,
+                notifPipelineInitializer,
                 falsingManager,
                 broadcastDispatcher,
                 remoteInputQuickSettingsDisabler,
@@ -390,7 +391,7 @@
                 assistManagerLazy,
                 notificationListener,
                 configurationController,
-                statusBarWindowController,
+                notificationShadeWindowController,
                 lockscreenLockIconController,
                 dozeParameters,
                 scrimController,
@@ -421,7 +422,9 @@
                 keyguardDismissUtil,
                 extensionController,
                 userInfoControllerImpl,
+                notificationRowBinder,
                 dismissCallbackRegistry);
+        mUserSwitcherController = userSwitcherController;
         mScrimController = scrimController;
         mLockscreenLockIconController = lockscreenLockIconController;
         mCarDeviceProvisionedController =
@@ -429,7 +432,8 @@
         mShadeController = shadeController;
         mCarServiceProvider = carServiceProvider;
         mPowerManagerHelperLazy = powerManagerHelperLazy;
-        mFullscreenUserSwitcherLazy = fullscreenUserSwitcherLazy;
+        mFullscreenUserSwitcher = fullscreenUserSwitcher;
+        mSystemUIPrimaryWindowController = systemUIPrimaryWindowController;
         mCarNavigationBarController = carNavigationBarController;
         mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder;
     }
@@ -444,6 +448,13 @@
         mScreenLifecycle = Dependency.get(ScreenLifecycle.class);
         mScreenLifecycle.addObserver(mScreenObserver);
 
+        // TODO: Remove the setup of user switcher from Car Status Bar.
+        mSystemUIPrimaryWindowController.attach();
+        mFullscreenUserSwitcher.setStatusBar(this);
+        mFullscreenUserSwitcher.setContainer(
+                mSystemUIPrimaryWindowController.getBaseLayout().findViewById(
+                        R.id.fullscreen_user_switcher_stub));
+
         // Notification bar related setup.
         mInitialBackgroundAlpha = (float) mContext.getResources().getInteger(
                 R.integer.config_initialNotificationBackgroundAlpha) / 100;
@@ -465,7 +476,7 @@
 
         super.start();
 
-        mNotificationPanel.setScrollingEnabled(true);
+        mNotificationPanelViewController.setScrollingEnabled(true);
         mSettleOpenPercentage = mContext.getResources().getInteger(
                 R.integer.notification_settle_open_percentage);
         mSettleClosePercentage = mContext.getResources().getInteger(
@@ -510,16 +521,6 @@
                 });
     }
 
-    /**
-     * Allows for showing or hiding just the navigation bars. This is indented to be used when
-     * the full screen user selector is shown.
-     */
-    void setNavBarVisibility(@View.Visibility int visibility) {
-        mCarNavigationBarController.setBottomWindowVisibility(visibility);
-        mCarNavigationBarController.setLeftWindowVisibility(visibility);
-        mCarNavigationBarController.setRightWindowVisibility(visibility);
-    }
-
     @Override
     public boolean hideKeyguard() {
         boolean result = super.hideKeyguard();
@@ -544,7 +545,7 @@
         mNotificationPanelBackground = getDefaultWallpaper();
         mScrimController.setScrimBehindDrawable(mNotificationPanelBackground);
 
-        FragmentHostManager manager = FragmentHostManager.get(mStatusBarWindow);
+        FragmentHostManager manager = FragmentHostManager.get(mPhoneStatusBarWindow);
         manager.addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
             mBatteryMeterView = fragment.getView().findViewById(R.id.battery);
 
@@ -645,9 +646,9 @@
         carNotificationListener.registerAsSystemService(mContext, mCarUxRestrictionManagerWrapper,
                 carHeadsUpNotificationManager, mNotificationDataManager);
 
-        mNotificationView = mStatusBarWindow.findViewById(R.id.notification_view);
-        View glassPane = mStatusBarWindow.findViewById(R.id.glass_pane);
-        mHandleBar = mStatusBarWindow.findViewById(R.id.handle_bar);
+        final View glassPane = mNotificationShadeWindowView.findViewById(R.id.glass_pane);
+        mNotificationView = mNotificationShadeWindowView.findViewById(R.id.notification_view);
+        mHandleBar = mNotificationShadeWindowView.findViewById(R.id.handle_bar);
         mNotificationView.setClickHandlerFactory(mNotificationClickHandlerFactory);
         mNotificationView.setNotificationDataManager(mNotificationDataManager);
 
@@ -778,7 +779,7 @@
         }
         // scroll to top
         mNotificationList.scrollToPosition(0);
-        mStatusBarWindowController.setPanelVisible(true);
+        mNotificationShadeWindowController.setPanelVisible(true);
         mNotificationView.setVisibility(View.VISIBLE);
         animateNotificationPanel(mOpeningVelocity, false);
 
@@ -862,7 +863,7 @@
                 mOpeningVelocity = DEFAULT_FLING_VELOCITY;
                 mClosingVelocity = DEFAULT_FLING_VELOCITY;
                 if (isClosing) {
-                    mStatusBarWindowController.setPanelVisible(false);
+                    mNotificationShadeWindowController.setPanelVisible(false);
                     mNotificationView.setVisibility(View.INVISIBLE);
                     mNotificationView.setClipBounds(null);
                     mNotificationViewController.onVisibilityChanged(false);
@@ -924,9 +925,6 @@
                     + " scroll " + mStackScroller.getScrollX()
                     + "," + mStackScroller.getScrollY());
         }
-
-        pw.print("  mFullscreenUserSwitcher=");
-        pw.println(mFullscreenUserSwitcher);
         pw.print("  mCarBatteryController=");
         pw.println(mCarBatteryController);
         pw.print("  mBatteryMeterView=");
@@ -972,14 +970,7 @@
 
     @Override
     protected void createUserSwitcher() {
-        UserSwitcherController userSwitcherController =
-                Dependency.get(UserSwitcherController.class);
-        if (userSwitcherController.useFullscreenUserSwitcher()) {
-            mFullscreenUserSwitcher = mFullscreenUserSwitcherLazy.get();
-            mFullscreenUserSwitcher.setStatusBar(this);
-            mFullscreenUserSwitcher.setContainer(
-                    mStatusBarWindow.findViewById(R.id.fullscreen_user_switcher_stub));
-        } else {
+        if (!mUserSwitcherController.useFullscreenUserSwitcher()) {
             super.createUserSwitcher();
         }
     }
@@ -996,25 +987,12 @@
         super.onStateChanged(newState);
 
         if (newState != StatusBarState.FULLSCREEN_USER_SWITCHER) {
-            hideUserSwitcher();
+            mFullscreenUserSwitcher.hide();
         } else {
             dismissKeyguardWhenUserSwitcherNotDisplayed();
         }
     }
 
-    /** Makes the full screen user switcher visible, if applicable. */
-    public void showUserSwitcher() {
-        if (mFullscreenUserSwitcher != null && mState == StatusBarState.FULLSCREEN_USER_SWITCHER) {
-            mFullscreenUserSwitcher.show(); // Makes the switcher visible.
-        }
-    }
-
-    private void hideUserSwitcher() {
-        if (mFullscreenUserSwitcher != null) {
-            mFullscreenUserSwitcher.hide();
-        }
-    }
-
     final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
         @Override
         public void onScreenTurnedOn() {
@@ -1024,7 +1002,7 @@
 
     // We automatically dismiss keyguard unless user switcher is being shown on the keyguard.
     private void dismissKeyguardWhenUserSwitcherNotDisplayed() {
-        if (mFullscreenUserSwitcher == null) {
+        if (!mUserSwitcherController.useFullscreenUserSwitcher()) {
             return; // Not using the full screen user switcher.
         }
 
@@ -1150,7 +1128,7 @@
                 // when the on-scroll is called for the first time to open.
                 mNotificationList.scrollToPosition(0);
             }
-            mStatusBarWindowController.setPanelVisible(true);
+            mNotificationShadeWindowController.setPanelVisible(true);
             mNotificationView.setVisibility(View.VISIBLE);
 
             // clips the view for the notification shade when the user scrolls to open.
@@ -1316,7 +1294,7 @@
         @Override
         protected View createHeadsUpPanel() {
             // In SystemUi the view is already in the window so just return a reference.
-            return mStatusBarWindow.findViewById(R.id.notification_headsup);
+            return mNotificationShadeWindowView.findViewById(R.id.notification_headsup);
         }
 
         @Override
@@ -1342,7 +1320,7 @@
 
             super.setHeadsUpVisible();
             if (mHeadsUpPanel.getVisibility() == View.VISIBLE) {
-                mStatusBarWindowController.setHeadsUpShowing(true);
+                mNotificationShadeWindowController.setHeadsUpShowing(true);
                 mStatusBarWindowController.setForceStatusBarVisible(true);
             }
         }
@@ -1352,7 +1330,7 @@
             super.removeNotificationFromPanel(currentHeadsUpNotification);
             // If the panel ended up empty and hidden we can remove it from SystemUi
             if (mHeadsUpPanel.getVisibility() != View.VISIBLE) {
-                mStatusBarWindowController.setHeadsUpShowing(false);
+                mNotificationShadeWindowController.setHeadsUpShowing(false);
                 mStatusBarWindowController.setForceStatusBarVisible(false);
             }
         }
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarKeyguardViewManager.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarKeyguardViewManager.java
index 0ad0992..59f9f94 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarKeyguardViewManager.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarKeyguardViewManager.java
@@ -24,11 +24,12 @@
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.R;
 import com.android.systemui.dock.DockManager;
+import com.android.systemui.navigationbar.car.CarNavigationBarController;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.phone.NavigationModeController;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.StatusBarWindowController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -40,6 +41,8 @@
 public class CarStatusBarKeyguardViewManager extends StatusBarKeyguardViewManager {
 
     protected boolean mShouldHideNavBar;
+    private final CarNavigationBarController mCarNavigationBarController;
+    private final FullscreenUserSwitcher mFullscreenUserSwitcher;
 
     @Inject
     public CarStatusBarKeyguardViewManager(Context context,
@@ -50,15 +53,19 @@
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             NavigationModeController navigationModeController,
             DockManager dockManager,
-            StatusBarWindowController statusBarWindowController,
+            NotificationShadeWindowController notificationShadeWindowController,
             KeyguardStateController keyguardStateController,
-            NotificationMediaManager notificationMediaManager) {
+            NotificationMediaManager notificationMediaManager,
+            CarNavigationBarController carNavigationBarController,
+            FullscreenUserSwitcher fullscreenUserSwitcher) {
         super(context, callback, lockPatternUtils, sysuiStatusBarStateController,
                 configurationController, keyguardUpdateMonitor, navigationModeController,
-                dockManager, statusBarWindowController, keyguardStateController,
+                dockManager, notificationShadeWindowController, keyguardStateController,
                 notificationMediaManager);
         mShouldHideNavBar = context.getResources()
                 .getBoolean(R.bool.config_hideNavWhenKeyguardBouncerShown);
+        mCarNavigationBarController = carNavigationBarController;
+        mFullscreenUserSwitcher = fullscreenUserSwitcher;
     }
 
     @Override
@@ -66,8 +73,10 @@
         if (!mShouldHideNavBar) {
             return;
         }
-        CarStatusBar statusBar = (CarStatusBar) mStatusBar;
-        statusBar.setNavBarVisibility(navBarVisible ? View.VISIBLE : View.GONE);
+        int visibility = navBarVisible ? View.VISIBLE : View.GONE;
+        mCarNavigationBarController.setBottomWindowVisibility(visibility);
+        mCarNavigationBarController.setLeftWindowVisibility(visibility);
+        mCarNavigationBarController.setRightWindowVisibility(visibility);
     }
 
     /**
@@ -86,8 +95,7 @@
      */
     @Override
     public void onCancelClicked() {
-        CarStatusBar statusBar = (CarStatusBar) mStatusBar;
-        statusBar.showUserSwitcher();
+        mFullscreenUserSwitcher.show();
     }
 
     /**
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
index 1ebaef7..45da822 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.car;
 
-import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
 
 import android.content.Context;
@@ -32,6 +31,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.car.SystemUIPrimaryWindowController;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
@@ -67,7 +67,8 @@
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.phone.AutoHideController;
@@ -84,13 +85,13 @@
 import com.android.systemui.statusbar.phone.LockscreenWallpaper;
 import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.StatusBarComponent;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
-import com.android.systemui.statusbar.phone.StatusBarWindowController;
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -138,8 +139,7 @@
             HeadsUpManagerPhone headsUpManagerPhone,
             DynamicPrivacyController dynamicPrivacyController,
             BypassHeadsUpNotifier bypassHeadsUpNotifier,
-            @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowNotificationLongPress,
-            Lazy<NewNotifPipeline> newNotifPipeline,
+            Lazy<NotifPipelineInitializer> notifPipelineInitializer,
             FalsingManager falsingManager,
             BroadcastDispatcher broadcastDispatcher,
             RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
@@ -173,7 +173,7 @@
             Lazy<AssistManager> assistManagerLazy,
             NotificationListener notificationListener,
             ConfigurationController configurationController,
-            StatusBarWindowController statusBarWindowController,
+            NotificationShadeWindowController notificationShadeWindowController,
             LockscreenLockIconController lockscreenLockIconController,
             DozeParameters dozeParameters,
             ScrimController scrimController,
@@ -204,10 +204,12 @@
             KeyguardDismissUtil keyguardDismissUtil,
             ExtensionController extensionController,
             UserInfoControllerImpl userInfoControllerImpl,
+            NotificationRowBinderImpl notificationRowBinder,
             DismissCallbackRegistry dismissCallbackRegistry,
             CarServiceProvider carServiceProvider,
             Lazy<PowerManagerHelper> powerManagerHelperLazy,
-            Lazy<FullscreenUserSwitcher> fullscreenUserSwitcherLazy,
+            FullscreenUserSwitcher fullscreenUserSwitcher,
+            SystemUIPrimaryWindowController systemUIPrimaryWindowController,
             CarNavigationBarController carNavigationBarController,
             FlingAnimationUtils.Builder flingAnimationUtilsBuilder) {
         return new CarStatusBar(
@@ -224,8 +226,7 @@
                 headsUpManagerPhone,
                 dynamicPrivacyController,
                 bypassHeadsUpNotifier,
-                allowNotificationLongPress,
-                newNotifPipeline,
+                notifPipelineInitializer,
                 falsingManager,
                 broadcastDispatcher,
                 remoteInputQuickSettingsDisabler,
@@ -259,7 +260,7 @@
                 assistManagerLazy,
                 notificationListener,
                 configurationController,
-                statusBarWindowController,
+                notificationShadeWindowController,
                 lockscreenLockIconController,
                 dozeParameters,
                 scrimController,
@@ -289,10 +290,12 @@
                 keyguardDismissUtil,
                 extensionController,
                 userInfoControllerImpl,
+                notificationRowBinder,
                 dismissCallbackRegistry,
                 carServiceProvider,
                 powerManagerHelperLazy,
-                fullscreenUserSwitcherLazy,
+                fullscreenUserSwitcher,
+                systemUIPrimaryWindowController,
                 carNavigationBarController,
                 flingAnimationUtilsBuilder);
     }
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarTrustAgentUnlockDialogHelper.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarTrustAgentUnlockDialogHelper.java
index b2f8aad..07dbd66 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarTrustAgentUnlockDialogHelper.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarTrustAgentUnlockDialogHelper.java
@@ -249,7 +249,7 @@
                         | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION,
                 PixelFormat.TRANSLUCENT
         );
-        attrs.setFitWindowInsetsTypes(0 /* types */);
+        attrs.setFitInsetsTypes(0 /* types */);
         return attrs;
     }
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
index f8fc3bb..3cd66c2 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
@@ -38,6 +38,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.R;
 import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.car.SystemUIPrimaryWindowController;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.car.CarTrustAgentUnlockDialogHelper.OnHideListener;
 import com.android.systemui.statusbar.car.UserGridRecyclerView.UserRecord;
@@ -56,9 +57,10 @@
     private final UserManager mUserManager;
     private final CarServiceProvider mCarServiceProvider;
     private final CarTrustAgentUnlockDialogHelper mUnlockDialogHelper;
+    private final SystemUIPrimaryWindowController mSystemUIPrimaryWindowController;
+    private CarStatusBar mCarStatusBar;
     private final int mShortAnimDuration;
 
-    private CarStatusBar mStatusBar;
     private View mParent;
     private UserGridRecyclerView mUserGridView;
     private CarTrustAgentEnrollmentManager mEnrollmentManager;
@@ -81,23 +83,35 @@
             @Main Resources resources,
             UserManager userManager,
             CarServiceProvider carServiceProvider,
-            CarTrustAgentUnlockDialogHelper carTrustAgentUnlockDialogHelper) {
+            CarTrustAgentUnlockDialogHelper carTrustAgentUnlockDialogHelper,
+            SystemUIPrimaryWindowController systemUIPrimaryWindowController) {
         mContext = context;
         mResources = resources;
         mUserManager = userManager;
         mCarServiceProvider = carServiceProvider;
         mUnlockDialogHelper = carTrustAgentUnlockDialogHelper;
+        mSystemUIPrimaryWindowController = systemUIPrimaryWindowController;
 
         mShortAnimDuration = mResources.getInteger(android.R.integer.config_shortAnimTime);
     }
 
-    /** Sets the status bar which controls the keyguard. */
+    /** Sets the status bar which gives an entry point to dismiss the keyguard. */
+    // TODO: Remove this in favor of a keyguard controller.
     public void setStatusBar(CarStatusBar statusBar) {
-        mStatusBar = statusBar;
+        mCarStatusBar = statusBar;
+    }
+
+    /** Returns {@code true} if the user switcher already has a parent view. */
+    public boolean isAttached() {
+        return mParent != null;
     }
 
     /** Sets the {@link ViewStub} to show the user switcher. */
     public void setContainer(ViewStub containerStub) {
+        if (isAttached()) {
+            return;
+        }
+
         mParent = containerStub.inflate();
 
         View container = mParent.findViewById(R.id.container);
@@ -148,20 +162,31 @@
      * Makes user grid visible.
      */
     public void show() {
+        if (!isAttached()) {
+            return;
+        }
         mParent.setVisibility(View.VISIBLE);
+        mSystemUIPrimaryWindowController.setWindowExpanded(true);
     }
 
     /**
      * Hides the user grid.
      */
     public void hide() {
+        if (!isAttached()) {
+            return;
+        }
         mParent.setVisibility(View.INVISIBLE);
+        mSystemUIPrimaryWindowController.setWindowExpanded(false);
     }
 
     /**
      * @return {@code true} if user grid is visible, {@code false} otherwise.
      */
     public boolean isVisible() {
+        if (!isAttached()) {
+            return false;
+        }
         return mParent.getVisibility() == View.VISIBLE;
     }
 
@@ -196,7 +221,7 @@
         }
         if (mSelectedUser.mType == UserRecord.FOREGROUND_USER) {
             hide();
-            mStatusBar.dismissKeyguard();
+            mCarStatusBar.dismissKeyguard();
             return;
         }
         // Switching is about to happen, since it takes time, fade out the switcher gradually.
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
index cdabeeb..7dd3be4 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
@@ -20,6 +20,7 @@
 import static android.content.DialogInterface.BUTTON_POSITIVE;
 import static android.os.UserManager.DISALLOW_ADD_USER;
 import static android.os.UserManager.SWITCHABILITY_STATUS_OK;
+import static android.view.WindowInsets.Type.statusBars;
 
 import android.annotation.IntDef;
 import android.annotation.Nullable;
@@ -43,6 +44,9 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.WindowManager;
 import android.widget.ImageView;
 import android.widget.TextView;
 
@@ -53,7 +57,6 @@
 
 import com.android.internal.util.UserIcons;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -337,7 +340,7 @@
                     .setPositiveButton(android.R.string.ok, null)
                     .create();
             // Sets window flags for the SysUI dialog
-            SystemUIDialog.applyFlags(maxUsersDialog);
+            applyCarSysUIDialogFlags(maxUsersDialog);
             maxUsersDialog.show();
         }
 
@@ -356,10 +359,19 @@
                     .setOnCancelListener(this)
                     .create();
             // Sets window flags for the SysUI dialog
-            SystemUIDialog.applyFlags(addUserDialog);
+            applyCarSysUIDialogFlags(addUserDialog);
             addUserDialog.show();
         }
 
+        private void applyCarSysUIDialogFlags(AlertDialog dialog) {
+            final Window window = dialog.getWindow();
+            window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+            window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
+                    | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+            window.getAttributes().setFitInsetsTypes(
+                    window.getAttributes().getFitInsetsTypes() & ~statusBars());
+        }
+
         private void notifyUserSelected(UserRecord userRecord) {
             // Notify the listener which user was selected
             if (mUserSelectionListener != null) {
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
index 642dc82..5054281 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
@@ -36,6 +36,7 @@
 import android.os.Bundle;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -48,8 +49,6 @@
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
-import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.TrafficStatsConstants;
 
@@ -203,11 +202,11 @@
     }
 
     private URL getUrlForCaptivePortal() {
-        String url = getIntent().getStringExtra(TelephonyIntents.EXTRA_REDIRECTION_URL_KEY);
+        String url = getIntent().getStringExtra(TelephonyManager.EXTRA_REDIRECTION_URL);
         if (TextUtils.isEmpty(url)) url = mCm.getCaptivePortalServerUrl();
         final CarrierConfigManager configManager = getApplicationContext()
                 .getSystemService(CarrierConfigManager.class);
-        final int subId = getIntent().getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+        final int subId = getIntent().getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
                 SubscriptionManager.getDefaultVoiceSubscriptionId());
         final String[] portalURLs = configManager.getConfigForSubId(subId).getStringArray(
                 CarrierConfigManager.KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY);
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
index 2697a10..cb062a6 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
@@ -30,8 +30,6 @@
 import android.text.TextUtils;
 import android.util.Log;
 
-import com.android.internal.telephony.PhoneConstants;
-
 /**
  * This util class provides common logic for carrier actions
  */
@@ -103,7 +101,7 @@
     }
 
     private static void onDisableAllMeteredApns(Intent intent, Context context) {
-        int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+        int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
                 SubscriptionManager.getDefaultVoiceSubscriptionId());
         logd("onDisableAllMeteredApns subId: " + subId);
         final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
@@ -111,7 +109,7 @@
     }
 
     private static void onEnableAllMeteredApns(Intent intent, Context context) {
-        int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+        int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
                 SubscriptionManager.getDefaultVoiceSubscriptionId());
         logd("onEnableAllMeteredApns subId: " + subId);
         final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
@@ -135,7 +133,7 @@
     }
 
     private static void onRegisterDefaultNetworkAvail(Intent intent, Context context) {
-        int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+        int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
                 SubscriptionManager.getDefaultVoiceSubscriptionId());
         logd("onRegisterDefaultNetworkAvail subId: " + subId);
         final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
@@ -143,7 +141,7 @@
     }
 
     private static void onDeregisterDefaultNetworkAvail(Intent intent, Context context) {
-        int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+        int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
                 SubscriptionManager.getDefaultVoiceSubscriptionId());
         logd("onDeregisterDefaultNetworkAvail subId: " + subId);
         final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
@@ -151,7 +149,7 @@
     }
 
     private static void onDisableRadio(Intent intent, Context context) {
-        int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+        int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
                 SubscriptionManager.getDefaultVoiceSubscriptionId());
         logd("onDisableRadio subId: " + subId);
         final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
@@ -159,7 +157,7 @@
     }
 
     private static void onEnableRadio(Intent intent, Context context) {
-        int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+        int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
                 SubscriptionManager.getDefaultVoiceSubscriptionId());
         logd("onEnableRadio subId: " + subId);
         final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
@@ -202,7 +200,7 @@
     }
 
     private static void onResetAllCarrierActions(Intent intent, Context context) {
-        int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+        int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
                 SubscriptionManager.getDefaultVoiceSubscriptionId());
         logd("onResetAllCarrierActions subId: " + subId);
         final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
index 46b1d5f..c7f5e9a 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
@@ -19,10 +19,10 @@
 import android.content.Intent;
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
 
-import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.util.ArrayUtils;
 
 import java.util.ArrayList;
@@ -50,7 +50,7 @@
      * @param intent passing signal for config match
      * @return a list of carrier action for the given signal based on the carrier config.
      *
-     *  Example: input intent TelephonyIntent.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED
+     *  Example: input intent TelephonyManager.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED
      *  This intent allows fined-grained matching based on both intent type & extra values:
      *  apnType and errorCode.
      *  apnType read from passing intent is "default" and errorCode is 0x26 for example and
@@ -78,25 +78,25 @@
             String arg1 = null;
             String arg2 = null;
             switch (intent.getAction()) {
-                case TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED:
+                case TelephonyManager.ACTION_CARRIER_SIGNAL_REDIRECTED:
                     configs = b.getStringArray(CarrierConfigManager
                             .KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY);
                     break;
-                case TelephonyIntents.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED:
+                case TelephonyManager.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED:
                     configs = b.getStringArray(CarrierConfigManager
                             .KEY_CARRIER_DEFAULT_ACTIONS_ON_DCFAILURE_STRING_ARRAY);
-                    arg1 = intent.getStringExtra(TelephonyIntents.EXTRA_APN_TYPE_KEY);
-                    arg2 = intent.getStringExtra(TelephonyIntents.EXTRA_ERROR_CODE_KEY);
+                    arg1 = intent.getStringExtra(TelephonyManager.EXTRA_APN_TYPE);
+                    arg2 = intent.getStringExtra(TelephonyManager.EXTRA_ERROR_CODE);
                     break;
-                case TelephonyIntents.ACTION_CARRIER_SIGNAL_RESET:
+                case TelephonyManager.ACTION_CARRIER_SIGNAL_RESET:
                     configs = b.getStringArray(CarrierConfigManager
                             .KEY_CARRIER_DEFAULT_ACTIONS_ON_RESET);
                     break;
-                case TelephonyIntents.ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE:
+                case TelephonyManager.ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE:
                     configs = b.getStringArray(CarrierConfigManager
                             .KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE);
-                    arg1 = String.valueOf(intent.getBooleanExtra(TelephonyIntents
-                            .EXTRA_DEFAULT_NETWORK_AVAILABLE_KEY, false));
+                    arg1 = String.valueOf(intent.getBooleanExtra(TelephonyManager
+                            .EXTRA_DEFAULT_NETWORK_AVAILABLE, false));
                     break;
                 default:
                     Log.e(TAG, "load carrier config failure with un-configured key: "
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java
index 3e34f0a..78a02d7 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java
@@ -27,10 +27,9 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.provider.Settings;
+import android.telephony.TelephonyManager;
 import android.util.Log;
 
-import com.android.internal.telephony.TelephonyIntents;
-
 /**
  * Service to run {@link android.app.job.JobScheduler} job.
  * Service to monitor when there is a change to conent URI
@@ -93,7 +92,7 @@
         }
         int jobId;
         switch(intent.getAction()) {
-            case TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED:
+            case TelephonyManager.ACTION_CARRIER_SIGNAL_REDIRECTED:
                 jobId = PROVISION_OBSERVER_REEVALUATION_JOB_ID;
                 break;
             default:
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java
index 1928ad9..6229434 100644
--- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java
@@ -15,18 +15,22 @@
  */
 package com.android.carrierdefaultapp;
 
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.Intent;
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.test.InstrumentationTestCase;
 
-import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.TelephonyIntents;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -34,10 +38,6 @@
 import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
 
 public class CarrierDefaultReceiverTest extends InstrumentationTestCase {
     @Mock
@@ -69,6 +69,7 @@
         mContext.injectSystemService(NotificationManager.class, mNotificationMgr);
         mContext.injectSystemService(TelephonyManager.class, mTelephonyMgr);
         mContext.injectSystemService(CarrierConfigManager.class, mCarrierConfigMgr);
+        doReturn(mTelephonyMgr).when(mTelephonyMgr).createForSubscriptionId(anyInt());
 
         mReceiver = new CarrierDefaultBroadcastReceiver();
     }
@@ -87,8 +88,8 @@
                 .KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY, new String[]{"4,1"});
         doReturn(b).when(mCarrierConfigMgr).getConfig();
 
-        Intent intent = new Intent(TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED);
-        intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
+        Intent intent = new Intent(TelephonyManager.ACTION_CARRIER_SIGNAL_REDIRECTED);
+        intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
         mReceiver.onReceive(mContext, intent);
 
         mContext.waitForMs(100);
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index c2ce840..9ccb837 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -56,6 +56,7 @@
 import android.os.RemoteException;
 import android.os.image.DynamicSystemClient;
 import android.os.image.DynamicSystemManager;
+import android.text.TextUtils;
 import android.util.Log;
 import android.widget.Toast;
 
@@ -74,6 +75,8 @@
 
     // TODO (b/131866826): This is currently for test only. Will move this to System API.
     static final String KEY_ENABLE_WHEN_COMPLETED = "KEY_ENABLE_WHEN_COMPLETED";
+    static final String KEY_DSU_SLOT = "KEY_DSU_SLOT";
+    static final String DEFAULT_DSU_SLOT = "dsu";
 
     /*
      * Intent actions
@@ -244,10 +247,15 @@
         long systemSize = intent.getLongExtra(DynamicSystemClient.KEY_SYSTEM_SIZE, 0);
         long userdataSize = intent.getLongExtra(DynamicSystemClient.KEY_USERDATA_SIZE, 0);
         mEnableWhenCompleted = intent.getBooleanExtra(KEY_ENABLE_WHEN_COMPLETED, false);
+        String dsuSlot = intent.getStringExtra(KEY_DSU_SLOT);
 
+        if (TextUtils.isEmpty(dsuSlot)) {
+            dsuSlot = DEFAULT_DSU_SLOT;
+        }
         // TODO: better constructor or builder
-        mInstallTask = new InstallationAsyncTask(
-                url, systemSize, userdataSize, this, mDynSystem, this);
+        mInstallTask =
+                new InstallationAsyncTask(
+                        url, dsuSlot, systemSize, userdataSize, this, mDynSystem, this);
 
         mInstallTask.execute();
 
@@ -409,7 +417,9 @@
                 break;
 
             case STATUS_READY:
-                builder.setContentText(getString(R.string.notification_install_completed));
+                String msgCompleted = getString(R.string.notification_install_completed);
+                builder.setContentText(msgCompleted)
+                        .setStyle(new Notification.BigTextStyle().bigText(msgCompleted));
 
                 builder.addAction(new Notification.Action.Builder(
                         null, getString(R.string.notification_action_discard),
@@ -422,7 +432,9 @@
                 break;
 
             case STATUS_IN_USE:
-                builder.setContentText(getString(R.string.notification_dynsystem_in_use));
+                String msgInUse = getString(R.string.notification_dynsystem_in_use);
+                builder.setContentText(msgInUse)
+                        .setStyle(new Notification.BigTextStyle().bigText(msgInUse));
 
                 builder.addAction(new Notification.Action.Builder(
                         null, getString(R.string.notification_action_uninstall),
@@ -452,7 +464,49 @@
     }
 
     private void postStatus(int status, int cause, Throwable detail) {
-        Log.d(TAG, "postStatus(): statusCode=" + status + ", causeCode=" + cause);
+        String statusString;
+        String causeString;
+
+        switch (status) {
+            case STATUS_NOT_STARTED:
+                statusString = "NOT_STARTED";
+                break;
+            case STATUS_IN_PROGRESS:
+                statusString = "IN_PROGRESS";
+                break;
+            case STATUS_READY:
+                statusString = "READY";
+                break;
+            case STATUS_IN_USE:
+                statusString = "IN_USE";
+                break;
+            default:
+                statusString = "UNKNOWN";
+                break;
+        }
+
+        switch (cause) {
+            case CAUSE_INSTALL_COMPLETED:
+                causeString = "INSTALL_COMPLETED";
+                break;
+            case CAUSE_INSTALL_CANCELLED:
+                causeString = "INSTALL_CANCELLED";
+                break;
+            case CAUSE_ERROR_IO:
+                causeString = "ERROR_IO";
+                break;
+            case CAUSE_ERROR_INVALID_URL:
+                causeString = "ERROR_INVALID_URL";
+                break;
+            case CAUSE_ERROR_EXCEPTION:
+                causeString = "ERROR_EXCEPTION";
+                break;
+            default:
+                causeString = "CAUSE_NOT_SPECIFIED";
+                break;
+        }
+
+        Log.d(TAG, "status=" + statusString + ", cause=" + causeString);
 
         boolean notifyOnNotificationBar = true;
 
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
index b206a1f..9aea0e7 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
@@ -89,10 +89,12 @@
 
     interface ProgressListener {
         void onProgressUpdate(Progress progress);
+
         void onResult(int resultCode, Throwable detail);
     }
 
     private final String mUrl;
+    private final String mDsuSlot;
     private final long mSystemSize;
     private final long mUserdataSize;
     private final Context mContext;
@@ -106,9 +108,16 @@
     private InputStream mStream;
     private ZipFile mZipFile;
 
-    InstallationAsyncTask(String url, long systemSize, long userdataSize, Context context,
-            DynamicSystemManager dynSystem, ProgressListener listener) {
+    InstallationAsyncTask(
+            String url,
+            String dsuSlot,
+            long systemSize,
+            long userdataSize,
+            Context context,
+            DynamicSystemManager dynSystem,
+            ProgressListener listener) {
         mUrl = url;
+        mDsuSlot = dsuSlot;
         mSystemSize = systemSize;
         mUserdataSize = userdataSize;
         mContext = context;
@@ -126,14 +135,17 @@
 
             verifyAndPrepare();
 
-            mDynSystem.startInstallation();
+            mDynSystem.startInstallation(mDsuSlot);
 
             installUserdata();
             if (isCancelled()) {
                 mDynSystem.remove();
                 return null;
             }
-
+            if (mUrl == null) {
+                mDynSystem.finishInstallation();
+                return null;
+            }
             installImages();
             if (isCancelled()) {
                 mDynSystem.remove();
@@ -194,6 +206,9 @@
     }
 
     private void verifyAndPrepare() throws Exception {
+        if (mUrl == null) {
+            return;
+        }
         String extension = mUrl.substring(mUrl.lastIndexOf('.') + 1);
 
         if ("gz".equals(extension) || "gzip".equals(extension)) {
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
index 3b3933b..e42ded7 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
@@ -28,11 +28,9 @@
 import android.util.FeatureFlagUtils;
 import android.util.Log;
 
-
 /**
- * This Activity starts KeyguardManager and ask the user to confirm
- * before any installation request. If the device is not protected by
- * a password, it approves the request by default.
+ * This Activity starts KeyguardManager and ask the user to confirm before any installation request.
+ * If the device is not protected by a password, it approves the request by default.
  */
 public class VerificationActivity extends Activity {
 
@@ -88,11 +86,15 @@
         Uri url = callingIntent.getData();
         Bundle extras = callingIntent.getExtras();
 
-        sVerifiedUrl = url.toString();
+        if (url != null) {
+            sVerifiedUrl = url.toString();
+        }
 
         // start service
         Intent intent = new Intent(this, DynamicSystemInstallationService.class);
-        intent.setData(url);
+        if (url != null) {
+            intent.setData(url);
+        }
         intent.setAction(DynamicSystemClient.ACTION_START_INSTALL);
         intent.putExtras(extras);
 
@@ -106,6 +108,7 @@
     }
 
     static boolean isVerified(String url) {
+        if (url == null) return true;
         return sVerifiedUrl != null && sVerifiedUrl.equals(url);
     }
 }
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 1e19786..ec445d4 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -160,7 +160,7 @@
         final int userId = UserHandle.myUserId();
         final List<VolumeInfo> volumes = mStorageManager.getVolumes();
         for (VolumeInfo volume : volumes) {
-            if (!volume.isMountedReadable()) continue;
+            if (!volume.isMountedReadable() || volume.getMountUserId() != userId) continue;
 
             final String rootId;
             final String title;
@@ -192,9 +192,8 @@
                     title = mStorageManager.getBestVolumeDescription(privateVol);
                     storageUuid = StorageManager.convert(privateVol.fsUuid);
                 }
-            } else if ((volume.getType() == VolumeInfo.TYPE_PUBLIC
-                            || volume.getType() == VolumeInfo.TYPE_STUB)
-                    && volume.getMountUserId() == userId) {
+            } else if (volume.getType() == VolumeInfo.TYPE_PUBLIC
+                    || volume.getType() == VolumeInfo.TYPE_STUB) {
                 rootId = volume.getFsUuid();
                 title = mStorageManager.getBestVolumeDescription(volume);
                 storageUuid = null;
diff --git a/packages/FusedLocation/Android.bp b/packages/FusedLocation/Android.bp
index e794f72..c70ab71 100644
--- a/packages/FusedLocation/Android.bp
+++ b/packages/FusedLocation/Android.bp
@@ -14,9 +14,33 @@
 
 android_app {
     name: "FusedLocation",
-    srcs: ["**/*.java"],
+    srcs: ["src/**/*.java"],
     libs: ["com.android.location.provider"],
     platform_apis: true,
     certificate: "platform",
     privileged: true,
 }
+
+android_test {
+    name: "FusedLocationTests",
+    manifest: "test/AndroidManifest.xml",
+    test_config: "test/AndroidTest.xml",
+    srcs: [
+        "test/src/**/*.java",
+        "src/**/*.java",  // include real sources because we're forced to test this directly
+    ],
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+        "com.android.location.provider",
+    ],
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "androidx.test.ext.truth",
+        "mockito-target-minus-junit4",
+        "truth-prebuilt",
+    ],
+    test_suites: ["device-tests"]
+}
\ No newline at end of file
diff --git a/packages/FusedLocation/AndroidManifest.xml b/packages/FusedLocation/AndroidManifest.xml
index a8319ab..bad0497 100644
--- a/packages/FusedLocation/AndroidManifest.xml
+++ b/packages/FusedLocation/AndroidManifest.xml
@@ -23,6 +23,8 @@
 
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+    <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
     <uses-permission android:name="android.permission.INSTALL_LOCATION_PROVIDER" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
 
diff --git a/packages/FusedLocation/TEST_MAPPING b/packages/FusedLocation/TEST_MAPPING
new file mode 100644
index 0000000..e810d6a
--- /dev/null
+++ b/packages/FusedLocation/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "FusedLocationTests"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java b/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java
index be817d6..fb7dbc8 100644
--- a/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java
+++ b/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java
@@ -16,70 +16,307 @@
 
 package com.android.location.fused;
 
+import static android.content.Intent.ACTION_USER_SWITCHED;
+import static android.location.LocationManager.GPS_PROVIDER;
+import static android.location.LocationManager.NETWORK_PROVIDER;
+
+import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.location.Criteria;
-import android.os.Handler;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.location.LocationRequest;
+import android.os.Bundle;
 import android.os.Looper;
-import android.os.UserHandle;
+import android.os.Parcelable;
 import android.os.WorkSource;
 
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.location.ProviderRequest;
 import com.android.location.provider.LocationProviderBase;
+import com.android.location.provider.LocationRequestUnbundled;
 import com.android.location.provider.ProviderPropertiesUnbundled;
 import com.android.location.provider.ProviderRequestUnbundled;
 
-import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
-class FusedLocationProvider extends LocationProviderBase implements FusionEngine.Callback {
+/** Basic fused location provider implementation. */
+public class FusedLocationProvider extends LocationProviderBase {
+
     private static final String TAG = "FusedLocationProvider";
 
-    private static ProviderPropertiesUnbundled PROPERTIES = ProviderPropertiesUnbundled.create(
-            false, false, false, false, true, true, true, Criteria.POWER_LOW,
-            Criteria.ACCURACY_FINE);
+    private static final ProviderPropertiesUnbundled PROPERTIES =
+            ProviderPropertiesUnbundled.create(
+                    /* requiresNetwork = */ false,
+                    /* requiresSatellite = */ false,
+                    /* requiresCell = */ false,
+                    /* hasMonetaryCost = */ false,
+                    /* supportsAltitude = */ true,
+                    /* supportsSpeed = */ true,
+                    /* supportsBearing = */ true,
+                    Criteria.POWER_LOW,
+                    Criteria.ACCURACY_FINE
+            );
+
+    private static final long MAX_LOCATION_COMPARISON_NS = 11 * 1000000000L; // 11 seconds
+
+    private final Object mLock = new Object();
 
     private final Context mContext;
-    private final Handler mHandler;
-    private final FusionEngine mEngine;
+    private final LocationManager mLocationManager;
+    private final LocationListener mGpsListener;
+    private final LocationListener mNetworkListener;
+    private final BroadcastReceiver mUserChangeReceiver;
 
-    private final BroadcastReceiver mUserSwitchReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (Intent.ACTION_USER_SWITCHED.equals(action)) {
-                mEngine.switchUser();
+    @GuardedBy("mLock")
+    private ProviderRequestUnbundled mRequest;
+    @GuardedBy("mLock")
+    private WorkSource mWorkSource;
+    @GuardedBy("mLock")
+    private long mGpsInterval;
+    @GuardedBy("mLock")
+    private long mNetworkInterval;
+
+    @GuardedBy("mLock")
+    @Nullable private Location mFusedLocation;
+    @GuardedBy("mLock")
+    @Nullable private Location mGpsLocation;
+    @GuardedBy("mLock")
+    @Nullable private Location mNetworkLocation;
+
+    public FusedLocationProvider(Context context) {
+        super(TAG, PROPERTIES);
+        mContext = context;
+        mLocationManager = context.getSystemService(LocationManager.class);
+
+        mGpsListener = new LocationListener() {
+            @Override
+            public void onLocationChanged(Location location) {
+                synchronized (mLock) {
+                    mGpsLocation = location;
+                    reportBestLocationLocked();
+                }
+            }
+
+            @Override
+            public void onProviderDisabled(String provider) {
+                synchronized (mLock) {
+                    // if satisfying a bypass request, don't clear anything
+                    if (mRequest.getReportLocation() && mRequest.isLocationSettingsIgnored()) {
+                        return;
+                    }
+
+                    mGpsLocation = null;
+                }
+            }
+        };
+
+        mNetworkListener = new LocationListener() {
+            @Override
+            public void onLocationChanged(Location location) {
+                synchronized (mLock) {
+                    mNetworkLocation = location;
+                    reportBestLocationLocked();
+                }
+            }
+
+            @Override
+            public void onProviderDisabled(String provider) {
+                synchronized (mLock) {
+                    // if satisfying a bypass request, don't clear anything
+                    if (mRequest.getReportLocation() && mRequest.isLocationSettingsIgnored()) {
+                        return;
+                    }
+
+                    mNetworkLocation = null;
+                }
+            }
+        };
+
+        mUserChangeReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (!ACTION_USER_SWITCHED.equals(intent.getAction())) {
+                    return;
+                }
+
+                onUserChanged();
+            }
+        };
+
+        mRequest = new ProviderRequestUnbundled(ProviderRequest.EMPTY_REQUEST);
+        mWorkSource = new WorkSource();
+        mGpsInterval = Long.MAX_VALUE;
+        mNetworkInterval = Long.MAX_VALUE;
+    }
+
+    void start() {
+        mContext.registerReceiver(mUserChangeReceiver, new IntentFilter(ACTION_USER_SWITCHED));
+    }
+
+    void stop() {
+        mContext.unregisterReceiver(mUserChangeReceiver);
+
+        synchronized (mLock) {
+            mRequest = new ProviderRequestUnbundled(ProviderRequest.EMPTY_REQUEST);
+            updateRequirementsLocked();
+        }
+    }
+
+    @Override
+    public void onSetRequest(ProviderRequestUnbundled request, WorkSource workSource) {
+        synchronized (mLock) {
+            mRequest = request;
+            mWorkSource = workSource;
+            updateRequirementsLocked();
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void updateRequirementsLocked() {
+        long gpsInterval = Long.MAX_VALUE;
+        long networkInterval = Long.MAX_VALUE;
+        if (mRequest.getReportLocation()) {
+            for (LocationRequestUnbundled request : mRequest.getLocationRequests()) {
+                switch (request.getQuality()) {
+                    case LocationRequestUnbundled.ACCURACY_FINE:
+                    case LocationRequestUnbundled.POWER_HIGH:
+                        if (request.getInterval() < gpsInterval) {
+                            gpsInterval = request.getInterval();
+                        }
+                        if (request.getInterval() < networkInterval) {
+                            networkInterval = request.getInterval();
+                        }
+                        break;
+                    case LocationRequestUnbundled.ACCURACY_BLOCK:
+                    case LocationRequestUnbundled.ACCURACY_CITY:
+                    case LocationRequestUnbundled.POWER_LOW:
+                        if (request.getInterval() < networkInterval) {
+                            networkInterval = request.getInterval();
+                        }
+                        break;
+                }
             }
         }
-    };
 
-    FusedLocationProvider(Context context) {
-        super(TAG, PROPERTIES);
-
-        mContext = context;
-        mHandler = new Handler(Looper.myLooper());
-        mEngine = new FusionEngine(context, Looper.myLooper(), this);
+        if (gpsInterval != mGpsInterval) {
+            resetProviderRequestLocked(GPS_PROVIDER, mGpsInterval, gpsInterval, mGpsListener);
+            mGpsInterval = gpsInterval;
+        }
+        if (networkInterval != mNetworkInterval) {
+            resetProviderRequestLocked(NETWORK_PROVIDER, mNetworkInterval, networkInterval,
+                    mNetworkListener);
+            mNetworkInterval = networkInterval;
+        }
     }
 
-    void init() {
-        // listen for user change
-        mContext.registerReceiverAsUser(mUserSwitchReceiver, UserHandle.ALL,
-                new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler);
+    @GuardedBy("mLock")
+    private void resetProviderRequestLocked(String provider, long oldInterval, long newInterval,
+            LocationListener listener) {
+        if (oldInterval != Long.MAX_VALUE) {
+            mLocationManager.removeUpdates(listener);
+        }
+        if (newInterval != Long.MAX_VALUE) {
+            LocationRequest request = LocationRequest.createFromDeprecatedProvider(
+                    provider, newInterval, 0, false);
+            if (mRequest.isLocationSettingsIgnored()) {
+                request.setLocationSettingsIgnored(true);
+            }
+            request.setWorkSource(mWorkSource);
+            mLocationManager.requestLocationUpdates(request, listener, Looper.getMainLooper());
+        }
     }
 
-    void destroy() {
-        mContext.unregisterReceiver(mUserSwitchReceiver);
-        mHandler.post(() -> mEngine.setRequest(null));
+    @GuardedBy("mLock")
+    private void reportBestLocationLocked() {
+        Location bestLocation = chooseBestLocation(mGpsLocation, mNetworkLocation);
+        if (bestLocation == mFusedLocation) {
+            return;
+        }
+
+        mFusedLocation = bestLocation;
+        if (mFusedLocation == null) {
+            return;
+        }
+
+        // copy NO_GPS_LOCATION extra from mNetworkLocation into mFusedLocation
+        if (mNetworkLocation != null) {
+            Bundle srcExtras = mNetworkLocation.getExtras();
+            if (srcExtras != null) {
+                Parcelable srcParcelable =
+                        srcExtras.getParcelable(LocationProviderBase.EXTRA_NO_GPS_LOCATION);
+                if (srcParcelable instanceof Location) {
+                    Bundle dstExtras = mFusedLocation.getExtras();
+                    if (dstExtras == null) {
+                        dstExtras = new Bundle();
+                        mFusedLocation.setExtras(dstExtras);
+                    }
+                    dstExtras.putParcelable(LocationProviderBase.EXTRA_NO_GPS_LOCATION,
+                            srcParcelable);
+                }
+            }
+        }
+
+        reportLocation(mFusedLocation);
     }
 
-    @Override
-    public void onSetRequest(ProviderRequestUnbundled request, WorkSource source) {
-        mHandler.post(() -> mEngine.setRequest(request));
+    private void onUserChanged() {
+        // clear cached locations when the user changes to prevent leaking user information
+        synchronized (mLock) {
+            mFusedLocation = null;
+            mGpsLocation = null;
+            mNetworkLocation = null;
+        }
     }
 
-    @Override
-    public void onDump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        mEngine.dump(fd, pw, args);
+    void dump(PrintWriter writer) {
+        synchronized (mLock) {
+            writer.println("request: " + mRequest);
+            if (mGpsInterval != Long.MAX_VALUE) {
+                writer.println("  gps interval: " + mGpsInterval);
+            }
+            if (mNetworkInterval != Long.MAX_VALUE) {
+                writer.println("  network interval: " + mNetworkInterval);
+            }
+            if (mGpsLocation != null) {
+                writer.println("  last gps location: " + mGpsLocation);
+            }
+            if (mNetworkLocation != null) {
+                writer.println("  last network location: " + mNetworkLocation);
+            }
+        }
+    }
+
+    @Nullable
+    private static Location chooseBestLocation(
+            @Nullable Location locationA,
+            @Nullable Location locationB) {
+        if (locationA == null) {
+            return locationB;
+        }
+        if (locationB == null) {
+            return locationA;
+        }
+
+        if (locationA.getElapsedRealtimeNanos()
+                > locationB.getElapsedRealtimeNanos() + MAX_LOCATION_COMPARISON_NS) {
+            return locationA;
+        }
+        if (locationB.getElapsedRealtimeNanos()
+                > locationA.getElapsedRealtimeNanos() + MAX_LOCATION_COMPARISON_NS) {
+            return locationB;
+        }
+
+        if (!locationA.hasAccuracy()) {
+            return locationB;
+        }
+        if (!locationB.hasAccuracy()) {
+            return locationA;
+        }
+        return locationA.getAccuracy() < locationB.getAccuracy() ? locationA : locationB;
     }
 }
diff --git a/packages/FusedLocation/src/com/android/location/fused/FusedLocationService.java b/packages/FusedLocation/src/com/android/location/fused/FusedLocationService.java
index 75bb5ec..1fa3824 100644
--- a/packages/FusedLocation/src/com/android/location/fused/FusedLocationService.java
+++ b/packages/FusedLocation/src/com/android/location/fused/FusedLocationService.java
@@ -16,19 +16,23 @@
 
 package com.android.location.fused;
 
+import android.annotation.Nullable;
 import android.app.Service;
 import android.content.Intent;
 import android.os.IBinder;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
 public class FusedLocationService extends Service {
 
-    private FusedLocationProvider mProvider;
+    @Nullable private FusedLocationProvider mProvider;
 
     @Override
     public IBinder onBind(Intent intent) {
         if (mProvider == null) {
             mProvider = new FusedLocationProvider(this);
-            mProvider.init();
+            mProvider.start();
         }
 
         return mProvider.getBinder();
@@ -37,8 +41,15 @@
     @Override
     public void onDestroy() {
         if (mProvider != null) {
-            mProvider.destroy();
+            mProvider.stop();
             mProvider = null;
         }
     }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        if (mProvider != null) {
+            mProvider.dump(writer);
+        }
+    }
 }
diff --git a/packages/FusedLocation/src/com/android/location/fused/FusionEngine.java b/packages/FusedLocation/src/com/android/location/fused/FusionEngine.java
deleted file mode 100644
index e4610cf..0000000
--- a/packages/FusedLocation/src/com/android/location/fused/FusionEngine.java
+++ /dev/null
@@ -1,270 +0,0 @@
-/*
- * Copyright (C) 2012 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.location.fused;
-
-import android.content.Context;
-import android.location.Location;
-import android.location.LocationListener;
-import android.location.LocationManager;
-import android.os.Bundle;
-import android.os.Looper;
-import android.os.Parcelable;
-import android.os.SystemClock;
-import android.util.Log;
-
-import com.android.location.provider.LocationProviderBase;
-import com.android.location.provider.LocationRequestUnbundled;
-import com.android.location.provider.ProviderRequestUnbundled;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.HashMap;
-
-public class FusionEngine implements LocationListener {
-    public interface Callback {
-        void reportLocation(Location location);
-    }
-
-    private static final String TAG = "FusedLocation";
-    private static final String NETWORK = LocationManager.NETWORK_PROVIDER;
-    private static final String GPS = LocationManager.GPS_PROVIDER;
-    private static final String FUSED = LocationProviderBase.FUSED_PROVIDER;
-
-    public static final long SWITCH_ON_FRESHNESS_CLIFF_NS = 11 * 1000000000L; // 11 seconds
-
-    private final LocationManager mLocationManager;
-    private final Looper mLooper;
-    private final Callback mCallback;
-
-    // all fields are only used on mLooper thread. except for in dump() which is not thread-safe
-    private Location mFusedLocation;
-    private Location mGpsLocation;
-    private Location mNetworkLocation;
-
-    private ProviderRequestUnbundled mRequest;
-
-    private final HashMap<String, ProviderStats> mStats = new HashMap<>();
-
-    FusionEngine(Context context, Looper looper, Callback callback) {
-        mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
-        mNetworkLocation = new Location("");
-        mNetworkLocation.setAccuracy(Float.MAX_VALUE);
-        mGpsLocation = new Location("");
-        mGpsLocation.setAccuracy(Float.MAX_VALUE);
-        mLooper = looper;
-        mCallback = callback;
-
-        mStats.put(GPS, new ProviderStats());
-        mStats.put(NETWORK, new ProviderStats());
-    }
-
-    /** Called on mLooper thread */
-    public void setRequest(ProviderRequestUnbundled request) {
-        mRequest = request;
-        updateRequirements();
-    }
-
-    private static class ProviderStats {
-        public boolean requested;
-        public long requestTime;
-        public long minTime;
-
-        @Override
-        public String toString() {
-            return (requested ? " REQUESTED" : " ---");
-        }
-    }
-
-    private void enableProvider(String name, long minTime) {
-        ProviderStats stats = mStats.get(name);
-        if (stats == null) return;
-
-        if (mLocationManager.isProviderEnabled(name)) {
-            if (!stats.requested) {
-                stats.requestTime = SystemClock.elapsedRealtime();
-                stats.requested = true;
-                stats.minTime = minTime;
-                mLocationManager.requestLocationUpdates(name, minTime, 0, this, mLooper);
-            } else if (stats.minTime != minTime) {
-                stats.minTime = minTime;
-                mLocationManager.requestLocationUpdates(name, minTime, 0, this, mLooper);
-            }
-        }
-    }
-
-    private void disableProvider(String name) {
-        ProviderStats stats = mStats.get(name);
-        if (stats == null) return;
-
-        if (stats.requested) {
-            stats.requested = false;
-            mLocationManager.removeUpdates(this);  //TODO GLOBAL
-        }
-    }
-
-    private void updateRequirements() {
-        if (mRequest == null || !mRequest.getReportLocation()) {
-            mRequest = null;
-            disableProvider(NETWORK);
-            disableProvider(GPS);
-            return;
-        }
-
-        long networkInterval = Long.MAX_VALUE;
-        long gpsInterval = Long.MAX_VALUE;
-        for (LocationRequestUnbundled request : mRequest.getLocationRequests()) {
-            switch (request.getQuality()) {
-                case LocationRequestUnbundled.ACCURACY_FINE:
-                case LocationRequestUnbundled.POWER_HIGH:
-                    if (request.getInterval() < gpsInterval) {
-                        gpsInterval = request.getInterval();
-                    }
-                    if (request.getInterval() < networkInterval) {
-                        networkInterval = request.getInterval();
-                    }
-                    break;
-                case LocationRequestUnbundled.ACCURACY_BLOCK:
-                case LocationRequestUnbundled.ACCURACY_CITY:
-                case LocationRequestUnbundled.POWER_LOW:
-                    if (request.getInterval() < networkInterval) {
-                        networkInterval = request.getInterval();
-                    }
-                    break;
-            }
-        }
-
-        if (gpsInterval < Long.MAX_VALUE) {
-            enableProvider(GPS, gpsInterval);
-        } else {
-            disableProvider(GPS);
-        }
-        if (networkInterval < Long.MAX_VALUE) {
-            enableProvider(NETWORK, networkInterval);
-        } else {
-            disableProvider(NETWORK);
-        }
-    }
-
-    /**
-     * Test whether one location (a) is better to use than another (b).
-     */
-    private static boolean isBetterThan(Location locationA, Location locationB) {
-        if (locationA == null) {
-            return false;
-        }
-        if (locationB == null) {
-            return true;
-        }
-        // A provider is better if the reading is sufficiently newer.  Heading
-        // underground can cause GPS to stop reporting fixes.  In this case it's
-        // appropriate to revert to cell, even when its accuracy is less.
-        if (locationA.getElapsedRealtimeNanos()
-                > locationB.getElapsedRealtimeNanos() + SWITCH_ON_FRESHNESS_CLIFF_NS) {
-            return true;
-        }
-
-        // A provider is better if it has better accuracy.  Assuming both readings
-        // are fresh (and by that accurate), choose the one with the smaller
-        // accuracy circle.
-        if (!locationA.hasAccuracy()) {
-            return false;
-        }
-        if (!locationB.hasAccuracy()) {
-            return true;
-        }
-        return locationA.getAccuracy() < locationB.getAccuracy();
-    }
-
-    private void updateFusedLocation() {
-        // may the best location win!
-        if (isBetterThan(mGpsLocation, mNetworkLocation)) {
-            mFusedLocation = new Location(mGpsLocation);
-        } else {
-            mFusedLocation = new Location(mNetworkLocation);
-        }
-        mFusedLocation.setProvider(FUSED);
-        if (mNetworkLocation != null) {
-            // copy NO_GPS_LOCATION extra from mNetworkLocation into mFusedLocation
-            Bundle srcExtras = mNetworkLocation.getExtras();
-            if (srcExtras != null) {
-                Parcelable srcParcelable =
-                        srcExtras.getParcelable(LocationProviderBase.EXTRA_NO_GPS_LOCATION);
-                if (srcParcelable instanceof Location) {
-                    Bundle dstExtras = mFusedLocation.getExtras();
-                    if (dstExtras == null) {
-                        dstExtras = new Bundle();
-                        mFusedLocation.setExtras(dstExtras);
-                    }
-                    dstExtras.putParcelable(LocationProviderBase.EXTRA_NO_GPS_LOCATION,
-                            srcParcelable);
-                }
-            }
-        }
-
-        if (mCallback != null) {
-            mCallback.reportLocation(mFusedLocation);
-        } else {
-            Log.w(TAG, "Location updates received while fusion engine not started");
-        }
-    }
-
-    /** Called on mLooper thread */
-    @Override
-    public void onLocationChanged(Location location) {
-        if (GPS.equals(location.getProvider())) {
-            mGpsLocation = location;
-            updateFusedLocation();
-        } else if (NETWORK.equals(location.getProvider())) {
-            mNetworkLocation = location;
-            updateFusedLocation();
-        }
-    }
-
-    /** Called on mLooper thread */
-    @Override
-    public void onStatusChanged(String provider, int status, Bundle extras) {
-    }
-
-    /** Called on mLooper thread */
-    @Override
-    public void onProviderEnabled(String provider) {
-    }
-
-    /** Called on mLooper thread */
-    @Override
-    public void onProviderDisabled(String provider) {
-    }
-
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        StringBuilder s = new StringBuilder();
-        s.append(mRequest).append('\n');
-        s.append("fused=").append(mFusedLocation).append('\n');
-        s.append(String.format("gps %s\n", mGpsLocation));
-        s.append("    ").append(mStats.get(GPS)).append('\n');
-        s.append(String.format("net %s\n", mNetworkLocation));
-        s.append("    ").append(mStats.get(NETWORK)).append('\n');
-        pw.append(s);
-    }
-
-    /** Called on mLooper thread */
-    public void switchUser() {
-        // reset state to prevent location data leakage
-        mFusedLocation = null;
-        mGpsLocation = null;
-        mNetworkLocation = null;
-    }
-}
diff --git a/packages/FusedLocation/test/AndroidManifest.xml b/packages/FusedLocation/test/AndroidManifest.xml
new file mode 100644
index 0000000..d6c4107
--- /dev/null
+++ b/packages/FusedLocation/test/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.location.fused.tests">
+
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+    <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
+
+    <application android:label="FusedLocation Tests">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.location.fused.tests"
+                     android:label="FusedLocation Tests" />
+</manifest>
diff --git a/packages/FusedLocation/test/AndroidTest.xml b/packages/FusedLocation/test/AndroidTest.xml
new file mode 100644
index 0000000..f88e595
--- /dev/null
+++ b/packages/FusedLocation/test/AndroidTest.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.
+  -->
+<configuration description="FusedLocation Tests">
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="FusedLocationTests.apk" />
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="framework-base-presubmit" />
+    <option name="test-tag" value="FusedLocationTests" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.location.fused.tests" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
new file mode 100644
index 0000000..3312651
--- /dev/null
+++ b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.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 com.android.location.fused.tests;
+
+import static android.location.LocationManager.FUSED_PROVIDER;
+import static android.location.LocationManager.GPS_PROVIDER;
+import static android.location.LocationManager.NETWORK_PROVIDER;
+
+import static androidx.test.ext.truth.location.LocationSubject.assertThat;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.location.Criteria;
+import android.location.Location;
+import android.location.LocationManager;
+import android.location.LocationRequest;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.os.WorkSource;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.location.ILocationProvider;
+import com.android.internal.location.ILocationProviderManager;
+import com.android.internal.location.ProviderProperties;
+import com.android.internal.location.ProviderRequest;
+import com.android.location.fused.FusedLocationProvider;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class FusedLocationServiceTest {
+
+    private static final String TAG = "FusedLocationServiceTest";
+
+    private static final long TIMEOUT_MS = 5000;
+
+    private Context mContext;
+    private Random mRandom;
+    private LocationManager mLocationManager;
+
+    private ILocationProvider mProvider;
+    private LocationProviderManagerCapture mManager;
+
+    @Before
+    public void setUp() throws Exception {
+        long seed = System.currentTimeMillis();
+        Log.i(TAG, "location seed: " + seed);
+
+        mContext = InstrumentationRegistry.getTargetContext();
+        mRandom = new Random(seed);
+        mLocationManager = mContext.getSystemService(LocationManager.class);
+
+        setMockLocation(true);
+
+        mManager = new LocationProviderManagerCapture();
+        mProvider = ILocationProvider.Stub.asInterface(
+                new FusedLocationProvider(mContext).getBinder());
+        mProvider.setLocationProviderManager(mManager);
+
+        mLocationManager.addTestProvider(NETWORK_PROVIDER,
+                true,
+                false,
+                true,
+                false,
+                false,
+                false,
+                false,
+                Criteria.POWER_MEDIUM,
+                Criteria.ACCURACY_FINE);
+        mLocationManager.setTestProviderEnabled(NETWORK_PROVIDER, true);
+        mLocationManager.addTestProvider(GPS_PROVIDER,
+                true,
+                false,
+                true,
+                false,
+                false,
+                false,
+                false,
+                Criteria.POWER_MEDIUM,
+                Criteria.ACCURACY_FINE);
+        mLocationManager.setTestProviderEnabled(GPS_PROVIDER, true);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        for (String provider : mLocationManager.getAllProviders()) {
+            mLocationManager.removeTestProvider(provider);
+        }
+
+        setMockLocation(false);
+    }
+
+    @Test
+    public void testNetworkRequest() throws Exception {
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(FUSED_PROVIDER, 1000,
+                0, false);
+
+        mProvider.setRequest(
+                        new ProviderRequest.Builder()
+                                .setInterval(1000)
+                                .setLocationRequests(Collections.singletonList(request))
+                                .build(),
+                new WorkSource());
+
+        Location location = createLocation(NETWORK_PROVIDER, mRandom);
+        mLocationManager.setTestProviderLocation(NETWORK_PROVIDER, location);
+
+        assertThat(mManager.getNextLocation(TIMEOUT_MS)).isEqualTo(location);
+    }
+
+    @Test
+    public void testGpsRequest() throws Exception {
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(FUSED_PROVIDER, 1000,
+                0, false).setQuality(LocationRequest.POWER_HIGH);
+
+        mProvider.setRequest(
+                new ProviderRequest.Builder()
+                        .setInterval(1000)
+                        .setLocationRequests(Collections.singletonList(request))
+                        .build(),
+                new WorkSource());
+
+        Location location = createLocation(GPS_PROVIDER, mRandom);
+        mLocationManager.setTestProviderLocation(GPS_PROVIDER, location);
+
+        assertThat(mManager.getNextLocation(TIMEOUT_MS)).isEqualTo(location);
+    }
+
+    @Test
+    public void testBypassRequest() throws Exception {
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(FUSED_PROVIDER, 1000,
+                0, false).setQuality(LocationRequest.POWER_HIGH).setLocationSettingsIgnored(true);
+
+        mProvider.setRequest(
+                new ProviderRequest.Builder()
+                        .setInterval(1000)
+                        .setLocationSettingsIgnored(true)
+                        .setLocationRequests(Collections.singletonList(request))
+                        .build(),
+                new WorkSource());
+
+        boolean containsNetworkBypass = false;
+        for (LocationRequest iRequest : mLocationManager.getTestProviderCurrentRequests(
+                NETWORK_PROVIDER)) {
+            if (iRequest.isLocationSettingsIgnored()) {
+                containsNetworkBypass = true;
+                break;
+            }
+        }
+
+        boolean containsGpsBypass = false;
+        for (LocationRequest iRequest : mLocationManager.getTestProviderCurrentRequests(
+                GPS_PROVIDER)) {
+            if (iRequest.isLocationSettingsIgnored()) {
+                containsGpsBypass = true;
+                break;
+            }
+        }
+
+        assertThat(containsNetworkBypass).isTrue();
+        assertThat(containsGpsBypass).isTrue();
+    }
+
+    private static class LocationProviderManagerCapture extends ILocationProviderManager.Stub {
+
+        private final LinkedBlockingQueue<Location> mLocations;
+
+        private LocationProviderManagerCapture() {
+            mLocations = new LinkedBlockingQueue<>();
+        }
+
+        @Override
+        public void onSetAdditionalProviderPackages(List<String> packageNames) {
+
+        }
+
+        @Override
+        public void onSetEnabled(boolean enabled) {
+
+        }
+
+        @Override
+        public void onSetProperties(ProviderProperties properties) {
+
+        }
+
+        @Override
+        public void onReportLocation(Location location) {
+            mLocations.add(location);
+        }
+
+        public Location getNextLocation(long timeoutMs) throws InterruptedException {
+            return mLocations.poll(timeoutMs, TimeUnit.MILLISECONDS);
+        }
+    }
+
+    private static final double MIN_LATITUDE = -90D;
+    private static final double MAX_LATITUDE = 90D;
+    private static final double MIN_LONGITUDE = -180D;
+    private static final double MAX_LONGITUDE = 180D;
+
+    private static final float MIN_ACCURACY = 1;
+    private static final float MAX_ACCURACY = 100;
+
+    private static Location createLocation(String provider, Random random) {
+        return createLocation(provider,
+                MIN_LATITUDE + random.nextDouble() * (MAX_LATITUDE - MIN_LATITUDE),
+                MIN_LONGITUDE + random.nextDouble() * (MAX_LONGITUDE - MIN_LONGITUDE),
+                MIN_ACCURACY + random.nextFloat() * (MAX_ACCURACY - MIN_ACCURACY));
+    }
+
+    private static Location createLocation(String provider, double latitude, double longitude,
+            float accuracy) {
+        Location location = new Location(provider);
+        location.setLatitude(latitude);
+        location.setLongitude(longitude);
+        location.setAccuracy(accuracy);
+        location.setTime(System.currentTimeMillis());
+        location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
+        return location;
+    }
+
+    private static void setMockLocation(boolean allowed) throws IOException {
+        ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .executeShellCommand("appops set "
+                        + InstrumentationRegistry.getTargetContext().getPackageName()
+                        + " android:mock_location " + (allowed ? "allow" : "deny"));
+        try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
+            ByteArrayOutputStream os = new ByteArrayOutputStream();
+            byte[] buffer = new byte[32768];
+            int count;
+            try {
+                while ((count = fis.read(buffer)) != -1) {
+                    os.write(buffer, 0, count);
+                }
+                fis.close();
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+            Log.e(TAG, new String(os.toByteArray()));
+        }
+    }
+}
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
index 50f858e..e87148e 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
@@ -211,6 +211,12 @@
 
     private int performBackupInternal(
             PackageInfo packageInfo, ParcelFileDescriptor data, int flags) {
+        if ((flags & BackupTransport.FLAG_DATA_NOT_CHANGED) != 0) {
+            // For unchanged data notifications we do nothing and tell the
+            // caller everything was OK
+            return BackupTransport.TRANSPORT_OK;
+        }
+
         boolean isIncremental = (flags & FLAG_INCREMENTAL) != 0;
         boolean isNonIncremental = (flags & FLAG_NON_INCREMENTAL) != 0;
 
diff --git a/packages/PackageInstaller/res/values-bs/strings.xml b/packages/PackageInstaller/res/values-bs/strings.xml
index 421526b..a099147 100644
--- a/packages/PackageInstaller/res/values-bs/strings.xml
+++ b/packages/PackageInstaller/res/values-bs/strings.xml
@@ -62,7 +62,7 @@
     <string name="uninstalling_notification_channel" msgid="840153394325714653">"Tekuća deinstaliranja"</string>
     <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"Neuspjela deinstaliranja"</string>
     <string name="uninstalling" msgid="8709566347688966845">"Deinstaliranje..."</string>
-    <string name="uninstalling_app" msgid="8866082646836981397">"Deinstaliranje paketa <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>…"</string>
+    <string name="uninstalling_app" msgid="8866082646836981397">"Deinstaliranje aplikacije <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>…"</string>
     <string name="uninstall_done" msgid="439354138387969269">"Deinstaliranje je završeno."</string>
     <string name="uninstall_done_app" msgid="4588850984473605768">"Deinstaliran je paket <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
     <string name="uninstall_failed" msgid="1847750968168364332">"Deinstaliranje nije uspjelo."</string>
diff --git a/packages/PackageInstaller/res/values-ky/strings.xml b/packages/PackageInstaller/res/values-ky/strings.xml
index 61ff87f..822c548 100644
--- a/packages/PackageInstaller/res/values-ky/strings.xml
+++ b/packages/PackageInstaller/res/values-ky/strings.xml
@@ -24,8 +24,8 @@
     <string name="installing_app" msgid="1165095864863849422">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> орнотулууда…"</string>
     <string name="install_done" msgid="5987363587661783896">"Колдонмо орнотулду."</string>
     <string name="install_confirm_question" msgid="8176284075816604590">"Бул колдонмону орнотоюн деп жатасызбы?"</string>
-    <string name="install_confirm_question_update" msgid="7942235418781274635">"Учурдагы колдонмону жаңыртканы жатасызбы? Буга чейин сакталган дайындарыңыз өчүрүлбөйт."</string>
-    <string name="install_confirm_question_update_system" msgid="4713001702777910263">"Учурдагы алдын ала орнотулган колдонмону жаңыртканы жатасызбы? Буга чейин сакталган дайындарыңыз өчүрүлбөйт."</string>
+    <string name="install_confirm_question_update" msgid="7942235418781274635">"Учурдагы колдонмону жаңыртканы жатасызбы? Буга чейин сакталган дайын-даректериңиз өчүрүлбөйт."</string>
+    <string name="install_confirm_question_update_system" msgid="4713001702777910263">"Учурдагы алдын ала орнотулган колдонмону жаңыртканы жатасызбы? Буга чейин сакталган дайын-даректериңиз өчүрүлбөйт."</string>
     <string name="install_failed" msgid="5777824004474125469">"Колдонмо орнотулган жок."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Топтомду орнотууга болбойт."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Башка топтом менен дал келбегендиктен колдонмо орнотулган жок."</string>
@@ -83,9 +83,9 @@
     <string name="untrusted_external_source_warning" product="tablet" msgid="6539403649459942547">"Коопсуздукту сактоо максатында, планшетиңизге бул булактан колдонмолорду орнотууга уруксат жок."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="1206648674551321364">"Коопсуздукту сактоо максатында, сыналгыңызга бул булактан колдонмолорду орнотууга уруксат жок."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="7279739265754475165">"Коопсуздукту сактоо максатында, телефонуңузга бул булактан колдонмолорду орнотууга уруксат жок."</string>
-    <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Телефонуңуз жана жеке дайындарыңыз белгисиз колдонмолордон зыян тартып калышы мүмкүн. Бул колдонмону орнотуп, аны пайдалануудан улам телефонуңузга кандайдыр бир зыян келтирилсе же дайындарыңызды жоготуп алсаңыз, өзүңүз жооптуу болосуз."</string>
-    <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Планшетиңиз жана жеке дайындарыңыз белгисиз колдонмолордон зыян тартып калышы мүмкүн. Бул колдонмону орнотуп, аны пайдалануудан улам планшетиңизге кандайдыр бир зыян келтирилсе же дайындарыңызды жоготуп алсаңыз, өзүңүз жооптуу болосуз."</string>
-    <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"Сыналгыңыз жана жеке дайындарыңыз белгисиз колдонмолордон зыян тартып калышы мүмкүн. Бул колдонмону орнотуп, аны пайдалануудан улам сыналгыңызга кандайдыр бир зыян келтирилсе же дайындарыңызды жоготуп алсаңыз, өзүңүз жооптуу болосуз."</string>
+    <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Телефонуңуз жана жеке дайын-даректериңиз белгисиз колдонмолордон зыян тартып калышы мүмкүн. Бул колдонмону орнотуп, аны пайдалануудан улам телефонуңузга кандайдыр бир зыян келтирилсе же дайын-даректериңизды жоготуп алсаңыз, өзүңүз жооптуу болосуз."</string>
+    <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Планшетиңиз жана жеке дайын-даректериңиз белгисиз колдонмолордон зыян тартып калышы мүмкүн. Бул колдонмону орнотуп, аны пайдалануудан улам планшетиңизге кандайдыр бир зыян келтирилсе же дайын-даректериңизды жоготуп алсаңыз, өзүңүз жооптуу болосуз."</string>
+    <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"Сыналгыңыз жана жеке дайын-даректериңиз белгисиз колдонмолордон зыян тартып калышы мүмкүн. Бул колдонмону орнотуп, аны пайдалануудан улам сыналгыңызга кандайдыр бир зыян келтирилсе же дайын-даректериңизды жоготуп алсаңыз, өзүңүз жооптуу болосуз."</string>
     <string name="anonymous_source_continue" msgid="4375745439457209366">"Улантуу"</string>
     <string name="external_sources_settings" msgid="4046964413071713807">"Жөндөөлөр"</string>
     <string name="wear_app_channel" msgid="1960809674709107850">"Тагынма колдонмолорду орнотуу/чыгаруу"</string>
diff --git a/packages/PackageInstaller/res/values-pl/strings.xml b/packages/PackageInstaller/res/values-pl/strings.xml
index 7b50547..5ce30c2 100644
--- a/packages/PackageInstaller/res/values-pl/strings.xml
+++ b/packages/PackageInstaller/res/values-pl/strings.xml
@@ -62,7 +62,7 @@
     <string name="uninstalling_notification_channel" msgid="840153394325714653">"Aktywne odinstalowania"</string>
     <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"Nieudane odinstalowania"</string>
     <string name="uninstalling" msgid="8709566347688966845">"Odinstalowuję…"</string>
-    <string name="uninstalling_app" msgid="8866082646836981397">"Odinstalowuję pakiet <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>…"</string>
+    <string name="uninstalling_app" msgid="8866082646836981397">"Odinstalowuję <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>…"</string>
     <string name="uninstall_done" msgid="439354138387969269">"Odinstalowywanie zakończone."</string>
     <string name="uninstall_done_app" msgid="4588850984473605768">"Odinstalowano pakiet <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
     <string name="uninstall_failed" msgid="1847750968168364332">"Nie udało się odinstalować."</string>
diff --git a/packages/PackageInstaller/res/values-sl/strings.xml b/packages/PackageInstaller/res/values-sl/strings.xml
index a0702d6..c123d69 100644
--- a/packages/PackageInstaller/res/values-sl/strings.xml
+++ b/packages/PackageInstaller/res/values-sl/strings.xml
@@ -53,7 +53,7 @@
     <string name="uninstall_application_title" msgid="4045420072401428123">"Odstrani aplikacijo"</string>
     <string name="uninstall_update_title" msgid="824411791011583031">"Odstrani posodobitev"</string>
     <string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> je del te aplikacije:"</string>
-    <string name="uninstall_application_text" msgid="3816830743706143980">"Ali želite odstraniti to aplikacijo?"</string>
+    <string name="uninstall_application_text" msgid="3816830743706143980">"Ali želite odmestiti to aplikacijo?"</string>
     <string name="uninstall_application_text_all_users" msgid="575491774380227119">"Ali želite odstraniti aplikacijo za "<b>"vse"</b>" uporabnike? Aplikacija in njeni podatki bodo odstranjeni iz "<b>"vseh"</b>" uporabnikov v napravi."</string>
     <string name="uninstall_application_text_user" msgid="498072714173920526">"Ali želite to aplikacijo odstraniti za uporabnika <xliff:g id="USERNAME">%1$s</xliff:g>?"</string>
     <string name="uninstall_update_text" msgid="863648314632448705">"Želite to aplikacijo nadomestiti s tovarniško različico? Odstranjeni bodo vsi podatki."</string>
@@ -62,11 +62,11 @@
     <string name="uninstalling_notification_channel" msgid="840153394325714653">"Odstranitve v teku"</string>
     <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"Neuspele odstranitve"</string>
     <string name="uninstalling" msgid="8709566347688966845">"Odstranjevanje …"</string>
-    <string name="uninstalling_app" msgid="8866082646836981397">"Odstranjevanje aplikacije <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> …"</string>
+    <string name="uninstalling_app" msgid="8866082646836981397">"Odmeščanje aplikacije <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> …"</string>
     <string name="uninstall_done" msgid="439354138387969269">"Odstranitev je končana."</string>
     <string name="uninstall_done_app" msgid="4588850984473605768">"Aplikacija <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> je bila odstranjena"</string>
     <string name="uninstall_failed" msgid="1847750968168364332">"Odstranitev ni uspela."</string>
-    <string name="uninstall_failed_app" msgid="5506028705017601412">"Odstranjevanje aplikacije <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> ni uspelo."</string>
+    <string name="uninstall_failed_app" msgid="5506028705017601412">"Odmeščanje aplikacije <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> ni uspelo."</string>
     <string name="uninstall_failed_device_policy_manager" msgid="785293813665540305">"Aktivne skrbniške aplikacije naprave ni mogoče odstraniti"</string>
     <string name="uninstall_failed_device_policy_manager_of_user" msgid="4813104025494168064">"Aktivne skrbniške aplikacije za uporabnika <xliff:g id="USERNAME">%1$s</xliff:g> ni mogoče odstraniti"</string>
     <string name="uninstall_all_blocked_profile_owner" msgid="2009393666026751501">"Aplikacija je obvezna za nekatere uporabnike/profile in je odstranjena za druge."</string>
diff --git a/packages/SettingsLib/SearchWidget/res/values-pt-rPT/strings.xml b/packages/SettingsLib/SearchWidget/res/values-pt-rPT/strings.xml
index ddf838e..85a8d73 100644
--- a/packages/SettingsLib/SearchWidget/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/SearchWidget/res/values-pt-rPT/strings.xml
@@ -17,5 +17,5 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="search_menu" msgid="1914043873178389845">"Pesquisar definições"</string>
+    <string name="search_menu" msgid="1914043873178389845">"Pesquisa de definições"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-af/arrays.xml b/packages/SettingsLib/res/values-af/arrays.xml
index d9aaf7d..8a02c77 100644
--- a/packages/SettingsLib/res/values-af/arrays.xml
+++ b/packages/SettingsLib/res/values-af/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (verstek)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Gebruik stelselkeuse (verstek)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 2590338..2b143e4 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Aktiveer Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP-weergawe"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Kies Bluetooth AVRCP-weergawe"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP-weergawe"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Kies Bluetooth MAP-weergawe"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth-oudiokodek"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Gebruik Bluetooth-oudiokodek\nKeuse"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth-oudiovoorbeeldkoers"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Kontroleer programme wat via ADB/ADT geïnstalleer is vir skadelike gedrag."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Bluetooth-toestelle sonder name (net MAC-adresse) sal gewys word"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Deaktiveer die Bluetooth-kenmerk vir absolute volume indien daar volumeprobleme met afgeleë toestelle is, soos onaanvaarbare harde klank of geen beheer nie."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Aktiveer die Bluetooth Gabeldorsche-kenmerkstapel."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Aktiveer die Bluetooth Gabeldorsche-kenmerkstapel."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Plaaslike terminaal"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Aktiveer terminaalprogram wat plaaslike skermtoegang bied"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP-kontrolering"</string>
diff --git a/packages/SettingsLib/res/values-am/arrays.xml b/packages/SettingsLib/res/values-am/arrays.xml
index fff7cae3..6a9334e 100644
--- a/packages/SettingsLib/res/values-am/arrays.xml
+++ b/packages/SettingsLib/res/values-am/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (ነባሪ)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"የስርዓቱን ምርጫ (ነባሪ) ተጠቀም"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index 8277711..2a93e017 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorscheን አንቃ"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"የብሉቱዝ AVRCP ስሪት"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"የብሉቱዝ AVRCP ስሪት ይምረጡ"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"የብሉቱዝ MAP ስሪት"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"የብሉቱዝ MAP ስሪቱን ይምረጡ"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"የብሉቱዝ ኦዲዮ ኮዴክ"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"የብሉቱዝ ኦዲዮ ኮዴክ አስጀምር\nምርጫ"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"የብሉቱዝ ኦዲዮ ናሙና ፍጥነት"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"በADB/ADT በኩል የተጫኑ መተግበሪያዎች ጎጂ ባህሪ ካላቸው ያረጋግጡ።"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"የብሉቱዝ መሣሪያዎች ያለ ስሞች (MAC አድራሻዎች ብቻ) ይታያሉ"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"እንደ ተቀባይነት በሌለው ደረጃ ድምፁ ከፍ ማለት ወይም መቆጣጠር አለመቻል ያሉ ከሩቅ መሣሪያዎች ጋር የድምፅ ችግር በሚኖርበት ጊዜ የብሉቱዝ ፍጹማዊ ድምፅን ባሕሪ ያሰናክላል።"</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"የብሉቱዝ Gabeldorche ባህሪ ቁልልን ያነቃል።"</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"የብሉቱዝ Gabeldorsche ባህሪ ቁልሉን ያነቃል።"</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"አካባቢያዊ ተርሚናል"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"የአካባቢያዊ ሼል መዳረሻ የሚያቀርብ የተርሚናል መተግበሪያ አንቃ"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"የHDCP ምልከታ"</string>
diff --git a/packages/SettingsLib/res/values-ar/arrays.xml b/packages/SettingsLib/res/values-ar/arrays.xml
index b4f5253..851a3d8 100644
--- a/packages/SettingsLib/res/values-ar/arrays.xml
+++ b/packages/SettingsLib/res/values-ar/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"‏MAP 1.2 (الإعداد الافتراضي)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"استخدام اختيار النظام (تلقائي)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 43d2ece..5485656 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"‏تفعيل Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"‏إصدار Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"‏اختيار إصدار Bluetooth AVRCP"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"‏إصدار Bluetooth MAP"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"‏اختيار إصدار Bluetooth MAP"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"برنامج ترميز صوت بلوتوث"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"اختيار برنامج ترميز الصوت لمشغّل\nالبلوتوث"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"معدّل عيّنة صوت بلوتوث"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"‏التحقق من التطبيقات المثبتة عبر ADB/ADT لكشف السلوك الضار"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"‏سيتم عرض أجهزة البلوتوث بدون أسماء (عناوين MAC فقط)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"لإيقاف ميزة مستوى الصوت المطلق للبلوتوث في حال حدوث مشاكل متعلقة بمستوى الصوت في الأجهزة البعيدة، مثل مستوى صوت عالٍ بشكل غير مقبول أو عدم إمكانية التحكّم في الصوت"</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"‏تفعيل حِزم ميزة Bluetooth Gabeldorche"</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"‏تفعيل حِزم ميزة Bluetooth Gabeldorsche"</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"تطبيق طرفي محلي"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"تفعيل تطبيق طرفي يوفر إمكانية الدخول إلى واجهة النظام المحلية"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"‏التحقق من HDCP"</string>
@@ -435,7 +437,7 @@
     <string name="screen_zoom_summary_extremely_large" msgid="1438045624562358554">"أكبر مستوى"</string>
     <string name="screen_zoom_summary_custom" msgid="3468154096832912210">"مخصص (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string>
     <string name="content_description_menu_button" msgid="6254844309171779931">"القائمة"</string>
-    <string name="retail_demo_reset_message" msgid="5392824901108195463">"إدخال كلمة المرور لإعادة الضبط بحسب بيانات المصنع في الوضع التجريبي"</string>
+    <string name="retail_demo_reset_message" msgid="5392824901108195463">"إدخال كلمة المرور لإعادة الضبط على الإعدادات الأصلية في الوضع التجريبي"</string>
     <string name="retail_demo_reset_next" msgid="3688129033843885362">"التالي"</string>
     <string name="retail_demo_reset_title" msgid="1866911701095959800">"يلزم توفر كلمة مرور"</string>
     <string name="active_input_method_subtypes" msgid="4232680535471633046">"طرق الإدخال النشطة"</string>
diff --git a/packages/SettingsLib/res/values-as/arrays.xml b/packages/SettingsLib/res/values-as/arrays.xml
index 1a044f5..503c13e 100644
--- a/packages/SettingsLib/res/values-as/arrays.xml
+++ b/packages/SettingsLib/res/values-as/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp১৫"</item>
     <item msgid="1963366694959681026">"avrcp১৬"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (ডিফ’ল্ট)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"ছিষ্টেমৰ বাছনি ব্যৱহাৰ কৰক (ডিফ\'ল্ট)"</item>
     <item msgid="4055460186095649420">"এছবিচি"</item>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index 3f85fe3..fa26b9b 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche সক্ষম কৰক"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"ব্লুটুথ AVRCP সংস্কৰণ"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"ব্লুটুথ AVRCP সংস্কৰণ বাছনি কৰক"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"ব্লুটুথ MAP সংস্কৰণ"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"ব্লুটুথ MAP সংস্কৰণ বাছনি কৰক"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"ব্লুটুথ অডিঅ’ ক’ডেক"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"ব্লুটুথ অডিঅ\' ক\'ডেকৰ বাছনি\nআৰম্ভ কৰক"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"ব্লুটুথ অডিঅ\' ছেম্পল ৰেইট"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"ADB/ADTৰ যোগেৰে ইনষ্টল কৰা এপসমূহে কিবা ক্ষতিকাৰক আচৰণ কৰিছে নেকি পৰীক্ষা কৰক।"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"নামহীন ব্লুটুথ ডিভাইচসমূহ (মাত্ৰ MAC ঠিকনাযুক্ত) দেখুওৱা হ\'ব"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"ৰিম\'ট ডিভাইচবিলাকৰ সৈতে ভলিউম সম্পৰ্কীয় সমস্যা, যেনেকৈ অতি উচ্চ ভলিউম বা নিয়ন্ত্ৰণ কৰিবই নোৱাৰা অৱস্থাত ব্লুটুথৰ পূৰ্ণ ভলিউম সুবিধা অক্ষম কৰে।"</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"ব্লুটুথ Gabeldorche সুবিধাৰ সমষ্টিটো সক্ষম কৰে।"</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"ব্লুটুথ Gabeldorche সুবিধাৰ সমষ্টিটো সক্ষম কৰে।"</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"স্থানীয় টাৰ্মিনেল"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"স্থানীয় শ্বেল প্ৰৱেশাধিকাৰ দিয়া টাৰ্মিনেল এপ্ সক্ষম কৰক"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP পৰীক্ষণ"</string>
diff --git a/packages/SettingsLib/res/values-az/arrays.xml b/packages/SettingsLib/res/values-az/arrays.xml
index eb81381..005bdf7 100644
--- a/packages/SettingsLib/res/values-az/arrays.xml
+++ b/packages/SettingsLib/res/values-az/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (Defolt)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Sistem Seçimini istifadə edin (Defolt)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index 3a1a543..2e80fdc 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche\'ni aktiv edin"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP Versiya"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Bluetooth AVRCP Versiyasını seçin"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP Versiyası"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Bluetooth MAP Versiyasını seçin"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth Audio Kodek"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Bluetooth Audio KodeK\nSeçimini aktiv edin"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth Audio Nümunə Göstəricisi"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"ADB/ADT vasitəsi ilə quraşdırılmış tətbiqləri zərərli davranış üzrə yoxlayın."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Adsız Bluetooth cihazları (yalnız MAC ünvanları) göstəriləcək"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Uzaqdan idarə olunan cihazlarda dözülməz yüksək səs həcmi və ya nəzarət çatışmazlığı kimi səs problemləri olduqda Bluetooth mütləq səs həcmi xüsusiyyətini deaktiv edir."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Bluetooth Gabeldorche xüsusiyyətini aktiv edir."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Bluetooth Gabeldorsche funksiyasını aktiv edir."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Yerli terminal"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Yerli örtük girişini təklif edən terminal tətbiqi aktiv edin"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP yoxlanılır"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/arrays.xml b/packages/SettingsLib/res/values-b+sr+Latn/arrays.xml
index c7b63b3..10c0d6c 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/arrays.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (podrazumevano)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Koristi izbor sistema (podrazumevano)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 02e5e3b..ae5c936 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Omogući Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Verzija Bluetooth AVRCP-a"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Izaberite verziju Bluetooth AVRCP-a"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Verzija Bluetooth MAP-a"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Izaberite verziju Bluetooth MAP-a"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth audio kodek"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Izaberite Bluetooth audio kodek\n"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Brzina uzorkovanja za Bluetooth audio"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Proverava da li su aplikacije instalirane preko ADB-a/ADT-a štetne."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Biće prikazani Bluetooth uređaji bez naziva (samo sa MAC adresama)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Onemogućava glavno podešavanje jačine zvuka na Bluetooth uređaju u slučaju problema sa jačinom zvuka na daljinskim uređajima, kao što su izuzetno velika jačina zvuka ili nedostatak kontrole."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Omogućava grupu Bluetooth Gabeldorche funkcija."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Omogućava grupu Bluetooth Gabeldorsche funkcija."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Lokalni terminal"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Omogući apl. terminala za pristup lokalnom komandnom okruženju"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP provera"</string>
diff --git a/packages/SettingsLib/res/values-be/arrays.xml b/packages/SettingsLib/res/values-be/arrays.xml
index 9eaab6a..e05fd60 100644
--- a/packages/SettingsLib/res/values-be/arrays.xml
+++ b/packages/SettingsLib/res/values-be/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (стандартна)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Выбар сістэмы (стандартны)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index 481dfb0..ad201d0 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Уключыць Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Версія Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Выбраць версію Bluetooth AVRCP"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Версія Bluetooth MAP"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Выбраць версію Bluetooth MAP"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Кодэк Bluetooth Audio"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Уключыць кодэк Bluetooth Audio\nВыбар"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Частата дыскрэтызацыі Bluetooth Audio"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Праверка бяспекі праграм, усталяваных з дапамогай ADB/ADT."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Прылады Bluetooth будуць паказаны без назваў (толькі MAC-адрасы)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Адключыць функцыю абсалютнага гуку Bluetooth у выпадку праблем з гукам на аддаленых прыладах, напрыклад, пры непрымальна высокай гучнасці або адсутнасці кіравання."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Уключае стос функцый Bluetooth Gabeldorche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Уключае стос функцый Bluetooth Gabeldorsche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Лакальны тэрмінал"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Уключэнне прыкладання тэрмінала, якое прапануе доступ да лакальнай абалонкі"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"Праверка HDCP"</string>
diff --git a/packages/SettingsLib/res/values-bg/arrays.xml b/packages/SettingsLib/res/values-bg/arrays.xml
index e7976fc..a071baf 100644
--- a/packages/SettingsLib/res/values-bg/arrays.xml
+++ b/packages/SettingsLib/res/values-bg/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (по подразбиране)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Използване на сист. избор (стандартно)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index 2a02640..94f78ad 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Активиране на Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Версия на AVRCP за Bluetooth"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Избиране на версия на AVRCP за Bluetooth"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"MAP версия за Bluetooth"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Изберете MAP версия за Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Аудиокодек за Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Задействане на аудиокодек за Bluetooth\nИзбор"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Честота на дискретизация за звука през Bluetooth"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Проверка на инсталираните чрез ADB/ADT приложения за опасно поведение."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Ще бъдат показани устройствата с Bluetooth без имена (само MAC адресите)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Деактивира функцията на Bluetooth за пълна сила на звука в случай на проблеми със звука на отдалечени устройства, като например неприемливо висока сила на звука или липса на управление."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Активира стека на функциите на Bluetooth Gabeldorsche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Активира стека на функциите на Bluetooth Gabeldorsche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Локален терминал"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Актив. на прил. за терминал с достъп до локалния команден ред"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"Проверка с HDCP"</string>
diff --git a/packages/SettingsLib/res/values-bn/arrays.xml b/packages/SettingsLib/res/values-bn/arrays.xml
index a67b9eb..a131a3b 100644
--- a/packages/SettingsLib/res/values-bn/arrays.xml
+++ b/packages/SettingsLib/res/values-bn/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (ডিফল্ট)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"সিস্টেমের নির্বাচন ব্যবহার করুন (ডিফল্ট)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 9bebe41..51719160 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche ফিচার চালু করুন"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"ব্লুটুথ AVRCP ভার্সন"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"ব্লুটুথ AVRCP ভার্সন বেছে নিন"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"ব্লুটুথ MAP ভার্সন"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"ব্লুটুথ MAP ভার্সন বেছে নিন"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"ব্লুটুথ অডিও কোডেক"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"ব্লুটুথ অডিও কোডেক ট্রিগার করুন\nএটি বেছে নেওয়া আছে"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"ব্লুটুথ অডিওর নমুনা হার"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"ক্ষতিকারক ক্রিয়াকলাপ করছে কিনা তার জন্য ADB/ADT মারফত ইনস্টল করা অ্যাপ্লিকেশানগুলি চেক করুন।"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"নামহীন ব্লুটুথ ডিভাইসগুলি দেখানো হবে (শুধুমাত্র MAC অ্যাড্রেস)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"অপ্রত্যাশিত উচ্চ ভলিউম বা নিয়ন্ত্রণের অভাবের মত দূরবর্তী ডিভাইসের ভলিউম সমস্যাগুলির ক্ষেত্রে, ব্লুটুথ চুড়ান্ত ভলিউম বৈশিষ্ট্য অক্ষম করে৷"</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"ব্লুটুথ Gabeldorche ফিচার স্ট্যাক চালু করা হয়েছে।"</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"ব্লুটুথ Gabeldorche ফিচার স্ট্যাক চালু করে।"</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"স্থানীয় টার্মিনাল"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"স্থানীয় শেল অ্যাক্সেসের প্রস্তাব করে এমন টার্মিনাল অ্যাপ্লিকেশন সক্ষম করুন"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP পরীক্ষণ"</string>
diff --git a/packages/SettingsLib/res/values-bs/arrays.xml b/packages/SettingsLib/res/values-bs/arrays.xml
index d8b2bcd..6489cef 100644
--- a/packages/SettingsLib/res/values-bs/arrays.xml
+++ b/packages/SettingsLib/res/values-bs/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (Zadano)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Korištenje odabira sistema (zadano)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index f2446ad..f74bcee 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Omogući Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP verzija"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Odaberite Bluetooth AVRCP verziju"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP verzija"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Odaberite Bluetooth MAP verziju"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth Audio kodek"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Aktivirajte Bluetooth Audio Codec\nOdabir"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Brzina uzorkovanja za Bluetooth audio"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Provjerava da li se u aplikacijama instaliranim putem ADB-a/ADT-a javlja zlonamjerno ponašanje."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Prikazat će se Bluetooth uređaji bez naziva (samo MAC adrese)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Onemogućava funkciju apsolutne jačine zvuka za Bluetooth u slučaju problema s jačinom zvuka na udaljenim uređajima, kao što je neprihvatljivo glasan zvuk ili nedostatak kontrole."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Omogućava grupisanje funkcije Bluetooth Gabeldorsche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Omogućava grupisanje funkcije Bluetooth Gabeldorsche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Lokalni terminal"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Omogući terminalnu aplik. koja nudi pristup lok. kom. okruženju"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP provjera"</string>
diff --git a/packages/SettingsLib/res/values-ca/arrays.xml b/packages/SettingsLib/res/values-ca/arrays.xml
index 600a7ce..950e469 100644
--- a/packages/SettingsLib/res/values-ca/arrays.xml
+++ b/packages/SettingsLib/res/values-ca/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (predeterminada)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Utilitza selecció del sistema (predeterminada)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index e33fd29..1b23ecf 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Activa Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Versió AVRCP de Bluetooth"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Selecciona la versió AVRCP de Bluetooth"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Versió MAP de Bluetooth"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Selecciona la versió MAP de Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Còdec d\'àudio per Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Activa el còdec d\'àudio per Bluetooth\nSelecció"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Freqüència de mostratge d’àudio per Bluetooth"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Comprova les aplicacions instal·lades mitjançant ADB/ADT per detectar comportaments perillosos"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Es mostraran els dispositius Bluetooth sense el nom (només l\'adreça MAC)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Desactiva la funció de volum absolut del Bluetooth en cas que es produeixin problemes de volum amb dispositius remots, com ara un volum massa alt o una manca de control."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Activa el conjunt de funcions de Bluetooth Gabeldorsche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Activa el conjunt de funcions de Bluetooth Gabeldorsche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Terminal local"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Activa l\'aplicació de terminal que ofereix accés al shell local"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"Comprovació d\'HDCP"</string>
diff --git a/packages/SettingsLib/res/values-cs/arrays.xml b/packages/SettingsLib/res/values-cs/arrays.xml
index e7474a9..16358ee 100644
--- a/packages/SettingsLib/res/values-cs/arrays.xml
+++ b/packages/SettingsLib/res/values-cs/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (výchozí)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Použít systémový výběr (výchozí)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index e787510..22603cc 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Zapnout funkci Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Verze profilu Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Vyberte verzi profilu Bluetooth AVRCP"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Verze MAP pro Bluetooth"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Vyberte verzi MAP pro Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth Audio – kodek"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Spustit zvukový kodek Bluetooth\nVýběr"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth Audio – vzorkovací frekvence"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Kontrolovat škodlivost aplikací nainstalovaných pomocí nástroje ADB/ADT"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Zařízení Bluetooth se budou zobrazovat bez názvů (pouze adresy MAC)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Zakáže funkci absolutní hlasitosti Bluetooth. Zabrání tak problémům s hlasitostí vzdálených zařízení (jako je příliš vysoká hlasitost nebo nemožnost ovládání)."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Zapnout sadu funkcí Bluetooth Gabeldorche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Zapne sadu funkcí Bluetooth Gabeldorche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Místní terminál"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Aktivovat terminálovou aplikaci pro místní přístup k prostředí shell"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"Kontrola HDCP"</string>
diff --git a/packages/SettingsLib/res/values-da/arrays.xml b/packages/SettingsLib/res/values-da/arrays.xml
index 0394562..b3ce257 100644
--- a/packages/SettingsLib/res/values-da/arrays.xml
+++ b/packages/SettingsLib/res/values-da/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (standard)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Brug systemvalg (standard)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 4e36e38..bef1855 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Aktivér Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"AVRCP-version for Bluetooth"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Vælg AVRCP-version for Bluetooth"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"MAP-version for Bluetooth"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Vælg MAP-version for Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth-lydcodec"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Udløs codec for Bluetooth-lyd\nValg"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Eksempelfrekvens for Bluetooth-lyd"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Tjek apps, der er installeret via ADB/ADT, for skadelig adfærd."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Bluetooth-enheder uden navne (kun MAC-adresser) vises"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Deaktiverer funktionen til absolut lydstyrke via Bluetooth i tilfælde af problemer med lydstyrken på eksterne enheder, f.eks. uacceptabel høj lyd eller manglende kontrol."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Aktiverer funktioner fra Bluetooth Gabeldorche"</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Aktiverer funktioner fra Bluetooth Gabeldorsche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Lokal terminal"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Aktivér terminalappen, der giver lokal shell-adgang"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP-kontrol"</string>
diff --git a/packages/SettingsLib/res/values-de/arrays.xml b/packages/SettingsLib/res/values-de/arrays.xml
index d7d3226..fdd799c 100644
--- a/packages/SettingsLib/res/values-de/arrays.xml
+++ b/packages/SettingsLib/res/values-de/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (Standard)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Systemauswahl verwenden (Standard)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index 6369906..c8c97bd 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Bluetooth-Gabeldorsche aktivieren"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP-Version"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Bluetooth AVRCP-Version auswählen"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP-Version"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Bluetooth MAP-Version auswählen"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth-Audio-Codec"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Bluetooth-Audio-Codec auslösen\nAuswahl"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth-Audio-Abtastrate"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Über ADB/ADT installierte Apps werden auf schädliches Verhalten geprüft"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Bluetooth-Geräte werden ohne Namen und nur mit ihren MAC-Adressen angezeigt"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Deaktiviert die Funktion \"Absolute Lautstärkeregelung\" für Bluetooth-Geräte, falls auf Remote-Geräten Probleme mit der Lautstärke auftreten, wie beispielsweise übermäßig laute Wiedergabe oder fehlende Steuerungsmöglichkeiten."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Aktiviert das Bluetooth-Gabeldorsche-Funktionspaket."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Aktiviert das Bluetooth-Gabeldorsche-Funktionspaket."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Lokales Terminal"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Terminal-App mit Zugriff auf lokale Shell aktivieren"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP-Prüfung"</string>
diff --git a/packages/SettingsLib/res/values-el/arrays.xml b/packages/SettingsLib/res/values-el/arrays.xml
index 5c4ef13..79f631f 100644
--- a/packages/SettingsLib/res/values-el/arrays.xml
+++ b/packages/SettingsLib/res/values-el/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (Προεπιλογή)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Χρήση επιλογής συστήματος (Προεπιλογή)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index 194324f..371075c 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Ενεργοποίηση Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Έκδοση AVRCP Bluetooth"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Επιλογή έκδοσης AVRCP Bluetooth"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Έκδοση MAP Bluetooth"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Επιλογή έκδοσης MAP Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Κωδικοποιητής ήχου Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Ενεργοποίηση κωδικοποιητή ήχου Bluetooth\nΕπιλογή"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Ρυθμός δειγματοληψίας ήχου Bluetooth"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Έλεγχος εφαρμογών που έχουν εγκατασταθεί μέσω ADB/ADT για επιβλαβή συμπεριφορά."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Θα εμφανιστούν οι συσκευές Bluetooth χωρίς ονόματα (μόνο διευθύνσεις MAC)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Απενεργοποιεί τη δυνατότητα απόλυτης έντασης του Bluetooth σε περίπτωση προβλημάτων έντασης με απομακρυσμένες συσκευές, όπως όταν υπάρχει μη αποδεκτά υψηλή ένταση ή απουσία ελέγχου."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Ενεργοποιεί τη στοίβα λειτουργιών Bluetooth Gabeldorche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Ενεργοποιεί τη στοίβα λειτουργιών Bluetooth Gabeldorsche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Τοπική τερματική εφαρμογή"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Ενεργοπ.τερμ.εφαρμογής που προσφέρει πρόσβαση στο τοπικό κέλυφος"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"Έλεγχος HDCP"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/arrays.xml b/packages/SettingsLib/res/values-en-rAU/arrays.xml
index ae1fb78..97e598e 100644
--- a/packages/SettingsLib/res/values-en-rAU/arrays.xml
+++ b/packages/SettingsLib/res/values-en-rAU/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (Default)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Use system selection (default)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index 92ed8e5..b314d17 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Enable Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP version"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Select Bluetooth AVRCP Version"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP version"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Select Bluetooth MAP version"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth audio codec"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Trigger Bluetooth Audio Codec\nSelection"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth audio sample rate"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Check apps installed via ADB/ADT for harmful behaviour."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Bluetooth devices without names (MAC addresses only) will be displayed"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Disables the Bluetooth absolute volume feature in case of volume issues with remote devices such as unacceptably loud volume or lack of control."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Enables the Bluetooth Gabeldorsche feature stack."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Enables the Bluetooth Gabeldorsche feature stack."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Local terminal"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Enable terminal app that offers local shell access"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP checking"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/arrays.xml b/packages/SettingsLib/res/values-en-rCA/arrays.xml
index ae1fb78..97e598e 100644
--- a/packages/SettingsLib/res/values-en-rCA/arrays.xml
+++ b/packages/SettingsLib/res/values-en-rCA/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (Default)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Use system selection (default)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index 92ed8e5..b314d17 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Enable Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP version"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Select Bluetooth AVRCP Version"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP version"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Select Bluetooth MAP version"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth audio codec"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Trigger Bluetooth Audio Codec\nSelection"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth audio sample rate"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Check apps installed via ADB/ADT for harmful behaviour."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Bluetooth devices without names (MAC addresses only) will be displayed"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Disables the Bluetooth absolute volume feature in case of volume issues with remote devices such as unacceptably loud volume or lack of control."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Enables the Bluetooth Gabeldorsche feature stack."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Enables the Bluetooth Gabeldorsche feature stack."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Local terminal"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Enable terminal app that offers local shell access"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP checking"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/arrays.xml b/packages/SettingsLib/res/values-en-rGB/arrays.xml
index ae1fb78..97e598e 100644
--- a/packages/SettingsLib/res/values-en-rGB/arrays.xml
+++ b/packages/SettingsLib/res/values-en-rGB/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (Default)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Use system selection (default)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index 92ed8e5..b314d17 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Enable Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP version"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Select Bluetooth AVRCP Version"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP version"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Select Bluetooth MAP version"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth audio codec"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Trigger Bluetooth Audio Codec\nSelection"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth audio sample rate"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Check apps installed via ADB/ADT for harmful behaviour."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Bluetooth devices without names (MAC addresses only) will be displayed"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Disables the Bluetooth absolute volume feature in case of volume issues with remote devices such as unacceptably loud volume or lack of control."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Enables the Bluetooth Gabeldorsche feature stack."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Enables the Bluetooth Gabeldorsche feature stack."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Local terminal"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Enable terminal app that offers local shell access"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP checking"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/arrays.xml b/packages/SettingsLib/res/values-en-rIN/arrays.xml
index ae1fb78..97e598e 100644
--- a/packages/SettingsLib/res/values-en-rIN/arrays.xml
+++ b/packages/SettingsLib/res/values-en-rIN/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (Default)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Use system selection (default)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index 92ed8e5..b314d17 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Enable Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP version"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Select Bluetooth AVRCP Version"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP version"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Select Bluetooth MAP version"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth audio codec"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Trigger Bluetooth Audio Codec\nSelection"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth audio sample rate"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Check apps installed via ADB/ADT for harmful behaviour."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Bluetooth devices without names (MAC addresses only) will be displayed"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Disables the Bluetooth absolute volume feature in case of volume issues with remote devices such as unacceptably loud volume or lack of control."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Enables the Bluetooth Gabeldorsche feature stack."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Enables the Bluetooth Gabeldorsche feature stack."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Local terminal"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Enable terminal app that offers local shell access"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP checking"</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/arrays.xml b/packages/SettingsLib/res/values-en-rXC/arrays.xml
index af5d7f3..eca7c75 100644
--- a/packages/SettingsLib/res/values-en-rXC/arrays.xml
+++ b/packages/SettingsLib/res/values-en-rXC/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‏‎‏‎‎‎‎‏‏‎‎‎‏‎‎‏‏‏‎‎‏‏‎‎‏‎‎‏‏‏‎‏‏‎‏‏‎‏‎‎‏‏‎‏‏‏‏‏‎‎‎‏‎‏‎‎‏‎‏‎avrcp15‎‏‎‎‏‎"</item>
     <item msgid="1963366694959681026">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‏‏‎‎‏‏‏‏‏‏‎‏‎‎‎‏‏‏‏‎‎‏‏‎‏‏‎‏‎‏‎‎‎‎‏‏‏‎‎‏‏‏‏‎‎‎‏‏‏‎‎‎‎‎‎‎‏‎‎avrcp16‎‏‎‎‏‎"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‏‏‏‏‏‎‎‏‎‎‏‎‏‎‏‎‎‏‏‎‎‎‏‏‎‏‏‏‎‎‏‎‏‏‎‎‎‎‎‎‏‏‎‎‎‏‎‏‏‎‏‏‎MAP 1.2 (Default)‎‏‎‎‏‎"</item>
+    <item msgid="6817922176194686449">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‎‏‏‏‏‎‎‎‏‎‎‎‎‎‎‎‎‏‏‎‎‎‏‎‏‎‏‎‏‎‎‏‎‏‎‏‏‏‏‎‏‎‏‏‎‏‏‏‏‏‎‎‎‏‎MAP 1.3‎‏‎‎‏‎"</item>
+    <item msgid="3423518690032737851">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‏‏‏‏‎‎‎‎‎‏‎‏‏‎‎‏‎‎‎‎‎‎‏‏‏‎‎‎‎‏‎‏‎‎‏‎‏‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎‎‏‏‏‎‏‏‎MAP 1.4‎‏‎‎‏‎"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‎‎‎‎‎‏‎‏‎‎‏‏‎‏‎‏‏‎‎‏‎‏‎‏‎‏‏‎‎‏‏‏‎‏‎‎‎‏‏‏‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‎map12‎‏‎‎‏‎"</item>
+    <item msgid="7073042887003102964">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‏‎‎‎‎‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‏‏‏‎‏‎‎‎map13‎‏‎‎‏‎"</item>
+    <item msgid="8147982633566548515">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‎‏‏‎‏‏‏‎‎‏‏‎‎‎‎‏‎‎‏‎‎‎‎‏‏‎‏‏‏‎‎‏‎‎‏‎‎‏‏‎‎‏‎‎‎‏‎‎‎‏‏‎map14‎‏‎‎‏‎"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‏‎‏‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‎‎‎‎‏‏‏‎‏‎‎‏‎‎‏‏‏‎‎‎‏‎‏‏‏‎‎‎‎‏‏‎‏‎‏‏‏‎‏‏‎Use System Selection (Default)‎‏‎‎‏‎"</item>
     <item msgid="4055460186095649420">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‎‎‏‏‏‏‏‏‎‎‎‏‏‏‎‎‎‏‏‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‎‎‎‏‎‎‎‎‎‏‎‏‎‎‎‏‏‎‎‎SBC‎‏‎‎‏‎"</item>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index 9813efd..95944dc 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‎‎‏‏‎‏‏‎‎‎‏‏‎‏‎‎‏‏‏‎‏‎‏‎‎‏‎‏‏‎‎‎‏‏‎‏‏‎‎‏‏‎‏‎‏‏‎‎‎‎Enable Gabeldorsche‎‏‎‎‏‎"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‏‏‏‏‎‏‏‏‏‎‏‎‎‏‎‏‎‏‏‏‏‎‏‎‎‎‏‎‎‎‎‎‎‏‏‏‎‎‎‎‎‎‏‏‏‏‎‎‎‎‎‏‏‏‏‎‏‏‏‎Bluetooth AVRCP Version‎‏‎‎‏‎"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‎‎‏‎‏‏‏‎‏‏‏‏‎‎‎‏‏‏‏‏‏‏‎‎‎‎‎‎‎‏‎‎‎‏‎‎‎‏‏‎‎‎‎‏‎‏‏‎‎‎‎‎‏‎Select Bluetooth AVRCP Version‎‏‎‎‏‎"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‏‎‏‎‎‏‏‎‏‏‏‎‏‎‎‏‎‎‏‏‏‏‎‎‎‎‎‏‏‎‎‏‏‏‎‎‎‎‎‎‎‎‏‎‏‏‏‏‎‎‏‎‏‏‏‏‏‎Bluetooth MAP Version‎‏‎‎‏‎"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‏‎‎‏‎‏‎‏‏‎‎‏‎‎‏‎‏‏‏‏‎‎‎‏‎‏‎‎‏‎‏‏‏‏‎‏‎‎‎‎‎‎‎‎‏‎‏‎‎‎‎‏‏‎‏‏‎‎‎Select Bluetooth MAP Version‎‏‎‎‏‎"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‏‎‏‎‎‏‏‎‏‏‎‎‎‏‏‎‎‎‎‎‎‏‏‏‏‎‎‏‎‏‏‎‎‎‎‏‎‏‎‏‎‎‎‏‎‏‏‎‏‏‎‏‏‏‎‏‏‏‎‎Bluetooth Audio Codec‎‏‎‎‏‎"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‏‏‎‏‎‏‏‎‎‏‏‏‎‏‎‎‎‎‏‏‎‎‎‏‎‏‎‏‎‎‏‏‏‏‎‎‎‏‏‎‏‎‏‏‏‏‏‎‎‏‎‎‏‎Trigger Bluetooth Audio Codec‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎Selection‎‏‎‎‏‎"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‏‏‎‏‎‏‏‏‏‎‏‏‎‎‎‏‏‏‏‎‎‎‎‎‎‎‏‏‎‎‏‏‎‏‎‎‏‏‎‎‏‏‏‎‏‎‎‏‎‎‏‏‎‏‏‏‏‏‏‎Bluetooth Audio Sample Rate‎‏‎‎‏‎"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‎‏‎‎‏‎‎‏‎‏‎‎‎‏‏‏‏‎‏‏‏‎‎‎‎‎‎‎‏‏‎‏‎‏‎‎‎‎‎‎‏‏‏‎‏‎‎‎‏‎‎‏‏‎‏‏‏‏‏‎Check apps installed via ADB/ADT for harmful behavior.‎‏‎‎‏‎"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‎‏‎‏‏‎‏‎‏‏‎‏‎‎‎‏‎‏‎‏‏‏‏‎‎‎‏‎‏‎‎‏‏‎‎‏‏‏‎‏‏‏‏‎‎‏‎‏‏‏‎‎‎‏‎‏‎‏‏‎Bluetooth devices without names (MAC addresses only) will be displayed‎‏‎‎‏‎"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‏‏‏‏‎‏‎‏‏‏‏‏‎‏‏‎‎‎‎‏‎‎‎‎‎‎‏‏‎‏‏‏‏‎‎‏‎‎‎‏‏‏‎‎‎‎‎‎‏‏‎‎‏‎‎‎‎‏‎Disables the Bluetooth absolute volume feature in case of volume issues with remote devices such as unacceptably loud volume or lack of control.‎‏‎‎‏‎"</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‏‎‎‏‏‏‏‎‏‎‎‎‎‏‏‎‎‏‏‎‎‏‎‎‏‎‏‎‎‏‎‎‎‎‎‏‏‏‏‎‎‏‏‏‏‎‏‏‏‎‏‏‎‎Enables the Bluetooth Gabeldorche feature stack.‎‏‎‎‏‎"</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‏‎‎‏‎‎‎‎‎‏‏‏‏‎‏‏‏‏‎‎‏‎‏‏‎‎‏‏‎‏‎‏‏‏‎‎‏‏‎‏‎‎‏‏‏‏‏‎‎‏‎‎‎‏‏‎‏‎‏‎Enables the Bluetooth Gabeldorsche feature stack.‎‏‎‎‏‎"</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‏‎‏‎‎‏‏‎‏‏‏‏‏‏‎‏‎‎‏‏‎‏‎‏‏‏‎‎‎‏‎‏‎‏‏‎‎‎‏‏‏‏‏‎‎‎‎‏‏‏‎‏‎‏‎‎‏‏‎‎Local terminal‎‏‎‎‏‎"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‏‎‎‏‏‎‏‏‏‎‏‎‎‎‏‏‎‎‎‏‏‎‏‎‏‎‏‏‎‎‎‎‎‎‏‏‎‎‎‎‎‏‏‎‏‏‎‏‎‏‏‏‏‏‎‏‎‎‎Enable terminal app that offers local shell access‎‏‎‎‏‎"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‎‏‏‏‏‎‎‏‎‏‏‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‎‎‏‎‏‎‎‏‎‏‎‎‏‏‎‏‎‏‏‎‎‎‏‏‏‎‏‏‏‎‎‏‎‎HDCP checking‎‏‎‎‏‎"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/arrays.xml b/packages/SettingsLib/res/values-es-rUS/arrays.xml
index d37ffb7..ad58235 100644
--- a/packages/SettingsLib/res/values-es-rUS/arrays.xml
+++ b/packages/SettingsLib/res/values-es-rUS/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (predeterminado)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Usar selección del sistema (predeterminado)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 8899c07..c6dfdd3 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Habilitar Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Versión de AVRCP del Bluetooth"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Selecciona la versión de AVRCP del Bluetooth"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Versión de MAP de Bluetooth"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Seleccionar versión de MAP de Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Códec del audio Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Activar el códec de audio por Bluetooth\nSelección"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Frecuencia de muestreo del audio Bluetooth"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Comprobar que las aplicaciones instaladas mediante ADB/ADT no ocasionen daños"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Se mostrarán los dispositivos Bluetooth sin nombre (solo direcciones MAC)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Inhabilita la función de volumen absoluto de Bluetooth si se producen problemas de volumen con dispositivos remotos (por ejemplo, volumen demasiado alto o falta de control)."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Habilita la pila de funciones de Bluetooth Gabeldorsche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Habilita la pila de funciones de Bluetooth Gabeldorsche"</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Terminal local"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Habilitar aplicac. de terminal que ofrece acceso al shell local"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"Comprobación HDCP"</string>
diff --git a/packages/SettingsLib/res/values-es/arrays.xml b/packages/SettingsLib/res/values-es/arrays.xml
index 04d4b2d..a403e3e6 100644
--- a/packages/SettingsLib/res/values-es/arrays.xml
+++ b/packages/SettingsLib/res/values-es/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (predeterminado)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Usar preferencia del sistema (predeterminado)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index 83ae2df..ba4a9ff 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Habilitar Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Versión AVRCP de Bluetooth"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Selecciona la versión AVRCP de Bluetooth"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Versión de MAP de Bluetooth"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Seleccionar versión de MAP de Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Códec de audio de Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Activar el códec de audio por Bluetooth\nSelección"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Frecuencia de muestreo de audio de Bluetooth"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Comprobar las aplicaciones instaladas mediante ADB/ADT para detectar comportamientos dañinos"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Mostrar dispositivos Bluetooth sin nombre (solo direcciones MAC)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Inhabilitar la función de volumen absoluto de Bluetooth si se producen problemas de volumen con dispositivos remotos (por ejemplo, volumen demasiado alto o falta de control)"</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Habilita la pila de funciones de Bluetooth Gabeldorsche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Habilita la pila de funciones de Bluetooth Gabeldorsche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Terminal local"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Habilitar aplicación de terminal que ofrece acceso a shell local"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"Comprobación de HDCP"</string>
diff --git a/packages/SettingsLib/res/values-et/arrays.xml b/packages/SettingsLib/res/values-et/arrays.xml
index 14a68b0e..eb5f347 100644
--- a/packages/SettingsLib/res/values-et/arrays.xml
+++ b/packages/SettingsLib/res/values-et/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (vaikeseade)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Süsteemi valiku kasutamine (vaikeseade)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index 297e965..df3b792 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Luba Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetoothi AVRCP versioon"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Valige Bluetoothi AVRCP versioon"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetoothi MAP-i versioon"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Bluetoothi MAP-i versiooni valimine"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetoothi heli kodek"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Bluetoothi helikodeki käivitamine\nValik"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetoothi heli diskreetimissagedus"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Kontrolli, kas ADB/ADT-ga installitud rakendused on ohtlikud."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Kuvatakse ilma nimedeta (ainult MAC-aadressidega) Bluetoothi seadmed"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Keelatakse Bluetoothi absoluutse helitugevuse funktsioon, kui kaugseadmetega on helitugevuse probleeme (nt liiga vali heli või juhitavuse puudumine)."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Lubab Bluetoothi Gabeldorche\'i funktsiooni virnastamise."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Lubab Bluetooth Gabeldorsche\'i funktsiooni virnastamise."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Kohalik terminal"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Luba kohalikku turvalist juurdepääsu pakkuv terminalirakendus"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP-kontrollimine"</string>
diff --git a/packages/SettingsLib/res/values-eu/arrays.xml b/packages/SettingsLib/res/values-eu/arrays.xml
index e87413c..30ac525f7 100644
--- a/packages/SettingsLib/res/values-eu/arrays.xml
+++ b/packages/SettingsLib/res/values-eu/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"1.2 MAPa (lehenetsia)"</item>
+    <item msgid="6817922176194686449">"1.3 MAPa"</item>
+    <item msgid="3423518690032737851">"1.4 MAPa"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Erabili sistema-hautapena (lehenetsia)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 5b84b10..fcb320f 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gaitu Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP bertsioa"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Hautatu Bluetooth AVRCP bertsioa"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAParen bertsioa"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Hautatu Bluetooth MAParen bertsioa"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth bidezko audioaren kodeka"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Abiarazi Bluetooth bidezko audio-kodeka\nHautapena"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth bidezko audioaren lagin-abiadura"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Egiaztatu ADB/ADT bidez instalatutako aplikazioak portaera kaltegarriak atzemateko"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Bluetooth gailuak izenik gabe (MAC helbideak soilik) erakutsiko dira"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Bluetooth bidezko bolumen absolutuaren eginbidea desgaitu egiten du urruneko gailuetan arazoak hautematen badira; esaterako, bolumena ozenegia bada edo ezin bada kontrolatu"</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Bluetooth Gabeldorsche eginbide sorta gaitzen du."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Bluetooth Gabeldorsche eginbide sorta gaitzen du."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Tokiko terminala"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Gaitu tokiko shell-sarbidea duen terminal-aplikazioa"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP egiaztapena"</string>
diff --git a/packages/SettingsLib/res/values-fa/arrays.xml b/packages/SettingsLib/res/values-fa/arrays.xml
index ba56d24..3b05075 100644
--- a/packages/SettingsLib/res/values-fa/arrays.xml
+++ b/packages/SettingsLib/res/values-fa/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"‏MAP نسخه ۱.۲ (پیش‌فرض)"</item>
+    <item msgid="6817922176194686449">"‏MAP نسخه ۱.۳"</item>
+    <item msgid="3423518690032737851">"‏MAP نسخه ۱.۴"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"استفاده از انتخاب سیستم (پیش‌فرض)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index 61fe37a..261a438 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"‏فعال کردن Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"‏نسخه AVRCP بلوتوث"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"‏انتخاب نسخه AVRCP بلوتوث"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"‏نسخه MAP بلوتوث"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"‏انتخاب نسخه MAP بلوتوث"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"کدک بلوتوث صوتی"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"راه‌اندازی کدک صوتی بلوتوثی\nانتخاب"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"سرعت نمونه بلوتوث صوتی"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"‏برنامه‌های نصب‌شده ازطریق ADB/ADT را ازنظر رفتار مخاطره‌آمیز بررسی کنید."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"‏دستگاه‌های بلوتوث بدون نام (فقط نشانی‌های MAC) نشان داده خواهند شد"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"درصورت وجود مشکل در صدا با دستگاه‌های راه دور مثل صدای بلند ناخوشایند یا عدم کنترل صدا، ویژگی میزان صدای کامل بلوتوث را غیرفعال کنید."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"‏دسته ویژگی Gabeldorche، بلوتوث را فعال می‌کند."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"‏دسته ویژگی Gabeldorsche، بلوتوث را فعال می‌کند."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"ترمینال محلی"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"فعال کردن ترمینال برنامه‌ کاربردی که دسترسی به برنامه محلی را پیشنهاد می‌کند"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"‏بررسی HDCP"</string>
diff --git a/packages/SettingsLib/res/values-fi/arrays.xml b/packages/SettingsLib/res/values-fi/arrays.xml
index 79a46ba..c899d9c 100644
--- a/packages/SettingsLib/res/values-fi/arrays.xml
+++ b/packages/SettingsLib/res/values-fi/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (oletus)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Käytä järjestelmän valintaa (oletus)."</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 7e8b160..4ccf430 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Ota Gabeldorsche käyttöön"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetoothin AVRCP-versio"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Valitse Bluetoothin AVRCP-versio"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetoothin MAP-versio"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Valitse Bluetoothin MAP-versio"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth-äänen koodekki"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Käynnistä Bluetooth-äänipakkaus\nValinta"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth-ääninäytteen siirtonopeus"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Tarkista ADB:n/ADT:n kautta asennetut sovellukset haitallisen toiminnan varalta."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Näytetään Bluetooth-laitteet, joilla ei ole nimiä (vain MAC-osoitteet)."</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Bluetoothin yleinen äänenvoimakkuuden säätö poistetaan käytöstä ongelmien välttämiseksi esimerkiksi silloin, kun laitteen äänenvoimakkuus on liian kova tai sitä ei voi säätää."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Bluetoothin Gabeldorche-ominaisuuspino tulee käyttöön."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Bluetoothin Gabeldorsche-ominaisuuspino otetaan käyttöön."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Paikallinen pääte"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Ota käyttöön päätesov. joka mahdollistaa paikall. liittymäkäytön"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP-tarkistus"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/arrays.xml b/packages/SettingsLib/res/values-fr-rCA/arrays.xml
index 3ee3209..02b374a 100644
--- a/packages/SettingsLib/res/values-fr-rCA/arrays.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (valeur par défaut)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Utiliser sélect. du système (par défaut)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index d21eeef..84a9797 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Activer le Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Version du profil Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Sélectionner la version du profil Bluetooth AVRCP"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Version du profil Bluetooth MAP"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Sélectionner la version du profil Bluetooth MAP"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Codec audio Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Déclencher le codec audio Bluetooth\nSélection"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Taux d\'échantillonnage pour l\'audio Bluetooth"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Vérifier que les applications installées par ADB/ADT ne présentent pas de comportement dangereux."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Les appareils Bluetooth sans nom (adresses MAC seulement) seront affichés"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Désactive la fonctionnalité de volume absolu par Bluetooth en cas de problème de volume sur les appareils à distance, par exemple si le volume est trop élevé ou s\'il ne peut pas être contrôlé."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Active la pile de la fonctionnalité Bluetooth Gabeldorche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Active la pile de la fonctionnalité Bluetooth Gabeldorsche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Terminal local"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Activer l\'application Terminal permettant l\'accès au shell local"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"Vérification HDCP"</string>
diff --git a/packages/SettingsLib/res/values-fr/arrays.xml b/packages/SettingsLib/res/values-fr/arrays.xml
index e23fc6a..9ccaf09 100644
--- a/packages/SettingsLib/res/values-fr/arrays.xml
+++ b/packages/SettingsLib/res/values-fr/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (valeur par défaut)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Utiliser la sélection du système (par défaut)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index 23dd5a8..030a7f9 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Activer Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Version Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Sélectionner la version Bluetooth AVRCP"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Version Bluetooth MAP"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Sélectionner la version Bluetooth MAP"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Codec audio Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Critère pour déclencher la sélection du codec audio\nBluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Taux d\'échantillonnage audio Bluetooth"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Vérifier que les applications installées par ADB/ADT ne présentent pas de comportement dangereux"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Les appareils Bluetooth sans nom (adresses MAC seulement) seront affichés"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Désactive la fonctionnalité de volume absolu du Bluetooth en cas de problème de volume sur les appareils à distance, par exemple si le volume est trop élevé ou s\'il ne peut pas être contrôlé"</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Active la pile de fonctionnalités Bluetooth Gabeldorsche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Active la pile de fonctionnalités Bluetooth Gabeldorsche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Terminal local"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Activer l\'application Terminal permettant l\'accès au shell local"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"Vérification HDCP"</string>
diff --git a/packages/SettingsLib/res/values-gl/arrays.xml b/packages/SettingsLib/res/values-gl/arrays.xml
index c0a5a80..5fad943 100644
--- a/packages/SettingsLib/res/values-gl/arrays.xml
+++ b/packages/SettingsLib/res/values-gl/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (predeterminada)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Usar selección do sistema (predeterminado)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index c8f6f81..af033cf 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Activar Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Versión de Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Selecciona a versión de Bluetooth AVRCP"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Versión de MAP de Bluetooth"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Selecciona a versión de MAP de Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Códec de audio por Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Activar códec de audio por Bluetooth\nSelección"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Taxa de mostra de audio por Bluetooth"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Comproba as aplicacións instaladas a través de ADB/ADT para detectar comportamento perigoso"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Mostraranse dispositivos Bluetooth sen nomes (só enderezos MAC)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Desactiva a función do volume absoluto do Bluetooth en caso de que se produzan problemas de volume cos dispositivos remotos, como volume demasiado alto ou falta de control"</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Activa a pilla de funcións Bluetooth Gabeldorche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Activa o conxunto de funcións de Bluetooth Gabeldorsche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Terminal local"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Activa a aplicación terminal que ofrece acceso ao shell local"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"Comprobación HDCP"</string>
diff --git a/packages/SettingsLib/res/values-gu/arrays.xml b/packages/SettingsLib/res/values-gu/arrays.xml
index 75c904d..8e28b8b 100644
--- a/packages/SettingsLib/res/values-gu/arrays.xml
+++ b/packages/SettingsLib/res/values-gu/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (ડિફૉલ્ટ)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"સિસ્ટમ પસંદગીનો ઉપયોગ કરો (ડિફૉલ્ટ)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index d274674..87fd876 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche ચાલુ કરો"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"બ્લૂટૂથ AVRCP સંસ્કરણ"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"બ્લૂટૂથ AVRCP સંસ્કરણ પસંદ કરો"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"બ્લૂટૂથ MAP વર્ઝન"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"બ્લૂટૂથ MAP વર્ઝન પસંદ કરો"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"બ્લૂટૂથ ઑડિઓ કોડેક"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"બ્લૂટૂથ ઑડિઓ કોડેક\nપસંદગી ટ્રિગર કરો"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"બ્લૂટૂથ ઑડિઓ નમૂના દર"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"હાનિકારક વર્તણૂંક માટે ADB/ADT મારફતે ઇન્સ્ટોલ કરવામાં આવેલી ઍપ્લિકેશનો તપાસો."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"નામ વગરના (ફક્ત MAC ઍડ્રેસવાળા) બ્લૂટૂથ ઉપકરણો બતાવવામાં આવશે"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"રિમોટ ઉપકરણોમાં વધુ પડતું ઊંચું વૉલ્યૂમ અથવા નિયંત્રણની કમી જેવી વૉલ્યૂમની સમસ્યાઓની સ્થિતિમાં બ્લૂટૂથ ચોક્કસ વૉલ્યૂમ સુવિધાને અક્ષમ કરે છે."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"બ્લૂટૂથ Gabeldorche સુવિધા સ્ટૅક ચાલુ કરે છે."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"બ્લૂટૂથ Gabeldorsche સુવિધાનું સ્ટૅક ચાલુ કરે છે."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"સ્થાનિક ટર્મિનલ"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"સ્થાનિક શેલ અ‍ૅક્સેસની ઑફર કરતી ટર્મિનલ એપ્લિકેશનને સક્ષમ કરો"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP તપાસણી"</string>
diff --git a/packages/SettingsLib/res/values-hi/arrays.xml b/packages/SettingsLib/res/values-hi/arrays.xml
index ffaf80a..3c744e1 100644
--- a/packages/SettingsLib/res/values-hi/arrays.xml
+++ b/packages/SettingsLib/res/values-hi/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (डिफ़ॉल्ट)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"सिस्टम से चुने जाने का उपयोग करें (डिफ़ॉल्ट)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index d0307ec..02b5c96 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche चालू करें"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"ब्लूटूथ एवीआरसीपी वर्शन"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"ब्लूटूथ AVRCP वर्शन चुनें"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"ब्लूटूथ का MAP वर्शन"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"ब्लूटूथ का MAP वर्शन चुनें"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"ब्लूटूथ ऑडियो कोडेक"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"ब्लूटूथ ऑडियो कोडेक का\nविकल्प चालू करें"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"ब्लूटूथ ऑडियो नमूना दर"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"नुकसानदेह व्यवहार के लिए ADB/ADT से इंस्टॉल किए गए ऐप्लिकेशन जाँचें."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"बिना नाम वाले ब्लूटूथ डिवाइस (केवल MAC पते वाले) दिखाए जाएंगे"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"दूर के डिवाइस पर आवाज़ बहुत बढ़ जाने या उससे नियंत्रण हटने जैसी समस्याएं होने पर, यह ब्लूटूथ के ज़रिए आवाज़ के नियंत्रण की सुविधा रोक देता है."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"ब्लूटूथ Gabeldorsche सुविधा का स्टैक चालू करें."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"ब्लूटूथ सेटिंग में Gabeldorsche सुविधा को चालू करता है."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"स्थानीय टर्मिनल"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"लोकल शेल तक पहुंचने की सुविधा देने वाले टर्मिनल ऐप को चालू करें"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"एचडीसीपी जाँच"</string>
diff --git a/packages/SettingsLib/res/values-hr/arrays.xml b/packages/SettingsLib/res/values-hr/arrays.xml
index ec979f6..c573e6c 100644
--- a/packages/SettingsLib/res/values-hr/arrays.xml
+++ b/packages/SettingsLib/res/values-hr/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (zadano)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Upotreba odabira sustava (zadano)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index 84adcaa..598cfe2 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Omogući Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Verzija AVRCP-a za Bluetooth"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Odaberite verziju AVRCP-a za Bluetooth"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Verzija MAP-a za Bluetooth"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Odaberite verziju MAP-a za Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Kodek za Bluetooth Audio"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Pokreni odabir kodeka za Bluetooth\nAudio"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Brzina uzorka za Bluetooth Audio"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Provjerite uzrokuju li aplikacije instalirane putem ADB-a/ADT-a poteškoće"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Prikazivat će se Bluetooth uređaji bez naziva (samo MAC adrese)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Onemogućuje Bluetoothovu značajku apsolutne glasnoće ako udaljeni uređaji imaju poteškoća sa zvukom, kao što su neprihvatljiva glasnoća ili nepostojanje kontrole"</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Omogućuje nizove značajke Bluetooth Gabeldorche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Omogućuje nizove značajke Bluetooth Gabeldorsche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Lokalni terminal"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Omogući aplikaciju terminala koja nudi pristup lokalnoj ovojnici"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP provjera"</string>
diff --git a/packages/SettingsLib/res/values-hu/arrays.xml b/packages/SettingsLib/res/values-hu/arrays.xml
index 64d92e4..608a9e03 100644
--- a/packages/SettingsLib/res/values-hu/arrays.xml
+++ b/packages/SettingsLib/res/values-hu/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (Alapértelmezett)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Rendszerérték (alapértelmezett)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index cb87e56..d970c73 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"A Gabeldorsche engedélyezése"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"A Bluetooth AVRCP-verziója"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"A Bluetooth AVRCP-verziójának kiválasztása"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"A Bluetooth MAP-verziója"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"A Bluetooth MAP-verziójának kiválasztása"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth hang – Kodek"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Bluetooth-hangkodek aktiválása\nKiválasztás"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth hang – mintavételezési gyakoriság"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Az ADB/ADT útján telepített alkalmazások ellenőrzése kártékony viselkedésre."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Név nélküli Bluetooth-eszközök jelennek meg (csak MAC-címekkel)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Letiltja a Bluetooth abszolút hangerő funkcióját a távoli eszközökkel kapcsolatos hangerőproblémák – például elfogadhatatlanul magas vagy nem vezérelhető hangerő – esetén."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Engedélyezi a Bluetooth Gabeldorche funkcióit."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Engedélyezi a Bluetooth Gabeldorsche funkcióit."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Helyi végpont"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Végalkalmazás engedélyezése a helyi rendszerhéj eléréséhez"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP ellenőrzés"</string>
diff --git a/packages/SettingsLib/res/values-hy/arrays.xml b/packages/SettingsLib/res/values-hy/arrays.xml
index b1226f4..a2de6df 100644
--- a/packages/SettingsLib/res/values-hy/arrays.xml
+++ b/packages/SettingsLib/res/values-hy/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (կանխադրված)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Օգտագործել համակարգի կարգավորումը (կանխադրված)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 6c30d4e..9086934 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Միացնել Gabeldorsche-ը"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP տարբերակը"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Ընտրել Bluetooth AVRCP տարբերակը"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP-ի տարբերակ"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Ընտրել Bluetooth MAP-ի տարբերակը"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth աուդիո կոդեկ"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Գործարկել Bluetooth աուդիո կոդեկը\nԸնտրություն"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth աուդիոյի ընդհատավորման հաճախականությունը"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Ստուգել հավելվածների անվտանգությունը ADB/ADT-ի միջոցով տեղադրված լինելու դեպքում։"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Bluetooth սարքերը կցուցադրվեն առանց անունների (միայն MAC հասցեները)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Կասեցնում է Bluetooth-ի ձայնի բացարձակ ուժգնության գործառույթը՝ հեռավոր սարքերի հետ ձայնի ուժգնությանը վերաբերող խնդիրներ ունենալու դեպքում (օրինակ՝ երբ ձայնի ուժգնությունն անընդունելի է կամ դրա կառավարումը հնարավոր չէ):"</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Միացնել Bluetooth Gabeldorche գործառույթի զտիչը"</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Միացնել Bluetooth Gabeldorsche գործառույթի զտիչը"</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Տեղային տերմինալ"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Միացնել տերմինալային հավելվածը, որն առաջարկում է մուտք տեղային խեցի"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP ստուգում"</string>
diff --git a/packages/SettingsLib/res/values-in/arrays.xml b/packages/SettingsLib/res/values-in/arrays.xml
index 9d2528a..e73febc 100644
--- a/packages/SettingsLib/res/values-in/arrays.xml
+++ b/packages/SettingsLib/res/values-in/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (Default)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Gunakan Pilihan Sistem (Default)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 654c06c..3c1504c 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Aktifkan Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Versi AVRCP Bluetooth"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Pilih Versi AVRCP Bluetooth"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Versi MAP Bluetooth"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Pilih Versi Map Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Codec Audio Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Aktifkan Codec Audio Bluetooth\nPilihan"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Frekuensi Sampel Audio Bluetooth"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Periksa perilaku membahayakan dalam aplikasi yang terpasang melalui ADB/ADT."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Perangkat Bluetooth tanpa nama (hanya alamat MAC) akan ditampilkan"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Menonaktifkan fitur volume absolut Bluetooth jika ada masalah volume dengan perangkat jarak jauh, misalnya volume terlalu keras atau kurangnya kontrol."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Aktifkan stack fitur Gabeldorche Bluetooth."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Mengaktifkan stack fitur Gabeldorsche Bluetooth."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Terminal lokal"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Aktifkan aplikasi terminal yang menawarkan akses kerangka lokal"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"Pemeriksaan HDCP"</string>
diff --git a/packages/SettingsLib/res/values-is/arrays.xml b/packages/SettingsLib/res/values-is/arrays.xml
index 1ac19f1..07b2ef1 100644
--- a/packages/SettingsLib/res/values-is/arrays.xml
+++ b/packages/SettingsLib/res/values-is/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (sjálfgefið)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Nota val kerfisins (sjálfgefið)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index 8662615..438e900 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Virkja Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP-útgáfa"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Velja Bluetooth AVRCP-útgáfu"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP-útgáfa"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Veldu Bluetooth MAP-útgáfu"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth hljóðkóðari"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Virkja Bluetooth-hljóðkóðara\nVal"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth hljóðtökutíðni"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Kanna skaðlega hegðun forrita sem sett eru upp frá ADB/ADT."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Bluetooth-tæki án heita (aðeins MAC-vistfang) verða birt"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Slekkur á samstillingu Bluetooth-hljóðstyrks ef vandamál koma upp með hljóðstyrk hjá fjartengdum tækjum, svo sem of hár hljóðstyrkur eða erfiðleikar við stjórnun."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Virkjar Bluetooth Gabeldorche eiginleikastafla."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Kveikir á eiginleikastafla Bluetooth Gabeldorsche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Staðbundin skipanalína"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Virkja skipanalínuforrit sem leyfir staðbundinn skeljaraðgang"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP-athugun"</string>
diff --git a/packages/SettingsLib/res/values-it/arrays.xml b/packages/SettingsLib/res/values-it/arrays.xml
index 44c519b..0bca8ea 100644
--- a/packages/SettingsLib/res/values-it/arrays.xml
+++ b/packages/SettingsLib/res/values-it/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (versione predefinita)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Usa selezione di sistema (predefinita)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 0e98d98..114b33b 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Attiva Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Versione Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Seleziona versione Bluetooth AVRCP"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Versione Bluetooth MAP"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Seleziona versione Bluetooth MAP"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Codec audio Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Attiva il codec audio Bluetooth\nSelezione"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Frequenza campionamento audio Bluetooth"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Controlla che le app installate tramite ADB/ADT non abbiano un comportamento dannoso"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Verranno mostrati solo dispositivi Bluetooth senza nome (solo indirizzo MAC)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Disattiva la funzione del volume assoluto Bluetooth in caso di problemi con il volume dei dispositivi remoti, ad esempio un volume troppo alto o la mancanza di controllo"</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Consente di attivare lo stack delle funzionalità Bluetooth Gabeldorche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Consente di attivare lo stack delle funzionalità Bluetooth Gabeldorsche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Terminale locale"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Abilita l\'app Terminale che offre l\'accesso alla shell locale"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"Verifica HDCP"</string>
diff --git a/packages/SettingsLib/res/values-iw/arrays.xml b/packages/SettingsLib/res/values-iw/arrays.xml
index c8fc6d3..2f7f310 100644
--- a/packages/SettingsLib/res/values-iw/arrays.xml
+++ b/packages/SettingsLib/res/values-iw/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"‏MAP 1.2 (ברירת מחדל)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"שימוש בבחירת המערכת (ברירת המחדל)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 30a6295..62085a8 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"‏הפעלת Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"‏Bluetooth גרסה AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"‏בחר Bluetooth גרסה AVRCP"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"‏גרסת Bluetooth MAP"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"‏יש לבחור גרסה של Bluetooth MAP"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"‏Codec אודיו ל-Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"‏הפעלת ‏Codec אודיו ל-Bluetooth\nבחירה"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"‏קצב דגימה של אודיו ל-Bluetooth"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"‏בדוק אפליקציות שהותקנו באמצעות ADB/ADT לאיתור התנהגות מזיקה."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"‏יוצגו מכשירי Bluetooth ללא שמות (כתובות MAC בלבד)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"‏משבית את תכונת עוצמת הקול המוחלטת ב-Bluetooth במקרה של בעיות בעוצמת הקול במכשירים מרוחקים, כגון עוצמת קול רמה מדי או חוסר שליטה ברמת העוצמה."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"‏הפעלת מקבץ הפיצ\'רים של Bluetooth Gabeldorche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"‏הפעלת מקבץ הפיצ\'רים של Bluetooth Gabeldorsche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"מסוף מקומי"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"הפעל אפליקציית מסוף המציעה גישה מקומית למעטפת"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"‏בדיקת HDCP"</string>
diff --git a/packages/SettingsLib/res/values-ja/arrays.xml b/packages/SettingsLib/res/values-ja/arrays.xml
index fdc68c6..2966f09 100644
--- a/packages/SettingsLib/res/values-ja/arrays.xml
+++ b/packages/SettingsLib/res/values-ja/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2(デフォルト)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"システムの選択(デフォルト)を使用"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index 22efe44..28b98ee 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche を有効にする"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP バージョン"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Bluetooth AVRCP バージョンを選択する"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP バージョン"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Bluetooth MAP バージョンの選択"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth オーディオ コーデック"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Bluetooth オーディオ コーデックを起動\n選択"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth オーディオ サンプルレート"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"ADB/ADT経由でインストールされたアプリに不正な動作がないかを確認する"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Bluetooth デバイスを名前なしで(MAC アドレスのみで)表示します"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"リモートデバイスで音量に関する問題(音量が大きすぎる、制御できないなど)が発生した場合に、Bluetooth の絶対音量の機能を無効にする"</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Bluetooth Gabeldorche 機能スタックを有効にします。"</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Bluetooth Gabeldorsche 機能スタックを有効にします。"</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"ローカルターミナル"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"ローカルシェルアクセスを提供するターミナルアプリを有効にします"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCPチェック"</string>
diff --git a/packages/SettingsLib/res/values-ka/arrays.xml b/packages/SettingsLib/res/values-ka/arrays.xml
index dda3b07..5a86eae 100644
--- a/packages/SettingsLib/res/values-ka/arrays.xml
+++ b/packages/SettingsLib/res/values-ka/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (ნაგულისხმევი)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"სისტემის არჩეულის გამოყენება (ნაგულისხმევი)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index 0139f58..6f5d0b3 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche-ის ჩართვა"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth-ის AVRCP-ის ვერსია"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"აირჩიეთ Bluetooth-ის AVRCP-ის ვერსია"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP-ის ვერსია"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"აირჩიეთ Bluetooth MAP-ის ვერსია"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth აუდიოს კოდეკი"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Bluetooth-ის აუდიო კოდეკის გაშვება\nარჩევანი"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth აუდიოს დისკრეტიზაციის სიხშირე"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"შეამოწმეთ, რამდენად უსაფრთხოა ADB/ADT-ის საშუალებით ინსტალირებული აპლიკაციები."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Bluetooth-მოწყობილობები ნაჩვენები იქნება სახელების გარეშე (მხოლოდ MAC-მისამართები)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"გათიშავს Bluetooth-ის ხმის აბსოლუტური სიძლიერის ფუნქციას დისტანციურ მოწყობილობებზე ხმასთან დაკავშირებული ისეთი პრობლემების არსებობის შემთხვევაში, როგორიცაა ხმის დაუშვებლად მაღალი სიძლიერე ან კონტროლის შეუძლებლობა."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"ჩართავს Bluetooth Gabeldorche-ის ფუნქციების დასტას."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"ჩართავს Bluetooth Gabeldorsche-ის ფუნქციების დასტას."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"ადგილობრივი ტერმინალი"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"ლოკალურ გარსზე წვდომის ტერმინალური აპლიკაციის ჩართვა"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP შემოწმება"</string>
diff --git a/packages/SettingsLib/res/values-kk/arrays.xml b/packages/SettingsLib/res/values-kk/arrays.xml
index 70119c8..3c96f43 100644
--- a/packages/SettingsLib/res/values-kk/arrays.xml
+++ b/packages/SettingsLib/res/values-kk/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (әдепкі)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Жүйенің таңдағанын алу (әдепкі)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index f7a773a..3fe426e 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche функциясын іске қосу"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP нұсқасы"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Bluetooth AVRCP нұсқасын таңдау"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP нұсқасы"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Bluetooth MAP нұсқасын таңдау"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth аудиокодегі"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Bluetooth аудиокодегін іске қосу\nТаңдау"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth арқылы дыбыс іріктеу жиілігі"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"ADB/ADT арқылы орнатылған қолданбалардың қауіпсіздігін тексеру."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Bluetooth құрылғылары атаусыз (тек MAC мекенжайымен) көрсетіледі"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Қашықтағы құрылғыларда дыбыстың тым қатты шығуы немесе реттеуге келмеуі сияқты дыбыс деңгейіне қатысты мәселелер туындағанда, Bluetooth абсолютті дыбыс деңгейі функциясын өшіреді."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Bluetooth Gabeldorche функциясын іске қосады."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Bluetooth Gabeldorsche функциясы стегін қосады."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Жергілікті терминал"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Жергілікті шелл-код қол жетімділігін ұсынатын терминалды қолданбаны қосу"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP тексеру"</string>
diff --git a/packages/SettingsLib/res/values-km/arrays.xml b/packages/SettingsLib/res/values-km/arrays.xml
index 327754b..24efd08 100644
--- a/packages/SettingsLib/res/values-km/arrays.xml
+++ b/packages/SettingsLib/res/values-km/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (លំនាំដើម)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"ប្រើ​ការ​ជ្រើសរើស​ប្រព័ន្ធ (លំនាំ​ដើម)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 5c64a40..24bfa35 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"បើក Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"កំណែប្ល៊ូធូស AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"ជ្រើសរើសកំណែប្ល៊ូធូស AVRCP"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"កំណែ​ប៊្លូធូស MAP"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"ជ្រើសរើស​កំណែ​ប្ល៊ូធូស MAP"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"កូឌិក​សំឡេង​ប៊្លូធូស"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"ជំរុញ​ការជ្រើសរើស​កូឌិក​សំឡេង​\n​ប៊្លូធូស"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"អត្រា​គំរូ​សំឡេង​ប៊្លូធូស"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"ពិនិត្យ​កម្មវិធី​បាន​ដំឡើង​តាម​រយៈ ADB/ADT សម្រាប់​ឥរិយាបថ​ដែល​គ្រោះ​ថ្នាក់។"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"​ឧបករណ៍​ប្ល៊ូធូសគ្មានឈ្មោះ​ (អាសយដ្ឋាន MAC តែប៉ុណ្ណោះ) នឹង​បង្ហាញ"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"បិទមុខងារកម្រិតសំឡេងឮខ្លាំងពេលភ្ជាប់ប៊្លូធូសក្នុងករណីមានបញ្ហាជាមួយឧបករណ៍បញ្ជាពីចម្ងាយ ដូចជាកម្រិតសំឡេងឮខ្លាំងដែលមិនអាចទទួលយកបាន ឬខ្វះការគ្រប់គ្រង។"</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"បើក​ជង់​មុខងារ​ប៊្លូធូស Gabeldorche​។"</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"បើកជង់​មុខងារ​ប៊្លូធូស Gabeldorsche។"</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"ស្ថានីយ​មូលដ្ឋាន"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"បើក​កម្មវិធី​ស្ថានីយ​ដែល​ផ្ដល់​ការ​ចូល​សែល​មូលដ្ឋាន"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"ពិនិត្យ HDCP"</string>
diff --git a/packages/SettingsLib/res/values-kn/arrays.xml b/packages/SettingsLib/res/values-kn/arrays.xml
index b06af1c..4d8bde2 100644
--- a/packages/SettingsLib/res/values-kn/arrays.xml
+++ b/packages/SettingsLib/res/values-kn/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (ಡೀಫಾಲ್ಟ್)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"ಸಿಸ್ಟಂ ಆಯ್ಕೆಯನ್ನು ಬಳಸಿ (ಡಿಫಾಲ್ಟ್)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index 08a0db7..0699bbc 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"ಬ್ಲೂಟೂತ್ AVRCP ಆವೃತ್ತಿ"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"ಬ್ಲೂಟೂತ್ AVRCP ಆವೃತ್ತಿಯನ್ನು ಆಯ್ಕೆ ಮಾಡಿ"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"ಬ್ಲೂಟೂತ್ MAP ಆವೃತ್ತಿ"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"ಬ್ಲೂಟೂತ್ MAP ಆವೃತ್ತಿಯನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"ಬ್ಲೂಟೂತ್ ಆಡಿಯೋ ಕೋಡೆಕ್"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"ಬ್ಲೂಟೂತ್ ಆಡಿಯೋ ಕೋಡೆಕ್ ಅನ್ನು ಟ್ರಿಗ್ಗರ್ ಮಾಡಿ\nಆಯ್ಕೆ"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"ಬ್ಲೂಟೂತ್ ಆಡಿಯೋ ಮಾದರಿ ದರ"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"ಹಾನಿಮಾಡುವಂತಹ ವರ್ತನೆಗಾಗಿ ADB/ADT ಮೂಲಕ ಸ್ಥಾಪಿಸಲಾದ ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಪರಿಶೀಲಿಸಿ."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"ಹೆಸರುಗಳಿಲ್ಲದ (ಕೇವಲ MAC ವಿಳಾಸಗಳು ಮಾತ್ರ) ಬ್ಲೂಟೂತ್ ಸಾಧನಗಳನ್ನು ಪ್ರದರ್ಶಿಸಲಾಗುತ್ತದೆ"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"ರಿಮೋಟ್ ಸಾಧನಗಳಲ್ಲಿ ಕಂಡುಬರುವ ಸ್ವೀಕಾರಾರ್ಹವಲ್ಲದ ಜೋರಾದ ವಾಲ್ಯೂಮ್ ಅಥವಾ ನಿಯಂತ್ರಣದ ಕೊರತೆಯಂತಹ ವಾಲ್ಯೂಮ್ ಸಮಸ್ಯೆಗಳಂತಹ ಸಂದರ್ಭದಲ್ಲಿ ಬ್ಲೂಟೂತ್‍ನ ನಿಚ್ಚಳ ವಾಲ್ಯೂಮ್ ವೈಶಿಷ್ಟ್ಯವನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸುತ್ತದೆ."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"ಬ್ಲೂಟೂತ್ Gabeldorche ವೈಶಿಷ್ಟ್ಯದ ಸ್ಟ್ಯಾಕ್‌ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸುತ್ತದೆ."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"ಬ್ಲೂಟೂತ್ Gabeldorsche ವೈಶಿಷ್ಟ್ಯದ ಸ್ಟ್ಯಾಕ್‌ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸುತ್ತದೆ."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"ಸ್ಥಳೀಯ ಟರ್ಮಿನಲ್"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"ಸ್ಥಳೀಯ ಶೆಲ್ ಪ್ರವೇಶವನ್ನು ಒದಗಿಸುವ ಟರ್ಮಿನಲ್ ಅಪ್ಲಿಕೇಶನ್ ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP ಪರೀಕ್ಷಿಸುವಿಕೆ"</string>
diff --git a/packages/SettingsLib/res/values-ko/arrays.xml b/packages/SettingsLib/res/values-ko/arrays.xml
index 9f7a751..999f3ae 100644
--- a/packages/SettingsLib/res/values-ko/arrays.xml
+++ b/packages/SettingsLib/res/values-ko/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2(기본)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"시스템 설정 사용(기본)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index d93b1cd..25e9cfe 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche 사용 설정"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"블루투스 AVRCP 버전"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"블루투스 AVRCP 버전 선택"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"블루투스 MAP 버전"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"블루투스 MAP 버전 선택"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"블루투스 오디오 코덱"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"블루투스 오디오 코덱 실행\n선택"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"블루투스 오디오 샘플링 비율"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"ADB/ADT를 통해 설치된 앱에 유해한 동작이 있는지 확인"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"이름이 없이 MAC 주소만 있는 블루투스 기기 표시"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"참기 어려울 정도로 볼륨이 크거나 제어가 되지 않는 등 원격 기기에서 볼륨 문제가 발생할 경우 블루투스 절대 볼륨 기능을 사용 중지"</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"블루투스 Gabeldorche 기능 스택을 사용 설정합니다."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"블루투스 Gabeldorsche 기능 스택을 사용 설정합니다."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"로컬 터미널"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"로컬 셸 액세스를 제공하는 터미널 앱 사용"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP 확인"</string>
diff --git a/packages/SettingsLib/res/values-ky/arrays.xml b/packages/SettingsLib/res/values-ky/arrays.xml
index b2eaf9f..fd47dad 100644
--- a/packages/SettingsLib/res/values-ky/arrays.xml
+++ b/packages/SettingsLib/res/values-ky/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"КАРТА 1.2 (Демейки)"</item>
+    <item msgid="6817922176194686449">"КАРТА 1.3"</item>
+    <item msgid="3423518690032737851">"КАРТА 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"карта12"</item>
+    <item msgid="7073042887003102964">"карта13"</item>
+    <item msgid="8147982633566548515">"карта14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Тутум тандаганды колдонуу (демейки)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index a24dd65b..3dfce1e 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche функциясын иштетүү"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP версиясы"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Bluetooth AVRCP версиясын тандоо"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP версиясы"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Bluetooth MAP версиясын тандоо"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth аудио кодек"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Bluetooth Audio кодегин иштетүү\nТандоо"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth аудио үлгүсүнүн ылдамдыгы"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"ADB/ADT аркылуу орнотулган колдонмолордун коопсуздугу текшерилет."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Аталышсыз Bluetooth түзмөктөрү (MAC даректери менен гана) көрсөтүлөт"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Алыскы түзмөктөр өтө катуу добуш чыгарып же көзөмөлдөнбөй жатса Bluetooth \"Үндүн абсолюттук деңгээли\" функциясын өчүрөт."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Bluetooth Gabeldorche функциясынын топтомун иштетет."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Bluetooth Gabeldorsche функциясынын топтомун иштетет."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Жергиликтүү терминал"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Жергиликтүү буйрук кабыгын сунуштаган терминалга уруксат берүү"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP текшерүү"</string>
diff --git a/packages/SettingsLib/res/values-lo/arrays.xml b/packages/SettingsLib/res/values-lo/arrays.xml
index 7e7ea1f..5e25ab0 100644
--- a/packages/SettingsLib/res/values-lo/arrays.xml
+++ b/packages/SettingsLib/res/values-lo/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (Default)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"ໃຊ້ການເລືອກຂອງລະບົບ (ຄ່າເລີ່ມຕົ້ນ)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 3f93483..48e5093 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"ເປີດໃຊ້ Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"ເວີຊັນ Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"ເລືອກເວີຊັນ Bluetooth AVRCP"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"ເວີຊັນ Bluetooth MAP"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"ເລືອກເວີຊັນ Bluetooth MAP"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth Audio Codec"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"ເປີດໃຊ້ Bluetooth Audio Codec\nການເລືອກ"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth Audio Sample Rate"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"ກວດສອບແອັບທີ່ຕິດຕັ້ງແລ້ວຜ່ານທາງ ADB/ADT ເພື່ອກວດຫາພຶດຕິກຳທີ່ເປັນອັນຕະລາຍ."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"ຈະສະແດງອຸປະກອນ Bluetooth ທີ່ບໍ່ມີຊື່ (ທີ່ຢູ່ MAC ເທົ່ານັ້ນ)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"ປິດໃຊ້ຄຸນສົມບັດລະດັບສຽງສົມບູນຂອງ Bluetooth ໃນກໍລະນີເກີດບັນຫາລະດັບສຽງສົມບູນກັບອຸປະກອນທາງໄກ ເຊັ່ນວ່າ ລະດັບສຽງດັງເກີນຍອມຮັບໄດ້ ຫຼື ຄວບຄຸມບໍ່ໄດ້."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"ເປີດໃຊ້ການວາງຊ້ອນຄຸນສົມບັດ Bluetooth Gabeldorche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"ເປີດໃຊ້ສະແຕັກຄຸນສົມບັດ Bluetooth Gabeldorsche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Terminal ໃນໂຕເຄື່ອງ"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"ເປີດນຳໃຊ້ແອັບຯ Terminal ທີ່ໃຫ້ການເຂົ້າເຖິງ shell ໃນໂຕເຄື່ອງໄດ້"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"ການກວດສອບ HDCP"</string>
diff --git a/packages/SettingsLib/res/values-lt/arrays.xml b/packages/SettingsLib/res/values-lt/arrays.xml
index 3356efb..e4b55ab 100644
--- a/packages/SettingsLib/res/values-lt/arrays.xml
+++ b/packages/SettingsLib/res/values-lt/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"1.2 versijos MRK (numatytoji)"</item>
+    <item msgid="6817922176194686449">"1.3 versijos MRK"</item>
+    <item msgid="3423518690032737851">"1.4 versijos MRK"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Naudoti sistemos pasirink. (numatytasis)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index 9409093..8b3fbad 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Įgalinti „Gabeldorsche“"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"„Bluetooth“ AVRCP versija"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Pasirinkite „Bluetooth“ AVRCP versiją"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"„Bluetooth“ MRK versija"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Pasirinkite „Bluetooth“ MRK versiją"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"„Bluetooth“ garso kodekas"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Suaktyvinti „Bluetooth“ garso kodeką\nPasirinkimas"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"„Bluetooth“ garso pavyzdžio dažnis"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Patikrinkite, ar programų, įdiegtų naudojant ADB / ADT, veikimas nėra žalingas."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Bus rodomi „Bluetooth“ įrenginiai be pavadinimų (tik MAC adresai)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Išjungiama „Bluetooth“ didžiausio garso funkcija, jei naudojant nuotolinio valdymo įrenginius kyla problemų dėl garso, pvz., garsas yra per didelis arba jo negalima tinkamai valdyti."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Įgalinama „Bluetooth Gabeldorche“ funkcijų grupė."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Įgalinama „Bluetooth Gabeldorsche“ funkcijų grupė."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Vietinis terminalas"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Įgal. terminalo progr., siūlančią prieigą prie viet. apvalkalo"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP tikrinimas"</string>
diff --git a/packages/SettingsLib/res/values-lv/arrays.xml b/packages/SettingsLib/res/values-lv/arrays.xml
index 2f0f507..b90cf22 100644
--- a/packages/SettingsLib/res/values-lv/arrays.xml
+++ b/packages/SettingsLib/res/values-lv/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (noklusējums)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Sistēmas atlases izmantošana (nokl.)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index b51b69a..4fc5b22 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Iespējot Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP versija"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Atlasiet Bluetooth AVRCP versiju"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP versija"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Atlasiet Bluetooth MAP versiju"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth audio kodeks"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Aktivizēt Bluetooth audio kodeku\nAtlase"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth audio iztveršanas ātrums"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Pārbaudīt, vai lietotņu, kuru instalēšanai izmantots ADB/ADT, darbība nav kaitīga."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Tiks parādītas Bluetooth ierīces bez nosaukumiem (tikai MAC adreses)."</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Atspējo Bluetooth absolūtā skaļuma funkciju skaļuma problēmu gadījumiem attālajās ierīcēs, piemēram, ja ir nepieņemami liels skaļums vai nav iespējas kontrolēt skaļumu."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Šis iestatījums iespējo funkciju grupu Bluetooth Gabeldorche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Tiek iespējota funkciju grupa Bluetooth Gabeldorsche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Vietējā beigu lietotne"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Iespējot beigu lietotni, kurā piedāvāta vietējā čaulas piekļuve"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP pārbaude"</string>
diff --git a/packages/SettingsLib/res/values-mk/arrays.xml b/packages/SettingsLib/res/values-mk/arrays.xml
index 3753a51..90956ad 100644
--- a/packages/SettingsLib/res/values-mk/arrays.xml
+++ b/packages/SettingsLib/res/values-mk/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (стандардна)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Користи избор на системот (стандардно)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index d38301e..288e526 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Овозможи Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Верзија Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Изберете верзија Bluetooth AVRCP"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Верзија на Bluetooth MAP"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Изберете верзија на Bluetooth MAP"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Кодек за аудио преку Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Вклучете го аудио кодекот преку Bluetooth\nСелекција"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Стапка на семпл преку Bluetooth"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Провери апликации инсталирани преку ADB/ADT за штетно однесување."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Уредите со Bluetooth без имиња (само MAC-адреси) ќе се прикажуваат"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Ја оневозможува карактеристиката за апсолутна јачина на звук преку Bluetooth во случај кога ќе настанат проблеми со далечинските уреди, како на пр., неприфатливо силен звук или недоволна контрола."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Ја овозможува функцијата Bluetooth Gabeldorche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Ја овозможува функцијата Bluetooth Gabeldorsche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Локален терминал"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Овозможи апликација на терминал што овозможува локален пристап кон школка."</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"Проверување HDCP"</string>
diff --git a/packages/SettingsLib/res/values-ml/arrays.xml b/packages/SettingsLib/res/values-ml/arrays.xml
index 1e07994..60eb24e 100644
--- a/packages/SettingsLib/res/values-ml/arrays.xml
+++ b/packages/SettingsLib/res/values-ml/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (ഡിഫോൾട്ട്)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"സിസ്റ്റം സെലക്ഷൻ ഉപയോഗിക്കൂ ‌(ഡിഫോൾട്ട്)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index 27019d1..e775297 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche പ്രവർത്തനക്ഷമമാക്കുക"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP പതിപ്പ്"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Bluetooth AVRCP പതിപ്പ് തിരഞ്ഞെടുക്കുക"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP പതിപ്പ്"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Bluetooth MAP പതിപ്പ് തിരഞ്ഞെടുക്കുക"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth ഓഡിയോ കോഡെക്"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Bluetooth Audio Codec\nSelection ട്രിഗ്ഗര്‍ ചെയ്യുക"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth ഓഡിയോ സാമ്പിൾ നിരക്ക്"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"ADB/ADT വഴി ഇൻസ്റ്റാൾ ചെയ്‌ത കേടാക്കുന്ന പ്രവർത്തനരീതിയുള്ള ആപ്പുകൾ പരിശോധിക്കുക."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"പേരില്ലാത്ത Bluetooth ഉപകരണങ്ങൾ (MAC വിലാസങ്ങൾ മാത്രം) പ്രദർശിപ്പിക്കും"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"അസ്വീകാര്യമായ തരത്തിൽ ഉയർന്ന വോളിയമോ ശബ്ദ നിയന്ത്രണത്തിന്റെ അഭാവമോ പോലെ, വിദൂര ഉപകരണങ്ങളുമായി ബന്ധപ്പെട്ട വോളിയം പ്രശ്നങ്ങൾ ഉണ്ടാകുന്ന സാഹചര്യത്തിൽ, Bluetooth അബ്‌സൊല്യൂട്ട് വോളിയം ഫീച്ചർ പ്രവർത്തനരഹിതമാക്കുന്നു."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Bluetooth Gabeldorche ഫീച്ചർ സ്‌റ്റാക്ക് പ്രവർത്തനക്ഷമമാക്കുന്നു."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Bluetooth Gabeldorsche ഫീച്ചർ സ്റ്റാക്ക് പ്രവർത്തനക്ഷമമാക്കുന്നു."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"പ്രാദേശിക ടെർമിനൽ"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"പ്രാദേശിക ഷെൽ ആക്‌സസ് നൽകുന്ന ടെർമിനൽ അപ്ലിക്കേഷൻ പ്രവർത്തനക്ഷമമാക്കുക"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP പരിശോധന"</string>
diff --git a/packages/SettingsLib/res/values-mn/arrays.xml b/packages/SettingsLib/res/values-mn/arrays.xml
index d1eca7b..ce868af 100644
--- a/packages/SettingsLib/res/values-mn/arrays.xml
+++ b/packages/SettingsLib/res/values-mn/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (өгөгдмөл)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Системийн сонголтыг ашиглах (Өгөгдмөл)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index f0136c9..ba69f9b 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche-г идэвхжүүлэх"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP хувилбар"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Bluetooth AVRCP хувилбарыг сонгох"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP хувилбар"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Bluetooth MAP хувилбарыг сонгох"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth аудио кодлогч"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Bluetooth-н аудио кодлогчийг өдөөх\nСонголт"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth аудио жишээний үнэлгээ"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"ADB/ADT-р суулгасан апп-уудыг хорлонтой авиртай эсэхийг шалгах."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Нэргүй Bluetooth төхөөрөмжийг (зөвхөн MAC хаяг) харуулна"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Хэт чанга дуугаралт эсвэл муу тохиргоо зэрэг алсын зайн төхөөрөмжийн дуугаралттай холбоотой асуудлын үед Bluetooth-ийн үнэмлэхүй дууны түвшинг идэвхгүй болго."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Bluetooth Gabeldorsche онцлогийн өрөлтийг идэвхжүүлдэг."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Bluetooth Gabeldorsche онцлогийн өрөлтийг идэвхжүүлдэг."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Локал терминал"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Локал суурьт хандалт хийх боломж олгодог терминалын апп-г идэвхжүүлэх"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP шалгах"</string>
diff --git a/packages/SettingsLib/res/values-mr/arrays.xml b/packages/SettingsLib/res/values-mr/arrays.xml
index e62e6ff..3e6e3d0 100644
--- a/packages/SettingsLib/res/values-mr/arrays.xml
+++ b/packages/SettingsLib/res/values-mr/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (डीफॉल्ट)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"सिस्टम निवड वापरा (डीफॉल्ट)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 4d8069e..1930cdf 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"गाबलडॉर्ष सुरू करा"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"ब्लूटूथ AVRCP आवृत्ती"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"ब्लूटूथ AVRCP आवृत्ती निवडा"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"ब्लूटूथ MAP आवृत्ती"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"ब्लूटूथ MAP आवृत्ती निवडा"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"ब्लूटूथ ऑडिओ कोडेक"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"ब्लूटूथ ऑडिओ Codec ट्रिगर करा\nनिवड"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"ब्लूटूथ ऑडिओ पॅटर्न दर"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"हानिकारक वर्तनासाठी ADB/ADT द्वारे इंस्टॉल अ‍ॅप्स तपासा."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"नावांशिवाय ब्‍लूटूथ डीव्‍हाइस (फक्‍त MAC पत्‍ते) दाखवले जातील"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"रिमोट डिव्हाइसमध्ये सहन न होणारा मोठा आवाज किंवा नियंत्रणाचा अभाव यासारखी आवाजाची समस्या असल्यास ब्लूटूथ संपूर्ण आवाज वैशिष्ट्य बंद करते."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"ब्लूटूथ गाबलडॉर्ष वैशिष्ट्य स्टॅक सुरू करते."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"ब्लूटूथ गाबलडॉर्ष वैशिष्‍ट्य स्टॅक सुरू करा."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"स्थानिक टर्मिनल"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"स्थानिक शेल प्रवेश देणारा टर्मिनल अ‍ॅप सुरू करा"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP तपासणी"</string>
diff --git a/packages/SettingsLib/res/values-ms/arrays.xml b/packages/SettingsLib/res/values-ms/arrays.xml
index 91dd81c..a2d314b 100644
--- a/packages/SettingsLib/res/values-ms/arrays.xml
+++ b/packages/SettingsLib/res/values-ms/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (Lalai)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Gunakan Pilihan Sistem (Lalai)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index 7efc9875..e493188 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Dayakan Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Versi AVRCP Bluetooth"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Pilih Versi AVRCP Bluetooth"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Versi MAP Bluetooth"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Pilih Versi MAP Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Codec Audio Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Cetuskan Codec Audio Bluetooth\nPilihan"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Kadar Sampel Audio Bluetooth"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Semak apl yang dipasang melalui ADB/ADT untuk tingkah laku yang berbahaya."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Peranti Bluetooth tanpa nama (alamat MAC sahaja) akan dipaparkan"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Lumpuhkan ciri kelantangan mutlak Bluetooth dalam kes isu kelantangan menggunakan peranti kawalan jauh seperti kelantangan yang sangat kuat atau tidak dapat mengawal."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Mendayakan tindanan ciri Bluetooth Gabeldorche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Mendayakan tindanan ciri Gabeldorche Bluetooth."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Terminal setempat"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Dayakan apl terminal yang menawarkan akses shell tempatan"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"Penyemakan HDCP"</string>
diff --git a/packages/SettingsLib/res/values-my/arrays.xml b/packages/SettingsLib/res/values-my/arrays.xml
index 9c4a2b9..dbeabc0 100644
--- a/packages/SettingsLib/res/values-my/arrays.xml
+++ b/packages/SettingsLib/res/values-my/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (မူရင်း)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"စနစ်ရွေးချယ်မှုကို အသုံးပြုပါ (မူရင်း)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index befdaa9..2c4b32c 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche ကို ဖွင့်ရန်"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"ဘလူးတုသ် AVRCP ဗားရှင်း"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"ဘလူးတုသ် AVRCP ဗားရှင်းကို ရွေးပါ"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"ဘလူးတုသ် MAP ဗားရှင်း"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"ဘလူးတုသ် MAP ဗားရှင်းကို ရွေးပါ"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"ဘလူးတုသ်အသံ ကိုးဒက်ခ်"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"ဘလူးတုသ် အသံ LDAC ကိုးဒက်ခ် ဖွင့်ခြင်း\nရွေးချယ်မှု"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"ဘလူးတုသ်အသံနမူနာနှုန်း"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"ADB/ADT မှတစ်ဆင့် ထည့်သွင်းသော အက်ပ်များ အန္တရာယ်ဖြစ်နိုင်ခြင်း ရှိမရှိ စစ်ဆေးသည်။"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"အမည်မရှိသော (MAC လိပ်စာများသာပါသော) ဘလူးတုသ်စက်ပစ္စည်းများကို ပြသပါမည်"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"ချိတ်ဆက်ထားသည့် ကိရိယာတွင် လက်မခံနိုင်လောက်အောင် ဆူညံ သို့မဟုတ် ထိန်းညှိမရနိုင်သော အသံပိုင်းပြဿနာ ရှိခဲ့လျှင် ဘလူးတုသ် ပကတိ အသံနှုန်းကို ပိတ်ပါ။"</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"ဘလူးတုသ် Gabeldorche အထူးတည်းဖြတ်ခြင်းကို ဖွင့်သည်။"</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"ဘလူးတုသ် Gabeldorsche လုပ်ဆောင်ချက်အပိုင်းကို ဖွင့်သည်။"</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"လိုကယ်တာမီနယ်"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"local shell အသုံးပြုခွင့်ကမ်းလှမ်းသော တာမင်နယ်အပလီကေးရှင်းဖွင့်ပါ"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP စစ်ဆေးမှု"</string>
diff --git a/packages/SettingsLib/res/values-nb/arrays.xml b/packages/SettingsLib/res/values-nb/arrays.xml
index ed045ad..8d005b3 100644
--- a/packages/SettingsLib/res/values-nb/arrays.xml
+++ b/packages/SettingsLib/res/values-nb/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (standard)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Bruk systemvalg (standard)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index cb0931f..093c06f 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Aktiver Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP-versjon"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Velg Bluetooth AVRCP-versjon"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP-versjon"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Velg Bluetooth MAP-versjon"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Kodek for Bluetooth-lyd"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Utløs kodek for Bluetooth-lyd\nValg"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Samplefrekvens for Bluetooth-lyd"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Sjekk apper som er installert via ADB/ADT, for skadelig atferd."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Bluetooth-enheter uten navn (bare MAC-adresser) vises"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Slår av funksjonen for absolutt volum via Bluetooth i tilfelle det oppstår volumrelaterte problemer med eksterne enheter, for eksempel uakseptabelt høyt volum eller mangel på kontroll."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Aktiverer funksjonsstabelen Bluetooth Gabeldorche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Aktiverer funksjonsstabelen Bluetooth Gabeldorsche"</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Lokal terminal"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Aktiver terminalappen som gir lokal kommandolistetilgang"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP-kontroll"</string>
diff --git a/packages/SettingsLib/res/values-ne/arrays.xml b/packages/SettingsLib/res/values-ne/arrays.xml
index c8ee48b..5d79e80 100644
--- a/packages/SettingsLib/res/values-ne/arrays.xml
+++ b/packages/SettingsLib/res/values-ne/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp १५"</item>
     <item msgid="1963366694959681026">"avrcp १६"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP १.२ (पूर्वनिर्धारित)"</item>
+    <item msgid="6817922176194686449">"MAP १.३"</item>
+    <item msgid="3423518690032737851">"MAP १.४"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"प्रणालीको चयन प्रयोग गर्नुहोस् (पूर्वनिर्धारित)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index c7ffc64..fb8b737 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche सक्षम पार्नुहोस्"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"ब्लुटुथको AVRCP संस्करण"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"ब्लुटुथको AVRCP संस्करण चयन गर्नुहोस्"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"ब्लुटुथको MAP संस्करण"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"ब्लुटुथको MAP संस्करण चयन गर्नुहोस्"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"ब्लुटुथ अडियोको कोडेक"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"ब्लुटुथ अडियो कोडेक ट्रिगर गर्नुहोस्\nचयन"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"ब्लुटुथ अडियोको नमूना दर"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"हानिकारक व्यवहारको लागि ADB/ADT को माध्यमबाट स्थापित अनुप्रयोगहरूको जाँच गर्नुहोस्।"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"नामकरण नगरिएका ब्लुटुथ यन्त्रहरू (MAC ठेगाना भएका मात्र) देखाइनेछ"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"रिमोट यन्त्रहरूमा अस्वीकार्य चर्को आवाज वा नियन्त्रणमा कमी जस्ता आवाज सम्बन्धी समस्याहरूको अवस्थामा ब्लुटुथ निरपेक्ष आवाज सुविधालाई असक्षम गराउँछ।"</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"ब्लुटुथ Gabeldorche सुविधाको स्ट्याक सक्षम पार्नुहोस्।"</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"ब्लुटुथ Gabeldorsche सुविधाको स्ट्याक सक्षम पार्नुहोस्।"</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"स्थानीय टर्मिनल"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"स्थानीय सेल पहुँच प्रदान गर्ने टर्मिनल अनुप्रयोग सक्षम गर्नुहोस्"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP जाँच गर्दै"</string>
diff --git a/packages/SettingsLib/res/values-nl/arrays.xml b/packages/SettingsLib/res/values-nl/arrays.xml
index d86dab6..9b94ae50 100644
--- a/packages/SettingsLib/res/values-nl/arrays.xml
+++ b/packages/SettingsLib/res/values-nl/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (standaard)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Systeemselectie gebruiken (standaard)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index a196ccf..267dab4 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche inschakelen"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth-AVRCP-versie"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Bluetooth-AVRCP-versie selecteren"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"MAP-versie voor bluetooth"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"MAP-versie voor bluetooth selecteren"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth-audiocodec"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Codec voor Bluetooth-audio activeren\nSelectie"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Sample rate van Bluetooth-audio"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Apps die zijn geïnstalleerd via ADB/ADT, controleren op schadelijk gedrag"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Bluetooth-apparaten zonder namen (alleen MAC-adressen) worden weergegeven"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Hiermee wordt de functie voor absoluut volume van Bluetooth uitgeschakeld in geval van volumeproblemen met externe apparaten, zoals een onacceptabel hoog volume of geen volumeregeling."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Hierdoor wordt de Gabeldorsche-functiestack voor bluetooth ingeschakeld"</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Hierdoor wordt de Gabeldorsche-functiestack voor bluetooth ingeschakeld."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Lokale terminal"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Terminal-app inschakelen die lokale shell-toegang biedt"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP-controle"</string>
diff --git a/packages/SettingsLib/res/values-or/arrays.xml b/packages/SettingsLib/res/values-or/arrays.xml
index 2553978..a021446 100644
--- a/packages/SettingsLib/res/values-or/arrays.xml
+++ b/packages/SettingsLib/res/values-or/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (ଡିଫଲ୍ଟ)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"ସିଷ୍ଟମ୍ ଚୟନ ବ୍ୟବହାର କରନ୍ତୁ (ଡିଫଲ୍ଟ)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index faa399d..d8ae3bf 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"ଗାବେଲ୍‌ଡୋର୍ସ ସକ୍ରିୟ କରନ୍ତୁ"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"ବ୍ଲୁଟୂଥ୍‌ AVRCP ଭର୍ସନ୍"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"ବ୍ଲୁଟୂଥ୍‍‌ AVRCP ଭର୍ସନ୍‌"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"ବ୍ଲୁଟୁଥ୍ MAP ସଂସ୍କରଣ"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"ବ୍ଲୁଟୁଥ୍ MAP ସଂସ୍କରଣ ଚୟନ କରନ୍ତୁ"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"ବ୍ଲୁଟୁଥ୍‌ ଅଡିଓ କୋଡେକ୍‌"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"ବ୍ଲୁଟୂଥ୍‍ ଅଡିଓ କୋଡେକ୍\nସିଲେକ୍ସନ୍‌କୁ ଗତିଶୀଳ କରନ୍ତୁ"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"ବ୍ଲୁଟୂଥ୍‌ ଅଡିଓ ସାମ୍ପଲ୍‌ ରେଟ୍‌"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"ADB/ADT ମାଧ୍ୟମରେ ଇନଷ୍ଟଲ ହୋଇଥିବା ଆପ୍‌ଗୁଡ଼ିକ କ୍ଷତିକାରକ କି ନୁହେଁ ଯାଞ୍ଚ କରନ୍ତୁ।"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"(କେବଳ MAC ଠିକଣା ଥାଇ) ନାମ ବିନା ବ୍ଲୁଟୂଥ ଡିଭାଇସଗୁଡ଼ିକ ପ୍ରଦର୍ଶିତ ହେବ"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"ରିମୋଟ୍‌ ଡିଭାଇସ୍‌ଗୁଡ଼ିକରେ ଯଦି ଅସ୍ୱୀକାର୍ଯ୍ୟ ଭାବେ ଉଚ୍ଚ ଭଲ୍ୟୁମ୍ କିମ୍ବା ନିୟନ୍ତ୍ରଣର ଅଭାବ ପରି ଭଲ୍ୟୁମ୍ ସମସ୍ୟା ଥାଏ, ବ୍ଲୁଟୂଥ୍‌ ପୂର୍ଣ୍ଣ ଭଲ୍ୟୁମ୍ ଫିଚର୍ ଅକ୍ଷମ କରିଥାଏ।"</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"ବ୍ଲୁଟୁଥ୍ ଗାବେଲ୍‌ଡୋର୍ସ ଫିଚର୍ ଷ୍ଟକ୍ ସକ୍ଷମ କରେ।"</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"ବ୍ଲୁଟୁଥ୍ ଗାବେଲଡୋର୍ସ ଫିଚର୍ ଷ୍ଟକ୍ ସକ୍ଷମ କରେ।"</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"ସ୍ଥାନୀୟ ଟର୍ମିନାଲ୍‌"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"ସ୍ଥାନୀୟ ଶେଲ୍‌କୁ ଆକ‌ସେସ୍‌ ଦେଉଥିବା ଟର୍ମିନଲ୍‌ ଆପ୍‌କୁ ସକ୍ଷମ କରନ୍ତୁ"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP ଯାଞ୍ଚ କରୁଛି"</string>
diff --git a/packages/SettingsLib/res/values-pa/arrays.xml b/packages/SettingsLib/res/values-pa/arrays.xml
index 8acc439..48e7fb4 100644
--- a/packages/SettingsLib/res/values-pa/arrays.xml
+++ b/packages/SettingsLib/res/values-pa/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (ਪੂਰਵ-ਨਿਰਧਾਰਤ)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"ਸਿਸਟਮ ਚੋਣ ਦੀ ਵਰਤੋਂ ਕਰੋ (ਪੂਰਵ-ਨਿਰਧਾਰਤ)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index cd83c2c..6a78486 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche ਨੂੰ ਚਾਲੂ ਕਰੋ"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"ਬਲੂਟੁੱਥ AVRCP ਵਰਜਨ"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"ਬਲੂਟੁੱਥ AVRCP ਵਰਜਨ ਚੁਣੋ"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP ਵਰਜਨ"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Bluetooth MAP ਵਰਜਨ ਚੁਣੋ"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"ਬਲੂਟੁੱਥ ਆਡੀਓ ਕੋਡੇਕ"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"ਬਲੂਟੁੱਥ ਆਡੀਓ ਕੋਡੇਕ\nਚੋਣ ਨੂੰ ਟ੍ਰਿਗਰ ਕਰੋ"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"ਬਲੂਟੁੱਥ ਆਡੀਓ ਸੈਂਪਲ ਰੇਟ"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"ਹਾਨੀਕਾਰਕ ਵਿਵਹਾਰ ਲਈ ADB/ADT ਰਾਹੀਂ ਸਥਾਪਤ ਕੀਤੀਆਂ ਐਪਾਂ ਦੀ ਜਾਂਚ ਕਰੋ।"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"ਅਨਾਮ ਬਲੂਟੁੱਥ ਡੀਵਾਈਸਾਂ ਦਿਖਾਈਆਂ ਜਾਣਗੀਆਂ (ਸਿਰਫ਼ MAC ਪਤੇ)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"ਰਿਮੋਟ ਡੀਵਾਈਸਾਂ ਨਾਲ ਅਵਾਜ਼ੀ ਸਮੱਸਿਆਵਾਂ ਜਿਵੇਂ ਕਿ ਨਾ ਪਸੰਦ ਕੀਤੀ ਜਾਣ ਵਾਲੀ ਉੱਚੀ ਅਵਾਜ਼ ਜਾਂ ਕੰਟਰੋਲ ਦੀ ਕਮੀ ਵਰਗੀ ਹਾਲਤ ਵਿੱਚ ਬਲੂਟੁੱਥ ਪੂਰਨ ਅਵਾਜ਼ ਵਿਸ਼ੇਸ਼ਤਾ ਨੂੰ ਬੰਦ ਕਰਦਾ ਹੈ।"</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"ਬਲੂਟੁੱਥ Gabeldorche ਵਿਸ਼ੇਸ਼ਤਾ ਸਟੈਕ ਨੂੰ ਚਾਲੂ ਕਰਦਾ ਹੈ।"</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"ਬਲੂਟੁੱਥ Gabeldorsche ਵਿਸ਼ੇਸ਼ਤਾ ਸਟੈਕ ਨੂੰ ਚਾਲੂ ਕਰਦਾ ਹੈ।"</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"ਸਥਾਨਕ ਟਰਮੀਨਲ"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"ਟਰਮੀਨਲ ਐਪ ਨੂੰ ਚਾਲੂ ਕਰੋ ਜੋ ਸਥਾਨਕ ਸ਼ੈਲ ਪਹੁੰਚ ਪੇਸ਼ਕਸ਼ ਕਰਦਾ ਹੈ"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP ਜਾਂਚ"</string>
diff --git a/packages/SettingsLib/res/values-pl/arrays.xml b/packages/SettingsLib/res/values-pl/arrays.xml
index 00b23bc..43b8f5f 100644
--- a/packages/SettingsLib/res/values-pl/arrays.xml
+++ b/packages/SettingsLib/res/values-pl/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (domyślny)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Użyj wyboru systemu (domyślnie)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index b47365b..8c5547c 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Włącz Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Wersja AVRCP Bluetooth"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Wybierz wersję AVRCP Bluetooth"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Wersja MAP Bluetooth"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Wybierz wersję MAP Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Kodek dźwięku Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Uruchom kodek dźwięku Bluetooth\nWybór"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Dźwięk Bluetooth – współczynnik próbkowania"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Sprawdź, czy aplikacje zainstalowane przez ADB/ADT nie zachowują się w szkodliwy sposób"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Zostaną wyświetlone urządzenia Bluetooth bez nazw (tylko adresy MAC)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Wyłącza funkcję Głośność bezwzględna Bluetooth, jeśli występują problemy z urządzeniami zdalnymi, np. zbyt duża głośność lub brak kontroli."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Włącza funkcje Bluetooth Gabeldorche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Włącza funkcje Bluetooth Gabeldorsche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Terminal lokalny"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Włącz terminal, który umożliwia dostęp do powłoki lokalnej"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"Sprawdzanie HDCP"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/arrays.xml b/packages/SettingsLib/res/values-pt-rBR/arrays.xml
index 4e23c19..4658ffd 100644
--- a/packages/SettingsLib/res/values-pt-rBR/arrays.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (padrão)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Usar seleção do sistema (padrão)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index 4cccad4..8c03616 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Ativar Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Versão do Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Selecionar versão do Bluetooth AVRCP"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Versão MAP do Bluetooth"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Selecionar versão MAP do Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Codec de áudio Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Acionar codec de áudio Bluetooth\nSeleção"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Taxa de amostra do áudio Bluetooth"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Verificar comportamento nocivo em apps instalados via ADB/ADT"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Dispositivos Bluetooth sem nomes (somente endereços MAC) serão exibidos"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Desativa o recurso Bluetooth de volume absoluto em caso de problemas com o volume em dispositivos remotos, como volume excessivamente alto ou falta de controle"</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Ativa a pilha de recursos Bluetooth Gabeldorsche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Ativa a pilha de recursos Bluetooth Gabeldorsche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Terminal local"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Ativar o app terminal que oferece acesso ao shell local"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"Verificação HDCP"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/arrays.xml b/packages/SettingsLib/res/values-pt-rPT/arrays.xml
index 98e9c87..527f740 100644
--- a/packages/SettingsLib/res/values-pt-rPT/arrays.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (predefinição)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Utilizar seleção do sistema (predefinido)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 6e80bd2..6aeff1c 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Ativar o Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Versão de Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Selecionar versão de Bluetooth AVRCP"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Versão do MAP do Bluetooth"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Selecione a versão do MAP do Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Codec de áudio Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Acionar o codec de áudio Bluetooth\nSeleção"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Taxa de amostragem de áudio Bluetooth"</string>
@@ -241,7 +243,7 @@
     <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="2076949781460359589">"Acionar o codec de áudio Bluetooth\nSeleção: modo de canal"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3233402355917446304">"Codec LDAC de áudio Bluetooth: qualidade de reprodução"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="7274396574659784285">"Acionar a seleção do codec LDAC de áudio\nBluetooth: Qualidade de reprodução"</string>
-    <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="2040810756832027227">"Transmissão em fluxo contínuo: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="2040810756832027227">"Stream: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
     <string name="select_private_dns_configuration_title" msgid="7887550926056143018">"DNS privado"</string>
     <string name="select_private_dns_configuration_dialog_title" msgid="3731422918335951912">"Selecionar modo DNS privado"</string>
     <string name="private_dns_mode_off" msgid="7065962499349997041">"Desativado"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Verificar as aplicações instaladas via ADB/ADT para detetar comportamento perigoso."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"São apresentados os dispositivos Bluetooth sem nomes (apenas endereços MAC)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Desativa a funcionalidade de volume absoluto do Bluetooth caso existam problemas de volume com dispositivos remotos, como um volume insuportavelmente alto ou a ausência de controlo."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Ativa a pilha de funcionalidades Bluetooth Gabeldorche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Ativa a pilha de funcionalidades Bluetooth Gabeldorche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Terminal local"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Ativar aplicação terminal que oferece acesso local à shell"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"Verificação HDCP"</string>
diff --git a/packages/SettingsLib/res/values-pt/arrays.xml b/packages/SettingsLib/res/values-pt/arrays.xml
index 4e23c19..4658ffd 100644
--- a/packages/SettingsLib/res/values-pt/arrays.xml
+++ b/packages/SettingsLib/res/values-pt/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (padrão)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Usar seleção do sistema (padrão)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index 4cccad4..8c03616 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Ativar Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Versão do Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Selecionar versão do Bluetooth AVRCP"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Versão MAP do Bluetooth"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Selecionar versão MAP do Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Codec de áudio Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Acionar codec de áudio Bluetooth\nSeleção"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Taxa de amostra do áudio Bluetooth"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Verificar comportamento nocivo em apps instalados via ADB/ADT"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Dispositivos Bluetooth sem nomes (somente endereços MAC) serão exibidos"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Desativa o recurso Bluetooth de volume absoluto em caso de problemas com o volume em dispositivos remotos, como volume excessivamente alto ou falta de controle"</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Ativa a pilha de recursos Bluetooth Gabeldorsche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Ativa a pilha de recursos Bluetooth Gabeldorsche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Terminal local"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Ativar o app terminal que oferece acesso ao shell local"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"Verificação HDCP"</string>
diff --git a/packages/SettingsLib/res/values-ro/arrays.xml b/packages/SettingsLib/res/values-ro/arrays.xml
index befb771..5d25101 100644
--- a/packages/SettingsLib/res/values-ro/arrays.xml
+++ b/packages/SettingsLib/res/values-ro/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (prestabilit)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Folosiți selectarea sistemului (prestabilit)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index a742932..387441f 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Activați Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Versiunea AVRCP pentru Bluetooth"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Selectați versiunea AVRCP pentru Bluetooth"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Versiunea MAP pentru Bluetooth"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Selectați versiunea MAP pentru Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Codec audio Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Declanșați codecul audio Bluetooth\nSelecție"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Rată de eșantionare audio Bluetooth"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Verificați aplicațiile instalate utilizând ADB/ADT, pentru a detecta un comportament dăunător."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Vor fi afișate dispozitivele Bluetooth fără nume (numai adresele MAC)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Dezactivează funcția Bluetooth de volum absolut în cazul problemelor de volum apărute la dispozitivele la distanță, cum ar fi volumul mult prea ridicat sau lipsa de control asupra acestuia."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Activează setul de funcții Bluetooth Gabeldorche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Activează setul de funcții Bluetooth Gabeldorsche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Aplicație terminal locală"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Activați aplicația terminal care oferă acces la shell local"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"Verificare HDCP"</string>
diff --git a/packages/SettingsLib/res/values-ru/arrays.xml b/packages/SettingsLib/res/values-ru/arrays.xml
index d0d04d6..f5367a4 100644
--- a/packages/SettingsLib/res/values-ru/arrays.xml
+++ b/packages/SettingsLib/res/values-ru/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (по умолчанию)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Выбор системы (по умолчанию)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 769b5f3..361e29f 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Включить Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Версия Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Выберите версию Bluetooth AVRCP"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Версия Bluetooth MAP"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Выберите версию Bluetooth MAP"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Аудиокодек Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Запустить аудиокодек для Bluetooth\nВыбор"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Частота дискретизации аудио Bluetooth"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Выполнять проверку безопасности приложений при установке через ADB/ADT"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Показывать Bluetooth-устройства без названий (только с MAC-адресами)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Отключить абсолютный уровень громкости Bluetooth при возникновении проблем на удаленных устройствах, например при слишком громком звучании или невозможности контролировать настройку"</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Включить стек Bluetooth Gabeldorche"</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Включить стек Bluetooth Gabeldorsche"</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Локальный терминальный доступ"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Разрешить терминальный доступ к локальной оболочке"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"Проверка HDCP"</string>
diff --git a/packages/SettingsLib/res/values-si/arrays.xml b/packages/SettingsLib/res/values-si/arrays.xml
index 4764d47..f8c871e 100644
--- a/packages/SettingsLib/res/values-si/arrays.xml
+++ b/packages/SettingsLib/res/values-si/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (පෙරනිමි)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"පද්ධති තේරීම භාවිත කරන්න (පෙරනිමි)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index 1d925bf..faa848f 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche සබල කරන්න"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"බ්ලූටූත් AVRCP අනුවාදය"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"බ්ලූටූත් AVRCP අනුවාදය තෝරන්න"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP අනුවාදය"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Bluetooth MAP අනුවාදය තෝරන්න"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"බ්ලූටූත් ශ්‍රව්‍ය Codec"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"බ්ලූටූත් ශ්‍රව්‍ය කෝඩෙක් ක්‍රියාරම්භ කරන්න\nතෝරා ගැනීම"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"බ්ලූටූත් ශ්‍රව්‍ය නියැදි අනුපාතය"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"ADB/ADT හරහා ස්ථාපනය වූ යෙදුම්, විනාශකාරී ක්‍රියාවන් ඇත්දැයි පරික්ෂාකර බලන්න."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"නම් නොමැති බ්ලූටූත් උපාංග (MAC ලිපින පමණි) සංදර්ශනය කරනු ඇත"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"පිළිගත නොහැකි ලෙස වැඩි හඩ පරිමාව හෝ පාලනය නොමැති වීම යනාදී දුරස්ථ උපාංග සමගින් වන හඬ පරිමා ගැටලුවලදී බ්ලූටූත් නිරපේක්ෂ හඬ පරිමා විශේෂාංගය අබල කරයි."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"බ්ලූටූත් Gabeldorche විශේෂාංග අට්ටිය සබල කරයි."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Bluetooth Gabeldorsche විශේෂාංග අට්ටිය සබල කරයි."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"අභ්‍යන්තර අන්තය"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"දේශීය ෂෙල් ප්‍රවේශනය පිරිනමන ටර්මිනල් යෙදුම සබල කරන්න"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP පරික්ෂාව"</string>
diff --git a/packages/SettingsLib/res/values-sk/arrays.xml b/packages/SettingsLib/res/values-sk/arrays.xml
index 427ee45..f862d88 100644
--- a/packages/SettingsLib/res/values-sk/arrays.xml
+++ b/packages/SettingsLib/res/values-sk/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (predvolené)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Použiť voľbu systému (predvolené)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index e022c36..2035d88c 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Povoliť Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Verzia rozhrania Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Zvoľte verziu rozhrania Bluetooth AVRCP"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Verzia profilu Bluetooth MAP"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Výber verzie profilu Bluetooth MAP"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth Audio – kodek"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Spustiť zvukový kodek Bluetooth\nVýber"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth Audio – vzorkovacia frekvencia"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Kontrolovať škodlivosť aplikácií nainštalovaných pomocou nástroja ADB alebo ADT"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Zariadenia Bluetooth sa budú zobrazovať bez názvov (iba adresy MAC)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Umožňuje zakázať funkciu absolútnej hlasitosti rozhrania Bluetooth v prípade problémov s hlasitosťou vo vzdialených zariadeniach, ako je napríklad neprijateľne vysoká hlasitosť alebo absencia ovládacích prvkov."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Umožňuje povoliť skupiny funkcií Bluetooth Gabeldorche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Umožňuje povoliť skupinu funkcií Bluetooth Gabeldorche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Miestny terminál"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Povoliť terminálovú apl. na miestny prístup k prostrediu shell"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"Kontrola HDCP"</string>
diff --git a/packages/SettingsLib/res/values-sl/arrays.xml b/packages/SettingsLib/res/values-sl/arrays.xml
index d946316..eb86074 100644
--- a/packages/SettingsLib/res/values-sl/arrays.xml
+++ b/packages/SettingsLib/res/values-sl/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (privzeto)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Uporabi sistemsko izbiro (privzeto)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index efd7c08..2889619 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Omogoči Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Različica profila AVRCP za Bluetooth"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Izberite različico profila AVRCP za Bluetooth"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Različica profila MAP za Bluetooth"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Izbira različice profila MAP za Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Zvočni kodek za Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Sproži zvočni kodek za Bluetooth\nIzbor"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Hitrost vzorčenja zvoka prek Bluetootha"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Preveri, ali so aplikacije, nameščene prek ADB/ADT, škodljive."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Prikazane bodo naprave Bluetooth brez imen (samo z naslovi MAC)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Onemogoči funkcijo absolutne glasnosti za Bluetooth, če pride do težav z glasnostjo z oddaljenimi napravami, kot je nesprejemljivo visoka glasnost ali pomanjkanje nadzora."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Omogoči sklad funkcij Bluetooth Gabeldorche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Omogoči sklad funkcij Bluetooth Gabeldorsche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Lokalni terminal"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Omogočanje terminalske aplikacije za dostop do lokalne lupine"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"Preverjanje HDCP"</string>
diff --git a/packages/SettingsLib/res/values-sq/arrays.xml b/packages/SettingsLib/res/values-sq/arrays.xml
index 26ba289..1363e83 100644
--- a/packages/SettingsLib/res/values-sq/arrays.xml
+++ b/packages/SettingsLib/res/values-sq/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (parazgjedhja)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Përdor përzgjedhjen e sistemit (e parazgjedhur)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index c7c4230..ccd4e306 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Aktivizo Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Versioni AVRCP i Bluetooth-it"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Zgjidh versionin AVRCP të Bluetooth-it"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Versioni MAP i Bluetooth-it"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Zgjidh versionin MAP të Bluetooth-it"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Kodeku Bluetooth Audio"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Aktivizo kodekun e audios me Bluetooth\nZgjedhja"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Shpejtësia e shembullit të Bluetooth Audio"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Kontrollo aplikacionet e instaluara nëpërmjet ADB/ADT për sjellje të dëmshme."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Pajisjet me Bluetooth do të shfaqen pa emra (vetëm adresat MAC)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Çaktivizon funksionin e volumit absolut të Bluetooth në rast të problemeve të volumit me pajisjet në largësi, si p.sh. një volum i lartë i papranueshëm ose mungesa e kontrollit."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Aktivizon grupin e veçorive të Bluetooth Gabeldorche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Aktivizon grupin e veçorive të Bluetooth Gabeldorsche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Terminali lokal"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Aktivizo aplikacionin terminal që ofron qasje në guaskën lokale"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"Kontrolli HDCP"</string>
diff --git a/packages/SettingsLib/res/values-sr/arrays.xml b/packages/SettingsLib/res/values-sr/arrays.xml
index c543ac1..a4e9156 100644
--- a/packages/SettingsLib/res/values-sr/arrays.xml
+++ b/packages/SettingsLib/res/values-sr/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (подразумевано)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Користи избор система (подразумевано)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index a395d7b..08e2bc8 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Омогући Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Верзија Bluetooth AVRCP-а"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Изаберите верзију Bluetooth AVRCP-а"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Верзија Bluetooth MAP-а"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Изаберите верзију Bluetooth MAP-а"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth аудио кодек"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Изаберите Bluetooth аудио кодек\n"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Брзина узорковања за Bluetooth аудио"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Проверава да ли су апликације инсталиране преко ADB-а/ADT-а штетне."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Биће приказани Bluetooth уређаји без назива (само са MAC адресама)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Онемогућава главно подешавање јачине звука на Bluetooth уређају у случају проблема са јачином звука на даљинским уређајима, као што су изузетно велика јачина звука или недостатак контроле."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Омогућава групу Bluetooth Gabeldorche функција."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Омогућава групу Bluetooth Gabeldorsche функција."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Локални терминал"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Омогући апл. терминала за приступ локалном командном окружењу"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP провера"</string>
diff --git a/packages/SettingsLib/res/values-sv/arrays.xml b/packages/SettingsLib/res/values-sv/arrays.xml
index c31b80c..b5b1186a 100644
--- a/packages/SettingsLib/res/values-sv/arrays.xml
+++ b/packages/SettingsLib/res/values-sv/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (standard)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Använd systemval (standardinställning)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index f021cc2..c0cdbc9 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Aktivera Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"AVRCP-version för Bluetooth"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Välj AVRCP-version för Bluetooth"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"MAP-version för Bluetooth"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Välj MAP-version för Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Ljudkodek för Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Aktivera ljudkodek för Bluetooth\nVal"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Samplingsfrekvens för Bluetooth-ljud"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Kontrollera om appar som installeras via ADB/ADT kan vara skadliga."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Bluetooth-enheter utan namn (enbart MAC-adresser) visas"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Inaktivera Bluetooth-funktionen Absolute volume om det skulle uppstå problem med volymen på fjärrenheter, t.ex. alldeles för hög volym eller brist på kontroll."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Aktiverar funktionsgruppen Bluetooth Gabeldorche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Aktiverar funktionsgruppen Bluetooth Gabeldorsche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Lokal terminal"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Aktivera en terminalapp som ger åtkomst till hyllor lokalt"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP-kontroll"</string>
diff --git a/packages/SettingsLib/res/values-sw/arrays.xml b/packages/SettingsLib/res/values-sw/arrays.xml
index ff48858..a29b74e 100644
--- a/packages/SettingsLib/res/values-sw/arrays.xml
+++ b/packages/SettingsLib/res/values-sw/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"RAMANI YA 1.2 (Chaguomsingi)"</item>
+    <item msgid="6817922176194686449">"RAMANI YA 1.3"</item>
+    <item msgid="3423518690032737851">"RAMANI YA 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"ramani ya 12"</item>
+    <item msgid="7073042887003102964">"ramani ya 13"</item>
+    <item msgid="8147982633566548515">"ramani ya 14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Tumia Uteuzi wa Mfumo (Chaguomsingi)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 42422fb..f00dea3 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Washa Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Toleo la Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Chagua Toleo la Bluetooth AVRCP"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Toleo la Ramani ya Bluetooth"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Chagua Toleo la Ramani ya Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Kodeki ya Sauti ya Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Weka Kodeki ya Sauti ya Bluetooth\nUteuzi"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Kiwango cha Sampuli ya Sauti ya Bluetooth"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Kagua iwapo programu zilizosakinishwa kupitia ADB/ADT zina tabia ya kudhuru."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Itaonyesha vifaa vya Bluetooth bila majina (anwani za MAC pekee)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Huzima kipengele cha Bluetooth cha sauti kamili kunapotokea matatizo ya sauti katika vifaa vya mbali kama vile sauti ya juu mno au inaposhindikana kuidhibiti."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Huwasha rafu ya kipengele cha Bluetooth Gabeldorche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Huwasha rafu ya kipengele ya Bluetooth Gabeldorsche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Kituo cha karibu"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Washa programu ya mwisho inayotoa ufikiaji mkuu wa karibu"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"Inakagua HDCP"</string>
diff --git a/packages/SettingsLib/res/values-ta/arrays.xml b/packages/SettingsLib/res/values-ta/arrays.xml
index 5668b6d..0f19148 100644
--- a/packages/SettingsLib/res/values-ta/arrays.xml
+++ b/packages/SettingsLib/res/values-ta/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (இயல்பாக)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"சாதனத் தேர்வைப் பயன்படுத்து (இயல்பு)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 295399d..52e0363 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -227,10 +227,11 @@
     <string name="tethering_hardware_offload" msgid="4116053719006939161">"வன்பொருள் விரைவுப்படுத்துதல் இணைப்பு முறை"</string>
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"பெயர்கள் இல்லாத புளூடூத் சாதனங்களைக் காட்டு"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"அப்சல்யூட் ஒலியளவு அம்சத்தை முடக்கு"</string>
-    <!-- no translation found for bluetooth_enable_gabeldorsche (9131730396242883416) -->
-    <skip />
+    <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorscheவை இயக்கு"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"புளூடூத் AVRCP பதிப்பு"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"புளூடூத் AVRCP பதிப்பைத் தேர்ந்தெடு"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"புளூடூத்தின் MAP பதிப்பு"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"புளூடூத்தின் MAP பதிப்பைத் தேர்வுசெய்க"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"புளூடூத் ஆடியோ கோடெக்"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"புளூடூத் ஆடியோ கோடெக்கைத் தொடங்கு\nதேர்வு"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"புளூடூத் ஆடியோ சாம்பிள் ரேட்"</string>
@@ -277,8 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"தீங்கு விளைவிக்கும் செயல்பாட்டை அறிய ADB/ADT மூலம் நிறுவப்பட்ட ஆப்ஸைச் சரிபார்."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"பெயர்கள் இல்லாத புளூடூத் சாதனங்கள் (MAC முகவரிகள் மட்டும்) காட்டப்படும்"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"மிகவும் அதிகமான ஒலியளவு அல்லது கட்டுப்பாடு இழப்பு போன்ற தொலைநிலைச் சாதனங்களில் ஏற்படும் ஒலி தொடர்பான சிக்கல்கள் இருக்கும் சமயங்களில், புளூடூத் அப்சல்யூட் ஒலியளவு அம்சத்தை முடக்கும்."</string>
-    <!-- no translation found for bluetooth_enable_gabeldorsche_summary (8472344901097607030) -->
-    <skip />
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"புளூடூத்தின் Gabeldorsche அம்சங்களை இயக்கும்."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"அக முனையம்"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"அக ஷெல் அணுகலை வழங்கும் இறுதிப் ஆப்ஸை இயக்கு"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP சரிபார்ப்பு"</string>
diff --git a/packages/SettingsLib/res/values-te/arrays.xml b/packages/SettingsLib/res/values-te/arrays.xml
index 70068bf..23256ee 100644
--- a/packages/SettingsLib/res/values-te/arrays.xml
+++ b/packages/SettingsLib/res/values-te/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (డిఫాల్ట్)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"సిస్టమ్ ఎంపికను ఉపయోగించండి (డిఫాల్ట్)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index f92b8af..a39c4e1 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorscheను ఎనేబుల్ చేయి"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"బ్లూటూత్ AVRCP వెర్షన్"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"బ్లూటూత్ AVRCP సంస్కరణను ఎంచుకోండి"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"బ్లూటూత్ MAP వెర్షన్‌"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"బ్లూటూత్ MAP వెర్షన్‌ను ఎంచుకోండి"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"బ్లూటూత్ ఆడియో కోడెక్"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"బ్లూటూత్ ఆడియో కోడెక్‌ని సక్రియం చేయండి\nఎంపిక"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"బ్లూటూత్ ఆడియో నమూనా రేట్"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"హానికరమైన ప్రవర్తన కోసం ADB/ADT ద్వారా ఇన్‌స్టాల్ చేయబడిన యాప్‌లను తనిఖీ చేయి."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"పేర్లు (MAC చిరునామాలు మాత్రమే) లేని బ్లూటూత్ పరికరాలు ప్రదర్శించబడతాయి"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"రిమోట్ పరికరాల్లో ఆమోదించలేని స్థాయిలో అధిక వాల్యూమ్ ఉండటం లేదా వాల్యూమ్ నియంత్రణ లేకపోవడం వంటి సమస్యలు ఉంటే బ్లూటూత్ సంపూర్ణ వాల్యూమ్ ఫీచర్‌ని నిలిపివేస్తుంది."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"బ్లూటూత్ ఫీచర్ స్ట్యాక్‌ను ఎనేబుల్ చేస్తుంది."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"బ్లూటూత్ Gabeldorsche ఫీచర్ స్ట్యాక్‌ను ఎనేబుల్ చేస్తుంది."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"స్థానిక టెర్మినల్"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"స్థానిక షెల్ ప్రాప్యతను అందించే టెర్మినల్ అనువర్తనాన్ని ప్రారంభించు"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP తనిఖీ"</string>
diff --git a/packages/SettingsLib/res/values-th/arrays.xml b/packages/SettingsLib/res/values-th/arrays.xml
index 20333b7..8aac165 100644
--- a/packages/SettingsLib/res/values-th/arrays.xml
+++ b/packages/SettingsLib/res/values-th/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (ค่าเริ่มต้น)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"ใช้การเลือกของระบบ (ค่าเริ่มต้น)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 19ae491..635d77a 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"เปิดใช้ Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"เวอร์ชันของบลูทูธ AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"เลือกเวอร์ชันของบลูทูธ AVRCP"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"เวอร์ชัน MAP ของบลูทูธ"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"เลือกเวอร์ชัน MAP ของบลูทูธ"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"ตัวแปลงสัญญาณเสียงบลูทูธ"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"ทริกเกอร์การเลือกตัวแปลงรหัส\nเสียงบลูทูธ"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"อัตราตัวอย่างเสียงบลูทูธ"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"ตรวจสอบแอปที่ติดตั้งผ่าน ADB/ADT เพื่อตรวจดูพฤติกรรมที่เป็นอันตราย"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"ระบบจะแสดงอุปกรณ์บลูทูธที่ไม่มีชื่อ (มีเฉพาะที่อยู่ MAC)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"ปิดใช้ฟีเจอร์การควบคุมระดับเสียงของอุปกรณ์อื่นผ่านบลูทูธในกรณีที่มีปัญหาเกี่ยวกับระดับเสียงของอุปกรณ์ระยะไกล เช่น ระดับเสียงที่ดังเกินไปหรือระดับเสียงที่ไม่มีการควบคุม"</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"เปิดใช้สแต็กฟีเจอร์ Bluetooth Gabeldorche"</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"เปิดใช้สแต็กฟีเจอร์ Bluetooth Gabeldorsche"</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"เทอร์มินัลในตัวเครื่อง"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"เปิดใช้งานแอปเทอร์มินัลที่ให้การเข้าถึงเชลล์ในตัวเครื่อง"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"การตรวจสอบ HDCP"</string>
diff --git a/packages/SettingsLib/res/values-tl/arrays.xml b/packages/SettingsLib/res/values-tl/arrays.xml
index 4734807..9e08b8f 100644
--- a/packages/SettingsLib/res/values-tl/arrays.xml
+++ b/packages/SettingsLib/res/values-tl/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (Default)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Gamitin ang Pagpili ng System (Default)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index f6dfdba..3f7f0ff 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"I-enable ang Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bersyon ng AVRCP ng Bluetooth"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Pumili ng Bersyon ng AVRCP ng Bluetooth"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bersyon ng MAP ng Bluetooth"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Pumili ng Bersyon ng MAP ng Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth Audio Codec"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"I-trigger ang Pagpili sa Audio Codec ng\nBluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Sample na Rate ng Bluetooth Audio"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Tingnan kung may nakakahamak na pagkilos sa apps na na-install sa pamamagitan ng ADB/ADT."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Ipapakita ang mga Bluetooth device na walang pangalan (mga MAC address lang)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Dini-disable ang absolute volume feature ng Bluetooth kung may mga isyu sa volume ang mga malayong device gaya ng hindi katanggap-tanggap na malakas na volume o kawalan ng kontrol."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Ine-enable ang stack ng feature ng Bluetooth Gabeldorche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Ine-enable ang stack ng feature ng Bluetooth Gabeldorsche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Lokal na terminal"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Paganahin ang terminal app na nag-aalok ng lokal na shell access"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"Pagsusuring HDCP"</string>
diff --git a/packages/SettingsLib/res/values-tr/arrays.xml b/packages/SettingsLib/res/values-tr/arrays.xml
index ac6e0f5..7ce6c24 100644
--- a/packages/SettingsLib/res/values-tr/arrays.xml
+++ b/packages/SettingsLib/res/values-tr/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (Varsayılan)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Sistem Seçimini Kullan (Varsayılan)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index 554d62e..7ad6fcd 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche\'yi etkileştir"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP Sürümü"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Bluetooth AVRCP Sürümünü seçin"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP Sürümü"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Bluetooth MAP Sürümünü seçin"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth Ses Codec\'i"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Bluetooth Ses Codec\'i Tetikleme\nSeçimi"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth Ses Örnek Hızı"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"ADB/ADT üzerinden yüklenen uygulamaları zararlı davranışlara karşı denetle."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Adsız Bluetooth cihazları (yalnızca MAC adresleri) gösterilecek"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Uzak cihazda sesin aşırı yüksek olması veya kontrol edilememesi gibi ses sorunları olması ihtimaline karşı Bluetooh mutlak ses özelliğini iptal eder."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Bluetooth Gabeldorche özellik grubunu etkinleştirir."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Bluetooth Gabeldorsche özellik yığınını etkinleştirir."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Yerel terminal"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Yerel kabuk erişimi sunan terminal uygulamasını etkinleştir"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP denetimi"</string>
diff --git a/packages/SettingsLib/res/values-uk/arrays.xml b/packages/SettingsLib/res/values-uk/arrays.xml
index effd496..2d0abe0 100644
--- a/packages/SettingsLib/res/values-uk/arrays.xml
+++ b/packages/SettingsLib/res/values-uk/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (за умовчанням)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Використовувати вибір системи (за умовчанням)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index 17f2393..b5dd618 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Увімкнути Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Версія Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Виберіть версію Bluetooth AVRCP"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Версія Bluetooth MAP"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Виберіть версію Bluetooth MAP"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Кодек для аудіо Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Активувати кодек для аудіо Bluetooth\nВибір"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Частота вибірки для аудіо Bluetooth"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Перевіряти безпеку додатків, установлених через ADB/ADT."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Пристрої Bluetooth відображатимуться без назв (лише MAC-адреси)"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Функція абсолютної гучності Bluetooth вимикається, якщо на віддалених пристроях виникають проблеми, як-от надто висока гучність або втрата контролю."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Вмикає функції Bluetooth Gabeldorche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Вмикає функції Bluetooth Gabeldorsche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Локальний термінал"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Увімк. програму-термінал, що надає локальний доступ до оболонки"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"Перевірка HDCP"</string>
diff --git a/packages/SettingsLib/res/values-ur/arrays.xml b/packages/SettingsLib/res/values-ur/arrays.xml
index d5a59ac..e056c1c 100644
--- a/packages/SettingsLib/res/values-ur/arrays.xml
+++ b/packages/SettingsLib/res/values-ur/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"‏MAP 1.2 ‏(ڈیفالٹ)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"سسٹم انتخاب کا استعمال کریں (ڈیفالٹ)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index 6830c59..ef9b2a1 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"‏Gabeldorsche فعال کریں"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"‏بلوٹوتھ AVRCP ورژن"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"‏بلوٹوتھ AVRCP ورژن منتخب کریں"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"‏بلوٹوتھ MAP ورژن"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"‏بلوٹوتھ MAP ورژن منتخب کریں"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"بلوٹوتھ آڈیو کوڈیک"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"بلوٹوتھ آڈیو کوڈیک کو ٹریگر کریں\nانتخاب"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"بلوٹوتھ آڈیو کے نمونے کی شرح"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"‏نقصان دہ رویے کے مدنظر ADB/ADT کی معرفت انسٹال شدہ ایپس کی جانچ کریں۔"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"‏بغیر نام والے بلوٹوتھ آلات (صرف MAC پتے) ڈسپلے کئے جائیں گے"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"ریموٹ آلات کے ساتھ والیوم کے مسائل مثلاً نا قابل قبول حد تک بلند والیوم یا کنٹرول نہ ہونے کی صورت میں بلو ٹوتھ مطلق والیوم والی خصوصیت کو غیر فعال کریں۔"</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"‏بلوٹوتھ Gabeldorche خصوصیت کے انبار کو فعال کرتا ہے۔"</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"‏بلوٹوتھ Gabeldorsche خصوصیت کے انبار کو فعال کرتا ہے۔"</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"مقامی ٹرمینل"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"مقامی شیل رسائی پیش کرنے والی ٹرمینل ایپ فعال کریں"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"‏HDCP چیکنگ"</string>
diff --git a/packages/SettingsLib/res/values-uz/arrays.xml b/packages/SettingsLib/res/values-uz/arrays.xml
index 4d30e46..892ebe0 100644
--- a/packages/SettingsLib/res/values-uz/arrays.xml
+++ b/packages/SettingsLib/res/values-uz/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (Standart)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Tizim tanlovi (birlamchi)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index 52ec545..b202d64 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gabeldorsche funksiyasini yoqish"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP versiyasi"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Bluetooth AVRCP versiyasini tanlang"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP versiyasi"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Bluetooth MAP versiyasini tanlang"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth audio kodeki"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Bluetooth orqali uzatish uchun audiokodek\nTanlash"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth audio namunasi chastotasi"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"ADB/ADT orqali o‘rnatilgan ilovalar xavfsizligini tekshiring"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Bluetooth qurilmalari nomsiz (faqat MAC manzillari) ko‘rsatiladi"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Masofadan ulanadigan qurilmalar bilan muammolar yuz berganda, jumladan, juda baland ovoz yoki sozlamalarni boshqarib bo‘lmaydigan holatlarda Bluetooth ovozi balandligining mutlaq darajasini o‘chirib qo‘yadi."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Bluetooth Gabeldorche funksiyasini ishga tushiradi."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Bluetooth Gabeldorsche funksiyasini ishga tushiradi."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Mahalliy terminal"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Mahalliy terminalga kirishga ruxsat beruvchi terminal ilovani faollashtirish"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP tekshiruvi"</string>
diff --git a/packages/SettingsLib/res/values-vi/arrays.xml b/packages/SettingsLib/res/values-vi/arrays.xml
index edfe89e..db29bc8 100644
--- a/packages/SettingsLib/res/values-vi/arrays.xml
+++ b/packages/SettingsLib/res/values-vi/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (Mặc định)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Sử dụng lựa chọn của hệ thống (Mặc định)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 2a12464..cda42d3 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Bật tính năng Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Phiên bản Bluetooth AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Chọn phiên bản Bluetooth AVRCP"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Phiên bản Bluetooth MAP"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Chọn phiên bản Bluetooth MAP"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Codec âm thanh Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Kích hoạt chế độ chọn codec\nâm thanh Bluetooth"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Tốc độ lấy mẫu âm thanh Bluetooth"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Kiểm tra các ứng dụng được cài đặt qua ADB/ADT để xem có hoạt động gây hại hay không."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Các thiết bị Bluetooth không có tên (chỉ có địa chỉ MAC) sẽ được hiển thị"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Vô hiệu hóa tính năng âm lượng tuyệt đối qua Bluetooth trong trường hợp xảy ra sự cố về âm lượng với các thiết bị từ xa, chẳng hạn như âm lượng lớn không thể chấp nhận được hoặc thiếu kiểm soát."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Bật ngăn xếp tính năng Bluetooth Gabeldorche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Bật ngăn xếp tính năng Bluetooth Gabeldorsche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Dòng lệnh cục bộ"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Bật ứng dụng dòng lệnh cung cấp quyền truy cập vỏ cục bộ"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"Kiểm tra HDCP"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/arrays.xml b/packages/SettingsLib/res/values-zh-rCN/arrays.xml
index 992e3e0..3016f65 100644
--- a/packages/SettingsLib/res/values-zh-rCN/arrays.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2(默认)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"使用系统选择(默认)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 3fe925b..418370b 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"启用“Gabeldorsche”"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"蓝牙 AVRCP 版本"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"选择蓝牙 AVRCP 版本"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"蓝牙 MAP 版本"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"选择蓝牙 MAP 版本"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"蓝牙音频编解码器"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"触发蓝牙音频编解码器\n选择"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"蓝牙音频采样率"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"检查通过 ADB/ADT 安装的应用是否存在有害行为。"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"系统将显示没有名称(只有 MAC 地址)的蓝牙设备"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"停用蓝牙绝对音量功能,即可避免在连接到远程设备时出现音量问题(例如音量高得让人无法接受或无法控制音量等)。"</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"启用“蓝牙 Gabeldorche”功能堆栈。"</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"启用“蓝牙 Gabeldorsche”功能堆栈。"</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"本地终端"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"启用终端应用,以便在本地访问 Shell"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP 检查"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/arrays.xml b/packages/SettingsLib/res/values-zh-rHK/arrays.xml
index d91e61e..0b57af9 100644
--- a/packages/SettingsLib/res/values-zh-rHK/arrays.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (預設)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"使用系統選擇 (預設)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index ed6d505..79b5579 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"啟用 Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"藍牙 AVRCP 版本"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"選擇藍牙 AVRCP 版本"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"藍牙 MAP 版本"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"選擇藍牙 MAP 版本"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"藍牙音訊編解碼器"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"觸發藍牙音訊編解碼器\n選項"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"藍牙音訊取樣率"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"透過 ADB/ADT 檢查安裝的應用程式有否有害的行為。"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"系統將顯示沒有名稱 (只有 MAC 位址) 的藍牙裝置"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"連線至遠端裝置時,如發生音量過大或無法控制音量等問題,請停用藍牙絕對音量功能。"</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"啟用藍牙 Gabeldorche 功能組合。"</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"啟用藍牙 Gabeldorsche 功能組合。"</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"本機終端機"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"啟用可提供本機命令介面存取權的終端機應用程式"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP 檢查"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/arrays.xml b/packages/SettingsLib/res/values-zh-rTW/arrays.xml
index f39ab84..7b25772 100644
--- a/packages/SettingsLib/res/values-zh-rTW/arrays.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"avrcp15"</item>
     <item msgid="1963366694959681026">"avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"MAP 1.2 (預設)"</item>
+    <item msgid="6817922176194686449">"MAP 1.3"</item>
+    <item msgid="3423518690032737851">"MAP 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"map12"</item>
+    <item msgid="7073042887003102964">"map13"</item>
+    <item msgid="8147982633566548515">"map14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"系統自動選擇 (預設)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 45866d4..47ab764 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"啟用 Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"藍牙 AVRCP 版本"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"選取藍牙 AVRCP 版本"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"藍牙 MAP 版本"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"選取 Bluetooth MAP 版本"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"藍牙音訊轉碼器"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"觸發藍牙音訊轉碼器\n選項"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"藍牙音訊取樣率"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"檢查透過 ADB/ADT 安裝的應用程式是否具有有害行為。"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"系統會顯示沒有名稱 (僅具有 MAC 位址) 的藍牙裝置"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"只要停用藍牙絕對音量功能,即可避免在連線到遠端裝置時,發生音量過大或無法控制音量等問題。"</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"啟用藍牙 Gabeldorsche 功能堆疊。"</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"啟用藍牙 Gabeldorsche 功能堆疊。"</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"本機終端機"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"啟用可提供本機命令介面存取權的終端機應用程式"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP 檢查"</string>
diff --git a/packages/SettingsLib/res/values-zu/arrays.xml b/packages/SettingsLib/res/values-zu/arrays.xml
index 5c93cc5..517d1c8 100644
--- a/packages/SettingsLib/res/values-zu/arrays.xml
+++ b/packages/SettingsLib/res/values-zu/arrays.xml
@@ -75,6 +75,16 @@
     <item msgid="4398977131424970917">"I-avrcp15"</item>
     <item msgid="1963366694959681026">"I-avrcp16"</item>
   </string-array>
+  <string-array name="bluetooth_map_versions">
+    <item msgid="8786402640610987099">"IMEPHU 1.2 (Okuzenzakalelayo)"</item>
+    <item msgid="6817922176194686449">"IMEPHU 1.3"</item>
+    <item msgid="3423518690032737851">"IMEPHU 1.4"</item>
+  </string-array>
+  <string-array name="bluetooth_map_version_values">
+    <item msgid="1164651830068248391">"Imephu12"</item>
+    <item msgid="7073042887003102964">"Imephu13"</item>
+    <item msgid="8147982633566548515">"Imephu14"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
     <item msgid="2494959071796102843">"Sebenzisa ukukhetha kwesistimu (Okuzenzakalelayo)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index 8b004f9..87f45de 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -230,6 +230,8 @@
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Nika amandla i-Gabeldorsche"</string>
     <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Inguqulo ye-Bluetooth ye-AVRCP"</string>
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Khetha inguqulo ye-Bluetooth AVRCP"</string>
+    <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Inguqulo ye-Bluetooth MAP"</string>
+    <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Khetha inguqulo ye-Bluetooth MAP"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"I-Bluetooth Audio Codec"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Qalisa i-codec ye-bluetooth yomsindo\nUkukhethwa"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Isilinganiso sesampula yomsindo we-Bluetooth"</string>
@@ -276,7 +278,7 @@
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Hlola izinhlelo zokusebenza ezifakiwe nge-ADB/ADT ngokuziphatha okuyingozi."</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Amadivayisi e-Bluetooth anganawo amagama (Amakheli e-MAC kuphela) azoboniswa"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Ikhubaza isici esiphelele sevolumu ye-Bluetooth uma kuba nezinkinga zevolumu ngamadivayisi esilawuli kude ezifana nevolumu ephezulu noma eshoda ngokulawuleka."</string>
-    <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Inika amandla isitaki sesici se-Bluetooth Gabeldorche."</string>
+    <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Inika amandla isitaki sesici se-Bluetooth Gabeldorsche."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Itheminali yasendaweni"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Nika amandla uhlelo lokusebenza letheminali olunikeza ukufinyelela kwasendaweni kwe-shell"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"Ihlola i-HDCP"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
index 69bd0ed..ff00fb3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
@@ -16,8 +16,6 @@
 
 package com.android.settingslib;
 
-import static android.content.Context.TELEPHONY_SERVICE;
-
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -172,36 +170,38 @@
         }
     }
 
-    public static String getFormattedPhoneNumber(Context context, SubscriptionInfo subscriptionInfo) {
+    /**
+     * Format a phone number.
+     * @param subscriptionInfo {@link SubscriptionInfo} subscription information.
+     * @return Returns formatted phone number.
+     */
+    public static String getFormattedPhoneNumber(Context context,
+            SubscriptionInfo subscriptionInfo) {
         String formattedNumber = null;
         if (subscriptionInfo != null) {
-            final TelephonyManager telephonyManager =
-                    (TelephonyManager) context.getSystemService(TELEPHONY_SERVICE);
-            final String rawNumber =
-                    telephonyManager.getLine1Number(subscriptionInfo.getSubscriptionId());
+            final TelephonyManager telephonyManager = context.getSystemService(
+                    TelephonyManager.class);
+            final String rawNumber = telephonyManager.createForSubscriptionId(
+                    subscriptionInfo.getSubscriptionId()).getLine1Number();
             if (!TextUtils.isEmpty(rawNumber)) {
                 formattedNumber = PhoneNumberUtils.formatNumber(rawNumber);
             }
-
         }
         return formattedNumber;
     }
 
     public static String getFormattedPhoneNumbers(Context context,
-            List<SubscriptionInfo> subscriptionInfo) {
+            List<SubscriptionInfo> subscriptionInfoList) {
         StringBuilder sb = new StringBuilder();
-        if (subscriptionInfo != null) {
-            final TelephonyManager telephonyManager =
-                    (TelephonyManager) context.getSystemService(TELEPHONY_SERVICE);
-            final int count = subscriptionInfo.size();
-            for (int i = 0; i < count; i++) {
-                final String rawNumber = telephonyManager.getLine1Number(
-                        subscriptionInfo.get(i).getSubscriptionId());
+        if (subscriptionInfoList != null) {
+            final TelephonyManager telephonyManager = context.getSystemService(
+                    TelephonyManager.class);
+            final int count = subscriptionInfoList.size();
+            for (SubscriptionInfo subscriptionInfo : subscriptionInfoList) {
+                final String rawNumber = telephonyManager.createForSubscriptionId(
+                        subscriptionInfo.getSubscriptionId()).getLine1Number();
                 if (!TextUtils.isEmpty(rawNumber)) {
-                    sb.append(PhoneNumberUtils.formatNumber(rawNumber));
-                    if (i < count - 1) {
-                        sb.append("\n");
-                    }
+                    sb.append(PhoneNumberUtils.formatNumber(rawNumber)).append("\n");
                 }
             }
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index a2bd210..a784e04 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
@@ -148,17 +151,18 @@
     }
 
     public boolean connect(BluetoothDevice device) {
-        if (mService == null) return false;
-        return mService.connect(device);
+        if (mService == null) {
+            return false;
+        }
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) return false;
-        // Downgrade priority as user is disconnecting the headset.
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        if (mService == null) {
+            return false;
         }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -182,12 +186,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -197,11 +201,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
     boolean isA2dpPlaying() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
index bc03c34..8ca5a74 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothA2dpSink;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
@@ -116,18 +119,15 @@
         if (mService == null) {
             return false;
         }
-        return mService.connect(device);
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
-        // Downgrade priority as user is disconnecting the headset.
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
-        }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -141,12 +141,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -156,11 +156,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 747ceb1..50d3a5d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -262,13 +262,28 @@
         }
     }
 
+    /**
+     * Connect this device.
+     *
+     * @param connectAllProfiles {@code true} to connect all profile, {@code false} otherwise.
+     *
+     * @deprecated use {@link #connect()} instead.
+     */
+    @Deprecated
     public void connect(boolean connectAllProfiles) {
+        connect();
+    }
+
+    /**
+     * Connect this device.
+     */
+    public void connect() {
         if (!ensurePaired()) {
             return;
         }
 
         mConnectAttempted = SystemClock.elapsedRealtime();
-        connectWithoutResettingTimer(connectAllProfiles);
+        connectAllEnabledProfiles();
     }
 
     public long getHiSyncId() {
@@ -289,10 +304,10 @@
     void onBondingDockConnect() {
         // Attempt to connect if UUIDs are available. Otherwise,
         // we will connect when the ACTION_UUID intent arrives.
-        connect(false);
+        connect();
     }
 
-    private void connectWithoutResettingTimer(boolean connectAllProfiles) {
+    private void connectAllEnabledProfiles() {
         synchronized (mProfileLock) {
             // Try to initialize the profiles if they were not.
             if (mProfiles.isEmpty()) {
@@ -307,36 +322,7 @@
                 return;
             }
 
-            int preferredProfiles = 0;
-            for (LocalBluetoothProfile profile : mProfiles) {
-                if (connectAllProfiles ? profile.accessProfileEnabled()
-                        : profile.isAutoConnectable()) {
-                    if (profile.isPreferred(mDevice)) {
-                        ++preferredProfiles;
-                        connectInt(profile);
-                    }
-                }
-            }
-            if (BluetoothUtils.D) Log.d(TAG, "Preferred profiles = " + preferredProfiles);
-
-            if (preferredProfiles == 0) {
-                connectAutoConnectableProfiles();
-            }
-        }
-    }
-
-    private void connectAutoConnectableProfiles() {
-        if (!ensurePaired()) {
-            return;
-        }
-
-        synchronized (mProfileLock) {
-            for (LocalBluetoothProfile profile : mProfiles) {
-                if (profile.isAutoConnectable()) {
-                    profile.setPreferred(mDevice, true);
-                    connectInt(profile);
-                }
-            }
+            mLocalAdapter.connectAllEnabledProfiles(mDevice);
         }
     }
 
@@ -703,7 +689,7 @@
          */
         if (!mProfiles.isEmpty()
                 && ((mConnectAttempted + timeout) > SystemClock.elapsedRealtime())) {
-            connectWithoutResettingTimer(false);
+            connectAllEnabledProfiles();
         }
 
         dispatchAttributesChanged();
@@ -722,7 +708,7 @@
         refresh();
 
         if (bondState == BluetoothDevice.BOND_BONDED && mDevice.isBondingInitiatedLocally()) {
-            connect(false);
+            connect();
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
index 560cb3b..d65b5da 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -112,18 +115,15 @@
         if (mService == null) {
             return false;
         }
-        return mService.connect(device);
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
-        // Downgrade priority as user is disconnecting the headset.
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
-        }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -165,12 +165,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -180,11 +180,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
index b4b55f3..9f1af66 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -146,17 +149,18 @@
     }
 
     public boolean connect(BluetoothDevice device) {
-        if (mService == null) return false;
-        return mService.connect(device);
+        if (mService == null) {
+            return false;
+        }
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) return false;
-        // Downgrade priority as user is disconnecting the hearing aid.
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        if (mService == null) {
+            return false;
         }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -180,12 +184,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -195,11 +199,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
index a372e23..678f2e3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -126,7 +129,7 @@
         if (mService == null) {
             return false;
         }
-        return mService.connect(device);
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     @Override
@@ -134,11 +137,8 @@
         if (mService == null) {
             return false;
         }
-        // Downgrade priority as user is disconnecting the headset.
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
-        }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     @Override
@@ -154,13 +154,13 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     @Override
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -171,11 +171,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
index 975a1e6..588083e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -99,13 +102,17 @@
     }
 
     public boolean connect(BluetoothDevice device) {
-        if (mService == null) return false;
-        return mService.connect(device);
+        if (mService == null) {
+            return false;
+        }
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) return false;
-        return mService.disconnect(device);
+        if (mService == null) {
+            return false;
+        }
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -119,12 +126,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) != CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -132,11 +139,11 @@
     public void setPreferred(BluetoothDevice device, boolean preferred) {
         if (mService == null) return;
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
index 95139a1..7d121aa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -115,18 +118,15 @@
         if (mService == null) {
             return false;
         }
-        return mService.connect(device);
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
-        // Downgrade priority as user is disconnecting.
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
-        }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -140,12 +140,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -155,11 +155,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
index 31a0eea..a96a4e7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
@@ -16,6 +16,8 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -119,10 +121,8 @@
         if (mService == null) {
             return false;
         }
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
-        }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -136,12 +136,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -155,7 +155,7 @@
                 mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
index 4ea0df6..56267fc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -129,7 +132,7 @@
             return false;
         }
         Log.d(TAG,"PBAPClientProfile attempting to connect to " + device.getAddress());
-        return mService.connect(device);
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
@@ -137,7 +140,7 @@
         if (mService == null) {
             return false;
         }
-        return mService.disconnect(device);
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -151,12 +154,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -166,11 +169,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
index 3f920a8..f7c0bf5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
@@ -16,6 +16,8 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -96,8 +98,10 @@
     }
 
     public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) return false;
-        return mService.disconnect(device);
+        if (mService == null) {
+            return false;
+        }
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
index 0ca4d61..3022c5b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -112,17 +115,15 @@
         if (mService == null) {
             return false;
         }
-        return mService.connect(device);
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
-        }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -136,12 +137,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -151,11 +152,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
index 61e47f8..6e7a429 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
@@ -87,8 +87,10 @@
         if (mMetricsFeature == null || mMetricsCategory == METRICS_CATEGORY_UNKNOWN) {
             return;
         }
-        final int elapse = (int) (SystemClock.elapsedRealtime() - mCreationTimestamp);
-        mMetricsFeature.action(METRICS_CATEGORY_UNKNOWN, action, mMetricsCategory, key, elapse);
+        if (mCreationTimestamp != 0L) {
+            final int elapse = (int) (SystemClock.elapsedRealtime() - mCreationTimestamp);
+            mMetricsFeature.action(METRICS_CATEGORY_UNKNOWN, action, mMetricsCategory, key, elapse);
+        }
     }
 
     /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractImsStatusPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractImsStatusPreferenceController.java
index 4c3e605..d427f7a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractImsStatusPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractImsStatusPreferenceController.java
@@ -23,7 +23,9 @@
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
+import android.telephony.ims.ImsMmTelManager;
+import android.telephony.ims.RegistrationManager;
+import android.util.Log;
 
 import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
@@ -32,15 +34,26 @@
 import com.android.settingslib.R;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
 /**
  * Preference controller for IMS status
  */
 public abstract class AbstractImsStatusPreferenceController
         extends AbstractConnectivityPreferenceController {
 
+    private static final String LOG_TAG = "AbstractImsPrefController";
+
     @VisibleForTesting
     static final String KEY_IMS_REGISTRATION_STATE = "ims_reg_state";
 
+    private static final long MAX_THREAD_BLOCKING_TIME_MS = 2000;
+
     private static final String[] CONNECTIVITY_INTENTS = {
             BluetoothAdapter.ACTION_STATE_CHANGED,
             ConnectivityManager.CONNECTIVITY_ACTION,
@@ -57,8 +70,9 @@
 
     @Override
     public boolean isAvailable() {
-        CarrierConfigManager configManager = mContext.getSystemService(CarrierConfigManager.class);
-        int subId = SubscriptionManager.getDefaultDataSubscriptionId();
+        final CarrierConfigManager configManager =
+                mContext.getSystemService(CarrierConfigManager.class);
+        final int subId = SubscriptionManager.getDefaultDataSubscriptionId();
         PersistableBundle config = null;
         if (configManager != null) {
             config = configManager.getConfigForSubId(subId);
@@ -86,11 +100,57 @@
 
     @Override
     protected void updateConnectivity() {
-        int subId = SubscriptionManager.getDefaultDataSubscriptionId();
-        if (mImsStatus != null) {
-            TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
-            mImsStatus.setSummary((tm != null && tm.isImsRegistered(subId)) ?
-                    R.string.ims_reg_status_registered : R.string.ims_reg_status_not_registered);
+        if (mImsStatus == null) {
+            return;
+        }
+        final int subId = SubscriptionManager.getDefaultDataSubscriptionId();
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            mImsStatus.setSummary(R.string.ims_reg_status_not_registered);
+            return;
+        }
+        final ExecutorService executors = Executors.newSingleThreadExecutor();
+        final StateCallback stateCallback = new StateCallback();
+
+        final ImsMmTelManager imsMmTelManager = ImsMmTelManager.createForSubscriptionId(subId);
+        try {
+            imsMmTelManager.getRegistrationState(executors, stateCallback);
+        } catch (Exception ex) {
+        }
+
+        mImsStatus.setSummary(stateCallback.waitUntilResult()
+                ? R.string.ims_reg_status_registered : R.string.ims_reg_status_not_registered);
+
+        try {
+            executors.shutdownNow();
+        } catch (Exception exception) {
+        }
+    }
+
+    private final class StateCallback extends AtomicBoolean implements Consumer<Integer> {
+        private StateCallback() {
+            super(false);
+            mSemaphore = new Semaphore(0);
+        }
+
+        private final Semaphore mSemaphore;
+
+        public void accept(Integer state) {
+            set(state == RegistrationManager.REGISTRATION_STATE_REGISTERED);
+            try {
+                mSemaphore.release();
+            } catch (Exception ex) {
+            }
+        }
+
+        public boolean waitUntilResult() {
+            try {
+                if (!mSemaphore.tryAcquire(MAX_THREAD_BLOCKING_TIME_MS, TimeUnit.MILLISECONDS)) {
+                    Log.w(LOG_TAG, "IMS registration state query timeout");
+                    return false;
+                }
+            } catch (Exception ex) {
+            }
+            return get();
         }
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
index 4ab9a9a..b07fc2b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
@@ -61,6 +61,8 @@
             "com.android.settings.category.ia.my_device_info";
     public static final String CATEGORY_BATTERY_SAVER_SETTINGS =
             "com.android.settings.category.ia.battery_saver_settings";
+    public static final String CATEGORY_SMART_BATTERY_SETTINGS =
+            "com.android.settings.category.ia.smart_battery_settings";
 
     public static final Map<String, String> KEY_COMPAT_MAP;
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 5b4ef3a..e85a102 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -122,7 +122,7 @@
             final CachedBluetoothDevice cachedDevice =
                     ((BluetoothMediaDevice) device).getCachedDevice();
             if (!cachedDevice.isConnected() && !cachedDevice.isBusy()) {
-                cachedDevice.connect(true);
+                cachedDevice.connect();
                 return;
             }
         }
@@ -194,6 +194,14 @@
         }
     }
 
+    void dispatchDeviceAttributesChanged() {
+        synchronized (mCallbacks) {
+            for (DeviceCallback callback : mCallbacks) {
+                callback.onDeviceAttributesChanged();
+            }
+        }
+    }
+
     /**
      * Stop scan MediaDevice
      */
@@ -306,14 +314,12 @@
             }
             mCurrentConnectedDevice = connectDevice;
             updatePhoneMediaDeviceSummary();
-            dispatchDeviceListUpdate();
+            dispatchDeviceAttributesChanged();
         }
 
         @Override
         public void onDeviceAttributesChanged() {
-            addPhoneDeviceIfNecessary();
-            removePhoneMediaDeviceIfNecessary();
-            dispatchDeviceListUpdate();
+            dispatchDeviceAttributesChanged();
         }
     }
 
@@ -327,7 +333,7 @@
          *
          * @param devices MediaDevice list
          */
-        void onDeviceListUpdate(List<MediaDevice> devices);
+        default void onDeviceListUpdate(List<MediaDevice> devices) {};
 
         /**
          * Callback for notifying the connected device is changed.
@@ -338,6 +344,12 @@
          * {@link MediaDeviceState#STATE_CONNECTING},
          * {@link MediaDeviceState#STATE_DISCONNECTED}
          */
-        void onSelectedDeviceStateChanged(MediaDevice device, @MediaDeviceState int state);
+        default void onSelectedDeviceStateChanged(MediaDevice device,
+                @MediaDeviceState int state) {};
+
+        /**
+         * Callback for notifying the device attributes is changed.
+         */
+        default void onDeviceAttributesChanged() {};
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
index ebca962..9d7e2c8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
@@ -38,9 +38,9 @@
         final SubscriptionManager subscriptionManager = context.getSystemService(
                 SubscriptionManager.class);
         final NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll(
-                telephonyManager.getSubscriberId(subId));
+                telephonyManager.getSubscriberId());
 
-        if (!subscriptionManager.isActiveSubId(subId)) {
+        if (!subscriptionManager.isActiveSubscriptionId(subId)) {
             Log.i(TAG, "Subscription is not active: " + subId);
             return mobileAll;
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 81739e0..9d4c24e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -211,13 +211,6 @@
     private static final int EAP_WPA = 1; // WPA-EAP
     private static final int EAP_WPA2_WPA3 = 2; // RSN-EAP
 
-    /**
-     * The number of distinct wifi levels.
-     *
-     * <p>Must keep in sync with {@link R.array.wifi_signal} and {@link WifiManager#RSSI_LEVELS}.
-     */
-    public static final int SIGNAL_LEVELS = 5;
-
     public static final int UNREACHABLE_RSSI = Integer.MIN_VALUE;
 
     public static final String KEY_PREFIX_AP = "AP:";
@@ -453,9 +446,10 @@
             return other.getSpeed() - getSpeed();
         }
 
+        WifiManager wifiManager = getWifiManager();
         // Sort by signal strength, bucketed by level
-        int difference = WifiManager.calculateSignalLevel(other.mRssi, SIGNAL_LEVELS)
-                - WifiManager.calculateSignalLevel(mRssi, SIGNAL_LEVELS);
+        int difference = wifiManager.calculateSignalLevel(other.mRssi)
+                - wifiManager.calculateSignalLevel(mRssi);
         if (difference != 0) {
             return difference;
         }
@@ -869,13 +863,14 @@
     }
 
     /**
-     * Returns the number of levels to show for a Wifi icon, from 0 to {@link #SIGNAL_LEVELS}-1.
+     * Returns the number of levels to show for a Wifi icon, from 0 to
+     * {@link WifiManager#getMaxSignalLevel()}.
      *
-     * <p>Use {@#isReachable()} to determine if an AccessPoint is in range, as this method will
+     * <p>Use {@link #isReachable()} to determine if an AccessPoint is in range, as this method will
      * always return at least 0.
      */
     public int getLevel() {
-        return WifiManager.calculateSignalLevel(mRssi, SIGNAL_LEVELS);
+        return getWifiManager().calculateSignalLevel(mRssi);
     }
 
     public int getRssi() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
index 4a4f0e6..f21e466 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
@@ -22,6 +22,7 @@
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
 import android.os.Bundle;
 import android.os.Parcelable;
 
@@ -126,13 +127,15 @@
     @Keep
     public TestAccessPointBuilder setLevel(int level) {
         // Reversal of WifiManager.calculateSignalLevels
+        WifiManager wifiManager = mContext.getSystemService(WifiManager.class);
+        int maxSignalLevel = wifiManager.getMaxSignalLevel();
         if (level == 0) {
             mRssi = MIN_RSSI;
-        } else if (level >= AccessPoint.SIGNAL_LEVELS) {
+        } else if (level > maxSignalLevel) {
             mRssi = MAX_RSSI;
         } else {
             float inputRange = MAX_RSSI - MIN_RSSI;
-            float outputRange = AccessPoint.SIGNAL_LEVELS - 1;
+            float outputRange = maxSignalLevel;
             mRssi = (int) (level * inputRange / outputRange + MIN_RSSI);
         }
         return this;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
index 440315f..4ebb102 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
@@ -118,7 +118,7 @@
             notifyChanged();
         }
 
-        setSummary(mWifiEntry.getSummary());
+        setSummary(mWifiEntry.getSummary(false /* concise */));
         mContentDescription = buildContentDescription();
     }
 
@@ -199,8 +199,7 @@
             return;
         }
         if ((mWifiEntry.getSecurity() != WifiEntry.SECURITY_NONE)
-                && (mWifiEntry.getSecurity() != WifiEntry.SECURITY_OWE)
-                && (mWifiEntry.getSecurity() != WifiEntry.SECURITY_OWE_TRANSITION)) {
+                && (mWifiEntry.getSecurity() != WifiEntry.SECURITY_OWE)) {
             mFrictionSld.setState(STATE_SECURED);
         } else if (mWifiEntry.isMetered()) {
             mFrictionSld.setState(STATE_METERED);
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 8591116..3f55cea 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -89,7 +89,7 @@
     public void setListening(boolean listening) {
         if (listening) {
             mNetworkScoreManager.registerNetworkScoreCache(NetworkKey.TYPE_WIFI,
-                    mWifiNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK);
+                    mWifiNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_CURRENT_NETWORK);
             mWifiNetworkScoreCache.registerListener(mCacheListener);
             mConnectivityManager.registerNetworkCallback(
                     mNetworkRequest, mNetworkCallback, mHandler);
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index ba6a8ea..ed4ff08 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -361,7 +361,7 @@
         mNetworkScoreManager.registerNetworkScoreCache(
                 NetworkKey.TYPE_WIFI,
                 mScoreCache,
-                NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS);
+                NetworkScoreManager.SCORE_FILTER_SCAN_RESULTS);
     }
 
     private void requestScoresForNetworkKeys(Collection<NetworkKey> keys) {
diff --git a/packages/SettingsLib/tests/integ/Android.bp b/packages/SettingsLib/tests/integ/Android.bp
index 4600793..2ccff1e 100644
--- a/packages/SettingsLib/tests/integ/Android.bp
+++ b/packages/SettingsLib/tests/integ/Android.bp
@@ -14,7 +14,10 @@
 
 android_test {
     name: "SettingsLibTests",
-    defaults: ["SettingsLibDefaults"],
+    defaults: [
+        "SettingsLibDefaults",
+        "framework-wifi-test-defaults"
+    ],
 
     certificate: "platform",
 
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
index 61cbbd3..03201ae 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
@@ -83,7 +83,6 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class AccessPointTest {
-
     private static final String TEST_SSID = "\"test_ssid\"";
     private static final String ROAMING_SSID = "\"roaming_ssid\"";
     private static final String OSU_FRIENDLY_NAME = "osu_friendly_name";
@@ -98,6 +97,7 @@
             20 * DateUtils.MINUTE_IN_MILLIS;
 
     private Context mContext;
+    private int mMaxSignalLevel;
     private WifiInfo mWifiInfo;
     @Mock private Context mMockContext;
     @Mock private WifiManager mMockWifiManager;
@@ -128,6 +128,7 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mContext = InstrumentationRegistry.getTargetContext();
+        mMaxSignalLevel = mContext.getSystemService(WifiManager.class).getMaxSignalLevel();
         mWifiInfo = new WifiInfo();
         mWifiInfo.setSSID(WifiSsid.createFromAsciiEncoded(TEST_SSID));
         mWifiInfo.setBSSID(TEST_BSSID);
@@ -180,7 +181,7 @@
 
     @Test
     public void testCompareTo_GivesHighLevelBeforeLowLevel() {
-        final int highLevel = AccessPoint.SIGNAL_LEVELS - 1;
+        final int highLevel = mMaxSignalLevel;
         final int lowLevel = 1;
         assertThat(highLevel).isGreaterThan(lowLevel);
 
@@ -226,7 +227,7 @@
                 .setReachable(true).build();
         AccessPoint saved = new TestAccessPointBuilder(mContext).setSaved(true).build();
         AccessPoint highLevelAndReachable = new TestAccessPointBuilder(mContext)
-                .setLevel(AccessPoint.SIGNAL_LEVELS - 1).build();
+                .setLevel(mMaxSignalLevel).build();
         AccessPoint firstName = new TestAccessPointBuilder(mContext).setSsid("a").build();
         AccessPoint lastname = new TestAccessPointBuilder(mContext).setSsid("z").build();
 
@@ -520,6 +521,8 @@
         when(packageManager.getApplicationInfoAsUser(eq(appPackageName), anyInt(), anyInt()))
                 .thenReturn(applicationInfo);
         when(applicationInfo.loadLabel(packageManager)).thenReturn(appLabel);
+        when(context.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager);
+        when(mMockWifiManager.calculateSignalLevel(rssi)).thenReturn(4);
 
         NetworkInfo networkInfo =
                 new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0 /* subtype */, "WIFI", "");
@@ -636,14 +639,14 @@
     public void testBuilder_setLevel() {
         AccessPoint testAp;
 
-        for (int i = 0; i < AccessPoint.SIGNAL_LEVELS; i++) {
+        for (int i = 0; i <= mMaxSignalLevel; i++) {
             testAp = new TestAccessPointBuilder(mContext).setLevel(i).build();
             assertThat(testAp.getLevel()).isEqualTo(i);
         }
 
         // numbers larger than the max level should be set to max
-        testAp = new TestAccessPointBuilder(mContext).setLevel(AccessPoint.SIGNAL_LEVELS).build();
-        assertThat(testAp.getLevel()).isEqualTo(AccessPoint.SIGNAL_LEVELS - 1);
+        testAp = new TestAccessPointBuilder(mContext).setLevel(mMaxSignalLevel + 1).build();
+        assertThat(testAp.getLevel()).isEqualTo(mMaxSignalLevel);
 
         // numbers less than 0 should give level 0
         testAp = new TestAccessPointBuilder(mContext).setLevel(-100).build();
@@ -653,7 +656,7 @@
     @Test
     public void testBuilder_settingReachableAfterLevelDoesNotAffectLevel() {
         int level = 1;
-        assertThat(level).isLessThan(AccessPoint.SIGNAL_LEVELS - 1);
+        assertThat(level).isLessThan(mMaxSignalLevel);
 
         AccessPoint testAp =
                 new TestAccessPointBuilder(mContext).setLevel(level).setReachable(true).build();
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/OWNER b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/OWNER
new file mode 100644
index 0000000..5c2a7b8
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/OWNER
@@ -0,0 +1,4 @@
+# People who can approve changes for submission
+arcwang@google.com
+govenliu@google.com
+qal@google.com
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java
index 976445e..ccb6646 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.verify;
@@ -67,13 +70,13 @@
     @Test
     public void connect_shouldConnectBluetoothA2dpSink() {
         mProfile.connect(mBluetoothDevice);
-        verify(mService).connect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
     }
 
     @Test
     public void disconnect_shouldDisconnectBluetoothA2dpSink() {
         mProfile.disconnect(mBluetoothDevice);
-        verify(mService).disconnect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java
index 69c020d..9180760 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.verify;
@@ -67,13 +70,13 @@
     @Test
     public void connect_shouldConnectBluetoothHeadsetClient() {
         mProfile.connect(mBluetoothDevice);
-        verify(mService).connect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
     }
 
     @Test
     public void disconnect_shouldDisconnectBluetoothHeadsetClient() {
         mProfile.disconnect(mBluetoothDevice);
-        verify(mService).disconnect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java
index 6f66709..1425c38 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.verify;
@@ -67,13 +70,13 @@
     @Test
     public void connect_shouldConnectBluetoothMapClient() {
         mProfile.connect(mBluetoothDevice);
-        verify(mService).connect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
     }
 
     @Test
     public void disconnect_shouldDisconnectBluetoothMapClient() {
         mProfile.disconnect(mBluetoothDevice);
-        verify(mService).disconnect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java
index b21ec9c3..15f560b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.verify;
@@ -67,13 +70,13 @@
     @Test
     public void connect_shouldConnectBluetoothPbapClient() {
         mProfile.connect(mBluetoothDevice);
-        verify(mService).connect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
     }
 
     @Test
     public void disconnect_shouldDisconnectBluetoothPbapClient() {
         mProfile.disconnect(mBluetoothDevice);
-        verify(mService).disconnect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java
index ec88034..4f978a8 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.verify;
@@ -66,13 +69,13 @@
     @Test
     public void connect_shouldConnectBluetoothSap() {
         mProfile.connect(mBluetoothDevice);
-        verify(mService).connect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
     }
 
     @Test
     public void disconnect_shouldDisconnectBluetoothSap() {
         mProfile.disconnect(mBluetoothDevice);
-        verify(mService).disconnect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
index 605c861..340a6c7 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
@@ -59,8 +59,9 @@
         allKeys.add(CategoryKey.CATEGORY_SYSTEM_DEVELOPMENT);
         allKeys.add(CategoryKey.CATEGORY_GESTURES);
         allKeys.add(CategoryKey.CATEGORY_NIGHT_DISPLAY);
+        allKeys.add(CategoryKey.CATEGORY_SMART_BATTERY_SETTINGS);
         // DO NOT REMOVE ANYTHING ABOVE
 
-        assertThat(allKeys.size()).isEqualTo(18);
+        assertThat(allKeys.size()).isEqualTo(19);
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index 98bb74a..c780a64 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -127,7 +127,7 @@
         mLocalMediaManager.registerCallback(mCallback);
         mLocalMediaManager.connectDevice(device);
 
-        verify(cachedDevice).connect(true);
+        verify(cachedDevice).connect();
     }
 
     @Test
@@ -355,7 +355,7 @@
         mLocalMediaManager.mMediaDeviceCallback.onConnectedDeviceChanged(TEST_DEVICE_ID_2);
 
         assertThat(mLocalMediaManager.getCurrentConnectedDevice()).isEqualTo(device2);
-        verify(mCallback).onDeviceListUpdate(any());
+        verify(mCallback).onDeviceAttributesChanged();
     }
 
     @Test
@@ -373,7 +373,7 @@
         mLocalMediaManager.registerCallback(mCallback);
         mLocalMediaManager.mMediaDeviceCallback.onConnectedDeviceChanged(TEST_DEVICE_ID_1);
 
-        verify(mCallback, never()).onDeviceListUpdate(any());
+        verify(mCallback, never()).onDeviceAttributesChanged();
     }
 
     @Test
@@ -382,6 +382,6 @@
 
         mLocalMediaManager.mMediaDeviceCallback.onDeviceAttributesChanged();
 
-        verify(mCallback).onDeviceListUpdate(any());
+        verify(mCallback).onDeviceAttributesChanged();
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java
index d98f50b..b0a647e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java
@@ -31,6 +31,7 @@
 import android.telephony.TelephonyManager;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -71,12 +72,13 @@
         when(mTelephonyManager.getSubscriberId(SUB_ID)).thenReturn(SUBSCRIBER_ID);
         when(mTelephonyManager.getSubscriberId(SUB_ID_2)).thenReturn(SUBSCRIBER_ID_2);
         when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager);
-        when(mSubscriptionManager.isActiveSubId(anyInt())).thenReturn(true);
+        when(mSubscriptionManager.isActiveSubscriptionId(anyInt())).thenReturn(true);
     }
 
     @Test
+    @Ignore
     public void getMobileTemplate_infoNull_returnMobileAll() {
-        when(mSubscriptionManager.isActiveSubId(SUB_ID)).thenReturn(false);
+        when(mSubscriptionManager.isActiveSubscriptionId(SUB_ID)).thenReturn(false);
 
         final NetworkTemplate networkTemplate = DataUsageUtils.getMobileTemplate(mContext, SUB_ID);
         assertThat(networkTemplate.matchesSubscriberId(SUBSCRIBER_ID)).isTrue();
@@ -84,6 +86,7 @@
     }
 
     @Test
+    @Ignore
     public void getMobileTemplate_groupUuidNull_returnMobileAll() {
         when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(mInfo1);
         when(mInfo1.getGroupUuid()).thenReturn(null);
@@ -96,6 +99,7 @@
     }
 
     @Test
+    @Ignore
     public void getMobileTemplate_groupUuidExist_returnMobileMerged() {
         when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(mInfo1);
         when(mInfo1.getGroupUuid()).thenReturn(mParcelUuid);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java
index 21aa526..2bd20a9 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java
@@ -72,7 +72,7 @@
 
         assertThat(AccessPointPreference.buildContentDescription(
                 RuntimeEnvironment.application, pref, ap))
-                .isEqualTo("ssid,connected,Wifi signal full.,Secure network");
+                .isEqualTo("ssid,connected,Wifi disconnected.,Secure network");
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/OWNER b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/OWNER
new file mode 100644
index 0000000..5c2a7b8
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/OWNER
@@ -0,0 +1,4 @@
+# People who can approve changes for submission
+arcwang@google.com
+govenliu@google.com
+qal@google.com
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java
index 752a549..0f1e0ff 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java
@@ -67,7 +67,7 @@
         MockitoAnnotations.initMocks(this);
 
         when(mMockWifiEntry.getTitle()).thenReturn(MOCK_TITLE);
-        when(mMockWifiEntry.getSummary()).thenReturn(MOCK_SUMMARY);
+        when(mMockWifiEntry.getSummary(false /* concise */)).thenReturn(MOCK_SUMMARY);
 
         when(mMockIconInjector.getIcon(0)).thenReturn(mMockDrawable0);
         when(mMockIconInjector.getIcon(1)).thenReturn(mMockDrawable1);
@@ -112,7 +112,7 @@
         final WifiEntryPreference pref =
                 new WifiEntryPreference(mContext, mMockWifiEntry, mMockIconInjector);
         final String updatedSummary = "updated summary";
-        when(mMockWifiEntry.getSummary()).thenReturn(updatedSummary);
+        when(mMockWifiEntry.getSummary(false /* concise */)).thenReturn(updatedSummary);
 
         pref.refresh();
 
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index f40d3a1..d67bd8d 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -8,6 +8,7 @@
     libs: [
         "telephony-common",
         "ims-common",
+        "unsupportedappusage",
     ],
     static_libs: [
         "junit",
@@ -29,7 +30,7 @@
         "src/com/android/providers/settings/SettingsBackupAgent.java",
         "src/com/android/providers/settings/SettingsState.java",
         "src/com/android/providers/settings/SettingsHelper.java",
-        "src/com/android/providers/settings/WifiSoftApBandChangedNotifier.java",
+        "src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java",
     ],
     static_libs: [
         "androidx.test.rules",
@@ -40,6 +41,7 @@
     libs: [
         "android.test.base",
         "android.test.mock",
+        "unsupportedappusage",
     ],
     resource_dirs: ["res"],
     aaptflags: [
diff --git a/packages/SettingsProvider/res/values/overlayable.xml b/packages/SettingsProvider/res/values/overlayable.xml
deleted file mode 100644
index dc41a77..0000000
--- a/packages/SettingsProvider/res/values/overlayable.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
--->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-  <overlayable name="SettingsToNotRestore">
-    <policy type="product|system|vendor">
-      <item type="array" name="restore_blocked_device_specific_settings" />
-      <item type="array" name="restore_blocked_global_settings" />
-      <item type="array" name="restore_blocked_secure_settings" />
-      <item type="array" name="restore_blocked_system_settings" />
-    </policy>
-  </overlayable>
-</resources>
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 049b9f0..de4817c 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -16,7 +16,7 @@
 
 package android.provider.settings.backup;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.provider.Settings;
 
 /** Information relating to the Secure settings which should be backed up */
@@ -85,6 +85,7 @@
         Settings.Secure.TTY_MODE_ENABLED,
         Settings.Secure.RTT_CALLING_MODE,
         Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR,
+        Settings.Secure.MINIMAL_POST_PROCESSING_ALLOWED,
         Settings.Secure.NIGHT_DISPLAY_CUSTOM_START_TIME,
         Settings.Secure.NIGHT_DISPLAY_CUSTOM_END_TIME,
         Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE,
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 89b19de..3f5b0da 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -16,7 +16,7 @@
 
 package android.provider.settings.backup;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.provider.Settings;
 
 /** Information about the system settings to back up */
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 72923a3..dd94d2e 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -141,9 +141,6 @@
         VALIDATORS.put(Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.REQUIRE_PASSWORD_TO_DECRYPT, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.DEVICE_DEMO_MODE, BOOLEAN_VALIDATOR);
-        VALIDATORS.put(Global.WIFI_PNO_FREQUENCY_CULLING_ENABLED, BOOLEAN_VALIDATOR);
-        VALIDATORS.put(Global.WIFI_PNO_RECENCY_SORTING_ENABLED, BOOLEAN_VALIDATOR);
-        VALIDATORS.put(Global.WIFI_LINK_PROBING_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.AWARE_ALLOWED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.POWER_BUTTON_LONG_PRESS, new InclusiveIntegerRangeValidator(0, 5));
         VALIDATORS.put(
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index ed06fa7..849f22f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -131,6 +131,7 @@
         VALIDATORS.put(
                 Secure.INCALL_POWER_BUTTON_BEHAVIOR,
                 new DiscreteValueValidator(new String[] {"1", "2"}));
+        VALIDATORS.put(Secure.MINIMAL_POST_PROCESSING_ALLOWED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, NON_NEGATIVE_INTEGER_VALIDATOR);
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 94ab0f1..c5d4fa9 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -25,7 +25,7 @@
 import static android.provider.settings.validators.SettingsValidators.VIBRATION_INTENSITY_VALIDATOR;
 
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.hardware.display.ColorDisplayManager;
 import android.os.BatteryManager;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 375a650..266bfe0 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -2554,7 +2554,7 @@
             for (int phoneId = 0; phoneId < phoneCount; phoneId++) {
                 int mode = defaultNetworks.size() <= phoneId
                         || defaultNetworks.get(phoneId) == null
-                        ? RILConstants.PREFERRED_NETWORK_MODE : defaultNetworks.get(phoneId);
+                        ? TelephonyManager.DEFAULT_PREFERRED_NETWORK_MODE : defaultNetworks.get(phoneId);
                 if (phoneId > 0) val.append(',');
                 val.append(mode);
             }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index fb558ab..cdf9728 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -968,18 +968,14 @@
     }
 
     private void restoreSoftApConfiguration(byte[] data) {
-        SoftApConfiguration config = mWifiManager.restoreSoftApBackupData(data);
-        if (config != null) {
-            int originalApBand = config.getBand();
+        SoftApConfiguration configInCloud = mWifiManager.restoreSoftApBackupData(data);
+        if (configInCloud != null) {
             if (DEBUG) Log.d(TAG, "Successfully unMarshaled SoftApConfiguration ");
-
-            // Depending on device hardware, we may need to notify the user of a setting change for
-            // the apBand preference
-            boolean dualMode = mWifiManager.isDualModeSupported();
-            int storedApBand = mWifiManager.getSoftApConfiguration().getBand();
-            if (dualMode && storedApBand != originalApBand) {
+            // Depending on device hardware, we may need to notify the user of a setting change
+            SoftApConfiguration storedConfig = mWifiManager.getSoftApConfiguration();
+            if (!storedConfig.equals(configInCloud)) {
                 Log.d(TAG, "restored ap configuration requires a conversion, notify the user");
-                WifiSoftApBandChangedNotifier.notifyUserOfApBandConversion(this);
+                WifiSoftApConfigChangedNotifier.notifyUserOfConfigConversion(this);
             }
         }
     }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index aa36dca..f6e5062 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1070,9 +1070,6 @@
                 Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE,
                 GlobalSettingsProto.Network.RECOMMENDATIONS_PACKAGE);
         dumpSetting(s, p,
-                Settings.Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS,
-                GlobalSettingsProto.Network.RECOMMENDATION_REQUEST_TIMEOUT_MS);
-        dumpSetting(s, p,
                 Settings.Global.NETWORK_WATCHLIST_ENABLED,
                 GlobalSettingsProto.Network.WATCHLIST_ENABLED);
         dumpSetting(s, p,
@@ -1587,9 +1584,6 @@
                 Settings.Global.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED,
                 GlobalSettingsProto.Wifi.WATCHDOG_POOR_NETWORK_TEST_ENABLED);
         dumpSetting(s, p,
-                Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED,
-                GlobalSettingsProto.Wifi.SUSPEND_OPTIMIZATIONS_ENABLED);
-        dumpSetting(s, p,
                 Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED,
                 GlobalSettingsProto.Wifi.VERBOSE_LOGGING_ENABLED);
         dumpSetting(s, p,
@@ -2005,6 +1999,9 @@
         dumpSetting(s, p,
                 Settings.Secure.DOZE_TAP_SCREEN_GESTURE,
                 SecureSettingsProto.Doze.PULSE_ON_TAP);
+        dumpSetting(s, p,
+                Settings.Secure.SUPPRESS_DOZE,
+                SecureSettingsProto.Doze.SUPPRESS);
         p.end(dozeToken);
 
         dumpSetting(s, p,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 1e0c1d8..486386f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -20,6 +20,7 @@
 import static android.os.Process.ROOT_UID;
 import static android.os.Process.SHELL_UID;
 import static android.os.Process.SYSTEM_UID;
+import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY;
 
 import android.Manifest;
@@ -61,6 +62,7 @@
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
+import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.SELinux;
 import android.os.ServiceManager;
@@ -274,6 +276,9 @@
     private final Object mLock = new Object();
 
     @GuardedBy("mLock")
+    private RemoteCallback mConfigMonitorCallback;
+
+    @GuardedBy("mLock")
     private SettingsRegistry mSettingsRegistry;
 
     @GuardedBy("mLock")
@@ -366,7 +371,9 @@
                 String value = getSettingValue(args);
                 String tag = getSettingTag(args);
                 final boolean makeDefault = getSettingMakeDefault(args);
-                insertGlobalSetting(name, value, tag, makeDefault, requestingUserId, false);
+                final boolean overrideableByRestore = getSettingOverrideableByRestore(args);
+                insertGlobalSetting(name, value, tag, makeDefault, requestingUserId, false,
+                        overrideableByRestore);
                 break;
             }
 
@@ -374,13 +381,16 @@
                 String value = getSettingValue(args);
                 String tag = getSettingTag(args);
                 final boolean makeDefault = getSettingMakeDefault(args);
-                insertSecureSetting(name, value, tag, makeDefault, requestingUserId, false);
+                final boolean overrideableByRestore = getSettingOverrideableByRestore(args);
+                insertSecureSetting(name, value, tag, makeDefault, requestingUserId, false,
+                        overrideableByRestore);
                 break;
             }
 
             case Settings.CALL_METHOD_PUT_SYSTEM: {
                 String value = getSettingValue(args);
-                insertSystemSetting(name, value, requestingUserId);
+                boolean overrideableByRestore = getSettingOverrideableByRestore(args);
+                insertSystemSetting(name, value, requestingUserId, overrideableByRestore);
                 break;
             }
 
@@ -444,8 +454,17 @@
 
             case Settings.CALL_METHOD_LIST_CONFIG: {
                 String prefix = getSettingPrefix(args);
-                return packageValuesForCallResult(getAllConfigFlags(prefix),
+                Bundle result = packageValuesForCallResult(getAllConfigFlags(prefix),
                         isTrackingGeneration(args));
+                reportDeviceConfigAccess(prefix);
+                return result;
+            }
+
+            case Settings.CALL_METHOD_REGISTER_MONITOR_CALLBACK_CONFIG: {
+                RemoteCallback callback = args.getParcelable(
+                        Settings.CALL_METHOD_MONITOR_CALLBACK_KEY);
+                setMonitorCallback(callback);
+                break;
             }
 
             case Settings.CALL_METHOD_LIST_GLOBAL: {
@@ -575,20 +594,23 @@
         switch (table) {
             case TABLE_GLOBAL: {
                 if (insertGlobalSetting(name, value, null, false,
-                        UserHandle.getCallingUserId(), false)) {
+                        UserHandle.getCallingUserId(), false,
+                        /* overrideableByRestore */ false)) {
                     return Uri.withAppendedPath(Settings.Global.CONTENT_URI, name);
                 }
             } break;
 
             case TABLE_SECURE: {
                 if (insertSecureSetting(name, value, null, false,
-                        UserHandle.getCallingUserId(), false)) {
+                        UserHandle.getCallingUserId(), false,
+                        /* overrideableByRestore */ false)) {
                     return Uri.withAppendedPath(Settings.Secure.CONTENT_URI, name);
                 }
             } break;
 
             case TABLE_SYSTEM: {
-                if (insertSystemSetting(name, value, UserHandle.getCallingUserId())) {
+                if (insertSystemSetting(name, value, UserHandle.getCallingUserId(),
+                        /* overridableByRestore */ false)) {
                     return Uri.withAppendedPath(Settings.System.CONTENT_URI, name);
                 }
             } break;
@@ -1043,8 +1065,9 @@
         enforceWritePermission(Manifest.permission.WRITE_DEVICE_CONFIG);
 
         synchronized (mLock) {
-            return mSettingsRegistry.setSettingsLocked(SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM,
-                    prefix, keyValues, resolveCallingPackage());
+            final int key = makeKey(SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM);
+            return mSettingsRegistry.setConfigSettingsLocked(key, prefix, keyValues,
+                    resolveCallingPackage());
         }
     }
 
@@ -1074,7 +1097,8 @@
                 case MUTATION_OPERATION_INSERT: {
                     return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_CONFIG,
                             UserHandle.USER_SYSTEM, name, value, null, makeDefault, true,
-                            resolveCallingPackage(), false, null);
+                            resolveCallingPackage(), false, null,
+                            /* overrideableByRestore */ false);
                 }
 
                 case MUTATION_OPERATION_DELETE: {
@@ -1178,14 +1202,15 @@
     }
 
     private boolean insertGlobalSetting(String name, String value, String tag,
-            boolean makeDefault, int requestingUserId, boolean forceNotify) {
+            boolean makeDefault, int requestingUserId, boolean forceNotify,
+            boolean overrideableByRestore) {
         if (DEBUG) {
             Slog.v(LOG_TAG, "insertGlobalSetting(" + name + ", " + value  + ", "
                     + ", " + tag + ", " + makeDefault + ", " + requestingUserId
                     + ", " + forceNotify + ")");
         }
         return mutateGlobalSetting(name, value, tag, makeDefault, requestingUserId,
-                MUTATION_OPERATION_INSERT, forceNotify, 0);
+                MUTATION_OPERATION_INSERT, forceNotify, 0, overrideableByRestore);
     }
 
     private boolean deleteGlobalSetting(String name, int requestingUserId, boolean forceNotify) {
@@ -1220,6 +1245,15 @@
     private boolean mutateGlobalSetting(String name, String value, String tag,
             boolean makeDefault, int requestingUserId, int operation, boolean forceNotify,
             int mode) {
+        // overrideableByRestore = false as by default settings values shouldn't be overrideable by
+        // restore.
+        return mutateGlobalSetting(name, value, tag, makeDefault, requestingUserId, operation,
+                forceNotify, mode, /* overrideableByRestore */ false);
+    }
+
+    private boolean mutateGlobalSetting(String name, String value, String tag,
+            boolean makeDefault, int requestingUserId, int operation, boolean forceNotify,
+            int mode, boolean overrideableByRestore) {
         // Make sure the caller can change the settings - treated as secure.
         enforceWritePermission(Manifest.permission.WRITE_SECURE_SETTINGS);
 
@@ -1238,7 +1272,8 @@
                 case MUTATION_OPERATION_INSERT: {
                     return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_GLOBAL,
                             UserHandle.USER_SYSTEM, name, value, tag, makeDefault,
-                            getCallingPackage(), forceNotify, CRITICAL_GLOBAL_SETTINGS);
+                            getCallingPackage(), forceNotify,
+                            CRITICAL_GLOBAL_SETTINGS, overrideableByRestore);
                 }
 
                 case MUTATION_OPERATION_DELETE: {
@@ -1474,7 +1509,7 @@
             ) {
                 @Override
                 public boolean update(String value, boolean setDefault, String packageName,
-                        String tag, boolean forceNonSystemPackage) {
+                        String tag, boolean forceNonSystemPackage, boolean overrideableByRestore) {
                     Slog.wtf(LOG_TAG, "update shouldn't be called on this instance.");
                     return false;
                 }
@@ -1483,14 +1518,15 @@
     }
 
     private boolean insertSecureSetting(String name, String value, String tag,
-            boolean makeDefault, int requestingUserId, boolean forceNotify) {
+            boolean makeDefault, int requestingUserId, boolean forceNotify,
+            boolean overrideableByRestore) {
         if (DEBUG) {
             Slog.v(LOG_TAG, "insertSecureSetting(" + name + ", " + value + ", "
                     + ", " + tag  + ", " + makeDefault + ", "  + requestingUserId
                     + ", " + forceNotify + ")");
         }
         return mutateSecureSetting(name, value, tag, makeDefault, requestingUserId,
-                MUTATION_OPERATION_INSERT, forceNotify, 0);
+                MUTATION_OPERATION_INSERT, forceNotify, 0, overrideableByRestore);
     }
 
     private boolean deleteSecureSetting(String name, int requestingUserId, boolean forceNotify) {
@@ -1528,6 +1564,15 @@
     private boolean mutateSecureSetting(String name, String value, String tag,
             boolean makeDefault, int requestingUserId, int operation, boolean forceNotify,
             int mode) {
+        // overrideableByRestore = false as by default settings values shouldn't be overrideable by
+        // restore.
+        return mutateSecureSetting(name, value, tag, makeDefault, requestingUserId, operation,
+                forceNotify, mode, /* overrideableByRestore */ false);
+    }
+
+    private boolean mutateSecureSetting(String name, String value, String tag,
+            boolean makeDefault, int requestingUserId, int operation, boolean forceNotify,
+            int mode, boolean overrideableByRestore) {
         // Make sure the caller can change the settings.
         enforceWritePermission(Manifest.permission.WRITE_SECURE_SETTINGS);
 
@@ -1560,7 +1605,8 @@
                 case MUTATION_OPERATION_INSERT: {
                     return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SECURE,
                             owningUserId, name, value, tag, makeDefault,
-                            getCallingPackage(), forceNotify, CRITICAL_SECURE_SETTINGS);
+                            getCallingPackage(), forceNotify, CRITICAL_SECURE_SETTINGS,
+                            overrideableByRestore);
                 }
 
                 case MUTATION_OPERATION_DELETE: {
@@ -1636,13 +1682,15 @@
         }
     }
 
-    private boolean insertSystemSetting(String name, String value, int requestingUserId) {
+    private boolean insertSystemSetting(String name, String value, int requestingUserId,
+            boolean overrideableByRestore) {
         if (DEBUG) {
             Slog.v(LOG_TAG, "insertSystemSetting(" + name + ", " + value + ", "
                     + requestingUserId + ")");
         }
 
-        return mutateSystemSetting(name, value, requestingUserId, MUTATION_OPERATION_INSERT);
+        return mutateSystemSetting(name, value, requestingUserId, MUTATION_OPERATION_INSERT,
+                overrideableByRestore);
     }
 
     private boolean deleteSystemSetting(String name, int requestingUserId) {
@@ -1662,8 +1710,15 @@
         return mutateSystemSetting(name, value, requestingUserId, MUTATION_OPERATION_UPDATE);
     }
 
-    private boolean mutateSystemSetting(String name, String value, int runAsUserId,
-            int operation) {
+    private boolean mutateSystemSetting(String name, String value, int runAsUserId, int operation) {
+        // overrideableByRestore = false as by default settings values shouldn't be overrideable by
+        // restore.
+        return mutateSystemSetting(name, value, runAsUserId, operation,
+                /* overrideableByRestore */ false);
+    }
+
+    private boolean mutateSystemSetting(String name, String value, int runAsUserId, int operation,
+            boolean overrideableByRestore) {
         if (!hasWriteSecureSettingsPermission()) {
             // If the caller doesn't hold WRITE_SECURE_SETTINGS, we verify whether this
             // operation is allowed for the calling package through appops.
@@ -1713,7 +1768,7 @@
                     validateSystemSettingValue(name, value);
                     return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SYSTEM,
                             owningUserId, name, value, null, false, getCallingPackage(),
-                            false, null);
+                            false, null, overrideableByRestore);
                 }
 
                 case MUTATION_OPERATION_DELETE: {
@@ -2050,7 +2105,8 @@
         }
         return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SECURE,
                 owningUserId, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, newProviders, tag,
-                makeDefault, getCallingPackage(), forceNotify, CRITICAL_SECURE_SETTINGS);
+                makeDefault, getCallingPackage(), forceNotify, CRITICAL_SECURE_SETTINGS,
+                /* overrideableByRestore */ false);
     }
 
     private static void warnOrThrowForUndesiredSecureSettingsMutationForTargetSdk(
@@ -2113,6 +2169,59 @@
         return result;
     }
 
+    private void setMonitorCallback(RemoteCallback callback) {
+        if (callback == null) {
+            return;
+        }
+        getContext().enforceCallingOrSelfPermission(
+                Manifest.permission.MONITOR_DEVICE_CONFIG_ACCESS,
+                "Permission denial: registering for config access requires: "
+                        + Manifest.permission.MONITOR_DEVICE_CONFIG_ACCESS);
+        synchronized (mLock) {
+            mConfigMonitorCallback = callback;
+        }
+    }
+
+    private void reportDeviceConfigAccess(@Nullable String prefix) {
+        if (prefix == null) {
+            return;
+        }
+        String callingPackage = getCallingPackage();
+        String namespace = prefix.replace("/", "");
+        if (DeviceConfig.getPublicNamespaces().contains(namespace)) {
+            return;
+        }
+        synchronized (mLock) {
+            if (mConfigMonitorCallback != null) {
+                Bundle callbackResult = new Bundle();
+                callbackResult.putString(Settings.EXTRA_MONITOR_CALLBACK_TYPE,
+                        Settings.EXTRA_ACCESS_CALLBACK);
+                callbackResult.putString(Settings.EXTRA_CALLING_PACKAGE, callingPackage);
+                callbackResult.putString(Settings.EXTRA_NAMESPACE, namespace);
+                mConfigMonitorCallback.sendResult(callbackResult);
+            }
+        }
+    }
+
+    private void reportDeviceConfigUpdate(@Nullable String prefix) {
+        if (prefix == null) {
+            return;
+        }
+        String namespace = prefix.replace("/", "");
+        if (DeviceConfig.getPublicNamespaces().contains(namespace)) {
+            return;
+        }
+        synchronized (mLock) {
+            if (mConfigMonitorCallback != null) {
+                Bundle callbackResult = new Bundle();
+                callbackResult.putString(Settings.EXTRA_MONITOR_CALLBACK_TYPE,
+                        Settings.EXTRA_NAMESPACE_UPDATED_CALLBACK);
+                callbackResult.putString(Settings.EXTRA_NAMESPACE, namespace);
+                mConfigMonitorCallback.sendResult(callbackResult);
+            }
+        }
+    }
+
     private static int getRequestingUserId(Bundle args) {
         final int callingUserId = UserHandle.getCallingUserId();
         return (args != null) ? args.getInt(Settings.CALL_METHOD_USER_KEY, callingUserId)
@@ -2144,6 +2253,10 @@
         return (args != null) && args.getBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY);
     }
 
+    private static boolean getSettingOverrideableByRestore(Bundle args) {
+        return (args != null) && args.getBoolean(Settings.CALL_METHOD_OVERRIDEABLE_BY_RESTORE_KEY);
+    }
+
     private static int getResetModeEnforcingPermission(Bundle args) {
         final int mode = (args != null) ? args.getInt(Settings.CALL_METHOD_RESET_MODE_KEY) : 0;
         switch (mode) {
@@ -2641,21 +2754,21 @@
 
         public boolean insertSettingLocked(int type, int userId, String name, String value,
                 String tag, boolean makeDefault, String packageName, boolean forceNotify,
-                Set<String> criticalSettings) {
+                Set<String> criticalSettings, boolean overrideableByRestore) {
             return insertSettingLocked(type, userId, name, value, tag, makeDefault, false,
-                    packageName, forceNotify, criticalSettings);
+                    packageName, forceNotify, criticalSettings, overrideableByRestore);
         }
 
         public boolean insertSettingLocked(int type, int userId, String name, String value,
                 String tag, boolean makeDefault, boolean forceNonSystemPackage, String packageName,
-                boolean forceNotify, Set<String> criticalSettings) {
+                boolean forceNotify, Set<String> criticalSettings, boolean overrideableByRestore) {
             final int key = makeKey(type, userId);
 
             boolean success = false;
             SettingsState settingsState = peekSettingsStateLocked(key);
             if (settingsState != null) {
                 success = settingsState.insertSettingLocked(name, value,
-                        tag, makeDefault, forceNonSystemPackage, packageName);
+                        tag, makeDefault, forceNonSystemPackage, packageName, overrideableByRestore);
             }
 
             if (success && criticalSettings != null && criticalSettings.contains(name)) {
@@ -2669,22 +2782,20 @@
         }
 
         /**
-         * Set Settings using consumed keyValues, returns true if the keyValues can be set, false
-         * otherwise.
+         * Set Config Settings using consumed keyValues, returns true if the keyValues can be set,
+         * false otherwise.
          */
-        public boolean setSettingsLocked(int type, int userId, String prefix,
+        public boolean setConfigSettingsLocked(int key, String prefix,
                 Map<String, String> keyValues, String packageName) {
-            final int key = makeKey(type, userId);
-
             SettingsState settingsState = peekSettingsStateLocked(key);
             if (settingsState != null) {
-                if (SETTINGS_TYPE_CONFIG == type && settingsState.isNewConfigBannedLocked(prefix,
-                        keyValues)) {
+                if (settingsState.isNewConfigBannedLocked(prefix, keyValues)) {
                     return false;
                 }
                 List<String> changedSettings =
                         settingsState.setSettingsLocked(prefix, keyValues, packageName);
                 if (!changedSettings.isEmpty()) {
+                    reportDeviceConfigUpdate(prefix);
                     notifyForConfigSettingsChangeLocked(key, prefix, changedSettings);
                 }
             }
@@ -3304,7 +3415,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 185;
+            private static final int SETTINGS_VERSION = 186;
 
             private final int mUserId;
 
@@ -3390,6 +3501,10 @@
              * for this user from the old to the new version. When you add a new
              * upgrade step you *must* update SETTINGS_VERSION.
              *
+             * All settings modifications should be made through
+             * {@link SettingsState#insertSettingOverrideableByRestoreLocked(String, String, String,
+             * boolean, String)} so that restore can override those values if needed.
+             *
              * This is an example of moving a setting from secure to global.
              *
              * // v119: Example settings changes.
@@ -3435,7 +3550,8 @@
                 // v120: Add double tap to wake setting.
                 if (currentVersion == 119) {
                     SettingsState secureSettings = getSecureSettingsLocked(userId);
-                    secureSettings.insertSettingLocked(Settings.Secure.DOUBLE_TAP_TO_WAKE,
+                    secureSettings.insertSettingOverrideableByRestoreLocked(
+                            Settings.Secure.DOUBLE_TAP_TO_WAKE,
                             getContext().getResources().getBoolean(
                                     R.bool.def_double_tap_to_wake) ? "1" : "0", null, true,
                             SettingsState.SYSTEM_PACKAGE_NAME);
@@ -3460,7 +3576,7 @@
                             Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT);
                     if (defaultComponent != null && !defaultComponent.isEmpty() &&
                         currentSetting.isNull()) {
-                        secureSettings.insertSettingLocked(
+                        secureSettings.insertSettingOverrideableByRestoreLocked(
                                 Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
                                 defaultComponent, null, true, SettingsState.SYSTEM_PACKAGE_NAME);
                     }
@@ -3475,7 +3591,7 @@
                         Setting currentSetting = globalSettings.getSettingLocked(
                                 Settings.Global.ADD_USERS_WHEN_LOCKED);
                         if (currentSetting.isNull()) {
-                            globalSettings.insertSettingLocked(
+                            globalSettings.insertSettingOverrideableByRestoreLocked(
                                     Settings.Global.ADD_USERS_WHEN_LOCKED,
                                     getContext().getResources().getBoolean(
                                             R.bool.def_add_users_from_lockscreen) ? "1" : "0",
@@ -3489,8 +3605,9 @@
                     final SettingsState globalSettings = getGlobalSettingsLocked();
                     String defaultDisabledProfiles = (getContext().getResources().getString(
                             R.string.def_bluetooth_disabled_profiles));
-                    globalSettings.insertSettingLocked(Settings.Global.BLUETOOTH_DISABLED_PROFILES,
-                            defaultDisabledProfiles, null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+                    globalSettings.insertSettingOverrideableByRestoreLocked(
+                            Settings.Global.BLUETOOTH_DISABLED_PROFILES, defaultDisabledProfiles,
+                            null, true, SettingsState.SYSTEM_PACKAGE_NAME);
                     currentVersion = 124;
                 }
 
@@ -3501,7 +3618,7 @@
                     Setting currentSetting = secureSettings.getSettingLocked(
                             Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD);
                     if (currentSetting.isNull()) {
-                        secureSettings.insertSettingLocked(
+                        secureSettings.insertSettingOverrideableByRestoreLocked(
                                 Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
                                 getContext().getResources().getBoolean(
                                         R.bool.def_show_ime_with_hard_keyboard) ? "1" : "0",
@@ -3530,7 +3647,7 @@
                                 b.append(c.flattenToString());
                                 start = false;
                             }
-                            secureSettings.insertSettingLocked(
+                            secureSettings.insertSettingOverrideableByRestoreLocked(
                                     Settings.Secure.ENABLED_VR_LISTENERS, b.toString(),
                                     null, true, SettingsState.SYSTEM_PACKAGE_NAME);
                         }
@@ -3550,7 +3667,7 @@
                                 Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS);
                         if (!showNotifications.isNull()) {
                             final SettingsState secureSettings = getSecureSettingsLocked(userId);
-                            secureSettings.insertSettingLocked(
+                            secureSettings.insertSettingOverrideableByRestoreLocked(
                                     Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
                                     showNotifications.getValue(), null, true,
                                     SettingsState.SYSTEM_PACKAGE_NAME);
@@ -3560,7 +3677,7 @@
                                 Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
                         if (!allowPrivate.isNull()) {
                             final SettingsState secureSettings = getSecureSettingsLocked(userId);
-                            secureSettings.insertSettingLocked(
+                            secureSettings.insertSettingOverrideableByRestoreLocked(
                                     Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
                                     allowPrivate.getValue(), null, true,
                                     SettingsState.SYSTEM_PACKAGE_NAME);
@@ -3587,7 +3704,7 @@
                     final String oldValue = systemSecureSettings.getSettingLocked(
                             Settings.Secure.LONG_PRESS_TIMEOUT).getValue();
                     if (TextUtils.equals("500", oldValue)) {
-                        systemSecureSettings.insertSettingLocked(
+                        systemSecureSettings.insertSettingOverrideableByRestoreLocked(
                                 Settings.Secure.LONG_PRESS_TIMEOUT,
                                 String.valueOf(getContext().getResources().getInteger(
                                         R.integer.def_long_press_timeout_millis)),
@@ -3603,10 +3720,12 @@
                             getSettingLocked(Settings.Secure.DOZE_ENABLED).getValue());
 
                     if (dozeExplicitlyDisabled) {
-                        secureSettings.insertSettingLocked(Settings.Secure.DOZE_PICK_UP_GESTURE,
-                                "0", null, true, SettingsState.SYSTEM_PACKAGE_NAME);
-                        secureSettings.insertSettingLocked(Settings.Secure.DOZE_DOUBLE_TAP_GESTURE,
-                                "0", null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+                        secureSettings.insertSettingOverrideableByRestoreLocked(
+                                Settings.Secure.DOZE_PICK_UP_GESTURE, "0", null, true,
+                                SettingsState.SYSTEM_PACKAGE_NAME);
+                        secureSettings.insertSettingOverrideableByRestoreLocked(
+                                Settings.Secure.DOZE_DOUBLE_TAP_GESTURE, "0", null, true,
+                                SettingsState.SYSTEM_PACKAGE_NAME);
                     }
                     currentVersion = 131;
                 }
@@ -3617,7 +3736,7 @@
                     final String oldValue = systemSecureSettings.getSettingLocked(
                             Settings.Secure.MULTI_PRESS_TIMEOUT).getValue();
                     if (TextUtils.equals(null, oldValue)) {
-                        systemSecureSettings.insertSettingLocked(
+                        systemSecureSettings.insertSettingOverrideableByRestoreLocked(
                                 Settings.Secure.MULTI_PRESS_TIMEOUT,
                                 String.valueOf(getContext().getResources().getInteger(
                                         R.integer.def_multi_press_timeout_millis)),
@@ -3632,7 +3751,7 @@
                     final SettingsState systemSecureSettings = getSecureSettingsLocked(userId);
                     String defaultSyncParentSounds = (getContext().getResources()
                             .getBoolean(R.bool.def_sync_parent_sounds) ? "1" : "0");
-                    systemSecureSettings.insertSettingLocked(
+                    systemSecureSettings.insertSettingOverrideableByRestoreLocked(
                             Settings.Secure.SYNC_PARENT_SOUNDS, defaultSyncParentSounds,
                             null, true, SettingsState.SYSTEM_PACKAGE_NAME);
                     currentVersion = 133;
@@ -3645,9 +3764,9 @@
                             .isNull()) {
                         String defaultEndButtonBehavior = Integer.toString(getContext()
                                 .getResources().getInteger(R.integer.def_end_button_behavior));
-                        systemSettings.insertSettingLocked(Settings.System.END_BUTTON_BEHAVIOR,
-                                defaultEndButtonBehavior, null, true,
-                                SettingsState.SYSTEM_PACKAGE_NAME);
+                        systemSettings.insertSettingOverrideableByRestoreLocked(
+                                Settings.System.END_BUTTON_BEHAVIOR, defaultEndButtonBehavior, null,
+                                true, SettingsState.SYSTEM_PACKAGE_NAME);
                     }
                     currentVersion = 134;
                 }
@@ -3705,8 +3824,8 @@
 
                             if (ssaid.isNull() || ssaid.getValue() == null) {
                                 // Android Id doesn't exist for this package so create it.
-                                ssaidSettings.insertSettingLocked(uid, legacySsaid, null, true,
-                                        info.packageName);
+                                ssaidSettings.insertSettingOverrideableByRestoreLocked(uid,
+                                        legacySsaid, null, true, info.packageName);
                                 if (DEBUG) {
                                     Slog.d(LOG_TAG, "Keep the legacy ssaid for uid=" + uid);
                                 }
@@ -3726,13 +3845,14 @@
                             && secureSetting.getSettingLocked(
                             Settings.Secure.INSTALL_NON_MARKET_APPS).getValue().equals("0")) {
 
-                        secureSetting.insertSettingLocked(Settings.Secure.INSTALL_NON_MARKET_APPS,
-                                "1", null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+                        secureSetting.insertSettingOverrideableByRestoreLocked(
+                                Settings.Secure.INSTALL_NON_MARKET_APPS, "1", null, true,
+                                SettingsState.SYSTEM_PACKAGE_NAME);
                         // For managed profiles with profile owners, DevicePolicyManagerService
                         // may want to set the user restriction in this case
-                        secureSetting.insertSettingLocked(
-                                Settings.Secure.UNKNOWN_SOURCES_DEFAULT_REVERSED, "1", null, true,
-                                SettingsState.SYSTEM_PACKAGE_NAME);
+                        secureSetting.insertSettingOverrideableByRestoreLocked(
+                                Settings.Secure.UNKNOWN_SOURCES_DEFAULT_REVERSED, "1", null,
+                                true, SettingsState.SYSTEM_PACKAGE_NAME);
                     }
                     currentVersion = 138;
                 }
@@ -3773,7 +3893,7 @@
                         Setting currentSetting = globalSettings.getSettingLocked(
                                 Settings.Global.WIFI_WAKEUP_ENABLED);
                         if (currentSetting.isNull()) {
-                            globalSettings.insertSettingLocked(
+                            globalSettings.insertSettingOverrideableByRestoreLocked(
                                     Settings.Global.WIFI_WAKEUP_ENABLED,
                                     getContext().getResources().getBoolean(
                                             R.bool.def_wifi_wakeup_enabled) ? "1" : "0",
@@ -3795,8 +3915,9 @@
                         if (defaultValue != null) {
                             Slog.d(LOG_TAG, "Setting [" + defaultValue + "] as Autofill Service "
                                     + "for user " + userId);
-                            secureSettings.insertSettingLocked(Settings.Secure.AUTOFILL_SERVICE,
-                                    defaultValue, null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+                            secureSettings.insertSettingOverrideableByRestoreLocked(
+                                    Settings.Secure.AUTOFILL_SERVICE, defaultValue, null, true,
+                                    SettingsState.SYSTEM_PACKAGE_NAME);
                         }
                     }
 
@@ -3847,7 +3968,7 @@
                         final Setting currentSetting = globalSettings.getSettingLocked(
                                 Global.DEFAULT_RESTRICT_BACKGROUND_DATA);
                         if (currentSetting.isNull()) {
-                            globalSettings.insertSettingLocked(
+                            globalSettings.insertSettingOverrideableByRestoreLocked(
                                     Global.DEFAULT_RESTRICT_BACKGROUND_DATA,
                                     getContext().getResources().getBoolean(
                                             R.bool.def_restrict_background_data) ? "1" : "0",
@@ -3866,7 +3987,7 @@
                         final String defaultValue = getContext().getResources().getString(
                                 R.string.def_backup_manager_constants);
                         if (!TextUtils.isEmpty(defaultValue)) {
-                            systemSecureSettings.insertSettingLocked(
+                            systemSecureSettings.insertSettingOverrideableByRestoreLocked(
                                     Settings.Secure.BACKUP_MANAGER_CONSTANTS, defaultValue, null,
                                     true, SettingsState.SYSTEM_PACKAGE_NAME);
                         }
@@ -3880,7 +4001,7 @@
                     final Setting currentSetting = globalSettings.getSettingLocked(
                             Settings.Global.MOBILE_DATA_ALWAYS_ON);
                     if (currentSetting.isNull()) {
-                        globalSettings.insertSettingLocked(
+                        globalSettings.insertSettingOverrideableByRestoreLocked(
                                 Settings.Global.MOBILE_DATA_ALWAYS_ON,
                                 getContext().getResources().getBoolean(
                                         R.bool.def_mobile_data_always_on) ? "1" : "0",
@@ -3916,7 +4037,7 @@
                     if (showNotificationBadges.isNull()) {
                         final boolean defaultValue = getContext().getResources().getBoolean(
                                 com.android.internal.R.bool.config_notificationBadging);
-                        systemSecureSettings.insertSettingLocked(
+                        systemSecureSettings.insertSettingOverrideableByRestoreLocked(
                                 Secure.NOTIFICATION_BADGING,
                                 defaultValue ? "1" : "0",
                                 null, true, SettingsState.SYSTEM_PACKAGE_NAME);
@@ -3933,7 +4054,7 @@
                         final String defaultValue = getContext().getResources().getString(
                                 R.string.def_backup_local_transport_parameters);
                         if (!TextUtils.isEmpty(defaultValue)) {
-                            systemSecureSettings.insertSettingLocked(
+                            systemSecureSettings.insertSettingOverrideableByRestoreLocked(
                                     Settings.Secure.BACKUP_LOCAL_TRANSPORT_PARAMETERS, defaultValue,
                                     null, true, SettingsState.SYSTEM_PACKAGE_NAME);
                         }
@@ -3956,7 +4077,7 @@
                     if (currentSetting.isNull()) {
                         String defaultZenDuration = Integer.toString(getContext()
                                 .getResources().getInteger(R.integer.def_zen_duration));
-                        globalSettings.insertSettingLocked(
+                        globalSettings.insertSettingOverrideableByRestoreLocked(
                                 Global.ZEN_DURATION, defaultZenDuration,
                                 null, true, SettingsState.SYSTEM_PACKAGE_NAME);
                     }
@@ -3972,7 +4093,7 @@
                         final String defaultValue = getContext().getResources().getString(
                                 R.string.def_backup_agent_timeout_parameters);
                         if (!TextUtils.isEmpty(defaultValue)) {
-                            globalSettings.insertSettingLocked(
+                            globalSettings.insertSettingOverrideableByRestoreLocked(
                                     Settings.Global.BACKUP_AGENT_TIMEOUT_PARAMETERS, defaultValue,
                                     null, true,
                                     SettingsState.SYSTEM_PACKAGE_NAME);
@@ -4001,7 +4122,7 @@
                             Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS);
                         // The default value is "1", check if user has turned it off.
                         if ("0".equals(showNotifications.getValue())) {
-                            secureSettings.insertSettingLocked(
+                            secureSettings.insertSettingOverrideableByRestoreLocked(
                                 Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, "0",
                                 null /* tag */, false /* makeDefault */,
                                 SettingsState.SYSTEM_PACKAGE_NAME);
@@ -4022,7 +4143,7 @@
                     String oldValue = globalSettings.getSettingLocked(
                             Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY).getValue();
                     if (TextUtils.equals(null, oldValue)) {
-                        globalSettings.insertSettingLocked(
+                        globalSettings.insertSettingOverrideableByRestoreLocked(
                                 Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
                                 Integer.toString(getContext().getResources().getInteger(
                                         R.integer.def_max_sound_trigger_detection_service_ops_per_day)),
@@ -4032,7 +4153,7 @@
                     oldValue = globalSettings.getSettingLocked(
                             Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT).getValue();
                     if (TextUtils.equals(null, oldValue)) {
-                        globalSettings.insertSettingLocked(
+                        globalSettings.insertSettingOverrideableByRestoreLocked(
                                 Settings.Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT,
                                 Integer.toString(getContext().getResources().getInteger(
                                         R.integer.def_sound_trigger_detection_service_op_timeout)),
@@ -4047,7 +4168,7 @@
                     final Setting currentSetting = secureSettings.getSettingLocked(
                             Secure.VOLUME_HUSH_GESTURE);
                     if (currentSetting.isNull()) {
-                        secureSettings.insertSettingLocked(
+                        secureSettings.insertSettingOverrideableByRestoreLocked(
                                 Secure.VOLUME_HUSH_GESTURE,
                                 Integer.toString(Secure.VOLUME_HUSH_VIBRATE),
                                 null, true, SettingsState.SYSTEM_PACKAGE_NAME);
@@ -4068,7 +4189,7 @@
                     final Setting currentSetting = settings.getSettingLocked(
                             Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY);
                     if (currentSetting.isDefaultFromSystem()) {
-                        settings.insertSettingLocked(
+                        settings.insertSettingOverrideableByRestoreLocked(
                                 Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
                                 Integer.toString(getContext().getResources().getInteger(
                                         R.integer
@@ -4097,7 +4218,7 @@
                     Setting currentHushUsedSetting = secureSettings.getSettingLocked(
                             Secure.HUSH_GESTURE_USED);
                     if (currentHushUsedSetting.isNull()) {
-                        secureSettings.insertSettingLocked(
+                        secureSettings.insertSettingOverrideableByRestoreLocked(
                                 Settings.Secure.HUSH_GESTURE_USED, "0", null, true,
                                 SettingsState.SYSTEM_PACKAGE_NAME);
                     }
@@ -4105,7 +4226,7 @@
                     Setting currentRingerToggleCountSetting = secureSettings.getSettingLocked(
                             Secure.MANUAL_RINGER_TOGGLE_COUNT);
                     if (currentRingerToggleCountSetting.isNull()) {
-                        secureSettings.insertSettingLocked(
+                        secureSettings.insertSettingOverrideableByRestoreLocked(
                                 Settings.Secure.MANUAL_RINGER_TOGGLE_COUNT, "0", null, true,
                                 SettingsState.SYSTEM_PACKAGE_NAME);
                     }
@@ -4124,7 +4245,7 @@
                     final Setting currentSetting = systemSettings.getSettingLocked(
                             Settings.System.VIBRATE_WHEN_RINGING);
                     if (currentSetting.isNull()) {
-                        systemSettings.insertSettingLocked(
+                        systemSettings.insertSettingOverrideableByRestoreLocked(
                                 Settings.System.VIBRATE_WHEN_RINGING,
                                 getContext().getResources().getBoolean(
                                         R.bool.def_vibrate_when_ringing) ? "1" : "0",
@@ -4148,18 +4269,18 @@
 
                     // ZEN_DURATION
                     if (!globalZenDuration.isNull()) {
-                        secureSettings.insertSettingLocked(
+                        secureSettings.insertSettingOverrideableByRestoreLocked(
                                 Secure.ZEN_DURATION, globalZenDuration.getValue(), null, false,
                                 SettingsState.SYSTEM_PACKAGE_NAME);
 
                         // set global zen duration setting to null since it's deprecated
-                        globalSettings.insertSettingLocked(
+                        globalSettings.insertSettingOverrideableByRestoreLocked(
                                 Global.ZEN_DURATION, null, null, true,
                                 SettingsState.SYSTEM_PACKAGE_NAME);
                     } else if (secureZenDuration.isNull()) {
                         String defaultZenDuration = Integer.toString(getContext()
                                 .getResources().getInteger(R.integer.def_zen_duration));
-                        secureSettings.insertSettingLocked(
+                        secureSettings.insertSettingOverrideableByRestoreLocked(
                                 Secure.ZEN_DURATION, defaultZenDuration, null, true,
                                 SettingsState.SYSTEM_PACKAGE_NAME);
                     }
@@ -4168,7 +4289,7 @@
                     final Setting currentShowZenSettingSuggestion = secureSettings.getSettingLocked(
                             Secure.SHOW_ZEN_SETTINGS_SUGGESTION);
                     if (currentShowZenSettingSuggestion.isNull()) {
-                        secureSettings.insertSettingLocked(
+                        secureSettings.insertSettingOverrideableByRestoreLocked(
                                 Secure.SHOW_ZEN_SETTINGS_SUGGESTION, "1",
                                 null, true, SettingsState.SYSTEM_PACKAGE_NAME);
                     }
@@ -4177,7 +4298,7 @@
                     final Setting currentUpdatedSetting = secureSettings.getSettingLocked(
                             Secure.ZEN_SETTINGS_UPDATED);
                     if (currentUpdatedSetting.isNull()) {
-                        secureSettings.insertSettingLocked(
+                        secureSettings.insertSettingOverrideableByRestoreLocked(
                                 Secure.ZEN_SETTINGS_UPDATED, "0",
                                 null, true, SettingsState.SYSTEM_PACKAGE_NAME);
                     }
@@ -4186,7 +4307,7 @@
                     final Setting currentSettingSuggestionViewed = secureSettings.getSettingLocked(
                             Secure.ZEN_SETTINGS_SUGGESTION_VIEWED);
                     if (currentSettingSuggestionViewed.isNull()) {
-                        secureSettings.insertSettingLocked(
+                        secureSettings.insertSettingOverrideableByRestoreLocked(
                                 Secure.ZEN_SETTINGS_SUGGESTION_VIEWED, "0",
                                 null, true, SettingsState.SYSTEM_PACKAGE_NAME);
                     }
@@ -4209,20 +4330,20 @@
 
                     if (!globalChargingSoundEnabled.isNull()) {
                         if (secureChargingSoundsEnabled.isNull()) {
-                            secureSettings.insertSettingLocked(
+                            secureSettings.insertSettingOverrideableByRestoreLocked(
                                     Secure.CHARGING_SOUNDS_ENABLED,
                                     globalChargingSoundEnabled.getValue(), null, false,
                                     SettingsState.SYSTEM_PACKAGE_NAME);
                         }
 
                         // set global charging_sounds_enabled setting to null since it's deprecated
-                        globalSettings.insertSettingLocked(
+                        globalSettings.insertSettingOverrideableByRestoreLocked(
                                 Global.CHARGING_SOUNDS_ENABLED, null, null, true,
                                 SettingsState.SYSTEM_PACKAGE_NAME);
                     } else if (secureChargingSoundsEnabled.isNull()) {
                         String defChargingSoundsEnabled = getContext().getResources()
                                 .getBoolean(R.bool.def_charging_sounds_enabled) ? "1" : "0";
-                        secureSettings.insertSettingLocked(
+                        secureSettings.insertSettingOverrideableByRestoreLocked(
                                 Secure.CHARGING_SOUNDS_ENABLED, defChargingSoundsEnabled, null,
                                 true, SettingsState.SYSTEM_PACKAGE_NAME);
                     }
@@ -4234,7 +4355,7 @@
                     if (secureChargingVibrationEnabled.isNull()) {
                         String defChargingVibrationEnabled = getContext().getResources()
                                 .getBoolean(R.bool.def_charging_vibration_enabled) ? "1" : "0";
-                        secureSettings.insertSettingLocked(
+                        secureSettings.insertSettingOverrideableByRestoreLocked(
                                 Secure.CHARGING_VIBRATION_ENABLED, defChargingVibrationEnabled,
                                 null, true, SettingsState.SYSTEM_PACKAGE_NAME);
                     }
@@ -4254,7 +4375,7 @@
                                     currentSetting.getValue());
                             if ((currentSettingIntegerValue
                                  & (1 << AudioManager.STREAM_VOICE_CALL)) == 0) {
-                                systemSettings.insertSettingLocked(
+                                systemSettings.insertSettingOverrideableByRestoreLocked(
                                     Settings.System.MUTE_STREAMS_AFFECTED,
                                     Integer.toString(
                                         currentSettingIntegerValue
@@ -4295,7 +4416,7 @@
                                             ? Secure.LOCATION_MODE_ON
                                             : Secure.LOCATION_MODE_OFF;
                         }
-                        secureSettings.insertSettingLocked(
+                        secureSettings.insertSettingOverrideableByRestoreLocked(
                                 Secure.LOCATION_MODE, Integer.toString(defLocationMode),
                                 null, true, SettingsState.SYSTEM_PACKAGE_NAME);
                     }
@@ -4317,7 +4438,7 @@
                     Setting currentRampingRingerSetting = globalSettings.getSettingLocked(
                             Settings.Global.APPLY_RAMPING_RINGER);
                     if (currentRampingRingerSetting.isNull()) {
-                        globalSettings.insertSettingLocked(
+                        globalSettings.insertSettingOverrideableByRestoreLocked(
                                 Settings.Global.APPLY_RAMPING_RINGER,
                                 getContext().getResources().getBoolean(
                                         R.bool.def_apply_ramping_ringer) ? "1" : "0", null,
@@ -4343,7 +4464,7 @@
 
                     if (!notificationVibrationIntensity.isNull()
                             && ringVibrationIntensity.isNull()) {
-                        systemSettings.insertSettingLocked(
+                        systemSettings.insertSettingOverrideableByRestoreLocked(
                                 Settings.System.RING_VIBRATION_INTENSITY,
                                 notificationVibrationIntensity.getValue(),
                                 null , true, SettingsState.SYSTEM_PACKAGE_NAME);
@@ -4387,7 +4508,7 @@
                     if (awareEnabled.isNull()) {
                         final boolean defAwareEnabled = getContext().getResources().getBoolean(
                                 R.bool.def_aware_enabled);
-                        secureSettings.insertSettingLocked(
+                        secureSettings.insertSettingOverrideableByRestoreLocked(
                                 Secure.AWARE_ENABLED, defAwareEnabled ? "1" : "0",
                                 null, true, SettingsState.SYSTEM_PACKAGE_NAME);
                     }
@@ -4407,7 +4528,7 @@
                     if (skipGesture.isNull()) {
                         final boolean defSkipGesture = getContext().getResources().getBoolean(
                                 R.bool.def_skip_gesture);
-                        secureSettings.insertSettingLocked(
+                        secureSettings.insertSettingOverrideableByRestoreLocked(
                                 Secure.SKIP_GESTURE, defSkipGesture ? "1" : "0",
                                 null, true, SettingsState.SYSTEM_PACKAGE_NAME);
                     }
@@ -4418,7 +4539,7 @@
                     if (silenceGesture.isNull()) {
                         final boolean defSilenceGesture = getContext().getResources().getBoolean(
                                 R.bool.def_silence_gesture);
-                        secureSettings.insertSettingLocked(
+                        secureSettings.insertSettingOverrideableByRestoreLocked(
                                 Secure.SILENCE_GESTURE, defSilenceGesture ? "1" : "0",
                                 null, true, SettingsState.SYSTEM_PACKAGE_NAME);
                     }
@@ -4446,7 +4567,7 @@
                     if (awareLockEnabled.isNull()) {
                         final boolean defAwareLockEnabled = getContext().getResources().getBoolean(
                                 R.bool.def_aware_lock_enabled);
-                        secureSettings.insertSettingLocked(
+                        secureSettings.insertSettingOverrideableByRestoreLocked(
                                 Secure.AWARE_LOCK_ENABLED, defAwareLockEnabled ? "1" : "0",
                                 null, true, SettingsState.SYSTEM_PACKAGE_NAME);
                     }
@@ -4466,7 +4587,7 @@
                                     currentSetting.getValue());
                             if ((currentSettingIntegerValue
                                     & (1 << AudioManager.STREAM_BLUETOOTH_SCO)) == 0) {
-                                systemSettings.insertSettingLocked(
+                                systemSettings.insertSettingOverrideableByRestoreLocked(
                                         Settings.System.MUTE_STREAMS_AFFECTED,
                                         Integer.toString(
                                         currentSettingIntegerValue
@@ -4512,13 +4633,13 @@
                     if (oldValueWireless == null
                             || TextUtils.equals(oldValueWireless, defaultValueWired)) {
                         if (!TextUtils.isEmpty(defaultValueWireless)) {
-                            globalSettings.insertSettingLocked(
+                            globalSettings.insertSettingOverrideableByRestoreLocked(
                                     Global.WIRELESS_CHARGING_STARTED_SOUND, defaultValueWireless,
                                     null /* tag */, true /* makeDefault */,
                                     SettingsState.SYSTEM_PACKAGE_NAME);
                         } else if (!TextUtils.isEmpty(defaultValueWired)) {
                             // if the wireless sound is empty, use the wired charging sound
-                            globalSettings.insertSettingLocked(
+                            globalSettings.insertSettingOverrideableByRestoreLocked(
                                     Global.WIRELESS_CHARGING_STARTED_SOUND, defaultValueWired,
                                     null /* tag */, true /* makeDefault */,
                                     SettingsState.SYSTEM_PACKAGE_NAME);
@@ -4527,7 +4648,7 @@
 
                     // wired charging sound
                     if (oldValueWired == null && !TextUtils.isEmpty(defaultValueWired)) {
-                        globalSettings.insertSettingLocked(
+                        globalSettings.insertSettingOverrideableByRestoreLocked(
                                 Global.CHARGING_STARTED_SOUND, defaultValueWired,
                                 null /* tag */, true /* makeDefault */,
                                 SettingsState.SYSTEM_PACKAGE_NAME);
@@ -4539,14 +4660,40 @@
                     // Version 184: Reset the default for Global Settings: NOTIFICATION_BUBBLES
                     // This is originally set in version 182, however, the default value changed
                     // so this step is to ensure the value is updated to the correct default.
-                    getGlobalSettingsLocked().insertSettingLocked(Global.NOTIFICATION_BUBBLES,
-                            getContext().getResources().getBoolean(
+                    getGlobalSettingsLocked().insertSettingOverrideableByRestoreLocked(
+                            Global.NOTIFICATION_BUBBLES, getContext().getResources().getBoolean(
                                     R.bool.def_notification_bubbles) ? "1" : "0", null /* tag */,
                             true /* makeDefault */, SettingsState.SYSTEM_PACKAGE_NAME);
 
                     currentVersion = 185;
                 }
 
+                if (currentVersion == 185) {
+                    // Deprecate ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, and migrate it
+                    // to ACCESSIBILITY_BUTTON_TARGET_COMPONENT.
+                    final SettingsState secureSettings = getSecureSettingsLocked(userId);
+                    final Setting magnifyNavbarEnabled = secureSettings.getSettingLocked(
+                            Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
+                    if ("1".equals(magnifyNavbarEnabled.getValue())) {
+                        secureSettings.insertSettingLocked(
+                                Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
+                                ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER,
+                                null /* tag */, false /* makeDefault */,
+                                SettingsState.SYSTEM_PACKAGE_NAME);
+                    } else {
+                        // Clear a11y button targets list setting. A11yManagerService will end up
+                        // adding all legacy enabled services that want the button to the list, so
+                        // there's no need to keep tracking them.
+                        secureSettings.insertSettingLocked(
+                                Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
+                                null, null /* tag */, false /* makeDefault */,
+                                SettingsState.SYSTEM_PACKAGE_NAME);
+                    }
+                    secureSettings.deleteSettingLocked(
+                            Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
+                    currentVersion = 186;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 if (currentVersion != newVersion) {
@@ -4590,7 +4737,7 @@
                     final boolean systemSet = SettingsState.isSystemPackage(getContext(),
                             setting.getPackageName(), callingUid, userId);
                     if (systemSet) {
-                        settings.insertSettingLocked(name, setting.getValue(),
+                        settings.insertSettingOverrideableByRestoreLocked(name, setting.getValue(),
                                 setting.getTag(), true, setting.getPackageName());
                     } else if (setting.getDefaultValue() != null && setting.isDefaultFromSystem()) {
                         // We had a bug where changes by non-system packages were marked
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 5b1b530..db18213a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -117,6 +117,8 @@
     private static final String ATTR_NAMESPACE = "namespace";
     private static final String ATTR_BANNED_HASH = "bannedHash";
 
+    private static final String ATTR_PRESERVE_IN_RESTORE = "preserve_in_restore";
+
     /**
      * Non-binary value will be written in this attributes.
      */
@@ -388,15 +390,25 @@
 
     // The settings provider must hold its lock when calling here.
     @GuardedBy("mLock")
-    public boolean insertSettingLocked(String name, String value, String tag,
+    public boolean insertSettingOverrideableByRestoreLocked(String name, String value, String tag,
             boolean makeDefault, String packageName) {
-        return insertSettingLocked(name, value, tag, makeDefault, false, packageName);
+        return insertSettingLocked(name, value, tag, makeDefault, false, packageName,
+                /* overrideableByRestore */ true);
     }
 
     // The settings provider must hold its lock when calling here.
     @GuardedBy("mLock")
     public boolean insertSettingLocked(String name, String value, String tag,
-            boolean makeDefault, boolean forceNonSystemPackage, String packageName) {
+            boolean makeDefault, String packageName) {
+        return insertSettingLocked(name, value, tag, makeDefault, false, packageName,
+                /* overrideableByRestore */ false);
+    }
+
+    // The settings provider must hold its lock when calling here.
+    @GuardedBy("mLock")
+    public boolean insertSettingLocked(String name, String value, String tag,
+            boolean makeDefault, boolean forceNonSystemPackage, String packageName,
+            boolean overrideableByRestore) {
         if (TextUtils.isEmpty(name)) {
             return false;
         }
@@ -407,7 +419,8 @@
         Setting newState;
 
         if (oldState != null) {
-            if (!oldState.update(value, makeDefault, packageName, tag, forceNonSystemPackage)) {
+            if (!oldState.update(value, makeDefault, packageName, tag, forceNonSystemPackage,
+                    overrideableByRestore)) {
                 return false;
             }
             newState = oldState;
@@ -495,7 +508,8 @@
                 changedKeys.add(key); // key was added
             } else if (state.value != value) {
                 oldValue = state.value;
-                state.update(value, false, packageName, null, true);
+                state.update(value, false, packageName, null, true,
+                        /* overrideableByRestore */ false);
                 changedKeys.add(key); // key was updated
             } else {
                 // this key/value already exists, no change and no logging necessary
@@ -797,7 +811,8 @@
 
                     writeSingleSetting(mVersion, serializer, setting.getId(), setting.getName(),
                             setting.getValue(), setting.getDefaultValue(), setting.getPackageName(),
-                            setting.getTag(), setting.isDefaultFromSystem());
+                            setting.getTag(), setting.isDefaultFromSystem(),
+                            setting.isValuePreservedInRestore());
 
                     if (DEBUG_PERSISTENCE) {
                         Slog.i(LOG_TAG, "[PERSISTED]" + setting.getName() + "="
@@ -886,7 +901,8 @@
 
     static void writeSingleSetting(int version, XmlSerializer serializer, String id,
             String name, String value, String defaultValue, String packageName,
-            String tag, boolean defaultSysSet) throws IOException {
+            String tag, boolean defaultSysSet, boolean isValuePreservedInRestore)
+            throws IOException {
         if (id == null || isBinary(id) || name == null || isBinary(name)
                 || packageName == null || isBinary(packageName)) {
             // This shouldn't happen.
@@ -905,6 +921,9 @@
             setValueAttribute(ATTR_TAG, ATTR_TAG_BASE64,
                     version, serializer, tag);
         }
+        if (isValuePreservedInRestore) {
+            serializer.attribute(null, ATTR_PRESERVE_IN_RESTORE, Boolean.toString(true));
+        }
         serializer.endTag(null, TAG_SETTING);
     }
 
@@ -1041,6 +1060,10 @@
                 String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
                 String defaultValue = getValueAttribute(parser, ATTR_DEFAULT_VALUE,
                         ATTR_DEFAULT_VALUE_BASE64);
+                String isPreservedInRestoreString = parser.getAttributeValue(null,
+                        ATTR_PRESERVE_IN_RESTORE);
+                boolean isPreservedInRestore = isPreservedInRestoreString != null
+                        && Boolean.parseBoolean(isPreservedInRestoreString);
                 String tag = null;
                 boolean fromSystem = false;
                 if (defaultValue != null) {
@@ -1049,7 +1072,7 @@
                     tag = getValueAttribute(parser, ATTR_TAG, ATTR_TAG_BASE64);
                 }
                 mSettings.put(name, new Setting(name, value, defaultValue, packageName, tag,
-                        fromSystem, id));
+                        fromSystem, id, isPreservedInRestore));
 
                 if (DEBUG_PERSISTENCE) {
                     Slog.i(LOG_TAG, "[RESTORED] " + name + "=" + value);
@@ -1133,6 +1156,8 @@
         private String tag;
         // Whether the default is set by the system
         private boolean defaultFromSystem;
+        // Whether the value of this setting will be preserved when restore happens.
+        private boolean isValuePreservedInRestore;
 
         public Setting(Setting other) {
             name = other.name;
@@ -1142,25 +1167,38 @@
             id = other.id;
             defaultFromSystem = other.defaultFromSystem;
             tag = other.tag;
+            isValuePreservedInRestore = other.isValuePreservedInRestore;
         }
 
         public Setting(String name, String value, boolean makeDefault, String packageName,
                 String tag) {
             this.name = name;
-            update(value, makeDefault, packageName, tag, false);
+            // overrideableByRestore = true as the first initialization isn't considered a
+            // modification.
+            update(value, makeDefault, packageName, tag, false,
+                    /* overrideableByRestore */ true);
         }
 
         public Setting(String name, String value, String defaultValue,
                 String packageName, String tag, boolean fromSystem, String id) {
+            this(name, value, defaultValue, packageName, tag, fromSystem, id,
+                    /* isOverrideableByRestore */ false);
+        }
+
+        Setting(String name, String value, String defaultValue,
+                String packageName, String tag, boolean fromSystem, String id,
+                boolean isValuePreservedInRestore) {
             mNextId = Math.max(mNextId, Long.parseLong(id) + 1);
             if (NULL_VALUE.equals(value)) {
                 value = null;
             }
-            init(name, value, tag, defaultValue, packageName, fromSystem, id);
+            init(name, value, tag, defaultValue, packageName, fromSystem, id,
+                    isValuePreservedInRestore);
         }
 
         private void init(String name, String value, String tag, String defaultValue,
-                String packageName, boolean fromSystem, String id) {
+                String packageName, boolean fromSystem, String id,
+                boolean isValuePreservedInRestore) {
             this.name = name;
             this.value = value;
             this.tag = tag;
@@ -1168,6 +1206,7 @@
             this.packageName = packageName;
             this.id = id;
             this.defaultFromSystem = fromSystem;
+            this.isValuePreservedInRestore = isValuePreservedInRestore;
         }
 
         public String getName() {
@@ -1198,6 +1237,10 @@
             return defaultFromSystem;
         }
 
+        public boolean isValuePreservedInRestore() {
+            return isValuePreservedInRestore;
+        }
+
         public String getId() {
             return id;
         }
@@ -1208,7 +1251,9 @@
 
         /** @return whether the value changed */
         public boolean reset() {
-            return update(this.defaultValue, false, packageName, null, true);
+            // overrideableByRestore = true as resetting to default value isn't considered a
+            // modification.
+            return update(this.defaultValue, false, packageName, null, true, true);
         }
 
         public boolean isTransient() {
@@ -1220,7 +1265,7 @@
         }
 
         public boolean update(String value, boolean setDefault, String packageName, String tag,
-                boolean forceNonSystemPackage) {
+                boolean forceNonSystemPackage, boolean overrideableByRestore) {
             if (NULL_VALUE.equals(value)) {
                 value = null;
             }
@@ -1253,17 +1298,22 @@
                 }
             }
 
+            // isValuePreservedInRestore shouldn't change back to false if it has been set to true.
+            boolean isPreserved = this.isValuePreservedInRestore || !overrideableByRestore;
+
             // Is something gonna change?
             if (Objects.equals(value, this.value)
                     && Objects.equals(defaultValue, this.defaultValue)
                     && Objects.equals(packageName, this.packageName)
                     && Objects.equals(tag, this.tag)
-                    && defaultFromSystem == this.defaultFromSystem) {
+                    && defaultFromSystem == this.defaultFromSystem
+                    && isPreserved == this.isValuePreservedInRestore) {
                 return false;
             }
 
             init(name, value, tag, defaultValue, packageName, defaultFromSystem,
-                    String.valueOf(mNextId++));
+                    String.valueOf(mNextId++), isPreserved);
+
             return true;
         }
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApBandChangedNotifier.java b/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApBandChangedNotifier.java
deleted file mode 100644
index d0d4956..0000000
--- a/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApBandChangedNotifier.java
+++ /dev/null
@@ -1,88 +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.providers.settings;
-
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-
-import com.android.internal.messages.nano.SystemMessageProto;
-import com.android.internal.notification.SystemNotificationChannels;
-
-/**
- * Helper class for sending notifications when the user's Soft AP Band was changed upon restore.
- */
-public class WifiSoftApBandChangedNotifier {
-    private WifiSoftApBandChangedNotifier() {}
-
-    /**
-     * Send a notification informing the user that their' Soft AP Band was changed upon restore.
-     * When the user taps on the notification, they are taken to the Wifi Tethering page in
-     * Settings.
-     */
-    public static void notifyUserOfApBandConversion(Context context) {
-        NotificationManager notificationManager =
-                context.getSystemService(NotificationManager.class);
-
-        // create channel, or update it if it already exists
-        NotificationChannel channel = new NotificationChannel(
-                SystemNotificationChannels.NETWORK_STATUS,
-                context.getString(android.R.string.notification_channel_network_status),
-                NotificationManager.IMPORTANCE_LOW);
-        notificationManager.createNotificationChannel(channel);
-
-        notificationManager.notify(
-                SystemMessageProto.SystemMessage.NOTE_SOFTAP_CONFIG_CHANGED,
-                createConversionNotification(context));
-    }
-
-    private static Notification createConversionNotification(Context context) {
-        Resources resources = context.getResources();
-        CharSequence title = resources.getText(R.string.wifi_softap_config_change);
-        CharSequence contentSummary = resources.getText(R.string.wifi_softap_config_change_summary);
-        CharSequence content = resources.getText(R.string.wifi_softap_config_change_detailed);
-        int color = resources.getColor(
-                android.R.color.system_notification_accent_color, context.getTheme());
-
-        return new Notification.Builder(context, SystemNotificationChannels.NETWORK_STATUS)
-                .setSmallIcon(R.drawable.ic_wifi_settings)
-                .setPriority(Notification.PRIORITY_HIGH)
-                .setCategory(Notification.CATEGORY_SYSTEM)
-                .setContentTitle(title)
-                .setContentText(contentSummary)
-                .setContentIntent(getPendingActivity(context))
-                .setTicker(title)
-                .setShowWhen(false)
-                .setLocalOnly(true)
-                .setColor(color)
-                .setStyle(new Notification.BigTextStyle()
-                        .bigText(content)
-                        .setBigContentTitle(title)
-                        .setSummaryText(contentSummary))
-                .setAutoCancel(true)
-                .build();
-    }
-
-    private static PendingIntent getPendingActivity(Context context) {
-        Intent intent = new Intent("com.android.settings.WIFI_TETHER_SETTINGS")
-                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
-    }
-}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java b/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java
new file mode 100644
index 0000000..1ee5f90
--- /dev/null
+++ b/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java
@@ -0,0 +1,88 @@
+/*
+ * 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.providers.settings;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+
+import com.android.internal.messages.nano.SystemMessageProto;
+import com.android.internal.notification.SystemNotificationChannels;
+
+/**
+ * Helper class for sending notifications when the user's Soft AP config was changed upon restore.
+ */
+public class WifiSoftApConfigChangedNotifier {
+    private WifiSoftApConfigChangedNotifier() {}
+
+    /**
+     * Send a notification informing the user that their' Soft AP Config was changed upon restore.
+     * When the user taps on the notification, they are taken to the Wifi Tethering page in
+     * Settings.
+     */
+    public static void notifyUserOfConfigConversion(Context context) {
+        NotificationManager notificationManager =
+                context.getSystemService(NotificationManager.class);
+
+        // create channel, or update it if it already exists
+        NotificationChannel channel = new NotificationChannel(
+                SystemNotificationChannels.NETWORK_STATUS,
+                context.getString(android.R.string.notification_channel_network_status),
+                NotificationManager.IMPORTANCE_LOW);
+        notificationManager.createNotificationChannel(channel);
+
+        notificationManager.notify(
+                SystemMessageProto.SystemMessage.NOTE_SOFTAP_CONFIG_CHANGED,
+                createConversionNotification(context));
+    }
+
+    private static Notification createConversionNotification(Context context) {
+        Resources resources = context.getResources();
+        CharSequence title = resources.getText(R.string.wifi_softap_config_change);
+        CharSequence contentSummary = resources.getText(R.string.wifi_softap_config_change_summary);
+        CharSequence content = resources.getText(R.string.wifi_softap_config_change_detailed);
+        int color = resources.getColor(
+                android.R.color.system_notification_accent_color, context.getTheme());
+
+        return new Notification.Builder(context, SystemNotificationChannels.NETWORK_STATUS)
+                .setSmallIcon(R.drawable.ic_wifi_settings)
+                .setPriority(Notification.PRIORITY_HIGH)
+                .setCategory(Notification.CATEGORY_SYSTEM)
+                .setContentTitle(title)
+                .setContentText(contentSummary)
+                .setContentIntent(getPendingActivity(context))
+                .setTicker(title)
+                .setShowWhen(false)
+                .setLocalOnly(true)
+                .setColor(color)
+                .setStyle(new Notification.BigTextStyle()
+                        .bigText(content)
+                        .setBigContentTitle(title)
+                        .setSummaryText(contentSummary))
+                .setAutoCancel(true)
+                .build();
+    }
+
+    private static PendingIntent getPendingActivity(Context context) {
+        Intent intent = new Intent("com.android.settings.WIFI_TETHER_SETTINGS")
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+    }
+}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 7278225..b896a2a 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -195,6 +195,7 @@
                     Settings.Global.CERT_PIN_UPDATE_CONTENT_URL,
                     Settings.Global.CERT_PIN_UPDATE_METADATA_URL,
                     Settings.Global.COMPATIBILITY_MODE,
+                    Settings.Global.COMMON_CRITERIA_MODE,
                     Settings.Global.CONNECTIVITY_CHANGE_DELAY,
                     Settings.Global.CONNECTIVITY_METRICS_BUFFER_SIZE,
                     Settings.Global.CONNECTIVITY_SAMPLING_INTERVAL_IN_SECONDS,
@@ -369,7 +370,6 @@
                     Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME,
                     Settings.Global.NETWORK_PREFERENCE,
                     Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE,
-                    Settings.Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS,
                     Settings.Global.NETWORK_SCORER_APP,
                     Settings.Global.NETWORK_SCORING_PROVISIONED,
                     Settings.Global.NETWORK_SCORING_UI_ENABLED,
@@ -523,8 +523,6 @@
                     Settings.Global.WIFI_BADGING_THRESHOLDS,
                     Settings.Global.WIFI_BOUNCE_DELAY_OVERRIDE_MS,
                     Settings.Global.WIFI_COUNTRY_CODE,
-                    Settings.Global.WIFI_DATA_STALL_MIN_TX_BAD,
-                    Settings.Global.WIFI_DATA_STALL_MIN_TX_SUCCESS_WITHOUT_RX,
                     Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN,
                     Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON,
                     Settings.Global.WIFI_DISPLAY_ON,
@@ -534,11 +532,6 @@
                     Settings.Global.WIFI_FRAMEWORK_SCAN_INTERVAL_MS,
                     Settings.Global.WIFI_FREQUENCY_BAND,
                     Settings.Global.WIFI_IDLE_MS,
-                    Settings.Global.WIFI_IS_UNUSABLE_EVENT_METRICS_ENABLED,
-                    Settings.Global.WIFI_LINK_SPEED_METRICS_ENABLED,
-                    Settings.Global.WIFI_PNO_FREQUENCY_CULLING_ENABLED,
-                    Settings.Global.WIFI_PNO_RECENCY_SORTING_ENABLED,
-                    Settings.Global.WIFI_LINK_PROBING_ENABLED,
                     Settings.Global.WIFI_MAX_DHCP_RETRY_COUNT,
                     Settings.Global.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS,
                     Settings.Global.WIFI_NETWORK_SHOW_RSSI,
@@ -547,7 +540,6 @@
                     Settings.Global.WIFI_ON,
                     Settings.Global.WIFI_P2P_DEVICE_NAME,
                     Settings.Global.WIFI_P2P_PENDING_FACTORY_RESET,
-                    Settings.Global.WIFI_RTT_BACKGROUND_EXEC_GAP_MS,
                     Settings.Global.WIFI_SAVED_STATE,
                     Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE,
                     Settings.Global.WIFI_SCAN_INTERVAL_WHEN_P2P_CONNECTED_MS,
@@ -555,7 +547,6 @@
                     Settings.Global.WIFI_SCORE_PARAMS,
                     Settings.Global.WIFI_SLEEP_POLICY,
                     Settings.Global.WIFI_SUPPLICANT_SCAN_INTERVAL_MS,
-                    Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED,
                     Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED,
                     Settings.Global.WIFI_WATCHDOG_ON,
                     Settings.Global.WIMAX_NETWORKS_AVAILABLE_NOTIFICATION_ON,
@@ -734,7 +725,9 @@
                  Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE,
                  Settings.Secure.FACE_UNLOCK_RE_ENROLL,
                  Settings.Secure.TAP_GESTURE,
-                 Settings.Secure.WINDOW_MAGNIFICATION);
+                 Settings.Secure.WINDOW_MAGNIFICATION,
+                 Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER,
+                 Settings.Secure.SUPPRESS_DOZE);
 
     @Test
     public void systemSettingsBackedUpOrBlacklisted() {
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 3f68554..b855d87 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -46,6 +46,18 @@
             "\uD800ab\uDC00 " + // broken surrogate pairs
             "日本語";
 
+    private static final String TEST_PACKAGE = "package";
+    private static final String SETTING_NAME = "test_setting";
+
+    private final Object mLock = new Object();
+
+    private File mSettingsFile;
+
+    @Override
+    protected void setUp() {
+        mSettingsFile = new File(getContext().getCacheDir(), "setting.xml");
+        mSettingsFile.delete();
+    }
 
     public void testIsBinary() {
         assertFalse(SettingsState.isBinary(" abc 日本語"));
@@ -99,10 +111,10 @@
         checkWriteSingleSetting(serializer, CRAZY_STRING, null);
         SettingsState.writeSingleSetting(
                 SettingsState.SETTINGS_VERSION_NEW_ENCODING,
-                serializer, null, "k", "v", null, "package", null, false);
+                serializer, null, "k", "v", null, "package", null, false, false);
         SettingsState.writeSingleSetting(
                 SettingsState.SETTINGS_VERSION_NEW_ENCODING,
-                serializer, "1", "k", "v", null, null, null, false);
+                serializer, "1", "k", "v", null, null, null, false, false);
     }
 
     private void checkWriteSingleSetting(XmlSerializer serializer, String key, String value)
@@ -115,7 +127,7 @@
         // Make sure the XML serializer won't crash.
         SettingsState.writeSingleSetting(
                 SettingsState.SETTINGS_VERSION_NEW_ENCODING,
-                serializer, "1", key, value, null, "package", null, false);
+                serializer, "1", key, value, null, "package", null, false, false);
     }
 
     /**
@@ -182,4 +194,57 @@
             assertEquals("p2", s.getPackageName());
         }
     }
+
+    public void testInitializeSetting_preserveFlagNotSet() {
+        SettingsState settingsWriter = getSettingStateObject();
+        settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE);
+        settingsWriter.persistSyncLocked();
+
+        SettingsState settingsReader = getSettingStateObject();
+        assertFalse(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
+    }
+
+    public void testModifySetting_preserveFlagSet() {
+        SettingsState settingsWriter = getSettingStateObject();
+        settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE);
+        settingsWriter.insertSettingLocked(SETTING_NAME, "2", null, false, TEST_PACKAGE);
+        settingsWriter.persistSyncLocked();
+
+        SettingsState settingsReader = getSettingStateObject();
+        assertTrue(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
+    }
+
+    public void testModifySettingOverrideableByRestore_preserveFlagNotSet() {
+        SettingsState settingsWriter = getSettingStateObject();
+        settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE);
+        settingsWriter.insertSettingLocked(SETTING_NAME, "2", null, false, false, TEST_PACKAGE,
+                /* overrideableByRestore */ true);
+        settingsWriter.persistSyncLocked();
+
+        SettingsState settingsReader = getSettingStateObject();
+        assertFalse(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
+    }
+
+    public void testModifySettingOverrideableByRestore_preserveFlagAlreadySet_flagValueUnchanged() {
+        SettingsState settingsWriter = getSettingStateObject();
+        // Init the setting.
+        settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE);
+        // This modification will set isValuePreservedInRestore = true.
+        settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE);
+        // This modification shouldn't change the value of isValuePreservedInRestore since it's
+        // already been set to true.
+        settingsWriter.insertSettingLocked(SETTING_NAME, "2", null, false, false, TEST_PACKAGE,
+                /* overrideableByRestore */ true);
+        settingsWriter.persistSyncLocked();
+
+        SettingsState settingsReader = getSettingStateObject();
+        assertTrue(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
+    }
+
+    private SettingsState getSettingStateObject() {
+        SettingsState settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1,
+                SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+        settingsState.setVersionLocked(SettingsState.SETTINGS_VERSION_NEW_ENCODING);
+        return settingsState;
+    }
 }
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 347d6c2..fb9bc52 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -194,6 +194,9 @@
 
     <uses-permission android:name="android.permission.MANAGE_APPOPS" />
 
+    <!-- Permission required for storage tests - FuseDaemonHostTest -->
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+
     <!-- Permission needed to run network tests in CTS -->
     <uses-permission android:name="android.permission.MANAGE_TEST_NETWORKS" />
     <!-- Permission needed to test tcp keepalive offload. -->
@@ -218,7 +221,10 @@
     <!-- Permission required for CTS test - UiModeManagerTest -->
     <uses-permission android:name="android.permission.ENTER_CAR_MODE_PRIORITIZED"/>
 
-      <!-- Permission required for CTS test - CarModeInCallServiceTest -->
+    <!-- Permission required for CTS test - SystemConfigTest -->
+    <uses-permission android:name="android.permission.READ_CARRIER_APP_INFO"/>
+
+    <!-- Permission required for CTS test - CarModeInCallServiceTest -->
     <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
 
     <!-- Permission requried for CTS test - CellBroadcastIntentsTest -->
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 2a1e74e..6aeb0a1 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -70,7 +70,7 @@
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
     <uses-permission android:name="android.permission.OVERRIDE_WIFI_CONFIG" />
     <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
-    <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
+    <uses-permission android:name="android.permission.OBSERVE_NETWORK_POLICY" />
     <uses-permission android:name="android.permission.NETWORK_SETTINGS" />
     <uses-permission android:name="android.permission.TETHER_PRIVILEGED" />
     <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
@@ -174,6 +174,9 @@
     <!-- Adding Quick Settings tiles -->
     <uses-permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE" />
 
+    <!-- Access Quick Access Wallet cards -->
+    <uses-permission android:name="android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE" />
+
     <!-- Adding Controls to SystemUI -->
     <uses-permission android:name="android.permission.BIND_CONTROLS" />
 
@@ -211,6 +214,7 @@
 
     <!-- accessibility -->
     <uses-permission android:name="android.permission.MODIFY_ACCESSIBILITY_DATA" />
+    <uses-permission android:name="android.permission.MANAGE_ACCESSIBILITY" />
 
     <!-- to control accessibility volume -->
     <uses-permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME" />
@@ -307,7 +311,8 @@
         </receiver>
 
         <activity android:name=".screenrecord.ScreenRecordDialog"
-            android:theme="@style/ScreenRecord" />
+            android:theme="@style/ScreenRecord"
+            android:excludeFromRecents="true" />
         <service android:name=".screenrecord.RecordingService" />
 
         <receiver android:name=".SysuiRestartReceiver"
@@ -636,6 +641,23 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".controls.management.ControlsProviderSelectorActivity"
+                  android:label="Controls Providers"
+                  android:theme="@style/Theme.SystemUI"
+                  android:exported="true"
+                  android:excludeFromRecents="true"
+                  android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
+                  android:visibleToInstantApps="true">
+        </activity>
+
+        <activity android:name=".controls.management.ControlsFavoritingActivity"
+                  android:parentActivityName=".controls.management.ControlsProviderSelectorActivity"
+                  android:theme="@style/Theme.SystemUI"
+                  android:excludeFromRecents="true"
+                  android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
+                  android:visibleToInstantApps="true">
+        </activity>
+
         <!-- Doze with notifications, run in main sysui process for every user  -->
         <service
             android:name=".doze.DozeService"
diff --git a/packages/SystemUI/docs/broadcasts.md b/packages/SystemUI/docs/broadcasts.md
index 56a637f..28657f2 100644
--- a/packages/SystemUI/docs/broadcasts.md
+++ b/packages/SystemUI/docs/broadcasts.md
@@ -42,24 +42,29 @@
 
 ```kotlin
 /**
-    * Register a receiver for broadcast with the dispatcher
-    *
-    * @param receiver A receiver to dispatch the [Intent]
-    * @param filter A filter to determine what broadcasts should be dispatched to this receiver.
-    *               It will only take into account actions and categories for filtering. It must
-    *               have at least one action.
-    * @param handler A handler to dispatch [BroadcastReceiver.onReceive]. By default, it is the
-    *                main handler. Pass `null` to use the default.
-    * @param user A user handle to determine which broadcast should be dispatched to this receiver.
-    *             By default, it is the current user.
-    * @throws IllegalArgumentException if the filter has other constraints that are not actions or
-    *                                  categories or the filter has no actions.
-    */
+ * Register a receiver for broadcast with the dispatcher
+ *
+ * @param receiver A receiver to dispatch the [Intent]
+ * @param filter A filter to determine what broadcasts should be dispatched to this receiver.
+ *               It will only take into account actions and categories for filtering. It must
+ *               have at least one action.
+ * @param executor An executor to dispatch [BroadcastReceiver.onReceive]. Pass null to use an
+ *                 executor in the main thread (default).
+ * @param user A user handle to determine which broadcast should be dispatched to this receiver.
+ *             By default, it is the current user.
+ * @throws IllegalArgumentException if the filter has other constraints that are not actions or
+ *                                  categories or the filter has no actions.
+ */
 @JvmOverloads
-fun registerReceiver(BroadcastReceiver, IntentFilter, Handler? = mainHandler, UserHandle = context.user)
+fun registerReceiver(
+        BroadcastReceiver, 
+        IntentFilter, 
+        Executor? = context.mainExecutor,
+        UserHandle = context.user
+) {
 ```
 
-All subscriptions are done with the same overloaded method. As specified in the doc, in order to pass a `UserHandle` with the default `Handler`, pass `null` for the `Handler`.
+All subscriptions are done with the same overloaded method. As specified in the doc, in order to pass a `UserHandle` with the default `Executor`, pass `null` for the `Executor`.
 
 In the same way as with `Context`, subscribing the same `BroadcastReceiver` for the same user using different filters will result on two subscriptions, not in replacing the filter.
 
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
index 8db0d02..02c4c5e 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
@@ -46,6 +46,8 @@
      */
     public void snooze(StatusBarNotification sbn, SnoozeOption snoozeOption);
 
+    public void snooze(StatusBarNotification sbn, int hours);
+
     public float getMinDismissVelocity();
 
     public boolean isDismissGesture(MotionEvent ev);
diff --git a/packages/SystemUI/res-keyguard/values-af/strings.xml b/packages/SystemUI/res-keyguard/values-af/strings.xml
index 92dd9fd..8a1f6de 100644
--- a/packages/SystemUI/res-keyguard/values-af/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-af/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Geen diens nie."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Wissel invoermetode"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Vliegtuigmodus"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"PIN word vereis om vir opdatering voor te berei"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Patroon word vereis om vir opdatering voor te berei"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Wagwoord word vereis om vir opdatering voor te berei"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Patroon word vereis nadat toestel herbegin het"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN word vereis nadat toestel herbegin het"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Wagwoord word vereis nadat toestel herbegin het"</string>
diff --git a/packages/SystemUI/res-keyguard/values-am/strings.xml b/packages/SystemUI/res-keyguard/values-am/strings.xml
index f94c20f..0a4aee5 100644
--- a/packages/SystemUI/res-keyguard/values-am/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-am/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"ከአገልግሎት መስጫ ክልል ውጪ።"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"የግቤት ስልት ቀይር"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"የአውሮፕላን ሁነታ"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"ለዝማኔ ለማዘጋጀት ፒን ያስፈልጋል"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"ለዝማኔ ለማዘጋጀት ሥርዓተ ጥለት ያስፈልጋል"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"ለዝማኔ ለማዘጋጀት የይለፍ ቃል ያስፈልጋል"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"መሣሪያ ዳግም ከጀመረ በኋላ ሥርዓተ ጥለት ያስፈልጋል"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"መሣሪያ ዳግም ከተነሳ በኋላ ፒን ያስፈልጋል"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"መሣሪያ ዳግም ከጀመረ በኋላ የይለፍ ቃል ያስፈልጋል"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ar/strings.xml b/packages/SystemUI/res-keyguard/values-ar/strings.xml
index 393da27..491dc39a 100644
--- a/packages/SystemUI/res-keyguard/values-ar/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ar/strings.xml
@@ -48,7 +48,7 @@
     <string name="keyguard_permanent_disabled_sim_instructions" msgid="2490584154727897806">"‏تم إيقاف شريحة SIM بشكل دائم.\n اتصل بمقدم خدمة اللاسلكي للحصول على شريحة SIM أخرى."</string>
     <string name="keyguard_sim_locked_message" msgid="4343544458476911044">"‏شريحة SIM مؤمّنة."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="6253830777745450550">"‏شريحة SIM مؤمّنة برمز PUK."</string>
-    <string name="keyguard_sim_unlock_progress_dialog_message" msgid="2394023844117630429">"‏جارٍ إلغاء تأمين شريحة SIM…"</string>
+    <string name="keyguard_sim_unlock_progress_dialog_message" msgid="2394023844117630429">"‏جارٍ فتح قفل شريحة SIM…"</string>
     <string name="keyguard_accessibility_pin_area" msgid="7403009340414014734">"منطقة رقم التعريف الشخصي"</string>
     <string name="keyguard_accessibility_password" msgid="3524161948484801450">"كلمة مرور الجهاز"</string>
     <string name="keyguard_accessibility_sim_pin_area" msgid="6272116591533888062">"‏منطقة رقم التعريف الشخصي لشريحة SIM"</string>
@@ -81,7 +81,7 @@
     <string name="kg_puk_enter_puk_hint_multi" msgid="4876780689904862943">"‏SIM \"<xliff:g id="CARRIER">%1$s</xliff:g>\" غير مفعّلة الآن. أدخل رمز PUK للمتابعة. واتصل بمشغل شبكة الجوّال لمعرفة التفاصيل."</string>
     <string name="kg_puk_enter_pin_hint" msgid="6028432138916150399">"أدخل رمز رقم التعريف الشخصي المطلوب"</string>
     <string name="kg_enter_confirm_pin_hint" msgid="4261064020391799132">"تأكيد رمز رقم التعريف الشخصي المطلوب"</string>
-    <string name="kg_sim_unlock_progress_dialog_message" msgid="4251352015304070326">"‏جارٍ إلغاء تأمين شريحة SIM…"</string>
+    <string name="kg_sim_unlock_progress_dialog_message" msgid="4251352015304070326">"‏جارٍ فتح قفل شريحة SIM…"</string>
     <string name="kg_invalid_sim_pin_hint" msgid="2762202646949552978">"اكتب رمز رقم التعريف الشخصي المكوّن من ٤ إلى ٨ أرقام."</string>
     <string name="kg_invalid_sim_puk_hint" msgid="5319756880543857694">"‏يجب أن يتضمن رمز PUK‏ ۸ أرقام أو أكثر."</string>
     <string name="kg_invalid_puk" msgid="1774337070084931186">"‏أعد إدخال رمز PUK الصحيح. وستؤدي المحاولات المتكررة إلى إيقاف شريحة SIM نهائيًا."</string>
@@ -113,6 +113,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"لا تتوفر خدمة."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"تبديل أسلوب الإدخال"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"وضع الطائرة"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"يجب إدخال رقم التعريف الشخصي للتحضير للتحديث."</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"يجب رسم النقش للتحضير للتحديث."</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"يجب إدخال كلمة المرور للتحضير للتحديث."</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"يجب رسم النقش بعد إعادة تشغيل الجهاز"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"يجب إدخال رقم التعريف الشخصي بعد إعادة تشغيل الجهاز"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"يجب إدخال كلمة المرور بعد إعادة تشغيل الجهاز"</string>
@@ -125,28 +128,28 @@
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"اختار المشرف قفل الجهاز"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"تم حظر الجهاز يدويًا"</string>
     <plurals name="kg_prompt_reason_time_pattern" formatted="false" msgid="1337428979661197957">
-      <item quantity="zero">لم يتم إلغاء تأمين الجهاز لمدة <xliff:g id="NUMBER_1">%d</xliff:g> ساعة. تأكيد النقش.</item>
-      <item quantity="two">لم يتم إلغاء تأمين الجهاز لمدة ساعتين (<xliff:g id="NUMBER_1">%d</xliff:g>). تأكيد النقش.</item>
-      <item quantity="few">لم يتم إلغاء تأمين الجهاز لمدة <xliff:g id="NUMBER_1">%d</xliff:g> ساعات. تأكيد النقش.</item>
-      <item quantity="many">لم يتم إلغاء تأمين الجهاز لمدة <xliff:g id="NUMBER_1">%d</xliff:g> ساعة. تأكيد النقش.</item>
-      <item quantity="other">لم يتم إلغاء تأمين الجهاز لمدة <xliff:g id="NUMBER_1">%d</xliff:g> ساعة. تأكيد النقش.</item>
-      <item quantity="one">لم يتم إلغاء تأمين الجهاز لمدة <xliff:g id="NUMBER_0">%d</xliff:g> ساعة. تأكيد النقش.</item>
+      <item quantity="zero">لم يتم فتح قفل الجهاز لمدة <xliff:g id="NUMBER_1">%d</xliff:g> ساعة. تأكيد النقش.</item>
+      <item quantity="two">لم يتم فتح قفل الجهاز لمدة ساعتين (<xliff:g id="NUMBER_1">%d</xliff:g>). تأكيد النقش.</item>
+      <item quantity="few">لم يتم فتح قفل الجهاز لمدة <xliff:g id="NUMBER_1">%d</xliff:g> ساعات. تأكيد النقش.</item>
+      <item quantity="many">لم يتم فتح قفل الجهاز لمدة <xliff:g id="NUMBER_1">%d</xliff:g> ساعة. تأكيد النقش.</item>
+      <item quantity="other">لم يتم فتح قفل الجهاز لمدة <xliff:g id="NUMBER_1">%d</xliff:g> ساعة. تأكيد النقش.</item>
+      <item quantity="one">لم يتم فتح قفل الجهاز لمدة <xliff:g id="NUMBER_0">%d</xliff:g> ساعة. تأكيد النقش.</item>
     </plurals>
     <plurals name="kg_prompt_reason_time_pin" formatted="false" msgid="6444519502336330270">
-      <item quantity="zero">لم يتم إلغاء تأمين الجهاز لمدة <xliff:g id="NUMBER_1">%d</xliff:g> ساعة. تأكيد رقم التعريف الشخصي.</item>
-      <item quantity="two">لم يتم إلغاء تأمين الجهاز لمدة ساعتين (<xliff:g id="NUMBER_1">%d</xliff:g>). تأكيد رقم التعريف الشخصي.</item>
-      <item quantity="few">لم يتم إلغاء تأمين الجهاز لمدة <xliff:g id="NUMBER_1">%d</xliff:g> ساعة. تأكيد رقم التعريف الشخصي.</item>
-      <item quantity="many">لم يتم إلغاء تأمين الجهاز لمدة <xliff:g id="NUMBER_1">%d</xliff:g> ساعة. تأكيد رقم التعريف الشخصي.</item>
-      <item quantity="other">لم يتم إلغاء تأمين الجهاز لمدة <xliff:g id="NUMBER_1">%d</xliff:g> ساعة. تأكيد رقم التعريف الشخصي.</item>
-      <item quantity="one">لم يتم إلغاء تأمين الجهاز لمدة <xliff:g id="NUMBER_0">%d</xliff:g> ساعة. تأكيد رقم التعريف الشخصي.</item>
+      <item quantity="zero">لم يتم فتح قفل الجهاز لمدة <xliff:g id="NUMBER_1">%d</xliff:g> ساعة. تأكيد رقم التعريف الشخصي.</item>
+      <item quantity="two">لم يتم فتح قفل الجهاز لمدة ساعتين (<xliff:g id="NUMBER_1">%d</xliff:g>). تأكيد رقم التعريف الشخصي.</item>
+      <item quantity="few">لم يتم فتح قفل الجهاز لمدة <xliff:g id="NUMBER_1">%d</xliff:g> ساعة. تأكيد رقم التعريف الشخصي.</item>
+      <item quantity="many">لم يتم فتح قفل الجهاز لمدة <xliff:g id="NUMBER_1">%d</xliff:g> ساعة. تأكيد رقم التعريف الشخصي.</item>
+      <item quantity="other">لم يتم فتح قفل الجهاز لمدة <xliff:g id="NUMBER_1">%d</xliff:g> ساعة. تأكيد رقم التعريف الشخصي.</item>
+      <item quantity="one">لم يتم فتح قفل الجهاز لمدة <xliff:g id="NUMBER_0">%d</xliff:g> ساعة. تأكيد رقم التعريف الشخصي.</item>
     </plurals>
     <plurals name="kg_prompt_reason_time_password" formatted="false" msgid="5343961527665116914">
-      <item quantity="zero">لم يتم إلغاء تأمين الجهاز لمدة <xliff:g id="NUMBER_1">%d</xliff:g> ساعة. تأكيد كلمة المرور.</item>
-      <item quantity="two">لم يتم إلغاء تأمين الجهاز لمدة ساعتين (<xliff:g id="NUMBER_1">%d</xliff:g>). تأكيد كلمة المرور.</item>
-      <item quantity="few">لم يتم إلغاء تأمين الجهاز لمدة <xliff:g id="NUMBER_1">%d</xliff:g> ساعات. تأكيد كلمة المرور.</item>
-      <item quantity="many">لم يتم إلغاء تأمين الجهاز لمدة <xliff:g id="NUMBER_1">%d</xliff:g> ساعة. تأكيد كلمة المرور.</item>
-      <item quantity="other">لم يتم إلغاء تأمين الجهاز لمدة <xliff:g id="NUMBER_1">%d</xliff:g> ساعة. تأكيد كلمة المرور.</item>
-      <item quantity="one">لم يتم إلغاء تأمين الجهاز لمدة <xliff:g id="NUMBER_0">%d</xliff:g> ساعة. تأكيد كلمة المرور.</item>
+      <item quantity="zero">لم يتم فتح قفل الجهاز لمدة <xliff:g id="NUMBER_1">%d</xliff:g> ساعة. تأكيد كلمة المرور.</item>
+      <item quantity="two">لم يتم فتح قفل الجهاز لمدة ساعتين (<xliff:g id="NUMBER_1">%d</xliff:g>). تأكيد كلمة المرور.</item>
+      <item quantity="few">لم يتم فتح قفل الجهاز لمدة <xliff:g id="NUMBER_1">%d</xliff:g> ساعات. تأكيد كلمة المرور.</item>
+      <item quantity="many">لم يتم فتح قفل الجهاز لمدة <xliff:g id="NUMBER_1">%d</xliff:g> ساعة. تأكيد كلمة المرور.</item>
+      <item quantity="other">لم يتم فتح قفل الجهاز لمدة <xliff:g id="NUMBER_1">%d</xliff:g> ساعة. تأكيد كلمة المرور.</item>
+      <item quantity="one">لم يتم فتح قفل الجهاز لمدة <xliff:g id="NUMBER_0">%d</xliff:g> ساعة. تأكيد كلمة المرور.</item>
     </plurals>
     <string name="kg_fingerprint_not_recognized" msgid="5982606907039479545">"لم يتم التعرف عليها."</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"لم يتم التعرّف عليه."</string>
diff --git a/packages/SystemUI/res-keyguard/values-as/strings.xml b/packages/SystemUI/res-keyguard/values-as/strings.xml
index 3b51e48..4367efb 100644
--- a/packages/SystemUI/res-keyguard/values-as/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-as/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"কোনো সেৱা নাই।"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"ইনপুট পদ্ধতি সলনি কৰক"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"এয়াৰপ্লেন ম\'ড"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"আপডে\'টৰ বাবে সাজু হ\'বলৈ পিনৰ আৱশ্যক"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"আপডে\'টৰ বাবে সাজু হ\'বলৈ আর্হিৰ আৱশ্যক"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"আপডে\'টৰ বাবে সাজু হ\'বলৈ পাছৱৰ্ডৰ আৱশ্যক"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পিছত আৰ্হি দিয়াটো বাধ্যতামূলক"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পিছত পিন দিয়াটো বাধ্যতামূলক"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পিছত পাছৱৰ্ড দিয়াটো বাধ্যতামূলক"</string>
diff --git a/packages/SystemUI/res-keyguard/values-az/strings.xml b/packages/SystemUI/res-keyguard/values-az/strings.xml
index d63c23f..aadd201 100644
--- a/packages/SystemUI/res-keyguard/values-az/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-az/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Xidmət yoxdur."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Daxiletmə metoduna keçin"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Təyyarə rejimi"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Güncəlləməyə hazırlıq üçün PIN kod tələb olunur"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Güncəlləməyə hazırlıq üçün model tələb olunur"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Güncəlləməyə hazırlıq üçün parol tələb olunur"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Cihaz yenidən başladıqdan sonra model tələb olunur"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Cihaz yeniden başladıqdan sonra PIN tələb olunur"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Cihaz yeniden başladıqdan sonra parol tələb olunur"</string>
diff --git a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
index e206958..656e323 100644
--- a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
@@ -104,6 +104,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Mreža nije dostupna."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Promeni metod unosa"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Režim rada u avionu"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"PIN je obavezan radi pripreme za ažuriranje"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Šablon je obavezan radi pripreme za ažuriranje"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Lozinka je obavezna radi pripreme za ažuriranje"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Treba da unesete šablon kada se uređaj ponovo pokrene"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Treba da unesete PIN kada se uređaj ponovo pokrene"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Treba da unesete lozinku kada se uređaj ponovo pokrene"</string>
diff --git a/packages/SystemUI/res-keyguard/values-be/strings.xml b/packages/SystemUI/res-keyguard/values-be/strings.xml
index 569e705..07b6f35 100644
--- a/packages/SystemUI/res-keyguard/values-be/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-be/strings.xml
@@ -107,6 +107,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Не абслугоўваецца."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Пераключэнне рэжыму ўводу"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Рэжым палёту"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Для падрыхтоўкі да абнаўлення неабходна ўвесці PIN-код"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Для падрыхтоўкі да абнаўлення неабходна ўвесці ўзор разблакіроўкі"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Для падрыхтоўкі да абнаўлення неабходна ўвесці пароль"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Пасля перазапуску прылады патрабуецца ўзор"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Пасля перазапуску прылады патрабуецца PIN-код"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Пасля перазапуску прылады патрабуецца пароль"</string>
diff --git a/packages/SystemUI/res-keyguard/values-bg/strings.xml b/packages/SystemUI/res-keyguard/values-bg/strings.xml
index d015be3..a8c64f5 100644
--- a/packages/SystemUI/res-keyguard/values-bg/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bg/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Няма покритие."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Превключване на метода на въвеждане"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Самолетен режим"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"За подготовката за актуализация се изисква ПИН код"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"За подготовката за актуализация се изисква фигура"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"За подготовката за актуализация се изисква парола"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"След рестартиране на устройството се изисква фигура"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"След рестартиране на устройството се изисква ПИН код"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"След рестартиране на устройството се изисква парола"</string>
diff --git a/packages/SystemUI/res-keyguard/values-bn/strings.xml b/packages/SystemUI/res-keyguard/values-bn/strings.xml
index 8eae6e6..479e83a 100644
--- a/packages/SystemUI/res-keyguard/values-bn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bn/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"কোনো পরিষেবা নেই।"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"ইনপুট পদ্ধতি পরিবর্তন করুন"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"বিমান মোড"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"আপডেট প্রস্তুত করতে পিন দরকার"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"আপডেট প্রস্তুত করতে প্যাটার্ন দরকার"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"আপডেট প্রস্তুত করতে পাসওয়ার্ড দরকার"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ডিভাইসটি পুনরায় চালু হওয়ার পর প্যাটার্নের প্রয়োজন হবে"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ডিভাইসটি পুনরায় চালু হওয়ার পর পিন প্রয়োজন হবে"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ডিভাইসটি পুনরায় চালু হওয়ার পর পাসওয়ার্ডের প্রয়োজন হবে"</string>
diff --git a/packages/SystemUI/res-keyguard/values-bs/strings.xml b/packages/SystemUI/res-keyguard/values-bs/strings.xml
index 286b08b..ada4c13 100644
--- a/packages/SystemUI/res-keyguard/values-bs/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bs/strings.xml
@@ -104,6 +104,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Nema mreže."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Promjena načina unosa"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Način rada u avionu"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Za pripremu ažuriranja potreban je PIN"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Za pripremu ažuriranja potreban je uzorak"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Za pripremu ažuriranja potrebna je lozinka"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Potreban je uzorak nakon što se uređaj ponovo pokrene"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Potreban je PIN nakon što se uređaj ponovo pokrene"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Potrebna je lozinka nakon što se uređaj ponovo pokrene"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ca/strings.xml b/packages/SystemUI/res-keyguard/values-ca/strings.xml
index cb7fa37..6f5b682 100644
--- a/packages/SystemUI/res-keyguard/values-ca/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ca/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Sense servei"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Canvia el mètode d\'introducció"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Mode d\'avió"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Cal introduir el PIN per preparar l\'actualització"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Cal introduir el patró per preparar l\'actualització"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Cal introduir la contrasenya per preparar l\'actualització"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Cal introduir el patró quan es reinicia el dispositiu"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Cal introduir el PIN quan es reinicia el dispositiu"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Cal introduir la contrasenya quan es reinicia el dispositiu"</string>
diff --git a/packages/SystemUI/res-keyguard/values-cs/strings.xml b/packages/SystemUI/res-keyguard/values-cs/strings.xml
index 4f0c0ff..a2f79ad 100644
--- a/packages/SystemUI/res-keyguard/values-cs/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-cs/strings.xml
@@ -107,6 +107,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Žádný signál"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Přepnout metodu zadávání"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Režim Letadlo"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Příprava na aktualizaci vyžaduje PIN"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Příprava na aktualizaci vyžaduje gesto"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Příprava na aktualizaci vyžaduje heslo"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Po restartování zařízení je vyžadováno gesto"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Po restartování zařízení je vyžadován kód PIN"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Po restartování zařízení je vyžadováno heslo"</string>
diff --git a/packages/SystemUI/res-keyguard/values-da/strings.xml b/packages/SystemUI/res-keyguard/values-da/strings.xml
index e486fc6..ef06269 100644
--- a/packages/SystemUI/res-keyguard/values-da/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-da/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Ingen dækning."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Skift indtastningsmetode"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Flytilstand"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Du skal angive din pinkode for at forberede opdateringen"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Du skal angive dit mønster for at forberede opdateringen"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Du skal angive din adgangskode for at forberede opdateringen"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Du skal angive et mønster, når du har genstartet enheden"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Der skal angives en pinkode efter genstart af enheden"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Der skal angives en adgangskode efter genstart af enheden"</string>
diff --git a/packages/SystemUI/res-keyguard/values-de/strings.xml b/packages/SystemUI/res-keyguard/values-de/strings.xml
index 06d012f..fdfce1f 100644
--- a/packages/SystemUI/res-keyguard/values-de/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-de/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Dienst nicht verfügbar"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Eingabemethode wechseln"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Flugmodus"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Zur Vorbereitung auf das Update ist eine PIN erforderlich"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Zur Vorbereitung auf das Update ist ein Muster erforderlich"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Zur Vorbereitung auf das Update ist ein Passwort erforderlich"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Nach dem Neustart des Geräts ist die Eingabe des Musters erforderlich"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Nach dem Neustart des Geräts ist die Eingabe der PIN erforderlich"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Nach dem Neustart des Geräts ist die Eingabe des Passworts erforderlich"</string>
diff --git a/packages/SystemUI/res-keyguard/values-el/strings.xml b/packages/SystemUI/res-keyguard/values-el/strings.xml
index 1764284..8e4578f 100644
--- a/packages/SystemUI/res-keyguard/values-el/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-el/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Καμία υπηρεσία."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Εναλλαγή μεθόδου εισαγωγής"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Λειτουργία πτήσης"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Απαιτείται PIN για την προετοιμασία για ενημέρωση"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Απαιτείται μοτίβο για την προετοιμασία για ενημέρωση"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Απαιτείται κωδικός πρόσβασης για την προετοιμασία για ενημέρωση"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Απαιτείται μοτίβο μετά από την επανεκκίνηση της συσκευής"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Απαιτείται PIN μετά από την επανεκκίνηση της συσκευής"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Απαιτείται κωδικός πρόσβασης μετά από την επανεκκίνηση της συσκευής"</string>
diff --git a/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml b/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
index 92a1594..21cfe48 100644
--- a/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"No service"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Switch input method"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Aeroplane mode"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"PIN required to prepare for update"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Pattern required to prepare for update"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Password required to prepare for update"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pattern required after device restarts"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN required after device restarts"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password required after device restarts"</string>
diff --git a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
index 719f1a1..921ba6b 100644
--- a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"No service"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Switch input method"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Airplane mode"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"PIN required to prepare for update"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Pattern required to prepare for update"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Password required to prepare for update"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pattern required after device restarts"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN required after device restarts"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password required after device restarts"</string>
diff --git a/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml b/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
index 92a1594..21cfe48 100644
--- a/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"No service"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Switch input method"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Aeroplane mode"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"PIN required to prepare for update"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Pattern required to prepare for update"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Password required to prepare for update"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pattern required after device restarts"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN required after device restarts"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password required after device restarts"</string>
diff --git a/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml b/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
index 92a1594..21cfe48 100644
--- a/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"No service"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Switch input method"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Aeroplane mode"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"PIN required to prepare for update"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Pattern required to prepare for update"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Password required to prepare for update"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pattern required after device restarts"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN required after device restarts"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password required after device restarts"</string>
diff --git a/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml b/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
index 975b1f6..fc59d0d 100644
--- a/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‎‎‎‏‎‏‎‎‏‎‏‎‎‎‎‎‏‏‎‏‏‏‎‎‎‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‎‎‏‎‎‏‎‏‎‏‎‎No service.‎‏‎‎‏‎"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‏‏‎‎‎‎‎‏‎‎‏‏‎‎‎‎‏‏‎‏‎‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‎‏‎‏‎‎‎‏‎‎‎‏‎Switch input method‎‏‎‎‏‎"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‏‎‎‎‏‎‏‎‏‎‏‎‎‎‏‏‏‎‏‏‏‎‏‏‏‎‏‏‎‎‎‏‏‏‎‏‏‏‏‏‎‎‏‎‎‏‏‎‏‎‏‎‎‏‎‏‎‎Airplane mode‎‏‎‎‏‎"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‎‏‏‏‏‎‎‏‎‎‎‎‏‎‎‏‎‎‎‏‏‎‎‏‎‏‏‎‎‎‎‏‏‏‎‎‎‎‎‎‎‎‎‏‎‎‏‎‎‏‏‏‎PIN required to prepare for update‎‏‎‎‏‎"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‏‎‎‎‎‏‏‏‎‎‎‏‏‎‎‏‎‏‎‏‏‎‎‎‎‏‏‎‎‏‎‎‏‎‏‎‏‎‎‎‏‏‎‏‏‏‎‏‎‏‏‏‏‏‎Pattern required to prepare for update‎‏‎‎‏‎"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‎‏‎‎‎‏‏‎‎‏‏‎‏‎‎‎‎‏‎‎‎‎‎‎‏‎‏‏‏‏‎‏‎‎‎‎‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‎‎‏‎Password required to prepare for update‎‏‎‎‏‎"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‎‎‎‎‎‏‎‏‏‎‎‎‏‏‏‎‎‏‎‏‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‎‏‎‎‏‏‎‎‏‎‏‎‎‎‏‎‎Pattern required after device restarts‎‏‎‎‏‎"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‎‎‎‎‏‎‎‎‏‎‎‎‏‎‏‎‏‏‏‎‎‎‎‎‏‎‏‏‏‏‎‏‎‎‎‏‏‎‏‎‏‏‎‎‏‏‎‏‏‎‏‏‏‎‎‎‎PIN required after device restarts‎‏‎‎‏‎"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‎‏‎‏‎‏‎‎‏‏‏‏‎‏‎‎‎‏‏‏‎‎‏‎‎‏‎‎‎‎‎‏‏‏‎‎‎‏‏‎‎‎‎‏‎‎Password required after device restarts‎‏‎‎‏‎"</string>
diff --git a/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml b/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
index 25ab615..ab0c8f3 100644
--- a/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Sin servicio"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Cambiar método de entrada"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Modo de avión"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Se requiere el PIN para actualizar el sistema"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Se requiere el patrón para actualizar el sistema"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Se requiere la contraseña para actualizar el sistema"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Se requiere el patrón después de reiniciar el dispositivo"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Se requiere el PIN después de reiniciar el dispositivo"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Se requiere la contraseña después de reiniciar el dispositivo"</string>
diff --git a/packages/SystemUI/res-keyguard/values-es/strings.xml b/packages/SystemUI/res-keyguard/values-es/strings.xml
index ce323c7..3813ddd 100644
--- a/packages/SystemUI/res-keyguard/values-es/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-es/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Sin servicio"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Cambiar método de introducción"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Modo avión"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Debes introducir el PIN para prepararte para la actualización"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Debes dibujar el patrón para prepararte para la actualización"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Debes introducir la contraseña para prepararte para la actualización"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Debes introducir el patrón después de reiniciar el dispositivo"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Debes introducir el PIN después de reiniciar el dispositivo"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Debes introducir la contraseña después de reiniciar el dispositivo"</string>
diff --git a/packages/SystemUI/res-keyguard/values-et/strings.xml b/packages/SystemUI/res-keyguard/values-et/strings.xml
index 331a95c..f8ad18b 100644
--- a/packages/SystemUI/res-keyguard/values-et/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-et/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Teenus puudub."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Vaheta sisestusmeetodit"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Lennukirežiim"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Värskendamiseks ettevalmistuste tegemiseks tuleb sisestada PIN-kood"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Värskendamiseks ettevalmistuste tegemiseks tuleb sisestada muster"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Värskendamiseks ettevalmistuste tegemiseks tuleb sisestada parool"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pärast seadme taaskäivitamist tuleb sisestada muster"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Pärast seadme taaskäivitamist tuleb sisestada PIN-kood"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Pärast seadme taaskäivitamist tuleb sisestada parool"</string>
diff --git a/packages/SystemUI/res-keyguard/values-eu/strings.xml b/packages/SystemUI/res-keyguard/values-eu/strings.xml
index 3ff224b..8510bee 100644
--- a/packages/SystemUI/res-keyguard/values-eu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-eu/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Ez dago konektatuta inongo saretara."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Aldatu idazketa-metodoa"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Hegaldi modua"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"PIN kodea behar da eguneratzea prestatzeko"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Eredua behar da eguneratzea prestatzeko"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Pasahitza behar da eguneratzea prestatzeko"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Eredua marraztu beharko duzu gailua berrabiarazten denean"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN kodea idatzi beharko duzu gailua berrabiarazten denean"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Pasahitza idatzi beharko duzu gailua berrabiarazten denean"</string>
diff --git a/packages/SystemUI/res-keyguard/values-fa/strings.xml b/packages/SystemUI/res-keyguard/values-fa/strings.xml
index 5e69636..43d3214 100644
--- a/packages/SystemUI/res-keyguard/values-fa/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fa/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"سرویسی وجود ندارد."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"تغییر روش ورودی"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"حالت هواپیما"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"آماده‌سازی برای به‌روزرسانی به پین نیاز دارد"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"آماده‌سازی برای به‌روزرسانی به الگو نیاز دارد"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"آماده‌سازی برای به‌روزرسانی به گذرواژه نیاز دارد"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"بعد از بازنشانی دستگاه باید الگو وارد شود"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"بعد از بازنشانی دستگاه باید پین وارد شود"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"بعد از بازنشانی دستگاه باید گذرواژه وارد شود"</string>
diff --git a/packages/SystemUI/res-keyguard/values-fi/strings.xml b/packages/SystemUI/res-keyguard/values-fi/strings.xml
index 54bc4d8..7dc12c9 100644
--- a/packages/SystemUI/res-keyguard/values-fi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fi/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Ei yhteyttä"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Vaihda syöttötapaa."</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Lentokonetila"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Päivitykseen valmistautuminen edellyttää PIN-koodia"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Päivitykseen valmistautuminen edellyttää kuviota"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Päivitykseen valmistautuminen edellyttää salasanaa"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Kuvio vaaditaan laitteen uudelleenkäynnistyksen jälkeen."</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN-koodi vaaditaan laitteen uudelleenkäynnistyksen jälkeen."</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Salasana vaaditaan laitteen uudelleenkäynnistyksen jälkeen."</string>
diff --git a/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml b/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml
index 3e858c2..f093c17 100644
--- a/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Aucun service"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Changer de méthode d\'entrée"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Mode Avion"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Le NIP est nécessaire pour préparer la mise à jour"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Le schéma est nécessaire pour préparer la mise à jour"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Le mot de passe est nécessaire pour préparer la mise à jour"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Le schéma est exigé après le redémarrage de l\'appareil"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Le NIP est exigé après le redémarrage de l\'appareil"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Le mot de passe est exigé après le redémarrage de l\'appareil"</string>
diff --git a/packages/SystemUI/res-keyguard/values-fr/strings.xml b/packages/SystemUI/res-keyguard/values-fr/strings.xml
index 8551fab..d6d5a32 100644
--- a/packages/SystemUI/res-keyguard/values-fr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fr/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Aucun service."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Changer le mode de saisie"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Mode Avion"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Veuillez saisir le code pour lancer la préparation de la mise à jour"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Veuillez saisir le schéma pour lancer la préparation de la mise à jour"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Veuillez saisir le mot de passe pour lancer la préparation de la mise à jour"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Veuillez dessiner le schéma après le redémarrage de l\'appareil"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Veuillez saisir le code après le redémarrage de l\'appareil"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Veuillez saisir le mot de passe après le redémarrage de l\'appareil"</string>
diff --git a/packages/SystemUI/res-keyguard/values-gl/strings.xml b/packages/SystemUI/res-keyguard/values-gl/strings.xml
index 420649e..5b8a255 100644
--- a/packages/SystemUI/res-keyguard/values-gl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-gl/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Non hai servizo."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Cambia o método de introdución"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Modo avión"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Necesítase o PIN para preparar o dispositivo co fin de actualizalo"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Necesítase o padrón para preparar o dispositivo co fin de actualizalo"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Necesítase o contrasinal para preparar o dispositivo co fin de actualizalo"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"É necesario o padrón despois do reinicio do dispositivo"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"É necesario o PIN despois do reinicio do dispositivo"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"É necesario o contrasinal despois do reinicio do dispositivo"</string>
diff --git a/packages/SystemUI/res-keyguard/values-gu/strings.xml b/packages/SystemUI/res-keyguard/values-gu/strings.xml
index b02d3d9..29e2fe0 100644
--- a/packages/SystemUI/res-keyguard/values-gu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-gu/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"કોઈ સેવા નથી."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"ઇનપુટ પદ્ધતિ સ્વિચ કરો"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"એરપ્લેન મોડ"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"અપડેટ માટે તૈયાર કરવા માટે પિન જરુરી છે"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"અપડેટ માટે તૈયાર કરવા માટે પૅટર્ન જરુરી છે"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"અપડેટ માટે તૈયાર કરવા માટે પાસવર્ડ જરુરી છે"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ઉપકરણનો પુનઃપ્રારંભ થાય તે પછી પૅટર્ન જરૂરી છે"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ઉપકરણનો પુનઃપ્રારંભ થાય તે પછી પિન જરૂરી છે"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ઉપકરણનો પુનઃપ્રારંભ થાય તે પછી પાસવર્ડ જરૂરી છે"</string>
diff --git a/packages/SystemUI/res-keyguard/values-hi/strings.xml b/packages/SystemUI/res-keyguard/values-hi/strings.xml
index f6b15de..d26c79f 100644
--- a/packages/SystemUI/res-keyguard/values-hi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hi/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"कोई सेवा नहीं."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"इनपुट का तरीका बदलें"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"हवाई जहाज़ मोड"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"अपडेट के लिए पिन डालना ज़रूरी है"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"अपडेट के लिए पैटर्न डालना ज़रूरी है"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"अपडेट के लिए पासवर्ड डालना ज़रूरी है"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"डिवाइस फिर से चालू होने के बाद पैटर्न ज़रूरी है"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"डिवाइस फिर से चालू होने के बाद पिन ज़रूरी है"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"डिवाइस फिर से चालू होने के बाद पासवर्ड ज़रूरी है"</string>
diff --git a/packages/SystemUI/res-keyguard/values-hr/strings.xml b/packages/SystemUI/res-keyguard/values-hr/strings.xml
index 49db3f88..c8dd9b0 100644
--- a/packages/SystemUI/res-keyguard/values-hr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hr/strings.xml
@@ -104,6 +104,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Nema usluge."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Promjena načina unosa"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Način rada u zrakoplovu"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Za pripremu ažuriranja potreban je PIN"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Za pripremu ažuriranja potreban je uzorak"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Za pripremu ažuriranja potrebna je zaporka"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Nakon ponovnog pokretanja uređaja morate unijeti uzorak"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Nakon ponovnog pokretanja uređaja morate unijeti PIN"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Nakon ponovnog pokretanja uređaja morate unijeti zaporku"</string>
diff --git a/packages/SystemUI/res-keyguard/values-hu/strings.xml b/packages/SystemUI/res-keyguard/values-hu/strings.xml
index c26998f..f0023d2 100644
--- a/packages/SystemUI/res-keyguard/values-hu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hu/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Nincs szolgáltatás."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Beviteli módszer váltása"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Repülős üzemmód"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"A frissítésre való felkészüléshez meg kell adni a PIN-kódot"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"A frissítésre való felkészüléshez meg kell adni a mintát"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"A frissítésre való felkészüléshez meg kell adni a jelszót"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Az eszköz újraindítását követően meg kell adni a mintát"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Az eszköz újraindítását követően meg kell adni a PIN-kódot"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Az eszköz újraindítását követően meg kell adni a jelszót"</string>
diff --git a/packages/SystemUI/res-keyguard/values-hy/strings.xml b/packages/SystemUI/res-keyguard/values-hy/strings.xml
index ad949d4..4224705 100644
--- a/packages/SystemUI/res-keyguard/values-hy/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hy/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Ծառայությունն անհասանելի է։"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Փոխել ներածման եղանակը"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Ավիառեժիմ"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Թարմացմանը պատրաստվելու համար անհրաժեշտ է մուտքագրել PIN-ը"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Թարմացմանը պատրաստվելու համար անհրաժեշտ է մուտքագրել նախշը"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Թարմացմանը պատրաստվելու համար անհրաժեշտ է մուտքագրել գաղտնաբառը"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Սարքը վերագործարկելուց հետո անհրաժեշտ է մուտքագրել նախշը"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Սարքը վերագործարկելուց հետո անհրաժեշտ է մուտքագրել PIN կոդը"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Սարքը վերագործարկելուց հետո անհրաժեշտ է մուտքագրել գաղտնաբառը"</string>
diff --git a/packages/SystemUI/res-keyguard/values-in/strings.xml b/packages/SystemUI/res-keyguard/values-in/strings.xml
index 875d8d5..9c00ff7 100644
--- a/packages/SystemUI/res-keyguard/values-in/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-in/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Tidak ada layanan."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Beralih metode masukan"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Mode pesawat"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"PIN diwajibkan untuk menyiapkan update"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Pola diwajibkan untuk menyiapkan update"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Sandi diwajibkan untuk menyiapkan update"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pola diperlukan setelah perangkat dimulai ulang"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN diperlukan setelah perangkat dimulai ulang"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Sandi diperlukan setelah perangkat dimulai ulang"</string>
diff --git a/packages/SystemUI/res-keyguard/values-is/strings.xml b/packages/SystemUI/res-keyguard/values-is/strings.xml
index e40cdca..5e37655 100644
--- a/packages/SystemUI/res-keyguard/values-is/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-is/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Ekkert símasamband."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Skipta um innsláttaraðferð"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Flugstilling"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Slá þarf inn PIN-númer til að undirbúa uppfærsluna"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Teikna þarf mynstur til að undirbúa uppfærsluna"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Gefa þarf upp aðgangsorð til að undirbúa uppfærsluna"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Mynsturs er krafist þegar tækið er endurræst"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN-númers er krafist þegar tækið er endurræst"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Aðgangsorðs er krafist þegar tækið er endurræst"</string>
diff --git a/packages/SystemUI/res-keyguard/values-it/strings.xml b/packages/SystemUI/res-keyguard/values-it/strings.xml
index 16767d1..80deb34 100644
--- a/packages/SystemUI/res-keyguard/values-it/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-it/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Nessun servizio."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Cambia metodo di immissione"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Modalità aereo"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"PIN obbligatorio per la preparazione all\'aggiornamento"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Sequenza obbligatoria per la preparazione all\'aggiornamento"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Password obbligatoria per la preparazione all\'aggiornamento"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Sequenza obbligatoria dopo il riavvio del dispositivo"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN obbligatorio dopo il riavvio del dispositivo"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password obbligatoria dopo il riavvio del dispositivo"</string>
diff --git a/packages/SystemUI/res-keyguard/values-iw/strings.xml b/packages/SystemUI/res-keyguard/values-iw/strings.xml
index e054f62..71f3048 100644
--- a/packages/SystemUI/res-keyguard/values-iw/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-iw/strings.xml
@@ -107,6 +107,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"אין שירות."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"החלפת שיטת קלט"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"מצב טיסה"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"נדרש קוד אימות להכנת העדכון"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"נדרש קו ביטול נעילה להכנת העדכון"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"נדרשת סיסמה להכנת העדכון"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"יש להזין את קו ביטול הנעילה לאחר הפעלה מחדש של המכשיר"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"יש להזין קוד גישה לאחר הפעלה מחדש של המכשיר"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"יש להזין סיסמה לאחר הפעלה מחדש של המכשיר"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ja/strings.xml b/packages/SystemUI/res-keyguard/values-ja/strings.xml
index 957d78a..23ff82c 100644
--- a/packages/SystemUI/res-keyguard/values-ja/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ja/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"通信サービスはありません。"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"入力方法の切り替え"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"機内モード"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"更新の準備には PIN の入力が必要です"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"更新の準備にはパターンの入力が必要です"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"更新の準備にはパスワードが必要です"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"デバイスの再起動後はパターンの入力が必要となります"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"デバイスの再起動後は PIN の入力が必要となります"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"デバイスの再起動後はパスワードの入力が必要となります"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ka/strings.xml b/packages/SystemUI/res-keyguard/values-ka/strings.xml
index d0d15fe..25b9b1b 100644
--- a/packages/SystemUI/res-keyguard/values-ka/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ka/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"სერვისი არ არის."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"შეყვანის მეთოდის გადართვა"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"თვითმფრინავის რეჟიმი"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"განახლების მოსამზადებლად საჭიროა PIN-კოდი"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"განახლების მოსამზადებლად საჭიროა ნიმუში"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"განახლების მოსამზადებლად საჭიროა პაროლი"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"მოწყობილობის გადატვირთვის შემდეგ საჭიროა ნიმუშის დახატვა"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"მოწყობილობის გადატვირთვის შემდეგ საჭიროა PIN-კოდის შეყვანა"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"მოწყობილობის გადატვირთვის შემდეგ საჭიროა პაროლის შეყვანა"</string>
diff --git a/packages/SystemUI/res-keyguard/values-kk/strings.xml b/packages/SystemUI/res-keyguard/values-kk/strings.xml
index 96972a7..4989e91 100644
--- a/packages/SystemUI/res-keyguard/values-kk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-kk/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Қызмет көрсетілмейді."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Енгізу әдісін ауыстыру"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Ұшақ режимі"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Жаңа нұсқа орнатуға дайындау үшін PIN кодын енгізу қажет."</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Жаңа нұсқа орнатуға дайындау үшін өрнек енгізу қажет."</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Жаңа нұсқа орнатуға дайындау үшін құпия сөз енгізу қажет."</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Құрылғы қайта іске қосылғаннан кейін, өрнекті енгізу қажет"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Құрылғы қайта іске қосылғаннан кейін, PIN кодын енгізу қажет"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Құрылғы қайта іске қосылғаннан кейін, құпия сөзді енгізу қажет"</string>
diff --git a/packages/SystemUI/res-keyguard/values-km/strings.xml b/packages/SystemUI/res-keyguard/values-km/strings.xml
index 24b5c23..e5ea9ea 100644
--- a/packages/SystemUI/res-keyguard/values-km/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-km/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"គ្មាន​សេវា​ទេ។"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"ប្ដូរ​វិធី​បញ្ចូល"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"មុខងារ​ពេល​ជិះ​យន្តហោះ"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"តម្រូវ​ឱ្យ​មាន​កូដ PIN ដើម្បី​រៀបចំ​ធ្វើបច្ចុប្បន្នភាព"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"តម្រូវ​ឱ្យ​មាន​លំនាំ ដើម្បី​រៀបចំ​ធ្វើបច្ចុប្បន្នភាព"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"តម្រូវ​ឱ្យ​មាន​ពាក្យ​សម្ងាត់ ដើម្បី​រៀបចំ​ធ្វើបច្ចុប្បន្នភាព"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"តម្រូវឲ្យប្រើលំនាំ បន្ទាប់ពីឧបករណ៍ចាប់ផ្តើមឡើងវិញ"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"តម្រូវឲ្យបញ្ចូលកូដ PIN បន្ទាប់ពីឧបករណ៍ចាប់ផ្តើមឡើងវិញ"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"តម្រូវឲ្យបញ្ចូលពាក្យសម្ងាត់ បន្ទាប់ពីឧបករណ៍ចាប់ផ្តើមឡើងវិញ"</string>
diff --git a/packages/SystemUI/res-keyguard/values-kn/strings.xml b/packages/SystemUI/res-keyguard/values-kn/strings.xml
index 785ca43..8173ca0 100644
--- a/packages/SystemUI/res-keyguard/values-kn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-kn/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"ಸೇವೆ ಇಲ್ಲ."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"ಇನ್‌ಪುಟ್‌‌ ವಿಧಾನ ಬದಲಿಸಿ"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"ಏರ್‌ಪ್ಲೇನ್ ಮೋಡ್"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"ಅಪ್‌ಡೇಟ್‌ಗಾಗಿ ಸಿದ್ಧಗೊಳಿಸಲು, ಪಿನ್‌‌ ಅಗತ್ಯವಿದೆ"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"ಅಪ್‌ಡೇಟ್‌ಗಾಗಿ ಸಿದ್ಧಗೊಳಿಸಲು, ಪ್ಯಾಟರ್ನ್ ಅಗತ್ಯವಿದೆ"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"ಅಪ್‌ಡೇಟ್‌ಗಾಗಿ ಸಿದ್ಧಗೊಳಿಸಲು, ಪಾಸ್‌ವರ್ಡ್ ಅಗತ್ಯವಿದೆ"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ಸಾಧನ ಮರುಪ್ರಾರಂಭಗೊಂಡ ನಂತರ ಪ್ಯಾಟರ್ನ್ ಅಗತ್ಯವಿರುತ್ತದೆ"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ಸಾಧನ ಮರುಪ್ರಾರಂಭಗೊಂಡ ನಂತರ ಪಿನ್ ಅಗತ್ಯವಿರುತ್ತದೆ"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ಸಾಧನ ಮರುಪ್ರಾರಂಭಗೊಂಡ ನಂತರ ಪಾಸ್‌ವರ್ಡ್ ಅಗತ್ಯವಿರುತ್ತದೆ"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ko/strings.xml b/packages/SystemUI/res-keyguard/values-ko/strings.xml
index 6ae5935..06074b2 100644
--- a/packages/SystemUI/res-keyguard/values-ko/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ko/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"서비스 불가"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"입력 방법 전환"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"비행기 모드"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"업데이트를 준비하려면 PIN이 필요합니다."</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"업데이트를 준비하려면 패턴이 필요합니다."</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"업데이트를 준비하려면 비밀번호가 필요합니다."</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"기기가 다시 시작되면 패턴이 필요합니다."</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"기기가 다시 시작되면 PIN이 필요합니다."</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"기기가 다시 시작되면 비밀번호가 필요합니다."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ky/strings.xml b/packages/SystemUI/res-keyguard/values-ky/strings.xml
index 9675cc9..022726e 100644
--- a/packages/SystemUI/res-keyguard/values-ky/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ky/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Байланыш жок."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Киргизүү ыкмасын өзгөртүү"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Учак режими"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Жаңыртууга даярдоо үчүн PIN код талап кылынат"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Жаңыртууга даярдоо үчүн графикалык ачкыч талап кылынат"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Жаңыртууга даярдоо үчүн сырсөз талап кылынат"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Түзмөк кайра күйгүзүлгөндөн кийин графикалык ачкычты тартуу талап кылынат"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Түзмөк кайра күйгүзүлгөндөн кийин PIN-кодду киргизүү талап кылынат"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Түзмөк кайра күйгүзүлгөндөн кийин сырсөздү киргизүү талап кылынат"</string>
diff --git a/packages/SystemUI/res-keyguard/values-lo/strings.xml b/packages/SystemUI/res-keyguard/values-lo/strings.xml
index ebaffb1..25e36c1 100644
--- a/packages/SystemUI/res-keyguard/values-lo/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lo/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"ບໍ່ມີບໍລິການ"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"ສະລັບຮູບແບບການປ້ອນຂໍ້ມູນ"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"ໂໝດໃນຍົນ"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"ຕ້ອງໃຊ້ PIN ເພື່ອກະກຽມອັບເດດ"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"ຕ້ອງໃຊ້ຮູບແບບເພື່ອກະກຽມອັບເດດ"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"ຕ້ອງໃຊ້ລະຫັດຜ່ານເພື່ອກະກຽມອັບເດດ"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ຈຳເປັນຕ້ອງມີແບບຮູບປົດລັອກຫຼັງຈາກອຸປະກອນເລີ່ມລະບົບໃໝ່"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ຈຳເປັນຕ້ອງມີ PIN ຫຼັງຈາກອຸປະກອນເລີ່ມລະບົບໃໝ່"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ຈຳເປັນຕ້ອງມີລະຫັດຜ່ານຫຼັງຈາກອຸປະກອນເລີ່ມລະບົບໃໝ່"</string>
diff --git a/packages/SystemUI/res-keyguard/values-lt/strings.xml b/packages/SystemUI/res-keyguard/values-lt/strings.xml
index 4d598f6..158efe2 100644
--- a/packages/SystemUI/res-keyguard/values-lt/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lt/strings.xml
@@ -107,6 +107,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Nėra paslaugos."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Perjungti įvesties metodą"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Lėktuvo režimas"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Kad būtų pasiruošta atnaujinti, reikia įvesti PIN kodą"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Kad būtų pasiruošta atnaujinti, reikia įvesti atrakinimo piešinį"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Kad būtų pasiruošta atnaujinti, reikia įvesti slaptažodį"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Iš naujo paleidus įrenginį būtinas atrakinimo piešinys"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Iš naujo paleidus įrenginį būtinas PIN kodas"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Iš naujo paleidus įrenginį būtinas slaptažodis"</string>
diff --git a/packages/SystemUI/res-keyguard/values-lv/strings.xml b/packages/SystemUI/res-keyguard/values-lv/strings.xml
index fad67d5..c4a4e7f 100644
--- a/packages/SystemUI/res-keyguard/values-lv/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lv/strings.xml
@@ -104,6 +104,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Nav pakalpojuma."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Pārslēgt ievades metodi"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Lidojuma režīms"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Lai sagatavotos atjauninājumam, nepieciešams PIN."</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Lai sagatavotos atjauninājumam, nepieciešama kombinācija."</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Lai sagatavotos atjauninājumam, nepieciešama parole."</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pēc ierīces restartēšanas ir jāievada atbloķēšanas kombinācija."</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Pēc ierīces restartēšanas ir jāievada PIN kods."</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Pēc ierīces restartēšanas ir jāievada parole."</string>
diff --git a/packages/SystemUI/res-keyguard/values-mk/strings.xml b/packages/SystemUI/res-keyguard/values-mk/strings.xml
index 1397f46..de4a83b 100644
--- a/packages/SystemUI/res-keyguard/values-mk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mk/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Нема услуга."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Префрли метод за внесување"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Авионски режим"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Потребен е PIN за да се подготви за ажурирање"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Потребна е шема за да се подготви за ажурирање"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Потребна е лозинка за да се подготви за ажурирање"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Потребна е шема по рестартирање на уредот"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Потребен е PIN-код по рестартирање на уредот"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Потребна е лозинка по рестартирање на уредот"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ml/strings.xml b/packages/SystemUI/res-keyguard/values-ml/strings.xml
index f82f822..da26ba7 100644
--- a/packages/SystemUI/res-keyguard/values-ml/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ml/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"സേവനമില്ല"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"ഇൻപുട്ട് രീതി മാറുക"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"ഫ്ലൈറ്റ് മോഡ്"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"അപ്‌ഡേറ്റിനായി തയ്യാറെടുക്കാൻ പിൻ ആവശ്യമാണ്"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"അപ്‌ഡേറ്റിനായി തയ്യാറെടുക്കാൻ പാറ്റേൺ ആവശ്യമാണ്"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"അപ്‌ഡേറ്റിനായി തയ്യാറെടുക്കാൻ പാസ്‌വേഡ് ആവശ്യമാണ്"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ഉപകരണം റീസ്റ്റാർട്ടായശേഷം ‌പാറ്റേൺ വരയ്‌ക്കേണ്ടതുണ്ട്"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ഉപകരണം റീസ്റ്റാർട്ടായശേഷം ‌പിൻ നൽകേണ്ടതുണ്ട്"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ഉപകരണം റീസ്റ്റാർട്ടായശേഷം ‌പാസ്‌വേഡ് നൽകേണ്ടതുണ്ട്"</string>
diff --git a/packages/SystemUI/res-keyguard/values-mn/strings.xml b/packages/SystemUI/res-keyguard/values-mn/strings.xml
index 462017a..fb032f1 100644
--- a/packages/SystemUI/res-keyguard/values-mn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mn/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Үйлчилгээ алга."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Оруулах аргыг сэлгэх"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Нислэгийн горим"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Шинэчлэхэд бэлтгэхийн тулд ПИН шаардлагатай"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Шинэчлэхэд бэлтгэхийн тулд хээ шаардлагатай"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Шинэчлэхэд бэлтгэхийн тулд нууц үг шаардлагатай"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Төхөөрөмжийг дахин эхлүүлсний дараа загвар оруулах шаардлагатай"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Төхөөрөмжийг дахин эхлүүлсний дараа ПИН оруулах шаардлагатай"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Төхөөрөмжийг дахин эхлүүлсний дараа нууц үг оруулах шаардлагатай"</string>
diff --git a/packages/SystemUI/res-keyguard/values-mr/strings.xml b/packages/SystemUI/res-keyguard/values-mr/strings.xml
index 0166791..e79e5c5 100644
--- a/packages/SystemUI/res-keyguard/values-mr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mr/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"सेवा नाही."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"इनपुट पद्धत स्विच करा"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"विमान मोड"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"अपडेटसाठी तयार करण्याकरिता पिन आवश्यक आहे"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"अपडेटसाठी तयार करण्याकरिता पॅटर्न आवश्यक आहे"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"अपडेटसाठी तयार करण्याकरिता पासवर्ड आवश्यक आहे"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"डिव्हाइस रीस्टार्ट झाल्यावर पॅटर्न आवश्यक आहे"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"डिव्हाइस रीस्टार्ट झाल्यावर पिन आवश्यक आहे"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"डिव्हाइस रीस्टार्ट झाल्यावर पासवर्ड आवश्यक आहे"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ms/strings.xml b/packages/SystemUI/res-keyguard/values-ms/strings.xml
index 6750086..5bc5df4 100644
--- a/packages/SystemUI/res-keyguard/values-ms/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ms/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Tiada perkhidmatan."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Tukar kaedah masukan"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Mod Pesawat"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"PIN diperlukan untuk menyediakan kemas kini"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Corak diperlukan untuk menyediakan kemas kini"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Kata laluan diperlukan untuk menyediakan kemas kini"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Corak diperlukan setelah peranti dimulakan semula"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN diperlukan setelah peranti dimulakan semula"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Kata laluan diperlukan setelah peranti dimulakan semula"</string>
diff --git a/packages/SystemUI/res-keyguard/values-my/strings.xml b/packages/SystemUI/res-keyguard/values-my/strings.xml
index 3b32f06..43732d8 100644
--- a/packages/SystemUI/res-keyguard/values-my/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-my/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"ဝန်ဆောင်မှု မရှိပါ။"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"စာရိုက်စနစ်ပြောင်းရန်"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"လေယာဉ်ပျံမုဒ်"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"အပ်ဒိတ်အတွက် ပြင်ဆင်ရန် ပင်နံပါတ် လိုပါသည်"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"အပ်ဒိတ်အတွက် ပြင်ဆင်ရန် ပုံစံလိုပါသည်"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"အပ်ဒိတ်အတွက် ပြင်ဆင်ရန် စကားဝှက် လိုပါသည်"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"စက်ပစ္စည်းကို ပိတ်ပြီးပြန်ဖွင့်လိုက်သည့်အခါတွင် ပုံစံ လိုအပ်ပါသည်"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"စက်ပစ္စည်းကို ပိတ်ပြီးပြန်ဖွင့်လိုက်သည့်အခါတွင် ပင်နံပါတ် လိုအပ်ပါသည်"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"စက်ပစ္စည်းကို ပိတ်ပြီးပြန်ဖွင့်လိုက်သည့်အခါတွင် စကားဝှက် လိုအပ်ပါသည်"</string>
diff --git a/packages/SystemUI/res-keyguard/values-nb/strings.xml b/packages/SystemUI/res-keyguard/values-nb/strings.xml
index ebd8f29..6dd3b2a 100644
--- a/packages/SystemUI/res-keyguard/values-nb/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-nb/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Ingen tilkobling."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Bytt inndatametode"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Flymodus"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"PIN-koden kreves for å klargjøre for oppdateringen"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Mønsteret kreves for å klargjøre for oppdateringen"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Passordet kreves for å klargjøre for oppdateringen"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Du må tegne mønsteret etter at enheten har startet på nytt"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Du må skrive inn PIN-koden etter at enheten har startet på nytt"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Du må skrive inn passordet etter at enheten har startet på nytt"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ne/strings.xml b/packages/SystemUI/res-keyguard/values-ne/strings.xml
index 0cec32e..37b2a60 100644
--- a/packages/SystemUI/res-keyguard/values-ne/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ne/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"सेवा उपलब्ध छैन।"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"इनपुट विधिलाई स्विच गर्नुहोस्"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"हवाइजहाज मोड"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"अद्यावधिक गर्ने कार्यका लागि तयार पार्न PIN चाहिन्छ"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"अद्यावधिक गर्ने कार्यका लागि तयार पार्न प्याटर्न चाहिन्छ"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"अद्यावधिक गर्ने कार्यका लागि तयार पार्न पासवर्ड चाहिन्छ"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"यन्त्र पुनः सुरु भएपछि ढाँचा आवश्यक पर्दछ"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"यन्त्र पुनः सुरु भएपछि PIN आवश्यक पर्दछ"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"यन्त्र पुनः सुरु भएपछि पासवर्ड आवश्यक पर्दछ"</string>
diff --git a/packages/SystemUI/res-keyguard/values-nl/strings.xml b/packages/SystemUI/res-keyguard/values-nl/strings.xml
index aa783e8..f3c35a4 100644
--- a/packages/SystemUI/res-keyguard/values-nl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-nl/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Geen service."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Invoermethode wijzigen"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Vliegtuigmodus"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Pincode vereist voor voorbereiding op update"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Patroon vereist voor voorbereiding op update"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Wachtwoord vereist voor voorbereiding op update"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Patroon vereist nadat het apparaat opnieuw is opgestart"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Pincode vereist nadat het apparaat opnieuw is opgestart"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Wachtwoord vereist nadat het apparaat opnieuw is opgestart"</string>
diff --git a/packages/SystemUI/res-keyguard/values-or/strings.xml b/packages/SystemUI/res-keyguard/values-or/strings.xml
index 8bbdcf1..e92dc42 100644
--- a/packages/SystemUI/res-keyguard/values-or/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-or/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"କୌଣସି ସେବା ନାହିଁ।"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"ଇନପୁଟ୍‌ ପଦ୍ଧତି ବଦଳାନ୍ତୁ"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"ଏରୋପ୍ଲେନ୍‍ ମୋଡ୍"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"ଅପଡେଟ୍ ପାଇଁ ପ୍ରସ୍ତୁତ ହେବାକୁ PIN ଆବଶ୍ୟକ"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"ଅପଡେଟ୍ ପାଇଁ ପ୍ରସ୍ତୁତ ହେବାକୁ ପାଟର୍ନ ଆବଶ୍ୟକ"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"ଅପଡେଟ୍ ପାଇଁ ପ୍ରସ୍ତୁତ ହେବାକୁ ପାସୱାର୍ଡ ଆବଶ୍ୟକ"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ଡିଭାଇସ୍‍ ରିଷ୍ଟାର୍ଟ ହେବା ପରେ ପାଟର୍ନ ଆବଶ୍ୟକ ଅଟେ"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ଡିଭାଇସ୍‍ ରିଷ୍ଟାର୍ଟ ହେବାପରେ ପାସ୍‌ୱର୍ଡ ଆବଶ୍ୟକ"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ଡିଭାଇସ୍‍ ରିଷ୍ଟାର୍ଟ ହେବା ପରେ ପାସୱର୍ଡ ଆବଶ୍ୟକ ଅଟେ"</string>
diff --git a/packages/SystemUI/res-keyguard/values-pa/strings.xml b/packages/SystemUI/res-keyguard/values-pa/strings.xml
index 78e0665..5c83ab8 100644
--- a/packages/SystemUI/res-keyguard/values-pa/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pa/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"ਕੋਈ ਸੇਵਾ ਨਹੀਂ।"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"ਇਨਪੁੱਟ ਵਿਧੀ ਸਵਿੱਚ ਕਰੋ"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"ਹਵਾਈ-ਜਹਾਜ਼ ਮੋਡ"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"ਅੱਪਡੇਟ ਨੂੰ ਤਿਆਰ ਕਰਨ ਲਈ ਪਿੰਨ ਲੋੜੀਂਦਾ ਹੈ"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"ਅੱਪਡੇਟ ਨੂੰ ਤਿਆਰ ਕਰਨ ਲਈ ਪੈਟਰਨ ਲੋੜੀਂਦਾ ਹੈ"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"ਅੱਪਡੇਟ ਨੂੰ ਤਿਆਰ ਕਰਨ ਲਈ ਪਾਸਵਰਡ ਲੋੜੀਂਦਾ ਹੈ"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ਡੀਵਾਈਸ ਦੇ ਮੁੜ-ਚਾਲੂ ਹੋਣ \'ਤੇ ਪੈਟਰਨ ਦੀ ਲੋੜ ਹੈ"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ਡੀਵਾਈਸ ਦੇ ਮੁੜ-ਚਾਲੂ ਹੋਣ \'ਤੇ ਪਿੰਨ ਦੀ ਲੋੜ ਹੈ"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ਡੀਵਾਈਸ ਦੇ ਮੁੜ-ਚਾਲੂ ਹੋਣ \'ਤੇ ਪਾਸਵਰਡ ਦੀ ਲੋੜ ਹੈ"</string>
diff --git a/packages/SystemUI/res-keyguard/values-pl/strings.xml b/packages/SystemUI/res-keyguard/values-pl/strings.xml
index 9b6f857..34633d3 100644
--- a/packages/SystemUI/res-keyguard/values-pl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pl/strings.xml
@@ -107,6 +107,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Brak usługi."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Przełączanie metody wprowadzania"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Tryb samolotowy"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Aby przygotować się do aktualizacji, wymagany jest kod PIN"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Aby przygotować się do aktualizacji, wymagany jest wzór"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Aby przygotować się do aktualizacji, wymagane jest hasło"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Po ponownym uruchomieniu urządzenia wymagany jest wzór"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Po ponownym uruchomieniu urządzenia wymagany jest kod PIN"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Po ponownym uruchomieniu urządzenia wymagane jest hasło"</string>
diff --git a/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml b/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml
index cc0c044..a770297 100644
--- a/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Sem serviço."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Alterar o método de entrada"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Modo avião"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Insira o PIN para se preparar para a atualização"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Insira o padrão para se preparar para a atualização"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Insira a senha para se preparar para a atualização"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"O padrão é exigido após a reinicialização do dispositivo"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"O PIN é exigido após a reinicialização do dispositivo"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"A senha é exigida após a reinicialização do dispositivo"</string>
diff --git a/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml b/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
index 5af8bc0..09cfcf1 100644
--- a/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Sem serviço."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Alternar o método de introdução"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Modo de avião"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"É necessário introduzir o PIN para a preparação para a atualização."</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"É necessário introduzir o padrão para a preparação para a atualização."</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"É necessário introduzir a palavra-passe para a preparação para a atualização."</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"É necessário um padrão após reiniciar o dispositivo"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"É necessário um PIN após reiniciar o dispositivo"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"É necessária uma palavra-passe após reiniciar o dispositivo"</string>
diff --git a/packages/SystemUI/res-keyguard/values-pt/strings.xml b/packages/SystemUI/res-keyguard/values-pt/strings.xml
index cc0c044..a770297 100644
--- a/packages/SystemUI/res-keyguard/values-pt/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Sem serviço."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Alterar o método de entrada"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Modo avião"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Insira o PIN para se preparar para a atualização"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Insira o padrão para se preparar para a atualização"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Insira a senha para se preparar para a atualização"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"O padrão é exigido após a reinicialização do dispositivo"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"O PIN é exigido após a reinicialização do dispositivo"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"A senha é exigida após a reinicialização do dispositivo"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ro/strings.xml b/packages/SystemUI/res-keyguard/values-ro/strings.xml
index 8122241..7df2db6 100644
--- a/packages/SystemUI/res-keyguard/values-ro/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ro/strings.xml
@@ -104,6 +104,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Fără serviciu."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Comutați metoda de introducere"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Mod Avion"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Pentru a vă pregăti pentru actualizare este necesar codul PIN"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Pentru a vă pregăti pentru actualizare este necesar modelul"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Pentru a vă pregăti pentru actualizare este necesară parola"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Modelul este necesar după repornirea dispozitivului"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Codul PIN este necesar după repornirea dispozitivului"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Parola este necesară după repornirea dispozitivului"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ru/strings.xml b/packages/SystemUI/res-keyguard/values-ru/strings.xml
index b80b479..ccd3c96 100644
--- a/packages/SystemUI/res-keyguard/values-ru/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ru/strings.xml
@@ -107,6 +107,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Нет сигнала."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Сменить способ ввода"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Режим полета"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Для подготовки к обновлению необходимо ввести PIN-код."</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Для подготовки к обновлению необходимо ввести графический ключ."</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Для подготовки к обновлению необходимо ввести пароль."</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"После перезагрузки устройства необходимо ввести графический ключ"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"После перезагрузки устройства необходимо ввести PIN-код"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"После перезагрузки устройства необходимо ввести пароль"</string>
diff --git a/packages/SystemUI/res-keyguard/values-si/strings.xml b/packages/SystemUI/res-keyguard/values-si/strings.xml
index 1cd876f..3dfc282 100644
--- a/packages/SystemUI/res-keyguard/values-si/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-si/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"සේවාව නැත."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"ආදාන ක්‍රමය මාරු කිරීම"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"ගුවන් යානා ප්‍රකාරය"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"යාවත්කාලීනය සඳහා සුදානම් කිරීමට PIN අවශ්‍යය"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"යාවත්කාලීනය සඳහා සුදානම් කිරීමට රටාව අවශ්‍යය"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"යාවත්කාලීනය සඳහා සුදානම් කිරීමට මුරපදය අවශ්‍යය"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"උපාංගය නැවත ආරම්භ වූ පසු රටාව අවශ්‍යයි"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"උපාංගය නැවත ආරම්භ වූ පසු PIN අංකය අවශ්‍යයි"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"උපාංගය නැවත ආරම්භ වූ පසු මුරපදය අවශ්‍යයි"</string>
diff --git a/packages/SystemUI/res-keyguard/values-sk/strings.xml b/packages/SystemUI/res-keyguard/values-sk/strings.xml
index 801a7db..8568e14 100644
--- a/packages/SystemUI/res-keyguard/values-sk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sk/strings.xml
@@ -107,6 +107,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Žiadny signál."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Prepnúť metódu vstupu"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Režim v lietadle"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Príprava na aktualizáciu vyžaduje zadanie kódu PIN"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Príprava na aktualizáciu vyžaduje zadanie vzoru"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Príprava na aktualizáciu vyžaduje zadanie hesla"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Po reštartovaní zariadenia musíte zadať bezpečnostný vzor"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Po reštartovaní zariadenia musíte zadať kód PIN"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Po reštartovaní zariadenia musíte zadať heslo"</string>
diff --git a/packages/SystemUI/res-keyguard/values-sl/strings.xml b/packages/SystemUI/res-keyguard/values-sl/strings.xml
index a141ed7..16dc4a8 100644
--- a/packages/SystemUI/res-keyguard/values-sl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sl/strings.xml
@@ -107,6 +107,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Ni storitve."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Preklop načina vnosa"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Način za letalo"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Za pripravo na posodobitev morate vnesti kodo PIN"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Za pripravo na posodobitev morate vnesti vzorec"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Za pripravo na posodobitev morate vnesti geslo"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Po vnovičnem zagonu naprave je treba vnesti vzorec"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Po vnovičnem zagonu naprave je treba vnesti kodo PIN"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Po vnovičnem zagonu naprave je treba vnesti geslo"</string>
diff --git a/packages/SystemUI/res-keyguard/values-sq/strings.xml b/packages/SystemUI/res-keyguard/values-sq/strings.xml
index 1d34e3f..ec44b61 100644
--- a/packages/SystemUI/res-keyguard/values-sq/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sq/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Nuk ka shërbim."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Ndërro metodën e hyrjes"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Modaliteti i aeroplanit"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Kërkohet kodi PIN për t\'u përgatitur për përditësimin"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Kërkohet motivi për t\'u përgatitur për përditësimin"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Kërkohet fjalëkalimi për t\'u përgatitur për përditësimin"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Kërkohet motivi pas rinisjes së pajisjes"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Kërkohet kodi PIN pas rinisjes së pajisjes"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Kërkohet fjalëkalimi pas rinisjes së pajisjes"</string>
diff --git a/packages/SystemUI/res-keyguard/values-sr/strings.xml b/packages/SystemUI/res-keyguard/values-sr/strings.xml
index f83df3f..24a1125 100644
--- a/packages/SystemUI/res-keyguard/values-sr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sr/strings.xml
@@ -104,6 +104,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Мрежа није доступна."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Промени метод уноса"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Режим рада у авиону"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"PIN је обавезан ради припреме за ажурирање"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Шаблон је обавезан ради припреме за ажурирање"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Лозинка је обавезна ради припреме за ажурирање"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Треба да унесете шаблон када се уређај поново покрене"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Треба да унесете PIN када се уређај поново покрене"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Треба да унесете лозинку када се уређај поново покрене"</string>
diff --git a/packages/SystemUI/res-keyguard/values-sv/strings.xml b/packages/SystemUI/res-keyguard/values-sv/strings.xml
index a037bff..a37c480 100644
--- a/packages/SystemUI/res-keyguard/values-sv/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sv/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Ingen tjänst."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Byt inmatningsmetod"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Flygplansläge"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Pinkod krävs för att förbereda för uppdatering"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Grafiskt lösenord krävs för att förbereda för uppdatering"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Lösenord krävs för att förbereda för uppdatering"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Du måste ange grafiskt lösenord när du har startat om enheten"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Du måste ange pinkod när du har startat om enheten"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Du måste ange lösenord när du har startat om enheten"</string>
diff --git a/packages/SystemUI/res-keyguard/values-sw/strings.xml b/packages/SystemUI/res-keyguard/values-sw/strings.xml
index efa5ecf..c4a9a14 100644
--- a/packages/SystemUI/res-keyguard/values-sw/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sw/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Hakuna mtandao."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Kubadili mbinu ya kuingiza data"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Hali ya ndegeni"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Inahitaji PIN ili kujiandaa kwa ajili ya sasisho"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Inahitaji mchoro ili kujiandaa kwa ajili ya sasisho"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Inahitaji nenosiri ili kujiandaa kwa ajili ya sasisho"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Unafaa kuchora mchoro baada ya kuwasha kifaa upya"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Unafaa kuweka PIN baada ya kuwasha kifaa upya"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Unafaa kuweka nenosiri baada ya kuwasha kifaa upya"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ta/strings.xml b/packages/SystemUI/res-keyguard/values-ta/strings.xml
index 96dbbb0..a4dc0be 100644
--- a/packages/SystemUI/res-keyguard/values-ta/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ta/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"சேவை இல்லை."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"உள்ளீட்டு முறையை மாற்றும்"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"விமானப் பயன்முறை"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"புதுப்பிப்பிற்குத் தயார்செய்ய பின் தேவை"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"புதுப்பிப்பிற்குத் தயார்செய்ய பேட்டர்ன் தேவை"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"புதுப்பிப்பிற்குத் தயார்செய்ய கடவுச்சொல் தேவை"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"சாதனத்தை மீண்டும் தொடங்கியதும், பேட்டர்னை வரைய வேண்டும்"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"சாதனத்தை மீண்டும் தொடங்கியதும், பின்னை உள்ளிட வேண்டும்"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"சாதனத்தை மீண்டும் தொடங்கியதும், கடவுச்சொல்லை உள்ளிட வேண்டும்"</string>
diff --git a/packages/SystemUI/res-keyguard/values-te/strings.xml b/packages/SystemUI/res-keyguard/values-te/strings.xml
index 74386bc..3e27ce8 100644
--- a/packages/SystemUI/res-keyguard/values-te/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-te/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"సేవ లేదు."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"ఇన్‌పుట్ పద్ధతిని మార్చు"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"విమానం మోడ్"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"అప్‌డేట్‌కు సిద్ధం చేయడానికి పిన్ అవసరం"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"అప్‌డేట్‌కు సిద్ధం చేయడానికి ఆకృతి అవసరం"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"అప్‌డేట్‌కు సిద్ధం చేయడానికి పాస్‌వర్డ్ అవసరం"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"పరికరాన్ని పునఃప్రారంభించిన తర్వాత నమూనాను గీయాలి"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"డివైజ్‌ను పునఃప్రారంభించిన తర్వాత పిన్ నమోదు చేయాలి"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"పరికరాన్ని పునఃప్రారంభించిన తర్వాత పాస్‌వర్డ్‌ను నమోదు చేయాలి"</string>
diff --git a/packages/SystemUI/res-keyguard/values-th/strings.xml b/packages/SystemUI/res-keyguard/values-th/strings.xml
index aa9e693..8f94c636 100644
--- a/packages/SystemUI/res-keyguard/values-th/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-th/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"ไม่มีบริการ"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"สลับวิธีการป้อนข้อมูล"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"โหมดบนเครื่องบิน"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"ต้องใช้ PIN เพื่อเตรียมรับการอัปเดต"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"ต้องใช้รูปแบบเพื่อเตรียมรับการอัปเดต"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"ต้องใช้รหัสผ่านเพื่อเตรียมรับการอัปเดต"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ต้องวาดรูปแบบหลังจากอุปกรณ์รีสตาร์ท"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ต้องระบุ PIN หลังจากอุปกรณ์รีสตาร์ท"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ต้องป้อนรหัสผ่านหลังจากอุปกรณ์รีสตาร์ท"</string>
diff --git a/packages/SystemUI/res-keyguard/values-tl/strings.xml b/packages/SystemUI/res-keyguard/values-tl/strings.xml
index 7b7e17d..bd87b20 100644
--- a/packages/SystemUI/res-keyguard/values-tl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-tl/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Walang serbisyo."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Magpalit ng pamamaraan ng pag-input"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Airplane mode"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Kinakailangan ang PIN para makapaghanda sa pag-update"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Kinakailangan ang pattern para makapaghanda sa pag-update"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Kinakailangan ang password para makapaghanda sa pag-update"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Kailangan ng pattern pagkatapos mag-restart ng device"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Kailangan ng PIN pagkatapos mag-restart ng device"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Kailangan ng password pagkatapos mag-restart ng device"</string>
diff --git a/packages/SystemUI/res-keyguard/values-tr/strings.xml b/packages/SystemUI/res-keyguard/values-tr/strings.xml
index 8c0caea..cf37451 100644
--- a/packages/SystemUI/res-keyguard/values-tr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-tr/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Hizmet yok."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Giriş yöntemini değiştir"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Uçak modu"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Güncellemenin hazırlanması için PIN gerekli"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Güncellemenin hazırlanması için desen gerekli"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Güncellemenin hazırlanması için şifre gerekli"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Cihaz yeniden başladıktan sonra desen gerekir"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Cihaz yeniden başladıktan sonra PIN gerekir"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Cihaz yeniden başladıktan sonra şifre gerekir"</string>
diff --git a/packages/SystemUI/res-keyguard/values-uk/strings.xml b/packages/SystemUI/res-keyguard/values-uk/strings.xml
index 6e5ce0f..0ccd012 100644
--- a/packages/SystemUI/res-keyguard/values-uk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-uk/strings.xml
@@ -107,6 +107,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Зв’язку немає."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Змінити метод введення"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Режим польоту"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Щоб підготуватися до оновлення, введіть PIN-код"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Щоб підготуватися до оновлення, введіть ключ"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Щоб підготуватися до оновлення, введіть пароль"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Після перезавантаження пристрою потрібно ввести ключ"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Після перезавантаження пристрою потрібно ввести PIN-код"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Після перезавантаження пристрою потрібно ввести пароль"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ur/strings.xml b/packages/SystemUI/res-keyguard/values-ur/strings.xml
index 7b946aa..981bc033 100644
--- a/packages/SystemUI/res-keyguard/values-ur/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ur/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"کوئی سروس نہیں ہے۔"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"اندراج کا طریقہ سوئچ کریں"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"ہوائی جہاز وضع"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"‏اپ ڈیٹ تیار کرنے کے لیے PIN درکار ہے"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"اپ ڈیٹ تیار کرنے کے لیے پیٹرن درکار ہے"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"اپ ڈیٹ تیار کرنے کے لیے پاس ورڈ درکار ہے"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"آلہ دوبارہ چالو ہونے کے بعد پیٹرن درکار ہوتا ہے"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"‏آلہ دوبارہ چالو ہونے کے بعد PIN درکار ہوتا ہے"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"آلہ دوبارہ چالو ہونے کے بعد پاسورڈ درکار ہوتا ہے"</string>
diff --git a/packages/SystemUI/res-keyguard/values-uz/strings.xml b/packages/SystemUI/res-keyguard/values-uz/strings.xml
index a6c2aa0..1905e64e 100644
--- a/packages/SystemUI/res-keyguard/values-uz/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-uz/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Aloqa yo‘q."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Matn kiritish usulini almashtirish"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Parvoz rejimi"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Yangilashga tayyorlash uchun PIN kod talab etiladi"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Yangilashga tayyorlash uchun grafik kalit talab etiladi"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Yangilashga tayyorlash uchun parol talab etiladi"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Qurilma o‘chirib yoqilgandan keyin grafik kalit talab qilinadi"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Qurilma o‘chirib yoqilgandan keyin PIN kod talab qilinadi"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Qurilma o‘chirib yoqilgandan keyin parol talab qilinadi"</string>
diff --git a/packages/SystemUI/res-keyguard/values-vi/strings.xml b/packages/SystemUI/res-keyguard/values-vi/strings.xml
index 31737fc..cfb1564 100644
--- a/packages/SystemUI/res-keyguard/values-vi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-vi/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Không có dịch vụ."</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Chuyển phương thức nhập"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Chế độ trên máy bay"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Cần nhập mã PIN để chuẩn bị cập nhật"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Cần nhập hình mở khóa để chuẩn bị cập nhật"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Cần nhập mật khẩu để chuẩn bị cập nhật"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Yêu cầu hình mở khóa sau khi thiết bị khởi động lại"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Yêu cầu mã PIN sau khi thiết bị khởi động lại"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Yêu cầu mật khẩu sau khi thiết bị khởi động lại"</string>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
index b4bff5f..be162b0 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"无服务。"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"切换输入法"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"飞行模式"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"需要输入 PIN 码才能让设备做好更新准备"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"需要绘制解锁图案才能让设备做好更新准备"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"需要输入密码才能让设备做好更新准备"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"重启设备后需要绘制解锁图案"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"重启设备后需要输入 PIN 码"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"重启设备后需要输入密码"</string>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
index b3d3877..33e5b44 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"沒有服務。"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"轉換輸入方法"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"飛行模式"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"必須提供 PIN 碼,才能準備進行更新"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"必須畫出上鎖圖案,才能準備進行更新"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"必須輸入密碼,才能準備進行更新"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"裝置重新啟動後,必須畫出上鎖圖案才能使用"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"裝置重新啟動後,必須輸入 PIN 碼才能使用"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"裝置重新啟動後,必須輸入密碼才能使用"</string>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
index 03dec48..763233c 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"沒有服務。"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"切換輸入法"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"飛航模式"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"請輸入 PIN 碼,以便為更新作業進行準備"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"請畫出解鎖圖案,以便為更新作業進行準備"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"請輸入密碼,以便為更新作業進行準備"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"裝置重新啟動後需要畫出解鎖圖案"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"裝置重新啟動後需要輸入 PIN 碼"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"裝置重新啟動後需要輸入密碼"</string>
diff --git a/packages/SystemUI/res-keyguard/values-zu/strings.xml b/packages/SystemUI/res-keyguard/values-zu/strings.xml
index 5ab567f..397c868 100644
--- a/packages/SystemUI/res-keyguard/values-zu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zu/strings.xml
@@ -101,6 +101,9 @@
     <string name="keyguard_carrier_default" msgid="6359808469637388586">"Ayikho isevisi"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Shintsha indlela yokufaka"</string>
     <string name="airplane_mode" msgid="2528005343938497866">"Imodi yendiza"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pin" msgid="8878724145347297575">"Iphinikhodi iyadingeka ukuze kulungiselelwe isibuyekezo"</string>
+    <string name="kg_prompt_reason_prepare_for_update_pattern" msgid="4873394344883271519">"Iphethini iyadingeka ukuze kulungiselelwe isibuyekezo"</string>
+    <string name="kg_prompt_reason_prepare_for_update_password" msgid="5625446803865598337">"Iphasiwedi iyadingeka ukuze kulungiselelwe isibuyekezo"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Iphethini iyadingeka ngemuva kokuqala kabusha kwedivayisi"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Iphinikhodi iyadingeka ngemuva kokuqala kabusha kwedivayisi"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Iphasiwedi iyadingeka ngemuva kokuqala kabusha kwedivayisi"</string>
diff --git a/packages/SystemUI/res/drawable/action_chip_background.xml b/packages/SystemUI/res/drawable/action_chip_background.xml
index fc3dfeb..ac227a6 100644
--- a/packages/SystemUI/res/drawable/action_chip_background.xml
+++ b/packages/SystemUI/res/drawable/action_chip_background.xml
@@ -19,7 +19,7 @@
     android:color="@color/global_screenshot_button_ripple">
     <item android:id="@android:id/background">
         <shape android:shape="rectangle">
-            <stroke android:width="1dp" android:color="@color/global_screenshot_button_text"/>
+            <stroke android:width="1dp" android:color="@color/global_screenshot_button_border"/>
             <solid android:color="@color/global_screenshot_button_background"/>
             <corners android:radius="@dimen/screenshot_button_corner_radius"/>
         </shape>
diff --git a/packages/SystemUI/res/drawable/ic_add_to_home.xml b/packages/SystemUI/res/drawable/ic_add_to_home.xml
new file mode 100644
index 0000000..2b40c62
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_add_to_home.xml
@@ -0,0 +1,26 @@
+<!--
+  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
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M18,1.01L8,1c-1.1,0 -2,0.9 -2,2v3h2V5h10v14H8v-1H6v3c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-1.99 -2,-1.99zM10,15h2V8H5v2h3.59L3,15.59 4.41,17 10,11.41V15z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_arrow_downward.xml b/packages/SystemUI/res/drawable/ic_arrow_downward.xml
new file mode 100644
index 0000000..ddd075d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_arrow_downward.xml
@@ -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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:tint="?attr/colorControlNormal">
+    <path
+        android:pathData="M20,12l-1.41,-1.41L13,16.17V4h-2v12.17l-5.58,-5.59L4,12l8,8 8,-8z"
+        android:fillColor="@android:color/white"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_demote_conversation.xml b/packages/SystemUI/res/drawable/ic_demote_conversation.xml
new file mode 100644
index 0000000..5a88160
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_demote_conversation.xml
@@ -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
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M20,2L4.83,2l2,2L20,4v12h-1.17l1.87,1.87c0.75,-0.29 1.3,-1.02 1.3,-1.87L22,4c0,-1.1 -0.9,-2 -2,-2zM6,12h2v2L6,14zM18,11L18,9h-6.17l2,2zM18,6h-8v1.17l0.83,0.83L18,8zM0.69,3.51l1.32,1.32L2,22l4,-4h9.17l5.31,5.31 1.41,-1.41L2.1,2.1 0.69,3.51zM6,16h-0.83l-0.59,0.59 -0.58,0.58L4,6.83l2,2L6,11h2.17l5,5L6,16z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_qs_screenrecord.xml b/packages/SystemUI/res/drawable/ic_qs_screenrecord.xml
new file mode 100644
index 0000000..687c9c4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_screenrecord.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M18,10.48L18,6c0,-1.1 -0.9,-2 -2,-2L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-4.48l4,3.98v-11l-4,3.98zM16,9.69L16,18L4,18L4,6h12v3.69z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_screenrecord.xml b/packages/SystemUI/res/drawable/ic_screenrecord.xml
new file mode 100644
index 0000000..6d8bd0d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_screenrecord.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0"
+    android:tint="?android:attr/colorControlNormal">
+    <path
+        android:pathData="M12,12m-8,0a8,8 0,1 1,16 0a8,8 0,1 1,-16 0"
+        android:fillColor="#FFFFFFFF"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_snooze.xml b/packages/SystemUI/res/drawable/ic_snooze.xml
index b0b03a9..f4c074d 100644
--- a/packages/SystemUI/res/drawable/ic_snooze.xml
+++ b/packages/SystemUI/res/drawable/ic_snooze.xml
@@ -1,12 +1,24 @@
+<!--
+  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
+  -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
         android:width="24dp"
         android:height="24dp"
         android:viewportWidth="24.0"
         android:viewportHeight="24.0">
     <path
-        android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"
-        android:fillColor="#757575"/>
-    <path
-        android:pathData="M12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"
-        android:fillColor="#757575"/>
+        android:fillColor="@android:color/white"
+        android:pathData="M9,11h3.63L9,15.2L9,17h6v-2h-3.63L15,10.8L15,9L9,9v2zM16.056,3.346l1.282,-1.535 4.607,3.85 -1.28,1.54zM3.336,7.19l-1.28,-1.536L6.662,1.81l1.28,1.536zM12,6c3.86,0 7,3.14 7,7s-3.14,7 -7,7 -7,-3.14 -7,-7 3.14,-7 7,-7m0,-2c-4.97,0 -9,4.03 -9,9s4.03,9 9,9 9,-4.03 9,-9 -4.03,-9 -9,-9z"/>
 </vector>
diff --git a/packages/SystemUI/res/drawable/ic_star.xml b/packages/SystemUI/res/drawable/ic_star.xml
new file mode 100644
index 0000000..4a731b3
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_star.xml
@@ -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
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_star_border.xml b/packages/SystemUI/res/drawable/ic_star_border.xml
new file mode 100644
index 0000000..9ede40b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_star_border.xml
@@ -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
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml b/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
new file mode 100644
index 0000000..163015b7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/screenshot_actions_background_protection.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.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+    <gradient
+        android:angle="90"
+        android:startColor="#1f000000"
+        android:endColor="#00000000"/>
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/screenshot_rounded_corners.xml b/packages/SystemUI/res/drawable/screenshot_rounded_corners.xml
new file mode 100644
index 0000000..fb8e9b7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/screenshot_rounded_corners.xml
@@ -0,0 +1,20 @@
+<?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.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <corners android:radius="@dimen/screenshot_button_corner_radius"/>
+</shape>
diff --git a/packages/SystemUI/res/layout-land/global_actions_grid_item.xml b/packages/SystemUI/res/layout-land/global_actions_grid_item.xml
index bc12338..0f9deaa 100644
--- a/packages/SystemUI/res/layout-land/global_actions_grid_item.xml
+++ b/packages/SystemUI/res/layout-land/global_actions_grid_item.xml
@@ -57,15 +57,5 @@
             android:textColor="@color/global_actions_text"
             android:textAppearance="?android:attr/textAppearanceSmall"
         />
-
-        <TextView
-            android:visibility="gone"
-            android:id="@*android:id/status"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:gravity="center"
-            android:textColor="@color/global_actions_text"
-            android:textAppearance="?android:attr/textAppearanceSmall"
-        />
     </LinearLayout>
 </LinearLayout>
diff --git a/packages/SystemUI/res/layout/app_item.xml b/packages/SystemUI/res/layout/app_item.xml
new file mode 100644
index 0000000..83e7887
--- /dev/null
+++ b/packages/SystemUI/res/layout/app_item.xml
@@ -0,0 +1,70 @@
+<?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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?android:attr/selectableItemBackground"
+    android:gravity="center_vertical"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+    <LinearLayout
+        android:id="@+id/icon_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="start|center_vertical"
+        android:minWidth="56dp"
+        android:orientation="horizontal"
+        android:paddingEnd="8dp"
+        android:paddingTop="4dp"
+        android:paddingBottom="4dp">
+        <ImageView
+            android:id="@android:id/icon"
+            android:layout_width="@dimen/app_icon_size"
+            android:layout_height="@dimen/app_icon_size"/>
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:orientation="vertical"
+        android:paddingTop="16dp"
+        android:paddingBottom="16dp">
+
+        <TextView
+            android:id="@android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal"
+            android:singleLine="true"
+            android:textAppearance="?android:attr/textAppearanceListItem"/>
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@android:id/widget_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:gravity="center_vertical|end"
+        android:minWidth="64dp"
+        android:orientation="vertical"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bubble_flyout.xml b/packages/SystemUI/res/layout/bubble_flyout.xml
index 5f773f4..7ab0a0c 100644
--- a/packages/SystemUI/res/layout/bubble_flyout.xml
+++ b/packages/SystemUI/res/layout/bubble_flyout.xml
@@ -15,25 +15,50 @@
   -->
 <merge xmlns:android="http://schemas.android.com/apk/res/android">
 
-    <FrameLayout
+    <LinearLayout
         android:id="@+id/bubble_flyout_text_container"
         android:layout_height="wrap_content"
         android:layout_width="wrap_content"
+        android:orientation="horizontal"
         android:clipToPadding="false"
-        android:paddingLeft="@dimen/bubble_flyout_padding_x"
-        android:paddingRight="@dimen/bubble_flyout_padding_x"
+        android:clipChildren="false"
+        android:paddingStart="@dimen/bubble_flyout_padding_x"
+        android:paddingEnd="@dimen/bubble_flyout_padding_x"
         android:paddingTop="@dimen/bubble_flyout_padding_y"
         android:paddingBottom="@dimen/bubble_flyout_padding_y"
         android:translationZ="@dimen/bubble_flyout_elevation">
 
-        <TextView
-            android:id="@+id/bubble_flyout_text"
+        <ImageView
+            android:id="@+id/bubble_flyout_avatar"
+            android:layout_width="30dp"
+            android:layout_height="30dp"
+            android:layout_marginEnd="@dimen/bubble_flyout_avatar_message_space"
+            android:scaleType="centerInside"
+            android:src="@drawable/ic_create_bubble"/>
+
+        <LinearLayout
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:fontFamily="@*android:string/config_bodyFontFamily"
-            android:maxLines="2"
-            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"/>
+            android:orientation="vertical">
 
-    </FrameLayout>
+            <TextView
+                android:id="@+id/bubble_flyout_name"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
+                android:maxLines="1"
+                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"/>
+
+            <TextView
+                android:id="@+id/bubble_flyout_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:fontFamily="@*android:string/config_bodyFontFamily"
+                android:maxLines="2"
+                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"/>
+
+        </LinearLayout>
+
+    </LinearLayout>
 
 </merge>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/control_item.xml b/packages/SystemUI/res/layout/control_item.xml
new file mode 100644
index 0000000..85701aa
--- /dev/null
+++ b/packages/SystemUI/res/layout/control_item.xml
@@ -0,0 +1,72 @@
+<?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.
+-->
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="100dp"
+    android:padding="15dp"
+    android:clickable="true"
+    android:focusable="true">
+
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/status"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="12sp"
+        android:textColor="?android:attr/textColorPrimary"
+        android:fontFamily="@*android:string/config_bodyFontFamily"
+        android:paddingLeft="3dp"
+        app:layout_constraintBottom_toBottomOf="@+id/icon"
+        app:layout_constraintStart_toEndOf="@+id/icon" />
+
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="18sp"
+        android:textColor="?android:attr/textColorPrimary"
+        android:fontFamily="@*android:string/config_headlineFontFamily"
+        app:layout_constraintBottom_toTopOf="@+id/subtitle"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/icon" />
+
+    <TextView
+        android:id="@+id/subtitle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="16sp"
+        android:textColor="?android:attr/textColorSecondary"
+        android:fontFamily="@*android:string/config_headlineFontFamily"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent" />
+
+    <CheckBox
+        android:id="@+id/favorite"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"/>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/global_actions_grid_item.xml b/packages/SystemUI/res/layout/global_actions_grid_item.xml
index 4404c874..31c7cbf 100644
--- a/packages/SystemUI/res/layout/global_actions_grid_item.xml
+++ b/packages/SystemUI/res/layout/global_actions_grid_item.xml
@@ -56,15 +56,5 @@
             android:textColor="@color/global_actions_text"
             android:textAppearance="?android:attr/textAppearanceSmall"
         />
-
-        <TextView
-            android:visibility="gone"
-            android:id="@*android:id/status"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:gravity="center"
-            android:textColor="@color/global_actions_text"
-            android:textAppearance="?android:attr/textAppearanceSmall"
-        />
     </LinearLayout>
 </LinearLayout>
diff --git a/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml
new file mode 100644
index 0000000..50aa212
--- /dev/null
+++ b/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<!-- RelativeLayouts have an issue enforcing minimum heights, so just
+     work around this for now with LinearLayouts. -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:gravity="center"
+    android:paddingTop="@dimen/global_actions_grid_item_vertical_margin"
+    android:paddingBottom="@dimen/global_actions_grid_item_vertical_margin"
+    android:paddingLeft="@dimen/global_actions_grid_item_side_margin"
+    android:paddingRight="@dimen/global_actions_grid_item_side_margin"
+    android:layout_marginRight="3dp"
+    android:layout_marginLeft="3dp"
+    android:background="@drawable/rounded_bg_full">
+    <LinearLayout
+        android:layout_width="@dimen/global_actions_grid_item_width"
+        android:layout_height="@dimen/global_actions_grid_item_height"
+        android:gravity="top|center_horizontal"
+        android:orientation="vertical">
+        <ImageView
+            android:id="@*android:id/icon"
+            android:layout_width="@dimen/global_actions_grid_item_icon_width"
+            android:layout_height="@dimen/global_actions_grid_item_icon_height"
+            android:layout_marginTop="@dimen/global_actions_grid_item_icon_top_margin"
+            android:layout_marginBottom="@dimen/global_actions_grid_item_icon_bottom_margin"
+            android:layout_marginLeft="@dimen/global_actions_grid_item_icon_side_margin"
+            android:layout_marginRight="@dimen/global_actions_grid_item_icon_side_margin"
+            android:scaleType="centerInside"
+            android:tint="@color/global_actions_text" />
+
+        <TextView
+            android:id="@*android:id/message"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:ellipsize="marquee"
+            android:marqueeRepeatLimit="marquee_forever"
+            android:singleLine="true"
+            android:gravity="center"
+            android:textSize="12dp"
+            android:textColor="@color/global_actions_text"
+            android:textAppearance="?android:attr/textAppearanceSmall" />
+
+        <TextView
+            android:visibility="gone"
+            android:id="@*android:id/status"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:textColor="@color/global_actions_text"
+            android:textAppearance="?android:attr/textAppearanceSmall" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/global_actions_grid_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_v2.xml
new file mode 100644
index 0000000..4cfb47e
--- /dev/null
+++ b/packages/SystemUI/res/layout/global_actions_grid_v2.xml
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+  <androidx.constraintlayout.widget.ConstraintLayout
+      android:id="@+id/global_actions_grid_root"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:clipChildren="false"
+      android:clipToPadding="false"
+      android:layout_marginBottom="@dimen/global_actions_grid_container_negative_shadow_offset">
+
+    <com.android.systemui.globalactions.GlobalActionsFlatLayout
+        android:id="@id/global_actions_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:theme="@style/qs_theme"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        android:gravity="top | center_horizontal"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:paddingBottom="@dimen/global_actions_grid_container_shadow_offset"
+        android:layout_marginTop="@dimen/global_actions_top_margin"
+        android:layout_marginBottom="@dimen/global_actions_grid_container_negative_shadow_offset">
+      <LinearLayout
+          android:layout_height="wrap_content"
+          android:layout_width="wrap_content"
+          android:layoutDirection="ltr"
+          android:clipChildren="false"
+          android:clipToPadding="false"
+          android:layout_marginBottom="@dimen/global_actions_grid_container_bottom_margin">
+        <!-- For separated items-->
+        <LinearLayout
+            android:id="@+id/separated_button"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_marginLeft="@dimen/global_actions_grid_side_margin"
+            android:layout_marginRight="@dimen/global_actions_grid_side_margin"
+            android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
+            android:paddingRight="@dimen/global_actions_grid_horizontal_padding"
+            android:paddingTop="@dimen/global_actions_grid_vertical_padding"
+            android:paddingBottom="@dimen/global_actions_grid_vertical_padding"
+            android:orientation="vertical"
+            android:gravity="center"
+            android:translationZ="@dimen/global_actions_translate"
+            />
+        <!-- Grid of action items -->
+        <com.android.systemui.globalactions.ListGridLayout
+            android:id="@android:id/list"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            android:gravity="right"
+            android:layout_marginRight="@dimen/global_actions_grid_side_margin"
+            android:translationZ="@dimen/global_actions_translate"
+            android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
+            android:paddingRight="@dimen/global_actions_grid_horizontal_padding"
+            android:paddingTop="@dimen/global_actions_grid_vertical_padding"
+            android:paddingBottom="@dimen/global_actions_grid_vertical_padding"
+            >
+          <LinearLayout
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:visibility="gone"
+              android:layoutDirection="locale"
+              />
+          <LinearLayout
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:visibility="gone"
+              android:layoutDirection="locale"
+              />
+          <LinearLayout
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:visibility="gone"
+              android:layoutDirection="locale"
+              />
+        </com.android.systemui.globalactions.ListGridLayout>
+      </LinearLayout>
+    </com.android.systemui.globalactions.GlobalActionsFlatLayout>
+
+    <LinearLayout
+        android:id="@+id/global_actions_panel"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/global_actions_view">
+
+      <FrameLayout
+          android:translationY="@dimen/global_actions_plugin_offset"
+          android:id="@+id/global_actions_panel_container"
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:translationY="@dimen/global_actions_plugin_offset"
+        android:id="@+id/global_actions_controls"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/global_actions_panel">
+      <TextView
+          android:text="Home"
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content"
+          android:singleLine="true"
+          android:gravity="center"
+          android:textSize="25dp"
+          android:textColor="?android:attr/textColorPrimary"
+          android:fontFamily="@*android:string/config_headlineFontFamily" />
+    <LinearLayout
+        android:id="@+id/global_actions_controls_list"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical" />
+    </LinearLayout>
+  </androidx.constraintlayout.widget.ConstraintLayout>
+</ScrollView>
diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml
index 6ac9da4..1f7def2 100644
--- a/packages/SystemUI/res/layout/global_screenshot.xml
+++ b/packages/SystemUI/res/layout/global_screenshot.xml
@@ -1,51 +1,75 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 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.
--->
+<!--
+  ~ Copyright (C) 2011 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.
+  -->
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-    <ImageView android:id="@+id/global_screenshot_background"
+             android:layout_width="match_parent"
+             android:layout_height="match_parent">
+    <ImageView
+        android:id="@+id/global_screenshot_background"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:src="@android:color/black"
-        android:visibility="gone" />
-    <ImageView android:id="@+id/global_screenshot"
+        android:visibility="gone"/>
+    <ImageView
+        android:id="@+id/global_screenshot_actions_background"
+        android:layout_height="400dp"
+        android:layout_width="match_parent"
+        android:layout_gravity="bottom|center"
+        android:src="@drawable/screenshot_actions_background_protection"
+        android:alpha="0"/>
+    <HorizontalScrollView
+        android:id="@+id/global_screenshot_actions_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom|center"
+        android:elevation="3dp"
+        android:fillViewport="true"
+        android:layout_marginHorizontal="@dimen/screenshot_action_container_margin_horizontal"
+        android:gravity="center"
+        android:paddingLeft="@dimen/screenshot_action_container_padding_left"
+        android:paddingRight="@dimen/screenshot_action_container_padding_right"
+        android:paddingVertical="@dimen/screenshot_action_container_padding_vertical"
+        android:visibility="gone"
+        android:scrollbars="none"
+        android:background="@drawable/action_chip_container_background">
+        <LinearLayout
+            android:id="@+id/global_screenshot_actions"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"/>
+    </HorizontalScrollView>
+    <ImageView
+        android:id="@+id/global_screenshot"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center"
-        android:background="@drawable/screenshot_panel"
+        android:elevation="8dp"
         android:visibility="gone"
-        android:adjustViewBounds="true" />
-    <ImageView android:id="@+id/global_screenshot_flash"
+        android:background="@drawable/screenshot_rounded_corners"
+        android:adjustViewBounds="true"/>
+    <ImageView
+        android:id="@+id/global_screenshot_flash"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:src="@android:color/white"
-        android:visibility="gone" />
+        android:visibility="gone"/>
     <com.android.systemui.screenshot.ScreenshotSelectorView
         android:id="@+id/global_screenshot_selector"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:visibility="gone"
         android:pointerIcon="crosshair"/>
-    <LinearLayout
-        android:id="@+id/global_screenshot_actions"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_gravity="bottom|center"
-        android:gravity="center"
-        android:paddingVertical="@dimen/screenshot_action_container_padding"
-        android:visibility="gone"
-        android:background="@drawable/action_chip_container_background"/>
+
 </FrameLayout>
diff --git a/packages/SystemUI/res/layout/global_screenshot_action_chip.xml b/packages/SystemUI/res/layout/global_screenshot_action_chip.xml
index 6b42400..79867a16b 100644
--- a/packages/SystemUI/res/layout/global_screenshot_action_chip.xml
+++ b/packages/SystemUI/res/layout/global_screenshot_action_chip.xml
@@ -14,12 +14,28 @@
   ~ 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/global_screenshot_action_chip"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:layout_marginHorizontal="@dimen/screenshot_action_chip_margin_horizontal"
-          android:paddingVertical="@dimen/screenshot_action_chip_padding_vertical"
-          android:paddingHorizontal="@dimen/screenshot_action_chip_padding_horizontal"
-          android:background="@drawable/action_chip_background"
-          android:textColor="@color/global_screenshot_button_text"/>
+<com.android.systemui.screenshot.ScreenshotActionChip
+    xmlns:android="http://schemas.android.com/apk/res/android"
+              android:id="@+id/global_screenshot_action_chip"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:layout_marginHorizontal="@dimen/screenshot_action_chip_margin_horizontal"
+              android:layout_gravity="center"
+              android:paddingVertical="@dimen/screenshot_action_chip_padding_vertical"
+              android:background="@drawable/action_chip_background"
+              android:gravity="center">
+    <ImageView
+        android:id="@+id/screenshot_action_chip_icon"
+        android:layout_width="@dimen/screenshot_action_chip_icon_size"
+        android:layout_height="@dimen/screenshot_action_chip_icon_size"
+        android:layout_marginStart="@dimen/screenshot_action_chip_padding_start"
+        android:layout_marginEnd="@dimen/screenshot_action_chip_padding_middle"/>
+    <TextView
+        android:id="@+id/screenshot_action_chip_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/screenshot_action_chip_padding_end"
+        android:textSize="@dimen/screenshot_action_chip_text_size"
+        android:textStyle="bold"
+        android:textColor="@color/global_screenshot_button_text"/>
+</com.android.systemui.screenshot.ScreenshotActionChip>
diff --git a/packages/SystemUI/res/layout/global_screenshot_legacy.xml b/packages/SystemUI/res/layout/global_screenshot_legacy.xml
new file mode 100644
index 0000000..791c7ea
--- /dev/null
+++ b/packages/SystemUI/res/layout/global_screenshot_legacy.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <ImageView android:id="@+id/global_screenshot_legacy_background"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:src="@android:color/black"
+        android:visibility="gone" />
+    <ImageView android:id="@+id/global_screenshot_legacy"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:background="@drawable/screenshot_panel"
+        android:visibility="gone"
+        android:adjustViewBounds="true" />
+    <ImageView android:id="@+id/global_screenshot_legacy_flash"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:src="@android:color/white"
+        android:visibility="gone" />
+    <com.android.systemui.screenshot.ScreenshotSelectorView
+        android:id="@+id/global_screenshot_legacy_selector"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="gone"
+        android:pointerIcon="crosshair"/>
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/media_carousel.xml b/packages/SystemUI/res/layout/media_carousel.xml
new file mode 100644
index 0000000..e91f840f
--- /dev/null
+++ b/packages/SystemUI/res/layout/media_carousel.xml
@@ -0,0 +1,35 @@
+<?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
+  -->
+
+<!-- Carousel for media controls -->
+<HorizontalScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/qs_media_height"
+    android:padding="@dimen/qs_media_padding"
+    android:scrollbars="none"
+    android:visibility="gone"
+    >
+    <LinearLayout
+        android:id="@+id/media_carousel"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        >
+        <!-- QSMediaPlayers will be added here dynamically -->
+    </LinearLayout>
+</HorizontalScrollView>
diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml
new file mode 100644
index 0000000..a9d6e35
--- /dev/null
+++ b/packages/SystemUI/res/layout/notification_conversation_info.xml
@@ -0,0 +1,256 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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.
+-->
+
+<com.android.systemui.statusbar.notification.row.NotificationConversationInfo
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/notification_guts"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:clickable="true"
+    android:clipChildren="false"
+    android:clipToPadding="true"
+    android:orientation="vertical"
+    android:background="@color/notification_material_background_color"
+    android:paddingStart="@*android:dimen/notification_content_margin_start">
+
+    <!-- Package Info -->
+    <RelativeLayout
+        android:id="@+id/header"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/notification_guts_conversation_header_height"
+        android:gravity="center_vertical"
+        android:clipChildren="false"
+        android:clipToPadding="false">
+        <ImageView
+            android:id="@+id/conversation_icon"
+            android:layout_width="@dimen/notification_guts_conversation_icon_size"
+            android:layout_height="@dimen/notification_guts_conversation_icon_size"
+            android:layout_centerVertical="true"
+            android:layout_alignParentStart="true"
+            android:layout_marginEnd="6dp" />
+        <LinearLayout
+            android:id="@+id/names"
+            android:orientation="vertical"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:minHeight="@dimen/notification_guts_conversation_icon_size"
+            android:layout_centerVertical="true"
+            android:gravity="center_vertical"
+            android:layout_toEndOf="@id/conversation_icon">
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="start"
+                android:orientation="horizontal">
+                <TextView
+                    android:id="@+id/parent_channel_name"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    style="@style/TextAppearance.NotificationImportanceChannel"/>
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    style="@style/TextAppearance.NotificationImportanceHeader"
+                    android:visibility="gone"
+                    android:layout_marginStart="2dp"
+                    android:layout_marginEnd="2dp"
+                    android:text="@*android:string/notification_header_divider_symbol" />
+                <TextView
+                    android:id="@+id/name"
+                    android:visibility="gone"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    style="@style/TextAppearance.NotificationImportanceChannel"/>
+            </LinearLayout>
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="start"
+                android:orientation="horizontal">
+                <TextView
+                    android:id="@+id/pkg_name"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    style="@style/TextAppearance.NotificationImportanceChannelGroup"
+                    android:ellipsize="end"
+                    android:maxLines="1"/>
+                <TextView
+                    android:id="@+id/group_divider"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    style="@style/TextAppearance.NotificationImportanceHeader"
+                    android:layout_marginStart="2dp"
+                    android:layout_marginEnd="2dp"
+                    android:text="@*android:string/notification_header_divider_symbol" />
+                <TextView
+                    android:id="@+id/group_name"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    style="@style/TextAppearance.NotificationImportanceChannel"/>
+            </LinearLayout>
+
+        </LinearLayout>
+
+        <TextView
+            android:id="@+id/pkg_divider"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerVertical="true"
+            style="@style/TextAppearance.NotificationImportanceHeader"
+            android:layout_marginStart="2dp"
+            android:layout_marginEnd="2dp"
+            android:layout_toEndOf="@id/name"
+            android:text="@*android:string/notification_header_divider_symbol" />
+        <TextView
+            android:id="@+id/delegate_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerVertical="true"
+            style="@style/TextAppearance.NotificationImportanceHeader"
+            android:layout_marginStart="2dp"
+            android:layout_marginEnd="2dp"
+            android:ellipsize="end"
+            android:text="@string/notification_delegate_header"
+            android:layout_toEndOf="@id/pkg_divider"
+            android:maxLines="1" />
+
+        <!-- end aligned fields -->
+        <ImageButton
+            android:id="@+id/demote"
+            android:layout_width="@dimen/notification_importance_toggle_size"
+            android:layout_height="@dimen/notification_importance_toggle_size"
+            android:layout_centerVertical="true"
+            android:background="@drawable/ripple_drawable"
+            android:contentDescription="@string/demote"
+            android:src="@drawable/ic_demote_conversation"
+            android:layout_toStartOf="@id/app_settings"
+            android:tint="@color/notification_guts_link_icon_tint"/>
+        <!-- Optional link to app. Only appears if the channel is not disabled and the app
+asked for it -->
+        <ImageButton
+            android:id="@+id/app_settings"
+            android:layout_width="@dimen/notification_importance_toggle_size"
+            android:layout_height="@dimen/notification_importance_toggle_size"
+            android:layout_centerVertical="true"
+            android:visibility="gone"
+            android:background="@drawable/ripple_drawable"
+            android:contentDescription="@string/notification_app_settings"
+            android:src="@drawable/ic_info"
+            android:layout_toStartOf="@id/info"
+            android:tint="@color/notification_guts_link_icon_tint"/>
+        <ImageButton
+            android:id="@+id/info"
+            android:layout_width="@dimen/notification_importance_toggle_size"
+            android:layout_height="@dimen/notification_importance_toggle_size"
+            android:layout_centerVertical="true"
+            android:background="@drawable/ripple_drawable"
+            android:contentDescription="@string/notification_more_settings"
+            android:src="@drawable/ic_settings"
+            android:layout_alignParentEnd="true"
+            android:tint="@color/notification_guts_link_icon_tint"/>
+    </RelativeLayout>
+
+    <LinearLayout
+        android:id="@+id/actions"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@*android:dimen/notification_content_margin_end"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:orientation="vertical">
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="0.5dp"
+            android:background="@color/GM2_grey_300" />
+        <Button
+            android:id="@+id/bubble"
+            android:layout_height="@dimen/notification_guts_conversation_action_height"
+            android:layout_width="match_parent"
+            style="?android:attr/borderlessButtonStyle"
+            android:text="@string/notification_conversation_favorite"
+            android:gravity="left|center_vertical"
+            android:drawableStart="@drawable/ic_create_bubble"
+            android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+            android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="0.5dp"
+            android:background="@color/GM2_grey_300" />
+        <Button
+            android:id="@+id/home"
+            android:layout_height="@dimen/notification_guts_conversation_action_height"
+            android:layout_width="match_parent"
+            style="?android:attr/borderlessButtonStyle"
+            android:text="@string/notification_conversation_home_screen"
+            android:gravity="left|center_vertical"
+            android:drawableStart="@drawable/ic_add_to_home"
+            android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+            android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="0.5dp"
+            android:background="@color/GM2_grey_300" />
+        <Button
+            android:id="@+id/fave"
+            android:layout_height="@dimen/notification_guts_conversation_action_height"
+            android:layout_width="match_parent"
+            style="?android:attr/borderlessButtonStyle"
+            android:gravity="left|center_vertical"
+            android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+            android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="0.5dp"
+            android:background="@color/GM2_grey_300" />
+        <Button
+            android:id="@+id/snooze"
+            android:layout_height="@dimen/notification_guts_conversation_action_height"
+            android:layout_width="match_parent"
+            style="?android:attr/borderlessButtonStyle"
+            android:text="@string/notification_menu_snooze_action"
+            android:gravity="left|center_vertical"
+            android:drawableStart="@drawable/ic_snooze"
+            android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+            android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="0.5dp"
+            android:background="@color/GM2_grey_300" />
+        <Button
+            android:id="@+id/mute"
+            android:layout_height="@dimen/notification_guts_conversation_action_height"
+            android:layout_width="match_parent"
+            style="?android:attr/borderlessButtonStyle"
+            android:text="@string/notification_conversation_mute"
+            android:gravity="left|center_vertical"
+            android:drawableStart="@drawable/ic_notifications_silence"
+            android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+            android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+        </LinearLayout>
+
+</com.android.systemui.statusbar.notification.row.NotificationConversationInfo>
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index 964a591..87de9d4 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -220,58 +220,6 @@
             android:orientation="vertical">
 
             <com.android.systemui.statusbar.notification.row.ButtonLinearLayout
-                android:id="@+id/bubble"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:padding="@dimen/notification_importance_button_padding"
-                android:layout_marginBottom="@dimen/notification_importance_button_separation"
-                android:clickable="true"
-                android:focusable="true"
-                android:background="@drawable/notification_guts_priority_button_bg"
-                android:orientation="vertical">
-                <LinearLayout
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:orientation="horizontal"
-                    android:gravity="center"
-                >
-                    <ImageView
-                        android:id="@+id/bubble_icon"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:src="@drawable/ic_create_bubble"
-                        android:background="@android:color/transparent"
-                        android:tint="@color/notification_guts_priority_contents"
-                        android:clickable="false"
-                        android:focusable="false"/>
-                    <TextView
-                        android:id="@+id/bubble_label"
-                        android:layout_width="0dp"
-                        android:layout_height="wrap_content"
-                        android:layout_marginStart="@dimen/notification_importance_drawable_padding"
-                        android:layout_weight="1"
-                        android:ellipsize="end"
-                        android:maxLines="1"
-                        android:clickable="false"
-                        android:focusable="false"
-                        android:textAppearance="@style/TextAppearance.NotificationImportanceButton"
-                        android:text="@string/notification_bubble_title"/>
-                </LinearLayout>
-                <TextView
-                    android:id="@+id/bubble_summary"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_marginTop="@dimen/notification_importance_button_description_top_margin"
-                    android:visibility="gone"
-                    android:text="@string/notification_channel_summary_bubble"
-                    android:clickable="false"
-                    android:focusable="false"
-                    android:ellipsize="end"
-                    android:maxLines="2"
-                    android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"/>
-            </com.android.systemui.statusbar.notification.row.ButtonLinearLayout>
-
-            <com.android.systemui.statusbar.notification.row.ButtonLinearLayout
                 android:id="@+id/alert"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml
deleted file mode 100644
index 3d63b7d..0000000
--- a/packages/SystemUI/res/layout/screen_record_dialog.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"
-              android:clipChildren="false"
-              android:clipToPadding="false"
-              android:gravity="top"
-              android:orientation="vertical"
-              android:padding="@dimen/global_actions_padding"
-              android:background="@drawable/rounded_bg_full">
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical">
-        <CheckBox
-            android:id="@+id/checkbox_mic"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:textAppearance="?android:attr/textAppearanceMedium"
-            android:text="@string/screenrecord_mic_label"/>
-        <CheckBox
-            android:id="@+id/checkbox_taps"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:textAppearance="?android:attr/textAppearanceMedium"
-            android:text="@string/screenrecord_taps_label"/>
-        <Button
-            android:id="@+id/record_button"
-            android:layout_width="match_parent"
-            android:layout_height="50dp"
-            android:text="@string/screenrecord_start_label"
-        />
-    </LinearLayout>
-
-</LinearLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 4869be1..479f255 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -17,9 +17,14 @@
 */
 -->
 
-<merge
+
+<com.android.systemui.statusbar.phone.NotificationPanelView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:systemui="http://schemas.android.com/apk/res-auto">
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/notification_panel"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@android:color/transparent">
     <FrameLayout
         android:id="@+id/big_clock_container"
         android:layout_width="match_parent"
@@ -97,4 +102,4 @@
         android:background="@drawable/qs_navbar_scrim" />
 
     <include layout="@layout/status_bar_expanded_plugin_frame"/>
-</merge>
+</com.android.systemui.statusbar.phone.NotificationPanelView>
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
new file mode 100644
index 0000000..dc070cb
--- /dev/null
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** 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.
+*/
+-->
+
+<!-- This is the notification shade window. -->
+<com.android.systemui.statusbar.phone.NotificationShadeWindowView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:sysui="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true">
+
+    <com.android.systemui.statusbar.BackDropView
+            android:id="@+id/backdrop"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:visibility="gone"
+            sysui:ignoreRightInset="true"
+            >
+        <ImageView android:id="@+id/backdrop_back"
+                   android:layout_width="match_parent"
+                   android:scaleType="centerCrop"
+                   android:layout_height="match_parent" />
+        <ImageView android:id="@+id/backdrop_front"
+                   android:layout_width="match_parent"
+                   android:layout_height="match_parent"
+                   android:scaleType="centerCrop"
+                   android:visibility="invisible" />
+    </com.android.systemui.statusbar.BackDropView>
+
+    <com.android.systemui.statusbar.ScrimView
+        android:id="@+id/scrim_for_bubble"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:importantForAccessibility="no"
+        sysui:ignoreRightInset="true"
+        />
+
+    <com.android.systemui.statusbar.ScrimView
+        android:id="@+id/scrim_behind"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:importantForAccessibility="no"
+        sysui:ignoreRightInset="true"
+        />
+
+    <include layout="@layout/status_bar_expanded"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="invisible" />
+
+    <include layout="@layout/brightness_mirror" />
+
+    <com.android.systemui.statusbar.ScrimView
+        android:id="@+id/scrim_in_front"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:importantForAccessibility="no"
+        sysui:ignoreRightInset="true"
+    />
+
+    <LinearLayout
+        android:id="@+id/lock_icon_container"
+        android:orientation="vertical"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/status_bar_height"
+        android:layout_gravity="top|center_horizontal">
+        <com.android.systemui.statusbar.phone.LockIcon
+            android:id="@+id/lock_icon"
+            android:layout_width="@dimen/keyguard_lock_width"
+            android:layout_height="@dimen/keyguard_lock_height"
+            android:layout_gravity="center_horizontal"
+            android:layout_marginTop="@dimen/keyguard_lock_padding"
+            android:contentDescription="@string/accessibility_unlock_button"
+            android:src="@*android:drawable/ic_lock"
+            android:scaleType="center" />
+        <com.android.keyguard.KeyguardMessageArea
+            android:id="@+id/keyguard_message_area"
+            style="@style/Keyguard.TextView"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/keyguard_lock_padding"
+            android:gravity="center"
+            android:singleLine="true"
+            android:ellipsize="marquee"
+            android:focusable="true" />
+    </LinearLayout>
+</com.android.systemui.statusbar.phone.NotificationShadeWindowView>
diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml
index 57834da..8fee2cf9 100644
--- a/packages/SystemUI/res/layout/super_status_bar.xml
+++ b/packages/SystemUI/res/layout/super_status_bar.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
 **
-** Copyright 2012, 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.
@@ -17,7 +17,7 @@
 */
 -->
 
-<!-- This is the combined status bar / notification panel window. -->
+<!-- This is the status bar window. -->
 <com.android.systemui.statusbar.phone.StatusBarWindowView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:sysui="http://schemas.android.com/apk/res-auto"
@@ -25,85 +25,8 @@
     android:layout_height="match_parent"
     android:fitsSystemWindows="true">
 
-    <com.android.systemui.statusbar.BackDropView
-            android:id="@+id/backdrop"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:visibility="gone"
-            sysui:ignoreRightInset="true"
-            >
-        <ImageView android:id="@+id/backdrop_back"
-                   android:layout_width="match_parent"
-                   android:scaleType="centerCrop"
-                   android:layout_height="match_parent" />
-        <ImageView android:id="@+id/backdrop_front"
-                   android:layout_width="match_parent"
-                   android:layout_height="match_parent"
-                   android:scaleType="centerCrop"
-                   android:visibility="invisible" />
-    </com.android.systemui.statusbar.BackDropView>
-
-    <com.android.systemui.statusbar.ScrimView
-        android:id="@+id/scrim_for_bubble"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:importantForAccessibility="no"
-        sysui:ignoreRightInset="true"
-        />
-
     <FrameLayout
         android:id="@+id/status_bar_container"
         android:layout_width="match_parent"
         android:layout_height="wrap_content" />
-
-    <com.android.systemui.statusbar.ScrimView
-        android:id="@+id/scrim_behind"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:importantForAccessibility="no"
-        sysui:ignoreRightInset="true"
-        />
-
-    <ViewStub android:id="@+id/status_bar_expanded"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:visibility="invisible" />
-
-    <include layout="@layout/brightness_mirror" />
-
-    <com.android.systemui.statusbar.ScrimView
-        android:id="@+id/scrim_in_front"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:importantForAccessibility="no"
-        sysui:ignoreRightInset="true"
-    />
-
-    <LinearLayout
-        android:id="@+id/lock_icon_container"
-        android:orientation="vertical"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/status_bar_height"
-        android:layout_gravity="top|center_horizontal">
-        <com.android.systemui.statusbar.phone.LockIcon
-            android:id="@+id/lock_icon"
-            android:layout_width="@dimen/keyguard_lock_width"
-            android:layout_height="@dimen/keyguard_lock_height"
-            android:layout_gravity="center_horizontal"
-            android:layout_marginTop="@dimen/keyguard_lock_padding"
-            android:contentDescription="@string/accessibility_unlock_button"
-            android:src="@*android:drawable/ic_lock"
-            android:scaleType="center" />
-        <com.android.keyguard.KeyguardMessageArea
-            android:id="@+id/keyguard_message_area"
-            style="@style/Keyguard.TextView"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/keyguard_lock_padding"
-            android:gravity="center"
-            android:singleLine="true"
-            android:ellipsize="marquee"
-            android:focusable="true" />
-    </LinearLayout>
 </com.android.systemui.statusbar.phone.StatusBarWindowView>
diff --git a/packages/SystemUI/res/layout/tv_audio_recording_indicator.xml b/packages/SystemUI/res/layout/tv_audio_recording_indicator.xml
index f04226e..cd90efe 100644
--- a/packages/SystemUI/res/layout/tv_audio_recording_indicator.xml
+++ b/packages/SystemUI/res/layout/tv_audio_recording_indicator.xml
@@ -32,8 +32,8 @@
             android:orientation="horizontal">
 
             <FrameLayout
-                android:layout_width="90dp"
-                android:layout_height="94dp">
+                android:layout_width="45dp"
+                android:layout_height="47dp">
 
                 <View
                     android:id="@+id/icon_container_bg"
@@ -43,35 +43,34 @@
 
                 <FrameLayout
                     android:id="@+id/icon_mic"
-                    android:layout_width="70dp"
-                    android:layout_height="70dp"
-                    android:layout_marginLeft="12dp"
-                    android:layout_marginTop="12dp"
-                    android:layout_marginRight="8dp"
-                    android:layout_marginBottom="12dp">
+                    android:layout_width="35dp"
+                    android:layout_height="35dp"
+                    android:layout_marginLeft="6dp"
+                    android:layout_marginTop="6dp"
+                    android:layout_marginBottom="6dp">
 
                     <View
-                        android:layout_width="54dp"
-                        android:layout_height="54dp"
+                        android:layout_width="27dp"
+                        android:layout_height="27dp"
                         android:layout_gravity="center"
                         android:background="@drawable/tv_circle_dark"/>
 
                     <ImageView
                         android:id="@+id/pulsating_circle"
-                        android:layout_width="54dp"
-                        android:layout_height="54dp"
+                        android:layout_width="27dp"
+                        android:layout_height="27dp"
                         android:layout_gravity="center"
                         android:background="@drawable/tv_circle_white_translucent"/>
 
                     <ImageView
-                        android:layout_width="54dp"
-                        android:layout_height="54dp"
+                        android:layout_width="27dp"
+                        android:layout_height="27dp"
                         android:layout_gravity="center"
                         android:src="@drawable/tv_ring_white"/>
 
                     <ImageView
-                        android:layout_width="32dp"
-                        android:layout_height="32dp"
+                        android:layout_width="16dp"
+                        android:layout_height="16dp"
                         android:layout_gravity="center"
                         android:background="@drawable/tv_ic_mic_white"/>
                 </FrameLayout>
@@ -81,29 +80,30 @@
             <LinearLayout
                 android:id="@+id/texts_container"
                 android:layout_width="wrap_content"
-                android:layout_height="94dp"
+                android:layout_height="47dp"
                 android:background="@color/tv_audio_recording_indicator_background"
-                android:gravity="center_vertical"
                 android:orientation="vertical"
                 android:visibility="visible">
 
                 <TextView
                     android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
+                    android:layout_height="14dp"
+                    android:layout_marginTop="10dp"
+                    android:layout_marginBottom="1dp"
                     android:text="@string/mic_active"
                     android:textColor="@android:color/white"
                     android:fontFamily="sans-serif"
-                    android:textSize="20dp"/>
+                    android:textSize="10dp"/>
 
                 <TextView
                     android:id="@+id/text"
                     android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
+                    android:layout_height="14dp"
                     android:singleLine="true"
                     android:text="SomeApplication accessed your microphone"
                     android:textColor="@android:color/white"
                     android:fontFamily="sans-serif"
-                    android:textSize="16dp"/>
+                    android:textSize="8dp"/>
 
             </LinearLayout>
 
@@ -113,8 +113,8 @@
 
     <View
         android:id="@+id/bg_right"
-        android:layout_width="24dp"
-        android:layout_height="94dp"
+        android:layout_width="12dp"
+        android:layout_height="47dp"
         android:background="@drawable/tv_rect_dark_right_rounded"
         android:visibility="visible"/>
 
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index ea7b9bb..4de8543 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC is gedeaktiveer"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC is geaktiveer"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Swiep op om programme te wissel"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Sleep regs om programme vinnig te wissel"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Wissel oorsig"</string>
@@ -471,6 +477,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Vee alles uit"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Bestuur"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Stil kennisgewings"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Gesprekke"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Vee alle stil kennisgewings uit"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Kennisgewings onderbreek deur Moenie Steur Nie"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Begin nou"</string>
@@ -689,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Pasmaak"</string>
     <string name="notification_done" msgid="6215117625922713976">"Klaar"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Ontdoen"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"kennisgewingkontroles"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"kennisgewing-sluimeropsies"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 9412f0f..87603ac 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -391,6 +391,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"ኤንኤፍሲ"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"ኤንኤፍሲ ተሰናክሏል"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"ኤንኤፍሲ ነቅቷል"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"የማያ ገጽ ቀረጻ"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"ጀምር"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"አቁም"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"መተግበሪያዎችን ለመቀየር ወደ ላይ ያንሸራትቱ"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"መተግበሪያዎችን በፍጥነት ለመቀየር ወደ ቀኝ ይጎትቱ"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"አጠቃላይ እይታን ቀያይር"</string>
@@ -471,6 +474,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ሁሉንም አጽዳ"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"ያቀናብሩ"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"ጸጥ ያሉ ማሳወቂያዎች"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"ውይይቶች"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"ሁሉንም ጸጥ ያሉ ማሳወቂያዎችን ያጽዱ"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"ማሳወቂያዎች በአትረብሽ ባሉበት ቆመዋል"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"አሁን ጀምር"</string>
@@ -689,6 +693,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"አብጅ"</string>
     <string name="notification_done" msgid="6215117625922713976">"ተከናውኗል"</string>
     <string name="inline_undo" msgid="9026953267645116526">"ቀልብስ"</string>
+    <string name="demote" msgid="6225813324237153980">"ይህን ማሳወቂያ ውይይት እንዳልሆነ ምልክት ያድርጉበት"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"ተወዳጅ"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"ተወዳጅ አታድርግ"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"ድምጽ ዝጋ"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"ድምጽ ክፈት"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"እንደ አረፋ አሳይ"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"አረፋዎችን አጥፋ"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"ወደ መነሻ ማያ ገጽ አክል"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"የማሳወቂያ መቆጣጠሪያዎች"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"የማሳወቂያ ማሸለቢያ አማራጮች"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index f675265..c764004 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -109,7 +109,7 @@
     <string name="accessibility_camera_button" msgid="2938898391716647247">"الكاميرا"</string>
     <string name="accessibility_phone_button" msgid="4256353121703100427">"الهاتف"</string>
     <string name="accessibility_voice_assist_button" msgid="6497706615649754510">"المساعد الصوتي"</string>
-    <string name="accessibility_unlock_button" msgid="122785427241471085">"إلغاء القفل"</string>
+    <string name="accessibility_unlock_button" msgid="122785427241471085">"فتح القفل"</string>
     <string name="accessibility_waiting_for_fingerprint" msgid="5209142744692162598">"في انتظار بصمة الإصبع"</string>
     <string name="accessibility_unlock_without_fingerprint" msgid="1811563723195375298">"فتح القفل بدون استخدام بصمة إصبعك"</string>
     <string name="accessibility_scanning_face" msgid="3093828357921541387">"مسح الوجه"</string>
@@ -140,7 +140,7 @@
     <string name="face_dialog_looking_for_face" msgid="2656848512116189509">"جارٍ البحث عن وجهك…"</string>
     <string name="accessibility_face_dialog_face_icon" msgid="8335095612223716768">"رمز الوجه"</string>
     <string name="accessibility_compatibility_zoom_button" msgid="5845799798708790509">"زر تكبير/تصغير للتوافق."</string>
-    <string name="accessibility_compatibility_zoom_example" msgid="2617218726091234073">"استخدام التكبير/التصغير لتحويل شاشة صغيرة إلى شاشة أكبر"</string>
+    <string name="accessibility_compatibility_zoom_example" msgid="2617218726091234073">"استخدام التكبير أو التصغير لتحويل شاشة صغيرة إلى شاشة أكبر"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"تم توصيل البلوتوث."</string>
     <string name="accessibility_bluetooth_disconnected" msgid="7195823280221275929">"تم فصل البلوتوث."</string>
     <string name="accessibility_no_battery" msgid="3789287732041910804">"ليست هناك بطارية."</string>
@@ -399,6 +399,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"‏الاتصالات قصيرة المدى (NFC)"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"تم إيقاف الاتصال القريب المدى"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"تم تفعيل الاتصال القريب المدى"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"مرّر سريعًا لأعلى لتبديل التطبيقات"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"اسحب لليسار للتبديل السريع بين التطبيقات"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"تبديل \"النظرة العامة\""</string>
@@ -477,13 +483,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"إيقاف ميزة توفير شحن البطارية"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"سيتمكن تطبيق <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> من الوصول إلى كل المعلومات المرئية لك على الشاشة أو التي يتم تشغيلها على جهازك أثناء التسجيل أو الإرسال. ويشمل ذلك معلومات مثل كلمات المرور وتفاصيل الدفع والصور والرسائل والمقاطع الصوتية التي تشغِّلها."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"ستتمكن الخدمة التي تقدّم هذه الوظيفة من الوصول إلى كل المعلومات المرئية لك على الشاشة أو التي يتم تشغيلها على جهازك أثناء التسجيل أو الإرسال. ويشمل ذلك معلومات مثل كلمات المرور وتفاصيل الدفع والصور والرسائل والمقاطع الصوتية التي تشغِّلها."</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"هل تريد بدء التسجيل أو الإرسال؟"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"هل تريد بدء التسجيل أو الإرسال باستخدام <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>؟"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"عدم الإظهار مرة أخرى"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"محو الكل"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"إدارة"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"الإشعارات الصامتة"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"المحادثات"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"محو جميع الإشعارات الصامتة"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"تم إيقاف الإشعارات مؤقتًا وفقًا لإعداد \"الرجاء عدم الإزعاج\""</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"البدء الآن"</string>
@@ -544,10 +550,10 @@
     <string name="branded_monitoring_description_app_personal" msgid="1703511985892688885">"أنت متصل بـ <xliff:g id="APPLICATION">%1$s</xliff:g>، الذي يمكنه مراقبة أنشطتك الشخصية على الشبكة، بما في ذلك الرسائل الإلكترونية والتطبيقات ومواقع الويب."</string>
     <string name="monitoring_description_app_work" msgid="3713084153786663662">"يخضع الملف الشخصي للعمل لإدارة <xliff:g id="ORGANIZATION">%1$s</xliff:g>. تم ربط الملف الشخصي بـ <xliff:g id="APPLICATION">%2$s</xliff:g>، الذي يمكنه مراقبة أنشطة شبكتك، بما في ذلك الرسائل الإلكترونية والتطبيقات ومواقع الويب.\n\nيمكنك الاتصال بالمشرف للحصول على مزيد من المعلومات."</string>
     <string name="monitoring_description_app_personal_work" msgid="6175816356939166101">"يخضع الملف الشخصي للعمل لإدارة <xliff:g id="ORGANIZATION">%1$s</xliff:g>. تم ربط هذا الملف الشخصي بـ <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>، الذي يمكنه مراقبة أنشطة شبكتك، بما في ذلك الرسائل الإلكترونية والتطبيقات ومواقع الويب.\n\nتم ربطك بـ <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>، الذي يمكنه مراقبة أنشطة شبكتك الشخصية."</string>
-    <string name="keyguard_indication_trust_unlocked" msgid="7395154975733744547">"‏إلغاء القفل باستمرار بواسطة TrustAgent"</string>
-    <string name="keyguard_indication_trust_disabled" msgid="6820793704816727918">"سيظل الجهاز مقفلاً إلى أن يتم إلغاء قفله يدويًا"</string>
+    <string name="keyguard_indication_trust_unlocked" msgid="7395154975733744547">"‏فتح القفل باستمرار بواسطة TrustAgent"</string>
+    <string name="keyguard_indication_trust_disabled" msgid="6820793704816727918">"سيظل الجهاز مقفلاً إلى أن يتم فتح قفله يدويًا"</string>
     <string name="hidden_notifications_title" msgid="1782412844777612795">"الحصول على الإشعارات بشكل أسرع"</string>
-    <string name="hidden_notifications_text" msgid="5899627470450792578">"الاطّلاع عليها قبل إلغاء القفل"</string>
+    <string name="hidden_notifications_text" msgid="5899627470450792578">"الاطّلاع عليها قبل فتح القفل"</string>
     <string name="hidden_notifications_cancel" msgid="4805370226181001278">"لا، شكرًا"</string>
     <string name="hidden_notifications_setup" msgid="2064795578526982467">"إعداد"</string>
     <string name="zen_mode_and_condition" msgid="5043165189511223718">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
@@ -702,6 +708,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"تخصيص"</string>
     <string name="notification_done" msgid="6215117625922713976">"تم"</string>
     <string name="inline_undo" msgid="9026953267645116526">"تراجع"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"عناصر التحكم في الإشعارات"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"خيارات تأجيل الإشعارات"</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index d8a7b26..2fc393c 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC নিষ্ক্ৰিয় হৈ আছে"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC সক্ষম হৈ আছে"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"আনটো এপ্ ব্য়ৱহাৰ কৰিবলৈ ওপৰলৈ ছোৱাইপ কৰক"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"খৰতকীয়াকৈ আনটো এপ্ ব্য়ৱহাৰ কৰিবলৈ সোঁফালে টানক"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"অৱলোকন ট’গল কৰক"</string>
@@ -465,13 +471,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"বেটাৰি সঞ্চয়কাৰী অফ কৰক"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>এ আপোনাৰ স্ক্ৰীনত দৃশ্যমান হোৱা অথবা ৰেকর্ডিং অথবা কাষ্টিংৰ সময়ত আপোনাৰ ডিভাইচত প্লে\' কৰা সকলো তথ্যলৈ এক্সেছ পাব। এইটোত পাছৱর্ড, পৰিশোধৰ সবিশেষ, ফট\', বার্তাসমূহ আৰু আপুনি প্লে\' কৰা অডিঅ\'ৰ দৰে তথ্য অন্তর্ভুক্ত হয়।"</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"এই সুবিধাটো প্ৰদান কৰা সেৱাটোৱে আপোনাৰ স্ক্ৰীনত দৃশ্যমান হোৱা অথবা ৰেকর্ডিং অথবা কাষ্টিংৰ সময়ত আপোনাৰ ডিভাইচত প্লে\' কৰা সকলো তথ্যলৈ এক্সেছ পাব। এইটোত পাছৱর্ড, পৰিশোধৰ সবিশেষ, ফট\', বার্তাসমূহ আৰু আপুনি প্লে\' কৰা অডিঅ\'ৰ দৰে তথ্য অন্তর্ভুক্ত হয়।"</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ৰেকর্ডিং অথবা কাষ্টিং আৰম্ভ কৰিবনে?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>ৰ জৰিয়তে ৰেকর্ডিং অথবা কাষ্টিং আৰম্ভ কৰিবনে ?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"পুনৰাই নেদেখুৱাব"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"সকলো মচক"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"পৰিচালনা"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"নীৰৱ জাননীসমূহ"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"বাৰ্তালাপসমূহ"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"সকলো নীৰৱ জাননী মচক"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"অসুবিধা নিদিব-ই জাননী পজ কৰিছে"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"এতিয়াই আৰম্ভ কৰক"</string>
@@ -690,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"নিজৰ উপযোগিতা অনুসৰি"</string>
     <string name="notification_done" msgid="6215117625922713976">"সম্পন্ন হ’ল"</string>
     <string name="inline_undo" msgid="9026953267645116526">"আনডু কৰক"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"জাননীৰ নিয়ন্ত্ৰণসমূহ"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"জাননীক স্নুজ কৰাৰ বিকল্পসমূহ"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 7511a7f..de9c605 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC deaktiv edilib"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC aktiv edilib"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Tətbiqi dəyişmək üçün yuxarı sürüşdürün"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Tətbiqləri cəld dəyişmək üçün sağa çəkin"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"İcmala Keçin"</string>
@@ -465,13 +471,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Batareya Qənaətini deaktiv edin"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tətbiqinin yazma və ya yayım zamanı ekranda görünən və ya cihazdan oxudulan bütün məlumatlara girişi olacaq. Bura parollar, ödəniş detalları, fotolar, mesajlar və oxudulan audio kimi məlumatlar daxildir."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Bu funksiyanı təmin edən xidmətin yazma və ya yayım zamanı ekranda görünən və ya cihazdan oxudulan bütün məlumatlara girişi olacaq. Bura parollar, ödəniş detalları, fotolar, mesajlar və oxudulan audio kimi məlumatlar daxildir."</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Yazma və ya yayımlama başladılsın?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ilə yazma və ya yayımlama başladılsın?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"Daha göstərmə"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Hamısını silin"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"İdarə edin"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Səssiz bildirişlər"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Söhbətlər"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Səssiz bildirişlərin hamısını silin"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Bildirişlər \"Narahat Etməyin\" rejimi tərəfindən dayandırıldı"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"İndi başlayın"</string>
@@ -690,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Fərdiləşdirin"</string>
     <string name="notification_done" msgid="6215117625922713976">"Hazırdır"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Ləğv edin"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"bildiriş nəzarəti"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"bildiriş təxirə salma seçimləri"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 2b94d67..a895f35 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -393,6 +393,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC je onemogućen"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC je omogućen"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Snimak ekrana"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Počnite"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Zaustavite"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Prevucite nagore da biste menjali aplikacije"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Prevucite udesno da biste brzo promenili aplikacije"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Uključi/isključi pregled"</string>
@@ -474,6 +477,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Obriši sve"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Upravljajte"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Nečujna obaveštenja"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Konverzacije"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Obrišite sva nečujna obaveštenja"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Obaveštenja su pauzirana režimom Ne uznemiravaj"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Započni odmah"</string>
@@ -692,6 +696,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Prilagodi"</string>
     <string name="notification_done" msgid="6215117625922713976">"Gotovo"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Opozovi"</string>
+    <string name="demote" msgid="6225813324237153980">"Označi da ovo obaveštenje nije konverzacija"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Označi kao omiljeno"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Ukloni iz omiljenih"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Isključi zvuk"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Uključi zvuk"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Prikaži kao oblačić"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Isključi oblačiće"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Dodaj na početni ekran"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"kontrole obaveštenja"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"opcije za odlaganje obaveštenja"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 35b0ddb..67aa899 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -397,6 +397,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC адключаны"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC уключаны"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Правядзіце ўверх, каб пераключыць праграмы"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Каб хутка пераключыцца паміж праграмамі, перацягніце ўправа"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Уключыць/выключыць агляд"</string>
@@ -473,13 +479,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Адключыць рэжым эканоміі зараду"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"Падчас запісу ці трансляцыі праграма \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" будзе мець доступ да ўсёй інфармацыі, адлюстраванай на экране вашай прылады, ці той, якая праз яе прайграецца. Гэта інфармацыя ўключае паролі, звесткі пра аплату, фота, паведамленні і аўдыя, якое вы прайграяце."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Падчас запісу ці трансляцыі служба, якая забяспечвае работу гэтай функцыі, будзе мець доступ да ўсёй інфармацыі, адлюстраванай на экране вашай прылады, ці той, якая праз яе прайграецца. Гэта інфармацыя ўключае паролі, звесткі пра аплату, фота, паведамленні і аўдыя, якое вы прайграяце."</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Пачаць запіс або трансляцыю?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Пачаць запіс або трансляцыю з дапамогай праграмы \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\"?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"Не паказваць зноў"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Ачысціць усё"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Кіраваць"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Апавяшчэнні без гуку"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Размовы"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Выдаліць усе апавяшчэнні без гуку"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Паказ апавяшчэнняў прыпынены ў рэжыме \"Не турбаваць\""</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Пачаць зараз"</string>
@@ -698,6 +704,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Наладзіць"</string>
     <string name="notification_done" msgid="6215117625922713976">"Гатова"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Адрабіць"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"кіраванне апавяшчэннямі"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"параметры адкладвання апавяшчэнняў"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 26102fb..f83bf96 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -391,6 +391,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"КБП"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"КБП е деактивирана"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"КБП е активирана"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Записване на екрана"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Старт"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Стоп"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Прекарайте пръст нагоре, за да превключите между приложенията"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Плъзнете надясно за бързо превключване между приложенията"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Превключване на общия преглед"</string>
@@ -471,6 +474,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Изчистване на всички"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Управление"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Беззвучни известия"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Разговори"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Изчистване на всички беззвучни известия"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Известията са поставени на пауза от режима „Не безпокойте“"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Стартиране сега"</string>
@@ -689,6 +693,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Персонализиране"</string>
     <string name="notification_done" msgid="6215117625922713976">"Готово"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Отмяна"</string>
+    <string name="demote" msgid="6225813324237153980">"Отбелязване, че известието не е за разговор"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Означаване като любим"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Не е любим"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Спиране"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Пускане"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Показване като балонче"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Изключване на балончетата"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Добавяне към началния екран"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g> от <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"контроли за известията"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"опции за отлагане на известията"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index e17905a..10479b1 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC অক্ষম করা আছে"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC সক্ষম করা আছে"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"অন্য অ্যাপে যেতে উপরের দিকে সোয়াইপ করুন"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"একটি অ্যাপ ছেড়ে দ্রুত অন্য অ্যাপে যেতে ডান দিকে টেনে আনুন"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"\'এক নজরে\' বৈশিষ্ট্যটি চালু বা বন্ধ করুন"</string>
@@ -465,13 +471,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"ব্যাটারি সেভার বন্ধ করুন"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"রেকর্ড করা বা কাস্টিং করার সময় স্ক্রিনে দেখানো বা ডিভাইসে দেখানো সমস্ত তথ্যের অ্যাক্সেস <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>-এর থাকবে। এর মধ্যে আপনার পাসওয়ার্ড, পেমেন্টের বিবরণ, ফটো, মেসেজ এবং যে অডিও আপনি চালান সেগুলি সম্পর্কিত তথ্য রয়েছে।"</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"রেকর্ড করা বা কাস্টিং করার সময় আপনার স্ক্রিনে দেখানো বা ডিভাইসে চালানো হয়েছে এমন সমস্ত তথ্যের অ্যাক্সেস এই ফাংশন প্রদানকারী পরিষেবার কাছে থাকবে। এর মধ্যে আপনার পাসওয়ার্ড, পেমেন্টের বিবরণ, ফটো, মেসেজ এবং যে অডিও আপনি চালান সেগুলি সম্পর্কিত তথ্য রয়েছে।"</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"রেকর্ড অথবা কাস্টিং শুরু করতে চান?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> দিয়ে রেকর্ড করা বা কাস্টিং শুরু করবেন?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"আর দেখাবেন না"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"সবকিছু সাফ করুন"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"পরিচালনা করুন"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"নীরব বিজ্ঞপ্তি"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"কথোপকথন"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"সব নীরব বিজ্ঞপ্তি মুছুন"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"\'বিরক্ত করবে না\' দিয়ে বিজ্ঞপ্তি পজ করা হয়েছে"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"এখন শুরু করুন"</string>
@@ -690,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"কাস্টমাইজ করুন"</string>
     <string name="notification_done" msgid="6215117625922713976">"সম্পন্ন"</string>
     <string name="inline_undo" msgid="9026953267645116526">"আগের অবস্থায় ফিরে যান"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"বিজ্ঞপ্তির নিয়ন্ত্রণগুলি"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"বিজ্ঞপ্তি মনে করিয়ে দেওয়ার বিকল্পগুলি"</string>
@@ -886,7 +908,7 @@
     <string name="notification_channel_general" msgid="4384774889645929705">"সাধারণ বার্তাগুলি"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"স্টোরেজ"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"হিন্ট"</string>
-    <string name="instant_apps" msgid="8337185853050247304">"ঝটপট অ্যাপ"</string>
+    <string name="instant_apps" msgid="8337185853050247304">"ইনস্ট্যান্ট অ্যাপ"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> চলছে"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"অ্যাপটি ইনস্টল না করে চালু করা হয়েছে।"</string>
     <string name="instant_apps_message_with_help" msgid="1816952263531203932">"অ্যাপটি ইনস্টল না করে চালু করা হয়েছে। আরও জানতে ট্যাপ করুন।"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 5851011..6c89d97 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -393,6 +393,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC je onemogućen"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC je omogućen"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Snimanje ekrana"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Započnite"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Zaustavite"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Prevucite prema gore za promjenu aplikacije"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Prevucite udesno za brzu promjenu aplikacija"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Pregled uključivanja/isključivanja"</string>
@@ -474,6 +477,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Očisti sve"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Upravljajte"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Nečujna obavještenja"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Razgovori"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Obriši sva nečujna obavještenja"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Obavještenja su pauzirana načinom rada Ne ometaj"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Započni odmah"</string>
@@ -694,6 +698,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Prilagodi"</string>
     <string name="notification_done" msgid="6215117625922713976">"Gotovo"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Opozovi"</string>
+    <string name="demote" msgid="6225813324237153980">"Označi da ovo obavještenje nije razgovor"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Omiljeno"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Ukloni iz omiljenog"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Isključi zvuk"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Uključi zvuk"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Prikaži kao oblačić"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Isključi oblačiće"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Dodaj na početni ekran"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"kontrole obavještenja"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"opcije za odgodu obavještenja"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 9fb7230..f675e20 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -391,6 +391,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"L\'NFC està desactivada"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"L\'NFC està activada"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Gravació de la pantalla"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Inicia"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Atura"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Llisca cap amunt per canviar d\'aplicació"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Arrossega el dit cap a la dreta per canviar ràpidament d\'aplicació"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Activa o desactiva Aplicacions recents"</string>
@@ -471,6 +474,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Esborra-ho tot"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Gestiona"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Notificacions silencioses"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Converses"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Esborra totes les notificacions silencioses"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notificacions pausades pel mode No molestis"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Comença ara"</string>
@@ -689,6 +693,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Personalitza"</string>
     <string name="notification_done" msgid="6215117625922713976">"Fet"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Desfés"</string>
+    <string name="demote" msgid="6225813324237153980">"Marca que la notificació no és una conversa"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Marca com a preferida"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Desmarca com a preferida"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Silencia"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Deixa de silenciar"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Mostra com a bombolla"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Desactiva les bombolles"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Afegeix a la pantalla d\'inici"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"controls de notificació"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"opcions per posposar la notificació"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 30724f8..354ccf4 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -395,6 +395,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC je vypnuto"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC je zapnuto"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Záznam obrazovky"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Spustit"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Ukončit"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Přejetím nahoru přepnete aplikace"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Přetažením doprava rychle přepnete aplikace"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Přepnout přehled"</string>
@@ -477,6 +480,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Smazat vše"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Spravovat"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Tichá oznámení"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Konverzace"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Vymazat všechna tichá oznámení"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Oznámení jsou pozastavena režimem Nerušit"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Spustit"</string>
@@ -695,6 +699,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Upravit"</string>
     <string name="notification_done" msgid="6215117625922713976">"Hotovo"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Zpět"</string>
+    <string name="demote" msgid="6225813324237153980">"Označit, že toto oznámení není součástí konverzace"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Přidat mezi oblíbené"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Odebrat z oblíbených"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Ignorovat"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Přestat ignorovat"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Zobrazit jako bublinu"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Vypnout bubliny"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Přidat na plochu"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g> aplikace <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"Nastavení oznámení"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"Možnosti odložení oznámení"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index e20a276..04e83d4 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -391,6 +391,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC er deaktiveret"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC er aktiveret"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Optag skærm"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Start"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Stop"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Stryg opad for at skifte apps"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Træk til højre for hurtigt at skifte app"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Slå Oversigt til/fra"</string>
@@ -471,6 +474,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Ryd alle"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Administrer"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Lydløse notifikationer"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Samtaler"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Ryd alle lydløse notifikationer"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notifikationer er sat på pause af Forstyr ikke"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Start nu"</string>
@@ -689,6 +693,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Tilpas"</string>
     <string name="notification_done" msgid="6215117625922713976">"Udfør"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Fortryd"</string>
+    <string name="demote" msgid="6225813324237153980">"Markér denne notifikation som ikke en samtale"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Angiv som favorit"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Fjern som favorit"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Slå lyden fra"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Slå lyden til"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Vis som boble"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Slå bobler fra"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Føj til startskærm"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"kontrolelementer til notifikationer"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"Indstillinger for udsættelse"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 7defdc1..baf3e7c 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -395,6 +395,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC ist deaktiviert"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC ist aktiviert"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Nach oben wischen, um Apps zu wechseln"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Zum schnellen Wechseln der Apps nach rechts ziehen"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Übersicht ein-/ausblenden"</string>
@@ -469,13 +475,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Energiesparmodus deaktivieren"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"Die App \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" erhält Zugriff auf alle Informationen, die auf deinem Bildschirm sichtbar sind oder von deinem Gerät wiedergegeben werden, während du aufnimmst oder streamst. Dazu gehören beispielsweise Passwörter, Zahlungsdetails, Fotos, Nachrichten und Audioinhalte."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Der Anbieter dieser App erhält Zugriff auf alle Informationen, die auf deinem Bildschirm sichtbar sind oder von deinem Gerät wiedergegeben werden, während du aufnimmst oder streamst. Dazu gehören beispielsweise Passwörter, Zahlungsdetails, Fotos, Nachrichten und Audioinhalte."</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Aufnahme oder Stream starten?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Aufnehmen oder Streamen mit der App \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" starten?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"Nicht mehr anzeigen"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Alle löschen"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Verwalten"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Lautlose Benachrichtigungen"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Unterhaltungen"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Alle lautlosen Benachrichtigungen löschen"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Benachrichtigungen durch \"Bitte nicht stören\" pausiert"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Jetzt starten"</string>
@@ -694,6 +700,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Anpassen"</string>
     <string name="notification_done" msgid="6215117625922713976">"Fertig"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Rückgängig machen"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g> – <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"Benachrichtigungseinstellungen"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"Optionen für spätere Erinnerung bei Benachrichtigungen"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 14da101..45ad302 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -391,6 +391,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"Το NFC είναι απενεργοποιημένο"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"Το NFC είναι ενεργοποιημένο"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Εγγραφή οθόνης"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Έναρξη"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Διακοπή"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Σύρετε προς τα επάνω για εναλλαγή των εφαρμογών"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Σύρετε προς τα δεξιά για γρήγορη εναλλαγή εφαρμογών"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Εναλλαγή επισκόπησης"</string>
@@ -471,6 +474,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Διαγραφή όλων"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Διαχείριση"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Ειδοποιήσεις σε σίγαση"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Συνομιλίες"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Διαγραφή όλων των ειδοποιήσεων σε σίγαση"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Οι ειδοποιήσεις τέθηκαν σε παύση από τη λειτουργία \"Μην ενοχλείτε\""</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Έναρξη τώρα"</string>
@@ -689,6 +693,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Προσαρμογή"</string>
     <string name="notification_done" msgid="6215117625922713976">"Τέλος"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Αναίρεση"</string>
+    <string name="demote" msgid="6225813324237153980">"Επισήμανση αυτής της ειδοποίησης ως μη συνομιλίας"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Αγαπημένη"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Μη αγαπημένη"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Σίγαση"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Κατάργηση σίγασης"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Εμφάνιση ως φούσκας"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Απενεργοποίηση φουσκών"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Προσθήκη στην αρχική οθόνη"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"στοιχεία ελέγχου ειδοποιήσεων"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"επιλογές αναβολής ειδοποιήσεων"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 5f38830..b6a5d15 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -391,6 +391,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC is disabled"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC is enabled"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Screen record"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Start"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Stop"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Swipe up to switch apps"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Drag right to quickly switch apps"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Toggle Overview"</string>
@@ -471,6 +474,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Manage"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Silent notifications"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversations"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Clear all silent notifications"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notifications paused by Do Not Disturb"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Start now"</string>
@@ -516,7 +520,7 @@
     <string name="monitoring_description_do_header_with_name" msgid="2696255132542779511">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> uses <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> to manage your device."</string>
     <string name="monitoring_description_do_body" msgid="7700878065625769970">"Your admin can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information."</string>
     <string name="monitoring_description_do_learn_more_separator" msgid="1467280496376492558">" "</string>
-    <string name="monitoring_description_do_learn_more" msgid="645149183455573790">"Find out more"</string>
+    <string name="monitoring_description_do_learn_more" msgid="645149183455573790">"Learn more"</string>
     <string name="monitoring_description_do_body_vpn" msgid="7699280130070502303">"You\'re connected to <xliff:g id="VPN_APP">%1$s</xliff:g>, which can monitor your network activity, including emails, apps and websites."</string>
     <string name="monitoring_description_vpn_settings_separator" msgid="8292589617720435430">" "</string>
     <string name="monitoring_description_vpn_settings" msgid="5264167033247632071">"Open VPN settings"</string>
@@ -689,6 +693,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Customise"</string>
     <string name="notification_done" msgid="6215117625922713976">"Done"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Undo"</string>
+    <string name="demote" msgid="6225813324237153980">"Mark this notification as not a conversation"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Favourite"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Unfavourite"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Mute"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Unmute"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Show as bubble"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Turn off bubbles"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Add to home screen"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"notification controls"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"notification snooze options"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index 1da1df3..e194e69 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -391,6 +391,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC is disabled"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC is enabled"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Screen record"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Start"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Stop"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Swipe up to switch apps"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Drag right to quickly switch apps"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Toggle Overview"</string>
@@ -471,6 +474,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Manage"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Silent notifications"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversations"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Clear all silent notifications"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notifications paused by Do Not Disturb"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Start now"</string>
@@ -516,7 +520,7 @@
     <string name="monitoring_description_do_header_with_name" msgid="2696255132542779511">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> uses <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> to manage your device."</string>
     <string name="monitoring_description_do_body" msgid="7700878065625769970">"Your admin can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information."</string>
     <string name="monitoring_description_do_learn_more_separator" msgid="1467280496376492558">" "</string>
-    <string name="monitoring_description_do_learn_more" msgid="645149183455573790">"Find out more"</string>
+    <string name="monitoring_description_do_learn_more" msgid="645149183455573790">"Learn more"</string>
     <string name="monitoring_description_do_body_vpn" msgid="7699280130070502303">"You\'re connected to <xliff:g id="VPN_APP">%1$s</xliff:g>, which can monitor your network activity, including emails, apps and websites."</string>
     <string name="monitoring_description_vpn_settings_separator" msgid="8292589617720435430">" "</string>
     <string name="monitoring_description_vpn_settings" msgid="5264167033247632071">"Open VPN settings"</string>
@@ -689,6 +693,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Customise"</string>
     <string name="notification_done" msgid="6215117625922713976">"Done"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Undo"</string>
+    <string name="demote" msgid="6225813324237153980">"Mark this notification as not a conversation"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Favourite"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Unfavourite"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Mute"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Unmute"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Show as bubble"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Turn off bubbles"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Add to home screen"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"notification controls"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"notification snooze options"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 5f38830..b6a5d15 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -391,6 +391,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC is disabled"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC is enabled"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Screen record"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Start"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Stop"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Swipe up to switch apps"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Drag right to quickly switch apps"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Toggle Overview"</string>
@@ -471,6 +474,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Manage"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Silent notifications"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversations"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Clear all silent notifications"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notifications paused by Do Not Disturb"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Start now"</string>
@@ -516,7 +520,7 @@
     <string name="monitoring_description_do_header_with_name" msgid="2696255132542779511">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> uses <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> to manage your device."</string>
     <string name="monitoring_description_do_body" msgid="7700878065625769970">"Your admin can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information."</string>
     <string name="monitoring_description_do_learn_more_separator" msgid="1467280496376492558">" "</string>
-    <string name="monitoring_description_do_learn_more" msgid="645149183455573790">"Find out more"</string>
+    <string name="monitoring_description_do_learn_more" msgid="645149183455573790">"Learn more"</string>
     <string name="monitoring_description_do_body_vpn" msgid="7699280130070502303">"You\'re connected to <xliff:g id="VPN_APP">%1$s</xliff:g>, which can monitor your network activity, including emails, apps and websites."</string>
     <string name="monitoring_description_vpn_settings_separator" msgid="8292589617720435430">" "</string>
     <string name="monitoring_description_vpn_settings" msgid="5264167033247632071">"Open VPN settings"</string>
@@ -689,6 +693,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Customise"</string>
     <string name="notification_done" msgid="6215117625922713976">"Done"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Undo"</string>
+    <string name="demote" msgid="6225813324237153980">"Mark this notification as not a conversation"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Favourite"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Unfavourite"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Mute"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Unmute"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Show as bubble"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Turn off bubbles"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Add to home screen"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"notification controls"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"notification snooze options"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 5f38830..b6a5d15 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -391,6 +391,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC is disabled"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC is enabled"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Screen record"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Start"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Stop"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Swipe up to switch apps"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Drag right to quickly switch apps"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Toggle Overview"</string>
@@ -471,6 +474,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Manage"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Silent notifications"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversations"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Clear all silent notifications"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notifications paused by Do Not Disturb"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Start now"</string>
@@ -516,7 +520,7 @@
     <string name="monitoring_description_do_header_with_name" msgid="2696255132542779511">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> uses <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> to manage your device."</string>
     <string name="monitoring_description_do_body" msgid="7700878065625769970">"Your admin can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information."</string>
     <string name="monitoring_description_do_learn_more_separator" msgid="1467280496376492558">" "</string>
-    <string name="monitoring_description_do_learn_more" msgid="645149183455573790">"Find out more"</string>
+    <string name="monitoring_description_do_learn_more" msgid="645149183455573790">"Learn more"</string>
     <string name="monitoring_description_do_body_vpn" msgid="7699280130070502303">"You\'re connected to <xliff:g id="VPN_APP">%1$s</xliff:g>, which can monitor your network activity, including emails, apps and websites."</string>
     <string name="monitoring_description_vpn_settings_separator" msgid="8292589617720435430">" "</string>
     <string name="monitoring_description_vpn_settings" msgid="5264167033247632071">"Open VPN settings"</string>
@@ -689,6 +693,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Customise"</string>
     <string name="notification_done" msgid="6215117625922713976">"Done"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Undo"</string>
+    <string name="demote" msgid="6225813324237153980">"Mark this notification as not a conversation"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Favourite"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Unfavourite"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Mute"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Unmute"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Show as bubble"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Turn off bubbles"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Add to home screen"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"notification controls"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"notification snooze options"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index fd3ff45..e3a569a 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -391,6 +391,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‏‎‎‎‎‏‏‎‏‏‎‎‎‎‎‎‎‏‏‎‏‎‏‎‏‎‎‎‏‎‎‏‎‏‏‏‏‎‏‏‎‏‎‎‎‏‏‏‎‏‎‏‎‏‎NFC‎‏‎‎‏‎"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‎‎‎‏‎‏‏‎‎‎‏‎‎‏‏‏‎‎‏‏‎‎‎‏‏‏‏‎‎‎‏‎‏‎‎‎‎‏‎‏‎‎‎‎‎‏‎‏‏‎‎‏‎‏‏‏‎NFC is disabled‎‏‎‎‏‎"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‏‏‏‏‏‎‎‏‎‎‏‏‎‎‏‎‎‏‏‏‎‎‏‏‏‎‎‏‏‏‏‏‎‎‏‎‏‎‏‏‏‏‎‏‏‏‏‎‎‏‎‏‏‎‏‏‎‎NFC is enabled‎‏‎‎‏‎"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‎‏‏‎‎‏‏‏‏‏‎‏‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‏‏‎‏‎‏‎‎‏‎‎‏‎‎‎Screen Record‎‏‎‎‏‎"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‏‏‎‏‏‎‏‎‏‎‎‎‏‏‎‎‎‏‏‎‎‎‎‏‎‏‎‏‎‎‎‏‏‏‎‎‏‏‏‎‏‏‏‎‎‎‏‎‏‏‎‎‏‎‎‏‎Start‎‏‎‎‏‎"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‏‎‎‎‎‎‎‏‎‎‎‏‎‏‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‎‎‏‎‏‎‎‎‎‎‎‎‎‏‏‏‎‏‎‏‏‏‎Stop‎‏‎‎‏‎"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‎‎‏‎‎‎‏‏‏‎‎‏‏‎‎‎‏‏‏‎‎‎‎‏‏‎‎‏‏‏‎‎‎‏‎‎‎‏‏‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎Swipe up to switch apps‎‏‎‎‏‎"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‏‎‏‎‎‎‎‏‎‎‏‎‎‏‎‏‎‎‏‏‎‎‎‎‎‎‏‎‎‎‎‏‏‎‏‎‏‏‏‏‏‎‏‏‎‎‎‎‎‎‏‎‎‎‎‎‎Drag right to quickly switch apps‎‏‎‎‏‎"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‎‎‎‎‏‎‎‎‏‏‏‏‎‎‎‎‎‏‏‎‎‏‏‎‏‏‎‏‎‏‎‎‏‎‏‏‏‎‎‏‏‎‎‏‏‏‎‎‏‎‎‎‏‏‎Toggle Overview‎‏‎‎‏‎"</string>
@@ -471,6 +474,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‎‏‏‎‏‎‏‎‏‎‏‏‏‎‏‎‎‎‏‎‎‎‎‎‏‎‏‎‏‏‏‏‎‏‏‎‎‏‎‎‏‏‎‎‏‎‎‎‏‏‏‏‏‎‎‏‎‎Clear all‎‏‎‎‏‎"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‏‏‏‎‏‎‏‏‏‎‎‏‏‏‏‏‎‏‎‏‏‎‎‏‎‎‎‏‎‎‎‎‏‏‎‎‏‏‎‏‏‏‏‏‎‏‏‏‏‏‎‎‎Manage‎‏‎‎‏‎"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‎‏‎‎‎‎‎‏‏‎‏‏‎‎‏‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‏‎‎‏‎‏‏‎‏‎‎‎‏‎‎‏‎‏‎‏‏‎‏‎‏‎‎Silent notifications‎‏‎‎‏‎"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‏‏‎‎‏‏‏‏‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‏‎‎‎‏‏‎‎‎‏‎‎‏‎‎‎‎‎‏‏‎‎‎‎‏‎‏‏‏‏‎‏‎Conversations‎‏‎‎‏‎"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‎‎‏‎‎‎‏‏‏‎‏‏‎‎‏‏‎‏‏‎‎‏‎‎‏‎‎‏‎‎‎‏‎‏‏‎‏‏‎‏‎‏‏‎‎‎‏‎‎‎‏‏‏‏‏‎Clear all silent notifications‎‏‎‎‏‎"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‎‎‎‏‏‎‏‎‏‏‏‎‏‎‏‏‏‎‏‎‎‎‎‎‏‏‎‏‎‏‎‎‏‎‏‏‎‎‎‎‎‏‎‏‎‏‏‎‎‏‎‏‎‏‎‎Notifications paused by Do Not Disturb‎‏‎‎‏‎"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‎‏‏‏‎‎‎‏‏‏‎‎‏‎‎‎‎‏‏‏‎‎‏‎‎‎‏‎‏‎‎‏‏‏‎‏‎‎‏‎‎‏‏‏‎‏‎‏‏‏‎‎‏‎‎‎‎Start now‎‏‎‎‏‎"</string>
@@ -689,6 +693,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‎‏‎‏‎‎‎‏‏‎‏‏‎‎‏‎‎‏‎‏‎‏‎‎‏‏‏‎‏‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‎‏‎Customize‎‏‎‎‏‎"</string>
     <string name="notification_done" msgid="6215117625922713976">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‏‎‎‎‎‎‎‏‎‎‎‏‎‎‎‏‎‎‎‏‏‎‏‎‎‏‏‏‎‏‎‎‏‏‎‎‏‎‎‎‏‏‏‏‎‎‏‎‏‏‏‏‎‎‎‎Done‎‏‎‎‏‎"</string>
     <string name="inline_undo" msgid="9026953267645116526">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‏‎‎‎‏‎‏‏‏‎‎‎‏‎‏‎‏‏‏‎‎‎‏‎‏‎‎‎‎‎‏‎‎‏‏‏‏‏‎‎‎‎‎‏‏‎‏‏‏‎‎Undo‎‏‎‎‏‎"</string>
+    <string name="demote" msgid="6225813324237153980">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‏‏‎‎‏‏‎‏‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‎‎‎‏‏‎‏‎‎‎‎‏‏‎‎‏‎‏‎‎‏‏‎‏‎‏‏‏‏‎‎‎Mark this notification as not a conversation‎‏‎‎‏‎"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‏‎‎‎‎‏‏‏‎‏‏‎‎‏‎‏‏‏‏‎‏‏‏‏‏‏‎‎‎‏‎‎‏‎‏‎‏‎‎‏‎‎‏‎‎‏‎‎‎‏‎‏‎Favorite‎‏‎‎‏‎"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‏‎‎‏‎‎‏‏‏‏‏‎‎‎‎‎‎‏‎‏‏‎‏‎‏‏‎‏‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎Unfavorite‎‏‎‎‏‎"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‎‏‎‏‎‎‎‎‎‎‎‏‎‏‏‎‏‏‎‎‏‏‏‎‎‎‏‎‎‎‏‎‎‎‏‏‎‏‎‎‏‏‎‏‏‎‏‏‏‏‎‏‏‎‏‏‏‎Mute‎‏‎‎‏‎"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‎‏‏‎‎‏‏‏‏‎‎‎‎‎‏‏‎‏‏‏‎‏‏‎‎‏‎‏‏‎‎‎‏‏‏‎‏‎‏‎‏‎‎‎‏‎‏‏‎‏‏‏‏‏‎‎Unmute‎‏‎‎‏‎"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‎‎‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‏‎‎‎‏‎‎‎‏‎‏‏‎‎‎‏‏‏‏‏‎‏‎‏‏‏‎‏‎‏‏‎‎‏‏‎‎‎Show as bubble‎‏‎‎‏‎"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‏‎‏‏‎‎‏‎‎‎‏‏‏‎‎‏‏‏‎‏‏‎‏‎‎‎‏‎‎‎‏‎‏‎‏‎‏‎‎‎‎‎‏‎Turn off bubbles‎‏‎‎‏‎"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‏‎‏‏‎‏‏‏‏‏‏‎‎‎‎‎‎‎‎‎‎‎‏‏‎‎‎‏‎‎‏‎‏‏‏‏‏‏‏‎‏‎‎‎‎‎‎‎‏‎‏‏‏‎Add to home screen‎‏‎‎‏‎"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‏‏‎‎‎‏‎‏‏‎‎‎‎‏‎‎‎‎‎‏‏‎‎‎‎‏‏‎‏‏‎‏‎‏‏‎‏‎‏‏‎‏‏‏‎‏‏‎‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ ‎‏‎‎‏‏‎<xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‎‏‏‏‎‏‎‏‏‎‎‎‏‎‏‏‏‎‏‏‏‏‎‎‏‎‎‏‏‎‏‎‎‏‏‎‏‏‎‏‎‎‎‎‏‏‎‎‎‏‎‏‏‏‎‎notification controls‎‏‎‎‏‎"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‎‎‏‎‎‎‎‏‎‏‎‏‏‎‎‎‏‎‏‎‏‎‏‎‏‎‎‎‎‏‎‏‎‎‏‎‎‎‏‏‏‎‏‏‎‎‏‏‏‏‏‏‎‎‎notification snooze options‎‏‎‎‏‎"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index eb27998f..255db92 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -391,6 +391,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"La tecnología NFC está inhabilitada"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"La tecnología NFC está habilitada"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Grabar pantalla"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Iniciar"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Detener"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Desliza el dedo hacia arriba para cambiar de app"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Arrastra a la derecha para cambiar aplicaciones rápidamente"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Ocultar o mostrar Recientes"</string>
@@ -471,6 +474,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Borrar todo"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Administrar"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Notificaciones silenciosas"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversaciones"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Borrar todas las notificaciones silenciosas"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notificaciones pausadas por el modo \"No interrumpir\""</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Comenzar ahora"</string>
@@ -689,6 +693,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Personalizar"</string>
     <string name="notification_done" msgid="6215117625922713976">"Listo"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Deshacer"</string>
+    <string name="demote" msgid="6225813324237153980">"Marcar que la notificación no es una conversación"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Marcar como favorita"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Borrar de las favoritas"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Silenciar"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Dejar de silenciar"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Mostrar como cuadro"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Desactivar cuadros"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Agregar a la pantalla principal"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g> de <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"controles de notificación"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"opciones para posponer notificaciones"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 731d727..d1d959c 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -389,8 +389,11 @@
     <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Al anochecer"</string>
     <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Hasta el amanecer"</string>
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
-    <string name="quick_settings_nfc_off" msgid="3465000058515424663">"La conexión NFC está inhabilitada"</string>
-    <string name="quick_settings_nfc_on" msgid="1004976611203202230">"La conexión NFC está habilitada"</string>
+    <string name="quick_settings_nfc_off" msgid="3465000058515424663">"El NFC está desactivado"</string>
+    <string name="quick_settings_nfc_on" msgid="1004976611203202230">"El NFC está activado"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Grabación de la pantalla"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Inicio"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Detener"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Desliza el dedo hacia arriba para cambiar de aplicación"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Arrastra hacia la derecha para cambiar rápidamente de aplicación"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Mostrar u ocultar aplicaciones recientes"</string>
@@ -471,6 +474,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Borrar todo"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Gestionar"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Notificaciones silenciadas"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversaciones"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Borrar todas las notificaciones silenciadas"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notificaciones pausadas por el modo No molestar"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Iniciar ahora"</string>
@@ -689,6 +693,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Personalizar"</string>
     <string name="notification_done" msgid="6215117625922713976">"Listo"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Deshacer"</string>
+    <string name="demote" msgid="6225813324237153980">"Marcar esta notificación como que no es una conversación"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Favorita"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Quitar de favoritos"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Silenciar"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Activar sonido"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Mostrar como burbuja"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Desactivar las burbujas"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Añadir a la pantalla de inicio"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g> de <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"Controles de las notificaciones"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"Opciones para posponer las notificaciones"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index ab86e03..6f69a17 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC on keelatud"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC on lubatud"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Rakenduste vahetamiseks pühkige üles"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Lohistage paremale, et rakendusi kiiresti vahetada"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Lehe Ülevaade sisse- ja väljalülitamine"</string>
@@ -471,6 +477,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Tühjenda kõik"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Haldamine"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Hääletud märguanded"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Vestlused"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Kustuta kõik hääletud märguanded"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Režiim Mitte segada peatas märguanded"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Alusta kohe"</string>
@@ -689,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Kohandamine"</string>
     <string name="notification_done" msgid="6215117625922713976">"Valmis"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Võta tagasi"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"märguannete juhtnupud"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"märguannete edasilükkamise valikud"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 18ac031..1b89c5a 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -57,12 +57,12 @@
     <string name="label_view" msgid="6815442985276363364">"Ikusi"</string>
     <string name="always_use_device" msgid="210535878779644679">"Ireki <xliff:g id="APPLICATION">%1$s</xliff:g> <xliff:g id="USB_DEVICE">%2$s</xliff:g> konektatzen den guztietan"</string>
     <string name="always_use_accessory" msgid="1977225429341838444">"Ireki <xliff:g id="APPLICATION">%1$s</xliff:g> <xliff:g id="USB_ACCESSORY">%2$s</xliff:g> konektatzen den guztietan"</string>
-    <string name="usb_debugging_title" msgid="8274884945238642726">"USB arazketa onartu?"</string>
+    <string name="usb_debugging_title" msgid="8274884945238642726">"USB bidezko arazketa onartu?"</string>
     <string name="usb_debugging_message" msgid="5794616114463921773">"Ordenagailuaren RSA gakoaren erreferentzia-gako digitala hau da:\n<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string>
     <string name="usb_debugging_always" msgid="4003121804294739548">"Eman beti ordenagailu honetatik arazteko baimena"</string>
     <string name="usb_debugging_allow" msgid="1722643858015321328">"Baimendu"</string>
-    <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Ez da onartzen USB arazketa"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Gailu honetan saioa hasita daukan erabiltzaileak ezin du aktibatu USB arazketa. Eginbide hori erabiltzeko, aldatu erabiltzaile nagusira."</string>
+    <string name="usb_debugging_secondary_user_title" msgid="7843050591380107998">"Ez da onartzen USB bidezko arazketa"</string>
+    <string name="usb_debugging_secondary_user_message" msgid="3740347841470403244">"Gailu honetan saioa hasita daukan erabiltzaileak ezin du aktibatu USB bidezko arazketa. Eginbide hori erabiltzeko, aldatu erabiltzaile nagusira."</string>
     <string name="usb_contaminant_title" msgid="894052515034594113">"Desgaitu egin da USB ataka"</string>
     <string name="usb_contaminant_message" msgid="7730476585174719805">"USB ataka desgaitu egin da gailua likido edo zikinkeriengandik babesteko, eta ez du hautemango osagarririk.\n\nJakinarazpen bat jasoko duzu USB ataka berriz erabiltzeko moduan dagoenean."</string>
     <string name="usb_port_enabled" msgid="531823867664717018">"USB ataka gaitu da kargagailuak eta osagarriak hautemateko"</string>
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"Desgaituta dago NFC"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"Gaituta dago NFC"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Egin gora aplikazioa aldatzeko"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Arrastatu eskuinera aplikazioa azkar aldatzeko"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Aldatu ikuspegi orokorra"</string>
@@ -471,6 +477,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Garbitu guztiak"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Kudeatu"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Soinurik gabeko jakinarazpenak"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Elkarrizketak"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Garbitu soinurik gabeko jakinarazpen guztiak"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Ez molestatzeko moduak pausatu egin ditu jakinarazpenak"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Hasi"</string>
@@ -689,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Pertsonalizatu"</string>
     <string name="notification_done" msgid="6215117625922713976">"Eginda"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Desegin"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"jakinarazpena kontrolatzeko aukerak"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"jakinarazpena atzeratzeko aukerak"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 77164b7..2b76557 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"‏NFC غیرفعال است"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"‏NFC فعال است"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"برای تغییر برنامه‌ها،‌ تند به بالا بکشید"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"برای جابه‌جایی سریع میان برنامه‌ها، به چپ بکشید"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"تغییر وضعیت نمای کلی"</string>
@@ -471,6 +477,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"پاک کردن همه موارد"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"مدیریت"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"اعلان‌های بی‌صدا"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"مکالمه‌ها"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"پاک کردن همه اعلان‌های بی‌صدا"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"اعلان‌ها توسط «مزاحم نشوید» موقتاً متوقف شدند"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"اکنون شروع شود"</string>
@@ -689,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"سفارشی کردن"</string>
     <string name="notification_done" msgid="6215117625922713976">"تمام"</string>
     <string name="inline_undo" msgid="9026953267645116526">"واگرد"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"کنترل‌های اعلان"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"گزینه‌های تعویق اعلان"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index e70b4d5..9b76f39 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC on poistettu käytöstä"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC on käytössä"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Vaihda sovellusta pyyhkäisemällä ylös"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Vaihda sovellusta nopeasti vetämällä oikealle"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Näytä/piilota viimeisimmät"</string>
@@ -471,6 +477,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Poista kaikki"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Muuta asetuksia"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Hiljaiset ilmoitukset"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Keskustelut"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Tyhjennä kaikki hiljaiset ilmoitukset"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Älä häiritse ‑tila keskeytti ilmoitukset"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Aloita nyt"</string>
@@ -689,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Muokkaa"</string>
     <string name="notification_done" msgid="6215117625922713976">"Valmis"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Kumoa"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"Ilmoitusten hallinta"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"Ilmoitusten torkkuasetukset"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index ead29c0..55eb39e 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -391,6 +391,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC désactivée"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC activée"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Enregistrement d\'écran"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Démarrer"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Arrêter"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Balayez vers le haut pour changer d\'application"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Balayez l\'écran vers la droite pour changer rapidement d\'application"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Basculer l\'aperçu"</string>
@@ -471,6 +474,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Tout effacer"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Gérer"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Notifications silencieuses"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversations"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Effacer toutes les notifications silencieuses"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Les notifications sont suspendues par le mode Ne pas déranger"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Commencer"</string>
@@ -689,6 +693,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Personnaliser"</string>
     <string name="notification_done" msgid="6215117625922713976">"Terminé"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Annuler"</string>
+    <string name="demote" msgid="6225813324237153980">"Marquer cette notification comme n\'étant pas une conversation"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Ajouter aux favoris"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Supprimer des favoris"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Ignorer"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Réactiver"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Afficher comme bulle"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Désactiver les bulles"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Ajouter à l\'écran d\'accueil"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"paramètres des notifications"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"options de répétition des notifications"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index b701276..5b12612 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -389,8 +389,11 @@
     <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Activé la nuit"</string>
     <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Jusqu\'à l\'aube"</string>
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
-    <string name="quick_settings_nfc_off" msgid="3465000058515424663">"La technologie NFC est désactivée"</string>
+    <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC désactivée"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"La technologie NFC est activée"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Enregistrement de l\'écran"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Démarrer"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Arrêter"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Balayer l\'écran vers le haut pour changer d\'application"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Déplacer vers la droite pour changer rapidement d\'application"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Activer/Désactiver l\'aperçu"</string>
@@ -471,6 +474,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Tout effacer"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Gérer"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Notifications silencieuses"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversations"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Effacer toutes les notifications silencieuses"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notifications suspendues par le mode Ne pas déranger"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Commencer"</string>
@@ -689,6 +693,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Personnaliser"</string>
     <string name="notification_done" msgid="6215117625922713976">"Terminé"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Annuler"</string>
+    <string name="demote" msgid="6225813324237153980">"Marquer cette notification comme n\'étant pas une conversation"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Ajouter aux favoris"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Supprimer des favoris"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Couper le son"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Réactiver le son"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Afficher sous forme de bulle"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Désactiver les bulles"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Ajouter à l\'écran d\'accueil"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> : <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"paramètres des notifications"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"options de répétition des notifications"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 464684e..74ee202 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -391,6 +391,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"A opción NFC está desactivada"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"A opción NFC está activada"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Gravación da pantalla"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Iniciar"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Deter"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Pasar o dedo cara arriba para cambiar de aplicación"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Arrastra cara á dereita para cambiar de aplicacións rapidamente"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Activar/desactivar Visión xeral"</string>
@@ -465,13 +468,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Desactivar a función Aforro de batería"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> terá acceso a toda a información visible na pantalla ou reproducida desde o teu dispositivo mentres graves ou emitas contido. Isto inclúe información como contrasinais, detalles de pago, fotos, mensaxes e o audio que reproduzas."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"O servizo que proporciona esta función terá acceso a toda a información visible na pantalla ou reproducida desde o teu dispositivo mentres graves ou emitas contido. Isto inclúe información como contrasinais, detalles de pago, fotos, mensaxes e o audio que reproduzas."</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Queres iniciar a gravación ou a emisión?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Queres comezar a gravar ou emitir contido con <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"Non mostrar outra vez"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Eliminar todas"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Xestionar"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Notificacións silenciadas"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversas"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Borra todas as notificacións silenciadas"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"O modo Non molestar puxo en pausa as notificacións"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Iniciar agora"</string>
@@ -690,6 +693,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Personalizar"</string>
     <string name="notification_done" msgid="6215117625922713976">"Feito"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Desfacer"</string>
+    <string name="demote" msgid="6225813324237153980">"Marcar que esta notificación non é unha conversa"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Engadir a favoritos"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Quitar de favoritos"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Silenciar"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Activar son"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Mostrar como unha burbulla"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Desactivar burbullas"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Engadir á pantalla de inicio"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g> de <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"controis de notificacións"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"opcións para adiar notificacións"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index de6b3793..4b5dd25 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -37,7 +37,7 @@
     <string name="battery_saver_confirmation_title_generic" msgid="2299231884234959849">"બૅટરી સેવર વિશે"</string>
     <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"ચાલુ કરો"</string>
     <string name="battery_saver_start_action" msgid="4553256017945469937">"બૅટરી સેવર ચાલુ કરો"</string>
-    <string name="status_bar_settings_settings_button" msgid="534331565185171556">"સેટિંગ્સ"</string>
+    <string name="status_bar_settings_settings_button" msgid="534331565185171556">"સેટિંગ"</string>
     <string name="status_bar_settings_wifi_button" msgid="7243072479837270946">"વાઇ-ફાઇ"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"ઑટો રોટેટ સ્ક્રીન"</string>
     <string name="status_bar_settings_mute_label" msgid="914392730086057522">"મ્યૂટ કરો"</string>
@@ -224,7 +224,7 @@
     <string name="accessibility_desc_notification_shade" msgid="5355229129428759989">"નોટિફિકેશન શેડ."</string>
     <string name="accessibility_desc_quick_settings" msgid="4374766941484719179">"ઝડપી સેટિંગ્સ."</string>
     <string name="accessibility_desc_lock_screen" msgid="5983125095181194887">"લૉક સ્ક્રીન."</string>
-    <string name="accessibility_desc_settings" msgid="6728577365389151969">"સેટિંગ્સ"</string>
+    <string name="accessibility_desc_settings" msgid="6728577365389151969">"સેટિંગ"</string>
     <string name="accessibility_desc_recent_apps" msgid="1748675199348914194">"ઝલક."</string>
     <string name="accessibility_desc_work_lock" msgid="4355620395354680575">"કાર્ય લૉક સ્ક્રીન"</string>
     <string name="accessibility_desc_close" msgid="8293708213442107755">"બંધ કરો"</string>
@@ -295,7 +295,7 @@
     </plurals>
     <string name="notification_summary_message_format" msgid="5158219088501909966">"<xliff:g id="CONTACT_NAME">%1$s</xliff:g>: <xliff:g id="MESSAGE_CONTENT">%2$s</xliff:g>"</string>
     <string name="status_bar_notification_inspect_item_title" msgid="6818779631806163080">"સૂચનાઓની સેટિંગ્સ"</string>
-    <string name="status_bar_notification_app_settings_title" msgid="5050006438806013903">"<xliff:g id="APP_NAME">%s</xliff:g> સેટિંગ્સ"</string>
+    <string name="status_bar_notification_app_settings_title" msgid="5050006438806013903">"<xliff:g id="APP_NAME">%s</xliff:g> સેટિંગ"</string>
     <string name="accessibility_rotation_lock_off" msgid="3880436123632448930">"સ્ક્રીન આપમેળે ફરશે."</string>
     <string name="accessibility_rotation_lock_on_landscape" msgid="936972553861524360">"સ્ક્રીન લેન્ડસ્કેપ ઓરિએન્ટેશનમાં લૉક કરેલ છે."</string>
     <string name="accessibility_rotation_lock_on_portrait" msgid="2356633398683813837">"સ્ક્રીન પોટ્રેટ ઓરિએન્ટેશનમાં લૉક કરેલ છે."</string>
@@ -333,7 +333,7 @@
     <string name="quick_settings_media_device_label" msgid="8034019242363789941">"મીડિયા ઉપકરણ"</string>
     <string name="quick_settings_rssi_label" msgid="3397615415140356701">"RSSI"</string>
     <string name="quick_settings_rssi_emergency_only" msgid="7499207215265078598">"ફક્ત કટોકટીના કૉલ્સ"</string>
-    <string name="quick_settings_settings_label" msgid="2214639529565474534">"સેટિંગ્સ"</string>
+    <string name="quick_settings_settings_label" msgid="2214639529565474534">"સેટિંગ"</string>
     <string name="quick_settings_time_label" msgid="3352680970557509303">"સમય"</string>
     <string name="quick_settings_user_label" msgid="1253515509432672496">"હું"</string>
     <string name="quick_settings_user_title" msgid="8673045967216204537">"વપરાશકર્તા"</string>
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC અક્ષમ કરેલ છે"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC સક્ષમ કરેલ છે"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"ઍપ સ્વિચ કરવા માટે ઉપરની તરફ સ્વાઇપ કરો"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"ઍપને ઝડપથી સ્વિચ કરવા માટે જમણે ખેંચો"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"ઝલકને ટૉગલ કરો"</string>
@@ -471,6 +477,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"બધુ સાફ કરો"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"મેનેજ કરો"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"સાઇલન્ટ નોટિફિકેશન"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"વાતચીત"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"બધા સાઇલન્ટ નોટિફિકેશન સાફ કરો"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"ખલેલ પાડશો નહીં દ્વારા થોભાવેલ નોટિફિકેશન"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"હવે પ્રારંભ કરો"</string>
@@ -689,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"કસ્ટમાઇઝ કરો"</string>
     <string name="notification_done" msgid="6215117625922713976">"થઈ ગયું"</string>
     <string name="inline_undo" msgid="9026953267645116526">"રદ કરો"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"સૂચના નિયંત્રણો"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"સૂચના સ્નૂઝ કરવાના વિકલ્પો"</string>
@@ -885,7 +908,7 @@
     <string name="notification_channel_general" msgid="4384774889645929705">"સામાન્ય સંદેશા"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"સ્ટોરેજ"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"હિન્ટ"</string>
-    <string name="instant_apps" msgid="8337185853050247304">"ઝટપટ ઍપ્લિકેશનો"</string>
+    <string name="instant_apps" msgid="8337185853050247304">"ઝટપટ ઍપ્લિકેશન"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> ચાલી રહી છે"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"ઍપ ઇન્સ્ટૉલ કર્યા વિના ખુલી જાય છે."</string>
     <string name="instant_apps_message_with_help" msgid="1816952263531203932">"ઍપ ઇન્સ્ટૉલ કર્યા વિના ખુલી જાય છે. વધુ જાણવા માટે ટૅપ કરો."</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 706f8b3..c22d234 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"एनएफ़सी"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC बंद है"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC चालू है"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"ऐप्लिकेशन बदलने के लिए ऊपर स्वाइप करें"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"ऐप्लिकेशन को झटपट स्विच करने के लिए उसे दाईं ओर खींचें और छोड़ें"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"खास जानकारी टॉगल करें"</string>
@@ -465,13 +471,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"बैटरी सेवर बंद करें"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"रिकॉर्ड या कास्ट करते समय, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> आपकी स्क्रीन पर दिख रही या आपके डिवाइस पर चलाई जा रही जानकारी ऐक्सेस कर सकता है. इसमें पासवर्ड, पैसे चुकाने का ब्यौरा, फ़ोटो, मैसेज, और चलाए गए ऑडियो जैसी जानकारी शामिल है."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"रिकॉर्ड या कास्ट करते समय, वह सेवा जो यह फ़ंक्शन उपलब्ध कराती है, आपके डिवाइस पर चलाई जा रही या स्क्रीन पर दिख रही जानकारी को ऐक्सेस कर सकती है. इसमें पासवर्ड, पैसे चुकाने का ब्यौरा, फ़ोटो, मैसेज, और चलाए गए ऑडियो जैसी जानकारी शामिल है."</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"रिकॉर्डिंग या कास्ट करना शुरू करें?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> का इस्तेमाल करके रिकॉर्ड और कास्ट करना शुरू करें?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"फिर से न दिखाएं"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"सभी को हटाएं"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"प्रबंधित करें"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"बिना आवाज़ या वाइब्रेशन वाली सूचनाएं"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"बातचीत"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"बिना आवाज़ की सभी सूचनाएं हटाएं"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"\'परेशान न करें\' सुविधा के ज़रिए कुछ समय के लिए सूचनाएं दिखाना रोक दिया गया है"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"अब शुरू करें"</string>
@@ -690,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"पसंद के मुताबिक बनाएं"</string>
     <string name="notification_done" msgid="6215117625922713976">"हो गया"</string>
     <string name="inline_undo" msgid="9026953267645116526">"पहले जैसा करें"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"सूचना नियंत्रण"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"सूचना को स्नूज़ (थोड़ी देर के लिए चुप करना) करने के विकल्प"</string>
@@ -886,7 +908,7 @@
     <string name="notification_channel_general" msgid="4384774889645929705">"सामान्य संदेश"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"जगह"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"संकेत"</string>
-    <string name="instant_apps" msgid="8337185853050247304">"इंस्टेंट ऐप"</string>
+    <string name="instant_apps" msgid="8337185853050247304">"झटपट ऐप्लिकेशन"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> चल रहा है"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"ऐप्लिकेशन इंस्टॉल किए बिना ही खुल गया है."</string>
     <string name="instant_apps_message_with_help" msgid="1816952263531203932">"ऐप्लिकेशन इंस्टॉल किए बिना ही खुल गया है. ज़्यादा जानने के लिए टैप करें."</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index bf027fb..1286709 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -393,6 +393,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC je onemogućen"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC je omogućen"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Snimač zaslona"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Početak"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Zaustavi"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Prijeđite prstom prema gore da biste promijenili aplikaciju"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Povucite udesno da biste brzo promijenili aplikaciju"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Uključivanje/isključivanje pregleda"</string>
@@ -474,6 +477,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Izbriši sve"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Upravljajte"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Bešumne obavijesti"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Razgovori"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Izbriši sve bešumne obavijesti"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Značajka Ne uznemiravaj pauzirala je Obavijesti"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Započni sad"</string>
@@ -692,6 +696,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Prilagodi"</string>
     <string name="notification_done" msgid="6215117625922713976">"Gotovo"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Poništi"</string>
+    <string name="demote" msgid="6225813324237153980">"Označi da ova obavijest nije razgovor"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Favorit"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Nije omiljeno"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Zanemari"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Opozovi"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Prikaži u oblačiću"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Isključi oblačiće"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Dodavanje na početni zaslon"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g> za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"kontrole obavijesti"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"opcije odgode obavijesti"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 90c8a1a..a3b6d3f 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -391,6 +391,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"Az NFC ki van kapcsolva"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"Az NFC be van kapcsolva"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Képernyő rögzítése"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Kezdés"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Leállítás"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Váltás az alkalmazások között felfelé csúsztatással"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Húzza jobbra az ujját az alkalmazások közötti gyors váltáshoz"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Áttekintés be- és kikapcsolása"</string>
@@ -471,6 +474,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Az összes törlése"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Kezelés"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Néma értesítések"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Beszélgetések"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Az összes néma értesítés törlése"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Ne zavarjanak funkcióval szüneteltetett értesítések"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Indítás most"</string>
@@ -689,6 +693,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Személyre szabás"</string>
     <string name="notification_done" msgid="6215117625922713976">"Kész"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Visszavonás"</string>
+    <string name="demote" msgid="6225813324237153980">"Értesítés megjelölése mint nem beszélgetés"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Kedvenc"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Törlés a kedvencek közül"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Némítás"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Némítás feloldása"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Megjelenítés buborékként"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Buborékok kikapcsolása"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Hozzáadás a kezdőképernyőhöz"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> – <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"értesítésvezérlők"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"értesítések halasztási beállításai"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index f25f6ae..5c73ed1 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC-ն անջատված է"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC-ն միացված է"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Սահեցրեք վերև՝ մյուս հավելվածին անցնելու համար"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Քաշեք աջ՝ հավելվածների միջև անցնելու համար"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Միացնել/անջատել համատեսքը"</string>
@@ -465,13 +471,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Անջատել մարտկոցի տնտեսումը"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"Ձայնագրման և հեռարձակման ընթացքում <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> հավելվածին հասանելի կլինեն ձեր սարքի էկրանին ցուցադրվող տեղեկությունները և ձեր սարքով նվագարկվող նյութերը։ Սա ներառում է այնպիսի տեղեկություններ, ինչպիսիք են, օրինակ, գաղտնաբառերը, վճարային տվյալները, լուսանկարները, հաղորդագրությունները և նվագարկվող աուդիո ֆայլերը։"</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Ձայնագրման և հեռարձակման ընթացքում ծառայությունների մատակարարին հասանելի կլինեն ձեր սարքի էկրանին ցուցադրվող տեղեկությունները և ձեր սարքով նվագարկվող նյութերը։ Սա ներառում է այնպիսի տեղեկություններ, ինչպիսիք են, օրինակ, գաղտնաբառերը, վճարային տվյալները, լուսանկարները, հաղորդագրությունները և նվագարկվող աուդիո ֆայլերը։"</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Սկսե՞լ ձայնագրումը կամ հեռարձակումը"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Սկսե՞լ ձայնագրումը կամ հեռարձակումը <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> հավելվածով"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"Այլևս ցույց չտալ"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Մաքրել բոլորը"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Կառավարել"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Անձայն ծանուցումներ"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Խոսակցություններ"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Ջնջել բոլոր անձայն ծանուցումները"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Ծանուցումները չեն ցուցադրվի «Չանհանգստացնել» ռեժիմում"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Սկսել հիմա"</string>
@@ -690,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Կարգավորել"</string>
     <string name="notification_done" msgid="6215117625922713976">"Պատրաստ է"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Հետարկել"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"ծանուցման կառավարներ"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"ծանուցման հետաձգման ընտրանքներ"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 835725f..bad3e67 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -391,6 +391,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC dinonaktifkan"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC diaktifkan"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Rekaman Layar"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Mulai"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Berhenti"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Geser ke atas untuk beralih aplikasi"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Tarik ke kanan untuk beralih aplikasi dengan cepat"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Aktifkan Ringkasan"</string>
@@ -465,13 +468,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Nonaktifkan Penghemat Baterai"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> akan memiliki akses ke semua informasi yang terlihat di layar atau diputar dari perangkat saat merekam atau melakukan transmisi. Ini mencakup informasi seperti sandi, detail pembayaran, foto, pesan, dan audio yang Anda putar."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Layanan yang menyediakan fungsi ini akan memiliki akses ke semua informasi yang terlihat di layar atau diputar dari perangkat saat merekam atau melakukan transmisi. Ini mencakup informasi seperti sandi, detail pembayaran, foto, pesan, dan audio yang Anda putar."</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Mulai merekam atau melakukan transmisi?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Mulai merekam atau melakukan transmisi dengan <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"Jangan tampilkan lagi"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Hapus semua"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Kelola"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Notifikasi senyap"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Percakapan"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Hapus semua notifikasi senyap"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notifikasi dijeda oleh mode Jangan Ganggu"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Mulai sekarang"</string>
@@ -690,6 +693,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Sesuaikan"</string>
     <string name="notification_done" msgid="6215117625922713976">"Selesai"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Urungkan"</string>
+    <string name="demote" msgid="6225813324237153980">"Tandai notifikasi ini sebagai bukan percakapan"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Favorit"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Batal favoritkan"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Bisukan"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Bunyikan"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Tampilkan sebagai balon"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Nonaktifkan balon"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Tambahkan ke layar utama"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"kontrol notifikasi"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"opsi tunda notifikasi"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 95e4010..68f04fb 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -391,6 +391,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"Slökkt á NFC"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"Kveikt á NFC"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Skjáupptaka"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Hefja"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Stöðva"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Strjúktu upp til að skipta á milli forrita"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Dragðu til hægri til að skipta hratt á milli forrita"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Kveikja/slökkva á yfirliti"</string>
@@ -471,6 +474,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Hreinsa allt"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Stjórna"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Þöglar tilkynningar"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Samtöl"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Hreinsa allar þöglar tilkynningar"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Hlé gert á tilkynningum þar sem stillt er á „Ónáðið ekki“"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Byrja núna"</string>
@@ -689,6 +693,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Sérsníða"</string>
     <string name="notification_done" msgid="6215117625922713976">"Lokið"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Afturkalla"</string>
+    <string name="demote" msgid="6225813324237153980">"Merkja þessa tilkynningu sem „ekki samtal“"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Eftirlæti"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Fjarlægja úr eftirlæti"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Þagga"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Hætta að þagga"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Sýna sem blöðru"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Slökkva á blöðrum"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Bæta á heimaskjá"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"tilkynningastýringar"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"þöggunarstillingar tilkynninga"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 4cec980..bdf93fa 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -391,6 +391,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC non attiva"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC attiva"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Registrazione dello schermo"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Inizia"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Interrompi"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Scorri verso l\'alto per passare ad altre app"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Trascina verso destra per cambiare velocemente app"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Attiva/disattiva la panoramica"</string>
@@ -471,6 +474,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Cancella tutto"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Gestisci"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Notifiche silenziose"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversazioni"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Cancella tutte le notifiche silenziose"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notifiche messe in pausa in base alla modalità Non disturbare"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Avvia adesso"</string>
@@ -689,6 +693,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Personalizza"</string>
     <string name="notification_done" msgid="6215117625922713976">"Fine"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Annulla"</string>
+    <string name="demote" msgid="6225813324237153980">"Contrassegna questa notifica come \"non è una conversazione\""</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Aggiungi ai preferiti"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Rimuovi dai preferiti"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Disattiva"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Riattiva"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Mostra sotto forma di fumetto"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Disattiva i fumetti"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Aggiungi a schermata Home"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"gestione delle notifiche"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"opzioni di posticipazione notifiche"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 949c8db..9cfd0b3 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -395,6 +395,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"‏NFC מושבת"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"‏NFC מופעל"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"יש להחליק מעלה כדי להחליף אפליקציות"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"יש לגרור ימינה כדי לעבור במהירות בין אפליקציות"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"החלפת מצב של מסכים אחרונים"</string>
@@ -477,6 +483,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ניקוי הכל"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"ניהול"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"התראות שקטות"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"שיחות"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"ניקוי כל ההתראות השקטות"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"התראות הושהו על ידי מצב \'נא לא להפריע\'"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"התחל כעת"</string>
@@ -695,6 +702,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"התאמה אישית"</string>
     <string name="notification_done" msgid="6215117625922713976">"סיום"</string>
     <string name="inline_undo" msgid="9026953267645116526">"ביטול"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"בקרת התראות"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"אפשרויות של דחיית התראות לטיפול בהמשך"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 2f1ad09..0c57372 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC は無効です"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC は有効です"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"アプリを切り替えるには上にスワイプ"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"右にドラッグするとアプリを素早く切り替えることができます"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"概要を切り替え"</string>
@@ -465,13 +471,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"バッテリー セーバーを OFF"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>は、記録中やキャスト中に画面上に表示またはデバイスから再生されるすべての情報にアクセスできます。これには、パスワード、お支払いの詳細、写真、メッセージ、再生される音声などの情報が含まれます。"</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"この機能を提供するサービスは、記録中やキャスト中に画面上に表示またはデバイスから再生されるすべての情報にアクセスできます。これには、パスワード、お支払いの詳細、写真、メッセージ、再生される音声などの情報が含まれます。"</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"記録やキャストを開始しますか?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>で記録やキャストを開始しますか?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"次回から表示しない"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"すべて消去"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"管理"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"サイレント通知"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"会話"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"サイレント通知がすべて消去されます"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"サイレント モードにより通知は一時停止中です"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"今すぐ開始"</string>
@@ -690,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"カスタマイズ"</string>
     <string name="notification_done" msgid="6215117625922713976">"完了"</string>
     <string name="inline_undo" msgid="9026953267645116526">"元に戻す"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"通知管理"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"通知スヌーズ設定"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 7754572..a3827d2 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC გათიშულია"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC ჩართულია"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"გადაფურცლეთ ზემოთ აპების გადასართავად"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"აპების სწრაფად გადასართავად ჩავლებით გადაიტანეთ მარჯვნივ"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"მიმოხილვის გადართვა"</string>
@@ -465,13 +471,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"ბატარეის დამზოგის გამორთვა"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>-ს ექნება წვდომა ყველა ინფორმაციაზე, რომელიც თქვენს ეკრანზე გამოჩნდება ან თქვენს მოწყობილობაზე დაიკვრება ჩაწერის ან ტრანსლირების განმავლობაში. აღნიშნული მოიცავს ისეთ ინფორმაციას, როგორიც არის პაროლები, გადახდის დეტალები, ფოტოები, შეტყობინებები და თქვენ მიერ დაკრული აუდიო."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"ამ ფუნქციის მომწოდებელ სერვისს ექნება წვდომა ყველა ინფორმაციაზე, რომელიც თქვენს ეკრანზე გამოჩნდება ან თქვენს მოწყობილობაზე დაიკვრება ჩაწერის ან ტრანსლირების განმავლობაში. აღნიშნული მოიცავს ისეთ ინფორმაციას, როგორიც არის პაროლები, გადახდის დეტალები, ფოტოები, შეტყობინებები და თქვენ მიერ დაკრული აუდიო."</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"დაიწყოს ჩაწერა ან ტრანსლირება?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"დაიწყოს ჩაწერა ან ტრანსლირება <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>-ით?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"აღარ მაჩვენო"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ყველას გასუფთავება"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"მართვა"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"ჩუმი შეტყობინებები"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"მიმოწერები"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"ყველა ჩუმი შეტყობინების გასუფთავება"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"შეტყობინებები დაპაუზდა „არ შემაწუხოთ“ რეჟიმის მეშვეობით"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"დაწყება ახლავე"</string>
@@ -690,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"მორგება"</string>
     <string name="notification_done" msgid="6215117625922713976">"მზადაა"</string>
     <string name="inline_undo" msgid="9026953267645116526">"მოქმედების გაუქმება"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"შეტყობინებების მართვის საშუალებები"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"შეტყობინებების ჩაჩუმების ვარიანტები"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index f60aeaf..19ae4e2 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC өшірулі"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC қосулы"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Қолданбалар арасында ауысу үшін жоғары сырғытыңыз"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Қолданбаларды жылдам ауыстырып қосу үшін оңға қарай сүйреңіз"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Шолуды қосу/өшіру"</string>
@@ -465,13 +471,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Battery saver функциясын өшіру"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> экранда көрсетілетін немесе жазу не трансляциялау кезінде құрылғыда ойнатылған барлық ақпаратты пайдалана алады. Бұған құпия сөздер, төлем туралы мәліметтер, суреттер, хабарлар және ойнатылатын аудио сияқты ақпарат кіреді."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Осы функцияны ұсынатын қызмет экранда көрсетілетін немесе жазу не трансляциялау кезінде құрылғыда ойнатылған барлық ақпаратты пайдалана алады. Бұған құпия сөздер, төлем туралы мәліметтер, суреттер, хабарлар және ойнатылатын аудио сияқты ақпарат кіреді."</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Жазу немесе трансляциялау басталсын ба?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> арқылы жазу немесе трансляциялау басталсын ба?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"Қайта көрсетпеу"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Барлығын тазалау"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Басқару"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Дыбыссыз хабарландырулар"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Сөйлесулер"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Барлық дыбыссыз хабарландыруларды өшіру"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Хабарландырулар \"Мазаламау\" режимінде кідіртілді"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Қазір бастау"</string>
@@ -690,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Реттеу"</string>
     <string name="notification_done" msgid="6215117625922713976">"Дайын"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Қайтару"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"хабарландыруларды басқару элементтері"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"хабарландыруды кідірту опциялары"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 0550adef..d6ec177 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"បាន​បិទ NFC"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"បាន​បើក NFC"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"អូស​ឡើង​លើ​ដើម្បី​ប្តូរ​កម្មវិធី"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"អូសទៅស្ដាំដើម្បីប្ដូរកម្មវិធីបានរហ័ស"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"បិទ/បើក​ទិដ្ឋភាពរួម"</string>
@@ -465,13 +471,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"បិទ​កម្មវិធី​សន្សំ​ថ្ម"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> នឹងមានសិទ្ធិ​ចូលប្រើ​ព័ត៌មាន​ទាំងអស់​ដែលអាច​មើលឃើញ​នៅលើ​អេក្រង់​របស់អ្នក ឬដែលចាក់​ពីឧបករណ៍​របស់អ្នក នៅពេល​កំពុង​ថត ឬបញ្ជូន។ ព័ត៌មាន​នេះមាន​ដូចជា ពាក្យសម្ងាត់ ព័ត៌មាន​លម្អិត​អំពីការទូទាត់​ប្រាក់ រូបថត សារ និង​សំឡេង​ដែល​អ្នកចាក់​ជាដើម។"</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"សេវាកម្មដែល​ផ្ដល់​មុខងារ​នេះ​នឹងមាន​សិទ្ធិ​ចូលប្រើ​ព័ត៌មាន​ទាំងអស់​ដែល​អាច​មើលឃើញ​នៅលើ​អេក្រង់​របស់អ្នក ឬ​ដែលចាក់​ពីឧបករណ៍​របស់អ្នក នៅពេល​កំពុង​ថត ឬបញ្ជូន។ ព័ត៌មាន​នេះមាន​ដូចជា ពាក្យសម្ងាត់ ព័ត៌មាន​លម្អិត​អំពីការទូទាត់​ប្រាក់ រូបថត សារ និង​សំឡេង​ដែល​អ្នកចាក់​ជាដើម។"</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ចាប់ផ្ដើម​ថត ឬបញ្ជូន​មែនទេ?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"ចាប់ផ្ដើម​ថត ឬបញ្ជូន​ដោយប្រើ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ឬ?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"កុំ​បង្ហាញ​ម្ដងទៀត"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"សម្អាត​ទាំងអស់"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"គ្រប់គ្រង"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"ការជូនដំណឹង​ស្ងាត់"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"ការសន្ទនា"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"សម្អាត​ការជូនដំណឹង​ស្ងាត់ទាំងអស់"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"ការជូនដំណឹង​បានផ្អាក​ដោយ​មុខងារកុំរំខាន"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"ចាប់ផ្ដើម​ឥឡូវ"</string>
@@ -690,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"ប្ដូរតាមបំណង"</string>
     <string name="notification_done" msgid="6215117625922713976">"រួចរាល់"</string>
     <string name="inline_undo" msgid="9026953267645116526">"ត្រឡប់វិញ"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"ការគ្រប់គ្រង​ការជូន​ដំណឹង"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"ជម្រើស​ផ្អាកការ​ជូនដំណឹង"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 154f13b..fc0f8e6 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC ನಿಷ್ಕ್ರಿಯಗೊಂಡಿದೆ"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC ಸಕ್ರಿಯಗೊಂಡಿದೆ"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಬದಲಾಯಿಸಲು ಸ್ವೈಪ್ ಮಾಡಿ"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಬದಲಿಸಲು ತ್ವರಿತವಾಗಿ ಬಲಕ್ಕೆ ಡ್ರ್ಯಾಗ್ ಮಾಡಿ"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"ಟಾಗಲ್ ನ ಅವಲೋಕನ"</string>
@@ -465,13 +471,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"ಬ್ಯಾಟರಿ ಸೇವರ್‌ ಆಫ್ ಮಾಡಿ"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>, ಸ್ಕ್ರೀನ್ ಮೇಲೆ ಗೋಚರಿಸುವ ಅಥವಾ ರೆಕಾರ್ಡಿಂಗ್ ಅಥವಾ ಬಿತ್ತರಿಸುವಾಗ ಸಾಧನದಲ್ಲಿ ಪ್ಲೇ ಆಗುವ ಎಲ್ಲಾ ಮಾಹಿತಿಗಳಿಗೆ ಪ್ರವೇಶವನ್ನು ಹೊಂದಿರುತ್ತವೆ. ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಫೋಟೋಗಳು, ಸಂದೇಶಗಳು ಮತ್ತು ಆಡಿಯೊ ಪ್ಲೇಬ್ಯಾಕ್‌ನಂತಹ ಮಾಹಿತಿಯನ್ನು ಇದು ಒಳಗೊಂಡಿದೆ."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"ಈ ವೈಶಿಷ್ಟ್ಯವು ಒದಗಿಸುವ ಸೇವೆಗಳು, ಸ್ಕ್ರೀನ್ ಮೇಲೆ ಗೋಚರಿಸುವ ಅಥವಾ ರೆಕಾರ್ಡಿಂಗ್ ಅಥವಾ ಬಿತ್ತರಿಸುವಾಗ ಸಾಧನದಲ್ಲಿ ಪ್ಲೇ ಆಗುವ ಎಲ್ಲಾ ಮಾಹಿತಿಗಳಿಗೆ ಪ್ರವೇಶವನ್ನು ಹೊಂದಿರುತ್ತವೆ. ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಫೋಟೋಗಳು, ಸಂದೇಶಗಳು ಮತ್ತು ಆಡಿಯೊ ಪ್ಲೇಬ್ಯಾಕ್‌ನಂತಹ ಮಾಹಿತಿಯನ್ನು ಇದು ಒಳಗೊಂಡಿದೆ."</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ರೆಕಾರ್ಡಿಂಗ್ ಅಥವಾ ಬಿತ್ತರಿಸುವಿಕೆಯನ್ನು ಪ್ರಾರಂಭಿಸಬೇಕೆ?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ಬಳಸಿಕೊಂಡು ರೆಕಾರ್ಡಿಂಗ್ ಅಥವಾ ಬಿತ್ತರಿಸುವುದನ್ನು ಪ್ರಾರಂಭಿಸುವುದೇ?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"ಮತ್ತೊಮ್ಮೆ ತೋರಿಸದಿರು"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ಎಲ್ಲವನ್ನೂ ತೆರವುಗೊಳಿಸಿ"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"ನಿರ್ವಹಿಸಿ"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"ನಿಶ್ಶಬ್ಧ ಅಧಿಸೂಚನೆಗಳು"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"ಸಂವಾದಗಳು"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"ಎಲ್ಲಾ ನಿಶ್ಶಬ್ಧ ಅಧಿಸೂಚನೆಗಳನ್ನು ತೆರವುಗೊಳಿಸಿ"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"ಅಡಚಣೆ ಮಾಡಬೇಡಿ ಎನ್ನುವ ಮೂಲಕ ಅಧಿಸೂಚನೆಗಳನ್ನು ವಿರಾಮಗೊಳಿಸಲಾಗಿದೆ"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"ಈಗ ಪ್ರಾರಂಭಿಸಿ"</string>
@@ -690,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"ಕಸ್ಟಮೈಜ್‌ ಮಾಡಿ"</string>
     <string name="notification_done" msgid="6215117625922713976">"ಮುಗಿದಿದೆ"</string>
     <string name="inline_undo" msgid="9026953267645116526">"ರದ್ದುಮಾಡಿ"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"ಅಧಿಸೂಚನೆ ನಿಯಂತ್ರಣಗಳು"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"ಅಧಿಸೂಚನೆ ಸ್ನೂಜ್ ಆಯ್ಕೆಗಳು"</string>
@@ -886,7 +908,7 @@
     <string name="notification_channel_general" msgid="4384774889645929705">"ಸಾಮಾನ್ಯ ಸಂದೇಶಗಳು"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"ಸಂಗ್ರಹಣೆ"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"ಸುಳಿವುಗಳು"</string>
-    <string name="instant_apps" msgid="8337185853050247304">"ತತ್‌ಕ್ಷಣ ಆಪ್‌ಗಳು"</string>
+    <string name="instant_apps" msgid="8337185853050247304">"ಇನ್‌ಸ್ಟಂಟ್ ಆ್ಯಪ್‌ಗಳು"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> ರನ್ ಆಗುತ್ತಿದೆ"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"ಇನ್‌ಸ್ಟಾಲ್‌ ಮಾಡದೆ ಆ್ಯಪ್‌ ತೆರೆಯಲಾಗಿದೆ."</string>
     <string name="instant_apps_message_with_help" msgid="1816952263531203932">"ಇನ್‌ಸ್ಟಾಲ್‌ ಮಾಡದೆ ಆ್ಯಪ್‌ ತೆರೆಯಲಾಗಿದೆ. ಇನ್ನಷ್ಟು ತಿಳಿಯಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index b72a30e..eb3ebc9 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC 사용 중지됨"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC 사용 설정됨"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"위로 스와이프하여 앱 전환"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"앱을 빠르게 전환하려면 오른쪽으로 드래그"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"최근 사용 버튼 전환"</string>
@@ -465,13 +471,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"절전 모드 사용 중지"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>이(가) 녹화 또는 전송 중에 화면에 표시되거나 기기에서 재생되는 모든 정보에 액세스할 수 있습니다. 여기에는 비밀번호, 결제 세부정보, 사진, 메시지, 재생하는 오디오 같은 정보가 포함됩니다."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"이 기능을 제공하는 서비스는 녹화 또는 전송 중에 화면에 표시되거나 기기에서 재생되는 모든 정보에 액세스할 수 있습니다. 여기에는 비밀번호, 결제 세부정보, 사진, 메시지, 재생하는 오디오 같은 정보가 포함됩니다."</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"녹화 또는 전송을 시작하시겠습니까?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>(으)로 녹화 또는 전송을 시작하시겠습니까?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"다시 표시 안함"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"모두 지우기"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"관리"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"무음 알림"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"대화"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"무음 알림 모두 삭제"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"방해 금지 모드로 일시중지된 알림"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"시작하기"</string>
@@ -690,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"맞춤설정"</string>
     <string name="notification_done" msgid="6215117625922713976">"완료"</string>
     <string name="inline_undo" msgid="9026953267645116526">"실행취소"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"알림 관리"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"알림 일시 중지 옵션"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index d2f674f..06f8161 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC өчүрүлгөн"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC иштетилген"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Колдонмолорду которуштуруу үчүн өйдө сүрүңүз"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Колдонмолорду тез которуштуруу үчүн оңго сүйрөңүз"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Сереп салууну өчүрүү/күйгүзүү"</string>
@@ -465,13 +471,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Батареяны үнөмдөгүч режимин өчүрүү"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"Бул функцияны аткарган <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> кызматы экраныңызда көрүнүп турган бардык маалыматты же жаздыруу жана тышкы экранга чыгаруу учурунда түзмөгүңүздө ойнотулган маалыматты колдоно алат. Буга сырсөздөр, төлөмдүн чоо-жайы, сүрөттөр, билдирүүлөр жана ойнотулган аудио кирет."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Бул функцияны аткарган кызмат экраныңызда көрүнүп турган бардык маалыматты же жаздыруу жана тышкы экранга чыгаруу учурунда түзмөгүңүздө ойнотулган маалыматты колдоно алат. Буга сырсөздөр, төлөмдүн чоо-жайы, сүрөттөр, билдирүүлөр жана ойнотулган аудио кирет."</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Жаздырылып же тышкы экранга чыгарылып башталсынбы?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> менен жаздырылып же тышкы экранга чыгарылып башталсынбы?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"Экинчи көрсөтүлбөсүн"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Баарын тазалап салуу"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Башкаруу"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Үнсүз билдирмелер"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Жазышуулар"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Маанилүү эмес билдирмелердин баарын өчүрүү"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"\"Тынчымды алба\" режиминде билдирмелер тындырылды"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Азыр баштоо"</string>
@@ -690,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Ыңгайлаштыруу"</string>
     <string name="notification_done" msgid="6215117625922713976">"Бүттү"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Кайтаруу"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"эскертмелерди башкаруу каражаттары"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"эскертмени тындыруу опциялары"</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index ca68972..3013e1e 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC is disabled"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC is enabled"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"ປັດຂື້ນເພື່ອສະຫຼັບແອັບ"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"ລາກໄປຂວາເພື່ອສະຫຼັບແອັບດ່ວນ"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"ສະຫຼັບພາບຮວມ"</string>
@@ -465,13 +471,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"ປິດຕົວປະຢັດແບັດເຕີຣີ"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ຈະມີສິດເຂົ້າເຖິງຂໍ້ມູນທັງໝົດທີ່ປາກົດຢູ່ໜ້າຈໍຂອງທ່ານ ຫຼື ຫຼິ້ນຈາກອຸປະກອນຂອງທ່ານໃນເວລາບັນທຶກ ຫຼື ສົ່ງສັນຍານໜ້າຈໍ. ນີ້ຮວມເຖິງຂໍ້ມູນຕ່າງໆ ເຊັ່ນ: ລະຫັດຜ່ານ, ລາຍລະອຽດການຈ່າຍເງິນ, ຮູບ, ຂໍ້ຄວາມ ແລະ ສຽງທີ່ທ່ານຫຼິ້ນ."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"ບໍລິການທີ່ສະໜອງຄວາມສາມາດນີ້ຈະມີສິດເຂົ້າເຖິງຂໍ້ມູນທັງໝົດທີ່ປາກົດຢູ່ໜ້າຈໍຂອງທ່ານ ຫຼື ຫຼິ້ນຈາກອຸປະກອນຂອງທ່ານໃນເວລາບັນທຶກ ຫຼື ສົ່ງສັນຍານໜ້າຈໍ. ນີ້ຮວມເຖິງຂໍ້ມູນຕ່າງໆ ເຊັ່ນ: ລະຫັດຜ່ານ, ລາຍລະອຽດການຈ່າຍເງິນ, ຮູບ, ຂໍ້ຄວາມ ແລະ ສຽງທີ່ທ່ານຫຼິ້ນ."</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ເລີ່ມການບັນທຶກ ຫຼື ການສົ່ງສັນຍານໜ້າຈໍບໍ?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"ເລີ່ມການບັນທຶກ ຫຼື ການສົ່ງສັນຍານໜ້າຈໍກັບ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ບໍ?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"ບໍ່​ຕ້ອງ​ສະ​ແດງ​ອີກ"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ລຶບລ້າງທັງໝົດ"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"ຈັດການ"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"ການແຈ້ງເຕືອນແບບງຽບ"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"ການສົນທະນາ"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"ລຶບລ້າງການແຈ້ງເຕືອນແບບງຽບທັງໝົດ"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"ຢຸດການແຈ້ງເຕືອນໂດຍໂໝດຫ້າມລົບກວນແລ້ວ"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"ເລີ່ມດຽວນີ້"</string>
@@ -690,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"ປັບແຕ່ງ"</string>
     <string name="notification_done" msgid="6215117625922713976">"ສຳເລັດແລ້ວ"</string>
     <string name="inline_undo" msgid="9026953267645116526">"ຍົກເລີກ"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"ການຄວບຄຸມການແຈ້ງເຕືອນ"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"ຕົວເລືອກການເລື່ອນການແຈ້ງເຕືອນ"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 49d678b..c6b50d9 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -395,6 +395,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"ALR"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"ALR išjungtas"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"ALR įjungtas"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Ekrano įrašas"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Pradėti"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Stabdyti"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Perbraukite aukštyn, kad perjungtumėte programas"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Vilkite į dešinę, kad greitai perjungtumėte programas"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Perjungti apžvalgą"</string>
@@ -477,6 +480,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Viską išvalyti"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Tvarkyti"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Tylieji pranešimai"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Pokalbiai"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Išvalyti visus tylius pranešimus"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Pranešimai pristabdyti naudojant netrukdymo režimą"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Pradėti dabar"</string>
@@ -695,6 +699,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Tinkinti"</string>
     <string name="notification_done" msgid="6215117625922713976">"Atlikta"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Anuliuoti"</string>
+    <string name="demote" msgid="6225813324237153980">"Žymėti šį pranešimą kaip ne pokalbį"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Mėgstamiausi"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Pašalinti iš mėgstamiausių"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Nutildyti"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Rodyti"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Rodyti kaip debesėlį"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Išjungti debesėlius"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Pridėti prie pagrindinio ekrano"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"„<xliff:g id="APP_NAME">%1$s</xliff:g>“ <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"pranešimų valdikliai"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"pranešimų snaudimo parinktys"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 1973192..b519f05 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -393,6 +393,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC ir atspējoti"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC ir iespējoti"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Velciet augšup, lai pārslēgtu lietotnes"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Lai ātri pārslēgtu lietotnes, velciet pa labi"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Pārskata pārslēgšana"</string>
@@ -468,13 +474,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Izslēgt akumulatora jaudas taupīšanas režīmu"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> iegūs piekļuvi visai informācijai, kas ierakstīšanas vai apraides laikā tiks rādīta jūsu ekrānā vai atskaņota jūsu ierīcē. Atļauja attiecas uz tādu informāciju kā paroles, maksājumu informācija, fotoattēli, ziņojumi un jūsu atskaņotais audio saturs."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Pakalpojums, kas nodrošina šo funkciju, iegūs piekļuvi visai informācijai, kas ierakstīšanas vai apraides laikā tiks rādīta jūsu ekrānā vai atskaņota jūsu ierīcē. Atļauja attiecas uz tādu informāciju kā paroles, maksājumu informācija, fotoattēli, ziņojumi un jūsu atskaņotais audio saturs."</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Vai vēlaties sākt ierakstīšanu/apraidi?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Vai vēlaties sākt ierakstīšanu vai apraidi, izmantojot lietotni <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"Vairs nerādīt"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Dzēst visu"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Pārvaldīt"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Klusie paziņojumi"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Sarunas"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Notīrīt visus klusos paziņojumus"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Paziņojumi pārtraukti, izmantojot iestatījumu “Netraucēt”"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Sākt tūlīt"</string>
@@ -693,6 +699,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Pielāgot"</string>
     <string name="notification_done" msgid="6215117625922713976">"Gatavs"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Atsaukt"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"paziņojumu vadīklas"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"paziņojumu atlikšanas opcijas"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 0294a1f..9080b57 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -391,6 +391,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC е оневозможено"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC е овозможено"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Снимање екран"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Започни"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Сопри"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Повлечете нагоре за да се префрлите од една на друга апликација"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Повлечете надесно за брзо префрлање меѓу апликациите"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Вклучи/исклучи преглед"</string>
@@ -471,6 +474,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Избриши сѐ"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Управувајте"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Тивки известувања"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Разговори"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Исчисти ги сите тивки известувања"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Известувањата се паузирани од „Не вознемирувај“"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Започни сега"</string>
@@ -689,6 +693,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Приспособете"</string>
     <string name="notification_done" msgid="6215117625922713976">"Готово"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Врати"</string>
+    <string name="demote" msgid="6225813324237153980">"Означи го известувањево како „не е разговор“"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Омилен"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Неомилен"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Исклучи звук"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Вклучи звук"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Прикажи како балонче"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Исклучи балончиња"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Додај на почетниот екран"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"контроли за известувањето"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"опции за одложување на известувањето"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index a8303139..b761c44 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC പ്രവർത്തനരഹിതമാക്കി"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC പ്രവർത്തനക്ഷമമാക്കി"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"ആപ്പുകൾ മാറാൻ മുകളിലേക്ക് സ്വൈപ്പ് ചെയ്യുക"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"ആപ്പുകൾ പെട്ടെന്ന് മാറാൻ വലത്തോട്ട് വലിച്ചിടുക"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"അവലോകനം മാറ്റുക"</string>
@@ -465,13 +471,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"ബാറ്ററി ലാഭിക്കൽ ഓഫാക്കുക"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"റെക്കോർഡ് ചെയ്യുമ്പോഴോ കാസ്‌റ്റ് ചെയ്യുമ്പോഴോ നിങ്ങളുടെ ഉപകരണത്തിൽ നിന്ന് പ്ലേ ചെയ്യുന്നതോ നിങ്ങളുടെ സ്‌ക്രീനിൽ ദൃശ്യമാകുന്നതോ ആയ എല്ലാ വിവരങ്ങളിലേക്കും <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> എന്നതിന് ആക്‌സസ് ഉണ്ടായിരിക്കും. നിങ്ങൾ പ്ലേ ചെയ്യുന്ന ഒഡിയോ, സന്ദേശങ്ങൾ, ഫോട്ടോകൾ, പേയ്‌മെന്റ് വിശദാംശങ്ങൾ, പാസ്‌വേഡുകൾ എന്നിവ പോലുള്ള വിവരങ്ങൾ ഇതിൽ ഉൾപ്പെടുന്നു."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"റെക്കോർഡ് ചെയ്യുമ്പോഴോ കാസ്‌റ്റ് ചെയ്യുമ്പോഴോ നിങ്ങളുടെ ഉപകരണത്തിൽ നിന്ന് പ്ലേ ചെയ്യുന്നതോ നിങ്ങളുടെ സ്‌ക്രീനിൽ ദൃശ്യമാകുന്നതോ ആയ എല്ലാ വിവരങ്ങളിലേക്കും ഈ ഫംഗ്‌ഷൻ ലഭ്യമാക്കുന്ന സേവനത്തിന് ആക്‌സസ് ഉണ്ടായിരിക്കും. നിങ്ങൾ പ്ലേ ചെയ്യുന്ന ഒഡിയോ, സന്ദേശങ്ങൾ, ഫോട്ടോകൾ, പേയ്‌മെന്റ് വിശദാംശങ്ങൾ, പാസ്‌വേഡുകൾ എന്നിവ പോലുള്ള വിവരങ്ങൾ ഇതിൽ ഉൾപ്പെടുന്നു."</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"റെക്കോർഡ് ചെയ്യൽ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യൽ ആരംഭിക്കണോ?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ഉപയോഗിച്ച് റെക്കോർഡ് ചെയ്യൽ അല്ലെങ്കിൽ കാസ്‌റ്റ് ചെയ്യൽ ആരംഭിക്കണോ?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"വീണ്ടും കാണിക്കരുത്"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"എല്ലാം മായ്‌ക്കുക"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"മാനേജ് ചെയ്യുക"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"നിശബ്‌ദ അറിയിപ്പുകൾ"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"സംഭാഷണങ്ങൾ"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"എല്ലാ നിശബ്‌ദ അറിയിപ്പുകളും മായ്ക്കുക"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"\'ശല്യപ്പെടുത്തരുത്\' വഴി അറിയിപ്പുകൾ താൽക്കാലികമായി നിർത്തി"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"ഇപ്പോൾ ആരംഭിക്കുക"</string>
@@ -690,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"ഇഷ്‌ടാനുസൃതമാക്കുക"</string>
     <string name="notification_done" msgid="6215117625922713976">"പൂർത്തിയായി"</string>
     <string name="inline_undo" msgid="9026953267645116526">"പഴയപടിയാക്കുക"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"അറിയിപ്പ് നിയന്ത്രണങ്ങൾ"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"അറിയിപ്പ് സ്‌നൂസ് ഓപ്ഷനുകൾ"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index e5497ea..4f9b447 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC-г цуцалсан"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC-г идэвхжүүлсэн"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Апп сэлгэхийн тулд дээш шударна уу"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Аппуудыг хурдан сэлгэхийн тулд баруун тийш чирнэ үү"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Тоймыг унтраах/асаах"</string>
@@ -471,6 +477,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Бүгдийг арилгах"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Удирдах"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Чимээгүй мэдэгдэл"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Харилцан яриа"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Бүх чимээгүй мэдэгдлийг арилгах"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Бүү саад бол горимын түр зогсоосон мэдэгдэл"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Одоо эхлүүлэх"</string>
@@ -689,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Тохируулах"</string>
     <string name="notification_done" msgid="6215117625922713976">"Дууссан"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Болих"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"мэдэгдлийн удирдлага"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"мэдэгдэл түр хойшлуулагчийн сонголт"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 3eb74b7..99f48e6 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC अक्षम केले आहे"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC सक्षम केले आहे"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"अ‍ॅप्स स्विच करण्यासाठी वर स्वाइप करा"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"अ‍ॅप्स वर झटपट स्विच करण्यासाठी उजवीकडे ड्रॅग करा"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"अवलोकन टॉगल करा."</string>
@@ -465,13 +471,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"बॅटरी सेव्हर बंद करा"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"तुमच्या स्क्रीनवर दृश्यमान असलेल्या किंवा रेकॉर्ड किंवा कास्ट करताना तुमच्या डिव्हाइसमधून प्ले केलेल्या सर्व माहितीचा अ‍ॅक्सेस <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ला असेल. यामध्ये पासवर्ड, पेमेंट तपशील, फोटो, मेसेज आणि तुम्ही प्ले केलेला ऑडिओ यासारख्या माहितीचा समावेश असतो."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"हे कार्य पुरवठा करणाऱ्या सेवेस तुमच्या स्क्रीनवर दृश्यमान असलेल्या किंवा रेकॉर्ड किंवा कास्ट करताना तुमच्या डिव्हाइसमधून प्ले केलेल्या सर्व माहितीचा अ‍ॅक्सेस असेल. यामध्ये पासवर्ड, पेमेंट तपशील, फोटो, मेसेज आणि तुम्ही प्ले केलेला ऑडिओ यासारख्या माहितीचा समावेश असतो."</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"रेकॉर्ड किंवा कास्ट करणे सुरू करायचे आहे का ?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ने रेकॉर्ड करणे किंवा कास्ट करणे सुरू करा?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"पुन्हा दर्शवू नका"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"सर्व साफ करा"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"व्यवस्थापित करा"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"सायलंट सूचना"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"संभाषणे"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"सर्व सायलंट सूचना साफ करा"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"व्यत्यय आणून नकाद्वारे सूचना थांबवल्या"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"आता सुरू करा"</string>
@@ -690,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"कस्टमाइझ करा"</string>
     <string name="notification_done" msgid="6215117625922713976">"पूर्ण झाले"</string>
     <string name="inline_undo" msgid="9026953267645116526">"पहिल्यासारखे करा"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"सूचना नियंत्रणे"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"सूचना स्नूझ पर्याय"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index b632bcd..42c51b7 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC dilumpuhkan"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC didayakan"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Leret ke atas untuk menukar apl"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Seret ke kanan untuk beralih apl dengan pantas"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Togol Ikhtisar"</string>
@@ -471,6 +477,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Kosongkan semua"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Urus"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Pemberitahuan senyap"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Perbualan"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Kosongkan semua pemberitahuan senyap"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Pemberitahuan dijeda oleh Jangan Ganggu"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Mulakan sekarang"</string>
@@ -689,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Sesuaikan"</string>
     <string name="notification_done" msgid="6215117625922713976">"Selesai"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Buat asal"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"kawalan pemberitahuan"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"pilihan tunda pemberitahuan"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 45893c1..dd5acfa 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC ကို ပိတ်ထားသည်"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC ကို ဖွင့်ထားသည်"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"အက်ပ်များကို ဖွင့်ရန် အပေါ်သို့ ပွတ်ဆွဲပါ"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"အက်ပ်များကို ပြောင်းရန် ညာဘက်သို့ ဖိဆွဲပါ"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"ဖွင့်၊ ပိတ် အနှစ်ချုပ်"</string>
@@ -465,13 +471,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"ဘက်ထရီ အားထိန်းကို ပိတ်ရန်"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> သည် အသံဖမ်းနေစဉ် (သို့) ကာစ်လုပ်နေစဉ် သင့်မျက်နှာပြင်တွင် မြင်ရသော (သို့) သင့်စက်တွင် ဖွင့်ထားသော အချက်အလက်မှန်သမျှကို သုံးနိုင်ပါမည်။ ၎င်းတွင် စကားဝှက်များ၊ ငွေပေးချေမှုအသေးစိတ်များ၊ ဓာတ်ပုံများ၊ မက်ဆေ့ဂျ်များနှင့် သင်ဖွင့်သည့်အသံကဲ့သို့သော အချက်အလက်များ ပါဝင်သည်။"</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"ဤလုပ်ဆောင်ချက်ကို ပေးအပ်သည့် ဝန်ဆောင်မှုသည် အသံဖမ်းနေစဉ် (သို့) ကာစ်လုပ်နေစဉ် သင့်မျက်နှာပြင်တွင် မြင်ရသော (သို့) သင့်စက်တွင် ဖွင့်ထားသော အချက်အလက်မှန်သမျှကို သုံးနိုင်ပါမည်။ ၎င်းတွင် စကားဝှက်များ၊ ငွေပေးချေမှုအသေးစိတ်များ၊ ဓာတ်ပုံများ၊ မက်ဆေ့ဂျ်များနှင့် သင်ဖွင့်သည့်အသံကဲ့သို့သော အချက်အလက်များ ပါဝင်သည်။"</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ဖမ်းယူခြင်း သို့မဟုတ် ကာစ်လုပ်ခြင်း စတင်မလား။"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> နှင့် ဖမ်းယူခြင်း သို့မဟုတ် ကာစ်လုပ်ခြင်း စတင်မလား။"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"နောက်ထပ် မပြပါနှင့်"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"အားလုံး ဖယ်ရှားရန်"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"စီမံရန်"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"အကြောင်းကြားချက်များကို အသံတိတ်ခြင်း"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"စကားဝိုင်းများ"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"အသံတိတ် အကြောင်းကြားချက်များအားလုံးကို ရှင်းလင်းရန်"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"အကြောင်းကြားချက်များကို \'မနှောင့်ယှက်ရ\' က ခေတ္တရပ်ထားသည်"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"ယခု စတင်ပါ"</string>
@@ -690,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"စိတ်ကြိုက်ပြုလုပ်ရန်"</string>
     <string name="notification_done" msgid="6215117625922713976">"ပြီးပါပြီ"</string>
     <string name="inline_undo" msgid="9026953267645116526">"တစ်ဆင့်နောက်ပြန်ရန်"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"အကြောင်းကြားချက် ထိန်းချုပ်မှုများ"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"အကြောင်းကြားချက်များကို ဆိုင်းငံ့ရန် ရွေးချယ်စရာများ"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index d07bd8e..97d6792 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC er slått av"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC er slått på"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Sveip opp for å bytte apper"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Dra til høyre for å bytte apper raskt"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Slå oversikten av eller på"</string>
@@ -465,13 +471,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Slå av batterisparing"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> får tilgang til all informasjon som er synlig på skjermen din, eller som spilles av fra enheten når du tar opp eller caster. Dette inkluderer informasjon som passord, betalingsopplysninger, bilder, meldinger og lyd du spiller av."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Tjenesten som leverer denne funksjonen, får tilgang til all informasjon som er synlig på skjermen din, eller som spilles av fra enheten når du tar opp eller caster. Dette inkluderer informasjon som passord, betalingsopplysninger, bilder, meldinger og lyd du spiller av."</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Vil du starte opptak eller casting?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Vil du starte opptak eller casting med <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"Ikke vis igjen"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Fjern alt"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Administrer"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Lydløse varsler"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Samtaler"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Fjern alle lydløse varsler"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Varsler er satt på pause av «Ikke forstyrr»"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Start nå"</string>
@@ -690,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Tilpass"</string>
     <string name="notification_done" msgid="6215117625922713976">"Ferdig"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Angre"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"varselinnstillinger"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"slumrealternativer for varsler"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index ba6b0ed..1345b09 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC लाई असक्षम पारिएको छ"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC लाई सक्षम पारिएको छ"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"अनुप्रयोगहरू बदल्न माथितिर स्वाइप गर्नुहोस्"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"अनुप्रयोगहरू बदल्न द्रुत गतिमा दायाँतिर ड्र्याग गर्नुहोस्"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"परिदृश्य टगल गर्नुहोस्"</string>
@@ -471,6 +477,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"सबै हटाउनुहोस्"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"व्यवस्थित गर्नुहोस्"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"मौन सूचनाहरू"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"वार्तालापहरू"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"सबै मौन सूचनाहरू हटाउनुहोस्"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"बाधा नपुऱ्याउनुहोस् नामक मोडमार्फत पज पारिएका सूचनाहरू"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"अहिले सुरु गर्नुहोस्"</string>
@@ -689,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"आफू अनुकूल पार्नुहोस्"</string>
     <string name="notification_done" msgid="6215117625922713976">"सम्पन्‍न भयो"</string>
     <string name="inline_undo" msgid="9026953267645116526">"अन्डू गर्नुहोस्"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"सूचना सम्बन्धी नियन्त्रणहरू"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"सूचना स्नुज गर्ने विकल्पहरू"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 638fcad..71d2388 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -391,6 +391,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC is uitgeschakeld"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC is ingeschakeld"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Schermopname"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Starten"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Stoppen"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Veeg omhoog om te schakelen tussen apps"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Sleep naar rechts om snel tussen apps te schakelen"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Overzicht in-/uitschakelen"</string>
@@ -471,6 +474,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Alles wissen"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Beheren"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Stille meldingen"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Gesprekken"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Alle stille meldingen wissen"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Meldingen onderbroken door \'Niet storen\'"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Nu starten"</string>
@@ -689,6 +693,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Aanpassen"</string>
     <string name="notification_done" msgid="6215117625922713976">"Gereed"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Ongedaan maken"</string>
+    <string name="demote" msgid="6225813324237153980">"Deze melding markeren als geen gesprek"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Markeren als favoriet"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Favoriet verwijderen"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Negeren"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Negeren opheffen"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Weergeven als ballon"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Ballonnen uitschakelen"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Toevoegen aan startscherm"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"beheeropties voor meldingen"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"snooze-opties voor meldingen"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 7586be5..85841fe 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC ଅକ୍ଷମ କରାଯାଇଛି"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC ସକ୍ଷମ କରାଯାଇଛି"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"ଆପ୍‌କୁ ବଦଳ କରିବା ପାଇଁ ସ୍ଵାଇପ୍ କରନ୍ତୁ"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"ଆପ୍‌ଗୁଡ଼ିକ ମଧ୍ୟରେ ଶୀଘ୍ର ବଦଳ କରିବା ପାଇଁ ଡାହାଣକୁ ଡ୍ରାଗ୍ କରନ୍ତୁ"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"ସଂକ୍ଷିପ୍ତ ବିବରଣୀକୁ ଟୋଗଲ୍ କରନ୍ତୁ"</string>
@@ -465,13 +471,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"ବ୍ୟାଟେରୀ ସେଭର୍‌ ଅଫ୍‍ କରନ୍ତୁ"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>ରେ ସମସ୍ତ ସୂଚନାକୁ ଆକ୍ସେସ୍ ରହିବ ଯାହା ଆପଣଙ୍କର ସ୍କ୍ରିନ୍‌ରେ ଦେଖାଯିବ ବା ରେକର୍ଡିଂ ବା କାଷ୍ଟିଂ ବେଳେ ଆପଣଙ୍କର ଡିଭାଇସ୍ ଠାରୁ ଚାଲିବ। ପାସ୍‌ୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ଫଟୋ, ମେସେଜ୍ ଏବଂ ଆପଣ ଚଲାଉଥିବା ଅଡିଓ ପରି ସୂଚନା ଅନ୍ତର୍ଭୁକ୍ତ ଅଛି।"</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"ଏହି ପ୍ରକାର୍ଯ୍ୟ ପ୍ରଦାନ କରୁଥିବା ସେବା ସମସ୍ତ ସୂଚନାକୁ ଆକ୍ସେସ୍ ରହିବ ଯାହା ସ୍କ୍ରିନ୍‌ରେ ଦେଖାଯିବ ବା ରେକର୍ଡିଂ ବା କାଷ୍ଟିଂ ବେଳେ ଆପଣଙ୍କର ଡିଭାଇସ୍ ଠାରୁ ଚାଲିବ। ପାସ୍‌ୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ଫଟୋ, ମେସେଜ୍ ଏବଂ ଆପଣ ଚଲାଉଥିବା ଅଡିଓ ପରି ସୂଚନା ଅନ୍ତର୍ଭୁକ୍ତ ଅଛି।"</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ରେକର୍ଡିଂ ବା କାଷ୍ଟିଂ ଆରମ୍ଭ କରିବେ?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ସହ ରେକର୍ଡିଂ ବା କାଷ୍ଟିଂ ଆରମ୍ଭ କରିବେ?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"ପୁଣି ଦେଖାନ୍ତୁ ନାହିଁ"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ସମସ୍ତ ଖାଲି କରନ୍ତୁ"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"ପରିଚାଳନା କରନ୍ତୁ"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"ନୀରବ ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"ବାର୍ତ୍ତାଳାପଗୁଡ଼ିକ"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"ସମସ୍ତ ନୀରବ ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ ଖାଲି କରନ୍ତୁ"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"\"ବିରକ୍ତ କରନ୍ତୁ ନାହିଁ\" ବିକଳ୍ପ ଦ୍ୱାରା ବିଜ୍ଞପ୍ତି ପଜ୍‍ ହୋଇଛି"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"ବର୍ତ୍ତମାନ ଆରମ୍ଭ କରନ୍ତୁ"</string>
@@ -690,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"କଷ୍ଟମାଇଜ୍‌ କରନ୍ତୁ"</string>
     <string name="notification_done" msgid="6215117625922713976">"ହୋଇଗଲା"</string>
     <string name="inline_undo" msgid="9026953267645116526">"ପୂର୍ବସ୍ଥାନକୁ ଫେରାଇଆଣନ୍ତୁ"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"ବିଜ୍ଞପ୍ତି ନିୟନ୍ତ୍ରଣ"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"ବିଜ୍ଞପ୍ତି ସ୍ନୁଜ୍‍ ବିକଳ୍ପ"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 6e1f230..4ea51dd 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC ਨੂੰ ਅਯੋਗ ਬਣਾਇਆ ਗਿਆ ਹੈ"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC ਨੂੰ ਯੋਗ ਬਣਾਇਆ ਗਿਆ ਹੈ"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"ਐਪਾਂ ਵਿਚਾਲੇ ਅਦਲਾ-ਬਦਲੀ ਕਰਨ ਲਈ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"ਐਪਾਂ ਵਿਚਾਲੇ ਤੇਜ਼ੀ ਨਾਲ ਅਦਲਾ-ਬਦਲੀ ਕਰਨ ਲਈ ਸੱਜੇ ਪਾਸੇ ਵੱਲ ਘਸੀਟੋ"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"ਰੂਪ-ਰੇਖਾ ਨੂੰ ਟੌਗਲ ਕਰੋ"</string>
@@ -465,13 +471,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"ਬੈਟਰੀ ਸੇਵਰ ਬੰਦ ਕਰੋ"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ਕੋਲ ਬਾਕੀ ਸਾਰੀ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ ਹੋਵੇਗੀ ਜੋ ਕਿ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦਿਖਣਯੋਗ ਹੈ ਜਾਂ ਰਿਕਾਰਡਿੰਗ ਜਾਂ ਕਾਸਟ ਕਰਨ ਵੇਲੇ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਚਲਾਈ ਜਾਂਦੀ ਹੈ। ਇਸ ਵਿੱਚ ਪਾਸਵਰਡ, ਭੁਗਤਾਨ ਵੇਰਵੇ, ਫ਼ੋਟੋਆਂ, ਸੁਨੇਹੇ ਅਤੇ ਤੁਹਾਡੇ ਵੱਲੋਂ ਚਲਾਏ ਆਡੀਓ ਦੀ ਜਾਣਕਾਰੀ ਸ਼ਾਮਲ ਹੁੰਦੀ ਹੈ।"</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"ਇਸ ਫੰਕਸ਼ਨ ਦੇ ਸੇਵਾ ਪ੍ਰਦਾਨਕ ਕੋਲ ਬਾਕੀ ਸਾਰੀ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ ਹੋਵੇਗੀ ਜੋ ਕਿ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦਿਖਣਯੋਗ ਹੁੰਦੀ ਹੈ ਜਾਂ ਰਿਕਾਰਡਿੰਗ ਜਾਂ ਕਾਸਟ ਕਰਨ ਵੇਲੇ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਚਲਾਈ ਜਾਂਦੀ ਹੈ। ਇਸ ਵਿੱਚ ਪਾਸਵਰਡ, ਭੁਗਤਾਨ ਵੇਰਵੇ, ਫ਼ੋਟੋਆਂ, ਸੁਨੇਹੇ ਅਤੇ ਤੁਹਾਡੇ ਵੱਲੋਂ ਚਲਾਏ ਆਡੀਓ ਦੀ ਜਾਣਕਾਰੀ ਸ਼ਾਮਲ ਹੁੰਦੀ ਹੈ।"</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ਕੀ ਰਿਕਾਰਡਿੰਗ ਜਾਂ ਕਾਸਟ ਕਰਨਾ ਸ਼ੁਰੂ ਕਰਨਾ ਹੈ?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ਨਾਲ ਰਿਕਾਰਡਿੰਗ ਜਾਂ ਕਾਸਟ ਕਰਨਾ ਸ਼ੁਰੂ ਕਰਨਾ ਹੈ?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"ਦੁਬਾਰਾ ਨਾ ਦਿਖਾਓ"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ਸਭ ਕਲੀਅਰ ਕਰੋ"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"ਪ੍ਰਬੰਧਨ ਕਰੋ"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"ਸ਼ਾਂਤ ਸੂਚਨਾਵਾਂ"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"ਗੱਲਾਂਬਾਤਾਂ"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"ਸਾਰੀਆਂ ਖਾਮੋਸ਼ ਸੂਚਨਾਵਾਂ ਕਲੀਅਰ ਕਰੋ"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"\'ਪਰੇਸ਼ਾਨ ਨਾ ਕਰੋ\' ਵੱਲੋਂ ਸੂਚਨਾਵਾਂ ਨੂੰ ਰੋਕਿਆ ਗਿਆ"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"ਹੁਣ ਚਾਲੂ ਕਰੋ"</string>
@@ -690,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"ਵਿਉਂਤਬੱਧ ਕਰੋ"</string>
     <string name="notification_done" msgid="6215117625922713976">"ਹੋ ਗਿਆ"</string>
     <string name="inline_undo" msgid="9026953267645116526">"ਅਣਕੀਤਾ ਕਰੋ"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"ਸੂਚਨਾ ਕੰਟਰੋਲ"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"ਸੂਚਨਾ ਸਨੂਜ਼ ਵਿਕਲਪ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 307d417..515d659 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -395,6 +395,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"Komunikacja NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"Komunikacja NFC jest wyłączona"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"Komunikacja NFC jest włączona"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Przesuń w górę, by przełączyć aplikacje"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Szybko przeciągnij w prawo, by przełączyć aplikacje"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Przełącz Przegląd"</string>
@@ -477,6 +483,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Usuń wszystkie"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Zarządzaj"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Ciche powiadomienia"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Rozmowy"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Usuń wszystkie ciche powiadomienia"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Powiadomienia wstrzymane przez tryb Nie przeszkadzać"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Rozpocznij teraz"</string>
@@ -695,6 +702,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Dostosuj"</string>
     <string name="notification_done" msgid="6215117625922713976">"Gotowe"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Cofnij"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"sterowanie powiadomieniami"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"opcje odkładania powiadomień"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index c1dd243..d486b9b 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -391,6 +391,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"A NFC está desativada"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"A NFC está ativada"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Gravação de tela"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Iniciar"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Parar"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Deslize para cima para alternar entre os apps"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Arraste para a direita para alternar rapidamente entre os apps"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Alternar Visão geral"</string>
@@ -471,6 +474,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Limpar tudo"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Gerenciar"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Notificações silenciosas"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversas"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Apagar todas as notificações silenciosas"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notificações pausadas pelo modo \"Não perturbe\""</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Iniciar agora"</string>
@@ -689,6 +693,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Personalizar"</string>
     <string name="notification_done" msgid="6215117625922713976">"Concluído"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Desfazer"</string>
+    <string name="demote" msgid="6225813324237153980">"Marcar esta notificação como não sendo uma conversa"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Adicionar como favorito"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Remover dos favoritos"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Desativar som"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Ativar som"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Mostrar como balão"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Desativar balões"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Adicionar à tela inicial"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g> do <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"controles de notificação"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"opções de adiamento de notificação"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 9a5a4b3..d17b002 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -391,6 +391,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"O NFC está desativado"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"O NFC está ativado"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Gravação de ecrã"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Iniciar"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Parar"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Deslizar rapidamente para cima para mudar de aplicação"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Arraste para a direita para mudar rapidamente de aplicação."</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Ativar/desativar Vista geral"</string>
@@ -471,6 +474,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Limpar tudo"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Gerir"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Notificações silenciosas"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversas"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Limpar todas as notificações silenciosas"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notificações colocadas em pausa pelo modo Não incomodar."</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Começar agora"</string>
@@ -557,7 +561,7 @@
     <string name="screen_pinning_toast" msgid="2083944237147005811">"Para soltar este ecrã, toque sem soltar nos botões Anterior e Vista geral."</string>
     <string name="screen_pinning_toast_recents_invisible" msgid="6343770487795352573">"Para soltar este ecrã, toque sem soltar nos botões Anterior e Página inicial."</string>
     <string name="screen_pinning_toast_gesture_nav" msgid="2884536903398445645">"Para soltar este ecrã, deslize rapidamente para cima sem soltar."</string>
-    <string name="screen_pinning_positive" msgid="3285785989665266984">"Compreendi"</string>
+    <string name="screen_pinning_positive" msgid="3285785989665266984">"OK"</string>
     <string name="screen_pinning_negative" msgid="6882816864569211666">"Não, obrigado"</string>
     <string name="screen_pinning_start" msgid="5695091877402422575">"Ecrã fixo"</string>
     <string name="screen_pinning_exit" msgid="5114993350662745840">"Ecrã solto"</string>
@@ -621,7 +625,7 @@
     <string name="tuner_warning_title" msgid="7721976098452135267">"Diversão para alguns, mas não para todos"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"O Sintonizador da interface do sistema disponibiliza-lhe formas adicionais ajustar e personalizar a interface do utilizador do Android. Estas funcionalidades experimentais podem ser alteradas, deixar de funcionar ou desaparecer em versões futuras. Prossiga com cuidado."</string>
     <string name="tuner_persistent_warning" msgid="230466285569307806">"Estas funcionalidades experimentais podem ser alteradas, deixar de funcionar ou desaparecer em versões futuras. Prossiga com cuidado."</string>
-    <string name="got_it" msgid="477119182261892069">"Compreendi"</string>
+    <string name="got_it" msgid="477119182261892069">"OK"</string>
     <string name="tuner_toast" msgid="3812684836514766951">"Parabéns! O Sintonizador da interface do sistema foi adicionado às Definições"</string>
     <string name="remove_from_settings" msgid="633775561782209994">"Remover das Definições"</string>
     <string name="remove_from_settings_prompt" msgid="551565437265615426">"Pretende remover o Sintonizador da interface do sistema das Definições e deixar de utilizar todas as respetivas funcionalidades?"</string>
@@ -689,6 +693,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Personalizar"</string>
     <string name="notification_done" msgid="6215117625922713976">"Concluído"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Anular"</string>
+    <string name="demote" msgid="6225813324237153980">"Marcar esta notificação como não sendo uma conversa"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Adicionar aos favoritos"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Remover dos favoritos"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Ignorar"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Ativar"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Mostrar como balão"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Desativar balões"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Adicionar ao ecrã principal"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g> do <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"controlos de notificação"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"opções de suspensão de notificações"</string>
@@ -921,7 +933,7 @@
     <string name="auto_saver_enabled_title" msgid="4294726198280286333">"Poupança de bateria agendada ativada"</string>
     <string name="auto_saver_enabled_text" msgid="7889491183116752719">"A Poupança de bateria é ativada automaticamente quando o nível de bateria está abaixo de <xliff:g id="PERCENTAGE">%d</xliff:g>%%."</string>
     <string name="open_saver_setting_action" msgid="2111461909782935190">"Definições"</string>
-    <string name="auto_saver_okay_action" msgid="7815925750741935386">"Compreendi"</string>
+    <string name="auto_saver_okay_action" msgid="7815925750741935386">"OK"</string>
     <string name="heap_dump_tile_name" msgid="2464189856478823046">"Despejar pilha SysUI"</string>
     <string name="sensor_privacy_mode" msgid="4462866919026513692">"Sensores desativados"</string>
     <string name="device_services" msgid="1549944177856658705">"Serviços do dispositivo"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index c1dd243..d486b9b 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -391,6 +391,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"A NFC está desativada"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"A NFC está ativada"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Gravação de tela"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Iniciar"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Parar"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Deslize para cima para alternar entre os apps"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Arraste para a direita para alternar rapidamente entre os apps"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Alternar Visão geral"</string>
@@ -471,6 +474,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Limpar tudo"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Gerenciar"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Notificações silenciosas"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversas"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Apagar todas as notificações silenciosas"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notificações pausadas pelo modo \"Não perturbe\""</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Iniciar agora"</string>
@@ -689,6 +693,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Personalizar"</string>
     <string name="notification_done" msgid="6215117625922713976">"Concluído"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Desfazer"</string>
+    <string name="demote" msgid="6225813324237153980">"Marcar esta notificação como não sendo uma conversa"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Adicionar como favorito"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Remover dos favoritos"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Desativar som"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Ativar som"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Mostrar como balão"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Desativar balões"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Adicionar à tela inicial"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g> do <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"controles de notificação"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"opções de adiamento de notificação"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 63c2c0a..2674504 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -393,6 +393,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"Serviciul NFC este dezactivat"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"Serviciul NFC este activat"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Glisați în sus pentru a comuta între aplicații"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Glisați la dreapta pentru a comuta rapid între aplicații"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Comutați secțiunea Recente"</string>
@@ -468,13 +474,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Dezactivați economisirea bateriei"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> va avea acces la toate informațiile vizibile pe ecran sau redate pe dispozitiv în timp ce înregistrați sau proiectați. Între aceste informații se numără parole, detalii de plată, fotografii, mesaje și conținutul audio pe care îl redați."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Serviciul care oferă această funcție va avea acces la toate informațiile vizibile pe ecran sau redate pe dispozitiv în timp ce înregistrați sau proiectați. Între aceste informații se numără parole, detalii de plată, fotografii, mesaje și conținutul audio pe care îl redați."</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Începeți să înregistrați sau să proiectați?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Începeți să înregistrați sau să proiectați cu <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"Nu se mai afișează"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Ștergeți toate notificările"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Gestionați"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Notificări silențioase"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversații"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Ștergeți toate notificările silențioase"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notificări întrerupte prin „Nu deranja”"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Începeți acum"</string>
@@ -693,6 +699,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Personalizați"</string>
     <string name="notification_done" msgid="6215117625922713976">"Terminat"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Anulați"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"comenzile notificării"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"opțiuni de amânare a notificării"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 2da136f..9156989 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -395,6 +395,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"Модуль NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"Модуль NFC отключен"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"Модуль NFC включен"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Чтобы переключиться между приложениями, проведите по экрану вверх."</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Перетащите вправо, чтобы быстро переключиться между приложениями"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Переключить режим обзора"</string>
@@ -471,13 +477,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Отключить режим энергосбережения"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"Во время записи или трансляции у приложения \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" будет доступ ко всей информации, которая видна на экране или воспроизводится с устройства, в том числе к паролям, сведениям о платежах, фотографиям, сообщениям и прослушиваемым аудиозаписям."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Во время записи или трансляции у сервиса, предоставляющего эту функцию, будет доступ ко всей информации, которая видна на экране или воспроизводится с устройства, в том числе к паролям, сведениям о платежах, фотографиям, сообщениям и прослушиваемым аудиозаписям."</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Начать запись или трансляцию?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Начать запись или трансляцию через приложение \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\"?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"Больше не показывать"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Очистить все"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Настроить"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Беззвучные уведомления"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Чаты"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Отклонить все беззвучные уведомления"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"В режиме \"Не беспокоить\" уведомления заблокированы"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Начать"</string>
@@ -696,6 +702,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Настроить"</string>
     <string name="notification_done" msgid="6215117625922713976">"Готово"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Отменить"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g>: <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"настройки уведомлений"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"параметры отсрочки уведомлений"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 94a7448..84e01d0 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC අබලයි"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC සබලයි"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"යෙදුම් මාරු කිරීමට ස්වයිප් කරන්න"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"ඉක්මනින් යෙදුම් මාරු කිරීමට දකුණට අදින්න"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"දළ විශ්ලේෂණය ටොගල කරන්න"</string>
@@ -465,13 +471,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"බැටරි සුරැකුම ක්‍රියාවිරහිත කරන්න"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>ට පටිගත කරන හෝ විකාශ කරන අතරතුර ඔබේ තිරයේ දිස් වන හෝ ඔබේ උපාංගයෙන් වාදනය කරන සියලු තොරතුරු වෙත ප්‍රවේශ ලැබෙනු ඇත. මෙහි මුරපද, ගෙවීම් විස්තර, ඡායාරූප, පණිවිඩ සහ ඔබ වාදනය කරන ඕඩියෝ යනාදි තොරතුරු ඇතුළත් වේ."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"මෙම ශ්‍රිතය සපයන සේවාවට පටිගත කරන හෝ විකාශ කරන අතරතුර ඔබේ තිරයේ දිස් වන හෝ ඔබේ උපාංගයෙන් වාදනය කරන සියලු තොරතුරු වෙත ප්‍රවේශය ලැබෙනු ඇත. මෙහි මුරපද, ගෙවීම් විස්තර, ඡායාරූප, පණිවිඩ සහ ඔබ වාදනය කරන ඕඩියෝ යනාදි තොරතුරු ඇතුළත් වේ."</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"පටිගත කිරීම හෝ විකාශය කිරීම ආරම්භ කරන්නද?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> සමග පටිගත කිරීම හෝ විකාශය කිරීම ආරම්භ කරන්නද?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"නැවත නොපෙන්වන්න"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"සියල්ල හිස් කරන්න"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"කළමනාකරණය කරන්න"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"නිහඬ දැනුම්දීම්"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"සංවාද"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"සියලු නිහඬ දැනුම්දීම් හිස් කරන්න"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"බාධා නොකරන්න මගින් විරාම කරන ලද දැනුම්දීම්"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"දැන් අරඹන්න"</string>
@@ -690,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"අභිරුචිකරණය"</string>
     <string name="notification_done" msgid="6215117625922713976">"නිමයි"</string>
     <string name="inline_undo" msgid="9026953267645116526">"පසුගමනය කරන්න"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"දැනුම්දීම් පාලන"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"දැනුම්දීම් මදක් නතර කිරීමේ විකල්ප"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index c38c855..fced8a4 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -395,6 +395,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC je deaktivované"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC je aktivované"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Záznam obrazovky"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Začať"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Ukončiť"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Potiahnutím nahor prepnete aplikácie"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Presunutím doprava rýchlo prepnete aplikácie"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Prepnúť prehľad"</string>
@@ -477,6 +480,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Vymazať všetko"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Spravovať"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Tiché upozornenia"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Konverzácie"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Vymazať všetky tiché upozornenia"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Upozornenia sú pozastavené režimom bez vyrušení"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Spustiť"</string>
@@ -695,6 +699,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Prispôsobiť"</string>
     <string name="notification_done" msgid="6215117625922713976">"Hotovo"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Späť"</string>
+    <string name="demote" msgid="6225813324237153980">"Označiť, že toto upozornenie nie je konverzácia"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Obľúbená"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Odstrániť z obľúbených"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Vypnúť zvuk"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Zapnúť zvuk"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Zobraziť ako bublinu"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Vypnúť bubliny"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Pridať na plochu"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"ovládacie prvky pre upozornenia"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"možnosti stlmenia upozornení"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 2c8893e..4816a66 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -395,6 +395,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"Tehnologija NFC je onemogočena"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"Tehnologija NFC je omogočena"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Za preklop aplikacij povlecite navzgor"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Povlecite v desno za hiter preklop med aplikacijami"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Vklop/izklop pregleda"</string>
@@ -471,13 +477,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Izklop varčevanja z energijo akumulatorja"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"Aplikacija <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> bo imela dostop do vseh podatkov, ki so med snemanjem ali predvajanjem prikazani na vašem zaslonu ali se predvajajo iz vaše naprave. To vključuje podatke, kot so gesla, podrobnosti o plačilu, fotografije, sporočila in zvok, ki ga predvajate."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Storitev, ki zagotavlja to funkcijo, bo imela dostop do vseh podatkov, ki so med snemanjem ali predvajanjem prikazani na vašem zaslonu ali se predvajajo iz vaše naprave. To vključuje podatke, kot so gesla, podrobnosti o plačilu, fotografije, sporočila in zvok, ki ga predvajate."</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Želite začeti snemati ali predvajati?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Želite začeti snemati ali predvajati z aplikacijo <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"Tega ne prikaži več"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Izbriši vse"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Upravljanje"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Tiha obvestila"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Pogovori"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Brisanje vseh tihih obvestil"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Prikazovanje obvestil je začasno zaustavljeno z načinom »ne moti«"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Začni zdaj"</string>
@@ -696,6 +702,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Prilagodi"</string>
     <string name="notification_done" msgid="6215117625922713976">"Dokončano"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Razveljavi"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"kontrolniki obvestil"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"možnosti preložitve obvestil"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 445f8ca4..14d72b4 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC është çaktivizuar"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC është aktivizuar"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Rrëshqit shpejt lart për të ndërruar aplikacionet"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Zvarrit djathtas për të ndërruar aplikacionet me shpejtësi"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Kalo te përmbledhja"</string>
@@ -471,6 +477,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Pastroji të gjitha"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Menaxho"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Njoftimet në heshtje"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Bisedat"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Pastro të gjitha njoftimet në heshtje"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Njoftimet janë vendosur në pauzë nga modaliteti \"Mos shqetëso\""</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Fillo tani"</string>
@@ -689,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Personalizo"</string>
     <string name="notification_done" msgid="6215117625922713976">"U krye"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Zhbëj"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"kontrollet e njoftimit"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"opsionet e shtyrjes së njoftimit"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index acff269..29e7e10 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -393,6 +393,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC је онемогућен"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC је омогућен"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Снимак екрана"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Почните"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Зауставите"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Превуците нагоре да бисте мењали апликације"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Превуците удесно да бисте брзо променили апликације"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Укључи/искључи преглед"</string>
@@ -474,6 +477,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Обриши све"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Управљајте"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Нечујна обавештења"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Конверзације"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Обришите сва нечујна обавештења"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Обавештења су паузирана режимом Не узнемиравај"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Започни одмах"</string>
@@ -692,6 +696,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Прилагоди"</string>
     <string name="notification_done" msgid="6215117625922713976">"Готово"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Опозови"</string>
+    <string name="demote" msgid="6225813324237153980">"Означи да ово обавештење није конверзација"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Означи као омиљено"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Уклони из омиљених"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Искључи звук"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Укључи звук"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Прикажи као облачић"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Искључи облачиће"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Додај на почетни екран"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"контроле обавештења"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"опције за одлагање обавештења"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index d63206a3..b0d78779 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -391,6 +391,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC är inaktiverat"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC är aktiverat"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Skärminspelning"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Starta"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Stoppa"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Byt appar genom att svepa uppåt"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Tryck och dra åt höger för att snabbt byta mellan appar"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Aktivera och inaktivera översikten"</string>
@@ -471,6 +474,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Rensa alla"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Hantera"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Ljudlösa aviseringar"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Konversationer"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Rensa alla ljudlösa aviseringar"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Aviseringar har pausats via Stör ej"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Starta nu"</string>
@@ -689,6 +693,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Anpassa"</string>
     <string name="notification_done" msgid="6215117625922713976">"Klar"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Ångra"</string>
+    <string name="demote" msgid="6225813324237153980">"Markera aviseringen som icke-konversation"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Favorit"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Ta bort från favoriter"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Dölj"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Visa"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Visa som bubbla"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Inaktivera bubblor"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Lägg till på startskärmen"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"inställningar för aviseringar"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"alternativ för att snooza aviseringar"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 9f319a6..ecaefb0 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC imezimwa"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC imewashwa"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Telezesha kidole juu ili ubadilishe programu"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Buruta kulia ili ubadilishe programu haraka"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Washa Muhtasari"</string>
@@ -465,13 +471,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Zima Kiokoa Betri"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> itaweza kufikia maelezo yote yanayoonekana kwenye skrini yako au yanayochezwa kwenye kifaa chako wakati wa kurekodi au kutuma. Hii ni pamoja na maelezo kama vile manenosiri, maelezo ya malipo, picha, ujumbe na sauti unayocheza."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Huduma inayotoa utendaji huu itaweza kufikia maelezo yote yanayoonekana kwenye skrini yako au yanayochezwa kwenye kifaa chako wakati wa kurekodi au kutuma. Hii ni pamoja na maelezo kama vile manenosiri, maelezo ya malipo, picha, ujumbe na sauti unayocheza."</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Ungependa kuanza kurekodi au kutuma?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Ungependa kuanza kurekodi au kutuma ukitumia <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"Usionyeshe tena"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Futa zote"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Dhibiti"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Arifa zisizo na sauti"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Mazungumzo"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Futa arifa zote zisizo na sauti"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Kipengele cha Usinisumbue kimesitisha arifa"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Anza sasa"</string>
@@ -690,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Weka mapendeleo"</string>
     <string name="notification_done" msgid="6215117625922713976">"Nimemaliza"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Tendua"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"vidhibiti vya arifa"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"chaguo za kuahirisha arifa"</string>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index d4c3c05a..0bea7d6 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC முடக்கப்பட்டது"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC இயக்கப்பட்டது"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"ஆப்ஸிற்கு இடையே மாற்றுவதற்கு, மேல்நோக்கி ஸ்வைப் செய்க"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"ஆப்ஸை வேகமாக மாற்ற, வலப்புறம் இழுக்கவும்"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"மேலோட்டப் பார்வையை நிலைமாற்று"</string>
@@ -465,13 +471,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"பேட்டரி சேமிப்பானை ஆஃப் செய்"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> உங்கள் திரையில் தெரியும் தகவல்கள், ரெக்கார்டு செய்யும்போதோ அனுப்பும்போதோ உங்கள் சாதனத்திலிருந்து பிளே ஆகும் அனைத்து தகவல்கள் ஆகியவற்றுக்கான அணுகலைக் கொண்டிருக்கும். கடவுச்சொற்கள், பேமெண்ட் தொடர்பான தகவல்கள், படங்கள், மெசேஜ்கள், நீங்கள் பிளே செய்யும் ஆடியோ போன்ற அனைத்துத் தகவல்களும் இதில் அடங்கும்."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"இந்தச் செயல்பாட்டை வழங்கும் சேவையானது உங்கள் திரையில் தெரியும் தகவல்கள், ரெக்கார்டு செய்யும்போதோ அனுப்பும்போதோ உங்கள் சாதனத்திலிருந்து பிளே ஆகும் அனைத்துத் தகவல்கள் ஆகியவற்றுக்கான அணுகலைக் கொண்டிருக்கும். கடவுச்சொற்கள், பேமெண்ட் தொடர்பான தகவல்கள், படங்கள், மெசேஜ்கள், நீங்கள் பிளே செய்யும் ஆடியோ போன்ற அனைத்துத் தகவல்களும் இதில் அடங்கும்."</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ரெக்கார்டிங் செய்யவோ அனுப்புவதற்கோ தொடங்கிவிட்டீர்களா?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> மூலம் ரெக்கார்டிங் செய்யவோ அனுப்புவதற்கோ தொடங்கிவீட்டீர்களா?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"மீண்டும் காட்டாதே"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"எல்லாவற்றையும் அழி"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"அறிவிப்புகளை நிர்வகி"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"ஒலியில்லாத அறிவிப்புகள்"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"உரையாடல்கள்"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"ஒலியில்லாத அழைப்புகள் அனைத்தையும் அழிக்கும்"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"\'தொந்தரவு செய்ய வேண்டாம்\' அம்சத்தின் மூலம் அறிவிப்புகள் இடைநிறுத்தப்பட்டுள்ளன"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"இப்போது தொடங்கு"</string>
@@ -690,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"தனிப்பயனாக்கு"</string>
     <string name="notification_done" msgid="6215117625922713976">"முடிந்தது"</string>
     <string name="inline_undo" msgid="9026953267645116526">"செயல்தவிர்"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"அறிவிப்புக் கட்டுப்பாடுகள்"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"அறிவிப்பை உறக்கநிலையாக்கும் விருப்பங்கள்"</string>
@@ -946,10 +968,7 @@
     <string name="notification_content_system_nav_changed" msgid="5077913144844684544">"சிஸ்டம் நேவிகேஷன் மாற்றப்பட்டது. மாற்றங்களைச் செய்ய ‘அமைப்புகளுக்குச்’ செல்லவும்."</string>
     <string name="notification_content_gesture_nav_available" msgid="4431460803004659888">"சிஸ்டம் நேவிகேஷனை மாற்ற ’அமைப்புகளுக்குச்’ செல்லவும்"</string>
     <string name="inattentive_sleep_warning_title" msgid="3891371591713990373">"இயக்க நேரம்"</string>
-    <!-- no translation found for magnification_overlay_title (6584179429612427958) -->
-    <skip />
-    <!-- no translation found for magnification_window_title (4863914360847258333) -->
-    <skip />
-    <!-- no translation found for magnification_controls_title (8421106606708891519) -->
-    <skip />
+    <string name="magnification_overlay_title" msgid="6584179429612427958">"Magnification Overlay Window"</string>
+    <string name="magnification_window_title" msgid="4863914360847258333">"பெரிதாக்கல் சாளரம்"</string>
+    <string name="magnification_controls_title" msgid="8421106606708891519">"பெரிதாக்கல் சாளரக் கட்டுப்பாடுகள்"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 1baf89c..bb62490 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC నిలిపివేయబడింది"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC ప్రారంభించబడింది"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"యాప్‌లను మార్చడం కోసం ఎగువకు స్వైప్ చేయండి"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"యాప్‌లను శీఘ్రంగా స్విచ్ చేయడానికి కుడి వైపుకు లాగండి"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"స్థూలదృష్టిని టోగుల్ చేయి"</string>
@@ -465,13 +471,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"బ్యాటరీ సేవర్‌ను ఆఫ్ చేయండి"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"రికార్డ్ చేస్తున్నప్పుడు లేదా ప్రసారం చేస్తున్నప్పుడు, మీ స్క్రీన్‌పై ప్రదర్శించబడిన లేదా మీ పరికరం నుండి ప్లే చేయబడిన సమాచారం మొత్తాన్ని, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> యాక్సెస్ చేయగలుగుతుంది. ఈ సమాచారంలో, పాస్‌వర్డ్‌లు, చెల్లింపు వివరాలు, ఫోటోలు, సందేశాలు, మీరు ప్లే చేసే ఆడియో వంటివి ఉంటాయి."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"రికార్డ్ చేస్తున్నప్పుడు లేదా ప్రసారం చేస్తున్నప్పుడు మీ స్క్రీన్‌పై ప్రదర్శించబడిన లేదా మీ పరికరం నుండి ప్లే చేయబడిన సమాచారం మొత్తాన్ని, ఈ ఫంక్షన్‌ను అందిస్తున్న సర్వీసు యాక్సెస్ చేయగలదు. ఈ సమాచారంలో, పాస్‌వర్డ్‌లు, చెల్లింపు వివరాలు, ఫోటోలు, సందేశాలు, మీరు ప్లే చేసే ఆడియో వంటివి ఉంటాయి."</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"రికార్డ్ చేయడం లేదా ప్రసారం చేయడం ప్రారంభించాలా?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>తో రికార్డ్ చేయడం లేదా ప్రసారం చేయడం ప్రారంభించాలా?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"మళ్లీ చూపవద్దు"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"అన్నీ క్లియర్ చేయండి"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"నిర్వహించండి"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"నిశ్శబ్ద నోటిఫికేషన్‌లు"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"సంభాషణలు"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"అన్ని నిశ్శబ్ద నోటిఫికేషన్‌లను క్లియర్ చేస్తుంది"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"అంతరాయం కలిగించవద్దు ద్వారా నోటిఫికేషన్‌లు పాజ్ చేయబడ్డాయి"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"ఇప్పుడే ప్రారంభించు"</string>
@@ -690,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"అనుకూలపరచండి"</string>
     <string name="notification_done" msgid="6215117625922713976">"పూర్తయింది"</string>
     <string name="inline_undo" msgid="9026953267645116526">"చర్యరద్దు చేయి"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"నోటిఫికేషన్ నియంత్రణలు"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"నోటిఫికేషన్ తాత్కాలిక ఆపివేత ఎంపికలు"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 569b05c..54c3d73 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC ถูกปิดใช้งาน"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"เปิดใช้งาน NFC แล้ว"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"เลื่อนขึ้นเพื่อสลับแอป"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"ลากไปทางขวาเพื่อสลับแอปอย่างรวดเร็ว"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"สลับภาพรวม"</string>
@@ -465,13 +471,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"ปิดโหมดประหยัดแบตเตอรี่"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> จะมีสิทธิ์เข้าถึงข้อมูลทั้งหมดที่ปรากฏบนหน้าจอหรือเปิดจากอุปกรณ์ของคุณขณะบันทึกหรือแคสต์ ซึ่งรวมถึงข้อมูลอย่างเช่นรหัสผ่าน รายละเอียดการชำระเงิน รูปภาพ ข้อความ และเสียงที่คุณเล่น"</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"บริการที่มีฟังก์ชันนี้จะมีสิทธิ์เข้าถึงข้อมูลทั้งหมดที่ปรากฏบนหน้าจอหรือเปิดจากอุปกรณ์ของคุณขณะบันทึกหรือแคสต์ ซึ่งรวมถึงข้อมูลอย่างเช่นรหัสผ่าน รายละเอียดการชำระเงิน รูปภาพ ข้อความ และเสียงที่คุณเล่น"</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"เริ่มบันทึกหรือแคสต์ใช่ไหม"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"เริ่มบันทึกหรือแคสต์ด้วย <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> เลยไหม"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"ไม่ต้องแสดงข้อความนี้อีก"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ล้างทั้งหมด"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"จัดการ"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"การแจ้งเตือนแบบไม่มีเสียง"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"การสนทนา"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"ล้างการแจ้งเตือนแบบไม่มีเสียงทั้งหมด"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"หยุดการแจ้งเตือนชั่วคราวโดย \"ห้ามรบกวน\""</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"เริ่มเลย"</string>
@@ -690,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"ปรับแต่ง"</string>
     <string name="notification_done" msgid="6215117625922713976">"เสร็จสิ้น"</string>
     <string name="inline_undo" msgid="9026953267645116526">"เลิกทำ"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"ส่วนควบคุมการแจ้งเตือน"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"ตัวเลือกการปิดเสียงแจ้งเตือนชั่วคราว"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 027eaab..ef812ff 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"Naka-disable ang NFC"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"Naka-enable ang NFC"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Mag-swipe pataas upang lumipat ng app"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"I-drag pakanan para mabilisang magpalipat-lipat ng app"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"I-toggle ang Overview"</string>
@@ -465,13 +471,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"I-off ang Pangtipid sa Baterya"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"Magkakaroon ng access ang <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> sa lahat ng impormasyong nakikita sa iyong screen o pine-play mula sa device mo habang nagre-record o nagka-cast. Kasama rito ang impormasyong tulad ng mga password, detalye ng pagbabayad, larawan, mensahe, at audio na pine-play mo."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Ang serbisyong nagbibigay ng function na ito ay magkakaroon ng access sa lahat ng impormasyong nakikita sa iyong screen o pine-play mula sa device mo habang nagre-record o nagka-cast. Kasama rito ang impormasyong tulad ng mga password, detalye ng pagbabayad, larawan, mensahe, at audio na pine-play mo."</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Magsimulang mag-record o mag-cast?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Simulang mag-record o mag-cast gamit ang <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"Huwag ipakitang muli"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"I-clear lahat"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Pamahalaan"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Mga silent na notification"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Mga Pag-uusap"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"I-clear ang lahat ng silent na notification"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Mga notification na na-pause ng Huwag Istorbohin"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Magsimula ngayon"</string>
@@ -690,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"I-customize"</string>
     <string name="notification_done" msgid="6215117625922713976">"Tapos Na"</string>
     <string name="inline_undo" msgid="9026953267645116526">"I-undo"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"mga kontrol ng notification"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"mga opsyon sa pag-snooze ng notification"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index e9f08d3..470606f 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC devre dışı"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC etkin"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Uygulamalar arasında geçiş yapmak için yukarı kaydırın"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Uygulamaları hızlıca değiştirmek için sağa kaydırın"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Genel bakışı aç/kapat"</string>
@@ -465,13 +471,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Pil Tasarrufu\'nu kapat"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>, ekranınızda görünen veya kayıt ya da yayın sırasında cihazınızdan oynatılan tüm bilgilere erişecektir. Bu bilgiler arasında şifreler, ödeme detayları, fotoğraflar, mesajlar ve çaldığınız sesler gibi bilgiler yer alır."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Bu işlevi sağlayan hizmet, ekranınızda görünen veya kayıt ya da yayın sırasında cihazınızdan oynatılan tüm bilgilere erişecektir. Bu bilgiler arasında şifreler, ödeme detayları, fotoğraflar, mesajlar ve çaldığınız sesler gibi bilgiler yer alır."</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Kayıt veya yayınlama başlatılsın mı?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ile kayıt veya yayınlama başlatılsın mı?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"Bir daha gösterme"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Tümünü temizle"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Yönet"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Sessiz bildirimler"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Görüşmeler"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Sessiz bildirimlerin tümünü temizle"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Bildirimler, Rahatsız Etmeyin özelliği tarafından duraklatıldı"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Şimdi başlat"</string>
@@ -690,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Özelleştir"</string>
     <string name="notification_done" msgid="6215117625922713976">"Bitti"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Geri al"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"Bildirim kontrolleri"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"bildirim erteleme seçenekleri"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 3640c80..496206a 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -395,6 +395,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC вимкнено"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC ввімкнено"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Проводьте пальцем угору, щоб переходити між додатками"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Перетягуйте праворуч, щоб швидко переходити між додатками"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Увімкнути або вимкнути огляд"</string>
@@ -477,6 +483,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Очистити все"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Керувати"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Беззвучні сповіщення"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Чати"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Очистити всі беззвучні сповіщення"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Режим \"Не турбувати\" призупинив сповіщення"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Почати зараз"</string>
@@ -695,6 +702,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Налаштувати"</string>
     <string name="notification_done" msgid="6215117625922713976">"Готово"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Відмінити"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"елементи керування сповіщеннями"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"параметри відкладення сповіщень"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index fdc618b..a6de509 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"‏NFC غیر فعال ہے"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"‏NFC فعال ہے"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"ایپس سوئچ کرنے کیلئے اوپر سوائپ کریں"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"تیزی سے ایپس کو سوئچ کرنے کے لیے دائیں طرف گھسیٹیں"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"مجموعی جائزہ ٹوگل کریں"</string>
@@ -471,6 +477,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"سبھی کو صاف کریں"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"نظم کریں"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"اطلاعات خاموش کریں"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"گفتگوئیں"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"سبھی خاموش اطلاعات کو صاف کریں"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"\'ڈسٹرب نہ کریں\' کے ذریعے اطلاعات کو موقوف کیا گیا"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"ابھی شروع کریں"</string>
@@ -689,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"حسب ضرورت بنائیں"</string>
     <string name="notification_done" msgid="6215117625922713976">"ہوگیا"</string>
     <string name="inline_undo" msgid="9026953267645116526">"کالعدم کریں"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"اطلاع کے کنٹرولز"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"اطلاع اسنوز کرنے کے اختیارات"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index df40325..1eb02f3 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -391,6 +391,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC o‘chiq"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC yoniq"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Ekranni yozib olish"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Boshlash"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Toʻxtatish"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Ilovalarni almashtirish uchun ekranni tepaga suring"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Ilovalarni tezkor almashtirish uchun o‘ngga torting"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Umumiy nazar rejimini almashtirish"</string>
@@ -465,13 +468,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Quvvat tejash rejimidan chiqish"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ekranda chiqqan yoki yozib olish va translatsiya vaqtida ijro etilgan barcha axborotlarga ruxsat oladi. Bu axborotlar parollar, toʻlov tafsilotlari, rasmlar, xabarlar va ijro etilgan audiolardan iborat boʻlishi mumkin."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Bu funksiyani taʼminlovchi xizmat ekranda chiqqan yoki yozib olish va translatsiya vaqtida ijro etilgan barcha axborotlarga ruxsat oladi. Bu axborotlar parollar, toʻlov tafsilotlari, rasmlar, xabarlar va ijro etilgan audiolardan iborat boʻlishi mumkin."</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Yozib olish yoki translatsiya boshlansinmi?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> bilan yozib olinsin yoki translatsiya qilinsinmi?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"Boshqa ko‘rsatilmasin"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Hammasini tozalash"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Boshqarish"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Tovushsiz bildirishnomalar"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Suhbatlar"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Barcha tovushsiz bildirishnomalarni tozalash"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Bezovta qilinmasin rejimida bildirishnomalar pauza qilingan"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Boshlash"</string>
@@ -690,6 +693,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Moslash"</string>
     <string name="notification_done" msgid="6215117625922713976">"Tayyor"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Qaytarish"</string>
+    <string name="demote" msgid="6225813324237153980">"Bu bildirishnomani zanjirsiz deb belgilash"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Sevimli"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Sevimlilardan olib tashlash"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Ovozsiz"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Ovozli"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Bir zanjirda chiqarish"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Zanjirlarsiz chiqarish"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Bosh ekranga chiqarish"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"bildirishnoma sozlamalari"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"bildirishnomalarni kechiktirish parametrlari"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 8a99f43..dc3882e 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC đã được tắt"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC đã được bật"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Vuốt lên để chuyển đổi ứng dụng"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Kéo sang phải để chuyển đổi nhanh giữa các ứng dụng"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Bật/tắt chế độ xem Tổng quan"</string>
@@ -465,13 +471,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Tắt trình tiết kiệm pin"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> sẽ có quyền truy cập vào tất cả các thông tin hiển thị trên màn hình của bạn hoặc phát từ thiết bị trong khi ghi âm/ghi hình hoặc truyền, bao gồm cả thông tin như mật khẩu, chi tiết thanh toán, ảnh, tin nhắn và âm thanh mà bạn phát."</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Dịch vụ cung cấp chức năng này có quyền truy cập vào tất cả các thông tin hiển thị trên màn hình của bạn hoặc phát từ thiết bị trong khi ghi âm/ghi hình hoặc truyền, bao gồm cả thông tin như mật khẩu, chi tiết thanh toán, ảnh, tin nhắn và âm thanh mà bạn phát."</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Bắt đầu ghi âm/ghi hình hoặc truyền?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Bắt đầu ghi âm/ghi hình hoặc truyền bằng <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"Không hiển thị lại"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Xóa tất cả"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Quản lý"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Thông báo im lặng"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Cuộc trò chuyện"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Xóa tất cả thông báo im lặng"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Chế độ Không làm phiền đã tạm dừng thông báo"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Bắt đầu ngay"</string>
@@ -690,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Tùy chỉnh"</string>
     <string name="notification_done" msgid="6215117625922713976">"Xong"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Hoàn tác"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"điều khiển thông báo"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"Tùy chọn báo lại thông báo"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 66bf66b..baacdae 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC 已停用"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC 已启用"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"向上滑动可切换应用"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"向右拖动可快速切换应用"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"切换概览"</string>
@@ -465,13 +471,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"关闭省电模式"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"在录制或投射内容时,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>将可获取您屏幕上显示或设备中播放的所有信息,其中包括密码、付款明细、照片、消息以及您播放的音频等信息。"</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"在录制或投射内容时,提供此功能的服务将可获取您屏幕上显示或设备中播放的所有信息,其中包括密码、付款明细、照片、消息以及您播放的音频等信息。"</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"要开始录制或投射内容吗?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"要开始使用<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>录制或投射内容吗?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"不再显示"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"管理"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"无声通知"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"对话"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"清除所有无声通知"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"勿扰模式暂停的通知"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"立即开始"</string>
@@ -690,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"自定义"</string>
     <string name="notification_done" msgid="6215117625922713976">"完成"</string>
     <string name="inline_undo" msgid="9026953267645116526">"撤消"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g><xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"通知设置"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"通知延后选项"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index dae6eb1..0f1696e7 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC 已停用"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC 已啟用"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"向上滑動即可切換應用程式"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"向右拖曳即可快速切換應用程式"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"切換概覽"</string>
@@ -471,6 +477,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"管理"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"靜音通知"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"對話"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"清除所有靜音通知"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"「請勿騷擾」模式已將通知暫停"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"立即開始"</string>
@@ -689,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"自訂"</string>
     <string name="notification_done" msgid="6215117625922713976">"完成"</string>
     <string name="inline_undo" msgid="9026953267645116526">"復原"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"通知控制項"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"通知延後選項"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 1586bb8..e8e76ec 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -391,6 +391,12 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC 已停用"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC 已啟用"</string>
+    <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) -->
+    <skip />
+    <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) -->
+    <skip />
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"向上滑動即可切換應用程式"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"向右拖曳即可快速切換應用程式"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"切換總覽"</string>
@@ -465,13 +471,13 @@
     <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"關閉節約耗電量模式"</string>
     <string name="media_projection_dialog_text" msgid="1755705274910034772">"在錄製或投放內容時,「<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>」可存取畫面上顯示的任何資訊或裝置播放的任何內容,包括密碼、付款詳情、相片、訊息和你播放的音訊。"</string>
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"在錄製或投放內容時,提供這項功能的服務可存取畫面上顯示的任何資訊或裝置播放的任何內容,包括密碼、付款詳情、相片、訊息和你播放的音訊。"</string>
-    <!-- no translation found for media_projection_dialog_service_title (2888507074107884040) -->
-    <skip />
+    <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"要開始錄製或投放內容嗎?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"要使用「<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>」開始錄製或投放內容嗎?"</string>
     <string name="media_projection_remember_text" msgid="6896767327140422951">"不要再顯示"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"管理"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"靜音通知"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"對話"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"清除所有靜音通知"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"「零打擾」模式已將通知設為暫停"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"立即開始"</string>
@@ -690,6 +696,22 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"自訂"</string>
     <string name="notification_done" msgid="6215117625922713976">"完成"</string>
     <string name="inline_undo" msgid="9026953267645116526">"復原"</string>
+    <!-- no translation found for demote (6225813324237153980) -->
+    <skip />
+    <!-- no translation found for notification_conversation_favorite (8252976467488182853) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unfavorite (633301300443356176) -->
+    <skip />
+    <!-- no translation found for notification_conversation_mute (477431709687199671) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unmute (410885000669775294) -->
+    <skip />
+    <!-- no translation found for notification_conversation_bubble (4598142032706190028) -->
+    <skip />
+    <!-- no translation found for notification_conversation_unbubble (2303087159802926401) -->
+    <skip />
+    <!-- no translation found for notification_conversation_home_screen (8347136037958438935) -->
+    <skip />
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"通知控制項"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"通知延後選項"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 5353018..fa764b5 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -391,6 +391,9 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"I-NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"I-NFC ikhutshaziwe"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"I-NFC inikwe amandla"</string>
+    <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Irekhodi lesikrini"</string>
+    <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Qala"</string>
+    <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Misa"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Swayiphela phezulu ukuze ushintshe izinhlelo zokusebenza"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Hudula ngqo ukuze ushintshe ngokushesha izinhlelo zokusebenza"</string>
     <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Guqula ukubuka konke"</string>
@@ -471,6 +474,7 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Sula konke"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Phatha"</string>
     <string name="notification_section_header_gentle" msgid="3044910806569985386">"Thulisa izaziso"</string>
+    <string name="notification_section_header_conversations" msgid="821834744538345661">"Izingxoxo"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Sula zonke izaziso ezithulile"</string>
     <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Izaziso zimiswe okwesikhashana ukungaphazamisi"</string>
     <string name="media_projection_action_text" msgid="3634906766918186440">"Qala manje"</string>
@@ -689,6 +693,14 @@
     <string name="notification_app_settings" msgid="8963648463858039377">"Enza ngendlela oyifisayo"</string>
     <string name="notification_done" msgid="6215117625922713976">"Kwenziwe"</string>
     <string name="inline_undo" msgid="9026953267645116526">"Susa"</string>
+    <string name="demote" msgid="6225813324237153980">"Maka lesi saziso njengokungesiyo ingxoxo"</string>
+    <string name="notification_conversation_favorite" msgid="8252976467488182853">"Intandokazi"</string>
+    <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Susa ubuntandokazi"</string>
+    <string name="notification_conversation_mute" msgid="477431709687199671">"Thulisa"</string>
+    <string name="notification_conversation_unmute" msgid="410885000669775294">"Susa ukuthula"</string>
+    <string name="notification_conversation_bubble" msgid="4598142032706190028">"Bonisa njengebhamuza"</string>
+    <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Vala amabhamuza"</string>
+    <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Faka kusikrini sasekhaya"</string>
     <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="notification_menu_gear_description" msgid="6429668976593634862">"izilawuli zesaziso"</string>
     <string name="notification_menu_snooze_description" msgid="4740133348901973244">"izinketho zokusnuza zesaziso"</string>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index c142465..8dd2a8b 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -182,8 +182,10 @@
 
     <!-- Global screenshot actions -->
     <color name="global_screenshot_button_background">#F5F5F5</color>
+    <color name="global_screenshot_button_text">#000000</color>
+    <color name="global_screenshot_button_border">@color/GM2_grey_300</color>
     <color name="global_screenshot_button_ripple">#1f000000</color>
-    <color name="global_screenshot_button_text">@color/GM2_blue_500</color>
+    <color name="global_screenshot_button_icon">@color/GM2_blue_500</color>
 
     <!-- GM2 colors -->
     <color name="GM2_grey_50">#F8F9FA</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 640f31b..e520106 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -109,7 +109,7 @@
 
     <!-- The default tiles to display in QuickSettings -->
     <string name="quick_settings_tiles_default" translatable="false">
-        wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast
+        wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast,screenrecord
     </string>
 
     <!-- The minimum number of tiles to display in QuickSettings -->
@@ -117,7 +117,7 @@
 
     <!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
     <string name="quick_settings_tiles_stock" translatable="false">
-        wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,controls
+        wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,controls,screenrecord
     </string>
 
     <!-- The tiles to display in QuickSettings -->
@@ -484,4 +484,13 @@
     <!-- Package name for controls plugin -->
     <string name="config_controlsPluginPackageName" translatable="false">com.android.systemui.controls.panel</string>
 
+    <!-- Defines the blacklist for system icons.  That is to say, the icons in the status bar that
+         are part of the blacklist are never displayed. Each item in the blacklist must be a string
+         defined in core/res/res/config.xml to properly blacklist the icon.
+     -->
+    <string-array name="config_statusBarIconBlackList" translatable="false">
+        <item>@*android:string/status_bar_rotate</item>
+        <item>@*android:string/status_bar_headset</item>
+    </string-array>
+
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index da0323a..65ca9f2 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -213,6 +213,11 @@
     <!-- The horizontal space around the buttons in the inline settings -->
     <dimen name="notification_guts_button_horizontal_spacing">8dp</dimen>
 
+    <dimen name="notification_guts_conversation_header_height">84dp</dimen>
+    <dimen name="notification_guts_conversation_icon_size">52dp</dimen>
+    <dimen name="notification_guts_conversation_action_height">56dp</dimen>
+    <dimen name="notification_guts_conversation_action_text_padding_start">32dp</dimen>
+
     <!-- The height of the header in inline settings -->
     <dimen name="notification_guts_header_height">24dp</dimen>
 
@@ -288,14 +293,27 @@
     <!-- Dimensions related to screenshots -->
 
     <!-- The padding on the global screenshot background image -->
+    <dimen name="global_screenshot_legacy_bg_padding">20dp</dimen>
     <dimen name="global_screenshot_bg_padding">20dp</dimen>
+    <dimen name="global_screenshot_x_scale">80dp</dimen>
+    <dimen name="screenshot_offset_y">48dp</dimen>
+    <dimen name="screenshot_offset_x">16dp</dimen>
+    <dimen name="screenshot_action_container_offset_y">32dp</dimen>
     <dimen name="screenshot_action_container_corner_radius">10dp</dimen>
-    <dimen name="screenshot_action_container_padding">20dp</dimen>
+    <dimen name="screenshot_action_container_padding_vertical">10dp</dimen>
+    <dimen name="screenshot_action_container_margin_horizontal">8dp</dimen>
+    <dimen name="screenshot_action_container_padding_left">100dp</dimen>
+    <dimen name="screenshot_action_container_padding_right">8dp</dimen>
     <!-- Radius of the chip background on global screenshot actions -->
     <dimen name="screenshot_button_corner_radius">20dp</dimen>
-    <dimen name="screenshot_action_chip_margin_horizontal">10dp</dimen>
+    <dimen name="screenshot_action_chip_margin_horizontal">4dp</dimen>
     <dimen name="screenshot_action_chip_padding_vertical">10dp</dimen>
-    <dimen name="screenshot_action_chip_padding_horizontal">15dp</dimen>
+    <dimen name="screenshot_action_chip_icon_size">20dp</dimen>
+    <dimen name="screenshot_action_chip_padding_start">8dp</dimen>
+    <!-- Padding between icon and text -->
+    <dimen name="screenshot_action_chip_padding_middle">8dp</dimen>
+    <dimen name="screenshot_action_chip_padding_end">16dp</dimen>
+    <dimen name="screenshot_action_chip_text_size">14sp</dimen>
 
 
     <!-- The width of the view containing navigation buttons -->
@@ -950,6 +968,9 @@
     <dimen name="cell_overlay_padding">18dp</dimen>
 
     <!-- Global actions power menu -->
+    <dimen name="global_actions_top_margin">12dp</dimen>
+    <dimen name="global_actions_plugin_offset">-145dp</dimen>
+
     <dimen name="global_actions_panel_width">120dp</dimen>
     <dimen name="global_actions_padding">12dp</dimen>
     <dimen name="global_actions_translate">9dp</dimen>
@@ -1065,6 +1086,10 @@
     <dimen name="logout_button_margin_bottom">12dp</dimen>
     <dimen name="logout_button_corner_radius">2dp</dimen>
 
+    <!--  Blur radius on status bar window and power menu  -->
+    <dimen name="min_window_blur_radius">1px</dimen>
+    <dimen name="max_window_blur_radius">100px</dimen>
+
     <!-- How much into a DisplayCutout's bounds we can go, on each side -->
     <dimen name="display_cutout_margin_consumption">0px</dimen>
     <!-- How much each bubble is elevated. -->
@@ -1072,13 +1097,15 @@
     <!-- How much the bubble flyout text container is elevated. -->
     <dimen name="bubble_flyout_elevation">4dp</dimen>
     <!-- How much padding is around the left and right sides of the flyout text. -->
-    <dimen name="bubble_flyout_padding_x">16dp</dimen>
+    <dimen name="bubble_flyout_padding_x">12dp</dimen>
     <!-- How much padding is around the top and bottom of the flyout text. -->
-    <dimen name="bubble_flyout_padding_y">8dp</dimen>
+    <dimen name="bubble_flyout_padding_y">10dp</dimen>
     <!-- Size of the triangle that points from the flyout to the bubble stack. -->
     <dimen name="bubble_flyout_pointer_size">6dp</dimen>
     <!-- How much space to leave between the flyout (tip of the arrow) and the bubble stack. -->
     <dimen name="bubble_flyout_space_from_bubble">8dp</dimen>
+    <!-- How much space to leave between the flyout text and the avatar displayed in the flyout. -->
+    <dimen name="bubble_flyout_avatar_message_space">6dp</dimen>
     <!-- Padding between status bar and bubbles when displayed in expanded state -->
     <dimen name="bubble_padding_top">16dp</dimen>
     <!-- Size of individual bubbles. -->
@@ -1151,4 +1178,5 @@
     <dimen name="magnifier_up_down_controls_width">45dp</dimen>
     <dimen name="magnifier_up_down_controls_height">40dp</dimen>
 
+    <dimen name="app_icon_size">32dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 4f532b7..44a7fda 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -194,6 +194,10 @@
     <!-- Power menu item for taking a screenshot [CHAR LIMIT=20]-->
     <string name="global_action_screenshot">Screenshot</string>
 
+    <!-- text to show in place of RemoteInput images when they cannot be shown.
+         [CHAR LIMIT=50] -->
+    <string name="remote_input_image_insertion_text">Image inserted</string>
+
     <!-- Notification ticker displayed when a screenshot is being saved to the Gallery. [CHAR LIMIT=30] -->
     <string name="screenshot_saving_ticker">Saving screenshot\u2026</string>
     <!-- Notification title displayed when a screenshot is being saved to the Gallery. [CHAR LIMIT=50] -->
@@ -222,6 +226,8 @@
     <string name="screenrecord_mic_label">Record voiceover</string>
     <!-- Label for the checkbox to enable showing location of touches during screen recording [CHAR LIMIT=NONE]-->
     <string name="screenrecord_taps_label">Show taps</string>
+    <!-- Label for notification that the user can tap to stop and save the screen recording [CHAR LIMIT=NONE] -->
+    <string name="screenrecord_stop_text">Tap to stop</string>
     <!-- Label for notification action to stop and save the screen recording [CHAR LIMIT=35] -->
     <string name="screenrecord_stop_label">Stop</string>
     <!-- Label for notification action to pause screen recording [CHAR LIMIT=35] -->
@@ -920,6 +926,10 @@
     <string name="quick_settings_dark_mode_secondary_label_on_at_sunset">On at sunset</string>
     <!-- QuickSettings: Secondary text for when the Dark Mode will be on until sunrise. [CHAR LIMIT=20] -->
     <string name="quick_settings_dark_mode_secondary_label_until_sunrise">Until sunrise</string>
+    <!-- QuickSettings: Secondary text for when the Dark theme will be enabled at some user-selected time. [CHAR LIMIT=20] -->
+    <string name="quick_settings_dark_mode_secondary_label_on_at">On at <xliff:g id="time" example="10 pm">%s</xliff:g></string>
+    <!-- QuickSettings: Secondary text for when the Dark theme or some other tile will be on until some user-selected time. [CHAR LIMIT=20] -->
+    <string name="quick_settings_dark_mode_secondary_label_until">Until <xliff:g id="time" example="7 am">%s</xliff:g></string>
 
     <!-- QuickSettings: NFC tile [CHAR LIMIT=NONE] -->
     <string name="quick_settings_nfc_label">NFC</string>
@@ -928,6 +938,13 @@
     <!-- QuickSettings: NFC (on) [CHAR LIMIT=NONE] -->
     <string name="quick_settings_nfc_on">NFC is enabled</string>
 
+    <!-- QuickSettings: Screen record tile [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_screen_record_label">Screen Record</string>
+    <!-- QuickSettings: Text to prompt the user to begin a new recording [CHAR LIMIT=20] -->
+    <string name="quick_settings_screen_record_start">Start</string>
+    <!-- QuickSettings: Text to prompt the user to stop an ongoing recording [CHAR LIMIT=20] -->
+    <string name="quick_settings_screen_record_stop">Stop</string>
+
     <!-- Recents: Text that shows above the navigation bar after launching a few apps. [CHAR LIMIT=NONE] -->
     <string name="recents_swipe_up_onboarding">Swipe up to switch apps</string>
     <!-- Recents: Text that shows above the navigation bar after launching several apps. [CHAR LIMIT=NONE] -->
@@ -1791,6 +1808,29 @@
     <string name="notification_done">Done</string>
     <!-- Notification: inline controls: undo block button -->
     <string name="inline_undo">Undo</string>
+    <!-- Notification: Conversation: control panel, label for button that demotes notification from conversation to normal notification -->
+    <string name="demote">Mark this notification as not a conversation</string>
+
+    <!-- [CHAR LIMIT=100] Mark this conversation as a favorite -->
+    <string name="notification_conversation_favorite">Favorite</string>
+
+    <!-- [CHAR LIMIT=100] Unmark this conversation as a favorite -->
+    <string name="notification_conversation_unfavorite">Unfavorite</string>
+
+    <!-- [CHAR LIMIT=100] Mute this conversation -->
+    <string name="notification_conversation_mute">Mute</string>
+
+    <!-- [CHAR LIMIT=100] Umute this conversation -->
+    <string name="notification_conversation_unmute">Unmute</string>
+
+    <!-- [CHAR LIMIT=100] Show notification as bubble -->
+    <string name="notification_conversation_bubble">Show as bubble</string>
+
+    <!-- [CHAR LIMIT=100] Turn off bubbles for notification -->
+    <string name="notification_conversation_unbubble">Turn off bubbles</string>
+
+    <!-- [CHAR LIMIT=100] Add this conversation to home screen -->
+    <string name="notification_conversation_home_screen">Add to home screen</string>
 
     <!-- Notification: Menu row: Content description for menu items. [CHAR LIMIT=NONE] -->
     <string name="notification_menu_accessibility"><xliff:g id="app_name" example="YouTube">%1$s</xliff:g> <xliff:g id="menu_description" example="notification controls">%2$s</xliff:g></string>
diff --git a/packages/SystemUI/scripts/update_shared_lib.sh b/packages/SystemUI/scripts/update_shared_lib.sh
new file mode 100755
index 0000000..25f723f
--- /dev/null
+++ b/packages/SystemUI/scripts/update_shared_lib.sh
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+NUM_ARGS=$#
+JAR_DESTINATION="$1/prebuilts/framework_intermediates/quickstep/libs/sysui_shared.jar"
+
+has_croot() {
+  declare -F croot > /dev/null
+  return $?
+}
+
+check_environment() {
+  if ! has_croot; then
+    echo "Run script in a shell that has had envsetup run. Run '. update_shared_lib.sh' from scripts directory"
+    return 1
+  fi
+
+  if [ $NUM_ARGS -ne 1 ]; then
+    echo "Usage: . update_shared_lib PATH_TO_UNBUNDLED_LAUNCER     e.g. . update_shared_lib ~/src/ub-launcher3-master"
+    return 1
+  fi
+  return 0
+}
+
+main() {
+  if check_environment ; then
+    pushd .
+    croot
+    mma -j16 SystemUISharedLib
+    cp out/target/product/$TARGET_PRODUCT/obj/JAVA_LIBRARIES/SystemUISharedLib_intermediates/javalib.jar $JAR_DESTINATION
+    popd
+  fi
+}
+
+main
+
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 4471818..fe6e44b 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -12,8 +12,22 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-android_library {
+genrule {
+    name: "statslog-SystemUI-java-gen",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --java $(out) --module sysui --javaPackage com.android.systemui.shared.system --javaClass SysUiStatsLog",
+    out: ["com/android/systemui/shared/system/SysUiStatsLog.java"],
+}
 
+java_library {
+    name: "SystemUI-statsd",
+
+    srcs: [
+        ":statslog-SystemUI-java-gen",
+    ],
+}
+
+android_library {
     name: "SystemUISharedLib",
     srcs: [
         "src/**/*.java",
@@ -21,7 +35,8 @@
     ],
 
     static_libs: [
-        "PluginCoreLib"
+        "PluginCoreLib",
+        "SystemUI-statsd",
     ],
 
     // Enforce that the library is built against java 7 so that there are
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/StatsLogCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/StatsLogCompat.java
deleted file mode 100644
index 59ed111..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/StatsLogCompat.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.systemui.shared.system;
-
-import android.util.StatsLog;
-
-/**
- * Wrapper class to make StatsLog hidden API accessible.
- */
-public class StatsLogCompat {
-
-    /**
-     * StatsLog.write(StatsLog.LAUNCHER_EVENT, int action, int src_state, int dst_state,
-     *                byte[] extension, boolean is_swipe_up_enabled);
-     */
-    public static void write(int action, int srcState, int dstState, byte [] extension,
-            boolean swipeUpEnabled) {
-        StatsLog.write(19, action, srcState, dstState, extension,
-                swipeUpEnabled);
-    }
-
-    /**
-     *  StatsLog.write(StatsLog.STYLE_EVENT, action, colorPackageHash,
-     *           fontPackageHash, shapePackageHash, clockPackageHash,
-     *           launcherGrid, wallpaperCategoryHash, wallpaperIdHash,
-     *           colorPreference, locationPreference);
-     */
-    public static void write(int action, int colorPackageHash,
-            int fontPackageHash, int shapePackageHash, int clockPackageHash,
-            int launcherGrid, int wallpaperCategoryHash, int wallpaperIdHash,
-            int colorPreference, int locationPreference) {
-        StatsLog.write(179, action, colorPackageHash,
-                fontPackageHash, shapePackageHash, clockPackageHash,
-                launcherGrid, wallpaperCategoryHash, wallpaperIdHash,
-                colorPreference, locationPreference);
-    }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/UniversalSmartspaceUtils.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/UniversalSmartspaceUtils.java
new file mode 100644
index 0000000..871cae3
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/UniversalSmartspaceUtils.java
@@ -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.systemui.shared.system;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.view.SurfaceView;
+
+/** Utility class that is shared between SysUI and Launcher for Universal Smartspace features. */
+public final class UniversalSmartspaceUtils {
+    public static final String ACTION_REQUEST_SMARTSPACE_VIEW =
+            "com.android.systemui.REQUEST_SMARTSPACE_VIEW";
+
+    private static final String SYSUI_PACKAGE = "com.android.systemui";
+    private static final String INTENT_KEY_INPUT_BUNDLE = "input_bundle";
+    private static final String BUNDLE_KEY_INPUT_TOKEN = "input_token";
+    private static final String INTENT_KEY_SURFACE_CONTROL = "surface_control";
+
+    /** Creates an intent to request that sysui draws the Smartspace to the SurfaceView. */
+    public static Intent createRequestSmartspaceIntent(SurfaceView surfaceView) {
+        Intent intent = new Intent(ACTION_REQUEST_SMARTSPACE_VIEW);
+
+        Bundle inputBundle = new Bundle();
+        inputBundle.putBinder(BUNDLE_KEY_INPUT_TOKEN, surfaceView.getHostToken());
+        return intent
+                .putExtra(INTENT_KEY_SURFACE_CONTROL, surfaceView.getSurfaceControl())
+                .putExtra(INTENT_KEY_INPUT_BUNDLE, inputBundle)
+                .setPackage(SYSUI_PACKAGE)
+                .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                        | Intent.FLAG_RECEIVER_FOREGROUND);
+    }
+
+    /**
+     * Retrieves the SurfaceControl from an Intent created by
+     * {@link #createRequestSmartspaceIntent(SurfaceView)}.
+     **/
+    public static SurfaceControl getSurfaceControl(Intent intent) {
+        return intent.getParcelableExtra(INTENT_KEY_SURFACE_CONTROL);
+    }
+
+    /**
+     * Retrieves the input token from an Intent created by
+     * {@link #createRequestSmartspaceIntent(SurfaceView)}.
+     **/
+    public static IBinder getInputToken(Intent intent) {
+        Bundle inputBundle = intent.getBundleExtra(INTENT_KEY_INPUT_BUNDLE);
+        return inputBundle == null ? null : inputBundle.getBinder(BUNDLE_KEY_INPUT_TOKEN);
+    }
+
+    private UniversalSmartspaceUtils() {}
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
new file mode 100644
index 0000000..b2423b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.keyguard;
+
+import android.annotation.Nullable;
+import android.app.admin.IKeyguardCallback;
+import android.app.admin.IKeyguardClient;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.SurfaceControl;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.ViewGroup;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Encapsulates all logic for secondary lockscreen state management.
+ */
+public class AdminSecondaryLockScreenController {
+    private static final String TAG = "AdminSecondaryLockScreenController";
+    private static final int REMOTE_CONTENT_READY_TIMEOUT_MILLIS = 500;
+    private final KeyguardUpdateMonitor mUpdateMonitor;
+    private final Context mContext;
+    private final ViewGroup mParent;
+    private AdminSecurityView mView;
+    private Handler mHandler;
+    private IKeyguardClient mClient;
+    private KeyguardSecurityCallback mKeyguardCallback;
+    private SurfaceControl.Transaction mTransaction;
+
+    private final ServiceConnection mConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            mClient = IKeyguardClient.Stub.asInterface(service);
+            if (mView.isAttachedToWindow() && mClient != null) {
+                onSurfaceReady();
+
+                try {
+                    service.linkToDeath(mKeyguardClientDeathRecipient, 0);
+                } catch (RemoteException e) {
+                    // Failed to link to death, just dismiss and unbind the service for now.
+                    Log.e(TAG, "Lost connection to secondary lockscreen service", e);
+                    dismiss(KeyguardUpdateMonitor.getCurrentUser());
+                }
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName className) {
+            mClient = null;
+        }
+    };
+
+    private final IBinder.DeathRecipient mKeyguardClientDeathRecipient = () -> {
+        hide(); // hide also takes care of unlinking to death.
+        Log.d(TAG, "KeyguardClient service died");
+    };
+
+    private final IKeyguardCallback mCallback = new IKeyguardCallback.Stub() {
+        @Override
+        public void onDismiss() {
+            dismiss(UserHandle.getCallingUserId());
+        }
+
+        @Override
+        public void onSurfaceControlCreated(@Nullable SurfaceControl remoteSurfaceControl) {
+            if (mHandler != null) {
+                mHandler.removeCallbacksAndMessages(null);
+            }
+            if (remoteSurfaceControl != null) {
+                mTransaction.reparent(remoteSurfaceControl, mView.getSurfaceControl())
+                    .apply();
+            } else {
+                dismiss(KeyguardUpdateMonitor.getCurrentUser());
+            }
+        }
+    };
+
+    private final KeyguardUpdateMonitorCallback mUpdateCallback =
+            new KeyguardUpdateMonitorCallback() {
+                @Override
+                public void onSecondaryLockscreenRequirementChanged(int userId) {
+                    Intent newIntent = mUpdateMonitor.getSecondaryLockscreenRequirement(userId);
+                    if (newIntent == null) {
+                        dismiss(userId);
+                    }
+                }
+            };
+
+    @VisibleForTesting
+    protected SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
+        @Override
+        public void surfaceCreated(SurfaceHolder holder) {
+            final int userId = KeyguardUpdateMonitor.getCurrentUser();
+            mUpdateMonitor.registerCallback(mUpdateCallback);
+
+            if (mClient != null) {
+                onSurfaceReady();
+            }
+            mHandler.postDelayed(
+                    () -> {
+                        // If the remote content is not readied within the timeout period,
+                        // move on without the secondary lockscreen.
+                        dismiss(userId);
+                    },
+                    REMOTE_CONTENT_READY_TIMEOUT_MILLIS);
+        }
+
+        @Override
+        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
+
+        @Override
+        public void surfaceDestroyed(SurfaceHolder holder) {
+            mUpdateMonitor.removeCallback(mUpdateCallback);
+        }
+    };
+
+    public AdminSecondaryLockScreenController(Context context, ViewGroup parent,
+            KeyguardUpdateMonitor updateMonitor, KeyguardSecurityCallback callback,
+            Handler handler, SurfaceControl.Transaction transaction) {
+        mContext = context;
+        mHandler = handler;
+        mParent = parent;
+        mTransaction = transaction;
+        mUpdateMonitor = updateMonitor;
+        mKeyguardCallback = callback;
+        mView = new AdminSecurityView(mContext, mSurfaceHolderCallback);
+    }
+
+    /**
+     * Displays the Admin security Surface view.
+     */
+    public void show(Intent serviceIntent) {
+        mContext.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE);
+        mParent.addView(mView);
+    }
+
+    /**
+     * Hides the Admin security Surface view.
+     */
+    public void hide() {
+        if (mView.isAttachedToWindow()) {
+            mParent.removeView(mView);
+        }
+        if (mClient != null) {
+            mClient.asBinder().unlinkToDeath(mKeyguardClientDeathRecipient, 0);
+            mContext.unbindService(mConnection);
+            mClient = null;
+        }
+    }
+
+    private void onSurfaceReady() {
+        try {
+            mClient.onSurfaceReady(mView.getHostToken(), mCallback);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error in onSurfaceReady", e);
+            dismiss(KeyguardUpdateMonitor.getCurrentUser());
+        }
+    }
+
+    private void dismiss(int userId) {
+        mHandler.removeCallbacksAndMessages(null);
+        if (mView != null && mView.isAttachedToWindow()
+                && userId == KeyguardUpdateMonitor.getCurrentUser()) {
+            hide();
+            mKeyguardCallback.dismiss(true, userId);
+        }
+    }
+
+    /**
+     * Custom {@link SurfaceView} used to allow a device admin to present an additional security
+     * screen.
+     */
+    private class AdminSecurityView extends SurfaceView {
+        private SurfaceHolder.Callback mSurfaceHolderCallback;
+
+        AdminSecurityView(Context context, SurfaceHolder.Callback surfaceHolderCallback) {
+            super(context);
+            mSurfaceHolderCallback = surfaceHolderCallback;
+            setZOrderOnTop(true);
+        }
+
+        @Override
+        protected void onAttachedToWindow() {
+            super.onAttachedToWindow();
+            getHolder().addCallback(mSurfaceHolderCallback);
+        }
+
+        @Override
+        protected void onDetachedFromWindow() {
+            super.onDetachedFromWindow();
+            getHolder().removeCallback(mSurfaceHolderCallback);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
index 4a5bc2a..0106609 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -39,7 +39,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.internal.telephony.TelephonyIntents;
 import com.android.settingslib.WirelessUtils;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
@@ -337,15 +336,15 @@
                 CharSequence text =
                         getContext().getText(com.android.internal.R.string.emergency_calls_only);
                 Intent i = getContext().registerReceiver(null,
-                        new IntentFilter(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION));
+                        new IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED));
                 if (i != null) {
                     String spn = "";
                     String plmn = "";
-                    if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false)) {
-                        spn = i.getStringExtra(TelephonyIntents.EXTRA_SPN);
+                    if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_SPN, false)) {
+                        spn = i.getStringExtra(TelephonyManager.EXTRA_SPN);
                     }
-                    if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false)) {
-                        plmn = i.getStringExtra(TelephonyIntents.EXTRA_PLMN);
+                    if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false)) {
+                        plmn = i.getStringExtra(TelephonyManager.EXTRA_PLMN);
                     }
                     if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn);
                     if (Objects.equals(plmn, spn)) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index df0dc46..e475ef1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -387,8 +387,8 @@
      * Refresh the time of the clock, due to either time tick broadcast or doze time tick alarm.
      */
     public void refresh() {
-        mClockView.refresh();
-        mClockViewBold.refresh();
+        mClockView.refreshTime();
+        mClockViewBold.refreshTime();
         if (mClockPlugin != null) {
             mClockPlugin.onTimeTick();
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 4e7956d..571c4ae 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -290,7 +290,7 @@
                     View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                             | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                             | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
-            getWindow().setFitWindowInsetsTypes(0 /* types */);
+            getWindow().getAttributes().setFitInsetsTypes(0 /* types */);
             getWindow().setNavigationBarContrastEnforced(false);
             getWindow().setNavigationBarColor(Color.TRANSPARENT);
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 91b22d1..ae78726 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -21,17 +21,20 @@
 import android.app.AlertDialog;
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.ColorStateList;
 import android.graphics.Rect;
 import android.metrics.LogMaker;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.UserHandle;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Slog;
-import android.util.StatsLog;
 import android.util.TypedValue;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
+import android.view.SurfaceControl;
 import android.view.VelocityTracker;
 import android.view.View;
 import android.view.ViewConfiguration;
@@ -50,6 +53,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SystemUIFactory;
+import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.InjectionInflationController;
 
@@ -90,6 +94,7 @@
     private AlertDialog mAlertDialog;
     private InjectionInflationController mInjectionInflationController;
     private boolean mSwipeUpToRetry;
+    private AdminSecondaryLockScreenController mSecondaryLockScreenController;
 
     private final ViewConfiguration mViewConfiguration;
     private final SpringAnimation mSpringAnimation;
@@ -137,6 +142,9 @@
             SystemUIFactory.getInstance().getRootComponent());
         mViewConfiguration = ViewConfiguration.get(context);
         mKeyguardStateController = Dependency.get(KeyguardStateController.class);
+        mSecondaryLockScreenController = new AdminSecondaryLockScreenController(context, this,
+                mUpdateMonitor, mCallback, new Handler(Looper.myLooper()),
+                new SurfaceControl.Transaction());
     }
 
     public void setSecurityCallback(SecurityCallback callback) {
@@ -157,6 +165,7 @@
             mAlertDialog.dismiss();
             mAlertDialog = null;
         }
+        mSecondaryLockScreenController.hide();
         if (mCurrentSecuritySelection != SecurityMode.None) {
             getSecurityView(mCurrentSecuritySelection).onPause();
         }
@@ -532,6 +541,15 @@
                     break;
             }
         }
+        // Check for device admin specified additional security measures.
+        if (finish) {
+            Intent secondaryLockscreenIntent =
+                    mUpdateMonitor.getSecondaryLockscreenRequirement(targetUserId);
+            if (secondaryLockscreenIntent != null) {
+                mSecondaryLockScreenController.show(secondaryLockscreenIntent);
+                return false;
+            }
+        }
         if (eventSubtype != -1) {
             mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER)
                     .setType(MetricsEvent.TYPE_DISMISS).setSubtype(eventSubtype));
@@ -615,8 +633,8 @@
 
         public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) {
             if (success) {
-                StatsLog.write(StatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED,
-                    StatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__SUCCESS);
+                SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED,
+                        SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__SUCCESS);
                 mLockPatternUtils.reportSuccessfulPasswordAttempt(userId);
                 // Force a garbage collection in an attempt to erase any lockscreen password left in
                 // memory. Do it asynchronously with a 5-sec delay to avoid making the keyguard
@@ -628,8 +646,8 @@
                     Runtime.getRuntime().gc();
                 });
             } else {
-                StatsLog.write(StatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED,
-                    StatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__FAILURE);
+                SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED,
+                        SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__FAILURE);
                 KeyguardSecurityContainer.this.reportFailedUnlockAttempt(userId, timeoutMs);
             }
             mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER)
@@ -751,6 +769,5 @@
     public void showUsabilityHint() {
         mSecurityViewFlipper.showUsabilityHint();
     }
-
 }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
index 35a65aa..76adf04 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
@@ -16,6 +16,7 @@
 
 package com.android.keyguard;
 
+import android.annotation.NonNull;
 import android.app.AlertDialog;
 import android.app.AlertDialog.Builder;
 import android.app.Dialog;
@@ -26,6 +27,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Color;
+import android.telephony.PinResult;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -35,7 +37,6 @@
 import android.view.WindowManager;
 import android.widget.ImageView;
 
-import com.android.internal.telephony.PhoneConstants;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 
@@ -139,11 +140,11 @@
 
         // Sending empty PIN here to query the number of remaining PIN attempts
         new CheckSimPin("", mSubId) {
-            void onSimCheckResponse(final int result, final int attemptsRemaining) {
-                Log.d(LOG_TAG, "onSimCheckResponse " + " dummy One result" + result +
-                        " attemptsRemaining=" + attemptsRemaining);
-                if (attemptsRemaining >= 0) {
-                    mRemainingAttempts = attemptsRemaining;
+            void onSimCheckResponse(final PinResult result) {
+                Log.d(LOG_TAG, "onSimCheckResponse " + " dummy One result "
+                        + result.toString());
+                if (result.getAttemptsRemaining() >= 0) {
+                    mRemainingAttempts = result.getAttemptsRemaining();
                     setLockedSimMessage();
                 }
             }
@@ -251,7 +252,7 @@
             mSubId = subId;
         }
 
-        abstract void onSimCheckResponse(final int result, final int attemptsRemaining);
+        abstract void onSimCheckResponse(@NonNull PinResult result);
 
         @Override
         public void run() {
@@ -261,23 +262,23 @@
             TelephonyManager telephonyManager =
                     ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE))
                             .createForSubscriptionId(mSubId);
-            final int[] result = telephonyManager.supplyPinReportResult(mPin);
-            if (result == null || result.length == 0) {
+            final PinResult result = telephonyManager.supplyPinReportPinResult(mPin);
+            if (result == null) {
                 Log.e(TAG, "Error result for supplyPinReportResult.");
                 post(new Runnable() {
                     @Override
                     public void run() {
-                        onSimCheckResponse(PhoneConstants.PIN_GENERAL_FAILURE, -1);
+                        onSimCheckResponse(PinResult.getDefaultFailedResult());
                     }
                 });
             } else {
                 if (DEBUG) {
-                    Log.v(TAG, "supplyPinReportResult returned: " + result[0] + " " + result[1]);
+                    Log.v(TAG, "supplyPinReportResult returned: " + result.toString());
                 }
                 post(new Runnable() {
                     @Override
                     public void run() {
-                        onSimCheckResponse(result[0], result[1]);
+                        onSimCheckResponse(result);
                     }
                 });
             }
@@ -330,17 +331,18 @@
         if (mCheckSimPinThread == null) {
             mCheckSimPinThread = new CheckSimPin(mPasswordEntry.getText(), mSubId) {
                 @Override
-                void onSimCheckResponse(final int result, final int attemptsRemaining) {
+                void onSimCheckResponse(final PinResult result) {
                     post(new Runnable() {
                         @Override
                         public void run() {
-                            mRemainingAttempts = attemptsRemaining;
+                            mRemainingAttempts = result.getAttemptsRemaining();
                             if (mSimUnlockProgressDialog != null) {
                                 mSimUnlockProgressDialog.hide();
                             }
                             resetPasswordText(true /* animate */,
-                                    result != PhoneConstants.PIN_RESULT_SUCCESS /* announce */);
-                            if (result == PhoneConstants.PIN_RESULT_SUCCESS) {
+                                    /* announce */
+                                    result.getType() != PinResult.PIN_RESULT_TYPE_SUCCESS);
+                            if (result.getType() == PinResult.PIN_RESULT_TYPE_SUCCESS) {
                                 Dependency.get(KeyguardUpdateMonitor.class)
                                         .reportSimUnlocked(mSubId);
                                 mRemainingAttempts = -1;
@@ -350,14 +352,16 @@
                                 }
                             } else {
                                 mShowDefaultMessage = false;
-                                if (result == PhoneConstants.PIN_PASSWORD_INCORRECT) {
-                                    if (attemptsRemaining <= 2) {
+                                if (result.getType() == PinResult.PIN_RESULT_TYPE_INCORRECT) {
+                                    if (result.getAttemptsRemaining() <= 2) {
                                         // this is getting critical - show dialog
-                                        getSimRemainingAttemptsDialog(attemptsRemaining).show();
+                                        getSimRemainingAttemptsDialog(
+                                                result.getAttemptsRemaining()).show();
                                     } else {
                                         // show message
                                         mSecurityMessageDisplay.setMessage(
-                                                getPinPasswordErrorMessage(attemptsRemaining, false));
+                                                getPinPasswordErrorMessage(
+                                                        result.getAttemptsRemaining(), false));
                                     }
                                 } else {
                                     // "PIN operation failed!" - no idea what this was and no way to
@@ -367,7 +371,7 @@
                                 }
                                 if (DEBUG) Log.d(LOG_TAG, "verifyPasswordAndUnlock "
                                         + " CheckSimPin.onSimCheckResponse: " + result
-                                        + " attemptsRemaining=" + attemptsRemaining);
+                                        + " attemptsRemaining=" + result.getAttemptsRemaining());
                             }
                             mCallback.userActivity();
                             mCheckSimPinThread = null;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
index dc68115..10dcbd6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
@@ -16,6 +16,7 @@
 
 package com.android.keyguard;
 
+import android.annotation.NonNull;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
@@ -25,6 +26,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Color;
+import android.telephony.PinResult;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -34,7 +36,6 @@
 import android.view.WindowManager;
 import android.widget.ImageView;
 
-import com.android.internal.telephony.PhoneConstants;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 
@@ -191,13 +192,16 @@
 
         // Sending empty PUK here to query the number of remaining PIN attempts
         new CheckSimPuk("", "", mSubId) {
-            void onSimLockChangedResponse(final int result, final int attemptsRemaining) {
-                Log.d(LOG_TAG, "onSimCheckResponse " + " dummy One result" + result +
-                        " attemptsRemaining=" + attemptsRemaining);
-                if (attemptsRemaining >= 0) {
-                    mRemainingAttempts = attemptsRemaining;
-                    mSecurityMessageDisplay.setMessage(
-                            getPukPasswordErrorMessage(attemptsRemaining, true));
+            void onSimLockChangedResponse(final PinResult result) {
+                if (result == null) Log.e(LOG_TAG, "onSimCheckResponse, pin result is NULL");
+                else {
+                    Log.d(LOG_TAG, "onSimCheckResponse " + " dummy One result "
+                            + result.toString());
+                    if (result.getAttemptsRemaining() >= 0) {
+                        mRemainingAttempts = result.getAttemptsRemaining();
+                        mSecurityMessageDisplay.setMessage(
+                                getPukPasswordErrorMessage(result.getAttemptsRemaining(), true));
+                    }
                 }
             }
         }.start();
@@ -311,7 +315,7 @@
             mSubId = subId;
         }
 
-        abstract void onSimLockChangedResponse(final int result, final int attemptsRemaining);
+        abstract void onSimLockChangedResponse(@NonNull PinResult result);
 
         @Override
         public void run() {
@@ -319,23 +323,23 @@
             TelephonyManager telephonyManager =
                     ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE))
                             .createForSubscriptionId(mSubId);
-            final int[] result = telephonyManager.supplyPukReportResult(mPuk, mPin);
-            if (result == null || result.length == 0) {
+            final PinResult result = telephonyManager.supplyPukReportPinResult(mPuk, mPin);
+            if (result == null) {
                 Log.e(TAG, "Error result for supplyPukReportResult.");
                 post(new Runnable() {
                     @Override
                     public void run() {
-                        onSimLockChangedResponse(PhoneConstants.PIN_GENERAL_FAILURE, -1);
+                        onSimLockChangedResponse(PinResult.getDefaultFailedResult());
                     }
                 });
             } else {
                 if (DEBUG) {
-                    Log.v(TAG, "supplyPukReportResult returned: " + result[0] + " " + result[1]);
+                    Log.v(TAG, "supplyPukReportResult returned: " + result.toString());
                 }
                 post(new Runnable() {
                     @Override
                     public void run() {
-                        onSimLockChangedResponse(result[0], result[1]);
+                        onSimLockChangedResponse(result);
                     }
                 });
             }
@@ -402,7 +406,7 @@
         if (mCheckSimPukThread == null) {
             mCheckSimPukThread = new CheckSimPuk(mPukText, mPinText, mSubId) {
                 @Override
-                void onSimLockChangedResponse(final int result, final int attemptsRemaining) {
+                void onSimLockChangedResponse(final PinResult result) {
                     post(new Runnable() {
                         @Override
                         public void run() {
@@ -410,29 +414,32 @@
                                 mSimUnlockProgressDialog.hide();
                             }
                             resetPasswordText(true /* animate */,
-                                    result != PhoneConstants.PIN_RESULT_SUCCESS /* announce */);
-                            if (result == PhoneConstants.PIN_RESULT_SUCCESS) {
+                                    /* announce */
+                                    result.getType() != PinResult.PIN_RESULT_TYPE_SUCCESS);
+                            if (result.getType() == PinResult.PIN_RESULT_TYPE_SUCCESS) {
                                 Dependency.get(KeyguardUpdateMonitor.class)
                                         .reportSimUnlocked(mSubId);
                                 mRemainingAttempts = -1;
                                 mShowDefaultMessage = true;
                                 if (mCallback != null) {
-                                    mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+                                    mCallback.dismiss(true,
+                                            KeyguardUpdateMonitor.getCurrentUser());
                                 }
                             } else {
                                 mShowDefaultMessage = false;
-                                if (result == PhoneConstants.PIN_PASSWORD_INCORRECT) {
+                                if (result.getType() == PinResult.PIN_RESULT_TYPE_INCORRECT) {
                                     // show message
                                     mSecurityMessageDisplay.setMessage(getPukPasswordErrorMessage(
-                                            attemptsRemaining, false));
-                                    if (attemptsRemaining <= 2) {
+                                            result.getAttemptsRemaining(), false));
+                                    if (result.getAttemptsRemaining() <= 2) {
                                         // this is getting critical - show dialog
-                                        getPukRemainingAttemptsDialog(attemptsRemaining).show();
+                                        getPukRemainingAttemptsDialog(
+                                                result.getAttemptsRemaining()).show();
                                     } else {
                                         // show message
                                         mSecurityMessageDisplay.setMessage(
                                                 getPukPasswordErrorMessage(
-                                                attemptsRemaining, false));
+                                                        result.getAttemptsRemaining(), false));
                                     }
                                 } else {
                                     mSecurityMessageDisplay.setMessage(getContext().getString(
@@ -440,7 +447,7 @@
                                 }
                                 if (DEBUG) Log.d(LOG_TAG, "verifyPasswordAndUnlock "
                                         + " UpdateSim.onSimCheckResponse: "
-                                        + " attemptsRemaining=" + attemptsRemaining);
+                                        + " attemptsRemaining=" + result.getAttemptsRemaining());
                                 mStateMachine.reset();
                             }
                             mCheckSimPukThread = null;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index 5d35169..f61f585 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -140,6 +140,13 @@
         mLayoutTransition.setAnimateParentHierarchy(false);
     }
 
+    // Temporary workaround to allow KeyguardStatusView to inflate a copy for Universal Smartspace.
+    // Eventually the existing copy will be reparented instead, and we won't need this.
+    public KeyguardSliceView(Context context, AttributeSet attributeSet) {
+        this(context, attributeSet, Dependency.get(ActivityStarter.class),
+                Dependency.get(ConfigurationController.class));
+    }
+
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 5a1c997..61caf3b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -18,10 +18,15 @@
 
 import android.app.ActivityManager;
 import android.app.IActivityManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.res.Resources;
 import android.graphics.Color;
+import android.graphics.PixelFormat;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.text.TextUtils;
@@ -30,7 +35,11 @@
 import android.util.Log;
 import android.util.Slog;
 import android.util.TypedValue;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
 import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowlessWindowManager;
 import android.widget.GridLayout;
 import android.widget.LinearLayout;
 import android.widget.TextView;
@@ -40,6 +49,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.shared.system.UniversalSmartspaceUtils;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import java.io.FileDescriptor;
@@ -76,6 +86,7 @@
     private int mIconTopMargin;
     private int mIconTopMarginWithHeader;
     private boolean mShowingHeader;
+    private SurfaceControlViewHost mUniversalSmartspaceViewHost;
 
     private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
 
@@ -122,6 +133,38 @@
         }
     };
 
+    private BroadcastReceiver mUniversalSmartspaceBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent i) {
+            // TODO(b/148159743): Restrict to Pixel Launcher.
+            if (UniversalSmartspaceUtils.ACTION_REQUEST_SMARTSPACE_VIEW.equals(i.getAction())) {
+                if (mUniversalSmartspaceViewHost != null) {
+                    mUniversalSmartspaceViewHost.die();
+                }
+                SurfaceControl surfaceControl = UniversalSmartspaceUtils.getSurfaceControl(i);
+                if (surfaceControl != null) {
+                    IBinder input = UniversalSmartspaceUtils.getInputToken(i);
+
+                    WindowlessWindowManager windowlessWindowManager =
+                            new WindowlessWindowManager(context.getResources().getConfiguration(),
+                                    surfaceControl, input);
+                    mUniversalSmartspaceViewHost = new SurfaceControlViewHost(context,
+                            context.getDisplay(), windowlessWindowManager);
+                    WindowManager.LayoutParams layoutParams =
+                            new WindowManager.LayoutParams(
+                                    surfaceControl.getWidth(),
+                                    surfaceControl.getHeight(),
+                                    WindowManager.LayoutParams.TYPE_APPLICATION,
+                                    0,
+                                    PixelFormat.TRANSPARENT);
+
+                    mUniversalSmartspaceViewHost.addView(
+                            inflate(context, R.layout.keyguard_status_area, null), layoutParams);
+                }
+            }
+        }
+    };;
+
     public KeyguardStatusView(Context context) {
         this(context, null, 0);
     }
@@ -316,6 +359,8 @@
         super.onAttachedToWindow();
         Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mInfoCallback);
         Dependency.get(ConfigurationController.class).addCallback(this);
+        getContext().registerReceiver(mUniversalSmartspaceBroadcastReceiver,
+                new IntentFilter(UniversalSmartspaceUtils.ACTION_REQUEST_SMARTSPACE_VIEW));
     }
 
     @Override
@@ -323,6 +368,7 @@
         super.onDetachedFromWindow();
         Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mInfoCallback);
         Dependency.get(ConfigurationController.class).removeCallback(this);
+        getContext().unregisterReceiver(mUniversalSmartspaceBroadcastReceiver);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 431862f..58a6c17 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -31,8 +31,8 @@
 import static android.os.BatteryManager.EXTRA_PLUGGED;
 import static android.os.BatteryManager.EXTRA_STATUS;
 import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE;
+import static android.telephony.TelephonyManager.MODEM_COUNT_DUAL_MODEM;
 
-import static com.android.internal.telephony.PhoneConstants.MAX_PHONE_COUNT_DUAL_SIM;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
@@ -92,8 +92,6 @@
 import android.util.SparseBooleanArray;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.settingslib.WirelessUtils;
 import com.android.systemui.DejankUtils;
@@ -114,6 +112,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Map.Entry;
 import java.util.TimeZone;
 import java.util.function.Consumer;
@@ -335,6 +334,7 @@
     private SparseBooleanArray mUserFingerprintAuthenticated = new SparseBooleanArray();
     private SparseBooleanArray mUserFaceAuthenticated = new SparseBooleanArray();
     private SparseBooleanArray mUserFaceUnlockRunning = new SparseBooleanArray();
+    private Map<Integer, Intent> mSecondaryLockscreenRequirement = new HashMap<Integer, Intent>();
 
     private static int sCurrentUser;
     private Runnable mUpdateBiometricListeningState = this::updateBiometricListeningState;
@@ -446,7 +446,7 @@
      */
     public List<SubscriptionInfo> getFilteredSubscriptionInfo(boolean forceReload) {
         List<SubscriptionInfo> subscriptions = getSubscriptionInfo(false);
-        if (subscriptions.size() == MAX_PHONE_COUNT_DUAL_SIM) {
+        if (subscriptions.size() == MODEM_COUNT_DUAL_MODEM) {
             SubscriptionInfo info1 = subscriptions.get(0);
             SubscriptionInfo info2 = subscriptions.get(1);
             if (info1.getGroupUuid() != null && info1.getGroupUuid().equals(info2.getGroupUuid())) {
@@ -929,6 +929,45 @@
         return mUserTrustIsManaged.get(userId) && !isTrustDisabled(userId);
     }
 
+    private void updateSecondaryLockscreenRequirement(int userId) {
+        Intent oldIntent = mSecondaryLockscreenRequirement.get(userId);
+        boolean enabled = mDevicePolicyManager.isSecondaryLockscreenEnabled(userId);
+        boolean changed = false;
+
+        if (enabled && (oldIntent == null)) {
+            ResolveInfo resolveInfo =
+                    mContext.getPackageManager().resolveService(
+                            new Intent(
+                                    DevicePolicyManager.ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE),
+                            0);
+            if (resolveInfo != null) {
+                Intent newIntent = new Intent();
+                newIntent.setComponent(resolveInfo.serviceInfo.getComponentName());
+                mSecondaryLockscreenRequirement.put(userId, newIntent);
+                changed = true;
+            }
+        } else if (!enabled && (oldIntent != null)) {
+            mSecondaryLockscreenRequirement.put(userId, null);
+            changed = true;
+        }
+        if (changed) {
+            for (int i = 0; i < mCallbacks.size(); i++) {
+                KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+                if (cb != null) {
+                    cb.onSecondaryLockscreenRequirementChanged(userId);
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns an Intent by which to bind to a service that will provide additional security screen
+     * content that must be shown prior to dismissing the keyguard for this user.
+     */
+    public Intent getSecondaryLockscreenRequirement(int userId) {
+        return mSecondaryLockscreenRequirement.get(userId);
+    }
+
     /**
      * Cached version of {@link TrustManager#isTrustUsuallyManaged(int)}.
      */
@@ -1043,13 +1082,13 @@
                         MSG_BATTERY_UPDATE, new BatteryStatus(status, level, plugged, health,
                                 maxChargingMicroWatt));
                 mHandler.sendMessage(msg);
-            } else if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(action)) {
+            } else if (Intent.ACTION_SIM_STATE_CHANGED.equals(action)) {
                 SimData args = SimData.fromIntent(intent);
                 // ACTION_SIM_STATE_CHANGED is rebroadcast after unlocking the device to
                 // keep compatibility with apps that aren't direct boot aware.
                 // SysUI should just ignore this broadcast because it was already received
                 // and processed previously.
-                if (intent.getBooleanExtra(TelephonyIntents.EXTRA_REBROADCAST_ON_UNLOCK, false)) {
+                if (intent.getBooleanExtra(Intent.EXTRA_REBROADCAST_ON_UNLOCK, false)) {
                     // Guarantee mTelephonyCapable state after SysUI crash and restart
                     if (args.simState == TelephonyManager.SIM_STATE_ABSENT) {
                         mHandler.obtainMessage(MSG_TELEPHONY_CAPABLE, true).sendToTarget();
@@ -1074,7 +1113,7 @@
                 mHandler.sendEmptyMessage(MSG_AIRPLANE_MODE_CHANGED);
             } else if (Intent.ACTION_SERVICE_STATE.equals(action)) {
                 ServiceState serviceState = ServiceState.newFromBundle(intent.getExtras());
-                int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+                int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
                         SubscriptionManager.INVALID_SUBSCRIPTION_ID);
                 if (DEBUG) {
                     Log.v(TAG, "action " + action + " serviceState=" + serviceState + " subId="
@@ -1082,7 +1121,7 @@
                 }
                 mHandler.sendMessage(
                         mHandler.obtainMessage(MSG_SERVICE_STATE_CHANGE, subId, 0, serviceState));
-            } else if (TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED.equals(action)) {
+            } else if (TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED.equals(action)) {
                 mHandler.sendEmptyMessage(MSG_SIM_SUBSCRIPTION_INFO_CHANGED);
             } else if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(
                     action)) {
@@ -1114,7 +1153,8 @@
                         getSendingUserId()));
             } else if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED
                     .equals(action)) {
-                mHandler.sendEmptyMessage(MSG_DPM_STATE_CHANGED);
+                mHandler.sendMessage(mHandler.obtainMessage(MSG_DPM_STATE_CHANGED,
+                        getSendingUserId()));
             } else if (ACTION_USER_UNLOCKED.equals(action)) {
                 mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_UNLOCKED,
                         getSendingUserId(), 0));
@@ -1232,12 +1272,12 @@
 
         static SimData fromIntent(Intent intent) {
             int state;
-            if (!TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(intent.getAction())) {
+            if (!Intent.ACTION_SIM_STATE_CHANGED.equals(intent.getAction())) {
                 throw new IllegalArgumentException("only handles intent ACTION_SIM_STATE_CHANGED");
             }
             String stateExtra = intent.getStringExtra(Intent.EXTRA_SIM_STATE);
-            int slotId = intent.getIntExtra(PhoneConstants.PHONE_KEY, 0);
-            int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+            int slotId = intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 0);
+            int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
                     SubscriptionManager.INVALID_SUBSCRIPTION_ID);
             if (Intent.SIM_STATE_ABSENT.equals(stateExtra)) {
                 final String absentReason = intent
@@ -1531,7 +1571,7 @@
                         handleDeviceProvisioned();
                         break;
                     case MSG_DPM_STATE_CHANGED:
-                        handleDevicePolicyManagerStateChanged();
+                        handleDevicePolicyManagerStateChanged(msg.arg1);
                         break;
                     case MSG_USER_SWITCHING:
                         handleUserSwitching(msg.arg1, (IRemoteCallback) msg.obj);
@@ -1632,13 +1672,13 @@
         filter.addAction(Intent.ACTION_BATTERY_CHANGED);
         filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
         filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-        filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+        filter.addAction(Intent.ACTION_SIM_STATE_CHANGED);
         filter.addAction(Intent.ACTION_SERVICE_STATE);
         filter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
         filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
         filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
         filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
-        broadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, mHandler);
+        broadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter, mHandler);
 
         final IntentFilter allUserFilter = new IntentFilter();
         allUserFilter.addAction(Intent.ACTION_USER_INFO_CHANGED);
@@ -1649,8 +1689,8 @@
         allUserFilter.addAction(ACTION_USER_UNLOCKED);
         allUserFilter.addAction(ACTION_USER_STOPPED);
         allUserFilter.addAction(ACTION_USER_REMOVED);
-        broadcastDispatcher.registerReceiver(mBroadcastAllReceiver, allUserFilter, mHandler,
-                UserHandle.ALL);
+        broadcastDispatcher.registerReceiverWithHandler(mBroadcastAllReceiver, allUserFilter,
+                mHandler, UserHandle.ALL);
 
         mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionListener);
         try {
@@ -1707,6 +1747,7 @@
         mUserIsUnlocked.put(user, mUserManager.isUserUnlocked(user));
         mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
         mLogoutEnabled = mDevicePolicyManager.isLogoutEnabled();
+        updateSecondaryLockscreenRequirement(user);
         List<UserInfo> allUsers = mUserManager.getUsers();
         for (UserInfo userInfo : allUsers) {
             mUserTrustIsUsuallyManaged.put(userInfo.id,
@@ -2047,9 +2088,10 @@
     /**
      * Handle {@link #MSG_DPM_STATE_CHANGED}
      */
-    private void handleDevicePolicyManagerStateChanged() {
+    private void handleDevicePolicyManagerStateChanged(int userId) {
         checkIsHandlerThread();
         updateFingerprintListeningState();
+        updateSecondaryLockscreenRequirement(userId);
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 04502f0..8e87b7a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -310,4 +310,9 @@
      */
     public void onBiometricsCleared() { }
 
+    /**
+     * Called when the secondary lock screen requirement changes.
+     */
+    public void onSecondaryLockscreenRequirementChanged(int userId) { }
+
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
index eba2400..99e122e 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
@@ -188,7 +188,7 @@
     public void onTimeTick() {
         mAnalogClock.onTimeChanged();
         mBigClockView.onTimeChanged();
-        mLockClock.refresh();
+        mLockClock.refreshTime();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
index 3a2fbe5..fac923c01 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
@@ -195,7 +195,7 @@
     public void onTimeTick() {
         mAnalogClock.onTimeChanged();
         mView.onTimeChanged();
-        mLockClock.refresh();
+        mLockClock.refreshTime();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index d68fe15..a46ab3a 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -288,7 +288,8 @@
     @Override
     public void onTuningChanged(String key, String newValue) {
         if (StatusBarIconController.ICON_BLACKLIST.equals(key)) {
-            ArraySet<String> icons = StatusBarIconController.getIconBlacklist(newValue);
+            ArraySet<String> icons = StatusBarIconController.getIconBlacklist(
+                    getContext(), newValue);
             setVisibility(icons.contains(mSlotBattery) ? View.GONE : View.VISIBLE);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index eecc54c..aacc2c4 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -56,6 +56,7 @@
 import com.android.systemui.power.PowerUI;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
@@ -88,6 +89,7 @@
 import com.android.systemui.statusbar.phone.NavigationModeController;
 import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -121,6 +123,7 @@
 import com.android.systemui.util.leak.LeakDetector;
 import com.android.systemui.util.leak.LeakReporter;
 import com.android.systemui.util.sensors.AsyncSensorManager;
+import com.android.systemui.wm.DisplayImeController;
 import com.android.systemui.wm.DisplayWindowController;
 import com.android.systemui.wm.SystemWindows;
 
@@ -236,7 +239,8 @@
     @Inject Lazy<LeakReporter> mLeakReporter;
     @Inject Lazy<GarbageMonitor> mGarbageMonitor;
     @Inject Lazy<TunerService> mTunerService;
-    @Inject Lazy<StatusBarWindowController> mStatusBarWindowController;
+    @Inject Lazy<NotificationShadeWindowController> mNotificationShadeWindowController;
+    @Inject Lazy<StatusBarWindowController> mTempStatusBarWindowController;
     @Inject Lazy<DarkIconDispatcher> mDarkIconDispatcher;
     @Inject Lazy<ConfigurationController> mConfigurationController;
     @Inject Lazy<StatusBarIconController> mStatusBarIconController;
@@ -321,6 +325,8 @@
     @Inject Lazy<StatusBar> mStatusBar;
     @Inject Lazy<DisplayWindowController> mDisplayWindowController;
     @Inject Lazy<SystemWindows> mSystemWindows;
+    @Inject Lazy<DisplayImeController> mDisplayImeController;
+    @Inject Lazy<RecordingController> mRecordingController;
 
     @Inject
     public Dependency() {
@@ -396,7 +402,10 @@
 
         mProviders.put(TunerService.class, mTunerService::get);
 
-        mProviders.put(StatusBarWindowController.class, mStatusBarWindowController::get);
+        mProviders.put(NotificationShadeWindowController.class,
+                mNotificationShadeWindowController::get);
+
+        mProviders.put(StatusBarWindowController.class, mTempStatusBarWindowController::get);
 
         mProviders.put(DarkIconDispatcher.class, mDarkIconDispatcher::get);
 
@@ -509,6 +518,7 @@
         mProviders.put(StatusBar.class, mStatusBar::get);
         mProviders.put(DisplayWindowController.class, mDisplayWindowController::get);
         mProviders.put(SystemWindows.class, mSystemWindows::get);
+        mProviders.put(DisplayImeController.class, mDisplayImeController::get);
 
         // TODO(b/118592525): to support multi-display , we start to add something which is
         //                    per-display, while others may be global. I think it's time to add
@@ -516,6 +526,8 @@
         //                    Dependency problem.
         mProviders.put(AutoHideController.class, mAutoHideController::get);
 
+        mProviders.put(RecordingController.class, mRecordingController::get);
+
         sDependency = this;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/DumpController.kt b/packages/SystemUI/src/com/android/systemui/DumpController.kt
index 8c7075b..f14c4cd 100644
--- a/packages/SystemUI/src/com/android/systemui/DumpController.kt
+++ b/packages/SystemUI/src/com/android/systemui/DumpController.kt
@@ -30,6 +30,14 @@
 /**
  * Controller that allows any [Dumpable] to subscribe and be dumped along with other SystemUI
  * dependencies.
+ *
+ * To dump a specific dumpable on-demand:
+ *
+ * ```
+ * $ adb shell dumpsys activity service com.android.systemui/.SystemUIService dependency DumpController <tag1>,<tag2>,<tag3>
+ * ```
+ *
+ * Where tag1, tag2, etc. are the tags of the dumpables you want to dump.
  */
 @Singleton
 class DumpController @Inject constructor() : Dumpable {
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
index 8105faa..eab9706 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
@@ -27,9 +27,9 @@
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
-import com.android.systemui.statusbar.notification.collection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -49,7 +49,7 @@
     public ForegroundServiceNotificationListener(Context context,
             ForegroundServiceController foregroundServiceController,
             NotificationEntryManager notificationEntryManager,
-            NotifCollection notifCollection) {
+            NotifPipeline notifPipeline) {
         mContext = context;
         mForegroundServiceController = foregroundServiceController;
 
@@ -77,7 +77,7 @@
         });
         mEntryManager.addNotificationLifetimeExtender(new ForegroundServiceLifetimeExtender());
 
-        notifCollection.addCollectionListener(new NotifCollectionListener() {
+        notifPipeline.addCollectionListener(new NotifCollectionListener() {
             @Override
             public void onEntryAdded(NotificationEntry entry) {
                 addNotification(entry, entry.getImportance());
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 0e736dc..e66b9f2 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -266,7 +266,7 @@
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_USER_SWITCHED);
-        mBroadcastDispatcher.registerReceiver(mIntentReceiver, filter, mHandler);
+        mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter, mHandler);
 
         mOverlay.addOnLayoutChangeListener(new OnLayoutChangeListener() {
             @Override
@@ -455,17 +455,25 @@
     private void setupStatusBarPadding(int padding) {
         // Add some padding to all the content near the edge of the screen.
         StatusBar statusBar = mStatusBarLazy.get();
-        View statusBarWindow = statusBar.getStatusBarWindow();
-        if (statusBarWindow != null) {
-            TunablePadding.addTunablePadding(statusBarWindow.findViewById(R.id.keyguard_header),
+        final View notificationShadeWindowView = statusBar.getNotificationShadeWindowView();
+        if (notificationShadeWindowView != null) {
+            TunablePadding.addTunablePadding(
+                    notificationShadeWindowView.findViewById(R.id.keyguard_header),
                     PADDING, padding, FLAG_END);
 
-            FragmentHostManager fragmentHostManager = FragmentHostManager.get(statusBarWindow);
-            fragmentHostManager.addTagListener(CollapsedStatusBarFragment.TAG,
-                    new TunablePaddingTagListener(padding, R.id.status_bar));
+            final FragmentHostManager fragmentHostManager =
+                    FragmentHostManager.get(notificationShadeWindowView);
             fragmentHostManager.addTagListener(QS.TAG,
                     new TunablePaddingTagListener(padding, R.id.header));
         }
+
+        final View statusBarWindow = statusBar.getStatusBarWindow();
+        if (statusBarWindow != null) {
+            final FragmentHostManager fragmentHostManager =
+                    FragmentHostManager.get(statusBarWindow);
+            fragmentHostManager.addTagListener(CollapsedStatusBarFragment.TAG,
+                    new TunablePaddingTagListener(padding, R.id.status_bar));
+        }
     }
 
     @VisibleForTesting
@@ -495,7 +503,7 @@
             lp.gravity = Gravity.TOP | Gravity.LEFT;
         }
         lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-        lp.setFitWindowInsetsTypes(0 /* types */);
+        lp.setFitInsetsTypes(0 /* types */);
         if (isLandscape(mRotation)) {
             lp.width = WRAP_CONTENT;
             lp.height = MATCH_PARENT;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index 895207d..898cd13 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -17,6 +17,8 @@
 package com.android.systemui.accessibility;
 
 import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
 import android.database.ContentObserver;
 import android.os.Handler;
 import android.provider.Settings;
@@ -35,10 +37,25 @@
     private WindowMagnificationController mWindowMagnificationController;
     private final Handler mHandler;
 
+    private Configuration mLastConfiguration;
+
     @Inject
     public WindowMagnification(Context context, @Main Handler mainHandler) {
         super(context);
         mHandler = mainHandler;
+        mLastConfiguration = new Configuration(context.getResources().getConfiguration());
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        final int configDiff = newConfig.diff(mLastConfiguration);
+        if ((configDiff & ActivityInfo.CONFIG_DENSITY) == 0) {
+            return;
+        }
+        mLastConfiguration.setTo(newConfig);
+        if (mWindowMagnificationController != null) {
+            mWindowMagnificationController.onConfigurationChanged(configDiff);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index bfac4fc..c243309 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -113,6 +113,7 @@
         if (mMirrorView != null) {
             return;
         }
+        setInitialStartBounds();
         createOverlayWindow();
     }
 
@@ -178,9 +179,20 @@
         }
     }
 
-    private void createMirrorWindow() {
-        setInitialStartBounds();
+    /**
+     * Called when the configuration has changed, and it updates window magnification UI.
+     *
+     * @param configDiff a bit mask of the differences between the configurations
+     */
+    void onConfigurationChanged(int configDiff) {
+        // TODO(b/145780606): update toggle button UI.
+        if (mMirrorView != null) {
+            mWm.removeView(mMirrorView);
+            createMirrorWindow();
+        }
+    }
 
+    private void createMirrorWindow() {
         // The window should be the size the mirrored surface will be but also add room for the
         // border and the drag handle.
         int dragViewHeight = (int) mContext.getResources().getDimension(
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index b083123..23d6458 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -18,7 +18,6 @@
 
 import android.app.AppOpsManager;
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserHandle;
@@ -64,7 +63,6 @@
     private H mBGHandler;
     private final List<AppOpsController.Callback> mCallbacks = new ArrayList<>();
     private final ArrayMap<Integer, Set<Callback>> mCallbacksByCode = new ArrayMap<>();
-    private final PermissionFlagsCache mFlagsCache;
     private boolean mListening;
 
     @GuardedBy("mActiveItems")
@@ -81,17 +79,10 @@
     };
 
     @Inject
-    public AppOpsControllerImpl(Context context, @Background Looper bgLooper,
-            DumpController dumpController) {
-        this(context, bgLooper, new PermissionFlagsCache(context), dumpController);
-    }
-
-    @VisibleForTesting
-    protected AppOpsControllerImpl(Context context, Looper bgLooper, PermissionFlagsCache cache,
-            DumpController dumpController) {
+    public AppOpsControllerImpl(Context context,
+            @Background Looper bgLooper, DumpController dumpController) {
         mContext = context;
         mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
-        mFlagsCache = cache;
         mBGHandler = new H(bgLooper);
         final int numOps = OPS.length;
         for (int i = 0; i < numOps; i++) {
@@ -209,7 +200,14 @@
             mNotedItems.remove(item);
             if (DEBUG) Log.w(TAG, "Removed item: " + item.toString());
         }
-        notifySuscribers(code, uid, packageName, false);
+        boolean active;
+        // Check if the item is also active
+        synchronized (mActiveItems) {
+            active = getAppOpItemLocked(mActiveItems, code, uid, packageName) != null;
+        }
+        if (!active) {
+            notifySuscribers(code, uid, packageName, false);
+        }
     }
 
     private boolean addNoted(int code, int uid, String packageName) {
@@ -224,64 +222,13 @@
                 createdNew = true;
             }
         }
+        // We should keep this so we make sure it cannot time out.
+        mBGHandler.removeCallbacksAndMessages(item);
         mBGHandler.scheduleRemoval(item, NOTED_OP_TIME_DELAY_MS);
         return createdNew;
     }
 
     /**
-     * Does the app-op code refer to a user sensitive permission for the specified user id
-     * and package. Only user sensitive permission should be shown to the user by default.
-     *
-     * @param appOpCode The code of the app-op.
-     * @param uid The uid of the user.
-     * @param packageName The name of the package.
-     *
-     * @return {@code true} iff the app-op item is user sensitive
-     */
-    private boolean isUserSensitive(int appOpCode, int uid, String packageName) {
-        String permission = AppOpsManager.opToPermission(appOpCode);
-        if (permission == null) {
-            return false;
-        }
-        int permFlags = mFlagsCache.getPermissionFlags(permission,
-                packageName, UserHandle.getUserHandleForUid(uid));
-        return (permFlags & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) != 0;
-    }
-
-    /**
-     * Does the app-op item refer to an operation that should be shown to the user.
-     * Only specficic ops (like SYSTEM_ALERT_WINDOW) or ops that refer to user sensitive
-     * permission should be shown to the user by default.
-     *
-     * @param item The item
-     *
-     * @return {@code true} iff the app-op item should be shown to the user
-     */
-    private boolean isUserVisible(AppOpItem item) {
-        return isUserVisible(item.getCode(), item.getUid(), item.getPackageName());
-    }
-
-
-    /**
-     * Does the app-op, uid and package name, refer to an operation that should be shown to the
-     * user. Only specficic ops (like {@link AppOpsManager.OP_SYSTEM_ALERT_WINDOW}) or
-     * ops that refer to user sensitive permission should be shown to the user by default.
-     *
-     * @param item The item
-     *
-     * @return {@code true} iff the app-op for should be shown to the user
-     */
-    private boolean isUserVisible(int appOpCode, int uid, String packageName) {
-        // currently OP_SYSTEM_ALERT_WINDOW does not correspond to a platform permission
-        // which may be user senstive, so for now always show it to the user.
-        if (appOpCode == AppOpsManager.OP_SYSTEM_ALERT_WINDOW) {
-            return true;
-        }
-
-        return isUserSensitive(appOpCode, uid, packageName);
-    }
-
-    /**
      * Returns a copy of the list containing all the active AppOps that the controller tracks.
      *
      * @return List of active AppOps information
@@ -304,8 +251,8 @@
             final int numActiveItems = mActiveItems.size();
             for (int i = 0; i < numActiveItems; i++) {
                 AppOpItem item = mActiveItems.get(i);
-                if ((userId == UserHandle.USER_ALL || UserHandle.getUserId(item.getUid()) == userId)
-                        && isUserVisible(item)) {
+                if ((userId == UserHandle.USER_ALL
+                        || UserHandle.getUserId(item.getUid()) == userId)) {
                     list.add(item);
                 }
             }
@@ -314,8 +261,8 @@
             final int numNotedItems = mNotedItems.size();
             for (int i = 0; i < numNotedItems; i++) {
                 AppOpItem item = mNotedItems.get(i);
-                if ((userId == UserHandle.USER_ALL || UserHandle.getUserId(item.getUid()) == userId)
-                        && isUserVisible(item)) {
+                if ((userId == UserHandle.USER_ALL
+                        || UserHandle.getUserId(item.getUid()) == userId)) {
                     list.add(item);
                 }
             }
@@ -325,7 +272,21 @@
 
     @Override
     public void onOpActiveChanged(int code, int uid, String packageName, boolean active) {
-        if (updateActives(code, uid, packageName, active)) {
+        if (DEBUG) {
+            Log.w(TAG, String.format("onActiveChanged(%d,%d,%s,%s", code, uid, packageName,
+                    Boolean.toString(active)));
+        }
+        boolean activeChanged = updateActives(code, uid, packageName, active);
+        if (!activeChanged) return; // early return
+        // Check if the item is also noted, in that case, there's no update.
+        boolean alsoNoted;
+        synchronized (mNotedItems) {
+            alsoNoted = getAppOpItemLocked(mNotedItems, code, uid, packageName) != null;
+        }
+        // If active is true, we only send the update if the op is not actively noted (already true)
+        // If active is false, we only send the update if the op is not actively noted (prevent
+        // early removal)
+        if (!alsoNoted) {
             mBGHandler.post(() -> notifySuscribers(code, uid, packageName, active));
         }
     }
@@ -333,17 +294,23 @@
     @Override
     public void onOpNoted(int code, int uid, String packageName, int result) {
         if (DEBUG) {
-            Log.w(TAG, "Op: " + code + " with result " + AppOpsManager.MODE_NAMES[result]);
+            Log.w(TAG, "Noted op: " + code + " with result "
+                    + AppOpsManager.MODE_NAMES[result] + " for package " + packageName);
         }
         if (result != AppOpsManager.MODE_ALLOWED) return;
-        if (addNoted(code, uid, packageName)) {
+        boolean notedAdded = addNoted(code, uid, packageName);
+        if (!notedAdded) return; // early return
+        boolean alsoActive;
+        synchronized (mActiveItems) {
+            alsoActive = getAppOpItemLocked(mActiveItems, code, uid, packageName) != null;
+        }
+        if (!alsoActive) {
             mBGHandler.post(() -> notifySuscribers(code, uid, packageName, true));
         }
     }
 
     private void notifySuscribers(int code, int uid, String packageName, boolean active) {
-        if (mCallbacksByCode.containsKey(code)
-                && isUserVisible(code, uid, packageName)) {
+        if (mCallbacksByCode.containsKey(code)) {
             if (DEBUG) Log.d(TAG, "Notifying of change in package " + packageName);
             for (Callback cb: mCallbacksByCode.get(code)) {
                 cb.onActiveStateChanged(code, uid, packageName, active);
@@ -368,7 +335,7 @@
 
     }
 
-    protected final class H extends Handler {
+    protected class H extends Handler {
         H(Looper looper) {
             super(looper);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt b/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt
deleted file mode 100644
index f02c7af..0000000
--- a/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt
+++ /dev/null
@@ -1,70 +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.appops
-
-import android.content.Context
-import android.content.pm.PackageManager
-import android.os.UserHandle
-import android.util.ArrayMap
-import com.android.internal.annotations.VisibleForTesting
-
-private data class PermissionFlag(val flag: Int, val timestamp: Long)
-
-private data class PermissionFlagKey(
-    val permission: String,
-    val packageName: String,
-    val user: UserHandle
-)
-
-internal const val CACHE_EXPIRATION = 10000L
-
-/**
- * Cache for PackageManager's PermissionFlags.
- *
- * Flags older than [CACHE_EXPIRATION] will be retrieved again.
- */
-internal open class PermissionFlagsCache(context: Context) {
-    private val packageManager = context.packageManager
-    private val permissionFlagsCache = ArrayMap<PermissionFlagKey, PermissionFlag>()
-
-    /**
-     * Retrieve permission flags from cache or PackageManager. There parameters will be passed
-     * directly to [PackageManager].
-     *
-     * Calls to this method should be done from a background thread.
-     */
-    fun getPermissionFlags(permission: String, packageName: String, user: UserHandle): Int {
-        val key = PermissionFlagKey(permission, packageName, user)
-        val now = getCurrentTime()
-        val value = permissionFlagsCache.getOrPut(key) {
-            PermissionFlag(getFlags(key), now)
-        }
-        if (now - value.timestamp > CACHE_EXPIRATION) {
-            val newValue = PermissionFlag(getFlags(key), now)
-            permissionFlagsCache.put(key, newValue)
-            return newValue.flag
-        } else {
-            return value.flag
-        }
-    }
-
-    private fun getFlags(key: PermissionFlagKey) =
-            packageManager.getPermissionFlags(key.permission, key.packageName, key.user)
-
-    @VisibleForTesting
-    protected open fun getCurrentTime() = System.currentTimeMillis()
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java b/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
index 659629b..5532a04 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
@@ -70,7 +70,7 @@
                             | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
                     PixelFormat.TRANSLUCENT);
             lp.setTitle("AssistDisclosure");
-            lp.setFitWindowInsetsTypes(0 /* types */);
+            lp.setFitInsetsTypes(0 /* types */);
 
             mWm.addView(mView, lp);
             mViewAdded = true;
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
index eb615a0..f201a6f 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
@@ -85,7 +85,7 @@
                 PixelFormat.TRANSLUCENT);
         mLayoutParams.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
         mLayoutParams.gravity = Gravity.BOTTOM;
-        mLayoutParams.setFitWindowInsetsTypes(0 /* types */);
+        mLayoutParams.setFitInsetsTypes(0 /* types */);
         mLayoutParams.setTitle("Assist");
 
         mInvocationLightsView = (InvocationLightsView)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index 84a592d..044c5a0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -162,6 +162,7 @@
     private Bundle mBiometricPromptBundle;
     private boolean mRequireConfirmation;
     private int mUserId;
+    private int mEffectiveUserId;
     @AuthDialog.DialogSize int mSize = AuthDialog.SIZE_UNKNOWN;
 
     private TextView mTitleView;
@@ -280,6 +281,10 @@
         mUserId = userId;
     }
 
+    public void setEffectiveUserId(int effectiveUserId) {
+        mEffectiveUserId = effectiveUserId;
+    }
+
     public void setRequireConfirmation(boolean requireConfirmation) {
         mRequireConfirmation = requireConfirmation;
     }
@@ -375,16 +380,7 @@
                     0 /* animateDurationMs */);
             mSize = newSize;
         } else if (newSize == AuthDialog.SIZE_LARGE) {
-            final boolean isManagedProfile = Utils.isManagedProfile(mContext, mUserId);
-
-            // If it's a managed profile, animate the contents and panel down, since the credential
-            // contents will be shown on the same "layer" as the background. If it's not a managed
-            // profile, animate the contents up and expand the panel to full-screen - the credential
-            // contents will be shown on the same "layer" as the panel.
-            final float translationY = isManagedProfile ?
-                    -getResources().getDimension(
-                            R.dimen.biometric_dialog_animation_translation_offset)
-                    : getResources().getDimension(
+            final float translationY = getResources().getDimension(
                             R.dimen.biometric_dialog_medium_to_large_translation_offset);
             final AuthBiometricView biometricView = this;
 
@@ -415,26 +411,20 @@
                 biometricView.setAlpha(opacity);
             });
 
-            if (!isManagedProfile) {
-                mPanelController.setUseFullScreen(true);
-                mPanelController.updateForContentDimensions(
-                        mPanelController.getContainerWidth(),
-                        mPanelController.getContainerHeight(),
-                        mInjector.getMediumToLargeAnimationDurationMs());
-            }
+            mPanelController.setUseFullScreen(true);
+            mPanelController.updateForContentDimensions(
+                    mPanelController.getContainerWidth(),
+                    mPanelController.getContainerHeight(),
+                    mInjector.getMediumToLargeAnimationDurationMs());
 
             // Start the animations together
             AnimatorSet as = new AnimatorSet();
             List<Animator> animators = new ArrayList<>();
             animators.add(translationAnimator);
             animators.add(opacityAnimator);
-            if (isManagedProfile) {
-                animators.add(mPanelController.getTranslationAnimator(translationY));
-                animators.add(mPanelController.getAlphaAnimator(0));
-            }
+
             as.playTogether(animators);
-            as.setDuration(isManagedProfile ? mInjector.getMediumToLargeAnimationDurationMs()
-                    : mInjector.getMediumToLargeAnimationDurationMs() * 2 / 3);
+            as.setDuration(mInjector.getMediumToLargeAnimationDurationMs() * 2 / 3);
             as.start();
         } else {
             Log.e(TAG, "Unknown transition from: " + mSize + " to: " + newSize);
@@ -650,8 +640,9 @@
         if (isDeviceCredentialAllowed()) {
 
             final @Utils.CredentialType int credentialType =
-                    Utils.getCredentialType(mContext, mUserId);
-            switch(credentialType) {
+                    Utils.getCredentialType(mContext, mEffectiveUserId);
+
+            switch (credentialType) {
                 case Utils.CREDENTIAL_PIN:
                     negativeText = getResources().getString(R.string.biometric_dialog_use_pin);
                     break;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 36c89fd..b8d32ae 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -19,17 +19,15 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
-import android.graphics.drawable.Drawable;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.UserManager;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
@@ -75,6 +73,7 @@
     @interface ContainerState {}
 
     final Config mConfig;
+    final int mEffectiveUserId;
     private final Handler mHandler;
     private final Injector mInjector;
     private final IBinder mWindowToken = new Binder();
@@ -166,9 +165,8 @@
                     R.layout.auth_container_view, root, false /* attachToRoot */);
         }
 
-        AuthPanelController getPanelController(Context context, View panelView,
-                boolean isManagedProfile) {
-            return new AuthPanelController(context, panelView, isManagedProfile);
+        AuthPanelController getPanelController(Context context, View panelView) {
+            return new AuthPanelController(context, panelView);
         }
 
         ImageView getBackgroundView(FrameLayout parent) {
@@ -182,6 +180,14 @@
         int getAnimateCredentialStartDelayMs() {
             return AuthDialog.ANIMATE_CREDENTIAL_START_DELAY_MS;
         }
+
+        UserManager getUserManager(Context context) {
+            return UserManager.get(context);
+        }
+
+        int getCredentialType(Context context, int effectiveUserId) {
+            return Utils.getCredentialType(context, effectiveUserId);
+        }
     }
 
     @VisibleForTesting
@@ -230,6 +236,9 @@
         mConfig = config;
         mInjector = injector;
 
+        mEffectiveUserId = mInjector.getUserManager(mContext)
+                .getCredentialOwnerProfile(mConfig.mUserId);
+
         mHandler = new Handler(Looper.getMainLooper());
         mWindowManager = mContext.getSystemService(WindowManager.class);
         mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
@@ -243,10 +252,8 @@
         final LayoutInflater factory = LayoutInflater.from(mContext);
         mFrameLayout = mInjector.inflateContainerView(factory, this);
 
-        final boolean isManagedProfile = Utils.isManagedProfile(mContext, mConfig.mUserId);
-
         mPanelView = mInjector.getPanelView(mFrameLayout);
-        mPanelController = mInjector.getPanelController(mContext, mPanelView, isManagedProfile);
+        mPanelController = mInjector.getPanelController(mContext, mPanelView);
 
         // Inflate biometric view only if necessary.
         if (Utils.isBiometricAllowed(mConfig.mBiometricPromptBundle)) {
@@ -268,17 +275,6 @@
         mBiometricScrollView = mInjector.getBiometricScrollView(mFrameLayout);
         mBackgroundView = mInjector.getBackgroundView(mFrameLayout);
 
-
-        if (isManagedProfile) {
-            final Drawable image = getResources().getDrawable(R.drawable.work_challenge_background,
-                    mContext.getTheme());
-            final DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
-            image.setColorFilter(dpm.getOrganizationColorForUser(mConfig.mUserId),
-                    PorterDuff.Mode.DARKEN);
-            mBackgroundView.setScaleType(ImageView.ScaleType.CENTER_CROP);
-            mBackgroundView.setImageDrawable(image);
-        }
-
         addView(mFrameLayout);
 
         setOnKeyListener((v, keyCode, event) -> {
@@ -307,6 +303,7 @@
         mBiometricView.setCallback(mBiometricCallback);
         mBiometricView.setBackgroundView(mBackgroundView);
         mBiometricView.setUserId(mConfig.mUserId);
+        mBiometricView.setEffectiveUserId(mEffectiveUserId);
         mBiometricScrollView.addView(mBiometricView);
     }
 
@@ -318,7 +315,10 @@
      */
     private void addCredentialView(boolean animatePanel, boolean animateContents) {
         final LayoutInflater factory = LayoutInflater.from(mContext);
-        final int credentialType = Utils.getCredentialType(mContext, mConfig.mUserId);
+
+        final @Utils.CredentialType int credentialType = mInjector.getCredentialType(
+                mContext, mEffectiveUserId);
+
         switch (credentialType) {
             case Utils.CREDENTIAL_PATTERN:
                 mCredentialView = (AuthCredentialView) factory.inflate(
@@ -334,7 +334,8 @@
         }
 
         mCredentialView.setContainerView(this);
-        mCredentialView.setUser(mConfig.mUserId);
+        mCredentialView.setEffectiveUserId(mEffectiveUserId);
+        mCredentialView.setCredentialType(credentialType);
         mCredentialView.setCallback(mCredentialCallback);
         mCredentialView.setBiometricPromptBundle(mConfig.mBiometricPromptBundle);
         mCredentialView.setPanelController(mPanelController, animatePanel);
@@ -591,7 +592,7 @@
         lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
         lp.setTitle("BiometricPrompt");
         lp.token = windowToken;
-        lp.setFitWindowInsetsTypes(lp.getFitWindowInsetsTypes() & ~Type.statusBars());
+        lp.setFitInsetsTypes(lp.getFitInsetsTypes() & ~Type.statusBars());
         return lp;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index e0ca1ac..875619a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -338,7 +338,13 @@
 
     @Override
     public void hideAuthenticationDialog() {
-        if (DEBUG) Log.d(TAG, "hideAuthenticationDialog");
+        if (DEBUG) Log.d(TAG, "hideAuthenticationDialog: " + mCurrentDialog);
+
+        if (mCurrentDialog == null) {
+            // Could be possible if the caller canceled authentication after credential success
+            // but before the client was notified.
+            return;
+        }
 
         mCurrentDialog.dismissFromSystemServer();
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
index bebaa4b..ccfd3a5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
@@ -96,7 +96,7 @@
     }
 
     private void checkPasswordAndUnlock() {
-        try (LockscreenCredential password =  mCredentialType == Utils.CREDENTIAL_PIN
+        try (LockscreenCredential password = mCredentialType == Utils.CREDENTIAL_PIN
                 ? LockscreenCredential.createPinOrNone(mPasswordField.getText())
                 : LockscreenCredential.createPasswordOrNone(mPasswordField.getText())) {
             if (password.isNone()) {
@@ -104,7 +104,7 @@
             }
 
             mPendingLockCheck = LockPatternChecker.checkCredential(mLockPatternUtils,
-                    password, mUserId, this::onCredentialChecked);
+                    password, mEffectiveUserId, this::onCredentialChecked);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
index 14414a4..1cb532b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
@@ -69,7 +69,7 @@
                 mPendingLockCheck = LockPatternChecker.checkCredential(
                         mLockPatternUtils,
                         credential,
-                        mUserId,
+                        mEffectiveUserId,
                         this::onPatternChecked);
             }
         }
@@ -99,7 +99,8 @@
         super.onFinishInflate();
         mLockPatternView = findViewById(R.id.lockPattern);
         mLockPatternView.setOnPatternListener(new UnlockPatternListener());
-        mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(mUserId));
+        mLockPatternView.setInStealthMode(
+                !mLockPatternUtils.isVisiblePatternEnabled(mEffectiveUserId));
         mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
index 8c8611e..8f2cf70 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
@@ -63,7 +63,7 @@
     protected AuthContainerView mContainerView;
     protected Callback mCallback;
     protected AsyncTask<?, ?, ?> mPendingLockCheck;
-    protected int mUserId;
+    protected int mEffectiveUserId;
     protected ErrorTimer mErrorTimer;
 
     interface Callback {
@@ -137,8 +137,12 @@
         view.setText(string);
     }
 
-    void setUser(int user) {
-        mUserId = user;
+    void setEffectiveUserId(int effectiveUserId) {
+        mEffectiveUserId = effectiveUserId;
+    }
+
+    void setCredentialType(@Utils.CredentialType int credentialType) {
+        mCredentialType = credentialType;
     }
 
     void setCallback(Callback callback) {
@@ -166,8 +170,6 @@
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
 
-        mCredentialType = Utils.getCredentialType(mContext, mUserId);
-
         setText(mTitleView, mBiometricPromptBundle.getString(BiometricPrompt.KEY_TITLE));
         setTextOrHide(mSubtitleView,
                 mBiometricPromptBundle.getString(BiometricPrompt.KEY_SUBTITLE));
@@ -230,7 +232,8 @@
         } else {
             if (timeoutMs > 0) {
                 mHandler.removeCallbacks(mClearErrorRunnable);
-                long deadline = mLockPatternUtils.setLockoutAttemptDeadline(mUserId, timeoutMs);
+                long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
+                        mEffectiveUserId, timeoutMs);
                 mErrorTimer = new ErrorTimer(mContext,
                         deadline - SystemClock.elapsedRealtime(),
                         LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
index 4acbade..11503fb 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
@@ -37,7 +37,6 @@
 
     private final Context mContext;
     private final View mPanelView;
-    private final boolean mIsManagedProfile;
 
     private boolean mUseFullScreen;
 
@@ -115,13 +114,6 @@
         final float cornerRadius = mUseFullScreen ? 0 : mContext.getResources()
                 .getDimension(R.dimen.biometric_dialog_corner_size);
 
-        // When going to full-screen for managed profiles, fade away so the managed profile
-        // background behind this view becomes visible.
-        final boolean shouldFadeAway = mUseFullScreen && mIsManagedProfile;
-        final int alpha = shouldFadeAway ? 0 : 255;
-        final float elevation = shouldFadeAway ? 0 :
-                mContext.getResources().getDimension(R.dimen.biometric_dialog_elevation);
-
         if (animateDurationMs > 0) {
             // Animate margin
             ValueAnimator marginAnimator = ValueAnimator.ofInt(mMargin, margin);
@@ -148,21 +140,11 @@
                 mContentWidth = (int) animation.getAnimatedValue();
             });
 
-            // Animate background
-            ValueAnimator alphaAnimator = ValueAnimator.ofInt(
-                    mPanelView.getBackground().getAlpha(), alpha);
-            alphaAnimator.addUpdateListener((animation) -> {
-                if (shouldFadeAway) {
-                    mPanelView.getBackground().setAlpha((int) animation.getAnimatedValue());
-                }
-            });
-
             // Play together
             AnimatorSet as = new AnimatorSet();
             as.setDuration(animateDurationMs);
             as.setInterpolator(new AccelerateDecelerateInterpolator());
-            as.playTogether(cornerAnimator, heightAnimator, widthAnimator, marginAnimator,
-                    alphaAnimator);
+            as.playTogether(cornerAnimator, heightAnimator, widthAnimator, marginAnimator);
             as.start();
 
         } else {
@@ -170,7 +152,6 @@
             mCornerRadius = cornerRadius;
             mContentWidth = contentWidth;
             mContentHeight = contentHeight;
-            mPanelView.getBackground().setAlpha(alpha);
             mPanelView.invalidateOutline();
         }
     }
@@ -183,10 +164,9 @@
         return mContainerHeight;
     }
 
-    AuthPanelController(Context context, View panelView, boolean isManagedProfile) {
+    AuthPanelController(Context context, View panelView) {
         mContext = context;
         mPanelView = panelView;
-        mIsManagedProfile = isManagedProfile;
         mCornerRadius = context.getResources()
                 .getDimension(R.dimen.biometric_dialog_corner_size);
         mMargin = (int) context.getResources()
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index 8cb0cc5..cedf7c3 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -20,6 +20,7 @@
 import android.content.Context
 import android.content.IntentFilter
 import android.os.Handler
+import android.os.HandlerExecutor
 import android.os.Looper
 import android.os.Message
 import android.os.UserHandle
@@ -32,13 +33,14 @@
 import com.android.systemui.dagger.qualifiers.Main
 import java.io.FileDescriptor
 import java.io.PrintWriter
+import java.util.concurrent.Executor
 import javax.inject.Inject
 import javax.inject.Singleton
 
 data class ReceiverData(
     val receiver: BroadcastReceiver,
     val filter: IntentFilter,
-    val handler: Handler,
+    val executor: Executor,
     val user: UserHandle
 )
 
@@ -76,8 +78,33 @@
      * @param filter A filter to determine what broadcasts should be dispatched to this receiver.
      *               It will only take into account actions and categories for filtering. It must
      *               have at least one action.
-     * @param handler A handler to dispatch [BroadcastReceiver.onReceive]. By default, it is the
-     *                main handler. Pass `null` to use the default.
+     * @param handler A handler to dispatch [BroadcastReceiver.onReceive].
+     * @param user A user handle to determine which broadcast should be dispatched to this receiver.
+     *             By default, it is the current user.
+     * @throws IllegalArgumentException if the filter has other constraints that are not actions or
+     *                                  categories or the filter has no actions.
+     */
+    @Deprecated(message = "Replacing Handler for Executor in SystemUI",
+            replaceWith = ReplaceWith("registerReceiver(receiver, filter, executor, user)"))
+    @JvmOverloads
+    fun registerReceiverWithHandler(
+        receiver: BroadcastReceiver,
+        filter: IntentFilter,
+        handler: Handler,
+        user: UserHandle = context.user
+    ) {
+        registerReceiver(receiver, filter, HandlerExecutor(handler), user)
+    }
+
+    /**
+     * Register a receiver for broadcast with the dispatcher
+     *
+     * @param receiver A receiver to dispatch the [Intent]
+     * @param filter A filter to determine what broadcasts should be dispatched to this receiver.
+     *               It will only take into account actions and categories for filtering. It must
+     *               have at least one action.
+     * @param executor An executor to dispatch [BroadcastReceiver.onReceive]. Pass null to use an
+     *                 executor in the main thread (default).
      * @param user A user handle to determine which broadcast should be dispatched to this receiver.
      *             By default, it is the current user.
      * @throws IllegalArgumentException if the filter has other constraints that are not actions or
@@ -85,15 +112,15 @@
      */
     @JvmOverloads
     fun registerReceiver(
-        receiver: BroadcastReceiver,
-        filter: IntentFilter,
-        handler: Handler? = mainHandler,
-        user: UserHandle = context.user
+            receiver: BroadcastReceiver,
+            filter: IntentFilter,
+            executor: Executor? = context.mainExecutor,
+            user: UserHandle = context.user
     ) {
         checkFilter(filter)
         this.handler
                 .obtainMessage(MSG_ADD_RECEIVER,
-                ReceiverData(receiver, filter, handler ?: mainHandler, user))
+                        ReceiverData(receiver, filter, executor ?: context.mainExecutor, user))
                 .sendToTarget()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
index b2942bb..0c631aa 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
@@ -193,7 +193,7 @@
                         it.filter.hasAction(intent.action) &&
                             it.filter.matchCategories(intent.categories) == null }
                     ?.forEach {
-                        it.handler.post {
+                        it.executor.execute {
                             if (DEBUG) Log.w(TAG,
                                     "[$index] Dispatching ${intent.action} to ${it.receiver}")
                             it.receiver.pendingResult = pendingResult
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index 77c8e0b..2d9775d 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -32,20 +32,17 @@
 import android.content.pm.ShortcutInfo;
 import android.content.res.Resources;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
-import android.os.Parcelable;
 import android.os.UserHandle;
 import android.provider.Settings;
-import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.R;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.List;
 import java.util.Objects;
 
 /**
@@ -85,6 +82,18 @@
     /** Whether flyout text should be suppressed, regardless of any other flags or state. */
     private boolean mSuppressFlyout;
 
+    /**
+     * Presentational info about the flyout.
+     */
+    public static class FlyoutMessage {
+        @Nullable public Drawable senderAvatar;
+        @Nullable public CharSequence senderName;
+        @Nullable public CharSequence message;
+        @Nullable public boolean isGroupChat;
+    }
+
+    private FlyoutMessage mFlyoutMessage;
+
     public static String groupId(NotificationEntry entry) {
         UserHandle user = entry.getSbn().getUser();
         return user.getIdentifier() + "|" + entry.getSbn().getPackageName();
@@ -194,6 +203,7 @@
 
         mShortcutInfo = info.shortcutInfo;
         mAppName = info.appName;
+        mFlyoutMessage = info.flyoutMessage;
 
         mExpandedView.update(this);
         mIconView.update(this, info.badgedBubbleImage, info.dotColor, info.dotPath);
@@ -307,6 +317,10 @@
         mSuppressFlyout = suppressFlyout;
     }
 
+    FlyoutMessage getFlyoutMessage() {
+        return mFlyoutMessage;
+    }
+
     /**
      * Returns whether the notification for this bubble is a foreground service. It shows that this
      * is an ongoing bubble.
@@ -346,14 +360,14 @@
      * To populate the icon use {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)}.
      */
     boolean usingShortcutInfo() {
-        return BubbleExperimentConfig.isShortcutIntent(getBubbleIntent());
+        return mEntry.getBubbleMetadata().getShortcutId() != null;
     }
 
     @Nullable
     PendingIntent getBubbleIntent() {
         Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
         if (data != null) {
-            return data.getIntent();
+            return data.getBubbleIntent();
         }
         return null;
     }
@@ -368,72 +382,6 @@
         return intent;
     }
 
-    /**
-     * Returns our best guess for the most relevant text summary of the latest update to this
-     * notification, based on its type. Returns null if there should not be an update message.
-     */
-    CharSequence getUpdateMessage(Context context) {
-        final Notification underlyingNotif = mEntry.getSbn().getNotification();
-        final Class<? extends Notification.Style> style = underlyingNotif.getNotificationStyle();
-
-        try {
-            if (Notification.BigTextStyle.class.equals(style)) {
-                // Return the big text, it is big so probably important. If it's not there use the
-                // normal text.
-                CharSequence bigText =
-                        underlyingNotif.extras.getCharSequence(Notification.EXTRA_BIG_TEXT);
-                return !TextUtils.isEmpty(bigText)
-                        ? bigText
-                        : underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
-            } else if (Notification.MessagingStyle.class.equals(style)) {
-                final List<Notification.MessagingStyle.Message> messages =
-                        Notification.MessagingStyle.Message.getMessagesFromBundleArray(
-                                (Parcelable[]) underlyingNotif.extras.get(
-                                        Notification.EXTRA_MESSAGES));
-
-                final Notification.MessagingStyle.Message latestMessage =
-                        Notification.MessagingStyle.findLatestIncomingMessage(messages);
-
-                if (latestMessage != null) {
-                    final CharSequence personName = latestMessage.getSenderPerson() != null
-                            ? latestMessage.getSenderPerson().getName()
-                            : null;
-
-                    // Prepend the sender name if available since group chats also use messaging
-                    // style.
-                    if (!TextUtils.isEmpty(personName)) {
-                        return context.getResources().getString(
-                                R.string.notification_summary_message_format,
-                                personName,
-                                latestMessage.getText());
-                    } else {
-                        return latestMessage.getText();
-                    }
-                }
-            } else if (Notification.InboxStyle.class.equals(style)) {
-                CharSequence[] lines =
-                        underlyingNotif.extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES);
-
-                // Return the last line since it should be the most recent.
-                if (lines != null && lines.length > 0) {
-                    return lines[lines.length - 1];
-                }
-            } else if (Notification.MediaStyle.class.equals(style)) {
-                // Return nothing, media updates aren't typically useful as a text update.
-                return null;
-            } else {
-                // Default to text extra.
-                return underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
-            }
-        } catch (ClassCastException | NullPointerException | ArrayIndexOutOfBoundsException e) {
-            // No use crashing, we'll just return null and the caller will assume there's no update
-            // message.
-            e.printStackTrace();
-        }
-
-        return null;
-    }
-
     private int getDimenForPackageUser(Context context, int resId, String pkg, int userId) {
         PackageManager pm = context.getPackageManager();
         Resources r;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index c7492a2..e642d4e 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -89,9 +89,9 @@
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.phone.StatusBarWindowController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.RemoteInputUriController;
 import com.android.systemui.statusbar.policy.ZenModeController;
@@ -166,7 +166,7 @@
     private final HashSet<String> mUserBlockedBubbles;
 
     // Bubbles get added to the status bar view
-    private final StatusBarWindowController mStatusBarWindowController;
+    private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final ZenModeController mZenModeController;
     private StatusBarStateListener mStatusBarStateListener;
     private final ScreenshotHelper mScreenshotHelper;
@@ -245,7 +245,7 @@
 
     @Inject
     public BubbleController(Context context,
-            StatusBarWindowController statusBarWindowController,
+            NotificationShadeWindowController notificationShadeWindowController,
             StatusBarStateController statusBarStateController,
             ShadeController shadeController,
             BubbleData data,
@@ -256,14 +256,14 @@
             NotificationGroupManager groupManager,
             NotificationEntryManager entryManager,
             RemoteInputUriController remoteInputUriController) {
-        this(context, statusBarWindowController, statusBarStateController, shadeController,
+        this(context, notificationShadeWindowController, statusBarStateController, shadeController,
                 data, null /* synchronizer */, configurationController, interruptionStateProvider,
                 zenModeController, notifUserManager, groupManager, entryManager,
                 remoteInputUriController);
     }
 
     public BubbleController(Context context,
-            StatusBarWindowController statusBarWindowController,
+            NotificationShadeWindowController notificationShadeWindowController,
             StatusBarStateController statusBarStateController,
             ShadeController shadeController,
             BubbleData data,
@@ -324,7 +324,7 @@
                     }
                 });
 
-        mStatusBarWindowController = statusBarWindowController;
+        mNotificationShadeWindowController = notificationShadeWindowController;
         mStatusBarStateListener = new StatusBarStateListener();
         statusBarStateController.addCallback(mStatusBarStateListener);
 
@@ -377,10 +377,10 @@
     private void ensureStackViewCreated() {
         if (mStackView == null) {
             mStackView = new BubbleStackView(mContext, mBubbleData, mSurfaceSynchronizer);
-            ViewGroup sbv = mStatusBarWindowController.getStatusBarView();
-            int bubbleScrimIndex = sbv.indexOfChild(sbv.findViewById(R.id.scrim_for_bubble));
+            ViewGroup nsv = mNotificationShadeWindowController.getNotificationShadeView();
+            int bubbleScrimIndex = nsv.indexOfChild(nsv.findViewById(R.id.scrim_for_bubble));
             int stackIndex = bubbleScrimIndex + 1;  // Show stack above bubble scrim.
-            sbv.addView(mStackView, stackIndex,
+            nsv.addView(mStackView, stackIndex,
                     new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
             if (mExpandListener != null) {
                 mStackView.setExpandListener(mExpandListener);
@@ -473,7 +473,7 @@
             if (listener != null) {
                 listener.onBubbleExpandChanged(isExpanding, key);
             }
-            mStatusBarWindowController.setBubbleExpanded(isExpanding);
+            mNotificationShadeWindowController.setBubbleExpanded(isExpanding);
         });
         if (mStackView != null) {
             mStackView.setExpandListener(mExpandListener);
@@ -746,9 +746,16 @@
             return !isAutogroupSummary;
         } else {
             // If it's not a user dismiss it's a cancel.
+            for (int i = 0; i < bubbleChildren.size(); i++) {
+                // First check if any of these are user-created (i.e. experimental bubbles)
+                if (mUserCreatedBubbles.contains(bubbleChildren.get(i).getKey())) {
+                    // Experimental bubble! Intercept the removal.
+                    return true;
+                }
+            }
+            // Not an experimental bubble, safe to remove.
             mBubbleData.removeSuppressedSummary(groupKey);
-
-            // Remove any associated bubble children.
+            // Remove any associated bubble children with the summary.
             for (int i = 0; i < bubbleChildren.size(); i++) {
                 Bubble bubbleChild = bubbleChildren.get(i);
                 mBubbleData.notificationEntryRemoved(bubbleChild.getEntry(),
@@ -937,9 +944,9 @@
         }
 
         // Let listeners know if bubble state changed.
-        boolean hadBubbles = mStatusBarWindowController.getBubblesShowing();
+        boolean hadBubbles = mNotificationShadeWindowController.getBubblesShowing();
         boolean hasBubblesShowing = hasBubbles() && mStackView.getVisibility() == VISIBLE;
-        mStatusBarWindowController.setBubblesShowing(hasBubblesShowing);
+        mNotificationShadeWindowController.setBubblesShowing(hasBubblesShowing);
         if (mStateChangeListener != null && hadBubbles != hasBubblesShowing) {
             mStateChangeListener.onHasBubblesChanged(hasBubblesShowing);
         }
@@ -976,7 +983,7 @@
                 && context.getDisplay().getDisplayId() == DEFAULT_DISPLAY;
         final Bubble expandedBubble = mStackView.getExpandedBubble();
         if (defaultDisplay && expandedBubble != null && isStackExpanded()
-                && !mStatusBarWindowController.getPanelExpanded()) {
+                && !mNotificationShadeWindowController.getPanelExpanded()) {
             return expandedBubble;
         }
         return null;
@@ -1068,8 +1075,12 @@
      */
     static boolean canLaunchInActivityView(Context context, NotificationEntry entry) {
         PendingIntent intent = entry.getBubbleMetadata() != null
-                ? entry.getBubbleMetadata().getIntent()
+                ? entry.getBubbleMetadata().getBubbleIntent()
                 : null;
+        if (entry.getBubbleMetadata() != null
+                && entry.getBubbleMetadata().getShortcutId() != null) {
+            return true;
+        }
         if (intent == null) {
             Log.w(TAG, "Unable to create bubble -- no intent: " + entry.getKey());
             return false;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 97224f1..cc0824e 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -111,7 +111,10 @@
     }
 
     private final Context mContext;
+    /** Bubbles that are actively in the stack. */
     private final List<Bubble> mBubbles;
+    /** Bubbles that are being loaded but haven't been added to the stack just yet. */
+    private final List<Bubble> mPendingBubbles;
     private Bubble mSelectedBubble;
     private boolean mExpanded;
     private final int mMaxBubbles;
@@ -143,6 +146,7 @@
     public BubbleData(Context context) {
         mContext = context;
         mBubbles = new ArrayList<>();
+        mPendingBubbles = new ArrayList<>();
         mStateChange = new Update(mBubbles);
         mMaxBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_rendered);
     }
@@ -188,7 +192,15 @@
     Bubble getOrCreateBubble(NotificationEntry entry) {
         Bubble bubble = getBubbleWithKey(entry.getKey());
         if (bubble == null) {
+            // Check for it in pending
+            for (int i = 0; i < mPendingBubbles.size(); i++) {
+                Bubble b = mPendingBubbles.get(i);
+                if (b.getKey().equals(entry.getKey())) {
+                    return b;
+                }
+            }
             bubble = new Bubble(entry);
+            mPendingBubbles.add(bubble);
         } else {
             bubble.setEntry(entry);
         }
@@ -204,9 +216,9 @@
         if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "notificationEntryUpdated: " + bubble);
         }
-
+        mPendingBubbles.remove(bubble); // No longer pending once we're here
         Bubble prevBubble = getBubbleWithKey(bubble.getKey());
-        suppressFlyout |= !shouldShowFlyout(bubble.getEntry());
+        suppressFlyout |= !bubble.getEntry().getRanking().visuallyInterruptive();
 
         if (prevBubble == null) {
             // Create a new bubble
@@ -317,14 +329,6 @@
         return bubbleChildren;
     }
 
-    private boolean shouldShowFlyout(NotificationEntry notif) {
-        if (notif.getRanking().visuallyInterruptive()) {
-            return true;
-        }
-        return hasBubbleWithKey(notif.getKey())
-                && !getBubbleWithKey(notif.getKey()).showInShade();
-    }
-
     private void doAdd(Bubble bubble) {
         if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "doAdd: " + bubble);
@@ -377,6 +381,12 @@
     }
 
     private void doRemove(String key, @DismissReason int reason) {
+        //  If it was pending remove it
+        for (int i = 0; i < mPendingBubbles.size(); i++) {
+            if (mPendingBubbles.get(i).getKey().equals(key)) {
+                mPendingBubbles.remove(mPendingBubbles.get(i));
+            }
+        }
         int indexToRemove = indexForKey(key);
         if (indexToRemove == -1) {
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index c1705db..48ce4e9 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -42,7 +42,6 @@
 import android.service.notification.StatusBarNotification;
 import android.util.AttributeSet;
 import android.util.Log;
-import android.util.StatsLog;
 import android.view.View;
 import android.view.WindowInsets;
 import android.view.WindowManager;
@@ -52,6 +51,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.recents.TriangleShape;
+import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.AlphaOptimizedButton;
 
 /**
@@ -357,7 +357,7 @@
 
             if (isNew) {
                 mBubbleIntent = mBubble.getBubbleIntent();
-                if (mBubbleIntent != null) {
+                if (mBubbleIntent != null || mBubble.getShortcutInfo() != null) {
                     setContentVisibility(false);
                     mActivityView.setVisibility(VISIBLE);
                 }
@@ -467,7 +467,7 @@
             mStackView.collapseStack(() -> {
                 mContext.startActivityAsUser(intent, mBubble.getEntry().getSbn().getUser());
                 logBubbleClickEvent(mBubble,
-                        StatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
+                        SysUiStatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
             });
         }
     }
@@ -543,7 +543,8 @@
     }
 
     private boolean usingActivityView() {
-        return mBubbleIntent != null && mActivityView != null;
+        return (mBubbleIntent != null || mBubble.getShortcutInfo() != null)
+                && mActivityView != null;
     }
 
     /**
@@ -564,7 +565,7 @@
      */
     private void logBubbleClickEvent(Bubble bubble, int action) {
         StatusBarNotification notification = bubble.getEntry().getSbn();
-        StatsLog.write(StatsLog.BUBBLE_UI_CHANGED,
+        SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
                 notification.getPackageName(),
                 notification.getNotification().getChannelId(),
                 notification.getId(),
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
index 4c1cf49..4252f72 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
@@ -29,7 +29,6 @@
 import android.app.PendingIntent;
 import android.app.Person;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.LauncherApps;
 import android.content.pm.ShortcutInfo;
 import android.graphics.Color;
@@ -182,7 +181,7 @@
             ShortcutInfo info = getShortcutInfo(context, entry.getSbn().getPackageName(),
                     entry.getSbn().getUser(), shortcutId);
             if (info != null) {
-                metadata = createForShortcut(context, entry);
+                metadata = createForShortcut(shortcutId);
             }
 
             // Replace existing metadata with shortcut, or we're bubbling for experiment
@@ -259,30 +258,17 @@
         }
         if (intent != null) {
             return new Notification.BubbleMetadata.Builder()
+                    .createIntentBubble(intent, icon)
                     .setDesiredHeight(BUBBLE_HEIGHT)
-                    .setIcon(icon)
-                    .setIntent(intent)
                     .build();
         }
         return null;
     }
 
-    static Notification.BubbleMetadata createForShortcut(Context context, NotificationEntry entry) {
-        // ShortcutInfo does not return an icon, instead a Drawable, lets just use
-        // notification icon for BubbleMetadata.
-        Icon icon = entry.getSbn().getNotification().getSmallIcon();
-
-        // ShortcutInfo does not return the intent, lets make a fake but identifiable
-        // intent so we can still add bubbleMetadata
-        if (sDummyShortcutIntent == null) {
-            Intent i = new Intent(SHORTCUT_DUMMY_INTENT);
-            sDummyShortcutIntent = PendingIntent.getActivity(context, 0, i,
-                    PendingIntent.FLAG_UPDATE_CURRENT);
-        }
+    static Notification.BubbleMetadata createForShortcut(String shortcutId) {
         return new Notification.BubbleMetadata.Builder()
                 .setDesiredHeight(BUBBLE_HEIGHT)
-                .setIcon(icon)
-                .setIntent(sDummyShortcutIntent)
+                .createShortcutBubble(shortcutId)
                 .build();
     }
 
@@ -304,10 +290,6 @@
                 : null;
     }
 
-    static boolean isShortcutIntent(PendingIntent intent) {
-        return intent != null && intent.equals(sDummyShortcutIntent);
-    }
-
     static List<Person> getPeopleFromNotification(NotificationEntry entry) {
         Bundle extras = entry.getSbn().getNotification().extras;
         ArrayList<Person> personList = new ArrayList<>();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
index 78e98eb..4194352 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
@@ -32,11 +32,13 @@
 import android.graphics.PointF;
 import android.graphics.RectF;
 import android.graphics.drawable.ShapeDrawable;
+import android.text.TextUtils;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewOutlineProvider;
 import android.widget.FrameLayout;
+import android.widget.ImageView;
 import android.widget.TextView;
 
 import androidx.annotation.Nullable;
@@ -65,7 +67,9 @@
     private final float mCornerRadius;
 
     private final ViewGroup mFlyoutTextContainer;
-    private final TextView mFlyoutText;
+    private final ImageView mSenderAvatar;
+    private final TextView mSenderText;
+    private final TextView mMessageText;
 
     /** Values related to the 'new' dot which we use to figure out where to collapse the flyout. */
     private final float mNewDotRadius;
@@ -142,7 +146,9 @@
         LayoutInflater.from(context).inflate(R.layout.bubble_flyout, this, true);
 
         mFlyoutTextContainer = findViewById(R.id.bubble_flyout_text_container);
-        mFlyoutText = mFlyoutTextContainer.findViewById(R.id.bubble_flyout_text);
+        mSenderText = findViewById(R.id.bubble_flyout_name);
+        mSenderAvatar = findViewById(R.id.bubble_flyout_avatar);
+        mMessageText = mFlyoutTextContainer.findViewById(R.id.bubble_flyout_text);
 
         final Resources res = getResources();
         mFlyoutPadding = res.getDimensionPixelSize(R.dimen.bubble_flyout_padding_x);
@@ -204,9 +210,34 @@
 
     /** Configures the flyout, collapsed into to dot form. */
     void setupFlyoutStartingAsDot(
-            CharSequence updateMessage, PointF stackPos, float parentWidth,
-            boolean arrowPointingLeft, int dotColor, @Nullable Runnable onLayoutComplete,
-            @Nullable Runnable onHide, float[] dotCenter, boolean hideDot) {
+            Bubble.FlyoutMessage flyoutMessage,
+            PointF stackPos,
+            float parentWidth,
+            boolean arrowPointingLeft,
+            int dotColor,
+            @Nullable Runnable onLayoutComplete,
+            @Nullable Runnable onHide,
+            float[] dotCenter,
+            boolean hideDot) {
+
+        if (flyoutMessage.senderAvatar != null && flyoutMessage.isGroupChat) {
+            mSenderAvatar.setVisibility(VISIBLE);
+            mSenderAvatar.setImageDrawable(flyoutMessage.senderAvatar);
+        } else {
+            mSenderAvatar.setVisibility(GONE);
+            mSenderAvatar.setTranslationX(0);
+            mMessageText.setTranslationX(0);
+            mSenderText.setTranslationX(0);
+        }
+
+        // Name visibility
+        if (!TextUtils.isEmpty(flyoutMessage.senderName)) {
+            mSenderText.setText(flyoutMessage.senderName);
+            mSenderText.setVisibility(VISIBLE);
+        } else {
+            mSenderText.setVisibility(GONE);
+        }
+
         mArrowPointingLeft = arrowPointingLeft;
         mDotColor = dotColor;
         mOnHide = onHide;
@@ -217,15 +248,15 @@
         // Set the flyout TextView's max width in terms of percent, and then subtract out the
         // padding so that the entire flyout view will be the desired width (rather than the
         // TextView being the desired width + extra padding).
-        mFlyoutText.setMaxWidth(
+        mMessageText.setMaxWidth(
                 (int) (parentWidth * FLYOUT_MAX_WIDTH_PERCENT) - mFlyoutPadding * 2);
-        mFlyoutText.setText(updateMessage);
+        mMessageText.setText(flyoutMessage.message);
 
         // Wait for the TextView to lay out so we know its line count.
         post(() -> {
             float restingTranslationY;
             // Multi line flyouts get top-aligned to the bubble.
-            if (mFlyoutText.getLineCount() > 1) {
+            if (mMessageText.getLineCount() > 1) {
                 restingTranslationY = stackPos.y + mBubbleIconTopPadding;
             } else {
                 // Single line flyouts are vertically centered with respect to the bubble.
@@ -289,11 +320,20 @@
         mPercentStillFlyout = (1f - mPercentTransitionedToDot);
 
         // Move and fade out the text.
-        mFlyoutText.setTranslationX(
-                (mArrowPointingLeft ? -getWidth() : getWidth()) * mPercentTransitionedToDot);
-        mFlyoutText.setAlpha(clampPercentage(
+        final float translationX = mPercentTransitionedToDot
+                * (mArrowPointingLeft ? -getWidth() : getWidth());
+        final float alpha = clampPercentage(
                 (mPercentStillFlyout - (1f - BubbleStackView.FLYOUT_DRAG_PERCENT_DISMISS))
-                        / BubbleStackView.FLYOUT_DRAG_PERCENT_DISMISS));
+                        / BubbleStackView.FLYOUT_DRAG_PERCENT_DISMISS);
+
+        mMessageText.setTranslationX(translationX);
+        mMessageText.setAlpha(alpha);
+
+        mSenderText.setTranslationX(translationX);
+        mSenderText.setAlpha(alpha);
+
+        mSenderAvatar.setTranslationX(translationX);
+        mSenderAvatar.setAlpha(alpha);
 
         // Reduce the elevation towards that of the topmost bubble.
         setTranslationZ(
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
index b32dbb7..3b818db 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
@@ -18,6 +18,7 @@
 import android.app.Notification;
 import android.content.Context;
 import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.drawable.BitmapDrawable;
@@ -48,16 +49,19 @@
     /**
      * Returns the drawable that the developer has provided to display in the bubble.
      */
-    Drawable getBubbleDrawable(Bubble b, Context context) {
-        if (b.getShortcutInfo() != null && b.usingShortcutInfo()) {
+    Drawable getBubbleDrawable(Context context, ShortcutInfo shortcutInfo,
+            Notification.BubbleMetadata metadata) {
+        if (shortcutInfo != null) {
             LauncherApps launcherApps =
                     (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
             int density = context.getResources().getConfiguration().densityDpi;
-            return launcherApps.getShortcutIconDrawable(b.getShortcutInfo(), density);
+            return launcherApps.getShortcutIconDrawable(shortcutInfo, density);
         } else {
-            Notification.BubbleMetadata metadata = b.getEntry().getBubbleMetadata();
-            Icon ic = metadata.getIcon();
-            return ic.loadDrawable(context);
+            Icon ic = metadata.getBubbleIcon();
+            if (ic != null) {
+                return ic.loadDrawable(context);
+            }
+            return null;
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
index 018b631..d99607f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
@@ -10,6 +10,8 @@
 
 import com.android.systemui.R;
 
+import javax.inject.Inject;
+
 /**
  * Activity for showing aged out bubbles.
  * Must be public to be accessible to androidx...AppComponentFactory
@@ -17,6 +19,12 @@
 public class BubbleOverflowActivity extends Activity {
     private RecyclerView mRecyclerView;
     private int mMaxBubbles;
+    private BubbleController mBubbleController;
+
+    @Inject
+    public BubbleOverflowActivity(BubbleController controller) {
+        mBubbleController = controller;
+    }
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -64,6 +72,6 @@
     }
 
     public void onDestroy() {
-        super.onStop();
+        super.onDestroy();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 8987683..54a42a6 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -45,7 +45,6 @@
 import android.os.Vibrator;
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
-import android.util.StatsLog;
 import android.view.Choreographer;
 import android.view.DisplayCutout;
 import android.view.Gravity;
@@ -73,7 +72,7 @@
 import com.android.systemui.bubbles.animation.ExpandedAnimationController;
 import com.android.systemui.bubbles.animation.PhysicsAnimationLayout;
 import com.android.systemui.bubbles.animation.StackAnimationController;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.shared.system.SysUiStatsLog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -716,22 +715,6 @@
         return mExpandedBubble;
     }
 
-    /**
-     * Sets the bubble that should be expanded and expands if needed.
-     *
-     * @param key the {@link NotificationEntry#key} associated with the bubble to expand.
-     * @deprecated replaced by setSelectedBubble(Bubble) + setExpanded(true)
-     */
-    @Deprecated
-    void setExpandedBubble(String key) {
-        Bubble bubbleToExpand = mBubbleData.getBubbleWithKey(key);
-        if (bubbleToExpand != null) {
-            setSelectedBubble(bubbleToExpand);
-            bubbleToExpand.setShowInShade(false);
-            setExpanded(true);
-        }
-    }
-
     // via BubbleData.Listener
     void addBubble(Bubble bubble) {
         if (DEBUG_BUBBLE_STACK_VIEW) {
@@ -752,7 +735,7 @@
         ViewClippingUtil.setClippingDeactivated(bubble.getIconView(), true, mClippingParameters);
         animateInFlyoutForBubble(bubble);
         requestUpdate();
-        logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__POSTED);
+        logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__POSTED);
         updatePointerPosition();
     }
 
@@ -766,7 +749,7 @@
         if (removedIndex >= 0) {
             mBubbleContainer.removeViewAt(removedIndex);
             bubble.cleanupExpandedState();
-            logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
+            logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
         } else {
             Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
         }
@@ -777,7 +760,7 @@
     void updateBubble(Bubble bubble) {
         animateInFlyoutForBubble(bubble);
         requestUpdate();
-        logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__UPDATED);
+        logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__UPDATED);
     }
 
     public void updateBubbleOrder(List<Bubble> bubbles) {
@@ -817,8 +800,9 @@
                 updateExpandedBubble();
                 updatePointerPosition();
                 requestUpdate();
-                logBubbleEvent(previouslySelected, StatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
-                logBubbleEvent(bubbleToSelect, StatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
+                logBubbleEvent(previouslySelected,
+                        SysUiStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
+                logBubbleEvent(bubbleToSelect, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
                 notifyExpansionChanged(previouslySelected, false /* expanded */);
                 notifyExpansionChanged(bubbleToSelect, true /* expanded */);
             });
@@ -840,12 +824,12 @@
         }
         if (mIsExpanded) {
             animateCollapse();
-            logBubbleEvent(mExpandedBubble, StatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
+            logBubbleEvent(mExpandedBubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
         } else {
             animateExpansion();
             // TODO: move next line to BubbleData
-            logBubbleEvent(mExpandedBubble, StatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
-            logBubbleEvent(mExpandedBubble, StatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED);
+            logBubbleEvent(mExpandedBubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
+            logBubbleEvent(mExpandedBubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED);
         }
         notifyExpansionChanged(mExpandedBubble, mIsExpanded);
     }
@@ -862,7 +846,7 @@
         }
         mBubbleData.dismissAll(reason);
         logBubbleEvent(null /* no bubble associated with bubble stack dismiss */,
-                StatsLog.BUBBLE_UICHANGED__ACTION__STACK_DISMISSED);
+                SysUiStatsLog.BUBBLE_UICHANGED__ACTION__STACK_DISMISSED);
     }
 
     /**
@@ -1110,7 +1094,7 @@
 
         final float newStackX = mStackAnimationController.flingStackThenSpringToEdge(x, velX, velY);
         logBubbleEvent(null /* no bubble associated with bubble stack move */,
-                StatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
+                SysUiStatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
 
         mStackOnLeftOrWillBe = newStackX <= 0;
         updateBubbleZOrdersAndDotPosition(true /* animate */);
@@ -1393,9 +1377,10 @@
      */
     @VisibleForTesting
     void animateInFlyoutForBubble(Bubble bubble) {
-        final CharSequence updateMessage = bubble.getUpdateMessage(getContext());
+        Bubble.FlyoutMessage flyoutMessage = bubble.getFlyoutMessage();
         final BadgedImageView bubbleView = bubble.getIconView();
-        if (updateMessage == null
+        if (flyoutMessage == null
+                || flyoutMessage.message == null
                 || !bubble.showFlyout()
                 || isExpanded()
                 || mIsExpansionAnimating
@@ -1448,8 +1433,8 @@
                 };
                 mFlyout.postDelayed(mAnimateInFlyout, 200);
             };
-            mFlyout.setupFlyoutStartingAsDot(
-                    updateMessage, mStackAnimationController.getStackPosition(), getWidth(),
+            mFlyout.setupFlyoutStartingAsDot(flyoutMessage,
+                    mStackAnimationController.getStackPosition(), getWidth(),
                     mStackAnimationController.isStackOnLeftSide(),
                     bubble.getIconView().getDotColor() /* dotColor */,
                     expandFlyoutAfterDelay /* onLayoutComplete */,
@@ -1460,7 +1445,7 @@
         });
         mFlyout.removeCallbacks(mHideFlyout);
         mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
-        logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__FLYOUT);
+        logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__FLYOUT);
     }
 
     /** Hide the flyout immediately and cancel any pending hide runnables. */
@@ -1658,7 +1643,7 @@
     private void logBubbleEvent(@Nullable Bubble bubble, int action) {
         if (bubble == null || bubble.getEntry() == null
                 || bubble.getEntry().getSbn() == null) {
-            StatsLog.write(StatsLog.BUBBLE_UI_CHANGED,
+            SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
                     null /* package name */,
                     null /* notification channel */,
                     0 /* notification ID */,
@@ -1672,7 +1657,7 @@
                     false /* isAppForeground (unused) */);
         } else {
             StatusBarNotification notification = bubble.getEntry().getSbn();
-            StatsLog.write(StatsLog.BUBBLE_UI_CHANGED,
+            SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
                     notification.getPackageName(),
                     notification.getNotification().getChannelId(),
                     notification.getId(),
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
index 41f5028..e705584 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
@@ -21,6 +21,9 @@
 import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
 import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.Person;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -31,7 +34,9 @@
 import android.graphics.Path;
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
+import android.os.Parcelable;
 import android.service.notification.StatusBarNotification;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.PathParser;
 import android.view.LayoutInflater;
@@ -41,8 +46,10 @@
 import com.android.internal.graphics.ColorUtils;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
 import java.lang.ref.WeakReference;
+import java.util.List;
 
 /**
  * Simple task to inflate views & load necessary info to display a bubble.
@@ -98,6 +105,9 @@
         }
     }
 
+    /**
+     * Info necessary to render a bubble.
+     */
     static class BubbleViewInfo {
         BadgedImageView imageView;
         BubbleExpandedView expandedView;
@@ -106,6 +116,7 @@
         Bitmap badgedBubbleImage;
         int dotColor;
         Path dotPath;
+        Bubble.FlyoutMessage flyoutMessage;
 
         @Nullable
         static BubbleViewInfo populate(Context c, BubbleStackView stackView,
@@ -126,15 +137,22 @@
             StatusBarNotification sbn = b.getEntry().getSbn();
             String packageName = sbn.getPackageName();
 
-            // Shortcut info for this bubble
-            String shortcutId = sbn.getNotification().getShortcutId();
-            if (BubbleExperimentConfig.useShortcutInfoToBubble(c)
-                    && shortcutId != null) {
-                info.shortcutInfo = BubbleExperimentConfig.getShortcutInfo(c,
-                        packageName,
-                        sbn.getUser(), shortcutId);
+            // Real shortcut info for this bubble
+            String bubbleShortcutId =  b.getEntry().getBubbleMetadata().getShortcutId();
+            if (bubbleShortcutId != null) {
+                info.shortcutInfo = BubbleExperimentConfig.getShortcutInfo(c, packageName,
+                        sbn.getUser(), bubbleShortcutId);
+            } else {
+                // Check for experimental shortcut
+                String shortcutId = sbn.getNotification().getShortcutId();
+                if (BubbleExperimentConfig.useShortcutInfoToBubble(c) && shortcutId != null) {
+                    info.shortcutInfo = BubbleExperimentConfig.getShortcutInfo(c,
+                            packageName,
+                            sbn.getUser(), shortcutId);
+                }
             }
 
+
             // App name & app icon
             PackageManager pm = c.getPackageManager();
             ApplicationInfo appInfo;
@@ -158,7 +176,8 @@
             }
 
             // Badged bubble image
-            Drawable bubbleDrawable = iconFactory.getBubbleDrawable(b, c);
+            Drawable bubbleDrawable = iconFactory.getBubbleDrawable(c, info.shortcutInfo,
+                    b.getEntry().getBubbleMetadata());
             BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon);
             info.badgedBubbleImage = iconFactory.getBubbleBitmap(bubbleDrawable,
                     badgeBitmapInfo).icon;
@@ -176,7 +195,80 @@
             info.dotPath = iconPath;
             info.dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color,
                     Color.WHITE, WHITE_SCRIM_ALPHA);
+
+            // Flyout
+            info.flyoutMessage = extractFlyoutMessage(c, b.getEntry());
             return info;
         }
     }
+
+
+    /**
+     * Returns our best guess for the most relevant text summary of the latest update to this
+     * notification, based on its type. Returns null if there should not be an update message.
+     */
+    @NonNull
+    static Bubble.FlyoutMessage extractFlyoutMessage(Context context,
+            NotificationEntry entry) {
+        final Notification underlyingNotif = entry.getSbn().getNotification();
+        final Class<? extends Notification.Style> style = underlyingNotif.getNotificationStyle();
+
+        Bubble.FlyoutMessage bubbleMessage = new Bubble.FlyoutMessage();
+        bubbleMessage.isGroupChat = underlyingNotif.extras.getBoolean(
+                Notification.EXTRA_IS_GROUP_CONVERSATION);
+        try {
+            if (Notification.BigTextStyle.class.equals(style)) {
+                // Return the big text, it is big so probably important. If it's not there use the
+                // normal text.
+                CharSequence bigText =
+                        underlyingNotif.extras.getCharSequence(Notification.EXTRA_BIG_TEXT);
+                bubbleMessage.message = !TextUtils.isEmpty(bigText)
+                        ? bigText
+                        : underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
+                return bubbleMessage;
+            } else if (Notification.MessagingStyle.class.equals(style)) {
+                final List<Notification.MessagingStyle.Message> messages =
+                        Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+                                (Parcelable[]) underlyingNotif.extras.get(
+                                        Notification.EXTRA_MESSAGES));
+
+                final Notification.MessagingStyle.Message latestMessage =
+                        Notification.MessagingStyle.findLatestIncomingMessage(messages);
+                if (latestMessage != null) {
+                    bubbleMessage.message = latestMessage.getText();
+                    Person sender = latestMessage.getSenderPerson();
+                    bubbleMessage.senderName = sender != null
+                            ? sender.getName()
+                            : null;
+                    bubbleMessage.senderAvatar = sender != null
+                            ? sender.getIcon().loadDrawable(context)
+                            : null;
+                    return bubbleMessage;
+                }
+            } else if (Notification.InboxStyle.class.equals(style)) {
+                CharSequence[] lines =
+                        underlyingNotif.extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES);
+
+                // Return the last line since it should be the most recent.
+                if (lines != null && lines.length > 0) {
+                    bubbleMessage.message = lines[lines.length - 1];
+                    return bubbleMessage;
+                }
+            } else if (Notification.MediaStyle.class.equals(style)) {
+                // Return nothing, media updates aren't typically useful as a text update.
+                return bubbleMessage;
+            } else {
+                // Default to text extra.
+                bubbleMessage.message =
+                        underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
+                return bubbleMessage;
+            }
+        } catch (ClassCastException | NullPointerException | ArrayIndexOutOfBoundsException e) {
+            // No use crashing, we'll just return null and the caller will assume there's no update
+            // message.
+            e.printStackTrace();
+        }
+
+        return bubbleMessage;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
index 59d68bc..6528f37 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -97,13 +97,11 @@
     private boolean mSpringingBubbleToTouch = false;
 
     private int mExpandedViewPadding;
-    private float mLauncherGridDiff;
 
     public ExpandedAnimationController(Point displaySize, int expandedViewPadding,
             int orientation) {
         updateOrientation(orientation, displaySize);
         mExpandedViewPadding = expandedViewPadding;
-        mLauncherGridDiff = 30f;
     }
 
     /**
@@ -569,15 +567,7 @@
      * @return Space between bubbles in row above expanded view.
      */
     private float getSpaceBetweenBubbles() {
-        /**
-         * Ordered left to right:
-         *  Screen edge
-         *      [mExpandedViewPadding]
-         *  Expanded view edge
-         *      [launcherGridDiff] --- arbitrary value until launcher exports widths
-         *  Launcher's app icon grid edge that we must match
-         */
-        final float rowMargins = (mExpandedViewPadding + mLauncherGridDiff) * 2;
+        final float rowMargins = mExpandedViewPadding * 2;
         final float maxRowWidth = getWidthForDisplayingBubbles() - rowMargins;
 
         final float totalBubbleWidth = mBubblesMaxRendered * mBubbleSizePx;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
index 563a0a7..31656a0 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
@@ -961,6 +961,13 @@
             if (view != null) {
                 final SpringAnimation animation =
                         (SpringAnimation) view.getTag(getTagIdForProperty(property));
+
+                // If the animation is null, the view was probably removed from the layout before
+                // the animation started.
+                if (animation == null) {
+                    return;
+                }
+
                 if (afterCallbacks != null) {
                     animation.addEndListener(new OneTimeEndListener() {
                         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
new file mode 100644
index 0000000..e6cdf50
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.controls
+
+import android.service.controls.Control
+
+data class ControlStatus(val control: Control, val favorite: Boolean, val removed: Boolean = false)
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
new file mode 100644
index 0000000..265ddd8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.controls
+
+import android.content.Context
+import android.content.pm.ServiceInfo
+import com.android.settingslib.applications.DefaultAppInfo
+
+class ControlsServiceInfo(
+    context: Context,
+    serviceInfo: ServiceInfo
+) : DefaultAppInfo(
+    context,
+    context.packageManager,
+    context.userId,
+    serviceInfo.componentName
+)
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt
new file mode 100644
index 0000000..b6cca3f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.controls.controller
+
+import android.content.ComponentName
+import android.service.controls.DeviceTypes
+import android.util.Log
+
+/**
+ * Stores basic information about a [Control] to persist and keep track of favorites.
+ */
+data class ControlInfo(
+    val component: ComponentName,
+    val controlId: String,
+    val controlTitle: CharSequence,
+    @DeviceTypes.DeviceType val deviceType: Int
+) {
+
+    companion object {
+        private const val TAG = "ControlInfo"
+        private const val SEPARATOR = ":"
+        fun createFromString(string: String): ControlInfo? {
+            val parts = string.split(SEPARATOR)
+            val component = ComponentName.unflattenFromString(parts[0])
+            if (parts.size != 4 || component == null) {
+                Log.e(TAG, "Cannot parse ControlInfo from $string")
+                return null
+            }
+            val type = try {
+                parts[3].toInt()
+            } catch (e: Exception) {
+                Log.e(TAG, "Cannot parse deviceType from ${parts[3]}")
+                return null
+            }
+            return ControlInfo(
+                    component,
+                    parts[1],
+                    parts[2],
+                    if (DeviceTypes.validDeviceType(type)) type else DeviceTypes.TYPE_UNKNOWN)
+        }
+    }
+    override fun toString(): String {
+        return component.flattenToString() +
+                "$SEPARATOR$controlId$SEPARATOR$controlTitle$SEPARATOR$deviceType"
+    }
+
+    class Builder {
+        lateinit var componentName: ComponentName
+        lateinit var controlId: String
+        lateinit var controlTitle: CharSequence
+        var deviceType: Int = DeviceTypes.TYPE_UNKNOWN
+
+        fun build() = ControlInfo(componentName, controlId, controlTitle, deviceType)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt
new file mode 100644
index 0000000..6b7fc4b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.controls.controller
+
+import android.content.ComponentName
+import android.service.controls.Control
+import android.service.controls.actions.ControlAction
+
+interface ControlsBindingController {
+    fun bindAndLoad(component: ComponentName, callback: (List<Control>) -> Unit)
+    fun bindServices(components: List<ComponentName>)
+    fun subscribe(controls: List<ControlInfo>)
+    fun action(controlInfo: ControlInfo, action: ControlAction)
+    fun unsubscribe()
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
new file mode 100644
index 0000000..80e48b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
@@ -0,0 +1,204 @@
+/*
+ * 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.controls.controller
+
+import android.content.ComponentName
+import android.content.Context
+import android.os.IBinder
+import android.service.controls.Control
+import android.service.controls.IControlsProviderCallback
+import android.service.controls.actions.ControlAction
+import android.util.ArrayMap
+import android.util.Log
+import com.android.internal.annotations.GuardedBy
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.concurrency.DelayableExecutor
+import dagger.Lazy
+import java.util.concurrent.atomic.AtomicBoolean
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+@VisibleForTesting
+open class ControlsBindingControllerImpl @Inject constructor(
+    private val context: Context,
+    @Background private val backgroundExecutor: DelayableExecutor,
+    private val lazyController: Lazy<ControlsController>
+) : ControlsBindingController {
+
+    companion object {
+        private const val TAG = "ControlsBindingControllerImpl"
+    }
+
+    private val refreshing = AtomicBoolean(false)
+
+    @GuardedBy("componentMap")
+    private val tokenMap: MutableMap<IBinder, ControlsProviderLifecycleManager> =
+            ArrayMap<IBinder, ControlsProviderLifecycleManager>()
+    @GuardedBy("componentMap")
+    private val componentMap: MutableMap<ComponentName, ControlsProviderLifecycleManager> =
+            ArrayMap<ComponentName, ControlsProviderLifecycleManager>()
+
+    private val serviceCallback = object : IControlsProviderCallback.Stub() {
+        override fun onLoad(token: IBinder, controls: MutableList<Control>) {
+            backgroundExecutor.execute(OnLoadRunnable(token, controls))
+        }
+
+        override fun onRefreshState(token: IBinder, controlStates: List<Control>) {
+            if (!refreshing.get()) {
+                Log.d(TAG, "Refresh outside of window for token:$token")
+            } else {
+                backgroundExecutor.execute(OnRefreshStateRunnable(token, controlStates))
+            }
+        }
+
+        override fun onControlActionResponse(
+            token: IBinder,
+            controlId: String,
+            @ControlAction.ResponseResult response: Int
+        ) {
+            backgroundExecutor.execute(OnActionResponseRunnable(token, controlId, response))
+        }
+    }
+
+    @VisibleForTesting
+    internal open fun createProviderManager(component: ComponentName):
+            ControlsProviderLifecycleManager {
+        return ControlsProviderLifecycleManager(
+                context,
+                backgroundExecutor,
+                serviceCallback,
+                component
+        )
+    }
+
+    private fun retrieveLifecycleManager(component: ComponentName):
+            ControlsProviderLifecycleManager {
+        synchronized(componentMap) {
+            val provider = componentMap.getOrPut(component) {
+                createProviderManager(component)
+            }
+            tokenMap.putIfAbsent(provider.token, provider)
+            return provider
+        }
+    }
+
+    override fun bindAndLoad(component: ComponentName, callback: (List<Control>) -> Unit) {
+        val provider = retrieveLifecycleManager(component)
+        provider.maybeBindAndLoad(callback)
+    }
+
+    override fun subscribe(controls: List<ControlInfo>) {
+        val controlsByComponentName = controls.groupBy { it.component }
+        if (refreshing.compareAndSet(false, true)) {
+            controlsByComponentName.forEach {
+                val provider = retrieveLifecycleManager(it.key)
+                backgroundExecutor.execute {
+                    provider.maybeBindAndSubscribe(it.value.map { it.controlId })
+                }
+            }
+        }
+        // Unbind unneeded providers
+        val providersWithFavorites = controlsByComponentName.keys
+        synchronized(componentMap) {
+            componentMap.forEach {
+                if (it.key !in providersWithFavorites) {
+                    backgroundExecutor.execute { it.value.unbindService() }
+                }
+            }
+        }
+    }
+
+    override fun unsubscribe() {
+        if (refreshing.compareAndSet(true, false)) {
+            val providers = synchronized(componentMap) {
+                componentMap.values.toList()
+            }
+            providers.forEach {
+                backgroundExecutor.execute { it.unsubscribe() }
+            }
+        }
+    }
+
+    override fun action(controlInfo: ControlInfo, action: ControlAction) {
+        val provider = retrieveLifecycleManager(controlInfo.component)
+        provider.maybeBindAndSendAction(controlInfo.controlId, action)
+    }
+
+    override fun bindServices(components: List<ComponentName>) {
+        components.forEach {
+            val provider = retrieveLifecycleManager(it)
+            backgroundExecutor.execute { provider.bindPermanently() }
+        }
+    }
+
+    private abstract inner class CallbackRunnable(val token: IBinder) : Runnable {
+        protected val provider: ControlsProviderLifecycleManager? =
+                synchronized(componentMap) {
+                    tokenMap.get(token)
+                }
+    }
+
+    private inner class OnLoadRunnable(
+        token: IBinder,
+        val list: List<Control>
+    ) : CallbackRunnable(token) {
+        override fun run() {
+            if (provider == null) {
+                Log.e(TAG, "No provider found for token:$token")
+                return
+            }
+            synchronized(componentMap) {
+                if (token !in tokenMap.keys) {
+                    Log.e(TAG, "Provider for token:$token does not exist anymore")
+                    return
+                }
+            }
+            provider.lastLoadCallback?.invoke(list) ?: run {
+                Log.w(TAG, "Null callback")
+            }
+            provider.maybeUnbindAndRemoveCallback()
+        }
+    }
+
+    private inner class OnRefreshStateRunnable(
+        token: IBinder,
+        val list: List<Control>
+    ) : CallbackRunnable(token) {
+        override fun run() {
+            if (!refreshing.get()) {
+                Log.d(TAG, "onRefresh outside of window from:${provider?.componentName}")
+            }
+            provider?.let {
+                lazyController.get().refreshStatus(it.componentName, list)
+            }
+        }
+    }
+
+    private inner class OnActionResponseRunnable(
+        token: IBinder,
+        val controlId: String,
+        @ControlAction.ResponseResult val response: Int
+    ) : CallbackRunnable(token) {
+        override fun run() {
+            provider?.let {
+                lazyController.get().onActionResponse(it.componentName, controlId, response)
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
new file mode 100644
index 0000000..4d95822
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.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.systemui.controls.controller
+
+import android.content.ComponentName
+import android.service.controls.Control
+import android.service.controls.actions.ControlAction
+import com.android.systemui.controls.ControlStatus
+
+interface ControlsController {
+    val available: Boolean
+
+    fun getFavoriteControls(): List<ControlInfo>
+    fun loadForComponent(componentName: ComponentName, callback: (List<ControlStatus>) -> Unit)
+    fun subscribeToFavorites()
+    fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean)
+    fun unsubscribe()
+    fun action(controlInfo: ControlInfo, action: ControlAction)
+    fun refreshStatus(componentName: ComponentName, controls: List<Control>)
+    fun onActionResponse(
+        componentName: ComponentName,
+        controlId: String,
+        @ControlAction.ResponseResult response: Int
+    )
+    fun clearFavorites()
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
new file mode 100644
index 0000000..7e328e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -0,0 +1,273 @@
+/*
+ * 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.controls.controller
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.os.Environment
+import android.provider.Settings
+import android.service.controls.Control
+import android.service.controls.actions.ControlAction
+import android.util.ArrayMap
+import android.util.Log
+import com.android.internal.annotations.GuardedBy
+import com.android.systemui.DumpController
+import com.android.systemui.Dumpable
+import com.android.systemui.controls.ControlStatus
+import com.android.systemui.controls.management.ControlsFavoritingActivity
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.concurrency.DelayableExecutor
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import java.util.Optional
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class ControlsControllerImpl @Inject constructor (
+    private val context: Context,
+    @Background private val executor: DelayableExecutor,
+    private val uiController: ControlsUiController,
+    private val bindingController: ControlsBindingController,
+    private val optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
+    dumpController: DumpController
+) : Dumpable, ControlsController {
+
+    companion object {
+        private const val TAG = "ControlsControllerImpl"
+        const val CONTROLS_AVAILABLE = "systemui.controls_available"
+    }
+
+    override val available = Settings.Secure.getInt(
+            context.contentResolver, CONTROLS_AVAILABLE, 0) != 0
+    val persistenceWrapper = optionalWrapper.orElseGet {
+        ControlsFavoritePersistenceWrapper(
+                Environment.buildPath(
+                        context.filesDir,
+                        ControlsFavoritePersistenceWrapper.FILE_NAME),
+                executor
+        )
+    }
+
+    // Map of map: ComponentName -> (String -> ControlInfo)
+    @GuardedBy("currentFavorites")
+    private val currentFavorites = ArrayMap<ComponentName, MutableMap<String, ControlInfo>>()
+
+    init {
+        if (available) {
+            dumpController.registerDumpable(this)
+            loadFavorites()
+        }
+    }
+
+    private fun loadFavorites() {
+        val infos = persistenceWrapper.readFavorites()
+        synchronized(currentFavorites) {
+            infos.forEach {
+                currentFavorites.getOrPut(it.component, { ArrayMap<String, ControlInfo>() })
+                        .put(it.controlId, it)
+            }
+        }
+    }
+
+    override fun loadForComponent(
+        componentName: ComponentName,
+        callback: (List<ControlStatus>) -> Unit
+    ) {
+        if (!available) {
+            Log.d(TAG, "Controls not available")
+            return
+        }
+        bindingController.bindAndLoad(componentName) {
+            synchronized(currentFavorites) {
+                val favoritesForComponentKeys: Set<String> =
+                        currentFavorites.get(componentName)?.keys ?: emptySet()
+                val changed = updateFavoritesLocked(componentName, it)
+                if (changed) {
+                    persistenceWrapper.storeFavorites(favoritesAsListLocked())
+                }
+                val removed = findRemovedLocked(favoritesForComponentKeys, it)
+                callback(removed.map { currentFavorites.getValue(componentName).getValue(it) }
+                            .map(::createRemovedStatus) +
+                        it.map { ControlStatus(it, it.controlId in favoritesForComponentKeys) })
+            }
+        }
+    }
+
+    private fun createRemovedStatus(controlInfo: ControlInfo): ControlStatus {
+        val intent = Intent(context, ControlsFavoritingActivity::class.java).apply {
+            putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, controlInfo.component)
+            flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
+        }
+        val pendingIntent = PendingIntent.getActivity(context,
+                controlInfo.component.hashCode(),
+                intent,
+                0)
+        val control = Control.StatelessBuilder(controlInfo.controlId, pendingIntent)
+                .setTitle(controlInfo.controlTitle)
+                .setDeviceType(controlInfo.deviceType)
+                .build()
+        return ControlStatus(control, true, true)
+    }
+
+    @GuardedBy("currentFavorites")
+    private fun findRemovedLocked(favoriteKeys: Set<String>, list: List<Control>): Set<String> {
+        val controlsKeys = list.map { it.controlId }
+        return favoriteKeys.minus(controlsKeys)
+    }
+
+    @GuardedBy("currentFavorites")
+    private fun updateFavoritesLocked(componentName: ComponentName, list: List<Control>): Boolean {
+        val favorites = currentFavorites.get(componentName) ?: mutableMapOf()
+        val favoriteKeys = favorites.keys
+        if (favoriteKeys.isEmpty()) return false // early return
+        var changed = false
+        list.forEach {
+            if (it.controlId in favoriteKeys) {
+                val value = favorites.getValue(it.controlId)
+                if (value.controlTitle != it.title || value.deviceType != it.deviceType) {
+                    favorites[it.controlId] = value.copy(controlTitle = it.title,
+                            deviceType = it.deviceType)
+                    changed = true
+                }
+            }
+        }
+        return changed
+    }
+
+    @GuardedBy("currentFavorites")
+    private fun favoritesAsListLocked(): List<ControlInfo> {
+        return currentFavorites.flatMap { it.value.values }
+    }
+
+    override fun subscribeToFavorites() {
+        if (!available) {
+            Log.d(TAG, "Controls not available")
+            return
+        }
+        // Make a copy of the favorites list
+        val favorites = synchronized(currentFavorites) {
+            currentFavorites.flatMap { it.value.values.toList() }
+        }
+        bindingController.subscribe(favorites)
+    }
+
+    override fun unsubscribe() {
+        if (!available) {
+            Log.d(TAG, "Controls not available")
+            return
+        }
+        bindingController.unsubscribe()
+    }
+
+    override fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean) {
+        if (!available) {
+            Log.d(TAG, "Controls not available")
+            return
+        }
+        var changed = false
+        val listOfControls = synchronized(currentFavorites) {
+            if (state) {
+                if (controlInfo.component !in currentFavorites) {
+                    currentFavorites.put(controlInfo.component, ArrayMap<String, ControlInfo>())
+                    changed = true
+                }
+                val controlsForComponent = currentFavorites.getValue(controlInfo.component)
+                if (controlInfo.controlId !in controlsForComponent) {
+                    controlsForComponent.put(controlInfo.controlId, controlInfo)
+                    changed = true
+                } else {
+                    if (controlsForComponent.getValue(controlInfo.controlId) != controlInfo) {
+                        controlsForComponent.put(controlInfo.controlId, controlInfo)
+                        changed = true
+                    }
+                }
+            } else {
+                changed = currentFavorites.get(controlInfo.component)
+                        ?.remove(controlInfo.controlId) != null
+            }
+            favoritesAsListLocked()
+        }
+        if (changed) {
+            persistenceWrapper.storeFavorites(listOfControls)
+        }
+    }
+
+    override fun refreshStatus(componentName: ComponentName, controls: List<Control>) {
+        if (!available) {
+            Log.d(TAG, "Controls not available")
+            return
+        }
+        executor.execute {
+            synchronized(currentFavorites) {
+                val changed = updateFavoritesLocked(componentName, controls)
+                if (changed) {
+                    persistenceWrapper.storeFavorites(favoritesAsListLocked())
+                }
+            }
+        }
+        uiController.onRefreshState(componentName, controls)
+    }
+
+    override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) {
+        if (!available) {
+            Log.d(TAG, "Controls not available")
+            return
+        }
+        uiController.onActionResponse(componentName, controlId, response)
+    }
+
+    override fun getFavoriteControls(): List<ControlInfo> {
+        if (!available) {
+            Log.d(TAG, "Controls not available")
+            return emptyList()
+        }
+        synchronized(currentFavorites) {
+            return favoritesAsListLocked()
+        }
+    }
+
+    override fun action(controlInfo: ControlInfo, action: ControlAction) {
+        bindingController.action(controlInfo, action)
+    }
+
+    override fun clearFavorites() {
+        val changed = synchronized(currentFavorites) {
+            currentFavorites.isNotEmpty().also {
+                currentFavorites.clear()
+            }
+        }
+        if (changed) {
+            persistenceWrapper.storeFavorites(emptyList())
+        }
+    }
+
+    override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+        pw.println("ControlsController state:")
+        pw.println("  Favorites:")
+        synchronized(currentFavorites) {
+            currentFavorites.forEach {
+                it.value.forEach {
+                    pw.println("    ${it.value}")
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt
new file mode 100644
index 0000000..6f2d71f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.controls.controller
+
+import android.app.ActivityManager
+import android.content.ComponentName
+import android.util.AtomicFile
+import android.util.Log
+import android.util.Slog
+import android.util.Xml
+import com.android.systemui.util.concurrency.DelayableExecutor
+import libcore.io.IoUtils
+import org.xmlpull.v1.XmlPullParser
+import org.xmlpull.v1.XmlPullParserException
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileNotFoundException
+import java.io.IOException
+
+class ControlsFavoritePersistenceWrapper(
+    val file: File,
+    val executor: DelayableExecutor
+) {
+
+    companion object {
+        private const val TAG = "ControlsFavoritePersistenceWrapper"
+        const val FILE_NAME = "controls_favorites.xml"
+        private const val TAG_CONTROLS = "controls"
+        private const val TAG_CONTROL = "control"
+        private const val TAG_COMPONENT = "component"
+        private const val TAG_ID = "id"
+        private const val TAG_TITLE = "title"
+        private const val TAG_TYPE = "type"
+    }
+
+    val currentUser: Int
+        get() = ActivityManager.getCurrentUser()
+
+    fun storeFavorites(list: List<ControlInfo>) {
+        executor.execute {
+            val atomicFile = AtomicFile(file)
+            val writer = try {
+                atomicFile.startWrite()
+            } catch (e: IOException) {
+                Log.e(TAG, "Failed to start write file", e)
+                return@execute
+            }
+            try {
+                Xml.newSerializer().apply {
+                    setOutput(writer, "utf-8")
+                    setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true)
+                    startDocument(null, true)
+                    startTag(null, TAG_CONTROLS)
+                    list.forEach {
+                        startTag(null, TAG_CONTROL)
+                        attribute(null, TAG_COMPONENT, it.component.flattenToString())
+                        attribute(null, TAG_ID, it.controlId)
+                        attribute(null, TAG_TITLE, it.controlTitle.toString())
+                        attribute(null, TAG_TYPE, it.deviceType.toString())
+                        endTag(null, TAG_CONTROL)
+                    }
+                    endTag(null, TAG_CONTROLS)
+                    endDocument()
+                    atomicFile.finishWrite(writer)
+                }
+            } catch (t: Throwable) {
+                Log.e(TAG, "Failed to write file, reverting to previous version")
+                atomicFile.failWrite(writer)
+            } finally {
+                IoUtils.closeQuietly(writer)
+            }
+        }
+    }
+
+    fun readFavorites(): List<ControlInfo> {
+        if (!file.exists()) {
+            Log.d(TAG, "No favorites, returning empty list")
+            return emptyList()
+        }
+        val reader = try {
+            FileInputStream(file)
+        } catch (fnfe: FileNotFoundException) {
+            Slog.i(TAG, "No file found")
+            return emptyList()
+        }
+        try {
+            val parser = Xml.newPullParser()
+            parser.setInput(reader, null)
+            return parseXml(parser)
+        } catch (e: XmlPullParserException) {
+            throw IllegalStateException("Failed parsing favorites file: $file", e)
+        } catch (e: IOException) {
+            throw IllegalStateException("Failed parsing favorites file: $file", e)
+        } finally {
+            IoUtils.closeQuietly(reader)
+        }
+    }
+
+    private fun parseXml(parser: XmlPullParser): List<ControlInfo> {
+        var type: Int = 0
+        val infos = mutableListOf<ControlInfo>()
+        while (parser.next().also { type = it } != XmlPullParser.END_DOCUMENT) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue
+            }
+            val tagName = parser.name
+            if (tagName == TAG_CONTROL) {
+                val component = ComponentName.unflattenFromString(
+                        parser.getAttributeValue(null, TAG_COMPONENT))
+                val id = parser.getAttributeValue(null, TAG_ID)
+                val title = parser.getAttributeValue(null, TAG_TITLE)
+                val type = parser.getAttributeValue(null, TAG_TYPE)?.toInt()
+                if (component != null && id != null && title != null && type != null) {
+                    infos.add(ControlInfo(component, id, title, type))
+                }
+            }
+        }
+        return infos
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
new file mode 100644
index 0000000..79057ad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
@@ -0,0 +1,283 @@
+/*
+ * 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.controls.controller
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.os.Binder
+import android.os.Bundle
+import android.os.IBinder
+import android.os.RemoteException
+import android.service.controls.Control
+import android.service.controls.ControlsProviderService.CALLBACK_BINDER
+import android.service.controls.ControlsProviderService.CALLBACK_BUNDLE
+import android.service.controls.ControlsProviderService.CALLBACK_TOKEN
+import android.service.controls.IControlsProvider
+import android.service.controls.IControlsProviderCallback
+import android.service.controls.actions.ControlAction
+import android.util.ArraySet
+import android.util.Log
+import com.android.internal.annotations.GuardedBy
+import com.android.systemui.util.concurrency.DelayableExecutor
+import java.util.concurrent.TimeUnit
+
+typealias LoadCallback = (List<Control>) -> Unit
+class ControlsProviderLifecycleManager(
+    private val context: Context,
+    private val executor: DelayableExecutor,
+    private val serviceCallback: IControlsProviderCallback.Stub,
+    val componentName: ComponentName
+) : IBinder.DeathRecipient {
+
+    var lastLoadCallback: LoadCallback? = null
+        private set
+    val token: IBinder = Binder()
+    private var unbindImmediate = false
+    private var requiresBound = false
+    private var isBound = false
+    @GuardedBy("queuedMessages")
+    private val queuedMessages: MutableSet<Message> = ArraySet()
+    private var wrapper: ControlsProviderServiceWrapper? = null
+    private var bindTryCount = 0
+    private val TAG = javaClass.simpleName
+    private var onLoadCanceller: Runnable? = null
+
+    companion object {
+        private const val MSG_LOAD = 0
+        private const val MSG_SUBSCRIBE = 1
+        private const val MSG_UNSUBSCRIBE = 2
+        private const val MSG_ON_ACTION = 3
+        private const val MSG_UNBIND = 4
+        private const val BIND_RETRY_DELAY = 1000L // ms
+        private const val LOAD_TIMEOUT = 5000L // ms
+        private const val MAX_BIND_RETRIES = 5
+        private const val DEBUG = true
+        private val BIND_FLAGS = Context.BIND_AUTO_CREATE or Context.BIND_FOREGROUND_SERVICE or
+                Context.BIND_WAIVE_PRIORITY
+    }
+
+    private val intent = Intent().apply {
+        component = componentName
+        putExtra(CALLBACK_BUNDLE, Bundle().apply {
+            putBinder(CALLBACK_BINDER, serviceCallback)
+            putBinder(CALLBACK_TOKEN, token)
+        })
+    }
+
+    private fun bindService(bind: Boolean) {
+        requiresBound = bind
+        if (bind) {
+            if (bindTryCount == MAX_BIND_RETRIES) {
+                return
+            }
+            if (DEBUG) {
+                Log.d(TAG, "Binding service $intent")
+            }
+            bindTryCount++
+            try {
+                isBound = context.bindService(intent, serviceConnection, BIND_FLAGS)
+            } catch (e: SecurityException) {
+                Log.e(TAG, "Failed to bind to service", e)
+                isBound = false
+            }
+        } else {
+            if (DEBUG) {
+                Log.d(TAG, "Unbinding service $intent")
+            }
+            bindTryCount = 0
+            wrapper = null
+            if (isBound) {
+                context.unbindService(serviceConnection)
+                isBound = false
+            }
+        }
+    }
+
+    fun bindPermanently() {
+        unbindImmediate = false
+        unqueueMessage(Message.Unbind)
+        bindService(true)
+    }
+
+    private val serviceConnection = object : ServiceConnection {
+        override fun onServiceConnected(name: ComponentName, service: IBinder) {
+            if (DEBUG) Log.d(TAG, "onServiceConnected $name")
+            bindTryCount = 0
+            wrapper = ControlsProviderServiceWrapper(IControlsProvider.Stub.asInterface(service))
+            try {
+                service.linkToDeath(this@ControlsProviderLifecycleManager, 0)
+            } catch (_: RemoteException) {}
+            handlePendingMessages()
+        }
+
+        override fun onServiceDisconnected(name: ComponentName?) {
+            if (DEBUG) Log.d(TAG, "onServiceDisconnected $name")
+            isBound = false
+            bindService(false)
+        }
+    }
+
+    private fun handlePendingMessages() {
+        val queue = synchronized(queuedMessages) {
+            ArraySet(queuedMessages).also {
+                queuedMessages.clear()
+            }
+        }
+        if (Message.Unbind in queue) {
+            bindService(false)
+            return
+        }
+        if (Message.Load in queue) {
+            load()
+        }
+        queue.filter { it is Message.Subscribe }.flatMap { (it as Message.Subscribe).list }.run {
+            subscribe(this)
+        }
+        queue.filter { it is Message.Action }.forEach {
+            val msg = it as Message.Action
+            onAction(msg.id, msg.action)
+        }
+    }
+
+    override fun binderDied() {
+        if (wrapper == null) return
+        wrapper = null
+        if (requiresBound) {
+            if (DEBUG) {
+                Log.d(TAG, "binderDied")
+            }
+            // Try rebinding some time later
+        }
+    }
+
+    private fun queueMessage(message: Message) {
+        synchronized(queuedMessages) {
+            queuedMessages.add(message)
+        }
+    }
+
+    private fun unqueueMessage(message: Message) {
+        synchronized(queuedMessages) {
+            queuedMessages.removeIf { it.type == message.type }
+        }
+    }
+
+    private fun load() {
+        if (DEBUG) {
+            Log.d(TAG, "load $componentName")
+        }
+        if (!(wrapper?.load() ?: false)) {
+            queueMessage(Message.Load)
+            binderDied()
+        }
+    }
+
+    fun maybeBindAndLoad(callback: LoadCallback) {
+        unqueueMessage(Message.Unbind)
+        lastLoadCallback = callback
+        onLoadCanceller = executor.executeDelayed({
+            // Didn't receive a response in time, log and send back empty list
+            Log.d(TAG, "Timeout waiting onLoad for $componentName")
+            serviceCallback.onLoad(token, emptyList())
+        }, LOAD_TIMEOUT, TimeUnit.MILLISECONDS)
+        if (isBound) {
+            load()
+        } else {
+            queueMessage(Message.Load)
+            unbindImmediate = true
+            bindService(true)
+        }
+    }
+
+    fun maybeBindAndSubscribe(controlIds: List<String>) {
+        if (isBound) {
+            subscribe(controlIds)
+        } else {
+            queueMessage(Message.Subscribe(controlIds))
+            bindService(true)
+        }
+    }
+
+    private fun subscribe(controlIds: List<String>) {
+        if (DEBUG) {
+            Log.d(TAG, "subscribe $componentName - $controlIds")
+        }
+        if (!(wrapper?.subscribe(controlIds) ?: false)) {
+            queueMessage(Message.Subscribe(controlIds))
+            binderDied()
+        }
+    }
+
+    fun maybeBindAndSendAction(controlId: String, action: ControlAction) {
+        if (isBound) {
+            onAction(controlId, action)
+        } else {
+            queueMessage(Message.Action(controlId, action))
+            bindService(true)
+        }
+    }
+
+    private fun onAction(controlId: String, action: ControlAction) {
+        if (DEBUG) {
+            Log.d(TAG, "onAction $componentName - $controlId")
+        }
+        if (!(wrapper?.onAction(controlId, action) ?: false)) {
+            queueMessage(Message.Action(controlId, action))
+            binderDied()
+        }
+    }
+
+    fun unsubscribe() {
+        if (DEBUG) {
+            Log.d(TAG, "unsubscribe $componentName")
+        }
+        unqueueMessage(Message.Subscribe(emptyList())) // Removes all subscribe messages
+        wrapper?.unsubscribe()
+    }
+
+    fun maybeUnbindAndRemoveCallback() {
+        lastLoadCallback = null
+        onLoadCanceller?.run()
+        onLoadCanceller = null
+        if (unbindImmediate) {
+            bindService(false)
+        }
+    }
+
+    fun unbindService() {
+        unbindImmediate = true
+        maybeUnbindAndRemoveCallback()
+    }
+
+    sealed class Message {
+        abstract val type: Int
+        object Load : Message() {
+            override val type = MSG_LOAD
+        }
+        object Unbind : Message() {
+            override val type = MSG_UNBIND
+        }
+        class Subscribe(val list: List<String>) : Message() {
+            override val type = MSG_SUBSCRIBE
+        }
+        class Action(val id: String, val action: ControlAction) : Message() {
+            override val type = MSG_ON_ACTION
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapper.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapper.kt
new file mode 100644
index 0000000..882a10d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapper.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.controls.controller
+
+import android.service.controls.actions.ControlAction
+import android.service.controls.IControlsProvider
+import android.util.Log
+
+class ControlsProviderServiceWrapper(val service: IControlsProvider) {
+    companion object {
+        private const val TAG = "ControlsProviderServiceWrapper"
+    }
+
+    private fun callThroughService(block: () -> Unit): Boolean {
+        try {
+            block()
+            return true
+        } catch (ex: Exception) {
+            Log.d(TAG, "Caught exception from ControlsProviderService", ex)
+            return false
+        }
+    }
+
+    fun load(): Boolean {
+        return callThroughService {
+            service.load()
+        }
+    }
+
+    fun subscribe(controlIds: List<String>): Boolean {
+        return callThroughService {
+            service.subscribe(controlIds)
+        }
+    }
+
+    fun unsubscribe(): Boolean {
+        return callThroughService {
+            service.unsubscribe()
+        }
+    }
+
+    fun onAction(controlId: String, action: ControlAction): Boolean {
+        return callThroughService {
+            service.onAction(controlId, action)
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
new file mode 100644
index 0000000..859311e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.controls.dagger
+
+import android.app.Activity
+import com.android.systemui.controls.controller.ControlsBindingController
+import com.android.systemui.controls.controller.ControlsBindingControllerImpl
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.controller.ControlsControllerImpl
+import com.android.systemui.controls.controller.ControlsFavoritePersistenceWrapper
+import com.android.systemui.controls.management.ControlsFavoritingActivity
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.management.ControlsListingControllerImpl
+import com.android.systemui.controls.management.ControlsProviderSelectorActivity
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.controls.ui.ControlsUiControllerImpl
+import dagger.Binds
+import dagger.BindsOptionalOf
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+abstract class ControlsModule {
+
+    @Binds
+    abstract fun provideControlsListingController(
+        controller: ControlsListingControllerImpl
+    ): ControlsListingController
+
+    @Binds
+    abstract fun provideControlsController(controller: ControlsControllerImpl): ControlsController
+
+    @Binds
+    abstract fun provideControlsBindingController(
+        controller: ControlsBindingControllerImpl
+    ): ControlsBindingController
+
+    @Binds
+    abstract fun provideUiController(controller: ControlsUiControllerImpl): ControlsUiController
+
+    @BindsOptionalOf
+    abstract fun optionalPersistenceWrapper(): ControlsFavoritePersistenceWrapper
+
+    @Binds
+    @IntoMap
+    @ClassKey(ControlsProviderSelectorActivity::class)
+    abstract fun provideControlsProviderActivity(
+        activity: ControlsProviderSelectorActivity
+    ): Activity
+
+    @Binds
+    @IntoMap
+    @ClassKey(ControlsFavoritingActivity::class)
+    abstract fun provideControlsFavoritingActivity(
+        activity: ControlsFavoritingActivity
+    ): Activity
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
new file mode 100644
index 0000000..d62bb4d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.controls.management
+
+import android.content.ComponentName
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.lifecycle.Lifecycle
+import androidx.recyclerview.widget.RecyclerView
+import com.android.settingslib.widget.CandidateInfo
+import com.android.systemui.R
+import java.util.concurrent.Executor
+
+/**
+ * Adapter for binding [CandidateInfo] related to [ControlsProviderService].
+ *
+ * This class handles subscribing and keeping track of the list of valid applications for
+ * displaying.
+ *
+ * @param uiExecutor an executor on the view thread of the containing [RecyclerView]
+ * @param lifecycle the lifecycle of the containing [LifecycleOwner] to control listening status
+ * @param controlsListingController the controller to keep track of valid applications
+ * @param layoutInflater an inflater for the views in the containing [RecyclerView]
+ * @param onAppSelected a callback to indicate that an app has been selected in the list.
+ */
+class AppAdapter(
+    uiExecutor: Executor,
+    lifecycle: Lifecycle,
+    controlsListingController: ControlsListingController,
+    private val layoutInflater: LayoutInflater,
+    private val onAppSelected: (ComponentName?) -> Unit = {}
+) : RecyclerView.Adapter<AppAdapter.Holder>() {
+
+    private var listOfServices = emptyList<CandidateInfo>()
+
+    private val callback = object : ControlsListingController.ControlsListingCallback {
+        override fun onServicesUpdated(list: List<CandidateInfo>) {
+            uiExecutor.execute {
+                listOfServices = list
+                notifyDataSetChanged()
+            }
+        }
+    }
+
+    init {
+        controlsListingController.observe(lifecycle, callback)
+    }
+
+    override fun onCreateViewHolder(parent: ViewGroup, i: Int): Holder {
+        return Holder(layoutInflater.inflate(R.layout.app_item, parent, false))
+    }
+
+    override fun getItemCount() = listOfServices.size
+
+    override fun onBindViewHolder(holder: Holder, index: Int) {
+        holder.bindData(listOfServices[index])
+        holder.itemView.setOnClickListener {
+            onAppSelected(ComponentName.unflattenFromString(listOfServices[index].key))
+        }
+    }
+
+    /**
+     * Holder for binding views in the [RecyclerView]-
+     */
+    class Holder(view: View) : RecyclerView.ViewHolder(view) {
+        private val icon: ImageView = itemView.requireViewById(com.android.internal.R.id.icon)
+        private val title: TextView = itemView.requireViewById(com.android.internal.R.id.title)
+
+        /**
+         * Bind data to the view
+         * @param data Information about the [ControlsProviderService] to bind to the data
+         */
+        fun bindData(data: CandidateInfo) {
+            icon.setImageDrawable(data.loadIcon())
+            title.text = data.loadLabel()
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
new file mode 100644
index 0000000..e6d3c26
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.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.systemui.controls.management
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.CheckBox
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.android.systemui.R
+import com.android.systemui.controls.ControlStatus
+import com.android.systemui.controls.controller.ControlInfo
+
+/**
+ * Adapter for binding [Control] information to views.
+ *
+ * @param layoutInflater an inflater for the views in the containing [RecyclerView]
+ * @param favoriteCallback a callback to be called when the favorite status of a [Control] is
+ *                         changed. The callback will take a [ControlInfo.Builder] that's
+ *                         pre-populated with the [Control] information and the new favorite
+ *                         status.
+ */
+class ControlAdapter(
+    private val layoutInflater: LayoutInflater,
+    private val favoriteCallback: (ControlInfo.Builder, Boolean) -> Unit
+) : RecyclerView.Adapter<ControlAdapter.Holder>() {
+
+    var listOfControls = emptyList<ControlStatus>()
+
+    override fun onCreateViewHolder(parent: ViewGroup, i: Int): Holder {
+        return Holder(layoutInflater.inflate(R.layout.control_item, parent, false))
+    }
+
+    override fun getItemCount() = listOfControls.size
+
+    override fun onBindViewHolder(holder: Holder, index: Int) {
+        holder.bindData(listOfControls[index], favoriteCallback)
+    }
+
+    /**
+     * Holder for binding views in the [RecyclerView]-
+     */
+    class Holder(view: View) : RecyclerView.ViewHolder(view) {
+        private val title: TextView = itemView.requireViewById(R.id.title)
+        private val subtitle: TextView = itemView.requireViewById(R.id.subtitle)
+        private val favorite: CheckBox = itemView.requireViewById(R.id.favorite)
+
+        /**
+         * Bind data to the view
+         * @param data information about the [Control]
+         * @param callback a callback to be called when the favorite status of the [Control] is
+         *                 changed. The callback will take a [ControlInfo.Builder] that's
+         *                 pre-populated with the [Control] information and the new favorite status.
+         */
+        fun bindData(data: ControlStatus, callback: (ControlInfo.Builder, Boolean) -> Unit) {
+            title.text = data.control.title
+            subtitle.text = data.control.subtitle
+            favorite.isChecked = data.favorite
+            favorite.setOnClickListener {
+                val infoBuilder = ControlInfo.Builder().apply {
+                    controlId = data.control.controlId
+                    controlTitle = data.control.title
+                    deviceType = data.control.deviceType
+                }
+                callback(infoBuilder, favorite.isChecked)
+            }
+        }
+    }
+
+    fun setItems(list: List<ControlStatus>) {
+        listOfControls = list
+        notifyDataSetChanged()
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
new file mode 100644
index 0000000..01c4fef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.controls.management
+
+import android.app.Activity
+import android.content.ComponentName
+import android.os.Bundle
+import android.view.LayoutInflater
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.android.systemui.controls.controller.ControlInfo
+import com.android.systemui.controls.controller.ControlsControllerImpl
+import com.android.systemui.dagger.qualifiers.Main
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+class ControlsFavoritingActivity @Inject constructor(
+    @Main private val executor: Executor,
+    private val controller: ControlsControllerImpl
+) : Activity() {
+
+    companion object {
+        private const val TAG = "ControlsFavoritingActivity"
+        const val EXTRA_APP = "extra_app_label"
+        const val EXTRA_COMPONENT = "extra_component"
+    }
+
+    private lateinit var recyclerView: RecyclerView
+    private lateinit var adapter: ControlAdapter
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        val app = intent.getCharSequenceExtra(EXTRA_APP)
+        val component = intent.getParcelableExtra<ComponentName>(EXTRA_COMPONENT)
+
+        // If we have no component name, there's not much we can do.
+        val callback = component?.let {
+            { infoBuilder: ControlInfo.Builder, status: Boolean ->
+                infoBuilder.componentName = it
+                controller.changeFavoriteStatus(infoBuilder.build(), status)
+            }
+        } ?: { _, _ -> Unit }
+
+        recyclerView = RecyclerView(applicationContext)
+        adapter = ControlAdapter(LayoutInflater.from(applicationContext), callback)
+        recyclerView.adapter = adapter
+        recyclerView.layoutManager = LinearLayoutManager(applicationContext)
+
+        if (app != null) {
+            setTitle("Controls for $app")
+        } else {
+            setTitle("Controls")
+        }
+        setContentView(recyclerView)
+
+        component?.let {
+            controller.loadForComponent(it) {
+                executor.execute {
+                    adapter.setItems(it)
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt
new file mode 100644
index 0000000..09e0ce9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.controls.management
+
+import android.content.ComponentName
+import com.android.settingslib.widget.CandidateInfo
+import com.android.systemui.statusbar.policy.CallbackController
+
+interface ControlsListingController :
+        CallbackController<ControlsListingController.ControlsListingCallback> {
+
+    fun getCurrentServices(): List<CandidateInfo>
+    fun getAppLabel(name: ComponentName): CharSequence? = ""
+
+    interface ControlsListingCallback {
+        fun onServicesUpdated(list: List<CandidateInfo>)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
new file mode 100644
index 0000000..9372162
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -0,0 +1,135 @@
+/*
+ * 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.controls.management
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.ServiceInfo
+import android.service.controls.ControlsProviderService
+import android.util.Log
+import com.android.internal.annotations.VisibleForTesting
+import com.android.settingslib.applications.DefaultAppInfo
+import com.android.settingslib.applications.ServiceListing
+import com.android.settingslib.widget.CandidateInfo
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.dagger.qualifiers.Background
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * Provides a listing of components to be used as ControlsServiceProvider.
+ *
+ * This controller keeps track of components that satisfy:
+ *
+ * * Has an intent-filter responding to [ControlsProviderService.CONTROLS_ACTION]
+ * * Has the bind permission `android.permission.BIND_CONTROLS`
+ */
+@Singleton
+class ControlsListingControllerImpl @VisibleForTesting constructor(
+    private val context: Context,
+    @Background private val backgroundExecutor: Executor,
+    private val serviceListing: ServiceListing
+) : ControlsListingController {
+
+    @Inject
+    constructor(context: Context, executor: Executor): this(
+            context,
+            executor,
+            ServiceListing.Builder(context)
+                    .setIntentAction(ControlsProviderService.CONTROLS_ACTION)
+                    .setPermission("android.permission.BIND_CONTROLS")
+                    .setNoun("Controls Provider")
+                    .setSetting("controls_providers")
+                    .setTag("controls_providers")
+                    .build()
+    )
+
+    companion object {
+        private const val TAG = "ControlsListingControllerImpl"
+    }
+
+    private var availableServices = emptyList<ServiceInfo>()
+
+    init {
+        serviceListing.addCallback {
+            Log.d(TAG, "ServiceConfig reloaded")
+            availableServices = it.toList()
+
+            backgroundExecutor.execute {
+                callbacks.forEach {
+                    it.onServicesUpdated(getCurrentServices())
+                }
+            }
+        }
+    }
+
+    // All operations in background thread
+    private val callbacks = mutableSetOf<ControlsListingController.ControlsListingCallback>()
+
+    /**
+     * Adds a callback to this controller.
+     *
+     * The callback will be notified after it is added as well as any time that the valid
+     * components change.
+     *
+     * @param listener a callback to be notified
+     */
+    override fun addCallback(listener: ControlsListingController.ControlsListingCallback) {
+        backgroundExecutor.execute {
+            callbacks.add(listener)
+            if (callbacks.size == 1) {
+                serviceListing.setListening(true)
+                serviceListing.reload()
+            } else {
+                listener.onServicesUpdated(getCurrentServices())
+            }
+        }
+    }
+
+    /**
+     * Removes a callback from this controller.
+     *
+     * @param listener the callback to be removed.
+     */
+    override fun removeCallback(listener: ControlsListingController.ControlsListingCallback) {
+        backgroundExecutor.execute {
+            callbacks.remove(listener)
+            if (callbacks.size == 0) {
+                serviceListing.setListening(false)
+            }
+        }
+    }
+
+    /**
+     * @return a list of components that satisfy the requirements to be a
+     *         [ControlsProviderService]
+     */
+    override fun getCurrentServices(): List<CandidateInfo> =
+            availableServices.map { ControlsServiceInfo(context, it) }
+
+    /**
+     * Get the localized label for the component.
+     *
+     * @param name the name of the component
+     * @return a label as returned by [CandidateInfo.loadLabel] or `null`.
+     */
+    override fun getAppLabel(name: ComponentName): CharSequence? {
+        return getCurrentServices().firstOrNull { (it as? DefaultAppInfo)?.componentName == name }
+                ?.loadLabel()
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
new file mode 100644
index 0000000..69af516
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.controls.management
+
+import android.content.ComponentName
+import android.content.Intent
+import android.os.Bundle
+import android.view.LayoutInflater
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.util.LifecycleActivity
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Activity to select an application to favorite the [Control] provided by them.
+ */
+class ControlsProviderSelectorActivity @Inject constructor(
+    @Main private val executor: Executor,
+    private val listingController: ControlsListingController
+) : LifecycleActivity() {
+
+    companion object {
+        private const val TAG = "ControlsProviderSelectorActivity"
+    }
+
+    private lateinit var recyclerView: RecyclerView
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        recyclerView = RecyclerView(applicationContext)
+        recyclerView.adapter = AppAdapter(executor, lifecycle, listingController,
+                LayoutInflater.from(this), ::launchFavoritingActivity)
+        recyclerView.layoutManager = LinearLayoutManager(applicationContext)
+
+        setContentView(recyclerView)
+    }
+
+    /**
+     * Launch the [ControlsFavoritingActivity] for the specified component.
+     * @param component a component name for a [ControlsProviderService]
+     */
+    fun launchFavoritingActivity(component: ComponentName?) {
+        component?.let {
+            val intent = Intent(applicationContext, ControlsFavoritingActivity::class.java).apply {
+                putExtra(ControlsFavoritingActivity.EXTRA_APP, listingController.getAppLabel(it))
+                putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, it)
+                flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
+            }
+            startActivity(intent)
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
new file mode 100644
index 0000000..0270c2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.controls.ui
+
+import android.content.ComponentName
+import android.service.controls.Control
+import android.service.controls.actions.ControlAction
+
+interface ControlsUiController {
+    fun onRefreshState(componentName: ComponentName, controls: List<Control>)
+    fun onActionResponse(
+        componentName: ComponentName,
+        controlId: String,
+        @ControlAction.ResponseResult response: Int
+    )
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
new file mode 100644
index 0000000..0ace126
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.ui
+
+import android.content.ComponentName
+import android.service.controls.Control
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class ControlsUiControllerImpl @Inject constructor() : ControlsUiController {
+
+    override fun onRefreshState(componentName: ComponentName, controls: List<Control>) {
+        TODO("not implemented")
+    }
+
+    override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) {
+        TODO("not implemented")
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index df79310..91f032d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -19,7 +19,9 @@
 import android.app.Activity;
 
 import com.android.systemui.ForegroundServicesDialog;
+import com.android.systemui.bubbles.BubbleOverflowActivity;
 import com.android.systemui.keyguard.WorkLockActivity;
+import com.android.systemui.screenrecord.ScreenRecordDialog;
 import com.android.systemui.settings.BrightnessDialog;
 import com.android.systemui.tuner.TunerActivity;
 
@@ -29,7 +31,7 @@
 import dagger.multibindings.IntoMap;
 
 /**
- * Services and Activities that are injectable should go here.
+ * Activities that are injectable should go here.
  */
 @Module
 public abstract class DefaultActivityBinder {
@@ -56,4 +58,16 @@
     @IntoMap
     @ClassKey(BrightnessDialog.class)
     public abstract Activity bindBrightnessDialog(BrightnessDialog activity);
+
+    /** Inject into ScreenRecordDialog */
+    @Binds
+    @IntoMap
+    @ClassKey(ScreenRecordDialog.class)
+    public abstract Activity bindScreenRecordDialog(ScreenRecordDialog activity);
+
+    /** Inject into BubbleOverflowActivity. */
+    @Binds
+    @IntoMap
+    @ClassKey(BubbleOverflowActivity.class)
+    public abstract Activity bindBubbleOverflowActivity(BubbleOverflowActivity activity);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
index f790d99..f006acf 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
@@ -22,6 +22,7 @@
 import com.android.systemui.SystemUIService;
 import com.android.systemui.doze.DozeService;
 import com.android.systemui.keyguard.KeyguardService;
+import com.android.systemui.screenrecord.RecordingService;
 import com.android.systemui.screenshot.TakeScreenshotService;
 
 import dagger.Binds;
@@ -63,4 +64,10 @@
     @IntoMap
     @ClassKey(TakeScreenshotService.class)
     public abstract Service bindTakeScreenshotService(TakeScreenshotService service);
+
+    /** Inject into RecordingService */
+    @Binds
+    @IntoMap
+    @ClassKey(RecordingService.class)
+    public abstract Service bindRecordingService(RecordingService service);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
index 6744d74..2877ed0 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
@@ -20,10 +20,14 @@
 import com.android.systemui.appops.AppOpsController;
 import com.android.systemui.appops.AppOpsControllerImpl;
 import com.android.systemui.classifier.FalsingManagerProxy;
+import com.android.systemui.controls.dagger.ControlsModule;
 import com.android.systemui.doze.DozeHost;
+import com.android.systemui.globalactions.GlobalActionsComponent;
+import com.android.systemui.globalactions.GlobalActionsImpl;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.GlobalActions;
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.PowerNotificationWarnings;
@@ -82,7 +86,7 @@
 /**
  * Maps interfaces to implementations for use with Dagger.
  */
-@Module
+@Module(includes = {ControlsModule.class})
 public abstract class DependencyBinder {
 
     /**
@@ -99,6 +103,17 @@
     /**
      */
     @Binds
+    public abstract GlobalActions provideGlobalActions(GlobalActionsImpl controllerImpl);
+
+    /**
+     */
+    @Binds
+    public abstract GlobalActions.GlobalActionsManager provideGlobalActionsManager(
+            GlobalActionsComponent controllerImpl);
+
+    /**
+     */
+    @Binds
     public abstract LocationController provideLocationController(
             LocationControllerImpl controllerImpl);
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
index 73b6584..3aa14a3 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
@@ -26,17 +26,24 @@
 import android.app.NotificationManager;
 import android.app.WallpaperManager;
 import android.app.admin.DevicePolicyManager;
+import android.app.trust.TrustManager;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.IPackageManager;
 import android.content.res.Resources;
 import android.hardware.SensorPrivacyManager;
+import android.media.AudioManager;
+import android.net.ConnectivityManager;
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.Vibrator;
 import android.service.dreams.DreamService;
 import android.service.dreams.IDreamManager;
+import android.telecom.TelecomManager;
+import android.telephony.TelephonyManager;
 import android.view.IWindowManager;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
@@ -46,6 +53,7 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.shared.system.PackageManagerWrapper;
 
@@ -65,6 +73,12 @@
         return context.getSystemService(AccessibilityManager.class);
     }
 
+    @Provides
+    @Singleton
+    static ActivityManager provideActivityManager(Context context) {
+        return context.getSystemService(ActivityManager.class);
+    }
+
     @Singleton
     @Provides
     static AlarmManager provideAlarmManager(Context context) {
@@ -73,6 +87,30 @@
 
     @Provides
     @Singleton
+    static AudioManager provideAudioManager(Context context) {
+        return context.getSystemService(AudioManager.class);
+    }
+
+    @Provides
+    @Singleton
+    static ConnectivityManager provideConnectivityManagager(Context context) {
+        return context.getSystemService(ConnectivityManager.class);
+    }
+
+    @Provides
+    @Singleton
+    static ContentResolver provideContentResolver(Context context) {
+        return context.getContentResolver();
+    }
+
+    @Provides
+    @DisplayId
+    static int provideDisplayId(Context context) {
+        return context.getDisplayId();
+    }
+
+    @Provides
+    @Singleton
     static DevicePolicyManager provideDevicePolicyManager(Context context) {
         return context.getSystemService(DevicePolicyManager.class);
     }
@@ -171,6 +209,31 @@
 
     @Provides
     @Singleton
+    static TelecomManager provideTelecomManager(Context context) {
+        return context.getSystemService(TelecomManager.class);
+    }
+
+    @Provides
+    @Singleton
+    static TelephonyManager provideTelephonyManager(Context context) {
+        return context.getSystemService(TelephonyManager.class);
+    }
+
+    @Provides
+    @Singleton
+    static TrustManager provideTrustManager(Context context) {
+        return context.getSystemService(TrustManager.class);
+    }
+
+    @Provides
+    @Singleton
+    @Nullable
+    static Vibrator provideVibrator(Context context) {
+        return context.getSystemService(Vibrator.class);
+    }
+
+    @Provides
+    @Singleton
     static UserManager provideUserManager(Context context) {
         return context.getSystemService(UserManager.class);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 442313d..36a8450 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -19,23 +19,29 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.res.Resources;
 
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.BootCompleteCache;
 import com.android.systemui.BootCompleteCacheImpl;
 import com.android.systemui.DumpController;
 import com.android.systemui.assist.AssistModule;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.stackdivider.Divider;
+import com.android.systemui.statusbar.BlurUtils;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
+import com.android.systemui.statusbar.StatusBarWindowBlurController;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
 import com.android.systemui.statusbar.notification.people.PeopleHubModule;
+import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
 import com.android.systemui.statusbar.phone.KeyguardLiftController;
 import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.phone.StatusBarComponent;
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.util.concurrency.ConcurrencyModule;
 import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -56,7 +62,7 @@
 @Module(includes = {AssistModule.class,
                     ConcurrencyModule.class,
                     PeopleHubModule.class},
-        subcomponents = {StatusBarComponent.class})
+        subcomponents = {StatusBarComponent.class, NotificationRowComponent.class})
 public abstract class SystemUIModule {
 
     @Binds
@@ -84,6 +90,21 @@
 
     @Singleton
     @Provides
+    @Nullable
+    static StatusBarWindowBlurController providesBlurController(BlurUtils blurUtils,
+            @Main Resources resources, SysuiStatusBarStateController statusBarStateController,
+            DumpController dumpController) {
+        return blurUtils.supportsBlursOnWindows() ? new StatusBarWindowBlurController(resources,
+                statusBarStateController, blurUtils, dumpController) : null;
+    }
+
+    /** */
+    @Binds
+    public abstract NotificationRowBinder bindNotificationRowBinder(
+            NotificationRowBinderImpl notificationRowBinder);
+
+    @Singleton
+    @Provides
     static SysUiState provideSysUiState() {
         return new SysUiState();
     }
@@ -106,9 +127,4 @@
     @Singleton
     @Binds
     abstract SystemClock bindSystemClock(SystemClockImpl systemClock);
-
-    @Singleton
-    @Binds
-    abstract NotifListBuilder bindNotifListBuilder(NotifListBuilderImpl impl);
-
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java
new file mode 100644
index 0000000..155a6d2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java
@@ -0,0 +1,30 @@
+/*
+ * 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.dagger.qualifiers;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface DisplayId {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index c9faf69..4e88726 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -91,7 +91,7 @@
         if (mDebuggable) {
             IntentFilter filter = new IntentFilter();
             filter.addAction(ACTION_AOD_BRIGHTNESS);
-            mBroadcastDispatcher.registerReceiver(this, filter, handler, UserHandle.ALL);
+            mBroadcastDispatcher.registerReceiverWithHandler(this, filter, handler, UserHandle.ALL);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
index 19b6f82..e949007a 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
@@ -19,7 +19,6 @@
 import android.os.ServiceManager;
 
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.Dependency;
 import com.android.systemui.SystemUI;
 import com.android.systemui.plugins.GlobalActions;
 import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
@@ -29,6 +28,7 @@
 import com.android.systemui.statusbar.policy.ExtensionController.Extension;
 
 import javax.inject.Inject;
+import javax.inject.Provider;
 import javax.inject.Singleton;
 
 /**
@@ -38,23 +38,29 @@
 public class GlobalActionsComponent extends SystemUI implements Callbacks, GlobalActionsManager {
 
     private final CommandQueue mCommandQueue;
+    private final ExtensionController mExtensionController;
+    private final Provider<GlobalActions> mGlobalActionsProvider;
     private GlobalActions mPlugin;
     private Extension<GlobalActions> mExtension;
     private IStatusBarService mBarService;
 
     @Inject
-    public GlobalActionsComponent(Context context, CommandQueue commandQueue) {
+    public GlobalActionsComponent(Context context, CommandQueue commandQueue,
+            ExtensionController extensionController,
+            Provider<GlobalActions> globalActionsProvider) {
         super(context);
         mCommandQueue = commandQueue;
+        mExtensionController = extensionController;
+        mGlobalActionsProvider = globalActionsProvider;
     }
 
     @Override
     public void start() {
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
-        mExtension = Dependency.get(ExtensionController.class).newExtension(GlobalActions.class)
+        mExtension = mExtensionController.newExtension(GlobalActions.class)
                 .withPlugin(GlobalActions.class)
-                .withDefault(() -> new GlobalActionsImpl(mContext, mCommandQueue))
+                .withDefault(mGlobalActionsProvider::get)
                 .withCallback(this::onExtensionCallback)
                 .build();
         mPlugin = mExtension.get();
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 3ccad64..83f6d45 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -21,8 +21,10 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.Dialog;
+import android.app.IActivityManager;
 import android.app.KeyguardManager;
 import android.app.PendingIntent;
 import android.app.StatusBarManager;
@@ -30,11 +32,13 @@
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.TrustManager;
 import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.UserInfo;
+import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
@@ -44,20 +48,17 @@
 import android.os.IBinder;
 import android.os.Message;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.Vibrator;
 import android.provider.Settings;
-import android.service.dreams.DreamService;
 import android.service.dreams.IDreamManager;
 import android.sysprop.TelephonyProperties;
 import android.telecom.TelecomManager;
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
-import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.FeatureFlagUtils;
 import android.util.Log;
@@ -92,11 +93,13 @@
 import com.android.systemui.MultiListLayout.MultiListAdapter;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
 import com.android.systemui.plugins.GlobalActionsPanelPlugin;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.BlurUtils;
 import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.statusbar.phone.StatusBarWindowController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.EmergencyDialerConstants;
@@ -106,6 +109,8 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import javax.inject.Inject;
+
 /**
  * Helper to show the global actions dialog.  Each item is an {@link Action} that
  * may show depending on whether the keyguard is showing, and whether the device
@@ -146,6 +151,14 @@
     private final LockPatternUtils mLockPatternUtils;
     private final KeyguardManager mKeyguardManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
+    private final ContentResolver mContentResolver;
+    private final Resources mResources;
+    private final UserManager mUserManager;
+    private final TrustManager mTrustManager;
+    private final IActivityManager mIActivityManager;
+    private final TelecomManager mTelecomManager;
+    private final MetricsLogger mMetricsLogger;
+    private final BlurUtils mBlurUtils;
 
     private ArrayList<Action> mItems;
     private ActionsDialog mDialog;
@@ -161,29 +174,52 @@
     private boolean mIsWaitingForEcmExit = false;
     private boolean mHasTelephony;
     private boolean mHasVibrator;
-    private boolean mHasLogoutButton;
-    private boolean mHasLockdownButton;
     private final boolean mShowSilentToggle;
     private final EmergencyAffordanceManager mEmergencyAffordanceManager;
     private final ScreenshotHelper mScreenshotHelper;
     private final ScreenRecordHelper mScreenRecordHelper;
     private final ActivityStarter mActivityStarter;
+    private final SysuiColorExtractor mSysuiColorExtractor;
+    private final IStatusBarService mStatusBarService;
+    private final NotificationShadeWindowController mNotificationShadeWindowController;
     private GlobalActionsPanelPlugin mPanelPlugin;
 
     /**
      * @param context everything needs a context :(
      */
-    public GlobalActionsDialog(Context context, GlobalActionsManager windowManagerFuncs) {
+    @Inject
+    public GlobalActionsDialog(Context context, GlobalActionsManager windowManagerFuncs,
+            AudioManager audioManager, IDreamManager iDreamManager,
+            DevicePolicyManager devicePolicyManager, LockPatternUtils lockPatternUtils,
+            KeyguardManager keyguardManager, BroadcastDispatcher broadcastDispatcher,
+            ConnectivityManager connectivityManager, TelephonyManager telephonyManager,
+            ContentResolver contentResolver, @Nullable Vibrator vibrator, @Main Resources resources,
+            ConfigurationController configurationController, ActivityStarter activityStarter,
+            KeyguardStateController keyguardStateController, UserManager userManager,
+            TrustManager trustManager, IActivityManager iActivityManager,
+            TelecomManager telecomManager, MetricsLogger metricsLogger,
+            BlurUtils blurUtils, SysuiColorExtractor colorExtractor,
+            IStatusBarService statusBarService,
+            NotificationShadeWindowController notificationShadeWindowController) {
         mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
         mWindowManagerFuncs = windowManagerFuncs;
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        mDreamManager = IDreamManager.Stub.asInterface(
-                ServiceManager.getService(DreamService.DREAM_SERVICE));
-        mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
-                Context.DEVICE_POLICY_SERVICE);
-        mLockPatternUtils = new LockPatternUtils(mContext);
-        mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
-        mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class);
+        mAudioManager = audioManager;
+        mDreamManager = iDreamManager;
+        mDevicePolicyManager = devicePolicyManager;
+        mLockPatternUtils = lockPatternUtils;
+        mKeyguardManager = keyguardManager;
+        mBroadcastDispatcher = broadcastDispatcher;
+        mContentResolver = contentResolver;
+        mResources = resources;
+        mUserManager = userManager;
+        mTrustManager = trustManager;
+        mIActivityManager = iActivityManager;
+        mTelecomManager = telecomManager;
+        mMetricsLogger = metricsLogger;
+        mBlurUtils = blurUtils;
+        mSysuiColorExtractor = colorExtractor;
+        mStatusBarService = statusBarService;
+        mNotificationShadeWindowController = notificationShadeWindowController;
 
         // receive broadcasts
         IntentFilter filter = new IntentFilter();
@@ -192,32 +228,25 @@
         filter.addAction(TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
         mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter);
 
-        ConnectivityManager cm = (ConnectivityManager)
-                context.getSystemService(Context.CONNECTIVITY_SERVICE);
-        mHasTelephony = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+        mHasTelephony = connectivityManager.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
 
         // get notified of phone state changes
-        TelephonyManager telephonyManager =
-                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
         telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
-        mContext.getContentResolver().registerContentObserver(
+        contentResolver.registerContentObserver(
                 Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
                 mAirplaneModeObserver);
-        Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
         mHasVibrator = vibrator != null && vibrator.hasVibrator();
 
-        mShowSilentToggle = SHOW_SILENT_TOGGLE && !mContext.getResources().getBoolean(
+        mShowSilentToggle = SHOW_SILENT_TOGGLE && !resources.getBoolean(
                 R.bool.config_useFixedVolume);
 
         mEmergencyAffordanceManager = new EmergencyAffordanceManager(context);
         mScreenshotHelper = new ScreenshotHelper(context);
         mScreenRecordHelper = new ScreenRecordHelper(context);
 
-        Dependency.get(ConfigurationController.class).addCallback(this);
+        configurationController.addCallback(this);
 
-        mActivityStarter = Dependency.get(ActivityStarter.class);
-        KeyguardStateController keyguardStateController =
-                Dependency.get(KeyguardStateController.class);
+        mActivityStarter = activityStarter;
         keyguardStateController.addCallback(new KeyguardStateController.Callback() {
             @Override
             public void onUnlockedChanged() {
@@ -343,12 +372,9 @@
         onAirplaneModeChanged();
 
         mItems = new ArrayList<Action>();
-        String[] defaultActions = mContext.getResources().getStringArray(
-                R.array.config_globalActionsList);
+        String[] defaultActions = mResources.getStringArray(R.array.config_globalActionsList);
 
         ArraySet<String> addedKeys = new ArraySet<String>();
-        mHasLogoutButton = false;
-        mHasLockdownButton = false;
         for (int i = 0; i < defaultActions.length; i++) {
             String actionKey = defaultActions[i];
             if (addedKeys.contains(actionKey)) {
@@ -360,7 +386,7 @@
             } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {
                 mItems.add(mAirplaneModeOn);
             } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {
-                if (Settings.Global.getInt(mContext.getContentResolver(),
+                if (Settings.Global.getInt(mContentResolver,
                         Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) {
                     mItems.add(new BugReportAction());
                 }
@@ -375,11 +401,10 @@
             } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) {
                 mItems.add(getSettingsAction());
             } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {
-                if (Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                if (Settings.Secure.getIntForUser(mContentResolver,
                             Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0, getCurrentUser().id) != 0
                         && shouldDisplayLockdown()) {
                     mItems.add(getLockdownAction());
-                    mHasLockdownButton = true;
                 }
             } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) {
                 mItems.add(getVoiceAssistAction());
@@ -393,7 +418,6 @@
                 if (mDevicePolicyManager.isLogoutEnabled()
                         && getCurrentUser().id != UserHandle.USER_SYSTEM) {
                     mItems.add(new LogoutAction());
-                    mHasLogoutButton = true;
                 }
             } else if (GLOBAL_ACTION_KEY_EMERGENCY.equals(actionKey)) {
                 if (!mEmergencyAffordanceManager.needsEmergencyAffordance()) {
@@ -431,7 +455,9 @@
                                 mKeyguardManager.isDeviceLocked())
                         : null;
 
-        ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, panelViewController);
+        ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, panelViewController,
+                mBlurUtils, mSysuiColorExtractor, mStatusBarService,
+                mNotificationShadeWindowController, isControlsEnabled(mContext));
         dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
         dialog.setKeyguardShowing(mKeyguardShowing);
 
@@ -474,8 +500,7 @@
 
         @Override
         public boolean onLongPress() {
-            UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-            if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
+            if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
                 mWindowManagerFuncs.reboot(true);
                 return true;
             }
@@ -506,7 +531,7 @@
 
         @Override
         public boolean shouldBeSeparated() {
-            return shouldUseSeparatedView();
+            return !isControlsEnabled(mContext);
         }
 
         @Override
@@ -560,9 +585,8 @@
 
         @Override
         public void onPress() {
-            MetricsLogger.action(mContext, MetricsEvent.ACTION_EMERGENCY_DIALER_FROM_POWER_MENU);
-            Intent intent = mContext.getSystemService(TelecomManager.class)
-                    .createLaunchEmergencyDialerIntent(null /* number */);
+            mMetricsLogger.action(MetricsEvent.ACTION_EMERGENCY_DIALER_FROM_POWER_MENU);
+            Intent intent = mTelecomManager.createLaunchEmergencyDialerIntent(null /* number */);
             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                     | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
                     | Intent.FLAG_ACTIVITY_CLEAR_TOP);
@@ -579,8 +603,7 @@
 
         @Override
         public boolean onLongPress() {
-            UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-            if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
+            if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
                 mWindowManagerFuncs.reboot(true);
                 return true;
             }
@@ -618,8 +641,7 @@
                 @Override
                 public void run() {
                     mScreenshotHelper.takeScreenshot(1, true, true, mHandler, null);
-                    MetricsLogger.action(mContext,
-                            MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
+                    mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
                 }
             }, 500);
         }
@@ -666,11 +688,11 @@
                 public void run() {
                     try {
                         // Take an "interactive" bugreport.
-                        MetricsLogger.action(mContext,
+                        mMetricsLogger.action(
                                 MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE);
-                        if (!ActivityManager.getService().launchBugReportHandlerApp()) {
+                        if (!mIActivityManager.launchBugReportHandlerApp()) {
                             Log.w(TAG, "Bugreport handler could not be launched");
-                            ActivityManager.getService().requestInteractiveBugReport();
+                            mIActivityManager.requestInteractiveBugReport();
                         }
                     } catch (RemoteException e) {
                     }
@@ -687,8 +709,8 @@
             }
             try {
                 // Take a "full" bugreport.
-                MetricsLogger.action(mContext, MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL);
-                ActivityManager.getService().requestFullBugReport();
+                mMetricsLogger.action(MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL);
+                mIActivityManager.requestFullBugReport();
             } catch (RemoteException e) {
             }
             return false;
@@ -726,8 +748,8 @@
             mHandler.postDelayed(() -> {
                 try {
                     int currentUserId = getCurrentUser().id;
-                    ActivityManager.getService().switchUser(UserHandle.USER_SYSTEM);
-                    ActivityManager.getService().stopUser(currentUserId, true /*force*/, null);
+                    mIActivityManager.switchUser(UserHandle.USER_SYSTEM);
+                    mIActivityManager.stopUser(currentUserId, true /*force*/, null);
                 } catch (RemoteException re) {
                     Log.e(TAG, "Couldn't logout user " + re);
                 }
@@ -834,20 +856,18 @@
     }
 
     private void lockProfiles() {
-        final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-        final TrustManager tm = (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE);
         final int currentUserId = getCurrentUser().id;
-        final int[] profileIds = um.getEnabledProfileIds(currentUserId);
+        final int[] profileIds = mUserManager.getEnabledProfileIds(currentUserId);
         for (final int id : profileIds) {
             if (id != currentUserId) {
-                tm.setDeviceLockedForUser(id, true);
+                mTrustManager.setDeviceLockedForUser(id, true);
             }
         }
     }
 
     private UserInfo getCurrentUser() {
         try {
-            return ActivityManager.getService().getCurrentUser();
+            return mIActivityManager.getCurrentUser();
         } catch (RemoteException re) {
             return null;
         }
@@ -859,9 +879,8 @@
     }
 
     private void addUsersToMenu(ArrayList<Action> items) {
-        UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-        if (um.isUserSwitcherEnabled()) {
-            List<UserInfo> users = um.getUsers();
+        if (mUserManager.isUserSwitcherEnabled()) {
+            List<UserInfo> users = mUserManager.getUsers();
             UserInfo currentUser = getCurrentUser();
             for (final UserInfo user : users) {
                 if (user.supportsSwitchToByUser()) {
@@ -875,7 +894,7 @@
                                     + (isCurrentUser ? " \u2714" : "")) {
                         public void onPress() {
                             try {
-                                ActivityManager.getService().switchUser(user.id);
+                                mIActivityManager.switchUser(user.id);
                             } catch (RemoteException re) {
                                 Log.e(TAG, "Couldn't switch user " + re);
                             }
@@ -932,7 +951,7 @@
 
     /** {@inheritDoc} */
     public void onShow(DialogInterface dialog) {
-        MetricsLogger.visible(mContext, MetricsEvent.POWER_MENU);
+        mMetricsLogger.visible(MetricsEvent.POWER_MENU);
     }
 
     /**
@@ -1148,6 +1167,9 @@
         }
 
         protected int getActionLayoutId(Context context) {
+            if (isControlsEnabled(context)) {
+                return com.android.systemui.R.layout.global_actions_grid_item_v2;
+            }
             return com.android.systemui.R.layout.global_actions_grid_item;
         }
 
@@ -1160,13 +1182,6 @@
             TextView messageView = (TextView) v.findViewById(R.id.message);
             messageView.setSelected(true); // necessary for marquee to work
 
-            TextView statusView = (TextView) v.findViewById(R.id.status);
-            final String status = getStatus();
-            if (!TextUtils.isEmpty(status)) {
-                statusView.setText(status);
-            } else {
-                statusView.setVisibility(View.GONE);
-            }
             if (mIcon != null) {
                 icon.setImageDrawable(mIcon);
                 icon.setScaleType(ScaleType.CENTER_CROP);
@@ -1251,32 +1266,26 @@
                 LayoutInflater inflater) {
             willCreate();
 
-            View v = inflater.inflate(R
-                    .layout.global_actions_item, parent, false);
+            View v = inflater.inflate(com.android.systemui.R
+                    .layout.global_actions_grid_item, parent, false);
 
             ImageView icon = (ImageView) v.findViewById(R.id.icon);
             TextView messageView = (TextView) v.findViewById(R.id.message);
-            TextView statusView = (TextView) v.findViewById(R.id.status);
             final boolean enabled = isEnabled();
+            boolean on = ((mState == State.On) || (mState == State.TurningOn));
 
             if (messageView != null) {
-                messageView.setText(mMessageResId);
+                messageView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId);
                 messageView.setEnabled(enabled);
                 messageView.setSelected(true); // necessary for marquee to work
             }
 
-            boolean on = ((mState == State.On) || (mState == State.TurningOn));
             if (icon != null) {
                 icon.setImageDrawable(context.getDrawable(
                         (on ? mEnabledIconResId : mDisabledIconResid)));
                 icon.setEnabled(enabled);
             }
 
-            if (statusView != null) {
-                statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId);
-                statusView.setVisibility(View.VISIBLE);
-                statusView.setEnabled(enabled);
-            }
             v.setEnabled(enabled);
 
             return v;
@@ -1422,8 +1431,8 @@
             } else if (TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) {
                 // Airplane mode can be changed after ECM exits if airplane toggle button
                 // is pressed during ECM mode
-                if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) &&
-                        mIsWaitingForEcmExit) {
+                if (!(intent.getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false))
+                        && mIsWaitingForEcmExit) {
                     mIsWaitingForEcmExit = false;
                     changeAirplaneModeSystemSetting(true);
                 }
@@ -1492,7 +1501,7 @@
         if (mHasTelephony) return;
 
         boolean airplaneModeOn = Settings.Global.getInt(
-                mContext.getContentResolver(),
+                mContentResolver,
                 Settings.Global.AIRPLANE_MODE_ON,
                 0) == 1;
         mAirplaneState = airplaneModeOn ? ToggleAction.State.On : ToggleAction.State.Off;
@@ -1504,7 +1513,7 @@
      */
     private void changeAirplaneModeSystemSetting(boolean on) {
         Settings.Global.putInt(
-                mContext.getContentResolver(),
+                mContentResolver,
                 Settings.Global.AIRPLANE_MODE_ON,
                 on ? 1 : 0);
         Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
@@ -1532,16 +1541,23 @@
         private float mScrimAlpha;
         private ResetOrientationData mResetOrientationData;
         private boolean mHadTopUi;
-        private final StatusBarWindowController mStatusBarWindowController;
+        private final NotificationShadeWindowController mNotificationShadeWindowController;
+        private final BlurUtils mBlurUtils;
+        private final boolean mControlsEnabled;
 
         ActionsDialog(Context context, MyAdapter adapter,
-                GlobalActionsPanelPlugin.PanelViewController plugin) {
+                GlobalActionsPanelPlugin.PanelViewController plugin, BlurUtils blurUtils,
+                SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService,
+                NotificationShadeWindowController notificationShadeWindowController,
+                boolean controlsEnabled) {
             super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions);
             mContext = context;
             mAdapter = adapter;
-            mColorExtractor = Dependency.get(SysuiColorExtractor.class);
-            mStatusBarService = Dependency.get(IStatusBarService.class);
-            mStatusBarWindowController = Dependency.get(StatusBarWindowController.class);
+            mBlurUtils = blurUtils;
+            mColorExtractor = sysuiColorExtractor;
+            mStatusBarService = statusBarService;
+            mNotificationShadeWindowController = notificationShadeWindowController;
+            mControlsEnabled = controlsEnabled;
 
             // Window initialization
             Window window = getWindow();
@@ -1561,7 +1577,7 @@
                     | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                     | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
             window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
-            window.setFitWindowInsetsTypes(0 /* types */);
+            window.getAttributes().setFitInsetsTypes(0 /* types */);
             setTitle(R.string.global_actions);
 
             mPanelController = plugin;
@@ -1658,6 +1674,10 @@
         }
 
         private int getGlobalActionsLayoutId(Context context) {
+            if (mControlsEnabled) {
+                return com.android.systemui.R.layout.global_actions_grid_v2;
+            }
+
             int rotation = RotationUtils.getRotation(context);
             boolean useGridLayout = isForceGridEnabled(context)
                     || (shouldUsePanel() && rotation == RotationUtils.ROTATION_NONE);
@@ -1718,8 +1738,8 @@
         public void show() {
             super.show();
             mShowing = true;
-            mHadTopUi = mStatusBarWindowController.getForceHasTopUi();
-            mStatusBarWindowController.setForceHasTopUi(true);
+            mHadTopUi = mNotificationShadeWindowController.getForceHasTopUi();
+            mNotificationShadeWindowController.setForceHasTopUi(true);
             mBackgroundDrawable.setAlpha(0);
             mGlobalActionsLayout.setTranslationX(mGlobalActionsLayout.getAnimationOffsetX());
             mGlobalActionsLayout.setTranslationY(mGlobalActionsLayout.getAnimationOffsetY());
@@ -1731,9 +1751,11 @@
                     .setDuration(300)
                     .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
                     .setUpdateListener(animation -> {
-                        int alpha = (int) ((Float) animation.getAnimatedValue()
-                                * mScrimAlpha * 255);
+                        float animatedValue = animation.getAnimatedFraction();
+                        int alpha = (int) (animatedValue * mScrimAlpha * 255);
                         mBackgroundDrawable.setAlpha(alpha);
+                        mBlurUtils.applyBlur(mGlobalActionsLayout.getViewRootImpl(),
+                                mBlurUtils.radiusForRatio(animatedValue));
                     })
                     .start();
         }
@@ -1755,9 +1777,11 @@
                     .withEndAction(this::completeDismiss)
                     .setInterpolator(new LogAccelerateInterpolator())
                     .setUpdateListener(animation -> {
-                        int alpha = (int) ((1f - (Float) animation.getAnimatedValue())
-                                * mScrimAlpha * 255);
+                        float animatedValue = 1f - animation.getAnimatedFraction();
+                        int alpha = (int) (animatedValue * mScrimAlpha * 255);
                         mBackgroundDrawable.setAlpha(alpha);
+                        mBlurUtils.applyBlur(mGlobalActionsLayout.getViewRootImpl(),
+                                mBlurUtils.radiusForRatio(animatedValue));
                     })
                     .start();
             dismissPanel();
@@ -1772,7 +1796,7 @@
         }
 
         private void completeDismiss() {
-            mStatusBarWindowController.setForceHasTopUi(mHadTopUi);
+            mNotificationShadeWindowController.setForceHasTopUi(mHadTopUi);
             super.dismiss();
         }
 
@@ -1861,4 +1885,9 @@
     private static boolean shouldUseSeparatedView() {
         return true;
     }
+
+    private static boolean isControlsEnabled(Context context) {
+        return Settings.Secure.getInt(
+                context.getContentResolver(), "systemui.controls_available", 0) == 1;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java
new file mode 100644
index 0000000..6749f1d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java
@@ -0,0 +1,186 @@
+/*
+ * 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.globalactions;
+
+import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE;
+import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE;
+import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Single row implementation of the button layout created by the global actions dialog.
+ */
+public class GlobalActionsFlatLayout extends GlobalActionsLayout {
+    public GlobalActionsFlatLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        mBackgroundsSet = true;
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        // backgrounds set only once, the first time onMeasure is called after inflation
+        // if (getListView() != null && !mBackgroundsSet) {
+        //     setBackgrounds();
+        //     mBackgroundsSet = true;
+        // }
+    }
+
+    @VisibleForTesting
+    protected void setupListView() {
+        ListGridLayout listView = getListView();
+        listView.setExpectedCount(Math.min(2, mAdapter.countListItems()));
+        listView.setReverseSublists(shouldReverseSublists());
+        listView.setReverseItems(shouldReverseListItems());
+        listView.setSwapRowsAndColumns(shouldSwapRowsAndColumns());
+    }
+
+    @Override
+    public void onUpdateList() {
+        setupListView();
+        super.onUpdateList();
+        updateSeparatedItemSize();
+    }
+
+    /**
+     * If the separated view contains only one item, expand the bounds of that item to take up the
+     * entire view, so that the whole thing is touch-able.
+     */
+    @VisibleForTesting
+    protected void updateSeparatedItemSize() {
+        ViewGroup separated = getSeparatedView();
+        if (separated.getChildCount() == 0) {
+            return;
+        }
+        View firstChild = separated.getChildAt(0);
+        ViewGroup.LayoutParams childParams = firstChild.getLayoutParams();
+
+        if (separated.getChildCount() == 1) {
+            childParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
+            childParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
+        } else {
+            childParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+            childParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+        }
+    }
+
+    @Override
+    protected ListGridLayout getListView() {
+        return (ListGridLayout) super.getListView();
+    }
+
+    @Override
+    protected void removeAllListViews() {
+        ListGridLayout list = getListView();
+        if (list != null) {
+            list.removeAllItems();
+        }
+    }
+
+    @Override
+    protected void addToListView(View v, boolean reverse) {
+        ListGridLayout list = getListView();
+        if (list != null) {
+            list.addItem(v);
+        }
+    }
+
+    @Override
+    public void removeAllItems() {
+        ViewGroup separatedList = getSeparatedView();
+        ListGridLayout list = getListView();
+        if (separatedList != null) {
+            separatedList.removeAllViews();
+        }
+        if (list != null) {
+            list.removeAllItems();
+        }
+    }
+
+    /**
+     * Determines whether the ListGridLayout should fill sublists in the reverse order.
+     * Used to account for sublist ordering changing between landscape and seascape views.
+     */
+    @VisibleForTesting
+    protected boolean shouldReverseSublists() {
+        if (getCurrentRotation() == ROTATION_SEASCAPE) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Determines whether the ListGridLayout should fill rows first instead of columns.
+     * Used to account for vertical/horizontal changes due to landscape or seascape rotations.
+     */
+    @VisibleForTesting
+    protected boolean shouldSwapRowsAndColumns() {
+        if (getCurrentRotation() == ROTATION_NONE) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    protected boolean shouldReverseListItems() {
+        int rotation = getCurrentRotation();
+        boolean reverse = false; // should we add items to parents in the reverse order?
+        if (rotation == ROTATION_NONE
+                || rotation == ROTATION_SEASCAPE) {
+            reverse = !reverse; // if we're in portrait or seascape, reverse items
+        }
+        if (getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
+            reverse = !reverse; // if we're in an RTL language, reverse items (again)
+        }
+        return reverse;
+    }
+
+    @VisibleForTesting
+    protected float getAnimationDistance() {
+        int rows = getListView().getRowCount();
+        float gridItemSize = getContext().getResources().getDimension(
+                com.android.systemui.R.dimen.global_actions_grid_item_height);
+        return rows * gridItemSize / 2;
+    }
+
+    @Override
+    public float getAnimationOffsetX() {
+        switch (getCurrentRotation()) {
+            case ROTATION_LANDSCAPE:
+                return getAnimationDistance();
+            case ROTATION_SEASCAPE:
+                return -getAnimationDistance();
+            default: // Portrait
+                return 0;
+        }
+    }
+
+    @Override
+    public float getAnimationOffsetY() {
+        if (getCurrentRotation() == ROTATION_NONE) {
+            return getAnimationDistance();
+        }
+        return 0;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
index d385123..dd1856a 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -45,24 +45,32 @@
 import com.android.systemui.statusbar.policy.ExtensionController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
+import javax.inject.Inject;
+
+import dagger.Lazy;
+
 public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks,
         PluginListener<GlobalActionsPanelPlugin> {
 
     private static final float SHUTDOWN_SCRIM_ALPHA = 0.95f;
 
     private final Context mContext;
+    private final Lazy<GlobalActionsDialog> mGlobalActionsDialogLazy;
     private final KeyguardStateController mKeyguardStateController;
     private final DeviceProvisionedController mDeviceProvisionedController;
     private final ExtensionController.Extension<GlobalActionsPanelPlugin> mPanelExtension;
     private GlobalActionsPanelPlugin mPlugin;
     private final CommandQueue mCommandQueue;
-    private GlobalActionsDialog mGlobalActions;
+    private GlobalActionsDialog mGlobalActionsDialog;
     private boolean mDisabled;
     private final PluginManager mPluginManager;
     private final String mPluginPackageName;
 
-    public GlobalActionsImpl(Context context, CommandQueue commandQueue) {
+    @Inject
+    public GlobalActionsImpl(Context context, CommandQueue commandQueue,
+            Lazy<GlobalActionsDialog> globalActionsDialogLazy) {
         mContext = context;
+        mGlobalActionsDialogLazy = globalActionsDialogLazy;
         mKeyguardStateController = Dependency.get(KeyguardStateController.class);
         mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
         mPluginManager = Dependency.get(PluginManager.class);
@@ -83,19 +91,17 @@
         mCommandQueue.removeCallback(this);
         mPluginManager.removePluginListener(this);
         if (mPlugin != null) mPlugin.onDestroy();
-        if (mGlobalActions != null) {
-            mGlobalActions.destroy();
-            mGlobalActions = null;
+        if (mGlobalActionsDialog != null) {
+            mGlobalActionsDialog.destroy();
+            mGlobalActionsDialog = null;
         }
     }
 
     @Override
     public void showGlobalActions(GlobalActionsManager manager) {
         if (mDisabled) return;
-        if (mGlobalActions == null) {
-            mGlobalActions = new GlobalActionsDialog(mContext, manager);
-        }
-        mGlobalActions.showDialog(mKeyguardStateController.isShowing(),
+        mGlobalActionsDialog = mGlobalActionsDialogLazy.get();
+        mGlobalActionsDialog.showDialog(mKeyguardStateController.isShowing(),
                 mDeviceProvisionedController.isDeviceProvisioned(),
                 mPlugin != null ? mPlugin : mPanelExtension.get());
         Dependency.get(KeyguardUpdateMonitor.class).requestFaceAuth();
@@ -120,7 +126,7 @@
         window.getAttributes().height = ViewGroup.LayoutParams.MATCH_PARENT;
         window.getAttributes().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
-        window.setFitWindowInsetsTypes(0 /* types */);
+        window.getAttributes().setFitInsetsTypes(0 /* types */);
         window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
         window.addFlags(
                 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
@@ -189,8 +195,8 @@
         final boolean disabled = (state2 & DISABLE2_GLOBAL_ACTIONS) != 0;
         if (displayId != mContext.getDisplayId() || disabled == mDisabled) return;
         mDisabled = disabled;
-        if (disabled && mGlobalActions != null) {
-            mGlobalActions.dismissDialog();
+        if (disabled && mGlobalActionsDialog != null) {
+            mGlobalActionsDialog.dismissDialog();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 8f2c3ed..2fc7a9c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -87,10 +87,10 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationPanelView;
+import com.android.systemui.statusbar.phone.NotificationPanelViewController;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.StatusBarWindowController;
 import com.android.systemui.util.InjectionInflationController;
 
 import java.io.FileDescriptor;
@@ -212,7 +212,7 @@
     private AlarmManager mAlarmManager;
     private AudioManager mAudioManager;
     private StatusBarManager mStatusBarManager;
-    private final StatusBarWindowController mStatusBarWindowController;
+    private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final Executor mUiBgExecutor;
 
     private boolean mSystemReady;
@@ -691,7 +691,7 @@
             FalsingManager falsingManager,
             LockPatternUtils lockPatternUtils,
             BroadcastDispatcher broadcastDispatcher,
-            StatusBarWindowController statusBarWindowController,
+            NotificationShadeWindowController notificationShadeWindowController,
             Lazy<StatusBarKeyguardViewManager> statusBarKeyguardViewManagerLazy,
             DismissCallbackRegistry dismissCallbackRegistry,
             @UiBackground Executor uiBgExecutor) {
@@ -699,7 +699,7 @@
         mFalsingManager = falsingManager;
         mLockPatternUtils = lockPatternUtils;
         mBroadcastDispatcher = broadcastDispatcher;
-        mStatusBarWindowController = statusBarWindowController;
+        mNotificationShadeWindowController = notificationShadeWindowController;
         mStatusBarKeyguardViewManagerLazy = statusBarKeyguardViewManagerLazy;
         mDismissCallbackRegistry = dismissCallbackRegistry;
         mUiBgExecutor = uiBgExecutor;
@@ -1819,7 +1819,7 @@
             adjustStatusBarLocked();
             userActivity();
             mUpdateMonitor.setKeyguardGoingAway(false);
-            mStatusBarWindowController.setKeyguardGoingAway(false);
+            mNotificationShadeWindowController.setKeyguardGoingAway(false);
             mShowKeyguardWakeLock.release();
         }
         mKeyguardDisplayManager.show();
@@ -1852,7 +1852,7 @@
             }
 
             mUpdateMonitor.setKeyguardGoingAway(true);
-            mStatusBarWindowController.setKeyguardGoingAway(true);
+            mNotificationShadeWindowController.setKeyguardGoingAway(true);
 
             // Don't actually hide the Keyguard at the moment, wait for window
             // manager until it tells us it's safe to do so with
@@ -2105,7 +2105,7 @@
     }
 
     public StatusBarKeyguardViewManager registerStatusBar(StatusBar statusBar,
-            ViewGroup container, NotificationPanelView panelView,
+            ViewGroup container, NotificationPanelViewController panelView,
             BiometricUnlockController biometricUnlockController, ViewGroup lockIconContainer,
             View notificationContainer, KeyguardBypassController bypassController) {
         mStatusBarKeyguardViewManagerLazy.get().registerStatusBar(statusBar, container, panelView,
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
index 750cc60..b725811 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
@@ -109,7 +109,7 @@
             lp.setTitle("pip-dismiss-overlay");
             lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
             lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
-            lp.setFitWindowInsetsTypes(0 /* types */);
+            lp.setFitInsetsTypes(0 /* types */);
             mWindowManager.addView(mDismissView, lp);
         }
         mDismissView.animate().cancel();
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
index 6afa0bf..eba2e78 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -19,17 +19,11 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 
-import static com.android.systemui.Interpolators.FAST_OUT_LINEAR_IN;
-import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN;
-import static com.android.systemui.Interpolators.LINEAR_OUT_SLOW_IN;
-
 import android.animation.AnimationHandler;
 import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.RectEvaluator;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.animation.TimeAnimator;
+import android.annotation.Nullable;
 import android.app.ActivityManager.StackInfo;
 import android.app.IActivityManager;
 import android.app.IActivityTaskManager;
@@ -42,13 +36,16 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.util.Log;
-import android.view.animation.Interpolator;
+
+import androidx.dynamicanimation.animation.SpringForce;
 
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.internal.os.SomeArgs;
 import com.android.systemui.pip.PipSnapAlgorithm;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 import com.android.systemui.statusbar.FlingAnimationUtils;
+import com.android.systemui.util.animation.FloatProperties;
+import com.android.systemui.util.animation.PhysicsAnimator;
 
 import java.io.PrintWriter;
 
@@ -60,18 +57,14 @@
     private static final String TAG = "PipMotionHelper";
     private static final boolean DEBUG = false;
 
-    private static final RectEvaluator RECT_EVALUATOR = new RectEvaluator(new Rect());
-
-    private static final int DEFAULT_MOVE_STACK_DURATION = 225;
-    private static final int SNAP_STACK_DURATION = 225;
-    private static final int DRAG_TO_TARGET_DISMISS_STACK_DURATION = 375;
-    private static final int DRAG_TO_DISMISS_STACK_DURATION = 175;
     private static final int SHRINK_STACK_FROM_MENU_DURATION = 250;
     private static final int EXPAND_STACK_TO_MENU_DURATION = 250;
     private static final int EXPAND_STACK_TO_FULLSCREEN_DURATION = 300;
-    private static final int MINIMIZE_STACK_MAX_DURATION = 200;
     private static final int SHIFT_DURATION = 300;
 
+    /** Friction to use for PIP when it moves via physics fling animations. */
+    private static final float DEFAULT_FRICTION = 2f;
+
     // The fraction of the stack width that the user has to drag offscreen to minimize the PiP
     private static final float MINIMIZE_OFFSCREEN_FRACTION = 0.3f;
     // The fraction of the stack height that the user has to drag offscreen to dismiss the PiP
@@ -89,12 +82,39 @@
     private PipMenuActivityController mMenuController;
     private PipSnapAlgorithm mSnapAlgorithm;
     private FlingAnimationUtils mFlingAnimationUtils;
-    private AnimationHandler mAnimationHandler;
 
-    private final Rect mBounds = new Rect();
     private final Rect mStableInsets = new Rect();
 
-    private ValueAnimator mBoundsAnimator = null;
+    /** PIP's current bounds on the screen. */
+    private final Rect mBounds = new Rect();
+
+    /**
+     * Bounds that are animated using the physics animator. PIP is moved to these bounds whenever
+     * the {@link #mVsyncTimeAnimator} ticks.
+     */
+    private final Rect mAnimatedBounds = new Rect();
+
+    /**
+     * PhysicsAnimator instance for animating {@link #mAnimatedBounds} using physics animations.
+     */
+    private PhysicsAnimator<Rect> mAnimatedBoundsPhysicsAnimator = PhysicsAnimator.getInstance(
+            mAnimatedBounds);
+
+    /**
+     * Time animator whose frame timing comes from the SurfaceFlinger vsync frame provider. At each
+     * frame, PIP is moved to {@link #mAnimatedBounds}, which are animated asynchronously using
+     * physics animations.
+     */
+    private TimeAnimator mVsyncTimeAnimator;
+
+    /** FlingConfig instances provided to PhysicsAnimator for fling gestures. */
+    private PhysicsAnimator.FlingConfig mFlingConfigX;
+    private PhysicsAnimator.FlingConfig mFlingConfigY;
+
+    /** SpringConfig to use for fling-then-spring animations. */
+    private final PhysicsAnimator.SpringConfig mSpringConfig =
+            new PhysicsAnimator.SpringConfig(
+                    SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
 
     public PipMotionHelper(Context context, IActivityManager activityManager,
             IActivityTaskManager activityTaskManager, PipMenuActivityController menuController,
@@ -106,9 +126,39 @@
         mMenuController = menuController;
         mSnapAlgorithm = snapAlgorithm;
         mFlingAnimationUtils = flingAnimationUtils;
-        mAnimationHandler = new AnimationHandler();
-        mAnimationHandler.setProvider(new SfVsyncFrameCallbackProvider());
+        final AnimationHandler vsyncFrameCallbackProvider = new AnimationHandler();
+        vsyncFrameCallbackProvider.setProvider(new SfVsyncFrameCallbackProvider());
+
         onConfigurationChanged();
+
+        // Construct a time animator that uses the vsync frame provider. Physics animations can't
+        // use custom frame providers, since they rely on constant time between frames to run the
+        // physics simulations. To work around this, we physically-animate a second set of bounds,
+        // and apply those animating bounds to the PIP in-sync via this TimeAnimator.
+        mVsyncTimeAnimator = new TimeAnimator() {
+            @Override
+            public AnimationHandler getAnimationHandler() {
+                return vsyncFrameCallbackProvider;
+            }
+        };
+
+        // When the time animator ticks, move PIP to the animated bounds.
+        mVsyncTimeAnimator.setTimeListener(
+                (animation, totalTime, deltaTime) ->
+                        resizePipUnchecked(mAnimatedBounds));
+
+        // Add a listener for cancel/end events that moves PIP to the final animated bounds.
+        mVsyncTimeAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                resizePipUnchecked(mAnimatedBounds);
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                resizePipUnchecked(mAnimatedBounds);
+            }
+        });
     }
 
     /**
@@ -241,88 +291,85 @@
     }
 
     /**
-     * Flings the minimized PiP to the closest minimized snap target.
-     */
-    Rect flingToMinimizedState(float velocityY, Rect movementBounds, Point dragStartPosition) {
-        cancelAnimations();
-        // We currently only allow flinging the minimized stack up and down, so just lock the
-        // movement bounds to the current stack bounds horizontally
-        movementBounds = new Rect(mBounds.left, movementBounds.top, mBounds.left,
-                movementBounds.bottom);
-        Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds,
-                0 /* velocityX */, velocityY, dragStartPosition);
-        if (!mBounds.equals(toBounds)) {
-            mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, 0, FAST_OUT_SLOW_IN);
-            mFlingAnimationUtils.apply(mBoundsAnimator, 0,
-                    distanceBetweenRectOffsets(mBounds, toBounds),
-                    velocityY);
-            mBoundsAnimator.start();
-        }
-        return toBounds;
-    }
-
-    /**
      * Animates the PiP to the minimized state, slightly offscreen.
      */
-    Rect animateToClosestMinimizedState(Rect movementBounds,
-            AnimatorUpdateListener updateListener) {
-        cancelAnimations();
-        Rect toBounds = getClosestMinimizedBounds(mBounds, movementBounds);
-        if (!mBounds.equals(toBounds)) {
-            mBoundsAnimator = createAnimationToBounds(mBounds, toBounds,
-                    MINIMIZE_STACK_MAX_DURATION, LINEAR_OUT_SLOW_IN);
-            if (updateListener != null) {
-                mBoundsAnimator.addUpdateListener(updateListener);
-            }
-            mBoundsAnimator.start();
+    void animateToClosestMinimizedState(Rect movementBounds, @Nullable Runnable updateAction) {
+        final Rect toBounds = getClosestMinimizedBounds(mBounds, movementBounds);
+
+        prepareForBoundsAnimation(movementBounds);
+
+        mAnimatedBoundsPhysicsAnimator
+                .spring(FloatProperties.RECT_X, toBounds.left, mSpringConfig)
+                .spring(FloatProperties.RECT_Y, toBounds.top, mSpringConfig);
+
+        if (updateAction != null) {
+            mAnimatedBoundsPhysicsAnimator.addUpdateListener(
+                    (target, values) -> updateAction.run());
         }
-        return toBounds;
+
+        startBoundsAnimation();
     }
 
     /**
      * Flings the PiP to the closest snap target.
      */
-    Rect flingToSnapTarget(float velocity, float velocityX, float velocityY, Rect movementBounds,
-            AnimatorUpdateListener updateListener, AnimatorListener listener,
-            Point startPosition) {
-        cancelAnimations();
-        Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds,
-                velocityX, velocityY, startPosition);
-        if (!mBounds.equals(toBounds)) {
-            mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, 0, FAST_OUT_SLOW_IN);
-            mFlingAnimationUtils.apply(mBoundsAnimator, 0,
-                    distanceBetweenRectOffsets(mBounds, toBounds),
-                    velocity);
-            if (updateListener != null) {
-                mBoundsAnimator.addUpdateListener(updateListener);
-            }
-            if (listener != null){
-                mBoundsAnimator.addListener(listener);
-            }
-            mBoundsAnimator.start();
-        }
-        return toBounds;
+    void flingToSnapTarget(
+            float velocityX, float velocityY, Rect movementBounds, Runnable updateAction,
+            @Nullable Runnable endAction) {
+        prepareForBoundsAnimation(movementBounds);
+
+        mAnimatedBoundsPhysicsAnimator
+                .flingThenSpring(
+                        FloatProperties.RECT_X, velocityX, mFlingConfigX, mSpringConfig,
+                        true /* flingMustReachMinOrMax */)
+                .flingThenSpring(
+                        FloatProperties.RECT_Y, velocityY, mFlingConfigY, mSpringConfig)
+                .addUpdateListener((target, values) -> updateAction.run())
+                .withEndActions(endAction);
+
+        startBoundsAnimation();
     }
 
     /**
      * Animates the PiP to the closest snap target.
      */
-    Rect animateToClosestSnapTarget(Rect movementBounds, AnimatorUpdateListener updateListener,
-            AnimatorListener listener) {
-        cancelAnimations();
-        Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds);
-        if (!mBounds.equals(toBounds)) {
-            mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, SNAP_STACK_DURATION,
-                    FAST_OUT_SLOW_IN);
-            if (updateListener != null) {
-                mBoundsAnimator.addUpdateListener(updateListener);
-            }
-            if (listener != null){
-                mBoundsAnimator.addListener(listener);
-            }
-            mBoundsAnimator.start();
+    void animateToClosestSnapTarget(Rect movementBounds) {
+        prepareForBoundsAnimation(movementBounds);
+
+        final Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds);
+        mAnimatedBoundsPhysicsAnimator
+                .spring(FloatProperties.RECT_X, toBounds.left, mSpringConfig)
+                .spring(FloatProperties.RECT_Y, toBounds.top, mSpringConfig);
+
+        startBoundsAnimation();
+    }
+
+    /**
+     * Animates the dismissal of the PiP off the edge of the screen.
+     */
+    void animateDismiss(float velocityX, float velocityY, @Nullable Runnable updateAction) {
+        final float velocity = PointF.length(velocityX, velocityY);
+        final boolean isFling = velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond();
+        final Point dismissEndPoint = getDismissEndPoint(mBounds, velocityX, velocityY, isFling);
+
+        // Set the animated bounds to start at the current bounds. We don't need to rebuild the
+        // fling configs here via prepareForBoundsAnimation, since animateDismiss isn't provided
+        // with new movement bounds.
+        mAnimatedBounds.set(mBounds);
+
+        // Animate to the dismiss end point, and then dismiss PIP.
+        mAnimatedBoundsPhysicsAnimator
+                .spring(FloatProperties.RECT_X, dismissEndPoint.x, velocityX, mSpringConfig)
+                .spring(FloatProperties.RECT_Y, dismissEndPoint.y, velocityY, mSpringConfig)
+                .withEndActions(this::dismissPip);
+
+        // If we were provided with an update action, run it whenever there's an update.
+        if (updateAction != null) {
+            mAnimatedBoundsPhysicsAnimator.addUpdateListener(
+                    (target, values) -> updateAction.run());
         }
-        return toBounds;
+
+        startBoundsAnimation();
     }
 
     /**
@@ -378,64 +425,42 @@
     }
 
     /**
-     * Animates the dismissal of the PiP off the edge of the screen.
-     */
-    Rect animateDismiss(Rect pipBounds, float velocityX, float velocityY,
-            AnimatorUpdateListener listener) {
-        cancelAnimations();
-        final float velocity = PointF.length(velocityX, velocityY);
-        final boolean isFling = velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond();
-        Point p = getDismissEndPoint(pipBounds, velocityX, velocityY, isFling);
-        Rect toBounds = new Rect(pipBounds);
-        toBounds.offsetTo(p.x, p.y);
-        mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, DRAG_TO_DISMISS_STACK_DURATION,
-                FAST_OUT_LINEAR_IN);
-        mBoundsAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                dismissPip();
-            }
-        });
-        if (isFling) {
-            mFlingAnimationUtils.apply(mBoundsAnimator, 0,
-                    distanceBetweenRectOffsets(mBounds, toBounds), velocity);
-        }
-        if (listener != null) {
-            mBoundsAnimator.addUpdateListener(listener);
-        }
-        mBoundsAnimator.start();
-        return toBounds;
-    }
-
-    /**
      * Cancels all existing animations.
      */
-    void cancelAnimations() {
-        if (mBoundsAnimator != null) {
-            mBoundsAnimator.cancel();
-            mBoundsAnimator = null;
-        }
+    private void cancelAnimations() {
+        mAnimatedBoundsPhysicsAnimator.cancel();
+        mVsyncTimeAnimator.cancel();
     }
 
     /**
-     * Creates an animation to move the PiP to give given {@param toBounds}.
+     * Set new fling configs whose min/max values respect the given movement bounds, and set the
+     * animated bounds to PIP's current 'real' bounds.
      */
-    private ValueAnimator createAnimationToBounds(Rect fromBounds, Rect toBounds, int duration,
-            Interpolator interpolator) {
-        ValueAnimator anim = new ValueAnimator() {
-            @Override
-            public AnimationHandler getAnimationHandler() {
-                return mAnimationHandler;
-            }
-        };
-        anim.setObjectValues(fromBounds, toBounds);
-        anim.setEvaluator(RECT_EVALUATOR);
-        anim.setDuration(duration);
-        anim.setInterpolator(interpolator);
-        anim.addUpdateListener((ValueAnimator animation) -> {
-            resizePipUnchecked((Rect) animation.getAnimatedValue());
-        });
-        return anim;
+    private void prepareForBoundsAnimation(Rect movementBounds) {
+        mFlingConfigX = new PhysicsAnimator.FlingConfig(
+                DEFAULT_FRICTION, movementBounds.left, movementBounds.right);
+        mFlingConfigY = new PhysicsAnimator.FlingConfig(
+                DEFAULT_FRICTION, movementBounds.top, movementBounds.bottom);
+
+        mAnimatedBounds.set(mBounds);
+    }
+
+    /**
+     * Starts the physics animator which will update the animated PIP bounds using physics
+     * animations, as well as the TimeAnimator which will apply those bounds to PIP at intervals
+     * synchronized with the SurfaceFlinger vsync frame provider.
+     *
+     * This will also add end actions to the bounds animator that cancel the TimeAnimator and update
+     * the 'real' bounds to equal the final animated bounds.
+     */
+    private void startBoundsAnimation() {
+        cancelAnimations();
+
+        mAnimatedBoundsPhysicsAnimator
+                .withEndActions(
+                        mVsyncTimeAnimator::cancel)
+                .start();
+        mVsyncTimeAnimator.start();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 95e3444..09f1638 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -20,10 +20,6 @@
 import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_FULL;
 import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_NONE;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.app.IActivityManager;
 import android.app.IActivityTaskManager;
 import android.content.ComponentName;
@@ -111,13 +107,6 @@
             }
         }
     };
-    private ValueAnimator.AnimatorUpdateListener mUpdateScrimListener =
-            new AnimatorUpdateListener() {
-                @Override
-                public void onAnimationUpdate(ValueAnimator animation) {
-                    updateDismissFraction();
-                }
-            };
 
     // Behaviour states
     private int mMenuState = MENU_STATE_NONE;
@@ -162,7 +151,7 @@
         @Override
         public void onPipMinimize() {
             setMinimizedStateInternal(true);
-            mMotionHelper.animateToClosestMinimizedState(mMovementBounds, null /* updateListener */);
+            mMotionHelper.animateToClosestMinimizedState(mMovementBounds, null /* updateAction */);
         }
 
         @Override
@@ -655,15 +644,6 @@
                 float lastY = mStartPosition.y + mDelta.y;
                 float left = lastX + lastDelta.x;
                 float top = lastY + lastDelta.y;
-                if (!touchState.allowDraggingOffscreen() || !ENABLE_MINIMIZE) {
-                    left = Math.max(mMovementBounds.left, Math.min(mMovementBounds.right, left));
-                }
-                if (mEnableDimissDragToEdge) {
-                    // Allow pip to move past bottom bounds
-                    top = Math.max(mMovementBounds.top, top);
-                } else {
-                    top = Math.max(mMovementBounds.top, Math.min(mMovementBounds.bottom, top));
-                }
 
                 // Add to the cumulative delta after bounding the position
                 mDelta.x += left - lastX;
@@ -720,8 +700,9 @@
                 if (mMotionHelper.shouldDismissPip() || isFlingToBot) {
                     MetricsLoggerWrapper.logPictureInPictureDismissByDrag(mContext,
                             PipUtils.getTopPinnedActivity(mContext, mActivityManager));
-                    mMotionHelper.animateDismiss(mMotionHelper.getBounds(), vel.x,
-                        vel.y, mUpdateScrimListener);
+                    mMotionHelper.animateDismiss(
+                            vel.x, vel.y,
+                            PipTouchHandler.this::updateDismissFraction /* updateAction */);
                     return true;
                 }
             }
@@ -739,8 +720,9 @@
                         // minimize offset adjusted
                         mMenuController.hideMenu();
                     } else {
-                        mMotionHelper.animateToClosestMinimizedState(mMovementBounds,
-                                mUpdateScrimListener);
+                        mMotionHelper.animateToClosestMinimizedState(
+                                mMovementBounds,
+                                PipTouchHandler.this::updateDismissFraction /* updateAction */);
                     }
                     return true;
                 }
@@ -750,7 +732,7 @@
                     setMinimizedStateInternal(false);
                 }
 
-                AnimatorListenerAdapter postAnimationCallback = null;
+                Runnable endAction = null;
                 if (mMenuState != MENU_STATE_NONE) {
                     // If the menu is still visible, and we aren't minimized, then just poke the
                     // menu so that it will timeout after the user stops touching it
@@ -759,26 +741,20 @@
                 } else {
                     // If the menu is not visible, then we can still be showing the activity for the
                     // dismiss overlay, so just finish it after the animation completes
-                    postAnimationCallback = new AnimatorListenerAdapter() {
-                        @Override
-                        public void onAnimationEnd(Animator animation) {
-                            mMenuController.hideMenu();
-                        }
-                    };
+                    endAction = mMenuController::hideMenu;
                 }
 
                 if (isFling) {
-                    mMotionHelper.flingToSnapTarget(velocity, vel.x, vel.y, mMovementBounds,
-                            mUpdateScrimListener, postAnimationCallback,
-                            mStartPosition);
+                    mMotionHelper.flingToSnapTarget(
+                            vel.x, vel.y, mMovementBounds,
+                            PipTouchHandler.this::updateDismissFraction /* updateAction */,
+                            endAction /* endAction */);
                 } else {
-                    mMotionHelper.animateToClosestSnapTarget(mMovementBounds, mUpdateScrimListener,
-                            postAnimationCallback);
+                    mMotionHelper.animateToClosestSnapTarget(mMovementBounds);
                 }
             } else if (mIsMinimized) {
                 // This was a tap, so no longer minimized
-                mMotionHelper.animateToClosestSnapTarget(mMovementBounds, null /* updateListener */,
-                        null /* animatorListener */);
+                mMotionHelper.animateToClosestSnapTarget(mMovementBounds);
                 setMinimizedStateInternal(false);
             } else if (mTouchState.isDoubleTap()) {
                 // Expand to fullscreen if this is a double tap
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 59ac329..48750fa 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -233,7 +233,7 @@
             filter.addAction(Intent.ACTION_SCREEN_OFF);
             filter.addAction(Intent.ACTION_SCREEN_ON);
             filter.addAction(Intent.ACTION_USER_SWITCHED);
-            mBroadcastDispatcher.registerReceiver(this, filter, mHandler);
+            mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
index f710f7f..f66a1ec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
@@ -68,7 +68,7 @@
     override fun updateResources(): Boolean {
         with(mContext.resources) {
             smallTileSize = getDimensionPixelSize(R.dimen.qs_quick_tile_size)
-            cellMarginHorizontal = getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal)
+            cellMarginHorizontal = getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal) / 2
             cellMarginVertical = getDimensionPixelSize(R.dimen.new_qs_vertical_margin)
         }
         requestLayout()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
index f7e4c79..1077834e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
@@ -124,7 +124,7 @@
                     }
                 }
             });
-            btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_replay));
+            btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play));
             btn.setImageTintList(ColorStateList.valueOf(mForegroundColor));
             btn.setVisibility(View.VISIBLE);
 
@@ -199,8 +199,7 @@
         List<ResolveInfo> info = pm.queryBroadcastReceiversAsUser(it, 0, mContext.getUser());
         if (info != null) {
             for (ResolveInfo inf : info) {
-                if (inf.activityInfo.packageName.equals(notif.contentIntent.getCreatorPackage())) {
-                    Log.d(TAG, "Found receiver for package: " + inf);
+                if (inf.activityInfo.packageName.equals(mController.getPackageName())) {
                     mRecvComponent = inf.getComponentInfo().getComponentName();
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 51e352b..35b8312 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -184,21 +184,10 @@
 
         // Add media carousel
         if (useQsMediaPlayer(context)) {
-            HorizontalScrollView mediaScrollView = new HorizontalScrollView(mContext);
-            mediaScrollView.setHorizontalScrollBarEnabled(false);
-            int playerHeight = (int) getResources().getDimension(R.dimen.qs_media_height);
-            int padding = (int) getResources().getDimension(R.dimen.qs_media_padding);
-            LayoutParams lpView = new LayoutParams(LayoutParams.MATCH_PARENT, playerHeight);
-            lpView.setMarginStart(padding);
-            lpView.setMarginEnd(padding);
-            addView(mediaScrollView, lpView);
-
-            LayoutParams lpCarousel = new LayoutParams(LayoutParams.MATCH_PARENT,
-                    LayoutParams.WRAP_CONTENT);
-            mMediaCarousel = new LinearLayout(mContext);
-            mMediaCarousel.setOrientation(LinearLayout.HORIZONTAL);
-            mediaScrollView.addView(mMediaCarousel, lpCarousel);
-            mediaScrollView.setVisibility(View.GONE);
+            HorizontalScrollView mediaScrollView = (HorizontalScrollView) LayoutInflater.from(
+                    mContext).inflate(R.layout.media_carousel, this, false);
+            mMediaCarousel = mediaScrollView.findViewById(R.id.media_carousel);
+            addView(mediaScrollView);
         } else {
             mMediaCarousel = null;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 86ed274..f71150f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -366,7 +366,7 @@
 
     protected static List<String> loadTileSpecs(Context context, String tileList) {
         final Resources res = context.getResources();
-        final String defaultTileList = res.getString(R.string.quick_settings_tiles_default);
+
         if (TextUtils.isEmpty(tileList)) {
             tileList = res.getString(R.string.quick_settings_tiles);
             if (DEBUG) Log.d(TAG, "Loaded tile specs from config: " + tileList);
@@ -380,11 +380,7 @@
             if (tile.isEmpty()) continue;
             if (tile.equals("default")) {
                 if (!addedDefault) {
-                    tiles.addAll(Arrays.asList(defaultTileList.split(",")));
-                    if (Build.IS_DEBUGGABLE
-                            && GarbageMonitor.MemoryTile.ADD_TO_DEFAULT_ON_DEBUGGABLE_BUILDS) {
-                        tiles.add(GarbageMonitor.MemoryTile.TILE_SPEC);
-                    }
+                    tiles.addAll(getDefaultSpecs(context));
                     addedDefault = true;
                 }
             } else {
@@ -394,6 +390,28 @@
         return tiles;
     }
 
+    /**
+     * Returns the default QS tiles for the context.
+     * @param context the context to obtain the resources from
+     * @return a list of specs of the default tiles
+     */
+    public static List<String> getDefaultSpecs(Context context) {
+        final ArrayList<String> tiles = new ArrayList<String>();
+
+        final Resources res = context.getResources();
+        final String defaultTileList = res.getString(R.string.quick_settings_tiles_default);
+        final String extraTileList = res.getString(
+                com.android.internal.R.string.config_defaultExtraQuickSettingsTiles);
+
+        tiles.addAll(Arrays.asList(defaultTileList.split(",")));
+        tiles.addAll(Arrays.asList(extraTileList.split(",")));
+        if (Build.IS_DEBUGGABLE
+                && GarbageMonitor.MemoryTile.ADD_TO_DEFAULT_ON_DEBUGGABLE_BUILDS) {
+            tiles.add(GarbageMonitor.MemoryTile.TILE_SPEC);
+        }
+        return tiles;
+    }
+
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("QSTileHost:");
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
index d40e250..cec1cb2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
@@ -106,7 +106,7 @@
                     }
                 }
             });
-            btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_replay));
+            btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play));
             btn.setImageTintList(ColorStateList.valueOf(mForegroundColor));
             btn.setVisibility(View.VISIBLE);
         }
@@ -136,14 +136,25 @@
      * @param actionsContainer a LinearLayout containing the media action buttons
      * @param actionsToShow indices of which actions to display in the mini player
      *                      (max 3: Notification.MediaStyle.MAX_MEDIA_BUTTONS_IN_COMPACT)
+     * @param contentIntent Intent to send when user taps on the view
      */
     public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor, int bgColor,
-            View actionsContainer, int[] actionsToShow) {
-        Log.d(TAG, "Setting media session: " + token);
+            View actionsContainer, int[] actionsToShow, PendingIntent contentIntent) {
         mToken = token;
         mForegroundColor = iconColor;
         mBackgroundColor = bgColor;
-        mController = new MediaController(mContext, token);
+
+        String oldPackage = "";
+        if (mController != null) {
+            oldPackage = mController.getPackageName();
+        }
+        MediaController controller = new MediaController(mContext, token);
+        boolean samePlayer = mToken.equals(token) && oldPackage.equals(controller.getPackageName());
+        if (mController != null && !samePlayer && !isPlaying(controller)) {
+            // Only update if this is a different session and currently playing
+            return;
+        }
+        mController = controller;
         MediaMetadata mMediaMetadata = mController.getMetadata();
 
         // Try to find a receiver for the media button that matches this app
@@ -153,7 +164,6 @@
         if (info != null) {
             for (ResolveInfo inf : info) {
                 if (inf.activityInfo.packageName.equals(mController.getPackageName())) {
-                    Log.d(TAG, "Found receiver for package: " + inf);
                     mRecvComponent = inf.getComponentInfo().getComponentName();
                 }
             }
@@ -165,6 +175,16 @@
             return;
         }
 
+        // Action
+        mMediaNotifView.setOnClickListener(v -> {
+            try {
+                contentIntent.send();
+                mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+            } catch (PendingIntent.CanceledException e) {
+                Log.e(TAG, "Pending intent was canceled: " + e.getMessage());
+            }
+        });
+
         // Album art
         addAlbumArtBackground(mMediaMetadata, mBackgroundColor);
 
@@ -237,12 +257,12 @@
      * Check whether the media controlled by this player is currently playing
      * @return whether it is playing, or false if no controller information
      */
-    public boolean isPlaying() {
-        if (mController == null) {
+    public boolean isPlaying(MediaController controller) {
+        if (controller == null) {
             return false;
         }
 
-        PlaybackState state = mController.getPlaybackState();
+        PlaybackState state = controller.getPlaybackState();
         if (state == null) {
             return false;
         }
@@ -261,12 +281,11 @@
     private void addAlbumArtBackground(MediaMetadata metadata, int bgColor) {
         Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
         float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius);
-        if (albumArt != null) {
-            Rect bounds = new Rect();
-            mMediaNotifView.getBoundsOnScreen(bounds);
-            int width = bounds.width();
-            int height = bounds.height();
-
+        Rect bounds = new Rect();
+        mMediaNotifView.getBoundsOnScreen(bounds);
+        int width = bounds.width();
+        int height = bounds.height();
+        if (albumArt != null && width > 0 && height > 0) {
             Bitmap original = albumArt.copy(Bitmap.Config.ARGB_8888, true);
             Bitmap scaled = scaleBitmap(original, width, height);
             Canvas canvas = new Canvas(scaled);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index db52e7d..b05d4fd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -85,20 +85,19 @@
             mHorizontalLinearLayout.setClipChildren(false);
             mHorizontalLinearLayout.setClipToPadding(false);
 
-            LayoutParams lp = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
-
             mTileLayout = new DoubleLineTileLayout(context);
             mMediaTileLayout = mTileLayout;
             mRegularTileLayout = new HeaderTileLayout(context);
+            LayoutParams lp = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
             lp.setMarginEnd(10);
             lp.setMarginStart(0);
             mHorizontalLinearLayout.addView((View) mTileLayout, lp);
 
             mMediaPlayer = new QuickQSMediaPlayer(mContext, mHorizontalLinearLayout);
-
-            lp.setMarginEnd(0);
-            lp.setMarginStart(10);
-            mHorizontalLinearLayout.addView(mMediaPlayer.getView(), lp);
+            LayoutParams lp2 = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
+            lp2.setMarginEnd(0);
+            lp2.setMarginStart(25);
+            mHorizontalLinearLayout.addView(mMediaPlayer.getView(), lp2);
 
             sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index 1de6355..fb89ed2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -266,12 +266,7 @@
     }
 
     private void reset() {
-        ArrayList<String> tiles = new ArrayList<>();
-        String defTiles = mContext.getString(R.string.quick_settings_tiles_default);
-        for (String tile : defTiles.split(",")) {
-            tiles.add(tile);
-        }
-        mTileAdapter.resetTileSpecs(mHost, tiles);
+        mTileAdapter.resetTileSpecs(mHost, QSTileHost.getDefaultSpecs(mContext));
     }
 
     private void setTileSpecs() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index f30c181..79a7df2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -111,7 +111,9 @@
 
         final ArrayList<QSTile> tilesToAdd = new ArrayList<>();
         for (String spec : possibleTiles) {
-            // Only add current and stock tiles that can be created from QSFactoryImpl
+            // Only add current and stock tiles that can be created from QSFactoryImpl.
+            // Do not include CustomTile. Those will be created by `addPackageTiles`.
+            if (spec.startsWith(CustomTile.PREFIX)) continue;
             final QSTile tile = host.createTile(spec);
             if (tile == null) {
                 continue;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index bbff117..ad79cad 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -322,7 +322,7 @@
         filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
         try {
             mUserReceiverRegistered.set(true);
-            mBroadcastDispatcher.registerReceiver(this, filter, mHandler, mUser);
+            mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler, mUser);
         } catch (Exception ex) {
             mUserReceiverRegistered.set(false);
             Log.e(TAG, "Could not register unlock receiver", ex);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index f06c849..2b53727 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -42,6 +42,7 @@
 import com.android.systemui.qs.tiles.NfcTile;
 import com.android.systemui.qs.tiles.NightDisplayTile;
 import com.android.systemui.qs.tiles.RotationLockTile;
+import com.android.systemui.qs.tiles.ScreenRecordTile;
 import com.android.systemui.qs.tiles.UiModeNightTile;
 import com.android.systemui.qs.tiles.UserTile;
 import com.android.systemui.qs.tiles.WifiTile;
@@ -77,6 +78,7 @@
     private final Provider<NfcTile> mNfcTileProvider;
     private final Provider<GarbageMonitor.MemoryTile> mMemoryTileProvider;
     private final Provider<UiModeNightTile> mUiModeNightTileProvider;
+    private final Provider<ScreenRecordTile> mScreenRecordTileProvider;
 
     private QSTileHost mHost;
 
@@ -100,7 +102,8 @@
             Provider<NightDisplayTile> nightDisplayTileProvider,
             Provider<NfcTile> nfcTileProvider,
             Provider<GarbageMonitor.MemoryTile> memoryTileProvider,
-            Provider<UiModeNightTile> uiModeNightTileProvider) {
+            Provider<UiModeNightTile> uiModeNightTileProvider,
+            Provider<ScreenRecordTile> screenRecordTileProvider) {
         mWifiTileProvider = wifiTileProvider;
         mBluetoothTileProvider = bluetoothTileProvider;
         mControlsTileProvider = controlsTileProvider;
@@ -121,6 +124,7 @@
         mNfcTileProvider = nfcTileProvider;
         mMemoryTileProvider = memoryTileProvider;
         mUiModeNightTileProvider = uiModeNightTileProvider;
+        mScreenRecordTileProvider = screenRecordTileProvider;
     }
 
     public void setHost(QSTileHost host) {
@@ -179,6 +183,8 @@
                 return mNfcTileProvider.get();
             case "dark":
                 return mUiModeNightTileProvider.get();
+            case "screenrecord":
+                return mScreenRecordTileProvider.get();
         }
 
         // Custom tiles
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
new file mode 100644
index 0000000..596c3b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -0,0 +1,132 @@
+/*
+ * 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.tiles;
+
+import android.content.Intent;
+import android.service.quicksettings.Tile;
+import android.util.Log;
+
+import com.android.systemui.R;
+import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.screenrecord.RecordingController;
+
+import javax.inject.Inject;
+
+/**
+ * Quick settings tile for screen recording
+ */
+public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> {
+    private static final String TAG = "ScreenRecordTile";
+    private RecordingController mController;
+    private long mMillisUntilFinished = 0;
+
+    @Inject
+    public ScreenRecordTile(QSHost host, RecordingController controller) {
+        super(host);
+        mController = controller;
+    }
+
+    @Override
+    public BooleanState newTileState() {
+        return new BooleanState();
+    }
+
+    @Override
+    protected void handleClick() {
+        if (mController.isStarting()) {
+            cancelCountdown();
+        } else if (mController.isRecording()) {
+            stopRecording();
+        } else {
+            startCountdown();
+        }
+        refreshState();
+    }
+
+    /**
+     * Refresh tile state
+     * @param millisUntilFinished Time until countdown completes, or 0 if not counting down
+     */
+    public void refreshState(long millisUntilFinished) {
+        mMillisUntilFinished = millisUntilFinished;
+        refreshState();
+    }
+
+    @Override
+    protected void handleUpdateState(BooleanState state, Object arg) {
+        boolean isStarting = mController.isStarting();
+        boolean isRecording = mController.isRecording();
+
+        state.label = mContext.getString(R.string.quick_settings_screen_record_label);
+        state.value = isRecording || isStarting;
+        state.state = (isRecording || isStarting) ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
+        state.handlesLongClick = false;
+
+        if (isRecording) {
+            state.icon = ResourceIcon.get(R.drawable.ic_qs_screenrecord);
+            state.secondaryLabel = mContext.getString(R.string.quick_settings_screen_record_stop);
+        } else if (isStarting) {
+            // round, since the timer isn't exact
+            int countdown = (int) Math.floorDiv(mMillisUntilFinished + 500, 1000);
+            // TODO update icon
+            state.icon = ResourceIcon.get(R.drawable.ic_qs_screenrecord);
+            state.secondaryLabel = String.format("%d...", countdown);
+        } else {
+            // TODO update icon
+            state.icon = ResourceIcon.get(R.drawable.ic_qs_screenrecord);
+            state.secondaryLabel = mContext.getString(R.string.quick_settings_screen_record_start);
+        }
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return 0;
+    }
+
+    @Override
+    public Intent getLongClickIntent() {
+        return null;
+    }
+
+    @Override
+    protected void handleSetListening(boolean listening) {
+    }
+
+    @Override
+    public CharSequence getTileLabel() {
+        return mContext.getString(R.string.quick_settings_screen_record_label);
+    }
+
+    private void startCountdown() {
+        Log.d(TAG, "Starting countdown");
+        // Close QS, otherwise the permission dialog appears beneath it
+        getHost().collapsePanels();
+        mController.launchRecordPrompt(this);
+    }
+
+    private void cancelCountdown() {
+        Log.d(TAG, "Cancelling countdown");
+        mController.cancelCountdown();
+    }
+
+    private void stopRecording() {
+        Log.d(TAG, "Stopping recording from tile");
+        mController.stopRecording();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
index 7bc2a0d..8f1769b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
@@ -33,6 +33,8 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import javax.inject.Inject;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
 
 /**
  * Quick Settings tile for: Night Mode / Dark Theme / Dark Mode.
@@ -43,7 +45,7 @@
 public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implements
         ConfigurationController.ConfigurationListener,
         BatteryController.BatteryStateChangeCallback {
-
+    public static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("hh:mm a");
     private final Icon mIcon = ResourceIcon.get(
             com.android.internal.R.drawable.ic_qs_ui_mode_night);
     private final UiModeManager mUiModeManager;
@@ -88,17 +90,28 @@
     protected void handleUpdateState(BooleanState state, Object arg) {
         int uiMode = mUiModeManager.getNightMode();
         boolean powerSave = mBatteryController.isPowerSave();
-        boolean isAuto = uiMode == UiModeManager.MODE_NIGHT_AUTO;
         boolean nightMode = (mContext.getResources().getConfiguration().uiMode
                         & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
 
         if (powerSave) {
             state.secondaryLabel = mContext.getResources().getString(
                     R.string.quick_settings_dark_mode_secondary_label_battery_saver);
-        } else if (isAuto) {
+        } else if (uiMode == UiModeManager.MODE_NIGHT_AUTO) {
             state.secondaryLabel = mContext.getResources().getString(nightMode
                     ? R.string.quick_settings_dark_mode_secondary_label_until_sunrise
                     : R.string.quick_settings_dark_mode_secondary_label_on_at_sunset);
+        } else if (uiMode == UiModeManager.MODE_NIGHT_CUSTOM) {
+            final boolean use24HourFormat = android.text.format.DateFormat.is24HourFormat(mContext);
+            final LocalTime time;
+            if (nightMode) {
+                time = mUiModeManager.getCustomNightModeEnd();
+            } else {
+                time = mUiModeManager.getCustomNightModeStart();
+            }
+            state.secondaryLabel = mContext.getResources().getString(nightMode
+                    ? R.string.quick_settings_dark_mode_secondary_label_until
+                    : R.string.quick_settings_dark_mode_secondary_label_on_at,
+                    use24HourFormat ? time.toString() : formatter.format(time));
         } else {
             state.secondaryLabel = null;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index ae48db2..569f660 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -68,9 +68,9 @@
 import com.android.systemui.statusbar.phone.NavigationBarFragment;
 import com.android.systemui.statusbar.phone.NavigationBarView;
 import com.android.systemui.statusbar.phone.NavigationModeController;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
-import com.android.systemui.statusbar.phone.StatusBarWindowController;
 import com.android.systemui.statusbar.policy.CallbackController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
@@ -109,7 +109,7 @@
     private SysUiState mSysUiState;
     private final Handler mHandler;
     private final NavigationBarController mNavBarController;
-    private final StatusBarWindowController mStatusBarWinController;
+    private final NotificationShadeWindowController mStatusBarWinController;
     private final Runnable mConnectionRunnable = this::internalConnectToCurrentUser;
     private final ComponentName mRecentsComponentName;
     private final DeviceProvisionedController mDeviceProvisionedController;
@@ -476,8 +476,9 @@
     @Inject
     public OverviewProxyService(Context context, DeviceProvisionedController provisionController,
             NavigationBarController navBarController, NavigationModeController navModeController,
-            StatusBarWindowController statusBarWinController, SysUiState sysUiState, PipUI pipUI,
-            Optional<Divider> dividerOptional, Optional<Lazy<StatusBar>> statusBarOptionalLazy) {
+            NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,
+            PipUI pipUI, Optional<Divider> dividerOptional,
+            Optional<Lazy<StatusBar>> statusBarOptionalLazy) {
         mContext = context;
         mPipUI = pipUI;
         mStatusBarOptionalLazy = statusBarOptionalLazy;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 1d649ee..fe84d818 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -138,7 +138,7 @@
         lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
         lp.setTitle("ScreenPinningConfirmation");
         lp.gravity = Gravity.FILL;
-        lp.setFitWindowInsetsTypes(0 /* types */);
+        lp.setFitInsetsTypes(0 /* types */);
         return lp;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
new file mode 100644
index 0000000..188501e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -0,0 +1,166 @@
+/*
+ * 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.screenrecord;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.CountDownTimer;
+import android.util.Log;
+
+import com.android.systemui.qs.tiles.ScreenRecordTile;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Helper class to initiate a screen recording
+ */
+@Singleton
+public class RecordingController {
+    private static final String TAG = "RecordingController";
+    private static final String SYSUI_PACKAGE = "com.android.systemui";
+    private static final String SYSUI_SCREENRECORD_LAUNCHER =
+            "com.android.systemui.screenrecord.ScreenRecordDialog";
+
+    private final Context mContext;
+    private boolean mIsStarting;
+    private boolean mIsRecording;
+    private ScreenRecordTile mTileToUpdate;
+    private PendingIntent mStopIntent;
+    private CountDownTimer mCountDownTimer = null;
+
+    /**
+     * Create a new RecordingController
+     * @param context Context for the controller
+     */
+    @Inject
+    public RecordingController(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Show dialog of screen recording options to user.
+     */
+    public void launchRecordPrompt(ScreenRecordTile tileToUpdate) {
+        final ComponentName launcherComponent = new ComponentName(SYSUI_PACKAGE,
+                SYSUI_SCREENRECORD_LAUNCHER);
+        final Intent intent = new Intent();
+        intent.setComponent(launcherComponent);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra("com.android.systemui.screenrecord.EXTRA_SETTINGS_ONLY", true);
+        mContext.startActivity(intent);
+
+        mTileToUpdate = tileToUpdate;
+    }
+
+    /**
+     * Start counting down in preparation to start a recording
+     * @param ms Time in ms to count down
+     * @param startIntent Intent to start a recording
+     * @param stopIntent Intent to stop a recording
+     */
+    public void startCountdown(long ms, PendingIntent startIntent, PendingIntent stopIntent) {
+        mIsStarting = true;
+        mStopIntent = stopIntent;
+
+        mCountDownTimer = new CountDownTimer(ms, 1000) {
+            @Override
+            public void onTick(long millisUntilFinished) {
+                refreshTile(millisUntilFinished);
+            }
+
+            @Override
+            public void onFinish() {
+                mIsStarting = false;
+                mIsRecording = true;
+                refreshTile();
+                try {
+                    startIntent.send();
+                } catch (PendingIntent.CanceledException e) {
+                    Log.e(TAG, "Pending intent was cancelled: " + e.getMessage());
+                }
+            }
+        };
+
+        mCountDownTimer.start();
+    }
+
+    private void refreshTile() {
+        refreshTile(0);
+    }
+
+    private void refreshTile(long millisUntilFinished) {
+        if (mTileToUpdate != null) {
+            mTileToUpdate.refreshState(millisUntilFinished);
+        } else {
+            Log.e(TAG, "No tile to refresh");
+        }
+    }
+
+    /**
+     * Cancel a countdown in progress. This will not stop the recording if it already started.
+     */
+    public void cancelCountdown() {
+        if (mCountDownTimer != null) {
+            mCountDownTimer.cancel();
+        } else {
+            Log.e(TAG, "Timer was null");
+        }
+        mIsStarting = false;
+        refreshTile();
+    }
+
+    /**
+     * Check if the recording is currently counting down to begin
+     * @return
+     */
+    public boolean isStarting() {
+        return mIsStarting;
+    }
+
+    /**
+     * Check if the recording is ongoing
+     * @return
+     */
+    public boolean isRecording() {
+        return mIsRecording;
+    }
+
+    /**
+     * Stop the recording
+     */
+    public void stopRecording() {
+        updateState(false);
+        try {
+            mStopIntent.send();
+        } catch (PendingIntent.CanceledException e) {
+            Log.e(TAG, "Error stopping: " + e.getMessage());
+        }
+        refreshTile();
+    }
+
+    /**
+     * Update the current status
+     * @param isRecording
+     */
+    public void updateState(boolean isRecording) {
+        mIsRecording = isRecording;
+        refreshTile();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 77c3ad9..b091ad8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -34,6 +34,7 @@
 import android.media.projection.MediaProjection;
 import android.media.projection.MediaProjectionManager;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.provider.MediaStore;
 import android.provider.Settings;
@@ -53,10 +54,14 @@
 import java.text.SimpleDateFormat;
 import java.util.Date;
 
+import javax.inject.Inject;
+
 /**
  * A service which records the device screen and optionally microphone input.
  */
 public class RecordingService extends Service {
+    public static final int REQUEST_CODE = 2;
+
     private static final int NOTIFICATION_ID = 1;
     private static final String TAG = "RecordingService";
     private static final String CHANNEL_ID = "screen_record";
@@ -65,13 +70,9 @@
     private static final String EXTRA_PATH = "extra_path";
     private static final String EXTRA_USE_AUDIO = "extra_useAudio";
     private static final String EXTRA_SHOW_TAPS = "extra_showTaps";
-    private static final int REQUEST_CODE = 2;
 
     private static final String ACTION_START = "com.android.systemui.screenrecord.START";
     private static final String ACTION_STOP = "com.android.systemui.screenrecord.STOP";
-    private static final String ACTION_PAUSE = "com.android.systemui.screenrecord.PAUSE";
-    private static final String ACTION_RESUME = "com.android.systemui.screenrecord.RESUME";
-    private static final String ACTION_CANCEL = "com.android.systemui.screenrecord.CANCEL";
     private static final String ACTION_SHARE = "com.android.systemui.screenrecord.SHARE";
     private static final String ACTION_DELETE = "com.android.systemui.screenrecord.DELETE";
 
@@ -81,6 +82,7 @@
     private static final int AUDIO_BIT_RATE = 16;
     private static final int AUDIO_SAMPLE_RATE = 44100;
 
+    private final RecordingController mController;
     private MediaProjectionManager mMediaProjectionManager;
     private MediaProjection mMediaProjection;
     private Surface mInputSurface;
@@ -90,8 +92,14 @@
 
     private boolean mUseAudio;
     private boolean mShowTaps;
+    private boolean mOriginalShowTaps;
     private File mTempFile;
 
+    @Inject
+    public RecordingService(RecordingController controller) {
+        mController = controller;
+    }
+
     /**
      * Get an intent to start the recording service.
      *
@@ -136,38 +144,11 @@
                 }
                 break;
 
-            case ACTION_CANCEL:
-                stopRecording();
-
-                // Delete temp file
-                if (!mTempFile.delete()) {
-                    Log.e(TAG, "Error canceling screen recording!");
-                    Toast.makeText(this, R.string.screenrecord_delete_error, Toast.LENGTH_LONG)
-                            .show();
-                } else {
-                    Toast.makeText(this, R.string.screenrecord_cancel_success, Toast.LENGTH_LONG)
-                            .show();
-                }
-
-                // Close quick shade
-                sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
-                break;
-
             case ACTION_STOP:
                 stopRecording();
                 saveRecording(notificationManager);
                 break;
 
-            case ACTION_PAUSE:
-                mMediaRecorder.pause();
-                setNotificationActions(true, notificationManager);
-                break;
-
-            case ACTION_RESUME:
-                mMediaRecorder.resume();
-                setNotificationActions(false, notificationManager);
-                break;
-
             case ACTION_SHARE:
                 Uri shareUri = Uri.parse(intent.getStringExtra(EXTRA_PATH));
 
@@ -224,9 +205,14 @@
      */
     private void startRecording() {
         try {
-            mTempFile = File.createTempFile("temp", ".mp4");
+            File cacheDir = getCacheDir();
+            cacheDir.mkdirs();
+            mTempFile = File.createTempFile("temp", ".mp4", cacheDir);
             Log.d(TAG, "Writing video output to: " + mTempFile.getAbsolutePath());
 
+            mOriginalShowTaps = 1 == Settings.System.getInt(
+                    getApplicationContext().getContentResolver(),
+                    Settings.System.SHOW_TOUCHES, 0);
             setTapsVisible(mShowTaps);
 
             // Set up media recorder
@@ -272,6 +258,7 @@
                     null);
 
             mMediaRecorder.start();
+            mController.updateState(true);
         } catch (IOException e) {
             Log.e(TAG, "Error starting screen recording: " + e.getMessage());
             e.printStackTrace();
@@ -285,59 +272,42 @@
         NotificationChannel channel = new NotificationChannel(
                 CHANNEL_ID,
                 getString(R.string.screenrecord_name),
-                NotificationManager.IMPORTANCE_HIGH);
+                NotificationManager.IMPORTANCE_DEFAULT);
         channel.setDescription(getString(R.string.screenrecord_channel_description));
         channel.enableVibration(true);
         NotificationManager notificationManager =
                 (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
         notificationManager.createNotificationChannel(channel);
 
+        Bundle extras = new Bundle();
+        extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
+                getResources().getString(R.string.screenrecord_name));
+
         mRecordingNotificationBuilder = new Notification.Builder(this, CHANNEL_ID)
-                .setSmallIcon(R.drawable.ic_android)
+                .setSmallIcon(R.drawable.ic_screenrecord)
                 .setContentTitle(getResources().getString(R.string.screenrecord_name))
+                .setContentText(getResources().getString(R.string.screenrecord_stop_text))
                 .setUsesChronometer(true)
-                .setOngoing(true);
-        setNotificationActions(false, notificationManager);
+                .setColorized(true)
+                .setColor(getResources().getColor(R.color.GM2_red_700))
+                .setOngoing(true)
+                .setContentIntent(
+                        PendingIntent.getService(
+                                this, REQUEST_CODE, getStopIntent(this),
+                                PendingIntent.FLAG_UPDATE_CURRENT))
+                .addExtras(extras);
+        notificationManager.notify(NOTIFICATION_ID, mRecordingNotificationBuilder.build());
         Notification notification = mRecordingNotificationBuilder.build();
         startForeground(NOTIFICATION_ID, notification);
     }
 
-    private void setNotificationActions(boolean isPaused, NotificationManager notificationManager) {
-        String pauseString = getResources()
-                .getString(isPaused ? R.string.screenrecord_resume_label
-                        : R.string.screenrecord_pause_label);
-        Intent pauseIntent = isPaused ? getResumeIntent(this) : getPauseIntent(this);
-
-        mRecordingNotificationBuilder.setActions(
-                new Notification.Action.Builder(
-                        Icon.createWithResource(this, R.drawable.ic_android),
-                        getResources().getString(R.string.screenrecord_stop_label),
-                        PendingIntent
-                                .getService(this, REQUEST_CODE, getStopIntent(this),
-                                        PendingIntent.FLAG_UPDATE_CURRENT))
-                        .build(),
-                new Notification.Action.Builder(
-                        Icon.createWithResource(this, R.drawable.ic_android), pauseString,
-                        PendingIntent.getService(this, REQUEST_CODE, pauseIntent,
-                                PendingIntent.FLAG_UPDATE_CURRENT))
-                        .build(),
-                new Notification.Action.Builder(
-                        Icon.createWithResource(this, R.drawable.ic_android),
-                        getResources().getString(R.string.screenrecord_cancel_label),
-                        PendingIntent
-                                .getService(this, REQUEST_CODE, getCancelIntent(this),
-                                        PendingIntent.FLAG_UPDATE_CURRENT))
-                        .build());
-        notificationManager.notify(NOTIFICATION_ID, mRecordingNotificationBuilder.build());
-    }
-
     private Notification createSaveNotification(Uri uri) {
         Intent viewIntent = new Intent(Intent.ACTION_VIEW)
                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION)
                 .setDataAndType(uri, "video/mp4");
 
         Notification.Action shareAction = new Notification.Action.Builder(
-                Icon.createWithResource(this, R.drawable.ic_android),
+                Icon.createWithResource(this, R.drawable.ic_screenrecord),
                 getResources().getString(R.string.screenrecord_share_label),
                 PendingIntent.getService(
                         this,
@@ -347,7 +317,7 @@
                 .build();
 
         Notification.Action deleteAction = new Notification.Action.Builder(
-                Icon.createWithResource(this, R.drawable.ic_android),
+                Icon.createWithResource(this, R.drawable.ic_screenrecord),
                 getResources().getString(R.string.screenrecord_delete_label),
                 PendingIntent.getService(
                         this,
@@ -356,8 +326,12 @@
                         PendingIntent.FLAG_UPDATE_CURRENT))
                 .build();
 
+        Bundle extras = new Bundle();
+        extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
+                getResources().getString(R.string.screenrecord_name));
+
         Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID)
-                .setSmallIcon(R.drawable.ic_android)
+                .setSmallIcon(R.drawable.ic_screenrecord)
                 .setContentTitle(getResources().getString(R.string.screenrecord_name))
                 .setContentText(getResources().getString(R.string.screenrecord_save_message))
                 .setContentIntent(PendingIntent.getActivity(
@@ -367,7 +341,8 @@
                         Intent.FLAG_GRANT_READ_URI_PERMISSION))
                 .addAction(shareAction)
                 .addAction(deleteAction)
-                .setAutoCancel(true);
+                .setAutoCancel(true)
+                .addExtras(extras);
 
         // Add thumbnail if available
         Bitmap thumbnailBitmap = null;
@@ -390,7 +365,7 @@
     }
 
     private void stopRecording() {
-        setTapsVisible(false);
+        setTapsVisible(mOriginalShowTaps);
         mMediaRecorder.stop();
         mMediaRecorder.release();
         mMediaRecorder = null;
@@ -399,6 +374,7 @@
         mInputSurface.release();
         mVirtualDisplay.release();
         stopSelf();
+        mController.updateState(false);
     }
 
     private void saveRecording(NotificationManager notificationManager) {
@@ -439,22 +415,15 @@
                 Settings.System.SHOW_TOUCHES, value);
     }
 
-    private static Intent getStopIntent(Context context) {
+    /**
+     * Get an intent to stop the recording service.
+     * @param context Context from the requesting activity
+     * @return
+     */
+    public static Intent getStopIntent(Context context) {
         return new Intent(context, RecordingService.class).setAction(ACTION_STOP);
     }
 
-    private static Intent getPauseIntent(Context context) {
-        return new Intent(context, RecordingService.class).setAction(ACTION_PAUSE);
-    }
-
-    private static Intent getResumeIntent(Context context) {
-        return new Intent(context, RecordingService.class).setAction(ACTION_RESUME);
-    }
-
-    private static Intent getCancelIntent(Context context) {
-        return new Intent(context, RecordingService.class).setAction(ACTION_CANCEL);
-    }
-
     private static Intent getShareIntent(Context context, String path) {
         return new Intent(context, RecordingService.class).setAction(ACTION_SHARE)
                 .putExtra(EXTRA_PATH, path);
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
index 27e9fba..8324986 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
@@ -18,55 +18,41 @@
 
 import android.Manifest;
 import android.app.Activity;
+import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.media.projection.MediaProjectionManager;
 import android.os.Bundle;
-import android.util.Log;
-import android.widget.Button;
-import android.widget.CheckBox;
 import android.widget.Toast;
 
 import com.android.systemui.R;
 
+import javax.inject.Inject;
+
 /**
  * Activity to select screen recording options
  */
 public class ScreenRecordDialog extends Activity {
-    private static final String TAG = "ScreenRecord";
     private static final int REQUEST_CODE_VIDEO_ONLY = 200;
     private static final int REQUEST_CODE_VIDEO_TAPS = 201;
     private static final int REQUEST_CODE_PERMISSIONS = 299;
     private static final int REQUEST_CODE_VIDEO_AUDIO = 300;
     private static final int REQUEST_CODE_VIDEO_AUDIO_TAPS = 301;
     private static final int REQUEST_CODE_PERMISSIONS_AUDIO = 399;
-    private boolean mUseAudio;
-    private boolean mShowTaps;
+    private static final long DELAY_MS = 3000;
+
+    private final RecordingController mController;
+
+    @Inject
+    public ScreenRecordDialog(RecordingController controller) {
+        mController = controller;
+    }
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setContentView(R.layout.screen_record_dialog);
-
-        final CheckBox micCheckBox = findViewById(R.id.checkbox_mic);
-        final CheckBox tapsCheckBox = findViewById(R.id.checkbox_taps);
-
-        final Button recordButton = findViewById(R.id.record_button);
-        recordButton.setOnClickListener(v -> {
-            mUseAudio = micCheckBox.isChecked();
-            mShowTaps = tapsCheckBox.isChecked();
-            Log.d(TAG, "Record button clicked: audio " + mUseAudio + ", taps " + mShowTaps);
-
-            if (mUseAudio && checkSelfPermission(Manifest.permission.RECORD_AUDIO)
-                    != PackageManager.PERMISSION_GRANTED) {
-                Log.d(TAG, "Requesting permission for audio");
-                requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO},
-                        REQUEST_CODE_PERMISSIONS_AUDIO);
-            } else {
-                requestScreenCapture();
-            }
-        });
+        requestScreenCapture();
     }
 
     private void requestScreenCapture() {
@@ -74,18 +60,23 @@
                 Context.MEDIA_PROJECTION_SERVICE);
         Intent permissionIntent = mediaProjectionManager.createScreenCaptureIntent();
 
-        if (mUseAudio) {
+        // TODO get saved settings
+        boolean useAudio = false;
+        boolean showTaps = false;
+        if (useAudio) {
             startActivityForResult(permissionIntent,
-                    mShowTaps ? REQUEST_CODE_VIDEO_AUDIO_TAPS : REQUEST_CODE_VIDEO_AUDIO);
+                    showTaps ? REQUEST_CODE_VIDEO_AUDIO_TAPS : REQUEST_CODE_VIDEO_AUDIO);
         } else {
             startActivityForResult(permissionIntent,
-                    mShowTaps ? REQUEST_CODE_VIDEO_TAPS : REQUEST_CODE_VIDEO_ONLY);
+                    showTaps ? REQUEST_CODE_VIDEO_TAPS : REQUEST_CODE_VIDEO_ONLY);
         }
     }
 
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        mShowTaps = (requestCode == REQUEST_CODE_VIDEO_TAPS
+        boolean showTaps = (requestCode == REQUEST_CODE_VIDEO_TAPS
+                || requestCode == REQUEST_CODE_VIDEO_AUDIO_TAPS);
+        boolean useAudio = (requestCode == REQUEST_CODE_VIDEO_AUDIO
                 || requestCode == REQUEST_CODE_VIDEO_AUDIO_TAPS);
         switch (requestCode) {
             case REQUEST_CODE_VIDEO_TAPS:
@@ -93,11 +84,17 @@
             case REQUEST_CODE_VIDEO_ONLY:
             case REQUEST_CODE_VIDEO_AUDIO:
                 if (resultCode == RESULT_OK) {
-                    mUseAudio = (requestCode == REQUEST_CODE_VIDEO_AUDIO
-                            || requestCode == REQUEST_CODE_VIDEO_AUDIO_TAPS);
-                    startForegroundService(
-                            RecordingService.getStartIntent(this, resultCode, data, mUseAudio,
-                                    mShowTaps));
+                    PendingIntent startIntent = PendingIntent.getForegroundService(
+                            this, RecordingService.REQUEST_CODE, RecordingService.getStartIntent(
+                                    ScreenRecordDialog.this, resultCode, data, useAudio,
+                                    showTaps),
+                            PendingIntent.FLAG_UPDATE_CURRENT
+                            );
+                    PendingIntent stopIntent = PendingIntent.getService(
+                            this, RecordingService.REQUEST_CODE,
+                            RecordingService.getStopIntent(this),
+                            PendingIntent.FLAG_UPDATE_CURRENT);
+                    mController.startCountdown(DELAY_MS, startIntent, stopIntent);
                 } else {
                     Toast.makeText(this,
                             getResources().getString(R.string.screenrecord_permission_error),
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 02c4beb..99a9dfe 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011 The Android Open Source Project
+ * 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.
@@ -20,7 +20,7 @@
 import static android.view.View.VISIBLE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_CORNER_FLOW;
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_SCROLLING_ENABLED;
 import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_SCREENSHOT;
 
 import android.animation.Animator;
@@ -29,19 +29,21 @@
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
-import android.graphics.Color;
+import android.graphics.Outline;
 import android.graphics.PixelFormat;
 import android.graphics.PointF;
 import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.drawable.Icon;
 import android.media.MediaActionSound;
 import android.net.Uri;
 import android.os.AsyncTask;
@@ -49,6 +51,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.util.DisplayMetrics;
@@ -60,11 +63,13 @@
 import android.view.SurfaceControl;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
+import android.view.ViewTreeObserver;
 import android.view.WindowManager;
 import android.view.animation.Interpolator;
+import android.widget.HorizontalScrollView;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
-import android.widget.TextView;
 import android.widget.Toast;
 
 import com.android.systemui.R;
@@ -88,7 +93,7 @@
  * Class for handling device screen shots
  */
 @Singleton
-public class GlobalScreenshot {
+public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInsetsListener {
 
     /**
      * POD used in the AsyncTask which saves an image in the background.
@@ -99,6 +104,7 @@
         public Consumer<Uri> finisher;
         public GlobalScreenshot.ActionsReadyListener mActionsReadyListener;
         public int errorMsgResId;
+        public boolean createDeleteAction;
 
         void clearImage() {
             image = null;
@@ -107,7 +113,8 @@
     }
 
     abstract static class ActionsReadyListener {
-        abstract void onActionsReady(Uri imageUri, List<Notification.Action> actions);
+        abstract void onActionsReady(Uri imageUri, List<Notification.Action> smartActions,
+                List<Notification.Action> actions);
     }
 
     // These strings are used for communicating the action invoked to
@@ -131,38 +138,36 @@
     private static final int SCREENSHOT_DROP_OUT_DELAY = 500;
     private static final int SCREENSHOT_DROP_OUT_DURATION = 430;
     private static final int SCREENSHOT_DROP_OUT_SCALE_DURATION = 370;
-    private static final int SCREENSHOT_FAST_DROP_OUT_DURATION = 320;
     private static final float BACKGROUND_ALPHA = 0.5f;
-    private static final float SCREENSHOT_SCALE = 1f;
-    private static final float SCREENSHOT_DROP_IN_MIN_SCALE = SCREENSHOT_SCALE * 0.725f;
-    private static final float SCREENSHOT_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.45f;
-    private static final float SCREENSHOT_CORNER_MIN_SCALE = SCREENSHOT_SCALE * 0.2f;
-    private static final float SCREENSHOT_FAST_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.6f;
-    private static final float SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET = 0f;
-    private static final float SCREENSHOT_CORNER_MIN_SCALE_OFFSET = .1f;
+    private static final float SCREENSHOT_DROP_IN_MIN_SCALE = 0.725f;
+    private static final float ROUNDED_CORNER_RADIUS = .05f;
     private static final long SCREENSHOT_CORNER_TIMEOUT_MILLIS = 8000;
     private static final int MESSAGE_CORNER_TIMEOUT = 2;
 
     private final ScreenshotNotificationsController mNotificationsController;
 
-    private Context mContext;
-    private WindowManager mWindowManager;
-    private WindowManager.LayoutParams mWindowLayoutParams;
-    private Display mDisplay;
-    private DisplayMetrics mDisplayMetrics;
+    private final Context mContext;
+    private final WindowManager mWindowManager;
+    private final WindowManager.LayoutParams mWindowLayoutParams;
+    private final Display mDisplay;
+    private final DisplayMetrics mDisplayMetrics;
+
+    private final View mScreenshotLayout;
+    private final ScreenshotSelectorView mScreenshotSelectorView;
+    private final ImageView mBackgroundView;
+    private final ImageView mScreenshotView;
+    private final ImageView mScreenshotFlash;
+    private final HorizontalScrollView mActionsContainer;
+    private final LinearLayout mActionsView;
+    private final ImageView mBackgroundProtection;
 
     private Bitmap mScreenBitmap;
-    private View mScreenshotLayout;
-    private ScreenshotSelectorView mScreenshotSelectorView;
-    private ImageView mBackgroundView;
-    private ImageView mScreenshotView;
-    private ImageView mScreenshotFlash;
-    private LinearLayout mActionsView;
-
     private AnimatorSet mScreenshotAnimation;
 
-    private float mBgPadding;
-    private float mBgPaddingScale;
+    private float mScreenshotOffsetXPx;
+    private float mScreenshotOffsetYPx;
+    private float mScreenshotHeightPx;
+    private float mCornerScale;
 
     private AsyncTask<Void, Void, Void> mSaveInBgTask;
 
@@ -173,7 +178,7 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MESSAGE_CORNER_TIMEOUT:
-                    GlobalScreenshot.this.clearScreenshot();
+                    GlobalScreenshot.this.clearScreenshot("timeout");
                     break;
                 default:
                     break;
@@ -195,7 +200,20 @@
         mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot, null);
         mBackgroundView = mScreenshotLayout.findViewById(R.id.global_screenshot_background);
         mScreenshotView = mScreenshotLayout.findViewById(R.id.global_screenshot);
+        mScreenshotView.setClipToOutline(true);
+        mScreenshotView.setOutlineProvider(new ViewOutlineProvider() {
+            @Override
+            public void getOutline(View view, Outline outline) {
+                outline.setRoundRect(new Rect(0, 0, view.getWidth(), view.getHeight()),
+                        ROUNDED_CORNER_RADIUS * view.getWidth());
+            }
+        });
+
+        mActionsContainer = mScreenshotLayout.findViewById(
+                R.id.global_screenshot_actions_container);
         mActionsView = mScreenshotLayout.findViewById(R.id.global_screenshot_actions);
+        mBackgroundProtection = mScreenshotLayout.findViewById(
+                R.id.global_screenshot_actions_background);
 
         mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_flash);
         mScreenshotSelectorView = mScreenshotLayout.findViewById(R.id.global_screenshot_selector);
@@ -203,6 +221,9 @@
         mScreenshotSelectorView.setFocusable(true);
         mScreenshotSelectorView.setFocusableInTouchMode(true);
         mScreenshotLayout.setOnTouchListener((v, event) -> {
+            if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
+                clearScreenshot("tap_outside");
+            }
             // Intercept and ignore all touch events
             return true;
         });
@@ -213,26 +234,45 @@
                 WindowManager.LayoutParams.TYPE_SCREENSHOT,
                 WindowManager.LayoutParams.FLAG_FULLSCREEN
                         | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                         | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
                 PixelFormat.TRANSLUCENT);
         mWindowLayoutParams.setTitle("ScreenshotAnimation");
         mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-        mWindowLayoutParams.setFitWindowInsetsTypes(0 /* types */);
+        mWindowLayoutParams.setFitInsetsTypes(0 /* types */);
         mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
         mDisplay = mWindowManager.getDefaultDisplay();
         mDisplayMetrics = new DisplayMetrics();
         mDisplay.getRealMetrics(mDisplayMetrics);
 
-        // Scale has to account for both sides of the bg
-        mBgPadding = (float) resources.getDimensionPixelSize(R.dimen.global_screenshot_bg_padding);
-        mBgPaddingScale = mBgPadding / mDisplayMetrics.widthPixels;
-
+        mScreenshotOffsetXPx = resources.getDimensionPixelSize(R.dimen.screenshot_offset_x);
+        mScreenshotOffsetYPx = resources.getDimensionPixelSize(R.dimen.screenshot_offset_y);
+        mScreenshotHeightPx =
+                resources.getDimensionPixelSize(R.dimen.screenshot_action_container_offset_y);
+        mCornerScale = resources.getDimensionPixelSize(R.dimen.global_screenshot_x_scale)
+                / (float) mDisplayMetrics.widthPixels;
 
         // Setup the Camera shutter sound
         mCameraSound = new MediaActionSound();
         mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
     }
 
+    @Override // ViewTreeObserver.OnComputeInternalInsetsListener
+    public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
+        inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+        Region touchRegion = new Region();
+
+        Rect screenshotRect = new Rect();
+        mScreenshotView.getBoundsOnScreen(screenshotRect);
+        touchRegion.op(screenshotRect, Region.Op.UNION);
+        Rect actionsRect = new Rect();
+        mActionsContainer.getBoundsOnScreen(actionsRect);
+        touchRegion.op(actionsRect, Region.Op.UNION);
+
+        inoutInfo.touchableRegion.set(touchRegion);
+    }
+
     /**
      * Creates a new worker thread and saves the screenshot to the media store.
      */
@@ -242,24 +282,20 @@
         data.image = mScreenBitmap;
         data.finisher = finisher;
         data.mActionsReadyListener = actionsReadyListener;
+        data.createDeleteAction = false;
         if (mSaveInBgTask != null) {
             mSaveInBgTask.cancel(false);
         }
 
-        if (!DeviceConfig.getBoolean(
-                NAMESPACE_SYSTEMUI, SCREENSHOT_CORNER_FLOW, false)) {
-            mNotificationsController.reset();
-            mNotificationsController.setImage(mScreenBitmap);
-            mNotificationsController.showSavingScreenshotNotification();
-        }
         mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data).execute();
     }
 
     /**
      * Takes a screenshot of the current display and shows an animation.
      */
-    private void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible,
-            boolean navBarVisible, Rect crop) {
+    private void takeScreenshot(Consumer<Uri> finisher, Rect crop) {
+        clearScreenshot("new screenshot requested");
+
         int rot = mDisplay.getRotation();
         int width = crop.width();
         int height = crop.height();
@@ -277,22 +313,24 @@
         mScreenBitmap.setHasAlpha(false);
         mScreenBitmap.prepareToDraw();
 
+        mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
+        mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+
         // Start the post-screenshot animation
-        startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
-                statusBarVisible, navBarVisible);
+        startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels);
     }
 
-    void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible, boolean navBarVisible) {
+    void takeScreenshot(Consumer<Uri> finisher) {
         mDisplay.getRealMetrics(mDisplayMetrics);
-        takeScreenshot(finisher, statusBarVisible, navBarVisible,
+        takeScreenshot(
+                finisher,
                 new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
     }
 
     /**
      * Displays a screenshot selector
      */
-    void takeScreenshotPartial(final Consumer<Uri> finisher, final boolean statusBarVisible,
-            final boolean navBarVisible) {
+    void takeScreenshotPartial(final Consumer<Uri> finisher) {
         mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
         mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() {
             @Override
@@ -312,8 +350,7 @@
                         if (rect != null) {
                             if (rect.width() != 0 && rect.height() != 0) {
                                 // Need mScreenshotLayout to handle it after the view disappears
-                                mScreenshotLayout.post(() -> takeScreenshot(
-                                        finisher, statusBarVisible, navBarVisible, rect));
+                                mScreenshotLayout.post(() -> takeScreenshot(finisher, rect));
                             }
                         }
 
@@ -324,12 +361,9 @@
                 return false;
             }
         });
-        mScreenshotLayout.post(new Runnable() {
-            @Override
-            public void run() {
-                mScreenshotSelectorView.setVisibility(View.VISIBLE);
-                mScreenshotSelectorView.requestFocus();
-            }
+        mScreenshotLayout.post(() -> {
+            mScreenshotSelectorView.setVisibility(View.VISIBLE);
+            mScreenshotSelectorView.requestFocus();
         });
     }
 
@@ -347,16 +381,20 @@
     /**
      * Clears current screenshot
      */
-    private void clearScreenshot() {
+    private void clearScreenshot(String reason) {
+        Log.e(TAG, "clearing screenshot: " + reason);
         if (mScreenshotLayout.isAttachedToWindow()) {
             mWindowManager.removeView(mScreenshotLayout);
         }
+        mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
+        mScreenshotLayout.getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
 
         // Clear any references to the bitmap
         mScreenBitmap = null;
         mScreenshotView.setImageBitmap(null);
-        mActionsView.setVisibility(View.GONE);
+        mActionsContainer.setVisibility(View.GONE);
         mBackgroundView.setVisibility(View.GONE);
+        mBackgroundProtection.setAlpha(0f);
         mScreenshotView.setVisibility(View.GONE);
         mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
     }
@@ -364,8 +402,7 @@
     /**
      * Starts the animation after taking the screenshot
      */
-    private void startAnimation(final Consumer<Uri> finisher, int w, int h,
-            boolean statusBarVisible, boolean navBarVisible) {
+    private void startAnimation(final Consumer<Uri> finisher, int w, int h) {
         // If power save is on, show a toast so there is some visual indication that a screenshot
         // has been taken.
         PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -385,50 +422,32 @@
             mScreenshotAnimation.removeAllListeners();
         }
 
-        boolean useCornerFlow =
-                DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, SCREENSHOT_CORNER_FLOW, false);
-        mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
         ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation();
-        ValueAnimator screenshotFadeOutAnim = useCornerFlow
-                ? createScreenshotToCornerAnimation(w, h)
-                : createScreenshotDropOutAnimation(w, h, statusBarVisible, navBarVisible);
+        ValueAnimator screenshotFadeOutAnim = createScreenshotToCornerAnimation(w, h);
         mScreenshotAnimation = new AnimatorSet();
         mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim);
         mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
                 // Save the screenshot once we have a bit of time now
-                if (!useCornerFlow) {
-                    saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
-                        @Override
-                        void onActionsReady(Uri uri, List<Notification.Action> actions) {
-                            if (uri == null) {
-                                mNotificationsController.notifyScreenshotError(
-                                        R.string.screenshot_failed_to_capture_text);
-                            } else {
-                                mNotificationsController
-                                        .showScreenshotActionsNotification(uri, actions);
-                            }
+                saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
+                    @Override
+                    void onActionsReady(Uri uri, List<Notification.Action> smartActions,
+                            List<Notification.Action> actions) {
+                        if (uri == null) {
+                            mNotificationsController.notifyScreenshotError(
+                                    R.string.screenshot_failed_to_capture_text);
+                        } else {
+                            mScreenshotHandler.post(() ->
+                                    createScreenshotActionsShadeAnimation(smartActions,
+                                            actions).start());
                         }
-                    });
-                    clearScreenshot();
-                } else {
-                    saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
-                        @Override
-                        void onActionsReady(Uri uri, List<Notification.Action> actions) {
-                            if (uri == null) {
-                                mNotificationsController.notifyScreenshotError(
-                                        R.string.screenshot_failed_to_capture_text);
-                            } else {
-                                mScreenshotHandler.post(() ->
-                                        createScreenshotActionsShadeAnimation(actions).start());
-                            }
-                        }
-                    });
-                    mScreenshotHandler.sendMessageDelayed(
-                            mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
-                            SCREENSHOT_CORNER_TIMEOUT_MILLIS);
-                }
+                    }
+                });
+                mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
+                mScreenshotHandler.sendMessageDelayed(
+                        mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
+                        SCREENSHOT_CORNER_TIMEOUT_MILLIS);
             }
         });
         mScreenshotHandler.post(() -> {
@@ -466,14 +485,6 @@
             }
         };
 
-        Resources r = mContext.getResources();
-        if ((r.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)
-                == Configuration.UI_MODE_NIGHT_YES) {
-            mScreenshotView.getBackground().setTint(Color.BLACK);
-        } else {
-            mScreenshotView.getBackground().setTintList(null);
-        }
-
         ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
         anim.setDuration(SCREENSHOT_DROP_IN_DURATION);
         anim.addListener(new AnimatorListenerAdapter() {
@@ -484,15 +495,15 @@
                 mScreenshotView.setAlpha(0f);
                 mScreenshotView.setTranslationX(0f);
                 mScreenshotView.setTranslationY(0f);
-                mScreenshotView.setScaleX(SCREENSHOT_SCALE + mBgPaddingScale);
-                mScreenshotView.setScaleY(SCREENSHOT_SCALE + mBgPaddingScale);
+                mScreenshotView.setScaleX(1);
+                mScreenshotView.setScaleY(1);
                 mScreenshotView.setVisibility(View.VISIBLE);
                 mScreenshotFlash.setAlpha(0f);
                 mScreenshotFlash.setVisibility(View.VISIBLE);
             }
 
             @Override
-            public void onAnimationEnd(android.animation.Animator animation) {
+            public void onAnimationEnd(Animator animation) {
                 mScreenshotFlash.setVisibility(View.GONE);
             }
         });
@@ -500,9 +511,8 @@
             @Override
             public void onAnimationUpdate(ValueAnimator animation) {
                 float t = (Float) animation.getAnimatedValue();
-                float scaleT = (SCREENSHOT_SCALE + mBgPaddingScale)
-                        - scaleInterpolator.getInterpolation(t)
-                        * (SCREENSHOT_SCALE - SCREENSHOT_DROP_IN_MIN_SCALE);
+                float scaleT = 1 - (scaleInterpolator.getInterpolation(t)
+                        * (1 - SCREENSHOT_DROP_IN_MIN_SCALE));
                 mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA);
                 mScreenshotView.setAlpha(t);
                 mScreenshotView.setScaleX(scaleT);
@@ -513,81 +523,6 @@
         return anim;
     }
 
-    private ValueAnimator createScreenshotDropOutAnimation(int w, int h, boolean statusBarVisible,
-            boolean navBarVisible) {
-        ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
-        anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);
-        anim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mBackgroundView.setVisibility(View.GONE);
-                mScreenshotView.setVisibility(View.GONE);
-                mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
-            }
-        });
-
-        if (!statusBarVisible || !navBarVisible) {
-            // There is no status bar/nav bar, so just fade the screenshot away in place
-            anim.setDuration(SCREENSHOT_FAST_DROP_OUT_DURATION);
-            anim.addUpdateListener(new AnimatorUpdateListener() {
-                @Override
-                public void onAnimationUpdate(ValueAnimator animation) {
-                    float t = (Float) animation.getAnimatedValue();
-                    float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
-                            - t * (SCREENSHOT_DROP_IN_MIN_SCALE
-                            - SCREENSHOT_FAST_DROP_OUT_MIN_SCALE);
-                    mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
-                    mScreenshotView.setAlpha(1f - t);
-                    mScreenshotView.setScaleX(scaleT);
-                    mScreenshotView.setScaleY(scaleT);
-                }
-            });
-        } else {
-            // In the case where there is a status bar, animate to the origin of the bar (top-left)
-            final float scaleDurationPct = (float) SCREENSHOT_DROP_OUT_SCALE_DURATION
-                    / SCREENSHOT_DROP_OUT_DURATION;
-            final Interpolator scaleInterpolator = new Interpolator() {
-                @Override
-                public float getInterpolation(float x) {
-                    if (x < scaleDurationPct) {
-                        // Decelerate, and scale the input accordingly
-                        return (float) (1f - Math.pow(1f - (x / scaleDurationPct), 2f));
-                    }
-                    return 1f;
-                }
-            };
-
-            // Determine the bounds of how to scale
-            float halfScreenWidth = (w - 2f * mBgPadding) / 2f;
-            float halfScreenHeight = (h - 2f * mBgPadding) / 2f;
-            final float offsetPct = SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET;
-            final PointF finalPos = new PointF(
-                    -halfScreenWidth
-                            + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenWidth,
-                    -halfScreenHeight
-                            + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenHeight);
-
-            // Animate the screenshot to the status bar
-            anim.setDuration(SCREENSHOT_DROP_OUT_DURATION);
-            anim.addUpdateListener(new AnimatorUpdateListener() {
-                @Override
-                public void onAnimationUpdate(ValueAnimator animation) {
-                    float t = (Float) animation.getAnimatedValue();
-                    float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
-                            - scaleInterpolator.getInterpolation(t)
-                            * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_DROP_OUT_MIN_SCALE);
-                    mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
-                    mScreenshotView.setAlpha(1f - scaleInterpolator.getInterpolation(t));
-                    mScreenshotView.setScaleX(scaleT);
-                    mScreenshotView.setScaleY(scaleT);
-                    mScreenshotView.setTranslationX(t * finalPos.x);
-                    mScreenshotView.setTranslationY(t * finalPos.y);
-                }
-            });
-        }
-        return anim;
-    }
-
     private ValueAnimator createScreenshotToCornerAnimation(int w, int h) {
         ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
         anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);
@@ -606,20 +541,19 @@
         };
 
         // Determine the bounds of how to scale
-        float halfScreenWidth = (w - 2f * mBgPadding) / 2f;
-        float halfScreenHeight = (h - 2f * mBgPadding) / 2f;
-        final float offsetPct = SCREENSHOT_CORNER_MIN_SCALE_OFFSET;
+        float halfScreenWidth = w / 2f;
+        float halfScreenHeight = h / 2f;
         final PointF finalPos = new PointF(
-                -halfScreenWidth + (SCREENSHOT_CORNER_MIN_SCALE + offsetPct) * halfScreenWidth,
-                halfScreenHeight - (SCREENSHOT_CORNER_MIN_SCALE + offsetPct) * halfScreenHeight);
+                -halfScreenWidth + mCornerScale * halfScreenWidth + mScreenshotOffsetXPx,
+                halfScreenHeight - mCornerScale * halfScreenHeight - mScreenshotOffsetYPx);
 
         // Animate the screenshot to the bottom left corner
         anim.setDuration(SCREENSHOT_DROP_OUT_DURATION);
         anim.addUpdateListener(animation -> {
             float t = (Float) animation.getAnimatedValue();
-            float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
+            float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE)
                     - scaleInterpolator.getInterpolation(t)
-                    * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_CORNER_MIN_SCALE);
+                    * (SCREENSHOT_DROP_IN_MIN_SCALE - mCornerScale);
             mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
             mScreenshotView.setScaleX(scaleT);
             mScreenshotView.setScaleY(scaleT);
@@ -629,50 +563,80 @@
         return anim;
     }
 
-    private ValueAnimator createScreenshotActionsShadeAnimation(List<Notification.Action> actions) {
+    private ValueAnimator createScreenshotActionsShadeAnimation(
+            List<Notification.Action> smartActions, List<Notification.Action> actions) {
         LayoutInflater inflater = LayoutInflater.from(mContext);
         mActionsView.removeAllViews();
+        mActionsContainer.setScrollX(0);
+        mScreenshotLayout.invalidate();
+        mScreenshotLayout.requestLayout();
+        mScreenshotLayout.getViewTreeObserver().dispatchOnGlobalLayout();
 
-        for (Notification.Action action : actions) {
-            TextView actionChip = (TextView) inflater.inflate(
+        // By default the activities won't be able to start immediately; override this to keep
+        // the same behavior as if started from a notification
+        try {
+            ActivityManager.getService().resumeAppSwitches();
+        } catch (RemoteException e) {
+        }
+
+        for (Notification.Action smartAction : smartActions) {
+            ScreenshotActionChip actionChip = (ScreenshotActionChip) inflater.inflate(
                     R.layout.global_screenshot_action_chip, mActionsView, false);
-            actionChip.setText(action.title);
-            actionChip.setOnClickListener(v -> {
-                try {
-                    action.actionIntent.send();
-                    clearScreenshot();
-                } catch (PendingIntent.CanceledException e) {
-                    Log.e(TAG,
-                            String.format("Intent cancelled (title: %s)", action.title), e);
-                }
-            });
+            actionChip.setText(smartAction.title);
+            actionChip.setIcon(smartAction.getIcon(), false);
+            actionChip.setPendingIntent(smartAction.actionIntent,
+                    () -> clearScreenshot("chip tapped"));
             mActionsView.addView(actionChip);
         }
-        TextView scrollChip = (TextView) inflater.inflate(
-                R.layout.global_screenshot_action_chip, mActionsView, false);
-        Toast scrollNotImplemented = Toast.makeText(
-                mContext, "Not implemented", Toast.LENGTH_SHORT);
-        scrollChip.setText("Scroll"); // TODO (mkephart): add resource and translate
-        scrollChip.setOnClickListener(v -> scrollNotImplemented.show());
-        mActionsView.addView(scrollChip);
+
+        for (Notification.Action action : actions) {
+            ScreenshotActionChip actionChip = (ScreenshotActionChip) inflater.inflate(
+                    R.layout.global_screenshot_action_chip, mActionsView, false);
+            actionChip.setText(action.title);
+            actionChip.setIcon(action.getIcon(), true);
+            actionChip.setPendingIntent(action.actionIntent, () -> clearScreenshot("chip tapped"));
+            if (action.actionIntent.getIntent().getAction().equals(Intent.ACTION_EDIT)) {
+                mScreenshotView.setOnClickListener(v -> {
+                    try {
+                        action.actionIntent.send();
+                        clearScreenshot("screenshot preview tapped");
+                    } catch (PendingIntent.CanceledException e) {
+                        Log.e(TAG, "Intent cancelled", e);
+                    }
+                });
+            }
+            mActionsView.addView(actionChip);
+        }
+
+        if (DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, SCREENSHOT_SCROLLING_ENABLED, false)) {
+            ScreenshotActionChip scrollChip = (ScreenshotActionChip) inflater.inflate(
+                    R.layout.global_screenshot_action_chip, mActionsView, false);
+            Toast scrollNotImplemented = Toast.makeText(
+                    mContext, "Not implemented", Toast.LENGTH_SHORT);
+            scrollChip.setText("Extend"); // TODO (mkephart): add resource and translate
+            scrollChip.setIcon(
+                    Icon.createWithResource(mContext, R.drawable.ic_arrow_downward), true);
+            scrollChip.setOnClickListener(v -> scrollNotImplemented.show());
+            mActionsView.addView(scrollChip);
+        }
 
         ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
-        mActionsView.setY(mDisplayMetrics.heightPixels);
-        mActionsView.setVisibility(VISIBLE);
-        mActionsView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
-        float actionsViewHeight = mActionsView.getMeasuredHeight();
-        float screenshotStartHeight = mScreenshotView.getTranslationY();
+        mActionsContainer.setY(mDisplayMetrics.heightPixels);
+        mActionsContainer.setVisibility(VISIBLE);
+        mActionsContainer.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+        float actionsViewHeight = mActionsContainer.getMeasuredHeight() + mScreenshotHeightPx;
 
         animator.addUpdateListener(animation -> {
             float t = animation.getAnimatedFraction();
-            mScreenshotView.setTranslationY(screenshotStartHeight - actionsViewHeight * t);
-            mActionsView.setY(mDisplayMetrics.heightPixels - actionsViewHeight * t);
+            mBackgroundProtection.setAlpha(t);
+            mActionsContainer.setY(mDisplayMetrics.heightPixels - actionsViewHeight * t);
         });
         animator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
                 super.onAnimationEnd(animation);
                 mScreenshotView.requestFocus();
+                mScreenshotView.setElevation(50);
             }
         });
         return animator;
@@ -776,8 +740,7 @@
             String actionType = intent.getStringExtra(EXTRA_ACTION_TYPE);
             Slog.d(TAG, "Executing smart action [" + actionType + "]:" + actionIntent);
             ActivityOptions opts = ActivityOptions.makeBasic();
-            context.startActivityAsUser(actionIntent, opts.toBundle(),
-                    UserHandle.CURRENT);
+            context.startActivityAsUser(actionIntent, opts.toBundle(), UserHandle.CURRENT);
 
             ScreenshotSmartActions.notifyScreenshotAction(
                     context, intent.getStringExtra(EXTRA_ID), actionType, true);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java
new file mode 100644
index 0000000..a5baa7a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java
@@ -0,0 +1,507 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.media.MediaActionSound;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.PowerManager;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.animation.Interpolator;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Class for handling device screen shots
+ *
+ * @deprecated will be removed when corner flow is complete and tested
+ */
+@Singleton
+@Deprecated
+public class GlobalScreenshotLegacy {
+
+    // These strings are used for communicating the action invoked to
+    // ScreenshotNotificationSmartActionsProvider.
+    static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type";
+    static final String EXTRA_ID = "android:screenshot_id";
+    static final String ACTION_TYPE_DELETE = "Delete";
+    static final String ACTION_TYPE_SHARE = "Share";
+    static final String ACTION_TYPE_EDIT = "Edit";
+    static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled";
+    static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
+
+    static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
+    static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
+    static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip";
+
+    private static final String TAG = "GlobalScreenshot";
+
+    private static final int SCREENSHOT_FLASH_TO_PEAK_DURATION = 130;
+    private static final int SCREENSHOT_DROP_IN_DURATION = 430;
+    private static final int SCREENSHOT_DROP_OUT_DELAY = 500;
+    private static final int SCREENSHOT_DROP_OUT_DURATION = 430;
+    private static final int SCREENSHOT_DROP_OUT_SCALE_DURATION = 370;
+    private static final int SCREENSHOT_FAST_DROP_OUT_DURATION = 320;
+    private static final float BACKGROUND_ALPHA = 0.5f;
+    private static final float SCREENSHOT_SCALE = 1f;
+    private static final float SCREENSHOT_DROP_IN_MIN_SCALE = SCREENSHOT_SCALE * 0.725f;
+    private static final float SCREENSHOT_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.45f;
+    private static final float SCREENSHOT_FAST_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.6f;
+    private static final float SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET = 0f;
+
+    private final ScreenshotNotificationsController mNotificationsController;
+
+    private Context mContext;
+    private WindowManager mWindowManager;
+    private WindowManager.LayoutParams mWindowLayoutParams;
+    private Display mDisplay;
+    private DisplayMetrics mDisplayMetrics;
+
+    private Bitmap mScreenBitmap;
+    private View mScreenshotLayout;
+    private ScreenshotSelectorView mScreenshotSelectorView;
+    private ImageView mBackgroundView;
+    private ImageView mScreenshotView;
+    private ImageView mScreenshotFlash;
+
+    private AnimatorSet mScreenshotAnimation;
+
+    private float mBgPadding;
+    private float mBgPaddingScale;
+
+    private AsyncTask<Void, Void, Void> mSaveInBgTask;
+
+    private MediaActionSound mCameraSound;
+
+    /**
+     * @param context everything needs a context :(
+     */
+    @Inject
+    public GlobalScreenshotLegacy(
+            Context context, @Main Resources resources, LayoutInflater layoutInflater,
+            ScreenshotNotificationsController screenshotNotificationsController) {
+        mContext = context;
+        mNotificationsController = screenshotNotificationsController;
+
+        // Inflate the screenshot layout
+        mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot_legacy, null);
+        mBackgroundView = mScreenshotLayout.findViewById(R.id.global_screenshot_legacy_background);
+        mScreenshotView = mScreenshotLayout.findViewById(R.id.global_screenshot_legacy);
+
+        mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_legacy_flash);
+        mScreenshotSelectorView = mScreenshotLayout.findViewById(
+                R.id.global_screenshot_legacy_selector);
+        mScreenshotLayout.setFocusable(true);
+        mScreenshotSelectorView.setFocusable(true);
+        mScreenshotSelectorView.setFocusableInTouchMode(true);
+        mScreenshotLayout.setOnTouchListener((v, event) -> {
+            // Intercept and ignore all touch events
+            return true;
+        });
+
+        // Setup the window that we are going to use
+        mWindowLayoutParams = new WindowManager.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0,
+                WindowManager.LayoutParams.TYPE_SCREENSHOT,
+                WindowManager.LayoutParams.FLAG_FULLSCREEN
+                        | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                        | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
+                PixelFormat.TRANSLUCENT);
+        mWindowLayoutParams.setTitle("ScreenshotAnimation");
+        mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        mWindowLayoutParams.setFitInsetsTypes(0 /* types */);
+        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        mDisplay = mWindowManager.getDefaultDisplay();
+        mDisplayMetrics = new DisplayMetrics();
+        mDisplay.getRealMetrics(mDisplayMetrics);
+
+        // Scale has to account for both sides of the bg
+        mBgPadding = (float) resources.getDimensionPixelSize(
+                R.dimen.global_screenshot_legacy_bg_padding);
+        mBgPaddingScale = mBgPadding / mDisplayMetrics.widthPixels;
+
+
+        // Setup the Camera shutter sound
+        mCameraSound = new MediaActionSound();
+        mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
+    }
+
+    /**
+     * Creates a new worker thread and saves the screenshot to the media store.
+     */
+    private void saveScreenshotInWorkerThread(
+            Consumer<Uri> finisher,
+            @Nullable GlobalScreenshot.ActionsReadyListener actionsReadyListener) {
+        GlobalScreenshot.SaveImageInBackgroundData data =
+                new GlobalScreenshot.SaveImageInBackgroundData();
+        data.image = mScreenBitmap;
+        data.finisher = finisher;
+        data.mActionsReadyListener = actionsReadyListener;
+        data.createDeleteAction = true;
+        if (mSaveInBgTask != null) {
+            mSaveInBgTask.cancel(false);
+        }
+
+        mNotificationsController.reset();
+        mNotificationsController.setImage(mScreenBitmap);
+        mNotificationsController.showSavingScreenshotNotification();
+
+        mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data).execute();
+    }
+
+    /**
+     * Takes a screenshot of the current display and shows an animation.
+     */
+    private void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible,
+            boolean navBarVisible, Rect crop) {
+        int rot = mDisplay.getRotation();
+        int width = crop.width();
+        int height = crop.height();
+
+        // Take the screenshot
+        mScreenBitmap = SurfaceControl.screenshot(crop, width, height, rot);
+        if (mScreenBitmap == null) {
+            mNotificationsController.notifyScreenshotError(
+                    R.string.screenshot_failed_to_capture_text);
+            finisher.accept(null);
+            return;
+        }
+
+        // Optimizations
+        mScreenBitmap.setHasAlpha(false);
+        mScreenBitmap.prepareToDraw();
+
+        // Start the post-screenshot animation
+        startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
+                statusBarVisible, navBarVisible);
+    }
+
+    void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible, boolean navBarVisible) {
+        mDisplay.getRealMetrics(mDisplayMetrics);
+        takeScreenshot(finisher, statusBarVisible, navBarVisible,
+                new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
+    }
+
+    /**
+     * Displays a screenshot selector
+     */
+    void takeScreenshotPartial(final Consumer<Uri> finisher, final boolean statusBarVisible,
+            final boolean navBarVisible) {
+        mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
+        mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() {
+            @Override
+            public boolean onTouch(View v, MotionEvent event) {
+                ScreenshotSelectorView view = (ScreenshotSelectorView) v;
+                switch (event.getAction()) {
+                    case MotionEvent.ACTION_DOWN:
+                        view.startSelection((int) event.getX(), (int) event.getY());
+                        return true;
+                    case MotionEvent.ACTION_MOVE:
+                        view.updateSelection((int) event.getX(), (int) event.getY());
+                        return true;
+                    case MotionEvent.ACTION_UP:
+                        view.setVisibility(View.GONE);
+                        mWindowManager.removeView(mScreenshotLayout);
+                        final Rect rect = view.getSelectionRect();
+                        if (rect != null) {
+                            if (rect.width() != 0 && rect.height() != 0) {
+                                // Need mScreenshotLayout to handle it after the view disappears
+                                mScreenshotLayout.post(() -> takeScreenshot(
+                                        finisher, statusBarVisible, navBarVisible, rect));
+                            }
+                        }
+
+                        view.stopSelection();
+                        return true;
+                }
+
+                return false;
+            }
+        });
+        mScreenshotLayout.post(() -> {
+            mScreenshotSelectorView.setVisibility(View.VISIBLE);
+            mScreenshotSelectorView.requestFocus();
+        });
+    }
+
+    /**
+     * Cancels screenshot request
+     */
+    void stopScreenshot() {
+        // If the selector layer still presents on screen, we remove it and resets its state.
+        if (mScreenshotSelectorView.getSelectionRect() != null) {
+            mWindowManager.removeView(mScreenshotLayout);
+            mScreenshotSelectorView.stopSelection();
+        }
+    }
+
+    /**
+     * Clears current screenshot
+     */
+    private void clearScreenshot() {
+        if (mScreenshotLayout.isAttachedToWindow()) {
+            mWindowManager.removeView(mScreenshotLayout);
+        }
+
+        // Clear any references to the bitmap
+        mScreenBitmap = null;
+        mScreenshotView.setImageBitmap(null);
+        mBackgroundView.setVisibility(View.GONE);
+        mScreenshotView.setVisibility(View.GONE);
+        mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
+    }
+
+    /**
+     * Starts the animation after taking the screenshot
+     */
+    private void startAnimation(final Consumer<Uri> finisher, int w, int h,
+            boolean statusBarVisible, boolean navBarVisible) {
+        // If power save is on, show a toast so there is some visual indication that a screenshot
+        // has been taken.
+        PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        if (powerManager.isPowerSaveMode()) {
+            Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show();
+        }
+
+        // Add the view for the animation
+        mScreenshotView.setImageBitmap(mScreenBitmap);
+        mScreenshotLayout.requestFocus();
+
+        // Setup the animation with the screenshot just taken
+        if (mScreenshotAnimation != null) {
+            if (mScreenshotAnimation.isStarted()) {
+                mScreenshotAnimation.end();
+            }
+            mScreenshotAnimation.removeAllListeners();
+        }
+
+        mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
+        ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation();
+        ValueAnimator screenshotFadeOutAnim =
+                createScreenshotDropOutAnimation(w, h, statusBarVisible, navBarVisible);
+        mScreenshotAnimation = new AnimatorSet();
+        mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim);
+        mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                // Save the screenshot once we have a bit of time now
+                saveScreenshotInWorkerThread(finisher, new GlobalScreenshot.ActionsReadyListener() {
+                    @Override
+                    void onActionsReady(Uri uri, List<Notification.Action> smartActions,
+                            List<Notification.Action> actions) {
+                        if (uri == null) {
+                            mNotificationsController.notifyScreenshotError(
+                                    R.string.screenshot_failed_to_capture_text);
+                        } else {
+                            mNotificationsController
+                                    .showScreenshotActionsNotification(uri, smartActions, actions);
+                        }
+                    }
+                });
+                clearScreenshot();
+            }
+        });
+        mScreenshotLayout.post(() -> {
+            // Play the shutter sound to notify that we've taken a screenshot
+            mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
+
+            mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+            mScreenshotView.buildLayer();
+            mScreenshotAnimation.start();
+        });
+    }
+
+    private ValueAnimator createScreenshotDropInAnimation() {
+        final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION)
+                / SCREENSHOT_DROP_IN_DURATION);
+        final float flashDurationPct = 2f * flashPeakDurationPct;
+        final Interpolator flashAlphaInterpolator = new Interpolator() {
+            @Override
+            public float getInterpolation(float x) {
+                // Flash the flash view in and out quickly
+                if (x <= flashDurationPct) {
+                    return (float) Math.sin(Math.PI * (x / flashDurationPct));
+                }
+                return 0;
+            }
+        };
+        final Interpolator scaleInterpolator = new Interpolator() {
+            @Override
+            public float getInterpolation(float x) {
+                // We start scaling when the flash is at it's peak
+                if (x < flashPeakDurationPct) {
+                    return 0;
+                }
+                return (x - flashDurationPct) / (1f - flashDurationPct);
+            }
+        };
+
+        Resources r = mContext.getResources();
+        if ((r.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)
+                == Configuration.UI_MODE_NIGHT_YES) {
+            mScreenshotView.getBackground().setTint(Color.BLACK);
+        } else {
+            mScreenshotView.getBackground().setTintList(null);
+        }
+
+        ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
+        anim.setDuration(SCREENSHOT_DROP_IN_DURATION);
+        anim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                mBackgroundView.setAlpha(0f);
+                mBackgroundView.setVisibility(View.VISIBLE);
+                mScreenshotView.setAlpha(0f);
+                mScreenshotView.setTranslationX(0f);
+                mScreenshotView.setTranslationY(0f);
+                mScreenshotView.setScaleX(SCREENSHOT_SCALE + mBgPaddingScale);
+                mScreenshotView.setScaleY(SCREENSHOT_SCALE + mBgPaddingScale);
+                mScreenshotView.setVisibility(View.VISIBLE);
+                mScreenshotFlash.setAlpha(0f);
+                mScreenshotFlash.setVisibility(View.VISIBLE);
+            }
+
+            @Override
+            public void onAnimationEnd(android.animation.Animator animation) {
+                mScreenshotFlash.setVisibility(View.GONE);
+            }
+        });
+        anim.addUpdateListener(new AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                float t = (Float) animation.getAnimatedValue();
+                float scaleT = (SCREENSHOT_SCALE + mBgPaddingScale)
+                        - scaleInterpolator.getInterpolation(t)
+                        * (SCREENSHOT_SCALE - SCREENSHOT_DROP_IN_MIN_SCALE);
+                mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA);
+                mScreenshotView.setAlpha(t);
+                mScreenshotView.setScaleX(scaleT);
+                mScreenshotView.setScaleY(scaleT);
+                mScreenshotFlash.setAlpha(flashAlphaInterpolator.getInterpolation(t));
+            }
+        });
+        return anim;
+    }
+
+    private ValueAnimator createScreenshotDropOutAnimation(int w, int h, boolean statusBarVisible,
+            boolean navBarVisible) {
+        ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
+        anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);
+        anim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mBackgroundView.setVisibility(View.GONE);
+                mScreenshotView.setVisibility(View.GONE);
+                mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
+            }
+        });
+
+        if (!statusBarVisible || !navBarVisible) {
+            // There is no status bar/nav bar, so just fade the screenshot away in place
+            anim.setDuration(SCREENSHOT_FAST_DROP_OUT_DURATION);
+            anim.addUpdateListener(new AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    float t = (Float) animation.getAnimatedValue();
+                    float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
+                            - t * (SCREENSHOT_DROP_IN_MIN_SCALE
+                            - SCREENSHOT_FAST_DROP_OUT_MIN_SCALE);
+                    mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
+                    mScreenshotView.setAlpha(1f - t);
+                    mScreenshotView.setScaleX(scaleT);
+                    mScreenshotView.setScaleY(scaleT);
+                }
+            });
+        } else {
+            // In the case where there is a status bar, animate to the origin of the bar (top-left)
+            final float scaleDurationPct = (float) SCREENSHOT_DROP_OUT_SCALE_DURATION
+                    / SCREENSHOT_DROP_OUT_DURATION;
+            final Interpolator scaleInterpolator = new Interpolator() {
+                @Override
+                public float getInterpolation(float x) {
+                    if (x < scaleDurationPct) {
+                        // Decelerate, and scale the input accordingly
+                        return (float) (1f - Math.pow(1f - (x / scaleDurationPct), 2f));
+                    }
+                    return 1f;
+                }
+            };
+
+            // Determine the bounds of how to scale
+            float halfScreenWidth = (w - 2f * mBgPadding) / 2f;
+            float halfScreenHeight = (h - 2f * mBgPadding) / 2f;
+            final float offsetPct = SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET;
+            final PointF finalPos = new PointF(
+                    -halfScreenWidth
+                            + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenWidth,
+                    -halfScreenHeight
+                            + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenHeight);
+
+            // Animate the screenshot to the status bar
+            anim.setDuration(SCREENSHOT_DROP_OUT_DURATION);
+            anim.addUpdateListener(new AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    float t = (Float) animation.getAnimatedValue();
+                    float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
+                            - scaleInterpolator.getInterpolation(t)
+                            * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_DROP_OUT_MIN_SCALE);
+                    mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
+                    mScreenshotView.setAlpha(1f - scaleInterpolator.getInterpolation(t));
+                    mScreenshotView.setScaleX(scaleT);
+                    mScreenshotView.setScaleY(scaleT);
+                    mScreenshotView.setTranslationX(t * finalPos.x);
+                    mScreenshotView.setTranslationY(t * finalPos.y);
+                }
+            });
+        }
+        return anim;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 6bad15c..e6082dd 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -65,7 +65,6 @@
 import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
 import java.util.Objects;
@@ -90,6 +89,7 @@
     private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
     private final String mScreenshotId;
     private final boolean mSmartActionsEnabled;
+    private final boolean mCreateDeleteAction;
     private final Random mRandom = new Random();
 
     SaveImageInBackgroundTask(Context context, GlobalScreenshot.SaveImageInBackgroundData data) {
@@ -102,6 +102,8 @@
         mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate);
         mScreenshotId = String.format(SCREENSHOT_ID_TEMPLATE, UUID.randomUUID());
 
+        mCreateDeleteAction = data.createDeleteAction;
+
         // Initialize screenshot notification smart actions provider.
         mSmartActionsEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
                 SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, false);
@@ -196,8 +198,20 @@
             }
 
             List<Notification.Action> actions =
-                    populateNotificationActions(mContext, r, uri, smartActionsFuture);
-            mParams.mActionsReadyListener.onActionsReady(uri, actions);
+                    populateNotificationActions(mContext, r, uri);
+            List<Notification.Action> smartActions = new ArrayList<>();
+            if (mSmartActionsEnabled) {
+                int timeoutMs = DeviceConfig.getInt(
+                        DeviceConfig.NAMESPACE_SYSTEMUI,
+                        SystemUiDeviceConfigFlags.SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS,
+                        1000);
+                smartActions.addAll(buildSmartActions(
+                        ScreenshotSmartActions.getSmartActions(
+                                mScreenshotId, smartActionsFuture, timeoutMs,
+                                mSmartActionsProvider),
+                        mContext));
+            }
+            mParams.mActionsReadyListener.onActionsReady(uri, smartActions, actions);
             mParams.imageUri = uri;
             mParams.image = null;
             mParams.errorMsgResId = 0;
@@ -207,7 +221,7 @@
             Slog.e(TAG, "unable to save screenshot", e);
             mParams.clearImage();
             mParams.errorMsgResId = R.string.screenshot_failed_to_save_text;
-            mParams.mActionsReadyListener.onActionsReady(null, null);
+            mParams.mActionsReadyListener.onActionsReady(null, null, null);
         }
 
         // Recycle the bitmap data
@@ -228,14 +242,13 @@
         // If we are cancelled while the task is running in the background, we may get null
         // params. The finisher is expected to always be called back, so just use the baked-in
         // params from the ctor in any case.
-        mParams.mActionsReadyListener.onActionsReady(null, null);
+        mParams.mActionsReadyListener.onActionsReady(null, null, null);
         mParams.finisher.accept(null);
         mParams.clearImage();
     }
 
     @VisibleForTesting
-    List<Notification.Action> populateNotificationActions(Context context, Resources r, Uri uri,
-            CompletableFuture<List<Notification.Action>> smartActionsFuture) {
+    List<Notification.Action> populateNotificationActions(Context context, Resources r, Uri uri) {
         // Note: Both the share and edit actions are proxied through ActionProxyReceiver in
         // order to do some common work like dismissing the keyguard and sending
         // closeSystemWindows
@@ -260,6 +273,8 @@
         // by setting the (otherwise unused) request code to the current user id.
         int requestCode = context.getUserId();
 
+        ArrayList<Notification.Action> actions = new ArrayList<>();
+
         PendingIntent chooserAction = PendingIntent.getBroadcast(context, requestCode,
                 new Intent(context, GlobalScreenshot.TargetChosenReceiver.class),
                 PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
@@ -282,6 +297,7 @@
         Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
                 Icon.createWithResource(r, R.drawable.ic_screenshot_share),
                 r.getString(com.android.internal.R.string.share), shareAction);
+        actions.add(shareActionBuilder.build());
 
         // Create an edit intent, if a specific package is provided as the editor, then
         // launch that directly
@@ -309,31 +325,21 @@
         Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
                 Icon.createWithResource(r, R.drawable.ic_screenshot_edit),
                 r.getString(com.android.internal.R.string.screenshot_edit), editAction);
+        actions.add(editActionBuilder.build());
 
-        // Create a delete action for the notification
-        PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode,
-                new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
-                        .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString())
-                        .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
-                        .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
-                                mSmartActionsEnabled),
-                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
-        Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
-                Icon.createWithResource(r, R.drawable.ic_screenshot_delete),
-                r.getString(com.android.internal.R.string.delete), deleteAction);
-
-        ArrayList<Notification.Action> actions = new ArrayList<>(
-                Arrays.asList(shareActionBuilder.build(), editActionBuilder.build(),
-                        deleteActionBuilder.build()));
-        if (mSmartActionsEnabled) {
-            int timeoutMs = DeviceConfig.getInt(
-                    DeviceConfig.NAMESPACE_SYSTEMUI,
-                    SystemUiDeviceConfigFlags.SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS,
-                    1000);
-            actions.addAll(buildSmartActions(
-                    ScreenshotSmartActions.getSmartActions(
-                            mScreenshotId, smartActionsFuture, timeoutMs, mSmartActionsProvider),
-                    context));
+        if (mCreateDeleteAction) {
+            // Create a delete action for the notification
+            PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode,
+                    new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
+                            .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString())
+                            .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
+                            .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
+                                    mSmartActionsEnabled),
+                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
+            Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
+                    Icon.createWithResource(r, R.drawable.ic_screenshot_delete),
+                    r.getString(com.android.internal.R.string.delete), deleteAction);
+            actions.add(deleteActionBuilder.build());
         }
         return actions;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
new file mode 100644
index 0000000..44b20e5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
@@ -0,0 +1,88 @@
+/*
+ * 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.screenshot;
+
+import android.annotation.ColorInt;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.graphics.drawable.Icon;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+
+/**
+ * View for a chip with an icon and text.
+ */
+public class ScreenshotActionChip extends LinearLayout {
+
+    private static final String TAG = "ScreenshotActionChip";
+
+    private ImageView mIcon;
+    private TextView mText;
+    private @ColorInt int mIconColor;
+
+    public ScreenshotActionChip(Context context) {
+        this(context, null);
+    }
+
+    public ScreenshotActionChip(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ScreenshotActionChip(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public ScreenshotActionChip(
+            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+
+        mIconColor = context.getColor(R.color.global_screenshot_button_icon);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        mIcon = findViewById(R.id.screenshot_action_chip_icon);
+        mText = findViewById(R.id.screenshot_action_chip_text);
+    }
+
+    void setIcon(Icon icon, boolean tint) {
+        if (tint) {
+            icon.setTint(mIconColor);
+        }
+        mIcon.setImageIcon(icon);
+    }
+
+    void setText(CharSequence text) {
+        mText.setText(text);
+    }
+
+    void setPendingIntent(PendingIntent intent, Runnable finisher) {
+        setOnClickListener(v -> {
+            try {
+                intent.send();
+                finisher.run();
+            } catch (PendingIntent.CanceledException e) {
+                Log.e(TAG, "Intent cancelled", e);
+            }
+        });
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
index 42fca94..811a8d9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
@@ -190,10 +190,14 @@
      */
     public void showScreenshotActionsNotification(
             Uri imageUri,
+            List<Notification.Action> smartActions,
             List<Notification.Action> actions) {
         for (Notification.Action action : actions) {
             mNotificationBuilder.addAction(action);
         }
+        for (Notification.Action smartAction : smartActions) {
+            mNotificationBuilder.addAction(smartAction);
+        }
 
         // Create the intent to show the screenshot in gallery
         Intent launchIntent = new Intent(Intent.ACTION_VIEW);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 29b96a9..9570b5a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -16,15 +16,21 @@
 
 package com.android.systemui.screenshot;
 
+import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
+
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_CORNER_FLOW;
+
 import android.app.Service;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Message;
 import android.os.Messenger;
 import android.os.RemoteException;
 import android.os.UserManager;
+import android.provider.DeviceConfig;
 import android.util.Log;
 import android.view.WindowManager;
 
@@ -36,9 +42,10 @@
     private static final String TAG = "TakeScreenshotService";
 
     private final GlobalScreenshot mScreenshot;
+    private final GlobalScreenshotLegacy mScreenshotLegacy;
     private final UserManager mUserManager;
 
-    private Handler mHandler = new Handler() {
+    private Handler mHandler = new Handler(Looper.myLooper()) {
         @Override
         public void handleMessage(Message msg) {
             final Messenger callback = msg.replyTo;
@@ -59,12 +66,24 @@
                 return;
             }
 
+            // TODO (mkephart): clean up once notifications flow is fully deprecated
+            boolean useCornerFlow = DeviceConfig.getBoolean(
+                    NAMESPACE_SYSTEMUI, SCREENSHOT_CORNER_FLOW, false);
             switch (msg.what) {
                 case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
-                    mScreenshot.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0);
+                    if (useCornerFlow) {
+                        mScreenshot.takeScreenshot(finisher);
+                    } else {
+                        mScreenshotLegacy.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0);
+                    }
                     break;
                 case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
-                    mScreenshot.takeScreenshotPartial(finisher, msg.arg1 > 0, msg.arg2 > 0);
+                    if (useCornerFlow) {
+                        mScreenshot.takeScreenshotPartial(finisher);
+                    } else {
+                        mScreenshotLegacy.takeScreenshotPartial(
+                                finisher, msg.arg1 > 0, msg.arg2 > 0);
+                    }
                     break;
                 default:
                     Log.d(TAG, "Invalid screenshot option: " + msg.what);
@@ -73,8 +92,10 @@
     };
 
     @Inject
-    public TakeScreenshotService(GlobalScreenshot globalScreenshot, UserManager userManager) {
+    public TakeScreenshotService(GlobalScreenshot globalScreenshot,
+            GlobalScreenshotLegacy globalScreenshotLegacy, UserManager userManager) {
         mScreenshot = globalScreenshot;
+        mScreenshotLegacy = globalScreenshotLegacy;
         mUserManager = userManager;
     }
 
@@ -86,6 +107,8 @@
     @Override
     public boolean onUnbind(Intent intent) {
         if (mScreenshot != null) mScreenshot.stopScreenshot();
+        // TODO (mkephart) remove once notifications flow is fully deprecated
+        if (mScreenshotLegacy != null) mScreenshotLegacy.stopScreenshot();
         return true;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
index 077d260..9599d77 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
@@ -98,7 +98,8 @@
             if (!mReceiverRegistered) {
                 mCurrentUserId = ActivityManager.getCurrentUser();
                 IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
-                mBroadcastDispatcher.registerReceiver(this, filter, null, UserHandle.ALL);
+                mBroadcastDispatcher.registerReceiver(this, filter, null,
+                        UserHandle.ALL);
                 mReceiverRegistered = true;
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 325af24..9fe6e84 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -332,6 +332,7 @@
             insets = state.calculateInsets(state.getDisplayFrame(), insets.isRound(),
                     insets.shouldAlwaysConsumeSystemBars(), insets.getDisplayCutout(),
                     null /* legacyContentInsets */, null /* legacyStableInsets */,
+                    0 /* legacySystemUiFlags */,
                     SOFT_INPUT_ADJUST_NOTHING, null /* typeSideMap */);
         }
         if (mStableInsets.left != insets.getStableInsetLeft()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
new file mode 100644
index 0000000..ab69d47
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.statusbar
+
+import android.app.ActivityManager
+import android.content.res.Resources
+import android.os.SystemProperties
+import android.util.MathUtils
+import android.view.SurfaceControl
+import android.view.ViewRootImpl
+import androidx.annotation.VisibleForTesting
+import com.android.internal.util.IndentingPrintWriter
+import com.android.systemui.DumpController
+import com.android.systemui.Dumpable
+import com.android.systemui.R
+import com.android.systemui.dagger.qualifiers.Main
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+open class BlurUtils @Inject constructor(
+    @Main private val resources: Resources,
+    val dumpController: DumpController
+) : Dumpable {
+    private val minBlurRadius = resources.getDimensionPixelSize(R.dimen.min_window_blur_radius)
+    private val maxBlurRadius = resources.getDimensionPixelSize(R.dimen.max_window_blur_radius)
+    private val blurSysProp = SystemProperties
+            .getBoolean("ro.surface_flinger.supports_background_blur", false)
+
+    init {
+        dumpController.registerDumpable(this)
+    }
+
+    /**
+     * Translates a ratio from 0 to 1 to a blur radius in pixels.
+     */
+    fun radiusForRatio(ratio: Float): Int {
+        if (ratio == 0f) {
+            return 0
+        }
+        return MathUtils.lerp(minBlurRadius.toFloat(), maxBlurRadius.toFloat(), ratio).toInt()
+    }
+
+    /**
+     * Applies background blurs to a {@link ViewRootImpl}.
+     *
+     * @param viewRootImpl The window root.
+     * @param radius blur radius in pixels.
+     */
+    fun applyBlur(viewRootImpl: ViewRootImpl?, radius: Int) {
+        if (viewRootImpl == null || !viewRootImpl.surfaceControl.isValid ||
+                !supportsBlursOnWindows()) {
+            return
+        }
+        createTransaction().use {
+            it.setBackgroundBlurRadius(viewRootImpl.surfaceControl, radius)
+            it.apply()
+        }
+    }
+
+    @VisibleForTesting
+    open fun createTransaction(): SurfaceControl.Transaction {
+        return SurfaceControl.Transaction()
+    }
+
+    /**
+     * If this device can render blurs.
+     *
+     * @see android.view.SurfaceControl.Transaction#setBackgroundBlurRadius(SurfaceControl, int)
+     * @return {@code true} when supported.
+     */
+    open fun supportsBlursOnWindows(): Boolean {
+        return blurSysProp && ActivityManager.isHighEndGfx()
+    }
+
+    override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+        IndentingPrintWriter(pw, "  ").use {
+            it.println("BlurUtils:")
+            it.increaseIndent()
+            it.println("minBlurRadius: $minBlurRadius")
+            it.println("maxBlurRadius: $maxBlurRadius")
+            it.println("blurSysProp: $blurSysProp")
+            it.println("supportsBlursOnWindows: ${supportsBlursOnWindows()}")
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
index 525b5b7..12749fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
@@ -368,13 +368,14 @@
     public static class Builder {
         private final DisplayMetrics mDisplayMetrics;
         float mMaxLengthSeconds;
-        float mSpeedUpFactor = 0.0f;
-        float mX2 = -1.0f;
-        float mY2 = 1.0f;
+        float mSpeedUpFactor;
+        float mX2;
+        float mY2;
 
         @Inject
         public Builder(DisplayMetrics displayMetrics) {
             mDisplayMetrics = displayMetrics;
+            reset();
         }
 
         public Builder setMaxLengthSeconds(float maxLengthSeconds) {
@@ -397,6 +398,15 @@
             return this;
         }
 
+        public Builder reset() {
+            mMaxLengthSeconds = 0;
+            mSpeedUpFactor = 0.0f;
+            mX2 = -1.0f;
+            mY2 = 1.0f;
+
+            return this;
+        }
+
         public FlingAnimationUtils build() {
             return new FlingAnimationUtils(mDisplayMetrics, mMaxLengthSeconds, mSpeedUpFactor,
                     mX2, mY2);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 8cc45f2..4d6764e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -195,6 +195,7 @@
                     new ArrayList<>(),
                     new ArrayList<>(),
                     false,
+                    false,
                     false
             );
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 246b0f0e..a457266 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -59,10 +59,10 @@
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.LockscreenWallpaper;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.ScrimState;
 import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.phone.StatusBarWindowController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.io.FileDescriptor;
@@ -105,7 +105,7 @@
     private final NotificationEntryManager mEntryManager;
 
     @Nullable
-    private Lazy<StatusBarWindowController> mStatusBarWindowController;
+    private Lazy<NotificationShadeWindowController> mStatusBarWindowController;
 
     @Nullable
     private BiometricUnlockController mBiometricUnlockController;
@@ -180,7 +180,7 @@
     public NotificationMediaManager(
             Context context,
             Lazy<StatusBar> statusBarLazy,
-            Lazy<StatusBarWindowController> statusBarWindowController,
+            Lazy<NotificationShadeWindowController> statusBarWindowController,
             NotificationEntryManager notificationEntryManager,
             MediaArtworkProcessor mediaArtworkProcessor,
             KeyguardBypassController keyguardBypassController) {
@@ -525,7 +525,7 @@
             }
         }
 
-        StatusBarWindowController windowController = mStatusBarWindowController.get();
+        NotificationShadeWindowController windowController = mStatusBarWindowController.get();
         boolean hideBecauseOccluded = mStatusBarLazy.get().isOccluded();
 
         final boolean hasArtwork = artworkDrawable != null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 43d0399..f3783c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -25,8 +25,10 @@
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.RemoteInput;
+import android.app.RemoteInputHistoryItem;
 import android.content.Context;
 import android.content.Intent;
+import android.net.Uri;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -345,7 +347,8 @@
         });
         mSmartReplyController.setCallback((entry, reply) -> {
             StatusBarNotification newSbn =
-                    rebuildNotificationWithRemoteInput(entry, reply, true /* showSpinner */);
+                    rebuildNotificationWithRemoteInput(entry, reply, true /* showSpinner */,
+                            null /* mimeType */, null /* uri */);
             mEntryManager.updateNotification(newSbn, null /* ranking */);
         });
     }
@@ -527,28 +530,36 @@
     StatusBarNotification rebuildNotificationForCanceledSmartReplies(
             NotificationEntry entry) {
         return rebuildNotificationWithRemoteInput(entry, null /* remoteInputTest */,
-                false /* showSpinner */);
+                false /* showSpinner */, null /* mimeType */, null /* uri */);
     }
 
     @VisibleForTesting
     StatusBarNotification rebuildNotificationWithRemoteInput(NotificationEntry entry,
-            CharSequence remoteInputText, boolean showSpinner) {
+            CharSequence remoteInputText, boolean showSpinner, String mimeType, Uri uri) {
         StatusBarNotification sbn = entry.getSbn();
 
         Notification.Builder b = Notification.Builder
                 .recoverBuilder(mContext, sbn.getNotification().clone());
-        if (remoteInputText != null) {
-            CharSequence[] oldHistory = sbn.getNotification().extras
-                    .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY);
-            CharSequence[] newHistory;
-            if (oldHistory == null) {
-                newHistory = new CharSequence[1];
+        if (remoteInputText != null || uri != null) {
+            RemoteInputHistoryItem[] oldHistoryItems = (RemoteInputHistoryItem[])
+                    sbn.getNotification().extras.getParcelableArray(
+                            Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+            RemoteInputHistoryItem[] newHistoryItems;
+
+            if (oldHistoryItems == null) {
+                newHistoryItems = new RemoteInputHistoryItem[1];
             } else {
-                newHistory = new CharSequence[oldHistory.length + 1];
-                System.arraycopy(oldHistory, 0, newHistory, 1, oldHistory.length);
+                newHistoryItems = new RemoteInputHistoryItem[oldHistoryItems.length + 1];
+                System.arraycopy(oldHistoryItems, 0, newHistoryItems, 1, oldHistoryItems.length);
             }
-            newHistory[0] = String.valueOf(remoteInputText);
-            b.setRemoteInputHistory(newHistory);
+            RemoteInputHistoryItem newItem;
+            if (uri != null) {
+                newItem = new RemoteInputHistoryItem(mimeType, uri, remoteInputText);
+            } else {
+                newItem = new RemoteInputHistoryItem(remoteInputText);
+            }
+            newHistoryItems[0] = newItem;
+            b.setRemoteInputHistory(newHistoryItems);
         }
         b.setShowRemoteInputSpinner(showSpinner);
         b.setHideSmartReplies(true);
@@ -584,7 +595,15 @@
 
     public void bindRow(ExpandableNotificationRow row) {
         row.setRemoteInputController(mRemoteInputController);
-        row.setRemoteViewClickHandler(mOnClickHandler);
+    }
+
+    /**
+     * Return on-click handler for notification remote views
+     *
+     * @return on-click handler
+     */
+    public RemoteViews.OnClickHandler getRemoteViewsOnClickHandler() {
+        return mOnClickHandler;
     }
 
     @VisibleForTesting
@@ -623,8 +642,11 @@
                 if (TextUtils.isEmpty(remoteInputText)) {
                     remoteInputText = entry.remoteInputTextWhenReset;
                 }
+                String remoteInputMimeType = entry.remoteInputMimeType;
+                Uri remoteInputUri = entry.remoteInputUri;
                 StatusBarNotification newSbn = rebuildNotificationWithRemoteInput(entry,
-                        remoteInputText, false /* showSpinner */);
+                        remoteInputText, false /* showSpinner */, remoteInputMimeType,
+                        remoteInputUri);
                 entry.onRemoteInputInserted();
 
                 if (newSbn == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java
index d1f6ebf..ec8dbea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java
@@ -18,6 +18,8 @@
 
 import android.content.Context;
 
+import com.android.systemui.statusbar.notification.row.NotificationRowModule;
+
 import javax.inject.Singleton;
 
 import dagger.Module;
@@ -26,7 +28,7 @@
 /**
  * Dagger Module providing common dependencies of StatusBar.
  */
-@Module
+@Module(includes = {NotificationRowModule.class})
 public class StatusBarDependenciesModule {
     /**
      * Provides our instance of CommandQueue which is considered optional.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index dc84b57..9c626f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -307,6 +307,7 @@
             case Icon.TYPE_RESOURCE:
                 return a.getResPackage().equals(b.getResPackage()) && a.getResId() == b.getResId();
             case Icon.TYPE_URI:
+            case Icon.TYPE_URI_ADAPTIVE_BITMAP:
                 return a.getUriString().equals(b.getUriString());
             default:
                 return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWindowBlurController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWindowBlurController.kt
new file mode 100644
index 0000000..2e72163
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWindowBlurController.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.statusbar
+
+import android.content.res.Resources
+import android.view.View
+import com.android.internal.util.IndentingPrintWriter
+import com.android.systemui.DumpController
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.statusbar.phone.PanelExpansionListener
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * Controller responsible for statusbar window blur.
+ */
+@Singleton
+class StatusBarWindowBlurController @Inject constructor(
+    @Main private val resources: Resources,
+    private val statusBarStateController: SysuiStatusBarStateController,
+    private val blurUtils: BlurUtils,
+    dumpController: DumpController
+) : PanelExpansionListener, Dumpable {
+
+    lateinit var root: View
+    private var blurRadius = 0
+
+    init {
+        dumpController.registerDumpable(this)
+    }
+
+    override fun onPanelExpansionChanged(expansion: Float, tracking: Boolean) {
+        val newBlur = if (statusBarStateController.state == StatusBarState.SHADE)
+            blurUtils.radiusForRatio(expansion)
+        else
+            0
+
+        if (blurRadius == newBlur) {
+            return
+        }
+        blurRadius = newBlur
+        blurUtils.applyBlur(root.viewRootImpl, blurRadius)
+    }
+
+    override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+        IndentingPrintWriter(pw, "  ").use {
+            it.println("StatusBarWindowBlurController:")
+            it.increaseIndent()
+            it.println("blurRadius: $blurRadius")
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java b/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
index 9398e6f..405f32a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
@@ -21,8 +21,10 @@
 import android.view.ViewGroup;
 
 import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
 import com.android.systemui.statusbar.phone.LockIcon;
 import com.android.systemui.statusbar.phone.NotificationPanelView;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
 import com.android.systemui.statusbar.phone.StatusBarWindowView;
 import com.android.systemui.util.InjectionInflationController;
 
@@ -30,35 +32,66 @@
 import javax.inject.Singleton;
 
 /**
- * Creates a single instance of super_status_bar that can be shared across various system ui
- * objects.
+ * Creates a single instance of super_status_bar and super_notification_shade that can be shared
+ * across various system ui objects.
  */
 @Singleton
 public class SuperStatusBarViewFactory {
 
     private final Context mContext;
     private final InjectionInflationController mInjectionInflationController;
+    private final NotificationRowComponent.Builder mNotificationRowComponentBuilder;
 
+    private NotificationShadeWindowView mNotificationShadeWindowView;
     private StatusBarWindowView mStatusBarWindowView;
     private NotificationShelf mNotificationShelf;
 
     @Inject
     public SuperStatusBarViewFactory(Context context,
-            InjectionInflationController injectionInflationController) {
+            InjectionInflationController injectionInflationController,
+            NotificationRowComponent.Builder notificationRowComponentBuilder) {
         mContext = context;
         mInjectionInflationController = injectionInflationController;
+        mNotificationRowComponentBuilder = notificationRowComponentBuilder;
     }
 
     /**
-     * Gets the inflated {@link StatusBarWindowView} from {@link R.layout#super_status_bar}. Returns
-     * a cached instance, if it has already been inflated.
+     * Gets the inflated {@link NotificationShadeWindowView} from
+     * {@link R.layout#super_notification_shade}.
+     * Returns a cached instance, if it has already been inflated.
+     */
+    public NotificationShadeWindowView getNotificationShadeWindowView() {
+        if (mNotificationShadeWindowView != null) {
+            return mNotificationShadeWindowView;
+        }
+
+        mNotificationShadeWindowView = (NotificationShadeWindowView)
+                mInjectionInflationController.injectable(
+                LayoutInflater.from(mContext)).inflate(R.layout.super_notification_shade,
+                /* root= */ null);
+        if (mNotificationShadeWindowView == null) {
+            throw new IllegalStateException(
+                    "R.layout.super_notification_shade could not be properly inflated");
+        }
+        return mNotificationShadeWindowView;
+    }
+
+    /** Gets the {@link LockIcon} inside of {@link R.layout#super_status_bar}. */
+    public LockIcon getLockIcon() {
+        return getNotificationShadeWindowView().findViewById(R.id.lock_icon);
+    }
+
+    /**
+     * Gets the inflated {@link StatusBarWindowView} from {@link R.layout#super_status_bar}.
+     * Returns a cached instance, if it has already been inflated.
      */
     public StatusBarWindowView getStatusBarWindowView() {
         if (mStatusBarWindowView != null) {
             return mStatusBarWindowView;
         }
 
-        mStatusBarWindowView = (StatusBarWindowView) mInjectionInflationController.injectable(
+        mStatusBarWindowView =
+                (StatusBarWindowView) mInjectionInflationController.injectable(
                 LayoutInflater.from(mContext)).inflate(R.layout.super_status_bar,
                 /* root= */ null);
         if (mStatusBarWindowView == null) {
@@ -68,11 +101,6 @@
         return mStatusBarWindowView;
     }
 
-    /** Gets the {@link LockIcon} inside of {@link R.layout#super_status_bar}. */
-    public LockIcon getLockIcon() {
-        return getStatusBarWindowView().findViewById(R.id.lock_icon);
-    }
-
     /**
      * Gets the inflated {@link NotificationShelf} from
      * {@link R.layout#status_bar_notification_shelf}.
@@ -90,6 +118,12 @@
         mNotificationShelf = (NotificationShelf) mInjectionInflationController.injectable(
                 LayoutInflater.from(mContext)).inflate(R.layout.status_bar_notification_shelf,
                 container, /* attachToRoot= */ false);
+
+        NotificationRowComponent component = mNotificationRowComponentBuilder
+                .activatableNotificationView(mNotificationShelf)
+                .build();
+        component.getActivatableNotificationViewController().init();
+
         if (mNotificationShelf == null) {
             throw new IllegalStateException(
                     "R.layout.status_bar_notification_shelf could not be properly inflated");
@@ -98,11 +132,11 @@
     }
 
     public NotificationPanelView getNotificationPanelView() {
-        StatusBarWindowView statusBarWindowView = getStatusBarWindowView();
-        if (statusBarWindowView == null) {
+        NotificationShadeWindowView notificationShadeWindowView = getNotificationShadeWindowView();
+        if (notificationShadeWindowView == null) {
             return null;
         }
 
-        return mStatusBarWindowView.findViewById(R.id.notification_panel);
+        return mNotificationShadeWindowView.findViewById(R.id.notification_panel);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
index 9b31234..5fc043a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
@@ -37,8 +37,8 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
-import com.android.systemui.statusbar.phone.NotificationPanelView;
-import com.android.systemui.statusbar.phone.StatusBarWindowViewController;
+import com.android.systemui.statusbar.phone.NotificationPanelViewController;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController;
 
 /**
  * A class that allows activities to be launched in a seamless way where the notification
@@ -53,10 +53,10 @@
             CollapsedStatusBarFragment.FADE_IN_DURATION - CollapsedStatusBarFragment.FADE_IN_DELAY
             - 16;
     private static final long LAUNCH_TIMEOUT = 500;
-    private final NotificationPanelView mNotificationPanel;
+    private final NotificationPanelViewController mNotificationPanel;
     private final NotificationListContainer mNotificationContainer;
     private final float mWindowCornerRadius;
-    private final StatusBarWindowViewController mStatusBarWindowViewController;
+    private final NotificationShadeWindowViewController mNotificationShadeWindowViewController;
     private Callback mCallback;
     private final Runnable mTimeoutRunnable = () -> {
         setAnimationPending(false);
@@ -67,21 +67,23 @@
     private boolean mIsLaunchForActivity;
 
     public ActivityLaunchAnimator(
-            StatusBarWindowViewController statusBarWindowViewController,
+            NotificationShadeWindowViewController notificationShadeWindowViewController,
             Callback callback,
-            NotificationPanelView notificationPanel,
+            NotificationPanelViewController notificationPanel,
             NotificationListContainer container) {
         mNotificationPanel = notificationPanel;
         mNotificationContainer = container;
-        mStatusBarWindowViewController = statusBarWindowViewController;
+        mNotificationShadeWindowViewController = notificationShadeWindowViewController;
         mCallback = callback;
         mWindowCornerRadius = ScreenDecorationsUtils
-                .getWindowCornerRadius(mStatusBarWindowViewController.getView().getResources());
+                .getWindowCornerRadius(mNotificationShadeWindowViewController.getView()
+                        .getResources());
     }
 
     public RemoteAnimationAdapter getLaunchAnimation(
             View sourceView, boolean occluded) {
-        if (!(sourceView instanceof ExpandableNotificationRow) || !mCallback.areLaunchAnimationsEnabled() || occluded) {
+        if (!(sourceView instanceof ExpandableNotificationRow)
+                || !mCallback.areLaunchAnimationsEnabled() || occluded) {
             return null;
         }
         AnimationRunner animationRunner = new AnimationRunner(
@@ -113,11 +115,12 @@
 
     private void setAnimationPending(boolean pending) {
         mAnimationPending = pending;
-        mStatusBarWindowViewController.setExpandAnimationPending(pending);
+        mNotificationShadeWindowViewController.setExpandAnimationPending(pending);
         if (pending) {
-            mStatusBarWindowViewController.getView().postDelayed(mTimeoutRunnable, LAUNCH_TIMEOUT);
+            mNotificationShadeWindowViewController.getView().postDelayed(mTimeoutRunnable,
+                    LAUNCH_TIMEOUT);
         } else {
-            mStatusBarWindowViewController.getView().removeCallbacks(mTimeoutRunnable);
+            mNotificationShadeWindowViewController.getView().removeCallbacks(mTimeoutRunnable);
         }
     }
 
@@ -247,7 +250,7 @@
         private void setExpandAnimationRunning(boolean running) {
             mNotificationPanel.setLaunchingNotification(running);
             mSourceNotification.setExpandAnimationRunning(running);
-            mStatusBarWindowViewController.setExpandAnimationRunning(running);
+            mNotificationShadeWindowViewController.setExpandAnimationRunning(running);
             mNotificationContainer.setExpandingNotification(running ? mSourceNotification : null);
             mAnimationRunning = running;
             if (!running) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index a2578ab..4a22831 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -32,7 +32,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLifetimeExtender;
@@ -44,7 +43,7 @@
 import com.android.systemui.statusbar.NotificationUiAdjustment;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
-import com.android.systemui.statusbar.notification.collection.NotificationRowBinder;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.logging.NotifEvent;
 import com.android.systemui.statusbar.notification.logging.NotifLog;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
@@ -68,6 +67,8 @@
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
+import dagger.Lazy;
+
 /**
  * NotificationEntryManager is responsible for the adding, removing, and updating of
  * {@link NotificationEntry}s. It also handles tasks such as their inflation and their interaction
@@ -126,8 +127,9 @@
             new ArrayMap<>();
 
     // Lazily retrieved dependencies
-    private NotificationRemoteInputManager mRemoteInputManager;
-    private NotificationRowBinder mNotificationRowBinder;
+    private final Lazy<NotificationRowBinder> mNotificationRowBinderLazy;
+    private final Lazy<NotificationRemoteInputManager> mRemoteInputManagerLazy;
+    private final LeakDetector mLeakDetector;
 
     private final KeyguardEnvironment mKeyguardEnvironment;
     private final NotificationGroupManager mGroupManager;
@@ -173,12 +175,18 @@
             NotificationGroupManager groupManager,
             NotificationRankingManager rankingManager,
             KeyguardEnvironment keyguardEnvironment,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            Lazy<NotificationRowBinder> notificationRowBinderLazy,
+            Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
+            LeakDetector leakDetector) {
         mNotifLog = notifLog;
         mGroupManager = groupManager;
         mRankingManager = rankingManager;
         mKeyguardEnvironment = keyguardEnvironment;
         mFeatureFlags = featureFlags;
+        mNotificationRowBinderLazy = notificationRowBinderLazy;
+        mRemoteInputManagerLazy = notificationRemoteInputManagerLazy;
+        mLeakDetector = leakDetector;
     }
 
     /** Once called, the NEM will start processing notification events from system server. */
@@ -204,20 +212,6 @@
         mRemoveInterceptor = interceptor;
     }
 
-    /**
-     * Our dependencies can have cyclic references, so some need to be lazy
-     */
-    private NotificationRemoteInputManager getRemoteInputManager() {
-        if (mRemoteInputManager == null) {
-            mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
-        }
-        return mRemoteInputManager;
-    }
-
-    public void setRowBinder(NotificationRowBinder notificationRowBinder) {
-        mNotificationRowBinder = notificationRowBinder;
-    }
-
     public void setUpWithPresenter(NotificationPresenter presenter,
             NotificationListContainer listContainer,
             HeadsUpManager headsUpManager) {
@@ -468,7 +462,7 @@
                 handleGroupSummaryRemoved(key);
                 removeVisibleNotification(key);
                 updateNotifications("removeNotificationInternal");
-                Dependency.get(LeakDetector.class).trackGarbage(entry);
+                mLeakDetector.trackGarbage(entry);
                 removedByUser |= entryDismissed;
 
                 mNotifLog.log(NotifEvent.NOTIF_REMOVED, entry.getSbn(),
@@ -507,8 +501,8 @@
                 boolean isForeground = (entry.getSbn().getNotification().flags
                         & Notification.FLAG_FOREGROUND_SERVICE) != 0;
                 boolean keepForReply =
-                        getRemoteInputManager().shouldKeepForRemoteInputHistory(childEntry)
-                        || getRemoteInputManager().shouldKeepForSmartReplyHistory(childEntry);
+                        mRemoteInputManagerLazy.get().shouldKeepForRemoteInputHistory(childEntry)
+                        || mRemoteInputManagerLazy.get().shouldKeepForSmartReplyHistory(childEntry);
                 if (isForeground || keepForReply) {
                     // the child is a foreground service notification which we can't remove or it's
                     // a child we're keeping around for reply!
@@ -536,12 +530,13 @@
 
         NotificationEntry entry = new NotificationEntry(notification, ranking);
 
-        Dependency.get(LeakDetector.class).trackInstance(entry);
+        mLeakDetector.trackInstance(entry);
 
         // Construct the expanded view.
         if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
-            requireBinder().inflateViews(entry, () -> performRemoveNotification(notification,
-                    REASON_CANCEL));
+            mNotificationRowBinderLazy.get()
+                    .inflateViews(entry, () -> performRemoveNotification(notification,
+                            REASON_CANCEL));
         }
 
         abortExistingInflation(key, "addNotification");
@@ -586,15 +581,16 @@
         }
 
         if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
-            requireBinder().inflateViews(entry, () -> performRemoveNotification(notification,
-                    REASON_CANCEL));
+            mNotificationRowBinderLazy.get()
+                    .inflateViews(entry, () -> performRemoveNotification(notification,
+                            REASON_CANCEL));
         }
 
         updateNotifications("updateNotificationInternal");
 
         if (DEBUG) {
             // Is this for you?
-            boolean isForCurrentUser = Dependency.get(KeyguardEnvironment.class)
+            boolean isForCurrentUser = mKeyguardEnvironment
                     .isNotificationForCurrentProfiles(notification);
             Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
         }
@@ -644,11 +640,12 @@
 
         // By comparing the old and new UI adjustments, reinflate the view accordingly.
         for (NotificationEntry entry : entries) {
-            requireBinder().onNotificationRankingUpdated(
-                    entry,
-                    oldImportances.get(entry.getKey()),
-                    oldAdjustments.get(entry.getKey()),
-                    NotificationUiAdjustment.extractFromNotificationEntry(entry));
+            mNotificationRowBinderLazy.get()
+                    .onNotificationRankingUpdated(
+                            entry,
+                            oldImportances.get(entry.getKey()),
+                            oldAdjustments.get(entry.getKey()),
+                            NotificationUiAdjustment.extractFromNotificationEntry(entry));
         }
 
         updateNotifications("updateNotificationRanking");
@@ -728,14 +725,6 @@
         }
     }
 
-    private NotificationRowBinder requireBinder() {
-        if (mNotificationRowBinder == null) {
-            throw new RuntimeException("You must initialize NotificationEntryManager by calling"
-                    + "setRowBinder() before using.");
-        }
-        return mNotificationRowBinder;
-    }
-
     /*
      * -----
      * Annexed from NotificationData below:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
index 66ed864..bbf2dde 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
@@ -188,7 +188,9 @@
             return false;
         }
 
-        if (entry.getBubbleMetadata() == null || entry.getBubbleMetadata().getIntent() == null) {
+        if (entry.getBubbleMetadata() == null
+                || (entry.getBubbleMetadata().getShortcutId() == null
+                    && entry.getBubbleMetadata().getBubbleIntent() == null)) {
             if (DEBUG) {
                 Log.d(TAG, "No bubble up: notification: " + sbn.getKey()
                         + " doesn't have valid metadata");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/CollectionReadyForBuildListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/CollectionReadyForBuildListener.java
deleted file mode 100644
index 87aaea0..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/CollectionReadyForBuildListener.java
+++ /dev/null
@@ -1,31 +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.statusbar.notification.collection;
-
-import java.util.Collection;
-
-/**
- * Interface for the class responsible for converting a NotifCollection into the final sorted,
- * filtered, and grouped list of currently visible notifications.
- */
-public interface CollectionReadyForBuildListener {
-    /**
-     * Called by the NotifCollection to indicate that something in the collection has changed and
-     * that the list builder should regenerate the list.
-     */
-    void onBuildList(Collection<NotificationEntry> entries);
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/DismissedByUserStats.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/DismissedByUserStats.java
deleted file mode 100644
index ecce6ea..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/DismissedByUserStats.java
+++ /dev/null
@@ -1,38 +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.statusbar.notification.collection;
-
-import android.service.notification.NotificationStats.DismissalSentiment;
-import android.service.notification.NotificationStats.DismissalSurface;
-
-import com.android.internal.statusbar.NotificationVisibility;
-
-/** Information that must be supplied when dismissing a notification on the behalf of the user. */
-public class DismissedByUserStats {
-    public final @DismissalSurface int dismissalSurface;
-    public final @DismissalSentiment int dismissalSentiment;
-    public final NotificationVisibility notificationVisibility;
-
-    public DismissedByUserStats(
-            @DismissalSurface int dismissalSurface,
-            @DismissalSentiment int dismissalSentiment,
-            NotificationVisibility notificationVisibility) {
-        this.dismissalSurface = dismissalSurface;
-        this.dismissalSentiment = dismissalSentiment;
-        this.notificationVisibility = notificationVisibility;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
index ec1efa5..b960b42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
@@ -24,7 +24,6 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
-import java.util.Objects;
 
 /**
  * Represents a set of grouped notifications. The final notification list is usually a mix of
@@ -58,22 +57,15 @@
 
     @VisibleForTesting
     public void setSummary(@Nullable NotificationEntry summary) {
-        if (!Objects.equals(mSummary, summary)) {
-            mSummary = summary;
-            onGroupingUpdated();
-        }
+        mSummary = summary;
     }
 
     void clearChildren() {
-        if (mChildren.size() != 0) {
-            mChildren.clear();
-            onGroupingUpdated();
-        }
+        mChildren.clear();
     }
 
     void addChild(NotificationEntry child) {
         mChildren.add(child);
-        onGroupingUpdated();
     }
 
     void sortChildren(Comparator<? super NotificationEntry> c) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
index e1268f6..eaa9d78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
@@ -16,44 +16,135 @@
 
 package com.android.systemui.statusbar.notification.collection;
 
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
-
+import java.util.Arrays;
 import java.util.List;
 
-
 /**
- * Utility class for dumping the results of a {@link NotifListBuilder} to a debug string.
+ * Utility class for dumping the results of a {@link ShadeListBuilder} to a debug string.
  */
 public class ListDumper {
 
-    /** See class description */
-    public static String dumpList(List<ListEntry> entries) {
+    /**
+     * Creates a debug string for a list of grouped notifications that will be printed
+     * in the order given in a tiered/tree structure.
+     * @param includeRecordKeeping whether to print out the Pluggables that caused the notification
+     *                             entry to be in its current state (ie: filter, lifeExtender)
+     */
+    public static String dumpTree(
+            List<ListEntry> entries,
+            boolean includeRecordKeeping,
+            String indent) {
         StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < entries.size(); i++) {
-            ListEntry entry = entries.get(i);
-            dumpEntry(entry, Integer.toString(i), "", sb);
+        final String childEntryIndent = indent + INDENT;
+        for (int topEntryIndex = 0; topEntryIndex < entries.size(); topEntryIndex++) {
+            ListEntry entry = entries.get(topEntryIndex);
+            dumpEntry(entry,
+                    Integer.toString(topEntryIndex),
+                    indent,
+                    sb,
+                    true,
+                    includeRecordKeeping);
             if (entry instanceof GroupEntry) {
                 GroupEntry ge = (GroupEntry) entry;
-                for (int j = 0; j < ge.getChildren().size(); j++) {
-                    dumpEntry(
-                            ge.getChildren().get(j),
-                            Integer.toString(j),
-                            INDENT,
-                            sb);
+                List<NotificationEntry> children = ge.getChildren();
+                for (int childIndex = 0;  childIndex < children.size(); childIndex++) {
+                    dumpEntry(children.get(childIndex),
+                            Integer.toString(topEntryIndex) + "." + Integer.toString(childIndex),
+                            childEntryIndent,
+                            sb,
+                            true,
+                            includeRecordKeeping);
                 }
             }
         }
         return sb.toString();
     }
 
+    /**
+     * Creates a debug string for a flat list of notifications
+     * @param includeRecordKeeping whether to print out the Pluggables that caused the notification
+     *                             entry to be in its current state (ie: filter, lifeExtender)
+     */
+    public static String dumpList(
+            List<NotificationEntry> entries,
+            boolean includeRecordKeeping,
+            String indent) {
+        StringBuilder sb = new StringBuilder();
+        for (int j = 0; j < entries.size(); j++) {
+            dumpEntry(
+                    entries.get(j),
+                    Integer.toString(j),
+                    indent,
+                    sb,
+                    false,
+                    includeRecordKeeping);
+        }
+        return sb.toString();
+    }
+
     private static void dumpEntry(
-            ListEntry entry, String index, String indent, StringBuilder sb) {
+            ListEntry entry,
+            String index,
+            String indent,
+            StringBuilder sb,
+            boolean includeParent,
+            boolean includeRecordKeeping) {
         sb.append(indent)
                 .append("[").append(index).append("] ")
-                .append(entry.getKey())
-                .append(" (parent=")
-                .append(entry.getParent() != null ? entry.getParent().getKey() : null)
-                .append(")\n");
+                .append(entry.getKey());
+
+        if (includeParent) {
+            sb.append(" (parent=")
+                    .append(entry.getParent() != null ? entry.getParent().getKey() : null)
+                    .append(")");
+        }
+
+        if (entry.mNotifSection != null) {
+            sb.append(" sectionIndex=")
+                    .append(entry.getSection())
+                    .append(" sectionName=")
+                    .append(entry.mNotifSection.getName());
+        }
+
+        if (includeRecordKeeping) {
+            NotificationEntry notifEntry = entry.getRepresentativeEntry();
+            StringBuilder rksb = new StringBuilder();
+
+            if (!notifEntry.mLifetimeExtenders.isEmpty()) {
+                String[] lifetimeExtenderNames = new String[notifEntry.mLifetimeExtenders.size()];
+                for (int i = 0; i < lifetimeExtenderNames.length; i++) {
+                    lifetimeExtenderNames[i] = notifEntry.mLifetimeExtenders.get(i).getName();
+                }
+                rksb.append("lifetimeExtenders=")
+                        .append(Arrays.toString(lifetimeExtenderNames))
+                        .append(" ");
+            }
+
+            if (notifEntry.mExcludingFilter != null) {
+                rksb.append("filter=")
+                        .append(notifEntry.mExcludingFilter)
+                        .append(" ");
+            }
+
+            if (notifEntry.mNotifPromoter != null) {
+                rksb.append("promoter=")
+                        .append(notifEntry.mNotifPromoter)
+                        .append(" ");
+            }
+
+            if (notifEntry.hasInflationError()) {
+                rksb.append("hasInflationError ");
+            }
+
+            String rkString = rksb.toString();
+            if (!rkString.isEmpty()) {
+                sb.append("\n\t")
+                        .append(indent)
+                        .append(rkString);
+            }
+        }
+
+        sb.append("\n");
     }
 
     private static final String INDENT = "  ";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
index 601b3e0..56ad0e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
@@ -18,14 +18,7 @@
 
 import android.annotation.Nullable;
 
-import com.android.systemui.Dependency;
-import com.android.systemui.statusbar.notification.collection.provider.DerivedMember;
-import com.android.systemui.statusbar.notification.collection.provider.IsHighPriorityProvider;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Objects;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
 
 /**
  * Abstract superclass for top-level entries, i.e. things that can appear in the final notification
@@ -33,23 +26,16 @@
  */
 public abstract class ListEntry {
     private final String mKey;
-    private final IsHighPriorityProvider mIsHighPriorityProvider = new IsHighPriorityProvider();
-    private final List<DerivedMember> mDerivedMemberList = Arrays.asList(mIsHighPriorityProvider);
 
     @Nullable private GroupEntry mParent;
     @Nullable private GroupEntry mPreviousParent;
-    private int mSection;
-    int mFirstAddedIteration = -1;
+    @Nullable NotifSection mNotifSection;
 
-    // TODO: (b/145659174) remove groupManager when moving to NewNotifPipeline. Logic
-    //  replaced in GroupEntry and NotifListBuilderImpl
-    private final NotificationGroupManager mGroupManager;
+    private int mSection = -1;
+    int mFirstAddedIteration = -1;
 
     ListEntry(String key) {
         mKey = key;
-
-        // TODO: (b/145659174) remove
-        mGroupManager = Dependency.get(NotificationGroupManager.class);
     }
 
     public String getKey() {
@@ -68,11 +54,7 @@
     }
 
     void setParent(@Nullable GroupEntry parent) {
-        if (!Objects.equals(mParent, parent)) {
-            invalidateParent();
-            mParent = parent;
-            onGroupingUpdated();
-        }
+        mParent = parent;
     }
 
     @Nullable public GroupEntry getPreviousParent() {
@@ -91,58 +73,4 @@
     void setSection(int section) {
         mSection = section;
     }
-
-    /**
-     * Resets the cached values of DerivedMembers.
-     */
-    void invalidateDerivedMembers() {
-        for (int i = 0; i < mDerivedMemberList.size(); i++) {
-            mDerivedMemberList.get(i).invalidate();
-        }
-    }
-
-    /**
-     * Whether this notification is shown to the user as a high priority notification: visible on
-     * the lock screen/status bar and in the top section in the shade.
-     */
-    public boolean isHighPriority() {
-        return mIsHighPriorityProvider.get(this);
-    }
-
-    private void invalidateParent() {
-        // invalidate our parent (GroupEntry) since DerivedMembers may be dependent on children
-        if (getParent() != null) {
-            getParent().invalidateDerivedMembers();
-        }
-
-        // TODO: (b/145659174) remove
-        final NotificationEntry notifEntry = getRepresentativeEntry();
-        if (notifEntry != null && mGroupManager.isGroupChild(notifEntry.getSbn())) {
-            NotificationEntry summary = mGroupManager.getLogicalGroupSummary(notifEntry.getSbn());
-            if (summary != null) {
-                summary.invalidateDerivedMembers();
-            }
-        }
-    }
-
-    void onGroupingUpdated() {
-        for (int i = 0; i < mDerivedMemberList.size(); i++) {
-            mDerivedMemberList.get(i).onGroupingUpdated();
-        }
-        invalidateParent();
-    }
-
-    void onSbnUpdated() {
-        for (int i = 0; i < mDerivedMemberList.size(); i++) {
-            mDerivedMemberList.get(i).onSbnUpdated();
-        }
-        invalidateParent();
-    }
-
-    void onRankingUpdated() {
-        for (int i = 0; i < mDerivedMemberList.size(); i++) {
-            mDerivedMemberList.get(i).onRankingUpdated();
-        }
-        invalidateParent();
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 873cdbc..c488c6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -47,11 +47,19 @@
 import android.util.Log;
 
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.statusbar.notification.collection.notifcollection.CoalescedEvent;
-import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer;
-import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer.BatchableNotificationHandler;
+import com.android.systemui.DumpController;
+import com.android.systemui.Dumpable;
+import com.android.systemui.statusbar.notification.collection.coalescer.CoalescedEvent;
+import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer;
+import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer.BatchableNotificationHandler;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
+import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
 import com.android.systemui.util.Assert;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -88,7 +96,7 @@
  */
 @MainThread
 @Singleton
-public class NotifCollection {
+public class NotifCollection implements Dumpable {
     private final IStatusBarService mStatusBarService;
 
     private final Map<String, NotificationEntry> mNotificationSet = new ArrayMap<>();
@@ -103,9 +111,10 @@
     private boolean mAmDispatchingToOtherCode;
 
     @Inject
-    public NotifCollection(IStatusBarService statusBarService) {
+    public NotifCollection(IStatusBarService statusBarService, DumpController dumpController) {
         Assert.isMainThread();
         mStatusBarService = statusBarService;
+        dumpController.registerDumpable(TAG, this);
     }
 
     /** Initializes the NotifCollection and registers it to receive notification events. */
@@ -123,36 +132,25 @@
      * Sets the class responsible for converting the collection into the list of currently-visible
      * notifications.
      */
-    public void setBuildListener(CollectionReadyForBuildListener buildListener) {
+    void setBuildListener(CollectionReadyForBuildListener buildListener) {
         Assert.isMainThread();
         mBuildListener = buildListener;
     }
 
-    /**
-     * Returns the list of "active" notifications, i.e. the notifications that are currently posted
-     * to the phone. In general, this tracks closely to the list maintained by NotificationManager,
-     * but it can diverge slightly due to lifetime extenders.
-     *
-     * The returned list is read-only, unsorted, unfiltered, and ungrouped.
-     */
-    public Collection<NotificationEntry> getNotifs() {
+    /** @see NotifPipeline#getActiveNotifs() */
+    Collection<NotificationEntry> getActiveNotifs() {
         Assert.isMainThread();
         return mReadOnlyNotificationSet;
     }
 
-    /**
-     * Registers a listener to be informed when notifications are added, removed or updated.
-     */
-    public void addCollectionListener(NotifCollectionListener listener) {
+    /** @see NotifPipeline#addCollectionListener(NotifCollectionListener) */
+    void addCollectionListener(NotifCollectionListener listener) {
         Assert.isMainThread();
         mNotifCollectionListeners.add(listener);
     }
 
-    /**
-     * Registers a lifetime extender. Lifetime extenders can cause notifications that have been
-     * dismissed or retracted to be temporarily retained in the collection.
-     */
-    public void addNotificationLifetimeExtender(NotifLifetimeExtender extender) {
+    /** @see NotifPipeline#addNotificationLifetimeExtender(NotifLifetimeExtender) */
+    void addNotificationLifetimeExtender(NotifLifetimeExtender extender) {
         Assert.isMainThread();
         checkForReentrantCall();
         if (mLifetimeExtenders.contains(extender)) {
@@ -165,7 +163,7 @@
     /**
      * Dismiss a notification on behalf of the user.
      */
-    public void dismissNotification(
+    void dismissNotification(
             NotificationEntry entry,
             @CancellationReason int reason,
             @NonNull DismissedByUserStats stats) {
@@ -299,6 +297,14 @@
             if (!isLifetimeExtended(entry)) {
                 Ranking ranking = requireRanking(rankingMap, entry.getKey());
                 entry.setRanking(ranking);
+
+                // TODO: (b/145659174) update the sbn's overrideGroupKey in
+                //  NotificationEntry.setRanking instead of here once we fully migrate to the
+                //  NewNotifPipeline
+                final String newOverrideGroupKey = ranking.getOverrideGroupKey();
+                if (!Objects.equals(entry.getSbn().getOverrideGroupKey(), newOverrideGroupKey)) {
+                    entry.getSbn().setOverrideGroupKey(newOverrideGroupKey);
+                }
             }
         }
     }
@@ -438,7 +444,22 @@
             REASON_TIMEOUT,
     })
     @Retention(RetentionPolicy.SOURCE)
-    @interface CancellationReason {}
+    public @interface CancellationReason {}
 
     public static final int REASON_UNKNOWN = 0;
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        final List<NotificationEntry> entries = new ArrayList<>(getActiveNotifs());
+
+        pw.println("\t" + TAG + " unsorted/unfiltered notifications:");
+        if (entries.size() == 0) {
+            pw.println("\t\t None");
+        }
+        pw.println(
+                ListDumper.dumpList(
+                        entries,
+                        true,
+                        "\t\t"));
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionListener.java
deleted file mode 100644
index 032620e..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionListener.java
+++ /dev/null
@@ -1,46 +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.statusbar.notification.collection;
-
-import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
-
-/**
- * Listener interface for {@link NotifCollection}.
- */
-public interface NotifCollectionListener {
-    /**
-     * Called whenever a notification with a new key is posted.
-     */
-    default void onEntryAdded(NotificationEntry entry) {
-    }
-
-    /**
-     * Called whenever a notification with the same key as an existing notification is posted. By
-     * the time this listener is called, the entry's SBN and Ranking will already have been updated.
-     */
-    default void onEntryUpdated(NotificationEntry entry) {
-    }
-
-    /**
-     * Called immediately after a notification has been removed from the collection.
-     */
-    default void onEntryRemoved(
-            NotificationEntry entry,
-            @CancellationReason int reason,
-            boolean removedByUser) {
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflater.java
deleted file mode 100644
index fc04827..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflater.java
+++ /dev/null
@@ -1,55 +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.systemui.statusbar.notification.collection;
-import com.android.systemui.statusbar.notification.collection.coordinator.PreparationCoordinator;
-
-/**
- * Used by the {@link PreparationCoordinator}.  When notifications are added or updated, the
- * NotifInflater is asked to (re)inflated and prepare their views.  This inflation occurs off the
- * main thread. When the inflation is finished, NotifInflater will trigger its InflationCallback.
- */
-public interface NotifInflater {
-
-    /**
-     * Callback used when inflation is finished.
-     */
-    void setInflationCallback(InflationCallback callback);
-
-    /**
-     * Called to rebind the entry's views.
-     */
-    void rebindViews(NotificationEntry entry);
-
-    /**
-     * Called to inflate the views of an entry.  Views are not considered inflated until all of its
-     * views are bound. Once all views are inflated, the InflationCallback is triggered.
-     */
-    void inflateViews(NotificationEntry entry);
-
-    /**
-     * Request to stop the inflation of an entry.  For example, called when a notification is
-     * removed and no longer needs to be inflated.
-     */
-    void abortInflation(NotificationEntry entry);
-
-    /**
-     * Callback once all the views are inflated and bound for a given NotificationEntry.
-     */
-    interface InflationCallback {
-        void onInflationFinished(NotificationEntry entry);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
index 0d17557..e7b772f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
@@ -25,6 +25,9 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.statusbar.notification.InflationException;
+import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
 
@@ -107,7 +110,7 @@
                                 DISMISS_SENTIMENT_NEUTRAL,
                                 NotificationVisibility.obtain(entry.getKey(),
                                         entry.getRanking().getRank(),
-                                        mNotifCollection.getNotifs().size(),
+                                        mNotifCollection.getActiveNotifs().size(),
                                         true,
                                         NotificationLogger.getNotificationLocation(entry))
                         ));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLifetimeExtender.java
deleted file mode 100644
index 2c7b138..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLifetimeExtender.java
+++ /dev/null
@@ -1,58 +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.statusbar.notification.collection;
-
-import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
-
-/**
- * A way for other code to temporarily extend the lifetime of a notification after it has been
- * retracted. See {@link NotifCollection#addNotificationLifetimeExtender(NotifLifetimeExtender)}.
- */
-public interface NotifLifetimeExtender {
-    /** Name to associate with this extender (for the purposes of debugging) */
-    String getName();
-
-    /**
-     * Called on the extender immediately after it has been registered. The extender should hang on
-     * to this callback and execute it whenever it no longer needs to extend the lifetime of a
-     * notification.
-     */
-    void setCallback(OnEndLifetimeExtensionCallback callback);
-
-    /**
-     * Called by the NotifCollection whenever a notification has been retracted (by the app) or
-     * dismissed (by the user). If the extender returns true, it is considered to be extending the
-     * lifetime of that notification. Lifetime-extended notifications are kept around until all
-     * active extenders expire their extension by calling onEndLifetimeExtension(). This method is
-     * called on all lifetime extenders even if earlier ones return true (in other words, multiple
-     * lifetime extenders can be extending a notification at the same time).
-     */
-    boolean shouldExtendLifetime(NotificationEntry entry, @CancellationReason int reason);
-
-    /**
-     * Called by the NotifCollection to inform a lifetime extender that its extension of a notif
-     * is no longer valid (usually because the notif has been reposted and so no longer needs
-     * lifetime extension). The extender should clean up any references it has to the notif in
-     * question.
-     */
-    void cancelLifetimeExtension(NotificationEntry entry);
-
-    /** Callback for notifying the NotifCollection that a lifetime extension has expired. */
-    interface OnEndLifetimeExtensionCallback {
-        void onEndLifetimeExtension(NotifLifetimeExtender extender, NotificationEntry entry);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java
deleted file mode 100644
index 19d90f0..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java
+++ /dev/null
@@ -1,807 +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.statusbar.notification.collection;
-
-import static com.android.systemui.statusbar.notification.collection.GroupEntry.ROOT_ENTRY;
-import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpList;
-import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_BUILD_STARTED;
-import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_FINALIZING;
-import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_GROUPING;
-import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_IDLE;
-import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_PRE_GROUP_FILTERING;
-import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_PRE_RENDER_FILTERING;
-import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_RESETTING;
-import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_SORTING;
-import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_TRANSFORMING;
-
-import android.annotation.MainThread;
-import android.annotation.Nullable;
-import android.util.ArrayMap;
-
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
-import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
-import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
-import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
-import com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.SectionsProvider;
-import com.android.systemui.statusbar.notification.logging.NotifEvent;
-import com.android.systemui.statusbar.notification.logging.NotifLog;
-import com.android.systemui.util.Assert;
-import com.android.systemui.util.time.SystemClock;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * The implementation of {@link NotifListBuilder}.
- */
-@MainThread
-@Singleton
-public class NotifListBuilderImpl implements NotifListBuilder {
-    private final SystemClock mSystemClock;
-    private final NotifLog mNotifLog;
-
-    private List<ListEntry> mNotifList = new ArrayList<>();
-    private List<ListEntry> mNewNotifList = new ArrayList<>();
-
-    private final PipelineState mPipelineState = new PipelineState();
-    private final Map<String, GroupEntry> mGroups = new ArrayMap<>();
-    private Collection<NotificationEntry> mAllEntries = Collections.emptyList();
-    private int mIterationCount = 0;
-
-    private final List<NotifFilter> mNotifPreGroupFilters = new ArrayList<>();
-    private final List<NotifPromoter> mNotifPromoters = new ArrayList<>();
-    private final List<NotifFilter> mNotifPreRenderFilters = new ArrayList<>();
-    private final List<NotifComparator> mNotifComparators = new ArrayList<>();
-    private SectionsProvider mSectionsProvider = new DefaultSectionsProvider();
-
-    private final List<OnBeforeTransformGroupsListener> mOnBeforeTransformGroupsListeners =
-            new ArrayList<>();
-    private final List<OnBeforeSortListener> mOnBeforeSortListeners =
-            new ArrayList<>();
-    private final List<OnBeforeRenderListListener> mOnBeforeRenderListListeners =
-            new ArrayList<>();
-    @Nullable private OnRenderListListener mOnRenderListListener;
-
-    private final List<ListEntry> mReadOnlyNotifList = Collections.unmodifiableList(mNotifList);
-
-    @Inject
-    public NotifListBuilderImpl(SystemClock systemClock, NotifLog notifLog) {
-        Assert.isMainThread();
-        mSystemClock = systemClock;
-        mNotifLog = notifLog;
-    }
-
-    /**
-     * Attach the list builder to the NotifCollection. After this is called, it will start building
-     * the notif list in response to changes to the colletion.
-     */
-    public void attach(NotifCollection collection) {
-        Assert.isMainThread();
-        collection.setBuildListener(mReadyForBuildListener);
-    }
-
-    /**
-     * Registers the listener that's responsible for rendering the notif list to the screen. Called
-     * At the very end of pipeline execution, after all other listeners and pluggables have fired.
-     */
-    public void setOnRenderListListener(OnRenderListListener onRenderListListener) {
-        Assert.isMainThread();
-
-        mPipelineState.requireState(STATE_IDLE);
-        mOnRenderListListener = onRenderListListener;
-    }
-
-    @Override
-    public void addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener listener) {
-        Assert.isMainThread();
-
-        mPipelineState.requireState(STATE_IDLE);
-        mOnBeforeTransformGroupsListeners.add(listener);
-    }
-
-    @Override
-    public void addOnBeforeSortListener(OnBeforeSortListener listener) {
-        Assert.isMainThread();
-
-        mPipelineState.requireState(STATE_IDLE);
-        mOnBeforeSortListeners.add(listener);
-    }
-
-    @Override
-    public void addOnBeforeRenderListListener(OnBeforeRenderListListener listener) {
-        Assert.isMainThread();
-
-        mPipelineState.requireState(STATE_IDLE);
-        mOnBeforeRenderListListeners.add(listener);
-    }
-
-    @Override
-    public void addPreGroupFilter(NotifFilter filter) {
-        Assert.isMainThread();
-        mPipelineState.requireState(STATE_IDLE);
-
-        mNotifPreGroupFilters.add(filter);
-        filter.setInvalidationListener(this::onPreGroupFilterInvalidated);
-    }
-
-    @Override
-    public void addPreRenderFilter(NotifFilter filter) {
-        Assert.isMainThread();
-        mPipelineState.requireState(STATE_IDLE);
-
-        mNotifPreRenderFilters.add(filter);
-        filter.setInvalidationListener(this::onPreRenderFilterInvalidated);
-    }
-
-    @Override
-    public void addPromoter(NotifPromoter promoter) {
-        Assert.isMainThread();
-        mPipelineState.requireState(STATE_IDLE);
-
-        mNotifPromoters.add(promoter);
-        promoter.setInvalidationListener(this::onPromoterInvalidated);
-    }
-
-    @Override
-    public void setSectionsProvider(SectionsProvider provider) {
-        Assert.isMainThread();
-        mPipelineState.requireState(STATE_IDLE);
-
-        mSectionsProvider = provider;
-        provider.setInvalidationListener(this::onSectionsProviderInvalidated);
-    }
-
-    @Override
-    public void setComparators(List<NotifComparator> comparators) {
-        Assert.isMainThread();
-        mPipelineState.requireState(STATE_IDLE);
-
-        mNotifComparators.clear();
-        for (NotifComparator comparator : comparators) {
-            mNotifComparators.add(comparator);
-            comparator.setInvalidationListener(this::onNotifComparatorInvalidated);
-        }
-    }
-
-    @Override
-    public List<ListEntry> getActiveNotifs() {
-        Assert.isMainThread();
-        return mReadOnlyNotifList;
-    }
-
-    private final CollectionReadyForBuildListener mReadyForBuildListener =
-            new CollectionReadyForBuildListener() {
-                @Override
-                public void onBuildList(Collection<NotificationEntry> entries) {
-                    Assert.isMainThread();
-                    mPipelineState.requireIsBefore(STATE_BUILD_STARTED);
-
-                    mNotifLog.log(NotifEvent.ON_BUILD_LIST, "Request received from "
-                            + "NotifCollection");
-                    mAllEntries = entries;
-                    buildList();
-                }
-            };
-
-    private void onPreGroupFilterInvalidated(NotifFilter filter) {
-        Assert.isMainThread();
-
-        mNotifLog.log(NotifEvent.PRE_GROUP_FILTER_INVALIDATED, String.format(
-                "Filter \"%s\" invalidated; pipeline state is %d",
-                filter.getName(),
-                mPipelineState.getState()));
-
-        rebuildListIfBefore(STATE_PRE_GROUP_FILTERING);
-    }
-
-    private void onPromoterInvalidated(NotifPromoter filter) {
-        Assert.isMainThread();
-
-        mNotifLog.log(NotifEvent.PROMOTER_INVALIDATED, String.format(
-                "NotifPromoter \"%s\" invalidated; pipeline state is %d",
-                filter.getName(),
-                mPipelineState.getState()));
-
-        rebuildListIfBefore(STATE_TRANSFORMING);
-    }
-
-    private void onSectionsProviderInvalidated(SectionsProvider provider) {
-        Assert.isMainThread();
-
-        mNotifLog.log(NotifEvent.SECTIONS_PROVIDER_INVALIDATED, String.format(
-                "Sections provider \"%s\" invalidated; pipeline state is %d",
-                provider.getName(),
-                mPipelineState.getState()));
-
-        rebuildListIfBefore(STATE_SORTING);
-    }
-
-    private void onPreRenderFilterInvalidated(NotifFilter filter) {
-        Assert.isMainThread();
-
-        mNotifLog.log(NotifEvent.PRE_RENDER_FILTER_INVALIDATED, String.format(
-                "Filter \"%s\" invalidated; pipeline state is %d",
-                filter.getName(),
-                mPipelineState.getState()));
-
-        rebuildListIfBefore(STATE_PRE_RENDER_FILTERING);
-    }
-
-    private void onNotifComparatorInvalidated(NotifComparator comparator) {
-        Assert.isMainThread();
-
-        mNotifLog.log(NotifEvent.COMPARATOR_INVALIDATED, String.format(
-                "Comparator \"%s\" invalidated; pipeline state is %d",
-                comparator.getName(),
-                mPipelineState.getState()));
-
-        rebuildListIfBefore(STATE_SORTING);
-    }
-
-    /**
-     * Points mNotifList to the list stored in mNewNotifList.
-     * Reuses the (emptied) mNotifList as mNewNotifList.
-     */
-    private void applyNewNotifList() {
-        mNotifList.clear();
-        List<ListEntry> emptyList = mNotifList;
-        mNotifList = mNewNotifList;
-        mNewNotifList = emptyList;
-    }
-
-    /**
-     * The core algorithm of the pipeline. See the top comment in {@link NotifListBuilder} for
-     * details on our contracts with other code.
-     *
-     * Once the build starts we are very careful to protect against reentrant code. Anything that
-     * tries to invalidate itself after the pipeline has passed it by will return in an exception.
-     * In general, we should be extremely sensitive to client code doing things in the wrong order;
-     * if we detect that behavior, we should crash instantly.
-     */
-    private void buildList() {
-        mNotifLog.log(NotifEvent.START_BUILD_LIST, "Run #" + mIterationCount + "...");
-
-        mPipelineState.requireIsBefore(STATE_BUILD_STARTED);
-        mPipelineState.setState(STATE_BUILD_STARTED);
-
-        // Step 1: Reset notification states
-        mPipelineState.incrementTo(STATE_RESETTING);
-        resetNotifs();
-
-        // Step 2: Filter out any notifications that shouldn't be shown right now
-        mPipelineState.incrementTo(STATE_PRE_GROUP_FILTERING);
-        filterNotifs(mAllEntries, mNotifList, mNotifPreGroupFilters);
-
-        // Step 3: Group notifications with the same group key and set summaries
-        mPipelineState.incrementTo(STATE_GROUPING);
-        groupNotifs(mNotifList, mNewNotifList);
-        applyNewNotifList();
-        pruneIncompleteGroups(mNotifList);
-
-        // Step 4: Group transforming
-        // Move some notifs out of their groups and up to top-level (mostly used for heads-upping)
-        dispatchOnBeforeTransformGroups(mReadOnlyNotifList);
-        mPipelineState.incrementTo(STATE_TRANSFORMING);
-        promoteNotifs(mNotifList);
-        pruneIncompleteGroups(mNotifList);
-
-        // Step 5: Sort
-        // Assign each top-level entry a section, then sort the list by section and then within
-        // section by our list of custom comparators
-        dispatchOnBeforeSort(mReadOnlyNotifList);
-        mPipelineState.incrementTo(STATE_SORTING);
-        sortList();
-
-        // Step 6: Filter out entries after pre-group filtering, grouping, promoting and sorting
-        // Now filters can see grouping information to determine whether to filter or not
-        mPipelineState.incrementTo(STATE_PRE_RENDER_FILTERING);
-        filterNotifs(mNotifList, mNewNotifList, mNotifPreRenderFilters);
-        applyNewNotifList();
-        pruneIncompleteGroups(mNotifList);
-
-        // Step 7: Lock in our group structure and log anything that's changed since the last run
-        mPipelineState.incrementTo(STATE_FINALIZING);
-        logParentingChanges();
-        freeEmptyGroups();
-
-        // Step 6: Dispatch the new list, first to any listeners and then to the view layer
-        mNotifLog.log(NotifEvent.DISPATCH_FINAL_LIST, "List finalized, is:\n"
-                + dumpList(mNotifList));
-        dispatchOnBeforeRenderList(mReadOnlyNotifList);
-        if (mOnRenderListListener != null) {
-            mOnRenderListListener.onRenderList(mReadOnlyNotifList);
-        }
-
-        // Step 7: We're done!
-        mNotifLog.log(NotifEvent.LIST_BUILD_COMPLETE,
-                "Notif list build #" + mIterationCount + " completed");
-        mPipelineState.setState(STATE_IDLE);
-        mIterationCount++;
-    }
-
-    private void resetNotifs() {
-        for (GroupEntry group : mGroups.values()) {
-            group.setPreviousParent(group.getParent());
-            group.setParent(null);
-            group.clearChildren();
-            group.setSummary(null);
-        }
-
-        for (NotificationEntry entry : mAllEntries) {
-            entry.setPreviousParent(entry.getParent());
-            entry.setParent(null);
-
-            if (entry.mFirstAddedIteration == -1) {
-                entry.mFirstAddedIteration = mIterationCount;
-            }
-        }
-
-        mNotifList.clear();
-    }
-
-    private void filterNotifs(Collection<? extends ListEntry> entries,
-            List<ListEntry> out, List<NotifFilter> filters) {
-        final long now = mSystemClock.uptimeMillis();
-        for (ListEntry entry : entries)  {
-            if (entry instanceof GroupEntry) {
-                final GroupEntry groupEntry = (GroupEntry) entry;
-
-                // apply filter on its summary
-                final NotificationEntry summary = groupEntry.getRepresentativeEntry();
-                if (applyFilters(summary, now, filters)) {
-                    groupEntry.setSummary(null);
-                    annulAddition(summary);
-                }
-
-                // apply filter on its children
-                final List<NotificationEntry> children = groupEntry.getRawChildren();
-                for (int j = children.size() - 1; j >= 0; j--) {
-                    final NotificationEntry child = children.get(j);
-                    if (applyFilters(child, now, filters)) {
-                        children.remove(child);
-                        annulAddition(child);
-                    }
-                }
-
-                out.add(groupEntry);
-            } else {
-                if (applyFilters((NotificationEntry) entry, now, filters)) {
-                    annulAddition(entry);
-                } else {
-                    out.add(entry);
-                }
-            }
-        }
-    }
-
-    private void groupNotifs(List<ListEntry> entries, List<ListEntry> out) {
-        for (ListEntry listEntry : entries) {
-            // since grouping hasn't happened yet, all notifs are NotificationEntries
-            NotificationEntry entry = (NotificationEntry) listEntry;
-            if (entry.getSbn().isGroup()) {
-                final String topLevelKey = entry.getSbn().getGroupKey();
-
-                GroupEntry group = mGroups.get(topLevelKey);
-                if (group == null) {
-                    group = new GroupEntry(topLevelKey);
-                    group.mFirstAddedIteration = mIterationCount;
-                    mGroups.put(topLevelKey, group);
-                }
-                if (group.getParent() == null) {
-                    group.setParent(ROOT_ENTRY);
-                    out.add(group);
-                }
-
-                entry.setParent(group);
-
-                if (entry.getSbn().getNotification().isGroupSummary()) {
-                    final NotificationEntry existingSummary = group.getSummary();
-
-                    if (existingSummary == null) {
-                        group.setSummary(entry);
-                    } else {
-                        mNotifLog.log(NotifEvent.WARN, String.format(
-                                "Duplicate summary for group '%s': '%s' vs. '%s'",
-                                group.getKey(),
-                                existingSummary.getKey(),
-                                entry.getKey()));
-
-                        // Use whichever one was posted most recently
-                        if (entry.getSbn().getPostTime()
-                                > existingSummary.getSbn().getPostTime()) {
-                            group.setSummary(entry);
-                            annulAddition(existingSummary, out);
-                        } else {
-                            annulAddition(entry, out);
-                        }
-                    }
-                } else {
-                    group.addChild(entry);
-                }
-
-            } else {
-
-                final String topLevelKey = entry.getKey();
-                if (mGroups.containsKey(topLevelKey)) {
-                    mNotifLog.log(NotifEvent.WARN,
-                            "Duplicate non-group top-level key: " + topLevelKey);
-                } else {
-                    entry.setParent(ROOT_ENTRY);
-                    out.add(entry);
-                }
-            }
-        }
-    }
-
-    private void promoteNotifs(List<ListEntry> list) {
-        for (int i = 0; i < list.size(); i++) {
-            final ListEntry tle = list.get(i);
-
-            if (tle instanceof GroupEntry) {
-                final GroupEntry group = (GroupEntry) tle;
-
-                group.getRawChildren().removeIf(child -> {
-                    final boolean shouldPromote = applyTopLevelPromoters(child);
-
-                    if (shouldPromote) {
-                        child.setParent(ROOT_ENTRY);
-                        list.add(child);
-                    }
-
-                    return shouldPromote;
-                });
-            }
-        }
-    }
-
-    private void pruneIncompleteGroups(List<ListEntry> shadeList) {
-        for (int i = 0; i < shadeList.size(); i++) {
-            final ListEntry tle = shadeList.get(i);
-
-            if (tle instanceof GroupEntry) {
-                final GroupEntry group = (GroupEntry) tle;
-                final List<NotificationEntry> children = group.getRawChildren();
-
-                if (group.getSummary() != null && children.size() == 0) {
-                    shadeList.remove(i);
-                    i--;
-
-                    NotificationEntry summary = group.getSummary();
-                    summary.setParent(ROOT_ENTRY);
-                    shadeList.add(summary);
-
-                    group.setSummary(null);
-                    annulAddition(group, shadeList);
-
-                } else if (group.getSummary() == null
-                        || children.size() < MIN_CHILDREN_FOR_GROUP) {
-                    // If the group doesn't provide a summary or is too small, ignore it and add
-                    // its children (if any) directly to top-level.
-
-                    shadeList.remove(i);
-                    i--;
-
-                    if (group.getSummary() != null) {
-                        final NotificationEntry summary = group.getSummary();
-                        group.setSummary(null);
-                        annulAddition(summary, shadeList);
-                    }
-
-                    for (int j = 0; j < children.size(); j++) {
-                        final NotificationEntry child = children.get(j);
-                        child.setParent(ROOT_ENTRY);
-                        shadeList.add(child);
-                    }
-                    children.clear();
-
-                    annulAddition(group, shadeList);
-                }
-            }
-        }
-    }
-
-    /**
-     * If a ListEntry was added to the shade list and then later removed (e.g. because it was a
-     * group that was broken up), this method will erase any bookkeeping traces of that addition
-     * and/or check that they were already erased.
-     *
-     * Before calling this method, the entry must already have been removed from its parent. If
-     * it's a group, its summary must be null and its children must be empty.
-     */
-    private void annulAddition(ListEntry entry, List<ListEntry> shadeList) {
-
-        // This function does very little, but if any of its assumptions are violated (and it has a
-        // lot of them), it will put the system into an inconsistent state. So we check all of them
-        // here.
-
-        if (entry.getParent() == null || entry.mFirstAddedIteration == -1) {
-            throw new IllegalStateException(
-                    "Cannot nullify addition of " + entry.getKey() + ": no such addition. ("
-                            + entry.getParent() + " " + entry.mFirstAddedIteration + ")");
-        }
-
-        if (entry.getParent() == ROOT_ENTRY) {
-            if (shadeList.contains(entry)) {
-                throw new IllegalStateException("Cannot nullify addition of " + entry.getKey()
-                        + ": it's still in the shade list.");
-            }
-        }
-
-        if (entry instanceof GroupEntry) {
-            GroupEntry ge = (GroupEntry) entry;
-            if (ge.getSummary() != null) {
-                throw new IllegalStateException(
-                        "Cannot nullify group " + ge.getKey() + ": summary is not null");
-            }
-            if (!ge.getChildren().isEmpty()) {
-                throw new IllegalStateException(
-                        "Cannot nullify group " + ge.getKey() + ": still has children");
-            }
-        } else if (entry instanceof NotificationEntry) {
-            if (entry == entry.getParent().getSummary()
-                    || entry.getParent().getChildren().contains(entry)) {
-                throw new IllegalStateException("Cannot nullify addition of child "
-                        + entry.getKey() + ": it's still attached to its parent.");
-            }
-        }
-
-        annulAddition(entry);
-
-    }
-
-    /**
-     * Erases bookkeeping traces stored on an entry when it is removed from the notif list.
-     * This can happen if the entry is removed from a group that was broken up or if the entry was
-     * filtered out during any of the filtering steps.
-     */
-    private void annulAddition(ListEntry entry) {
-        entry.setParent(null);
-        if (entry.mFirstAddedIteration == mIterationCount) {
-            entry.mFirstAddedIteration = -1;
-        }
-    }
-
-    private void sortList() {
-        // Assign sections to top-level elements and sort their children
-        for (ListEntry entry : mNotifList) {
-            entry.setSection(mSectionsProvider.getSection(entry));
-            if (entry instanceof GroupEntry) {
-                GroupEntry parent = (GroupEntry) entry;
-                for (NotificationEntry child : parent.getChildren()) {
-                    child.setSection(0);
-                }
-                parent.sortChildren(sChildComparator);
-            }
-        }
-
-        // Finally, sort all top-level elements
-        mNotifList.sort(mTopLevelComparator);
-    }
-
-    private void freeEmptyGroups() {
-        mGroups.values().removeIf(ge -> ge.getSummary() == null && ge.getChildren().isEmpty());
-    }
-
-    private void logParentingChanges() {
-        for (NotificationEntry entry : mAllEntries) {
-            if (entry.getParent() != entry.getPreviousParent()) {
-                mNotifLog.log(NotifEvent.PARENT_CHANGED, String.format(
-                        "%s: parent changed from %s to %s",
-                        entry.getKey(),
-                        entry.getPreviousParent() == null
-                                ? "null" : entry.getPreviousParent().getKey(),
-                        entry.getParent() == null
-                                ? "null" : entry.getParent().getKey()));
-            }
-        }
-        for (GroupEntry group : mGroups.values()) {
-            if (group.getParent() != group.getPreviousParent()) {
-                mNotifLog.log(NotifEvent.PARENT_CHANGED, String.format(
-                        "%s: parent changed from %s to %s",
-                        group.getKey(),
-                        group.getPreviousParent() == null
-                                ? "null" : group.getPreviousParent().getKey(),
-                        group.getParent() == null
-                                ? "null" : group.getParent().getKey()));
-            }
-        }
-    }
-
-    private final Comparator<ListEntry> mTopLevelComparator = (o1, o2) -> {
-
-        int cmp = Integer.compare(o1.getSection(), o2.getSection());
-
-        if (cmp == 0) {
-            for (int i = 0; i < mNotifComparators.size(); i++) {
-                cmp = mNotifComparators.get(i).compare(o1, o2);
-                if (cmp != 0) {
-                    break;
-                }
-            }
-        }
-
-        final NotificationEntry rep1 = o1.getRepresentativeEntry();
-        final NotificationEntry rep2 = o2.getRepresentativeEntry();
-
-        if (cmp == 0) {
-            cmp = rep1.getRanking().getRank() - rep2.getRanking().getRank();
-        }
-
-        if (cmp == 0) {
-            cmp = Long.compare(
-                    rep2.getSbn().getNotification().when,
-                    rep1.getSbn().getNotification().when);
-        }
-
-        return cmp;
-    };
-
-    private static final Comparator<NotificationEntry> sChildComparator = (o1, o2) -> {
-        int cmp = o1.getRanking().getRank() - o2.getRanking().getRank();
-
-        if (cmp == 0) {
-            cmp = Long.compare(
-                    o2.getSbn().getNotification().when,
-                    o1.getSbn().getNotification().when);
-        }
-
-        return cmp;
-    };
-
-    private boolean applyFilters(NotificationEntry entry, long now, List<NotifFilter> filters) {
-        NotifFilter filter = findRejectingFilter(entry, now, filters);
-
-        if (filter != entry.mExcludingFilter) {
-            if (entry.mExcludingFilter == null) {
-                mNotifLog.log(NotifEvent.FILTER_CHANGED, String.format(
-                        "%s: filtered out by '%s'",
-                        entry.getKey(),
-                        filter.getName()));
-            } else if (filter == null) {
-                mNotifLog.log(NotifEvent.FILTER_CHANGED, String.format(
-                        "%s: no longer filtered out (previous filter was '%s')",
-                        entry.getKey(),
-                        entry.mExcludingFilter.getName()));
-            } else {
-                mNotifLog.log(NotifEvent.FILTER_CHANGED, String.format(
-                        "%s: filter changed: '%s' -> '%s'",
-                        entry.getKey(),
-                        entry.mExcludingFilter,
-                        filter));
-            }
-
-            // Note that groups and summaries can also be filtered out later if they're part of a
-            // malformed group. We currently don't have a great way to track that beyond parenting
-            // change logs. Consider adding something similar to mExcludingFilter for them.
-            entry.mExcludingFilter = filter;
-        }
-
-        return filter != null;
-    }
-
-    @Nullable private static NotifFilter findRejectingFilter(NotificationEntry entry, long now,
-            List<NotifFilter> filters) {
-        final int size = filters.size();
-
-        for (int i = 0; i < size; i++) {
-            NotifFilter filter = filters.get(i);
-            if (filter.shouldFilterOut(entry, now)) {
-                return filter;
-            }
-        }
-        return null;
-    }
-
-    private boolean applyTopLevelPromoters(NotificationEntry entry) {
-        NotifPromoter promoter = findPromoter(entry);
-
-        if (promoter != entry.mNotifPromoter) {
-            if (entry.mNotifPromoter == null) {
-                mNotifLog.log(NotifEvent.PROMOTER_CHANGED, String.format(
-                        "%s: Entry promoted to top level by '%s'",
-                        entry.getKey(),
-                        promoter.getName()));
-            } else if (promoter == null) {
-                mNotifLog.log(NotifEvent.PROMOTER_CHANGED, String.format(
-                        "%s: Entry is no longer promoted to top level (previous promoter was '%s')",
-                        entry.getKey(),
-                        entry.mNotifPromoter.getName()));
-            } else {
-                mNotifLog.log(NotifEvent.PROMOTER_CHANGED, String.format(
-                        "%s: Top-level promoter changed: '%s' -> '%s'",
-                        entry.getKey(),
-                        entry.mNotifPromoter,
-                        promoter));
-            }
-            entry.mNotifPromoter = promoter;
-        }
-
-        return promoter != null;
-    }
-
-    @Nullable private NotifPromoter findPromoter(NotificationEntry entry) {
-        for (int i = 0; i < mNotifPromoters.size(); i++) {
-            NotifPromoter promoter = mNotifPromoters.get(i);
-            if (promoter.shouldPromoteToTopLevel(entry)) {
-                return promoter;
-            }
-        }
-        return null;
-    }
-
-    private void rebuildListIfBefore(@PipelineState.StateName int state) {
-        mPipelineState.requireIsBefore(state);
-        if (mPipelineState.is(STATE_IDLE)) {
-            buildList();
-        }
-    }
-
-    private void dispatchOnBeforeTransformGroups(List<ListEntry> entries) {
-        for (int i = 0; i < mOnBeforeTransformGroupsListeners.size(); i++) {
-            mOnBeforeTransformGroupsListeners.get(i).onBeforeTransformGroups(entries);
-        }
-    }
-
-    private void dispatchOnBeforeSort(List<ListEntry> entries) {
-        for (int i = 0; i < mOnBeforeSortListeners.size(); i++) {
-            mOnBeforeSortListeners.get(i).onBeforeSort(entries);
-        }
-    }
-
-    private void dispatchOnBeforeRenderList(List<ListEntry> entries) {
-        for (int i = 0; i < mOnBeforeRenderListListeners.size(); i++) {
-            mOnBeforeRenderListListeners.get(i).onBeforeRenderList(entries);
-        }
-    }
-
-    /** See {@link #setOnRenderListListener(OnRenderListListener)} */
-    public interface OnRenderListListener {
-        /**
-         * Called with the final filtered, grouped, and sorted list.
-         *
-         * @param entries A read-only view into the current notif list. Note that this list is
-         *                backed by the live list and will change in response to new pipeline runs.
-         */
-        void onRenderList(List<ListEntry> entries);
-    }
-
-    private static class DefaultSectionsProvider extends SectionsProvider {
-        DefaultSectionsProvider() {
-            super("DefaultSectionsProvider");
-        }
-
-        @Override
-        public int getSection(ListEntry entry) {
-            return 0;
-        }
-    }
-
-    private static final String TAG = "NotifListBuilderImpl";
-
-    private static final int MIN_CHILDREN_FOR_GROUP = 2;
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
new file mode 100644
index 0000000..0377f57
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
@@ -0,0 +1,190 @@
+/*
+ * 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.statusbar.notification.collection;
+
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
+
+import java.util.Collection;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * The system that constructs the "shade list", the filtered, grouped, and sorted list of
+ * notifications that are currently being displayed to the user in the notification shade.
+ *
+ * The pipeline proceeds through a series of stages in order to produce the final list (see below).
+ * Each stage exposes hooks and listeners to allow other code to participate.
+ *
+ * This list differs from the canonical one we receive from system server in a few ways:
+ * - Filtered: Some notifications are filtered out. For example, we filter out notifications whose
+ *   views haven't been inflated yet. We also filter out some notifications if we're on the lock
+ *   screen and notifications for other users. So participate, see
+ *   {@link #addPreGroupFilter} and similar methods.
+ * - Grouped: Notifications that are part of the same group are clustered together into a single
+ *   GroupEntry. These groups are then transformed in order to remove children or completely split
+ *   them apart. To participate, see {@link #addPromoter}.
+ * - Sorted: All top-level notifications are sorted. To participate, see
+ *   {@link #setSections} and {@link #setComparators}
+ *
+ * The exact order of all hooks is as follows:
+ *  0. Collection listeners are fired ({@link #addCollectionListener}).
+ *  1. Pre-group filters are fired on each notification ({@link #addPreGroupFilter}).
+ *  2. Initial grouping is performed (NotificationEntries will have their parents set
+ *     appropriately).
+ *  3. OnBeforeTransformGroupListeners are fired ({@link #addOnBeforeTransformGroupsListener})
+ *  4. NotifPromoters are called on each notification with a parent ({@link #addPromoter})
+ *  5. OnBeforeSortListeners are fired ({@link #addOnBeforeSortListener})
+ *  6. Top-level entries are assigned sections by NotifSections ({@link #setSections})
+ *  7. Top-level entries within the same section are sorted by NotifComparators
+ *     ({@link #setComparators})
+ *  8. Pre-render filters are fired on each notification ({@link #addPreRenderFilter})
+ *  9. OnBeforeRenderListListeners are fired ({@link #addOnBeforeRenderListListener})
+ *  9. The list is handed off to the view layer to be rendered
+ */
+@Singleton
+public class NotifPipeline {
+    private final NotifCollection mNotifCollection;
+    private final ShadeListBuilder mShadeListBuilder;
+
+    @Inject
+    public NotifPipeline(
+            NotifCollection notifCollection,
+            ShadeListBuilder shadeListBuilder) {
+        mNotifCollection = notifCollection;
+        mShadeListBuilder = shadeListBuilder;
+    }
+
+    /**
+     * Returns the list of "active" notifications, i.e. the notifications that are currently posted
+     * to the phone. In general, this tracks closely to the list maintained by NotificationManager,
+     * but it can diverge slightly due to lifetime extenders.
+     *
+     * The returned collection is read-only, unsorted, unfiltered, and ungrouped.
+     */
+    public Collection<NotificationEntry> getActiveNotifs() {
+        return mNotifCollection.getActiveNotifs();
+    }
+
+    /**
+     * Registers a listener to be informed when notifications are added, removed or updated.
+     */
+    public void addCollectionListener(NotifCollectionListener listener) {
+        mNotifCollection.addCollectionListener(listener);
+    }
+
+    /**
+     * Registers a lifetime extender. Lifetime extenders can cause notifications that have been
+     * dismissed or retracted to be temporarily retained in the collection.
+     */
+    public void addNotificationLifetimeExtender(NotifLifetimeExtender extender) {
+        mNotifCollection.addNotificationLifetimeExtender(extender);
+    }
+
+    /**
+     * Registers a filter with the pipeline before grouping, promoting and sorting occurs. Filters
+     * are called on each notification in the order that they were registered. If any filter
+     * returns true, the notification is removed from the pipeline (and no other filters are
+     * called on that notif).
+     */
+    public void addPreGroupFilter(NotifFilter filter) {
+        mShadeListBuilder.addPreGroupFilter(filter);
+    }
+
+    /**
+     * Called after notifications have been filtered and after the initial grouping has been
+     * performed but before NotifPromoters have had a chance to promote children out of groups.
+     */
+    public void addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener listener) {
+        mShadeListBuilder.addOnBeforeTransformGroupsListener(listener);
+    }
+
+    /**
+     * Registers a promoter with the pipeline. Promoters are able to promote child notifications to
+     * top-level, i.e. move a notification that would be a child of a group and make it appear
+     * ungrouped. Promoters are called on each child notification in the order that they are
+     * registered. If any promoter returns true, the notification is removed from the group (and no
+     * other promoters are called on it).
+     */
+    public void addPromoter(NotifPromoter promoter) {
+        mShadeListBuilder.addPromoter(promoter);
+    }
+
+    /**
+     * Called after notifs have been filtered and groups have been determined but before sections
+     * have been determined or the notifs have been sorted.
+     */
+    public void addOnBeforeSortListener(OnBeforeSortListener listener) {
+        mShadeListBuilder.addOnBeforeSortListener(listener);
+    }
+
+    /**
+     * Sections that are used to sort top-level entries.  If two entries have the same section,
+     * NotifComparators are consulted. Sections from this list are called in order for each
+     * notification passed through the pipeline. The first NotifSection to return true for
+     * {@link NotifSection#isInSection(ListEntry)} sets the entry as part of its Section.
+     */
+    public void setSections(List<NotifSection> sections) {
+        mShadeListBuilder.setSections(sections);
+    }
+
+    /**
+     * Comparators that are used to sort top-level entries that share the same section. The
+     * comparators are executed in order until one of them returns a non-zero result. If all return
+     * zero, the pipeline falls back to sorting by rank (and, failing that, Notification.when).
+     */
+    public void setComparators(List<NotifComparator> comparators) {
+        mShadeListBuilder.setComparators(comparators);
+    }
+
+    /**
+     * Registers a filter with the pipeline to filter right before rendering the list (after
+     * pre-group filtering, grouping, promoting and sorting occurs). Filters are
+     * called on each notification in the order that they were registered. If any filter returns
+     * true, the notification is removed from the pipeline (and no other filters are called on that
+     * notif).
+     */
+    public void addPreRenderFilter(NotifFilter filter) {
+        mShadeListBuilder.addPreRenderFilter(filter);
+    }
+
+    /**
+     * Called at the end of the pipeline after the notif list has been finalized but before it has
+     * been handed off to the view layer.
+     */
+    public void addOnBeforeRenderListListener(OnBeforeRenderListListener listener) {
+        mShadeListBuilder.addOnBeforeRenderListListener(listener);
+    }
+
+    /**
+     * Returns a read-only view in to the current shade list, i.e. the list of notifications that
+     * are currently present in the shade. If this method is called during pipeline execution it
+     * will return the current state of the list, which will likely be only partially-generated.
+     */
+    public List<ListEntry> getShadeList() {
+        return mShadeListBuilder.getShadeList();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 28e486d..1f77ec2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -37,8 +37,10 @@
 import android.app.NotificationChannel;
 import android.app.NotificationManager.Policy;
 import android.app.Person;
+import android.app.RemoteInputHistoryItem;
 import android.content.Context;
 import android.graphics.drawable.Icon;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.service.notification.NotificationListenerService.Ranking;
@@ -59,6 +61,7 @@
 import com.android.systemui.statusbar.notification.InflationException;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGuts;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
@@ -119,6 +122,8 @@
     public int targetSdk;
     private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
     public CharSequence remoteInputText;
+    public String remoteInputMimeType;
+    public Uri remoteInputUri;
     private Notification.BubbleMetadata mBubbleMetadata;
 
     /**
@@ -204,11 +209,8 @@
                     + " doesn't match existing key " + mKey);
         }
 
-        if (!Objects.equals(mSbn, sbn)) {
-            mSbn = sbn;
-            mBubbleMetadata = mSbn.getNotification().getBubbleMetadata();
-            onSbnUpdated();
-        }
+        mSbn = sbn;
+        mBubbleMetadata = mSbn.getNotification().getBubbleMetadata();
     }
 
     /**
@@ -233,10 +235,7 @@
                     + " doesn't match existing key " + mKey);
         }
 
-        if (!Objects.equals(mRanking, ranking)) {
-            mRanking = ranking;
-            onRankingUpdated();
-        }
+        mRanking = ranking;
     }
 
     /*
@@ -600,8 +599,8 @@
             return false;
         }
         Bundle extras = mSbn.getNotification().extras;
-        CharSequence[] replyTexts = extras.getCharSequenceArray(
-                Notification.EXTRA_REMOTE_INPUT_HISTORY);
+        RemoteInputHistoryItem[] replyTexts = (RemoteInputHistoryItem[]) extras.getParcelableArray(
+                Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
         if (!ArrayUtils.isEmpty(replyTexts)) {
             return true;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
index 7010943..1eeeab3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.statusbar.NotificationMediaManager
 import com.android.systemui.statusbar.notification.NotificationFilter
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
 import com.android.systemui.statusbar.notification.logging.NotifEvent
 import com.android.systemui.statusbar.notification.logging.NotifLog
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
@@ -54,7 +55,8 @@
     private val notifFilter: NotificationFilter,
     private val notifLog: NotifLog,
     sectionsFeatureManager: NotificationSectionsFeatureManager,
-    private val peopleNotificationIdentifier: PeopleNotificationIdentifier
+    private val peopleNotificationIdentifier: PeopleNotificationIdentifier,
+    private val highPriorityProvider: HighPriorityProvider
 ) {
 
     var rankingMap: RankingMap? = null
@@ -81,6 +83,9 @@
         val aHeadsUp = a.isRowHeadsUp
         val bHeadsUp = b.isRowHeadsUp
 
+        val aIsHighPriority = a.isHighPriority()
+        val bIsHighPriority = b.isHighPriority()
+
         when {
             usePeopleFiltering && aIsPeople != bIsPeople -> if (aIsPeople) -1 else 1
             aHeadsUp != bHeadsUp -> if (aHeadsUp) -1 else 1
@@ -90,8 +95,8 @@
             aMedia != bMedia -> if (aMedia) -1 else 1
             // Upsort PRIORITY_MAX system notifications
             aSystemMax != bSystemMax -> if (aSystemMax) -1 else 1
-            a.isHighPriority != b.isHighPriority ->
-                -1 * a.isHighPriority.compareTo(b.isHighPriority)
+            aIsHighPriority != bIsHighPriority ->
+                -1 * aIsHighPriority.compareTo(bIsHighPriority)
             aRank != bRank -> aRank - bRank
             else -> nb.notification.`when`.compareTo(na.notification.`when`)
         }
@@ -154,7 +159,7 @@
     ) {
         if (usePeopleFiltering && entry.isPeopleNotification()) {
             entry.bucket = BUCKET_PEOPLE
-        } else if (isHeadsUp || isMedia || isSystemMax || entry.isHighPriority) {
+        } else if (isHeadsUp || isMedia || isSystemMax || entry.isHighPriority()) {
             entry.bucket = BUCKET_ALERTING
         } else {
             entry.bucket = BUCKET_SILENT
@@ -171,26 +176,25 @@
                     }
                     entry.ranking = newRanking
 
-                    val oldSbn = entry.sbn.cloneLight()
                     val newOverrideGroupKey = newRanking.overrideGroupKey
-                    if (!Objects.equals(oldSbn.overrideGroupKey, newOverrideGroupKey)) {
+                    if (!Objects.equals(entry.sbn.overrideGroupKey, newOverrideGroupKey)) {
+                        val oldGroupKey = entry.sbn.groupKey
+                        val oldIsGroup = entry.sbn.isGroup
+                        val oldIsGroupSummary = entry.sbn.notification.isGroupSummary
                         entry.sbn.overrideGroupKey = newOverrideGroupKey
-                        // TODO: notify group manager here?
-                        groupManager.onEntryUpdated(entry, oldSbn)
+                        groupManager.onEntryUpdated(entry, oldGroupKey, oldIsGroup,
+                                oldIsGroupSummary)
                     }
-
-                    // TODO: (b/145659174) remove after moving to new NotifPipeline
-                    // (should be able to remove all groupManager code post-migration)
-                    entry.invalidateDerivedMembers()
                 }
             }
         }
     }
 
     private fun NotificationEntry.isPeopleNotification() =
-            sbn.isPeopleNotification()
-    private fun StatusBarNotification.isPeopleNotification() =
-            peopleNotificationIdentifier.isPeopleNotification(this)
+            peopleNotificationIdentifier.isPeopleNotification(sbn, ranking)
+
+    private fun NotificationEntry.isHighPriority() =
+            highPriorityProvider.isHighPriority(this)
 }
 
 // Convenience functions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinder.java
deleted file mode 100644
index 7504e86..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinder.java
+++ /dev/null
@@ -1,52 +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.statusbar.notification.collection;
-
-import android.annotation.Nullable;
-
-import com.android.systemui.statusbar.NotificationUiAdjustment;
-import com.android.systemui.statusbar.notification.InflationException;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-
-/**
- * Used by the {@link NotificationEntryManager}. When notifications are added or updated, the binder
- * is asked to (re)inflate and prepare their views. This inflation must occur off the main thread.
- */
-public interface NotificationRowBinder {
-    /**
-     * Called when a notification has been added or updated. The binder must asynchronously inflate
-     * and bind the views associated with the notification.
-     *
-     * TODO: The caller is notified when the inflation completes, but this is currently a very
-     * roundabout business. Add an explicit completion/failure callback to this method.
-     */
-    void inflateViews(
-            NotificationEntry entry,
-            Runnable onDismissRunnable)
-            throws InflationException;
-
-    /**
-     * Called when the ranking has been updated (but not add or remove has been done). The binder
-     * should inspect the old and new adjustments and re-inflate the entry's views if necessary
-     * (e.g. if something important changed).
-     */
-    void onNotificationRankingUpdated(
-            NotificationEntry entry,
-            @Nullable Integer oldImportance,
-            NotificationUiAdjustment oldAdjustment,
-            NotificationUiAdjustment newAdjustment);
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
deleted file mode 100644
index 6dc647d..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
+++ /dev/null
@@ -1,291 +0,0 @@
-/*
- * 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.systemui.statusbar.notification.collection;
-
-import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.os.Build;
-import android.service.notification.StatusBarNotification;
-import android.util.Log;
-import android.view.ViewGroup;
-
-import com.android.internal.util.NotificationMessagingUtil;
-import com.android.systemui.Dependency;
-import com.android.systemui.R;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationPresenter;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.NotificationUiAdjustment;
-import com.android.systemui.statusbar.notification.InflationException;
-import com.android.systemui.statusbar.notification.NotificationClicker;
-import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
-import com.android.systemui.statusbar.notification.row.RowInflaterTask;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-
-import java.util.Objects;
-
-/** Handles inflating and updating views for notifications. */
-public class NotificationRowBinderImpl implements NotificationRowBinder {
-
-    private static final String TAG = "NotificationViewManager";
-
-    private final NotificationGroupManager mGroupManager =
-            Dependency.get(NotificationGroupManager.class);
-    private final NotificationGutsManager mGutsManager =
-            Dependency.get(NotificationGutsManager.class);
-    private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider =
-            Dependency.get(NotificationInterruptionStateProvider.class);
-
-    private final Context mContext;
-    private final NotificationMessagingUtil mMessagingUtil;
-    private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger =
-            this::logNotificationExpansion;
-    private final boolean mAllowLongPress;
-    private final KeyguardBypassController mKeyguardBypassController;
-    private final StatusBarStateController mStatusBarStateController;
-
-    private NotificationRemoteInputManager mRemoteInputManager;
-    private NotificationPresenter mPresenter;
-    private NotificationListContainer mListContainer;
-    private HeadsUpManager mHeadsUpManager;
-    private NotificationContentInflater.InflationCallback mInflationCallback;
-    private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener;
-    private BindRowCallback mBindRowCallback;
-    private NotificationClicker mNotificationClicker;
-    private final NotificationLogger mNotificationLogger;
-
-    public NotificationRowBinderImpl(
-            Context context,
-            boolean allowLongPress,
-            KeyguardBypassController keyguardBypassController,
-            StatusBarStateController statusBarStateController,
-            NotificationLogger logger) {
-        mContext = context;
-        mMessagingUtil = new NotificationMessagingUtil(context);
-        mAllowLongPress = allowLongPress;
-        mKeyguardBypassController = keyguardBypassController;
-        mStatusBarStateController = statusBarStateController;
-        mNotificationLogger = logger;
-    }
-
-    private NotificationRemoteInputManager getRemoteInputManager() {
-        if (mRemoteInputManager == null) {
-            mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
-        }
-        return mRemoteInputManager;
-    }
-
-    /**
-     * Sets up late-bound dependencies for this component.
-     */
-    public void setUpWithPresenter(NotificationPresenter presenter,
-            NotificationListContainer listContainer,
-            HeadsUpManager headsUpManager,
-            BindRowCallback bindRowCallback) {
-        mPresenter = presenter;
-        mListContainer = listContainer;
-        mHeadsUpManager = headsUpManager;
-        mBindRowCallback = bindRowCallback;
-        mOnAppOpsClickListener = mGutsManager::openGuts;
-    }
-
-    public void setInflationCallback(NotificationContentInflater.InflationCallback callback) {
-        mInflationCallback = callback;
-    }
-
-    public void setNotificationClicker(NotificationClicker clicker) {
-        mNotificationClicker = clicker;
-    }
-
-    /**
-     * Inflates the views for the given entry (possibly asynchronously).
-     */
-    @Override
-    public void inflateViews(
-            NotificationEntry entry,
-            Runnable onDismissRunnable)
-            throws InflationException {
-        ViewGroup parent = mListContainer.getViewParentForNotification(entry);
-        PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
-                entry.getSbn().getUser().getIdentifier());
-
-        final StatusBarNotification sbn = entry.getSbn();
-        if (entry.rowExists()) {
-            entry.updateIcons(mContext, sbn);
-            entry.reset();
-            updateNotification(entry, pmUser, sbn, entry.getRow());
-            entry.getRow().setOnDismissRunnable(onDismissRunnable);
-        } else {
-            entry.createIcons(mContext, sbn);
-            new RowInflaterTask().inflate(mContext, parent, entry,
-                    row -> {
-                        bindRow(entry, pmUser, sbn, row, onDismissRunnable);
-                        updateNotification(entry, pmUser, sbn, row);
-                    });
-        }
-    }
-
-    private void bindRow(NotificationEntry entry, PackageManager pmUser,
-            StatusBarNotification sbn, ExpandableNotificationRow row,
-            Runnable onDismissRunnable) {
-        row.setExpansionLogger(mExpansionLogger, entry.getSbn().getKey());
-        row.setBypassController(mKeyguardBypassController);
-        row.setStatusBarStateController(mStatusBarStateController);
-        row.setGroupManager(mGroupManager);
-        row.setHeadsUpManager(mHeadsUpManager);
-        row.setOnExpandClickListener(mPresenter);
-        row.setInflationCallback(mInflationCallback);
-        if (mAllowLongPress) {
-            row.setLongPressListener(mGutsManager::openGuts);
-        }
-        mListContainer.bindRow(row);
-        getRemoteInputManager().bindRow(row);
-
-        // Get the app name.
-        // Note that Notification.Builder#bindHeaderAppName has similar logic
-        // but since this field is used in the guts, it must be accurate.
-        // Therefore we will only show the application label, or, failing that, the
-        // package name. No substitutions.
-        final String pkg = sbn.getPackageName();
-        String appname = pkg;
-        try {
-            final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
-                    PackageManager.MATCH_UNINSTALLED_PACKAGES
-                            | PackageManager.MATCH_DISABLED_COMPONENTS);
-            if (info != null) {
-                appname = String.valueOf(pmUser.getApplicationLabel(info));
-            }
-        } catch (PackageManager.NameNotFoundException e) {
-            // Do nothing
-        }
-        row.setAppName(appname);
-        row.setOnDismissRunnable(onDismissRunnable);
-        row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
-        if (ENABLE_REMOTE_INPUT) {
-            row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
-        }
-
-        row.setAppOpsOnClickListener(mOnAppOpsClickListener);
-
-        mBindRowCallback.onBindRow(entry, pmUser, sbn, row);
-    }
-
-    /**
-     * Updates the views bound to an entry when the entry's ranking changes, either in-place or by
-     * reinflating them.
-     */
-    @Override
-    public void onNotificationRankingUpdated(
-            NotificationEntry entry,
-            @Nullable Integer oldImportance,
-            NotificationUiAdjustment oldAdjustment,
-            NotificationUiAdjustment newAdjustment) {
-        if (NotificationUiAdjustment.needReinflate(oldAdjustment, newAdjustment)) {
-            if (entry.rowExists()) {
-                entry.reset();
-                PackageManager pmUser = StatusBar.getPackageManagerForUser(
-                        mContext,
-                        entry.getSbn().getUser().getIdentifier());
-                updateNotification(entry, pmUser, entry.getSbn(), entry.getRow());
-            } else {
-                // Once the RowInflaterTask is done, it will pick up the updated entry, so
-                // no-op here.
-            }
-        } else {
-            if (oldImportance != null && entry.getImportance() != oldImportance) {
-                if (entry.rowExists()) {
-                    entry.getRow().onNotificationRankingUpdated();
-                }
-            }
-        }
-    }
-
-    //TODO: This method associates a row with an entry, but eventually needs to not do that
-    private void updateNotification(
-            NotificationEntry entry,
-            PackageManager pmUser,
-            StatusBarNotification sbn,
-            ExpandableNotificationRow row) {
-        row.setIsLowPriority(entry.isAmbient());
-
-        // Extract target SDK version.
-        try {
-            ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0);
-            entry.targetSdk = info.targetSdkVersion;
-        } catch (PackageManager.NameNotFoundException ex) {
-            Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
-        }
-        row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
-                && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
-
-        // TODO: should updates to the entry be happening somewhere else?
-        entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
-
-        entry.setRow(row);
-        row.setOnActivatedListener(mPresenter);
-
-        boolean useIncreasedCollapsedHeight =
-                mMessagingUtil.isImportantMessaging(sbn, entry.getImportance());
-        boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight
-                && !mPresenter.isPresenterFullyCollapsed();
-        row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
-        row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
-        row.setEntry(entry);
-
-        if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) {
-            row.setInflationFlags(FLAG_CONTENT_VIEW_HEADS_UP);
-        }
-        row.setNeedsRedaction(
-                Dependency.get(NotificationLockscreenUserManager.class).needsRedaction(entry));
-        row.inflateViews();
-
-        // bind the click event to the content area
-        Objects.requireNonNull(mNotificationClicker).register(row, sbn);
-    }
-
-    private void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
-        mNotificationLogger.onExpansionChanged(key, userAction, expanded);
-    }
-
-    /** Callback for when a row is bound to an entry. */
-    public interface BindRowCallback {
-        /**
-         * Called when a new notification and row is created.
-         *
-         * @param entry  entry for the notification
-         * @param pmUser package manager for user
-         * @param sbn    notification
-         * @param row    row for the notification
-         */
-        void onBindRow(NotificationEntry entry, PackageManager pmUser,
-                StatusBarNotification sbn, ExpandableNotificationRow row);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
new file mode 100644
index 0000000..97f8ec5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -0,0 +1,863 @@
+/*
+ * 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.statusbar.notification.collection;
+
+import static com.android.systemui.statusbar.notification.collection.GroupEntry.ROOT_ENTRY;
+import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_BUILD_STARTED;
+import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_FINALIZING;
+import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_GROUPING;
+import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_IDLE;
+import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_PRE_GROUP_FILTERING;
+import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_PRE_RENDER_FILTERING;
+import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_RESETTING;
+import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_SORTING;
+import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_TRANSFORMING;
+
+import android.annotation.MainThread;
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.Pair;
+
+import com.android.systemui.DumpController;
+import com.android.systemui.Dumpable;
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
+import com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
+import com.android.systemui.statusbar.notification.logging.NotifEvent;
+import com.android.systemui.statusbar.notification.logging.NotifLog;
+import com.android.systemui.util.Assert;
+import com.android.systemui.util.time.SystemClock;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * The second half of {@link NotifPipeline}. Sits downstream of the NotifCollection and transforms
+ * its "notification set" into the "shade list", the filtered, grouped, and sorted list of
+ * notifications that are currently present in the notification shade.
+ */
+@MainThread
+@Singleton
+public class ShadeListBuilder implements Dumpable {
+    private final SystemClock mSystemClock;
+    private final NotifLog mNotifLog;
+
+    private List<ListEntry> mNotifList = new ArrayList<>();
+    private List<ListEntry> mNewNotifList = new ArrayList<>();
+
+    private final PipelineState mPipelineState = new PipelineState();
+    private final Map<String, GroupEntry> mGroups = new ArrayMap<>();
+    private Collection<NotificationEntry> mAllEntries = Collections.emptyList();
+    private int mIterationCount = 0;
+
+    private final List<NotifFilter> mNotifPreGroupFilters = new ArrayList<>();
+    private final List<NotifPromoter> mNotifPromoters = new ArrayList<>();
+    private final List<NotifFilter> mNotifPreRenderFilters = new ArrayList<>();
+    private final List<NotifComparator> mNotifComparators = new ArrayList<>();
+    private final List<NotifSection> mNotifSections = new ArrayList<>();
+
+    private final List<OnBeforeTransformGroupsListener> mOnBeforeTransformGroupsListeners =
+            new ArrayList<>();
+    private final List<OnBeforeSortListener> mOnBeforeSortListeners =
+            new ArrayList<>();
+    private final List<OnBeforeRenderListListener> mOnBeforeRenderListListeners =
+            new ArrayList<>();
+    @Nullable private OnRenderListListener mOnRenderListListener;
+
+    private final List<ListEntry> mReadOnlyNotifList = Collections.unmodifiableList(mNotifList);
+
+    @Inject
+    public ShadeListBuilder(
+            SystemClock systemClock,
+            NotifLog notifLog,
+            DumpController dumpController) {
+        Assert.isMainThread();
+        mSystemClock = systemClock;
+        mNotifLog = notifLog;
+        dumpController.registerDumpable(TAG, this);
+    }
+
+    /**
+     * Attach the list builder to the NotifCollection. After this is called, it will start building
+     * the notif list in response to changes to the colletion.
+     */
+    public void attach(NotifCollection collection) {
+        Assert.isMainThread();
+        collection.setBuildListener(mReadyForBuildListener);
+    }
+
+    /**
+     * Registers the listener that's responsible for rendering the notif list to the screen. Called
+     * At the very end of pipeline execution, after all other listeners and pluggables have fired.
+     */
+    public void setOnRenderListListener(OnRenderListListener onRenderListListener) {
+        Assert.isMainThread();
+
+        mPipelineState.requireState(STATE_IDLE);
+        mOnRenderListListener = onRenderListListener;
+    }
+
+    void addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener listener) {
+        Assert.isMainThread();
+
+        mPipelineState.requireState(STATE_IDLE);
+        mOnBeforeTransformGroupsListeners.add(listener);
+    }
+
+    void addOnBeforeSortListener(OnBeforeSortListener listener) {
+        Assert.isMainThread();
+
+        mPipelineState.requireState(STATE_IDLE);
+        mOnBeforeSortListeners.add(listener);
+    }
+
+    void addOnBeforeRenderListListener(OnBeforeRenderListListener listener) {
+        Assert.isMainThread();
+
+        mPipelineState.requireState(STATE_IDLE);
+        mOnBeforeRenderListListeners.add(listener);
+    }
+
+    void addPreGroupFilter(NotifFilter filter) {
+        Assert.isMainThread();
+        mPipelineState.requireState(STATE_IDLE);
+
+        mNotifPreGroupFilters.add(filter);
+        filter.setInvalidationListener(this::onPreGroupFilterInvalidated);
+    }
+
+    void addPreRenderFilter(NotifFilter filter) {
+        Assert.isMainThread();
+        mPipelineState.requireState(STATE_IDLE);
+
+        mNotifPreRenderFilters.add(filter);
+        filter.setInvalidationListener(this::onPreRenderFilterInvalidated);
+    }
+
+    void addPromoter(NotifPromoter promoter) {
+        Assert.isMainThread();
+        mPipelineState.requireState(STATE_IDLE);
+
+        mNotifPromoters.add(promoter);
+        promoter.setInvalidationListener(this::onPromoterInvalidated);
+    }
+
+    void setSections(List<NotifSection> sections) {
+        Assert.isMainThread();
+        mPipelineState.requireState(STATE_IDLE);
+
+        mNotifSections.clear();
+        for (NotifSection section : sections) {
+            mNotifSections.add(section);
+            section.setInvalidationListener(this::onNotifSectionInvalidated);
+        }
+    }
+
+    void setComparators(List<NotifComparator> comparators) {
+        Assert.isMainThread();
+        mPipelineState.requireState(STATE_IDLE);
+
+        mNotifComparators.clear();
+        for (NotifComparator comparator : comparators) {
+            mNotifComparators.add(comparator);
+            comparator.setInvalidationListener(this::onNotifComparatorInvalidated);
+        }
+    }
+
+    List<ListEntry> getShadeList() {
+        Assert.isMainThread();
+        return mReadOnlyNotifList;
+    }
+
+    private final CollectionReadyForBuildListener mReadyForBuildListener =
+            new CollectionReadyForBuildListener() {
+                @Override
+                public void onBuildList(Collection<NotificationEntry> entries) {
+                    Assert.isMainThread();
+                    mPipelineState.requireIsBefore(STATE_BUILD_STARTED);
+
+                    mNotifLog.log(NotifEvent.ON_BUILD_LIST, "Request received from "
+                            + "NotifCollection");
+                    mAllEntries = entries;
+                    buildList();
+                }
+            };
+
+    private void onPreGroupFilterInvalidated(NotifFilter filter) {
+        Assert.isMainThread();
+
+        mNotifLog.log(NotifEvent.PRE_GROUP_FILTER_INVALIDATED, String.format(
+                "Filter \"%s\" invalidated; pipeline state is %d",
+                filter.getName(),
+                mPipelineState.getState()));
+
+        rebuildListIfBefore(STATE_PRE_GROUP_FILTERING);
+    }
+
+    private void onPromoterInvalidated(NotifPromoter filter) {
+        Assert.isMainThread();
+
+        mNotifLog.log(NotifEvent.PROMOTER_INVALIDATED, String.format(
+                "NotifPromoter \"%s\" invalidated; pipeline state is %d",
+                filter.getName(),
+                mPipelineState.getState()));
+
+        rebuildListIfBefore(STATE_TRANSFORMING);
+    }
+
+    private void onNotifSectionInvalidated(NotifSection section) {
+        Assert.isMainThread();
+
+        mNotifLog.log(NotifEvent.SECTION_INVALIDATED, String.format(
+                "Section \"%s\" invalidated; pipeline state is %d",
+                section.getName(),
+                mPipelineState.getState()));
+
+        rebuildListIfBefore(STATE_SORTING);
+    }
+
+    private void onPreRenderFilterInvalidated(NotifFilter filter) {
+        Assert.isMainThread();
+
+        mNotifLog.log(NotifEvent.PRE_RENDER_FILTER_INVALIDATED, String.format(
+                "Filter \"%s\" invalidated; pipeline state is %d",
+                filter.getName(),
+                mPipelineState.getState()));
+
+        rebuildListIfBefore(STATE_PRE_RENDER_FILTERING);
+    }
+
+    private void onNotifComparatorInvalidated(NotifComparator comparator) {
+        Assert.isMainThread();
+
+        mNotifLog.log(NotifEvent.COMPARATOR_INVALIDATED, String.format(
+                "Comparator \"%s\" invalidated; pipeline state is %d",
+                comparator.getName(),
+                mPipelineState.getState()));
+
+        rebuildListIfBefore(STATE_SORTING);
+    }
+
+    /**
+     * Points mNotifList to the list stored in mNewNotifList.
+     * Reuses the (emptied) mNotifList as mNewNotifList.
+     */
+    private void applyNewNotifList() {
+        mNotifList.clear();
+        List<ListEntry> emptyList = mNotifList;
+        mNotifList = mNewNotifList;
+        mNewNotifList = emptyList;
+    }
+
+    /**
+     * The core algorithm of the pipeline. See the top comment in {@link NotifPipeline} for
+     * details on our contracts with other code.
+     *
+     * Once the build starts we are very careful to protect against reentrant code. Anything that
+     * tries to invalidate itself after the pipeline has passed it by will return in an exception.
+     * In general, we should be extremely sensitive to client code doing things in the wrong order;
+     * if we detect that behavior, we should crash instantly.
+     */
+    private void buildList() {
+        mNotifLog.log(NotifEvent.START_BUILD_LIST, "Run #" + mIterationCount + "...");
+
+        mPipelineState.requireIsBefore(STATE_BUILD_STARTED);
+        mPipelineState.setState(STATE_BUILD_STARTED);
+
+        // Step 1: Reset notification states
+        mPipelineState.incrementTo(STATE_RESETTING);
+        resetNotifs();
+
+        // Step 2: Filter out any notifications that shouldn't be shown right now
+        mPipelineState.incrementTo(STATE_PRE_GROUP_FILTERING);
+        filterNotifs(mAllEntries, mNotifList, mNotifPreGroupFilters);
+
+        // Step 3: Group notifications with the same group key and set summaries
+        mPipelineState.incrementTo(STATE_GROUPING);
+        groupNotifs(mNotifList, mNewNotifList);
+        applyNewNotifList();
+        pruneIncompleteGroups(mNotifList);
+
+        // Step 4: Group transforming
+        // Move some notifs out of their groups and up to top-level (mostly used for heads-upping)
+        dispatchOnBeforeTransformGroups(mReadOnlyNotifList);
+        mPipelineState.incrementTo(STATE_TRANSFORMING);
+        promoteNotifs(mNotifList);
+        pruneIncompleteGroups(mNotifList);
+
+        // Step 5: Sort
+        // Assign each top-level entry a section, then sort the list by section and then within
+        // section by our list of custom comparators
+        dispatchOnBeforeSort(mReadOnlyNotifList);
+        mPipelineState.incrementTo(STATE_SORTING);
+        sortList();
+
+        // Step 6: Filter out entries after pre-group filtering, grouping, promoting and sorting
+        // Now filters can see grouping information to determine whether to filter or not.
+        mPipelineState.incrementTo(STATE_PRE_RENDER_FILTERING);
+        filterNotifs(mNotifList, mNewNotifList, mNotifPreRenderFilters);
+        applyNewNotifList();
+        pruneIncompleteGroups(mNotifList);
+
+        // Step 7: Lock in our group structure and log anything that's changed since the last run
+        mPipelineState.incrementTo(STATE_FINALIZING);
+        logParentingChanges();
+        freeEmptyGroups();
+
+        // Step 6: Dispatch the new list, first to any listeners and then to the view layer
+        mNotifLog.log(NotifEvent.DISPATCH_FINAL_LIST, "List finalized, is:\n"
+                + ListDumper.dumpTree(mNotifList, false, "\t\t"));
+        dispatchOnBeforeRenderList(mReadOnlyNotifList);
+        if (mOnRenderListListener != null) {
+            mOnRenderListListener.onRenderList(mReadOnlyNotifList);
+        }
+
+        // Step 7: We're done!
+        mNotifLog.log(NotifEvent.LIST_BUILD_COMPLETE,
+                "Notif list build #" + mIterationCount + " completed");
+        mPipelineState.setState(STATE_IDLE);
+        mIterationCount++;
+    }
+
+    private void resetNotifs() {
+        for (GroupEntry group : mGroups.values()) {
+            group.setPreviousParent(group.getParent());
+            group.setParent(null);
+            group.clearChildren();
+            group.setSummary(null);
+        }
+
+        for (NotificationEntry entry : mAllEntries) {
+            entry.setPreviousParent(entry.getParent());
+            entry.setParent(null);
+
+            if (entry.mFirstAddedIteration == -1) {
+                entry.mFirstAddedIteration = mIterationCount;
+            }
+        }
+
+        mNotifList.clear();
+    }
+
+    private void filterNotifs(Collection<? extends ListEntry> entries,
+            List<ListEntry> out, List<NotifFilter> filters) {
+        final long now = mSystemClock.uptimeMillis();
+        for (ListEntry entry : entries)  {
+            if (entry instanceof GroupEntry) {
+                final GroupEntry groupEntry = (GroupEntry) entry;
+
+                // apply filter on its summary
+                final NotificationEntry summary = groupEntry.getRepresentativeEntry();
+                if (applyFilters(summary, now, filters)) {
+                    groupEntry.setSummary(null);
+                    annulAddition(summary);
+                }
+
+                // apply filter on its children
+                final List<NotificationEntry> children = groupEntry.getRawChildren();
+                for (int j = children.size() - 1; j >= 0; j--) {
+                    final NotificationEntry child = children.get(j);
+                    if (applyFilters(child, now, filters)) {
+                        children.remove(child);
+                        annulAddition(child);
+                    }
+                }
+
+                out.add(groupEntry);
+            } else {
+                if (applyFilters((NotificationEntry) entry, now, filters)) {
+                    annulAddition(entry);
+                } else {
+                    out.add(entry);
+                }
+            }
+        }
+    }
+
+    private void groupNotifs(List<ListEntry> entries, List<ListEntry> out) {
+        for (ListEntry listEntry : entries) {
+            // since grouping hasn't happened yet, all notifs are NotificationEntries
+            NotificationEntry entry = (NotificationEntry) listEntry;
+            if (entry.getSbn().isGroup()) {
+                final String topLevelKey = entry.getSbn().getGroupKey();
+
+                GroupEntry group = mGroups.get(topLevelKey);
+                if (group == null) {
+                    group = new GroupEntry(topLevelKey);
+                    group.mFirstAddedIteration = mIterationCount;
+                    mGroups.put(topLevelKey, group);
+                }
+                if (group.getParent() == null) {
+                    group.setParent(ROOT_ENTRY);
+                    out.add(group);
+                }
+
+                entry.setParent(group);
+
+                if (entry.getSbn().getNotification().isGroupSummary()) {
+                    final NotificationEntry existingSummary = group.getSummary();
+
+                    if (existingSummary == null) {
+                        group.setSummary(entry);
+                    } else {
+                        mNotifLog.log(NotifEvent.WARN, String.format(
+                                "Duplicate summary for group '%s': '%s' vs. '%s'",
+                                group.getKey(),
+                                existingSummary.getKey(),
+                                entry.getKey()));
+
+                        // Use whichever one was posted most recently
+                        if (entry.getSbn().getPostTime()
+                                > existingSummary.getSbn().getPostTime()) {
+                            group.setSummary(entry);
+                            annulAddition(existingSummary, out);
+                        } else {
+                            annulAddition(entry, out);
+                        }
+                    }
+                } else {
+                    group.addChild(entry);
+                }
+
+            } else {
+
+                final String topLevelKey = entry.getKey();
+                if (mGroups.containsKey(topLevelKey)) {
+                    mNotifLog.log(NotifEvent.WARN,
+                            "Duplicate non-group top-level key: " + topLevelKey);
+                } else {
+                    entry.setParent(ROOT_ENTRY);
+                    out.add(entry);
+                }
+            }
+        }
+    }
+
+    private void promoteNotifs(List<ListEntry> list) {
+        for (int i = 0; i < list.size(); i++) {
+            final ListEntry tle = list.get(i);
+
+            if (tle instanceof GroupEntry) {
+                final GroupEntry group = (GroupEntry) tle;
+
+                group.getRawChildren().removeIf(child -> {
+                    final boolean shouldPromote = applyTopLevelPromoters(child);
+
+                    if (shouldPromote) {
+                        child.setParent(ROOT_ENTRY);
+                        list.add(child);
+                    }
+
+                    return shouldPromote;
+                });
+            }
+        }
+    }
+
+    private void pruneIncompleteGroups(List<ListEntry> shadeList) {
+        for (int i = 0; i < shadeList.size(); i++) {
+            final ListEntry tle = shadeList.get(i);
+
+            if (tle instanceof GroupEntry) {
+                final GroupEntry group = (GroupEntry) tle;
+                final List<NotificationEntry> children = group.getRawChildren();
+
+                if (group.getSummary() != null && children.size() == 0) {
+                    shadeList.remove(i);
+                    i--;
+
+                    NotificationEntry summary = group.getSummary();
+                    summary.setParent(ROOT_ENTRY);
+                    shadeList.add(summary);
+
+                    group.setSummary(null);
+                    annulAddition(group, shadeList);
+
+                } else if (group.getSummary() == null
+                        || children.size() < MIN_CHILDREN_FOR_GROUP) {
+                    // If the group doesn't provide a summary or is too small, ignore it and add
+                    // its children (if any) directly to top-level.
+
+                    shadeList.remove(i);
+                    i--;
+
+                    if (group.getSummary() != null) {
+                        final NotificationEntry summary = group.getSummary();
+                        group.setSummary(null);
+                        annulAddition(summary, shadeList);
+                    }
+
+                    for (int j = 0; j < children.size(); j++) {
+                        final NotificationEntry child = children.get(j);
+                        child.setParent(ROOT_ENTRY);
+                        shadeList.add(child);
+                    }
+                    children.clear();
+
+                    annulAddition(group, shadeList);
+                }
+            }
+        }
+    }
+
+    /**
+     * If a ListEntry was added to the shade list and then later removed (e.g. because it was a
+     * group that was broken up), this method will erase any bookkeeping traces of that addition
+     * and/or check that they were already erased.
+     *
+     * Before calling this method, the entry must already have been removed from its parent. If
+     * it's a group, its summary must be null and its children must be empty.
+     */
+    private void annulAddition(ListEntry entry, List<ListEntry> shadeList) {
+
+        // This function does very little, but if any of its assumptions are violated (and it has a
+        // lot of them), it will put the system into an inconsistent state. So we check all of them
+        // here.
+
+        if (entry.getParent() == null || entry.mFirstAddedIteration == -1) {
+            throw new IllegalStateException(
+                    "Cannot nullify addition of " + entry.getKey() + ": no such addition. ("
+                            + entry.getParent() + " " + entry.mFirstAddedIteration + ")");
+        }
+
+        if (entry.getParent() == ROOT_ENTRY) {
+            if (shadeList.contains(entry)) {
+                throw new IllegalStateException("Cannot nullify addition of " + entry.getKey()
+                        + ": it's still in the shade list.");
+            }
+        }
+
+        if (entry instanceof GroupEntry) {
+            GroupEntry ge = (GroupEntry) entry;
+            if (ge.getSummary() != null) {
+                throw new IllegalStateException(
+                        "Cannot nullify group " + ge.getKey() + ": summary is not null");
+            }
+            if (!ge.getChildren().isEmpty()) {
+                throw new IllegalStateException(
+                        "Cannot nullify group " + ge.getKey() + ": still has children");
+            }
+        } else if (entry instanceof NotificationEntry) {
+            if (entry == entry.getParent().getSummary()
+                    || entry.getParent().getChildren().contains(entry)) {
+                throw new IllegalStateException("Cannot nullify addition of child "
+                        + entry.getKey() + ": it's still attached to its parent.");
+            }
+        }
+
+        annulAddition(entry);
+
+    }
+
+    /**
+     * Erases bookkeeping traces stored on an entry when it is removed from the notif list.
+     * This can happen if the entry is removed from a group that was broken up or if the entry was
+     * filtered out during any of the filtering steps.
+     */
+    private void annulAddition(ListEntry entry) {
+        entry.setSection(-1);
+        entry.mNotifSection = null;
+        entry.setParent(null);
+        if (entry.mFirstAddedIteration == mIterationCount) {
+            entry.mFirstAddedIteration = -1;
+        }
+    }
+
+    private void sortList() {
+        // Assign sections to top-level elements and sort their children
+        for (ListEntry entry : mNotifList) {
+            Pair<NotifSection, Integer> sectionWithIndex = applySections(entry);
+            if (entry instanceof GroupEntry) {
+                GroupEntry parent = (GroupEntry) entry;
+                for (NotificationEntry child : parent.getChildren()) {
+                    child.mNotifSection = sectionWithIndex.first;
+                    child.setSection(sectionWithIndex.second);
+                }
+                parent.sortChildren(sChildComparator);
+            }
+        }
+
+        // Finally, sort all top-level elements
+        mNotifList.sort(mTopLevelComparator);
+    }
+
+    private void freeEmptyGroups() {
+        mGroups.values().removeIf(ge -> ge.getSummary() == null && ge.getChildren().isEmpty());
+    }
+
+    private void logParentingChanges() {
+        for (NotificationEntry entry : mAllEntries) {
+            if (entry.getParent() != entry.getPreviousParent()) {
+                mNotifLog.log(NotifEvent.PARENT_CHANGED, String.format(
+                        "%s: parent changed from %s to %s",
+                        entry.getKey(),
+                        entry.getPreviousParent() == null
+                                ? "null" : entry.getPreviousParent().getKey(),
+                        entry.getParent() == null
+                                ? "null" : entry.getParent().getKey()));
+            }
+        }
+        for (GroupEntry group : mGroups.values()) {
+            if (group.getParent() != group.getPreviousParent()) {
+                mNotifLog.log(NotifEvent.PARENT_CHANGED, String.format(
+                        "%s: parent changed from %s to %s",
+                        group.getKey(),
+                        group.getPreviousParent() == null
+                                ? "null" : group.getPreviousParent().getKey(),
+                        group.getParent() == null
+                                ? "null" : group.getParent().getKey()));
+            }
+        }
+    }
+
+    private final Comparator<ListEntry> mTopLevelComparator = (o1, o2) -> {
+
+        int cmp = Integer.compare(o1.getSection(), o2.getSection());
+
+        if (cmp == 0) {
+            for (int i = 0; i < mNotifComparators.size(); i++) {
+                cmp = mNotifComparators.get(i).compare(o1, o2);
+                if (cmp != 0) {
+                    break;
+                }
+            }
+        }
+
+        final NotificationEntry rep1 = o1.getRepresentativeEntry();
+        final NotificationEntry rep2 = o2.getRepresentativeEntry();
+
+        if (cmp == 0) {
+            cmp = rep1.getRanking().getRank() - rep2.getRanking().getRank();
+        }
+
+        if (cmp == 0) {
+            cmp = Long.compare(
+                    rep2.getSbn().getNotification().when,
+                    rep1.getSbn().getNotification().when);
+        }
+
+        return cmp;
+    };
+
+    private static final Comparator<NotificationEntry> sChildComparator = (o1, o2) -> {
+        int cmp = o1.getRanking().getRank() - o2.getRanking().getRank();
+
+        if (cmp == 0) {
+            cmp = Long.compare(
+                    o2.getSbn().getNotification().when,
+                    o1.getSbn().getNotification().when);
+        }
+
+        return cmp;
+    };
+
+    private boolean applyFilters(NotificationEntry entry, long now, List<NotifFilter> filters) {
+        NotifFilter filter = findRejectingFilter(entry, now, filters);
+
+        if (filter != entry.mExcludingFilter) {
+            if (entry.mExcludingFilter == null) {
+                mNotifLog.log(NotifEvent.FILTER_CHANGED, String.format(
+                        "%s: filtered out by '%s'",
+                        entry.getKey(),
+                        filter.getName()));
+            } else if (filter == null) {
+                mNotifLog.log(NotifEvent.FILTER_CHANGED, String.format(
+                        "%s: no longer filtered out (previous filter was '%s')",
+                        entry.getKey(),
+                        entry.mExcludingFilter.getName()));
+            } else {
+                mNotifLog.log(NotifEvent.FILTER_CHANGED, String.format(
+                        "%s: filter changed: '%s' -> '%s'",
+                        entry.getKey(),
+                        entry.mExcludingFilter,
+                        filter));
+            }
+
+            // Note that groups and summaries can also be filtered out later if they're part of a
+            // malformed group. We currently don't have a great way to track that beyond parenting
+            // change logs. Consider adding something similar to mExcludingFilter for them.
+            entry.mExcludingFilter = filter;
+        }
+
+        return filter != null;
+    }
+
+    @Nullable private static NotifFilter findRejectingFilter(NotificationEntry entry, long now,
+            List<NotifFilter> filters) {
+        final int size = filters.size();
+
+        for (int i = 0; i < size; i++) {
+            NotifFilter filter = filters.get(i);
+            if (filter.shouldFilterOut(entry, now)) {
+                return filter;
+            }
+        }
+        return null;
+    }
+
+    private boolean applyTopLevelPromoters(NotificationEntry entry) {
+        NotifPromoter promoter = findPromoter(entry);
+
+        if (promoter != entry.mNotifPromoter) {
+            if (entry.mNotifPromoter == null) {
+                mNotifLog.log(NotifEvent.PROMOTER_CHANGED, String.format(
+                        "%s: Entry promoted to top level by '%s'",
+                        entry.getKey(),
+                        promoter.getName()));
+            } else if (promoter == null) {
+                mNotifLog.log(NotifEvent.PROMOTER_CHANGED, String.format(
+                        "%s: Entry is no longer promoted to top level (previous promoter was '%s')",
+                        entry.getKey(),
+                        entry.mNotifPromoter.getName()));
+            } else {
+                mNotifLog.log(NotifEvent.PROMOTER_CHANGED, String.format(
+                        "%s: Top-level promoter changed: '%s' -> '%s'",
+                        entry.getKey(),
+                        entry.mNotifPromoter,
+                        promoter));
+            }
+            entry.mNotifPromoter = promoter;
+        }
+
+        return promoter != null;
+    }
+
+    @Nullable private NotifPromoter findPromoter(NotificationEntry entry) {
+        for (int i = 0; i < mNotifPromoters.size(); i++) {
+            NotifPromoter promoter = mNotifPromoters.get(i);
+            if (promoter.shouldPromoteToTopLevel(entry)) {
+                return promoter;
+            }
+        }
+        return null;
+    }
+
+    private Pair<NotifSection, Integer> applySections(ListEntry entry) {
+        final Pair<NotifSection, Integer> sectionWithIndex = findSection(entry);
+        final NotifSection section = sectionWithIndex.first;
+        final Integer sectionIndex = sectionWithIndex.second;
+
+        if (section != entry.mNotifSection) {
+            if (entry.mNotifSection == null) {
+                mNotifLog.log(NotifEvent.SECTION_CHANGED, String.format(
+                        "%s: sectioned by '%s' [index=%d].",
+                        entry.getKey(),
+                        section.getName(),
+                        sectionIndex));
+            } else {
+                mNotifLog.log(NotifEvent.SECTION_CHANGED, String.format(
+                        "%s: section changed: '%s' [index=%d] -> '%s [index=%d]'.",
+                        entry.getKey(),
+                        entry.mNotifSection,
+                        entry.getSection(),
+                        section,
+                        sectionIndex));
+            }
+
+            entry.mNotifSection = section;
+            entry.setSection(sectionIndex);
+        }
+
+        return sectionWithIndex;
+    }
+
+    private Pair<NotifSection, Integer> findSection(ListEntry entry) {
+        for (int i = 0; i < mNotifSections.size(); i++) {
+            NotifSection sectioner = mNotifSections.get(i);
+            if (sectioner.isInSection(entry)) {
+                return new Pair<>(sectioner, i);
+            }
+        }
+        return new Pair<>(sDefaultSection, mNotifSections.size());
+    }
+
+    private void rebuildListIfBefore(@PipelineState.StateName int state) {
+        mPipelineState.requireIsBefore(state);
+        if (mPipelineState.is(STATE_IDLE)) {
+            buildList();
+        }
+    }
+
+    private void dispatchOnBeforeTransformGroups(List<ListEntry> entries) {
+        for (int i = 0; i < mOnBeforeTransformGroupsListeners.size(); i++) {
+            mOnBeforeTransformGroupsListeners.get(i).onBeforeTransformGroups(entries);
+        }
+    }
+
+    private void dispatchOnBeforeSort(List<ListEntry> entries) {
+        for (int i = 0; i < mOnBeforeSortListeners.size(); i++) {
+            mOnBeforeSortListeners.get(i).onBeforeSort(entries);
+        }
+    }
+
+    private void dispatchOnBeforeRenderList(List<ListEntry> entries) {
+        for (int i = 0; i < mOnBeforeRenderListListeners.size(); i++) {
+            mOnBeforeRenderListListeners.get(i).onBeforeRenderList(entries);
+        }
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("\t" + TAG + " shade notifications:");
+        if (getShadeList().size() == 0) {
+            pw.println("\t\t None");
+        }
+
+        pw.println(ListDumper.dumpTree(
+                getShadeList(),
+                true,
+                "\t\t"));
+    }
+
+    /** See {@link #setOnRenderListListener(OnRenderListListener)} */
+    public interface OnRenderListListener {
+        /**
+         * Called with the final filtered, grouped, and sorted list.
+         *
+         * @param entries A read-only view into the current notif list. Note that this list is
+         *                backed by the live list and will change in response to new pipeline runs.
+         */
+        void onRenderList(List<ListEntry> entries);
+    }
+
+    private static final NotifSection sDefaultSection =
+            new NotifSection("DefaultSection") {
+                @Override
+                public boolean isInSection(ListEntry entry) {
+                    return true;
+                }
+            };
+
+    private static final String TAG = "NotifListBuilderImpl";
+
+    private static final int MIN_CHILDREN_FOR_GROUP = 2;
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/CoalescedEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/CoalescedEvent.kt
new file mode 100644
index 0000000..143de8a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/CoalescedEvent.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coalescer
+
+import android.service.notification.NotificationListenerService.Ranking
+import android.service.notification.StatusBarNotification
+
+data class CoalescedEvent(
+    val key: String,
+    var position: Int,
+    var sbn: StatusBarNotification,
+    var ranking: Ranking,
+    var batch: EventBatch?
+) {
+    override fun toString(): String {
+        return "CoalescedEvent(key=$key)"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/EventBatch.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/EventBatch.java
new file mode 100644
index 0000000..2eec68b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/EventBatch.java
@@ -0,0 +1,46 @@
+/*
+ * 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.statusbar.notification.collection.coalescer;
+
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a set of notification post events for a particular notification group.
+ */
+public class EventBatch {
+    /** SystemClock.uptimeMillis() */
+    final long mCreatedTimestamp;
+
+    /** SBN.getGroupKey -- same for all members */
+    final String mGroupKey;
+
+    /**
+     * All members of the batch. Must share the same group key. Includes both children and
+     * summaries.
+     */
+    final List<CoalescedEvent> mMembers = new ArrayList<>();
+
+    @Nullable Runnable mCancelShortTimeout;
+
+    EventBatch(long createdTimestamp, String groupKey) {
+        mCreatedTimestamp = createdTimestamp;
+        this.mGroupKey = groupKey;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java
new file mode 100644
index 0000000..f589038
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java
@@ -0,0 +1,332 @@
+/*
+ * 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.statusbar.notification.collection.coalescer;
+
+import static com.android.systemui.statusbar.notification.logging.NotifEvent.BATCH_MAX_TIMEOUT;
+import static com.android.systemui.statusbar.notification.logging.NotifEvent.COALESCED_EVENT;
+import static com.android.systemui.statusbar.notification.logging.NotifEvent.EARLY_BATCH_EMIT;
+import static com.android.systemui.statusbar.notification.logging.NotifEvent.EMIT_EVENT_BATCH;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.MainThread;
+import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.NotificationListenerService.RankingMap;
+import android.service.notification.StatusBarNotification;
+import android.util.ArrayMap;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.NotificationListener;
+import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
+import com.android.systemui.statusbar.notification.logging.NotifLog;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.time.SystemClock;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+
+/**
+ * An attempt to make posting notification groups an atomic process
+ *
+ * Due to the nature of the groups API, individual members of a group are posted to system server
+ * one at a time. This means that whenever a group member is posted, we don't know if there are any
+ * more members soon to be posted.
+ *
+ * The Coalescer sits between the NotificationListenerService and the NotifCollection. It clusters
+ * new notifications that are members of groups and delays their posting until any of the following
+ * criteria are met:
+ *
+ * - A few milliseconds pass (see groupLingerDuration on the constructor)
+ * - Any notification in the delayed group is updated
+ * - Any notification in the delayed group is retracted
+ *
+ * Once we cross this threshold, all members of the group in question are posted atomically to the
+ * NotifCollection. If this process was triggered by an update or removal, then that event is then
+ * passed along to the NotifCollection.
+ */
+@MainThread
+public class GroupCoalescer implements Dumpable {
+    private final DelayableExecutor mMainExecutor;
+    private final SystemClock mClock;
+    private final NotifLog mLog;
+    private final long mMinGroupLingerDuration;
+    private final long mMaxGroupLingerDuration;
+
+    private BatchableNotificationHandler mHandler;
+
+    private final Map<String, CoalescedEvent> mCoalescedEvents = new ArrayMap<>();
+    private final Map<String, EventBatch> mBatches = new ArrayMap<>();
+
+    @Inject
+    public GroupCoalescer(
+            @Main DelayableExecutor mainExecutor,
+            SystemClock clock, NotifLog log) {
+        this(mainExecutor, clock, log, MIN_GROUP_LINGER_DURATION, MAX_GROUP_LINGER_DURATION);
+    }
+
+    /**
+     * @param minGroupLingerDuration How long, in ms, to wait for another notification from the same
+     *                               group to arrive before emitting all pending events for that
+     *                               group. Each subsequent arrival of a group member resets the
+     *                               timer for that group.
+     * @param maxGroupLingerDuration The maximum time, in ms, that a group can linger in the
+     *                               coalescer before it's force-emitted.
+     */
+    GroupCoalescer(
+            @Main DelayableExecutor mainExecutor,
+            SystemClock clock,
+            NotifLog log,
+            long minGroupLingerDuration,
+            long maxGroupLingerDuration) {
+        mMainExecutor = mainExecutor;
+        mClock = clock;
+        mLog = log;
+        mMinGroupLingerDuration = minGroupLingerDuration;
+        mMaxGroupLingerDuration = maxGroupLingerDuration;
+    }
+
+    /**
+     * Attaches the coalescer to the pipeline, making it ready to receive events. Should only be
+     * called once.
+     */
+    public void attach(NotificationListener listenerService) {
+        listenerService.addNotificationHandler(mListener);
+    }
+
+    public void setNotificationHandler(BatchableNotificationHandler handler) {
+        mHandler = handler;
+    }
+
+    private final NotificationHandler mListener = new NotificationHandler() {
+        @Override
+        public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
+            maybeEmitBatch(sbn);
+            applyRanking(rankingMap);
+
+            final boolean shouldCoalesce = handleNotificationPosted(sbn, rankingMap);
+
+            if (shouldCoalesce) {
+                mLog.log(COALESCED_EVENT, String.format("Coalesced notification %s", sbn.getKey()));
+                mHandler.onNotificationRankingUpdate(rankingMap);
+            } else {
+                mHandler.onNotificationPosted(sbn, rankingMap);
+            }
+        }
+
+        @Override
+        public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
+            maybeEmitBatch(sbn);
+            applyRanking(rankingMap);
+            mHandler.onNotificationRemoved(sbn, rankingMap);
+        }
+
+        @Override
+        public void onNotificationRemoved(
+                StatusBarNotification sbn,
+                RankingMap rankingMap,
+                int reason) {
+            maybeEmitBatch(sbn);
+            applyRanking(rankingMap);
+            mHandler.onNotificationRemoved(sbn, rankingMap, reason);
+        }
+
+        @Override
+        public void onNotificationRankingUpdate(RankingMap rankingMap) {
+            applyRanking(rankingMap);
+            mHandler.onNotificationRankingUpdate(rankingMap);
+        }
+    };
+
+    private void maybeEmitBatch(StatusBarNotification sbn) {
+        final CoalescedEvent event = mCoalescedEvents.get(sbn.getKey());
+        final EventBatch batch = mBatches.get(sbn.getGroupKey());
+        if (event != null) {
+            mLog.log(EARLY_BATCH_EMIT,
+                    String.format("Modification of %s triggered early emit of batched group %s",
+                            sbn.getKey(), requireNonNull(event.getBatch()).mGroupKey));
+            emitBatch(requireNonNull(event.getBatch()));
+        } else if (batch != null
+                && mClock.uptimeMillis() - batch.mCreatedTimestamp >= mMaxGroupLingerDuration) {
+            mLog.log(BATCH_MAX_TIMEOUT,
+                    String.format("Modification of %s triggered timeout emit of batched group %s",
+                            sbn.getKey(), batch.mGroupKey));
+            emitBatch(batch);
+        }
+    }
+
+    /**
+     * @return True if the notification was coalesced and false otherwise.
+     */
+    private boolean handleNotificationPosted(
+            StatusBarNotification sbn,
+            RankingMap rankingMap) {
+
+        if (mCoalescedEvents.containsKey(sbn.getKey())) {
+            throw new IllegalStateException(
+                    "Notification has already been coalesced: " + sbn.getKey());
+        }
+
+        if (sbn.isGroup()) {
+            final EventBatch batch = getOrBuildBatch(sbn.getGroupKey());
+
+            CoalescedEvent event =
+                    new CoalescedEvent(
+                            sbn.getKey(),
+                            batch.mMembers.size(),
+                            sbn,
+                            requireRanking(rankingMap, sbn.getKey()),
+                            batch);
+            mCoalescedEvents.put(event.getKey(), event);
+
+            batch.mMembers.add(event);
+            resetShortTimeout(batch);
+
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private EventBatch getOrBuildBatch(final String groupKey) {
+        EventBatch batch = mBatches.get(groupKey);
+        if (batch == null) {
+            batch = new EventBatch(mClock.uptimeMillis(), groupKey);
+            mBatches.put(groupKey, batch);
+        }
+        return batch;
+    }
+
+    private void resetShortTimeout(EventBatch batch) {
+        if (batch.mCancelShortTimeout != null) {
+            batch.mCancelShortTimeout.run();
+        }
+        batch.mCancelShortTimeout =
+                mMainExecutor.executeDelayed(
+                        () -> {
+                            batch.mCancelShortTimeout = null;
+                            emitBatch(batch);
+                        },
+                        mMinGroupLingerDuration);
+    }
+
+    private void emitBatch(EventBatch batch) {
+        if (batch != mBatches.get(batch.mGroupKey)) {
+            throw new IllegalStateException("Cannot emit out-of-date batch " + batch.mGroupKey);
+        }
+        if (batch.mMembers.isEmpty()) {
+            throw new IllegalStateException("Batch " + batch.mGroupKey + " cannot be empty");
+        }
+        if (batch.mCancelShortTimeout != null) {
+            batch.mCancelShortTimeout.run();
+            batch.mCancelShortTimeout = null;
+        }
+
+        mBatches.remove(batch.mGroupKey);
+
+        final List<CoalescedEvent> events = new ArrayList<>(batch.mMembers);
+        for (CoalescedEvent event : events) {
+            mCoalescedEvents.remove(event.getKey());
+            event.setBatch(null);
+        }
+        events.sort(mEventComparator);
+
+        mLog.log(EMIT_EVENT_BATCH, "Emitting event batch for group " + batch.mGroupKey);
+
+        mHandler.onNotificationBatchPosted(events);
+    }
+
+    private Ranking requireRanking(RankingMap rankingMap, String key) {
+        Ranking ranking = new Ranking();
+        if (!rankingMap.getRanking(key, ranking)) {
+            throw new IllegalArgumentException("Ranking map does not contain key " + key);
+        }
+        return ranking;
+    }
+
+    private void applyRanking(RankingMap rankingMap) {
+        for (CoalescedEvent event : mCoalescedEvents.values()) {
+            Ranking ranking = new Ranking();
+            if (!rankingMap.getRanking(event.getKey(), ranking)) {
+                throw new IllegalStateException(
+                        "Ranking map doesn't contain key: " + event.getKey());
+            }
+            event.setRanking(ranking);
+        }
+    }
+
+    @Override
+    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+        long now = mClock.uptimeMillis();
+
+        int eventCount = 0;
+
+        pw.println();
+        pw.println("Coalesced notifications:");
+        for (EventBatch batch : mBatches.values()) {
+            pw.println("   Batch " + batch.mGroupKey + ":");
+            pw.println("       Created " + (now - batch.mCreatedTimestamp) + "ms ago");
+            for (CoalescedEvent event : batch.mMembers) {
+                pw.println("       " + event.getKey());
+                eventCount++;
+            }
+        }
+
+        if (eventCount != mCoalescedEvents.size()) {
+            pw.println("    ERROR: batches contain " + mCoalescedEvents.size() + " events but"
+                    + " am tracking " + mCoalescedEvents.size() + " total events");
+            pw.println("    All tracked events:");
+            for (CoalescedEvent event : mCoalescedEvents.values()) {
+                pw.println("        " + event.getKey());
+            }
+        }
+    }
+
+    private final Comparator<CoalescedEvent> mEventComparator = (o1, o2) -> {
+        int cmp = Boolean.compare(
+                o2.getSbn().getNotification().isGroupSummary(),
+                o1.getSbn().getNotification().isGroupSummary());
+        if (cmp == 0) {
+            cmp = o1.getPosition() - o2.getPosition();
+        }
+        return cmp;
+    };
+
+    /**
+     * Extension of {@link NotificationListener.NotificationHandler} to include notification
+     * groups.
+     */
+    public interface BatchableNotificationHandler extends NotificationHandler {
+        /**
+         * Fired whenever the coalescer needs to emit a batch of multiple post events. This is
+         * usually the addition of a new group, but can contain just a single event, or just an
+         * update to a subset of an existing group.
+         */
+        void onNotificationBatchPosted(List<CoalescedEvent> events);
+    }
+
+    private static final int MIN_GROUP_LINGER_DURATION = 50;
+    private static final int MAX_GROUP_LINGER_DURATION = 500;
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java
index 898918e..d8b2e40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java
@@ -16,27 +16,21 @@
 
 package com.android.systemui.statusbar.notification.collection.coordinator;
 
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
-import com.android.systemui.statusbar.notification.collection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.collection.NotifLifetimeExtender;
-import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
 
 /**
- * Interface for registering callbacks to the {@link NewNotifPipeline}.
- *
- * This includes registering:
- *  {@link Pluggable}s to the {@link NotifListBuilder}
- *  {@link NotifCollectionListener}s and {@link NotifLifetimeExtender}s to {@link NotifCollection}
+ * Interface for registering callbacks to the {@link NotifPipeline}.
  */
 public interface Coordinator {
-
     /**
      * Called after the NewNotifPipeline is initialized.
-     * Coordinators should register their {@link Pluggable}s to the notifListBuilder
-     * and their {@link NotifCollectionListener}s and {@link NotifLifetimeExtender}s
-     * to the notifCollection in this method.
+     * Coordinators should register their listeners and {@link Pluggable}s to the pipeline.
      */
-    void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder);
+    void attach(NotifPipeline pipeline);
+
+    default NotifSection getSection() {
+        return null;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
index 5e7dd98..625d1b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
@@ -23,9 +23,8 @@
 import android.os.RemoteException;
 import android.service.notification.StatusBarNotification;
 
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 
@@ -52,10 +51,10 @@
     }
 
     @Override
-    public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
+    public void attach(NotifPipeline pipeline) {
         mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
 
-        notifListBuilder.addPreGroupFilter(mNotifFilter);
+        pipeline.addPreGroupFilter(mNotifFilter);
     }
 
     private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java
index 62342b1..854444f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.notification.collection.coordinator;
 
 import android.app.Notification;
-import android.os.Handler;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.util.ArraySet;
@@ -25,12 +24,13 @@
 import com.android.systemui.ForegroundServiceController;
 import com.android.systemui.appops.AppOpsController;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
-import com.android.systemui.statusbar.notification.collection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.collection.NotifLifetimeExtender;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
+import com.android.systemui.util.Assert;
+import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -48,6 +48,8 @@
  *  frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceController
  *  frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener
  *  frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender
+ *
+ *  TODO: AppOps stuff should be spun off into its own coordinator
  */
 @Singleton
 public class ForegroundCoordinator implements Coordinator {
@@ -55,35 +57,35 @@
 
     private final ForegroundServiceController mForegroundServiceController;
     private final AppOpsController mAppOpsController;
-    private final Handler mMainHandler;
+    private final DelayableExecutor mMainExecutor;
 
-    private NotifCollection mNotifCollection;
+    private NotifPipeline mNotifPipeline;
 
     @Inject
     public ForegroundCoordinator(
             ForegroundServiceController foregroundServiceController,
             AppOpsController appOpsController,
-            @Main Handler mainHandler) {
+            @Main DelayableExecutor mainExecutor) {
         mForegroundServiceController = foregroundServiceController;
         mAppOpsController = appOpsController;
-        mMainHandler = mainHandler;
+        mMainExecutor = mainExecutor;
     }
 
     @Override
-    public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
-        mNotifCollection = notifCollection;
+    public void attach(NotifPipeline pipeline) {
+        mNotifPipeline = pipeline;
 
         // extend the lifetime of foreground notification services to show for at least 5 seconds
-        mNotifCollection.addNotificationLifetimeExtender(mForegroundLifetimeExtender);
+        mNotifPipeline.addNotificationLifetimeExtender(mForegroundLifetimeExtender);
 
         // listen for new notifications to add appOps
-        mNotifCollection.addCollectionListener(mNotifCollectionListener);
+        mNotifPipeline.addCollectionListener(mNotifCollectionListener);
+
+        // filter out foreground service notifications that aren't necessary anymore
+        mNotifPipeline.addPreGroupFilter(mNotifFilter);
 
         // when appOps change, update any relevant notifications to update appOps for
         mAppOpsController.addCallback(ForegroundServiceController.APP_OPS, this::onAppOpsChanged);
-
-        // filter out foreground service notifications that aren't necessary anymore
-        notifListBuilder.addPreGroupFilter(mNotifFilter);
     }
 
     /**
@@ -94,7 +96,8 @@
         public boolean shouldFilterOut(NotificationEntry entry, long now) {
             StatusBarNotification sbn = entry.getSbn();
             if (mForegroundServiceController.isDisclosureNotification(sbn)
-                    && !mForegroundServiceController.isDisclosureNeededForUser(sbn.getUserId())) {
+                    && !mForegroundServiceController.isDisclosureNeededForUser(
+                            sbn.getUser().getIdentifier())) {
                 return true;
             }
 
@@ -103,7 +106,7 @@
                         Notification.EXTRA_FOREGROUND_APPS);
                 if (apps != null && apps.length >= 1) {
                     if (!mForegroundServiceController.isSystemAlertWarningNeeded(
-                            sbn.getUserId(), apps[0])) {
+                            sbn.getUser().getIdentifier(), apps[0])) {
                         return true;
                     }
                 }
@@ -120,7 +123,7 @@
             new NotifLifetimeExtender() {
         private static final int MIN_FGS_TIME_MS = 5000;
         private OnEndLifetimeExtensionCallback mEndCallback;
-        private Map<String, Runnable> mEndRunnables = new HashMap<>();
+        private Map<NotificationEntry, Runnable> mEndRunnables = new HashMap<>();
 
         @Override
         public String getName() {
@@ -143,16 +146,18 @@
             final boolean extendLife = currTime - entry.getSbn().getPostTime() < MIN_FGS_TIME_MS;
 
             if (extendLife) {
-                if (!mEndRunnables.containsKey(entry.getKey())) {
-                    final Runnable runnable = new Runnable() {
-                        @Override
-                        public void run() {
-                            mEndCallback.onEndLifetimeExtension(mForegroundLifetimeExtender, entry);
-                        }
+                if (!mEndRunnables.containsKey(entry)) {
+                    final Runnable endExtensionRunnable = () -> {
+                        mEndRunnables.remove(entry);
+                        mEndCallback.onEndLifetimeExtension(
+                                mForegroundLifetimeExtender,
+                                entry);
                     };
-                    mEndRunnables.put(entry.getKey(), runnable);
-                    mMainHandler.postDelayed(runnable,
+
+                    final Runnable cancelRunnable = mMainExecutor.executeDelayed(
+                            endExtensionRunnable,
                             MIN_FGS_TIME_MS - (currTime - entry.getSbn().getPostTime()));
+                    mEndRunnables.put(entry, cancelRunnable);
                 }
             }
 
@@ -161,9 +166,9 @@
 
         @Override
         public void cancelLifetimeExtension(NotificationEntry entry) {
-            if (mEndRunnables.containsKey(entry.getKey())) {
-                Runnable endRunnable = mEndRunnables.remove(entry.getKey());
-                mMainHandler.removeCallbacks(endRunnable);
+            Runnable cancelRunnable = mEndRunnables.remove(entry);
+            if (cancelRunnable != null) {
+                cancelRunnable.run();
             }
         }
     };
@@ -185,25 +190,32 @@
         private void tagForeground(NotificationEntry entry) {
             final StatusBarNotification sbn = entry.getSbn();
             // note: requires that the ForegroundServiceController is updating their appOps first
-            ArraySet<Integer> activeOps = mForegroundServiceController.getAppOps(sbn.getUserId(),
-                    sbn.getPackageName());
+            ArraySet<Integer> activeOps =
+                    mForegroundServiceController.getAppOps(
+                            sbn.getUser().getIdentifier(),
+                            sbn.getPackageName());
             if (activeOps != null) {
-                synchronized (entry.mActiveAppOps) {
-                    entry.mActiveAppOps.clear();
-                    entry.mActiveAppOps.addAll(activeOps);
-                }
+                entry.mActiveAppOps.clear();
+                entry.mActiveAppOps.addAll(activeOps);
             }
         }
     };
 
+    private void onAppOpsChanged(int code, int uid, String packageName, boolean active) {
+        mMainExecutor.execute(() -> handleAppOpsChanged(code, uid, packageName, active));
+    }
+
     /**
      * Update the appOp for the posted notification associated with the current foreground service
+     *
      * @param code code for appOp to add/remove
      * @param uid of user the notification is sent to
      * @param packageName package that created the notification
      * @param active whether the appOpCode is active or not
      */
-    private void onAppOpsChanged(int code, int uid, String packageName, boolean active) {
+    private void handleAppOpsChanged(int code, int uid, String packageName, boolean active) {
+        Assert.isMainThread();
+
         int userId = UserHandle.getUserId(uid);
 
         // Update appOp if there's an associated posted notification:
@@ -215,22 +227,20 @@
                     && uid == entry.getSbn().getUid()
                     && packageName.equals(entry.getSbn().getPackageName())) {
                 boolean changed;
-                synchronized (entry.mActiveAppOps) {
-                    if (active) {
-                        changed = entry.mActiveAppOps.add(code);
-                    } else {
-                        changed = entry.mActiveAppOps.remove(code);
-                    }
+                if (active) {
+                    changed = entry.mActiveAppOps.add(code);
+                } else {
+                    changed = entry.mActiveAppOps.remove(code);
                 }
                 if (changed) {
-                    mMainHandler.post(mNotifFilter::invalidateList);
+                    mNotifFilter.invalidateList();
                 }
             }
         }
     }
 
     private NotificationEntry findNotificationEntryWithKey(String key) {
-        for (NotificationEntry entry : mNotifCollection.getNotifs()) {
+        for (NotificationEntry entry : mNotifPipeline.getActiveNotifs()) {
             if (entry.getKey().equals(key)) {
                 return entry;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
index 9312c22..a26ee545 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
@@ -39,10 +39,10 @@
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import javax.inject.Inject;
@@ -62,6 +62,7 @@
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final StatusBarStateController mStatusBarStateController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final HighPriorityProvider mHighPriorityProvider;
 
     @Inject
     public KeyguardCoordinator(
@@ -71,21 +72,22 @@
             NotificationLockscreenUserManager lockscreenUserManager,
             BroadcastDispatcher broadcastDispatcher,
             StatusBarStateController statusBarStateController,
-            KeyguardUpdateMonitor keyguardUpdateMonitor) {
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            HighPriorityProvider highPriorityProvider) {
         mContext = context;
         mMainHandler = mainThreadHandler;
         mKeyguardStateController = keyguardStateController;
         mLockscreenUserManager = lockscreenUserManager;
-
         mBroadcastDispatcher = broadcastDispatcher;
         mStatusBarStateController = statusBarStateController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mHighPriorityProvider = highPriorityProvider;
     }
 
     @Override
-    public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
+    public void attach(NotifPipeline pipeline) {
         setupInvalidateNotifListCallbacks();
-        notifListBuilder.addPreRenderFilter(mNotifFilter);
+        pipeline.addPreRenderFilter(mNotifFilter);
     }
 
     private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
@@ -151,7 +153,7 @@
         }
         if (NotificationUtils.useNewInterruptionModel(mContext)
                 && hideSilentNotificationsOnLockscreen()) {
-            return entry.isHighPriority();
+            return mHighPriorityProvider.isHighPriority(entry);
         } else {
             return entry.getRepresentativeEntry() != null
                     && !entry.getRepresentativeEntry().getRanking().isAmbient();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
index eeb54ab..8d0dd5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
@@ -16,13 +16,14 @@
 
 package com.android.systemui.statusbar.notification.collection.coordinator;
 
+import com.android.systemui.DumpController;
 import com.android.systemui.Dumpable;
 import com.android.systemui.statusbar.FeatureFlags;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
-import com.android.systemui.statusbar.notification.collection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.collection.NotifLifetimeExtender;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -33,25 +34,28 @@
 import javax.inject.Singleton;
 
 /**
- * Handles the attachment of the {@link NotifListBuilder} and {@link NotifCollection} to the
- * {@link Coordinator}s, so that the Coordinators can register their respective callbacks.
+ * Handles the attachment of {@link Coordinator}s to the {@link NotifPipeline} so that the
+ * Coordinators can register their respective callbacks.
  */
 @Singleton
 public class NotifCoordinators implements Dumpable {
     private static final String TAG = "NotifCoordinators";
     private final List<Coordinator> mCoordinators = new ArrayList<>();
-
+    private final List<NotifSection> mOrderedSections = new ArrayList<>();
     /**
      * Creates all the coordinators.
      */
     @Inject
     public NotifCoordinators(
+            DumpController dumpController,
             FeatureFlags featureFlags,
             KeyguardCoordinator keyguardCoordinator,
             RankingCoordinator rankingCoordinator,
             ForegroundCoordinator foregroundCoordinator,
             DeviceProvisionedCoordinator deviceProvisionedCoordinator,
             PreparationCoordinator preparationCoordinator) {
+        dumpController.registerDumpable(TAG, this);
+
         mCoordinators.add(keyguardCoordinator);
         mCoordinators.add(rankingCoordinator);
         mCoordinators.add(foregroundCoordinator);
@@ -60,25 +64,37 @@
             mCoordinators.add(preparationCoordinator);
         }
         // TODO: add new Coordinators here! (b/145134683, b/112656837)
+
+        // TODO: add the sections in a particular ORDER (HeadsUp < People < Alerting)
+        for (Coordinator c : mCoordinators) {
+            if (c.getSection() != null) {
+                mOrderedSections.add(c.getSection());
+            }
+        }
     }
 
     /**
-     * Sends the initialized notifListBuilder and notifCollection to each
-     * coordinator to indicate the notifListBuilder is ready to accept {@link Pluggable}s
-     * and the notifCollection is ready to accept {@link NotifCollectionListener}s and
-     * {@link NotifLifetimeExtender}s.
+     * Sends the pipeline to each coordinator when the pipeline is ready to accept
+     * {@link Pluggable}s, {@link NotifCollectionListener}s and {@link NotifLifetimeExtender}s.
      */
-    public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
+    public void attach(NotifPipeline pipeline) {
         for (Coordinator c : mCoordinators) {
-            c.attach(notifCollection, notifListBuilder);
+            c.attach(pipeline);
         }
+
+        pipeline.setSections(mOrderedSections);
     }
 
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println();
         pw.println(TAG + ":");
         for (Coordinator c : mCoordinators) {
             pw.println("\t" + c.getClass());
         }
+
+        for (NotifSection s : mOrderedSections) {
+            pw.println("\t" + s.getName());
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index a14f0e1..20c9cbc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -16,13 +16,12 @@
 
 package com.android.systemui.statusbar.notification.collection.coordinator;
 
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
-import com.android.systemui.statusbar.notification.collection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.collection.NotifInflater;
 import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
+import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.logging.NotifEvent;
 import com.android.systemui.statusbar.notification.logging.NotifLog;
 
@@ -55,10 +54,10 @@
     }
 
     @Override
-    public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
-        notifCollection.addCollectionListener(mNotifCollectionListener);
-        notifListBuilder.addPreRenderFilter(mNotifInflationErrorFilter);
-        notifListBuilder.addPreRenderFilter(mNotifInflatingFilter);
+    public void attach(NotifPipeline pipeline) {
+        pipeline.addCollectionListener(mNotifCollectionListener);
+        pipeline.addPreRenderFilter(mNotifInflationErrorFilter);
+        pipeline.addPreRenderFilter(mNotifInflatingFilter);
     }
 
     private final NotifCollectionListener mNotifCollectionListener = new NotifCollectionListener() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
index 0751aa8..7e9e760 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
@@ -17,9 +17,8 @@
 package com.android.systemui.statusbar.notification.collection.coordinator;
 
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 
 import javax.inject.Inject;
@@ -43,10 +42,10 @@
     }
 
     @Override
-    public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
+    public void attach(NotifPipeline pipeline) {
         mStatusBarStateController.addCallback(mStatusBarStateCallback);
 
-        notifListBuilder.addPreGroupFilter(mNotifFilter);
+        pipeline.addPreGroupFilter(mNotifFilter);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.java
new file mode 100644
index 0000000..ea0ece4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.java
@@ -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.
+ */
+
+package com.android.systemui.statusbar.notification.collection.inflation;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.coordinator.PreparationCoordinator;
+
+/**
+ * Used by the {@link PreparationCoordinator}.  When notifications are added or updated, the
+ * NotifInflater is asked to (re)inflated and prepare their views.  This inflation occurs off the
+ * main thread. When the inflation is finished, NotifInflater will trigger its InflationCallback.
+ */
+public interface NotifInflater {
+
+    /**
+     * Callback used when inflation is finished.
+     */
+    void setInflationCallback(InflationCallback callback);
+
+    /**
+     * Called to rebind the entry's views.
+     */
+    void rebindViews(NotificationEntry entry);
+
+    /**
+     * Called to inflate the views of an entry.  Views are not considered inflated until all of its
+     * views are bound. Once all views are inflated, the InflationCallback is triggered.
+     */
+    void inflateViews(NotificationEntry entry);
+
+    /**
+     * Request to stop the inflation of an entry.  For example, called when a notification is
+     * removed and no longer needs to be inflated.
+     */
+    void abortInflation(NotificationEntry entry);
+
+    /**
+     * Callback once all the views are inflated and bound for a given NotificationEntry.
+     */
+    interface InflationCallback {
+        void onInflationFinished(NotificationEntry entry);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java
new file mode 100644
index 0000000..3f500644
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java
@@ -0,0 +1,53 @@
+/*
+ * 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.statusbar.notification.collection.inflation;
+
+import android.annotation.Nullable;
+
+import com.android.systemui.statusbar.NotificationUiAdjustment;
+import com.android.systemui.statusbar.notification.InflationException;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+/**
+ * Used by the {@link NotificationEntryManager}. When notifications are added or updated, the binder
+ * is asked to (re)inflate and prepare their views. This inflation must occur off the main thread.
+ */
+public interface NotificationRowBinder {
+    /**
+     * Called when a notification has been added or updated. The binder must asynchronously inflate
+     * and bind the views associated with the notification.
+     *
+     * TODO: The caller is notified when the inflation completes, but this is currently a very
+     * roundabout business. Add an explicit completion/failure callback to this method.
+     */
+    void inflateViews(
+            NotificationEntry entry,
+            Runnable onDismissRunnable)
+            throws InflationException;
+
+    /**
+     * Called when the ranking has been updated (but not add or remove has been done). The binder
+     * should inspect the old and new adjustments and re-inflate the entry's views if necessary
+     * (e.g. if something important changed).
+     */
+    void onNotificationRankingUpdated(
+            NotificationEntry entry,
+            @Nullable Integer oldImportance,
+            NotificationUiAdjustment oldAdjustment,
+            NotificationUiAdjustment newAdjustment);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
new file mode 100644
index 0000000..2a7683a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -0,0 +1,309 @@
+/*
+ * 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.statusbar.notification.collection.inflation;
+
+import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
+import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+import android.view.ViewGroup;
+
+import com.android.internal.util.NotificationMessagingUtil;
+import com.android.systemui.R;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationUiAdjustment;
+import com.android.systemui.statusbar.notification.InflationException;
+import com.android.systemui.statusbar.notification.NotificationClicker;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
+import com.android.systemui.statusbar.notification.row.RowInflaterTask;
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+
+import java.util.Objects;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Provider;
+import javax.inject.Singleton;
+
+/** Handles inflating and updating views for notifications. */
+@Singleton
+public class NotificationRowBinderImpl implements NotificationRowBinder {
+
+    private static final String TAG = "NotificationViewManager";
+
+    private final NotificationGroupManager mGroupManager;
+    private final NotificationGutsManager mGutsManager;
+    private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
+    private final Context mContext;
+    private final NotificationRowContentBinder mRowContentBinder;
+    private final NotificationMessagingUtil mMessagingUtil;
+    private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger =
+            this::logNotificationExpansion;
+    private final NotificationRemoteInputManager mNotificationRemoteInputManager;
+    private final NotificationLockscreenUserManager mNotificationLockscreenUserManager;
+    private final boolean mAllowLongPress;
+    private final KeyguardBypassController mKeyguardBypassController;
+    private final StatusBarStateController mStatusBarStateController;
+
+    private NotificationPresenter mPresenter;
+    private NotificationListContainer mListContainer;
+    private HeadsUpManager mHeadsUpManager;
+    private NotificationRowContentBinder.InflationCallback mInflationCallback;
+    private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener;
+    private BindRowCallback mBindRowCallback;
+    private NotificationClicker mNotificationClicker;
+    private final Provider<RowInflaterTask> mRowInflaterTaskProvider;
+    private final NotificationLogger mNotificationLogger;
+
+    @Inject
+    public NotificationRowBinderImpl(
+            Context context,
+            NotificationRemoteInputManager notificationRemoteInputManager,
+            NotificationLockscreenUserManager notificationLockscreenUserManager,
+            NotificationRowContentBinder rowContentBinder,
+            @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
+            KeyguardBypassController keyguardBypassController,
+            StatusBarStateController statusBarStateController,
+            NotificationGroupManager notificationGroupManager,
+            NotificationGutsManager notificationGutsManager,
+            NotificationInterruptionStateProvider notificationInterruptionStateProvider,
+            Provider<RowInflaterTask> rowInflaterTaskProvider,
+            NotificationLogger logger) {
+        mContext = context;
+        mRowContentBinder = rowContentBinder;
+        mMessagingUtil = new NotificationMessagingUtil(context);
+        mNotificationRemoteInputManager = notificationRemoteInputManager;
+        mNotificationLockscreenUserManager = notificationLockscreenUserManager;
+        mAllowLongPress = allowLongPress;
+        mKeyguardBypassController = keyguardBypassController;
+        mStatusBarStateController = statusBarStateController;
+        mGroupManager = notificationGroupManager;
+        mGutsManager = notificationGutsManager;
+        mNotificationInterruptionStateProvider = notificationInterruptionStateProvider;
+        mRowInflaterTaskProvider = rowInflaterTaskProvider;
+        mNotificationLogger = logger;
+    }
+
+    /**
+     * Sets up late-bound dependencies for this component.
+     */
+    public void setUpWithPresenter(NotificationPresenter presenter,
+            NotificationListContainer listContainer,
+            HeadsUpManager headsUpManager,
+            BindRowCallback bindRowCallback) {
+        mPresenter = presenter;
+        mListContainer = listContainer;
+        mHeadsUpManager = headsUpManager;
+        mBindRowCallback = bindRowCallback;
+        mOnAppOpsClickListener = mGutsManager::openGuts;
+    }
+
+    public void setInflationCallback(NotificationRowContentBinder.InflationCallback callback) {
+        mInflationCallback = callback;
+    }
+
+    public void setNotificationClicker(NotificationClicker clicker) {
+        mNotificationClicker = clicker;
+    }
+
+    /**
+     * Inflates the views for the given entry (possibly asynchronously).
+     */
+    @Override
+    public void inflateViews(
+            NotificationEntry entry,
+            Runnable onDismissRunnable)
+            throws InflationException {
+        ViewGroup parent = mListContainer.getViewParentForNotification(entry);
+        PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
+                entry.getSbn().getUser().getIdentifier());
+
+        final StatusBarNotification sbn = entry.getSbn();
+        if (entry.rowExists()) {
+            entry.updateIcons(mContext, sbn);
+            entry.reset();
+            updateNotification(entry, pmUser, sbn, entry.getRow());
+            entry.getRow().setOnDismissRunnable(onDismissRunnable);
+        } else {
+            entry.createIcons(mContext, sbn);
+            mRowInflaterTaskProvider.get().inflate(mContext, parent, entry,
+                    row -> {
+                        bindRow(entry, pmUser, sbn, row, onDismissRunnable);
+                        updateNotification(entry, pmUser, sbn, row);
+                    });
+        }
+    }
+
+    private void bindRow(NotificationEntry entry, PackageManager pmUser,
+            StatusBarNotification sbn, ExpandableNotificationRow row,
+            Runnable onDismissRunnable) {
+        // Get the app name.
+        // Note that Notification.Builder#bindHeaderAppName has similar logic
+        // but since this field is used in the guts, it must be accurate.
+        // Therefore we will only show the application label, or, failing that, the
+        // package name. No substitutions.
+        final String pkg = sbn.getPackageName();
+        String appname = pkg;
+        try {
+            final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
+                    PackageManager.MATCH_UNINSTALLED_PACKAGES
+                            | PackageManager.MATCH_DISABLED_COMPONENTS);
+            if (info != null) {
+                appname = String.valueOf(pmUser.getApplicationLabel(info));
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            // Do nothing
+        }
+
+        row.initialize(
+                appname,
+                sbn.getKey(),
+                mExpansionLogger,
+                mKeyguardBypassController,
+                mGroupManager,
+                mHeadsUpManager,
+                mRowContentBinder,
+                mPresenter);
+
+        // TODO: Either move these into ExpandableNotificationRow#initialize or out of row entirely
+        row.setStatusBarStateController(mStatusBarStateController);
+        row.setInflationCallback(mInflationCallback);
+        row.setAppOpsOnClickListener(mOnAppOpsClickListener);
+        if (mAllowLongPress) {
+            row.setLongPressListener(mGutsManager::openGuts);
+        }
+        mListContainer.bindRow(row);
+        mNotificationRemoteInputManager.bindRow(row);
+
+        row.setOnDismissRunnable(onDismissRunnable);
+        row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+        if (ENABLE_REMOTE_INPUT) {
+            row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
+        }
+
+        mBindRowCallback.onBindRow(entry, pmUser, sbn, row);
+    }
+
+    /**
+     * Updates the views bound to an entry when the entry's ranking changes, either in-place or by
+     * reinflating them.
+     */
+    @Override
+    public void onNotificationRankingUpdated(
+            NotificationEntry entry,
+            @Nullable Integer oldImportance,
+            NotificationUiAdjustment oldAdjustment,
+            NotificationUiAdjustment newAdjustment) {
+        if (NotificationUiAdjustment.needReinflate(oldAdjustment, newAdjustment)) {
+            if (entry.rowExists()) {
+                entry.reset();
+                PackageManager pmUser = StatusBar.getPackageManagerForUser(
+                        mContext,
+                        entry.getSbn().getUser().getIdentifier());
+                updateNotification(entry, pmUser, entry.getSbn(), entry.getRow());
+            } else {
+                // Once the RowInflaterTask is done, it will pick up the updated entry, so
+                // no-op here.
+            }
+        } else {
+            if (oldImportance != null && entry.getImportance() != oldImportance) {
+                if (entry.rowExists()) {
+                    entry.getRow().onNotificationRankingUpdated();
+                }
+            }
+        }
+    }
+
+    //TODO: This method associates a row with an entry, but eventually needs to not do that
+    private void updateNotification(
+            NotificationEntry entry,
+            PackageManager pmUser,
+            StatusBarNotification sbn,
+            ExpandableNotificationRow row) {
+        row.setIsLowPriority(entry.isAmbient());
+
+        // Extract target SDK version.
+        try {
+            ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0);
+            entry.targetSdk = info.targetSdkVersion;
+        } catch (PackageManager.NameNotFoundException ex) {
+            Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
+        }
+        row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
+                && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
+
+        // TODO: should updates to the entry be happening somewhere else?
+        entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
+
+        entry.setRow(row);
+        row.setOnActivatedListener(mPresenter);
+
+        boolean useIncreasedCollapsedHeight =
+                mMessagingUtil.isImportantMessaging(sbn, entry.getImportance());
+        boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight
+                && !mPresenter.isPresenterFullyCollapsed();
+        row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
+        row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
+        row.setEntry(entry);
+
+        if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) {
+            row.setInflationFlags(FLAG_CONTENT_VIEW_HEADS_UP);
+        }
+        row.setNeedsRedaction(mNotificationLockscreenUserManager.needsRedaction(entry));
+        row.inflateViews();
+
+        // bind the click event to the content area
+        Objects.requireNonNull(mNotificationClicker).register(row, sbn);
+    }
+
+    private void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
+        mNotificationLogger.onExpansionChanged(key, userAction, expanded);
+    }
+
+    /** Callback for when a row is bound to an entry. */
+    public interface BindRowCallback {
+        /**
+         * Called when a new notification and row is created.
+         *
+         * @param entry  entry for the notification
+         * @param pmUser package manager for user
+         * @param sbn    notification
+         * @param row    row for the notification
+         */
+        void onBindRow(NotificationEntry entry, PackageManager pmUser,
+                StatusBarNotification sbn, ExpandableNotificationRow row);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/FakePipelineConsumer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/FakePipelineConsumer.java
index 986ee17..15f312d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/FakePipelineConsumer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/FakePipelineConsumer.java
@@ -19,8 +19,8 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
-import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -36,7 +36,7 @@
     private List<ListEntry> mEntries = Collections.emptyList();
 
     /** Attach the consumer to the pipeline. */
-    public void attach(NotifListBuilderImpl listBuilder) {
+    public void attach(ShadeListBuilder listBuilder) {
         listBuilder.setOnRenderListListener(this::onBuildComplete);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java
deleted file mode 100644
index 8d3d0ff..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java
+++ /dev/null
@@ -1,103 +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.statusbar.notification.collection.init;
-
-import android.util.Log;
-
-import com.android.systemui.DumpController;
-import com.android.systemui.Dumpable;
-import com.android.systemui.statusbar.FeatureFlags;
-import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
-import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
-import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
-import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
-import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinators;
-import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * Initialization code for the new notification pipeline.
- */
-@Singleton
-public class NewNotifPipeline implements Dumpable {
-    private final GroupCoalescer mGroupCoalescer;
-    private final NotifCollection mNotifCollection;
-    private final NotifListBuilderImpl mNotifPipeline;
-    private final NotifCoordinators mNotifPluggableCoordinators;
-    private final NotifInflaterImpl mNotifInflater;
-    private final DumpController mDumpController;
-    private final FeatureFlags mFeatureFlags;
-
-    private final FakePipelineConsumer mFakePipelineConsumer = new FakePipelineConsumer();
-
-    @Inject
-    public NewNotifPipeline(
-            GroupCoalescer groupCoalescer,
-            NotifCollection notifCollection,
-            NotifListBuilderImpl notifPipeline,
-            NotifCoordinators notifCoordinators,
-            NotifInflaterImpl notifInflater,
-            DumpController dumpController,
-            FeatureFlags featureFlags) {
-        mGroupCoalescer = groupCoalescer;
-        mNotifCollection = notifCollection;
-        mNotifPipeline = notifPipeline;
-        mNotifPluggableCoordinators = notifCoordinators;
-        mDumpController = dumpController;
-        mNotifInflater = notifInflater;
-        mFeatureFlags = featureFlags;
-    }
-
-    /** Hooks the new pipeline up to NotificationManager */
-    public void initialize(
-            NotificationListener notificationService,
-            NotificationRowBinderImpl rowBinder) {
-
-        mDumpController.registerDumpable("NotifPipeline", this);
-
-        // Setup inflation
-        if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
-            mNotifInflater.setRowBinder(rowBinder);
-        }
-
-        // Wire up coordinators
-        mFakePipelineConsumer.attach(mNotifPipeline);
-        mNotifPluggableCoordinators.attach(mNotifCollection, mNotifPipeline);
-
-        // Wire up pipeline
-        mNotifPipeline.attach(mNotifCollection);
-        mNotifCollection.attach(mGroupCoalescer);
-        mGroupCoalescer.attach(notificationService);
-
-        Log.d(TAG, "Notif pipeline initialized");
-    }
-
-    @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        mFakePipelineConsumer.dump(fd, pw, args);
-        mNotifPluggableCoordinators.dump(fd, pw, args);
-        mGroupCoalescer.dump(fd, pw, args);
-    }
-
-    private static final String TAG = "NewNotifPipeline";
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
new file mode 100644
index 0000000..959b002
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
@@ -0,0 +1,107 @@
+/*
+ * 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.statusbar.notification.collection.init;
+
+import android.util.Log;
+
+import com.android.systemui.DumpController;
+import com.android.systemui.Dumpable;
+import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.NotificationListener;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
+import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer;
+import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinators;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Initialization code for the new notification pipeline.
+ */
+@Singleton
+public class NotifPipelineInitializer implements Dumpable {
+    private final NotifPipeline mPipelineWrapper;
+    private final GroupCoalescer mGroupCoalescer;
+    private final NotifCollection mNotifCollection;
+    private final ShadeListBuilder mListBuilder;
+    private final NotifCoordinators mNotifPluggableCoordinators;
+    private final NotifInflaterImpl mNotifInflater;
+    private final DumpController mDumpController;
+    private final FeatureFlags mFeatureFlags;
+
+    private final FakePipelineConsumer mFakePipelineConsumer = new FakePipelineConsumer();
+
+    @Inject
+    public NotifPipelineInitializer(
+            NotifPipeline pipelineWrapper,
+            GroupCoalescer groupCoalescer,
+            NotifCollection notifCollection,
+            ShadeListBuilder listBuilder,
+            NotifCoordinators notifCoordinators,
+            NotifInflaterImpl notifInflater,
+            DumpController dumpController,
+            FeatureFlags featureFlags) {
+        mPipelineWrapper = pipelineWrapper;
+        mGroupCoalescer = groupCoalescer;
+        mNotifCollection = notifCollection;
+        mListBuilder = listBuilder;
+        mNotifPluggableCoordinators = notifCoordinators;
+        mDumpController = dumpController;
+        mNotifInflater = notifInflater;
+        mFeatureFlags = featureFlags;
+    }
+
+    /** Hooks the new pipeline up to NotificationManager */
+    public void initialize(
+            NotificationListener notificationService,
+            NotificationRowBinderImpl rowBinder) {
+
+        mDumpController.registerDumpable("NotifPipeline", this);
+
+        // Setup inflation
+        if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+            mNotifInflater.setRowBinder(rowBinder);
+        }
+
+        // Wire up coordinators
+        mNotifPluggableCoordinators.attach(mPipelineWrapper);
+
+        // Wire up pipeline
+        mFakePipelineConsumer.attach(mListBuilder);
+        mListBuilder.attach(mNotifCollection);
+        mNotifCollection.attach(mGroupCoalescer);
+        mGroupCoalescer.attach(notificationService);
+
+        Log.d(TAG, "Notif pipeline initialized");
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        mFakePipelineConsumer.dump(fd, pw, args);
+        mNotifPluggableCoordinators.dump(fd, pw, args);
+        mGroupCoalescer.dump(fd, pw, args);
+    }
+
+    private static final String TAG = "NotifPipeline";
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifListBuilder.java
deleted file mode 100644
index 7580924..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifListBuilder.java
+++ /dev/null
@@ -1,127 +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.statusbar.notification.collection.listbuilder;
-
-import com.android.systemui.statusbar.notification.collection.ListEntry;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.SectionsProvider;
-
-import java.util.List;
-
-/**
- * The system that constructs the current "notification list", the list of notifications that are
- * currently being displayed to the user.
- *
- * The pipeline proceeds through a series of stages in order to produce the final list (see below).
- * Each stage exposes hooks and listeners for other code to participate.
- *
- * This list differs from the canonical one we receive from system server in a few ways:
- * - Filtered: Some notifications are filtered out. For example, we filter out notifications whose
- *   views haven't been inflated yet. We also filter out some notifications if we're on the lock
- *   screen. To participate, see {@link #addFilter(NotifFilter)}.
- * - Grouped: Notifications that are part of the same group are clustered together into a single
- *   GroupEntry. These groups are then transformed in order to remove children or completely split
- *   them apart. To participate, see {@link #addPromoter(NotifPromoter)}.
- * - Sorted: All top-level notifications are sorted. To participate, see
- *   {@link #setSectionsProvider(SectionsProvider)} and {@link #setComparators(List)}
- *
- * The exact order of all hooks is as follows:
- *  0. Collection listeners are fired (see {@link NotifCollection}).
- *  1. NotifFilters are called on each notification currently in NotifCollection.
- *  2. Initial grouping is performed (NotificationEntries will have their parents set
- *     appropriately).
- *  3. OnBeforeTransformGroupListeners are fired
- *  4. NotifPromoters are called on each notification with a parent
- *  5. OnBeforeSortListeners are fired
- *  6. SectionsProvider is called on each top-level entry in the list
- *  7. The top-level entries are sorted using the provided NotifComparators (plus some additional
- *     built-in logic).
- *  8. OnBeforeRenderListListeners are fired
- *  9. The list is handed off to the view layer to be rendered.
- */
-public interface NotifListBuilder {
-
-    /**
-     * Registers a filter with the pipeline before grouping, promoting and sorting occurs. Filters
-     * are called on each notification in the order that they were registered. If any filter
-     * returns true, the notification is removed from the pipeline (and no other filters are
-     * called on that notif).
-     */
-    void addPreGroupFilter(NotifFilter filter);
-
-    /**
-     * Registers a promoter with the pipeline. Promoters are able to promote child notifications to
-     * top-level, i.e. move a notification that would be a child of a group and make it appear
-     * ungrouped. Promoters are called on each child notification in the order that they are
-     * registered. If any promoter returns true, the notification is removed from the group (and no
-     * other promoters are called on it).
-     */
-    void addPromoter(NotifPromoter promoter);
-
-    /**
-     * Assigns sections to each top-level entry, where a section is simply an integer. Sections are
-     * the primary metric by which top-level entries are sorted; NotifComparators are only consulted
-     * when two entries are in the same section. The pipeline doesn't assign any particular meaning
-     * to section IDs -- from it's perspective they're just numbers and it sorts them by a simple
-     * numerical comparison.
-     */
-    void setSectionsProvider(SectionsProvider provider);
-
-    /**
-     * Comparators that are used to sort top-level entries that share the same section. The
-     * comparators are executed in order until one of them returns a non-zero result. If all return
-     * zero, the pipeline falls back to sorting by rank (and, failing that, Notification.when).
-     */
-    void setComparators(List<NotifComparator> comparators);
-
-    /**
-     * Registers a filter with the pipeline to filter right before rendering the list (after
-     * pre-group filtering, grouping, promoting and sorting occurs). Filters are
-     * called on each notification in the order that they were registered. If any filter returns
-     * true, the notification is removed from the pipeline (and no other filters are called on that
-     * notif).
-     */
-    void addPreRenderFilter(NotifFilter filter);
-
-    /**
-     * Called after notifications have been filtered and after the initial grouping has been
-     * performed but before NotifPromoters have had a chance to promote children out of groups.
-     */
-    void addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener listener);
-
-    /**
-     * Called after notifs have been filtered and groups have been determined but before sections
-     * have been determined or the notifs have been sorted.
-     */
-    void addOnBeforeSortListener(OnBeforeSortListener listener);
-
-    /**
-     * Called at the end of the pipeline after the notif list has been finalized but before it has
-     * been handed off to the view layer.
-     */
-    void addOnBeforeRenderListListener(OnBeforeRenderListListener listener);
-
-    /**
-     * Returns a read-only view in to the current notification list. If this method is called
-     * during pipeline execution it will return the current state of the list, which will likely
-     * be only partially-generated.
-     */
-    List<ListEntry> getActiveNotifs();
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeRenderListListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeRenderListListener.java
index f6ca12d..44a27a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeRenderListListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeRenderListListener.java
@@ -17,10 +17,11 @@
 package com.android.systemui.statusbar.notification.collection.listbuilder;
 
 import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 
 import java.util.List;
 
-/** See {@link NotifListBuilder#addOnBeforeRenderListListener(OnBeforeRenderListListener)} */
+/** See {@link NotifPipeline#addOnBeforeRenderListListener(OnBeforeRenderListListener)} */
 public interface OnBeforeRenderListListener {
     /**
      * Called at the end of the pipeline after the notif list has been finalized but before it has
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeSortListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeSortListener.java
index 7be7ac0..56cfe5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeSortListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeSortListener.java
@@ -17,10 +17,11 @@
 package com.android.systemui.statusbar.notification.collection.listbuilder;
 
 import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 
 import java.util.List;
 
-/** See {@link NotifListBuilder#addOnBeforeSortListener(OnBeforeSortListener)} */
+/** See {@link NotifPipeline#addOnBeforeSortListener(OnBeforeSortListener)} */
 public interface OnBeforeSortListener {
     /**
      * Called after the notif list has been filtered and grouped but before sections have been
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java
index d7a0815..0dc4df0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java
@@ -17,13 +17,14 @@
 package com.android.systemui.statusbar.notification.collection.listbuilder;
 
 import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
 
 import java.util.List;
 
 /**
  * See
- * {@link NotifListBuilder#addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener)}
+ * {@link NotifPipeline#addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener)}
  */
 public interface OnBeforeTransformGroupsListener {
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
index 084d038..1897ba2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
@@ -18,13 +18,13 @@
 
 import android.annotation.IntDef;
 
-import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
+import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
 /**
- * Used by {@link NotifListBuilderImpl} to track its internal state machine.
+ * Used by {@link ShadeListBuilder} to track its internal state machine.
  */
 public class PipelineState {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java
index a191c83..0d150ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java
@@ -17,13 +17,13 @@
 package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable;
 
 import com.android.systemui.statusbar.notification.collection.ListEntry;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 
 import java.util.Comparator;
 import java.util.List;
 
 /**
- * Pluggable for participating in notif sorting. See {@link NotifListBuilder#setComparators(List)}.
+ * Pluggable for participating in notif sorting. See {@link NotifPipeline#setComparators(List)}.
  */
 public abstract class NotifComparator
         extends Pluggable<NotifComparator>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java
index e6189ed..8f575cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java
@@ -16,12 +16,12 @@
 
 package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable;
 
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
 
 /**
  * Pluggable for participating in notif filtering.
- * See {@link NotifListBuilder#addPreGroupFilter} and {@link NotifListBuilder#addPreRenderFilter}.
+ * See {@link NotifPipeline#addPreGroupFilter} and {@link NotifPipeline#addPreRenderFilter}.
  */
 public abstract class NotifFilter extends Pluggable<NotifFilter> {
     protected NotifFilter(String name) {
@@ -35,9 +35,9 @@
      * however. If another filter returns true before yours, we'll skip straight to the next notif.
      *
      * @param entry The entry in question.
-     *              If this filter is registered via {@link NotifListBuilder#addPreGroupFilter},
+     *              If this filter is registered via {@link NotifPipeline#addPreGroupFilter},
      *              this entry will not have any grouping nor sorting information.
-     *              If this filter is registered via {@link NotifListBuilder#addPreRenderFilter},
+     *              If this filter is registered via {@link NotifPipeline#addPreRenderFilter},
      *              this entry will have grouping and sorting information.
      * @param now A timestamp in SystemClock.uptimeMillis that represents "now" for the purposes of
      *            pipeline execution. This value will be the same for all pluggable calls made
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifPromoter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifPromoter.java
index 84e16f4..5fce446 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifPromoter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifPromoter.java
@@ -16,13 +16,13 @@
 
 package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable;
 
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
 
 /**
  *  Pluggable for participating in notif promotion. Notif promoters can upgrade notifications
  *  from being children of a group to top-level notifications. See
- *  {@link NotifListBuilder#addPromoter(NotifPromoter)}.
+ *  {@link NotifPipeline#addPromoter}.
  */
 public abstract class NotifPromoter extends Pluggable<NotifPromoter> {
     protected NotifPromoter(String name) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSection.java
new file mode 100644
index 0000000..fe5ba3c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSection.java
@@ -0,0 +1,37 @@
+/*
+ * 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.statusbar.notification.collection.listbuilder.pluggable;
+
+import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
+
+/**
+ * Pluggable for participating in notif sectioning. See {@link ShadeListBuilder#setSections}.
+ */
+public abstract class NotifSection extends Pluggable<NotifSection> {
+    protected NotifSection(String name) {
+        super(name);
+    }
+
+    /**
+     * If returns true, this notification is considered within this section.
+     * Sectioning is performed on each top level notification each time the pipeline is run.
+     * However, this doesn't necessarily mean that your section will get called on each top-level
+     * notification. The first section to return true determines the section of the notification.
+     */
+    public abstract boolean isInSection(ListEntry entry);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java
index f9ce197..4270408 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java
@@ -18,10 +18,10 @@
 
 import android.annotation.Nullable;
 
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 
 /**
- * Generic superclass for chunks of code that can plug into the {@link NotifListBuilder}.
+ * Generic superclass for chunks of code that can plug into the {@link NotifPipeline}.
  *
  * A pluggable is fundamentally three things:
  * 1. A name (for debugging purposes)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/SectionsProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/SectionsProvider.java
deleted file mode 100644
index 11ea850..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/SectionsProvider.java
+++ /dev/null
@@ -1,37 +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.statusbar.notification.collection.listbuilder.pluggable;
-
-import com.android.systemui.statusbar.notification.collection.ListEntry;
-
-/**
- * Interface for sorting notifications into "sections", such as a heads-upping section, people
- * section, alerting section, silent section, etc.
- */
-public abstract class SectionsProvider extends Pluggable<SectionsProvider> {
-
-    protected SectionsProvider(String name) {
-        super(name);
-    }
-
-    /**
-     * Returns the section that this entry belongs to. A section can be any non-negative integer.
-     * When entries are sorted, they are first sorted by section and then by any remainining
-     * comparators.
-     */
-    public abstract int getSection(ListEntry entry);
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CoalescedEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CoalescedEvent.kt
deleted file mode 100644
index b6218b4..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CoalescedEvent.kt
+++ /dev/null
@@ -1,32 +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.statusbar.notification.collection.notifcollection
-
-import android.service.notification.NotificationListenerService.Ranking
-import android.service.notification.StatusBarNotification
-
-data class CoalescedEvent(
-    val key: String,
-    var position: Int,
-    var sbn: StatusBarNotification,
-    var ranking: Ranking,
-    var batch: EventBatch?
-) {
-    override fun toString(): String {
-        return "CoalescedEvent(key=$key)"
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java
new file mode 100644
index 0000000..4023474
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java
@@ -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.
+ */
+
+package com.android.systemui.statusbar.notification.collection.notifcollection;
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+import java.util.Collection;
+
+/**
+ * Interface for the class responsible for converting a NotifCollection into the final sorted,
+ * filtered, and grouped list of currently visible notifications.
+ */
+public interface CollectionReadyForBuildListener {
+    /**
+     * Called by the NotifCollection to indicate that something in the collection has changed and
+     * that the list builder should regenerate the list.
+     */
+    void onBuildList(Collection<NotificationEntry> entries);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/DismissedByUserStats.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/DismissedByUserStats.java
new file mode 100644
index 0000000..b268686
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/DismissedByUserStats.java
@@ -0,0 +1,38 @@
+/*
+ * 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.statusbar.notification.collection.notifcollection;
+
+import android.service.notification.NotificationStats.DismissalSentiment;
+import android.service.notification.NotificationStats.DismissalSurface;
+
+import com.android.internal.statusbar.NotificationVisibility;
+
+/** Information that must be supplied when dismissing a notification on the behalf of the user. */
+public class DismissedByUserStats {
+    public final @DismissalSurface int dismissalSurface;
+    public final @DismissalSentiment int dismissalSentiment;
+    public final NotificationVisibility notificationVisibility;
+
+    public DismissedByUserStats(
+            @DismissalSurface int dismissalSurface,
+            @DismissalSentiment int dismissalSentiment,
+            NotificationVisibility notificationVisibility) {
+        this.dismissalSurface = dismissalSurface;
+        this.dismissalSentiment = dismissalSentiment;
+        this.notificationVisibility = notificationVisibility;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/EventBatch.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/EventBatch.java
deleted file mode 100644
index ac51178..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/EventBatch.java
+++ /dev/null
@@ -1,42 +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.statusbar.notification.collection.notifcollection;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Represents a set of notification post events for a particular notification group.
- */
-public class EventBatch {
-    /** SystemClock.uptimeMillis() */
-    final long mCreatedTimestamp;
-
-    /** SBN.getGroupKey -- same for all members */
-    final String mGroupKey;
-
-    /**
-     * All members of the batch. Must share the same group key. Includes both children and
-     * summaries.
-     */
-    final List<CoalescedEvent> mMembers = new ArrayList<>();
-
-    EventBatch(long createdTimestamp, String groupKey) {
-        mCreatedTimestamp = createdTimestamp;
-        this.mGroupKey = groupKey;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescer.java
deleted file mode 100644
index 069c15f..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescer.java
+++ /dev/null
@@ -1,303 +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.statusbar.notification.collection.notifcollection;
-
-import static com.android.systemui.statusbar.notification.logging.NotifEvent.COALESCED_EVENT;
-import static com.android.systemui.statusbar.notification.logging.NotifEvent.EARLY_BATCH_EMIT;
-import static com.android.systemui.statusbar.notification.logging.NotifEvent.EMIT_EVENT_BATCH;
-
-import static java.util.Objects.requireNonNull;
-
-import android.annotation.MainThread;
-import android.service.notification.NotificationListenerService.Ranking;
-import android.service.notification.NotificationListenerService.RankingMap;
-import android.service.notification.StatusBarNotification;
-import android.util.ArrayMap;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.Dumpable;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
-import com.android.systemui.statusbar.notification.logging.NotifLog;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.time.SystemClock;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-
-import javax.inject.Inject;
-
-/**
- * An attempt to make posting notification groups an atomic process
- *
- * Due to the nature of the groups API, individual members of a group are posted to system server
- * one at a time. This means that whenever a group member is posted, we don't know if there are any
- * more members soon to be posted.
- *
- * The Coalescer sits between the NotificationListenerService and the NotifCollection. It clusters
- * new notifications that are members of groups and delays their posting until any of the following
- * criteria are met:
- *
- * - A few milliseconds pass (see groupLingerDuration on the constructor)
- * - Any notification in the delayed group is updated
- * - Any notification in the delayed group is retracted
- *
- * Once we cross this threshold, all members of the group in question are posted atomically to the
- * NotifCollection. If this process was triggered by an update or removal, then that event is then
- * passed along to the NotifCollection.
- */
-@MainThread
-public class GroupCoalescer implements Dumpable {
-    private final DelayableExecutor mMainExecutor;
-    private final SystemClock mClock;
-    private final NotifLog mLog;
-    private final long mGroupLingerDuration;
-
-    private BatchableNotificationHandler mHandler;
-
-    private final Map<String, CoalescedEvent> mCoalescedEvents = new ArrayMap<>();
-    private final Map<String, EventBatch> mBatches = new ArrayMap<>();
-
-    @Inject
-    public GroupCoalescer(
-            @Main DelayableExecutor mainExecutor,
-            SystemClock clock, NotifLog log) {
-        this(mainExecutor, clock, log, GROUP_LINGER_DURATION);
-    }
-
-    /**
-     * @param groupLingerDuration How long, in ms, that notifications that are members of a group
-     *                            are delayed within the GroupCoalescer before being posted
-     */
-    GroupCoalescer(
-            @Main DelayableExecutor mainExecutor,
-            SystemClock clock,
-            NotifLog log,
-            long groupLingerDuration) {
-        mMainExecutor = mainExecutor;
-        mClock = clock;
-        mLog = log;
-        mGroupLingerDuration = groupLingerDuration;
-    }
-
-    /**
-     * Attaches the coalescer to the pipeline, making it ready to receive events. Should only be
-     * called once.
-     */
-    public void attach(NotificationListener listenerService) {
-        listenerService.addNotificationHandler(mListener);
-    }
-
-    public void setNotificationHandler(BatchableNotificationHandler handler) {
-        mHandler = handler;
-    }
-
-    private final NotificationHandler mListener = new NotificationHandler() {
-        @Override
-        public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
-            maybeEmitBatch(sbn.getKey());
-            applyRanking(rankingMap);
-
-            final boolean shouldCoalesce = handleNotificationPosted(sbn, rankingMap);
-
-            if (shouldCoalesce) {
-                mLog.log(COALESCED_EVENT, String.format("Coalesced notification %s", sbn.getKey()));
-                mHandler.onNotificationRankingUpdate(rankingMap);
-            } else {
-                mHandler.onNotificationPosted(sbn, rankingMap);
-            }
-        }
-
-        @Override
-        public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
-            maybeEmitBatch(sbn.getKey());
-            applyRanking(rankingMap);
-            mHandler.onNotificationRemoved(sbn, rankingMap);
-        }
-
-        @Override
-        public void onNotificationRemoved(
-                StatusBarNotification sbn,
-                RankingMap rankingMap,
-                int reason) {
-            maybeEmitBatch(sbn.getKey());
-            applyRanking(rankingMap);
-            mHandler.onNotificationRemoved(sbn, rankingMap, reason);
-        }
-
-        @Override
-        public void onNotificationRankingUpdate(RankingMap rankingMap) {
-            applyRanking(rankingMap);
-            mHandler.onNotificationRankingUpdate(rankingMap);
-        }
-    };
-
-    private void maybeEmitBatch(String memberKey) {
-        CoalescedEvent event = mCoalescedEvents.get(memberKey);
-        if (event != null) {
-            mLog.log(EARLY_BATCH_EMIT,
-                    String.format("Modification of %s triggered early emit of batched group %s",
-                            memberKey, requireNonNull(event.getBatch()).mGroupKey));
-            emitBatch(requireNonNull(event.getBatch()));
-        }
-    }
-
-    /**
-     * @return True if the notification was coalesced and false otherwise.
-     */
-    private boolean handleNotificationPosted(
-            StatusBarNotification sbn,
-            RankingMap rankingMap) {
-
-        if (mCoalescedEvents.containsKey(sbn.getKey())) {
-            throw new IllegalStateException(
-                    "Notification has already been coalesced: " + sbn.getKey());
-        }
-
-        if (sbn.isGroup()) {
-            EventBatch batch = startBatchingGroup(sbn.getGroupKey());
-            CoalescedEvent event =
-                    new CoalescedEvent(
-                            sbn.getKey(),
-                            batch.mMembers.size(),
-                            sbn,
-                            requireRanking(rankingMap, sbn.getKey()),
-                            batch);
-
-            batch.mMembers.add(event);
-
-            mCoalescedEvents.put(event.getKey(), event);
-
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    private EventBatch startBatchingGroup(final String groupKey) {
-        EventBatch batch = mBatches.get(groupKey);
-        if (batch == null) {
-            final EventBatch newBatch = new EventBatch(mClock.uptimeMillis(), groupKey);
-            mBatches.put(groupKey, newBatch);
-            mMainExecutor.executeDelayed(() -> emitBatch(newBatch), mGroupLingerDuration);
-
-            batch = newBatch;
-        }
-        return batch;
-    }
-
-    private void emitBatch(EventBatch batch) {
-        if (batch != mBatches.get(batch.mGroupKey)) {
-            // If we emit a batch early, we don't want to emit it a second time when its timeout
-            // expires.
-            return;
-        }
-        if (batch.mMembers.isEmpty()) {
-            throw new IllegalStateException("Batch " + batch.mGroupKey + " cannot be empty");
-        }
-
-        mBatches.remove(batch.mGroupKey);
-
-        final List<CoalescedEvent> events = new ArrayList<>(batch.mMembers);
-        for (CoalescedEvent event : events) {
-            mCoalescedEvents.remove(event.getKey());
-            event.setBatch(null);
-        }
-        events.sort(mEventComparator);
-
-        mLog.log(EMIT_EVENT_BATCH, "Emitting event batch for group " + batch.mGroupKey);
-
-        mHandler.onNotificationBatchPosted(events);
-    }
-
-    private Ranking requireRanking(RankingMap rankingMap, String key) {
-        Ranking ranking = new Ranking();
-        if (!rankingMap.getRanking(key, ranking)) {
-            throw new IllegalArgumentException("Ranking map does not contain key " + key);
-        }
-        return ranking;
-    }
-
-    private void applyRanking(RankingMap rankingMap) {
-        for (CoalescedEvent event : mCoalescedEvents.values()) {
-            Ranking ranking = new Ranking();
-            if (!rankingMap.getRanking(event.getKey(), ranking)) {
-                throw new IllegalStateException(
-                        "Ranking map doesn't contain key: " + event.getKey());
-            }
-            event.setRanking(ranking);
-        }
-    }
-
-    @Override
-    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
-        long now = mClock.uptimeMillis();
-
-        int eventCount = 0;
-
-        pw.println();
-        pw.println("Coalesced notifications:");
-        for (EventBatch batch : mBatches.values()) {
-            pw.println("   Batch " + batch.mGroupKey + ":");
-            pw.println("       Created" + (now - batch.mCreatedTimestamp) + "ms ago");
-            for (CoalescedEvent event : batch.mMembers) {
-                pw.println("       " + event.getKey());
-                eventCount++;
-            }
-        }
-
-        if (eventCount != mCoalescedEvents.size()) {
-            pw.println("    ERROR: batches contain " + mCoalescedEvents.size() + " events but"
-                    + " am tracking " + mCoalescedEvents.size() + " total events");
-            pw.println("    All tracked events:");
-            for (CoalescedEvent event : mCoalescedEvents.values()) {
-                pw.println("        " + event.getKey());
-            }
-        }
-    }
-
-    private final Comparator<CoalescedEvent> mEventComparator = (o1, o2) -> {
-        int cmp = Boolean.compare(
-                o2.getSbn().getNotification().isGroupSummary(),
-                o1.getSbn().getNotification().isGroupSummary());
-        if (cmp == 0) {
-            cmp = o1.getPosition() - o2.getPosition();
-        }
-        return cmp;
-    };
-
-    /**
-     * Extension of {@link NotificationListener.NotificationHandler} to include notification
-     * groups.
-     */
-    public interface BatchableNotificationHandler extends NotificationHandler {
-        /**
-         * Fired whenever the coalescer needs to emit a batch of multiple post events. This is
-         * usually the addition of a new group, but can contain just a single event, or just an
-         * update to a subset of an existing group.
-         */
-        void onNotificationBatchPosted(List<CoalescedEvent> events);
-    }
-
-    private static final int GROUP_LINGER_DURATION = 40;
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
new file mode 100644
index 0000000..9cbc7d7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
@@ -0,0 +1,50 @@
+/*
+ * 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.statusbar.notification.collection.notifcollection;
+
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+/**
+ * Listener interface for {@link NotifCollection}.
+ */
+public interface NotifCollectionListener {
+    /**
+     * Called whenever a notification with a new key is posted.
+     */
+    default void onEntryAdded(NotificationEntry entry) {
+    }
+
+    /**
+     * Called whenever a notification with the same key as an existing notification is posted. By
+     * the time this listener is called, the entry's SBN and Ranking will already have been updated.
+     */
+    default void onEntryUpdated(NotificationEntry entry) {
+    }
+
+    /**
+     * Called whenever a notification is retracted by system server. This method is not called
+     * immediately after a user dismisses a notification: we wait until we receive confirmation from
+     * system server before considering the notification removed.
+     */
+    default void onEntryRemoved(
+            NotificationEntry entry,
+            @CancellationReason int reason,
+            boolean removedByUser) {
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java
new file mode 100644
index 0000000..05f5ea8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.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.systemui.statusbar.notification.collection.notifcollection;
+
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+/**
+ * A way for other code to temporarily extend the lifetime of a notification after it has been
+ * retracted. See {@link NotifCollection#addNotificationLifetimeExtender(NotifLifetimeExtender)}.
+ */
+public interface NotifLifetimeExtender {
+    /** Name to associate with this extender (for the purposes of debugging) */
+    String getName();
+
+    /**
+     * Called on the extender immediately after it has been registered. The extender should hang on
+     * to this callback and execute it whenever it no longer needs to extend the lifetime of a
+     * notification.
+     */
+    void setCallback(OnEndLifetimeExtensionCallback callback);
+
+    /**
+     * Called by the NotifCollection whenever a notification has been retracted (by the app) or
+     * dismissed (by the user). If the extender returns true, it is considered to be extending the
+     * lifetime of that notification. Lifetime-extended notifications are kept around until all
+     * active extenders expire their extension by calling onEndLifetimeExtension(). This method is
+     * called on all lifetime extenders even if earlier ones return true (in other words, multiple
+     * lifetime extenders can be extending a notification at the same time).
+     */
+    boolean shouldExtendLifetime(NotificationEntry entry, @CancellationReason int reason);
+
+    /**
+     * Called by the NotifCollection to inform a lifetime extender that its extension of a notif
+     * is no longer valid (usually because the notif has been reposted and so no longer needs
+     * lifetime extension). The extender should clean up any references it has to the notif in
+     * question.
+     */
+    void cancelLifetimeExtension(NotificationEntry entry);
+
+    /** Callback for notifying the NotifCollection that a lifetime extension has expired. */
+    interface OnEndLifetimeExtensionCallback {
+        void onEndLifetimeExtension(NotifLifetimeExtender extender, NotificationEntry entry);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DerivedMember.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DerivedMember.java
deleted file mode 100644
index 815e6f7..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DerivedMember.java
+++ /dev/null
@@ -1,62 +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.statusbar.notification.collection.provider;
-/**
- * Caches a computed value until invalidate() is called
- * @param <Parent> Object used to computeValue
- * @param <Value> type of value to cache until invalidate is called
- */
-public abstract class DerivedMember<Parent, Value> {
-    private Value mValue;
-    protected abstract Value computeValue(Parent parent);
-
-    /**
-     * Gets the last cached value, else recomputes the value.
-     */
-    public Value get(Parent parent) {
-        if (mValue == null) {
-            mValue = computeValue(parent);
-        }
-        return mValue;
-    }
-
-    /**
-     * Resets the cached value.
-     * Next time "get" is called, the value is recomputed.
-     */
-    public void invalidate() {
-        mValue = null;
-    }
-
-    /**
-     * Called when a NotificationEntry's status bar notification has updated.
-     * Derived members can invalidate here.
-     */
-    public void onSbnUpdated() {}
-
-    /**
-     * Called when a NotificationEntry's Ranking has updated.
-     * Derived members can invalidate here.
-     */
-    public void onRankingUpdated() {}
-
-    /**
-     * Called when a ListEntry's grouping information (parent or children) has changed.
-     * Derived members can invalidate here.
-     */
-    public void onGroupingUpdated() {}
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
new file mode 100644
index 0000000..b496116
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
@@ -0,0 +1,110 @@
+/*
+ * 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.statusbar.notification.collection.provider;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+
+import com.android.systemui.statusbar.notification.collection.GroupEntry;
+import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Determines whether a notification is considered 'high priority'.
+ *
+ * Notifications that are high priority are visible on the lock screen/status bar and in the top
+ * section in the shade.
+ */
+@Singleton
+public class HighPriorityProvider {
+    private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
+
+    @Inject
+    public HighPriorityProvider(PeopleNotificationIdentifier peopleNotificationIdentifier) {
+        mPeopleNotificationIdentifier = peopleNotificationIdentifier;
+    }
+
+    /**
+     * @return true if the ListEntry is high priority, else false
+     *
+     * A NotificationEntry is considered high priority if it:
+     *  - has importance greater than or equal to IMPORTANCE_DEFAULT
+     *  OR
+     *  - their importance has NOT been set to a low priority option by the user AND the
+     *  notification fulfills one of the following:
+     *      - has a person associated with it
+     *      - has a media session associated with it
+     *      - has messaging style
+     *
+     * A GroupEntry is considered high priority if its representativeEntry (summary) or children are
+     * high priority
+     */
+    public boolean isHighPriority(ListEntry entry) {
+        if (entry == null) {
+            return false;
+        }
+
+        final NotificationEntry notifEntry = entry.getRepresentativeEntry();
+        return notifEntry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_DEFAULT
+                || hasHighPriorityCharacteristics(notifEntry)
+                || hasHighPriorityChild(entry);
+    }
+
+
+    private boolean hasHighPriorityChild(ListEntry entry) {
+        if (entry instanceof GroupEntry) {
+            for (NotificationEntry child : ((GroupEntry) entry).getChildren()) {
+                if (isHighPriority(child)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private boolean hasHighPriorityCharacteristics(NotificationEntry entry) {
+        return !hasUserSetImportance(entry)
+                && (isImportantOngoing(entry)
+                || entry.getSbn().getNotification().hasMediaSession()
+                || isPeopleNotification(entry)
+                || isMessagingStyle(entry));
+    }
+
+    private boolean isImportantOngoing(NotificationEntry entry) {
+        return entry.getSbn().getNotification().isForegroundService()
+                && entry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_LOW;
+    }
+
+    private boolean isMessagingStyle(NotificationEntry entry) {
+        return Notification.MessagingStyle.class.equals(
+                entry.getSbn().getNotification().getNotificationStyle());
+    }
+
+    private boolean isPeopleNotification(NotificationEntry entry) {
+        return mPeopleNotificationIdentifier.isPeopleNotification(
+                entry.getSbn(), entry.getRanking());
+    }
+
+    private boolean hasUserSetImportance(NotificationEntry entry) {
+        return entry.getRanking().getChannel() != null
+                && entry.getRanking().getChannel().hasUserSetImportance();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java
deleted file mode 100644
index 76e256b..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java
+++ /dev/null
@@ -1,148 +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.statusbar.notification.collection.provider;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.Person;
-
-import com.android.systemui.Dependency;
-import com.android.systemui.statusbar.notification.collection.GroupEntry;
-import com.android.systemui.statusbar.notification.collection.ListEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Whether the ListEntry is shown to the user as a high priority notification: visible on
- * the lock screen/status bar and in the top section in the shade.
- *
- * A NotificationEntry is considered high priority if it:
- *  - has importance greater than or equal to IMPORTANCE_DEFAULT
- *  OR
- *  - their importance has NOT been set to a low priority option by the user AND the notification
- *  fulfills one of the following:
- *      - has a person associated with it
- *      - has a media session associated with it
- *      - has messaging style
- *
- * A GroupEntry is considered high priority if its representativeEntry (summary) or children are
- * high priority
- */
-public class IsHighPriorityProvider extends DerivedMember<ListEntry, Boolean> {
-    // TODO: (b/145659174) remove groupManager when moving to NewNotifPipeline. Logic
-    //  replaced in GroupEntry and NotifListBuilderImpl
-    private final NotificationGroupManager mGroupManager;
-
-
-    public IsHighPriorityProvider() {
-        // TODO: (b/145659174) remove
-        mGroupManager = Dependency.get(NotificationGroupManager.class);
-    }
-
-    @Override
-    protected Boolean computeValue(ListEntry entry) {
-        if (entry == null) {
-            return false;
-        }
-
-        return isHighPriority(entry);
-    }
-
-    private boolean isHighPriority(ListEntry listEntry) {
-        // requires groups have been set (AFTER PipelineState.STATE_TRANSFORMING)
-        final NotificationEntry notifEntry = listEntry.getRepresentativeEntry();
-        return notifEntry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_DEFAULT
-                || hasHighPriorityCharacteristics(notifEntry)
-                || hasHighPriorityChild(listEntry);
-
-    }
-
-    private boolean hasHighPriorityChild(ListEntry entry) {
-        // TODO: (b/145659174) remove
-        if (entry instanceof NotificationEntry) {
-            NotificationEntry notifEntry = (NotificationEntry) entry;
-            if (mGroupManager.isSummaryOfGroup(notifEntry.getSbn())) {
-                List<NotificationEntry> logicalChildren =
-                        mGroupManager.getLogicalChildren(notifEntry.getSbn());
-                for (NotificationEntry child : logicalChildren) {
-                    if (child.isHighPriority()) {
-                        return true;
-                    }
-                }
-            }
-        }
-
-        if (entry instanceof GroupEntry) {
-            for (NotificationEntry child : ((GroupEntry) entry).getChildren()) {
-                if (child.isHighPriority()) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    private boolean hasHighPriorityCharacteristics(NotificationEntry entry) {
-        return !hasUserSetImportance(entry)
-                && (isImportantOngoing(entry)
-                || entry.getSbn().getNotification().hasMediaSession()
-                || hasPerson(entry)
-                || isMessagingStyle(entry));
-    }
-
-    private boolean isImportantOngoing(NotificationEntry entry) {
-        return entry.getSbn().getNotification().isForegroundService()
-                && entry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_LOW;
-    }
-
-    private boolean isMessagingStyle(NotificationEntry entry) {
-        return Notification.MessagingStyle.class.equals(
-                entry.getSbn().getNotification().getNotificationStyle());
-    }
-
-    private boolean hasPerson(NotificationEntry entry) {
-        // TODO: cache favorite and recent contacts to check contact affinity
-        Notification notification = entry.getSbn().getNotification();
-        ArrayList<Person> people = notification.extras != null
-                ? notification.extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST)
-                : new ArrayList<>();
-        return people != null && !people.isEmpty();
-    }
-
-    private boolean hasUserSetImportance(NotificationEntry entry) {
-        return entry.getRanking().getChannel() != null
-                && entry.getRanking().getChannel().hasUserSetImportance();
-    }
-
-    @Override
-    public void onSbnUpdated() {
-        invalidate();
-    }
-
-    @Override
-    public void onRankingUpdated() {
-        invalidate();
-    }
-
-    @Override
-    public void onGroupingUpdated() {
-        invalidate();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
index c6c36ee..9adceb7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
@@ -22,8 +22,8 @@
 
 import com.android.systemui.log.RichEvent;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
-import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer;
+import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
+import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -67,7 +67,7 @@
     }
 
     /**
-     * @return if this event occurred in {@link NotifListBuilder}
+     * @return if this event occurred in {@link ShadeListBuilder}
      */
     static boolean isListBuilderEvent(@EventType int type) {
         return isBetweenInclusive(type, 0, TOTAL_LIST_BUILDER_EVENT_TYPES);
@@ -94,7 +94,7 @@
             LIST_BUILD_COMPLETE,
             PRE_GROUP_FILTER_INVALIDATED,
             PROMOTER_INVALIDATED,
-            SECTIONS_PROVIDER_INVALIDATED,
+            SECTION_INVALIDATED,
             COMPARATOR_INVALIDATED,
             PARENT_CHANGED,
             FILTER_CHANGED,
@@ -132,12 +132,13 @@
                     "ListBuildComplete",
                     "FilterInvalidated",
                     "PromoterInvalidated",
-                    "SectionsProviderInvalidated",
+                    "SectionInvalidated",
                     "ComparatorInvalidated",
                     "ParentChanged",
                     "FilterChanged",
                     "PromoterChanged",
                     "FinalFilterInvalidated",
+                    "SectionerChanged",
 
                     // NEM event labels:
                     "NotifAdded",
@@ -155,13 +156,14 @@
                     // GroupCoalescer labels:
                     "CoalescedEvent",
                     "EarlyBatchEmit",
-                    "EmitEventBatch"
+                    "EmitEventBatch",
+                    "BatchMaxTimeout"
             };
 
     private static final int TOTAL_EVENT_LABELS = EVENT_LABELS.length;
 
     /**
-     * Events related to {@link NotifListBuilder}
+     * Events related to {@link ShadeListBuilder}
      */
     public static final int WARN = 0;
     public static final int ON_BUILD_LIST = 1;
@@ -170,13 +172,14 @@
     public static final int LIST_BUILD_COMPLETE = 4;
     public static final int PRE_GROUP_FILTER_INVALIDATED = 5;
     public static final int PROMOTER_INVALIDATED = 6;
-    public static final int SECTIONS_PROVIDER_INVALIDATED = 7;
+    public static final int SECTION_INVALIDATED = 7;
     public static final int COMPARATOR_INVALIDATED = 8;
     public static final int PARENT_CHANGED = 9;
     public static final int FILTER_CHANGED = 10;
     public static final int PROMOTER_CHANGED = 11;
     public static final int PRE_RENDER_FILTER_INVALIDATED = 12;
-    private static final int TOTAL_LIST_BUILDER_EVENT_TYPES = 13;
+    public static final int SECTION_CHANGED = 13;
+    private static final int TOTAL_LIST_BUILDER_EVENT_TYPES = 14;
 
     /**
      * Events related to {@link NotificationEntryManager}
@@ -204,5 +207,6 @@
     public static final int COALESCED_EVENT = COALESCER_EVENT_START_INDEX;
     public static final int EARLY_BATCH_EMIT = COALESCER_EVENT_START_INDEX + 1;
     public static final int EMIT_EVENT_BATCH = COALESCER_EVENT_START_INDEX + 2;
+    public static final int BATCH_MAX_TIMEOUT = COALESCER_EVENT_START_INDEX + 3;
     private static final int TOTAL_COALESCER_EVENT_TYPES = 3;
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
index 78eaf3e..4672de0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
@@ -16,13 +16,13 @@
 
 package com.android.systemui.statusbar.notification.people
 
-import android.app.Notification
+import android.service.notification.NotificationListenerService.Ranking
 import android.service.notification.StatusBarNotification
 import javax.inject.Inject
 import javax.inject.Singleton
 
 interface PeopleNotificationIdentifier {
-    fun isPeopleNotification(sbn: StatusBarNotification): Boolean
+    fun isPeopleNotification(sbn: StatusBarNotification, ranking: Ranking): Boolean
 }
 
 @Singleton
@@ -30,7 +30,6 @@
     private val personExtractor: NotificationPersonExtractor
 ) : PeopleNotificationIdentifier {
 
-    override fun isPeopleNotification(sbn: StatusBarNotification) =
-            sbn.notification.notificationStyle == Notification.MessagingStyle::class.java ||
-                    personExtractor.isPersonNotification(sbn)
+    override fun isPeopleNotification(sbn: StatusBarNotification, ranking: Ranking) =
+            ranking.isConversation || personExtractor.isPersonNotification(sbn)
 }
\ No newline at end of file
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
new file mode 100644
index 0000000..18993ff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
@@ -0,0 +1,47 @@
+/*
+ * 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.statusbar.notification.row;
+
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.systemui.plugins.FalsingManager;
+
+import javax.inject.Inject;
+
+/**
+ * Controller for {@link ActivatableNotificationView}
+ */
+public class ActivatableNotificationViewController {
+    private final ActivatableNotificationView mView;
+    private final AccessibilityManager mAccessibilityManager;
+    private final FalsingManager mFalsingManager;
+
+    @Inject
+    public ActivatableNotificationViewController(ActivatableNotificationView view,
+            AccessibilityManager accessibilityManager, FalsingManager falsingManager) {
+        mView = view;
+        mAccessibilityManager = accessibilityManager;
+        mFalsingManager = falsingManager;
+    }
+
+    /**
+     * Initialize the controller, setting up handlers and other behavior.
+     */
+    public void init() {
+
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
index 315ea0a..4f27c0f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
@@ -35,6 +35,7 @@
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import android.view.Window
 import android.view.WindowInsets.Type
+import android.view.WindowInsets.Type.statusBars
 import android.view.WindowManager
 import android.widget.TextView
 import com.android.internal.annotations.VisibleForTesting
@@ -288,13 +289,13 @@
                 setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
                 addFlags(wmFlags)
                 setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL)
-                setFitWindowInsetsTypes(getFitWindowInsetsTypes() and Type.statusBars().inv())
                 setWindowAnimations(com.android.internal.R.style.Animation_InputMethod)
 
                 attributes = attributes.apply {
                     format = PixelFormat.TRANSLUCENT
                     title = ChannelEditorDialogController::class.java.simpleName
                     gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
+                    fitInsetsTypes = attributes.fitInsetsTypes and statusBars().inv()
                     width = MATCH_PARENT
                     height = WRAP_CONTENT
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 3c247df..b71beda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -65,7 +65,6 @@
 import android.widget.Chronometer;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
-import android.widget.RemoteViews;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
@@ -150,7 +149,7 @@
     private StatusBarStateController mStatusbarStateController;
     private KeyguardBypassController mBypassController;
     private LayoutListener mLayoutListener;
-    private final NotificationContentInflater mNotificationInflater;
+    private NotificationRowContentBinder mNotificationContentBinder;
     private int mIconTransformContentShift;
     private int mIconTransformContentShiftNoIcon;
     private int mMaxHeadsUpHeightBeforeN;
@@ -464,7 +463,7 @@
      * Inflate views based off the inflation flags set. Inflation happens asynchronously.
      */
     public void inflateViews() {
-        mNotificationInflater.bindContent(mEntry, this, mInflationFlags, mBindParams,
+        mNotificationContentBinder.bindContent(mEntry, this, mInflationFlags, mBindParams,
                 false /* forceInflate */, mInflationCallback);
     }
 
@@ -478,7 +477,7 @@
         // View should not be reinflated in the future
         clearInflationFlags(inflationFlag);
         Runnable freeViewRunnable =
-                () -> mNotificationInflater.unbindContent(mEntry, this, inflationFlag);
+                () -> mNotificationContentBinder.unbindContent(mEntry, this, inflationFlag);
         switch (inflationFlag) {
             case FLAG_CONTENT_VIEW_HEADS_UP:
                 getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_HEADSUP,
@@ -742,23 +741,10 @@
         return mIsHeadsUp || mHeadsupDisappearRunning;
     }
 
-
-    public void setGroupManager(NotificationGroupManager groupManager) {
-        mGroupManager = groupManager;
-        mPrivateLayout.setGroupManager(groupManager);
-    }
-
     public void setRemoteInputController(RemoteInputController r) {
         mPrivateLayout.setRemoteInputController(r);
     }
 
-    public void setAppName(String appName) {
-        mAppName = appName;
-        if (mMenuRow != null && mMenuRow.getMenuView() != null) {
-            mMenuRow.setAppName(mAppName);
-        }
-    }
-
     public void addChildNotification(ExpandableNotificationRow row) {
         addChildNotification(row, -1);
     }
@@ -852,7 +838,7 @@
             mIsChildInGroup = isChildInGroup;
             if (mIsLowPriority) {
                 int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED;
-                mNotificationInflater.bindContent(mEntry, this, flags, mBindParams,
+                mNotificationContentBinder.bindContent(mEntry, this, flags, mBindParams,
                         false /* forceInflate */, mInflationCallback);
             }
         }
@@ -1105,10 +1091,6 @@
         return mPrivateLayout.getContractedNotificationHeader();
     }
 
-    public void setOnExpandClickListener(OnExpandClickListener onExpandClickListener) {
-        mOnExpandClickListener = onExpandClickListener;
-    }
-
     public void setLongPressListener(LongPressListener longPressListener) {
         mLongPressListener = longPressListener;
     }
@@ -1131,10 +1113,6 @@
         }
     }
 
-    public void setHeadsUpManager(HeadsUpManager headsUpManager) {
-        mHeadsUpManager = headsUpManager;
-    }
-
     public HeadsUpManager getHeadsUpManager() {
         return mHeadsUpManager;
     }
@@ -1172,6 +1150,7 @@
         mMenuRow = plugin;
         if (mMenuRow.shouldUseDefaultMenuItems()) {
             ArrayList<MenuItem> items = new ArrayList<>();
+            items.add(NotificationMenuRow.createConversationItem(mContext));
             items.add(NotificationMenuRow.createInfoItem(mContext));
             items.add(NotificationMenuRow.createSnoozeItem(mContext));
             items.add(NotificationMenuRow.createAppOpsItem(mContext));
@@ -1185,7 +1164,7 @@
     @Override
     public void onPluginDisconnected(NotificationMenuRowPlugin plugin) {
         boolean existed = mMenuRow.getMenuView() != null;
-        mMenuRow = new NotificationMenuRow(mContext); // Back to default
+        mMenuRow = new NotificationMenuRow(mContext);
         if (existed) {
             createMenu();
         }
@@ -1259,7 +1238,7 @@
             l.reInflateViews();
         }
         mEntry.getSbn().clearPackageContext();
-        mNotificationInflater.bindContent(mEntry, this, mInflationFlags, mBindParams,
+        mNotificationContentBinder.bindContent(mEntry, this, mInflationFlags, mBindParams,
                 true /* forceInflate */, mInflationCallback);
     }
 
@@ -1634,10 +1613,6 @@
         mBindParams.usesIncreasedHeadsUpHeight = use;
     }
 
-    public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) {
-        mNotificationInflater.setRemoteViewClickHandler(remoteViewClickHandler);
-    }
-
     /**
      * Set callback for notification content inflation
      *
@@ -1652,7 +1627,7 @@
             mNeedsRedaction = needsRedaction;
             if (needsRedaction) {
                 setInflationFlags(FLAG_CONTENT_VIEW_PUBLIC);
-                mNotificationInflater.bindContent(mEntry, this, FLAG_CONTENT_VIEW_PUBLIC,
+                mNotificationContentBinder.bindContent(mEntry, this, FLAG_CONTENT_VIEW_PUBLIC,
                         mBindParams, false /* forceInflate */, mInflationCallback);
             } else {
                 clearInflationFlags(FLAG_CONTENT_VIEW_PUBLIC);
@@ -1661,18 +1636,12 @@
         }
     }
 
-    @VisibleForTesting
-    public NotificationContentInflater getNotificationInflater() {
-        return mNotificationInflater;
-    }
-
     public interface ExpansionLogger {
         void logNotificationExpansion(String key, boolean userAction, boolean expanded);
     }
 
     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mNotificationInflater = new NotificationContentInflater();
         mMenuRow = new NotificationMenuRow(mContext);
         mImageResolver = new NotificationInlineImageResolver(context,
                 new NotificationInlineImageCache());
@@ -1680,8 +1649,30 @@
         initDimens();
     }
 
-    public void setBypassController(KeyguardBypassController bypassController) {
+    /**
+     * Initialize row.
+     */
+    public void initialize(
+            String appName,
+            String notificationKey,
+            ExpansionLogger logger,
+            KeyguardBypassController bypassController,
+            NotificationGroupManager groupManager,
+            HeadsUpManager headsUpManager,
+            NotificationRowContentBinder rowContentBinder,
+            OnExpandClickListener onExpandClickListener) {
+        mAppName = appName;
+        if (mMenuRow != null && mMenuRow.getMenuView() != null) {
+            mMenuRow.setAppName(mAppName);
+        }
+        mLogger = logger;
+        mLoggingKey = notificationKey;
         mBypassController = bypassController;
+        mGroupManager = groupManager;
+        mPrivateLayout.setGroupManager(groupManager);
+        mHeadsUpManager = headsUpManager;
+        mNotificationContentBinder = rowContentBinder;
+        mOnExpandClickListener = onExpandClickListener;
     }
 
     public void setStatusBarStateController(StatusBarStateController statusBarStateController) {
@@ -1730,6 +1721,8 @@
      */
     public void reset() {
         mShowingPublicInitialized = false;
+        unDismiss();
+        resetTranslation();
         onHeightReset();
         requestLayout();
     }
@@ -2920,11 +2913,6 @@
         return 0;
     }
 
-    public void setExpansionLogger(ExpansionLogger logger, String key) {
-        mLogger = logger;
-        mLoggingKey = key;
-    }
-
     public void onExpandedByGesture(boolean userExpanded) {
         int event = MetricsEvent.ACTION_NOTIFICATION_GESTURE_EXPANDER;
         if (mGroupManager.isSummaryOfGroup(mEntry.getSbn())) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCache.java
new file mode 100644
index 0000000..c11c60f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCache.java
@@ -0,0 +1,75 @@
+/*
+ * 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.statusbar.notification.row;
+
+import android.widget.RemoteViews;
+
+import androidx.annotation.Nullable;
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+
+/**
+ * Caches {@link RemoteViews} for a notification's content views.
+ */
+public interface NotifRemoteViewCache {
+
+    /**
+     * Whether the notification has the remote view cached
+     *
+     * @param entry notification
+     * @param flag inflation flag for content view
+     * @return true if the remote view is cached
+     */
+    boolean hasCachedView(NotificationEntry entry, @InflationFlag int flag);
+
+    /**
+     * Get the remote view for the content flag specified.
+     *
+     * @param entry notification
+     * @param flag inflation flag for the content view
+     * @return the remote view if it is cached, null otherwise
+     */
+    @Nullable RemoteViews getCachedView(NotificationEntry entry, @InflationFlag int flag);
+
+    /**
+     * Cache a remote view for a given content flag on a notification.
+     *
+     * @param entry notification
+     * @param flag inflation flag for the content view
+     * @param remoteView remote view to store
+     */
+    void putCachedView(
+            NotificationEntry entry,
+            @InflationFlag int flag,
+            RemoteViews remoteView);
+
+    /**
+     * Remove a cached remote view for a given content flag on a notification.
+     *
+     * @param entry notification
+     * @param flag inflation flag for the content view
+     */
+    void removeCachedView(NotificationEntry entry, @InflationFlag int flag);
+
+    /**
+     * Clear a notification's remote view cache.
+     *
+     * @param entry notification
+     */
+    void clearCache(NotificationEntry entry);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java
new file mode 100644
index 0000000..a6e5c2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java
@@ -0,0 +1,110 @@
+/*
+ * 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.statusbar.notification.row;
+
+import android.util.ArrayMap;
+import android.util.SparseArray;
+import android.widget.RemoteViews;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+
+import java.util.Map;
+
+import javax.inject.Inject;
+
+/**
+ * Implementation of remote view cache that keeps remote views cached for all active notifications.
+ */
+public class NotifRemoteViewCacheImpl implements NotifRemoteViewCache {
+    private final Map<NotificationEntry, SparseArray<RemoteViews>> mNotifCachedContentViews =
+            new ArrayMap<>();
+
+    @Inject
+    NotifRemoteViewCacheImpl(NotificationEntryManager entryManager) {
+        entryManager.addNotificationEntryListener(mEntryListener);
+    }
+
+    @Override
+    public boolean hasCachedView(NotificationEntry entry, @InflationFlag int flag) {
+        return getCachedView(entry, flag) != null;
+    }
+
+    @Override
+    public @Nullable RemoteViews getCachedView(NotificationEntry entry, @InflationFlag int flag) {
+        SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry);
+        if (contentViews == null) {
+            return null;
+        }
+        return contentViews.get(flag);
+    }
+
+    @Override
+    public void putCachedView(
+            NotificationEntry entry,
+            @InflationFlag int flag,
+            RemoteViews remoteView) {
+        /**
+         * TODO: We should be more strict here in the future (i.e. throw an exception) if the
+         * content views aren't created. We don't do that right now because we have edge cases
+         * where we may bind/unbind content after a notification is removed.
+         */
+        SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry);
+        if (contentViews == null) {
+            return;
+        }
+        contentViews.put(flag, remoteView);
+    }
+
+    @Override
+    public void removeCachedView(NotificationEntry entry, @InflationFlag int flag) {
+        SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry);
+        if (contentViews == null) {
+            return;
+        }
+        contentViews.remove(flag);
+    }
+
+    @Override
+    public void clearCache(NotificationEntry entry) {
+        SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry);
+        if (contentViews == null) {
+            return;
+        }
+        contentViews.clear();
+    }
+
+    private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
+        @Override
+        public void onPendingEntryAdded(NotificationEntry entry) {
+            mNotifCachedContentViews.put(entry, new SparseArray<>());
+        }
+
+        @Override
+        public void onEntryRemoved(
+                NotificationEntry entry,
+                @Nullable NotificationVisibility visibility,
+                boolean removedByUser) {
+            mNotifCachedContentViews.remove(entry);
+        }
+    };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 30f22ac..e1a6747 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.row;
 
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
 
@@ -26,7 +27,6 @@
 import android.os.AsyncTask;
 import android.os.CancellationSignal;
 import android.service.notification.StatusBarNotification;
-import android.util.ArrayMap;
 import android.util.Log;
 import android.view.View;
 import android.widget.RemoteViews;
@@ -35,6 +35,7 @@
 import com.android.internal.widget.ImageMessageConsumer;
 import com.android.systemui.Dependency;
 import com.android.systemui.statusbar.InflationTask;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.notification.InflationException;
 import com.android.systemui.statusbar.notification.MediaNotificationProcessor;
@@ -49,17 +50,30 @@
 
 import java.util.HashMap;
 
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
 /**
  * {@link NotificationContentInflater} binds content to a {@link ExpandableNotificationRow} by
  * asynchronously building the content's {@link RemoteViews} and applying it to the row.
  */
+@Singleton
+@VisibleForTesting(visibility = PACKAGE)
 public class NotificationContentInflater implements NotificationRowContentBinder {
 
     public static final String TAG = "NotifContentInflater";
 
-    private RemoteViews.OnClickHandler mRemoteViewClickHandler;
     private boolean mInflateSynchronously = false;
-    private final ArrayMap<Integer, RemoteViews> mCachedContentViews = new ArrayMap<>();
+    private final NotificationRemoteInputManager mRemoteInputManager;
+    private final NotifRemoteViewCache mRemoteViewCache;
+
+    @Inject
+    public NotificationContentInflater(
+            NotifRemoteViewCache remoteViewCache,
+            NotificationRemoteInputManager remoteInputManager) {
+        mRemoteViewCache = remoteViewCache;
+        mRemoteInputManager = remoteInputManager;
+    }
 
     @Override
     public void bindContent(
@@ -76,27 +90,27 @@
             return;
         }
 
-        StatusBarNotification sbn = row.getEntry().getSbn();
+        StatusBarNotification sbn = entry.getSbn();
 
         // To check if the notification has inline image and preload inline image if necessary.
         row.getImageResolver().preloadImages(sbn.getNotification());
 
         if (forceInflate) {
-            mCachedContentViews.clear();
+            mRemoteViewCache.clearCache(entry);
         }
 
         AsyncInflationTask task = new AsyncInflationTask(
-                sbn,
                 mInflateSynchronously,
                 contentToBind,
-                mCachedContentViews,
+                mRemoteViewCache,
+                entry,
                 row,
                 bindParams.isLowPriority,
                 bindParams.isChildInGroup,
                 bindParams.usesIncreasedHeight,
                 bindParams.usesIncreasedHeadsUpHeight,
                 callback,
-                mRemoteViewClickHandler);
+                mRemoteInputManager.getRemoteViewsOnClickHandler());
         if (mInflateSynchronously) {
             task.onPostExecute(task.doInBackground());
         } else {
@@ -123,13 +137,15 @@
         result = inflateSmartReplyViews(result, reInflateFlags, entry,
                 row.getContext(), packageContext, row.getHeadsUpManager(),
                 row.getExistingSmartRepliesAndActions());
+
         apply(
                 inflateSynchronously,
                 result,
                 reInflateFlags,
-                mCachedContentViews,
+                mRemoteViewCache,
+                entry,
                 row,
-                mRemoteViewClickHandler,
+                mRemoteInputManager.getRemoteViewsOnClickHandler(),
                 null);
         return result;
     }
@@ -149,7 +165,7 @@
         int curFlag = 1;
         while (contentToUnbind != 0) {
             if ((contentToUnbind & curFlag) != 0) {
-                freeNotificationView(row, curFlag);
+                freeNotificationView(entry, row, curFlag);
             }
             contentToUnbind &= ~curFlag;
             curFlag = curFlag << 1;
@@ -157,34 +173,25 @@
     }
 
     /**
-     * Set click handler for notification remote views
-     *
-     * @param remoteViewClickHandler click handler for remote views
-     */
-    public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) {
-        mRemoteViewClickHandler = remoteViewClickHandler;
-    }
-
-    /**
      * Frees the content view associated with the inflation flag.  Will only succeed if the
      * view is safe to remove.
      *
      * @param inflateFlag the flag corresponding to the content view which should be freed
      */
-    private void freeNotificationView(ExpandableNotificationRow row,
+    private void freeNotificationView(NotificationEntry entry, ExpandableNotificationRow row,
             @InflationFlag int inflateFlag) {
         switch (inflateFlag) {
             case FLAG_CONTENT_VIEW_HEADS_UP:
                 if (row.getPrivateLayout().isContentViewInactive(VISIBLE_TYPE_HEADSUP)) {
                     row.getPrivateLayout().setHeadsUpChild(null);
-                    mCachedContentViews.remove(FLAG_CONTENT_VIEW_HEADS_UP);
+                    mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP);
                     row.getPrivateLayout().setHeadsUpInflatedSmartReplies(null);
                 }
                 break;
             case FLAG_CONTENT_VIEW_PUBLIC:
                 if (row.getPublicLayout().isContentViewInactive(VISIBLE_TYPE_CONTRACTED)) {
                     row.getPublicLayout().setContractedChild(null);
-                    mCachedContentViews.remove(FLAG_CONTENT_VIEW_PUBLIC);
+                    mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC);
                 }
                 break;
             case FLAG_CONTENT_VIEW_CONTRACTED:
@@ -245,11 +252,12 @@
         return result;
     }
 
-    public static CancellationSignal apply(
+    private static CancellationSignal apply(
             boolean inflateSynchronously,
             InflationProgress result,
             @InflationFlag int reInflateFlags,
-            ArrayMap<Integer, RemoteViews> cachedContentViews,
+            NotifRemoteViewCache remoteViewCache,
+            NotificationEntry entry,
             ExpandableNotificationRow row,
             RemoteViews.OnClickHandler remoteViewClickHandler,
             @Nullable InflationCallback callback) {
@@ -261,7 +269,7 @@
         if ((reInflateFlags & flag) != 0) {
             boolean isNewView =
                     !canReapplyRemoteView(result.newContentView,
-                            cachedContentViews.get(FLAG_CONTENT_VIEW_CONTRACTED));
+                            remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED));
             ApplyCallback applyCallback = new ApplyCallback() {
                 @Override
                 public void setResultView(View v) {
@@ -273,8 +281,8 @@
                     return result.newContentView;
                 }
             };
-            applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, cachedContentViews,
-                    row, isNewView, remoteViewClickHandler, callback, privateLayout,
+            applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache,
+                    entry, row, isNewView, remoteViewClickHandler, callback, privateLayout,
                     privateLayout.getContractedChild(), privateLayout.getVisibleWrapper(
                             NotificationContentView.VISIBLE_TYPE_CONTRACTED),
                     runningInflations, applyCallback);
@@ -285,7 +293,7 @@
             if (result.newExpandedView != null) {
                 boolean isNewView =
                         !canReapplyRemoteView(result.newExpandedView,
-                                cachedContentViews.get(FLAG_CONTENT_VIEW_EXPANDED));
+                                remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED));
                 ApplyCallback applyCallback = new ApplyCallback() {
                     @Override
                     public void setResultView(View v) {
@@ -297,8 +305,8 @@
                         return result.newExpandedView;
                     }
                 };
-                applyRemoteView(inflateSynchronously, result, reInflateFlags, flag,
-                        cachedContentViews, row, isNewView, remoteViewClickHandler,
+                applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache,
+                        entry, row, isNewView, remoteViewClickHandler,
                         callback, privateLayout, privateLayout.getExpandedChild(),
                         privateLayout.getVisibleWrapper(
                                 NotificationContentView.VISIBLE_TYPE_EXPANDED), runningInflations,
@@ -311,7 +319,7 @@
             if (result.newHeadsUpView != null) {
                 boolean isNewView =
                         !canReapplyRemoteView(result.newHeadsUpView,
-                                cachedContentViews.get(FLAG_CONTENT_VIEW_HEADS_UP));
+                                remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP));
                 ApplyCallback applyCallback = new ApplyCallback() {
                     @Override
                     public void setResultView(View v) {
@@ -323,8 +331,8 @@
                         return result.newHeadsUpView;
                     }
                 };
-                applyRemoteView(inflateSynchronously, result, reInflateFlags, flag,
-                        cachedContentViews, row, isNewView, remoteViewClickHandler,
+                applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache,
+                        entry, row, isNewView, remoteViewClickHandler,
                         callback, privateLayout, privateLayout.getHeadsUpChild(),
                         privateLayout.getVisibleWrapper(
                                 VISIBLE_TYPE_HEADSUP), runningInflations,
@@ -336,7 +344,7 @@
         if ((reInflateFlags & flag) != 0) {
             boolean isNewView =
                     !canReapplyRemoteView(result.newPublicView,
-                            cachedContentViews.get(FLAG_CONTENT_VIEW_PUBLIC));
+                            remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC));
             ApplyCallback applyCallback = new ApplyCallback() {
                 @Override
                 public void setResultView(View v) {
@@ -348,15 +356,16 @@
                     return result.newPublicView;
                 }
             };
-            applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, cachedContentViews,
-                    row, isNewView, remoteViewClickHandler, callback,
+            applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache,
+                    entry, row, isNewView, remoteViewClickHandler, callback,
                     publicLayout, publicLayout.getContractedChild(),
                     publicLayout.getVisibleWrapper(NotificationContentView.VISIBLE_TYPE_CONTRACTED),
                     runningInflations, applyCallback);
         }
 
         // Let's try to finish, maybe nobody is even inflating anything
-        finishIfDone(result, reInflateFlags, cachedContentViews, runningInflations, callback, row);
+        finishIfDone(result, reInflateFlags, remoteViewCache, runningInflations, callback, entry,
+                row);
         CancellationSignal cancellationSignal = new CancellationSignal();
         cancellationSignal.setOnCancelListener(
                 () -> runningInflations.values().forEach(CancellationSignal::cancel));
@@ -369,7 +378,8 @@
             final InflationProgress result,
             final @InflationFlag int reInflateFlags,
             @InflationFlag int inflationId,
-            final ArrayMap<Integer, RemoteViews> cachedContentViews,
+            final NotifRemoteViewCache remoteViewCache,
+            final NotificationEntry entry,
             final ExpandableNotificationRow row,
             boolean isNewView,
             RemoteViews.OnClickHandler remoteViewClickHandler,
@@ -422,8 +432,8 @@
                     existingWrapper.onReinflated();
                 }
                 runningInflations.remove(inflationId);
-                finishIfDone(result, reInflateFlags, cachedContentViews, runningInflations,
-                        callback, row);
+                finishIfDone(result, reInflateFlags, remoteViewCache, runningInflations,
+                        callback, entry, row);
             }
 
             @Override
@@ -488,11 +498,11 @@
      * @return true if the inflation was finished
      */
     private static boolean finishIfDone(InflationProgress result,
-            @InflationFlag int reInflateFlags, ArrayMap<Integer, RemoteViews> cachedContentViews,
+            @InflationFlag int reInflateFlags, NotifRemoteViewCache remoteViewCache,
             HashMap<Integer, CancellationSignal> runningInflations,
-            @Nullable InflationCallback endListener, ExpandableNotificationRow row) {
+            @Nullable InflationCallback endListener, NotificationEntry entry,
+            ExpandableNotificationRow row) {
         Assert.isMainThread();
-        NotificationEntry entry = row.getEntry();
         NotificationContentView privateLayout = row.getPrivateLayout();
         NotificationContentView publicLayout = row.getPublicLayout();
         if (runningInflations.isEmpty()) {
@@ -500,23 +510,27 @@
                 if (result.inflatedContentView != null) {
                     // New view case
                     privateLayout.setContractedChild(result.inflatedContentView);
-                    cachedContentViews.put(FLAG_CONTENT_VIEW_CONTRACTED, result.newContentView);
-                } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_CONTRACTED) != null) {
+                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
+                            result.newContentView);
+                } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)) {
                     // Reinflation case. Only update if it's still cached (i.e. view has not been
                     // freed while inflating).
-                    cachedContentViews.put(FLAG_CONTENT_VIEW_CONTRACTED, result.newContentView);
+                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
+                            result.newContentView);
                 }
             }
 
             if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
                 if (result.inflatedExpandedView != null) {
                     privateLayout.setExpandedChild(result.inflatedExpandedView);
-                    cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, result.newExpandedView);
+                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
+                            result.newExpandedView);
                 } else if (result.newExpandedView == null) {
                     privateLayout.setExpandedChild(null);
-                    cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, null);
-                } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_EXPANDED) != null) {
-                    cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, result.newExpandedView);
+                    remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED);
+                } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)) {
+                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
+                            result.newExpandedView);
                 }
                 if (result.newExpandedView != null) {
                     privateLayout.setExpandedInflatedSmartReplies(
@@ -530,12 +544,14 @@
             if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
                 if (result.inflatedHeadsUpView != null) {
                     privateLayout.setHeadsUpChild(result.inflatedHeadsUpView);
-                    cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, result.newHeadsUpView);
+                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
+                            result.newHeadsUpView);
                 } else if (result.newHeadsUpView == null) {
                     privateLayout.setHeadsUpChild(null);
-                    cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, null);
-                } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_HEADS_UP) != null) {
-                    cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, result.newHeadsUpView);
+                    remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP);
+                } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)) {
+                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
+                            result.newHeadsUpView);
                 }
                 if (result.newHeadsUpView != null) {
                     privateLayout.setHeadsUpInflatedSmartReplies(
@@ -548,16 +564,18 @@
             if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
                 if (result.inflatedPublicView != null) {
                     publicLayout.setContractedChild(result.inflatedPublicView);
-                    cachedContentViews.put(FLAG_CONTENT_VIEW_PUBLIC, result.newPublicView);
-                } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_PUBLIC) != null) {
-                    cachedContentViews.put(FLAG_CONTENT_VIEW_PUBLIC, result.newPublicView);
+                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
+                            result.newPublicView);
+                } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)) {
+                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
+                            result.newPublicView);
                 }
             }
 
             entry.headsUpStatusBarText = result.headsUpStatusBarText;
             entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic;
             if (endListener != null) {
-                endListener.onAsyncInflationFinished(row.getEntry(), reInflateFlags);
+                endListener.onAsyncInflationFinished(entry, reInflateFlags);
             }
             return true;
         }
@@ -615,7 +633,7 @@
     public static class AsyncInflationTask extends AsyncTask<Void, Void, InflationProgress>
             implements InflationCallback, InflationTask {
 
-        private final StatusBarNotification mSbn;
+        private final NotificationEntry mEntry;
         private final Context mContext;
         private final boolean mInflateSynchronously;
         private final boolean mIsLowPriority;
@@ -624,17 +642,17 @@
         private final InflationCallback mCallback;
         private final boolean mUsesIncreasedHeadsUpHeight;
         private @InflationFlag int mReInflateFlags;
-        private final ArrayMap<Integer, RemoteViews> mCachedContentViews;
+        private final NotifRemoteViewCache mRemoteViewCache;
         private ExpandableNotificationRow mRow;
         private Exception mError;
         private RemoteViews.OnClickHandler mRemoteViewClickHandler;
         private CancellationSignal mCancellationSignal;
 
         private AsyncInflationTask(
-                StatusBarNotification notification,
                 boolean inflateSynchronously,
                 @InflationFlag int reInflateFlags,
-                ArrayMap<Integer, RemoteViews> cachedContentViews,
+                NotifRemoteViewCache cache,
+                NotificationEntry entry,
                 ExpandableNotificationRow row,
                 boolean isLowPriority,
                 boolean isChildInGroup,
@@ -642,11 +660,11 @@
                 boolean usesIncreasedHeadsUpHeight,
                 InflationCallback callback,
                 RemoteViews.OnClickHandler remoteViewClickHandler) {
+            mEntry = entry;
             mRow = row;
-            mSbn = notification;
             mInflateSynchronously = inflateSynchronously;
             mReInflateFlags = reInflateFlags;
-            mCachedContentViews = cachedContentViews;
+            mRemoteViewCache = cache;
             mContext = mRow.getContext();
             mIsLowPriority = isLowPriority;
             mIsChildInGroup = isChildInGroup;
@@ -654,7 +672,6 @@
             mUsesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight;
             mRemoteViewClickHandler = remoteViewClickHandler;
             mCallback = callback;
-            NotificationEntry entry = row.getEntry();
             entry.setInflationTask(this);
         }
 
@@ -667,12 +684,13 @@
         @Override
         protected InflationProgress doInBackground(Void... params) {
             try {
+                final StatusBarNotification sbn = mEntry.getSbn();
                 final Notification.Builder recoveredBuilder
                         = Notification.Builder.recoverBuilder(mContext,
-                        mSbn.getNotification());
+                        sbn.getNotification());
 
-                Context packageContext = mSbn.getPackageContext(mContext);
-                Notification notification = mSbn.getNotification();
+                Context packageContext = sbn.getPackageContext(mContext);
+                Notification notification = sbn.getNotification();
                 if (notification.isMediaNotification()) {
                     MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext,
                             packageContext);
@@ -681,7 +699,7 @@
                 InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
                         recoveredBuilder, mIsLowPriority, mIsChildInGroup, mUsesIncreasedHeight,
                         mUsesIncreasedHeadsUpHeight, packageContext);
-                return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mRow.getEntry(),
+                return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mEntry,
                         mRow.getContext(), packageContext, mRow.getHeadsUpManager(),
                         mRow.getExistingSmartRepliesAndActions());
             } catch (Exception e) {
@@ -694,15 +712,15 @@
         protected void onPostExecute(InflationProgress result) {
             if (mError == null) {
                 mCancellationSignal = apply(mInflateSynchronously, result, mReInflateFlags,
-                        mCachedContentViews, mRow, mRemoteViewClickHandler, this);
+                        mRemoteViewCache, mEntry, mRow, mRemoteViewClickHandler, this);
             } else {
                 handleError(mError);
             }
         }
 
         private void handleError(Exception e) {
-            mRow.getEntry().onInflationTaskFinished();
-            StatusBarNotification sbn = mRow.getEntry().getSbn();
+            mEntry.onInflationTaskFinished();
+            StatusBarNotification sbn = mEntry.getSbn();
             final String ident = sbn.getPackageName() + "/0x"
                     + Integer.toHexString(sbn.getId());
             Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e);
@@ -736,10 +754,10 @@
         @Override
         public void onAsyncInflationFinished(NotificationEntry entry,
                 @InflationFlag int inflatedFlags) {
-            mRow.getEntry().onInflationTaskFinished();
+            mEntry.onInflationTaskFinished();
             mRow.onNotificationUpdated();
             if (mCallback != null) {
-                mCallback.onAsyncInflationFinished(mRow.getEntry(), inflatedFlags);
+                mCallback.onAsyncInflationFinished(mEntry, inflatedFlags);
             }
 
             // Notify the resolver that the inflation task has finished,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
new file mode 100644
index 0000000..18c755d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -0,0 +1,648 @@
+/*
+ * 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.statusbar.notification.row;
+
+import static android.app.Notification.EXTRA_IS_GROUP_CONVERSATION;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
+
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_BUBBLE;
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_DEMOTE;
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_FAVORITE;
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_HOME;
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_MUTE;
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_SNOOZE;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.INotificationManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.service.notification.StatusBarNotification;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Slog;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+import java.lang.annotation.Retention;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * The guts of a conversation notification revealed when performing a long press.
+ */
+public class NotificationConversationInfo extends LinearLayout implements
+        NotificationGuts.GutsContent {
+    private static final String TAG = "ConversationGuts";
+
+
+    private INotificationManager mINotificationManager;
+    private LauncherApps mLauncherApps;
+    ShortcutManager mShortcutManager;
+    private PackageManager mPm;
+    private VisualStabilityManager mVisualStabilityManager;
+
+    private String mPackageName;
+    private String mAppName;
+    private int mAppUid;
+    private String mDelegatePkg;
+    private NotificationChannel mNotificationChannel;
+    private ShortcutInfo mShortcutInfo;
+    private String mConversationId;
+    private NotificationEntry mEntry;
+    private StatusBarNotification mSbn;
+    private boolean mIsDeviceProvisioned;
+    private int mStartingChannelImportance;
+    private boolean mStartedAsBubble;
+    private boolean mIsBubbleable;
+    // TODO: remove when launcher api works
+    @VisibleForTesting
+    boolean mShowHomeScreen = false;
+
+    private @UpdateChannelRunnable.Action int mSelectedAction = -1;
+
+    private OnSnoozeClickListener mOnSnoozeClickListener;
+    private OnSettingsClickListener mOnSettingsClickListener;
+    private OnAppSettingsClickListener mAppSettingsClickListener;
+    private NotificationGuts mGutsContainer;
+    private BubbleController mBubbleController;
+
+    @VisibleForTesting
+    boolean mSkipPost = false;
+
+    private OnClickListener mOnBubbleClick = v -> {
+        mSelectedAction = ACTION_BUBBLE;
+        if (mStartedAsBubble) {
+            mBubbleController.onUserDemotedBubbleFromNotification(mEntry);
+        } else {
+            mBubbleController.onUserCreatedBubbleFromNotification(mEntry);
+            Settings.Global.putInt(
+                    mContext.getContentResolver(), Settings.Global.NOTIFICATION_BUBBLES, 1);
+        }
+        closeControls(v, true);
+    };
+
+    private OnClickListener mOnHomeClick = v -> {
+        mSelectedAction = ACTION_HOME;
+        mShortcutManager.requestPinShortcut(mShortcutInfo, null);
+        closeControls(v, true);
+    };
+
+    private OnClickListener mOnFavoriteClick = v -> {
+        mSelectedAction = ACTION_FAVORITE;
+        closeControls(v, true);
+    };
+
+    private OnClickListener mOnSnoozeClick = v -> {
+        mSelectedAction = ACTION_SNOOZE;
+        mOnSnoozeClickListener.onClick(v, 1);
+        closeControls(v, true);
+    };
+
+    private OnClickListener mOnMuteClick = v -> {
+      mSelectedAction = ACTION_MUTE;
+      closeControls(v, true);
+    };
+
+    private OnClickListener mOnDemoteClick = v -> {
+        mSelectedAction = ACTION_DEMOTE;
+        closeControls(v, true);
+    };
+
+    public NotificationConversationInfo(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public interface OnSettingsClickListener {
+        void onClick(View v, NotificationChannel channel, int appUid);
+    }
+
+    public interface OnAppSettingsClickListener {
+        void onClick(View v, Intent intent);
+    }
+
+    public interface OnSnoozeClickListener {
+        void onClick(View v, int hoursToSnooze);
+    }
+
+    public void bindNotification(
+            ShortcutManager shortcutManager,
+            LauncherApps launcherApps,
+            PackageManager pm,
+            INotificationManager iNotificationManager,
+            VisualStabilityManager visualStabilityManager,
+            String pkg,
+            NotificationChannel notificationChannel,
+            NotificationEntry entry,
+            OnSettingsClickListener onSettingsClick,
+            OnAppSettingsClickListener onAppSettingsClick,
+            OnSnoozeClickListener onSnoozeClickListener,
+            boolean isDeviceProvisioned) {
+        mSelectedAction = -1;
+        mINotificationManager = iNotificationManager;
+        mVisualStabilityManager = visualStabilityManager;
+        mBubbleController = Dependency.get(BubbleController.class);
+        mPackageName = pkg;
+        mEntry = entry;
+        mSbn = entry.getSbn();
+        mPm = pm;
+        mAppSettingsClickListener = onAppSettingsClick;
+        mAppName = mPackageName;
+        mOnSettingsClickListener = onSettingsClick;
+        mNotificationChannel = notificationChannel;
+        mStartingChannelImportance = mNotificationChannel.getImportance();
+        mAppUid = mSbn.getUid();
+        mDelegatePkg = mSbn.getOpPkg();
+        mIsDeviceProvisioned = isDeviceProvisioned;
+        mOnSnoozeClickListener = onSnoozeClickListener;
+
+        mShortcutManager = shortcutManager;
+        mLauncherApps = launcherApps;
+        mConversationId = mNotificationChannel.getConversationId();
+        if (TextUtils.isEmpty(mNotificationChannel.getConversationId())) {
+            mConversationId = mSbn.getShortcutId(mContext);
+        }
+        if (TextUtils.isEmpty(mConversationId)) {
+            throw new IllegalArgumentException("Does not have required information");
+        }
+        // TODO: consider querying this earlier in the notification pipeline and passing it in
+        LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery()
+                .setPackage(mPackageName)
+                .setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED)
+                .setShortcutIds(Arrays.asList(mConversationId));
+        List<ShortcutInfo> shortcuts = mLauncherApps.getShortcuts(query, mSbn.getUser());
+        if (shortcuts != null && !shortcuts.isEmpty()) {
+            mShortcutInfo = shortcuts.get(0);
+        }
+
+        mIsBubbleable = mEntry.getBubbleMetadata() != null;
+        mStartedAsBubble = mEntry.isBubble();
+
+        createConversationChannelIfNeeded();
+
+        bindHeader();
+        bindActions();
+
+    }
+
+    void createConversationChannelIfNeeded() {
+        // If this channel is not already a customized conversation channel, create
+        // a custom channel
+        if (TextUtils.isEmpty(mNotificationChannel.getConversationId())) {
+            try {
+                // TODO: remove
+                mNotificationChannel.setName(mContext.getString(
+                        R.string.notification_summary_message_format,
+                        getName(), mNotificationChannel.getName()));
+                mINotificationManager.createConversationNotificationChannelForPackage(
+                        mPackageName, mAppUid, mSbn.getKey(), mNotificationChannel,
+                        mConversationId);
+                mNotificationChannel = mINotificationManager.getConversationNotificationChannel(
+                        mContext.getOpPackageName(), UserHandle.getUserId(mAppUid), mPackageName,
+                        mNotificationChannel.getId(), false, mConversationId);
+
+                // TODO: ask LA to pin the shortcut once api exists for pinning one shortcut at a
+                // time
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Could not create conversation channel", e);
+            }
+        }
+    }
+
+    private void bindActions() {
+        // TODO: figure out what should happen for non-configurable channels
+
+        Button bubble = findViewById(R.id.bubble);
+        bubble.setVisibility(mIsBubbleable ? VISIBLE : GONE);
+        bubble.setOnClickListener(mOnBubbleClick);
+        if (mStartedAsBubble) {
+            bubble.setText(R.string.notification_conversation_unbubble);
+        } else {
+            bubble.setText(R.string.notification_conversation_bubble);
+        }
+
+        Button home = findViewById(R.id.home);
+        home.setOnClickListener(mOnHomeClick);
+        home.setVisibility(mShowHomeScreen && mShortcutInfo != null
+                && mShortcutManager.isRequestPinShortcutSupported()
+                ? VISIBLE : GONE);
+
+        Button favorite = findViewById(R.id.fave);
+        favorite.setOnClickListener(mOnFavoriteClick);
+        if (mNotificationChannel.canBypassDnd()) {
+            favorite.setText(R.string.notification_conversation_unfavorite);
+            favorite.setCompoundDrawablesRelative(
+                    mContext.getDrawable(R.drawable.ic_star), null, null, null);
+        } else {
+            favorite.setText(R.string.notification_conversation_favorite);
+            favorite.setCompoundDrawablesRelative(
+                    mContext.getDrawable(R.drawable.ic_star_border), null, null, null);
+        }
+
+        Button snooze = findViewById(R.id.snooze);
+        snooze.setOnClickListener(mOnSnoozeClick);
+
+        Button mute = findViewById(R.id.mute);
+        mute.setOnClickListener(mOnMuteClick);
+        if (mStartingChannelImportance >= IMPORTANCE_DEFAULT
+                || mStartingChannelImportance == IMPORTANCE_UNSPECIFIED) {
+            mute.setText(R.string.notification_conversation_mute);
+            favorite.setCompoundDrawablesRelative(
+                    mContext.getDrawable(R.drawable.ic_notifications_silence), null, null, null);
+        } else {
+            mute.setText(R.string.notification_conversation_unmute);
+            favorite.setCompoundDrawablesRelative(
+                    mContext.getDrawable(R.drawable.ic_notifications_alert), null, null, null);
+        }
+
+        ImageButton demote = findViewById(R.id.demote);
+        demote.setOnClickListener(mOnDemoteClick);
+    }
+
+    private void bindHeader() {
+        bindConversationDetails();
+
+        // Delegate
+        bindDelegate();
+
+        // Set up app settings link (i.e. Customize)
+        View settingsLinkView = findViewById(R.id.app_settings);
+        Intent settingsIntent = getAppSettingsIntent(mPm, mPackageName,
+                mNotificationChannel,
+                mSbn.getId(), mSbn.getTag());
+        if (settingsIntent != null
+                && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) {
+            settingsLinkView.setVisibility(VISIBLE);
+            settingsLinkView.setOnClickListener((View view) -> {
+                mAppSettingsClickListener.onClick(view, settingsIntent);
+            });
+        } else {
+            settingsLinkView.setVisibility(View.GONE);
+        }
+
+        // System Settings button.
+        final View settingsButton = findViewById(R.id.info);
+        settingsButton.setOnClickListener(getSettingsOnClickListener());
+        settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE);
+    }
+
+    private OnClickListener getSettingsOnClickListener() {
+        if (mAppUid >= 0 && mOnSettingsClickListener != null && mIsDeviceProvisioned) {
+            final int appUidF = mAppUid;
+            return ((View view) -> {
+                mOnSettingsClickListener.onClick(view, mNotificationChannel, appUidF);
+            });
+        }
+        return null;
+    }
+
+    private void bindConversationDetails() {
+        final TextView channelName = findViewById(R.id.parent_channel_name);
+        channelName.setText(mNotificationChannel.getName());
+
+        bindGroup();
+        // TODO: bring back when channel name does not include name
+        // bindName();
+        bindPackage();
+        bindIcon();
+
+    }
+
+    private void bindIcon() {
+        ImageView image = findViewById(R.id.conversation_icon);
+        if (mShortcutInfo != null) {
+            image.setImageDrawable(mLauncherApps.getShortcutBadgedIconDrawable(mShortcutInfo,
+                    mContext.getResources().getDisplayMetrics().densityDpi));
+        } else {
+            if (mSbn.getNotification().extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION, false)) {
+                // TODO: maybe use a generic group icon, or a composite of recent senders
+                image.setImageDrawable(mPm.getDefaultActivityIcon());
+            } else {
+                final List<Notification.MessagingStyle.Message> messages =
+                        Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+                                (Parcelable[]) mSbn.getNotification().extras.get(
+                                        Notification.EXTRA_MESSAGES));
+
+                final Notification.MessagingStyle.Message latestMessage =
+                        Notification.MessagingStyle.findLatestIncomingMessage(messages);
+                Icon personIcon = latestMessage.getSenderPerson().getIcon();
+                if (personIcon != null) {
+                    image.setImageIcon(latestMessage.getSenderPerson().getIcon());
+                } else {
+                    // TODO: choose something better
+                    image.setImageDrawable(mPm.getDefaultActivityIcon());
+                }
+            }
+        }
+    }
+
+    private void bindName() {
+        TextView name = findViewById(R.id.name);
+        name.setText(getName());
+    }
+
+    private String getName() {
+        if (mShortcutInfo != null) {
+            return mShortcutInfo.getShortLabel().toString();
+        } else {
+            Bundle extras = mSbn.getNotification().extras;
+            String nameString = extras.getString(Notification.EXTRA_CONVERSATION_TITLE);
+            if (TextUtils.isEmpty(nameString)) {
+                nameString = extras.getString(Notification.EXTRA_TITLE);
+            }
+            return nameString;
+        }
+    }
+
+    private void bindPackage() {
+        ApplicationInfo info;
+        try {
+            info = mPm.getApplicationInfo(
+                    mPackageName,
+                    PackageManager.MATCH_UNINSTALLED_PACKAGES
+                            | PackageManager.MATCH_DISABLED_COMPONENTS
+                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                            | PackageManager.MATCH_DIRECT_BOOT_AWARE);
+            if (info != null) {
+                mAppName = String.valueOf(mPm.getApplicationLabel(info));
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+        }
+        ((TextView) findViewById(R.id.pkg_name)).setText(mAppName);
+    }
+
+    private void bindDelegate() {
+        TextView delegateView = findViewById(R.id.delegate_name);
+        TextView dividerView = findViewById(R.id.pkg_divider);
+
+        if (!TextUtils.equals(mPackageName, mDelegatePkg)) {
+            // this notification was posted by a delegate!
+            delegateView.setVisibility(View.VISIBLE);
+            dividerView.setVisibility(View.VISIBLE);
+        } else {
+            delegateView.setVisibility(View.GONE);
+            dividerView.setVisibility(View.GONE);
+        }
+    }
+
+    private void bindGroup() {
+        // Set group information if this channel has an associated group.
+        CharSequence groupName = null;
+        if (mNotificationChannel != null && mNotificationChannel.getGroup() != null) {
+            try {
+                final NotificationChannelGroup notificationChannelGroup =
+                        mINotificationManager.getNotificationChannelGroupForPackage(
+                                mNotificationChannel.getGroup(), mPackageName, mAppUid);
+                if (notificationChannelGroup != null) {
+                    groupName = notificationChannelGroup.getName();
+                }
+            } catch (RemoteException e) {
+            }
+        }
+        TextView groupNameView = findViewById(R.id.group_name);
+        View groupDivider = findViewById(R.id.group_divider);
+        if (groupName != null) {
+            groupNameView.setText(groupName);
+            groupNameView.setVisibility(VISIBLE);
+            groupDivider.setVisibility(VISIBLE);
+        } else {
+            groupNameView.setVisibility(GONE);
+            groupDivider.setVisibility(GONE);
+        }
+    }
+
+    @Override
+    public boolean post(Runnable action) {
+        if (mSkipPost) {
+            action.run();
+            return true;
+        } else {
+            return super.post(action);
+        }
+    }
+
+    @Override
+    public void onFinishedClosing() {
+        // TODO: do we need to do anything here?
+    }
+
+    @Override
+    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+        super.onInitializeAccessibilityEvent(event);
+        if (mGutsContainer != null &&
+                event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+            if (mGutsContainer.isExposed()) {
+                event.getText().add(mContext.getString(
+                        R.string.notification_channel_controls_opened_accessibility, mAppName));
+            } else {
+                event.getText().add(mContext.getString(
+                        R.string.notification_channel_controls_closed_accessibility, mAppName));
+            }
+        }
+    }
+
+    private Intent getAppSettingsIntent(PackageManager pm, String packageName,
+            NotificationChannel channel, int id, String tag) {
+        Intent intent = new Intent(Intent.ACTION_MAIN)
+                .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES)
+                .setPackage(packageName);
+        final List<ResolveInfo> resolveInfos = pm.queryIntentActivities(
+                intent,
+                PackageManager.MATCH_DEFAULT_ONLY
+        );
+        if (resolveInfos == null || resolveInfos.size() == 0 || resolveInfos.get(0) == null) {
+            return null;
+        }
+        final ActivityInfo activityInfo = resolveInfos.get(0).activityInfo;
+        intent.setClassName(activityInfo.packageName, activityInfo.name);
+        if (channel != null) {
+            intent.putExtra(Notification.EXTRA_CHANNEL_ID, channel.getId());
+        }
+        intent.putExtra(Notification.EXTRA_NOTIFICATION_ID, id);
+        intent.putExtra(Notification.EXTRA_NOTIFICATION_TAG, tag);
+        return intent;
+    }
+
+    /**
+     * Closes the controls and commits the updated importance values (indirectly).
+     *
+     * <p><b>Note,</b> this will only get called once the view is dismissing. This means that the
+     * user does not have the ability to undo the action anymore.
+     */
+    @VisibleForTesting
+    void closeControls(View v, boolean save) {
+        int[] parentLoc = new int[2];
+        int[] targetLoc = new int[2];
+        mGutsContainer.getLocationOnScreen(parentLoc);
+        v.getLocationOnScreen(targetLoc);
+        final int centerX = v.getWidth() / 2;
+        final int centerY = v.getHeight() / 2;
+        final int x = targetLoc[0] - parentLoc[0] + centerX;
+        final int y = targetLoc[1] - parentLoc[1] + centerY;
+        mGutsContainer.closeControls(x, y, save, false /* force */);
+    }
+
+    @Override
+    public void setGutsParent(NotificationGuts guts) {
+        mGutsContainer = guts;
+    }
+
+    @Override
+    public boolean willBeRemoved() {
+        return false;
+    }
+
+    @Override
+    public boolean shouldBeSaved() {
+        return mSelectedAction > -1;
+    }
+
+    @Override
+    public View getContentView() {
+        return this;
+    }
+
+    @Override
+    public boolean handleCloseControls(boolean save, boolean force) {
+        if (save && mSelectedAction > -1) {
+            Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
+            bgHandler.post(
+                    new UpdateChannelRunnable(mINotificationManager, mPackageName,
+                            mAppUid, mSelectedAction, mNotificationChannel));
+            mVisualStabilityManager.temporarilyAllowReordering();
+        }
+        return false;
+    }
+
+    @Override
+    public int getActualHeight() {
+        return getHeight();
+    }
+
+    @VisibleForTesting
+    public boolean isAnimating() {
+        return false;
+    }
+
+    static class UpdateChannelRunnable implements Runnable {
+
+        @Retention(SOURCE)
+        @IntDef({ACTION_BUBBLE, ACTION_HOME, ACTION_FAVORITE, ACTION_SNOOZE, ACTION_MUTE,
+                ACTION_DEMOTE})
+        private @interface Action {}
+        static final int ACTION_BUBBLE = 0;
+        static final int ACTION_HOME = 1;
+        static final int ACTION_FAVORITE = 2;
+        static final int ACTION_SNOOZE = 3;
+        static final int ACTION_MUTE = 4;
+        static final int ACTION_DEMOTE = 5;
+
+        private final INotificationManager mINotificationManager;
+        private final String mAppPkg;
+        private final int mAppUid;
+        private  NotificationChannel mChannelToUpdate;
+        private final @Action int mAction;
+
+        public UpdateChannelRunnable(INotificationManager notificationManager,
+                String packageName, int appUid, @Action int action,
+                @NonNull NotificationChannel channelToUpdate) {
+            mINotificationManager = notificationManager;
+            mAppPkg = packageName;
+            mAppUid = appUid;
+            mChannelToUpdate = channelToUpdate;
+            mAction = action;
+        }
+
+        @Override
+        public void run() {
+            try {
+                switch (mAction) {
+                    case ACTION_BUBBLE:
+                        mChannelToUpdate.setAllowBubbles(!mChannelToUpdate.canBubble());
+                        break;
+                    case ACTION_FAVORITE:
+                        // TODO: extend beyond DND
+                        mChannelToUpdate.setBypassDnd(!mChannelToUpdate.canBypassDnd());
+                        break;
+                    case ACTION_MUTE:
+                        if (mChannelToUpdate.getImportance() == IMPORTANCE_UNSPECIFIED
+                                || mChannelToUpdate.getImportance() >= IMPORTANCE_DEFAULT) {
+                            mChannelToUpdate.setImportance(IMPORTANCE_LOW);
+                        } else {
+                            mChannelToUpdate.setImportance(Math.max(
+                                    mChannelToUpdate.getOriginalImportance(), IMPORTANCE_DEFAULT));
+                        }
+                        break;
+                    case ACTION_DEMOTE:
+                        mChannelToUpdate.setDemoted(!mChannelToUpdate.isDemoted());
+                        break;
+
+                }
+
+                if (mAction != ACTION_HOME && mAction != ACTION_SNOOZE) {
+                    mINotificationManager.updateNotificationChannelForPackage(
+                            mAppPkg, mAppUid, mChannelToUpdate);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "Unable to update notification channel", e);
+            }
+        }
+    }
+
+    @Retention(SOURCE)
+    @IntDef({BEHAVIOR_ALERTING, BEHAVIOR_SILENT, BEHAVIOR_BUBBLE})
+    private @interface AlertingBehavior {}
+    private static final int BEHAVIOR_ALERTING = 0;
+    private static final int BEHAVIOR_SILENT = 1;
+    private static final int BEHAVIOR_BUBBLE = 2;
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 6f2abba..6789c81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -23,7 +23,9 @@
 import android.app.NotificationChannel;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
+import android.content.pm.ShortcutManager;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -53,6 +55,7 @@
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -81,6 +84,7 @@
     private final Context mContext;
     private final VisualStabilityManager mVisualStabilityManager;
     private final AccessibilityManager mAccessibilityManager;
+    private final HighPriorityProvider mHighPriorityProvider;
 
     // Dependencies:
     private final NotificationLockscreenUserManager mLockscreenUserManager =
@@ -109,12 +113,14 @@
     @Inject
     public NotificationGutsManager(Context context, VisualStabilityManager visualStabilityManager,
             Lazy<StatusBar> statusBarLazy, @Main Handler mainHandler,
-            AccessibilityManager accessibilityManager) {
+            AccessibilityManager accessibilityManager,
+            HighPriorityProvider highPriorityProvider) {
         mContext = context;
         mVisualStabilityManager = visualStabilityManager;
         mStatusBarLazy = statusBarLazy;
         mMainHandler = mainHandler;
         mAccessibilityManager = accessibilityManager;
+        mHighPriorityProvider = highPriorityProvider;
     }
 
     public void setUpWithPresenter(NotificationPresenter presenter,
@@ -224,6 +230,9 @@
                 initializeAppOpsInfo(row, (AppOpsInfo) gutsView);
             } else if (gutsView instanceof NotificationInfo) {
                 initializeNotificationInfo(row, (NotificationInfo) gutsView);
+            } else if (gutsView instanceof NotificationConversationInfo) {
+                initializeConversationNotificationInfo(
+                        row, (NotificationConversationInfo) gutsView);
             }
             return true;
         } catch (Exception e) {
@@ -331,7 +340,66 @@
                 row.getIsNonblockable(),
                 isForBlockingHelper,
                 row.getEntry().getImportance(),
-                row.getEntry().isHighPriority());
+                mHighPriorityProvider.isHighPriority(row.getEntry()));
+    }
+
+    /**
+     * Sets up the {@link NotificationConversationInfo} inside the notification row's guts.
+     * @param row view to set up the guts for
+     * @param notificationInfoView view to set up/bind within {@code row}
+     */
+    @VisibleForTesting
+    void initializeConversationNotificationInfo(
+            final ExpandableNotificationRow row,
+            NotificationConversationInfo notificationInfoView) throws Exception {
+        NotificationGuts guts = row.getGuts();
+        StatusBarNotification sbn = row.getEntry().getSbn();
+        String packageName = sbn.getPackageName();
+        // Settings link is only valid for notifications that specify a non-system user
+        NotificationConversationInfo.OnSettingsClickListener onSettingsClick = null;
+        UserHandle userHandle = sbn.getUser();
+        PackageManager pmUser = StatusBar.getPackageManagerForUser(
+                mContext, userHandle.getIdentifier());
+        LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
+        ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class);
+        INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
+                ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+        final NotificationConversationInfo.OnAppSettingsClickListener onAppSettingsClick =
+                (View v, Intent intent) -> {
+                    mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_APP_NOTE_SETTINGS);
+                    guts.resetFalsingCheck();
+                    mNotificationActivityStarter.startNotificationGutsIntent(intent, sbn.getUid(),
+                            row);
+                };
+
+        final NotificationConversationInfo.OnSnoozeClickListener onSnoozeClickListener =
+                (View v, int hours) -> {
+                    mListContainer.getSwipeActionHelper().snooze(sbn, hours);
+                };
+
+        if (!userHandle.equals(UserHandle.ALL)
+                || mLockscreenUserManager.getCurrentUserId() == UserHandle.USER_SYSTEM) {
+            onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
+                mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
+                guts.resetFalsingCheck();
+                mOnSettingsClickListener.onSettingsClick(sbn.getKey());
+                startAppNotificationSettingsActivity(packageName, appUid, channel, row);
+            };
+        }
+
+        notificationInfoView.bindNotification(
+                shortcutManager,
+                launcherApps,
+                pmUser,
+                iNotificationManager,
+                mVisualStabilityManager,
+                packageName,
+                row.getEntry().getChannel(),
+                row.getEntry(),
+                onSettingsClick,
+                onAppSettingsClick,
+                onSnoozeClickListener,
+                mDeviceProvisionedController.isDeviceProvisioned());
 
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index a9a4804..6b4511d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -65,8 +65,6 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleController;
-import com.android.systemui.bubbles.BubbleExperimentConfig;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.logging.NotificationCounters;
@@ -102,7 +100,6 @@
     // standard controls
     private static final int ACTION_ALERT = 5;
 
-    private TextView mBubbleDescriptionView;
     private TextView mPriorityDescriptionView;
     private TextView mSilentDescriptionView;
 
@@ -120,7 +117,6 @@
     private Set<NotificationChannel> mUniqueChannelsInRow;
     private NotificationChannel mSingleNotificationChannel;
     private int mStartingChannelImportance;
-    private boolean mStartedAsBubble;
     private boolean mWasShownHighPriority;
     private boolean mPressedApply;
     private boolean mPresentingChannelEditorDialog = false;
@@ -130,14 +126,8 @@
      * level; non-null once the user takes an action which indicates an explicit preference.
      */
     @Nullable private Integer mChosenImportance;
-    /**
-     * The last bubble setting chosen by the user. Null if the user has not chosen a bubble level;
-     * non-null once the user takes an action which indicates an explicit preference.
-     */
-    @Nullable private Boolean mChosenBubbleEnabled;
     private boolean mIsSingleDefaultChannel;
     private boolean mIsNonblockable;
-    private boolean mIsBubbleable;
     private NotificationEntry mEntry;
     private StatusBarNotification mSbn;
     private AnimatorSet mExpandAnimation;
@@ -149,8 +139,6 @@
     private NotificationGuts mGutsContainer;
     private Drawable mPkgIcon;
 
-    private BubbleController mBubbleController;
-
     /** Whether this view is being shown as part of the blocking helper. */
     private boolean mIsForBlockingHelper;
 
@@ -167,9 +155,6 @@
     private OnClickListener mOnAlert = v -> {
         mExitReason = NotificationCounters.BLOCKING_HELPER_KEEP_SHOWING;
         mChosenImportance = IMPORTANCE_DEFAULT;
-        if (mStartedAsBubble) {
-            mChosenBubbleEnabled = false;
-        }
         applyAlertingBehavior(BEHAVIOR_ALERTING, true /* userTriggered */);
     };
 
@@ -177,18 +162,9 @@
     private OnClickListener mOnSilent = v -> {
         mExitReason = NotificationCounters.BLOCKING_HELPER_DELIVER_SILENTLY;
         mChosenImportance = IMPORTANCE_LOW;
-        if (mStartedAsBubble) {
-            mChosenBubbleEnabled = false;
-        }
         applyAlertingBehavior(BEHAVIOR_SILENT, true /* userTriggered */);
     };
 
-    /** Used by standard ui (in an experiment) {@see BubbleExperimentConfig#allowNotifBubbleMenu} */
-    private OnClickListener mOnBubble = v -> {
-        mExitReason = NotificationCounters.BLOCKING_HELPER_KEEP_SHOWING;
-        mChosenBubbleEnabled = true;
-        applyAlertingBehavior(BEHAVIOR_BUBBLE, true /* userTriggered */);
-    };
 
     // used by standard ui
     private OnClickListener mOnDismissSettings = v -> {
@@ -255,7 +231,6 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
 
-        mBubbleDescriptionView = findViewById(R.id.bubble_summary);
         mPriorityDescriptionView = findViewById(R.id.alert_summary);
         mSilentDescriptionView = findViewById(R.id.silence_summary);
     }
@@ -320,7 +295,6 @@
         mMetricsLogger = Dependency.get(MetricsLogger.class);
         mVisualStabilityManager = visualStabilityManager;
         mChannelEditorDialogController = Dependency.get(ChannelEditorDialogController.class);
-        mBubbleController = Dependency.get(BubbleController.class);
         mPackageName = pkg;
         mUniqueChannelsInRow = uniqueChannelsInRow;
         mNumUniqueChannelsInRow = uniqueChannelsInRow.size();
@@ -352,9 +326,6 @@
                     && numTotalChannels == 1;
         }
 
-        mIsBubbleable = mEntry.getBubbleMetadata() != null;
-        mStartedAsBubble = mEntry.isBubble();
-
         bindHeader();
         bindChannelDetails();
 
@@ -402,7 +373,6 @@
             findViewById(R.id.non_configurable_text).setVisibility(GONE);
             findViewById(R.id.non_configurable_multichannel_text).setVisibility(GONE);
             findViewById(R.id.interruptiveness_settings).setVisibility(VISIBLE);
-            findViewById(R.id.bubble).setVisibility(mIsBubbleable ? VISIBLE : GONE);
         }
 
         View turnOffButton = findViewById(R.id.turn_off_notifications);
@@ -416,14 +386,10 @@
 
         View silent = findViewById(R.id.silence);
         View alert = findViewById(R.id.alert);
-        View bubble = findViewById(R.id.bubble);
         silent.setOnClickListener(mOnSilent);
         alert.setOnClickListener(mOnAlert);
-        bubble.setOnClickListener(mOnBubble);
 
-        int behavior = mStartedAsBubble
-                ? BEHAVIOR_BUBBLE
-                : mWasShownHighPriority
+        int behavior = mWasShownHighPriority
                         ? BEHAVIOR_ALERTING
                         : BEHAVIOR_SILENT;
         applyAlertingBehavior(behavior, false /* userTriggered */);
@@ -587,14 +553,6 @@
                 }
             }
 
-            if (mChosenBubbleEnabled != null && mStartedAsBubble != mChosenBubbleEnabled) {
-                if (mChosenBubbleEnabled) {
-                    mBubbleController.onUserCreatedBubbleFromNotification(mEntry);
-                } else {
-                    mBubbleController.onUserDemotedBubbleFromNotification(mEntry);
-                }
-            }
-
             Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
             bgHandler.post(
                     new UpdateImportanceRunnable(mINotificationManager, mPackageName, mAppUid,
@@ -630,7 +588,6 @@
             TransitionManager.beginDelayedTransition(this, transition);
         }
 
-        View bubble = findViewById(R.id.bubble);
         View alert = findViewById(R.id.alert);
         View silence = findViewById(R.id.silence);
 
@@ -638,33 +595,18 @@
             case BEHAVIOR_ALERTING:
                 mPriorityDescriptionView.setVisibility(VISIBLE);
                 mSilentDescriptionView.setVisibility(GONE);
-                mBubbleDescriptionView.setVisibility(GONE);
                 post(() -> {
                     alert.setSelected(true);
                     silence.setSelected(false);
-                    bubble.setSelected(false);
                 });
                 break;
 
             case BEHAVIOR_SILENT:
                 mSilentDescriptionView.setVisibility(VISIBLE);
                 mPriorityDescriptionView.setVisibility(GONE);
-                mBubbleDescriptionView.setVisibility(GONE);
                 post(() -> {
                     alert.setSelected(false);
                     silence.setSelected(true);
-                    bubble.setSelected(false);
-                });
-                break;
-
-            case BEHAVIOR_BUBBLE:
-                mBubbleDescriptionView.setVisibility(VISIBLE);
-                mSilentDescriptionView.setVisibility(GONE);
-                mPriorityDescriptionView.setVisibility(GONE);
-                post(() -> {
-                    alert.setSelected(false);
-                    silence.setSelected(false);
-                    bubble.setSelected(true);
                 });
                 break;
 
@@ -673,9 +615,8 @@
         }
 
         boolean isAChange = mWasShownHighPriority != (behavior == BEHAVIOR_ALERTING);
-        boolean isABubbleChange = mStartedAsBubble != (behavior == BEHAVIOR_BUBBLE);
         TextView done = findViewById(R.id.done);
-        done.setText((isAChange || isABubbleChange)
+        done.setText(isAChange
                 ? R.string.inline_ok_button
                 : R.string.inline_done_button);
     }
@@ -684,7 +625,6 @@
         switch (action) {
             case ACTION_UNDO:
                 mChosenImportance = mStartingChannelImportance;
-                mChosenBubbleEnabled = mStartedAsBubble;
                 break;
             case ACTION_DELIVER_SILENTLY:
                 mExitReason = NotificationCounters.BLOCKING_HELPER_DELIVER_SILENTLY;
@@ -767,9 +707,6 @@
         if (mChosenImportance != null) {
             mStartingChannelImportance = mChosenImportance;
         }
-        if (mChosenBubbleEnabled != null) {
-            mStartedAsBubble = mChosenBubbleEnabled;
-        }
         mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED;
 
         if (mIsForBlockingHelper) {
@@ -969,9 +906,8 @@
     }
 
     @Retention(SOURCE)
-    @IntDef({BEHAVIOR_ALERTING, BEHAVIOR_SILENT, BEHAVIOR_BUBBLE})
+    @IntDef({BEHAVIOR_ALERTING, BEHAVIOR_SILENT})
     private @interface AlertingBehavior {}
     private static final int BEHAVIOR_ALERTING = 0;
     private static final int BEHAVIOR_SILENT = 1;
-    private static final int BEHAVIOR_BUBBLE = 2;
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java
index a3e1305..fa4bc2a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java
@@ -80,7 +80,15 @@
     public Drawable loadImage(Uri uri) {
         Drawable result = null;
         try {
-            result = hasCache() ? mImageCache.get(uri) : resolveImage(uri);
+            if (hasCache()) {
+                // if the uri isn't currently cached, try caching it first
+                if (!mImageCache.hasEntry(uri)) {
+                    mImageCache.preload((uri));
+                }
+                result = mImageCache.get(uri);
+            } else {
+                result = resolveImage(uri);
+            }
         } catch (IOException | SecurityException ex) {
             Log.d(TAG, "loadImage: Can't load image from " + uri, ex);
         }
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 b4ccb56..212cba6 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
@@ -46,6 +46,7 @@
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.AlphaOptimizedImageView;
 import com.android.systemui.statusbar.notification.row.NotificationGuts.GutsContent;
+import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 
 import java.util.ArrayList;
@@ -82,7 +83,6 @@
     private OnMenuEventListener mMenuListener;
     private boolean mDismissRtl;
     private boolean mIsForeground;
-    private final boolean mIsUsingBidirectionalSwipe;
 
     private ValueAnimator mFadeAnimator;
     private boolean mAnimating;
@@ -115,19 +115,11 @@
     private boolean mIsUserTouching;
 
     public NotificationMenuRow(Context context) {
-        //TODO: (b/131242807) not using bidirectional swipe for now
-        this(context, false);
-    }
-
-    // Only needed for testing until we want to turn bidirectional swipe back on
-    @VisibleForTesting
-    NotificationMenuRow(Context context, boolean isUsingBidirectionalSwipe) {
         mContext = context;
         mShouldShowMenu = context.getResources().getBoolean(R.bool.config_showNotificationGear);
         mHandler = new Handler(Looper.getMainLooper());
         mLeftMenuItems = new ArrayList<>();
         mRightMenuItems = new ArrayList<>();
-        mIsUsingBidirectionalSwipe = isUsingBidirectionalSwipe;
     }
 
     @Override
@@ -268,23 +260,18 @@
             mSnoozeItem = createSnoozeItem(mContext);
         }
         mAppOpsItem = createAppOpsItem(mContext);
-        if (mIsUsingBidirectionalSwipe) {
-            mInfoItem = createInfoItem(mContext, !mParent.getEntry().isHighPriority());
+        if (mParent.getEntry().getBucket() == NotificationSectionsManager.BUCKET_PEOPLE) {
+            mInfoItem = createConversationItem(mContext);
         } else {
             mInfoItem = createInfoItem(mContext);
         }
 
-        if (!mIsUsingBidirectionalSwipe) {
-            if (!isForeground && showSnooze) {
-                mRightMenuItems.add(mSnoozeItem);
-            }
-            mRightMenuItems.add(mInfoItem);
-            mRightMenuItems.add(mAppOpsItem);
-            mLeftMenuItems.addAll(mRightMenuItems);
-        } else {
-            ArrayList<MenuItem> menuItems = mDismissRtl ? mLeftMenuItems : mRightMenuItems;
-            menuItems.add(mInfoItem);
+        if (!isForeground && showSnooze) {
+            mRightMenuItems.add(mSnoozeItem);
         }
+        mRightMenuItems.add(mInfoItem);
+        mRightMenuItems.add(mAppOpsItem);
+        mLeftMenuItems.addAll(mRightMenuItems);
 
         populateMenuViews();
         if (resetState) {
@@ -631,12 +618,12 @@
 
     @Override
     public boolean shouldShowGutsOnSnapOpen() {
-        return mIsUsingBidirectionalSwipe;
+        return false;
     }
 
     @Override
     public MenuItem menuItemToExposeOnSnap() {
-        return mIsUsingBidirectionalSwipe ? mInfoItem : null;
+        return null;
     }
 
     @Override
@@ -662,6 +649,16 @@
         return snooze;
     }
 
+    static NotificationMenuItem createConversationItem(Context context) {
+        Resources res = context.getResources();
+        String infoDescription = res.getString(R.string.notification_menu_gear_description);
+        NotificationConversationInfo infoContent =
+                (NotificationConversationInfo) LayoutInflater.from(context).inflate(
+                        R.layout.notification_conversation_info, null, false);
+        return new NotificationMenuItem(context, infoDescription, infoContent,
+                R.drawable.ic_settings);
+    }
+
     static NotificationMenuItem createInfoItem(Context context) {
         Resources res = context.getResources();
         String infoDescription = res.getString(R.string.notification_menu_gear_description);
@@ -671,17 +668,6 @@
                 R.drawable.ic_settings);
     }
 
-    static NotificationMenuItem createInfoItem(Context context, boolean isCurrentlySilent) {
-        Resources res = context.getResources();
-        String infoDescription = res.getString(R.string.notification_menu_gear_description);
-        NotificationInfo infoContent = (NotificationInfo) LayoutInflater.from(context).inflate(
-                R.layout.notification_info, null, false);
-        int iconResId = isCurrentlySilent
-                ? R.drawable.ic_notifications_silence
-                : R.drawable.ic_notifications_alert;
-        return new NotificationMenuItem(context, infoDescription, infoContent, iconResId);
-    }
-
     static MenuItem createAppOpsItem(Context context) {
         AppOpsInfo appOpsContent = (AppOpsInfo) LayoutInflater.from(context).inflate(
                 R.layout.app_ops_info, null, false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
new file mode 100644
index 0000000..df8653c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row;
+
+import javax.inject.Singleton;
+
+import dagger.Binds;
+import dagger.Module;
+
+/**
+ * Dagger Module containing notification row and view inflation implementations.
+ */
+@Module
+public abstract class NotificationRowModule {
+    /**
+     * Provides notification row content binder instance.
+     */
+    @Binds
+    @Singleton
+    public abstract NotificationRowContentBinder provideNotificationRowContentBinder(
+            NotificationContentInflater contentBinderImpl);
+
+    /**
+     * Provides notification remote view cache instance.
+     */
+    @Binds
+    @Singleton
+    public abstract NotifRemoteViewCache provideNotifRemoteViewCache(
+            NotifRemoteViewCacheImpl cacheImpl);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
index 0160c547..c173b4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
@@ -26,6 +26,9 @@
 import com.android.systemui.R;
 import com.android.systemui.statusbar.InflationTask;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
+
+import javax.inject.Inject;
 
 /**
  * An inflater task that asynchronously inflates a ExpandableNotificationRow
@@ -34,12 +37,20 @@
 
     private static final String TAG = "RowInflaterTask";
     private static final boolean TRACE_ORIGIN = true;
+    private final NotificationRowComponent.Builder mNotificationRowComponentBuilder;
 
     private RowInflationFinishedListener mListener;
     private NotificationEntry mEntry;
     private boolean mCancelled;
     private Throwable mInflateOrigin;
 
+    @Inject
+    public RowInflaterTask(
+            NotificationRowComponent.Builder notificationRowComponentBuilder) {
+        super();
+        mNotificationRowComponentBuilder = notificationRowComponentBuilder;
+    }
+
     /**
      * Inflates a new notificationView. This should not be called twice on this object
      */
@@ -64,6 +75,12 @@
     public void onInflateFinished(View view, int resid, ViewGroup parent) {
         if (!mCancelled) {
             try {
+                // Setup the controller for the view.
+                NotificationRowComponent component = mNotificationRowComponentBuilder
+                        .activatableNotificationView((ActivatableNotificationView) view)
+                        .build();
+                component.getActivatableNotificationViewController().init();
+
                 mEntry.onInflationTaskFinished();
                 mListener.onInflationFinished((ExpandableNotificationRow) view);
             } catch (Throwable t) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowComponent.java
new file mode 100644
index 0000000..f16ea7a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowComponent.java
@@ -0,0 +1,61 @@
+/*
+ * 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.statusbar.notification.row.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Scope;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/**
+ * Dagger subcomponent for Notification related views.
+ */
+@Subcomponent(modules = {})
+@NotificationRowComponent.NotificationRowScope
+public interface NotificationRowComponent {
+    /**
+     * Builder for {@link NotificationRowComponent}.
+     */
+    @Subcomponent.Builder
+    interface Builder {
+        @BindsInstance
+        Builder activatableNotificationView(ActivatableNotificationView view);
+        NotificationRowComponent build();
+    }
+
+    /**
+     * Scope annotation for singleton items within the StatusBarComponent.
+     */
+    @Documented
+    @Retention(RUNTIME)
+    @Scope
+    @interface NotificationRowScope {}
+
+    /**
+     * Creates a ActivatableNotificationViewController.
+     */
+    @NotificationRowScope
+    ActivatableNotificationViewController getActivatableNotificationViewController();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
index 352ba0f..874d81d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
@@ -28,6 +28,7 @@
 import android.media.session.PlaybackState;
 import android.metrics.LogMaker;
 import android.os.Handler;
+import android.service.notification.StatusBarNotification;
 import android.text.format.DateUtils;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -46,7 +47,7 @@
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.TransformableView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.phone.StatusBarWindowController;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.util.Utils;
 
 import java.util.Timer;
@@ -176,27 +177,31 @@
         final MediaSession.Token token = mRow.getEntry().getSbn().getNotification().extras
                 .getParcelable(Notification.EXTRA_MEDIA_SESSION);
 
-        if (Utils.useQsMediaPlayer(mContext)) {
+        if (Utils.useQsMediaPlayer(mContext) && token != null) {
             final int[] compactActions = mRow.getEntry().getSbn().getNotification().extras
                     .getIntArray(Notification.EXTRA_COMPACT_ACTIONS);
             int tintColor = getNotificationHeader().getOriginalIconColor();
-            StatusBarWindowController ctrl = Dependency.get(StatusBarWindowController.class);
-            QuickQSPanel panel = ctrl.getStatusBarView().findViewById(
+            NotificationShadeWindowController ctrl = Dependency.get(
+                    NotificationShadeWindowController.class);
+            QuickQSPanel panel = ctrl.getNotificationShadeView().findViewById(
                     com.android.systemui.R.id.quick_qs_panel);
+            StatusBarNotification sbn = mRow.getEntry().getSbn();
+            Notification notif = sbn.getNotification();
             panel.getMediaPlayer().setMediaSession(token,
-                    mRow.getEntry().getSbn().getNotification().getSmallIcon(),
+                    notif.getSmallIcon(),
                     tintColor,
                     mBackgroundColor,
                     mActions,
-                    compactActions);
-            QSPanel bigPanel = ctrl.getStatusBarView().findViewById(
+                    compactActions,
+                    notif.contentIntent);
+            QSPanel bigPanel = ctrl.getNotificationShadeView().findViewById(
                     com.android.systemui.R.id.quick_settings_panel);
             bigPanel.addMediaSession(token,
-                    mRow.getEntry().getSbn().getNotification().getSmallIcon(),
+                    notif.getSmallIcon(),
                     tintColor,
                     mBackgroundColor,
                     mActions,
-                    mRow.getEntry().getSbn());
+                    sbn);
         }
 
         boolean showCompactSeekbar = mMediaManager.getShowCompactMediaSeekbar();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
index 09c1fad..23433cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
@@ -33,12 +33,14 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
 import com.android.systemui.statusbar.notification.people.DataListener;
 import com.android.systemui.statusbar.notification.people.PeopleHubSectionFooterViewAdapter;
 import com.android.systemui.statusbar.notification.people.PeopleHubSectionFooterViewBoundary;
 import com.android.systemui.statusbar.notification.people.PersonViewModel;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 
@@ -46,6 +48,8 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import javax.inject.Inject;
+
 import kotlin.sequences.Sequence;
 
 /**
@@ -59,11 +63,13 @@
     private static final String TAG = "NotifSectionsManager";
     private static final boolean DEBUG = false;
 
-    private final NotificationStackScrollLayout mParent;
+    private NotificationStackScrollLayout mParent;
     private final ActivityStarter mActivityStarter;
     private final StatusBarStateController mStatusBarStateController;
     private final ConfigurationController mConfigurationController;
     private final int mNumberOfSections;
+    private final NotificationSectionsFeatureManager mSectionsFeatureManager;
+    private final NotificationRowComponent.Builder mNotificationRowComponentBuilder;
     private boolean mInitialized = false;
 
     private SectionHeaderView mGentleHeader;
@@ -99,22 +105,25 @@
 
     @Nullable private View.OnClickListener mOnClearGentleNotifsClickListener;
 
+    @Inject
     NotificationSectionsManager(
-            NotificationStackScrollLayout parent,
             ActivityStarter activityStarter,
             StatusBarStateController statusBarStateController,
             ConfigurationController configurationController,
             PeopleHubSectionFooterViewAdapter peopleHubViewAdapter,
-            int numberOfSections) {
-        mParent = parent;
+            NotificationSectionsFeatureManager sectionsFeatureManager,
+            NotificationRowComponent.Builder notificationRowComponentBuilder) {
         mActivityStarter = activityStarter;
         mStatusBarStateController = statusBarStateController;
         mConfigurationController = configurationController;
         mPeopleHubViewAdapter = peopleHubViewAdapter;
-        mNumberOfSections = numberOfSections;
+        mSectionsFeatureManager = sectionsFeatureManager;
+        mNumberOfSections = mSectionsFeatureManager.getNumberOfBuckets();
+        mNotificationRowComponentBuilder = notificationRowComponentBuilder;
     }
 
-    NotificationSection[] createSectionsForBuckets(int[] buckets) {
+    NotificationSection[] createSectionsForBuckets() {
+        int[] buckets = mSectionsFeatureManager.getNotificationBuckets();
         NotificationSection[] sections = new NotificationSection[buckets.length];
         for (int i = 0; i < buckets.length; i++) {
             sections[i] = new NotificationSection(mParent, buckets[i] /* bucket */);
@@ -124,11 +133,13 @@
     }
 
     /** Must be called before use. */
-    void initialize(LayoutInflater layoutInflater) {
+    void initialize(
+            NotificationStackScrollLayout parent, LayoutInflater layoutInflater) {
         if (mInitialized) {
             throw new IllegalStateException("NotificationSectionsManager already initialized");
         }
         mInitialized = true;
+        mParent = parent;
         reinflateViews(layoutInflater);
         mPeopleHubViewAdapter.bindView(mPeopleHubViewBoundary);
         mConfigurationController.addCallback(mConfigurationListener);
@@ -159,6 +170,11 @@
 
         mGentleHeader = (SectionHeaderView) layoutInflater.inflate(
                 R.layout.status_bar_notification_section_header, mParent, false);
+        NotificationRowComponent sectionHeaderComponent = mNotificationRowComponentBuilder
+                .activatableNotificationView(mGentleHeader)
+                .build();
+        sectionHeaderComponent.getActivatableNotificationViewController().init();
+
         mGentleHeader.setOnHeaderClickListener(this::onGentleHeaderClick);
         mGentleHeader.setOnClearAllClickListener(this::onClearGentleNotifsClick);
 
@@ -169,6 +185,11 @@
         mPeopleHubView = (PeopleHubView) layoutInflater.inflate(
                 R.layout.people_strip, mParent, false);
 
+        NotificationRowComponent notificationRowComponent = mNotificationRowComponentBuilder
+                .activatableNotificationView(mPeopleHubView)
+                .build();
+        notificationRowComponent.getActivatableNotificationViewController().init();
+
         if (oldPeopleHubPos != -1) {
             mParent.addView(mPeopleHubView, oldPeopleHubPos);
         }
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 71342c5..11ead8b 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
@@ -88,7 +88,6 @@
 import com.android.systemui.R;
 import com.android.systemui.SwipeHelper;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
@@ -109,14 +108,12 @@
 import com.android.systemui.statusbar.notification.FakeShadowView;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.ShadeViewRefactor;
 import com.android.systemui.statusbar.notification.ShadeViewRefactor.RefactorComponent;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.people.PeopleHubSectionFooterViewAdapter;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -134,7 +131,7 @@
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
-import com.android.systemui.statusbar.phone.NotificationPanelView;
+import com.android.systemui.statusbar.phone.NotificationPanelViewController;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -496,8 +493,7 @@
     protected boolean mClearAllEnabled;
 
     private Interpolator mHideXInterpolator = Interpolators.FAST_OUT_SLOW_IN;
-    private NotificationPanelView mNotificationPanel;
-    private final ShadeController mShadeController = Dependency.get(ShadeController.class);
+    private NotificationPanelViewController mNotificationPanelController;
 
     private final NotificationGutsManager mNotificationGutsManager;
     private final NotificationSectionsManager mSectionsManager;
@@ -513,17 +509,14 @@
             @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
             NotificationRoundnessManager notificationRoundnessManager,
             DynamicPrivacyController dynamicPrivacyController,
-            ConfigurationController configurationController,
-            ActivityStarter activityStarter,
-            StatusBarStateController statusBarStateController,
+            SysuiStatusBarStateController statusBarStateController,
             HeadsUpManagerPhone headsUpManager,
             KeyguardBypassController keyguardBypassController,
             FalsingManager falsingManager,
             NotificationLockscreenUserManager notificationLockscreenUserManager,
             NotificationGutsManager notificationGutsManager,
-            NotificationSectionsFeatureManager sectionsFeatureManager,
-            PeopleHubSectionFooterViewAdapter peopleHubViewAdapter,
-            ZenModeController zenController) {
+            ZenModeController zenController,
+            NotificationSectionsManager notificationSectionsManager) {
         super(context, attrs, 0, 0);
         Resources res = getResources();
 
@@ -540,22 +533,14 @@
         mFalsingManager = falsingManager;
         mZenController = zenController;
 
-        int[] buckets = sectionsFeatureManager.getNotificationBuckets();
-        mSectionsManager =
-                new NotificationSectionsManager(
-                        this,
-                        activityStarter,
-                        statusBarStateController,
-                        configurationController,
-                        peopleHubViewAdapter,
-                        buckets.length);
-        mSectionsManager.initialize(LayoutInflater.from(context));
+        mSectionsManager = notificationSectionsManager;
+        mSectionsManager.initialize(this, LayoutInflater.from(context));
         mSectionsManager.setOnClearGentleNotifsClickListener(v -> {
             // Leave the shade open if there will be other notifs left over to clear
             final boolean closeShade = !hasActiveClearableNotifications(ROWS_HIGH_PRIORITY);
             clearNotifications(ROWS_GENTLE, closeShade);
         });
-        mSections = mSectionsManager.createSectionsForBuckets(buckets);
+        mSections = mSectionsManager.createSectionsForBuckets();
 
         mAmbientState = new AmbientState(context, mSectionsManager, mHeadsUpManager);
         mBgColor = context.getColor(R.color.notification_shade_background_color);
@@ -618,7 +603,7 @@
         });
         dynamicPrivacyController.addListener(this);
         mDynamicPrivacyController = dynamicPrivacyController;
-        mStatusbarStateController = (SysuiStatusBarStateController) statusBarStateController;
+        mStatusbarStateController = statusBarStateController;
     }
 
     private void updateDismissRtlSetting(boolean dismissRtl) {
@@ -5519,7 +5504,8 @@
 
         if (viewsToRemove.isEmpty()) {
             if (closeShade) {
-                mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+                Dependency.get(ShadeController.class).animateCollapsePanels(
+                        CommandQueue.FLAG_EXCLUDE_NONE);
             }
             return;
         }
@@ -5577,11 +5563,12 @@
 
         final Runnable onSlideAwayAnimationComplete = () -> {
             if (closeShade) {
-                mShadeController.addPostCollapseAction(() -> {
+                Dependency.get(ShadeController.class).addPostCollapseAction(() -> {
                     setDismissAllInProgress(false);
                     onAnimationComplete.run();
                 });
-                mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+                Dependency.get(ShadeController.class).animateCollapsePanels(
+                        CommandQueue.FLAG_EXCLUDE_NONE);
             } else {
                 setDismissAllInProgress(false);
                 onAnimationComplete.run();
@@ -5657,8 +5644,9 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void setNotificationPanel(NotificationPanelView notificationPanelView) {
-        mNotificationPanel = notificationPanelView;
+    public void setNotificationPanelController(
+            NotificationPanelViewController notificationPanelViewController) {
+        mNotificationPanelController = notificationPanelViewController;
     }
 
     public void updateIconAreaViews() {
@@ -6205,6 +6193,11 @@
         }
 
         @Override
+        public void onSnooze(StatusBarNotification sbn, int hours) {
+            mStatusBar.setNotificationSnoozed(sbn, hours);
+        }
+
+        @Override
         public boolean shouldDismissQuickly() {
             return NotificationStackScrollLayout.this.isExpanded() && mAmbientState.isFullyAwake();
         }
@@ -6402,7 +6395,7 @@
 
                 if (!mAmbientState.isDozing() || startingChild != null) {
                     // We have notifications, go to locked shade.
-                    mShadeController.goToLockedShade(startingChild);
+                    Dependency.get(ShadeController.class).goToLockedShade(startingChild);
                     if (startingChild instanceof ExpandableNotificationRow) {
                         ExpandableNotificationRow row = (ExpandableNotificationRow) startingChild;
                         row.onExpandedByGesture(true /* drag down is always an open */);
@@ -6441,7 +6434,7 @@
 
         @Override
         public void setEmptyDragAmount(float amount) {
-            mNotificationPanel.setEmptyDragAmount(amount);
+            mNotificationPanelController.setEmptyDragAmount(amount);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index 4845ea1..6c0655e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -282,6 +282,11 @@
         mCallback.onSnooze(sbn, snoozeOption);
     }
 
+    @Override
+    public void snooze(StatusBarNotification sbn, int hours) {
+        mCallback.onSnooze(sbn, hours);
+    }
+
     @VisibleForTesting
     protected void handleMenuCoveredOrDismissed() {
         View exposedMenuView = getExposedMenuView();
@@ -447,6 +452,8 @@
 
         void onSnooze(StatusBarNotification sbn, SnoozeOption snoozeOption);
 
+        void onSnooze(StatusBarNotification sbn, int hours);
+
         void onDismiss();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 4880520..691e1c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -141,7 +141,7 @@
     private final KeyguardUpdateMonitor mUpdateMonitor;
     private final DozeParameters mDozeParameters;
     private final KeyguardStateController mKeyguardStateController;
-    private final StatusBarWindowController mStatusBarWindowController;
+    private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final Context mContext;
     private final int mWakeUpDelay;
     private int mMode;
@@ -162,7 +162,7 @@
     public BiometricUnlockController(Context context, DozeScrimController dozeScrimController,
             KeyguardViewMediator keyguardViewMediator, ScrimController scrimController,
             StatusBar statusBar, ShadeController shadeController,
-            StatusBarWindowController statusBarWindowController,
+            NotificationShadeWindowController notificationShadeWindowController,
             KeyguardStateController keyguardStateController, Handler handler,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             @Main Resources resources,
@@ -177,7 +177,8 @@
         mMediaManager = Dependency.get(NotificationMediaManager.class);
         Dependency.get(WakefulnessLifecycle.class).addObserver(mWakefulnessObserver);
         Dependency.get(ScreenLifecycle.class).addObserver(mScreenObserver);
-        mStatusBarWindowController = statusBarWindowController;
+
+        mNotificationShadeWindowController = notificationShadeWindowController;
         mDozeScrimController = dozeScrimController;
         mKeyguardViewMediator = keyguardViewMediator;
         mScrimController = scrimController;
@@ -284,7 +285,7 @@
             // notifications would light up first, creating an unpleasant animation.
             // Defer changing the screen brightness by forcing doze brightness on our window
             // until the clock and the notifications are faded out.
-            mStatusBarWindowController.setForceDozeBrightness(true);
+            mNotificationShadeWindowController.setForceDozeBrightness(true);
         }
         // During wake and unlock, we need to draw black before waking up to avoid abrupt
         // brightness changes due to display state transitions.
@@ -340,7 +341,7 @@
                     Trace.beginSection("MODE_WAKE_AND_UNLOCK_FROM_DREAM");
                     mUpdateMonitor.awakenFromDream();
                 }
-                mStatusBarWindowController.setStatusBarFocusable(false);
+                mNotificationShadeWindowController.setNotificationShadeFocusable(false);
                 if (delayWakeUp) {
                     mHandler.postDelayed(wakeUp, mWakeUpDelay);
                 } else {
@@ -508,7 +509,7 @@
         mHandler.postDelayed(new Runnable() {
             @Override
             public void run() {
-                mStatusBarWindowController.setForceDozeBrightness(false);
+                mNotificationShadeWindowController.setForceDozeBrightness(false);
             }
         }, StatusBar.FADE_KEYGUARD_DURATION_PULSING);
     }
@@ -522,7 +523,7 @@
 
     private void resetMode() {
         mMode = MODE_NONE;
-        mStatusBarWindowController.setForceDozeBrightness(false);
+        mNotificationShadeWindowController.setForceDozeBrightness(false);
         if (mStatusBar.getNavigationBarView() != null) {
             mStatusBar.getNavigationBarView().setWakeAndUnlocking(false);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 8b31da4..e03db2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -230,7 +230,7 @@
         // The shelf will be hidden when dozing with a custom clock, we must show notification
         // icons in this occasion.
         if (mStatusBarStateController.isDozing()
-                && mStatusBarComponent.getPanel().hasCustomClock()) {
+                && mStatusBarComponent.getPanelController().hasCustomClock()) {
             state |= DISABLE_CLOCK | DISABLE_SYSTEM_INFO;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index ce1123e..04efc2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -85,13 +85,13 @@
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final VisualStabilityManager mVisualStabilityManager;
     private final PulseExpansionHandler mPulseExpansionHandler;
-    private final StatusBarWindowController mStatusBarWindowController;
+    private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
-    private StatusBarWindowViewController mStatusBarWindowViewController;
+    private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
     private final LockscreenLockIconController mLockscreenLockIconController;
     private NotificationIconAreaController mNotificationIconAreaController;
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    private NotificationPanelView mNotificationPanel;
+    private NotificationPanelViewController mNotificationPanel;
     private View mAmbientIndicationContainer;
     private StatusBar mStatusBar;
 
@@ -108,7 +108,7 @@
             DozeScrimController dozeScrimController, KeyguardUpdateMonitor keyguardUpdateMonitor,
             VisualStabilityManager visualStabilityManager,
             PulseExpansionHandler pulseExpansionHandler,
-            StatusBarWindowController statusBarWindowController,
+            NotificationShadeWindowController notificationShadeWindowController,
             NotificationWakeUpCoordinator notificationWakeUpCoordinator,
             LockscreenLockIconController lockscreenLockIconController) {
         super();
@@ -127,7 +127,7 @@
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mVisualStabilityManager = visualStabilityManager;
         mPulseExpansionHandler = pulseExpansionHandler;
-        mStatusBarWindowController = statusBarWindowController;
+        mNotificationShadeWindowController = notificationShadeWindowController;
         mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
         mLockscreenLockIconController = lockscreenLockIconController;
     }
@@ -140,13 +140,13 @@
     public void initialize(StatusBar statusBar,
             NotificationIconAreaController notificationIconAreaController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
-            StatusBarWindowViewController statusBarWindowViewController,
-            NotificationPanelView notificationPanel, View ambientIndicationContainer) {
+            NotificationShadeWindowViewController notificationShadeWindowViewController,
+            NotificationPanelViewController notificationPanel, View ambientIndicationContainer) {
         mStatusBar = statusBar;
         mNotificationIconAreaController = notificationIconAreaController;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mNotificationPanel = notificationPanel;
-        mStatusBarWindowViewController = statusBarWindowViewController;
+        mNotificationShadeWindowViewController = notificationShadeWindowViewController;
         mAmbientIndicationContainer = ambientIndicationContainer;
         mBiometricUnlockController = mBiometricUnlockControllerLazy.get();
     }
@@ -292,7 +292,7 @@
         }
         mIgnoreTouchWhilePulsing = ignore;
         if (mStatusBarStateController.isDozing() && ignore) {
-            mStatusBarWindowViewController.cancelCurrentTouch();
+            mNotificationShadeWindowViewController.cancelCurrentTouch();
         }
     }
 
@@ -391,7 +391,7 @@
 
     @Override
     public void setDozeScreenBrightness(int value) {
-        mStatusBarWindowController.setDozeScreenBrightness(value);
+        mNotificationShadeWindowController.setDozeScreenBrightness(value);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
index f25f910..6bd122d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
@@ -31,7 +31,6 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.util.Log;
-import android.util.StatsLog;
 import android.view.ISystemGestureExclusionListener;
 import android.view.InputChannel;
 import android.view.InputDevice;
@@ -54,6 +53,7 @@
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.SysUiStatsLog;
 
 import java.io.PrintWriter;
 import java.util.concurrent.Executor;
@@ -97,6 +97,8 @@
 
     // The edge width where touch down is allowed
     private int mEdgeWidth;
+    // The bottom gesture area height
+    private int mBottomGestureHeight;
     // The slop to distinguish between horizontal and vertical motion
     private final float mTouchSlop;
     // Duration after which we consider the event as longpress.
@@ -131,23 +133,23 @@
                     mOverviewProxyService.notifyBackAction(true, (int) mDownPoint.x,
                             (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
                     int backtype = (mInRejectedExclusion
-                            ? StatsLog.BACK_GESTURE__TYPE__COMPLETED_REJECTED :
-                            StatsLog.BACK_GESTURE__TYPE__COMPLETED);
-                    StatsLog.write(StatsLog.BACK_GESTURE_REPORTED_REPORTED, backtype,
+                            ? SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED_REJECTED :
+                            SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED);
+                    SysUiStatsLog.write(SysUiStatsLog.BACK_GESTURE_REPORTED_REPORTED, backtype,
                             (int) mDownPoint.y, mIsOnLeftEdge
-                                    ? StatsLog.BACK_GESTURE__X_LOCATION__LEFT :
-                                    StatsLog.BACK_GESTURE__X_LOCATION__RIGHT);
+                                    ? SysUiStatsLog.BACK_GESTURE__X_LOCATION__LEFT :
+                                    SysUiStatsLog.BACK_GESTURE__X_LOCATION__RIGHT);
                 }
 
                 @Override
                 public void cancelBack() {
                     mOverviewProxyService.notifyBackAction(false, (int) mDownPoint.x,
                             (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
-                    int backtype = StatsLog.BACK_GESTURE__TYPE__INCOMPLETE;
-                    StatsLog.write(StatsLog.BACK_GESTURE_REPORTED_REPORTED, backtype,
+                    int backtype = SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE;
+                    SysUiStatsLog.write(SysUiStatsLog.BACK_GESTURE_REPORTED_REPORTED, backtype,
                             (int) mDownPoint.y, mIsOnLeftEdge
-                                    ? StatsLog.BACK_GESTURE__X_LOCATION__LEFT :
-                                    StatsLog.BACK_GESTURE__X_LOCATION__RIGHT);
+                                    ? SysUiStatsLog.BACK_GESTURE__X_LOCATION__LEFT :
+                                    SysUiStatsLog.BACK_GESTURE__X_LOCATION__RIGHT);
                 }
             };
 
@@ -174,6 +176,8 @@
     public void updateCurrentUserResources(Resources res) {
         mEdgeWidth = res.getDimensionPixelSize(
                 com.android.internal.R.dimen.config_backGestureInset);
+        mBottomGestureHeight = res.getDimensionPixelSize(
+                com.android.internal.R.dimen.navigation_bar_gesture_height);
     }
 
     /**
@@ -300,7 +304,7 @@
         layoutParams.setTitle(TAG + mContext.getDisplayId());
         layoutParams.accessibilityTitle = mContext.getString(R.string.nav_bar_edge_panel);
         layoutParams.windowAnimations = 0;
-        layoutParams.setFitWindowInsetsTypes(0 /* types */);
+        layoutParams.setFitInsetsTypes(0 /* types */);
         return layoutParams;
     }
 
@@ -316,6 +320,11 @@
             return false;
         }
 
+        // Disallow if we are in the bottom gesture area
+        if (y >= (mDisplaySize.y - mBottomGestureHeight)) {
+            return false;
+        }
+
         // Always allow if the user is in a transient sticky immersive state
         if (mIsNavBarShownTransiently) {
             return true;
@@ -325,10 +334,10 @@
         if (isInExcludedRegion) {
             mOverviewProxyService.notifyBackAction(false /* completed */, -1, -1,
                     false /* isButton */, !mIsOnLeftEdge);
-            StatsLog.write(StatsLog.BACK_GESTURE_REPORTED_REPORTED,
-                    StatsLog.BACK_GESTURE__TYPE__INCOMPLETE_EXCLUDED, y,
-                    mIsOnLeftEdge ? StatsLog.BACK_GESTURE__X_LOCATION__LEFT :
-                            StatsLog.BACK_GESTURE__X_LOCATION__RIGHT);
+            SysUiStatsLog.write(SysUiStatsLog.BACK_GESTURE_REPORTED_REPORTED,
+                    SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_EXCLUDED, y,
+                    mIsOnLeftEdge ? SysUiStatsLog.BACK_GESTURE__X_LOCATION__LEFT :
+                            SysUiStatsLog.BACK_GESTURE__X_LOCATION__RIGHT);
         } else {
             mInRejectedExclusion = mUnrestrictedExcludeRegion.contains(x, y);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java
index 783e7ad..16b5a23 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java
@@ -84,7 +84,7 @@
                 PixelFormat.TRANSLUCENT);
         lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
         lp.setTitle("FloatingRotationButton");
-        lp.setFitWindowInsetsTypes(0 /*types */);
+        lp.setFitInsetsTypes(0 /*types */);
         switch (mWindowManager.getDefaultDisplay().getRotation()) {
             case Surface.ROTATION_0:
                 lp.gravity = Gravity.BOTTOM | Gravity.LEFT;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 8e5a912..c39ee3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -58,7 +58,7 @@
     private final View mClockView;
     private final View mOperatorNameView;
     private final DarkIconDispatcher mDarkIconDispatcher;
-    private final NotificationPanelView mPanelView;
+    private final NotificationPanelViewController mNotificationPanelViewController;
     private final Consumer<ExpandableNotificationRow>
             mSetTrackingHeadsUp = this::setTrackingHeadsUp;
     private final Runnable mUpdatePanelTranslation = this::updatePanelTranslation;
@@ -92,20 +92,22 @@
 
     public HeadsUpAppearanceController(
             NotificationIconAreaController notificationIconAreaController,
-            HeadsUpManagerPhone headsUpManager, View statusbarView,
+            HeadsUpManagerPhone headsUpManager,
+            View notificationShadeView,
             SysuiStatusBarStateController statusBarStateController,
             KeyguardBypassController keyguardBypassController,
             KeyguardStateController keyguardStateController,
-            NotificationWakeUpCoordinator wakeUpCoordinator, CommandQueue commandQueue) {
+            NotificationWakeUpCoordinator wakeUpCoordinator, CommandQueue commandQueue,
+            NotificationPanelViewController notificationPanelViewController, View statusBarView) {
         this(notificationIconAreaController, headsUpManager, statusBarStateController,
                 keyguardBypassController, wakeUpCoordinator, keyguardStateController,
                 commandQueue,
-                statusbarView.findViewById(R.id.heads_up_status_bar_view),
-                statusbarView.findViewById(R.id.notification_stack_scroller),
-                statusbarView.findViewById(R.id.notification_panel),
-                statusbarView.findViewById(R.id.clock),
-                statusbarView.findViewById(R.id.operator_name_frame),
-                statusbarView.findViewById(R.id.centered_icon_area));
+                statusBarView.findViewById(R.id.heads_up_status_bar_view),
+                notificationShadeView.findViewById(R.id.notification_stack_scroller),
+                notificationPanelViewController,
+                statusBarView.findViewById(R.id.clock),
+                statusBarView.findViewById(R.id.operator_name_frame),
+                statusBarView.findViewById(R.id.centered_icon_area));
     }
 
     @VisibleForTesting
@@ -119,7 +121,7 @@
             CommandQueue commandQueue,
             HeadsUpStatusBarView headsUpStatusBarView,
             NotificationStackScrollLayout stackScroller,
-            NotificationPanelView panelView,
+            NotificationPanelViewController notificationPanelViewController,
             View clockView,
             View operatorNameView,
             View centeredIconView) {
@@ -131,10 +133,10 @@
         headsUpStatusBarView.setOnDrawingRectChangedListener(
                 () -> updateIsolatedIconLocation(true /* requireUpdate */));
         mStackScroller = stackScroller;
-        mPanelView = panelView;
-        panelView.addTrackingHeadsUpListener(mSetTrackingHeadsUp);
-        panelView.addVerticalTranslationListener(mUpdatePanelTranslation);
-        panelView.setHeadsUpAppearanceController(this);
+        mNotificationPanelViewController = notificationPanelViewController;
+        notificationPanelViewController.addTrackingHeadsUpListener(mSetTrackingHeadsUp);
+        notificationPanelViewController.addVerticalTranslationListener(mUpdatePanelTranslation);
+        notificationPanelViewController.setHeadsUpAppearanceController(this);
         mStackScroller.addOnExpandedHeightChangedListener(mSetExpandedHeight);
         mStackScroller.addOnLayoutChangeListener(mStackScrollLayoutChangeListener);
         mStackScroller.setHeadsUpAppearanceController(this);
@@ -169,9 +171,9 @@
         mHeadsUpManager.removeListener(this);
         mHeadsUpStatusBarView.setOnDrawingRectChangedListener(null);
         mWakeUpCoordinator.removeListener(this);
-        mPanelView.removeTrackingHeadsUpListener(mSetTrackingHeadsUp);
-        mPanelView.removeVerticalTranslationListener(mUpdatePanelTranslation);
-        mPanelView.setHeadsUpAppearanceController(null);
+        mNotificationPanelViewController.removeTrackingHeadsUpListener(mSetTrackingHeadsUp);
+        mNotificationPanelViewController.removeVerticalTranslationListener(mUpdatePanelTranslation);
+        mNotificationPanelViewController.setHeadsUpAppearanceController(null);
         mStackScroller.removeOnExpandedHeightChangedListener(mSetExpandedHeight);
         mStackScroller.removeOnLayoutChangeListener(mStackScrollLayoutChangeListener);
         mDarkIconDispatcher.removeDarkReceiver(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 6ac6d35..c6e3fde 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -65,7 +65,7 @@
     private final StatusBarStateController mStatusBarStateController;
     private final KeyguardBypassController mBypassController;
     private final int mAutoHeadsUpNotificationDecay;
-    private View mStatusBarWindowView;
+    private View mNotificationShadeWindowView;
     private NotificationGroupManager mGroupManager;
     private VisualStabilityManager mVisualStabilityManager;
     private StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
@@ -124,14 +124,13 @@
         initResources();
     }
 
-
-    public void setUp(@NonNull View statusBarWindowView,
+    public void setUp(@NonNull View notificationShadeWindowView,
             @NonNull NotificationGroupManager groupManager,
             @NonNull StatusBar bar,
             @NonNull VisualStabilityManager visualStabilityManager) {
-        mStatusBarWindowView = statusBarWindowView;
-        mStatusBarTouchableRegionManager = new StatusBarTouchableRegionManager(mContext, this, bar,
-                statusBarWindowView);
+        mNotificationShadeWindowView = notificationShadeWindowView;
+        mStatusBarTouchableRegionManager = new StatusBarTouchableRegionManager(this, bar,
+                notificationShadeWindowView);
         mGroupManager = groupManager;
         mVisualStabilityManager = visualStabilityManager;
 
@@ -364,7 +363,7 @@
         // updated yet, but callbacks leading out of the headsUp manager, querying it. Let's
         // therefore also check if the topEntry is null.
         if (!hasPinnedHeadsUp() || topEntry == null) {
-            mTouchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight);
+            mTouchableRegion.set(0, 0, mNotificationShadeWindowView.getWidth(), mStatusBarHeight);
             updateRegionForNotch(mTouchableRegion);
 
         } else {
@@ -386,7 +385,7 @@
     }
 
     private void updateRegionForNotch(Region region) {
-        WindowInsets windowInsets = mStatusBarWindowView.getRootWindowInsets();
+        WindowInsets windowInsets = mNotificationShadeWindowView.getRootWindowInsets();
         if (windowInsets == null) {
             Log.w(TAG, "StatusBarWindowView is not attached.");
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
index ac06d9d..c282cb8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -39,12 +39,12 @@
     private boolean mTouchingHeadsUpView;
     private boolean mTrackingHeadsUp;
     private boolean mCollapseSnoozes;
-    private NotificationPanelView mPanel;
+    private NotificationPanelViewController mPanel;
     private ExpandableNotificationRow mPickedChild;
 
     public HeadsUpTouchHelper(HeadsUpManagerPhone headsUpManager,
             Callback callback,
-            NotificationPanelView notificationPanelView) {
+            NotificationPanelViewController notificationPanelView) {
         mHeadsUpManager = headsUpManager;
         mCallback = callback;
         mPanel = notificationPanelView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index d95d2b7..d3e44ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -155,7 +155,6 @@
     };
 
     private boolean mLeftIsVoiceAssist;
-    private AssistManager mAssistManager;
     private Drawable mLeftAssistIcon;
 
     private IntentButton mRightButton = new DefaultRightButton();
@@ -254,7 +253,6 @@
         mActivityStarter = Dependency.get(ActivityStarter.class);
         mFlashlightController = Dependency.get(FlashlightController.class);
         mAccessibilityController = Dependency.get(AccessibilityController.class);
-        mAssistManager = Dependency.get(AssistManager.class);
         mActivityIntentHelper = new ActivityIntentHelper(getContext());
         updateLeftAffordance();
     }
@@ -551,7 +549,7 @@
         Runnable runnable = new Runnable() {
             @Override
             public void run() {
-                mAssistManager.launchVoiceAssistFromKeyguard();
+                Dependency.get(AssistManager.class).launchVoiceAssistFromKeyguard();
             }
         };
         if (!mKeyguardStateController.canDismissLockScreen()) {
@@ -565,7 +563,7 @@
     }
 
     private boolean canLaunchVoiceAssist() {
-        return mAssistManager.canVoiceAssistBeLaunchedFromKeyguard();
+        return Dependency.get(AssistManager.class).canVoiceAssistBeLaunchedFromKeyguard();
     }
 
     private void launchPhone() {
@@ -647,7 +645,7 @@
         }
         if (mLeftIsVoiceAssist) {
             mLeftPreview = mPreviewInflater.inflatePreviewFromService(
-                    mAssistManager.getVoiceInteractorComponentName());
+                    Dependency.get(AssistManager.class).getVoiceInteractorComponentName());
         } else {
             mLeftPreview = mPreviewInflater.inflatePreview(mLeftButton.getIntent());
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index f34c15c..3074e33 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -27,7 +27,6 @@
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.Slog;
-import android.util.StatsLog;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -47,6 +46,7 @@
 import com.android.systemui.R;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.io.PrintWriter;
@@ -248,8 +248,8 @@
                 mKeyguardView.onResume();
                 mKeyguardView.resetSecurityContainer();
             }
-            StatsLog.write(StatsLog.KEYGUARD_BOUNCER_STATE_CHANGED,
-                StatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN);
+            SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED,
+                    SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN);
         }
     };
 
@@ -290,8 +290,8 @@
 
     public void hide(boolean destroyView) {
         if (isShowing()) {
-            StatsLog.write(StatsLog.KEYGUARD_BOUNCER_STATE_CHANGED,
-                StatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN);
+            SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED,
+                    SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN);
             mDismissCallbackRegistry.notifyDismissCancelled();
         }
         mIsScrimmed = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 4e91e4c..a3f14ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -90,7 +90,7 @@
     private int mContainerTopPadding;
 
     /**
-     * @see NotificationPanelView#getExpandedFraction()
+     * @see NotificationPanelViewController#getExpandedFraction()
      */
     private float mPanelExpansion;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index d4cf272..3e3ef0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -27,6 +27,7 @@
 import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_BARS;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
 
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_FORCE_OPAQUE;
 import static com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
@@ -64,6 +65,7 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.telecom.TelecomManager;
 import android.text.TextUtils;
@@ -157,7 +159,6 @@
     private int mNavigationIconHints = 0;
     private @TransitionMode int mNavigationBarMode;
     private AccessibilityManager mAccessibilityManager;
-    private MagnificationContentObserver mMagnificationObserver;
     private ContentResolver mContentResolver;
     private boolean mAssistantAvailable;
 
@@ -176,6 +177,8 @@
     private Locale mLocale;
     private int mLayoutDirection;
 
+    private boolean mForceNavBarHandleOpaque;
+
     /** @see android.view.WindowInsetsController#setSystemBarsAppearance(int) */
     private @Appearance int mAppearance;
 
@@ -228,14 +231,17 @@
         @Override
         public void onNavBarButtonAlphaChanged(float alpha, boolean animate) {
             ButtonDispatcher buttonDispatcher = null;
+            boolean forceVisible = false;
             if (QuickStepContract.isSwipeUpMode(mNavBarMode)) {
                 buttonDispatcher = mNavigationBarView.getBackButton();
             } else if (QuickStepContract.isGesturalMode(mNavBarMode)) {
+                forceVisible = mForceNavBarHandleOpaque;
                 buttonDispatcher = mNavigationBarView.getHomeHandle();
             }
             if (buttonDispatcher != null) {
-                buttonDispatcher.setVisibility(alpha > 0 ? View.VISIBLE : View.INVISIBLE);
-                buttonDispatcher.setAlpha(alpha, animate);
+                buttonDispatcher.setVisibility(
+                        (forceVisible || alpha > 0) ? View.VISIBLE : View.INVISIBLE);
+                buttonDispatcher.setAlpha(forceVisible ? 1f : alpha, animate);
             }
         }
     };
@@ -292,6 +298,21 @@
         mDivider = divider;
         mRecentsOptional = recentsOptional;
         mHandler = mainHandler;
+
+        mForceNavBarHandleOpaque = DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_SYSTEMUI,
+                NAV_BAR_HANDLE_FORCE_OPAQUE,
+                /* defaultValue = */ false);
+        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post,
+                new DeviceConfig.OnPropertiesChangedListener() {
+                    @Override
+                    public void onPropertiesChanged(DeviceConfig.Properties properties) {
+                        if (properties.getKeyset().contains(NAV_BAR_HANDLE_FORCE_OPAQUE)) {
+                            mForceNavBarHandleOpaque = properties.getBoolean(
+                                    NAV_BAR_HANDLE_FORCE_OPAQUE, /* defaultValue = */ false);
+                        }
+                    }
+                });
     }
 
     // ----- Fragment Lifecycle Callbacks -----
@@ -303,11 +324,6 @@
         mWindowManager = getContext().getSystemService(WindowManager.class);
         mAccessibilityManager = getContext().getSystemService(AccessibilityManager.class);
         mContentResolver = getContext().getContentResolver();
-        mMagnificationObserver = new MagnificationContentObserver(
-                getContext().getMainThreadHandler());
-        mContentResolver.registerContentObserver(Settings.Secure.getUriFor(
-                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED), false,
-                mMagnificationObserver, UserHandle.USER_ALL);
         mContentResolver.registerContentObserver(
                 Settings.Secure.getUriFor(Settings.Secure.ASSISTANT),
                 false /* notifyForDescendants */, mAssistContentObserver, UserHandle.USER_ALL);
@@ -329,7 +345,6 @@
         super.onDestroy();
         mNavigationModeController.removeListener(this);
         mAccessibilityManagerWrapper.removeCallback(mAccessibilityListener);
-        mContentResolver.unregisterContentObserver(mMagnificationObserver);
         mContentResolver.unregisterContentObserver(mAssistContentObserver);
     }
 
@@ -350,7 +365,7 @@
             mIsOnDefaultDisplay = mDisplayId == Display.DEFAULT_DISPLAY;
         }
 
-        mNavigationBarView.setComponents(mStatusBarLazy.get().getPanel(), mAssistManager);
+        mNavigationBarView.setComponents(mStatusBarLazy.get().getPanelController());
         mNavigationBarView.setDisabledFlags(mDisabledFlags1);
         mNavigationBarView.setOnVerticalChangedListener(this::onVerticalChanged);
         mNavigationBarView.setOnTouchListener(this::onNavigationTouch);
@@ -366,8 +381,8 @@
         IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
         filter.addAction(Intent.ACTION_SCREEN_ON);
         filter.addAction(Intent.ACTION_USER_SWITCHED);
-        mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, Handler.getMain(),
-                UserHandle.ALL);
+        mBroadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter,
+                Handler.getMain(), UserHandle.ALL);
         notifyNavigationBarScreenOn();
 
         mOverviewProxyService.addCallback(mOverviewProxyListener);
@@ -969,28 +984,18 @@
      * @param outFeedbackEnabled if non-null, sets it to true if accessibility feedback is enabled.
      */
     public int getA11yButtonState(@Nullable boolean[] outFeedbackEnabled) {
-        int requestingServices = 0;
-        try {
-            if (Settings.Secure.getIntForUser(mContentResolver,
-                    Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
-                    UserHandle.USER_CURRENT) == 1) {
-                requestingServices++;
-            }
-        } catch (Settings.SettingNotFoundException e) {
-        }
-
         boolean feedbackEnabled = false;
         // AccessibilityManagerService resolves services for the current user since the local
         // AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS permission
         final List<AccessibilityServiceInfo> services =
                 mAccessibilityManager.getEnabledAccessibilityServiceList(
                         AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+        final List<String> a11yButtonTargets =
+                mAccessibilityManager.getAccessibilityShortcutTargets(
+                        AccessibilityManager.ACCESSIBILITY_BUTTON);
+        final int requestingServices = a11yButtonTargets.size();
         for (int i = services.size() - 1; i >= 0; --i) {
             AccessibilityServiceInfo info = services.get(i);
-            if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) {
-                requestingServices++;
-            }
-
             if (info.feedbackType != 0 && info.feedbackType !=
                     AccessibilityServiceInfo.FEEDBACK_GENERIC) {
                 feedbackEnabled = true;
@@ -1114,18 +1119,6 @@
     private final AccessibilityServicesStateChangeListener mAccessibilityListener =
             this::updateAccessibilityServicesState;
 
-    private class MagnificationContentObserver extends ContentObserver {
-
-        public MagnificationContentObserver(Handler handler) {
-            super(handler);
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            NavigationBarFragment.this.updateAccessibilityServicesState(mAccessibilityManager);
-        }
-    }
-
     private final Consumer<Integer> mRotationWatcher = rotation -> {
         if (mNavigationBarView != null
                 && mNavigationBarView.needsReorient(rotation)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 5a1b20d..ba9ba6c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -67,7 +67,6 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.assist.AssistHandleViewController;
-import com.android.systemui.assist.AssistManager;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.recents.Recents;
@@ -148,7 +147,7 @@
 
     private NavigationBarInflaterView mNavigationInflaterView;
     private RecentsOnboarding mRecentsOnboarding;
-    private NotificationPanelView mPanelView;
+    private NotificationPanelViewController mPanelView;
     private FloatingRotationButton mFloatingRotationButton;
     private RotationButtonController mRotationButtonController;
 
@@ -349,7 +348,7 @@
         return mBarTransitions.getLightTransitionsController();
     }
 
-    public void setComponents(NotificationPanelView panel, AssistManager assistManager) {
+    public void setComponents(NotificationPanelViewController panel) {
         mPanelView = panel;
         updatePanelSystemUiStateFlags();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
index fe0739f..896b6e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
@@ -32,7 +32,6 @@
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater.AsyncInflationTask;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.phone.NotificationGroupManager.NotificationGroup;
 import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener;
@@ -428,7 +427,7 @@
          * The notification is still pending inflation but we've decided that we no longer need
          * the content view (e.g. suppression might have changed and we decided we need to transfer
          * back). However, there is no way to abort just this inflation if other inflation requests
-         * have started (see {@link AsyncInflationTask#supersedeTask(InflationTask)}). So instead
+         * have started (see {@link InflationTask#supersedeTask(InflationTask)}). So instead
          * we just flag it as aborted and free when it's inflated.
          */
         boolean mAbortOnInflation;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index e11fc1b..8c947ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -116,7 +116,13 @@
      */
     private void onEntryRemovedInternal(NotificationEntry removed,
             final StatusBarNotification sbn) {
-        String groupKey = getGroupKey(sbn);
+        onEntryRemovedInternal(removed, sbn.getGroupKey(), sbn.isGroup(),
+                sbn.getNotification().isGroupSummary());
+    }
+
+    private void onEntryRemovedInternal(NotificationEntry removed, String notifGroupKey, boolean
+            isGroup, boolean isGroupSummary) {
+        String groupKey = getGroupKey(removed.getKey(), notifGroupKey);
         final NotificationGroup group = mGroupMap.get(groupKey);
         if (group == null) {
             // When an app posts 2 different notifications as summary of the same group, then a
@@ -125,7 +131,7 @@
             // the close future. See b/23676310 for reference.
             return;
         }
-        if (isGroupChild(sbn)) {
+        if (isGroupChild(removed.getKey(), isGroup, isGroupSummary)) {
             group.children.remove(removed.getKey());
         } else {
             group.summary = null;
@@ -229,7 +235,7 @@
     private int getNumberOfIsolatedChildren(String groupKey) {
         int count = 0;
         for (StatusBarNotification sbn : mIsolatedEntries.values()) {
-            if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn)) {
+            if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn.getKey())) {
                 count++;
             }
         }
@@ -238,31 +244,47 @@
 
     private NotificationEntry getIsolatedChild(String groupKey) {
         for (StatusBarNotification sbn : mIsolatedEntries.values()) {
-            if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn)) {
+            if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn.getKey())) {
                 return mGroupMap.get(sbn.getKey()).summary;
             }
         }
         return null;
     }
 
-    public void onEntryUpdated(NotificationEntry entry,
-            StatusBarNotification oldNotification) {
-        String oldKey = oldNotification.getGroupKey();
-        String newKey = entry.getSbn().getGroupKey();
-        boolean groupKeysChanged = !oldKey.equals(newKey);
-        boolean wasGroupChild = isGroupChild(oldNotification);
+    /**
+     * Update an entry's group information
+     * @param entry notification entry to update
+     * @param oldNotification previous notification info before this update
+     */
+    public void onEntryUpdated(NotificationEntry entry, StatusBarNotification oldNotification) {
+        onEntryUpdated(entry, oldNotification.getGroupKey(), oldNotification.isGroup(),
+                oldNotification.getNotification().isGroupSummary());
+    }
+
+    /**
+     * Updates an entry's group information
+     * @param entry notification entry to update
+     * @param oldGroupKey the notification's previous group key before this update
+     * @param oldIsGroup whether this notification was a group before this update
+     * @param oldIsGroupSummary whether this notification was a group summary before this update
+     */
+    public void onEntryUpdated(NotificationEntry entry, String oldGroupKey, boolean oldIsGroup,
+            boolean oldIsGroupSummary) {
+        String newGroupKey = entry.getSbn().getGroupKey();
+        boolean groupKeysChanged = !oldGroupKey.equals(newGroupKey);
+        boolean wasGroupChild = isGroupChild(entry.getKey(), oldIsGroup, oldIsGroupSummary);
         boolean isGroupChild = isGroupChild(entry.getSbn());
         mIsUpdatingUnchangedGroup = !groupKeysChanged && wasGroupChild == isGroupChild;
-        if (mGroupMap.get(getGroupKey(oldNotification)) != null) {
-            onEntryRemovedInternal(entry, oldNotification);
+        if (mGroupMap.get(getGroupKey(entry.getKey(), oldGroupKey)) != null) {
+            onEntryRemovedInternal(entry, oldGroupKey, oldIsGroup, oldIsGroupSummary);
         }
         onEntryAdded(entry);
         mIsUpdatingUnchangedGroup = false;
-        if (isIsolated(entry.getSbn())) {
+        if (isIsolated(entry.getSbn().getKey())) {
             mIsolatedEntries.put(entry.getKey(), entry.getSbn());
             if (groupKeysChanged) {
-                updateSuppression(mGroupMap.get(oldKey));
-                updateSuppression(mGroupMap.get(newKey));
+                updateSuppression(mGroupMap.get(oldGroupKey));
+                updateSuppression(mGroupMap.get(newGroupKey));
             }
         } else if (!wasGroupChild && isGroupChild) {
             onEntryBecomingChild(entry);
@@ -418,10 +440,14 @@
      * @return the key of the notification
      */
     public String getGroupKey(StatusBarNotification sbn) {
-        if (isIsolated(sbn)) {
-            return sbn.getKey();
+        return getGroupKey(sbn.getKey(), sbn.getGroupKey());
+    }
+
+    private String getGroupKey(String key, String groupKey) {
+        if (isIsolated(key)) {
+            return key;
         }
-        return sbn.getGroupKey();
+        return groupKey;
     }
 
     /** @return group expansion state after toggling. */
@@ -434,8 +460,8 @@
         return group.expanded;
     }
 
-    private boolean isIsolated(StatusBarNotification sbn) {
-        return mIsolatedEntries.containsKey(sbn.getKey());
+    private boolean isIsolated(String sbnKey) {
+        return mIsolatedEntries.containsKey(sbnKey);
     }
 
     /**
@@ -445,7 +471,7 @@
      * @return true if it is visually a group summary
      */
     public boolean isGroupSummary(StatusBarNotification sbn) {
-        if (isIsolated(sbn)) {
+        if (isIsolated(sbn.getKey())) {
             return true;
         }
         return sbn.getNotification().isGroupSummary();
@@ -458,10 +484,14 @@
      * @return true if it is visually a group child
      */
     public boolean isGroupChild(StatusBarNotification sbn) {
-        if (isIsolated(sbn)) {
+        return isGroupChild(sbn.getKey(), sbn.isGroup(), sbn.getNotification().isGroupSummary());
+    }
+
+    private boolean isGroupChild(String key, boolean isGroup, boolean isGroupSummary) {
+        if (isIsolated(key)) {
             return false;
         }
-        return sbn.isGroup() && !sbn.getNotification().isGroupSummary();
+        return isGroup && !isGroupSummary;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 3554b54..707138e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -128,7 +128,7 @@
             mAodIcons.setAnimationsEnabled(false);
             mAodIcons.removeAllViews();
         }
-        mAodIcons = mStatusBar.getStatusBarWindow().findViewById(
+        mAodIcons = mStatusBar.getNotificationShadeWindowView().findViewById(
                 R.id.clock_notification_icon_container);
         mAodIcons.setOnLockScreen(true);
         updateAodIconsVisibility(false /* animate */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index c74286d..0f3af09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -16,118 +16,15 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
-import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
-import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.app.ActivityManager;
-import android.app.Fragment;
-import android.app.StatusBarManager;
 import android.content.Context;
-import android.content.pm.ResolveInfo;
-import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
-import android.graphics.PointF;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.hardware.biometrics.BiometricSourceType;
-import android.os.PowerManager;
-import android.os.SystemClock;
-import android.provider.DeviceConfig;
-import android.provider.Settings;
 import android.util.AttributeSet;
-import android.util.Log;
-import android.util.MathUtils;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowInsets;
-import android.view.accessibility.AccessibilityManager;
-import android.widget.FrameLayout;
 
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.keyguard.KeyguardClockSwitch;
-import com.android.keyguard.KeyguardStatusView;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.systemui.DejankUtils;
-import com.android.systemui.Dependency;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.doze.DozeLog;
-import com.android.systemui.fragments.FragmentHostManager;
-import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.HomeControlsPlugin;
-import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.qs.QSFragment;
-import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.FlingAnimationUtils;
-import com.android.systemui.statusbar.GestureRecorder;
-import com.android.systemui.statusbar.KeyguardAffordanceView;
-import com.android.systemui.statusbar.KeyguardIndicationController;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationShelf;
-import com.android.systemui.statusbar.PulseExpansionHandler;
-import com.android.systemui.statusbar.RemoteInputController;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
-import com.android.systemui.statusbar.notification.AnimatableProperty;
-import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.PropertyAnimator;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.ExpandableView;
-import com.android.systemui.statusbar.notification.stack.AnimationProperties;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
-import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
-import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.util.InjectionInflationController;
-import com.android.systemui.util.Utils;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.function.Consumer;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-public class NotificationPanelView extends PanelView implements
-        ExpandableView.OnHeightChangedListener,
-        View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener,
-        KeyguardAffordanceHelper.Callback, NotificationStackScrollLayout.OnEmptySpaceClickListener,
-        OnHeadsUpChangedListener, QS.HeightListener, ZenModeController.Callback,
-        ConfigurationController.ConfigurationListener, StateListener,
-        PulseExpansionHandler.ExpansionCallback, DynamicPrivacyController.Listener,
-        NotificationWakeUpCoordinator.WakeUpListener {
+public class NotificationPanelView extends PanelView {
 
     private static final boolean DEBUG = false;
 
@@ -136,2873 +33,34 @@
      */
     public static final int FLING_EXPAND = 0;
 
-    /**
-     * Fling collapsing QS, potentially stopping when QS becomes QQS.
-     */
-    public static final int FLING_COLLAPSE = 1;
-
-    /**
-     * Fling until QS is completely hidden.
-     */
-    public static final int FLING_HIDE = 2;
-    private final DozeParameters mDozeParameters;
-
-    private double mQqsSplitFraction;
-
-    // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is
-    // changed.
-    private static final int CAP_HEIGHT = 1456;
-    private static final int FONT_HEIGHT = 2163;
-
-    /**
-     * Maximum time before which we will expand the panel even for slow motions when getting a
-     * touch passed over from launcher.
-     */
-    private static final int MAX_TIME_TO_OPEN_WHEN_FLINGING_FROM_LAUNCHER = 300;
-
     static final String COUNTER_PANEL_OPEN = "panel_open";
     static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs";
-    private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek";
 
-    private static final Rect mDummyDirtyRect = new Rect(0, 0, 1, 1);
-    private static final Rect mEmptyRect = new Rect();
-
-    private static final AnimationProperties CLOCK_ANIMATION_PROPERTIES = new AnimationProperties()
-            .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-    private static final AnimatableProperty KEYGUARD_HEADS_UP_SHOWING_AMOUNT
-            = AnimatableProperty.from("KEYGUARD_HEADS_UP_SHOWING_AMOUNT",
-            NotificationPanelView::setKeyguardHeadsUpShowingAmount,
-            NotificationPanelView::getKeyguardHeadsUpShowingAmount,
-            R.id.keyguard_hun_animator_tag,
-            R.id.keyguard_hun_animator_end_tag,
-            R.id.keyguard_hun_animator_start_tag);
-    private static final AnimationProperties KEYGUARD_HUN_PROPERTIES =
-            new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-    @VisibleForTesting
-    final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
-            new KeyguardUpdateMonitorCallback() {
-
-                @Override
-                public void onBiometricAuthenticated(int userId,
-                        BiometricSourceType biometricSourceType) {
-                    if (mFirstBypassAttempt && mUpdateMonitor.isUnlockingWithBiometricAllowed()) {
-                        mDelayShowingKeyguardStatusBar = true;
-                    }
-                }
-
-                @Override
-                public void onBiometricRunningStateChanged(boolean running,
-                        BiometricSourceType biometricSourceType) {
-                    boolean keyguardOrShadeLocked = mBarState == StatusBarState.KEYGUARD
-                            || mBarState == StatusBarState.SHADE_LOCKED;
-                    if (!running && mFirstBypassAttempt && keyguardOrShadeLocked && !mDozing
-                            && !mDelayShowingKeyguardStatusBar) {
-                        mFirstBypassAttempt = false;
-                        animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-                    }
-                }
-
-                @Override
-                public void onFinishedGoingToSleep(int why) {
-                    mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
-                    mDelayShowingKeyguardStatusBar = false;
-                }
-            };
-    private final KeyguardStateController.Callback mKeyguardMonitorCallback =
-            new KeyguardStateController.Callback() {
-                @Override
-                public void onKeyguardFadingAwayChanged() {
-                    if (!mKeyguardStateController.isKeyguardFadingAway()) {
-                        mFirstBypassAttempt = false;
-                        mDelayShowingKeyguardStatusBar = false;
-                    }
-                }
-            };
-
-    private final InjectionInflationController mInjectionInflationController;
-    private final PowerManager mPowerManager;
-    private final AccessibilityManager mAccessibilityManager;
-    private final NotificationWakeUpCoordinator mWakeUpCoordinator;
-    private final PulseExpansionHandler mPulseExpansionHandler;
-    private final KeyguardBypassController mKeyguardBypassController;
-    private final KeyguardUpdateMonitor mUpdateMonitor;
-
-    @VisibleForTesting
-    protected KeyguardAffordanceHelper mAffordanceHelper;
-    private KeyguardUserSwitcher mKeyguardUserSwitcher;
-    @VisibleForTesting
-    protected KeyguardStatusBarView mKeyguardStatusBar;
-    @VisibleForTesting
-    protected ViewGroup mBigClockContainer;
-    private QS mQs;
-    @VisibleForTesting
-    protected FrameLayout mQsFrame;
-    @VisibleForTesting
-    protected KeyguardStatusView mKeyguardStatusView;
-    private View mQsNavbarScrim;
-    protected NotificationsQuickSettingsContainer mNotificationContainerParent;
-    protected NotificationStackScrollLayout mNotificationStackScroller;
-    protected FrameLayout mHomeControlsLayout;
-    private boolean mAnimateNextPositionUpdate;
-
-    private int mTrackingPointer;
-    private VelocityTracker mQsVelocityTracker;
-    private boolean mQsTracking;
-
-    /**
-     * If set, the ongoing touch gesture might both trigger the expansion in {@link PanelView} and
-     * the expansion for quick settings.
-     */
-    private boolean mConflictingQsExpansionGesture;
-
-    /**
-     * Whether we are currently handling a motion gesture in #onInterceptTouchEvent, but haven't
-     * intercepted yet.
-     */
-    private boolean mIntercepting;
-    private boolean mPanelExpanded;
-    private boolean mQsExpanded;
-    private boolean mQsExpandedWhenExpandingStarted;
-    private boolean mQsFullyExpanded;
-    private boolean mKeyguardShowing;
-    private boolean mDozing;
-    private boolean mDozingOnDown;
-    protected int mBarState;
-    private float mInitialHeightOnTouch;
-    private float mInitialTouchX;
-    private float mInitialTouchY;
-    private float mLastTouchX;
-    private float mLastTouchY;
-    protected float mQsExpansionHeight;
-    protected int mQsMinExpansionHeight;
-    protected int mQsMaxExpansionHeight;
-    private int mQsPeekHeight;
-    private boolean mStackScrollerOverscrolling;
-    private boolean mQsExpansionFromOverscroll;
-    private float mLastOverscroll;
-    protected boolean mQsExpansionEnabled = true;
-    private ValueAnimator mQsExpansionAnimator;
-    private FlingAnimationUtils mFlingAnimationUtils;
-    private int mStatusBarMinHeight;
-    private int mNotificationsHeaderCollideDistance;
-    private int mUnlockMoveDistance;
-    private float mEmptyDragAmount;
-    private float mDownX;
-    private float mDownY;
-
-    private final KeyguardClockPositionAlgorithm mClockPositionAlgorithm =
-            new KeyguardClockPositionAlgorithm();
-    private final KeyguardClockPositionAlgorithm.Result mClockPositionResult =
-            new KeyguardClockPositionAlgorithm.Result();
-    private boolean mIsExpanding;
-
-    private boolean mBlockTouches;
-    // Used for two finger gesture as well as accessibility shortcut to QS.
-    private boolean mQsExpandImmediate;
-    private boolean mTwoFingerQsExpandPossible;
-
-    /**
-     * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still
-     * need to take this into account in our panel height calculation.
-     */
-    private boolean mQsAnimatorExpand;
-    private boolean mIsLaunchTransitionFinished;
-    private boolean mIsLaunchTransitionRunning;
-    private Runnable mLaunchAnimationEndRunnable;
-    private boolean mOnlyAffordanceInThisMotion;
-    private boolean mKeyguardStatusViewAnimating;
-    private ValueAnimator mQsSizeChangeAnimator;
-
-    private boolean mShowEmptyShadeView;
-
-    private boolean mQsScrimEnabled = true;
-    private boolean mLastAnnouncementWasQuickSettings;
-    private boolean mQsTouchAboveFalsingThreshold;
-    private int mQsFalsingThreshold;
-
-    private float mKeyguardStatusBarAnimateAlpha = 1f;
-    private int mOldLayoutDirection;
-    private HeadsUpTouchHelper mHeadsUpTouchHelper;
-    private boolean mIsExpansionFromHeadsUp;
-    private boolean mListenForHeadsUp;
-    private int mNavigationBarBottomHeight;
-    private boolean mExpandingFromHeadsUp;
-    private boolean mCollapsedOnDown;
-    private int mPositionMinSideMargin;
-    private int mMaxFadeoutHeight;
-    private int mLastOrientation = -1;
-    private boolean mClosingWithAlphaFadeOut;
-    private boolean mHeadsUpAnimatingAway;
-    private boolean mLaunchingAffordance;
-    private boolean mAffordanceHasPreview;
-    private FalsingManager mFalsingManager;
-    private String mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
-
-    private Runnable mHeadsUpExistenceChangedRunnable = new Runnable() {
-        @Override
-        public void run() {
-            setHeadsUpAnimatingAway(false);
-            notifyBarPanelExpansionChanged();
-        }
-    };
-    private NotificationGroupManager mGroupManager;
-    private boolean mShowIconsWhenExpanded;
-    private int mIndicationBottomPadding;
-    private int mAmbientIndicationBottomPadding;
-    private boolean mIsFullWidth;
-    private boolean mBlockingExpansionForCurrentTouch;
-
-    /**
-     * Following variables maintain state of events when input focus transfer may occur.
-     */
-    private boolean mExpectingSynthesizedDown; // expecting to see synthesized DOWN event
-    private boolean mLastEventSynthesizedDown; // last event was synthesized DOWN event
-
-    /**
-     * Current dark amount that follows regular interpolation curve of animation.
-     */
-    private float mInterpolatedDarkAmount;
-
-    /**
-     * Dark amount that animates from 0 to 1 or vice-versa in linear manner, even if the
-     * interpolation curve is different.
-     */
-    private float mLinearDarkAmount;
-
-    private boolean mPulsing;
-    private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
-    private boolean mNoVisibleNotifications = true;
-    private boolean mUserSetupComplete;
-    private int mQsNotificationTopPadding;
-    private float mExpandOffset;
-    private boolean mHideIconsDuringNotificationLaunch = true;
-    private int mStackScrollerMeasuringPass;
-    private ArrayList<Consumer<ExpandableNotificationRow>> mTrackingHeadsUpListeners
-            = new ArrayList<>();
-    private ArrayList<Runnable> mVerticalTranslationListener = new ArrayList<>();
-    private HeadsUpAppearanceController mHeadsUpAppearanceController;
-
-    private int mPanelAlpha;
     private int mCurrentPanelAlpha;
     private final Paint mAlphaPaint = new Paint();
-    private Runnable mPanelAlphaEndAction;
-    private float mBottomAreaShadeAlpha;
-    private final ValueAnimator mBottomAreaShadeAlphaAnimator;
-    private AnimatorListenerAdapter mAnimatorListenerAdapter = new AnimatorListenerAdapter() {
-        @Override
-        public void onAnimationEnd(Animator animation) {
-            if (mPanelAlphaEndAction != null) {
-                mPanelAlphaEndAction.run();
-            }
-        }
-    };
-    private final AnimatableProperty PANEL_ALPHA = AnimatableProperty.from(
-            "panelAlpha",
-            NotificationPanelView::setPanelAlphaInternal,
-            NotificationPanelView::getCurrentPanelAlpha,
-            R.id.panel_alpha_animator_tag,
-            R.id.panel_alpha_animator_start_tag,
-            R.id.panel_alpha_animator_end_tag);
-    private final AnimationProperties PANEL_ALPHA_OUT_PROPERTIES = new AnimationProperties()
-            .setDuration(150)
-            .setCustomInterpolator(PANEL_ALPHA.getProperty(), Interpolators.ALPHA_OUT);
-    private final AnimationProperties PANEL_ALPHA_IN_PROPERTIES = new AnimationProperties()
-            .setDuration(200)
-            .setAnimationFinishListener(mAnimatorListenerAdapter)
-            .setCustomInterpolator(PANEL_ALPHA.getProperty(), Interpolators.ALPHA_IN);
-    private final NotificationEntryManager mEntryManager;
+    private boolean mDozing;
+    private RtlChangeListener mRtlChangeListener;
 
-    private final CommandQueue mCommandQueue;
-    private final NotificationLockscreenUserManager mLockscreenUserManager;
-    private final ShadeController mShadeController;
-    private int mDisplayId;
-
-    /**
-     * Cache the resource id of the theme to avoid unnecessary work in onThemeChanged.
-     *
-     * onThemeChanged is forced when the theme might not have changed. So, to avoid unncessary
-     * work, check the current id with the cached id.
-     */
-    private int mThemeResId;
-    private KeyguardIndicationController mKeyguardIndicationController;
-    private Consumer<Boolean> mAffordanceLaunchListener;
-    private int mShelfHeight;
-    private Runnable mOnReinflationListener;
-    private int mDarkIconSize;
-    private int mHeadsUpInset;
-    private boolean mHeadsUpPinnedMode;
-    private float mKeyguardHeadsUpShowingAmount = 0.0f;
-    private boolean mShowingKeyguardHeadsUp;
-    private boolean mAllowExpandForSmallExpansion;
-    private Runnable mExpandAfterLayoutRunnable;
-
-    /**
-     * If face auth with bypass is running for the first time after you turn on the screen.
-     * (From aod or screen off)
-     */
-    private boolean mFirstBypassAttempt;
-    /**
-     * If auth happens successfully during {@code mFirstBypassAttempt}, and we should wait until
-     * the keyguard is dismissed to show the status bar.
-     */
-    private boolean mDelayShowingKeyguardStatusBar;
-
-    private PluginManager mPluginManager;
-    private FrameLayout mPluginFrame;
-    private NPVPluginManager mNPVPluginManager;
-
-    @Inject
-    public NotificationPanelView(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
-            InjectionInflationController injectionInflationController,
-            NotificationWakeUpCoordinator coordinator, PulseExpansionHandler pulseExpansionHandler,
-            DynamicPrivacyController dynamicPrivacyController,
-            KeyguardBypassController bypassController, FalsingManager falsingManager,
-            PluginManager pluginManager, ShadeController shadeController,
-            NotificationLockscreenUserManager notificationLockscreenUserManager,
-            NotificationEntryManager notificationEntryManager,
-            KeyguardStateController keyguardStateController,
-            StatusBarStateController statusBarStateController, DozeLog dozeLog,
-            DozeParameters dozeParameters, CommandQueue commandQueue) {
-        super(context, attrs, falsingManager, dozeLog, keyguardStateController,
-                (SysuiStatusBarStateController) statusBarStateController);
+    public NotificationPanelView(Context context, AttributeSet attrs) {
+        super(context, attrs);
         setWillNotDraw(!DEBUG);
-        mInjectionInflationController = injectionInflationController;
-        mFalsingManager = falsingManager;
-        mPowerManager = context.getSystemService(PowerManager.class);
-        mWakeUpCoordinator = coordinator;
-        mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
-        setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
         mAlphaPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
-        setPanelAlpha(255, false /* animate */);
-        mCommandQueue = commandQueue;
-        mDisplayId = context.getDisplayId();
-        mPulseExpansionHandler = pulseExpansionHandler;
-        mDozeParameters = dozeParameters;
-        pulseExpansionHandler.setPulseExpandAbortListener(() -> {
-            if (mQs != null) {
-                mQs.animateHeaderSlidingOut();
-            }
-        });
-        mThemeResId = context.getThemeResId();
-        mKeyguardBypassController = bypassController;
-        mUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
-        mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
-        mKeyguardStateController.addCallback(mKeyguardMonitorCallback);
-        dynamicPrivacyController.addListener(this);
-
-        mBottomAreaShadeAlphaAnimator = ValueAnimator.ofFloat(1f, 0);
-        mBottomAreaShadeAlphaAnimator.addUpdateListener(animation -> {
-            mBottomAreaShadeAlpha = (float) animation.getAnimatedValue();
-            updateKeyguardBottomAreaAlpha();
-        });
-        mBottomAreaShadeAlphaAnimator.setDuration(160);
-        mBottomAreaShadeAlphaAnimator.setInterpolator(Interpolators.ALPHA_OUT);
-        mPluginManager = pluginManager;
-        mShadeController = shadeController;
-        mLockscreenUserManager = notificationLockscreenUserManager;
-        mEntryManager = notificationEntryManager;
 
         setBackgroundColor(Color.TRANSPARENT);
     }
 
-    /**
-     * Returns if there's a custom clock being presented.
-     */
-    public boolean hasCustomClock() {
-        return mKeyguardStatusView.hasCustomClock();
-    }
-
-    private void setStatusBar(StatusBar bar) {
-        mStatusBar = bar;
-        mKeyguardBottomArea.setStatusBar(mStatusBar);
-    }
-
-    /**
-     * Call after this view has been fully inflated and had its children attached.
-     */
-    public void onChildrenAttached() {
-        loadDimens();
-        mKeyguardStatusBar = findViewById(R.id.keyguard_header);
-        mKeyguardStatusView = findViewById(R.id.keyguard_status_view);
-
-        KeyguardClockSwitch keyguardClockSwitch = findViewById(R.id.keyguard_clock_container);
-        mBigClockContainer = findViewById(R.id.big_clock_container);
-        keyguardClockSwitch.setBigClockContainer(mBigClockContainer);
-
-        mHomeControlsLayout = findViewById(R.id.home_controls_layout);
-        mNotificationContainerParent = findViewById(R.id.notification_container_parent);
-        mNotificationStackScroller = findViewById(R.id.notification_stack_scroller);
-        mNotificationStackScroller.setOnHeightChangedListener(this);
-        mNotificationStackScroller.setOverscrollTopChangedListener(this);
-        mNotificationStackScroller.setOnEmptySpaceClickListener(this);
-        addTrackingHeadsUpListener(mNotificationStackScroller::setTrackingHeadsUp);
-        mKeyguardBottomArea = findViewById(R.id.keyguard_bottom_area);
-        mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim);
-        mLastOrientation = getResources().getConfiguration().orientation;
-        mPluginFrame = findViewById(R.id.plugin_frame);
-        if (Settings.System.getInt(
-                mContext.getContentResolver(), "npv_plugin_flag", 0) == 1) {
-            mNPVPluginManager = new NPVPluginManager(mPluginFrame, mPluginManager);
-        }
-
-
-        initBottomArea();
-
-        mWakeUpCoordinator.setStackScroller(mNotificationStackScroller);
-        mQsFrame = findViewById(R.id.qs_frame);
-        mPulseExpansionHandler.setUp(mNotificationStackScroller, this, mShadeController);
-        mWakeUpCoordinator.addListener(new NotificationWakeUpCoordinator.WakeUpListener() {
-            @Override
-            public void onFullyHiddenChanged(boolean isFullyHidden) {
-                updateKeyguardStatusBarForHeadsUp();
-            }
-
-            @Override
-            public void onPulseExpansionChanged(boolean expandingChanged) {
-                if (mKeyguardBypassController.getBypassEnabled()) {
-                    // Position the notifications while dragging down while pulsing
-                    requestScrollerTopPaddingUpdate(false /* animate */);
-                    updateQSPulseExpansion();
-                }
-            }
-        });
-
-        mPluginManager.addPluginListener(
-                new PluginListener<HomeControlsPlugin>() {
-
-                    @Override
-                    public void onPluginConnected(HomeControlsPlugin plugin,
-                                                  Context pluginContext) {
-                        plugin.sendParentGroup(mHomeControlsLayout);
-                    }
-
-                    @Override
-                    public void onPluginDisconnected(HomeControlsPlugin plugin) {
-
-                    }
-                }, HomeControlsPlugin.class, false);
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        FragmentHostManager.get(this).addTagListener(QS.TAG, mFragmentListener);
-        Dependency.get(StatusBarStateController.class).addCallback(this);
-        Dependency.get(ZenModeController.class).addCallback(this);
-        Dependency.get(ConfigurationController.class).addCallback(this);
-        mUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
-        // Theme might have changed between inflating this view and attaching it to the window, so
-        // force a call to onThemeChanged
-        onThemeChanged();
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        FragmentHostManager.get(this).removeTagListener(QS.TAG, mFragmentListener);
-        Dependency.get(StatusBarStateController.class).removeCallback(this);
-        Dependency.get(ZenModeController.class).removeCallback(this);
-        Dependency.get(ConfigurationController.class).removeCallback(this);
-        mUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
-    }
-
-    @Override
-    protected void loadDimens() {
-        super.loadDimens();
-        mFlingAnimationUtils = new FlingAnimationUtils(getResources().getDisplayMetrics(), 0.4f);
-        mStatusBarMinHeight = getResources().getDimensionPixelSize(
-                com.android.internal.R.dimen.status_bar_height);
-        mQsPeekHeight = getResources().getDimensionPixelSize(R.dimen.qs_peek_height);
-        mNotificationsHeaderCollideDistance =
-                getResources().getDimensionPixelSize(R.dimen.header_notifications_collide_distance);
-        mUnlockMoveDistance = getResources().getDimensionPixelOffset(R.dimen.unlock_move_distance);
-        mClockPositionAlgorithm.loadDimens(getResources());
-        mQsFalsingThreshold = getResources().getDimensionPixelSize(
-                R.dimen.qs_falsing_threshold);
-        mPositionMinSideMargin = getResources().getDimensionPixelSize(
-                R.dimen.notification_panel_min_side_margin);
-        mMaxFadeoutHeight = getResources().getDimensionPixelSize(
-                R.dimen.max_notification_fadeout_height);
-        mIndicationBottomPadding = getResources().getDimensionPixelSize(
-                R.dimen.keyguard_indication_bottom_padding);
-        mQsNotificationTopPadding = getResources().getDimensionPixelSize(
-                R.dimen.qs_notification_padding);
-        mShelfHeight = getResources().getDimensionPixelSize(R.dimen.notification_shelf_height);
-        mDarkIconSize = getResources().getDimensionPixelSize(
-                R.dimen.status_bar_icon_drawing_size_dark);
-        int statusbarHeight = getResources().getDimensionPixelSize(
-                com.android.internal.R.dimen.status_bar_height);
-        mHeadsUpInset = statusbarHeight + getResources().getDimensionPixelSize(
-                R.dimen.heads_up_status_bar_padding);
-        mQqsSplitFraction = ((float) getResources().getInteger(R.integer.qqs_split_fraction)) / (
-                getResources().getInteger(R.integer.qqs_split_fraction)
-                        + getResources().getInteger(R.integer.qs_split_fraction));
-    }
-
-    /**
-     * @see #launchCamera(boolean, int)
-     * @see #setLaunchingAffordance(boolean)
-     */
-    public void setLaunchAffordanceListener(Consumer<Boolean> listener) {
-        mAffordanceLaunchListener = listener;
-    }
-
-    public void updateResources() {
-        Resources res = getResources();
-        int qsWidth = res.getDimensionPixelSize(R.dimen.qs_panel_width);
-        int panelGravity = getResources().getInteger(R.integer.notification_panel_layout_gravity);
-        FrameLayout.LayoutParams lp =
-                (FrameLayout.LayoutParams) mQsFrame.getLayoutParams();
-        if (lp.width != qsWidth || lp.gravity != panelGravity) {
-            lp.width = qsWidth;
-            lp.gravity = panelGravity;
-            mQsFrame.setLayoutParams(lp);
-        }
-
-        int panelWidth = res.getDimensionPixelSize(R.dimen.notification_panel_width);
-        lp = (FrameLayout.LayoutParams) mNotificationStackScroller.getLayoutParams();
-        if (lp.width != panelWidth || lp.gravity != panelGravity) {
-            lp.width = panelWidth;
-            lp.gravity = panelGravity;
-            mNotificationStackScroller.setLayoutParams(lp);
-        }
-        int sideMargin = res.getDimensionPixelOffset(R.dimen.notification_side_paddings);
-        int topMargin = sideMargin;
-        lp = (FrameLayout.LayoutParams) mPluginFrame.getLayoutParams();
-        if (lp.width != qsWidth || lp.gravity != panelGravity || lp.leftMargin != sideMargin
-                || lp.rightMargin != sideMargin || lp.topMargin != topMargin) {
-            lp.width = qsWidth;
-            lp.gravity = panelGravity;
-            lp.leftMargin = sideMargin;
-            lp.rightMargin = sideMargin;
-            lp.topMargin = topMargin;
-            mPluginFrame.setLayoutParams(lp);
-        }
-    }
-
-    @Override
-    public void onDensityOrFontScaleChanged() {
-        updateShowEmptyShadeView();
-    }
-
-    @Override
-    public void onThemeChanged() {
-        final int themeResId = getContext().getThemeResId();
-        if (mThemeResId == themeResId) {
-            return;
-        }
-        mThemeResId = themeResId;
-
-        reInflateViews();
-    }
-
-    @Override
-    public void onOverlayChanged() {
-        reInflateViews();
-    }
-
-    private void reInflateViews() {
-        updateShowEmptyShadeView();
-
-        // Re-inflate the status view group.
-        int index = indexOfChild(mKeyguardStatusView);
-        removeView(mKeyguardStatusView);
-        mKeyguardStatusView = (KeyguardStatusView) mInjectionInflationController
-                .injectable(LayoutInflater.from(mContext)).inflate(
-                        R.layout.keyguard_status_view,
-                        this,
-                        false);
-        addView(mKeyguardStatusView, index);
-
-        // Re-associate the clock container with the keyguard clock switch.
-        mBigClockContainer.removeAllViews();
-        KeyguardClockSwitch keyguardClockSwitch = findViewById(R.id.keyguard_clock_container);
-        keyguardClockSwitch.setBigClockContainer(mBigClockContainer);
-
-        // Update keyguard bottom area
-        index = indexOfChild(mKeyguardBottomArea);
-        removeView(mKeyguardBottomArea);
-        KeyguardBottomAreaView oldBottomArea = mKeyguardBottomArea;
-        mKeyguardBottomArea = (KeyguardBottomAreaView) mInjectionInflationController
-                .injectable(LayoutInflater.from(mContext)).inflate(
-                        R.layout.keyguard_bottom_area,
-                        this,
-                        false);
-        mKeyguardBottomArea.initFrom(oldBottomArea);
-        addView(mKeyguardBottomArea, index);
-        initBottomArea();
-        mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
-        onDozeAmountChanged(mStatusBarStateController.getDozeAmount(),
-                mStatusBarStateController.getInterpolatedDozeAmount());
-
-        if (mKeyguardStatusBar != null) {
-            mKeyguardStatusBar.onThemeChanged();
-        }
-
-        setKeyguardStatusViewVisibility(mBarState, false, false);
-        setKeyguardBottomAreaVisibility(mBarState, false);
-        if (mOnReinflationListener != null) {
-            mOnReinflationListener.run();
-        }
-        reinflatePluginContainer();
-    }
-
-    @Override
-    public void onUiModeChanged() {
-        reinflatePluginContainer();
-    }
-
-    private void reinflatePluginContainer() {
-        int index = indexOfChild(mPluginFrame);
-        removeView(mPluginFrame);
-        mPluginFrame = (FrameLayout) mInjectionInflationController
-                .injectable(LayoutInflater.from(mContext)).inflate(
-                        R.layout.status_bar_expanded_plugin_frame,
-                        this,
-                        false);
-        addView(mPluginFrame, index);
-
-        Resources res = getResources();
-        int qsWidth = res.getDimensionPixelSize(R.dimen.qs_panel_width);
-        int panelGravity = getResources().getInteger(R.integer.notification_panel_layout_gravity);
-        FrameLayout.LayoutParams lp;
-        int sideMargin = res.getDimensionPixelOffset(R.dimen.notification_side_paddings);
-        int topMargin =
-                res.getDimensionPixelOffset(com.android.internal.R.dimen.quick_qs_total_height);
-        if (Utils.useQsMediaPlayer(mContext)) {
-            topMargin = res.getDimensionPixelOffset(
-                    com.android.internal.R.dimen.quick_qs_total_height_with_media);
-        }
-        lp = (FrameLayout.LayoutParams) mPluginFrame.getLayoutParams();
-        if (lp.width != qsWidth || lp.gravity != panelGravity || lp.leftMargin != sideMargin
-                || lp.rightMargin != sideMargin || lp.topMargin != topMargin) {
-            lp.width = qsWidth;
-            lp.gravity = panelGravity;
-            lp.leftMargin = sideMargin;
-            lp.rightMargin = sideMargin;
-            lp.topMargin = topMargin;
-            mPluginFrame.setLayoutParams(lp);
-        }
-
-        if (mNPVPluginManager != null) mNPVPluginManager.replaceFrameLayout(mPluginFrame);
-    }
-
-    private void initBottomArea() {
-        mAffordanceHelper = new KeyguardAffordanceHelper(this, getContext(), mFalsingManager);
-        mKeyguardBottomArea.setAffordanceHelper(mAffordanceHelper);
-        mKeyguardBottomArea.setStatusBar(mStatusBar);
-        mKeyguardBottomArea.setUserSetupComplete(mUserSetupComplete);
-    }
-
-    public void setKeyguardIndicationController(KeyguardIndicationController indicationController) {
-        mKeyguardIndicationController = indicationController;
-        mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        DejankUtils.startDetectingBlockingIpcs("NVP#onLayout");
-        super.onLayout(changed, left, top, right, bottom);
-        setIsFullWidth(mNotificationStackScroller.getWidth() == getWidth());
-
-        // Update Clock Pivot
-        mKeyguardStatusView.setPivotX(getWidth() / 2);
-        mKeyguardStatusView.setPivotY((FONT_HEIGHT - CAP_HEIGHT) / 2048f *
-                mKeyguardStatusView.getClockTextSize());
-
-        // Calculate quick setting heights.
-        int oldMaxHeight = mQsMaxExpansionHeight;
-        if (mQs != null) {
-            mQsMinExpansionHeight = mKeyguardShowing ? 0 : mQs.getQsMinExpansionHeight();
-            if (mNPVPluginManager != null) {
-                mNPVPluginManager.setYOffset(mQsMinExpansionHeight);
-                mQsMinExpansionHeight += mNPVPluginManager.getHeight();
-            }
-            mQsMaxExpansionHeight = mQs.getDesiredHeight();
-            mNotificationStackScroller.setMaxTopPadding(
-                    mQsMaxExpansionHeight + mQsNotificationTopPadding);
-        }
-        positionClockAndNotifications();
-        if (mQsExpanded && mQsFullyExpanded) {
-            mQsExpansionHeight = mQsMaxExpansionHeight;
-            requestScrollerTopPaddingUpdate(false /* animate */);
-            requestPanelHeightUpdate();
-
-            // Size has changed, start an animation.
-            if (mQsMaxExpansionHeight != oldMaxHeight) {
-                startQsSizeChangeAnimation(oldMaxHeight, mQsMaxExpansionHeight);
-            }
-        } else if (!mQsExpanded) {
-            setQsExpansion(mQsMinExpansionHeight + mLastOverscroll);
-        }
-        updateExpandedHeight(getExpandedHeight());
-        updateHeader();
-
-        // If we are running a size change animation, the animation takes care of the height of
-        // the container. However, if we are not animating, we always need to make the QS container
-        // the desired height so when closing the QS detail, it stays smaller after the size change
-        // animation is finished but the detail view is still being animated away (this animation
-        // takes longer than the size change animation).
-        if (mQsSizeChangeAnimator == null && mQs != null) {
-            mQs.setHeightOverride(mQs.getDesiredHeight());
-        }
-        updateMaxHeadsUpTranslation();
-        updateGestureExclusionRect();
-        if (mExpandAfterLayoutRunnable != null) {
-            mExpandAfterLayoutRunnable.run();
-            mExpandAfterLayoutRunnable = null;
-        }
-        DejankUtils.stopDetectingBlockingIpcs("NVP#onLayout");
-    }
-
-    private void updateGestureExclusionRect() {
-        Rect exclusionRect = calculateGestureExclusionRect();
-        setSystemGestureExclusionRects(exclusionRect.isEmpty()
-                ? Collections.EMPTY_LIST
-                : Collections.singletonList(exclusionRect));
-    }
-
-    private Rect calculateGestureExclusionRect() {
-        Rect exclusionRect = null;
-        Region touchableRegion = mHeadsUpManager.calculateTouchableRegion();
-        if (isFullyCollapsed() && touchableRegion != null) {
-            // Note: The heads up manager also calculates the non-pinned touchable region
-            exclusionRect = touchableRegion.getBounds();
-        }
-        return exclusionRect != null
-                ? exclusionRect
-                : mEmptyRect;
-    }
-
-    private void setIsFullWidth(boolean isFullWidth) {
-        mIsFullWidth = isFullWidth;
-        mNotificationStackScroller.setIsFullWidth(isFullWidth);
-    }
-
-    private void startQsSizeChangeAnimation(int oldHeight, final int newHeight) {
-        if (mQsSizeChangeAnimator != null) {
-            oldHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
-            mQsSizeChangeAnimator.cancel();
-        }
-        mQsSizeChangeAnimator = ValueAnimator.ofInt(oldHeight, newHeight);
-        mQsSizeChangeAnimator.setDuration(300);
-        mQsSizeChangeAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
-        mQsSizeChangeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                requestScrollerTopPaddingUpdate(false /* animate */);
-                requestPanelHeightUpdate();
-                int height = (int) mQsSizeChangeAnimator.getAnimatedValue();
-                mQs.setHeightOverride(height);
-            }
-        });
-        mQsSizeChangeAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mQsSizeChangeAnimator = null;
-            }
-        });
-        mQsSizeChangeAnimator.start();
-    }
-
-    /**
-     * Positions the clock and notifications dynamically depending on how many notifications are
-     * showing.
-     */
-    private void positionClockAndNotifications() {
-        boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending();
-        boolean animateClock = animate || mAnimateNextPositionUpdate;
-        int stackScrollerPadding;
-        if (mBarState != StatusBarState.KEYGUARD) {
-            stackScrollerPadding = getUnlockedStackScrollerPadding();
-        } else {
-            int totalHeight = getHeight();
-            int bottomPadding = Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding);
-            int clockPreferredY = mKeyguardStatusView.getClockPreferredY(totalHeight);
-            boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled();
-            final boolean hasVisibleNotifications =
-                    !bypassEnabled && mNotificationStackScroller.getVisibleNotificationCount() != 0;
-            mKeyguardStatusView.setHasVisibleNotifications(hasVisibleNotifications);
-            mClockPositionAlgorithm.setup(
-                    mStatusBarMinHeight,
-                    totalHeight - bottomPadding,
-                    mNotificationStackScroller.getIntrinsicContentHeight(),
-                    getExpandedFraction(),
-                    totalHeight,
-                    (int) (mKeyguardStatusView.getHeight()
-                            - mShelfHeight / 2.0f - mDarkIconSize / 2.0f),
-                    clockPreferredY,
-                    hasCustomClock(),
-                    hasVisibleNotifications,
-                    mInterpolatedDarkAmount,
-                    mEmptyDragAmount,
-                    bypassEnabled,
-                    getUnlockedStackScrollerPadding());
-            mClockPositionAlgorithm.run(mClockPositionResult);
-            PropertyAnimator.setProperty(mKeyguardStatusView, AnimatableProperty.X,
-                    mClockPositionResult.clockX, CLOCK_ANIMATION_PROPERTIES, animateClock);
-            PropertyAnimator.setProperty(mKeyguardStatusView, AnimatableProperty.Y,
-                    mClockPositionResult.clockY, CLOCK_ANIMATION_PROPERTIES, animateClock);
-            updateNotificationTranslucency();
-            updateClock();
-            stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded;
-        }
-        mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding);
-        mKeyguardBottomArea.setAntiBurnInOffsetX(mClockPositionResult.clockX);
-
-        mStackScrollerMeasuringPass++;
-        requestScrollerTopPaddingUpdate(animate);
-        mStackScrollerMeasuringPass = 0;
-        mAnimateNextPositionUpdate = false;
-    }
-
-    /**
-     * @return the padding of the stackscroller when unlocked
-     */
-    private int getUnlockedStackScrollerPadding() {
-        return (mQs != null ? mQs.getHeader().getHeight() : 0) + mQsPeekHeight
-                + mQsNotificationTopPadding;
-    }
-
-    /**
-     * @param maximum the maximum to return at most
-     * @return the maximum keyguard notifications that can fit on the screen
-     */
-    public int computeMaxKeyguardNotifications(int maximum) {
-        float minPadding = mClockPositionAlgorithm.getMinStackScrollerPadding();
-        int notificationPadding = Math.max(1, getResources().getDimensionPixelSize(
-                R.dimen.notification_divider_height));
-        NotificationShelf shelf = mNotificationStackScroller.getNotificationShelf();
-        float shelfSize = shelf.getVisibility() == GONE ? 0
-                : shelf.getIntrinsicHeight() + notificationPadding;
-        float availableSpace = mNotificationStackScroller.getHeight() - minPadding - shelfSize
-                - Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding)
-                - mKeyguardStatusView.getLogoutButtonHeight();
-        int count = 0;
-        for (int i = 0; i < mNotificationStackScroller.getChildCount(); i++) {
-            ExpandableView child = (ExpandableView) mNotificationStackScroller.getChildAt(i);
-            if (!(child instanceof ExpandableNotificationRow)) {
-                continue;
-            }
-            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-            boolean suppressedSummary = mGroupManager != null
-                    && mGroupManager.isSummaryOfSuppressedGroup(row.getEntry().getSbn());
-            if (suppressedSummary) {
-                continue;
-            }
-            if (!mLockscreenUserManager.shouldShowOnKeyguard(row.getEntry())) {
-                continue;
-            }
-            if (row.isRemoved()) {
-                continue;
-            }
-            availableSpace -= child.getMinHeight(true /* ignoreTemporaryStates */)
-                    + notificationPadding;
-            if (availableSpace >= 0 && count < maximum) {
-                count++;
-            } else if (availableSpace > -shelfSize) {
-                // if we are exactly the last view, then we can show us still!
-                for (int j = i + 1; j < mNotificationStackScroller.getChildCount(); j++) {
-                    if (mNotificationStackScroller.getChildAt(j)
-                            instanceof ExpandableNotificationRow) {
-                        return count;
-                    }
-                }
-                count++;
-                return count;
-            } else {
-                return count;
-            }
-        }
-        return count;
-    }
-
-    private void updateClock() {
-        if (!mKeyguardStatusViewAnimating) {
-            mKeyguardStatusView.setAlpha(mClockPositionResult.clockAlpha);
-        }
-    }
-
-    public void animateToFullShade(long delay) {
-        mNotificationStackScroller.goToFullShade(delay);
-        requestLayout();
-        mAnimateNextPositionUpdate = true;
-    }
-
-    public void setQsExpansionEnabled(boolean qsExpansionEnabled) {
-        mQsExpansionEnabled = qsExpansionEnabled;
-        if (mQs == null) return;
-        mQs.setHeaderClickable(qsExpansionEnabled);
-    }
-
-    @Override
-    public void resetViews(boolean animate) {
-        mIsLaunchTransitionFinished = false;
-        mBlockTouches = false;
-        if (!mLaunchingAffordance) {
-            mAffordanceHelper.reset(false);
-            mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
-        }
-        mStatusBar.getGutsManager().closeAndSaveGuts(true /* leavebehind */, true /* force */,
-                true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
-        if (animate) {
-            animateCloseQs(true /* animateAway */);
-        } else {
-            closeQs();
-        }
-        mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, animate,
-                !animate /* cancelAnimators */);
-        mNotificationStackScroller.resetScrollPosition();
-    }
-
-    @Override
-    public void collapse(boolean delayed, float speedUpFactor) {
-        if (!canPanelBeCollapsed()) {
-            return;
-        }
-
-        if (mQsExpanded) {
-            mQsExpandImmediate = true;
-            mNotificationStackScroller.setShouldShowShelfOnly(true);
-        }
-        super.collapse(delayed, speedUpFactor);
-    }
-
-    public void closeQs() {
-        cancelQsAnimation();
-        setQsExpansion(mQsMinExpansionHeight);
-    }
-
-    /**
-     * Animate QS closing by flinging it.
-     * If QS is expanded, it will collapse into QQS and stop.
-     *
-     * @param animateAway Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
-     */
-    public void animateCloseQs(boolean animateAway) {
-        if (mQsExpansionAnimator != null) {
-            if (!mQsAnimatorExpand) {
-                return;
-            }
-            float height = mQsExpansionHeight;
-            mQsExpansionAnimator.cancel();
-            setQsExpansion(height);
-        }
-        flingSettings(0 /* vel */, animateAway ? FLING_HIDE : FLING_COLLAPSE);
-    }
-
-    public void expandWithQs() {
-        if (mQsExpansionEnabled) {
-            mQsExpandImmediate = true;
-            mNotificationStackScroller.setShouldShowShelfOnly(true);
-        }
-        if (isFullyCollapsed()) {
-            expand(true /* animate */);
-        } else {
-            flingSettings(0 /* velocity */, FLING_EXPAND);
-        }
-    }
-
-    public void expandWithoutQs() {
-        if (isQsExpanded()) {
-            flingSettings(0 /* velocity */, FLING_COLLAPSE);
-        } else {
-            expand(true /* animate */);
-        }
-    }
-
-    @Override
-    public void fling(float vel, boolean expand) {
-        GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder();
-        if (gr != null) {
-            gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel);
-        }
-        super.fling(vel, expand);
-    }
-
-    @Override
-    protected void flingToHeight(float vel, boolean expand, float target,
-            float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
-        mHeadsUpTouchHelper.notifyFling(!expand);
-        setClosingWithAlphaFadeout(!expand && !isOnKeyguard() && getFadeoutAlpha() == 1.0f);
-        super.flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
-    }
-
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent event) {
-        if (mBlockTouches || mQsFullyExpanded && mQs.onInterceptTouchEvent(event)) {
-            return false;
-        }
-        initDownStates(event);
-        // Do not let touches go to shade or QS if the bouncer is visible,
-        // but still let user swipe down to expand the panel, dismissing the bouncer.
-        if (mStatusBar.isBouncerShowing()) {
-            return true;
-        }
-        if (mBar.panelEnabled() && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
-            mIsExpansionFromHeadsUp = true;
-            MetricsLogger.count(mContext, COUNTER_PANEL_OPEN, 1);
-            MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_PEEK, 1);
-            return true;
-        }
-        if (!shouldQuickSettingsIntercept(mDownX, mDownY, 0)
-                && mPulseExpansionHandler.onInterceptTouchEvent(event)) {
-            return true;
-        }
-
-        if (!isFullyCollapsed() && onQsIntercept(event)) {
-            return true;
-        }
-        return super.onInterceptTouchEvent(event);
-    }
-
-    private boolean onQsIntercept(MotionEvent event) {
-        int pointerIndex = event.findPointerIndex(mTrackingPointer);
-        if (pointerIndex < 0) {
-            pointerIndex = 0;
-            mTrackingPointer = event.getPointerId(pointerIndex);
-        }
-        final float x = event.getX(pointerIndex);
-        final float y = event.getY(pointerIndex);
-
-        switch (event.getActionMasked()) {
-            case MotionEvent.ACTION_DOWN:
-                mIntercepting = true;
-                mInitialTouchY = y;
-                mInitialTouchX = x;
-                initVelocityTracker();
-                trackMovement(event);
-                if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
-                    getParent().requestDisallowInterceptTouchEvent(true);
-                }
-                if (mQsExpansionAnimator != null) {
-                    onQsExpansionStarted();
-                    mInitialHeightOnTouch = mQsExpansionHeight;
-                    mQsTracking = true;
-                    mIntercepting = false;
-                    mNotificationStackScroller.cancelLongPress();
-                }
-                break;
-            case MotionEvent.ACTION_POINTER_UP:
-                final int upPointer = event.getPointerId(event.getActionIndex());
-                if (mTrackingPointer == upPointer) {
-                    // gesture is ongoing, find a new pointer to track
-                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
-                    mTrackingPointer = event.getPointerId(newIndex);
-                    mInitialTouchX = event.getX(newIndex);
-                    mInitialTouchY = event.getY(newIndex);
-                }
-                break;
-
-            case MotionEvent.ACTION_MOVE:
-                final float h = y - mInitialTouchY;
-                trackMovement(event);
-                if (mQsTracking) {
-
-                    // Already tracking because onOverscrolled was called. We need to update here
-                    // so we don't stop for a frame until the next touch event gets handled in
-                    // onTouchEvent.
-                    setQsExpansion(h + mInitialHeightOnTouch);
-                    trackMovement(event);
-                    mIntercepting = false;
-                    return true;
-                }
-                if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)
-                        && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
-                    mQsTracking = true;
-                    onQsExpansionStarted();
-                    notifyExpandingFinished();
-                    mInitialHeightOnTouch = mQsExpansionHeight;
-                    mInitialTouchY = y;
-                    mInitialTouchX = x;
-                    mIntercepting = false;
-                    mNotificationStackScroller.cancelLongPress();
-                    return true;
-                }
-                break;
-
-            case MotionEvent.ACTION_CANCEL:
-            case MotionEvent.ACTION_UP:
-                trackMovement(event);
-                if (mQsTracking) {
-                    flingQsWithCurrentVelocity(y,
-                            event.getActionMasked() == MotionEvent.ACTION_CANCEL);
-                    mQsTracking = false;
-                }
-                mIntercepting = false;
-                break;
-        }
-        return false;
-    }
-
-    @Override
-    protected boolean isInContentBounds(float x, float y) {
-        float stackScrollerX = mNotificationStackScroller.getX();
-        return !mNotificationStackScroller.isBelowLastNotification(x - stackScrollerX, y)
-                && stackScrollerX < x && x < stackScrollerX + mNotificationStackScroller.getWidth();
-    }
-
-    private void initDownStates(MotionEvent event) {
-        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
-            mOnlyAffordanceInThisMotion = false;
-            mQsTouchAboveFalsingThreshold = mQsFullyExpanded;
-            mDozingOnDown = isDozing();
-            mDownX = event.getX();
-            mDownY = event.getY();
-            mCollapsedOnDown = isFullyCollapsed();
-            mListenForHeadsUp = mCollapsedOnDown && mHeadsUpManager.hasPinnedHeadsUp();
-            mAllowExpandForSmallExpansion = mExpectingSynthesizedDown;
-            mTouchSlopExceededBeforeDown = mExpectingSynthesizedDown;
-            if (mExpectingSynthesizedDown) {
-                mLastEventSynthesizedDown = true;
-            } else {
-                // down but not synthesized motion event.
-                mLastEventSynthesizedDown = false;
-            }
-        } else {
-            // not down event at all.
-            mLastEventSynthesizedDown = false;
-        }
-    }
-
-    private void flingQsWithCurrentVelocity(float y, boolean isCancelMotionEvent) {
-        float vel = getCurrentQSVelocity();
-        final boolean expandsQs = flingExpandsQs(vel);
-        if (expandsQs) {
-            logQsSwipeDown(y);
-        }
-        flingSettings(vel, expandsQs && !isCancelMotionEvent ? FLING_EXPAND : FLING_COLLAPSE);
-    }
-
-    private void logQsSwipeDown(float y) {
-        float vel = getCurrentQSVelocity();
-        final int gesture = mBarState == StatusBarState.KEYGUARD
-                ? MetricsEvent.ACTION_LS_QS
-                : MetricsEvent.ACTION_SHADE_QS_PULL;
-        mLockscreenGestureLogger.write(gesture,
-                (int) ((y - mInitialTouchY) / mStatusBar.getDisplayDensity()),
-                (int) (vel / mStatusBar.getDisplayDensity()));
-    }
-
-    private boolean flingExpandsQs(float vel) {
-        if (mFalsingManager.isUnlockingDisabled() || isFalseTouch()) {
-            return false;
-        }
-        if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
-            return getQsExpansionFraction() > 0.5f;
-        } else {
-            return vel > 0;
-        }
-    }
-
-    private boolean isFalseTouch() {
-        if (!needsAntiFalsing()) {
-            return false;
-        }
-        if (mFalsingManager.isClassifierEnabled()) {
-            return mFalsingManager.isFalseTouch();
-        }
-        return !mQsTouchAboveFalsingThreshold;
-    }
-
-    private float getQsExpansionFraction() {
-        return Math.min(1f, (mQsExpansionHeight - mQsMinExpansionHeight)
-                / (mQsMaxExpansionHeight - mQsMinExpansionHeight));
-    }
-
-    @Override
-    protected boolean shouldExpandWhenNotFlinging() {
-        if (super.shouldExpandWhenNotFlinging()) {
-            return true;
-        }
-        if (mAllowExpandForSmallExpansion) {
-            // When we get a touch that came over from launcher, the velocity isn't always correct
-            // Let's err on expanding if the gesture has been reasonably slow
-            long timeSinceDown = SystemClock.uptimeMillis() - mDownTime;
-            return timeSinceDown <= MAX_TIME_TO_OPEN_WHEN_FLINGING_FROM_LAUNCHER;
-        }
-        return false;
-    }
-
-    @Override
-    protected float getOpeningHeight() {
-        return mNotificationStackScroller.getOpeningHeight();
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        if (mBlockTouches || (mQs != null && mQs.isCustomizing())) {
-            return false;
-        }
-
-        // Do not allow panel expansion if bouncer is scrimmed, otherwise user would be able to
-        // pull down QS or expand the shade.
-        if (mStatusBar.isBouncerShowingScrimmed()) {
-            return false;
-        }
-
-        // Make sure the next touch won't the blocked after the current ends.
-        if (event.getAction() == MotionEvent.ACTION_UP
-                || event.getAction() == MotionEvent.ACTION_CANCEL) {
-            mBlockingExpansionForCurrentTouch = false;
-        }
-        // When touch focus transfer happens, ACTION_DOWN->ACTION_UP may happen immediately
-        // without any ACTION_MOVE event.
-        // In such case, simply expand the panel instead of being stuck at the bottom bar.
-        if (mLastEventSynthesizedDown && event.getAction() == MotionEvent.ACTION_UP) {
-            expand(true /* animate */);
-        }
-        initDownStates(event);
-        if (!mIsExpanding && !shouldQuickSettingsIntercept(mDownX, mDownY, 0)
-                && mPulseExpansionHandler.onTouchEvent(event)) {
-            // We're expanding all the other ones shouldn't get this anymore
-            return true;
-        }
-        if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp()
-                && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
-            mIsExpansionFromHeadsUp = true;
-            MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_PEEK, 1);
-        }
-        boolean handled = false;
-        if ((!mIsExpanding || mHintAnimationRunning)
-                && !mQsExpanded
-                && mBarState != StatusBarState.SHADE
-                && !mDozing) {
-            handled |= mAffordanceHelper.onTouchEvent(event);
-        }
-        if (mOnlyAffordanceInThisMotion) {
-            return true;
-        }
-        handled |= mHeadsUpTouchHelper.onTouchEvent(event);
-
-        if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) {
-            return true;
-        }
-        if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
-            MetricsLogger.count(mContext, COUNTER_PANEL_OPEN, 1);
-            updateVerticalPanelPosition(event.getX());
-            handled = true;
-        }
-        handled |= super.onTouchEvent(event);
-        return !mDozing || mPulsing || handled;
-    }
-
-    private boolean handleQsTouch(MotionEvent event) {
-        final int action = event.getActionMasked();
-        if (action == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f
-                && mBarState != StatusBarState.KEYGUARD && !mQsExpanded
-                && mQsExpansionEnabled) {
-
-            // Down in the empty area while fully expanded - go to QS.
-            mQsTracking = true;
-            mConflictingQsExpansionGesture = true;
-            onQsExpansionStarted();
-            mInitialHeightOnTouch = mQsExpansionHeight;
-            mInitialTouchY = event.getX();
-            mInitialTouchX = event.getY();
-        }
-        if (!isFullyCollapsed()) {
-            handleQsDown(event);
-        }
-        if (!mQsExpandImmediate && mQsTracking) {
-            onQsTouch(event);
-            if (!mConflictingQsExpansionGesture) {
-                return true;
-            }
-        }
-        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
-            mConflictingQsExpansionGesture = false;
-        }
-        if (action == MotionEvent.ACTION_DOWN && isFullyCollapsed()
-                && mQsExpansionEnabled) {
-            mTwoFingerQsExpandPossible = true;
-        }
-        if (mTwoFingerQsExpandPossible && isOpenQsEvent(event)
-                && event.getY(event.getActionIndex()) < mStatusBarMinHeight) {
-            MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_QS, 1);
-            mQsExpandImmediate = true;
-            mNotificationStackScroller.setShouldShowShelfOnly(true);
-            requestPanelHeightUpdate();
-
-            // Normally, we start listening when the panel is expanded, but here we need to start
-            // earlier so the state is already up to date when dragging down.
-            setListening(true);
-        }
-        if (isQsSplitEnabled() && !mKeyguardShowing) {
-            if (mQsExpandImmediate) {
-                mNotificationStackScroller.setVisibility(View.GONE);
-                mQsFrame.setVisibility(View.VISIBLE);
-                mHomeControlsLayout.setVisibility(View.VISIBLE);
-            } else {
-                mNotificationStackScroller.setVisibility(View.VISIBLE);
-                mQsFrame.setVisibility(View.GONE);
-                mHomeControlsLayout.setVisibility(View.GONE);
-            }
-        }
-        return false;
-    }
-
-    private boolean isInQsArea(float x, float y) {
-        return (x >= mQsFrame.getX()
-                && x <= mQsFrame.getX() + mQsFrame.getWidth())
-                && (y <= mNotificationStackScroller.getBottomMostNotificationBottom()
-                || y <= mQs.getView().getY() + mQs.getView().getHeight());
-    }
-
-    private boolean isOnQsEndArea(float x) {
-        if (!isQsSplitEnabled()) return false;
-        if (getLayoutDirection() == LAYOUT_DIRECTION_LTR) {
-            return x >= mQsFrame.getX() + mQqsSplitFraction * mQsFrame.getWidth()
-                    && x <= mQsFrame.getX() + mQsFrame.getWidth();
-        } else {
-            return x >= mQsFrame.getX()
-                    && x <= mQsFrame.getX() + (1 - mQqsSplitFraction) * mQsFrame.getWidth();
-        }
-    }
-
-    private boolean isOpenQsEvent(MotionEvent event) {
-        final int pointerCount = event.getPointerCount();
-        final int action = event.getActionMasked();
-
-        final boolean twoFingerDrag = action == MotionEvent.ACTION_POINTER_DOWN
-                && pointerCount == 2;
-
-        final boolean stylusButtonClickDrag = action == MotionEvent.ACTION_DOWN
-                && (event.isButtonPressed(MotionEvent.BUTTON_STYLUS_PRIMARY)
-                || event.isButtonPressed(MotionEvent.BUTTON_STYLUS_SECONDARY));
-
-        final boolean mouseButtonClickDrag = action == MotionEvent.ACTION_DOWN
-                && (event.isButtonPressed(MotionEvent.BUTTON_SECONDARY)
-                || event.isButtonPressed(MotionEvent.BUTTON_TERTIARY));
-
-        final boolean onHeaderRight = isOnQsEndArea(event.getX());
-
-        return twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag || onHeaderRight;
-    }
-
-    private void handleQsDown(MotionEvent event) {
-        if (event.getActionMasked() == MotionEvent.ACTION_DOWN
-                && shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) {
-            mFalsingManager.onQsDown();
-            mQsTracking = true;
-            onQsExpansionStarted();
-            mInitialHeightOnTouch = mQsExpansionHeight;
-            mInitialTouchY = event.getX();
-            mInitialTouchX = event.getY();
-
-            // If we interrupt an expansion gesture here, make sure to update the state correctly.
-            notifyExpandingFinished();
-        }
-    }
-
-    /**
-     * Input focus transfer is about to happen.
-     */
-    public void startWaitingForOpenPanelGesture() {
-        if (!isFullyCollapsed()) {
-            return;
-        }
-        mExpectingSynthesizedDown = true;
-        onTrackingStarted();
-        updatePanelExpanded();
-    }
-
-    /**
-     * Called when this view is no longer waiting for input focus transfer.
-     *
-     * There are two scenarios behind this function call. First, input focus transfer
-     * has successfully happened and this view already received synthetic DOWN event.
-     * (mExpectingSynthesizedDown == false). Do nothing.
-     *
-     * Second, before input focus transfer finished, user may have lifted finger
-     * in previous window and this window never received synthetic DOWN event.
-     * (mExpectingSynthesizedDown == true).
-     * In this case, we use the velocity to trigger fling event.
-     *
-     * @param velocity unit is in px / millis
-     */
-    public void stopWaitingForOpenPanelGesture(final float velocity) {
-        if (mExpectingSynthesizedDown) {
-            mExpectingSynthesizedDown = false;
-            maybeVibrateOnOpening();
-            Runnable runnable = () -> fling(velocity > 1f ? 1000f * velocity : 0,
-                    true /* expand */);
-            if (mStatusBar.getStatusBarWindow().getHeight()
-                    != mStatusBar.getStatusBarHeight()) {
-                // The panel is already expanded to its full size, let's expand directly
-                runnable.run();
-            } else {
-                mExpandAfterLayoutRunnable = runnable;
-            }
-            onTrackingStopped(false);
-        }
-    }
-
-    @Override
-    protected boolean flingExpands(float vel, float vectorVel, float x, float y) {
-        boolean expands = super.flingExpands(vel, vectorVel, x, y);
-
-        // If we are already running a QS expansion, make sure that we keep the panel open.
-        if (mQsExpansionAnimator != null) {
-            expands = true;
-        }
-        return expands;
-    }
-
-    @Override
-    protected boolean shouldGestureWaitForTouchSlop() {
-        if (mExpectingSynthesizedDown) {
-            mExpectingSynthesizedDown = false;
-            return false;
-        }
-        return isFullyCollapsed() || mBarState != StatusBarState.SHADE;
-    }
-
-    @Override
-    protected boolean shouldGestureIgnoreXTouchSlop(float x, float y) {
-        return !mAffordanceHelper.isOnAffordanceIcon(x, y);
-    }
-
-    private void onQsTouch(MotionEvent event) {
-        int pointerIndex = event.findPointerIndex(mTrackingPointer);
-        if (pointerIndex < 0) {
-            pointerIndex = 0;
-            mTrackingPointer = event.getPointerId(pointerIndex);
-        }
-        final float y = event.getY(pointerIndex);
-        final float x = event.getX(pointerIndex);
-        final float h = y - mInitialTouchY;
-
-        switch (event.getActionMasked()) {
-            case MotionEvent.ACTION_DOWN:
-                mQsTracking = true;
-                mInitialTouchY = y;
-                mInitialTouchX = x;
-                onQsExpansionStarted();
-                mInitialHeightOnTouch = mQsExpansionHeight;
-                initVelocityTracker();
-                trackMovement(event);
-                break;
-
-            case MotionEvent.ACTION_POINTER_UP:
-                final int upPointer = event.getPointerId(event.getActionIndex());
-                if (mTrackingPointer == upPointer) {
-                    // gesture is ongoing, find a new pointer to track
-                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
-                    final float newY = event.getY(newIndex);
-                    final float newX = event.getX(newIndex);
-                    mTrackingPointer = event.getPointerId(newIndex);
-                    mInitialHeightOnTouch = mQsExpansionHeight;
-                    mInitialTouchY = newY;
-                    mInitialTouchX = newX;
-                }
-                break;
-
-            case MotionEvent.ACTION_MOVE:
-                setQsExpansion(h + mInitialHeightOnTouch);
-                if (h >= getFalsingThreshold()) {
-                    mQsTouchAboveFalsingThreshold = true;
-                }
-                trackMovement(event);
-                break;
-
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL:
-                mQsTracking = false;
-                mTrackingPointer = -1;
-                trackMovement(event);
-                float fraction = getQsExpansionFraction();
-                if (fraction != 0f || y >= mInitialTouchY) {
-                    flingQsWithCurrentVelocity(y,
-                            event.getActionMasked() == MotionEvent.ACTION_CANCEL);
-                }
-                if (mQsVelocityTracker != null) {
-                    mQsVelocityTracker.recycle();
-                    mQsVelocityTracker = null;
-                }
-                break;
-        }
-    }
-
-    private int getFalsingThreshold() {
-        float factor = mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
-        return (int) (mQsFalsingThreshold * factor);
-    }
-
-    @Override
-    public void onOverscrollTopChanged(float amount, boolean isRubberbanded) {
-        cancelQsAnimation();
-        if (!mQsExpansionEnabled) {
-            amount = 0f;
-        }
-        float rounded = amount >= 1f ? amount : 0f;
-        setOverScrolling(rounded != 0f && isRubberbanded);
-        mQsExpansionFromOverscroll = rounded != 0f;
-        mLastOverscroll = rounded;
-        updateQsState();
-        setQsExpansion(mQsMinExpansionHeight + rounded);
-    }
-
-    @Override
-    public void flingTopOverscroll(float velocity, boolean open) {
-        mLastOverscroll = 0f;
-        mQsExpansionFromOverscroll = false;
-        setQsExpansion(mQsExpansionHeight);
-        flingSettings(!mQsExpansionEnabled && open ? 0f : velocity,
-                open && mQsExpansionEnabled ? FLING_EXPAND : FLING_COLLAPSE,
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        mStackScrollerOverscrolling = false;
-                        setOverScrolling(false);
-                        updateQsState();
-                    }
-                }, false /* isClick */);
-    }
-
-    private void setOverScrolling(boolean overscrolling) {
-        mStackScrollerOverscrolling = overscrolling;
-        if (mQs == null) return;
-        mQs.setOverscrolling(overscrolling);
-    }
-
-    private void onQsExpansionStarted() {
-        onQsExpansionStarted(0);
-    }
-
-    protected void onQsExpansionStarted(int overscrollAmount) {
-        cancelQsAnimation();
-        cancelHeightAnimator();
-
-        // Reset scroll position and apply that position to the expanded height.
-        float height = mQsExpansionHeight - overscrollAmount;
-        setQsExpansion(height);
-        requestPanelHeightUpdate();
-        mNotificationStackScroller.checkSnoozeLeavebehind();
-
-        // When expanding QS, let's authenticate the user if possible,
-        // this will speed up notification actions.
-        if (height == 0) {
-            mStatusBar.requestFaceAuth();
-        }
-    }
-
-    private void setQsExpanded(boolean expanded) {
-        boolean changed = mQsExpanded != expanded;
-        if (changed) {
-            mQsExpanded = expanded;
-            updateQsState();
-            requestPanelHeightUpdate();
-            mFalsingManager.setQsExpanded(expanded);
-            mStatusBar.setQsExpanded(expanded);
-            mNotificationContainerParent.setQsExpanded(expanded);
-            mPulseExpansionHandler.setQsExpanded(expanded);
-            mKeyguardBypassController.setQSExpanded(expanded);
-        }
-    }
-
-    @Override
-    public void onStateChanged(int statusBarState) {
-        boolean goingToFullShade = mStatusBarStateController.goingToFullShade();
-        boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway();
-        int oldState = mBarState;
-        boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD;
-        setKeyguardStatusViewVisibility(statusBarState, keyguardFadingAway, goingToFullShade);
-        setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
-
-        mBarState = statusBarState;
-        mKeyguardShowing = keyguardShowing;
-        if (mKeyguardShowing && isQsSplitEnabled()) {
-            mNotificationStackScroller.setVisibility(View.VISIBLE);
-            mQsFrame.setVisibility(View.VISIBLE);
-            mHomeControlsLayout.setVisibility(View.GONE);
-        }
-
-        if (oldState == StatusBarState.KEYGUARD
-                && (goingToFullShade || statusBarState == StatusBarState.SHADE_LOCKED)) {
-            animateKeyguardStatusBarOut();
-            long delay = mBarState == StatusBarState.SHADE_LOCKED
-                    ? 0 : mKeyguardStateController.calculateGoingToFullShadeDelay();
-            mQs.animateHeaderSlidingIn(delay);
-        } else if (oldState == StatusBarState.SHADE_LOCKED
-                && statusBarState == StatusBarState.KEYGUARD) {
-            animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-            mNotificationStackScroller.resetScrollPosition();
-            // Only animate header if the header is visible. If not, it will partially animate out
-            // the top of QS
-            if (!mQsExpanded) {
-                mQs.animateHeaderSlidingOut();
-            }
-        } else {
-            mKeyguardStatusBar.setAlpha(1f);
-            mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE);
-            ((PhoneStatusBarView) mBar).maybeShowDivider(keyguardShowing);
-            if (keyguardShowing && oldState != mBarState) {
-                if (mQs != null) {
-                    mQs.hideImmediately();
-                }
-            }
-        }
-        updateKeyguardStatusBarForHeadsUp();
-        if (keyguardShowing) {
-            updateDozingVisibilities(false /* animate */);
-        }
-        // THe update needs to happen after the headerSlide in above, otherwise the translation
-        // would reset
-        updateQSPulseExpansion();
-        maybeAnimateBottomAreaAlpha();
-        resetHorizontalPanelPosition();
-        updateQsState();
-    }
-
-    private void maybeAnimateBottomAreaAlpha() {
-        mBottomAreaShadeAlphaAnimator.cancel();
-        if (mBarState == StatusBarState.SHADE_LOCKED) {
-            mBottomAreaShadeAlphaAnimator.start();
-        } else {
-            mBottomAreaShadeAlpha = 1f;
-        }
-    }
-
-    private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = new Runnable() {
-        @Override
-        public void run() {
-            mKeyguardStatusViewAnimating = false;
-            mKeyguardStatusView.setVisibility(View.INVISIBLE);
-        }
-    };
-
-    private final Runnable mAnimateKeyguardStatusViewGoneEndRunnable = new Runnable() {
-        @Override
-        public void run() {
-            mKeyguardStatusViewAnimating = false;
-            mKeyguardStatusView.setVisibility(View.GONE);
-        }
-    };
-
-    private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = new Runnable() {
-        @Override
-        public void run() {
-            mKeyguardStatusViewAnimating = false;
-        }
-    };
-
-    private final Runnable mAnimateKeyguardStatusBarInvisibleEndRunnable = new Runnable() {
-        @Override
-        public void run() {
-            mKeyguardStatusBar.setVisibility(View.INVISIBLE);
-            mKeyguardStatusBar.setAlpha(1f);
-            mKeyguardStatusBarAnimateAlpha = 1f;
-        }
-    };
-
-    private void animateKeyguardStatusBarOut() {
-        ValueAnimator anim = ValueAnimator.ofFloat(mKeyguardStatusBar.getAlpha(), 0f);
-        anim.addUpdateListener(mStatusBarAnimateAlphaListener);
-        anim.setStartDelay(mKeyguardStateController.isKeyguardFadingAway()
-                ? mKeyguardStateController.getKeyguardFadingAwayDelay()
-                : 0);
-
-        long duration;
-        if (mKeyguardStateController.isKeyguardFadingAway()) {
-            duration = mKeyguardStateController.getShortenedFadingAwayDuration();
-        } else {
-            duration = StackStateAnimator.ANIMATION_DURATION_STANDARD;
-        }
-        anim.setDuration(duration);
-
-        anim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
-        anim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mAnimateKeyguardStatusBarInvisibleEndRunnable.run();
-            }
-        });
-        anim.start();
-    }
-
-    private final ValueAnimator.AnimatorUpdateListener mStatusBarAnimateAlphaListener =
-            new ValueAnimator.AnimatorUpdateListener() {
-                @Override
-                public void onAnimationUpdate(ValueAnimator animation) {
-                    mKeyguardStatusBarAnimateAlpha = (float) animation.getAnimatedValue();
-                    updateHeaderKeyguardAlpha();
-                }
-            };
-
-    private void animateKeyguardStatusBarIn(long duration) {
-        mKeyguardStatusBar.setVisibility(View.VISIBLE);
-        mKeyguardStatusBar.setAlpha(0f);
-        ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
-        anim.addUpdateListener(mStatusBarAnimateAlphaListener);
-        anim.setDuration(duration);
-        anim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
-        anim.start();
-    }
-
-    private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable = new Runnable() {
-        @Override
-        public void run() {
-            mKeyguardBottomArea.setVisibility(View.GONE);
-        }
-    };
-
-    private void setKeyguardBottomAreaVisibility(int statusBarState, boolean goingToFullShade) {
-        mKeyguardBottomArea.animate().cancel();
-        if (goingToFullShade) {
-            mKeyguardBottomArea.animate()
-                    .alpha(0f)
-                    .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
-                    .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration())
-                    .setInterpolator(Interpolators.ALPHA_OUT)
-                    .withEndAction(mAnimateKeyguardBottomAreaInvisibleEndRunnable)
-                    .start();
-        } else if (statusBarState == StatusBarState.KEYGUARD
-                || statusBarState == StatusBarState.SHADE_LOCKED) {
-            mKeyguardBottomArea.setVisibility(View.VISIBLE);
-            mKeyguardBottomArea.setAlpha(1f);
-        } else {
-            mKeyguardBottomArea.setVisibility(View.GONE);
-        }
-    }
-
-    private void setKeyguardStatusViewVisibility(int statusBarState, boolean keyguardFadingAway,
-            boolean goingToFullShade) {
-        mKeyguardStatusView.animate().cancel();
-        mKeyguardStatusViewAnimating = false;
-        if ((!keyguardFadingAway && mBarState == StatusBarState.KEYGUARD
-                && statusBarState != StatusBarState.KEYGUARD) || goingToFullShade) {
-            mKeyguardStatusViewAnimating = true;
-            mKeyguardStatusView.animate()
-                    .alpha(0f)
-                    .setStartDelay(0)
-                    .setDuration(160)
-                    .setInterpolator(Interpolators.ALPHA_OUT)
-                    .withEndAction(mAnimateKeyguardStatusViewGoneEndRunnable);
-            if (keyguardFadingAway) {
-                mKeyguardStatusView.animate()
-                        .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
-                        .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration())
-                        .start();
-            }
-        } else if (mBarState == StatusBarState.SHADE_LOCKED
-                && statusBarState == StatusBarState.KEYGUARD) {
-            mKeyguardStatusView.setVisibility(View.VISIBLE);
-            mKeyguardStatusViewAnimating = true;
-            mKeyguardStatusView.setAlpha(0f);
-            mKeyguardStatusView.animate()
-                    .alpha(1f)
-                    .setStartDelay(0)
-                    .setDuration(320)
-                    .setInterpolator(Interpolators.ALPHA_IN)
-                    .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable);
-        } else if (statusBarState == StatusBarState.KEYGUARD) {
-            if (keyguardFadingAway) {
-                mKeyguardStatusViewAnimating = true;
-                mKeyguardStatusView.animate()
-                        .alpha(0)
-                        .translationYBy(-getHeight() * 0.05f)
-                        .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
-                        .setDuration(125)
-                        .setStartDelay(0)
-                        .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable)
-                        .start();
-            } else {
-                mKeyguardStatusView.setVisibility(View.VISIBLE);
-                mKeyguardStatusView.setAlpha(1f);
-            }
-        } else {
-            mKeyguardStatusView.setVisibility(View.GONE);
-            mKeyguardStatusView.setAlpha(1f);
-        }
-    }
-
-    private void updateQsState() {
-        mNotificationStackScroller.setQsExpanded(mQsExpanded);
-        mNotificationStackScroller.setScrollingEnabled(
-                mBarState != StatusBarState.KEYGUARD && (!mQsExpanded
-                        || mQsExpansionFromOverscroll));
-        updateEmptyShadeView();
-        if (mNPVPluginManager != null) {
-            mNPVPluginManager.changeVisibility((mBarState != StatusBarState.KEYGUARD)
-                    ? View.VISIBLE
-                    : View.INVISIBLE);
-        }
-        mQsNavbarScrim.setVisibility(mBarState == StatusBarState.SHADE && mQsExpanded
-                && !mStackScrollerOverscrolling && mQsScrimEnabled
-                ? View.VISIBLE
-                : View.INVISIBLE);
-        if (mKeyguardUserSwitcher != null && mQsExpanded && !mStackScrollerOverscrolling) {
-            mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */);
-        }
-        if (mQs == null) return;
-        mQs.setExpanded(mQsExpanded);
-    }
-
-    private void setQsExpansion(float height) {
-        height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
-        mQsFullyExpanded = height == mQsMaxExpansionHeight && mQsMaxExpansionHeight != 0;
-        if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling
-                && !mDozing) {
-            setQsExpanded(true);
-        } else if (height <= mQsMinExpansionHeight && mQsExpanded) {
-            setQsExpanded(false);
-        }
-        mQsExpansionHeight = height;
-        updateQsExpansion();
-        requestScrollerTopPaddingUpdate(false /* animate */);
-        updateHeaderKeyguardAlpha();
-        if (mBarState == StatusBarState.SHADE_LOCKED
-                || mBarState == StatusBarState.KEYGUARD) {
-            updateKeyguardBottomAreaAlpha();
-            updateBigClockAlpha();
-        }
-        if (mBarState == StatusBarState.SHADE && mQsExpanded
-                && !mStackScrollerOverscrolling && mQsScrimEnabled) {
-            mQsNavbarScrim.setAlpha(getQsExpansionFraction());
-        }
-
-        if (mAccessibilityManager.isEnabled()) {
-            setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
-        }
-
-        if (!mFalsingManager.isUnlockingDisabled() && mQsFullyExpanded
-                && mFalsingManager.shouldEnforceBouncer()) {
-            mStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
-                    false /* dismissShade */, true /* afterKeyguardGone */, false /* deferred */);
-        }
-        for (int i = 0; i < mExpansionListeners.size(); i++) {
-            mExpansionListeners.get(i).onQsExpansionChanged(mQsMaxExpansionHeight != 0
-                    ? mQsExpansionHeight / mQsMaxExpansionHeight : 0);
-        }
-        if (DEBUG) {
-            invalidate();
-        }
-    }
-
-    protected void updateQsExpansion() {
-        if (mQs == null) return;
-        float qsExpansionFraction = getQsExpansionFraction();
-        mQs.setQsExpansion(qsExpansionFraction, getHeaderTranslation());
-        int heightDiff = mQs.getDesiredHeight() - mQs.getQsMinExpansionHeight();
-        if (mNPVPluginManager != null) {
-            mNPVPluginManager.setExpansion(qsExpansionFraction, getHeaderTranslation(), heightDiff);
-        }
-        mNotificationStackScroller.setQsExpansionFraction(qsExpansionFraction);
-    }
-
-    private String determineAccessibilityPaneTitle() {
-        if (mQs != null && mQs.isCustomizing()) {
-            return getContext().getString(R.string.accessibility_desc_quick_settings_edit);
-        } else if (mQsExpansionHeight != 0.0f && mQsFullyExpanded) {
-            // Upon initialisation when we are not layouted yet we don't want to announce that we
-            // are fully expanded, hence the != 0.0f check.
-            return getContext().getString(R.string.accessibility_desc_quick_settings);
-        } else if (mBarState == StatusBarState.KEYGUARD) {
-            return getContext().getString(R.string.accessibility_desc_lock_screen);
-        } else {
-            return getContext().getString(R.string.accessibility_desc_notification_shade);
-        }
-    }
-
-    private float calculateQsTopPadding() {
-        if (mKeyguardShowing
-                && (mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted)) {
-
-            // Either QS pushes the notifications down when fully expanded, or QS is fully above the
-            // notifications (mostly on tablets). maxNotificationPadding denotes the normal top
-            // padding on Keyguard, maxQsPadding denotes the top padding from the quick settings
-            // panel. We need to take the maximum and linearly interpolate with the panel expansion
-            // for a nice motion.
-            int maxNotificationPadding = getKeyguardNotificationStaticPadding();
-            int maxQsPadding = mQsMaxExpansionHeight + mQsNotificationTopPadding;
-            int max = mBarState == StatusBarState.KEYGUARD
-                    ? Math.max(maxNotificationPadding, maxQsPadding)
-                    : maxQsPadding;
-            return (int) MathUtils.lerp((float) mQsMinExpansionHeight, (float) max,
-                    getExpandedFraction());
-        } else if (mQsSizeChangeAnimator != null) {
-            return Math.max((int) mQsSizeChangeAnimator.getAnimatedValue(),
-                    getKeyguardNotificationStaticPadding());
-        } else if (mKeyguardShowing) {
-            // We can only do the smoother transition on Keyguard when we also are not collapsing
-            // from a scrolled quick settings.
-            return MathUtils.lerp((float) getKeyguardNotificationStaticPadding(),
-                    (float) (mQsMaxExpansionHeight + mQsNotificationTopPadding),
-                    getQsExpansionFraction());
-        } else {
-            return mQsExpansionHeight + mQsNotificationTopPadding;
-        }
-    }
-
-    /**
-     * @return the topPadding of notifications when on keyguard not respecting quick settings
-     *         expansion
-     */
-    private int getKeyguardNotificationStaticPadding() {
-        if (!mKeyguardShowing) {
-            return 0;
-        }
-        if (!mKeyguardBypassController.getBypassEnabled()) {
-            return mClockPositionResult.stackScrollerPadding;
-        }
-        int collapsedPosition = mHeadsUpInset;
-        if (!mNotificationStackScroller.isPulseExpanding()) {
-            return collapsedPosition;
-        } else {
-            int expandedPosition = mClockPositionResult.stackScrollerPadding;
-            return (int) MathUtils.lerp(collapsedPosition, expandedPosition,
-                    mNotificationStackScroller.calculateAppearFractionBypass());
-        }
-    }
-
-
-    protected void requestScrollerTopPaddingUpdate(boolean animate) {
-        mNotificationStackScroller.updateTopPadding(calculateQsTopPadding(), animate);
-        if (mKeyguardShowing && mKeyguardBypassController.getBypassEnabled()) {
-            // update the position of the header
-            updateQsExpansion();
-        }
-    }
-
-
-    private void updateQSPulseExpansion() {
-        if (mQs != null) {
-            mQs.setShowCollapsedOnKeyguard(mKeyguardShowing
-                    && mKeyguardBypassController.getBypassEnabled()
-                    && mNotificationStackScroller.isPulseExpanding());
-        }
-    }
-
-    private void trackMovement(MotionEvent event) {
-        if (mQsVelocityTracker != null) mQsVelocityTracker.addMovement(event);
-        mLastTouchX = event.getX();
-        mLastTouchY = event.getY();
-    }
-
-    private void initVelocityTracker() {
-        if (mQsVelocityTracker != null) {
-            mQsVelocityTracker.recycle();
-        }
-        mQsVelocityTracker = VelocityTracker.obtain();
-    }
-
-    private float getCurrentQSVelocity() {
-        if (mQsVelocityTracker == null) {
-            return 0;
-        }
-        mQsVelocityTracker.computeCurrentVelocity(1000);
-        return mQsVelocityTracker.getYVelocity();
-    }
-
-    private void cancelQsAnimation() {
-        if (mQsExpansionAnimator != null) {
-            mQsExpansionAnimator.cancel();
-        }
-    }
-
-    /**
-     * @see #flingSettings(float, int, Runnable, boolean)
-     */
-    public void flingSettings(float vel, int type) {
-        flingSettings(vel, type, null, false /* isClick */);
-    }
-
-    /**
-     * Animates QS or QQS as if the user had swiped up or down.
-     *
-     * @param vel Finger velocity or 0 when not initiated by touch events.
-     * @param type Either {@link #FLING_EXPAND}, {@link #FLING_COLLAPSE} or {@link #FLING_HIDE}.
-     * @param onFinishRunnable Runnable to be executed at the end of animation.
-     * @param isClick If originated by click (different interpolator and duration.)
-     */
-    protected void flingSettings(float vel, int type, final Runnable onFinishRunnable,
-            boolean isClick) {
-        float target;
-        switch (type) {
-            case FLING_EXPAND:
-                target = mQsMaxExpansionHeight;
-                break;
-            case FLING_COLLAPSE:
-                target = mQsMinExpansionHeight;
-                break;
-            case FLING_HIDE:
-            default:
-                target = 0;
-        }
-        if (target == mQsExpansionHeight) {
-            if (onFinishRunnable != null) {
-                onFinishRunnable.run();
-            }
-            return;
-        }
-
-        // If we move in the opposite direction, reset velocity and use a different duration.
-        boolean oppositeDirection = false;
-        boolean expanding = type == FLING_EXPAND;
-        if (vel > 0 && !expanding || vel < 0 && expanding) {
-            vel = 0;
-            oppositeDirection = true;
-        }
-        ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target);
-        if (isClick) {
-            animator.setInterpolator(Interpolators.TOUCH_RESPONSE);
-            animator.setDuration(368);
-        } else {
-            mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel);
-        }
-        if (oppositeDirection) {
-            animator.setDuration(350);
-        }
-        animator.addUpdateListener(animation -> {
-            setQsExpansion((Float) animation.getAnimatedValue());
-        });
-        animator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mNotificationStackScroller.resetCheckSnoozeLeavebehind();
-                mQsExpansionAnimator = null;
-                if (onFinishRunnable != null) {
-                    onFinishRunnable.run();
-                }
-            }
-        });
-        animator.start();
-        mQsExpansionAnimator = animator;
-        mQsAnimatorExpand = expanding;
-    }
-
-    /**
-     * @return Whether we should intercept a gesture to open Quick Settings.
-     */
-    private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) {
-        if (!mQsExpansionEnabled || mCollapsedOnDown
-                || (mKeyguardShowing && mKeyguardBypassController.getBypassEnabled())) {
-            return false;
-        }
-        View header = mKeyguardShowing || mQs == null ? mKeyguardStatusBar : mQs.getHeader();
-        final boolean onHeader = x >= mQsFrame.getX()
-                && x <= mQsFrame.getX() + mQsFrame.getWidth()
-                && y >= header.getTop() && y <= header.getBottom();
-        if (mQsExpanded) {
-            return onHeader || (yDiff < 0 && isInQsArea(x, y));
-        } else {
-            return onHeader;
-        }
-    }
-
-    @Override
-    protected boolean isScrolledToBottom() {
-        if (!isInSettings()) {
-            return mBarState == StatusBarState.KEYGUARD
-                    || mNotificationStackScroller.isScrolledToBottom();
-        } else {
-            return true;
-        }
-    }
-
-    @Override
-    protected int getMaxPanelHeight() {
-        if (mKeyguardBypassController.getBypassEnabled() && mBarState == StatusBarState.KEYGUARD) {
-            return getMaxPanelHeightBypass();
-        } else {
-            return getMaxPanelHeightNonBypass();
-        }
-    }
-
-    private int getMaxPanelHeightNonBypass() {
-        int min = mStatusBarMinHeight;
-        if (!(mBarState == StatusBarState.KEYGUARD)
-                && mNotificationStackScroller.getNotGoneChildCount() == 0) {
-            int minHeight = (int) (mQsMinExpansionHeight + getOverExpansionAmount());
-            min = Math.max(min, minHeight);
-        }
-        int maxHeight;
-        if (mQsExpandImmediate || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted
-                || mPulsing) {
-            maxHeight = calculatePanelHeightQsExpanded();
-        } else {
-            maxHeight = calculatePanelHeightShade();
-        }
-        maxHeight = Math.max(maxHeight, min);
-        return maxHeight;
-    }
-
-    private int getMaxPanelHeightBypass() {
-        int position = mClockPositionAlgorithm.getExpandedClockPosition()
-                + mKeyguardStatusView.getHeight();
-        if (mNotificationStackScroller.getVisibleNotificationCount() != 0) {
-            position += mShelfHeight / 2.0f + mDarkIconSize / 2.0f;
-        }
-        return position;
-    }
-
-    public boolean isInSettings() {
-        return mQsExpanded;
-    }
-
-    public boolean isExpanding() {
-        return mIsExpanding;
-    }
-
-    @Override
-    protected void onHeightUpdated(float expandedHeight) {
-        if (!mQsExpanded || mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted) {
-            // Updating the clock position will set the top padding which might
-            // trigger a new panel height and re-position the clock.
-            // This is a circular dependency and should be avoided, otherwise we'll have
-            // a stack overflow.
-            if (mStackScrollerMeasuringPass > 2) {
-                if (DEBUG) Log.d(TAG, "Unstable notification panel height. Aborting.");
-            } else {
-                positionClockAndNotifications();
-            }
-        }
-        if (mQsExpandImmediate || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
-                && !mQsExpansionFromOverscroll) {
-            float t;
-            if (mKeyguardShowing) {
-
-                // On Keyguard, interpolate the QS expansion linearly to the panel expansion
-                t = expandedHeight / (getMaxPanelHeight());
-            } else {
-                // In Shade, interpolate linearly such that QS is closed whenever panel height is
-                // minimum QS expansion + minStackHeight
-                float panelHeightQsCollapsed = mNotificationStackScroller.getIntrinsicPadding()
-                        + mNotificationStackScroller.getLayoutMinHeight();
-                float panelHeightQsExpanded = calculatePanelHeightQsExpanded();
-                t = (expandedHeight - panelHeightQsCollapsed)
-                        / (panelHeightQsExpanded - panelHeightQsCollapsed);
-            }
-            float targetHeight = mQsMinExpansionHeight
-                    + t * (mQsMaxExpansionHeight - mQsMinExpansionHeight);
-            setQsExpansion(targetHeight);
-            mHomeControlsLayout.setTranslationY(targetHeight);
-        }
-        updateExpandedHeight(expandedHeight);
-        updateHeader();
-        updateNotificationTranslucency();
-        updatePanelExpanded();
-        updateGestureExclusionRect();
-        if (DEBUG) {
-            invalidate();
-        }
-    }
-
-    private void updatePanelExpanded() {
-        boolean isExpanded = !isFullyCollapsed() || mExpectingSynthesizedDown;
-        if (mPanelExpanded != isExpanded) {
-            mHeadsUpManager.setIsPanelExpanded(isExpanded);
-            mStatusBar.setPanelExpanded(isExpanded);
-            mPanelExpanded = isExpanded;
-        }
-    }
-
-    private int calculatePanelHeightShade() {
-        int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin();
-        int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin;
-        maxHeight += mNotificationStackScroller.getTopPaddingOverflow();
-
-        if (mBarState == StatusBarState.KEYGUARD) {
-            int minKeyguardPanelBottom = mClockPositionAlgorithm.getExpandedClockPosition()
-                    + mKeyguardStatusView.getHeight()
-                    + mNotificationStackScroller.getIntrinsicContentHeight();
-            return Math.max(maxHeight, minKeyguardPanelBottom);
-        } else {
-            return maxHeight;
-        }
-    }
-
-    private int calculatePanelHeightQsExpanded() {
-        float notificationHeight = mNotificationStackScroller.getHeight()
-                - mNotificationStackScroller.getEmptyBottomMargin()
-                - mNotificationStackScroller.getTopPadding();
-
-        // When only empty shade view is visible in QS collapsed state, simulate that we would have
-        // it in expanded QS state as well so we don't run into troubles when fading the view in/out
-        // and expanding/collapsing the whole panel from/to quick settings.
-        if (mNotificationStackScroller.getNotGoneChildCount() == 0
-                && mShowEmptyShadeView) {
-            notificationHeight = mNotificationStackScroller.getEmptyShadeViewHeight();
-        }
-        int maxQsHeight = mQsMaxExpansionHeight;
-
-        if (mKeyguardShowing) {
-            maxQsHeight += mQsNotificationTopPadding;
-        }
-
-        // If an animation is changing the size of the QS panel, take the animated value.
-        if (mQsSizeChangeAnimator != null) {
-            maxQsHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
-        }
-        float totalHeight = Math.max(
-                maxQsHeight, mBarState == StatusBarState.KEYGUARD
-                        ? mClockPositionResult.stackScrollerPadding : 0)
-                + notificationHeight + mNotificationStackScroller.getTopPaddingOverflow();
-        if (totalHeight > mNotificationStackScroller.getHeight()) {
-            float fullyCollapsedHeight = maxQsHeight
-                    + mNotificationStackScroller.getLayoutMinHeight();
-            totalHeight = Math.max(fullyCollapsedHeight, mNotificationStackScroller.getHeight());
-        }
-        return (int) totalHeight;
-    }
-
-    private void updateNotificationTranslucency() {
-        float alpha = 1f;
-        if (mClosingWithAlphaFadeOut && !mExpandingFromHeadsUp &&
-                !mHeadsUpManager.hasPinnedHeadsUp()) {
-            alpha = getFadeoutAlpha();
-        }
-        if (mBarState == StatusBarState.KEYGUARD && !mHintAnimationRunning
-                && !mKeyguardBypassController.getBypassEnabled()) {
-            alpha *= mClockPositionResult.clockAlpha;
-        }
-        mNotificationStackScroller.setAlpha(alpha);
-    }
-
-    private float getFadeoutAlpha() {
-        float alpha;
-        if (mQsMinExpansionHeight == 0) {
-            return 1.0f;
-        }
-        alpha = getExpandedHeight() / mQsMinExpansionHeight;
-        alpha = Math.max(0, Math.min(alpha, 1));
-        alpha = (float) Math.pow(alpha, 0.75);
-        return alpha;
-    }
-
-    @Override
-    protected float getOverExpansionAmount() {
-        return mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */);
-    }
-
-    @Override
-    protected float getOverExpansionPixels() {
-        return mNotificationStackScroller.getCurrentOverScrolledPixels(true /* top */);
-    }
-
-    /**
-     * Hides the header when notifications are colliding with it.
-     */
-    private void updateHeader() {
-        if (mBarState == StatusBarState.KEYGUARD) {
-            updateHeaderKeyguardAlpha();
-        }
-        updateQsExpansion();
-    }
-
-    protected float getHeaderTranslation() {
-        if (mBarState == StatusBarState.KEYGUARD && !mKeyguardBypassController.getBypassEnabled()) {
-            return -mQs.getQsMinExpansionHeight();
-        }
-        float appearAmount = mNotificationStackScroller.calculateAppearFraction(mExpandedHeight);
-        float startHeight = -mQsExpansionHeight;
-        if (mKeyguardBypassController.getBypassEnabled() && isOnKeyguard()
-                && mNotificationStackScroller.isPulseExpanding()) {
-            if (!mPulseExpansionHandler.isExpanding()
-                    && !mPulseExpansionHandler.getLeavingLockscreen()) {
-                // If we aborted the expansion we need to make sure the header doesn't reappear
-                // again after the header has animated away
-                appearAmount = 0;
-            } else {
-                appearAmount = mNotificationStackScroller.calculateAppearFractionBypass();
-            }
-            startHeight = -mQs.getQsMinExpansionHeight();
-            if (mNPVPluginManager != null) startHeight -= mNPVPluginManager.getHeight();
-        }
-        float translation = MathUtils.lerp(startHeight, 0,
-                Math.min(1.0f, appearAmount))
-                + mExpandOffset;
-        return Math.min(0, translation);
-    }
-
-    /**
-     * @return the alpha to be used to fade out the contents on Keyguard (status bar, bottom area)
-     *         during swiping up
-     */
-    private float getKeyguardContentsAlpha() {
-        float alpha;
-        if (mBarState == StatusBarState.KEYGUARD) {
-
-            // When on Keyguard, we hide the header as soon as we expanded close enough to the
-            // header
-            alpha = getExpandedHeight()
-                    /
-                    (mKeyguardStatusBar.getHeight() + mNotificationsHeaderCollideDistance);
-        } else {
-
-            // In SHADE_LOCKED, the top card is already really close to the header. Hide it as
-            // soon as we start translating the stack.
-            alpha = getExpandedHeight() / mKeyguardStatusBar.getHeight();
-        }
-        alpha = MathUtils.saturate(alpha);
-        alpha = (float) Math.pow(alpha, 0.75);
-        return alpha;
-    }
-
-    private void updateHeaderKeyguardAlpha() {
-        if (!mKeyguardShowing) {
-            return;
-        }
-        float alphaQsExpansion = 1 - Math.min(1, getQsExpansionFraction() * 2);
-        float newAlpha = Math.min(getKeyguardContentsAlpha(), alphaQsExpansion)
-                * mKeyguardStatusBarAnimateAlpha;
-        newAlpha *= 1.0f - mKeyguardHeadsUpShowingAmount;
-        mKeyguardStatusBar.setAlpha(newAlpha);
-        boolean hideForBypass = mFirstBypassAttempt && mUpdateMonitor.shouldListenForFace()
-                || mDelayShowingKeyguardStatusBar;
-        mKeyguardStatusBar.setVisibility(newAlpha != 0f && !mDozing && !hideForBypass
-                ? VISIBLE : INVISIBLE);
-    }
-
-    private void updateKeyguardBottomAreaAlpha() {
-        // There are two possible panel expansion behaviors:
-        // • User dragging up to unlock: we want to fade out as quick as possible
-        //   (ALPHA_EXPANSION_THRESHOLD) to avoid seeing the bouncer over the bottom area.
-        // • User tapping on lock screen: bouncer won't be visible but panel expansion will
-        //   change due to "unlock hint animation." In this case, fading out the bottom area
-        //   would also hide the message that says "swipe to unlock," we don't want to do that.
-        float expansionAlpha = MathUtils.map(isUnlockHintRunning()
-                        ? 0 : KeyguardBouncer.ALPHA_EXPANSION_THRESHOLD, 1f,
-                0f, 1f, getExpandedFraction());
-        float alpha = Math.min(expansionAlpha, 1 - getQsExpansionFraction());
-        alpha *= mBottomAreaShadeAlpha;
-        mKeyguardBottomArea.setAffordanceAlpha(alpha);
-        mKeyguardBottomArea.setImportantForAccessibility(alpha == 0f
-                ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
-                : IMPORTANT_FOR_ACCESSIBILITY_AUTO);
-        View ambientIndicationContainer = mStatusBar.getAmbientIndicationContainer();
-        if (ambientIndicationContainer != null) {
-            ambientIndicationContainer.setAlpha(alpha);
-        }
-    }
-
-    /**
-     * Custom clock fades away when user drags up to unlock or pulls down quick settings.
-     *
-     * Updates alpha of custom clock to match the alpha of the KeyguardBottomArea. See
-     * {@link updateKeyguardBottomAreaAlpha}.
-     */
-    private void updateBigClockAlpha() {
-        float expansionAlpha = MathUtils.map(isUnlockHintRunning()
-                ? 0 : KeyguardBouncer.ALPHA_EXPANSION_THRESHOLD, 1f, 0f, 1f, getExpandedFraction());
-        float alpha = Math.min(expansionAlpha, 1 - getQsExpansionFraction());
-        mBigClockContainer.setAlpha(alpha);
-    }
-
-    @Override
-    protected void onExpandingStarted() {
-        super.onExpandingStarted();
-        mNotificationStackScroller.onExpansionStarted();
-        mIsExpanding = true;
-        mQsExpandedWhenExpandingStarted = mQsFullyExpanded;
-        if (mQsExpanded) {
-            onQsExpansionStarted();
-        }
-        // Since there are QS tiles in the header now, we need to make sure we start listening
-        // immediately so they can be up to date.
-        if (mQs == null) return;
-        mQs.setHeaderListening(true);
-    }
-
-    @Override
-    protected void onExpandingFinished() {
-        super.onExpandingFinished();
-        mNotificationStackScroller.onExpansionStopped();
-        mHeadsUpManager.onExpandingFinished();
-        mIsExpanding = false;
-        if (isFullyCollapsed()) {
-            DejankUtils.postAfterTraversal(new Runnable() {
-                @Override
-                public void run() {
-                    setListening(false);
-                }
-            });
-
-            // Workaround b/22639032: Make sure we invalidate something because else RenderThread
-            // thinks we are actually drawing a frame put in reality we don't, so RT doesn't go
-            // ahead with rendering and we jank.
-            postOnAnimation(new Runnable() {
-                @Override
-                public void run() {
-                    getParent().invalidateChild(NotificationPanelView.this, mDummyDirtyRect);
-                }
-            });
-        } else {
-            setListening(true);
-        }
-        mQsExpandImmediate = false;
-        mNotificationStackScroller.setShouldShowShelfOnly(false);
-        mTwoFingerQsExpandPossible = false;
-        mIsExpansionFromHeadsUp = false;
-        notifyListenersTrackingHeadsUp(null);
-        mExpandingFromHeadsUp = false;
-        setPanelScrimMinFraction(0.0f);
-    }
-
-    private void notifyListenersTrackingHeadsUp(ExpandableNotificationRow pickedChild) {
-        for (int i = 0; i < mTrackingHeadsUpListeners.size(); i++) {
-            Consumer<ExpandableNotificationRow> listener
-                    = mTrackingHeadsUpListeners.get(i);
-            listener.accept(pickedChild);
-        }
-    }
-
-    private void setListening(boolean listening) {
-        mKeyguardStatusBar.setListening(listening);
-        if (mQs == null) return;
-        mQs.setListening(listening);
-        if (mNPVPluginManager != null) mNPVPluginManager.setListening(listening);
-    }
-
-    @Override
-    public void expand(boolean animate) {
-        super.expand(animate);
-        setListening(true);
-    }
-
-    @Override
-    protected void setOverExpansion(float overExpansion, boolean isPixels) {
-        if (mConflictingQsExpansionGesture || mQsExpandImmediate) {
-            return;
-        }
-        if (mBarState != StatusBarState.KEYGUARD) {
-            mNotificationStackScroller.setOnHeightChangedListener(null);
-            if (isPixels) {
-                mNotificationStackScroller.setOverScrolledPixels(
-                        overExpansion, true /* onTop */, false /* animate */);
-            } else {
-                mNotificationStackScroller.setOverScrollAmount(
-                        overExpansion, true /* onTop */, false /* animate */);
-            }
-            mNotificationStackScroller.setOnHeightChangedListener(this);
-        }
-    }
-
-    @Override
-    protected void onTrackingStarted() {
-        mFalsingManager.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen());
-        super.onTrackingStarted();
-        if (mQsFullyExpanded) {
-            mQsExpandImmediate = true;
-            mNotificationStackScroller.setShouldShowShelfOnly(true);
-        }
-        if (mBarState == StatusBarState.KEYGUARD
-                || mBarState == StatusBarState.SHADE_LOCKED) {
-            mAffordanceHelper.animateHideLeftRightIcon();
-        }
-        mNotificationStackScroller.onPanelTrackingStarted();
-    }
-
-    @Override
-    protected void onTrackingStopped(boolean expand) {
-        mFalsingManager.onTrackingStopped();
-        super.onTrackingStopped(expand);
-        if (expand) {
-            mNotificationStackScroller.setOverScrolledPixels(
-                    0.0f, true /* onTop */, true /* animate */);
-        }
-        mNotificationStackScroller.onPanelTrackingStopped();
-        if (expand && (mBarState == StatusBarState.KEYGUARD
-                || mBarState == StatusBarState.SHADE_LOCKED)) {
-            if (!mHintAnimationRunning) {
-                mAffordanceHelper.reset(true);
-            }
-        }
-    }
-
-    @Override
-    public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
-
-        // Block update if we are in quick settings and just the top padding changed
-        // (i.e. view == null).
-        if (view == null && mQsExpanded) {
-            return;
-        }
-        if (needsAnimation && mInterpolatedDarkAmount == 0) {
-            mAnimateNextPositionUpdate = true;
-        }
-        ExpandableView firstChildNotGone = mNotificationStackScroller.getFirstChildNotGone();
-        ExpandableNotificationRow firstRow = firstChildNotGone instanceof ExpandableNotificationRow
-                ? (ExpandableNotificationRow) firstChildNotGone
-                : null;
-        if (firstRow != null
-                && (view == firstRow || (firstRow.getNotificationParent() == firstRow))) {
-            requestScrollerTopPaddingUpdate(false /* animate */);
-        }
-        requestPanelHeightUpdate();
-    }
-
-    @Override
-    public void onReset(ExpandableView view) {
-    }
-
-    public void onQsHeightChanged() {
-        mQsMaxExpansionHeight = mQs != null ? mQs.getDesiredHeight() : 0;
-        if (mQsExpanded && mQsFullyExpanded) {
-            mQsExpansionHeight = mQsMaxExpansionHeight;
-            requestScrollerTopPaddingUpdate(false /* animate */);
-            requestPanelHeightUpdate();
-        }
-        if (mAccessibilityManager.isEnabled()) {
-            setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
-        }
-        mNotificationStackScroller.setMaxTopPadding(
-                mQsMaxExpansionHeight + mQsNotificationTopPadding);
-    }
-
-    @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        mAffordanceHelper.onConfigurationChanged();
-        if (newConfig.orientation != mLastOrientation) {
-            resetHorizontalPanelPosition();
-        }
-        mLastOrientation = newConfig.orientation;
-    }
-
-    @Override
-    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-        mNavigationBarBottomHeight = insets.getStableInsetBottom();
-        updateMaxHeadsUpTranslation();
-        return insets;
-    }
-
-    private void updateMaxHeadsUpTranslation() {
-        mNotificationStackScroller.setHeadsUpBoundaries(getHeight(), mNavigationBarBottomHeight);
-    }
-
     @Override
     public void onRtlPropertiesChanged(int layoutDirection) {
-        if (layoutDirection != mOldLayoutDirection) {
-            mAffordanceHelper.onRtlPropertiesChanged();
-            mOldLayoutDirection = layoutDirection;
+        if (mRtlChangeListener != null) {
+            mRtlChangeListener.onRtlPropertielsChanged(layoutDirection);
         }
     }
 
     @Override
-    public void onClick(View v) {
-        onQsExpansionStarted();
-        if (mQsExpanded) {
-            flingSettings(0 /* vel */, FLING_COLLAPSE, null /* onFinishRunnable */,
-                    true /* isClick */);
-        } else if (mQsExpansionEnabled) {
-            mLockscreenGestureLogger.write(MetricsEvent.ACTION_SHADE_QS_TAP, 0, 0);
-            flingSettings(0 /* vel */, FLING_EXPAND, null /* onFinishRunnable */,
-                    true /* isClick */);
-        }
-    }
-
-    @Override
-    public void onAnimationToSideStarted(boolean rightPage, float translation, float vel) {
-        boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? rightPage : !rightPage;
-        mIsLaunchTransitionRunning = true;
-        mLaunchAnimationEndRunnable = null;
-        float displayDensity = mStatusBar.getDisplayDensity();
-        int lengthDp = Math.abs((int) (translation / displayDensity));
-        int velocityDp = Math.abs((int) (vel / displayDensity));
-        if (start) {
-            mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_DIALER, lengthDp, velocityDp);
-
-            mFalsingManager.onLeftAffordanceOn();
-            if (mFalsingManager.shouldEnforceBouncer()) {
-                mStatusBar.executeRunnableDismissingKeyguard(new Runnable() {
-                    @Override
-                    public void run() {
-                        mKeyguardBottomArea.launchLeftAffordance();
-                    }
-                }, null, true /* dismissShade */, false /* afterKeyguardGone */,
-                        true /* deferred */);
-            } else {
-                mKeyguardBottomArea.launchLeftAffordance();
-            }
-        } else {
-            if (KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE.equals(
-                    mLastCameraLaunchSource)) {
-                mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_CAMERA, lengthDp, velocityDp);
-            }
-            mFalsingManager.onCameraOn();
-            if (mFalsingManager.shouldEnforceBouncer()) {
-                mStatusBar.executeRunnableDismissingKeyguard(new Runnable() {
-                    @Override
-                    public void run() {
-                        mKeyguardBottomArea.launchCamera(mLastCameraLaunchSource);
-                    }
-                }, null, true /* dismissShade */, false /* afterKeyguardGone */,
-                    true /* deferred */);
-            }
-            else {
-                mKeyguardBottomArea.launchCamera(mLastCameraLaunchSource);
-            }
-        }
-        mStatusBar.startLaunchTransitionTimeout();
-        mBlockTouches = true;
-    }
-
-    @Override
-    public void onAnimationToSideEnded() {
-        mIsLaunchTransitionRunning = false;
-        mIsLaunchTransitionFinished = true;
-        if (mLaunchAnimationEndRunnable != null) {
-            mLaunchAnimationEndRunnable.run();
-            mLaunchAnimationEndRunnable = null;
-        }
-        mStatusBar.readyForKeyguardDone();
-    }
-
-    @Override
-    protected void startUnlockHintAnimation() {
-        if (mPowerManager.isPowerSaveMode()) {
-            onUnlockHintStarted();
-            onUnlockHintFinished();
-            return;
-        }
-        super.startUnlockHintAnimation();
-    }
-
-    @Override
-    public float getMaxTranslationDistance() {
-        return (float) Math.hypot(getWidth(), getHeight());
-    }
-
-    @Override
-    public void onSwipingStarted(boolean rightIcon) {
-        mFalsingManager.onAffordanceSwipingStarted(rightIcon);
-        boolean camera = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? !rightIcon
-                : rightIcon;
-        if (camera) {
-            mKeyguardBottomArea.bindCameraPrewarmService();
-        }
-        requestDisallowInterceptTouchEvent(true);
-        mOnlyAffordanceInThisMotion = true;
-        mQsTracking = false;
-    }
-
-    @Override
-    public void onSwipingAborted() {
-        mFalsingManager.onAffordanceSwipingAborted();
-        mKeyguardBottomArea.unbindCameraPrewarmService(false /* launched */);
-    }
-
-    @Override
-    public void onIconClicked(boolean rightIcon) {
-        if (mHintAnimationRunning) {
-            return;
-        }
-        mHintAnimationRunning = true;
-        mAffordanceHelper.startHintAnimation(rightIcon, new Runnable() {
-            @Override
-            public void run() {
-                mHintAnimationRunning = false;
-                mStatusBar.onHintFinished();
-            }
-        });
-        rightIcon = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? !rightIcon : rightIcon;
-        if (rightIcon) {
-            mStatusBar.onCameraHintStarted();
-        } else {
-            if (mKeyguardBottomArea.isLeftVoiceAssist()) {
-                mStatusBar.onVoiceAssistHintStarted();
-            } else {
-                mStatusBar.onPhoneHintStarted();
-            }
-        }
-    }
-
-    @Override
-    protected void onUnlockHintFinished() {
-        super.onUnlockHintFinished();
-        mNotificationStackScroller.setUnlockHintRunning(false);
-    }
-
-    @Override
-    protected void onUnlockHintStarted() {
-        super.onUnlockHintStarted();
-        mNotificationStackScroller.setUnlockHintRunning(true);
-    }
-
-    @Override
-    public KeyguardAffordanceView getLeftIcon() {
-        return getLayoutDirection() == LAYOUT_DIRECTION_RTL
-                ? mKeyguardBottomArea.getRightView()
-                : mKeyguardBottomArea.getLeftView();
-    }
-
-    @Override
-    public KeyguardAffordanceView getRightIcon() {
-        return getLayoutDirection() == LAYOUT_DIRECTION_RTL
-                ? mKeyguardBottomArea.getLeftView()
-                : mKeyguardBottomArea.getRightView();
-    }
-
-    @Override
-    public View getLeftPreview() {
-        return getLayoutDirection() == LAYOUT_DIRECTION_RTL
-                ? mKeyguardBottomArea.getRightPreview()
-                : mKeyguardBottomArea.getLeftPreview();
-    }
-
-    @Override
-    public View getRightPreview() {
-        return getLayoutDirection() == LAYOUT_DIRECTION_RTL
-                ? mKeyguardBottomArea.getLeftPreview()
-                : mKeyguardBottomArea.getRightPreview();
-    }
-
-    @Override
-    public float getAffordanceFalsingFactor() {
-        return mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
-    }
-
-    @Override
-    public boolean needsAntiFalsing() {
-        return mBarState == StatusBarState.KEYGUARD;
-    }
-
-    @Override
-    protected float getPeekHeight() {
-        if (mNotificationStackScroller.getNotGoneChildCount() > 0) {
-            return mNotificationStackScroller.getPeekHeight();
-        } else {
-            return mQsMinExpansionHeight;
-        }
-    }
-
-    @Override
-    protected boolean shouldUseDismissingAnimation() {
-        return mBarState != StatusBarState.SHADE
-                && (mKeyguardStateController.canDismissLockScreen() || !isTracking());
-    }
-
-    @Override
-    protected boolean fullyExpandedClearAllVisible() {
-        return mNotificationStackScroller.isFooterViewNotGone()
-                && mNotificationStackScroller.isScrolledToBottom() && !mQsExpandImmediate;
-    }
-
-    @Override
-    protected boolean isClearAllVisible() {
-        return mNotificationStackScroller.isFooterViewContentVisible();
-    }
-
-    @Override
-    protected int getClearAllHeight() {
-        return mNotificationStackScroller.getFooterViewHeight();
-    }
-
-    @Override
-    protected boolean isTrackingBlocked() {
-        return mConflictingQsExpansionGesture && mQsExpanded || mBlockingExpansionForCurrentTouch;
-    }
-
-    public boolean isQsExpanded() {
-        return mQsExpanded;
-    }
-
-    public boolean isQsDetailShowing() {
-        return mQs.isShowingDetail();
-    }
-
-    public void closeQsDetail() {
-        mQs.closeDetail();
-    }
-
-    @Override
     public boolean shouldDelayChildPressedState() {
         return true;
     }
 
-    public boolean isLaunchTransitionFinished() {
-        return mIsLaunchTransitionFinished;
-    }
-
-    public boolean isLaunchTransitionRunning() {
-        return mIsLaunchTransitionRunning;
-    }
-
-    public void setLaunchTransitionEndRunnable(Runnable r) {
-        mLaunchAnimationEndRunnable = r;
-    }
-
-    public void setEmptyDragAmount(float amount) {
-        mEmptyDragAmount = amount * 0.2f;
-        positionClockAndNotifications();
-    }
-
-    private void updateDozingVisibilities(boolean animate) {
-        mKeyguardBottomArea.setDozing(mDozing, animate);
-        if (!mDozing && animate) {
-            animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-        }
-    }
-
-    @Override
-    public boolean isDozing() {
-        return mDozing;
-    }
-
-    public void showEmptyShadeView(boolean emptyShadeViewVisible) {
-        mShowEmptyShadeView = emptyShadeViewVisible;
-        updateEmptyShadeView();
-    }
-
-    private void updateEmptyShadeView() {
-        // Hide "No notifications" in QS.
-        mNotificationStackScroller.updateEmptyShadeView(mShowEmptyShadeView && !mQsExpanded);
-    }
-
-    public void setQsScrimEnabled(boolean qsScrimEnabled) {
-        boolean changed = mQsScrimEnabled != qsScrimEnabled;
-        mQsScrimEnabled = qsScrimEnabled;
-        if (changed) {
-            updateQsState();
-        }
-    }
-
-    public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
-        mKeyguardUserSwitcher = keyguardUserSwitcher;
-    }
-
-    public void onScreenTurningOn() {
-        mKeyguardStatusView.dozeTimeTick();
-    }
-
-    @Override
-    public void onEmptySpaceClicked(float x, float y) {
-        onEmptySpaceClick(x);
-    }
-
-    @Override
-    protected boolean onMiddleClicked() {
-        switch (mBarState) {
-            case StatusBarState.KEYGUARD:
-                if (!mDozingOnDown) {
-                    if (mKeyguardBypassController.getBypassEnabled()) {
-                        mUpdateMonitor.requestFaceAuth();
-                    } else {
-                        mLockscreenGestureLogger.write(
-                                MetricsEvent.ACTION_LS_HINT,
-                                0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
-                        startUnlockHintAnimation();
-                    }
-                }
-                return true;
-            case StatusBarState.SHADE_LOCKED:
-                if (!mQsExpanded) {
-                    mStatusBarStateController.setState(StatusBarState.KEYGUARD);
-                }
-                return true;
-            case StatusBarState.SHADE:
-
-                // This gets called in the middle of the touch handling, where the state is still
-                // that we are tracking the panel. Collapse the panel after this is done.
-                post(mPostCollapseRunnable);
-                return false;
-            default:
-                return true;
-        }
-    }
-
     @Override
     protected void dispatchDraw(Canvas canvas) {
         super.dispatchDraw(canvas);
@@ -3011,250 +69,18 @@
         }
     }
 
-    public float getCurrentPanelAlpha() {
+    float getCurrentPanelAlpha() {
         return mCurrentPanelAlpha;
     }
 
-    public boolean setPanelAlpha(int alpha, boolean animate) {
-        if (mPanelAlpha != alpha) {
-            mPanelAlpha = alpha;
-            PropertyAnimator.setProperty(this, PANEL_ALPHA, alpha,
-                    alpha == 255 ? PANEL_ALPHA_IN_PROPERTIES : PANEL_ALPHA_OUT_PROPERTIES, animate);
-            return true;
-        }
-        return false;
-    }
-
-    public void setPanelAlphaInternal(float alpha) {
+    void setPanelAlphaInternal(float alpha) {
         mCurrentPanelAlpha = (int) alpha;
         mAlphaPaint.setARGB(mCurrentPanelAlpha, 255, 255, 255);
         invalidate();
     }
 
-    public void setPanelAlphaEndAction(Runnable r) {
-        mPanelAlphaEndAction = r;
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-
-        if (DEBUG) {
-            Paint p = new Paint();
-            p.setColor(Color.RED);
-            p.setStrokeWidth(2);
-            p.setStyle(Paint.Style.STROKE);
-            canvas.drawLine(0, getMaxPanelHeight(), getWidth(), getMaxPanelHeight(), p);
-            p.setColor(Color.BLUE);
-            canvas.drawLine(0, getExpandedHeight(), getWidth(), getExpandedHeight(), p);
-            p.setColor(Color.GREEN);
-            canvas.drawLine(0, calculatePanelHeightQsExpanded(), getWidth(),
-                    calculatePanelHeightQsExpanded(), p);
-            p.setColor(Color.YELLOW);
-            canvas.drawLine(0, calculatePanelHeightShade(), getWidth(),
-                    calculatePanelHeightShade(), p);
-            p.setColor(Color.MAGENTA);
-            canvas.drawLine(0, calculateQsTopPadding(), getWidth(),
-                    calculateQsTopPadding(), p);
-            p.setColor(Color.CYAN);
-            canvas.drawLine(0, mClockPositionResult.stackScrollerPadding, getWidth(),
-                    mNotificationStackScroller.getTopPadding(), p);
-            p.setColor(Color.GRAY);
-            canvas.drawLine(0, mClockPositionResult.clockY, getWidth(),
-                    mClockPositionResult.clockY, p);
-        }
-    }
-
-    @Override
-    public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) {
-        mNotificationStackScroller.setInHeadsUpPinnedMode(inPinnedMode);
-        if (inPinnedMode) {
-            mHeadsUpExistenceChangedRunnable.run();
-            updateNotificationTranslucency();
-        } else {
-            setHeadsUpAnimatingAway(true);
-            mNotificationStackScroller.runAfterAnimationFinished(
-                    mHeadsUpExistenceChangedRunnable);
-        }
-        updateGestureExclusionRect();
-        mHeadsUpPinnedMode = inPinnedMode;
-        updateHeadsUpVisibility();
-        updateKeyguardStatusBarForHeadsUp();
-    }
-
-    private void updateKeyguardStatusBarForHeadsUp() {
-        boolean showingKeyguardHeadsUp = mKeyguardShowing
-                && mHeadsUpAppearanceController.shouldBeVisible();
-        if (mShowingKeyguardHeadsUp != showingKeyguardHeadsUp) {
-            mShowingKeyguardHeadsUp = showingKeyguardHeadsUp;
-            if (mKeyguardShowing) {
-                PropertyAnimator.setProperty(this, KEYGUARD_HEADS_UP_SHOWING_AMOUNT,
-                        showingKeyguardHeadsUp ? 1.0f : 0.0f, KEYGUARD_HUN_PROPERTIES,
-                        true /* animate */);
-            } else {
-                PropertyAnimator.applyImmediately(this, KEYGUARD_HEADS_UP_SHOWING_AMOUNT, 0.0f);
-            }
-        }
-    }
-
-    private void setKeyguardHeadsUpShowingAmount(float amount) {
-        mKeyguardHeadsUpShowingAmount = amount;
-        updateHeaderKeyguardAlpha();
-    }
-
-    private float getKeyguardHeadsUpShowingAmount() {
-        return mKeyguardHeadsUpShowingAmount;
-    }
-
-    public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
-        mHeadsUpAnimatingAway = headsUpAnimatingAway;
-        mNotificationStackScroller.setHeadsUpAnimatingAway(headsUpAnimatingAway);
-        updateHeadsUpVisibility();
-    }
-
-    private void updateHeadsUpVisibility() {
-        ((PhoneStatusBarView) mBar).setHeadsUpVisible(mHeadsUpAnimatingAway || mHeadsUpPinnedMode);
-    }
-
-    @Override
-    public void onHeadsUpPinned(NotificationEntry entry) {
-        if (!isOnKeyguard()) {
-            mNotificationStackScroller.generateHeadsUpAnimation(entry.getHeadsUpAnimationView(),
-                    true);
-        }
-    }
-
-    @Override
-    public void onHeadsUpUnPinned(NotificationEntry entry) {
-
-        // When we're unpinning the notification via active edge they remain heads-upped,
-        // we need to make sure that an animation happens in this case, otherwise the notification
-        // will stick to the top without any interaction.
-        if (isFullyCollapsed() && entry.isRowHeadsUp() && !isOnKeyguard()) {
-            mNotificationStackScroller.generateHeadsUpAnimation(
-                    entry.getHeadsUpAnimationView(), false);
-            entry.setHeadsUpIsVisible();
-        }
-    }
-
-    @Override
-    public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
-        mNotificationStackScroller.generateHeadsUpAnimation(entry, isHeadsUp);
-    }
-
-    @Override
-    public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
-        super.setHeadsUpManager(headsUpManager);
-        mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager,
-                mNotificationStackScroller.getHeadsUpCallback(), this);
-    }
-
-    public void setTrackedHeadsUp(ExpandableNotificationRow pickedChild) {
-        if (pickedChild != null) {
-            notifyListenersTrackingHeadsUp(pickedChild);
-            mExpandingFromHeadsUp = true;
-        }
-        // otherwise we update the state when the expansion is finished
-    }
-
-    @Override
-    protected void onClosingFinished() {
-        super.onClosingFinished();
-        resetHorizontalPanelPosition();
-        setClosingWithAlphaFadeout(false);
-    }
-
-    private void setClosingWithAlphaFadeout(boolean closing) {
-        mClosingWithAlphaFadeOut = closing;
-        mNotificationStackScroller.forceNoOverlappingRendering(closing);
-    }
-
-    /**
-     * Updates the vertical position of the panel so it is positioned closer to the touch
-     * responsible for opening the panel.
-     *
-     * @param x the x-coordinate the touch event
-     */
-    protected void updateVerticalPanelPosition(float x) {
-        if (mNotificationStackScroller.getWidth() * 1.75f > getWidth()) {
-            resetHorizontalPanelPosition();
-            return;
-        }
-        float leftMost = mPositionMinSideMargin + mNotificationStackScroller.getWidth() / 2;
-        float rightMost = getWidth() - mPositionMinSideMargin
-                - mNotificationStackScroller.getWidth() / 2;
-        if (Math.abs(x - getWidth() / 2) < mNotificationStackScroller.getWidth() / 4) {
-            x = getWidth() / 2;
-        }
-        x = Math.min(rightMost, Math.max(leftMost, x));
-        float center =
-                mNotificationStackScroller.getLeft() + mNotificationStackScroller.getWidth() / 2;
-        setHorizontalPanelTranslation(x - center);
-    }
-
-    private void resetHorizontalPanelPosition() {
-        setHorizontalPanelTranslation(0f);
-    }
-
-    protected void setHorizontalPanelTranslation(float translation) {
-        mNotificationStackScroller.setTranslationX(translation);
-        mQsFrame.setTranslationX(translation);
-        int size = mVerticalTranslationListener.size();
-        for (int i = 0; i < size; i++) {
-            mVerticalTranslationListener.get(i).run();
-        }
-    }
-
-    protected void updateExpandedHeight(float expandedHeight) {
-        if (mTracking) {
-            mNotificationStackScroller.setExpandingVelocity(getCurrentExpandVelocity());
-        }
-        if (mKeyguardBypassController.getBypassEnabled() && isOnKeyguard()) {
-            // The expandedHeight is always the full panel Height when bypassing
-            expandedHeight = getMaxPanelHeightNonBypass();
-        }
-        mNotificationStackScroller.setExpandedHeight(expandedHeight);
-        updateKeyguardBottomAreaAlpha();
-        updateBigClockAlpha();
-        updateStatusBarIcons();
-    }
-
-    /**
-     * @return whether the notifications are displayed full width and don't have any margins on
-     *         the side.
-     */
-    public boolean isFullWidth() {
-        return mIsFullWidth;
-    }
-
-    private void updateStatusBarIcons() {
-        boolean showIconsWhenExpanded = (isPanelVisibleBecauseOfHeadsUp() || isFullWidth())
-                && getExpandedHeight() < getOpeningHeight();
-        if (showIconsWhenExpanded && mNoVisibleNotifications && isOnKeyguard()) {
-            showIconsWhenExpanded = false;
-        }
-        if (showIconsWhenExpanded != mShowIconsWhenExpanded) {
-            mShowIconsWhenExpanded = showIconsWhenExpanded;
-            mCommandQueue.recomputeDisableFlags(mDisplayId, false);
-        }
-    }
-
-    private boolean isOnKeyguard() {
-        return mBarState == StatusBarState.KEYGUARD;
-    }
-
-    public void setPanelScrimMinFraction(float minFraction) {
-        mBar.panelScrimMinFractionChanged(minFraction);
-    }
-
-    public void clearNotificationEffects() {
-        mStatusBar.clearNotificationEffects();
-    }
-
-    @Override
-    protected boolean isPanelVisibleBecauseOfHeadsUp() {
-        return (mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway)
-                && mBarState == StatusBarState.SHADE;
+    public void setDozing(boolean dozing) {
+        mDozing = dozing;
     }
 
     @Override
@@ -3262,382 +88,11 @@
         return !mDozing;
     }
 
-    public void launchCamera(boolean animate, int source) {
-        if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP) {
-            mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP;
-        } else if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE) {
-            mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_WIGGLE;
-        } else if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER) {
-            mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER;
-        } else {
-
-            // Default.
-            mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
-        }
-
-        // If we are launching it when we are occluded already we don't want it to animate,
-        // nor setting these flags, since the occluded state doesn't change anymore, hence it's
-        // never reset.
-        if (!isFullyCollapsed()) {
-            setLaunchingAffordance(true);
-        } else {
-            animate = false;
-        }
-        mAffordanceHasPreview = mKeyguardBottomArea.getRightPreview() != null;
-        mAffordanceHelper.launchAffordance(animate, getLayoutDirection() == LAYOUT_DIRECTION_RTL);
+    void setRtlChangeListener(RtlChangeListener listener) {
+        mRtlChangeListener = listener;
     }
 
-    public void onAffordanceLaunchEnded() {
-        setLaunchingAffordance(false);
-    }
-
-    /**
-     * Set whether we are currently launching an affordance. This is currently only set when
-     * launched via a camera gesture.
-     */
-    private void setLaunchingAffordance(boolean launchingAffordance) {
-        mLaunchingAffordance = launchingAffordance;
-        getLeftIcon().setLaunchingAffordance(launchingAffordance);
-        getRightIcon().setLaunchingAffordance(launchingAffordance);
-        mKeyguardBypassController.setLaunchingAffordance(launchingAffordance);
-        if (mAffordanceLaunchListener != null) {
-            mAffordanceLaunchListener.accept(launchingAffordance);
-        }
-    }
-
-    /**
-     * Return true when a bottom affordance is launching an occluded activity with a splash screen.
-     */
-    public boolean isLaunchingAffordanceWithPreview() {
-        return mLaunchingAffordance && mAffordanceHasPreview;
-    }
-
-    /**
-     * Whether the camera application can be launched for the camera launch gesture.
-     */
-    public boolean canCameraGestureBeLaunched() {
-        if (!mStatusBar.isCameraAllowedByAdmin()) {
-            return false;
-        }
-
-        ResolveInfo resolveInfo = mKeyguardBottomArea.resolveCameraIntent();
-        String packageToLaunch = (resolveInfo == null || resolveInfo.activityInfo == null)
-                ? null : resolveInfo.activityInfo.packageName;
-        return packageToLaunch != null &&
-                (mBarState != StatusBarState.SHADE || !isForegroundApp(packageToLaunch))
-                && !mAffordanceHelper.isSwipingInProgress();
-    }
-
-    /**
-     * Return true if the applications with the package name is running in foreground.
-     *
-     * @param pkgName application package name.
-     */
-    private boolean isForegroundApp(String pkgName) {
-        ActivityManager am = getContext().getSystemService(ActivityManager.class);
-        List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1);
-        return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName());
-    }
-
-    private void setGroupManager(NotificationGroupManager groupManager) {
-        mGroupManager = groupManager;
-    }
-
-    public boolean hideStatusBarIconsWhenExpanded() {
-        if (mLaunchingNotification) {
-            return mHideIconsDuringNotificationLaunch;
-        }
-        if (mHeadsUpAppearanceController != null
-                && mHeadsUpAppearanceController.shouldBeVisible()) {
-            return false;
-        }
-        return !isFullWidth() || !mShowIconsWhenExpanded;
-    }
-
-    private final FragmentListener mFragmentListener = new FragmentListener() {
-        @Override
-        public void onFragmentViewCreated(String tag, Fragment fragment) {
-            mQs = (QS) fragment;
-            mQs.setPanelView(NotificationPanelView.this);
-            mQs.setExpandClickListener(NotificationPanelView.this);
-            mQs.setHeaderClickable(mQsExpansionEnabled);
-            updateQSPulseExpansion();
-            mQs.setOverscrolling(mStackScrollerOverscrolling);
-
-            // recompute internal state when qspanel height changes
-            mQs.getView().addOnLayoutChangeListener(
-                    (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
-                        final int height = bottom - top;
-                        final int oldHeight = oldBottom - oldTop;
-                        if (height != oldHeight) {
-                            onQsHeightChanged();
-                        }
-                    });
-            mNotificationStackScroller.setQsContainer((ViewGroup) mQs.getView());
-            if (mQs instanceof QSFragment) {
-                mKeyguardStatusBar.setQSPanel(((QSFragment) mQs).getQsPanel());
-            }
-            updateQsExpansion();
-        }
-
-        @Override
-        public void onFragmentViewDestroyed(String tag, Fragment fragment) {
-            // Manual handling of fragment lifecycle is only required because this bridges
-            // non-fragment and fragment code. Once we are using a fragment for the notification
-            // panel, mQs will not need to be null cause it will be tied to the same lifecycle.
-            if (fragment == mQs) {
-                mQs = null;
-            }
-        }
-    };
-
-    @Override
-    public void setTouchAndAnimationDisabled(boolean disabled) {
-        super.setTouchAndAnimationDisabled(disabled);
-        if (disabled && mAffordanceHelper.isSwipingInProgress() && !mIsLaunchTransitionRunning) {
-            mAffordanceHelper.reset(false /* animate */);
-        }
-        mNotificationStackScroller.setAnimationsEnabled(!disabled);
-    }
-
-    /**
-     * Sets the dozing state.
-     *
-     * @param dozing {@code true} when dozing.
-     * @param animate if transition should be animated.
-     * @param wakeUpTouchLocation touch event location - if woken up by SLPI sensor.
-     */
-    public void setDozing(boolean dozing, boolean animate, PointF wakeUpTouchLocation) {
-        if (dozing == mDozing) return;
-        mDozing = dozing;
-        mNotificationStackScroller.setDozing(mDozing, animate, wakeUpTouchLocation);
-        mKeyguardBottomArea.setDozing(mDozing, animate);
-
-        if (dozing) {
-            mBottomAreaShadeAlphaAnimator.cancel();
-        }
-
-        if (mBarState == StatusBarState.KEYGUARD
-                || mBarState == StatusBarState.SHADE_LOCKED) {
-            updateDozingVisibilities(animate);
-        }
-
-        final float dozeAmount = dozing ? 1 : 0;
-        mStatusBarStateController.setDozeAmount(dozeAmount, animate);
-    }
-
-    @Override
-    public void onDozeAmountChanged(float linearAmount, float amount) {
-        mInterpolatedDarkAmount = amount;
-        mLinearDarkAmount = linearAmount;
-        mKeyguardStatusView.setDarkAmount(mInterpolatedDarkAmount);
-        mKeyguardBottomArea.setDarkAmount(mInterpolatedDarkAmount);
-        positionClockAndNotifications();
-    }
-
-    public void setPulsing(boolean pulsing) {
-        mPulsing = pulsing;
-        final boolean animatePulse = !mDozeParameters.getDisplayNeedsBlanking()
-                && mDozeParameters.getAlwaysOn();
-        if (animatePulse) {
-            mAnimateNextPositionUpdate = true;
-        }
-        // Do not animate the clock when waking up from a pulse.
-        // The height callback will take care of pushing the clock to the right position.
-        if (!mPulsing && !mDozing) {
-            mAnimateNextPositionUpdate = false;
-        }
-        mNotificationStackScroller.setPulsing(pulsing, animatePulse);
-        mKeyguardStatusView.setPulsing(pulsing);
-    }
-
-    public void setAmbientIndicationBottomPadding(int ambientIndicationBottomPadding) {
-        if (mAmbientIndicationBottomPadding != ambientIndicationBottomPadding) {
-            mAmbientIndicationBottomPadding = ambientIndicationBottomPadding;
-            mStatusBar.updateKeyguardMaxNotifications();
-        }
-    }
-
-    public void dozeTimeTick() {
-        mKeyguardBottomArea.dozeTimeTick();
-        mKeyguardStatusView.dozeTimeTick();
-        if (mInterpolatedDarkAmount > 0) {
-            positionClockAndNotifications();
-        }
-    }
-
-    public void setStatusAccessibilityImportance(int mode) {
-        mKeyguardStatusView.setImportantForAccessibility(mode);
-    }
-
-    /**
-     * TODO: this should be removed.
-     * It's not correct to pass this view forward because other classes will end up adding
-     * children to it. Theme will be out of sync.
-     *
-     * @return bottom area view
-     */
-    public KeyguardBottomAreaView getKeyguardBottomAreaView() {
-        return mKeyguardBottomArea;
-    }
-
-    public void setUserSetupComplete(boolean userSetupComplete) {
-        mUserSetupComplete = userSetupComplete;
-        mKeyguardBottomArea.setUserSetupComplete(userSetupComplete);
-    }
-
-    public void applyExpandAnimationParams(ExpandAnimationParameters params) {
-        mExpandOffset = params != null ? params.getTopChange() : 0;
-        updateQsExpansion();
-        if (params != null) {
-            boolean hideIcons = params.getProgress(
-                    ActivityLaunchAnimator.ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f;
-            if (hideIcons != mHideIconsDuringNotificationLaunch) {
-                mHideIconsDuringNotificationLaunch = hideIcons;
-                if (!hideIcons) {
-                    mCommandQueue.recomputeDisableFlags(mDisplayId, true /* animate */);
-                }
-            }
-        }
-    }
-
-    public void addTrackingHeadsUpListener(Consumer<ExpandableNotificationRow> listener) {
-        mTrackingHeadsUpListeners.add(listener);
-    }
-
-    public void removeTrackingHeadsUpListener(Consumer<ExpandableNotificationRow> listener) {
-        mTrackingHeadsUpListeners.remove(listener);
-    }
-
-    public void addVerticalTranslationListener(Runnable verticalTranslationListener) {
-        mVerticalTranslationListener.add(verticalTranslationListener);
-    }
-
-    public void removeVerticalTranslationListener(Runnable verticalTranslationListener) {
-        mVerticalTranslationListener.remove(verticalTranslationListener);
-    }
-
-    public void setHeadsUpAppearanceController(
-            HeadsUpAppearanceController headsUpAppearanceController) {
-        mHeadsUpAppearanceController = headsUpAppearanceController;
-    }
-
-    /**
-     * Starts the animation before we dismiss Keyguard, i.e. an disappearing animation on the
-     * security view of the bouncer.
-     */
-    public void onBouncerPreHideAnimation() {
-        setKeyguardStatusViewVisibility(mBarState, true /* keyguardFadingAway */,
-                false /* goingToFullShade */);
-    }
-
-    /**
-     * Do not let the user drag the shade up and down for the current touch session.
-     * This is necessary to avoid shade expansion while/after the bouncer is dismissed.
-     */
-    public void blockExpansionForCurrentTouch() {
-        mBlockingExpansionForCurrentTouch = mTracking;
-    }
-
-    @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        super.dump(fd, pw, args);
-        pw.println("    gestureExclusionRect: " + calculateGestureExclusionRect());
-        if (mKeyguardStatusBar != null) {
-            mKeyguardStatusBar.dump(fd, pw, args);
-        }
-        if (mKeyguardStatusView != null) {
-            mKeyguardStatusView.dump(fd, pw, args);
-        }
-    }
-
-    public boolean hasActiveClearableNotifications() {
-        return mNotificationStackScroller.hasActiveClearableNotifications(ROWS_ALL);
-    }
-
-    @Override
-    public void onZenChanged(int zen) {
-        updateShowEmptyShadeView();
-    }
-
-    private void updateShowEmptyShadeView() {
-        boolean showEmptyShadeView =
-                mBarState != StatusBarState.KEYGUARD && !mEntryManager.hasActiveNotifications();
-        showEmptyShadeView(showEmptyShadeView);
-    }
-
-    public RemoteInputController.Delegate createRemoteInputDelegate() {
-        return mNotificationStackScroller.createDelegate();
-    }
-
-    public void updateNotificationViews() {
-        mNotificationStackScroller.updateSectionBoundaries();
-        mNotificationStackScroller.updateSpeedBumpIndex();
-        mNotificationStackScroller.updateFooter();
-        updateShowEmptyShadeView();
-        mNotificationStackScroller.updateIconAreaViews();
-    }
-
-    public void onUpdateRowStates() {
-        mNotificationStackScroller.onUpdateRowStates();
-    }
-
-    public boolean hasPulsingNotifications() {
-        return mNotificationStackScroller.hasPulsingNotifications();
-    }
-
-    public ActivatableNotificationView getActivatedChild() {
-        return mNotificationStackScroller.getActivatedChild();
-    }
-
-    public void setActivatedChild(ActivatableNotificationView o) {
-        mNotificationStackScroller.setActivatedChild(o);
-    }
-
-    public void runAfterAnimationFinished(Runnable r) {
-        mNotificationStackScroller.runAfterAnimationFinished(r);
-    }
-
-    public void setScrollingEnabled(boolean b) {
-        mNotificationStackScroller.setScrollingEnabled(b);
-    }
-
-    public void initDependencies(StatusBar statusBar, NotificationGroupManager groupManager,
-            NotificationShelf notificationShelf,
-            HeadsUpManagerPhone headsUpManager,
-            NotificationIconAreaController notificationIconAreaController,
-            ScrimController scrimController) {
-        setStatusBar(statusBar);
-        setGroupManager(mGroupManager);
-        mNotificationStackScroller.setNotificationPanel(this);
-        mNotificationStackScroller.setIconAreaController(notificationIconAreaController);
-        mNotificationStackScroller.setStatusBar(statusBar);
-        mNotificationStackScroller.setGroupManager(groupManager);
-        mNotificationStackScroller.setShelf(notificationShelf);
-        mNotificationStackScroller.setScrimController(scrimController);
-        updateShowEmptyShadeView();
-    }
-
-    public void showTransientIndication(int id) {
-        mKeyguardIndicationController.showTransientIndication(id);
-    }
-
-    @Override
-    public void onDynamicPrivacyChanged() {
-        // Do not request animation when pulsing or waking up, otherwise the clock wiill be out
-        // of sync with the notification panel.
-        if (mLinearDarkAmount != 0) {
-            return;
-        }
-        mAnimateNextPositionUpdate = true;
-    }
-
-    public void setOnReinflationListener(Runnable onReinflationListener) {
-        mOnReinflationListener = onReinflationListener;
-    }
-
-    public static boolean isQsSplitEnabled() {
-        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.QS_SPLIT_ENABLED, false);
+    interface RtlChangeListener {
+        void onRtlPropertielsChanged(int layoutDirection);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
new file mode 100644
index 0000000..6112ae8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -0,0 +1,3734 @@
+/*
+ * 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.statusbar.phone;
+
+import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
+import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.app.ActivityManager;
+import android.app.Fragment;
+import android.app.StatusBarManager;
+import android.content.Context;
+import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.drawable.Drawable;
+import android.hardware.biometrics.BiometricSourceType;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.MathUtils;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewPropertyAnimator;
+import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.LatencyTracker;
+import com.android.keyguard.KeyguardClockSwitch;
+import com.android.keyguard.KeyguardStatusView;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.DejankUtils;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.DisplayId;
+import com.android.systemui.doze.DozeLog;
+import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.HomeControlsPlugin;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.qs.QSFragment;
+import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FlingAnimationUtils;
+import com.android.systemui.statusbar.GestureRecorder;
+import com.android.systemui.statusbar.KeyguardAffordanceView;
+import com.android.systemui.statusbar.KeyguardIndicationController;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.PulseExpansionHandler;
+import com.android.systemui.statusbar.RemoteInputController;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.stack.AnimationProperties;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
+import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.util.InjectionInflationController;
+import com.android.systemui.util.Utils;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import javax.inject.Inject;
+
+@StatusBarComponent.StatusBarScope
+public class NotificationPanelViewController extends PanelViewController {
+
+    private static final boolean DEBUG = false;
+
+    /**
+     * Fling expanding QS.
+     */
+    private static final int FLING_EXPAND = 0;
+
+    /**
+     * Fling collapsing QS, potentially stopping when QS becomes QQS.
+     */
+    private static final int FLING_COLLAPSE = 1;
+
+    /**
+     * Fling until QS is completely hidden.
+     */
+    private static final int FLING_HIDE = 2;
+    private final DozeParameters mDozeParameters;
+    private final OnHeightChangedListener mOnHeightChangedListener = new OnHeightChangedListener();
+    private final OnClickListener mOnClickListener = new OnClickListener();
+    private final OnOverscrollTopChangedListener
+            mOnOverscrollTopChangedListener =
+            new OnOverscrollTopChangedListener();
+    private final KeyguardAffordanceHelperCallback
+            mKeyguardAffordanceHelperCallback =
+            new KeyguardAffordanceHelperCallback();
+    private final OnEmptySpaceClickListener
+            mOnEmptySpaceClickListener =
+            new OnEmptySpaceClickListener();
+    private final MyOnHeadsUpChangedListener
+            mOnHeadsUpChangedListener =
+            new MyOnHeadsUpChangedListener();
+    private final HeightListener mHeightListener = new HeightListener();
+    private final ZenModeControllerCallback
+            mZenModeControllerCallback =
+            new ZenModeControllerCallback();
+    private final ConfigurationListener mConfigurationListener = new ConfigurationListener();
+    private final StatusBarStateListener mStatusBarStateListener = new StatusBarStateListener();
+    private final ExpansionCallback mExpansionCallback = new ExpansionCallback();
+    private final NotificationPanelView mView;
+    private final MetricsLogger mMetricsLogger;
+    private final ActivityManager mActivityManager;
+    private final ZenModeController mZenModeController;
+    private final ConfigurationController mConfigurationController;
+    private final FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
+
+    private double mQqsSplitFraction;
+
+    // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is
+    // changed.
+    private static final int CAP_HEIGHT = 1456;
+    private static final int FONT_HEIGHT = 2163;
+
+    /**
+     * Maximum time before which we will expand the panel even for slow motions when getting a
+     * touch passed over from launcher.
+     */
+    private static final int MAX_TIME_TO_OPEN_WHEN_FLINGING_FROM_LAUNCHER = 300;
+
+    private static final String COUNTER_PANEL_OPEN = "panel_open";
+    private static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs";
+    private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek";
+
+    private static final Rect M_DUMMY_DIRTY_RECT = new Rect(0, 0, 1, 1);
+    private static final Rect EMPTY_RECT = new Rect();
+
+    private static final AnimationProperties
+            CLOCK_ANIMATION_PROPERTIES =
+            new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+    private final AnimatableProperty KEYGUARD_HEADS_UP_SHOWING_AMOUNT = AnimatableProperty.from(
+            "KEYGUARD_HEADS_UP_SHOWING_AMOUNT",
+            (notificationPanelView, aFloat) -> setKeyguardHeadsUpShowingAmount(aFloat),
+            (Function<NotificationPanelView, Float>) notificationPanelView ->
+                    getKeyguardHeadsUpShowingAmount(),
+            R.id.keyguard_hun_animator_tag, R.id.keyguard_hun_animator_end_tag,
+            R.id.keyguard_hun_animator_start_tag);
+    private static final AnimationProperties
+            KEYGUARD_HUN_PROPERTIES =
+            new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+    @VisibleForTesting
+    final KeyguardUpdateMonitorCallback
+            mKeyguardUpdateCallback =
+            new KeyguardUpdateMonitorCallback() {
+
+                @Override
+                public void onBiometricAuthenticated(int userId,
+                        BiometricSourceType biometricSourceType) {
+                    if (mFirstBypassAttempt && mUpdateMonitor.isUnlockingWithBiometricAllowed()) {
+                        mDelayShowingKeyguardStatusBar = true;
+                    }
+                }
+
+                @Override
+                public void onBiometricRunningStateChanged(boolean running,
+                        BiometricSourceType biometricSourceType) {
+                    boolean
+                            keyguardOrShadeLocked =
+                            mBarState == StatusBarState.KEYGUARD
+                                    || mBarState == StatusBarState.SHADE_LOCKED;
+                    if (!running && mFirstBypassAttempt && keyguardOrShadeLocked && !mDozing
+                            && !mDelayShowingKeyguardStatusBar) {
+                        mFirstBypassAttempt = false;
+                        animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+                    }
+                }
+
+                @Override
+                public void onFinishedGoingToSleep(int why) {
+                    mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
+                    mDelayShowingKeyguardStatusBar = false;
+                }
+            };
+
+    private final InjectionInflationController mInjectionInflationController;
+    private final PowerManager mPowerManager;
+    private final AccessibilityManager mAccessibilityManager;
+    private final NotificationWakeUpCoordinator mWakeUpCoordinator;
+    private final PulseExpansionHandler mPulseExpansionHandler;
+    private final KeyguardBypassController mKeyguardBypassController;
+    private final KeyguardUpdateMonitor mUpdateMonitor;
+
+    private KeyguardAffordanceHelper mAffordanceHelper;
+    private KeyguardUserSwitcher mKeyguardUserSwitcher;
+    private KeyguardStatusBarView mKeyguardStatusBar;
+    private ViewGroup mBigClockContainer;
+    private QS mQs;
+    private FrameLayout mQsFrame;
+    private KeyguardStatusView mKeyguardStatusView;
+    private View mQsNavbarScrim;
+    private NotificationsQuickSettingsContainer mNotificationContainerParent;
+    private NotificationStackScrollLayout mNotificationStackScroller;
+    private FrameLayout mHomeControlsLayout;
+    private boolean mAnimateNextPositionUpdate;
+
+    private int mTrackingPointer;
+    private VelocityTracker mQsVelocityTracker;
+    private boolean mQsTracking;
+
+    /**
+     * If set, the ongoing touch gesture might both trigger the expansion in {@link PanelView} and
+     * the expansion for quick settings.
+     */
+    private boolean mConflictingQsExpansionGesture;
+
+    private boolean mPanelExpanded;
+    private boolean mQsExpanded;
+    private boolean mQsExpandedWhenExpandingStarted;
+    private boolean mQsFullyExpanded;
+    private boolean mKeyguardShowing;
+    private boolean mDozing;
+    private boolean mDozingOnDown;
+    private int mBarState;
+    private float mInitialHeightOnTouch;
+    private float mInitialTouchX;
+    private float mInitialTouchY;
+    private float mQsExpansionHeight;
+    private int mQsMinExpansionHeight;
+    private int mQsMaxExpansionHeight;
+    private int mQsPeekHeight;
+    private boolean mStackScrollerOverscrolling;
+    private boolean mQsExpansionFromOverscroll;
+    private float mLastOverscroll;
+    private boolean mQsExpansionEnabled = true;
+    private ValueAnimator mQsExpansionAnimator;
+    private FlingAnimationUtils mFlingAnimationUtils;
+    private int mStatusBarMinHeight;
+    private int mNotificationsHeaderCollideDistance;
+    private float mEmptyDragAmount;
+    private float mDownX;
+    private float mDownY;
+
+    private final KeyguardClockPositionAlgorithm
+            mClockPositionAlgorithm =
+            new KeyguardClockPositionAlgorithm();
+    private final KeyguardClockPositionAlgorithm.Result
+            mClockPositionResult =
+            new KeyguardClockPositionAlgorithm.Result();
+    private boolean mIsExpanding;
+
+    private boolean mBlockTouches;
+    // Used for two finger gesture as well as accessibility shortcut to QS.
+    private boolean mQsExpandImmediate;
+    private boolean mTwoFingerQsExpandPossible;
+
+    /**
+     * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still
+     * need to take this into account in our panel height calculation.
+     */
+    private boolean mQsAnimatorExpand;
+    private boolean mIsLaunchTransitionFinished;
+    private boolean mIsLaunchTransitionRunning;
+    private Runnable mLaunchAnimationEndRunnable;
+    private boolean mOnlyAffordanceInThisMotion;
+    private boolean mKeyguardStatusViewAnimating;
+    private ValueAnimator mQsSizeChangeAnimator;
+
+    private boolean mShowEmptyShadeView;
+
+    private boolean mQsScrimEnabled = true;
+    private boolean mQsTouchAboveFalsingThreshold;
+    private int mQsFalsingThreshold;
+
+    private float mKeyguardStatusBarAnimateAlpha = 1f;
+    private HeadsUpTouchHelper mHeadsUpTouchHelper;
+    private boolean mListenForHeadsUp;
+    private int mNavigationBarBottomHeight;
+    private boolean mExpandingFromHeadsUp;
+    private boolean mCollapsedOnDown;
+    private int mPositionMinSideMargin;
+    private int mLastOrientation = -1;
+    private boolean mClosingWithAlphaFadeOut;
+    private boolean mHeadsUpAnimatingAway;
+    private boolean mLaunchingAffordance;
+    private boolean mAffordanceHasPreview;
+    private FalsingManager mFalsingManager;
+    private String mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
+
+    private Runnable mHeadsUpExistenceChangedRunnable = () -> {
+        setHeadsUpAnimatingAway(false);
+        notifyBarPanelExpansionChanged();
+    };
+    private NotificationGroupManager mGroupManager;
+    private boolean mShowIconsWhenExpanded;
+    private int mIndicationBottomPadding;
+    private int mAmbientIndicationBottomPadding;
+    private boolean mIsFullWidth;
+    private boolean mBlockingExpansionForCurrentTouch;
+
+    /**
+     * Following variables maintain state of events when input focus transfer may occur.
+     */
+    private boolean mExpectingSynthesizedDown; // expecting to see synthesized DOWN event
+    private boolean mLastEventSynthesizedDown; // last event was synthesized DOWN event
+
+    /**
+     * Current dark amount that follows regular interpolation curve of animation.
+     */
+    private float mInterpolatedDarkAmount;
+
+    /**
+     * Dark amount that animates from 0 to 1 or vice-versa in linear manner, even if the
+     * interpolation curve is different.
+     */
+    private float mLinearDarkAmount;
+
+    private boolean mPulsing;
+    private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
+    private boolean mUserSetupComplete;
+    private int mQsNotificationTopPadding;
+    private float mExpandOffset;
+    private boolean mHideIconsDuringNotificationLaunch = true;
+    private int mStackScrollerMeasuringPass;
+    private ArrayList<Consumer<ExpandableNotificationRow>>
+            mTrackingHeadsUpListeners =
+            new ArrayList<>();
+    private ArrayList<Runnable> mVerticalTranslationListener = new ArrayList<>();
+    private HeadsUpAppearanceController mHeadsUpAppearanceController;
+
+    private int mPanelAlpha;
+    private Runnable mPanelAlphaEndAction;
+    private float mBottomAreaShadeAlpha;
+    private final ValueAnimator mBottomAreaShadeAlphaAnimator;
+    private AnimatorListenerAdapter mAnimatorListenerAdapter = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            if (mPanelAlphaEndAction != null) {
+                mPanelAlphaEndAction.run();
+            }
+        }
+    };
+    private final AnimatableProperty mPanelAlphaAnimator = AnimatableProperty.from("panelAlpha",
+            NotificationPanelView::setPanelAlphaInternal,
+            NotificationPanelView::getCurrentPanelAlpha,
+            R.id.panel_alpha_animator_tag, R.id.panel_alpha_animator_start_tag,
+            R.id.panel_alpha_animator_end_tag);
+    private final AnimationProperties mPanelAlphaOutPropertiesAnimator =
+            new AnimationProperties().setDuration(150).setCustomInterpolator(
+                    mPanelAlphaAnimator.getProperty(), Interpolators.ALPHA_OUT);
+    private final AnimationProperties mPanelAlphaInPropertiesAnimator =
+            new AnimationProperties().setDuration(200).setAnimationFinishListener(
+                    mAnimatorListenerAdapter).setCustomInterpolator(
+                    mPanelAlphaAnimator.getProperty(), Interpolators.ALPHA_IN);
+    private final NotificationEntryManager mEntryManager;
+
+    private final CommandQueue mCommandQueue;
+    private final NotificationLockscreenUserManager mLockscreenUserManager;
+    private final ShadeController mShadeController;
+    private int mDisplayId;
+
+    /**
+     * Cache the resource id of the theme to avoid unnecessary work in onThemeChanged.
+     *
+     * onThemeChanged is forced when the theme might not have changed. So, to avoid unncessary
+     * work, check the current id with the cached id.
+     */
+    private int mThemeResId;
+    private KeyguardIndicationController mKeyguardIndicationController;
+    private Consumer<Boolean> mAffordanceLaunchListener;
+    private int mShelfHeight;
+    private Runnable mOnReinflationListener;
+    private int mDarkIconSize;
+    private int mHeadsUpInset;
+    private boolean mHeadsUpPinnedMode;
+    private float mKeyguardHeadsUpShowingAmount = 0.0f;
+    private boolean mShowingKeyguardHeadsUp;
+    private boolean mAllowExpandForSmallExpansion;
+    private Runnable mExpandAfterLayoutRunnable;
+
+    /**
+     * If face auth with bypass is running for the first time after you turn on the screen.
+     * (From aod or screen off)
+     */
+    private boolean mFirstBypassAttempt;
+    /**
+     * If auth happens successfully during {@code mFirstBypassAttempt}, and we should wait until
+     * the keyguard is dismissed to show the status bar.
+     */
+    private boolean mDelayShowingKeyguardStatusBar;
+
+    private PluginManager mPluginManager;
+    private FrameLayout mPluginFrame;
+    private NPVPluginManager mNPVPluginManager;
+    private int mOldLayoutDirection;
+
+    @Inject
+    public NotificationPanelViewController(NotificationPanelView view,
+            InjectionInflationController injectionInflationController,
+            NotificationWakeUpCoordinator coordinator, PulseExpansionHandler pulseExpansionHandler,
+            DynamicPrivacyController dynamicPrivacyController,
+            KeyguardBypassController bypassController, FalsingManager falsingManager,
+            PluginManager pluginManager, ShadeController shadeController,
+            NotificationLockscreenUserManager notificationLockscreenUserManager,
+            NotificationEntryManager notificationEntryManager,
+            KeyguardStateController keyguardStateController,
+            StatusBarStateController statusBarStateController, DozeLog dozeLog,
+            DozeParameters dozeParameters, CommandQueue commandQueue, VibratorHelper vibratorHelper,
+            LatencyTracker latencyTracker, PowerManager powerManager,
+            AccessibilityManager accessibilityManager, @DisplayId int displayId,
+            KeyguardUpdateMonitor keyguardUpdateMonitor, MetricsLogger metricsLogger,
+            ActivityManager activityManager, ZenModeController zenModeController,
+            ConfigurationController configurationController,
+            FlingAnimationUtils.Builder flingAnimationUtilsBuilder) {
+        super(view, falsingManager, dozeLog, keyguardStateController,
+                (SysuiStatusBarStateController) statusBarStateController, vibratorHelper,
+                latencyTracker, flingAnimationUtilsBuilder);
+        mView = view;
+        mMetricsLogger = metricsLogger;
+        mActivityManager = activityManager;
+        mZenModeController = zenModeController;
+        mConfigurationController = configurationController;
+        mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder;
+        mView.setWillNotDraw(!DEBUG);
+        mInjectionInflationController = injectionInflationController;
+        mFalsingManager = falsingManager;
+        mPowerManager = powerManager;
+        mWakeUpCoordinator = coordinator;
+        mAccessibilityManager = accessibilityManager;
+        mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
+        setPanelAlpha(255, false /* animate */);
+        mCommandQueue = commandQueue;
+        mDisplayId = displayId;
+        mPulseExpansionHandler = pulseExpansionHandler;
+        mDozeParameters = dozeParameters;
+        pulseExpansionHandler.setPulseExpandAbortListener(() -> {
+            if (mQs != null) {
+                mQs.animateHeaderSlidingOut();
+            }
+        });
+        mThemeResId = mView.getContext().getThemeResId();
+        mKeyguardBypassController = bypassController;
+        mUpdateMonitor = keyguardUpdateMonitor;
+        mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
+        KeyguardStateController.Callback
+                keyguardMonitorCallback =
+                new KeyguardStateController.Callback() {
+                    @Override
+                    public void onKeyguardFadingAwayChanged() {
+                        if (!mKeyguardStateController.isKeyguardFadingAway()) {
+                            mFirstBypassAttempt = false;
+                            mDelayShowingKeyguardStatusBar = false;
+                        }
+                    }
+                };
+        mKeyguardStateController.addCallback(keyguardMonitorCallback);
+        DynamicPrivacyControlListener
+                dynamicPrivacyControlListener =
+                new DynamicPrivacyControlListener();
+        dynamicPrivacyController.addListener(dynamicPrivacyControlListener);
+
+        mBottomAreaShadeAlphaAnimator = ValueAnimator.ofFloat(1f, 0);
+        mBottomAreaShadeAlphaAnimator.addUpdateListener(animation -> {
+            mBottomAreaShadeAlpha = (float) animation.getAnimatedValue();
+            updateKeyguardBottomAreaAlpha();
+        });
+        mBottomAreaShadeAlphaAnimator.setDuration(160);
+        mBottomAreaShadeAlphaAnimator.setInterpolator(Interpolators.ALPHA_OUT);
+        mPluginManager = pluginManager;
+        mShadeController = shadeController;
+        mLockscreenUserManager = notificationLockscreenUserManager;
+        mEntryManager = notificationEntryManager;
+
+        mView.setBackgroundColor(Color.TRANSPARENT);
+        OnAttachStateChangeListener onAttachStateChangeListener = new OnAttachStateChangeListener();
+        mView.addOnAttachStateChangeListener(onAttachStateChangeListener);
+        if (mView.isAttachedToWindow()) {
+            onAttachStateChangeListener.onViewAttachedToWindow(mView);
+        }
+
+        mView.setOnApplyWindowInsetsListener(new OnApplyWindowInsetsListener());
+
+        if (DEBUG) {
+            mView.getOverlay().add(new DebugDrawable());
+        }
+
+        onFinishInflate();
+    }
+
+    private void onFinishInflate() {
+        loadDimens();
+        mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header);
+        mKeyguardStatusView = mView.findViewById(R.id.keyguard_status_view);
+
+        KeyguardClockSwitch keyguardClockSwitch = mView.findViewById(R.id.keyguard_clock_container);
+        mBigClockContainer = mView.findViewById(R.id.big_clock_container);
+        keyguardClockSwitch.setBigClockContainer(mBigClockContainer);
+
+        mHomeControlsLayout = mView.findViewById(R.id.home_controls_layout);
+        mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent);
+        mNotificationStackScroller = mView.findViewById(R.id.notification_stack_scroller);
+        mNotificationStackScroller.setOnHeightChangedListener(mOnHeightChangedListener);
+        mNotificationStackScroller.setOverscrollTopChangedListener(mOnOverscrollTopChangedListener);
+        mNotificationStackScroller.setOnEmptySpaceClickListener(mOnEmptySpaceClickListener);
+        addTrackingHeadsUpListener(mNotificationStackScroller::setTrackingHeadsUp);
+        mKeyguardBottomArea = mView.findViewById(R.id.keyguard_bottom_area);
+        mQsNavbarScrim = mView.findViewById(R.id.qs_navbar_scrim);
+        mLastOrientation = mResources.getConfiguration().orientation;
+        mPluginFrame = mView.findViewById(R.id.plugin_frame);
+        if (Settings.System.getInt(mView.getContext().getContentResolver(), "npv_plugin_flag", 0)
+                == 1) {
+            mNPVPluginManager = new NPVPluginManager(mPluginFrame, mPluginManager);
+        }
+
+
+        initBottomArea();
+
+        mWakeUpCoordinator.setStackScroller(mNotificationStackScroller);
+        mQsFrame = mView.findViewById(R.id.qs_frame);
+        mPulseExpansionHandler.setUp(
+                mNotificationStackScroller, mExpansionCallback, mShadeController);
+        mWakeUpCoordinator.addListener(new NotificationWakeUpCoordinator.WakeUpListener() {
+            @Override
+            public void onFullyHiddenChanged(boolean isFullyHidden) {
+                updateKeyguardStatusBarForHeadsUp();
+            }
+
+            @Override
+            public void onPulseExpansionChanged(boolean expandingChanged) {
+                if (mKeyguardBypassController.getBypassEnabled()) {
+                    // Position the notifications while dragging down while pulsing
+                    requestScrollerTopPaddingUpdate(false /* animate */);
+                    updateQSPulseExpansion();
+                }
+            }
+        });
+
+        mPluginManager.addPluginListener(new PluginListener<HomeControlsPlugin>() {
+
+            @Override
+            public void onPluginConnected(HomeControlsPlugin plugin, Context pluginContext) {
+                plugin.sendParentGroup(mHomeControlsLayout);
+            }
+
+            @Override
+            public void onPluginDisconnected(HomeControlsPlugin plugin) {
+
+            }
+        }, HomeControlsPlugin.class, false);
+
+        mView.setRtlChangeListener(layoutDirection -> {
+            if (layoutDirection != mOldLayoutDirection) {
+                mAffordanceHelper.onRtlPropertiesChanged();
+                mOldLayoutDirection = layoutDirection;
+            }
+        });
+    }
+
+    @Override
+    protected void loadDimens() {
+        super.loadDimens();
+        mFlingAnimationUtils = mFlingAnimationUtilsBuilder.reset()
+                .setMaxLengthSeconds(0.4f).build();
+        mStatusBarMinHeight = mResources.getDimensionPixelSize(
+                com.android.internal.R.dimen.status_bar_height);
+        mQsPeekHeight = mResources.getDimensionPixelSize(R.dimen.qs_peek_height);
+        mNotificationsHeaderCollideDistance = mResources.getDimensionPixelSize(
+                R.dimen.header_notifications_collide_distance);
+        mClockPositionAlgorithm.loadDimens(mResources);
+        mQsFalsingThreshold = mResources.getDimensionPixelSize(R.dimen.qs_falsing_threshold);
+        mPositionMinSideMargin = mResources.getDimensionPixelSize(
+                R.dimen.notification_panel_min_side_margin);
+        mIndicationBottomPadding = mResources.getDimensionPixelSize(
+                R.dimen.keyguard_indication_bottom_padding);
+        mQsNotificationTopPadding = mResources.getDimensionPixelSize(
+                R.dimen.qs_notification_padding);
+        mShelfHeight = mResources.getDimensionPixelSize(R.dimen.notification_shelf_height);
+        mDarkIconSize = mResources.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size_dark);
+        int statusbarHeight = mResources.getDimensionPixelSize(
+                com.android.internal.R.dimen.status_bar_height);
+        mHeadsUpInset = statusbarHeight + mResources.getDimensionPixelSize(
+                R.dimen.heads_up_status_bar_padding);
+        mQqsSplitFraction = ((float) mResources.getInteger(R.integer.qqs_split_fraction)) / (
+                mResources.getInteger(R.integer.qqs_split_fraction) + mResources.getInteger(
+                        R.integer.qs_split_fraction));
+    }
+
+    /**
+     * Returns if there's a custom clock being presented.
+     */
+    public boolean hasCustomClock() {
+        return mKeyguardStatusView.hasCustomClock();
+    }
+
+    private void setStatusBar(StatusBar bar) {
+        // TODO: this can be injected.
+        mStatusBar = bar;
+        mKeyguardBottomArea.setStatusBar(mStatusBar);
+    }
+    /**
+     * @see #launchCamera(boolean, int)
+     * @see #setLaunchingAffordance(boolean)
+     */
+    public void setLaunchAffordanceListener(Consumer<Boolean> listener) {
+        mAffordanceLaunchListener = listener;
+    }
+
+    public void updateResources() {
+        int qsWidth = mResources.getDimensionPixelSize(R.dimen.qs_panel_width);
+        int panelGravity = mResources.getInteger(R.integer.notification_panel_layout_gravity);
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mQsFrame.getLayoutParams();
+        if (lp.width != qsWidth || lp.gravity != panelGravity) {
+            lp.width = qsWidth;
+            lp.gravity = panelGravity;
+            mQsFrame.setLayoutParams(lp);
+        }
+
+        int panelWidth = mResources.getDimensionPixelSize(R.dimen.notification_panel_width);
+        lp = (FrameLayout.LayoutParams) mNotificationStackScroller.getLayoutParams();
+        if (lp.width != panelWidth || lp.gravity != panelGravity) {
+            lp.width = panelWidth;
+            lp.gravity = panelGravity;
+            mNotificationStackScroller.setLayoutParams(lp);
+        }
+        int sideMargin = mResources.getDimensionPixelOffset(R.dimen.notification_side_paddings);
+        int topMargin = sideMargin;
+        lp = (FrameLayout.LayoutParams) mPluginFrame.getLayoutParams();
+        if (lp.width != qsWidth || lp.gravity != panelGravity || lp.leftMargin != sideMargin
+                || lp.rightMargin != sideMargin || lp.topMargin != topMargin) {
+            lp.width = qsWidth;
+            lp.gravity = panelGravity;
+            lp.leftMargin = sideMargin;
+            lp.rightMargin = sideMargin;
+            lp.topMargin = topMargin;
+            mPluginFrame.setLayoutParams(lp);
+        }
+    }
+
+    private void reInflateViews() {
+        updateShowEmptyShadeView();
+
+        // Re-inflate the status view group.
+        int index = mView.indexOfChild(mKeyguardStatusView);
+        mView.removeView(mKeyguardStatusView);
+        mKeyguardStatusView = (KeyguardStatusView) mInjectionInflationController.injectable(
+                LayoutInflater.from(mView.getContext())).inflate(
+                R.layout.keyguard_status_view, mView, false);
+        mView.addView(mKeyguardStatusView, index);
+
+        // Re-associate the clock container with the keyguard clock switch.
+        mBigClockContainer.removeAllViews();
+        KeyguardClockSwitch keyguardClockSwitch = mView.findViewById(R.id.keyguard_clock_container);
+        keyguardClockSwitch.setBigClockContainer(mBigClockContainer);
+
+        // Update keyguard bottom area
+        index = mView.indexOfChild(mKeyguardBottomArea);
+        mView.removeView(mKeyguardBottomArea);
+        KeyguardBottomAreaView oldBottomArea = mKeyguardBottomArea;
+        mKeyguardBottomArea = (KeyguardBottomAreaView) mInjectionInflationController.injectable(
+                LayoutInflater.from(mView.getContext())).inflate(
+                R.layout.keyguard_bottom_area, mView, false);
+        mKeyguardBottomArea.initFrom(oldBottomArea);
+        mView.addView(mKeyguardBottomArea, index);
+        initBottomArea();
+        mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
+        mStatusBarStateListener.onDozeAmountChanged(mStatusBarStateController.getDozeAmount(),
+                mStatusBarStateController.getInterpolatedDozeAmount());
+
+        if (mKeyguardStatusBar != null) {
+            mKeyguardStatusBar.onThemeChanged();
+        }
+
+        setKeyguardStatusViewVisibility(mBarState, false, false);
+        setKeyguardBottomAreaVisibility(mBarState, false);
+        if (mOnReinflationListener != null) {
+            mOnReinflationListener.run();
+        }
+        reinflatePluginContainer();
+    }
+
+    private void reinflatePluginContainer() {
+        int index = mView.indexOfChild(mPluginFrame);
+        mView.removeView(mPluginFrame);
+        mPluginFrame = (FrameLayout) mInjectionInflationController.injectable(
+                LayoutInflater.from(mView.getContext())).inflate(
+                R.layout.status_bar_expanded_plugin_frame, mView, false);
+        mView.addView(mPluginFrame, index);
+
+        Resources res = mView.getResources();
+        int qsWidth = res.getDimensionPixelSize(R.dimen.qs_panel_width);
+        int panelGravity = mView.getResources().getInteger(
+                R.integer.notification_panel_layout_gravity);
+        FrameLayout.LayoutParams lp;
+        int sideMargin = res.getDimensionPixelOffset(R.dimen.notification_side_paddings);
+        int topMargin = res.getDimensionPixelOffset(
+                com.android.internal.R.dimen.quick_qs_total_height);
+        if (Utils.useQsMediaPlayer(mView.getContext())) {
+            topMargin = res.getDimensionPixelOffset(
+                    com.android.internal.R.dimen.quick_qs_total_height_with_media);
+        }
+        lp = (FrameLayout.LayoutParams) mPluginFrame.getLayoutParams();
+        if (lp.width != qsWidth || lp.gravity != panelGravity || lp.leftMargin != sideMargin
+                || lp.rightMargin != sideMargin || lp.topMargin != topMargin) {
+            lp.width = qsWidth;
+            lp.gravity = panelGravity;
+            lp.leftMargin = sideMargin;
+            lp.rightMargin = sideMargin;
+            lp.topMargin = topMargin;
+            mPluginFrame.setLayoutParams(lp);
+        }
+
+        if (mNPVPluginManager != null) mNPVPluginManager.replaceFrameLayout(mPluginFrame);
+    }
+
+    private void initBottomArea() {
+        mAffordanceHelper = new KeyguardAffordanceHelper(
+                mKeyguardAffordanceHelperCallback, mView.getContext(), mFalsingManager);
+        mKeyguardBottomArea.setAffordanceHelper(mAffordanceHelper);
+        mKeyguardBottomArea.setStatusBar(mStatusBar);
+        mKeyguardBottomArea.setUserSetupComplete(mUserSetupComplete);
+    }
+
+    public void setKeyguardIndicationController(KeyguardIndicationController indicationController) {
+        mKeyguardIndicationController = indicationController;
+        mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
+    }
+
+    private void updateGestureExclusionRect() {
+        Rect exclusionRect = calculateGestureExclusionRect();
+        mView.setSystemGestureExclusionRects(exclusionRect.isEmpty() ? Collections.EMPTY_LIST
+                : Collections.singletonList(exclusionRect));
+    }
+
+    private Rect calculateGestureExclusionRect() {
+        Rect exclusionRect = null;
+        Region touchableRegion = mHeadsUpManager.calculateTouchableRegion();
+        if (isFullyCollapsed() && touchableRegion != null) {
+            // Note: The heads up manager also calculates the non-pinned touchable region
+            exclusionRect = touchableRegion.getBounds();
+        }
+        return exclusionRect != null ? exclusionRect : EMPTY_RECT;
+    }
+
+    private void setIsFullWidth(boolean isFullWidth) {
+        mIsFullWidth = isFullWidth;
+        mNotificationStackScroller.setIsFullWidth(isFullWidth);
+    }
+
+    private void startQsSizeChangeAnimation(int oldHeight, final int newHeight) {
+        if (mQsSizeChangeAnimator != null) {
+            oldHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
+            mQsSizeChangeAnimator.cancel();
+        }
+        mQsSizeChangeAnimator = ValueAnimator.ofInt(oldHeight, newHeight);
+        mQsSizeChangeAnimator.setDuration(300);
+        mQsSizeChangeAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+        mQsSizeChangeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                requestScrollerTopPaddingUpdate(false /* animate */);
+                requestPanelHeightUpdate();
+                int height = (int) mQsSizeChangeAnimator.getAnimatedValue();
+                mQs.setHeightOverride(height);
+            }
+        });
+        mQsSizeChangeAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mQsSizeChangeAnimator = null;
+            }
+        });
+        mQsSizeChangeAnimator.start();
+    }
+
+    /**
+     * Positions the clock and notifications dynamically depending on how many notifications are
+     * showing.
+     */
+    private void positionClockAndNotifications() {
+        boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending();
+        boolean animateClock = animate || mAnimateNextPositionUpdate;
+        int stackScrollerPadding;
+        if (mBarState != StatusBarState.KEYGUARD) {
+            stackScrollerPadding = getUnlockedStackScrollerPadding();
+        } else {
+            int totalHeight = mView.getHeight();
+            int bottomPadding = Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding);
+            int clockPreferredY = mKeyguardStatusView.getClockPreferredY(totalHeight);
+            boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled();
+            final boolean
+                    hasVisibleNotifications =
+                    !bypassEnabled && mNotificationStackScroller.getVisibleNotificationCount() != 0;
+            mKeyguardStatusView.setHasVisibleNotifications(hasVisibleNotifications);
+            mClockPositionAlgorithm.setup(mStatusBarMinHeight, totalHeight - bottomPadding,
+                    mNotificationStackScroller.getIntrinsicContentHeight(), getExpandedFraction(),
+                    totalHeight, (int) (mKeyguardStatusView.getHeight() - mShelfHeight / 2.0f
+                            - mDarkIconSize / 2.0f), clockPreferredY, hasCustomClock(),
+                    hasVisibleNotifications, mInterpolatedDarkAmount, mEmptyDragAmount,
+                    bypassEnabled, getUnlockedStackScrollerPadding());
+            mClockPositionAlgorithm.run(mClockPositionResult);
+            PropertyAnimator.setProperty(mKeyguardStatusView, AnimatableProperty.X,
+                    mClockPositionResult.clockX, CLOCK_ANIMATION_PROPERTIES, animateClock);
+            PropertyAnimator.setProperty(mKeyguardStatusView, AnimatableProperty.Y,
+                    mClockPositionResult.clockY, CLOCK_ANIMATION_PROPERTIES, animateClock);
+            updateNotificationTranslucency();
+            updateClock();
+            stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded;
+        }
+        mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding);
+        mKeyguardBottomArea.setAntiBurnInOffsetX(mClockPositionResult.clockX);
+
+        mStackScrollerMeasuringPass++;
+        requestScrollerTopPaddingUpdate(animate);
+        mStackScrollerMeasuringPass = 0;
+        mAnimateNextPositionUpdate = false;
+    }
+
+    /**
+     * @return the padding of the stackscroller when unlocked
+     */
+    private int getUnlockedStackScrollerPadding() {
+        return (mQs != null ? mQs.getHeader().getHeight() : 0) + mQsPeekHeight
+                + mQsNotificationTopPadding;
+    }
+
+    /**
+     * @param maximum the maximum to return at most
+     * @return the maximum keyguard notifications that can fit on the screen
+     */
+    public int computeMaxKeyguardNotifications(int maximum) {
+        float minPadding = mClockPositionAlgorithm.getMinStackScrollerPadding();
+        int notificationPadding = Math.max(
+                1, mResources.getDimensionPixelSize(R.dimen.notification_divider_height));
+        NotificationShelf shelf = mNotificationStackScroller.getNotificationShelf();
+        float
+                shelfSize =
+                shelf.getVisibility() == View.GONE ? 0
+                        : shelf.getIntrinsicHeight() + notificationPadding;
+        float
+                availableSpace =
+                mNotificationStackScroller.getHeight() - minPadding - shelfSize - Math.max(
+                        mIndicationBottomPadding, mAmbientIndicationBottomPadding)
+                        - mKeyguardStatusView.getLogoutButtonHeight();
+        int count = 0;
+        for (int i = 0; i < mNotificationStackScroller.getChildCount(); i++) {
+            ExpandableView child = (ExpandableView) mNotificationStackScroller.getChildAt(i);
+            if (!(child instanceof ExpandableNotificationRow)) {
+                continue;
+            }
+            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+            boolean
+                    suppressedSummary =
+                    mGroupManager != null && mGroupManager.isSummaryOfSuppressedGroup(
+                            row.getEntry().getSbn());
+            if (suppressedSummary) {
+                continue;
+            }
+            if (!mLockscreenUserManager.shouldShowOnKeyguard(row.getEntry())) {
+                continue;
+            }
+            if (row.isRemoved()) {
+                continue;
+            }
+            availableSpace -= child.getMinHeight(true /* ignoreTemporaryStates */)
+                    + notificationPadding;
+            if (availableSpace >= 0 && count < maximum) {
+                count++;
+            } else if (availableSpace > -shelfSize) {
+                // if we are exactly the last view, then we can show us still!
+                for (int j = i + 1; j < mNotificationStackScroller.getChildCount(); j++) {
+                    if (mNotificationStackScroller.getChildAt(
+                            j) instanceof ExpandableNotificationRow) {
+                        return count;
+                    }
+                }
+                count++;
+                return count;
+            } else {
+                return count;
+            }
+        }
+        return count;
+    }
+
+    private void updateClock() {
+        if (!mKeyguardStatusViewAnimating) {
+            mKeyguardStatusView.setAlpha(mClockPositionResult.clockAlpha);
+        }
+    }
+
+    public void animateToFullShade(long delay) {
+        mNotificationStackScroller.goToFullShade(delay);
+        mView.requestLayout();
+        mAnimateNextPositionUpdate = true;
+    }
+
+    public void setQsExpansionEnabled(boolean qsExpansionEnabled) {
+        mQsExpansionEnabled = qsExpansionEnabled;
+        if (mQs == null) return;
+        mQs.setHeaderClickable(qsExpansionEnabled);
+    }
+
+    @Override
+    public void resetViews(boolean animate) {
+        mIsLaunchTransitionFinished = false;
+        mBlockTouches = false;
+        if (!mLaunchingAffordance) {
+            mAffordanceHelper.reset(false);
+            mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
+        }
+        mStatusBar.getGutsManager().closeAndSaveGuts(true /* leavebehind */, true /* force */,
+                true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
+        if (animate) {
+            animateCloseQs(true /* animateAway */);
+        } else {
+            closeQs();
+        }
+        mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, animate,
+                !animate /* cancelAnimators */);
+        mNotificationStackScroller.resetScrollPosition();
+    }
+
+    @Override
+    public void collapse(boolean delayed, float speedUpFactor) {
+        if (!canPanelBeCollapsed()) {
+            return;
+        }
+
+        if (mQsExpanded) {
+            mQsExpandImmediate = true;
+            mNotificationStackScroller.setShouldShowShelfOnly(true);
+        }
+        super.collapse(delayed, speedUpFactor);
+    }
+
+    public void closeQs() {
+        cancelQsAnimation();
+        setQsExpansion(mQsMinExpansionHeight);
+    }
+
+    public void cancelAnimation() {
+        mView.animate().cancel();
+    }
+
+
+    /**
+     * Animate QS closing by flinging it.
+     * If QS is expanded, it will collapse into QQS and stop.
+     *
+     * @param animateAway Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
+     */
+    public void animateCloseQs(boolean animateAway) {
+        if (mQsExpansionAnimator != null) {
+            if (!mQsAnimatorExpand) {
+                return;
+            }
+            float height = mQsExpansionHeight;
+            mQsExpansionAnimator.cancel();
+            setQsExpansion(height);
+        }
+        flingSettings(0 /* vel */, animateAway ? FLING_HIDE : FLING_COLLAPSE);
+    }
+
+    public void expandWithQs() {
+        if (mQsExpansionEnabled) {
+            mQsExpandImmediate = true;
+            mNotificationStackScroller.setShouldShowShelfOnly(true);
+        }
+        if (isFullyCollapsed()) {
+            expand(true /* animate */);
+        } else {
+            flingSettings(0 /* velocity */, FLING_EXPAND);
+        }
+    }
+
+    public void expandWithoutQs() {
+        if (isQsExpanded()) {
+            flingSettings(0 /* velocity */, FLING_COLLAPSE);
+        } else {
+            expand(true /* animate */);
+        }
+    }
+
+    @Override
+    public void fling(float vel, boolean expand) {
+        GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder();
+        if (gr != null) {
+            gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel);
+        }
+        super.fling(vel, expand);
+    }
+
+    @Override
+    protected void flingToHeight(float vel, boolean expand, float target,
+            float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
+        mHeadsUpTouchHelper.notifyFling(!expand);
+        setClosingWithAlphaFadeout(!expand && !isOnKeyguard() && getFadeoutAlpha() == 1.0f);
+        super.flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
+    }
+
+
+    private boolean onQsIntercept(MotionEvent event) {
+        int pointerIndex = event.findPointerIndex(mTrackingPointer);
+        if (pointerIndex < 0) {
+            pointerIndex = 0;
+            mTrackingPointer = event.getPointerId(pointerIndex);
+        }
+        final float x = event.getX(pointerIndex);
+        final float y = event.getY(pointerIndex);
+
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                mInitialTouchY = y;
+                mInitialTouchX = x;
+                initVelocityTracker();
+                trackMovement(event);
+                if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
+                    mView.getParent().requestDisallowInterceptTouchEvent(true);
+                }
+                if (mQsExpansionAnimator != null) {
+                    onQsExpansionStarted();
+                    mInitialHeightOnTouch = mQsExpansionHeight;
+                    mQsTracking = true;
+                    mNotificationStackScroller.cancelLongPress();
+                }
+                break;
+            case MotionEvent.ACTION_POINTER_UP:
+                final int upPointer = event.getPointerId(event.getActionIndex());
+                if (mTrackingPointer == upPointer) {
+                    // gesture is ongoing, find a new pointer to track
+                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
+                    mTrackingPointer = event.getPointerId(newIndex);
+                    mInitialTouchX = event.getX(newIndex);
+                    mInitialTouchY = event.getY(newIndex);
+                }
+                break;
+
+            case MotionEvent.ACTION_MOVE:
+                final float h = y - mInitialTouchY;
+                trackMovement(event);
+                if (mQsTracking) {
+
+                    // Already tracking because onOverscrolled was called. We need to update here
+                    // so we don't stop for a frame until the next touch event gets handled in
+                    // onTouchEvent.
+                    setQsExpansion(h + mInitialHeightOnTouch);
+                    trackMovement(event);
+                    return true;
+                }
+                if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)
+                        && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
+                    mQsTracking = true;
+                    onQsExpansionStarted();
+                    notifyExpandingFinished();
+                    mInitialHeightOnTouch = mQsExpansionHeight;
+                    mInitialTouchY = y;
+                    mInitialTouchX = x;
+                    mNotificationStackScroller.cancelLongPress();
+                    return true;
+                }
+                break;
+
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                trackMovement(event);
+                if (mQsTracking) {
+                    flingQsWithCurrentVelocity(y,
+                            event.getActionMasked() == MotionEvent.ACTION_CANCEL);
+                    mQsTracking = false;
+                }
+                break;
+        }
+        return false;
+    }
+
+    @Override
+    protected boolean isInContentBounds(float x, float y) {
+        float stackScrollerX = mNotificationStackScroller.getX();
+        return !mNotificationStackScroller.isBelowLastNotification(x - stackScrollerX, y)
+                && stackScrollerX < x && x < stackScrollerX + mNotificationStackScroller.getWidth();
+    }
+
+    private void initDownStates(MotionEvent event) {
+        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+            mOnlyAffordanceInThisMotion = false;
+            mQsTouchAboveFalsingThreshold = mQsFullyExpanded;
+            mDozingOnDown = isDozing();
+            mDownX = event.getX();
+            mDownY = event.getY();
+            mCollapsedOnDown = isFullyCollapsed();
+            mListenForHeadsUp = mCollapsedOnDown && mHeadsUpManager.hasPinnedHeadsUp();
+            mAllowExpandForSmallExpansion = mExpectingSynthesizedDown;
+            mTouchSlopExceededBeforeDown = mExpectingSynthesizedDown;
+            if (mExpectingSynthesizedDown) {
+                mLastEventSynthesizedDown = true;
+            } else {
+                // down but not synthesized motion event.
+                mLastEventSynthesizedDown = false;
+            }
+        } else {
+            // not down event at all.
+            mLastEventSynthesizedDown = false;
+        }
+    }
+
+    private void flingQsWithCurrentVelocity(float y, boolean isCancelMotionEvent) {
+        float vel = getCurrentQSVelocity();
+        final boolean expandsQs = flingExpandsQs(vel);
+        if (expandsQs) {
+            logQsSwipeDown(y);
+        }
+        flingSettings(vel, expandsQs && !isCancelMotionEvent ? FLING_EXPAND : FLING_COLLAPSE);
+    }
+
+    private void logQsSwipeDown(float y) {
+        float vel = getCurrentQSVelocity();
+        final int
+                gesture =
+                mBarState == StatusBarState.KEYGUARD ? MetricsEvent.ACTION_LS_QS
+                        : MetricsEvent.ACTION_SHADE_QS_PULL;
+        mLockscreenGestureLogger.write(gesture,
+                (int) ((y - mInitialTouchY) / mStatusBar.getDisplayDensity()),
+                (int) (vel / mStatusBar.getDisplayDensity()));
+    }
+
+    private boolean flingExpandsQs(float vel) {
+        if (mFalsingManager.isUnlockingDisabled() || isFalseTouch()) {
+            return false;
+        }
+        if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
+            return getQsExpansionFraction() > 0.5f;
+        } else {
+            return vel > 0;
+        }
+    }
+
+    private boolean isFalseTouch() {
+        if (!mKeyguardAffordanceHelperCallback.needsAntiFalsing()) {
+            return false;
+        }
+        if (mFalsingManager.isClassifierEnabled()) {
+            return mFalsingManager.isFalseTouch();
+        }
+        return !mQsTouchAboveFalsingThreshold;
+    }
+
+    private float getQsExpansionFraction() {
+        return Math.min(
+                1f, (mQsExpansionHeight - mQsMinExpansionHeight) / (mQsMaxExpansionHeight
+                        - mQsMinExpansionHeight));
+    }
+
+    @Override
+    protected boolean shouldExpandWhenNotFlinging() {
+        if (super.shouldExpandWhenNotFlinging()) {
+            return true;
+        }
+        if (mAllowExpandForSmallExpansion) {
+            // When we get a touch that came over from launcher, the velocity isn't always correct
+            // Let's err on expanding if the gesture has been reasonably slow
+            long timeSinceDown = SystemClock.uptimeMillis() - mDownTime;
+            return timeSinceDown <= MAX_TIME_TO_OPEN_WHEN_FLINGING_FROM_LAUNCHER;
+        }
+        return false;
+    }
+
+    @Override
+    protected float getOpeningHeight() {
+        return mNotificationStackScroller.getOpeningHeight();
+    }
+
+
+    private boolean handleQsTouch(MotionEvent event) {
+        final int action = event.getActionMasked();
+        if (action == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f
+                && mBarState != StatusBarState.KEYGUARD && !mQsExpanded && mQsExpansionEnabled) {
+
+            // Down in the empty area while fully expanded - go to QS.
+            mQsTracking = true;
+            mConflictingQsExpansionGesture = true;
+            onQsExpansionStarted();
+            mInitialHeightOnTouch = mQsExpansionHeight;
+            mInitialTouchY = event.getX();
+            mInitialTouchX = event.getY();
+        }
+        if (!isFullyCollapsed()) {
+            handleQsDown(event);
+        }
+        if (!mQsExpandImmediate && mQsTracking) {
+            onQsTouch(event);
+            if (!mConflictingQsExpansionGesture) {
+                return true;
+            }
+        }
+        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
+            mConflictingQsExpansionGesture = false;
+        }
+        if (action == MotionEvent.ACTION_DOWN && isFullyCollapsed() && mQsExpansionEnabled) {
+            mTwoFingerQsExpandPossible = true;
+        }
+        if (mTwoFingerQsExpandPossible && isOpenQsEvent(event) && event.getY(event.getActionIndex())
+                < mStatusBarMinHeight) {
+            mMetricsLogger.count(COUNTER_PANEL_OPEN_QS, 1);
+            mQsExpandImmediate = true;
+            mNotificationStackScroller.setShouldShowShelfOnly(true);
+            requestPanelHeightUpdate();
+
+            // Normally, we start listening when the panel is expanded, but here we need to start
+            // earlier so the state is already up to date when dragging down.
+            setListening(true);
+        }
+        if (isQsSplitEnabled() && !mKeyguardShowing) {
+            if (mQsExpandImmediate) {
+                mNotificationStackScroller.setVisibility(View.GONE);
+                mQsFrame.setVisibility(View.VISIBLE);
+                mHomeControlsLayout.setVisibility(View.VISIBLE);
+            } else {
+                mNotificationStackScroller.setVisibility(View.VISIBLE);
+                mQsFrame.setVisibility(View.GONE);
+                mHomeControlsLayout.setVisibility(View.GONE);
+            }
+        }
+        return false;
+    }
+
+    private boolean isInQsArea(float x, float y) {
+        return (x >= mQsFrame.getX() && x <= mQsFrame.getX() + mQsFrame.getWidth()) && (
+                y <= mNotificationStackScroller.getBottomMostNotificationBottom()
+                        || y <= mQs.getView().getY() + mQs.getView().getHeight());
+    }
+
+    private boolean isOnQsEndArea(float x) {
+        if (!isQsSplitEnabled()) return false;
+        if (mView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR) {
+            return x >= mQsFrame.getX() + mQqsSplitFraction * mQsFrame.getWidth()
+                    && x <= mQsFrame.getX() + mQsFrame.getWidth();
+        } else {
+            return x >= mQsFrame.getX()
+                    && x <= mQsFrame.getX() + (1 - mQqsSplitFraction) * mQsFrame.getWidth();
+        }
+    }
+
+    private boolean isOpenQsEvent(MotionEvent event) {
+        final int pointerCount = event.getPointerCount();
+        final int action = event.getActionMasked();
+
+        final boolean
+                twoFingerDrag =
+                action == MotionEvent.ACTION_POINTER_DOWN && pointerCount == 2;
+
+        final boolean
+                stylusButtonClickDrag =
+                action == MotionEvent.ACTION_DOWN && (event.isButtonPressed(
+                        MotionEvent.BUTTON_STYLUS_PRIMARY) || event.isButtonPressed(
+                        MotionEvent.BUTTON_STYLUS_SECONDARY));
+
+        final boolean
+                mouseButtonClickDrag =
+                action == MotionEvent.ACTION_DOWN && (event.isButtonPressed(
+                        MotionEvent.BUTTON_SECONDARY) || event.isButtonPressed(
+                        MotionEvent.BUTTON_TERTIARY));
+
+        final boolean onHeaderRight = isOnQsEndArea(event.getX());
+
+        return twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag || onHeaderRight;
+    }
+
+    private void handleQsDown(MotionEvent event) {
+        if (event.getActionMasked() == MotionEvent.ACTION_DOWN && shouldQuickSettingsIntercept(
+                event.getX(), event.getY(), -1)) {
+            mFalsingManager.onQsDown();
+            mQsTracking = true;
+            onQsExpansionStarted();
+            mInitialHeightOnTouch = mQsExpansionHeight;
+            mInitialTouchY = event.getX();
+            mInitialTouchX = event.getY();
+
+            // If we interrupt an expansion gesture here, make sure to update the state correctly.
+            notifyExpandingFinished();
+        }
+    }
+
+    /**
+     * Input focus transfer is about to happen.
+     */
+    public void startWaitingForOpenPanelGesture() {
+        if (!isFullyCollapsed()) {
+            return;
+        }
+        mExpectingSynthesizedDown = true;
+        onTrackingStarted();
+        updatePanelExpanded();
+    }
+
+    /**
+     * Called when this view is no longer waiting for input focus transfer.
+     *
+     * There are two scenarios behind this function call. First, input focus transfer
+     * has successfully happened and this view already received synthetic DOWN event.
+     * (mExpectingSynthesizedDown == false). Do nothing.
+     *
+     * Second, before input focus transfer finished, user may have lifted finger
+     * in previous window and this window never received synthetic DOWN event.
+     * (mExpectingSynthesizedDown == true).
+     * In this case, we use the velocity to trigger fling event.
+     *
+     * @param velocity unit is in px / millis
+     */
+    public void stopWaitingForOpenPanelGesture(final float velocity) {
+        if (mExpectingSynthesizedDown) {
+            mExpectingSynthesizedDown = false;
+            maybeVibrateOnOpening();
+            fling(velocity > 1f ? 1000f * velocity : 0, true /* expand */);
+            onTrackingStopped(false);
+        }
+    }
+
+    @Override
+    protected boolean flingExpands(float vel, float vectorVel, float x, float y) {
+        boolean expands = super.flingExpands(vel, vectorVel, x, y);
+
+        // If we are already running a QS expansion, make sure that we keep the panel open.
+        if (mQsExpansionAnimator != null) {
+            expands = true;
+        }
+        return expands;
+    }
+
+    @Override
+    protected boolean shouldGestureWaitForTouchSlop() {
+        if (mExpectingSynthesizedDown) {
+            mExpectingSynthesizedDown = false;
+            return false;
+        }
+        return isFullyCollapsed() || mBarState != StatusBarState.SHADE;
+    }
+
+    @Override
+    protected boolean shouldGestureIgnoreXTouchSlop(float x, float y) {
+        return !mAffordanceHelper.isOnAffordanceIcon(x, y);
+    }
+
+    private void onQsTouch(MotionEvent event) {
+        int pointerIndex = event.findPointerIndex(mTrackingPointer);
+        if (pointerIndex < 0) {
+            pointerIndex = 0;
+            mTrackingPointer = event.getPointerId(pointerIndex);
+        }
+        final float y = event.getY(pointerIndex);
+        final float x = event.getX(pointerIndex);
+        final float h = y - mInitialTouchY;
+
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                mQsTracking = true;
+                mInitialTouchY = y;
+                mInitialTouchX = x;
+                onQsExpansionStarted();
+                mInitialHeightOnTouch = mQsExpansionHeight;
+                initVelocityTracker();
+                trackMovement(event);
+                break;
+
+            case MotionEvent.ACTION_POINTER_UP:
+                final int upPointer = event.getPointerId(event.getActionIndex());
+                if (mTrackingPointer == upPointer) {
+                    // gesture is ongoing, find a new pointer to track
+                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
+                    final float newY = event.getY(newIndex);
+                    final float newX = event.getX(newIndex);
+                    mTrackingPointer = event.getPointerId(newIndex);
+                    mInitialHeightOnTouch = mQsExpansionHeight;
+                    mInitialTouchY = newY;
+                    mInitialTouchX = newX;
+                }
+                break;
+
+            case MotionEvent.ACTION_MOVE:
+                setQsExpansion(h + mInitialHeightOnTouch);
+                if (h >= getFalsingThreshold()) {
+                    mQsTouchAboveFalsingThreshold = true;
+                }
+                trackMovement(event);
+                break;
+
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                mQsTracking = false;
+                mTrackingPointer = -1;
+                trackMovement(event);
+                float fraction = getQsExpansionFraction();
+                if (fraction != 0f || y >= mInitialTouchY) {
+                    flingQsWithCurrentVelocity(y,
+                            event.getActionMasked() == MotionEvent.ACTION_CANCEL);
+                }
+                if (mQsVelocityTracker != null) {
+                    mQsVelocityTracker.recycle();
+                    mQsVelocityTracker = null;
+                }
+                break;
+        }
+    }
+
+    private int getFalsingThreshold() {
+        float factor = mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
+        return (int) (mQsFalsingThreshold * factor);
+    }
+
+    private void setOverScrolling(boolean overscrolling) {
+        mStackScrollerOverscrolling = overscrolling;
+        if (mQs == null) return;
+        mQs.setOverscrolling(overscrolling);
+    }
+
+    private void onQsExpansionStarted() {
+        onQsExpansionStarted(0);
+    }
+
+    protected void onQsExpansionStarted(int overscrollAmount) {
+        cancelQsAnimation();
+        cancelHeightAnimator();
+
+        // Reset scroll position and apply that position to the expanded height.
+        float height = mQsExpansionHeight - overscrollAmount;
+        setQsExpansion(height);
+        requestPanelHeightUpdate();
+        mNotificationStackScroller.checkSnoozeLeavebehind();
+
+        // When expanding QS, let's authenticate the user if possible,
+        // this will speed up notification actions.
+        if (height == 0) {
+            mStatusBar.requestFaceAuth();
+        }
+    }
+
+    private void setQsExpanded(boolean expanded) {
+        boolean changed = mQsExpanded != expanded;
+        if (changed) {
+            mQsExpanded = expanded;
+            updateQsState();
+            requestPanelHeightUpdate();
+            mFalsingManager.setQsExpanded(expanded);
+            mStatusBar.setQsExpanded(expanded);
+            mNotificationContainerParent.setQsExpanded(expanded);
+            mPulseExpansionHandler.setQsExpanded(expanded);
+            mKeyguardBypassController.setQSExpanded(expanded);
+        }
+    }
+
+    private void maybeAnimateBottomAreaAlpha() {
+        mBottomAreaShadeAlphaAnimator.cancel();
+        if (mBarState == StatusBarState.SHADE_LOCKED) {
+            mBottomAreaShadeAlphaAnimator.start();
+        } else {
+            mBottomAreaShadeAlpha = 1f;
+        }
+    }
+
+    private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mKeyguardStatusViewAnimating = false;
+            mKeyguardStatusView.setVisibility(View.INVISIBLE);
+        }
+    };
+
+    private final Runnable mAnimateKeyguardStatusViewGoneEndRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mKeyguardStatusViewAnimating = false;
+            mKeyguardStatusView.setVisibility(View.GONE);
+        }
+    };
+
+    private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mKeyguardStatusViewAnimating = false;
+        }
+    };
+
+    private final Runnable mAnimateKeyguardStatusBarInvisibleEndRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mKeyguardStatusBar.setVisibility(View.INVISIBLE);
+            mKeyguardStatusBar.setAlpha(1f);
+            mKeyguardStatusBarAnimateAlpha = 1f;
+        }
+    };
+
+    private void animateKeyguardStatusBarOut() {
+        ValueAnimator anim = ValueAnimator.ofFloat(mKeyguardStatusBar.getAlpha(), 0f);
+        anim.addUpdateListener(mStatusBarAnimateAlphaListener);
+        anim.setStartDelay(mKeyguardStateController.isKeyguardFadingAway()
+                ? mKeyguardStateController.getKeyguardFadingAwayDelay() : 0);
+
+        long duration;
+        if (mKeyguardStateController.isKeyguardFadingAway()) {
+            duration = mKeyguardStateController.getShortenedFadingAwayDuration();
+        } else {
+            duration = StackStateAnimator.ANIMATION_DURATION_STANDARD;
+        }
+        anim.setDuration(duration);
+
+        anim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+        anim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mAnimateKeyguardStatusBarInvisibleEndRunnable.run();
+            }
+        });
+        anim.start();
+    }
+
+    private final ValueAnimator.AnimatorUpdateListener
+            mStatusBarAnimateAlphaListener =
+            new ValueAnimator.AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    mKeyguardStatusBarAnimateAlpha = (float) animation.getAnimatedValue();
+                    updateHeaderKeyguardAlpha();
+                }
+            };
+
+    private void animateKeyguardStatusBarIn(long duration) {
+        mKeyguardStatusBar.setVisibility(View.VISIBLE);
+        mKeyguardStatusBar.setAlpha(0f);
+        ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
+        anim.addUpdateListener(mStatusBarAnimateAlphaListener);
+        anim.setDuration(duration);
+        anim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+        anim.start();
+    }
+
+    private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mKeyguardBottomArea.setVisibility(View.GONE);
+        }
+    };
+
+    private void setKeyguardBottomAreaVisibility(int statusBarState, boolean goingToFullShade) {
+        mKeyguardBottomArea.animate().cancel();
+        if (goingToFullShade) {
+            mKeyguardBottomArea.animate().alpha(0f).setStartDelay(
+                    mKeyguardStateController.getKeyguardFadingAwayDelay()).setDuration(
+                    mKeyguardStateController.getShortenedFadingAwayDuration()).setInterpolator(
+                    Interpolators.ALPHA_OUT).withEndAction(
+                    mAnimateKeyguardBottomAreaInvisibleEndRunnable).start();
+        } else if (statusBarState == StatusBarState.KEYGUARD
+                || statusBarState == StatusBarState.SHADE_LOCKED) {
+            mKeyguardBottomArea.setVisibility(View.VISIBLE);
+            mKeyguardBottomArea.setAlpha(1f);
+        } else {
+            mKeyguardBottomArea.setVisibility(View.GONE);
+        }
+    }
+
+    private void setKeyguardStatusViewVisibility(int statusBarState, boolean keyguardFadingAway,
+            boolean goingToFullShade) {
+        mKeyguardStatusView.animate().cancel();
+        mKeyguardStatusViewAnimating = false;
+        if ((!keyguardFadingAway && mBarState == StatusBarState.KEYGUARD
+                && statusBarState != StatusBarState.KEYGUARD) || goingToFullShade) {
+            mKeyguardStatusViewAnimating = true;
+            mKeyguardStatusView.animate().alpha(0f).setStartDelay(0).setDuration(
+                    160).setInterpolator(Interpolators.ALPHA_OUT).withEndAction(
+                    mAnimateKeyguardStatusViewGoneEndRunnable);
+            if (keyguardFadingAway) {
+                mKeyguardStatusView.animate().setStartDelay(
+                        mKeyguardStateController.getKeyguardFadingAwayDelay()).setDuration(
+                        mKeyguardStateController.getShortenedFadingAwayDuration()).start();
+            }
+        } else if (mBarState == StatusBarState.SHADE_LOCKED
+                && statusBarState == StatusBarState.KEYGUARD) {
+            mKeyguardStatusView.setVisibility(View.VISIBLE);
+            mKeyguardStatusViewAnimating = true;
+            mKeyguardStatusView.setAlpha(0f);
+            mKeyguardStatusView.animate().alpha(1f).setStartDelay(0).setDuration(
+                    320).setInterpolator(Interpolators.ALPHA_IN).withEndAction(
+                    mAnimateKeyguardStatusViewVisibleEndRunnable);
+        } else if (statusBarState == StatusBarState.KEYGUARD) {
+            if (keyguardFadingAway) {
+                mKeyguardStatusViewAnimating = true;
+                mKeyguardStatusView.animate().alpha(0).translationYBy(
+                        -getHeight() * 0.05f).setInterpolator(
+                        Interpolators.FAST_OUT_LINEAR_IN).setDuration(125).setStartDelay(
+                        0).withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable).start();
+            } else {
+                mKeyguardStatusView.setVisibility(View.VISIBLE);
+                mKeyguardStatusView.setAlpha(1f);
+            }
+        } else {
+            mKeyguardStatusView.setVisibility(View.GONE);
+            mKeyguardStatusView.setAlpha(1f);
+        }
+    }
+
+    private void updateQsState() {
+        mNotificationStackScroller.setQsExpanded(mQsExpanded);
+        mNotificationStackScroller.setScrollingEnabled(
+                mBarState != StatusBarState.KEYGUARD && (!mQsExpanded
+                        || mQsExpansionFromOverscroll));
+        updateEmptyShadeView();
+        if (mNPVPluginManager != null) {
+            mNPVPluginManager.changeVisibility(
+                    (mBarState != StatusBarState.KEYGUARD) ? View.VISIBLE : View.INVISIBLE);
+        }
+        mQsNavbarScrim.setVisibility(
+                mBarState == StatusBarState.SHADE && mQsExpanded && !mStackScrollerOverscrolling
+                        && mQsScrimEnabled ? View.VISIBLE : View.INVISIBLE);
+        if (mKeyguardUserSwitcher != null && mQsExpanded && !mStackScrollerOverscrolling) {
+            mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */);
+        }
+        if (mQs == null) return;
+        mQs.setExpanded(mQsExpanded);
+    }
+
+    private void setQsExpansion(float height) {
+        height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
+        mQsFullyExpanded = height == mQsMaxExpansionHeight && mQsMaxExpansionHeight != 0;
+        if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling
+                && !mDozing) {
+            setQsExpanded(true);
+        } else if (height <= mQsMinExpansionHeight && mQsExpanded) {
+            setQsExpanded(false);
+        }
+        mQsExpansionHeight = height;
+        updateQsExpansion();
+        requestScrollerTopPaddingUpdate(false /* animate */);
+        updateHeaderKeyguardAlpha();
+        if (mBarState == StatusBarState.SHADE_LOCKED || mBarState == StatusBarState.KEYGUARD) {
+            updateKeyguardBottomAreaAlpha();
+            updateBigClockAlpha();
+        }
+        if (mBarState == StatusBarState.SHADE && mQsExpanded && !mStackScrollerOverscrolling
+                && mQsScrimEnabled) {
+            mQsNavbarScrim.setAlpha(getQsExpansionFraction());
+        }
+
+        if (mAccessibilityManager.isEnabled()) {
+            mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
+        }
+
+        if (!mFalsingManager.isUnlockingDisabled() && mQsFullyExpanded
+                && mFalsingManager.shouldEnforceBouncer()) {
+            mStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
+                    false /* dismissShade */, true /* afterKeyguardGone */, false /* deferred */);
+        }
+        for (int i = 0; i < mExpansionListeners.size(); i++) {
+            mExpansionListeners.get(i).onQsExpansionChanged(
+                    mQsMaxExpansionHeight != 0 ? mQsExpansionHeight / mQsMaxExpansionHeight : 0);
+        }
+        if (DEBUG) {
+            mView.invalidate();
+        }
+    }
+
+    protected void updateQsExpansion() {
+        if (mQs == null) return;
+        float qsExpansionFraction = getQsExpansionFraction();
+        mQs.setQsExpansion(qsExpansionFraction, getHeaderTranslation());
+        int heightDiff = mQs.getDesiredHeight() - mQs.getQsMinExpansionHeight();
+        if (mNPVPluginManager != null) {
+            mNPVPluginManager.setExpansion(qsExpansionFraction, getHeaderTranslation(), heightDiff);
+        }
+        mNotificationStackScroller.setQsExpansionFraction(qsExpansionFraction);
+    }
+
+    private String determineAccessibilityPaneTitle() {
+        if (mQs != null && mQs.isCustomizing()) {
+            return mResources.getString(R.string.accessibility_desc_quick_settings_edit);
+        } else if (mQsExpansionHeight != 0.0f && mQsFullyExpanded) {
+            // Upon initialisation when we are not layouted yet we don't want to announce that we
+            // are fully expanded, hence the != 0.0f check.
+            return mResources.getString(R.string.accessibility_desc_quick_settings);
+        } else if (mBarState == StatusBarState.KEYGUARD) {
+            return mResources.getString(R.string.accessibility_desc_lock_screen);
+        } else {
+            return mResources.getString(R.string.accessibility_desc_notification_shade);
+        }
+    }
+
+    private float calculateQsTopPadding() {
+        if (mKeyguardShowing && (mQsExpandImmediate
+                || mIsExpanding && mQsExpandedWhenExpandingStarted)) {
+
+            // Either QS pushes the notifications down when fully expanded, or QS is fully above the
+            // notifications (mostly on tablets). maxNotificationPadding denotes the normal top
+            // padding on Keyguard, maxQsPadding denotes the top padding from the quick settings
+            // panel. We need to take the maximum and linearly interpolate with the panel expansion
+            // for a nice motion.
+            int maxNotificationPadding = getKeyguardNotificationStaticPadding();
+            int maxQsPadding = mQsMaxExpansionHeight + mQsNotificationTopPadding;
+            int max = mBarState == StatusBarState.KEYGUARD ? Math.max(
+                    maxNotificationPadding, maxQsPadding) : maxQsPadding;
+            return (int) MathUtils.lerp((float) mQsMinExpansionHeight, (float) max,
+                    getExpandedFraction());
+        } else if (mQsSizeChangeAnimator != null) {
+            return Math.max(
+                    (int) mQsSizeChangeAnimator.getAnimatedValue(),
+                    getKeyguardNotificationStaticPadding());
+        } else if (mKeyguardShowing) {
+            // We can only do the smoother transition on Keyguard when we also are not collapsing
+            // from a scrolled quick settings.
+            return MathUtils.lerp((float) getKeyguardNotificationStaticPadding(),
+                    (float) (mQsMaxExpansionHeight + mQsNotificationTopPadding),
+                    getQsExpansionFraction());
+        } else {
+            return mQsExpansionHeight + mQsNotificationTopPadding;
+        }
+    }
+
+    /**
+     * @return the topPadding of notifications when on keyguard not respecting quick settings
+     * expansion
+     */
+    private int getKeyguardNotificationStaticPadding() {
+        if (!mKeyguardShowing) {
+            return 0;
+        }
+        if (!mKeyguardBypassController.getBypassEnabled()) {
+            return mClockPositionResult.stackScrollerPadding;
+        }
+        int collapsedPosition = mHeadsUpInset;
+        if (!mNotificationStackScroller.isPulseExpanding()) {
+            return collapsedPosition;
+        } else {
+            int expandedPosition = mClockPositionResult.stackScrollerPadding;
+            return (int) MathUtils.lerp(collapsedPosition, expandedPosition,
+                    mNotificationStackScroller.calculateAppearFractionBypass());
+        }
+    }
+
+
+    protected void requestScrollerTopPaddingUpdate(boolean animate) {
+        mNotificationStackScroller.updateTopPadding(calculateQsTopPadding(), animate);
+        if (mKeyguardShowing && mKeyguardBypassController.getBypassEnabled()) {
+            // update the position of the header
+            updateQsExpansion();
+        }
+    }
+
+
+    private void updateQSPulseExpansion() {
+        if (mQs != null) {
+            mQs.setShowCollapsedOnKeyguard(
+                    mKeyguardShowing && mKeyguardBypassController.getBypassEnabled()
+                            && mNotificationStackScroller.isPulseExpanding());
+        }
+    }
+
+    private void trackMovement(MotionEvent event) {
+        if (mQsVelocityTracker != null) mQsVelocityTracker.addMovement(event);
+    }
+
+    private void initVelocityTracker() {
+        if (mQsVelocityTracker != null) {
+            mQsVelocityTracker.recycle();
+        }
+        mQsVelocityTracker = VelocityTracker.obtain();
+    }
+
+    private float getCurrentQSVelocity() {
+        if (mQsVelocityTracker == null) {
+            return 0;
+        }
+        mQsVelocityTracker.computeCurrentVelocity(1000);
+        return mQsVelocityTracker.getYVelocity();
+    }
+
+    private void cancelQsAnimation() {
+        if (mQsExpansionAnimator != null) {
+            mQsExpansionAnimator.cancel();
+        }
+    }
+
+    /**
+     * @see #flingSettings(float, int, Runnable, boolean)
+     */
+    public void flingSettings(float vel, int type) {
+        flingSettings(vel, type, null, false /* isClick */);
+    }
+
+    /**
+     * Animates QS or QQS as if the user had swiped up or down.
+     *
+     * @param vel              Finger velocity or 0 when not initiated by touch events.
+     * @param type             Either {@link #FLING_EXPAND}, {@link #FLING_COLLAPSE} or {@link
+     *                         #FLING_HIDE}.
+     * @param onFinishRunnable Runnable to be executed at the end of animation.
+     * @param isClick          If originated by click (different interpolator and duration.)
+     */
+    protected void flingSettings(float vel, int type, final Runnable onFinishRunnable,
+            boolean isClick) {
+        float target;
+        switch (type) {
+            case FLING_EXPAND:
+                target = mQsMaxExpansionHeight;
+                break;
+            case FLING_COLLAPSE:
+                target = mQsMinExpansionHeight;
+                break;
+            case FLING_HIDE:
+            default:
+                target = 0;
+        }
+        if (target == mQsExpansionHeight) {
+            if (onFinishRunnable != null) {
+                onFinishRunnable.run();
+            }
+            return;
+        }
+
+        // If we move in the opposite direction, reset velocity and use a different duration.
+        boolean oppositeDirection = false;
+        boolean expanding = type == FLING_EXPAND;
+        if (vel > 0 && !expanding || vel < 0 && expanding) {
+            vel = 0;
+            oppositeDirection = true;
+        }
+        ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target);
+        if (isClick) {
+            animator.setInterpolator(Interpolators.TOUCH_RESPONSE);
+            animator.setDuration(368);
+        } else {
+            mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel);
+        }
+        if (oppositeDirection) {
+            animator.setDuration(350);
+        }
+        animator.addUpdateListener(animation -> {
+            setQsExpansion((Float) animation.getAnimatedValue());
+        });
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mNotificationStackScroller.resetCheckSnoozeLeavebehind();
+                mQsExpansionAnimator = null;
+                if (onFinishRunnable != null) {
+                    onFinishRunnable.run();
+                }
+            }
+        });
+        animator.start();
+        mQsExpansionAnimator = animator;
+        mQsAnimatorExpand = expanding;
+    }
+
+    /**
+     * @return Whether we should intercept a gesture to open Quick Settings.
+     */
+    private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) {
+        if (!mQsExpansionEnabled || mCollapsedOnDown || (mKeyguardShowing
+                && mKeyguardBypassController.getBypassEnabled())) {
+            return false;
+        }
+        View header = mKeyguardShowing || mQs == null ? mKeyguardStatusBar : mQs.getHeader();
+        final boolean
+                onHeader =
+                x >= mQsFrame.getX() && x <= mQsFrame.getX() + mQsFrame.getWidth()
+                        && y >= header.getTop() && y <= header.getBottom();
+        if (mQsExpanded) {
+            return onHeader || (yDiff < 0 && isInQsArea(x, y));
+        } else {
+            return onHeader;
+        }
+    }
+
+    @Override
+    protected boolean isScrolledToBottom() {
+        if (!isInSettings()) {
+            return mBarState == StatusBarState.KEYGUARD
+                    || mNotificationStackScroller.isScrolledToBottom();
+        } else {
+            return true;
+        }
+    }
+
+    @Override
+    protected int getMaxPanelHeight() {
+        if (mKeyguardBypassController.getBypassEnabled() && mBarState == StatusBarState.KEYGUARD) {
+            return getMaxPanelHeightBypass();
+        } else {
+            return getMaxPanelHeightNonBypass();
+        }
+    }
+
+    private int getMaxPanelHeightNonBypass() {
+        int min = mStatusBarMinHeight;
+        if (!(mBarState == StatusBarState.KEYGUARD)
+                && mNotificationStackScroller.getNotGoneChildCount() == 0) {
+            int minHeight = (int) (mQsMinExpansionHeight + getOverExpansionAmount());
+            min = Math.max(min, minHeight);
+        }
+        int maxHeight;
+        if (mQsExpandImmediate || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted
+                || mPulsing) {
+            maxHeight = calculatePanelHeightQsExpanded();
+        } else {
+            maxHeight = calculatePanelHeightShade();
+        }
+        maxHeight = Math.max(maxHeight, min);
+        return maxHeight;
+    }
+
+    private int getMaxPanelHeightBypass() {
+        int position =
+                mClockPositionAlgorithm.getExpandedClockPosition()
+                        + mKeyguardStatusView.getHeight();
+        if (mNotificationStackScroller.getVisibleNotificationCount() != 0) {
+            position += mShelfHeight / 2.0f + mDarkIconSize / 2.0f;
+        }
+        return position;
+    }
+
+    public boolean isInSettings() {
+        return mQsExpanded;
+    }
+
+    public boolean isExpanding() {
+        return mIsExpanding;
+    }
+
+    @Override
+    protected void onHeightUpdated(float expandedHeight) {
+        if (!mQsExpanded || mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted) {
+            // Updating the clock position will set the top padding which might
+            // trigger a new panel height and re-position the clock.
+            // This is a circular dependency and should be avoided, otherwise we'll have
+            // a stack overflow.
+            if (mStackScrollerMeasuringPass > 2) {
+                if (DEBUG) Log.d(TAG, "Unstable notification panel height. Aborting.");
+            } else {
+                positionClockAndNotifications();
+            }
+        }
+        if (mQsExpandImmediate || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
+                && !mQsExpansionFromOverscroll) {
+            float t;
+            if (mKeyguardShowing) {
+
+                // On Keyguard, interpolate the QS expansion linearly to the panel expansion
+                t = expandedHeight / (getMaxPanelHeight());
+            } else {
+                // In Shade, interpolate linearly such that QS is closed whenever panel height is
+                // minimum QS expansion + minStackHeight
+                float
+                        panelHeightQsCollapsed =
+                        mNotificationStackScroller.getIntrinsicPadding()
+                                + mNotificationStackScroller.getLayoutMinHeight();
+                float panelHeightQsExpanded = calculatePanelHeightQsExpanded();
+                t =
+                        (expandedHeight - panelHeightQsCollapsed) / (panelHeightQsExpanded
+                                - panelHeightQsCollapsed);
+            }
+            float
+                    targetHeight =
+                    mQsMinExpansionHeight + t * (mQsMaxExpansionHeight - mQsMinExpansionHeight);
+            setQsExpansion(targetHeight);
+            mHomeControlsLayout.setTranslationY(targetHeight);
+        }
+        updateExpandedHeight(expandedHeight);
+        updateHeader();
+        updateNotificationTranslucency();
+        updatePanelExpanded();
+        updateGestureExclusionRect();
+        if (DEBUG) {
+            mView.invalidate();
+        }
+    }
+
+    private void updatePanelExpanded() {
+        boolean isExpanded = !isFullyCollapsed() || mExpectingSynthesizedDown;
+        if (mPanelExpanded != isExpanded) {
+            mHeadsUpManager.setIsPanelExpanded(isExpanded);
+            mStatusBar.setPanelExpanded(isExpanded);
+            mPanelExpanded = isExpanded;
+        }
+    }
+
+    private int calculatePanelHeightShade() {
+        int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin();
+        int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin;
+        maxHeight += mNotificationStackScroller.getTopPaddingOverflow();
+
+        if (mBarState == StatusBarState.KEYGUARD) {
+            int
+                    minKeyguardPanelBottom =
+                    mClockPositionAlgorithm.getExpandedClockPosition()
+                            + mKeyguardStatusView.getHeight()
+                            + mNotificationStackScroller.getIntrinsicContentHeight();
+            return Math.max(maxHeight, minKeyguardPanelBottom);
+        } else {
+            return maxHeight;
+        }
+    }
+
+    private int calculatePanelHeightQsExpanded() {
+        float
+                notificationHeight =
+                mNotificationStackScroller.getHeight()
+                        - mNotificationStackScroller.getEmptyBottomMargin()
+                        - mNotificationStackScroller.getTopPadding();
+
+        // When only empty shade view is visible in QS collapsed state, simulate that we would have
+        // it in expanded QS state as well so we don't run into troubles when fading the view in/out
+        // and expanding/collapsing the whole panel from/to quick settings.
+        if (mNotificationStackScroller.getNotGoneChildCount() == 0 && mShowEmptyShadeView) {
+            notificationHeight = mNotificationStackScroller.getEmptyShadeViewHeight();
+        }
+        int maxQsHeight = mQsMaxExpansionHeight;
+
+        if (mKeyguardShowing) {
+            maxQsHeight += mQsNotificationTopPadding;
+        }
+
+        // If an animation is changing the size of the QS panel, take the animated value.
+        if (mQsSizeChangeAnimator != null) {
+            maxQsHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
+        }
+        float totalHeight = Math.max(maxQsHeight,
+                mBarState == StatusBarState.KEYGUARD ? mClockPositionResult.stackScrollerPadding
+                        : 0) + notificationHeight
+                + mNotificationStackScroller.getTopPaddingOverflow();
+        if (totalHeight > mNotificationStackScroller.getHeight()) {
+            float
+                    fullyCollapsedHeight =
+                    maxQsHeight + mNotificationStackScroller.getLayoutMinHeight();
+            totalHeight = Math.max(fullyCollapsedHeight, mNotificationStackScroller.getHeight());
+        }
+        return (int) totalHeight;
+    }
+
+    private void updateNotificationTranslucency() {
+        float alpha = 1f;
+        if (mClosingWithAlphaFadeOut && !mExpandingFromHeadsUp
+                && !mHeadsUpManager.hasPinnedHeadsUp()) {
+            alpha = getFadeoutAlpha();
+        }
+        if (mBarState == StatusBarState.KEYGUARD && !mHintAnimationRunning
+                && !mKeyguardBypassController.getBypassEnabled()) {
+            alpha *= mClockPositionResult.clockAlpha;
+        }
+        mNotificationStackScroller.setAlpha(alpha);
+    }
+
+    private float getFadeoutAlpha() {
+        float alpha;
+        if (mQsMinExpansionHeight == 0) {
+            return 1.0f;
+        }
+        alpha = getExpandedHeight() / mQsMinExpansionHeight;
+        alpha = Math.max(0, Math.min(alpha, 1));
+        alpha = (float) Math.pow(alpha, 0.75);
+        return alpha;
+    }
+
+    @Override
+    protected float getOverExpansionAmount() {
+        return mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */);
+    }
+
+    @Override
+    protected float getOverExpansionPixels() {
+        return mNotificationStackScroller.getCurrentOverScrolledPixels(true /* top */);
+    }
+
+    /**
+     * Hides the header when notifications are colliding with it.
+     */
+    private void updateHeader() {
+        if (mBarState == StatusBarState.KEYGUARD) {
+            updateHeaderKeyguardAlpha();
+        }
+        updateQsExpansion();
+    }
+
+    protected float getHeaderTranslation() {
+        if (mBarState == StatusBarState.KEYGUARD && !mKeyguardBypassController.getBypassEnabled()) {
+            return -mQs.getQsMinExpansionHeight();
+        }
+        float appearAmount = mNotificationStackScroller.calculateAppearFraction(mExpandedHeight);
+        float startHeight = -mQsExpansionHeight;
+        if (mKeyguardBypassController.getBypassEnabled() && isOnKeyguard()
+                && mNotificationStackScroller.isPulseExpanding()) {
+            if (!mPulseExpansionHandler.isExpanding()
+                    && !mPulseExpansionHandler.getLeavingLockscreen()) {
+                // If we aborted the expansion we need to make sure the header doesn't reappear
+                // again after the header has animated away
+                appearAmount = 0;
+            } else {
+                appearAmount = mNotificationStackScroller.calculateAppearFractionBypass();
+            }
+            startHeight = -mQs.getQsMinExpansionHeight();
+            if (mNPVPluginManager != null) startHeight -= mNPVPluginManager.getHeight();
+        }
+        float translation = MathUtils.lerp(startHeight, 0, Math.min(1.0f, appearAmount))
+                + mExpandOffset;
+        return Math.min(0, translation);
+    }
+
+    /**
+     * @return the alpha to be used to fade out the contents on Keyguard (status bar, bottom area)
+     * during swiping up
+     */
+    private float getKeyguardContentsAlpha() {
+        float alpha;
+        if (mBarState == StatusBarState.KEYGUARD) {
+
+            // When on Keyguard, we hide the header as soon as we expanded close enough to the
+            // header
+            alpha =
+                    getExpandedHeight() / (mKeyguardStatusBar.getHeight()
+                            + mNotificationsHeaderCollideDistance);
+        } else {
+
+            // In SHADE_LOCKED, the top card is already really close to the header. Hide it as
+            // soon as we start translating the stack.
+            alpha = getExpandedHeight() / mKeyguardStatusBar.getHeight();
+        }
+        alpha = MathUtils.saturate(alpha);
+        alpha = (float) Math.pow(alpha, 0.75);
+        return alpha;
+    }
+
+    private void updateHeaderKeyguardAlpha() {
+        if (!mKeyguardShowing) {
+            return;
+        }
+        float alphaQsExpansion = 1 - Math.min(1, getQsExpansionFraction() * 2);
+        float newAlpha = Math.min(getKeyguardContentsAlpha(), alphaQsExpansion)
+                * mKeyguardStatusBarAnimateAlpha;
+        newAlpha *= 1.0f - mKeyguardHeadsUpShowingAmount;
+        mKeyguardStatusBar.setAlpha(newAlpha);
+        boolean
+                hideForBypass =
+                mFirstBypassAttempt && mUpdateMonitor.shouldListenForFace()
+                        || mDelayShowingKeyguardStatusBar;
+        mKeyguardStatusBar.setVisibility(
+                newAlpha != 0f && !mDozing && !hideForBypass ? View.VISIBLE : View.INVISIBLE);
+    }
+
+    private void updateKeyguardBottomAreaAlpha() {
+        // There are two possible panel expansion behaviors:
+        // • User dragging up to unlock: we want to fade out as quick as possible
+        //   (ALPHA_EXPANSION_THRESHOLD) to avoid seeing the bouncer over the bottom area.
+        // • User tapping on lock screen: bouncer won't be visible but panel expansion will
+        //   change due to "unlock hint animation." In this case, fading out the bottom area
+        //   would also hide the message that says "swipe to unlock," we don't want to do that.
+        float expansionAlpha = MathUtils.map(
+                isUnlockHintRunning() ? 0 : KeyguardBouncer.ALPHA_EXPANSION_THRESHOLD, 1f, 0f, 1f,
+                getExpandedFraction());
+        float alpha = Math.min(expansionAlpha, 1 - getQsExpansionFraction());
+        alpha *= mBottomAreaShadeAlpha;
+        mKeyguardBottomArea.setAffordanceAlpha(alpha);
+        mKeyguardBottomArea.setImportantForAccessibility(
+                alpha == 0f ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                        : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+        View ambientIndicationContainer = mStatusBar.getAmbientIndicationContainer();
+        if (ambientIndicationContainer != null) {
+            ambientIndicationContainer.setAlpha(alpha);
+        }
+    }
+
+    /**
+     * Custom clock fades away when user drags up to unlock or pulls down quick settings.
+     *
+     * Updates alpha of custom clock to match the alpha of the KeyguardBottomArea. See
+     * {@link #updateKeyguardBottomAreaAlpha}.
+     */
+    private void updateBigClockAlpha() {
+        float expansionAlpha = MathUtils.map(
+                isUnlockHintRunning() ? 0 : KeyguardBouncer.ALPHA_EXPANSION_THRESHOLD, 1f, 0f, 1f,
+                getExpandedFraction());
+        float alpha = Math.min(expansionAlpha, 1 - getQsExpansionFraction());
+        mBigClockContainer.setAlpha(alpha);
+    }
+
+    @Override
+    protected void onExpandingStarted() {
+        super.onExpandingStarted();
+        mNotificationStackScroller.onExpansionStarted();
+        mIsExpanding = true;
+        mQsExpandedWhenExpandingStarted = mQsFullyExpanded;
+        if (mQsExpanded) {
+            onQsExpansionStarted();
+        }
+        // Since there are QS tiles in the header now, we need to make sure we start listening
+        // immediately so they can be up to date.
+        if (mQs == null) return;
+        mQs.setHeaderListening(true);
+    }
+
+    @Override
+    protected void onExpandingFinished() {
+        super.onExpandingFinished();
+        mNotificationStackScroller.onExpansionStopped();
+        mHeadsUpManager.onExpandingFinished();
+        mIsExpanding = false;
+        if (isFullyCollapsed()) {
+            DejankUtils.postAfterTraversal(new Runnable() {
+                @Override
+                public void run() {
+                    setListening(false);
+                }
+            });
+
+            // Workaround b/22639032: Make sure we invalidate something because else RenderThread
+            // thinks we are actually drawing a frame put in reality we don't, so RT doesn't go
+            // ahead with rendering and we jank.
+            mView.postOnAnimation(new Runnable() {
+                @Override
+                public void run() {
+                    mView.getParent().invalidateChild(mView, M_DUMMY_DIRTY_RECT);
+                }
+            });
+        } else {
+            setListening(true);
+        }
+        mQsExpandImmediate = false;
+        mNotificationStackScroller.setShouldShowShelfOnly(false);
+        mTwoFingerQsExpandPossible = false;
+        notifyListenersTrackingHeadsUp(null);
+        mExpandingFromHeadsUp = false;
+        setPanelScrimMinFraction(0.0f);
+    }
+
+    private void notifyListenersTrackingHeadsUp(ExpandableNotificationRow pickedChild) {
+        for (int i = 0; i < mTrackingHeadsUpListeners.size(); i++) {
+            Consumer<ExpandableNotificationRow> listener = mTrackingHeadsUpListeners.get(i);
+            listener.accept(pickedChild);
+        }
+    }
+
+    private void setListening(boolean listening) {
+        mKeyguardStatusBar.setListening(listening);
+        if (mQs == null) return;
+        mQs.setListening(listening);
+        if (mNPVPluginManager != null) mNPVPluginManager.setListening(listening);
+    }
+
+    @Override
+    public void expand(boolean animate) {
+        super.expand(animate);
+        setListening(true);
+    }
+
+    @Override
+    protected void setOverExpansion(float overExpansion, boolean isPixels) {
+        if (mConflictingQsExpansionGesture || mQsExpandImmediate) {
+            return;
+        }
+        if (mBarState != StatusBarState.KEYGUARD) {
+            mNotificationStackScroller.setOnHeightChangedListener(null);
+            if (isPixels) {
+                mNotificationStackScroller.setOverScrolledPixels(overExpansion, true /* onTop */,
+                        false /* animate */);
+            } else {
+                mNotificationStackScroller.setOverScrollAmount(overExpansion, true /* onTop */,
+                        false /* animate */);
+            }
+            mNotificationStackScroller.setOnHeightChangedListener(mOnHeightChangedListener);
+        }
+    }
+
+    @Override
+    protected void onTrackingStarted() {
+        mFalsingManager.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen());
+        super.onTrackingStarted();
+        if (mQsFullyExpanded) {
+            mQsExpandImmediate = true;
+            mNotificationStackScroller.setShouldShowShelfOnly(true);
+        }
+        if (mBarState == StatusBarState.KEYGUARD || mBarState == StatusBarState.SHADE_LOCKED) {
+            mAffordanceHelper.animateHideLeftRightIcon();
+        }
+        mNotificationStackScroller.onPanelTrackingStarted();
+    }
+
+    @Override
+    protected void onTrackingStopped(boolean expand) {
+        mFalsingManager.onTrackingStopped();
+        super.onTrackingStopped(expand);
+        if (expand) {
+            mNotificationStackScroller.setOverScrolledPixels(0.0f, true /* onTop */,
+                    true /* animate */);
+        }
+        mNotificationStackScroller.onPanelTrackingStopped();
+        if (expand && (mBarState == StatusBarState.KEYGUARD
+                || mBarState == StatusBarState.SHADE_LOCKED)) {
+            if (!mHintAnimationRunning) {
+                mAffordanceHelper.reset(true);
+            }
+        }
+    }
+
+    private void updateMaxHeadsUpTranslation() {
+        mNotificationStackScroller.setHeadsUpBoundaries(getHeight(), mNavigationBarBottomHeight);
+    }
+
+    @Override
+    protected void startUnlockHintAnimation() {
+        if (mPowerManager.isPowerSaveMode()) {
+            onUnlockHintStarted();
+            onUnlockHintFinished();
+            return;
+        }
+        super.startUnlockHintAnimation();
+    }
+
+    @Override
+    protected void onUnlockHintFinished() {
+        super.onUnlockHintFinished();
+        mNotificationStackScroller.setUnlockHintRunning(false);
+    }
+
+    @Override
+    protected void onUnlockHintStarted() {
+        super.onUnlockHintStarted();
+        mNotificationStackScroller.setUnlockHintRunning(true);
+    }
+
+    @Override
+    protected float getPeekHeight() {
+        if (mNotificationStackScroller.getNotGoneChildCount() > 0) {
+            return mNotificationStackScroller.getPeekHeight();
+        } else {
+            return mQsMinExpansionHeight;
+        }
+    }
+
+    @Override
+    protected boolean shouldUseDismissingAnimation() {
+        return mBarState != StatusBarState.SHADE && (mKeyguardStateController.canDismissLockScreen()
+                || !isTracking());
+    }
+
+    @Override
+    protected boolean fullyExpandedClearAllVisible() {
+        return mNotificationStackScroller.isFooterViewNotGone()
+                && mNotificationStackScroller.isScrolledToBottom() && !mQsExpandImmediate;
+    }
+
+    @Override
+    protected boolean isClearAllVisible() {
+        return mNotificationStackScroller.isFooterViewContentVisible();
+    }
+
+    @Override
+    protected int getClearAllHeight() {
+        return mNotificationStackScroller.getFooterViewHeight();
+    }
+
+    @Override
+    protected boolean isTrackingBlocked() {
+        return mConflictingQsExpansionGesture && mQsExpanded || mBlockingExpansionForCurrentTouch;
+    }
+
+    public boolean isQsExpanded() {
+        return mQsExpanded;
+    }
+
+    public boolean isQsDetailShowing() {
+        return mQs.isShowingDetail();
+    }
+
+    public void closeQsDetail() {
+        mQs.closeDetail();
+    }
+
+    public boolean isLaunchTransitionFinished() {
+        return mIsLaunchTransitionFinished;
+    }
+
+    public boolean isLaunchTransitionRunning() {
+        return mIsLaunchTransitionRunning;
+    }
+
+    public void setLaunchTransitionEndRunnable(Runnable r) {
+        mLaunchAnimationEndRunnable = r;
+    }
+
+    private void updateDozingVisibilities(boolean animate) {
+        mKeyguardBottomArea.setDozing(mDozing, animate);
+        if (!mDozing && animate) {
+            animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+        }
+    }
+
+    @Override
+    public boolean isDozing() {
+        return mDozing;
+    }
+
+    public void showEmptyShadeView(boolean emptyShadeViewVisible) {
+        mShowEmptyShadeView = emptyShadeViewVisible;
+        updateEmptyShadeView();
+    }
+
+    private void updateEmptyShadeView() {
+        // Hide "No notifications" in QS.
+        mNotificationStackScroller.updateEmptyShadeView(mShowEmptyShadeView && !mQsExpanded);
+    }
+
+    public void setQsScrimEnabled(boolean qsScrimEnabled) {
+        boolean changed = mQsScrimEnabled != qsScrimEnabled;
+        mQsScrimEnabled = qsScrimEnabled;
+        if (changed) {
+            updateQsState();
+        }
+    }
+
+    public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
+        mKeyguardUserSwitcher = keyguardUserSwitcher;
+    }
+
+    public void onScreenTurningOn() {
+        mKeyguardStatusView.dozeTimeTick();
+    }
+
+    @Override
+    protected boolean onMiddleClicked() {
+        switch (mBarState) {
+            case StatusBarState.KEYGUARD:
+                if (!mDozingOnDown) {
+                    if (mKeyguardBypassController.getBypassEnabled()) {
+                        mUpdateMonitor.requestFaceAuth();
+                    } else {
+                        mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_HINT,
+                                0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
+                        startUnlockHintAnimation();
+                    }
+                }
+                return true;
+            case StatusBarState.SHADE_LOCKED:
+                if (!mQsExpanded) {
+                    mStatusBarStateController.setState(StatusBarState.KEYGUARD);
+                }
+                return true;
+            case StatusBarState.SHADE:
+
+                // This gets called in the middle of the touch handling, where the state is still
+                // that we are tracking the panel. Collapse the panel after this is done.
+                mView.post(mPostCollapseRunnable);
+                return false;
+            default:
+                return true;
+        }
+    }
+
+    public void setPanelAlpha(int alpha, boolean animate) {
+        if (mPanelAlpha != alpha) {
+            mPanelAlpha = alpha;
+            PropertyAnimator.setProperty(mView, mPanelAlphaAnimator, alpha, alpha == 255
+                            ? mPanelAlphaInPropertiesAnimator : mPanelAlphaOutPropertiesAnimator,
+                    animate);
+        }
+    }
+
+    public void setPanelAlphaEndAction(Runnable r) {
+        mPanelAlphaEndAction = r;
+    }
+
+    private void updateKeyguardStatusBarForHeadsUp() {
+        boolean
+                showingKeyguardHeadsUp =
+                mKeyguardShowing && mHeadsUpAppearanceController.shouldBeVisible();
+        if (mShowingKeyguardHeadsUp != showingKeyguardHeadsUp) {
+            mShowingKeyguardHeadsUp = showingKeyguardHeadsUp;
+            if (mKeyguardShowing) {
+                PropertyAnimator.setProperty(mView, KEYGUARD_HEADS_UP_SHOWING_AMOUNT,
+                        showingKeyguardHeadsUp ? 1.0f : 0.0f, KEYGUARD_HUN_PROPERTIES,
+                        true /* animate */);
+            } else {
+                PropertyAnimator.applyImmediately(mView, KEYGUARD_HEADS_UP_SHOWING_AMOUNT, 0.0f);
+            }
+        }
+    }
+
+    private void setKeyguardHeadsUpShowingAmount(float amount) {
+        mKeyguardHeadsUpShowingAmount = amount;
+        updateHeaderKeyguardAlpha();
+    }
+
+    private float getKeyguardHeadsUpShowingAmount() {
+        return mKeyguardHeadsUpShowingAmount;
+    }
+
+    public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
+        mHeadsUpAnimatingAway = headsUpAnimatingAway;
+        mNotificationStackScroller.setHeadsUpAnimatingAway(headsUpAnimatingAway);
+        updateHeadsUpVisibility();
+    }
+
+    private void updateHeadsUpVisibility() {
+        ((PhoneStatusBarView) mBar).setHeadsUpVisible(mHeadsUpAnimatingAway || mHeadsUpPinnedMode);
+    }
+
+    @Override
+    public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
+        super.setHeadsUpManager(headsUpManager);
+        mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager,
+                mNotificationStackScroller.getHeadsUpCallback(),
+                NotificationPanelViewController.this);
+    }
+
+    public void setTrackedHeadsUp(ExpandableNotificationRow pickedChild) {
+        if (pickedChild != null) {
+            notifyListenersTrackingHeadsUp(pickedChild);
+            mExpandingFromHeadsUp = true;
+        }
+        // otherwise we update the state when the expansion is finished
+    }
+
+    @Override
+    protected void onClosingFinished() {
+        super.onClosingFinished();
+        resetHorizontalPanelPosition();
+        setClosingWithAlphaFadeout(false);
+    }
+
+    private void setClosingWithAlphaFadeout(boolean closing) {
+        mClosingWithAlphaFadeOut = closing;
+        mNotificationStackScroller.forceNoOverlappingRendering(closing);
+    }
+
+    /**
+     * Updates the vertical position of the panel so it is positioned closer to the touch
+     * responsible for opening the panel.
+     *
+     * @param x the x-coordinate the touch event
+     */
+    protected void updateVerticalPanelPosition(float x) {
+        if (mNotificationStackScroller.getWidth() * 1.75f > mView.getWidth()) {
+            resetHorizontalPanelPosition();
+            return;
+        }
+        float leftMost = mPositionMinSideMargin + mNotificationStackScroller.getWidth() / 2;
+        float
+                rightMost =
+                mView.getWidth() - mPositionMinSideMargin
+                        - mNotificationStackScroller.getWidth() / 2;
+        if (Math.abs(x - mView.getWidth() / 2) < mNotificationStackScroller.getWidth() / 4) {
+            x = mView.getWidth() / 2;
+        }
+        x = Math.min(rightMost, Math.max(leftMost, x));
+        float
+                center =
+                mNotificationStackScroller.getLeft() + mNotificationStackScroller.getWidth() / 2;
+        setHorizontalPanelTranslation(x - center);
+    }
+
+    private void resetHorizontalPanelPosition() {
+        setHorizontalPanelTranslation(0f);
+    }
+
+    protected void setHorizontalPanelTranslation(float translation) {
+        mNotificationStackScroller.setTranslationX(translation);
+        mQsFrame.setTranslationX(translation);
+        int size = mVerticalTranslationListener.size();
+        for (int i = 0; i < size; i++) {
+            mVerticalTranslationListener.get(i).run();
+        }
+    }
+
+    protected void updateExpandedHeight(float expandedHeight) {
+        if (mTracking) {
+            mNotificationStackScroller.setExpandingVelocity(getCurrentExpandVelocity());
+        }
+        if (mKeyguardBypassController.getBypassEnabled() && isOnKeyguard()) {
+            // The expandedHeight is always the full panel Height when bypassing
+            expandedHeight = getMaxPanelHeightNonBypass();
+        }
+        mNotificationStackScroller.setExpandedHeight(expandedHeight);
+        updateKeyguardBottomAreaAlpha();
+        updateBigClockAlpha();
+        updateStatusBarIcons();
+    }
+
+    /**
+     * @return whether the notifications are displayed full width and don't have any margins on
+     * the side.
+     */
+    public boolean isFullWidth() {
+        return mIsFullWidth;
+    }
+
+    private void updateStatusBarIcons() {
+        boolean
+                showIconsWhenExpanded =
+                (isPanelVisibleBecauseOfHeadsUp() || isFullWidth())
+                        && getExpandedHeight() < getOpeningHeight();
+        boolean noVisibleNotifications = true;
+        if (showIconsWhenExpanded && noVisibleNotifications && isOnKeyguard()) {
+            showIconsWhenExpanded = false;
+        }
+        if (showIconsWhenExpanded != mShowIconsWhenExpanded) {
+            mShowIconsWhenExpanded = showIconsWhenExpanded;
+            mCommandQueue.recomputeDisableFlags(mDisplayId, false);
+        }
+    }
+
+    private boolean isOnKeyguard() {
+        return mBarState == StatusBarState.KEYGUARD;
+    }
+
+    public void setPanelScrimMinFraction(float minFraction) {
+        mBar.panelScrimMinFractionChanged(minFraction);
+    }
+
+    public void clearNotificationEffects() {
+        mStatusBar.clearNotificationEffects();
+    }
+
+    @Override
+    protected boolean isPanelVisibleBecauseOfHeadsUp() {
+        return (mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway)
+                && mBarState == StatusBarState.SHADE;
+    }
+
+    public void launchCamera(boolean animate, int source) {
+        if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP) {
+            mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP;
+        } else if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE) {
+            mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_WIGGLE;
+        } else if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER) {
+            mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER;
+        } else {
+
+            // Default.
+            mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
+        }
+
+        // If we are launching it when we are occluded already we don't want it to animate,
+        // nor setting these flags, since the occluded state doesn't change anymore, hence it's
+        // never reset.
+        if (!isFullyCollapsed()) {
+            setLaunchingAffordance(true);
+        } else {
+            animate = false;
+        }
+        mAffordanceHasPreview = mKeyguardBottomArea.getRightPreview() != null;
+        mAffordanceHelper.launchAffordance(
+                animate, mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
+    }
+
+    public void onAffordanceLaunchEnded() {
+        setLaunchingAffordance(false);
+    }
+
+    /**
+     * Set whether we are currently launching an affordance. This is currently only set when
+     * launched via a camera gesture.
+     */
+    private void setLaunchingAffordance(boolean launchingAffordance) {
+        mLaunchingAffordance = launchingAffordance;
+        mKeyguardAffordanceHelperCallback.getLeftIcon().setLaunchingAffordance(launchingAffordance);
+        mKeyguardAffordanceHelperCallback.getRightIcon().setLaunchingAffordance(
+                launchingAffordance);
+        mKeyguardBypassController.setLaunchingAffordance(launchingAffordance);
+        if (mAffordanceLaunchListener != null) {
+            mAffordanceLaunchListener.accept(launchingAffordance);
+        }
+    }
+
+    /**
+     * Return true when a bottom affordance is launching an occluded activity with a splash screen.
+     */
+    public boolean isLaunchingAffordanceWithPreview() {
+        return mLaunchingAffordance && mAffordanceHasPreview;
+    }
+
+    /**
+     * Whether the camera application can be launched for the camera launch gesture.
+     */
+    public boolean canCameraGestureBeLaunched() {
+        if (!mStatusBar.isCameraAllowedByAdmin()) {
+            return false;
+        }
+
+        ResolveInfo resolveInfo = mKeyguardBottomArea.resolveCameraIntent();
+        String
+                packageToLaunch =
+                (resolveInfo == null || resolveInfo.activityInfo == null) ? null
+                        : resolveInfo.activityInfo.packageName;
+        return packageToLaunch != null && (mBarState != StatusBarState.SHADE || !isForegroundApp(
+                packageToLaunch)) && !mAffordanceHelper.isSwipingInProgress();
+    }
+
+    /**
+     * Return true if the applications with the package name is running in foreground.
+     *
+     * @param pkgName application package name.
+     */
+    private boolean isForegroundApp(String pkgName) {
+        List<ActivityManager.RunningTaskInfo> tasks = mActivityManager.getRunningTasks(1);
+        return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName());
+    }
+
+    private void setGroupManager(NotificationGroupManager groupManager) {
+        mGroupManager = groupManager;
+    }
+
+    public boolean hideStatusBarIconsWhenExpanded() {
+        if (mLaunchingNotification) {
+            return mHideIconsDuringNotificationLaunch;
+        }
+        if (mHeadsUpAppearanceController != null
+                && mHeadsUpAppearanceController.shouldBeVisible()) {
+            return false;
+        }
+        return !isFullWidth() || !mShowIconsWhenExpanded;
+    }
+
+    private final FragmentListener mFragmentListener = new FragmentListener() {
+        @Override
+        public void onFragmentViewCreated(String tag, Fragment fragment) {
+            mQs = (QS) fragment;
+            mQs.setPanelView(mHeightListener);
+            mQs.setExpandClickListener(mOnClickListener);
+            mQs.setHeaderClickable(mQsExpansionEnabled);
+            updateQSPulseExpansion();
+            mQs.setOverscrolling(mStackScrollerOverscrolling);
+
+            // recompute internal state when qspanel height changes
+            mQs.getView().addOnLayoutChangeListener(
+                    (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+                        final int height = bottom - top;
+                        final int oldHeight = oldBottom - oldTop;
+                        if (height != oldHeight) {
+                            mHeightListener.onQsHeightChanged();
+                        }
+                    });
+            mNotificationStackScroller.setQsContainer((ViewGroup) mQs.getView());
+            if (mQs instanceof QSFragment) {
+                mKeyguardStatusBar.setQSPanel(((QSFragment) mQs).getQsPanel());
+            }
+            updateQsExpansion();
+        }
+
+        @Override
+        public void onFragmentViewDestroyed(String tag, Fragment fragment) {
+            // Manual handling of fragment lifecycle is only required because this bridges
+            // non-fragment and fragment code. Once we are using a fragment for the notification
+            // panel, mQs will not need to be null cause it will be tied to the same lifecycle.
+            if (fragment == mQs) {
+                mQs = null;
+            }
+        }
+    };
+
+    @Override
+    public void setTouchAndAnimationDisabled(boolean disabled) {
+        super.setTouchAndAnimationDisabled(disabled);
+        if (disabled && mAffordanceHelper.isSwipingInProgress() && !mIsLaunchTransitionRunning) {
+            mAffordanceHelper.reset(false /* animate */);
+        }
+        mNotificationStackScroller.setAnimationsEnabled(!disabled);
+    }
+
+    /**
+     * Sets the dozing state.
+     *
+     * @param dozing              {@code true} when dozing.
+     * @param animate             if transition should be animated.
+     * @param wakeUpTouchLocation touch event location - if woken up by SLPI sensor.
+     */
+    public void setDozing(boolean dozing, boolean animate, PointF wakeUpTouchLocation) {
+        if (dozing == mDozing) return;
+        mView.setDozing(dozing);
+        mDozing = dozing;
+        mNotificationStackScroller.setDozing(mDozing, animate, wakeUpTouchLocation);
+        mKeyguardBottomArea.setDozing(mDozing, animate);
+
+        if (dozing) {
+            mBottomAreaShadeAlphaAnimator.cancel();
+        }
+
+        if (mBarState == StatusBarState.KEYGUARD || mBarState == StatusBarState.SHADE_LOCKED) {
+            updateDozingVisibilities(animate);
+        }
+
+        final float dozeAmount = dozing ? 1 : 0;
+        mStatusBarStateController.setDozeAmount(dozeAmount, animate);
+    }
+
+    public void setPulsing(boolean pulsing) {
+        mPulsing = pulsing;
+        final boolean
+                animatePulse =
+                !mDozeParameters.getDisplayNeedsBlanking() && mDozeParameters.getAlwaysOn();
+        if (animatePulse) {
+            mAnimateNextPositionUpdate = true;
+        }
+        // Do not animate the clock when waking up from a pulse.
+        // The height callback will take care of pushing the clock to the right position.
+        if (!mPulsing && !mDozing) {
+            mAnimateNextPositionUpdate = false;
+        }
+        mNotificationStackScroller.setPulsing(pulsing, animatePulse);
+        mKeyguardStatusView.setPulsing(pulsing);
+    }
+
+    public void setAmbientIndicationBottomPadding(int ambientIndicationBottomPadding) {
+        if (mAmbientIndicationBottomPadding != ambientIndicationBottomPadding) {
+            mAmbientIndicationBottomPadding = ambientIndicationBottomPadding;
+            mStatusBar.updateKeyguardMaxNotifications();
+        }
+    }
+
+    public void dozeTimeTick() {
+        mKeyguardBottomArea.dozeTimeTick();
+        mKeyguardStatusView.dozeTimeTick();
+        if (mInterpolatedDarkAmount > 0) {
+            positionClockAndNotifications();
+        }
+    }
+
+    public void setStatusAccessibilityImportance(int mode) {
+        mKeyguardStatusView.setImportantForAccessibility(mode);
+    }
+
+    /**
+     * TODO: this should be removed.
+     * It's not correct to pass this view forward because other classes will end up adding
+     * children to it. Theme will be out of sync.
+     *
+     * @return bottom area view
+     */
+    public KeyguardBottomAreaView getKeyguardBottomAreaView() {
+        return mKeyguardBottomArea;
+    }
+
+    public void setUserSetupComplete(boolean userSetupComplete) {
+        mUserSetupComplete = userSetupComplete;
+        mKeyguardBottomArea.setUserSetupComplete(userSetupComplete);
+    }
+
+    public void applyExpandAnimationParams(ExpandAnimationParameters params) {
+        mExpandOffset = params != null ? params.getTopChange() : 0;
+        updateQsExpansion();
+        if (params != null) {
+            boolean hideIcons = params.getProgress(
+                    ActivityLaunchAnimator.ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f;
+            if (hideIcons != mHideIconsDuringNotificationLaunch) {
+                mHideIconsDuringNotificationLaunch = hideIcons;
+                if (!hideIcons) {
+                    mCommandQueue.recomputeDisableFlags(mDisplayId, true /* animate */);
+                }
+            }
+        }
+    }
+
+    public void addTrackingHeadsUpListener(Consumer<ExpandableNotificationRow> listener) {
+        mTrackingHeadsUpListeners.add(listener);
+    }
+
+    public void removeTrackingHeadsUpListener(Consumer<ExpandableNotificationRow> listener) {
+        mTrackingHeadsUpListeners.remove(listener);
+    }
+
+    public void addVerticalTranslationListener(Runnable verticalTranslationListener) {
+        mVerticalTranslationListener.add(verticalTranslationListener);
+    }
+
+    public void removeVerticalTranslationListener(Runnable verticalTranslationListener) {
+        mVerticalTranslationListener.remove(verticalTranslationListener);
+    }
+
+    public void setHeadsUpAppearanceController(
+            HeadsUpAppearanceController headsUpAppearanceController) {
+        mHeadsUpAppearanceController = headsUpAppearanceController;
+    }
+
+    /**
+     * Starts the animation before we dismiss Keyguard, i.e. an disappearing animation on the
+     * security view of the bouncer.
+     */
+    public void onBouncerPreHideAnimation() {
+        setKeyguardStatusViewVisibility(mBarState, true /* keyguardFadingAway */,
+                false /* goingToFullShade */);
+    }
+
+    /**
+     * Do not let the user drag the shade up and down for the current touch session.
+     * This is necessary to avoid shade expansion while/after the bouncer is dismissed.
+     */
+    public void blockExpansionForCurrentTouch() {
+        mBlockingExpansionForCurrentTouch = mTracking;
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        super.dump(fd, pw, args);
+        pw.println("    gestureExclusionRect: " + calculateGestureExclusionRect());
+        if (mKeyguardStatusBar != null) {
+            mKeyguardStatusBar.dump(fd, pw, args);
+        }
+        if (mKeyguardStatusView != null) {
+            mKeyguardStatusView.dump(fd, pw, args);
+        }
+    }
+
+    public boolean hasActiveClearableNotifications() {
+        return mNotificationStackScroller.hasActiveClearableNotifications(ROWS_ALL);
+    }
+
+    private void updateShowEmptyShadeView() {
+        boolean
+                showEmptyShadeView =
+                mBarState != StatusBarState.KEYGUARD && !mEntryManager.hasActiveNotifications();
+        showEmptyShadeView(showEmptyShadeView);
+    }
+
+    public RemoteInputController.Delegate createRemoteInputDelegate() {
+        return mNotificationStackScroller.createDelegate();
+    }
+
+    public void updateNotificationViews() {
+        mNotificationStackScroller.updateSectionBoundaries();
+        mNotificationStackScroller.updateSpeedBumpIndex();
+        mNotificationStackScroller.updateFooter();
+        updateShowEmptyShadeView();
+        mNotificationStackScroller.updateIconAreaViews();
+    }
+
+    public void onUpdateRowStates() {
+        mNotificationStackScroller.onUpdateRowStates();
+    }
+
+    public boolean hasPulsingNotifications() {
+        return mNotificationStackScroller.hasPulsingNotifications();
+    }
+
+    public ActivatableNotificationView getActivatedChild() {
+        return mNotificationStackScroller.getActivatedChild();
+    }
+
+    public void setActivatedChild(ActivatableNotificationView o) {
+        mNotificationStackScroller.setActivatedChild(o);
+    }
+
+    public void runAfterAnimationFinished(Runnable r) {
+        mNotificationStackScroller.runAfterAnimationFinished(r);
+    }
+
+    public void setScrollingEnabled(boolean b) {
+        mNotificationStackScroller.setScrollingEnabled(b);
+    }
+
+    public void initDependencies(StatusBar statusBar, NotificationGroupManager groupManager,
+            NotificationShelf notificationShelf,
+            NotificationIconAreaController notificationIconAreaController,
+            ScrimController scrimController) {
+        setStatusBar(statusBar);
+        setGroupManager(mGroupManager);
+        mNotificationStackScroller.setNotificationPanelController(this);
+        mNotificationStackScroller.setIconAreaController(notificationIconAreaController);
+        mNotificationStackScroller.setStatusBar(statusBar);
+        mNotificationStackScroller.setGroupManager(groupManager);
+        mNotificationStackScroller.setShelf(notificationShelf);
+        mNotificationStackScroller.setScrimController(scrimController);
+        updateShowEmptyShadeView();
+    }
+
+    public void showTransientIndication(int id) {
+        mKeyguardIndicationController.showTransientIndication(id);
+    }
+
+    public void setOnReinflationListener(Runnable onReinflationListener) {
+        mOnReinflationListener = onReinflationListener;
+    }
+
+    public static boolean isQsSplitEnabled() {
+        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.QS_SPLIT_ENABLED, false);
+    }
+
+    public void setAlpha(float alpha) {
+        mView.setAlpha(alpha);
+    }
+
+    public ViewPropertyAnimator fadeOut(long startDelayMs, long durationMs, Runnable endAction) {
+        return mView.animate().alpha(0).setStartDelay(startDelayMs).setDuration(
+                durationMs).setInterpolator(Interpolators.ALPHA_OUT).withLayer().withEndAction(
+                endAction);
+    }
+
+    public void resetViewGroupFade() {
+        ViewGroupFadeHelper.reset(mView);
+    }
+
+    public void addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
+        mView.getViewTreeObserver().addOnGlobalLayoutListener(listener);
+    }
+
+    public void removeOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
+        mView.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
+    }
+
+    public MyOnHeadsUpChangedListener getOnHeadsUpChangedListener() {
+        return mOnHeadsUpChangedListener;
+    }
+
+    public int getHeight() {
+        return mView.getHeight();
+    }
+
+    public TextView getHeaderDebugInfo() {
+        return mView.findViewById(R.id.header_debug_info);
+    }
+
+    public void onThemeChanged() {
+        mConfigurationListener.onThemeChanged();
+    }
+
+    @Override
+    public OnLayoutChangeListener createLayoutChangeListener() {
+        return new OnLayoutChangeListener();
+    }
+
+    public void setEmptyDragAmount(float amount) {
+        mExpansionCallback.setEmptyDragAmount(amount);
+    }
+
+    @Override
+    protected TouchHandler createTouchHandler() {
+        return new TouchHandler() {
+            @Override
+            public boolean onInterceptTouchEvent(MotionEvent event) {
+                if (mBlockTouches || mQsFullyExpanded && mQs.onInterceptTouchEvent(event)) {
+                    return false;
+                }
+                initDownStates(event);
+                // Do not let touches go to shade or QS if the bouncer is visible,
+                // but still let user swipe down to expand the panel, dismissing the bouncer.
+                if (mStatusBar.isBouncerShowing()) {
+                    return true;
+                }
+                if (mBar.panelEnabled() && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
+                    mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
+                    mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
+                    return true;
+                }
+                if (!shouldQuickSettingsIntercept(mDownX, mDownY, 0)
+                        && mPulseExpansionHandler.onInterceptTouchEvent(event)) {
+                    return true;
+                }
+
+                if (!isFullyCollapsed() && onQsIntercept(event)) {
+                    return true;
+                }
+                return super.onInterceptTouchEvent(event);
+            }
+
+            @Override
+            public boolean onTouch(View v, MotionEvent event) {
+                if (mBlockTouches || (mQs != null && mQs.isCustomizing())) {
+                    return false;
+                }
+
+                // Do not allow panel expansion if bouncer is scrimmed, otherwise user would be able
+                // to pull down QS or expand the shade.
+                if (mStatusBar.isBouncerShowingScrimmed()) {
+                    return false;
+                }
+
+                // Make sure the next touch won't the blocked after the current ends.
+                if (event.getAction() == MotionEvent.ACTION_UP
+                        || event.getAction() == MotionEvent.ACTION_CANCEL) {
+                    mBlockingExpansionForCurrentTouch = false;
+                }
+                // When touch focus transfer happens, ACTION_DOWN->ACTION_UP may happen immediately
+                // without any ACTION_MOVE event.
+                // In such case, simply expand the panel instead of being stuck at the bottom bar.
+                if (mLastEventSynthesizedDown && event.getAction() == MotionEvent.ACTION_UP) {
+                    expand(true /* animate */);
+                }
+                initDownStates(event);
+                if (!mIsExpanding && !shouldQuickSettingsIntercept(mDownX, mDownY, 0)
+                        && mPulseExpansionHandler.onTouchEvent(event)) {
+                    // We're expanding all the other ones shouldn't get this anymore
+                    return true;
+                }
+                if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp()
+                        && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
+                    mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
+                }
+                boolean handled = false;
+                if ((!mIsExpanding || mHintAnimationRunning) && !mQsExpanded
+                        && mBarState != StatusBarState.SHADE && !mDozing) {
+                    handled |= mAffordanceHelper.onTouchEvent(event);
+                }
+                if (mOnlyAffordanceInThisMotion) {
+                    return true;
+                }
+                handled |= mHeadsUpTouchHelper.onTouchEvent(event);
+
+                if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) {
+                    return true;
+                }
+                if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
+                    mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
+                    updateVerticalPanelPosition(event.getX());
+                    handled = true;
+                }
+                handled |= super.onTouch(v, event);
+                return !mDozing || mPulsing || handled;
+            }
+        };
+    }
+
+    @Override
+    protected PanelViewController.OnConfigurationChangedListener
+            createOnConfigurationChangedListener() {
+        return new OnConfigurationChangedListener();
+    }
+
+    private class OnHeightChangedListener implements ExpandableView.OnHeightChangedListener {
+        @Override
+        public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
+
+            // Block update if we are in quick settings and just the top padding changed
+            // (i.e. view == null).
+            if (view == null && mQsExpanded) {
+                return;
+            }
+            if (needsAnimation && mInterpolatedDarkAmount == 0) {
+                mAnimateNextPositionUpdate = true;
+            }
+            ExpandableView firstChildNotGone = mNotificationStackScroller.getFirstChildNotGone();
+            ExpandableNotificationRow
+                    firstRow =
+                    firstChildNotGone instanceof ExpandableNotificationRow
+                            ? (ExpandableNotificationRow) firstChildNotGone : null;
+            if (firstRow != null && (view == firstRow || (firstRow.getNotificationParent()
+                    == firstRow))) {
+                requestScrollerTopPaddingUpdate(false /* animate */);
+            }
+            requestPanelHeightUpdate();
+        }
+
+        @Override
+        public void onReset(ExpandableView view) {
+        }
+    }
+
+    private class OnClickListener implements View.OnClickListener {
+        @Override
+        public void onClick(View v) {
+            onQsExpansionStarted();
+            if (mQsExpanded) {
+                flingSettings(0 /* vel */, FLING_COLLAPSE, null /* onFinishRunnable */,
+                        true /* isClick */);
+            } else if (mQsExpansionEnabled) {
+                mLockscreenGestureLogger.write(MetricsEvent.ACTION_SHADE_QS_TAP, 0, 0);
+                flingSettings(0 /* vel */, FLING_EXPAND, null /* onFinishRunnable */,
+                        true /* isClick */);
+            }
+        }
+    }
+
+    private class OnOverscrollTopChangedListener implements
+            NotificationStackScrollLayout.OnOverscrollTopChangedListener {
+        @Override
+        public void onOverscrollTopChanged(float amount, boolean isRubberbanded) {
+            cancelQsAnimation();
+            if (!mQsExpansionEnabled) {
+                amount = 0f;
+            }
+            float rounded = amount >= 1f ? amount : 0f;
+            setOverScrolling(rounded != 0f && isRubberbanded);
+            mQsExpansionFromOverscroll = rounded != 0f;
+            mLastOverscroll = rounded;
+            updateQsState();
+            setQsExpansion(mQsMinExpansionHeight + rounded);
+        }
+
+        @Override
+        public void flingTopOverscroll(float velocity, boolean open) {
+            mLastOverscroll = 0f;
+            mQsExpansionFromOverscroll = false;
+            setQsExpansion(mQsExpansionHeight);
+            flingSettings(!mQsExpansionEnabled && open ? 0f : velocity,
+                    open && mQsExpansionEnabled ? FLING_EXPAND : FLING_COLLAPSE, () -> {
+                        mStackScrollerOverscrolling = false;
+                        setOverScrolling(false);
+                        updateQsState();
+                    }, false /* isClick */);
+        }
+    }
+
+    private class DynamicPrivacyControlListener implements DynamicPrivacyController.Listener {
+        @Override
+        public void onDynamicPrivacyChanged() {
+            // Do not request animation when pulsing or waking up, otherwise the clock wiill be out
+            // of sync with the notification panel.
+            if (mLinearDarkAmount != 0) {
+                return;
+            }
+            mAnimateNextPositionUpdate = true;
+        }
+    }
+
+    private class KeyguardAffordanceHelperCallback implements KeyguardAffordanceHelper.Callback {
+        @Override
+        public void onAnimationToSideStarted(boolean rightPage, float translation, float vel) {
+            boolean
+                    start =
+                    mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? rightPage
+                            : !rightPage;
+            mIsLaunchTransitionRunning = true;
+            mLaunchAnimationEndRunnable = null;
+            float displayDensity = mStatusBar.getDisplayDensity();
+            int lengthDp = Math.abs((int) (translation / displayDensity));
+            int velocityDp = Math.abs((int) (vel / displayDensity));
+            if (start) {
+                mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_DIALER, lengthDp, velocityDp);
+
+                mFalsingManager.onLeftAffordanceOn();
+                if (mFalsingManager.shouldEnforceBouncer()) {
+                    mStatusBar.executeRunnableDismissingKeyguard(
+                            () -> mKeyguardBottomArea.launchLeftAffordance(), null,
+                            true /* dismissShade */, false /* afterKeyguardGone */,
+                            true /* deferred */);
+                } else {
+                    mKeyguardBottomArea.launchLeftAffordance();
+                }
+            } else {
+                if (KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE.equals(
+                        mLastCameraLaunchSource)) {
+                    mLockscreenGestureLogger.write(
+                            MetricsEvent.ACTION_LS_CAMERA, lengthDp, velocityDp);
+                }
+                mFalsingManager.onCameraOn();
+                if (mFalsingManager.shouldEnforceBouncer()) {
+                    mStatusBar.executeRunnableDismissingKeyguard(
+                            () -> mKeyguardBottomArea.launchCamera(mLastCameraLaunchSource), null,
+                            true /* dismissShade */, false /* afterKeyguardGone */,
+                            true /* deferred */);
+                } else {
+                    mKeyguardBottomArea.launchCamera(mLastCameraLaunchSource);
+                }
+            }
+            mStatusBar.startLaunchTransitionTimeout();
+            mBlockTouches = true;
+        }
+
+        @Override
+        public void onAnimationToSideEnded() {
+            mIsLaunchTransitionRunning = false;
+            mIsLaunchTransitionFinished = true;
+            if (mLaunchAnimationEndRunnable != null) {
+                mLaunchAnimationEndRunnable.run();
+                mLaunchAnimationEndRunnable = null;
+            }
+            mStatusBar.readyForKeyguardDone();
+        }
+
+        @Override
+        public float getMaxTranslationDistance() {
+            return (float) Math.hypot(mView.getWidth(), getHeight());
+        }
+
+        @Override
+        public void onSwipingStarted(boolean rightIcon) {
+            mFalsingManager.onAffordanceSwipingStarted(rightIcon);
+            boolean
+                    camera =
+                    mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? !rightIcon
+                            : rightIcon;
+            if (camera) {
+                mKeyguardBottomArea.bindCameraPrewarmService();
+            }
+            mView.requestDisallowInterceptTouchEvent(true);
+            mOnlyAffordanceInThisMotion = true;
+            mQsTracking = false;
+        }
+
+        @Override
+        public void onSwipingAborted() {
+            mFalsingManager.onAffordanceSwipingAborted();
+            mKeyguardBottomArea.unbindCameraPrewarmService(false /* launched */);
+        }
+
+        @Override
+        public void onIconClicked(boolean rightIcon) {
+            if (mHintAnimationRunning) {
+                return;
+            }
+            mHintAnimationRunning = true;
+            mAffordanceHelper.startHintAnimation(rightIcon, () -> {
+                mHintAnimationRunning = false;
+                mStatusBar.onHintFinished();
+            });
+            rightIcon =
+                    mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? !rightIcon
+                            : rightIcon;
+            if (rightIcon) {
+                mStatusBar.onCameraHintStarted();
+            } else {
+                if (mKeyguardBottomArea.isLeftVoiceAssist()) {
+                    mStatusBar.onVoiceAssistHintStarted();
+                } else {
+                    mStatusBar.onPhoneHintStarted();
+                }
+            }
+        }
+
+        @Override
+        public KeyguardAffordanceView getLeftIcon() {
+            return mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
+                    ? mKeyguardBottomArea.getRightView() : mKeyguardBottomArea.getLeftView();
+        }
+
+        @Override
+        public KeyguardAffordanceView getRightIcon() {
+            return mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
+                    ? mKeyguardBottomArea.getLeftView() : mKeyguardBottomArea.getRightView();
+        }
+
+        @Override
+        public View getLeftPreview() {
+            return mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
+                    ? mKeyguardBottomArea.getRightPreview() : mKeyguardBottomArea.getLeftPreview();
+        }
+
+        @Override
+        public View getRightPreview() {
+            return mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
+                    ? mKeyguardBottomArea.getLeftPreview() : mKeyguardBottomArea.getRightPreview();
+        }
+
+        @Override
+        public float getAffordanceFalsingFactor() {
+            return mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
+        }
+
+        @Override
+        public boolean needsAntiFalsing() {
+            return mBarState == StatusBarState.KEYGUARD;
+        }
+    }
+
+    private class OnEmptySpaceClickListener implements
+            NotificationStackScrollLayout.OnEmptySpaceClickListener {
+        @Override
+        public void onEmptySpaceClicked(float x, float y) {
+            onEmptySpaceClick(x);
+        }
+    }
+
+    private class MyOnHeadsUpChangedListener implements OnHeadsUpChangedListener {
+        @Override
+        public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) {
+            mNotificationStackScroller.setInHeadsUpPinnedMode(inPinnedMode);
+            if (inPinnedMode) {
+                mHeadsUpExistenceChangedRunnable.run();
+                updateNotificationTranslucency();
+            } else {
+                setHeadsUpAnimatingAway(true);
+                mNotificationStackScroller.runAfterAnimationFinished(
+                        mHeadsUpExistenceChangedRunnable);
+            }
+            updateGestureExclusionRect();
+            mHeadsUpPinnedMode = inPinnedMode;
+            updateHeadsUpVisibility();
+            updateKeyguardStatusBarForHeadsUp();
+        }
+
+        @Override
+        public void onHeadsUpPinned(NotificationEntry entry) {
+            if (!isOnKeyguard()) {
+                mNotificationStackScroller.generateHeadsUpAnimation(entry.getHeadsUpAnimationView(),
+                        true);
+            }
+        }
+
+        @Override
+        public void onHeadsUpUnPinned(NotificationEntry entry) {
+
+            // When we're unpinning the notification via active edge they remain heads-upped,
+            // we need to make sure that an animation happens in this case, otherwise the
+            // notification
+            // will stick to the top without any interaction.
+            if (isFullyCollapsed() && entry.isRowHeadsUp() && !isOnKeyguard()) {
+                mNotificationStackScroller.generateHeadsUpAnimation(
+                        entry.getHeadsUpAnimationView(), false);
+                entry.setHeadsUpIsVisible();
+            }
+        }
+
+        @Override
+        public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
+            mNotificationStackScroller.generateHeadsUpAnimation(entry, isHeadsUp);
+        }
+    }
+
+    private class HeightListener implements QS.HeightListener {
+        public void onQsHeightChanged() {
+            mQsMaxExpansionHeight = mQs != null ? mQs.getDesiredHeight() : 0;
+            if (mQsExpanded && mQsFullyExpanded) {
+                mQsExpansionHeight = mQsMaxExpansionHeight;
+                requestScrollerTopPaddingUpdate(false /* animate */);
+                requestPanelHeightUpdate();
+            }
+            if (mAccessibilityManager.isEnabled()) {
+                mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
+            }
+            mNotificationStackScroller.setMaxTopPadding(
+                    mQsMaxExpansionHeight + mQsNotificationTopPadding);
+        }
+    }
+
+    private class ZenModeControllerCallback implements ZenModeController.Callback {
+        @Override
+        public void onZenChanged(int zen) {
+            updateShowEmptyShadeView();
+        }
+    }
+
+    private class ConfigurationListener implements ConfigurationController.ConfigurationListener {
+        @Override
+        public void onDensityOrFontScaleChanged() {
+            updateShowEmptyShadeView();
+        }
+
+        @Override
+        public void onThemeChanged() {
+            final int themeResId = mView.getContext().getThemeResId();
+            if (mThemeResId == themeResId) {
+                return;
+            }
+            mThemeResId = themeResId;
+
+            reInflateViews();
+        }
+
+        @Override
+        public void onOverlayChanged() {
+            reInflateViews();
+        }
+
+        @Override
+        public void onUiModeChanged() {
+            reinflatePluginContainer();
+        }
+    }
+
+    private class StatusBarStateListener implements StateListener {
+        @Override
+        public void onStateChanged(int statusBarState) {
+            boolean goingToFullShade = mStatusBarStateController.goingToFullShade();
+            boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway();
+            int oldState = mBarState;
+            boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD;
+            setKeyguardStatusViewVisibility(statusBarState, keyguardFadingAway, goingToFullShade);
+            setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
+
+            mBarState = statusBarState;
+            mKeyguardShowing = keyguardShowing;
+            if (mKeyguardShowing && isQsSplitEnabled()) {
+                mNotificationStackScroller.setVisibility(View.VISIBLE);
+                mQsFrame.setVisibility(View.VISIBLE);
+                mHomeControlsLayout.setVisibility(View.GONE);
+            }
+
+            if (oldState == StatusBarState.KEYGUARD && (goingToFullShade
+                    || statusBarState == StatusBarState.SHADE_LOCKED)) {
+                animateKeyguardStatusBarOut();
+                long
+                        delay =
+                        mBarState == StatusBarState.SHADE_LOCKED ? 0
+                                : mKeyguardStateController.calculateGoingToFullShadeDelay();
+                mQs.animateHeaderSlidingIn(delay);
+            } else if (oldState == StatusBarState.SHADE_LOCKED
+                    && statusBarState == StatusBarState.KEYGUARD) {
+                animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+                mNotificationStackScroller.resetScrollPosition();
+                // Only animate header if the header is visible. If not, it will partially
+                // animate out
+                // the top of QS
+                if (!mQsExpanded) {
+                    mQs.animateHeaderSlidingOut();
+                }
+            } else {
+                mKeyguardStatusBar.setAlpha(1f);
+                mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE);
+                ((PhoneStatusBarView) mBar).maybeShowDivider(keyguardShowing);
+                if (keyguardShowing && oldState != mBarState) {
+                    if (mQs != null) {
+                        mQs.hideImmediately();
+                    }
+                }
+            }
+            updateKeyguardStatusBarForHeadsUp();
+            if (keyguardShowing) {
+                updateDozingVisibilities(false /* animate */);
+            }
+            // THe update needs to happen after the headerSlide in above, otherwise the translation
+            // would reset
+            updateQSPulseExpansion();
+            maybeAnimateBottomAreaAlpha();
+            resetHorizontalPanelPosition();
+            updateQsState();
+        }
+
+        @Override
+        public void onDozeAmountChanged(float linearAmount, float amount) {
+            mInterpolatedDarkAmount = amount;
+            mLinearDarkAmount = linearAmount;
+            mKeyguardStatusView.setDarkAmount(mInterpolatedDarkAmount);
+            mKeyguardBottomArea.setDarkAmount(mInterpolatedDarkAmount);
+            positionClockAndNotifications();
+        }
+    }
+
+    private class ExpansionCallback implements PulseExpansionHandler.ExpansionCallback {
+        public void setEmptyDragAmount(float amount) {
+            mEmptyDragAmount = amount * 0.2f;
+            positionClockAndNotifications();
+        }
+    }
+
+    private class OnAttachStateChangeListener implements View.OnAttachStateChangeListener {
+        @Override
+        public void onViewAttachedToWindow(View v) {
+            FragmentHostManager.get(mView).addTagListener(QS.TAG, mFragmentListener);
+            mStatusBarStateController.addCallback(mStatusBarStateListener);
+            mZenModeController.addCallback(mZenModeControllerCallback);
+            mConfigurationController.addCallback(mConfigurationListener);
+            mUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
+            // Theme might have changed between inflating this view and attaching it to the
+            // window, so
+            // force a call to onThemeChanged
+            mConfigurationListener.onThemeChanged();
+        }
+
+        @Override
+        public void onViewDetachedFromWindow(View v) {
+            FragmentHostManager.get(mView).removeTagListener(QS.TAG, mFragmentListener);
+            mStatusBarStateController.removeCallback(mStatusBarStateListener);
+            mZenModeController.removeCallback(mZenModeControllerCallback);
+            mConfigurationController.removeCallback(mConfigurationListener);
+            mUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
+        }
+    }
+
+    private class OnLayoutChangeListener extends PanelViewController.OnLayoutChangeListener {
+
+        @Override
+        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
+                int oldTop, int oldRight, int oldBottom) {
+            DejankUtils.startDetectingBlockingIpcs("NVP#onLayout");
+            super.onLayoutChange(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom);
+            setIsFullWidth(mNotificationStackScroller.getWidth() == mView.getWidth());
+
+            // Update Clock Pivot
+            mKeyguardStatusView.setPivotX(mView.getWidth() / 2);
+            mKeyguardStatusView.setPivotY(
+                    (FONT_HEIGHT - CAP_HEIGHT) / 2048f * mKeyguardStatusView.getClockTextSize());
+
+            // Calculate quick setting heights.
+            int oldMaxHeight = mQsMaxExpansionHeight;
+            if (mQs != null) {
+                mQsMinExpansionHeight = mKeyguardShowing ? 0 : mQs.getQsMinExpansionHeight();
+                if (mNPVPluginManager != null) {
+                    mNPVPluginManager.setYOffset(mQsMinExpansionHeight);
+                    mQsMinExpansionHeight += mNPVPluginManager.getHeight();
+                }
+                mQsMaxExpansionHeight = mQs.getDesiredHeight();
+                mNotificationStackScroller.setMaxTopPadding(
+                        mQsMaxExpansionHeight + mQsNotificationTopPadding);
+            }
+            positionClockAndNotifications();
+            if (mQsExpanded && mQsFullyExpanded) {
+                mQsExpansionHeight = mQsMaxExpansionHeight;
+                requestScrollerTopPaddingUpdate(false /* animate */);
+                requestPanelHeightUpdate();
+
+                // Size has changed, start an animation.
+                if (mQsMaxExpansionHeight != oldMaxHeight) {
+                    startQsSizeChangeAnimation(oldMaxHeight, mQsMaxExpansionHeight);
+                }
+            } else if (!mQsExpanded) {
+                setQsExpansion(mQsMinExpansionHeight + mLastOverscroll);
+            }
+            updateExpandedHeight(getExpandedHeight());
+            updateHeader();
+
+            // If we are running a size change animation, the animation takes care of the height of
+            // the container. However, if we are not animating, we always need to make the QS
+            // container
+            // the desired height so when closing the QS detail, it stays smaller after the size
+            // change
+            // animation is finished but the detail view is still being animated away (this
+            // animation
+            // takes longer than the size change animation).
+            if (mQsSizeChangeAnimator == null && mQs != null) {
+                mQs.setHeightOverride(mQs.getDesiredHeight());
+            }
+            updateMaxHeadsUpTranslation();
+            updateGestureExclusionRect();
+            if (mExpandAfterLayoutRunnable != null) {
+                mExpandAfterLayoutRunnable.run();
+                mExpandAfterLayoutRunnable = null;
+            }
+            DejankUtils.stopDetectingBlockingIpcs("NVP#onLayout");
+        }
+    }
+
+    private class DebugDrawable extends Drawable {
+
+        @Override
+        public void draw(Canvas canvas) {
+            Paint p = new Paint();
+            p.setColor(Color.RED);
+            p.setStrokeWidth(2);
+            p.setStyle(Paint.Style.STROKE);
+            canvas.drawLine(0, getMaxPanelHeight(), mView.getWidth(), getMaxPanelHeight(), p);
+            p.setColor(Color.BLUE);
+            canvas.drawLine(0, getExpandedHeight(), mView.getWidth(), getExpandedHeight(), p);
+            p.setColor(Color.GREEN);
+            canvas.drawLine(0, calculatePanelHeightQsExpanded(), mView.getWidth(),
+                    calculatePanelHeightQsExpanded(), p);
+            p.setColor(Color.YELLOW);
+            canvas.drawLine(0, calculatePanelHeightShade(), mView.getWidth(),
+                    calculatePanelHeightShade(), p);
+            p.setColor(Color.MAGENTA);
+            canvas.drawLine(
+                    0, calculateQsTopPadding(), mView.getWidth(), calculateQsTopPadding(), p);
+            p.setColor(Color.CYAN);
+            canvas.drawLine(0, mClockPositionResult.stackScrollerPadding, mView.getWidth(),
+                    mNotificationStackScroller.getTopPadding(), p);
+            p.setColor(Color.GRAY);
+            canvas.drawLine(0, mClockPositionResult.clockY, mView.getWidth(),
+                    mClockPositionResult.clockY, p);
+        }
+
+        @Override
+        public void setAlpha(int alpha) {
+
+        }
+
+        @Override
+        public void setColorFilter(ColorFilter colorFilter) {
+
+        }
+
+        @Override
+        public int getOpacity() {
+            return 0;
+        }
+    }
+
+    private class OnConfigurationChangedListener extends
+            PanelViewController.OnConfigurationChangedListener {
+        @Override
+        public void onConfigurationChanged(Configuration newConfig) {
+            super.onConfigurationChanged(newConfig);
+            mAffordanceHelper.onConfigurationChanged();
+            if (newConfig.orientation != mLastOrientation) {
+                resetHorizontalPanelPosition();
+            }
+            mLastOrientation = newConfig.orientation;
+        }
+    }
+
+    private class OnApplyWindowInsetsListener implements View.OnApplyWindowInsetsListener {
+        public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
+            mNavigationBarBottomHeight = insets.getStableInsetBottom();
+            updateMaxHeadsUpTranslation();
+            return insets;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
new file mode 100644
index 0000000..3af8038
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
@@ -0,0 +1,719 @@
+/*
+ * 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.statusbar.phone;
+
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
+
+import android.app.IActivityManager;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.util.Log;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+
+import com.android.systemui.Dumpable;
+import com.android.systemui.R;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.statusbar.RemoteInputController.Callback;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.SuperStatusBarViewFactory;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+
+import com.google.android.collect.Lists;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Encapsulates all logic for the notification shade window state management.
+ */
+@Singleton
+public class NotificationShadeWindowController implements Callback, Dumpable,
+        ConfigurationListener {
+
+    private static final String TAG = "NotificationShadeWindowController";
+    private static final boolean DEBUG = false;
+
+    private final Context mContext;
+    private final WindowManager mWindowManager;
+    private final IActivityManager mActivityManager;
+    private final DozeParameters mDozeParameters;
+    private final LayoutParams mLpChanged;
+    private final boolean mKeyguardScreenRotation;
+    private final long mLockScreenDisplayTimeout;
+    private final Display.Mode mKeyguardDisplayMode;
+    private final KeyguardBypassController mKeyguardBypassController;
+    private ViewGroup mNotificationShadeView;
+    private LayoutParams mLp;
+    private boolean mHasTopUi;
+    private boolean mHasTopUiChanged;
+    private float mScreenBrightnessDoze;
+    private final State mCurrentState = new State();
+    private OtherwisedCollapsedListener mListener;
+    private ForcePluginOpenListener mForcePluginOpenListener;
+    private final ArrayList<WeakReference<StatusBarWindowCallback>>
+            mCallbacks = Lists.newArrayList();
+
+    private final SysuiColorExtractor mColorExtractor;
+    private final SuperStatusBarViewFactory mSuperStatusBarViewFactory;
+
+    @Inject
+    public NotificationShadeWindowController(Context context, WindowManager windowManager,
+            IActivityManager activityManager, DozeParameters dozeParameters,
+            StatusBarStateController statusBarStateController,
+            ConfigurationController configurationController,
+            KeyguardBypassController keyguardBypassController, SysuiColorExtractor colorExtractor,
+            SuperStatusBarViewFactory superStatusBarViewFactory) {
+        mContext = context;
+        mWindowManager = windowManager;
+        mActivityManager = activityManager;
+        mKeyguardScreenRotation = shouldEnableKeyguardScreenRotation();
+        mDozeParameters = dozeParameters;
+        mScreenBrightnessDoze = mDozeParameters.getScreenBrightnessDoze();
+        mLpChanged = new LayoutParams();
+        mKeyguardBypassController = keyguardBypassController;
+        mColorExtractor = colorExtractor;
+        mSuperStatusBarViewFactory = superStatusBarViewFactory;
+        mNotificationShadeView = mSuperStatusBarViewFactory.getNotificationShadeWindowView();
+
+        mLockScreenDisplayTimeout = context.getResources()
+                .getInteger(R.integer.config_lockScreenDisplayTimeout);
+        ((SysuiStatusBarStateController) statusBarStateController)
+                .addCallback(mStateListener,
+                        SysuiStatusBarStateController.RANK_STATUS_BAR_WINDOW_CONTROLLER);
+        configurationController.addCallback(this);
+
+        Display.Mode[] supportedModes = context.getDisplay().getSupportedModes();
+        Display.Mode currentMode = context.getDisplay().getMode();
+        // Running on the highest frame rate available can be expensive.
+        // Let's specify a preferred refresh rate, and allow higher FPS only when we
+        // know that we're not falsing (because we unlocked.)
+        int keyguardRefreshRate = context.getResources()
+                .getInteger(R.integer.config_keyguardRefreshRate);
+        // Find supported display mode with the same resolution and requested refresh rate.
+        mKeyguardDisplayMode = Arrays.stream(supportedModes).filter(mode ->
+                (int) mode.getRefreshRate() == keyguardRefreshRate
+                        && mode.getPhysicalWidth() == currentMode.getPhysicalWidth()
+                        && mode.getPhysicalHeight() == currentMode.getPhysicalHeight())
+                .findFirst().orElse(null);
+    }
+
+    /**
+     * Register to receive notifications about status bar window state changes.
+     */
+    public void registerCallback(StatusBarWindowCallback callback) {
+        // Prevent adding duplicate callbacks
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            if (mCallbacks.get(i).get() == callback) {
+                return;
+            }
+        }
+        mCallbacks.add(new WeakReference<StatusBarWindowCallback>(callback));
+    }
+
+    private boolean shouldEnableKeyguardScreenRotation() {
+        Resources res = mContext.getResources();
+        return SystemProperties.getBoolean("lockscreen.rot_override", false)
+                || res.getBoolean(R.bool.config_enableLockScreenRotation);
+    }
+
+    /**
+     * Adds the notification shade view to the window manager.
+     */
+    public void attach() {
+        // Now that the notification shade encompasses the sliding panel and its
+        // translucent backdrop, the entire thing is made TRANSLUCENT and is
+        // hardware-accelerated.
+        mLp = new LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                LayoutParams.TYPE_NOTIFICATION_SHADE,
+                LayoutParams.FLAG_NOT_FOCUSABLE
+                        | LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
+                        | LayoutParams.FLAG_SPLIT_TOUCH
+                        | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+                        | LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+                PixelFormat.TRANSLUCENT);
+        mLp.token = new Binder();
+        mLp.gravity = Gravity.TOP;
+        mLp.setFitInsetsTypes(0 /* types */);
+        mLp.softInputMode = LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+        mLp.setTitle("NotificationShade");
+        mLp.packageName = mContext.getPackageName();
+        mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        mWindowManager.addView(mNotificationShadeView, mLp);
+        mLpChanged.copyFrom(mLp);
+        onThemeChanged();
+    }
+
+    public ViewGroup getNotificationShadeView() {
+        return mNotificationShadeView;
+    }
+
+    public void setDozeScreenBrightness(int value) {
+        mScreenBrightnessDoze = value / 255f;
+    }
+
+    private void setKeyguardDark(boolean dark) {
+        int vis = mNotificationShadeView.getSystemUiVisibility();
+        if (dark) {
+            vis = vis | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+            vis = vis | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+        } else {
+            vis = vis & ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+            vis = vis & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+        }
+        mNotificationShadeView.setSystemUiVisibility(vis);
+    }
+
+    private void applyKeyguardFlags(State state) {
+        final boolean scrimsOccludingWallpaper =
+                state.mScrimsVisibility == ScrimController.OPAQUE;
+        final boolean keyguardOrAod = state.mKeyguardShowing
+                || (state.mDozing && mDozeParameters.getAlwaysOn());
+        if (keyguardOrAod && !state.mBackdropShowing && !scrimsOccludingWallpaper) {
+            mLpChanged.flags |= LayoutParams.FLAG_SHOW_WALLPAPER;
+        } else {
+            mLpChanged.flags &= ~LayoutParams.FLAG_SHOW_WALLPAPER;
+        }
+
+        if (state.mDozing) {
+            mLpChanged.privateFlags |= LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+        } else {
+            mLpChanged.privateFlags &= ~LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+        }
+
+        if (mKeyguardDisplayMode != null) {
+            boolean bypassOnKeyguard = mKeyguardBypassController.getBypassEnabled()
+                    && state.mStatusBarState == StatusBarState.KEYGUARD
+                    && !state.mKeyguardFadingAway && !state.mKeyguardGoingAway;
+            if (state.mDozing || bypassOnKeyguard) {
+                mLpChanged.preferredDisplayModeId = mKeyguardDisplayMode.getModeId();
+            } else {
+                mLpChanged.preferredDisplayModeId = 0;
+            }
+            Trace.setCounter("display_mode_id", mLpChanged.preferredDisplayModeId);
+        }
+    }
+
+    private void adjustScreenOrientation(State state) {
+        if (state.isKeyguardShowingAndNotOccluded() || state.mDozing) {
+            if (mKeyguardScreenRotation) {
+                mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER;
+            } else {
+                mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+            }
+        } else {
+            mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+        }
+    }
+
+    private void applyFocusableFlag(State state) {
+        boolean panelFocusable = state.mNotificationShadeFocusable && state.mPanelExpanded;
+        if (state.mBouncerShowing && (state.mKeyguardOccluded || state.mKeyguardNeedsInput)
+                || ENABLE_REMOTE_INPUT && state.mRemoteInputActive
+                || state.mBubbleExpanded) {
+            mLpChanged.flags &= ~LayoutParams.FLAG_NOT_FOCUSABLE;
+            mLpChanged.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+        } else if (state.isKeyguardShowingAndNotOccluded() || panelFocusable) {
+            mLpChanged.flags &= ~LayoutParams.FLAG_NOT_FOCUSABLE;
+            // Make sure to remove FLAG_ALT_FOCUSABLE_IM when keyguard needs input.
+            if (state.mKeyguardNeedsInput) {
+                mLpChanged.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+            } else {
+                mLpChanged.flags |= LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+            }
+        } else {
+            mLpChanged.flags |= LayoutParams.FLAG_NOT_FOCUSABLE;
+            mLpChanged.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+        }
+
+        mLpChanged.softInputMode = LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+    }
+
+    private void applyForceShowNavigationFlag(State state) {
+        if (state.mPanelExpanded || state.mBouncerShowing
+                || ENABLE_REMOTE_INPUT && state.mRemoteInputActive) {
+            mLpChanged.privateFlags |= LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
+        } else {
+            mLpChanged.privateFlags &= ~LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
+        }
+    }
+
+    private void applyVisibility(State state) {
+        boolean visible = isExpanded(state);
+        if (state.mForcePluginOpen) {
+            if (mListener != null) {
+                mListener.setWouldOtherwiseCollapse(visible);
+            }
+            visible = true;
+        }
+        if (visible) {
+            mNotificationShadeView.setVisibility(View.VISIBLE);
+        } else {
+            mNotificationShadeView.setVisibility(View.INVISIBLE);
+        }
+    }
+
+    private boolean isExpanded(State state) {
+        return !state.mForceCollapsed && (state.isKeyguardShowingAndNotOccluded()
+                || state.mPanelVisible || state.mKeyguardFadingAway || state.mBouncerShowing
+                || state.mHeadsUpShowing || state.mBubblesShowing
+                || state.mScrimsVisibility != ScrimController.TRANSPARENT);
+    }
+
+    private void applyFitsSystemWindows(State state) {
+        boolean fitsSystemWindows = !state.isKeyguardShowingAndNotOccluded();
+        if (mNotificationShadeView != null
+                && mNotificationShadeView.getFitsSystemWindows() != fitsSystemWindows) {
+            mNotificationShadeView.setFitsSystemWindows(fitsSystemWindows);
+            mNotificationShadeView.requestApplyInsets();
+        }
+    }
+
+    private void applyUserActivityTimeout(State state) {
+        if (state.isKeyguardShowingAndNotOccluded()
+                && state.mStatusBarState == StatusBarState.KEYGUARD
+                && !state.mQsExpanded) {
+            mLpChanged.userActivityTimeout = state.mBouncerShowing
+                    ? KeyguardViewMediator.AWAKE_INTERVAL_BOUNCER_MS : mLockScreenDisplayTimeout;
+        } else {
+            mLpChanged.userActivityTimeout = -1;
+        }
+    }
+
+    private void applyInputFeatures(State state) {
+        if (state.isKeyguardShowingAndNotOccluded()
+                && state.mStatusBarState == StatusBarState.KEYGUARD
+                && !state.mQsExpanded && !state.mForceUserActivity) {
+            mLpChanged.inputFeatures |=
+                    LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
+        } else {
+            mLpChanged.inputFeatures &=
+                    ~LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
+        }
+    }
+
+    private void applyStatusBarColorSpaceAgnosticFlag(State state) {
+        if (!isExpanded(state)) {
+            mLpChanged.privateFlags |= LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
+        } else {
+            mLpChanged.privateFlags &=
+                    ~LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
+        }
+    }
+
+    private void apply(State state) {
+        applyKeyguardFlags(state);
+        applyFocusableFlag(state);
+        applyForceShowNavigationFlag(state);
+        adjustScreenOrientation(state);
+        applyVisibility(state);
+        applyUserActivityTimeout(state);
+        applyInputFeatures(state);
+        applyFitsSystemWindows(state);
+        applyModalFlag(state);
+        applyBrightness(state);
+        applyHasTopUi(state);
+        applyNotTouchable(state);
+        applyStatusBarColorSpaceAgnosticFlag(state);
+        if (mLp != null && mLp.copyFrom(mLpChanged) != 0) {
+            mWindowManager.updateViewLayout(mNotificationShadeView, mLp);
+        }
+        if (mHasTopUi != mHasTopUiChanged) {
+            whitelistIpcs(() -> {
+                try {
+                    mActivityManager.setHasTopUi(mHasTopUiChanged);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Failed to call setHasTopUi", e);
+                }
+                mHasTopUi = mHasTopUiChanged;
+            });
+        }
+        notifyStateChangedCallbacks();
+    }
+
+    public void notifyStateChangedCallbacks() {
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            StatusBarWindowCallback cb = mCallbacks.get(i).get();
+            if (cb != null) {
+                cb.onStateChanged(mCurrentState.mKeyguardShowing,
+                        mCurrentState.mKeyguardOccluded,
+                        mCurrentState.mBouncerShowing);
+            }
+        }
+    }
+
+    private void applyModalFlag(State state) {
+        if (state.mHeadsUpShowing) {
+            mLpChanged.flags |= LayoutParams.FLAG_NOT_TOUCH_MODAL;
+        } else {
+            mLpChanged.flags &= ~LayoutParams.FLAG_NOT_TOUCH_MODAL;
+        }
+    }
+
+    private void applyBrightness(State state) {
+        if (state.mForceDozeBrightness) {
+            mLpChanged.screenBrightness = mScreenBrightnessDoze;
+        } else {
+            mLpChanged.screenBrightness = LayoutParams.BRIGHTNESS_OVERRIDE_NONE;
+        }
+    }
+
+    private void applyHasTopUi(State state) {
+        mHasTopUiChanged = state.mForceHasTopUi || isExpanded(state);
+    }
+
+    private void applyNotTouchable(State state) {
+        if (state.mNotTouchable) {
+            mLpChanged.flags |= LayoutParams.FLAG_NOT_TOUCHABLE;
+        } else {
+            mLpChanged.flags &= ~LayoutParams.FLAG_NOT_TOUCHABLE;
+        }
+    }
+
+    public void setKeyguardShowing(boolean showing) {
+        mCurrentState.mKeyguardShowing = showing;
+        apply(mCurrentState);
+    }
+
+    public void setKeyguardOccluded(boolean occluded) {
+        mCurrentState.mKeyguardOccluded = occluded;
+        apply(mCurrentState);
+    }
+
+    public void setKeyguardNeedsInput(boolean needsInput) {
+        mCurrentState.mKeyguardNeedsInput = needsInput;
+        apply(mCurrentState);
+    }
+
+    public void setPanelVisible(boolean visible) {
+        mCurrentState.mPanelVisible = visible;
+        mCurrentState.mNotificationShadeFocusable = visible;
+        apply(mCurrentState);
+    }
+
+    public void setNotificationShadeFocusable(boolean focusable) {
+        mCurrentState.mNotificationShadeFocusable = focusable;
+        apply(mCurrentState);
+    }
+
+    public void setBouncerShowing(boolean showing) {
+        mCurrentState.mBouncerShowing = showing;
+        apply(mCurrentState);
+    }
+
+    public void setBackdropShowing(boolean showing) {
+        mCurrentState.mBackdropShowing = showing;
+        apply(mCurrentState);
+    }
+
+    public void setKeyguardFadingAway(boolean keyguardFadingAway) {
+        mCurrentState.mKeyguardFadingAway = keyguardFadingAway;
+        apply(mCurrentState);
+    }
+
+    public void setQsExpanded(boolean expanded) {
+        mCurrentState.mQsExpanded = expanded;
+        apply(mCurrentState);
+    }
+
+    public void setForceUserActivity(boolean forceUserActivity) {
+        mCurrentState.mForceUserActivity = forceUserActivity;
+        apply(mCurrentState);
+    }
+
+    public void setScrimsVisibility(int scrimsVisibility) {
+        mCurrentState.mScrimsVisibility = scrimsVisibility;
+        apply(mCurrentState);
+    }
+
+    public void setHeadsUpShowing(boolean showing) {
+        mCurrentState.mHeadsUpShowing = showing;
+        apply(mCurrentState);
+    }
+
+    public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
+        mCurrentState.mWallpaperSupportsAmbientMode = supportsAmbientMode;
+        apply(mCurrentState);
+    }
+
+    /**
+     * @param state The {@link StatusBarStateController} of the status bar.
+     */
+    private void setStatusBarState(int state) {
+        mCurrentState.mStatusBarState = state;
+        apply(mCurrentState);
+    }
+
+    /**
+     * Force the window to be collapsed, even if it should theoretically be expanded.
+     * Used for when a heads-up comes in but we still need to wait for the touchable regions to
+     * be computed.
+     */
+    public void setForceWindowCollapsed(boolean force) {
+        mCurrentState.mForceCollapsed = force;
+        apply(mCurrentState);
+    }
+
+    public void setPanelExpanded(boolean isExpanded) {
+        mCurrentState.mPanelExpanded = isExpanded;
+        apply(mCurrentState);
+    }
+
+    @Override
+    public void onRemoteInputActive(boolean remoteInputActive) {
+        mCurrentState.mRemoteInputActive = remoteInputActive;
+        apply(mCurrentState);
+    }
+
+    /**
+     * Set whether the screen brightness is forced to the value we use for doze mode by the status
+     * bar window.
+     */
+    public void setForceDozeBrightness(boolean forceDozeBrightness) {
+        mCurrentState.mForceDozeBrightness = forceDozeBrightness;
+        apply(mCurrentState);
+    }
+
+    public void setDozing(boolean dozing) {
+        mCurrentState.mDozing = dozing;
+        apply(mCurrentState);
+    }
+
+    public void setForcePluginOpen(boolean forcePluginOpen) {
+        mCurrentState.mForcePluginOpen = forcePluginOpen;
+        apply(mCurrentState);
+        if (mForcePluginOpenListener != null) {
+            mForcePluginOpenListener.onChange(forcePluginOpen);
+        }
+    }
+
+    /**
+     * The forcePluginOpen state for the status bar.
+     */
+    public boolean getForcePluginOpen() {
+        return mCurrentState.mForcePluginOpen;
+    }
+
+    public void setNotTouchable(boolean notTouchable) {
+        mCurrentState.mNotTouchable = notTouchable;
+        apply(mCurrentState);
+    }
+
+    /**
+     * Sets whether there are bubbles showing on the screen.
+     */
+    public void setBubblesShowing(boolean bubblesShowing) {
+        mCurrentState.mBubblesShowing = bubblesShowing;
+        apply(mCurrentState);
+    }
+
+    /**
+     * The bubbles showing state for the status bar.
+     */
+    public boolean getBubblesShowing() {
+        return mCurrentState.mBubblesShowing;
+    }
+
+    /**
+     * Sets if there is a bubble being expanded on the screen.
+     */
+    public void setBubbleExpanded(boolean bubbleExpanded) {
+        mCurrentState.mBubbleExpanded = bubbleExpanded;
+        apply(mCurrentState);
+    }
+
+    /**
+     * Whether the bubble is shown in expanded state for the status bar.
+     */
+    public boolean getBubbleExpanded() {
+        return mCurrentState.mBubbleExpanded;
+    }
+
+    /**
+     * Whether the status bar panel is expanded or not.
+     */
+    public boolean getPanelExpanded() {
+        return mCurrentState.mPanelExpanded;
+    }
+
+    public void setStateListener(OtherwisedCollapsedListener listener) {
+        mListener = listener;
+    }
+
+    public void setForcePluginOpenListener(ForcePluginOpenListener listener) {
+        mForcePluginOpenListener = listener;
+    }
+
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("StatusBarWindowController:");
+        pw.println("  mKeyguardDisplayMode=" + mKeyguardDisplayMode);
+        pw.println(mCurrentState);
+    }
+
+    public boolean isShowingWallpaper() {
+        return !mCurrentState.mBackdropShowing;
+    }
+
+    @Override
+    public void onThemeChanged() {
+        if (mNotificationShadeView == null) {
+            return;
+        }
+
+        final boolean useDarkText = mColorExtractor.getNeutralColors().supportsDarkText();
+        // Make sure we have the correct navbar/statusbar colors.
+        setKeyguardDark(useDarkText);
+    }
+
+    /**
+     * When keyguard will be dismissed but didn't start animation yet.
+     */
+    public void setKeyguardGoingAway(boolean goingAway) {
+        mCurrentState.mKeyguardGoingAway = goingAway;
+        apply(mCurrentState);
+    }
+
+    public boolean getForceHasTopUi() {
+        return mCurrentState.mForceHasTopUi;
+    }
+
+    public void setForceHasTopUi(boolean forceHasTopUi) {
+        mCurrentState.mForceHasTopUi = forceHasTopUi;
+        apply(mCurrentState);
+    }
+
+    private static class State {
+        boolean mKeyguardShowing;
+        boolean mKeyguardOccluded;
+        boolean mKeyguardNeedsInput;
+        boolean mPanelVisible;
+        boolean mPanelExpanded;
+        boolean mNotificationShadeFocusable;
+        boolean mBouncerShowing;
+        boolean mKeyguardFadingAway;
+        boolean mKeyguardGoingAway;
+        boolean mQsExpanded;
+        boolean mHeadsUpShowing;
+        boolean mForceCollapsed;
+        boolean mForceDozeBrightness;
+        boolean mForceUserActivity;
+        boolean mBackdropShowing;
+        boolean mWallpaperSupportsAmbientMode;
+        boolean mNotTouchable;
+        boolean mBubblesShowing;
+        boolean mBubbleExpanded;
+        boolean mForceHasTopUi;
+
+        /**
+         * The {@link StatusBar} state from the status bar.
+         */
+        int mStatusBarState;
+
+        boolean mRemoteInputActive;
+        boolean mForcePluginOpen;
+        boolean mDozing;
+        int mScrimsVisibility;
+
+        private boolean isKeyguardShowingAndNotOccluded() {
+            return mKeyguardShowing && !mKeyguardOccluded;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder result = new StringBuilder();
+            String newLine = "\n";
+            result.append("Window State {");
+            result.append(newLine);
+
+            Field[] fields = this.getClass().getDeclaredFields();
+
+            // Print field names paired with their values
+            for (Field field : fields) {
+                result.append("  ");
+                try {
+                    result.append(field.getName());
+                    result.append(": ");
+                    //requires access to private field:
+                    result.append(field.get(this));
+                } catch (IllegalAccessException ex) {
+                }
+                result.append(newLine);
+            }
+            result.append("}");
+
+            return result.toString();
+        }
+    }
+
+    private final StateListener mStateListener = new StateListener() {
+        @Override
+        public void onStateChanged(int newState) {
+            setStatusBarState(newState);
+        }
+
+        @Override
+        public void onDozingChanged(boolean isDozing) {
+            setDozing(isDozing);
+        }
+    };
+
+    /**
+     * Custom listener to pipe data back to plugins about whether or not the status bar would be
+     * collapsed if not for the plugin.
+     * TODO: Find cleaner way to do this.
+     */
+    public interface OtherwisedCollapsedListener {
+        void setWouldOtherwiseCollapse(boolean otherwiseCollapse);
+    }
+
+    /**
+     * Listener to indicate forcePluginOpen has changed
+     */
+    public interface ForcePluginOpenListener {
+        /**
+         * Called when mState.forcePluginOpen is changed
+         */
+        void onChange(boolean forceOpen);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java
new file mode 100644
index 0000000..7650a3a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java
@@ -0,0 +1,616 @@
+/*
+ * 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.statusbar.phone;
+
+import static android.view.WindowInsets.Type.systemBars;
+
+import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
+import android.annotation.LayoutRes;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Insets;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.ActionMode;
+import android.view.DisplayCutout;
+import android.view.InputQueue;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.SurfaceHolder;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.WindowInsetsController;
+import android.widget.FrameLayout;
+
+import com.android.internal.view.FloatingActionMode;
+import com.android.internal.widget.FloatingToolbar;
+import com.android.systemui.R;
+
+/**
+ * Combined keyguard and notification panel view. Also holding backdrop and scrims.
+ */
+public class NotificationShadeWindowView extends FrameLayout {
+    public static final String TAG = "NotificationShadeWindowView";
+    public static final boolean DEBUG = StatusBar.DEBUG;
+
+    private int mRightInset = 0;
+    private int mLeftInset = 0;
+
+    // Implements the floating action mode for TextView's Cut/Copy/Past menu. Normally provided by
+    // DecorView, but since this is a special window we have to roll our own.
+    private View mFloatingActionModeOriginatingView;
+    private ActionMode mFloatingActionMode;
+    private FloatingToolbar mFloatingToolbar;
+    private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener;
+
+    private InteractionEventHandler mInteractionEventHandler;
+
+    public NotificationShadeWindowView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setMotionEventSplittingEnabled(false);
+    }
+
+    public NotificationPanelView getNotificationPanelView() {
+        return findViewById(R.id.notification_panel);
+    }
+
+    @Override
+    public WindowInsets onApplyWindowInsets(WindowInsets windowInsets) {
+        final Insets insets = windowInsets.getInsetsIgnoringVisibility(systemBars());
+        if (getFitsSystemWindows()) {
+            boolean paddingChanged = insets.top != getPaddingTop()
+                    || insets.bottom != getPaddingBottom();
+
+            int rightCutout = 0;
+            int leftCutout = 0;
+            DisplayCutout displayCutout = getRootWindowInsets().getDisplayCutout();
+            if (displayCutout != null) {
+                leftCutout = displayCutout.getSafeInsetLeft();
+                rightCutout = displayCutout.getSafeInsetRight();
+            }
+
+            int targetLeft = Math.max(insets.left, leftCutout);
+            int targetRight = Math.max(insets.right, rightCutout);
+
+            // Super-special right inset handling, because scrims and backdrop need to ignore it.
+            if (targetRight != mRightInset || targetLeft != mLeftInset) {
+                mRightInset = targetRight;
+                mLeftInset = targetLeft;
+                applyMargins();
+            }
+            // Drop top inset, and pass through bottom inset.
+            if (paddingChanged) {
+                setPadding(0, 0, 0, 0);
+            }
+        } else {
+            if (mRightInset != 0 || mLeftInset != 0) {
+                mRightInset = 0;
+                mLeftInset = 0;
+                applyMargins();
+            }
+            boolean changed = getPaddingLeft() != 0
+                    || getPaddingRight() != 0
+                    || getPaddingTop() != 0
+                    || getPaddingBottom() != 0;
+            if (changed) {
+                setPadding(0, 0, 0, 0);
+            }
+        }
+        return windowInsets;
+    }
+
+    private void applyMargins() {
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            View child = getChildAt(i);
+            if (child.getLayoutParams() instanceof LayoutParams) {
+                LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                if (!lp.ignoreRightInset
+                        && (lp.rightMargin != mRightInset || lp.leftMargin != mLeftInset)) {
+                    lp.rightMargin = mRightInset;
+                    lp.leftMargin = mLeftInset;
+                    child.requestLayout();
+                }
+            }
+        }
+    }
+
+    @Override
+    public FrameLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new LayoutParams(getContext(), attrs);
+    }
+
+    @Override
+    protected FrameLayout.LayoutParams generateDefaultLayoutParams() {
+        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        setWillNotDraw(!DEBUG);
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        if (mInteractionEventHandler.interceptMediaKey(event)) {
+            return true;
+        }
+
+        if (super.dispatchKeyEvent(event)) {
+            return true;
+        }
+
+        return mInteractionEventHandler.dispatchKeyEvent(event);
+    }
+
+    protected void setInteractionEventHandler(InteractionEventHandler listener) {
+        mInteractionEventHandler = listener;
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        Boolean result = mInteractionEventHandler.handleDispatchTouchEvent(ev);
+
+        return result != null ? result : super.dispatchTouchEvent(ev);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        boolean intercept = mInteractionEventHandler.shouldInterceptTouchEvent(ev);
+        if (!intercept) {
+            intercept = super.onInterceptTouchEvent(ev);
+        }
+        if (intercept) {
+            mInteractionEventHandler.didIntercept(ev);
+        }
+
+        return intercept;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        boolean handled = mInteractionEventHandler.handleTouchEvent(ev);
+
+        if (!handled) {
+            handled = super.onTouchEvent(ev);
+        }
+
+        if (!handled) {
+            mInteractionEventHandler.didNotHandleTouchEvent(ev);
+        }
+
+        return handled;
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        if (DEBUG) {
+            Paint pt = new Paint();
+            pt.setColor(0x80FFFF00);
+            pt.setStrokeWidth(12.0f);
+            pt.setStyle(Paint.Style.STROKE);
+            canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), pt);
+        }
+    }
+
+    class LayoutParams extends FrameLayout.LayoutParams {
+
+        public boolean ignoreRightInset;
+
+        LayoutParams(int width, int height) {
+            super(width, height);
+        }
+
+        LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+
+            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.StatusBarWindowView_Layout);
+            ignoreRightInset = a.getBoolean(
+                    R.styleable.StatusBarWindowView_Layout_ignoreRightInset, false);
+            a.recycle();
+        }
+    }
+
+    @Override
+    public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback,
+            int type) {
+        if (type == ActionMode.TYPE_FLOATING) {
+            return startActionMode(originalView, callback, type);
+        }
+        return super.startActionModeForChild(originalView, callback, type);
+    }
+
+    private ActionMode createFloatingActionMode(
+            View originatingView, ActionMode.Callback2 callback) {
+        if (mFloatingActionMode != null) {
+            mFloatingActionMode.finish();
+        }
+        cleanupFloatingActionModeViews();
+        mFloatingToolbar = new FloatingToolbar(mFakeWindow);
+        final FloatingActionMode mode =
+                new FloatingActionMode(mContext, callback, originatingView, mFloatingToolbar);
+        mFloatingActionModeOriginatingView = originatingView;
+        mFloatingToolbarPreDrawListener =
+                new ViewTreeObserver.OnPreDrawListener() {
+                    @Override
+                    public boolean onPreDraw() {
+                        mode.updateViewLocationInWindow();
+                        return true;
+                    }
+                };
+        return mode;
+    }
+
+    private void setHandledFloatingActionMode(ActionMode mode) {
+        mFloatingActionMode = mode;
+        mFloatingActionMode.invalidate();  // Will show the floating toolbar if necessary.
+        mFloatingActionModeOriginatingView.getViewTreeObserver()
+                .addOnPreDrawListener(mFloatingToolbarPreDrawListener);
+    }
+
+    private void cleanupFloatingActionModeViews() {
+        if (mFloatingToolbar != null) {
+            mFloatingToolbar.dismiss();
+            mFloatingToolbar = null;
+        }
+        if (mFloatingActionModeOriginatingView != null) {
+            if (mFloatingToolbarPreDrawListener != null) {
+                mFloatingActionModeOriginatingView.getViewTreeObserver()
+                        .removeOnPreDrawListener(mFloatingToolbarPreDrawListener);
+                mFloatingToolbarPreDrawListener = null;
+            }
+            mFloatingActionModeOriginatingView = null;
+        }
+    }
+
+    private ActionMode startActionMode(
+            View originatingView, ActionMode.Callback callback, int type) {
+        ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback);
+        ActionMode mode = createFloatingActionMode(originatingView, wrappedCallback);
+        if (mode != null && wrappedCallback.onCreateActionMode(mode, mode.getMenu())) {
+            setHandledFloatingActionMode(mode);
+        } else {
+            mode = null;
+        }
+        return mode;
+    }
+
+    private class ActionModeCallback2Wrapper extends ActionMode.Callback2 {
+        private final ActionMode.Callback mWrapped;
+
+        ActionModeCallback2Wrapper(ActionMode.Callback wrapped) {
+            mWrapped = wrapped;
+        }
+
+        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+            return mWrapped.onCreateActionMode(mode, menu);
+        }
+
+        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+            requestFitSystemWindows();
+            return mWrapped.onPrepareActionMode(mode, menu);
+        }
+
+        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+            return mWrapped.onActionItemClicked(mode, item);
+        }
+
+        public void onDestroyActionMode(ActionMode mode) {
+            mWrapped.onDestroyActionMode(mode);
+            if (mode == mFloatingActionMode) {
+                cleanupFloatingActionModeViews();
+                mFloatingActionMode = null;
+            }
+            requestFitSystemWindows();
+        }
+
+        @Override
+        public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
+            if (mWrapped instanceof ActionMode.Callback2) {
+                ((ActionMode.Callback2) mWrapped).onGetContentRect(mode, view, outRect);
+            } else {
+                super.onGetContentRect(mode, view, outRect);
+            }
+        }
+    }
+
+    interface InteractionEventHandler {
+        /**
+         * Returns a result for {@link ViewGroup#dispatchTouchEvent(MotionEvent)} or null to defer
+         * to the super method.
+         */
+        Boolean handleDispatchTouchEvent(MotionEvent ev);
+
+        /**
+         * Returns if the view should intercept the touch event.
+         *
+         * The touch event may still be interecepted if
+         * {@link ViewGroup#onInterceptTouchEvent(MotionEvent)} decides to do so.
+         */
+        boolean shouldInterceptTouchEvent(MotionEvent ev);
+
+        /**
+         * Called when the view decides to intercept the touch event.
+         */
+        void didIntercept(MotionEvent ev);
+
+        boolean handleTouchEvent(MotionEvent ev);
+
+        void didNotHandleTouchEvent(MotionEvent ev);
+
+        boolean interceptMediaKey(KeyEvent event);
+
+        boolean dispatchKeyEvent(KeyEvent event);
+    }
+
+    /**
+     * Minimal window to satisfy FloatingToolbar.
+     */
+    private Window mFakeWindow = new Window(mContext) {
+        @Override
+        public void takeSurface(SurfaceHolder.Callback2 callback) {
+        }
+
+        @Override
+        public void takeInputQueue(InputQueue.Callback callback) {
+        }
+
+        @Override
+        public boolean isFloating() {
+            return false;
+        }
+
+        @Override
+        public void alwaysReadCloseOnTouchAttr() {
+        }
+
+        @Override
+        public void setContentView(@LayoutRes int layoutResID) {
+        }
+
+        @Override
+        public void setContentView(View view) {
+        }
+
+        @Override
+        public void setContentView(View view, ViewGroup.LayoutParams params) {
+        }
+
+        @Override
+        public void addContentView(View view, ViewGroup.LayoutParams params) {
+        }
+
+        @Override
+        public void clearContentView() {
+        }
+
+        @Override
+        public View getCurrentFocus() {
+            return null;
+        }
+
+        @Override
+        public LayoutInflater getLayoutInflater() {
+            return null;
+        }
+
+        @Override
+        public void setTitle(CharSequence title) {
+        }
+
+        @Override
+        public void setTitleColor(@ColorInt int textColor) {
+        }
+
+        @Override
+        public void openPanel(int featureId, KeyEvent event) {
+        }
+
+        @Override
+        public void closePanel(int featureId) {
+        }
+
+        @Override
+        public void togglePanel(int featureId, KeyEvent event) {
+        }
+
+        @Override
+        public void invalidatePanelMenu(int featureId) {
+        }
+
+        @Override
+        public boolean performPanelShortcut(int featureId, int keyCode, KeyEvent event, int flags) {
+            return false;
+        }
+
+        @Override
+        public boolean performPanelIdentifierAction(int featureId, int id, int flags) {
+            return false;
+        }
+
+        @Override
+        public void closeAllPanels() {
+        }
+
+        @Override
+        public boolean performContextMenuIdentifierAction(int id, int flags) {
+            return false;
+        }
+
+        @Override
+        public void onConfigurationChanged(Configuration newConfig) {
+        }
+
+        @Override
+        public void setBackgroundDrawable(Drawable drawable) {
+        }
+
+        @Override
+        public void setFeatureDrawableResource(int featureId, @DrawableRes int resId) {
+        }
+
+        @Override
+        public void setFeatureDrawableUri(int featureId, Uri uri) {
+        }
+
+        @Override
+        public void setFeatureDrawable(int featureId, Drawable drawable) {
+        }
+
+        @Override
+        public void setFeatureDrawableAlpha(int featureId, int alpha) {
+        }
+
+        @Override
+        public void setFeatureInt(int featureId, int value) {
+        }
+
+        @Override
+        public void takeKeyEvents(boolean get) {
+        }
+
+        @Override
+        public boolean superDispatchKeyEvent(KeyEvent event) {
+            return false;
+        }
+
+        @Override
+        public boolean superDispatchKeyShortcutEvent(KeyEvent event) {
+            return false;
+        }
+
+        @Override
+        public boolean superDispatchTouchEvent(MotionEvent event) {
+            return false;
+        }
+
+        @Override
+        public boolean superDispatchTrackballEvent(MotionEvent event) {
+            return false;
+        }
+
+        @Override
+        public boolean superDispatchGenericMotionEvent(MotionEvent event) {
+            return false;
+        }
+
+        @Override
+        public View getDecorView() {
+            return NotificationShadeWindowView.this;
+        }
+
+        @Override
+        public View peekDecorView() {
+            return null;
+        }
+
+        @Override
+        public Bundle saveHierarchyState() {
+            return null;
+        }
+
+        @Override
+        public void restoreHierarchyState(Bundle savedInstanceState) {
+        }
+
+        @Override
+        protected void onActive() {
+        }
+
+        @Override
+        public void setChildDrawable(int featureId, Drawable drawable) {
+        }
+
+        @Override
+        public void setChildInt(int featureId, int value) {
+        }
+
+        @Override
+        public boolean isShortcutKey(int keyCode, KeyEvent event) {
+            return false;
+        }
+
+        @Override
+        public void setVolumeControlStream(int streamType) {
+        }
+
+        @Override
+        public int getVolumeControlStream() {
+            return 0;
+        }
+
+        @Override
+        public int getStatusBarColor() {
+            return 0;
+        }
+
+        @Override
+        public void setStatusBarColor(@ColorInt int color) {
+        }
+
+        @Override
+        public int getNavigationBarColor() {
+            return 0;
+        }
+
+        @Override
+        public void setNavigationBarColor(@ColorInt int color) {
+        }
+
+        @Override
+        public void setDecorCaptionShade(int decorCaptionShade) {
+        }
+
+        @Override
+        public void setResizingCaptionDrawable(Drawable drawable) {
+        }
+
+        @Override
+        public void onMultiWindowModeChanged() {
+        }
+
+        @Override
+        public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+        }
+
+        @Override
+        public void reportActivityRelaunched() {
+        }
+
+        @Override
+        public WindowInsetsController getInsetsController() {
+            return null;
+        }
+    };
+
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
new file mode 100644
index 0000000..ab1c8ad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
@@ -0,0 +1,462 @@
+/*
+ * 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.statusbar.phone;
+
+import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
+
+import android.annotation.Nullable;
+import android.app.StatusBarManager;
+import android.graphics.RectF;
+import android.hardware.display.AmbientDisplayConfiguration;
+import android.media.AudioManager;
+import android.media.session.MediaSessionLegacyHelper;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.view.GestureDetector;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.ExpandHelper;
+import com.android.systemui.R;
+import com.android.systemui.dock.DockManager;
+import com.android.systemui.doze.DozeLog;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.DragDownHelper;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.PulseExpansionHandler;
+import com.android.systemui.statusbar.StatusBarWindowBlurController;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.tuner.TunerService;
+import com.android.systemui.util.InjectionInflationController;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+import javax.inject.Inject;
+
+/**
+ * Controller for {@link NotificationShadeWindowView}.
+ */
+public class NotificationShadeWindowViewController {
+    private final InjectionInflationController mInjectionInflationController;
+    private final NotificationWakeUpCoordinator mCoordinator;
+    private final PulseExpansionHandler mPulseExpansionHandler;
+    private final DynamicPrivacyController mDynamicPrivacyController;
+    private final KeyguardBypassController mBypassController;
+    private final PluginManager mPluginManager;
+    private final FalsingManager mFalsingManager;
+    private final TunerService mTunerService;
+    private final NotificationLockscreenUserManager mNotificationLockscreenUserManager;
+    private final NotificationEntryManager mNotificationEntryManager;
+    private final KeyguardStateController mKeyguardStateController;
+    private final SysuiStatusBarStateController mStatusBarStateController;
+    private final DozeLog mDozeLog;
+    private final DozeParameters mDozeParameters;
+    private final CommandQueue mCommandQueue;
+    private final NotificationShadeWindowView mView;
+    private final ShadeController mShadeController;
+    private final StatusBarWindowBlurController mBlurController;
+
+    private GestureDetector mGestureDetector;
+    private View mBrightnessMirror;
+    private boolean mTouchActive;
+    private boolean mTouchCancelled;
+    private boolean mExpandAnimationPending;
+    private boolean mExpandAnimationRunning;
+    private NotificationStackScrollLayout mStackScrollLayout;
+    private PhoneStatusBarView mStatusBarView;
+    private StatusBar mService;
+    private DragDownHelper mDragDownHelper;
+    private boolean mDoubleTapEnabled;
+    private boolean mSingleTapEnabled;
+    private boolean mExpandingBelowNotch;
+    private final DockManager mDockManager;
+    private final NotificationPanelViewController mNotificationPanelViewController;
+
+    // Used for determining view / touch intersection
+    private int[] mTempLocation = new int[2];
+    private RectF mTempRect = new RectF();
+    private boolean mIsTrackingBarGesture = false;
+
+    @Inject
+    public NotificationShadeWindowViewController(
+            InjectionInflationController injectionInflationController,
+            NotificationWakeUpCoordinator coordinator,
+            PulseExpansionHandler pulseExpansionHandler,
+            DynamicPrivacyController dynamicPrivacyController,
+            KeyguardBypassController bypassController,
+            FalsingManager falsingManager,
+            PluginManager pluginManager,
+            TunerService tunerService,
+            NotificationLockscreenUserManager notificationLockscreenUserManager,
+            NotificationEntryManager notificationEntryManager,
+            KeyguardStateController keyguardStateController,
+            SysuiStatusBarStateController statusBarStateController,
+            DozeLog dozeLog,
+            DozeParameters dozeParameters,
+            CommandQueue commandQueue,
+            ShadeController shadeController,
+            DockManager dockManager,
+            @Nullable StatusBarWindowBlurController blurController,
+            NotificationShadeWindowView statusBarWindowView,
+            NotificationPanelViewController notificationPanelViewController) {
+        mInjectionInflationController = injectionInflationController;
+        mCoordinator = coordinator;
+        mPulseExpansionHandler = pulseExpansionHandler;
+        mDynamicPrivacyController = dynamicPrivacyController;
+        mBypassController = bypassController;
+        mFalsingManager = falsingManager;
+        mPluginManager = pluginManager;
+        mTunerService = tunerService;
+        mNotificationLockscreenUserManager = notificationLockscreenUserManager;
+        mNotificationEntryManager = notificationEntryManager;
+        mKeyguardStateController = keyguardStateController;
+        mStatusBarStateController = statusBarStateController;
+        mDozeLog = dozeLog;
+        mDozeParameters = dozeParameters;
+        mCommandQueue = commandQueue;
+        mView = statusBarWindowView;
+        mShadeController = shadeController;
+        mDockManager = dockManager;
+        mNotificationPanelViewController = notificationPanelViewController;
+        mBlurController = blurController;
+
+        // This view is not part of the newly inflated expanded status bar.
+        mBrightnessMirror = mView.findViewById(R.id.brightness_mirror);
+    }
+
+    /** Inflates the {@link R.layout#status_bar_expanded} layout and sets it up. */
+    public void setupExpandedStatusBar() {
+        mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller);
+
+        TunerService.Tunable tunable = (key, newValue) -> {
+            AmbientDisplayConfiguration configuration =
+                    new AmbientDisplayConfiguration(mView.getContext());
+            switch (key) {
+                case Settings.Secure.DOZE_DOUBLE_TAP_GESTURE:
+                    mDoubleTapEnabled = configuration.doubleTapGestureEnabled(
+                            UserHandle.USER_CURRENT);
+                    break;
+                case Settings.Secure.DOZE_TAP_SCREEN_GESTURE:
+                    mSingleTapEnabled = configuration.tapGestureEnabled(UserHandle.USER_CURRENT);
+            }
+        };
+        mTunerService.addTunable(tunable,
+                Settings.Secure.DOZE_DOUBLE_TAP_GESTURE,
+                Settings.Secure.DOZE_TAP_SCREEN_GESTURE);
+
+        GestureDetector.SimpleOnGestureListener gestureListener =
+                new GestureDetector.SimpleOnGestureListener() {
+                    @Override
+                    public boolean onSingleTapConfirmed(MotionEvent e) {
+                        if (mSingleTapEnabled && !mDockManager.isDocked()) {
+                            mService.wakeUpIfDozing(
+                                    SystemClock.uptimeMillis(), mView, "SINGLE_TAP");
+                            return true;
+                        }
+                        return false;
+                    }
+
+                    @Override
+                    public boolean onDoubleTap(MotionEvent e) {
+                        if (mDoubleTapEnabled || mSingleTapEnabled) {
+                            mService.wakeUpIfDozing(
+                                    SystemClock.uptimeMillis(), mView, "DOUBLE_TAP");
+                            return true;
+                        }
+                        return false;
+                    }
+                };
+        mGestureDetector = new GestureDetector(mView.getContext(), gestureListener);
+
+        mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() {
+            @Override
+            public Boolean handleDispatchTouchEvent(MotionEvent ev) {
+                boolean isDown = ev.getActionMasked() == MotionEvent.ACTION_DOWN;
+                boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP;
+                boolean isCancel = ev.getActionMasked() == MotionEvent.ACTION_CANCEL;
+
+                boolean expandingBelowNotch = mExpandingBelowNotch;
+                if (isUp || isCancel) {
+                    mExpandingBelowNotch = false;
+                }
+
+                // Reset manual touch dispatch state here but make sure the UP/CANCEL event still
+                // gets
+                // delivered.
+
+                if (!isCancel && mService.shouldIgnoreTouch()) {
+                    return false;
+                }
+                if (isDown && mNotificationPanelViewController.isFullyCollapsed()) {
+                    mNotificationPanelViewController.startExpandLatencyTracking();
+                }
+                if (isDown) {
+                    setTouchActive(true);
+                    mTouchCancelled = false;
+                } else if (ev.getActionMasked() == MotionEvent.ACTION_UP
+                        || ev.getActionMasked() == MotionEvent.ACTION_CANCEL) {
+                    setTouchActive(false);
+                }
+                if (mTouchCancelled || mExpandAnimationRunning || mExpandAnimationPending) {
+                    return false;
+                }
+                mFalsingManager.onTouchEvent(ev, mView.getWidth(), mView.getHeight());
+                mGestureDetector.onTouchEvent(ev);
+                if (mBrightnessMirror != null
+                        && mBrightnessMirror.getVisibility() == View.VISIBLE) {
+                    // Disallow new pointers while the brightness mirror is visible. This is so that
+                    // you can't touch anything other than the brightness slider while the mirror is
+                    // showing and the rest of the panel is transparent.
+                    if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
+                        return false;
+                    }
+                }
+                if (isDown) {
+                    mStackScrollLayout.closeControlsIfOutsideTouch(ev);
+                }
+                if (mStatusBarStateController.isDozing()) {
+                    mService.mDozeScrimController.extendPulse();
+                }
+                // In case we start outside of the view bounds (below the status bar), we need to
+                // dispatch
+                // the touch manually as the view system can't accommodate for touches outside of
+                // the
+                // regular view bounds.
+                if (isDown && ev.getY() >= mView.getBottom()) {
+                    mExpandingBelowNotch = true;
+                    expandingBelowNotch = true;
+                }
+                if (expandingBelowNotch) {
+                    return mStatusBarView.dispatchTouchEvent(ev);
+                }
+
+                if (!mIsTrackingBarGesture && isDown
+                        && mNotificationPanelViewController.isFullyCollapsed()) {
+                    float x = ev.getRawX();
+                    float y = ev.getRawY();
+                    if (isIntersecting(mStatusBarView, x, y)) {
+                        if (mService.isSameStatusBarState(WINDOW_STATE_SHOWING)) {
+                            mIsTrackingBarGesture = true;
+                            return mStatusBarView.dispatchTouchEvent(ev);
+                        } else { // it's hidden or hiding, don't send to notification shade.
+                            return true;
+                        }
+                    }
+                } else if (mIsTrackingBarGesture) {
+                    final boolean sendToNotification = mStatusBarView.dispatchTouchEvent(ev);
+                    if (isUp || isCancel) {
+                        mIsTrackingBarGesture = false;
+                    }
+                    return sendToNotification;
+                }
+
+                return null;
+            }
+
+            @Override
+            public boolean shouldInterceptTouchEvent(MotionEvent ev) {
+                if (mStatusBarStateController.isDozing() && !mService.isPulsing()
+                        && !mDockManager.isDocked()) {
+                    // Capture all touch events in always-on.
+                    return true;
+                }
+                boolean intercept = false;
+                if (mNotificationPanelViewController.isFullyExpanded()
+                        && mDragDownHelper.isDragDownEnabled()
+                        && !mService.isBouncerShowing()
+                        && !mStatusBarStateController.isDozing()) {
+                    intercept = mDragDownHelper.onInterceptTouchEvent(ev);
+                }
+
+                return intercept;
+
+            }
+
+            @Override
+            public void didIntercept(MotionEvent ev) {
+                MotionEvent cancellation = MotionEvent.obtain(ev);
+                cancellation.setAction(MotionEvent.ACTION_CANCEL);
+                mStackScrollLayout.onInterceptTouchEvent(cancellation);
+                mNotificationPanelViewController.getView().onInterceptTouchEvent(cancellation);
+                cancellation.recycle();
+            }
+
+            @Override
+            public boolean handleTouchEvent(MotionEvent ev) {
+                boolean handled = false;
+                if (mStatusBarStateController.isDozing()) {
+                    handled = !mService.isPulsing();
+                }
+                if ((mDragDownHelper.isDragDownEnabled() && !handled)
+                        || mDragDownHelper.isDraggingDown()) {
+                    // we still want to finish our drag down gesture when locking the screen
+                    handled = mDragDownHelper.onTouchEvent(ev);
+                }
+
+                return handled;
+            }
+
+            @Override
+            public void didNotHandleTouchEvent(MotionEvent ev) {
+                final int action = ev.getActionMasked();
+                if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+                    mService.setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
+                }
+            }
+
+            @Override
+            public boolean interceptMediaKey(KeyEvent event) {
+                return mService.interceptMediaKey(event);
+            }
+
+            @Override
+            public boolean dispatchKeyEvent(KeyEvent event) {
+                boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
+                switch (event.getKeyCode()) {
+                    case KeyEvent.KEYCODE_BACK:
+                        if (!down) {
+                            mService.onBackPressed();
+                        }
+                        return true;
+                    case KeyEvent.KEYCODE_MENU:
+                        if (!down) {
+                            return mService.onMenuPressed();
+                        }
+                        break;
+                    case KeyEvent.KEYCODE_SPACE:
+                        if (!down) {
+                            return mService.onSpacePressed();
+                        }
+                        break;
+                    case KeyEvent.KEYCODE_VOLUME_DOWN:
+                    case KeyEvent.KEYCODE_VOLUME_UP:
+                        if (mStatusBarStateController.isDozing()) {
+                            MediaSessionLegacyHelper.getHelper(mView.getContext())
+                                    .sendVolumeKeyEvent(
+                                            event, AudioManager.USE_DEFAULT_STREAM_TYPE, true);
+                            return true;
+                        }
+                        break;
+                }
+                return false;
+            }
+        });
+
+        mView.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
+            @Override
+            public void onChildViewAdded(View parent, View child) {
+                if (child.getId() == R.id.brightness_mirror) {
+                    mBrightnessMirror = child;
+                }
+            }
+
+            @Override
+            public void onChildViewRemoved(View parent, View child) {
+            }
+        });
+
+        ExpandHelper.Callback expandHelperCallback = mStackScrollLayout.getExpandHelperCallback();
+        DragDownHelper.DragDownCallback dragDownCallback = mStackScrollLayout.getDragDownCallback();
+        setDragDownHelper(
+                new DragDownHelper(
+                        mView.getContext(), mView, expandHelperCallback,
+                        dragDownCallback, mFalsingManager));
+
+        if (mBlurController != null) {
+            mBlurController.setRoot(mView);
+            mNotificationPanelViewController.addExpansionListener(mBlurController);
+        }
+    }
+
+    public NotificationShadeWindowView getView() {
+        return mView;
+    }
+
+    public void setTouchActive(boolean touchActive) {
+        mTouchActive = touchActive;
+    }
+
+    public void cancelCurrentTouch() {
+        if (mTouchActive) {
+            final long now = SystemClock.uptimeMillis();
+            MotionEvent event = MotionEvent.obtain(now, now,
+                    MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
+            event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+            mView.dispatchTouchEvent(event);
+            event.recycle();
+            mTouchCancelled = true;
+        }
+    }
+
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.print("  mExpandAnimationPending=");
+        pw.println(mExpandAnimationPending);
+        pw.print("  mExpandAnimationRunning=");
+        pw.println(mExpandAnimationRunning);
+        pw.print("  mTouchCancelled=");
+        pw.println(mTouchCancelled);
+        pw.print("  mTouchActive=");
+        pw.println(mTouchActive);
+    }
+
+    public void setExpandAnimationPending(boolean pending) {
+        mExpandAnimationPending = pending;
+    }
+
+    public void setExpandAnimationRunning(boolean running) {
+        mExpandAnimationRunning = running;
+    }
+
+    public void cancelExpandHelper() {
+        if (mStackScrollLayout != null) {
+            mStackScrollLayout.cancelExpandHelper();
+        }
+    }
+
+    public void setStatusBarView(PhoneStatusBarView statusBarView) {
+        mStatusBarView = statusBarView;
+    }
+
+    public void setService(StatusBar statusBar) {
+        mService = statusBar;
+    }
+
+    @VisibleForTesting
+    void setDragDownHelper(DragDownHelper dragDownHelper) {
+        mDragDownHelper = dragDownHelper;
+    }
+
+    private boolean isIntersecting(View view, float x, float y) {
+        mTempLocation = view.getLocationOnScreen();
+        mTempRect.set(mTempLocation[0], mTempLocation[1], mTempLocation[0] + view.getWidth(),
+                mTempLocation[1] + view.getHeight());
+        return mTempRect.contains(x, y);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
index 063d00b..8d8c8da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
@@ -43,7 +43,7 @@
     public static final int STATE_OPENING = 1;
     public static final int STATE_OPEN = 2;
 
-    PanelView mPanel;
+    PanelViewController mPanel;
     private int mState = STATE_CLOSED;
     private boolean mTracking;
 
@@ -83,7 +83,8 @@
         super.onFinishInflate();
     }
 
-    public void setPanel(PanelView pv) {
+    /** Set the PanelViewController */
+    public void setPanel(PanelViewController pv) {
         mPanel = pv;
         pv.setBar(this);
     }
@@ -96,7 +97,7 @@
         setImportantForAccessibility(important);
         updateVisibility();
 
-        if (mPanel != null) mPanel.setImportantForAccessibility(important);
+        if (mPanel != null) mPanel.getView().setImportantForAccessibility(important);
     }
 
     public float getExpansionFraction() {
@@ -108,7 +109,7 @@
     }
 
     protected void updateVisibility() {
-        mPanel.setVisibility(shouldPanelBeVisible() ? VISIBLE : INVISIBLE);
+        mPanel.getView().setVisibility(shouldPanelBeVisible() ? VISIBLE : INVISIBLE);
     }
 
     protected boolean shouldPanelBeVisible() {
@@ -131,7 +132,7 @@
         }
 
         if (event.getAction() == MotionEvent.ACTION_DOWN) {
-            final PanelView panel = mPanel;
+            final PanelViewController panel = mPanel;
             if (panel == null) {
                 // panel is not there, so we'll eat the gesture
                 Log.v(TAG, String.format("onTouch: no panel for touch at (%d,%d)",
@@ -149,7 +150,7 @@
                 return true;
             }
         }
-        return mPanel == null || mPanel.onTouchEvent(event);
+        return mPanel == null || mPanel.getView().dispatchTouchEvent(event);
     }
 
     public abstract void panelScrimMinFractionChanged(float minFraction);
@@ -163,7 +164,7 @@
         boolean fullyClosed = true;
         boolean fullyOpened = false;
         if (SPEW) LOG("panelExpansionChanged: start state=%d", mState);
-        PanelView pv = mPanel;
+        PanelViewController pv = mPanel;
         mExpanded = expanded;
         mPanelFraction = frac;
         updateVisibility();
@@ -192,7 +193,7 @@
 
     public void collapsePanel(boolean animate, boolean delayed, float speedUpFactor) {
         boolean waiting = false;
-        PanelView pv = mPanel;
+        PanelViewController pv = mPanel;
         if (animate && !pv.isFullyCollapsed()) {
             pv.collapse(delayed, speedUpFactor);
             waiting = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index cd56d06..2719a32 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -16,1255 +16,62 @@
 
 package com.android.systemui.statusbar.phone;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.os.SystemClock;
-import android.os.VibrationEffect;
 import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.InputDevice;
 import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewTreeObserver;
-import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
 
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.util.LatencyTracker;
-import com.android.systemui.DejankUtils;
-import com.android.systemui.Dependency;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.doze.DozeLog;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.statusbar.FlingAnimationUtils;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
 public abstract class PanelView extends FrameLayout {
     public static final boolean DEBUG = PanelBar.DEBUG;
     public static final String TAG = PanelView.class.getSimpleName();
-    private static final int INITIAL_OPENING_PEEK_DURATION = 200;
-    private static final int PEEK_ANIMATION_DURATION = 360;
-    private static final int NO_FIXED_DURATION = -1;
-    protected long mDownTime;
-    protected boolean mTouchSlopExceededBeforeDown;
-    private float mMinExpandHeight;
-    private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
-    private boolean mPanelUpdateWhenAnimatorEnds;
-    private boolean mVibrateOnOpening;
-    protected boolean mLaunchingNotification;
-    private int mFixedDuration = NO_FIXED_DURATION;
-    protected ArrayList<PanelExpansionListener> mExpansionListeners = new ArrayList<>();
-
-    private final void logf(String fmt, Object... args) {
-        Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
-    }
+    private PanelViewController.TouchHandler mTouchHandler;
 
     protected StatusBar mStatusBar;
     protected HeadsUpManagerPhone mHeadsUpManager;
 
-    private float mPeekHeight;
-    private float mHintDistance;
-    private float mInitialOffsetOnTouch;
-    private boolean mCollapsedAndHeadsUpOnDown;
-    private float mExpandedFraction = 0;
-    protected float mExpandedHeight = 0;
-    private boolean mPanelClosedOnDown;
-    private boolean mHasLayoutedSinceDown;
-    private float mUpdateFlingVelocity;
-    private boolean mUpdateFlingOnLayout;
-    private boolean mPeekTouching;
-    private boolean mJustPeeked;
-    private boolean mClosing;
-    protected boolean mTracking;
-    private boolean mTouchSlopExceeded;
-    private int mTrackingPointer;
     protected int mTouchSlop;
-    protected boolean mHintAnimationRunning;
-    private boolean mOverExpandedBeforeFling;
-    private boolean mTouchAboveFalsingThreshold;
-    private int mUnlockFalsingThreshold;
-    private boolean mTouchStartedInEmptyArea;
-    private boolean mMotionAborted;
-    private boolean mUpwardsWhenTresholdReached;
-    private boolean mAnimatingOnDown;
 
-    private ValueAnimator mHeightAnimator;
-    private ObjectAnimator mPeekAnimator;
-    private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
-    private FlingAnimationUtils mFlingAnimationUtils;
-    private FlingAnimationUtils mFlingAnimationUtilsClosing;
-    private FlingAnimationUtils mFlingAnimationUtilsDismissing;
-    private final FalsingManager mFalsingManager;
-    private final DozeLog mDozeLog;
-    private final VibratorHelper mVibratorHelper;
-
-    /**
-     * Whether an instant expand request is currently pending and we are just waiting for layout.
-     */
-    private boolean mInstantExpanding;
-    private boolean mAnimateAfterExpanding;
-
-    PanelBar mBar;
-
-    private String mViewName;
-    private float mInitialTouchY;
-    private float mInitialTouchX;
-    private boolean mTouchDisabled;
-
-    /**
-     * Whether or not the PanelView can be expanded or collapsed with a drag.
-     */
-    private boolean mNotificationsDragEnabled;
-
-    private Interpolator mBounceInterpolator;
     protected KeyguardBottomAreaView mKeyguardBottomArea;
+    private OnConfigurationChangedListener mOnConfigurationChangedListener;
 
-    /**
-     * Speed-up factor to be used when {@link #mFlingCollapseRunnable} runs the next time.
-     */
-    private float mNextCollapseSpeedUpFactor = 1.0f;
-
-    protected boolean mExpanding;
-    private boolean mGestureWaitForTouchSlop;
-    private boolean mIgnoreXTouchSlop;
-    private boolean mExpandLatencyTracking;
-    protected final KeyguardStateController mKeyguardStateController;
-    protected final SysuiStatusBarStateController mStatusBarStateController;
-
-    protected void onExpandingFinished() {
-        mBar.onExpandingFinished();
+    public PanelView(Context context) {
+        super(context);
     }
 
-    protected void onExpandingStarted() {
-    }
-
-    private void notifyExpandingStarted() {
-        if (!mExpanding) {
-            mExpanding = true;
-            onExpandingStarted();
-        }
-    }
-
-    protected final void notifyExpandingFinished() {
-        endClosing();
-        if (mExpanding) {
-            mExpanding = false;
-            onExpandingFinished();
-        }
-    }
-
-    private void runPeekAnimation(long duration, float peekHeight, boolean collapseWhenFinished) {
-        mPeekHeight = peekHeight;
-        if (DEBUG) logf("peek to height=%.1f", mPeekHeight);
-        if (mHeightAnimator != null) {
-            return;
-        }
-        if (mPeekAnimator != null) {
-            mPeekAnimator.cancel();
-        }
-        mPeekAnimator = ObjectAnimator.ofFloat(this, "expandedHeight", mPeekHeight)
-                .setDuration(duration);
-        mPeekAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
-        mPeekAnimator.addListener(new AnimatorListenerAdapter() {
-            private boolean mCancelled;
-
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                mCancelled = true;
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mPeekAnimator = null;
-                if (!mCancelled && collapseWhenFinished) {
-                    postOnAnimation(mPostCollapseRunnable);
-                }
-
-            }
-        });
-        notifyExpandingStarted();
-        mPeekAnimator.start();
-        mJustPeeked = true;
-    }
-
-    public PanelView(Context context, AttributeSet attrs, FalsingManager falsingManager,
-            DozeLog dozeLog, KeyguardStateController keyguardStateController,
-            SysuiStatusBarStateController statusBarStateController) {
+    public PanelView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mKeyguardStateController = keyguardStateController;
-        mStatusBarStateController = statusBarStateController;
-        DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
-        mFlingAnimationUtils = new FlingAnimationUtils(displayMetrics,
-                0.6f /* maxLengthSeconds */, 0.6f /* speedUpFactor */);
-        mFlingAnimationUtilsClosing = new FlingAnimationUtils(displayMetrics,
-                0.5f /* maxLengthSeconds */, 0.6f /* speedUpFactor */);
-        mFlingAnimationUtilsDismissing = new FlingAnimationUtils(displayMetrics,
-                0.5f /* maxLengthSeconds */, 0.2f /* speedUpFactor */, 0.6f /* x2 */,
-                0.84f /* y2 */);
-        mBounceInterpolator = new BounceInterpolator();
-        mFalsingManager = falsingManager;
-        mDozeLog = dozeLog;
-        mNotificationsDragEnabled =
-                getResources().getBoolean(R.bool.config_enableNotificationShadeDrag);
-        mVibratorHelper = Dependency.get(VibratorHelper.class);
-        mVibrateOnOpening = mContext.getResources().getBoolean(
-                R.bool.config_vibrateOnIconAnimation);
     }
 
-    protected void loadDimens() {
-        final Resources res = getContext().getResources();
-        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
-        mTouchSlop = configuration.getScaledTouchSlop();
-        mHintDistance = res.getDimension(R.dimen.hint_move_distance);
-        mUnlockFalsingThreshold = res.getDimensionPixelSize(R.dimen.unlock_falsing_threshold);
+    public PanelView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
     }
 
-    private void addMovement(MotionEvent event) {
-        // Add movement to velocity tracker using raw screen X and Y coordinates instead
-        // of window coordinates because the window frame may be moving at the same time.
-        float deltaX = event.getRawX() - event.getX();
-        float deltaY = event.getRawY() - event.getY();
-        event.offsetLocation(deltaX, deltaY);
-        mVelocityTracker.addMovement(event);
-        event.offsetLocation(-deltaX, -deltaY);
+    public PanelView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
     }
 
-    public void setTouchAndAnimationDisabled(boolean disabled) {
-        mTouchDisabled = disabled;
-        if (mTouchDisabled) {
-            cancelHeightAnimator();
-            if (mTracking) {
-                onTrackingStopped(true /* expanded */);
-            }
-            notifyExpandingFinished();
-        }
+    public void setOnTouchListener(PanelViewController.TouchHandler touchHandler) {
+        super.setOnTouchListener(touchHandler);
+        mTouchHandler = touchHandler;
     }
 
-    public void startExpandLatencyTracking() {
-        if (LatencyTracker.isEnabled(mContext)) {
-            LatencyTracker.getInstance(mContext).onActionStart(
-                    LatencyTracker.ACTION_EXPAND_PANEL);
-            mExpandLatencyTracking = true;
-        }
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        if (mInstantExpanding
-                || (mTouchDisabled && event.getActionMasked() != MotionEvent.ACTION_CANCEL)
-                || (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
-            return false;
-        }
-
-        // If dragging should not expand the notifications shade, then return false.
-        if (!mNotificationsDragEnabled) {
-            if (mTracking) {
-                // Turn off tracking if it's on or the shade can get stuck in the down position.
-                onTrackingStopped(true /* expand */);
-            }
-            return false;
-        }
-
-        // On expanding, single mouse click expands the panel instead of dragging.
-        if (isFullyCollapsed() && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
-            if (event.getAction() == MotionEvent.ACTION_UP) {
-                expand(true);
-            }
-            return true;
-        }
-
-        /*
-         * We capture touch events here and update the expand height here in case according to
-         * the users fingers. This also handles multi-touch.
-         *
-         * If the user just clicks shortly, we show a quick peek of the shade.
-         *
-         * Flinging is also enabled in order to open or close the shade.
-         */
-
-        int pointerIndex = event.findPointerIndex(mTrackingPointer);
-        if (pointerIndex < 0) {
-            pointerIndex = 0;
-            mTrackingPointer = event.getPointerId(pointerIndex);
-        }
-        final float x = event.getX(pointerIndex);
-        final float y = event.getY(pointerIndex);
-
-        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
-            mGestureWaitForTouchSlop = shouldGestureWaitForTouchSlop();
-            mIgnoreXTouchSlop = isFullyCollapsed() || shouldGestureIgnoreXTouchSlop(x, y);
-        }
-
-        switch (event.getActionMasked()) {
-            case MotionEvent.ACTION_DOWN:
-                startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
-                mJustPeeked = false;
-                mMinExpandHeight = 0.0f;
-                mPanelClosedOnDown = isFullyCollapsed();
-                mHasLayoutedSinceDown = false;
-                mUpdateFlingOnLayout = false;
-                mMotionAborted = false;
-                mPeekTouching = mPanelClosedOnDown;
-                mDownTime = SystemClock.uptimeMillis();
-                mTouchAboveFalsingThreshold = false;
-                mCollapsedAndHeadsUpOnDown = isFullyCollapsed()
-                        && mHeadsUpManager.hasPinnedHeadsUp();
-                addMovement(event);
-                if (!mGestureWaitForTouchSlop || (mHeightAnimator != null && !mHintAnimationRunning)
-                        || mPeekAnimator != null) {
-                    mTouchSlopExceeded = (mHeightAnimator != null && !mHintAnimationRunning)
-                            || mPeekAnimator != null || mTouchSlopExceededBeforeDown;
-                    cancelHeightAnimator();
-                    cancelPeek();
-                    onTrackingStarted();
-                }
-                if (isFullyCollapsed() && !mHeadsUpManager.hasPinnedHeadsUp()
-                        && !mStatusBar.isBouncerShowing()) {
-                    startOpening(event);
-                }
-                break;
-
-            case MotionEvent.ACTION_POINTER_UP:
-                final int upPointer = event.getPointerId(event.getActionIndex());
-                if (mTrackingPointer == upPointer) {
-                    // gesture is ongoing, find a new pointer to track
-                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
-                    final float newY = event.getY(newIndex);
-                    final float newX = event.getX(newIndex);
-                    mTrackingPointer = event.getPointerId(newIndex);
-                    startExpandMotion(newX, newY, true /* startTracking */, mExpandedHeight);
-                }
-                break;
-            case MotionEvent.ACTION_POINTER_DOWN:
-                if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
-                    mMotionAborted = true;
-                    endMotionEvent(event, x, y, true /* forceCancel */);
-                    return false;
-                }
-                break;
-            case MotionEvent.ACTION_MOVE:
-                addMovement(event);
-                float h = y - mInitialTouchY;
-
-                // If the panel was collapsed when touching, we only need to check for the
-                // y-component of the gesture, as we have no conflicting horizontal gesture.
-                if (Math.abs(h) > mTouchSlop
-                        && (Math.abs(h) > Math.abs(x - mInitialTouchX)
-                        || mIgnoreXTouchSlop)) {
-                    mTouchSlopExceeded = true;
-                    if (mGestureWaitForTouchSlop && !mTracking && !mCollapsedAndHeadsUpOnDown) {
-                        if (!mJustPeeked && mInitialOffsetOnTouch != 0f) {
-                            startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
-                            h = 0;
-                        }
-                        cancelHeightAnimator();
-                        onTrackingStarted();
-                    }
-                }
-                float newHeight = Math.max(0, h + mInitialOffsetOnTouch);
-                if (newHeight > mPeekHeight) {
-                    if (mPeekAnimator != null) {
-                        mPeekAnimator.cancel();
-                    }
-                    mJustPeeked = false;
-                } else if (mPeekAnimator == null && mJustPeeked) {
-                    // The initial peek has finished, but we haven't dragged as far yet, lets
-                    // speed it up by starting at the peek height.
-                    mInitialOffsetOnTouch = mExpandedHeight;
-                    mInitialTouchY = y;
-                    mMinExpandHeight = mExpandedHeight;
-                    mJustPeeked = false;
-                }
-                newHeight = Math.max(newHeight, mMinExpandHeight);
-                if (-h >= getFalsingThreshold()) {
-                    mTouchAboveFalsingThreshold = true;
-                    mUpwardsWhenTresholdReached = isDirectionUpwards(x, y);
-                }
-                if (!mJustPeeked && (!mGestureWaitForTouchSlop || mTracking) &&
-                        !isTrackingBlocked()) {
-                    setExpandedHeightInternal(newHeight);
-                }
-                break;
-
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL:
-                addMovement(event);
-                endMotionEvent(event, x, y, false /* forceCancel */);
-                break;
-        }
-        return !mGestureWaitForTouchSlop || mTracking;
-    }
-
-    private void startOpening(MotionEvent event) {
-        runPeekAnimation(INITIAL_OPENING_PEEK_DURATION, getOpeningHeight(),
-                false /* collapseWhenFinished */);
-        notifyBarPanelExpansionChanged();
-        maybeVibrateOnOpening();
-
-        //TODO: keyguard opens QS a different way; log that too?
-
-        // Log the position of the swipe that opened the panel
-        float width = mStatusBar.getDisplayWidth();
-        float height = mStatusBar.getDisplayHeight();
-        int rot = mStatusBar.getRotation();
-
-        mLockscreenGestureLogger.writeAtFractionalPosition(MetricsEvent.ACTION_PANEL_VIEW_EXPAND,
-                (int) (event.getX() / width * 100),
-                (int) (event.getY() / height * 100),
-                rot);
-    }
-
-    protected void maybeVibrateOnOpening() {
-        if (mVibrateOnOpening) {
-            mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
-        }
-    }
-
-    protected abstract float getOpeningHeight();
-
-    /**
-     * @return whether the swiping direction is upwards and above a 45 degree angle compared to the
-     * horizontal direction
-     */
-    private boolean isDirectionUpwards(float x, float y) {
-        float xDiff = x - mInitialTouchX;
-        float yDiff = y - mInitialTouchY;
-        if (yDiff >= 0) {
-            return false;
-        }
-        return Math.abs(yDiff) >= Math.abs(xDiff);
-    }
-
-    protected void startExpandingFromPeek() {
-        mStatusBar.handlePeekToExpandTransistion();
-    }
-
-    protected void startExpandMotion(float newX, float newY, boolean startTracking,
-            float expandedHeight) {
-        mInitialOffsetOnTouch = expandedHeight;
-        mInitialTouchY = newY;
-        mInitialTouchX = newX;
-        if (startTracking) {
-            mTouchSlopExceeded = true;
-            setExpandedHeight(mInitialOffsetOnTouch);
-            onTrackingStarted();
-        }
-    }
-
-    private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) {
-        mTrackingPointer = -1;
-        if ((mTracking && mTouchSlopExceeded)
-                || Math.abs(x - mInitialTouchX) > mTouchSlop
-                || Math.abs(y - mInitialTouchY) > mTouchSlop
-                || event.getActionMasked() == MotionEvent.ACTION_CANCEL
-                || forceCancel) {
-            mVelocityTracker.computeCurrentVelocity(1000);
-            float vel = mVelocityTracker.getYVelocity();
-            float vectorVel = (float) Math.hypot(
-                    mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
-
-            boolean expand = flingExpands(vel, vectorVel, x, y)
-                    || event.getActionMasked() == MotionEvent.ACTION_CANCEL
-                    || forceCancel;
-            mDozeLog.traceFling(expand, mTouchAboveFalsingThreshold,
-                    mStatusBar.isFalsingThresholdNeeded(),
-                    mStatusBar.isWakeUpComingFromTouch());
-                    // Log collapse gesture if on lock screen.
-                    if (!expand && mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
-                        float displayDensity = mStatusBar.getDisplayDensity();
-                        int heightDp = (int) Math.abs((y - mInitialTouchY) / displayDensity);
-                        int velocityDp = (int) Math.abs(vel / displayDensity);
-                        mLockscreenGestureLogger.write(
-                                MetricsEvent.ACTION_LS_UNLOCK,
-                                heightDp, velocityDp);
-                    }
-            fling(vel, expand, isFalseTouch(x, y));
-            onTrackingStopped(expand);
-            mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown;
-            if (mUpdateFlingOnLayout) {
-                mUpdateFlingVelocity = vel;
-            }
-        } else if (mPanelClosedOnDown && !mHeadsUpManager.hasPinnedHeadsUp() && !mTracking
-                && !mStatusBar.isBouncerShowing()
-                && !mKeyguardStateController.isKeyguardFadingAway()) {
-            long timePassed = SystemClock.uptimeMillis() - mDownTime;
-            if (timePassed < ViewConfiguration.getLongPressTimeout()) {
-                // Lets show the user that he can actually expand the panel
-                runPeekAnimation(PEEK_ANIMATION_DURATION, getPeekHeight(), true /* collapseWhenFinished */);
-            } else {
-                // We need to collapse the panel since we peeked to the small height.
-                postOnAnimation(mPostCollapseRunnable);
-            }
-        } else if (!mStatusBar.isBouncerShowing()) {
-            boolean expands = onEmptySpaceClick(mInitialTouchX);
-            onTrackingStopped(expands);
-        }
-
-        mVelocityTracker.clear();
-        mPeekTouching = false;
-    }
-
-    protected float getCurrentExpandVelocity() {
-        mVelocityTracker.computeCurrentVelocity(1000);
-        return mVelocityTracker.getYVelocity();
-    }
-
-    private int getFalsingThreshold() {
-        float factor = mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
-        return (int) (mUnlockFalsingThreshold * factor);
-    }
-
-    protected abstract boolean shouldGestureWaitForTouchSlop();
-
-    protected abstract boolean shouldGestureIgnoreXTouchSlop(float x, float y);
-
-    protected void onTrackingStopped(boolean expand) {
-        mTracking = false;
-        mBar.onTrackingStopped(expand);
-        notifyBarPanelExpansionChanged();
-    }
-
-    protected void onTrackingStarted() {
-        endClosing();
-        mTracking = true;
-        mBar.onTrackingStarted();
-        notifyExpandingStarted();
-        notifyBarPanelExpansionChanged();
+    public void setOnConfigurationChangedListener(OnConfigurationChangedListener listener) {
+        mOnConfigurationChangedListener = listener;
     }
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent event) {
-        if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled
-                || (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
-            return false;
-        }
-
-        /*
-         * If the user drags anywhere inside the panel we intercept it if the movement is
-         * upwards. This allows closing the shade from anywhere inside the panel.
-         *
-         * We only do this if the current content is scrolled to the bottom,
-         * i.e isScrolledToBottom() is true and therefore there is no conflicting scrolling gesture
-         * possible.
-         */
-        int pointerIndex = event.findPointerIndex(mTrackingPointer);
-        if (pointerIndex < 0) {
-            pointerIndex = 0;
-            mTrackingPointer = event.getPointerId(pointerIndex);
-        }
-        final float x = event.getX(pointerIndex);
-        final float y = event.getY(pointerIndex);
-        boolean scrolledToBottom = isScrolledToBottom();
-
-        switch (event.getActionMasked()) {
-            case MotionEvent.ACTION_DOWN:
-                mStatusBar.userActivity();
-                mAnimatingOnDown = mHeightAnimator != null;
-                mMinExpandHeight = 0.0f;
-                mDownTime = SystemClock.uptimeMillis();
-                if (mAnimatingOnDown && mClosing && !mHintAnimationRunning
-                        || mPeekAnimator != null) {
-                    cancelHeightAnimator();
-                    cancelPeek();
-                    mTouchSlopExceeded = true;
-                    return true;
-                }
-                mInitialTouchY = y;
-                mInitialTouchX = x;
-                mTouchStartedInEmptyArea = !isInContentBounds(x, y);
-                mTouchSlopExceeded = mTouchSlopExceededBeforeDown;
-                mJustPeeked = false;
-                mMotionAborted = false;
-                mPanelClosedOnDown = isFullyCollapsed();
-                mCollapsedAndHeadsUpOnDown = false;
-                mHasLayoutedSinceDown = false;
-                mUpdateFlingOnLayout = false;
-                mTouchAboveFalsingThreshold = false;
-                addMovement(event);
-                break;
-            case MotionEvent.ACTION_POINTER_UP:
-                final int upPointer = event.getPointerId(event.getActionIndex());
-                if (mTrackingPointer == upPointer) {
-                    // gesture is ongoing, find a new pointer to track
-                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
-                    mTrackingPointer = event.getPointerId(newIndex);
-                    mInitialTouchX = event.getX(newIndex);
-                    mInitialTouchY = event.getY(newIndex);
-                }
-                break;
-            case MotionEvent.ACTION_POINTER_DOWN:
-                if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
-                    mMotionAborted = true;
-                    mVelocityTracker.clear();
-                }
-                break;
-            case MotionEvent.ACTION_MOVE:
-                final float h = y - mInitialTouchY;
-                addMovement(event);
-                if (scrolledToBottom || mTouchStartedInEmptyArea || mAnimatingOnDown) {
-                    float hAbs = Math.abs(h);
-                    if ((h < -mTouchSlop || (mAnimatingOnDown && hAbs > mTouchSlop))
-                            && hAbs > Math.abs(x - mInitialTouchX)) {
-                        cancelHeightAnimator();
-                        startExpandMotion(x, y, true /* startTracking */, mExpandedHeight);
-                        return true;
-                    }
-                }
-                break;
-            case MotionEvent.ACTION_CANCEL:
-            case MotionEvent.ACTION_UP:
-                mVelocityTracker.clear();
-                break;
-        }
-        return false;
-    }
-
-    /**
-     * @return Whether a pair of coordinates are inside the visible view content bounds.
-     */
-    protected abstract boolean isInContentBounds(float x, float y);
-
-    protected void cancelHeightAnimator() {
-        if (mHeightAnimator != null) {
-            if (mHeightAnimator.isRunning()) {
-                mPanelUpdateWhenAnimatorEnds = false;
-            }
-            mHeightAnimator.cancel();
-        }
-        endClosing();
-    }
-
-    private void endClosing() {
-        if (mClosing) {
-            mClosing = false;
-            onClosingFinished();
-        }
-    }
-
-    protected boolean isScrolledToBottom() {
-        return true;
-    }
-
-    protected float getContentHeight() {
-        return mExpandedHeight;
+        return mTouchHandler.onInterceptTouchEvent(event);
     }
 
     @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        loadDimens();
+    public void dispatchConfigurationChanged(Configuration newConfig) {
+        super.dispatchConfigurationChanged(newConfig);
+        mOnConfigurationChangedListener.onConfigurationChanged(newConfig);
     }
 
-    @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        loadDimens();
-    }
-
-    /**
-     * @param vel the current vertical velocity of the motion
-     * @param vectorVel the length of the vectorial velocity
-     * @return whether a fling should expands the panel; contracts otherwise
-     */
-    protected boolean flingExpands(float vel, float vectorVel, float x, float y) {
-        if (mFalsingManager.isUnlockingDisabled()) {
-            return true;
-        }
-
-        if (isFalseTouch(x, y)) {
-            return true;
-        }
-        if (Math.abs(vectorVel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
-            return shouldExpandWhenNotFlinging();
-        } else {
-            return vel > 0;
-        }
-    }
-
-    protected boolean shouldExpandWhenNotFlinging() {
-        return getExpandedFraction() > 0.5f;
-    }
-
-    /**
-     * @param x the final x-coordinate when the finger was lifted
-     * @param y the final y-coordinate when the finger was lifted
-     * @return whether this motion should be regarded as a false touch
-     */
-    private boolean isFalseTouch(float x, float y) {
-        if (!mStatusBar.isFalsingThresholdNeeded()) {
-            return false;
-        }
-        if (mFalsingManager.isClassifierEnabled()) {
-            return mFalsingManager.isFalseTouch();
-        }
-        if (!mTouchAboveFalsingThreshold) {
-            return true;
-        }
-        if (mUpwardsWhenTresholdReached) {
-            return false;
-        }
-        return !isDirectionUpwards(x, y);
-    }
-
-    protected void fling(float vel, boolean expand) {
-        fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, false);
-    }
-
-    protected void fling(float vel, boolean expand, boolean expandBecauseOfFalsing) {
-        fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, expandBecauseOfFalsing);
-    }
-
-    protected void fling(float vel, boolean expand, float collapseSpeedUpFactor,
-            boolean expandBecauseOfFalsing) {
-        cancelPeek();
-        float target = expand ? getMaxPanelHeight() : 0;
-        if (!expand) {
-            mClosing = true;
-        }
-        flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
-    }
-
-    protected void flingToHeight(float vel, boolean expand, float target,
-            float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
-        // Hack to make the expand transition look nice when clear all button is visible - we make
-        // the animation only to the last notification, and then jump to the maximum panel height so
-        // clear all just fades in and the decelerating motion is towards the last notification.
-        final boolean clearAllExpandHack = expand && fullyExpandedClearAllVisible()
-                && mExpandedHeight < getMaxPanelHeight() - getClearAllHeight()
-                && !isClearAllVisible();
-        if (clearAllExpandHack) {
-            target = getMaxPanelHeight() - getClearAllHeight();
-        }
-        if (target == mExpandedHeight || getOverExpansionAmount() > 0f && expand) {
-            notifyExpandingFinished();
-            return;
-        }
-        mOverExpandedBeforeFling = getOverExpansionAmount() > 0f;
-        ValueAnimator animator = createHeightAnimator(target);
-        if (expand) {
-            if (expandBecauseOfFalsing && vel < 0) {
-                vel = 0;
-            }
-            mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, getHeight());
-            if (vel == 0) {
-                animator.setDuration(350);
-            }
-        } else {
-            if (shouldUseDismissingAnimation()) {
-                if (vel == 0) {
-                    animator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
-                    long duration = (long) (200 + mExpandedHeight / getHeight() * 100);
-                    animator.setDuration(duration);
-                } else {
-                    mFlingAnimationUtilsDismissing.apply(animator, mExpandedHeight, target, vel,
-                            getHeight());
-                }
-            } else {
-                mFlingAnimationUtilsClosing
-                        .apply(animator, mExpandedHeight, target, vel, getHeight());
-            }
-
-            // Make it shorter if we run a canned animation
-            if (vel == 0) {
-                animator.setDuration((long) (animator.getDuration() / collapseSpeedUpFactor));
-            }
-            if (mFixedDuration != NO_FIXED_DURATION) {
-                animator.setDuration(mFixedDuration);
-            }
-        }
-        animator.addListener(new AnimatorListenerAdapter() {
-            private boolean mCancelled;
-
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                mCancelled = true;
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                if (clearAllExpandHack && !mCancelled) {
-                    setExpandedHeightInternal(getMaxPanelHeight());
-                }
-                setAnimator(null);
-                if (!mCancelled) {
-                    notifyExpandingFinished();
-                }
-                notifyBarPanelExpansionChanged();
-            }
-        });
-        setAnimator(animator);
-        animator.start();
-    }
-
-    protected abstract boolean shouldUseDismissingAnimation();
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        mViewName = getResources().getResourceName(getId());
-    }
-
-    public String getName() {
-        return mViewName;
-    }
-
-    public void setExpandedHeight(float height) {
-        if (DEBUG) logf("setExpandedHeight(%.1f)", height);
-        setExpandedHeightInternal(height + getOverExpansionPixels());
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-        mStatusBar.onPanelLaidOut();
-        requestPanelHeightUpdate();
-        mHasLayoutedSinceDown = true;
-        if (mUpdateFlingOnLayout) {
-            abortAnimations();
-            fling(mUpdateFlingVelocity, true /* expands */);
-            mUpdateFlingOnLayout = false;
-        }
-    }
-
-    protected void requestPanelHeightUpdate() {
-        float currentMaxPanelHeight = getMaxPanelHeight();
-
-        if (isFullyCollapsed()) {
-            return;
-        }
-
-        if (currentMaxPanelHeight == mExpandedHeight) {
-            return;
-        }
-
-        if (mPeekAnimator != null || mPeekTouching) {
-            return;
-        }
-
-        if (mTracking && !isTrackingBlocked()) {
-            return;
-        }
-
-        if (mHeightAnimator != null) {
-            mPanelUpdateWhenAnimatorEnds = true;
-            return;
-        }
-
-        setExpandedHeight(currentMaxPanelHeight);
-    }
-
-    public void setExpandedHeightInternal(float h) {
-        if (mExpandLatencyTracking && h != 0f) {
-            DejankUtils.postAfterTraversal(() -> LatencyTracker.getInstance(mContext).onActionEnd(
-                    LatencyTracker.ACTION_EXPAND_PANEL));
-            mExpandLatencyTracking = false;
-        }
-        float fhWithoutOverExpansion = getMaxPanelHeight() - getOverExpansionAmount();
-        if (mHeightAnimator == null) {
-            float overExpansionPixels = Math.max(0, h - fhWithoutOverExpansion);
-            if (getOverExpansionPixels() != overExpansionPixels && mTracking) {
-                setOverExpansion(overExpansionPixels, true /* isPixels */);
-            }
-            mExpandedHeight = Math.min(h, fhWithoutOverExpansion) + getOverExpansionAmount();
-        } else {
-            mExpandedHeight = h;
-            if (mOverExpandedBeforeFling) {
-                setOverExpansion(Math.max(0, h - fhWithoutOverExpansion), false /* isPixels */);
-            }
-        }
-
-        // If we are closing the panel and we are almost there due to a slow decelerating
-        // interpolator, abort the animation.
-        if (mExpandedHeight < 1f && mExpandedHeight != 0f && mClosing) {
-            mExpandedHeight = 0f;
-            if (mHeightAnimator != null) {
-                mHeightAnimator.end();
-            }
-        }
-        mExpandedFraction = Math.min(1f,
-                fhWithoutOverExpansion == 0 ? 0 : mExpandedHeight / fhWithoutOverExpansion);
-        onHeightUpdated(mExpandedHeight);
-        notifyBarPanelExpansionChanged();
-    }
-
-    /**
-     * @return true if the panel tracking should be temporarily blocked; this is used when a
-     *         conflicting gesture (opening QS) is happening
-     */
-    protected abstract boolean isTrackingBlocked();
-
-    protected abstract void setOverExpansion(float overExpansion, boolean isPixels);
-
-    protected abstract void onHeightUpdated(float expandedHeight);
-
-    protected abstract float getOverExpansionAmount();
-
-    protected abstract float getOverExpansionPixels();
-
-    /**
-     * This returns the maximum height of the panel. Children should override this if their
-     * desired height is not the full height.
-     *
-     * @return the default implementation simply returns the maximum height.
-     */
-    protected abstract int getMaxPanelHeight();
-
-    public void setExpandedFraction(float frac) {
-        setExpandedHeight(getMaxPanelHeight() * frac);
-    }
-
-    public float getExpandedHeight() {
-        return mExpandedHeight;
-    }
-
-    public float getExpandedFraction() {
-        return mExpandedFraction;
-    }
-
-    public boolean isFullyExpanded() {
-        return mExpandedHeight >= getMaxPanelHeight();
-    }
-
-    public boolean isFullyCollapsed() {
-        return mExpandedFraction <= 0.0f;
-    }
-
-    public boolean isCollapsing() {
-        return mClosing || mLaunchingNotification;
-    }
-
-    public boolean isTracking() {
-        return mTracking;
-    }
-
-    public void setBar(PanelBar panelBar) {
-        mBar = panelBar;
-    }
-
-    public void collapse(boolean delayed, float speedUpFactor) {
-        if (DEBUG) logf("collapse: " + this);
-        if (canPanelBeCollapsed()) {
-            cancelHeightAnimator();
-            notifyExpandingStarted();
-
-            // Set after notifyExpandingStarted, as notifyExpandingStarted resets the closing state.
-            mClosing = true;
-            if (delayed) {
-                mNextCollapseSpeedUpFactor = speedUpFactor;
-                postDelayed(mFlingCollapseRunnable, 120);
-            } else {
-                fling(0, false /* expand */, speedUpFactor, false /* expandBecauseOfFalsing */);
-            }
-        }
-    }
-
-    public boolean canPanelBeCollapsed() {
-        return !isFullyCollapsed() && !mTracking && !mClosing;
-    }
-
-    private final Runnable mFlingCollapseRunnable = new Runnable() {
-        @Override
-        public void run() {
-            fling(0, false /* expand */, mNextCollapseSpeedUpFactor,
-                    false /* expandBecauseOfFalsing */);
-        }
-    };
-
-    public void cancelPeek() {
-        boolean cancelled = false;
-        if (mPeekAnimator != null) {
-            cancelled = true;
-            mPeekAnimator.cancel();
-        }
-
-        if (cancelled) {
-            // When peeking, we already tell mBar that we expanded ourselves. Make sure that we also
-            // notify mBar that we might have closed ourselves.
-            notifyBarPanelExpansionChanged();
-        }
-    }
-
-    public void expand(final boolean animate) {
-        if (!isFullyCollapsed() && !isCollapsing()) {
-            return;
-        }
-
-        mInstantExpanding = true;
-        mAnimateAfterExpanding = animate;
-        mUpdateFlingOnLayout = false;
-        abortAnimations();
-        cancelPeek();
-        if (mTracking) {
-            onTrackingStopped(true /* expands */); // The panel is expanded after this call.
-        }
-        if (mExpanding) {
-            notifyExpandingFinished();
-        }
-        notifyBarPanelExpansionChanged();
-
-        // Wait for window manager to pickup the change, so we know the maximum height of the panel
-        // then.
-        getViewTreeObserver().addOnGlobalLayoutListener(
-                new ViewTreeObserver.OnGlobalLayoutListener() {
-                    @Override
-                    public void onGlobalLayout() {
-                        if (!mInstantExpanding) {
-                            getViewTreeObserver().removeOnGlobalLayoutListener(this);
-                            return;
-                        }
-                        if (mStatusBar.getStatusBarWindow().getHeight()
-                                != mStatusBar.getStatusBarHeight()) {
-                            getViewTreeObserver().removeOnGlobalLayoutListener(this);
-                            if (mAnimateAfterExpanding) {
-                                notifyExpandingStarted();
-                                fling(0, true /* expand */);
-                            } else {
-                                setExpandedFraction(1f);
-                            }
-                            mInstantExpanding = false;
-                        }
-                    }
-                });
-
-        // Make sure a layout really happens.
-        requestLayout();
-    }
-
-    public void instantCollapse() {
-        abortAnimations();
-        setExpandedFraction(0f);
-        if (mExpanding) {
-            notifyExpandingFinished();
-        }
-        if (mInstantExpanding) {
-            mInstantExpanding = false;
-            notifyBarPanelExpansionChanged();
-        }
-    }
-
-    private void abortAnimations() {
-        cancelPeek();
-        cancelHeightAnimator();
-        removeCallbacks(mPostCollapseRunnable);
-        removeCallbacks(mFlingCollapseRunnable);
-    }
-
-    protected void onClosingFinished() {
-        mBar.onClosingFinished();
-    }
-
-
-    protected void startUnlockHintAnimation() {
-
-        // We don't need to hint the user if an animation is already running or the user is changing
-        // the expansion.
-        if (mHeightAnimator != null || mTracking) {
-            return;
-        }
-        cancelPeek();
-        notifyExpandingStarted();
-        startUnlockHintAnimationPhase1(() -> {
-            notifyExpandingFinished();
-            onUnlockHintFinished();
-            mHintAnimationRunning = false;
-        });
-        onUnlockHintStarted();
-        mHintAnimationRunning = true;
-    }
-
-    protected void onUnlockHintFinished() {
-        mStatusBar.onHintFinished();
-    }
-
-    protected void onUnlockHintStarted() {
-        mStatusBar.onUnlockHintStarted();
-    }
-
-    public boolean isUnlockHintRunning() {
-        return mHintAnimationRunning;
-    }
-
-    /**
-     * Phase 1: Move everything upwards.
-     */
-    private void startUnlockHintAnimationPhase1(final Runnable onAnimationFinished) {
-        float target = Math.max(0, getMaxPanelHeight() - mHintDistance);
-        ValueAnimator animator = createHeightAnimator(target);
-        animator.setDuration(250);
-        animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
-        animator.addListener(new AnimatorListenerAdapter() {
-            private boolean mCancelled;
-
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                mCancelled = true;
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                if (mCancelled) {
-                    setAnimator(null);
-                    onAnimationFinished.run();
-                } else {
-                    startUnlockHintAnimationPhase2(onAnimationFinished);
-                }
-            }
-        });
-        animator.start();
-        setAnimator(animator);
-
-        View[] viewsToAnimate = {
-                mKeyguardBottomArea.getIndicationArea(),
-                mStatusBar.getAmbientIndicationContainer()};
-        for (View v : viewsToAnimate) {
-            if (v == null) {
-                continue;
-            }
-            v.animate()
-                    .translationY(-mHintDistance)
-                    .setDuration(250)
-                    .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                    .withEndAction(() -> v.animate()
-                            .translationY(0)
-                            .setDuration(450)
-                            .setInterpolator(mBounceInterpolator)
-                            .start())
-                    .start();
-        }
-    }
-
-    private void setAnimator(ValueAnimator animator) {
-        mHeightAnimator = animator;
-        if (animator == null && mPanelUpdateWhenAnimatorEnds) {
-            mPanelUpdateWhenAnimatorEnds = false;
-            requestPanelHeightUpdate();
-        }
-    }
-
-    /**
-     * Phase 2: Bounce down.
-     */
-    private void startUnlockHintAnimationPhase2(final Runnable onAnimationFinished) {
-        ValueAnimator animator = createHeightAnimator(getMaxPanelHeight());
-        animator.setDuration(450);
-        animator.setInterpolator(mBounceInterpolator);
-        animator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                setAnimator(null);
-                onAnimationFinished.run();
-                notifyBarPanelExpansionChanged();
-            }
-        });
-        animator.start();
-        setAnimator(animator);
-    }
-
-    private ValueAnimator createHeightAnimator(float targetHeight) {
-        ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight);
-        animator.addUpdateListener(
-                animation -> setExpandedHeightInternal((float) animation.getAnimatedValue()));
-        return animator;
-    }
-
-    protected void notifyBarPanelExpansionChanged() {
-        if (mBar != null) {
-            mBar.panelExpansionChanged(mExpandedFraction, mExpandedFraction > 0f
-                    || mPeekAnimator != null || mInstantExpanding
-                    || isPanelVisibleBecauseOfHeadsUp() || mTracking || mHeightAnimator != null);
-        }
-        for (int i = 0; i < mExpansionListeners.size(); i++) {
-            mExpansionListeners.get(i).onPanelExpansionChanged(mExpandedFraction, mTracking);
-        }
-    }
-
-    public void addExpansionListener(PanelExpansionListener panelExpansionListener) {
-        mExpansionListeners.add(panelExpansionListener);
-    }
-
-    protected abstract boolean isPanelVisibleBecauseOfHeadsUp();
-
-    /**
-     * Gets called when the user performs a click anywhere in the empty area of the panel.
-     *
-     * @return whether the panel will be expanded after the action performed by this method
-     */
-    protected boolean onEmptySpaceClick(float x) {
-        if (mHintAnimationRunning) {
-            return true;
-        }
-        return onMiddleClicked();
-    }
-
-    protected final Runnable mPostCollapseRunnable = new Runnable() {
-        @Override
-        public void run() {
-            collapse(false /* delayed */, 1.0f /* speedUpFactor */);
-        }
-    };
-
-    protected abstract boolean onMiddleClicked();
-
-    protected abstract boolean isDozing();
-
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s"
-                + " tracking=%s justPeeked=%s peekAnim=%s%s timeAnim=%s%s touchDisabled=%s"
-                + "]",
-                this.getClass().getSimpleName(),
-                getExpandedHeight(),
-                getMaxPanelHeight(),
-                mClosing?"T":"f",
-                mTracking?"T":"f",
-                mJustPeeked?"T":"f",
-                mPeekAnimator, ((mPeekAnimator!=null && mPeekAnimator.isStarted())?" (started)":""),
-                mHeightAnimator, ((mHeightAnimator !=null && mHeightAnimator.isStarted())?" (started)":""),
-                mTouchDisabled?"T":"f"
-        ));
-    }
-
-    public abstract void resetViews(boolean animate);
-
-    protected abstract float getPeekHeight();
-    /**
-     * @return whether "Clear all" button will be visible when the panel is fully expanded
-     */
-    protected abstract boolean fullyExpandedClearAllVisible();
-
-    protected abstract boolean isClearAllVisible();
-
-    /**
-     * @return the height of the clear all button, in pixels
-     */
-    protected abstract int getClearAllHeight();
-
-    public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
-        mHeadsUpManager = headsUpManager;
-    }
-
-    public void setLaunchingNotification(boolean launchingNotification) {
-        mLaunchingNotification = launchingNotification;
-    }
-
-    public void collapseWithDuration(int animationDuration) {
-        mFixedDuration = animationDuration;
-        collapse(false /* delayed */, 1.0f /* speedUpFactor */);
-        mFixedDuration = NO_FIXED_DURATION;
+    interface OnConfigurationChangedListener {
+        void onConfigurationChanged(Configuration newConfig);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
new file mode 100644
index 0000000..af46f7b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -0,0 +1,1296 @@
+/*
+ * 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.statusbar.phone;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.SystemClock;
+import android.os.VibrationEffect;
+import android.util.Log;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.animation.Interpolator;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.LatencyTracker;
+import com.android.systemui.DejankUtils;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.doze.DozeLog;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.statusbar.FlingAnimationUtils;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+public abstract class PanelViewController {
+    public static final boolean DEBUG = PanelBar.DEBUG;
+    public static final String TAG = PanelView.class.getSimpleName();
+    private static final int INITIAL_OPENING_PEEK_DURATION = 200;
+    private static final int PEEK_ANIMATION_DURATION = 360;
+    private static final int NO_FIXED_DURATION = -1;
+    protected long mDownTime;
+    protected boolean mTouchSlopExceededBeforeDown;
+    private float mMinExpandHeight;
+    private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
+    private boolean mPanelUpdateWhenAnimatorEnds;
+    private boolean mVibrateOnOpening;
+    protected boolean mLaunchingNotification;
+    private int mFixedDuration = NO_FIXED_DURATION;
+    protected ArrayList<PanelExpansionListener> mExpansionListeners = new ArrayList<>();
+
+    private void logf(String fmt, Object... args) {
+        Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
+    }
+
+    protected StatusBar mStatusBar;
+    protected HeadsUpManagerPhone mHeadsUpManager;
+
+    private float mPeekHeight;
+    private float mHintDistance;
+    private float mInitialOffsetOnTouch;
+    private boolean mCollapsedAndHeadsUpOnDown;
+    private float mExpandedFraction = 0;
+    protected float mExpandedHeight = 0;
+    private boolean mPanelClosedOnDown;
+    private boolean mHasLayoutedSinceDown;
+    private float mUpdateFlingVelocity;
+    private boolean mUpdateFlingOnLayout;
+    private boolean mPeekTouching;
+    private boolean mJustPeeked;
+    private boolean mClosing;
+    protected boolean mTracking;
+    private boolean mTouchSlopExceeded;
+    private int mTrackingPointer;
+    protected int mTouchSlop;
+    protected boolean mHintAnimationRunning;
+    private boolean mOverExpandedBeforeFling;
+    private boolean mTouchAboveFalsingThreshold;
+    private int mUnlockFalsingThreshold;
+    private boolean mTouchStartedInEmptyArea;
+    private boolean mMotionAborted;
+    private boolean mUpwardsWhenThresholdReached;
+    private boolean mAnimatingOnDown;
+
+    private ValueAnimator mHeightAnimator;
+    private ObjectAnimator mPeekAnimator;
+    private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
+    private FlingAnimationUtils mFlingAnimationUtils;
+    private FlingAnimationUtils mFlingAnimationUtilsClosing;
+    private FlingAnimationUtils mFlingAnimationUtilsDismissing;
+    private final LatencyTracker mLatencyTracker;
+    private final FalsingManager mFalsingManager;
+    private final DozeLog mDozeLog;
+    private final VibratorHelper mVibratorHelper;
+
+    /**
+     * Whether an instant expand request is currently pending and we are just waiting for layout.
+     */
+    private boolean mInstantExpanding;
+    private boolean mAnimateAfterExpanding;
+
+    PanelBar mBar;
+
+    private String mViewName;
+    private float mInitialTouchY;
+    private float mInitialTouchX;
+    private boolean mTouchDisabled;
+
+    /**
+     * Whether or not the PanelView can be expanded or collapsed with a drag.
+     */
+    private boolean mNotificationsDragEnabled;
+
+    private Interpolator mBounceInterpolator;
+    protected KeyguardBottomAreaView mKeyguardBottomArea;
+
+    /**
+     * Speed-up factor to be used when {@link #mFlingCollapseRunnable} runs the next time.
+     */
+    private float mNextCollapseSpeedUpFactor = 1.0f;
+
+    protected boolean mExpanding;
+    private boolean mGestureWaitForTouchSlop;
+    private boolean mIgnoreXTouchSlop;
+    private boolean mExpandLatencyTracking;
+    private final PanelView mView;
+    protected final Resources mResources;
+    protected final KeyguardStateController mKeyguardStateController;
+    protected final SysuiStatusBarStateController mStatusBarStateController;
+
+    protected void onExpandingFinished() {
+        mBar.onExpandingFinished();
+    }
+
+    protected void onExpandingStarted() {
+    }
+
+    private void notifyExpandingStarted() {
+        if (!mExpanding) {
+            mExpanding = true;
+            onExpandingStarted();
+        }
+    }
+
+    protected final void notifyExpandingFinished() {
+        endClosing();
+        if (mExpanding) {
+            mExpanding = false;
+            onExpandingFinished();
+        }
+    }
+
+    private void runPeekAnimation(long duration, float peekHeight, boolean collapseWhenFinished) {
+        mPeekHeight = peekHeight;
+        if (DEBUG) logf("peek to height=%.1f", mPeekHeight);
+        if (mHeightAnimator != null) {
+            return;
+        }
+        if (mPeekAnimator != null) {
+            mPeekAnimator.cancel();
+        }
+        mPeekAnimator = ObjectAnimator.ofFloat(this, "expandedHeight", mPeekHeight).setDuration(
+                duration);
+        mPeekAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+        mPeekAnimator.addListener(new AnimatorListenerAdapter() {
+            private boolean mCancelled;
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                mCancelled = true;
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mPeekAnimator = null;
+                if (!mCancelled && collapseWhenFinished) {
+                    mView.postOnAnimation(mPostCollapseRunnable);
+                }
+
+            }
+        });
+        notifyExpandingStarted();
+        mPeekAnimator.start();
+        mJustPeeked = true;
+    }
+
+    public PanelViewController(PanelView view,
+            FalsingManager falsingManager, DozeLog dozeLog,
+            KeyguardStateController keyguardStateController,
+            SysuiStatusBarStateController statusBarStateController, VibratorHelper vibratorHelper,
+            LatencyTracker latencyTracker, FlingAnimationUtils.Builder flingAnimationUtilsBuilder) {
+        mView = view;
+        mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+            @Override
+            public void onViewAttachedToWindow(View v) {
+                mViewName = mResources.getResourceName(mView.getId());
+            }
+
+            @Override
+            public void onViewDetachedFromWindow(View v) {
+            }
+        });
+
+        mView.addOnLayoutChangeListener(createLayoutChangeListener());
+        mView.setOnTouchListener(createTouchHandler());
+        mView.setOnConfigurationChangedListener(createOnConfigurationChangedListener());
+
+        mResources = mView.getResources();
+        mKeyguardStateController = keyguardStateController;
+        mStatusBarStateController = statusBarStateController;
+        mFlingAnimationUtils = flingAnimationUtilsBuilder
+                .reset()
+                .setMaxLengthSeconds(0.6f)
+                .setSpeedUpFactor(0.6f)
+                .build();
+        mFlingAnimationUtilsClosing = flingAnimationUtilsBuilder
+                .reset()
+                .setMaxLengthSeconds(0.5f)
+                .setSpeedUpFactor(0.6f)
+                .build();
+        mFlingAnimationUtilsDismissing = flingAnimationUtilsBuilder
+                .reset()
+                .setMaxLengthSeconds(0.5f)
+                .setSpeedUpFactor(0.6f)
+                .setX2(0.6f)
+                .setY2(0.84f)
+                .build();
+        mLatencyTracker = latencyTracker;
+        mBounceInterpolator = new BounceInterpolator();
+        mFalsingManager = falsingManager;
+        mDozeLog = dozeLog;
+        mNotificationsDragEnabled = mResources.getBoolean(
+                R.bool.config_enableNotificationShadeDrag);
+        mVibratorHelper = vibratorHelper;
+        mVibrateOnOpening = mResources.getBoolean(R.bool.config_vibrateOnIconAnimation);
+    }
+
+    protected void loadDimens() {
+        final ViewConfiguration configuration = ViewConfiguration.get(mView.getContext());
+        mTouchSlop = configuration.getScaledTouchSlop();
+        mHintDistance = mResources.getDimension(R.dimen.hint_move_distance);
+        mUnlockFalsingThreshold = mResources.getDimensionPixelSize(
+                R.dimen.unlock_falsing_threshold);
+    }
+
+    private void addMovement(MotionEvent event) {
+        // Add movement to velocity tracker using raw screen X and Y coordinates instead
+        // of window coordinates because the window frame may be moving at the same time.
+        float deltaX = event.getRawX() - event.getX();
+        float deltaY = event.getRawY() - event.getY();
+        event.offsetLocation(deltaX, deltaY);
+        mVelocityTracker.addMovement(event);
+        event.offsetLocation(-deltaX, -deltaY);
+    }
+
+    public void setTouchAndAnimationDisabled(boolean disabled) {
+        mTouchDisabled = disabled;
+        if (mTouchDisabled) {
+            cancelHeightAnimator();
+            if (mTracking) {
+                onTrackingStopped(true /* expanded */);
+            }
+            notifyExpandingFinished();
+        }
+    }
+
+    public void startExpandLatencyTracking() {
+        if (mLatencyTracker.isEnabled()) {
+            mLatencyTracker.onActionStart(LatencyTracker.ACTION_EXPAND_PANEL);
+            mExpandLatencyTracking = true;
+        }
+    }
+
+    private void startOpening(MotionEvent event) {
+        runPeekAnimation(INITIAL_OPENING_PEEK_DURATION, getOpeningHeight(),
+                false /* collapseWhenFinished */);
+        notifyBarPanelExpansionChanged();
+        maybeVibrateOnOpening();
+
+        //TODO: keyguard opens QS a different way; log that too?
+
+        // Log the position of the swipe that opened the panel
+        float width = mStatusBar.getDisplayWidth();
+        float height = mStatusBar.getDisplayHeight();
+        int rot = mStatusBar.getRotation();
+
+        mLockscreenGestureLogger.writeAtFractionalPosition(MetricsEvent.ACTION_PANEL_VIEW_EXPAND,
+                (int) (event.getX() / width * 100), (int) (event.getY() / height * 100), rot);
+    }
+
+    protected void maybeVibrateOnOpening() {
+        if (mVibrateOnOpening) {
+            mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
+        }
+    }
+
+    protected abstract float getOpeningHeight();
+
+    /**
+     * @return whether the swiping direction is upwards and above a 45 degree angle compared to the
+     * horizontal direction
+     */
+    private boolean isDirectionUpwards(float x, float y) {
+        float xDiff = x - mInitialTouchX;
+        float yDiff = y - mInitialTouchY;
+        if (yDiff >= 0) {
+            return false;
+        }
+        return Math.abs(yDiff) >= Math.abs(xDiff);
+    }
+
+    protected void startExpandingFromPeek() {
+        mStatusBar.handlePeekToExpandTransistion();
+    }
+
+    protected void startExpandMotion(float newX, float newY, boolean startTracking,
+            float expandedHeight) {
+        mInitialOffsetOnTouch = expandedHeight;
+        mInitialTouchY = newY;
+        mInitialTouchX = newX;
+        if (startTracking) {
+            mTouchSlopExceeded = true;
+            setExpandedHeight(mInitialOffsetOnTouch);
+            onTrackingStarted();
+        }
+    }
+
+    private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) {
+        mTrackingPointer = -1;
+        if ((mTracking && mTouchSlopExceeded) || Math.abs(x - mInitialTouchX) > mTouchSlop
+                || Math.abs(y - mInitialTouchY) > mTouchSlop
+                || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
+            mVelocityTracker.computeCurrentVelocity(1000);
+            float vel = mVelocityTracker.getYVelocity();
+            float vectorVel = (float) Math.hypot(
+                    mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
+
+            boolean expand = flingExpands(vel, vectorVel, x, y)
+                    || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel;
+            mDozeLog.traceFling(expand, mTouchAboveFalsingThreshold,
+                    mStatusBar.isFalsingThresholdNeeded(), mStatusBar.isWakeUpComingFromTouch());
+            // Log collapse gesture if on lock screen.
+            if (!expand && mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+                float displayDensity = mStatusBar.getDisplayDensity();
+                int heightDp = (int) Math.abs((y - mInitialTouchY) / displayDensity);
+                int velocityDp = (int) Math.abs(vel / displayDensity);
+                mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_UNLOCK, heightDp, velocityDp);
+            }
+            fling(vel, expand, isFalseTouch(x, y));
+            onTrackingStopped(expand);
+            mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown;
+            if (mUpdateFlingOnLayout) {
+                mUpdateFlingVelocity = vel;
+            }
+        } else if (mPanelClosedOnDown && !mHeadsUpManager.hasPinnedHeadsUp() && !mTracking
+                && !mStatusBar.isBouncerShowing()
+                && !mKeyguardStateController.isKeyguardFadingAway()) {
+            long timePassed = SystemClock.uptimeMillis() - mDownTime;
+            if (timePassed < ViewConfiguration.getLongPressTimeout()) {
+                // Lets show the user that he can actually expand the panel
+                runPeekAnimation(
+                        PEEK_ANIMATION_DURATION, getPeekHeight(), true /* collapseWhenFinished */);
+            } else {
+                // We need to collapse the panel since we peeked to the small height.
+                mView.postOnAnimation(mPostCollapseRunnable);
+            }
+        } else if (!mStatusBar.isBouncerShowing()) {
+            boolean expands = onEmptySpaceClick(mInitialTouchX);
+            onTrackingStopped(expands);
+        }
+
+        mVelocityTracker.clear();
+        mPeekTouching = false;
+    }
+
+    protected float getCurrentExpandVelocity() {
+        mVelocityTracker.computeCurrentVelocity(1000);
+        return mVelocityTracker.getYVelocity();
+    }
+
+    private int getFalsingThreshold() {
+        float factor = mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
+        return (int) (mUnlockFalsingThreshold * factor);
+    }
+
+    protected abstract boolean shouldGestureWaitForTouchSlop();
+
+    protected abstract boolean shouldGestureIgnoreXTouchSlop(float x, float y);
+
+    protected void onTrackingStopped(boolean expand) {
+        mTracking = false;
+        mBar.onTrackingStopped(expand);
+        notifyBarPanelExpansionChanged();
+    }
+
+    protected void onTrackingStarted() {
+        endClosing();
+        mTracking = true;
+        mBar.onTrackingStarted();
+        notifyExpandingStarted();
+        notifyBarPanelExpansionChanged();
+    }
+
+    /**
+     * @return Whether a pair of coordinates are inside the visible view content bounds.
+     */
+    protected abstract boolean isInContentBounds(float x, float y);
+
+    protected void cancelHeightAnimator() {
+        if (mHeightAnimator != null) {
+            if (mHeightAnimator.isRunning()) {
+                mPanelUpdateWhenAnimatorEnds = false;
+            }
+            mHeightAnimator.cancel();
+        }
+        endClosing();
+    }
+
+    private void endClosing() {
+        if (mClosing) {
+            mClosing = false;
+            onClosingFinished();
+        }
+    }
+
+    protected boolean isScrolledToBottom() {
+        return true;
+    }
+
+    protected float getContentHeight() {
+        return mExpandedHeight;
+    }
+
+    /**
+     * @param vel       the current vertical velocity of the motion
+     * @param vectorVel the length of the vectorial velocity
+     * @return whether a fling should expands the panel; contracts otherwise
+     */
+    protected boolean flingExpands(float vel, float vectorVel, float x, float y) {
+        if (mFalsingManager.isUnlockingDisabled()) {
+            return true;
+        }
+
+        if (isFalseTouch(x, y)) {
+            return true;
+        }
+        if (Math.abs(vectorVel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
+            return shouldExpandWhenNotFlinging();
+        } else {
+            return vel > 0;
+        }
+    }
+
+    protected boolean shouldExpandWhenNotFlinging() {
+        return getExpandedFraction() > 0.5f;
+    }
+
+    /**
+     * @param x the final x-coordinate when the finger was lifted
+     * @param y the final y-coordinate when the finger was lifted
+     * @return whether this motion should be regarded as a false touch
+     */
+    private boolean isFalseTouch(float x, float y) {
+        if (!mStatusBar.isFalsingThresholdNeeded()) {
+            return false;
+        }
+        if (mFalsingManager.isClassifierEnabled()) {
+            return mFalsingManager.isFalseTouch();
+        }
+        if (!mTouchAboveFalsingThreshold) {
+            return true;
+        }
+        if (mUpwardsWhenThresholdReached) {
+            return false;
+        }
+        return !isDirectionUpwards(x, y);
+    }
+
+    protected void fling(float vel, boolean expand) {
+        fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, false);
+    }
+
+    protected void fling(float vel, boolean expand, boolean expandBecauseOfFalsing) {
+        fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, expandBecauseOfFalsing);
+    }
+
+    protected void fling(float vel, boolean expand, float collapseSpeedUpFactor,
+            boolean expandBecauseOfFalsing) {
+        cancelPeek();
+        float target = expand ? getMaxPanelHeight() : 0;
+        if (!expand) {
+            mClosing = true;
+        }
+        flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
+    }
+
+    protected void flingToHeight(float vel, boolean expand, float target,
+            float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
+        // Hack to make the expand transition look nice when clear all button is visible - we make
+        // the animation only to the last notification, and then jump to the maximum panel height so
+        // clear all just fades in and the decelerating motion is towards the last notification.
+        final boolean
+                clearAllExpandHack =
+                expand && fullyExpandedClearAllVisible()
+                        && mExpandedHeight < getMaxPanelHeight() - getClearAllHeight()
+                        && !isClearAllVisible();
+        if (clearAllExpandHack) {
+            target = getMaxPanelHeight() - getClearAllHeight();
+        }
+        if (target == mExpandedHeight || getOverExpansionAmount() > 0f && expand) {
+            notifyExpandingFinished();
+            return;
+        }
+        mOverExpandedBeforeFling = getOverExpansionAmount() > 0f;
+        ValueAnimator animator = createHeightAnimator(target);
+        if (expand) {
+            if (expandBecauseOfFalsing && vel < 0) {
+                vel = 0;
+            }
+            mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, mView.getHeight());
+            if (vel == 0) {
+                animator.setDuration(350);
+            }
+        } else {
+            if (shouldUseDismissingAnimation()) {
+                if (vel == 0) {
+                    animator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
+                    long duration = (long) (200 + mExpandedHeight / mView.getHeight() * 100);
+                    animator.setDuration(duration);
+                } else {
+                    mFlingAnimationUtilsDismissing.apply(animator, mExpandedHeight, target, vel,
+                            mView.getHeight());
+                }
+            } else {
+                mFlingAnimationUtilsClosing.apply(
+                        animator, mExpandedHeight, target, vel, mView.getHeight());
+            }
+
+            // Make it shorter if we run a canned animation
+            if (vel == 0) {
+                animator.setDuration((long) (animator.getDuration() / collapseSpeedUpFactor));
+            }
+            if (mFixedDuration != NO_FIXED_DURATION) {
+                animator.setDuration(mFixedDuration);
+            }
+        }
+        animator.addListener(new AnimatorListenerAdapter() {
+            private boolean mCancelled;
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                mCancelled = true;
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (clearAllExpandHack && !mCancelled) {
+                    setExpandedHeightInternal(getMaxPanelHeight());
+                }
+                setAnimator(null);
+                if (!mCancelled) {
+                    notifyExpandingFinished();
+                }
+                notifyBarPanelExpansionChanged();
+            }
+        });
+        setAnimator(animator);
+        animator.start();
+    }
+
+    protected abstract boolean shouldUseDismissingAnimation();
+
+    public String getName() {
+        return mViewName;
+    }
+
+    public void setExpandedHeight(float height) {
+        if (DEBUG) logf("setExpandedHeight(%.1f)", height);
+        setExpandedHeightInternal(height + getOverExpansionPixels());
+    }
+
+    protected void requestPanelHeightUpdate() {
+        float currentMaxPanelHeight = getMaxPanelHeight();
+
+        if (isFullyCollapsed()) {
+            return;
+        }
+
+        if (currentMaxPanelHeight == mExpandedHeight) {
+            return;
+        }
+
+        if (mPeekAnimator != null || mPeekTouching) {
+            return;
+        }
+
+        if (mTracking && !isTrackingBlocked()) {
+            return;
+        }
+
+        if (mHeightAnimator != null) {
+            mPanelUpdateWhenAnimatorEnds = true;
+            return;
+        }
+
+        setExpandedHeight(currentMaxPanelHeight);
+    }
+
+    public void setExpandedHeightInternal(float h) {
+        if (mExpandLatencyTracking && h != 0f) {
+            DejankUtils.postAfterTraversal(
+                    () -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL));
+            mExpandLatencyTracking = false;
+        }
+        float fhWithoutOverExpansion = getMaxPanelHeight() - getOverExpansionAmount();
+        if (mHeightAnimator == null) {
+            float overExpansionPixels = Math.max(0, h - fhWithoutOverExpansion);
+            if (getOverExpansionPixels() != overExpansionPixels && mTracking) {
+                setOverExpansion(overExpansionPixels, true /* isPixels */);
+            }
+            mExpandedHeight = Math.min(h, fhWithoutOverExpansion) + getOverExpansionAmount();
+        } else {
+            mExpandedHeight = h;
+            if (mOverExpandedBeforeFling) {
+                setOverExpansion(Math.max(0, h - fhWithoutOverExpansion), false /* isPixels */);
+            }
+        }
+
+        // If we are closing the panel and we are almost there due to a slow decelerating
+        // interpolator, abort the animation.
+        if (mExpandedHeight < 1f && mExpandedHeight != 0f && mClosing) {
+            mExpandedHeight = 0f;
+            if (mHeightAnimator != null) {
+                mHeightAnimator.end();
+            }
+        }
+        mExpandedFraction = Math.min(1f,
+                fhWithoutOverExpansion == 0 ? 0 : mExpandedHeight / fhWithoutOverExpansion);
+        onHeightUpdated(mExpandedHeight);
+        notifyBarPanelExpansionChanged();
+    }
+
+    /**
+     * @return true if the panel tracking should be temporarily blocked; this is used when a
+     * conflicting gesture (opening QS) is happening
+     */
+    protected abstract boolean isTrackingBlocked();
+
+    protected abstract void setOverExpansion(float overExpansion, boolean isPixels);
+
+    protected abstract void onHeightUpdated(float expandedHeight);
+
+    protected abstract float getOverExpansionAmount();
+
+    protected abstract float getOverExpansionPixels();
+
+    /**
+     * This returns the maximum height of the panel. Children should override this if their
+     * desired height is not the full height.
+     *
+     * @return the default implementation simply returns the maximum height.
+     */
+    protected abstract int getMaxPanelHeight();
+
+    public void setExpandedFraction(float frac) {
+        setExpandedHeight(getMaxPanelHeight() * frac);
+    }
+
+    public float getExpandedHeight() {
+        return mExpandedHeight;
+    }
+
+    public float getExpandedFraction() {
+        return mExpandedFraction;
+    }
+
+    public boolean isFullyExpanded() {
+        return mExpandedHeight >= getMaxPanelHeight();
+    }
+
+    public boolean isFullyCollapsed() {
+        return mExpandedFraction <= 0.0f;
+    }
+
+    public boolean isCollapsing() {
+        return mClosing || mLaunchingNotification;
+    }
+
+    public boolean isTracking() {
+        return mTracking;
+    }
+
+    public void setBar(PanelBar panelBar) {
+        mBar = panelBar;
+    }
+
+    public void collapse(boolean delayed, float speedUpFactor) {
+        if (DEBUG) logf("collapse: " + this);
+        if (canPanelBeCollapsed()) {
+            cancelHeightAnimator();
+            notifyExpandingStarted();
+
+            // Set after notifyExpandingStarted, as notifyExpandingStarted resets the closing state.
+            mClosing = true;
+            if (delayed) {
+                mNextCollapseSpeedUpFactor = speedUpFactor;
+                mView.postDelayed(mFlingCollapseRunnable, 120);
+            } else {
+                fling(0, false /* expand */, speedUpFactor, false /* expandBecauseOfFalsing */);
+            }
+        }
+    }
+
+    public boolean canPanelBeCollapsed() {
+        return !isFullyCollapsed() && !mTracking && !mClosing;
+    }
+
+    private final Runnable mFlingCollapseRunnable = new Runnable() {
+        @Override
+        public void run() {
+            fling(0, false /* expand */, mNextCollapseSpeedUpFactor,
+                    false /* expandBecauseOfFalsing */);
+        }
+    };
+
+    public void cancelPeek() {
+        boolean cancelled = false;
+        if (mPeekAnimator != null) {
+            cancelled = true;
+            mPeekAnimator.cancel();
+        }
+
+        if (cancelled) {
+            // When peeking, we already tell mBar that we expanded ourselves. Make sure that we also
+            // notify mBar that we might have closed ourselves.
+            notifyBarPanelExpansionChanged();
+        }
+    }
+
+    public void expand(final boolean animate) {
+        if (!isFullyCollapsed() && !isCollapsing()) {
+            return;
+        }
+
+        mInstantExpanding = true;
+        mAnimateAfterExpanding = animate;
+        mUpdateFlingOnLayout = false;
+        abortAnimations();
+        cancelPeek();
+        if (mTracking) {
+            onTrackingStopped(true /* expands */); // The panel is expanded after this call.
+        }
+        if (mExpanding) {
+            notifyExpandingFinished();
+        }
+        notifyBarPanelExpansionChanged();
+
+        // Wait for window manager to pickup the change, so we know the maximum height of the panel
+        // then.
+        mView.getViewTreeObserver().addOnGlobalLayoutListener(
+                new ViewTreeObserver.OnGlobalLayoutListener() {
+                    @Override
+                    public void onGlobalLayout() {
+                        if (!mInstantExpanding) {
+                            mView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                            return;
+                        }
+                        if (mStatusBar.getNotificationShadeWindowView().isVisibleToUser()) {
+                            mView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                            if (mAnimateAfterExpanding) {
+                                notifyExpandingStarted();
+                                fling(0, true /* expand */);
+                            } else {
+                                setExpandedFraction(1f);
+                            }
+                            mInstantExpanding = false;
+                        }
+                    }
+                });
+
+        // Make sure a layout really happens.
+        mView.requestLayout();
+    }
+
+    public void instantCollapse() {
+        abortAnimations();
+        setExpandedFraction(0f);
+        if (mExpanding) {
+            notifyExpandingFinished();
+        }
+        if (mInstantExpanding) {
+            mInstantExpanding = false;
+            notifyBarPanelExpansionChanged();
+        }
+    }
+
+    private void abortAnimations() {
+        cancelPeek();
+        cancelHeightAnimator();
+        mView.removeCallbacks(mPostCollapseRunnable);
+        mView.removeCallbacks(mFlingCollapseRunnable);
+    }
+
+    protected void onClosingFinished() {
+        mBar.onClosingFinished();
+    }
+
+
+    protected void startUnlockHintAnimation() {
+
+        // We don't need to hint the user if an animation is already running or the user is changing
+        // the expansion.
+        if (mHeightAnimator != null || mTracking) {
+            return;
+        }
+        cancelPeek();
+        notifyExpandingStarted();
+        startUnlockHintAnimationPhase1(() -> {
+            notifyExpandingFinished();
+            onUnlockHintFinished();
+            mHintAnimationRunning = false;
+        });
+        onUnlockHintStarted();
+        mHintAnimationRunning = true;
+    }
+
+    protected void onUnlockHintFinished() {
+        mStatusBar.onHintFinished();
+    }
+
+    protected void onUnlockHintStarted() {
+        mStatusBar.onUnlockHintStarted();
+    }
+
+    public boolean isUnlockHintRunning() {
+        return mHintAnimationRunning;
+    }
+
+    /**
+     * Phase 1: Move everything upwards.
+     */
+    private void startUnlockHintAnimationPhase1(final Runnable onAnimationFinished) {
+        float target = Math.max(0, getMaxPanelHeight() - mHintDistance);
+        ValueAnimator animator = createHeightAnimator(target);
+        animator.setDuration(250);
+        animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+        animator.addListener(new AnimatorListenerAdapter() {
+            private boolean mCancelled;
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                mCancelled = true;
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (mCancelled) {
+                    setAnimator(null);
+                    onAnimationFinished.run();
+                } else {
+                    startUnlockHintAnimationPhase2(onAnimationFinished);
+                }
+            }
+        });
+        animator.start();
+        setAnimator(animator);
+
+        View[] viewsToAnimate = {
+                mKeyguardBottomArea.getIndicationArea(),
+                mStatusBar.getAmbientIndicationContainer()};
+        for (View v : viewsToAnimate) {
+            if (v == null) {
+                continue;
+            }
+            v.animate().translationY(-mHintDistance).setDuration(250).setInterpolator(
+                    Interpolators.FAST_OUT_SLOW_IN).withEndAction(() -> v.animate().translationY(
+                    0).setDuration(450).setInterpolator(mBounceInterpolator).start()).start();
+        }
+    }
+
+    private void setAnimator(ValueAnimator animator) {
+        mHeightAnimator = animator;
+        if (animator == null && mPanelUpdateWhenAnimatorEnds) {
+            mPanelUpdateWhenAnimatorEnds = false;
+            requestPanelHeightUpdate();
+        }
+    }
+
+    /**
+     * Phase 2: Bounce down.
+     */
+    private void startUnlockHintAnimationPhase2(final Runnable onAnimationFinished) {
+        ValueAnimator animator = createHeightAnimator(getMaxPanelHeight());
+        animator.setDuration(450);
+        animator.setInterpolator(mBounceInterpolator);
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                setAnimator(null);
+                onAnimationFinished.run();
+                notifyBarPanelExpansionChanged();
+            }
+        });
+        animator.start();
+        setAnimator(animator);
+    }
+
+    private ValueAnimator createHeightAnimator(float targetHeight) {
+        ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight);
+        animator.addUpdateListener(
+                animation -> setExpandedHeightInternal((float) animation.getAnimatedValue()));
+        return animator;
+    }
+
+    protected void notifyBarPanelExpansionChanged() {
+        if (mBar != null) {
+            mBar.panelExpansionChanged(
+                    mExpandedFraction,
+                    mExpandedFraction > 0f || mPeekAnimator != null || mInstantExpanding
+                            || isPanelVisibleBecauseOfHeadsUp() || mTracking
+                            || mHeightAnimator != null);
+        }
+        for (int i = 0; i < mExpansionListeners.size(); i++) {
+            mExpansionListeners.get(i).onPanelExpansionChanged(mExpandedFraction, mTracking);
+        }
+    }
+
+    public void addExpansionListener(PanelExpansionListener panelExpansionListener) {
+        mExpansionListeners.add(panelExpansionListener);
+    }
+
+    protected abstract boolean isPanelVisibleBecauseOfHeadsUp();
+
+    /**
+     * Gets called when the user performs a click anywhere in the empty area of the panel.
+     *
+     * @return whether the panel will be expanded after the action performed by this method
+     */
+    protected boolean onEmptySpaceClick(float x) {
+        if (mHintAnimationRunning) {
+            return true;
+        }
+        return onMiddleClicked();
+    }
+
+    protected final Runnable mPostCollapseRunnable = new Runnable() {
+        @Override
+        public void run() {
+            collapse(false /* delayed */, 1.0f /* speedUpFactor */);
+        }
+    };
+
+    protected abstract boolean onMiddleClicked();
+
+    protected abstract boolean isDozing();
+
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s"
+                        + " tracking=%s justPeeked=%s peekAnim=%s%s timeAnim=%s%s "
+                        + "touchDisabled=%s" + "]",
+                this.getClass().getSimpleName(), getExpandedHeight(), getMaxPanelHeight(),
+                mClosing ? "T" : "f", mTracking ? "T" : "f", mJustPeeked ? "T" : "f", mPeekAnimator,
+                ((mPeekAnimator != null && mPeekAnimator.isStarted()) ? " (started)" : ""),
+                mHeightAnimator,
+                ((mHeightAnimator != null && mHeightAnimator.isStarted()) ? " (started)" : ""),
+                mTouchDisabled ? "T" : "f"));
+    }
+
+    public abstract void resetViews(boolean animate);
+
+    protected abstract float getPeekHeight();
+
+    /**
+     * @return whether "Clear all" button will be visible when the panel is fully expanded
+     */
+    protected abstract boolean fullyExpandedClearAllVisible();
+
+    protected abstract boolean isClearAllVisible();
+
+    /**
+     * @return the height of the clear all button, in pixels
+     */
+    protected abstract int getClearAllHeight();
+
+    public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
+        mHeadsUpManager = headsUpManager;
+    }
+
+    public void setLaunchingNotification(boolean launchingNotification) {
+        mLaunchingNotification = launchingNotification;
+    }
+
+    public void collapseWithDuration(int animationDuration) {
+        mFixedDuration = animationDuration;
+        collapse(false /* delayed */, 1.0f /* speedUpFactor */);
+        mFixedDuration = NO_FIXED_DURATION;
+    }
+
+    public ViewGroup getView() {
+        // TODO: remove this method, or at least reduce references to it.
+        return mView;
+    }
+
+    public boolean isEnabled() {
+        return mView.isEnabled();
+    }
+
+    public OnLayoutChangeListener createLayoutChangeListener() {
+        return new OnLayoutChangeListener();
+    }
+
+    protected TouchHandler createTouchHandler() {
+        return new TouchHandler();
+    }
+
+    protected OnConfigurationChangedListener createOnConfigurationChangedListener() {
+        return new OnConfigurationChangedListener();
+    }
+
+    public class TouchHandler implements View.OnTouchListener {
+        public boolean onInterceptTouchEvent(MotionEvent event) {
+            if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted
+                    && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
+                return false;
+            }
+
+            /*
+             * If the user drags anywhere inside the panel we intercept it if the movement is
+             * upwards. This allows closing the shade from anywhere inside the panel.
+             *
+             * We only do this if the current content is scrolled to the bottom,
+             * i.e isScrolledToBottom() is true and therefore there is no conflicting scrolling
+             * gesture
+             * possible.
+             */
+            int pointerIndex = event.findPointerIndex(mTrackingPointer);
+            if (pointerIndex < 0) {
+                pointerIndex = 0;
+                mTrackingPointer = event.getPointerId(pointerIndex);
+            }
+            final float x = event.getX(pointerIndex);
+            final float y = event.getY(pointerIndex);
+            boolean scrolledToBottom = isScrolledToBottom();
+
+            switch (event.getActionMasked()) {
+                case MotionEvent.ACTION_DOWN:
+                    mStatusBar.userActivity();
+                    mAnimatingOnDown = mHeightAnimator != null;
+                    mMinExpandHeight = 0.0f;
+                    mDownTime = SystemClock.uptimeMillis();
+                    if (mAnimatingOnDown && mClosing && !mHintAnimationRunning
+                            || mPeekAnimator != null) {
+                        cancelHeightAnimator();
+                        cancelPeek();
+                        mTouchSlopExceeded = true;
+                        return true;
+                    }
+                    mInitialTouchY = y;
+                    mInitialTouchX = x;
+                    mTouchStartedInEmptyArea = !isInContentBounds(x, y);
+                    mTouchSlopExceeded = mTouchSlopExceededBeforeDown;
+                    mJustPeeked = false;
+                    mMotionAborted = false;
+                    mPanelClosedOnDown = isFullyCollapsed();
+                    mCollapsedAndHeadsUpOnDown = false;
+                    mHasLayoutedSinceDown = false;
+                    mUpdateFlingOnLayout = false;
+                    mTouchAboveFalsingThreshold = false;
+                    addMovement(event);
+                    break;
+                case MotionEvent.ACTION_POINTER_UP:
+                    final int upPointer = event.getPointerId(event.getActionIndex());
+                    if (mTrackingPointer == upPointer) {
+                        // gesture is ongoing, find a new pointer to track
+                        final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
+                        mTrackingPointer = event.getPointerId(newIndex);
+                        mInitialTouchX = event.getX(newIndex);
+                        mInitialTouchY = event.getY(newIndex);
+                    }
+                    break;
+                case MotionEvent.ACTION_POINTER_DOWN:
+                    if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+                        mMotionAborted = true;
+                        mVelocityTracker.clear();
+                    }
+                    break;
+                case MotionEvent.ACTION_MOVE:
+                    final float h = y - mInitialTouchY;
+                    addMovement(event);
+                    if (scrolledToBottom || mTouchStartedInEmptyArea || mAnimatingOnDown) {
+                        float hAbs = Math.abs(h);
+                        if ((h < -mTouchSlop || (mAnimatingOnDown && hAbs > mTouchSlop))
+                                && hAbs > Math.abs(x - mInitialTouchX)) {
+                            cancelHeightAnimator();
+                            startExpandMotion(x, y, true /* startTracking */, mExpandedHeight);
+                            return true;
+                        }
+                    }
+                    break;
+                case MotionEvent.ACTION_CANCEL:
+                case MotionEvent.ACTION_UP:
+                    mVelocityTracker.clear();
+                    break;
+            }
+            return false;
+        }
+
+        @Override
+        public boolean onTouch(View v, MotionEvent event) {
+            if (mInstantExpanding || (mTouchDisabled
+                    && event.getActionMasked() != MotionEvent.ACTION_CANCEL) || (mMotionAborted
+                    && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
+                return false;
+            }
+
+            // If dragging should not expand the notifications shade, then return false.
+            if (!mNotificationsDragEnabled) {
+                if (mTracking) {
+                    // Turn off tracking if it's on or the shade can get stuck in the down position.
+                    onTrackingStopped(true /* expand */);
+                }
+                return false;
+            }
+
+            // On expanding, single mouse click expands the panel instead of dragging.
+            if (isFullyCollapsed() && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+                if (event.getAction() == MotionEvent.ACTION_UP) {
+                    expand(true);
+                }
+                return true;
+            }
+
+            /*
+             * We capture touch events here and update the expand height here in case according to
+             * the users fingers. This also handles multi-touch.
+             *
+             * If the user just clicks shortly, we show a quick peek of the shade.
+             *
+             * Flinging is also enabled in order to open or close the shade.
+             */
+
+            int pointerIndex = event.findPointerIndex(mTrackingPointer);
+            if (pointerIndex < 0) {
+                pointerIndex = 0;
+                mTrackingPointer = event.getPointerId(pointerIndex);
+            }
+            final float x = event.getX(pointerIndex);
+            final float y = event.getY(pointerIndex);
+
+            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+                mGestureWaitForTouchSlop = shouldGestureWaitForTouchSlop();
+                mIgnoreXTouchSlop = isFullyCollapsed() || shouldGestureIgnoreXTouchSlop(x, y);
+            }
+
+            switch (event.getActionMasked()) {
+                case MotionEvent.ACTION_DOWN:
+                    startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
+                    mJustPeeked = false;
+                    mMinExpandHeight = 0.0f;
+                    mPanelClosedOnDown = isFullyCollapsed();
+                    mHasLayoutedSinceDown = false;
+                    mUpdateFlingOnLayout = false;
+                    mMotionAborted = false;
+                    mPeekTouching = mPanelClosedOnDown;
+                    mDownTime = SystemClock.uptimeMillis();
+                    mTouchAboveFalsingThreshold = false;
+                    mCollapsedAndHeadsUpOnDown =
+                            isFullyCollapsed() && mHeadsUpManager.hasPinnedHeadsUp();
+                    addMovement(event);
+                    if (!mGestureWaitForTouchSlop || (mHeightAnimator != null
+                            && !mHintAnimationRunning) || mPeekAnimator != null) {
+                        mTouchSlopExceeded =
+                                (mHeightAnimator != null && !mHintAnimationRunning)
+                                        || mPeekAnimator != null || mTouchSlopExceededBeforeDown;
+                        cancelHeightAnimator();
+                        cancelPeek();
+                        onTrackingStarted();
+                    }
+                    if (isFullyCollapsed() && !mHeadsUpManager.hasPinnedHeadsUp()
+                            && !mStatusBar.isBouncerShowing()) {
+                        startOpening(event);
+                    }
+                    break;
+
+                case MotionEvent.ACTION_POINTER_UP:
+                    final int upPointer = event.getPointerId(event.getActionIndex());
+                    if (mTrackingPointer == upPointer) {
+                        // gesture is ongoing, find a new pointer to track
+                        final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
+                        final float newY = event.getY(newIndex);
+                        final float newX = event.getX(newIndex);
+                        mTrackingPointer = event.getPointerId(newIndex);
+                        startExpandMotion(newX, newY, true /* startTracking */, mExpandedHeight);
+                    }
+                    break;
+                case MotionEvent.ACTION_POINTER_DOWN:
+                    if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+                        mMotionAborted = true;
+                        endMotionEvent(event, x, y, true /* forceCancel */);
+                        return false;
+                    }
+                    break;
+                case MotionEvent.ACTION_MOVE:
+                    addMovement(event);
+                    float h = y - mInitialTouchY;
+
+                    // If the panel was collapsed when touching, we only need to check for the
+                    // y-component of the gesture, as we have no conflicting horizontal gesture.
+                    if (Math.abs(h) > mTouchSlop && (Math.abs(h) > Math.abs(x - mInitialTouchX)
+                            || mIgnoreXTouchSlop)) {
+                        mTouchSlopExceeded = true;
+                        if (mGestureWaitForTouchSlop && !mTracking && !mCollapsedAndHeadsUpOnDown) {
+                            if (!mJustPeeked && mInitialOffsetOnTouch != 0f) {
+                                startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
+                                h = 0;
+                            }
+                            cancelHeightAnimator();
+                            onTrackingStarted();
+                        }
+                    }
+                    float newHeight = Math.max(0, h + mInitialOffsetOnTouch);
+                    if (newHeight > mPeekHeight) {
+                        if (mPeekAnimator != null) {
+                            mPeekAnimator.cancel();
+                        }
+                        mJustPeeked = false;
+                    } else if (mPeekAnimator == null && mJustPeeked) {
+                        // The initial peek has finished, but we haven't dragged as far yet, lets
+                        // speed it up by starting at the peek height.
+                        mInitialOffsetOnTouch = mExpandedHeight;
+                        mInitialTouchY = y;
+                        mMinExpandHeight = mExpandedHeight;
+                        mJustPeeked = false;
+                    }
+                    newHeight = Math.max(newHeight, mMinExpandHeight);
+                    if (-h >= getFalsingThreshold()) {
+                        mTouchAboveFalsingThreshold = true;
+                        mUpwardsWhenThresholdReached = isDirectionUpwards(x, y);
+                    }
+                    if (!mJustPeeked && (!mGestureWaitForTouchSlop || mTracking)
+                            && !isTrackingBlocked()) {
+                        setExpandedHeightInternal(newHeight);
+                    }
+                    break;
+
+                case MotionEvent.ACTION_UP:
+                case MotionEvent.ACTION_CANCEL:
+                    addMovement(event);
+                    endMotionEvent(event, x, y, false /* forceCancel */);
+                    break;
+            }
+            return !mGestureWaitForTouchSlop || mTracking;
+        }
+    }
+
+    public class OnLayoutChangeListener implements View.OnLayoutChangeListener {
+        @Override
+        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
+                int oldTop, int oldRight, int oldBottom) {
+            mStatusBar.onPanelLaidOut();
+            requestPanelHeightUpdate();
+            mHasLayoutedSinceDown = true;
+            if (mUpdateFlingOnLayout) {
+                abortAnimations();
+                fling(mUpdateFlingVelocity, true /* expands */);
+                mUpdateFlingOnLayout = false;
+            }
+        }
+    }
+
+    public class OnConfigurationChangedListener implements
+            PanelView.OnConfigurationChangedListener {
+        @Override
+        public void onConfigurationChanged(Configuration newConfig) {
+            loadDimens();
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 5b34aa7..41d8968 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -37,7 +37,6 @@
 import android.text.format.DateFormat;
 import android.util.Log;
 
-import com.android.internal.telephony.TelephonyIntents;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -174,12 +173,12 @@
         filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
         filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
         filter.addAction(AudioManager.ACTION_HEADSET_PLUG);
-        filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+        filter.addAction(Intent.ACTION_SIM_STATE_CHANGED);
         filter.addAction(TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
-        broadcastDispatcher.registerReceiver(mIntentReceiver, filter, mHandler);
+        broadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter, mHandler);
 
         // listen for user / profile change.
         try {
@@ -614,10 +613,9 @@
                 case AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION:
                     updateVolumeZen();
                     break;
-                case TelephonyIntents.ACTION_SIM_STATE_CHANGED:
+                case Intent.ACTION_SIM_STATE_CHANGED:
                     // Avoid rebroadcast because SysUI is direct boot aware.
-                    if (intent.getBooleanExtra(TelephonyIntents.EXTRA_REBROADCAST_ON_UNLOCK,
-                            false)) {
+                    if (intent.getBooleanExtra(Intent.EXTRA_REBROADCAST_ON_UNLOCK, false)) {
                         break;
                     }
                     updateSimState(intent);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 312ca26..ffbbffc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -236,7 +236,7 @@
     public void onPanelFullyOpened() {
         super.onPanelFullyOpened();
         if (!mIsFullyOpenedPanel) {
-            mPanel.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+            mPanel.getView().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
         }
         mIsFullyOpenedPanel = true;
         maybeShowDivider(!mBar.mPanelExpanded);
@@ -323,8 +323,7 @@
                 R.dimen.display_cutout_margin_consumption);
 
         ViewGroup.LayoutParams layoutParams = getLayoutParams();
-        layoutParams.height = getResources().getDimensionPixelSize(
-                R.dimen.status_bar_height);
+        layoutParams.height = getResources().getDimensionPixelSize(R.dimen.status_bar_height);
         setLayoutParams(layoutParams);
     }
 
@@ -420,7 +419,8 @@
 
     void maybeShowDivider(boolean showDivider) {
         int state =
-                showDivider && NotificationPanelView.isQsSplitEnabled() ? View.VISIBLE : View.GONE;
+                showDivider && NotificationPanelViewController.isQsSplitEnabled()
+                        ? View.VISIBLE : View.GONE;
         mDividerContainer.setVisibility(state);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
index 57e7014..3330615 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
@@ -44,7 +44,7 @@
 
     private final CommandQueue mCommandQueue;
     private final StatusBarStateController mStatusBarStateController;
-    protected final StatusBarWindowController mStatusBarWindowController;
+    protected final NotificationShadeWindowController mNotificationShadeWindowController;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private final int mDisplayId;
     protected final Lazy<StatusBar> mStatusBarLazy;
@@ -57,7 +57,7 @@
     public ShadeControllerImpl(
             CommandQueue commandQueue,
             StatusBarStateController statusBarStateController,
-            StatusBarWindowController statusBarWindowController,
+            NotificationShadeWindowController notificationShadeWindowController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             WindowManager windowManager,
             Lazy<StatusBar> statusBarLazy,
@@ -66,7 +66,7 @@
     ) {
         mCommandQueue = commandQueue;
         mStatusBarStateController = statusBarStateController;
-        mStatusBarWindowController = statusBarWindowController;
+        mNotificationShadeWindowController = notificationShadeWindowController;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mDisplayId = windowManager.getDefaultDisplay().getDisplayId();
         // TODO: Remove circular reference to StatusBar when possible.
@@ -79,7 +79,7 @@
     public void instantExpandNotificationsPanel() {
         // Make our window larger and the panel expanded.
         getStatusBar().makeExpandedVisible(true /* force */);
-        getNotificationPanelView().expand(false /* animate */);
+        getNotificationPanelViewController().expand(false /* animate */);
         mCommandQueue.recomputeDisableFlags(mDisplayId, false /* animate */);
     }
 
@@ -122,13 +122,15 @@
         }
 
         // TODO(b/62444020): remove when this bug is fixed
-        Log.v(TAG, "mStatusBarWindow: " + getStatusBarWindowView() + " canPanelBeCollapsed(): "
-                + getNotificationPanelView().canPanelBeCollapsed());
-        if (getStatusBarWindowView() != null && getNotificationPanelView().canPanelBeCollapsed()) {
+        Log.v(TAG, "NotificationShadeWindow: " + getNotificationShadeWindowView()
+                + " canPanelBeCollapsed(): "
+                + getNotificationPanelViewController().canPanelBeCollapsed());
+        if (getNotificationShadeWindowView() != null
+                && getNotificationPanelViewController().canPanelBeCollapsed()) {
             // release focus immediately to kick off focus change transition
-            mStatusBarWindowController.setStatusBarFocusable(false);
+            mNotificationShadeWindowController.setNotificationShadeFocusable(false);
 
-            getStatusBar().getStatusBarWindowViewController().cancelExpandHelper();
+            getStatusBar().getNotificationShadeWindowViewController().cancelExpandHelper();
             getStatusBarView().collapsePanel(true /* animate */, delayed, speedUpFactor);
         } else {
             mBubbleControllerLazy.get().collapseStack();
@@ -138,7 +140,7 @@
 
     @Override
     public boolean closeShadeIfOpen() {
-        if (!getNotificationPanelView().isFullyCollapsed()) {
+        if (!getNotificationPanelViewController().isFullyCollapsed()) {
             mCommandQueue.animateCollapsePanels(
                     CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
             getStatusBar().visibilityChanged(false);
@@ -149,15 +151,13 @@
 
     @Override
     public void postOnShadeExpanded(Runnable executable) {
-        getNotificationPanelView().getViewTreeObserver().addOnGlobalLayoutListener(
+        getNotificationPanelViewController().addOnGlobalLayoutListener(
                 new ViewTreeObserver.OnGlobalLayoutListener() {
                     @Override
                     public void onGlobalLayout() {
-                        if (getStatusBar().getStatusBarWindow().getHeight()
-                                != getStatusBar().getStatusBarHeight()) {
-                            getNotificationPanelView().getViewTreeObserver()
-                                    .removeOnGlobalLayoutListener(this);
-                            getNotificationPanelView().post(executable);
+                        if (getStatusBar().getNotificationShadeWindowView().isVisibleToUser()) {
+                            getNotificationPanelViewController().removeOnGlobalLayoutListener(this);
+                            getNotificationPanelViewController().getView().post(executable);
                         }
                     }
                 });
@@ -187,7 +187,7 @@
 
     @Override
     public boolean collapsePanel() {
-        if (!getNotificationPanelView().isFullyCollapsed()) {
+        if (!getNotificationPanelViewController().isFullyCollapsed()) {
             // close the shade if it was open
             animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
                     true /* force */, true /* delayed */);
@@ -222,15 +222,15 @@
         return getStatusBar().getPresenter();
     }
 
-    protected StatusBarWindowView getStatusBarWindowView() {
-        return getStatusBar().getStatusBarWindow();
+    protected NotificationShadeWindowView getNotificationShadeWindowView() {
+        return getStatusBar().getNotificationShadeWindowView();
     }
 
     protected PhoneStatusBarView getStatusBarView() {
         return (PhoneStatusBarView) getStatusBar().getStatusBarView();
     }
 
-    private NotificationPanelView getNotificationPanelView() {
-        return getStatusBar().getPanel();
+    private NotificationPanelViewController getNotificationPanelViewController() {
+        return getStatusBar().getPanelController();
     }
 }
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 c5d7da4..4e8442f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -29,7 +29,6 @@
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS;
 
-import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
@@ -131,7 +130,6 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.InitController;
-import com.android.systemui.Interpolators;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
@@ -199,15 +197,15 @@
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.NotificationListController;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
-import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
@@ -344,9 +342,11 @@
 
     private final Point mCurrentDisplaySize = new Point();
 
-    protected StatusBarWindowView mStatusBarWindow;
+    protected NotificationShadeWindowView mNotificationShadeWindowView;
+    protected StatusBarWindowView mPhoneStatusBarWindow;
     protected PhoneStatusBarView mStatusBarView;
     private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
+    protected NotificationShadeWindowController mNotificationShadeWindowController;
     protected StatusBarWindowController mStatusBarWindowController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final LockscreenLockIconController mLockscreenLockIconController;
@@ -366,12 +366,11 @@
     private final HeadsUpManagerPhone mHeadsUpManager;
     private final DynamicPrivacyController mDynamicPrivacyController;
     private final BypassHeadsUpNotifier mBypassHeadsUpNotifier;
-    private final boolean mAllowNotificationLongPress;
-    private final Lazy<NewNotifPipeline> mNewNotifPipeline;
+    private final Lazy<NotifPipelineInitializer> mNewNotifPipeline;
     private final FalsingManager mFalsingManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final ConfigurationController mConfigurationController;
-    protected StatusBarWindowViewController mStatusBarWindowViewController;
+    protected NotificationShadeWindowViewController mNotificationShadeWindowViewController;
     private final DozeParameters mDozeParameters;
     private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
     private final Provider<StatusBarComponent.Builder> mStatusBarComponentBuilder;
@@ -389,10 +388,12 @@
     private final KeyguardDismissUtil mKeyguardDismissUtil;
     private final ExtensionController mExtensionController;
     private final UserInfoControllerImpl mUserInfoControllerImpl;
+    private final NotificationRowBinderImpl mNotificationRowBinder;
     private final DismissCallbackRegistry mDismissCallbackRegistry;
 
     // expanded notifications
-    protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
+    // the sliding/resizing panel within the notification window
+    protected NotificationPanelViewController mNotificationPanelViewController;
 
     // settings
     private QSPanel mQSPanel;
@@ -461,8 +462,8 @@
                 mUserSetup = userSetup;
                 if (!mUserSetup && mStatusBarView != null)
                     animateCollapseQuickSettings();
-                if (mNotificationPanel != null) {
-                    mNotificationPanel.setUserSetupComplete(mUserSetup);
+                if (mNotificationPanelViewController != null) {
+                    mNotificationPanelViewController.setUserSetupComplete(mUserSetup);
                 }
                 updateQsExpansionEnabled();
             }
@@ -504,7 +505,7 @@
                     && ((info == null && imageWallpaperInAmbient)
                         || (info != null && info.supportsAmbientMode()));
 
-            mStatusBarWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
+            mNotificationShadeWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
             mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
         }
     };
@@ -626,8 +627,7 @@
             HeadsUpManagerPhone headsUpManagerPhone,
             DynamicPrivacyController dynamicPrivacyController,
             BypassHeadsUpNotifier bypassHeadsUpNotifier,
-            @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowNotificationLongPress,
-            Lazy<NewNotifPipeline> newNotifPipeline,
+            Lazy<NotifPipelineInitializer> newNotifPipeline,
             FalsingManager falsingManager,
             BroadcastDispatcher broadcastDispatcher,
             RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
@@ -661,7 +661,7 @@
             Lazy<AssistManager> assistManagerLazy,
             NotificationListener notificationListener,
             ConfigurationController configurationController,
-            StatusBarWindowController statusBarWindowController,
+            NotificationShadeWindowController notificationShadeWindowController,
             LockscreenLockIconController lockscreenLockIconController,
             DozeParameters dozeParameters,
             ScrimController scrimController,
@@ -693,6 +693,7 @@
             KeyguardDismissUtil keyguardDismissUtil,
             ExtensionController extensionController,
             UserInfoControllerImpl userInfoControllerImpl,
+            NotificationRowBinderImpl notificationRowBinder,
             DismissCallbackRegistry dismissCallbackRegistry) {
         super(context);
         mFeatureFlags = featureFlags;
@@ -707,7 +708,6 @@
         mHeadsUpManager = headsUpManagerPhone;
         mDynamicPrivacyController = dynamicPrivacyController;
         mBypassHeadsUpNotifier = bypassHeadsUpNotifier;
-        mAllowNotificationLongPress = allowNotificationLongPress;
         mNewNotifPipeline = newNotifPipeline;
         mFalsingManager = falsingManager;
         mBroadcastDispatcher = broadcastDispatcher;
@@ -742,7 +742,7 @@
         mAssistManagerLazy = assistManagerLazy;
         mNotificationListener = notificationListener;
         mConfigurationController = configurationController;
-        mStatusBarWindowController = statusBarWindowController;
+        mNotificationShadeWindowController = notificationShadeWindowController;
         mLockscreenLockIconController = lockscreenLockIconController;
         mDozeServiceHost = dozeServiceHost;
         mPowerManager = powerManager;
@@ -772,6 +772,7 @@
         mKeyguardDismissUtil = keyguardDismissUtil;
         mExtensionController = extensionController;
         mUserInfoControllerImpl = userInfoControllerImpl;
+        mNotificationRowBinder = notificationRowBinder;
         mDismissCallbackRegistry = dismissCallbackRegistry;
 
         mBubbleExpandListener =
@@ -913,9 +914,8 @@
 
         mKeyguardUpdateMonitor.registerCallback(mUpdateCallback);
         mDozeServiceHost.initialize(this, mNotificationIconAreaController,
-                mStatusBarKeyguardViewManager,
-                mStatusBarWindowViewController,
-                mNotificationPanel, mAmbientIndicationContainer);
+                mStatusBarKeyguardViewManager, mNotificationShadeWindowViewController,
+                mNotificationPanelViewController, mAmbientIndicationContainer);
 
         mConfigurationController.addCallback(this);
 
@@ -932,7 +932,8 @@
                     @Override
                     public void onPluginConnected(OverlayPlugin plugin, Context pluginContext) {
                         mMainThreadHandler.post(
-                                () -> plugin.setup(getStatusBarWindow(), getNavigationBarView(),
+                                () -> plugin.setup(getNotificationShadeWindowView(),
+                                        getNavigationBarView(),
                                         new Callback(plugin), mDozeParameters));
                     }
 
@@ -940,7 +941,8 @@
                     public void onPluginDisconnected(OverlayPlugin plugin) {
                         mMainThreadHandler.post(() -> {
                             mOverlays.remove(plugin);
-                            mStatusBarWindowController.setForcePluginOpen(mOverlays.size() != 0);
+                            mNotificationShadeWindowController
+                                    .setForcePluginOpen(mOverlays.size() != 0);
                         });
                     }
 
@@ -959,10 +961,10 @@
                                 mOverlays.remove(mPlugin);
                             }
                             mMainThreadHandler.post(() -> {
-                                mStatusBarWindowController
+                                mNotificationShadeWindowController
                                         .setStateListener(b -> mOverlays.forEach(
                                                 o -> o.setCollapseDesired(b)));
-                                mStatusBarWindowController
+                                mNotificationShadeWindowController
                                         .setForcePluginOpen(mOverlays.size() != 0);
                             });
                         }
@@ -980,14 +982,13 @@
         updateTheme();
 
         inflateStatusBarWindow();
-        mStatusBarWindowViewController.setService(this);
-        mStatusBarWindow.setOnTouchListener(getStatusBarWindowTouchListener());
+        mNotificationShadeWindowViewController.setService(this);
+        mNotificationShadeWindowView.setOnTouchListener(getStatusBarWindowTouchListener());
 
         // TODO: Deal with the ugliness that comes from having some of the statusbar broken out
         // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
-        mNotificationPanel = mSuperStatusBarViewFactory.getNotificationPanelView();
-
-        mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
+        mStackScroller = mNotificationShadeWindowView.findViewById(
+                R.id.notification_stack_scroller);
         NotificationListContainer notifListContainer = (NotificationListContainer) mStackScroller;
         mNotificationLogger.setUpWithContainer(notifListContainer);
 
@@ -1000,24 +1001,26 @@
         mWakeUpCoordinator.setIconAreaController(mNotificationIconAreaController);
         inflateShelf();
         mNotificationIconAreaController.setupShelf(mNotificationShelf);
-        mNotificationPanel.setOnReinflationListener(mNotificationIconAreaController::initAodIcons);
-        mNotificationPanel.addExpansionListener(mWakeUpCoordinator);
+        mNotificationPanelViewController.setOnReinflationListener(
+                mNotificationIconAreaController::initAodIcons);
+        mNotificationPanelViewController.addExpansionListener(mWakeUpCoordinator);
 
         mDarkIconDispatcher.addDarkReceiver(mNotificationIconAreaController);
         // Allow plugins to reference DarkIconDispatcher and StatusBarStateController
         mPluginDependencyProvider.allowPluginDependency(DarkIconDispatcher.class);
         mPluginDependencyProvider.allowPluginDependency(StatusBarStateController.class);
-        FragmentHostManager.get(mStatusBarWindow)
+        FragmentHostManager.get(mPhoneStatusBarWindow)
                 .addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
                     CollapsedStatusBarFragment statusBarFragment =
                             (CollapsedStatusBarFragment) fragment;
-                    statusBarFragment.initNotificationIconArea(mNotificationIconAreaController);
+
                     PhoneStatusBarView oldStatusBarView = mStatusBarView;
-                    mStatusBarView = (PhoneStatusBarView) fragment.getView();
+                    mStatusBarView = (PhoneStatusBarView) statusBarFragment.getView();
                     mStatusBarView.setBar(this);
-                    mStatusBarView.setPanel(mNotificationPanel);
+                    mStatusBarView.setPanel(mNotificationPanelViewController);
                     mStatusBarView.setScrimController(mScrimController);
 
+                    statusBarFragment.initNotificationIconArea(mNotificationIconAreaController);
                     // CollapsedStatusBarFragment re-inflated PhoneStatusBarView and both of
                     // mStatusBarView.mExpanded and mStatusBarView.mBouncerShowing are false.
                     // PhoneStatusBarView's new instance will set to be gone in
@@ -1026,7 +1029,7 @@
                     // it needs to notify PhoneStatusBarView's new instance to update the correct
                     // status by calling mNotificationPanel.notifyBarPanelExpansionChanged().
                     if (mHeadsUpManager.hasPinnedHeadsUp()) {
-                        mNotificationPanel.notifyBarPanelExpansionChanged();
+                        mNotificationPanelViewController.notifyBarPanelExpansionChanged();
                     }
                     mStatusBarView.setBouncerShowing(mBouncerShowing);
                     if (oldStatusBarView != null) {
@@ -1040,15 +1043,19 @@
                         // This view is being recreated, let's destroy the old one
                         mHeadsUpAppearanceController.destroy();
                     }
+                    // TODO: this should probably be scoped to the StatusBarComponent
+                    // TODO (b/136993073) Separate notification shade and status bar
                     mHeadsUpAppearanceController = new HeadsUpAppearanceController(
-                            mNotificationIconAreaController, mHeadsUpManager, mStatusBarWindow,
+                            mNotificationIconAreaController, mHeadsUpManager,
+                            mNotificationShadeWindowView,
                             mStatusBarStateController, mKeyguardBypassController,
-                            mKeyguardStateController, mWakeUpCoordinator, mCommandQueue);
+                            mKeyguardStateController, mWakeUpCoordinator, mCommandQueue,
+                            mNotificationPanelViewController, mStatusBarView);
                     mHeadsUpAppearanceController.readFrom(oldController);
 
                     mLightsOutNotifController.setLightsOutNotifView(
                             mStatusBarView.findViewById(R.id.notification_lights_out));
-                    mStatusBarWindowViewController.setStatusBarView(mStatusBarView);
+                    mNotificationShadeWindowViewController.setStatusBarView(mStatusBarView);
                     checkBarModes();
                 }).getFragmentManager()
                 .beginTransaction()
@@ -1056,14 +1063,15 @@
                         CollapsedStatusBarFragment.TAG)
                 .commit();
 
-        mHeadsUpManager.setUp(mStatusBarWindow, mGroupManager, this, mVisualStabilityManager);
+        mHeadsUpManager.setUp(mNotificationShadeWindowView, mGroupManager, this,
+                mVisualStabilityManager);
         mConfigurationController.addCallback(mHeadsUpManager);
         mHeadsUpManager.addListener(this);
-        mHeadsUpManager.addListener(mNotificationPanel);
+        mHeadsUpManager.addListener(mNotificationPanelViewController.getOnHeadsUpChangedListener());
         mHeadsUpManager.addListener(mGroupManager);
         mHeadsUpManager.addListener(mGroupAlertTransferHelper);
         mHeadsUpManager.addListener(mVisualStabilityManager);
-        mNotificationPanel.setHeadsUpManager(mHeadsUpManager);
+        mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
         mGroupManager.setHeadsUpManager(mHeadsUpManager);
         mGroupAlertTransferHelper.setHeadsUpManager(mHeadsUpManager);
         mNotificationLogger.setHeadsUpManager(mHeadsUpManager);
@@ -1076,11 +1084,12 @@
 
         mKeyguardIndicationController =
                 SystemUIFactory.getInstance().createKeyguardIndicationController(mContext,
-                        mStatusBarWindow.findViewById(R.id.keyguard_indication_area),
-                        mStatusBarWindow.findViewById(R.id.lock_icon));
-        mNotificationPanel.setKeyguardIndicationController(mKeyguardIndicationController);
+                        mNotificationShadeWindowView.findViewById(R.id.keyguard_indication_area),
+                        mNotificationShadeWindowView.findViewById(R.id.lock_icon));
+        mNotificationPanelViewController.setKeyguardIndicationController(
+                mKeyguardIndicationController);
 
-        mAmbientIndicationContainer = mStatusBarWindow.findViewById(
+        mAmbientIndicationContainer = mNotificationShadeWindowView.findViewById(
                 R.id.ambient_indication_container);
 
         // TODO: Find better place for this callback.
@@ -1101,35 +1110,35 @@
 
         mAutoHideController.setStatusBar(this);
 
-        ScrimView scrimBehind = mStatusBarWindow.findViewById(R.id.scrim_behind);
-        ScrimView scrimInFront = mStatusBarWindow.findViewById(R.id.scrim_in_front);
-        ScrimView scrimForBubble = mStatusBarWindow.findViewById(R.id.scrim_for_bubble);
+        ScrimView scrimBehind = mNotificationShadeWindowView.findViewById(R.id.scrim_behind);
+        ScrimView scrimInFront = mNotificationShadeWindowView.findViewById(R.id.scrim_in_front);
+        ScrimView scrimForBubble = mNotificationShadeWindowView.findViewById(R.id.scrim_for_bubble);
 
         mScrimController.setScrimVisibleListener(scrimsVisible -> {
-            mStatusBarWindowController.setScrimsVisibility(scrimsVisible);
-            if (mStatusBarWindow != null) {
+            mNotificationShadeWindowController.setScrimsVisibility(scrimsVisible);
+            if (mNotificationShadeWindowView != null) {
                 mLockscreenLockIconController.onScrimVisibilityChanged(scrimsVisible);
             }
         });
         mScrimController.attachViews(scrimBehind, scrimInFront, scrimForBubble);
 
-        mNotificationPanel.initDependencies(this, mGroupManager, mNotificationShelf,
-                mHeadsUpManager, mNotificationIconAreaController, mScrimController);
+        mNotificationPanelViewController.initDependencies(this, mGroupManager, mNotificationShelf,
+                mNotificationIconAreaController, mScrimController);
 
-        BackDropView backdrop = mStatusBarWindow.findViewById(R.id.backdrop);
+        BackDropView backdrop = mNotificationShadeWindowView.findViewById(R.id.backdrop);
         mMediaManager.setup(backdrop, backdrop.findViewById(R.id.backdrop_front),
                 backdrop.findViewById(R.id.backdrop_back), mScrimController, mLockscreenWallpaper);
 
-        mNotificationPanel.setUserSetupComplete(mUserSetup);
+        mNotificationPanelViewController.setUserSetupComplete(mUserSetup);
         if (UserManager.get(mContext).isUserSwitcherEnabled()) {
             createUserSwitcher();
         }
 
-        mNotificationPanel.setLaunchAffordanceListener(
+        mNotificationPanelViewController.setLaunchAffordanceListener(
                 mLockscreenLockIconController::onShowingLaunchAffordanceChanged);
 
         // Set up the quick settings tile panel
-        View container = mStatusBarWindow.findViewById(R.id.qs_frame);
+        final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame);
         if (container != null) {
             FragmentHostManager fragmentHostManager = FragmentHostManager.get(container);
             ExtensionFragmentListener.attachExtensonToFragment(container, QS.TAG, R.id.qs_frame,
@@ -1138,7 +1147,9 @@
                             .withPlugin(QS.class)
                             .withDefault(this::createDefaultQSFragment)
                             .build());
-            mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow,
+            mBrightnessMirrorController = new BrightnessMirrorController(
+                    mNotificationShadeWindowView,
+                    mNotificationPanelViewController,
                     (visible) -> {
                         mBrightnessMirrorVisible = visible;
                         updateScrimController();
@@ -1152,7 +1163,8 @@
             });
         }
 
-        mReportRejectedTouch = mStatusBarWindow.findViewById(R.id.report_rejected_touch);
+        mReportRejectedTouch = mNotificationShadeWindowView
+                .findViewById(R.id.report_rejected_touch);
         if (mReportRejectedTouch != null) {
             updateReportRejectedTouchVisibility();
             mReportRejectedTouch.setOnClickListener(v -> {
@@ -1226,28 +1238,20 @@
     }
 
     protected QS createDefaultQSFragment() {
-        return FragmentHostManager.get(mStatusBarWindow).create(QSFragment.class);
+        return FragmentHostManager.get(mNotificationShadeWindowView).create(QSFragment.class);
     }
 
     private void setUpPresenter() {
         // Set up the initial notification state.
         mActivityLaunchAnimator = new ActivityLaunchAnimator(
-                mStatusBarWindowViewController, this, mNotificationPanel,
+                mNotificationShadeWindowViewController, this, mNotificationPanelViewController,
                 (NotificationListContainer) mStackScroller);
 
-        final NotificationRowBinderImpl rowBinder =
-                new NotificationRowBinderImpl(
-                        mContext,
-                        mAllowNotificationLongPress,
-                        mKeyguardBypassController,
-                        mStatusBarStateController,
-                        mNotificationLogger);
-
         // TODO: inject this.
-        mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanel,
-                mHeadsUpManager, mStatusBarWindow, mStackScroller, mDozeScrimController,
+        mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanelViewController,
+                mHeadsUpManager, mNotificationShadeWindowView, mStackScroller, mDozeScrimController,
                 mScrimController, mActivityLaunchAnimator, mDynamicPrivacyController,
-                mNotificationAlertingManager, rowBinder, mKeyguardStateController,
+                mNotificationAlertingManager, mNotificationRowBinder, mKeyguardStateController,
                 mKeyguardIndicationController,
                 this /* statusBar */, mShadeController, mCommandQueue, mInitController);
 
@@ -1258,32 +1262,32 @@
                         mDeviceProvisionedController);
 
         mNotificationShelf.setOnActivatedListener(mPresenter);
-        mRemoteInputManager.getController().addCallback(mStatusBarWindowController);
+        mRemoteInputManager.getController().addCallback(mNotificationShadeWindowController);
 
         mNotificationActivityStarter =
                 mStatusBarNotificationActivityStarterBuilder
                         .setStatusBar(this)
                         .setActivityLaunchAnimator(mActivityLaunchAnimator)
                         .setNotificationPresenter(mPresenter)
+                        .setNotificationPanelViewController(mNotificationPanelViewController)
                         .build();
 
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
 
         if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
-            mEntryManager.setRowBinder(rowBinder);
-            rowBinder.setInflationCallback(mEntryManager);
+            mNotificationRowBinder.setInflationCallback(mEntryManager);
         }
 
         mRemoteInputUriController.attach(mEntryManager);
 
-        rowBinder.setNotificationClicker(new NotificationClicker(
+        mNotificationRowBinder.setNotificationClicker(new NotificationClicker(
                 Optional.of(this), mBubbleController, mNotificationActivityStarter));
 
         mGroupAlertTransferHelper.bind(mEntryManager, mGroupManager);
         mNotificationListController.bind();
 
         if (mFeatureFlags.isNewNotifPipelineEnabled()) {
-            mNewNotifPipeline.get().initialize(mNotificationListener, rowBinder);
+            mNewNotifPipeline.get().initialize(mNotificationListener, mNotificationRowBinder);
         }
         mEntryManager.attach(mNotificationListener);
     }
@@ -1335,7 +1339,7 @@
                     mShadeController.animateCollapsePanels();
                 }
             }
-            return mStatusBarWindow.onTouchEvent(event);
+            return mNotificationShadeWindowView.onTouchEvent(event);
         };
     }
 
@@ -1378,7 +1382,7 @@
         }
         // We need the new R.id.keyguard_indication_area before recreating
         // mKeyguardIndicationController
-        mNotificationPanel.onThemeChanged();
+        mNotificationPanelViewController.onThemeChanged();
         onThemeChanged();
     }
 
@@ -1391,27 +1395,32 @@
 
     protected void createUserSwitcher() {
         mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext,
-                mStatusBarWindow.findViewById(R.id.keyguard_user_switcher),
-                mStatusBarWindow.findViewById(R.id.keyguard_header),
-                mNotificationPanel);
+                mNotificationShadeWindowView.findViewById(R.id.keyguard_user_switcher),
+                mNotificationShadeWindowView.findViewById(R.id.keyguard_header),
+                mNotificationPanelViewController);
     }
 
     private void inflateStatusBarWindow() {
-        mStatusBarWindow = mSuperStatusBarViewFactory.getStatusBarWindowView();
+        mNotificationShadeWindowView = mSuperStatusBarViewFactory.getNotificationShadeWindowView();
         StatusBarComponent statusBarComponent = mStatusBarComponentBuilder.get()
-                .statusBarWindowView(mStatusBarWindow).build();
-        mStatusBarWindowViewController = statusBarComponent.getStatusBarWindowViewController();
-        mStatusBarWindowViewController.setupExpandedStatusBar();
+                .statusBarWindowView(mNotificationShadeWindowView).build();
+        mNotificationShadeWindowViewController = statusBarComponent
+                .getNotificationShadeWindowViewController();
+        mNotificationShadeWindowViewController.setupExpandedStatusBar();
+        mStatusBarWindowController = statusBarComponent.getStatusBarWindowController();
+        mPhoneStatusBarWindow = mSuperStatusBarViewFactory.getStatusBarWindowView();
+        mNotificationPanelViewController = statusBarComponent.getNotificationPanelViewController();
     }
 
     protected void startKeyguard() {
         Trace.beginSection("StatusBar#startKeyguard");
         mBiometricUnlockController = mBiometricUnlockControllerLazy.get();
         mStatusBarKeyguardViewManager.registerStatusBar(
-                /* statusBar= */ this, getBouncerContainer(), mNotificationPanel,
-                mBiometricUnlockController, mDismissCallbackRegistry,
-                mStatusBarWindow.findViewById(R.id.lock_icon_container), mStackScroller,
-                mKeyguardBypassController, mFalsingManager);
+                /* statusBar= */ this, getBouncerContainer(),
+                mNotificationPanelViewController, mBiometricUnlockController,
+                mDismissCallbackRegistry,
+                mNotificationShadeWindowView.findViewById(R.id.lock_icon_container),
+                mStackScroller, mKeyguardBypassController, mFalsingManager);
         mKeyguardIndicationController
                 .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
         mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
@@ -1428,16 +1437,20 @@
         return mStatusBarView;
     }
 
-    public StatusBarWindowView getStatusBarWindow() {
-        return mStatusBarWindow;
+    public NotificationShadeWindowView getNotificationShadeWindowView() {
+        return mNotificationShadeWindowView;
     }
 
-    public StatusBarWindowViewController getStatusBarWindowViewController() {
-        return mStatusBarWindowViewController;
+    public StatusBarWindowView getStatusBarWindow() {
+        return mPhoneStatusBarWindow;
+    }
+
+    public NotificationShadeWindowViewController getNotificationShadeWindowViewController() {
+        return mNotificationShadeWindowViewController;
     }
 
     protected ViewGroup getBouncerContainer() {
-        return mStatusBarWindow;
+        return mNotificationShadeWindowView;
     }
 
     public int getStatusBarHeight() {
@@ -1488,7 +1501,7 @@
                 && ((mDisabled2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) == 0)
                 && !mDozing
                 && !ONLY_CORE_APPS;
-        mNotificationPanel.setQsExpansionEnabled(expandEnabled);
+        mNotificationPanelViewController.setQsExpansionEnabled(expandEnabled);
         Log.d(TAG, "updateQsExpansionEnabled - QS Expand enabled: " + expandEnabled);
     }
 
@@ -1647,8 +1660,8 @@
     }
 
     public void setQsExpanded(boolean expanded) {
-        mStatusBarWindowController.setQsExpanded(expanded);
-        mNotificationPanel.setStatusAccessibilityImportance(expanded
+        mNotificationShadeWindowController.setQsExpanded(expanded);
+        mNotificationPanelViewController.setStatusAccessibilityImportance(expanded
                 ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
                 : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
         if (getNavigationBarView() != null) {
@@ -1680,27 +1693,27 @@
     @Override
     public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) {
         if (inPinnedMode) {
-            mStatusBarWindowController.setHeadsUpShowing(true);
+            mNotificationShadeWindowController.setHeadsUpShowing(true);
             mStatusBarWindowController.setForceStatusBarVisible(true);
-            if (mNotificationPanel.isFullyCollapsed()) {
+            if (mNotificationPanelViewController.isFullyCollapsed()) {
                 // We need to ensure that the touchable region is updated before the window will be
                 // resized, in order to not catch any touches. A layout will ensure that
                 // onComputeInternalInsets will be called and after that we can resize the layout. Let's
                 // make sure that the window stays small for one frame until the touchableRegion is set.
-                mNotificationPanel.requestLayout();
-                mStatusBarWindowController.setForceWindowCollapsed(true);
-                mNotificationPanel.post(() -> {
-                    mStatusBarWindowController.setForceWindowCollapsed(false);
+                mNotificationPanelViewController.getView().requestLayout();
+                mNotificationShadeWindowController.setForceWindowCollapsed(true);
+                mNotificationPanelViewController.getView().post(() -> {
+                    mNotificationShadeWindowController.setForceWindowCollapsed(false);
                 });
             }
         } else {
             boolean bypassKeyguard = mKeyguardBypassController.getBypassEnabled()
                     && mState == StatusBarState.KEYGUARD;
-            if (!mNotificationPanel.isFullyCollapsed() || mNotificationPanel.isTracking()
-                    || bypassKeyguard) {
+            if (!mNotificationPanelViewController.isFullyCollapsed()
+                    || mNotificationPanelViewController.isTracking() || bypassKeyguard) {
                 // We are currently tracking or is open and the shade doesn't need to be kept
                 // open artificially.
-                mStatusBarWindowController.setHeadsUpShowing(false);
+                mNotificationShadeWindowController.setHeadsUpShowing(false);
                 if (bypassKeyguard) {
                     mStatusBarWindowController.setForceStatusBarVisible(false);
                 }
@@ -1708,9 +1721,9 @@
                 // we need to keep the panel open artificially, let's wait until the animation
                 // is finished.
                 mHeadsUpManager.setHeadsUpGoingAway(true);
-                mNotificationPanel.runAfterAnimationFinished(() -> {
+                mNotificationPanelViewController.runAfterAnimationFinished(() -> {
                     if (!mHeadsUpManager.hasPinnedHeadsUp()) {
-                        mStatusBarWindowController.setHeadsUpShowing(false);
+                        mNotificationShadeWindowController.setHeadsUpShowing(false);
                         mHeadsUpManager.setHeadsUpGoingAway(false);
                     }
                     mRemoteInputManager.onPanelCollapsed();
@@ -1738,7 +1751,7 @@
     public void setPanelExpanded(boolean isExpanded) {
         mPanelExpanded = isExpanded;
         updateHideIconsForBouncer(false /* animate */);
-        mStatusBarWindowController.setPanelExpanded(isExpanded);
+        mNotificationShadeWindowController.setPanelExpanded(isExpanded);
         mVisualStabilityManager.setPanelExpanded(isExpanded);
         if (isExpanded && mStatusBarStateController.getState() != StatusBarState.KEYGUARD) {
             if (DEBUG) {
@@ -1761,7 +1774,7 @@
     }
 
     public boolean hideStatusBarIconsWhenExpanded() {
-        return mNotificationPanel.hideStatusBarIconsWhenExpanded();
+        return mNotificationPanelViewController.hideStatusBarIconsWhenExpanded();
     }
 
     @Override
@@ -1951,19 +1964,21 @@
 
         if (KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP == key) {
             mMetricsLogger.action(MetricsEvent.ACTION_SYSTEM_NAVIGATION_KEY_UP);
-            mNotificationPanel.collapse(false /* delayed */, 1.0f /* speedUpFactor */);
+            mNotificationPanelViewController.collapse(
+                    false /* delayed */, 1.0f /* speedUpFactor */);
         } else if (KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN == key) {
             mMetricsLogger.action(MetricsEvent.ACTION_SYSTEM_NAVIGATION_KEY_DOWN);
-            if (mNotificationPanel.isFullyCollapsed()) {
+            if (mNotificationPanelViewController.isFullyCollapsed()) {
                 if (mVibrateOnOpening) {
                     mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
                 }
-                mNotificationPanel.expand(true /* animate */);
+                mNotificationPanelViewController.expand(true /* animate */);
                 ((NotificationListContainer) mStackScroller).setWillExpand(true);
                 mHeadsUpManager.unpinAll(true /* userUnpinned */);
                 mMetricsLogger.count(NotificationPanelView.COUNTER_PANEL_OPEN, 1);
-            } else if (!mNotificationPanel.isInSettings() && !mNotificationPanel.isExpanding()){
-                mNotificationPanel.flingSettings(0 /* velocity */,
+            } else if (!mNotificationPanelViewController.isInSettings()
+                    && !mNotificationPanelViewController.isExpanding()) {
+                mNotificationPanelViewController.flingSettings(0 /* velocity */,
                         NotificationPanelView.FLING_EXPAND);
                 mMetricsLogger.count(NotificationPanelView.COUNTER_PANEL_OPEN_QS, 1);
             }
@@ -1995,7 +2010,7 @@
 
         // Expand the window to encompass the full screen in anticipation of the drag.
         // This is only possible to do atomically because the status bar is at the top of the screen!
-        mStatusBarWindowController.setPanelVisible(true);
+        mNotificationShadeWindowController.setPanelVisible(true);
 
         visibilityChanged(true);
         mCommandQueue.recomputeDisableFlags(mDisplayId, !force /* animate */);
@@ -2058,9 +2073,9 @@
         }
 
         if (start) {
-            mNotificationPanel.startWaitingForOpenPanelGesture();
+            mNotificationPanelViewController.startWaitingForOpenPanelGesture();
         } else {
-            mNotificationPanel.stopWaitingForOpenPanelGesture(velocity);
+            mNotificationPanelViewController.stopWaitingForOpenPanelGesture(velocity);
         }
     }
 
@@ -2071,7 +2086,7 @@
             return ;
         }
 
-        mNotificationPanel.expandWithoutQs();
+        mNotificationPanelViewController.expandWithoutQs();
 
         if (false) postStartTracing();
     }
@@ -2089,7 +2104,7 @@
         if (subPanel != null) {
             mQSPanel.openDetails(subPanel);
         }
-        mNotificationPanel.expandWithQs();
+        mNotificationPanelViewController.expandWithQs();
 
         if (false) postStartTracing();
     }
@@ -2104,7 +2119,7 @@
         if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible
                 + " mExpandedVisible=" + mExpandedVisible);
 
-        if (!mExpandedVisible || mStatusBarWindow == null) {
+        if (!mExpandedVisible || mNotificationShadeWindowView == null) {
             return;
         }
 
@@ -2112,13 +2127,13 @@
         mStatusBarView.collapsePanel(/*animate=*/ false, false /* delayed*/,
                 1.0f /* speedUpFactor */);
 
-        mNotificationPanel.closeQs();
+        mNotificationPanelViewController.closeQs();
 
         mExpandedVisible = false;
         visibilityChanged(false);
 
-        // Shrink the window to the size of the status bar only
-        mStatusBarWindowController.setPanelVisible(false);
+        // Update the visibility of notification shade and status bar window.
+        mNotificationShadeWindowController.setPanelVisible(false);
         mStatusBarWindowController.setForceStatusBarVisible(false);
 
         // Close any guts that might be visible
@@ -2133,7 +2148,8 @@
             Log.d(TAG, "Not showing bouncer due to activity showing over lockscreen");
         }
         mCommandQueue.recomputeDisableFlags(
-                mDisplayId, mNotificationPanel.hideStatusBarIconsWhenExpanded() /* animate */);
+                mDisplayId,
+                mNotificationPanelViewController.hideStatusBarIconsWhenExpanded() /* animate */);
 
         // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
         // the bouncer appear animation.
@@ -2181,6 +2197,10 @@
         return false;
     }
 
+    boolean isSameStatusBarState(int state) {
+        return mStatusBarWindowState == state;
+    }
+
     public GestureRecorder getGestureRecorder() {
         return mGestureRec;
     }
@@ -2196,7 +2216,7 @@
             return;
         }
         boolean showing = state == WINDOW_STATE_SHOWING;
-        if (mStatusBarWindow != null
+        if (mNotificationShadeWindowView != null
                 && window == StatusBarManager.WINDOW_STATUS_BAR
                 && mStatusBarWindowState != state) {
             mStatusBarWindowState = state;
@@ -2314,12 +2334,12 @@
                     batteryLevel, new WirelessChargingAnimation.Callback() {
                         @Override
                         public void onAnimationStarting() {
-                            CrossFadeHelper.fadeOut(mNotificationPanel, 1);
+                            CrossFadeHelper.fadeOut(mNotificationPanelViewController.getView(), 1);
                         }
 
                         @Override
                         public void onAnimationEnded() {
-                            CrossFadeHelper.fadeIn(mNotificationPanel);
+                            CrossFadeHelper.fadeIn(mNotificationPanelViewController.getView());
                         }
                     }, mDozing).show();
         } else {
@@ -2348,7 +2368,7 @@
 
     // Called by NavigationBarFragment
     void setQsScrimEnabled(boolean scrimEnabled) {
-        mNotificationPanel.setQsScrimEnabled(scrimEnabled);
+        mNotificationPanelViewController.setQsScrimEnabled(scrimEnabled);
     }
 
     void checkBarMode(@TransitionMode int mode, @WindowVisibleState int windowState,
@@ -2430,8 +2450,8 @@
             dumpBarTransitions(pw, "mStatusBarView", mStatusBarView.getBarTransitions());
         }
         pw.println("  StatusBarWindowView: ");
-        if (mStatusBarWindowViewController != null) {
-            mStatusBarWindowViewController.dump(fd, pw, args);
+        if (mNotificationShadeWindowViewController != null) {
+            mNotificationShadeWindowViewController.dump(fd, pw, args);
         }
 
         pw.println("  mMediaManager: ");
@@ -2440,11 +2460,12 @@
         }
 
         pw.println("  Panels: ");
-        if (mNotificationPanel != null) {
-            pw.println("    mNotificationPanel=" +
-                mNotificationPanel + " params=" + mNotificationPanel.getLayoutParams().debug(""));
+        if (mNotificationPanelViewController != null) {
+            pw.println("    mNotificationPanel="
+                    + mNotificationPanelViewController.getView() + " params="
+                    + mNotificationPanelViewController.getView().getLayoutParams().debug(""));
             pw.print  ("      ");
-            mNotificationPanel.dump(fd, pw, args);
+            mNotificationPanelViewController.dump(fd, pw, args);
         }
         pw.println("  mStackScroller: ");
         if (mStackScroller instanceof Dumpable) {
@@ -2529,6 +2550,7 @@
 
     public void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
         makeStatusBarView(result);
+        mNotificationShadeWindowController.attach();
         mStatusBarWindowController.attach();
     }
 
@@ -2657,7 +2679,8 @@
                     // Do it after DismissAction has been processed to conserve the needed ordering.
                     mHandler.post(mShadeController::runPostCollapseRunnables);
                 }
-            } else if (isInLaunchTransition() && mNotificationPanel.isLaunchTransitionFinished()) {
+            } else if (isInLaunchTransition()
+                    && mNotificationPanelViewController.isLaunchTransitionFinished()) {
 
                 // We are not dismissing the shade, but the launch transition is already finished,
                 // so nobody will call readyForKeyguardDone anymore. Post it such that
@@ -2691,8 +2714,8 @@
                 }
             }
             else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
-                if (mStatusBarWindowController != null) {
-                    mStatusBarWindowController.setNotTouchable(false);
+                if (mNotificationShadeWindowController != null) {
+                    mNotificationShadeWindowController.setNotTouchable(false);
                 }
                 if (mBubbleController.isStackExpanded()) {
                     mBubbleController.collapseStack();
@@ -2809,13 +2832,15 @@
             mQSPanel.updateResources();
         }
 
-        mStatusBarWindowController.refreshStatusBarHeight();
+        if (mStatusBarWindowController != null) {
+            mStatusBarWindowController.refreshStatusBarHeight();
+        }
 
         if (mStatusBarView != null) {
             mStatusBarView.updateResources();
         }
-        if (mNotificationPanel != null) {
-            mNotificationPanel.updateResources();
+        if (mNotificationPanelViewController != null) {
+            mNotificationPanelViewController.updateResources();
         }
         if (mBrightnessMirrorController != null) {
             mBrightnessMirrorController.updateResources();
@@ -3107,7 +3132,7 @@
     public void showKeyguardImpl() {
         mIsKeyguard = true;
         if (mKeyguardStateController.isLaunchTransitionFadingAway()) {
-            mNotificationPanel.animate().cancel();
+            mNotificationPanelViewController.cancelAnimation();
             onLaunchTransitionFadingEnded();
         }
         mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
@@ -3134,8 +3159,8 @@
     }
 
     private void onLaunchTransitionFadingEnded() {
-        mNotificationPanel.setAlpha(1.0f);
-        mNotificationPanel.onAffordanceLaunchEnded();
+        mNotificationPanelViewController.setAlpha(1.0f);
+        mNotificationPanelViewController.onAffordanceLaunchEnded();
         releaseGestureWakeLock();
         runLaunchTransitionEndRunnable();
         mKeyguardStateController.setLaunchTransitionFadingAway(false);
@@ -3143,8 +3168,8 @@
     }
 
     public boolean isInLaunchTransition() {
-        return mNotificationPanel.isLaunchTransitionRunning()
-                || mNotificationPanel.isLaunchTransitionFinished();
+        return mNotificationPanelViewController.isLaunchTransitionRunning()
+                || mNotificationPanelViewController.isLaunchTransitionFinished();
     }
 
     /**
@@ -3165,18 +3190,15 @@
             }
             updateScrimController();
             mPresenter.updateMediaMetaData(false, true);
-            mNotificationPanel.setAlpha(1);
-            mNotificationPanel.animate()
-                    .alpha(0)
-                    .setStartDelay(FADE_KEYGUARD_START_DELAY)
-                    .setDuration(FADE_KEYGUARD_DURATION)
-                    .withLayer()
-                    .withEndAction(this::onLaunchTransitionFadingEnded);
+            mNotificationPanelViewController.setAlpha(1);
+            mNotificationPanelViewController.fadeOut(
+                    FADE_KEYGUARD_START_DELAY, FADE_KEYGUARD_DURATION,
+                    this::onLaunchTransitionFadingEnded);
             mCommandQueue.appTransitionStarting(mDisplayId, SystemClock.uptimeMillis(),
                     LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true);
         };
-        if (mNotificationPanel.isLaunchTransitionRunning()) {
-            mNotificationPanel.setLaunchTransitionEndRunnable(hideRunnable);
+        if (mNotificationPanelViewController.isLaunchTransitionRunning()) {
+            mNotificationPanelViewController.setLaunchTransitionEndRunnable(hideRunnable);
         } else {
             hideRunnable.run();
         }
@@ -3187,22 +3209,18 @@
      * fading.
      */
     public void fadeKeyguardWhilePulsing() {
-        mNotificationPanel.animate()
-                .alpha(0f)
-                .setStartDelay(0)
-                .setDuration(FADE_KEYGUARD_DURATION_PULSING)
-                .setInterpolator(Interpolators.ALPHA_OUT)
-                .withEndAction(()-> {
-                    hideKeyguard();
-                    mStatusBarKeyguardViewManager.onKeyguardFadedAway();
-                }).start();
+        mNotificationPanelViewController.fadeOut(0, FADE_KEYGUARD_DURATION_PULSING,
+                ()-> {
+                hideKeyguard();
+                mStatusBarKeyguardViewManager.onKeyguardFadedAway();
+            }).start();
     }
 
     /**
      * Plays the animation when an activity that was occluding Keyguard goes away.
      */
     public void animateKeyguardUnoccluding() {
-        mNotificationPanel.setExpandedFraction(0f);
+        mNotificationPanelViewController.setExpandedFraction(0f);
         animateExpandNotificationsPanel();
     }
 
@@ -3218,9 +3236,9 @@
 
     private void onLaunchTransitionTimeout() {
         Log.w(TAG, "Launch transition: Timeout!");
-        mNotificationPanel.onAffordanceLaunchEnded();
+        mNotificationPanelViewController.onAffordanceLaunchEnded();
         releaseGestureWakeLock();
-        mNotificationPanel.resetViews(false /* animate */);
+        mNotificationPanelViewController.resetViews(false /* animate */);
     }
 
     private void runLaunchTransitionEndRunnable() {
@@ -3253,7 +3271,7 @@
                 mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
             }
             long delay = mKeyguardStateController.calculateGoingToFullShadeDelay();
-            mNotificationPanel.animateToFullShade(delay);
+            mNotificationPanelViewController.animateToFullShade(delay);
             if (mDraggedDownEntry != null) {
                 mDraggedDownEntry.setUserLocked(false);
                 mDraggedDownEntry = null;
@@ -3262,7 +3280,7 @@
             // Disable layout transitions in navbar for this transition because the load is just
             // too heavy for the CPU and GPU on any device.
             mNavigationBarController.disableAnimationsDuringHide(mDisplayId, delay);
-        } else if (!mNotificationPanel.isCollapsing()) {
+        } else if (!mNotificationPanelViewController.isCollapsing()) {
             instantCollapseNotificationPanel();
         }
 
@@ -3273,10 +3291,10 @@
         }
         mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
         releaseGestureWakeLock();
-        mNotificationPanel.onAffordanceLaunchEnded();
-        mNotificationPanel.animate().cancel();
-        mNotificationPanel.setAlpha(1f);
-        ViewGroupFadeHelper.reset(mNotificationPanel);
+        mNotificationPanelViewController.onAffordanceLaunchEnded();
+        mNotificationPanelViewController.cancelAnimation();
+        mNotificationPanelViewController.setAlpha(1f);
+        mNotificationPanelViewController.resetViewGroupFade();
         updateScrimController();
         Trace.endSection();
         return staying;
@@ -3351,7 +3369,7 @@
         boolean animate = (!mDozing && mDozeServiceHost.shouldAnimateWakeup() && !wakeAndUnlock)
                 || (mDozing && mDozeServiceHost.shouldAnimateScreenOff() && sleepingFromKeyguard);
 
-        mNotificationPanel.setDozing(mDozing, animate, mWakeUpTouchLocation);
+        mNotificationPanelViewController.setDozing(mDozing, animate, mWakeUpTouchLocation);
         updateQsExpansionEnabled();
         Trace.endSection();
     }
@@ -3383,27 +3401,27 @@
 
     public void endAffordanceLaunch() {
         releaseGestureWakeLock();
-        mNotificationPanel.onAffordanceLaunchEnded();
+        mNotificationPanelViewController.onAffordanceLaunchEnded();
     }
 
     public boolean onBackPressed() {
         boolean isScrimmedBouncer = mScrimController.getState() == ScrimState.BOUNCER_SCRIMMED;
         if (mStatusBarKeyguardViewManager.onBackPressed(isScrimmedBouncer /* hideImmediately */)) {
             if (!isScrimmedBouncer) {
-                mNotificationPanel.expandWithoutQs();
+                mNotificationPanelViewController.expandWithoutQs();
             }
             return true;
         }
-        if (mNotificationPanel.isQsExpanded()) {
-            if (mNotificationPanel.isQsDetailShowing()) {
-                mNotificationPanel.closeQsDetail();
+        if (mNotificationPanelViewController.isQsExpanded()) {
+            if (mNotificationPanelViewController.isQsDetailShowing()) {
+                mNotificationPanelViewController.closeQsDetail();
             } else {
-                mNotificationPanel.animateCloseQs(false /* animateAway */);
+                mNotificationPanelViewController.animateCloseQs(false /* animateAway */);
             }
             return true;
         }
         if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED) {
-            if (mNotificationPanel.canPanelBeCollapsed()) {
+            if (mNotificationPanelViewController.canPanelBeCollapsed()) {
                 mShadeController.animateCollapsePanels();
             } else {
                 mBubbleController.performBackPressIfNeeded();
@@ -3433,7 +3451,7 @@
     }
 
     void instantCollapseNotificationPanel() {
-        mNotificationPanel.instantCollapse();
+        mNotificationPanelViewController.instantCollapse();
         mShadeController.runPostCollapseRunnables();
     }
 
@@ -3499,7 +3517,7 @@
         // Collapse the notification panel if open
         boolean dozingAnimated = mDozeServiceHost.getDozingRequested()
                 && mDozeParameters.shouldControlScreenOff();
-        mNotificationPanel.resetViews(dozingAnimated);
+        mNotificationPanelViewController.resetViews(dozingAnimated);
 
         updateQsExpansionEnabled();
         mKeyguardViewMediator.setDozing(mDozing);
@@ -3526,7 +3544,7 @@
         if (!mPresenter.isPresenterFullyCollapsed()) {
             // if we set it not to be focusable when collapsing, we have to undo it when we aborted
             // the closing
-            mStatusBarWindowController.setStatusBarFocusable(true);
+            mNotificationShadeWindowController.setNotificationShadeFocusable(true);
         }
     }
 
@@ -3573,7 +3591,7 @@
      * @return bottom area view
      */
     public KeyguardBottomAreaView getKeyguardBottomAreaView() {
-        return mNotificationPanel.getKeyguardBottomAreaView();
+        return mNotificationPanelViewController.getKeyguardBottomAreaView();
     }
 
     /**
@@ -3612,7 +3630,7 @@
             mDraggedDownEntry = entry;
             mPendingRemoteInputView = null;
         } else {
-            mNotificationPanel.animateToFullShade(0 /* delay */);
+            mNotificationPanelViewController.animateToFullShade(0 /* delay */);
             mStatusBarStateController.setState(StatusBarState.SHADE_LOCKED);
         }
     }
@@ -3638,8 +3656,8 @@
      * Collapses the notification shade if it is tracking or expanded.
      */
     public void collapseShade() {
-        if (mNotificationPanel.isTracking()) {
-            mStatusBarWindowViewController.cancelCurrentTouch();
+        if (mNotificationPanelViewController.isTracking()) {
+            mNotificationShadeWindowViewController.cancelCurrentTouch();
         }
         if (mPanelExpanded && mState == StatusBarState.SHADE) {
             mShadeController.animateCollapsePanels();
@@ -3650,7 +3668,7 @@
     final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
         @Override
         public void onFinishedGoingToSleep() {
-            mNotificationPanel.onAffordanceLaunchEnded();
+            mNotificationPanelViewController.onAffordanceLaunchEnded();
             releaseGestureWakeLock();
             mLaunchCameraWhenFinishedWaking = false;
             mDeviceInteractive = false;
@@ -3660,7 +3678,7 @@
             updateVisibleToUser();
 
             updateNotificationPanelTouchState();
-            mStatusBarWindowViewController.cancelCurrentTouch();
+            mNotificationShadeWindowViewController.cancelCurrentTouch();
             if (mLaunchCameraOnFinishedGoingToSleep) {
                 mLaunchCameraOnFinishedGoingToSleep = false;
 
@@ -3711,7 +3729,8 @@
             mBypassHeadsUpNotifier.setFullyAwake(true);
             mWakeUpCoordinator.setWakingUp(false);
             if (mLaunchCameraWhenFinishedWaking) {
-                mNotificationPanel.launchCamera(false /* animate */, mLastCameraLaunchSource);
+                mNotificationPanelViewController.launchCamera(
+                        false /* animate */, mLastCameraLaunchSource);
                 mLaunchCameraWhenFinishedWaking = false;
             }
             updateScrimController();
@@ -3728,7 +3747,7 @@
                 && !mDozeParameters.shouldControlScreenOff();
         boolean disabled = (!mDeviceInteractive && !mDozeServiceHost.isPulsing())
                 || goingToSleepWithoutAnimation;
-        mNotificationPanel.setTouchAndAnimationDisabled(disabled);
+        mNotificationPanelViewController.setTouchAndAnimationDisabled(disabled);
         mNotificationIconAreaController.setAnimationsEnabled(!disabled);
     }
 
@@ -3736,7 +3755,7 @@
         @Override
         public void onScreenTurningOn() {
             mFalsingManager.onScreenTurningOn();
-            mNotificationPanel.onScreenTurningOn();
+            mNotificationPanelViewController.onScreenTurningOn();
         }
 
         @Override
@@ -3805,7 +3824,7 @@
             mLaunchCameraOnFinishedGoingToSleep = true;
             return;
         }
-        if (!mNotificationPanel.canCameraGestureBeLaunched()) {
+        if (!mNotificationPanelViewController.canCameraGestureBeLaunched()) {
             if (DEBUG_CAMERA_LIFT) Slog.d(TAG, "Can't launch camera right now");
             return;
         }
@@ -3835,7 +3854,8 @@
                 if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
                     mStatusBarKeyguardViewManager.reset(true /* hide */);
                 }
-                mNotificationPanel.launchCamera(mDeviceInteractive /* animate */, source);
+                mNotificationPanelViewController.launchCamera(
+                        mDeviceInteractive /* animate */, source);
                 updateScrimController();
             } else {
                 // We need to defer the camera launch until the screen comes on, since otherwise
@@ -3894,7 +3914,7 @@
                 !mBiometricUnlockController.isBiometricUnlock());
 
         boolean launchingAffordanceWithPreview =
-                mNotificationPanel.isLaunchingAffordanceWithPreview();
+                mNotificationPanelViewController.isLaunchingAffordanceWithPreview();
         mScrimController.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview);
 
         if (mBouncerShowing) {
@@ -4032,6 +4052,11 @@
         }
     }
 
+    public void setNotificationSnoozed(StatusBarNotification sbn, int hoursToSnooze) {
+        mNotificationListener.snoozeNotification(sbn.getKey(),
+                hoursToSnooze * 60 * 60 * 1000);
+    }
+
     @Override
     public void toggleSplitScreen() {
         toggleSplitScreenMode(-1 /* metricsDockAction */, -1 /* metricsUndockAction */);
@@ -4243,7 +4268,7 @@
      * When {@link KeyguardBouncer} starts to be dismissed, playing its animation.
      */
     public void onBouncerPreHideAnimation() {
-        mNotificationPanel.onBouncerPreHideAnimation();
+        mNotificationPanelViewController.onBouncerPreHideAnimation();
         mLockscreenLockIconController.onBouncerPreHideAnimation();
     }
 
@@ -4286,8 +4311,8 @@
         mAssistManagerLazy.get().showDisclosure();
     }
 
-    public NotificationPanelView getPanel() {
-        return mNotificationPanel;
+    public NotificationPanelViewController getPanelController() {
+        return mNotificationPanelViewController;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarComponent.java
deleted file mode 100644
index f3c843c..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarComponent.java
+++ /dev/null
@@ -1,57 +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.statusbar.phone;
-
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-
-import javax.inject.Scope;
-
-import dagger.BindsInstance;
-import dagger.Subcomponent;
-
-/**
- * Dagger subcomponent tied to the lifecycle of StatusBar views.
- */
-@Subcomponent
-public interface StatusBarComponent {
-    /**
-     * Builder for {@link StatusBarComponent}.
-     */
-    @Subcomponent.Builder
-    interface Builder {
-        @BindsInstance Builder statusBarWindowView(StatusBarWindowView statusBarWindowView);
-        StatusBarComponent build();
-    }
-
-    /**
-     * Scope annotation for singleton items within the StatusBarComponent.
-     */
-    @Documented
-    @Retention(RUNTIME)
-    @Scope
-    @interface StatusBarScope {}
-
-    /**
-     * Creates a StatusBarWindowViewController.
-     */
-    @StatusBarScope
-    StatusBarWindowViewController getStatusBarWindowViewController();
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 5daef24..75da5d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -76,12 +76,12 @@
 
     public static final String ICON_BLACKLIST = "icon_blacklist";
 
-    public static ArraySet<String> getIconBlacklist(String blackListStr) {
+    /** Reads the default blacklist from config value unless blacklistStr is provided. */
+    static ArraySet<String> getIconBlacklist(Context context, String blackListStr) {
         ArraySet<String> ret = new ArraySet<>();
-        if (blackListStr == null) {
-            blackListStr = "rotate,headset";
-        }
-        String[] blacklist = blackListStr.split(",");
+        String[] blacklist = blackListStr == null
+            ? context.getResources().getStringArray(R.array.config_statusBarIconBlackList)
+            : blackListStr.split(",");
         for (String slot : blacklist) {
             if (!TextUtils.isEmpty(slot)) {
                 ret.add(slot);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index aa062eb..bfcbcea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -111,7 +111,7 @@
             return;
         }
         mIconBlacklist.clear();
-        mIconBlacklist.addAll(StatusBarIconController.getIconBlacklist(newValue));
+        mIconBlacklist.addAll(StatusBarIconController.getIconBlacklist(mContext, newValue));
         ArrayList<Slot> currentSlots = getSlots();
         ArrayMap<Slot, List<StatusBarIconHolder>> slotsToReAdd = new ArrayMap<>();
 
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 f51174b..86a81ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -26,7 +26,6 @@
 import android.content.res.ColorStateList;
 import android.os.Bundle;
 import android.os.SystemClock;
-import android.util.StatsLog;
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup;
@@ -48,6 +47,7 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.RemoteInputController;
@@ -92,7 +92,7 @@
     private static String TAG = "StatusBarKeyguardViewManager";
 
     protected final Context mContext;
-    private final StatusBarWindowController mStatusBarWindowController;
+    private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final BouncerExpansionCallback mExpansionCallback = new BouncerExpansionCallback() {
         @Override
         public void onFullyShown() {
@@ -133,7 +133,7 @@
     protected LockPatternUtils mLockPatternUtils;
     protected ViewMediatorCallback mViewMediatorCallback;
     protected StatusBar mStatusBar;
-    private NotificationPanelView mNotificationPanelView;
+    private NotificationPanelViewController mNotificationPanelViewController;
     private BiometricUnlockController mBiometricUnlockController;
 
     private ViewGroup mContainer;
@@ -199,13 +199,13 @@
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             NavigationModeController navigationModeController,
             DockManager dockManager,
-            StatusBarWindowController statusBarWindowController,
+            NotificationShadeWindowController notificationShadeWindowController,
             KeyguardStateController keyguardStateController,
             NotificationMediaManager notificationMediaManager) {
         mContext = context;
         mViewMediatorCallback = callback;
         mLockPatternUtils = lockPatternUtils;
-        mStatusBarWindowController = statusBarWindowController;
+        mNotificationShadeWindowController = notificationShadeWindowController;
         mKeyguardStateController = keyguardStateController;
         mMediaManager = notificationMediaManager;
         mKeyguardUpdateManager = keyguardUpdateMonitor;
@@ -224,7 +224,7 @@
 
     public void registerStatusBar(StatusBar statusBar,
             ViewGroup container,
-            NotificationPanelView notificationPanelView,
+            NotificationPanelViewController notificationPanelViewController,
             BiometricUnlockController biometricUnlockController,
             DismissCallbackRegistry dismissCallbackRegistry,
             ViewGroup lockIconContainer, View notificationContainer,
@@ -239,8 +239,8 @@
         mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
                 mViewMediatorCallback, mLockPatternUtils, container, dismissCallbackRegistry,
                 mExpansionCallback, mKeyguardStateController, falsingManager, bypassController);
-        mNotificationPanelView = notificationPanelView;
-        notificationPanelView.addExpansionListener(this);
+        mNotificationPanelViewController = notificationPanelViewController;
+        notificationPanelViewController.addExpansionListener(this);
         mBypassController = bypassController;
         mNotificationContainer = notificationContainer;
     }
@@ -253,7 +253,7 @@
         // • The user quickly taps on the display and we show "swipe up to unlock."
         // • Keyguard will be dismissed by an action. a.k.a: FLAG_DISMISS_KEYGUARD_ACTIVITY
         // • Full-screen user switcher is displayed.
-        if (mNotificationPanelView.isUnlockHintRunning()) {
+        if (mNotificationPanelViewController.isUnlockHintRunning()) {
             mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
         } else if (bouncerNeedsScrimming()) {
             mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
@@ -284,7 +284,7 @@
             return;
         }
         boolean keyguardWithoutQs = mStatusBarStateController.getState() == StatusBarState.KEYGUARD
-                && !mNotificationPanelView.isQsExpanded();
+                && !mNotificationPanelViewController.isQsExpanded();
         boolean lockVisible = (mBouncer.isShowing() || keyguardWithoutQs)
                 && !mBouncer.isAnimatingAway() && !mKeyguardStateController.isKeyguardFadingAway();
 
@@ -315,12 +315,12 @@
      */
     public void show(Bundle options) {
         mShowing = true;
-        mStatusBarWindowController.setKeyguardShowing(true);
+        mNotificationShadeWindowController.setKeyguardShowing(true);
         mKeyguardStateController.notifyKeyguardState(mShowing,
                 mKeyguardStateController.isOccluded());
         reset(true /* hideBouncerWhenShowing */);
-        StatsLog.write(StatsLog.KEYGUARD_STATE_CHANGED,
-            StatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN);
+        SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
+                SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN);
     }
 
     /**
@@ -491,40 +491,40 @@
     }
 
     public void setNeedsInput(boolean needsInput) {
-        mStatusBarWindowController.setKeyguardNeedsInput(needsInput);
+        mNotificationShadeWindowController.setKeyguardNeedsInput(needsInput);
     }
 
     public boolean isUnlockWithWallpaper() {
-        return mStatusBarWindowController.isShowingWallpaper();
+        return mNotificationShadeWindowController.isShowingWallpaper();
     }
 
     public void setOccluded(boolean occluded, boolean animate) {
         mStatusBar.setOccluded(occluded);
         if (occluded && !mOccluded && mShowing) {
-            StatsLog.write(StatsLog.KEYGUARD_STATE_CHANGED,
-                StatsLog.KEYGUARD_STATE_CHANGED__STATE__OCCLUDED);
+            SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
+                    SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__OCCLUDED);
             if (mStatusBar.isInLaunchTransition()) {
                 mOccluded = true;
                 mStatusBar.fadeKeyguardAfterLaunchTransition(null /* beforeFading */,
                         new Runnable() {
                             @Override
                             public void run() {
-                                mStatusBarWindowController.setKeyguardOccluded(mOccluded);
+                                mNotificationShadeWindowController.setKeyguardOccluded(mOccluded);
                                 reset(true /* hideBouncerWhenShowing */);
                             }
                         });
                 return;
             }
         } else if (!occluded && mOccluded && mShowing) {
-            StatsLog.write(StatsLog.KEYGUARD_STATE_CHANGED,
-                StatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN);
+            SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
+                    SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN);
         }
         boolean isOccluding = !mOccluded && occluded;
         mOccluded = occluded;
         if (mShowing) {
             mMediaManager.updateMediaMetaData(false, animate && !occluded);
         }
-        mStatusBarWindowController.setKeyguardOccluded(occluded);
+        mNotificationShadeWindowController.setKeyguardOccluded(occluded);
 
         // setDozing(false) will call reset once we stop dozing.
         if (!mDozing) {
@@ -555,7 +555,7 @@
         } else if (finishRunnable != null) {
             finishRunnable.run();
         }
-        mNotificationPanelView.blockExpansionForCurrentTouch();
+        mNotificationPanelViewController.blockExpansionForCurrentTouch();
         updateLockIcon();
     }
 
@@ -578,8 +578,8 @@
             mStatusBar.fadeKeyguardAfterLaunchTransition(new Runnable() {
                 @Override
                 public void run() {
-                    mStatusBarWindowController.setKeyguardShowing(false);
-                    mStatusBarWindowController.setKeyguardFadingAway(true);
+                    mNotificationShadeWindowController.setKeyguardShowing(false);
+                    mNotificationShadeWindowController.setKeyguardFadingAway(true);
                     hideBouncer(true /* destroyView */);
                     updateStates();
                 }
@@ -587,7 +587,7 @@
                 @Override
                 public void run() {
                     mStatusBar.hideKeyguard();
-                    mStatusBarWindowController.setKeyguardFadingAway(false);
+                    mNotificationShadeWindowController.setKeyguardFadingAway(false);
                     mViewMediatorCallback.keyguardGone();
                     executeAfterKeyguardGoneAction();
                 }
@@ -609,7 +609,8 @@
             hideBouncer(true /* destroyView */);
             if (wakeUnlockPulsing) {
                 if (needsFading) {
-                    ViewGroupFadeHelper.fadeOutAllChildrenExcept(mNotificationPanelView,
+                    ViewGroupFadeHelper.fadeOutAllChildrenExcept(
+                            mNotificationPanelViewController.getView(),
                             mNotificationContainer,
                             fadeoutDuration,
                                     () -> {
@@ -623,9 +624,10 @@
             } else {
                 boolean staying = mStatusBarStateController.leaveOpenOnKeyguardHide();
                 if (!staying) {
-                    mStatusBarWindowController.setKeyguardFadingAway(true);
+                    mNotificationShadeWindowController.setKeyguardFadingAway(true);
                     if (needsFading) {
-                        ViewGroupFadeHelper.fadeOutAllChildrenExcept(mNotificationPanelView,
+                        ViewGroupFadeHelper.fadeOutAllChildrenExcept(
+                                mNotificationPanelViewController.getView(),
                                 mNotificationContainer,
                                 fadeoutDuration,
                                 () -> {
@@ -648,11 +650,11 @@
             }
             updateLockIcon();
             updateStates();
-            mStatusBarWindowController.setKeyguardShowing(false);
+            mNotificationShadeWindowController.setKeyguardShowing(false);
             mViewMediatorCallback.keyguardGone();
         }
-        StatsLog.write(StatsLog.KEYGUARD_STATE_CHANGED,
-            StatsLog.KEYGUARD_STATE_CHANGED__STATE__HIDDEN);
+        SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
+                SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__HIDDEN);
     }
 
     private boolean needsBypassFading() {
@@ -682,9 +684,9 @@
     }
 
     public void onKeyguardFadedAway() {
-        mContainer.postDelayed(() -> mStatusBarWindowController.setKeyguardFadingAway(false),
-                100);
-        ViewGroupFadeHelper.reset(mNotificationPanelView);
+        mContainer.postDelayed(() -> mNotificationShadeWindowController
+                        .setKeyguardFadingAway(false), 100);
+        ViewGroupFadeHelper.reset(mNotificationPanelViewController.getView());
         mStatusBar.finishKeyguardFadingAway();
         mBiometricUnlockController.finishKeyguardFadingAway();
         WindowManagerGlobal.getInstance().trimMemory(
@@ -816,7 +818,7 @@
         }
 
         if (bouncerShowing != mLastBouncerShowing || mFirstUpdate) {
-            mStatusBarWindowController.setBouncerShowing(bouncerShowing);
+            mNotificationShadeWindowController.setBouncerShowing(bouncerShowing);
             mStatusBar.setBouncerShowing(bouncerShowing);
             updateLockIcon();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
index 12033de..7615bf8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
 
 import android.content.Context;
@@ -66,9 +65,11 @@
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -116,8 +117,7 @@
             HeadsUpManagerPhone headsUpManagerPhone,
             DynamicPrivacyController dynamicPrivacyController,
             BypassHeadsUpNotifier bypassHeadsUpNotifier,
-            @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowNotificationLongPress,
-            Lazy<NewNotifPipeline> newNotifPipeline,
+            Lazy<NotifPipelineInitializer> newNotifPipeline,
             FalsingManager falsingManager,
             BroadcastDispatcher broadcastDispatcher,
             RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
@@ -151,7 +151,7 @@
             Lazy<AssistManager> assistManagerLazy,
             NotificationListener notificationListener,
             ConfigurationController configurationController,
-            StatusBarWindowController statusBarWindowController,
+            NotificationShadeWindowController notificationShadeWindowController,
             LockscreenLockIconController lockscreenLockIconController,
             DozeParameters dozeParameters,
             ScrimController scrimController,
@@ -183,6 +183,7 @@
             KeyguardDismissUtil keyguardDismissUtil,
             ExtensionController extensionController,
             UserInfoControllerImpl userInfoControllerImpl,
+            NotificationRowBinderImpl notificationRowBinder,
             DismissCallbackRegistry dismissCallbackRegistry) {
         return new StatusBar(
                 context,
@@ -198,7 +199,6 @@
                 headsUpManagerPhone,
                 dynamicPrivacyController,
                 bypassHeadsUpNotifier,
-                allowNotificationLongPress,
                 newNotifPipeline,
                 falsingManager,
                 broadcastDispatcher,
@@ -233,7 +233,7 @@
                 assistManagerLazy,
                 notificationListener,
                 configurationController,
-                statusBarWindowController,
+                notificationShadeWindowController,
                 lockscreenLockIconController,
                 dozeParameters,
                 scrimController,
@@ -264,6 +264,7 @@
                 keyguardDismissUtil,
                 extensionController,
                 userInfoControllerImpl,
+                notificationRowBinder,
                 dismissCallbackRegistry);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 661a7b1..0f3b5db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -61,7 +61,6 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
@@ -102,7 +101,7 @@
     private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
     private final MetricsLogger mMetricsLogger;
     private final Context mContext;
-    private final NotificationPanelView mNotificationPanel;
+    private final NotificationPanelViewController mNotificationPanel;
     private final NotificationPresenter mPresenter;
     private final LockPatternUtils mLockPatternUtils;
     private final HeadsUpManagerPhone mHeadsUpManager;
@@ -121,7 +120,7 @@
     private boolean mIsCollapsingToShowActivityOverLockscreen;
 
     private StatusBarNotificationActivityStarter(Context context, CommandQueue commandQueue,
-            Lazy<AssistManager> assistManagerLazy, NotificationPanelView panel,
+            Lazy<AssistManager> assistManagerLazy, NotificationPanelViewController panel,
             NotificationPresenter presenter, NotificationEntryManager entryManager,
             HeadsUpManagerPhone headsUpManager, ActivityStarter activityStarter,
             ActivityLaunchAnimator activityLaunchAnimator, IStatusBarService statusBarService,
@@ -519,7 +518,6 @@
         private final NotificationGroupManager mGroupManager;
         private final NotificationLockscreenUserManager mLockscreenUserManager;
         private final KeyguardStateController mKeyguardStateController;
-        private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
         private final MetricsLogger mMetricsLogger;
         private final LockPatternUtils mLockPatternUtils;
         private final Handler mMainThreadHandler;
@@ -527,7 +525,8 @@
         private final Executor mUiBgExecutor;
         private final ActivityIntentHelper mActivityIntentHelper;
         private final BubbleController mBubbleController;
-        private final SuperStatusBarViewFactory mSuperStatusBarViewFactory;
+        private NotificationPanelViewController mNotificationPanelViewController;
+        private NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
         private final ShadeController mShadeController;
         private NotificationPresenter mNotificationPresenter;
         private ActivityLaunchAnimator mActivityLaunchAnimator;
@@ -558,8 +557,7 @@
                 @UiBackground Executor uiBgExecutor,
                 ActivityIntentHelper activityIntentHelper,
                 BubbleController bubbleController,
-                ShadeController shadeController,
-                SuperStatusBarViewFactory superStatusBarViewFactory) {
+                ShadeController shadeController) {
             mContext = context;
             mCommandQueue = commandQueue;
             mAssistManagerLazy = assistManagerLazy;
@@ -585,7 +583,6 @@
             mActivityIntentHelper = activityIntentHelper;
             mBubbleController = bubbleController;
             mShadeController = shadeController;
-            mSuperStatusBarViewFactory = superStatusBarViewFactory;
         }
 
         /** Sets the status bar to use as {@link StatusBar}. */
@@ -604,10 +601,19 @@
             return this;
         }
 
+        /** Set the NotificationPanelViewController */
+        public Builder setNotificationPanelViewController(
+                NotificationPanelViewController notificationPanelViewController) {
+            mNotificationPanelViewController = notificationPanelViewController;
+            return this;
+        }
+
+
+
         public StatusBarNotificationActivityStarter build() {
             return new StatusBarNotificationActivityStarter(mContext,
                     mCommandQueue, mAssistManagerLazy,
-                    mSuperStatusBarViewFactory.getNotificationPanelView(),
+                    mNotificationPanelViewController,
                     mNotificationPresenter,
                     mEntryManager,
                     mHeadsUpManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index ab9d540..1336b2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -66,7 +66,7 @@
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -107,7 +107,7 @@
     private final NotificationGutsManager mGutsManager =
             Dependency.get(NotificationGutsManager.class);
 
-    private final NotificationPanelView mNotificationPanel;
+    private final NotificationPanelViewController mNotificationPanel;
     private final HeadsUpManagerPhone mHeadsUpManager;
     private final AboveShelfObserver mAboveShelfObserver;
     private final DozeScrimController mDozeScrimController;
@@ -132,9 +132,9 @@
     private int mMaxKeyguardNotifications;
 
     public StatusBarNotificationPresenter(Context context,
-            NotificationPanelView panel,
+            NotificationPanelViewController panel,
             HeadsUpManagerPhone headsUp,
-            StatusBarWindowView statusBarWindow,
+            NotificationShadeWindowView statusBarWindow,
             ViewGroup stackScroller,
             DozeScrimController dozeScrimController,
             ScrimController scrimController,
@@ -172,7 +172,7 @@
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
 
         if (MULTIUSER_DEBUG) {
-            mNotificationPanelDebugText = mNotificationPanel.findViewById(R.id.header_debug_info);
+            mNotificationPanelDebugText = mNotificationPanel.getHeaderDebugInfo();
             mNotificationPanelDebugText.setVisibility(View.VISIBLE);
         }
 
@@ -191,7 +191,7 @@
                 Dependency.get(NotificationRemoteInputManager.Callback.class),
                 mNotificationPanel.createRemoteInputDelegate());
         remoteInputManager.getController().addCallback(
-                Dependency.get(StatusBarWindowController.class));
+                Dependency.get(NotificationShadeWindowController.class));
 
         NotificationListContainer notifListContainer = (NotificationListContainer) stackScroller;
         initController.addPostInitTask(() -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index 8286d26..d2e9262 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -117,7 +117,7 @@
         if (!StatusBarIconController.ICON_BLACKLIST.equals(key)) {
             return;
         }
-        ArraySet<String> blockList = StatusBarIconController.getIconBlacklist(newValue);
+        ArraySet<String> blockList = StatusBarIconController.getIconBlacklist(mContext, newValue);
         boolean blockAirplane = blockList.contains(mSlotAirplane);
         boolean blockMobile = blockList.contains(mSlotMobile);
         boolean blockWifi = blockList.contains(mSlotWifi);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
index 3d25749..b8fb6d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
@@ -17,15 +17,13 @@
 package com.android.systemui.statusbar.phone;
 
 import android.annotation.NonNull;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.graphics.Rect;
 import android.view.View;
 import android.view.ViewTreeObserver;
 import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
 
 import com.android.systemui.Dependency;
+import com.android.systemui.R;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -38,60 +36,62 @@
         OnComputeInternalInsetsListener, ConfigurationListener {
 
     private final BubbleController mBubbleController = Dependency.get(BubbleController.class);
-    private final Context mContext;
     private final HeadsUpManagerPhone mHeadsUpManager;
     private boolean mIsStatusBarExpanded = false;
     private boolean mShouldAdjustInsets = false;
     private final StatusBar mStatusBar;
-    private int mStatusBarHeight;
-    private final View mStatusBarWindowView;
+    private final View mNotificationShadeWindowView;
+    private View mNotificationPanelView;
     private boolean mForceCollapsedUntilLayout = false;
-    private final StatusBarWindowController mStatusBarWindowController;
+    private final NotificationShadeWindowController mNotificationShadeWindowController;
 
-    public StatusBarTouchableRegionManager(@NonNull Context context,
-                                           HeadsUpManagerPhone headsUpManager,
+    public StatusBarTouchableRegionManager(HeadsUpManagerPhone headsUpManager,
                                            @NonNull StatusBar statusBar,
-                                           @NonNull View statusBarWindowView) {
-        mContext = context;
+                                           @NonNull View notificationShadeWindowView) {
         mHeadsUpManager = headsUpManager;
         mStatusBar = statusBar;
-        mStatusBarWindowView = statusBarWindowView;
-        mStatusBarWindowController = Dependency.get(StatusBarWindowController.class);
-
-        initResources();
+        mNotificationShadeWindowView = notificationShadeWindowView;
+        mNotificationShadeWindowController =
+                Dependency.get(NotificationShadeWindowController.class);
 
         mBubbleController.setBubbleStateChangeListener((hasBubbles) -> {
             updateTouchableRegion();
         });
 
-        mStatusBarWindowController.setForcePluginOpenListener((forceOpen) -> {
+        mNotificationShadeWindowController.setForcePluginOpenListener((forceOpen) -> {
             updateTouchableRegion();
         });
         Dependency.get(ConfigurationController.class).addCallback(this);
+        if (mNotificationShadeWindowView != null) {
+            mNotificationPanelView = mNotificationShadeWindowView.findViewById(
+                    R.id.notification_panel);
+        }
     }
 
     /**
      * Set the touchable portion of the status bar based on what elements are visible.
      */
     public void updateTouchableRegion() {
-        boolean hasCutoutInset = (mStatusBarWindowView != null)
-                && (mStatusBarWindowView.getRootWindowInsets() != null)
-                && (mStatusBarWindowView.getRootWindowInsets().getDisplayCutout() != null);
+        boolean hasCutoutInset = (mNotificationShadeWindowView != null)
+                && (mNotificationShadeWindowView.getRootWindowInsets() != null)
+                && (mNotificationShadeWindowView.getRootWindowInsets().getDisplayCutout() != null);
         boolean shouldObserve =
                 mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpManager.isHeadsUpGoingAway()
                         || mBubbleController.hasBubbles()
                         || mForceCollapsedUntilLayout
                         || hasCutoutInset
-                        || mStatusBarWindowController.getForcePluginOpen();
+                        || mNotificationShadeWindowController.getForcePluginOpen();
         if (shouldObserve == mShouldAdjustInsets) {
             return;
         }
 
         if (shouldObserve) {
-            mStatusBarWindowView.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
-            mStatusBarWindowView.requestLayout();
+            mNotificationShadeWindowView.getViewTreeObserver()
+                    .addOnComputeInternalInsetsListener(this);
+            mNotificationShadeWindowView.requestLayout();
         } else {
-            mStatusBarWindowView.getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+            mNotificationShadeWindowView.getViewTreeObserver()
+                    .removeOnComputeInternalInsetsListener(this);
         }
         mShouldAdjustInsets = shouldObserve;
     }
@@ -100,19 +100,20 @@
      * Calls {@code updateTouchableRegion()} after a layout pass completes.
      */
     public void updateTouchableRegionAfterLayout() {
-        mForceCollapsedUntilLayout = true;
-        mStatusBarWindowView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
-            @Override
-            public void onLayoutChange(View v, int left, int top, int right, int bottom,
-                                       int oldLeft,
-                                       int oldTop, int oldRight, int oldBottom) {
-                if (mStatusBarWindowView.getHeight() <= mStatusBarHeight) {
-                    mStatusBarWindowView.removeOnLayoutChangeListener(this);
-                    mForceCollapsedUntilLayout = false;
-                    updateTouchableRegion();
+        if (mNotificationPanelView != null) {
+            mForceCollapsedUntilLayout = true;
+            mNotificationPanelView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+                @Override
+                public void onLayoutChange(View v, int left, int top, int right, int bottom,
+                        int oldLeft, int oldTop, int oldRight, int oldBottom) {
+                    if (!mNotificationPanelView.isVisibleToUser()) {
+                        mNotificationPanelView.removeOnLayoutChangeListener(this);
+                        mForceCollapsedUntilLayout = false;
+                        updateTouchableRegion();
+                    }
                 }
-            }
-        });
+            });
+        }
     }
 
     /**
@@ -145,25 +146,4 @@
             info.touchableRegion.union(bubbleRect);
         }
     }
-
-    @Override
-    public void onConfigChanged(Configuration newConfig) {
-        initResources();
-    }
-
-    @Override
-    public void onDensityOrFontScaleChanged() {
-        initResources();
-    }
-
-    @Override
-    public void onOverlayChanged() {
-        initResources();
-    }
-
-    private void initResources() {
-        Resources resources = mContext.getResources();
-        mStatusBarHeight = resources.getDimensionPixelSize(
-                com.android.internal.R.dimen.status_bar_height);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
index ce498a3..e448d0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -11,55 +11,25 @@
  * 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.statusbar.phone;
 
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
 
-import static com.android.systemui.DejankUtils.whitelistIpcs;
-import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
-
-import android.app.IActivityManager;
 import android.content.Context;
-import android.content.pm.ActivityInfo;
 import android.content.res.Resources;
 import android.graphics.PixelFormat;
 import android.os.Binder;
-import android.os.RemoteException;
-import android.os.SystemProperties;
-import android.os.Trace;
 import android.util.Log;
-import android.view.Display;
 import android.view.Gravity;
-import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
-import android.view.WindowManager.LayoutParams;
 
-import com.android.systemui.Dumpable;
-import com.android.systemui.R;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.statusbar.RemoteInputController.Callback;
-import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SuperStatusBarViewFactory;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-
-import com.google.android.collect.Lists;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.Arrays;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -68,101 +38,36 @@
  * Encapsulates all logic for the status bar window state management.
  */
 @Singleton
-public class StatusBarWindowController implements Callback, Dumpable, ConfigurationListener {
-
+public class StatusBarWindowController {
     private static final String TAG = "StatusBarWindowController";
     private static final boolean DEBUG = false;
 
     private final Context mContext;
     private final WindowManager mWindowManager;
-    private final IActivityManager mActivityManager;
-    private final DozeParameters mDozeParameters;
-    private final LayoutParams mLpChanged;
-    private final boolean mKeyguardScreenRotation;
-    private final long mLockScreenDisplayTimeout;
-    private final Display.Mode mKeyguardDisplayMode;
-    private final KeyguardBypassController mKeyguardBypassController;
-    private ViewGroup mStatusBarView;
-    private LayoutParams mLp;
-    private boolean mHasTopUi;
-    private boolean mHasTopUiChanged;
-    private int mBarHeight = -1;
-    private float mScreenBrightnessDoze;
-    private final State mCurrentState = new State();
-    private OtherwisedCollapsedListener mListener;
-    private ForcePluginOpenListener mForcePluginOpenListener;
-    private final ArrayList<WeakReference<StatusBarWindowCallback>>
-            mCallbacks = Lists.newArrayList();
-
-    private final SysuiColorExtractor mColorExtractor;
     private final SuperStatusBarViewFactory mSuperStatusBarViewFactory;
     private final Resources mResources;
+    private int mBarHeight = -1;
+    private final State mCurrentState = new State();
+
+    private ViewGroup mStatusBarView;
+    private WindowManager.LayoutParams mLp;
+    private final WindowManager.LayoutParams mLpChanged;
 
     @Inject
     public StatusBarWindowController(Context context, WindowManager windowManager,
-            IActivityManager activityManager, DozeParameters dozeParameters,
-            StatusBarStateController statusBarStateController,
-            ConfigurationController configurationController,
-            KeyguardBypassController keyguardBypassController, SysuiColorExtractor colorExtractor,
             SuperStatusBarViewFactory superStatusBarViewFactory,
             @Main Resources resources) {
         mContext = context;
         mWindowManager = windowManager;
-        mActivityManager = activityManager;
-        mKeyguardScreenRotation = shouldEnableKeyguardScreenRotation();
-        mDozeParameters = dozeParameters;
-        mScreenBrightnessDoze = mDozeParameters.getScreenBrightnessDoze();
-        mLpChanged = new LayoutParams();
-        mKeyguardBypassController = keyguardBypassController;
-        mColorExtractor = colorExtractor;
         mSuperStatusBarViewFactory = superStatusBarViewFactory;
         mStatusBarView = mSuperStatusBarViewFactory.getStatusBarWindowView();
+        mLpChanged = new WindowManager.LayoutParams();
         mResources = resources;
 
         if (mBarHeight < 0) {
             mBarHeight = mResources.getDimensionPixelSize(
                     com.android.internal.R.dimen.status_bar_height);
         }
-
-        mLockScreenDisplayTimeout = context.getResources()
-                .getInteger(R.integer.config_lockScreenDisplayTimeout);
-        ((SysuiStatusBarStateController) statusBarStateController)
-                .addCallback(mStateListener,
-                        SysuiStatusBarStateController.RANK_STATUS_BAR_WINDOW_CONTROLLER);
-        configurationController.addCallback(this);
-
-        Display.Mode[] supportedModes = context.getDisplay().getSupportedModes();
-        Display.Mode currentMode = context.getDisplay().getMode();
-        // Running on the highest frame rate available can be expensive.
-        // Let's specify a preferred refresh rate, and allow higher FPS only when we
-        // know that we're not falsing (because we unlocked.)
-        int keyguardRefreshRate = context.getResources()
-                .getInteger(R.integer.config_keyguardRefreshRate);
-        // Find supported display mode with the same resolution and requested refresh rate.
-        mKeyguardDisplayMode = Arrays.stream(supportedModes).filter(mode ->
-                (int) mode.getRefreshRate() == keyguardRefreshRate
-                        && mode.getPhysicalWidth() == currentMode.getPhysicalWidth()
-                        && mode.getPhysicalHeight() == currentMode.getPhysicalHeight())
-                .findFirst().orElse(null);
-    }
-
-    /**
-     * Register to receive notifications about status bar window state changes.
-     */
-    public void registerCallback(StatusBarWindowCallback callback) {
-        // Prevent adding duplicate callbacks
-        for (int i = 0; i < mCallbacks.size(); i++) {
-            if (mCallbacks.get(i).get() == callback) {
-                return;
-            }
-        }
-        mCallbacks.add(new WeakReference<StatusBarWindowCallback>(callback));
-    }
-
-    private boolean shouldEnableKeyguardScreenRotation() {
-        Resources res = mContext.getResources();
-        return SystemProperties.getBoolean("lockscreen.rot_override", false)
-                || res.getBoolean(R.bool.config_enableLockScreenRotation);
     }
 
     public int getStatusBarHeight() {
@@ -192,574 +97,52 @@
         // Now that the status bar window encompasses the sliding panel and its
         // translucent backdrop, the entire thing is made TRANSLUCENT and is
         // hardware-accelerated.
-        mLp = new LayoutParams(
+        mLp = new WindowManager.LayoutParams(
                 ViewGroup.LayoutParams.MATCH_PARENT,
                 mBarHeight,
-                LayoutParams.TYPE_STATUS_BAR,
-                LayoutParams.FLAG_NOT_FOCUSABLE
-                        | LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
-                        | LayoutParams.FLAG_SPLIT_TOUCH
-                        | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
-                        | LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+                WindowManager.LayoutParams.TYPE_STATUS_BAR,
+                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+                        | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
                 PixelFormat.TRANSLUCENT);
         mLp.token = new Binder();
         mLp.gravity = Gravity.TOP;
-        mLp.setFitWindowInsetsTypes(0 /* types */);
-        mLp.softInputMode = LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+        mLp.setFitInsetsTypes(0 /* types */);
         mLp.setTitle("StatusBar");
         mLp.packageName = mContext.getPackageName();
         mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
         mWindowManager.addView(mStatusBarView, mLp);
         mLpChanged.copyFrom(mLp);
-        onThemeChanged();
     }
 
-    public ViewGroup getStatusBarView() {
-        return mStatusBarView;
+    /** Set force status bar visible. */
+    public void setForceStatusBarVisible(boolean forceStatusBarVisible) {
+        mCurrentState.mForceStatusBarVisible = forceStatusBarVisible;
+        apply(mCurrentState);
     }
 
-    public void setDozeScreenBrightness(int value) {
-        mScreenBrightnessDoze = value / 255f;
-    }
-
-    private void setKeyguardDark(boolean dark) {
-        int vis = mStatusBarView.getSystemUiVisibility();
-        if (dark) {
-            vis = vis | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
-            vis = vis | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
-        } else {
-            vis = vis & ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
-            vis = vis & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
-        }
-        mStatusBarView.setSystemUiVisibility(vis);
-    }
-
-    private void applyKeyguardFlags(State state) {
-        if (state.keyguardShowing) {
-            mLpChanged.privateFlags |= LayoutParams.PRIVATE_FLAG_KEYGUARD;
-        } else {
-            mLpChanged.privateFlags &= ~LayoutParams.PRIVATE_FLAG_KEYGUARD;
-        }
-
-        final boolean scrimsOccludingWallpaper =
-                state.scrimsVisibility == ScrimController.OPAQUE;
-        final boolean keyguardOrAod = state.keyguardShowing
-                || (state.dozing && mDozeParameters.getAlwaysOn());
-        if (keyguardOrAod && !state.backdropShowing && !scrimsOccludingWallpaper) {
-            mLpChanged.flags |= LayoutParams.FLAG_SHOW_WALLPAPER;
-        } else {
-            mLpChanged.flags &= ~LayoutParams.FLAG_SHOW_WALLPAPER;
-        }
-
-        if (state.dozing) {
-            mLpChanged.privateFlags |= LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
-        } else {
-            mLpChanged.privateFlags &= ~LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
-        }
-
-        if (mKeyguardDisplayMode != null) {
-            boolean bypassOnKeyguard = mKeyguardBypassController.getBypassEnabled()
-                    && state.statusBarState == StatusBarState.KEYGUARD && !state.keyguardFadingAway
-                    && !state.keyguardGoingAway;
-            if (state.dozing || bypassOnKeyguard) {
-                mLpChanged.preferredDisplayModeId = mKeyguardDisplayMode.getModeId();
-            } else {
-                mLpChanged.preferredDisplayModeId = 0;
-            }
-            Trace.setCounter("display_mode_id", mLpChanged.preferredDisplayModeId);
-        }
-    }
-
-    private void adjustScreenOrientation(State state) {
-        if (state.isKeyguardShowingAndNotOccluded() || state.dozing) {
-            if (mKeyguardScreenRotation) {
-                mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER;
-            } else {
-                mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
-            }
-        } else {
-            mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-        }
-    }
-
-    private void applyFocusableFlag(State state) {
-        boolean panelFocusable = state.statusBarFocusable && state.panelExpanded;
-        if (state.bouncerShowing && (state.keyguardOccluded || state.keyguardNeedsInput)
-                || ENABLE_REMOTE_INPUT && state.remoteInputActive
-                || state.bubbleExpanded) {
-            mLpChanged.flags &= ~LayoutParams.FLAG_NOT_FOCUSABLE;
-            mLpChanged.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM;
-        } else if (state.isKeyguardShowingAndNotOccluded() || panelFocusable) {
-            mLpChanged.flags &= ~LayoutParams.FLAG_NOT_FOCUSABLE;
-            mLpChanged.flags |= LayoutParams.FLAG_ALT_FOCUSABLE_IM;
-        } else {
-            mLpChanged.flags |= LayoutParams.FLAG_NOT_FOCUSABLE;
-            mLpChanged.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM;
-        }
-
-        mLpChanged.softInputMode = LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
-    }
-
-    private void applyForceShowNavigationFlag(State state) {
-        if (state.panelExpanded || state.bouncerShowing
-                || ENABLE_REMOTE_INPUT && state.remoteInputActive) {
-            mLpChanged.privateFlags |= LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
-        } else {
-            mLpChanged.privateFlags &= ~LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
-        }
-    }
-
-    private void applyHeight(State state) {
-        boolean expanded = isExpanded(state);
-        if (state.forcePluginOpen) {
-            if (mListener != null) {
-                mListener.setWouldOtherwiseCollapse(expanded);
-            }
-            expanded = true;
-        }
-        if (expanded) {
-            mLpChanged.height = ViewGroup.LayoutParams.MATCH_PARENT;
-        } else {
-            mLpChanged.height = mBarHeight;
-        }
-    }
-
-    private boolean isExpanded(State state) {
-        return !state.forceCollapsed && (state.isKeyguardShowingAndNotOccluded()
-                || state.panelVisible || state.keyguardFadingAway || state.bouncerShowing
-                || state.headsUpShowing || state.bubblesShowing
-                || state.scrimsVisibility != ScrimController.TRANSPARENT);
-    }
-
-    private void applyFitsSystemWindows(State state) {
-        boolean fitsSystemWindows = !state.isKeyguardShowingAndNotOccluded();
-        if (mStatusBarView != null && mStatusBarView.getFitsSystemWindows() != fitsSystemWindows) {
-            mStatusBarView.setFitsSystemWindows(fitsSystemWindows);
-            mStatusBarView.requestApplyInsets();
-        }
-    }
-
-    private void applyUserActivityTimeout(State state) {
-        if (state.isKeyguardShowingAndNotOccluded()
-                && state.statusBarState == StatusBarState.KEYGUARD
-                && !state.qsExpanded) {
-            mLpChanged.userActivityTimeout = state.bouncerShowing
-                    ? KeyguardViewMediator.AWAKE_INTERVAL_BOUNCER_MS : mLockScreenDisplayTimeout;
-        } else {
-            mLpChanged.userActivityTimeout = -1;
-        }
-    }
-
-    private void applyInputFeatures(State state) {
-        if (state.isKeyguardShowingAndNotOccluded()
-                && state.statusBarState == StatusBarState.KEYGUARD
-                && !state.qsExpanded && !state.forceUserActivity) {
-            mLpChanged.inputFeatures |=
-                    LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
-        } else {
-            mLpChanged.inputFeatures &=
-                    ~LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
-        }
-    }
-
-    private void applyStatusBarColorSpaceAgnosticFlag(State state) {
-        if (!isExpanded(state)) {
-            mLpChanged.privateFlags |= LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
-        } else {
-            mLpChanged.privateFlags &=
-                    ~LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
-        }
+    private void applyHeight() {
+        mLpChanged.height = mBarHeight;
     }
 
     private void apply(State state) {
-        applyKeyguardFlags(state);
         applyForceStatusBarVisibleFlag(state);
-        applyFocusableFlag(state);
-        applyForceShowNavigationFlag(state);
-        adjustScreenOrientation(state);
-        applyHeight(state);
-        applyUserActivityTimeout(state);
-        applyInputFeatures(state);
-        applyFitsSystemWindows(state);
-        applyModalFlag(state);
-        applyBrightness(state);
-        applyHasTopUi(state);
-        applyNotTouchable(state);
-        applyStatusBarColorSpaceAgnosticFlag(state);
+        applyHeight();
         if (mLp != null && mLp.copyFrom(mLpChanged) != 0) {
             mWindowManager.updateViewLayout(mStatusBarView, mLp);
         }
-        if (mHasTopUi != mHasTopUiChanged) {
-            whitelistIpcs(() -> {
-                try {
-                    mActivityManager.setHasTopUi(mHasTopUiChanged);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Failed to call setHasTopUi", e);
-                }
-                mHasTopUi = mHasTopUiChanged;
-            });
-        }
-        notifyStateChangedCallbacks();
-    }
-
-    public void notifyStateChangedCallbacks() {
-        for (int i = 0; i < mCallbacks.size(); i++) {
-            StatusBarWindowCallback cb = mCallbacks.get(i).get();
-            if (cb != null) {
-                cb.onStateChanged(mCurrentState.keyguardShowing,
-                        mCurrentState.keyguardOccluded,
-                        mCurrentState.bouncerShowing);
-            }
-        }
-    }
-
-    private void applyForceStatusBarVisibleFlag(State state) {
-        if (state.forceStatusBarVisible || state.forcePluginOpen) {
-            mLpChanged.privateFlags |= WindowManager
-                    .LayoutParams.PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT;
-        } else {
-            mLpChanged.privateFlags
-                    &= ~LayoutParams.PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT;
-        }
-    }
-
-    private void applyModalFlag(State state) {
-        if (state.headsUpShowing) {
-            mLpChanged.flags |= LayoutParams.FLAG_NOT_TOUCH_MODAL;
-        } else {
-            mLpChanged.flags &= ~LayoutParams.FLAG_NOT_TOUCH_MODAL;
-        }
-    }
-
-    private void applyBrightness(State state) {
-        if (state.forceDozeBrightness) {
-            mLpChanged.screenBrightness = mScreenBrightnessDoze;
-        } else {
-            mLpChanged.screenBrightness = LayoutParams.BRIGHTNESS_OVERRIDE_NONE;
-        }
-    }
-
-    private void applyHasTopUi(State state) {
-        mHasTopUiChanged = state.forceHasTopUi || isExpanded(state);
-    }
-
-    private void applyNotTouchable(State state) {
-        if (state.notTouchable) {
-            mLpChanged.flags |= LayoutParams.FLAG_NOT_TOUCHABLE;
-        } else {
-            mLpChanged.flags &= ~LayoutParams.FLAG_NOT_TOUCHABLE;
-        }
-    }
-
-    public void setKeyguardShowing(boolean showing) {
-        mCurrentState.keyguardShowing = showing;
-        apply(mCurrentState);
-    }
-
-    public void setKeyguardOccluded(boolean occluded) {
-        mCurrentState.keyguardOccluded = occluded;
-        apply(mCurrentState);
-    }
-
-    public void setKeyguardNeedsInput(boolean needsInput) {
-        mCurrentState.keyguardNeedsInput = needsInput;
-        apply(mCurrentState);
-    }
-
-    public void setPanelVisible(boolean visible) {
-        mCurrentState.panelVisible = visible;
-        mCurrentState.statusBarFocusable = visible;
-        apply(mCurrentState);
-    }
-
-    public void setStatusBarFocusable(boolean focusable) {
-        mCurrentState.statusBarFocusable = focusable;
-        apply(mCurrentState);
-    }
-
-    public void setBouncerShowing(boolean showing) {
-        mCurrentState.bouncerShowing = showing;
-        apply(mCurrentState);
-    }
-
-    public void setBackdropShowing(boolean showing) {
-        mCurrentState.backdropShowing = showing;
-        apply(mCurrentState);
-    }
-
-    public void setKeyguardFadingAway(boolean keyguardFadingAway) {
-        mCurrentState.keyguardFadingAway = keyguardFadingAway;
-        apply(mCurrentState);
-    }
-
-    public void setQsExpanded(boolean expanded) {
-        mCurrentState.qsExpanded = expanded;
-        apply(mCurrentState);
-    }
-
-    public void setForceUserActivity(boolean forceUserActivity) {
-        mCurrentState.forceUserActivity = forceUserActivity;
-        apply(mCurrentState);
-    }
-
-    public void setScrimsVisibility(int scrimsVisibility) {
-        mCurrentState.scrimsVisibility = scrimsVisibility;
-        apply(mCurrentState);
-    }
-
-    public void setHeadsUpShowing(boolean showing) {
-        mCurrentState.headsUpShowing = showing;
-        apply(mCurrentState);
-    }
-
-    public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
-        mCurrentState.wallpaperSupportsAmbientMode = supportsAmbientMode;
-        apply(mCurrentState);
-    }
-
-    /**
-     * @param state The {@link StatusBarStateController} of the status bar.
-     */
-    private void setStatusBarState(int state) {
-        mCurrentState.statusBarState = state;
-        apply(mCurrentState);
-    }
-
-    public void setForceStatusBarVisible(boolean forceStatusBarVisible) {
-        mCurrentState.forceStatusBarVisible = forceStatusBarVisible;
-        apply(mCurrentState);
-    }
-
-    /**
-     * Force the window to be collapsed, even if it should theoretically be expanded.
-     * Used for when a heads-up comes in but we still need to wait for the touchable regions to
-     * be computed.
-     */
-    public void setForceWindowCollapsed(boolean force) {
-        mCurrentState.forceCollapsed = force;
-        apply(mCurrentState);
-    }
-
-    public void setPanelExpanded(boolean isExpanded) {
-        mCurrentState.panelExpanded = isExpanded;
-        apply(mCurrentState);
-    }
-
-    @Override
-    public void onRemoteInputActive(boolean remoteInputActive) {
-        mCurrentState.remoteInputActive = remoteInputActive;
-        apply(mCurrentState);
-    }
-
-    /**
-     * Set whether the screen brightness is forced to the value we use for doze mode by the status
-     * bar window.
-     */
-    public void setForceDozeBrightness(boolean forceDozeBrightness) {
-        mCurrentState.forceDozeBrightness = forceDozeBrightness;
-        apply(mCurrentState);
-    }
-
-    public void setDozing(boolean dozing) {
-        mCurrentState.dozing = dozing;
-        apply(mCurrentState);
-    }
-
-    public void setForcePluginOpen(boolean forcePluginOpen) {
-        mCurrentState.forcePluginOpen = forcePluginOpen;
-        apply(mCurrentState);
-        if (mForcePluginOpenListener != null) {
-            mForcePluginOpenListener.onChange(forcePluginOpen);
-        }
-    }
-
-    /**
-     * The forcePluginOpen state for the status bar.
-     */
-    public boolean getForcePluginOpen() {
-        return mCurrentState.forcePluginOpen;
-    }
-
-    public void setNotTouchable(boolean notTouchable) {
-        mCurrentState.notTouchable = notTouchable;
-        apply(mCurrentState);
-    }
-
-    /**
-     * Sets whether there are bubbles showing on the screen.
-     */
-    public void setBubblesShowing(boolean bubblesShowing) {
-        mCurrentState.bubblesShowing = bubblesShowing;
-        apply(mCurrentState);
-    }
-
-    /**
-     * The bubbles showing state for the status bar.
-     */
-    public boolean getBubblesShowing() {
-        return mCurrentState.bubblesShowing;
-    }
-
-    /**
-     * Sets if there is a bubble being expanded on the screen.
-     */
-    public void setBubbleExpanded(boolean bubbleExpanded) {
-        mCurrentState.bubbleExpanded = bubbleExpanded;
-        apply(mCurrentState);
-    }
-
-    /**
-     * Whether the bubble is shown in expanded state for the status bar.
-     */
-    public boolean getBubbleExpanded() {
-        return mCurrentState.bubbleExpanded;
-    }
-
-    /**
-     * Whether the status bar panel is expanded or not.
-     */
-    public boolean getPanelExpanded() {
-        return mCurrentState.panelExpanded;
-    }
-
-    public void setStateListener(OtherwisedCollapsedListener listener) {
-        mListener = listener;
-    }
-
-    public void setForcePluginOpenListener(ForcePluginOpenListener listener) {
-        mForcePluginOpenListener = listener;
-    }
-
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("StatusBarWindowController:");
-        pw.println("  mKeyguardDisplayMode=" + mKeyguardDisplayMode);
-        pw.println(mCurrentState);
-    }
-
-    public boolean isShowingWallpaper() {
-        return !mCurrentState.backdropShowing;
-    }
-
-    @Override
-    public void onThemeChanged() {
-        if (mStatusBarView == null) {
-            return;
-        }
-
-        final boolean useDarkText = mColorExtractor.getNeutralColors().supportsDarkText();
-        // Make sure we have the correct navbar/statusbar colors.
-        setKeyguardDark(useDarkText);
-    }
-
-    /**
-     * When keyguard will be dismissed but didn't start animation yet.
-     */
-    public void setKeyguardGoingAway(boolean goingAway) {
-        mCurrentState.keyguardGoingAway = goingAway;
-        apply(mCurrentState);
-    }
-
-    public boolean getForceHasTopUi() {
-        return mCurrentState.forceHasTopUi;
-    }
-
-    public void setForceHasTopUi(boolean forceHasTopUi) {
-        mCurrentState.forceHasTopUi = forceHasTopUi;
-        apply(mCurrentState);
     }
 
     private static class State {
-        boolean keyguardShowing;
-        boolean keyguardOccluded;
-        boolean keyguardNeedsInput;
-        boolean panelVisible;
-        boolean panelExpanded;
-        boolean statusBarFocusable;
-        boolean bouncerShowing;
-        boolean keyguardFadingAway;
-        boolean keyguardGoingAway;
-        boolean qsExpanded;
-        boolean headsUpShowing;
-        boolean forceStatusBarVisible;
-        boolean forceCollapsed;
-        boolean forceDozeBrightness;
-        boolean forceUserActivity;
-        boolean backdropShowing;
-        boolean wallpaperSupportsAmbientMode;
-        boolean notTouchable;
-        boolean bubblesShowing;
-        boolean bubbleExpanded;
-        boolean forceHasTopUi;
-
-        /**
-         * The {@link StatusBar} state from the status bar.
-         */
-        int statusBarState;
-
-        boolean remoteInputActive;
-        boolean forcePluginOpen;
-        boolean dozing;
-        int scrimsVisibility;
-
-        private boolean isKeyguardShowingAndNotOccluded() {
-            return keyguardShowing && !keyguardOccluded;
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder result = new StringBuilder();
-            String newLine = "\n";
-            result.append("Window State {");
-            result.append(newLine);
-
-            Field[] fields = this.getClass().getDeclaredFields();
-
-            // Print field names paired with their values
-            for (Field field : fields) {
-                result.append("  ");
-                try {
-                    result.append(field.getName());
-                    result.append(": ");
-                    //requires access to private field:
-                    result.append(field.get(this));
-                } catch (IllegalAccessException ex) {
-                }
-                result.append(newLine);
-            }
-            result.append("}");
-
-            return result.toString();
-        }
+        boolean mForceStatusBarVisible;
     }
 
-    private final StateListener mStateListener = new StateListener() {
-        @Override
-        public void onStateChanged(int newState) {
-            setStatusBarState(newState);
+    private void applyForceStatusBarVisibleFlag(State state) {
+        if (state.mForceStatusBarVisible) {
+            mLpChanged.privateFlags |= PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
+        } else {
+            mLpChanged.privateFlags &= ~PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
         }
-
-        @Override
-        public void onDozingChanged(boolean isDozing) {
-            setDozing(isDozing);
-        }
-    };
-
-    /**
-     * Custom listener to pipe data back to plugins about whether or not the status bar would be
-     * collapsed if not for the plugin.
-     * TODO: Find cleaner way to do this.
-     */
-    public interface OtherwisedCollapsedListener {
-        void setWouldOtherwiseCollapse(boolean otherwiseCollapse);
-    }
-
-    /**
-     * Listener to indicate forcePluginOpen has changed
-     */
-    public interface ForcePluginOpenListener {
-        /**
-         * Called when mState.forcePluginOpen is changed
-         */
-        void onChange(boolean forceOpen);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 6b51391..da5df6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * 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.
@@ -16,595 +16,19 @@
 
 package com.android.systemui.statusbar.phone;
 
-import android.annotation.ColorInt;
-import android.annotation.DrawableRes;
-import android.annotation.LayoutRes;
 import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Insets;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Bundle;
 import android.util.AttributeSet;
-import android.view.ActionMode;
-import android.view.DisplayCutout;
-import android.view.InputQueue;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.SurfaceHolder;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.view.Window;
-import android.view.WindowInsets;
-import android.view.WindowInsetsController;
 import android.widget.FrameLayout;
 
-import com.android.internal.view.FloatingActionMode;
-import com.android.internal.widget.FloatingToolbar;
-import com.android.systemui.R;
-
 /**
- * Combined status bar and notification panel view. Also holding backdrop and scrims.
+ * Status bar view.
  */
 public class StatusBarWindowView extends FrameLayout {
-    public static final String TAG = "StatusBarWindowView";
+
+    public static final String TAG = "PhoneStatusBarWindowView";
     public static final boolean DEBUG = StatusBar.DEBUG;
 
-    private int mRightInset = 0;
-    private int mLeftInset = 0;
-
-    // Implements the floating action mode for TextView's Cut/Copy/Past menu. Normally provided by
-    // DecorView, but since this is a special window we have to roll our own.
-    private View mFloatingActionModeOriginatingView;
-    private ActionMode mFloatingActionMode;
-    private FloatingToolbar mFloatingToolbar;
-    private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener;
-
-    private InteractionEventHandler mInteractionEventHandler;
-
     public StatusBarWindowView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        setMotionEventSplittingEnabled(false);
     }
-
-    @Override
-    public WindowInsets onApplyWindowInsets(WindowInsets windowInsets) {
-        final Insets insets = windowInsets.getMaxInsets(WindowInsets.Type.systemBars());
-        if (getFitsSystemWindows()) {
-            boolean paddingChanged = insets.top != getPaddingTop()
-                    || insets.bottom != getPaddingBottom();
-
-            int rightCutout = 0;
-            int leftCutout = 0;
-            DisplayCutout displayCutout = getRootWindowInsets().getDisplayCutout();
-            if (displayCutout != null) {
-                leftCutout = displayCutout.getSafeInsetLeft();
-                rightCutout = displayCutout.getSafeInsetRight();
-            }
-
-            int targetLeft = Math.max(insets.left, leftCutout);
-            int targetRight = Math.max(insets.right, rightCutout);
-
-            // Super-special right inset handling, because scrims and backdrop need to ignore it.
-            if (targetRight != mRightInset || targetLeft != mLeftInset) {
-                mRightInset = targetRight;
-                mLeftInset = targetLeft;
-                applyMargins();
-            }
-            // Drop top inset, and pass through bottom inset.
-            if (paddingChanged) {
-                setPadding(0, 0, 0, 0);
-            }
-        } else {
-            if (mRightInset != 0 || mLeftInset != 0) {
-                mRightInset = 0;
-                mLeftInset = 0;
-                applyMargins();
-            }
-            boolean changed = getPaddingLeft() != 0
-                    || getPaddingRight() != 0
-                    || getPaddingTop() != 0
-                    || getPaddingBottom() != 0;
-            if (changed) {
-                setPadding(0, 0, 0, 0);
-            }
-        }
-        return windowInsets;
-    }
-
-    private void applyMargins() {
-        final int N = getChildCount();
-        for (int i = 0; i < N; i++) {
-            View child = getChildAt(i);
-            if (child.getLayoutParams() instanceof LayoutParams) {
-                LayoutParams lp = (LayoutParams) child.getLayoutParams();
-                if (!lp.ignoreRightInset
-                        && (lp.rightMargin != mRightInset || lp.leftMargin != mLeftInset)) {
-                    lp.rightMargin = mRightInset;
-                    lp.leftMargin = mLeftInset;
-                    child.requestLayout();
-                }
-            }
-        }
-    }
-
-    @Override
-    public FrameLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
-        return new LayoutParams(getContext(), attrs);
-    }
-
-    @Override
-    protected FrameLayout.LayoutParams generateDefaultLayoutParams() {
-        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
-    }
-
-    @Override
-    protected void onAttachedToWindow () {
-        super.onAttachedToWindow();
-        setWillNotDraw(!DEBUG);
-    }
-
-    @Override
-    public boolean dispatchKeyEvent(KeyEvent event) {
-        if (mInteractionEventHandler.interceptMediaKey(event)) {
-            return true;
-        }
-
-        if (super.dispatchKeyEvent(event)) {
-            return true;
-        }
-
-        return mInteractionEventHandler.dispatchKeyEvent(event);
-    }
-
-    protected void setInteractionEventHandler(InteractionEventHandler listener) {
-        mInteractionEventHandler = listener;
-    }
-
-    @Override
-    public boolean dispatchTouchEvent(MotionEvent ev) {
-        Boolean result = mInteractionEventHandler.handleDispatchTouchEvent(ev);
-
-        return result != null ? result : super.dispatchTouchEvent(ev);
-    }
-
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        boolean intercept = mInteractionEventHandler.shouldInterceptTouchEvent(ev);
-        if (!intercept) {
-            intercept = super.onInterceptTouchEvent(ev);
-        }
-        if (intercept) {
-            mInteractionEventHandler.didIntercept(ev);
-        }
-
-        return intercept;
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        boolean handled = mInteractionEventHandler.handleTouchEvent(ev);
-
-        if (!handled) {
-            handled = super.onTouchEvent(ev);
-        }
-
-        if (!handled) {
-            mInteractionEventHandler.didNotHandleTouchEvent(ev);
-        }
-
-        return handled;
-    }
-
-    @Override
-    public void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-        if (DEBUG) {
-            Paint pt = new Paint();
-            pt.setColor(0x80FFFF00);
-            pt.setStrokeWidth(12.0f);
-            pt.setStyle(Paint.Style.STROKE);
-            canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), pt);
-        }
-    }
-
-    public class LayoutParams extends FrameLayout.LayoutParams {
-
-        public boolean ignoreRightInset;
-
-        public LayoutParams(int width, int height) {
-            super(width, height);
-        }
-
-        public LayoutParams(Context c, AttributeSet attrs) {
-            super(c, attrs);
-
-            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.StatusBarWindowView_Layout);
-            ignoreRightInset = a.getBoolean(
-                    R.styleable.StatusBarWindowView_Layout_ignoreRightInset, false);
-            a.recycle();
-        }
-    }
-
-    @Override
-    public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback,
-            int type) {
-        if (type == ActionMode.TYPE_FLOATING) {
-            return startActionMode(originalView, callback, type);
-        }
-        return super.startActionModeForChild(originalView, callback, type);
-    }
-
-    private ActionMode createFloatingActionMode(
-            View originatingView, ActionMode.Callback2 callback) {
-        if (mFloatingActionMode != null) {
-            mFloatingActionMode.finish();
-        }
-        cleanupFloatingActionModeViews();
-        mFloatingToolbar = new FloatingToolbar(mFakeWindow);
-        final FloatingActionMode mode =
-                new FloatingActionMode(mContext, callback, originatingView, mFloatingToolbar);
-        mFloatingActionModeOriginatingView = originatingView;
-        mFloatingToolbarPreDrawListener =
-                new ViewTreeObserver.OnPreDrawListener() {
-                    @Override
-                    public boolean onPreDraw() {
-                        mode.updateViewLocationInWindow();
-                        return true;
-                    }
-                };
-        return mode;
-    }
-
-    private void setHandledFloatingActionMode(ActionMode mode) {
-        mFloatingActionMode = mode;
-        mFloatingActionMode.invalidate();  // Will show the floating toolbar if necessary.
-        mFloatingActionModeOriginatingView.getViewTreeObserver()
-                .addOnPreDrawListener(mFloatingToolbarPreDrawListener);
-    }
-
-    private void cleanupFloatingActionModeViews() {
-        if (mFloatingToolbar != null) {
-            mFloatingToolbar.dismiss();
-            mFloatingToolbar = null;
-        }
-        if (mFloatingActionModeOriginatingView != null) {
-            if (mFloatingToolbarPreDrawListener != null) {
-                mFloatingActionModeOriginatingView.getViewTreeObserver()
-                        .removeOnPreDrawListener(mFloatingToolbarPreDrawListener);
-                mFloatingToolbarPreDrawListener = null;
-            }
-            mFloatingActionModeOriginatingView = null;
-        }
-    }
-
-    private ActionMode startActionMode(
-            View originatingView, ActionMode.Callback callback, int type) {
-        ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback);
-        ActionMode mode = createFloatingActionMode(originatingView, wrappedCallback);
-        if (mode != null && wrappedCallback.onCreateActionMode(mode, mode.getMenu())) {
-            setHandledFloatingActionMode(mode);
-        } else {
-            mode = null;
-        }
-        return mode;
-    }
-
-    private class ActionModeCallback2Wrapper extends ActionMode.Callback2 {
-        private final ActionMode.Callback mWrapped;
-
-        public ActionModeCallback2Wrapper(ActionMode.Callback wrapped) {
-            mWrapped = wrapped;
-        }
-
-        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
-            return mWrapped.onCreateActionMode(mode, menu);
-        }
-
-        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
-            requestFitSystemWindows();
-            return mWrapped.onPrepareActionMode(mode, menu);
-        }
-
-        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
-            return mWrapped.onActionItemClicked(mode, item);
-        }
-
-        public void onDestroyActionMode(ActionMode mode) {
-            mWrapped.onDestroyActionMode(mode);
-            if (mode == mFloatingActionMode) {
-                cleanupFloatingActionModeViews();
-                mFloatingActionMode = null;
-            }
-            requestFitSystemWindows();
-        }
-
-        @Override
-        public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
-            if (mWrapped instanceof ActionMode.Callback2) {
-                ((ActionMode.Callback2) mWrapped).onGetContentRect(mode, view, outRect);
-            } else {
-                super.onGetContentRect(mode, view, outRect);
-            }
-        }
-    }
-
-    interface InteractionEventHandler {
-        /**
-         * Returns a result for {@link ViewGroup#dispatchTouchEvent(MotionEvent)} or null to defer
-         * to the super method.
-         */
-        Boolean handleDispatchTouchEvent(MotionEvent ev);
-
-        /**
-         * Returns if the view should intercept the touch event.
-         *
-         * The touch event may still be interecepted if
-         * {@link ViewGroup#onInterceptTouchEvent(MotionEvent)} decides to do so.
-         */
-        boolean shouldInterceptTouchEvent(MotionEvent ev);
-
-        /**
-         * Called when the view decides to intercept the touch event.
-         */
-        void didIntercept(MotionEvent ev);
-
-        boolean handleTouchEvent(MotionEvent ev);
-
-        void didNotHandleTouchEvent(MotionEvent ev);
-
-        boolean interceptMediaKey(KeyEvent event);
-
-        boolean dispatchKeyEvent(KeyEvent event);
-    }
-
-    /**
-     * Minimal window to satisfy FloatingToolbar.
-     */
-    private Window mFakeWindow = new Window(mContext) {
-        @Override
-        public void takeSurface(SurfaceHolder.Callback2 callback) {
-        }
-
-        @Override
-        public void takeInputQueue(InputQueue.Callback callback) {
-        }
-
-        @Override
-        public boolean isFloating() {
-            return false;
-        }
-
-        @Override
-        public void alwaysReadCloseOnTouchAttr() {
-        }
-
-        @Override
-        public void setContentView(@LayoutRes int layoutResID) {
-        }
-
-        @Override
-        public void setContentView(View view) {
-        }
-
-        @Override
-        public void setContentView(View view, ViewGroup.LayoutParams params) {
-        }
-
-        @Override
-        public void addContentView(View view, ViewGroup.LayoutParams params) {
-        }
-
-        @Override
-        public void clearContentView() {
-        }
-
-        @Override
-        public View getCurrentFocus() {
-            return null;
-        }
-
-        @Override
-        public LayoutInflater getLayoutInflater() {
-            return null;
-        }
-
-        @Override
-        public void setTitle(CharSequence title) {
-        }
-
-        @Override
-        public void setTitleColor(@ColorInt int textColor) {
-        }
-
-        @Override
-        public void openPanel(int featureId, KeyEvent event) {
-        }
-
-        @Override
-        public void closePanel(int featureId) {
-        }
-
-        @Override
-        public void togglePanel(int featureId, KeyEvent event) {
-        }
-
-        @Override
-        public void invalidatePanelMenu(int featureId) {
-        }
-
-        @Override
-        public boolean performPanelShortcut(int featureId, int keyCode, KeyEvent event, int flags) {
-            return false;
-        }
-
-        @Override
-        public boolean performPanelIdentifierAction(int featureId, int id, int flags) {
-            return false;
-        }
-
-        @Override
-        public void closeAllPanels() {
-        }
-
-        @Override
-        public boolean performContextMenuIdentifierAction(int id, int flags) {
-            return false;
-        }
-
-        @Override
-        public void onConfigurationChanged(Configuration newConfig) {
-        }
-
-        @Override
-        public void setBackgroundDrawable(Drawable drawable) {
-        }
-
-        @Override
-        public void setFeatureDrawableResource(int featureId, @DrawableRes int resId) {
-        }
-
-        @Override
-        public void setFeatureDrawableUri(int featureId, Uri uri) {
-        }
-
-        @Override
-        public void setFeatureDrawable(int featureId, Drawable drawable) {
-        }
-
-        @Override
-        public void setFeatureDrawableAlpha(int featureId, int alpha) {
-        }
-
-        @Override
-        public void setFeatureInt(int featureId, int value) {
-        }
-
-        @Override
-        public void takeKeyEvents(boolean get) {
-        }
-
-        @Override
-        public boolean superDispatchKeyEvent(KeyEvent event) {
-            return false;
-        }
-
-        @Override
-        public boolean superDispatchKeyShortcutEvent(KeyEvent event) {
-            return false;
-        }
-
-        @Override
-        public boolean superDispatchTouchEvent(MotionEvent event) {
-            return false;
-        }
-
-        @Override
-        public boolean superDispatchTrackballEvent(MotionEvent event) {
-            return false;
-        }
-
-        @Override
-        public boolean superDispatchGenericMotionEvent(MotionEvent event) {
-            return false;
-        }
-
-        @Override
-        public View getDecorView() {
-            return StatusBarWindowView.this;
-        }
-
-        @Override
-        public View peekDecorView() {
-            return null;
-        }
-
-        @Override
-        public Bundle saveHierarchyState() {
-            return null;
-        }
-
-        @Override
-        public void restoreHierarchyState(Bundle savedInstanceState) {
-        }
-
-        @Override
-        protected void onActive() {
-        }
-
-        @Override
-        public void setChildDrawable(int featureId, Drawable drawable) {
-        }
-
-        @Override
-        public void setChildInt(int featureId, int value) {
-        }
-
-        @Override
-        public boolean isShortcutKey(int keyCode, KeyEvent event) {
-            return false;
-        }
-
-        @Override
-        public void setVolumeControlStream(int streamType) {
-        }
-
-        @Override
-        public int getVolumeControlStream() {
-            return 0;
-        }
-
-        @Override
-        public int getStatusBarColor() {
-            return 0;
-        }
-
-        @Override
-        public void setStatusBarColor(@ColorInt int color) {
-        }
-
-        @Override
-        public int getNavigationBarColor() {
-            return 0;
-        }
-
-        @Override
-        public void setNavigationBarColor(@ColorInt int color) {
-        }
-
-        @Override
-        public void setDecorCaptionShade(int decorCaptionShade) {
-        }
-
-        @Override
-        public void setResizingCaptionDrawable(Drawable drawable) {
-        }
-
-        @Override
-        public void onMultiWindowModeChanged() {
-        }
-
-        @Override
-        public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
-        }
-
-        @Override
-        public void reportActivityRelaunched() {
-        }
-
-        @Override
-        public WindowInsetsController getInsetsController() {
-            return null;
-        }
-    };
-
 }
-
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java
deleted file mode 100644
index eb86bcc..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java
+++ /dev/null
@@ -1,449 +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.statusbar.phone;
-
-import android.app.StatusBarManager;
-import android.hardware.display.AmbientDisplayConfiguration;
-import android.media.AudioManager;
-import android.media.session.MediaSessionLegacyHelper;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.view.GestureDetector;
-import android.view.InputDevice;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewStub;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.ExpandHelper;
-import com.android.systemui.R;
-import com.android.systemui.dock.DockManager;
-import com.android.systemui.doze.DozeLog;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.DragDownHelper;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.PulseExpansionHandler;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.util.InjectionInflationController;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-import javax.inject.Inject;
-
-/**
- * Controller for {@link StatusBarWindowView}.
- */
-public class StatusBarWindowViewController {
-    private final InjectionInflationController mInjectionInflationController;
-    private final NotificationWakeUpCoordinator mCoordinator;
-    private final PulseExpansionHandler mPulseExpansionHandler;
-    private final DynamicPrivacyController mDynamicPrivacyController;
-    private final KeyguardBypassController mBypassController;
-    private final PluginManager mPluginManager;
-    private final FalsingManager mFalsingManager;
-    private final TunerService mTunerService;
-    private final NotificationLockscreenUserManager mNotificationLockscreenUserManager;
-    private final NotificationEntryManager mNotificationEntryManager;
-    private final KeyguardStateController mKeyguardStateController;
-    private final SysuiStatusBarStateController mStatusBarStateController;
-    private final DozeLog mDozeLog;
-    private final DozeParameters mDozeParameters;
-    private final CommandQueue mCommandQueue;
-    private final StatusBarWindowView mView;
-    private final ShadeController mShadeController;
-
-    private GestureDetector mGestureDetector;
-    private View mBrightnessMirror;
-    private boolean mTouchActive;
-    private boolean mTouchCancelled;
-    private boolean mExpandAnimationPending;
-    private boolean mExpandAnimationRunning;
-    private NotificationStackScrollLayout mStackScrollLayout;
-    private PhoneStatusBarView mStatusBarView;
-    private StatusBar mService;
-    private DragDownHelper mDragDownHelper;
-    private boolean mDoubleTapEnabled;
-    private boolean mSingleTapEnabled;
-    private boolean mExpandingBelowNotch;
-    private final DockManager mDockManager;
-
-    @Inject
-    public StatusBarWindowViewController(
-            InjectionInflationController injectionInflationController,
-            NotificationWakeUpCoordinator coordinator,
-            PulseExpansionHandler pulseExpansionHandler,
-            DynamicPrivacyController dynamicPrivacyController,
-            KeyguardBypassController bypassController,
-            FalsingManager falsingManager,
-            PluginManager pluginManager,
-            TunerService tunerService,
-            NotificationLockscreenUserManager notificationLockscreenUserManager,
-            NotificationEntryManager notificationEntryManager,
-            KeyguardStateController keyguardStateController,
-            SysuiStatusBarStateController statusBarStateController,
-            DozeLog dozeLog,
-            DozeParameters dozeParameters,
-            CommandQueue commandQueue,
-            ShadeController shadeController,
-            DockManager dockManager,
-            StatusBarWindowView statusBarWindowView) {
-        mInjectionInflationController = injectionInflationController;
-        mCoordinator = coordinator;
-        mPulseExpansionHandler = pulseExpansionHandler;
-        mDynamicPrivacyController = dynamicPrivacyController;
-        mBypassController = bypassController;
-        mFalsingManager = falsingManager;
-        mPluginManager = pluginManager;
-        mTunerService = tunerService;
-        mNotificationLockscreenUserManager = notificationLockscreenUserManager;
-        mNotificationEntryManager = notificationEntryManager;
-        mKeyguardStateController = keyguardStateController;
-        mStatusBarStateController = statusBarStateController;
-        mDozeLog = dozeLog;
-        mDozeParameters = dozeParameters;
-        mCommandQueue = commandQueue;
-        mView = statusBarWindowView;
-        mShadeController = shadeController;
-        mDockManager = dockManager;
-
-        // This view is not part of the newly inflated expanded status bar.
-        mBrightnessMirror = mView.findViewById(R.id.brightness_mirror);
-    }
-
-    /** Inflates the {@link R.layout#status_bar_expanded} layout and sets it up. */
-    public void setupExpandedStatusBar() {
-        // TODO: create controller for NotificationPanelView
-        NotificationPanelView notificationPanelView = new NotificationPanelView(
-                mView.getContext(),
-                null,
-                mInjectionInflationController,
-                mCoordinator,
-                mPulseExpansionHandler,
-                mDynamicPrivacyController,
-                mBypassController,
-                mFalsingManager,
-                mPluginManager,
-                mShadeController,
-                mNotificationLockscreenUserManager,
-                mNotificationEntryManager,
-                mKeyguardStateController,
-                mStatusBarStateController,
-                mDozeLog,
-                mDozeParameters,
-                mCommandQueue);
-        ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
-        notificationPanelView.setVisibility(View.INVISIBLE);
-        notificationPanelView.setId(R.id.notification_panel);
-        LayoutInflater li = mInjectionInflationController.injectable(
-                LayoutInflater.from(mView.getContext()));
-
-        li.inflate(R.layout.status_bar_expanded, notificationPanelView);
-        notificationPanelView.onChildrenAttached();
-
-        ViewStub statusBarExpanded = mView.findViewById(R.id.status_bar_expanded);
-        mView.addView(notificationPanelView, mView.indexOfChild(statusBarExpanded), lp);
-        mView.removeView(statusBarExpanded);
-
-        mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller);
-
-        TunerService.Tunable tunable = (key, newValue) -> {
-            AmbientDisplayConfiguration configuration =
-                    new AmbientDisplayConfiguration(mView.getContext());
-            switch (key) {
-                case Settings.Secure.DOZE_DOUBLE_TAP_GESTURE:
-                    mDoubleTapEnabled = configuration.doubleTapGestureEnabled(
-                            UserHandle.USER_CURRENT);
-                    break;
-                case Settings.Secure.DOZE_TAP_SCREEN_GESTURE:
-                    mSingleTapEnabled = configuration.tapGestureEnabled(UserHandle.USER_CURRENT);
-            }
-        };
-        mTunerService.addTunable(tunable,
-                Settings.Secure.DOZE_DOUBLE_TAP_GESTURE,
-                Settings.Secure.DOZE_TAP_SCREEN_GESTURE);
-
-        GestureDetector.SimpleOnGestureListener gestureListener =
-                new GestureDetector.SimpleOnGestureListener() {
-                    @Override
-                    public boolean onSingleTapConfirmed(MotionEvent e) {
-                        if (mSingleTapEnabled && !mDockManager.isDocked()) {
-                            mService.wakeUpIfDozing(
-                                    SystemClock.uptimeMillis(), mView, "SINGLE_TAP");
-                            return true;
-                        }
-                        return false;
-                    }
-
-                    @Override
-                    public boolean onDoubleTap(MotionEvent e) {
-                        if (mDoubleTapEnabled || mSingleTapEnabled) {
-                            mService.wakeUpIfDozing(
-                                    SystemClock.uptimeMillis(), mView, "DOUBLE_TAP");
-                            return true;
-                        }
-                        return false;
-                    }
-                };
-        mGestureDetector = new GestureDetector(mView.getContext(), gestureListener);
-
-        mView.setInteractionEventHandler(new StatusBarWindowView.InteractionEventHandler() {
-            @Override
-            public Boolean handleDispatchTouchEvent(MotionEvent ev) {
-                boolean isDown = ev.getActionMasked() == MotionEvent.ACTION_DOWN;
-                boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP;
-                boolean isCancel = ev.getActionMasked() == MotionEvent.ACTION_CANCEL;
-
-                boolean expandingBelowNotch = mExpandingBelowNotch;
-                if (isUp || isCancel) {
-                    mExpandingBelowNotch = false;
-                }
-
-                // Reset manual touch dispatch state here but make sure the UP/CANCEL event still
-                // gets
-                // delivered.
-
-                if (!isCancel && mService.shouldIgnoreTouch()) {
-                    return false;
-                }
-                if (isDown && notificationPanelView.isFullyCollapsed()) {
-                    notificationPanelView.startExpandLatencyTracking();
-                }
-                if (isDown) {
-                    setTouchActive(true);
-                    mTouchCancelled = false;
-                } else if (ev.getActionMasked() == MotionEvent.ACTION_UP
-                        || ev.getActionMasked() == MotionEvent.ACTION_CANCEL) {
-                    setTouchActive(false);
-                }
-                if (mTouchCancelled || mExpandAnimationRunning || mExpandAnimationPending) {
-                    return false;
-                }
-                mFalsingManager.onTouchEvent(ev, mView.getWidth(), mView.getHeight());
-                mGestureDetector.onTouchEvent(ev);
-                if (mBrightnessMirror != null
-                        && mBrightnessMirror.getVisibility() == View.VISIBLE) {
-                    // Disallow new pointers while the brightness mirror is visible. This is so that
-                    // you can't touch anything other than the brightness slider while the mirror is
-                    // showing and the rest of the panel is transparent.
-                    if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
-                        return false;
-                    }
-                }
-                if (isDown) {
-                    mStackScrollLayout.closeControlsIfOutsideTouch(ev);
-                }
-                if (mStatusBarStateController.isDozing()) {
-                    mService.mDozeScrimController.extendPulse();
-                }
-                // In case we start outside of the view bounds (below the status bar), we need to
-                // dispatch
-                // the touch manually as the view system can't accommodate for touches outside of
-                // the
-                // regular view bounds.
-                if (isDown && ev.getY() >= mView.getBottom()) {
-                    mExpandingBelowNotch = true;
-                    expandingBelowNotch = true;
-                }
-                if (expandingBelowNotch) {
-                    return mStatusBarView.dispatchTouchEvent(ev);
-                }
-
-                return null;
-            }
-
-            @Override
-            public boolean shouldInterceptTouchEvent(MotionEvent ev) {
-                if (mStatusBarStateController.isDozing() && !mService.isPulsing()
-                        && !mDockManager.isDocked()) {
-                    // Capture all touch events in always-on.
-                    return true;
-                }
-                boolean intercept = false;
-                if (notificationPanelView.isFullyExpanded()
-                        && mDragDownHelper.isDragDownEnabled()
-                        && !mService.isBouncerShowing()
-                        && !mStatusBarStateController.isDozing()) {
-                    intercept = mDragDownHelper.onInterceptTouchEvent(ev);
-                }
-
-                return intercept;
-
-            }
-
-            @Override
-            public void didIntercept(MotionEvent ev) {
-                MotionEvent cancellation = MotionEvent.obtain(ev);
-                cancellation.setAction(MotionEvent.ACTION_CANCEL);
-                mStackScrollLayout.onInterceptTouchEvent(cancellation);
-                notificationPanelView.onInterceptTouchEvent(cancellation);
-                cancellation.recycle();
-            }
-
-            @Override
-            public boolean handleTouchEvent(MotionEvent ev) {
-                boolean handled = false;
-                if (mStatusBarStateController.isDozing()) {
-                    handled = !mService.isPulsing();
-                }
-                if ((mDragDownHelper.isDragDownEnabled() && !handled)
-                        || mDragDownHelper.isDraggingDown()) {
-                    // we still want to finish our drag down gesture when locking the screen
-                    handled = mDragDownHelper.onTouchEvent(ev);
-                }
-
-                return handled;
-            }
-
-            @Override
-            public void didNotHandleTouchEvent(MotionEvent ev) {
-                final int action = ev.getActionMasked();
-                if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
-                    mService.setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
-                }
-            }
-
-            @Override
-            public boolean interceptMediaKey(KeyEvent event) {
-                return mService.interceptMediaKey(event);
-            }
-
-            @Override
-            public boolean dispatchKeyEvent(KeyEvent event) {
-                boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
-                switch (event.getKeyCode()) {
-                    case KeyEvent.KEYCODE_BACK:
-                        if (!down) {
-                            mService.onBackPressed();
-                        }
-                        return true;
-                    case KeyEvent.KEYCODE_MENU:
-                        if (!down) {
-                            return mService.onMenuPressed();
-                        }
-                        break;
-                    case KeyEvent.KEYCODE_SPACE:
-                        if (!down) {
-                            return mService.onSpacePressed();
-                        }
-                        break;
-                    case KeyEvent.KEYCODE_VOLUME_DOWN:
-                    case KeyEvent.KEYCODE_VOLUME_UP:
-                        if (mStatusBarStateController.isDozing()) {
-                            MediaSessionLegacyHelper.getHelper(mView.getContext())
-                                    .sendVolumeKeyEvent(
-                                            event, AudioManager.USE_DEFAULT_STREAM_TYPE, true);
-                            return true;
-                        }
-                        break;
-                }
-                return false;
-            }
-        });
-
-        mView.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
-            @Override
-            public void onChildViewAdded(View parent, View child) {
-                if (child.getId() == R.id.brightness_mirror) {
-                    mBrightnessMirror = child;
-                }
-            }
-
-            @Override
-            public void onChildViewRemoved(View parent, View child) {
-            }
-        });
-
-        ExpandHelper.Callback expandHelperCallback = mStackScrollLayout.getExpandHelperCallback();
-        DragDownHelper.DragDownCallback dragDownCallback = mStackScrollLayout.getDragDownCallback();
-        setDragDownHelper(
-                new DragDownHelper(
-                        mView.getContext(), mView, expandHelperCallback,
-                        dragDownCallback, mFalsingManager));
-    }
-
-    public StatusBarWindowView getView() {
-        return mView;
-    }
-
-    public void setTouchActive(boolean touchActive) {
-        mTouchActive = touchActive;
-    }
-
-    public void cancelCurrentTouch() {
-        if (mTouchActive) {
-            final long now = SystemClock.uptimeMillis();
-            MotionEvent event = MotionEvent.obtain(now, now,
-                    MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
-            event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
-            mView.dispatchTouchEvent(event);
-            event.recycle();
-            mTouchCancelled = true;
-        }
-    }
-
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.print("  mExpandAnimationPending=");
-        pw.println(mExpandAnimationPending);
-        pw.print("  mExpandAnimationRunning=");
-        pw.println(mExpandAnimationRunning);
-        pw.print("  mTouchCancelled=");
-        pw.println(mTouchCancelled);
-        pw.print("  mTouchActive=");
-        pw.println(mTouchActive);
-    }
-
-    public void setExpandAnimationPending(boolean pending) {
-        mExpandAnimationPending = pending;
-    }
-
-    public void setExpandAnimationRunning(boolean running) {
-        mExpandAnimationRunning = running;
-    }
-
-    public void cancelExpandHelper() {
-        if (mStackScrollLayout != null) {
-            mStackScrollLayout.cancelExpandHelper();
-        }
-    }
-
-    public void setStatusBarView(PhoneStatusBarView statusBarView) {
-        mStatusBarView = statusBarView;
-    }
-
-    public void setService(StatusBar statusBar) {
-        mService = statusBar;
-    }
-
-    @VisibleForTesting
-    void setDragDownHelper(DragDownHelper dragDownHelper) {
-        mDragDownHelper = dragDownHelper;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 28b6c38..06105f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -106,8 +106,8 @@
         if (Dependency.get(KeyguardStateController.class).isShowing()) {
             final Window window = dialog.getWindow();
             window.setType(LayoutParams.TYPE_STATUS_BAR_PANEL);
-            window.setFitWindowInsetsTypes(
-                    window.getFitWindowInsetsTypes() & ~Type.statusBars());
+            window.getAttributes().setFitInsetsTypes(
+                    window.getAttributes().getFitInsetsTypes() & ~Type.statusBars());
         } else {
             dialog.getWindow().setType(LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
         }
@@ -118,8 +118,8 @@
         window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL);
         window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
                 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
-        window.setFitWindowInsetsTypes(
-                    window.getFitWindowInsetsTypes() & ~Type.statusBars());
+        window.getAttributes().setFitInsetsTypes(
+                window.getAttributes().getFitInsetsTypes() & ~Type.statusBars());
         return dialog;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
new file mode 100644
index 0000000..802da3e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
@@ -0,0 +1,76 @@
+/*
+ * 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.statusbar.phone.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.statusbar.phone.NotificationPanelViewController;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController;
+import com.android.systemui.statusbar.phone.StatusBarWindowController;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Scope;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/**
+ * Dagger subcomponent tied to the lifecycle of StatusBar views.
+ */
+@Subcomponent(modules = {StatusBarViewModule.class})
+@StatusBarComponent.StatusBarScope
+public interface StatusBarComponent {
+    /**
+     * Builder for {@link StatusBarComponent}.
+     */
+    @Subcomponent.Builder
+    interface Builder {
+        @BindsInstance Builder statusBarWindowView(
+                NotificationShadeWindowView notificationShadeWindowView);
+        StatusBarComponent build();
+    }
+
+    /**
+     * Scope annotation for singleton items within the StatusBarComponent.
+     */
+    @Documented
+    @Retention(RUNTIME)
+    @Scope
+    @interface StatusBarScope {}
+
+    /**
+     * Creates a NotificationShadeWindowViewController.
+     */
+    @StatusBarScope
+    NotificationShadeWindowViewController getNotificationShadeWindowViewController();
+
+    /**
+     * Creates a StatusBarWindowViewController.
+     */
+    @StatusBarScope
+    StatusBarWindowController getStatusBarWindowController();
+
+    /**
+     * Creates a NotificationPanelViewController.
+     */
+    @StatusBarScope
+    NotificationPanelViewController getNotificationPanelViewController();
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
new file mode 100644
index 0000000..37d8c9a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -0,0 +1,35 @@
+/*
+ * 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.statusbar.phone.dagger;
+
+import com.android.systemui.statusbar.phone.NotificationPanelView;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
+
+import dagger.Module;
+import dagger.Provides;
+
+@Module
+public abstract class StatusBarViewModule {
+    /** */
+    @Provides
+    @StatusBarComponent.StatusBarScope
+    public static NotificationPanelView getNotificationPanelView(
+            NotificationShadeWindowView notificationShadeWindowView) {
+        return notificationShadeWindowView.getNotificationPanelView();
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index 88edf8e..d62da10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -24,8 +24,8 @@
 import android.widget.FrameLayout;
 
 import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.NotificationPanelView;
-import com.android.systemui.statusbar.phone.StatusBarWindowView;
+import com.android.systemui.statusbar.phone.NotificationPanelViewController;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
 
 import java.util.Objects;
 import java.util.function.Consumer;
@@ -36,18 +36,19 @@
 public class BrightnessMirrorController
         implements CallbackController<BrightnessMirrorController.BrightnessMirrorListener> {
 
-    private final StatusBarWindowView mStatusBarWindow;
+    private final NotificationShadeWindowView mStatusBarWindow;
     private final Consumer<Boolean> mVisibilityCallback;
-    private final NotificationPanelView mNotificationPanel;
+    private final NotificationPanelViewController mNotificationPanel;
     private final ArraySet<BrightnessMirrorListener> mBrightnessMirrorListeners = new ArraySet<>();
     private final int[] mInt2Cache = new int[2];
     private View mBrightnessMirror;
 
-    public BrightnessMirrorController(StatusBarWindowView statusBarWindow,
+    public BrightnessMirrorController(NotificationShadeWindowView statusBarWindow,
+            NotificationPanelViewController notificationPanelViewController,
             @NonNull Consumer<Boolean> visibilityCallback) {
         mStatusBarWindow = statusBarWindow;
         mBrightnessMirror = statusBarWindow.findViewById(R.id.brightness_mirror);
-        mNotificationPanel = statusBarWindow.findViewById(R.id.notification_panel);
+        mNotificationPanel = notificationPanelViewController;
         mNotificationPanel.setPanelAlphaEndAction(() -> {
             mBrightnessMirror.setVisibility(View.INVISIBLE);
         });
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index ddacc3a..759bad4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -188,7 +188,7 @@
             // NOTE: This receiver could run before this method returns, as it's not dispatching
             // on the main thread and BroadcastDispatcher may not need to register with Context.
             // The receiver will return immediately if the view does not have a Handler yet.
-            mBroadcastDispatcher.registerReceiver(mIntentReceiver, filter,
+            mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter,
                     Dependency.get(Dependency.TIME_TICK_HANDLER), UserHandle.ALL);
             Dependency.get(TunerService.class).addTunable(this, CLOCK_SECONDS,
                     StatusBarIconController.ICON_BLACKLIST);
@@ -298,7 +298,7 @@
             mShowSeconds = TunerService.parseIntegerSwitch(newValue, false);
             updateShowSeconds();
         } else {
-            setClockVisibleByUser(!StatusBarIconController.getIconBlacklist(newValue)
+            setClockVisibleByUser(!StatusBarIconController.getIconBlacklist(getContext(), newValue)
                     .contains("clock"));
             updateClockVisibility();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
index 2e26711..b4c154a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
@@ -96,7 +96,7 @@
         filter.addAction(Intent.ACTION_TIME_CHANGED);
         filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
         filter.addAction(Intent.ACTION_LOCALE_CHANGED);
-        mBroadcastDispatcher.registerReceiver(mIntentReceiver, filter,
+        mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter,
                 Dependency.get(Dependency.TIME_TICK_HANDLER));
 
         updateClock();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
index e675a7f..5dc9104 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
@@ -29,7 +29,6 @@
 import android.util.AttributeSet;
 import android.widget.TextView;
 
-import com.android.internal.telephony.TelephonyIntents;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.Dependency;
@@ -137,9 +136,9 @@
                 displayText = getContext().getText(
                         com.android.internal.R.string.emergency_calls_only);
                 Intent i = getContext().registerReceiver(null,
-                        new IntentFilter(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION));
+                        new IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED));
                 if (i != null) {
-                    displayText = i.getStringExtra(TelephonyIntents.EXTRA_PLMN);
+                    displayText = i.getStringExtra(TelephonyManager.EXTRA_PLMN);
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
index 4b283fed..d28a6699 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
@@ -35,7 +35,7 @@
 import com.android.systemui.R;
 import com.android.systemui.qs.tiles.UserDetailItemView;
 import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
-import com.android.systemui.statusbar.phone.NotificationPanelView;
+import com.android.systemui.statusbar.phone.NotificationPanelViewController;
 
 /**
  * Manages the user switcher on the Keyguard.
@@ -57,7 +57,8 @@
     private boolean mAnimating;
 
     public KeyguardUserSwitcher(Context context, ViewStub userSwitcher,
-            KeyguardStatusBarView statusBarView, NotificationPanelView panelView) {
+            KeyguardStatusBarView statusBarView,
+            NotificationPanelViewController panelViewController) {
         boolean keyguardUserSwitcherEnabled =
                 context.getResources().getBoolean(R.bool.config_keyguardUserSwitcher) || ALWAYS_ON;
         UserSwitcherController userSwitcherController = Dependency.get(UserSwitcherController.class);
@@ -67,7 +68,7 @@
             reinflateViews();
             mStatusBarView = statusBarView;
             mStatusBarView.setKeyguardUserSwitcher(this);
-            panelView.setKeyguardUserSwitcher(this);
+            panelViewController.setKeyguardUserSwitcher(this);
             mAdapter = new Adapter(context, userSwitcherController, this);
             mAdapter.registerDataSetObserver(mDataSetObserver);
             mUserSwitcherController = userSwitcherController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index 570f153..cb40d77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -79,7 +79,8 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION);
         filter.addAction(LocationManager.MODE_CHANGED_ACTION);
-        mBroadcastDispatcher.registerReceiver(this, filter, new Handler(bgLooper), UserHandle.ALL);
+        mBroadcastDispatcher.registerReceiverWithHandler(this, filter,
+                new Handler(bgLooper), UserHandle.ALL);
 
         mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
         mStatusBarManager
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index 6b842d5..3a1feaa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -15,6 +15,9 @@
  */
 package com.android.systemui.statusbar.policy;
 
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+
 import android.content.Context;
 import android.content.Intent;
 import android.database.ContentObserver;
@@ -23,6 +26,8 @@
 import android.os.Looper;
 import android.os.Message;
 import android.provider.Settings.Global;
+import android.telephony.Annotation;
+import android.telephony.CdmaEriInformation;
 import android.telephony.CellSignalStrength;
 import android.telephony.CellSignalStrengthCdma;
 import android.telephony.NetworkRegistrationInfo;
@@ -34,11 +39,8 @@
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
-import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.telephony.cdma.EriInfo;
 import com.android.settingslib.Utils;
 import com.android.settingslib.graph.SignalDrawable;
 import com.android.settingslib.net.SignalStrengthUtil;
@@ -50,7 +52,9 @@
 
 import java.io.PrintWriter;
 import java.util.BitSet;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.regex.Matcher;
@@ -74,12 +78,14 @@
     final SubscriptionInfo mSubscriptionInfo;
 
     // @VisibleForDemoMode
-    final SparseArray<MobileIconGroup> mNetworkToIconLookup;
+    final Map<String, MobileIconGroup> mNetworkToIconLookup;
 
     // Since some pieces of the phone state are interdependent we store it locally,
     // this could potentially become part of MobileState for simplification/complication
     // of code.
     private int mDataNetType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+    private boolean mCA = false;
+    private boolean mCAPlus = false;
     private int mDataState = TelephonyManager.DATA_DISCONNECTED;
     private ServiceState mServiceState;
     private SignalStrength mSignalStrength;
@@ -90,9 +96,6 @@
     boolean mInflateSignalStrengths = false;
     @VisibleForTesting
     boolean mIsShowingIconGracefully = false;
-    // Some specific carriers have 5GE network which is special LTE CA network.
-    private static final int NETWORK_TYPE_LTE_CA_5GE =
-            TelephonyManager.getAllNetworkTypes().length + 1;
 
     // TODO: Reduce number of vars passed in, if we have the NetworkController, probably don't
     // need listener lists anymore.
@@ -103,7 +106,7 @@
         super("MobileSignalController(" + info.getSubscriptionId() + ")", context,
                 NetworkCapabilities.TRANSPORT_CELLULAR, callbackHandler,
                 networkController);
-        mNetworkToIconLookup = new SparseArray<>();
+        mNetworkToIconLookup = new HashMap<>();
         mConfig = config;
         mPhone = phone;
         mDefaults = defaults;
@@ -210,29 +213,38 @@
     private void mapIconSets() {
         mNetworkToIconLookup.clear();
 
-        mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyIcons.THREE_G);
-        mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_A, TelephonyIcons.THREE_G);
-        mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_B, TelephonyIcons.THREE_G);
-        mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EHRPD, TelephonyIcons.THREE_G);
-        mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UMTS, TelephonyIcons.THREE_G);
-        mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_TD_SCDMA, TelephonyIcons.THREE_G);
+        mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_EVDO_0),
+                TelephonyIcons.THREE_G);
+        mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_EVDO_A),
+                TelephonyIcons.THREE_G);
+        mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_EVDO_B),
+                TelephonyIcons.THREE_G);
+        mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_EHRPD),
+                TelephonyIcons.THREE_G);
+        mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_UMTS),
+                TelephonyIcons.THREE_G);
+        mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_TD_SCDMA),
+                TelephonyIcons.THREE_G);
 
         if (!mConfig.showAtLeast3G) {
-            mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN,
+            mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_UNKNOWN),
                     TelephonyIcons.UNKNOWN);
-            mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE, TelephonyIcons.E);
-            mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA, TelephonyIcons.ONE_X);
-            mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT, TelephonyIcons.ONE_X);
+            mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_EDGE),
+                    TelephonyIcons.E);
+            mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_CDMA),
+                    TelephonyIcons.ONE_X);
+            mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_1xRTT),
+                    TelephonyIcons.ONE_X);
 
             mDefaultIcons = TelephonyIcons.G;
         } else {
-            mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN,
+            mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_UNKNOWN),
                     TelephonyIcons.THREE_G);
-            mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE,
+            mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_EDGE),
                     TelephonyIcons.THREE_G);
-            mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA,
+            mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_CDMA),
                     TelephonyIcons.THREE_G);
-            mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT,
+            mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_1xRTT),
                     TelephonyIcons.THREE_G);
             mDefaultIcons = TelephonyIcons.THREE_G;
         }
@@ -247,33 +259,59 @@
             hPlusGroup = TelephonyIcons.H_PLUS;
         }
 
-        mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSDPA, hGroup);
-        mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSUPA, hGroup);
-        mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPA, hGroup);
-        mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPAP, hPlusGroup);
+        mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_HSDPA), hGroup);
+        mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_HSUPA), hGroup);
+        mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_HSPA), hGroup);
+        mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_HSPAP), hPlusGroup);
 
         if (mConfig.show4gForLte) {
-            mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.FOUR_G);
+            mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_LTE),
+                    TelephonyIcons.FOUR_G);
             if (mConfig.hideLtePlus) {
-                mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE_CA,
+                mNetworkToIconLookup.put(toIconKeyCA(TelephonyManager.NETWORK_TYPE_LTE),
                         TelephonyIcons.FOUR_G);
             } else {
-                mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE_CA,
+                mNetworkToIconLookup.put(toIconKeyCA(TelephonyManager.NETWORK_TYPE_LTE),
                         TelephonyIcons.FOUR_G_PLUS);
             }
         } else {
-            mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.LTE);
+            mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_LTE),
+                    TelephonyIcons.LTE);
             if (mConfig.hideLtePlus) {
-                mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE_CA,
+                mNetworkToIconLookup.put(toIconKeyCA(TelephonyManager.NETWORK_TYPE_LTE),
                         TelephonyIcons.LTE);
             } else {
-                mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE_CA,
+                mNetworkToIconLookup.put(toIconKeyCA(TelephonyManager.NETWORK_TYPE_LTE),
                         TelephonyIcons.LTE_PLUS);
             }
         }
-        mNetworkToIconLookup.put(NETWORK_TYPE_LTE_CA_5GE,
+        mNetworkToIconLookup.put(toIconKeyCAPlus(TelephonyManager.NETWORK_TYPE_LTE),
                 TelephonyIcons.LTE_CA_5G_E);
-        mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_IWLAN, TelephonyIcons.WFC);
+        mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_IWLAN),
+                TelephonyIcons.WFC);
+    }
+
+    private String getIconKey() {
+        if (mCA) {
+            return toIconKeyCA(mDataNetType);
+        } else if (mCAPlus) {
+            return toIconKeyCAPlus(mDataNetType);
+        } else {
+            return toIconKey(mDataNetType);
+        }
+    }
+
+    // Some specific carriers have 5GE network which is special CA network.
+    private String toIconKeyCAPlus(@Annotation.NetworkType int networkType) {
+        return toIconKeyCA(networkType) + "_Plus";
+    }
+
+    private String toIconKeyCA(@Annotation.NetworkType int networkType) {
+        return toIconKey(networkType) + "_CA";
+    }
+
+    private String toIconKey(@Annotation.NetworkType int networkType) {
+        return Integer.toString(networkType);
     }
 
     private void updateInflateSignalStrength() {
@@ -376,10 +414,10 @@
             return false;
         }
         if (isCdma() && mServiceState != null) {
-            final int iconMode = mServiceState.getCdmaEriIconMode();
-            return mServiceState.getCdmaEriIconIndex() != EriInfo.ROAMING_INDICATOR_OFF
-                    && (iconMode == EriInfo.ROAMING_ICON_MODE_NORMAL
-                    || iconMode == EriInfo.ROAMING_ICON_MODE_FLASH);
+            final int iconMode = mPhone.getCdmaEriInformation().getEriIconMode();
+            return mPhone.getCdmaEriInformation().getEriIconIndex() != CdmaEriInformation.ERI_OFF
+                    && (iconMode == CdmaEriInformation.ERI_ICON_MODE_NORMAL
+                    || iconMode == CdmaEriInformation.ERI_ICON_MODE_FLASH);
         } else {
             return mServiceState != null && mServiceState.getRoaming();
         }
@@ -391,12 +429,12 @@
 
     public void handleBroadcast(Intent intent) {
         String action = intent.getAction();
-        if (action.equals(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION)) {
-            updateNetworkName(intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false),
-                    intent.getStringExtra(TelephonyIntents.EXTRA_SPN),
-                    intent.getStringExtra(TelephonyIntents.EXTRA_DATA_SPN),
-                    intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false),
-                    intent.getStringExtra(TelephonyIntents.EXTRA_PLMN));
+        if (action.equals(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED)) {
+            updateNetworkName(intent.getBooleanExtra(TelephonyManager.EXTRA_SHOW_SPN, false),
+                    intent.getStringExtra(TelephonyManager.EXTRA_SPN),
+                    intent.getStringExtra(TelephonyManager.EXTRA_DATA_SPN),
+                    intent.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false),
+                    intent.getStringExtra(TelephonyManager.EXTRA_PLMN));
             notifyListenersIfNecessary();
         } else if (action.equals(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) {
             updateDataSim();
@@ -520,10 +558,11 @@
             nr5GIconGroup = adjustNr5GIconGroupByDisplayGraceTime(nr5GIconGroup);
         }
 
+        String iconKey = getIconKey();
         if (nr5GIconGroup != null) {
             mCurrentState.iconGroup = nr5GIconGroup;
-        } else if (mNetworkToIconLookup.indexOfKey(mDataNetType) >= 0) {
-            mCurrentState.iconGroup = mNetworkToIconLookup.get(mDataNetType);
+        } else if (mNetworkToIconLookup.get(iconKey) != null) {
+            mCurrentState.iconGroup = mNetworkToIconLookup.get(iconKey);
         } else {
             mCurrentState.iconGroup = mDefaultIcons;
         }
@@ -654,7 +693,7 @@
     }
 
     boolean isDataDisabled() {
-        return !mPhone.isDataCapable();
+        return !mPhone.isDataConnectionEnabled();
     }
 
     @VisibleForTesting
@@ -676,6 +715,8 @@
         pw.println("  mSignalStrength=" + mSignalStrength + ",");
         pw.println("  mDataState=" + mDataState + ",");
         pw.println("  mDataNetType=" + mDataNetType + ",");
+        pw.println("  mCA=" + mCA + ",");
+        pw.println("  mCAPlus=" + mCAPlus + ",");
         pw.println("  mInflateSignalStrengths=" + mInflateSignalStrengths + ",");
         pw.println("  isDataDisabled=" + isDataDisabled() + ",");
         pw.println("  mIsShowingIconGracefully=" + mIsShowingIconGracefully + ",");
@@ -704,7 +745,11 @@
             }
             mServiceState = state;
             if (mServiceState != null) {
-                updateDataNetType(mServiceState.getDataNetworkType());
+                NetworkRegistrationInfo regInfo = mServiceState.getNetworkRegistrationInfo(
+                        DOMAIN_PS, TRANSPORT_TYPE_WWAN);
+                if (regInfo != null) {
+                    updateDataNetType(regInfo.getAccessNetworkTechnology());
+                }
             }
             updateTelephony();
         }
@@ -722,11 +767,13 @@
 
         private void updateDataNetType(int networkType) {
             mDataNetType = networkType;
+            mCA = false;
+            mCAPlus = false;
             if (mDataNetType == TelephonyManager.NETWORK_TYPE_LTE) {
                 if (isCarrierSpecificDataIcon()) {
-                    mDataNetType = NETWORK_TYPE_LTE_CA_5GE;
+                    mCAPlus = true;
                 } else if (mServiceState != null && mServiceState.isUsingCarrierAggregation()) {
-                    mDataNetType = TelephonyManager.NETWORK_TYPE_LTE_CA;
+                    mCA = true;
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index f640d03..6dd1133 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -22,8 +22,7 @@
 import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE;
 import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT;
 import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE;
-
-import static com.android.internal.telephony.PhoneConstants.MAX_PHONE_COUNT_DUAL_SIM;
+import static android.telephony.TelephonyManager.MODEM_COUNT_DUAL_MODEM;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -57,7 +56,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.settingslib.net.DataUsageController;
 import com.android.systemui.DemoMode;
@@ -317,16 +315,16 @@
         filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
         filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
         filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
-        filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+        filter.addAction(Intent.ACTION_SIM_STATE_CHANGED);
         filter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
         filter.addAction(TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED);
         filter.addAction(Intent.ACTION_SERVICE_STATE);
-        filter.addAction(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION);
+        filter.addAction(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED);
         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
         filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);
         filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-        mBroadcastDispatcher.registerReceiver(this, filter, mReceiverHandler);
+        mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mReceiverHandler);
         mListening = true;
 
         updateMobileControllers();
@@ -526,9 +524,9 @@
                 mConfig = Config.readConfig(mContext);
                 mReceiverHandler.post(this::handleConfigurationChanged);
                 break;
-            case TelephonyIntents.ACTION_SIM_STATE_CHANGED:
+            case Intent.ACTION_SIM_STATE_CHANGED:
                 // Avoid rebroadcast because SysUI is direct boot aware.
-                if (intent.getBooleanExtra(TelephonyIntents.EXTRA_REBROADCAST_ON_UNLOCK, false)) {
+                if (intent.getBooleanExtra(Intent.EXTRA_REBROADCAST_ON_UNLOCK, false)) {
                     break;
                 }
                 // Might have different subscriptions now.
@@ -547,7 +545,7 @@
                 mReceiverHandler.post(this::handleConfigurationChanged);
                 break;
             default:
-                int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+                int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
                         SubscriptionManager.INVALID_SUBSCRIPTION_ID);
                 if (SubscriptionManager.isValidSubscriptionId(subId)) {
                     if (mMobileSignalControllers.indexOfKey(subId) >= 0) {
@@ -582,7 +580,7 @@
     }
 
     private void filterMobileSubscriptionInSameGroup(List<SubscriptionInfo> subscriptions) {
-        if (subscriptions.size() == MAX_PHONE_COUNT_DUAL_SIM) {
+        if (subscriptions.size() == MODEM_COUNT_DUAL_MODEM) {
             SubscriptionInfo info1 = subscriptions.get(0);
             SubscriptionInfo info2 = subscriptions.get(1);
             if (info1.getGroupUuid() != null && info1.getGroupUuid().equals(info2.getGroupUuid())) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 307e3bc..408b3a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -161,6 +161,11 @@
         Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent,
                 results);
+
+        mEntry.remoteInputText = mEditText.getText();
+        mEntry.remoteInputUri = null;
+        mEntry.remoteInputMimeType = null;
+
         if (mEntry.editedSuggestionInfo == null) {
             RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_FREE_FORM_INPUT);
         } else {
@@ -177,6 +182,10 @@
         Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         RemoteInput.addDataResultToIntent(mRemoteInput, fillInIntent, results);
 
+        mEntry.remoteInputText = mContext.getString(R.string.remote_input_image_insertion_text);
+        mEntry.remoteInputMimeType = contentType;
+        mEntry.remoteInputUri = data;
+
         return fillInIntent;
     }
 
@@ -184,7 +193,6 @@
         mEditText.setEnabled(false);
         mSendButton.setVisibility(INVISIBLE);
         mProgressBar.setVisibility(VISIBLE);
-        mEntry.remoteInputText = mEditText.getText();
         mEntry.lastRemoteInputSent = SystemClock.elapsedRealtime();
         mController.addSpinning(mEntry.getKey(), mToken);
         mController.removeRemoteInput(mEntry, mToken);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 019ef3b..312c4ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -126,7 +126,8 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
         filter.addAction(Intent.ACTION_USER_UNLOCKED);
-        broadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, bgHandler, UserHandle.ALL);
+        broadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter, bgHandler,
+                UserHandle.ALL);
 
         // TODO: re-register network callback on user change.
         mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 7758aba..31b9952 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -88,7 +88,7 @@
         final IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_USER_SWITCHED);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
-        mBroadcastDispatcher.registerReceiver(new BroadcastReceiver() {
+        mBroadcastDispatcher.registerReceiverWithHandler(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
                 if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added.");
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/BatteryPreference.java b/packages/SystemUI/src/com/android/systemui/tuner/BatteryPreference.java
index 905b9a3..66372c3 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/BatteryPreference.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/BatteryPreference.java
@@ -64,7 +64,7 @@
     @Override
     public void onTuningChanged(String key, String newValue) {
         if (StatusBarIconController.ICON_BLACKLIST.equals(key)) {
-            mBlacklist = StatusBarIconController.getIconBlacklist(newValue);
+            mBlacklist = StatusBarIconController.getIconBlacklist(getContext(), newValue);
             mBatteryEnabled = !mBlacklist.contains(mBattery);
         }
         if (!mHasSetValue) {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/ClockPreference.java b/packages/SystemUI/src/com/android/systemui/tuner/ClockPreference.java
index a526603..f7d0c9f 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/ClockPreference.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/ClockPreference.java
@@ -61,7 +61,7 @@
     public void onTuningChanged(String key, String newValue) {
         if (StatusBarIconController.ICON_BLACKLIST.equals(key)) {
             mReceivedClock = true;
-            mBlacklist = StatusBarIconController.getIconBlacklist(newValue);
+            mBlacklist = StatusBarIconController.getIconBlacklist(getContext(), newValue);
             mClockEnabled = !mBlacklist.contains(mClock);
         } else if (Clock.CLOCK_SECONDS.equals(key)) {
             mReceivedSeconds = true;
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/StatusBarSwitch.java b/packages/SystemUI/src/com/android/systemui/tuner/StatusBarSwitch.java
index 6f23e20..de8ccfa 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/StatusBarSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/StatusBarSwitch.java
@@ -57,7 +57,7 @@
         if (!StatusBarIconController.ICON_BLACKLIST.equals(key)) {
             return;
         }
-        mBlacklist = StatusBarIconController.getIconBlacklist(newValue);
+        mBlacklist = StatusBarIconController.getIconBlacklist(getContext(), newValue);
         setChecked(!mBlacklist.contains(getKey()));
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index 19f0ba2..142fdc2 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -117,7 +117,7 @@
             String blacklistStr = getValue(StatusBarIconController.ICON_BLACKLIST);
             if (blacklistStr != null) {
                 ArraySet<String> iconBlacklist =
-                        StatusBarIconController.getIconBlacklist(blacklistStr);
+                        StatusBarIconController.getIconBlacklist(mContext, blacklistStr);
 
                 iconBlacklist.add("rotate");
                 iconBlacklist.add("headset");
diff --git a/packages/SystemUI/src/com/android/systemui/util/Assert.java b/packages/SystemUI/src/com/android/systemui/util/Assert.java
index 096ac3f..f6e921e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Assert.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Assert.java
@@ -18,7 +18,7 @@
 
 import android.os.Looper;
 
-import com.android.internal.annotations.VisibleForTesting;
+import androidx.annotation.VisibleForTesting;
 
 /**
  * Helper providing common assertions.
@@ -30,7 +30,9 @@
 
     public static void isMainThread() {
         if (!sMainLooper.isCurrentThread()) {
-            throw new IllegalStateException("should be called from the main thread.");
+            throw new IllegalStateException("should be called from the main thread."
+                    + " sMainLooper.threadName=" + sMainLooper.getThread().getName()
+                    + " Thread.currentThread()=" + Thread.currentThread().getName());
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
index 6976649..56aae17 100644
--- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
@@ -35,7 +35,6 @@
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.phone.LockIcon;
-import com.android.systemui.statusbar.phone.NotificationPanelView;
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -129,11 +128,6 @@
         NotificationStackScrollLayout createNotificationStackScrollLayout();
 
         /**
-         * Creates the NotificationPanelView.
-         */
-        NotificationPanelView createPanelView();
-
-        /**
          * Creates the Shelf.
          */
         NotificationShelf creatNotificationShelf();
diff --git a/packages/SystemUI/src/com/android/systemui/util/LifecycleActivity.kt b/packages/SystemUI/src/com/android/systemui/util/LifecycleActivity.kt
new file mode 100644
index 0000000..e4b7a20
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/LifecycleActivity.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.util
+
+import android.app.Activity
+import android.os.Bundle
+import android.os.PersistableBundle
+import androidx.lifecycle.LifecycleOwner
+import com.android.settingslib.core.lifecycle.Lifecycle
+
+open class LifecycleActivity : Activity(), LifecycleOwner {
+
+    private val lifecycle = Lifecycle(this)
+
+    override fun getLifecycle() = lifecycle
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        lifecycle.onAttach(this)
+        lifecycle.onCreate(savedInstanceState)
+        lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_CREATE)
+        super.onCreate(savedInstanceState)
+    }
+
+    override fun onCreate(
+        savedInstanceState: Bundle?,
+        persistentState: PersistableBundle?
+    ) {
+        lifecycle.onAttach(this)
+        lifecycle.onCreate(savedInstanceState)
+        lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_CREATE)
+        super.onCreate(savedInstanceState, persistentState)
+    }
+
+    override fun onStart() {
+        lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_START)
+        super.onStart()
+    }
+
+    override fun onResume() {
+        lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_RESUME)
+        super.onResume()
+    }
+
+    override fun onPause() {
+        lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_PAUSE)
+        super.onPause()
+    }
+
+    override fun onStop() {
+        lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_STOP)
+        super.onStop()
+    }
+
+    override fun onDestroy() {
+        lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_DESTROY)
+        super.onDestroy()
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt b/packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt
new file mode 100644
index 0000000..ecd3afd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.util.animation
+
+import android.graphics.Rect
+import android.graphics.RectF
+import androidx.dynamicanimation.animation.FloatPropertyCompat
+
+/**
+ * Helpful extra properties to use with the [PhysicsAnimator]. These allow you to animate objects
+ * such as [Rect] and [RectF].
+ *
+ * There are additional, more basic properties available in [DynamicAnimation].
+ */
+class FloatProperties {
+    companion object {
+        /**
+         * Represents the x-coordinate of a [Rect]. Typically used to animate moving a Rect
+         * horizontally.
+         *
+         * This property's getter returns [Rect.left], and its setter uses [Rect.offsetTo], which
+         * sets [Rect.left] to the new value and offsets [Rect.right] so that the width of the Rect
+         * does not change.
+         */
+        @JvmField
+        val RECT_X = object : FloatPropertyCompat<Rect>("RectX") {
+            override fun setValue(rect: Rect?, value: Float) {
+                rect?.offsetTo(value.toInt(), rect.top)
+            }
+
+            override fun getValue(rect: Rect?): Float {
+                return rect?.left?.toFloat() ?: -Float.MAX_VALUE
+            }
+        }
+
+        /**
+         * Represents the y-coordinate of a [Rect]. Typically used to animate moving a Rect
+         * vertically.
+         *
+         * This property's getter returns [Rect.top], and its setter uses [Rect.offsetTo], which
+         * sets [Rect.top] to the new value and offsets [Rect.bottom] so that the height of the Rect
+         * does not change.
+         */
+        @JvmField
+        val RECT_Y = object : FloatPropertyCompat<Rect>("RectY") {
+            override fun setValue(rect: Rect?, value: Float) {
+                rect?.offsetTo(rect.left, value.toInt())
+            }
+
+            override fun getValue(rect: Rect?): Float {
+                return rect?.top?.toFloat() ?: -Float.MAX_VALUE
+            }
+        }
+
+        /**
+         * Represents the x-coordinate of a [RectF]. Typically used to animate moving a RectF
+         * horizontally.
+         *
+         * This property's getter returns [RectF.left], and its setter uses [RectF.offsetTo], which
+         * sets [RectF.left] to the new value and offsets [RectF.right] so that the width of the
+         * RectF does not change.
+         */
+        @JvmField
+        val RECTF_X = object : FloatPropertyCompat<RectF>("RectFX") {
+            override fun setValue(rect: RectF?, value: Float) {
+                rect?.offsetTo(value, rect.top)
+            }
+
+            override fun getValue(rect: RectF?): Float {
+                return rect?.left ?: -Float.MAX_VALUE
+            }
+        }
+
+        /**
+         * Represents the y-coordinate of a [RectF]. Typically used to animate moving a RectF
+         * vertically.
+         *
+         * This property's getter returns [RectF.top], and its setter uses [RectF.offsetTo], which
+         * sets [RectF.top] to the new value and offsets [RectF.bottom] so that the height of the
+         * RectF does not change.
+         */
+        @JvmField
+        val RECTF_Y = object : FloatPropertyCompat<RectF>("RectFY") {
+            override fun setValue(rect: RectF?, value: Float) {
+                rect?.offsetTo(rect.left, value)
+            }
+
+            override fun getValue(rect: RectF?): Float {
+                return rect?.top ?: -Float.MAX_VALUE
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
index cca76bd..cfd77be 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
@@ -27,6 +27,9 @@
 import androidx.dynamicanimation.animation.SpringForce
 import com.android.systemui.util.animation.PhysicsAnimator.Companion.getInstance
 import java.util.WeakHashMap
+import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.min
 
 /**
  * Extension function for all objects which will return a PhysicsAnimator instance for that object.
@@ -35,6 +38,15 @@
 
 private const val TAG = "PhysicsAnimator"
 
+private val UNSET = -Float.MAX_VALUE
+
+/**
+ * [FlingAnimation] multiplies the friction set via [FlingAnimation.setFriction] by 4.2f, which is
+ * where this number comes from. We use it in [PhysicsAnimator.flingThenSpring] to calculate the
+ * minimum velocity for a fling to reach a certain value, given the fling's friction.
+ */
+private const val FLING_FRICTION_SCALAR_MULTIPLIER = 4.2f
+
 typealias EndAction = () -> Unit
 
 /** A map of Property -> AnimationUpdate, which is provided to update listeners on each frame. */
@@ -236,6 +248,75 @@
     }
 
     /**
+     * Flings a property using the given start velocity. If the fling animation reaches the min/max
+     * bounds (from the [flingConfig]) with velocity remaining, it'll overshoot it and spring back.
+     *
+     * If the object is already out of the fling bounds, it will immediately spring back within
+     * bounds.
+     *
+     * This is useful for animating objects that are bounded by constraints such as screen edges,
+     * since otherwise the fling animation would end abruptly upon reaching the min/max bounds.
+     *
+     * @param property The property to animate.
+     * @param startVelocity The velocity, in pixels/second, with which to start the fling. If the
+     * object is already outside the fling bounds, this velocity will be used as the start velocity
+     * of the spring that will spring it back within bounds.
+     * @param flingMustReachMinOrMax If true, the fling animation is guaranteed to reach either its
+     * minimum bound (if [startVelocity] is negative) or maximum bound (if it's positive). The
+     * animator will use startVelocity if it's sufficient, or add more velocity if necessary. This
+     * is useful when fling's deceleration-based physics are preferable to the acceleration-based
+     * forces used by springs - typically, when you're allowing the user to move an object somewhere
+     * on the screen, but it needs to be along an edge.
+     * @param flingConfig The configuration to use for the fling portion of the animation.
+     * @param springConfig The configuration to use for the spring portion of the animation.
+     */
+    @JvmOverloads
+    fun flingThenSpring(
+        property: FloatPropertyCompat<in T>,
+        startVelocity: Float,
+        flingConfig: FlingConfig,
+        springConfig: SpringConfig,
+        flingMustReachMinOrMax: Boolean = false
+    ): PhysicsAnimator<T> {
+        val flingConfigCopy = flingConfig.copy()
+        val springConfigCopy = springConfig.copy()
+        val toAtLeast = if (startVelocity < 0) flingConfig.min else flingConfig.max
+
+        // If the fling needs to reach min/max, calculate the velocity required to do so and use
+        // that if the provided start velocity is not sufficient.
+        if (flingMustReachMinOrMax &&
+                toAtLeast != -Float.MAX_VALUE && toAtLeast != Float.MAX_VALUE) {
+            val distanceToDestination = toAtLeast - property.getValue(target)
+
+            // The minimum velocity required for the fling to end up at the given destination,
+            // taking the provided fling friction value.
+            val velocityToReachDestination = distanceToDestination *
+                    (flingConfig.friction * FLING_FRICTION_SCALAR_MULTIPLIER)
+
+            // If there's distance to cover, and the provided velocity is moving in the correct
+            // direction, ensure that the velocity is high enough to reach the destination.
+            // Otherwise, just use startVelocity - this means that the fling is at or out of bounds.
+            // The fling will immediately end and a spring will bring the object back into bounds
+            // with this startVelocity.
+            flingConfigCopy.startVelocity = when {
+                distanceToDestination > 0f && startVelocity >= 0f ->
+                    max(velocityToReachDestination, startVelocity)
+                distanceToDestination < 0f && startVelocity <= 0f ->
+                    min(velocityToReachDestination, startVelocity)
+                else -> startVelocity
+            }
+
+            springConfigCopy.finalPosition = toAtLeast
+        } else {
+            flingConfigCopy.startVelocity = startVelocity
+        }
+
+        flingConfigs[property] = flingConfigCopy
+        springConfigs[property] = springConfigCopy
+        return this
+    }
+
+    /**
      * Adds a listener that will be called whenever any property on the animated object is updated.
      * This will be called on every animation frame, with the current value of the animated object
      * and the new property values.
@@ -246,7 +327,7 @@
     }
 
     /**
-     * Adds a listener that will be called whenever a property's animation ends. This is useful if
+     * Adds a listener that will be called when a property stops animating. This is useful if
      * you care about a specific property ending, or want to use the end value/end velocity from a
      * particular property's animation. If you just want to run an action when all property
      * animations have ended, use [withEndActions].
@@ -290,8 +371,17 @@
      * animation is explicitly canceled, use [addEndListener]. End listeners have an allEnded param,
      * which indicates that all relevant animations have ended.
      */
-    fun withEndActions(vararg endActions: EndAction): PhysicsAnimator<T> {
-        this.endActions.addAll(endActions)
+    fun withEndActions(vararg endActions: EndAction?): PhysicsAnimator<T> {
+        this.endActions.addAll(endActions.filterNotNull())
+        return this
+    }
+
+    /**
+     * Helper overload so that callers from Java can use Runnables or method references as end
+     * actions without having to explicitly return Unit.
+     */
+    fun withEndActions(vararg endActions: Runnable?): PhysicsAnimator<T> {
+        this.endActions.addAll(endActions.filterNotNull().map { it::run })
         return this
     }
 
@@ -311,6 +401,116 @@
                     "your test setup.")
         }
 
+        // Functions that will actually start the animations. These are run after we build and add
+        // the InternalListener, since some animations might update/end immediately and we don't
+        // want to miss those updates.
+        val animationStartActions = ArrayList<() -> Unit>()
+
+        for (animatedProperty in getAnimatedProperties()) {
+            val flingConfig = flingConfigs[animatedProperty]
+            val springConfig = springConfigs[animatedProperty]
+
+            // The property's current value on the object.
+            val currentValue = animatedProperty.getValue(target)
+
+            // Start by checking for a fling configuration. If one is present, we're either flinging
+            // or flinging-then-springing. Either way, we'll want to start the fling first.
+            if (flingConfig != null) {
+                animationStartActions.add {
+                    // When the animation is starting, adjust the min/max bounds to include the
+                    // current value of the property, if necessary. This is required to allow a
+                    // fling to bring an out-of-bounds object back into bounds. For example, if an
+                    // object was dragged halfway off the left side of the screen, but then flung to
+                    // the right, we don't want the animation to end instantly just because the
+                    // object started out of bounds. If the fling is in the direction that would
+                    // take it farther out of bounds, it will end instantly as expected.
+                    flingConfig.apply {
+                        min = min(currentValue, this.min)
+                        max = max(currentValue, this.max)
+                    }
+
+                    // Apply the configuration and start the animation. Since flings can't be
+                    // redirected while in motion, cancel it first.
+                    getFlingAnimation(animatedProperty)
+                            .also { it.cancel() }
+                            .also { flingConfig.applyToAnimation(it) }
+                            .start()
+                }
+            }
+
+            // Check for a spring configuration. If one is present, we're either springing, or
+            // flinging-then-springing.
+            if (springConfig != null) {
+
+                // If there is no corresponding fling config, we're only springing.
+                if (flingConfig == null) {
+                    // Apply the configuration and start the animation.
+                    val springAnim = getSpringAnimation(animatedProperty)
+                    springConfig.applyToAnimation(springAnim)
+                    animationStartActions.add(springAnim::start)
+                } else {
+                    // If there's a corresponding fling config, we're flinging-then-springing. Save
+                    // the fling's original bounds so we can spring to them when the fling ends.
+                    val flingMin = flingConfig.min
+                    val flingMax = flingConfig.max
+
+                    // Add an end listener that will start the spring when the fling ends.
+                    endListeners.add(0, object : EndListener<T> {
+                        override fun onAnimationEnd(
+                            target: T,
+                            property: FloatPropertyCompat<in T>,
+                            wasFling: Boolean,
+                            canceled: Boolean,
+                            finalValue: Float,
+                            finalVelocity: Float,
+                            allRelevantPropertyAnimsEnded: Boolean
+                        ) {
+                            // If this isn't the relevant property, it wasn't a fling, or the fling
+                            // was explicitly cancelled, don't spring.
+                            if (property != animatedProperty || !wasFling || canceled) {
+                                return
+                            }
+
+                            val endedWithVelocity = abs(finalVelocity) > 0
+
+                            // If the object was out of bounds when the fling animation started, it
+                            // will immediately end. In that case, we'll spring it back in bounds.
+                            val endedOutOfBounds = finalValue !in flingMin..flingMax
+
+                            // If the fling ended either out of bounds or with remaining velocity,
+                            // it's time to spring.
+                            if (endedWithVelocity || endedOutOfBounds) {
+                                springConfig.startVelocity = finalVelocity
+
+                                // If the spring's final position isn't set, this is a
+                                // flingThenSpring where flingMustReachMinOrMax was false. We'll
+                                // need to set the spring's final position here.
+                                if (springConfig.finalPosition == UNSET) {
+                                    if (endedWithVelocity) {
+                                        // If the fling ended with negative velocity, that means it
+                                        // hit the min bound, so spring to that bound (and vice
+                                        // versa).
+                                        springConfig.finalPosition =
+                                                if (finalVelocity < 0) flingMin else flingMax
+                                    } else if (endedOutOfBounds) {
+                                        // If the fling ended out of bounds, spring it to the
+                                        // nearest bound.
+                                        springConfig.finalPosition =
+                                                if (finalValue < flingMin) flingMin else flingMax
+                                    }
+                                }
+
+                                // Apply the configuration and start the spring animation.
+                                getSpringAnimation(animatedProperty)
+                                        .also { springConfig.applyToAnimation(it) }
+                                        .start()
+                            }
+                        }
+                    })
+                }
+            }
+        }
+
         // Add an internal listener that will dispatch animation events to the provided listeners.
         internalListeners.add(InternalListener(
                 getAnimatedProperties(),
@@ -318,24 +518,10 @@
                 ArrayList(endListeners),
                 ArrayList(endActions)))
 
-        for ((property, config) in flingConfigs) {
-            val currentValue = property.getValue(target)
-
-            // If the fling is already out of bounds, don't start it.
-            if (currentValue <= config.min || currentValue >= config.max) {
-                continue
-            }
-
-            val flingAnim = getFlingAnimation(property)
-            config.applyToAnimation(flingAnim)
-            flingAnim.start()
-        }
-
-        for ((property, config) in springConfigs) {
-            val springAnim = getSpringAnimation(property)
-            config.applyToAnimation(springAnim)
-            springAnim.start()
-        }
+        // Actually start the DynamicAnimations. This is delayed until after the InternalListener is
+        // constructed and added so that we don't miss the end listener firing for any animations
+        // that immediately end.
+        animationStartActions.forEach { it.invoke() }
 
         clearAnimator()
     }
@@ -381,7 +567,10 @@
         }
         anim.addEndListener { _, canceled, value, velocity ->
             internalListeners.removeAll {
-                it.onInternalAnimationEnd(property, canceled, value, velocity) } }
+                it.onInternalAnimationEnd(
+                        property, canceled, value, velocity, anim is FlingAnimation)
+            }
+        }
         return anim
     }
 
@@ -434,7 +623,8 @@
             property: FloatPropertyCompat<in T>,
             canceled: Boolean,
             finalValue: Float,
-            finalVelocity: Float
+            finalVelocity: Float,
+            isFling: Boolean
         ): Boolean {
 
             // If this property animation isn't relevant to this listener, ignore it.
@@ -461,7 +651,15 @@
 
             val allEnded = !arePropertiesAnimating(properties)
             endListeners.forEach {
-                it.onAnimationEnd(target, property, canceled, finalValue, finalVelocity, allEnded) }
+                it.onAnimationEnd(
+                        target, property, isFling, canceled, finalValue, finalVelocity,
+                        allEnded)
+
+                // Check that the end listener didn't restart this property's animation.
+                if (isPropertyAnimating(property)) {
+                    return false
+                }
+            }
 
             // If all of the animations that this listener cares about have ended, run the end
             // actions unless the animation was canceled.
@@ -495,7 +693,8 @@
 
     /** Returns whether the given property is animating.  */
     fun isPropertyAnimating(property: FloatPropertyCompat<in T>): Boolean {
-        return springAnimations[property]?.isRunning ?: false
+        return springAnimations[property]?.isRunning ?: false ||
+                flingAnimations[property]?.isRunning ?: false
     }
 
     /** Returns whether any of the given properties are animating.  */
@@ -523,15 +722,15 @@
     data class SpringConfig internal constructor(
         internal var stiffness: Float,
         internal var dampingRatio: Float,
-        internal var startVel: Float = 0f,
-        internal var finalPosition: Float = -Float.MAX_VALUE
+        internal var startVelocity: Float = 0f,
+        internal var finalPosition: Float = UNSET
     ) {
 
         constructor() :
                 this(defaultSpring.stiffness, defaultSpring.dampingRatio)
 
         constructor(stiffness: Float, dampingRatio: Float) :
-                this(stiffness = stiffness, dampingRatio = dampingRatio, startVel = 0f)
+                this(stiffness = stiffness, dampingRatio = dampingRatio, startVelocity = 0f)
 
         /** Apply these configuration settings to the given SpringAnimation. */
         internal fun applyToAnimation(anim: SpringAnimation) {
@@ -542,7 +741,7 @@
                 finalPosition = this@SpringConfig.finalPosition
             }
 
-            if (startVel != 0f) anim.setStartVelocity(startVel)
+            if (startVelocity != 0f) anim.setStartVelocity(startVelocity)
         }
     }
 
@@ -555,7 +754,7 @@
         internal var friction: Float,
         internal var min: Float,
         internal var max: Float,
-        internal var startVel: Float
+        internal var startVelocity: Float
     ) {
 
         constructor() : this(defaultFling.friction)
@@ -564,7 +763,7 @@
                 this(friction, defaultFling.min, defaultFling.max)
 
         constructor(friction: Float, min: Float, max: Float) :
-                this(friction, min, max, startVel = 0f)
+                this(friction, min, max, startVelocity = 0f)
 
         /** Apply these configuration settings to the given FlingAnimation. */
         internal fun applyToAnimation(anim: FlingAnimation) {
@@ -572,7 +771,7 @@
                 friction = this@FlingConfig.friction
                 setMinValue(min)
                 setMaxValue(max)
-                setStartVelocity(startVel)
+                setStartVelocity(startVelocity)
             }
         }
     }
@@ -625,6 +824,10 @@
          *
          * @param target The animated object itself.
          * @param property The property whose animation has just ended.
+         * @param wasFling Whether this property ended after a fling animation (as opposed to a
+         * spring animation). If this property was animated via [flingThenSpring], this will be true
+         * if the fling animation did not reach the min/max bounds, decelerating to a stop
+         * naturally. It will be false if it hit the bounds and was sprung back.
          * @param canceled Whether the animation was explicitly canceled before it naturally ended.
          * @param finalValue The final value of the animated property.
          * @param finalVelocity The final velocity (in pixels per second) of the ended animation.
@@ -662,6 +865,7 @@
         fun onAnimationEnd(
             target: T,
             property: FloatPropertyCompat<in T>,
+            wasFling: Boolean,
             canceled: Boolean,
             finalValue: Float,
             finalVelocity: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
index e86970c..965decd 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
@@ -19,6 +19,7 @@
 import android.os.Looper
 import android.util.ArrayMap
 import androidx.dynamicanimation.animation.FloatPropertyCompat
+import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.prepareForTest
 import java.util.ArrayDeque
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
@@ -119,6 +120,7 @@
                 override fun onAnimationEnd(
                     target: T,
                     property: FloatPropertyCompat<in T>,
+                    wasFling: Boolean,
                     canceled: Boolean,
                     finalValue: Float,
                     finalVelocity: Float,
@@ -389,8 +391,6 @@
             val unblockLatch = CountDownLatch(if (startBlocksUntilAnimationsEnd) 2 else 1)
 
             animationThreadHandler.post {
-                val animatedProperties = animator.getAnimatedProperties()
-
                 // Add an update listener that dispatches to any test update listeners added by
                 // tests.
                 animator.addUpdateListener(object : PhysicsAnimator.UpdateListener<T> {
@@ -398,6 +398,10 @@
                         target: T,
                         values: ArrayMap<FloatPropertyCompat<in T>, PhysicsAnimator.AnimationUpdate>
                     ) {
+                        values.forEach { (property, value) ->
+                            allUpdates.getOrPut(property, { ArrayList() }).add(value)
+                        }
+
                         for (listener in testUpdateListeners) {
                             listener.onAnimationUpdateForProperty(target, values)
                         }
@@ -410,6 +414,7 @@
                     override fun onAnimationEnd(
                         target: T,
                         property: FloatPropertyCompat<in T>,
+                        wasFling: Boolean,
                         canceled: Boolean,
                         finalValue: Float,
                         finalVelocity: Float,
@@ -417,7 +422,7 @@
                     ) {
                         for (listener in testEndListeners) {
                             listener.onAnimationEnd(
-                                    target, property, canceled, finalValue, finalVelocity,
+                                    target, property, wasFling, canceled, finalValue, finalVelocity,
                                     allRelevantPropertyAnimsEnded)
                         }
 
@@ -432,31 +437,6 @@
                     }
                 })
 
-                val updateListeners = ArrayList<PhysicsAnimator.UpdateListener<T>>().also {
-                    it.add(object : PhysicsAnimator.UpdateListener<T> {
-                        override fun onAnimationUpdateForProperty(
-                            target: T,
-                            values: ArrayMap<FloatPropertyCompat<in T>,
-                                             PhysicsAnimator.AnimationUpdate>
-                        ) {
-                            values.forEach { (property, value) ->
-                                allUpdates.getOrPut(property, { ArrayList() }).add(value)
-                            }
-                        }
-                    })
-                }
-
-                /**
-                 * Add an internal listener at the head of the list that captures update values
-                 * directly from DynamicAnimation. We use this to build a list of all updates so we
-                 * can verify that InternalListener dispatches to the real listeners properly.
-                 */
-                animator.internalListeners.add(0, animator.InternalListener(
-                        animatedProperties,
-                        updateListeners,
-                        ArrayList(),
-                        ArrayList()))
-
                 animator.startInternal()
                 unblockLatch.countDown()
             }
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
index 7cdba86..cc6d607 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
@@ -83,17 +83,19 @@
      * Provide a Background-Thread Executor by default.
      */
     @Provides
+    @Singleton
     public static Executor provideExecutor(@Background Looper looper) {
-        return new ExecutorImpl(new Handler(looper));
+        return new ExecutorImpl(looper);
     }
 
     /**
      * Provide a Background-Thread Executor.
      */
     @Provides
+    @Singleton
     @Background
     public static Executor provideBackgroundExecutor(@Background Looper looper) {
-        return new ExecutorImpl(new Handler(looper));
+        return new ExecutorImpl(looper);
     }
 
     /**
@@ -109,26 +111,29 @@
      * Provide a Background-Thread Executor by default.
      */
     @Provides
+    @Singleton
     public static DelayableExecutor provideDelayableExecutor(@Background Looper looper) {
-        return new ExecutorImpl(new Handler(looper));
+        return new ExecutorImpl(looper);
     }
 
     /**
      * Provide a Background-Thread Executor.
      */
     @Provides
+    @Singleton
     @Background
     public static DelayableExecutor provideBackgroundDelayableExecutor(@Background Looper looper) {
-        return new ExecutorImpl(new Handler(looper));
+        return new ExecutorImpl(looper);
     }
 
     /**
      * Provide a Main-Thread Executor.
      */
     @Provides
+    @Singleton
     @Main
     public static DelayableExecutor provideMainDelayableExecutor(@Main Looper looper) {
-        return new ExecutorImpl(new Handler(looper));
+        return new ExecutorImpl(looper);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java
index 7e77321..2bbf950 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java
@@ -17,37 +17,69 @@
 package com.android.systemui.util.concurrency;
 
 import android.os.Handler;
-import android.os.HandlerExecutor;
+import android.os.Looper;
 import android.os.Message;
 
+import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.TimeUnit;
 
 /**
  * Implementations of {@link DelayableExecutor} for SystemUI.
  */
-public class ExecutorImpl extends HandlerExecutor implements DelayableExecutor {
+public class ExecutorImpl implements DelayableExecutor {
     private final Handler mHandler;
 
-    public ExecutorImpl(Handler handler) {
-        super(handler);
-        mHandler = handler;
+    ExecutorImpl(Looper looper) {
+        mHandler = new Handler(looper, this::onHandleMessage);
+    }
+
+    @Override
+    public void execute(Runnable command) {
+        if (!mHandler.post(command)) {
+            throw new RejectedExecutionException(mHandler + " is shutting down");
+        }
     }
 
     @Override
     public Runnable executeDelayed(Runnable r, long delay, TimeUnit unit) {
-        Object token = new Object();
-        Message m = mHandler.obtainMessage(0, token);
+        ExecutionToken token = new ExecutionToken(r);
+        Message m = mHandler.obtainMessage(MSG_EXECUTE_RUNNABLE, token);
         mHandler.sendMessageDelayed(m, unit.toMillis(delay));
 
-        return () -> mHandler.removeCallbacksAndMessages(token);
+        return token;
     }
 
     @Override
     public Runnable executeAtTime(Runnable r, long uptimeMillis, TimeUnit unit) {
-        Object token = new Object();
-        Message m = mHandler.obtainMessage(0, token);
+        ExecutionToken token = new ExecutionToken(r);
+        Message m = mHandler.obtainMessage(MSG_EXECUTE_RUNNABLE, token);
         mHandler.sendMessageAtTime(m, unit.toMillis(uptimeMillis));
 
-        return () -> mHandler.removeCallbacksAndMessages(token);
+        return token;
     }
+
+    private boolean onHandleMessage(Message msg) {
+        if (msg.what == MSG_EXECUTE_RUNNABLE) {
+            ExecutionToken token = (ExecutionToken) msg.obj;
+            token.runnable.run();
+        } else {
+            throw new IllegalStateException("Unrecognized message: " + msg.what);
+        }
+        return true;
+    }
+
+    private class ExecutionToken implements Runnable {
+        public final Runnable runnable;
+
+        private ExecutionToken(Runnable runnable) {
+            this.runnable = runnable;
+        }
+
+        @Override
+        public void run() {
+            mHandler.removeCallbacksAndMessages(this);
+        }
+    }
+
+    private static final int MSG_EXECUTE_RUNNABLE = 0;
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index a4ed31d..112ae6f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -1008,7 +1008,7 @@
             filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
             filter.addAction(Intent.ACTION_SCREEN_OFF);
             filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
-            mBroadcastDispatcher.registerReceiver(this, filter, mWorker);
+            mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mWorker);
         }
 
         public void destroy() {
diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
new file mode 100644
index 0000000..d413308
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
@@ -0,0 +1,333 @@
+/*
+ * 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.wm;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.res.Configuration;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.IDisplayWindowInsetsController;
+import android.view.InsetsSource;
+import android.view.InsetsSourceControl;
+import android.view.InsetsState;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.WindowInsets;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+
+import com.android.systemui.dagger.qualifiers.Main;
+
+import java.util.ArrayList;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Manages IME control at the display-level. This occurs when IME comes up in multi-window mode.
+ */
+@Singleton
+public class DisplayImeController implements DisplayWindowController.DisplayWindowListener {
+    private static final String TAG = "DisplayImeController";
+
+    static final int ANIMATION_DURATION_SHOW_MS = 275;
+    static final int ANIMATION_DURATION_HIDE_MS = 340;
+    static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+    private static final int DIRECTION_NONE = 0;
+    private static final int DIRECTION_SHOW = 1;
+    private static final int DIRECTION_HIDE = 2;
+
+    SystemWindows mSystemWindows;
+    final Handler mHandler;
+
+    final SparseArray<PerDisplay> mImePerDisplay = new SparseArray<>();
+
+    final ArrayList<ImePositionProcessor> mPositionProcessors = new ArrayList<>();
+
+    @Inject
+    DisplayImeController(SystemWindows syswin, DisplayWindowController displayController,
+            @Main Handler mainHandler) {
+        mHandler = mainHandler;
+        mSystemWindows = syswin;
+        displayController.addDisplayWindowListener(this);
+    }
+
+    @Override
+    public void onDisplayAdded(int displayId) {
+        // Add's a system-ui window-manager specifically for ime. This type is special because
+        // WM will defer IME inset handling to it in multi-window scenarious.
+        PerDisplay pd = new PerDisplay(displayId,
+                mSystemWindows.mDisplayController.getDisplayLayout(displayId).rotation());
+        try {
+            mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, pd);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Unable to set insets controller on display " + displayId);
+        }
+        mImePerDisplay.put(displayId, pd);
+    }
+
+    @Override
+    public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+        PerDisplay pd = mImePerDisplay.get(displayId);
+        if (pd == null) {
+            return;
+        }
+        if (mSystemWindows.mDisplayController.getDisplayLayout(displayId).rotation()
+                != pd.mRotation && isImeShowing(displayId)) {
+            pd.startAnimation(true);
+        }
+    }
+
+    @Override
+    public void onDisplayRemoved(int displayId) {
+        try {
+            mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, null);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Unable to remove insets controller on display " + displayId);
+        }
+        mImePerDisplay.remove(displayId);
+    }
+
+    private boolean isImeShowing(int displayId) {
+        PerDisplay pd = mImePerDisplay.get(displayId);
+        if (pd == null) {
+            return false;
+        }
+        final InsetsSource imeSource = pd.mInsetsState.getSource(InsetsState.ITYPE_IME);
+        return imeSource != null && pd.mImeSourceControl != null && imeSource.isVisible();
+    }
+
+    private void dispatchPositionChanged(int displayId, int imeTop,
+            SurfaceControl.Transaction t) {
+        synchronized (mPositionProcessors) {
+            for (ImePositionProcessor pp : mPositionProcessors) {
+                pp.onImePositionChanged(displayId, imeTop, t);
+            }
+        }
+    }
+
+    private void dispatchStartPositioning(int displayId, int imeTop, int finalImeTop,
+            boolean show, SurfaceControl.Transaction t) {
+        synchronized (mPositionProcessors) {
+            for (ImePositionProcessor pp : mPositionProcessors) {
+                pp.onImeStartPositioning(displayId, imeTop, finalImeTop, show, t);
+            }
+        }
+    }
+
+    private void dispatchEndPositioning(int displayId, int imeTop, boolean show,
+            SurfaceControl.Transaction t) {
+        synchronized (mPositionProcessors) {
+            for (ImePositionProcessor pp : mPositionProcessors) {
+                pp.onImeEndPositioning(displayId, imeTop, show, t);
+            }
+        }
+    }
+
+    /**
+     * Adds an {@link ImePositionProcessor} to be called during ime position updates.
+     */
+    public void addPositionProcessor(ImePositionProcessor processor) {
+        synchronized (mPositionProcessors) {
+            if (mPositionProcessors.contains(processor)) {
+                return;
+            }
+            mPositionProcessors.add(processor);
+        }
+    }
+
+    /**
+     * Removes an {@link ImePositionProcessor} to be called during ime position updates.
+     */
+    public void removePositionProcessor(ImePositionProcessor processor) {
+        synchronized (mPositionProcessors) {
+            mPositionProcessors.remove(processor);
+        }
+    }
+
+    class PerDisplay extends IDisplayWindowInsetsController.Stub {
+        final int mDisplayId;
+        final InsetsState mInsetsState = new InsetsState();
+        InsetsSourceControl mImeSourceControl = null;
+        int mAnimationDirection = DIRECTION_NONE;
+        ValueAnimator mAnimation = null;
+        int mRotation = Surface.ROTATION_0;
+
+        PerDisplay(int displayId, int initialRotation) {
+            mDisplayId = displayId;
+            mRotation = initialRotation;
+        }
+
+        @Override
+        public void insetsChanged(InsetsState insetsState) {
+            if (mInsetsState.equals(insetsState)) {
+                return;
+            }
+            mInsetsState.set(insetsState, true /* copySources */);
+        }
+
+        @Override
+        public void insetsControlChanged(InsetsState insetsState,
+                InsetsSourceControl[] activeControls) {
+            insetsChanged(insetsState);
+            if (activeControls != null) {
+                for (InsetsSourceControl activeControl : activeControls) {
+                    if (activeControl == null) {
+                        continue;
+                    }
+                    if (activeControl.getType() == InsetsState.ITYPE_IME) {
+                        mImeSourceControl = activeControl;
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void showInsets(int types, boolean fromIme) {
+            if ((types & WindowInsets.Type.ime()) == 0) {
+                return;
+            }
+            startAnimation(true /* show */);
+        }
+
+        @Override
+        public void hideInsets(int types, boolean fromIme) {
+            if ((types & WindowInsets.Type.ime()) == 0) {
+                return;
+            }
+            startAnimation(false /* show */);
+        }
+
+        /**
+         * Sends the local visibility state back to window manager. Needed for legacy adjustForIme.
+         */
+        private void setVisibleDirectly(boolean visible) {
+            mInsetsState.getSource(InsetsState.ITYPE_IME).setVisible(visible);
+            try {
+                mSystemWindows.mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState);
+            } catch (RemoteException e) {
+            }
+        }
+
+        private int imeTop(InsetsSource imeSource, float surfaceOffset) {
+            return imeSource.getFrame().top + (int) surfaceOffset;
+        }
+
+        private void startAnimation(final boolean show) {
+            final InsetsSource imeSource = mInsetsState.getSource(InsetsState.ITYPE_IME);
+            if (imeSource == null || mImeSourceControl == null) {
+                return;
+            }
+            if ((mAnimationDirection == DIRECTION_SHOW && show)
+                    || (mAnimationDirection == DIRECTION_HIDE && !show)) {
+                return;
+            }
+            if (mAnimationDirection != DIRECTION_NONE) {
+                mAnimation.end();
+                mAnimationDirection = DIRECTION_NONE;
+            }
+            mAnimationDirection = show ? DIRECTION_SHOW : DIRECTION_HIDE;
+            mHandler.post(() -> {
+                final float defaultY = mImeSourceControl.getSurfacePosition().y;
+                final float x = mImeSourceControl.getSurfacePosition().x;
+                final float startY = show ? defaultY + imeSource.getFrame().height() : defaultY;
+                final float endY = show ? defaultY : defaultY + imeSource.getFrame().height();
+                mAnimation = ValueAnimator.ofFloat(startY, endY);
+                mAnimation.setDuration(
+                        show ? ANIMATION_DURATION_SHOW_MS : ANIMATION_DURATION_HIDE_MS);
+
+                mAnimation.addUpdateListener(animation -> {
+                    SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+                    float value = (float) animation.getAnimatedValue();
+                    t.setPosition(mImeSourceControl.getLeash(), x, value);
+                    dispatchPositionChanged(mDisplayId, imeTop(imeSource, value), t);
+                    t.apply();
+                    t.close();
+                });
+                mAnimation.setInterpolator(INTERPOLATOR);
+                mAnimation.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationStart(Animator animation) {
+                        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+                        t.setPosition(mImeSourceControl.getLeash(), x, startY);
+                        dispatchStartPositioning(mDisplayId, imeTop(imeSource, startY),
+                                imeTop(imeSource, endY), mAnimationDirection == DIRECTION_SHOW,
+                                t);
+                        if (mAnimationDirection == DIRECTION_SHOW) {
+                            t.show(mImeSourceControl.getLeash());
+                        }
+                        t.apply();
+                        t.close();
+                    }
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+                        t.setPosition(mImeSourceControl.getLeash(), x, endY);
+                        dispatchEndPositioning(mDisplayId, imeTop(imeSource, endY),
+                                mAnimationDirection == DIRECTION_SHOW, t);
+                        if (mAnimationDirection == DIRECTION_HIDE) {
+                            t.hide(mImeSourceControl.getLeash());
+                        }
+                        t.apply();
+                        t.close();
+
+                        mAnimationDirection = DIRECTION_NONE;
+                        mAnimation = null;
+                    }
+                });
+                if (!show) {
+                    // When going away, queue up insets change first, otherwise any bounds changes
+                    // can have a "flicker" of ime-provided insets.
+                    setVisibleDirectly(false /* visible */);
+                }
+                mAnimation.start();
+                if (show) {
+                    // When showing away, queue up insets change last, otherwise any bounds changes
+                    // can have a "flicker" of ime-provided insets.
+                    setVisibleDirectly(true /* visible */);
+                }
+            });
+        }
+    }
+
+    /**
+     * Allows other things to synchronize with the ime position
+     */
+    public interface ImePositionProcessor {
+        /**
+         * Called when the IME position is starting to animate.
+         */
+        void onImeStartPositioning(int displayId, int imeTop, int finalImeTop, boolean showing,
+                SurfaceControl.Transaction t);
+
+        /**
+         * Called when the ime position changed. This is expected to be a synchronous call on the
+         * animation thread. Operations can be added to the transaction to be applied in sync.
+         */
+        void onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t);
+
+        /**
+         * Called when the IME position is done animating.
+         */
+        void onImeEndPositioning(int displayId, int imeTop, boolean showing,
+                SurfaceControl.Transaction t);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java b/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java
index 02c7857..5aba013 100644
--- a/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java
+++ b/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java
@@ -39,10 +39,10 @@
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
-import android.view.SurfaceControlViewHost;
 import android.view.WindowlessWindowManager;
 
 import com.android.internal.os.IResultReceiver;
@@ -238,11 +238,13 @@
                 long frameNumber, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
                 Rect outVisibleInsets, Rect outStableInsets,
                 DisplayCutout.ParcelableWrapper cutout, MergedConfiguration mergedConfiguration,
-                SurfaceControl outSurfaceControl, InsetsState outInsetsState) {
+                SurfaceControl outSurfaceControl, InsetsState outInsetsState,
+                Point outSurfaceSize) {
             int res = super.relayout(window, seq, attrs, requestedWidth, requestedHeight,
                     viewVisibility, flags, frameNumber, outFrame, outOverscanInsets,
                     outContentInsets, outVisibleInsets, outStableInsets,
-                    cutout, mergedConfiguration, outSurfaceControl, outInsetsState);
+                    cutout, mergedConfiguration, outSurfaceControl, outInsetsState,
+                    outSurfaceSize);
             if (res != 0) {
                 return res;
             }
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index bfb0e15..c51624b 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -35,7 +35,6 @@
     <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
     <uses-permission android:name="android.permission.ACCESS_VR_MANAGER" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
-    <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
     <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
     <uses-permission android:name="android.permission.REQUEST_NETWORK_SCORES" />
     <uses-permission android:name="android.permission.CONTROL_VPN" />
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
new file mode 100644
index 0000000..1954b39
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.AdditionalAnswers.answerVoid;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.IKeyguardCallback;
+import android.app.admin.IKeyguardClient;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+import android.testing.ViewUtils;
+import android.view.SurfaceControl;
+import android.view.SurfaceView;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+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;
+import org.mockito.Spy;
+
+@RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase {
+
+    private static final int TARGET_USER_ID = KeyguardUpdateMonitor.getCurrentUser();
+
+    private AdminSecondaryLockScreenController mTestController;
+    private ComponentName mComponentName;
+    private Intent mServiceIntent;
+    private TestableLooper mTestableLooper;
+    private ViewGroup mParent;
+
+    @Mock
+    private Handler mHandler;
+    @Mock
+    private IKeyguardClient.Stub mKeyguardClient;
+    @Mock
+    private KeyguardSecurityCallback mKeyguardCallback;
+    @Mock
+    private KeyguardUpdateMonitor mUpdateMonitor;
+    @Spy
+    private StubTransaction mTransaction;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mParent = spy(new FrameLayout(mContext));
+        ViewUtils.attachView(mParent);
+
+        mTestableLooper = TestableLooper.get(this);
+        mComponentName = new ComponentName(mContext, "FakeKeyguardClient.class");
+        mServiceIntent = new Intent().setComponent(mComponentName);
+
+        mContext.addMockService(mComponentName, mKeyguardClient);
+        // Have Stub.asInterface return the mocked interface.
+        when(mKeyguardClient.queryLocalInterface(anyString())).thenReturn(mKeyguardClient);
+        when(mKeyguardClient.asBinder()).thenReturn(mKeyguardClient);
+
+        mTestController = new AdminSecondaryLockScreenController(
+                mContext, mParent, mUpdateMonitor, mKeyguardCallback, mHandler, mTransaction);
+    }
+
+    @Test
+    public void testShow() throws Exception {
+        doAnswer(invocation -> {
+            IKeyguardCallback callback = (IKeyguardCallback) invocation.getArguments()[1];
+            callback.onSurfaceControlCreated(new SurfaceControl());
+            return null;
+        }).when(mKeyguardClient).onSurfaceReady(any(), any(IKeyguardCallback.class));
+
+        mTestController.show(mServiceIntent);
+
+        verifySurfaceReady();
+        verify(mTransaction).reparent(any(), any());
+        assertThat(mContext.isBound(mComponentName)).isTrue();
+    }
+
+    @Test
+    public void testShow_dismissedByCallback() throws Exception {
+        doAnswer(invocation -> {
+            IKeyguardCallback callback = (IKeyguardCallback) invocation.getArguments()[1];
+            callback.onDismiss();
+            return null;
+        }).when(mKeyguardClient).onSurfaceReady(any(), any(IKeyguardCallback.class));
+
+        mTestController.show(mServiceIntent);
+
+        verifyViewDismissed(verifySurfaceReady());
+    }
+
+    @Test
+    public void testHide() throws Exception {
+        // Show the view first, then hide.
+        doAnswer(invocation -> {
+            IKeyguardCallback callback = (IKeyguardCallback) invocation.getArguments()[1];
+            callback.onSurfaceControlCreated(new SurfaceControl());
+            return null;
+        }).when(mKeyguardClient).onSurfaceReady(any(), any(IKeyguardCallback.class));
+
+        mTestController.show(mServiceIntent);
+        SurfaceView v = verifySurfaceReady();
+
+        mTestController.hide();
+        verify(mParent).removeView(v);
+        assertThat(mContext.isBound(mComponentName)).isFalse();
+    }
+
+    @Test
+    public void testHide_notShown() throws Exception {
+        mTestController.hide();
+        // Nothing should happen if trying to hide when the view isn't attached yet.
+        verify(mParent, never()).removeView(any(SurfaceView.class));
+    }
+
+    @Test
+    public void testDismissed_onSurfaceReady_RemoteException() throws Exception {
+        doThrow(new RemoteException()).when(mKeyguardClient)
+                .onSurfaceReady(any(), any(IKeyguardCallback.class));
+
+        mTestController.show(mServiceIntent);
+
+        verifyViewDismissed(verifySurfaceReady());
+    }
+
+    @Test
+    public void testDismissed_onSurfaceReady_timeout() throws Exception {
+        // Mocked KeyguardClient never handles the onSurfaceReady, so the operation times out,
+        // resulting in the view being dismissed.
+        doAnswer(answerVoid(Runnable::run)).when(mHandler)
+                .postDelayed(any(Runnable.class), anyLong());
+
+        mTestController.show(mServiceIntent);
+
+        verifyViewDismissed(verifySurfaceReady());
+    }
+
+    private SurfaceView verifySurfaceReady() throws Exception {
+        mTestableLooper.processAllMessages();
+        ArgumentCaptor<SurfaceView> captor = ArgumentCaptor.forClass(SurfaceView.class);
+        verify(mParent).addView(captor.capture());
+
+        mTestableLooper.processAllMessages();
+        verify(mKeyguardClient).onSurfaceReady(any(), any(IKeyguardCallback.class));
+        return captor.getValue();
+    }
+
+    private void verifyViewDismissed(SurfaceView v) throws Exception {
+        verify(mParent).removeView(v);
+        verify(mKeyguardCallback).dismiss(true, TARGET_USER_ID);
+        assertThat(mContext.isBound(mComponentName)).isFalse();
+    }
+
+    /**
+     * Stubbed {@link SurfaceControl.Transaction} class that can be used when unit testing to
+     * avoid calls to native code.
+     */
+    private class StubTransaction extends SurfaceControl.Transaction {
+        @Override
+        public void apply() {
+        }
+
+        @Override
+        public SurfaceControl.Transaction reparent(SurfaceControl sc, SurfaceControl newParent) {
+            return this;
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
index b70fdbd..eccf096 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
@@ -19,7 +19,7 @@
 
 import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
 import static android.telephony.SubscriptionManager.DATA_ROAMING_ENABLE;
-import static android.telephony.SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE;
+import static android.telephony.SubscriptionManager.NAME_SOURCE_DEFAULT;
 
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.TestCase.assertFalse;
@@ -83,14 +83,14 @@
     private static final String TEST_CARRIER_2 = "TEST_CARRIER_2";
     private static final int TEST_CARRIER_ID = 1;
     private static final SubscriptionInfo TEST_SUBSCRIPTION = new SubscriptionInfo(0, "", 0,
-            TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "",
+            TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT, 0xFFFFFF, "",
             DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", false, null,
             TEST_CARRIER_ID, 0);
     private static final SubscriptionInfo TEST_SUBSCRIPTION_NULL = new SubscriptionInfo(0, "", 0,
-            TEST_CARRIER, null, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "", DATA_ROAMING_DISABLE,
+            TEST_CARRIER, null, NAME_SOURCE_DEFAULT, 0xFFFFFF, "", DATA_ROAMING_DISABLE,
             null, null, null, null, false, null, "");
     private static final SubscriptionInfo TEST_SUBSCRIPTION_ROAMING = new SubscriptionInfo(0, "", 0,
-            TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "",
+            TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT, 0xFFFFFF, "",
             DATA_ROAMING_ENABLE, null, null, null, null, false, null, "");
     @Mock
     private WifiManager mWifiManager;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 40ea6dd..59eb6c5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -17,7 +17,7 @@
 package com.android.keyguard;
 
 import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
-import static android.telephony.SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE;
+import static android.telephony.SubscriptionManager.NAME_SOURCE_DEFAULT;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -34,12 +34,16 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.Activity;
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.TrustManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricSourceType;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
@@ -57,7 +61,6 @@
 import android.testing.TestableContext;
 import android.testing.TestableLooper;
 
-import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.systemui.DumpController;
 import com.android.systemui.SysuiTestCase;
@@ -84,11 +87,11 @@
     private static final int TEST_CARRIER_ID = 1;
     private static final String TEST_GROUP_UUID = "59b5c870-fc4c-47a4-a99e-9db826b48b24";
     private static final SubscriptionInfo TEST_SUBSCRIPTION = new SubscriptionInfo(1, "", 0,
-            TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "",
+            TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT, 0xFFFFFF, "",
             DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", false, TEST_GROUP_UUID,
             TEST_CARRIER_ID, 0);
     private static final SubscriptionInfo TEST_SUBSCRIPTION_2 = new SubscriptionInfo(2, "", 0,
-            TEST_CARRIER, TEST_CARRIER_2, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "",
+            TEST_CARRIER, TEST_CARRIER_2, NAME_SOURCE_DEFAULT, 0xFFFFFF, "",
             DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", true, TEST_GROUP_UUID,
             TEST_CARRIER_ID, 0);
     @Mock
@@ -150,10 +153,10 @@
 
     @Test
     public void testReceiversRegistered() {
-        verify(mBroadcastDispatcher, atLeastOnce()).registerReceiver(
+        verify(mBroadcastDispatcher, atLeastOnce()).registerReceiverWithHandler(
                 eq(mKeyguardUpdateMonitor.mBroadcastReceiver),
                 any(IntentFilter.class), any(Handler.class));
-        verify(mBroadcastDispatcher, atLeastOnce()).registerReceiver(
+        verify(mBroadcastDispatcher, atLeastOnce()).registerReceiverWithHandler(
                 eq(mKeyguardUpdateMonitor.mBroadcastAllReceiver),
                 any(IntentFilter.class), any(Handler.class), eq(UserHandle.ALL));
     }
@@ -167,7 +170,7 @@
         Assert.assertTrue("onSimStateChanged not called",
                 mKeyguardUpdateMonitor.hasSimStateJustChanged());
 
-        intent.putExtra(TelephonyIntents.EXTRA_REBROADCAST_ON_UNLOCK, true);
+        intent.putExtra(Intent.EXTRA_REBROADCAST_ON_UNLOCK, true);
         mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext(), intent);
         mTestableLooper.processAllMessages();
         Assert.assertFalse("onSimStateChanged should have been skipped",
@@ -520,13 +523,59 @@
         assertThat(mKeyguardUpdateMonitor.isTrustUsuallyManaged(user)).isFalse();
     }
 
+    @Test
+    public void testSecondaryLockscreenRequirement() {
+        int user = KeyguardUpdateMonitor.getCurrentUser();
+        String packageName = "fake.test.package";
+        String cls = "FakeService";
+        ServiceInfo serviceInfo = new ServiceInfo();
+        serviceInfo.packageName = packageName;
+        serviceInfo.name = cls;
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.serviceInfo = serviceInfo;
+        when(mPackageManager.resolveService(any(Intent.class), eq(0))).thenReturn(resolveInfo);
+        when(mDevicePolicyManager.isSecondaryLockscreenEnabled(eq(user))).thenReturn(true, false);
+
+        // Initially null.
+        assertThat(mKeyguardUpdateMonitor.getSecondaryLockscreenRequirement(user)).isNull();
+
+        // Set non-null after DPM change.
+        setBroadcastReceiverPendingResult(mKeyguardUpdateMonitor.mBroadcastAllReceiver);
+        Intent intent = new Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
+        mKeyguardUpdateMonitor.mBroadcastAllReceiver.onReceive(getContext(), intent);
+        mTestableLooper.processAllMessages();
+
+        Intent storedIntent = mKeyguardUpdateMonitor.getSecondaryLockscreenRequirement(user);
+        assertThat(storedIntent.getComponent().getClassName()).isEqualTo(cls);
+        assertThat(storedIntent.getComponent().getPackageName()).isEqualTo(packageName);
+
+        // Back to null after another DPM change.
+        mKeyguardUpdateMonitor.mBroadcastAllReceiver.onReceive(getContext(), intent);
+        mTestableLooper.processAllMessages();
+        assertThat(mKeyguardUpdateMonitor.getSecondaryLockscreenRequirement(user)).isNull();
+    }
+
+    private void setBroadcastReceiverPendingResult(BroadcastReceiver receiver) {
+        BroadcastReceiver.PendingResult pendingResult =
+                new BroadcastReceiver.PendingResult(Activity.RESULT_OK,
+                        "resultData",
+                        /* resultExtras= */ null,
+                        BroadcastReceiver.PendingResult.TYPE_UNREGISTERED,
+                        /* ordered= */ true,
+                        /* sticky= */ false,
+                        /* token= */ null,
+                        UserHandle.myUserId(),
+                        /* flags= */ 0);
+        receiver.setPendingResult(pendingResult);
+    }
+
     private Intent putPhoneInfo(Intent intent, Bundle data, Boolean simInited) {
         int subscription = simInited
                 ? 1/* mock subid=1 */ : SubscriptionManager.DUMMY_SUBSCRIPTION_ID_BASE;
         if (data != null) intent.putExtras(data);
-        intent.putExtra(PhoneConstants.PHONE_NAME_KEY, "Phone");
-        intent.putExtra("subscription", subscription);
-        intent.putExtra("slot", 0/* SLOT 1 */);
+
+        intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subscription);
+        intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 0);
         return intent;
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
index 9c9a627f..8d11b54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
@@ -48,7 +48,7 @@
 import com.android.systemui.appops.AppOpsController;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 
@@ -71,7 +71,7 @@
     @Mock private NotificationEntryManager mEntryManager;
     @Mock private AppOpsController mAppOpsController;
     @Mock private Handler mMainHandler;
-    @Mock private NotifCollection mNotifCollection;
+    @Mock private NotifPipeline mNotifPipeline;
 
     @Before
     public void setUp() throws Exception {
@@ -81,7 +81,7 @@
         MockitoAnnotations.initMocks(this);
         mFsc = new ForegroundServiceController(mEntryManager, mAppOpsController, mMainHandler);
         mListener = new ForegroundServiceNotificationListener(
-                mContext, mFsc, mEntryManager, mNotifCollection);
+                mContext, mFsc, mEntryManager, mNotifPipeline);
         ArgumentCaptor<NotificationEntryListener> entryListenerCaptor =
                 ArgumentCaptor.forClass(NotificationEntryListener.class);
         verify(mEntryManager).addNotificationEntryListener(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index ba264c0..5706bee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -53,6 +53,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.fragments.FragmentService;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarWindowView;
 import com.android.systemui.tuner.TunablePadding;
@@ -80,7 +81,8 @@
     private WindowManager mWindowManager;
     private FragmentService mFragmentService;
     private FragmentHostManager mFragmentHostManager;
-    private StatusBarWindowView mView;
+    private NotificationShadeWindowView mView;
+    private StatusBarWindowView mStatusBarWindowView;
     private TunablePaddingService mTunablePaddingService;
     private Handler mMainHandler;
     @Mock
@@ -99,9 +101,11 @@
         mFragmentService = mDependency.injectMockDependency(FragmentService.class);
 
         mWindowManager = mock(WindowManager.class);
-        mView = spy(new StatusBarWindowView(mContext, null));
+        mView = spy(new NotificationShadeWindowView(mContext, null));
+        mStatusBarWindowView = spy(new StatusBarWindowView(mContext, null));
         when(mStatusBarLazy.get()).thenReturn(mStatusBar);
-        when(mStatusBar.getStatusBarWindow()).thenReturn(mView);
+        when(mStatusBar.getNotificationShadeWindowView()).thenReturn(mView);
+        when(mStatusBar.getStatusBarWindow()).thenReturn(mStatusBarWindowView);
 
         Display display = mContext.getSystemService(WindowManager.class).getDefaultDisplay();
         when(mWindowManager.getDefaultDisplay()).thenReturn(display);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
index 0c53b03..b3071f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
@@ -19,8 +19,11 @@
 import android.content.Context;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.Log;
 
 public class TestableDependency extends Dependency {
+    private static final String TAG = "TestableDependency";
+
     private final ArrayMap<Object, Object> mObjs = new ArrayMap<>();
     private final ArraySet<Object> mInstantiatedObjects = new ArraySet<>();
 
@@ -44,7 +47,7 @@
 
     public <T> void injectTestDependency(Class<T> key, T obj) {
         if (mInstantiatedObjects.contains(key)) {
-            throw new IllegalStateException(key + " was already initialized");
+            Log.d(TAG, key + " was already initialized but overriding with testDependency.");
         }
         mObjs.put(key, obj);
     }
@@ -52,6 +55,7 @@
     @Override
     protected <T> T createDependency(Object key) {
         if (mObjs.containsKey(key)) return (T) mObjs.get(key);
+
         mInstantiatedObjects.add(key);
         return super.createDependency(key);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
index af0ef82..e5ae1aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -25,11 +25,11 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+
+import static java.lang.Thread.sleep;
 
 import android.app.AppOpsManager;
 import android.content.pm.PackageManager;
@@ -57,7 +57,6 @@
     private static final String TEST_PACKAGE_NAME = "test";
     private static final int TEST_UID = UserHandle.getUid(0, 0);
     private static final int TEST_UID_OTHER = UserHandle.getUid(1, 0);
-    private static final int TEST_UID_NON_USER_SENSITIVE = UserHandle.getUid(2, 0);
 
     @Mock
     private AppOpsManager mAppOpsManager;
@@ -68,8 +67,6 @@
     @Mock
     private AppOpsControllerImpl.H mMockHandler;
     @Mock
-    private PermissionFlagsCache mFlagsCache;
-    @Mock
     private DumpController mDumpController;
 
     private AppOpsControllerImpl mController;
@@ -85,17 +82,9 @@
         // All permissions of TEST_UID and TEST_UID_OTHER are user sensitive. None of
         // TEST_UID_NON_USER_SENSITIVE are user sensitive.
         getContext().setMockPackageManager(mPackageManager);
-        when(mFlagsCache.getPermissionFlags(anyString(), anyString(),
-                eq(UserHandle.getUserHandleForUid(TEST_UID)))).thenReturn(
-                PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED);
-        when(mFlagsCache.getPermissionFlags(anyString(), anyString(),
-                eq(UserHandle.getUserHandleForUid(TEST_UID_OTHER)))).thenReturn(
-                PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED);
-        when(mFlagsCache.getPermissionFlags(anyString(), anyString(),
-                eq(UserHandle.getUserHandleForUid(TEST_UID_NON_USER_SENSITIVE)))).thenReturn(0);
 
-        mController = new AppOpsControllerImpl(mContext, mTestableLooper.getLooper(), mFlagsCache,
-                mDumpController);
+        mController =
+                new AppOpsControllerImpl(mContext, mTestableLooper.getLooper(), mDumpController);
     }
 
     @Test
@@ -191,14 +180,6 @@
     }
 
     @Test
-    public void nonUserSensitiveOpsAreIgnored() {
-        mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO,
-                TEST_UID_NON_USER_SENSITIVE, TEST_PACKAGE_NAME, true);
-        assertEquals(0, mController.getActiveAppOpsForUser(
-                UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE)).size());
-    }
-
-    @Test
     public void opNotedScheduledForRemoval() {
         mController.setBGHandler(mMockHandler);
         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
@@ -251,4 +232,100 @@
         // Only one post to notify subscribers
         verify(mMockHandler, times(2)).scheduleRemoval(any(), anyLong());
     }
+
+    @Test
+    public void testActiveOpNotRemovedAfterNoted() throws InterruptedException {
+        // Replaces the timeout delay with 5 ms
+        AppOpsControllerImpl.H testHandler = mController.new H(mTestableLooper.getLooper()) {
+            @Override
+            public void scheduleRemoval(AppOpItem item, long timeToRemoval) {
+                super.scheduleRemoval(item, 5L);
+            }
+        };
+
+        mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
+        mController.setBGHandler(testHandler);
+
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+
+        mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
+                AppOpsManager.MODE_ALLOWED);
+
+        mTestableLooper.processAllMessages();
+        List<AppOpItem> list = mController.getActiveAppOps();
+        verify(mCallback).onActiveStateChanged(
+                AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+
+        // Duplicates are not removed between active and noted
+        assertEquals(2, list.size());
+
+        sleep(10L);
+
+        mTestableLooper.processAllMessages();
+
+        verify(mCallback, never()).onActiveStateChanged(
+                AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, false);
+        list = mController.getActiveAppOps();
+        assertEquals(1, list.size());
+    }
+
+    @Test
+    public void testNotedNotRemovedAfterActive() {
+        mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
+
+        mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
+                AppOpsManager.MODE_ALLOWED);
+
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+
+        mTestableLooper.processAllMessages();
+        List<AppOpItem> list = mController.getActiveAppOps();
+        verify(mCallback).onActiveStateChanged(
+                AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+
+        // Duplicates are not removed between active and noted
+        assertEquals(2, list.size());
+
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, false);
+
+        mTestableLooper.processAllMessages();
+
+        verify(mCallback, never()).onActiveStateChanged(
+                AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, false);
+        list = mController.getActiveAppOps();
+        assertEquals(1, list.size());
+    }
+
+    @Test
+    public void testNotedAndActiveOnlyOneCall() {
+        mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
+
+        mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
+                AppOpsManager.MODE_ALLOWED);
+
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+
+        mTestableLooper.processAllMessages();
+        verify(mCallback).onActiveStateChanged(
+                AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+    }
+
+    @Test
+    public void testActiveAndNotedOnlyOneCall() {
+        mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
+
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+
+        mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
+                AppOpsManager.MODE_ALLOWED);
+
+        mTestableLooper.processAllMessages();
+        verify(mCallback).onActiveStateChanged(
+                AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/PermissionFlagsCacheTest.kt b/packages/SystemUI/tests/src/com/android/systemui/appops/PermissionFlagsCacheTest.kt
deleted file mode 100644
index dc070de..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/PermissionFlagsCacheTest.kt
+++ /dev/null
@@ -1,88 +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.appops
-
-import android.content.Context
-import android.content.pm.PackageManager
-import android.os.UserHandle
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.anyString
-import org.mockito.Mock
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class PermissionFlagsCacheTest : SysuiTestCase() {
-
-    companion object {
-        const val TEST_PERMISSION = "test_permission"
-        const val TEST_PACKAGE = "test_package"
-    }
-
-    @Mock
-    private lateinit var mPackageManager: PackageManager
-    @Mock
-    private lateinit var mUserHandle: UserHandle
-    private lateinit var flagsCache: TestPermissionFlagsCache
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        mContext.setMockPackageManager(mPackageManager)
-        flagsCache = TestPermissionFlagsCache(mContext)
-    }
-
-    @Test
-    fun testCallsPackageManager_exactlyOnce() {
-        flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle)
-        flagsCache.time = CACHE_EXPIRATION - 1
-        verify(mPackageManager).getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle)
-    }
-
-    @Test
-    fun testCallsPackageManager_cacheExpired() {
-        flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle)
-        flagsCache.time = CACHE_EXPIRATION + 1
-        flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle)
-        verify(mPackageManager, times(2))
-                .getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle)
-    }
-
-    @Test
-    fun testCallsPackageMaanger_multipleKeys() {
-        flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle)
-        flagsCache.getPermissionFlags(TEST_PERMISSION, "", mUserHandle)
-        verify(mPackageManager, times(2))
-                .getPermissionFlags(anyString(), anyString(), any())
-    }
-
-    private class TestPermissionFlagsCache(context: Context) : PermissionFlagsCache(context) {
-        var time = 0L
-
-        override fun getCurrentTime(): Long {
-            return time
-        }
-    }
-}
\ No newline at end of file
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 162b16e..f264259 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
@@ -18,19 +18,25 @@
 
 import static android.hardware.biometrics.BiometricManager.Authenticators;
 
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.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.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricPrompt;
 import android.os.Bundle;
+import android.os.UserManager;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
@@ -57,6 +63,7 @@
     private TestableAuthContainer mAuthContainer;
 
     private @Mock AuthDialogCallback mCallback;
+    private @Mock UserManager mUserManager;
 
     @Before
     public void setup() {
@@ -156,6 +163,18 @@
         verify(mAuthContainer.mFrameLayout).addView(mAuthContainer.mCredentialView);
     }
 
+    @Test
+    public void testCredentialViewUsesEffectiveUserId() {
+        final int dummyEffectiveUserId = 200;
+        when(mUserManager.getCredentialOwnerProfile(anyInt())).thenReturn(dummyEffectiveUserId);
+
+        initializeContainer(Authenticators.DEVICE_CREDENTIAL);
+        mAuthContainer.onAttachedToWindowInternal();
+        assertTrue(mAuthContainer.mCredentialView instanceof AuthCredentialPatternView);
+        assertEquals(dummyEffectiveUserId, mAuthContainer.mCredentialView.mEffectiveUserId);
+        assertEquals(Utils.CREDENTIAL_PATTERN, mAuthContainer.mCredentialView.mCredentialType);
+    }
+
     private void initializeContainer(int authenticators) {
         AuthContainerView.Config config = new AuthContainerView.Config();
         config.mContext = mContext;
@@ -192,8 +211,7 @@
         }
 
         @Override
-        public AuthPanelController getPanelController(Context context, View view,
-                boolean isManagedProfile) {
+        public AuthPanelController getPanelController(Context context, View view) {
             return mock(AuthPanelController.class);
         }
 
@@ -211,5 +229,15 @@
         public int getAnimateCredentialStartDelayMs() {
             return 0;
         }
+
+        @Override
+        public UserManager getUserManager(Context context) {
+            return mUserManager;
+        }
+
+        @Override
+        public @Utils.CredentialType int getCredentialType(Context context, int effectiveUserId) {
+            return Utils.CREDENTIAL_PATTERN;
+        }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index c0e92e0..65399bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -20,7 +20,6 @@
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNull;
-import static junit.framework.TestCase.assertNotNull;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -292,6 +291,24 @@
     // Corner case tests
 
     @Test
+    public void testCancelAuthentication_whenCredentialConfirmed_doesntCrash() throws Exception {
+        // It's possible that before the client is notified that credential is confirmed, the client
+        // requests to cancel authentication.
+        //
+        // Test that the following sequence of events does not crash SystemUI:
+        // 1) Credential is confirmed
+        // 2) Client cancels authentication
+
+        showDialog(Authenticators.DEVICE_CREDENTIAL, BiometricPrompt.TYPE_NONE);
+        verify(mDialog1).show(any(), any());
+
+        mAuthController.onDismissed(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED);
+        verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED);
+
+        mAuthController.hideAuthenticationDialog();
+    }
+
+    @Test
     public void testShowNewDialog_beforeOldDialogDismissed_SkipsAnimations() {
         showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
         verify(mDialog1).show(any(), any());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
index 42fbf59..22b1837 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
@@ -27,6 +27,8 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
 import junit.framework.Assert.assertSame
 import org.junit.Before
 import org.junit.Test
@@ -39,6 +41,7 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import java.util.concurrent.Executor
 
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
@@ -73,6 +76,8 @@
     @Mock
     private lateinit var mockHandler: Handler
 
+    private lateinit var executor: Executor
+
     @Captor
     private lateinit var argumentCaptor: ArgumentCaptor<ReceiverData>
 
@@ -83,6 +88,7 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
+        executor = FakeExecutor(FakeSystemClock())
 
         broadcastDispatcher = TestBroadcastDispatcher(
                 mockContext,
@@ -98,8 +104,9 @@
 
     @Test
     fun testAddingReceiverToCorrectUBR() {
-        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user0)
-        broadcastDispatcher.registerReceiver(
+        broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
+                mockHandler, user0)
+        broadcastDispatcher.registerReceiverWithHandler(
                 broadcastReceiverOther, intentFilterOther, mockHandler, user1)
 
         testableLooper.processAllMessages()
@@ -115,9 +122,29 @@
     }
 
     @Test
+    fun testAddingReceiverToCorrectUBR_executor() {
+        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, executor, user0)
+        broadcastDispatcher.registerReceiver(
+                broadcastReceiverOther, intentFilterOther, executor, user1)
+
+        testableLooper.processAllMessages()
+
+        verify(mockUBRUser0).registerReceiver(capture(argumentCaptor))
+
+        assertSame(broadcastReceiver, argumentCaptor.value.receiver)
+        assertSame(intentFilter, argumentCaptor.value.filter)
+
+        verify(mockUBRUser1).registerReceiver(capture(argumentCaptor))
+        assertSame(broadcastReceiverOther, argumentCaptor.value.receiver)
+        assertSame(intentFilterOther, argumentCaptor.value.filter)
+    }
+
+    @Test
     fun testRemovingReceiversRemovesFromAllUBR() {
-        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user0)
-        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user1)
+        broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
+                mockHandler, user0)
+        broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
+                mockHandler, user1)
 
         broadcastDispatcher.unregisterReceiver(broadcastReceiver)
 
@@ -129,8 +156,10 @@
 
     @Test
     fun testRemoveReceiverFromUser() {
-        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user0)
-        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user1)
+        broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
+                mockHandler, user0)
+        broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
+                mockHandler, user1)
 
         broadcastDispatcher.unregisterReceiverForUser(broadcastReceiver, user0)
 
@@ -143,8 +172,8 @@
     @Test
     fun testRegisterCurrentAsActualUser() {
         setUserMock(mockContext, user1)
-        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler,
-                UserHandle.CURRENT)
+        broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
+                mockHandler, UserHandle.CURRENT)
 
         testableLooper.processAllMessages()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
index 21ed155..7821ae2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
@@ -26,6 +26,8 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
@@ -69,8 +71,6 @@
     @Mock
     private lateinit var mockContext: Context
     @Mock
-    private lateinit var mockHandler: Handler
-    @Mock
     private lateinit var mPendingResult: BroadcastReceiver.PendingResult
 
     @Captor
@@ -81,12 +81,14 @@
     private lateinit var intentFilter: IntentFilter
     private lateinit var intentFilterOther: IntentFilter
     private lateinit var handler: Handler
+    private lateinit var fakeExecutor: FakeExecutor
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
         handler = Handler(testableLooper.looper)
+        fakeExecutor = FakeExecutor(FakeSystemClock())
 
         userBroadcastDispatcher = UserBroadcastDispatcher(
                 mockContext, USER_ID, handler, testableLooper.looper)
@@ -108,7 +110,7 @@
         intentFilter = IntentFilter(ACTION_1)
 
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, mockHandler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
         testableLooper.processAllMessages()
 
         assertTrue(userBroadcastDispatcher.isRegistered())
@@ -128,7 +130,7 @@
         intentFilter = IntentFilter(ACTION_1)
 
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, mockHandler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
         testableLooper.processAllMessages()
         reset(mockContext)
 
@@ -151,9 +153,9 @@
         }
 
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, mockHandler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiverOther, intentFilterOther, mockHandler, USER_HANDLE))
+                ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE))
 
         testableLooper.processAllMessages()
         assertTrue(userBroadcastDispatcher.isRegistered())
@@ -179,14 +181,15 @@
         intentFilterOther = IntentFilter(ACTION_2)
 
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiverOther, intentFilterOther, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE))
 
         val intent = Intent(ACTION_2)
 
         userBroadcastDispatcher.onReceive(mockContext, intent)
         testableLooper.processAllMessages()
+        fakeExecutor.runAllReady()
 
         verify(broadcastReceiver, never()).onReceive(any(), any())
         verify(broadcastReceiverOther).onReceive(mockContext, intent)
@@ -198,14 +201,15 @@
         intentFilterOther = IntentFilter(ACTION_2)
 
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilterOther, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilterOther, fakeExecutor, USER_HANDLE))
 
         val intent = Intent(ACTION_2)
 
         userBroadcastDispatcher.onReceive(mockContext, intent)
         testableLooper.processAllMessages()
+        fakeExecutor.runAllReady()
 
         verify(broadcastReceiver).onReceive(mockContext, intent)
     }
@@ -218,14 +222,15 @@
         intentFilterOther.addCategory(CATEGORY_2)
 
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiverOther, intentFilterOther, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE))
 
         val intent = Intent(ACTION_1)
 
         userBroadcastDispatcher.onReceive(mockContext, intent)
         testableLooper.processAllMessages()
+        fakeExecutor.runAllReady()
 
         verify(broadcastReceiver).onReceive(mockContext, intent)
         verify(broadcastReceiverOther).onReceive(mockContext, intent)
@@ -235,12 +240,13 @@
     fun testPendingResult() {
         intentFilter = IntentFilter(ACTION_1)
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
 
         val intent = Intent(ACTION_1)
         userBroadcastDispatcher.onReceive(mockContext, intent)
 
         testableLooper.processAllMessages()
+        fakeExecutor.runAllReady()
 
         verify(broadcastReceiver).onReceive(mockContext, intent)
         verify(broadcastReceiver).pendingResult = mPendingResult
@@ -250,15 +256,16 @@
     fun testRemoveReceiverReferences() {
         intentFilter = IntentFilter(ACTION_1)
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
 
         intentFilterOther = IntentFilter(ACTION_1)
         intentFilterOther.addAction(ACTION_2)
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiverOther, intentFilterOther, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE))
 
         userBroadcastDispatcher.unregisterReceiver(broadcastReceiver)
         testableLooper.processAllMessages()
+        fakeExecutor.runAllReady()
 
         assertFalse(userBroadcastDispatcher.isReceiverReferenceHeld(broadcastReceiver))
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index e0b4b81..2c9058a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -70,12 +70,14 @@
 import com.android.systemui.statusbar.notification.NotificationFilter;
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.StatusBarWindowController;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -126,7 +128,7 @@
     private ArgumentCaptor<NotificationRemoveInterceptor> mRemoveInterceptorCaptor;
 
     private TestableBubbleController mBubbleController;
-    private StatusBarWindowController mStatusBarWindowController;
+    private NotificationShadeWindowController mNotificationShadeWindowController;
     private NotificationEntryListener mEntryListener;
     private NotificationRemoveInterceptor mRemoveInterceptor;
 
@@ -151,6 +153,8 @@
     private ShadeController mShadeController;
     @Mock
     private RemoteInputUriController mRemoteInputUriController;
+    @Mock
+    private NotificationRowComponent mNotificationRowComponent;
 
     private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
     private BubbleData mBubbleData;
@@ -167,14 +171,26 @@
         when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
 
         mSuperStatusBarViewFactory = new SuperStatusBarViewFactory(mContext,
-                new InjectionInflationController(SystemUIFactory.getInstance().getRootComponent()));
+                new InjectionInflationController(SystemUIFactory.getInstance().getRootComponent()),
+                new NotificationRowComponent.Builder() {
+                    @Override
+                    public NotificationRowComponent.Builder activatableNotificationView(
+                            ActivatableNotificationView view) {
+                        return this;
+                    }
+
+                    @Override
+                    public NotificationRowComponent build() {
+                        return mNotificationRowComponent;
+                    }
+                });
 
         // Bubbles get added to status bar window view
-        mStatusBarWindowController = new StatusBarWindowController(mContext, mWindowManager,
-                mActivityManager, mDozeParameters, mStatusBarStateController,
+        mNotificationShadeWindowController = new NotificationShadeWindowController(mContext,
+                mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
                 mConfigurationController, mKeyguardBypassController, mColorExtractor,
-                mSuperStatusBarViewFactory, mResources);
-        mStatusBarWindowController.attach();
+                mSuperStatusBarViewFactory);
+        mNotificationShadeWindowController.attach();
 
         // Need notifications for bubbles
         mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency);
@@ -201,7 +217,7 @@
                 mock(NotificationInterruptionStateProvider.HeadsUpSuppressor.class));
         mBubbleData = new BubbleData(mContext);
         mBubbleController = new TestableBubbleController(mContext,
-                mStatusBarWindowController,
+                mNotificationShadeWindowController,
                 mStatusBarStateController,
                 mShadeController,
                 mBubbleData,
@@ -250,7 +266,7 @@
 
         mBubbleController.removeBubble(
                 mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
-        assertFalse(mStatusBarWindowController.getBubblesShowing());
+        assertFalse(mNotificationShadeWindowController.getBubblesShowing());
         assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
         verify(mNotificationEntryManager, times(2)).updateNotifications(anyString());
         verify(mBubbleStateChangeListener).onHasBubblesChanged(false);
@@ -290,7 +306,7 @@
         assertTrue(mBubbleController.hasBubbles());
 
         mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
-        assertFalse(mStatusBarWindowController.getBubblesShowing());
+        assertFalse(mNotificationShadeWindowController.getBubblesShowing());
         verify(mNotificationEntryManager, times(3)).updateNotifications(any());
         assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
         assertNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().getKey()));
@@ -308,14 +324,14 @@
         assertTrue(mBubbleController.hasBubbles());
         assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
                 mRow.getEntry().getKey()));
-        assertFalse(mStatusBarWindowController.getBubbleExpanded());
+        assertFalse(mNotificationShadeWindowController.getBubbleExpanded());
 
         // Expand the stack
         BubbleStackView stackView = mBubbleController.getStackView();
         mBubbleController.expandStack();
         assertTrue(mBubbleController.isStackExpanded());
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
-        assertTrue(mStatusBarWindowController.getBubbleExpanded());
+        assertTrue(mNotificationShadeWindowController.getBubbleExpanded());
 
         // Make sure the notif is suppressed
         assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
@@ -325,7 +341,7 @@
         mBubbleController.collapseStack();
         verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey());
         assertFalse(mBubbleController.isStackExpanded());
-        assertFalse(mStatusBarWindowController.getBubbleExpanded());
+        assertFalse(mNotificationShadeWindowController.getBubbleExpanded());
     }
 
     @Test
@@ -356,8 +372,7 @@
 
         // Switch which bubble is expanded
         mBubbleController.selectBubble(mRow.getEntry().getKey());
-        stackView.setExpandedBubble(mRow.getEntry().getKey());
-        assertEquals(mRow.getEntry(), stackView.getExpandedBubble().getEntry());
+        mBubbleController.expandStack();
         assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
                 mRow.getEntry().getKey()));
 
@@ -712,7 +727,7 @@
     static class TestableBubbleController extends BubbleController {
         // Let's assume surfaces can be synchronized immediately.
         TestableBubbleController(Context context,
-                StatusBarWindowController statusBarWindowController,
+                NotificationShadeWindowController notificationShadeWindowController,
                 StatusBarStateController statusBarStateController,
                 ShadeController shadeController,
                 BubbleData data,
@@ -724,7 +739,7 @@
                 NotificationEntryManager entryManager,
                 RemoteInputUriController remoteInputUriController) {
             super(context,
-                    statusBarWindowController, statusBarStateController, shadeController,
+                    notificationShadeWindowController, statusBarStateController, shadeController,
                     data, Runnable::run, configurationController, interruptionStateProvider,
                     zenModeController, lockscreenUserManager, groupManager, entryManager,
                     remoteInputUriController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
index c4ae409..b09603d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
@@ -228,7 +228,7 @@
     }
 
     @Test
-    public void sameUpdate_NotInShade_showFlyout() {
+    public void sameUpdate_NotInShade_NotVisuallyInterruptive_dontShowFlyout() {
         // Setup
         mBubbleData.setListener(mListener);
 
@@ -247,7 +247,7 @@
 
         // Verify
         BubbleData.Update update = mUpdateCaptor.getValue();
-        assertThat(update.updatedBubble.showFlyout()).isTrue();
+        assertThat(update.updatedBubble.showFlyout()).isFalse();
     }
 
     // COLLAPSED / ADD
@@ -952,9 +952,8 @@
             long postTime) {
         // BubbleMetadata
         Notification.BubbleMetadata bubbleMetadata = new Notification.BubbleMetadata.Builder()
-                .setIntent(mExpandIntent)
+                .createIntentBubble(mExpandIntent, Icon.createWithResource("", 0))
                 .setDeleteIntent(mDeleteIntent)
-                .setIcon(Icon.createWithResource("", 0))
                 .build();
         // Notification -> BubbleMetadata
         Notification notification = mNotificationTestHelper.createNotification(false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java
index 376ecf7..fd6e2ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java
@@ -45,14 +45,22 @@
 public class BubbleFlyoutViewTest extends SysuiTestCase {
     private BubbleFlyoutView mFlyout;
     private TextView mFlyoutText;
+    private TextView mSenderName;
     private float[] mDotCenter = new float[2];
+    private Bubble.FlyoutMessage mFlyoutMessage;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+
+        mFlyoutMessage = new Bubble.FlyoutMessage();
+        mFlyoutMessage.senderName = "Josh";
+        mFlyoutMessage.message = "Hello";
+
         mFlyout = new BubbleFlyoutView(getContext());
 
         mFlyoutText = mFlyout.findViewById(R.id.bubble_flyout_text);
+        mSenderName = mFlyout.findViewById(R.id.bubble_flyout_name);
         mDotCenter[0] = 30;
         mDotCenter[1] = 30;
     }
@@ -60,19 +68,21 @@
     @Test
     public void testShowFlyout_isVisible() {
         mFlyout.setupFlyoutStartingAsDot(
-                "Hello", new PointF(100, 100), 500, true, Color.WHITE, null, null, mDotCenter,
+                mFlyoutMessage,
+                new PointF(100, 100), 500, true, Color.WHITE, null, null, mDotCenter,
                 false);
         mFlyout.setVisibility(View.VISIBLE);
 
         assertEquals("Hello", mFlyoutText.getText());
+        assertEquals("Josh", mSenderName.getText());
         assertEquals(View.VISIBLE, mFlyout.getVisibility());
     }
 
     @Test
     public void testFlyoutHide_runsCallback() {
         Runnable after = Mockito.mock(Runnable.class);
-        mFlyout.setupFlyoutStartingAsDot(
-                "Hello", new PointF(100, 100), 500, true, Color.WHITE, null, after, mDotCenter,
+        mFlyout.setupFlyoutStartingAsDot(mFlyoutMessage,
+                new PointF(100, 100), 500, true, Color.WHITE, null, after, mDotCenter,
                 false);
         mFlyout.hideFlyout();
 
@@ -81,8 +91,8 @@
 
     @Test
     public void testSetCollapsePercent() {
-        mFlyout.setupFlyoutStartingAsDot(
-                "Hello", new PointF(100, 100), 500, true, Color.WHITE, null, null, mDotCenter,
+        mFlyout.setupFlyoutStartingAsDot(mFlyoutMessage,
+                new PointF(100, 100), 500, true, Color.WHITE, null, null, mDotCenter,
                 false);
         mFlyout.setVisibility(View.VISIBLE);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java
index 3c42fd1..02f721c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java
@@ -45,7 +45,6 @@
     private Notification mNotif;
 
     private NotificationEntry mEntry;
-    private Bubble mBubble;
     private Bundle mExtras;
 
     @Before
@@ -58,7 +57,6 @@
         mEntry = new NotificationEntryBuilder()
                 .setNotification(mNotif)
                 .build();
-        mBubble = new Bubble(mEntry);
     }
 
     @Test
@@ -66,7 +64,8 @@
         final String msg = "Hello there!";
         doReturn(Notification.Style.class).when(mNotif).getNotificationStyle();
         mExtras.putCharSequence(Notification.EXTRA_TEXT, msg);
-        assertEquals(msg, mBubble.getUpdateMessage(mContext));
+        assertEquals(msg, BubbleViewInfoTask.extractFlyoutMessage(mContext,
+                mEntry).message);
     }
 
     @Test
@@ -77,7 +76,8 @@
         mExtras.putCharSequence(Notification.EXTRA_BIG_TEXT, msg);
 
         // Should be big text, not the small text.
-        assertEquals(msg, mBubble.getUpdateMessage(mContext));
+        assertEquals(msg, BubbleViewInfoTask.extractFlyoutMessage(mContext,
+                mEntry).message);
     }
 
     @Test
@@ -85,7 +85,8 @@
         doReturn(Notification.MediaStyle.class).when(mNotif).getNotificationStyle();
 
         // Media notifs don't get update messages.
-        assertNull(mBubble.getUpdateMessage(mContext));
+        assertNull(BubbleViewInfoTask.extractFlyoutMessage(mContext,
+                mEntry).message);
     }
 
     @Test
@@ -100,7 +101,8 @@
                         "Really? I prefer them that way."});
 
         // Should be the last one only.
-        assertEquals("Really? I prefer them that way.", mBubble.getUpdateMessage(mContext));
+        assertEquals("Really? I prefer them that way.",
+                BubbleViewInfoTask.extractFlyoutMessage(mContext, mEntry).message);
     }
 
     @Test
@@ -115,6 +117,10 @@
                                 "Oh, hello!", 0, "Mady").toBundle()});
 
         // Should be the last one only.
-        assertEquals("Mady: Oh, hello!", mBubble.getUpdateMessage(mContext));
+        assertEquals("Oh, hello!",
+                BubbleViewInfoTask.extractFlyoutMessage(mContext, mEntry).message);
+        assertEquals("Mady",
+                BubbleViewInfoTask.extractFlyoutMessage(mContext,
+                        mEntry).senderName);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
new file mode 100644
index 0000000..7c8c7c8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
@@ -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 com.android.systemui.controls.controller
+
+import android.content.ComponentName
+import android.content.Context
+import android.os.Binder
+import android.service.controls.Control
+import android.service.controls.DeviceTypes
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import dagger.Lazy
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsBindingControllerTest : SysuiTestCase() {
+
+    companion object {
+        fun <T> any(): T = Mockito.any<T>()
+        private val TEST_COMPONENT_NAME_1 = ComponentName("TEST_PKG", "TEST_CLS_1")
+        private val TEST_COMPONENT_NAME_2 = ComponentName("TEST_PKG", "TEST_CLS_2")
+        private val TEST_COMPONENT_NAME_3 = ComponentName("TEST_PKG", "TEST_CLS_3")
+    }
+
+    @Mock
+    private lateinit var mockControlsController: ControlsController
+
+    private val executor = FakeExecutor(FakeSystemClock())
+    private lateinit var controller: ControlsBindingController
+    private val providers = TestableControlsBindingControllerImpl.providers
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        controller = TestableControlsBindingControllerImpl(
+                mContext, executor, Lazy { mockControlsController })
+    }
+
+    @After
+    fun tearDown() {
+        executor.advanceClockToLast()
+        executor.runAllReady()
+        providers.clear()
+    }
+
+    @Test
+    fun testBindAndLoad() {
+        val callback: (List<Control>) -> Unit = {}
+        controller.bindAndLoad(TEST_COMPONENT_NAME_1, callback)
+
+        assertEquals(1, providers.size)
+        val provider = providers.first()
+        verify(provider).maybeBindAndLoad(callback)
+    }
+
+    @Test
+    fun testBindServices() {
+        controller.bindServices(listOf(TEST_COMPONENT_NAME_1, TEST_COMPONENT_NAME_2))
+        executor.runAllReady()
+
+        assertEquals(2, providers.size)
+        assertEquals(setOf(TEST_COMPONENT_NAME_1, TEST_COMPONENT_NAME_2),
+                providers.map { it.componentName }.toSet())
+        providers.forEach {
+            verify(it).bindPermanently()
+        }
+    }
+
+    @Test
+    fun testSubscribe() {
+        val controlInfo1 = ControlInfo(TEST_COMPONENT_NAME_1, "id_1", "", DeviceTypes.TYPE_UNKNOWN)
+        val controlInfo2 = ControlInfo(TEST_COMPONENT_NAME_2, "id_2", "", DeviceTypes.TYPE_UNKNOWN)
+        controller.bindServices(listOf(TEST_COMPONENT_NAME_3))
+
+        controller.subscribe(listOf(controlInfo1, controlInfo2))
+
+        executor.runAllReady()
+
+        assertEquals(3, providers.size)
+        val provider1 = providers.first { it.componentName == TEST_COMPONENT_NAME_1 }
+        val provider2 = providers.first { it.componentName == TEST_COMPONENT_NAME_2 }
+        val provider3 = providers.first { it.componentName == TEST_COMPONENT_NAME_3 }
+
+        verify(provider1).maybeBindAndSubscribe(listOf(controlInfo1.controlId))
+        verify(provider2).maybeBindAndSubscribe(listOf(controlInfo2.controlId))
+        verify(provider3, never()).maybeBindAndSubscribe(any())
+        verify(provider3).unbindService() // Not needed services will be unbound
+    }
+
+    @Test
+    fun testUnsubscribe_notRefreshing() {
+        controller.bindServices(listOf(TEST_COMPONENT_NAME_1, TEST_COMPONENT_NAME_2))
+        controller.unsubscribe()
+
+        executor.runAllReady()
+
+        providers.forEach {
+            verify(it, never()).unsubscribe()
+        }
+    }
+
+    @Test
+    fun testUnsubscribe_refreshing() {
+        val controlInfo1 = ControlInfo(TEST_COMPONENT_NAME_1, "id_1", "", DeviceTypes.TYPE_UNKNOWN)
+        val controlInfo2 = ControlInfo(TEST_COMPONENT_NAME_2, "id_2", "", DeviceTypes.TYPE_UNKNOWN)
+
+        controller.subscribe(listOf(controlInfo1, controlInfo2))
+
+        controller.unsubscribe()
+
+        executor.runAllReady()
+
+        providers.forEach {
+            verify(it).unsubscribe()
+        }
+    }
+}
+
+class TestableControlsBindingControllerImpl(
+    context: Context,
+    executor: DelayableExecutor,
+    lazyController: Lazy<ControlsController>
+) : ControlsBindingControllerImpl(context, executor, lazyController) {
+
+    companion object {
+        val providers = mutableSetOf<ControlsProviderLifecycleManager>()
+    }
+
+    override fun createProviderManager(component: ComponentName):
+            ControlsProviderLifecycleManager {
+        val provider = mock(ControlsProviderLifecycleManager::class.java)
+        val token = Binder()
+        `when`(provider.componentName).thenReturn(component)
+        `when`(provider.token).thenReturn(token)
+        providers.add(provider)
+        return provider
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
new file mode 100644
index 0000000..a19c299
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -0,0 +1,360 @@
+/*
+ * 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.controls.controller
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.provider.Settings
+import android.service.controls.Control
+import android.service.controls.DeviceTypes
+import android.service.controls.actions.ControlAction
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.DumpController
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlStatus
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.util.Optional
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsControllerImplTest : SysuiTestCase() {
+
+    @Mock
+    private lateinit var uiController: ControlsUiController
+    @Mock
+    private lateinit var bindingController: ControlsBindingController
+    @Mock
+    private lateinit var dumpController: DumpController
+    @Mock
+    private lateinit var pendingIntent: PendingIntent
+    @Mock
+    private lateinit var persistenceWrapper: ControlsFavoritePersistenceWrapper
+
+    @Captor
+    private lateinit var controlInfoListCaptor: ArgumentCaptor<List<ControlInfo>>
+    @Captor
+    private lateinit var controlLoadCallbackCaptor: ArgumentCaptor<(List<Control>) -> Unit>
+
+    private lateinit var delayableExecutor: FakeExecutor
+    private lateinit var controller: ControlsController
+
+    companion object {
+        fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+        fun <T : Any> safeEq(value: T): T = eq(value) ?: value
+
+        private val TEST_COMPONENT = ComponentName("test.pkg", "test.class")
+        private const val TEST_CONTROL_ID = "control1"
+        private const val TEST_CONTROL_TITLE = "Test"
+        private const val TEST_DEVICE_TYPE = DeviceTypes.TYPE_AC_HEATER
+        private val TEST_CONTROL_INFO = ControlInfo(
+                TEST_COMPONENT, TEST_CONTROL_ID, TEST_CONTROL_TITLE, TEST_DEVICE_TYPE)
+
+        private val TEST_COMPONENT_2 = ComponentName("test.pkg", "test.class.2")
+        private const val TEST_CONTROL_ID_2 = "control2"
+        private const val TEST_CONTROL_TITLE_2 = "Test 2"
+        private const val TEST_DEVICE_TYPE_2 = DeviceTypes.TYPE_CAMERA
+        private val TEST_CONTROL_INFO_2 = ControlInfo(
+                TEST_COMPONENT_2, TEST_CONTROL_ID_2, TEST_CONTROL_TITLE_2, TEST_DEVICE_TYPE_2)
+    }
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        Settings.Secure.putInt(mContext.contentResolver,
+                ControlsControllerImpl.CONTROLS_AVAILABLE, 1)
+
+        delayableExecutor = FakeExecutor(FakeSystemClock())
+
+        controller = ControlsControllerImpl(
+                mContext,
+                delayableExecutor,
+                uiController,
+                bindingController,
+                Optional.of(persistenceWrapper),
+                dumpController
+        )
+        assertTrue(controller.available)
+    }
+
+    private fun builderFromInfo(controlInfo: ControlInfo): Control.StatelessBuilder {
+        return Control.StatelessBuilder(controlInfo.controlId, pendingIntent)
+                .setDeviceType(controlInfo.deviceType).setTitle(controlInfo.controlTitle)
+    }
+
+    @Test
+    fun testStartWithoutFavorites() {
+        assertTrue(controller.getFavoriteControls().isEmpty())
+    }
+
+    @Test
+    fun testStartWithSavedFavorites() {
+        `when`(persistenceWrapper.readFavorites()).thenReturn(listOf(TEST_CONTROL_INFO))
+        val controller_other = ControlsControllerImpl(
+                mContext,
+                delayableExecutor,
+                uiController,
+                bindingController,
+                Optional.of(persistenceWrapper),
+                dumpController
+        )
+        assertEquals(listOf(TEST_CONTROL_INFO), controller_other.getFavoriteControls())
+    }
+
+    @Test
+    fun testAddFavorite() {
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+        val favorites = controller.getFavoriteControls()
+        assertTrue(TEST_CONTROL_INFO in favorites)
+        assertEquals(1, favorites.size)
+    }
+
+    @Test
+    fun testAddMultipleFavorites() {
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true)
+
+        val favorites = controller.getFavoriteControls()
+        assertTrue(TEST_CONTROL_INFO in favorites)
+        assertTrue(TEST_CONTROL_INFO_2 in favorites)
+        assertEquals(2, favorites.size)
+    }
+
+    @Test
+    fun testAddAndRemoveFavorite() {
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true)
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, false)
+
+        val favorites = controller.getFavoriteControls()
+        assertTrue(TEST_CONTROL_INFO !in favorites)
+        assertTrue(TEST_CONTROL_INFO_2 in favorites)
+        assertEquals(1, favorites.size)
+    }
+
+    @Test
+    fun testFavoritesSavedOnAdd() {
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+        verify(persistenceWrapper).storeFavorites(listOf(TEST_CONTROL_INFO))
+    }
+
+    @Test
+    fun testFavoritesSavedOnRemove() {
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+        reset(persistenceWrapper)
+
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, false)
+        verify(persistenceWrapper).storeFavorites(emptyList())
+    }
+
+    @Test
+    fun testFavoritesSavedOnChange() {
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+        val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
+        val control = builderFromInfo(newControlInfo).build()
+
+        controller.loadForComponent(TEST_COMPONENT) {}
+
+        reset(persistenceWrapper)
+        verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+                capture(controlLoadCallbackCaptor))
+
+        controlLoadCallbackCaptor.value.invoke(listOf(control))
+
+        verify(persistenceWrapper).storeFavorites(listOf(newControlInfo))
+    }
+
+    @Test
+    fun testFavoritesNotSavedOnRedundantAdd() {
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+        reset(persistenceWrapper)
+
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+        verify(persistenceWrapper, never()).storeFavorites(ArgumentMatchers.anyList())
+    }
+
+    @Test
+    fun testFavoritesNotSavedOnNotRemove() {
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, false)
+        verify(persistenceWrapper, never()).storeFavorites(ArgumentMatchers.anyList())
+    }
+
+    @Test
+    fun testOnActionResponse() {
+        controller.onActionResponse(TEST_COMPONENT, TEST_CONTROL_ID, ControlAction.RESPONSE_OK)
+
+        verify(uiController).onActionResponse(TEST_COMPONENT, TEST_CONTROL_ID,
+                ControlAction.RESPONSE_OK)
+    }
+
+    @Test
+    fun testRefreshStatus() {
+        val list = listOf(Control.StatefulBuilder(TEST_CONTROL_ID, pendingIntent).build())
+        controller.refreshStatus(TEST_COMPONENT, list)
+
+        verify(uiController).onRefreshState(TEST_COMPONENT, list)
+    }
+
+    @Test
+    fun testUnsubscribe() {
+        controller.unsubscribe()
+        verify(bindingController).unsubscribe()
+    }
+
+    @Test
+    fun testSubscribeFavorites() {
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true)
+
+        controller.subscribeToFavorites()
+
+        verify(bindingController).subscribe(capture(controlInfoListCaptor))
+
+        assertTrue(TEST_CONTROL_INFO in controlInfoListCaptor.value)
+        assertTrue(TEST_CONTROL_INFO_2 in controlInfoListCaptor.value)
+    }
+
+    @Test
+    fun testLoadForComponent_noFavorites() {
+        var loaded = false
+        val control = builderFromInfo(TEST_CONTROL_INFO).build()
+
+        controller.loadForComponent(TEST_COMPONENT) {
+            loaded = true
+            assertEquals(1, it.size)
+            val controlStatus = it[0]
+            assertEquals(ControlStatus(control, false), controlStatus)
+        }
+
+        verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+                capture(controlLoadCallbackCaptor))
+
+        controlLoadCallbackCaptor.value.invoke(listOf(control))
+
+        assertTrue(loaded)
+    }
+
+    @Test
+    fun testLoadForComponent_favorites() {
+        var loaded = false
+        val control = builderFromInfo(TEST_CONTROL_INFO).build()
+        val control2 = builderFromInfo(TEST_CONTROL_INFO_2).build()
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+        controller.loadForComponent(TEST_COMPONENT) {
+            loaded = true
+            assertEquals(2, it.size)
+            val controlStatus = it.first { it.control.controlId == TEST_CONTROL_ID }
+            assertEquals(ControlStatus(control, true), controlStatus)
+
+            val controlStatus2 = it.first { it.control.controlId == TEST_CONTROL_ID_2 }
+            assertEquals(ControlStatus(control2, false), controlStatus2)
+        }
+
+        verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+                capture(controlLoadCallbackCaptor))
+
+        controlLoadCallbackCaptor.value.invoke(listOf(control, control2))
+
+        assertTrue(loaded)
+    }
+
+    @Test
+    fun testLoadForComponent_removed() {
+        var loaded = false
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+        controller.loadForComponent(TEST_COMPONENT) {
+            loaded = true
+            assertEquals(1, it.size)
+            val controlStatus = it[0]
+            assertEquals(TEST_CONTROL_ID, controlStatus.control.controlId)
+            assertTrue(controlStatus.favorite)
+            assertTrue(controlStatus.removed)
+        }
+
+        verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+                capture(controlLoadCallbackCaptor))
+
+        controlLoadCallbackCaptor.value.invoke(emptyList())
+
+        assertTrue(loaded)
+    }
+
+    @Test
+    fun testFavoriteInformationModifiedOnLoad() {
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+        val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
+        val control = builderFromInfo(newControlInfo).build()
+
+        controller.loadForComponent(TEST_COMPONENT) {}
+
+        verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+                capture(controlLoadCallbackCaptor))
+
+        controlLoadCallbackCaptor.value.invoke(listOf(control))
+
+        val favorites = controller.getFavoriteControls()
+        assertEquals(1, favorites.size)
+        assertEquals(newControlInfo, favorites[0])
+    }
+
+    @Test
+    fun testFavoriteInformationModifiedOnRefresh() {
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+        val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
+        val control = builderFromInfo(newControlInfo).build()
+
+        controller.refreshStatus(TEST_COMPONENT, listOf(control))
+
+        delayableExecutor.runAllReady()
+
+        val favorites = controller.getFavoriteControls()
+        assertEquals(1, favorites.size)
+        assertEquals(newControlInfo, favorites[0])
+    }
+
+    @Test
+    fun testClearFavorites() {
+        controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+        assertEquals(1, controller.getFavoriteControls().size)
+
+        controller.clearFavorites()
+        assertTrue(controller.getFavoriteControls().isEmpty())
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt
new file mode 100644
index 0000000..c145c1f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.controls.controller
+
+import android.content.ComponentName
+import android.service.controls.DeviceTypes
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.io.File
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsFavoritePersistenceWrapperTest : SysuiTestCase() {
+
+    private lateinit var file: File
+
+    private val executor = FakeExecutor(FakeSystemClock())
+
+    private lateinit var wrapper: ControlsFavoritePersistenceWrapper
+
+    @Before
+    fun setUp() {
+        file = File.createTempFile("controls_favorites", ".temp")
+        wrapper = ControlsFavoritePersistenceWrapper(file, executor)
+    }
+
+    @After
+    fun tearDown() {
+        if (file.exists() ?: false) {
+            file.delete()
+        }
+    }
+
+    @Test
+    fun testSaveAndRestore() {
+        val controlInfo1 = ControlInfo(
+                ComponentName.unflattenFromString("TEST_PKG/.TEST_CLS_1")!!,
+                "id1", "name_1", DeviceTypes.TYPE_UNKNOWN)
+        val controlInfo2 = ControlInfo(
+                ComponentName.unflattenFromString("TEST_PKG/.TEST_CLS_2")!!,
+                "id2", "name_2", DeviceTypes.TYPE_GENERIC_ON_OFF)
+        val list = listOf(controlInfo1, controlInfo2)
+
+        wrapper.storeFavorites(list)
+
+        executor.runAllReady()
+
+        assertEquals(list, wrapper.readFavorites())
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
new file mode 100644
index 0000000..556bb40
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
@@ -0,0 +1,138 @@
+/*
+ * 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.controls.controller
+
+import android.content.ComponentName
+import android.service.controls.Control
+import android.service.controls.IControlsProvider
+import android.service.controls.IControlsProviderCallback
+import android.service.controls.actions.ControlAction
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsProviderLifecycleManagerTest : SysuiTestCase() {
+
+    @Mock
+    private lateinit var serviceCallback: IControlsProviderCallback.Stub
+    @Mock
+    private lateinit var service: IControlsProvider.Stub
+
+    private val componentName = ComponentName("test.pkg", "test.cls")
+    private lateinit var manager: ControlsProviderLifecycleManager
+    private lateinit var executor: DelayableExecutor
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        mContext.addMockService(componentName, service)
+        executor = FakeExecutor(FakeSystemClock())
+        `when`(service.asBinder()).thenCallRealMethod()
+        `when`(service.queryLocalInterface(ArgumentMatchers.anyString())).thenReturn(service)
+
+        manager = ControlsProviderLifecycleManager(
+                context,
+                executor,
+                serviceCallback,
+                componentName
+        )
+    }
+
+    @After
+    fun tearDown() {
+        manager.unbindService()
+    }
+
+    @Test
+    fun testBindService() {
+        manager.bindPermanently()
+        assertTrue(mContext.isBound(componentName))
+    }
+
+    @Test
+    fun testUnbindService() {
+        manager.bindPermanently()
+        manager.unbindService()
+        assertFalse(mContext.isBound(componentName))
+    }
+
+    @Test
+    fun testMaybeBindAndLoad() {
+        val callback: (List<Control>) -> Unit = {}
+        manager.maybeBindAndLoad(callback)
+
+        verify(service).load()
+
+        assertTrue(mContext.isBound(componentName))
+        assertEquals(callback, manager.lastLoadCallback)
+    }
+
+    @Test
+    fun testMaybeUnbind_bindingAndCallback() {
+        manager.maybeBindAndLoad {}
+
+        manager.maybeUnbindAndRemoveCallback()
+        assertFalse(mContext.isBound(componentName))
+        assertNull(manager.lastLoadCallback)
+    }
+
+    @Test
+    fun testUnsubscribe() {
+        manager.bindPermanently()
+        manager.unsubscribe()
+
+        verify(service).unsubscribe()
+    }
+
+    @Test
+    fun testMaybeBindAndSubscribe() {
+        val list = listOf("TEST_ID")
+        manager.maybeBindAndSubscribe(list)
+
+        assertTrue(mContext.isBound(componentName))
+        verify(service).subscribe(list)
+    }
+
+    @Test
+    fun testMaybeBindAndAction() {
+        val controlId = "TEST_ID"
+        val action = ControlAction.UNKNOWN_ACTION
+        manager.maybeBindAndSendAction(controlId, action)
+
+        assertTrue(mContext.isBound(componentName))
+        verify(service).onAction(controlId, action)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapperTest.kt
new file mode 100644
index 0000000..d6993c0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapperTest.kt
@@ -0,0 +1,127 @@
+/*
+ * 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.controls.controller
+
+import android.os.RemoteException
+import android.service.controls.IControlsProvider
+import android.service.controls.actions.ControlAction
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+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.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsProviderServiceWrapperTest : SysuiTestCase() {
+
+    @Mock
+    private lateinit var service: IControlsProvider
+
+    private val exception = RemoteException()
+
+    private lateinit var wrapper: ControlsProviderServiceWrapper
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        wrapper = ControlsProviderServiceWrapper(service)
+    }
+
+    @Test
+    fun testLoad_happyPath() {
+        val result = wrapper.load()
+
+        assertTrue(result)
+        verify(service).load()
+    }
+
+    @Test
+    fun testLoad_error() {
+        `when`(service.load()).thenThrow(exception)
+        val result = wrapper.load()
+
+        assertFalse(result)
+    }
+
+    @Test
+    fun testSubscribe_happyPath() {
+        val list = listOf("TEST_ID")
+        val result = wrapper.subscribe(list)
+
+        assertTrue(result)
+        verify(service).subscribe(list)
+    }
+
+    @Test
+    fun testSubscribe_error() {
+        `when`(service.subscribe(any())).thenThrow(exception)
+
+        val list = listOf("TEST_ID")
+        val result = wrapper.subscribe(list)
+
+        assertFalse(result)
+    }
+
+    @Test
+    fun testUnsubscribe_happyPath() {
+        val result = wrapper.unsubscribe()
+
+        assertTrue(result)
+        verify(service).unsubscribe()
+    }
+
+    @Test
+    fun testUnsubscribe_error() {
+        `when`(service.unsubscribe()).thenThrow(exception)
+        val result = wrapper.unsubscribe()
+
+        assertFalse(result)
+    }
+
+    @Test
+    fun testOnAction_happyPath() {
+        val id = "TEST_ID"
+        val action = ControlAction.UNKNOWN_ACTION
+
+        val result = wrapper.onAction(id, action)
+
+        assertTrue(result)
+        verify(service).onAction(id, action)
+    }
+
+    @Test
+    fun testOnAction_error() {
+        `when`(service.onAction(any(), any())).thenThrow(exception)
+
+        val id = "TEST_ID"
+        val action = ControlAction.UNKNOWN_ACTION
+
+        val result = wrapper.onAction(id, action)
+
+        assertFalse(result)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
new file mode 100644
index 0000000..f09aab9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -0,0 +1,188 @@
+/*
+ * 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.controls.management
+
+import android.content.ComponentName
+import android.content.pm.ServiceInfo
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.settingslib.applications.ServiceListing
+import com.android.settingslib.widget.CandidateInfo
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsListingControllerImplTest : SysuiTestCase() {
+
+    companion object {
+        private const val TEST_LABEL = "TEST_LABEL"
+        private const val TEST_PERMISSION = "permission"
+        fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+        fun <T> any(): T = Mockito.any<T>()
+    }
+
+    @Mock
+    private lateinit var mockSL: ServiceListing
+    @Mock
+    private lateinit var mockCallback: ControlsListingController.ControlsListingCallback
+    @Mock
+    private lateinit var mockCallbackOther: ControlsListingController.ControlsListingCallback
+    @Mock
+    private lateinit var serviceInfo: ServiceInfo
+    @Mock
+    private lateinit var componentName: ComponentName
+
+    private val executor = FakeExecutor(FakeSystemClock())
+
+    private lateinit var controller: ControlsListingControllerImpl
+
+    private var serviceListingCallbackCaptor =
+            ArgumentCaptor.forClass(ServiceListing.Callback::class.java)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        `when`(serviceInfo.componentName).thenReturn(componentName)
+
+        controller = ControlsListingControllerImpl(mContext, executor, mockSL)
+        verify(mockSL).addCallback(capture(serviceListingCallbackCaptor))
+    }
+
+    @After
+    fun tearDown() {
+        executor.advanceClockToLast()
+        executor.runAllReady()
+    }
+
+    @Test
+    fun testNoServices_notListening() {
+        assertTrue(controller.getCurrentServices().isEmpty())
+    }
+
+    @Test
+    fun testStartListening_onFirstCallback() {
+        controller.addCallback(mockCallback)
+        executor.runAllReady()
+
+        verify(mockSL).setListening(true)
+    }
+
+    @Test
+    fun testStartListening_onlyOnce() {
+        controller.addCallback(mockCallback)
+        controller.addCallback(mockCallbackOther)
+
+        executor.runAllReady()
+
+        verify(mockSL).setListening(true)
+    }
+
+    @Test
+    fun testStopListening_callbackRemoved() {
+        controller.addCallback(mockCallback)
+
+        executor.runAllReady()
+
+        controller.removeCallback(mockCallback)
+
+        executor.runAllReady()
+
+        verify(mockSL).setListening(false)
+    }
+
+    @Test
+    fun testStopListening_notWhileRemainingCallbacks() {
+        controller.addCallback(mockCallback)
+        controller.addCallback(mockCallbackOther)
+
+        executor.runAllReady()
+
+        controller.removeCallback(mockCallback)
+
+        executor.runAllReady()
+
+        verify(mockSL, never()).setListening(false)
+    }
+
+    @Test
+    fun testReloadOnFirstCallbackAdded() {
+        controller.addCallback(mockCallback)
+        executor.runAllReady()
+
+        verify(mockSL).reload()
+    }
+
+    @Test
+    fun testCallbackCalledWhenAdded() {
+        `when`(mockSL.reload()).then {
+            serviceListingCallbackCaptor.value.onServicesReloaded(emptyList())
+        }
+
+        controller.addCallback(mockCallback)
+        executor.runAllReady()
+        verify(mockCallback).onServicesUpdated(any())
+        reset(mockCallback)
+
+        controller.addCallback(mockCallbackOther)
+        executor.runAllReady()
+        verify(mockCallbackOther).onServicesUpdated(any())
+        verify(mockCallback, never()).onServicesUpdated(any())
+    }
+
+    @Test
+    fun testCallbackGetsList() {
+        val list = listOf(serviceInfo)
+        controller.addCallback(mockCallback)
+        controller.addCallback(mockCallbackOther)
+
+        @Suppress("unchecked_cast")
+        val captor: ArgumentCaptor<List<CandidateInfo>> =
+                ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<CandidateInfo>>
+
+        executor.runAllReady()
+        reset(mockCallback)
+        reset(mockCallbackOther)
+
+        serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+        executor.runAllReady()
+        verify(mockCallback).onServicesUpdated(capture(captor))
+        assertEquals(1, captor.value.size)
+        assertEquals(componentName.flattenToString(), captor.value[0].key)
+
+        verify(mockCallbackOther).onServicesUpdated(capture(captor))
+        assertEquals(1, captor.value.size)
+        assertEquals(componentName.flattenToString(), captor.value[0].key)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 64fbc1b..acc30d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -36,8 +36,8 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.StatusBarWindowController;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -57,7 +57,7 @@
     private @Mock LockPatternUtils mLockPatternUtils;
     private @Mock KeyguardUpdateMonitor mUpdateMonitor;
     private @Mock StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    private @Mock StatusBarWindowController mStatusBarWindowController;
+    private @Mock NotificationShadeWindowController mNotificationShadeWindowController;
     private @Mock BroadcastDispatcher mBroadcastDispatcher;
     private @Mock DismissCallbackRegistry mDismissCallbackRegistry;
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
@@ -77,7 +77,7 @@
         TestableLooper.get(this).runWithLooper(() -> {
             mViewMediator = new KeyguardViewMediator(
                     mContext, mFalsingManager, mLockPatternUtils, mBroadcastDispatcher,
-                    mStatusBarWindowController, () -> mStatusBarKeyguardViewManager,
+                    mNotificationShadeWindowController, () -> mStatusBarKeyguardViewManager,
                     mDismissCallbackRegistry, mUiBgExecutor);
         });
     }
@@ -87,6 +87,6 @@
         mViewMediator.start();
         mViewMediator.onStartedGoingToSleep(OFF_BECAUSE_OF_USER);
         verify(mUpdateMonitor).setKeyguardGoingAway(false);
-        verify(mStatusBarWindowController, never()).setKeyguardGoingAway(anyBoolean());
+        verify(mNotificationShadeWindowController, never()).setKeyguardGoingAway(anyBoolean());
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index 167f361..548da8e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -110,7 +110,7 @@
     @Test
     public void testReceiverIsRegisteredToDispatcherOnStart() {
         mPowerUI.start();
-        verify(mBroadcastDispatcher).registerReceiver(
+        verify(mBroadcastDispatcher).registerReceiverWithHandler(
                 any(BroadcastReceiver.class),
                 any(IntentFilter.class),
                 any(Handler.class)); //PowerUI does not call with User
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index fad7cbd..34111e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -44,6 +44,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.plugins.qs.QSFactory;
 import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.qs.external.CustomTile;
 import com.android.systemui.qs.tileimpl.QSFactoryImpl;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.shared.plugins.PluginManager;
@@ -73,6 +74,7 @@
 public class QSTileHostTest extends SysuiTestCase {
 
     private static String MOCK_STATE_STRING = "MockState";
+    private static final String CUSTOM_TILE_SPEC = "custom(TEST_PKG/.TEST_CLS)";
 
     @Mock
     private StatusBarIconController mIconController;
@@ -92,6 +94,8 @@
     private QSTile.State mMockState;
     @Mock
     private StatusBar mStatusBar;
+    @Mock
+    private CustomTile mCustomTile;
 
     private Handler mHandler;
     private TestableLooper mLooper;
@@ -103,9 +107,8 @@
         mLooper = TestableLooper.get(this);
         mHandler = new Handler(mLooper.getLooper());
         mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mHandler,
-                mLooper.getLooper(),
-                mPluginManager, mTunerService, mAutoTiles, mDumpController, mBroadcastDispatcher,
-                mStatusBar);
+                mLooper.getLooper(), mPluginManager, mTunerService, mAutoTiles, mDumpController,
+                mBroadcastDispatcher, mStatusBar);
         setUpTileFactory();
         Settings.Secure.putStringForUser(mContext.getContentResolver(), QSTileHost.TILES_SETTING,
                 "", ActivityManager.getCurrentUser());
@@ -121,10 +124,13 @@
                             return new TestTile1(mQSTileHost);
                         case "spec2":
                             return new TestTile2(mQSTileHost);
+                        case CUSTOM_TILE_SPEC:
+                            return mCustomTile;
                         default:
                             return null;
                     }
                 });
+        when(mCustomTile.isAvailable()).thenReturn(true);
     }
 
     @Test
@@ -173,6 +179,39 @@
         assertEquals(output, w.getBuffer().toString());
     }
 
+    @Test
+    public void testDefault() {
+        mContext.getOrCreateTestableResources()
+                .addOverride(R.string.quick_settings_tiles_default, "spec1");
+        mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "default");
+        assertEquals(1, mQSTileHost.getTiles().size());
+        QSTile element = CollectionUtils.firstOrNull(mQSTileHost.getTiles());
+        assertTrue(element instanceof TestTile1);
+    }
+
+    @Test
+    public void testDefaultAndExtra() {
+        mContext.getOrCreateTestableResources()
+                .addOverride(R.string.quick_settings_tiles_default, "spec1");
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.string.config_defaultExtraQuickSettingsTiles, "spec2");
+        mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "default");
+        assertEquals(2, mQSTileHost.getTiles().size());
+        QSTile[] elements = mQSTileHost.getTiles().toArray(new QSTile[0]);
+        assertTrue(elements[0] instanceof TestTile1);
+        assertTrue(elements[1] instanceof TestTile2);
+    }
+
+    @Test
+    public void testExtraCustom() {
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.string.config_defaultExtraQuickSettingsTiles,
+                CUSTOM_TILE_SPEC);
+        mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "default");
+        assertEquals(1, mQSTileHost.getTiles().size());
+        assertEquals(mCustomTile, CollectionUtils.firstOrNull(mQSTileHost.getTiles()));
+    }
+
     private static class TestQSTileHost extends QSTileHost {
         TestQSTileHost(Context context, StatusBarIconController iconController,
                 QSFactoryImpl defaultFactory, Handler mainHandler, Looper bgLooper,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
index 8a9a7a2..4c68102 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -77,12 +78,14 @@
     private static final Set<String> FACTORY_TILES = new ArraySet<>();
     private static final String TEST_PKG = "test_pkg";
     private static final String TEST_CLS = "test_cls";
+    private static final String CUSTOM_TILE = "custom(" + TEST_PKG + "/" + TEST_CLS + ")";
 
     static {
         FACTORY_TILES.addAll(Arrays.asList(
                 new String[]{"wifi", "bt", "cell", "dnd", "inversion", "airplane", "work",
                         "rotation", "flashlight", "location", "cast", "hotspot", "user", "battery",
                         "saver", "night", "nfc"}));
+        FACTORY_TILES.add(CUSTOM_TILE);
     }
 
     @Mock
@@ -223,6 +226,15 @@
     }
 
     @Test
+    public void testCustomTileNotCreated() {
+        Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES,
+                CUSTOM_TILE);
+        mTileQueryHelper.queryTiles(mQSTileHost);
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+        verify(mQSTileHost, never()).createTile(CUSTOM_TILE);
+    }
+
+    @Test
     public void testThirdPartyTilesInactive() {
         ResolveInfo resolveInfo = new ResolveInfo();
         ServiceInfo serviceInfo = mock(ServiceInfo.class, Answers.RETURNS_MOCKS);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
index 2c7cee3..aeb31e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
@@ -169,11 +169,11 @@
         data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
         data.finisher = null;
         data.mActionsReadyListener = null;
+        data.createDeleteAction = true;
         SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data);
         List<Notification.Action> actions = task.populateNotificationActions(
                 mContext, mContext.getResources(),
-                Uri.parse("Screenshot_123.png"),
-                CompletableFuture.completedFuture(Collections.emptyList()));
+                Uri.parse("Screenshot_123.png"));
 
         Assert.assertEquals(actions.size(), 3);
         boolean isShareFound = false;
@@ -184,7 +184,8 @@
             Assert.assertNotNull(intent);
             Bundle bundle = intent.getExtras();
             Assert.assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_ID));
-            Assert.assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED));
+            Assert.assertTrue(
+                    bundle.containsKey(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED));
 
             if (action.title.equals(GlobalScreenshot.ACTION_TYPE_DELETE)) {
                 isDeleteFound = intent.getAction() == null;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/BlurUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/BlurUtilsTest.kt
new file mode 100644
index 0000000..c180a88
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/BlurUtilsTest.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.statusbar
+
+import android.content.res.Resources
+import android.view.SurfaceControl
+import android.view.ViewRootImpl
+import androidx.test.filters.SmallTest
+import com.android.systemui.DumpController
+import com.android.systemui.SysuiTestCase
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class BlurUtilsTest : SysuiTestCase() {
+
+    @Mock lateinit var resources: Resources
+    @Mock lateinit var dumpController: DumpController
+    @Mock lateinit var transaction: SurfaceControl.Transaction
+    lateinit var blurUtils: BlurUtils
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        blurUtils = TestableBlurUtils()
+    }
+
+    @Test
+    fun testApplyBlur_noViewRoot_doesntCrash() {
+        blurUtils.applyBlur(null /* viewRootImple */, 10 /* radius */)
+    }
+
+    @Test
+    fun testApplyBlur_invalidSurfaceControl() {
+        val surfaceControl = mock(SurfaceControl::class.java)
+        val viewRootImpl = mock(ViewRootImpl::class.java)
+        `when`(viewRootImpl.surfaceControl).thenReturn(surfaceControl)
+        blurUtils.applyBlur(viewRootImpl, 10 /* radius */)
+    }
+
+    @Test
+    fun testApplyBlur_success() {
+        val radius = 10
+        val surfaceControl = mock(SurfaceControl::class.java)
+        val viewRootImpl = mock(ViewRootImpl::class.java)
+        `when`(viewRootImpl.surfaceControl).thenReturn(surfaceControl)
+        `when`(surfaceControl.isValid).thenReturn(true)
+        blurUtils.applyBlur(viewRootImpl, radius)
+        verify(transaction).setBackgroundBlurRadius(eq(surfaceControl), eq(radius))
+        verify(transaction).apply()
+    }
+
+    inner class TestableBlurUtils() : BlurUtils(resources, dumpController) {
+        override fun supportsBlursOnWindows(): Boolean {
+            return true
+        }
+
+        override fun createTransaction(): SurfaceControl.Transaction {
+            return transaction
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
index bb9c14b..6d83ac3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
@@ -34,8 +34,8 @@
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
 import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.StatusBarWindowController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import org.junit.Before;
@@ -97,6 +97,6 @@
         viewHierarchyManager.setUpWithPresenter(mPresenter, mListContainer);
 
         TestableLooper.get(this).processAllMessages();
-        assertFalse(mDependency.hasInstantiatedDependency(StatusBarWindowController.class));
+        assertFalse(mDependency.hasInstantiatedDependency(NotificationShadeWindowController.class));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
index 677a6fc..1693e7f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
@@ -557,8 +557,8 @@
 
     private NotificationEntry createBubble() {
         Notification.BubbleMetadata data = new Notification.BubbleMetadata.Builder()
-                .setIntent(PendingIntent.getActivity(mContext, 0, new Intent(), 0))
-                .setIcon(Icon.createWithResource(mContext.getResources(), R.drawable.android))
+                .createIntentBubble(PendingIntent.getActivity(mContext, 0, new Intent(), 0),
+                        Icon.createWithResource(mContext.getResources(), R.drawable.android))
                 .build();
         Notification n = new Notification.Builder(getContext(), "a")
                 .setContentTitle("title")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
index 64b10c8..1117646 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -10,7 +10,9 @@
 import static org.mockito.Mockito.when;
 
 import android.app.Notification;
+import android.app.RemoteInputHistoryItem;
 import android.content.Context;
+import android.net.Uri;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.SystemClock;
@@ -150,13 +152,30 @@
     }
 
     @Test
+    public void testRebuildWithRemoteInput_noExistingInput_image() {
+        Uri uri = mock(Uri.class);
+        String mimeType  = "image/jpeg";
+        String text = "image inserted";
+        StatusBarNotification newSbn =
+                mRemoteInputManager.rebuildNotificationWithRemoteInput(
+                        mEntry, text, false, mimeType, uri);
+        RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
+                .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+        assertEquals(1, messages.length);
+        assertEquals(text, messages[0].getText());
+        assertEquals(mimeType, messages[0].getMimeType());
+        assertEquals(uri, messages[0].getUri());
+    }
+
+    @Test
     public void testRebuildWithRemoteInput_noExistingInputNoSpinner() {
         StatusBarNotification newSbn =
-                mRemoteInputManager.rebuildNotificationWithRemoteInput(mEntry, "A Reply", false);
-        CharSequence[] messages = newSbn.getNotification().extras
-                .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY);
+                mRemoteInputManager.rebuildNotificationWithRemoteInput(
+                        mEntry, "A Reply", false, null, null);
+        RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
+                .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
         assertEquals(1, messages.length);
-        assertEquals("A Reply", messages[0]);
+        assertEquals("A Reply", messages[0].getText());
         assertFalse(newSbn.getNotification().extras
                 .getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false));
         assertTrue(newSbn.getNotification().extras
@@ -166,11 +185,12 @@
     @Test
     public void testRebuildWithRemoteInput_noExistingInputWithSpinner() {
         StatusBarNotification newSbn =
-                mRemoteInputManager.rebuildNotificationWithRemoteInput(mEntry, "A Reply", true);
-        CharSequence[] messages = newSbn.getNotification().extras
-                .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY);
+                mRemoteInputManager.rebuildNotificationWithRemoteInput(
+                        mEntry, "A Reply", true, null, null);
+        RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
+                .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
         assertEquals(1, messages.length);
-        assertEquals("A Reply", messages[0]);
+        assertEquals("A Reply", messages[0].getText());
         assertTrue(newSbn.getNotification().extras
                 .getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false));
         assertTrue(newSbn.getNotification().extras
@@ -181,18 +201,45 @@
     public void testRebuildWithRemoteInput_withExistingInput() {
         // Setup a notification entry with 1 remote input.
         StatusBarNotification newSbn =
-                mRemoteInputManager.rebuildNotificationWithRemoteInput(mEntry, "A Reply", false);
+                mRemoteInputManager.rebuildNotificationWithRemoteInput(
+                        mEntry, "A Reply", false, null, null);
         NotificationEntry entry = new NotificationEntryBuilder()
                 .setSbn(newSbn)
                 .build();
 
         // Try rebuilding to add another reply.
-        newSbn = mRemoteInputManager.rebuildNotificationWithRemoteInput(entry, "Reply 2", true);
-        CharSequence[] messages = newSbn.getNotification().extras
-                .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY);
+        newSbn = mRemoteInputManager.rebuildNotificationWithRemoteInput(
+                entry, "Reply 2", true, null, null);
+        RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
+                .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
         assertEquals(2, messages.length);
-        assertEquals("Reply 2", messages[0]);
-        assertEquals("A Reply", messages[1]);
+        assertEquals("Reply 2", messages[0].getText());
+        assertEquals("A Reply", messages[1].getText());
+    }
+
+    @Test
+    public void testRebuildWithRemoteInput_withExistingInput_image() {
+        // Setup a notification entry with 1 remote input.
+        Uri uri = mock(Uri.class);
+        String mimeType  = "image/jpeg";
+        String text = "image inserted";
+        StatusBarNotification newSbn =
+                mRemoteInputManager.rebuildNotificationWithRemoteInput(
+                        mEntry, text, false, mimeType, uri);
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setSbn(newSbn)
+                .build();
+
+        // Try rebuilding to add another reply.
+        newSbn = mRemoteInputManager.rebuildNotificationWithRemoteInput(
+                entry, "Reply 2", true, null, null);
+        RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
+                .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+        assertEquals(2, messages.length);
+        assertEquals("Reply 2", messages[0].getText());
+        assertEquals(text, messages[1].getText());
+        assertEquals(mimeType, messages[1].getMimeType());
+        assertEquals(uri, messages[1].getUri());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index 77659df..457bbe23 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -47,12 +47,15 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpansionLogger;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
+import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache;
 import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.StatusBarWindowController;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.tests.R;
 
 import java.util.concurrent.CountDownLatch;
@@ -72,6 +75,7 @@
     public static final UserHandle USER_HANDLE = UserHandle.of(ActivityManager.getCurrentUser());
 
     private static final String GROUP_KEY = "gruKey";
+    private static final String APP_NAME = "appName";
 
     private final Context mContext;
     private int mId;
@@ -83,7 +87,7 @@
         mContext = context;
         dependency.injectMockDependency(NotificationMediaManager.class);
         dependency.injectMockDependency(BubbleController.class);
-        dependency.injectMockDependency(StatusBarWindowController.class);
+        dependency.injectMockDependency(NotificationShadeWindowController.class);
         dependency.injectMockDependency(SmartReplyController.class);
         StatusBarStateController stateController = mock(StatusBarStateController.class);
         mGroupManager = new NotificationGroupManager(stateController);
@@ -303,9 +307,6 @@
                 null /* root */,
                 false /* attachToRoot */);
         ExpandableNotificationRow row = mRow;
-        row.setGroupManager(mGroupManager);
-        row.setHeadsUpManager(mHeadsUpManager);
-        row.setAboveShelfChangedListener(aboveShelf -> {});
 
         final NotificationChannel channel =
                 new NotificationChannel(
@@ -329,6 +330,23 @@
         entry.setRow(row);
         entry.createIcons(mContext, entry.getSbn());
         row.setEntry(entry);
+
+        NotificationContentInflater contentBinder = new NotificationContentInflater(
+                mock(NotifRemoteViewCache.class),
+                mock(NotificationRemoteInputManager.class));
+        contentBinder.setInflateSynchronously(true);
+
+        row.initialize(
+                APP_NAME,
+                entry.getKey(),
+                mock(ExpansionLogger.class),
+                mock(KeyguardBypassController.class),
+                mGroupManager,
+                mHeadsUpManager,
+                contentBinder,
+                mock(OnExpandClickListener.class));
+        row.setAboveShelfChangedListener(aboveShelf -> { });
+
         row.setInflationFlags(extraInflationFlags);
         inflateAndWait(row);
 
@@ -341,7 +359,6 @@
 
     private static void inflateAndWait(ExpandableNotificationRow row) throws Exception {
         CountDownLatch countDownLatch = new CountDownLatch(1);
-        row.getNotificationInflater().setInflateSynchronously(true);
         NotificationContentInflater.InflationCallback callback =
                 new NotificationContentInflater.InflationCallback() {
                     @Override
@@ -366,9 +383,9 @@
         PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, target, 0);
 
         return new BubbleMetadata.Builder()
-                .setIntent(bubbleIntent)
+                .createIntentBubble(bubbleIntent,
+                        Icon.createWithResource(mContext, R.drawable.android))
                 .setDeleteIntent(deleteIntent)
-                .setIcon(Icon.createWithResource(mContext, R.drawable.android))
                 .setDesiredHeight(314)
                 .build();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
index 5310dd8..16f105d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
@@ -52,6 +52,7 @@
     private ArrayList<CharSequence> mSmartReplies = new ArrayList<>();
     private boolean mCanBubble = false;
     private boolean mIsVisuallyInterruptive = false;
+    private boolean mIsConversation = false;
 
     public RankingBuilder() {
     }
@@ -77,6 +78,7 @@
         mSmartReplies = copyList(ranking.getSmartReplies());
         mCanBubble = ranking.canBubble();
         mIsVisuallyInterruptive = ranking.visuallyInterruptive();
+        mIsConversation = ranking.isConversation();
     }
 
     public Ranking build() {
@@ -101,7 +103,8 @@
                 mSmartActions,
                 mSmartReplies,
                 mCanBubble,
-                mIsVisuallyInterruptive);
+                mIsVisuallyInterruptive,
+                mIsConversation);
         return ranking;
     }
 
@@ -181,6 +184,11 @@
         return this;
     }
 
+    public RankingBuilder setIsConversation(boolean isConversation) {
+        mIsConversation = isConversation;
+        return this;
+    }
+
     public RankingBuilder setImportance(@Importance int importance) {
         mImportance = importance;
         return this;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java
index 145a25c..a07cfc3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java
@@ -33,9 +33,9 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.NotificationPanelView;
-import com.android.systemui.statusbar.phone.StatusBarWindowView;
-import com.android.systemui.statusbar.phone.StatusBarWindowViewController;
+import com.android.systemui.statusbar.phone.NotificationPanelViewController;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController;
 
 import org.junit.Assert;
 import org.junit.Before;
@@ -49,22 +49,24 @@
 
     private ActivityLaunchAnimator mLaunchAnimator;
     private ActivityLaunchAnimator.Callback mCallback = mock(ActivityLaunchAnimator.Callback.class);
-    private StatusBarWindowViewController mStatusBarWindowViewController = mock(
-            StatusBarWindowViewController.class);
-    private StatusBarWindowView mStatusBarWindowView = mock(StatusBarWindowView.class);
+    private NotificationShadeWindowViewController mNotificationShadeWindowViewController = mock(
+            NotificationShadeWindowViewController.class);
+    private NotificationShadeWindowView mNotificationShadeWindowView = mock(
+            NotificationShadeWindowView.class);
     private NotificationListContainer mNotificationContainer
             = mock(NotificationListContainer.class);
     private ExpandableNotificationRow mRow = mock(ExpandableNotificationRow.class);
 
     @Before
     public void setUp() throws Exception {
-        when(mStatusBarWindowViewController.getView()).thenReturn(mStatusBarWindowView);
-        when(mStatusBarWindowView.getResources()).thenReturn(mContext.getResources());
+        when(mNotificationShadeWindowViewController.getView())
+                .thenReturn(mNotificationShadeWindowView);
+        when(mNotificationShadeWindowView.getResources()).thenReturn(mContext.getResources());
         when(mCallback.areLaunchAnimationsEnabled()).thenReturn(true);
         mLaunchAnimator = new ActivityLaunchAnimator(
-                mStatusBarWindowViewController,
+                mNotificationShadeWindowViewController,
                 mCallback,
-                mock(NotificationPanelView.class),
+                mock(NotificationPanelViewController.class),
                 mNotificationContainer);
 
     }
@@ -92,7 +94,7 @@
         RemoteAnimationAdapter launchAnimation = mLaunchAnimator.getLaunchAnimation(mRow,
                 false /* occluded */);
         Assert.assertTrue("No animation generated", launchAnimation != null);
-        executePostsImmediately(mStatusBarWindowView);
+        executePostsImmediately(mNotificationShadeWindowView);
         mLaunchAnimator.setLaunchResult(ActivityManager.START_SUCCESS,
                 true /* wasIntentActivity */);
         verify(mCallback).onExpandAnimationTimedOut();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index c559265..296d0cef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -45,7 +45,6 @@
 import android.content.Intent;
 import android.graphics.drawable.Icon;
 import android.os.Handler;
-import android.os.Looper;
 import android.os.UserHandle;
 import android.service.notification.NotificationListenerService.Ranking;
 import android.service.notification.NotificationListenerService.RankingMap;
@@ -58,16 +57,13 @@
 import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.logging.MetricsLogger;
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.Dependency;
-import com.android.systemui.ForegroundServiceController;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLifetimeExtender;
-import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationPresenter;
@@ -81,22 +77,27 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
-import com.android.systemui.statusbar.notification.collection.NotificationRowBinder;
-import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.logging.NotifLog;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache;
+import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.row.RowInflaterTask;
+import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.util.Assert;
+import com.android.systemui.util.leak.LeakDetector;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -129,22 +130,18 @@
     @Mock private HeadsUpManager mHeadsUpManager;
     @Mock private RankingMap mRankingMap;
     @Mock private RemoteInputController mRemoteInputController;
-
-    // Dependency mocks:
-    @Mock private ForegroundServiceController mForegroundServiceController;
+    @Mock private NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
     @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
     @Mock private NotificationGroupManager mGroupManager;
     @Mock private NotificationGutsManager mGutsManager;
     @Mock private NotificationRemoteInputManager mRemoteInputManager;
-    @Mock private NotificationListener mNotificationListener;
     @Mock private DeviceProvisionedController mDeviceProvisionedController;
-    @Mock private VisualStabilityManager mVisualStabilityManager;
-    @Mock private MetricsLogger mMetricsLogger;
-    @Mock private SmartReplyController mSmartReplyController;
     @Mock private RowInflaterTask mAsyncInflationTask;
-    @Mock private NotificationRowBinder mMockedRowBinder;
     @Mock private NotifLog mNotifLog;
     @Mock private FeatureFlags mFeatureFlags;
+    @Mock private LeakDetector mLeakDetector;
+    @Mock private ActivatableNotificationViewController mActivatableNotificationViewController;
+    @Mock private NotificationRowComponent.Builder mNotificationRowComponentBuilder;
 
     private int mId;
     private NotificationEntry mEntry;
@@ -164,7 +161,8 @@
                     0,
                     IMPORTANCE_DEFAULT,
                     null, null,
-                    null, null, null, true, sentiment, false, -1, false, null, null, false, false);
+                    null, null, null, true, sentiment, false, -1, false, null, null, false, false,
+                    false);
             return true;
         }).when(mRankingMap).getRanking(eq(key), any(Ranking.class));
     }
@@ -183,7 +181,7 @@
                     null, null,
                     null, null, null, true,
                     Ranking.USER_SENTIMENT_NEUTRAL, false, -1,
-                    false, smartActions, null, false, false);
+                    false, smartActions, null, false, false, false);
             return true;
         }).when(mRankingMap).getRanking(eq(key), any(Ranking.class));
     }
@@ -191,27 +189,14 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mDependency.injectMockDependency(ShadeController.class);
-        mDependency.injectTestDependency(ForegroundServiceController.class,
-                mForegroundServiceController);
-        mDependency.injectTestDependency(NotificationLockscreenUserManager.class,
-                mLockscreenUserManager);
-        mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager);
-        mDependency.injectTestDependency(NotificationGutsManager.class, mGutsManager);
-        mDependency.injectTestDependency(NotificationRemoteInputManager.class, mRemoteInputManager);
-        mDependency.injectTestDependency(NotificationListener.class, mNotificationListener);
-        mDependency.injectTestDependency(DeviceProvisionedController.class,
-                mDeviceProvisionedController);
-        mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
-        mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
-        mDependency.injectTestDependency(SmartReplyController.class, mSmartReplyController);
-        mDependency.injectTestDependency(KeyguardEnvironment.class, mEnvironment);
+        mDependency.injectMockDependency(SmartReplyController.class);
         mDependency.injectMockDependency(NotificationMediaManager.class);
 
         mCountDownLatch = new CountDownLatch(1);
 
+        Assert.sMainLooper = TestableLooper.get(this).getLooper();
         mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
-                Handler.createAsync(Looper.myLooper()));
+                Handler.createAsync(TestableLooper.get(this).getLooper()));
         when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController);
         when(mListContainer.getViewParentForNotification(any())).thenReturn(
                 new FrameLayout(mContext));
@@ -221,6 +206,29 @@
 
         mEntry.expandedIcon = mock(StatusBarIconView.class);
 
+        NotificationContentInflater contentBinder = new NotificationContentInflater(
+                mock(NotifRemoteViewCache.class),
+                mRemoteInputManager);
+        contentBinder.setInflateSynchronously(true);
+
+        when(mNotificationRowComponentBuilder.activatableNotificationView(any()))
+                .thenReturn(mNotificationRowComponentBuilder);
+        when(mNotificationRowComponentBuilder.build()).thenReturn(
+                () -> mActivatableNotificationViewController);
+        NotificationRowBinderImpl notificationRowBinder =
+                new NotificationRowBinderImpl(mContext,
+                        mRemoteInputManager,
+                        mLockscreenUserManager,
+                        contentBinder,
+                        true, /* allowLongPress */
+                        mock(KeyguardBypassController.class),
+                        mock(StatusBarStateController.class),
+                        mGroupManager,
+                        mGutsManager,
+                        mNotificationInterruptionStateProvider,
+                        () -> new RowInflaterTask(mNotificationRowComponentBuilder),
+                        mock(NotificationLogger.class));
+
         when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(false);
         when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
         mEntryManager = new TestableNotificationEntryManager(
@@ -233,29 +241,33 @@
                         mock(NotificationFilter.class),
                         mNotifLog,
                         mock(NotificationSectionsFeatureManager.class),
-                        mock(PeopleNotificationIdentifier.class)),
+                        mock(PeopleNotificationIdentifier.class),
+                        mock(HighPriorityProvider.class)),
                 mEnvironment,
-                mFeatureFlags
+                mFeatureFlags,
+                () -> notificationRowBinder,
+                () -> mRemoteInputManager,
+                mLeakDetector
         );
         mEntryManager.setUpWithPresenter(mPresenter, mListContainer, mHeadsUpManager);
         mEntryManager.addNotificationEntryListener(mEntryListener);
         mEntryManager.setNotificationRemoveInterceptor(mRemoveInterceptor);
 
-        NotificationRowBinderImpl notificationRowBinder =
-                new NotificationRowBinderImpl(mContext, true, /* allowLongPress */
-                        mock(KeyguardBypassController.class),
-                        mock(StatusBarStateController.class),
-                        mock(NotificationLogger.class));
         notificationRowBinder.setUpWithPresenter(
                 mPresenter, mListContainer, mHeadsUpManager, mBindCallback);
         notificationRowBinder.setInflationCallback(mEntryManager);
         notificationRowBinder.setNotificationClicker(mock(NotificationClicker.class));
-        mEntryManager.setRowBinder(notificationRowBinder);
 
         setUserSentiment(
                 mEntry.getKey(), Ranking.USER_SENTIMENT_NEUTRAL);
     }
 
+    @After
+    public void tearDown() {
+        // CLEAN UP inflation tasks so they don't callback in a future test
+        mEntry.abortTask();
+    }
+
     @Test
     public void testAddNotification() throws Exception {
         TestableLooper.get(this).processAllMessages();
@@ -341,9 +353,6 @@
 
     @Test
     public void testRemoveNotification() {
-        // Row inflation happens off thread, so pretend that this test looper is main
-        Assert.sMainLooper = TestableLooper.get(this).getLooper();
-
         mEntry.setRow(mRow);
         mEntryManager.addActiveNotificationForTest(mEntry);
 
@@ -370,9 +379,6 @@
 
     @Test
     public void testRemoveNotification_whilePending() {
-
-        mEntryManager.setRowBinder(mMockedRowBinder);
-
         mEntryManager.addNotification(mSbn, mRankingMap);
         mEntryManager.removeNotification(mSbn.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
 
@@ -445,7 +451,6 @@
     @Test
     public void testLifetimeExtenders_ifNotificationIsRetainedItIsntRemoved() {
         // GIVEN an entry manager with a notification
-        mEntryManager.setRowBinder(mMockedRowBinder);
         mEntryManager.addActiveNotificationForTest(mEntry);
 
         // GIVEN a lifetime extender that always tries to extend lifetime
@@ -465,11 +470,7 @@
 
     @Test
     public void testLifetimeExtenders_whenRetentionEndsNotificationIsRemoved() {
-        // Row inflation happens off thread, so pretend that this test looper is main
-        Assert.sMainLooper = TestableLooper.get(this).getLooper();
-
         // GIVEN an entry manager with a notification whose life has been extended
-        mEntryManager.setRowBinder(mMockedRowBinder);
         mEntryManager.addActiveNotificationForTest(mEntry);
         final FakeNotificationLifetimeExtender extender = new FakeNotificationLifetimeExtender();
         mEntryManager.addNotificationLifetimeExtender(extender);
@@ -488,7 +489,6 @@
     @Test
     public void testLifetimeExtenders_whenNotificationUpdatedRetainersAreCanceled() {
         // GIVEN an entry manager with a notification whose life has been extended
-        mEntryManager.setRowBinder(mMockedRowBinder);
         mEntryManager.addActiveNotificationForTest(mEntry);
         NotificationLifetimeExtender extender = mock(NotificationLifetimeExtender.class);
         when(extender.shouldExtendLifetime(mEntry)).thenReturn(true);
@@ -505,7 +505,6 @@
     @Test
     public void testLifetimeExtenders_whenNewExtenderTakesPrecedenceOldExtenderIsCanceled() {
         // GIVEN an entry manager with a notification
-        mEntryManager.setRowBinder(mMockedRowBinder);
         mEntryManager.addActiveNotificationForTest(mEntry);
 
         // GIVEN two lifetime extenders, the first which never extends and the second which
@@ -544,7 +543,6 @@
     @Test
     public void testRemoveInterceptor_interceptsDontGetRemoved() throws InterruptedException {
         // GIVEN an entry manager with a notification
-        mEntryManager.setRowBinder(mMockedRowBinder);
         mEntryManager.addActiveNotificationForTest(mEntry);
 
         // GIVEN interceptor that intercepts that entry
@@ -562,11 +560,7 @@
 
     @Test
     public void testRemoveInterceptor_notInterceptedGetsRemoved() {
-        // Row inflation happens off thread, so pretend that this test looper is main
-        Assert.sMainLooper = TestableLooper.get(this).getLooper();
-
         // GIVEN an entry manager with a notification
-        mEntryManager.setRowBinder(mMockedRowBinder);
         mEntryManager.addActiveNotificationForTest(mEntry);
 
         // GIVEN interceptor that doesn't intercept
@@ -583,7 +577,7 @@
     }
 
     private NotificationEntry createNotification() {
-        Notification.Builder n = new Notification.Builder(mContext, "")
+        Notification.Builder n = new Notification.Builder(mContext, "id")
                 .setSmallIcon(R.drawable.ic_person)
                 .setContentTitle("Title")
                 .setContentText("Text");
@@ -594,6 +588,7 @@
                 .setUid(TEST_UID)
                 .setId(mId++)
                 .setNotification(n.build())
+                .setChannel(new NotificationChannel("id", "", IMPORTANCE_DEFAULT))
                 .setUser(new UserHandle(ActivityManager.getCurrentUser()))
                 .build();
     }
@@ -627,8 +622,7 @@
 
     @Test
     public void testGetNotificationsForCurrentUser_shouldFilterNonCurrentUserNotifications() {
-        Assert.sMainLooper = TestableLooper.get(this).getLooper();
-        Notification.Builder n = new Notification.Builder(mContext, "")
+        Notification.Builder n = new Notification.Builder(mContext, "di")
                 .setSmallIcon(R.drawable.ic_person)
                 .setContentTitle("Title")
                 .setContentText("Text");
@@ -640,6 +634,7 @@
                 .setId(mId++)
                 .setNotification(n.build())
                 .setUser(new UserHandle(ActivityManager.getCurrentUser()))
+                .setChannel(new NotificationChannel("id", "", IMPORTANCE_DEFAULT))
                 .build();
 
         mEntryManager.addActiveNotificationForTest(mEntry);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt
index 1afee12..29ce9207 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt
@@ -18,12 +18,15 @@
 
 import com.android.systemui.statusbar.FeatureFlags
 import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.NotificationRemoteInputManager
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder
 import com.android.systemui.statusbar.notification.logging.NotifLog
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
 import com.android.systemui.statusbar.phone.NotificationGroupManager
+import com.android.systemui.util.leak.LeakDetector
 
 import java.util.concurrent.CountDownLatch
 
@@ -35,8 +38,12 @@
     gm: NotificationGroupManager,
     rm: NotificationRankingManager,
     ke: KeyguardEnvironment,
-    ff: FeatureFlags
-) : NotificationEntryManager(log, gm, rm, ke, ff) {
+    ff: FeatureFlags,
+    rb: dagger.Lazy<NotificationRowBinder>,
+    notificationRemoteInputManagerLazy: dagger.Lazy<NotificationRemoteInputManager>,
+    leakDetector: LeakDetector
+) : NotificationEntryManager(log, gm, rm, ke, ff, rb,
+        notificationRemoteInputManagerLazy, leakDetector) {
 
     public var countDownLatch: CountDownLatch = CountDownLatch(1)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java
deleted file mode 100644
index a06d6c1..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java
+++ /dev/null
@@ -1,148 +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.statusbar.notification.collection;
-
-import static android.app.NotificationManager.IMPORTANCE_HIGH;
-import static android.app.NotificationManager.IMPORTANCE_MIN;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.RankingBuilder;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class GroupEntryTest extends SysuiTestCase {
-    @Test
-    public void testIsHighPriority_addChild() {
-        // GIVEN a GroupEntry with a lowPrioritySummary and no children
-        final GroupEntry parentEntry = new GroupEntry("test_group_key");
-        final NotificationEntry lowPrioritySummary = createNotifEntry(false);
-        setSummary(parentEntry, lowPrioritySummary);
-        assertFalse(parentEntry.isHighPriority());
-
-        // WHEN we add a high priority child and invalidate derived members
-        addChild(parentEntry, createNotifEntry(true));
-        parentEntry.invalidateDerivedMembers();
-
-        // THEN the GroupEntry's priority is updated to high even though the summary is still low
-        // priority
-        assertTrue(parentEntry.isHighPriority());
-        assertFalse(lowPrioritySummary.isHighPriority());
-    }
-
-    @Test
-    public void testIsHighPriority_clearChildren() {
-        // GIVEN a GroupEntry with a lowPrioritySummary and high priority children
-        final GroupEntry parentEntry = new GroupEntry("test_group_key");
-        setSummary(parentEntry, createNotifEntry(false));
-        addChild(parentEntry, createNotifEntry(true));
-        addChild(parentEntry, createNotifEntry(true));
-        addChild(parentEntry, createNotifEntry(true));
-        assertTrue(parentEntry.isHighPriority());
-
-        // WHEN we clear the children and invalidate derived members
-        parentEntry.clearChildren();
-        parentEntry.invalidateDerivedMembers();
-
-        // THEN the parentEntry isn't high priority anymore
-        assertFalse(parentEntry.isHighPriority());
-    }
-
-    @Test
-    public void testIsHighPriority_summaryUpdated() {
-        // GIVEN a GroupEntry with a lowPrioritySummary and no children
-        final GroupEntry parentEntry = new GroupEntry("test_group_key");
-        final NotificationEntry lowPrioritySummary = createNotifEntry(false);
-        setSummary(parentEntry, lowPrioritySummary);
-        assertFalse(parentEntry.isHighPriority());
-
-        // WHEN the summary changes to high priority and invalidates its derived members
-        lowPrioritySummary.setRanking(
-                new RankingBuilder()
-                        .setKey(lowPrioritySummary.getKey())
-                        .setImportance(IMPORTANCE_HIGH)
-                        .build());
-        lowPrioritySummary.invalidateDerivedMembers();
-        assertTrue(lowPrioritySummary.isHighPriority());
-
-        // THEN the GroupEntry's priority is updated to high
-        assertTrue(parentEntry.isHighPriority());
-    }
-
-    @Test
-    public void testIsHighPriority_checkChildrenToCalculatePriority() {
-        // GIVEN:
-        // GroupEntry = parentEntry, summary = lowPrioritySummary
-        //      NotificationEntry = lowPriorityChild
-        //      NotificationEntry = highPriorityChild
-        final GroupEntry parentEntry = new GroupEntry("test_group_key");
-        setSummary(parentEntry, createNotifEntry(false));
-        addChild(parentEntry, createNotifEntry(false));
-        addChild(parentEntry, createNotifEntry(true));
-
-        // THEN the GroupEntry parentEntry is high priority since it has a high priority child
-        assertTrue(parentEntry.isHighPriority());
-    }
-
-    @Test
-    public void testIsHighPriority_childEntryRankingUpdated() {
-        // GIVEN:
-        // GroupEntry = parentEntry, summary = lowPrioritySummary
-        //      NotificationEntry = lowPriorityChild
-        final GroupEntry parentEntry = new GroupEntry("test_group_key");
-        final NotificationEntry lowPriorityChild = createNotifEntry(false);
-        setSummary(parentEntry, createNotifEntry(false));
-        addChild(parentEntry, lowPriorityChild);
-
-        // WHEN the child entry ranking changes to high priority and invalidates its derived members
-        lowPriorityChild.setRanking(
-                new RankingBuilder()
-                        .setKey(lowPriorityChild.getKey())
-                        .setImportance(IMPORTANCE_HIGH)
-                        .build());
-        lowPriorityChild.invalidateDerivedMembers();
-
-        // THEN the parent entry's high priority value is updated - but not the parent's summary
-        assertTrue(parentEntry.isHighPriority());
-        assertFalse(parentEntry.getSummary().isHighPriority());
-    }
-
-    private NotificationEntry createNotifEntry(boolean highPriority) {
-        return new NotificationEntryBuilder()
-                .setImportance(highPriority ? IMPORTANCE_HIGH : IMPORTANCE_MIN)
-                .build();
-    }
-
-    private void setSummary(GroupEntry parent, NotificationEntry summary) {
-        parent.setSummary(summary);
-        summary.setParent(parent);
-    }
-
-    private void addChild(GroupEntry parent, NotificationEntry child) {
-        parent.addChild(child);
-        child.setParent(parent);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
new file mode 100644
index 0000000..e4865b6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
@@ -0,0 +1,231 @@
+/*
+ * 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.statusbar.notification.collection;
+
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_MIN;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.RankingBuilder;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
+
+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 HighPriorityProviderTest extends SysuiTestCase {
+    @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
+    private HighPriorityProvider mHighPriorityProvider;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mHighPriorityProvider = new HighPriorityProvider(mPeopleNotificationIdentifier);
+    }
+
+    @Test
+    public void highImportance() {
+        // GIVEN notification has high importance
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setImportance(IMPORTANCE_HIGH)
+                .build();
+        when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getRanking()))
+                .thenReturn(false);
+
+        // THEN it has high priority
+        assertTrue(mHighPriorityProvider.isHighPriority(entry));
+    }
+
+    @Test
+    public void peopleNotification() {
+        // GIVEN notification is low importance and is a people notification
+        final Notification notification = new Notification.Builder(mContext, "test")
+                .build();
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setNotification(notification)
+                .setImportance(IMPORTANCE_LOW)
+                .build();
+        when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getRanking()))
+                .thenReturn(true);
+
+        // THEN it has high priority
+        assertTrue(mHighPriorityProvider.isHighPriority(entry));
+    }
+
+    @Test
+    public void messagingStyle() {
+        // GIVEN notification is low importance but has messaging style
+        final Notification notification = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.MessagingStyle(""))
+                .build();
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setNotification(notification)
+                .build();
+        when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getRanking()))
+                .thenReturn(false);
+
+        // THEN it has high priority
+        assertTrue(mHighPriorityProvider.isHighPriority(entry));
+    }
+
+    @Test
+    public void lowImportanceForeground() {
+        // GIVEN notification is low importance and is associated with a foreground service
+        final Notification notification = mock(Notification.class);
+        when(notification.isForegroundService()).thenReturn(true);
+
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setNotification(notification)
+                .setImportance(IMPORTANCE_LOW)
+                .build();
+        when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getRanking()))
+                .thenReturn(false);
+
+        // THEN it has high priority
+        assertTrue(mHighPriorityProvider.isHighPriority(entry));
+    }
+
+    @Test
+    public void minImportanceForeground() {
+        // GIVEN notification is low importance and is associated with a foreground service
+        final Notification notification = mock(Notification.class);
+        when(notification.isForegroundService()).thenReturn(true);
+
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setNotification(notification)
+                .setImportance(IMPORTANCE_MIN)
+                .build();
+        when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getRanking()))
+                .thenReturn(false);
+
+        // THEN it does NOT have high priority
+        assertFalse(mHighPriorityProvider.isHighPriority(entry));
+    }
+
+    @Test
+    public void userChangeTrumpsHighPriorityCharacteristics() {
+        // GIVEN notification has high priority characteristics but the user changed the importance
+        // to less than IMPORTANCE_DEFAULT (ie: IMPORTANCE_LOW or IMPORTANCE_MIN)
+        final Notification notification = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.MessagingStyle(""))
+                .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
+                .build();
+        final NotificationChannel channel = new NotificationChannel("a", "a",
+                IMPORTANCE_LOW);
+        channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
+
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setNotification(notification)
+                .setChannel(channel)
+                .build();
+        when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getRanking()))
+                .thenReturn(true);
+
+        // THEN it does NOT have high priority
+        assertFalse(mHighPriorityProvider.isHighPriority(entry));
+    }
+
+    @Test
+    public void testIsHighPriority_summaryUpdated() {
+        // GIVEN a GroupEntry with a lowPrioritySummary and no children
+        final GroupEntry parentEntry = new GroupEntry("test_group_key");
+        final NotificationEntry lowPrioritySummary = createNotifEntry(false);
+        setSummary(parentEntry, lowPrioritySummary);
+        assertFalse(mHighPriorityProvider.isHighPriority(parentEntry));
+
+        // WHEN the summary changes to high priority
+        lowPrioritySummary.setRanking(
+                new RankingBuilder()
+                        .setKey(lowPrioritySummary.getKey())
+                        .setImportance(IMPORTANCE_HIGH)
+                        .build());
+        assertTrue(mHighPriorityProvider.isHighPriority(lowPrioritySummary));
+
+        // THEN the GroupEntry's priority is updated to high
+        assertTrue(mHighPriorityProvider.isHighPriority(parentEntry));
+    }
+
+    @Test
+    public void testIsHighPriority_checkChildrenToCalculatePriority() {
+        // GIVEN:
+        // GroupEntry = parentEntry, summary = lowPrioritySummary
+        //      NotificationEntry = lowPriorityChild
+        //      NotificationEntry = highPriorityChild
+        final GroupEntry parentEntry = new GroupEntry("test_group_key");
+        setSummary(parentEntry, createNotifEntry(false));
+        addChild(parentEntry, createNotifEntry(false));
+        addChild(parentEntry, createNotifEntry(true));
+
+        // THEN the GroupEntry parentEntry is high priority since it has a high priority child
+        assertTrue(mHighPriorityProvider.isHighPriority(parentEntry));
+    }
+
+    @Test
+    public void testIsHighPriority_childEntryRankingUpdated() {
+        // GIVEN:
+        // GroupEntry = parentEntry, summary = lowPrioritySummary
+        //      NotificationEntry = lowPriorityChild
+        final GroupEntry parentEntry = new GroupEntry("test_group_key");
+        final NotificationEntry lowPriorityChild = createNotifEntry(false);
+        setSummary(parentEntry, createNotifEntry(false));
+        addChild(parentEntry, lowPriorityChild);
+
+        // WHEN the child entry ranking changes to high priority
+        lowPriorityChild.setRanking(
+                new RankingBuilder()
+                        .setKey(lowPriorityChild.getKey())
+                        .setImportance(IMPORTANCE_HIGH)
+                        .build());
+
+        // THEN the parent entry's high priority value is updated - but not the parent's summary
+        assertTrue(mHighPriorityProvider.isHighPriority(parentEntry));
+        assertFalse(mHighPriorityProvider.isHighPriority(parentEntry.getSummary()));
+    }
+
+    private NotificationEntry createNotifEntry(boolean highPriority) {
+        return new NotificationEntryBuilder()
+                .setImportance(highPriority ? IMPORTANCE_HIGH : IMPORTANCE_MIN)
+                .build();
+    }
+
+    private void setSummary(GroupEntry parent, NotificationEntry summary) {
+        parent.setSummary(summary);
+        summary.setParent(parent);
+    }
+
+    private void addChild(GroupEntry parent, NotificationEntry child) {
+        parent.addChild(child);
+        child.setParent(parent);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 0837a42..fe8d769 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -24,10 +24,12 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -47,13 +49,18 @@
 
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.DumpController;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.RankingBuilder;
 import com.android.systemui.statusbar.notification.collection.NoManSimulator.NotifEvent;
 import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
-import com.android.systemui.statusbar.notification.collection.notifcollection.CoalescedEvent;
-import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer;
-import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer.BatchableNotificationHandler;
+import com.android.systemui.statusbar.notification.collection.coalescer.CoalescedEvent;
+import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer;
+import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer.BatchableNotificationHandler;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
+import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
 import com.android.systemui.util.Assert;
 
 import org.junit.Before;
@@ -98,7 +105,7 @@
         MockitoAnnotations.initMocks(this);
         Assert.sMainLooper = TestableLooper.get(this).getLooper();
 
-        mCollection = new NotifCollection(mStatusBarService);
+        mCollection = new NotifCollection(mStatusBarService, mock(DumpController.class));
         mCollection.attach(mGroupCoalescer);
         mCollection.addCollectionListener(mCollectionListener);
         mCollection.setBuildListener(mBuildListener);
@@ -261,9 +268,13 @@
                 .setRank(5)
                 .setExplanation("baz buzz")
                 .build();
+
+        // WHEN entry3's ranking update includes an update to its overrideGroupKey
+        final String newOverrideGroupKey = "newOverrideGroupKey";
         Ranking newRanking3 = new RankingBuilder(notif3.ranking)
                 .setRank(6)
                 .setExplanation("Penguin pizza")
+                .setOverrideGroupKey(newOverrideGroupKey)
                 .build();
 
         mNoMan.setRanking(notif1.sbn.getKey(), newRanking1);
@@ -275,6 +286,10 @@
         assertEquals(newRanking1, entry1.getRanking());
         assertEquals(newRanking2, entry2.getRanking());
         assertEquals(newRanking3, entry3.getRanking());
+
+        // THEN the entry3's overrideGroupKey is updated along with its groupKey
+        assertEquals(newOverrideGroupKey, entry3.getSbn().getOverrideGroupKey());
+        assertNotNull(entry3.getSbn().getGroupKey());
     }
 
     @Test
@@ -368,7 +383,7 @@
         verify(mExtender3).shouldExtendLifetime(entry2, REASON_UNKNOWN);
 
         // THEN the entry is not removed
-        assertTrue(mCollection.getNotifs().contains(entry2));
+        assertTrue(mCollection.getActiveNotifs().contains(entry2));
 
         // THEN the entry properly records all extenders that returned true
         assertEquals(Arrays.asList(mExtender1, mExtender2), entry2.mLifetimeExtenders);
@@ -389,7 +404,7 @@
 
         // GIVEN a notification gets lifetime-extended by one of them
         mNoMan.retractNotif(notif2.sbn, REASON_APP_CANCEL);
-        assertTrue(mCollection.getNotifs().contains(entry2));
+        assertTrue(mCollection.getActiveNotifs().contains(entry2));
         clearInvocations(mExtender1, mExtender2, mExtender3);
 
         // WHEN the last active extender expires (but new ones become active)
@@ -404,7 +419,7 @@
         verify(mExtender3).shouldExtendLifetime(entry2, REASON_UNKNOWN);
 
         // THEN the entry is not removed
-        assertTrue(mCollection.getNotifs().contains(entry2));
+        assertTrue(mCollection.getActiveNotifs().contains(entry2));
 
         // THEN the entry properly records all extenders that returned true
         assertEquals(Arrays.asList(mExtender1, mExtender3), entry2.mLifetimeExtenders);
@@ -426,7 +441,7 @@
 
         // GIVEN a notification gets lifetime-extended by a couple of them
         mNoMan.retractNotif(notif2.sbn, REASON_APP_CANCEL);
-        assertTrue(mCollection.getNotifs().contains(entry2));
+        assertTrue(mCollection.getActiveNotifs().contains(entry2));
         clearInvocations(mExtender1, mExtender2, mExtender3);
 
         // WHEN one (but not all) of the extenders expires
@@ -434,7 +449,7 @@
         mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2);
 
         // THEN the entry is not removed
-        assertTrue(mCollection.getNotifs().contains(entry2));
+        assertTrue(mCollection.getActiveNotifs().contains(entry2));
 
         // THEN we don't re-query the extenders
         verify(mExtender1, never()).shouldExtendLifetime(eq(entry2), anyInt());
@@ -461,7 +476,7 @@
 
         // GIVEN a notification gets lifetime-extended by a couple of them
         mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
-        assertTrue(mCollection.getNotifs().contains(entry2));
+        assertTrue(mCollection.getActiveNotifs().contains(entry2));
         clearInvocations(mExtender1, mExtender2, mExtender3);
 
         // WHEN all of the active extenders expire
@@ -471,7 +486,7 @@
         mExtender1.callback.onEndLifetimeExtension(mExtender1, entry2);
 
         // THEN the entry removed
-        assertFalse(mCollection.getNotifs().contains(entry2));
+        assertFalse(mCollection.getActiveNotifs().contains(entry2));
         verify(mCollectionListener).onEntryRemoved(entry2, REASON_UNKNOWN, false);
     }
 
@@ -491,7 +506,7 @@
 
         // GIVEN a notification gets lifetime-extended by a couple of them
         mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
-        assertTrue(mCollection.getNotifs().contains(entry2));
+        assertTrue(mCollection.getActiveNotifs().contains(entry2));
         clearInvocations(mExtender1, mExtender2, mExtender3);
 
         // WHEN the notification is reposted
@@ -502,7 +517,7 @@
         verify(mExtender2).cancelLifetimeExtension(entry2);
 
         // THEN the notification is still present
-        assertTrue(mCollection.getNotifs().contains(entry2));
+        assertTrue(mCollection.getActiveNotifs().contains(entry2));
     }
 
     @Test(expected = IllegalStateException.class)
@@ -521,7 +536,7 @@
 
         // GIVEN a notification gets lifetime-extended by a couple of them
         mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
-        assertTrue(mCollection.getNotifs().contains(entry2));
+        assertTrue(mCollection.getActiveNotifs().contains(entry2));
         clearInvocations(mExtender1, mExtender2, mExtender3);
 
         // WHEN a lifetime extender makes a reentrant call during cancelLifetimeExtension()
@@ -550,7 +565,7 @@
 
         // GIVEN a notification gets lifetime-extended by a couple of them
         mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
-        assertTrue(mCollection.getNotifs().contains(entry2));
+        assertTrue(mCollection.getActiveNotifs().contains(entry2));
         clearInvocations(mExtender1, mExtender2, mExtender3);
 
         // WHEN the notification is reposted
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImplTest.java
deleted file mode 100644
index 3e4068b..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImplTest.java
+++ /dev/null
@@ -1,1241 +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.statusbar.notification.collection;
-
-import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpList;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyList;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.util.ArrayMap;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl.OnRenderListListener;
-import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
-import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
-import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.SectionsProvider;
-import com.android.systemui.statusbar.notification.logging.NotifLog;
-import com.android.systemui.util.Assert;
-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.Captor;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.stream.Collectors;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class NotifListBuilderImplTest extends SysuiTestCase {
-
-    private NotifListBuilderImpl mListBuilder;
-    private FakeSystemClock mSystemClock = new FakeSystemClock();
-
-    @Mock private NotifLog mNotifLog;
-    @Mock private NotifCollection mNotifCollection;
-    @Spy private OnBeforeTransformGroupsListener mOnBeforeTransformGroupsListener;
-    @Spy private OnBeforeSortListener mOnBeforeSortListener;
-    @Spy private OnBeforeRenderListListener mOnBeforeRenderListListener;
-    @Spy private OnRenderListListener mOnRenderListListener = list -> mBuiltList = list;
-
-    @Captor private ArgumentCaptor<CollectionReadyForBuildListener> mBuildListenerCaptor;
-
-    private CollectionReadyForBuildListener mReadyForBuildListener;
-    private List<NotificationEntryBuilder> mPendingSet = new ArrayList<>();
-    private List<NotificationEntry> mEntrySet = new ArrayList<>();
-    private List<ListEntry> mBuiltList;
-
-    private Map<String, Integer> mNextIdMap = new ArrayMap<>();
-    private int mNextRank = 0;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        Assert.sMainLooper = TestableLooper.get(this).getLooper();
-
-        mListBuilder = new NotifListBuilderImpl(mSystemClock, mNotifLog);
-        mListBuilder.setOnRenderListListener(mOnRenderListListener);
-
-        mListBuilder.attach(mNotifCollection);
-
-        Mockito.verify(mNotifCollection).setBuildListener(mBuildListenerCaptor.capture());
-        mReadyForBuildListener = Objects.requireNonNull(mBuildListenerCaptor.getValue());
-    }
-
-    @Test
-    public void testNotifsAreSortedByRankAndWhen() {
-        // GIVEN a simple pipeline
-
-        // WHEN a series of notifs with jumbled ranks are added
-        addNotif(0, PACKAGE_1).setRank(2);
-        addNotif(1, PACKAGE_2).setRank(4).modifyNotification(mContext).setWhen(22);
-        addNotif(2, PACKAGE_3).setRank(4).modifyNotification(mContext).setWhen(33);
-        addNotif(3, PACKAGE_3).setRank(3);
-        addNotif(4, PACKAGE_5).setRank(4).modifyNotification(mContext).setWhen(11);
-        addNotif(5, PACKAGE_3).setRank(1);
-        addNotif(6, PACKAGE_1).setRank(0);
-        dispatchBuild();
-
-        // The final output is sorted based first by rank and then by when
-        verifyBuiltList(
-                notif(6),
-                notif(5),
-                notif(0),
-                notif(3),
-                notif(2),
-                notif(1),
-                notif(4)
-        );
-    }
-
-    @Test
-    public void testNotifsAreGrouped() {
-        // GIVEN a simple pipeline
-
-        // WHEN a group is added
-        addGroupChild(0, PACKAGE_1, GROUP_1);
-        addGroupChild(1, PACKAGE_1, GROUP_1);
-        addGroupChild(2, PACKAGE_1, GROUP_1);
-        addGroupSummary(3, PACKAGE_1, GROUP_1);
-        dispatchBuild();
-
-        // THEN the notifs are grouped together
-        verifyBuiltList(
-                group(
-                        summary(3),
-                        child(0),
-                        child(1),
-                        child(2)
-                )
-        );
-    }
-
-    @Test
-    public void testNotifsWithDifferentGroupKeysAreGrouped() {
-        // GIVEN a simple pipeline
-
-        // WHEN a package posts two different groups
-        addGroupChild(0, PACKAGE_1, GROUP_1);
-        addGroupChild(1, PACKAGE_1, GROUP_2);
-        addGroupSummary(2, PACKAGE_1, GROUP_2);
-        addGroupChild(3, PACKAGE_1, GROUP_2);
-        addGroupChild(4, PACKAGE_1, GROUP_1);
-        addGroupChild(5, PACKAGE_1, GROUP_2);
-        addGroupChild(6, PACKAGE_1, GROUP_1);
-        addGroupSummary(7, PACKAGE_1, GROUP_1);
-        dispatchBuild();
-
-        // THEN the groups are separated separately
-        verifyBuiltList(
-                group(
-                        summary(2),
-                        child(1),
-                        child(3),
-                        child(5)
-                ),
-                group(
-                        summary(7),
-                        child(0),
-                        child(4),
-                        child(6)
-                )
-        );
-    }
-
-    @Test
-    public void testNotifsNotifChildrenAreSorted() {
-        // GIVEN a simple pipeline
-
-        // WHEN a group is added
-        addGroupChild(0, PACKAGE_1, GROUP_1).setRank(4);
-        addGroupChild(1, PACKAGE_1, GROUP_1).setRank(2)
-                .modifyNotification(mContext).setWhen(11);
-        addGroupChild(2, PACKAGE_1, GROUP_1).setRank(1);
-        addGroupChild(3, PACKAGE_1, GROUP_1).setRank(2)
-                .modifyNotification(mContext).setWhen(33);
-        addGroupChild(4, PACKAGE_1, GROUP_1).setRank(2)
-                .modifyNotification(mContext).setWhen(22);
-        addGroupChild(5, PACKAGE_1, GROUP_1).setRank(0);
-        addGroupSummary(6, PACKAGE_1, GROUP_1).setRank(3);
-        dispatchBuild();
-
-        // THEN the children are sorted by rank and when
-        verifyBuiltList(
-                group(
-                        summary(6),
-                        child(5),
-                        child(2),
-                        child(3),
-                        child(4),
-                        child(1),
-                        child(0)
-                )
-        );
-    }
-
-    @Test
-    public void testDuplicateGroupSummariesAreDiscarded() {
-        // GIVEN a simple pipeline
-
-        // WHEN a group with multiple summaries is added
-        addNotif(0, PACKAGE_3);
-        addGroupChild(1, PACKAGE_1, GROUP_1);
-        addGroupChild(2, PACKAGE_1, GROUP_1);
-        addGroupSummary(3, PACKAGE_1, GROUP_1).setPostTime(22);
-        addGroupSummary(4, PACKAGE_1, GROUP_1).setPostTime(33);
-        addNotif(5, PACKAGE_2);
-        addGroupSummary(6, PACKAGE_1, GROUP_1).setPostTime(11);
-        addGroupChild(7, PACKAGE_1, GROUP_1);
-        dispatchBuild();
-
-        // THEN only most recent summary is used
-        verifyBuiltList(
-                notif(0),
-                group(
-                        summary(4),
-                        child(1),
-                        child(2),
-                        child(7)
-                ),
-                notif(5)
-        );
-
-        // THEN the extra summaries have their parents set to null
-        assertNull(mEntrySet.get(3).getParent());
-        assertNull(mEntrySet.get(6).getParent());
-    }
-
-    @Test
-    public void testGroupsWithNoSummaryAreUngrouped() {
-        // GIVEN a group with no summary
-        addNotif(0, PACKAGE_2);
-        addGroupChild(1, PACKAGE_4, GROUP_2);
-        addGroupChild(2, PACKAGE_4, GROUP_2);
-        addGroupChild(3, PACKAGE_4, GROUP_2);
-        addGroupChild(4, PACKAGE_4, GROUP_2);
-
-        // WHEN we build the list
-        dispatchBuild();
-
-        // THEN the children aren't grouped
-        verifyBuiltList(
-                notif(0),
-                notif(1),
-                notif(2),
-                notif(3),
-                notif(4)
-        );
-    }
-
-    @Test
-    public void testGroupsWithNoChildrenAreUngrouped() {
-        // GIVEN a group with a summary but no children
-        addGroupSummary(0, PACKAGE_5, GROUP_1);
-        addNotif(1, PACKAGE_5);
-        addNotif(2, PACKAGE_1);
-
-        // WHEN we build the list
-        dispatchBuild();
-
-        // THEN the summary isn't grouped but is still added to the final list
-        verifyBuiltList(
-                notif(0),
-                notif(1),
-                notif(2)
-        );
-    }
-
-    @Test
-    public void testGroupsWithTooFewChildrenAreSplitUp() {
-        // GIVEN a group with one child
-        addGroupChild(0, PACKAGE_2, GROUP_1);
-        addGroupSummary(1, PACKAGE_2, GROUP_1);
-
-        // WHEN we build the list
-        dispatchBuild();
-
-        // THEN the child is added at top level and the summary is discarded
-        verifyBuiltList(
-                notif(0)
-        );
-
-        assertNull(mEntrySet.get(1).getParent());
-    }
-
-    @Test
-    public void testGroupsWhoLoseChildrenMidPipelineAreSplitUp() {
-        // GIVEN a group with two children
-        addGroupChild(0, PACKAGE_2, GROUP_1);
-        addGroupSummary(1, PACKAGE_2, GROUP_1);
-        addGroupChild(2, PACKAGE_2, GROUP_1);
-
-        // GIVEN a promoter that will promote one of children to top level
-        mListBuilder.addPromoter(new IdPromoter(0));
-
-        // WHEN we build the list
-        dispatchBuild();
-
-        // THEN both children end up at top level (because group is now too small)
-        verifyBuiltList(
-                notif(0),
-                notif(2)
-        );
-
-        // THEN the summary is discarded
-        assertNull(mEntrySet.get(1).getParent());
-    }
-
-    @Test
-    public void testPreviousParentsAreSetProperly() {
-        // GIVEN a notification that is initially added to the list
-        PackageFilter filter = new PackageFilter(PACKAGE_2);
-        filter.setEnabled(false);
-        mListBuilder.addPreGroupFilter(filter);
-
-        addNotif(0, PACKAGE_1);
-        addNotif(1, PACKAGE_2);
-        addNotif(2, PACKAGE_3);
-        dispatchBuild();
-
-        // WHEN it is suddenly filtered out
-        filter.setEnabled(true);
-        dispatchBuild();
-
-        // THEN its previous parent indicates that it used to be added
-        assertNull(mEntrySet.get(1).getParent());
-        assertEquals(GroupEntry.ROOT_ENTRY, mEntrySet.get(1).getPreviousParent());
-    }
-
-    @Test
-    public void testThatAnnulledGroupsAndSummariesAreProperlyRolledBack() {
-        // GIVEN a registered transform groups listener
-        RecordingOnBeforeTransformGroupsListener listener =
-                new RecordingOnBeforeTransformGroupsListener();
-        mListBuilder.addOnBeforeTransformGroupsListener(listener);
-
-        // GIVEN a malformed group that will be dismantled
-        addGroupChild(0, PACKAGE_2, GROUP_1);
-        addGroupSummary(1, PACKAGE_2, GROUP_1);
-        addNotif(2, PACKAGE_1);
-
-        // WHEN we build the list
-        dispatchBuild();
-
-        // THEN only the child appears in the final list
-        verifyBuiltList(
-                notif(0),
-                notif(2)
-        );
-
-        // THEN the summary has a null parent and an unset firstAddedIteration
-        assertNull(mEntrySet.get(1).getParent());
-        assertEquals(-1, mEntrySet.get(1).mFirstAddedIteration);
-    }
-
-    @Test
-    public void testPreGroupNotifsAreFiltered() {
-        // GIVEN a PreGroupNotifFilter and PreRenderFilter that filters out the same package
-        NotifFilter preGroupFilter = spy(new PackageFilter(PACKAGE_2));
-        NotifFilter preRenderFilter = spy(new PackageFilter(PACKAGE_2));
-        mListBuilder.addPreGroupFilter(preGroupFilter);
-        mListBuilder.addPreRenderFilter(preRenderFilter);
-
-        // WHEN the pipeline is kicked off on a list of notifs
-        addNotif(0, PACKAGE_1);
-        addNotif(1, PACKAGE_2);
-        addNotif(2, PACKAGE_3);
-        addNotif(3, PACKAGE_2);
-        dispatchBuild();
-
-        // THEN the preGroupFilter is called on each notif in the original set
-        verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(0)), anyLong());
-        verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(1)), anyLong());
-        verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(2)), anyLong());
-        verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(3)), anyLong());
-
-        // THEN the preRenderFilter is only called on the notifications not already filtered out
-        verify(preRenderFilter).shouldFilterOut(eq(mEntrySet.get(0)), anyLong());
-        verify(preRenderFilter, never()).shouldFilterOut(eq(mEntrySet.get(1)), anyLong());
-        verify(preRenderFilter).shouldFilterOut(eq(mEntrySet.get(2)), anyLong());
-        verify(preRenderFilter, never()).shouldFilterOut(eq(mEntrySet.get(3)), anyLong());
-
-        // THEN the final list doesn't contain any filtered-out notifs
-        verifyBuiltList(
-                notif(0),
-                notif(2)
-        );
-
-        // THEN each filtered notif records the NotifFilter that did it
-        assertEquals(preGroupFilter, mEntrySet.get(1).mExcludingFilter);
-        assertEquals(preGroupFilter, mEntrySet.get(3).mExcludingFilter);
-    }
-
-    @Test
-    public void testPreRenderNotifsAreFiltered() {
-        // GIVEN a NotifFilter that filters out a specific package
-        NotifFilter filter1 = spy(new PackageFilter(PACKAGE_2));
-        mListBuilder.addPreRenderFilter(filter1);
-
-        // WHEN the pipeline is kicked off on a list of notifs
-        addNotif(0, PACKAGE_1);
-        addNotif(1, PACKAGE_2);
-        addNotif(2, PACKAGE_3);
-        addNotif(3, PACKAGE_2);
-        dispatchBuild();
-
-        // THEN the filter is called on each notif in the original set
-        verify(filter1).shouldFilterOut(eq(mEntrySet.get(0)), anyLong());
-        verify(filter1).shouldFilterOut(eq(mEntrySet.get(1)), anyLong());
-        verify(filter1).shouldFilterOut(eq(mEntrySet.get(2)), anyLong());
-        verify(filter1).shouldFilterOut(eq(mEntrySet.get(3)), anyLong());
-
-        // THEN the final list doesn't contain any filtered-out notifs
-        verifyBuiltList(
-                notif(0),
-                notif(2)
-        );
-
-        // THEN each filtered notif records the filter that did it
-        assertEquals(filter1, mEntrySet.get(1).mExcludingFilter);
-        assertEquals(filter1, mEntrySet.get(3).mExcludingFilter);
-    }
-
-    @Test
-    public void testNotifFiltersCanBePreempted() {
-        // GIVEN two notif filters
-        NotifFilter filter1 = spy(new PackageFilter(PACKAGE_2));
-        NotifFilter filter2 = spy(new PackageFilter(PACKAGE_5));
-        mListBuilder.addPreGroupFilter(filter1);
-        mListBuilder.addPreGroupFilter(filter2);
-
-        // WHEN the pipeline is kicked off on a list of notifs
-        addNotif(0, PACKAGE_1);
-        addNotif(1, PACKAGE_2);
-        addNotif(2, PACKAGE_5);
-        dispatchBuild();
-
-        // THEN both filters are called on the first notif but the second filter is never called
-        // on the already-filtered second notif
-        verify(filter1).shouldFilterOut(eq(mEntrySet.get(0)), anyLong());
-        verify(filter1).shouldFilterOut(eq(mEntrySet.get(1)), anyLong());
-        verify(filter1).shouldFilterOut(eq(mEntrySet.get(2)), anyLong());
-        verify(filter2).shouldFilterOut(eq(mEntrySet.get(0)), anyLong());
-        verify(filter2).shouldFilterOut(eq(mEntrySet.get(2)), anyLong());
-
-        // THEN the final list doesn't contain any filtered-out notifs
-        verifyBuiltList(
-                notif(0)
-        );
-
-        // THEN each filtered notif records the filter that did it
-        assertEquals(filter1, mEntrySet.get(1).mExcludingFilter);
-        assertEquals(filter2, mEntrySet.get(2).mExcludingFilter);
-    }
-
-    @Test
-    public void testNotifsArePromoted() {
-        // GIVEN a NotifPromoter that promotes certain notif IDs
-        NotifPromoter promoter = spy(new IdPromoter(1, 2));
-        mListBuilder.addPromoter(promoter);
-
-        // WHEN the pipeline is kicked off
-        addNotif(0, PACKAGE_1);
-        addGroupChild(1, PACKAGE_2, GROUP_1);
-        addGroupChild(2, PACKAGE_2, GROUP_1);
-        addGroupChild(3, PACKAGE_2, GROUP_1);
-        addGroupChild(4, PACKAGE_2, GROUP_1);
-        addGroupSummary(5, PACKAGE_2, GROUP_1);
-        addNotif(6, PACKAGE_3);
-        dispatchBuild();
-
-        // THEN the filter is called on each group child
-        verify(promoter).shouldPromoteToTopLevel(mEntrySet.get(1));
-        verify(promoter).shouldPromoteToTopLevel(mEntrySet.get(2));
-        verify(promoter).shouldPromoteToTopLevel(mEntrySet.get(3));
-        verify(promoter).shouldPromoteToTopLevel(mEntrySet.get(4));
-
-        // THEN the final list contains the promoted entries at top level
-        verifyBuiltList(
-                notif(0),
-                notif(2),
-                notif(3),
-                group(
-                        summary(5),
-                        child(1),
-                        child(4)),
-                notif(6)
-        );
-
-        // THEN each promoted notif records the promoter that did it
-        assertEquals(promoter, mEntrySet.get(2).mNotifPromoter);
-        assertEquals(promoter, mEntrySet.get(3).mNotifPromoter);
-    }
-
-    @Test
-    public void testNotifPromotersCanBePreempted() {
-        // GIVEN two notif promoters
-        NotifPromoter promoter1 = spy(new IdPromoter(1));
-        NotifPromoter promoter2 = spy(new IdPromoter(2));
-        mListBuilder.addPromoter(promoter1);
-        mListBuilder.addPromoter(promoter2);
-
-        // WHEN the pipeline is kicked off on some notifs and a group
-        addNotif(0, PACKAGE_1);
-        addGroupChild(1, PACKAGE_2, GROUP_1);
-        addGroupChild(2, PACKAGE_2, GROUP_1);
-        addGroupChild(3, PACKAGE_2, GROUP_1);
-        addGroupSummary(4, PACKAGE_2, GROUP_1);
-        addNotif(5, PACKAGE_3);
-        dispatchBuild();
-
-        // THEN both promoters are called on each child, except for children that a previous
-        // promoter has already promoted
-        verify(promoter1).shouldPromoteToTopLevel(mEntrySet.get(1));
-        verify(promoter1).shouldPromoteToTopLevel(mEntrySet.get(2));
-        verify(promoter1).shouldPromoteToTopLevel(mEntrySet.get(3));
-
-        verify(promoter2).shouldPromoteToTopLevel(mEntrySet.get(1));
-        verify(promoter2).shouldPromoteToTopLevel(mEntrySet.get(3));
-
-        // THEN each promoter is recorded on each notif it promoted
-        assertEquals(promoter1, mEntrySet.get(2).mNotifPromoter);
-        assertEquals(promoter2, mEntrySet.get(3).mNotifPromoter);
-    }
-
-    @Test
-    public void testNotifsAreSectioned() {
-        // GIVEN a filter that removes all PACKAGE_4 notifs and a SectionsProvider that divides
-        // notifs based on package name
-        mListBuilder.addPreGroupFilter(new PackageFilter(PACKAGE_4));
-        final SectionsProvider sectionsProvider = spy(new PackageSectioner());
-        mListBuilder.setSectionsProvider(sectionsProvider);
-
-        // WHEN we build a list with different packages
-        addNotif(0, PACKAGE_4);
-        addNotif(1, PACKAGE_2);
-        addNotif(2, PACKAGE_1);
-        addNotif(3, PACKAGE_3);
-        addGroupSummary(4, PACKAGE_2, GROUP_1);
-        addGroupChild(5, PACKAGE_2, GROUP_1);
-        addGroupChild(6, PACKAGE_2, GROUP_1);
-        addNotif(7, PACKAGE_1);
-        addNotif(8, PACKAGE_2);
-        addNotif(9, PACKAGE_5);
-        addNotif(10, PACKAGE_4);
-        dispatchBuild();
-
-        // THEN the list is sorted according to section
-        verifyBuiltList(
-                notif(2),
-                notif(7),
-                notif(1),
-                group(
-                        summary(4),
-                        child(5),
-                        child(6)
-                ),
-                notif(8),
-                notif(3),
-                notif(9)
-        );
-
-        // THEN the sections provider is called on all top level elements (but no children and no
-        // entries that were filtered out)
-        verify(sectionsProvider).getSection(mEntrySet.get(1));
-        verify(sectionsProvider).getSection(mEntrySet.get(2));
-        verify(sectionsProvider).getSection(mEntrySet.get(3));
-        verify(sectionsProvider).getSection(mEntrySet.get(7));
-        verify(sectionsProvider).getSection(mEntrySet.get(8));
-        verify(sectionsProvider).getSection(mEntrySet.get(9));
-        verify(sectionsProvider).getSection(mBuiltList.get(3));
-    }
-
-    @Test
-    public void testThatNotifComparatorsAreCalled() {
-        // GIVEN a set of comparators that care about specific packages
-        mListBuilder.setComparators(Arrays.asList(
-                new HypeComparator(PACKAGE_4),
-                new HypeComparator(PACKAGE_1, PACKAGE_3),
-                new HypeComparator(PACKAGE_2)
-        ));
-
-        // WHEN the pipeline is kicked off on a bunch of notifications
-        addNotif(0, PACKAGE_1);
-        addNotif(1, PACKAGE_5);
-        addNotif(2, PACKAGE_3);
-        addNotif(3, PACKAGE_4);
-        addNotif(4, PACKAGE_2);
-        dispatchBuild();
-
-        // THEN the notifs are sorted according to the hierarchy of comparators
-        verifyBuiltList(
-                notif(3),
-                notif(0),
-                notif(2),
-                notif(4),
-                notif(1)
-        );
-    }
-
-    @Test
-    public void testListenersAndPluggablesAreFiredInOrder() {
-        // GIVEN a bunch of registered listeners and pluggables
-        NotifFilter preGroupFilter = spy(new PackageFilter(PACKAGE_1));
-        NotifPromoter promoter = spy(new IdPromoter(3));
-        PackageSectioner sectioner = spy(new PackageSectioner());
-        NotifComparator comparator = spy(new HypeComparator(PACKAGE_4));
-        NotifFilter preRenderFilter = spy(new PackageFilter(PACKAGE_5));
-        mListBuilder.addPreGroupFilter(preGroupFilter);
-        mListBuilder.addOnBeforeTransformGroupsListener(mOnBeforeTransformGroupsListener);
-        mListBuilder.addPromoter(promoter);
-        mListBuilder.addOnBeforeSortListener(mOnBeforeSortListener);
-        mListBuilder.setComparators(Collections.singletonList(comparator));
-        mListBuilder.setSectionsProvider(sectioner);
-        mListBuilder.addOnBeforeRenderListListener(mOnBeforeRenderListListener);
-        mListBuilder.addPreRenderFilter(preRenderFilter);
-
-        // WHEN a few new notifs are added
-        addNotif(0, PACKAGE_1);
-        addGroupSummary(1, PACKAGE_2, GROUP_1);
-        addGroupChild(2, PACKAGE_2, GROUP_1);
-        addGroupChild(3, PACKAGE_2, GROUP_1);
-        addNotif(4, PACKAGE_5);
-        addNotif(5, PACKAGE_5);
-        addNotif(6, PACKAGE_4);
-        dispatchBuild();
-
-        // THEN the pluggables and listeners are called in order
-        InOrder inOrder = inOrder(
-                preGroupFilter,
-                mOnBeforeTransformGroupsListener,
-                promoter,
-                mOnBeforeSortListener,
-                sectioner,
-                comparator,
-                preRenderFilter,
-                mOnBeforeRenderListListener,
-                mOnRenderListListener);
-
-        inOrder.verify(preGroupFilter, atLeastOnce())
-                .shouldFilterOut(any(NotificationEntry.class), anyLong());
-        inOrder.verify(mOnBeforeTransformGroupsListener)
-                .onBeforeTransformGroups(anyList());
-        inOrder.verify(promoter, atLeastOnce())
-                .shouldPromoteToTopLevel(any(NotificationEntry.class));
-        inOrder.verify(mOnBeforeSortListener).onBeforeSort(anyList());
-        inOrder.verify(sectioner, atLeastOnce()).getSection(any(ListEntry.class));
-        inOrder.verify(comparator, atLeastOnce())
-                .compare(any(ListEntry.class), any(ListEntry.class));
-        inOrder.verify(preRenderFilter, atLeastOnce())
-                .shouldFilterOut(any(NotificationEntry.class), anyLong());
-        inOrder.verify(mOnBeforeRenderListListener).onBeforeRenderList(anyList());
-        inOrder.verify(mOnRenderListListener).onRenderList(anyList());
-    }
-
-    @Test
-    public void testThatPluggableInvalidationsTriggersRerun() {
-        // GIVEN a variety of pluggables
-        NotifFilter packageFilter = new PackageFilter(PACKAGE_1);
-        NotifPromoter idPromoter = new IdPromoter(4);
-        SectionsProvider sectionsProvider = new PackageSectioner();
-        NotifComparator hypeComparator = new HypeComparator(PACKAGE_2);
-
-        mListBuilder.addPreGroupFilter(packageFilter);
-        mListBuilder.addPromoter(idPromoter);
-        mListBuilder.setSectionsProvider(sectionsProvider);
-        mListBuilder.setComparators(Collections.singletonList(hypeComparator));
-
-        // GIVEN a set of random notifs
-        addNotif(0, PACKAGE_1);
-        addNotif(1, PACKAGE_2);
-        addNotif(2, PACKAGE_3);
-        dispatchBuild();
-
-        // WHEN each pluggable is invalidated THEN the list is re-rendered
-
-        clearInvocations(mOnRenderListListener);
-        packageFilter.invalidateList();
-        verify(mOnRenderListListener).onRenderList(anyList());
-
-        clearInvocations(mOnRenderListListener);
-        idPromoter.invalidateList();
-        verify(mOnRenderListListener).onRenderList(anyList());
-
-        clearInvocations(mOnRenderListListener);
-        sectionsProvider.invalidateList();
-        verify(mOnRenderListListener).onRenderList(anyList());
-
-        clearInvocations(mOnRenderListListener);
-        hypeComparator.invalidateList();
-        verify(mOnRenderListListener).onRenderList(anyList());
-    }
-
-    @Test
-    public void testNotifFiltersAreAllSentTheSameNow() {
-        // GIVEN three notif filters
-        NotifFilter filter1 = spy(new PackageFilter(PACKAGE_5));
-        NotifFilter filter2 = spy(new PackageFilter(PACKAGE_5));
-        NotifFilter filter3 = spy(new PackageFilter(PACKAGE_5));
-        mListBuilder.addPreGroupFilter(filter1);
-        mListBuilder.addPreGroupFilter(filter2);
-        mListBuilder.addPreGroupFilter(filter3);
-
-        // GIVEN the SystemClock is set to a particular time:
-        mSystemClock.setUptimeMillis(10047);
-
-        // WHEN the pipeline is kicked off on a list of notifs
-        addNotif(0, PACKAGE_1);
-        addNotif(1, PACKAGE_2);
-        dispatchBuild();
-
-        // THEN the value of `now` is the same for all calls to shouldFilterOut
-        verify(filter1).shouldFilterOut(mEntrySet.get(0), 10047);
-        verify(filter2).shouldFilterOut(mEntrySet.get(0), 10047);
-        verify(filter3).shouldFilterOut(mEntrySet.get(0), 10047);
-        verify(filter1).shouldFilterOut(mEntrySet.get(1), 10047);
-        verify(filter2).shouldFilterOut(mEntrySet.get(1), 10047);
-        verify(filter3).shouldFilterOut(mEntrySet.get(1), 10047);
-    }
-
-    @Test
-    public void testGroupTransformEntries() {
-        // GIVEN a registered OnBeforeTransformGroupsListener
-        RecordingOnBeforeTransformGroupsListener listener =
-                spy(new RecordingOnBeforeTransformGroupsListener());
-        mListBuilder.addOnBeforeTransformGroupsListener(listener);
-
-        // GIVEN some new notifs
-        addNotif(0, PACKAGE_1);
-        addGroupChild(1, PACKAGE_2, GROUP_1);
-        addGroupSummary(2, PACKAGE_2, GROUP_1);
-        addGroupChild(3, PACKAGE_2, GROUP_1);
-        addNotif(4, PACKAGE_3);
-        addGroupChild(5, PACKAGE_2, GROUP_1);
-
-        // WHEN we run the pipeline
-        dispatchBuild();
-
-        verifyBuiltList(
-                notif(0),
-                group(
-                        summary(2),
-                        child(1),
-                        child(3),
-                        child(5)
-                ),
-                notif(4)
-        );
-
-        // THEN all the new notifs, including the new GroupEntry, are passed to the listener
-        verify(listener).onBeforeTransformGroups(
-                Arrays.asList(
-                        mEntrySet.get(0),
-                        mBuiltList.get(1),
-                        mEntrySet.get(4)
-                )
-        );
-    }
-
-    @Test
-    public void testGroupTransformEntriesOnSecondRun() {
-        // GIVEN a registered OnBeforeTransformGroupsListener
-        RecordingOnBeforeTransformGroupsListener listener =
-                spy(new RecordingOnBeforeTransformGroupsListener());
-        mListBuilder.addOnBeforeTransformGroupsListener(listener);
-
-        // GIVEN some notifs that have already been added (two of which are in malformed groups)
-        addNotif(0, PACKAGE_1);
-        addGroupChild(1, PACKAGE_2, GROUP_1);
-        addGroupChild(2, PACKAGE_3, GROUP_2);
-
-        dispatchBuild();
-        clearInvocations(listener);
-
-        // WHEN we run the pipeline
-        addGroupSummary(3, PACKAGE_2, GROUP_1);
-        addGroupChild(4, PACKAGE_3, GROUP_2);
-        addGroupSummary(5, PACKAGE_3, GROUP_2);
-        addGroupChild(6, PACKAGE_3, GROUP_2);
-        addNotif(7, PACKAGE_2);
-
-        dispatchBuild();
-
-        verifyBuiltList(
-                notif(0),
-                notif(1),
-                group(
-                        summary(5),
-                        child(2),
-                        child(4),
-                        child(6)
-                ),
-                notif(7)
-        );
-
-        // THEN all the new notifs, including the new GroupEntry, are passed to the listener
-        verify(listener).onBeforeTransformGroups(
-                Arrays.asList(
-                        mEntrySet.get(0),
-                        mEntrySet.get(1),
-                        mBuiltList.get(2),
-                        mEntrySet.get(7)
-                )
-        );
-    }
-
-    @Test
-    public void testAnnulledGroupsHaveParentSetProperly() {
-        // GIVEN a list containing a small group that's already been built once
-        addGroupChild(0, PACKAGE_2, GROUP_2);
-        addGroupSummary(1, PACKAGE_2, GROUP_2);
-        addGroupChild(2, PACKAGE_2, GROUP_2);
-        dispatchBuild();
-
-        verifyBuiltList(
-                group(
-                        summary(1),
-                        child(0),
-                        child(2)
-                )
-        );
-        GroupEntry group = (GroupEntry) mBuiltList.get(0);
-
-        // WHEN a child is removed such that the group is no longer big enough
-        mEntrySet.remove(2);
-        dispatchBuild();
-
-        // THEN the group is annulled and its parent is set back to null
-        verifyBuiltList(
-                notif(0)
-        );
-        assertNull(group.getParent());
-
-        // but its previous parent indicates that it was added in the previous iteration
-        assertEquals(GroupEntry.ROOT_ENTRY, group.getPreviousParent());
-    }
-
-    @Test(expected = IllegalStateException.class)
-    public void testOutOfOrderPreGroupFilterInvalidationThrows() {
-        // GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage
-        NotifFilter filter = new PackageFilter(PACKAGE_5);
-        OnBeforeTransformGroupsListener listener = (list) -> filter.invalidateList();
-        mListBuilder.addPreGroupFilter(filter);
-        mListBuilder.addOnBeforeTransformGroupsListener(listener);
-
-        // WHEN we try to run the pipeline and the filter is invalidated
-        addNotif(0, PACKAGE_1);
-        dispatchBuild();
-
-        // THEN an exception is thrown
-    }
-
-    @Test(expected = IllegalStateException.class)
-    public void testOutOfOrderPrompterInvalidationThrows() {
-        // GIVEN a NotifPromoter that gets invalidated during the sorting stage
-        NotifPromoter promoter = new IdPromoter(47);
-        OnBeforeSortListener listener =
-                (list) -> promoter.invalidateList();
-        mListBuilder.addPromoter(promoter);
-        mListBuilder.addOnBeforeSortListener(listener);
-
-        // WHEN we try to run the pipeline and the promoter is invalidated
-        addNotif(0, PACKAGE_1);
-        dispatchBuild();
-
-        // THEN an exception is thrown
-    }
-
-    @Test(expected = IllegalStateException.class)
-    public void testOutOfOrderComparatorInvalidationThrows() {
-        // GIVEN a NotifComparator that gets invalidated during the finalizing stage
-        NotifComparator comparator = new HypeComparator(PACKAGE_5);
-        OnBeforeRenderListListener listener =
-                (list) -> comparator.invalidateList();
-        mListBuilder.setComparators(Collections.singletonList(comparator));
-        mListBuilder.addOnBeforeRenderListListener(listener);
-
-        // WHEN we try to run the pipeline and the comparator is invalidated
-        addNotif(0, PACKAGE_1);
-        dispatchBuild();
-
-        // THEN an exception is thrown
-    }
-
-    @Test(expected = IllegalStateException.class)
-    public void testOutOfOrderPreRenderFilterInvalidationThrows() {
-        // GIVEN a PreRenderNotifFilter that gets invalidated during the finalizing stage
-        NotifFilter filter = new PackageFilter(PACKAGE_5);
-        OnBeforeRenderListListener listener = (list) -> filter.invalidateList();
-        mListBuilder.addPreRenderFilter(filter);
-        mListBuilder.addOnBeforeRenderListListener(listener);
-
-        // WHEN we try to run the pipeline and the PreRenderFilter is invalidated
-        addNotif(0, PACKAGE_1);
-        dispatchBuild();
-
-        // THEN an exception is thrown
-    }
-
-    @Test
-    public void testInOrderPreRenderFilter() {
-        // GIVEN a PreRenderFilter that gets invalidated during the grouping stage
-        NotifFilter filter = new PackageFilter(PACKAGE_5);
-        OnBeforeTransformGroupsListener listener = (list) -> filter.invalidateList();
-        mListBuilder.addPreRenderFilter(filter);
-        mListBuilder.addOnBeforeTransformGroupsListener(listener);
-
-        // WHEN we try to run the pipeline and the filter is invalidated
-        addNotif(0, PACKAGE_1);
-        dispatchBuild();
-
-        // THEN no exception thrown
-    }
-
-    /**
-     * Adds a notif to the collection that will be passed to the list builder when
-     * {@link #dispatchBuild()}s is called.
-     *
-     * @param index Index of this notification in the set. This must be the current size of the set.
-     *              it exists to improve readability of the resulting code, since later tests will
-     *              have to refer to notifs by index.
-     * @param packageId Package that the notif should be posted under
-     * @return A NotificationEntryBuilder that can be used to further modify the notif. Do not call
-     *         build() on the builder; that will be done on the next dispatchBuild().
-     */
-    private NotificationEntryBuilder addNotif(int index, String packageId) {
-        final NotificationEntryBuilder builder = new NotificationEntryBuilder()
-                .setPkg(packageId)
-                .setId(nextId(packageId))
-                .setRank(nextRank());
-
-        builder.modifyNotification(mContext)
-                .setContentTitle("Top level singleton")
-                .setChannelId("test_channel");
-
-        assertEquals(mEntrySet.size() + mPendingSet.size(), index);
-        mPendingSet.add(builder);
-        return builder;
-    }
-
-    /** Same behavior as {@link #addNotif(int, String)}. */
-    private NotificationEntryBuilder addGroupSummary(int index, String packageId, String groupId) {
-        final NotificationEntryBuilder builder = new NotificationEntryBuilder()
-                .setPkg(packageId)
-                .setId(nextId(packageId))
-                .setRank(nextRank());
-
-        builder.modifyNotification(mContext)
-                .setChannelId("test_channel")
-                .setContentTitle("Group summary")
-                .setGroup(groupId)
-                .setGroupSummary(true);
-
-        assertEquals(mEntrySet.size() + mPendingSet.size(), index);
-        mPendingSet.add(builder);
-        return builder;
-    }
-
-    /** Same behavior as {@link #addNotif(int, String)}. */
-    private NotificationEntryBuilder addGroupChild(int index, String packageId, String groupId) {
-        final NotificationEntryBuilder builder = new NotificationEntryBuilder()
-                .setPkg(packageId)
-                .setId(nextId(packageId))
-                .setRank(nextRank());
-
-        builder.modifyNotification(mContext)
-                .setChannelId("test_channel")
-                .setContentTitle("Group child")
-                .setGroup(groupId);
-
-        assertEquals(mEntrySet.size() + mPendingSet.size(), index);
-        mPendingSet.add(builder);
-        return builder;
-    }
-
-    private int nextId(String packageName) {
-        Integer nextId = mNextIdMap.get(packageName);
-        if (nextId == null) {
-            nextId = 0;
-        }
-        mNextIdMap.put(packageName, nextId + 1);
-        return nextId;
-    }
-
-    private int nextRank() {
-        int nextRank = mNextRank;
-        mNextRank++;
-        return nextRank;
-    }
-
-    private void dispatchBuild() {
-        if (mPendingSet.size() > 0) {
-            for (NotificationEntryBuilder builder : mPendingSet) {
-                mEntrySet.add(builder.build());
-            }
-            mPendingSet.clear();
-        }
-
-        mReadyForBuildListener.onBuildList(mEntrySet);
-    }
-
-    private void verifyBuiltList(ExpectedEntry ...expectedEntries) {
-        try {
-            assertEquals(
-                    "List is the wrong length",
-                    expectedEntries.length,
-                    mBuiltList.size());
-
-            for (int i = 0; i < expectedEntries.length; i++) {
-                ListEntry outEntry = mBuiltList.get(i);
-                ExpectedEntry expectedEntry = expectedEntries[i];
-
-                if (expectedEntry instanceof ExpectedNotif) {
-                    assertEquals(
-                            "Entry " + i + " isn't a NotifEntry",
-                            NotificationEntry.class,
-                            outEntry.getClass());
-                    assertEquals(
-                            "Entry " + i + " doesn't match expected value.",
-                            ((ExpectedNotif) expectedEntry).entry, outEntry);
-                } else {
-                    ExpectedGroup cmpGroup = (ExpectedGroup) expectedEntry;
-
-                    assertEquals(
-                            "Entry " + i + " isn't a GroupEntry",
-                            GroupEntry.class,
-                            outEntry.getClass());
-
-                    GroupEntry outGroup = (GroupEntry) outEntry;
-
-                    assertEquals(
-                            "Summary notif for entry " + i
-                                    + " doesn't match expected value",
-                            cmpGroup.summary,
-                            outGroup.getSummary());
-                    assertEquals(
-                            "Summary notif for entry " + i
-                                        + " doesn't have proper parent",
-                            outGroup,
-                            outGroup.getSummary().getParent());
-
-                    assertEquals("Children for entry " + i,
-                            cmpGroup.children,
-                            outGroup.getChildren());
-
-                    for (int j = 0; j < outGroup.getChildren().size(); j++) {
-                        NotificationEntry child = outGroup.getChildren().get(j);
-                        assertEquals(
-                                "Child " + j + " for entry " + i
-                                        + " doesn't have proper parent",
-                                outGroup,
-                                child.getParent());
-                    }
-                }
-            }
-        } catch (AssertionError err) {
-            throw new AssertionError(
-                    "List under test failed verification:\n" + dumpList(mBuiltList), err);
-        }
-    }
-
-    private ExpectedNotif notif(int index) {
-        return new ExpectedNotif(mEntrySet.get(index));
-    }
-
-    private ExpectedGroup group(ExpectedSummary summary, ExpectedChild...children) {
-        return new ExpectedGroup(
-                summary.entry,
-                Arrays.stream(children)
-                        .map(child -> child.entry)
-                        .collect(Collectors.toList()));
-    }
-
-    private ExpectedSummary summary(int index) {
-        return new ExpectedSummary(mEntrySet.get(index));
-    }
-
-    private ExpectedChild child(int index) {
-        return new ExpectedChild(mEntrySet.get(index));
-    }
-
-    private abstract static class ExpectedEntry {
-    }
-
-    private static class ExpectedNotif extends ExpectedEntry {
-        public final NotificationEntry entry;
-
-        private ExpectedNotif(NotificationEntry entry) {
-            this.entry = entry;
-        }
-    }
-
-    private static class ExpectedGroup extends ExpectedEntry {
-        public final NotificationEntry summary;
-        public final List<NotificationEntry> children;
-
-        private ExpectedGroup(
-                NotificationEntry summary,
-                List<NotificationEntry> children) {
-            this.summary = summary;
-            this.children = children;
-        }
-    }
-
-    private static class ExpectedSummary {
-        public final NotificationEntry entry;
-
-        private ExpectedSummary(NotificationEntry entry) {
-            this.entry = entry;
-        }
-    }
-
-    private static class ExpectedChild {
-        public final NotificationEntry entry;
-
-        private ExpectedChild(NotificationEntry entry) {
-            this.entry = entry;
-        }
-    }
-
-    /** Filters out notifs from a particular package */
-    private static class PackageFilter extends NotifFilter {
-        private final String mPackageName;
-
-        private boolean mEnabled = true;
-
-        PackageFilter(String packageName) {
-            super("PackageFilter");
-
-            mPackageName = packageName;
-        }
-
-        @Override
-        public boolean shouldFilterOut(NotificationEntry entry, long now) {
-            return mEnabled && entry.getSbn().getPackageName().equals(mPackageName);
-        }
-
-        public void setEnabled(boolean enabled) {
-            mEnabled = enabled;
-        }
-    }
-
-    /** Promotes notifs with particular IDs */
-    private static class IdPromoter extends NotifPromoter {
-        private final List<Integer> mIds;
-
-        IdPromoter(Integer... ids) {
-            super("IdPromoter");
-            mIds = Arrays.asList(ids);
-        }
-
-        @Override
-        public boolean shouldPromoteToTopLevel(NotificationEntry child) {
-            return mIds.contains(child.getSbn().getId());
-        }
-    }
-
-    /** Sorts specific notifs above all others. */
-    private static class HypeComparator extends NotifComparator {
-
-        private final List<String> mPreferredPackages;
-
-        HypeComparator(String ...preferredPackages) {
-            super("HypeComparator");
-            mPreferredPackages = Arrays.asList(preferredPackages);
-        }
-
-        @Override
-        public int compare(ListEntry o1, ListEntry o2) {
-            boolean contains1 = mPreferredPackages.contains(
-                    o1.getRepresentativeEntry().getSbn().getPackageName());
-            boolean contains2 = mPreferredPackages.contains(
-                    o2.getRepresentativeEntry().getSbn().getPackageName());
-
-            return Boolean.compare(contains2, contains1);
-        }
-    }
-
-    /** Sorts notifs into sections based on their package name */
-    private static class PackageSectioner extends SectionsProvider {
-
-        PackageSectioner() {
-            super("PackageSectioner");
-        }
-
-        @Override
-        public int getSection(ListEntry entry) {
-            switch (entry.getRepresentativeEntry().getSbn().getPackageName()) {
-                case PACKAGE_1:
-                    return 1;
-                case PACKAGE_2:
-                    return 2;
-                case PACKAGE_3:
-                    return 3;
-                default:
-                    return 4;
-            }
-        }
-    }
-
-    private static class RecordingOnBeforeTransformGroupsListener
-            implements OnBeforeTransformGroupsListener {
-
-        @Override
-        public void onBeforeTransformGroups(List<ListEntry> list) { }
-    }
-
-    private static final String PACKAGE_1 = "com.test1";
-    private static final String PACKAGE_2 = "com.test2";
-    private static final String PACKAGE_3 = "org.test3";
-    private static final String PACKAGE_4 = "com.test4";
-    private static final String PACKAGE_5 = "com.test5";
-
-    private static final String GROUP_1 = "group_1";
-    private static final String GROUP_2 = "group_2";
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
index 39ae68a..5b0b668 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
@@ -21,8 +21,6 @@
 import static android.app.Notification.CATEGORY_EVENT;
 import static android.app.Notification.CATEGORY_MESSAGE;
 import static android.app.Notification.CATEGORY_REMINDER;
-import static android.app.NotificationManager.IMPORTANCE_HIGH;
-import static android.app.NotificationManager.IMPORTANCE_MIN;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
 
 import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
@@ -92,44 +90,6 @@
     }
 
     @Test
-    public void testIsHighPriority_notificationUpdates() {
-        // GIVEN a notification with high importance
-        final NotificationEntry entryHigh = new NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_HIGH)
-                .build();
-
-        // WHEN we get the value for the high priority entry, we're caching the high priority value
-        assertTrue(entryHigh.isHighPriority());
-
-        // WHEN we change the ranking and derived members (high priority) are invalidated
-        entryHigh.setRanking(
-                new RankingBuilder()
-                        .setKey(entryHigh.getKey())
-                        .setImportance(IMPORTANCE_MIN)
-                        .build());
-        entryHigh.invalidateDerivedMembers();
-
-        // THEN the priority is recalculated and is now low
-        assertFalse(entryHigh.isHighPriority());
-
-        // WHEN the sbn is updated to have messaging style (high priority characteristic)
-        //  AND the entry invalidates its derived members
-        final Notification notification =
-                new Notification.Builder(mContext, "test")
-                        .setStyle(new Notification.MessagingStyle(""))
-                        .build();
-        final StatusBarNotification sbn = entryHigh.getSbn();
-        entryHigh.setSbn(new StatusBarNotification(
-                sbn.getPackageName(), sbn.getPackageName(),
-                sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
-                notification, sbn.getUser(), sbn.getOverrideGroupKey(), 0));
-        entryHigh.invalidateDerivedMembers();
-
-        // THEN the priority is recalculated and is now high
-        assertTrue(entryHigh.isHighPriority());
-    }
-
-    @Test
     public void testIsExemptFromDndVisualSuppression_foreground() {
         mEntry.getSbn().getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
index 10450fa..7ab4846 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.collection
 
 import android.app.Notification
+import android.app.NotificationChannel
 import android.app.NotificationManager.IMPORTANCE_DEFAULT
 import android.app.NotificationManager.IMPORTANCE_HIGH
 import android.app.NotificationManager.IMPORTANCE_LOW
@@ -28,6 +29,7 @@
 import com.android.systemui.statusbar.NotificationMediaManager
 import com.android.systemui.statusbar.notification.NotificationFilter
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
 import com.android.systemui.statusbar.notification.logging.NotifLog
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
 import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING
@@ -62,7 +64,8 @@
                 mock(NotificationFilter::class.java),
                 mock(NotifLog::class.java),
                 mock(NotificationSectionsFeatureManager::class.java),
-                personNotificationIdentifier
+                personNotificationIdentifier,
+                HighPriorityProvider(personNotificationIdentifier)
         )
     }
 
@@ -79,6 +82,7 @@
                 .setNotification(
                         Notification.Builder(mContext, "test")
                                 .build())
+                .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
                 .setUser(mContext.getUser())
                 .setOverrideGroupKey("")
                 .build()
@@ -92,6 +96,7 @@
                 .setNotification(
                         Notification.Builder(mContext, "test")
                                 .build())
+                .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
                 .setUser(mContext.getUser())
                 .setOverrideGroupKey("")
                 .build()
@@ -114,6 +119,7 @@
                 .setOpPkg("pkg")
                 .setTag("tag")
                 .setNotification(aN)
+                .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
                 .setUser(mContext.getUser())
                 .setOverrideGroupKey("")
                 .build()
@@ -128,6 +134,7 @@
                 .setOpPkg("pkg2")
                 .setTag("tag")
                 .setNotification(bN)
+                .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
                 .setUser(mContext.getUser())
                 .setOverrideGroupKey("")
                 .build()
@@ -147,6 +154,7 @@
                 .setTag("tag")
                 .setNotification(notif)
                 .setUser(mContext.user)
+                .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
                 .setOverrideGroupKey("")
                 .build()
 
@@ -166,6 +174,7 @@
                 .setTag("tag")
                 .setNotification(notif)
                 .setUser(mContext.user)
+                .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
                 .setOverrideGroupKey("")
                 .build()
 
@@ -182,7 +191,8 @@
         filter: NotificationFilter,
         notifLog: NotifLog,
         sectionsFeatureManager: NotificationSectionsFeatureManager,
-        peopleNotificationIdentifier: PeopleNotificationIdentifier
+        peopleNotificationIdentifier: PeopleNotificationIdentifier,
+        highPriorityProvider: HighPriorityProvider
     ) : NotificationRankingManager(
         mediaManager,
         groupManager,
@@ -190,7 +200,8 @@
         filter,
         notifLog,
         sectionsFeatureManager,
-        peopleNotificationIdentifier
+        peopleNotificationIdentifier,
+        highPriorityProvider
     ) {
         fun applyTestRankingMap(r: RankingMap) {
             rankingMap = r
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
new file mode 100644
index 0000000..e915be3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -0,0 +1,1362 @@
+/*
+ * 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.statusbar.notification.collection;
+
+import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpTree;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.ArrayMap;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.DumpController;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.collection.ShadeListBuilder.OnRenderListListener;
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
+import com.android.systemui.statusbar.notification.logging.NotifLog;
+import com.android.systemui.util.Assert;
+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.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class ShadeListBuilderTest extends SysuiTestCase {
+
+    private ShadeListBuilder mListBuilder;
+    private FakeSystemClock mSystemClock = new FakeSystemClock();
+
+    @Mock private NotifLog mNotifLog;
+    @Mock private NotifCollection mNotifCollection;
+    @Spy private OnBeforeTransformGroupsListener mOnBeforeTransformGroupsListener;
+    @Spy private OnBeforeSortListener mOnBeforeSortListener;
+    @Spy private OnBeforeRenderListListener mOnBeforeRenderListListener;
+    @Spy private OnRenderListListener mOnRenderListListener = list -> mBuiltList = list;
+
+    @Captor private ArgumentCaptor<CollectionReadyForBuildListener> mBuildListenerCaptor;
+
+    private CollectionReadyForBuildListener mReadyForBuildListener;
+    private List<NotificationEntryBuilder> mPendingSet = new ArrayList<>();
+    private List<NotificationEntry> mEntrySet = new ArrayList<>();
+    private List<ListEntry> mBuiltList;
+
+    private Map<String, Integer> mNextIdMap = new ArrayMap<>();
+    private int mNextRank = 0;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Assert.sMainLooper = TestableLooper.get(this).getLooper();
+
+        mListBuilder = new ShadeListBuilder(mSystemClock, mNotifLog, mock(DumpController.class));
+        mListBuilder.setOnRenderListListener(mOnRenderListListener);
+
+        mListBuilder.attach(mNotifCollection);
+
+        Mockito.verify(mNotifCollection).setBuildListener(mBuildListenerCaptor.capture());
+        mReadyForBuildListener = Objects.requireNonNull(mBuildListenerCaptor.getValue());
+    }
+
+    @Test
+    public void testNotifsAreSortedByRankAndWhen() {
+        // GIVEN a simple pipeline
+
+        // WHEN a series of notifs with jumbled ranks are added
+        addNotif(0, PACKAGE_1).setRank(2);
+        addNotif(1, PACKAGE_2).setRank(4).modifyNotification(mContext).setWhen(22);
+        addNotif(2, PACKAGE_3).setRank(4).modifyNotification(mContext).setWhen(33);
+        addNotif(3, PACKAGE_3).setRank(3);
+        addNotif(4, PACKAGE_5).setRank(4).modifyNotification(mContext).setWhen(11);
+        addNotif(5, PACKAGE_3).setRank(1);
+        addNotif(6, PACKAGE_1).setRank(0);
+        dispatchBuild();
+
+        // The final output is sorted based first by rank and then by when
+        verifyBuiltList(
+                notif(6),
+                notif(5),
+                notif(0),
+                notif(3),
+                notif(2),
+                notif(1),
+                notif(4)
+        );
+    }
+
+    @Test
+    public void testNotifsAreGrouped() {
+        // GIVEN a simple pipeline
+
+        // WHEN a group is added
+        addGroupChild(0, PACKAGE_1, GROUP_1);
+        addGroupChild(1, PACKAGE_1, GROUP_1);
+        addGroupChild(2, PACKAGE_1, GROUP_1);
+        addGroupSummary(3, PACKAGE_1, GROUP_1);
+        dispatchBuild();
+
+        // THEN the notifs are grouped together
+        verifyBuiltList(
+                group(
+                        summary(3),
+                        child(0),
+                        child(1),
+                        child(2)
+                )
+        );
+    }
+
+    @Test
+    public void testNotifsWithDifferentGroupKeysAreGrouped() {
+        // GIVEN a simple pipeline
+
+        // WHEN a package posts two different groups
+        addGroupChild(0, PACKAGE_1, GROUP_1);
+        addGroupChild(1, PACKAGE_1, GROUP_2);
+        addGroupSummary(2, PACKAGE_1, GROUP_2);
+        addGroupChild(3, PACKAGE_1, GROUP_2);
+        addGroupChild(4, PACKAGE_1, GROUP_1);
+        addGroupChild(5, PACKAGE_1, GROUP_2);
+        addGroupChild(6, PACKAGE_1, GROUP_1);
+        addGroupSummary(7, PACKAGE_1, GROUP_1);
+        dispatchBuild();
+
+        // THEN the groups are separated separately
+        verifyBuiltList(
+                group(
+                        summary(2),
+                        child(1),
+                        child(3),
+                        child(5)
+                ),
+                group(
+                        summary(7),
+                        child(0),
+                        child(4),
+                        child(6)
+                )
+        );
+    }
+
+    @Test
+    public void testNotifsNotifChildrenAreSorted() {
+        // GIVEN a simple pipeline
+
+        // WHEN a group is added
+        addGroupChild(0, PACKAGE_1, GROUP_1).setRank(4);
+        addGroupChild(1, PACKAGE_1, GROUP_1).setRank(2)
+                .modifyNotification(mContext).setWhen(11);
+        addGroupChild(2, PACKAGE_1, GROUP_1).setRank(1);
+        addGroupChild(3, PACKAGE_1, GROUP_1).setRank(2)
+                .modifyNotification(mContext).setWhen(33);
+        addGroupChild(4, PACKAGE_1, GROUP_1).setRank(2)
+                .modifyNotification(mContext).setWhen(22);
+        addGroupChild(5, PACKAGE_1, GROUP_1).setRank(0);
+        addGroupSummary(6, PACKAGE_1, GROUP_1).setRank(3);
+        dispatchBuild();
+
+        // THEN the children are sorted by rank and when
+        verifyBuiltList(
+                group(
+                        summary(6),
+                        child(5),
+                        child(2),
+                        child(3),
+                        child(4),
+                        child(1),
+                        child(0)
+                )
+        );
+    }
+
+    @Test
+    public void testDuplicateGroupSummariesAreDiscarded() {
+        // GIVEN a simple pipeline
+
+        // WHEN a group with multiple summaries is added
+        addNotif(0, PACKAGE_3);
+        addGroupChild(1, PACKAGE_1, GROUP_1);
+        addGroupChild(2, PACKAGE_1, GROUP_1);
+        addGroupSummary(3, PACKAGE_1, GROUP_1).setPostTime(22);
+        addGroupSummary(4, PACKAGE_1, GROUP_1).setPostTime(33);
+        addNotif(5, PACKAGE_2);
+        addGroupSummary(6, PACKAGE_1, GROUP_1).setPostTime(11);
+        addGroupChild(7, PACKAGE_1, GROUP_1);
+        dispatchBuild();
+
+        // THEN only most recent summary is used
+        verifyBuiltList(
+                notif(0),
+                group(
+                        summary(4),
+                        child(1),
+                        child(2),
+                        child(7)
+                ),
+                notif(5)
+        );
+
+        // THEN the extra summaries have their parents set to null
+        assertNull(mEntrySet.get(3).getParent());
+        assertNull(mEntrySet.get(6).getParent());
+    }
+
+    @Test
+    public void testGroupsWithNoSummaryAreUngrouped() {
+        // GIVEN a group with no summary
+        addNotif(0, PACKAGE_2);
+        addGroupChild(1, PACKAGE_4, GROUP_2);
+        addGroupChild(2, PACKAGE_4, GROUP_2);
+        addGroupChild(3, PACKAGE_4, GROUP_2);
+        addGroupChild(4, PACKAGE_4, GROUP_2);
+
+        // WHEN we build the list
+        dispatchBuild();
+
+        // THEN the children aren't grouped
+        verifyBuiltList(
+                notif(0),
+                notif(1),
+                notif(2),
+                notif(3),
+                notif(4)
+        );
+    }
+
+    @Test
+    public void testGroupsWithNoChildrenAreUngrouped() {
+        // GIVEN a group with a summary but no children
+        addGroupSummary(0, PACKAGE_5, GROUP_1);
+        addNotif(1, PACKAGE_5);
+        addNotif(2, PACKAGE_1);
+
+        // WHEN we build the list
+        dispatchBuild();
+
+        // THEN the summary isn't grouped but is still added to the final list
+        verifyBuiltList(
+                notif(0),
+                notif(1),
+                notif(2)
+        );
+    }
+
+    @Test
+    public void testGroupsWithTooFewChildrenAreSplitUp() {
+        // GIVEN a group with one child
+        addGroupChild(0, PACKAGE_2, GROUP_1);
+        addGroupSummary(1, PACKAGE_2, GROUP_1);
+
+        // WHEN we build the list
+        dispatchBuild();
+
+        // THEN the child is added at top level and the summary is discarded
+        verifyBuiltList(
+                notif(0)
+        );
+
+        assertNull(mEntrySet.get(1).getParent());
+    }
+
+    @Test
+    public void testGroupsWhoLoseChildrenMidPipelineAreSplitUp() {
+        // GIVEN a group with two children
+        addGroupChild(0, PACKAGE_2, GROUP_1);
+        addGroupSummary(1, PACKAGE_2, GROUP_1);
+        addGroupChild(2, PACKAGE_2, GROUP_1);
+
+        // GIVEN a promoter that will promote one of children to top level
+        mListBuilder.addPromoter(new IdPromoter(0));
+
+        // WHEN we build the list
+        dispatchBuild();
+
+        // THEN both children end up at top level (because group is now too small)
+        verifyBuiltList(
+                notif(0),
+                notif(2)
+        );
+
+        // THEN the summary is discarded
+        assertNull(mEntrySet.get(1).getParent());
+    }
+
+    @Test
+    public void testPreviousParentsAreSetProperly() {
+        // GIVEN a notification that is initially added to the list
+        PackageFilter filter = new PackageFilter(PACKAGE_2);
+        filter.setEnabled(false);
+        mListBuilder.addPreGroupFilter(filter);
+
+        addNotif(0, PACKAGE_1);
+        addNotif(1, PACKAGE_2);
+        addNotif(2, PACKAGE_3);
+        dispatchBuild();
+
+        // WHEN it is suddenly filtered out
+        filter.setEnabled(true);
+        dispatchBuild();
+
+        // THEN its previous parent indicates that it used to be added
+        assertNull(mEntrySet.get(1).getParent());
+        assertEquals(GroupEntry.ROOT_ENTRY, mEntrySet.get(1).getPreviousParent());
+    }
+
+    @Test
+    public void testThatAnnulledGroupsAndSummariesAreProperlyRolledBack() {
+        // GIVEN a registered transform groups listener
+        RecordingOnBeforeTransformGroupsListener listener =
+                new RecordingOnBeforeTransformGroupsListener();
+        mListBuilder.addOnBeforeTransformGroupsListener(listener);
+
+        // GIVEN a malformed group that will be dismantled
+        addGroupChild(0, PACKAGE_2, GROUP_1);
+        addGroupSummary(1, PACKAGE_2, GROUP_1);
+        addNotif(2, PACKAGE_1);
+
+        // WHEN we build the list
+        dispatchBuild();
+
+        // THEN only the child appears in the final list
+        verifyBuiltList(
+                notif(0),
+                notif(2)
+        );
+
+        // THEN the summary has a null parent and an unset firstAddedIteration
+        assertNull(mEntrySet.get(1).getParent());
+        assertEquals(-1, mEntrySet.get(1).mFirstAddedIteration);
+    }
+
+    @Test
+    public void testPreGroupNotifsAreFiltered() {
+        // GIVEN a PreGroupNotifFilter and PreRenderFilter that filters out the same package
+        NotifFilter preGroupFilter = spy(new PackageFilter(PACKAGE_2));
+        NotifFilter preRenderFilter = spy(new PackageFilter(PACKAGE_2));
+        mListBuilder.addPreGroupFilter(preGroupFilter);
+        mListBuilder.addPreRenderFilter(preRenderFilter);
+
+        // WHEN the pipeline is kicked off on a list of notifs
+        addNotif(0, PACKAGE_1);
+        addNotif(1, PACKAGE_2);
+        addNotif(2, PACKAGE_3);
+        addNotif(3, PACKAGE_2);
+        dispatchBuild();
+
+        // THEN the preGroupFilter is called on each notif in the original set
+        verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(0)), anyLong());
+        verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(1)), anyLong());
+        verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(2)), anyLong());
+        verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(3)), anyLong());
+
+        // THEN the preRenderFilter is only called on the notifications not already filtered out
+        verify(preRenderFilter).shouldFilterOut(eq(mEntrySet.get(0)), anyLong());
+        verify(preRenderFilter, never()).shouldFilterOut(eq(mEntrySet.get(1)), anyLong());
+        verify(preRenderFilter).shouldFilterOut(eq(mEntrySet.get(2)), anyLong());
+        verify(preRenderFilter, never()).shouldFilterOut(eq(mEntrySet.get(3)), anyLong());
+
+        // THEN the final list doesn't contain any filtered-out notifs
+        verifyBuiltList(
+                notif(0),
+                notif(2)
+        );
+
+        // THEN each filtered notif records the NotifFilter that did it
+        assertEquals(preGroupFilter, mEntrySet.get(1).mExcludingFilter);
+        assertEquals(preGroupFilter, mEntrySet.get(3).mExcludingFilter);
+    }
+
+    @Test
+    public void testPreRenderNotifsAreFiltered() {
+        // GIVEN a NotifFilter that filters out a specific package
+        NotifFilter filter1 = spy(new PackageFilter(PACKAGE_2));
+        mListBuilder.addPreRenderFilter(filter1);
+
+        // WHEN the pipeline is kicked off on a list of notifs
+        addNotif(0, PACKAGE_1);
+        addNotif(1, PACKAGE_2);
+        addNotif(2, PACKAGE_3);
+        addNotif(3, PACKAGE_2);
+        dispatchBuild();
+
+        // THEN the filter is called on each notif in the original set
+        verify(filter1).shouldFilterOut(eq(mEntrySet.get(0)), anyLong());
+        verify(filter1).shouldFilterOut(eq(mEntrySet.get(1)), anyLong());
+        verify(filter1).shouldFilterOut(eq(mEntrySet.get(2)), anyLong());
+        verify(filter1).shouldFilterOut(eq(mEntrySet.get(3)), anyLong());
+
+        // THEN the final list doesn't contain any filtered-out notifs
+        verifyBuiltList(
+                notif(0),
+                notif(2)
+        );
+
+        // THEN each filtered notif records the filter that did it
+        assertEquals(filter1, mEntrySet.get(1).mExcludingFilter);
+        assertEquals(filter1, mEntrySet.get(3).mExcludingFilter);
+    }
+
+    @Test
+    public void testPreRenderNotifsFilteredBreakupGroups() {
+        final String filterTag = "FILTER_ME";
+        // GIVEN a NotifFilter that filters out notifications with a tag
+        NotifFilter filter1 = spy(new NotifFilterWithTag(filterTag));
+        mListBuilder.addPreRenderFilter(filter1);
+
+        // WHEN the pipeline is kicked off on a list of notifs
+        addGroupChildWithTag(0, PACKAGE_2, GROUP_1, filterTag);
+        addGroupChild(1, PACKAGE_2, GROUP_1);
+        addGroupSummary(2, PACKAGE_2, GROUP_1);
+        dispatchBuild();
+
+        // THEN the final list doesn't contain any filtered-out notifs
+        // and groups that are too small are broken up
+        verifyBuiltList(
+                notif(1)
+        );
+
+        // THEN each filtered notif records the filter that did it
+        assertEquals(filter1, mEntrySet.get(0).mExcludingFilter);
+    }
+
+    @Test
+    public void testNotifFiltersCanBePreempted() {
+        // GIVEN two notif filters
+        NotifFilter filter1 = spy(new PackageFilter(PACKAGE_2));
+        NotifFilter filter2 = spy(new PackageFilter(PACKAGE_5));
+        mListBuilder.addPreGroupFilter(filter1);
+        mListBuilder.addPreGroupFilter(filter2);
+
+        // WHEN the pipeline is kicked off on a list of notifs
+        addNotif(0, PACKAGE_1);
+        addNotif(1, PACKAGE_2);
+        addNotif(2, PACKAGE_5);
+        dispatchBuild();
+
+        // THEN both filters are called on the first notif but the second filter is never called
+        // on the already-filtered second notif
+        verify(filter1).shouldFilterOut(eq(mEntrySet.get(0)), anyLong());
+        verify(filter1).shouldFilterOut(eq(mEntrySet.get(1)), anyLong());
+        verify(filter1).shouldFilterOut(eq(mEntrySet.get(2)), anyLong());
+        verify(filter2).shouldFilterOut(eq(mEntrySet.get(0)), anyLong());
+        verify(filter2).shouldFilterOut(eq(mEntrySet.get(2)), anyLong());
+
+        // THEN the final list doesn't contain any filtered-out notifs
+        verifyBuiltList(
+                notif(0)
+        );
+
+        // THEN each filtered notif records the filter that did it
+        assertEquals(filter1, mEntrySet.get(1).mExcludingFilter);
+        assertEquals(filter2, mEntrySet.get(2).mExcludingFilter);
+    }
+
+    @Test
+    public void testNotifsArePromoted() {
+        // GIVEN a NotifPromoter that promotes certain notif IDs
+        NotifPromoter promoter = spy(new IdPromoter(1, 2));
+        mListBuilder.addPromoter(promoter);
+
+        // WHEN the pipeline is kicked off
+        addNotif(0, PACKAGE_1);
+        addGroupChild(1, PACKAGE_2, GROUP_1);
+        addGroupChild(2, PACKAGE_2, GROUP_1);
+        addGroupChild(3, PACKAGE_2, GROUP_1);
+        addGroupChild(4, PACKAGE_2, GROUP_1);
+        addGroupSummary(5, PACKAGE_2, GROUP_1);
+        addNotif(6, PACKAGE_3);
+        dispatchBuild();
+
+        // THEN the filter is called on each group child
+        verify(promoter).shouldPromoteToTopLevel(mEntrySet.get(1));
+        verify(promoter).shouldPromoteToTopLevel(mEntrySet.get(2));
+        verify(promoter).shouldPromoteToTopLevel(mEntrySet.get(3));
+        verify(promoter).shouldPromoteToTopLevel(mEntrySet.get(4));
+
+        // THEN the final list contains the promoted entries at top level
+        verifyBuiltList(
+                notif(0),
+                notif(2),
+                notif(3),
+                group(
+                        summary(5),
+                        child(1),
+                        child(4)),
+                notif(6)
+        );
+
+        // THEN each promoted notif records the promoter that did it
+        assertEquals(promoter, mEntrySet.get(2).mNotifPromoter);
+        assertEquals(promoter, mEntrySet.get(3).mNotifPromoter);
+    }
+
+    @Test
+    public void testNotifPromotersCanBePreempted() {
+        // GIVEN two notif promoters
+        NotifPromoter promoter1 = spy(new IdPromoter(1));
+        NotifPromoter promoter2 = spy(new IdPromoter(2));
+        mListBuilder.addPromoter(promoter1);
+        mListBuilder.addPromoter(promoter2);
+
+        // WHEN the pipeline is kicked off on some notifs and a group
+        addNotif(0, PACKAGE_1);
+        addGroupChild(1, PACKAGE_2, GROUP_1);
+        addGroupChild(2, PACKAGE_2, GROUP_1);
+        addGroupChild(3, PACKAGE_2, GROUP_1);
+        addGroupSummary(4, PACKAGE_2, GROUP_1);
+        addNotif(5, PACKAGE_3);
+        dispatchBuild();
+
+        // THEN both promoters are called on each child, except for children that a previous
+        // promoter has already promoted
+        verify(promoter1).shouldPromoteToTopLevel(mEntrySet.get(1));
+        verify(promoter1).shouldPromoteToTopLevel(mEntrySet.get(2));
+        verify(promoter1).shouldPromoteToTopLevel(mEntrySet.get(3));
+
+        verify(promoter2).shouldPromoteToTopLevel(mEntrySet.get(1));
+        verify(promoter2).shouldPromoteToTopLevel(mEntrySet.get(3));
+
+        // THEN each promoter is recorded on each notif it promoted
+        assertEquals(promoter1, mEntrySet.get(2).mNotifPromoter);
+        assertEquals(promoter2, mEntrySet.get(3).mNotifPromoter);
+    }
+
+    @Test
+    public void testNotifSections() {
+        // GIVEN a filter that removes all PACKAGE_4 notifs and sections that divide
+        // notifs based on package name
+        mListBuilder.addPreGroupFilter(new PackageFilter(PACKAGE_4));
+        final NotifSection pkg1Section = spy(new PackageSection(PACKAGE_1));
+        final NotifSection pkg2Section = spy(new PackageSection(PACKAGE_2));
+        // NOTE: no package 3 section explicitly added, so notifs with package 3 will get set by
+        // ShadeListBuilder's sDefaultSection which will demote it to the last section
+        final NotifSection pkg4Section = spy(new PackageSection(PACKAGE_4));
+        final NotifSection pkg5Section = spy(new PackageSection(PACKAGE_5));
+        mListBuilder.setSections(Arrays.asList(pkg1Section, pkg2Section, pkg4Section, pkg5Section));
+
+        // WHEN we build a list with different packages
+        addNotif(0, PACKAGE_4);
+        addNotif(1, PACKAGE_2);
+        addNotif(2, PACKAGE_1);
+        addNotif(3, PACKAGE_3);
+        addGroupSummary(4, PACKAGE_2, GROUP_1);
+        addGroupChild(5, PACKAGE_2, GROUP_1);
+        addGroupChild(6, PACKAGE_2, GROUP_1);
+        addNotif(7, PACKAGE_1);
+        addNotif(8, PACKAGE_2);
+        addNotif(9, PACKAGE_5);
+        addNotif(10, PACKAGE_4);
+        dispatchBuild();
+
+        // THEN the list is sorted according to section
+        verifyBuiltList(
+                notif(2),
+                notif(7),
+                notif(1),
+                group(
+                        summary(4),
+                        child(5),
+                        child(6)
+                ),
+                notif(8),
+                notif(9),
+                notif(3)
+        );
+
+        // THEN the first section (pkg1Section) is called on all top level elements (but
+        // no children and no entries that were filtered out)
+        verify(pkg1Section).isInSection(mEntrySet.get(1));
+        verify(pkg1Section).isInSection(mEntrySet.get(2));
+        verify(pkg1Section).isInSection(mEntrySet.get(3));
+        verify(pkg1Section).isInSection(mEntrySet.get(7));
+        verify(pkg1Section).isInSection(mEntrySet.get(8));
+        verify(pkg1Section).isInSection(mEntrySet.get(9));
+        verify(pkg1Section).isInSection(mBuiltList.get(3));
+
+        verify(pkg1Section, never()).isInSection(mEntrySet.get(0));
+        verify(pkg1Section, never()).isInSection(mEntrySet.get(4));
+        verify(pkg1Section, never()).isInSection(mEntrySet.get(5));
+        verify(pkg1Section, never()).isInSection(mEntrySet.get(6));
+        verify(pkg1Section, never()).isInSection(mEntrySet.get(10));
+
+        // THEN the last section (pkg5Section) is not called on any of the entries that were
+        // filtered or already in a section
+        verify(pkg5Section, never()).isInSection(mEntrySet.get(0));
+        verify(pkg5Section, never()).isInSection(mEntrySet.get(1));
+        verify(pkg5Section, never()).isInSection(mEntrySet.get(2));
+        verify(pkg5Section, never()).isInSection(mEntrySet.get(4));
+        verify(pkg5Section, never()).isInSection(mEntrySet.get(5));
+        verify(pkg5Section, never()).isInSection(mEntrySet.get(6));
+        verify(pkg5Section, never()).isInSection(mEntrySet.get(7));
+        verify(pkg5Section, never()).isInSection(mEntrySet.get(8));
+        verify(pkg5Section, never()).isInSection(mEntrySet.get(10));
+
+        verify(pkg5Section).isInSection(mEntrySet.get(3));
+        verify(pkg5Section).isInSection(mEntrySet.get(9));
+
+        // THEN the correct section is assigned for entries in pkg1Section
+        assertEquals(pkg1Section, mEntrySet.get(2).mNotifSection);
+        assertEquals(0, mEntrySet.get(2).getSection());
+        assertEquals(pkg1Section, mEntrySet.get(7).mNotifSection);
+        assertEquals(0, mEntrySet.get(7).getSection());
+
+        // THEN the correct section is assigned for entries in pkg2Section
+        assertEquals(pkg2Section, mEntrySet.get(1).mNotifSection);
+        assertEquals(1, mEntrySet.get(1).getSection());
+        assertEquals(pkg2Section, mEntrySet.get(8).mNotifSection);
+        assertEquals(1, mEntrySet.get(8).getSection());
+        assertEquals(pkg2Section, mBuiltList.get(3).mNotifSection);
+        assertEquals(1, mBuiltList.get(3).getSection());
+
+        // THEN no section was assigned to entries in pkg4Section (since they were filtered)
+        assertEquals(null, mEntrySet.get(0).mNotifSection);
+        assertEquals(-1, mEntrySet.get(0).getSection());
+        assertEquals(null, mEntrySet.get(10).mNotifSection);
+        assertEquals(-1, mEntrySet.get(10).getSection());
+
+
+        // THEN the correct section is assigned for entries in pkg5Section
+        assertEquals(pkg5Section, mEntrySet.get(9).mNotifSection);
+        assertEquals(3, mEntrySet.get(9).getSection());
+
+        // THEN the children entries are assigned the same section as its parent
+        assertEquals(mBuiltList.get(3).mNotifSection, child(5).entry.mNotifSection);
+        assertEquals(mBuiltList.get(3).getSection(), child(5).entry.getSection());
+        assertEquals(mBuiltList.get(3).mNotifSection, child(6).entry.mNotifSection);
+        assertEquals(mBuiltList.get(3).getSection(), child(6).entry.getSection());
+    }
+
+    @Test
+    public void testNotifUsesDefaultSection() {
+        // GIVEN a Section for Package2
+        final NotifSection pkg2Section = spy(new PackageSection(PACKAGE_2));
+        mListBuilder.setSections(Arrays.asList(pkg2Section));
+
+        // WHEN we build a list with pkg1 and pkg2 packages
+        addNotif(0, PACKAGE_1);
+        addNotif(1, PACKAGE_2);
+        dispatchBuild();
+
+        // THEN the list is sorted according to section
+        verifyBuiltList(
+                notif(1),
+                notif(0)
+        );
+
+        // THEN the entry that didn't have an explicit section gets assigned the DefaultSection
+        assertEquals(1, notif(0).entry.getSection());
+        assertNotNull(notif(0).entry.mNotifSection);
+    }
+
+    @Test
+    public void testThatNotifComparatorsAreCalled() {
+        // GIVEN a set of comparators that care about specific packages
+        mListBuilder.setComparators(Arrays.asList(
+                new HypeComparator(PACKAGE_4),
+                new HypeComparator(PACKAGE_1, PACKAGE_3),
+                new HypeComparator(PACKAGE_2)
+        ));
+
+        // WHEN the pipeline is kicked off on a bunch of notifications
+        addNotif(0, PACKAGE_1);
+        addNotif(1, PACKAGE_5);
+        addNotif(2, PACKAGE_3);
+        addNotif(3, PACKAGE_4);
+        addNotif(4, PACKAGE_2);
+        dispatchBuild();
+
+        // THEN the notifs are sorted according to the hierarchy of comparators
+        verifyBuiltList(
+                notif(3),
+                notif(0),
+                notif(2),
+                notif(4),
+                notif(1)
+        );
+    }
+
+    @Test
+    public void testListenersAndPluggablesAreFiredInOrder() {
+        // GIVEN a bunch of registered listeners and pluggables
+        NotifFilter preGroupFilter = spy(new PackageFilter(PACKAGE_1));
+        NotifPromoter promoter = spy(new IdPromoter(3));
+        NotifSection section = spy(new PackageSection(PACKAGE_1));
+        NotifComparator comparator = spy(new HypeComparator(PACKAGE_4));
+        NotifFilter preRenderFilter = spy(new PackageFilter(PACKAGE_5));
+        mListBuilder.addPreGroupFilter(preGroupFilter);
+        mListBuilder.addOnBeforeTransformGroupsListener(mOnBeforeTransformGroupsListener);
+        mListBuilder.addPromoter(promoter);
+        mListBuilder.addOnBeforeSortListener(mOnBeforeSortListener);
+        mListBuilder.setComparators(Collections.singletonList(comparator));
+        mListBuilder.setSections(Arrays.asList(section));
+        mListBuilder.addOnBeforeRenderListListener(mOnBeforeRenderListListener);
+        mListBuilder.addPreRenderFilter(preRenderFilter);
+
+        // WHEN a few new notifs are added
+        addNotif(0, PACKAGE_1);
+        addGroupSummary(1, PACKAGE_2, GROUP_1);
+        addGroupChild(2, PACKAGE_2, GROUP_1);
+        addGroupChild(3, PACKAGE_2, GROUP_1);
+        addNotif(4, PACKAGE_5);
+        addNotif(5, PACKAGE_5);
+        addNotif(6, PACKAGE_4);
+        dispatchBuild();
+
+        // THEN the pluggables and listeners are called in order
+        InOrder inOrder = inOrder(
+                preGroupFilter,
+                mOnBeforeTransformGroupsListener,
+                promoter,
+                mOnBeforeSortListener,
+                section,
+                comparator,
+                preRenderFilter,
+                mOnBeforeRenderListListener,
+                mOnRenderListListener);
+
+        inOrder.verify(preGroupFilter, atLeastOnce())
+                .shouldFilterOut(any(NotificationEntry.class), anyLong());
+        inOrder.verify(mOnBeforeTransformGroupsListener)
+                .onBeforeTransformGroups(anyList());
+        inOrder.verify(promoter, atLeastOnce())
+                .shouldPromoteToTopLevel(any(NotificationEntry.class));
+        inOrder.verify(mOnBeforeSortListener).onBeforeSort(anyList());
+        inOrder.verify(section, atLeastOnce()).isInSection(any(ListEntry.class));
+        inOrder.verify(comparator, atLeastOnce())
+                .compare(any(ListEntry.class), any(ListEntry.class));
+        inOrder.verify(preRenderFilter, atLeastOnce())
+                .shouldFilterOut(any(NotificationEntry.class), anyLong());
+        inOrder.verify(mOnBeforeRenderListListener).onBeforeRenderList(anyList());
+        inOrder.verify(mOnRenderListListener).onRenderList(anyList());
+    }
+
+    @Test
+    public void testThatPluggableInvalidationsTriggersRerun() {
+        // GIVEN a variety of pluggables
+        NotifFilter packageFilter = new PackageFilter(PACKAGE_1);
+        NotifPromoter idPromoter = new IdPromoter(4);
+        NotifSection section = new PackageSection(PACKAGE_1);
+        NotifComparator hypeComparator = new HypeComparator(PACKAGE_2);
+
+        mListBuilder.addPreGroupFilter(packageFilter);
+        mListBuilder.addPromoter(idPromoter);
+        mListBuilder.setSections(Arrays.asList(section));
+        mListBuilder.setComparators(Collections.singletonList(hypeComparator));
+
+        // GIVEN a set of random notifs
+        addNotif(0, PACKAGE_1);
+        addNotif(1, PACKAGE_2);
+        addNotif(2, PACKAGE_3);
+        dispatchBuild();
+
+        // WHEN each pluggable is invalidated THEN the list is re-rendered
+
+        clearInvocations(mOnRenderListListener);
+        packageFilter.invalidateList();
+        verify(mOnRenderListListener).onRenderList(anyList());
+
+        clearInvocations(mOnRenderListListener);
+        idPromoter.invalidateList();
+        verify(mOnRenderListListener).onRenderList(anyList());
+
+        clearInvocations(mOnRenderListListener);
+        section.invalidateList();
+        verify(mOnRenderListListener).onRenderList(anyList());
+
+        clearInvocations(mOnRenderListListener);
+        hypeComparator.invalidateList();
+        verify(mOnRenderListListener).onRenderList(anyList());
+    }
+
+    @Test
+    public void testNotifFiltersAreAllSentTheSameNow() {
+        // GIVEN three notif filters
+        NotifFilter filter1 = spy(new PackageFilter(PACKAGE_5));
+        NotifFilter filter2 = spy(new PackageFilter(PACKAGE_5));
+        NotifFilter filter3 = spy(new PackageFilter(PACKAGE_5));
+        mListBuilder.addPreGroupFilter(filter1);
+        mListBuilder.addPreGroupFilter(filter2);
+        mListBuilder.addPreGroupFilter(filter3);
+
+        // GIVEN the SystemClock is set to a particular time:
+        mSystemClock.setUptimeMillis(10047);
+
+        // WHEN the pipeline is kicked off on a list of notifs
+        addNotif(0, PACKAGE_1);
+        addNotif(1, PACKAGE_2);
+        dispatchBuild();
+
+        // THEN the value of `now` is the same for all calls to shouldFilterOut
+        verify(filter1).shouldFilterOut(mEntrySet.get(0), 10047);
+        verify(filter2).shouldFilterOut(mEntrySet.get(0), 10047);
+        verify(filter3).shouldFilterOut(mEntrySet.get(0), 10047);
+        verify(filter1).shouldFilterOut(mEntrySet.get(1), 10047);
+        verify(filter2).shouldFilterOut(mEntrySet.get(1), 10047);
+        verify(filter3).shouldFilterOut(mEntrySet.get(1), 10047);
+    }
+
+    @Test
+    public void testGroupTransformEntries() {
+        // GIVEN a registered OnBeforeTransformGroupsListener
+        RecordingOnBeforeTransformGroupsListener listener =
+                spy(new RecordingOnBeforeTransformGroupsListener());
+        mListBuilder.addOnBeforeTransformGroupsListener(listener);
+
+        // GIVEN some new notifs
+        addNotif(0, PACKAGE_1);
+        addGroupChild(1, PACKAGE_2, GROUP_1);
+        addGroupSummary(2, PACKAGE_2, GROUP_1);
+        addGroupChild(3, PACKAGE_2, GROUP_1);
+        addNotif(4, PACKAGE_3);
+        addGroupChild(5, PACKAGE_2, GROUP_1);
+
+        // WHEN we run the pipeline
+        dispatchBuild();
+
+        verifyBuiltList(
+                notif(0),
+                group(
+                        summary(2),
+                        child(1),
+                        child(3),
+                        child(5)
+                ),
+                notif(4)
+        );
+
+        // THEN all the new notifs, including the new GroupEntry, are passed to the listener
+        verify(listener).onBeforeTransformGroups(
+                Arrays.asList(
+                        mEntrySet.get(0),
+                        mBuiltList.get(1),
+                        mEntrySet.get(4)
+                )
+        );
+    }
+
+    @Test
+    public void testGroupTransformEntriesOnSecondRun() {
+        // GIVEN a registered OnBeforeTransformGroupsListener
+        RecordingOnBeforeTransformGroupsListener listener =
+                spy(new RecordingOnBeforeTransformGroupsListener());
+        mListBuilder.addOnBeforeTransformGroupsListener(listener);
+
+        // GIVEN some notifs that have already been added (two of which are in malformed groups)
+        addNotif(0, PACKAGE_1);
+        addGroupChild(1, PACKAGE_2, GROUP_1);
+        addGroupChild(2, PACKAGE_3, GROUP_2);
+
+        dispatchBuild();
+        clearInvocations(listener);
+
+        // WHEN we run the pipeline
+        addGroupSummary(3, PACKAGE_2, GROUP_1);
+        addGroupChild(4, PACKAGE_3, GROUP_2);
+        addGroupSummary(5, PACKAGE_3, GROUP_2);
+        addGroupChild(6, PACKAGE_3, GROUP_2);
+        addNotif(7, PACKAGE_2);
+
+        dispatchBuild();
+
+        verifyBuiltList(
+                notif(0),
+                notif(1),
+                group(
+                        summary(5),
+                        child(2),
+                        child(4),
+                        child(6)
+                ),
+                notif(7)
+        );
+
+        // THEN all the new notifs, including the new GroupEntry, are passed to the listener
+        verify(listener).onBeforeTransformGroups(
+                Arrays.asList(
+                        mEntrySet.get(0),
+                        mEntrySet.get(1),
+                        mBuiltList.get(2),
+                        mEntrySet.get(7)
+                )
+        );
+    }
+
+    @Test
+    public void testAnnulledGroupsHaveParentSetProperly() {
+        // GIVEN a list containing a small group that's already been built once
+        addGroupChild(0, PACKAGE_2, GROUP_2);
+        addGroupSummary(1, PACKAGE_2, GROUP_2);
+        addGroupChild(2, PACKAGE_2, GROUP_2);
+        dispatchBuild();
+
+        verifyBuiltList(
+                group(
+                        summary(1),
+                        child(0),
+                        child(2)
+                )
+        );
+        GroupEntry group = (GroupEntry) mBuiltList.get(0);
+
+        // WHEN a child is removed such that the group is no longer big enough
+        mEntrySet.remove(2);
+        dispatchBuild();
+
+        // THEN the group is annulled and its parent is set back to null
+        verifyBuiltList(
+                notif(0)
+        );
+        assertNull(group.getParent());
+
+        // but its previous parent indicates that it was added in the previous iteration
+        assertEquals(GroupEntry.ROOT_ENTRY, group.getPreviousParent());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testOutOfOrderPreGroupFilterInvalidationThrows() {
+        // GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage
+        NotifFilter filter = new PackageFilter(PACKAGE_5);
+        OnBeforeTransformGroupsListener listener = (list) -> filter.invalidateList();
+        mListBuilder.addPreGroupFilter(filter);
+        mListBuilder.addOnBeforeTransformGroupsListener(listener);
+
+        // WHEN we try to run the pipeline and the filter is invalidated
+        addNotif(0, PACKAGE_1);
+        dispatchBuild();
+
+        // THEN an exception is thrown
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testOutOfOrderPrompterInvalidationThrows() {
+        // GIVEN a NotifPromoter that gets invalidated during the sorting stage
+        NotifPromoter promoter = new IdPromoter(47);
+        OnBeforeSortListener listener =
+                (list) -> promoter.invalidateList();
+        mListBuilder.addPromoter(promoter);
+        mListBuilder.addOnBeforeSortListener(listener);
+
+        // WHEN we try to run the pipeline and the promoter is invalidated
+        addNotif(0, PACKAGE_1);
+        dispatchBuild();
+
+        // THEN an exception is thrown
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testOutOfOrderComparatorInvalidationThrows() {
+        // GIVEN a NotifComparator that gets invalidated during the finalizing stage
+        NotifComparator comparator = new HypeComparator(PACKAGE_5);
+        OnBeforeRenderListListener listener =
+                (list) -> comparator.invalidateList();
+        mListBuilder.setComparators(Collections.singletonList(comparator));
+        mListBuilder.addOnBeforeRenderListListener(listener);
+
+        // WHEN we try to run the pipeline and the comparator is invalidated
+        addNotif(0, PACKAGE_1);
+        dispatchBuild();
+
+        // THEN an exception is thrown
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testOutOfOrderPreRenderFilterInvalidationThrows() {
+        // GIVEN a PreRenderNotifFilter that gets invalidated during the finalizing stage
+        NotifFilter filter = new PackageFilter(PACKAGE_5);
+        OnBeforeRenderListListener listener = (list) -> filter.invalidateList();
+        mListBuilder.addPreRenderFilter(filter);
+        mListBuilder.addOnBeforeRenderListListener(listener);
+
+        // WHEN we try to run the pipeline and the PreRenderFilter is invalidated
+        addNotif(0, PACKAGE_1);
+        dispatchBuild();
+
+        // THEN an exception is thrown
+    }
+
+    @Test
+    public void testInOrderPreRenderFilter() {
+        // GIVEN a PreRenderFilter that gets invalidated during the grouping stage
+        NotifFilter filter = new PackageFilter(PACKAGE_5);
+        OnBeforeTransformGroupsListener listener = (list) -> filter.invalidateList();
+        mListBuilder.addPreRenderFilter(filter);
+        mListBuilder.addOnBeforeTransformGroupsListener(listener);
+
+        // WHEN we try to run the pipeline and the filter is invalidated
+        addNotif(0, PACKAGE_1);
+        dispatchBuild();
+
+        // THEN no exception thrown
+    }
+
+    /**
+     * Adds a notif to the collection that will be passed to the list builder when
+     * {@link #dispatchBuild()}s is called.
+     *
+     * @param index Index of this notification in the set. This must be the current size of the set.
+     *              it exists to improve readability of the resulting code, since later tests will
+     *              have to refer to notifs by index.
+     * @param packageId Package that the notif should be posted under
+     * @return A NotificationEntryBuilder that can be used to further modify the notif. Do not call
+     *         build() on the builder; that will be done on the next dispatchBuild().
+     */
+    private NotificationEntryBuilder addNotif(int index, String packageId) {
+        final NotificationEntryBuilder builder = new NotificationEntryBuilder()
+                .setPkg(packageId)
+                .setId(nextId(packageId))
+                .setRank(nextRank());
+
+        builder.modifyNotification(mContext)
+                .setContentTitle("Top level singleton")
+                .setChannelId("test_channel");
+
+        assertEquals(mEntrySet.size() + mPendingSet.size(), index);
+        mPendingSet.add(builder);
+        return builder;
+    }
+
+    /** Same behavior as {@link #addNotif(int, String)}. */
+    private NotificationEntryBuilder addGroupSummary(int index, String packageId, String groupId) {
+        final NotificationEntryBuilder builder = new NotificationEntryBuilder()
+                .setPkg(packageId)
+                .setId(nextId(packageId))
+                .setRank(nextRank());
+
+        builder.modifyNotification(mContext)
+                .setChannelId("test_channel")
+                .setContentTitle("Group summary")
+                .setGroup(groupId)
+                .setGroupSummary(true);
+
+        assertEquals(mEntrySet.size() + mPendingSet.size(), index);
+        mPendingSet.add(builder);
+        return builder;
+    }
+
+    private NotificationEntryBuilder addGroupChildWithTag(int index, String packageId,
+            String groupId, String tag) {
+        final NotificationEntryBuilder builder = new NotificationEntryBuilder()
+                .setTag(tag)
+                .setPkg(packageId)
+                .setId(nextId(packageId))
+                .setRank(nextRank());
+
+        builder.modifyNotification(mContext)
+                .setChannelId("test_channel")
+                .setContentTitle("Group child")
+                .setGroup(groupId);
+
+        assertEquals(mEntrySet.size() + mPendingSet.size(), index);
+        mPendingSet.add(builder);
+        return builder;
+    }
+
+    /** Same behavior as {@link #addNotif(int, String)}. */
+    private NotificationEntryBuilder addGroupChild(int index, String packageId, String groupId) {
+        return addGroupChildWithTag(index, packageId, groupId, null);
+    }
+
+    private int nextId(String packageName) {
+        Integer nextId = mNextIdMap.get(packageName);
+        if (nextId == null) {
+            nextId = 0;
+        }
+        mNextIdMap.put(packageName, nextId + 1);
+        return nextId;
+    }
+
+    private int nextRank() {
+        int nextRank = mNextRank;
+        mNextRank++;
+        return nextRank;
+    }
+
+    private void dispatchBuild() {
+        if (mPendingSet.size() > 0) {
+            for (NotificationEntryBuilder builder : mPendingSet) {
+                mEntrySet.add(builder.build());
+            }
+            mPendingSet.clear();
+        }
+
+        mReadyForBuildListener.onBuildList(mEntrySet);
+    }
+
+    private void verifyBuiltList(ExpectedEntry ...expectedEntries) {
+        try {
+            assertEquals(
+                    "List is the wrong length",
+                    expectedEntries.length,
+                    mBuiltList.size());
+
+            for (int i = 0; i < expectedEntries.length; i++) {
+                ListEntry outEntry = mBuiltList.get(i);
+                ExpectedEntry expectedEntry = expectedEntries[i];
+
+                if (expectedEntry instanceof ExpectedNotif) {
+                    assertEquals(
+                            "Entry " + i + " isn't a NotifEntry",
+                            NotificationEntry.class,
+                            outEntry.getClass());
+                    assertEquals(
+                            "Entry " + i + " doesn't match expected value.",
+                            ((ExpectedNotif) expectedEntry).entry, outEntry);
+                } else {
+                    ExpectedGroup cmpGroup = (ExpectedGroup) expectedEntry;
+
+                    assertEquals(
+                            "Entry " + i + " isn't a GroupEntry",
+                            GroupEntry.class,
+                            outEntry.getClass());
+
+                    GroupEntry outGroup = (GroupEntry) outEntry;
+
+                    assertEquals(
+                            "Summary notif for entry " + i
+                                    + " doesn't match expected value",
+                            cmpGroup.summary,
+                            outGroup.getSummary());
+                    assertEquals(
+                            "Summary notif for entry " + i
+                                        + " doesn't have proper parent",
+                            outGroup,
+                            outGroup.getSummary().getParent());
+
+                    assertEquals("Children for entry " + i,
+                            cmpGroup.children,
+                            outGroup.getChildren());
+
+                    for (int j = 0; j < outGroup.getChildren().size(); j++) {
+                        NotificationEntry child = outGroup.getChildren().get(j);
+                        assertEquals(
+                                "Child " + j + " for entry " + i
+                                        + " doesn't have proper parent",
+                                outGroup,
+                                child.getParent());
+                    }
+                }
+            }
+        } catch (AssertionError err) {
+            throw new AssertionError(
+                    "List under test failed verification:\n" + dumpTree(mBuiltList,
+                            true, ""), err);
+        }
+    }
+
+    private ExpectedNotif notif(int index) {
+        return new ExpectedNotif(mEntrySet.get(index));
+    }
+
+    private ExpectedGroup group(ExpectedSummary summary, ExpectedChild...children) {
+        return new ExpectedGroup(
+                summary.entry,
+                Arrays.stream(children)
+                        .map(child -> child.entry)
+                        .collect(Collectors.toList()));
+    }
+
+    private ExpectedSummary summary(int index) {
+        return new ExpectedSummary(mEntrySet.get(index));
+    }
+
+    private ExpectedChild child(int index) {
+        return new ExpectedChild(mEntrySet.get(index));
+    }
+
+    private abstract static class ExpectedEntry {
+    }
+
+    private static class ExpectedNotif extends ExpectedEntry {
+        public final NotificationEntry entry;
+
+        private ExpectedNotif(NotificationEntry entry) {
+            this.entry = entry;
+        }
+    }
+
+    private static class ExpectedGroup extends ExpectedEntry {
+        public final NotificationEntry summary;
+        public final List<NotificationEntry> children;
+
+        private ExpectedGroup(
+                NotificationEntry summary,
+                List<NotificationEntry> children) {
+            this.summary = summary;
+            this.children = children;
+        }
+    }
+
+    private static class ExpectedSummary {
+        public final NotificationEntry entry;
+
+        private ExpectedSummary(NotificationEntry entry) {
+            this.entry = entry;
+        }
+    }
+
+    private static class ExpectedChild {
+        public final NotificationEntry entry;
+
+        private ExpectedChild(NotificationEntry entry) {
+            this.entry = entry;
+        }
+    }
+
+    /** Filters out notifs from a particular package */
+    private static class PackageFilter extends NotifFilter {
+        private final String mPackageName;
+
+        private boolean mEnabled = true;
+
+        PackageFilter(String packageName) {
+            super("PackageFilter");
+
+            mPackageName = packageName;
+        }
+
+        @Override
+        public boolean shouldFilterOut(NotificationEntry entry, long now) {
+            return mEnabled && entry.getSbn().getPackageName().equals(mPackageName);
+        }
+
+        public void setEnabled(boolean enabled) {
+            mEnabled = enabled;
+        }
+    }
+
+    /** Filters out notifications with a particular tag */
+    private static class NotifFilterWithTag extends NotifFilter {
+        private final String mTag;
+
+        NotifFilterWithTag(String tag) {
+            super("NotifFilterWithTag_" + tag);
+            mTag = tag;
+        }
+
+        @Override
+        public boolean shouldFilterOut(NotificationEntry entry, long now) {
+            return Objects.equals(entry.getSbn().getTag(), mTag);
+        }
+    }
+
+    /** Promotes notifs with particular IDs */
+    private static class IdPromoter extends NotifPromoter {
+        private final List<Integer> mIds;
+
+        IdPromoter(Integer... ids) {
+            super("IdPromoter");
+            mIds = Arrays.asList(ids);
+        }
+
+        @Override
+        public boolean shouldPromoteToTopLevel(NotificationEntry child) {
+            return mIds.contains(child.getSbn().getId());
+        }
+    }
+
+    /** Sorts specific notifs above all others. */
+    private static class HypeComparator extends NotifComparator {
+
+        private final List<String> mPreferredPackages;
+
+        HypeComparator(String ...preferredPackages) {
+            super("HypeComparator");
+            mPreferredPackages = Arrays.asList(preferredPackages);
+        }
+
+        @Override
+        public int compare(ListEntry o1, ListEntry o2) {
+            boolean contains1 = mPreferredPackages.contains(
+                    o1.getRepresentativeEntry().getSbn().getPackageName());
+            boolean contains2 = mPreferredPackages.contains(
+                    o2.getRepresentativeEntry().getSbn().getPackageName());
+
+            return Boolean.compare(contains2, contains1);
+        }
+    }
+
+    /** Represents a section for the passed pkg */
+    private static class PackageSection extends NotifSection {
+        private final String mPackage;
+
+        PackageSection(String pkg) {
+            super("PackageSection_" + pkg);
+            mPackage = pkg;
+        }
+
+        @Override
+        public boolean isInSection(ListEntry entry) {
+            return entry.getRepresentativeEntry().getSbn().getPackageName().equals(mPackage);
+        }
+    }
+
+    private static class RecordingOnBeforeTransformGroupsListener
+            implements OnBeforeTransformGroupsListener {
+
+        @Override
+        public void onBeforeTransformGroups(List<ListEntry> list) { }
+    }
+
+    private static final String PACKAGE_1 = "com.test1";
+    private static final String PACKAGE_2 = "com.test2";
+    private static final String PACKAGE_3 = "org.test3";
+    private static final String PACKAGE_4 = "com.test4";
+    private static final String PACKAGE_5 = "com.test5";
+
+    private static final String GROUP_1 = "group_1";
+    private static final String GROUP_2 = "group_2";
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java
new file mode 100644
index 0000000..86c1eb97
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java
@@ -0,0 +1,377 @@
+/*
+ * 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.statusbar.notification.collection.coalescer;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.NotificationListenerService.RankingMap;
+import android.service.notification.StatusBarNotification;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.NotificationListener;
+import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
+import com.android.systemui.statusbar.RankingBuilder;
+import com.android.systemui.statusbar.notification.collection.NoManSimulator;
+import com.android.systemui.statusbar.notification.collection.NoManSimulator.NotifEvent;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.logging.NotifLog;
+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.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class GroupCoalescerTest extends SysuiTestCase {
+
+    private GroupCoalescer mCoalescer;
+
+    @Mock private NotificationListener mListenerService;
+    @Mock private GroupCoalescer.BatchableNotificationHandler mListener;
+    @Mock private NotifLog mLog;
+
+    @Captor private ArgumentCaptor<NotificationHandler> mListenerCaptor;
+
+    private final NoManSimulator mNoMan = new NoManSimulator();
+    private final FakeSystemClock mClock = new FakeSystemClock();
+    private final FakeExecutor mExecutor = new FakeExecutor(mClock);
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mCoalescer =
+                new GroupCoalescer(
+                        mExecutor,
+                        mClock,
+                        mLog,
+                        MIN_LINGER_DURATION,
+                        MAX_LINGER_DURATION);
+        mCoalescer.setNotificationHandler(mListener);
+        mCoalescer.attach(mListenerService);
+
+        verify(mListenerService).addNotificationHandler(mListenerCaptor.capture());
+        NotificationHandler serviceListener = checkNotNull(mListenerCaptor.getValue());
+        mNoMan.addListener(serviceListener);
+    }
+
+    @Test
+    public void testUngroupedNotificationsAreNotCoalesced() {
+        // WHEN a notification that doesn't have a group key is posted
+        NotifEvent notif1 = mNoMan.postNotif(
+                new NotificationEntryBuilder()
+                        .setId(0)
+                        .setPkg(TEST_PACKAGE_A));
+        mClock.advanceTime(MIN_LINGER_DURATION);
+
+        // THEN the event is passed through to the handler
+        verify(mListener).onNotificationPosted(notif1.sbn, notif1.rankingMap);
+
+        // Then the event isn't emitted in a batch
+        verify(mListener, never()).onNotificationBatchPosted(anyList());
+    }
+
+    @Test
+    public void testGroupedNotificationsAreCoalesced() {
+        // WHEN a notification that has a group key is posted
+        NotifEvent notif1 = mNoMan.postNotif(
+                new NotificationEntryBuilder()
+                        .setId(0)
+                        .setPkg(TEST_PACKAGE_A)
+                        .setGroup(mContext, GROUP_1));
+
+        // THEN the event is not passed on to the handler
+        verify(mListener, never()).onNotificationPosted(
+                any(StatusBarNotification.class),
+                any(RankingMap.class));
+
+        // Then the event isn't (yet) emitted in a batch
+        verify(mListener, never()).onNotificationBatchPosted(anyList());
+    }
+
+    @Test
+    public void testCoalescedNotificationsStillPassThroughRankingUpdate() {
+        // WHEN a notification that has a group key is posted
+        NotifEvent notif1 = mNoMan.postNotif(
+                new NotificationEntryBuilder()
+                        .setId(0)
+                        .setPkg(TEST_PACKAGE_A)
+                        .setGroup(mContext, GROUP_1));
+
+        // THEN the listener receives a ranking update instead of an add
+        verify(mListener).onNotificationRankingUpdate(notif1.rankingMap);
+    }
+
+    @Test
+    public void testCoalescedNotificationsArePosted() {
+        // GIVEN three notifs are posted that are part of the same group
+        NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_A)
+                .setId(1)
+                .setGroup(mContext, GROUP_1));
+
+        mClock.advanceTime(2);
+
+        NotifEvent notif2 = mNoMan.postNotif(new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_A)
+                .setId(2)
+                .setGroup(mContext, GROUP_1)
+                .setGroupSummary(mContext, true));
+
+        mClock.advanceTime(3);
+
+        NotifEvent notif3 = mNoMan.postNotif(new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_A)
+                .setId(3)
+                .setGroup(mContext, GROUP_1));
+
+        verify(mListener, never()).onNotificationPosted(
+                any(StatusBarNotification.class),
+                any(RankingMap.class));
+        verify(mListener, never()).onNotificationBatchPosted(anyList());
+
+        // WHEN enough time passes
+        mClock.advanceTime(MIN_LINGER_DURATION);
+
+        // THEN the coalesced notifs are applied. The summary is sorted to the front.
+        verify(mListener).onNotificationBatchPosted(Arrays.asList(
+                new CoalescedEvent(notif2.key, 1, notif2.sbn, notif2.ranking, null),
+                new CoalescedEvent(notif1.key, 0, notif1.sbn, notif1.ranking, null),
+                new CoalescedEvent(notif3.key, 2, notif3.sbn, notif3.ranking, null)
+        ));
+    }
+
+    @Test
+    public void testCoalescedEventsThatAreLaterUngroupedAreEmittedImmediatelyAndNotLater() {
+        // GIVEN a few newly posted notifications in the same group
+        NotifEvent notif1a = mNoMan.postNotif(new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_A)
+                .setId(1)
+                .setContentTitle(mContext, "Grouped message")
+                .setGroup(mContext, GROUP_1));
+        NotifEvent notif2 = mNoMan.postNotif(new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_A)
+                .setId(2)
+                .setGroup(mContext, GROUP_1));
+        NotifEvent notif3 = mNoMan.postNotif(new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_A)
+                .setId(3)
+                .setGroup(mContext, GROUP_1));
+
+        verify(mListener, never()).onNotificationPosted(
+                any(StatusBarNotification.class),
+                any(RankingMap.class));
+        verify(mListener, never()).onNotificationBatchPosted(anyList());
+
+        // WHEN one of them is updated to no longer be in the group
+        NotifEvent notif1b = mNoMan.postNotif(new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_A)
+                .setId(1)
+                .setContentTitle(mContext, "Oops no longer grouped"));
+
+        // THEN the pre-existing batch is first emitted
+        InOrder inOrder = inOrder(mListener);
+        inOrder.verify(mListener).onNotificationBatchPosted(Arrays.asList(
+                new CoalescedEvent(notif1a.key, 0, notif1a.sbn, notif1a.ranking, null),
+                new CoalescedEvent(notif2.key, 1, notif2.sbn, notif2.ranking, null),
+                new CoalescedEvent(notif3.key, 2, notif3.sbn, notif3.ranking, null)
+        ));
+
+        // THEN the updated notif is emitted
+        inOrder.verify(mListener).onNotificationPosted(notif1b.sbn, notif1b.rankingMap);
+
+        // WHEN the time runs out on the remainder of the group
+        clearInvocations(mListener);
+        mClock.advanceTime(MIN_LINGER_DURATION);
+
+        // THEN no lingering batch is applied
+        verify(mListener, never()).onNotificationBatchPosted(anyList());
+    }
+
+    @Test
+    public void testUpdatingCoalescedNotifTriggersBatchEmit() {
+        // GIVEN two grouped, coalesced notifications
+        NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_A)
+                .setId(1)
+                .setGroup(mContext, GROUP_1));
+        mClock.advanceTime(2);
+        NotifEvent notif2a = mNoMan.postNotif(new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_A)
+                .setId(2)
+                .setContentTitle(mContext, "Version 1")
+                .setGroup(mContext, GROUP_1));
+        mClock.advanceTime(4);
+
+        // WHEN one of them gets updated
+        NotifEvent notif2b = mNoMan.postNotif(new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_A)
+                .setId(2)
+                .setContentTitle(mContext, "Version 2")
+                .setGroup(mContext, GROUP_1));
+
+        // THEN first, the coalesced group is emitted
+        verify(mListener).onNotificationBatchPosted(Arrays.asList(
+                new CoalescedEvent(notif1.key, 0, notif1.sbn, notif1.ranking, null),
+                new CoalescedEvent(notif2a.key, 1, notif2a.sbn, notif2a.ranking, null)
+        ));
+        verify(mListener, never()).onNotificationPosted(
+                any(StatusBarNotification.class),
+                any(RankingMap.class));
+
+        // THEN second, the update is emitted
+        mClock.advanceTime(MIN_LINGER_DURATION);
+        verify(mListener).onNotificationBatchPosted(Collections.singletonList(
+                new CoalescedEvent(notif2b.key, 0, notif2b.sbn, notif2b.ranking, null)
+        ));
+    }
+
+    @Test
+    public void testRemovingCoalescedNotifTriggersBatchEmit() {
+        // GIVEN two grouped, coalesced notifications
+        NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_A)
+                .setId(1)
+                .setGroup(mContext, GROUP_1));
+        NotifEvent notif2a = mNoMan.postNotif(new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_A)
+                .setId(2)
+                .setGroup(mContext, GROUP_1));
+
+        // WHEN one of them gets retracted
+        NotifEvent notif2b = mNoMan.retractNotif(notif2a.sbn, 0);
+
+        // THEN first, the coalesced group is emitted
+        InOrder inOrder = inOrder(mListener);
+        inOrder.verify(mListener).onNotificationBatchPosted(Arrays.asList(
+                new CoalescedEvent(notif1.key, 0, notif1.sbn, notif1.ranking, null),
+                new CoalescedEvent(notif2a.key, 1, notif2a.sbn, notif2a.ranking, null)
+        ));
+
+        // THEN second, the removal is emitted
+        inOrder.verify(mListener).onNotificationRemoved(notif2b.sbn, notif2b.rankingMap, 0);
+    }
+
+    @Test
+    public void testRankingsAreUpdated() {
+        // GIVEN a couple coalesced notifications
+        NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_A)
+                .setId(1)
+                .setGroup(mContext, GROUP_1));
+        NotifEvent notif2 = mNoMan.postNotif(new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_A)
+                .setId(2)
+                .setGroup(mContext, GROUP_1));
+
+        // WHEN an update to an unrelated notification comes in that updates their rankings
+        Ranking ranking1b = new RankingBuilder()
+                .setKey(notif1.key)
+                .setLastAudiblyAlertedMs(4747)
+                .build();
+        Ranking ranking2b = new RankingBuilder()
+                .setKey(notif2.key)
+                .setLastAudiblyAlertedMs(3333)
+                .build();
+        mNoMan.setRanking(notif1.key, ranking1b);
+        mNoMan.setRanking(notif2.key, ranking2b);
+        mNoMan.postNotif(new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_B)
+                .setId(17));
+
+        // THEN they have the new rankings when they are eventually emitted
+        mClock.advanceTime(MIN_LINGER_DURATION);
+        verify(mListener).onNotificationBatchPosted(Arrays.asList(
+                new CoalescedEvent(notif1.key, 0, notif1.sbn, ranking1b, null),
+                new CoalescedEvent(notif2.key, 1, notif2.sbn, ranking2b, null)
+        ));
+    }
+
+    @Test
+    public void testMaxLingerDuration() {
+        // GIVEN five coalesced notifications that have collectively taken 20ms to arrive, 2ms
+        // longer than the max linger duration
+        NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_A)
+                .setId(1)
+                .setGroup(mContext, GROUP_1));
+        mClock.advanceTime(4);
+        NotifEvent notif2 = mNoMan.postNotif(new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_A)
+                .setId(2)
+                .setGroup(mContext, GROUP_1));
+        mClock.advanceTime(4);
+        NotifEvent notif3 = mNoMan.postNotif(new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_A)
+                .setId(3)
+                .setGroup(mContext, GROUP_1));
+        mClock.advanceTime(4);
+        NotifEvent notif4 = mNoMan.postNotif(new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_A)
+                .setId(4)
+                .setGroup(mContext, GROUP_1));
+        mClock.advanceTime(4);
+        NotifEvent notif5 = mNoMan.postNotif(new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_A)
+                .setId(5)
+                .setGroup(mContext, GROUP_1));
+        mClock.advanceTime(4);
+
+        // WHEN a sixth notification arrives
+        NotifEvent notif6 = mNoMan.postNotif(new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_A)
+                .setId(6)
+                .setGroup(mContext, GROUP_1));
+
+        // THEN the first five notifications are emitted in a batch
+        verify(mListener).onNotificationBatchPosted(Arrays.asList(
+                new CoalescedEvent(notif1.key, 0, notif1.sbn, notif1.ranking, null),
+                new CoalescedEvent(notif2.key, 1, notif2.sbn, notif2.ranking, null),
+                new CoalescedEvent(notif3.key, 2, notif3.sbn, notif3.ranking, null),
+                new CoalescedEvent(notif4.key, 3, notif4.sbn, notif4.ranking, null),
+                new CoalescedEvent(notif5.key, 4, notif5.sbn, notif5.ranking, null)
+        ));
+    }
+
+    private static final long MIN_LINGER_DURATION = 5;
+    private static final long MAX_LINGER_DURATION = 18;
+
+    private static final String TEST_PACKAGE_A = "com.test.package_a";
+    private static final String TEST_PACKAGE_B = "com.test.package_b";
+    private static final String GROUP_1 = "group_1";
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
index ea6c70a..701cf95 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
@@ -36,7 +36,7 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.RankingBuilder;
-import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
@@ -65,7 +65,7 @@
     @Mock private ActivityManagerInternal mActivityMangerInternal;
     @Mock private IPackageManager mIPackageManager;
     @Mock private DeviceProvisionedController mDeviceProvisionedController;
-    @Mock private NotifListBuilderImpl mNotifListBuilder;
+    @Mock private NotifPipeline mNotifPipeline;
     private Notification mNotification;
     private NotificationEntry mEntry;
     private DeviceProvisionedCoordinator mDeviceProvisionedCoordinator;
@@ -84,8 +84,8 @@
                 .build();
 
         ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
-        mDeviceProvisionedCoordinator.attach(null, mNotifListBuilder);
-        verify(mNotifListBuilder, times(1)).addPreGroupFilter(filterCaptor.capture());
+        mDeviceProvisionedCoordinator.attach(mNotifPipeline);
+        verify(mNotifPipeline, times(1)).addPreGroupFilter(filterCaptor.capture());
         mDeviceProvisionedFilter = filterCaptor.getValue();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java
index 01bca0d..eb1af7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java
@@ -16,63 +16,81 @@
 
 package com.android.systemui.statusbar.notification.collection.coordinator;
 
+import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.Notification;
 import android.os.Bundle;
-import android.os.Handler;
 import android.os.UserHandle;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.ArraySet;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.ForegroundServiceController;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.appops.AppOpsController;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
-import com.android.systemui.statusbar.notification.collection.NotifLifetimeExtender;
-import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
+import com.android.systemui.util.Assert;
+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.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.List;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class ForegroundCoordinatorTest extends SysuiTestCase {
     private static final String TEST_PKG = "test_pkg";
     private static final int NOTIF_USER_ID = 0;
 
-    @Mock private Handler mMainHandler;
     @Mock private ForegroundServiceController mForegroundServiceController;
     @Mock private AppOpsController mAppOpsController;
-    @Mock private NotifListBuilderImpl mNotifListBuilder;
-    @Mock private NotifCollection mNotifCollection;
+    @Mock private NotifPipeline mNotifPipeline;
+
+    @Captor private ArgumentCaptor<AppOpsController.Callback> mAppOpsCaptor;
 
     private NotificationEntry mEntry;
     private Notification mNotification;
     private ForegroundCoordinator mForegroundCoordinator;
     private NotifFilter mForegroundFilter;
+    private AppOpsController.Callback mAppOpsCallback;
     private NotifLifetimeExtender mForegroundNotifLifetimeExtender;
 
+    private FakeSystemClock mClock = new FakeSystemClock();
+    private FakeExecutor mExecutor = new FakeExecutor(mClock);
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mForegroundCoordinator = new ForegroundCoordinator(
-                mForegroundServiceController, mAppOpsController, mMainHandler);
+        Assert.sMainLooper = TestableLooper.get(this).getLooper();
+
+        mForegroundCoordinator =
+                new ForegroundCoordinator(
+                        mForegroundServiceController,
+                        mAppOpsController,
+                        mExecutor);
 
         mNotification = new Notification();
         mEntry = new NotificationEntryBuilder()
@@ -84,13 +102,15 @@
         ArgumentCaptor<NotifLifetimeExtender> lifetimeExtenderCaptor =
                 ArgumentCaptor.forClass(NotifLifetimeExtender.class);
 
-        mForegroundCoordinator.attach(mNotifCollection, mNotifListBuilder);
-        verify(mNotifListBuilder, times(1)).addPreGroupFilter(filterCaptor.capture());
-        verify(mNotifCollection, times(1)).addNotificationLifetimeExtender(
+        mForegroundCoordinator.attach(mNotifPipeline);
+        verify(mNotifPipeline, times(1)).addPreGroupFilter(filterCaptor.capture());
+        verify(mNotifPipeline, times(1)).addNotificationLifetimeExtender(
                 lifetimeExtenderCaptor.capture());
+        verify(mAppOpsController).addCallback(any(int[].class), mAppOpsCaptor.capture());
 
         mForegroundFilter = filterCaptor.getValue();
         mForegroundNotifLifetimeExtender = lifetimeExtenderCaptor.getValue();
+        mAppOpsCallback = mAppOpsCaptor.getValue();
     }
 
     @Test
@@ -185,4 +205,74 @@
         assertFalse(mForegroundNotifLifetimeExtender
                 .shouldExtendLifetime(mEntry, NotificationListenerService.REASON_CLICK));
     }
+
+    @Test
+    public void testAppOpsAreApplied() {
+        // GIVEN Three current notifications, two with the same key but from different users
+        NotificationEntry entry1 = new NotificationEntryBuilder()
+                .setUser(new UserHandle(NOTIF_USER_ID))
+                .setPkg(TEST_PKG)
+                .setId(1)
+                .build();
+        NotificationEntry entry2 = new NotificationEntryBuilder()
+                .setUser(new UserHandle(NOTIF_USER_ID))
+                .setPkg(TEST_PKG)
+                .setId(2)
+                .build();
+        NotificationEntry entry2Other = new NotificationEntryBuilder()
+                .setUser(new UserHandle(NOTIF_USER_ID + 1))
+                .setPkg(TEST_PKG)
+                .setId(2)
+                .build();
+        when(mNotifPipeline.getActiveNotifs()).thenReturn(List.of(entry1, entry2, entry2Other));
+
+        // GIVEN that entry2 is currently associated with a foreground service
+        when(mForegroundServiceController.getStandardLayoutKey(0, TEST_PKG))
+                .thenReturn(entry2.getKey());
+
+        // WHEN a new app ops code comes in
+        mAppOpsCallback.onActiveStateChanged(47, NOTIF_USER_ID, TEST_PKG, true);
+        mExecutor.runAllReady();
+
+        // THEN entry2's app ops are updated, but no one else's are
+        assertEquals(
+                new ArraySet<>(),
+                entry1.mActiveAppOps);
+        assertEquals(
+                new ArraySet<>(List.of(47)),
+                entry2.mActiveAppOps);
+        assertEquals(
+                new ArraySet<>(),
+                entry2Other.mActiveAppOps);
+    }
+
+    @Test
+    public void testAppOpsAreRemoved() {
+        // GIVEN One notification which is associated with app ops
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setUser(new UserHandle(NOTIF_USER_ID))
+                .setPkg(TEST_PKG)
+                .setId(2)
+                .build();
+        when(mNotifPipeline.getActiveNotifs()).thenReturn(List.of(entry));
+        when(mForegroundServiceController.getStandardLayoutKey(0, TEST_PKG))
+                .thenReturn(entry.getKey());
+
+        // GIVEN that the notification's app ops are already [47, 33]
+        mAppOpsCallback.onActiveStateChanged(47, NOTIF_USER_ID, TEST_PKG, true);
+        mAppOpsCallback.onActiveStateChanged(33, NOTIF_USER_ID, TEST_PKG, true);
+        mExecutor.runAllReady();
+        assertEquals(
+                new ArraySet<>(List.of(47, 33)),
+                entry.mActiveAppOps);
+
+        // WHEN one of the app ops is removed
+        mAppOpsCallback.onActiveStateChanged(47, NOTIF_USER_ID, TEST_PKG, false);
+        mExecutor.runAllReady();
+
+        // THEN the entry's active app ops are updated as well
+        assertEquals(
+                new ArraySet<>(List.of(33)),
+                entry.mActiveAppOps);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
index 979b8a9..5866d90 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
@@ -41,10 +41,11 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.RankingBuilder;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
-import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
@@ -66,7 +67,8 @@
     @Mock private BroadcastDispatcher mBroadcastDispatcher;
     @Mock private StatusBarStateController mStatusBarStateController;
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock private NotifListBuilderImpl mNotifListBuilder;
+    @Mock private HighPriorityProvider mHighPriorityProvider;
+    @Mock private NotifPipeline mNotifPipeline;
 
     private NotificationEntry mEntry;
     private KeyguardCoordinator mKeyguardCoordinator;
@@ -78,15 +80,15 @@
         mKeyguardCoordinator = new KeyguardCoordinator(
                 mContext, mMainHandler, mKeyguardStateController, mLockscreenUserManager,
                 mBroadcastDispatcher, mStatusBarStateController,
-                mKeyguardUpdateMonitor);
+                mKeyguardUpdateMonitor, mHighPriorityProvider);
 
         mEntry = new NotificationEntryBuilder()
                 .setUser(new UserHandle(NOTIF_USER_ID))
                 .build();
 
         ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
-        mKeyguardCoordinator.attach(null, mNotifListBuilder);
-        verify(mNotifListBuilder, times(1)).addPreRenderFilter(filterCaptor.capture());
+        mKeyguardCoordinator.attach(mNotifPipeline);
+        verify(mNotifPipeline, times(1)).addPreRenderFilter(filterCaptor.capture());
         mKeyguardFilter = filterCaptor.getValue();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
index d3b16c3..e84f9cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
@@ -33,7 +33,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.RankingBuilder;
-import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
@@ -50,7 +50,7 @@
 public class RankingCoordinatorTest extends SysuiTestCase {
 
     @Mock private StatusBarStateController mStatusBarStateController;
-    @Mock private NotifListBuilderImpl mNotifListBuilder;
+    @Mock private NotifPipeline mNotifPipeline;
     private NotificationEntry mEntry;
     private RankingCoordinator mRankingCoordinator;
     private NotifFilter mRankingFilter;
@@ -62,8 +62,8 @@
         mEntry = new NotificationEntryBuilder().build();
 
         ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
-        mRankingCoordinator.attach(null, mNotifListBuilder);
-        verify(mNotifListBuilder, times(1)).addPreGroupFilter(filterCaptor.capture());
+        mRankingCoordinator.attach(mNotifPipeline);
+        verify(mNotifPipeline, times(1)).addPreGroupFilter(filterCaptor.capture());
         mRankingFilter = filterCaptor.getValue();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescerTest.java
deleted file mode 100644
index 7ff3240..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescerTest.java
+++ /dev/null
@@ -1,323 +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.statusbar.notification.collection.notifcollection;
-
-import static com.android.internal.util.Preconditions.checkNotNull;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyList;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-
-import android.service.notification.NotificationListenerService.Ranking;
-import android.service.notification.NotificationListenerService.RankingMap;
-import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
-import com.android.systemui.statusbar.RankingBuilder;
-import com.android.systemui.statusbar.notification.collection.NoManSimulator;
-import com.android.systemui.statusbar.notification.collection.NoManSimulator.NotifEvent;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.logging.NotifLog;
-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.Captor;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Arrays;
-import java.util.Collections;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class GroupCoalescerTest extends SysuiTestCase {
-
-    private GroupCoalescer mCoalescer;
-
-    @Mock private NotificationListener mListenerService;
-    @Mock private GroupCoalescer.BatchableNotificationHandler mListener;
-    @Mock private NotifLog mLog;
-
-    @Captor private ArgumentCaptor<NotificationHandler> mListenerCaptor;
-
-    private final NoManSimulator mNoMan = new NoManSimulator();
-    private final FakeSystemClock mClock = new FakeSystemClock();
-    private final FakeExecutor mExecutor = new FakeExecutor(mClock);
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        mCoalescer =
-                new GroupCoalescer(
-                        mExecutor,
-                        mClock,
-                        mLog,
-                        LINGER_DURATION);
-        mCoalescer.setNotificationHandler(mListener);
-        mCoalescer.attach(mListenerService);
-
-        verify(mListenerService).addNotificationHandler(mListenerCaptor.capture());
-        NotificationHandler serviceListener = checkNotNull(mListenerCaptor.getValue());
-        mNoMan.addListener(serviceListener);
-    }
-
-    @Test
-    public void testUngroupedNotificationsAreNotCoalesced() {
-        // WHEN a notification that doesn't have a group key is posted
-        NotifEvent notif1 = mNoMan.postNotif(
-                new NotificationEntryBuilder()
-                        .setId(0)
-                        .setPkg(TEST_PACKAGE_A));
-        mClock.advanceTime(LINGER_DURATION);
-
-        // THEN the event is passed through to the handler
-        verify(mListener).onNotificationPosted(notif1.sbn, notif1.rankingMap);
-
-        // Then the event isn't emitted in a batch
-        verify(mListener, never()).onNotificationBatchPosted(anyList());
-    }
-
-    @Test
-    public void testGroupedNotificationsAreCoalesced() {
-        // WHEN a notification that has a group key is posted
-        NotifEvent notif1 = mNoMan.postNotif(
-                new NotificationEntryBuilder()
-                        .setId(0)
-                        .setPkg(TEST_PACKAGE_A)
-                        .setGroup(mContext, GROUP_1));
-
-        // THEN the event is not passed on to the handler
-        verify(mListener, never()).onNotificationPosted(
-                any(StatusBarNotification.class),
-                any(RankingMap.class));
-
-        // Then the event isn't (yet) emitted in a batch
-        verify(mListener, never()).onNotificationBatchPosted(anyList());
-    }
-
-    @Test
-    public void testCoalescedNotificationsStillPassThroughRankingUpdate() {
-        // WHEN a notification that has a group key is posted
-        NotifEvent notif1 = mNoMan.postNotif(
-                new NotificationEntryBuilder()
-                        .setId(0)
-                        .setPkg(TEST_PACKAGE_A)
-                        .setGroup(mContext, GROUP_1));
-
-        // THEN the listener receives a ranking update instead of an add
-        verify(mListener).onNotificationRankingUpdate(notif1.rankingMap);
-    }
-
-    @Test
-    public void testCoalescedNotificationsArePosted() {
-        // GIVEN three notifs are posted that are part of the same group
-        NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
-                .setPkg(TEST_PACKAGE_A)
-                .setId(1)
-                .setGroup(mContext, GROUP_1));
-
-        NotifEvent notif2 = mNoMan.postNotif(new NotificationEntryBuilder()
-                .setPkg(TEST_PACKAGE_A)
-                .setId(2)
-                .setGroup(mContext, GROUP_1)
-                .setGroupSummary(mContext, true));
-
-        NotifEvent notif3 = mNoMan.postNotif(new NotificationEntryBuilder()
-                .setPkg(TEST_PACKAGE_A)
-                .setId(3)
-                .setGroup(mContext, GROUP_1));
-
-        verify(mListener, never()).onNotificationPosted(
-                any(StatusBarNotification.class),
-                any(RankingMap.class));
-        verify(mListener, never()).onNotificationBatchPosted(anyList());
-
-        // WHEN enough time passes
-        mClock.advanceTime(LINGER_DURATION);
-
-        // THEN the coalesced notifs are applied. The summary is sorted to the front.
-        verify(mListener).onNotificationBatchPosted(Arrays.asList(
-                new CoalescedEvent(notif2.key, 1, notif2.sbn, notif2.ranking, null),
-                new CoalescedEvent(notif1.key, 0, notif1.sbn, notif1.ranking, null),
-                new CoalescedEvent(notif3.key, 2, notif3.sbn, notif3.ranking, null)
-        ));
-    }
-
-    @Test
-    public void testCoalescedEventsThatAreLaterUngroupedAreEmittedImmediatelyAndNotLater() {
-        // GIVEN a few newly posted notifications in the same group
-        NotifEvent notif1a = mNoMan.postNotif(new NotificationEntryBuilder()
-                .setPkg(TEST_PACKAGE_A)
-                .setId(1)
-                .setContentTitle(mContext, "Grouped message")
-                .setGroup(mContext, GROUP_1));
-        NotifEvent notif2 = mNoMan.postNotif(new NotificationEntryBuilder()
-                .setPkg(TEST_PACKAGE_A)
-                .setId(2)
-                .setGroup(mContext, GROUP_1));
-        NotifEvent notif3 = mNoMan.postNotif(new NotificationEntryBuilder()
-                .setPkg(TEST_PACKAGE_A)
-                .setId(3)
-                .setGroup(mContext, GROUP_1));
-
-        verify(mListener, never()).onNotificationPosted(
-                any(StatusBarNotification.class),
-                any(RankingMap.class));
-        verify(mListener, never()).onNotificationBatchPosted(anyList());
-
-        // WHEN one of them is updated to no longer be in the group
-        NotifEvent notif1b = mNoMan.postNotif(new NotificationEntryBuilder()
-                .setPkg(TEST_PACKAGE_A)
-                .setId(1)
-                .setContentTitle(mContext, "Oops no longer grouped"));
-
-        // THEN the pre-existing batch is first emitted
-        InOrder inOrder = inOrder(mListener);
-        inOrder.verify(mListener).onNotificationBatchPosted(Arrays.asList(
-                new CoalescedEvent(notif1a.key, 0, notif1a.sbn, notif1a.ranking, null),
-                new CoalescedEvent(notif2.key, 1, notif2.sbn, notif2.ranking, null),
-                new CoalescedEvent(notif3.key, 2, notif3.sbn, notif3.ranking, null)
-        ));
-
-        // THEN the updated notif is emitted
-        inOrder.verify(mListener).onNotificationPosted(notif1b.sbn, notif1b.rankingMap);
-
-        // WHEN the time runs out on the remainder of the group
-        clearInvocations(mListener);
-        mClock.advanceTime(LINGER_DURATION);
-
-        // THEN no lingering batch is applied
-        verify(mListener, never()).onNotificationBatchPosted(anyList());
-    }
-
-    @Test
-    public void testUpdatingCoalescedNotifTriggersBatchEmit() {
-        // GIVEN two grouped, coalesced notifications
-        NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
-                .setPkg(TEST_PACKAGE_A)
-                .setId(1)
-                .setGroup(mContext, GROUP_1));
-        NotifEvent notif2a = mNoMan.postNotif(new NotificationEntryBuilder()
-                .setPkg(TEST_PACKAGE_A)
-                .setId(2)
-                .setContentTitle(mContext, "Version 1")
-                .setGroup(mContext, GROUP_1));
-
-        // WHEN one of them gets updated
-        NotifEvent notif2b = mNoMan.postNotif(new NotificationEntryBuilder()
-                .setPkg(TEST_PACKAGE_A)
-                .setId(2)
-                .setContentTitle(mContext, "Version 2")
-                .setGroup(mContext, GROUP_1));
-
-        // THEN first, the coalesced group is emitted
-        verify(mListener).onNotificationBatchPosted(Arrays.asList(
-                new CoalescedEvent(notif1.key, 0, notif1.sbn, notif1.ranking, null),
-                new CoalescedEvent(notif2a.key, 1, notif2a.sbn, notif2a.ranking, null)
-        ));
-        verify(mListener, never()).onNotificationPosted(
-                any(StatusBarNotification.class),
-                any(RankingMap.class));
-
-        // THEN second, the update is emitted
-        mClock.advanceTime(LINGER_DURATION);
-        verify(mListener).onNotificationBatchPosted(Collections.singletonList(
-                new CoalescedEvent(notif2b.key, 0, notif2b.sbn, notif2b.ranking, null)
-        ));
-    }
-
-    @Test
-    public void testRemovingCoalescedNotifTriggersBatchEmit() {
-        // GIVEN two grouped, coalesced notifications
-        NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
-                .setPkg(TEST_PACKAGE_A)
-                .setId(1)
-                .setGroup(mContext, GROUP_1));
-        NotifEvent notif2a = mNoMan.postNotif(new NotificationEntryBuilder()
-                .setPkg(TEST_PACKAGE_A)
-                .setId(2)
-                .setGroup(mContext, GROUP_1));
-
-        // WHEN one of them gets retracted
-        NotifEvent notif2b = mNoMan.retractNotif(notif2a.sbn, 0);
-
-        // THEN first, the coalesced group is emitted
-        InOrder inOrder = inOrder(mListener);
-        inOrder.verify(mListener).onNotificationBatchPosted(Arrays.asList(
-                new CoalescedEvent(notif1.key, 0, notif1.sbn, notif1.ranking, null),
-                new CoalescedEvent(notif2a.key, 1, notif2a.sbn, notif2a.ranking, null)
-        ));
-
-        // THEN second, the removal is emitted
-        inOrder.verify(mListener).onNotificationRemoved(notif2b.sbn, notif2b.rankingMap, 0);
-    }
-
-    @Test
-    public void testRankingsAreUpdated() {
-        // GIVEN a couple coalesced notifications
-        NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
-                .setPkg(TEST_PACKAGE_A)
-                .setId(1)
-                .setGroup(mContext, GROUP_1));
-        NotifEvent notif2 = mNoMan.postNotif(new NotificationEntryBuilder()
-                .setPkg(TEST_PACKAGE_A)
-                .setId(2)
-                .setGroup(mContext, GROUP_1));
-
-        // WHEN an update to an unrelated notification comes in that updates their rankings
-        Ranking ranking1b = new RankingBuilder()
-                .setKey(notif1.key)
-                .setLastAudiblyAlertedMs(4747)
-                .build();
-        Ranking ranking2b = new RankingBuilder()
-                .setKey(notif2.key)
-                .setLastAudiblyAlertedMs(3333)
-                .build();
-        mNoMan.setRanking(notif1.key, ranking1b);
-        mNoMan.setRanking(notif2.key, ranking2b);
-        mNoMan.postNotif(new NotificationEntryBuilder()
-                .setPkg(TEST_PACKAGE_B)
-                .setId(17));
-
-        // THEN they have the new rankings when they are eventually emitted
-        mClock.advanceTime(LINGER_DURATION);
-        verify(mListener).onNotificationBatchPosted(Arrays.asList(
-                new CoalescedEvent(notif1.key, 0, notif1.sbn, ranking1b, null),
-                new CoalescedEvent(notif2.key, 1, notif2.sbn, ranking2b, null)
-        ));
-    }
-
-    private static final long LINGER_DURATION = 4700;
-
-    private static final String TEST_PACKAGE_A = "com.test.package_a";
-    private static final String TEST_PACKAGE_B = "com.test.package_b";
-    private static final String GROUP_1 = "group_1";
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java
deleted file mode 100644
index 6fa1a89..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java
+++ /dev/null
@@ -1,184 +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.statusbar.notification.collection.provider;
-
-import static android.app.NotificationManager.IMPORTANCE_HIGH;
-import static android.app.NotificationManager.IMPORTANCE_LOW;
-import static android.app.NotificationManager.IMPORTANCE_MIN;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.Person;
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class IsHighPriorityProviderTest extends SysuiTestCase {
-    private IsHighPriorityProvider mIsHighPriorityProvider;
-
-    @Before
-    public void setup() {
-        mIsHighPriorityProvider = new IsHighPriorityProvider();
-    }
-
-    @Test
-    public void testCache() {
-        // GIVEN a notification with high importance
-        final NotificationEntry entryHigh = new NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_HIGH)
-                .build();
-
-        // GIVEN notification with min importance
-        final NotificationEntry entryMin = new NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_MIN)
-                .build();
-
-        // WHEN we get the value for the high priority entry
-        assertTrue(mIsHighPriorityProvider.get(entryHigh));
-
-        // THEN the value is cached, so even when passed an entryMin, we still get high priority
-        assertTrue(mIsHighPriorityProvider.get(entryMin));
-
-        // UNTIL the provider is invalidated
-        mIsHighPriorityProvider.invalidate();
-
-        // THEN the priority is recalculated
-        assertFalse(mIsHighPriorityProvider.get(entryMin));
-    }
-
-    @Test
-    public void highImportance() {
-        // GIVEN notification has high importance
-        final NotificationEntry entry = new NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_HIGH)
-                .build();
-
-        // THEN it has high priority
-        assertTrue(mIsHighPriorityProvider.get(entry));
-    }
-
-    @Test
-    public void peopleNotification() {
-        // GIVEN notification is low importance but has a person associated with it
-        final Notification notification = new Notification.Builder(mContext, "test")
-                .addPerson(
-                        new Person.Builder()
-                                .setName("name")
-                                .setKey("abc")
-                                .setUri("uri")
-                                .setBot(true)
-                                .build())
-                .build();
-
-        final NotificationEntry entry = new NotificationEntryBuilder()
-                .setNotification(notification)
-                .setImportance(IMPORTANCE_LOW)
-                .build();
-
-        // THEN it has high priority
-        assertTrue(mIsHighPriorityProvider.get(entry));
-    }
-
-    @Test
-    public void messagingStyle() {
-        // GIVEN notification is low importance but has messaging style
-        final Notification notification = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.MessagingStyle(""))
-                .build();
-
-        final NotificationEntry entry = new NotificationEntryBuilder()
-                .setNotification(notification)
-                .build();
-
-        // THEN it has high priority
-        assertTrue(mIsHighPriorityProvider.get(entry));
-    }
-
-    @Test
-    public void lowImportanceForeground() {
-        // GIVEN notification is low importance and is associated with a foreground service
-        final Notification notification = mock(Notification.class);
-        when(notification.isForegroundService()).thenReturn(true);
-
-        final NotificationEntry entry = new NotificationEntryBuilder()
-                .setNotification(notification)
-                .setImportance(IMPORTANCE_LOW)
-                .build();
-
-        // THEN it has high priority
-        assertTrue(mIsHighPriorityProvider.get(entry));
-    }
-
-    @Test
-    public void minImportanceForeground() {
-        // GIVEN notification is low importance and is associated with a foreground service
-        final Notification notification = mock(Notification.class);
-        when(notification.isForegroundService()).thenReturn(true);
-
-        final NotificationEntry entry = new NotificationEntryBuilder()
-                .setNotification(notification)
-                .setImportance(IMPORTANCE_MIN)
-                .build();
-
-        // THEN it does NOT have high priority
-        assertFalse(mIsHighPriorityProvider.get(entry));
-    }
-
-    @Test
-    public void userChangeTrumpsHighPriorityCharacteristics() {
-        // GIVEN notification has high priority characteristics but the user changed the importance
-        // to less than IMPORTANCE_DEFAULT (ie: IMPORTANCE_LOW or IMPORTANCE_MIN)
-        final Notification notification = new Notification.Builder(mContext, "test")
-                .addPerson(
-                        new Person.Builder()
-                                .setName("name")
-                                .setKey("abc")
-                                .setUri("uri")
-                                .setBot(true)
-                                .build())
-                .setStyle(new Notification.MessagingStyle(""))
-                .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
-                .build();
-
-        final NotificationChannel channel = new NotificationChannel("a", "a",
-                IMPORTANCE_LOW);
-        channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
-
-        final NotificationEntry entry = new NotificationEntryBuilder()
-                .setNotification(notification)
-                .setChannel(channel)
-                .build();
-
-        // THEN it does NOT have high priority
-        assertFalse(mIsHighPriorityProvider.get(entry));
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
new file mode 100644
index 0000000..d7214f3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
@@ -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.systemui.statusbar.notification.row;
+
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.widget.RemoteViews;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+
+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 NotifRemoteViewCacheImplTest extends SysuiTestCase {
+
+    private NotifRemoteViewCacheImpl mNotifRemoteViewCache;
+    private NotificationEntry mEntry;
+    private NotificationEntryListener mEntryListener;
+    @Mock private RemoteViews mRemoteViews;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mEntry = new NotificationEntryBuilder().build();
+
+        NotificationEntryManager entryManager = mock(NotificationEntryManager.class);
+        mNotifRemoteViewCache = new NotifRemoteViewCacheImpl(entryManager);
+        ArgumentCaptor<NotificationEntryListener> entryListenerCaptor =
+                ArgumentCaptor.forClass(NotificationEntryListener.class);
+        verify(entryManager).addNotificationEntryListener(entryListenerCaptor.capture());
+        mEntryListener = entryListenerCaptor.getValue();
+    }
+
+    @Test
+    public void testPutCachedView() {
+        // GIVEN an initialized cache for an entry.
+        mEntryListener.onPendingEntryAdded(mEntry);
+
+        // WHEN a notification's cached remote views is put in.
+        mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews);
+
+        // THEN the remote view is cached.
+        assertTrue(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
+        assertEquals(
+                "Cached remote view is not the one we put in.",
+                mRemoteViews,
+                mNotifRemoteViewCache.getCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
+    }
+
+    @Test
+    public void testRemoveCachedView() {
+        // GIVEN a cache with a cached view.
+        mEntryListener.onPendingEntryAdded(mEntry);
+        mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews);
+
+        // WHEN we remove the cached view.
+        mNotifRemoteViewCache.removeCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED);
+
+        // THEN the remote view is not cached.
+        assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
+    }
+
+    @Test
+    public void testClearCache() {
+        // GIVEN a non-empty cache.
+        mEntryListener.onPendingEntryAdded(mEntry);
+        mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews);
+        mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_EXPANDED, mRemoteViews);
+
+        // WHEN we clear the cache.
+        mNotifRemoteViewCache.clearCache(mEntry);
+
+        // THEN the cache is empty.
+        assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
+        assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_EXPANDED));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index f916fe5..cb9da6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -22,11 +22,16 @@
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.Notification;
 import android.content.Context;
@@ -35,16 +40,17 @@
 import android.os.Looper;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
-import android.util.ArrayMap;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.RemoteViews;
+import android.widget.TextView;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.filters.Suppress;
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.InflationTask;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
@@ -57,6 +63,8 @@
 import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.util.HashMap;
 import java.util.concurrent.CountDownLatch;
@@ -73,8 +81,11 @@
     private Notification.Builder mBuilder;
     private ExpandableNotificationRow mRow;
 
+    @Mock private NotifRemoteViewCache mCache;
+
     @Before
     public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
         mBuilder = new Notification.Builder(mContext).setSmallIcon(
                 R.drawable.ic_person)
                 .setContentTitle("Title")
@@ -83,7 +94,9 @@
         ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency).createRow(
                 mBuilder.build());
         mRow = spy(row);
-        mNotificationInflater = new NotificationContentInflater();
+        mNotificationInflater = new NotificationContentInflater(
+                mCache,
+                mock(NotificationRemoteInputManager.class));
     }
 
     @Test
@@ -174,7 +187,9 @@
                 result,
                 FLAG_CONTENT_VIEW_EXPANDED,
                 0,
-                new ArrayMap() /* cachedContentViews */, mRow,
+                mock(NotifRemoteViewCache.class),
+                mRow.getEntry(),
+                mRow,
                 true /* isNewView */, (v, p, r) -> true,
                 new InflationCallback() {
                     @Override
@@ -244,6 +259,71 @@
                 NotificationContentInflater.canReapplyRemoteView(mediaView, decoratedMediaView));
     }
 
+    @Test
+    public void testUsesSameViewWhenCachedPossibleToReuse() throws Exception {
+        // GIVEN a cached view.
+        RemoteViews contractedRemoteView = mBuilder.createContentView();
+        when(mCache.hasCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
+                .thenReturn(true);
+        when(mCache.getCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
+                .thenReturn(contractedRemoteView);
+
+        // GIVEN existing bound view with same layout id.
+        View view = contractedRemoteView.apply(mContext, null /* parent */);
+        mRow.getPrivateLayout().setContractedChild(view);
+
+        // WHEN inflater inflates
+        inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow);
+
+        // THEN the view should be re-used
+        assertEquals("Binder inflated a new view even though the old one was cached and usable.",
+                view, mRow.getPrivateLayout().getContractedChild());
+    }
+
+    @Test
+    public void testInflatesNewViewWhenCachedNotPossibleToReuse() throws Exception {
+        // GIVEN a cached remote view.
+        RemoteViews contractedRemoteView = mBuilder.createHeadsUpContentView();
+        when(mCache.hasCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
+                .thenReturn(true);
+        when(mCache.getCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
+                .thenReturn(contractedRemoteView);
+
+        // GIVEN existing bound view with different layout id.
+        View view = new TextView(mContext);
+        mRow.getPrivateLayout().setContractedChild(view);
+
+        // WHEN inflater inflates
+        inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow);
+
+        // THEN the view should be a new view
+        assertNotEquals("Binder (somehow) used the same view when inflating.",
+                view, mRow.getPrivateLayout().getContractedChild());
+    }
+
+    @Test
+    public void testInflationCachesCreatedRemoteView() throws Exception {
+        // WHEN inflater inflates
+        inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow);
+
+        // THEN inflater informs cache of the new remote view
+        verify(mCache).putCachedView(
+                eq(mRow.getEntry()),
+                eq(FLAG_CONTENT_VIEW_CONTRACTED),
+                any());
+    }
+
+    @Test
+    public void testUnbindRemovesCachedRemoteView() {
+        // WHEN inflated unbinds content
+        mNotificationInflater.unbindContent(mRow.getEntry(), mRow, FLAG_CONTENT_VIEW_HEADS_UP);
+
+        // THEN inflated informs cache to remove remote view
+        verify(mCache).removeCachedView(
+                eq(mRow.getEntry()),
+                eq(FLAG_CONTENT_VIEW_HEADS_UP));
+    }
+
     private static void inflateAndWait(NotificationContentInflater inflater,
             @InflationFlag int contentToInflate,
             ExpandableNotificationRow row)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
index 675b3ef..84c6513 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
@@ -16,20 +16,17 @@
 
 package com.android.systemui.statusbar.notification.row;
 
-import static org.junit.Assert.assertFalse;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.AppOpsManager;
-import android.graphics.drawable.Icon;
 import android.util.ArraySet;
 import android.view.NotificationHeaderView;
 import android.view.View;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
new file mode 100644
index 0000000..5fc40cc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -0,0 +1,825 @@
+/*
+ * 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.statusbar.notification.row;
+
+import static android.app.Notification.FLAG_BUBBLE;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.print.PrintManager.PRINT_SPOOLER_PACKAGE_NAME;
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.INotificationManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.app.PendingIntent;
+import android.app.Person;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.Slog;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.BubblesTestActivity;
+import com.android.systemui.statusbar.SbnBuilder;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.mockito.stubbing.Answer;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NotificationConversationInfoTest extends SysuiTestCase {
+    private static final String TEST_PACKAGE_NAME = "test_package";
+    private static final String TEST_SYSTEM_PACKAGE_NAME = PRINT_SPOOLER_PACKAGE_NAME;
+    private static final int TEST_UID = 1;
+    private static final String TEST_CHANNEL = "test_channel";
+    private static final String TEST_CHANNEL_NAME = "TEST CHANNEL NAME";
+    private static final String CONVERSATION_ID = "convo";
+
+    private TestableLooper mTestableLooper;
+    private NotificationConversationInfo mNotificationInfo;
+    private NotificationChannel mNotificationChannel;
+    private NotificationChannel mConversationChannel;
+    private StatusBarNotification mSbn;
+    private NotificationEntry mEntry;
+    private StatusBarNotification mBubbleSbn;
+    private NotificationEntry mBubbleEntry;
+    @Mock
+    private ShortcutInfo mShortcutInfo;
+    private Drawable mImage;
+
+    @Rule
+    public MockitoRule mockito = MockitoJUnit.rule();
+    @Mock
+    private MetricsLogger mMetricsLogger;
+    @Mock
+    private INotificationManager mMockINotificationManager;
+    @Mock
+    private PackageManager mMockPackageManager;
+    @Mock
+    private VisualStabilityManager mVisualStabilityManager;
+    @Mock
+    private BubbleController mBubbleController;
+    @Mock
+    private LauncherApps mLauncherApps;
+    @Mock
+    private ShortcutManager mShortcutManager;
+    @Mock
+    private NotificationGuts mNotificationGuts;
+
+    @Before
+    public void setUp() throws Exception {
+        mTestableLooper = TestableLooper.get(this);
+
+        mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
+        mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
+        mDependency.injectTestDependency(BubbleController.class, mBubbleController);
+        // Inflate the layout
+        final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
+        mNotificationInfo = (NotificationConversationInfo) layoutInflater.inflate(
+                R.layout.notification_conversation_info,
+                null);
+        mNotificationInfo.mShowHomeScreen = true;
+        mNotificationInfo.setGutsParent(mNotificationGuts);
+        doAnswer((Answer<Object>) invocation -> {
+            mNotificationInfo.handleCloseControls(true, false);
+            return null;
+        }).when(mNotificationGuts).closeControls(anyInt(), anyInt(), eq(true), eq(false));
+        // Our view is never attached to a window so the View#post methods in NotificationInfo never
+        // get called. Setting this will skip the post and do the action immediately.
+        mNotificationInfo.mSkipPost = true;
+
+        // PackageManager must return a packageInfo and applicationInfo.
+        final PackageInfo packageInfo = new PackageInfo();
+        packageInfo.packageName = TEST_PACKAGE_NAME;
+        when(mMockPackageManager.getPackageInfo(eq(TEST_PACKAGE_NAME), anyInt()))
+                .thenReturn(packageInfo);
+        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.uid = TEST_UID;  // non-zero
+        when(mMockPackageManager.getApplicationInfo(eq(TEST_PACKAGE_NAME), anyInt())).thenReturn(
+                applicationInfo);
+        final PackageInfo systemPackageInfo = new PackageInfo();
+        systemPackageInfo.packageName = TEST_SYSTEM_PACKAGE_NAME;
+        when(mMockPackageManager.getPackageInfo(eq(TEST_SYSTEM_PACKAGE_NAME), anyInt()))
+                .thenReturn(systemPackageInfo);
+        when(mMockPackageManager.getPackageInfo(eq("android"), anyInt()))
+                .thenReturn(packageInfo);
+
+        when(mShortcutInfo.getShortLabel()).thenReturn("Convo name");
+        List<ShortcutInfo> shortcuts = Arrays.asList(mShortcutInfo);
+        when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcuts);
+        mImage = mContext.getDrawable(R.drawable.ic_star);
+        when(mLauncherApps.getShortcutBadgedIconDrawable(eq(mShortcutInfo),
+                anyInt())).thenReturn(mImage);
+
+        mNotificationChannel = new NotificationChannel(
+                TEST_CHANNEL, TEST_CHANNEL_NAME, IMPORTANCE_LOW);
+
+        Notification notification = new Notification.Builder(mContext, mNotificationChannel.getId())
+                .setShortcutId(CONVERSATION_ID)
+                .setStyle(new Notification.MessagingStyle(new Person.Builder().setName("m").build())
+                        .addMessage(new Notification.MessagingStyle.Message(
+                                "hello!", 1000, new Person.Builder().setName("other").build())))
+                .build();
+        mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
+                notification, UserHandle.CURRENT, null, 0);
+        mEntry = new NotificationEntryBuilder().setSbn(mSbn).build();
+
+        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0,
+                new Intent(mContext, BubblesTestActivity.class), 0);
+        mBubbleSbn = new SbnBuilder(mSbn).setBubbleMetadata(
+                new Notification.BubbleMetadata.Builder()
+                        .createIntentBubble(bubbleIntent,
+                                Icon.createWithResource(mContext, R.drawable.android)).build())
+                .build();
+        mBubbleEntry = new NotificationEntryBuilder().setSbn(mBubbleSbn).build();
+
+        mConversationChannel = new NotificationChannel(
+                TEST_CHANNEL + " : " + CONVERSATION_ID, TEST_CHANNEL_NAME, IMPORTANCE_LOW);
+        mConversationChannel.setConversationId(TEST_CHANNEL, CONVERSATION_ID);
+        when(mMockINotificationManager.getConversationNotificationChannel(anyString(), anyInt(),
+                anyString(), eq(TEST_CHANNEL), eq(false), eq(CONVERSATION_ID)))
+                .thenReturn(mConversationChannel);
+    }
+
+    @Test
+    public void testBindNotification_SetsShortcutIcon() {
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+        final ImageView view = mNotificationInfo.findViewById(R.id.conversation_icon);
+        assertEquals(mImage, view.getDrawable());
+    }
+
+    @Test
+    public void testBindNotification_SetsTextApplicationName() {
+        when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+        final TextView textView = mNotificationInfo.findViewById(R.id.pkg_name);
+        assertTrue(textView.getText().toString().contains("App Name"));
+        assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
+    }
+/**
+    @Test
+    public void testBindNotification_SetsTextChannelName() {
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+        final TextView textView = mNotificationInfo.findViewById(R.id.parent_channel_name);
+        assertTrue(textView.getText().toString().contains(mNotificationChannel.getName()));
+        assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
+    }
+*/
+    @Test
+    public void testBindNotification_SetsTextGroupName() throws Exception {
+        NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
+        when(mMockINotificationManager.getNotificationChannelGroupForPackage(
+               anyString(), anyString(), anyInt())).thenReturn(group);
+        mNotificationChannel.setGroup(group.getId());
+        mConversationChannel.setGroup(group.getId());
+
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+        final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
+        assertTrue(textView.getText().toString().contains(group.getName()));
+        assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
+        assertEquals(VISIBLE, textView.getVisibility());
+        assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.group_divider).getVisibility());
+    }
+
+    @Test
+    public void testBindNotification_GroupNameHiddenIfNoGroup() {
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+        final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
+        assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
+        assertEquals(GONE, textView.getVisibility());
+        assertEquals(GONE, mNotificationInfo.findViewById(R.id.group_divider).getVisibility());
+    }
+
+    @Test
+    public void testBindNotification_noDelegate() {
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+        final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
+        assertEquals(GONE, nameView.getVisibility());
+        final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider);
+        assertEquals(GONE, dividerView.getVisibility());
+    }
+
+    @Test
+    public void testBindNotification_delegate() throws Exception {
+        mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, "other", 0, null, TEST_UID, 0,
+                mSbn.getNotification(), UserHandle.CURRENT, null, 0);
+        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.uid = 7;  // non-zero
+        when(mMockPackageManager.getApplicationInfo(eq("other"), anyInt())).thenReturn(
+                applicationInfo);
+        when(mMockPackageManager.getApplicationLabel(any())).thenReturn("Other");
+
+        NotificationEntry entry = new NotificationEntryBuilder().setSbn(mSbn).build();
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                entry,
+                null,
+                null,
+                null,
+                true);
+        final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
+        assertEquals(VISIBLE, nameView.getVisibility());
+        assertTrue(nameView.getText().toString().contains("Proxied"));
+        final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider);
+        assertEquals(VISIBLE, dividerView.getVisibility());
+    }
+
+    @Test
+    public void testBindNotification_SetsOnClickListenerForSettings() {
+        final CountDownLatch latch = new CountDownLatch(1);
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                (View v, NotificationChannel c, int appUid) -> {
+                    assertEquals(mConversationChannel, c);
+                    latch.countDown();
+                },
+                null,
+                null,
+                true);
+
+        final View settingsButton = mNotificationInfo.findViewById(R.id.info);
+        settingsButton.performClick();
+        // Verify that listener was triggered.
+        assertEquals(0, latch.getCount());
+    }
+
+    @Test
+    public void testBindNotification_SettingsButtonInvisibleWhenNoClickListener() {
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+        final View settingsButton = mNotificationInfo.findViewById(R.id.info);
+        assertTrue(settingsButton.getVisibility() != View.VISIBLE);
+    }
+
+    @Test
+    public void testBindNotification_SettingsButtonInvisibleWhenDeviceUnprovisioned() {
+        final CountDownLatch latch = new CountDownLatch(1);
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                (View v, NotificationChannel c, int appUid) -> {
+                    assertEquals(mNotificationChannel, c);
+                    latch.countDown();
+                },
+                null,
+                null,
+                false);
+        final View settingsButton = mNotificationInfo.findViewById(R.id.info);
+        assertTrue(settingsButton.getVisibility() != View.VISIBLE);
+    }
+
+    @Test
+    public void testBindNotification_bubbleActionVisibleWhenCanBubble()  {
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mBubbleEntry,
+                null,
+                null,
+                null,
+                true);
+
+        View bubbleView = mNotificationInfo.findViewById(R.id.bubble);
+        assertEquals(View.VISIBLE, bubbleView.getVisibility());
+    }
+
+    @Test
+    public void testBindNotification_bubbleActionVisibleWhenCannotBubble()  {
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+
+        View bubbleView = mNotificationInfo.findViewById(R.id.bubble);
+        assertEquals(View.GONE, bubbleView.getVisibility());
+    }
+
+    @Test
+    public void testAddToHome() throws Exception {
+        when(mShortcutManager.isRequestPinShortcutSupported()).thenReturn(true);
+
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mBubbleEntry,
+                null,
+                null,
+                null,
+                true);
+
+
+        // Promote it
+        mNotificationInfo.findViewById(R.id.home).performClick();
+        mTestableLooper.processAllMessages();
+
+        verify(mShortcutManager, times(1)).requestPinShortcut(mShortcutInfo, null);
+        verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
+                anyString(), anyInt(), any());
+    }
+
+    @Test
+    public void testSnooze() throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mBubbleEntry,
+                null,
+                null,
+                (View v, int hours) -> {
+                    latch.countDown();
+                },
+                true);
+
+
+        // Promote it
+        mNotificationInfo.findViewById(R.id.snooze).performClick();
+        mTestableLooper.processAllMessages();
+
+        assertEquals(0, latch.getCount());
+        verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
+                anyString(), anyInt(), any());
+    }
+
+    @Test
+    public void testBubble_promotesBubble() throws Exception {
+        mNotificationChannel.setAllowBubbles(false);
+        mConversationChannel.setAllowBubbles(false);
+
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mBubbleEntry,
+                null,
+                null,
+                null,
+                true);
+
+        assertFalse(mBubbleEntry.isBubble());
+
+        // Promote it
+        mNotificationInfo.findViewById(R.id.bubble).performClick();
+        mTestableLooper.processAllMessages();
+
+        verify(mBubbleController, times(1)).onUserCreatedBubbleFromNotification(mBubbleEntry);
+        ArgumentCaptor<NotificationChannel> captor =
+                ArgumentCaptor.forClass(NotificationChannel.class);
+        verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+                anyString(), anyInt(), captor.capture());
+        assertTrue(captor.getValue().canBubble());
+    }
+
+    @Test
+    public void testBubble_demotesBubble() throws Exception {
+        mBubbleEntry.getSbn().getNotification().flags |= FLAG_BUBBLE;
+
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mBubbleEntry,
+                null,
+                null,
+                null,
+                true);
+
+        assertTrue(mBubbleEntry.isBubble());
+
+        // Demote it
+        mNotificationInfo.findViewById(R.id.bubble).performClick();
+        mTestableLooper.processAllMessages();
+
+        verify(mBubbleController, times(1)).onUserDemotedBubbleFromNotification(mBubbleEntry);
+        ArgumentCaptor<NotificationChannel> captor =
+                ArgumentCaptor.forClass(NotificationChannel.class);
+        verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+                anyString(), anyInt(), captor.capture());
+        assertFalse(captor.getValue().canBubble());
+    }
+
+    @Test
+    public void testFavorite_favorite() throws Exception {
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+
+
+        Button fave = mNotificationInfo.findViewById(R.id.fave);
+        assertEquals(mContext.getString(R.string.notification_conversation_favorite),
+                fave.getText().toString());
+
+        fave.performClick();
+        mTestableLooper.processAllMessages();
+
+        ArgumentCaptor<NotificationChannel> captor =
+                ArgumentCaptor.forClass(NotificationChannel.class);
+        verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+                anyString(), anyInt(), captor.capture());
+        assertTrue(captor.getValue().canBypassDnd());
+    }
+
+    @Test
+    public void testFavorite_unfavorite() throws Exception {
+        mNotificationChannel.setBypassDnd(true);
+        mConversationChannel.setBypassDnd(true);
+
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+
+        Button fave = mNotificationInfo.findViewById(R.id.fave);
+        assertEquals(mContext.getString(R.string.notification_conversation_unfavorite),
+                fave.getText().toString());
+
+        fave.performClick();
+        mTestableLooper.processAllMessages();
+
+        ArgumentCaptor<NotificationChannel> captor =
+                ArgumentCaptor.forClass(NotificationChannel.class);
+        verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+                anyString(), anyInt(), captor.capture());
+        assertFalse(captor.getValue().canBypassDnd());
+    }
+
+    @Test
+    public void testDemote() throws Exception {
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+
+
+        ImageButton demote = mNotificationInfo.findViewById(R.id.demote);
+        demote.performClick();
+        mTestableLooper.processAllMessages();
+
+        ArgumentCaptor<NotificationChannel> captor =
+                ArgumentCaptor.forClass(NotificationChannel.class);
+        verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+                anyString(), anyInt(), captor.capture());
+        assertTrue(captor.getValue().isDemoted());
+    }
+
+    @Test
+    public void testMute_mute() throws Exception {
+        mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
+        mConversationChannel.setImportance(IMPORTANCE_DEFAULT);
+
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+
+        Button mute = mNotificationInfo.findViewById(R.id.mute);
+        assertEquals(mContext.getString(R.string.notification_conversation_mute),
+                mute.getText().toString());
+
+        mute.performClick();
+        mTestableLooper.processAllMessages();
+
+        ArgumentCaptor<NotificationChannel> captor =
+                ArgumentCaptor.forClass(NotificationChannel.class);
+        verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+                anyString(), anyInt(), captor.capture());
+        assertEquals(IMPORTANCE_LOW, captor.getValue().getImportance());
+    }
+
+    @Test
+    public void testMute_unmute() throws Exception {
+        mNotificationChannel.setImportance(IMPORTANCE_LOW);
+        mNotificationChannel.setOriginalImportance(IMPORTANCE_HIGH);
+        mConversationChannel.setImportance(IMPORTANCE_LOW);
+        mConversationChannel.setOriginalImportance(IMPORTANCE_HIGH);
+
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+
+
+        Button mute = mNotificationInfo.findViewById(R.id.mute);
+        assertEquals(mContext.getString(R.string.notification_conversation_unmute),
+                mute.getText().toString());
+
+        mute.performClick();
+        mTestableLooper.processAllMessages();
+
+        ArgumentCaptor<NotificationChannel> captor =
+                ArgumentCaptor.forClass(NotificationChannel.class);
+        verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+                anyString(), anyInt(), captor.capture());
+        assertEquals(IMPORTANCE_HIGH, captor.getValue().getImportance());
+    }
+
+    @Test
+    public void testBindNotification_createsNewChannel() throws Exception {
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+        verify(mMockINotificationManager, times(1)).createConversationNotificationChannelForPackage(
+                anyString(), anyInt(), anyString(), any(), eq(CONVERSATION_ID));
+    }
+
+    @Test
+    public void testBindNotification_doesNotCreateNewChannelIfExists() throws Exception {
+        mNotificationChannel.setConversationId("", CONVERSATION_ID);
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+        verify(mMockINotificationManager, never()).createConversationNotificationChannelForPackage(
+                anyString(), anyInt(), anyString(), any(), eq(CONVERSATION_ID));
+    }
+
+    @Test
+    public void testAdjustImportanceTemporarilyAllowsReordering() {
+        mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
+        mConversationChannel.setImportance(IMPORTANCE_DEFAULT);
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                true);
+
+        mNotificationInfo.findViewById(R.id.mute).performClick();
+
+        verify(mVisualStabilityManager).temporarilyAllowReordering();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index ccc9496..4e27770 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -70,6 +70,7 @@
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -113,6 +114,7 @@
     @Mock private DeviceProvisionedController mDeviceProvisionedController;
     @Mock private StatusBar mStatusBar;
     @Mock private AccessibilityManager mAccessibilityManager;
+    @Mock private HighPriorityProvider mHighPriorityProvider;
 
     @Before
     public void setUp() {
@@ -128,7 +130,7 @@
         when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
 
         mGutsManager = new NotificationGutsManager(mContext, mVisualStabilityManager,
-                () -> mStatusBar, mHandler, mAccessibilityManager);
+                () -> mStatusBar, mHandler, mAccessibilityManager, mHighPriorityProvider);
         mGutsManager.setUpWithPresenter(mPresenter, mStackScroller,
                 mCheckSaveListener, mOnSettingsClickListener);
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
@@ -391,6 +393,7 @@
                 .build();
 
         when(row.getIsNonblockable()).thenReturn(false);
+        when(mHighPriorityProvider.isHighPriority(entry)).thenReturn(true);
         StatusBarNotification statusBarNotification = entry.getSbn();
         mGutsManager.initializeNotificationInfo(row, notificationInfoView);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index f513c2d..c62487a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.notification.row;
 
-import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
@@ -50,13 +49,10 @@
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationChannelGroup;
-import android.app.PendingIntent;
-import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
 import android.os.IBinder;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -76,9 +72,6 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.BubbleController;
-import com.android.systemui.bubbles.BubblesTestActivity;
-import com.android.systemui.statusbar.SbnBuilder;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -116,8 +109,6 @@
     private Set<NotificationChannel> mDefaultNotificationChannelSet = new HashSet<>();
     private StatusBarNotification mSbn;
     private NotificationEntry mEntry;
-    private StatusBarNotification mBubbleSbn;
-    private NotificationEntry mBubbleEntry;
 
     @Rule
     public MockitoRule mockito = MockitoJUnit.rule();
@@ -131,8 +122,6 @@
     private NotificationBlockingHelperManager mBlockingHelperManager;
     @Mock
     private VisualStabilityManager mVisualStabilityManager;
-    @Mock
-    private BubbleController mBubbleController;
 
     @Before
     public void setUp() throws Exception {
@@ -143,7 +132,6 @@
 
         mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
         mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
-        mDependency.injectTestDependency(BubbleController.class, mBubbleController);
         // Inflate the layout
         final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
         mNotificationInfo = (NotificationInfo) layoutInflater.inflate(R.layout.notification_info,
@@ -185,15 +173,6 @@
                 new Notification(), UserHandle.CURRENT, null, 0);
         mEntry = new NotificationEntryBuilder().setSbn(mSbn).build();
 
-        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0,
-                new Intent(mContext, BubblesTestActivity.class), 0);
-        mBubbleSbn = new SbnBuilder(mSbn).setBubbleMetadata(
-                new Notification.BubbleMetadata.Builder()
-                        .setIntent(bubbleIntent)
-                        .setIcon(Icon.createWithResource(mContext, R.drawable.android)).build())
-                .build();
-        mBubbleEntry = new NotificationEntryBuilder().setSbn(mBubbleSbn).build();
-
         Settings.Secure.putInt(mContext.getContentResolver(),
                 NOTIFICATION_NEW_INTERRUPTION_MODEL, 1);
     }
@@ -765,7 +744,7 @@
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mNotificationChannelSet,
-                mBubbleEntry,
+                mEntry,
                 null,
                 null,
                 null,
@@ -785,7 +764,7 @@
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mNotificationChannelSet,
-                mBubbleEntry,
+                mEntry,
                 null,
                 null,
                 null,
@@ -797,162 +776,6 @@
     }
 
     @Test
-    public void testBindNotification_bubbleIsSelected() throws Exception {
-        mBubbleEntry.getSbn().getNotification().flags |= FLAG_BUBBLE;
-        mNotificationInfo.bindNotification(
-                mMockPackageManager,
-                mMockINotificationManager,
-                mVisualStabilityManager,
-                TEST_PACKAGE_NAME,
-                mNotificationChannel,
-                mNotificationChannelSet,
-                mBubbleEntry,
-                null,
-                null,
-                null,
-                true,
-                false,
-                IMPORTANCE_DEFAULT,
-                true);
-
-        View bubbleView = mNotificationInfo.findViewById(R.id.bubble);
-        assertEquals(View.VISIBLE, bubbleView.getVisibility());
-        assertTrue(bubbleView.isSelected());
-    }
-
-    @Test
-    public void testBindNotification_whenCanBubble() throws Exception {
-        mNotificationInfo.bindNotification(
-                mMockPackageManager,
-                mMockINotificationManager,
-                mVisualStabilityManager,
-                TEST_PACKAGE_NAME,
-                mNotificationChannel,
-                mNotificationChannelSet,
-                mBubbleEntry,
-                null,
-                null,
-                null,
-                true,
-                false,
-                IMPORTANCE_DEFAULT,
-                true);
-
-        View bubbleView = mNotificationInfo.findViewById(R.id.bubble);
-        assertEquals(View.VISIBLE, bubbleView.getVisibility());
-        assertFalse(bubbleView.isSelected());
-    }
-
-    @Test
-    public void testBindNotification_whenCantBubble() throws Exception {
-        mNotificationInfo.bindNotification(
-                mMockPackageManager,
-                mMockINotificationManager,
-                mVisualStabilityManager,
-                TEST_PACKAGE_NAME,
-                mNotificationChannel,
-                mNotificationChannelSet,
-                mEntry,
-                null,
-                null,
-                null,
-                true,
-                false,
-                IMPORTANCE_DEFAULT,
-                true);
-        View bubbleView = mNotificationInfo.findViewById(R.id.bubble);
-        assertEquals(View.GONE, bubbleView.getVisibility());
-    }
-
-    @Test
-    public void testBubble_promotesBubble() throws Exception {
-        mNotificationInfo.bindNotification(
-                mMockPackageManager,
-                mMockINotificationManager,
-                mVisualStabilityManager,
-                TEST_PACKAGE_NAME,
-                mNotificationChannel,
-                mNotificationChannelSet,
-                mBubbleEntry,
-                null,
-                null,
-                null,
-                true,
-                false,
-                IMPORTANCE_DEFAULT,
-                true);
-
-        assertFalse(mBubbleEntry.isBubble());
-
-        // Promote it
-        mNotificationInfo.findViewById(R.id.bubble).performClick();
-        mNotificationInfo.findViewById(R.id.done).performClick();
-        mNotificationInfo.handleCloseControls(true, false);
-
-        verify(mBubbleController, times(1)).onUserCreatedBubbleFromNotification(mBubbleEntry);
-    }
-
-    @Test
-    public void testAlert_demotesBubble() throws Exception {
-        mBubbleEntry.getSbn().getNotification().flags |= FLAG_BUBBLE;
-
-        mNotificationInfo.bindNotification(
-                mMockPackageManager,
-                mMockINotificationManager,
-                mVisualStabilityManager,
-                TEST_PACKAGE_NAME,
-                mNotificationChannel,
-                mNotificationChannelSet,
-                mBubbleEntry,
-                null,
-                null,
-                null,
-                true,
-                false,
-                IMPORTANCE_DEFAULT,
-                true);
-
-        assertTrue(mBubbleEntry.isBubble());
-
-        // Demote it
-        mNotificationInfo.findViewById(R.id.alert).performClick();
-        mNotificationInfo.findViewById(R.id.done).performClick();
-        mNotificationInfo.handleCloseControls(true, false);
-
-        verify(mBubbleController, times(1)).onUserDemotedBubbleFromNotification(mBubbleEntry);
-    }
-
-    @Test
-    public void testSilence_demotesBubble() throws Exception {
-        mBubbleEntry.getSbn().getNotification().flags |= FLAG_BUBBLE;
-
-        mNotificationInfo.bindNotification(
-                mMockPackageManager,
-                mMockINotificationManager,
-                mVisualStabilityManager,
-                TEST_PACKAGE_NAME,
-                mNotificationChannel,
-                mNotificationChannelSet,
-                mBubbleEntry,
-                null,
-                null,
-                null,
-                true,
-                false,
-                IMPORTANCE_DEFAULT,
-                true);
-
-        assertTrue(mBubbleEntry.isBubble());
-
-        // Demote it
-        mNotificationInfo.findViewById(R.id.silence).performClick();
-        mNotificationInfo.findViewById(R.id.done).performClick();
-        mNotificationInfo.handleCloseControls(true, false);
-
-        verify(mBubbleController, times(1)).onUserDemotedBubbleFromNotification(mBubbleEntry);
-    }
-
-    @Test
     public void testBindNotification_DoesNotUpdateNotificationChannel() throws Exception {
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
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 f48c40c..b33d26f 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
@@ -132,16 +132,6 @@
     }
 
     @Test
-    public void testNoAppOpsInSlowSwipe_biDirectionalSwipe() {
-        NotificationMenuRow row = new NotificationMenuRow(mContext, true);
-        row.createMenu(mRow, null);
-
-        ViewGroup container = (ViewGroup) row.getMenuView();
-        // in the new interruption model there is only the blocking item
-        assertEquals(1, container.getChildCount());
-    }
-
-    @Test
     public void testIsSnappedAndOnSameSide() {
         NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index 003d803..51f214d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -44,8 +44,12 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
 import com.android.systemui.statusbar.notification.people.PeopleHubSectionFooterViewAdapter;
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import org.junit.Before;
@@ -58,7 +62,7 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class NotificationSectionsManagerTest extends SysuiTestCase {
 
     @Rule public final MockitoRule mockitoRule = MockitoJUnit.rule();
@@ -68,30 +72,47 @@
     @Mock private StatusBarStateController mStatusBarStateController;
     @Mock private ConfigurationController mConfigurationController;
     @Mock private PeopleHubSectionFooterViewAdapter mPeopleHubAdapter;
+    @Mock private NotificationSectionsFeatureManager mSectionsFeatureManager;
+    @Mock private NotificationRowComponent mNotificationRowComponent;
+    @Mock private ActivatableNotificationViewController mActivatableNotificationViewController;
 
     private NotificationSectionsManager mSectionsManager;
 
     @Before
     public void setUp() {
+        when(mSectionsFeatureManager.getNumberOfBuckets()).thenReturn(2);
+        when(mNotificationRowComponent.getActivatableNotificationViewController()).thenReturn(
+                mActivatableNotificationViewController
+        );
         mSectionsManager =
                 new NotificationSectionsManager(
-                        mNssl,
                         mActivityStarterDelegate,
                         mStatusBarStateController,
                         mConfigurationController,
                         mPeopleHubAdapter,
-                        2);
+                        mSectionsFeatureManager,
+                        new NotificationRowComponent.Builder() {
+                    @Override
+                    public NotificationRowComponent.Builder activatableNotificationView(
+                            ActivatableNotificationView view) {
+                        return this;
+                    }
+
+                    @Override
+                    public NotificationRowComponent build() {
+                        return mNotificationRowComponent;
+                    }});
         // Required in order for the header inflation to work properly
         when(mNssl.generateLayoutParams(any(AttributeSet.class)))
                 .thenReturn(new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
-        mSectionsManager.initialize(LayoutInflater.from(mContext));
+        mSectionsManager.initialize(mNssl, LayoutInflater.from(mContext));
         when(mNssl.indexOfChild(any(View.class))).thenReturn(-1);
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
     }
 
     @Test(expected =  IllegalStateException.class)
     public void testDuplicateInitializeThrows() {
-        mSectionsManager.initialize(LayoutInflater.from(mContext));
+        mSectionsManager.initialize(mNssl, LayoutInflater.from(mContext));
     }
 
     @Test
@@ -267,8 +288,6 @@
                     ExpandableNotificationRow notifRow = mock(ExpandableNotificationRow.class,
                             RETURNS_DEEP_STUBS);
                     when(notifRow.getVisibility()).thenReturn(View.VISIBLE);
-                    when(notifRow.getEntry().isHighPriority())
-                            .thenReturn(children[i] == ChildType.HIPRI);
                     when(notifRow.getEntry().getBucket()).thenReturn(
                             children[i] == ChildType.HIPRI ? BUCKET_ALERTING : BUCKET_SILENT);
                     when(notifRow.getParent()).thenReturn(mNssl);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 39f037c..7602e45 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -46,7 +46,6 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
-import com.android.systemui.ActivityStarterDelegate;
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
@@ -71,8 +70,9 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.logging.NotifLog;
-import com.android.systemui.statusbar.notification.people.PeopleHubSectionFooterViewAdapter;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.FooterView;
@@ -85,9 +85,8 @@
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.util.DeviceConfigProxyFake;
+import com.android.systemui.util.leak.LeakDetector;
 
 import org.junit.After;
 import org.junit.Before;
@@ -129,6 +128,8 @@
     @Mock private NotificationRoundnessManager mNotificationRoundnessManager;
     @Mock private KeyguardBypassController mKeyguardBypassController;
     @Mock private ZenModeController mZenModeController;
+    @Mock private NotificationSectionsManager mNotificationSectionsManager;
+    @Mock private NotificationSection mNotificationSection;
     private TestableNotificationEntryManager mEntryManager;
     private int mOriginalInterruptionModelSetting;
 
@@ -165,16 +166,23 @@
                         mock(NotificationFilter.class),
                         mock(NotifLog.class),
                         mock(NotificationSectionsFeatureManager.class),
-                        mock(PeopleNotificationIdentifier.class)
+                        mock(PeopleNotificationIdentifier.class),
+                        mock(HighPriorityProvider.class)
                 ),
                 mock(NotificationEntryManager.KeyguardEnvironment.class),
-                mock(FeatureFlags.class));
+                mock(FeatureFlags.class),
+                () -> mock(NotificationRowBinder.class),
+                () -> mRemoteInputManager,
+                mock(LeakDetector.class));
         mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
         mEntryManager.setUpForTest(mock(NotificationPresenter.class), null, mHeadsUpManager);
 
 
         NotificationShelf notificationShelf = mock(NotificationShelf.class);
-
+        when(mNotificationSectionsManager.createSectionsForBuckets()).thenReturn(
+                new NotificationSection[]{
+                        mNotificationSection
+                });
         // The actual class under test.  You may need to work with this class directly when
         // testing anonymous class members of mStackScroller, like mMenuEventListener,
         // which refer to members of NotificationStackScrollLayout. The spy
@@ -183,17 +191,14 @@
         mStackScrollerInternal = new NotificationStackScrollLayout(getContext(), null,
                 true /* allowLongPress */, mNotificationRoundnessManager,
                 mock(DynamicPrivacyController.class),
-                mock(ConfigurationController.class),
-                mock(ActivityStarterDelegate.class),
                 mock(SysuiStatusBarStateController.class),
                 mHeadsUpManager,
                 mKeyguardBypassController,
                 new FalsingManagerFake(),
                 mock(NotificationLockscreenUserManager.class),
                 mock(NotificationGutsManager.class),
-                new NotificationSectionsFeatureManager(new DeviceConfigProxyFake(), mContext),
-                mock(PeopleHubSectionFooterViewAdapter.class),
-                mZenModeController);
+                mZenModeController,
+                mNotificationSectionsManager);
         mStackScroller = spy(mStackScrollerInternal);
         mStackScroller.setShelf(notificationShelf);
         mStackScroller.setStatusBar(mBar);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 5907a0a..769b774 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -67,7 +67,7 @@
     @Mock
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     @Mock
-    private StatusBarWindowController mStatusBarWindowController;
+    private NotificationShadeWindowController mNotificationShadeWindowController;
     @Mock
     private DozeScrimController mDozeScrimController;
     @Mock
@@ -104,9 +104,9 @@
         res.addOverride(com.android.internal.R.integer.config_wakeUpDelayDoze, 0);
         mBiometricUnlockController = new BiometricUnlockController(mContext, mDozeScrimController,
                 mKeyguardViewMediator, mScrimController, mStatusBar, mShadeController,
-                mStatusBarWindowController, mKeyguardStateController, mHandler, mUpdateMonitor,
-                res.getResources(), mKeyguardBypassController, mDozeParameters, mMetricsLogger,
-                mDumpController);
+                mNotificationShadeWindowController, mKeyguardStateController, mHandler,
+                mUpdateMonitor, res.getResources(), mKeyguardBypassController, mDozeParameters,
+                mMetricsLogger, mDumpController);
         mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
index 8decae3..ae87eef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
@@ -64,7 +64,8 @@
         mMockNotificiationAreaController = mock(NotificationIconAreaController.class);
         mNotificationAreaInner = mock(View.class);
         mCenteredNotificationAreaView = mock(View.class);
-        when(statusBar.getPanel()).thenReturn(mock(NotificationPanelView.class));
+        when(statusBar.getPanelController()).thenReturn(
+                mock(NotificationPanelViewController.class));
         when(mNotificationAreaInner.animate()).thenReturn(mock(ViewPropertyAnimator.class));
         when(mMockNotificiationAreaController.getNotificationInnerAreaView()).thenReturn(
                 mNotificationAreaInner);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index 46f6cfe..3c6a698 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -79,14 +79,14 @@
     @Mock private DozeLog mDozeLog;
     @Mock private PulseExpansionHandler mPulseExpansionHandler;
     @Mock private NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
-    @Mock private StatusBarWindowController mStatusBarWindowController;
+    @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
     @Mock private PowerManager mPowerManager;
     @Mock private WakefulnessLifecycle mWakefullnessLifecycle;
     @Mock private StatusBar mStatusBar;
     @Mock private NotificationIconAreaController mNotificationIconAreaController;
-    @Mock private StatusBarWindowViewController mStatusBarWindowViewController;
+    @Mock private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
     @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    @Mock private NotificationPanelView mNotificationPanel;
+    @Mock private NotificationPanelViewController mNotificationPanel;
     @Mock private View mAmbientIndicationContainer;
     @Mock private BiometricUnlockController mBiometricUnlockController;
     @Mock private LockscreenLockIconController mLockscreenLockIconController;
@@ -99,12 +99,12 @@
                 mBatteryController, mScrimController, () -> mBiometricUnlockController,
                 mKeyguardViewMediator, () -> mAssistManager, mDozeScrimController,
                 mKeyguardUpdateMonitor, mVisualStabilityManager, mPulseExpansionHandler,
-                mStatusBarWindowController, mNotificationWakeUpCoordinator,
+                mNotificationShadeWindowController, mNotificationWakeUpCoordinator,
                 mLockscreenLockIconController);
 
         mDozeServiceHost.initialize(mStatusBar, mNotificationIconAreaController,
-                mStatusBarKeyguardViewManager, mStatusBarWindowViewController, mNotificationPanel,
-                mAmbientIndicationContainer);
+                mStatusBarKeyguardViewManager, mNotificationShadeWindowViewController,
+                mNotificationPanel, mAmbientIndicationContainer);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index 0260269..7448dbd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -53,7 +53,8 @@
 
     private final NotificationStackScrollLayout mStackScroller =
             mock(NotificationStackScrollLayout.class);
-    private final NotificationPanelView mPanelView = mock(NotificationPanelView.class);
+    private final NotificationPanelViewController mPanelView =
+            mock(NotificationPanelViewController.class);
     private final DarkIconDispatcher mDarkIconDispatcher = mock(DarkIconDispatcher.class);
     private HeadsUpAppearanceController mHeadsUpAppearanceController;
     private ExpandableNotificationRow mFirst;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index 7fa6901..50d8bf0b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -57,7 +57,7 @@
     private HeadsUpManagerPhone mHeadsUpManager;
 
     @Mock private NotificationGroupManager mGroupManager;
-    @Mock private View mStatusBarWindowView;
+    @Mock private View mNotificationShadeWindowView;
     @Mock private VisualStabilityManager mVSManager;
     @Mock private StatusBar mBar;
     @Mock private StatusBarStateController mStatusBarStateController;
@@ -65,13 +65,13 @@
     private boolean mLivesPastNormalTime;
 
     private final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone {
-        TestableHeadsUpManagerPhone(Context context, View statusBarWindowView,
+        TestableHeadsUpManagerPhone(Context context, View notificationShadeWindowView,
                 NotificationGroupManager groupManager, StatusBar bar,
                 VisualStabilityManager vsManager,
                 StatusBarStateController statusBarStateController,
                 KeyguardBypassController keyguardBypassController) {
             super(context, statusBarStateController, keyguardBypassController);
-            setUp(statusBarWindowView, groupManager, bar, vsManager);
+            setUp(notificationShadeWindowView, groupManager, bar, vsManager);
             mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
             mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
         }
@@ -89,9 +89,9 @@
                 .thenReturn(TEST_AUTO_DISMISS_TIME);
         when(mVSManager.isReorderingAllowed()).thenReturn(true);
         mDependency.injectMockDependency(BubbleController.class);
-        mDependency.injectMockDependency(StatusBarWindowController.class);
+        mDependency.injectMockDependency(NotificationShadeWindowController.class);
         mDependency.injectMockDependency(ConfigurationController.class);
-        mHeadsUpManager = new TestableHeadsUpManagerPhone(mContext, mStatusBarWindowView,
+        mHeadsUpManager = new TestableHeadsUpManagerPhone(mContext, mNotificationShadeWindowView,
                 mGroupManager, mBar, mVSManager, mStatusBarStateController, mBypassController);
         super.setUp();
         mHeadsUpManager.mHandler = mTestHandler;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
index 39afbe0..8f645b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
@@ -190,7 +190,7 @@
         mFragments.dispatchResume();
         processAllMessages();
 
-        verify(mBroadcastDispatcher).registerReceiver(
+        verify(mBroadcastDispatcher).registerReceiverWithHandler(
                 any(BroadcastReceiver.class),
                 any(IntentFilter.class),
                 any(Handler.class),
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 c165e56..1f37ad8 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
@@ -16,9 +16,13 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+
 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.doAnswer;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -26,37 +30,44 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
 import android.app.StatusBarManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.hardware.biometrics.BiometricSourceType;
+import android.os.PowerManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.util.DisplayMetrics;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityManager;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.keyguard.KeyguardStatusView;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.util.LatencyTracker;
+import com.android.keyguard.KeyguardClockSwitch;
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.SystemUIFactory;
+import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FlingAnimationUtils;
 import com.android.systemui.statusbar.KeyguardAffordanceView;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
-import com.android.systemui.statusbar.notification.logging.NotifLog;
 import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -70,6 +81,7 @@
 import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
 
 import java.util.function.Consumer;
 
@@ -85,8 +97,6 @@
     @Mock
     private NotificationStackScrollLayout mNotificationStackScrollLayout;
     @Mock
-    private KeyguardStatusView mKeyguardStatusView;
-    @Mock
     private KeyguardBottomAreaView mKeyguardBottomArea;
     @Mock
     private KeyguardBottomAreaView mQsFrame;
@@ -109,27 +119,89 @@
     @Mock
     private PanelBar mPanelBar;
     @Mock
-    private KeyguardAffordanceHelper mAffordanceHelper;
-    @Mock
     private KeyguardUpdateMonitor mUpdateMonitor;
     @Mock
     private FalsingManager mFalsingManager;
     @Mock
     private KeyguardBypassController mKeyguardBypassController;
-    @Mock private DozeParameters mDozeParameters;
-    private NotificationPanelView mNotificationPanelView;
+    @Mock
+    private DozeParameters mDozeParameters;
+    @Mock
+    private NotificationPanelView mView;
+    @Mock
+    private InjectionInflationController mInjectionInflationController;
+    @Mock
+    private DynamicPrivacyController mDynamicPrivacyController;
+    @Mock
+    private PluginManager mPluginManager;
+    @Mock
+    private ShadeController mShadeController;
+    @Mock
+    private NotificationLockscreenUserManager mNotificationLockscreenUserManager;
+    @Mock
+    private NotificationEntryManager mNotificationEntryManager;
+    @Mock
+    private KeyguardStateController mKeyguardStateController;
+    @Mock
+    private DozeLog mDozeLog;
+    @Mock
+    private CommandQueue mCommandQueue;
+    @Mock
+    private VibratorHelper mVibratorHelper;
+    @Mock
+    private LatencyTracker mLatencyTracker;
+    @Mock
+    private PowerManager mPowerManager;
+    @Mock
+    private AccessibilityManager mAccessibilityManager;
+    @Mock
+    private MetricsLogger mMetricsLogger;
+    @Mock
+    private ActivityManager mActivityManager;
+    @Mock
+    private Resources mResources;
+    @Mock
+    private Configuration mConfiguration;
+    private DisplayMetrics mDisplayMetrics = new DisplayMetrics();
+    @Mock
+    private KeyguardClockSwitch mKeyguardClockSwitch;
+    private PanelViewController.TouchHandler mTouchHandler;
+    @Mock
+    private ZenModeController mZenModeController;
+    @Mock
+    private ConfigurationController mConfigurationController;
+    private FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
+
+    private NotificationPanelViewController mNotificationPanelViewController;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+        when(mHeadsUpCallback.getContext()).thenReturn(mContext);
+        when(mView.getResources()).thenReturn(mResources);
+        when(mResources.getConfiguration()).thenReturn(mConfiguration);
+        mConfiguration.orientation = ORIENTATION_PORTRAIT;
+        when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
+        mDisplayMetrics.density = 100;
+        when(mResources.getBoolean(R.bool.config_enableNotificationShadeDrag)).thenReturn(true);
+        when(mView.getContext()).thenReturn(getContext());
+        when(mView.findViewById(R.id.keyguard_clock_container)).thenReturn(mKeyguardClockSwitch);
+        when(mView.findViewById(R.id.notification_stack_scroller))
+                .thenReturn(mNotificationStackScrollLayout);
         when(mNotificationStackScrollLayout.getHeight()).thenReturn(1000);
         when(mNotificationStackScrollLayout.getHeadsUpCallback()).thenReturn(mHeadsUpCallback);
-        when(mHeadsUpCallback.getContext()).thenReturn(mContext);
-        mDependency.injectTestDependency(StatusBarStateController.class,
-                mStatusBarStateController);
-        mDependency.injectTestDependency(KeyguardUpdateMonitor.class, mUpdateMonitor);
-        mDependency.injectMockDependency(ConfigurationController.class);
-        mDependency.injectMockDependency(ZenModeController.class);
+        when(mView.findViewById(R.id.keyguard_bottom_area)).thenReturn(mKeyguardBottomArea);
+        when(mKeyguardBottomArea.getLeftView()).thenReturn(mock(KeyguardAffordanceView.class));
+        when(mKeyguardBottomArea.getRightView()).thenReturn(mock(KeyguardAffordanceView.class));
+        when(mView.findViewById(R.id.big_clock_container)).thenReturn(mBigClockContainer);
+        when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
+        mFlingAnimationUtilsBuilder = new FlingAnimationUtils.Builder(mDisplayMetrics);
+
+        doAnswer((Answer<Void>) invocation -> {
+            mTouchHandler = invocation.getArgument(0);
+            return null;
+        }).when(mView).setOnTouchListener(any(PanelViewController.TouchHandler.class));
+
         NotificationWakeUpCoordinator coordinator =
                 new NotificationWakeUpCoordinator(
                         mock(HeadsUpManagerPhone.class),
@@ -143,18 +215,26 @@
                 mock(NotificationRoundnessManager.class),
                 mStatusBarStateController,
                 new FalsingManagerFake());
-        mNotificationPanelView = new TestableNotificationPanelView(coordinator, expansionHandler,
-                mKeyguardBypassController, mStatusBarStateController);
-        mNotificationPanelView.setHeadsUpManager(mHeadsUpManager);
-        mNotificationPanelView.setBar(mPanelBar);
-
-        when(mKeyguardBottomArea.getLeftView()).thenReturn(mock(KeyguardAffordanceView.class));
-        when(mKeyguardBottomArea.getRightView()).thenReturn(mock(KeyguardAffordanceView.class));
+        mNotificationPanelViewController = new NotificationPanelViewController(mView,
+                mInjectionInflationController,
+                coordinator, expansionHandler, mDynamicPrivacyController, mKeyguardBypassController,
+                mFalsingManager, mPluginManager, mShadeController,
+                mNotificationLockscreenUserManager, mNotificationEntryManager,
+                mKeyguardStateController, mStatusBarStateController, mDozeLog,
+                mDozeParameters, mCommandQueue, mVibratorHelper,
+                mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
+                mMetricsLogger, mActivityManager, mZenModeController, mConfigurationController,
+                mFlingAnimationUtilsBuilder);
+        mNotificationPanelViewController.initDependencies(mStatusBar, mGroupManager,
+                mNotificationShelf, mNotificationAreaController, mScrimController);
+        mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
+        mNotificationPanelViewController.setBar(mPanelBar);
     }
 
     @Test
     public void testSetDozing_notifiesNsslAndStateController() {
-        mNotificationPanelView.setDozing(true /* dozing */, true /* animate */, null /* touch */);
+        mNotificationPanelViewController.setDozing(true /* dozing */, true /* animate */,
+                null /* touch */);
         InOrder inOrder = inOrder(mNotificationStackScrollLayout, mStatusBarStateController);
         inOrder.verify(mNotificationStackScrollLayout).setDozing(eq(true), eq(true), eq(null));
         inOrder.verify(mStatusBarStateController).setDozeAmount(eq(1f), eq(true));
@@ -162,103 +242,63 @@
 
     @Test
     public void testSetExpandedHeight() {
-        mNotificationPanelView.setExpandedHeight(200);
-        assertThat((int) mNotificationPanelView.getExpandedHeight()).isEqualTo(200);
+        mNotificationPanelViewController.setExpandedHeight(200);
+        assertThat((int) mNotificationPanelViewController.getExpandedHeight()).isEqualTo(200);
     }
 
     @Test
     public void testAffordanceLaunchingListener() {
         Consumer<Boolean> listener = spy((showing) -> { });
-        mNotificationPanelView.setExpandedFraction(1f);
-        mNotificationPanelView.setLaunchAffordanceListener(listener);
-        mNotificationPanelView.launchCamera(false /* animate */,
+        mNotificationPanelViewController.setExpandedFraction(1f);
+        mNotificationPanelViewController.setLaunchAffordanceListener(listener);
+        mNotificationPanelViewController.launchCamera(false /* animate */,
                 StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP);
         verify(listener).accept(eq(true));
 
-        mNotificationPanelView.onAffordanceLaunchEnded();
+        mNotificationPanelViewController.onAffordanceLaunchEnded();
         verify(listener).accept(eq(false));
     }
 
     @Test
     public void testOnTouchEvent_expansionCanBeBlocked() {
-        mNotificationPanelView.onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+        onTouchEvent(MotionEvent.obtain(0L /* downTime */,
                 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
                 0 /* metaState */));
-        mNotificationPanelView.onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+        onTouchEvent(MotionEvent.obtain(0L /* downTime */,
                 0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 200f /* y */,
                 0 /* metaState */));
-        assertThat((int) mNotificationPanelView.getExpandedHeight()).isEqualTo(200);
-        assertThat(mNotificationPanelView.isTrackingBlocked()).isFalse();
+        assertThat((int) mNotificationPanelViewController.getExpandedHeight()).isEqualTo(200);
+        assertThat(mNotificationPanelViewController.isTrackingBlocked()).isFalse();
 
-        mNotificationPanelView.blockExpansionForCurrentTouch();
-        mNotificationPanelView.onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+        mNotificationPanelViewController.blockExpansionForCurrentTouch();
+        onTouchEvent(MotionEvent.obtain(0L /* downTime */,
                 0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 300f /* y */,
                 0 /* metaState */));
         // Expansion should not have changed because it was blocked
-        assertThat((int) mNotificationPanelView.getExpandedHeight()).isEqualTo(200);
-        assertThat(mNotificationPanelView.isTrackingBlocked()).isTrue();
+        assertThat((int) mNotificationPanelViewController.getExpandedHeight()).isEqualTo(200);
+        assertThat(mNotificationPanelViewController.isTrackingBlocked()).isTrue();
 
-        mNotificationPanelView.onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+        onTouchEvent(MotionEvent.obtain(0L /* downTime */,
                 0L /* eventTime */, MotionEvent.ACTION_UP, 0f /* x */, 300f /* y */,
                 0 /* metaState */));
-        assertThat(mNotificationPanelView.isTrackingBlocked()).isFalse();
+        assertThat(mNotificationPanelViewController.isTrackingBlocked()).isFalse();
     }
 
     @Test
     public void testKeyguardStatusBarVisibility_hiddenForBypass() {
         when(mUpdateMonitor.shouldListenForFace()).thenReturn(true);
-        mNotificationPanelView.mKeyguardUpdateCallback.onBiometricRunningStateChanged(true,
-                BiometricSourceType.FACE);
+        mNotificationPanelViewController.mKeyguardUpdateCallback.onBiometricRunningStateChanged(
+                true, BiometricSourceType.FACE);
         verify(mKeyguardStatusBar, never()).setVisibility(View.VISIBLE);
 
         when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
-        mNotificationPanelView.mKeyguardUpdateCallback.onFinishedGoingToSleep(0);
-        mNotificationPanelView.mKeyguardUpdateCallback.onBiometricRunningStateChanged(true,
-                BiometricSourceType.FACE);
+        mNotificationPanelViewController.mKeyguardUpdateCallback.onFinishedGoingToSleep(0);
+        mNotificationPanelViewController.mKeyguardUpdateCallback.onBiometricRunningStateChanged(
+                true, BiometricSourceType.FACE);
         verify(mKeyguardStatusBar, never()).setVisibility(View.VISIBLE);
     }
 
-    private class TestableNotificationPanelView extends NotificationPanelView {
-        TestableNotificationPanelView(NotificationWakeUpCoordinator coordinator,
-                PulseExpansionHandler expansionHandler,
-                KeyguardBypassController bypassController,
-                SysuiStatusBarStateController statusBarStateController) {
-            super(
-                    NotificationPanelViewTest.this.mContext,
-                    null,
-                    new InjectionInflationController(
-                            SystemUIFactory.getInstance().getRootComponent()),
-                    coordinator,
-                    expansionHandler,
-                    mock(DynamicPrivacyController.class),
-                    bypassController,
-                    mFalsingManager,
-                    mock(PluginManager.class),
-                    mock(ShadeController.class),
-                    mock(NotificationLockscreenUserManager.class),
-                    new NotificationEntryManager(
-                            mock(NotifLog.class),
-                            mock(NotificationGroupManager.class),
-                            mock(NotificationRankingManager.class),
-                            mock(NotificationEntryManager.KeyguardEnvironment.class)),
-                    mock(KeyguardStateController.class),
-                    statusBarStateController,
-                    mock(DozeLog.class),
-                    mDozeParameters,
-                    new CommandQueue(NotificationPanelViewTest.this.mContext));
-            mNotificationStackScroller = mNotificationStackScrollLayout;
-            mKeyguardStatusView = NotificationPanelViewTest.this.mKeyguardStatusView;
-            mKeyguardStatusBar = NotificationPanelViewTest.this.mKeyguardStatusBar;
-            mKeyguardBottomArea = NotificationPanelViewTest.this.mKeyguardBottomArea;
-            mBigClockContainer = NotificationPanelViewTest.this.mBigClockContainer;
-            mQsFrame = NotificationPanelViewTest.this.mQsFrame;
-            mAffordanceHelper = NotificationPanelViewTest.this.mAffordanceHelper;
-            initDependencies(NotificationPanelViewTest.this.mStatusBar,
-                    NotificationPanelViewTest.this.mGroupManager,
-                    NotificationPanelViewTest.this.mNotificationShelf,
-                    NotificationPanelViewTest.this.mHeadsUpManager,
-                    NotificationPanelViewTest.this.mNotificationAreaController,
-                    NotificationPanelViewTest.this.mScrimController);
-        }
+    private void onTouchEvent(MotionEvent ev) {
+        mTouchHandler.onTouch(mView, ev);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java
new file mode 100644
index 0000000..40d3395
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.systemui.statusbar.phone;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.IActivityManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.WindowManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.colorextraction.ColorExtractor;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.statusbar.SuperStatusBarViewFactory;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+@SmallTest
+public class NotificationShadeWindowControllerTest extends SysuiTestCase {
+
+    @Mock private WindowManager mWindowManager;
+    @Mock private DozeParameters mDozeParameters;
+    @Mock private NotificationShadeWindowView mStatusBarView;
+    @Mock private IActivityManager mActivityManager;
+    @Mock private SysuiStatusBarStateController mStatusBarStateController;
+    @Mock private ConfigurationController mConfigurationController;
+    @Mock private KeyguardBypassController mKeyguardBypassController;
+    @Mock private SysuiColorExtractor mColorExtractor;
+    @Mock ColorExtractor.GradientColors mGradientColors;
+    @Mock private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
+
+    private NotificationShadeWindowController mNotificationShadeWindowController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mDozeParameters.getAlwaysOn()).thenReturn(true);
+        when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
+        when(mSuperStatusBarViewFactory.getNotificationShadeWindowView())
+                .thenReturn(mStatusBarView);
+
+        mNotificationShadeWindowController = new NotificationShadeWindowController(mContext,
+                mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
+                mConfigurationController, mKeyguardBypassController, mColorExtractor,
+                mSuperStatusBarViewFactory);
+
+        mNotificationShadeWindowController.attach();
+    }
+
+    @Test
+    public void testSetDozing_hidesSystemOverlays() {
+        mNotificationShadeWindowController.setDozing(true);
+        ArgumentCaptor<WindowManager.LayoutParams> captor =
+                ArgumentCaptor.forClass(WindowManager.LayoutParams.class);
+        verify(mWindowManager).updateViewLayout(any(), captor.capture());
+        int flag = captor.getValue().privateFlags
+                & WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+        assertThat(flag).isNotEqualTo(0);
+
+        reset(mWindowManager);
+        mNotificationShadeWindowController.setDozing(false);
+        verify(mWindowManager).updateViewLayout(any(), captor.capture());
+        flag = captor.getValue().privateFlags
+                & WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+        assertThat(flag).isEqualTo(0);
+    }
+
+    @Test
+    public void testOnThemeChanged_doesntCrash() {
+        mNotificationShadeWindowController.onThemeChanged();
+    }
+
+    @Test
+    public void testAdd_updatesVisibilityFlags() {
+        verify(mStatusBarView).setSystemUiVisibility(anyInt());
+    }
+
+    @Test
+    public void testSetForcePluginOpen_beforeStatusBarInitialization() {
+        mNotificationShadeWindowController.setForcePluginOpen(true);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
new file mode 100644
index 0000000..8936a2d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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 static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.SystemClock;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.MotionEvent;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SystemUIFactory;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.dock.DockManager;
+import com.android.systemui.doze.DozeLog;
+import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.DragDownHelper;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.PulseExpansionHandler;
+import com.android.systemui.statusbar.StatusBarWindowBlurController;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.tuner.TunerService;
+import com.android.systemui.util.InjectionInflationController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+public class NotificationShadeWindowViewTest extends SysuiTestCase {
+
+    private NotificationShadeWindowView mView;
+    private NotificationShadeWindowViewController mController;
+
+    @Mock private NotificationWakeUpCoordinator mCoordinator;
+    @Mock private PulseExpansionHandler mPulseExpansionHandler;
+    @Mock private DynamicPrivacyController mDynamicPrivacyController;
+    @Mock private KeyguardBypassController mBypassController;
+    @Mock private PluginManager mPluginManager;
+    @Mock private TunerService mTunerService;
+    @Mock private DragDownHelper mDragDownHelper;
+    @Mock private KeyguardStateController mKeyguardStateController;
+    @Mock private SysuiStatusBarStateController mStatusBarStateController;
+    @Mock private ShadeController mShadeController;
+    @Mock private NotificationLockscreenUserManager mNotificationLockScreenUserManager;
+    @Mock private NotificationEntryManager mNotificationEntryManager;
+    @Mock private StatusBar mStatusBar;
+    @Mock private DozeLog mDozeLog;
+    @Mock private DozeParameters mDozeParameters;
+    @Mock private DockManager mDockManager;
+    @Mock private NotificationPanelViewController mNotificationPanelViewController;
+    @Mock private NotificationStackScrollLayout mNotificationStackScrollLayout;
+    @Mock private StatusBarWindowBlurController mStatusBarWindowBlurController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mView = spy(new NotificationShadeWindowView(getContext(), null));
+        when(mView.findViewById(R.id.notification_stack_scroller))
+                .thenReturn(mNotificationStackScrollLayout);
+
+        when(mStatusBarStateController.isDozing()).thenReturn(false);
+        mDependency.injectTestDependency(ShadeController.class, mShadeController);
+
+        when(mDockManager.isDocked()).thenReturn(false);
+
+        mController = new NotificationShadeWindowViewController(
+                new InjectionInflationController(
+                        SystemUIFactory.getInstance().getRootComponent()),
+                mCoordinator,
+                mPulseExpansionHandler,
+                mDynamicPrivacyController,
+                mBypassController,
+                new FalsingManagerFake(),
+                mPluginManager,
+                mTunerService,
+                mNotificationLockScreenUserManager,
+                mNotificationEntryManager,
+                mKeyguardStateController,
+                mStatusBarStateController,
+                mDozeLog,
+                mDozeParameters,
+                new CommandQueue(mContext),
+                mShadeController,
+                mDockManager,
+                mStatusBarWindowBlurController,
+                mView,
+                mNotificationPanelViewController);
+        mController.setupExpandedStatusBar();
+        mController.setService(mStatusBar);
+        mController.setDragDownHelper(mDragDownHelper);
+
+    }
+
+    @Test
+    public void testDragDownHelperCalledWhenDraggingDown() {
+        when(mDragDownHelper.isDraggingDown()).thenReturn(true);
+        long now = SystemClock.elapsedRealtime();
+        MotionEvent ev = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, 0 /* x */, 0 /* y */,
+                0 /* meta */);
+        mView.onTouchEvent(ev);
+        verify(mDragDownHelper).onTouchEvent(ev);
+        ev.recycle();
+    }
+}
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 b27e84a..675a2df 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
@@ -74,7 +74,7 @@
     @Mock
     private ViewGroup mContainer;
     @Mock
-    private NotificationPanelView mNotificationPanelView;
+    private NotificationPanelViewController mNotificationPanelView;
     @Mock
     private BiometricUnlockController mBiometrucUnlockController;
     @Mock
@@ -104,7 +104,7 @@
                 mock(KeyguardUpdateMonitor.class),
                 mock(NavigationModeController.class),
                 mock(DockManager.class),
-                mock(StatusBarWindowController.class),
+                mock(NotificationShadeWindowController.class),
                 mKeyguardStateController,
                 mock(NotificationMediaManager.class));
         mStatusBarKeyguardViewManager.registerStatusBar(mStatusBar, mContainer,
@@ -270,23 +270,23 @@
                 KeyguardUpdateMonitor keyguardUpdateMonitor,
                 NavigationModeController navigationModeController,
                 DockManager dockManager,
-                StatusBarWindowController statusBarWindowController,
+                NotificationShadeWindowController notificationShadeWindowController,
                 KeyguardStateController keyguardStateController,
                 NotificationMediaManager notificationMediaManager) {
             super(context, callback, lockPatternUtils, sysuiStatusBarStateController,
                     configurationController, keyguardUpdateMonitor, navigationModeController,
-                    dockManager, statusBarWindowController, keyguardStateController,
+                    dockManager, notificationShadeWindowController, keyguardStateController,
                     notificationMediaManager);
         }
 
         @Override
         public void registerStatusBar(StatusBar statusBar, ViewGroup container,
-                NotificationPanelView notificationPanelView,
+                NotificationPanelViewController notificationPanelViewController,
                 BiometricUnlockController fingerprintUnlockController,
                 DismissCallbackRegistry dismissCallbackRegistry,
                 ViewGroup lockIconContainer, View notificationContainer,
                 KeyguardBypassController bypassController, FalsingManager falsingManager) {
-            super.registerStatusBar(statusBar, container, notificationPanelView,
+            super.registerStatusBar(statusBar, container, notificationPanelViewController,
                     fingerprintUnlockController, dismissCallbackRegistry, lockIconContainer,
                     notificationContainer, bypassController, falsingManager);
             mBouncer = StatusBarKeyguardViewManagerTest.this.mBouncer;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 86b2a44..fea4b8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -64,7 +64,6 @@
 import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -124,10 +123,6 @@
     private Intent mContentIntentInner;
     @Mock
     private NotificationActivityStarter mNotificationActivityStarter;
-    @Mock
-    private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
-    @Mock
-    private NotificationPanelView mNotificationPanelView;
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
 
     private NotificationTestHelper mNotificationTestHelper;
@@ -167,8 +162,6 @@
         mActiveNotifications.add(mBubbleNotificationRow.getEntry());
         when(mEntryManager.getVisibleNotifications()).thenReturn(mActiveNotifications);
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
-        when(mSuperStatusBarViewFactory.getNotificationPanelView())
-                .thenReturn(mNotificationPanelView);
 
         mNotificationActivityStarter = (new StatusBarNotificationActivityStarter.Builder(
                 getContext(), mock(CommandQueue.class), () -> mAssistManager,
@@ -182,9 +175,9 @@
                 mKeyguardStateController,
                 mock(NotificationInterruptionStateProvider.class), mock(MetricsLogger.class),
                 mock(LockPatternUtils.class), mHandler, mHandler, mUiBgExecutor,
-                mActivityIntentHelper, mBubbleController, mShadeController,
-                mSuperStatusBarViewFactory))
+                mActivityIntentHelper, mBubbleController, mShadeController))
                 .setStatusBar(mStatusBar)
+                .setNotificationPanelViewController(mock(NotificationPanelViewController.class))
                 .setNotificationPresenter(mock(NotificationPresenter.class))
                 .setActivityLaunchAnimator(mock(ActivityLaunchAnimator.class))
         .build();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 1296a97..dd896be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -54,7 +54,7 @@
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -100,16 +100,17 @@
         mDependency.injectMockDependency(NotificationMediaManager.class);
         mDependency.injectMockDependency(VisualStabilityManager.class);
         mDependency.injectMockDependency(NotificationGutsManager.class);
-        mDependency.injectMockDependency(StatusBarWindowController.class);
+        mDependency.injectMockDependency(NotificationShadeWindowController.class);
         NotificationEntryManager entryManager =
                 mDependency.injectMockDependency(NotificationEntryManager.class);
         when(entryManager.getActiveNotificationsForCurrentUser()).thenReturn(new ArrayList<>());
 
-        StatusBarWindowView statusBarWindowView = mock(StatusBarWindowView.class);
-        when(statusBarWindowView.getResources()).thenReturn(mContext.getResources());
+        NotificationShadeWindowView notificationShadeWindowView =
+                mock(NotificationShadeWindowView.class);
+        when(notificationShadeWindowView.getResources()).thenReturn(mContext.getResources());
         mStatusBarNotificationPresenter = new StatusBarNotificationPresenter(mContext,
-                mock(NotificationPanelView.class), mock(HeadsUpManagerPhone.class),
-                statusBarWindowView, mock(NotificationListContainerViewGroup.class),
+                mock(NotificationPanelViewController.class), mock(HeadsUpManagerPhone.class),
+                notificationShadeWindowView, mock(NotificationListContainerViewGroup.class),
                 mock(DozeScrimController.class), mock(ScrimController.class),
                 mock(ActivityLaunchAnimator.class), mock(DynamicPrivacyController.class),
                 mock(NotificationAlertingManager.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 1cdba47..e90e398 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -120,10 +120,12 @@
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -172,6 +174,7 @@
     @Mock private KeyguardIndicationController mKeyguardIndicationController;
     @Mock private NotificationStackScrollLayout mStackScroller;
     @Mock private HeadsUpManagerPhone mHeadsUpManager;
+    @Mock private NotificationPanelViewController mNotificationPanelViewController;
     @Mock private NotificationPanelView mNotificationPanelView;
     @Mock private IStatusBarService mBarService;
     @Mock private IDreamManager mDreamManager;
@@ -198,7 +201,7 @@
     @Mock private NotificationLogger.ExpansionStateLogger mExpansionStateLogger;
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock private AmbientDisplayConfiguration mAmbientDisplayConfiguration;
-    @Mock private StatusBarWindowView mStatusBarWindowView;
+    @Mock private NotificationShadeWindowView mNotificationShadeWindowView;
     @Mock private BroadcastDispatcher mBroadcastDispatcher;
     @Mock private AssistManager mAssistManager;
     @Mock private NotificationGutsManager mNotificationGutsManager;
@@ -211,7 +214,7 @@
     @Mock private NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
     @Mock private KeyguardBypassController mKeyguardBypassController;
     @Mock private DynamicPrivacyController mDynamicPrivacyController;
-    @Mock private NewNotifPipeline mNewNotifPipeline;
+    @Mock private NotifPipelineInitializer mNewNotifPipeline;
     @Mock private ZenModeController mZenModeController;
     @Mock private AutoHideController mAutoHideController;
     @Mock private NotificationViewHierarchyManager mNotificationViewHierarchyManager;
@@ -221,9 +224,9 @@
     @Mock private BubbleController mBubbleController;
     @Mock private NotificationGroupManager mGroupManager;
     @Mock private NotificationGroupAlertTransferHelper mGroupAlertTransferHelper;
-    @Mock private StatusBarWindowController mStatusBarWindowController;
+    @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
     @Mock private NotificationIconAreaController mNotificationIconAreaController;
-    @Mock private StatusBarWindowViewController mStatusBarWindowViewController;
+    @Mock private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
     @Mock private DozeParameters mDozeParameters;
     @Mock private Lazy<LockscreenWallpaper> mLockscreenWallpaperLazy;
     @Mock private LockscreenWallpaper mLockscreenWallpaper;
@@ -253,6 +256,7 @@
     @Mock private KeyguardDismissUtil mKeyguardDismissUtil;
     @Mock private ExtensionController mExtensionController;
     @Mock private UserInfoControllerImpl mUserInfoControllerImpl;
+    @Mock private NotificationRowBinderImpl mNotificationRowBinder;
     private ShadeController mShadeController;
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
     private InitController mInitController = new InitController();
@@ -285,6 +289,7 @@
         mContext.setTheme(R.style.Theme_SystemUI_Light);
 
         when(mStackScroller.generateLayoutParams(any())).thenReturn(new LayoutParams(0, 0));
+        when(mNotificationPanelViewController.getView()).thenReturn(mNotificationPanelView);
         when(mNotificationPanelView.getLayoutParams()).thenReturn(new LayoutParams(0, 0));
         when(powerManagerService.isInteractive()).thenReturn(true);
         when(mStackScroller.getActivatedChild()).thenReturn(null);
@@ -319,11 +324,11 @@
 
         when(mStatusBarComponentBuilderProvider.get()).thenReturn(mStatusBarComponentBuilder);
         when(mStatusBarComponentBuilder.build()).thenReturn(mStatusBarComponent);
-        when(mStatusBarComponent.getStatusBarWindowViewController()).thenReturn(
-                mStatusBarWindowViewController);
+        when(mStatusBarComponent.getNotificationShadeWindowViewController()).thenReturn(
+                mNotificationShadeWindowViewController);
 
         mShadeController = new ShadeControllerImpl(mCommandQueue,
-                mStatusBarStateController, mStatusBarWindowController,
+                mStatusBarStateController, mNotificationShadeWindowController,
                 mStatusBarKeyguardViewManager, mContext.getSystemService(WindowManager.class),
                 () -> mStatusBar, () -> mAssistManager, () -> mBubbleController);
 
@@ -341,7 +346,6 @@
                 mHeadsUpManager,
                 mDynamicPrivacyController,
                 mBypassHeadsUpNotifier,
-                true,
                 () -> mNewNotifPipeline,
                 new FalsingManagerFake(),
                 mBroadcastDispatcher,
@@ -380,7 +384,7 @@
                 () -> mAssistManager,
                 mNotificationListener,
                 configurationController,
-                mStatusBarWindowController,
+                mNotificationShadeWindowController,
                 mLockscreenLockIconController,
                 mDozeParameters,
                 mScrimController,
@@ -410,13 +414,14 @@
                 mKeyguardDismissUtil,
                 mExtensionController,
                 mUserInfoControllerImpl,
+                mNotificationRowBinder,
                 mDismissCallbackRegistry);
 
-        when(mStatusBarWindowView.findViewById(R.id.lock_icon_container)).thenReturn(
+        when(mNotificationShadeWindowView.findViewById(R.id.lock_icon_container)).thenReturn(
                 mLockIconContainer);
 
         when(mKeyguardViewMediator.registerStatusBar(any(StatusBar.class), any(ViewGroup.class),
-                any(NotificationPanelView.class), any(BiometricUnlockController.class),
+                any(NotificationPanelViewController.class), any(BiometricUnlockController.class),
                 any(ViewGroup.class), any(ViewGroup.class), any(KeyguardBypassController.class)))
                 .thenReturn(mStatusBarKeyguardViewManager);
 
@@ -425,8 +430,8 @@
 
         // TODO: we should be able to call mStatusBar.start() and have all the below values
         // initialized automatically.
-        mStatusBar.mStatusBarWindow = mStatusBarWindowView;
-        mStatusBar.mNotificationPanel = mNotificationPanelView;
+        mStatusBar.mNotificationShadeWindowView = mNotificationShadeWindowView;
+        mStatusBar.mNotificationPanelViewController = mNotificationPanelViewController;
         mStatusBar.mDozeScrimController = mDozeScrimController;
         mStatusBar.mNotificationIconAreaController = mNotificationIconAreaController;
         mStatusBar.mPresenter = mNotificationPresenter;
@@ -731,20 +736,20 @@
         when(mCommandQueue.panelsEnabled()).thenReturn(false);
         mStatusBar.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE,
                 StatusBarManager.DISABLE2_NOTIFICATION_SHADE, false);
-        verify(mNotificationPanelView).setQsExpansionEnabled(false);
+        verify(mNotificationPanelViewController).setQsExpansionEnabled(false);
         mStatusBar.animateExpandNotificationsPanel();
-        verify(mNotificationPanelView, never()).expand(anyBoolean());
+        verify(mNotificationPanelViewController, never()).expand(anyBoolean());
         mStatusBar.animateExpandSettingsPanel(null);
-        verify(mNotificationPanelView, never()).expand(anyBoolean());
+        verify(mNotificationPanelViewController, never()).expand(anyBoolean());
 
         when(mCommandQueue.panelsEnabled()).thenReturn(true);
         mStatusBar.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE,
                 StatusBarManager.DISABLE2_NONE, false);
-        verify(mNotificationPanelView).setQsExpansionEnabled(true);
+        verify(mNotificationPanelViewController).setQsExpansionEnabled(true);
         mStatusBar.animateExpandNotificationsPanel();
-        verify(mNotificationPanelView).expandWithoutQs();
+        verify(mNotificationPanelViewController).expandWithoutQs();
         mStatusBar.animateExpandSettingsPanel(null);
-        verify(mNotificationPanelView).expandWithQs();
+        verify(mNotificationPanelViewController).expandWithQs();
     }
 
     @Test
@@ -834,12 +839,12 @@
         when(mDozeServiceHost.getDozingRequested()).thenReturn(true);
         mStatusBar.updateIsKeyguard();
         // TODO: mNotificationPanelView.expand(false) gets called twice. Should be once.
-        verify(mNotificationPanelView, times(2)).expand(eq(false));
-        clearInvocations(mNotificationPanelView);
+        verify(mNotificationPanelViewController, times(2)).expand(eq(false));
+        clearInvocations(mNotificationPanelViewController);
 
         mStatusBar.mWakefulnessObserver.onStartedWakingUp();
         verify(mDozeServiceHost).stopDozing();
-        verify(mNotificationPanelView).expand(eq(false));
+        verify(mNotificationPanelViewController).expand(eq(false));
     }
 
     @Test
@@ -848,11 +853,11 @@
         when(mStatusBarStateController.isKeyguardRequested()).thenReturn(true);
         when(mDozeServiceHost.getDozingRequested()).thenReturn(true);
         mStatusBar.updateIsKeyguard();
-        clearInvocations(mNotificationPanelView);
+        clearInvocations(mNotificationPanelViewController);
 
         mStatusBar.setBouncerShowing(true);
         mStatusBar.mWakefulnessObserver.onStartedWakingUp();
-        verify(mNotificationPanelView, never()).expand(anyBoolean());
+        verify(mNotificationPanelViewController, never()).expand(anyBoolean());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java
deleted file mode 100644
index 147edf6..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * 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.systemui.statusbar.phone;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.IActivityManager;
-import android.content.res.Resources;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
-import android.view.WindowManager;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.colorextraction.ColorExtractor;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.statusbar.SuperStatusBarViewFactory;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-@SmallTest
-public class StatusBarWindowControllerTest extends SysuiTestCase {
-
-    @Mock private WindowManager mWindowManager;
-    @Mock private DozeParameters mDozeParameters;
-    @Mock private StatusBarWindowView mStatusBarView;
-    @Mock private IActivityManager mActivityManager;
-    @Mock private SysuiStatusBarStateController mStatusBarStateController;
-    @Mock private ConfigurationController mConfigurationController;
-    @Mock private KeyguardBypassController mKeyguardBypassController;
-    @Mock private SysuiColorExtractor mColorExtractor;
-    @Mock ColorExtractor.GradientColors mGradientColors;
-    @Mock private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
-    @Mock private Resources mResources;
-
-    private StatusBarWindowController mStatusBarWindowController;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        when(mDozeParameters.getAlwaysOn()).thenReturn(true);
-        when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
-        when(mSuperStatusBarViewFactory.getStatusBarWindowView()).thenReturn(mStatusBarView);
-
-        mStatusBarWindowController = new StatusBarWindowController(mContext, mWindowManager,
-                mActivityManager, mDozeParameters, mStatusBarStateController,
-                mConfigurationController, mKeyguardBypassController, mColorExtractor,
-                mSuperStatusBarViewFactory, mResources);
-
-        mStatusBarWindowController.attach();
-    }
-
-    @Test
-    public void testSetDozing_hidesSystemOverlays() {
-        mStatusBarWindowController.setDozing(true);
-        ArgumentCaptor<WindowManager.LayoutParams> captor =
-                ArgumentCaptor.forClass(WindowManager.LayoutParams.class);
-        verify(mWindowManager).updateViewLayout(any(), captor.capture());
-        int flag = captor.getValue().privateFlags
-                & WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
-        assertThat(flag).isNotEqualTo(0);
-
-        reset(mWindowManager);
-        mStatusBarWindowController.setDozing(false);
-        verify(mWindowManager).updateViewLayout(any(), captor.capture());
-        flag = captor.getValue().privateFlags
-                & WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
-        assertThat(flag).isEqualTo(0);
-    }
-
-    @Test
-    public void testOnThemeChanged_doesntCrash() {
-        mStatusBarWindowController.onThemeChanged();
-    }
-
-    @Test
-    public void testAdd_updatesVisibilityFlags() {
-        verify(mStatusBarView).setSystemUiVisibility(anyInt());
-    }
-
-    @Test
-    public void testSetForcePluginOpen_beforeStatusBarInitialization() {
-        mStatusBarWindowController.setForcePluginOpen(true);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java
deleted file mode 100644
index 9f899ee..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java
+++ /dev/null
@@ -1,124 +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 static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.os.SystemClock;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.MotionEvent;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SystemUIFactory;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.dock.DockManager;
-import com.android.systemui.doze.DozeLog;
-import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.DragDownHelper;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.PulseExpansionHandler;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.util.InjectionInflationController;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@SmallTest
-public class StatusBarWindowViewTest extends SysuiTestCase {
-
-    private StatusBarWindowView mView;
-    private StatusBarWindowViewController mController;
-
-    @Mock private NotificationWakeUpCoordinator mCoordinator;
-    @Mock private PulseExpansionHandler mPulseExpansionHandler;
-    @Mock private DynamicPrivacyController mDynamicPrivacyController;
-    @Mock private KeyguardBypassController mBypassController;
-    @Mock private PluginManager mPluginManager;
-    @Mock private TunerService mTunerService;
-    @Mock private DragDownHelper mDragDownHelper;
-    @Mock private KeyguardStateController mKeyguardStateController;
-    @Mock private SysuiStatusBarStateController mStatusBarStateController;
-    @Mock private ShadeController mShadeController;
-    @Mock private NotificationLockscreenUserManager mNotificationLockScreenUserManager;
-    @Mock private NotificationEntryManager mNotificationEntryManager;
-    @Mock private StatusBar mStatusBar;
-    @Mock private DozeLog mDozeLog;
-    @Mock private DozeParameters mDozeParameters;
-    @Mock private DockManager mDockManager;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        mView = new StatusBarWindowView(getContext(), null);
-        when(mStatusBarStateController.isDozing()).thenReturn(false);
-        mDependency.injectTestDependency(ShadeController.class, mShadeController);
-
-        when(mDockManager.isDocked()).thenReturn(false);
-
-        mController = new StatusBarWindowViewController(
-                new InjectionInflationController(
-                        SystemUIFactory.getInstance().getRootComponent()),
-                mCoordinator,
-                mPulseExpansionHandler,
-                mDynamicPrivacyController,
-                mBypassController,
-                new FalsingManagerFake(),
-                mPluginManager,
-                mTunerService,
-                mNotificationLockScreenUserManager,
-                mNotificationEntryManager,
-                mKeyguardStateController,
-                mStatusBarStateController,
-                mDozeLog,
-                mDozeParameters,
-                new CommandQueue(mContext),
-                mShadeController,
-                mDockManager,
-                mView);
-        mController.setupExpandedStatusBar();
-        mController.setService(mStatusBar);
-        mController.setDragDownHelper(mDragDownHelper);
-
-    }
-
-    @Test
-    public void testDragDownHelperCalledWhenDraggingDown() {
-        when(mDragDownHelper.isDraggingDown()).thenReturn(true);
-        long now = SystemClock.elapsedRealtime();
-        MotionEvent ev = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, 0 /* x */, 0 /* y */,
-                0 /* meta */);
-        mView.onTouchEvent(ev);
-        verify(mDragDownHelper).onTouchEvent(ev);
-        ev.recycle();
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index c4caeb3..cc3c3cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.statusbar.policy;
 
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotNull;
 
@@ -39,6 +42,8 @@
 import android.os.Handler;
 import android.provider.Settings;
 import android.provider.Settings.Global;
+import android.telephony.CdmaEriInformation;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
@@ -107,6 +112,8 @@
     private NetworkCapabilities mNetCapabilities;
     private ConnectivityManager.NetworkCallback mNetworkCallback;
 
+    private CdmaEriInformation mEriInformation;
+
     @Rule
     public TestWatcher failWatcher = new TestWatcher() {
         @Override
@@ -151,6 +158,10 @@
         mSignalStrength = mock(SignalStrength.class);
         mServiceState = mock(ServiceState.class);
 
+        mEriInformation = new CdmaEriInformation(CdmaEriInformation.ERI_OFF,
+                CdmaEriInformation.ERI_ICON_MODE_NORMAL);
+        when(mMockTm.getCdmaEriInformation()).thenReturn(mEriInformation);
+
         mConfig = new Config();
         mConfig.hspaDataDistinguishable = true;
         mCallbackHandler = mock(CallbackHandler.class);
@@ -180,7 +191,7 @@
     protected void setupNetworkController() {
         // For now just pretend to be the data sim, so we can test that too.
         mSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
-        when(mMockTm.isDataCapable()).thenReturn(true);
+        when(mMockTm.isDataConnectionEnabled()).thenReturn(true);
         setDefaultSubId(mSubId);
         setSubscriptions(mSubId);
         mMobileSignalController = mNetworkController.mMobileSignalControllers.get(mSubId);
@@ -301,11 +312,9 @@
     }
 
     public void setCdmaRoaming(boolean isRoaming) {
-        when(mServiceState.getCdmaEriIconIndex()).thenReturn(isRoaming ?
-                EriInfo.ROAMING_INDICATOR_ON : EriInfo.ROAMING_INDICATOR_OFF);
-        when(mServiceState.getCdmaEriIconMode()).thenReturn(isRoaming ?
-                EriInfo.ROAMING_ICON_MODE_NORMAL : -1);
-        updateServiceState();
+        mEriInformation.setEriIconIndex(isRoaming ?
+                CdmaEriInformation.ERI_ON : CdmaEriInformation.ERI_OFF);
+        when(mMockTm.getCdmaEriInformation()).thenReturn(mEriInformation);
     }
 
     public void setVoiceRegState(int voiceRegState) {
@@ -358,7 +367,13 @@
     }
 
     public void updateDataConnectionState(int dataState, int dataNetType) {
-        when(mServiceState.getDataNetworkType()).thenReturn(dataNetType);
+        NetworkRegistrationInfo fakeRegInfo = new NetworkRegistrationInfo.Builder()
+                .setTransportType(TRANSPORT_TYPE_WWAN)
+                .setDomain(DOMAIN_PS)
+                .setAccessNetworkTechnology(dataNetType)
+                .build();
+        when(mServiceState.getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN))
+                .thenReturn(fakeRegInfo);
         mPhoneStateListener.onDataConnectionStateChanged(dataState, dataNetType);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index 95b055c..f6c750d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -1,5 +1,8 @@
 package com.android.systemui.statusbar.policy;
 
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.anyInt;
@@ -121,7 +124,7 @@
     @Test
     public void testNoInternetIcon_withDefaultSub() {
         setupNetworkController();
-        when(mMockTm.isDataCapable()).thenReturn(false);
+        when(mMockTm.isDataConnectionEnabled()).thenReturn(false);
         setupDefaultSignal();
         updateDataConnectionState(TelephonyManager.DATA_CONNECTED, 0);
         setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
@@ -135,7 +138,7 @@
     @Test
     public void testDataDisabledIcon_withDefaultSub() {
         setupNetworkController();
-        when(mMockTm.isDataCapable()).thenReturn(false);
+        when(mMockTm.isDataConnectionEnabled()).thenReturn(false);
         setupDefaultSignal();
         updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0);
         setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
@@ -149,7 +152,7 @@
     @Test
     public void testNonDefaultSIM_showsFullSignal_connected() {
         setupNetworkController();
-        when(mMockTm.isDataCapable()).thenReturn(false);
+        when(mMockTm.isDataConnectionEnabled()).thenReturn(false);
         setupDefaultSignal();
         setDefaultSubId(mSubId + 1);
         updateDataConnectionState(TelephonyManager.DATA_CONNECTED, 0);
@@ -164,7 +167,7 @@
     @Test
     public void testNonDefaultSIM_showsFullSignal_disconnected() {
         setupNetworkController();
-        when(mMockTm.isDataCapable()).thenReturn(false);
+        when(mMockTm.isDataConnectionEnabled()).thenReturn(false);
         setupDefaultSignal();
         setDefaultSubId(mSubId + 1);
         updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0);
@@ -418,7 +421,13 @@
 
         // State from NR_5G to NONE NR_STATE_RESTRICTED, showing corresponding icon
         doReturn(NetworkRegistrationInfo.NR_STATE_RESTRICTED).when(mServiceState).getNrState();
-        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+        NetworkRegistrationInfo fakeRegInfo = new NetworkRegistrationInfo.Builder()
+                .setTransportType(TRANSPORT_TYPE_WWAN)
+                .setDomain(DOMAIN_PS)
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                .build();
+        doReturn(fakeRegInfo).when(mServiceState)
+                .getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN);
         mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
                 TelephonyManager.NETWORK_TYPE_LTE);
 
@@ -429,7 +438,7 @@
     @Test
     public void testDataDisabledIcon_UserNotSetup() {
         setupNetworkController();
-        when(mMockTm.isDataCapable()).thenReturn(false);
+        when(mMockTm.isDataConnectionEnabled()).thenReturn(false);
         setupDefaultSignal();
         updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0);
         setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
@@ -444,7 +453,7 @@
     @Test
     public void testAlwaysShowDataRatIcon() {
         setupDefaultSignal();
-        when(mMockTm.isDataCapable()).thenReturn(false);
+        when(mMockTm.isDataConnectionEnabled()).thenReturn(false);
         updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED,
                 TelephonyManager.NETWORK_TYPE_GSM);
 
@@ -480,8 +489,13 @@
 
         verifyDataIndicators(TelephonyIcons.ICON_LTE);
 
-        when(mServiceState.getDataNetworkType())
-                .thenReturn(TelephonyManager.NETWORK_TYPE_HSPA);
+        NetworkRegistrationInfo fakeRegInfo = new NetworkRegistrationInfo.Builder()
+                .setTransportType(TRANSPORT_TYPE_WWAN)
+                .setDomain(DOMAIN_PS)
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_HSPA)
+                .build();
+        when(mServiceState.getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN))
+                .thenReturn(fakeRegInfo);
         updateServiceState();
         verifyDataIndicators(TelephonyIcons.ICON_H);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
index 4103d71..4406248 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
@@ -30,13 +30,12 @@
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 
-import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.TelephonyIntents;
 import com.android.settingslib.graph.SignalDrawable;
 import com.android.settingslib.net.DataUsageController;
 import com.android.systemui.R;
@@ -411,14 +410,14 @@
             boolean showPlmn, String plmn) {
 
         Intent intent = new Intent();
-        intent.setAction(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION);
+        intent.setAction(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED);
 
-        intent.putExtra(TelephonyIntents.EXTRA_SHOW_SPN, showSpn);
-        intent.putExtra(TelephonyIntents.EXTRA_SPN, spn);
+        intent.putExtra(TelephonyManager.EXTRA_SHOW_SPN, showSpn);
+        intent.putExtra(TelephonyManager.EXTRA_SPN, spn);
 
-        intent.putExtra(TelephonyIntents.EXTRA_SHOW_PLMN, showPlmn);
-        intent.putExtra(TelephonyIntents.EXTRA_PLMN, plmn);
-        intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, mSubId);
+        intent.putExtra(TelephonyManager.EXTRA_SHOW_PLMN, showPlmn);
+        intent.putExtra(TelephonyManager.EXTRA_PLMN, plmn);
+        SubscriptionManager.putSubscriptionIdExtra(intent, mSubId);
 
         return intent;
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
index 709a1a8..3f3f4d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
@@ -19,9 +19,11 @@
 import org.junit.After
 import org.junit.Assert
 import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
 import org.junit.Assert.assertNotEquals
 import org.junit.Assert.assertTrue
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyFloat
@@ -154,6 +156,7 @@
         verify(mockEndListener).onAnimationEnd(
                 testView,
                 DynamicAnimation.TRANSLATION_X,
+                wasFling = false,
                 canceled = false,
                 finalValue = 10f,
                 finalVelocity = 0f,
@@ -175,6 +178,7 @@
         verify(mockEndListener).onAnimationEnd(
                 testView,
                 DynamicAnimation.TRANSLATION_Y,
+                wasFling = false,
                 canceled = false,
                 finalValue = 500f,
                 finalVelocity = 0f,
@@ -214,8 +218,8 @@
 
         verifyUpdateListenerCalls(animator, mockUpdateListener)
         verify(mockEndListener, times(1)).onAnimationEnd(
-                eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), anyFloat(), anyFloat(),
-                eq(true))
+                eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(false), anyFloat(),
+                anyFloat(), eq(true))
         verify(mockEndAction, times(1)).run()
 
         animator
@@ -329,13 +333,24 @@
         // The original end listener should also have been called, with allEnded = true since it was
         // provided to an animator that animated only TRANSLATION_X.
         verify(mockEndListener, times(1))
-                .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X, false, 200f, 0f, true)
+                .onAnimationEnd(
+                        testView, DynamicAnimation.TRANSLATION_X,
+                        wasFling = false,
+                        canceled = false,
+                        finalValue = 200f,
+                        finalVelocity = 0f,
+                        allRelevantPropertyAnimsEnded = true)
         verifyNoMoreInteractions(mockEndListener)
 
         // The second end listener should have been called, but with allEnded = false since it was
         // provided to an animator that animated both TRANSLATION_X and TRANSLATION_Y.
         verify(secondEndListener, times(1))
-                .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X, false, 200f, 0f, false)
+                .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X,
+                        wasFling = false,
+                        canceled = false,
+                        finalValue = 200f,
+                        finalVelocity = 0f,
+                        allRelevantPropertyAnimsEnded = false)
         verifyNoMoreInteractions(secondEndListener)
 
         PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_Y)
@@ -345,7 +360,12 @@
         verifyNoMoreInteractions(mockEndListener)
 
         verify(secondEndListener, times(1))
-                .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_Y, false, 4000f, 0f, true)
+                .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_Y,
+                        wasFling = false,
+                        canceled = false,
+                        finalValue = 4000f,
+                        finalVelocity = 0f,
+                        allRelevantPropertyAnimsEnded = true)
         verifyNoMoreInteractions(secondEndListener)
     }
 
@@ -364,8 +384,8 @@
         assertEquals(10f, testView.translationX, 1f)
         verify(mockEndListener, times(1))
                 .onAnimationEnd(
-                        eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(10f),
-                        anyFloat(), eq(true))
+                        eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(true), eq(false),
+                        eq(10f), anyFloat(), eq(true))
 
         animator
                 .fling(
@@ -381,8 +401,39 @@
         assertEquals(-5f, testView.translationX, 1f)
         verify(mockEndListener, times(1))
                 .onAnimationEnd(
-                        eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(-5f),
-                        anyFloat(), eq(true))
+                        eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(true), eq(false),
+                        eq(-5f), anyFloat(), eq(true))
+    }
+
+    @Test
+    fun testIsPropertyAnimating() {
+        PhysicsAnimatorTestUtils.setAllAnimationsBlock(false)
+
+        testView.physicsAnimator
+                .spring(DynamicAnimation.TRANSLATION_X, 500f, springConfig)
+                .fling(DynamicAnimation.TRANSLATION_Y, 10f, flingConfig)
+                .spring(DynamicAnimation.TRANSLATION_Z, 1000f, springConfig)
+                .start()
+
+        // All of the properties we just started should be animating.
+        assertTrue(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_X))
+        assertTrue(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Y))
+        assertTrue(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Z))
+
+        // Block until x and y end.
+        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(testView.physicsAnimator,
+                DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y)
+
+        // Verify that x and y are no longer animating, but that Z is (it's springing to 1000f).
+        assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_X))
+        assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Y))
+        assertTrue(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Z))
+
+        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Z)
+
+        assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_X))
+        assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Y))
+        assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Z))
     }
 
     @Test
@@ -395,6 +446,119 @@
         assertEquals(200f, testView.translationX, 1f)
     }
 
+    @Test
+    @Ignore("Sporadically flaking.")
+    fun testFlingThenSpring() {
+        PhysicsAnimatorTestUtils.setAllAnimationsBlock(false)
+
+        // Start at 500f and fling hard to the left. We should quickly reach the 250f minimum, fly
+        // past it since there's so much velocity remaining, then spring back to 250f.
+        testView.translationX = 500f
+        animator
+                .flingThenSpring(
+                        DynamicAnimation.TRANSLATION_X,
+                        -5000f,
+                        flingConfig.apply { min = 250f },
+                        springConfig)
+                .addUpdateListener(mockUpdateListener)
+                .addEndListener(mockEndListener)
+                .withEndActions(mockEndAction::run)
+                .start()
+
+        // Block until we pass the minimum.
+        PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(
+                animator) { v -> v.translationX <= 250f }
+
+        // Double check that the view is there.
+        assertTrue(testView.translationX <= 250f)
+
+        // The update listener should have been called with a value < 500f, and then a value less
+        // than or equal to the 250f minimum.
+        verifyAnimationUpdateFrames(animator, DynamicAnimation.TRANSLATION_X,
+                { u -> u.value < 500f },
+                { u -> u.value <= 250f })
+
+        // Despite the fact that the fling has ended, the end listener shouldn't have been called
+        // since we're about to begin springing the same property.
+        verifyNoMoreInteractions(mockEndListener)
+        verifyNoMoreInteractions(mockEndAction)
+
+        // Wait for the spring to finish.
+        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_X)
+
+        // Make sure we continued past 250f since the spring should have been started with some
+        // remaining negative velocity from the fling.
+        verifyAnimationUpdateFrames(animator, DynamicAnimation.TRANSLATION_X,
+                { u -> u.value < 250f })
+
+        // At this point, the animation end listener should have been called once, and only once,
+        // when the spring ended at 250f.
+        verify(mockEndListener).onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X,
+                wasFling = false,
+                canceled = false,
+                finalValue = 250f,
+                finalVelocity = 0f,
+                allRelevantPropertyAnimsEnded = true)
+        verifyNoMoreInteractions(mockEndListener)
+
+        // The end action should also have been called once.
+        verify(mockEndAction, times(1)).run()
+        verifyNoMoreInteractions(mockEndAction)
+
+        assertEquals(250f, testView.translationX)
+    }
+
+    @Test
+    fun testFlingThenSpring_objectOutsideFlingBounds() {
+        // Start the view at x = -500, well outside the fling bounds of min = 0f, with strong
+        // negative velocity.
+        testView.translationX = -500f
+        animator
+                .flingThenSpring(
+                        DynamicAnimation.TRANSLATION_X,
+                        -5000f,
+                        flingConfig.apply { min = 0f },
+                        springConfig)
+                .addUpdateListener(mockUpdateListener)
+                .addEndListener(mockEndListener)
+                .withEndActions(mockEndAction::run)
+                .start()
+
+        // The initial -5000f velocity should result in frames to the left of -500f before the view
+        // springs back towards 0f.
+        verifyAnimationUpdateFrames(
+                animator, DynamicAnimation.TRANSLATION_X,
+                { u -> u.value < -500f },
+                { u -> u.value > -500f })
+
+        // We should end up at the fling min.
+        assertEquals(0f, testView.translationX, 1f)
+    }
+
+    @Test
+    fun testFlingToMinMaxThenSpring() {
+        // Start at x = 500f.
+        testView.translationX = 500f
+
+        // Fling to the left at the very sad rate of -1 pixels per second. That won't get us much of
+        // anywhere, and certainly not to the 0f min.
+        animator
+                // Good thing we have flingToMinMaxThenSpring!
+                .flingThenSpring(
+                        DynamicAnimation.TRANSLATION_X,
+                        -10000f,
+                        flingConfig.apply { min = 0f },
+                        springConfig,
+                        flingMustReachMinOrMax = true)
+                .addUpdateListener(mockUpdateListener)
+                .addEndListener(mockEndListener)
+                .withEndActions(mockEndAction::run)
+                .start()
+
+        // Thanks, flingToMinMaxThenSpring, for adding enough velocity to get us here.
+        assertEquals(0f, testView.translationX, 1f)
+    }
+
     /**
      * Verifies that the calls to the mock update listener match the animation update frames
      * reported by the test internal listener, in order.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 2854665..80aa6f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -69,7 +69,7 @@
 
     @Test
     public void testRegisteredWithDispatcher() {
-        verify(mBroadcastDispatcher).registerReceiver(any(BroadcastReceiver.class),
+        verify(mBroadcastDispatcher).registerReceiverWithHandler(any(BroadcastReceiver.class),
                 any(IntentFilter.class),
                 any(Handler.class)); // VolumeDialogControllerImpl does not call with user
     }
diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp
index 0be853a..cf2b1f06 100644
--- a/packages/Tethering/Android.bp
+++ b/packages/Tethering/Android.bp
@@ -16,11 +16,11 @@
 
 java_defaults {
     name: "TetheringAndroidLibraryDefaults",
-    platform_apis: true,
+    sdk_version: "system_current",
     srcs: [
         "src/**/*.java",
         ":framework-tethering-shared-srcs",
-        ":net-module-utils-srcs",
+        ":tethering-module-utils-srcs",
         ":services-tethering-shared-srcs",
     ],
     static_libs: [
@@ -29,12 +29,13 @@
         "netlink-client",
         "networkstack-aidl-interfaces-unstable-java",
         "android.hardware.tetheroffload.control-V1.0-java",
+        "net-utils-framework-common",
     ],
     libs: [
         "framework-tethering",
         "unsupportedappusage",
     ],
-
+    plugins: ["java_api_finder"],
     manifest: "AndroidManifestBase.xml",
 }
 
@@ -80,7 +81,7 @@
 // Common defaults for compiling the actual APK.
 java_defaults {
     name: "TetheringAppDefaults",
-    platform_apis: true,
+    sdk_version: "system_current",
     privileged: true,
     // Build system doesn't track transitive dependeicies for jni_libs, list all the dependencies
     // explicitly.
@@ -123,4 +124,5 @@
     use_embedded_native_libs: true,
     // The permission configuration *must* be included to ensure security of the device
     required: ["NetworkPermissionConfig"],
+    apex_available: ["com.android.tethering"],
 }
diff --git a/packages/Tethering/AndroidManifest.xml b/packages/Tethering/AndroidManifest.xml
index 87a8c3f..c71d0d7 100644
--- a/packages/Tethering/AndroidManifest.xml
+++ b/packages/Tethering/AndroidManifest.xml
@@ -32,8 +32,11 @@
     <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
     <uses-permission android:name="android.permission.MANAGE_USB" />
     <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
+    <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
     <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
+    <uses-permission android:name="android.permission.TETHER_PRIVILEGED" />
     <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
+    <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
 
     <application
diff --git a/packages/Tethering/common/TetheringLib/Android.bp b/packages/Tethering/common/TetheringLib/Android.bp
index 5785707..a8d1239 100644
--- a/packages/Tethering/common/TetheringLib/Android.bp
+++ b/packages/Tethering/common/TetheringLib/Android.bp
@@ -36,6 +36,7 @@
     sdk_version: "system_current",
     srcs: [
         "src/android/net/TetheringManager.java",
+        "src/android/net/TetheringConstants.java",
         ":framework-tethering-annotations",
     ],
     static_libs: [
@@ -47,15 +48,27 @@
     libs: [
         "android_system_stubs_current",
     ],
+
+    hostdex: true, // for hiddenapi check
+    visibility: [
+        "//frameworks/base/packages/Tethering:__subpackages__",
+        //TODO(b/147200698) remove below lines when the platform is built with stubs
+        "//frameworks/base",
+        "//frameworks/base/services",
+        "//frameworks/base/services/core",
+    ],
+    apex_available: ["com.android.tethering"],
 }
 
 filegroup {
     name: "framework-tethering-srcs",
     srcs: [
         "src/android/net/TetheringManager.java",
+        "src/android/net/TetheringConstants.java",
         "src/android/net/IIntResultListener.aidl",
         "src/android/net/ITetheringEventCallback.aidl",
         "src/android/net/ITetheringConnector.aidl",
+        "src/android/net/TetheringCallbackStartedParcel.aidl",
         "src/android/net/TetheringConfigurationParcel.aidl",
         "src/android/net/TetherStatesParcel.aidl",
     ],
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl b/packages/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl
index 2836195..28a810d 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl
+++ b/packages/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl
@@ -18,6 +18,7 @@
 
 import android.net.Network;
 import android.net.TetheringConfigurationParcel;
+import android.net.TetheringCallbackStartedParcel;
 import android.net.TetherStatesParcel;
 
 /**
@@ -26,8 +27,8 @@
  */
 oneway interface ITetheringEventCallback
 {
-    void onCallbackStarted(in Network network, in TetheringConfigurationParcel config,
-            in TetherStatesParcel states);
+    /** Called immediately after the callbacks are registered */
+    void onCallbackStarted(in TetheringCallbackStartedParcel parcel);
     void onCallbackStopped(int errorCode);
     void onUpstreamChanged(in Network network);
     void onConfigurationChanged(in TetheringConfigurationParcel config);
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl b/packages/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl
new file mode 100644
index 0000000..14ee2d3e
--- /dev/null
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.Network;
+import android.net.TetheringConfigurationParcel;
+import android.net.TetherStatesParcel;
+
+/**
+ * Initial information reported by tethering upon callback registration.
+ * @hide
+ */
+parcelable TetheringCallbackStartedParcel {
+    boolean tetheringSupported;
+    Network upstreamNetwork;
+    TetheringConfigurationParcel config;
+    TetherStatesParcel states;
+}
\ No newline at end of file
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java
new file mode 100644
index 0000000..00cf98e
--- /dev/null
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java
@@ -0,0 +1,66 @@
+/*
+ * 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;
+
+import android.os.ResultReceiver;
+
+/**
+ * Collections of constants for internal tethering usage.
+ *
+ * <p>These hidden constants are not in TetheringManager as they are not part of the API stubs
+ * generated for TetheringManager, which prevents the tethering module from linking them at
+ * build time.
+ * TODO: investigate changing the tethering build rules so that Tethering can reference hidden
+ * symbols from framework-tethering even when they are in a non-hidden class.
+ * @hide
+ */
+public class TetheringConstants {
+    /**
+     * Extra used for communicating with the TetherService. Includes the type of tethering to
+     * enable if any.
+     *
+     * {@hide}
+     */
+    public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType";
+    /**
+     * Extra used for communicating with the TetherService. Includes the type of tethering for
+     * which to cancel provisioning.
+     *
+     * {@hide}
+     */
+    public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType";
+    /**
+     * Extra used for communicating with the TetherService. True to schedule a recheck of tether
+     * provisioning.
+     *
+     * {@hide}
+     */
+    public static final String EXTRA_SET_ALARM = "extraSetAlarm";
+    /**
+     * Tells the TetherService to run a provision check now.
+     *
+     * {@hide}
+     */
+    public static final String EXTRA_RUN_PROVISION = "extraRunProvision";
+    /**
+     * Extra used for communicating with the TetherService. Contains the {@link ResultReceiver}
+     * which will receive provisioning results. Can be left empty.
+     *
+     * {@hide}
+     */
+    public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback";
+}
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index a49ab85..e1b9c16 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -15,11 +15,13 @@
  */
 package android.net;
 
-import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
-
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.content.Context;
-import android.net.ConnectivityManager.OnTetheringEventCallback;
+import android.os.Bundle;
 import android.os.ConditionVariable;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -27,6 +29,11 @@
 import android.util.ArrayMap;
 import android.util.Log;
 
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
@@ -36,7 +43,8 @@
  *
  * @hide
  */
-// TODO: make it @SystemApi
+@SystemApi
+@TestApi
 public class TetheringManager {
     private static final String TAG = TetheringManager.class.getSimpleName();
     private static final int DEFAULT_TIMEOUT_MS = 60_000;
@@ -46,12 +54,80 @@
     private final ITetheringConnector mConnector;
     private final TetheringCallbackInternal mCallback;
     private final Context mContext;
-    private final ArrayMap<OnTetheringEventCallback, ITetheringEventCallback>
+    private final ArrayMap<TetheringEventCallback, ITetheringEventCallback>
             mTetheringEventCallbacks = new ArrayMap<>();
 
     private TetheringConfigurationParcel mTetheringConfiguration;
     private TetherStatesParcel mTetherStatesParcel;
 
+    /**
+     * Broadcast Action: A tetherable connection has come or gone.
+     * Uses {@code TetheringManager.EXTRA_AVAILABLE_TETHER},
+     * {@code TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY},
+     * {@code TetheringManager.EXTRA_ACTIVE_TETHER}, and
+     * {@code TetheringManager.EXTRA_ERRORED_TETHER} to indicate
+     * the current state of tethering.  Each include a list of
+     * interface names in that state (may be empty).
+     */
+    public static final String ACTION_TETHER_STATE_CHANGED =
+            "android.net.conn.TETHER_STATE_CHANGED";
+
+    /**
+     * gives a String[] listing all the interfaces configured for
+     * tethering and currently available for tethering.
+     */
+    public static final String EXTRA_AVAILABLE_TETHER = "availableArray";
+
+    /**
+     * gives a String[] listing all the interfaces currently in local-only
+     * mode (ie, has DHCPv4+IPv6-ULA support and no packet forwarding)
+     */
+    public static final String EXTRA_ACTIVE_LOCAL_ONLY = "android.net.extra.ACTIVE_LOCAL_ONLY";
+
+    /**
+     * gives a String[] listing all the interfaces currently tethered
+     * (ie, has DHCPv4 support and packets potentially forwarded/NATed)
+     */
+    public static final String EXTRA_ACTIVE_TETHER = "tetherArray";
+
+    /**
+     * gives a String[] listing all the interfaces we tried to tether and
+     * failed.  Use {@link #getLastTetherError} to find the error code
+     * for any interfaces listed here.
+     */
+    public static final String EXTRA_ERRORED_TETHER = "erroredArray";
+
+    /**
+     * Invalid tethering type.
+     * @see #startTethering.
+     */
+    public static final int TETHERING_INVALID   = -1;
+
+    /**
+     * Wifi tethering type.
+     * @see #startTethering.
+     */
+    public static final int TETHERING_WIFI      = 0;
+
+    /**
+     * USB tethering type.
+     * @see #startTethering.
+     */
+    public static final int TETHERING_USB       = 1;
+
+    /**
+     * Bluetooth tethering type.
+     * @see #startTethering.
+     */
+    public static final int TETHERING_BLUETOOTH = 2;
+
+    /**
+     * Wifi P2p tethering type.
+     * Wifi P2p tethering is set through events automatically, and don't
+     * need to start from #startTethering.
+     */
+    public static final int TETHERING_WIFI_P2P = 3;
+
     public static final int TETHER_ERROR_NO_ERROR           = 0;
     public static final int TETHER_ERROR_UNKNOWN_IFACE      = 1;
     public static final int TETHER_ERROR_SERVICE_UNAVAIL    = 2;
@@ -65,12 +141,14 @@
     public static final int TETHER_ERROR_IFACE_CFG_ERROR      = 10;
     public static final int TETHER_ERROR_PROVISION_FAILED     = 11;
     public static final int TETHER_ERROR_DHCPSERVER_ERROR     = 12;
-    public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN  = 13;
+    public static final int TETHER_ERROR_ENTITLEMENT_UNKNOWN = 13;
     public static final int TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION = 14;
     public static final int TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION = 15;
 
     /**
      * Create a TetheringManager object for interacting with the tethering service.
+     *
+     * {@hide}
      */
     public TetheringManager(@NonNull final Context context, @NonNull final IBinder service) {
         mContext = context;
@@ -134,10 +212,9 @@
         private final ConditionVariable mWaitForCallback = new ConditionVariable();
 
         @Override
-        public void onCallbackStarted(Network network, TetheringConfigurationParcel config,
-                TetherStatesParcel states) {
-            mTetheringConfiguration = config;
-            mTetherStatesParcel = states;
+        public void onCallbackStarted(TetheringCallbackStartedParcel parcel) {
+            mTetheringConfiguration = parcel.config;
+            mTetherStatesParcel = parcel.states;
             mWaitForCallback.open();
         }
 
@@ -180,6 +257,8 @@
      *
      * @param iface the interface name to tether.
      * @return error a {@code TETHER_ERROR} value indicating success or failure type
+     *
+     * {@hide}
      */
     @Deprecated
     public int tether(@NonNull final String iface) {
@@ -201,6 +280,8 @@
      *
      * @deprecated The only usages is PanService. It uses this for legacy reasons
      * and will migrate away as soon as possible.
+     *
+     * {@hide}
      */
     @Deprecated
     public int untether(@NonNull final String iface) {
@@ -225,6 +306,8 @@
      * #startTethering or #stopTethering which encapsulate proper entitlement logic. If the API is
      * used and an entitlement check is needed, downstream USB tethering will be enabled but will
      * not have any upstream.
+     *
+     * {@hide}
      */
     @Deprecated
     public int setUsbTethering(final boolean enable) {
@@ -245,7 +328,7 @@
     /**
      * Starts tethering and runs tether provisioning for the given type if needed. If provisioning
      * fails, stopTethering will be called automatically.
-     *
+     * @hide
      */
     // TODO: improve the usage of ResultReceiver, b/145096122
     public void startTethering(final int type, @NonNull final ResultReceiver receiver,
@@ -280,11 +363,63 @@
     }
 
     /**
+     * Callback for use with {@link #getLatestTetheringEntitlementResult} to find out whether
+     * entitlement succeeded.
+     */
+    public interface OnTetheringEntitlementResultListener  {
+        /**
+         * Called to notify entitlement result.
+         *
+         * @param resultCode an int value of entitlement result. It may be one of
+         *         {@link #TETHER_ERROR_NO_ERROR},
+         *         {@link #TETHER_ERROR_PROVISION_FAILED}, or
+         *         {@link #TETHER_ERROR_ENTITLEMENT_UNKNOWN}.
+         */
+        void onTetheringEntitlementResult(int resultCode);
+    }
+
+    /**
      * Request the latest value of the tethering entitlement check.
      *
-     * Note: Allow privileged apps who have TETHER_PRIVILEGED permission to access. If it turns
-     * out some such apps are observed to abuse this API, change to per-UID limits on this API
-     * if it's really needed.
+     * <p>This method will only return the latest entitlement result if it is available. If no
+     * cached entitlement result is available, and {@code showEntitlementUi} is false,
+     * {@link #TETHER_ERROR_ENTITLEMENT_UNKNOWN} will be returned. If {@code showEntitlementUi} is
+     * true, entitlement will be run.
+     *
+     * @param type the downstream type of tethering. Must be one of {@code #TETHERING_*} constants.
+     * @param showEntitlementUi a boolean indicating whether to run UI-based entitlement check.
+     * @param executor the executor on which callback will be invoked.
+     * @param listener an {@link OnTetheringEntitlementResultListener} which will be called to
+     *         notify the caller of the result of entitlement check. The listener may be called zero
+     *         or one time.
+     */
+    @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
+    public void requestLatestTetheringEntitlementResult(int type, boolean showEntitlementUi,
+            @NonNull Executor executor,
+            @NonNull final OnTetheringEntitlementResultListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException(
+                    "OnTetheringEntitlementResultListener cannot be null.");
+        }
+
+        ResultReceiver wrappedListener = new ResultReceiver(null /* handler */) {
+            @Override
+            protected void onReceiveResult(int resultCode, Bundle resultData) {
+                executor.execute(() -> {
+                    listener.onTetheringEntitlementResult(resultCode);
+                });
+            }
+        };
+
+        requestLatestTetheringEntitlementResult(type, wrappedListener,
+                    showEntitlementUi);
+    }
+
+    /**
+     * Helper function of #requestLatestTetheringEntitlementResult to remain backwards compatible
+     * with ConnectivityManager#getLatestTetheringEntitlementResult
+     *
+     * {@hide}
      */
     // TODO: improve the usage of ResultReceiver, b/145096122
     public void requestLatestTetheringEntitlementResult(final int type,
@@ -301,9 +436,126 @@
     }
 
     /**
+     * Callback for use with {@link registerTetheringEventCallback} to find out tethering
+     * upstream status.
+     */
+    public abstract static class TetheringEventCallback {
+        /**
+         * Called when tethering supported status changed.
+         *
+         * <p>This will be called immediately after the callback is registered, and may be called
+         * multiple times later upon changes.
+         *
+         * <p>Tethering may be disabled via system properties, device configuration, or device
+         * policy restrictions.
+         *
+         * @param supported The new supported status
+         */
+        public void onTetheringSupported(boolean supported) {}
+
+        /**
+         * Called when tethering upstream changed.
+         *
+         * <p>This will be called immediately after the callback is registered, and may be called
+         * multiple times later upon changes.
+         *
+         * @param network the {@link Network} of tethering upstream. Null means tethering doesn't
+         * have any upstream.
+         */
+        public void onUpstreamChanged(@Nullable Network network) {}
+
+        /**
+         * Called when there was a change in tethering interface regular expressions.
+         *
+         * <p>This will be called immediately after the callback is registered, and may be called
+         * multiple times later upon changes.
+         * @param reg The new regular expressions.
+         * @deprecated Referencing interfaces by regular expressions is a deprecated mechanism.
+         */
+        @Deprecated
+        public void onTetherableInterfaceRegexpsChanged(@NonNull TetheringInterfaceRegexps reg) {}
+
+        /**
+         * Called when there was a change in the list of tetherable interfaces.
+         *
+         * <p>This will be called immediately after the callback is registered, and may be called
+         * multiple times later upon changes.
+         * @param interfaces The list of tetherable interfaces.
+         */
+        public void onTetherableInterfacesChanged(@NonNull List<String> interfaces) {}
+
+        /**
+         * Called when there was a change in the list of tethered interfaces.
+         *
+         * <p>This will be called immediately after the callback is registered, and may be called
+         * multiple times later upon changes.
+         * @param interfaces The list of tethered interfaces.
+         */
+        public void onTetheredInterfacesChanged(@NonNull List<String> interfaces) {}
+
+        /**
+         * Called when an error occurred configuring tethering.
+         *
+         * <p>This will be called immediately after the callback is registered if the latest status
+         * on the interface is an error, and may be called multiple times later upon changes.
+         * @param ifName Name of the interface.
+         * @param error One of {@code TetheringManager#TETHER_ERROR_*}.
+         */
+        public void onError(@NonNull String ifName, int error) {}
+    }
+
+    /**
+     * Regular expressions used to identify tethering interfaces.
+     * @deprecated Referencing interfaces by regular expressions is a deprecated mechanism.
+     */
+    @Deprecated
+    public static class TetheringInterfaceRegexps {
+        private final String[] mTetherableBluetoothRegexs;
+        private final String[] mTetherableUsbRegexs;
+        private final String[] mTetherableWifiRegexs;
+
+        public TetheringInterfaceRegexps(@NonNull String[] tetherableBluetoothRegexs,
+                @NonNull String[] tetherableUsbRegexs, @NonNull String[] tetherableWifiRegexs) {
+            mTetherableBluetoothRegexs = tetherableBluetoothRegexs.clone();
+            mTetherableUsbRegexs = tetherableUsbRegexs.clone();
+            mTetherableWifiRegexs = tetherableWifiRegexs.clone();
+        }
+
+        @NonNull
+        public List<String> getTetherableBluetoothRegexs() {
+            return Collections.unmodifiableList(Arrays.asList(mTetherableBluetoothRegexs));
+        }
+
+        @NonNull
+        public List<String> getTetherableUsbRegexs() {
+            return Collections.unmodifiableList(Arrays.asList(mTetherableUsbRegexs));
+        }
+
+        @NonNull
+        public List<String> getTetherableWifiRegexs() {
+            return Collections.unmodifiableList(Arrays.asList(mTetherableWifiRegexs));
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mTetherableBluetoothRegexs, mTetherableUsbRegexs,
+                    mTetherableWifiRegexs);
+        }
+
+        @Override
+        public boolean equals(@Nullable Object obj) {
+            if (!(obj instanceof TetheringInterfaceRegexps)) return false;
+            final TetheringInterfaceRegexps other = (TetheringInterfaceRegexps) obj;
+            return Arrays.equals(mTetherableBluetoothRegexs, other.mTetherableBluetoothRegexs)
+                    && Arrays.equals(mTetherableUsbRegexs, other.mTetherableUsbRegexs)
+                    && Arrays.equals(mTetherableWifiRegexs, other.mTetherableWifiRegexs);
+        }
+    }
+
+    /**
      * Start listening to tethering change events. Any new added callback will receive the last
      * tethering status right away. If callback is registered,
-     * {@link OnTetheringEventCallback#onUpstreamChanged} will immediately be called. If tethering
+     * {@link TetheringEventCallback#onUpstreamChanged} will immediately be called. If tethering
      * has no upstream or disabled, the argument of callback will be null. The same callback object
      * cannot be registered twice.
      *
@@ -311,15 +563,20 @@
      * @param callback the callback to be called when tethering has change events.
      */
     public void registerTetheringEventCallback(@NonNull Executor executor,
-            @NonNull OnTetheringEventCallback callback) {
+            @NonNull TetheringEventCallback callback) {
         final String callerPkg = mContext.getOpPackageName();
         Log.i(TAG, "registerTetheringEventCallback caller:" + callerPkg);
 
         synchronized (mTetheringEventCallbacks) {
-            if (!mTetheringEventCallbacks.containsKey(callback)) {
+            if (mTetheringEventCallbacks.containsKey(callback)) {
                 throw new IllegalArgumentException("callback was already registered.");
             }
             final ITetheringEventCallback remoteCallback = new ITetheringEventCallback.Stub() {
+                // Only accessed with a lock on this object
+                private final HashMap<String, Integer> mErrorStates = new HashMap<>();
+                private String[] mLastTetherableInterfaces = null;
+                private String[] mLastTetheredInterfaces = null;
+
                 @Override
                 public void onUpstreamChanged(Network network) throws RemoteException {
                     executor.execute(() -> {
@@ -327,11 +584,45 @@
                     });
                 }
 
+                private synchronized void sendErrorCallbacks(final TetherStatesParcel newStates) {
+                    for (int i = 0; i < newStates.erroredIfaceList.length; i++) {
+                        final String iface = newStates.erroredIfaceList[i];
+                        final Integer lastError = mErrorStates.get(iface);
+                        final int newError = newStates.lastErrorList[i];
+                        if (newError != TETHER_ERROR_NO_ERROR
+                                && !Objects.equals(lastError, newError)) {
+                            callback.onError(iface, newError);
+                        }
+                        mErrorStates.put(iface, newError);
+                    }
+                }
+
+                private synchronized void maybeSendTetherableIfacesChangedCallback(
+                        final TetherStatesParcel newStates) {
+                    if (Arrays.equals(mLastTetherableInterfaces, newStates.availableList)) return;
+                    mLastTetherableInterfaces = newStates.availableList.clone();
+                    callback.onTetherableInterfacesChanged(
+                            Collections.unmodifiableList(Arrays.asList(mLastTetherableInterfaces)));
+                }
+
+                private synchronized void maybeSendTetheredIfacesChangedCallback(
+                        final TetherStatesParcel newStates) {
+                    if (Arrays.equals(mLastTetheredInterfaces, newStates.tetheredList)) return;
+                    mLastTetheredInterfaces = newStates.tetheredList.clone();
+                    callback.onTetheredInterfacesChanged(
+                            Collections.unmodifiableList(Arrays.asList(mLastTetheredInterfaces)));
+                }
+
+                // Called immediately after the callbacks are registered.
                 @Override
-                public void onCallbackStarted(Network network, TetheringConfigurationParcel config,
-                        TetherStatesParcel states) {
+                public void onCallbackStarted(TetheringCallbackStartedParcel parcel) {
                     executor.execute(() -> {
-                        callback.onUpstreamChanged(network);
+                        callback.onTetheringSupported(parcel.tetheringSupported);
+                        callback.onUpstreamChanged(parcel.upstreamNetwork);
+                        sendErrorCallbacks(parcel.states);
+                        sendRegexpsChanged(parcel.config);
+                        maybeSendTetherableIfacesChangedCallback(parcel.states);
+                        maybeSendTetheredIfacesChangedCallback(parcel.states);
                     });
                 }
 
@@ -342,11 +633,26 @@
                     });
                 }
 
-                @Override
-                public void onConfigurationChanged(TetheringConfigurationParcel config) { }
+                private void sendRegexpsChanged(TetheringConfigurationParcel parcel) {
+                    callback.onTetherableInterfaceRegexpsChanged(new TetheringInterfaceRegexps(
+                            parcel.tetherableBluetoothRegexs,
+                            parcel.tetherableUsbRegexs,
+                            parcel.tetherableWifiRegexs));
+                }
 
                 @Override
-                public void onTetherStatesChanged(TetherStatesParcel states) { }
+                public void onConfigurationChanged(TetheringConfigurationParcel config) {
+                    executor.execute(() -> sendRegexpsChanged(config));
+                }
+
+                @Override
+                public void onTetherStatesChanged(TetherStatesParcel states) {
+                    executor.execute(() -> {
+                        sendErrorCallbacks(states);
+                        maybeSendTetherableIfacesChangedCallback(states);
+                        maybeSendTetheredIfacesChangedCallback(states);
+                    });
+                }
             };
             try {
                 mConnector.registerTetheringEventCallback(remoteCallback, callerPkg);
@@ -363,7 +669,7 @@
      *
      * @param callback previously registered callback.
      */
-    public void unregisterTetheringEventCallback(@NonNull final OnTetheringEventCallback callback) {
+    public void unregisterTetheringEventCallback(@NonNull final TetheringEventCallback callback) {
         final String callerPkg = mContext.getOpPackageName();
         Log.i(TAG, "unregisterTetheringEventCallback caller:" + callerPkg);
 
@@ -387,6 +693,7 @@
      * @param iface The name of the interface of interest
      * @return error The error code of the last error tethering or untethering the named
      *               interface
+     * @hide
      */
     public int getLastTetherError(@NonNull final String iface) {
         mCallback.waitForStarted();
@@ -408,6 +715,7 @@
      *
      * @return an array of 0 or more regular expression Strings defining
      *        what interfaces are considered tetherable usb interfaces.
+     * @hide
      */
     public @NonNull String[] getTetherableUsbRegexs() {
         mCallback.waitForStarted();
@@ -421,6 +729,7 @@
      *
      * @return an array of 0 or more regular expression Strings defining
      *        what interfaces are considered tetherable wifi interfaces.
+     * @hide
      */
     public @NonNull String[] getTetherableWifiRegexs() {
         mCallback.waitForStarted();
@@ -434,6 +743,7 @@
      *
      * @return an array of 0 or more regular expression Strings defining
      *        what interfaces are considered tetherable bluetooth interfaces.
+     * @hide
      */
     public @NonNull String[] getTetherableBluetoothRegexs() {
         mCallback.waitForStarted();
@@ -445,6 +755,7 @@
      * device configuration and current interface existence.
      *
      * @return an array of 0 or more Strings of tetherable interface names.
+     * @hide
      */
     public @NonNull String[] getTetherableIfaces() {
         mCallback.waitForStarted();
@@ -457,6 +768,7 @@
      * Get the set of tethered interfaces.
      *
      * @return an array of 0 or more String of currently tethered interface names.
+     * @hide
      */
     public @NonNull String[] getTetheredIfaces() {
         mCallback.waitForStarted();
@@ -470,11 +782,12 @@
      * failed.  Re-attempting to tether may cause them to reset to the Tethered
      * state.  Alternatively, causing the interface to be destroyed and recreated
      * may cause them to reset to the available state.
-     * {@link ConnectivityManager#getLastTetherError} can be used to get more
+     * {@link TetheringManager#getLastTetherError} can be used to get more
      * information on the cause of the errors.
      *
      * @return an array of 0 or more String indicating the interface names
      *        which failed to tether.
+     * @hide
      */
     public @NonNull String[] getTetheringErroredIfaces() {
         mCallback.waitForStarted();
@@ -487,6 +800,7 @@
      * Get the set of tethered dhcp ranges.
      *
      * @deprecated This API just return the default value which is not used in DhcpServer.
+     * @hide
      */
     @Deprecated
     public @NonNull String[] getTetheredDhcpRanges() {
@@ -500,6 +814,7 @@
      * due to device configuration.
      *
      * @return a boolean - {@code true} indicating Tethering is supported.
+     * @hide
      */
     public boolean isTetheringSupported() {
         final String callerPkg = mContext.getOpPackageName();
diff --git a/packages/Tethering/jarjar-rules.txt b/packages/Tethering/jarjar-rules.txt
index d93531b..c6efa41 100644
--- a/packages/Tethering/jarjar-rules.txt
+++ b/packages/Tethering/jarjar-rules.txt
@@ -11,6 +11,7 @@
 rule com.android.internal.util.Preconditions* com.android.networkstack.tethering.util.Preconditions@1
 rule com.android.internal.util.State* com.android.networkstack.tethering.util.State@1
 rule com.android.internal.util.StateMachine* com.android.networkstack.tethering.util.StateMachine@1
+rule com.android.internal.util.TrafficStatsConstants* com.android.networkstack.tethering.util.TrafficStatsConstants@1
 
 rule android.net.LocalLog* com.android.networkstack.tethering.LocalLog@1
 
diff --git a/packages/Tethering/res/values/config.xml b/packages/Tethering/res/values/config.xml
index 37e679d..19e8d69 100644
--- a/packages/Tethering/res/values/config.xml
+++ b/packages/Tethering/res/values/config.xml
@@ -1,7 +1,153 @@
 <?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.
+-->
 <resources>
     <!--
     OEMs that wish to change the below settings must do so via a runtime resource overlay package
     and *NOT* by changing this file. This file is part of the tethering mainline module.
+    TODO: define two resources for each config item: a default_* resource and a config_* resource,
+    config_* is empty by default but may be overridden by RROs.
     -->
+    <!-- List of regexpressions describing the interface (if any) that represent tetherable
+         USB interfaces.  If the device doesn't want to support tethering over USB this should
+         be empty.  An example would be "usb.*" -->
+    <string-array translatable="false" name="config_tether_usb_regexs">
+        <item>"usb\\d"</item>
+        <item>"rndis\\d"</item>
+    </string-array>
+
+    <!-- List of regexpressions describing the interface (if any) that represent tetherable
+         Wifi interfaces.  If the device doesn't want to support tethering over Wifi this
+         should be empty.  An example would be "softap.*" -->
+    <string-array translatable="false" name="config_tether_wifi_regexs">
+        <item>"wlan\\d"</item>
+        <item>"softap\\d"</item>
+    </string-array>
+
+    <!-- List of regexpressions describing the interface (if any) that represent tetherable
+         Wifi P2P interfaces.  If the device doesn't want to support tethering over Wifi P2p this
+         should be empty.  An example would be "p2p-p2p\\d-.*" -->
+    <string-array translatable="false" name="config_tether_wifi_p2p_regexs">
+        <item>"p2p-p2p\\d-.*"</item>
+    </string-array>
+
+    <!-- List of regexpressions describing the interface (if any) that represent tetherable
+         bluetooth interfaces.  If the device doesn't want to support tethering over bluetooth this
+         should be empty. -->
+    <string-array translatable="false" name="config_tether_bluetooth_regexs">
+        <item>"bt-pan"</item>
+    </string-array>
+
+    <!-- Use the old dnsmasq DHCP server for tethering instead of the framework implementation. -->
+    <bool translatable="false" name="config_tether_enable_legacy_dhcp_server">false</bool>
+
+    <!-- Dhcp range (min, max) to use for tethering purposes -->
+    <string-array translatable="false" name="config_tether_dhcp_range">
+    </string-array>
+
+    <!-- Array of ConnectivityManager.TYPE_{BLUETOOTH, ETHERNET, MOBILE, MOBILE_DUN, MOBILE_HIPRI,
+         WIFI} values allowable for tethering.
+
+         Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
+         [1,7,0] for TYPE_WIFI, TYPE_BLUETOOTH, and TYPE_MOBILE.
+
+         This list is also modified by code within the framework, including:
+
+             - TYPE_ETHERNET (9) is prepended to this list, and
+
+             - the return value of TelephonyManager.isTetheringApnRequired()
+               determines how the array is further modified:
+
+                   * TRUE (DUN REQUIRED).
+                     TYPE_MOBILE is removed (if present).
+                     TYPE_MOBILE_HIPRI is removed (if present).
+                     TYPE_MOBILE_DUN is appended (if not already present).
+
+                   * FALSE (DUN NOT REQUIRED).
+                     TYPE_MOBILE_DUN is removed (if present).
+                     If both of TYPE_MOBILE{,_HIPRI} are not present:
+                        TYPE_MOBILE is appended.
+                        TYPE_MOBILE_HIPRI is appended.
+
+         For other changes applied to this list, now and in the future, see
+         com.android.server.connectivity.tethering.TetheringConfiguration.
+
+         Note also: the order of this is important. The first upstream type
+         for which a satisfying network exists is used.
+    -->
+    <integer-array translatable="false" name="config_tether_upstream_types">
+    </integer-array>
+
+    <!-- When true, the tethering upstream network follows the current default
+         Internet network (except when the current default network is mobile,
+         in which case a DUN network will be used if required).
+
+         When true, overrides the config_tether_upstream_types setting above.
+    -->
+    <bool translatable="false" name="config_tether_upstream_automatic">true</bool>
+
+
+    <!-- If the mobile hotspot feature requires provisioning, a package name and class name
+         can be provided to launch a supported application that provisions the devices.
+         EntitlementManager will send an intent to Settings with the specified package name and
+         class name in extras to launch provision app.
+         TODO: note what extras here.
+
+         See EntitlementManager#runUiTetherProvisioning and
+         packages/apps/Settings/src/com/android/settings/network/TetherProvisioningActivity.java
+         for more details.
+
+         For ui-less/periodic recheck support see config_mobile_hotspot_provision_app_no_ui
+        -->
+    <!-- The first element is the package name and the second element is the class name
+         of the provisioning app -->
+    <string-array translatable="false" name="config_mobile_hotspot_provision_app">
+    <!--
+        <item>com.example.provisioning</item>
+        <item>com.example.provisioning.Activity</item>
+    -->
+    </string-array>
+
+    <!-- If the mobile hotspot feature requires provisioning, an action can be provided
+         that will be broadcast in non-ui cases for checking the provisioning status.
+         EntitlementManager will pass the specified name to Settings and Settings would
+         launch provisioning app by sending an intent with the package name.
+
+         A second broadcast, action defined by config_mobile_hotspot_provision_response,
+         will be sent back to notify if provisioning succeeded or not.  The response will
+         match that of the activity in config_mobile_hotspot_provision_app, but instead
+         contained within the int extra "EntitlementResult".
+         TODO: provide the system api for "EntitlementResult" extra and note it here.
+
+         See EntitlementManager#runSilentTetherProvisioning and
+         packages/apps/Settings/src/com/android/settings/wifi/tether/TetherService.java for more
+         details.
+        -->
+    <string translatable="false" name="config_mobile_hotspot_provision_app_no_ui"></string>
+
+    <!-- Sent in response to a provisioning check. The caller must hold the
+         permission android.permission.TETHER_PRIVILEGED for Settings to
+         receive this response.
+
+         See config_mobile_hotspot_provision_response
+         -->
+    <string translatable="false" name="config_mobile_hotspot_provision_response"></string>
+
+    <!-- Number of hours between each background provisioning call -->
+    <integer translatable="false" name="config_mobile_hotspot_provision_check_period">24</integer>
+
+    <!-- ComponentName of the service used to run no ui tether provisioning. -->
+    <string translatable="false" name="config_wifi_tether_enable">com.android.settings/.wifi.tether.TetherService</string>
 </resources>
diff --git a/packages/Tethering/res/values/overlayable.xml b/packages/Tethering/res/values/overlayable.xml
new file mode 100644
index 0000000..e089d9d
--- /dev/null
+++ b/packages/Tethering/res/values/overlayable.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <overlayable name="TetheringConfig">
+        <policy type="product|system|vendor">
+            <item type="array" name="config_tether_usb_regexs"/>
+            <item type="array" name="config_tether_wifi_regexs"/>
+            <item type="array" name="config_tether_wifi_p2p_regexs"/>
+            <item type="array" name="config_tether_bluetooth_regexs"/>
+            <item type="array" name="config_tether_dhcp_range"/>
+            <item type="bool" name="config_tether_enable_legacy_dhcp_server"/>
+            <item type="array" name="config_tether_upstream_types"/>
+            <item type="bool" name="config_tether_upstream_automatic"/>
+            <!-- Configuration values for tethering entitlement check -->
+            <item type="array" name="config_mobile_hotspot_provision_app"/>
+            <item type="string" name="config_mobile_hotspot_provision_app_no_ui"/>
+            <item type="string" name="config_mobile_hotspot_provision_response"/>
+            <item type="integer" name="config_mobile_hotspot_provision_check_period"/>
+            <item type="string" name="config_wifi_tether_enable"/>
+        </policy>
+    </overlayable>
+</resources>
diff --git a/packages/Tethering/res/values/strings.xml b/packages/Tethering/res/values/strings.xml
index ca866a9..792bce9 100644
--- a/packages/Tethering/res/values/strings.xml
+++ b/packages/Tethering/res/values/strings.xml
@@ -1,4 +1,18 @@
 <?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.
+-->
 <resources>
     <!-- Shown when the device is tethered -->
     <!-- Strings for tethered notification title [CHAR LIMIT=200] -->
@@ -9,8 +23,11 @@
     <!-- This notification is shown when tethering has been disabled on a user's device.
     The device is managed by the user's employer. Tethering can't be turned on unless the
     IT administrator allows it. The noun "admin" is another reference for "IT administrator." -->
-    <!-- Strings for tether disabling notification title  [CHAR LIMIT=200] -->
+    <!-- Strings for tether disabling notification title [CHAR LIMIT=200] -->
     <string name="disable_tether_notification_title">Tethering is disabled</string>
-    <!-- Strings for tether disabling notification message  [CHAR LIMIT=200] -->
+    <!-- Strings for tether disabling notification message [CHAR LIMIT=200] -->
     <string name="disable_tether_notification_message">Contact your admin for details</string>
+
+    <!-- Strings for tether notification channel name [CHAR LIMIT=200] -->
+    <string name="notification_channel_tethering_status">Hotspot &amp; tethering status</string>
 </resources>
\ No newline at end of file
diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java
index abfb33c..0491ad7 100644
--- a/packages/Tethering/src/android/net/ip/IpServer.java
+++ b/packages/Tethering/src/android/net/ip/IpServer.java
@@ -24,25 +24,23 @@
 import static android.net.util.NetworkConstants.asByte;
 import static android.net.util.TetheringMessageBase.BASE_IPSERVER;
 
-import android.net.ConnectivityManager;
 import android.net.INetd;
 import android.net.INetworkStackStatusCallback;
-import android.net.INetworkStatsService;
-import android.net.InterfaceConfiguration;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.RouteInfo;
+import android.net.TetheringManager;
 import android.net.dhcp.DhcpServerCallbacks;
 import android.net.dhcp.DhcpServingParamsParcel;
 import android.net.dhcp.DhcpServingParamsParcelExt;
 import android.net.dhcp.IDhcpServer;
 import android.net.ip.RouterAdvertisementDaemon.RaParams;
+import android.net.shared.NetdUtils;
+import android.net.shared.RouteUtils;
 import android.net.util.InterfaceParams;
 import android.net.util.InterfaceSet;
-import android.net.util.NetdService;
 import android.net.util.SharedLog;
-import android.os.INetworkManagementService;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
@@ -119,7 +117,7 @@
          *
          * @param who the calling instance of IpServer
          * @param state one of STATE_*
-         * @param lastError one of ConnectivityManager.TETHER_ERROR_*
+         * @param lastError one of TetheringManager.TETHER_ERROR_*
          */
         public void updateInterfaceState(IpServer who, int state, int lastError) { }
 
@@ -144,10 +142,6 @@
             return InterfaceParams.getByName(ifName);
         }
 
-        public INetd getNetdService() {
-            return NetdService.getInstance();
-        }
-
         /** Create a DhcpServer instance to be used by IpServer. */
         public abstract void makeDhcpServer(String ifName, DhcpServingParamsParcel params,
                 DhcpServerCallbacks cb);
@@ -180,9 +174,7 @@
     private final State mUnavailableState;
 
     private final SharedLog mLog;
-    private final INetworkManagementService mNMService;
     private final INetd mNetd;
-    private final INetworkStatsService mStatsService;
     private final Callback mCallback;
     private final InterfaceController mInterfaceCtrl;
 
@@ -210,16 +202,14 @@
     private int mDhcpServerStartIndex = 0;
     private IDhcpServer mDhcpServer;
     private RaParams mLastRaParams;
+    private LinkAddress mIpv4Address;
 
     public IpServer(
             String ifaceName, Looper looper, int interfaceType, SharedLog log,
-            INetworkManagementService nMService, INetworkStatsService statsService,
-            Callback callback, boolean usingLegacyDhcp, Dependencies deps) {
+            INetd netd, Callback callback, boolean usingLegacyDhcp, Dependencies deps) {
         super(ifaceName, looper);
         mLog = log.forSubComponent(ifaceName);
-        mNMService = nMService;
-        mNetd = deps.getNetdService();
-        mStatsService = statsService;
+        mNetd = netd;
         mCallback = callback;
         mInterfaceCtrl = new InterfaceController(ifaceName, mNetd, mLog);
         mIfaceName = ifaceName;
@@ -228,7 +218,7 @@
         mUsingLegacyDhcp = usingLegacyDhcp;
         mDeps = deps;
         resetLinkProperties();
-        mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+        mLastError = TetheringManager.TETHER_ERROR_NO_ERROR;
         mServingMode = STATE_AVAILABLE;
 
         mInitialState = new InitialState();
@@ -249,7 +239,7 @@
     }
 
     /**
-     * Tethering downstream type. It would be one of ConnectivityManager#TETHERING_*.
+     * Tethering downstream type. It would be one of TetheringManager#TETHERING_*.
      */
     public int interfaceType() {
         return mInterfaceType;
@@ -347,13 +337,13 @@
                         }
                     });
                 } catch (RemoteException e) {
-                    e.rethrowFromSystemServer();
+                    throw new IllegalStateException(e);
                 }
             });
         }
 
         private void handleError() {
-            mLastError = ConnectivityManager.TETHER_ERROR_DHCPSERVER_ERROR;
+            mLastError = TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR;
             transitionTo(mInitialState);
         }
     }
@@ -388,14 +378,15 @@
                     public void callback(int statusCode) {
                         if (statusCode != STATUS_SUCCESS) {
                             mLog.e("Error stopping DHCP server: " + statusCode);
-                            mLastError = ConnectivityManager.TETHER_ERROR_DHCPSERVER_ERROR;
+                            mLastError = TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR;
                             // Not much more we can do here
                         }
                     }
                 });
                 mDhcpServer = null;
             } catch (RemoteException e) {
-                e.rethrowFromSystemServer();
+                mLog.e("Error stopping DHCP", e);
+                // Not much more we can do here
             }
         }
     }
@@ -414,85 +405,70 @@
         // NOTE: All of configureIPv4() will be refactored out of existence
         // into calls to InterfaceController, shared with startIPv4().
         mInterfaceCtrl.clearIPv4Address();
+        mIpv4Address = null;
     }
 
-    // TODO: Refactor this in terms of calls to InterfaceController.
     private boolean configureIPv4(boolean enabled) {
         if (VDBG) Log.d(TAG, "configureIPv4(" + enabled + ")");
 
         // TODO: Replace this hard-coded information with dynamically selected
         // config passed down to us by a higher layer IP-coordinating element.
-        String ipAsString = null;
+        final Inet4Address srvAddr;
         int prefixLen = 0;
-        if (mInterfaceType == ConnectivityManager.TETHERING_USB) {
-            ipAsString = USB_NEAR_IFACE_ADDR;
-            prefixLen = USB_PREFIX_LENGTH;
-        } else if (mInterfaceType == ConnectivityManager.TETHERING_WIFI) {
-            ipAsString = getRandomWifiIPv4Address();
-            prefixLen = WIFI_HOST_IFACE_PREFIX_LENGTH;
-        } else if (mInterfaceType == ConnectivityManager.TETHERING_WIFI_P2P) {
-            ipAsString = WIFI_P2P_IFACE_ADDR;
-            prefixLen = WIFI_P2P_IFACE_PREFIX_LENGTH;
-        } else {
-            // BT configures the interface elsewhere: only start DHCP.
-            final Inet4Address srvAddr = (Inet4Address) parseNumericAddress(BLUETOOTH_IFACE_ADDR);
-            return configureDhcp(enabled, srvAddr, BLUETOOTH_DHCP_PREFIX_LENGTH);
+        try {
+            if (mInterfaceType == TetheringManager.TETHERING_USB) {
+                srvAddr = (Inet4Address) parseNumericAddress(USB_NEAR_IFACE_ADDR);
+                prefixLen = USB_PREFIX_LENGTH;
+            } else if (mInterfaceType == TetheringManager.TETHERING_WIFI) {
+                srvAddr = (Inet4Address) parseNumericAddress(getRandomWifiIPv4Address());
+                prefixLen = WIFI_HOST_IFACE_PREFIX_LENGTH;
+            } else if (mInterfaceType == TetheringManager.TETHERING_WIFI_P2P) {
+                srvAddr = (Inet4Address) parseNumericAddress(WIFI_P2P_IFACE_ADDR);
+                prefixLen = WIFI_P2P_IFACE_PREFIX_LENGTH;
+            } else {
+                // BT configures the interface elsewhere: only start DHCP.
+                // TODO: make all tethering types behave the same way, and delete the bluetooth
+                // code that calls into NetworkManagementService directly.
+                srvAddr = (Inet4Address) parseNumericAddress(BLUETOOTH_IFACE_ADDR);
+                mIpv4Address = new LinkAddress(srvAddr, BLUETOOTH_DHCP_PREFIX_LENGTH);
+                return configureDhcp(enabled, srvAddr, BLUETOOTH_DHCP_PREFIX_LENGTH);
+            }
+            mIpv4Address = new LinkAddress(srvAddr, prefixLen);
+        } catch (IllegalArgumentException e) {
+            mLog.e("Error selecting ipv4 address", e);
+            if (!enabled) stopDhcp();
+            return false;
         }
 
-        final LinkAddress linkAddr;
-        try {
-            final InterfaceConfiguration ifcg = mNMService.getInterfaceConfig(mIfaceName);
-            if (ifcg == null) {
-                mLog.e("Received null interface config");
-                return false;
-            }
+        final Boolean setIfaceUp;
+        if (mInterfaceType == TetheringManager.TETHERING_WIFI
+                || mInterfaceType == TetheringManager.TETHERING_WIFI_P2P) {
+            // The WiFi stack has ownership of the interface up/down state.
+            // It is unclear whether the Bluetooth or USB stacks will manage their own
+            // state.
+            setIfaceUp = null;
+        } else {
+            setIfaceUp = enabled;
+        }
+        if (!mInterfaceCtrl.setInterfaceConfiguration(mIpv4Address, setIfaceUp)) {
+            mLog.e("Error configuring interface");
+            if (!enabled) stopDhcp();
+            return false;
+        }
 
-            InetAddress addr = parseNumericAddress(ipAsString);
-            linkAddr = new LinkAddress(addr, prefixLen);
-            ifcg.setLinkAddress(linkAddr);
-            if (mInterfaceType == ConnectivityManager.TETHERING_WIFI) {
-                // The WiFi stack has ownership of the interface up/down state.
-                // It is unclear whether the Bluetooth or USB stacks will manage their own
-                // state.
-                ifcg.ignoreInterfaceUpDownStatus();
-            } else {
-                if (enabled) {
-                    ifcg.setInterfaceUp();
-                } else {
-                    ifcg.setInterfaceDown();
-                }
-            }
-            ifcg.clearFlag("running");
-
-            // TODO: this may throw if the interface is already gone. Do proper handling and
-            // simplify the DHCP server start/stop.
-            mNMService.setInterfaceConfig(mIfaceName, ifcg);
-
-            if (!configureDhcp(enabled, (Inet4Address) addr, prefixLen)) {
-                return false;
-            }
-        } catch (Exception e) {
-            mLog.e("Error configuring interface " + e);
-            if (!enabled) {
-                try {
-                    // Calling stopDhcp several times is fine
-                    stopDhcp();
-                } catch (Exception dhcpError) {
-                    mLog.e("Error stopping DHCP", dhcpError);
-                }
-            }
+        if (!configureDhcp(enabled, srvAddr, prefixLen)) {
             return false;
         }
 
         // Directly-connected route.
-        final IpPrefix ipv4Prefix = new IpPrefix(linkAddr.getAddress(),
-                linkAddr.getPrefixLength());
+        final IpPrefix ipv4Prefix = new IpPrefix(mIpv4Address.getAddress(),
+                mIpv4Address.getPrefixLength());
         final RouteInfo route = new RouteInfo(ipv4Prefix, null, null, RTN_UNICAST);
         if (enabled) {
-            mLinkProperties.addLinkAddress(linkAddr);
+            mLinkProperties.addLinkAddress(mIpv4Address);
             mLinkProperties.addRoute(route);
         } else {
-            mLinkProperties.removeLinkAddress(linkAddr);
+            mLinkProperties.removeLinkAddress(mIpv4Address);
             mLinkProperties.removeRoute(route);
         }
         return true;
@@ -584,14 +560,12 @@
         if (!deprecatedPrefixes.isEmpty()) {
             final ArrayList<RouteInfo> toBeRemoved =
                     getLocalRoutesFor(mIfaceName, deprecatedPrefixes);
-            try {
-                final int removalFailures = mNMService.removeRoutesFromLocalNetwork(toBeRemoved);
-                if (removalFailures > 0) {
-                    mLog.e(String.format("Failed to remove %d IPv6 routes from local table.",
-                            removalFailures));
-                }
-            } catch (RemoteException e) {
-                mLog.e("Failed to remove IPv6 routes from local table: " + e);
+            // Remove routes from local network.
+            final int removalFailures = RouteUtils.removeRoutesFromLocalNetwork(
+                    mNetd, toBeRemoved);
+            if (removalFailures > 0) {
+                mLog.e(String.format("Failed to remove %d IPv6 routes from local table.",
+                        removalFailures));
             }
 
             for (RouteInfo route : toBeRemoved) mLinkProperties.removeRoute(route);
@@ -608,13 +582,18 @@
                 final ArrayList<RouteInfo> toBeAdded =
                         getLocalRoutesFor(mIfaceName, addedPrefixes);
                 try {
-                    // It's safe to call addInterfaceToLocalNetwork() even if
-                    // the interface is already in the local_network. Note also
-                    // that adding routes that already exist does not cause an
-                    // error (EEXIST is silently ignored).
-                    mNMService.addInterfaceToLocalNetwork(mIfaceName, toBeAdded);
-                } catch (Exception e) {
-                    mLog.e("Failed to add IPv6 routes to local table: " + e);
+                    // It's safe to call networkAddInterface() even if
+                    // the interface is already in the local_network.
+                    mNetd.networkAddInterface(INetd.LOCAL_NET_ID, mIfaceName);
+                    try {
+                        // Add routes from local network. Note that adding routes that
+                        // already exist does not cause an error (EEXIST is silently ignored).
+                        RouteUtils.addRoutesToLocalNetwork(mNetd, mIfaceName, toBeAdded);
+                    } catch (IllegalStateException e) {
+                        mLog.e("Failed to add IPv6 routes to local table: " + e);
+                    }
+                } catch (ServiceSpecificException | RemoteException e) {
+                    mLog.e("Failed to add " + mIfaceName + " to local table: ", e);
                 }
 
                 for (RouteInfo route : toBeAdded) mLinkProperties.addRoute(route);
@@ -728,7 +707,7 @@
             logMessage(this, message.what);
             switch (message.what) {
                 case CMD_TETHER_REQUESTED:
-                    mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+                    mLastError = TetheringManager.TETHER_ERROR_NO_ERROR;
                     switch (message.arg1) {
                         case STATE_LOCAL_ONLY:
                             transitionTo(mLocalHotspotState);
@@ -757,15 +736,17 @@
         @Override
         public void enter() {
             if (!startIPv4()) {
-                mLastError = ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR;
+                mLastError = TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
                 return;
             }
 
             try {
-                mNMService.tetherInterface(mIfaceName);
-            } catch (Exception e) {
+                final IpPrefix ipv4Prefix = new IpPrefix(mIpv4Address.getAddress(),
+                        mIpv4Address.getPrefixLength());
+                NetdUtils.tetherInterface(mNetd, mIfaceName, ipv4Prefix);
+            } catch (RemoteException | ServiceSpecificException e) {
                 mLog.e("Error Tethering: " + e);
-                mLastError = ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR;
+                mLastError = TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR;
                 return;
             }
 
@@ -784,9 +765,9 @@
             stopIPv6();
 
             try {
-                mNMService.untetherInterface(mIfaceName);
-            } catch (Exception e) {
-                mLastError = ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
+                NetdUtils.untetherInterface(mNetd, mIfaceName);
+            } catch (RemoteException | ServiceSpecificException e) {
+                mLastError = TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
                 mLog.e("Failed to untether interface: " + e);
             }
 
@@ -816,7 +797,7 @@
                 case CMD_START_TETHERING_ERROR:
                 case CMD_STOP_TETHERING_ERROR:
                 case CMD_SET_DNS_FORWARDERS_ERROR:
-                    mLastError = ConnectivityManager.TETHER_ERROR_MASTER_ERROR;
+                    mLastError = TetheringManager.TETHER_ERROR_MASTER_ERROR;
                     transitionTo(mInitialState);
                     break;
                 default:
@@ -835,7 +816,7 @@
         @Override
         public void enter() {
             super.enter();
-            if (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+            if (mLastError != TetheringManager.TETHER_ERROR_NO_ERROR) {
                 transitionTo(mInitialState);
             }
 
@@ -871,7 +852,7 @@
         @Override
         public void enter() {
             super.enter();
-            if (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+            if (mLastError != TetheringManager.TETHER_ERROR_NO_ERROR) {
                 transitionTo(mInitialState);
             }
 
@@ -898,20 +879,14 @@
             // to remove their rules, which generates errors.
             // Just do the best we can.
             try {
-                // About to tear down NAT; gather remaining statistics.
-                mStatsService.forceUpdate();
-            } catch (Exception e) {
-                if (VDBG) Log.e(TAG, "Exception in forceUpdate: " + e.toString());
+                mNetd.ipfwdRemoveInterfaceForward(mIfaceName, upstreamIface);
+            } catch (RemoteException | ServiceSpecificException e) {
+                mLog.e("Exception in ipfwdRemoveInterfaceForward: " + e.toString());
             }
             try {
-                mNMService.stopInterfaceForwarding(mIfaceName, upstreamIface);
-            } catch (Exception e) {
-                if (VDBG) Log.e(TAG, "Exception in removeInterfaceForward: " + e.toString());
-            }
-            try {
-                mNMService.disableNat(mIfaceName, upstreamIface);
-            } catch (Exception e) {
-                if (VDBG) Log.e(TAG, "Exception in disableNat: " + e.toString());
+                mNetd.tetherRemoveForward(mIfaceName, upstreamIface);
+            } catch (RemoteException | ServiceSpecificException e) {
+                mLog.e("Exception in disableNat: " + e.toString());
             }
         }
 
@@ -947,12 +922,12 @@
 
                     for (String ifname : added) {
                         try {
-                            mNMService.enableNat(mIfaceName, ifname);
-                            mNMService.startInterfaceForwarding(mIfaceName, ifname);
-                        } catch (Exception e) {
-                            mLog.e("Exception enabling NAT: " + e);
+                            mNetd.tetherAddForward(mIfaceName, ifname);
+                            mNetd.ipfwdAddInterfaceForward(mIfaceName, ifname);
+                        } catch (RemoteException | ServiceSpecificException e) {
+                            mLog.e("Exception enabling NAT: " + e.toString());
                             cleanupUpstream();
-                            mLastError = ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
+                            mLastError = TetheringManager.TETHER_ERROR_ENABLE_NAT_ERROR;
                             transitionTo(mInitialState);
                             return true;
                         }
@@ -997,7 +972,7 @@
     class UnavailableState extends State {
         @Override
         public void enter() {
-            mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+            mLastError = TetheringManager.TETHER_ERROR_NO_ERROR;
             sendInterfaceState(STATE_UNAVAILABLE);
         }
     }
diff --git a/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java b/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
index bba61d7..6f017dc 100644
--- a/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
+++ b/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
@@ -668,7 +668,7 @@
     }
 
     private final class UnicastResponder extends Thread {
-        private final InetSocketAddress mSolicitor = new InetSocketAddress();
+        private final InetSocketAddress mSolicitor = new InetSocketAddress(0);
         // The recycled buffer for receiving Router Solicitations from clients.
         // If the RS is larger than IPV6_MIN_MTU the packets are truncated.
         // This is fine since currently only byte 0 is examined anyway.
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
index 7e685fb..e81d6ac 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
@@ -16,18 +16,16 @@
 
 package com.android.server.connectivity.tethering;
 
-import static android.net.ConnectivityManager.EXTRA_ADD_TETHER_TYPE;
-import static android.net.ConnectivityManager.EXTRA_PROVISION_CALLBACK;
-import static android.net.ConnectivityManager.EXTRA_RUN_PROVISION;
-import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
-import static android.net.ConnectivityManager.TETHERING_INVALID;
-import static android.net.ConnectivityManager.TETHERING_USB;
-import static android.net.ConnectivityManager.TETHERING_WIFI;
-import static android.net.ConnectivityManager.TETHER_ERROR_ENTITLEMENT_UNKONWN;
-import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
-import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED;
-
-import static com.android.internal.R.string.config_wifi_tether_enable;
+import static android.net.TetheringConstants.EXTRA_ADD_TETHER_TYPE;
+import static android.net.TetheringConstants.EXTRA_PROVISION_CALLBACK;
+import static android.net.TetheringConstants.EXTRA_RUN_PROVISION;
+import static android.net.TetheringManager.TETHERING_BLUETOOTH;
+import static android.net.TetheringManager.TETHERING_INVALID;
+import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_PROVISION_FAILED;
 
 import android.app.AlarmManager;
 import android.app.PendingIntent;
@@ -36,7 +34,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.res.Resources;
 import android.net.util.SharedLog;
 import android.os.Bundle;
 import android.os.Handler;
@@ -54,12 +51,13 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.StateMachine;
+import com.android.networkstack.tethering.R;
 
 import java.io.PrintWriter;
 
 /**
  * Re-check tethering provisioning for enabled downstream tether types.
- * Reference ConnectivityManager.TETHERING_{@code *} for each tether type.
+ * Reference TetheringManager.TETHERING_{@code *} for each tether type.
  *
  * All methods of this class must be accessed from the thread of tethering
  * state machine.
@@ -75,9 +73,7 @@
             "com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM";
     private static final String EXTRA_SUBID = "subId";
 
-    // {@link ComponentName} of the Service used to run tether provisioning.
-    private static final ComponentName TETHER_SERVICE = ComponentName.unflattenFromString(
-            Resources.getSystem().getString(config_wifi_tether_enable));
+    private final ComponentName mSilentProvisioningService;
     private static final int MS_PER_HOUR = 60 * 60 * 1000;
     private static final int EVENT_START_PROVISIONING = 0;
     private static final int EVENT_STOP_PROVISIONING = 1;
@@ -86,9 +82,9 @@
     private static final int EVENT_GET_ENTITLEMENT_VALUE = 4;
 
     // The ArraySet contains enabled downstream types, ex:
-    // {@link ConnectivityManager.TETHERING_WIFI}
-    // {@link ConnectivityManager.TETHERING_USB}
-    // {@link ConnectivityManager.TETHERING_BLUETOOTH}
+    // {@link TetheringManager.TETHERING_WIFI}
+    // {@link TetheringManager.TETHERING_USB}
+    // {@link TetheringManager.TETHERING_BLUETOOTH}
     private final ArraySet<Integer> mCurrentTethers;
     private final Context mContext;
     private final int mPermissionChangeMessageCode;
@@ -96,8 +92,8 @@
     private final SparseIntArray mEntitlementCacheValue;
     private final EntitlementHandler mHandler;
     private final StateMachine mTetherMasterSM;
-    // Key: ConnectivityManager.TETHERING_*(downstream).
-    // Value: ConnectivityManager.TETHER_ERROR_{NO_ERROR or PROVISION_FAILED}(provisioning result).
+    // Key: TetheringManager.TETHERING_*(downstream).
+    // Value: TetheringManager.TETHER_ERROR_{NO_ERROR or PROVISION_FAILED}(provisioning result).
     private final SparseIntArray mCellularPermitted;
     private PendingIntent mProvisioningRecheckAlarm;
     private boolean mCellularUpstreamPermitted = true;
@@ -122,6 +118,8 @@
         mHandler = new EntitlementHandler(masterHandler.getLooper());
         mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_PROVISIONING_ALARM),
                 null, mHandler);
+        mSilentProvisioningService = ComponentName.unflattenFromString(
+                mContext.getResources().getString(R.string.config_wifi_tether_enable));
     }
 
     public void setOnUiEntitlementFailedListener(final OnUiEntitlementFailedListener listener) {
@@ -133,7 +131,7 @@
         /**
          * Ui entitlement check fails in |downstream|.
          *
-         * @param downstream tethering type from ConnectivityManager.TETHERING_{@code *}.
+         * @param downstream tethering type from TetheringManager.TETHERING_{@code *}.
          */
         void onUiEntitlementFailed(int downstream);
     }
@@ -169,7 +167,7 @@
      * This is called when tethering starts.
      * Launch provisioning app if upstream is cellular.
      *
-     * @param downstreamType tethering type from ConnectivityManager.TETHERING_{@code *}
+     * @param downstreamType tethering type from TetheringManager.TETHERING_{@code *}
      * @param showProvisioningUi a boolean indicating whether to show the
      *        provisioning app UI if there is one.
      */
@@ -210,7 +208,7 @@
     /**
      * Tell EntitlementManager that a given type of tethering has been disabled
      *
-     * @param type tethering type from ConnectivityManager.TETHERING_{@code *}
+     * @param type tethering type from TetheringManager.TETHERING_{@code *}
      */
     public void stopProvisioningIfNeeded(int type) {
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_STOP_PROVISIONING, type, 0));
@@ -296,7 +294,7 @@
 
     /**
      * Re-check tethering provisioning for all enabled tether types.
-     * Reference ConnectivityManager.TETHERING_{@code *} for each tether type.
+     * Reference TetheringManager.TETHERING_{@code *} for each tether type.
      *
      * @param config an object that encapsulates the various tethering configuration elements.
      * Note: this method is only called from TetherMaster on the handler thread.
@@ -363,7 +361,7 @@
 
     /**
      * Run no UI tethering provisioning check.
-     * @param type tethering type from ConnectivityManager.TETHERING_{@code *}
+     * @param type tethering type from TetheringManager.TETHERING_{@code *}
      * @param subId default data subscription ID.
      */
     @VisibleForTesting
@@ -377,7 +375,7 @@
         intent.putExtra(EXTRA_RUN_PROVISION, true);
         intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver);
         intent.putExtra(EXTRA_SUBID, subId);
-        intent.setComponent(TETHER_SERVICE);
+        intent.setComponent(mSilentProvisioningService);
         // Only admin user can change tethering and SilentTetherProvisioning don't need to
         // show UI, it is fine to always start setting's background service as system user.
         mContext.startService(intent);
@@ -390,7 +388,7 @@
 
     /**
      * Run the UI-enabled tethering provisioning check.
-     * @param type tethering type from ConnectivityManager.TETHERING_{@code *}
+     * @param type tethering type from TetheringManager.TETHERING_{@code *}
      * @param subId default data subscription ID.
      * @param receiver to receive entitlement check result.
      */
@@ -398,7 +396,7 @@
     protected void runUiTetherProvisioning(int type, int subId, ResultReceiver receiver) {
         if (DBG) mLog.i("runUiTetherProvisioning: " + type);
 
-        Intent intent = new Intent(Settings.ACTION_TETHER_PROVISIONING);
+        Intent intent = new Intent(Settings.ACTION_TETHER_PROVISIONING_UI);
         intent.putExtra(EXTRA_ADD_TETHER_TYPE, type);
         intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver);
         intent.putExtra(EXTRA_SUBID, subId);
@@ -461,7 +459,7 @@
      * Add the mapping between provisioning result and tethering type.
      * Notify UpstreamNetworkMonitor if Cellular permission changes.
      *
-     * @param type tethering type from ConnectivityManager.TETHERING_{@code *}
+     * @param type tethering type from TetheringManager.TETHERING_{@code *}
      * @param resultCode Provisioning result
      */
     protected void addDownstreamMapping(int type, int resultCode) {
@@ -476,7 +474,7 @@
 
     /**
      * Remove the mapping for input tethering type.
-     * @param type tethering type from ConnectivityManager.TETHERING_{@code *}
+     * @param type tethering type from TetheringManager.TETHERING_{@code *}
      */
     protected void removeDownstreamMapping(int type) {
         mLog.i("removeDownstreamMapping: " + type);
@@ -579,7 +577,7 @@
 
     private static String errorString(int value) {
         switch (value) {
-            case TETHER_ERROR_ENTITLEMENT_UNKONWN: return "TETHER_ERROR_ENTITLEMENT_UNKONWN";
+            case TETHER_ERROR_ENTITLEMENT_UNKNOWN: return "TETHER_ERROR_ENTITLEMENT_UNKONWN";
             case TETHER_ERROR_NO_ERROR: return "TETHER_ERROR_NO_ERROR";
             case TETHER_ERROR_PROVISION_FAILED: return "TETHER_ERROR_PROVISION_FAILED";
             default:
@@ -625,7 +623,7 @@
     /**
      * Update the last entitlement value to internal cache
      *
-     * @param type tethering type from ConnectivityManager.TETHERING_{@code *}
+     * @param type tethering type from TetheringManager.TETHERING_{@code *}
      * @param resultCode last entitlement value
      * @return the last updated entitlement value
      */
@@ -659,7 +657,7 @@
         }
 
         final int cacheValue = mEntitlementCacheValue.get(
-                downstream, TETHER_ERROR_ENTITLEMENT_UNKONWN);
+                downstream, TETHER_ERROR_ENTITLEMENT_UNKNOWN);
         if (cacheValue == TETHER_ERROR_NO_ERROR || !showEntitlementUi) {
             receiver.send(cacheValue, null);
         } else {
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java
index 38fa91e..cc36f4a 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java
@@ -16,35 +16,40 @@
 
 package com.android.server.connectivity.tethering;
 
+import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.ROAMING_NO;
 import static android.net.NetworkStats.SET_DEFAULT;
-import static android.net.NetworkStats.STATS_PER_UID;
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
-import static android.net.TrafficStats.UID_TETHERING;
+import static android.net.NetworkStats.UID_TETHERING;
 import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.usage.NetworkStatsManager;
 import android.content.ContentResolver;
-import android.net.ITetheringStatsProvider;
 import android.net.InetAddresses;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.NetworkStats;
+import android.net.NetworkStats.Entry;
 import android.net.RouteInfo;
 import android.net.netlink.ConntrackMessage;
 import android.net.netlink.NetlinkConstants;
 import android.net.netlink.NetlinkSocket;
+import android.net.netstats.provider.AbstractNetworkStatsProvider;
+import android.net.netstats.provider.NetworkStatsProviderCallback;
 import android.net.util.SharedLog;
 import android.os.Handler;
-import android.os.INetworkManagementService;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.SystemClock;
 import android.provider.Settings;
 import android.system.ErrnoException;
 import android.system.OsConstants;
 import android.text.TextUtils;
+import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats;
 
@@ -73,13 +78,19 @@
     private static final String ANYIP = "0.0.0.0";
     private static final ForwardedStats EMPTY_STATS = new ForwardedStats();
 
+    @VisibleForTesting
+    enum StatsType {
+        STATS_PER_IFACE,
+        STATS_PER_UID,
+    }
+
     private enum UpdateType { IF_NEEDED, FORCE };
 
     private final Handler mHandler;
     private final OffloadHardwareInterface mHwInterface;
     private final ContentResolver mContentResolver;
-    private final INetworkManagementService mNms;
-    private final ITetheringStatsProvider mStatsProvider;
+    private final @NonNull OffloadTetheringStatsProvider mStatsProvider;
+    private final @Nullable NetworkStatsProviderCallback mStatsProviderCb;
     private final SharedLog mLog;
     private final HashMap<String, LinkProperties> mDownstreams;
     private boolean mConfigInitialized;
@@ -109,22 +120,23 @@
     private int mNatUpdateNetlinkErrors;
 
     public OffloadController(Handler h, OffloadHardwareInterface hwi,
-            ContentResolver contentResolver, INetworkManagementService nms, SharedLog log) {
+            ContentResolver contentResolver, NetworkStatsManager nsm, SharedLog log) {
         mHandler = h;
         mHwInterface = hwi;
         mContentResolver = contentResolver;
-        mNms = nms;
         mStatsProvider = new OffloadTetheringStatsProvider();
         mLog = log.forSubComponent(TAG);
         mDownstreams = new HashMap<>();
         mExemptPrefixes = new HashSet<>();
         mLastLocalPrefixStrs = new HashSet<>();
-
+        NetworkStatsProviderCallback providerCallback = null;
         try {
-            mNms.registerTetheringStatsProvider(mStatsProvider, getClass().getSimpleName());
-        } catch (RemoteException e) {
-            mLog.e("Cannot register offload stats provider: " + e);
+            providerCallback = nsm.registerNetworkStatsProvider(
+                    getClass().getSimpleName(), mStatsProvider);
+        } catch (RuntimeException e) {
+            Log.wtf(TAG, "Cannot register offload stats provider: " + e);
         }
+        mStatsProviderCb = providerCallback;
     }
 
     /** Start hardware offload. */
@@ -173,7 +185,7 @@
                         // and we need to synchronize stats and limits between
                         // software and hardware forwarding.
                         updateStatsForAllUpstreams();
-                        forceTetherStatsPoll();
+                        mStatsProvider.pushTetherStats();
                     }
 
                     @Override
@@ -186,7 +198,7 @@
                         // limits set take into account any software tethering
                         // traffic that has been happening in the meantime.
                         updateStatsForAllUpstreams();
-                        forceTetherStatsPoll();
+                        mStatsProvider.pushTetherStats();
                         // [2] (Re)Push all state.
                         computeAndPushLocalPrefixes(UpdateType.FORCE);
                         pushAllDownstreamState();
@@ -204,14 +216,11 @@
                         // the HAL queued the callback.
                         // TODO: rev the HAL so that it provides an interface name.
 
-                        // Fetch current stats, so that when our notification reaches
-                        // NetworkStatsService and triggers a poll, we will respond with
-                        // current data (which will be above the limit that was reached).
-                        // Note that if we just changed upstream, this is unnecessary but harmless.
-                        // The stats for the previous upstream were already updated on this thread
-                        // just after the upstream was changed, so they are also up-to-date.
                         updateStatsForCurrentUpstream();
-                        forceTetherStatsPoll();
+                        mStatsProvider.pushTetherStats();
+                        // Push stats to service does not cause the service react to it immediately.
+                        // Inform the service about limit reached.
+                        if (mStatsProviderCb != null) mStatsProviderCb.onLimitReached();
                     }
 
                     @Override
@@ -253,42 +262,37 @@
         return mConfigInitialized && mControlInitialized;
     }
 
-    private class OffloadTetheringStatsProvider extends ITetheringStatsProvider.Stub {
-        @Override
-        public NetworkStats getTetherStats(int how) {
-            // getTetherStats() is the only function in OffloadController that can be called from
-            // a different thread. Do not attempt to update stats by querying the offload HAL
-            // synchronously from a different thread than our Handler thread. http://b/64771555.
-            Runnable updateStats = () -> {
-                updateStatsForCurrentUpstream();
-            };
-            if (Looper.myLooper() == mHandler.getLooper()) {
-                updateStats.run();
-            } else {
-                mHandler.post(updateStats);
-            }
+    @VisibleForTesting
+    class OffloadTetheringStatsProvider extends AbstractNetworkStatsProvider {
+        // These stats must only ever be touched on the handler thread.
+        @NonNull
+        private NetworkStats mIfaceStats = new NetworkStats(0L, 0);
+        @NonNull
+        private NetworkStats mUidStats = new NetworkStats(0L, 0);
 
-            NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
-            NetworkStats.Entry entry = new NetworkStats.Entry();
-            entry.set = SET_DEFAULT;
-            entry.tag = TAG_NONE;
-            entry.uid = (how == STATS_PER_UID) ? UID_TETHERING : UID_ALL;
+        @VisibleForTesting
+        @NonNull
+        NetworkStats getTetherStats(@NonNull StatsType how) {
+            NetworkStats stats = new NetworkStats(0L, 0);
+            final int uid = (how == StatsType.STATS_PER_UID) ? UID_TETHERING : UID_ALL;
 
-            for (Map.Entry<String, ForwardedStats> kv : mForwardedStats.entrySet()) {
-                ForwardedStats value = kv.getValue();
-                entry.iface = kv.getKey();
-                entry.rxBytes = value.rxBytes;
-                entry.txBytes = value.txBytes;
-                stats.addValues(entry);
+            for (final Map.Entry<String, ForwardedStats> kv : mForwardedStats.entrySet()) {
+                final ForwardedStats value = kv.getValue();
+                final Entry entry = new Entry(kv.getKey(), uid, SET_DEFAULT, TAG_NONE, METERED_NO,
+                        ROAMING_NO, DEFAULT_NETWORK_NO, value.rxBytes, 0L, value.txBytes, 0L, 0L);
+                stats = stats.addValues(entry);
             }
 
             return stats;
         }
 
         @Override
-        public void setInterfaceQuota(String iface, long quotaBytes) {
+        public void setLimit(String iface, long quotaBytes) {
+            mLog.i("setLimit: " + iface + "," + quotaBytes);
+            // Listen for all iface is necessary since upstream might be changed after limit
+            // is set.
             mHandler.post(() -> {
-                if (quotaBytes == ITetheringStatsProvider.QUOTA_UNLIMITED) {
+                if (quotaBytes == QUOTA_UNLIMITED) {
                     mInterfaceQuotas.remove(iface);
                 } else {
                     mInterfaceQuotas.put(iface, quotaBytes);
@@ -296,6 +300,42 @@
                 maybeUpdateDataLimit(iface);
             });
         }
+
+        /**
+         * Push stats to service, but does not cause a force polling. Note that this can only be
+         * called on the handler thread.
+         */
+        public void pushTetherStats() {
+            // TODO: remove the accumulated stats and report the diff from HAL directly.
+            if (null == mStatsProviderCb) return;
+            final NetworkStats ifaceDiff =
+                    getTetherStats(StatsType.STATS_PER_IFACE).subtract(mIfaceStats);
+            final NetworkStats uidDiff =
+                    getTetherStats(StatsType.STATS_PER_UID).subtract(mUidStats);
+            try {
+                mStatsProviderCb.onStatsUpdated(0 /* token */, ifaceDiff, uidDiff);
+                mIfaceStats = mIfaceStats.add(ifaceDiff);
+                mUidStats = mUidStats.add(uidDiff);
+            } catch (RuntimeException e) {
+                mLog.e("Cannot report network stats: ", e);
+            }
+        }
+
+        @Override
+        public void requestStatsUpdate(int token) {
+            mLog.i("requestStatsUpdate: " + token);
+            // Do not attempt to update stats by querying the offload HAL
+            // synchronously from a different thread than the Handler thread. http://b/64771555.
+            mHandler.post(() -> {
+                updateStatsForCurrentUpstream();
+                pushTetherStats();
+            });
+        }
+
+        @Override
+        public void setAlert(long quotaBytes) {
+            // TODO: Ask offload HAL to notify alert without stopping traffic.
+        }
     }
 
     private String currentUpstreamInterface() {
@@ -353,14 +393,6 @@
         }
     }
 
-    private void forceTetherStatsPoll() {
-        try {
-            mNms.tetherLimitReached(mStatsProvider);
-        } catch (RemoteException e) {
-            mLog.e("Cannot report data limit reached: " + e);
-        }
-    }
-
     /** Set current tethering upstream LinkProperties. */
     public void setUpstreamLinkProperties(LinkProperties lp) {
         if (!started() || Objects.equals(mUpstreamLinkProperties, lp)) return;
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
index 4a8ef1f..90b9d3f 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
@@ -29,6 +29,8 @@
 import android.os.RemoteException;
 import android.system.OsConstants;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.ArrayList;
 
 
@@ -91,6 +93,12 @@
             txBytes = 0;
         }
 
+        @VisibleForTesting
+        public ForwardedStats(long rxBytes, long txBytes) {
+            this.rxBytes = rxBytes;
+            this.txBytes = txBytes;
+        }
+
         /** Add Tx/Rx bytes. */
         public void add(ForwardedStats other) {
             rxBytes += other.rxBytes;
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
index 5b26704..5370145 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
@@ -20,23 +20,24 @@
 import static android.hardware.usb.UsbManager.USB_CONFIGURED;
 import static android.hardware.usb.UsbManager.USB_CONNECTED;
 import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS;
-import static android.net.ConnectivityManager.ACTION_TETHER_STATE_CHANGED;
+import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
-import static android.net.ConnectivityManager.EXTRA_ACTIVE_LOCAL_ONLY;
-import static android.net.ConnectivityManager.EXTRA_ACTIVE_TETHER;
-import static android.net.ConnectivityManager.EXTRA_AVAILABLE_TETHER;
-import static android.net.ConnectivityManager.EXTRA_ERRORED_TETHER;
 import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO;
-import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
-import static android.net.ConnectivityManager.TETHERING_INVALID;
-import static android.net.ConnectivityManager.TETHERING_USB;
-import static android.net.ConnectivityManager.TETHERING_WIFI;
-import static android.net.ConnectivityManager.TETHERING_WIFI_P2P;
-import static android.net.ConnectivityManager.TETHER_ERROR_MASTER_ERROR;
-import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
-import static android.net.ConnectivityManager.TETHER_ERROR_SERVICE_UNAVAIL;
-import static android.net.ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE;
-import static android.net.ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
+import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED;
+import static android.net.TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY;
+import static android.net.TetheringManager.EXTRA_ACTIVE_TETHER;
+import static android.net.TetheringManager.EXTRA_AVAILABLE_TETHER;
+import static android.net.TetheringManager.EXTRA_ERRORED_TETHER;
+import static android.net.TetheringManager.TETHERING_BLUETOOTH;
+import static android.net.TetheringManager.TETHERING_INVALID;
+import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+import static android.net.TetheringManager.TETHER_ERROR_MASTER_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL;
+import static android.net.TetheringManager.TETHER_ERROR_UNAVAIL_IFACE;
+import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE;
 import static android.net.util.TetheringMessageBase.BASE_MASTER;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE;
@@ -50,8 +51,10 @@
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
 import android.app.Notification;
+import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.app.usage.NetworkStatsManager;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothPan;
 import android.bluetooth.BluetoothProfile;
@@ -62,19 +65,19 @@
 import android.content.IntentFilter;
 import android.content.res.Resources;
 import android.hardware.usb.UsbManager;
+import android.net.ConnectivityManager;
 import android.net.INetd;
-import android.net.INetworkPolicyManager;
-import android.net.INetworkStatsService;
 import android.net.ITetheringEventCallback;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkInfo;
-import android.net.NetworkUtils;
 import android.net.TetherStatesParcel;
+import android.net.TetheringCallbackStartedParcel;
 import android.net.TetheringConfigurationParcel;
 import android.net.ip.IpServer;
+import android.net.shared.NetdUtils;
 import android.net.util.BaseNetdUnsolicitedEventListener;
 import android.net.util.InterfaceSet;
 import android.net.util.PrefixUtils;
@@ -87,12 +90,12 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.INetworkManagementService;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.os.ServiceSpecificException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.telephony.PhoneStateListener;
@@ -102,9 +105,10 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.MessageUtils;
 import com.android.internal.util.State;
@@ -139,6 +143,8 @@
     };
     private static final SparseArray<String> sMagicDecoderRing =
             MessageUtils.findMessageNames(sMessageClasses);
+    // Keep in sync with NETID_UNSET in system/netd/include/netid_client.h
+    private static final int NETID_UNSET = 0;
 
     private static class TetherState {
         public final IpServer ipServer;
@@ -172,10 +178,6 @@
     private final Context mContext;
     private final ArrayMap<String, TetherState> mTetherStates;
     private final BroadcastReceiver mStateReceiver;
-    // Stopship: replace mNMService before production.
-    private final INetworkManagementService mNMService;
-    private final INetworkStatsService mStatsService;
-    private final INetworkPolicyManager mPolicyManager;
     private final Looper mLooper;
     private final StateMachine mTetherMasterSM;
     private final OffloadController mOffloadController;
@@ -205,14 +207,13 @@
     private boolean mWifiTetherRequested;
     private Network mTetherUpstream;
     private TetherStatesParcel mTetherStatesParcel;
+    private boolean mDataSaverEnabled = false;
+    private String mWifiP2pTetherInterface = null;
 
     public Tethering(TetheringDependencies deps) {
         mLog.mark("Tethering.constructed");
         mDeps = deps;
         mContext = mDeps.getContext();
-        mNMService = mDeps.getINetworkManagementService();
-        mStatsService = mDeps.getINetworkStatsService();
-        mPolicyManager = mDeps.getINetworkPolicyManager();
         mNetd = mDeps.getINetd(mContext);
         mLooper = mDeps.getTetheringLooper();
 
@@ -223,12 +224,13 @@
         mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper, deps);
         mTetherMasterSM.start();
 
+        final NetworkStatsManager statsManager =
+                (NetworkStatsManager) mContext.getSystemService(Context.NETWORK_STATS_SERVICE);
         mHandler = mTetherMasterSM.getHandler();
         mOffloadController = new OffloadController(mHandler,
-                mDeps.getOffloadHardwareInterface(mHandler, mLog),
-                mContext.getContentResolver(), mNMService,
-                mLog);
-        mUpstreamNetworkMonitor = deps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog,
+                mDeps.getOffloadHardwareInterface(mHandler, mLog), mContext.getContentResolver(),
+                statsManager, mLog);
+        mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog,
                 TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
         mForwardedDownstreams = new HashSet<>();
 
@@ -264,7 +266,7 @@
         }
 
         final UserManager userManager = (UserManager) mContext.getSystemService(
-                    Context.USER_SERVICE);
+                Context.USER_SERVICE);
         mTetheringRestriction = new UserRestrictionActionListener(userManager, this);
         final TetheringThreadExecutor executor = new TetheringThreadExecutor(mHandler);
         mActiveDataSubIdListener = new ActiveDataSubIdListener(executor);
@@ -288,14 +290,8 @@
         filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
         filter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
         filter.addAction(UserManager.ACTION_USER_RESTRICTIONS_CHANGED);
+        filter.addAction(ACTION_RESTRICT_BACKGROUND_CHANGED);
         mContext.registerReceiver(mStateReceiver, filter, null, handler);
-
-        filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_MEDIA_SHARED);
-        filter.addAction(Intent.ACTION_MEDIA_UNSHARED);
-        filter.addDataScheme("file");
-        mContext.registerReceiver(mStateReceiver, filter, null, handler);
-
     }
 
     private class TetheringThreadExecutor implements Executor {
@@ -421,7 +417,6 @@
         }
     }
 
-
     void interfaceRemoved(String iface) {
         if (VDBG) Log.d(TAG, "interfaceRemoved " + iface);
         synchronized (mPublicSync) {
@@ -492,7 +487,7 @@
     }
 
     private void setBluetoothTethering(final boolean enable, final ResultReceiver receiver) {
-        final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        final BluetoothAdapter adapter = mDeps.getBluetoothAdapter();
         if (adapter == null || !adapter.isEnabled()) {
             Log.w(TAG, "Tried to enable bluetooth tethering with null or disabled adapter. null: "
                     + (adapter == null));
@@ -670,19 +665,19 @@
 
         if (usbTethered) {
             if (wifiTethered || bluetoothTethered) {
-                showTetheredNotification(SystemMessage.NOTE_TETHER_GENERAL);
+                showTetheredNotification(R.drawable.stat_sys_tether_general);
             } else {
-                showTetheredNotification(SystemMessage.NOTE_TETHER_USB);
+                showTetheredNotification(R.drawable.stat_sys_tether_usb);
             }
         } else if (wifiTethered) {
             if (bluetoothTethered) {
-                showTetheredNotification(SystemMessage.NOTE_TETHER_GENERAL);
+                showTetheredNotification(R.drawable.stat_sys_tether_general);
             } else {
                 /* We now have a status bar icon for WifiTethering, so drop the notification */
                 clearTetheredNotification();
             }
         } else if (bluetoothTethered) {
-            showTetheredNotification(SystemMessage.NOTE_TETHER_BLUETOOTH);
+            showTetheredNotification(R.drawable.stat_sys_tether_bluetooth);
         } else {
             clearTetheredNotification();
         }
@@ -695,30 +690,22 @@
     @VisibleForTesting
     protected void showTetheredNotification(int id, boolean tetheringOn) {
         NotificationManager notificationManager =
-                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+                (NotificationManager) mContext.createContextAsUser(UserHandle.ALL, 0)
+                        .getSystemService(Context.NOTIFICATION_SERVICE);
         if (notificationManager == null) {
             return;
         }
-        int icon = 0;
-        switch(id) {
-            case SystemMessage.NOTE_TETHER_USB:
-                icon = R.drawable.stat_sys_tether_usb;
-                break;
-            case SystemMessage.NOTE_TETHER_BLUETOOTH:
-                icon = R.drawable.stat_sys_tether_bluetooth;
-                break;
-            case SystemMessage.NOTE_TETHER_GENERAL:
-            default:
-                icon = R.drawable.stat_sys_tether_general;
-                break;
-        }
+        final NotificationChannel channel = new NotificationChannel(
+                "TETHERING_STATUS",
+                mContext.getResources().getString(R.string.notification_channel_tethering_status),
+                NotificationManager.IMPORTANCE_LOW);
+        notificationManager.createNotificationChannel(channel);
 
         if (mLastNotificationId != 0) {
-            if (mLastNotificationId == icon) {
+            if (mLastNotificationId == id) {
                 return;
             }
-            notificationManager.cancelAsUser(null, mLastNotificationId,
-                    UserHandle.ALL);
+            notificationManager.cancel(null, mLastNotificationId);
             mLastNotificationId = 0;
         }
 
@@ -726,8 +713,8 @@
         intent.setClassName("com.android.settings", "com.android.settings.TetherSettings");
         intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
 
-        PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0, intent, 0,
-                null, UserHandle.CURRENT);
+        PendingIntent pi = PendingIntent.getActivity(
+                mContext.createContextAsUser(UserHandle.CURRENT, 0), 0, intent, 0, null);
 
         Resources r = mContext.getResources();
         final CharSequence title;
@@ -742,32 +729,30 @@
         }
 
         if (mTetheredNotificationBuilder == null) {
-            mTetheredNotificationBuilder = new Notification.Builder(mContext,
-                    SystemNotificationChannels.NETWORK_STATUS);
+            mTetheredNotificationBuilder = new Notification.Builder(mContext, channel.getId());
             mTetheredNotificationBuilder.setWhen(0)
                     .setOngoing(true)
                     .setColor(mContext.getColor(
-                            com.android.internal.R.color.system_notification_accent_color))
+                            android.R.color.system_notification_accent_color))
                     .setVisibility(Notification.VISIBILITY_PUBLIC)
                     .setCategory(Notification.CATEGORY_STATUS);
         }
-        mTetheredNotificationBuilder.setSmallIcon(icon)
+        mTetheredNotificationBuilder.setSmallIcon(id)
                 .setContentTitle(title)
                 .setContentText(message)
                 .setContentIntent(pi);
         mLastNotificationId = id;
 
-        notificationManager.notifyAsUser(null, mLastNotificationId,
-                mTetheredNotificationBuilder.buildInto(new Notification()), UserHandle.ALL);
+        notificationManager.notify(null, mLastNotificationId, mTetheredNotificationBuilder.build());
     }
 
     @VisibleForTesting
     protected void clearTetheredNotification() {
         NotificationManager notificationManager =
-                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+                (NotificationManager) mContext.createContextAsUser(UserHandle.ALL, 0)
+                        .getSystemService(Context.NOTIFICATION_SERVICE);
         if (notificationManager != null && mLastNotificationId != 0) {
-            notificationManager.cancelAsUser(null, mLastNotificationId,
-                    UserHandle.ALL);
+            notificationManager.cancel(null, mLastNotificationId);
             mLastNotificationId = 0;
         }
     }
@@ -792,6 +777,9 @@
             } else if (action.equals(UserManager.ACTION_USER_RESTRICTIONS_CHANGED)) {
                 mLog.log("OBSERVED user restrictions changed");
                 handleUserRestrictionAction();
+            } else if (action.equals(ACTION_RESTRICT_BACKGROUND_CHANGED)) {
+                mLog.log("OBSERVED data saver changed");
+                handleDataSaverChanged();
             }
         }
 
@@ -866,6 +854,11 @@
             }
         }
 
+        private boolean isGroupOwner(WifiP2pGroup group) {
+            return group != null && group.isGroupOwner()
+                    && !TextUtils.isEmpty(group.getInterface());
+        }
+
         private void handleWifiP2pAction(Intent intent) {
             if (mConfig.isWifiP2pLegacyTetheringMode()) return;
 
@@ -878,30 +871,51 @@
                 Log.d(TAG, "WifiP2pAction: P2pInfo: " + p2pInfo + " Group: " + group);
             }
 
-            if (p2pInfo == null) return;
-            // When a p2p group is disconnected, p2pInfo would be cleared.
-            // group is still valid for detecting whether this device is group owner.
-            if (group == null || !group.isGroupOwner()
-                    || TextUtils.isEmpty(group.getInterface())) return;
-
             synchronized (Tethering.this.mPublicSync) {
-                // Enter below only if this device is Group Owner with a valid interface.
-                if (p2pInfo.groupFormed) {
-                    TetherState tetherState = mTetherStates.get(group.getInterface());
-                    if (tetherState == null
-                            || (tetherState.lastState != IpServer.STATE_TETHERED
-                                && tetherState.lastState != IpServer.STATE_LOCAL_ONLY)) {
-                        enableWifiIpServingLocked(group.getInterface(), IFACE_IP_MODE_LOCAL_ONLY);
-                    }
-                } else {
-                    disableWifiP2pIpServingLocked(group.getInterface());
+                // if no group is formed, bring it down if needed.
+                if (p2pInfo == null || !p2pInfo.groupFormed) {
+                    disableWifiP2pIpServingLockedIfNeeded(mWifiP2pTetherInterface);
+                    mWifiP2pTetherInterface = null;
+                    return;
                 }
+
+                // If there is a group but the device is not the owner, bail out.
+                if (!isGroupOwner(group)) return;
+
+                // If already serving from the correct interface, nothing to do.
+                if (group.getInterface().equals(mWifiP2pTetherInterface)) return;
+
+                // If already serving from another interface, turn it down first.
+                if (!TextUtils.isEmpty(mWifiP2pTetherInterface)) {
+                    mLog.w("P2P tethered interface " + mWifiP2pTetherInterface
+                            + "is different from current interface "
+                            + group.getInterface() + ", re-tether it");
+                    disableWifiP2pIpServingLockedIfNeeded(mWifiP2pTetherInterface);
+                }
+
+                // Finally bring up serving on the new interface
+                mWifiP2pTetherInterface = group.getInterface();
+                enableWifiIpServingLocked(mWifiP2pTetherInterface, IFACE_IP_MODE_LOCAL_ONLY);
             }
         }
 
         private void handleUserRestrictionAction() {
             mTetheringRestriction.onUserRestrictionsChanged();
         }
+
+        private void handleDataSaverChanged() {
+            final ConnectivityManager connMgr = (ConnectivityManager) mContext.getSystemService(
+                    Context.CONNECTIVITY_SERVICE);
+            final boolean isDataSaverEnabled = connMgr.getRestrictBackgroundStatus()
+                    != ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
+
+            if (mDataSaverEnabled == isDataSaverEnabled) return;
+
+            mDataSaverEnabled = isDataSaverEnabled;
+            if (mDataSaverEnabled) {
+                untetherAll();
+            }
+        }
     }
 
     @VisibleForTesting
@@ -938,6 +952,7 @@
                 mWrapper.showTetheredNotification(
                         R.drawable.stat_sys_tether_general, false);
                 mWrapper.untetherAll();
+                // TODO(b/148139325): send tetheringSupported on restriction change
             }
         }
     }
@@ -979,7 +994,9 @@
         disableWifiIpServingLockedCommon(TETHERING_WIFI, ifname, apState);
     }
 
-    private void disableWifiP2pIpServingLocked(String ifname) {
+    private void disableWifiP2pIpServingLockedIfNeeded(String ifname) {
+        if (TextUtils.isEmpty(ifname)) return;
+
         disableWifiIpServingLockedCommon(TETHERING_WIFI_P2P, ifname, /* dummy */ 0);
     }
 
@@ -1022,8 +1039,8 @@
 
         String[] ifaces = null;
         try {
-            ifaces = mNMService.listInterfaces();
-        } catch (Exception e) {
+            ifaces = mNetd.interfaceGetList();
+        } catch (RemoteException | ServiceSpecificException e) {
             Log.e(TAG, "Error listing Interfaces", e);
             return;
         }
@@ -1282,25 +1299,25 @@
         protected boolean turnOnMasterTetherSettings() {
             final TetheringConfiguration cfg = mConfig;
             try {
-                mNMService.setIpForwardingEnabled(true);
-            } catch (Exception e) {
+                mNetd.ipfwdEnableForwarding(TAG);
+            } catch (RemoteException | ServiceSpecificException e) {
                 mLog.e(e);
                 transitionTo(mSetIpForwardingEnabledErrorState);
                 return false;
             }
+
             // TODO: Randomize DHCPv4 ranges, especially in hotspot mode.
             // Legacy DHCP server is disabled if passed an empty ranges array
             final String[] dhcpRanges = cfg.enableLegacyDhcpServer
-                    ? cfg.legacyDhcpRanges
-                    : new String[0];
+                    ? cfg.legacyDhcpRanges : new String[0];
             try {
-                // TODO: Find a more accurate method name (startDHCPv4()?).
-                mNMService.startTethering(dhcpRanges);
-            } catch (Exception e) {
+                NetdUtils.tetherStart(mNetd, true /** usingLegacyDnsProxy */, dhcpRanges);
+            } catch (RemoteException | ServiceSpecificException e) {
                 try {
-                    mNMService.stopTethering();
-                    mNMService.startTethering(dhcpRanges);
-                } catch (Exception ee) {
+                    // Stop and retry.
+                    mNetd.tetherStop();
+                    NetdUtils.tetherStart(mNetd, true /** usingLegacyDnsProxy */, dhcpRanges);
+                } catch (RemoteException | ServiceSpecificException ee) {
                     mLog.e(ee);
                     transitionTo(mStartTetheringErrorState);
                     return false;
@@ -1312,15 +1329,15 @@
 
         protected boolean turnOffMasterTetherSettings() {
             try {
-                mNMService.stopTethering();
-            } catch (Exception e) {
+                mNetd.tetherStop();
+            } catch (RemoteException | ServiceSpecificException e) {
                 mLog.e(e);
                 transitionTo(mStopTetheringErrorState);
                 return false;
             }
             try {
-                mNMService.setIpForwardingEnabled(false);
-            } catch (Exception e) {
+                mNetd.ipfwdDisableForwarding(TAG);
+            } catch (RemoteException | ServiceSpecificException e) {
                 mLog.e(e);
                 transitionTo(mSetIpForwardingDisabledErrorState);
                 return false;
@@ -1383,19 +1400,25 @@
 
         protected void setDnsForwarders(final Network network, final LinkProperties lp) {
             // TODO: Set v4 and/or v6 DNS per available connectivity.
-            String[] dnsServers = mConfig.defaultIPv4DNS;
             final Collection<InetAddress> dnses = lp.getDnsServers();
             // TODO: Properly support the absence of DNS servers.
+            final String[] dnsServers;
             if (dnses != null && !dnses.isEmpty()) {
-                // TODO: remove this invocation of NetworkUtils.makeStrings().
-                dnsServers = NetworkUtils.makeStrings(dnses);
+                dnsServers = new String[dnses.size()];
+                int i = 0;
+                for (InetAddress dns : dnses) {
+                    dnsServers[i++] = dns.getHostAddress();
+                }
+            } else {
+                dnsServers = mConfig.defaultIPv4DNS;
             }
+            final int netId = (network != null) ? network.netId : NETID_UNSET;
             try {
-                mNMService.setDnsForwarders(network, dnsServers);
+                mNetd.tetherDnsSet(netId, dnsServers);
                 mLog.log(String.format(
                         "SET DNS forwarders: network=%s dnsServers=%s",
                         network, Arrays.toString(dnsServers)));
-            } catch (Exception e) {
+            } catch (RemoteException | ServiceSpecificException e) {
                 // TODO: Investigate how this can fail and what exactly
                 // happens if/when such failures occur.
                 mLog.e("setting DNS forwarders failed, " + e);
@@ -1698,8 +1721,8 @@
                 Log.e(TAG, "Error in startTethering");
                 notify(IpServer.CMD_START_TETHERING_ERROR);
                 try {
-                    mNMService.setIpForwardingEnabled(false);
-                } catch (Exception e) { }
+                    mNetd.ipfwdDisableForwarding(TAG);
+                } catch (RemoteException | ServiceSpecificException e) { }
             }
         }
 
@@ -1709,8 +1732,8 @@
                 Log.e(TAG, "Error in stopTethering");
                 notify(IpServer.CMD_STOP_TETHERING_ERROR);
                 try {
-                    mNMService.setIpForwardingEnabled(false);
-                } catch (Exception e) { }
+                    mNetd.ipfwdDisableForwarding(TAG);
+                } catch (RemoteException | ServiceSpecificException e) { }
             }
         }
 
@@ -1720,11 +1743,11 @@
                 Log.e(TAG, "Error in setDnsForwarders");
                 notify(IpServer.CMD_SET_DNS_FORWARDERS_ERROR);
                 try {
-                    mNMService.stopTethering();
-                } catch (Exception e) { }
+                    mNetd.tetherStop();
+                } catch (RemoteException | ServiceSpecificException e) { }
                 try {
-                    mNMService.setIpForwardingEnabled(false);
-                } catch (Exception e) { }
+                    mNetd.ipfwdDisableForwarding(TAG);
+                } catch (RemoteException | ServiceSpecificException e) { }
             }
         }
 
@@ -1823,9 +1846,13 @@
     void registerTetheringEventCallback(ITetheringEventCallback callback) {
         mHandler.post(() -> {
             mTetheringEventCallbacks.register(callback);
+            final TetheringCallbackStartedParcel parcel = new TetheringCallbackStartedParcel();
+            parcel.tetheringSupported = mDeps.isTetheringSupported();
+            parcel.upstreamNetwork = mTetherUpstream;
+            parcel.config = mConfig.toStableParcelable();
+            parcel.states = mTetherStatesParcel;
             try {
-                callback.onCallbackStarted(mTetherUpstream, mConfig.toStableParcelable(),
-                        mTetherStatesParcel);
+                callback.onCallbackStarted(parcel);
             } catch (RemoteException e) {
                 // Not really very much to do here.
             }
@@ -1860,6 +1887,7 @@
             for (int i = 0; i < length; i++) {
                 try {
                     mTetheringEventCallbacks.getBroadcastItem(i).onConfigurationChanged(config);
+                    // TODO(b/148139325): send tetheringSupported on configuration change
                 } catch (RemoteException e) {
                     // Not really very much to do here.
                 }
@@ -1884,7 +1912,7 @@
         }
     }
 
-    void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+    void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args) {
         // Binder.java closes the resource for us.
         @SuppressWarnings("resource")
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
@@ -1993,15 +2021,6 @@
 
         mLog.log(String.format("OBSERVED iface=%s state=%s error=%s", iface, state, error));
 
-        try {
-            // Notify that we're tethering (or not) this interface.
-            // This is how data saver for instance knows if the user explicitly
-            // turned on tethering (thus keeping us from being in data saver mode).
-            mPolicyManager.onTetheringChanged(iface, state == IpServer.STATE_TETHERED);
-        } catch (RemoteException e) {
-            // Not really very much we can do here.
-        }
-
         // If TetherMasterSM is in ErrorState, TetherMasterSM stays there.
         // Thus we give a chance for TetherMasterSM to recover to InitialState
         // by sending CMD_CLEAR_ERROR
@@ -2065,7 +2084,7 @@
 
         mLog.log("adding TetheringInterfaceStateMachine for: " + iface);
         final TetherState tetherState = new TetherState(
-                new IpServer(iface, mLooper, interfaceType, mLog, mNMService, mStatsService,
+                new IpServer(iface, mLooper, interfaceType, mLog, mNetd,
                              makeControlCallback(), mConfig.enableLegacyDhcpServer,
                              mDeps.getIpServerDependencies()));
         mTetherStates.put(iface, tetherState);
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
index 490614b..068c346 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
@@ -21,31 +21,19 @@
 import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
-import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
 
-import static com.android.internal.R.array.config_mobile_hotspot_provision_app;
-import static com.android.internal.R.array.config_tether_bluetooth_regexs;
-import static com.android.internal.R.array.config_tether_dhcp_range;
-import static com.android.internal.R.array.config_tether_upstream_types;
-import static com.android.internal.R.array.config_tether_usb_regexs;
-import static com.android.internal.R.array.config_tether_wifi_p2p_regexs;
-import static com.android.internal.R.array.config_tether_wifi_regexs;
-import static com.android.internal.R.bool.config_tether_upstream_automatic;
-import static com.android.internal.R.integer.config_mobile_hotspot_provision_check_period;
-import static com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui;
-
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
-import android.net.ConnectivityManager;
 import android.net.TetheringConfigurationParcel;
 import android.net.util.SharedLog;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.networkstack.tethering.R;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -85,6 +73,12 @@
 
     private static final String[] DEFAULT_IPV4_DNS = {"8.8.4.4", "8.8.8.8"};
 
+    /**
+     * Use the old dnsmasq DHCP server for tethering instead of the framework implementation.
+     */
+    public static final String TETHER_ENABLE_LEGACY_DHCP_SERVER =
+            "tether_enable_legacy_dhcp_server";
+
     public final String[] tetherableUsbRegexs;
     public final String[] tetherableWifiRegexs;
     public final String[] tetherableWifiP2pRegexs;
@@ -108,27 +102,30 @@
         activeDataSubId = id;
         Resources res = getResources(ctx, activeDataSubId);
 
-        tetherableUsbRegexs = getResourceStringArray(res, config_tether_usb_regexs);
+        tetherableUsbRegexs = getResourceStringArray(res, R.array.config_tether_usb_regexs);
         // TODO: Evaluate deleting this altogether now that Wi-Fi always passes
         // us an interface name. Careful consideration needs to be given to
         // implications for Settings and for provisioning checks.
-        tetherableWifiRegexs = getResourceStringArray(res, config_tether_wifi_regexs);
-        tetherableWifiP2pRegexs = getResourceStringArray(res, config_tether_wifi_p2p_regexs);
-        tetherableBluetoothRegexs = getResourceStringArray(res, config_tether_bluetooth_regexs);
+        tetherableWifiRegexs = getResourceStringArray(res, R.array.config_tether_wifi_regexs);
+        tetherableWifiP2pRegexs = getResourceStringArray(
+                res, R.array.config_tether_wifi_p2p_regexs);
+        tetherableBluetoothRegexs = getResourceStringArray(
+                res, R.array.config_tether_bluetooth_regexs);
 
         isDunRequired = checkDunRequired(ctx);
 
-        chooseUpstreamAutomatically = getResourceBoolean(res, config_tether_upstream_automatic);
+        chooseUpstreamAutomatically = getResourceBoolean(
+                res, R.bool.config_tether_upstream_automatic);
         preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(res, isDunRequired);
 
         legacyDhcpRanges = getLegacyDhcpRanges(res);
         defaultIPv4DNS = copy(DEFAULT_IPV4_DNS);
-        enableLegacyDhcpServer = getEnableLegacyDhcpServer(ctx);
+        enableLegacyDhcpServer = getEnableLegacyDhcpServer(res);
 
-        provisioningApp = getResourceStringArray(res, config_mobile_hotspot_provision_app);
+        provisioningApp = getResourceStringArray(res, R.array.config_mobile_hotspot_provision_app);
         provisioningAppNoUi = getProvisioningAppNoUi(res);
         provisioningCheckPeriod = getResourceInteger(res,
-                config_mobile_hotspot_provision_check_period,
+                R.integer.config_mobile_hotspot_provision_check_period,
                 0 /* No periodic re-check */);
 
         configLog.log(toString());
@@ -179,8 +176,8 @@
 
         pw.print("chooseUpstreamAutomatically: ");
         pw.println(chooseUpstreamAutomatically);
-        dumpStringArray(pw, "preferredUpstreamIfaceTypes",
-                preferredUpstreamNames(preferredUpstreamIfaceTypes));
+        pw.print("legacyPreredUpstreamIfaceTypes: ");
+        pw.println(Arrays.toString(toIntArray(preferredUpstreamIfaceTypes)));
 
         dumpStringArray(pw, "legacyDhcpRanges", legacyDhcpRanges);
         dumpStringArray(pw, "defaultIPv4DNS", defaultIPv4DNS);
@@ -205,7 +202,7 @@
         sj.add(String.format("isDunRequired:%s", isDunRequired));
         sj.add(String.format("chooseUpstreamAutomatically:%s", chooseUpstreamAutomatically));
         sj.add(String.format("preferredUpstreamIfaceTypes:%s",
-                makeString(preferredUpstreamNames(preferredUpstreamIfaceTypes))));
+                toIntArray(preferredUpstreamIfaceTypes)));
         sj.add(String.format("provisioningApp:%s", makeString(provisioningApp)));
         sj.add(String.format("provisioningAppNoUi:%s", provisioningAppNoUi));
         sj.add(String.format("enableLegacyDhcpServer:%s", enableLegacyDhcpServer));
@@ -234,21 +231,6 @@
         return sj.toString();
     }
 
-    private static String[] preferredUpstreamNames(Collection<Integer> upstreamTypes) {
-        String[] upstreamNames = null;
-
-        if (upstreamTypes != null) {
-            upstreamNames = new String[upstreamTypes.size()];
-            int i = 0;
-            for (Integer netType : upstreamTypes) {
-                upstreamNames[i] = ConnectivityManager.getNetworkTypeName(netType);
-                i++;
-            }
-        }
-
-        return upstreamNames;
-    }
-
     /** Check whether dun is required. */
     public static boolean checkDunRequired(Context ctx) {
         final TelephonyManager tm = (TelephonyManager) ctx.getSystemService(TELEPHONY_SERVICE);
@@ -258,7 +240,7 @@
     }
 
     private static Collection<Integer> getUpstreamIfaceTypes(Resources res, boolean dunRequired) {
-        final int[] ifaceTypes = res.getIntArray(config_tether_upstream_types);
+        final int[] ifaceTypes = res.getIntArray(R.array.config_tether_upstream_types);
         final ArrayList<Integer> upstreamIfaceTypes = new ArrayList<>(ifaceTypes.length);
         for (int i : ifaceTypes) {
             switch (i) {
@@ -308,7 +290,7 @@
     }
 
     private static String[] getLegacyDhcpRanges(Resources res) {
-        final String[] fromResource = getResourceStringArray(res, config_tether_dhcp_range);
+        final String[] fromResource = getResourceStringArray(res, R.array.config_tether_dhcp_range);
         if ((fromResource.length > 0) && (fromResource.length % 2 == 0)) {
             return fromResource;
         }
@@ -317,7 +299,7 @@
 
     private static String getProvisioningAppNoUi(Resources res) {
         try {
-            return res.getString(config_mobile_hotspot_provision_app_no_ui);
+            return res.getString(R.string.config_mobile_hotspot_provision_app_no_ui);
         } catch (Resources.NotFoundException e) {
             return "";
         }
@@ -348,10 +330,14 @@
         }
     }
 
-    private static boolean getEnableLegacyDhcpServer(Context ctx) {
-        final ContentResolver cr = ctx.getContentResolver();
-        final int intVal = Settings.Global.getInt(cr, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0);
-        return intVal != 0;
+    private boolean getEnableLegacyDhcpServer(final Resources res) {
+        return getResourceBoolean(res, R.bool.config_tether_enable_legacy_dhcp_server)
+                || getDeviceConfigBoolean(TETHER_ENABLE_LEGACY_DHCP_SERVER);
+    }
+
+    @VisibleForTesting
+    protected boolean getDeviceConfigBoolean(final String name) {
+        return DeviceConfig.getBoolean(NAMESPACE_CONNECTIVITY, name, false /** defaultValue */);
     }
 
     private Resources getResources(Context ctx, int subId) {
@@ -388,6 +374,15 @@
         return false;
     }
 
+    private static int[] toIntArray(Collection<Integer> values) {
+        final int[] result = new int[values.size()];
+        int index = 0;
+        for (Integer value : values) {
+            result[index++] = value;
+        }
+        return result;
+    }
+
     /**
      * Convert this TetheringConfiguration to a TetheringConfigurationParcel.
      */
@@ -400,12 +395,7 @@
         parcel.isDunRequired = isDunRequired;
         parcel.chooseUpstreamAutomatically = chooseUpstreamAutomatically;
 
-        int[] preferredTypes = new int[preferredUpstreamIfaceTypes.size()];
-        int index = 0;
-        for (Integer type : preferredUpstreamIfaceTypes) {
-            preferredTypes[index++] = type;
-        }
-        parcel.preferredUpstreamIfaceTypes = preferredTypes;
+        parcel.preferredUpstreamIfaceTypes = toIntArray(preferredUpstreamIfaceTypes);
 
         parcel.legacyDhcpRanges = legacyDhcpRanges;
         parcel.defaultIPv4DNS = defaultIPv4DNS;
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java
index b16b329..e019c3a 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java
@@ -16,18 +16,15 @@
 
 package com.android.server.connectivity.tethering;
 
+import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
 import android.net.INetd;
-import android.net.INetworkPolicyManager;
-import android.net.INetworkStatsService;
 import android.net.NetworkRequest;
 import android.net.ip.IpServer;
 import android.net.util.SharedLog;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.INetworkManagementService;
 import android.os.Looper;
-import android.os.ServiceManager;
 
 import com.android.internal.util.StateMachine;
 
@@ -97,33 +94,6 @@
     }
 
     /**
-     * Get a reference to INetworkManagementService to registerTetheringStatsProvider from
-     * OffloadController. Note: This should be removed soon by Usage refactor work in R
-     * development cycle.
-     */
-    public INetworkManagementService getINetworkManagementService() {
-        return INetworkManagementService.Stub.asInterface(
-                ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
-    }
-
-    /**
-     *  Get a reference to INetworkStatsService to force update tethering usage.
-     *  Note: This should be removed in R development cycle.
-     */
-    public INetworkStatsService getINetworkStatsService() {
-        return INetworkStatsService.Stub.asInterface(
-                ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
-    }
-
-    /**
-     * Get a reference to INetworkPolicyManager to be used by tethering.
-     */
-    public INetworkPolicyManager getINetworkPolicyManager() {
-        return INetworkPolicyManager.Stub.asInterface(
-                ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
-    }
-
-    /**
      * Get a reference to INetd to be used by tethering.
      */
     public INetd getINetd(Context context) {
@@ -140,4 +110,9 @@
      *  Get Context of TetheringSerice.
      */
     public abstract Context getContext();
+
+    /**
+     * Get a reference to BluetoothAdapter to be used by tethering.
+     */
+    public abstract BluetoothAdapter getBluetoothAdapter();
 }
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
index 6334c20..4dd6830 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
@@ -22,14 +22,19 @@
 import android.net.RouteInfo;
 import android.net.util.InterfaceSet;
 
-import java.net.Inet4Address;
-import java.net.Inet6Address;
+import com.android.net.module.util.NetUtils;
+
 import java.net.InetAddress;
+import java.net.UnknownHostException;
 
 /**
  * @hide
  */
 public final class TetheringInterfaceUtils {
+    private static final InetAddress IN6ADDR_ANY = getByAddress(
+            new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
+    private static final InetAddress INADDR_ANY = getByAddress(new byte[] {0, 0, 0, 0});
+
     /**
      * Get upstream interfaces for tethering based on default routes for IPv4/IPv6.
      * @return null if there is no usable interface, or a set of at least one interface otherwise.
@@ -40,7 +45,7 @@
         }
 
         final LinkProperties lp = ns.linkProperties;
-        final String if4 = getInterfaceForDestination(lp, Inet4Address.ANY);
+        final String if4 = getInterfaceForDestination(lp, INADDR_ANY);
         final String if6 = getIPv6Interface(ns);
 
         return (if4 == null && if6 == null) ? null : new InterfaceSet(if4, if6);
@@ -76,14 +81,22 @@
                 && ns.networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR);
 
         return canTether
-                ? getInterfaceForDestination(ns.linkProperties, Inet6Address.ANY)
+                ? getInterfaceForDestination(ns.linkProperties, IN6ADDR_ANY)
                 : null;
     }
 
     private static String getInterfaceForDestination(LinkProperties lp, InetAddress dst) {
         final RouteInfo ri = (lp != null)
-                ? RouteInfo.selectBestRoute(lp.getAllRoutes(), dst)
+                ? NetUtils.selectBestRoute(lp.getAllRoutes(), dst)
                 : null;
         return (ri != null) ? ri.getInterface() : null;
     }
+
+    private static InetAddress getByAddress(final byte[] addr) {
+        try {
+            return InetAddress.getByAddress(null, addr);
+        } catch (UnknownHostException e) {
+            throw new AssertionError("illegal address length" + addr.length);
+        }
+    }
 }
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
index 775484e..cb7d392 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
@@ -24,13 +24,14 @@
 import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR;
 
 import android.app.Service;
+import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
 import android.content.Intent;
-import android.net.ConnectivityManager;
 import android.net.IIntResultListener;
 import android.net.INetworkStackConnector;
 import android.net.ITetheringConnector;
 import android.net.ITetheringEventCallback;
+import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.dhcp.DhcpServerCallbacks;
 import android.net.dhcp.DhcpServingParamsParcel;
@@ -42,7 +43,6 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
-import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -307,9 +307,15 @@
             mDeps = new TetheringDependencies() {
                 @Override
                 public NetworkRequest getDefaultNetworkRequest() {
-                    ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(
-                            Context.CONNECTIVITY_SERVICE);
-                    return cm.getDefaultRequest();
+                    // TODO: b/147280869, add a proper system API to replace this.
+                    final NetworkRequest trackDefaultRequest = new NetworkRequest.Builder()
+                            .clearCapabilities()
+                            .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                            .addCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+                            .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+                            .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                            .build();
+                    return trackDefaultRequest;
                 }
 
                 @Override
@@ -357,7 +363,7 @@
                     IBinder connector;
                     try {
                         final long before = System.currentTimeMillis();
-                        while ((connector = ServiceManager.getService(
+                        while ((connector = (IBinder) mContext.getSystemService(
                                 Context.NETWORK_STACK_SERVICE)) == null) {
                             if (System.currentTimeMillis() - before > NETWORKSTACK_TIMEOUT_MS) {
                                 Log.wtf(TAG, "Timeout, fail to get INetworkStackConnector");
@@ -371,6 +377,11 @@
                     }
                     return INetworkStackConnector.Stub.asInterface(connector);
                 }
+
+                @Override
+                public BluetoothAdapter getBluetoothAdapter() {
+                    return BluetoothAdapter.getDefaultAdapter();
+                }
             };
         }
         return mDeps;
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
index 22150f6..2875f71 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -16,11 +16,15 @@
 
 package com.android.server.connectivity.tethering;
 
+import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
+import static android.net.ConnectivityManager.TYPE_ETHERNET;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
-import static android.net.ConnectivityManager.TYPE_NONE;
-import static android.net.ConnectivityManager.getNetworkTypeName;
+import static android.net.ConnectivityManager.TYPE_WIFI;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 
@@ -35,10 +39,11 @@
 import android.net.util.PrefixUtils;
 import android.net.util.SharedLog;
 import android.os.Handler;
-import android.os.Process;
 import android.util.Log;
+import android.util.SparseIntArray;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
 import com.android.internal.util.StateMachine;
 
 import java.util.HashMap;
@@ -79,11 +84,25 @@
     public static final int EVENT_ON_LINKPROPERTIES = 2;
     public static final int EVENT_ON_LOST           = 3;
     public static final int NOTIFY_LOCAL_PREFIXES   = 10;
+    // This value is used by deprecated preferredUpstreamIfaceTypes selection which is default
+    // disabled.
+    @VisibleForTesting
+    public static final int TYPE_NONE = -1;
 
     private static final int CALLBACK_LISTEN_ALL = 1;
     private static final int CALLBACK_DEFAULT_INTERNET = 2;
     private static final int CALLBACK_MOBILE_REQUEST = 3;
 
+    private static final SparseIntArray sLegacyTypeToTransport = new SparseIntArray();
+    static {
+        sLegacyTypeToTransport.put(TYPE_MOBILE,       NetworkCapabilities.TRANSPORT_CELLULAR);
+        sLegacyTypeToTransport.put(TYPE_MOBILE_DUN,   NetworkCapabilities.TRANSPORT_CELLULAR);
+        sLegacyTypeToTransport.put(TYPE_MOBILE_HIPRI, NetworkCapabilities.TRANSPORT_CELLULAR);
+        sLegacyTypeToTransport.put(TYPE_WIFI,         NetworkCapabilities.TRANSPORT_WIFI);
+        sLegacyTypeToTransport.put(TYPE_BLUETOOTH,    NetworkCapabilities.TRANSPORT_BLUETOOTH);
+        sLegacyTypeToTransport.put(TYPE_ETHERNET,     NetworkCapabilities.TRANSPORT_ETHERNET);
+    }
+
     private final Context mContext;
     private final SharedLog mLog;
     private final StateMachine mTarget;
@@ -130,15 +149,15 @@
      */
     public void startTrackDefaultNetwork(NetworkRequest defaultNetworkRequest,
             EntitlementManager entitle) {
-        // This is not really a "request", just a way of tracking the system default network.
-        // It's guaranteed not to actually bring up any networks because it's the same request
-        // as the ConnectivityService default request, and thus shares fate with it. We can't
-        // use registerDefaultNetworkCallback because it will not track the system default
-        // network if there is a VPN that applies to our UID.
+
+        // defaultNetworkRequest is not really a "request", just a way of tracking the system
+        // default network. It's guaranteed not to actually bring up any networks because it's
+        // the should be the same request as the ConnectivityService default request, and thus
+        // shares fate with it. We can't use registerDefaultNetworkCallback because it will not
+        // track the system default network if there is a VPN that applies to our UID.
         if (mDefaultNetworkCallback == null) {
-            final NetworkRequest trackDefaultRequest = new NetworkRequest(defaultNetworkRequest);
             mDefaultNetworkCallback = new UpstreamNetworkCallback(CALLBACK_DEFAULT_INTERNET);
-            cm().requestNetwork(trackDefaultRequest, mDefaultNetworkCallback, mHandler);
+            cm().requestNetwork(defaultNetworkRequest, mDefaultNetworkCallback, mHandler);
         }
         if (mEntitlementMgr == null) {
             mEntitlementMgr = entitle;
@@ -198,19 +217,28 @@
             mLog.e("registerMobileNetworkRequest() already registered");
             return;
         }
-        // The following use of the legacy type system cannot be removed until
-        // after upstream selection no longer finds networks by legacy type.
-        // See also http://b/34364553 .
-        final int legacyType = mDunRequired ? TYPE_MOBILE_DUN : TYPE_MOBILE_HIPRI;
 
-        final NetworkRequest mobileUpstreamRequest = new NetworkRequest.Builder()
-                .setCapabilities(ConnectivityManager.networkCapabilitiesForType(legacyType))
-                .build();
+        final NetworkRequest mobileUpstreamRequest;
+        if (mDunRequired) {
+            mobileUpstreamRequest = new NetworkRequest.Builder()
+                    .addCapability(NET_CAPABILITY_DUN)
+                    .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                    .addTransportType(TRANSPORT_CELLULAR).build();
+        } else {
+            mobileUpstreamRequest = new NetworkRequest.Builder()
+                    .addCapability(NET_CAPABILITY_INTERNET)
+                    .addTransportType(TRANSPORT_CELLULAR).build();
+        }
 
         // The existing default network and DUN callbacks will be notified.
         // Therefore, to avoid duplicate notifications, we only register a no-op.
         mMobileNetworkCallback = new UpstreamNetworkCallback(CALLBACK_MOBILE_REQUEST);
 
+        // The following use of the legacy type system cannot be removed until
+        // upstream selection no longer finds networks by legacy type.
+        // See also http://b/34364553 .
+        final int legacyType = mDunRequired ? TYPE_MOBILE_DUN : TYPE_MOBILE_HIPRI;
+
         // TODO: Change the timeout from 0 (no onUnavailable callback) to some
         // moderate callback timeout. This might be useful for updating some UI.
         // Additionally, we log a message to aid in any subsequent debugging.
@@ -239,7 +267,7 @@
         final TypeStatePair typeStatePair = findFirstAvailableUpstreamByType(
                 mNetworkMap.values(), preferredTypes, isCellularUpstreamPermitted());
 
-        mLog.log("preferred upstream type: " + getNetworkTypeName(typeStatePair.type));
+        mLog.log("preferred upstream type: " + typeStatePair.type);
 
         switch (typeStatePair.type) {
             case TYPE_MOBILE_DUN:
@@ -356,16 +384,6 @@
         notifyTarget(EVENT_ON_LINKPROPERTIES, network);
     }
 
-    private void handleSuspended(Network network) {
-        if (!network.equals(mTetheringUpstreamNetwork)) return;
-        mLog.log("SUSPENDED current upstream: " + network);
-    }
-
-    private void handleResumed(Network network) {
-        if (!network.equals(mTetheringUpstreamNetwork)) return;
-        mLog.log("RESUMED current upstream: " + network);
-    }
-
     private void handleLost(Network network) {
         // There are few TODOs within ConnectivityService's rematching code
         // pertaining to spurious onLost() notifications.
@@ -455,20 +473,6 @@
         }
 
         @Override
-        public void onNetworkSuspended(Network network) {
-            if (mCallbackType == CALLBACK_LISTEN_ALL) {
-                handleSuspended(network);
-            }
-        }
-
-        @Override
-        public void onNetworkResumed(Network network) {
-            if (mCallbackType == CALLBACK_LISTEN_ALL) {
-                handleResumed(network);
-            }
-        }
-
-        @Override
         public void onLost(Network network) {
             if (mCallbackType == CALLBACK_DEFAULT_INTERNET) {
                 mDefaultInternetNetwork = null;
@@ -512,18 +516,15 @@
         for (int type : preferredTypes) {
             NetworkCapabilities nc;
             try {
-                nc = ConnectivityManager.networkCapabilitiesForType(type);
+                nc = networkCapabilitiesForType(type);
             } catch (IllegalArgumentException iae) {
-                Log.e(TAG, "No NetworkCapabilities mapping for legacy type: "
-                        + ConnectivityManager.getNetworkTypeName(type));
+                Log.e(TAG, "No NetworkCapabilities mapping for legacy type: " + type);
                 continue;
             }
             if (!isCellularUpstreamPermitted && isCellular(nc)) {
                 continue;
             }
 
-            nc.setSingleUid(Process.myUid());
-
             for (UpstreamNetworkState value : netStates) {
                 if (!nc.satisfiedByNetworkCapabilities(value.networkCapabilities)) {
                     continue;
@@ -577,4 +578,28 @@
 
         return null;
     }
+
+    /**
+     * Given a legacy type (TYPE_WIFI, ...) returns the corresponding NetworkCapabilities instance.
+     * This function is used for deprecated legacy type and be disabled by default.
+     */
+    @VisibleForTesting
+    public static NetworkCapabilities networkCapabilitiesForType(int type) {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+
+        // Map from type to transports.
+        final int notFound = -1;
+        final int transport = sLegacyTypeToTransport.get(type, notFound);
+        Preconditions.checkArgument(transport != notFound, "unknown legacy type: " + type);
+        nc.addTransportType(transport);
+
+        if (type == TYPE_MOBILE_DUN) {
+            nc.addCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
+            // DUN is restricted network, see NetworkCapabilities#FORCE_RESTRICTED_CAPABILITIES.
+            nc.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+        } else {
+            nc.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        }
+        return nc;
+    }
 }
diff --git a/packages/Tethering/tests/unit/jarjar-rules.txt b/packages/Tethering/tests/unit/jarjar-rules.txt
index 64fdebd..921fbed 100644
--- a/packages/Tethering/tests/unit/jarjar-rules.txt
+++ b/packages/Tethering/tests/unit/jarjar-rules.txt
@@ -7,5 +7,6 @@
 rule com.android.internal.util.Preconditions* com.android.networkstack.tethering.util.Preconditions@1
 rule com.android.internal.util.State* com.android.networkstack.tethering.util.State@1
 rule com.android.internal.util.StateMachine* com.android.networkstack.tethering.util.StateMachine@1
+rule com.android.internal.util.TrafficStatsConstants* com.android.networkstack.tethering.util.TrafficStatsConstants@1
 
 rule android.net.LocalLog* com.android.networkstack.tethering.LocalLog@1
diff --git a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index fd2f708..f29ad78 100644
--- a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -16,13 +16,14 @@
 
 package android.net.ip;
 
-import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
-import static android.net.ConnectivityManager.TETHERING_USB;
-import static android.net.ConnectivityManager.TETHERING_WIFI;
-import static android.net.ConnectivityManager.TETHERING_WIFI_P2P;
-import static android.net.ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
-import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
-import static android.net.ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR;
+import static android.net.INetd.IF_STATE_UP;
+import static android.net.TetheringManager.TETHERING_BLUETOOTH;
+import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+import static android.net.TetheringManager.TETHER_ERROR_ENABLE_NAT_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR;
 import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
 import static android.net.ip.IpServer.STATE_AVAILABLE;
 import static android.net.ip.IpServer.STATE_LOCAL_ONLY;
@@ -51,8 +52,7 @@
 import static org.mockito.Mockito.when;
 
 import android.net.INetd;
-import android.net.INetworkStatsService;
-import android.net.InterfaceConfiguration;
+import android.net.InterfaceConfigurationParcel;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
@@ -64,7 +64,6 @@
 import android.net.util.InterfaceParams;
 import android.net.util.InterfaceSet;
 import android.net.util.SharedLog;
-import android.os.INetworkManagementService;
 import android.os.RemoteException;
 import android.os.test.TestLooper;
 import android.text.TextUtils;
@@ -89,6 +88,8 @@
     private static final String IFACE_NAME = "testnet1";
     private static final String UPSTREAM_IFACE = "upstream0";
     private static final String UPSTREAM_IFACE2 = "upstream1";
+    private static final String BLUETOOTH_IFACE_ADDR = "192.168.42.1";
+    private static final int BLUETOOTH_DHCP_PREFIX_LENGTH = 24;
     private static final int DHCP_LEASE_TIME_SECS = 3600;
 
     private static final InterfaceParams TEST_IFACE_PARAMS = new InterfaceParams(
@@ -96,11 +97,8 @@
 
     private static final int MAKE_DHCPSERVER_TIMEOUT_MS = 1000;
 
-    @Mock private INetworkManagementService mNMService;
     @Mock private INetd mNetd;
-    @Mock private INetworkStatsService mStatsService;
     @Mock private IpServer.Callback mCallback;
-    @Mock private InterfaceConfiguration mInterfaceConfiguration;
     @Mock private SharedLog mSharedLog;
     @Mock private IDhcpServer mDhcpServer;
     @Mock private RouterAdvertisementDaemon mRaDaemon;
@@ -112,6 +110,7 @@
     private final ArgumentCaptor<LinkProperties> mLinkPropertiesCaptor =
             ArgumentCaptor.forClass(LinkProperties.class);
     private IpServer mIpServer;
+    private InterfaceConfigurationParcel mInterfaceConfiguration;
 
     private void initStateMachine(int interfaceType) throws Exception {
         initStateMachine(interfaceType, false /* usingLegacyDhcp */);
@@ -131,17 +130,20 @@
         }).when(mDependencies).makeDhcpServer(any(), mDhcpParamsCaptor.capture(), any());
         when(mDependencies.getRouterAdvertisementDaemon(any())).thenReturn(mRaDaemon);
         when(mDependencies.getInterfaceParams(IFACE_NAME)).thenReturn(TEST_IFACE_PARAMS);
-        when(mDependencies.getNetdService()).thenReturn(mNetd);
-
+        mInterfaceConfiguration = new InterfaceConfigurationParcel();
+        mInterfaceConfiguration.flags = new String[0];
+        if (interfaceType == TETHERING_BLUETOOTH) {
+            mInterfaceConfiguration.ipv4Addr = BLUETOOTH_IFACE_ADDR;
+            mInterfaceConfiguration.prefixLength = BLUETOOTH_DHCP_PREFIX_LENGTH;
+        }
         mIpServer = new IpServer(
-                IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog,
-                mNMService, mStatsService, mCallback, usingLegacyDhcp, mDependencies);
+                IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd,
+                mCallback, usingLegacyDhcp, mDependencies);
         mIpServer.start();
         // Starting the state machine always puts us in a consistent state and notifies
         // the rest of the world that we've changed from an unknown to available state.
         mLooper.dispatchAll();
-        reset(mNMService, mStatsService, mCallback);
-        when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
+        reset(mNetd, mCallback);
 
         when(mRaDaemon.start()).thenReturn(true);
     }
@@ -158,8 +160,7 @@
         if (upstreamIface != null) {
             dispatchTetherConnectionChanged(upstreamIface);
         }
-        reset(mNMService, mStatsService, mCallback);
-        when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
+        reset(mNetd, mCallback);
     }
 
     @Before public void setUp() throws Exception {
@@ -169,15 +170,14 @@
 
     @Test
     public void startsOutAvailable() {
-        mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(),
-                TETHERING_BLUETOOTH, mSharedLog, mNMService, mStatsService, mCallback,
-                false /* usingLegacyDhcp */, mDependencies);
+        mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), TETHERING_BLUETOOTH, mSharedLog,
+                mNetd, mCallback, false /* usingLegacyDhcp */, mDependencies);
         mIpServer.start();
         mLooper.dispatchAll();
         verify(mCallback).updateInterfaceState(
                 mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
         verify(mCallback).updateLinkProperties(eq(mIpServer), any(LinkProperties.class));
-        verifyNoMoreInteractions(mCallback, mNMService, mStatsService);
+        verifyNoMoreInteractions(mCallback, mNetd);
     }
 
     @Test
@@ -196,7 +196,7 @@
             // None of these commands should trigger us to request action from
             // the rest of the system.
             dispatchCommand(command);
-            verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+            verifyNoMoreInteractions(mNetd, mCallback);
         }
     }
 
@@ -208,7 +208,7 @@
         verify(mCallback).updateInterfaceState(
                 mIpServer, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR);
         verify(mCallback).updateLinkProperties(eq(mIpServer), any(LinkProperties.class));
-        verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+        verifyNoMoreInteractions(mNetd, mCallback);
     }
 
     @Test
@@ -216,13 +216,17 @@
         initStateMachine(TETHERING_BLUETOOTH);
 
         dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
-        InOrder inOrder = inOrder(mCallback, mNMService);
-        inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
+        InOrder inOrder = inOrder(mCallback, mNetd);
+        inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
+        inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
+        // One for ipv4 route, one for ipv6 link local route.
+        inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME),
+                any(), any());
         inOrder.verify(mCallback).updateInterfaceState(
                 mIpServer, STATE_TETHERED, TETHER_ERROR_NO_ERROR);
         inOrder.verify(mCallback).updateLinkProperties(
                 eq(mIpServer), any(LinkProperties.class));
-        verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+        verifyNoMoreInteractions(mNetd, mCallback);
     }
 
     @Test
@@ -230,14 +234,16 @@
         initTetheredStateMachine(TETHERING_BLUETOOTH, null);
 
         dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
-        InOrder inOrder = inOrder(mNMService, mNetd, mStatsService, mCallback);
-        inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
+        InOrder inOrder = inOrder(mNetd, mCallback);
+        inOrder.verify(mNetd).tetherApplyDnsInterfaces();
+        inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME);
+        inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
         inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
         inOrder.verify(mCallback).updateInterfaceState(
                 mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
         inOrder.verify(mCallback).updateLinkProperties(
                 eq(mIpServer), any(LinkProperties.class));
-        verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+        verifyNoMoreInteractions(mNetd, mCallback);
     }
 
     @Test
@@ -245,16 +251,19 @@
         initStateMachine(TETHERING_USB);
 
         dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
-        InOrder inOrder = inOrder(mCallback, mNMService);
-        inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME);
-        inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
-        inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
+        InOrder inOrder = inOrder(mCallback, mNetd);
+        inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
+                  IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
+        inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
+        inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
+        inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME),
+                any(), any());
         inOrder.verify(mCallback).updateInterfaceState(
                 mIpServer, STATE_TETHERED, TETHER_ERROR_NO_ERROR);
         inOrder.verify(mCallback).updateLinkProperties(
                 eq(mIpServer), mLinkPropertiesCaptor.capture());
         assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue());
-        verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+        verifyNoMoreInteractions(mNetd, mCallback);
     }
 
     @Test
@@ -262,16 +271,19 @@
         initStateMachine(TETHERING_WIFI_P2P);
 
         dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_LOCAL_ONLY);
-        InOrder inOrder = inOrder(mCallback, mNMService);
-        inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME);
-        inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
-        inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
+        InOrder inOrder = inOrder(mCallback, mNetd);
+        inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
+                  IFACE_NAME.equals(cfg.ifName) && assertNotContainsFlag(cfg.flags, IF_STATE_UP)));
+        inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
+        inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
+        inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME),
+                any(), any());
         inOrder.verify(mCallback).updateInterfaceState(
                 mIpServer, STATE_LOCAL_ONLY, TETHER_ERROR_NO_ERROR);
         inOrder.verify(mCallback).updateLinkProperties(
                 eq(mIpServer), mLinkPropertiesCaptor.capture());
         assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue());
-        verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+        verifyNoMoreInteractions(mNetd, mCallback);
     }
 
     @Test
@@ -281,10 +293,10 @@
         // Telling the state machine about its upstream interface triggers
         // a little more configuration.
         dispatchTetherConnectionChanged(UPSTREAM_IFACE);
-        InOrder inOrder = inOrder(mNMService);
-        inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE);
-        inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
-        verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+        InOrder inOrder = inOrder(mNetd);
+        inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
+        verifyNoMoreInteractions(mNetd, mCallback);
     }
 
     @Test
@@ -292,49 +304,44 @@
         initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
 
         dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
-        InOrder inOrder = inOrder(mNMService, mStatsService);
-        inOrder.verify(mStatsService).forceUpdate();
-        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
-        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
-        inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
-        inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
-        verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+        InOrder inOrder = inOrder(mNetd);
+        inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
+        verifyNoMoreInteractions(mNetd, mCallback);
     }
 
     @Test
     public void handlesChangingUpstreamNatFailure() throws Exception {
         initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
 
-        doThrow(RemoteException.class).when(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
+        doThrow(RemoteException.class).when(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
 
         dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
-        InOrder inOrder = inOrder(mNMService, mStatsService);
-        inOrder.verify(mStatsService).forceUpdate();
-        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
-        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
-        inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
-        inOrder.verify(mStatsService).forceUpdate();
-        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
-        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE2);
+        InOrder inOrder = inOrder(mNetd);
+        inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE2);
     }
 
     @Test
     public void handlesChangingUpstreamInterfaceForwardingFailure() throws Exception {
         initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
 
-        doThrow(RemoteException.class).when(mNMService).startInterfaceForwarding(
+        doThrow(RemoteException.class).when(mNetd).ipfwdAddInterfaceForward(
                 IFACE_NAME, UPSTREAM_IFACE2);
 
         dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
-        InOrder inOrder = inOrder(mNMService, mStatsService);
-        inOrder.verify(mStatsService).forceUpdate();
-        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
-        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
-        inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
-        inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
-        inOrder.verify(mStatsService).forceUpdate();
-        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
-        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE2);
+        InOrder inOrder = inOrder(mNetd);
+        inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE2);
     }
 
     @Test
@@ -342,17 +349,18 @@
         initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
 
         dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
-        InOrder inOrder = inOrder(mNMService, mNetd, mStatsService, mCallback);
-        inOrder.verify(mStatsService).forceUpdate();
-        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
-        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
-        inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
+        InOrder inOrder = inOrder(mNetd, mCallback);
+        inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNetd).tetherApplyDnsInterfaces();
+        inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME);
+        inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
         inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
         inOrder.verify(mCallback).updateInterfaceState(
                 mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
         inOrder.verify(mCallback).updateLinkProperties(
                 eq(mIpServer), any(LinkProperties.class));
-        verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+        verifyNoMoreInteractions(mNetd, mCallback);
     }
 
     @Test
@@ -361,13 +369,14 @@
             initTetheredStateMachine(TETHERING_USB, null);
 
             if (shouldThrow) {
-                doThrow(RemoteException.class).when(mNMService).untetherInterface(IFACE_NAME);
+                doThrow(RemoteException.class).when(mNetd).tetherInterfaceRemove(IFACE_NAME);
             }
             dispatchCommand(IpServer.CMD_INTERFACE_DOWN);
-            InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mCallback);
-            usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
-            usbTeardownOrder.verify(mNMService).setInterfaceConfig(
-                    IFACE_NAME, mInterfaceConfiguration);
+            InOrder usbTeardownOrder = inOrder(mNetd, mCallback);
+            // Currently IpServer interfaceSetCfg twice to stop IPv4. One just set interface down
+            // Another one is set IPv4 to 0.0.0.0/0 as clearng ipv4 address.
+            usbTeardownOrder.verify(mNetd, times(2)).interfaceSetCfg(
+                    argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
             usbTeardownOrder.verify(mCallback).updateInterfaceState(
                     mIpServer, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR);
             usbTeardownOrder.verify(mCallback).updateLinkProperties(
@@ -380,12 +389,15 @@
     public void usbShouldBeTornDownOnTetherError() throws Exception {
         initStateMachine(TETHERING_USB);
 
-        doThrow(RemoteException.class).when(mNMService).tetherInterface(IFACE_NAME);
+        doThrow(RemoteException.class).when(mNetd).tetherInterfaceAdd(IFACE_NAME);
         dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
-        InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mCallback);
-        usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
-        usbTeardownOrder.verify(mNMService).setInterfaceConfig(
-                IFACE_NAME, mInterfaceConfiguration);
+        InOrder usbTeardownOrder = inOrder(mNetd, mCallback);
+        usbTeardownOrder.verify(mNetd).interfaceSetCfg(
+                argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
+        usbTeardownOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
+
+        usbTeardownOrder.verify(mNetd, times(2)).interfaceSetCfg(
+                argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
         usbTeardownOrder.verify(mCallback).updateInterfaceState(
                 mIpServer, STATE_AVAILABLE, TETHER_ERROR_TETHER_IFACE_ERROR);
         usbTeardownOrder.verify(mCallback).updateLinkProperties(
@@ -397,11 +409,13 @@
     public void shouldTearDownUsbOnUpstreamError() throws Exception {
         initTetheredStateMachine(TETHERING_USB, null);
 
-        doThrow(RemoteException.class).when(mNMService).enableNat(anyString(), anyString());
+        doThrow(RemoteException.class).when(mNetd).tetherAddForward(anyString(), anyString());
         dispatchTetherConnectionChanged(UPSTREAM_IFACE);
-        InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mCallback);
-        usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
-        usbTeardownOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
+        InOrder usbTeardownOrder = inOrder(mNetd, mCallback);
+        usbTeardownOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE);
+
+        usbTeardownOrder.verify(mNetd, times(2)).interfaceSetCfg(
+                argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
         usbTeardownOrder.verify(mCallback).updateInterfaceState(
                 mIpServer, STATE_AVAILABLE, TETHER_ERROR_ENABLE_NAT_ERROR);
         usbTeardownOrder.verify(mCallback).updateLinkProperties(
@@ -413,11 +427,11 @@
     public void ignoresDuplicateUpstreamNotifications() throws Exception {
         initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
 
-        verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+        verifyNoMoreInteractions(mNetd, mCallback);
 
         for (int i = 0; i < 5; i++) {
             dispatchTetherConnectionChanged(UPSTREAM_IFACE);
-            verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+            verifyNoMoreInteractions(mNetd, mCallback);
         }
     }
 
@@ -525,4 +539,22 @@
         // never see an empty interface name in any LinkProperties update.
         assertFalse(TextUtils.isEmpty(lp.getInterfaceName()));
     }
+
+    private boolean assertContainsFlag(String[] flags, String match) {
+        for (String flag : flags) {
+            if (flag.equals(match)) return true;
+        }
+        fail("Missing flag: " + match);
+        return false;
+    }
+
+    private boolean assertNotContainsFlag(String[] flags, String match) {
+        for (String flag : flags) {
+            if (flag.equals(match)) {
+                fail("Unexpected flag: " + match);
+                return false;
+            }
+        }
+        return true;
+    }
 }
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
index 99cf9e9..3a1d4a6 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
@@ -16,12 +16,13 @@
 
 package com.android.server.connectivity.tethering;
 
-import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
-import static android.net.ConnectivityManager.TETHERING_USB;
-import static android.net.ConnectivityManager.TETHERING_WIFI;
-import static android.net.ConnectivityManager.TETHER_ERROR_ENTITLEMENT_UNKONWN;
-import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
-import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED;
+import static android.net.TetheringManager.TETHERING_BLUETOOTH;
+import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_PROVISION_FAILED;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -39,7 +40,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
 import android.net.util.SharedLog;
@@ -49,18 +49,16 @@
 import android.os.ResultReceiver;
 import android.os.SystemProperties;
 import android.os.test.TestLooper;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
 import android.telephony.CarrierConfigManager;
-import android.test.mock.MockContentResolver;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.R;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.internal.util.test.BroadcastInterceptingContext;
-import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.networkstack.tethering.R;
 
 import org.junit.After;
 import org.junit.Before;
@@ -94,7 +92,6 @@
     private final PersistableBundle mCarrierConfig = new PersistableBundle();
     private final TestLooper mLooper = new TestLooper();
     private Context mMockContext;
-    private MockContentResolver mContentResolver;
 
     private TestStateMachine mSM;
     private WrappedEntitlementManager mEnMgr;
@@ -110,15 +107,10 @@
         public Resources getResources() {
             return mResources;
         }
-
-        @Override
-        public ContentResolver getContentResolver() {
-            return mContentResolver;
-        }
     }
 
     public class WrappedEntitlementManager extends EntitlementManager {
-        public int fakeEntitlementResult = TETHER_ERROR_ENTITLEMENT_UNKONWN;
+        public int fakeEntitlementResult = TETHER_ERROR_ENTITLEMENT_UNKNOWN;
         public int uiProvisionCount = 0;
         public int silentProvisionCount = 0;
 
@@ -128,7 +120,7 @@
         }
 
         public void reset() {
-            fakeEntitlementResult = TETHER_ERROR_ENTITLEMENT_UNKONWN;
+            fakeEntitlementResult = TETHER_ERROR_ENTITLEMENT_UNKNOWN;
             uiProvisionCount = 0;
             silentProvisionCount = 0;
         }
@@ -151,28 +143,33 @@
         MockitoAnnotations.initMocks(this);
         mMockingSession = mockitoSession()
                 .initMocks(this)
-                .spyStatic(SystemProperties.class)
+                .mockStatic(SystemProperties.class)
+                .mockStatic(DeviceConfig.class)
                 .strictness(Strictness.WARN)
                 .startMocking();
         // Don't disable tethering provisioning unless requested.
         doReturn(false).when(
                 () -> SystemProperties.getBoolean(
                 eq(EntitlementManager.DISABLE_PROVISIONING_SYSPROP_KEY), anyBoolean()));
+        doReturn(false).when(
+                () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
+                eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
 
         when(mResources.getStringArray(R.array.config_tether_dhcp_range))
-            .thenReturn(new String[0]);
+                .thenReturn(new String[0]);
         when(mResources.getStringArray(R.array.config_tether_usb_regexs))
-            .thenReturn(new String[0]);
+                .thenReturn(new String[0]);
         when(mResources.getStringArray(R.array.config_tether_wifi_regexs))
-            .thenReturn(new String[0]);
+                .thenReturn(new String[0]);
         when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
-            .thenReturn(new String[0]);
+                .thenReturn(new String[0]);
         when(mResources.getIntArray(R.array.config_tether_upstream_types))
-            .thenReturn(new int[0]);
+                .thenReturn(new int[0]);
+        when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+                false);
+        when(mResources.getString(R.string.config_wifi_tether_enable)).thenReturn("");
         when(mLog.forSubComponent(anyString())).thenReturn(mLog);
 
-        mContentResolver = new MockContentResolver();
-        mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
         mMockContext = new MockContext(mContext);
         mSM = new TestStateMachine();
         mEnMgr = new WrappedEntitlementManager(mMockContext, mSM, mLog, EVENT_EM_UPDATE);
@@ -277,7 +274,7 @@
         receiver = new ResultReceiver(null) {
             @Override
             protected void onReceiveResult(int resultCode, Bundle resultData) {
-                assertEquals(TETHER_ERROR_ENTITLEMENT_UNKONWN, resultCode);
+                assertEquals(TETHER_ERROR_ENTITLEMENT_UNKNOWN, resultCode);
                 mCallbacklatch.countDown();
             }
         };
@@ -346,7 +343,7 @@
         receiver = new ResultReceiver(null) {
             @Override
             protected void onReceiveResult(int resultCode, Bundle resultData) {
-                assertEquals(TETHER_ERROR_ENTITLEMENT_UNKONWN, resultCode);
+                assertEquals(TETHER_ERROR_ENTITLEMENT_UNKNOWN, resultCode);
                 mCallbacklatch.countDown();
             }
         };
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java
index 7886ca6..7e62e5a 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java
@@ -16,21 +16,26 @@
 
 package com.android.server.connectivity.tethering;
 
+import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.ROAMING_NO;
 import static android.net.NetworkStats.SET_DEFAULT;
-import static android.net.NetworkStats.STATS_PER_IFACE;
-import static android.net.NetworkStats.STATS_PER_UID;
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
+import static android.net.NetworkStats.UID_TETHERING;
 import static android.net.RouteInfo.RTN_UNICAST;
-import static android.net.TrafficStats.UID_TETHERING;
 import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
 
+import static com.android.server.connectivity.tethering.OffloadController.StatsType.STATS_PER_IFACE;
+import static com.android.server.connectivity.tethering.OffloadController.StatsType.STATS_PER_UID;
 import static com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats;
 import static com.android.testutils.MiscAssertsKt.assertContainsAll;
 import static com.android.testutils.MiscAssertsKt.assertThrows;
+import static com.android.testutils.NetworkStatsUtilsKt.orderInsensitiveEquals;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.anyObject;
@@ -39,11 +44,14 @@
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import android.annotation.NonNull;
+import android.app.usage.NetworkStatsManager;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.net.ITetheringStatsProvider;
@@ -51,10 +59,12 @@
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.NetworkStats;
+import android.net.NetworkStats.Entry;
 import android.net.RouteInfo;
+import android.net.netstats.provider.AbstractNetworkStatsProvider;
+import android.net.netstats.provider.NetworkStatsProviderCallback;
 import android.net.util.SharedLog;
 import android.os.Handler;
-import android.os.INetworkManagementService;
 import android.os.Looper;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
@@ -97,11 +107,13 @@
     @Mock private OffloadHardwareInterface mHardware;
     @Mock private ApplicationInfo mApplicationInfo;
     @Mock private Context mContext;
-    @Mock private INetworkManagementService mNMService;
+    @Mock private NetworkStatsManager mStatsManager;
+    @Mock private NetworkStatsProviderCallback mTetherStatsProviderCb;
     private final ArgumentCaptor<ArrayList> mStringArrayCaptor =
             ArgumentCaptor.forClass(ArrayList.class);
-    private final ArgumentCaptor<ITetheringStatsProvider.Stub> mTetherStatsProviderCaptor =
-            ArgumentCaptor.forClass(ITetheringStatsProvider.Stub.class);
+    private final ArgumentCaptor<OffloadController.OffloadTetheringStatsProvider>
+            mTetherStatsProviderCaptor =
+            ArgumentCaptor.forClass(OffloadController.OffloadTetheringStatsProvider.class);
     private final ArgumentCaptor<OffloadHardwareInterface.ControlCallback> mControlCallbackCaptor =
             ArgumentCaptor.forClass(OffloadHardwareInterface.ControlCallback.class);
     private MockContentResolver mContentResolver;
@@ -114,6 +126,8 @@
         mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
         when(mContext.getContentResolver()).thenReturn(mContentResolver);
         FakeSettingsProvider.clearSettingsProvider();
+        when(mStatsManager.registerNetworkStatsProvider(anyString(), any()))
+                .thenReturn(mTetherStatsProviderCb);
     }
 
     @After public void tearDown() throws Exception {
@@ -139,9 +153,9 @@
 
     private OffloadController makeOffloadController() throws Exception {
         OffloadController offload = new OffloadController(new Handler(Looper.getMainLooper()),
-                mHardware, mContentResolver, mNMService, new SharedLog("test"));
-        verify(mNMService).registerTetheringStatsProvider(
-                mTetherStatsProviderCaptor.capture(), anyString());
+                mHardware, mContentResolver, mStatsManager, new SharedLog("test"));
+        verify(mStatsManager).registerNetworkStatsProvider(anyString(),
+                mTetherStatsProviderCaptor.capture());
         return offload;
     }
 
@@ -384,12 +398,11 @@
         inOrder.verifyNoMoreInteractions();
     }
 
-    private void assertNetworkStats(String iface, ForwardedStats stats, NetworkStats.Entry entry) {
-        assertEquals(iface, entry.iface);
-        assertEquals(stats.rxBytes, entry.rxBytes);
-        assertEquals(stats.txBytes, entry.txBytes);
-        assertEquals(SET_DEFAULT, entry.set);
-        assertEquals(TAG_NONE, entry.tag);
+    private static @NonNull Entry buildTestEntry(@NonNull OffloadController.StatsType how,
+            @NonNull String iface, long rxBytes, long txBytes) {
+        return new Entry(iface, how == STATS_PER_IFACE ? UID_ALL : UID_TETHERING, SET_DEFAULT,
+                TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, rxBytes, 0L,
+                txBytes, 0L, 0L);
     }
 
     @Test
@@ -400,19 +413,16 @@
         final OffloadController offload = makeOffloadController();
         offload.start();
 
+        final OffloadController.OffloadTetheringStatsProvider provider =
+                mTetherStatsProviderCaptor.getValue();
+
         final String ethernetIface = "eth1";
         final String mobileIface = "rmnet_data0";
 
-        ForwardedStats ethernetStats = new ForwardedStats();
-        ethernetStats.rxBytes = 12345;
-        ethernetStats.txBytes = 54321;
-
-        ForwardedStats mobileStats = new ForwardedStats();
-        mobileStats.rxBytes = 999;
-        mobileStats.txBytes = 99999;
-
-        when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(ethernetStats);
-        when(mHardware.getForwardedStats(eq(mobileIface))).thenReturn(mobileStats);
+        when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(
+                new ForwardedStats(12345, 54321));
+        when(mHardware.getForwardedStats(eq(mobileIface))).thenReturn(
+                new ForwardedStats(999, 99999));
 
         InOrder inOrder = inOrder(mHardware);
 
@@ -432,10 +442,35 @@
         // Expect that we fetch stats from the previous upstream.
         inOrder.verify(mHardware, times(1)).getForwardedStats(eq(mobileIface));
 
-        ethernetStats = new ForwardedStats();
-        ethernetStats.rxBytes = 100000;
-        ethernetStats.txBytes = 100000;
-        when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(ethernetStats);
+        // Verify that the fetched stats are stored.
+        final NetworkStats ifaceStats = provider.getTetherStats(STATS_PER_IFACE);
+        final NetworkStats uidStats = provider.getTetherStats(STATS_PER_UID);
+        final NetworkStats expectedIfaceStats = new NetworkStats(0L, 2)
+                .addValues(buildTestEntry(STATS_PER_IFACE, mobileIface, 999, 99999))
+                .addValues(buildTestEntry(STATS_PER_IFACE, ethernetIface, 12345, 54321));
+
+        final NetworkStats expectedUidStats = new NetworkStats(0L, 2)
+                .addValues(buildTestEntry(STATS_PER_UID, mobileIface, 999, 99999))
+                .addValues(buildTestEntry(STATS_PER_UID, ethernetIface, 12345, 54321));
+
+        assertTrue(orderInsensitiveEquals(expectedIfaceStats, ifaceStats));
+        assertTrue(orderInsensitiveEquals(expectedUidStats, uidStats));
+
+        final ArgumentCaptor<NetworkStats> ifaceStatsCaptor = ArgumentCaptor.forClass(
+                NetworkStats.class);
+        final ArgumentCaptor<NetworkStats> uidStatsCaptor = ArgumentCaptor.forClass(
+                NetworkStats.class);
+
+        // Force pushing stats update to verify the stats reported.
+        provider.pushTetherStats();
+        verify(mTetherStatsProviderCb, times(1)).onStatsUpdated(anyInt(),
+                ifaceStatsCaptor.capture(), uidStatsCaptor.capture());
+        assertTrue(orderInsensitiveEquals(expectedIfaceStats, ifaceStatsCaptor.getValue()));
+        assertTrue(orderInsensitiveEquals(expectedUidStats, uidStatsCaptor.getValue()));
+
+
+        when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(
+                new ForwardedStats(100000, 100000));
         offload.setUpstreamLinkProperties(null);
         // Expect that we first clear the HAL's upstream parameters.
         inOrder.verify(mHardware, times(1)).setUpstreamParameters(
@@ -443,37 +478,38 @@
         // Expect that we fetch stats from the previous upstream.
         inOrder.verify(mHardware, times(1)).getForwardedStats(eq(ethernetIface));
 
-        ITetheringStatsProvider provider = mTetherStatsProviderCaptor.getValue();
-        NetworkStats stats = provider.getTetherStats(STATS_PER_IFACE);
-        NetworkStats perUidStats = provider.getTetherStats(STATS_PER_UID);
-        waitForIdle();
         // There is no current upstream, so no stats are fetched.
         inOrder.verify(mHardware, never()).getForwardedStats(any());
         inOrder.verifyNoMoreInteractions();
 
-        assertEquals(2, stats.size());
-        assertEquals(2, perUidStats.size());
+        // Verify that the stored stats is accumulated.
+        final NetworkStats ifaceStatsAccu = provider.getTetherStats(STATS_PER_IFACE);
+        final NetworkStats uidStatsAccu = provider.getTetherStats(STATS_PER_UID);
+        final NetworkStats expectedIfaceStatsAccu = new NetworkStats(0L, 2)
+                .addValues(buildTestEntry(STATS_PER_IFACE, mobileIface, 999, 99999))
+                .addValues(buildTestEntry(STATS_PER_IFACE, ethernetIface, 112345, 154321));
 
-        NetworkStats.Entry entry = null;
-        for (int i = 0; i < stats.size(); i++) {
-            assertEquals(UID_ALL, stats.getValues(i, entry).uid);
-            assertEquals(UID_TETHERING, perUidStats.getValues(i, entry).uid);
-        }
+        final NetworkStats expectedUidStatsAccu = new NetworkStats(0L, 2)
+                .addValues(buildTestEntry(STATS_PER_UID, mobileIface, 999, 99999))
+                .addValues(buildTestEntry(STATS_PER_UID, ethernetIface, 112345, 154321));
 
-        int ethernetPosition = ethernetIface.equals(stats.getValues(0, entry).iface) ? 0 : 1;
-        int mobilePosition = 1 - ethernetPosition;
+        assertTrue(orderInsensitiveEquals(expectedIfaceStatsAccu, ifaceStatsAccu));
+        assertTrue(orderInsensitiveEquals(expectedUidStatsAccu, uidStatsAccu));
 
-        entry = stats.getValues(mobilePosition, entry);
-        assertNetworkStats(mobileIface, mobileStats, entry);
-        entry = perUidStats.getValues(mobilePosition, entry);
-        assertNetworkStats(mobileIface, mobileStats, entry);
+        // Verify that only diff of stats is reported.
+        reset(mTetherStatsProviderCb);
+        provider.pushTetherStats();
+        final NetworkStats expectedIfaceStatsDiff = new NetworkStats(0L, 2)
+                .addValues(buildTestEntry(STATS_PER_IFACE, mobileIface, 0, 0))
+                .addValues(buildTestEntry(STATS_PER_IFACE, ethernetIface, 100000, 100000));
 
-        ethernetStats.rxBytes = 12345 + 100000;
-        ethernetStats.txBytes = 54321 + 100000;
-        entry = stats.getValues(ethernetPosition, entry);
-        assertNetworkStats(ethernetIface, ethernetStats, entry);
-        entry = perUidStats.getValues(ethernetPosition, entry);
-        assertNetworkStats(ethernetIface, ethernetStats, entry);
+        final NetworkStats expectedUidStatsDiff = new NetworkStats(0L, 2)
+                .addValues(buildTestEntry(STATS_PER_UID, mobileIface, 0, 0))
+                .addValues(buildTestEntry(STATS_PER_UID, ethernetIface, 100000, 100000));
+        verify(mTetherStatsProviderCb, times(1)).onStatsUpdated(anyInt(),
+                ifaceStatsCaptor.capture(), uidStatsCaptor.capture());
+        assertTrue(orderInsensitiveEquals(expectedIfaceStatsDiff, ifaceStatsCaptor.getValue()));
+        assertTrue(orderInsensitiveEquals(expectedUidStatsDiff, uidStatsCaptor.getValue()));
     }
 
     @Test
@@ -493,19 +529,19 @@
         lp.setInterfaceName(ethernetIface);
         offload.setUpstreamLinkProperties(lp);
 
-        ITetheringStatsProvider provider = mTetherStatsProviderCaptor.getValue();
+        AbstractNetworkStatsProvider provider = mTetherStatsProviderCaptor.getValue();
         final InOrder inOrder = inOrder(mHardware);
         when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(true);
         when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(true);
 
         // Applying an interface quota to the current upstream immediately sends it to the hardware.
-        provider.setInterfaceQuota(ethernetIface, ethernetLimit);
+        provider.setLimit(ethernetIface, ethernetLimit);
         waitForIdle();
         inOrder.verify(mHardware).setDataLimit(ethernetIface, ethernetLimit);
         inOrder.verifyNoMoreInteractions();
 
         // Applying an interface quota to another upstream does not take any immediate action.
-        provider.setInterfaceQuota(mobileIface, mobileLimit);
+        provider.setLimit(mobileIface, mobileLimit);
         waitForIdle();
         inOrder.verify(mHardware, never()).setDataLimit(anyString(), anyLong());
 
@@ -518,7 +554,7 @@
 
         // Setting a limit of ITetheringStatsProvider.QUOTA_UNLIMITED causes the limit to be set
         // to Long.MAX_VALUE.
-        provider.setInterfaceQuota(mobileIface, ITetheringStatsProvider.QUOTA_UNLIMITED);
+        provider.setLimit(mobileIface, ITetheringStatsProvider.QUOTA_UNLIMITED);
         waitForIdle();
         inOrder.verify(mHardware).setDataLimit(mobileIface, Long.MAX_VALUE);
 
@@ -526,7 +562,7 @@
         when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(false);
         lp.setInterfaceName(ethernetIface);
         offload.setUpstreamLinkProperties(lp);
-        provider.setInterfaceQuota(mobileIface, mobileLimit);
+        provider.setLimit(mobileIface, mobileLimit);
         waitForIdle();
         inOrder.verify(mHardware, never()).setDataLimit(anyString(), anyLong());
 
@@ -535,7 +571,7 @@
         when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(false);
         lp.setInterfaceName(mobileIface);
         offload.setUpstreamLinkProperties(lp);
-        provider.setInterfaceQuota(mobileIface, mobileLimit);
+        provider.setLimit(mobileIface, mobileLimit);
         waitForIdle();
         inOrder.verify(mHardware).getForwardedStats(ethernetIface);
         inOrder.verify(mHardware).stopOffloadControl();
@@ -551,7 +587,7 @@
 
         OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue();
         callback.onStoppedLimitReached();
-        verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue());
+        verify(mTetherStatsProviderCb, times(1)).onStatsUpdated(anyInt(), any(), any());
     }
 
     @Test
@@ -654,9 +690,10 @@
         // Verify forwarded stats behaviour.
         verify(mHardware, times(1)).getForwardedStats(eq(RMNET0));
         verify(mHardware, times(1)).getForwardedStats(eq(WLAN0));
+        // TODO: verify the exact stats reported.
+        verify(mTetherStatsProviderCb, times(1)).onStatsUpdated(anyInt(), any(), any());
+        verifyNoMoreInteractions(mTetherStatsProviderCb);
         verifyNoMoreInteractions(mHardware);
-        verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue());
-        verifyNoMoreInteractions(mNMService);
     }
 
     @Test
@@ -719,8 +756,8 @@
         // Verify forwarded stats behaviour.
         verify(mHardware, times(1)).getForwardedStats(eq(RMNET0));
         verify(mHardware, times(1)).getForwardedStats(eq(WLAN0));
-        verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue());
-        verifyNoMoreInteractions(mNMService);
+        verify(mTetherStatsProviderCb, times(1)).onStatsUpdated(anyInt(), any(), any());
+        verifyNoMoreInteractions(mTetherStatsProviderCb);
 
         // TODO: verify local prefixes and downstreams are also pushed to the HAL.
         verify(mHardware, times(1)).setLocalPrefixes(mStringArrayCaptor.capture());
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
index 7799da4..3635964 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
@@ -21,40 +21,38 @@
 import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
 import static android.net.ConnectivityManager.TYPE_WIFI;
-import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
-import static com.android.internal.R.array.config_mobile_hotspot_provision_app;
-import static com.android.internal.R.array.config_tether_bluetooth_regexs;
-import static com.android.internal.R.array.config_tether_dhcp_range;
-import static com.android.internal.R.array.config_tether_upstream_types;
-import static com.android.internal.R.array.config_tether_usb_regexs;
-import static com.android.internal.R.array.config_tether_wifi_regexs;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.when;
 
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
 import android.net.util.SharedLog;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
 import android.telephony.TelephonyManager;
-import android.test.mock.MockContentResolver;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.test.BroadcastInterceptingContext;
-import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.networkstack.tethering.R;
 
+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 org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
 
 import java.util.Arrays;
 import java.util.Iterator;
@@ -69,9 +67,10 @@
     @Mock private TelephonyManager mTelephonyManager;
     @Mock private Resources mResources;
     @Mock private Resources mResourcesForSubId;
-    private MockContentResolver mContentResolver;
     private Context mMockContext;
     private boolean mHasTelephonyManager;
+    private boolean mEnableLegacyDhcpServer;
+    private MockitoSession mMockingSession;
 
     private class MockTetheringConfiguration extends TetheringConfiguration {
         MockTetheringConfiguration(Context ctx, SharedLog log, int id) {
@@ -101,32 +100,44 @@
             }
             return super.getSystemService(name);
         }
-
-        @Override
-        public ContentResolver getContentResolver() {
-            return mContentResolver;
-        }
     }
 
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        when(mResources.getStringArray(config_tether_dhcp_range)).thenReturn(new String[0]);
-        when(mResources.getStringArray(config_tether_usb_regexs)).thenReturn(new String[0]);
-        when(mResources.getStringArray(config_tether_wifi_regexs))
+        // TODO: use a dependencies class instead of mock statics.
+        mMockingSession = mockitoSession()
+                .initMocks(this)
+                .mockStatic(DeviceConfig.class)
+                .strictness(Strictness.WARN)
+                .startMocking();
+        doReturn(false).when(
+                () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
+                eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
+
+        when(mResources.getStringArray(R.array.config_tether_dhcp_range)).thenReturn(
+                new String[0]);
+        when(mResources.getStringArray(R.array.config_tether_usb_regexs)).thenReturn(new String[0]);
+        when(mResources.getStringArray(R.array.config_tether_wifi_regexs))
                 .thenReturn(new String[]{ "test_wlan\\d" });
-        when(mResources.getStringArray(config_tether_bluetooth_regexs)).thenReturn(new String[0]);
-        when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(new int[0]);
-        when(mResources.getStringArray(config_mobile_hotspot_provision_app))
+        when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs)).thenReturn(
+                new String[0]);
+        when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(new int[0]);
+        when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app))
                 .thenReturn(new String[0]);
-        mContentResolver = new MockContentResolver();
-        mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+        when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+                false);
         mHasTelephonyManager = true;
         mMockContext = new MockContext(mContext);
+        mEnableLegacyDhcpServer = false;
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mMockingSession.finishMocking();
     }
 
     private TetheringConfiguration getTetheringConfiguration(int... legacyTetherUpstreamTypes) {
-        when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(
+        when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(
                 legacyTetherUpstreamTypes);
         return new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
     }
@@ -210,7 +221,7 @@
 
     @Test
     public void testNoDefinedUpstreamTypesAddsEthernet() {
-        when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(new int[]{});
+        when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(new int[]{});
         when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
 
         final TetheringConfiguration cfg = new TetheringConfiguration(
@@ -232,7 +243,7 @@
 
     @Test
     public void testDefinedUpstreamTypesSansEthernetAddsEthernet() {
-        when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(
+        when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(
                 new int[]{TYPE_WIFI, TYPE_MOBILE_HIPRI});
         when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
 
@@ -250,7 +261,7 @@
 
     @Test
     public void testDefinedUpstreamTypesWithEthernetDoesNotAddEthernet() {
-        when(mResources.getIntArray(config_tether_upstream_types))
+        when(mResources.getIntArray(R.array.config_tether_upstream_types))
                 .thenReturn(new int[]{TYPE_WIFI, TYPE_ETHERNET, TYPE_MOBILE_HIPRI});
         when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
 
@@ -268,19 +279,38 @@
 
     @Test
     public void testNewDhcpServerDisabled() {
-        Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 1);
+        when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+                true);
+        doReturn(false).when(
+                () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
+                eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
 
-        final TetheringConfiguration cfg = new TetheringConfiguration(
-                mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
-        assertTrue(cfg.enableLegacyDhcpServer);
+        final TetheringConfiguration enableByRes =
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+        assertTrue(enableByRes.enableLegacyDhcpServer);
+
+        when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+                false);
+        doReturn(true).when(
+                () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
+                eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
+
+        final TetheringConfiguration enableByDevConfig =
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+        assertTrue(enableByDevConfig.enableLegacyDhcpServer);
     }
 
     @Test
     public void testNewDhcpServerEnabled() {
-        Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0);
+        when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+                false);
+        doReturn(false).when(
+                () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
+                eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
 
-        final TetheringConfiguration cfg = new TetheringConfiguration(
-                mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+        final TetheringConfiguration cfg =
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+
         assertFalse(cfg.enableLegacyDhcpServer);
     }
 
@@ -299,16 +329,17 @@
 
     private void setUpResourceForSubId() {
         when(mResourcesForSubId.getStringArray(
-                config_tether_dhcp_range)).thenReturn(new String[0]);
+                R.array.config_tether_dhcp_range)).thenReturn(new String[0]);
         when(mResourcesForSubId.getStringArray(
-                config_tether_usb_regexs)).thenReturn(new String[0]);
+                R.array.config_tether_usb_regexs)).thenReturn(new String[0]);
         when(mResourcesForSubId.getStringArray(
-                config_tether_wifi_regexs)).thenReturn(new String[]{ "test_wlan\\d" });
+                R.array.config_tether_wifi_regexs)).thenReturn(new String[]{ "test_wlan\\d" });
         when(mResourcesForSubId.getStringArray(
-                config_tether_bluetooth_regexs)).thenReturn(new String[0]);
-        when(mResourcesForSubId.getIntArray(config_tether_upstream_types)).thenReturn(new int[0]);
+                R.array.config_tether_bluetooth_regexs)).thenReturn(new String[0]);
+        when(mResourcesForSubId.getIntArray(R.array.config_tether_upstream_types)).thenReturn(
+                new int[0]);
         when(mResourcesForSubId.getStringArray(
-                config_mobile_hotspot_provision_app)).thenReturn(PROVISIONING_APP_NAME);
+                R.array.config_mobile_hotspot_provision_app)).thenReturn(PROVISIONING_APP_NAME);
     }
 
 }
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
index 04b2eb4..9f0d876 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
@@ -19,16 +19,18 @@
 import static android.hardware.usb.UsbManager.USB_CONFIGURED;
 import static android.hardware.usb.UsbManager.USB_CONNECTED;
 import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS;
-import static android.net.ConnectivityManager.ACTION_TETHER_STATE_CHANGED;
-import static android.net.ConnectivityManager.EXTRA_ACTIVE_LOCAL_ONLY;
-import static android.net.ConnectivityManager.EXTRA_ACTIVE_TETHER;
-import static android.net.ConnectivityManager.EXTRA_AVAILABLE_TETHER;
-import static android.net.ConnectivityManager.TETHERING_USB;
-import static android.net.ConnectivityManager.TETHERING_WIFI;
-import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
-import static android.net.ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
-import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
+import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
+import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
+import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
 import static android.net.RouteInfo.RTN_UNICAST;
+import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED;
+import static android.net.TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY;
+import static android.net.TetheringManager.EXTRA_ACTIVE_TETHER;
+import static android.net.TetheringManager.EXTRA_AVAILABLE_TETHER;
+import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE;
 import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE;
@@ -36,7 +38,6 @@
 import static android.net.wifi.WifiManager.IFACE_IP_MODE_LOCAL_ONLY;
 import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED;
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
-import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -50,10 +51,10 @@
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
@@ -61,6 +62,8 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import android.app.usage.NetworkStatsManager;
+import android.bluetooth.BluetoothAdapter;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -69,22 +72,21 @@
 import android.content.pm.ApplicationInfo;
 import android.content.res.Resources;
 import android.hardware.usb.UsbManager;
+import android.net.ConnectivityManager;
 import android.net.INetd;
-import android.net.INetworkPolicyManager;
-import android.net.INetworkStatsService;
 import android.net.ITetheringEventCallback;
 import android.net.InetAddresses;
-import android.net.InterfaceConfiguration;
+import android.net.InterfaceConfigurationParcel;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.MacAddress;
 import android.net.Network;
 import android.net.NetworkCapabilities;
-import android.net.NetworkInfo;
 import android.net.NetworkRequest;
 import android.net.RouteInfo;
 import android.net.TetherStatesParcel;
+import android.net.TetheringCallbackStartedParcel;
 import android.net.TetheringConfigurationParcel;
 import android.net.dhcp.DhcpServerCallbacks;
 import android.net.dhcp.DhcpServingParamsParcel;
@@ -101,7 +103,6 @@
 import android.net.wifi.p2p.WifiP2pManager;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.INetworkManagementService;
 import android.os.Looper;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
@@ -122,6 +123,7 @@
 import com.android.internal.util.StateMachine;
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.networkstack.tethering.R;
 
 import org.junit.After;
 import org.junit.Before;
@@ -135,6 +137,7 @@
 import java.net.Inet6Address;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Vector;
 
 @RunWith(AndroidJUnit4.class)
@@ -147,14 +150,13 @@
     private static final String TEST_USB_IFNAME = "test_rndis0";
     private static final String TEST_WLAN_IFNAME = "test_wlan0";
     private static final String TEST_P2P_IFNAME = "test_p2p-p2p0-0";
+    private static final String TETHERING_NAME = "Tethering";
 
     private static final int DHCPSERVER_START_TIMEOUT_MS = 1000;
 
     @Mock private ApplicationInfo mApplicationInfo;
     @Mock private Context mContext;
-    @Mock private INetworkManagementService mNMService;
-    @Mock private INetworkStatsService mStatsService;
-    @Mock private INetworkPolicyManager mPolicyManager;
+    @Mock private NetworkStatsManager mStatsManager;
     @Mock private OffloadHardwareInterface mOffloadHardwareInterface;
     @Mock private Resources mResources;
     @Mock private TelephonyManager mTelephonyManager;
@@ -168,6 +170,7 @@
     @Mock private INetd mNetd;
     @Mock private UserManager mUserManager;
     @Mock private NetworkRequest mNetworkRequest;
+    @Mock private ConnectivityManager mCm;
 
     private final MockIpServerDependencies mIpServerDependencies =
             spy(new MockIpServerDependencies());
@@ -185,6 +188,7 @@
     private BroadcastReceiver mBroadcastReceiver;
     private Tethering mTethering;
     private PhoneStateListener mPhoneStateListener;
+    private InterfaceConfigurationParcel mInterfaceConfiguration;
 
     private class TestContext extends BroadcastInterceptingContext {
         TestContext(Context base) {
@@ -217,6 +221,8 @@
             if (Context.USB_SERVICE.equals(name)) return mUsbManager;
             if (Context.TELEPHONY_SERVICE.equals(name)) return mTelephonyManager;
             if (Context.USER_SERVICE.equals(name)) return mUserManager;
+            if (Context.NETWORK_STATS_SERVICE.equals(name)) return mStatsManager;
+            if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm;
             return super.getSystemService(name);
         }
 
@@ -225,6 +231,11 @@
             if (TelephonyManager.class.equals(serviceClass)) return Context.TELEPHONY_SERVICE;
             return super.getSystemServiceName(serviceClass);
         }
+
+        @Override
+        public Context createContextAsUser(UserHandle user, int flags) {
+            return mContext;
+        }
     }
 
     public class MockIpServerDependencies extends IpServer.Dependencies {
@@ -248,11 +259,6 @@
         }
 
         @Override
-        public INetd getNetdService() {
-            return mNetd;
-        }
-
-        @Override
         public void makeDhcpServer(String ifName, DhcpServingParamsParcel params,
                 DhcpServerCallbacks cb) {
             new Thread(() -> {
@@ -271,6 +277,11 @@
         }
 
         @Override
+        protected boolean getDeviceConfigBoolean(final String name) {
+            return false;
+        }
+
+        @Override
         protected Resources getResourcesForSubIdWrapper(Context ctx, int subId) {
             return mResources;
         }
@@ -329,21 +340,6 @@
         }
 
         @Override
-        public INetworkManagementService getINetworkManagementService() {
-            return mNMService;
-        }
-
-        @Override
-        public INetworkStatsService getINetworkStatsService() {
-            return mStatsService;
-        }
-
-        @Override
-        public INetworkPolicyManager getINetworkPolicyManager() {
-            return mPolicyManager;
-        }
-
-        @Override
         public INetd getINetd(Context context) {
             return mNetd;
         }
@@ -357,6 +353,12 @@
         public Context getContext() {
             return mServiceContext;
         }
+
+        @Override
+        public BluetoothAdapter getBluetoothAdapter() {
+            // TODO: add test for bluetooth tethering.
+            return null;
+        }
     }
 
     private static UpstreamNetworkState buildMobileUpstreamState(boolean withIPv4,
@@ -415,32 +417,33 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        when(mResources.getStringArray(com.android.internal.R.array.config_tether_dhcp_range))
+        when(mResources.getStringArray(R.array.config_tether_dhcp_range))
                 .thenReturn(new String[0]);
-        when(mResources.getStringArray(com.android.internal.R.array.config_tether_usb_regexs))
+        when(mResources.getStringArray(R.array.config_tether_usb_regexs))
                 .thenReturn(new String[] { "test_rndis\\d" });
-        when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_regexs))
+        when(mResources.getStringArray(R.array.config_tether_wifi_regexs))
                 .thenReturn(new String[]{ "test_wlan\\d" });
-        when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_p2p_regexs))
+        when(mResources.getStringArray(R.array.config_tether_wifi_p2p_regexs))
                 .thenReturn(new String[]{ "test_p2p-p2p\\d-.*" });
-        when(mResources.getStringArray(com.android.internal.R.array.config_tether_bluetooth_regexs))
+        when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
                 .thenReturn(new String[0]);
-        when(mResources.getIntArray(com.android.internal.R.array.config_tether_upstream_types))
-                .thenReturn(new int[0]);
-        when(mResources.getBoolean(com.android.internal.R.bool.config_tether_upstream_automatic))
-                .thenReturn(false);
-        when(mNMService.listInterfaces())
+        when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(new int[0]);
+        when(mResources.getBoolean(R.bool.config_tether_upstream_automatic)).thenReturn(false);
+        when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+                false);
+        when(mNetd.interfaceGetList())
                 .thenReturn(new String[] {
                         TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_USB_IFNAME, TEST_P2P_IFNAME});
-        when(mNMService.getInterfaceConfig(anyString()))
-                .thenReturn(new InterfaceConfiguration());
+        when(mResources.getString(R.string.config_wifi_tether_enable)).thenReturn("");
+        mInterfaceConfiguration = new InterfaceConfigurationParcel();
+        mInterfaceConfiguration.flags = new String[0];
         when(mRouterAdvertisementDaemon.start())
                 .thenReturn(true);
 
         mServiceContext = new TestContext(mContext);
+        when(mContext.getSystemService(Context.NOTIFICATION_SERVICE)).thenReturn(null);
         mContentResolver = new MockContentResolver(mServiceContext);
         mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
-        Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0);
         mIntents = new Vector<>();
         mBroadcastReceiver = new BroadcastReceiver() {
             @Override
@@ -451,7 +454,7 @@
         mServiceContext.registerReceiver(mBroadcastReceiver,
                 new IntentFilter(ACTION_TETHER_STATE_CHANGED));
         mTethering = makeTethering();
-        verify(mNMService).registerTetheringStatsProvider(any(), anyString());
+        verify(mStatsManager, times(1)).registerNetworkStatsProvider(anyString(), any());
         verify(mNetd).registerUnsolicitedEventListener(any());
         final ArgumentCaptor<PhoneStateListener> phoneListenerCaptor =
                 ArgumentCaptor.forClass(PhoneStateListener.class);
@@ -491,20 +494,21 @@
 
     private void sendWifiP2pConnectionChanged(
             boolean isGroupFormed, boolean isGroupOwner, String ifname) {
+        WifiP2pGroup group = null;
         WifiP2pInfo p2pInfo = new WifiP2pInfo();
         p2pInfo.groupFormed = isGroupFormed;
-        p2pInfo.isGroupOwner = isGroupOwner;
+        if (isGroupFormed) {
+            p2pInfo.isGroupOwner = isGroupOwner;
+            group = mock(WifiP2pGroup.class);
+            when(group.isGroupOwner()).thenReturn(isGroupOwner);
+            when(group.getInterface()).thenReturn(ifname);
+        }
 
-        NetworkInfo networkInfo = new NetworkInfo(TYPE_WIFI_P2P, 0, null, null);
+        final Intent intent = mock(Intent.class);
+        when(intent.getAction()).thenReturn(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
+        when(intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO)).thenReturn(p2pInfo);
+        when(intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP)).thenReturn(group);
 
-        WifiP2pGroup group = new WifiP2pGroup();
-        group.setIsGroupOwner(isGroupOwner);
-        group.setInterface(ifname);
-
-        final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
-        intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO, p2pInfo);
-        intent.putExtra(WifiP2pManager.EXTRA_NETWORK_INFO, networkInfo);
-        intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP, group);
         mServiceContext.sendBroadcastAsUserMultiplePermissions(intent, UserHandle.ALL,
                 P2P_RECEIVER_PERMISSIONS_FOR_BROADCAST);
     }
@@ -523,10 +527,11 @@
     }
 
     private void verifyInterfaceServingModeStarted(String ifname) throws Exception {
-        verify(mNMService, times(1)).getInterfaceConfig(ifname);
-        verify(mNMService, times(1))
-                .setInterfaceConfig(eq(ifname), any(InterfaceConfiguration.class));
-        verify(mNMService, times(1)).tetherInterface(ifname);
+        verify(mNetd, times(1)).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
+        verify(mNetd, times(1)).tetherInterfaceAdd(ifname);
+        verify(mNetd, times(1)).networkAddInterface(INetd.LOCAL_NET_ID, ifname);
+        verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(ifname),
+                anyString(), anyString());
     }
 
     private void verifyTetheringBroadcast(String ifname, String whichExtra) {
@@ -558,7 +563,7 @@
             verify(mWifiManager).updateInterfaceIpState(
                     TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
         }
-        verifyNoMoreInteractions(mNMService);
+        verifyNoMoreInteractions(mNetd);
         verifyNoMoreInteractions(mWifiManager);
     }
 
@@ -581,14 +586,14 @@
         prepareUsbTethering(upstreamState);
 
         // This should produce no activity of any kind.
-        verifyNoMoreInteractions(mNMService);
+        verifyNoMoreInteractions(mNetd);
 
         // Pretend we then receive USB configured broadcast.
         sendUsbBroadcast(true, true, true);
         mLooper.dispatchAll();
         // Now we should see the start of tethering mechanics (in this case:
         // tetherMatchingInterfaces() which starts by fetching all interfaces).
-        verify(mNMService, times(1)).listInterfaces();
+        verify(mNetd, times(1)).interfaceGetList();
 
         // UpstreamNetworkMonitor should receive selected upstream
         verify(mUpstreamNetworkMonitor, times(1)).selectPreferredUpstreamType(any());
@@ -618,9 +623,9 @@
 
         verifyInterfaceServingModeStarted(TEST_WLAN_IFNAME);
         verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
-        verify(mNMService, times(1)).setIpForwardingEnabled(true);
-        verify(mNMService, times(1)).startTethering(any(String[].class));
-        verifyNoMoreInteractions(mNMService);
+        verify(mNetd, times(1)).ipfwdEnableForwarding(TETHERING_NAME);
+        verify(mNetd, times(1)).tetherStartWithConfiguration(any());
+        verifyNoMoreInteractions(mNetd);
         verify(mWifiManager).updateInterfaceIpState(
                 TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
         verify(mWifiManager).updateInterfaceIpState(
@@ -638,16 +643,16 @@
         mTethering.interfaceRemoved(TEST_WLAN_IFNAME);
         mLooper.dispatchAll();
 
-        verify(mNMService, times(1)).untetherInterface(TEST_WLAN_IFNAME);
-        // {g,s}etInterfaceConfig() called twice for enabling and disabling IPv4.
-        verify(mNMService, times(2)).getInterfaceConfig(TEST_WLAN_IFNAME);
-        verify(mNMService, times(2))
-                .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class));
-        verify(mNMService, times(1)).stopTethering();
-        verify(mNMService, times(1)).setIpForwardingEnabled(false);
+        verify(mNetd, times(1)).tetherApplyDnsInterfaces();
+        verify(mNetd, times(1)).tetherInterfaceRemove(TEST_WLAN_IFNAME);
+        verify(mNetd, times(1)).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_WLAN_IFNAME);
+        // interfaceSetCfg() called once for enabling and twice disabling IPv4.
+        verify(mNetd, times(3)).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
+        verify(mNetd, times(1)).tetherStop();
+        verify(mNetd, times(1)).ipfwdDisableForwarding(TETHERING_NAME);
         verify(mWifiManager, times(3)).updateInterfaceIpState(
                 TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
-        verifyNoMoreInteractions(mNMService);
+        verifyNoMoreInteractions(mNetd);
         verifyNoMoreInteractions(mWifiManager);
         // Asking for the last error after the per-interface state machine
         // has been reaped yields an unknown interface error.
@@ -684,8 +689,8 @@
         UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
         runUsbTethering(upstreamState);
 
-        verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
-        verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+        verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+        verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
 
         sendIPv6TetherUpdates(upstreamState);
         verify(mRouterAdvertisementDaemon, never()).buildNewRa(any(), notNull());
@@ -694,7 +699,8 @@
 
     @Test
     public void workingMobileUsbTethering_IPv4LegacyDhcp() {
-        Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 1);
+        when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+                true);
         sendConfigurationChanged();
         final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
         runUsbTethering(upstreamState);
@@ -708,8 +714,8 @@
         UpstreamNetworkState upstreamState = buildMobileIPv6UpstreamState();
         runUsbTethering(upstreamState);
 
-        verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
-        verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+        verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+        verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
 
         sendIPv6TetherUpdates(upstreamState);
         verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull());
@@ -721,8 +727,8 @@
         UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
         runUsbTethering(upstreamState);
 
-        verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
-        verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+        verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+        verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
         verify(mRouterAdvertisementDaemon, times(1)).start();
         verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
 
@@ -736,12 +742,11 @@
         UpstreamNetworkState upstreamState = buildMobile464xlatUpstreamState();
         runUsbTethering(upstreamState);
 
-        verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
-        verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+        verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
+        verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
         verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
-        verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
-        verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME,
-                TEST_XLAT_MOBILE_IFNAME);
+        verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
+        verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
 
         sendIPv6TetherUpdates(upstreamState);
         verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull());
@@ -754,9 +759,9 @@
         UpstreamNetworkState upstreamState = buildMobileIPv6UpstreamState();
         runUsbTethering(upstreamState);
 
-        verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+        verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
         verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
-        verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+        verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
 
         // Then 464xlat comes up
         upstreamState = buildMobile464xlatUpstreamState();
@@ -772,20 +777,18 @@
         mLooper.dispatchAll();
 
         // Forwarding is added for 464xlat
-        verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
-        verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME,
-                TEST_XLAT_MOBILE_IFNAME);
+        verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
+        verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
         // Forwarding was not re-added for v6 (still times(1))
-        verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
-        verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+        verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+        verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
         // DHCP not restarted on downstream (still times(1))
         verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
     }
 
     @Test
     public void configTetherUpstreamAutomaticIgnoresConfigTetherUpstreamTypes() throws Exception {
-        when(mResources.getBoolean(com.android.internal.R.bool.config_tether_upstream_automatic))
-                .thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_tether_upstream_automatic)).thenReturn(true);
         sendConfigurationChanged();
 
         // Setup IPv6
@@ -820,7 +823,7 @@
         mLooper.dispatchAll();
         verify(mWifiManager, times(1)).startTetheredHotspot(null);
         verifyNoMoreInteractions(mWifiManager);
-        verifyNoMoreInteractions(mNMService);
+        verifyNoMoreInteractions(mNetd);
 
         // Emulate externally-visible WifiManager effects, causing the
         // per-interface state machine to start up, and telling us that
@@ -833,7 +836,7 @@
         verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
         verify(mWifiManager).updateInterfaceIpState(
                 TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
-        verifyNoMoreInteractions(mNMService);
+        verifyNoMoreInteractions(mNetd);
         verifyNoMoreInteractions(mWifiManager);
     }
 
@@ -847,7 +850,7 @@
         mLooper.dispatchAll();
         verify(mWifiManager, times(1)).startTetheredHotspot(null);
         verifyNoMoreInteractions(mWifiManager);
-        verifyNoMoreInteractions(mNMService);
+        verifyNoMoreInteractions(mNetd);
 
         // Emulate externally-visible WifiManager effects, causing the
         // per-interface state machine to start up, and telling us that
@@ -858,9 +861,11 @@
 
         verifyInterfaceServingModeStarted(TEST_WLAN_IFNAME);
         verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
-        verify(mNMService, times(1)).setIpForwardingEnabled(true);
-        verify(mNMService, times(1)).startTethering(any(String[].class));
-        verifyNoMoreInteractions(mNMService);
+        verify(mNetd, times(1)).ipfwdEnableForwarding(TETHERING_NAME);
+        verify(mNetd, times(1)).tetherStartWithConfiguration(any());
+        verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(TEST_WLAN_IFNAME),
+                anyString(), anyString());
+        verifyNoMoreInteractions(mNetd);
         verify(mWifiManager).updateInterfaceIpState(
                 TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
         verify(mWifiManager).updateInterfaceIpState(
@@ -878,8 +883,8 @@
         /////
         // We do not currently emulate any upstream being found.
         //
-        // This is why there are no calls to verify mNMService.enableNat() or
-        // mNMService.startInterfaceForwarding().
+        // This is why there are no calls to verify mNetd.tetherAddForward() or
+        // mNetd.ipfwdAddInterfaceForward().
         /////
 
         // Emulate pressing the WiFi tethering button.
@@ -887,7 +892,7 @@
         mLooper.dispatchAll();
         verify(mWifiManager, times(1)).stopSoftAp();
         verifyNoMoreInteractions(mWifiManager);
-        verifyNoMoreInteractions(mNMService);
+        verifyNoMoreInteractions(mNetd);
 
         // Emulate externally-visible WifiManager effects, when tethering mode
         // is being torn down.
@@ -895,16 +900,16 @@
         mTethering.interfaceRemoved(TEST_WLAN_IFNAME);
         mLooper.dispatchAll();
 
-        verify(mNMService, times(1)).untetherInterface(TEST_WLAN_IFNAME);
-        // {g,s}etInterfaceConfig() called twice for enabling and disabling IPv4.
-        verify(mNMService, atLeastOnce()).getInterfaceConfig(TEST_WLAN_IFNAME);
-        verify(mNMService, atLeastOnce())
-                .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class));
-        verify(mNMService, times(1)).stopTethering();
-        verify(mNMService, times(1)).setIpForwardingEnabled(false);
+        verify(mNetd, times(1)).tetherApplyDnsInterfaces();
+        verify(mNetd, times(1)).tetherInterfaceRemove(TEST_WLAN_IFNAME);
+        verify(mNetd, times(1)).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_WLAN_IFNAME);
+        // interfaceSetCfg() called once for enabling and twice for disabling IPv4.
+        verify(mNetd, times(3)).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
+        verify(mNetd, times(1)).tetherStop();
+        verify(mNetd, times(1)).ipfwdDisableForwarding(TETHERING_NAME);
         verify(mWifiManager, times(3)).updateInterfaceIpState(
                 TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
-        verifyNoMoreInteractions(mNMService);
+        verifyNoMoreInteractions(mNetd);
         verifyNoMoreInteractions(mWifiManager);
         // Asking for the last error after the per-interface state machine
         // has been reaped yields an unknown interface error.
@@ -915,14 +920,14 @@
     @Test
     public void failureEnablingIpForwarding() throws Exception {
         when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
-        doThrow(new RemoteException()).when(mNMService).setIpForwardingEnabled(true);
+        doThrow(new RemoteException()).when(mNetd).ipfwdEnableForwarding(TETHERING_NAME);
 
         // Emulate pressing the WiFi tethering button.
         mTethering.startTethering(TETHERING_WIFI, null, false);
         mLooper.dispatchAll();
         verify(mWifiManager, times(1)).startTetheredHotspot(null);
         verifyNoMoreInteractions(mWifiManager);
-        verifyNoMoreInteractions(mNMService);
+        verifyNoMoreInteractions(mNetd);
 
         // Emulate externally-visible WifiManager effects, causing the
         // per-interface state machine to start up, and telling us that
@@ -931,15 +936,15 @@
         sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
         mLooper.dispatchAll();
 
-        // We verify get/set called thrice here: twice for setup (on NMService) and once during
-        // teardown (on Netd) because all events happen over the course of the single
+        // We verify get/set called three times here: twice for setup and once during
+        // teardown because all events happen over the course of the single
         // dispatchAll() above. Note that once the IpServer IPv4 address config
         // code is refactored the two calls during shutdown will revert to one.
-        verify(mNMService, times(2)).getInterfaceConfig(TEST_WLAN_IFNAME);
-        verify(mNMService, times(2))
-                .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class));
-        verify(mNetd, times(1)).interfaceSetCfg(argThat(p -> TEST_WLAN_IFNAME.equals(p.ifName)));
-        verify(mNMService, times(1)).tetherInterface(TEST_WLAN_IFNAME);
+        verify(mNetd, times(3)).interfaceSetCfg(argThat(p -> TEST_WLAN_IFNAME.equals(p.ifName)));
+        verify(mNetd, times(1)).tetherInterfaceAdd(TEST_WLAN_IFNAME);
+        verify(mNetd, times(1)).networkAddInterface(INetd.LOCAL_NET_ID, TEST_WLAN_IFNAME);
+        verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(TEST_WLAN_IFNAME),
+                anyString(), anyString());
         verify(mWifiManager).updateInterfaceIpState(
                 TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
         verify(mWifiManager).updateInterfaceIpState(
@@ -949,18 +954,20 @@
         assertEquals(3, mTetheringDependencies.mIsTetheringSupportedCalls);
         verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
         // This is called, but will throw.
-        verify(mNMService, times(1)).setIpForwardingEnabled(true);
+        verify(mNetd, times(1)).ipfwdEnableForwarding(TETHERING_NAME);
         // This never gets called because of the exception thrown above.
-        verify(mNMService, times(0)).startTethering(any(String[].class));
+        verify(mNetd, times(0)).tetherStartWithConfiguration(any());
         // When the master state machine transitions to an error state it tells
         // downstream interfaces, which causes us to tell Wi-Fi about the error
         // so it can take down AP mode.
-        verify(mNMService, times(1)).untetherInterface(TEST_WLAN_IFNAME);
+        verify(mNetd, times(1)).tetherApplyDnsInterfaces();
+        verify(mNetd, times(1)).tetherInterfaceRemove(TEST_WLAN_IFNAME);
+        verify(mNetd, times(1)).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_WLAN_IFNAME);
         verify(mWifiManager).updateInterfaceIpState(
                 TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_CONFIGURATION_ERROR);
 
         verifyNoMoreInteractions(mWifiManager);
-        verifyNoMoreInteractions(mNMService);
+        verifyNoMoreInteractions(mNetd);
     }
 
     private void runUserRestrictionsChange(
@@ -1107,11 +1114,10 @@
         }
 
         @Override
-        public void onCallbackStarted(Network network, TetheringConfigurationParcel config,
-                TetherStatesParcel states) {
-            mActualUpstreams.add(network);
-            mTetheringConfigs.add(config);
-            mTetherStates.add(states);
+        public void onCallbackStarted(TetheringCallbackStartedParcel parcel) {
+            mActualUpstreams.add(parcel.upstreamNetwork);
+            mTetheringConfigs.add(parcel.config);
+            mTetherStates.add(parcel.states);
         }
 
         @Override
@@ -1232,9 +1238,9 @@
 
         verifyInterfaceServingModeStarted(TEST_P2P_IFNAME);
         verifyTetheringBroadcast(TEST_P2P_IFNAME, EXTRA_AVAILABLE_TETHER);
-        verify(mNMService, times(1)).setIpForwardingEnabled(true);
-        verify(mNMService, times(1)).startTethering(any(String[].class));
-        verifyNoMoreInteractions(mNMService);
+        verify(mNetd, times(1)).ipfwdEnableForwarding(TETHERING_NAME);
+        verify(mNetd, times(1)).tetherStartWithConfiguration(any());
+        verifyNoMoreInteractions(mNetd);
         verifyTetheringBroadcast(TEST_P2P_IFNAME, EXTRA_ACTIVE_LOCAL_ONLY);
         verify(mUpstreamNetworkMonitor, times(1)).startObserveAllNetworks();
         // This will be called twice, one is on entering IpServer.STATE_AVAILABLE,
@@ -1249,16 +1255,16 @@
         mTethering.interfaceRemoved(TEST_P2P_IFNAME);
         mLooper.dispatchAll();
 
-        verify(mNMService, times(1)).untetherInterface(TEST_P2P_IFNAME);
-        // {g,s}etInterfaceConfig() called twice for enabling and disabling IPv4.
-        verify(mNMService, times(2)).getInterfaceConfig(TEST_P2P_IFNAME);
-        verify(mNMService, times(2))
-                .setInterfaceConfig(eq(TEST_P2P_IFNAME), any(InterfaceConfiguration.class));
-        verify(mNMService, times(1)).stopTethering();
-        verify(mNMService, times(1)).setIpForwardingEnabled(false);
+        verify(mNetd, times(1)).tetherApplyDnsInterfaces();
+        verify(mNetd, times(1)).tetherInterfaceRemove(TEST_P2P_IFNAME);
+        verify(mNetd, times(1)).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_P2P_IFNAME);
+        // interfaceSetCfg() called once for enabling and twice for disabling IPv4.
+        verify(mNetd, times(3)).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
+        verify(mNetd, times(1)).tetherStop();
+        verify(mNetd, times(1)).ipfwdDisableForwarding(TETHERING_NAME);
         verify(mUpstreamNetworkMonitor, never()).getCurrentPreferredUpstream();
         verify(mUpstreamNetworkMonitor, never()).selectPreferredUpstreamType(any());
-        verifyNoMoreInteractions(mNMService);
+        verifyNoMoreInteractions(mNetd);
         // Asking for the last error after the per-interface state machine
         // has been reaped yields an unknown interface error.
         assertEquals(TETHER_ERROR_UNKNOWN_IFACE, mTethering.getLastTetherError(TEST_P2P_IFNAME));
@@ -1272,12 +1278,11 @@
         sendWifiP2pConnectionChanged(true, false, TEST_P2P_IFNAME);
         mLooper.dispatchAll();
 
-        verify(mNMService, never()).getInterfaceConfig(TEST_P2P_IFNAME);
-        verify(mNMService, never())
-                .setInterfaceConfig(eq(TEST_P2P_IFNAME), any(InterfaceConfiguration.class));
-        verify(mNMService, never()).tetherInterface(TEST_P2P_IFNAME);
-        verify(mNMService, never()).setIpForwardingEnabled(true);
-        verify(mNMService, never()).startTethering(any(String[].class));
+        verify(mNetd, never()).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
+        verify(mNetd, never()).tetherInterfaceAdd(TEST_P2P_IFNAME);
+        verify(mNetd, never()).networkAddInterface(INetd.LOCAL_NET_ID, TEST_P2P_IFNAME);
+        verify(mNetd, never()).ipfwdEnableForwarding(TETHERING_NAME);
+        verify(mNetd, never()).tetherStartWithConfiguration(any());
 
         // Emulate externally-visible WifiP2pManager effects, when wifi p2p group
         // is being removed.
@@ -1285,13 +1290,13 @@
         mTethering.interfaceRemoved(TEST_P2P_IFNAME);
         mLooper.dispatchAll();
 
-        verify(mNMService, never()).untetherInterface(TEST_P2P_IFNAME);
-        verify(mNMService, never()).getInterfaceConfig(TEST_P2P_IFNAME);
-        verify(mNMService, never())
-                .setInterfaceConfig(eq(TEST_P2P_IFNAME), any(InterfaceConfiguration.class));
-        verify(mNMService, never()).stopTethering();
-        verify(mNMService, never()).setIpForwardingEnabled(false);
-        verifyNoMoreInteractions(mNMService);
+        verify(mNetd, never()).tetherApplyDnsInterfaces();
+        verify(mNetd, never()).tetherInterfaceRemove(TEST_P2P_IFNAME);
+        verify(mNetd, never()).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_P2P_IFNAME);
+        verify(mNetd, never()).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
+        verify(mNetd, never()).tetherStop();
+        verify(mNetd, never()).ipfwdDisableForwarding(TETHERING_NAME);
+        verifyNoMoreInteractions(mNetd);
         // Asking for the last error after the per-interface state machine
         // has been reaped yields an unknown interface error.
         assertEquals(TETHER_ERROR_UNKNOWN_IFACE, mTethering.getLastTetherError(TEST_P2P_IFNAME));
@@ -1310,7 +1315,7 @@
     private void workingWifiP2pGroupOwnerLegacyMode(
             boolean emulateInterfaceStatusChanged) throws Exception {
         // change to legacy mode and update tethering information by chaning SIM
-        when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_p2p_regexs))
+        when(mResources.getStringArray(R.array.config_tether_wifi_p2p_regexs))
                 .thenReturn(new String[]{});
         final int fakeSubId = 1234;
         mPhoneStateListener.onActiveDataSubscriptionIdChanged(fakeSubId);
@@ -1321,12 +1326,11 @@
         sendWifiP2pConnectionChanged(true, true, TEST_P2P_IFNAME);
         mLooper.dispatchAll();
 
-        verify(mNMService, never()).getInterfaceConfig(TEST_P2P_IFNAME);
-        verify(mNMService, never())
-                .setInterfaceConfig(eq(TEST_P2P_IFNAME), any(InterfaceConfiguration.class));
-        verify(mNMService, never()).tetherInterface(TEST_P2P_IFNAME);
-        verify(mNMService, never()).setIpForwardingEnabled(true);
-        verify(mNMService, never()).startTethering(any(String[].class));
+        verify(mNetd, never()).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
+        verify(mNetd, never()).tetherInterfaceAdd(TEST_P2P_IFNAME);
+        verify(mNetd, never()).networkAddInterface(INetd.LOCAL_NET_ID, TEST_P2P_IFNAME);
+        verify(mNetd, never()).ipfwdEnableForwarding(TETHERING_NAME);
+        verify(mNetd, never()).tetherStartWithConfiguration(any());
         assertEquals(TETHER_ERROR_UNKNOWN_IFACE, mTethering.getLastTetherError(TEST_P2P_IFNAME));
     }
     @Test
@@ -1349,6 +1353,50 @@
         workingWifiP2pGroupClient(false);
     }
 
+    private void setDataSaverEnabled(boolean enabled) {
+        final Intent intent = new Intent(ACTION_RESTRICT_BACKGROUND_CHANGED);
+        mServiceContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+
+        final int status = enabled ? RESTRICT_BACKGROUND_STATUS_ENABLED
+                : RESTRICT_BACKGROUND_STATUS_DISABLED;
+        when(mCm.getRestrictBackgroundStatus()).thenReturn(status);
+        mLooper.dispatchAll();
+    }
+
+    @Test
+    public void testDataSaverChanged() {
+        // Start Tethering.
+        final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
+        runUsbTethering(upstreamState);
+        assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_USB_IFNAME);
+        // Data saver is ON.
+        setDataSaverEnabled(true);
+        // Verify that tethering should be disabled.
+        verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
+        mTethering.interfaceRemoved(TEST_USB_IFNAME);
+        mLooper.dispatchAll();
+        assertEquals(mTethering.getTetheredIfaces(), new String[0]);
+        reset(mUsbManager);
+
+        runUsbTethering(upstreamState);
+        // Verify that user can start tethering again without turning OFF data saver.
+        assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_USB_IFNAME);
+
+        // If data saver is keep ON with change event, tethering should not be OFF this time.
+        setDataSaverEnabled(true);
+        verify(mUsbManager, times(0)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
+        assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_USB_IFNAME);
+
+        // If data saver is turned OFF, it should not change tethering.
+        setDataSaverEnabled(false);
+        verify(mUsbManager, times(0)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
+        assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_USB_IFNAME);
+    }
+
+    private static <T> void assertContains(Collection<T> collection, T element) {
+        assertTrue(element + " not found in " + collection, collection.contains(element));
+    }
+
     // TODO: Test that a request for hotspot mode doesn't interfere with an
     // already operating tethering mode interface.
 }
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
index c90abbb..5ed75bf 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
@@ -18,13 +18,14 @@
 
 import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
-import static android.net.ConnectivityManager.TYPE_NONE;
 import static android.net.ConnectivityManager.TYPE_WIFI;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
+import static com.android.server.connectivity.tethering.UpstreamNetworkMonitor.TYPE_NONE;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -538,13 +539,15 @@
                 mUNM.selectPreferredUpstreamType(preferredTypes));
         verify(mEntitleMgr, times(1)).maybeRunProvisioning();
     }
+
     private void assertSatisfiesLegacyType(int legacyType, UpstreamNetworkState ns) {
         if (legacyType == TYPE_NONE) {
             assertTrue(ns == null);
             return;
         }
 
-        final NetworkCapabilities nc = ConnectivityManager.networkCapabilitiesForType(legacyType);
+        final NetworkCapabilities nc =
+                UpstreamNetworkMonitor.networkCapabilitiesForType(legacyType);
         assertTrue(nc.satisfiedByNetworkCapabilities(ns.networkCapabilities));
     }
 
diff --git a/packages/services/PacProcessor/jni/Android.bp b/packages/services/PacProcessor/jni/Android.bp
index 2a94237..ab21a76 100644
--- a/packages/services/PacProcessor/jni/Android.bp
+++ b/packages/services/PacProcessor/jni/Android.bp
@@ -37,4 +37,7 @@
         "-Wunused",
         "-Wunreachable-code",
     ],
+    sanitize: {
+        cfi: true,
+    },
 }
diff --git a/proto/Android.bp b/proto/Android.bp
index 65bccbb..01a72ea 100644
--- a/proto/Android.bp
+++ b/proto/Android.bp
@@ -27,3 +27,13 @@
     srcs: ["src/metrics_constants/metrics_constants.proto"],
     sdk_version: "system_current",
 }
+
+filegroup {
+    name: "system-messages-proto-src",
+    srcs: ["src/system_messages.proto"],
+}
+
+filegroup {
+    name: "ipconnectivity-proto-src",
+    srcs: ["src/ipconnectivity.proto"],
+}
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 068707f..54b4201 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -237,6 +237,13 @@
     // Inform the user that the current network may not support using a randomized MAC address.
     NOTE_NETWORK_NO_MAC_RANDOMIZATION_SUPPORT = 56;
 
+    // Inform the user that EAP failure occurs
+    NOTE_WIFI_EAP_FAILURE = 57;
+
+    // Notify the user that their softap disabled because auto shutdown timeout expired.
+    // Package: android
+    NOTE_SOFTAP_AUTO_DISABLED = 58;
+
     // ADD_NEW_IDS_ABOVE_THIS_LINE
     // Legacy IDs with arbitrary values appear below
     // Legacy IDs existed as stable non-conflicting constants prior to the O release
@@ -305,5 +312,9 @@
     // Notify the user that data or apps are being moved to external storage.
     // Package: com.android.systemui
     NOTE_STORAGE_MOVE = 0x534d4f56;
+
+    // Notify the user that the admin suspended personal apps on the device.
+    // Package: android
+    NOTE_PERSONAL_APPS_SUSPENDED = 1003;
   }
 }
diff --git a/rs/java/android/renderscript/BaseObj.java b/rs/java/android/renderscript/BaseObj.java
index b7e05d9..7b5514b 100644
--- a/rs/java/android/renderscript/BaseObj.java
+++ b/rs/java/android/renderscript/BaseObj.java
@@ -16,8 +16,10 @@
 
 package android.renderscript;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
 import dalvik.system.CloseGuard;
+
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 /**
diff --git a/rs/java/android/renderscript/Element.java b/rs/java/android/renderscript/Element.java
index b8eb3a1..0941907 100644
--- a/rs/java/android/renderscript/Element.java
+++ b/rs/java/android/renderscript/Element.java
@@ -16,7 +16,7 @@
 
 package android.renderscript;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * <p>An Element represents one item within an {@link
diff --git a/rs/java/android/renderscript/FileA3D.java b/rs/java/android/renderscript/FileA3D.java
index 9a6b0bc..7cc2825 100644
--- a/rs/java/android/renderscript/FileA3D.java
+++ b/rs/java/android/renderscript/FileA3D.java
@@ -16,13 +16,13 @@
 
 package android.renderscript;
 
-import java.io.File;
-import java.io.InputStream;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.AssetManager;
 import android.content.res.Resources;
 
+import java.io.File;
+import java.io.InputStream;
+
 /**
  * @hide
  * @deprecated in API 16
diff --git a/rs/java/android/renderscript/Font.java b/rs/java/android/renderscript/Font.java
index 583350e..df9d801 100644
--- a/rs/java/android/renderscript/Font.java
+++ b/rs/java/android/renderscript/Font.java
@@ -16,17 +16,16 @@
 
 package android.renderscript;
 
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.os.Environment;
+
 import java.io.File;
 import java.io.InputStream;
 import java.util.HashMap;
 import java.util.Map;
 
-import android.os.Environment;
-
-import android.annotation.UnsupportedAppUsage;
-import android.content.res.AssetManager;
-import android.content.res.Resources;
-
 /**
  * @hide
  * @deprecated in API 16
diff --git a/rs/java/android/renderscript/Matrix4f.java b/rs/java/android/renderscript/Matrix4f.java
index 026c9fb..a9469c9 100644
--- a/rs/java/android/renderscript/Matrix4f.java
+++ b/rs/java/android/renderscript/Matrix4f.java
@@ -16,8 +16,7 @@
 
 package android.renderscript;
 
-import android.annotation.UnsupportedAppUsage;
-import java.lang.Math;
+import android.compat.annotation.UnsupportedAppUsage;
 
 
 /**
diff --git a/rs/java/android/renderscript/Mesh.java b/rs/java/android/renderscript/Mesh.java
index 5321dcb..826225a 100644
--- a/rs/java/android/renderscript/Mesh.java
+++ b/rs/java/android/renderscript/Mesh.java
@@ -16,7 +16,8 @@
 
 package android.renderscript;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
 import java.util.Vector;
 
 /**
diff --git a/rs/java/android/renderscript/Program.java b/rs/java/android/renderscript/Program.java
index e28d646..ff07218 100644
--- a/rs/java/android/renderscript/Program.java
+++ b/rs/java/android/renderscript/Program.java
@@ -17,14 +17,14 @@
 package android.renderscript;
 
 
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.res.Resources;
+import android.util.Log;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.UnsupportedEncodingException;
 
-import android.annotation.UnsupportedAppUsage;
-import android.content.res.Resources;
-import android.util.Log;
-
 
 /**
  * @hide
diff --git a/rs/java/android/renderscript/ProgramFragment.java b/rs/java/android/renderscript/ProgramFragment.java
index 3dde9b6..8805312 100644
--- a/rs/java/android/renderscript/ProgramFragment.java
+++ b/rs/java/android/renderscript/ProgramFragment.java
@@ -16,7 +16,7 @@
 
 package android.renderscript;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 
 /**
diff --git a/rs/java/android/renderscript/ProgramFragmentFixedFunction.java b/rs/java/android/renderscript/ProgramFragmentFixedFunction.java
index d05d41d..c741ce6 100644
--- a/rs/java/android/renderscript/ProgramFragmentFixedFunction.java
+++ b/rs/java/android/renderscript/ProgramFragmentFixedFunction.java
@@ -16,7 +16,7 @@
 
 package android.renderscript;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 
 /**
diff --git a/rs/java/android/renderscript/ProgramRaster.java b/rs/java/android/renderscript/ProgramRaster.java
index 33000ac..a21696c 100644
--- a/rs/java/android/renderscript/ProgramRaster.java
+++ b/rs/java/android/renderscript/ProgramRaster.java
@@ -16,7 +16,7 @@
 
 package android.renderscript;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 
 /**
diff --git a/rs/java/android/renderscript/ProgramStore.java b/rs/java/android/renderscript/ProgramStore.java
index 622fe21..7e61347 100644
--- a/rs/java/android/renderscript/ProgramStore.java
+++ b/rs/java/android/renderscript/ProgramStore.java
@@ -16,7 +16,7 @@
 
 package android.renderscript;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 
 /**
diff --git a/rs/java/android/renderscript/ProgramVertex.java b/rs/java/android/renderscript/ProgramVertex.java
index 83d9ea7..9257234 100644
--- a/rs/java/android/renderscript/ProgramVertex.java
+++ b/rs/java/android/renderscript/ProgramVertex.java
@@ -38,7 +38,7 @@
  **/
 package android.renderscript;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 
 /**
diff --git a/rs/java/android/renderscript/ProgramVertexFixedFunction.java b/rs/java/android/renderscript/ProgramVertexFixedFunction.java
index 579d3bb..03c2eaf 100644
--- a/rs/java/android/renderscript/ProgramVertexFixedFunction.java
+++ b/rs/java/android/renderscript/ProgramVertexFixedFunction.java
@@ -16,7 +16,7 @@
 
 package android.renderscript;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 
 /**
diff --git a/rs/java/android/renderscript/RSSurfaceView.java b/rs/java/android/renderscript/RSSurfaceView.java
index 561373c..6bdde38 100644
--- a/rs/java/android/renderscript/RSSurfaceView.java
+++ b/rs/java/android/renderscript/RSSurfaceView.java
@@ -16,7 +16,7 @@
 
 package android.renderscript;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.SurfaceHolder;
diff --git a/rs/java/android/renderscript/RenderScript.java b/rs/java/android/renderscript/RenderScript.java
index 5b79d51..39efe73 100644
--- a/rs/java/android/renderscript/RenderScript.java
+++ b/rs/java/android/renderscript/RenderScript.java
@@ -16,7 +16,7 @@
 
 package android.renderscript;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.AssetManager;
 import android.graphics.Bitmap;
diff --git a/rs/java/android/renderscript/RenderScriptCacheDir.java b/rs/java/android/renderscript/RenderScriptCacheDir.java
index 1797bef..862d032 100644
--- a/rs/java/android/renderscript/RenderScriptCacheDir.java
+++ b/rs/java/android/renderscript/RenderScriptCacheDir.java
@@ -16,7 +16,8 @@
 
 package android.renderscript;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
 import java.io.File;
 
 /**
diff --git a/rs/java/android/renderscript/RenderScriptGL.java b/rs/java/android/renderscript/RenderScriptGL.java
index 6fac83e..dafaf36 100644
--- a/rs/java/android/renderscript/RenderScriptGL.java
+++ b/rs/java/android/renderscript/RenderScriptGL.java
@@ -16,7 +16,7 @@
 
 package android.renderscript;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.graphics.SurfaceTexture;
 import android.view.Surface;
diff --git a/rs/java/android/renderscript/Script.java b/rs/java/android/renderscript/Script.java
index 9ad9aea..d1d3a76 100644
--- a/rs/java/android/renderscript/Script.java
+++ b/rs/java/android/renderscript/Script.java
@@ -16,7 +16,7 @@
 
 package android.renderscript;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.util.SparseArray;
 
 /**
diff --git a/services/Android.bp b/services/Android.bp
index 1e11936..a582453 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -6,7 +6,7 @@
 }
 
 filegroup {
-    name: "services-sources",
+    name: "services-all-sources",
     srcs: [
         ":services.core-sources",
         ":services.accessibility-sources",
@@ -29,6 +29,7 @@
         ":services.usage-sources",
         ":services.usb-sources",
         ":services.voiceinteraction-sources",
+        ":service-permission-sources",
     ],
     visibility: ["//visibility:private"],
 }
@@ -110,10 +111,10 @@
 
 droidstubs {
     name: "services-stubs.sources",
-    srcs: [":services-sources"],
+    srcs: [":services-all-sources"],
     installable: false,
     // TODO: remove the --hide options below
-    args: " --show-single-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES,process=android.annotation.SystemApi.Process.SYSTEM_SERVER\\)" +
+    args: " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES,process=android.annotation.SystemApi.Process.SYSTEM_SERVER\\)" +
         " --hide-annotation android.annotation.Hide" +
         " --hide-package com.google.android.startop.iorap" +
         " --hide ReferencesHidden" +
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 5eaa80a..3e74b7a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -36,8 +36,12 @@
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.graphics.Region;
 import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -54,6 +58,7 @@
 import android.view.Display;
 import android.view.KeyEvent;
 import android.view.MagnificationSpec;
+import android.view.SurfaceControl;
 import android.view.View;
 import android.view.WindowInfo;
 import android.view.accessibility.AccessibilityCache;
@@ -75,6 +80,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -90,6 +96,7 @@
     private static final String LOG_TAG = "AbstractAccessibilityServiceConnection";
     private static final int WAIT_WINDOWS_TIMEOUT_MILLIS = 5000;
 
+    protected static final String TAKE_SCREENSHOT = "takeScreenshot";
     protected final Context mContext;
     protected final SystemSupport mSystemSupport;
     protected final WindowManagerInternal mWindowManagerService;
@@ -769,6 +776,16 @@
     }
 
     @Override
+    public @NonNull List<AccessibilityNodeInfo.AccessibilityAction> getSystemActions() {
+        synchronized (mLock) {
+            if (!hasRightsToCurrentUserLocked()) {
+                return Collections.emptyList();
+            }
+        }
+        return mSystemActionPerformer.getSystemActions();
+    }
+
+    @Override
     public boolean isFingerprintGestureDetectionAvailable() {
         if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
             return false;
@@ -934,6 +951,54 @@
         mInvocationHandler.setSoftKeyboardCallbackEnabled(enabled);
     }
 
+    @Nullable
+    @Override
+    public Bitmap takeScreenshot(int displayId) {
+        synchronized (mLock) {
+            if (!hasRightsToCurrentUserLocked()) {
+                return null;
+            }
+
+            if (!mSecurityPolicy.canTakeScreenshotLocked(this)) {
+                return null;
+            }
+        }
+
+        if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
+            return null;
+        }
+
+        final Display display = DisplayManagerGlobal.getInstance()
+                .getRealDisplay(displayId);
+        if (display == null) {
+            return null;
+        }
+        final Point displaySize = new Point();
+        display.getRealSize(displaySize);
+
+        final int rotation = display.getRotation();
+        Bitmap screenShot = null;
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final Rect crop = new Rect(0, 0, displaySize.x, displaySize.y);
+            // TODO (b/145893483): calling new API with the display as a parameter
+            // when surface control supported.
+            screenShot = SurfaceControl.screenshot(crop, displaySize.x, displaySize.y,
+                    rotation);
+            if (screenShot != null) {
+                // Optimization for telling the bitmap that all of the pixels are known to be
+                // opaque (false). This is meant as a drawing hint, as in some cases a bitmap
+                // that is known to be opaque can take a faster drawing case than one that may
+                // have non-opaque per-pixel alpha values.
+                screenShot.setHasAlpha(false);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+        return screenShot;
+    }
+
     @Override
     public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
@@ -1018,6 +1083,20 @@
         }
     }
 
+    /**
+     * Gets windowId of given token.
+     *
+     * @param token The token
+     * @return window id
+     */
+    @Override
+    public int getWindowIdForLeashToken(@NonNull IBinder token) {
+        synchronized (mLock) {
+            // TODO: Add a method to lookup window ID by given leash token.
+            return -1;
+        }
+    }
+
     public void resetLocked() {
         mSystemSupport.getKeyEventDispatcher().flush(this);
         try {
@@ -1184,6 +1263,11 @@
                 gestureEvent).sendToTarget();
     }
 
+    public void notifySystemActionsChangedLocked() {
+        mInvocationHandler.sendEmptyMessage(
+                InvocationHandler.MSG_ON_SYSTEM_ACTIONS_CHANGED);
+    }
+
     public void notifyClearAccessibilityNodeInfoCache() {
         mInvocationHandler.sendEmptyMessage(
                 InvocationHandler.MSG_CLEAR_ACCESSIBILITY_CACHE);
@@ -1282,6 +1366,18 @@
         }
     }
 
+    private void notifySystemActionsChangedInternal() {
+        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
+        if (listener != null) {
+            try {
+                listener.onSystemActionsChanged();
+            } catch (RemoteException re) {
+                Slog.e(LOG_TAG, "Error sending system actions change to " + mService,
+                        re);
+            }
+        }
+    }
+
     private void notifyClearAccessibilityCacheInternal() {
         final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
         if (listener != null) {
@@ -1476,6 +1572,7 @@
         private static final int MSG_ON_SOFT_KEYBOARD_STATE_CHANGED = 6;
         private static final int MSG_ON_ACCESSIBILITY_BUTTON_CLICKED = 7;
         private static final int MSG_ON_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED = 8;
+        private static final int MSG_ON_SYSTEM_ACTIONS_CHANGED = 9;
 
         /** List of magnification callback states, mapping from displayId -> Boolean */
         @GuardedBy("mlock")
@@ -1523,7 +1620,10 @@
                     final boolean available = (message.arg1 != 0);
                     notifyAccessibilityButtonAvailabilityChangedInternal(available);
                 } break;
-
+                case MSG_ON_SYSTEM_ACTIONS_CHANGED: {
+                    notifySystemActionsChangedInternal();
+                    break;
+                }
                 default: {
                     throw new IllegalArgumentException("Unknown message: " + type);
                 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index c733d3b..49582a9 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -31,6 +31,7 @@
 
 import com.android.server.LocalServices;
 import com.android.server.accessibility.gestures.TouchExplorer;
+import com.android.server.accessibility.magnification.MagnificationGestureHandler;
 import com.android.server.policy.WindowManagerPolicy;
 
 import java.util.ArrayList;
@@ -402,7 +403,7 @@
                 final boolean triggerable = (mEnabledFeatures
                         & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0;
                 MagnificationGestureHandler magnificationGestureHandler =
-                        new MagnificationGestureHandler(displayContext,
+                        new FullScreenMagnificationGestureHandler(displayContext,
                                 mAms.getMagnificationController(),
                                 detectControlGestures, triggerable, displayId);
                 addFirstEventHandler(displayId, magnificationGestureHandler);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 6a6e2b2..bcaecea 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -35,6 +35,7 @@
 import android.app.ActivityOptions;
 import android.app.AlertDialog;
 import android.app.PendingIntent;
+import android.app.RemoteAction;
 import android.appwidget.AppWidgetManagerInternal;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
@@ -93,6 +94,7 @@
 import android.view.accessibility.IAccessibilityInteractionConnection;
 import android.view.accessibility.IAccessibilityManager;
 import android.view.accessibility.IAccessibilityManagerClient;
+import android.view.accessibility.IWindowMagnificationConnection;
 
 import com.android.internal.R;
 import com.android.internal.accessibility.AccessibilityShortcutController;
@@ -105,6 +107,7 @@
 import com.android.internal.util.IntPair;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.accessibility.magnification.WindowMagnificationManager;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
@@ -134,7 +137,8 @@
         implements AbstractAccessibilityServiceConnection.SystemSupport,
         AccessibilityUserState.ServiceInfoChangeListener,
         AccessibilityWindowManager.AccessibilityEventSender,
-        AccessibilitySecurityPolicy.AccessibilityUserManager {
+        AccessibilitySecurityPolicy.AccessibilityUserManager,
+        SystemActionPerformer.SystemActionsChangedListener {
 
     private static final boolean DEBUG = false;
 
@@ -148,6 +152,8 @@
     //       their capabilities are ready.
     private static final int WAIT_MOTION_INJECTOR_TIMEOUT_MILLIS = 1000;
 
+    static final String FUNCTION_REGISTER_SYSTEM_ACTION = "registerSystemAction";
+    static final String FUNCTION_UNREGISTER_SYSTEM_ACTION = "unregisterSystemAction";
     private static final String FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE =
         "registerUiTestAutomationService";
 
@@ -201,6 +207,8 @@
 
     private AccessibilityInputFilter mInputFilter;
 
+    private WindowMagnificationManager mWindowMagnificationMgr;
+
     private boolean mHasInputFilter;
 
     private KeyEventDispatcher mKeyEventDispatcher;
@@ -253,6 +261,27 @@
         }
     }
 
+    @VisibleForTesting
+    AccessibilityManagerService(
+            Context context,
+            PackageManager packageManager,
+            AccessibilitySecurityPolicy securityPolicy,
+            SystemActionPerformer systemActionPerformer,
+            AccessibilityWindowManager a11yWindowManager,
+            AccessibilityDisplayListener a11yDisplayListener) {
+        mContext = context;
+        mPowerManager =  (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
+        mMainHandler = new MainHandler(mContext.getMainLooper());
+        mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class);
+        mPackageManager = packageManager;
+        mSecurityPolicy = securityPolicy;
+        mSystemActionPerformer = systemActionPerformer;
+        mA11yWindowManager = a11yWindowManager;
+        mA11yDisplayListener = a11yDisplayListener;
+        init();
+    }
+
     /**
      * Creates a new instance.
      *
@@ -260,21 +289,25 @@
      */
     public AccessibilityManagerService(Context context) {
         mContext = context;
-        mPackageManager = mContext.getPackageManager();
-        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        mPowerManager =  (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
-        mSecurityPolicy = new AccessibilitySecurityPolicy(mContext, this);
         mMainHandler = new MainHandler(mContext.getMainLooper());
-        mSystemActionPerformer = new SystemActionPerformer(mContext, mWindowManagerService);
+        mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class);
+        mPackageManager = mContext.getPackageManager();
+        mSecurityPolicy = new AccessibilitySecurityPolicy(mContext, this);
+        mSystemActionPerformer =
+                new SystemActionPerformer(mContext, mWindowManagerService, null, this);
         mA11yWindowManager = new AccessibilityWindowManager(mLock, mMainHandler,
                 mWindowManagerService, this, mSecurityPolicy, this);
         mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler);
-        mSecurityPolicy.setAccessibilityWindowManager(mA11yWindowManager);
-        mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class);
+        init();
+    }
 
+    private void init() {
+        mSecurityPolicy.setAccessibilityWindowManager(mA11yWindowManager);
         registerBroadcastReceivers();
         new AccessibilityContentObserver(mMainHandler).register(
-                context.getContentResolver());
+                mContext.getContentResolver());
     }
 
     @Override
@@ -384,6 +417,7 @@
                     if (reboundAService) {
                         onUserStateChangedLocked(userState);
                     }
+                    migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, packageName);
                 }
             }
 
@@ -623,6 +657,30 @@
         event.recycle();
     }
 
+    /**
+     * This is the implementation of AccessibilityManager system API.
+     * System UI calls into this method through AccessibilityManager system API to register a
+     * system action.
+     */
+    @Override
+    public void registerSystemAction(RemoteAction action, int actionId) {
+        mSecurityPolicy.enforceCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY,
+                FUNCTION_REGISTER_SYSTEM_ACTION);
+        mSystemActionPerformer.registerSystemAction(actionId, action);
+    }
+
+    /**
+     * This is the implementation of AccessibilityManager system API.
+     * System UI calls into this method through AccessibilityManager system API to unregister a
+     * system action.
+     */
+    @Override
+    public void unregisterSystemAction(int actionId) {
+        mSecurityPolicy.enforceCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY,
+                FUNCTION_UNREGISTER_SYSTEM_ACTION);
+        mSystemActionPerformer.unregisterSystemAction(actionId);
+    }
+
     @Override
     public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId) {
         synchronized (mLock) {
@@ -760,9 +818,7 @@
 
             userState.setTouchExplorationEnabledLocked(touchExplorationEnabled);
             userState.setDisplayMagnificationEnabledLocked(false);
-            userState.setNavBarMagnificationEnabledLocked(false);
             userState.disableShortcutMagnificationLocked();
-
             userState.setAutoclickEnabledLocked(false);
             userState.mEnabledServices.clear();
             userState.mEnabledServices.add(service);
@@ -802,17 +858,20 @@
      * navigation area has been clicked.
      *
      * @param displayId The logical display id.
+     * @param targetName The flattened {@link ComponentName} string or the class name of a system
+     *        class implementing a supported accessibility feature, or {@code null} if there's no
+     *        specified target.
      */
     @Override
-    public void notifyAccessibilityButtonClicked(int displayId) {
+    public void notifyAccessibilityButtonClicked(int displayId, String targetName) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Caller does not hold permission "
                     + android.Manifest.permission.STATUS_BAR_SERVICE);
         }
-        synchronized (mLock) {
-            notifyAccessibilityButtonClickedLocked(displayId);
-        }
+        mMainHandler.sendMessage(obtainMessage(
+                AccessibilityManagerService::performAccessibilityShortcutInternal, this,
+                displayId, ACCESSIBILITY_BUTTON, targetName));
     }
 
     /**
@@ -824,11 +883,8 @@
      */
     @Override
     public void notifyAccessibilityButtonVisibilityChanged(boolean shown) {
-        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Caller does not hold permission "
-                    + android.Manifest.permission.STATUS_BAR_SERVICE);
-        }
+        mSecurityPolicy.enforceCallingOrSelfPermission(
+                android.Manifest.permission.STATUS_BAR_SERVICE);
         synchronized (mLock) {
             notifyAccessibilityButtonVisibilityChangedLocked(shown);
         }
@@ -850,6 +906,25 @@
         }
     }
 
+    /**
+     * Called when the system action list is changed.
+     */
+    @Override
+    public void onSystemActionsChanged() {
+        synchronized (mLock) {
+            AccessibilityUserState state = getCurrentUserStateLocked();
+            notifySystemActionsChangedLocked(state);
+        }
+    }
+
+    @VisibleForTesting
+    void notifySystemActionsChangedLocked(AccessibilityUserState userState) {
+        for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) {
+            AccessibilityServiceConnection service = userState.mBoundServices.get(i);
+            service.notifySystemActionsChangedLocked();
+        }
+    }
+
     @VisibleForTesting
     public boolean notifyKeyEvent(KeyEvent event, int policyFlags) {
         synchronized (mLock) {
@@ -972,6 +1047,7 @@
             // the state since the context in which the current user
             // state was used has changed since it was inactive.
             onUserStateChangedLocked(userState);
+            migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, null);
 
             if (announceNewUser) {
                 // Schedule announcement of the current user if needed.
@@ -1081,66 +1157,6 @@
         }
     }
 
-    // TODO(a11y shortcut): Remove this function and Use #performAccessibilityShortcutInternal(
-    //  ACCESSIBILITY_BUTTON) instead, after the new Settings shortcut Ui merged.
-    private void notifyAccessibilityButtonClickedLocked(int displayId) {
-        final AccessibilityUserState state = getCurrentUserStateLocked();
-
-        int potentialTargets = state.isNavBarMagnificationEnabledLocked() ? 1 : 0;
-        for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
-            final AccessibilityServiceConnection service = state.mBoundServices.get(i);
-            if (service.mRequestAccessibilityButton) {
-                potentialTargets++;
-            }
-        }
-
-        if (potentialTargets == 0) {
-            return;
-        }
-        if (potentialTargets == 1) {
-            if (state.isNavBarMagnificationEnabledLocked()) {
-                mMainHandler.sendMessage(obtainMessage(
-                        AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this,
-                        displayId));
-                return;
-            } else {
-                for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
-                    final AccessibilityServiceConnection service = state.mBoundServices.get(i);
-                    if (service.mRequestAccessibilityButton) {
-                        service.notifyAccessibilityButtonClickedLocked(displayId);
-                        return;
-                    }
-                }
-            }
-        } else {
-            if (state.getServiceAssignedToAccessibilityButtonLocked() == null
-                    && !state.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
-                mMainHandler.sendMessage(obtainMessage(
-                        AccessibilityManagerService::showAccessibilityTargetsSelection, this,
-                        displayId, ACCESSIBILITY_BUTTON));
-            } else if (state.isNavBarMagnificationEnabledLocked()
-                    && state.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
-                mMainHandler.sendMessage(obtainMessage(
-                        AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this,
-                        displayId));
-                return;
-            } else {
-                for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
-                    final AccessibilityServiceConnection service = state.mBoundServices.get(i);
-                    if (service.mRequestAccessibilityButton && (service.mComponentName.equals(
-                            state.getServiceAssignedToAccessibilityButtonLocked()))) {
-                        service.notifyAccessibilityButtonClickedLocked(displayId);
-                        return;
-                    }
-                }
-            }
-            // The user may have turned off the assigned service or feature
-            mMainHandler.sendMessage(obtainMessage(
-                    AccessibilityManagerService::showAccessibilityTargetsSelection, this,
-                    displayId, ACCESSIBILITY_BUTTON));
-        }
-    }
-
     private void sendAccessibilityButtonToInputFilter(int displayId) {
         synchronized (mLock) {
             if (mHasInputFilter && mInputFilter != null) {
@@ -1153,8 +1169,8 @@
             @ShortcutType int shortcutType) {
         Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        intent.putExtra(AccessibilityManager.EXTRA_SHORTCUT_TYPE, shortcutType);
         final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle();
-        bundle.putInt(AccessibilityManager.EXTRA_SHORTCUT_TYPE, shortcutType);
         mContext.startActivityAsUser(intent, bundle, UserHandle.of(mCurrentUserId));
     }
 
@@ -1584,8 +1600,7 @@
             if (userState.isDisplayMagnificationEnabledLocked()) {
                 flags |= AccessibilityInputFilter.FLAG_FEATURE_SCREEN_MAGNIFIER;
             }
-            if (userState.isNavBarMagnificationEnabledLocked()
-                    || userState.isShortcutKeyMagnificationEnabledLocked()) {
+            if (userState.isShortcutMagnificationEnabledLocked()) {
                 flags |= AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER;
             }
             if (userHasMagnificationServicesLocked(userState)) {
@@ -1835,14 +1850,8 @@
                 mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
                 0, userState.mUserId) == 1;
-        final boolean navBarMagnificationEnabled = Settings.Secure.getIntForUser(
-                mContext.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
-                0, userState.mUserId) == 1;
-        if ((displayMagnificationEnabled != userState.isDisplayMagnificationEnabledLocked())
-                || (navBarMagnificationEnabled != userState.isNavBarMagnificationEnabledLocked())) {
+        if ((displayMagnificationEnabled != userState.isDisplayMagnificationEnabledLocked())) {
             userState.setDisplayMagnificationEnabledLocked(displayMagnificationEnabled);
-            userState.setNavBarMagnificationEnabledLocked(navBarMagnificationEnabled);
             return true;
         }
         return false;
@@ -1896,11 +1905,12 @@
     }
 
     private boolean readAccessibilityShortcutKeySettingLocked(AccessibilityUserState userState) {
+        final String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, userState.mUserId);
         final Set<String> targetsFromSetting = new ArraySet<>();
-        readColonDelimitedSettingToSet(Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
-                userState.mUserId, targetsFromSetting, str -> str);
-        if (targetsFromSetting.isEmpty()) {
-            // Fall back to device's default a11y service.
+        readColonDelimitedStringToSet(settingValue, targetsFromSetting, false, str -> str);
+        // Fall back to device's default a11y service, only when setting is never updated.
+        if (settingValue == null) {
             final String defaultService = mContext.getString(
                     R.string.config_defaultAccessibilityService);
             if (!TextUtils.isEmpty(defaultService)) {
@@ -2037,8 +2047,7 @@
         // displays in one display. It's not a real display and there's no input events for it.
         final ArrayList<Display> displays = getValidDisplayList();
         if (userState.isDisplayMagnificationEnabledLocked()
-                || userState.isNavBarMagnificationEnabledLocked()
-                || userState.isShortcutKeyMagnificationEnabledLocked()) {
+                || userState.isShortcutMagnificationEnabledLocked()) {
             for (int i = 0; i < displays.size(); i++) {
                 final Display display = displays.get(i);
                 getMagnificationController().register(display.getDisplayId());
@@ -2157,6 +2166,85 @@
         scheduleNotifyClientsOfServicesStateChangeLocked(userState);
     }
 
+    /**
+     * 1) Check if the service assigned to accessibility button target sdk version > Q.
+     *    If it isn't, remove it from the list and associated setting.
+     *    (It happens when an accessibility service package is downgraded.)
+     * 2) Check if an enabled service targeting sdk version > Q and requesting a11y button is
+     *    assigned to a shortcut. If it isn't, assigns it to the accessibility button.
+     *    (It happens when an enabled accessibility service package is upgraded.)
+     *
+     * @param packageName The package name to check, or {@code null} to check all services.
+     */
+    private void migrateAccessibilityButtonSettingsIfNecessaryLocked(
+            AccessibilityUserState userState, @Nullable String packageName) {
+        final Set<String> buttonTargets =
+                userState.getShortcutTargetsLocked(ACCESSIBILITY_BUTTON);
+        int lastSize = buttonTargets.size();
+        buttonTargets.removeIf(name -> {
+            if (packageName != null && name != null && !name.contains(packageName)) {
+                return false;
+            }
+            final ComponentName componentName = ComponentName.unflattenFromString(name);
+            if (componentName == null) {
+                return false;
+            }
+            final AccessibilityServiceInfo serviceInfo =
+                    userState.getInstalledServiceInfoLocked(componentName);
+            if (serviceInfo == null) {
+                return false;
+            }
+            if (serviceInfo.getResolveInfo().serviceInfo.applicationInfo
+                    .targetSdkVersion > Build.VERSION_CODES.Q) {
+                return false;
+            }
+            // A11y services targeting sdk version <= Q should not be in the list.
+            return true;
+        });
+        boolean changed = (lastSize != buttonTargets.size());
+        lastSize = buttonTargets.size();
+
+        final Set<String> shortcutKeyTargets =
+                userState.getShortcutTargetsLocked(ACCESSIBILITY_SHORTCUT_KEY);
+        userState.mEnabledServices.forEach(componentName -> {
+            if (packageName != null && componentName != null
+                    && !packageName.equals(componentName.getPackageName())) {
+                return;
+            }
+            final AccessibilityServiceInfo serviceInfo =
+                    userState.getInstalledServiceInfoLocked(componentName);
+            if (serviceInfo == null) {
+                return;
+            }
+            final boolean requestA11yButton = (serviceInfo.flags
+                    & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
+            if (!(serviceInfo.getResolveInfo().serviceInfo.applicationInfo
+                    .targetSdkVersion > Build.VERSION_CODES.Q && requestA11yButton)) {
+                return;
+            }
+            final String serviceName = serviceInfo.getComponentName().flattenToString();
+            if (TextUtils.isEmpty(serviceName)) {
+                return;
+            }
+            if (shortcutKeyTargets.contains(serviceName) || buttonTargets.contains(serviceName)) {
+                return;
+            }
+            // For enabled a11y services targeting sdk version > Q and requesting a11y button should
+            // be assigned to a shortcut.
+            buttonTargets.add(serviceName);
+        });
+        changed |= (lastSize != buttonTargets.size());
+        if (!changed) {
+            return;
+        }
+
+        // Update setting key with new value.
+        persistColonDelimitedSetToSettingLocked(
+                Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
+                userState.mUserId, buttonTargets, str -> str);
+        scheduleNotifyClientsOfServicesStateChangeLocked(userState);
+    }
+
     private void updateRecommendedUiTimeoutLocked(AccessibilityUserState userState) {
         int newNonInteractiveUiTimeout = userState.getUserNonInteractiveUiTimeoutLocked();
         int newInteractiveUiTimeout = userState.getUserInteractiveUiTimeoutLocked();
@@ -2218,9 +2306,13 @@
      * AIDL-exposed method to be called when the accessibility shortcut key is enabled. Requires
      * permission to write secure settings, since someone with that permission can enable
      * accessibility services themselves.
+     *
+     * @param targetName The flattened {@link ComponentName} string or the class name of a system
+     *        class implementing a supported accessibility feature, or {@code null} if there's no
+     *        specified target.
      */
     @Override
-    public void performAccessibilityShortcut() {
+    public void performAccessibilityShortcut(String targetName) {
         if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)
                 && (mContext.checkCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
                 != PackageManager.PERMISSION_GRANTED)) {
@@ -2229,7 +2321,7 @@
         }
         mMainHandler.sendMessage(obtainMessage(
                 AccessibilityManagerService::performAccessibilityShortcutInternal, this,
-                Display.DEFAULT_DISPLAY, ACCESSIBILITY_SHORTCUT_KEY));
+                Display.DEFAULT_DISPLAY, ACCESSIBILITY_SHORTCUT_KEY, targetName));
     }
 
     /**
@@ -2237,20 +2329,31 @@
      *
      * @param shortcutType The shortcut type.
      * @param displayId The display id of the accessibility button.
+     * @param targetName The flattened {@link ComponentName} string or the class name of a system
+     *        class implementing a supported accessibility feature, or {@code null} if there's no
+     *        specified target.
      */
     private void performAccessibilityShortcutInternal(int displayId,
-            @ShortcutType int shortcutType) {
+            @ShortcutType int shortcutType, @Nullable String targetName) {
         final List<String> shortcutTargets = getAccessibilityShortcutTargetsInternal(shortcutType);
         if (shortcutTargets.isEmpty()) {
             Slog.d(LOG_TAG, "No target to perform shortcut, shortcutType=" + shortcutType);
             return;
         }
-        // In case there are many targets assigned to the given shortcut.
-        if (shortcutTargets.size() > 1) {
-            showAccessibilityTargetsSelection(displayId, shortcutType);
-            return;
+        // In case the caller specified a target name
+        if (targetName != null) {
+            if (!shortcutTargets.contains(targetName)) {
+                Slog.d(LOG_TAG, "Perform shortcut failed, invalid target name:" + targetName);
+                return;
+            }
+        } else {
+            // In case there are many targets assigned to the given shortcut.
+            if (shortcutTargets.size() > 1) {
+                showAccessibilityTargetsSelection(displayId, shortcutType);
+                return;
+            }
+            targetName = shortcutTargets.get(0);
         }
-        final String targetName = shortcutTargets.get(0);
         // In case user assigned magnification to the given shortcut.
         if (targetName.equals(MAGNIFICATION_CONTROLLER_NAME)) {
             sendAccessibilityButtonToInputFilter(displayId);
@@ -2508,6 +2611,24 @@
     }
 
     @Override
+    public void setWindowMagnificationConnection(
+            IWindowMagnificationConnection connection) throws RemoteException {
+        mSecurityPolicy.enforceCallingOrSelfPermission(
+                android.Manifest.permission.STATUS_BAR_SERVICE);
+
+        getWindowMagnificationMgr().setConnection(connection);
+    }
+
+    WindowMagnificationManager getWindowMagnificationMgr() {
+        synchronized (mLock) {
+            if (mWindowMagnificationMgr == null) {
+                mWindowMagnificationMgr = new WindowMagnificationManager();
+            }
+            return mWindowMagnificationMgr;
+        }
+    }
+
+    @Override
     public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
         synchronized (mLock) {
@@ -2793,11 +2914,6 @@
         private final Uri mDisplayMagnificationEnabledUri = Settings.Secure.getUriFor(
                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED);
 
-        // TODO(a11y shortcut): Remove this setting key, and have a migrate function in
-        //  Setting provider after new shortcut UI merged.
-        private final Uri mNavBarMagnificationEnabledUri = Settings.Secure.getUriFor(
-                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
-
         private final Uri mAutoclickEnabledUri = Settings.Secure.getUriFor(
                 Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED);
 
@@ -2837,8 +2953,6 @@
                     false, this, UserHandle.USER_ALL);
             contentResolver.registerContentObserver(mDisplayMagnificationEnabledUri,
                     false, this, UserHandle.USER_ALL);
-            contentResolver.registerContentObserver(mNavBarMagnificationEnabledUri,
-                    false, this, UserHandle.USER_ALL);
             contentResolver.registerContentObserver(mAutoclickEnabledUri,
                     false, this, UserHandle.USER_ALL);
             contentResolver.registerContentObserver(mEnabledAccessibilityServicesUri,
@@ -2873,8 +2987,7 @@
                     if (readTouchExplorationEnabledSettingLocked(userState)) {
                         onUserStateChangedLocked(userState);
                     }
-                } else if (mDisplayMagnificationEnabledUri.equals(uri)
-                        || mNavBarMagnificationEnabledUri.equals(uri)) {
+                } else if (mDisplayMagnificationEnabledUri.equals(uri)) {
                     if (readMagnificationEnabledSettingsLocked(userState)) {
                         onUserStateChangedLocked(userState);
                     }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
index 20321099..7a42cd1 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
@@ -326,6 +326,19 @@
     }
 
     /**
+     * Checks if a service can take screenshot.
+     *
+     * @param service The service requesting access
+     *
+     * @return Whether ot not the service may take screenshot
+     */
+    public boolean canTakeScreenshotLocked(
+            @NonNull AbstractAccessibilityServiceConnection service) {
+        return (service.getCapabilities()
+                & AccessibilityServiceInfo.CAPABILITY_CAN_TAKE_SCREENSHOT) != 0;
+    }
+
+    /**
      * Returns the parent userId of the profile according to the specified userId.
      *
      * @param userId The userId to check
@@ -426,6 +439,7 @@
                 return false;
             }
         }
+        // TODO: Check parent windowId if the giving windowId is from embedded view hierarchy.
         if (windowId == mAccessibilityWindowManager.getActiveWindowId(userId)) {
             return true;
         }
@@ -536,4 +550,17 @@
             Binder.restoreCallingIdentity(identityToken);
         }
     }
+
+    /**
+     * Enforcing permission check to IPC caller or grant it if it's not through IPC.
+     *
+     * @param permission The permission to check
+     */
+    public void enforceCallingOrSelfPermission(@NonNull String permission) {
+        if (mContext.checkCallingOrSelfPermission(permission)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Caller does not hold permission "
+                    + permission);
+        }
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index cbff6bd..25911a7 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -16,6 +16,8 @@
 
 package com.android.server.accessibility;
 
+import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT;
+
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
 import android.Manifest;
@@ -25,16 +27,21 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Process;
+import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Slog;
 import android.view.Display;
 
+import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
@@ -264,6 +271,24 @@
     }
 
     @Override
+    public boolean switchToInputMethod(String imeId) {
+        synchronized (mLock) {
+            if (!hasRightsToCurrentUserLocked()) {
+                return false;
+            }
+        }
+        final boolean result;
+        final int callingUserId = UserHandle.getCallingUserId();
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            result = InputMethodManagerInternal.get().switchToInputMethod(imeId, callingUserId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+        return result;
+    }
+
+    @Override
     public boolean isAccessibilityButtonAvailable() {
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
@@ -294,48 +319,16 @@
         }
     }
 
-    // TODO(a11y shortcut): Refactoring the logic here, after the new Settings shortcut Ui merged.
     public boolean isAccessibilityButtonAvailableLocked(AccessibilityUserState userState) {
         // If the service does not request the accessibility button, it isn't available
         if (!mRequestAccessibilityButton) {
             return false;
         }
-
         // If the accessibility button isn't currently shown, it cannot be available to services
         if (!mSystemSupport.isAccessibilityButtonShown()) {
             return false;
         }
-
-        // If magnification is on and assigned to the accessibility button, services cannot be
-        if (userState.isNavBarMagnificationEnabledLocked()
-                && userState.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
-            return false;
-        }
-
-        int requestingServices = 0;
-        for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) {
-            final AccessibilityServiceConnection service = userState.mBoundServices.get(i);
-            if (service.mRequestAccessibilityButton) {
-                requestingServices++;
-            }
-        }
-
-        if (requestingServices == 1) {
-            // If only a single service is requesting, it must be this service, and the
-            // accessibility button is available to it
-            return true;
-        } else {
-            // With more than one active service, we derive the target from the user's settings
-            if (userState.getServiceAssignedToAccessibilityButtonLocked() == null) {
-                // If the user has not made an assignment, we treat the button as available to
-                // all services until the user interacts with the button to make an assignment
-                return true;
-            } else {
-                // If an assignment was made, it defines availability
-                return mComponentName.equals(
-                        userState.getServiceAssignedToAccessibilityButtonLocked());
-            }
-        }
+        return true;
     }
 
     @Override
@@ -400,4 +393,15 @@
             }
         }
     }
+
+    @Override
+    public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {
+        mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> {
+            final Bitmap screenshot = super.takeScreenshot(displayId);
+            // Send back the result.
+            final Bundle payload = new Bundle();
+            payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT, screenshot);
+            callback.sendResult(payload);
+        }, null).recycleOnUse());
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index a163f74..ebe2af6 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -100,7 +100,6 @@
     private boolean mIsAutoclickEnabled;
     private boolean mIsDisplayMagnificationEnabled;
     private boolean mIsFilterKeyEventsEnabled;
-    private boolean mIsNavBarMagnificationEnabled;
     private boolean mIsPerformGesturesEnabled;
     private boolean mIsTextHighContrastEnabled;
     private boolean mIsTouchExplorationEnabled;
@@ -153,7 +152,6 @@
         mAccessibilityButtonTargets.clear();
         mIsTouchExplorationEnabled = false;
         mIsDisplayMagnificationEnabled = false;
-        mIsNavBarMagnificationEnabled = false;
         mIsAutoclickEnabled = false;
         mUserNonInteractiveUiTimeout = 0;
         mUserInteractiveUiTimeout = 0;
@@ -435,8 +433,6 @@
         pw.append(", touchExplorationEnabled=").append(String.valueOf(mIsTouchExplorationEnabled));
         pw.append(", displayMagnificationEnabled=").append(String.valueOf(
                 mIsDisplayMagnificationEnabled));
-        pw.append(", navBarMagnificationEnabled=").append(String.valueOf(
-                mIsNavBarMagnificationEnabled));
         pw.append(", autoclickEnabled=").append(String.valueOf(mIsAutoclickEnabled));
         pw.append(", nonInteractiveUiTimeout=").append(String.valueOf(mNonInteractiveUiTimeout));
         pw.append(", interactiveUiTimeout=").append(String.valueOf(mInteractiveUiTimeout));
@@ -553,8 +549,12 @@
         mLastSentClientState = state;
     }
 
-    public boolean isShortcutKeyMagnificationEnabledLocked() {
-        return mAccessibilityShortcutKeyTargets.contains(MAGNIFICATION_CONTROLLER_NAME);
+    /**
+     * Returns true if navibar magnification or shortcut key magnification is enabled.
+     */
+    public boolean isShortcutMagnificationEnabledLocked() {
+        return mAccessibilityShortcutKeyTargets.contains(MAGNIFICATION_CONTROLLER_NAME)
+                || mAccessibilityButtonTargets.contains(MAGNIFICATION_CONTROLLER_NAME);
     }
 
     /**
@@ -690,28 +690,4 @@
     public void setUserNonInteractiveUiTimeoutLocked(int timeout) {
         mUserNonInteractiveUiTimeout = timeout;
     }
-
-    // TODO(a11y shortcut): These functions aren't necessary, after the new Settings shortcut Ui
-    //  is merged.
-    boolean isNavBarMagnificationEnabledLocked() {
-        return mIsNavBarMagnificationEnabled;
-    }
-
-    void setNavBarMagnificationEnabledLocked(boolean enabled) {
-        mIsNavBarMagnificationEnabled = enabled;
-    }
-
-    boolean isNavBarMagnificationAssignedToAccessibilityButtonLocked() {
-        return mAccessibilityButtonTargets.contains(MAGNIFICATION_CONTROLLER_NAME);
-    }
-
-    ComponentName getServiceAssignedToAccessibilityButtonLocked() {
-        final String targetName = mAccessibilityButtonTargets.isEmpty() ? null
-                : mAccessibilityButtonTargets.valueAt(0);
-        if (targetName == null) {
-            return null;
-        }
-        return ComponentName.unflattenFromString(targetName);
-    }
-    // TODO(a11y shortcut): End
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index 8ce92a3..a6041e0 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -701,6 +701,7 @@
                 case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL:
                 case WindowManager.LayoutParams.TYPE_SEARCH_BAR:
                 case WindowManager.LayoutParams.TYPE_STATUS_BAR:
+                case WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE:
                 case WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL:
                 case WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL:
                 case WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY:
diff --git a/services/accessibility/java/com/android/server/accessibility/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/FullScreenMagnificationGestureHandler.java
new file mode 100644
index 0000000..9e4fd80
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/FullScreenMagnificationGestureHandler.java
@@ -0,0 +1,1111 @@
+/*
+ * 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.server.accessibility;
+
+import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_POINTER_DOWN;
+import static android.view.MotionEvent.ACTION_POINTER_UP;
+import static android.view.MotionEvent.ACTION_UP;
+
+import static com.android.server.accessibility.gestures.GestureUtils.distance;
+
+import static java.lang.Math.abs;
+import static java.util.Arrays.asList;
+import static java.util.Arrays.copyOfRange;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.MathUtils;
+import android.util.Slog;
+import android.util.TypedValue;
+import android.view.GestureDetector;
+import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.MotionEvent;
+import android.view.MotionEvent.PointerCoords;
+import android.view.MotionEvent.PointerProperties;
+import android.view.ScaleGestureDetector;
+import android.view.ScaleGestureDetector.OnScaleGestureListener;
+import android.view.ViewConfiguration;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.accessibility.gestures.GestureUtils;
+import com.android.server.accessibility.magnification.MagnificationGestureHandler;
+
+import java.util.ArrayDeque;
+import java.util.Queue;
+
+/**
+ * This class handles full screen magnification in response to touch events.
+ *
+ * The behavior is as follows:
+ *
+ * 1. Triple tap toggles permanent screen magnification which is magnifying
+ *    the area around the location of the triple tap. One can think of the
+ *    location of the triple tap as the center of the magnified viewport.
+ *    For example, a triple tap when not magnified would magnify the screen
+ *    and leave it in a magnified state. A triple tapping when magnified would
+ *    clear magnification and leave the screen in a not magnified state.
+ *
+ * 2. Triple tap and hold would magnify the screen if not magnified and enable
+ *    viewport dragging mode until the finger goes up. One can think of this
+ *    mode as a way to move the magnified viewport since the area around the
+ *    moving finger will be magnified to fit the screen. For example, if the
+ *    screen was not magnified and the user triple taps and holds the screen
+ *    would magnify and the viewport will follow the user's finger. When the
+ *    finger goes up the screen will zoom out. If the same user interaction
+ *    is performed when the screen is magnified, the viewport movement will
+ *    be the same but when the finger goes up the screen will stay magnified.
+ *    In other words, the initial magnified state is sticky.
+ *
+ * 3. Magnification can optionally be "triggered" by some external shortcut
+ *    affordance. When this occurs via {@link #notifyShortcutTriggered()} a
+ *    subsequent tap in a magnifiable region will engage permanent screen
+ *    magnification as described in #1. Alternatively, a subsequent long-press
+ *    or drag will engage magnification with viewport dragging as described in
+ *    #2. Once magnified, all following behaviors apply whether magnification
+ *    was engaged via a triple-tap or by a triggered shortcut.
+ *
+ * 4. Pinching with any number of additional fingers when viewport dragging
+ *    is enabled, i.e. the user triple tapped and holds, would adjust the
+ *    magnification scale which will become the current default magnification
+ *    scale. The next time the user magnifies the same magnification scale
+ *    would be used.
+ *
+ * 5. When in a permanent magnified state the user can use two or more fingers
+ *    to pan the viewport. Note that in this mode the content is panned as
+ *    opposed to the viewport dragging mode in which the viewport is moved.
+ *
+ * 6. When in a permanent magnified state the user can use two or more
+ *    fingers to change the magnification scale which will become the current
+ *    default magnification scale. The next time the user magnifies the same
+ *    magnification scale would be used.
+ *
+ * 7. The magnification scale will be persisted in settings and in the cloud.
+ */
+@SuppressWarnings("WeakerAccess")
+class FullScreenMagnificationGestureHandler extends MagnificationGestureHandler {
+    private static final String LOG_TAG = "FullScreenMagnificationGestureHandler";
+
+    private static final boolean DEBUG_ALL = false;
+    private static final boolean DEBUG_STATE_TRANSITIONS = false | DEBUG_ALL;
+    private static final boolean DEBUG_DETECTING = false | DEBUG_ALL;
+    private static final boolean DEBUG_PANNING_SCALING = false | DEBUG_ALL;
+    private static final boolean DEBUG_EVENT_STREAM = false | DEBUG_ALL;
+
+    // The MIN_SCALE is different from MagnificationController.MIN_SCALE due
+    // to AccessibilityService.MagnificationController#setScale() has
+    // different scale range
+    private static final float MIN_SCALE = 2.0f;
+    private static final float MAX_SCALE = MagnificationController.MAX_SCALE;
+
+    @VisibleForTesting final MagnificationController mMagnificationController;
+
+    @VisibleForTesting final DelegatingState mDelegatingState;
+    @VisibleForTesting final DetectingState mDetectingState;
+    @VisibleForTesting final PanningScalingState mPanningScalingState;
+    @VisibleForTesting final ViewportDraggingState mViewportDraggingState;
+
+    private final ScreenStateReceiver mScreenStateReceiver;
+
+    /**
+     * {@code true} if this detector should detect and respond to triple-tap
+     * gestures for engaging and disengaging magnification,
+     * {@code false} if it should ignore such gestures
+     */
+    final boolean mDetectTripleTap;
+
+    /**
+     * Whether {@link DetectingState#mShortcutTriggered shortcut} is enabled
+     */
+    final boolean mDetectShortcutTrigger;
+
+    @VisibleForTesting State mCurrentState;
+    @VisibleForTesting State mPreviousState;
+
+    private PointerCoords[] mTempPointerCoords;
+    private PointerProperties[] mTempPointerProperties;
+
+    private final int mDisplayId;
+
+    private final Queue<MotionEvent> mDebugInputEventHistory;
+    private final Queue<MotionEvent> mDebugOutputEventHistory;
+
+    /**
+     * @param context Context for resolving various magnification-related resources
+     * @param magnificationController the {@link MagnificationController}
+     *
+     * @param detectTripleTap {@code true} if this detector should detect and respond to triple-tap
+     *                                gestures for engaging and disengaging magnification,
+     *                                {@code false} if it should ignore such gestures
+     * @param detectShortcutTrigger {@code true} if this detector should be "triggerable" by some
+     *                           external shortcut invoking {@link #notifyShortcutTriggered},
+     *                           {@code false} if it should ignore such triggers.
+     * @param displayId The logical display id.
+     */
+    FullScreenMagnificationGestureHandler(Context context,
+            MagnificationController magnificationController,
+            boolean detectTripleTap,
+            boolean detectShortcutTrigger,
+            int displayId) {
+        if (DEBUG_ALL) {
+            Log.i(LOG_TAG,
+                    "FullScreenMagnificationGestureHandler(detectTripleTap = " + detectTripleTap
+                            + ", detectShortcutTrigger = " + detectShortcutTrigger + ")");
+        }
+
+        mMagnificationController = magnificationController;
+        mDisplayId = displayId;
+
+        mDelegatingState = new DelegatingState();
+        mDetectingState = new DetectingState(context);
+        mViewportDraggingState = new ViewportDraggingState();
+        mPanningScalingState = new PanningScalingState(context);
+
+        mDetectTripleTap = detectTripleTap;
+        mDetectShortcutTrigger = detectShortcutTrigger;
+
+        if (mDetectShortcutTrigger) {
+            mScreenStateReceiver = new ScreenStateReceiver(context, this);
+            mScreenStateReceiver.register();
+        } else {
+            mScreenStateReceiver = null;
+        }
+
+        mDebugInputEventHistory = DEBUG_EVENT_STREAM ? new ArrayDeque<>() : null;
+        mDebugOutputEventHistory = DEBUG_EVENT_STREAM ? new ArrayDeque<>() : null;
+
+        transitionTo(mDetectingState);
+    }
+
+    @Override
+    public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (DEBUG_EVENT_STREAM) {
+            storeEventInto(mDebugInputEventHistory, event);
+            try {
+                onMotionEventInternal(event, rawEvent, policyFlags);
+            } catch (Exception e) {
+                throw new RuntimeException(
+                        "Exception following input events: " + mDebugInputEventHistory, e);
+            }
+        } else {
+            onMotionEventInternal(event, rawEvent, policyFlags);
+        }
+    }
+
+    private void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (DEBUG_ALL) Slog.i(LOG_TAG, "onMotionEvent(" + event + ")");
+
+        if ((!mDetectTripleTap && !mDetectShortcutTrigger)
+                || !event.isFromSource(SOURCE_TOUCHSCREEN)) {
+            dispatchTransformedEvent(event, rawEvent, policyFlags);
+            return;
+        }
+
+        handleEventWith(mCurrentState, event, rawEvent, policyFlags);
+    }
+
+    private void handleEventWith(State stateHandler,
+            MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        // To keep InputEventConsistencyVerifiers within GestureDetectors happy
+        mPanningScalingState.mScrollGestureDetector.onTouchEvent(event);
+        mPanningScalingState.mScaleGestureDetector.onTouchEvent(event);
+
+        stateHandler.onMotionEvent(event, rawEvent, policyFlags);
+    }
+
+    @Override
+    public void clearEvents(int inputSource) {
+        if (inputSource == SOURCE_TOUCHSCREEN) {
+            clearAndTransitionToStateDetecting();
+        }
+
+        super.clearEvents(inputSource);
+    }
+
+    @Override
+    public void onDestroy() {
+        if (DEBUG_STATE_TRANSITIONS) {
+            Slog.i(LOG_TAG, "onDestroy(); delayed = "
+                    + MotionEventInfo.toString(mDetectingState.mDelayedEventQueue));
+        }
+
+        if (mScreenStateReceiver != null) {
+            mScreenStateReceiver.unregister();
+        }
+        // Check if need to reset when MagnificationGestureHandler is the last magnifying service.
+        mMagnificationController.resetAllIfNeeded(
+                AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+        clearAndTransitionToStateDetecting();
+    }
+
+    @Override
+    public void notifyShortcutTriggered() {
+        if (mDetectShortcutTrigger) {
+            boolean wasMagnifying = mMagnificationController.resetIfNeeded(mDisplayId,
+                    /* animate */ true);
+            if (wasMagnifying) {
+                clearAndTransitionToStateDetecting();
+            } else {
+                mDetectingState.toggleShortcutTriggered();
+            }
+        }
+    }
+
+    void clearAndTransitionToStateDetecting() {
+        mCurrentState = mDetectingState;
+        mDetectingState.clear();
+        mViewportDraggingState.clear();
+        mPanningScalingState.clear();
+    }
+
+    private void dispatchTransformedEvent(MotionEvent event, MotionEvent rawEvent,
+            int policyFlags) {
+        if (DEBUG_EVENT_STREAM) {
+            storeEventInto(mDebugOutputEventHistory, event);
+            try {
+                super.onMotionEvent(event, rawEvent, policyFlags);
+            } catch (Exception e) {
+                throw new RuntimeException(
+                        "Exception downstream following input events: " + mDebugInputEventHistory
+                                + "\nTransformed into output events: " + mDebugOutputEventHistory,
+                        e);
+            }
+        } else {
+            super.onMotionEvent(event, rawEvent, policyFlags);
+        }
+    }
+
+    private static void storeEventInto(Queue<MotionEvent> queue, MotionEvent event) {
+        queue.add(MotionEvent.obtain(event));
+        // Prune old events
+        while (!queue.isEmpty() && (event.getEventTime() - queue.peek().getEventTime() > 5000)) {
+            queue.remove().recycle();
+        }
+    }
+
+    private PointerCoords[] getTempPointerCoordsWithMinSize(int size) {
+        final int oldSize = (mTempPointerCoords != null) ? mTempPointerCoords.length : 0;
+        if (oldSize < size) {
+            PointerCoords[] oldTempPointerCoords = mTempPointerCoords;
+            mTempPointerCoords = new PointerCoords[size];
+            if (oldTempPointerCoords != null) {
+                System.arraycopy(oldTempPointerCoords, 0, mTempPointerCoords, 0, oldSize);
+            }
+        }
+        for (int i = oldSize; i < size; i++) {
+            mTempPointerCoords[i] = new PointerCoords();
+        }
+        return mTempPointerCoords;
+    }
+
+    private PointerProperties[] getTempPointerPropertiesWithMinSize(int size) {
+        final int oldSize = (mTempPointerProperties != null) ? mTempPointerProperties.length
+                : 0;
+        if (oldSize < size) {
+            PointerProperties[] oldTempPointerProperties = mTempPointerProperties;
+            mTempPointerProperties = new PointerProperties[size];
+            if (oldTempPointerProperties != null) {
+                System.arraycopy(oldTempPointerProperties, 0, mTempPointerProperties, 0,
+                        oldSize);
+            }
+        }
+        for (int i = oldSize; i < size; i++) {
+            mTempPointerProperties[i] = new PointerProperties();
+        }
+        return mTempPointerProperties;
+    }
+
+    private void transitionTo(State state) {
+        if (DEBUG_STATE_TRANSITIONS) {
+            Slog.i(LOG_TAG,
+                    (State.nameOf(mCurrentState) + " -> " + State.nameOf(state)
+                    + " at " + asList(copyOfRange(new RuntimeException().getStackTrace(), 1, 5)))
+                    .replace(getClass().getName(), ""));
+        }
+        mPreviousState = mCurrentState;
+        mCurrentState = state;
+    }
+
+    interface State {
+        void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags);
+
+        default void clear() {}
+
+        default String name() {
+            return getClass().getSimpleName();
+        }
+
+        static String nameOf(@Nullable State s) {
+            return s != null ? s.name() : "null";
+        }
+    }
+
+    /**
+     * This class determines if the user is performing a scale or pan gesture.
+     *
+     * Unlike when {@link ViewportDraggingState dragging the viewport}, in panning mode the viewport
+     * moves in the same direction as the fingers, and allows to easily and precisely scale the
+     * magnification level.
+     * This makes it the preferred mode for one-off adjustments, due to its precision and ease of
+     * triggering.
+     */
+    final class PanningScalingState extends SimpleOnGestureListener
+            implements OnScaleGestureListener, State {
+
+        private final ScaleGestureDetector mScaleGestureDetector;
+        private final GestureDetector mScrollGestureDetector;
+        final float mScalingThreshold;
+
+        float mInitialScaleFactor = -1;
+        boolean mScaling;
+
+        PanningScalingState(Context context) {
+            final TypedValue scaleValue = new TypedValue();
+            context.getResources().getValue(
+                    R.dimen.config_screen_magnification_scaling_threshold,
+                    scaleValue, false);
+            mScalingThreshold = scaleValue.getFloat();
+            mScaleGestureDetector = new ScaleGestureDetector(context, this, Handler.getMain());
+            mScaleGestureDetector.setQuickScaleEnabled(false);
+            mScrollGestureDetector = new GestureDetector(context, this, Handler.getMain());
+        }
+
+        @Override
+        public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+            int action = event.getActionMasked();
+
+            if (action == ACTION_POINTER_UP
+                    && event.getPointerCount() == 2 // includes the pointer currently being released
+                    && mPreviousState == mViewportDraggingState) {
+
+                persistScaleAndTransitionTo(mViewportDraggingState);
+
+            } else if (action == ACTION_UP || action == ACTION_CANCEL) {
+
+                persistScaleAndTransitionTo(mDetectingState);
+            }
+        }
+
+        public void persistScaleAndTransitionTo(State state) {
+            mMagnificationController.persistScale();
+            clear();
+            transitionTo(state);
+        }
+
+        @Override
+        public boolean onScroll(MotionEvent first, MotionEvent second,
+                float distanceX, float distanceY) {
+            if (mCurrentState != mPanningScalingState) {
+                return true;
+            }
+            if (DEBUG_PANNING_SCALING) {
+                Slog.i(LOG_TAG, "Panned content by scrollX: " + distanceX
+                        + " scrollY: " + distanceY);
+            }
+            mMagnificationController.offsetMagnifiedRegion(mDisplayId, distanceX,
+                    distanceY, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+            return /* event consumed: */ true;
+        }
+
+        @Override
+        public boolean onScale(ScaleGestureDetector detector) {
+            if (!mScaling) {
+                if (mInitialScaleFactor < 0) {
+                    mInitialScaleFactor = detector.getScaleFactor();
+                    return false;
+                }
+                final float deltaScale = detector.getScaleFactor() - mInitialScaleFactor;
+                mScaling = abs(deltaScale) > mScalingThreshold;
+                return mScaling;
+            }
+
+            final float initialScale = mMagnificationController.getScale(mDisplayId);
+            final float targetScale = initialScale * detector.getScaleFactor();
+
+            // Don't allow a gesture to move the user further outside the
+            // desired bounds for gesture-controlled scaling.
+            final float scale;
+            if (targetScale > MAX_SCALE && targetScale > initialScale) {
+                // The target scale is too big and getting bigger.
+                scale = MAX_SCALE;
+            } else if (targetScale < MIN_SCALE && targetScale < initialScale) {
+                // The target scale is too small and getting smaller.
+                scale = MIN_SCALE;
+            } else {
+                // The target scale may be outside our bounds, but at least
+                // it's moving in the right direction. This avoids a "jump" if
+                // we're at odds with some other service's desired bounds.
+                scale = targetScale;
+            }
+
+            final float pivotX = detector.getFocusX();
+            final float pivotY = detector.getFocusY();
+            if (DEBUG_PANNING_SCALING) Slog.i(LOG_TAG, "Scaled content to: " + scale + "x");
+            mMagnificationController.setScale(mDisplayId, scale, pivotX, pivotY, false,
+                    AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+            return /* handled: */ true;
+        }
+
+        @Override
+        public boolean onScaleBegin(ScaleGestureDetector detector) {
+            return /* continue recognizing: */ (mCurrentState == mPanningScalingState);
+        }
+
+        @Override
+        public void onScaleEnd(ScaleGestureDetector detector) {
+            clear();
+        }
+
+        @Override
+        public void clear() {
+            mInitialScaleFactor = -1;
+            mScaling = false;
+        }
+
+        @Override
+        public String toString() {
+            return "PanningScalingState{" + "mInitialScaleFactor=" + mInitialScaleFactor
+                    + ", mScaling=" + mScaling
+                    + '}';
+        }
+    }
+
+    /**
+     * This class handles motion events when the event dispatcher has
+     * determined that the user is performing a single-finger drag of the
+     * magnification viewport.
+     *
+     * Unlike when {@link PanningScalingState panning}, the viewport moves in the opposite direction
+     * of the finger, and any part of the screen is reachable without lifting the finger.
+     * This makes it the preferable mode for tasks like reading text spanning full screen width.
+     */
+    final class ViewportDraggingState implements State {
+
+        /** Whether to disable zoom after dragging ends */
+        boolean mZoomedInBeforeDrag;
+        private boolean mLastMoveOutsideMagnifiedRegion;
+
+        @Override
+        public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+            final int action = event.getActionMasked();
+            switch (action) {
+                case ACTION_POINTER_DOWN: {
+                    clear();
+                    transitionTo(mPanningScalingState);
+                }
+                break;
+                case ACTION_MOVE: {
+                    if (event.getPointerCount() != 1) {
+                        throw new IllegalStateException("Should have one pointer down.");
+                    }
+                    final float eventX = event.getX();
+                    final float eventY = event.getY();
+                    if (mMagnificationController.magnificationRegionContains(
+                            mDisplayId, eventX, eventY)) {
+                        mMagnificationController.setCenter(mDisplayId, eventX, eventY,
+                                /* animate */ mLastMoveOutsideMagnifiedRegion,
+                                AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+                        mLastMoveOutsideMagnifiedRegion = false;
+                    } else {
+                        mLastMoveOutsideMagnifiedRegion = true;
+                    }
+                }
+                break;
+
+                case ACTION_UP:
+                case ACTION_CANCEL: {
+                    if (!mZoomedInBeforeDrag) zoomOff();
+                    clear();
+                    transitionTo(mDetectingState);
+                }
+                    break;
+
+                case ACTION_DOWN:
+                case ACTION_POINTER_UP: {
+                    throw new IllegalArgumentException(
+                            "Unexpected event type: " + MotionEvent.actionToString(action));
+                }
+            }
+        }
+
+        @Override
+        public void clear() {
+            mLastMoveOutsideMagnifiedRegion = false;
+        }
+
+        @Override
+        public String toString() {
+            return "ViewportDraggingState{"
+                    + "mZoomedInBeforeDrag=" + mZoomedInBeforeDrag
+                    + ", mLastMoveOutsideMagnifiedRegion=" + mLastMoveOutsideMagnifiedRegion
+                    + '}';
+        }
+    }
+
+    final class DelegatingState implements State {
+        /**
+         * Time of last {@link MotionEvent#ACTION_DOWN} while in {@link DelegatingState}
+         */
+        public long mLastDelegatedDownEventTime;
+
+        @Override
+        public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+
+            // Ensures that the state at the end of delegation is consistent with the last delegated
+            // UP/DOWN event in queue: still delegating if pointer is down, detecting otherwise
+            switch (event.getActionMasked()) {
+                case ACTION_UP:
+                case ACTION_CANCEL: {
+                    transitionTo(mDetectingState);
+                }
+                    break;
+
+                case ACTION_DOWN: {
+                    transitionTo(mDelegatingState);
+                    mLastDelegatedDownEventTime = event.getDownTime();
+                } break;
+            }
+
+            if (getNext() != null) {
+                // We cache some events to see if the user wants to trigger magnification.
+                // If no magnification is triggered we inject these events with adjusted
+                // time and down time to prevent subsequent transformations being confused
+                // by stale events. After the cached events, which always have a down, are
+                // injected we need to also update the down time of all subsequent non cached
+                // events. All delegated events cached and non-cached are delivered here.
+                event.setDownTime(mLastDelegatedDownEventTime);
+                dispatchTransformedEvent(event, rawEvent, policyFlags);
+            }
+        }
+    }
+
+    /**
+     * This class handles motion events when the event dispatch has not yet
+     * determined what the user is doing. It watches for various tap events.
+     */
+    final class DetectingState implements State, Handler.Callback {
+
+        private static final int MESSAGE_ON_TRIPLE_TAP_AND_HOLD = 1;
+        private static final int MESSAGE_TRANSITION_TO_DELEGATING_STATE = 2;
+
+        final int mLongTapMinDelay;
+        final int mSwipeMinDistance;
+        final int mMultiTapMaxDelay;
+        final int mMultiTapMaxDistance;
+
+        private MotionEventInfo mDelayedEventQueue;
+        MotionEvent mLastDown;
+        private MotionEvent mPreLastDown;
+        private MotionEvent mLastUp;
+        private MotionEvent mPreLastUp;
+
+        private long mLastDetectingDownEventTime;
+
+        @VisibleForTesting boolean mShortcutTriggered;
+
+        @VisibleForTesting Handler mHandler = new Handler(Looper.getMainLooper(), this);
+
+        DetectingState(Context context) {
+            mLongTapMinDelay = ViewConfiguration.getLongPressTimeout();
+            mMultiTapMaxDelay = ViewConfiguration.getDoubleTapTimeout()
+                    + context.getResources().getInteger(
+                    R.integer.config_screen_magnification_multi_tap_adjustment);
+            mSwipeMinDistance = ViewConfiguration.get(context).getScaledTouchSlop();
+            mMultiTapMaxDistance = ViewConfiguration.get(context).getScaledDoubleTapSlop();
+        }
+
+        @Override
+        public boolean handleMessage(Message message) {
+            final int type = message.what;
+            switch (type) {
+                case MESSAGE_ON_TRIPLE_TAP_AND_HOLD: {
+                    MotionEvent down = (MotionEvent) message.obj;
+                    transitionToViewportDraggingStateAndClear(down);
+                    down.recycle();
+                }
+                break;
+                case MESSAGE_TRANSITION_TO_DELEGATING_STATE: {
+                    transitionToDelegatingStateAndClear();
+                }
+                break;
+                default: {
+                    throw new IllegalArgumentException("Unknown message type: " + type);
+                }
+            }
+            return true;
+        }
+
+        @Override
+        public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+            cacheDelayedMotionEvent(event, rawEvent, policyFlags);
+            switch (event.getActionMasked()) {
+                case MotionEvent.ACTION_DOWN: {
+
+                    mLastDetectingDownEventTime = event.getDownTime();
+                    mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
+
+                    if (!mMagnificationController.magnificationRegionContains(
+                            mDisplayId, event.getX(), event.getY())) {
+
+                        transitionToDelegatingStateAndClear();
+
+                    } else if (isMultiTapTriggered(2 /* taps */)) {
+
+                        // 3tap and hold
+                        afterLongTapTimeoutTransitionToDraggingState(event);
+
+                    } else if (isTapOutOfDistanceSlop()) {
+
+                        transitionToDelegatingStateAndClear();
+
+                    } else if (mDetectTripleTap
+                            // If magnified, delay an ACTION_DOWN for mMultiTapMaxDelay
+                            // to ensure reachability of
+                            // STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN)
+                            || mMagnificationController.isMagnifying(mDisplayId)) {
+
+                        afterMultiTapTimeoutTransitionToDelegatingState();
+
+                    } else {
+
+                        // Delegate pending events without delay
+                        transitionToDelegatingStateAndClear();
+                    }
+                }
+                break;
+                case ACTION_POINTER_DOWN: {
+                    if (mMagnificationController.isMagnifying(mDisplayId)) {
+                        transitionTo(mPanningScalingState);
+                        clear();
+                    } else {
+                        transitionToDelegatingStateAndClear();
+                    }
+                }
+                break;
+                case ACTION_MOVE: {
+                    if (isFingerDown()
+                            && distance(mLastDown, /* move */ event) > mSwipeMinDistance) {
+
+                        // Swipe detected - transition immediately
+
+                        // For convenience, viewport dragging takes precedence
+                        // over insta-delegating on 3tap&swipe
+                        // (which is a rare combo to be used aside from magnification)
+                        if (isMultiTapTriggered(2 /* taps */)) {
+                            transitionToViewportDraggingStateAndClear(event);
+                        } else {
+                            transitionToDelegatingStateAndClear();
+                        }
+                    }
+                }
+                break;
+                case ACTION_UP: {
+
+                    mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD);
+
+                    if (!mMagnificationController.magnificationRegionContains(
+                            mDisplayId, event.getX(), event.getY())) {
+
+                        transitionToDelegatingStateAndClear();
+
+                    } else if (isMultiTapTriggered(3 /* taps */)) {
+
+                        onTripleTap(/* up */ event);
+
+                    } else if (
+                            // Possible to be false on: 3tap&drag -> scale -> PTR_UP -> UP
+                            isFingerDown()
+                            //TODO long tap should never happen here
+                            && ((timeBetween(mLastDown, mLastUp) >= mLongTapMinDelay)
+                                    || (distance(mLastDown, mLastUp) >= mSwipeMinDistance))) {
+
+                        transitionToDelegatingStateAndClear();
+
+                    }
+                }
+                break;
+            }
+        }
+
+        public boolean isMultiTapTriggered(int numTaps) {
+
+            // Shortcut acts as the 2 initial taps
+            if (mShortcutTriggered) return tapCount() + 2 >= numTaps;
+
+            return mDetectTripleTap
+                    && tapCount() >= numTaps
+                    && isMultiTap(mPreLastDown, mLastDown)
+                    && isMultiTap(mPreLastUp, mLastUp);
+        }
+
+        private boolean isMultiTap(MotionEvent first, MotionEvent second) {
+            return GestureUtils.isMultiTap(first, second, mMultiTapMaxDelay, mMultiTapMaxDistance);
+        }
+
+        public boolean isFingerDown() {
+            return mLastDown != null;
+        }
+
+        private long timeBetween(@Nullable MotionEvent a, @Nullable MotionEvent b) {
+            if (a == null && b == null) return 0;
+            return abs(timeOf(a) - timeOf(b));
+        }
+
+        /**
+         * Nullsafe {@link MotionEvent#getEventTime} that interprets null event as something that
+         * has happened long enough ago to be gone from the event queue.
+         * Thus the time for a null event is a small number, that is below any other non-null
+         * event's time.
+         *
+         * @return {@link MotionEvent#getEventTime}, or {@link Long#MIN_VALUE} if the event is null
+         */
+        private long timeOf(@Nullable MotionEvent event) {
+            return event != null ? event.getEventTime() : Long.MIN_VALUE;
+        }
+
+        public int tapCount() {
+            return MotionEventInfo.countOf(mDelayedEventQueue, ACTION_UP);
+        }
+
+        /** -> {@link DelegatingState} */
+        public void afterMultiTapTimeoutTransitionToDelegatingState() {
+            mHandler.sendEmptyMessageDelayed(
+                    MESSAGE_TRANSITION_TO_DELEGATING_STATE,
+                    mMultiTapMaxDelay);
+        }
+
+        /** -> {@link ViewportDraggingState} */
+        public void afterLongTapTimeoutTransitionToDraggingState(MotionEvent event) {
+            mHandler.sendMessageDelayed(
+                    mHandler.obtainMessage(MESSAGE_ON_TRIPLE_TAP_AND_HOLD,
+                            MotionEvent.obtain(event)),
+                    ViewConfiguration.getLongPressTimeout());
+        }
+
+        @Override
+        public void clear() {
+            setShortcutTriggered(false);
+            removePendingDelayedMessages();
+            clearDelayedMotionEvents();
+        }
+
+        private void removePendingDelayedMessages() {
+            mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD);
+            mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
+        }
+
+        private void cacheDelayedMotionEvent(MotionEvent event, MotionEvent rawEvent,
+                int policyFlags) {
+            if (event.getActionMasked() == ACTION_DOWN) {
+                mPreLastDown = mLastDown;
+                mLastDown = MotionEvent.obtain(event);
+            } else if (event.getActionMasked() == ACTION_UP) {
+                mPreLastUp = mLastUp;
+                mLastUp = MotionEvent.obtain(event);
+            }
+
+            MotionEventInfo info = MotionEventInfo.obtain(event, rawEvent,
+                    policyFlags);
+            if (mDelayedEventQueue == null) {
+                mDelayedEventQueue = info;
+            } else {
+                MotionEventInfo tail = mDelayedEventQueue;
+                while (tail.mNext != null) {
+                    tail = tail.mNext;
+                }
+                tail.mNext = info;
+            }
+        }
+
+        private void sendDelayedMotionEvents() {
+            if (mDelayedEventQueue == null) {
+                return;
+            }
+
+            // Adjust down time to prevent subsequent modules being misleading, and also limit
+            // the maximum offset to mMultiTapMaxDelay to prevent the down time of 2nd tap is
+            // in the future when multi-tap happens.
+            final long offset = Math.min(
+                    SystemClock.uptimeMillis() - mLastDetectingDownEventTime, mMultiTapMaxDelay);
+
+            do {
+                MotionEventInfo info = mDelayedEventQueue;
+                mDelayedEventQueue = info.mNext;
+
+                info.event.setDownTime(info.event.getDownTime() + offset);
+                handleEventWith(mDelegatingState, info.event, info.rawEvent, info.policyFlags);
+
+                info.recycle();
+            } while (mDelayedEventQueue != null);
+        }
+
+        private void clearDelayedMotionEvents() {
+            while (mDelayedEventQueue != null) {
+                MotionEventInfo info = mDelayedEventQueue;
+                mDelayedEventQueue = info.mNext;
+                info.recycle();
+            }
+            mPreLastDown = null;
+            mPreLastUp = null;
+            mLastDown = null;
+            mLastUp = null;
+        }
+
+        void transitionToDelegatingStateAndClear() {
+            transitionTo(mDelegatingState);
+            sendDelayedMotionEvents();
+            removePendingDelayedMessages();
+        }
+
+        private void onTripleTap(MotionEvent up) {
+
+            if (DEBUG_DETECTING) {
+                Slog.i(LOG_TAG, "onTripleTap(); delayed: "
+                        + MotionEventInfo.toString(mDelayedEventQueue));
+            }
+            clear();
+
+            // Toggle zoom
+            if (mMagnificationController.isMagnifying(mDisplayId)) {
+                zoomOff();
+            } else {
+                zoomOn(up.getX(), up.getY());
+            }
+        }
+
+        void transitionToViewportDraggingStateAndClear(MotionEvent down) {
+
+            if (DEBUG_DETECTING) Slog.i(LOG_TAG, "onTripleTapAndHold()");
+            clear();
+
+            mViewportDraggingState.mZoomedInBeforeDrag =
+                    mMagnificationController.isMagnifying(mDisplayId);
+
+            zoomOn(down.getX(), down.getY());
+
+            transitionTo(mViewportDraggingState);
+        }
+
+        @Override
+        public String toString() {
+            return "DetectingState{"
+                    + "tapCount()=" + tapCount()
+                    + ", mShortcutTriggered=" + mShortcutTriggered
+                    + ", mDelayedEventQueue=" + MotionEventInfo.toString(mDelayedEventQueue)
+                    + '}';
+        }
+
+        void toggleShortcutTriggered() {
+            setShortcutTriggered(!mShortcutTriggered);
+        }
+
+        void setShortcutTriggered(boolean state) {
+            if (mShortcutTriggered == state) {
+                return;
+            }
+            if (DEBUG_DETECTING) Slog.i(LOG_TAG, "setShortcutTriggered(" + state + ")");
+
+            mShortcutTriggered = state;
+            mMagnificationController.setForceShowMagnifiableBounds(mDisplayId, state);
+        }
+
+        /**
+         * Detects if last action down is out of distance slop between with previous
+         * one, when triple tap is enabled.
+         *
+         * @return true if tap is out of distance slop
+         */
+        boolean isTapOutOfDistanceSlop() {
+            if (!mDetectTripleTap) return false;
+            if (mPreLastDown == null || mLastDown == null) {
+                return false;
+            }
+            final boolean outOfDistanceSlop =
+                    GestureUtils.distance(mPreLastDown, mLastDown) > mMultiTapMaxDistance;
+            if (tapCount() > 0) {
+                return outOfDistanceSlop;
+            }
+            // There's no tap in the queue here. We still need to check if this is the case that
+            // user tap screen quickly and out of distance slop.
+            if (outOfDistanceSlop
+                    && !GestureUtils.isTimedOut(mPreLastDown, mLastDown, mMultiTapMaxDelay)) {
+                return true;
+            }
+            return false;
+        }
+    }
+
+    private void zoomOn(float centerX, float centerY) {
+        if (DEBUG_DETECTING) Slog.i(LOG_TAG, "zoomOn(" + centerX + ", " + centerY + ")");
+
+        final float scale = MathUtils.constrain(
+                mMagnificationController.getPersistedScale(),
+                MIN_SCALE, MAX_SCALE);
+        mMagnificationController.setScaleAndCenter(mDisplayId,
+                scale, centerX, centerY,
+                /* animate */ true,
+                AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+    }
+
+    private void zoomOff() {
+        if (DEBUG_DETECTING) Slog.i(LOG_TAG, "zoomOff()");
+        mMagnificationController.reset(mDisplayId, /* animate */ true);
+    }
+
+    private static MotionEvent recycleAndNullify(@Nullable MotionEvent event) {
+        if (event != null) {
+            event.recycle();
+        }
+        return null;
+    }
+
+    @Override
+    public String toString() {
+        return "MagnificationGesture{"
+                + "mDetectingState=" + mDetectingState
+                + ", mDelegatingState=" + mDelegatingState
+                + ", mMagnifiedInteractionState=" + mPanningScalingState
+                + ", mViewportDraggingState=" + mViewportDraggingState
+                + ", mDetectTripleTap=" + mDetectTripleTap
+                + ", mDetectShortcutTrigger=" + mDetectShortcutTrigger
+                + ", mCurrentState=" + State.nameOf(mCurrentState)
+                + ", mPreviousState=" + State.nameOf(mPreviousState)
+                + ", mMagnificationController=" + mMagnificationController
+                + ", mDisplayId=" + mDisplayId
+                + '}';
+    }
+
+    private static final class MotionEventInfo {
+
+        private static final int MAX_POOL_SIZE = 10;
+        private static final Object sLock = new Object();
+        private static MotionEventInfo sPool;
+        private static int sPoolSize;
+
+        private MotionEventInfo mNext;
+        private boolean mInPool;
+
+        public MotionEvent event;
+        public MotionEvent rawEvent;
+        public int policyFlags;
+
+        public static MotionEventInfo obtain(MotionEvent event, MotionEvent rawEvent,
+                int policyFlags) {
+            synchronized (sLock) {
+                MotionEventInfo info = obtainInternal();
+                info.initialize(event, rawEvent, policyFlags);
+                return info;
+            }
+        }
+
+        @NonNull
+        private static MotionEventInfo obtainInternal() {
+            MotionEventInfo info;
+            if (sPoolSize > 0) {
+                sPoolSize--;
+                info = sPool;
+                sPool = info.mNext;
+                info.mNext = null;
+                info.mInPool = false;
+            } else {
+                info = new MotionEventInfo();
+            }
+            return info;
+        }
+
+        private void initialize(MotionEvent event, MotionEvent rawEvent,
+                int policyFlags) {
+            this.event = MotionEvent.obtain(event);
+            this.rawEvent = MotionEvent.obtain(rawEvent);
+            this.policyFlags = policyFlags;
+        }
+
+        public void recycle() {
+            synchronized (sLock) {
+                if (mInPool) {
+                    throw new IllegalStateException("Already recycled.");
+                }
+                clear();
+                if (sPoolSize < MAX_POOL_SIZE) {
+                    sPoolSize++;
+                    mNext = sPool;
+                    sPool = this;
+                    mInPool = true;
+                }
+            }
+        }
+
+        private void clear() {
+            event = recycleAndNullify(event);
+            rawEvent = recycleAndNullify(rawEvent);
+            policyFlags = 0;
+        }
+
+        static int countOf(MotionEventInfo info, int eventType) {
+            if (info == null) return 0;
+            return (info.event.getAction() == eventType ? 1 : 0)
+                    + countOf(info.mNext, eventType);
+        }
+
+        public static String toString(MotionEventInfo info) {
+            return info == null
+                    ? ""
+                    : MotionEvent.actionToString(info.event.getAction()).replace("ACTION_", "")
+                            + " " + MotionEventInfo.toString(info.mNext);
+        }
+    }
+
+    /**
+     * BroadcastReceiver used to cancel the magnification shortcut when the screen turns off
+     */
+    private static class ScreenStateReceiver extends BroadcastReceiver {
+        private final Context mContext;
+        private final FullScreenMagnificationGestureHandler mGestureHandler;
+
+        ScreenStateReceiver(Context context,
+                FullScreenMagnificationGestureHandler gestureHandler) {
+            mContext = context;
+            mGestureHandler = gestureHandler;
+        }
+
+        public void register() {
+            mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF));
+        }
+
+        public void unregister() {
+            mContext.unregisterReceiver(this);
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mGestureHandler.mDetectingState.setShortcutTriggered(false);
+        }
+    }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
deleted file mode 100644
index 06ca054..0000000
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
+++ /dev/null
@@ -1,1108 +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.server.accessibility;
-
-import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
-import static android.view.MotionEvent.ACTION_CANCEL;
-import static android.view.MotionEvent.ACTION_DOWN;
-import static android.view.MotionEvent.ACTION_MOVE;
-import static android.view.MotionEvent.ACTION_POINTER_DOWN;
-import static android.view.MotionEvent.ACTION_POINTER_UP;
-import static android.view.MotionEvent.ACTION_UP;
-
-import static com.android.server.accessibility.gestures.GestureUtils.distance;
-
-import static java.lang.Math.abs;
-import static java.util.Arrays.asList;
-import static java.util.Arrays.copyOfRange;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.SystemClock;
-import android.util.Log;
-import android.util.MathUtils;
-import android.util.Slog;
-import android.util.TypedValue;
-import android.view.GestureDetector;
-import android.view.GestureDetector.SimpleOnGestureListener;
-import android.view.MotionEvent;
-import android.view.MotionEvent.PointerCoords;
-import android.view.MotionEvent.PointerProperties;
-import android.view.ScaleGestureDetector;
-import android.view.ScaleGestureDetector.OnScaleGestureListener;
-import android.view.ViewConfiguration;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.accessibility.gestures.GestureUtils;
-
-import java.util.ArrayDeque;
-import java.util.Queue;
-
-/**
- * This class handles magnification in response to touch events.
- *
- * The behavior is as follows:
- *
- * 1. Triple tap toggles permanent screen magnification which is magnifying
- *    the area around the location of the triple tap. One can think of the
- *    location of the triple tap as the center of the magnified viewport.
- *    For example, a triple tap when not magnified would magnify the screen
- *    and leave it in a magnified state. A triple tapping when magnified would
- *    clear magnification and leave the screen in a not magnified state.
- *
- * 2. Triple tap and hold would magnify the screen if not magnified and enable
- *    viewport dragging mode until the finger goes up. One can think of this
- *    mode as a way to move the magnified viewport since the area around the
- *    moving finger will be magnified to fit the screen. For example, if the
- *    screen was not magnified and the user triple taps and holds the screen
- *    would magnify and the viewport will follow the user's finger. When the
- *    finger goes up the screen will zoom out. If the same user interaction
- *    is performed when the screen is magnified, the viewport movement will
- *    be the same but when the finger goes up the screen will stay magnified.
- *    In other words, the initial magnified state is sticky.
- *
- * 3. Magnification can optionally be "triggered" by some external shortcut
- *    affordance. When this occurs via {@link #notifyShortcutTriggered()} a
- *    subsequent tap in a magnifiable region will engage permanent screen
- *    magnification as described in #1. Alternatively, a subsequent long-press
- *    or drag will engage magnification with viewport dragging as described in
- *    #2. Once magnified, all following behaviors apply whether magnification
- *    was engaged via a triple-tap or by a triggered shortcut.
- *
- * 4. Pinching with any number of additional fingers when viewport dragging
- *    is enabled, i.e. the user triple tapped and holds, would adjust the
- *    magnification scale which will become the current default magnification
- *    scale. The next time the user magnifies the same magnification scale
- *    would be used.
- *
- * 5. When in a permanent magnified state the user can use two or more fingers
- *    to pan the viewport. Note that in this mode the content is panned as
- *    opposed to the viewport dragging mode in which the viewport is moved.
- *
- * 6. When in a permanent magnified state the user can use two or more
- *    fingers to change the magnification scale which will become the current
- *    default magnification scale. The next time the user magnifies the same
- *    magnification scale would be used.
- *
- * 7. The magnification scale will be persisted in settings and in the cloud.
- */
-@SuppressWarnings("WeakerAccess")
-class MagnificationGestureHandler extends BaseEventStreamTransformation {
-    private static final String LOG_TAG = "MagnificationGestureHandler";
-
-    private static final boolean DEBUG_ALL = false;
-    private static final boolean DEBUG_STATE_TRANSITIONS = false || DEBUG_ALL;
-    private static final boolean DEBUG_DETECTING = false || DEBUG_ALL;
-    private static final boolean DEBUG_PANNING_SCALING = false || DEBUG_ALL;
-    private static final boolean DEBUG_EVENT_STREAM = false || DEBUG_ALL;
-
-    // The MIN_SCALE is different from MagnificationController.MIN_SCALE due
-    // to AccessibilityService.MagnificationController#setScale() has
-    // different scale range
-    private static final float MIN_SCALE = 2.0f;
-    private static final float MAX_SCALE = MagnificationController.MAX_SCALE;
-
-    @VisibleForTesting final MagnificationController mMagnificationController;
-
-    @VisibleForTesting final DelegatingState mDelegatingState;
-    @VisibleForTesting final DetectingState mDetectingState;
-    @VisibleForTesting final PanningScalingState mPanningScalingState;
-    @VisibleForTesting final ViewportDraggingState mViewportDraggingState;
-
-    private final ScreenStateReceiver mScreenStateReceiver;
-
-    /**
-     * {@code true} if this detector should detect and respond to triple-tap
-     * gestures for engaging and disengaging magnification,
-     * {@code false} if it should ignore such gestures
-     */
-    final boolean mDetectTripleTap;
-
-    /**
-     * Whether {@link DetectingState#mShortcutTriggered shortcut} is enabled
-     */
-    final boolean mDetectShortcutTrigger;
-
-    @VisibleForTesting State mCurrentState;
-    @VisibleForTesting State mPreviousState;
-
-    private PointerCoords[] mTempPointerCoords;
-    private PointerProperties[] mTempPointerProperties;
-
-    private final int mDisplayId;
-
-    private final Queue<MotionEvent> mDebugInputEventHistory;
-    private final Queue<MotionEvent> mDebugOutputEventHistory;
-
-    /**
-     * @param context Context for resolving various magnification-related resources
-     * @param magnificationController the {@link MagnificationController}
-     *
-     * @param detectTripleTap {@code true} if this detector should detect and respond to triple-tap
-     *                                gestures for engaging and disengaging magnification,
-     *                                {@code false} if it should ignore such gestures
-     * @param detectShortcutTrigger {@code true} if this detector should be "triggerable" by some
-     *                           external shortcut invoking {@link #notifyShortcutTriggered},
-     *                           {@code false} if it should ignore such triggers.
-     * @param displayId The logical display id.
-     */
-    public MagnificationGestureHandler(Context context,
-            MagnificationController magnificationController,
-            boolean detectTripleTap,
-            boolean detectShortcutTrigger,
-            int displayId) {
-        if (DEBUG_ALL) {
-            Log.i(LOG_TAG,
-                    "MagnificationGestureHandler(detectTripleTap = " + detectTripleTap
-                            + ", detectShortcutTrigger = " + detectShortcutTrigger + ")");
-        }
-
-        mMagnificationController = magnificationController;
-        mDisplayId = displayId;
-
-        mDelegatingState = new DelegatingState();
-        mDetectingState = new DetectingState(context);
-        mViewportDraggingState = new ViewportDraggingState();
-        mPanningScalingState = new PanningScalingState(context);
-
-        mDetectTripleTap = detectTripleTap;
-        mDetectShortcutTrigger = detectShortcutTrigger;
-
-        if (mDetectShortcutTrigger) {
-            mScreenStateReceiver = new ScreenStateReceiver(context, this);
-            mScreenStateReceiver.register();
-        } else {
-            mScreenStateReceiver = null;
-        }
-
-        mDebugInputEventHistory = DEBUG_EVENT_STREAM ? new ArrayDeque<>() : null;
-        mDebugOutputEventHistory = DEBUG_EVENT_STREAM ? new ArrayDeque<>() : null;
-
-        transitionTo(mDetectingState);
-    }
-
-    @Override
-    public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
-        if (DEBUG_EVENT_STREAM) {
-            storeEventInto(mDebugInputEventHistory, event);
-            try {
-                onMotionEventInternal(event, rawEvent, policyFlags);
-            } catch (Exception e) {
-                throw new RuntimeException(
-                        "Exception following input events: " + mDebugInputEventHistory, e);
-            }
-        } else {
-            onMotionEventInternal(event, rawEvent, policyFlags);
-        }
-    }
-
-    private void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
-        if (DEBUG_ALL) Slog.i(LOG_TAG, "onMotionEvent(" + event + ")");
-
-        if ((!mDetectTripleTap && !mDetectShortcutTrigger)
-                || !event.isFromSource(SOURCE_TOUCHSCREEN)) {
-            dispatchTransformedEvent(event, rawEvent, policyFlags);
-            return;
-        }
-
-        handleEventWith(mCurrentState, event, rawEvent, policyFlags);
-    }
-
-    private void handleEventWith(State stateHandler,
-            MotionEvent event, MotionEvent rawEvent, int policyFlags) {
-        // To keep InputEventConsistencyVerifiers within GestureDetectors happy
-        mPanningScalingState.mScrollGestureDetector.onTouchEvent(event);
-        mPanningScalingState.mScaleGestureDetector.onTouchEvent(event);
-
-        stateHandler.onMotionEvent(event, rawEvent, policyFlags);
-    }
-
-    @Override
-    public void clearEvents(int inputSource) {
-        if (inputSource == SOURCE_TOUCHSCREEN) {
-            clearAndTransitionToStateDetecting();
-        }
-
-        super.clearEvents(inputSource);
-    }
-
-    @Override
-    public void onDestroy() {
-        if (DEBUG_STATE_TRANSITIONS) {
-            Slog.i(LOG_TAG, "onDestroy(); delayed = "
-                    + MotionEventInfo.toString(mDetectingState.mDelayedEventQueue));
-        }
-
-        if (mScreenStateReceiver != null) {
-            mScreenStateReceiver.unregister();
-        }
-        // Check if need to reset when MagnificationGestureHandler is the last magnifying service.
-        mMagnificationController.resetAllIfNeeded(
-                AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
-        clearAndTransitionToStateDetecting();
-    }
-
-    void notifyShortcutTriggered() {
-        if (mDetectShortcutTrigger) {
-            boolean wasMagnifying = mMagnificationController.resetIfNeeded(mDisplayId,
-                    /* animate */ true);
-            if (wasMagnifying) {
-                clearAndTransitionToStateDetecting();
-            } else {
-                mDetectingState.toggleShortcutTriggered();
-            }
-        }
-    }
-
-    void clearAndTransitionToStateDetecting() {
-        mCurrentState = mDetectingState;
-        mDetectingState.clear();
-        mViewportDraggingState.clear();
-        mPanningScalingState.clear();
-    }
-
-    private void dispatchTransformedEvent(MotionEvent event, MotionEvent rawEvent,
-            int policyFlags) {
-        if (DEBUG_EVENT_STREAM) {
-            storeEventInto(mDebugOutputEventHistory, event);
-            try {
-                super.onMotionEvent(event, rawEvent, policyFlags);
-            } catch (Exception e) {
-                throw new RuntimeException(
-                        "Exception downstream following input events: " + mDebugInputEventHistory
-                                + "\nTransformed into output events: " + mDebugOutputEventHistory,
-                        e);
-            }
-        } else {
-            super.onMotionEvent(event, rawEvent, policyFlags);
-        }
-    }
-
-    private static void storeEventInto(Queue<MotionEvent> queue, MotionEvent event) {
-        queue.add(MotionEvent.obtain(event));
-        // Prune old events
-        while (!queue.isEmpty() && (event.getEventTime() - queue.peek().getEventTime() > 5000)) {
-            queue.remove().recycle();
-        }
-    }
-
-    private PointerCoords[] getTempPointerCoordsWithMinSize(int size) {
-        final int oldSize = (mTempPointerCoords != null) ? mTempPointerCoords.length : 0;
-        if (oldSize < size) {
-            PointerCoords[] oldTempPointerCoords = mTempPointerCoords;
-            mTempPointerCoords = new PointerCoords[size];
-            if (oldTempPointerCoords != null) {
-                System.arraycopy(oldTempPointerCoords, 0, mTempPointerCoords, 0, oldSize);
-            }
-        }
-        for (int i = oldSize; i < size; i++) {
-            mTempPointerCoords[i] = new PointerCoords();
-        }
-        return mTempPointerCoords;
-    }
-
-    private PointerProperties[] getTempPointerPropertiesWithMinSize(int size) {
-        final int oldSize = (mTempPointerProperties != null) ? mTempPointerProperties.length
-                : 0;
-        if (oldSize < size) {
-            PointerProperties[] oldTempPointerProperties = mTempPointerProperties;
-            mTempPointerProperties = new PointerProperties[size];
-            if (oldTempPointerProperties != null) {
-                System.arraycopy(oldTempPointerProperties, 0, mTempPointerProperties, 0,
-                        oldSize);
-            }
-        }
-        for (int i = oldSize; i < size; i++) {
-            mTempPointerProperties[i] = new PointerProperties();
-        }
-        return mTempPointerProperties;
-    }
-
-    private void transitionTo(State state) {
-        if (DEBUG_STATE_TRANSITIONS) {
-            Slog.i(LOG_TAG,
-                    (State.nameOf(mCurrentState) + " -> " + State.nameOf(state)
-                    + " at " + asList(copyOfRange(new RuntimeException().getStackTrace(), 1, 5)))
-                    .replace(getClass().getName(), ""));
-        }
-        mPreviousState = mCurrentState;
-        mCurrentState = state;
-    }
-
-    interface State {
-        void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags);
-
-        default void clear() {}
-
-        default String name() {
-            return getClass().getSimpleName();
-        }
-
-        static String nameOf(@Nullable State s) {
-            return s != null ? s.name() : "null";
-        }
-    }
-
-    /**
-     * This class determines if the user is performing a scale or pan gesture.
-     *
-     * Unlike when {@link ViewportDraggingState dragging the viewport}, in panning mode the viewport
-     * moves in the same direction as the fingers, and allows to easily and precisely scale the
-     * magnification level.
-     * This makes it the preferred mode for one-off adjustments, due to its precision and ease of
-     * triggering.
-     */
-    final class PanningScalingState extends SimpleOnGestureListener
-            implements OnScaleGestureListener, State {
-
-        private final ScaleGestureDetector mScaleGestureDetector;
-        private final GestureDetector mScrollGestureDetector;
-        final float mScalingThreshold;
-
-        float mInitialScaleFactor = -1;
-        boolean mScaling;
-
-        public PanningScalingState(Context context) {
-            final TypedValue scaleValue = new TypedValue();
-            context.getResources().getValue(
-                    com.android.internal.R.dimen.config_screen_magnification_scaling_threshold,
-                    scaleValue, false);
-            mScalingThreshold = scaleValue.getFloat();
-            mScaleGestureDetector = new ScaleGestureDetector(context, this, Handler.getMain());
-            mScaleGestureDetector.setQuickScaleEnabled(false);
-            mScrollGestureDetector = new GestureDetector(context, this, Handler.getMain());
-        }
-
-        @Override
-        public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
-            int action = event.getActionMasked();
-
-            if (action == ACTION_POINTER_UP
-                    && event.getPointerCount() == 2 // includes the pointer currently being released
-                    && mPreviousState == mViewportDraggingState) {
-
-                persistScaleAndTransitionTo(mViewportDraggingState);
-
-            } else if (action == ACTION_UP || action == ACTION_CANCEL) {
-
-                persistScaleAndTransitionTo(mDetectingState);
-
-            }
-        }
-
-        public void persistScaleAndTransitionTo(State state) {
-            mMagnificationController.persistScale();
-            clear();
-            transitionTo(state);
-        }
-
-        @Override
-        public boolean onScroll(MotionEvent first, MotionEvent second,
-                float distanceX, float distanceY) {
-            if (mCurrentState != mPanningScalingState) {
-                return true;
-            }
-            if (DEBUG_PANNING_SCALING) {
-                Slog.i(LOG_TAG, "Panned content by scrollX: " + distanceX
-                        + " scrollY: " + distanceY);
-            }
-            mMagnificationController.offsetMagnifiedRegion(mDisplayId, distanceX,
-                    distanceY, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
-            return /* event consumed: */ true;
-        }
-
-        @Override
-        public boolean onScale(ScaleGestureDetector detector) {
-            if (!mScaling) {
-                if (mInitialScaleFactor < 0) {
-                    mInitialScaleFactor = detector.getScaleFactor();
-                    return false;
-                }
-                final float deltaScale = detector.getScaleFactor() - mInitialScaleFactor;
-                mScaling = abs(deltaScale) > mScalingThreshold;
-                return mScaling;
-            }
-
-            final float initialScale = mMagnificationController.getScale(mDisplayId);
-            final float targetScale = initialScale * detector.getScaleFactor();
-
-            // Don't allow a gesture to move the user further outside the
-            // desired bounds for gesture-controlled scaling.
-            final float scale;
-            if (targetScale > MAX_SCALE && targetScale > initialScale) {
-                // The target scale is too big and getting bigger.
-                scale = MAX_SCALE;
-            } else if (targetScale < MIN_SCALE && targetScale < initialScale) {
-                // The target scale is too small and getting smaller.
-                scale = MIN_SCALE;
-            } else {
-                // The target scale may be outside our bounds, but at least
-                // it's moving in the right direction. This avoids a "jump" if
-                // we're at odds with some other service's desired bounds.
-                scale = targetScale;
-            }
-
-            final float pivotX = detector.getFocusX();
-            final float pivotY = detector.getFocusY();
-            if (DEBUG_PANNING_SCALING) Slog.i(LOG_TAG, "Scaled content to: " + scale + "x");
-            mMagnificationController.setScale(mDisplayId, scale, pivotX, pivotY, false,
-                    AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
-            return /* handled: */ true;
-        }
-
-        @Override
-        public boolean onScaleBegin(ScaleGestureDetector detector) {
-            return /* continue recognizing: */ (mCurrentState == mPanningScalingState);
-        }
-
-        @Override
-        public void onScaleEnd(ScaleGestureDetector detector) {
-            clear();
-        }
-
-        @Override
-        public void clear() {
-            mInitialScaleFactor = -1;
-            mScaling = false;
-        }
-
-        @Override
-        public String toString() {
-            return "PanningScalingState{" +
-                    "mInitialScaleFactor=" + mInitialScaleFactor +
-                    ", mScaling=" + mScaling +
-                    '}';
-        }
-    }
-
-    /**
-     * This class handles motion events when the event dispatcher has
-     * determined that the user is performing a single-finger drag of the
-     * magnification viewport.
-     *
-     * Unlike when {@link PanningScalingState panning}, the viewport moves in the opposite direction
-     * of the finger, and any part of the screen is reachable without lifting the finger.
-     * This makes it the preferable mode for tasks like reading text spanning full screen width.
-     */
-    final class ViewportDraggingState implements State {
-
-        /** Whether to disable zoom after dragging ends */
-        boolean mZoomedInBeforeDrag;
-        private boolean mLastMoveOutsideMagnifiedRegion;
-
-        @Override
-        public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
-            final int action = event.getActionMasked();
-            switch (action) {
-                case ACTION_POINTER_DOWN: {
-                    clear();
-                    transitionTo(mPanningScalingState);
-                }
-                break;
-                case ACTION_MOVE: {
-                    if (event.getPointerCount() != 1) {
-                        throw new IllegalStateException("Should have one pointer down.");
-                    }
-                    final float eventX = event.getX();
-                    final float eventY = event.getY();
-                    if (mMagnificationController.magnificationRegionContains(
-                            mDisplayId, eventX, eventY)) {
-                        mMagnificationController.setCenter(mDisplayId, eventX, eventY,
-                                /* animate */ mLastMoveOutsideMagnifiedRegion,
-                                AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
-                        mLastMoveOutsideMagnifiedRegion = false;
-                    } else {
-                        mLastMoveOutsideMagnifiedRegion = true;
-                    }
-                }
-                break;
-
-                case ACTION_UP:
-                case ACTION_CANCEL: {
-                    if (!mZoomedInBeforeDrag) zoomOff();
-                    clear();
-                    transitionTo(mDetectingState);
-                }
-                break;
-
-                case ACTION_DOWN:
-                case ACTION_POINTER_UP: {
-                    throw new IllegalArgumentException(
-                            "Unexpected event type: " + MotionEvent.actionToString(action));
-                }
-            }
-        }
-
-        @Override
-        public void clear() {
-            mLastMoveOutsideMagnifiedRegion = false;
-        }
-
-        @Override
-        public String toString() {
-            return "ViewportDraggingState{" +
-                    "mZoomedInBeforeDrag=" + mZoomedInBeforeDrag +
-                    ", mLastMoveOutsideMagnifiedRegion=" + mLastMoveOutsideMagnifiedRegion +
-                    '}';
-        }
-    }
-
-    final class DelegatingState implements State {
-        /**
-         * Time of last {@link MotionEvent#ACTION_DOWN} while in {@link DelegatingState}
-         */
-        public long mLastDelegatedDownEventTime;
-
-        @Override
-        public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
-
-        	// Ensure that the state at the end of delegation is consistent with the last delegated
-            // UP/DOWN event in queue: still delegating if pointer is down, detecting otherwise
-            switch (event.getActionMasked()) {
-                case ACTION_UP:
-                case ACTION_CANCEL: {
-                    transitionTo(mDetectingState);
-                } break;
-
-                case ACTION_DOWN: {
-                	transitionTo(mDelegatingState);
-                    mLastDelegatedDownEventTime = event.getDownTime();
-                } break;
-            }
-
-            if (getNext() != null) {
-                // We cache some events to see if the user wants to trigger magnification.
-                // If no magnification is triggered we inject these events with adjusted
-                // time and down time to prevent subsequent transformations being confused
-                // by stale events. After the cached events, which always have a down, are
-                // injected we need to also update the down time of all subsequent non cached
-                // events. All delegated events cached and non-cached are delivered here.
-                event.setDownTime(mLastDelegatedDownEventTime);
-                dispatchTransformedEvent(event, rawEvent, policyFlags);
-            }
-        }
-    }
-
-    /**
-     * This class handles motion events when the event dispatch has not yet
-     * determined what the user is doing. It watches for various tap events.
-     */
-    final class DetectingState implements State, Handler.Callback {
-
-        private static final int MESSAGE_ON_TRIPLE_TAP_AND_HOLD = 1;
-        private static final int MESSAGE_TRANSITION_TO_DELEGATING_STATE = 2;
-
-        final int mLongTapMinDelay;
-        final int mSwipeMinDistance;
-        final int mMultiTapMaxDelay;
-        final int mMultiTapMaxDistance;
-
-        private MotionEventInfo mDelayedEventQueue;
-        MotionEvent mLastDown;
-        private MotionEvent mPreLastDown;
-        private MotionEvent mLastUp;
-        private MotionEvent mPreLastUp;
-
-        private long mLastDetectingDownEventTime;
-
-        @VisibleForTesting boolean mShortcutTriggered;
-
-        @VisibleForTesting Handler mHandler = new Handler(Looper.getMainLooper(), this);
-
-        public DetectingState(Context context) {
-            mLongTapMinDelay = ViewConfiguration.getLongPressTimeout();
-            mMultiTapMaxDelay = ViewConfiguration.getDoubleTapTimeout()
-                    + context.getResources().getInteger(
-                    com.android.internal.R.integer.config_screen_magnification_multi_tap_adjustment);
-            mSwipeMinDistance = ViewConfiguration.get(context).getScaledTouchSlop();
-            mMultiTapMaxDistance = ViewConfiguration.get(context).getScaledDoubleTapSlop();
-        }
-
-        @Override
-        public boolean handleMessage(Message message) {
-            final int type = message.what;
-            switch (type) {
-                case MESSAGE_ON_TRIPLE_TAP_AND_HOLD: {
-                    MotionEvent down = (MotionEvent) message.obj;
-                    transitionToViewportDraggingStateAndClear(down);
-                    down.recycle();
-                }
-                break;
-                case MESSAGE_TRANSITION_TO_DELEGATING_STATE: {
-                    transitionToDelegatingStateAndClear();
-                }
-                break;
-                default: {
-                    throw new IllegalArgumentException("Unknown message type: " + type);
-                }
-            }
-            return true;
-        }
-
-        @Override
-        public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
-            cacheDelayedMotionEvent(event, rawEvent, policyFlags);
-            switch (event.getActionMasked()) {
-                case MotionEvent.ACTION_DOWN: {
-
-                    mLastDetectingDownEventTime = event.getDownTime();
-                    mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
-
-                    if (!mMagnificationController.magnificationRegionContains(
-                            mDisplayId, event.getX(), event.getY())) {
-
-                        transitionToDelegatingStateAndClear();
-
-                    } else if (isMultiTapTriggered(2 /* taps */)) {
-
-                        // 3tap and hold
-                        afterLongTapTimeoutTransitionToDraggingState(event);
-
-                    } else if (isTapOutOfDistanceSlop()) {
-
-                        transitionToDelegatingStateAndClear();
-
-                    } else if (mDetectTripleTap
-                            // If magnified, delay an ACTION_DOWN for mMultiTapMaxDelay
-                            // to ensure reachability of
-                            // STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN)
-                            || mMagnificationController.isMagnifying(mDisplayId)) {
-
-                        afterMultiTapTimeoutTransitionToDelegatingState();
-
-                    } else {
-
-                        // Delegate pending events without delay
-                        transitionToDelegatingStateAndClear();
-                    }
-                }
-                break;
-                case ACTION_POINTER_DOWN: {
-                    if (mMagnificationController.isMagnifying(mDisplayId)) {
-                        transitionTo(mPanningScalingState);
-                        clear();
-                    } else {
-                        transitionToDelegatingStateAndClear();
-                    }
-                }
-                break;
-                case ACTION_MOVE: {
-                    if (isFingerDown()
-                            && distance(mLastDown, /* move */ event) > mSwipeMinDistance) {
-
-                        // Swipe detected - transition immediately
-
-                        // For convenience, viewport dragging takes precedence
-                        // over insta-delegating on 3tap&swipe
-                        // (which is a rare combo to be used aside from magnification)
-                        if (isMultiTapTriggered(2 /* taps */)) {
-                            transitionToViewportDraggingStateAndClear(event);
-                        } else {
-                            transitionToDelegatingStateAndClear();
-                        }
-                    }
-                }
-                break;
-                case ACTION_UP: {
-
-                    mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD);
-
-                    if (!mMagnificationController.magnificationRegionContains(
-                            mDisplayId, event.getX(), event.getY())) {
-
-                        transitionToDelegatingStateAndClear();
-
-                    } else if (isMultiTapTriggered(3 /* taps */)) {
-
-                        onTripleTap(/* up */ event);
-
-                    } else if (
-                            // Possible to be false on: 3tap&drag -> scale -> PTR_UP -> UP
-                            isFingerDown()
-                            //TODO long tap should never happen here
-                            && ((timeBetween(mLastDown, mLastUp) >= mLongTapMinDelay)
-                                    || (distance(mLastDown, mLastUp) >= mSwipeMinDistance))) {
-
-                        transitionToDelegatingStateAndClear();
-
-                    }
-                }
-                break;
-            }
-        }
-
-        public boolean isMultiTapTriggered(int numTaps) {
-
-            // Shortcut acts as the 2 initial taps
-            if (mShortcutTriggered) return tapCount() + 2 >= numTaps;
-
-            return mDetectTripleTap
-                    && tapCount() >= numTaps
-                    && isMultiTap(mPreLastDown, mLastDown)
-                    && isMultiTap(mPreLastUp, mLastUp);
-        }
-
-        private boolean isMultiTap(MotionEvent first, MotionEvent second) {
-            return GestureUtils.isMultiTap(first, second, mMultiTapMaxDelay, mMultiTapMaxDistance);
-        }
-
-        public boolean isFingerDown() {
-            return mLastDown != null;
-        }
-
-        private long timeBetween(@Nullable MotionEvent a, @Nullable MotionEvent b) {
-            if (a == null && b == null) return 0;
-            return abs(timeOf(a) - timeOf(b));
-        }
-
-        /**
-         * Nullsafe {@link MotionEvent#getEventTime} that interprets null event as something that
-         * has happened long enough ago to be gone from the event queue.
-         * Thus the time for a null event is a small number, that is below any other non-null
-         * event's time.
-         *
-         * @return {@link MotionEvent#getEventTime}, or {@link Long#MIN_VALUE} if the event is null
-         */
-        private long timeOf(@Nullable MotionEvent event) {
-            return event != null ? event.getEventTime() : Long.MIN_VALUE;
-        }
-
-        public int tapCount() {
-            return MotionEventInfo.countOf(mDelayedEventQueue, ACTION_UP);
-        }
-
-        /** -> {@link DelegatingState} */
-        public void afterMultiTapTimeoutTransitionToDelegatingState() {
-            mHandler.sendEmptyMessageDelayed(
-                    MESSAGE_TRANSITION_TO_DELEGATING_STATE,
-                    mMultiTapMaxDelay);
-        }
-
-        /** -> {@link ViewportDraggingState} */
-        public void afterLongTapTimeoutTransitionToDraggingState(MotionEvent event) {
-            mHandler.sendMessageDelayed(
-                    mHandler.obtainMessage(MESSAGE_ON_TRIPLE_TAP_AND_HOLD,
-                            MotionEvent.obtain(event)),
-                    ViewConfiguration.getLongPressTimeout());
-        }
-
-        @Override
-        public void clear() {
-            setShortcutTriggered(false);
-            removePendingDelayedMessages();
-            clearDelayedMotionEvents();
-        }
-
-        private void removePendingDelayedMessages() {
-            mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD);
-            mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
-        }
-
-        private void cacheDelayedMotionEvent(MotionEvent event, MotionEvent rawEvent,
-                int policyFlags) {
-            if (event.getActionMasked() == ACTION_DOWN) {
-                mPreLastDown = mLastDown;
-                mLastDown = MotionEvent.obtain(event);
-            } else if (event.getActionMasked() == ACTION_UP) {
-                mPreLastUp = mLastUp;
-                mLastUp = MotionEvent.obtain(event);
-            }
-
-            MotionEventInfo info = MotionEventInfo.obtain(event, rawEvent,
-                    policyFlags);
-            if (mDelayedEventQueue == null) {
-                mDelayedEventQueue = info;
-            } else {
-                MotionEventInfo tail = mDelayedEventQueue;
-                while (tail.mNext != null) {
-                    tail = tail.mNext;
-                }
-                tail.mNext = info;
-            }
-        }
-
-        private void sendDelayedMotionEvents() {
-            if (mDelayedEventQueue == null) {
-                return;
-            }
-
-            // Adjust down time to prevent subsequent modules being misleading, and also limit
-            // the maximum offset to mMultiTapMaxDelay to prevent the down time of 2nd tap is
-            // in the future when multi-tap happens.
-            final long offset = Math.min(
-                    SystemClock.uptimeMillis() - mLastDetectingDownEventTime, mMultiTapMaxDelay);
-
-            do {
-                MotionEventInfo info = mDelayedEventQueue;
-                mDelayedEventQueue = info.mNext;
-
-                info.event.setDownTime(info.event.getDownTime() + offset);
-                handleEventWith(mDelegatingState, info.event, info.rawEvent, info.policyFlags);
-
-                info.recycle();
-            } while (mDelayedEventQueue != null);
-        }
-
-        private void clearDelayedMotionEvents() {
-            while (mDelayedEventQueue != null) {
-                MotionEventInfo info = mDelayedEventQueue;
-                mDelayedEventQueue = info.mNext;
-                info.recycle();
-            }
-            mPreLastDown = null;
-            mPreLastUp = null;
-            mLastDown = null;
-            mLastUp = null;
-        }
-
-        void transitionToDelegatingStateAndClear() {
-            transitionTo(mDelegatingState);
-            sendDelayedMotionEvents();
-            removePendingDelayedMessages();
-        }
-
-        private void onTripleTap(MotionEvent up) {
-
-            if (DEBUG_DETECTING) {
-                Slog.i(LOG_TAG, "onTripleTap(); delayed: "
-                        + MotionEventInfo.toString(mDelayedEventQueue));
-            }
-            clear();
-
-            // Toggle zoom
-            if (mMagnificationController.isMagnifying(mDisplayId)) {
-                zoomOff();
-            } else {
-                zoomOn(up.getX(), up.getY());
-            }
-        }
-
-        void transitionToViewportDraggingStateAndClear(MotionEvent down) {
-
-            if (DEBUG_DETECTING) Slog.i(LOG_TAG, "onTripleTapAndHold()");
-            clear();
-
-            mViewportDraggingState.mZoomedInBeforeDrag =
-                    mMagnificationController.isMagnifying(mDisplayId);
-
-            zoomOn(down.getX(), down.getY());
-
-            transitionTo(mViewportDraggingState);
-        }
-
-        @Override
-        public String toString() {
-            return "DetectingState{" +
-                    "tapCount()=" + tapCount() +
-                    ", mShortcutTriggered=" + mShortcutTriggered +
-                    ", mDelayedEventQueue=" + MotionEventInfo.toString(mDelayedEventQueue) +
-                    '}';
-        }
-
-        void toggleShortcutTriggered() {
-            setShortcutTriggered(!mShortcutTriggered);
-        }
-
-        void setShortcutTriggered(boolean state) {
-            if (mShortcutTriggered == state) {
-                return;
-            }
-            if (DEBUG_DETECTING) Slog.i(LOG_TAG, "setShortcutTriggered(" + state + ")");
-
-            mShortcutTriggered = state;
-            mMagnificationController.setForceShowMagnifiableBounds(mDisplayId, state);
-        }
-
-        /**
-         * Detects if last action down is out of distance slop between with previous
-         * one, when triple tap is enabled.
-         *
-         * @return true if tap is out of distance slop
-         */
-        boolean isTapOutOfDistanceSlop() {
-            if (!mDetectTripleTap) return false;
-            if (mPreLastDown == null || mLastDown == null) {
-                return false;
-            }
-            final boolean outOfDistanceSlop =
-                    GestureUtils.distance(mPreLastDown, mLastDown) > mMultiTapMaxDistance;
-            if (tapCount() > 0) {
-                return outOfDistanceSlop;
-            }
-            // There's no tap in the queue here. We still need to check if this is the case that
-            // user tap screen quickly and out of distance slop.
-            if (outOfDistanceSlop
-                    && !GestureUtils.isTimedOut(mPreLastDown, mLastDown, mMultiTapMaxDelay)) {
-                return true;
-            }
-            return false;
-        }
-    }
-
-    private void zoomOn(float centerX, float centerY) {
-        if (DEBUG_DETECTING) Slog.i(LOG_TAG, "zoomOn(" + centerX + ", " + centerY + ")");
-
-        final float scale = MathUtils.constrain(
-                mMagnificationController.getPersistedScale(),
-                MIN_SCALE, MAX_SCALE);
-        mMagnificationController.setScaleAndCenter(mDisplayId,
-                scale, centerX, centerY,
-                /* animate */ true,
-                AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
-    }
-
-    private void zoomOff() {
-        if (DEBUG_DETECTING) Slog.i(LOG_TAG, "zoomOff()");
-        mMagnificationController.reset(mDisplayId, /* animate */ true);
-    }
-
-    private static MotionEvent recycleAndNullify(@Nullable MotionEvent event) {
-        if (event != null) {
-            event.recycle();
-        }
-        return null;
-    }
-
-    @Override
-    public String toString() {
-        return "MagnificationGesture{" +
-                "mDetectingState=" + mDetectingState +
-                ", mDelegatingState=" + mDelegatingState +
-                ", mMagnifiedInteractionState=" + mPanningScalingState +
-                ", mViewportDraggingState=" + mViewportDraggingState +
-                ", mDetectTripleTap=" + mDetectTripleTap +
-                ", mDetectShortcutTrigger=" + mDetectShortcutTrigger +
-                ", mCurrentState=" + State.nameOf(mCurrentState) +
-                ", mPreviousState=" + State.nameOf(mPreviousState) +
-                ", mMagnificationController=" + mMagnificationController +
-                ", mDisplayId=" + mDisplayId +
-                '}';
-    }
-
-    private static final class MotionEventInfo {
-
-        private static final int MAX_POOL_SIZE = 10;
-        private static final Object sLock = new Object();
-        private static MotionEventInfo sPool;
-        private static int sPoolSize;
-
-        private MotionEventInfo mNext;
-        private boolean mInPool;
-
-        public MotionEvent event;
-        public MotionEvent rawEvent;
-        public int policyFlags;
-
-        public static MotionEventInfo obtain(MotionEvent event, MotionEvent rawEvent,
-                int policyFlags) {
-            synchronized (sLock) {
-                MotionEventInfo info = obtainInternal();
-                info.initialize(event, rawEvent, policyFlags);
-                return info;
-            }
-        }
-
-        @NonNull
-        private static MotionEventInfo obtainInternal() {
-            MotionEventInfo info;
-            if (sPoolSize > 0) {
-                sPoolSize--;
-                info = sPool;
-                sPool = info.mNext;
-                info.mNext = null;
-                info.mInPool = false;
-            } else {
-                info = new MotionEventInfo();
-            }
-            return info;
-        }
-
-        private void initialize(MotionEvent event, MotionEvent rawEvent,
-                int policyFlags) {
-            this.event = MotionEvent.obtain(event);
-            this.rawEvent = MotionEvent.obtain(rawEvent);
-            this.policyFlags = policyFlags;
-        }
-
-        public void recycle() {
-            synchronized (sLock) {
-                if (mInPool) {
-                    throw new IllegalStateException("Already recycled.");
-                }
-                clear();
-                if (sPoolSize < MAX_POOL_SIZE) {
-                    sPoolSize++;
-                    mNext = sPool;
-                    sPool = this;
-                    mInPool = true;
-                }
-            }
-        }
-
-        private void clear() {
-            event = recycleAndNullify(event);
-            rawEvent = recycleAndNullify(rawEvent);
-            policyFlags = 0;
-        }
-
-        static int countOf(MotionEventInfo info, int eventType) {
-            if (info == null) return 0;
-            return (info.event.getAction() == eventType ? 1 : 0)
-                    + countOf(info.mNext, eventType);
-        }
-
-        public static String toString(MotionEventInfo info) {
-            return info == null
-                    ? ""
-                    : MotionEvent.actionToString(info.event.getAction()).replace("ACTION_", "")
-                            + " " + MotionEventInfo.toString(info.mNext);
-        }
-    }
-
-    /**
-     * BroadcastReceiver used to cancel the magnification shortcut when the screen turns off
-     */
-    private static class ScreenStateReceiver extends BroadcastReceiver {
-        private final Context mContext;
-        private final MagnificationGestureHandler mGestureHandler;
-
-        public ScreenStateReceiver(Context context, MagnificationGestureHandler gestureHandler) {
-            mContext = context;
-            mGestureHandler = gestureHandler;
-        }
-
-        public void register() {
-            mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF));
-        }
-
-        public void unregister() {
-            mContext.unregisterReceiver(this);
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            mGestureHandler.mDetectingState.setShortcutTriggered(false);
-        }
-    }
-}
diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
index 1754926..11dcfef 100644
--- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
+++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
@@ -157,8 +157,13 @@
     /**
      * This method is called to register a system action. If a system action is already registered
      * with the given id, the existing system action will be overwritten.
+     *
+     * This method is supposed to be package internal since this class is meant to be used by
+     * AccessibilityManagerService only. But Mockito has a bug which requiring this to be public
+     * to be mocked.
      */
-    void registerSystemAction(int id, RemoteAction action) {
+    @VisibleForTesting
+    public void registerSystemAction(int id, RemoteAction action) {
         synchronized (mSystemActionLock) {
             mRegisteredSystemActions.put(id, action);
         }
@@ -170,8 +175,13 @@
     /**
      * This method is called to unregister a system action previously registered through
      * registerSystemAction.
+     *
+     * This method is supposed to be package internal since this class is meant to be used by
+     * AccessibilityManagerService only. But Mockito has a bug which requiring this to be public
+     * to be mocked.
      */
-    void unregisterSystemAction(int id) {
+    @VisibleForTesting
+    public void unregisterSystemAction(int id) {
         synchronized (mSystemActionLock) {
             mRegisteredSystemActions.remove(id);
         }
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index 7dd4a70..5d9af26 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -25,6 +25,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.IBinder.DeathRecipient;
+import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.util.Slog;
 import android.view.Display;
@@ -297,6 +298,11 @@
         }
 
         @Override
+        public boolean switchToInputMethod(String imeId) {
+            return false;
+        }
+
+        @Override
         public boolean isAccessibilityButtonAvailable() {
             return false;
         }
@@ -320,5 +326,8 @@
 
         @Override
         public void onFingerprintGesture(int gesture) {}
+
+        @Override
+        public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {}
     }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
index 2891c6c..4c9e590 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
@@ -82,7 +82,7 @@
             mCurrentTaps++;
             if (mCurrentTaps == mTargetTaps) {
                 // Done.
-                completeAfterTapTimeout(event, rawEvent, policyFlags);
+                completeGesture(event, rawEvent, policyFlags);
                 return;
             }
             // Needs more taps.
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
new file mode 100644
index 0000000..aa500b5
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.magnification;
+
+import com.android.server.accessibility.BaseEventStreamTransformation;
+
+/**
+ * A base class that detects gestures and defines common methods for magnification.
+ */
+public abstract class MagnificationGestureHandler extends BaseEventStreamTransformation {
+
+    /**
+     * Called when the shortcut target is magnification.
+     */
+    public abstract void notifyShortcutTriggered();
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
new file mode 100644
index 0000000..351c9e0
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.magnification;
+
+import static android.os.IBinder.DeathRecipient;
+
+import android.annotation.NonNull;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.view.accessibility.IWindowMagnificationConnection;
+import android.view.accessibility.IWindowMagnificationConnectionCallback;
+
+/**
+ * A wrapper of {@link IWindowMagnificationConnection}.
+ */
+class WindowMagnificationConnectionWrapper {
+
+    private static final boolean DBG = false;
+    private static final String TAG = "WindowMagnificationConnectionWrapper";
+
+    private final @NonNull IWindowMagnificationConnection mConnection;
+
+    WindowMagnificationConnectionWrapper(@NonNull IWindowMagnificationConnection connection) {
+        mConnection = connection;
+    }
+
+    //Should not use this instance anymore after calling it.
+    void unlinkToDeath(@NonNull DeathRecipient deathRecipient) {
+        mConnection.asBinder().unlinkToDeath(deathRecipient, 0);
+    }
+
+    void linkToDeath(@NonNull DeathRecipient deathRecipient) throws RemoteException {
+        mConnection.asBinder().linkToDeath(deathRecipient, 0);
+    }
+
+    boolean enableWindowMagnification(int displayId, float scale, float centerX, float centerY) {
+        try {
+            mConnection.enableWindowMagnification(displayId, scale, centerX, centerY);
+        } catch (RemoteException e) {
+            if (DBG) {
+                Slog.e(TAG, "Error calling enableWindowMagnification()");
+            }
+            return false;
+        }
+        return true;
+    }
+
+    boolean setScale(int displayId, float scale) {
+        try {
+            mConnection.setScale(displayId, scale);
+        } catch (RemoteException e) {
+            if (DBG) {
+                Slog.e(TAG, "Error calling setScale()");
+            }
+            return false;
+        }
+        return true;
+    }
+
+    boolean disableWindowMagnification(int displayId) {
+        try {
+            mConnection.disableWindowMagnification(displayId);
+        } catch (RemoteException e) {
+            if (DBG) {
+                Slog.e(TAG, "Error calling disableWindowMagnification()");
+            }
+            return false;
+        }
+        return true;
+    }
+
+    boolean moveWindowMagnifier(int displayId, float offsetX, float offsetY) {
+        try {
+            mConnection.moveWindowMagnifier(displayId, offsetX, offsetY);
+        } catch (RemoteException e) {
+            if (DBG) {
+                Slog.e(TAG, "Error calling moveWindowMagnifier()");
+            }
+            return false;
+        }
+        return true;
+    }
+
+    boolean setConnectionCallback(IWindowMagnificationConnectionCallback connectionCallback) {
+        try {
+            mConnection.setConnectionCallback(connectionCallback);
+        } catch (RemoteException e) {
+            if (DBG) {
+                Slog.e(TAG, "Error calling setConnectionCallback()");
+            }
+            return false;
+        }
+        return true;
+    }
+
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
new file mode 100644
index 0000000..00db3294
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.magnification;
+
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.view.accessibility.IWindowMagnificationConnection;
+import android.view.accessibility.IWindowMagnificationConnectionCallback;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * A class to manipulate  window magnification through {@link WindowMagnificationConnectionWrapper}.
+ */
+public final class WindowMagnificationManager {
+
+    private static final String TAG = "WindowMagnificationMgr";
+    private final Object mLock = new Object();
+    @VisibleForTesting
+    @Nullable WindowMagnificationConnectionWrapper mConnectionWrapper;
+    private ConnectionCallback mConnectionCallback;
+
+    /**
+     * Sets {@link IWindowMagnificationConnection}.
+     * @param connection {@link IWindowMagnificationConnection}
+     */
+    public void setConnection(@Nullable IWindowMagnificationConnection connection) {
+        synchronized (mLock) {
+            //Reset connectionWrapper.
+            if (mConnectionWrapper != null) {
+                mConnectionWrapper.setConnectionCallback(null);
+                if (mConnectionCallback != null) {
+                    mConnectionCallback.mExpiredDeathRecipient = true;
+                }
+                mConnectionWrapper.unlinkToDeath(mConnectionCallback);
+                mConnectionWrapper = null;
+            }
+            if (connection != null) {
+                mConnectionWrapper = new WindowMagnificationConnectionWrapper(connection);
+            }
+
+            if (mConnectionWrapper != null) {
+                try {
+                    mConnectionCallback = new ConnectionCallback();
+                    mConnectionWrapper.linkToDeath(mConnectionCallback);
+                    mConnectionWrapper.setConnectionCallback(mConnectionCallback);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "setConnection failed", e);
+                    mConnectionWrapper = null;
+                }
+            }
+        }
+    }
+
+    private class ConnectionCallback extends IWindowMagnificationConnectionCallback.Stub implements
+            IBinder.DeathRecipient {
+        private boolean mExpiredDeathRecipient = false;
+
+        @Override
+        public void onWindowMagnifierBoundsChanged(int display, Rect frame) throws RemoteException {
+        }
+
+        @Override
+        public void onChangeMagnificationMode(int display, int magnificationMode)
+                throws RemoteException {
+        }
+
+        @Override
+        public void binderDied() {
+            synchronized (mLock) {
+                if (mExpiredDeathRecipient) {
+                    Slog.w(TAG, "binderDied DeathRecipient is expired");
+                    return;
+                }
+                mConnectionWrapper.unlinkToDeath(this);
+                mConnectionWrapper = null;
+                mConnectionCallback = null;
+            }
+        }
+    }
+}
diff --git a/services/api/current.txt b/services/api/current.txt
index d802177..8a82e61 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -1 +1,76 @@
 // Signature format: 2.0
+package com.android.permission.persistence {
+
+  public interface RuntimePermissionsPersistence {
+    method @NonNull public static com.android.permission.persistence.RuntimePermissionsPersistence createInstance();
+    method public void delete(@NonNull android.os.UserHandle);
+    method @Nullable public com.android.permission.persistence.RuntimePermissionsState read(@NonNull android.os.UserHandle);
+    method public void write(@NonNull com.android.permission.persistence.RuntimePermissionsState, @NonNull android.os.UserHandle);
+  }
+
+  public final class RuntimePermissionsState {
+    ctor public RuntimePermissionsState(int, @Nullable String, @NonNull java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>>, @NonNull java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>>);
+    method @Nullable public String getFingerprint();
+    method @NonNull public java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>> getPackagePermissions();
+    method @NonNull public java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>> getSharedUserPermissions();
+    method public int getVersion();
+    field public static final int NO_VERSION = -1; // 0xffffffff
+  }
+
+  public static class RuntimePermissionsState.PermissionState {
+    ctor public RuntimePermissionsState.PermissionState(@NonNull String, boolean, int);
+    method public int getFlags();
+    method @NonNull public String getName();
+    method public boolean isGranted();
+  }
+
+}
+
+package com.android.role.persistence {
+
+  public interface RolesPersistence {
+    method @NonNull public static com.android.role.persistence.RolesPersistence createInstance();
+    method public void delete(@NonNull android.os.UserHandle);
+    method @Nullable public com.android.role.persistence.RolesState read(@NonNull android.os.UserHandle);
+    method public void write(@NonNull com.android.role.persistence.RolesState, @NonNull android.os.UserHandle);
+  }
+
+  public final class RolesState {
+    ctor public RolesState(int, @Nullable String, @NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>>);
+    method @Nullable public String getPackagesHash();
+    method @NonNull public java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getRoles();
+    method public int getVersion();
+  }
+
+}
+
+package com.android.server {
+
+  public abstract class SystemService {
+    ctor public SystemService(@NonNull android.content.Context);
+    method @NonNull public final android.content.Context getContext();
+    method public boolean isSupportedUser(@NonNull com.android.server.SystemService.TargetUser);
+    method public void onBootPhase(int);
+    method public void onCleanupUser(@NonNull com.android.server.SystemService.TargetUser);
+    method public abstract void onStart();
+    method public void onStartUser(@NonNull com.android.server.SystemService.TargetUser);
+    method public void onStopUser(@NonNull com.android.server.SystemService.TargetUser);
+    method public void onSwitchUser(@Nullable com.android.server.SystemService.TargetUser, @NonNull com.android.server.SystemService.TargetUser);
+    method public void onUnlockUser(@NonNull com.android.server.SystemService.TargetUser);
+    method protected final void publishBinderService(@NonNull String, @NonNull android.os.IBinder);
+    method protected final void publishBinderService(@NonNull String, @NonNull android.os.IBinder, boolean);
+    field public static final int PHASE_ACTIVITY_MANAGER_READY = 550; // 0x226
+    field public static final int PHASE_BOOT_COMPLETED = 1000; // 0x3e8
+    field public static final int PHASE_DEVICE_SPECIFIC_SERVICES_READY = 520; // 0x208
+    field public static final int PHASE_LOCK_SETTINGS_READY = 480; // 0x1e0
+    field public static final int PHASE_SYSTEM_SERVICES_READY = 500; // 0x1f4
+    field public static final int PHASE_THIRD_PARTY_APPS_CAN_START = 600; // 0x258
+    field public static final int PHASE_WAIT_FOR_DEFAULT_DISPLAY = 100; // 0x64
+  }
+
+  public static final class SystemService.TargetUser {
+    method @NonNull public android.os.UserHandle getUserHandle();
+  }
+
+}
+
diff --git a/services/art-profile b/services/art-profile
index a0338d5..4e113c8 100644
--- a/services/art-profile
+++ b/services/art-profile
@@ -3066,18 +3066,18 @@
 HSPLcom/android/server/am/AppBindRecord;->dumpInIntentBind(Ljava/io/PrintWriter;Ljava/lang/String;)V
 PLcom/android/server/am/AppBindRecord;->toString()Ljava/lang/String;
 PLcom/android/server/am/AppBindRecord;->writeToProto(Landroid/util/proto/ProtoOutputStream;J)V
-PLcom/android/server/am/AppCompactor$1;->onPropertyChanged(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-HSPLcom/android/server/am/AppCompactor$MemCompactionHandler;->handleMessage(Landroid/os/Message;)V
-HSPLcom/android/server/am/AppCompactor;-><init>(Lcom/android/server/am/ActivityManagerService;)V
-HSPLcom/android/server/am/AppCompactor;->access$1000(Lcom/android/server/am/AppCompactor;)V
-HSPLcom/android/server/am/AppCompactor;->access$700(Lcom/android/server/am/AppCompactor;)Lcom/android/server/am/ActivityManagerService;
-HSPLcom/android/server/am/AppCompactor;->access$800(Lcom/android/server/am/AppCompactor;)Ljava/util/ArrayList;
-HSPLcom/android/server/am/AppCompactor;->access$900(Lcom/android/server/am/AppCompactor;)Ljava/util/Random;
-PLcom/android/server/am/AppCompactor;->dump(Ljava/io/PrintWriter;)V
-HSPLcom/android/server/am/AppCompactor;->init()V
-HSPLcom/android/server/am/AppCompactor;->updateCompactionThrottles()V
-HSPLcom/android/server/am/AppCompactor;->updateUseCompaction()V
-HSPLcom/android/server/am/AppCompactor;->useCompaction()Z
+PLcom/android/server/am/CachedAppOptimizer$1;->onPropertyChanged(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+HSPLcom/android/server/am/CachedAppOptimizer$MemCompactionHandler;->handleMessage(Landroid/os/Message;)V
+HSPLcom/android/server/am/CachedAppOptimizer;-><init>(Lcom/android/server/am/ActivityManagerService;)V
+HSPLcom/android/server/am/CachedAppOptimizer;->access$1000(Lcom/android/server/am/CachedAppOptimizer;)V
+HSPLcom/android/server/am/CachedAppOptimizer;->access$700(Lcom/android/server/am/CachedAppOptimizer;)Lcom/android/server/am/ActivityManagerService;
+HSPLcom/android/server/am/CachedAppOptimizer;->access$800(Lcom/android/server/am/CachedAppOptimizer;)Ljava/util/ArrayList;
+HSPLcom/android/server/am/CachedAppOptimizer;->access$900(Lcom/android/server/am/CachedAppOptimizer;)Ljava/util/Random;
+PLcom/android/server/am/CachedAppOptimizer;->dump(Ljava/io/PrintWriter;)V
+HSPLcom/android/server/am/CachedAppOptimizer;->init()V
+HSPLcom/android/server/am/CachedAppOptimizer;->updateCompactionThrottles()V
+HSPLcom/android/server/am/CachedAppOptimizer;->updateUseCompaction()V
+HSPLcom/android/server/am/CachedAppOptimizer;->useCompaction()Z
 PLcom/android/server/am/AppErrorDialog$1;->handleMessage(Landroid/os/Message;)V
 PLcom/android/server/am/AppErrorDialog;-><init>(Landroid/content/Context;Lcom/android/server/am/ActivityManagerService;Lcom/android/server/am/AppErrorDialog$Data;)V
 PLcom/android/server/am/AppErrorDialog;->onClick(Landroid/view/View;)V
@@ -18632,9 +18632,9 @@
 Lcom/android/server/am/ActivityManagerService$UidObserverRegistration;
 Lcom/android/server/am/ActivityManagerService;
 Lcom/android/server/am/AppBindRecord;
-Lcom/android/server/am/AppCompactor$1;
-Lcom/android/server/am/AppCompactor$MemCompactionHandler;
-Lcom/android/server/am/AppCompactor;
+Lcom/android/server/am/CachedAppOptimizer$1;
+Lcom/android/server/am/CachedAppOptimizer$MemCompactionHandler;
+Lcom/android/server/am/CachedAppOptimizer;
 Lcom/android/server/am/AppErrorDialog$Data;
 Lcom/android/server/am/AppErrorResult;
 Lcom/android/server/am/AppErrors$BadProcessInfo;
diff --git a/services/art-profile-boot b/services/art-profile-boot
index e09424b..fe4178a 100644
--- a/services/art-profile-boot
+++ b/services/art-profile-boot
@@ -538,7 +538,7 @@
 Lcom/android/server/am/ActivityManagerService;->updateLowMemStateLocked(III)Z
 Lcom/android/server/wm/ConfigurationContainer;->getWindowConfiguration()Landroid/app/WindowConfiguration;
 Lcom/android/server/am/OomAdjuster;->applyOomAdjLocked(Lcom/android/server/am/ProcessRecord;ZJJ)Z
-Lcom/android/server/am/AppCompactor;->useCompaction()Z
+Lcom/android/server/am/CachedAppOptimizer;->useCompaction()Z
 Lcom/android/server/am/ProcessList;->procStatesDifferForMem(II)Z
 Lcom/android/server/am/ActivityManagerService;->dispatchUidsChanged()V
 Lcom/android/server/audio/AudioService$VolumeStreamState;->setIndex(IILjava/lang/String;)Z
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 03d9626..5405fc7 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -331,8 +331,8 @@
     }
 
     @Override // from SystemService
-    public boolean isSupported(UserInfo userInfo) {
-        return userInfo.isFull() || userInfo.isManagedProfile();
+    public boolean isSupportedUser(TargetUser user) {
+        return user.getUserInfo().isFull() || user.getUserInfo().isManagedProfile();
     }
 
     @Override // from SystemService
diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/InlineSuggestionFactory.java
index 3af15c7..5e6f97e 100644
--- a/services/autofill/java/com/android/server/autofill/InlineSuggestionFactory.java
+++ b/services/autofill/java/com/android/server/autofill/InlineSuggestionFactory.java
@@ -20,23 +20,24 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.slice.Slice;
 import android.content.Context;
-import android.os.Handler;
 import android.os.RemoteException;
 import android.service.autofill.Dataset;
+import android.service.autofill.InlinePresentation;
 import android.util.Slog;
 import android.view.SurfaceControl;
 import android.view.View;
 import android.view.autofill.AutofillId;
 import android.view.autofill.IAutoFillManagerClient;
-import android.view.inline.InlinePresentationSpec;
 import android.view.inputmethod.InlineSuggestion;
 import android.view.inputmethod.InlineSuggestionInfo;
-import android.view.inputmethod.InlineSuggestionsRequest;
 import android.view.inputmethod.InlineSuggestionsResponse;
 
 import com.android.internal.view.inline.IInlineContentCallback;
 import com.android.internal.view.inline.IInlineContentProvider;
+import com.android.server.UiThread;
+import com.android.server.autofill.ui.AutoFillUI;
 import com.android.server.autofill.ui.InlineSuggestionUi;
 
 import java.util.ArrayList;
@@ -56,23 +57,59 @@
             int sessionId,
             @NonNull Dataset[] datasets,
             @NonNull AutofillId autofillId,
-            @NonNull InlineSuggestionsRequest request,
-            @NonNull Handler uiHandler,
             @NonNull Context context,
             @NonNull IAutoFillManagerClient client) {
+        if (sDebug) Slog.d(TAG, "createAugmentedInlineSuggestionsResponse called");
+
+        final ArrayList<InlineSuggestion> inlineSuggestions = new ArrayList<>();
+        final InlineSuggestionUi inlineSuggestionUi = new InlineSuggestionUi(context);
+        for (Dataset dataset : datasets) {
+            final int fieldIndex = dataset.getFieldIds().indexOf(autofillId);
+            if (fieldIndex < 0) {
+                Slog.w(TAG, "AutofillId=" + autofillId + " not found in dataset");
+                return null;
+            }
+            final InlinePresentation inlinePresentation = dataset.getFieldInlinePresentation(
+                    fieldIndex);
+            if (inlinePresentation == null) {
+                Slog.w(TAG, "InlinePresentation not found in dataset");
+                return null;
+            }
+            InlineSuggestion inlineSuggestion = createAugmentedInlineSuggestion(sessionId, dataset,
+                    inlinePresentation, inlineSuggestionUi, client);
+            inlineSuggestions.add(inlineSuggestion);
+        }
+        return new InlineSuggestionsResponse(inlineSuggestions);
+    }
+
+    /**
+     * Creates an {@link InlineSuggestionsResponse} with the {@code datasets} provided by the
+     * autofill service.
+     */
+    public static InlineSuggestionsResponse createInlineSuggestionsResponse(int requestId,
+            @NonNull Dataset[] datasets,
+            @NonNull AutofillId autofillId,
+            @NonNull Context context,
+            @NonNull AutoFillUI.AutoFillUiCallback client) {
         if (sDebug) Slog.d(TAG, "createInlineSuggestionsResponse called");
 
         final ArrayList<InlineSuggestion> inlineSuggestions = new ArrayList<>();
         final InlineSuggestionUi inlineSuggestionUi = new InlineSuggestionUi(context);
         for (Dataset dataset : datasets) {
-            // TODO(b/146453195): use the spec in the dataset.
-            InlinePresentationSpec spec = request.getPresentationSpecs().get(0);
-            if (spec == null) {
-                Slog.w(TAG, "InlinePresentationSpec is not provided in the response data set");
-                continue;
+            final int fieldIndex = dataset.getFieldIds().indexOf(autofillId);
+            if (fieldIndex < 0) {
+                Slog.w(TAG, "AutofillId=" + autofillId + " not found in dataset");
+                return null;
             }
-            InlineSuggestion inlineSuggestion = createAugmentedInlineSuggestion(sessionId, dataset,
-                    autofillId, spec, uiHandler, inlineSuggestionUi, client);
+            final InlinePresentation inlinePresentation = dataset.getFieldInlinePresentation(
+                    fieldIndex);
+            if (inlinePresentation == null) {
+                Slog.w(TAG, "InlinePresentation not found in dataset");
+                return null;
+            }
+            InlineSuggestion inlineSuggestion = createInlineSuggestion(requestId, dataset,
+                    fieldIndex,
+                    inlinePresentation, inlineSuggestionUi, client);
             inlineSuggestions.add(inlineSuggestion);
         }
         return new InlineSuggestionsResponse(inlineSuggestions);
@@ -80,33 +117,55 @@
 
     private static InlineSuggestion createAugmentedInlineSuggestion(int sessionId,
             @NonNull Dataset dataset,
-            @NonNull AutofillId autofillId,
-            @NonNull InlinePresentationSpec spec,
-            @NonNull Handler uiHandler,
+            @NonNull InlinePresentation inlinePresentation,
             @NonNull InlineSuggestionUi inlineSuggestionUi,
             @NonNull IAutoFillManagerClient client) {
         // TODO(b/146453195): fill in the autofill hint properly.
         final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo(
-                spec, InlineSuggestionInfo.SOURCE_PLATFORM, new String[]{""});
-        final View.OnClickListener onClickListener = createOnClickListener(sessionId, dataset,
-                client);
+                inlinePresentation.getInlinePresentationSpec(),
+                InlineSuggestionInfo.SOURCE_PLATFORM, new String[]{""});
+        final View.OnClickListener onClickListener = v -> {
+            try {
+                client.autofill(sessionId, dataset.getFieldIds(), dataset.getFieldValues());
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Encounter exception autofilling the values");
+            }
+        };
         final InlineSuggestion inlineSuggestion = new InlineSuggestion(inlineSuggestionInfo,
-                createInlineContentProvider(autofillId, dataset, uiHandler, inlineSuggestionUi,
+                createInlineContentProvider(inlinePresentation.getSlice(), inlineSuggestionUi,
+                        onClickListener));
+        return inlineSuggestion;
+    }
+
+    private static InlineSuggestion createInlineSuggestion(int requestId,
+            @NonNull Dataset dataset,
+            int fieldIndex,
+            @NonNull InlinePresentation inlinePresentation,
+            @NonNull InlineSuggestionUi inlineSuggestionUi,
+            @NonNull AutoFillUI.AutoFillUiCallback client) {
+        // TODO(b/146453195): fill in the autofill hint properly.
+        final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo(
+                inlinePresentation.getInlinePresentationSpec(),
+                InlineSuggestionInfo.SOURCE_AUTOFILL, new String[]{""});
+        final View.OnClickListener onClickListener = v -> {
+            client.fill(requestId, fieldIndex, dataset);
+        };
+        final InlineSuggestion inlineSuggestion = new InlineSuggestion(inlineSuggestionInfo,
+                createInlineContentProvider(inlinePresentation.getSlice(), inlineSuggestionUi,
                         onClickListener));
         return inlineSuggestion;
     }
 
     private static IInlineContentProvider.Stub createInlineContentProvider(
-            @NonNull AutofillId autofillId, @NonNull Dataset dataset, @NonNull Handler uiHandler,
-            @NonNull InlineSuggestionUi inlineSuggestionUi,
+            @NonNull Slice slice, @NonNull InlineSuggestionUi inlineSuggestionUi,
             @Nullable View.OnClickListener onClickListener) {
         return new IInlineContentProvider.Stub() {
             @Override
             public void provideContent(int width, int height,
                     IInlineContentCallback callback) {
-                uiHandler.post(() -> {
-                    SurfaceControl sc = inlineSuggestionUi.inflate(dataset, autofillId,
-                            width, height, onClickListener);
+                UiThread.getHandler().post(() -> {
+                    SurfaceControl sc = inlineSuggestionUi.inflate(slice, width, height,
+                            onClickListener);
                     try {
                         callback.onContent(sc);
                     } catch (RemoteException e) {
@@ -117,19 +176,6 @@
         };
     }
 
-    private static View.OnClickListener createOnClickListener(int sessionId,
-            @NonNull Dataset dataset,
-            @NonNull IAutoFillManagerClient client) {
-        return v -> {
-            if (sDebug) Slog.d(TAG, "Inline suggestion clicked");
-            try {
-                client.autofill(sessionId, dataset.getFieldIds(), dataset.getFieldValues());
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Encounter exception autofilling the values");
-            }
-        };
-    }
-
     private InlineSuggestionFactory() {
     }
 }
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index 2a7357b..5fbdd25 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -161,8 +161,7 @@
                                 @Override
                                 public void onSuccess(@Nullable Dataset[] inlineSuggestionsData) {
                                     maybeHandleInlineSuggestions(sessionId, inlineSuggestionsData,
-                                            focusedId, inlineSuggestionsRequest,
-                                            inlineSuggestionsCallback, client);
+                                            focusedId, inlineSuggestionsCallback, client);
                                     requestAutofill.complete(null);
                                 }
 
@@ -226,19 +225,15 @@
 
     private void maybeHandleInlineSuggestions(int sessionId,
             @Nullable Dataset[] inlineSuggestionsData, @NonNull AutofillId focusedId,
-            @Nullable InlineSuggestionsRequest inlineSuggestionsRequest,
             @Nullable IInlineSuggestionsResponseCallback inlineSuggestionsCallback,
             @NonNull IAutoFillManagerClient client) {
-        if (inlineSuggestionsRequest == null
-                || ArrayUtils.isEmpty(inlineSuggestionsData)
-                || inlineSuggestionsCallback == null) {
+        if (ArrayUtils.isEmpty(inlineSuggestionsData) || inlineSuggestionsCallback == null) {
             return;
         }
         try {
             inlineSuggestionsCallback.onInlineSuggestionsResponse(
-                    InlineSuggestionFactory.createAugmentedInlineSuggestionsResponse(
-                            sessionId, inlineSuggestionsData, focusedId, inlineSuggestionsRequest,
-                            getJobHandler(), mContext, client));
+                    InlineSuggestionFactory.createAugmentedInlineSuggestionsResponse(sessionId,
+                            inlineSuggestionsData, focusedId, mContext, client));
         } catch (RemoteException e) {
             Slog.w(TAG, "Exception sending inline suggestions response back to IME.");
         }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 95cd8fc..ee37de5 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -19,6 +19,7 @@
 import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
 import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
+import static android.view.autofill.AutofillManager.ACTION_RESPONSE_EXPIRED;
 import static android.view.autofill.AutofillManager.ACTION_START_SESSION;
 import static android.view.autofill.AutofillManager.ACTION_VALUE_CHANGED;
 import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED;
@@ -70,7 +71,6 @@
 import android.service.autofill.FillContext;
 import android.service.autofill.FillRequest;
 import android.service.autofill.FillResponse;
-import android.service.autofill.InlinePresentation;
 import android.service.autofill.InternalSanitizer;
 import android.service.autofill.InternalValidator;
 import android.service.autofill.SaveInfo;
@@ -92,9 +92,6 @@
 import android.view.autofill.AutofillValue;
 import android.view.autofill.IAutoFillManagerClient;
 import android.view.autofill.IAutofillWindowPresenter;
-import android.view.inline.InlinePresentationSpec;
-import android.view.inputmethod.InlineSuggestion;
-import android.view.inputmethod.InlineSuggestionInfo;
 import android.view.inputmethod.InlineSuggestionsRequest;
 import android.view.inputmethod.InlineSuggestionsResponse;
 
@@ -105,14 +102,11 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.view.IInlineSuggestionsRequestCallback;
 import com.android.internal.view.IInlineSuggestionsResponseCallback;
-import com.android.internal.view.inline.IInlineContentCallback;
-import com.android.internal.view.inline.IInlineContentProvider;
 import com.android.server.autofill.ui.AutoFillUI;
 import com.android.server.autofill.ui.PendingUi;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 
 import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -148,6 +142,7 @@
     private final Handler mHandler;
     private final Object mLock;
     private final AutoFillUI mUi;
+    private final Context mContext;
 
     private final MetricsLogger mMetricsLogger = new MetricsLogger();
 
@@ -163,6 +158,9 @@
     /** uid the session is for */
     public final int uid;
 
+    /** user id the session is for */
+    public final int userId;
+
     /** ID of the task associated with this session's activity */
     public final int taskId;
 
@@ -267,6 +265,9 @@
     @GuardedBy("mLock")
     private final LocalLog mWtfHistory;
 
+    @GuardedBy("mLock")
+    private boolean mExpiredResponse;
+
     /**
      * Map of {@link MetricsEvent#AUTOFILL_REQUEST} metrics, keyed by fill request id.
      */
@@ -311,20 +312,9 @@
     @NonNull
     private final InputMethodManagerInternal mInputMethodManagerInternal;
 
-    @GuardedBy("mLock")
-    @Nullable
-    private CompletableFuture<InlineSuggestionsRequest> mSuggestionsRequestFuture;
-
-    @GuardedBy("mLock")
-    @Nullable
-    private CompletableFuture<IInlineSuggestionsResponseCallback>
-            mInlineSuggestionsResponseCallbackFuture;
-
     @Nullable
     private InlineSuggestionsRequestCallbackImpl mInlineSuggestionsRequestCallback;
 
-    private static final int INLINE_REQUEST_TIMEOUT_MS = 1000;
-
     /**
      * Receiver of assist data from the app's {@link Activity}.
      */
@@ -336,7 +326,9 @@
                         + "mForAugmentedAutofillOnly: %s", mForAugmentedAutofillOnly);
                 return;
             }
-            if (mCurrentViewId == null) {
+            // Keeps to prevent it is cleared on multiple threads.
+            final AutofillId currentViewId = mCurrentViewId;
+            if (currentViewId == null) {
                 Slog.w(TAG, "No current view id - session might have finished");
                 return;
             }
@@ -410,7 +402,7 @@
                 if (mContexts == null) {
                     mContexts = new ArrayList<>(1);
                 }
-                mContexts.add(new FillContext(requestId, structure, mCurrentViewId));
+                mContexts.add(new FillContext(requestId, structure, currentViewId));
 
                 cancelCurrentRequestLocked();
 
@@ -422,7 +414,9 @@
                 final ArrayList<FillContext> contexts =
                         mergePreviousSessionLocked(/* forSave= */ false);
 
-                final InlineSuggestionsRequest suggestionsRequest = getInlineSuggestionsRequest();
+                final InlineSuggestionsRequest suggestionsRequest =
+                        mInlineSuggestionsRequestCallback != null
+                                ? mInlineSuggestionsRequestCallback.getRequest() : null;
 
                 request = new FillRequest(requestId, contexts, mClientState, flags,
                         suggestionsRequest);
@@ -620,14 +614,8 @@
     private void maybeRequestInlineSuggestionsRequestThenFillLocked(@NonNull ViewState viewState,
             int newState, int flags) {
         if (isInlineSuggestionsEnabled()) {
-            mSuggestionsRequestFuture = new CompletableFuture<>();
-            mInlineSuggestionsResponseCallbackFuture = new CompletableFuture<>();
-
-            if (mInlineSuggestionsRequestCallback == null) {
-                mInlineSuggestionsRequestCallback = new InlineSuggestionsRequestCallbackImpl(this);
-            }
-
-            mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(
+            mInlineSuggestionsRequestCallback = new InlineSuggestionsRequestCallbackImpl();
+            mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(userId,
                     mComponentName, mCurrentViewId, mInlineSuggestionsRequestCallback);
         }
 
@@ -636,10 +624,14 @@
 
     private static final class InlineSuggestionsRequestCallbackImpl
             extends IInlineSuggestionsRequestCallback.Stub {
-        private final WeakReference<Session> mSession;
+        private static final int INLINE_REQUEST_TIMEOUT_MS = 1000;
 
-        private InlineSuggestionsRequestCallbackImpl(Session session) {
-            mSession = new WeakReference<>(session);
+        private final CompletableFuture<InlineSuggestionsRequest> mRequest;
+        private final CompletableFuture<IInlineSuggestionsResponseCallback> mResponseCallback;
+
+        private InlineSuggestionsRequestCallbackImpl() {
+            mRequest = new CompletableFuture<>();
+            mResponseCallback = new CompletableFuture<>();
         }
 
         @Override
@@ -648,13 +640,8 @@
                 Log.d(TAG, "inline suggestions request unsupported, "
                         + "falling back to regular autofill");
             }
-            final Session session = mSession.get();
-            if (session != null) {
-                synchronized (session.mLock) {
-                    session.mSuggestionsRequestFuture.cancel(true);
-                    session.mInlineSuggestionsResponseCallbackFuture.cancel(true);
-                }
-            }
+            mRequest.cancel(true);
+            mResponseCallback.cancel(true);
         }
 
         @Override
@@ -663,13 +650,36 @@
             if (sDebug) {
                 Log.d(TAG, "onInlineSuggestionsRequest() received: " + request);
             }
-            final Session session = mSession.get();
-            if (session != null) {
-                synchronized (session.mLock) {
-                    session.mSuggestionsRequestFuture.complete(request);
-                    session.mInlineSuggestionsResponseCallbackFuture.complete(callback);
-                }
+            mRequest.complete(request);
+            mResponseCallback.complete(callback);
+        }
+
+        @Nullable
+        private InlineSuggestionsRequest getRequest() {
+            try {
+                return mRequest.get(INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            } catch (TimeoutException e) {
+                Log.w(TAG, "Exception getting inline suggestions request in time: " + e);
+            } catch (CancellationException e) {
+                Log.w(TAG, "Inline suggestions request cancelled");
+            } catch (InterruptedException | ExecutionException e) {
+                throw new RuntimeException(e);
             }
+            return null;
+        }
+
+        @Nullable
+        private IInlineSuggestionsResponseCallback getResponseCallback() {
+            try {
+                return mResponseCallback.get(INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            } catch (TimeoutException e) {
+                Log.w(TAG, "Exception getting inline suggestions callback in time: " + e);
+            } catch (CancellationException e) {
+                Log.w(TAG, "Inline suggestions callback cancelled");
+            } catch (InterruptedException | ExecutionException e) {
+                throw new RuntimeException(e);
+            }
+            return null;
         }
     }
 
@@ -679,6 +689,7 @@
     @GuardedBy("mLock")
     private void requestNewFillResponseLocked(@NonNull ViewState viewState, int newState,
             int flags) {
+        mExpiredResponse = false;
         if (mForAugmentedAutofillOnly || mRemoteFillService == null) {
             if (sVerbose) {
                 Slog.v(TAG, "requestNewFillResponse(): triggering augmented autofill instead "
@@ -751,11 +762,13 @@
         mFlags = flags;
         this.taskId = taskId;
         this.uid = uid;
+        this.userId = userId;
         mStartTime = SystemClock.elapsedRealtime();
         mService = service;
         mLock = lock;
         mUi = ui;
         mHandler = handler;
+        mContext = context;
         mRemoteFillService = serviceComponentName == null ? null
                 : new RemoteFillService(context, serviceComponentName, userId, this,
                         bindInstantServiceAllowed);
@@ -1295,6 +1308,9 @@
             }
         }
 
+        // The client becomes invisible for the authentication, the response is effective.
+        mExpiredResponse = false;
+
         final Parcelable result = data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT);
         final Bundle newClientState = data.getBundle(AutofillManager.EXTRA_CLIENT_STATE);
         if (sDebug) {
@@ -2310,16 +2326,18 @@
      * @param id The id of the view that is entered.
      * @param viewState The view that is entered.
      * @param flags The flag that was passed by the AutofillManager.
+     *
+     * @return {@code true} if a new fill response is requested.
      */
     @GuardedBy("mLock")
-    private void requestNewFillResponseOnViewEnteredIfNecessaryLocked(@NonNull AutofillId id,
+    private boolean requestNewFillResponseOnViewEnteredIfNecessaryLocked(@NonNull AutofillId id,
             @NonNull ViewState viewState, int flags) {
         if ((flags & FLAG_MANUAL_REQUEST) != 0) {
             mForAugmentedAutofillOnly = false;
             if (sDebug) Slog.d(TAG, "Re-starting session on view " + id + " and flags " + flags);
             maybeRequestInlineSuggestionsRequestThenFillLocked(viewState,
                     ViewState.STATE_RESTARTED_SESSION, flags);
-            return;
+            return true;
         }
 
         // If it's not, then check if it it should start a partition.
@@ -2330,12 +2348,14 @@
             }
             maybeRequestInlineSuggestionsRequestThenFillLocked(viewState,
                     ViewState.STATE_STARTED_PARTITION, flags);
+            return true;
         } else {
             if (sVerbose) {
                 Slog.v(TAG, "Not starting new partition for view " + id + ": "
                         + viewState.getStateAsString());
             }
         }
+        return false;
     }
 
     /**
@@ -2343,7 +2363,7 @@
      *
      * @param id The id of the view that is entered
      *
-     * @return {@code true} iff a new partition should be started
+     * @return {@code true} if a new partition should be started
      */
     @GuardedBy("mLock")
     private boolean shouldStartNewPartitionLocked(@NonNull AutofillId id) {
@@ -2351,6 +2371,13 @@
             return true;
         }
 
+        if (mExpiredResponse) {
+            if (sDebug) {
+                Slog.d(TAG, "Starting a new partition because the response has expired.");
+            }
+            return true;
+        }
+
         final int numResponses = mResponses.size();
         if (numResponses >= AutofillManagerService.getPartitionMaxCount()) {
             Slog.e(TAG, "Not starting a new partition on " + id + " because session " + this.id
@@ -2402,6 +2429,14 @@
                     + id + " destroyed");
             return;
         }
+        if (action == ACTION_RESPONSE_EXPIRED) {
+            mExpiredResponse = true;
+            if (sDebug) {
+                Slog.d(TAG, "Set the response has expired.");
+            }
+            return;
+        }
+
         id.setSessionId(this.id);
         if (sVerbose) {
             Slog.v(TAG, "updateLocked(" + this.id + "): id=" + id + ", action="
@@ -2565,7 +2600,9 @@
                     return;
                 }
 
-                requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags);
+                if (requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags)) {
+                    return;
+                }
 
                 if (isSameViewEntered) {
                     return;
@@ -2678,22 +2715,9 @@
      * Returns whether we made a request to show inline suggestions.
      */
     private boolean requestShowInlineSuggestions(FillResponse response) {
-        IInlineSuggestionsResponseCallback inlineContentCallback = null;
-        synchronized (mLock) {
-            if (mInlineSuggestionsResponseCallbackFuture != null) {
-                try {
-                    inlineContentCallback = mInlineSuggestionsResponseCallbackFuture.get(
-                            INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-                } catch (TimeoutException e) {
-                    Log.w(TAG, "Exception getting inline suggestions callback in time: " + e);
-                } catch (CancellationException e) {
-                    Log.w(TAG, "Inline suggestions callback cancelled");
-                } catch (InterruptedException | ExecutionException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-        }
-
+        final IInlineSuggestionsResponseCallback inlineContentCallback =
+                mInlineSuggestionsRequestCallback != null
+                        ? mInlineSuggestionsRequestCallback.getResponseCallback() : null;
         if (inlineContentCallback == null) {
             Log.w(TAG, "Session input method callback is not set yet");
             return false;
@@ -2705,35 +2729,11 @@
             return false;
         }
 
-        final int size = datasets.size();
-        final ArrayList<InlineSuggestion> inlineSuggestions = new ArrayList<>();
-
-        for (int index = 0; index < size; index++) {
-            final Dataset dataset = datasets.get(index);
-            //TODO(b/146453536): Use the proper presentation/spec for currently focused view.
-            final InlinePresentation inlinePresentation = dataset.getFieldInlinePresentation(0);
-            if (inlinePresentation == null) {
-                if (sDebug) Log.d(TAG, "Missing InlinePresentation on dataset=" + dataset);
-                continue;
-            }
-            final InlinePresentationSpec spec = inlinePresentation.getInlinePresentationSpec();
-            final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo(
-                    spec, InlineSuggestionInfo.SOURCE_AUTOFILL, new String[] { "" });
-
-            inlineSuggestions.add(new InlineSuggestion(inlineSuggestionInfo,
-                    new IInlineContentProvider.Stub() {
-                        @Override
-                        public void provideContent(int width, int height,
-                                IInlineContentCallback callback) throws RemoteException {
-                            getUiForShowing().getSuggestionSurfaceForShowing(dataset, response,
-                                    mCurrentViewId, width, height, callback);
-                        }
-                    }));
-        }
-
+        InlineSuggestionsResponse inlineSuggestionsResponse =
+                InlineSuggestionFactory.createInlineSuggestionsResponse(response.getRequestId(),
+                        datasets.toArray(new Dataset[]{}), mCurrentViewId, mContext, this);
         try  {
-            inlineContentCallback.onInlineSuggestionsResponse(
-                    new InlineSuggestionsResponse(inlineSuggestions));
+            inlineContentCallback.onInlineSuggestionsResponse(inlineSuggestionsResponse);
         } catch (RemoteException e) {
             Log.w(TAG, "onFillReady() remote error calling onInlineSuggestionsResponse()");
             return false;
@@ -3015,10 +3015,12 @@
 
         final AutofillId focusedId = AutofillId.withoutSession(mCurrentViewId);
 
-        final InlineSuggestionsRequest inlineSuggestionsRequest = getInlineSuggestionsRequest();
+        final InlineSuggestionsRequest inlineSuggestionsRequest =
+                mInlineSuggestionsRequestCallback != null
+                        ? mInlineSuggestionsRequestCallback.getRequest() : null;
         final IInlineSuggestionsResponseCallback inlineSuggestionsResponseCallback =
-                getInlineSuggestionsResponseCallback();
-
+                mInlineSuggestionsRequestCallback != null
+                        ? mInlineSuggestionsRequestCallback.getResponseCallback() : null;
         remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName, focusedId,
                 currentValue, inlineSuggestionsRequest, inlineSuggestionsResponseCallback);
 
@@ -3028,40 +3030,6 @@
         return mAugmentedAutofillDestroyer;
     }
 
-    @Nullable
-    private InlineSuggestionsRequest getInlineSuggestionsRequest() {
-        if (mSuggestionsRequestFuture != null) {
-            try {
-                return mSuggestionsRequestFuture.get(
-                        INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            } catch (TimeoutException e) {
-                Log.w(TAG, "Exception getting inline suggestions request in time: " + e);
-            } catch (CancellationException e) {
-                Log.w(TAG, "Inline suggestions request cancelled");
-            } catch (InterruptedException | ExecutionException e) {
-                throw new RuntimeException(e);
-            }
-        }
-        return null;
-    }
-
-    @Nullable
-    private IInlineSuggestionsResponseCallback getInlineSuggestionsResponseCallback() {
-        if (mInlineSuggestionsResponseCallbackFuture != null) {
-            try {
-                return mInlineSuggestionsResponseCallbackFuture.get(
-                        INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            } catch (TimeoutException e) {
-                Log.w(TAG, "Exception getting inline suggestions callback in time: " + e);
-            } catch (CancellationException e) {
-                Log.w(TAG, "Inline suggestions callback cancelled");
-            } catch (InterruptedException | ExecutionException e) {
-                throw new RuntimeException(e);
-            }
-        }
-        return null;
-    }
-
     @GuardedBy("mLock")
     private void cancelAugmentedAutofillLocked() {
         final RemoteAugmentedAutofillService remoteService = mService
@@ -3711,6 +3679,8 @@
                 return "VIEW_EXITED";
             case ACTION_VALUE_CHANGED:
                 return "VALUE_CHANGED";
+            case ACTION_RESPONSE_EXPIRED:
+                return "RESPONSE_EXPIRED";
             default:
                 return "UNKNOWN_" + action;
         }
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 0511bf2..26bb7c3 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -37,7 +37,6 @@
 import android.text.TextUtils;
 import android.util.Slog;
 import android.view.KeyEvent;
-import android.view.SurfaceControl;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
 import android.view.autofill.IAutofillWindowPresenter;
@@ -45,7 +44,6 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.view.inline.IInlineContentCallback;
 import com.android.server.LocalServices;
 import com.android.server.UiModeManagerInternal;
 import com.android.server.UiThread;
@@ -173,36 +171,6 @@
     }
 
     /**
-     * TODO(b/137800469): Fill in javadoc.
-     * TODO(b/137800469): peoperly manage lifecycle of suggestions surfaces.
-     */
-    public void getSuggestionSurfaceForShowing(@NonNull Dataset dataset,
-            @NonNull FillResponse response, AutofillId autofillId, int width, int height,
-            IInlineContentCallback cb) {
-        if (dataset == null) {
-            Slog.w(TAG, "getSuggestionSurfaceForShowing() called with null dataset");
-        }
-        mHandler.post(() -> {
-            final InlineSuggestionUi inlineSuggestionUi = new InlineSuggestionUi(mContext);
-            final SurfaceControl suggestionSurface = inlineSuggestionUi.inflate(dataset,
-                    autofillId, width, height, v -> {
-                        Slog.d(TAG, "Inline suggestion clicked");
-                        hideFillUiUiThread(mCallback, true);
-                        if (mCallback != null) {
-                            final int datasetIndex = response.getDatasets().indexOf(dataset);
-                            mCallback.fill(response.getRequestId(), datasetIndex, dataset);
-                        }
-                    });
-
-            try {
-                cb.onContent(suggestionSurface);
-            } catch (RemoteException e) {
-                Slog.w(TAG, "RemoteException replying onContent(" + suggestionSurface + "): " + e);
-            }
-        });
-    }
-
-    /**
      * Shows the fill UI, removing the previous fill UI if the has changed.
      *
      * @param focusedId the currently focused field
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java
index 17cb739..2460732 100644
--- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java
@@ -16,24 +16,32 @@
 
 package com.android.server.autofill.ui;
 
+import static android.app.slice.SliceItem.FORMAT_IMAGE;
+import static android.app.slice.SliceItem.FORMAT_TEXT;
+
 import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
 import android.content.Context;
-import android.graphics.Color;
 import android.graphics.PixelFormat;
+import android.graphics.drawable.Icon;
 import android.os.IBinder;
-import android.service.autofill.Dataset;
 import android.util.Log;
-import android.util.Slog;
+import android.view.LayoutInflater;
 import android.view.SurfaceControl;
 import android.view.SurfaceControlViewHost;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.WindowManager;
-import android.view.autofill.AutofillId;
-import android.view.autofill.AutofillValue;
+import android.widget.ImageView;
 import android.widget.TextView;
 
+import com.android.internal.R;
+
+import java.util.List;
+
 /**
  * This is a temporary inline suggestion UI inflater which will be replaced by the ExtServices
  * implementation.
@@ -57,33 +65,76 @@
      */
     @MainThread
     @Nullable
-    public SurfaceControl inflate(@NonNull Dataset dataset, @NonNull AutofillId autofillId,
-            int width, int height, @Nullable View.OnClickListener onClickListener) {
+    public SurfaceControl inflate(@NonNull Slice slice, int width, int height,
+            @Nullable View.OnClickListener onClickListener) {
         Log.d(TAG, "Inflating the inline suggestion UI");
-        final int index = dataset.getFieldIds().indexOf(autofillId);
-        if (index < 0) {
-            Slog.w(TAG, "inflateInlineSuggestion(): AutofillId=" + autofillId
-                    + " not found in dataset");
-            return null;
-        }
-        final AutofillValue datasetValue = dataset.getFieldValues().get(index);
+
         //TODO(b/137800469): Pass in inputToken from IME.
         final SurfaceControlViewHost wvr = new SurfaceControlViewHost(mContext,
                 mContext.getDisplay(), (IBinder) null);
         final SurfaceControl sc = wvr.getSurfacePackage().getSurfaceControl();
-
-        TextView textView = new TextView(mContext);
-        textView.setText(datasetValue.getTextValue());
-        textView.setBackgroundColor(Color.WHITE);
-        textView.setTextColor(Color.BLACK);
+        final ViewGroup suggestionView = (ViewGroup) renderSlice(slice);
         if (onClickListener != null) {
-            textView.setOnClickListener(onClickListener);
+            suggestionView.setOnClickListener(onClickListener);
         }
 
         WindowManager.LayoutParams lp =
                 new WindowManager.LayoutParams(width, height,
                         WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.TRANSPARENT);
-        wvr.addView(textView, lp);
+        wvr.addView(suggestionView, lp);
         return sc;
     }
+
+    private View renderSlice(Slice slice) {
+        final LayoutInflater inflater = LayoutInflater.from(mContext);
+        final ViewGroup suggestionView =
+                (ViewGroup) inflater.inflate(R.layout.autofill_inline_suggestion, null);
+
+        final ImageView startIconView =
+                suggestionView.findViewById(R.id.autofill_inline_suggestion_start_icon);
+        final TextView titleView =
+                suggestionView.findViewById(R.id.autofill_inline_suggestion_title);
+        final TextView subtitleView =
+                suggestionView.findViewById(R.id.autofill_inline_suggestion_subtitle);
+        final ImageView endIconView =
+                suggestionView.findViewById(R.id.autofill_inline_suggestion_end_icon);
+
+        boolean hasStartIcon = false;
+        boolean hasEndIcon = false;
+        boolean hasSubtitle = false;
+        final List<SliceItem> sliceItems = slice.getItems();
+        for (int i = 0; i < sliceItems.size(); i++) {
+            final SliceItem sliceItem = sliceItems.get(i);
+            if (sliceItem.getFormat().equals(FORMAT_IMAGE)) {
+                final Icon sliceIcon = sliceItem.getIcon();
+                if (i == 0) { // start icon
+                    startIconView.setImageIcon(sliceIcon);
+                    hasStartIcon = true;
+                } else { // end icon
+                    endIconView.setImageIcon(sliceIcon);
+                    hasEndIcon = true;
+                }
+            } else if (sliceItem.getFormat().equals(FORMAT_TEXT)) {
+                final List<String> sliceHints = sliceItem.getHints();
+                final String sliceText = sliceItem.getText().toString();
+                if (sliceHints.contains("inline_title")) { // title
+                    titleView.setText(sliceText);
+                } else { // subtitle
+                    subtitleView.setText(sliceText);
+                    hasSubtitle = true;
+                }
+            }
+        }
+        if (!hasStartIcon) {
+            startIconView.setVisibility(View.GONE);
+        }
+        if (!hasEndIcon) {
+            endIconView.setVisibility(View.GONE);
+        }
+        if (!hasSubtitle) {
+            subtitleView.setVisibility(View.GONE);
+        }
+
+        return suggestionView;
+    }
 }
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 3bce322..b13bef2 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -1406,10 +1406,7 @@
         long oldId = Binder.clearCallingIdentity();
         final int[] userIds;
         try {
-            userIds =
-                    mContext
-                            .getSystemService(UserManager.class)
-                            .getProfileIds(callingUserId, false);
+            userIds = getUserManager().getProfileIds(callingUserId, false);
         } finally {
             Binder.restoreCallingIdentity(oldId);
         }
@@ -1452,6 +1449,11 @@
         if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) {
             return;
         }
+        dumpWithoutCheckingPermission(fd, pw, args);
+    }
+
+    @VisibleForTesting
+    void dumpWithoutCheckingPermission(FileDescriptor fd, PrintWriter pw, String[] args) {
         int userId = binderGetCallingUserId();
         if (!isUserReadyForBackup(userId)) {
             pw.println("Inactive");
@@ -1460,7 +1462,16 @@
 
         if (args != null) {
             for (String arg : args) {
-                if ("users".equals(arg.toLowerCase())) {
+                if ("-h".equals(arg)) {
+                    pw.println("'dumpsys backup' optional arguments:");
+                    pw.println("  -h       : this help text");
+                    pw.println("  a[gents] : dump information about defined backup agents");
+                    pw.println("  transportclients : dump information about transport clients");
+                    pw.println("  transportstats : dump transport statts");
+                    pw.println("  users    : dump the list of users for which backup service "
+                            + "is running");
+                    return;
+                } else if ("users".equals(arg.toLowerCase())) {
                     pw.print(DUMP_RUNNING_USERS_MESSAGE);
                     for (int i = 0; i < mUserServices.size(); i++) {
                         pw.print(" " + mUserServices.keyAt(i));
@@ -1471,11 +1482,12 @@
             }
         }
 
-        UserBackupManagerService userBackupManagerService =
-                getServiceForUserIfCallerHasPermission(UserHandle.USER_SYSTEM, "dump()");
-
-        if (userBackupManagerService != null) {
-            userBackupManagerService.dump(fd, pw, args);
+        for (int i = 0; i < mUserServices.size(); i++) {
+            UserBackupManagerService userBackupManagerService =
+                    getServiceForUserIfCallerHasPermission(mUserServices.keyAt(i), "dump()");
+            if (userBackupManagerService != null) {
+                userBackupManagerService.dump(fd, pw, args);
+            }
         }
     }
 
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 064cd06..f0fa99a 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -31,6 +31,7 @@
 import static com.android.server.backup.internal.BackupHandler.MSG_RETRY_CLEAR;
 import static com.android.server.backup.internal.BackupHandler.MSG_RUN_ADB_BACKUP;
 import static com.android.server.backup.internal.BackupHandler.MSG_RUN_ADB_RESTORE;
+import static com.android.server.backup.internal.BackupHandler.MSG_RUN_BACKUP;
 import static com.android.server.backup.internal.BackupHandler.MSG_RUN_CLEAR;
 import static com.android.server.backup.internal.BackupHandler.MSG_RUN_RESTORE;
 import static com.android.server.backup.internal.BackupHandler.MSG_SCHEDULE_BACKUP_PACKAGE;
@@ -111,7 +112,6 @@
 import com.android.server.backup.internal.OnTaskFinishedListener;
 import com.android.server.backup.internal.Operation;
 import com.android.server.backup.internal.PerformInitializeTask;
-import com.android.server.backup.internal.RunBackupReceiver;
 import com.android.server.backup.internal.RunInitializeReceiver;
 import com.android.server.backup.internal.SetupObserver;
 import com.android.server.backup.keyvalue.BackupRequest;
@@ -257,7 +257,6 @@
     // Retry interval for clear/init when the transport is unavailable
     private static final long TRANSPORT_RETRY_INTERVAL = 1 * AlarmManager.INTERVAL_HOUR;
 
-    public static final String RUN_BACKUP_ACTION = "android.app.backup.intent.RUN";
     public static final String RUN_INITIALIZE_ACTION = "android.app.backup.intent.INIT";
     private static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED";
     private static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName";
@@ -319,7 +318,6 @@
     private boolean mSetupComplete;
     private boolean mAutoRestore;
 
-    private final PendingIntent mRunBackupIntent;
     private final PendingIntent mRunInitIntent;
 
     private final ArraySet<String> mPendingInits = new ArraySet<>();  // transport names
@@ -417,7 +415,6 @@
     @Nullable private File mAncestralSerialNumberFile;
 
     private final ContentObserver mSetupObserver;
-    private final BroadcastReceiver mRunBackupReceiver;
     private final BroadcastReceiver mRunInitReceiver;
 
     /**
@@ -566,19 +563,9 @@
         mDataDir = Objects.requireNonNull(dataDir, "dataDir cannot be null");
         mBackupPasswordManager = new BackupPasswordManager(mContext, mBaseStateDir, mRng);
 
-        // Receivers for scheduled backups and transport initialization operations.
-        mRunBackupReceiver = new RunBackupReceiver(this);
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(RUN_BACKUP_ACTION);
-        context.registerReceiverAsUser(
-                mRunBackupReceiver,
-                UserHandle.of(userId),
-                filter,
-                android.Manifest.permission.BACKUP,
-                /* scheduler */ null);
-
+        // Receiver for transport initialization.
         mRunInitReceiver = new RunInitializeReceiver(this);
-        filter = new IntentFilter();
+        IntentFilter filter = new IntentFilter();
         filter.addAction(RUN_INITIALIZE_ACTION);
         context.registerReceiverAsUser(
                 mRunInitReceiver,
@@ -587,16 +574,6 @@
                 android.Manifest.permission.BACKUP,
                 /* scheduler */ null);
 
-        Intent backupIntent = new Intent(RUN_BACKUP_ACTION);
-        backupIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-        mRunBackupIntent =
-                PendingIntent.getBroadcastAsUser(
-                        context,
-                        /* requestCode */ 0,
-                        backupIntent,
-                        /* flags */ 0,
-                        UserHandle.of(userId));
-
         Intent initIntent = new Intent(RUN_INITIALIZE_ACTION);
         initIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
         mRunInitIntent =
@@ -659,7 +636,6 @@
         mAgentTimeoutParameters.stop();
         mConstants.stop();
         mContext.getContentResolver().unregisterContentObserver(mSetupObserver);
-        mContext.unregisterReceiver(mRunBackupReceiver);
         mContext.unregisterReceiver(mRunInitReceiver);
         mContext.unregisterReceiver(mPackageTrackingReceiver);
         mBackupHandler.stop();
@@ -2538,18 +2514,38 @@
                 KeyValueBackupJob.schedule(mUserId, mContext, mConstants);
             } else {
                 if (DEBUG) Slog.v(TAG, "Scheduling immediate backup pass");
-                synchronized (mQueueLock) {
-                    // Fire the intent that kicks off the whole shebang...
-                    try {
-                        mRunBackupIntent.send();
-                    } catch (PendingIntent.CanceledException e) {
-                        // should never happen
-                        Slog.e(TAG, "run-backup intent cancelled!");
-                    }
 
-                    // ...and cancel any pending scheduled job, because we've just superseded it
-                    KeyValueBackupJob.cancel(mUserId, mContext);
+                synchronized (getQueueLock()) {
+                    if (getPendingInits().size() > 0) {
+                        // If there are pending init operations, we process those and then settle
+                        // into the usual periodic backup schedule.
+                        if (MORE_DEBUG) {
+                            Slog.v(TAG, "Init pending at scheduled backup");
+                        }
+                        try {
+                            getAlarmManager().cancel(mRunInitIntent);
+                            mRunInitIntent.send();
+                        } catch (PendingIntent.CanceledException ce) {
+                            Slog.w(TAG, "Run init intent cancelled");
+                        }
+                        return;
+                    }
                 }
+
+                // Don't run backups if we're disabled or not yet set up.
+                if (!isEnabled() || !isSetupComplete()) {
+                    Slog.w(
+                            TAG,
+                            "Backup pass but enabled="  + isEnabled()
+                                    + " setupComplete=" + isSetupComplete());
+                    return;
+                }
+
+                // Fire the msg that kicks off the whole shebang...
+                Message message = mBackupHandler.obtainMessage(MSG_RUN_BACKUP);
+                mBackupHandler.sendMessage(message);
+                // ...and cancel any pending scheduled job, because we've just superseded it
+                KeyValueBackupJob.cancel(mUserId, mContext);
             }
         } finally {
             Binder.restoreCallingIdentity(oldId);
@@ -3545,14 +3541,7 @@
         try {
             if (args != null) {
                 for (String arg : args) {
-                    if ("-h".equals(arg)) {
-                        pw.println("'dumpsys backup' optional arguments:");
-                        pw.println("  -h       : this help text");
-                        pw.println("  a[gents] : dump information about defined backup agents");
-                        pw.println("  users    : dump the list of users for which backup service "
-                                + "is running");
-                        return;
-                    } else if ("agents".startsWith(arg)) {
+                    if ("agents".startsWith(arg)) {
                         dumpAgents(pw);
                         return;
                     } else if ("transportclients".equals(arg.toLowerCase())) {
@@ -3583,8 +3572,10 @@
     }
 
     private void dumpInternal(PrintWriter pw) {
+        // Add prefix for only non-system users so that system user dumpsys is the same as before
+        String userPrefix = mUserId == UserHandle.USER_SYSTEM ? "" : "User " + mUserId + ":";
         synchronized (mQueueLock) {
-            pw.println("Backup Manager is " + (mEnabled ? "enabled" : "disabled")
+            pw.println(userPrefix + "Backup Manager is " + (mEnabled ? "enabled" : "disabled")
                     + " / " + (!mSetupComplete ? "not " : "") + "setup complete / "
                     + (this.mPendingInits.size() == 0 ? "not " : "") + "pending init");
             pw.println("Auto-restore is " + (mAutoRestore ? "enabled" : "disabled"));
@@ -3594,13 +3585,13 @@
                     + " (now = " + System.currentTimeMillis() + ')');
             pw.println("  next scheduled: " + KeyValueBackupJob.nextScheduled(mUserId));
 
-            pw.println("Transport whitelist:");
+            pw.println(userPrefix + "Transport whitelist:");
             for (ComponentName transport : mTransportManager.getTransportWhitelist()) {
                 pw.print("    ");
                 pw.println(transport.flattenToShortString());
             }
 
-            pw.println("Available transports:");
+            pw.println(userPrefix + "Available transports:");
             final String[] transports = listAllTransports();
             if (transports != null) {
                 for (String t : transports) {
@@ -3626,18 +3617,18 @@
 
             mTransportManager.dumpTransportClients(pw);
 
-            pw.println("Pending init: " + mPendingInits.size());
+            pw.println(userPrefix + "Pending init: " + mPendingInits.size());
             for (String s : mPendingInits) {
                 pw.println("    " + s);
             }
 
-            pw.print("Ancestral: ");
+            pw.print(userPrefix + "Ancestral: ");
             pw.println(Long.toHexString(mAncestralToken));
-            pw.print("Current:   ");
+            pw.print(userPrefix + "Current:   ");
             pw.println(Long.toHexString(mCurrentToken));
 
             int numPackages = mBackupParticipants.size();
-            pw.println("Participants:");
+            pw.println(userPrefix + "Participants:");
             for (int i = 0; i < numPackages; i++) {
                 int uid = mBackupParticipants.keyAt(i);
                 pw.print("  uid: ");
@@ -3648,7 +3639,7 @@
                 }
             }
 
-            pw.println("Ancestral packages: "
+            pw.println(userPrefix + "Ancestral packages: "
                     + (mAncestralPackages == null ? "none" : mAncestralPackages.size()));
             if (mAncestralPackages != null) {
                 for (String pkg : mAncestralPackages) {
@@ -3657,17 +3648,17 @@
             }
 
             Set<String> processedPackages = mProcessedPackagesJournal.getPackagesCopy();
-            pw.println("Ever backed up: " + processedPackages.size());
+            pw.println(userPrefix + "Ever backed up: " + processedPackages.size());
             for (String pkg : processedPackages) {
                 pw.println("    " + pkg);
             }
 
-            pw.println("Pending key/value backup: " + mPendingBackups.size());
+            pw.println(userPrefix + "Pending key/value backup: " + mPendingBackups.size());
             for (BackupRequest req : mPendingBackups.values()) {
                 pw.println("    " + req);
             }
 
-            pw.println("Full backup queue:" + mFullBackupQueue.size());
+            pw.println(userPrefix + "Full backup queue:" + mFullBackupQueue.size());
             for (FullBackupEntry entry : mFullBackupQueue) {
                 pw.print("    ");
                 pw.print(entry.lastBackup);
diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
index eb62620..05396f3 100644
--- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java
+++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
@@ -21,12 +21,10 @@
 import static com.android.server.backup.BackupManagerService.TAG;
 
 import android.app.backup.RestoreSet;
-import android.content.Intent;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Message;
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.util.EventLog;
 import android.util.Pair;
 import android.util.Slog;
@@ -40,7 +38,6 @@
 import com.android.server.backup.TransportManager;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.fullbackup.PerformAdbBackupTask;
-import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
 import com.android.server.backup.keyvalue.BackupRequest;
 import com.android.server.backup.keyvalue.KeyValueBackupTask;
 import com.android.server.backup.params.AdbBackupParams;
@@ -73,10 +70,7 @@
     public static final int MSG_RESTORE_SESSION_TIMEOUT = 8;
     public static final int MSG_FULL_CONFIRMATION_TIMEOUT = 9;
     public static final int MSG_RUN_ADB_RESTORE = 10;
-    public static final int MSG_RETRY_INIT = 11;
     public static final int MSG_RETRY_CLEAR = 12;
-    public static final int MSG_WIDGET_BROADCAST = 13;
-    public static final int MSG_RUN_FULL_TRANSPORT_BACKUP = 14;
     public static final int MSG_REQUEST_BACKUP = 15;
     public static final int MSG_SCHEDULE_BACKUP_PACKAGE = 16;
     public static final int MSG_BACKUP_OPERATION_TIMEOUT = 17;
@@ -164,10 +158,6 @@
                                 .disposeOfTransportClient(transportClient, callerLogString);
                     }
                     Slog.v(TAG, "Backup requested but no transport available");
-                    synchronized (backupManagerService.getQueueLock()) {
-                        backupManagerService.setBackupRunning(false);
-                    }
-                    backupManagerService.getWakelock().release();
                     break;
                 }
 
@@ -175,6 +165,21 @@
                 List<String> queue = new ArrayList<>();
                 DataChangedJournal oldJournal = backupManagerService.getJournal();
                 synchronized (backupManagerService.getQueueLock()) {
+                    // Don't run backups if one is already running.
+                    if (backupManagerService.isBackupRunning()) {
+                        Slog.i(TAG, "Backup time but one already running");
+                        return;
+                    }
+
+                    if (DEBUG) {
+                        Slog.v(TAG, "Running a backup pass");
+                    }
+
+                    // Acquire the wakelock and pass it to the backup thread. It will be released
+                    // once backup concludes.
+                    backupManagerService.setBackupRunning(true);
+                    backupManagerService.getWakelock().acquire();
+
                     // Do we have any work to do?  Construct the work queue
                     // then release the synchronization lock to actually run
                     // the backup.
@@ -279,12 +284,6 @@
                 break;
             }
 
-            case MSG_RUN_FULL_TRANSPORT_BACKUP: {
-                PerformFullTransportBackupTask task = (PerformFullTransportBackupTask) msg.obj;
-                (new Thread(task, "transport-backup")).start();
-                break;
-            }
-
             case MSG_RUN_RESTORE: {
                 RestoreParams params = (RestoreParams) msg.obj;
                 Slog.d(TAG, "MSG_RUN_RESTORE observer=" + params.observer);
@@ -445,12 +444,6 @@
                 break;
             }
 
-            case MSG_WIDGET_BROADCAST: {
-                final Intent intent = (Intent) msg.obj;
-                backupManagerService.getContext().sendBroadcastAsUser(intent, UserHandle.SYSTEM);
-                break;
-            }
-
             case MSG_REQUEST_BACKUP: {
                 BackupParams params = (BackupParams) msg.obj;
                 if (MORE_DEBUG) {
diff --git a/services/backup/java/com/android/server/backup/internal/RunBackupReceiver.java b/services/backup/java/com/android/server/backup/internal/RunBackupReceiver.java
deleted file mode 100644
index d37b106..0000000
--- a/services/backup/java/com/android/server/backup/internal/RunBackupReceiver.java
+++ /dev/null
@@ -1,110 +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.server.backup.internal;
-
-import static com.android.server.backup.BackupManagerService.DEBUG;
-import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
-import static com.android.server.backup.BackupManagerService.TAG;
-import static com.android.server.backup.UserBackupManagerService.RUN_BACKUP_ACTION;
-import static com.android.server.backup.internal.BackupHandler.MSG_RUN_BACKUP;
-
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Handler;
-import android.os.Message;
-import android.util.Slog;
-
-import com.android.server.backup.UserBackupManagerService;
-
-/**
- * A {@link BroadcastReceiver} for the action {@link UserBackupManagerService#RUN_BACKUP_ACTION}
- * that runs an immediate backup operation if eligible.
- */
-public class RunBackupReceiver extends BroadcastReceiver {
-    private final UserBackupManagerService mUserBackupManagerService;
-
-    public RunBackupReceiver(UserBackupManagerService userBackupManagerService) {
-        mUserBackupManagerService = userBackupManagerService;
-    }
-
-    /**
-     * Run a backup pass if we're eligible. We're eligible if the following conditions are met:
-     *
-     * <ul>
-     *   <li>No transports are pending initialization (otherwise we kick off an initialization
-     *       operation instead).
-     *   <li>Backup is enabled for the user.
-     *   <li>The user has completed setup.
-     *   <li>No backup operation is currently running for the user.
-     * </ul>
-     */
-    public void onReceive(Context context, Intent intent) {
-        if (!RUN_BACKUP_ACTION.equals(intent.getAction())) {
-            return;
-        }
-
-        synchronized (mUserBackupManagerService.getQueueLock()) {
-            if (mUserBackupManagerService.getPendingInits().size() > 0) {
-                // If there are pending init operations, we process those and then settle into the
-                // usual periodic backup schedule.
-                if (MORE_DEBUG) {
-                    Slog.v(TAG, "Init pending at scheduled backup");
-                }
-                try {
-                    PendingIntent runInitIntent = mUserBackupManagerService.getRunInitIntent();
-                    mUserBackupManagerService.getAlarmManager().cancel(runInitIntent);
-                    runInitIntent.send();
-                } catch (PendingIntent.CanceledException ce) {
-                    Slog.w(TAG, "Run init intent cancelled");
-                }
-            } else {
-                // Don't run backups if we're disabled or not yet set up.
-                if (!mUserBackupManagerService.isEnabled()
-                        || !mUserBackupManagerService.isSetupComplete()) {
-                    Slog.w(
-                            TAG,
-                            "Backup pass but enabled="
-                                    + mUserBackupManagerService.isEnabled()
-                                    + " setupComplete="
-                                    + mUserBackupManagerService.isSetupComplete());
-                    return;
-                }
-
-                // Don't run backups if one is already running.
-                if (mUserBackupManagerService.isBackupRunning()) {
-                    Slog.i(TAG, "Backup time but one already running");
-                    return;
-                }
-
-                if (DEBUG) {
-                    Slog.v(TAG, "Running a backup pass");
-                }
-
-                // Acquire the wakelock and pass it to the backup thread. It will be released once
-                // backup concludes.
-                mUserBackupManagerService.setBackupRunning(true);
-                mUserBackupManagerService.getWakelock().acquire();
-
-                Handler backupHandler = mUserBackupManagerService.getBackupHandler();
-                Message message = backupHandler.obtainMessage(MSG_RUN_BACKUP);
-                backupHandler.sendMessage(message);
-            }
-        }
-    }
-}
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index bda0e3b..0bcf45d 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -47,6 +47,8 @@
 import android.os.SELinux;
 import android.os.UserHandle;
 import android.os.WorkSource;
+import android.util.FeatureFlagUtils;
+import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -65,14 +67,18 @@
 import com.android.server.backup.remote.RemoteCallable;
 import com.android.server.backup.remote.RemoteResult;
 import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.transport.TransportNotAvailableException;
 import com.android.server.backup.utils.AppBackupUtils;
 
+import libcore.io.IoUtils;
+
 import java.io.Closeable;
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.lang.annotation.Retention;
@@ -80,8 +86,10 @@
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -169,10 +177,14 @@
 // TODO: Consider having the caller responsible for some clean-up (like resetting state)
 // TODO: Distinguish between cancel and time-out where possible for logging/monitoring/observing
 public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
+    private static final String TAG = "KVBT";
+
     private static final int THREAD_PRIORITY = Process.THREAD_PRIORITY_BACKGROUND;
     private static final AtomicInteger THREAD_COUNT = new AtomicInteger();
     private static final String BLANK_STATE_FILE_NAME = "blank_state";
     private static final String PM_PACKAGE = UserBackupManagerService.PACKAGE_MANAGER_SENTINEL;
+    private static final String SUCCESS_STATE_SUBDIR = "backing-up";
+    @VisibleForTesting static final String NO_DATA_END_SENTINEL = "@end@";
     @VisibleForTesting public static final String STAGING_FILE_SUFFIX = ".data";
     @VisibleForTesting public static final String NEW_STATE_FILE_SUFFIX = ".new";
 
@@ -336,6 +348,7 @@
 
         mHasDataToBackup = false;
 
+        Set<String> backedUpApps = new HashSet<>();
         int status = BackupTransport.TRANSPORT_OK;
         try {
             startTask();
@@ -347,13 +360,18 @@
                     } else {
                         backupPackage(packageName);
                     }
+                    setSuccessState(packageName, true);
+                    backedUpApps.add(packageName);
                 } catch (AgentException e) {
+                    setSuccessState(packageName, false);
                     if (e.isTransitory()) {
                         // We try again this package in the next backup pass.
                         mBackupManagerService.dataChangedImpl(packageName);
                     }
                 }
             }
+
+            informTransportOfUnchangedApps(backedUpApps);
         } catch (TaskException e) {
             if (e.isStateCompromised()) {
                 mBackupManagerService.resetBackupState(mStateDirectory);
@@ -364,6 +382,191 @@
         finishTask(status);
     }
 
+    /**
+     * Tell the transport about all of the packages which have successfully backed up but
+     * have not informed the framework that they have new data. This allows transports to
+     * differentiate between packages which are not backing data up due to an error and
+     * packages which are not backing up data because nothing has changed.
+     *
+     * The current implementation involves creating a state file when a backup succeeds,
+     * on subsequent runs the existence of the file indicates the backup ran successfully
+     * but there was no data. If a backup fails with an error, or if the package is not
+     * eligible for backup by the transport any more, the status file is removed and the
+     * "no data" message will not be sent to the transport until another successful data
+     * changed backup has succeeded.
+     *
+     * @param appsBackedUp The Set of apps backed up during this run so we can exclude them
+     *                     from the list of successfully backed up apps that we signal to
+     *                     the transport have no data.
+     */
+    private void informTransportOfUnchangedApps(Set<String> appsBackedUp) {
+        // If the feautre is not enabled then we just exit early.
+        if (!FeatureFlagUtils.isEnabled(mBackupManagerService.getContext(),
+                FeatureFlagUtils.BACKUP_NO_KV_DATA_CHANGE_CALLS)) {
+            return;
+        }
+
+        String[] succeedingPackages = getSucceedingPackages();
+        if (succeedingPackages == null) {
+            // Nothing is succeeding, so end early.
+            return;
+        }
+
+        int flags = BackupTransport.FLAG_DATA_NOT_CHANGED;
+        if (mUserInitiated) {
+            flags |= BackupTransport.FLAG_USER_INITIATED;
+        }
+
+        boolean noDataPackageEncountered = false;
+        try {
+            IBackupTransport transport =
+                    mTransportClient.connectOrThrow("KVBT.informTransportOfEmptyBackups()");
+
+            for (String packageName : succeedingPackages) {
+                if (appsBackedUp.contains(packageName)) {
+                    Log.v(TAG, "Skipping package which was backed up this time :" + packageName);
+                    // Skip packages we backed up in this run.
+                    continue;
+                }
+
+                PackageInfo packageInfo;
+                try {
+                    packageInfo = mPackageManager.getPackageInfo(packageName, /* flags */ 0);
+                    if (!isEligibleForNoDataCall(packageInfo)) {
+                        // If the package isn't eligible any more we can forget about it and move
+                        // on.
+                        clearStatus(packageName);
+                        continue;
+                    }
+                } catch (PackageManager.NameNotFoundException e) {
+                    // If the package has been uninstalled we can forget about it and move on.
+                    clearStatus(packageName);
+                    continue;
+                }
+
+                sendNoDataChangedTo(transport, packageInfo, flags);
+                noDataPackageEncountered = true;
+            }
+
+            if (noDataPackageEncountered) {
+                // If we've notified the transport of an unchanged package we need to
+                // tell it that it's seen all of the unchanged packages. We do this by
+                // reporting the end sentinel package as unchanged.
+                PackageInfo endSentinal = new PackageInfo();
+                endSentinal.packageName = NO_DATA_END_SENTINEL;
+                sendNoDataChangedTo(transport, endSentinal, flags);
+            }
+        } catch (TransportNotAvailableException | RemoteException e) {
+            Log.e(TAG, "Could not inform transport of all unchanged apps", e);
+        }
+    }
+
+    /** Determine if a package is eligible to be backed up to the transport */
+    private boolean isEligibleForNoDataCall(PackageInfo packageInfo) {
+        return AppBackupUtils.appIsKeyValueOnly(packageInfo)
+                && AppBackupUtils.appIsRunningAndEligibleForBackupWithTransport(mTransportClient,
+                packageInfo.packageName, mPackageManager, mUserId);
+    }
+
+    /** Send the "no data changed" message to a transport for a specific package */
+    private void sendNoDataChangedTo(IBackupTransport transport, PackageInfo packageInfo, int flags)
+            throws RemoteException {
+        ParcelFileDescriptor pfd;
+        try {
+            pfd = ParcelFileDescriptor.open(mBlankStateFile, MODE_READ_ONLY | MODE_CREATE);
+        } catch (FileNotFoundException e) {
+            Log.e(TAG, "Unable to find blank state file, aborting unchanged apps signal.");
+            return;
+        }
+        try {
+            int result = transport.performBackup(packageInfo, pfd, flags);
+            if (result == BackupTransport.TRANSPORT_ERROR
+                    || result == BackupTransport.TRANSPORT_NOT_INITIALIZED) {
+                Log.w(
+                        TAG,
+                        "Aborting informing transport of unchanged apps, transport" + " errored");
+                return;
+            }
+
+            transport.finishBackup();
+        } finally {
+            IoUtils.closeQuietly(pfd);
+        }
+    }
+
+    /** Get the list of package names which are marked as having previously succeeded */
+    private String[] getSucceedingPackages() {
+        File stateDirectory = getTopLevelSuccessStateDirectory(/* createIfMissing */ false);
+        if (stateDirectory == null) {
+            // getSuccessStateFileFor logs when we can't use the state area
+            return null;
+        }
+
+        return stateDirectory.list();
+    }
+
+    /** Sets the indicator that a package backup is succeeding */
+    private void setSuccessState(String packageName, boolean success) {
+        File successStateFile = getSuccessStateFileFor(packageName);
+        if (successStateFile == null) {
+            // The error will have been logged by getSuccessStateFileFor().
+            return;
+        }
+
+        if (successStateFile.exists() != success) {
+            // If there's been a change of state
+            if (!success) {
+                // Clear the status if we're now failing
+                clearStatus(packageName, successStateFile);
+                return;
+            }
+
+            // For succeeding packages we want the file
+            try {
+                if (!successStateFile.createNewFile()) {
+                    Log.w(TAG, "Unable to permanently record success for " + packageName);
+                }
+            } catch (IOException e) {
+                Log.w(TAG, "Unable to permanently record success for " + packageName, e);
+            }
+        }
+    }
+
+    /** Clear the status file for a specific package */
+    private void clearStatus(String packageName) {
+        File successStateFile = getSuccessStateFileFor(packageName);
+        if (successStateFile == null) {
+            // The error will have been logged by getSuccessStateFileFor().
+            return;
+        }
+        clearStatus(packageName, successStateFile);
+    }
+
+    /** Clear the status file for a package once we have the File representation */
+    private void clearStatus(String packageName, File successStateFile) {
+        if (successStateFile.exists()) {
+            if (!successStateFile.delete()) {
+                Log.w(TAG, "Unable to remove status file for " + packageName);
+            }
+        }
+    }
+
+    /** Get the backup state file for a package **/
+    private File getSuccessStateFileFor(String packageName) {
+        File stateDirectory = getTopLevelSuccessStateDirectory(/* createIfMissing */ true);
+        return stateDirectory == null ? null : new File(stateDirectory, packageName);
+    }
+
+    /** The top level directory for success state files */
+    private File getTopLevelSuccessStateDirectory(boolean createIfMissing) {
+        File directory = new File(mStateDirectory, SUCCESS_STATE_SUBDIR);
+        if (!directory.exists() && createIfMissing && !directory.mkdirs()) {
+            Log.e(TAG, "Unable to create backing-up state directory");
+            return null;
+        }
+        return directory;
+    }
+
     /** Returns transport status. */
     private int sendDataToTransport(@Nullable PackageInfo packageInfo)
             throws AgentException, TaskException {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index e976811..434a97e 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -361,13 +361,18 @@
         }
 
         @Override
-        public boolean isDeviceAssociated(String packageName, String macAddress, int userId) {
+        public boolean isDeviceAssociatedForWifiConnection(String packageName, String macAddress,
+                int userId) {
             getContext().enforceCallingOrSelfPermission(
                     android.Manifest.permission.MANAGE_COMPANION_DEVICES, "isDeviceAssociated");
 
+            boolean bypassMacPermission = getContext().getPackageManager().checkPermission(
+                    android.Manifest.permission.COMPANION_APPROVE_WIFI_CONNECTIONS, packageName)
+                    == PackageManager.PERMISSION_GRANTED;
+
             return CollectionUtils.any(
                     readAllAssociations(userId, packageName),
-                    a -> Objects.equals(a.deviceAddress, macAddress));
+                    a -> bypassMacPermission || Objects.equals(a.deviceAddress, macAddress));
         }
 
         private void checkCanCallNotificationApi(String callingPackage) throws RemoteException {
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 27824af..8eca62a 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -175,8 +175,8 @@
     }
 
     @Override // from SystemService
-    public boolean isSupported(UserInfo userInfo) {
-        return userInfo.isFull() || userInfo.isManagedProfile();
+    public boolean isSupportedUser(TargetUser user) {
+        return user.getUserInfo().isFull() || user.getUserInfo().isManagedProfile();
     }
 
     @Override // from SystemService
diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java
index 9cdb58d..b54ec4e 100644
--- a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java
+++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java
@@ -27,6 +27,7 @@
 import android.app.contentsuggestions.ISelectionsCallback;
 import android.app.contentsuggestions.SelectionsRequest;
 import android.content.Context;
+import android.graphics.Bitmap;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.RemoteException;
@@ -61,6 +62,10 @@
     private static final boolean VERBOSE = false; // TODO: make dynamic
 
     private static final int MAX_TEMP_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
+    /**
+     * Key into the extras Bundle passed to {@link #provideContextImage(int, Bundle)}.
+     */
+    private static final String EXTRA_BITMAP = "android.contentsuggestions.extra.BITMAP";
 
     private ActivityTaskManagerInternal mActivityTaskManagerInternal;
 
@@ -111,6 +116,33 @@
 
     private class ContentSuggestionsManagerStub extends IContentSuggestionsManager.Stub {
         @Override
+        public void provideContextBitmap(
+                int userId,
+                @NonNull Bitmap bitmap,
+                @NonNull Bundle imageContextRequestExtras) {
+            if (bitmap == null) {
+                throw new IllegalArgumentException("Expected non-null bitmap");
+            }
+            if (imageContextRequestExtras == null) {
+                throw new IllegalArgumentException("Expected non-null imageContextRequestExtras");
+            }
+            enforceCaller(UserHandle.getCallingUserId(), "provideContextBitmap");
+
+            synchronized (mLock) {
+                final ContentSuggestionsPerUserService service = getServiceForUserLocked(userId);
+                if (service != null) {
+                    // TODO(b/147324195): Temporarily pass bitmap until we change the service API.
+                    imageContextRequestExtras.putParcelable(EXTRA_BITMAP, bitmap);
+                    service.provideContextImageLocked(/* taskId = */ -1, imageContextRequestExtras);
+                } else {
+                    if (VERBOSE) {
+                        Slog.v(TAG, "provideContextImageLocked: no service for " + userId);
+                    }
+                }
+            }
+        }
+
+        @Override
         public void provideContextImage(
                 int userId,
                 int taskId,
diff --git a/services/core/Android.bp b/services/core/Android.bp
index a1f57cb..a603fa9 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -83,6 +83,7 @@
         ":storaged_aidl",
         ":vold_aidl",
         ":platform-compat-config",
+        ":display-device-config",
         "java/com/android/server/EventLogTags.logtags",
         "java/com/android/server/am/EventLogTags.logtags",
         "java/com/android/server/wm/EventLogTags.logtags",
@@ -90,6 +91,7 @@
     ],
 
     libs: [
+        "services-stubs",
         "services.net",
         "android.hardware.light-V2.0-java",
         "android.hardware.power-V1.0-java",
@@ -111,6 +113,7 @@
         "android.hardware.broadcastradio-V2.0-java",
         "android.hardware.health-V1.0-java",
         "android.hardware.health-V2.0-java",
+        "android.hardware.light-java",
         "android.hardware.weaver-V1.0-java",
         "android.hardware.biometrics.face-V1.0-java",
         "android.hardware.biometrics.fingerprint-V2.1-java",
@@ -165,9 +168,3 @@
     name: "protolog.conf.json.gz",
     src: ":services.core.json.gz",
 }
-
-platform_compat_config {
-    name: "services-core-platform-compat-config",
-    src: ":services.core.unboosted",
-}
-
diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
index 2f8c506..f3647602 100644
--- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -23,8 +23,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 
-import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
-
 import java.util.List;
 import java.util.Set;
 
@@ -198,6 +196,12 @@
             long beginTime, long endTime, boolean obfuscateInstantApps);
 
     /**
+     * Returns the events for the user in the given time period.
+     */
+    public abstract UsageEvents queryEventsForUser(@UserIdInt int userId, long beginTime,
+            long endTime, boolean shouldObfuscateInstantApps);
+
+    /**
      * Used to persist the last time a job was run for this app, in order to make decisions later
      * whether a job should be deferred until later. The time passed in should be in elapsed
      * realtime since boot.
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 312dd46..994c314 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -33,6 +33,7 @@
 import android.content.pm.parsing.ComponentParseUtils;
 import android.os.Bundle;
 import android.os.PersistableBundle;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.SparseArray;
 
@@ -41,6 +42,7 @@
 import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Collection;
 import java.util.List;
 import java.util.function.Consumer;
 
@@ -168,7 +170,7 @@
      * Return a List of all application packages that are installed on the
      * device, for a specific user. If flag GET_UNINSTALLED_PACKAGES has been
      * set, a list of all applications including those deleted with
-     * {@code DONT_DELETE_DATA} (partially installed apps with data directory)
+     * {@code DELETE_KEEP_DATA} (partially installed apps with data directory)
      * will be returned.
      *
      * @param flags Additional option flags to modify the data returned.
@@ -182,7 +184,7 @@
      *         information is retrieved from the list of uninstalled
      *         applications (which includes installed applications as well as
      *         applications with data directory i.e. applications which had been
-     *         deleted with {@code DONT_DELETE_DATA} flag set).
+     *         deleted with {@code DELETE_KEEP_DATA} flag set).
      */
     public abstract List<ApplicationInfo> getInstalledApplications(
             @ApplicationInfoFlags int flags, @UserIdInt int userId, int callingUid);
@@ -478,10 +480,12 @@
      *                            will be disabled. Pass in null or an empty list to disable
      *                            all overlays. The order of the items is significant if several
      *                            overlays modify the same resource.
+     * @param outUpdatedPackageNames An output list that contains the package names of packages
+     *                               affected by the update of enabled overlays.
      * @return true if all packages names were known by the package manager, false otherwise
      */
     public abstract boolean setEnabledOverlayPackages(int userId, String targetPackageName,
-            List<String> overlayPackageNames);
+            List<String> overlayPackageNames, Collection<String> outUpdatedPackageNames);
 
     /**
      * Resolves an activity intent, allowing instant apps to be resolved.
@@ -686,6 +690,27 @@
             int userId);
 
     /**
+     * Return the processes that have been declared for a uid.
+     *
+     * @param uid The uid to query.
+     *
+     * @return Returns null if there are no declared processes for the uid; otherwise,
+     * returns the set of processes it declared.
+     */
+    public abstract ArrayMap<String, ProcessInfo> getProcessesForUid(int uid);
+
+    /**
+     * Return the gids associated with a particular permission.
+     *
+     * @param permissionName The name of the permission to query.
+     * @param userId The user id the gids will be associated with.
+     *
+     * @return Returns null if there are no gids associated with the permission, otherwise an
+     * array if the gid ints.
+     */
+    public abstract int[] getPermissionGids(String permissionName, int userId);
+
+    /**
      * Return if device is currently in a "core" boot environment, typically
      * used to support full-disk encryption. Only apps marked with
      * {@code coreApp} attribute are available.
@@ -811,6 +836,12 @@
     public abstract boolean isApexPackage(String packageName);
 
     /**
+     * Returns list of {@code packageName} of apks inside the given apex.
+     * @param apexPackageName Package name of the apk container of apex
+     */
+    public abstract List<String> getApksInApex(String apexPackageName);
+
+    /**
      * Uninstalls given {@code packageName}.
      *
      * @param packageName apex package to uninstall.
diff --git a/services/core/java/android/os/UserManagerInternal.java b/services/core/java/android/os/UserManagerInternal.java
index d84197c..aedafbb 100644
--- a/services/core/java/android/os/UserManagerInternal.java
+++ b/services/core/java/android/os/UserManagerInternal.java
@@ -162,7 +162,8 @@
      * createAndManageUser is called by the device owner.
      */
     public abstract UserInfo createUserEvenWhenDisallowed(String name, String userType,
-            int flags, String[] disallowedPackages);
+            int flags, String[] disallowedPackages)
+            throws UserManager.CheckedUserOperationException;
 
     /**
      * Same as {@link UserManager#removeUser(int userId)}, but bypasses the check for
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 73b6c7a..0f2fb92 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -208,6 +208,7 @@
     AppWakeupHistory mAppWakeupHistory;
     ClockReceiver mClockReceiver;
     final DeliveryTracker mDeliveryTracker = new DeliveryTracker();
+    IBinder.DeathRecipient mListenerDeathRecipient;
     Intent mTimeTickIntent;
     IAlarmListener mTimeTickTrigger;
     PendingIntent mDateChangeSender;
@@ -1447,6 +1448,18 @@
     public void onStart() {
         mInjector.init();
 
+        mListenerDeathRecipient = new IBinder.DeathRecipient() {
+            @Override
+            public void binderDied() {
+            }
+
+            @Override
+            public void binderDied(IBinder who) {
+                final IAlarmListener listener = IAlarmListener.Stub.asInterface(who);
+                removeImpl(null, listener);
+            }
+        };
+
         synchronized (mLock) {
             mHandler = new AlarmHandler();
             mConstants = new Constants(mHandler);
@@ -1653,6 +1666,15 @@
             return;
         }
 
+        if (directReceiver != null) {
+            try {
+                directReceiver.asBinder().linkToDeath(mListenerDeathRecipient, 0);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Dropping unreachable alarm listener " + listenerTag);
+                return;
+            }
+        }
+
         // Sanity check the window length.  This will catch people mistakenly
         // trying to pass an end-of-window timestamp rather than a duration.
         if (windowLength > AlarmManager.INTERVAL_HALF_DAY) {
@@ -2079,8 +2101,9 @@
         @Override
         public long currentNetworkTimeMillis() {
             final NtpTrustedTime time = NtpTrustedTime.getInstance(getContext());
-            if (time.hasCache()) {
-                return time.currentTimeMillis();
+            NtpTrustedTime.TimeResult ntpResult = time.getCachedTimeResult();
+            if (ntpResult != null) {
+                return ntpResult.currentTimeMillis();
             } else {
                 throw new ParcelableException(new DateTimeException("Missing NTP fix"));
             }
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index a33fcd5..8074900 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -66,8 +66,8 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.util.DumpUtils;
 import com.android.server.am.BatteryStatsService;
-import com.android.server.lights.Light;
 import com.android.server.lights.LightsManager;
+import com.android.server.lights.LogicalLight;
 
 import java.io.File;
 import java.io.FileDescriptor;
@@ -1065,7 +1065,7 @@
     }
 
     private final class Led {
-        private final Light mBatteryLight;
+        private final LogicalLight mBatteryLight;
 
         private final int mBatteryLowARGB;
         private final int mBatteryMediumARGB;
@@ -1100,7 +1100,7 @@
                     mBatteryLight.setColor(mBatteryLowARGB);
                 } else {
                     // Flash red when battery is low and not charging
-                    mBatteryLight.setFlashing(mBatteryLowARGB, Light.LIGHT_FLASH_TIMED,
+                    mBatteryLight.setFlashing(mBatteryLowARGB, LogicalLight.LIGHT_FLASH_TIMED,
                             mBatteryLedOn, mBatteryLedOff);
                 }
             } else if (status == BatteryManager.BATTERY_STATUS_CHARGING
diff --git a/services/core/java/com/android/server/BluetoothAirplaneModeListener.java b/services/core/java/com/android/server/BluetoothAirplaneModeListener.java
new file mode 100644
index 0000000..31cd5d5
--- /dev/null
+++ b/services/core/java/com/android/server/BluetoothAirplaneModeListener.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProfile.ServiceListener;
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.provider.Settings;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * The BluetoothAirplaneModeListener handles system airplane mode change callback and checks
+ * whether we need to inform BluetoothManagerService on this change.
+ *
+ * The information of airplane mode turns on would not be passed to the BluetoothManagerService
+ * when Bluetooth is on and Bluetooth is in one of the following situations:
+ *   1. Bluetooth A2DP is connected.
+ *   2. Bluetooth Hearing Aid profile is connected.
+ */
+class BluetoothAirplaneModeListener {
+    private static final String TAG = "BluetoothAirplaneModeListener";
+    @VisibleForTesting static final String TOAST_COUNT = "bluetooth_airplane_toast_count";
+
+    private static final int MSG_AIRPLANE_MODE_CHANGED = 0;
+
+    @VisibleForTesting static final int MAX_TOAST_COUNT = 10; // 10 times
+
+    private final BluetoothManagerService mBluetoothManager;
+    private final BluetoothAirplaneModeHandler mHandler;
+    private AirplaneModeHelper mAirplaneHelper;
+
+    @VisibleForTesting int mToastCount = 0;
+
+    BluetoothAirplaneModeListener(BluetoothManagerService service, Looper looper, Context context) {
+        mBluetoothManager = service;
+
+        mHandler = new BluetoothAirplaneModeHandler(looper);
+        context.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
+                mAirplaneModeObserver);
+    }
+
+    private final ContentObserver mAirplaneModeObserver = new ContentObserver(null) {
+        @Override
+        public void onChange(boolean unused) {
+            // Post from system main thread to android_io thread.
+            Message msg = mHandler.obtainMessage(MSG_AIRPLANE_MODE_CHANGED);
+            mHandler.sendMessage(msg);
+        }
+    };
+
+    private class BluetoothAirplaneModeHandler extends Handler {
+        BluetoothAirplaneModeHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_AIRPLANE_MODE_CHANGED:
+                    handleAirplaneModeChange();
+                    break;
+                default:
+                    Log.e(TAG, "Invalid message: " + msg.what);
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Call after boot complete
+     */
+    @VisibleForTesting
+    void start(AirplaneModeHelper helper) {
+        Log.i(TAG, "start");
+        mAirplaneHelper = helper;
+        mToastCount = mAirplaneHelper.getSettingsInt(TOAST_COUNT);
+    }
+
+    @VisibleForTesting
+    boolean shouldPopToast() {
+        if (mToastCount >= MAX_TOAST_COUNT) {
+            return false;
+        }
+        mToastCount++;
+        mAirplaneHelper.setSettingsInt(TOAST_COUNT, mToastCount);
+        return true;
+    }
+
+    @VisibleForTesting
+    void handleAirplaneModeChange() {
+        if (shouldSkipAirplaneModeChange()) {
+            Log.i(TAG, "Ignore airplane mode change");
+            // We have to store Bluetooth state here, so if user turns off Bluetooth
+            // after airplane mode is turned on, we don't forget to turn on Bluetooth
+            // when airplane mode turns off.
+            mAirplaneHelper.setSettingsInt(Settings.Global.BLUETOOTH_ON,
+                    BluetoothManagerService.BLUETOOTH_ON_AIRPLANE);
+            if (shouldPopToast()) {
+                mAirplaneHelper.showToastMessage();
+            }
+            return;
+        }
+        mAirplaneHelper.onAirplaneModeChanged(mBluetoothManager);
+    }
+
+    @VisibleForTesting
+    boolean shouldSkipAirplaneModeChange() {
+        if (mAirplaneHelper == null) {
+            return false;
+        }
+        if (!mAirplaneHelper.isBluetoothOn() || !mAirplaneHelper.isAirplaneModeOn()
+                || !mAirplaneHelper.isA2dpOrHearingAidConnected()) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Helper class that handles callout and callback methods without
+     * complex logic.
+     */
+    @VisibleForTesting
+    public static class AirplaneModeHelper {
+        private volatile BluetoothA2dp mA2dp;
+        private volatile BluetoothHearingAid mHearingAid;
+        private final BluetoothAdapter mAdapter;
+        private final Context mContext;
+
+        AirplaneModeHelper(Context context) {
+            mAdapter = BluetoothAdapter.getDefaultAdapter();
+            mContext = context;
+
+            mAdapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.A2DP);
+            mAdapter.getProfileProxy(mContext, mProfileServiceListener,
+                    BluetoothProfile.HEARING_AID);
+        }
+
+        private final ServiceListener mProfileServiceListener = new ServiceListener() {
+            @Override
+            public void onServiceConnected(int profile, BluetoothProfile proxy) {
+                // Setup Bluetooth profile proxies
+                switch (profile) {
+                    case BluetoothProfile.A2DP:
+                        mA2dp = (BluetoothA2dp) proxy;
+                        break;
+                    case BluetoothProfile.HEARING_AID:
+                        mHearingAid = (BluetoothHearingAid) proxy;
+                        break;
+                    default:
+                        break;
+                }
+            }
+
+            @Override
+            public void onServiceDisconnected(int profile) {
+                // Clear Bluetooth profile proxies
+                switch (profile) {
+                    case BluetoothProfile.A2DP:
+                        mA2dp = null;
+                        break;
+                    case BluetoothProfile.HEARING_AID:
+                        mHearingAid = null;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        };
+
+        @VisibleForTesting
+        public boolean isA2dpOrHearingAidConnected() {
+            return isA2dpConnected() || isHearingAidConnected();
+        }
+
+        @VisibleForTesting
+        public boolean isBluetoothOn() {
+            final BluetoothAdapter adapter = mAdapter;
+            if (adapter == null) {
+                return false;
+            }
+            return adapter.getLeState() == BluetoothAdapter.STATE_ON;
+        }
+
+        @VisibleForTesting
+        public boolean isAirplaneModeOn() {
+            return Settings.Global.getInt(mContext.getContentResolver(),
+                    Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
+        }
+
+        @VisibleForTesting
+        public void onAirplaneModeChanged(BluetoothManagerService managerService) {
+            managerService.onAirplaneModeChanged();
+        }
+
+        @VisibleForTesting
+        public int getSettingsInt(String name) {
+            return Settings.Global.getInt(mContext.getContentResolver(),
+                    name, 0);
+        }
+
+        @VisibleForTesting
+        public void setSettingsInt(String name, int value) {
+            Settings.Global.putInt(mContext.getContentResolver(),
+                    name, value);
+        }
+
+        @VisibleForTesting
+        public void showToastMessage() {
+            Resources r = mContext.getResources();
+            final CharSequence text = r.getString(
+                    R.string.bluetooth_airplane_mode_toast, 0);
+            Toast.makeText(mContext, text, Toast.LENGTH_LONG).show();
+        }
+
+        private boolean isA2dpConnected() {
+            final BluetoothA2dp a2dp = mA2dp;
+            if (a2dp == null) {
+                return false;
+            }
+            return a2dp.getConnectedDevices().size() > 0;
+        }
+
+        private boolean isHearingAidConnected() {
+            final BluetoothHearingAid hearingAid = mHearingAid;
+            if (hearingAid == null) {
+                return false;
+            }
+            return hearingAid.getConnectedDevices().size() > 0;
+        }
+    };
+}
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 470300e..3774b64 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -72,6 +72,7 @@
 import android.util.StatsLog;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DumpUtils;
 import com.android.server.pm.UserRestrictionsUtils;
 
@@ -143,7 +144,8 @@
     // Bluetooth persisted setting is on
     // but Airplane mode will affect Bluetooth state at start up
     // and Airplane mode will have higher priority.
-    private static final int BLUETOOTH_ON_AIRPLANE = 2;
+    @VisibleForTesting
+    static final int BLUETOOTH_ON_AIRPLANE = 2;
 
     private static final int SERVICE_IBLUETOOTH = 1;
     private static final int SERVICE_IBLUETOOTHGATT = 2;
@@ -164,6 +166,8 @@
     private boolean mBinding;
     private boolean mUnbinding;
 
+    private BluetoothAirplaneModeListener mBluetoothAirplaneModeListener;
+
     // used inside handler thread
     private boolean mQuietEnable = false;
     private boolean mEnable;
@@ -262,68 +266,65 @@
                 }
             };
 
-    private final ContentObserver mAirplaneModeObserver = new ContentObserver(null) {
-        @Override
-        public void onChange(boolean unused) {
-            synchronized (this) {
-                if (isBluetoothPersistedStateOn()) {
-                    if (isAirplaneModeOn()) {
-                        persistBluetoothSetting(BLUETOOTH_ON_AIRPLANE);
-                    } else {
-                        persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
-                    }
-                }
-
-                int st = BluetoothAdapter.STATE_OFF;
-                try {
-                    mBluetoothLock.readLock().lock();
-                    if (mBluetooth != null) {
-                        st = mBluetooth.getState();
-                    }
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Unable to call getState", e);
-                    return;
-                } finally {
-                    mBluetoothLock.readLock().unlock();
-                }
-
-                Slog.d(TAG,
-                        "Airplane Mode change - current state:  " + BluetoothAdapter.nameForState(
-                                st) + ", isAirplaneModeOn()=" + isAirplaneModeOn());
-
+    public void onAirplaneModeChanged() {
+        synchronized (this) {
+            if (isBluetoothPersistedStateOn()) {
                 if (isAirplaneModeOn()) {
-                    // Clear registered LE apps to force shut-off
-                    clearBleApps();
-
-                    // If state is BLE_ON make sure we trigger disableBLE
-                    if (st == BluetoothAdapter.STATE_BLE_ON) {
-                        try {
-                            mBluetoothLock.readLock().lock();
-                            if (mBluetooth != null) {
-                                addActiveLog(
-                                        BluetoothProtoEnums.ENABLE_DISABLE_REASON_AIRPLANE_MODE,
-                                        mContext.getPackageName(), false);
-                                mBluetooth.onBrEdrDown();
-                                mEnable = false;
-                                mEnableExternal = false;
-                            }
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "Unable to call onBrEdrDown", e);
-                        } finally {
-                            mBluetoothLock.readLock().unlock();
-                        }
-                    } else if (st == BluetoothAdapter.STATE_ON) {
-                        sendDisableMsg(BluetoothProtoEnums.ENABLE_DISABLE_REASON_AIRPLANE_MODE,
-                                mContext.getPackageName());
-                    }
-                } else if (mEnableExternal) {
-                    sendEnableMsg(mQuietEnableExternal,
-                            BluetoothProtoEnums.ENABLE_DISABLE_REASON_AIRPLANE_MODE,
-                            mContext.getPackageName());
+                    persistBluetoothSetting(BLUETOOTH_ON_AIRPLANE);
+                } else {
+                    persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
                 }
             }
+
+            int st = BluetoothAdapter.STATE_OFF;
+            try {
+                mBluetoothLock.readLock().lock();
+                if (mBluetooth != null) {
+                    st = mBluetooth.getState();
+                }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Unable to call getState", e);
+                return;
+            } finally {
+                mBluetoothLock.readLock().unlock();
+            }
+
+            Slog.d(TAG,
+                    "Airplane Mode change - current state:  " + BluetoothAdapter.nameForState(
+                            st) + ", isAirplaneModeOn()=" + isAirplaneModeOn());
+
+            if (isAirplaneModeOn()) {
+                // Clear registered LE apps to force shut-off
+                clearBleApps();
+
+                // If state is BLE_ON make sure we trigger disableBLE
+                if (st == BluetoothAdapter.STATE_BLE_ON) {
+                    try {
+                        mBluetoothLock.readLock().lock();
+                        if (mBluetooth != null) {
+                            addActiveLog(
+                                    BluetoothProtoEnums.ENABLE_DISABLE_REASON_AIRPLANE_MODE,
+                                    mContext.getPackageName(), false);
+                            mBluetooth.onBrEdrDown();
+                            mEnable = false;
+                            mEnableExternal = false;
+                        }
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Unable to call onBrEdrDown", e);
+                    } finally {
+                        mBluetoothLock.readLock().unlock();
+                    }
+                } else if (st == BluetoothAdapter.STATE_ON) {
+                    sendDisableMsg(BluetoothProtoEnums.ENABLE_DISABLE_REASON_AIRPLANE_MODE,
+                            mContext.getPackageName());
+                }
+            } else if (mEnableExternal) {
+                sendEnableMsg(mQuietEnableExternal,
+                        BluetoothProtoEnums.ENABLE_DISABLE_REASON_AIRPLANE_MODE,
+                        mContext.getPackageName());
+            }
         }
-    };
+    }
 
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
@@ -435,9 +436,8 @@
                 Settings.Global.getString(mContentResolver, Settings.Global.AIRPLANE_MODE_RADIOS);
         if (airplaneModeRadios == null || airplaneModeRadios.contains(
                 Settings.Global.RADIO_BLUETOOTH)) {
-            mContentResolver.registerContentObserver(
-                    Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
-                    mAirplaneModeObserver);
+            mBluetoothAirplaneModeListener = new BluetoothAirplaneModeListener(
+                    this, IoThread.get().getLooper(), context);
         }
 
         int systemUiUid = -1;
@@ -483,6 +483,17 @@
         return state != BLUETOOTH_OFF;
     }
 
+    private boolean isBluetoothPersistedStateOnAirplane() {
+        if (!supportBluetoothPersistedState()) {
+            return false;
+        }
+        int state = Settings.Global.getInt(mContentResolver, Settings.Global.BLUETOOTH_ON, -1);
+        if (DBG) {
+            Slog.d(TAG, "Bluetooth persisted state: " + state);
+        }
+        return state == BLUETOOTH_ON_AIRPLANE;
+    }
+
     /**
      *  Returns true if the Bluetooth saved state is BLUETOOTH_ON_BLUETOOTH
      */
@@ -988,10 +999,12 @@
         }
 
         synchronized (mReceiver) {
-            if (persist) {
-                persistBluetoothSetting(BLUETOOTH_OFF);
+            if (!isBluetoothPersistedStateOnAirplane()) {
+                if (persist) {
+                    persistBluetoothSetting(BLUETOOTH_OFF);
+                }
+                mEnableExternal = false;
             }
-            mEnableExternal = false;
             sendDisableMsg(BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST,
                     packageName);
         }
@@ -1219,6 +1232,10 @@
             Message getMsg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS);
             mHandler.sendMessage(getMsg);
         }
+        if (mBluetoothAirplaneModeListener != null) {
+            mBluetoothAirplaneModeListener.start(
+                    new BluetoothAirplaneModeListener.AirplaneModeHelper(mContext));
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index bd8a361..1c9f5dc 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -39,6 +39,7 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkPolicyManager.RULE_NONE;
 import static android.net.NetworkPolicyManager.uidRulesToString;
@@ -79,19 +80,18 @@
 import android.net.IpMemoryStore;
 import android.net.IpPrefix;
 import android.net.LinkProperties;
-import android.net.LinkProperties.CompareResult;
 import android.net.MatchAllNetworkSpecifier;
 import android.net.NattSocketKeepalive;
 import android.net.Network;
 import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
 import android.net.NetworkConfig;
-import android.net.NetworkFactory;
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
-import android.net.NetworkMisc;
 import android.net.NetworkMonitorManager;
 import android.net.NetworkPolicyManager;
+import android.net.NetworkProvider;
 import android.net.NetworkQuotaInfo;
 import android.net.NetworkRequest;
 import android.net.NetworkScore;
@@ -113,6 +113,7 @@
 import android.net.metrics.NetworkEvent;
 import android.net.netlink.InetDiagMessage;
 import android.net.shared.PrivateDnsConfig;
+import android.net.util.LinkPropertiesUtils.CompareResult;
 import android.net.util.MultinetworkPolicyTracker;
 import android.net.util.NetdService;
 import android.os.Binder;
@@ -219,6 +220,7 @@
 import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * @hide
@@ -362,10 +364,10 @@
     private static final int EVENT_PROXY_HAS_CHANGED = 16;
 
     /**
-     * used internally when registering NetworkFactories
-     * obj = NetworkFactoryInfo
+     * used internally when registering NetworkProviders
+     * obj = NetworkProviderInfo
      */
-    private static final int EVENT_REGISTER_NETWORK_FACTORY = 17;
+    private static final int EVENT_REGISTER_NETWORK_PROVIDER = 17;
 
     /**
      * used internally when registering NetworkAgents
@@ -401,10 +403,10 @@
     private static final int EVENT_RELEASE_NETWORK_REQUEST = 22;
 
     /**
-     * used internally when registering NetworkFactories
+     * used internally when registering NetworkProviders
      * obj = Messenger
      */
-    private static final int EVENT_UNREGISTER_NETWORK_FACTORY = 23;
+    private static final int EVENT_UNREGISTER_NETWORK_PROVIDER = 23;
 
     /**
      * used internally to expire a wakelock when transitioning
@@ -595,6 +597,10 @@
     // sequence number of NetworkRequests
     private int mNextNetworkRequestId = 1;
 
+    // Sequence number for NetworkProvider IDs.
+    private final AtomicInteger mNextNetworkProviderId = new AtomicInteger(
+            NetworkProvider.FIRST_PROVIDER_ID);
+
     // NetworkRequest activity String log entries.
     private static final int MAX_NETWORK_REQUEST_LOGS = 20;
     private final LocalLog mNetworkRequestInfoLogs = new LocalLog(MAX_NETWORK_REQUEST_LOGS);
@@ -1565,48 +1571,49 @@
         enforceAccessPermission();
         final int uid = Binder.getCallingUid();
         NetworkState state = getUnfilteredActiveNetworkState(uid);
-        return state.linkProperties;
+        if (state.linkProperties == null) return null;
+        return linkPropertiesRestrictedForCallerPermissions(state.linkProperties,
+                Binder.getCallingPid(), uid);
     }
 
     @Override
     public LinkProperties getLinkPropertiesForType(int networkType) {
         enforceAccessPermission();
         NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
-        if (nai != null) {
-            synchronized (nai) {
-                return new LinkProperties(nai.linkProperties);
-            }
-        }
-        return null;
+        final LinkProperties lp = getLinkProperties(nai);
+        if (lp == null) return null;
+        return linkPropertiesRestrictedForCallerPermissions(
+                lp, Binder.getCallingPid(), Binder.getCallingUid());
     }
 
     // TODO - this should be ALL networks
     @Override
     public LinkProperties getLinkProperties(Network network) {
         enforceAccessPermission();
-        return getLinkProperties(getNetworkAgentInfoForNetwork(network));
+        final LinkProperties lp = getLinkProperties(getNetworkAgentInfoForNetwork(network));
+        if (lp == null) return null;
+        return linkPropertiesRestrictedForCallerPermissions(
+                lp, Binder.getCallingPid(), Binder.getCallingUid());
     }
 
-    private LinkProperties getLinkProperties(NetworkAgentInfo nai) {
+    @Nullable
+    private LinkProperties getLinkProperties(@Nullable NetworkAgentInfo nai) {
         if (nai == null) {
             return null;
         }
         synchronized (nai) {
-            return new LinkProperties(nai.linkProperties);
+            return nai.linkProperties;
         }
     }
 
     private NetworkCapabilities getNetworkCapabilitiesInternal(NetworkAgentInfo nai) {
-        if (nai != null) {
-            synchronized (nai) {
-                if (nai.networkCapabilities != null) {
-                    return networkCapabilitiesRestrictedForCallerPermissions(
-                            nai.networkCapabilities,
-                            Binder.getCallingPid(), Binder.getCallingUid());
-                }
-            }
+        if (nai == null) return null;
+        synchronized (nai) {
+            if (nai.networkCapabilities == null) return null;
+            return networkCapabilitiesRestrictedForCallerPermissions(
+                    nai.networkCapabilities,
+                    Binder.getCallingPid(), Binder.getCallingUid());
         }
-        return null;
     }
 
     @Override
@@ -1628,6 +1635,29 @@
         return newNc;
     }
 
+    private LinkProperties linkPropertiesRestrictedForCallerPermissions(
+            LinkProperties lp, int callerPid, int callerUid) {
+        if (lp == null) return new LinkProperties();
+
+        // Only do a permission check if sanitization is needed, to avoid unnecessary binder calls.
+        final boolean needsSanitization =
+                (lp.getCaptivePortalApiUrl() != null || lp.getCaptivePortalData() != null);
+        if (!needsSanitization) {
+            return new LinkProperties(lp);
+        }
+
+        if (checkSettingsPermission(callerPid, callerUid)) {
+            return lp.makeSensitiveFieldsParcelingCopy();
+        }
+
+        final LinkProperties newLp = new LinkProperties(lp);
+        // Sensitive fields would not be parceled anyway, but sanitize for consistency before the
+        // object gets parceled.
+        newLp.setCaptivePortalApiUrl(null);
+        newLp.setCaptivePortalData(null);
+        return newLp;
+    }
+
     private void restrictRequestUidsForCaller(NetworkCapabilities nc) {
         if (!checkSettingsPermission()) {
             nc.setSingleUid(Binder.getCallingUid());
@@ -2388,9 +2418,9 @@
             return;
         }
 
-        pw.print("NetworkFactories for:");
-        for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
-            pw.print(" " + nfi.name);
+        pw.print("NetworkProviders for:");
+        for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
+            pw.print(" " + npi.name);
         }
         pw.println();
         pw.println();
@@ -2625,8 +2655,8 @@
                     if (nai.everConnected) {
                         loge("ERROR: cannot call explicitlySelected on already-connected network");
                     }
-                    nai.networkMisc.explicitlySelected = toBool(msg.arg1);
-                    nai.networkMisc.acceptUnvalidated = toBool(msg.arg1) && toBool(msg.arg2);
+                    nai.networkAgentConfig.explicitlySelected = toBool(msg.arg1);
+                    nai.networkAgentConfig.acceptUnvalidated = toBool(msg.arg1) && toBool(msg.arg2);
                     // Mark the network as temporarily accepting partial connectivity so that it
                     // will be validated (and possibly become default) even if it only provides
                     // partial internet access. Note that if user connects to partial connectivity
@@ -2634,7 +2664,7 @@
                     // out of wifi coverage) and if the same wifi is available again, the device
                     // will auto connect to this wifi even though the wifi has "no internet".
                     // TODO: Evaluate using a separate setting in IpMemoryStore.
-                    nai.networkMisc.acceptPartialConnectivity = toBool(msg.arg2);
+                    nai.networkAgentConfig.acceptPartialConnectivity = toBool(msg.arg2);
                     break;
                 }
                 case NetworkAgent.EVENT_SOCKET_KEEPALIVE: {
@@ -2666,10 +2696,10 @@
                         }
                         // Only show the notification when the private DNS is broken and the
                         // PRIVATE_DNS_BROKEN notification hasn't shown since last valid.
-                        if (privateDnsBroken && !nai.networkMisc.hasShownBroken) {
+                        if (privateDnsBroken && !nai.networkAgentConfig.hasShownBroken) {
                             showNetworkNotification(nai, NotificationType.PRIVATE_DNS_BROKEN);
                         }
-                        nai.networkMisc.hasShownBroken = privateDnsBroken;
+                        nai.networkAgentConfig.hasShownBroken = privateDnsBroken;
                     } else if (nai.networkCapabilities.isPrivateDnsBroken()) {
                         // If probePrivateDnsCompleted is false but nai.networkCapabilities says
                         // private DNS is broken, it means this network is being reevaluated.
@@ -2679,7 +2709,7 @@
                         nai.networkCapabilities.setPrivateDnsBroken(false);
                         final int oldScore = nai.getCurrentScore();
                         updateCapabilities(oldScore, nai, nai.networkCapabilities);
-                        nai.networkMisc.hasShownBroken = false;
+                        nai.networkAgentConfig.hasShownBroken = false;
                     }
                     break;
                 }
@@ -2721,7 +2751,7 @@
                         nai.lastValidated = valid;
                         nai.everValidated |= valid;
                         updateCapabilities(oldScore, nai, nai.networkCapabilities);
-                        // If score has changed, rebroadcast to NetworkFactories. b/17726566
+                        // If score has changed, rebroadcast to NetworkProviders. b/17726566
                         if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
                         if (valid) {
                             handleFreshlyValidatedNetwork(nai);
@@ -2738,7 +2768,7 @@
                             // If network becomes valid, the hasShownBroken should be reset for
                             // that network so that the notification will be fired when the private
                             // DNS is broken again.
-                            nai.networkMisc.hasShownBroken = false;
+                            nai.networkAgentConfig.hasShownBroken = false;
                         }
                     } else if (partialConnectivityChanged) {
                         updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities);
@@ -2797,9 +2827,10 @@
                             loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor");
                             break;
                         }
-                        if (!nai.networkMisc.provisioningNotificationDisabled) {
+                        if (!nai.networkAgentConfig.provisioningNotificationDisabled) {
                             mNotifier.showNotification(netId, NotificationType.SIGN_IN, nai, null,
-                                    (PendingIntent) msg.obj, nai.networkMisc.explicitlySelected);
+                                    (PendingIntent) msg.obj,
+                                    nai.networkAgentConfig.explicitlySelected);
                         }
                     }
                     break;
@@ -2836,25 +2867,11 @@
             return true;
         }
 
-        private boolean maybeHandleNetworkFactoryMessage(Message msg) {
-            switch (msg.what) {
-                default:
-                    return false;
-                case NetworkFactory.EVENT_UNFULFILLABLE_REQUEST: {
-                    handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.sendingUid,
-                            /* callOnUnavailable */ true);
-                    break;
-                }
-            }
-            return true;
-        }
-
         @Override
         public void handleMessage(Message msg) {
             if (!maybeHandleAsyncChannelMessage(msg)
                     && !maybeHandleNetworkMonitorMessage(msg)
-                    && !maybeHandleNetworkAgentInfoMessage(msg)
-                    && !maybeHandleNetworkFactoryMessage(msg)) {
+                    && !maybeHandleNetworkAgentInfoMessage(msg)) {
                 maybeHandleNetworkAgentMessage(msg);
             }
         }
@@ -3025,32 +3042,16 @@
     private void handleAsyncChannelHalfConnect(Message msg) {
         ensureRunningOnConnectivityServiceThread();
         final AsyncChannel ac = (AsyncChannel) msg.obj;
-        if (mNetworkFactoryInfos.containsKey(msg.replyTo)) {
+        if (mNetworkProviderInfos.containsKey(msg.replyTo)) {
             if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
                 if (VDBG) log("NetworkFactory connected");
                 // Finish setting up the full connection
-                mNetworkFactoryInfos.get(msg.replyTo).asyncChannel.sendMessage(
-                        AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
-                // A network factory has connected.  Send it all current NetworkRequests.
-                for (NetworkRequestInfo nri : mNetworkRequests.values()) {
-                    if (nri.request.isListen()) continue;
-                    ensureRunningOnConnectivityServiceThread();
-                    NetworkAgentInfo nai = nri.mSatisfier;
-                    final int score;
-                    final int serial;
-                    if (nai != null) {
-                        score = nai.getCurrentScore();
-                        serial = nai.factorySerialNumber;
-                    } else {
-                        score = 0;
-                        serial = NetworkFactory.SerialNumber.NONE;
-                    }
-                    ac.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score, serial,
-                            nri.request);
-                }
+                NetworkProviderInfo npi = mNetworkProviderInfos.get(msg.replyTo);
+                npi.completeConnection();
+                sendAllRequestsToProvider(npi);
             } else {
                 loge("Error connecting NetworkFactory");
-                mNetworkFactoryInfos.remove(msg.obj);
+                mNetworkProviderInfos.remove(msg.obj);
             }
         } else if (mNetworkAgentInfos.containsKey(msg.replyTo)) {
             if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
@@ -3082,8 +3083,8 @@
         if (nai != null) {
             disconnectAndDestroyNetwork(nai);
         } else {
-            NetworkFactoryInfo nfi = mNetworkFactoryInfos.remove(msg.replyTo);
-            if (DBG && nfi != null) log("unregisterNetworkFactory for " + nfi.name);
+            NetworkProviderInfo npi = mNetworkProviderInfos.remove(msg.replyTo);
+            if (DBG && npi != null) log("unregisterNetworkFactory for " + npi.name);
         }
     }
 
@@ -3166,7 +3167,7 @@
             // ip[6]tables to flush routes and remove the incoming packet mark rule, so do it
             // after we've rematched networks with requests which should make a potential
             // fallback network the default or requested a new network from the
-            // NetworkFactories, so network traffic isn't interrupted for an unnecessarily
+            // NetworkProviders, so network traffic isn't interrupted for an unnecessarily
             // long time.
             destroyNativeNetwork(nai);
             mDnsManager.removeNetwork(nai.network);
@@ -3179,8 +3180,8 @@
             // This should never fail.  Specifying an already in use NetID will cause failure.
             if (networkAgent.isVPN()) {
                 mNetd.networkCreateVpn(networkAgent.network.netId,
-                        (networkAgent.networkMisc == null
-                                || !networkAgent.networkMisc.allowBypass));
+                        (networkAgent.networkAgentConfig == null
+                                || !networkAgent.networkAgentConfig.allowBypass));
             } else {
                 mNetd.networkCreatePhysical(networkAgent.network.netId,
                         getNetworkPermission(networkAgent.networkCapabilities));
@@ -3429,9 +3430,8 @@
                 }
             }
 
-            for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
-                nfi.asyncChannel.sendMessage(android.net.NetworkFactory.CMD_CANCEL_REQUEST,
-                        nri.request);
+            for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
+                npi.cancelRequest(nri.request);
             }
         } else {
             // listens don't have a singular affectedNetwork.  Check all networks to see
@@ -3481,16 +3481,16 @@
             return;
         }
 
-        if (!nai.networkMisc.explicitlySelected) {
+        if (!nai.networkAgentConfig.explicitlySelected) {
             Slog.wtf(TAG, "BUG: setAcceptUnvalidated non non-explicitly selected network");
         }
 
-        if (accept != nai.networkMisc.acceptUnvalidated) {
-            nai.networkMisc.acceptUnvalidated = accept;
+        if (accept != nai.networkAgentConfig.acceptUnvalidated) {
+            nai.networkAgentConfig.acceptUnvalidated = accept;
             // If network becomes partial connectivity and user already accepted to use this
             // network, we should respect the user's option and don't need to popup the
             // PARTIAL_CONNECTIVITY notification to user again.
-            nai.networkMisc.acceptPartialConnectivity = accept;
+            nai.networkAgentConfig.acceptPartialConnectivity = accept;
             rematchAllNetworksAndRequests();
             sendUpdatedScoreToFactories(nai);
         }
@@ -3527,8 +3527,8 @@
             return;
         }
 
-        if (accept != nai.networkMisc.acceptPartialConnectivity) {
-            nai.networkMisc.acceptPartialConnectivity = accept;
+        if (accept != nai.networkAgentConfig.acceptPartialConnectivity) {
+            nai.networkAgentConfig.acceptPartialConnectivity = accept;
         }
 
         // TODO: Use the current design or save the user choice into IpMemoryStore.
@@ -3626,17 +3626,32 @@
                 enforceSettingsPermission();
             }
 
-            // getNetworkAgentInfoForNetwork is thread-safe
-            final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(mNetwork);
-            if (nai == null) return;
-
-            // nai.networkMonitor() is thread-safe
-            final NetworkMonitorManager nm = nai.networkMonitor();
+            final NetworkMonitorManager nm = getNetworkMonitorManager(mNetwork);
             if (nm == null) return;
             nm.notifyCaptivePortalAppFinished(response);
         }
 
         @Override
+        public void appRequest(final int request) {
+            final NetworkMonitorManager nm = getNetworkMonitorManager(mNetwork);
+            if (nm == null) return;
+
+            if (request == CaptivePortal.APP_REQUEST_REEVALUATION_REQUIRED) {
+                nm.forceReevaluation(Binder.getCallingUid());
+            }
+        }
+
+        @Nullable
+        private NetworkMonitorManager getNetworkMonitorManager(final Network network) {
+            // getNetworkAgentInfoForNetwork is thread-safe
+            final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+            if (nai == null) return null;
+
+            // nai.networkMonitor() is thread-safe
+            return nai.networkMonitor();
+        }
+
+        @Override
         public void logEvent(int eventId, String packageName) {
             enforceSettingsPermission();
 
@@ -3738,7 +3753,7 @@
                 action = ConnectivityManager.ACTION_PROMPT_PARTIAL_CONNECTIVITY;
                 // Don't bother the user with a high-priority notification if the network was not
                 // explicitly selected by the user.
-                highPriority = nai.networkMisc.explicitlySelected;
+                highPriority = nai.networkAgentConfig.explicitlySelected;
                 break;
             default:
                 Slog.wtf(TAG, "Unknown notification type " + type);
@@ -3771,14 +3786,15 @@
         // automatically connects to a network that has partial Internet access, the user will
         // always be able to use it, either because they've already chosen "don't ask again" or
         // because we have prompt them.
-        if (nai.partialConnectivity && !nai.networkMisc.acceptPartialConnectivity) {
+        if (nai.partialConnectivity && !nai.networkAgentConfig.acceptPartialConnectivity) {
             return true;
         }
 
         // If a network has no Internet access, only prompt if the network was explicitly selected
         // and if the user has not already told us to use the network regardless of whether it
         // validated or not.
-        if (nai.networkMisc.explicitlySelected && !nai.networkMisc.acceptUnvalidated) {
+        if (nai.networkAgentConfig.explicitlySelected
+                && !nai.networkAgentConfig.acceptUnvalidated) {
             return true;
         }
 
@@ -3866,12 +3882,12 @@
                     handleApplyDefaultProxy((ProxyInfo)msg.obj);
                     break;
                 }
-                case EVENT_REGISTER_NETWORK_FACTORY: {
-                    handleRegisterNetworkFactory((NetworkFactoryInfo)msg.obj);
+                case EVENT_REGISTER_NETWORK_PROVIDER: {
+                    handleRegisterNetworkProvider((NetworkProviderInfo) msg.obj);
                     break;
                 }
-                case EVENT_UNREGISTER_NETWORK_FACTORY: {
-                    handleUnregisterNetworkFactory((Messenger)msg.obj);
+                case EVENT_UNREGISTER_NETWORK_PROVIDER: {
+                    handleUnregisterNetworkProvider((Messenger) msg.obj);
                     break;
                 }
                 case EVENT_REGISTER_NETWORK_AGENT: {
@@ -4916,7 +4932,7 @@
         }
     };
 
-    private final HashMap<Messenger, NetworkFactoryInfo> mNetworkFactoryInfos = new HashMap<>();
+    private final HashMap<Messenger, NetworkProviderInfo> mNetworkProviderInfos = new HashMap<>();
     private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests = new HashMap<>();
 
     private static final int MAX_NETWORK_REQUESTS_PER_UID = 100;
@@ -4924,18 +4940,73 @@
     @GuardedBy("mUidToNetworkRequestCount")
     private final SparseIntArray mUidToNetworkRequestCount = new SparseIntArray();
 
-    private static class NetworkFactoryInfo {
+    private static class NetworkProviderInfo {
         public final String name;
         public final Messenger messenger;
-        public final AsyncChannel asyncChannel;
-        public final int factorySerialNumber;
+        private final AsyncChannel mAsyncChannel;
+        private final IBinder.DeathRecipient mDeathRecipient;
+        public final int providerId;
 
-        NetworkFactoryInfo(String name, Messenger messenger, AsyncChannel asyncChannel,
-                int factorySerialNumber) {
+        NetworkProviderInfo(String name, Messenger messenger, AsyncChannel asyncChannel,
+                int providerId, IBinder.DeathRecipient deathRecipient) {
             this.name = name;
             this.messenger = messenger;
-            this.asyncChannel = asyncChannel;
-            this.factorySerialNumber = factorySerialNumber;
+            this.providerId = providerId;
+            mAsyncChannel = asyncChannel;
+            mDeathRecipient = deathRecipient;
+
+            if ((mAsyncChannel == null) == (mDeathRecipient == null)) {
+                throw new AssertionError("Must pass exactly one of asyncChannel or deathRecipient");
+            }
+        }
+
+        boolean isLegacyNetworkFactory() {
+            return mAsyncChannel != null;
+        }
+
+        void sendMessageToNetworkProvider(int what, int arg1, int arg2, Object obj) {
+            try {
+                messenger.send(Message.obtain(null /* handler */, what, arg1, arg2, obj));
+            } catch (RemoteException e) {
+                // Remote process died. Ignore; the death recipient will remove this
+                // NetworkProviderInfo from mNetworkProviderInfos.
+            }
+        }
+
+        void requestNetwork(NetworkRequest request, int score, int servingProviderId) {
+            if (isLegacyNetworkFactory()) {
+                mAsyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score,
+                        servingProviderId, request);
+            } else {
+                sendMessageToNetworkProvider(NetworkProvider.CMD_REQUEST_NETWORK, score,
+                            servingProviderId, request);
+            }
+        }
+
+        void cancelRequest(NetworkRequest request) {
+            if (isLegacyNetworkFactory()) {
+                mAsyncChannel.sendMessage(android.net.NetworkFactory.CMD_CANCEL_REQUEST, request);
+            } else {
+                sendMessageToNetworkProvider(NetworkProvider.CMD_CANCEL_REQUEST, 0, 0, request);
+            }
+        }
+
+        void connect(Context context, Handler handler) {
+            if (isLegacyNetworkFactory()) {
+                mAsyncChannel.connect(context, handler, messenger);
+            } else {
+                try {
+                    messenger.getBinder().linkToDeath(mDeathRecipient, 0);
+                } catch (RemoteException e) {
+                    mDeathRecipient.binderDied();
+                }
+            }
+        }
+
+        void completeConnection() {
+            if (isLegacyNetworkFactory()) {
+                mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
+            }
         }
     }
 
@@ -5306,6 +5377,11 @@
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri));
     }
 
+    /** Returns the next Network provider ID. */
+    public final int nextNetworkProviderId() {
+        return mNextNetworkProviderId.getAndIncrement();
+    }
+
     @Override
     public void releaseNetworkRequest(NetworkRequest networkRequest) {
         ensureNetworkRequestHasType(networkRequest);
@@ -5316,31 +5392,65 @@
     @Override
     public int registerNetworkFactory(Messenger messenger, String name) {
         enforceNetworkFactoryPermission();
-        NetworkFactoryInfo nfi = new NetworkFactoryInfo(name, messenger, new AsyncChannel(),
-                NetworkFactory.SerialNumber.nextSerialNumber());
-        mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_FACTORY, nfi));
-        return nfi.factorySerialNumber;
+        NetworkProviderInfo npi = new NetworkProviderInfo(name, messenger, new AsyncChannel(),
+                nextNetworkProviderId(), null /* deathRecipient */);
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_PROVIDER, npi));
+        return npi.providerId;
     }
 
-    private void handleRegisterNetworkFactory(NetworkFactoryInfo nfi) {
-        if (DBG) log("Got NetworkFactory Messenger for " + nfi.name);
-        mNetworkFactoryInfos.put(nfi.messenger, nfi);
-        nfi.asyncChannel.connect(mContext, mTrackerHandler, nfi.messenger);
+    private void handleRegisterNetworkProvider(NetworkProviderInfo npi) {
+        if (mNetworkProviderInfos.containsKey(npi.messenger)) {
+            // Avoid creating duplicates. even if an app makes a direct AIDL call.
+            // This will never happen if an app calls ConnectivityManager#registerNetworkProvider,
+            // as that will throw if a duplicate provider is registered.
+            Slog.e(TAG, "Attempt to register existing NetworkProviderInfo "
+                    + mNetworkProviderInfos.get(npi.messenger).name);
+            return;
+        }
+
+        if (DBG) log("Got NetworkProvider Messenger for " + npi.name);
+        mNetworkProviderInfos.put(npi.messenger, npi);
+        npi.connect(mContext, mTrackerHandler);
+        if (!npi.isLegacyNetworkFactory()) {
+            // Legacy NetworkFactories get their requests when their AsyncChannel connects.
+            sendAllRequestsToProvider(npi);
+        }
+    }
+
+    @Override
+    public int registerNetworkProvider(Messenger messenger, String name) {
+        enforceNetworkFactoryPermission();
+        NetworkProviderInfo npi = new NetworkProviderInfo(name, messenger,
+                null /* asyncChannel */, nextNetworkProviderId(),
+                () -> unregisterNetworkProvider(messenger));
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_PROVIDER, npi));
+        return npi.providerId;
+    }
+
+    @Override
+    public void unregisterNetworkProvider(Messenger messenger) {
+        enforceNetworkFactoryPermission();
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_PROVIDER, messenger));
     }
 
     @Override
     public void unregisterNetworkFactory(Messenger messenger) {
-        enforceNetworkFactoryPermission();
-        mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_FACTORY, messenger));
+        unregisterNetworkProvider(messenger);
     }
 
-    private void handleUnregisterNetworkFactory(Messenger messenger) {
-        NetworkFactoryInfo nfi = mNetworkFactoryInfos.remove(messenger);
-        if (nfi == null) {
-            loge("Failed to find Messenger in unregisterNetworkFactory");
+    private void handleUnregisterNetworkProvider(Messenger messenger) {
+        NetworkProviderInfo npi = mNetworkProviderInfos.remove(messenger);
+        if (npi == null) {
+            loge("Failed to find Messenger in unregisterNetworkProvider");
             return;
         }
-        if (DBG) log("unregisterNetworkFactory for " + nfi.name);
+        if (DBG) log("unregisterNetworkProvider for " + npi.name);
+    }
+
+    @Override
+    public void declareNetworkRequestUnfulfillable(NetworkRequest request) {
+        enforceNetworkFactoryPermission();
+        mHandler.post(() -> handleReleaseNetworkRequest(request, Binder.getCallingUid(), true));
     }
 
     // NOTE: Accessed on multiple threads, must be synchronized on itself.
@@ -5404,11 +5514,14 @@
     // changes that would conflict throughout the automerger graph. Having this method temporarily
     // helps with the process of going through with all these dependent changes across the entire
     // tree.
-    public int registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,
+    /**
+     * Register a new agent. {@see #registerNetworkAgent} below.
+     */
+    public Network registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,
             LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
-            int currentScore, NetworkMisc networkMisc) {
+            int currentScore, NetworkAgentConfig networkAgentConfig) {
         return registerNetworkAgent(messenger, networkInfo, linkProperties, networkCapabilities,
-                currentScore, networkMisc, NetworkFactory.SerialNumber.NONE);
+                currentScore, networkAgentConfig, NetworkProvider.ID_NONE);
     }
 
     /**
@@ -5423,12 +5536,13 @@
      *         later : see {@link #updateCapabilities}.
      * @param currentScore the initial score of the network. See
      *         {@link NetworkAgentInfo#getCurrentScore}.
-     * @param networkMisc metadata about the network. This is never updated.
-     * @param factorySerialNumber the serial number of the factory owning this NetworkAgent.
+     * @param networkAgentConfig metadata about the network. This is never updated.
+     * @param providerId the ID of the provider owning this NetworkAgent.
+     * @return the network created for this agent.
      */
-    public int registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,
+    public Network registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,
             LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
-            int currentScore, NetworkMisc networkMisc, int factorySerialNumber) {
+            int currentScore, NetworkAgentConfig networkAgentConfig, int providerId) {
         enforceNetworkFactoryPermission();
 
         LinkProperties lp = new LinkProperties(linkProperties);
@@ -5440,8 +5554,8 @@
         ns.putIntExtension(NetworkScore.LEGACY_SCORE, currentScore);
         final NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(),
                 new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo), lp, nc,
-                ns, mContext, mTrackerHandler, new NetworkMisc(networkMisc), this, mNetd,
-                mDnsResolver, mNMS, factorySerialNumber);
+                ns, mContext, mTrackerHandler, new NetworkAgentConfig(networkAgentConfig), this,
+                mNetd, mDnsResolver, mNMS, providerId);
         // Make sure the network capabilities reflect what the agent info says.
         nai.getAndSetNetworkCapabilities(mixInCapabilities(nai, nc));
         final String extraInfo = networkInfo.getExtraInfo();
@@ -5459,7 +5573,7 @@
         // If the network disconnects or sends any other event before that, messages are deferred by
         // NetworkAgent until nai.asyncChannel.connect(), which will be called when finalizing the
         // registration.
-        return nai.network.netId;
+        return nai.network;
     }
 
     private void handleRegisterNetworkAgent(NetworkAgentInfo nai, INetworkMonitor networkMonitor) {
@@ -5711,6 +5825,19 @@
         return INetd.PERMISSION_NONE;
     }
 
+    private void updateNetworkPermissions(@NonNull final NetworkAgentInfo nai,
+            @NonNull final NetworkCapabilities newNc) {
+        final int oldPermission = getNetworkPermission(nai.networkCapabilities);
+        final int newPermission = getNetworkPermission(newNc);
+        if (oldPermission != newPermission && nai.created && !nai.isVPN()) {
+            try {
+                mNMS.setNetworkPermission(nai.network.netId, newPermission);
+            } catch (RemoteException e) {
+                loge("Exception in setNetworkPermission: " + e);
+            }
+        }
+    }
+
     /**
      * Augments the NetworkCapabilities passed in by a NetworkAgent with capabilities that are
      * maintained here that the NetworkAgent is not aware of (e.g., validated, captive portal,
@@ -5720,7 +5847,7 @@
         // Once a NetworkAgent is connected, complain if some immutable capabilities are removed.
          // Don't complain for VPNs since they're not driven by requests and there is no risk of
          // causing a connect/teardown loop.
-         // TODO: remove this altogether and make it the responsibility of the NetworkFactories to
+         // TODO: remove this altogether and make it the responsibility of the NetworkProviders to
          // avoid connect/teardown loops.
         if (nai.everConnected &&
                 !nai.isVPN() &&
@@ -5751,11 +5878,6 @@
         } else {
             newNc.addCapability(NET_CAPABILITY_FOREGROUND);
         }
-        if (nai.isSuspended()) {
-            newNc.removeCapability(NET_CAPABILITY_NOT_SUSPENDED);
-        } else {
-            newNc.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
-        }
         if (nai.partialConnectivity) {
             newNc.addCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY);
         } else {
@@ -5763,6 +5885,12 @@
         }
         newNc.setPrivateDnsBroken(nai.networkCapabilities.isPrivateDnsBroken());
 
+        // TODO : remove this once all factories are updated to send NOT_SUSPENDED and NOT_ROAMING
+        if (!newNc.hasTransport(TRANSPORT_CELLULAR)) {
+            newNc.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
+            newNc.addCapability(NET_CAPABILITY_NOT_ROAMING);
+        }
+
         return newNc;
     }
 
@@ -5782,21 +5910,11 @@
      * @param nai the network having its capabilities updated.
      * @param nc the new network capabilities.
      */
-    private void updateCapabilities(int oldScore, NetworkAgentInfo nai, NetworkCapabilities nc) {
+    private void updateCapabilities(final int oldScore, @NonNull final NetworkAgentInfo nai,
+            @NonNull final NetworkCapabilities nc) {
         NetworkCapabilities newNc = mixInCapabilities(nai, nc);
-
         if (Objects.equals(nai.networkCapabilities, newNc)) return;
-
-        final int oldPermission = getNetworkPermission(nai.networkCapabilities);
-        final int newPermission = getNetworkPermission(newNc);
-        if (oldPermission != newPermission && nai.created && !nai.isVPN()) {
-            try {
-                mNMS.setNetworkPermission(nai.network.netId, newPermission);
-            } catch (RemoteException e) {
-                loge("Exception in setNetworkPermission: " + e);
-            }
-        }
-
+        updateNetworkPermissions(nai, newNc);
         final NetworkCapabilities prevNc = nai.getAndSetNetworkCapabilities(newNc);
 
         updateUids(nai, prevNc, newNc);
@@ -5807,6 +5925,19 @@
             // on this network. We might have been called by rematchNetworkAndRequests when a
             // network changed foreground state.
             processListenRequests(nai);
+            final boolean prevSuspended = !prevNc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED);
+            final boolean suspended = !newNc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED);
+            final boolean prevRoaming = !prevNc.hasCapability(NET_CAPABILITY_NOT_ROAMING);
+            final boolean roaming = !newNc.hasCapability(NET_CAPABILITY_NOT_ROAMING);
+            if (prevSuspended != suspended || prevRoaming != roaming) {
+                // TODO (b/73132094) : remove this call once the few users of onSuspended and
+                // onResumed have been removed.
+                notifyNetworkCallbacks(nai, suspended ? ConnectivityManager.CALLBACK_SUSPENDED
+                        : ConnectivityManager.CALLBACK_RESUMED);
+                // updateNetworkInfo will mix in the suspended info from the capabilities and
+                // take appropriate action for the network having possibly changed state.
+                updateNetworkInfo(nai, nai.networkInfo);
+            }
         } else {
             // If the requestable capabilities have changed or the score changed, we can't have been
             // called by rematchNetworkAndRequests, so it's safe to start a rematch.
@@ -5814,6 +5945,9 @@
             notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
         }
 
+        // TODO : static analysis indicates that prevNc can't be null here (getAndSetNetworkCaps
+        // never returns null), so mark the relevant members and functions in nai as @NonNull and
+        // remove this test
         if (prevNc != null) {
             final boolean oldMetered = prevNc.isMetered();
             final boolean newMetered = newNc.isMetered();
@@ -5862,7 +5996,7 @@
             LinkProperties lp) {
         if (nc == null || lp == null) return false;
         return nai.isVPN()
-                && !nai.networkMisc.allowBypass
+                && !nai.networkAgentConfig.allowBypass
                 && nc.getEstablishingVpnAppUid() != Process.SYSTEM_UID
                 && lp.getInterfaceName() != null
                 && (lp.hasIPv4DefaultRoute() || lp.hasIPv6DefaultRoute());
@@ -5960,9 +6094,27 @@
         if (VDBG || DDBG){
             log("sending new Min Network Score(" + score + "): " + networkRequest.toString());
         }
-        for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
-            nfi.asyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score,
-                    serial, networkRequest);
+        for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
+            npi.requestNetwork(networkRequest, score, serial);
+        }
+    }
+
+    /** Sends all current NetworkRequests to the specified factory. */
+    private void sendAllRequestsToProvider(NetworkProviderInfo npi) {
+        ensureRunningOnConnectivityServiceThread();
+        for (NetworkRequestInfo nri : mNetworkRequests.values()) {
+            if (nri.request.isListen()) continue;
+            NetworkAgentInfo nai = nri.mSatisfier;
+            final int score;
+            final int serial;
+            if (nai != null) {
+                score = nai.getCurrentScore();
+                serial = nai.factorySerialNumber;
+            } else {
+                score = 0;
+                serial = NetworkProvider.ID_NONE;
+            }
+            npi.requestNetwork(nri.request, score, serial);
         }
     }
 
@@ -6017,7 +6169,8 @@
             case ConnectivityManager.CALLBACK_AVAILABLE: {
                 putParcelable(bundle, networkCapabilitiesRestrictedForCallerPermissions(
                         networkAgent.networkCapabilities, nri.mPid, nri.mUid));
-                putParcelable(bundle, new LinkProperties(networkAgent.linkProperties));
+                putParcelable(bundle, linkPropertiesRestrictedForCallerPermissions(
+                        networkAgent.linkProperties, nri.mPid, nri.mUid));
                 // For this notification, arg1 contains the blocked status.
                 msg.arg1 = arg1;
                 break;
@@ -6034,7 +6187,8 @@
                 break;
             }
             case ConnectivityManager.CALLBACK_IP_CHANGED: {
-                putParcelable(bundle, new LinkProperties(networkAgent.linkProperties));
+                putParcelable(bundle, linkPropertiesRestrictedForCallerPermissions(
+                        networkAgent.linkProperties, nri.mPid, nri.mUid));
                 break;
             }
             case ConnectivityManager.CALLBACK_BLK_CHANGED: {
@@ -6143,6 +6297,30 @@
         }
     }
 
+    // An accumulator class to gather the list of changes that result from a rematch.
+    // TODO : enrich to represent an entire set of changes to apply.
+    private static class NetworkReassignment {
+        static class NetworkBgStatePair {
+            @NonNull final NetworkAgentInfo mNetwork;
+            final boolean mOldBackground;
+            NetworkBgStatePair(@NonNull final NetworkAgentInfo network,
+                    final boolean oldBackground) {
+                mNetwork = network;
+                mOldBackground = oldBackground;
+            }
+        }
+
+        @NonNull private final Set<NetworkBgStatePair> mRematchedNetworks = new ArraySet<>();
+
+        @NonNull Iterable<NetworkBgStatePair> getRematchedNetworks() {
+            return mRematchedNetworks;
+        }
+
+        void addRematchedNetwork(@NonNull final NetworkBgStatePair network) {
+            mRematchedNetworks.add(network);
+        }
+    }
+
     private ArrayMap<NetworkRequestInfo, NetworkAgentInfo> computeRequestReassignmentForNetwork(
             @NonNull final NetworkAgentInfo newNetwork) {
         final int score = newNetwork.getCurrentScore();
@@ -6188,8 +6366,8 @@
     //   needed. A network is needed if it is the best network for
     //   one or more NetworkRequests, or if it is a VPN.
     //
-    // - Tears down newNetwork if it just became validated
-    //   but turns out to be unneeded.
+    // - Writes into the passed reassignment object all changes that should be done for
+    //   rematching this network with all requests, to be applied later.
     //
     // NOTE: This function only adds NetworkRequests that "newNetwork" could satisfy,
     // it does not remove NetworkRequests that other Networks could better satisfy.
@@ -6197,15 +6375,22 @@
     // This function should be used when possible instead of {@code rematchAllNetworksAndRequests}
     // as it performs better by a factor of the number of Networks.
     //
+    // TODO : stop writing to the passed reassignment. This is temporarily more useful, but
+    // it's unidiomatic Java and it's hard to read.
+    //
+    // @param changes a currently-building list of changes to write to
     // @param newNetwork is the network to be matched against NetworkRequests.
     // @param now the time the rematch starts, as returned by SystemClock.elapsedRealtime();
-    private void rematchNetworkAndRequests(NetworkAgentInfo newNetwork, long now) {
+    private void rematchNetworkAndRequests(@NonNull final NetworkReassignment changes,
+            @NonNull final NetworkAgentInfo newNetwork, final long now) {
         ensureRunningOnConnectivityServiceThread();
         if (!newNetwork.everConnected) return;
         boolean isNewDefault = false;
         NetworkAgentInfo oldDefaultNetwork = null;
 
-        final boolean wasBackgroundNetwork = newNetwork.isBackgroundNetwork();
+        changes.addRematchedNetwork(new NetworkReassignment.NetworkBgStatePair(newNetwork,
+                newNetwork.isBackgroundNetwork()));
+
         final int score = newNetwork.getCurrentScore();
 
         if (VDBG || DDBG) log("rematching " + newNetwork.name());
@@ -6243,11 +6428,11 @@
                     Slog.wtf(TAG, "BUG: " + newSatisfier.name() + " already has " + nri.request);
                 }
                 addedRequests.add(nri);
-                // Tell NetworkFactories about the new score, so they can stop
+                // Tell NetworkProviders about the new score, so they can stop
                 // trying to connect if they know they cannot match it.
                 // TODO - this could get expensive if we have a lot of requests for this
                 // network.  Think about if there is a way to reduce this.  Push
-                // netid->request mapping to each factory?
+                // netid->request mapping to each provider?
                 sendUpdatedScoreToFactories(nri.request, newSatisfier);
                 if (isDefaultRequest(nri)) {
                     isNewDefault = true;
@@ -6276,7 +6461,7 @@
                 } else {
                     Slog.wtf(TAG, "BUG: Removing request " + nri.request.requestId + " from " +
                             newNetwork.name() +
-                            " without updating mSatisfier or factories!");
+                            " without updating mSatisfier or providers!");
                 }
                 // TODO: Technically, sending CALLBACK_LOST here is
                 // incorrect if there is a replacement network currently
@@ -6308,39 +6493,12 @@
         if (newNetwork.getCurrentScore() != score) {
             Slog.wtf(TAG, String.format(
                     "BUG: %s changed score during rematch: %d -> %d",
-                   newNetwork.name(), score, newNetwork.getCurrentScore()));
+                    newNetwork.name(), score, newNetwork.getCurrentScore()));
         }
 
         // Notify requested networks are available after the default net is switched, but
         // before LegacyTypeTracker sends legacy broadcasts
         for (NetworkRequestInfo nri : addedRequests) notifyNetworkAvailable(newNetwork, nri);
-
-        // Finally, process listen requests and update capabilities if the background state has
-        // changed for this network. For consistency with previous behavior, send onLost callbacks
-        // before onAvailable.
-        processNewlyLostListenRequests(newNetwork);
-
-        // Maybe the network changed background states. Update its capabilities.
-        final boolean backgroundChanged = wasBackgroundNetwork != newNetwork.isBackgroundNetwork();
-        if (backgroundChanged) {
-            final NetworkCapabilities newNc = mixInCapabilities(newNetwork,
-                    newNetwork.networkCapabilities);
-
-            final int oldPermission = getNetworkPermission(newNetwork.networkCapabilities);
-            final int newPermission = getNetworkPermission(newNc);
-            if (oldPermission != newPermission) {
-                try {
-                    mNMS.setNetworkPermission(newNetwork.network.netId, newPermission);
-                } catch (RemoteException e) {
-                    loge("Exception in setNetworkPermission: " + e);
-                }
-            }
-
-            newNetwork.getAndSetNetworkCapabilities(newNc);
-            notifyNetworkCallbacks(newNetwork, ConnectivityManager.CALLBACK_CAP_CHANGED);
-        }
-
-        processNewlySatisfiedListenRequests(newNetwork);
     }
 
     /**
@@ -6362,12 +6520,24 @@
         // scoring network and then a higher scoring network, which could produce multiple
         // callbacks.
         Arrays.sort(nais);
+        final NetworkReassignment changes = new NetworkReassignment();
         for (final NetworkAgentInfo nai : nais) {
-            rematchNetworkAndRequests(nai, now);
+            rematchNetworkAndRequests(changes, nai, now);
         }
 
         final NetworkAgentInfo newDefaultNetwork = getDefaultNetwork();
 
+        for (final NetworkReassignment.NetworkBgStatePair event : changes.getRematchedNetworks()) {
+            // Process listen requests and update capabilities if the background state has
+            // changed for this network. For consistency with previous behavior, send onLost
+            // callbacks before onAvailable.
+            processNewlyLostListenRequests(event.mNetwork);
+            if (event.mOldBackground != event.mNetwork.isBackgroundNetwork()) {
+                applyBackgroundChangeForRematch(event.mNetwork);
+            }
+            processNewlySatisfiedListenRequests(event.mNetwork);
+        }
+
         for (final NetworkAgentInfo nai : nais) {
             // Rematching may have altered the linger state of some networks, so update all linger
             // timers. updateLingerState reads the state from the network agent and does nothing
@@ -6399,6 +6569,24 @@
         }
     }
 
+    /**
+     * Apply a change in background state resulting from rematching networks with requests.
+     *
+     * During rematch, a network may change background states by starting to satisfy or stopping
+     * to satisfy a foreground request. Listens don't count for this. When a network changes
+     * background states, its capabilities need to be updated and callbacks fired for the
+     * capability change.
+     *
+     * @param nai The network that changed background states
+     */
+    private void applyBackgroundChangeForRematch(@NonNull final NetworkAgentInfo nai) {
+        final NetworkCapabilities newNc = mixInCapabilities(nai, nai.networkCapabilities);
+        if (Objects.equals(nai.networkCapabilities, newNc)) return;
+        updateNetworkPermissions(nai, newNc);
+        nai.getAndSetNetworkCapabilities(newNc);
+        notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
+    }
+
     private void updateLegacyTypeTrackerAndVpnLockdownForRematch(
             @Nullable final NetworkAgentInfo oldDefaultNetwork,
             @Nullable final NetworkAgentInfo newDefaultNetwork,
@@ -6490,10 +6678,31 @@
         }
     }
 
-    private void updateNetworkInfo(NetworkAgentInfo networkAgent, NetworkInfo newInfo) {
+    @NonNull
+    private NetworkInfo mixInInfo(@NonNull final NetworkAgentInfo nai, @NonNull NetworkInfo info) {
+        final NetworkInfo newInfo = new NetworkInfo(info);
+        // The suspended and roaming bits are managed in NetworkCapabilities.
+        final boolean suspended =
+                !nai.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_SUSPENDED);
+        if (suspended && info.getDetailedState() == NetworkInfo.DetailedState.CONNECTED) {
+            // Only override the state with SUSPENDED if the network is currently in CONNECTED
+            // state. This is because the network could have been suspended before connecting,
+            // or it could be disconnecting while being suspended, and in both these cases
+            // the state should not be overridden. Note that the only detailed state that
+            // maps to State.CONNECTED is DetailedState.CONNECTED, so there is also no need to
+            // worry about multiple different substates of CONNECTED.
+            newInfo.setDetailedState(NetworkInfo.DetailedState.SUSPENDED, info.getReason(),
+                    info.getExtraInfo());
+        }
+        newInfo.setRoaming(!nai.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_ROAMING));
+        return newInfo;
+    }
+
+    private void updateNetworkInfo(NetworkAgentInfo networkAgent, NetworkInfo info) {
+        final NetworkInfo newInfo = mixInInfo(networkAgent, info);
+
         final NetworkInfo.State state = newInfo.getState();
         NetworkInfo oldInfo = null;
-        final int oldScore = networkAgent.getCurrentScore();
         synchronized (networkAgent) {
             oldInfo = networkAgent.networkInfo;
             networkAgent.networkInfo = newInfo;
@@ -6535,7 +6744,7 @@
             // command must be sent after updating LinkProperties to maximize chances of
             // NetworkMonitor seeing the correct LinkProperties when starting.
             // TODO: pass LinkProperties to the NetworkMonitor in the notifyNetworkConnected call.
-            if (networkAgent.networkMisc.acceptPartialConnectivity) {
+            if (networkAgent.networkAgentConfig.acceptPartialConnectivity) {
                 networkAgent.networkMonitor().setAcceptPartialConnectivity();
             }
             networkAgent.networkMonitor().notifyNetworkConnected(
@@ -6575,17 +6784,6 @@
             }
         } else if (networkAgent.created && (oldInfo.getState() == NetworkInfo.State.SUSPENDED ||
                 state == NetworkInfo.State.SUSPENDED)) {
-            // going into or coming out of SUSPEND: re-score and notify
-            if (networkAgent.getCurrentScore() != oldScore) {
-                rematchAllNetworksAndRequests();
-            }
-            updateCapabilities(networkAgent.getCurrentScore(), networkAgent,
-                    networkAgent.networkCapabilities);
-            // TODO (b/73132094) : remove this call once the few users of onSuspended and
-            // onResumed have been removed.
-            notifyNetworkCallbacks(networkAgent, (state == NetworkInfo.State.SUSPENDED ?
-                    ConnectivityManager.CALLBACK_SUSPENDED :
-                    ConnectivityManager.CALLBACK_RESUMED));
             mLegacyTypeTracker.update(networkAgent);
         }
     }
diff --git a/services/core/java/com/android/server/DynamicSystemService.java b/services/core/java/com/android/server/DynamicSystemService.java
index 7909e30..c60460f 100644
--- a/services/core/java/com/android/server/DynamicSystemService.java
+++ b/services/core/java/com/android/server/DynamicSystemService.java
@@ -44,9 +44,9 @@
     private static final String TAG = "DynamicSystemService";
     private static final String NO_SERVICE_ERROR = "no gsiservice";
     private static final int GSID_ROUGH_TIMEOUT_MS = 8192;
-    private static final String PATH_DEFAULT = "/data/gsi";
+    private static final String PATH_DEFAULT = "/data/gsi/";
     private Context mContext;
-    private String mInstallPath;
+    private String mInstallPath, mDsuSlot;
     private volatile IGsiService mGsiService;
 
     DynamicSystemService(Context context) {
@@ -115,7 +115,7 @@
     }
 
     @Override
-    public boolean startInstallation() throws RemoteException {
+    public boolean startInstallation(String dsuSlot) throws RemoteException {
         IGsiService service = getGsiService();
         // priority from high to low: sysprop -> sdcard -> /data
         String path = SystemProperties.get("os.aot.path");
@@ -129,16 +129,17 @@
                 if (!Environment.MEDIA_MOUNTED.equals(volume.getState())) continue;
                 File sdCard = volume.getPathFile();
                 if (sdCard.isDirectory()) {
-                    path = sdCard.getPath();
+                    path = new File(sdCard, dsuSlot).getPath();
                     break;
                 }
             }
             if (path.isEmpty()) {
-                path = PATH_DEFAULT;
+                path = PATH_DEFAULT + dsuSlot;
             }
             Slog.i(TAG, "startInstallation -> " + path);
         }
         mInstallPath = path;
+        mDsuSlot = dsuSlot;
         if (service.openInstall(path) != 0) {
             Slog.i(TAG, "Failed to open " + path);
             return false;
@@ -203,7 +204,7 @@
     public boolean setEnable(boolean enable, boolean oneShot) throws RemoteException {
         IGsiService gsiService = getGsiService();
         if (enable) {
-            return gsiService.enableGsi(oneShot) == 0;
+            return gsiService.enableGsi(oneShot, mDsuSlot) == 0;
         } else {
             return gsiService.disableGsi();
         }
diff --git a/services/core/java/com/android/server/GnssManagerService.java b/services/core/java/com/android/server/GnssManagerService.java
index bbcfdc6..32cdc41 100644
--- a/services/core/java/com/android/server/GnssManagerService.java
+++ b/services/core/java/com/android/server/GnssManagerService.java
@@ -47,7 +47,6 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocationManagerServiceUtils.LinkedListener;
 import com.android.server.LocationManagerServiceUtils.LinkedListenerBase;
-import com.android.server.location.AbstractLocationProvider;
 import com.android.server.location.CallerIdentity;
 import com.android.server.location.GnssBatchingProvider;
 import com.android.server.location.GnssCapabilitiesProvider;
@@ -116,11 +115,9 @@
     private final Handler mHandler;
 
     public GnssManagerService(LocationManagerService locationManagerService,
-            Context context,
-            AbstractLocationProvider.LocationProviderManager gnssProviderManager,
-            LocationUsageLogger locationUsageLogger) {
-        this(locationManagerService, context, new GnssLocationProvider(context, gnssProviderManager,
-                FgThread.getHandler().getLooper()), locationUsageLogger);
+            Context context, LocationUsageLogger locationUsageLogger) {
+        this(locationManagerService, context,
+                new GnssLocationProvider(context, FgThread.getHandler()), locationUsageLogger);
     }
 
     // Can use this constructor to inject GnssLocationProvider for testing
diff --git a/services/core/java/com/android/server/GraphicsStatsService.java b/services/core/java/com/android/server/GraphicsStatsService.java
index 70569db..5179fa7 100644
--- a/services/core/java/com/android/server/GraphicsStatsService.java
+++ b/services/core/java/com/android/server/GraphicsStatsService.java
@@ -38,11 +38,13 @@
 import android.view.IGraphicsStatsCallback;
 
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FastPrintWriter;
 
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Calendar;
@@ -78,6 +80,8 @@
     private static final int SAVE_BUFFER = 1;
     private static final int DELETE_OLD = 2;
 
+    private static final int AID_STATSD = 1066; // Statsd uid is set to 1066 forever.
+
     // This isn't static because we need this to happen after registerNativeMethods, however
     // the class is loaded (and thus static ctor happens) before that occurs.
     private final int ASHMEM_SIZE = nGetAshmemSize();
@@ -121,6 +125,7 @@
                 return true;
             }
         });
+        nativeInit();
     }
 
     /**
@@ -186,6 +191,86 @@
         return pfd;
     }
 
+    // If lastFullDay is true, pullGraphicsStats returns stats for the last complete day/24h period
+    // that does not include today. If lastFullDay is false, pullGraphicsStats returns stats for the
+    // current day.
+    // This method is invoked from native code only.
+    @SuppressWarnings({"UnusedDeclaration"})
+    private long pullGraphicsStats(boolean lastFullDay) throws RemoteException {
+        int uid = Binder.getCallingUid();
+
+        // DUMP and PACKAGE_USAGE_STATS permissions are required to invoke this method.
+        // TODO: remove exception for statsd daemon after required permissions are granted. statsd
+        // TODO: should have these permissions granted by data/etc/platform.xml, but it does not.
+        if (uid != AID_STATSD) {
+            StringWriter sw = new StringWriter();
+            PrintWriter pw = new FastPrintWriter(sw);
+            if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) {
+                pw.flush();
+                throw new RemoteException(sw.toString());
+            }
+        }
+
+        long callingIdentity = Binder.clearCallingIdentity();
+        try {
+            return pullGraphicsStatsImpl(lastFullDay);
+        } finally {
+            Binder.restoreCallingIdentity(callingIdentity);
+        }
+    }
+
+    private long pullGraphicsStatsImpl(boolean lastFullDay) {
+        long targetDay;
+        if (lastFullDay) {
+            // Get stats from yesterday. Stats stay constant, because the day is over.
+            targetDay = normalizeDate(System.currentTimeMillis() - 86400000).getTimeInMillis();
+        } else {
+            // Get stats from today. Stats may change as more apps are run today.
+            targetDay = normalizeDate(System.currentTimeMillis()).getTimeInMillis();
+        }
+
+        // Find active buffers for targetDay.
+        ArrayList<HistoricalBuffer> buffers;
+        synchronized (mLock) {
+            buffers = new ArrayList<>(mActive.size());
+            for (int i = 0; i < mActive.size(); i++) {
+                ActiveBuffer buffer = mActive.get(i);
+                if (buffer.mInfo.startTime == targetDay) {
+                    try {
+                        buffers.add(new HistoricalBuffer(buffer));
+                    } catch (IOException ex) {
+                        // Ignore
+                    }
+                }
+            }
+        }
+
+        // Dump active and historic buffers for targetDay in a serialized
+        // GraphicsStatsServiceDumpProto proto.
+        long dump = nCreateDump(-1, true);
+        try {
+            synchronized (mFileAccessLock) {
+                HashSet<File> skipList = dumpActiveLocked(dump, buffers);
+                buffers.clear();
+                String subPath = String.format("%d", targetDay);
+                File dateDir = new File(mGraphicsStatsDir, subPath);
+                if (dateDir.exists()) {
+                    for (File pkg : dateDir.listFiles()) {
+                        for (File version : pkg.listFiles()) {
+                            File data = new File(version, "total");
+                            if (skipList.contains(data)) {
+                                continue;
+                            }
+                            nAddToDump(dump, data.getAbsolutePath());
+                        }
+                    }
+                }
+            }
+        } finally {
+            return nFinishDumpInMemory(dump);
+        }
+    }
+
     private ParcelFileDescriptor getPfd(MemoryFile file) {
         try {
             if (!file.getFileDescriptor().valid()) {
@@ -379,12 +464,21 @@
         }
     }
 
+    @Override
+    protected void finalize() throws Throwable {
+        nativeDestructor();
+    }
+
+    private native void nativeInit();
+    private static native void nativeDestructor();
+
     private static native int nGetAshmemSize();
     private static native long nCreateDump(int outFd, boolean isProto);
     private static native void nAddToDump(long dump, String path, String packageName,
             long versionCode, long startTime, long endTime, byte[] data);
     private static native void nAddToDump(long dump, String path);
     private static native void nFinishDump(long dump);
+    private static native long nFinishDumpInMemory(long dump);
     private static native void nSaveBuffer(String path, String packageName, long versionCode,
             long startTime, long endTime, byte[] data);
 
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index c5f1923..de0b6fc 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -16,6 +16,10 @@
 
 package com.android.server;
 
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.location.LocationManager.FUSED_PROVIDER;
 import static android.location.LocationManager.GPS_PROVIDER;
@@ -23,8 +27,6 @@
 import static android.location.LocationManager.PASSIVE_PROVIDER;
 import static android.os.PowerManager.locationPowerSaveModeToString;
 
-import static com.android.internal.util.Preconditions.checkState;
-
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -35,14 +37,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.content.pm.Signature;
-import android.content.res.Resources;
-import android.hardware.location.ActivityRecognitionHardware;
 import android.location.Address;
 import android.location.Criteria;
 import android.location.GeocoderParams;
@@ -52,6 +47,7 @@
 import android.location.IGnssMeasurementsListener;
 import android.location.IGnssNavigationMessageListener;
 import android.location.IGnssStatusListener;
+import android.location.IGpsGeofenceHardware;
 import android.location.ILocationListener;
 import android.location.ILocationManager;
 import android.location.Location;
@@ -71,10 +67,8 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.os.WorkSource;
 import android.os.WorkSource.WorkChain;
-import android.provider.Settings;
 import android.stats.location.LocationStatsEnums;
 import android.text.TextUtils;
 import android.util.EventLog;
@@ -92,11 +86,12 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.server.location.AbstractLocationProvider;
-import com.android.server.location.ActivityRecognitionProxy;
+import com.android.server.location.AbstractLocationProvider.State;
 import com.android.server.location.CallerIdentity;
 import com.android.server.location.GeocoderProxy;
 import com.android.server.location.GeofenceManager;
 import com.android.server.location.GeofenceProxy;
+import com.android.server.location.HardwareActivityRecognitionProxy;
 import com.android.server.location.LocationFudger;
 import com.android.server.location.LocationProviderProxy;
 import com.android.server.location.LocationRequestStatistics;
@@ -105,7 +100,9 @@
 import com.android.server.location.LocationSettingsStore;
 import com.android.server.location.LocationUsageLogger;
 import com.android.server.location.MockProvider;
+import com.android.server.location.MockableLocationProvider;
 import com.android.server.location.PassiveProvider;
+import com.android.server.location.UserInfoStore;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 
 import java.io.ByteArrayOutputStream;
@@ -113,7 +110,6 @@
 import java.io.PrintStream;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -121,6 +117,9 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Objects;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -193,33 +192,31 @@
     private final Object mLock = new Object();
     private final Context mContext;
     private final Handler mHandler;
+    private final UserInfoStore mUserInfoStore;
     private final LocationSettingsStore mSettingsStore;
     private final LocationUsageLogger mLocationUsageLogger;
 
+    private final PassiveLocationProviderManager mPassiveManager;
+
     private AppOpsManager mAppOps;
     private PackageManager mPackageManager;
     private PowerManager mPowerManager;
     private ActivityManager mActivityManager;
-    private UserManager mUserManager;
 
     private GeofenceManager mGeofenceManager;
     private LocationFudger mLocationFudger;
     private GeocoderProxy mGeocodeProvider;
-    @Nullable
-    private GnssManagerService mGnssManagerService;
-    private PassiveProvider mPassiveProvider; // track passive provider for special cases
+    @Nullable private GnssManagerService mGnssManagerService;
+
     @GuardedBy("mLock")
     private String mExtraLocationControllerPackage;
+    @GuardedBy("mLock")
     private boolean mExtraLocationControllerPackageEnabled;
 
-    // list of currently active providers
-    @GuardedBy("mLock")
-    private final ArrayList<LocationProviderManager> mProviders = new ArrayList<>();
-
-    // list of non-mock providers, so that when mock providers replace real providers, they can be
-    // later re-replaced
-    @GuardedBy("mLock")
-    private final ArrayList<LocationProviderManager> mRealProviders = new ArrayList<>();
+    // @GuardedBy("mLock")
+    // hold lock for write or to prevent write, no lock for read
+    private final CopyOnWriteArrayList<LocationProviderManager> mProviderManagers =
+            new CopyOnWriteArrayList<>();
 
     @GuardedBy("mLock")
     private final HashMap<Object, Receiver> mReceivers = new HashMap<>();
@@ -238,10 +235,6 @@
     private final HashMap<String, Location> mLastLocationCoarseInterval =
             new HashMap<>();
 
-    // current active user on the device - other users are denied location data
-    private int mCurrentUserId = UserHandle.USER_SYSTEM;
-    private int[] mCurrentUserProfiles = new int[]{UserHandle.USER_SYSTEM};
-
     @GuardedBy("mLock")
     @PowerManager.LocationPowerSaveMode
     private int mBatterySaverMode;
@@ -249,9 +242,18 @@
     private LocationManagerService(Context context) {
         mContext = context;
         mHandler = FgThread.getHandler();
+        mUserInfoStore = new UserInfoStore(mContext);
         mSettingsStore = new LocationSettingsStore(mContext, mHandler);
         mLocationUsageLogger = new LocationUsageLogger();
 
+        // set up passive provider -  we do this early because it has no dependencies on system
+        // services or external code that isn't ready yet, and because this allows the variable to
+        // be final. other more complex providers are initialized later, when system services are
+        // ready
+        mPassiveManager = new PassiveLocationProviderManager();
+        mProviderManagers.add(mPassiveManager);
+        mPassiveManager.setRealProvider(new PassiveProvider(mContext));
+
         // Let the package manager query which are the default location
         // providers as they get certain permissions granted by default.
         PermissionManagerServiceInternal permissionManagerInternal = LocalServices.getService(
@@ -267,6 +269,7 @@
     }
 
     private void onSystemReady() {
+        mUserInfoStore.onSystemReady();
         mSettingsStore.onSystemReady();
 
         synchronized (mLock) {
@@ -274,7 +277,6 @@
             mAppOps = mContext.getSystemService(AppOpsManager.class);
             mPowerManager = mContext.getSystemService(PowerManager.class);
             mActivityManager = mContext.getSystemService(ActivityManager.class);
-            mUserManager = mContext.getSystemService(UserManager.class);
 
             mLocationFudger = new LocationFudger(mContext, mHandler);
             mGeofenceManager = new GeofenceManager(mContext, mSettingsStore);
@@ -362,10 +364,13 @@
                 }
             }.register(mContext, mHandler.getLooper(), true);
 
+            mUserInfoStore.addListener((oldUserId, newUserId) -> {
+                synchronized (mLock) {
+                    onUserChangedLocked(oldUserId, newUserId);
+                }
+            });
+
             IntentFilter intentFilter = new IntentFilter();
-            intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
-            intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
-            intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
             intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
             intentFilter.addAction(Intent.ACTION_SCREEN_ON);
 
@@ -378,14 +383,6 @@
                     }
                     synchronized (mLock) {
                         switch (action) {
-                            case Intent.ACTION_USER_SWITCHED:
-                                onUserChangedLocked(
-                                        intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
-                                break;
-                            case Intent.ACTION_MANAGED_PROFILE_ADDED:
-                            case Intent.ACTION_MANAGED_PROFILE_REMOVED:
-                                onUserProfilesChangedLocked();
-                                break;
                             case Intent.ACTION_SCREEN_ON:
                             case Intent.ACTION_SCREEN_OFF:
                                 onScreenStateChangedLocked();
@@ -395,11 +392,10 @@
                 }
             }, UserHandle.ALL, intentFilter, null, mHandler);
 
-            // switching the user from null to system here performs the bulk of the initialization
+            // switching the user from null to current here performs the bulk of the initialization
             // work. the user being changed will cause a reload of all user specific settings, which
             // causes initialization, and propagates changes until a steady state is reached
-            mCurrentUserId = UserHandle.USER_NULL;
-            onUserChangedLocked(ActivityManager.getCurrentUser());
+            onUserChangedLocked(UserHandle.USER_NULL, mUserInfoStore.getCurrentUserId());
         }
     }
 
@@ -415,15 +411,15 @@
         for (Receiver receiver : mReceivers.values()) {
             receiver.updateMonitoring(true);
         }
-        for (LocationProviderManager p : mProviders) {
-            applyRequirementsLocked(p);
+        for (LocationProviderManager manager : mProviderManagers) {
+            applyRequirementsLocked(manager);
         }
     }
 
     @GuardedBy("mLock")
     private void onPermissionsChangedLocked() {
-        for (LocationProviderManager p : mProviders) {
-            applyRequirementsLocked(p);
+        for (LocationProviderManager manager : mProviderManagers) {
+            applyRequirementsLocked(manager);
         }
     }
 
@@ -442,16 +438,16 @@
 
         mBatterySaverMode = newLocationMode;
 
-        for (LocationProviderManager p : mProviders) {
-            applyRequirementsLocked(p);
+        for (LocationProviderManager manager : mProviderManagers) {
+            applyRequirementsLocked(manager);
         }
     }
 
     @GuardedBy("mLock")
     private void onScreenStateChangedLocked() {
         if (mBatterySaverMode == PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF) {
-            for (LocationProviderManager p : mProviders) {
-                applyRequirementsLocked(p);
+            for (LocationProviderManager manager : mProviderManagers) {
+                applyRequirementsLocked(manager);
             }
         }
     }
@@ -462,12 +458,14 @@
             Log.d(TAG, "[u" + userId + "] location enabled = " + isLocationEnabledForUser(userId));
         }
 
-        Intent intent = new Intent(LocationManager.MODE_CHANGED_ACTION);
-        intent.putExtra(LocationManager.EXTRA_LOCATION_ENABLED, isLocationEnabledForUser(userId));
+        Intent intent = new Intent(LocationManager.MODE_CHANGED_ACTION)
+                .putExtra(LocationManager.EXTRA_LOCATION_ENABLED, isLocationEnabledForUser(userId))
+                .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
+                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
 
-        for (LocationProviderManager p : mProviders) {
-            p.onUseableChangedLocked(userId);
+        for (LocationProviderManager manager : mProviderManagers) {
+            manager.onUseableChangedLocked(userId);
         }
     }
 
@@ -521,225 +519,93 @@
 
     @GuardedBy("mLock")
     private void onBackgroundThrottleIntervalChangedLocked() {
-        for (LocationProviderManager provider : mProviders) {
-            applyRequirementsLocked(provider);
+        for (LocationProviderManager manager : mProviderManagers) {
+            applyRequirementsLocked(manager);
         }
     }
 
     @GuardedBy("mLock")
     private void onBackgroundThrottleWhitelistChangedLocked() {
-        for (LocationProviderManager p : mProviders) {
-            applyRequirementsLocked(p);
+        for (LocationProviderManager manager : mProviderManagers) {
+            applyRequirementsLocked(manager);
         }
     }
 
     @GuardedBy("lock")
     private void onIgnoreSettingsWhitelistChangedLocked() {
-        for (LocationProviderManager p : mProviders) {
-            applyRequirementsLocked(p);
+        for (LocationProviderManager manager : mProviderManagers) {
+            applyRequirementsLocked(manager);
         }
     }
 
     @GuardedBy("mLock")
-    private void onUserProfilesChangedLocked() {
-        mCurrentUserProfiles = mUserManager.getProfileIdsWithDisabled(mCurrentUserId);
-    }
-
-    @GuardedBy("mLock")
-    private boolean isCurrentProfileLocked(int userId) {
-        return ArrayUtils.contains(mCurrentUserProfiles, userId);
-    }
-
-    @GuardedBy("mLock")
-    private void ensureFallbackFusedProviderPresentLocked(String[] pkgs) {
-        PackageManager pm = mContext.getPackageManager();
-        String systemPackageName = mContext.getPackageName();
-        ArrayList<HashSet<Signature>> sigSets = ServiceWatcher.getSignatureSets(mContext, pkgs);
-
-        List<ResolveInfo> rInfos = pm.queryIntentServicesAsUser(
-                new Intent(FUSED_LOCATION_SERVICE_ACTION),
-                PackageManager.GET_META_DATA, mCurrentUserId);
-        for (ResolveInfo rInfo : rInfos) {
-            String packageName = rInfo.serviceInfo.packageName;
-
-            // Check that the signature is in the list of supported sigs. If it's not in
-            // this list the standard provider binding logic won't bind to it.
-            try {
-                PackageInfo pInfo;
-                pInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
-                if (!ServiceWatcher.isSignatureMatch(pInfo.signatures, sigSets)) {
-                    Log.w(TAG, packageName + " resolves service " + FUSED_LOCATION_SERVICE_ACTION +
-                            ", but has wrong signature, ignoring");
-                    continue;
-                }
-            } catch (NameNotFoundException e) {
-                Log.e(TAG, "missing package: " + packageName);
-                continue;
-            }
-
-            // Get the version info
-            if (rInfo.serviceInfo.metaData == null) {
-                Log.w(TAG, "Found fused provider without metadata: " + packageName);
-                continue;
-            }
-
-            int version = rInfo.serviceInfo.metaData.getInt(
-                    ServiceWatcher.EXTRA_SERVICE_VERSION, -1);
-            if (version == 0) {
-                // This should be the fallback fused location provider.
-
-                // Make sure it's in the system partition.
-                if ((rInfo.serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
-                    if (D) Log.d(TAG, "Fallback candidate not in /system: " + packageName);
-                    continue;
-                }
-
-                // Check that the fallback is signed the same as the OS
-                // as a proxy for coreApp="true"
-                if (pm.checkSignatures(systemPackageName, packageName)
-                        != PackageManager.SIGNATURE_MATCH) {
-                    if (D) {
-                        Log.d(TAG, "Fallback candidate not signed the same as system: "
-                                + packageName);
-                    }
-                    continue;
-                }
-
-                // Found a valid fallback.
-                if (D) Log.d(TAG, "Found fallback provider: " + packageName);
-                return;
-            } else {
-                if (D) Log.d(TAG, "Fallback candidate not version 0: " + packageName);
-            }
-        }
-
-        throw new IllegalStateException("Unable to find a fused location provider that is in the "
-                + "system partition with version 0 and signed with the platform certificate. "
-                + "Such a package is needed to provide a default fused location provider in the "
-                + "event that no other fused location provider has been installed or is currently "
-                + "available. For example, coreOnly boot mode when decrypting the data "
-                + "partition. The fallback must also be marked coreApp=\"true\" in the manifest");
-    }
-
-    @GuardedBy("mLock")
     private void initializeProvidersLocked() {
-        // create a passive location provider, which is always enabled
-        LocationProviderManager passiveProviderManager = new LocationProviderManager(
-                PASSIVE_PROVIDER);
-        addProviderLocked(passiveProviderManager);
-        mPassiveProvider = new PassiveProvider(mContext, passiveProviderManager);
-        passiveProviderManager.attachLocked(mPassiveProvider);
-
         if (GnssManagerService.isGnssSupported()) {
-            // Create a gps location provider manager
-            LocationProviderManager gnssProviderManager = new LocationProviderManager(GPS_PROVIDER);
-            mRealProviders.add(gnssProviderManager);
-            addProviderLocked(gnssProviderManager);
-
-            mGnssManagerService = new GnssManagerService(this, mContext, gnssProviderManager,
-                    mLocationUsageLogger);
-            gnssProviderManager.attachLocked(mGnssManagerService.getGnssLocationProvider());
+            mGnssManagerService = new GnssManagerService(this, mContext, mLocationUsageLogger);
+            LocationProviderManager gnssManager = new LocationProviderManager(GPS_PROVIDER);
+            mProviderManagers.add(gnssManager);
+            gnssManager.setRealProvider(mGnssManagerService.getGnssLocationProvider());
         }
 
-        /*
-        Load package name(s) containing location provider support.
-        These packages can contain services implementing location providers:
-        Geocoder Provider, Network Location Provider, and
-        Fused Location Provider. They will each be searched for
-        service components implementing these providers.
-        The location framework also has support for installation
-        of new location providers at run-time. The new package does not
-        have to be explicitly listed here, however it must have a signature
-        that matches the signature of at least one package on this list.
-        */
-        Resources resources = mContext.getResources();
-        String[] pkgs = resources.getStringArray(
-                com.android.internal.R.array.config_locationProviderPackageNames);
-        if (D) {
-            Log.d(TAG, "certificates for location providers pulled from: " +
-                    Arrays.toString(pkgs));
-        }
-
-        ensureFallbackFusedProviderPresentLocked(pkgs);
-
-        // bind to network provider
-        LocationProviderManager networkProviderManager = new LocationProviderManager(
-                NETWORK_PROVIDER);
-        LocationProviderProxy networkProvider = LocationProviderProxy.createAndBind(
+        LocationProviderProxy networkProvider = LocationProviderProxy.createAndRegister(
                 mContext,
-                networkProviderManager,
                 NETWORK_LOCATION_SERVICE_ACTION,
                 com.android.internal.R.bool.config_enableNetworkLocationOverlay,
-                com.android.internal.R.string.config_networkLocationProviderPackageName,
-                com.android.internal.R.array.config_locationProviderPackageNames);
+                com.android.internal.R.string.config_networkLocationProviderPackageName);
         if (networkProvider != null) {
-            mRealProviders.add(networkProviderManager);
-            addProviderLocked(networkProviderManager);
-            networkProviderManager.attachLocked(networkProvider);
+            LocationProviderManager networkManager = new LocationProviderManager(NETWORK_PROVIDER);
+            mProviderManagers.add(networkManager);
+            networkManager.setRealProvider(networkProvider);
         } else {
             Slog.w(TAG, "no network location provider found");
         }
 
+        // ensure that a fused provider exists which will work in direct boot
+        Preconditions.checkState(!mContext.getPackageManager().queryIntentServicesAsUser(
+                new Intent(FUSED_LOCATION_SERVICE_ACTION),
+                MATCH_DIRECT_BOOT_AWARE | MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM).isEmpty(),
+                "Unable to find a direct boot aware fused location provider");
+
         // bind to fused provider
-        LocationProviderManager fusedProviderManager = new LocationProviderManager(FUSED_PROVIDER);
-        LocationProviderProxy fusedProvider = LocationProviderProxy.createAndBind(
+        LocationProviderProxy fusedProvider = LocationProviderProxy.createAndRegister(
                 mContext,
-                fusedProviderManager,
                 FUSED_LOCATION_SERVICE_ACTION,
                 com.android.internal.R.bool.config_enableFusedLocationOverlay,
-                com.android.internal.R.string.config_fusedLocationProviderPackageName,
-                com.android.internal.R.array.config_locationProviderPackageNames);
+                com.android.internal.R.string.config_fusedLocationProviderPackageName);
         if (fusedProvider != null) {
-            mRealProviders.add(fusedProviderManager);
-            addProviderLocked(fusedProviderManager);
-            fusedProviderManager.attachLocked(fusedProvider);
+            LocationProviderManager fusedManager = new LocationProviderManager(FUSED_PROVIDER);
+            mProviderManagers.add(fusedManager);
+            fusedManager.setRealProvider(fusedProvider);
         } else {
             Slog.e(TAG, "no fused location provider found",
                     new IllegalStateException("Location service needs a fused location provider"));
         }
 
         // bind to geocoder provider
-        mGeocodeProvider = GeocoderProxy.createAndBind(mContext,
-                com.android.internal.R.bool.config_enableGeocoderOverlay,
-                com.android.internal.R.string.config_geocoderProviderPackageName,
-                com.android.internal.R.array.config_locationProviderPackageNames);
+        mGeocodeProvider = GeocoderProxy.createAndRegister(mContext);
         if (mGeocodeProvider == null) {
             Slog.e(TAG, "no geocoder provider found");
         }
 
+        // bind to geofence proxy
         if (mGnssManagerService != null) {
-            // bind to geofence provider
-            GeofenceProxy provider = GeofenceProxy.createAndBind(
-                    mContext, com.android.internal.R.bool.config_enableGeofenceOverlay,
-                    com.android.internal.R.string.config_geofenceProviderPackageName,
-                    com.android.internal.R.array.config_locationProviderPackageNames,
-                    mGnssManagerService.getGpsGeofenceProxy(),
-                    null);
-            if (provider == null) {
-                Slog.d(TAG, "Unable to bind FLP Geofence proxy.");
+            IGpsGeofenceHardware gpsGeofenceHardware = mGnssManagerService.getGpsGeofenceProxy();
+            if (gpsGeofenceHardware != null) {
+                GeofenceProxy provider = GeofenceProxy.createAndBind(mContext, gpsGeofenceHardware);
+                if (provider == null) {
+                    Slog.d(TAG, "unable to bind to GeofenceProxy");
+                }
             }
         }
 
         // bind to hardware activity recognition
-        boolean activityRecognitionHardwareIsSupported = ActivityRecognitionHardware.isSupported();
-        ActivityRecognitionHardware activityRecognitionHardware = null;
-        if (activityRecognitionHardwareIsSupported) {
-            activityRecognitionHardware = ActivityRecognitionHardware.getInstance(mContext);
-        } else {
-            Slog.d(TAG, "Hardware Activity-Recognition not supported.");
-        }
-        ActivityRecognitionProxy proxy = ActivityRecognitionProxy.createAndBind(
-                mContext,
-                activityRecognitionHardwareIsSupported,
-                activityRecognitionHardware,
-                com.android.internal.R.bool.config_enableActivityRecognitionHardwareOverlay,
-                com.android.internal.R.string.config_activityRecognitionHardwarePackageName,
-                com.android.internal.R.array.config_locationProviderPackageNames);
-        if (proxy == null) {
-            Slog.d(TAG, "Unable to bind ActivityRecognitionProxy.");
+        HardwareActivityRecognitionProxy hardwareActivityRecognitionProxy =
+                HardwareActivityRecognitionProxy.createAndRegister(mContext);
+        if (hardwareActivityRecognitionProxy == null) {
+            Log.e(TAG, "unable to bind ActivityRecognitionProxy");
         }
 
-        String[] testProviderStrings = resources.getStringArray(
+        String[] testProviderStrings = mContext.getResources().getStringArray(
                 com.android.internal.R.array.config_testLocationProviders);
         for (String testProviderString : testProviderStrings) {
             String[] fragments = testProviderString.split(",");
@@ -754,250 +620,200 @@
                     Boolean.parseBoolean(fragments[7]) /* supportsBearing */,
                     Integer.parseInt(fragments[8]) /* powerRequirement */,
                     Integer.parseInt(fragments[9]) /* accuracy */);
-            LocationProviderManager testProviderManager = new LocationProviderManager(name);
-            addProviderLocked(testProviderManager);
-            testProviderManager.attachLocked(
-                    new MockProvider(mContext, testProviderManager, properties));
+            addTestProvider(name, properties, mContext.getOpPackageName());
         }
     }
 
     @GuardedBy("mLock")
-    private void onUserChangedLocked(int userId) {
-        if (mCurrentUserId == userId) {
-            return;
-        }
-
+    private void onUserChangedLocked(int oldUserId, int newUserId) {
         if (D) {
-            Log.d(TAG, "foreground user is changing to " + userId);
+            Log.d(TAG, "foreground user is changing to " + newUserId);
         }
 
-        int oldUserId = userId;
-        mCurrentUserId = userId;
-        onUserProfilesChangedLocked();
+        for (LocationProviderManager manager : mProviderManagers) {
+            // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility
+            mSettingsStore.setLocationProviderAllowed(manager.getName(),
+                    manager.isUseable(newUserId), newUserId);
 
-        // let providers know the current user has changed
-        for (LocationProviderManager p : mProviders) {
-            p.onUseableChangedLocked(oldUserId);
-            p.onUseableChangedLocked(mCurrentUserId);
+            manager.onUseableChangedLocked(oldUserId);
+            manager.onUseableChangedLocked(newUserId);
         }
     }
 
     /**
      * Location provider manager, manages a LocationProvider.
      */
-    class LocationProviderManager implements AbstractLocationProvider.LocationProviderManager {
+    class LocationProviderManager implements MockableLocationProvider.Listener {
 
         private final String mName;
 
-        // remember to clear binder identity before invoking any provider operation
-        @GuardedBy("mLock")
-        @Nullable
-        protected AbstractLocationProvider mProvider;
+        // acquiring mLock makes operations on mProvider atomic, but is otherwise unnecessary
+        protected final MockableLocationProvider mProvider;
 
+        // useable state for parent user ids, no entry implies false. location state is only kept
+        // for parent user ids, the location state for a profile user id is assumed to be the same
+        // as for the parent. if querying this structure, ensure that the user id being used is a
+        // parent id or the results may be incorrect.
         @GuardedBy("mLock")
-        private SparseArray<Boolean> mUseable;  // combined state for each user id
-        @GuardedBy("mLock")
-        private boolean mEnabled;  // state of provider
-
-        @GuardedBy("mLock")
-        @Nullable
-        private ProviderProperties mProperties;
+        private final SparseArray<Boolean> mUseable;
 
         private LocationProviderManager(String name) {
             mName = name;
-
-            mProvider = null;
             mUseable = new SparseArray<>(1);
-            mEnabled = false;
-            mProperties = null;
 
-            // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility
-            Settings.Secure.putStringForUser(
-                    mContext.getContentResolver(),
-                    Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
-                    "-" + mName,
-                    mCurrentUserId);
-        }
+            // initialize last since this lets our reference escape
+            mProvider = new MockableLocationProvider(mContext, mLock, this);
 
-        @GuardedBy("mLock")
-        public void attachLocked(AbstractLocationProvider provider) {
-            Objects.requireNonNull(provider);
-            checkState(mProvider == null);
-
-            if (D) {
-                Log.d(TAG, mName + " provider attached");
-            }
-
-            mProvider = provider;
-
-            // it would be more correct to call this for all users, but we know this can only
-            // affect the current user since providers are disabled for non-current users
-            onUseableChangedLocked(mCurrentUserId);
+            // we can assume all users start with unuseable location state since the initial state
+            // of all providers is disabled. no need to initialize mUseable further.
         }
 
         public String getName() {
             return mName;
         }
 
-        @GuardedBy("mLock")
-        public List<String> getPackagesLocked() {
-            if (mProvider == null) {
-                return Collections.emptyList();
-            } else {
-                // safe to not clear binder context since this doesn't call into the real provider
-                return mProvider.getProviderPackages();
-            }
+        public boolean hasProvider() {
+            return mProvider.getProvider() != null;
         }
 
-        public boolean isMock() {
-            return false;
+        public void setRealProvider(AbstractLocationProvider provider) {
+            mProvider.setRealProvider(provider);
         }
 
-        @GuardedBy("mLock")
-        public boolean isPassiveLocked() {
-            return mProvider == mPassiveProvider;
+        public void setMockProvider(@Nullable MockProvider provider) {
+            mProvider.setMockProvider(provider);
         }
 
-        @GuardedBy("mLock")
+        public Set<String> getPackages() {
+            return mProvider.getState().providerPackageNames;
+        }
+
         @Nullable
-        public ProviderProperties getPropertiesLocked() {
-            return mProperties;
+        public ProviderProperties getProperties() {
+            return mProvider.getState().properties;
         }
 
-        public void setRequest(ProviderRequest request, WorkSource workSource) {
-            // move calls going to providers onto a different thread to avoid deadlock
-            mHandler.post(() -> {
-                synchronized (mLock) {
-                    if (mProvider != null) {
-                        mProvider.onSetRequest(request, workSource);
-                    }
+        public void setMockProviderEnabled(boolean enabled) {
+            synchronized (mLock) {
+                if (!mProvider.isMock()) {
+                    throw new IllegalArgumentException(mName + " provider is not a test provider");
                 }
-            });
+
+                mProvider.setMockProviderEnabled(enabled);
+            }
         }
 
-        public void sendExtraCommand(String command, Bundle extras) {
-            int uid = Binder.getCallingUid();
-            int pid = Binder.getCallingPid();
-
-            // move calls going to providers onto a different thread to avoid deadlock
-            mHandler.post(() -> {
-                synchronized (mLock) {
-                    if (mProvider != null) {
-                        mProvider.onSendExtraCommand(uid, pid, command, extras);
-                    }
+        public void setMockProviderLocation(Location location) {
+            synchronized (mLock) {
+                if (!mProvider.isMock()) {
+                    throw new IllegalArgumentException(mName + " provider is not a test provider");
                 }
-            });
+
+                String locationProvider = location.getProvider();
+                if (!TextUtils.isEmpty(locationProvider) && !mName.equals(locationProvider)) {
+                    // The location has an explicit provider that is different from the mock
+                    // provider name. The caller may be trying to fool us via b/33091107.
+                    EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(),
+                            mName + "!=" + locationProvider);
+                }
+
+                mProvider.setMockProviderLocation(location);
+            }
+        }
+
+        public List<LocationRequest> getMockProviderRequests() {
+            synchronized (mLock) {
+                if (!mProvider.isMock()) {
+                    throw new IllegalArgumentException(mName + " provider is not a test provider");
+                }
+
+                return mProvider.getCurrentRequest().locationRequests;
+            }
+        }
+
+        public void setRequest(ProviderRequest request) {
+            mProvider.setRequest(request);
+        }
+
+        public void sendExtraCommand(int uid, int pid, String command, Bundle extras) {
+            mProvider.sendExtraCommand(uid, pid, command, extras);
         }
 
         @GuardedBy("mLock")
-        public void dumpLocked(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
-            pw.print(mName + " provider");
-            if (isMock()) {
-                pw.print(" [mock]");
-            }
-            pw.println(":");
-
-            pw.increaseIndent();
-
-            pw.println("useable=" + isUseableLocked(mCurrentUserId));
-            if (!isUseableLocked(mCurrentUserId)) {
-                pw.println("attached=" + (mProvider != null));
-                pw.println("enabled=" + mEnabled);
-            }
-
-            pw.println("properties=" + mProperties);
-
-            if (mProvider != null) {
-                // in order to be consistent with other provider APIs, this should be run on the
-                // location thread... but this likely isn't worth it just for dumping info.
-                long identity = Binder.clearCallingIdentity();
-                try {
-                    mProvider.dump(fd, pw, args);
-                } finally {
-                    Binder.restoreCallingIdentity(identity);
-                }
-            }
-
-            pw.decreaseIndent();
-        }
-
         @Override
         public void onReportLocation(Location location) {
-            // likelihood of a 0,0 bug is far greater than this being a valid location
-            if (!isMock() && location.getLatitude() == 0 && location.getLongitude() == 0) {
-                Slog.w(TAG, "blocking 0,0 location from " + mName + " provider");
-                return;
+            // don't validate mock locations
+            if (!location.isFromMockProvider()) {
+                if (location.getLatitude() == 0 && location.getLongitude() == 0) {
+                    Slog.w(TAG, "blocking 0,0 location from " + mName + " provider");
+                    return;
+                }
             }
 
-            synchronized (mLock) {
-                handleLocationChangedLocked(location, this);
-            }
+            handleLocationChangedLocked(location, this);
         }
 
+        @GuardedBy("mLock")
         @Override
         public void onReportLocation(List<Location> locations) {
             if (mGnssManagerService == null) {
                 return;
             }
-            synchronized (mLock) {
-                LocationProviderManager gpsProvider = getLocationProviderLocked(GPS_PROVIDER);
-                if (gpsProvider == null || !gpsProvider.isUseableLocked()) {
-                    Slog.w(TAG, "reportLocationBatch() called without user permission");
-                    return;
-                }
 
-                mGnssManagerService.onReportLocation(locations);
+            if (!GPS_PROVIDER.equals(mName) || !isUseable()) {
+                Slog.w(TAG, "reportLocationBatch() called without user permission");
+                return;
             }
-        }
 
-        @Override
-        public void onSetEnabled(boolean enabled) {
-            synchronized (mLock) {
-                if (enabled == mEnabled) {
-                    return;
-                }
-
-                if (D) {
-                    Log.d(TAG, mName + " provider enabled is now " + mEnabled);
-                }
-
-                mEnabled = enabled;
-
-                // it would be more correct to call this for all users, but we know this can only
-                // affect the current user since providers are disabled for non-current users
-                onUseableChangedLocked(mCurrentUserId);
-            }
-        }
-
-        @Override
-        public void onSetProperties(ProviderProperties properties) {
-            synchronized (mLock) {
-                mProperties = properties;
-            }
+            mGnssManagerService.onReportLocation(locations);
         }
 
         @GuardedBy("mLock")
-        public boolean isUseableLocked() {
-            return isUseableLocked(mCurrentUserId);
+        @Override
+        public void onStateChanged(State oldState, State newState) {
+            if (oldState.enabled != newState.enabled) {
+                // it would be more correct to call this for all users, but we know this can
+                // only affect the current user since providers are disabled for non-current
+                // users
+                onUseableChangedLocked(mUserInfoStore.getCurrentUserId());
+            }
         }
 
-        @GuardedBy("mLock")
-        public boolean isUseableLocked(int userId) {
-            return mUseable.get(userId, Boolean.FALSE);
+        public boolean isUseable() {
+            return isUseable(mUserInfoStore.getCurrentUserId());
+        }
+
+        public boolean isUseable(int userId) {
+            synchronized (mLock) {
+                // normalize user id to always refer to parent since profile state is always the
+                // same as parent state
+                userId = mUserInfoStore.getParentUserId(userId);
+
+                return mUseable.get(userId, Boolean.FALSE);
+            }
         }
 
         @GuardedBy("mLock")
         public void onUseableChangedLocked(int userId) {
+            if (userId == UserHandle.USER_NULL) {
+                // only used during initialization - we don't care about the null user
+                return;
+            }
+
+            // normalize user id to always refer to parent since profile state is always the same
+            // as parent state
+            userId = mUserInfoStore.getParentUserId(userId);
+
             // if any property that contributes to "useability" here changes state, it MUST result
             // in a direct or indrect call to onUseableChangedLocked. this allows the provider to
             // guarantee that it will always eventually reach the correct state.
-            boolean useable = mProvider != null && mProviders.contains(this)
-                    && isCurrentProfileLocked(userId) && isLocationEnabledForUser(userId)
-                    && mEnabled;
+            boolean useable = (userId == mUserInfoStore.getCurrentUserId())
+                    && mSettingsStore.isLocationEnabled(userId) && mProvider.getState().enabled;
 
-            if (useable == isUseableLocked(userId)) {
+            if (useable == isUseable(userId)) {
                 return;
             }
+
             mUseable.put(userId, useable);
 
             if (D) {
@@ -1007,15 +823,13 @@
             // fused and passive provider never get public updates for legacy reasons
             if (!FUSED_PROVIDER.equals(mName) && !PASSIVE_PROVIDER.equals(mName)) {
                 // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility
-                Settings.Secure.putStringForUser(
-                        mContext.getContentResolver(),
-                        Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
-                        (useable ? "+" : "-") + mName,
-                        userId);
+                mSettingsStore.setLocationProviderAllowed(mName, useable, userId);
 
-                Intent intent = new Intent(LocationManager.PROVIDERS_CHANGED_ACTION);
-                intent.putExtra(LocationManager.EXTRA_PROVIDER_NAME, mName);
-                intent.putExtra(LocationManager.EXTRA_PROVIDER_ENABLED, useable);
+                Intent intent = new Intent(LocationManager.PROVIDERS_CHANGED_ACTION)
+                        .putExtra(LocationManager.EXTRA_PROVIDER_NAME, mName)
+                        .putExtra(LocationManager.EXTRA_PROVIDER_ENABLED, useable)
+                        .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
                 mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
             }
 
@@ -1029,55 +843,64 @@
 
             updateProviderUseableLocked(this);
         }
+
+        public void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
+            synchronized (mLock) {
+                pw.print(mName + " provider");
+                if (mProvider.isMock()) {
+                    pw.print(" [mock]");
+                }
+                pw.println(":");
+
+                pw.increaseIndent();
+
+                boolean useable = isUseable();
+                pw.println("useable=" + useable);
+                if (!useable) {
+                    pw.println("enabled=" + mProvider.getState().enabled);
+                }
+
+                pw.println("properties=" + mProvider.getState().properties);
+            }
+
+            mProvider.dump(fd, pw, args);
+
+            pw.decreaseIndent();
+        }
     }
 
-    private class MockLocationProvider extends LocationProviderManager {
+    class PassiveLocationProviderManager extends LocationProviderManager {
 
-        private ProviderRequest mCurrentRequest;
-
-        private MockLocationProvider(String name) {
-            super(name);
+        private PassiveLocationProviderManager() {
+            super(PASSIVE_PROVIDER);
         }
 
         @Override
-        public void attachLocked(AbstractLocationProvider provider) {
-            checkState(provider instanceof MockProvider);
-            super.attachLocked(provider);
+        public void setRealProvider(AbstractLocationProvider provider) {
+            Preconditions.checkArgument(provider instanceof PassiveProvider);
+            super.setRealProvider(provider);
         }
 
-        public boolean isMock() {
-            return true;
+        @Override
+        public void setMockProvider(@Nullable MockProvider provider) {
+            if (provider != null) {
+                throw new IllegalArgumentException("Cannot mock the passive provider");
+            }
         }
 
-        @GuardedBy("mLock")
-        public void setEnabledLocked(boolean enabled) {
-            if (mProvider != null) {
+        public void updateLocation(Location location) {
+            synchronized (mLock) {
+                PassiveProvider passiveProvider = (PassiveProvider) mProvider.getProvider();
+                Preconditions.checkState(passiveProvider != null);
+
                 long identity = Binder.clearCallingIdentity();
                 try {
-                    ((MockProvider) mProvider).setEnabled(enabled);
+                    passiveProvider.updateLocation(location);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
             }
         }
-
-        @GuardedBy("mLock")
-        public void setLocationLocked(Location location) {
-            if (mProvider != null) {
-                long identity = Binder.clearCallingIdentity();
-                try {
-                    ((MockProvider) mProvider).setLocation(location);
-                } finally {
-                    Binder.restoreCallingIdentity(identity);
-                }
-            }
-        }
-
-        @Override
-        public void setRequest(ProviderRequest request, WorkSource workSource) {
-            super.setRequest(request, workSource);
-            mCurrentRequest = request;
-        }
     }
 
     /**
@@ -1181,17 +1004,17 @@
                 // See if receiver has any enabled update records.  Also note if any update records
                 // are high power (has a high power provider with an interval under a threshold).
                 for (UpdateRecord updateRecord : mUpdateRecords.values()) {
-                    LocationProviderManager provider = getLocationProviderLocked(
+                    LocationProviderManager manager = getLocationProviderManager(
                             updateRecord.mProvider);
-                    if (provider == null) {
+                    if (manager == null) {
                         continue;
                     }
-                    if (!provider.isUseableLocked() && !isSettingsExemptLocked(updateRecord)) {
+                    if (!manager.isUseable() && !isSettingsExemptLocked(updateRecord)) {
                         continue;
                     }
 
                     requestingLocation = true;
-                    ProviderProperties properties = provider.getPropertiesLocked();
+                    ProviderProperties properties = manager.getProperties();
                     if (properties != null
                             && properties.mPowerRequirement == Criteria.POWER_HIGH
                             && updateRecord.mRequest.getInterval() < HIGH_POWER_INTERVAL_MS) {
@@ -1432,7 +1255,7 @@
             String featureId, String listenerIdentifier) {
         Objects.requireNonNull(listenerIdentifier);
 
-        return mGnssManagerService == null ? false : mGnssManagerService.addGnssBatchingCallback(
+        return mGnssManagerService != null && mGnssManagerService.addGnssBatchingCallback(
                 callback, packageName, featureId, listenerIdentifier);
     }
 
@@ -1443,7 +1266,7 @@
 
     @Override
     public boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName) {
-        return mGnssManagerService == null ? false : mGnssManagerService.startGnssBatch(periodNanos,
+        return mGnssManagerService != null && mGnssManagerService.startGnssBatch(periodNanos,
                 wakeOnFifoFull, packageName);
     }
 
@@ -1454,35 +1277,14 @@
 
     @Override
     public boolean stopGnssBatch() {
-        return mGnssManagerService == null ? false : mGnssManagerService.stopGnssBatch();
+        return mGnssManagerService != null && mGnssManagerService.stopGnssBatch();
     }
 
-    @GuardedBy("mLock")
-    private void addProviderLocked(LocationProviderManager provider) {
-        Preconditions.checkState(getLocationProviderLocked(provider.getName()) == null);
-
-        mProviders.add(provider);
-
-        // it would be more correct to call this for all users, but we know this can only
-        // affect the current user since providers are disabled for non-current users
-        provider.onUseableChangedLocked(mCurrentUserId);
-    }
-
-    @GuardedBy("mLock")
-    private void removeProviderLocked(LocationProviderManager provider) {
-        if (mProviders.remove(provider)) {
-            // it would be more correct to call this for all users, but we know this can only
-            // affect the current user since providers are disabled for non-current users
-            provider.onUseableChangedLocked(mCurrentUserId);
-        }
-    }
-
-    @GuardedBy("mLock")
     @Nullable
-    private LocationProviderManager getLocationProviderLocked(String providerName) {
-        for (LocationProviderManager provider : mProviders) {
-            if (providerName.equals(provider.getName())) {
-                return provider;
+    private LocationProviderManager getLocationProviderManager(String providerName) {
+        for (LocationProviderManager manager : mProviderManagers) {
+            if (providerName.equals(manager.getName())) {
+                return manager;
             }
         }
 
@@ -1492,20 +1294,19 @@
     private String getResolutionPermission(int resolutionLevel) {
         switch (resolutionLevel) {
             case RESOLUTION_LEVEL_FINE:
-                return android.Manifest.permission.ACCESS_FINE_LOCATION;
+                return ACCESS_FINE_LOCATION;
             case RESOLUTION_LEVEL_COARSE:
-                return android.Manifest.permission.ACCESS_COARSE_LOCATION;
+                return ACCESS_COARSE_LOCATION;
             default:
                 return null;
         }
     }
 
     private int getAllowedResolutionLevel(int pid, int uid) {
-        if (mContext.checkPermission(android.Manifest.permission.ACCESS_FINE_LOCATION,
-                pid, uid) == PERMISSION_GRANTED) {
+        if (mContext.checkPermission(ACCESS_FINE_LOCATION, pid, uid) == PERMISSION_GRANTED) {
             return RESOLUTION_LEVEL_FINE;
-        } else if (mContext.checkPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION,
-                pid, uid) == PERMISSION_GRANTED) {
+        } else if (mContext.checkPermission(ACCESS_COARSE_LOCATION, pid, uid)
+                == PERMISSION_GRANTED) {
             return RESOLUTION_LEVEL_COARSE;
         } else {
             return RESOLUTION_LEVEL_NONE;
@@ -1516,59 +1317,28 @@
         return getAllowedResolutionLevel(Binder.getCallingPid(), Binder.getCallingUid());
     }
 
-    private void checkResolutionLevelIsSufficientForGeofenceUse(int allowedResolutionLevel) {
-        if (allowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
-            throw new SecurityException("Geofence usage requires ACCESS_FINE_LOCATION permission");
-        }
+    private boolean checkCallingOrSelfLocationPermission() {
+        return mContext.checkCallingOrSelfPermission(ACCESS_COARSE_LOCATION) == PERMISSION_GRANTED
+                || mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION)
+                == PERMISSION_GRANTED;
     }
 
-    @GuardedBy("mLock")
-    private int getMinimumResolutionLevelForProviderUseLocked(String provider) {
-        if (GPS_PROVIDER.equals(provider) || PASSIVE_PROVIDER.equals(provider)) {
-            // gps and passive providers require FINE permission
-            return RESOLUTION_LEVEL_FINE;
-        } else if (NETWORK_PROVIDER.equals(provider) || FUSED_PROVIDER.equals(provider)) {
-            // network and fused providers are ok with COARSE or FINE
-            return RESOLUTION_LEVEL_COARSE;
-        } else {
-            for (LocationProviderManager lp : mProviders) {
-                if (!lp.getName().equals(provider)) {
-                    continue;
-                }
-
-                ProviderProperties properties = lp.getPropertiesLocked();
-                if (properties != null) {
-                    if (properties.mRequiresSatellite) {
-                        // provider requiring satellites require FINE permission
-                        return RESOLUTION_LEVEL_FINE;
-                    } else if (properties.mRequiresNetwork || properties.mRequiresCell) {
-                        // provider requiring network and or cell require COARSE or FINE
-                        return RESOLUTION_LEVEL_COARSE;
-                    }
-                }
-            }
+    private void enforceCallingOrSelfLocationPermission() {
+        if (checkCallingOrSelfLocationPermission()) {
+            return;
         }
 
-        return RESOLUTION_LEVEL_FINE; // if in doubt, require FINE
+        throw new SecurityException("uid " + Binder.getCallingUid() + " does not have "
+            + ACCESS_COARSE_LOCATION + " or " + ACCESS_FINE_LOCATION + ".");
     }
 
-    @GuardedBy("mLock")
-    private void checkResolutionLevelIsSufficientForProviderUseLocked(int allowedResolutionLevel,
-            String providerName) {
-        int requiredResolutionLevel = getMinimumResolutionLevelForProviderUseLocked(providerName);
-        if (allowedResolutionLevel < requiredResolutionLevel) {
-            switch (requiredResolutionLevel) {
-                case RESOLUTION_LEVEL_FINE:
-                    throw new SecurityException("\"" + providerName + "\" location provider " +
-                            "requires ACCESS_FINE_LOCATION permission.");
-                case RESOLUTION_LEVEL_COARSE:
-                    throw new SecurityException("\"" + providerName + "\" location provider " +
-                            "requires ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission.");
-                default:
-                    throw new SecurityException("Insufficient permission for \"" + providerName +
-                            "\" location provider.");
-            }
+    private void enforceCallingOrSelfPackageName(String packageName) {
+        int uid = Binder.getCallingUid();
+        if (ArrayUtils.contains(mPackageManager.getPackagesForUid(uid), packageName)) {
+            return;
         }
+
+        throw new SecurityException("invalid package \"" + packageName + "\" for uid " + uid);
     }
 
     public static int resolutionLevelToOp(int allowedResolutionLevel) {
@@ -1587,11 +1357,9 @@
             case RESOLUTION_LEVEL_COARSE:
                 return AppOpsManager.OPSTR_COARSE_LOCATION;
             case RESOLUTION_LEVEL_FINE:
-                return AppOpsManager.OPSTR_FINE_LOCATION;
+                // fall through
             case RESOLUTION_LEVEL_NONE:
-                // The client is not allowed to get any location, so both FINE and COARSE ops will
-                // be denied. Pick the most restrictive one to be safe.
-                return AppOpsManager.OPSTR_FINE_LOCATION;
+                // fall through
             default:
                 // Use the most restrictive ops if not sure.
                 return AppOpsManager.OPSTR_FINE_LOCATION;
@@ -1629,17 +1397,14 @@
      */
     @Override
     public List<String> getAllProviders() {
-        synchronized (mLock) {
-            ArrayList<String> providers = new ArrayList<>(mProviders.size());
-            for (LocationProviderManager provider : mProviders) {
-                String name = provider.getName();
-                if (FUSED_PROVIDER.equals(name)) {
-                    continue;
-                }
-                providers.add(name);
+        ArrayList<String> providers = new ArrayList<>(mProviderManagers.size());
+        for (LocationProviderManager manager : mProviderManagers) {
+            if (FUSED_PROVIDER.equals(manager.getName())) {
+                continue;
             }
-            return providers;
+            providers.add(manager.getName());
         }
+        return providers;
     }
 
     /**
@@ -1649,23 +1414,23 @@
      */
     @Override
     public List<String> getProviders(Criteria criteria, boolean enabledOnly) {
-        int allowedResolutionLevel = getCallerAllowedResolutionLevel();
+        if (!checkCallingOrSelfLocationPermission()) {
+            return Collections.emptyList();
+        }
+
         synchronized (mLock) {
-            ArrayList<String> providers = new ArrayList<>(mProviders.size());
-            for (LocationProviderManager provider : mProviders) {
-                String name = provider.getName();
+            ArrayList<String> providers = new ArrayList<>(mProviderManagers.size());
+            for (LocationProviderManager manager : mProviderManagers) {
+                String name = manager.getName();
                 if (FUSED_PROVIDER.equals(name)) {
                     continue;
                 }
-                if (allowedResolutionLevel < getMinimumResolutionLevelForProviderUseLocked(name)) {
-                    continue;
-                }
-                if (enabledOnly && !provider.isUseableLocked()) {
+                if (enabledOnly && !manager.isUseable()) {
                     continue;
                 }
                 if (criteria != null
                         && !android.location.LocationProvider.propertiesMeetCriteria(
-                        name, provider.getPropertiesLocked(), criteria)) {
+                        name, manager.getProperties(), criteria)) {
                     continue;
                 }
                 providers.add(name);
@@ -1702,15 +1467,15 @@
     }
 
     @GuardedBy("mLock")
-    private void updateProviderUseableLocked(LocationProviderManager provider) {
-        boolean useable = provider.isUseableLocked();
+    private void updateProviderUseableLocked(LocationProviderManager manager) {
+        boolean useable = manager.isUseable();
 
         ArrayList<Receiver> deadReceivers = null;
 
-        ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName());
+        ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName());
         if (records != null) {
             for (UpdateRecord record : records) {
-                if (!isCurrentProfileLocked(
+                if (!mUserInfoStore.isCurrentUserOrProfile(
                         UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) {
                     continue;
                 }
@@ -1721,7 +1486,7 @@
                 }
 
                 // Sends a notification message to the receiver
-                if (!record.mReceiver.callProviderEnabledLocked(provider.getName(), useable)) {
+                if (!record.mReceiver.callProviderEnabledLocked(manager.getName(), useable)) {
                     if (deadReceivers == null) {
                         deadReceivers = new ArrayList<>();
                     }
@@ -1736,26 +1501,25 @@
             }
         }
 
-        applyRequirementsLocked(provider);
+        applyRequirementsLocked(manager);
     }
 
     @GuardedBy("mLock")
     private void applyRequirementsLocked(String providerName) {
-        LocationProviderManager provider = getLocationProviderLocked(providerName);
-        if (provider != null) {
-            applyRequirementsLocked(provider);
+        LocationProviderManager manager = getLocationProviderManager(providerName);
+        if (manager != null) {
+            applyRequirementsLocked(manager);
         }
     }
 
     @GuardedBy("mLock")
-    private void applyRequirementsLocked(LocationProviderManager provider) {
-        ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName());
-        WorkSource worksource = new WorkSource();
-        ProviderRequest providerRequest = new ProviderRequest();
+    private void applyRequirementsLocked(LocationProviderManager manager) {
+        ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName());
+        ProviderRequest.Builder providerRequest = new ProviderRequest.Builder();
 
         // if provider is not active, it should not respond to requests
 
-        if (mProviders.contains(provider) && records != null && !records.isEmpty()) {
+        if (mProviderManagers.contains(manager) && records != null && !records.isEmpty()) {
             long backgroundThrottleInterval;
 
             long identity = Binder.clearCallingIdentity();
@@ -1765,6 +1529,8 @@
                 Binder.restoreCallingIdentity(identity);
             }
 
+            ArrayList<LocationRequest> requests = new ArrayList<>(records.size());
+
             final boolean isForegroundOnlyMode =
                     mBatterySaverMode == PowerManager.LOCATION_MODE_FOREGROUND_ONLY;
             final boolean shouldThrottleRequests =
@@ -1772,9 +1538,9 @@
                             == PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF
                             && !mPowerManager.isInteractive();
             // initialize the low power mode to true and set to false if any of the records requires
-            providerRequest.lowPowerMode = true;
+            providerRequest.setLowPowerMode(true);
             for (UpdateRecord record : records) {
-                if (!isCurrentProfileLocked(
+                if (!mUserInfoStore.isCurrentUserOrProfile(
                         UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) {
                     continue;
                 }
@@ -1787,10 +1553,10 @@
                 }
                 final boolean isBatterySaverDisablingLocation = shouldThrottleRequests
                         || (isForegroundOnlyMode && !record.mIsForegroundUid);
-                if (!provider.isUseableLocked() || isBatterySaverDisablingLocation) {
+                if (!manager.isUseable() || isBatterySaverDisablingLocation) {
                     if (isSettingsExemptLocked(record)) {
-                        providerRequest.locationSettingsIgnored = true;
-                        providerRequest.lowPowerMode = false;
+                        providerRequest.setLocationSettingsIgnored(true);
+                        providerRequest.setLowPowerMode(false);
                     } else {
                         continue;
                     }
@@ -1801,7 +1567,7 @@
 
 
                 // if we're forcing location, don't apply any throttling
-                if (!providerRequest.locationSettingsIgnored && !isThrottlingExemptLocked(
+                if (!providerRequest.isLocationSettingsIgnored() && !isThrottlingExemptLocked(
                         record.mReceiver.mCallerIdentity)) {
                     if (!record.mIsForegroundUid) {
                         interval = Math.max(interval, backgroundThrottleInterval);
@@ -1813,42 +1579,44 @@
                 }
 
                 record.mRequest = locationRequest;
-                providerRequest.locationRequests.add(locationRequest);
+                requests.add(locationRequest);
                 if (!locationRequest.isLowPowerMode()) {
-                    providerRequest.lowPowerMode = false;
+                    providerRequest.setLowPowerMode(false);
                 }
-                if (interval < providerRequest.interval) {
-                    providerRequest.reportLocation = true;
-                    providerRequest.interval = interval;
+                if (interval < providerRequest.getInterval()) {
+                    providerRequest.setInterval(interval);
                 }
             }
 
-            if (providerRequest.reportLocation) {
+            providerRequest.setLocationRequests(requests);
+
+            if (providerRequest.getInterval() < Long.MAX_VALUE) {
                 // calculate who to blame for power
                 // This is somewhat arbitrary. We pick a threshold interval
                 // that is slightly higher that the minimum interval, and
                 // spread the blame across all applications with a request
                 // under that threshold.
-                long thresholdInterval = (providerRequest.interval + 1000) * 3 / 2;
+                // TODO: overflow
+                long thresholdInterval = (providerRequest.getInterval() + 1000) * 3 / 2;
                 for (UpdateRecord record : records) {
-                    if (isCurrentProfileLocked(
+                    if (mUserInfoStore.isCurrentUserOrProfile(
                             UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) {
                         LocationRequest locationRequest = record.mRequest;
 
                         // Don't assign battery blame for update records whose
                         // client has no permission to receive location data.
-                        if (!providerRequest.locationRequests.contains(locationRequest)) {
+                        if (!providerRequest.getLocationRequests().contains(locationRequest)) {
                             continue;
                         }
 
                         if (locationRequest.getInterval() <= thresholdInterval) {
                             if (record.mReceiver.mWorkSource != null
                                     && isValidWorkSource(record.mReceiver.mWorkSource)) {
-                                worksource.add(record.mReceiver.mWorkSource);
+                                providerRequest.getWorkSource().add(record.mReceiver.mWorkSource);
                             } else {
                                 // Assign blame to caller if there's no WorkSource associated with
                                 // the request or if it's invalid.
-                                worksource.add(
+                                providerRequest.getWorkSource().add(
                                         record.mReceiver.mCallerIdentity.mUid,
                                         record.mReceiver.mCallerIdentity.mPackageName);
                             }
@@ -1858,7 +1626,7 @@
             }
         }
 
-        provider.setRequest(providerRequest, worksource);
+        manager.setRequest(providerRequest.build());
     }
 
     /**
@@ -2100,33 +1868,18 @@
         return sanitizedRequest;
     }
 
-    private void checkPackageName(String packageName) {
-        if (packageName == null) {
-            throw new SecurityException("invalid package name: " + null);
-        }
-        int uid = Binder.getCallingUid();
-        String[] packages = mPackageManager.getPackagesForUid(uid);
-        if (packages == null) {
-            throw new SecurityException("invalid UID " + uid);
-        }
-        for (String pkg : packages) {
-            if (packageName.equals(pkg)) return;
-        }
-        throw new SecurityException("invalid package name: " + packageName);
-    }
-
     @Override
     public void requestLocationUpdates(LocationRequest request, ILocationListener listener,
             PendingIntent intent, String packageName, String featureId,
             String listenerIdentifier) {
         Objects.requireNonNull(listenerIdentifier);
 
+        enforceCallingOrSelfLocationPermission();
+        enforceCallingOrSelfPackageName(packageName);
+
         synchronized (mLock) {
             if (request == null) request = DEFAULT_LOCATION_REQUEST;
-            checkPackageName(packageName);
             int allowedResolutionLevel = getCallerAllowedResolutionLevel();
-            checkResolutionLevelIsSufficientForProviderUseLocked(allowedResolutionLevel,
-                    request.getProvider());
             WorkSource workSource = request.getWorkSource();
             if (workSource != null && !workSource.isEmpty()) {
                 mContext.enforceCallingOrSelfPermission(
@@ -2198,8 +1951,8 @@
             throw new IllegalArgumentException("provider name must not be null");
         }
 
-        LocationProviderManager provider = getLocationProviderLocked(name);
-        if (provider == null) {
+        LocationProviderManager manager = getLocationProviderManager(name);
+        if (manager == null) {
             throw new IllegalArgumentException("provider doesn't exist: " + name);
         }
 
@@ -2217,7 +1970,7 @@
             oldRecord.disposeLocked(false);
         }
 
-        if (!provider.isUseableLocked() && !isSettingsExemptLocked(record)) {
+        if (!manager.isUseable() && !isSettingsExemptLocked(record)) {
             // Notify the listener that updates are currently disabled - but only if the request
             // does not ignore location settings
             receiver.callProviderEnabledLocked(name, false);
@@ -2233,7 +1986,7 @@
     @Override
     public void removeUpdates(ILocationListener listener, PendingIntent intent,
             String packageName) {
-        checkPackageName(packageName);
+        enforceCallingOrSelfPackageName(packageName);
 
         int pid = Binder.getCallingPid();
         int uid = Binder.getCallingUid();
@@ -2295,12 +2048,12 @@
 
     @Override
     public Location getLastLocation(LocationRequest r, String packageName, String featureId) {
+        enforceCallingOrSelfLocationPermission();
+        enforceCallingOrSelfPackageName(packageName);
+
         synchronized (mLock) {
             LocationRequest request = r != null ? r : DEFAULT_LOCATION_REQUEST;
             int allowedResolutionLevel = getCallerAllowedResolutionLevel();
-            checkPackageName(packageName);
-            checkResolutionLevelIsSufficientForProviderUseLocked(allowedResolutionLevel,
-                    request.getProvider());
             // no need to sanitize this request, as only the provider name is used
 
             final int pid = Binder.getCallingPid();
@@ -2320,16 +2073,16 @@
                 // or use the fused provider
                 String name = request.getProvider();
                 if (name == null) name = LocationManager.FUSED_PROVIDER;
-                LocationProviderManager provider = getLocationProviderLocked(name);
-                if (provider == null) return null;
+                LocationProviderManager manager = getLocationProviderManager(name);
+                if (manager == null) return null;
 
                 // only the current user or location providers may get location this way
-                if (!isCurrentProfileLocked(UserHandle.getUserId(uid)) && !isProviderPackage(
-                        packageName)) {
+                if (!mUserInfoStore.isCurrentUserOrProfile(UserHandle.getUserId(uid))
+                        && !isProviderPackage(packageName)) {
                     return null;
                 }
 
-                if (!provider.isUseableLocked()) {
+                if (!manager.isUseable()) {
                     return null;
                 }
 
@@ -2446,23 +2199,23 @@
     public boolean injectLocation(Location location) {
         mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE,
                 "Location Hardware permission not granted to inject location");
-        mContext.enforceCallingPermission(android.Manifest.permission.ACCESS_FINE_LOCATION,
+        mContext.enforceCallingPermission(ACCESS_FINE_LOCATION,
                 "Access Fine Location permission not granted to inject Location");
 
         synchronized (mLock) {
-            LocationProviderManager provider = getLocationProviderLocked(location.getProvider());
-            if (provider == null || !provider.isUseableLocked()) {
+            LocationProviderManager manager = getLocationProviderManager(location.getProvider());
+            if (manager == null || !manager.isUseable()) {
                 return false;
             }
 
             // NOTE: If last location is already available, location is not injected.  If
             // provider's normal source (like a GPS chipset) have already provided an output
             // there is no need to inject this location.
-            if (mLastLocation.get(provider.getName()) != null) {
+            if (mLastLocation.get(manager.getName()) != null) {
                 return false;
             }
 
-            updateLastLocationLocked(location, provider.getName());
+            updateLastLocationLocked(location, manager.getName());
             return true;
         }
     }
@@ -2472,17 +2225,14 @@
             String packageName, String featureId, String listenerIdentifier) {
         Objects.requireNonNull(listenerIdentifier);
 
+        mContext.enforceCallingOrSelfPermission(ACCESS_FINE_LOCATION, null);
+        enforceCallingOrSelfPackageName(packageName);
+
         if (request == null) request = DEFAULT_LOCATION_REQUEST;
         int allowedResolutionLevel = getCallerAllowedResolutionLevel();
-        checkResolutionLevelIsSufficientForGeofenceUse(allowedResolutionLevel);
         if (intent == null) {
             throw new IllegalArgumentException("invalid pending intent: " + null);
         }
-        checkPackageName(packageName);
-        synchronized (mLock) {
-            checkResolutionLevelIsSufficientForProviderUseLocked(allowedResolutionLevel,
-                    request.getProvider());
-        }
         // Require that caller can manage given document
         boolean callerHasLocationHardwarePermission =
                 mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE)
@@ -2511,7 +2261,7 @@
                         packageName,
                         request,
                         /* hasListener= */ false,
-                        intent != null,
+                        true,
                         geofence,
                         mActivityManager.getPackageImportance(packageName));
             }
@@ -2528,7 +2278,7 @@
         if (intent == null) {
             throw new IllegalArgumentException("invalid pending intent: " + null);
         }
-        checkPackageName(packageName);
+        enforceCallingOrSelfPackageName(packageName);
 
         if (D) Log.d(TAG, "removeGeofence: " + geofence + " " + intent);
 
@@ -2542,7 +2292,7 @@
                         packageName,
                         /* LocationRequest= */ null,
                         /* hasListener= */ false,
-                        intent != null,
+                        true,
                         geofence,
                         mActivityManager.getPackageImportance(packageName));
             }
@@ -2555,7 +2305,7 @@
     @Override
     public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName,
             String featureId) {
-        return mGnssManagerService == null ? false : mGnssManagerService.registerGnssStatusCallback(
+        return mGnssManagerService != null && mGnssManagerService.registerGnssStatusCallback(
                 listener, packageName, featureId);
     }
 
@@ -2569,9 +2319,8 @@
             String packageName, String featureId, String listenerIdentifier) {
         Objects.requireNonNull(listenerIdentifier);
 
-        return mGnssManagerService == null ? false
-                : mGnssManagerService.addGnssMeasurementsListener(listener, packageName, featureId,
-                       listenerIdentifier);
+        return mGnssManagerService != null && mGnssManagerService.addGnssMeasurementsListener(
+                listener, packageName, featureId, listenerIdentifier);
     }
 
     @Override
@@ -2586,8 +2335,8 @@
     public void injectGnssMeasurementCorrections(
             GnssMeasurementCorrections measurementCorrections, String packageName) {
         if (mGnssManagerService != null) {
-            mGnssManagerService.injectGnssMeasurementCorrections(
-                    measurementCorrections, packageName);
+            mGnssManagerService.injectGnssMeasurementCorrections(measurementCorrections,
+                    packageName);
         }
     }
 
@@ -2602,9 +2351,8 @@
             String packageName, String featureId, String listenerIdentifier) {
         Objects.requireNonNull(listenerIdentifier);
 
-        return mGnssManagerService == null ? false
-                : mGnssManagerService.addGnssNavigationMessageListener(listener, packageName,
-                        featureId, listenerIdentifier);
+        return mGnssManagerService != null && mGnssManagerService.addGnssNavigationMessageListener(
+                listener, packageName, featureId, listenerIdentifier);
     }
 
     @Override
@@ -2617,76 +2365,65 @@
 
     @Override
     public boolean sendExtraCommand(String providerName, String command, Bundle extras) {
-        if (providerName == null) {
-            // throw NullPointerException to remain compatible with previous implementation
-            throw new NullPointerException();
-        }
+        Objects.requireNonNull(providerName);
+        Objects.requireNonNull(command);
 
         mContext.enforceCallingOrSelfPermission(
                 Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, null);
+        enforceCallingOrSelfLocationPermission();
 
-        synchronized (mLock) {
-            checkResolutionLevelIsSufficientForProviderUseLocked(getCallerAllowedResolutionLevel(),
-                    providerName);
+        mLocationUsageLogger.logLocationApiUsage(
+                LocationStatsEnums.USAGE_STARTED,
+                LocationStatsEnums.API_SEND_EXTRA_COMMAND,
+                providerName);
 
-            mLocationUsageLogger.logLocationApiUsage(
-                    LocationStatsEnums.USAGE_STARTED,
-                    LocationStatsEnums.API_SEND_EXTRA_COMMAND,
-                    providerName);
-
-            LocationProviderManager provider = getLocationProviderLocked(providerName);
-            if (provider != null) {
-                provider.sendExtraCommand(command, extras);
-            }
-
-            mLocationUsageLogger.logLocationApiUsage(
-                    LocationStatsEnums.USAGE_ENDED,
-                    LocationStatsEnums.API_SEND_EXTRA_COMMAND,
-                    providerName);
-
-            return true;
+        LocationProviderManager manager = getLocationProviderManager(providerName);
+        if (manager != null) {
+            manager.sendExtraCommand(Binder.getCallingUid(), Binder.getCallingPid(), command,
+                    extras);
         }
+
+        mLocationUsageLogger.logLocationApiUsage(
+                LocationStatsEnums.USAGE_ENDED,
+                LocationStatsEnums.API_SEND_EXTRA_COMMAND,
+                providerName);
+
+        return true;
     }
 
     @Override
     public boolean sendNiResponse(int notifId, int userResponse) {
-        return mGnssManagerService == null ? false : mGnssManagerService.sendNiResponse(notifId,
+        return mGnssManagerService != null && mGnssManagerService.sendNiResponse(notifId,
                 userResponse);
     }
 
     @Override
     public ProviderProperties getProviderProperties(String providerName) {
-        synchronized (mLock) {
-            LocationProviderManager provider = getLocationProviderLocked(providerName);
-            if (provider == null) {
-                return null;
-            }
-            return provider.getPropertiesLocked();
+        LocationProviderManager manager = getLocationProviderManager(providerName);
+        if (manager == null) {
+            return null;
         }
+        return manager.getProperties();
     }
 
     @Override
     public boolean isProviderPackage(String packageName) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG,
                 Manifest.permission.READ_DEVICE_CONFIG + " permission required");
-        synchronized (mLock) {
-            for (LocationProviderManager provider : mProviders) {
-                if (provider.getPackagesLocked().contains(packageName)) {
-                    return true;
-                }
+        for (LocationProviderManager manager : mProviderManagers) {
+            if (manager.getPackages().contains(packageName)) {
+                return true;
             }
-            return false;
         }
+        return false;
     }
 
     @Override
     public List<String> getProviderPackages(String providerName) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG,
                 Manifest.permission.READ_DEVICE_CONFIG + " permission required");
-        synchronized (mLock) {
-            LocationProviderManager provider = getLocationProviderLocked(providerName);
-            return provider == null ? Collections.emptyList() : provider.getPackagesLocked();
-        }
+        LocationProviderManager manager = getLocationProviderManager(providerName);
+        return manager == null ? Collections.emptyList() : new ArrayList<>(manager.getPackages());
     }
 
     @Override
@@ -2753,8 +2490,8 @@
         if (FUSED_PROVIDER.equals(providerName)) return false;
 
         synchronized (mLock) {
-            LocationProviderManager provider = getLocationProviderLocked(providerName);
-            return provider != null && provider.isUseableLocked(userId);
+            LocationProviderManager manager = getLocationProviderManager(providerName);
+            return manager != null && manager.isUseable(userId);
         }
     }
 
@@ -2792,37 +2529,39 @@
     }
 
     @GuardedBy("mLock")
-    private void handleLocationChangedLocked(Location location, LocationProviderManager provider) {
-        if (!mProviders.contains(provider)) {
+    private void handleLocationChangedLocked(Location location, LocationProviderManager manager) {
+        if (!mProviderManagers.contains(manager)) {
+            Log.w(TAG, "received location from unknown provider: " + manager.getName());
             return;
         }
         if (!location.isComplete()) {
-            Log.w(TAG, "Dropping incomplete location: " + location);
+            Log.w(TAG, "dropping incomplete location from " + manager.getName() + " provider: "
+                    + location);
             return;
         }
 
-        // only notify passive provider and update last location for locations that come from
-        // useable providers
-        if (provider.isUseableLocked()) {
-            if (!provider.isPassiveLocked()) {
-                mPassiveProvider.updateLocation(location);
-            }
+        // notify passive provider
+        if (manager != mPassiveManager) {
+            mPassiveManager.updateLocation(new Location(location));
         }
 
         if (D) Log.d(TAG, "incoming location: " + location);
         long now = SystemClock.elapsedRealtime();
-        if (provider.isUseableLocked()) {
-            updateLastLocationLocked(location, provider.getName());
+
+
+        // only update last location for locations that come from useable providers
+        if (manager.isUseable()) {
+            updateLastLocationLocked(location, manager.getName());
         }
 
         // Update last known coarse interval location if enough time has passed.
         Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(
-                provider.getName());
+                manager.getName());
         if (lastLocationCoarseInterval == null) {
             lastLocationCoarseInterval = new Location(location);
 
-            if (provider.isUseableLocked()) {
-                mLastLocationCoarseInterval.put(provider.getName(), lastLocationCoarseInterval);
+            if (manager.isUseable()) {
+                mLastLocationCoarseInterval.put(manager.getName(), lastLocationCoarseInterval);
             }
         }
         long timeDeltaMs = TimeUnit.NANOSECONDS.toMillis(location.getElapsedRealtimeNanos()
@@ -2837,7 +2576,7 @@
                 lastLocationCoarseInterval.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
 
         // Skip if there are no UpdateRecords for this provider.
-        ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName());
+        ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName());
         if (records == null || records.size() == 0) return;
 
         // Fetch coarse location
@@ -2854,17 +2593,16 @@
             Receiver receiver = r.mReceiver;
             boolean receiverDead = false;
 
-            if (!provider.isUseableLocked() && !isSettingsExemptLocked(r)) {
+            if (!manager.isUseable() && !isSettingsExemptLocked(r)) {
                 continue;
             }
 
             int receiverUserId = UserHandle.getUserId(receiver.mCallerIdentity.mUid);
-            if (!isCurrentProfileLocked(receiverUserId)
+            if (!mUserInfoStore.isCurrentUserOrProfile(receiverUserId)
                     && !isProviderPackage(receiver.mCallerIdentity.mPackageName)) {
                 if (D) {
                     Log.d(TAG, "skipping loc update for background user " + receiverUserId +
-                            " (current user: " + mCurrentUserId + ", app: " +
-                            receiver.mCallerIdentity.mPackageName + ")");
+                            " (app: " + receiver.mCallerIdentity.mPackageName + ")");
                 }
                 continue;
             }
@@ -2949,7 +2687,7 @@
             for (UpdateRecord r : deadUpdateRecords) {
                 r.disposeLocked(true);
             }
-            applyRequirementsLocked(provider);
+            applyRequirementsLocked(manager);
         }
     }
 
@@ -3006,143 +2744,99 @@
 
     // Mock Providers
 
-    private boolean canCallerAccessMockLocation(String opPackageName) {
-        return mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(),
-                opPackageName) == AppOpsManager.MODE_ALLOWED;
-    }
-
     @Override
-    public void addTestProvider(String name, ProviderProperties properties, String opPackageName) {
-        if (!canCallerAccessMockLocation(opPackageName)) {
+    public void addTestProvider(String provider, ProviderProperties properties,
+            String packageName) {
+        if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName)
+                != AppOpsManager.MODE_ALLOWED) {
             return;
         }
 
-        if (PASSIVE_PROVIDER.equals(name)) {
-            throw new IllegalArgumentException("Cannot mock the passive location provider");
+        synchronized (mLock) {
+            LocationProviderManager manager = getLocationProviderManager(provider);
+            if (manager == null) {
+                manager = new LocationProviderManager(provider);
+                mProviderManagers.add(manager);
+            }
+
+            manager.setMockProvider(new MockProvider(mContext, properties));
+        }
+    }
+
+    @Override
+    public void removeTestProvider(String provider, String packageName) {
+        if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName)
+                != AppOpsManager.MODE_ALLOWED) {
+            return;
         }
 
         synchronized (mLock) {
-            long identity = Binder.clearCallingIdentity();
-            try {
-                LocationProviderManager oldProvider = getLocationProviderLocked(name);
-                if (oldProvider != null) {
-                    removeProviderLocked(oldProvider);
-                }
+            LocationProviderManager manager = getLocationProviderManager(provider);
+            if (manager == null) {
+                return;
+            }
 
-                MockLocationProvider mockProviderManager = new MockLocationProvider(name);
-                addProviderLocked(mockProviderManager);
-                mockProviderManager.attachLocked(
-                        new MockProvider(mContext, mockProviderManager, properties));
-            } finally {
-                Binder.restoreCallingIdentity(identity);
+            manager.setMockProvider(null);
+            if (!manager.hasProvider()) {
+                mProviderManagers.remove(manager);
+                mLastLocation.remove(manager.getName());
+                mLastLocationCoarseInterval.remove(manager.getName());
             }
         }
     }
 
     @Override
-    public void removeTestProvider(String name, String opPackageName) {
-        if (!canCallerAccessMockLocation(opPackageName)) {
+    public void setTestProviderLocation(String provider, Location location, String packageName) {
+        if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName)
+                != AppOpsManager.MODE_ALLOWED) {
             return;
         }
 
-        synchronized (mLock) {
-            long identity = Binder.clearCallingIdentity();
-            try {
-                LocationProviderManager testProvider = getLocationProviderLocked(name);
-                if (testProvider == null || !testProvider.isMock()) {
-                    return;
-                }
-
-                removeProviderLocked(testProvider);
-
-                // reinstate real provider if available
-                LocationProviderManager realProvider = null;
-                for (LocationProviderManager provider : mRealProviders) {
-                    if (name.equals(provider.getName())) {
-                        realProvider = provider;
-                        break;
-                    }
-                }
-
-                if (realProvider != null) {
-                    addProviderLocked(realProvider);
-                }
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
+        LocationProviderManager manager = getLocationProviderManager(provider);
+        if (manager == null) {
+            throw new IllegalArgumentException("provider doesn't exist: " + provider);
         }
+
+        manager.setMockProviderLocation(location);
     }
 
     @Override
-    public void setTestProviderLocation(String providerName, Location location,
-            String opPackageName) {
-        if (!canCallerAccessMockLocation(opPackageName)) {
+    public void setTestProviderEnabled(String provider, boolean enabled, String packageName) {
+        if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName)
+                != AppOpsManager.MODE_ALLOWED) {
             return;
         }
 
-        synchronized (mLock) {
-            LocationProviderManager testProvider = getLocationProviderLocked(providerName);
-            if (testProvider == null || !testProvider.isMock()) {
-                throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
-            }
-
-            String locationProvider = location.getProvider();
-            if (!TextUtils.isEmpty(locationProvider) && !providerName.equals(locationProvider)) {
-                // The location has an explicit provider that is different from the mock
-                // provider name. The caller may be trying to fool us via b/33091107.
-                EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(),
-                        providerName + "!=" + location.getProvider());
-            }
-
-            ((MockLocationProvider) testProvider).setLocationLocked(location);
-        }
-    }
-
-    @Override
-    public void setTestProviderEnabled(String providerName, boolean enabled, String opPackageName) {
-        if (!canCallerAccessMockLocation(opPackageName)) {
-            return;
+        LocationProviderManager manager = getLocationProviderManager(provider);
+        if (manager == null) {
+            throw new IllegalArgumentException("provider doesn't exist: " + provider);
         }
 
-        synchronized (mLock) {
-            LocationProviderManager testProvider = getLocationProviderLocked(providerName);
-            if (testProvider == null || !testProvider.isMock()) {
-                throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
-            }
-
-            ((MockLocationProvider) testProvider).setEnabledLocked(enabled);
-        }
+        manager.setMockProviderEnabled(enabled);
     }
 
     @Override
     @NonNull
-    public List<LocationRequest> getTestProviderCurrentRequests(String providerName,
-            String opPackageName) {
-        if (!canCallerAccessMockLocation(opPackageName)) {
+    public List<LocationRequest> getTestProviderCurrentRequests(String provider,
+            String packageName) {
+        if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName)
+                != AppOpsManager.MODE_ALLOWED) {
             return Collections.emptyList();
         }
 
-        synchronized (mLock) {
-            LocationProviderManager testProvider = getLocationProviderLocked(providerName);
-            if (testProvider == null || !testProvider.isMock()) {
-                throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
-            }
-
-            MockLocationProvider provider = (MockLocationProvider) testProvider;
-            if (provider.mCurrentRequest == null) {
-                return Collections.emptyList();
-            }
-            List<LocationRequest> requests = new ArrayList<>();
-            for (LocationRequest request : provider.mCurrentRequest.locationRequests) {
-                requests.add(new LocationRequest(request));
-            }
-            return requests;
+        LocationProviderManager manager = getLocationProviderManager(provider);
+        if (manager == null) {
+            throw new IllegalArgumentException("provider doesn't exist: " + provider);
         }
+
+        return manager.getMockProviderRequests();
     }
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) {
+            return;
+        }
 
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
 
@@ -3158,9 +2852,17 @@
                     + TimeUtils.logTimeOfDay(System.currentTimeMillis()));
             ipw.println(", Current Elapsed Time: "
                     + TimeUtils.formatDuration(SystemClock.elapsedRealtime()));
-            ipw.println("Current user: " + mCurrentUserId + " " + Arrays.toString(
-                    mCurrentUserProfiles));
-            ipw.println("Location Mode: " + isLocationEnabledForUser(mCurrentUserId));
+
+            ipw.println("User Info:");
+            ipw.increaseIndent();
+            mUserInfoStore.dump(fd, ipw, args);
+            ipw.decreaseIndent();
+
+            ipw.println("Location Settings:");
+            ipw.increaseIndent();
+            mSettingsStore.dump(fd, ipw, args);
+            ipw.decreaseIndent();
+
             ipw.println("Battery Saver Location Mode: "
                     + locationPowerSaveModeToString(mBatterySaverMode));
 
@@ -3185,13 +2887,17 @@
 
             ipw.println("Historical Records by Provider:");
             ipw.increaseIndent();
+            TreeMap<PackageProviderKey, PackageStatistics> sorted = new TreeMap<>();
+            sorted.putAll(mRequestStatistics.statistics);
             for (Map.Entry<PackageProviderKey, PackageStatistics> entry
-                    : mRequestStatistics.statistics.entrySet()) {
+                    : sorted.entrySet()) {
                 PackageProviderKey key = entry.getKey();
-                ipw.println(key.packageName + ": " + key.providerName + ": " + entry.getValue());
+                ipw.println(key.providerName + ": " + key.packageName + ": " + entry.getValue());
             }
             ipw.decreaseIndent();
 
+            mRequestStatistics.history.dump(ipw);
+
             ipw.println("Last Known Locations:");
             ipw.increaseIndent();
             for (Map.Entry<String, Location> entry : mLastLocation.entrySet()) {
@@ -3225,24 +2931,19 @@
                 ipw.decreaseIndent();
             }
 
-            ipw.println("Location Settings:");
-            ipw.increaseIndent();
-            mSettingsStore.dump(fd, ipw, args);
-            ipw.decreaseIndent();
-
             ipw.println("Location Providers:");
             ipw.increaseIndent();
-            for (LocationProviderManager provider : mProviders) {
-                provider.dumpLocked(fd, ipw, args);
+            for (LocationProviderManager manager : mProviderManagers) {
+                manager.dump(fd, ipw, args);
             }
             ipw.decreaseIndent();
-        }
 
-        if (mGnssManagerService != null) {
-            ipw.println("GNSS:");
-            ipw.increaseIndent();
-            mGnssManagerService.dump(fd, ipw, args);
-            ipw.decreaseIndent();
+            if (mGnssManagerService != null) {
+                ipw.println("GNSS:");
+                ipw.increaseIndent();
+                mGnssManagerService.dump(fd, ipw, args);
+                ipw.decreaseIndent();
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 1f73650..05d8360 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -58,10 +58,12 @@
 import android.net.NetworkStats;
 import android.net.NetworkUtils;
 import android.net.RouteInfo;
-import android.net.TetherConfigParcel;
 import android.net.TetherStatsParcel;
 import android.net.UidRange;
 import android.net.UidRangeParcel;
+import android.net.shared.NetdUtils;
+import android.net.shared.RouteUtils;
+import android.net.shared.RouteUtils.ModifyOperation;
 import android.net.util.NetdService;
 import android.os.BatteryStats;
 import android.os.Binder;
@@ -898,48 +900,14 @@
 
     @Override
     public void addRoute(int netId, RouteInfo route) {
-        modifyRoute(MODIFY_OPERATION_ADD, netId, route);
+        NetworkStack.checkNetworkStackPermission(mContext);
+        RouteUtils.modifyRoute(mNetdService, ModifyOperation.ADD, netId, route);
     }
 
     @Override
     public void removeRoute(int netId, RouteInfo route) {
-        modifyRoute(MODIFY_OPERATION_REMOVE, netId, route);
-    }
-
-    private void modifyRoute(boolean add, int netId, RouteInfo route) {
         NetworkStack.checkNetworkStackPermission(mContext);
-
-        final String ifName = route.getInterface();
-        final String dst = route.getDestination().toString();
-        final String nextHop;
-
-        switch (route.getType()) {
-            case RouteInfo.RTN_UNICAST:
-                if (route.hasGateway()) {
-                    nextHop = route.getGateway().getHostAddress();
-                } else {
-                    nextHop = INetd.NEXTHOP_NONE;
-                }
-                break;
-            case RouteInfo.RTN_UNREACHABLE:
-                nextHop = INetd.NEXTHOP_UNREACHABLE;
-                break;
-            case RouteInfo.RTN_THROW:
-                nextHop = INetd.NEXTHOP_THROW;
-                break;
-            default:
-                nextHop = INetd.NEXTHOP_NONE;
-                break;
-        }
-        try {
-            if (add) {
-                mNetdService.networkAddRoute(netId, ifName, dst, nextHop);
-            } else {
-                mNetdService.networkRemoveRoute(netId, ifName, dst, nextHop);
-            }
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new IllegalStateException(e);
-        }
+        RouteUtils.modifyRoute(mNetdService, ModifyOperation.REMOVE, netId, route);
     }
 
     private ArrayList<String> readRouteList(String filename) {
@@ -1023,12 +991,8 @@
     @Override
     public void startTetheringWithConfiguration(boolean usingLegacyDnsProxy, String[] dhcpRange) {
         NetworkStack.checkNetworkStackPermission(mContext);
-        // an odd number of addrs will fail
         try {
-            final TetherConfigParcel config = new TetherConfigParcel();
-            config.usingLegacyDnsProxy = usingLegacyDnsProxy;
-            config.dhcpRanges = dhcpRange;
-            mNetdService.tetherStartWithConfiguration(config);
+            NetdUtils.tetherStart(mNetdService, usingLegacyDnsProxy, dhcpRange);
         } catch (RemoteException | ServiceSpecificException e) {
             throw new IllegalStateException(e);
         }
@@ -1060,26 +1024,21 @@
     public void tetherInterface(String iface) {
         NetworkStack.checkNetworkStackPermission(mContext);
         try {
-            mNetdService.tetherInterfaceAdd(iface);
+            final LinkAddress addr = getInterfaceConfig(iface).getLinkAddress();
+            final IpPrefix dest = new IpPrefix(addr.getAddress(), addr.getPrefixLength());
+            NetdUtils.tetherInterface(mNetdService, iface, dest);
         } catch (RemoteException | ServiceSpecificException e) {
             throw new IllegalStateException(e);
         }
-        List<RouteInfo> routes = new ArrayList<>();
-        // The RouteInfo constructor truncates the LinkAddress to a network prefix, thus making it
-        // suitable to use as a route destination.
-        routes.add(new RouteInfo(getInterfaceConfig(iface).getLinkAddress(), null, iface));
-        addInterfaceToLocalNetwork(iface, routes);
     }
 
     @Override
     public void untetherInterface(String iface) {
         NetworkStack.checkNetworkStackPermission(mContext);
         try {
-            mNetdService.tetherInterfaceRemove(iface);
+            NetdUtils.untetherInterface(mNetdService, iface);
         } catch (RemoteException | ServiceSpecificException e) {
             throw new IllegalStateException(e);
-        } finally {
-            removeInterfaceFromLocalNetwork(iface);
         }
     }
 
@@ -2122,16 +2081,8 @@
     @Override
     public void addInterfaceToLocalNetwork(String iface, List<RouteInfo> routes) {
         modifyInterfaceInNetwork(MODIFY_OPERATION_ADD, INetd.LOCAL_NET_ID, iface);
-
-        for (RouteInfo route : routes) {
-            if (!route.isDefaultRoute()) {
-                modifyRoute(MODIFY_OPERATION_ADD, INetd.LOCAL_NET_ID, route);
-            }
-        }
-
-        // IPv6 link local should be activated always.
-        modifyRoute(MODIFY_OPERATION_ADD, INetd.LOCAL_NET_ID,
-                new RouteInfo(new IpPrefix("fe80::/64"), null, iface));
+        // modifyInterfaceInNetwork already check calling permission.
+        RouteUtils.addRoutesToLocalNetwork(mNetdService, iface, routes);
     }
 
     @Override
@@ -2141,17 +2092,8 @@
 
     @Override
     public int removeRoutesFromLocalNetwork(List<RouteInfo> routes) {
-        int failures = 0;
-
-        for (RouteInfo route : routes) {
-            try {
-                modifyRoute(MODIFY_OPERATION_REMOVE, INetd.LOCAL_NET_ID, route);
-            } catch (IllegalStateException e) {
-                failures++;
-            }
-        }
-
-        return failures;
+        NetworkStack.checkNetworkStackPermission(mContext);
+        return RouteUtils.removeRoutesFromLocalNetwork(mNetdService, routes);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index b26ef92..d1d1cb3 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -523,7 +523,7 @@
 
         @Override
         public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
-            int filterType = NetworkScoreManager.CACHE_FILTER_NONE;
+            int filterType = NetworkScoreManager.SCORE_FILTER_NONE;
             if (cookie instanceof Integer) {
                 filterType = (Integer) cookie;
             }
@@ -547,17 +547,17 @@
         private List<ScoredNetwork> filterScores(List<ScoredNetwork> scoredNetworkList,
                 int filterType) {
             switch (filterType) {
-                case NetworkScoreManager.CACHE_FILTER_NONE:
+                case NetworkScoreManager.SCORE_FILTER_NONE:
                     return scoredNetworkList;
 
-                case NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK:
+                case NetworkScoreManager.SCORE_FILTER_CURRENT_NETWORK:
                     if (mCurrentNetworkFilter == null) {
                         mCurrentNetworkFilter =
                                 new CurrentNetworkScoreCacheFilter(new WifiInfoSupplier(mContext));
                     }
                     return mCurrentNetworkFilter.apply(scoredNetworkList);
 
-                case NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS:
+                case NetworkScoreManager.SCORE_FILTER_SCAN_RESULTS:
                     if (mScanResultsFilter == null) {
                         mScanResultsFilter = new ScanResultsScoreCacheFilter(
                                 new ScanResultsSupplier(mContext));
diff --git a/services/core/java/com/android/server/NetworkTimeUpdateService.java b/services/core/java/com/android/server/NetworkTimeUpdateService.java
index 1ff455ea..c34dd98 100644
--- a/services/core/java/com/android/server/NetworkTimeUpdateService.java
+++ b/services/core/java/com/android/server/NetworkTimeUpdateService.java
@@ -16,15 +16,273 @@
 
 package com.android.server;
 
-import android.os.IBinder;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.app.timedetector.NetworkTimeSuggestion;
+import android.app.timedetector.TimeDetector;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.os.TimestampedValue;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.NtpTrustedTime;
+import android.util.TimeUtils;
+
+import com.android.internal.util.DumpUtils;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 
 /**
- * An interface for NetworkTimeUpdateService implementations. Eventually part or all of this service
- * will be subsumed into {@link com.android.server.timedetector.TimeDetectorService}. In the
- * meantime this interface allows Android to use either the old or new implementation.
+ * Monitors the network time. If looking up the network time fails for some reason, it tries a few
+ * times with a short interval and then resets to checking on longer intervals.
+ *
+ * <p>When available, the time is always suggested to the {@link
+ * com.android.server.timedetector.TimeDetectorService} where it may be used to set the device
+ * system clock, depending on user settings and what other signals are available.
  */
-public interface NetworkTimeUpdateService extends IBinder {
+public class NetworkTimeUpdateService extends Binder {
+
+    private static final String TAG = "NetworkTimeUpdateService";
+    private static final boolean DBG = false;
+
+    private static final int EVENT_AUTO_TIME_ENABLED = 1;
+    private static final int EVENT_POLL_NETWORK_TIME = 2;
+    private static final int EVENT_NETWORK_CHANGED = 3;
+
+    private static final String ACTION_POLL =
+            "com.android.server.NetworkTimeUpdateService.action.POLL";
+
+    private static final int POLL_REQUEST = 0;
+
+    private Network mDefaultNetwork = null;
+
+    private final Context mContext;
+    private final NtpTrustedTime mTime;
+    private final AlarmManager mAlarmManager;
+    private final TimeDetector mTimeDetector;
+    private final ConnectivityManager mCM;
+    private final PendingIntent mPendingPollIntent;
+    private final PowerManager.WakeLock mWakeLock;
+
+    // NTP lookup is done on this thread and handler
+    private Handler mHandler;
+    private AutoTimeSettingObserver mAutoTimeSettingObserver;
+    private NetworkTimeUpdateCallback mNetworkTimeUpdateCallback;
+
+    // Normal polling frequency
+    private final long mPollingIntervalMs;
+    // Try-again polling interval, in case the network request failed
+    private final long mPollingIntervalShorterMs;
+    // Number of times to try again
+    private final int mTryAgainTimesMax;
+    // Keeps track of how many quick attempts were made to fetch NTP time.
+    // During bootup, the network may not have been up yet, or it's taking time for the
+    // connection to happen.
+    private int mTryAgainCounter;
+
+    public NetworkTimeUpdateService(Context context) {
+        mContext = context;
+        mTime = NtpTrustedTime.getInstance(context);
+        mAlarmManager = mContext.getSystemService(AlarmManager.class);
+        mTimeDetector = mContext.getSystemService(TimeDetector.class);
+        mCM = mContext.getSystemService(ConnectivityManager.class);
+
+        Intent pollIntent = new Intent(ACTION_POLL, null);
+        mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0);
+
+        mPollingIntervalMs = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_ntpPollingInterval);
+        mPollingIntervalShorterMs = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_ntpPollingIntervalShorter);
+        mTryAgainTimesMax = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_ntpRetry);
+
+        mWakeLock = context.getSystemService(PowerManager.class).newWakeLock(
+                PowerManager.PARTIAL_WAKE_LOCK, TAG);
+    }
 
     /** Initialize the receivers and initiate the first NTP request */
-    void systemRunning();
+    public void systemRunning() {
+        registerForAlarms();
+
+        HandlerThread thread = new HandlerThread(TAG);
+        thread.start();
+        mHandler = new MyHandler(thread.getLooper());
+        mNetworkTimeUpdateCallback = new NetworkTimeUpdateCallback();
+        mCM.registerDefaultNetworkCallback(mNetworkTimeUpdateCallback, mHandler);
+
+        mAutoTimeSettingObserver = new AutoTimeSettingObserver(mContext, mHandler,
+                EVENT_AUTO_TIME_ENABLED);
+        mAutoTimeSettingObserver.observe();
+    }
+
+    private void registerForAlarms() {
+        mContext.registerReceiver(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget();
+                    }
+                }, new IntentFilter(ACTION_POLL));
+    }
+
+    private void onPollNetworkTime(int event) {
+        // If we don't have any default network, don't bother.
+        if (mDefaultNetwork == null) return;
+        mWakeLock.acquire();
+        try {
+            onPollNetworkTimeUnderWakeLock(event);
+        } finally {
+            mWakeLock.release();
+        }
+    }
+
+    private void onPollNetworkTimeUnderWakeLock(int event) {
+        // Force an NTP fix when outdated
+        NtpTrustedTime.TimeResult cachedNtpResult = mTime.getCachedTimeResult();
+        if (cachedNtpResult == null || cachedNtpResult.getAgeMillis() >= mPollingIntervalMs) {
+            if (DBG) Log.d(TAG, "Stale NTP fix; forcing refresh");
+            mTime.forceRefresh();
+            cachedNtpResult = mTime.getCachedTimeResult();
+        }
+
+        if (cachedNtpResult != null && cachedNtpResult.getAgeMillis() < mPollingIntervalMs) {
+            // Obtained fresh fix; schedule next normal update
+            resetAlarm(mPollingIntervalMs);
+
+            // Suggest the time to the time detector. It may choose use it to set the system clock.
+            TimestampedValue<Long> timeSignal = new TimestampedValue<>(
+                    cachedNtpResult.getElapsedRealtimeMillis(), cachedNtpResult.getTimeMillis());
+            NetworkTimeSuggestion timeSuggestion = new NetworkTimeSuggestion(timeSignal);
+            timeSuggestion.addDebugInfo("Origin: NetworkTimeUpdateService. event=" + event);
+            mTimeDetector.suggestNetworkTime(timeSuggestion);
+        } else {
+            // No fresh fix; schedule retry
+            mTryAgainCounter++;
+            if (mTryAgainTimesMax < 0 || mTryAgainCounter <= mTryAgainTimesMax) {
+                resetAlarm(mPollingIntervalShorterMs);
+            } else {
+                // Try much later
+                mTryAgainCounter = 0;
+                resetAlarm(mPollingIntervalMs);
+            }
+        }
+    }
+
+    /**
+     * Cancel old alarm and starts a new one for the specified interval.
+     *
+     * @param interval when to trigger the alarm, starting from now.
+     */
+    private void resetAlarm(long interval) {
+        mAlarmManager.cancel(mPendingPollIntent);
+        long now = SystemClock.elapsedRealtime();
+        long next = now + interval;
+        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, next, mPendingPollIntent);
+    }
+
+    /** Handler to do the network accesses on */
+    private class MyHandler extends Handler {
+
+        MyHandler(Looper l) {
+            super(l);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case EVENT_AUTO_TIME_ENABLED:
+                case EVENT_POLL_NETWORK_TIME:
+                case EVENT_NETWORK_CHANGED:
+                    onPollNetworkTime(msg.what);
+                    break;
+            }
+        }
+    }
+
+    private class NetworkTimeUpdateCallback extends NetworkCallback {
+        @Override
+        public void onAvailable(Network network) {
+            Log.d(TAG, String.format("New default network %s; checking time.", network));
+            mDefaultNetwork = network;
+            // Running on mHandler so invoke directly.
+            onPollNetworkTime(EVENT_NETWORK_CHANGED);
+        }
+
+        @Override
+        public void onLost(Network network) {
+            if (network.equals(mDefaultNetwork)) mDefaultNetwork = null;
+        }
+    }
+
+    /**
+     * Observer to watch for changes to the AUTO_TIME setting. It only triggers when the setting
+     * is enabled.
+     */
+    private static class AutoTimeSettingObserver extends ContentObserver {
+
+        private final Context mContext;
+        private final int mMsg;
+        private final Handler mHandler;
+
+        AutoTimeSettingObserver(Context context, Handler handler, int msg) {
+            super(handler);
+            mContext = context;
+            mHandler = handler;
+            mMsg = msg;
+        }
+
+        void observe() {
+            ContentResolver resolver = mContext.getContentResolver();
+            resolver.registerContentObserver(Settings.Global.getUriFor(Settings.Global.AUTO_TIME),
+                    false, this);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            if (isAutomaticTimeEnabled()) {
+                mHandler.obtainMessage(mMsg).sendToTarget();
+            }
+        }
+
+        /**
+         * Checks if the user prefers to automatically set the time.
+         */
+        private boolean isAutomaticTimeEnabled() {
+            ContentResolver resolver = mContext.getContentResolver();
+            return Settings.Global.getInt(resolver, Settings.Global.AUTO_TIME, 0) != 0;
+        }
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+        pw.print("PollingIntervalMs: ");
+        TimeUtils.formatDuration(mPollingIntervalMs, pw);
+        pw.print("\nPollingIntervalShorterMs: ");
+        TimeUtils.formatDuration(mPollingIntervalShorterMs, pw);
+        pw.println("\nTryAgainTimesMax: " + mTryAgainTimesMax);
+        pw.println("\nTryAgainCounter: " + mTryAgainCounter);
+        NtpTrustedTime.TimeResult ntpResult = mTime.getCachedTimeResult();
+        pw.println("NTP cache result: " + ntpResult);
+        if (ntpResult != null) {
+            pw.println("NTP result age: " + ntpResult.getAgeMillis());
+        }
+        pw.println();
+    }
 }
diff --git a/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java b/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java
deleted file mode 100644
index d20936c..0000000
--- a/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java
+++ /dev/null
@@ -1,282 +0,0 @@
-/*
- * Copyright (C) 2010 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;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.app.timedetector.NetworkTimeSuggestion;
-import android.app.timedetector.TimeDetector;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.database.ContentObserver;
-import android.net.ConnectivityManager;
-import android.net.ConnectivityManager.NetworkCallback;
-import android.net.Network;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.SystemClock;
-import android.os.TimestampedValue;
-import android.provider.Settings;
-import android.util.Log;
-import android.util.NtpTrustedTime;
-import android.util.TimeUtils;
-
-import com.android.internal.util.DumpUtils;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-/**
- * Monitors the network time. If looking up the network time fails for some reason, it tries a few
- * times with a short interval and then resets to checking on longer intervals.
- *
- * <p>When available, the time is always suggested to the {@link
- * com.android.server.timedetector.TimeDetectorService} where it may be used to set the device
- * system clock, depending on user settings and what other signals are available.
- */
-public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeUpdateService {
-
-    private static final String TAG = "NetworkTimeUpdateService";
-    private static final boolean DBG = false;
-
-    private static final int EVENT_AUTO_TIME_ENABLED = 1;
-    private static final int EVENT_POLL_NETWORK_TIME = 2;
-    private static final int EVENT_NETWORK_CHANGED = 3;
-
-    private static final String ACTION_POLL =
-            "com.android.server.NetworkTimeUpdateService.action.POLL";
-
-    private static final int POLL_REQUEST = 0;
-
-    private Network mDefaultNetwork = null;
-
-    private final Context mContext;
-    private final NtpTrustedTime mTime;
-    private final AlarmManager mAlarmManager;
-    private final TimeDetector mTimeDetector;
-    private final ConnectivityManager mCM;
-    private final PendingIntent mPendingPollIntent;
-    private final PowerManager.WakeLock mWakeLock;
-
-    // NTP lookup is done on this thread and handler
-    private Handler mHandler;
-    private AutoTimeSettingObserver mAutoTimeSettingObserver;
-    private NetworkTimeUpdateCallback mNetworkTimeUpdateCallback;
-
-    // Normal polling frequency
-    private final long mPollingIntervalMs;
-    // Try-again polling interval, in case the network request failed
-    private final long mPollingIntervalShorterMs;
-    // Number of times to try again
-    private final int mTryAgainTimesMax;
-    // Keeps track of how many quick attempts were made to fetch NTP time.
-    // During bootup, the network may not have been up yet, or it's taking time for the
-    // connection to happen.
-    private int mTryAgainCounter;
-
-    public NetworkTimeUpdateServiceImpl(Context context) {
-        mContext = context;
-        mTime = NtpTrustedTime.getInstance(context);
-        mAlarmManager = mContext.getSystemService(AlarmManager.class);
-        mTimeDetector = mContext.getSystemService(TimeDetector.class);
-        mCM = mContext.getSystemService(ConnectivityManager.class);
-
-        Intent pollIntent = new Intent(ACTION_POLL, null);
-        mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0);
-
-        mPollingIntervalMs = mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_ntpPollingInterval);
-        mPollingIntervalShorterMs = mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_ntpPollingIntervalShorter);
-        mTryAgainTimesMax = mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_ntpRetry);
-
-        mWakeLock = context.getSystemService(PowerManager.class).newWakeLock(
-                PowerManager.PARTIAL_WAKE_LOCK, TAG);
-    }
-
-    @Override
-    public void systemRunning() {
-        registerForAlarms();
-
-        HandlerThread thread = new HandlerThread(TAG);
-        thread.start();
-        mHandler = new MyHandler(thread.getLooper());
-        mNetworkTimeUpdateCallback = new NetworkTimeUpdateCallback();
-        mCM.registerDefaultNetworkCallback(mNetworkTimeUpdateCallback, mHandler);
-
-        mAutoTimeSettingObserver = new AutoTimeSettingObserver(mContext, mHandler,
-                EVENT_AUTO_TIME_ENABLED);
-        mAutoTimeSettingObserver.observe();
-    }
-
-    private void registerForAlarms() {
-        mContext.registerReceiver(
-            new BroadcastReceiver() {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget();
-                }
-            }, new IntentFilter(ACTION_POLL));
-    }
-
-    private void onPollNetworkTime(int event) {
-        // If we don't have any default network, don't bother.
-        if (mDefaultNetwork == null) return;
-        mWakeLock.acquire();
-        try {
-            onPollNetworkTimeUnderWakeLock(event);
-        } finally {
-            mWakeLock.release();
-        }
-    }
-
-    private void onPollNetworkTimeUnderWakeLock(int event) {
-        // Force an NTP fix when outdated
-        if (mTime.getCacheAge() >= mPollingIntervalMs) {
-            if (DBG) Log.d(TAG, "Stale NTP fix; forcing refresh");
-            mTime.forceRefresh();
-        }
-
-        if (mTime.getCacheAge() < mPollingIntervalMs) {
-            // Obtained fresh fix; schedule next normal update
-            resetAlarm(mPollingIntervalMs);
-
-            // Suggest the time to the time detector. It may choose use it to set the system clock.
-            TimestampedValue<Long> timeSignal = mTime.getCachedNtpTimeSignal();
-            NetworkTimeSuggestion timeSuggestion = new NetworkTimeSuggestion(timeSignal);
-            timeSuggestion.addDebugInfo("Origin: NetworkTimeUpdateServiceImpl. event=" + event);
-            mTimeDetector.suggestNetworkTime(timeSuggestion);
-        } else {
-            // No fresh fix; schedule retry
-            mTryAgainCounter++;
-            if (mTryAgainTimesMax < 0 || mTryAgainCounter <= mTryAgainTimesMax) {
-                resetAlarm(mPollingIntervalShorterMs);
-            } else {
-                // Try much later
-                mTryAgainCounter = 0;
-                resetAlarm(mPollingIntervalMs);
-            }
-        }
-    }
-
-    /**
-     * Cancel old alarm and starts a new one for the specified interval.
-     *
-     * @param interval when to trigger the alarm, starting from now.
-     */
-    private void resetAlarm(long interval) {
-        mAlarmManager.cancel(mPendingPollIntent);
-        long now = SystemClock.elapsedRealtime();
-        long next = now + interval;
-        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, next, mPendingPollIntent);
-    }
-
-    /** Handler to do the network accesses on */
-    private class MyHandler extends Handler {
-
-        public MyHandler(Looper l) {
-            super(l);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case EVENT_AUTO_TIME_ENABLED:
-                case EVENT_POLL_NETWORK_TIME:
-                case EVENT_NETWORK_CHANGED:
-                    onPollNetworkTime(msg.what);
-                    break;
-            }
-        }
-    }
-
-    private class NetworkTimeUpdateCallback extends NetworkCallback {
-        @Override
-        public void onAvailable(Network network) {
-            Log.d(TAG, String.format("New default network %s; checking time.", network));
-            mDefaultNetwork = network;
-            // Running on mHandler so invoke directly.
-            onPollNetworkTime(EVENT_NETWORK_CHANGED);
-        }
-
-        @Override
-        public void onLost(Network network) {
-            if (network.equals(mDefaultNetwork)) mDefaultNetwork = null;
-        }
-    }
-
-    /**
-     * Observer to watch for changes to the AUTO_TIME setting. It only triggers when the setting
-     * is enabled.
-     */
-    private static class AutoTimeSettingObserver extends ContentObserver {
-
-        private final Context mContext;
-        private final int mMsg;
-        private final Handler mHandler;
-
-        AutoTimeSettingObserver(Context context, Handler handler, int msg) {
-            super(handler);
-            mContext = context;
-            mHandler = handler;
-            mMsg = msg;
-        }
-
-        void observe() {
-            ContentResolver resolver = mContext.getContentResolver();
-            resolver.registerContentObserver(Settings.Global.getUriFor(Settings.Global.AUTO_TIME),
-                    false, this);
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            if (isAutomaticTimeEnabled()) {
-                mHandler.obtainMessage(mMsg).sendToTarget();
-            }
-        }
-
-        /**
-         * Checks if the user prefers to automatically set the time.
-         */
-        private boolean isAutomaticTimeEnabled() {
-            ContentResolver resolver = mContext.getContentResolver();
-            return Settings.Global.getInt(resolver, Settings.Global.AUTO_TIME, 0) != 0;
-        }
-    }
-
-    @Override
-    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
-        pw.print("PollingIntervalMs: ");
-        TimeUtils.formatDuration(mPollingIntervalMs, pw);
-        pw.print("\nPollingIntervalShorterMs: ");
-        TimeUtils.formatDuration(mPollingIntervalShorterMs, pw);
-        pw.println("\nTryAgainTimesMax: " + mTryAgainTimesMax);
-        pw.println("\nTryAgainCounter: " + mTryAgainCounter);
-        pw.println("NTP cache age: " + mTime.getCacheAge());
-        pw.println("NTP cache certainty: " + mTime.getCacheCertainty());
-        pw.println();
-    }
-}
diff --git a/services/core/java/com/android/server/NsdService.java b/services/core/java/com/android/server/NsdService.java
index b9b7bf7..4a1820a 100644
--- a/services/core/java/com/android/server/NsdService.java
+++ b/services/core/java/com/android/server/NsdService.java
@@ -22,10 +22,10 @@
 import android.database.ContentObserver;
 import android.net.NetworkStack;
 import android.net.Uri;
-import android.net.nsd.DnsSdTxtRecord;
 import android.net.nsd.INsdManager;
 import android.net.nsd.NsdManager;
 import android.net.nsd.NsdServiceInfo;
+import android.net.util.nsd.DnsSdTxtRecord;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Message;
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index deff440..b464422 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -29,6 +29,7 @@
 import android.os.Environment;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Process;
 import android.os.SystemProperties;
 import android.provider.DeviceConfig;
 import android.text.TextUtils;
@@ -36,6 +37,7 @@
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.LongArrayQueue;
+import android.util.MathUtils;
 import android.util.Slog;
 import android.util.Xml;
 
@@ -117,6 +119,12 @@
     // Whether explicit health checks are enabled or not
     private static final boolean DEFAULT_EXPLICIT_HEALTH_CHECK_ENABLED = true;
 
+    @VisibleForTesting
+    static final int DEFAULT_BOOT_LOOP_TRIGGER_COUNT = 5;
+    static final long DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS = TimeUnit.MINUTES.toMillis(10);
+    private static final String PROP_RESCUE_BOOT_COUNT = "sys.rescue_boot_count";
+    private static final String PROP_RESCUE_BOOT_START = "sys.rescue_boot_start";
+
     private long mNumberOfNativeCrashPollsRemaining;
 
     private static final int DB_VERSION = 1;
@@ -152,6 +160,7 @@
     private final Runnable mSyncStateWithScheduledReason = this::syncStateWithScheduledReason;
     private final Runnable mSaveToFile = this::saveToFile;
     private final SystemClock mSystemClock;
+    private final BootThreshold mBootThreshold;
     @GuardedBy("mLock")
     private boolean mIsPackagesReady;
     // Flag to control whether explicit health checks are supported or not
@@ -169,6 +178,7 @@
     @FunctionalInterface
     @VisibleForTesting
     interface SystemClock {
+        // TODO: Add elapsedRealtime to this interface
         long uptimeMillis();
     }
 
@@ -198,14 +208,17 @@
         mConnectivityModuleConnector = connectivityModuleConnector;
         mSystemClock = clock;
         mNumberOfNativeCrashPollsRemaining = NUMBER_OF_NATIVE_CRASH_POLLS;
+        mBootThreshold = new BootThreshold(DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
+                DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS);
         loadFromFile();
+        sPackageWatchdog = this;
     }
 
     /** Creates or gets singleton instance of PackageWatchdog. */
     public static PackageWatchdog getInstance(Context context) {
         synchronized (PackageWatchdog.class) {
             if (sPackageWatchdog == null) {
-                sPackageWatchdog = new PackageWatchdog(context);
+                new PackageWatchdog(context);
             }
             return sPackageWatchdog;
         }
@@ -410,6 +423,35 @@
         }
     }
 
+    /**
+     * Called when the system server boots. If the system server is detected to be in a boot loop,
+     * query each observer and perform the mitigation action with the lowest user impact.
+     */
+    public void noteBoot() {
+        synchronized (mLock) {
+            if (mBootThreshold.incrementAndTest()) {
+                mBootThreshold.reset();
+                PackageHealthObserver currentObserverToNotify = null;
+                int currentObserverImpact = Integer.MAX_VALUE;
+                for (int i = 0; i < mAllObservers.size(); i++) {
+                    final ObserverInternal observer = mAllObservers.valueAt(i);
+                    PackageHealthObserver registeredObserver = observer.registeredObserver;
+                    if (registeredObserver != null) {
+                        int impact = registeredObserver.onBootLoop();
+                        if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
+                                && impact < currentObserverImpact) {
+                            currentObserverToNotify = registeredObserver;
+                            currentObserverImpact = impact;
+                        }
+                    }
+                }
+                if (currentObserverToNotify != null) {
+                    currentObserverToNotify.executeBootLoopMitigation();
+                }
+            }
+        }
+    }
+
     // TODO(b/120598832): Optimize write? Maybe only write a separate smaller file? Also
     // avoid holding lock?
     // This currently adds about 7ms extra to shutdown thread
@@ -518,6 +560,22 @@
         boolean execute(@Nullable VersionedPackage versionedPackage,
                 @FailureReasons int failureReason);
 
+
+        /**
+         * Called when the system server has booted several times within a window of time, defined
+         * by {@link #mBootThreshold}
+         */
+        default @PackageHealthObserverImpact int onBootLoop() {
+            return PackageHealthObserverImpact.USER_IMPACT_NONE;
+        }
+
+        /**
+         * Executes mitigation for {@link #onBootLoop}
+         */
+        default boolean executeBootLoopMitigation() {
+            return false;
+        }
+
         // TODO(b/120598832): Ensure uniqueness?
         /**
          * Identifier for the observer, should not change across device updates otherwise the
@@ -1366,4 +1424,62 @@
             return value > 0 ? value : Long.MAX_VALUE;
         }
     }
+
+    /**
+     * Handles the thresholding logic for system server boots.
+     */
+    static class BootThreshold {
+
+        private final int mBootTriggerCount;
+        private final long mTriggerWindow;
+
+        BootThreshold(int bootTriggerCount, long triggerWindow) {
+            this.mBootTriggerCount = bootTriggerCount;
+            this.mTriggerWindow = triggerWindow;
+        }
+
+        public void reset() {
+            setStart(0);
+            setCount(0);
+        }
+
+        private int getCount() {
+            return SystemProperties.getInt(PROP_RESCUE_BOOT_COUNT, 0);
+        }
+
+        private void setCount(int count) {
+            SystemProperties.set(PROP_RESCUE_BOOT_COUNT, Integer.toString(count));
+        }
+
+        public long getStart() {
+            return SystemProperties.getLong(PROP_RESCUE_BOOT_START, 0);
+        }
+
+        public void setStart(long start) {
+            final long now = android.os.SystemClock.elapsedRealtime();
+            final long newStart = MathUtils.constrain(start, 0, now);
+            SystemProperties.set(PROP_RESCUE_BOOT_START, Long.toString(newStart));
+        }
+
+        /** Increments the boot counter, and returns whether the device is bootlooping. */
+        public boolean incrementAndTest() {
+            final long now = android.os.SystemClock.elapsedRealtime();
+            if (now - getStart() < 0) {
+                Slog.e(TAG, "Window was less than zero. Resetting start to current time.");
+                setStart(now);
+            }
+            final long window = now - getStart();
+            if (window >= mTriggerWindow) {
+                setCount(1);
+                setStart(now);
+                return false;
+            } else {
+                int count = getCount() + 1;
+                setCount(count);
+                EventLogTags.writeRescueNote(Process.ROOT_UID, count, window);
+                return count >= mBootTriggerCount;
+            }
+        }
+
+    }
 }
diff --git a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
index 190fff1..21fa9f9 100644
--- a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
+++ b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
@@ -46,4 +46,7 @@
 
     /** Update the OEM unlock enabled bit, bypassing user restriction checks. */
     void forceOemUnlockEnabled(boolean enabled);
+
+    /** Retrieves the UID that can access the persistent data partition. */
+    int getAllowedUid();
 }
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 73c8520..00d8b0f 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -680,6 +680,11 @@
             writeDataBuffer(getTestHarnessModeDataOffset(), ByteBuffer.allocate(size));
         }
 
+        @Override
+        public int getAllowedUid() {
+            return mAllowedUid;
+        }
+
         private void writeInternal(byte[] data, long offset, int dataLength) {
             checkArgument(data == null || data.length > 0, "data must be null or non-empty");
             checkArgument(
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index fb1a962..131a22b 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -18,31 +18,49 @@
 
 import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.Environment;
 import android.os.FileUtils;
+import android.os.Process;
 import android.os.RecoverySystem;
+import android.os.RemoteCallback;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
-import android.text.format.DateUtils;
+import android.util.ArraySet;
 import android.util.ExceptionUtils;
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.Slog;
-import android.util.SparseArray;
 import android.util.StatsLog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
+import com.android.server.PackageWatchdog.FailureReasons;
+import com.android.server.PackageWatchdog.PackageHealthObserver;
+import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
 import com.android.server.am.SettingsToPropertiesMapper;
-import com.android.server.utils.FlagNamespaceUtils;
 
 import java.io.File;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Utilities to help rescue the system from crash loops. Callers are expected to
@@ -57,8 +75,6 @@
     @VisibleForTesting
     static final String PROP_ENABLE_RESCUE = "persist.sys.enable_rescue";
     @VisibleForTesting
-    static final int TRIGGER_COUNT = 5;
-    @VisibleForTesting
     static final String PROP_RESCUE_LEVEL = "sys.rescue_level";
     @VisibleForTesting
     static final int LEVEL_NONE = 0;
@@ -72,25 +88,25 @@
     static final int LEVEL_FACTORY_RESET = 4;
     @VisibleForTesting
     static final String PROP_RESCUE_BOOT_COUNT = "sys.rescue_boot_count";
-    /**
-     * The boot trigger window size must always be greater than Watchdog's deadlock timeout
-     * {@link Watchdog#DEFAULT_TIMEOUT}.
-     */
-    @VisibleForTesting
-    static final long BOOT_TRIGGER_WINDOW_MILLIS = 600 * DateUtils.SECOND_IN_MILLIS;
-    @VisibleForTesting
-    static final long PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS = 30 * DateUtils.SECOND_IN_MILLIS;
     @VisibleForTesting
     static final String TAG = "RescueParty";
+    @VisibleForTesting
+    static final long DEFAULT_OBSERVING_DURATION_MS = TimeUnit.DAYS.toMillis(2);
+
+    private static final String NAME = "rescue-party-observer";
+
 
     private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
-    private static final String PROP_RESCUE_BOOT_START = "sys.rescue_boot_start";
     private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device";
 
-    /** Threshold for boot loops */
-    private static final Threshold sBoot = new BootThreshold();
-    /** Threshold for app crash loops */
-    private static SparseArray<Threshold> sApps = new SparseArray<>();
+    private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
+            | ApplicationInfo.FLAG_SYSTEM;
+
+    /** Register the Rescue Party observer as a Package Watchdog health observer */
+    public static void registerHealthObserver(Context context) {
+        PackageWatchdog.getInstance(context).registerHealthObserver(
+                RescuePartyObserver.getInstance(context));
+    }
 
     private static boolean isDisabled() {
         // Check if we're explicitly enabled for testing
@@ -122,37 +138,6 @@
     }
 
     /**
-     * Take note of a boot event. If we notice too many of these events
-     * happening in rapid succession, we'll send out a rescue party.
-     */
-    public static void noteBoot(Context context) {
-        if (isDisabled()) return;
-        if (sBoot.incrementAndTest()) {
-            sBoot.reset();
-            incrementRescueLevel(sBoot.uid);
-            executeRescueLevel(context);
-        }
-    }
-
-    /**
-     * Take note of a persistent app or apex module crash. If we notice too many of these
-     * events happening in rapid succession, we'll send out a rescue party.
-     */
-    public static void noteAppCrash(Context context, int uid) {
-        if (isDisabled()) return;
-        Threshold t = sApps.get(uid);
-        if (t == null) {
-            t = new AppThreshold(uid);
-            sApps.put(uid, t);
-        }
-        if (t.incrementAndTest()) {
-            t.reset();
-            incrementRescueLevel(t.uid);
-            executeRescueLevel(context);
-        }
-    }
-
-    /**
      * Check if we're currently attempting to reboot for a factory reset.
      */
     public static boolean isAttemptingFactoryReset() {
@@ -165,17 +150,11 @@
      */
     public static void onSettingsProviderPublished(Context context) {
         handleNativeRescuePartyResets();
-        executeRescueLevel(context);
-    }
-
-    @VisibleForTesting
-    static void resetAllThresholds() {
-        sBoot.reset();
-
-        for (int i = 0; i < sApps.size(); i++) {
-            Threshold appThreshold = sApps.get(sApps.keyAt(i));
-            appThreshold.reset();
-        }
+        executeRescueLevel(context, /*failedPackage=*/ null);
+        ContentResolver contentResolver = context.getContentResolver();
+        Settings.Config.registerMonitorCallback(contentResolver, new RemoteCallback(result -> {
+            handleMonitorCallback(context, result);
+        }));
     }
 
     @VisibleForTesting
@@ -183,16 +162,67 @@
         return SystemClock.elapsedRealtime();
     }
 
+    private static void handleMonitorCallback(Context context, Bundle result) {
+        String callbackType = result.getString(Settings.EXTRA_MONITOR_CALLBACK_TYPE, "");
+        switch (callbackType) {
+            case Settings.EXTRA_NAMESPACE_UPDATED_CALLBACK:
+                String updatedNamespace = result.getString(Settings.EXTRA_NAMESPACE);
+                if (updatedNamespace != null) {
+                    startObservingPackages(context, updatedNamespace);
+                }
+                break;
+            case Settings.EXTRA_ACCESS_CALLBACK:
+                String callingPackage = result.getString(Settings.EXTRA_CALLING_PACKAGE, null);
+                String namespace = result.getString(Settings.EXTRA_NAMESPACE, null);
+                if (namespace != null && callingPackage != null) {
+                    RescuePartyObserver.getInstance(context).recordDeviceConfigAccess(
+                            callingPackage,
+                            namespace);
+                }
+                break;
+            default:
+                Slog.w(TAG, "Unrecognized DeviceConfig callback");
+                break;
+        }
+    }
+
+    private static void startObservingPackages(Context context, @NonNull String updatedNamespace) {
+        RescuePartyObserver rescuePartyObserver = RescuePartyObserver.getInstance(context);
+        Set<String> callingPackages = rescuePartyObserver.getCallingPackagesSet(updatedNamespace);
+        if (callingPackages == null) {
+            return;
+        }
+        List<String> callingPackageList = new ArrayList<>();
+        callingPackageList.addAll(callingPackages);
+        Slog.i(TAG, "Starting to observe: " + callingPackageList + ", updated namespace: "
+                + updatedNamespace);
+        PackageWatchdog.getInstance(context).startObservingHealth(
+                rescuePartyObserver,
+                callingPackageList,
+                DEFAULT_OBSERVING_DURATION_MS);
+    }
+
     private static void handleNativeRescuePartyResets() {
         if (SettingsToPropertiesMapper.isNativeFlagsResetPerformed()) {
-            FlagNamespaceUtils.resetDeviceConfig(Settings.RESET_MODE_TRUSTED_DEFAULTS,
-                    Arrays.asList(SettingsToPropertiesMapper.getResetNativeCategories()));
+            String[] resetNativeCategories = SettingsToPropertiesMapper.getResetNativeCategories();
+            for (int i = 0; i < resetNativeCategories.length; i++) {
+                DeviceConfig.resetToDefaults(Settings.RESET_MODE_TRUSTED_DEFAULTS,
+                        resetNativeCategories[i]);
+            }
         }
     }
 
     /**
+     * Get the current rescue level.
+     */
+    private static int getRescueLevel() {
+        return MathUtils.constrain(SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE),
+                LEVEL_NONE, LEVEL_FACTORY_RESET);
+    }
+
+    /**
      * Escalate to the next rescue level. After incrementing the level you'll
-     * probably want to call {@link #executeRescueLevel(Context)}.
+     * probably want to call {@link #executeRescueLevel(Context, String)}.
      */
     private static void incrementRescueLevel(int triggerUid) {
         final int level = MathUtils.constrain(
@@ -205,13 +235,13 @@
                 + levelToString(level) + " triggered by UID " + triggerUid);
     }
 
-    private static void executeRescueLevel(Context context) {
+    private static void executeRescueLevel(Context context, @Nullable String failedPackage) {
         final int level = SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE);
         if (level == LEVEL_NONE) return;
 
         Slog.w(TAG, "Attempting rescue level " + levelToString(level));
         try {
-            executeRescueLevelInternal(context, level);
+            executeRescueLevelInternal(context, level, failedPackage);
             EventLogTags.writeRescueSuccess(level);
             logCriticalInfo(Log.DEBUG,
                     "Finished rescue level " + levelToString(level));
@@ -223,33 +253,55 @@
         }
     }
 
-    private static void executeRescueLevelInternal(Context context, int level) throws Exception {
+    private static void executeRescueLevelInternal(Context context, int level, @Nullable
+            String failedPackage) throws Exception {
         StatsLog.write(StatsLog.RESCUE_PARTY_RESET_REPORTED, level);
         switch (level) {
             case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
-                resetAllSettings(context, Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
+                resetAllSettings(context, Settings.RESET_MODE_UNTRUSTED_DEFAULTS, failedPackage);
                 break;
             case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
-                resetAllSettings(context, Settings.RESET_MODE_UNTRUSTED_CHANGES);
+                resetAllSettings(context, Settings.RESET_MODE_UNTRUSTED_CHANGES, failedPackage);
                 break;
             case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
-                resetAllSettings(context, Settings.RESET_MODE_TRUSTED_DEFAULTS);
+                resetAllSettings(context, Settings.RESET_MODE_TRUSTED_DEFAULTS, failedPackage);
                 break;
             case LEVEL_FACTORY_RESET:
                 RecoverySystem.rebootPromptAndWipeUserData(context, TAG);
                 break;
         }
-        FlagNamespaceUtils.addToKnownResetNamespaces(
-                FlagNamespaceUtils.NAMESPACE_NO_PACKAGE);
     }
 
-    private static void resetAllSettings(Context context, int mode) throws Exception {
+    private static int mapRescueLevelToUserImpact(int rescueLevel) {
+        switch(rescueLevel) {
+            case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
+            case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
+                return PackageHealthObserverImpact.USER_IMPACT_LOW;
+            case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
+            case LEVEL_FACTORY_RESET:
+                return PackageHealthObserverImpact.USER_IMPACT_HIGH;
+            default:
+                return PackageHealthObserverImpact.USER_IMPACT_NONE;
+        }
+    }
+
+    private static int getPackageUid(Context context, String packageName) {
+        try {
+            return context.getPackageManager().getPackageUid(packageName, 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            // Since UIDs are always >= 0, this value means the UID could not be determined.
+            return -1;
+        }
+    }
+
+    private static void resetAllSettings(Context context, int mode, @Nullable String failedPackage)
+            throws Exception {
         // Try our best to reset all settings possible, and once finished
         // rethrow any exception that we encountered
         Exception res = null;
         final ContentResolver resolver = context.getContentResolver();
         try {
-            FlagNamespaceUtils.resetDeviceConfig(mode);
+            resetDeviceConfig(context, mode, failedPackage);
         } catch (Exception e) {
             res = new RuntimeException("Failed to reset config settings", e);
         }
@@ -270,104 +322,176 @@
         }
     }
 
-    /**
-     * Threshold that can be triggered if a number of events occur within a
-     * window of time.
-     */
-    private abstract static class Threshold {
-        public abstract int getCount();
-        public abstract void setCount(int count);
-        public abstract long getStart();
-        public abstract void setStart(long start);
-
-        private final int uid;
-        private final int triggerCount;
-        private final long triggerWindow;
-
-        public Threshold(int uid, int triggerCount, long triggerWindow) {
-            this.uid = uid;
-            this.triggerCount = triggerCount;
-            this.triggerWindow = triggerWindow;
+    private static void resetDeviceConfig(Context context, int resetMode,
+            @Nullable String failedPackage) {
+        if (!shouldPerformScopedResets() || failedPackage == null) {
+            DeviceConfig.resetToDefaults(resetMode, /*namespace=*/ null);
+        } else {
+            performScopedReset(context, resetMode, failedPackage);
         }
+    }
 
-        public void reset() {
-            setCount(0);
-            setStart(0);
-        }
+    private static boolean shouldPerformScopedResets() {
+        int rescueLevel = MathUtils.constrain(
+                SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE),
+                LEVEL_NONE, LEVEL_FACTORY_RESET);
+        return rescueLevel <= LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES;
+    }
 
-        /**
-         * @return if this threshold has been triggered
-         */
-        public boolean incrementAndTest() {
-            final long now = getElapsedRealtime();
-            final long window = now - getStart();
-            if (window > triggerWindow) {
-                setCount(1);
-                setStart(now);
-                return false;
-            } else {
-                int count = getCount() + 1;
-                setCount(count);
-                EventLogTags.writeRescueNote(uid, count, window);
-                Slog.w(TAG, "Noticed " + count + " events for UID " + uid + " in last "
-                        + (window / 1000) + " sec");
-                return (count >= triggerCount);
+    private static void performScopedReset(Context context, int resetMode,
+            @NonNull String failedPackage) {
+        RescuePartyObserver rescuePartyObserver = RescuePartyObserver.getInstance(context);
+        Set<String> affectedNamespaces = rescuePartyObserver.getAffectedNamespaceSet(
+                failedPackage);
+        if (affectedNamespaces == null) {
+            DeviceConfig.resetToDefaults(resetMode, /*namespace=*/ null);
+        } else {
+            Slog.w(TAG,
+                    "Performing scoped reset for package: " + failedPackage
+                            + ", affected namespaces: "
+                            + Arrays.toString(affectedNamespaces.toArray()));
+            Iterator<String> it = affectedNamespaces.iterator();
+            while (it.hasNext()) {
+                DeviceConfig.resetToDefaults(resetMode, it.next());
             }
         }
     }
 
     /**
-     * Specialization of {@link Threshold} for monitoring boot events. It stores
-     * counters in system properties for robustness.
+     * Handle mitigation action for package failures. This observer will be register to Package
+     * Watchdog and will receive calls about package failures. This observer is persistent so it
+     * may choose to mitigate failures for packages it has not explicitly asked to observe.
      */
-    private static class BootThreshold extends Threshold {
-        public BootThreshold() {
-            // We're interested in TRIGGER_COUNT events in any
-            // BOOT_TRIGGER_WINDOW_MILLIS second period; this window is super relaxed because
-            // booting can take a long time if forced to dexopt things.
-            super(android.os.Process.ROOT_UID, TRIGGER_COUNT, BOOT_TRIGGER_WINDOW_MILLIS);
+    public static class RescuePartyObserver implements PackageHealthObserver {
+
+        private final Context mContext;
+        private final Map<String, Set<String>> mCallingPackageNamespaceSetMap = new HashMap<>();
+        private final Map<String, Set<String>> mNamespaceCallingPackageSetMap = new HashMap<>();
+
+        @GuardedBy("RescuePartyObserver.class")
+        static RescuePartyObserver sRescuePartyObserver;
+
+        private RescuePartyObserver(Context context) {
+            mContext = context;
+        }
+
+        /** Creates or gets singleton instance of RescueParty. */
+        public static RescuePartyObserver getInstance(Context context) {
+            synchronized (RescuePartyObserver.class) {
+                if (sRescuePartyObserver == null) {
+                    sRescuePartyObserver = new RescuePartyObserver(context);
+                }
+                return sRescuePartyObserver;
+            }
+        }
+
+        @VisibleForTesting
+        static void reset() {
+            synchronized (RescuePartyObserver.class) {
+                sRescuePartyObserver = null;
+            }
         }
 
         @Override
-        public int getCount() {
-            return SystemProperties.getInt(PROP_RESCUE_BOOT_COUNT, 0);
+        public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage,
+                @FailureReasons int failureReason) {
+            if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
+                    || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) {
+                int rescueLevel = MathUtils.constrain(
+                        SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE) + 1,
+                        LEVEL_NONE, LEVEL_FACTORY_RESET);
+                return mapRescueLevelToUserImpact(rescueLevel);
+            } else {
+                return PackageHealthObserverImpact.USER_IMPACT_NONE;
+            }
         }
 
         @Override
-        public void setCount(int count) {
-            SystemProperties.set(PROP_RESCUE_BOOT_COUNT, Integer.toString(count));
+        public boolean execute(@Nullable VersionedPackage failedPackage,
+                @FailureReasons int failureReason) {
+            if (isDisabled()) {
+                return false;
+            }
+            if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
+                    || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) {
+                int triggerUid = getPackageUid(mContext, failedPackage.getPackageName());
+                incrementRescueLevel(triggerUid);
+                executeRescueLevel(mContext,
+                        failedPackage == null ? null : failedPackage.getPackageName());
+                return true;
+            } else {
+                return false;
+            }
         }
 
         @Override
-        public long getStart() {
-            return SystemProperties.getLong(PROP_RESCUE_BOOT_START, 0);
+        public boolean isPersistent() {
+            return true;
         }
 
         @Override
-        public void setStart(long start) {
-            SystemProperties.set(PROP_RESCUE_BOOT_START, Long.toString(start));
-        }
-    }
-
-    /**
-     * Specialization of {@link Threshold} for monitoring app crashes. It stores
-     * counters in memory.
-     */
-    private static class AppThreshold extends Threshold {
-        private int count;
-        private long start;
-
-        public AppThreshold(int uid) {
-            // We're interested in TRIGGER_COUNT events in any
-            // PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS second period; apps crash pretty quickly
-            // so we can keep a tight leash on them.
-            super(uid, TRIGGER_COUNT, PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS);
+        public boolean mayObservePackage(String packageName) {
+            PackageManager pm = mContext.getPackageManager();
+            try {
+                // A package is a Mainline module if this is non-null
+                if (pm.getModuleInfo(packageName, 0) != null) {
+                    return true;
+                }
+                ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
+                return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK;
+            } catch (PackageManager.NameNotFoundException e) {
+                return false;
+            }
         }
 
-        @Override public int getCount() { return count; }
-        @Override public void setCount(int count) { this.count = count; }
-        @Override public long getStart() { return start; }
-        @Override public void setStart(long start) { this.start = start; }
+        @Override
+        public int onBootLoop() {
+            if (isDisabled()) {
+                return PackageHealthObserverImpact.USER_IMPACT_NONE;
+            }
+            return mapRescueLevelToUserImpact(getRescueLevel());
+        }
+
+        @Override
+        public boolean executeBootLoopMitigation() {
+            if (isDisabled()) {
+                return false;
+            }
+            incrementRescueLevel(Process.ROOT_UID);
+            executeRescueLevel(mContext, /*failedPackage=*/ null);
+            return true;
+        }
+
+        @Override
+        public String getName() {
+            return NAME;
+        }
+
+        private synchronized void recordDeviceConfigAccess(@NonNull String callingPackage,
+                @NonNull String namespace) {
+            // Record it in calling packages to namespace map
+            Set<String> namespaceSet = mCallingPackageNamespaceSetMap.get(callingPackage);
+            if (namespaceSet == null) {
+                namespaceSet = new ArraySet<>();
+                mCallingPackageNamespaceSetMap.put(callingPackage, namespaceSet);
+            }
+            namespaceSet.add(namespace);
+            // Record it in namespace to calling packages map
+            Set<String> callingPackageSet = mNamespaceCallingPackageSetMap.get(namespace);
+            if (callingPackageSet == null) {
+                callingPackageSet = new ArraySet<>();
+            }
+            callingPackageSet.add(callingPackage);
+            mNamespaceCallingPackageSetMap.put(namespace, callingPackageSet);
+        }
+
+        private synchronized Set<String> getAffectedNamespaceSet(String failedPackage) {
+            return mCallingPackageNamespaceSetMap.get(failedPackage);
+        }
+
+        private synchronized Set<String> getCallingPackagesSet(String namespace) {
+            return mNamespaceCallingPackageSetMap.get(namespace);
+        }
     }
 
     private static int[] getAllUserIds() {
diff --git a/services/core/java/com/android/server/ServiceWatcher.java b/services/core/java/com/android/server/ServiceWatcher.java
index 7f51aa9..8564cb4 100644
--- a/services/core/java/com/android/server/ServiceWatcher.java
+++ b/services/core/java/com/android/server/ServiceWatcher.java
@@ -16,7 +16,19 @@
 
 package com.android.server;
 
+import static android.content.Context.BIND_AUTO_CREATE;
+import static android.content.Context.BIND_NOT_FOREGROUND;
+import static android.content.Context.BIND_NOT_VISIBLE;
+import static android.content.pm.PackageManager.GET_META_DATA;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+
+import android.annotation.BoolRes;
 import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -24,11 +36,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
-import android.content.pm.Signature;
 import android.content.res.Resources;
 import android.os.Bundle;
 import android.os.Handler;
@@ -37,15 +45,10 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.Log;
-import android.util.Slog;
 
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.util.Preconditions;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Callable;
@@ -55,16 +58,16 @@
 import java.util.concurrent.TimeoutException;
 
 /**
- * Find the best Service, and bind to it.
- * Handles run-time package changes.
+ * Maintains a binding to the best service that matches the given intent information. Bind and
+ * unbind callbacks, as well as all binder operations, will all be run on the given handler.
  */
 public class ServiceWatcher implements ServiceConnection {
 
     private static final String TAG = "ServiceWatcher";
-    private static final boolean D = false;
+    private static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
 
-    public static final String EXTRA_SERVICE_VERSION = "serviceVersion";
-    public static final String EXTRA_SERVICE_IS_MULTIUSER = "serviceIsMultiuser";
+    private static final String EXTRA_SERVICE_VERSION = "serviceVersion";
+    private static final String EXTRA_SERVICE_IS_MULTIUSER = "serviceIsMultiuser";
 
     private static final long BLOCKING_BINDER_TIMEOUT_MS = 30 * 1000;
 
@@ -83,280 +86,300 @@
         T run(IBinder binder) throws RemoteException;
     }
 
-    public static ArrayList<HashSet<Signature>> getSignatureSets(Context context,
-            String... packageNames) {
-        PackageManager pm = context.getPackageManager();
+    /**
+     * Information on the service ServiceWatcher has selected as the best option for binding.
+     */
+    public static final class ServiceInfo implements Comparable<ServiceInfo> {
 
-        ArrayList<HashSet<Signature>> signatureSets = new ArrayList<>(packageNames.length);
-        for (String packageName : packageNames) {
-            try {
-                Signature[] signatures = pm.getPackageInfo(packageName,
-                        PackageManager.MATCH_SYSTEM_ONLY
-                                | PackageManager.GET_SIGNATURES).signatures;
+        public static final ServiceInfo NONE = new ServiceInfo(Integer.MIN_VALUE, null,
+                UserHandle.USER_NULL);
 
-                HashSet<Signature> set = new HashSet<>();
-                Collections.addAll(set, signatures);
-                signatureSets.add(set);
-            } catch (NameNotFoundException e) {
-                Log.w(TAG, packageName + " not found");
+        public final int version;
+        @Nullable public final ComponentName component;
+        @UserIdInt public final int userId;
+
+        private ServiceInfo(ResolveInfo resolveInfo, int currentUserId) {
+            Preconditions.checkArgument(resolveInfo.serviceInfo.getComponentName() != null);
+
+            Bundle metadata = resolveInfo.serviceInfo.metaData;
+            boolean isMultiuser;
+            if (metadata != null) {
+                version = metadata.getInt(EXTRA_SERVICE_VERSION, Integer.MIN_VALUE);
+                isMultiuser = metadata.getBoolean(EXTRA_SERVICE_IS_MULTIUSER, false);
+            } else {
+                version = Integer.MIN_VALUE;
+                isMultiuser = false;
             }
+
+            component = resolveInfo.serviceInfo.getComponentName();
+            userId = isMultiuser ? UserHandle.USER_SYSTEM : currentUserId;
         }
-        return signatureSets;
-    }
 
-    /** Checks if signatures match. */
-    public static boolean isSignatureMatch(Signature[] signatures,
-            List<HashSet<Signature>> sigSets) {
-        if (signatures == null) return false;
+        private ServiceInfo(int version, @Nullable ComponentName component, int userId) {
+            Preconditions.checkArgument(component != null || version == Integer.MIN_VALUE);
+            this.version = version;
+            this.component = component;
+            this.userId = userId;
+        }
 
-        // build hashset of input to test against
-        HashSet<Signature> inputSet = new HashSet<>();
-        Collections.addAll(inputSet, signatures);
-
-        // test input against each of the signature sets
-        for (HashSet<Signature> referenceSet : sigSets) {
-            if (referenceSet.equals(inputSet)) {
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
                 return true;
             }
+            if (!(o instanceof ServiceInfo)) {
+                return false;
+            }
+            ServiceInfo that = (ServiceInfo) o;
+            return version == that.version && userId == that.userId
+                    && Objects.equals(component, that.component);
         }
-        return false;
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(version, component, userId);
+        }
+
+        @Override
+        public int compareTo(ServiceInfo that) {
+            // ServiceInfos with higher version numbers always win (having a version number >
+            // MIN_VALUE implies having a non-null component). if version numbers are equal, a
+            // non-null component wins over a null component. if the version numbers are equal and
+            // both components exist then we prefer components that work for all users vs components
+            // that only work for a single user at a time. otherwise everything's equal.
+            int ret = Integer.compare(version, that.version);
+            if (ret == 0) {
+                if (component == null && that.component != null) {
+                    ret = -1;
+                } else if (component != null && that.component == null) {
+                    ret = 1;
+                } else {
+                    if (userId != UserHandle.USER_SYSTEM && that.userId == UserHandle.USER_SYSTEM) {
+                        ret = -1;
+                    } else if (userId == UserHandle.USER_SYSTEM
+                            && that.userId != UserHandle.USER_SYSTEM) {
+                        ret = 1;
+                    }
+                }
+            }
+            return ret;
+        }
+
+        @Override
+        public String toString() {
+            return component + "@" + version + "[u" + userId + "]";
+        }
     }
 
     private final Context mContext;
-    private final String mTag;
-    private final String mAction;
-    private final String mServicePackageName;
-    private final List<HashSet<Signature>> mSignatureSets;
-
     private final Handler mHandler;
+    private final Intent mIntent;
 
-    // read/write from handler thread
-    private IBinder mBestService;
+    @Nullable private final BinderRunner mOnBind;
+    @Nullable private final Runnable mOnUnbind;
+
+    // read/write from handler thread only
     private int mCurrentUserId;
 
-    // read from any thread, write from handler thread
-    private volatile ComponentName mBestComponent;
-    private volatile int mBestVersion;
-    private volatile int mBestUserId;
+    // write from handler thread only, read anywhere
+    private volatile ServiceInfo mServiceInfo;
 
-    public ServiceWatcher(Context context, String logTag, String action,
-            int overlaySwitchResId, int defaultServicePackageNameResId,
-            int initialPackageNamesResId, Handler handler) {
-        Resources resources = context.getResources();
+    // read/write from handler thread only
+    private IBinder mBinder;
 
+    public ServiceWatcher(Context context, Handler handler, String action,
+            @Nullable BinderRunner onBind, @Nullable Runnable onUnbind,
+            @BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId) {
         mContext = context;
-        mTag = logTag;
-        mAction = action;
+        mHandler = FgThread.getHandler();
+        mIntent = new Intent(Objects.requireNonNull(action));
 
-        boolean enableOverlay = resources.getBoolean(overlaySwitchResId);
-        if (enableOverlay) {
-            String[] pkgs = resources.getStringArray(initialPackageNamesResId);
-            mServicePackageName = null;
-            mSignatureSets = getSignatureSets(context, pkgs);
-            if (D) Log.d(mTag, "Overlay enabled, packages=" + Arrays.toString(pkgs));
-        } else {
-            mServicePackageName = resources.getString(defaultServicePackageNameResId);
-            mSignatureSets = getSignatureSets(context, mServicePackageName);
-            if (D) Log.d(mTag, "Overlay disabled, default package=" + mServicePackageName);
+        Resources resources = context.getResources();
+        boolean enableOverlay = resources.getBoolean(enableOverlayResId);
+        if (!enableOverlay) {
+            mIntent.setPackage(resources.getString(nonOverlayPackageResId));
         }
 
-        mHandler = handler;
+        mOnBind = onBind;
+        mOnUnbind = onUnbind;
 
-        mBestComponent = null;
-        mBestVersion = Integer.MIN_VALUE;
-        mBestUserId = UserHandle.USER_NULL;
+        mCurrentUserId = UserHandle.USER_NULL;
 
-        mBestService = null;
+        mServiceInfo = ServiceInfo.NONE;
+        mBinder = null;
     }
 
-    protected void onBind() {}
-
-    protected void onUnbind() {}
-
     /**
-     * Start this watcher, including binding to the current best match and
-     * re-binding to any better matches down the road.
-     * <p>
-     * Note that if there are no matching encryption-aware services, we may not
-     * bind to a real service until after the current user is unlocked.
-     *
-     * @return {@code true} if a potential service implementation was found.
+     * Register this class, which will start the process of determining the best matching service
+     * and maintaining a binding to it. Will return false and fail if there are no possible matching
+     * services at the time this functions is called.
      */
-    public final boolean start() {
-        // if we have to return false, do it before registering anything
-        if (isServiceMissing()) return false;
-
-        // listen for relevant package changes if service overlay is enabled on handler
-        if (mServicePackageName == null) {
-            new PackageMonitor() {
-                @Override
-                public void onPackageUpdateFinished(String packageName, int uid) {
-                    bindBestPackage(Objects.equals(packageName, getCurrentPackageName()));
-                }
-
-                @Override
-                public void onPackageAdded(String packageName, int uid) {
-                    bindBestPackage(Objects.equals(packageName, getCurrentPackageName()));
-                }
-
-                @Override
-                public void onPackageRemoved(String packageName, int uid) {
-                    bindBestPackage(Objects.equals(packageName, getCurrentPackageName()));
-                }
-
-                @Override
-                public boolean onPackageChanged(String packageName, int uid, String[] components) {
-                    bindBestPackage(Objects.equals(packageName, getCurrentPackageName()));
-                    return super.onPackageChanged(packageName, uid, components);
-                }
-            }.register(mContext, UserHandle.ALL, true, mHandler);
+    public boolean register() {
+        if (mContext.getPackageManager().queryIntentServicesAsUser(mIntent,
+                MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE | MATCH_SYSTEM_ONLY,
+                UserHandle.USER_SYSTEM).isEmpty()) {
+            return false;
         }
 
-        // listen for user change on handler
+        new PackageMonitor() {
+            @Override
+            public void onPackageUpdateFinished(String packageName, int uid) {
+                ServiceWatcher.this.onPackageChanged(packageName);
+            }
+
+            @Override
+            public void onPackageAdded(String packageName, int uid) {
+                ServiceWatcher.this.onPackageChanged(packageName);
+            }
+
+            @Override
+            public void onPackageRemoved(String packageName, int uid) {
+                ServiceWatcher.this.onPackageChanged(packageName);
+            }
+
+            @Override
+            public boolean onPackageChanged(String packageName, int uid, String[] components) {
+                ServiceWatcher.this.onPackageChanged(packageName);
+                return super.onPackageChanged(packageName, uid, components);
+            }
+        }.register(mContext, UserHandle.ALL, true, mHandler);
+
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
         intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
         mContext.registerReceiverAsUser(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
-                final String action = intent.getAction();
-                final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
-                        UserHandle.USER_NULL);
-                if (Intent.ACTION_USER_SWITCHED.equals(action)) {
-                    mCurrentUserId = userId;
-                    bindBestPackage(false);
-                } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
-                    if (userId == mCurrentUserId) {
-                        bindBestPackage(false);
-                    }
+                String action = intent.getAction();
+                if (action == null) {
+                    return;
                 }
+                int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+                if (userId == UserHandle.USER_NULL) {
+                    return;
+                }
+
+                switch (action) {
+                    case Intent.ACTION_USER_SWITCHED:
+                        onUserSwitched(userId);
+                        break;
+                    case Intent.ACTION_USER_UNLOCKED:
+                        onUserUnlocked(userId);
+                        break;
+                    default:
+                        break;
+                }
+
             }
         }, UserHandle.ALL, intentFilter, null, mHandler);
 
         mCurrentUserId = ActivityManager.getCurrentUser();
 
-        mHandler.post(() -> bindBestPackage(false));
+        mHandler.post(() -> onBestServiceChanged(false));
         return true;
     }
 
-    /** Returns the name of the currently connected package or null. */
-    @Nullable
-    public String getCurrentPackageName() {
-        ComponentName bestComponent = mBestComponent;
-        return bestComponent == null ? null : bestComponent.getPackageName();
+    /**
+     * Returns information on the currently selected service.
+     */
+    public ServiceInfo getBoundService() {
+        return mServiceInfo;
     }
 
-    private boolean isServiceMissing() {
-        return mContext.getPackageManager().queryIntentServicesAsUser(new Intent(mAction),
-                PackageManager.MATCH_DIRECT_BOOT_AWARE
-                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
-                UserHandle.USER_SYSTEM).isEmpty();
-    }
-
-    private void bindBestPackage(boolean forceRebind) {
+    private void onBestServiceChanged(boolean forceRebind) {
         Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
 
-        Intent intent = new Intent(mAction);
-        if (mServicePackageName != null) {
-            intent.setPackage(mServicePackageName);
-        }
-
-        List<ResolveInfo> rInfos = mContext.getPackageManager().queryIntentServicesAsUser(intent,
-                PackageManager.GET_META_DATA | PackageManager.MATCH_DIRECT_BOOT_AUTO,
+        List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServicesAsUser(
+                mIntent,
+                GET_META_DATA | MATCH_DIRECT_BOOT_AUTO | MATCH_SYSTEM_ONLY,
                 mCurrentUserId);
-        if (rInfos == null) {
-            rInfos = Collections.emptyList();
-        }
 
-        ComponentName bestComponent = null;
-        int bestVersion = Integer.MIN_VALUE;
-        boolean bestIsMultiuser = false;
-
-        for (ResolveInfo rInfo : rInfos) {
-            ComponentName component = rInfo.serviceInfo.getComponentName();
-            String packageName = component.getPackageName();
-
-            // check signature
-            try {
-                PackageInfo pInfo = mContext.getPackageManager().getPackageInfo(packageName,
-                        PackageManager.GET_SIGNATURES
-                                | PackageManager.MATCH_DIRECT_BOOT_AUTO);
-                if (!isSignatureMatch(pInfo.signatures, mSignatureSets)) {
-                    Log.w(mTag, packageName + " resolves service " + mAction
-                            + ", but has wrong signature, ignoring");
-                    continue;
-                }
-            } catch (NameNotFoundException e) {
-                Log.wtf(mTag, e);
-                continue;
-            }
-
-            // check metadata
-            Bundle metadata = rInfo.serviceInfo.metaData;
-            int version = Integer.MIN_VALUE;
-            boolean isMultiuser = false;
-            if (metadata != null) {
-                version = metadata.getInt(EXTRA_SERVICE_VERSION, Integer.MIN_VALUE);
-                isMultiuser = metadata.getBoolean(EXTRA_SERVICE_IS_MULTIUSER, false);
-            }
-
-            if (version > bestVersion) {
-                bestComponent = component;
-                bestVersion = version;
-                bestIsMultiuser = isMultiuser;
+        ServiceInfo bestServiceInfo = ServiceInfo.NONE;
+        for (ResolveInfo resolveInfo : resolveInfos) {
+            ServiceInfo serviceInfo = new ServiceInfo(resolveInfo, mCurrentUserId);
+            if (serviceInfo.compareTo(bestServiceInfo) > 0) {
+                bestServiceInfo = serviceInfo;
             }
         }
 
-        if (D) {
-            Log.d(mTag, String.format("bindBestPackage for %s : %s found %d, %s", mAction,
-                    (mServicePackageName == null ? ""
-                            : "(" + mServicePackageName + ") "), rInfos.size(),
-                    (bestComponent == null ? "no new best component"
-                            : "new best component: " + bestComponent)));
+        if (forceRebind || !bestServiceInfo.equals(mServiceInfo)) {
+            rebind(bestServiceInfo);
+        }
+    }
+
+    private void rebind(ServiceInfo newServiceInfo) {
+        Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+
+        if (!mServiceInfo.equals(ServiceInfo.NONE)) {
+            if (D) {
+                Log.i(TAG, "[" + mIntent.getAction() + "] unbinding from " + mServiceInfo);
+            }
+
+            mContext.unbindService(this);
+            mServiceInfo = ServiceInfo.NONE;
         }
 
-        if (bestComponent == null) {
-            Slog.w(mTag, "Odd, no component found for service " + mAction);
-            unbind();
+        mServiceInfo = newServiceInfo;
+        if (mServiceInfo.equals(ServiceInfo.NONE)) {
             return;
         }
 
-        int userId = bestIsMultiuser ? UserHandle.USER_SYSTEM : mCurrentUserId;
-        boolean alreadyBound = Objects.equals(bestComponent, mBestComponent)
-                && bestVersion == mBestVersion && userId == mBestUserId;
-        if (forceRebind || !alreadyBound) {
-            unbind();
-            bind(bestComponent, bestVersion, userId);
+        Preconditions.checkState(mServiceInfo.component != null);
+
+        if (D) {
+            Log.i(TAG, getLogPrefix() + " binding to " + mServiceInfo);
+        }
+
+        Intent bindIntent = new Intent(mIntent).setComponent(mServiceInfo.component);
+        mContext.bindServiceAsUser(bindIntent, this,
+                BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE,
+                mHandler, UserHandle.of(mServiceInfo.userId));
+    }
+
+    @Override
+    public final void onServiceConnected(ComponentName component, IBinder binder) {
+        Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+
+        if (D) {
+            Log.i(TAG, getLogPrefix() + " connected to " + component);
+        }
+
+        mBinder = binder;
+        if (mOnBind != null) {
+            runOnBinder(mOnBind);
         }
     }
 
-    private void bind(ComponentName component, int version, int userId) {
+    @Override
+    public final void onServiceDisconnected(ComponentName component) {
         Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
 
-        Intent intent = new Intent(mAction);
-        intent.setComponent(component);
-
-        mBestComponent = component;
-        mBestVersion = version;
-        mBestUserId = userId;
-
-        if (D) Log.d(mTag, "binding " + component + " (v" + version + ") (u" + userId + ")");
-        mContext.bindServiceAsUser(intent, this,
-                Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | Context.BIND_NOT_VISIBLE,
-                UserHandle.of(userId));
-    }
-
-    private void unbind() {
-        Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-
-        if (mBestComponent != null) {
-            if (D) Log.d(mTag, "unbinding " + mBestComponent);
-            mContext.unbindService(this);
+        if (D) {
+            Log.i(TAG, getLogPrefix() + " disconnected from " + component);
         }
 
-        mBestComponent = null;
-        mBestVersion = Integer.MIN_VALUE;
-        mBestUserId = UserHandle.USER_NULL;
+        mBinder = null;
+        if (mOnUnbind != null) {
+            mOnUnbind.run();
+        }
+    }
+
+    private void onUserSwitched(@UserIdInt int userId) {
+        mCurrentUserId = userId;
+        onBestServiceChanged(false);
+    }
+
+    private void onUserUnlocked(@UserIdInt int userId) {
+        if (userId == mCurrentUserId) {
+            onBestServiceChanged(false);
+        }
+    }
+
+    private void onPackageChanged(String packageName) {
+        // force a rebind if the changed package was the currently connected package
+        String currentPackageName =
+                mServiceInfo.component != null ? mServiceInfo.component.getPackageName() : null;
+        onBestServiceChanged(packageName.equals(currentPackageName));
     }
 
     /**
@@ -365,26 +388,26 @@
      */
     public final void runOnBinder(BinderRunner runner) {
         runOnHandler(() -> {
-            if (mBestService == null) {
+            if (mBinder == null) {
                 return;
             }
 
             try {
-                runner.run(mBestService);
-            } catch (RuntimeException e) {
-                // the code being run is privileged, but may be outside the system server, and thus
-                // we cannot allow runtime exceptions to crash the system server
-                Log.e(TAG, "exception while while running " + runner + " on " + mBestService
-                        + " from " + this, e);
-            } catch (RemoteException e) {
-                // do nothing
+                runner.run(mBinder);
+            } catch (RuntimeException | RemoteException e) {
+                // binders may propagate some specific non-RemoteExceptions from the other side
+                // through the binder as well - we cannot allow those to crash the system server
+                Log.e(TAG, getLogPrefix() + " exception running on " + mServiceInfo, e);
             }
         });
     }
 
     /**
      * Runs the given function synchronously if currently connected, and returns the default value
-     * if not currently connected or if any exception is thrown.
+     * if not currently connected or if any exception is thrown. Do not obtain any locks within the
+     * BlockingBinderRunner, or risk deadlock. The default value will be returned if there is no
+     * service connection when this is run, if a RemoteException occurs, or if the operation times
+     * out.
      *
      * @deprecated Using this function is an indication that your AIDL API is broken. Calls from
      * system server to outside MUST be one-way, and so cannot return any result, and this
@@ -395,13 +418,16 @@
     public final <T> T runOnBinderBlocking(BlockingBinderRunner<T> runner, T defaultValue) {
         try {
             return runOnHandlerBlocking(() -> {
-                if (mBestService == null) {
+                if (mBinder == null) {
                     return defaultValue;
                 }
 
                 try {
-                    return runner.run(mBestService);
-                } catch (RemoteException e) {
+                    return runner.run(mBinder);
+                } catch (RuntimeException | RemoteException e) {
+                    // binders may propagate some specific non-RemoteExceptions from the other side
+                    // through the binder as well - we cannot allow those to crash the system server
+                    Log.e(TAG, getLogPrefix() + " exception running on " + mServiceInfo, e);
                     return defaultValue;
                 }
             });
@@ -410,30 +436,6 @@
         }
     }
 
-    @Override
-    public final void onServiceConnected(ComponentName component, IBinder binder) {
-        runOnHandler(() -> {
-            if (D) Log.d(mTag, component + " connected");
-            mBestService = binder;
-            onBind();
-        });
-    }
-
-    @Override
-    public final void onServiceDisconnected(ComponentName component) {
-        runOnHandler(() -> {
-            if (D) Log.d(mTag, component + " disconnected");
-            mBestService = null;
-            onUnbind();
-        });
-    }
-
-    @Override
-    public String toString() {
-        ComponentName bestComponent = mBestComponent;
-        return bestComponent == null ? "null" : bestComponent.toShortString() + "@" + mBestVersion;
-    }
-
     private void runOnHandler(Runnable r) {
         if (Looper.myLooper() == mHandler.getLooper()) {
             r.run();
@@ -467,4 +469,8 @@
             }
         }
     }
+
+    private String getLogPrefix() {
+        return "[" + mIntent.getAction() + "]";
+    }
 }
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 3d455ee..4b4ce34 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -16,6 +16,7 @@
 
 package com.android.server;
 
+import static android.Manifest.permission.ACCESS_MTP;
 import static android.Manifest.permission.INSTALL_PACKAGES;
 import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
 import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
@@ -36,6 +37,8 @@
 import static android.os.storage.OnObbStateChangeListener.ERROR_PERMISSION_DENIED;
 import static android.os.storage.OnObbStateChangeListener.MOUNTED;
 import static android.os.storage.OnObbStateChangeListener.UNMOUNTED;
+import static android.os.storage.StorageManager.PROP_FUSE;
+import static android.os.storage.StorageManager.PROP_SETTINGS_FUSE;
 
 import static com.android.internal.util.XmlUtils.readIntAttribute;
 import static com.android.internal.util.XmlUtils.readLongAttribute;
@@ -43,6 +46,7 @@
 import static com.android.internal.util.XmlUtils.writeIntAttribute;
 import static com.android.internal.util.XmlUtils.writeLongAttribute;
 import static com.android.internal.util.XmlUtils.writeStringAttribute;
+import static com.android.server.storage.StorageUserConnection.REMOTE_TIMEOUT_SECONDS;
 
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
@@ -111,6 +115,7 @@
 import android.os.storage.VolumeInfo;
 import android.os.storage.VolumeRecord;
 import android.provider.DeviceConfig;
+import android.provider.Downloads;
 import android.provider.MediaStore;
 import android.provider.Settings;
 import android.sysprop.VoldProperties;
@@ -367,6 +372,8 @@
 
     private volatile int mMediaStoreAuthorityAppId = -1;
 
+    private volatile int mDownloadsAuthorityAppId = -1;
+
     private volatile int mCurrentUserId = UserHandle.USER_SYSTEM;
 
     private final Installer mInstaller;
@@ -1612,7 +1619,10 @@
         SystemProperties.set(StorageManager.PROP_ISOLATED_STORAGE_SNAPSHOT, Boolean.toString(
                 SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, true)));
 
-        mIsFuseEnabled = SystemProperties.getBoolean(StorageManager.PROP_FUSE, false);
+        // If there is no value in the property yet (first boot after data wipe), this value may be
+        // incorrect until #updateFusePropFromSettings where we set the correct value and reboot if
+        // different
+        mIsFuseEnabled = SystemProperties.getBoolean(PROP_FUSE, false);
         mContext = context;
         mResolver = mContext.getContentResolver();
         mCallbacks = new Callbacks(FgThread.get().getLooper());
@@ -1675,25 +1685,31 @@
      *  and updates PROP_FUSE (reboots if changed).
      */
     private void updateFusePropFromSettings() {
-        String settingsFuseFlag = SystemProperties.get(StorageManager.PROP_SETTINGS_FUSE);
-        Slog.d(TAG, "The value of Settings Fuse Flag is "
-                + (settingsFuseFlag == null || settingsFuseFlag.isEmpty()
-                ? "null" : settingsFuseFlag));
-        // Set default value of PROP_SETTINGS_FUSE and PROP_FUSE if it
-        // is unset (neither true nor false, this happens only on the first boot
-        // after wiping data partition).
-        if (settingsFuseFlag == null || settingsFuseFlag.isEmpty()) {
-            SystemProperties.set(StorageManager.PROP_SETTINGS_FUSE, "false");
-            SystemProperties.set(StorageManager.PROP_FUSE, "false");
+        boolean defaultFuseFlag = false;
+        boolean settingsFuseFlag = SystemProperties.getBoolean(PROP_SETTINGS_FUSE, defaultFuseFlag);
+        Slog.d(TAG, "FUSE flags. Settings: " + settingsFuseFlag + ". Default: " + defaultFuseFlag);
+
+        if (TextUtils.isEmpty(SystemProperties.get(PROP_SETTINGS_FUSE))) {
+            // Set default value of PROP_SETTINGS_FUSE and PROP_FUSE if it
+            // is unset (neither true nor false).
+            // This happens only on the first boot after wiping data partition
+            SystemProperties.set(PROP_SETTINGS_FUSE, Boolean.toString(defaultFuseFlag));
+            SystemProperties.set(PROP_FUSE, Boolean.toString(defaultFuseFlag));
             return;
         }
 
-        if (!SystemProperties.get(StorageManager.PROP_FUSE).equals(settingsFuseFlag)) {
-            Slog.d(TAG, "Set persist.sys.fuse to " + settingsFuseFlag);
-            SystemProperties.set(StorageManager.PROP_FUSE, settingsFuseFlag);
-            // Perform hard reboot to kick policy into place
-            mContext.getSystemService(PowerManager.class).reboot("Reboot device for FUSE system"
-                    + "property change to take effect");
+        if (mIsFuseEnabled != settingsFuseFlag) {
+            Slog.i(TAG, "Toggling persist.sys.fuse to " + settingsFuseFlag);
+            SystemProperties.set(PROP_FUSE, Boolean.toString(settingsFuseFlag));
+
+            PowerManager powerManager = mContext.getSystemService(PowerManager.class);
+            if (powerManager.isRebootingUserspaceSupported()) {
+                // Perform userspace reboot to kick policy into place
+                powerManager.reboot(PowerManager.REBOOT_USERSPACE);
+            } else {
+                // Perform hard reboot to kick policy into place
+                powerManager.reboot("fuse_prop");
+            }
         }
     }
 
@@ -1788,6 +1804,15 @@
             mMediaStoreAuthorityAppId = UserHandle.getAppId(provider.applicationInfo.uid);
         }
 
+        provider = mPmInternal.resolveContentProvider(
+                Downloads.Impl.AUTHORITY, PackageManager.MATCH_DIRECT_BOOT_AWARE
+                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+                UserHandle.getUserId(UserHandle.USER_SYSTEM));
+
+        if (provider != null) {
+            mDownloadsAuthorityAppId = UserHandle.getAppId(provider.applicationInfo.uid);
+        }
+
         try {
             mIAppOpsService.startWatchingMode(OP_REQUEST_INSTALL_PACKAGES, null, mAppOpsCallback);
             mIAppOpsService.startWatchingMode(OP_LEGACY_STORAGE, null, mAppOpsCallback);
@@ -1972,16 +1997,29 @@
             Slog.i(TAG, "Mounting volume " + vol);
             mVold.mount(vol.id, vol.mountFlags, vol.mountUserId, new IVoldMountCallback.Stub() {
                     @Override
-                    public boolean onVolumeChecking(FileDescriptor deviceFd, String path,
+                    public boolean onVolumeChecking(FileDescriptor fd, String path,
                             String internalPath) {
                         vol.path = path;
                         vol.internalPath = internalPath;
+                        ParcelFileDescriptor pfd = new ParcelFileDescriptor(fd);
                         try {
-                            mStorageSessionController.onVolumeMount(deviceFd, vol);
+                            mStorageSessionController.onVolumeMount(pfd, vol);
                             return true;
                         } catch (ExternalStorageServiceException e) {
-                            Slog.i(TAG, "Failed to mount volume " + vol, e);
+                            Slog.e(TAG, "Failed to mount volume " + vol, e);
+
+                            int nextResetSeconds = REMOTE_TIMEOUT_SECONDS * 2;
+                            Slog.i(TAG, "Scheduling reset in " + nextResetSeconds + "s");
+                            mHandler.removeMessages(H_RESET);
+                            mHandler.sendMessageDelayed(mHandler.obtainMessage(H_RESET),
+                                    TimeUnit.SECONDS.toMillis(nextResetSeconds));
                             return false;
+                        } finally {
+                            try {
+                                pfd.close();
+                            } catch (Exception e) {
+                                Slog.e(TAG, "Failed to close FUSE device fd", e);
+                            }
                         }
                     }
                 });
@@ -2842,8 +2880,10 @@
      */
     @Override
     public void startCheckpoint(int numTries) throws RemoteException {
-        // Only the system process is permitted to start checkpoints
-        if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) {
+        // Only the root, system_server and shell processes are permitted to start checkpoints
+        final int callingUid = Binder.getCallingUid();
+        if (callingUid != Process.SYSTEM_UID && callingUid != Process.ROOT_UID
+                && callingUid != Process.SHELL_UID) {
             throw new SecurityException("no permission to start filesystem checkpoint");
         }
 
@@ -2961,6 +3001,24 @@
     }
 
     /*
+     * Clear disk encryption key bound to the associated token / secret pair. Removing the user
+     * binding of the Disk encryption key is done in two phases: first, this call will retrieve
+     * the disk encryption key using the provided token / secret pair and store it by
+     * encrypting it with a keymaster key not bound to the user, then fixateNewestUserKeyAuth
+     * is called to delete all other bindings of the disk encryption key.
+     */
+    @Override
+    public void clearUserKeyAuth(int userId, int serialNumber, byte[] token, byte[] secret) {
+        enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
+
+        try {
+            mVold.clearUserKeyAuth(userId, serialNumber, encodeBytes(token), encodeBytes(secret));
+        } catch (Exception e) {
+            Slog.wtf(TAG, e);
+        }
+    }
+
+    /*
      * Delete all disk encryption token/secret pairs except the most recently added one
      */
     @Override
@@ -3881,6 +3939,19 @@
                 return Zygote.MOUNT_EXTERNAL_PASS_THROUGH;
             }
 
+            if (mIsFuseEnabled && mDownloadsAuthorityAppId == UserHandle.getAppId(uid)) {
+                // DownloadManager can write in app-private directories on behalf of apps;
+                // give it write access to Android/
+                return Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE;
+            }
+
+            final boolean hasMtp = mIPackageManager.checkUidPermission(ACCESS_MTP, uid) ==
+                    PERMISSION_GRANTED;
+            if (mIsFuseEnabled && hasMtp) {
+                // The process hosting the MTP server should be able to write in Android/
+                return Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE;
+            }
+
             // Determine if caller is holding runtime permission
             final boolean hasRead = StorageManager.checkPermissionAndCheckOp(mContext, false, 0,
                     uid, packageName, READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE);
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index c5409f85..b1584fe 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -18,18 +18,26 @@
 
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.SystemApi.Client;
+import android.annotation.SystemApi.Process;
 import android.annotation.UserIdInt;
 import android.app.ActivityThread;
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.os.IBinder;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.os.UserManager;
 
 import com.android.server.pm.UserManagerService;
 
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -57,15 +65,17 @@
  *
  * {@hide}
  */
+@SystemApi(client = Client.MODULE_LIBRARIES, process = Process.SYSTEM_SERVER)
 public abstract class SystemService {
 
+    /** @hide */
     // TODO(b/133242016) STOPSHIP: change to false before R ships
     protected static final boolean DEBUG_USER = true;
 
     /*
-     * Boot Phases
+     * The earliest boot phase the system send to system services on boot.
      */
-    public static final int PHASE_WAIT_FOR_DEFAULT_DISPLAY = 100; // maybe should be a dependency?
+    public static final int PHASE_WAIT_FOR_DEFAULT_DISPLAY = 100;
 
     /**
      * After receiving this boot phase, services can obtain lock settings data.
@@ -98,13 +108,70 @@
      * After receiving this boot phase, services can allow user interaction with the device.
      * This phase occurs when boot has completed and the home application has started.
      * System services may prefer to listen to this phase rather than registering a
-     * broadcast receiver for ACTION_BOOT_COMPLETED to reduce overall latency.
+     * broadcast receiver for {@link android.content.Intent#ACTION_LOCKED_BOOT_COMPLETED}
+     * to reduce overall latency.
      */
     public static final int PHASE_BOOT_COMPLETED = 1000;
 
+    /** @hide */
+    @IntDef(flag = true, prefix = { "PHASE_" }, value = {
+            PHASE_WAIT_FOR_DEFAULT_DISPLAY,
+            PHASE_LOCK_SETTINGS_READY,
+            PHASE_SYSTEM_SERVICES_READY,
+            PHASE_DEVICE_SPECIFIC_SERVICES_READY,
+            PHASE_ACTIVITY_MANAGER_READY,
+            PHASE_THIRD_PARTY_APPS_CAN_START,
+            PHASE_BOOT_COMPLETED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface BootPhase {}
+
     private final Context mContext;
 
     /**
+     * Class representing user in question in the lifecycle callbacks.
+     * @hide
+     */
+    @SystemApi(client = Client.MODULE_LIBRARIES, process = Process.SYSTEM_SERVER)
+    public static final class TargetUser {
+        @NonNull
+        private final UserInfo mUserInfo;
+
+        /** @hide */
+        public TargetUser(@NonNull UserInfo userInfo) {
+            mUserInfo = userInfo;
+        }
+
+        /**
+         * @return The information about the user. <b>NOTE: </b> this is a "live" object
+         * referenced by {@link UserManagerService} and hence should not be modified.
+         *
+         * @hide
+         */
+        @NonNull
+        public UserInfo getUserInfo() {
+            return mUserInfo;
+        }
+
+        /**
+         * @return the target {@link UserHandle}.
+         */
+        @NonNull
+        public UserHandle getUserHandle() {
+            return mUserInfo.getUserHandle();
+        }
+
+        /**
+         * @return the integer user id
+         *
+         * @hide
+         */
+        public int getUserIdentifier() {
+            return mUserInfo.id;
+        }
+    }
+
+    /**
      * Initializes the system service.
      * <p>
      * Subclasses must define a single argument constructor that accepts the context
@@ -113,13 +180,14 @@
      *
      * @param context The system server context.
      */
-    public SystemService(Context context) {
+    public SystemService(@NonNull Context context) {
         mContext = context;
     }
 
     /**
      * Gets the system context.
      */
+    @NonNull
     public final Context getContext() {
         return mContext;
     }
@@ -128,6 +196,8 @@
      * Get the system UI context. This context is to be used for displaying UI. It is themable,
      * which means resources can be overridden at runtime. Do not use to retrieve properties that
      * configure the behavior of the device that is not UX related.
+     *
+     * @hide
      */
     public final Context getUiContext() {
         // This has already been set up by the time any SystemServices are created.
@@ -137,15 +207,16 @@
     /**
      * Returns true if the system is running in safe mode.
      * TODO: we should define in which phase this becomes valid
+     *
+     * @hide
      */
     public final boolean isSafeMode() {
         return getManager().isSafeMode();
     }
 
     /**
-     * Called when the dependencies listed in the @Service class-annotation are available
-     * and after the chosen start phase.
-     * When this method returns, the service should be published.
+     * Called when the system service should publish a binder service using
+     * {@link #publishBinderService(String, IBinder).}
      */
     public abstract void onStart();
 
@@ -155,7 +226,7 @@
      *
      * @param phase The current boot phase.
      */
-    public void onBootPhase(int phase) {}
+    public void onBootPhase(@BootPhase int phase) {}
 
     /**
      * Checks if the service should be available for the given user.
@@ -163,12 +234,14 @@
      * <p>By default returns {@code true}, but subclasses should extend for optimization, if they
      * don't support some types (like headless system user).
      */
-    public boolean isSupported(@NonNull UserInfo userInfo) {
+    public boolean isSupportedUser(@NonNull TargetUser user) {
         return true;
     }
 
     /**
-     * Helper method used to dump which users are {@link #onStartUser(UserInfo) supported}.
+     * Helper method used to dump which users are {@link #onStartUser(TargetUser) supported}.
+     *
+     * @hide
      */
     protected void dumpSupportedUsers(@NonNull PrintWriter pw, @NonNull String prefix) {
         final List<UserInfo> allUsers = UserManager.get(mContext).getUsers();
@@ -187,34 +260,59 @@
     }
 
     /**
-     * @deprecated subclasses should extend {@link #onStartUser(UserInfo)} instead (which by default
-     * calls this method).
+     * @deprecated subclasses should extend {@link #onStartUser(TargetUser)} instead
+     * (which by default calls this method).
+     *
+     * @hide
      */
     @Deprecated
     public void onStartUser(@UserIdInt int userId) {}
 
     /**
-     * Called when a new user is starting, for system services to initialize any per-user
-     * state they maintain for running users.
+     * @deprecated subclasses should extend {@link #onStartUser(TargetUser)} instead
+     * (which by default calls this method).
      *
-     * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} this
-     * user.
-     *
-     * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object
-     * referenced by {@link UserManagerService} and hence should not be modified.
+     * @hide
      */
+    @Deprecated
     public void onStartUser(@NonNull UserInfo userInfo) {
         onStartUser(userInfo.id);
     }
 
     /**
-     * @deprecated subclasses should extend {@link #onUnlockUser(UserInfo)} instead (which by
+     * Called when a new user is starting, for system services to initialize any per-user
+     * state they maintain for running users.
+     *
+     * <p>This method is only called when the service {@link #isSupportedUser(TargetUser) supports}
+     * this user.
+     *
+     * @param user target user
+     */
+    public void onStartUser(@NonNull TargetUser user) {
+        onStartUser(user.getUserInfo());
+    }
+
+    /**
+     * @deprecated subclasses should extend {@link #onUnlockUser(TargetUser)} instead (which by
      * default calls this method).
+     *
+     * @hide
      */
     @Deprecated
     public void onUnlockUser(@UserIdInt int userId) {}
 
     /**
+     * @deprecated subclasses should extend {@link #onUnlockUser(TargetUser)} instead (which by
+     * default calls this method).
+     *
+     * @hide
+     */
+    @Deprecated
+    public void onUnlockUser(@NonNull UserInfo userInfo) {
+        onUnlockUser(userInfo.id);
+    }
+
+    /**
      * Called when an existing user is in the process of being unlocked. This
      * means the credential-encrypted storage for that user is now available,
      * and encryption-aware component filtering is no longer in effect.
@@ -226,90 +324,127 @@
      * {@link UserManager#isUserUnlockingOrUnlocked(int)} to handle both of
      * these states.
      * <p>
-     * This method is only called when the service {@link #isSupported(UserInfo) supports} this
-     * user.
+     * This method is only called when the service {@link #isSupportedUser(TargetUser) supports}
+     * this user.
      *
-     * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object
-     * referenced by {@link UserManagerService} and hence should not be modified.
+     * @param user target user
      */
-    public void onUnlockUser(@NonNull UserInfo userInfo) {
-        onUnlockUser(userInfo.id);
+    public void onUnlockUser(@NonNull TargetUser user) {
+        onUnlockUser(user.getUserInfo());
     }
 
     /**
-     * @deprecated subclasses should extend {@link #onSwitchUser(UserInfo, UserInfo)} instead
+     * @deprecated subclasses should extend {@link #onSwitchUser(TargetUser, TargetUser)} instead
      * (which by default calls this method).
+     *
+     * @hide
      */
     @Deprecated
-    public void onSwitchUser(@UserIdInt int userId) {}
+    public void onSwitchUser(@UserIdInt int toUserId) {}
+
+    /**
+     * @deprecated subclasses should extend {@link #onSwitchUser(TargetUser, TargetUser)} instead
+     * (which by default calls this method).
+     *
+     * @hide
+     */
+    @Deprecated
+    public void onSwitchUser(@Nullable UserInfo from, @NonNull UserInfo to) {
+        onSwitchUser(to.id);
+    }
 
     /**
      * Called when switching to a different foreground user, for system services that have
      * special behavior for whichever user is currently in the foreground.  This is called
      * before any application processes are aware of the new user.
      *
-     * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} either
-     * of the users ({@code from} or {@code to}).
+     * <p>This method is only called when the service {@link #isSupportedUser(TargetUser) supports}
+     * either of the users ({@code from} or {@code to}).
      *
      * <b>NOTE: </b> both {@code from} and {@code to} are "live" objects
      * referenced by {@link UserManagerService} and hence should not be modified.
      *
-     * @param from The information about the user being switched from.
-     * @param to The information about the user being switched from to.
+     * @param from the user switching from
+     * @param to the user switching to
      */
-    public void onSwitchUser(@NonNull UserInfo from, @NonNull UserInfo to) {
-        onSwitchUser(to.id);
+    public void onSwitchUser(@Nullable TargetUser from, @NonNull TargetUser to) {
+        onSwitchUser((from == null ? null : from.getUserInfo()), to.getUserInfo());
     }
 
     /**
-     * @deprecated subclasses should extend {@link #onStopUser(UserInfo)} instead (which by default
-     * calls this method).
+     * @deprecated subclasses should extend {@link #onStopUser(TargetUser)} instead
+     * (which by default calls this method).
+     *
+     * @hide
      */
     @Deprecated
     public void onStopUser(@UserIdInt int userId) {}
 
     /**
+     * @deprecated subclasses should extend {@link #onStopUser(TargetUser)} instead
+     * (which by default calls this method).
+     *
+     * @hide
+     */
+    @Deprecated
+    public void onStopUser(@NonNull UserInfo user) {
+        onStopUser(user.id);
+
+    }
+
+    /**
      * Called when an existing user is stopping, for system services to finalize any per-user
      * state they maintain for running users.  This is called prior to sending the SHUTDOWN
      * broadcast to the user; it is a good place to stop making use of any resources of that
      * user (such as binding to a service running in the user).
      *
-     * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} this
-     * user.
+     * <p>This method is only called when the service {@link #isSupportedUser(TargetUser) supports}
+     * this user.
      *
      * <p>NOTE: This is the last callback where the callee may access the target user's CE storage.
      *
-     * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object
-     * referenced by {@link UserManagerService} and hence should not be modified.
+     * @param user target user
      */
-    public void onStopUser(@NonNull UserInfo userInfo) {
-        onStopUser(userInfo.id);
+    public void onStopUser(@NonNull TargetUser user) {
+        onStopUser(user.getUserInfo());
     }
 
     /**
-     * @deprecated subclasses should extend {@link #onCleanupUser(UserInfo)} instead (which by
+     * @deprecated subclasses should extend {@link #onCleanupUser(TargetUser)} instead (which by
      * default calls this method).
+     *
+     * @hide
      */
     @Deprecated
     public void onCleanupUser(@UserIdInt int userId) {}
 
     /**
+     * @deprecated subclasses should extend {@link #onCleanupUser(TargetUser)} instead (which by
+     * default calls this method).
+     *
+     * @hide
+     */
+    @Deprecated
+    public void onCleanupUser(@NonNull UserInfo user) {
+        onCleanupUser(user.id);
+    }
+
+    /**
      * Called when an existing user is stopping, for system services to finalize any per-user
      * state they maintain for running users.  This is called after all application process
      * teardown of the user is complete.
      *
-     * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} this
-     * user.
+     * <p>This method is only called when the service {@link #isSupportedUser(TargetUser) supports}
+     * this user.
      *
      * <p>NOTE: When this callback is called, the CE storage for the target user may not be
-     * accessible already.  Use {@link #onStopUser(UserInfo)} instead if you need to access the CE
+     * accessible already.  Use {@link #onStopUser(TargetUser)} instead if you need to access the CE
      * storage.
      *
-     * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object
-     * referenced by {@link UserManagerService} and hence should not be modified.
+     * @param user target user
      */
-    public void onCleanupUser(@NonNull UserInfo userInfo) {
-        onCleanupUser(userInfo.id);
+    public void onCleanupUser(@NonNull TargetUser user) {
+        onCleanupUser(user.getUserInfo());
     }
 
     /**
@@ -318,7 +453,7 @@
      * @param name the name of the new service
      * @param service the service object
      */
-    protected final void publishBinderService(String name, IBinder service) {
+    protected final void publishBinderService(@NonNull String name, @NonNull IBinder service) {
         publishBinderService(name, service, false);
     }
 
@@ -330,7 +465,7 @@
      * @param allowIsolated set to true to allow isolated sandboxed processes
      * to access this service
      */
-    protected final void publishBinderService(String name, IBinder service,
+    protected final void publishBinderService(@NonNull String name, @NonNull IBinder service,
             boolean allowIsolated) {
         publishBinderService(name, service, allowIsolated, DUMP_FLAG_PRIORITY_DEFAULT);
     }
@@ -343,6 +478,8 @@
      * @param allowIsolated set to true to allow isolated sandboxed processes
      * to access this service
      * @param dumpPriority supported dump priority levels as a bitmask
+     *
+     * @hide
      */
     protected final void publishBinderService(String name, IBinder service,
             boolean allowIsolated, int dumpPriority) {
@@ -351,6 +488,8 @@
 
     /**
      * Get a binder service by its name.
+     *
+     * @hide
      */
     protected final IBinder getBinderService(String name) {
         return ServiceManager.getService(name);
@@ -358,6 +497,8 @@
 
     /**
      * Publish the service so it is only accessible to the system process.
+     *
+     * @hide
      */
     protected final <T> void publishLocalService(Class<T> type, T service) {
         LocalServices.addService(type, service);
@@ -365,6 +506,8 @@
 
     /**
      * Get a local service by interface.
+     *
+     * @hide
      */
     protected final <T> T getLocalService(Class<T> type) {
         return LocalServices.getService(type);
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index c715798..e7f7846 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -25,10 +25,14 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManagerInternal;
+import android.util.ArrayMap;
 import android.util.Slog;
 
+import com.android.server.SystemService.TargetUser;
 import com.android.server.utils.TimingsTraceAndSlog;
 
+import dalvik.system.PathClassLoader;
+
 import java.io.File;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
@@ -62,6 +66,9 @@
     // Services that should receive lifecycle events.
     private final ArrayList<SystemService> mServices = new ArrayList<SystemService>();
 
+    // Map of paths to PathClassLoader, so we don't load the same path multiple times.
+    private final ArrayMap<String, PathClassLoader> mLoadedPaths = new ArrayMap<>();
+
     private int mCurrentPhase = -1;
 
     private UserManagerInternal mUserManagerInternal;
@@ -75,20 +82,46 @@
      *
      * @return The service instance.
      */
-    @SuppressWarnings("unchecked")
     public SystemService startService(String className) {
-        final Class<SystemService> serviceClass;
+        final Class<SystemService> serviceClass = loadClassFromLoader(className,
+                this.getClass().getClassLoader());
+        return startService(serviceClass);
+    }
+
+    /**
+     * Starts a service by class name and a path that specifies the jar where the service lives.
+     *
+     * @return The service instance.
+     */
+    public SystemService startServiceFromJar(String className, String path) {
+        PathClassLoader pathClassLoader = mLoadedPaths.get(path);
+        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());
+            mLoadedPaths.put(path, pathClassLoader);
+        }
+        final Class<SystemService> serviceClass = loadClassFromLoader(className, pathClassLoader);
+        return startService(serviceClass);
+    }
+
+    /*
+     * Loads and initializes a class from the given classLoader. Returns the class.
+     */
+    @SuppressWarnings("unchecked")
+    private static Class<SystemService> loadClassFromLoader(String className,
+            ClassLoader classLoader) {
         try {
-            serviceClass = (Class<SystemService>)Class.forName(className);
+            return (Class<SystemService>) Class.forName(className, true, classLoader);
         } catch (ClassNotFoundException ex) {
-            Slog.i(TAG, "Starting " + className);
             throw new RuntimeException("Failed to create service " + className
-                    + ": service class not found, usually indicates that the caller should "
+                    + " from class loader " + classLoader.toString() + ": service class not "
+                    + "found, usually indicates that the caller should "
                     + "have called PackageManager.hasSystemFeature() to check whether the "
                     + "feature is available on this device before trying to start the "
-                    + "services that implement it", ex);
+                    + "services that implement it. Also ensure that the correct path for the "
+                    + "classloader is supplied, if applicable.", ex);
         }
-        return startService(serviceClass);
     }
 
     /**
@@ -264,26 +297,26 @@
             @UserIdInt int curUserId, @UserIdInt int prevUserId) {
         t.traceBegin("ssm." + onWhat + "User-" + curUserId);
         Slog.i(TAG, "Calling on" + onWhat + "User " + curUserId);
-        final UserInfo curUserInfo = getUserInfo(curUserId);
-        final UserInfo prevUserInfo = prevUserId == UserHandle.USER_NULL ? null
-                : getUserInfo(prevUserId);
+        final TargetUser curUser = new TargetUser(getUserInfo(curUserId));
+        final TargetUser prevUser = prevUserId == UserHandle.USER_NULL ? null
+                : new TargetUser(getUserInfo(prevUserId));
         final int serviceLen = mServices.size();
         for (int i = 0; i < serviceLen; i++) {
             final SystemService service = mServices.get(i);
             final String serviceName = service.getClass().getName();
-            boolean supported = service.isSupported(curUserInfo);
+            boolean supported = service.isSupportedUser(curUser);
 
             // Must check if either curUser or prevUser is supported (for example, if switching from
             // unsupported to supported, we still need to notify the services)
-            if (!supported && prevUserInfo != null) {
-                supported = service.isSupported(prevUserInfo);
+            if (!supported && prevUser != null) {
+                supported = service.isSupportedUser(prevUser);
             }
 
             if (!supported) {
                 if (DEBUG) {
                     Slog.d(TAG, "Skipping " + onWhat + "User-" + curUserId + " on service "
                             + serviceName + " because it's not supported (curUser: "
-                            + curUserInfo + ", prevUser:" + prevUserInfo + ")");
+                            + curUser + ", prevUser:" + prevUser + ")");
                 } else {
                     Slog.i(TAG,  "Skipping " + onWhat + "User-" + curUserId + " on "
                             + serviceName);
@@ -295,25 +328,25 @@
             try {
                 switch (onWhat) {
                     case SWITCH:
-                        service.onSwitchUser(prevUserInfo, curUserInfo);
+                        service.onSwitchUser(prevUser, curUser);
                         break;
                     case START:
-                        service.onStartUser(curUserInfo);
+                        service.onStartUser(curUser);
                         break;
                     case UNLOCK:
-                        service.onUnlockUser(curUserInfo);
+                        service.onUnlockUser(curUser);
                         break;
                     case STOP:
-                        service.onStopUser(curUserInfo);
+                        service.onStopUser(curUser);
                         break;
                     case CLEANUP:
-                        service.onCleanupUser(curUserInfo);
+                        service.onCleanupUser(curUser);
                         break;
                     default:
                         throw new IllegalArgumentException(onWhat + " what?");
                 }
             } catch (Exception ex) {
-                Slog.wtf(TAG, "Failure reporting " + onWhat + " of user " + curUserInfo
+                Slog.wtf(TAG, "Failure reporting " + onWhat + " of user " + curUser
                         + " to service " + serviceName, ex);
             }
             warnIfTooLong(SystemClock.elapsedRealtime() - time, service,
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 3e6ccb5..68574f5 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -46,6 +46,7 @@
 import android.telephony.Annotation.DataFailureCause;
 import android.telephony.Annotation.RadioPowerState;
 import android.telephony.Annotation.SrvccState;
+import android.telephony.BarringInfo;
 import android.telephony.CallAttributes;
 import android.telephony.CallQuality;
 import android.telephony.CellIdentity;
@@ -83,7 +84,6 @@
 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
 import com.android.internal.telephony.IPhoneStateListener;
 import com.android.internal.telephony.ITelephonyRegistry;
-import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.TelephonyPermissions;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
@@ -255,6 +255,8 @@
 
     private int[] mCallPreciseDisconnectCause;
 
+    private List<BarringInfo> mBarringInfo = null;
+
     private boolean mCarrierNetworkChangeState = false;
 
     private PhoneCapability mPhoneCapability = null;
@@ -287,7 +289,10 @@
 
     static final int PRECISE_PHONE_STATE_PERMISSION_MASK =
                 PhoneStateListener.LISTEN_PRECISE_CALL_STATE
-                | PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE;
+                | PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE
+                | PhoneStateListener.LISTEN_CALL_DISCONNECT_CAUSES
+                | PhoneStateListener.LISTEN_CALL_ATTRIBUTES_CHANGED
+                | PhoneStateListener.LISTEN_IMS_CALL_DISCONNECT_CAUSES;
 
     static final int READ_ACTIVE_EMERGENCY_SESSION_PERMISSION_MASK =
             PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_CALL
@@ -437,6 +442,7 @@
             cutListToSize(mCellInfo, mNumPhones);
             cutListToSize(mImsReasonInfo, mNumPhones);
             cutListToSize(mPreciseDataConnectionStates, mNumPhones);
+            cutListToSize(mBarringInfo, mNumPhones);
             return;
         }
 
@@ -468,6 +474,7 @@
             mForegroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
             mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
             mPreciseDataConnectionStates.add(new HashMap<Integer, PreciseDataConnectionState>());
+            mBarringInfo.add(i, new BarringInfo());
         }
     }
 
@@ -525,6 +532,7 @@
         mEmergencyNumberList = new HashMap<>();
         mOutgoingCallEmergencyNumber = new EmergencyNumber[numPhones];
         mOutgoingSmsEmergencyNumber = new EmergencyNumber[numPhones];
+        mBarringInfo = new ArrayList<>();
         for (int i = 0; i < numPhones; i++) {
             mCallState[i] =  TelephonyManager.CALL_STATE_IDLE;
             mDataActivity[i] = TelephonyManager.DATA_ACTIVITY_NONE;
@@ -552,6 +560,7 @@
             mForegroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
             mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
             mPreciseDataConnectionStates.add(new HashMap<Integer, PreciseDataConnectionState>());
+            mBarringInfo.add(i, new BarringInfo());
         }
 
         mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -879,6 +888,10 @@
                             remove(r.binder);
                         }
                     }
+                    if ((events & PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH)
+                            != 0) {
+                        updateReportSignalStrengthDecision(r.subId);
+                    }
                     if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_INFO)) {
                         try {
                             if (DBG_LOC) log("listen: mCellInfo[" + phoneId + "] = "
@@ -994,6 +1007,19 @@
                             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.R)
+                                            ? barringInfo : biNoLocation);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
+                    }
                 }
             }
         } else {
@@ -1002,6 +1028,27 @@
         }
     }
 
+    private void updateReportSignalStrengthDecision(int subscriptionId) {
+        synchronized (mRecords) {
+            TelephonyManager telephonyManager = (TelephonyManager) mContext
+                    .getSystemService(Context.TELEPHONY_SERVICE);
+            for (Record r : mRecords) {
+                // If any of the system clients wants to always listen to signal strength,
+                // we need to set it on.
+                if (r.matchPhoneStateListenerEvent(
+                        PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH)) {
+                    telephonyManager.createForSubscriptionId(subscriptionId)
+                            .setAlwaysReportSignalStrength(true);
+                    return;
+                }
+            }
+            // If none of the system clients wants to always listen to signal strength,
+            // we need to set it off.
+            telephonyManager.createForSubscriptionId(subscriptionId)
+                    .setAlwaysReportSignalStrength(false);
+        }
+    }
+
     private String getCallIncomingNumber(Record record, int phoneId) {
         // Only reveal the incoming number if the record has read call log permission.
         return record.canReadCallLog() ? mCallIncomingNumber[phoneId] : "";
@@ -1059,6 +1106,14 @@
                     }
 
                     mRecords.remove(i);
+
+                    // Every time a client that is registrating to always receive the signal
+                    // 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.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH)) {
+                        updateReportSignalStrengthDecision(r.subId);
+                    }
                     return;
                 }
             }
@@ -2103,6 +2158,52 @@
         }
     }
 
+    /**
+     * Send a notification of changes to barring status to PhoneStateListener registrants.
+     *
+     * @param phoneId the phoneId
+     * @param subId the subId
+     * @param barringInfo a structure containing the complete updated barring info.
+     */
+    public void notifyBarringInfoChanged(int phoneId, int subId, @NonNull BarringInfo barringInfo) {
+        if (!checkNotifyPermission("notifyBarringInfo()")) {
+            return;
+        }
+        if (barringInfo == null) {
+            log("Received null BarringInfo for subId=" + subId + ", phoneId=" + phoneId);
+            mBarringInfo.set(phoneId, new BarringInfo());
+            return;
+        }
+
+        synchronized (mRecords) {
+            if (validatePhoneId(phoneId)) {
+                mBarringInfo.set(phoneId, barringInfo);
+                // Barring info is non-null
+                BarringInfo biNoLocation = barringInfo.createLocationInfoSanitizedCopy();
+                if (VDBG) log("listen: call onBarringInfoChanged=" + barringInfo);
+                for (Record r : mRecords) {
+                    if (r.matchPhoneStateListenerEvent(
+                            PhoneStateListener.LISTEN_BARRING_INFO)
+                            && idMatch(r.subId, subId, phoneId)) {
+                        try {
+                            if (DBG_LOC) {
+                                log("notifyBarringInfo: mBarringInfo="
+                                        + barringInfo + " r=" + r);
+                            }
+                            r.callback.onBarringInfoChanged(
+                                    checkFineLocationAccess(r, Build.VERSION_CODES.R)
+                                        ? barringInfo : biNoLocation);
+                        } catch (RemoteException ex) {
+                            mRemoveList.add(r.binder);
+                        }
+                    }
+                }
+            }
+            handleRemoveListLocked();
+        }
+    }
+
+
     @Override
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
@@ -2143,6 +2244,7 @@
                 pw.println("mPreciseDataConnectionStates=" + mPreciseDataConnectionStates.get(i));
                 pw.println("mOutgoingCallEmergencyNumber=" + mOutgoingCallEmergencyNumber[i]);
                 pw.println("mOutgoingSmsEmergencyNumber=" + mOutgoingSmsEmergencyNumber[i]);
+                pw.println("mBarringInfo=" + mBarringInfo.get(i));
                 pw.decreaseIndent();
             }
             pw.println("mCarrierNetworkChangeState=" + mCarrierNetworkChangeState);
@@ -2194,6 +2296,17 @@
     private static final String PHONE_CONSTANTS_STATE_KEY = "state";
     private static final String PHONE_CONSTANTS_SUBSCRIPTION_KEY = "subscription";
 
+    /**
+     * Broadcast Action: The phone's signal strength has changed. The intent will have the
+     * following extra values:
+     *   phoneName - A string version of the phone name.
+     *   asu - A numeric value for the signal strength.
+     *         An ASU is 0-31 or -1 if unknown (for GSM, dBm = -113 - 2 * asu).
+     *         The following special values are defined:
+     *         0 means "-113 dBm or less".31 means "-51 dBm or greater".
+     */
+    public static final String ACTION_SIGNAL_STRENGTH_CHANGED = "android.intent.action.SIG_STR";
+
     private void broadcastServiceStateChanged(ServiceState state, int phoneId, int subId) {
         long ident = Binder.clearCallingIdentity();
         try {
@@ -2228,7 +2341,7 @@
             Binder.restoreCallingIdentity(ident);
         }
 
-        Intent intent = new Intent(TelephonyIntents.ACTION_SIGNAL_STRENGTH_CHANGED);
+        Intent intent = new Intent(ACTION_SIGNAL_STRENGTH_CHANGED);
         Bundle data = new Bundle();
         fillInSignalStrengthNotifierBundle(signalStrength, data);
         intent.putExtras(data);
@@ -2336,43 +2449,12 @@
         Intent intent = new Intent(ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
         intent.putExtra(PHONE_CONSTANTS_STATE_KEY, dataStateToString(state));
         intent.putExtra(PHONE_CONSTANTS_DATA_APN_KEY, apn);
-        intent.putExtra(PHONE_CONSTANTS_DATA_APN_TYPE_KEY, getApnTypesStringFromBitmask(apnType));
+        intent.putExtra(PHONE_CONSTANTS_DATA_APN_TYPE_KEY,
+                ApnSetting.getApnTypesStringFromBitmask(apnType));
         intent.putExtra(PHONE_CONSTANTS_SUBSCRIPTION_KEY, subId);
         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
-    private static final Map<Integer, String> APN_TYPE_INT_MAP;
-    static {
-        APN_TYPE_INT_MAP = new android.util.ArrayMap<Integer, String>();
-        APN_TYPE_INT_MAP.put(ApnSetting.TYPE_DEFAULT, "default");
-        APN_TYPE_INT_MAP.put(ApnSetting.TYPE_MMS, "mms");
-        APN_TYPE_INT_MAP.put(ApnSetting.TYPE_SUPL, "supl");
-        APN_TYPE_INT_MAP.put(ApnSetting.TYPE_DUN, "dun");
-        APN_TYPE_INT_MAP.put(ApnSetting.TYPE_HIPRI, "hipri");
-        APN_TYPE_INT_MAP.put(ApnSetting.TYPE_FOTA, "fota");
-        APN_TYPE_INT_MAP.put(ApnSetting.TYPE_IMS, "ims");
-        APN_TYPE_INT_MAP.put(ApnSetting.TYPE_CBS, "cbs");
-        APN_TYPE_INT_MAP.put(ApnSetting.TYPE_IA, "ia");
-        APN_TYPE_INT_MAP.put(ApnSetting.TYPE_EMERGENCY, "emergency");
-        APN_TYPE_INT_MAP.put(ApnSetting.TYPE_MCX, "mcx");
-        APN_TYPE_INT_MAP.put(ApnSetting.TYPE_XCAP, "xcap");
-    }
-
-    /**
-     * Copy of ApnSetting#getApnTypesStringFromBitmask for legacy broadcast.
-     * @param apnTypeBitmask bitmask of APN types.
-     * @return comma delimited list of APN types.
-     */
-    private static String getApnTypesStringFromBitmask(int apnTypeBitmask) {
-        List<String> types = new ArrayList<>();
-        for (Integer type : APN_TYPE_INT_MAP.keySet()) {
-            if ((apnTypeBitmask & type) == type) {
-                types.add(APN_TYPE_INT_MAP.get(type));
-            }
-        }
-        return android.text.TextUtils.join(",", types);
-    }
-
     private void enforceNotifyPermissionOrCarrierPrivilege(String method) {
         if (checkNotifyPermission()) {
             return;
@@ -2439,8 +2521,14 @@
         }
 
         if ((events & PRECISE_PHONE_STATE_PERMISSION_MASK) != 0) {
-            mContext.enforceCallingOrSelfPermission(
-                    android.Manifest.permission.READ_PRECISE_PHONE_STATE, null);
+            // check if calling app has either permission READ_PRECISE_PHONE_STATE
+            // or with carrier privileges
+            try {
+                mContext.enforceCallingOrSelfPermission(
+                        android.Manifest.permission.READ_PRECISE_PHONE_STATE, null);
+            } catch (SecurityException se) {
+                TelephonyPermissions.enforceCallingOrSelfCarrierPrivilege(mContext, subId, message);
+            }
         }
 
         if ((events & READ_ACTIVE_EMERGENCY_SESSION_PERMISSION_MASK) != 0) {
@@ -2448,6 +2536,11 @@
                     android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION, null);
         }
 
+        if ((events & PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH) != 0) {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH, null);
+        }
+
         if ((events & PhoneStateListener.LISTEN_OEM_HOOK_RAW_EVENT) != 0) {
             mContext.enforceCallingOrSelfPermission(
                     android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null);
@@ -2458,16 +2551,6 @@
                     android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null);
         }
 
-        if ((events & PhoneStateListener.LISTEN_CALL_DISCONNECT_CAUSES) != 0) {
-            mContext.enforceCallingOrSelfPermission(
-                    android.Manifest.permission.READ_PRECISE_PHONE_STATE, null);
-        }
-
-        if ((events & PhoneStateListener.LISTEN_CALL_ATTRIBUTES_CHANGED) != 0) {
-            mContext.enforceCallingOrSelfPermission(
-                    android.Manifest.permission.READ_PRECISE_PHONE_STATE, null);
-        }
-
         if ((events & PhoneStateListener.LISTEN_RADIO_POWER_STATE_CHANGED) != 0) {
             mContext.enforceCallingOrSelfPermission(
                     android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null);
@@ -2478,11 +2561,6 @@
                     android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null);
         }
 
-        if ((events & PhoneStateListener.LISTEN_IMS_CALL_DISCONNECT_CAUSES) != 0) {
-            mContext.enforceCallingOrSelfPermission(
-                    android.Manifest.permission.READ_PRECISE_PHONE_STATE, null);
-        }
-
         return true;
     }
 
diff --git a/services/core/java/com/android/server/TestNetworkService.java b/services/core/java/com/android/server/TestNetworkService.java
index c27b0da..35a9802 100644
--- a/services/core/java/com/android/server/TestNetworkService.java
+++ b/services/core/java/com/android/server/TestNetworkService.java
@@ -218,7 +218,7 @@
             // Has to be in TestNetworkAgent to ensure all teardown codepaths properly clean up
             // resources, even for binder death or unwanted calls.
             synchronized (mTestNetworkTracker) {
-                mTestNetworkTracker.remove(netId);
+                mTestNetworkTracker.remove(getNetwork().netId);
             }
         }
     }
@@ -337,7 +337,7 @@
                                             callingUid,
                                             binder);
 
-                            mTestNetworkTracker.put(agent.netId, agent);
+                            mTestNetworkTracker.put(agent.getNetwork().netId, agent);
                         }
                     } catch (SocketException e) {
                         throw new UncheckedIOException(e);
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 2091c2a..b994e6c 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -21,6 +21,7 @@
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
+import android.app.AlarmManager;
 import android.app.IUiModeManager;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -70,10 +71,19 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.time.DateTimeException;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
 
+import static android.app.UiModeManager.MODE_NIGHT_AUTO;
+import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
+import static android.app.UiModeManager.MODE_NIGHT_YES;
+import static android.util.TimeUtils.isTimeBetween;
+
 final class UiModeManagerService extends SystemService {
     private static final String TAG = UiModeManager.class.getSimpleName();
     private static final boolean LOG = false;
@@ -90,7 +100,12 @@
     // we use the override auto mode
     // for example: force night mode off in the night time while in auto mode
     private int mNightModeOverride = mNightMode;
-    protected static final String OVERRIDE_NIGHT_MODE = Secure.UI_NIGHT_MODE + "_override";
+    private final LocalTime DEFAULT_CUSTOM_NIGHT_START_TIME = LocalTime.of(22, 0);
+    private final LocalTime DEFAULT_CUSTOM_NIGHT_END_TIME = LocalTime.of(6, 0);
+    private LocalTime mCustomAutoNightModeStartMilliseconds = DEFAULT_CUSTOM_NIGHT_START_TIME;
+    private LocalTime mCustomAutoNightModeEndMilliseconds = DEFAULT_CUSTOM_NIGHT_END_TIME;
+
+    protected static final String OVERRIDE_NIGHT_MODE = Secure.UI_NIGHT_MODE_OVERRIDE;
 
     private Map<Integer, String> mCarModePackagePriority = new HashMap<>();
     private boolean mCarModeEnabled = false;
@@ -131,6 +146,8 @@
     private NotificationManager mNotificationManager;
     private StatusBarManager mStatusBarManager;
     private WindowManagerInternal mWindowManager;
+    private AlarmManager mAlarmManager;
+    private PowerManager mPowerManager;
 
     private PowerManager.WakeLock mWakeLock;
 
@@ -141,14 +158,16 @@
     }
 
     @VisibleForTesting
-    protected UiModeManagerService(Context context, WindowManagerInternal wm,
-                                   PowerManager.WakeLock wl, TwilightManager tm,
+    protected UiModeManagerService(Context context, WindowManagerInternal wm, AlarmManager am,
+                                   PowerManager pm, PowerManager.WakeLock wl, TwilightManager tm,
                                    boolean setupWizardComplete) {
         super(context);
         mWindowManager = wm;
         mWakeLock = wl;
         mTwilightManager = tm;
         mSetupWizardComplete = setupWizardComplete;
+        mAlarmManager = am;
+        mPowerManager = pm;
     }
 
     private static Intent buildHomeIntent(String category) {
@@ -237,6 +256,21 @@
         }
     };
 
+    private final BroadcastReceiver mOnTimeChangedHandler = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            synchronized (mLock) {
+                updateCustomTimeLocked();
+            }
+        }
+    };
+
+    private final AlarmManager.OnAlarmListener mCustomTimeListener = () -> {
+        synchronized (mLock) {
+            updateCustomTimeLocked();
+        }
+    };
+
     private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
         @Override
         public void onVrStateChanged(boolean enabled) {
@@ -270,8 +304,9 @@
         public void onChange(boolean selfChange, Uri uri) {
             int mode = Secure.getIntForUser(getContext().getContentResolver(), Secure.UI_NIGHT_MODE,
                     mNightMode, 0);
-            mode = mode == UiModeManager.MODE_NIGHT_AUTO
-                    ? UiModeManager.MODE_NIGHT_YES : UiModeManager.MODE_NIGHT_NO;
+            if (mode == MODE_NIGHT_AUTO || mode == MODE_NIGHT_CUSTOM) {
+                mode = MODE_NIGHT_YES;
+            }
             SystemProperties.set(SYSTEM_PROPERTY_DEVICE_THEME, Integer.toString(mode));
         }
     };
@@ -287,10 +322,11 @@
     public void onStart() {
         final Context context = getContext();
 
-        final PowerManager powerManager =
+        mPowerManager =
                 (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-        mWakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
+        mWakeLock = mPowerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
         mWindowManager = LocalServices.getService(WindowManagerInternal.class);
+        mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
 
         // If setup isn't complete for this user listen for completion so we can unblock
         // being able to send a night mode configuration change event
@@ -387,6 +423,16 @@
                 Secure.USER_SETUP_COMPLETE, 0, UserHandle.getCallingUserId()) == 1;
     }
 
+    private void updateCustomTimeLocked() {
+        if (mNightMode != MODE_NIGHT_CUSTOM) return;
+        if (shouldApplyAutomaticChangesImmediately()) {
+            updateLocked(0, 0);
+        } else {
+            registerScreenOffEvent();
+        }
+        scheduleNextCustomTimeListener();
+    }
+
     /**
      * Updates the night mode setting in Settings.Global and returns if the value was successfully
      * changed.
@@ -404,9 +450,19 @@
                     Secure.UI_NIGHT_MODE, defaultNightMode, userId);
             mNightModeOverride = Secure.getIntForUser(context.getContentResolver(),
                     OVERRIDE_NIGHT_MODE, defaultNightMode, userId);
+            mCustomAutoNightModeStartMilliseconds = LocalTime.ofNanoOfDay(
+                    Secure.getLongForUser(context.getContentResolver(),
+                    Secure.DARK_THEME_CUSTOM_START_TIME,
+                    DEFAULT_CUSTOM_NIGHT_START_TIME.toNanoOfDay() / 1000L, userId) * 1000);
+            mCustomAutoNightModeEndMilliseconds = LocalTime.ofNanoOfDay(
+                    Secure.getLongForUser(context.getContentResolver(),
+                    Secure.DARK_THEME_CUSTOM_END_TIME,
+                    DEFAULT_CUSTOM_NIGHT_END_TIME.toNanoOfDay() / 1000L, userId) * 1000);
         } else {
             mNightMode = defaultNightMode;
             mNightModeOverride = defaultNightMode;
+            mCustomAutoNightModeEndMilliseconds = DEFAULT_CUSTOM_NIGHT_END_TIME;
+            mCustomAutoNightModeStartMilliseconds = DEFAULT_CUSTOM_NIGHT_START_TIME;
         }
 
         return oldNightMode != mNightMode;
@@ -419,6 +475,10 @@
         getContext().registerReceiver(mOnScreenOffHandler, intentFilter);
     }
 
+    private void cancelCustomAlarm() {
+        mAlarmManager.cancel(mCustomTimeListener);
+    }
+
     private void unregisterScreenOffEvent() {
         mWaitForScreenOff = false;
         try {
@@ -428,6 +488,21 @@
         }
     }
 
+    private void registerTimeChangeEvent() {
+        final IntentFilter intentFilter =
+                new IntentFilter(Intent.ACTION_TIME_CHANGED);
+        intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+        getContext().registerReceiver(mOnTimeChangedHandler, intentFilter);
+    }
+
+    private void unregisterTimeChangeEvent() {
+        try {
+            getContext().unregisterReceiver(mOnTimeChangedHandler);
+        } catch (IllegalArgumentException e) {
+            // we ignore this exception if the receiver is unregistered already.
+        }
+    }
+
     private final IUiModeManager.Stub mService = new IUiModeManager.Stub() {
         @Override
         public void enableCarMode(@UiModeManager.EnableCarMode int flags,
@@ -537,7 +612,8 @@
             switch (mode) {
                 case UiModeManager.MODE_NIGHT_NO:
                 case UiModeManager.MODE_NIGHT_YES:
-                case UiModeManager.MODE_NIGHT_AUTO:
+                case MODE_NIGHT_AUTO:
+                case MODE_NIGHT_CUSTOM:
                     break;
                 default:
                     throw new IllegalArgumentException("Unknown mode: " + mode);
@@ -548,8 +624,9 @@
             try {
                 synchronized (mLock) {
                     if (mNightMode != mode) {
-                        if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
+                        if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) {
                             unregisterScreenOffEvent();
+                            cancelCustomAlarm();
                         }
 
                         mNightMode = mode;
@@ -559,7 +636,9 @@
                             persistNightMode(user);
                         }
                         // on screen off will update configuration instead
-                        if (mNightMode != UiModeManager.MODE_NIGHT_AUTO || mCar) {
+                        if ((mNightMode != MODE_NIGHT_AUTO && mNightMode != MODE_NIGHT_CUSTOM)
+                                || shouldApplyAutomaticChangesImmediately()) {
+                            unregisterScreenOffEvent();
                             updateLocked(0, 0);
                         } else {
                             registerScreenOffEvent();
@@ -610,7 +689,7 @@
                 final int user = UserHandle.getCallingUserId();
                 final long ident = Binder.clearCallingIdentity();
                 try {
-                    if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
+                    if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) {
                         unregisterScreenOffEvent();
                         mNightModeOverride = active
                                 ? UiModeManager.MODE_NIGHT_YES : UiModeManager.MODE_NIGHT_NO;
@@ -630,8 +709,74 @@
                 }
             }
         }
+
+        @Override
+        public long getCustomNightModeStart() {
+            return mCustomAutoNightModeStartMilliseconds.toNanoOfDay() / 1000;
+        }
+
+        @Override
+        public void setCustomNightModeStart(long time) {
+            if (isNightModeLocked() && getContext().checkCallingOrSelfPermission(
+                    android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
+                    != PackageManager.PERMISSION_GRANTED) {
+                Slog.e(TAG, "Set custom time start, requires MODIFY_DAY_NIGHT_MODE permission");
+                return;
+            }
+            final int user = UserHandle.getCallingUserId();
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                LocalTime newTime = LocalTime.ofNanoOfDay(time * 1000);
+                if (newTime == null) return;
+                mCustomAutoNightModeStartMilliseconds = newTime;
+                persistNightMode(user);
+                onCustomTimeUpdated(user);
+            } catch (DateTimeException e) {
+                unregisterScreenOffEvent();
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override
+        public long getCustomNightModeEnd() {
+            return mCustomAutoNightModeEndMilliseconds.toNanoOfDay() / 1000;
+        }
+
+        @Override
+        public void setCustomNightModeEnd(long time) {
+            if (isNightModeLocked() && getContext().checkCallingOrSelfPermission(
+                    android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
+                    != PackageManager.PERMISSION_GRANTED) {
+                Slog.e(TAG, "Set custom time end, requires MODIFY_DAY_NIGHT_MODE permission");
+                return;
+            }
+            final int user = UserHandle.getCallingUserId();
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                LocalTime newTime = LocalTime.ofNanoOfDay(time * 1000);
+                if (newTime == null) return;
+                mCustomAutoNightModeEndMilliseconds = newTime;
+                onCustomTimeUpdated(user);
+            } catch (DateTimeException e) {
+                unregisterScreenOffEvent();
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
     };
 
+    private void onCustomTimeUpdated(int user) {
+        persistNightMode(user);
+        if (mNightMode != MODE_NIGHT_CUSTOM) return;
+        if (shouldApplyAutomaticChangesImmediately()) {
+            unregisterScreenOffEvent();
+            updateLocked(0, 0);
+        } else {
+            registerScreenOffEvent();
+        }
+    }
+
     void dumpImpl(PrintWriter pw) {
         synchronized (mLock) {
             pw.println("Current UI Mode Service state:");
@@ -677,7 +822,6 @@
                 mTwilightManager = getLocalService(TwilightManager.class);
                 mSystemReady = true;
                 mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR;
-                updateComputedNightModeLocked();
                 registerVrStateListener();
                 updateLocked(0, 0);
             }
@@ -838,6 +982,12 @@
                 Secure.UI_NIGHT_MODE, mNightMode, user);
         Secure.putIntForUser(getContext().getContentResolver(),
                 OVERRIDE_NIGHT_MODE, mNightModeOverride, user);
+        Secure.putLongForUser(getContext().getContentResolver(),
+                Secure.DARK_THEME_CUSTOM_START_TIME,
+                mCustomAutoNightModeStartMilliseconds.toNanoOfDay() / 1000, user);
+        Secure.putLongForUser(getContext().getContentResolver(),
+                Secure.DARK_THEME_CUSTOM_END_TIME,
+                mCustomAutoNightModeEndMilliseconds.toNanoOfDay() / 1000, user);
     }
 
     private void updateConfigurationLocked() {
@@ -856,13 +1006,16 @@
             uiMode = Configuration.UI_MODE_TYPE_VR_HEADSET;
         }
 
-        if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
+        if (mNightMode == MODE_NIGHT_AUTO) {
+            boolean activateNightMode = mComputedNightMode;
             if (mTwilightManager != null) {
                 mTwilightManager.registerListener(mTwilightListener, mHandler);
+                final TwilightState lastState = mTwilightManager.getLastTwilightState();
+                activateNightMode = lastState == null ? mComputedNightMode : lastState.isNight();
             }
-            updateComputedNightModeLocked();
-            uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES
-                    : Configuration.UI_MODE_NIGHT_NO;
+            
+            updateComputedNightModeLocked(activateNightMode);
+            uiMode = getComputedUiModeConfiguration(uiMode);
         } else {
             if (mTwilightManager != null) {
                 mTwilightManager.unregisterListener(mTwilightListener);
@@ -870,6 +1023,16 @@
             uiMode |= mNightMode << 4;
         }
 
+        if (mNightMode == MODE_NIGHT_CUSTOM) {
+            registerTimeChangeEvent();
+            final boolean activate = computeCustomNightMode();
+            updateComputedNightModeLocked(activate);
+            scheduleNextCustomTimeListener();
+            uiMode = getComputedUiModeConfiguration(uiMode);
+        } else {
+            unregisterTimeChangeEvent();
+        }
+
         // Override night mode in power save mode if not in car mode
         if (mPowerSave && !mCarModeEnabled) {
             uiMode &= ~Configuration.UI_MODE_NIGHT_NO;
@@ -885,11 +1048,26 @@
         }
 
         mCurUiMode = uiMode;
-        if (!mHoldingConfiguration || !mWaitForScreenOff) {
+        if (!mHoldingConfiguration && !mWaitForScreenOff) {
             mConfiguration.uiMode = uiMode;
         }
     }
 
+    @UiModeManager.NightMode
+    private int getComputedUiModeConfiguration(@UiModeManager.NightMode int uiMode) {
+        uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES
+                : Configuration.UI_MODE_NIGHT_NO;
+        uiMode &= mComputedNightMode ? ~Configuration.UI_MODE_NIGHT_NO
+                : ~Configuration.UI_MODE_NIGHT_YES;
+        return uiMode;
+    }
+
+    private boolean computeCustomNightMode() {
+        return isTimeBetween(LocalTime.now(),
+                mCustomAutoNightModeStartMilliseconds,
+                mCustomAutoNightModeEndMilliseconds);
+    }
+
     private void applyConfigurationExternallyLocked() {
         if (mSetUiMode != mConfiguration.uiMode) {
             mSetUiMode = mConfiguration.uiMode;
@@ -899,10 +1077,34 @@
                 ActivityTaskManager.getService().updateConfiguration(mConfiguration);
             } catch (RemoteException e) {
                 Slog.w(TAG, "Failure communicating with activity manager", e);
+            } catch (SecurityException e) {
+                Slog.e(TAG, "Activity does not have the ", e);
             }
         }
     }
 
+    private boolean shouldApplyAutomaticChangesImmediately() {
+        return mCar || !mPowerManager.isInteractive();
+    }
+
+    private void scheduleNextCustomTimeListener() {
+        cancelCustomAlarm();
+        LocalDateTime now = LocalDateTime.now();
+        final boolean active = computeCustomNightMode();
+        final LocalDateTime next = active
+                ? getDateTimeAfter(mCustomAutoNightModeEndMilliseconds, now)
+                : getDateTimeAfter(mCustomAutoNightModeStartMilliseconds, now);
+        final long millis = next.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
+        mAlarmManager.setExact(AlarmManager.RTC, millis, TAG, mCustomTimeListener, null);
+    }
+
+    private LocalDateTime getDateTimeAfter(LocalTime localTime, LocalDateTime compareTime) {
+        final LocalDateTime ldt = LocalDateTime.of(compareTime.toLocalDate(), localTime);
+
+        // Check if the local time has passed, if so return the same time tomorrow.
+        return ldt.isBefore(compareTime) ? ldt.plusDays(1) : ldt;
+    }
+
     void updateLocked(int enableFlags, int disableFlags) {
         String action = null;
         String oldAction = null;
@@ -1059,8 +1261,8 @@
             if (Sandman.shouldStartDockApp(getContext(), homeIntent)) {
                 try {
                     int result = ActivityTaskManager.getService().startActivityWithConfig(
-                            null, null, homeIntent, null, null, null, 0, 0,
-                            mConfiguration, null, UserHandle.USER_CURRENT);
+                            null, getContext().getBasePackageName(), homeIntent, null, null, null,
+                            0, 0, mConfiguration, null, UserHandle.USER_CURRENT);
                     if (ActivityManager.isStartResultSuccessful(result)) {
                         dockAppStarted = true;
                     } else if (result != ActivityManager.START_INTENT_NOT_RESOLVED) {
@@ -1133,26 +1335,21 @@
         }
     }
 
-    private void updateComputedNightModeLocked() {
-        if (mTwilightManager != null) {
-            TwilightState state = mTwilightManager.getLastTwilightState();
-            if (state != null) {
-                mComputedNightMode = state.isNight();
-            }
-            if (mNightModeOverride == UiModeManager.MODE_NIGHT_YES && !mComputedNightMode) {
-                mComputedNightMode = true;
-                return;
-            }
-            if (mNightModeOverride == UiModeManager.MODE_NIGHT_NO && mComputedNightMode) {
-                mComputedNightMode = false;
-                return;
-            }
-
-            mNightModeOverride = mNightMode;
-            final int user = UserHandle.getCallingUserId();
-            Secure.putIntForUser(getContext().getContentResolver(),
-                    OVERRIDE_NIGHT_MODE, mNightModeOverride, user);
+    private void updateComputedNightModeLocked(boolean activate) {
+        mComputedNightMode = activate;
+        if (mNightModeOverride == UiModeManager.MODE_NIGHT_YES && !mComputedNightMode) {
+            mComputedNightMode = true;
+            return;
         }
+        if (mNightModeOverride == UiModeManager.MODE_NIGHT_NO && mComputedNightMode) {
+            mComputedNightMode = false;
+            return;
+        }
+
+        mNightModeOverride = mNightMode;
+        final int user = UserHandle.getCallingUserId();
+        Secure.putIntForUser(getContext().getContentResolver(),
+                OVERRIDE_NIGHT_MODE, mNightModeOverride, user);
     }
 
     private void registerVrStateListener() {
@@ -1174,6 +1371,7 @@
         public static final String NIGHT_MODE_STR_YES = "yes";
         public static final String NIGHT_MODE_STR_NO = "no";
         public static final String NIGHT_MODE_STR_AUTO = "auto";
+        public static final String NIGHT_MODE_STR_CUSTOM = "custom";
         public static final String NIGHT_MODE_STR_UNKNOWN = "unknown";
         private final IUiModeManager mInterface;
 
@@ -1246,6 +1444,8 @@
                     return NIGHT_MODE_STR_NO;
                 case UiModeManager.MODE_NIGHT_AUTO:
                     return NIGHT_MODE_STR_AUTO;
+                case MODE_NIGHT_CUSTOM:
+                    return NIGHT_MODE_STR_CUSTOM;
                 default:
                     return NIGHT_MODE_STR_UNKNOWN;
             }
@@ -1259,6 +1459,8 @@
                     return UiModeManager.MODE_NIGHT_NO;
                 case NIGHT_MODE_STR_AUTO:
                     return UiModeManager.MODE_NIGHT_AUTO;
+                case NIGHT_MODE_STR_CUSTOM:
+                    return UiModeManager.MODE_NIGHT_CUSTOM;
                 default:
                     return -1;
             }
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 27e0d52..5a56a9f 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -61,7 +61,6 @@
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.util.DebugUtils;
-import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.StatsLog;
@@ -164,8 +163,7 @@
     private int mHapticFeedbackIntensity;
     private int mNotificationIntensity;
     private int mRingIntensity;
-    private SparseArray<Pair<VibrationEffect, VibrationAttributes>> mAlwaysOnEffects =
-            new SparseArray<>();
+    private SparseArray<Vibration> mAlwaysOnEffects = new SparseArray<>();
 
     static native boolean vibratorExists();
     static native void vibratorInit();
@@ -461,6 +459,10 @@
                     Settings.System.getUriFor(Settings.System.RING_VIBRATION_INTENSITY),
                     true, mSettingObserver, UserHandle.USER_ALL);
 
+            mContext.getContentResolver().registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.ZEN_MODE),
+                    true, mSettingObserver, UserHandle.USER_ALL);
+
             mContext.registerReceiver(new BroadcastReceiver() {
                 @Override
                 public void onReceive(Context context, Intent intent) {
@@ -508,7 +510,8 @@
     }
 
     @Override // Binder call
-    public boolean setAlwaysOnEffect(int id, VibrationEffect effect, VibrationAttributes attrs) {
+    public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect,
+            VibrationAttributes attrs) {
         if (!hasPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)) {
             throw new SecurityException("Requires VIBRATE_ALWAYS_ON permission");
         }
@@ -518,8 +521,8 @@
         }
         if (effect == null) {
             synchronized (mLock) {
-                mAlwaysOnEffects.delete(id);
-                vibratorAlwaysOnDisable(id);
+                mAlwaysOnEffects.delete(alwaysOnId);
+                vibratorAlwaysOnDisable(alwaysOnId);
             }
         } else {
             if (!verifyVibrationEffect(effect)) {
@@ -529,13 +532,11 @@
                 Slog.e(TAG, "Only prebaked effects supported for always-on.");
                 return false;
             }
-            if (attrs == null) {
-                attrs = new VibrationAttributes.Builder()
-                        .build();
-            }
+            attrs = fixupVibrationAttributes(attrs);
             synchronized (mLock) {
-                mAlwaysOnEffects.put(id, Pair.create(effect, attrs));
-                updateAlwaysOnLocked(id, effect, attrs);
+                Vibration vib = new Vibration(null, effect, attrs, uid, opPkg, null);
+                mAlwaysOnEffects.put(alwaysOnId, vib);
+                updateAlwaysOnLocked(alwaysOnId, vib);
             }
         }
         return true;
@@ -575,6 +576,23 @@
         return true;
     }
 
+    private VibrationAttributes fixupVibrationAttributes(VibrationAttributes attrs) {
+        if (attrs == null) {
+            attrs = DEFAULT_ATTRIBUTES;
+        }
+        if (shouldBypassDnd(attrs)) {
+            if (!(hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+                    || hasPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+                    || hasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING))) {
+                final int flags = attrs.getFlags()
+                        & ~VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
+                attrs = new VibrationAttributes.Builder(attrs).replaceFlags(flags).build();
+            }
+        }
+
+        return attrs;
+    }
+
     private static long[] getLongIntArray(Resources r, int resid) {
         int[] ar = r.getIntArray(resid);
         if (ar == null) {
@@ -604,19 +622,7 @@
                 return;
             }
 
-            if (attrs == null) {
-                attrs = DEFAULT_ATTRIBUTES;
-            }
-
-            if (shouldBypassDnd(attrs)) {
-                if (!(hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-                        || hasPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-                        || hasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING))) {
-                    final int flags = attrs.getFlags()
-                            & ~VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
-                    attrs = new VibrationAttributes.Builder(attrs).replaceFlags(flags).build();
-                }
-            }
+            attrs = fixupVibrationAttributes(attrs);
 
             // If our current vibration is longer than the new vibration and is the same amplitude,
             // then just let the current one finish.
@@ -777,29 +783,8 @@
     private void startVibrationLocked(final Vibration vib) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
         try {
-            if (!isAllowedToVibrateLocked(vib)) {
-                return;
-            }
-
             final int intensity = getCurrentIntensityLocked(vib);
-            if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
-                return;
-            }
-
-            if (vib.isRingtone() && !shouldVibrateForRingtone()) {
-                if (DEBUG) {
-                    Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones");
-                }
-                return;
-            }
-
-            final int mode = getAppOpMode(vib);
-            if (mode != AppOpsManager.MODE_ALLOWED) {
-                if (mode == AppOpsManager.MODE_ERRORED) {
-                    // We might be getting calls from within system_server, so we don't actually
-                    // want to throw a SecurityException here.
-                    Slog.w(TAG, "Would be an error: vibrate from uid " + vib.uid);
-                }
+            if (!shouldVibrate(vib, intensity)) {
                 return;
             }
             applyVibrationIntensityScalingLocked(vib, intensity);
@@ -958,6 +943,35 @@
         return mode;
     }
 
+    private boolean shouldVibrate(Vibration vib, int intensity) {
+        if (!isAllowedToVibrateLocked(vib)) {
+            return false;
+        }
+
+        if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
+            return false;
+        }
+
+        if (vib.isRingtone() && !shouldVibrateForRingtone()) {
+            if (DEBUG) {
+                Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones");
+            }
+            return false;
+        }
+
+        final int mode = getAppOpMode(vib);
+        if (mode != AppOpsManager.MODE_ALLOWED) {
+            if (mode == AppOpsManager.MODE_ERRORED) {
+                // We might be getting calls from within system_server, so we don't actually
+                // want to throw a SecurityException here.
+                Slog.w(TAG, "Would be an error: vibrate from uid " + vib.uid);
+            }
+            return false;
+        }
+
+        return true;
+    }
+
     @GuardedBy("mLock")
     private void reportFinishVibrationLocked() {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
@@ -1069,14 +1083,12 @@
                 mVibrator.getDefaultRingVibrationIntensity(), UserHandle.USER_CURRENT);
     }
 
-    private void updateAlwaysOnLocked(int id, VibrationEffect effect, VibrationAttributes attrs) {
-        // TODO: Check DND and LowPower settings
-        final Vibration vib = new Vibration(null, effect, attrs, 0, null, null);
+    private void updateAlwaysOnLocked(int id, Vibration vib) {
         final int intensity = getCurrentIntensityLocked(vib);
-        if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
+        if (!shouldVibrate(vib, intensity)) {
             vibratorAlwaysOnDisable(id);
         } else {
-            final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
+            final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.effect;
             final int strength = intensityToEffectStrength(intensity);
             vibratorAlwaysOnEnable(id, prebaked.getId(), strength);
         }
@@ -1085,8 +1097,8 @@
     private void updateAlwaysOnLocked() {
         for (int i = 0; i < mAlwaysOnEffects.size(); i++) {
             int id = mAlwaysOnEffects.keyAt(i);
-            Pair<VibrationEffect, VibrationAttributes> pair = mAlwaysOnEffects.valueAt(i);
-            updateAlwaysOnLocked(id, pair.first, pair.second);
+            Vibration vib = mAlwaysOnEffects.valueAt(i);
+            updateAlwaysOnLocked(id, vib);
         }
     }
 
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 454941c..a60b09f 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -39,8 +39,10 @@
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.StatsLog;
 
+import com.android.internal.os.ProcessCpuTracker;
 import com.android.internal.os.ZygoteConnectionConstants;
 import com.android.server.am.ActivityManagerService;
 import com.android.server.wm.SurfaceAnimationThread;
@@ -606,13 +608,18 @@
             pids.add(Process.myPid());
             if (mPhonePid > 0) pids.add(mPhonePid);
 
+            long anrTime = SystemClock.uptimeMillis();
+            ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(false);
             final File stack = ActivityManagerService.dumpStackTraces(
-                    pids, null, null, getInterestingNativePids());
+                    pids, processCpuTracker, new SparseArray<>(), getInterestingNativePids());
 
             // Give some extra time to make sure the stack traces get written.
             // The system's been hanging for a minute, another second or two won't hurt much.
             SystemClock.sleep(5000);
 
+            processCpuTracker.update();
+            String cpuInfo = processCpuTracker.printCurrentState(anrTime);
+
             // Trigger the kernel to dump all blocked threads, and backtraces on all CPUs to the kernel log
             doSysRq('w');
             doSysRq('l');
@@ -627,7 +634,7 @@
                         if (mActivity != null) {
                             mActivity.addErrorToDropBox(
                                     "watchdog", null, "system_server", null, null, null,
-                                    subject, null, stack, null);
+                                    subject, cpuInfo, stack, null);
                         }
                         StatsLog.write(StatsLog.SYSTEM_SERVER_WATCHDOG_OCCURRED, subject);
                     }
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index a372fca0..debc2a1 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -2129,16 +2129,6 @@
     }
 
     @Override
-    public void removeAccount(IAccountManagerResponse response, Account account,
-            boolean expectActivityLaunch) {
-        removeAccountAsUser(
-                response,
-                account,
-                expectActivityLaunch,
-                UserHandle.getCallingUserId());
-    }
-
-    @Override
     public void removeAccountAsUser(IAccountManagerResponse response, Account account,
             boolean expectActivityLaunch, int userId) {
         final int callingUid = Binder.getCallingUid();
@@ -4454,12 +4444,6 @@
 
     @Override
     @NonNull
-    public Account[] getAccounts(String type, String opPackageName) {
-        return getAccountsAsUser(type, UserHandle.getCallingUserId(), opPackageName);
-    }
-
-    @Override
-    @NonNull
     public Account[] getAccountsForPackage(String packageName, int uid, String opPackageName) {
         int callingUid = Binder.getCallingUid();
         if (!UserHandle.isSameApp(callingUid, Process.SYSTEM_UID)) {
diff --git a/services/core/java/com/android/server/accounts/AccountManagerServiceShellCommand.java b/services/core/java/com/android/server/accounts/AccountManagerServiceShellCommand.java
index 9bf0bd3..afb20ec 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerServiceShellCommand.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerServiceShellCommand.java
@@ -78,7 +78,18 @@
         final String option = getNextOption();
         if (option != null) {
             if (option.equals("--user")) {
-                return UserHandle.parseUserArg(getNextArgRequired());
+                int userId = UserHandle.parseUserArg(getNextArgRequired());
+                if (userId == UserHandle.USER_CURRENT) {
+                    return ActivityManager.getCurrentUser();
+                } else if (userId == UserHandle.USER_ALL) {
+                    getErrPrintWriter().println("USER_ALL not supported. Specify a user.");
+                    return null;
+                } else if (userId < 0) {
+                    getErrPrintWriter().println("Invalid user: " + userId);
+                    return null;
+                } else {
+                    return userId;
+                }
             } else {
                 getErrPrintWriter().println("Unknown option: " + option);
                 return null;
diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
index 4b48ef9..143474b 100644
--- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
@@ -413,6 +413,11 @@
                 case MESSAGE_ADB_CLEAR: {
                     Slog.d(TAG, "Received a request to clear the adb authorizations");
                     mConnectedKeys.clear();
+                    // If the key store has not yet been instantiated then do so now; this avoids
+                    // the unnecessary creation of the key store when adb is not enabled.
+                    if (mAdbKeyStore == null) {
+                        mAdbKeyStore = new AdbKeyStore();
+                    }
                     mAdbKeyStore.deleteKeyStore();
                     cancelJobToUpdateAdbKeyStore();
                     break;
diff --git a/services/core/java/com/android/server/adb/AdbService.java b/services/core/java/com/android/server/adb/AdbService.java
index 7fd98e0..c125b1b 100644
--- a/services/core/java/com/android/server/adb/AdbService.java
+++ b/services/core/java/com/android/server/adb/AdbService.java
@@ -17,6 +17,7 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.database.ContentObserver;
 import android.debug.AdbManagerInternal;
 import android.debug.IAdbManager;
@@ -260,6 +261,30 @@
         }
     }
 
+    /**
+     * @return true if the device supports secure ADB over Wi-Fi.
+     * @hide
+     */
+    @Override
+    public boolean isAdbWifiSupported() {
+        mContext.enforceCallingPermission(
+                android.Manifest.permission.MANAGE_DEBUGGING, "AdbService");
+        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI);
+    }
+
+    /**
+     * @return true if the device supports secure ADB over Wi-Fi and device pairing by
+     * QR code.
+     * @hide
+     */
+    @Override
+    public boolean isAdbWifiQrSupported() {
+        mContext.enforceCallingPermission(
+                android.Manifest.permission.MANAGE_DEBUGGING, "AdbService");
+        return isAdbWifiSupported() && mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_CAMERA_ANY);
+    }
+
     private void setAdbEnabled(boolean enable) {
         if (DEBUG) Slog.d(TAG, "setAdbEnabled(" + enable + "), mAdbEnabled=" + mAdbEnabled);
 
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index a58bd9b..ac85bf5 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -31,11 +31,13 @@
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityThread;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
+import android.app.ApplicationExitInfo;
 import android.app.IApplicationThread;
 import android.app.IServiceConnection;
 import android.app.Notification;
@@ -2067,9 +2069,9 @@
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "retrieveServiceLocked: " + service
                 + " type=" + resolvedType + " callingUid=" + callingUid);
 
-        userId = mAm.mUserController.handleIncomingUser(callingPid, callingUid, userId, false,
-                ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE, "service",
-                callingPackage);
+        userId = mAm.mUserController.handleIncomingUser(callingPid, callingUid, userId,
+                /* allowAll= */false, getAllowMode(service, callingPackage),
+                /* name= */ "service", callingPackage);
 
         ServiceMap smap = getServiceMapLocked(userId);
         final ComponentName comp;
@@ -2259,6 +2261,17 @@
         return null;
     }
 
+    private int getAllowMode(Intent service, @Nullable String callingPackage) {
+        if (callingPackage == null || service.getComponent() == null) {
+            return ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
+        }
+        if (callingPackage.equals(service.getComponent().getPackageName())) {
+            return ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE;
+        } else {
+            return ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
+        }
+    }
+
     private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, ">>> EXECUTING "
                 + why + " of " + r + " in app " + r.app);
@@ -2730,7 +2743,7 @@
             created = true;
         } catch (DeadObjectException e) {
             Slog.w(TAG, "Application dead when creating service " + r);
-            mAm.appDiedLocked(app);
+            mAm.appDiedLocked(app, "Died when creating service");
             throw e;
         } finally {
             if (!created) {
@@ -3649,7 +3662,8 @@
                             && proc.pid != 0 && proc.pid != ActivityManagerService.MY_PID
                             && proc.setProcState >= ActivityManager.PROCESS_STATE_LAST_ACTIVITY) {
                         proc.kill("bound to service " + sr.shortInstanceName
-                                + " in dying proc " + (app != null ? app.processName : "??"), true);
+                                + " in dying proc " + (app != null ? app.processName : "??"),
+                                ApplicationExitInfo.REASON_OTHER, true);
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 9085d18..fc4bad7 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -30,10 +30,14 @@
 import android.provider.DeviceConfig.Properties;
 import android.provider.Settings;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.KeyValueListParser;
 import android.util.Slog;
 
 import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * Settings constants that can modify the activity manager's behavior.
@@ -65,6 +69,10 @@
             = "service_usage_interaction_time";
     private static final String KEY_USAGE_STATS_INTERACTION_INTERVAL
             = "usage_stats_interaction_interval";
+    private static final String KEY_IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES =
+            "imperceptible_kill_exempt_packages";
+    private static final String KEY_IMPERCEPTIBLE_KILL_EXEMPT_PROC_STATES =
+            "imperceptible_kill_exempt_proc_states";
     static final String KEY_SERVICE_RESTART_DURATION = "service_restart_duration";
     static final String KEY_SERVICE_RESET_RUN_DURATION = "service_reset_run_duration";
     static final String KEY_SERVICE_RESTART_DURATION_FACTOR = "service_restart_duration_factor";
@@ -282,6 +290,19 @@
     // memory trimming.
     public int CUR_TRIM_CACHED_PROCESSES;
 
+    /**
+     * Packages that can't be killed even if it's requested to be killed on imperceptible.
+     */
+    public ArraySet<String> IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES = new ArraySet<String>();
+
+    /**
+     * Proc State that can't be killed even if it's requested to be killed on imperceptible.
+     */
+    public ArraySet<Integer> IMPERCEPTIBLE_KILL_EXEMPT_PROC_STATES = new ArraySet<Integer>();
+
+    private List<String> mDefaultImperceptibleKillExemptPackages;
+    private List<Integer> mDefaultImperceptibleKillExemptProcStates;
+
     @SuppressWarnings("unused")
     private static final int OOMADJ_UPDATE_POLICY_SLOW = 0;
     private static final int OOMADJ_UPDATE_POLICY_QUICK = 1;
@@ -332,6 +353,10 @@
                             case KEY_OOMADJ_UPDATE_POLICY:
                                 updateOomAdjUpdatePolicy();
                                 break;
+                            case KEY_IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES:
+                            case KEY_IMPERCEPTIBLE_KILL_EXEMPT_PROC_STATES:
+                                updateImperceptibleKillExemptions();
+                                break;
                             default:
                                 break;
                         }
@@ -350,6 +375,13 @@
                 MIN_AUTOMATIC_HEAP_DUMP_PSS_THRESHOLD_BYTES,
                 context.getResources().getInteger(
                         com.android.internal.R.integer.config_debugSystemServerPssThresholdBytes));
+        mDefaultImperceptibleKillExemptPackages = Arrays.asList(
+                context.getResources().getStringArray(
+                com.android.internal.R.array.config_defaultImperceptibleKillingExemptionPkgs));
+        mDefaultImperceptibleKillExemptProcStates = Arrays.stream(
+                context.getResources().getIntArray(
+                com.android.internal.R.array.config_defaultImperceptibleKillingExemptionProcStates))
+                .boxed().collect(Collectors.toList());
     }
 
     public void start(ContentResolver resolver) {
@@ -371,6 +403,7 @@
         updateActivityStartsLoggingEnabled();
         updateBackgroundActivityStarts();
         updateOomAdjUpdatePolicy();
+        updateImperceptibleKillExemptions();
     }
 
     public void setOverrideMaxCachedProcesses(int value) {
@@ -497,6 +530,29 @@
                 == OOMADJ_UPDATE_POLICY_QUICK;
     }
 
+    private void updateImperceptibleKillExemptions() {
+        IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.clear();
+        IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.addAll(mDefaultImperceptibleKillExemptPackages);
+        String val = DeviceConfig.getString(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES, null);
+        if (!TextUtils.isEmpty(val)) {
+            IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.addAll(Arrays.asList(val.split(",")));
+        }
+
+        IMPERCEPTIBLE_KILL_EXEMPT_PROC_STATES.clear();
+        IMPERCEPTIBLE_KILL_EXEMPT_PROC_STATES.addAll(mDefaultImperceptibleKillExemptProcStates);
+        val = DeviceConfig.getString(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_IMPERCEPTIBLE_KILL_EXEMPT_PROC_STATES, null);
+        if (!TextUtils.isEmpty(val)) {
+            Arrays.asList(val.split(",")).stream().forEach((v) -> {
+                try {
+                    IMPERCEPTIBLE_KILL_EXEMPT_PROC_STATES.add(Integer.parseInt(v));
+                } catch (NumberFormatException e) {
+                }
+            });
+        }
+    }
+
     private void updateEnableAutomaticSystemServerHeapDumps() {
         if (!mSystemServerAutomaticHeapDumpEnabled) {
             Slog.wtf(TAG,
@@ -603,6 +659,10 @@
         pw.println(MEMORY_INFO_THROTTLE_TIME);
         pw.print("  "); pw.print(KEY_TOP_TO_FGS_GRACE_DURATION); pw.print("=");
         pw.println(TOP_TO_FGS_GRACE_DURATION);
+        pw.print("  "); pw.print(KEY_IMPERCEPTIBLE_KILL_EXEMPT_PROC_STATES); pw.print("=");
+        pw.println(Arrays.toString(IMPERCEPTIBLE_KILL_EXEMPT_PROC_STATES.toArray()));
+        pw.print("  "); pw.print(KEY_IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES); pw.print("=");
+        pw.println(Arrays.toString(IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.toArray()));
 
         pw.println();
         if (mOverrideMaxCachedProcesses >= 0) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c21adb0..5596b2f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -155,6 +155,7 @@
 import android.app.AppOpsManager;
 import android.app.AppOpsManagerInternal.CheckOpsDelegate;
 import android.app.ApplicationErrorReport;
+import android.app.ApplicationExitInfo;
 import android.app.ApplicationThreadConstants;
 import android.app.BroadcastOptions;
 import android.app.ContentProviderHolder;
@@ -213,6 +214,7 @@
 import android.content.pm.ParceledListSlice;
 import android.content.pm.PathPermission;
 import android.content.pm.PermissionInfo;
+import android.content.pm.ProcessInfo;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.SELinuxUtil;
@@ -362,6 +364,7 @@
 import com.android.server.wm.ActivityServiceConnectionsHolder;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.ActivityTaskManagerService;
+import com.android.server.wm.WindowManagerInternal;
 import com.android.server.wm.WindowManagerService;
 import com.android.server.wm.WindowProcessController;
 
@@ -684,7 +687,7 @@
     /**
      * Process management.
      */
-    final ProcessList mProcessList = new ProcessList();
+    final ProcessList mProcessList;
 
     /**
      * Tracking long-term execution of processes to look for abuse and other
@@ -749,67 +752,23 @@
     }
 
     /**
+     * These are the currently running processes for which we have a ProcessInfo.
+     * Note: needs to be static since the permission checking call chain is static.  This
+     * all probably should be refactored into a separate permission checking object.
+     */
+    @GuardedBy("sActiveProcessInfoSelfLocked")
+    static final SparseArray<ProcessInfo> sActiveProcessInfoSelfLocked = new SparseArray<>();
+
+    /**
      * All of the processes we currently have running organized by pid.
      * The keys are the pid running the application.
      *
      * <p>NOTE: This object is protected by its own lock, NOT the global activity manager lock!
      */
     final PidMap mPidsSelfLocked = new PidMap();
-    final class PidMap {
+    static final class PidMap {
         private final SparseArray<ProcessRecord> mPidMap = new SparseArray<>();
 
-        /**
-         * Puts the process record in the map.
-         * <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this
-         * method.
-         */
-        void put(ProcessRecord app) {
-            synchronized (this) {
-                mPidMap.put(app.pid, app);
-            }
-            mAtmInternal.onProcessMapped(app.pid, app.getWindowProcessController());
-        }
-
-        /**
-         * Removes the process record from the map.
-         * <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this
-         * method.
-         */
-        void remove(ProcessRecord app) {
-            boolean removed = false;
-            synchronized (this) {
-                final ProcessRecord existingApp = mPidMap.get(app.pid);
-                if (existingApp != null && existingApp.startSeq == app.startSeq) {
-                    mPidMap.remove(app.pid);
-                    removed = true;
-                }
-            }
-            if (removed) {
-                mAtmInternal.onProcessUnMapped(app.pid);
-            }
-        }
-
-        /**
-         * Removes the process record from the map if it has a thread.
-         * <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this
-         * method.
-         */
-        boolean removeIfNoThread(ProcessRecord app) {
-            boolean removed = false;
-            synchronized (this) {
-                final ProcessRecord existingApp = get(app.pid);
-                if (existingApp != null && existingApp.startSeq == app.startSeq
-                        && app.thread == null) {
-                    mPidMap.remove(app.pid);
-                    removed = true;
-                }
-            }
-            if (removed) {
-                mAtmInternal.onProcessUnMapped(app.pid);
-            }
-            return removed;
-        }
-
         ProcessRecord get(int pid) {
             return mPidMap.get(pid);
         }
@@ -829,6 +788,82 @@
         int indexOfKey(int key) {
             return mPidMap.indexOfKey(key);
         }
+
+        void doAddInternal(ProcessRecord app) {
+            mPidMap.put(app.pid, app);
+        }
+
+        boolean doRemoveInternal(ProcessRecord app) {
+            final ProcessRecord existingApp = mPidMap.get(app.pid);
+            if (existingApp != null && existingApp.startSeq == app.startSeq) {
+                mPidMap.remove(app.pid);
+                return true;
+            }
+            return false;
+        }
+
+        boolean doRemoveIfNoThreadInternal(ProcessRecord app) {
+            if (app == null || app.thread != null) {
+                return false;
+            }
+            return doRemoveInternal(app);
+        }
+    }
+
+    /**
+     * Puts the process record in the map.
+     * <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this
+     * method.
+     */
+    void addPidLocked(ProcessRecord app) {
+        synchronized (mPidsSelfLocked) {
+            mPidsSelfLocked.doAddInternal(app);
+        }
+        synchronized (sActiveProcessInfoSelfLocked) {
+            if (app.processInfo != null) {
+                sActiveProcessInfoSelfLocked.put(app.pid, app.processInfo);
+            } else {
+                sActiveProcessInfoSelfLocked.remove(app.pid);
+            }
+        }
+        mAtmInternal.onProcessMapped(app.pid, app.getWindowProcessController());
+    }
+
+    /**
+     * Removes the process record from the map.
+     * <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this
+     * method.
+     */
+    void removePidLocked(ProcessRecord app) {
+        final boolean removed;
+        synchronized (mPidsSelfLocked) {
+            removed = mPidsSelfLocked.doRemoveInternal(app);
+        }
+        if (removed) {
+            synchronized (sActiveProcessInfoSelfLocked) {
+                sActiveProcessInfoSelfLocked.remove(app.pid);
+            }
+            mAtmInternal.onProcessUnMapped(app.pid);
+        }
+    }
+
+    /**
+     * Removes the process record from the map if it doesn't have a thread.
+     * <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this
+     * method.
+     */
+    boolean removePidIfNoThread(ProcessRecord app) {
+        final boolean removed;
+        synchronized (mPidsSelfLocked) {
+            removed = mPidsSelfLocked.doRemoveIfNoThreadInternal(app);
+        }
+        if (removed) {
+            synchronized (sActiveProcessInfoSelfLocked) {
+                sActiveProcessInfoSelfLocked.remove(app.pid);
+            }
+            mAtmInternal.onProcessUnMapped(app.pid);
+        }
+        return removed;
     }
 
     /**
@@ -1504,6 +1539,7 @@
 
     @VisibleForTesting
     public WindowManagerService mWindowManager;
+    WindowManagerInternal mWmInternal;
     @VisibleForTesting
     public ActivityTaskManagerService mActivityTaskManager;
     @VisibleForTesting
@@ -1534,7 +1570,7 @@
                 TAG, "Death received in " + this
                 + " for thread " + mAppThread.asBinder());
             synchronized(ActivityManagerService.this) {
-                appDiedLocked(mApp, mPid, mAppThread, true);
+                appDiedLocked(mApp, mPid, mAppThread, true, null);
             }
         }
     }
@@ -2058,7 +2094,7 @@
                 app.getWindowProcessController().setPid(MY_PID);
                 app.maxAdj = ProcessList.SYSTEM_ADJ;
                 app.makeActive(mSystemThread.getApplicationThread(), mProcessStats);
-                mPidsSelfLocked.put(app);
+                addPidLocked(app);
                 mProcessList.updateLruProcessLocked(app, false, null);
                 updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
             }
@@ -2084,6 +2120,7 @@
     public void setWindowManager(WindowManagerService wm) {
         synchronized (this) {
             mWindowManager = wm;
+            mWmInternal = LocalServices.getService(WindowManagerInternal.class);
             mActivityTaskManager.setWindowManager(wm);
         }
     }
@@ -2382,6 +2419,7 @@
                 ? new ActivityManagerConstants(mContext, this, mHandler) : null;
         final ActiveUids activeUids = new ActiveUids(this, false /* postChangesToAtm */);
         mPlatformCompat = null;
+        mProcessList = injector.getProcessList(this);
         mProcessList.init(this, activeUids, mPlatformCompat);
         mLowMemDetector = null;
         mOomAdjuster = hasHandlerThread
@@ -2436,6 +2474,7 @@
         final ActiveUids activeUids = new ActiveUids(this, true /* postChangesToAtm */);
         mPlatformCompat = (PlatformCompat) ServiceManager.getService(
                 Context.PLATFORM_COMPAT_SERVICE);
+        mProcessList = mInjector.getProcessList(this);
         mProcessList.init(this, activeUids, mPlatformCompat);
         mLowMemDetector = new LowMemDetector(this);
         mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids);
@@ -2555,7 +2594,7 @@
             Process.setThreadGroupAndCpuset(BackgroundThread.get().getThreadId(),
                     Process.THREAD_GROUP_SYSTEM);
             Process.setThreadGroupAndCpuset(
-                    mOomAdjuster.mAppCompact.mCompactionThread.getThreadId(),
+                    mOomAdjuster.mCachedAppOptimizer.mCachedAppOptimizerThread.getThreadId(),
                     Process.THREAD_GROUP_SYSTEM);
         } catch (Exception e) {
             Slog.w(TAG, "Setting background thread cpuset failed");
@@ -3680,13 +3719,13 @@
     }
 
     @GuardedBy("this")
-    final void appDiedLocked(ProcessRecord app) {
-       appDiedLocked(app, app.pid, app.thread, false);
+    final void appDiedLocked(ProcessRecord app, String reason) {
+        appDiedLocked(app, app.pid, app.thread, false, reason);
     }
 
     @GuardedBy("this")
     final void appDiedLocked(ProcessRecord app, int pid, IApplicationThread thread,
-            boolean fromBinderDied) {
+            boolean fromBinderDied, String reason) {
         // First check if this ProcessRecord is actually active for the pid.
         synchronized (mPidsSelfLocked) {
             ProcessRecord curProc = mPidsSelfLocked.get(pid);
@@ -3704,6 +3743,8 @@
         if (!app.killed) {
             if (!fromBinderDied) {
                 killProcessQuiet(pid);
+                mProcessList.noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
+                        ApplicationExitInfo.SUBREASON_UNKNOWN, reason);
             }
             ProcessList.killProcessGroup(app.uid, pid);
             app.killed = true;
@@ -4715,7 +4756,7 @@
     @GuardedBy("this")
     private final void processStartTimedOutLocked(ProcessRecord app) {
         final int pid = app.pid;
-        boolean gone = mPidsSelfLocked.removeIfNoThread(app);
+        boolean gone = removePidIfNoThread(app);
 
         if (gone) {
             Slog.w(TAG, "Process " + app + " failed to attach");
@@ -4727,7 +4768,7 @@
             cleanupAppInLaunchingProvidersLocked(app, true);
             // Take care of any services that are waiting for the process.
             mServices.processStartTimedOutLocked(app);
-            app.kill("start timeout", true);
+            app.kill("start timeout", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE, true);
             if (app.isolated) {
                 mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid);
             }
@@ -4788,7 +4829,7 @@
                 // If there is already an app occupying that pid that hasn't been cleaned up
                 cleanUpApplicationRecordLocked(app, false, false, -1,
                             true /*replacingPid*/);
-                mPidsSelfLocked.remove(app);
+                removePidLocked(app);
                 app = null;
             }
         } else {
@@ -4814,6 +4855,8 @@
             if (pid > 0 && pid != MY_PID) {
                 killProcessQuiet(pid);
                 //TODO: killProcessGroup(app.info.uid, pid);
+                mProcessList.noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
+                        ApplicationExitInfo.SUBREASON_UNKNOWN, "attach failed");
             } else {
                 try {
                     thread.scheduleExit();
@@ -4856,7 +4899,7 @@
         updateProcessForegroundLocked(app, false, 0, false);
         app.hasShownUi = false;
         app.setDebugging(false);
-        app.cached = false;
+        app.setCached(false);
         app.killedByAm = false;
         app.killed = false;
 
@@ -5153,7 +5196,7 @@
         }
 
         if (badApp) {
-            app.kill("error during init", true);
+            app.kill("error during init", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE, true);
             handleAppDiedLocked(app, false, true);
             return false;
         }
@@ -5304,7 +5347,7 @@
                                 String data, Bundle extras, boolean ordered,
                                 boolean sticky, int sendingUser) {
                             synchronized (ActivityManagerService.this) {
-                                mOomAdjuster.mAppCompact.compactAllSystem();
+                                mOomAdjuster.mCachedAppOptimizer.compactAllSystem();
                                 requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false);
                             }
                         }
@@ -5886,6 +5929,21 @@
         if (pid == MY_PID) {
             return PackageManager.PERMISSION_GRANTED;
         }
+        // If there is an explicit permission being checked, and this is coming from a process
+        // that has been denied access to that permission, then just deny.  Ultimately this may
+        // not be quite right -- it means that even if the caller would have access for another
+        // reason (such as being the owner of the component it is trying to access), it would still
+        // fail.  This also means the system and root uids would be able to deny themselves
+        // access to permissions, which...  well okay. ¯\_(ツ)_/¯
+        if (permission != null) {
+            synchronized (sActiveProcessInfoSelfLocked) {
+                ProcessInfo procInfo = sActiveProcessInfoSelfLocked.get(pid);
+                if (procInfo != null && procInfo.deniedPermissions != null
+                        && procInfo.deniedPermissions.contains(permission)) {
+                    return PackageManager.PERMISSION_DENIED;
+                }
+            }
+        }
         return ActivityManager.checkComponentPermission(permission, uid,
                 owningUid, exported);
     }
@@ -7544,7 +7602,7 @@
                     proc.info.uid);
             final long ident = Binder.clearCallingIdentity();
             try {
-                appDiedLocked(proc);
+                appDiedLocked(proc, "unstable content provider");
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -8856,7 +8914,7 @@
                 }
                 int adj = proc.setAdj;
                 if (adj >= worstType && !proc.killedByAm) {
-                    proc.kill(reason, true);
+                    proc.kill(reason, ApplicationExitInfo.REASON_OTHER, true);
                     killed = true;
                 }
             }
@@ -8904,7 +8962,7 @@
 
                 final int adj = proc.setAdj;
                 if (adj > belowAdj && !proc.killedByAm) {
-                    proc.kill(reason, true);
+                    proc.kill(reason, ApplicationExitInfo.REASON_PERMISSION_CHANGE, true);
                     killed = true;
                 }
             }
@@ -8912,6 +8970,26 @@
         return killed;
     }
 
+    /**
+     * Similar to {@link #killPids} but killing will be delayed until the device is idle
+     * and the given process is imperceptible.
+     */
+    @Override
+    public void killProcessesWhenImperceptible(int[] pids, String reason) {
+        if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires permission "
+                    + android.Manifest.permission.FORCE_STOP_PACKAGES);
+        }
+        int callerUid = Binder.getCallingUid();
+        long iden = Binder.clearCallingIdentity();
+        try {
+            mProcessList.killProcessesWhenImperceptible(pids, reason, callerUid);
+        } finally {
+            Binder.restoreCallingIdentity(iden);
+        }
+    }
+
     @Override
     public void hang(final IBinder who, boolean allowRestart) {
         if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER)
@@ -9000,7 +9078,7 @@
             final long timeSinceLastIdle = now - mLastIdleTime;
 
             // Compact all non-zygote processes to freshen up the page cache.
-            mOomAdjuster.mAppCompact.compactAllSystem();
+            mOomAdjuster.mCachedAppOptimizer.compactAllSystem();
 
             final long lowRamSinceLastIdle = getLowRamTimeSinceIdle(now);
             mLastIdleTime = now;
@@ -9048,7 +9126,10 @@
                             TimeUtils.formatDuration(lowRamSinceLastIdle, sb);
                             Slog.wtfQuiet(TAG, sb.toString());
                             proc.kill("idle maint (pss " + proc.lastPss
-                                    + " from " + proc.initialIdlePss + ")", true);
+                                    + " from " + proc.initialIdlePss + ")",
+                                    ApplicationExitInfo.REASON_OTHER,
+                                    ApplicationExitInfo.SUBREASON_MEMORY_PRESSURE,
+                                    true);
                         }
                     }
                 } else if (proc.setProcState < ActivityManager.PROCESS_STATE_HOME
@@ -9108,13 +9189,14 @@
             final Resources res = mContext.getResources();
             mAppErrors.loadAppsNotReportingCrashesFromConfigLocked(res.getString(
                     com.android.internal.R.string.config_appsNotReportingCrashes));
-            mUserController.mUserSwitchUiEnabled = !res.getBoolean(
+            final boolean userSwitchUiEnabled = !res.getBoolean(
                     com.android.internal.R.bool.config_customUserSwitchUi);
-            mUserController.mMaxRunningUsers = res.getInteger(
+            final int maxRunningUsers = res.getInteger(
                     com.android.internal.R.integer.config_multiuserMaxRunningUsers);
-            mUserController.mDelayUserDataLocking = res.getBoolean(
+            final boolean delayUserDataLocking = res.getBoolean(
                     com.android.internal.R.bool.config_multiuserDelayUserDataLocking);
-
+            mUserController.setInitialConfig(userSwitchUiEnabled, maxRunningUsers,
+                    delayUserDataLocking);
             mWaitForNetworkTimeoutMs = waitForNetworkTimeoutMs;
             mPssDeferralTime = pssDeferralMs;
         }
@@ -9144,6 +9226,7 @@
             // Make sure we have the current profile info, since it is needed for security checks.
             mUserController.onSystemReady();
             mAppOpsService.systemReady();
+            mProcessList.onSystemReady();
             mSystemReady = true;
             t.traceEnd();
         }
@@ -9673,7 +9756,7 @@
      * Utility function for addErrorToDropBox and handleStrictModeViolation's logging
      * to append various headers to the dropbox log text.
      */
-    private void appendDropBoxProcessHeaders(ProcessRecord process, String processName,
+    void appendDropBoxProcessHeaders(ProcessRecord process, String processName,
             StringBuilder sb) {
         // Watchdog thread ends up invoking this function (with
         // a null ProcessRecord) to add the stack file to dropbox.
@@ -9971,6 +10054,67 @@
     }
 
     @Override
+    public ParceledListSlice<ApplicationExitInfo> getHistoricalProcessExitReasons(
+            String packageName, int pid, int maxNum, int userId) {
+        enforceNotIsolatedCaller("getHistoricalProcessExitReasons");
+
+        // For the simplification, we don't support USER_ALL nor USER_CURRENT here.
+        if (userId == UserHandle.USER_ALL || userId == UserHandle.USER_CURRENT) {
+            throw new IllegalArgumentException("Unsupported userId");
+        }
+
+        final int callingPid = Binder.getCallingPid();
+        final int callingUid = Binder.getCallingUid();
+        final int callingUserId = UserHandle.getCallingUserId();
+        mUserController.handleIncomingUser(callingPid, callingUid, userId, true, ALLOW_NON_FULL,
+                "getHistoricalProcessExitReasons", null);
+
+        final ArrayList<ApplicationExitInfo> results = new ArrayList<ApplicationExitInfo>();
+        if (!TextUtils.isEmpty(packageName)) {
+            final int uid = enforceDumpPermissionForPackage(packageName, userId, callingUid,
+                      "getHistoricalProcessExitReasons");
+            if (uid != Process.INVALID_UID) {
+                mProcessList.mAppExitInfoTracker.getExitInfo(
+                        packageName, uid, pid, maxNum, results);
+            }
+        } else {
+            // If no package name is given, use the caller's uid as the filter uid.
+            mProcessList.mAppExitInfoTracker.getExitInfo(
+                    packageName, callingUid, pid, maxNum, results);
+        }
+
+        return new ParceledListSlice<ApplicationExitInfo>(results);
+    }
+
+    /**
+     * Check if the calling process has the permission to dump given package,
+     * throw SecurityException if it doesn't have the permission.
+     *
+     * @return The UID of the given package, or {@link android.os.Process#INVALID_UID}
+     *         if the package is not found.
+     */
+    private int enforceDumpPermissionForPackage(String packageName, int userId, int callingUid,
+            String function) {
+        long identity = Binder.clearCallingIdentity();
+        int uid = Process.INVALID_UID;
+        try {
+            uid = mPackageManagerInt.getPackageUid(packageName,
+                    MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+        if (uid == Process.INVALID_UID) {
+            return Process.INVALID_UID;
+        }
+        if (UserHandle.getAppId(uid) != UserHandle.getAppId(callingUid)) {
+            // Requires the DUMP permission if the target package doesn't belong
+            // to the caller.
+            enforceCallingPermission(android.Manifest.permission.DUMP, function);
+        }
+        return uid;
+    }
+
+    @Override
     public void getMyMemoryState(ActivityManager.RunningAppProcessInfo outState) {
         if (outState == null) {
             throw new IllegalArgumentException("outState is null");
@@ -10020,7 +10164,7 @@
 
         synchronized(this) {
             mConstants.dump(pw);
-            mOomAdjuster.dumpAppCompactorSettings(pw);
+            mOomAdjuster.dumpCachedAppOptimizerSettings(pw);
             pw.println();
             if (dumpAll) {
                 pw.println("-------------------------------------------------------------------------------");
@@ -10149,6 +10293,12 @@
             if (dumpAll) {
                 pw.println("-------------------------------------------------------------------------------");
             }
+            mProcessList.mAppExitInfoTracker.dumpHistoryProcessExitInfo(pw, dumpPackage);
+            pw.println();
+            if (dumpAll) {
+                pw.println("-------------------------------------------------------------------"
+                        + "------------");
+            }
             dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage, dumpAppId);
             pw.println();
             if (dumpAll) {
@@ -10425,7 +10575,7 @@
             } else if ("settings".equals(cmd)) {
                 synchronized (this) {
                     mConstants.dump(pw);
-                    mOomAdjuster.dumpAppCompactorSettings(pw);
+                    mOomAdjuster.dumpCachedAppOptimizerSettings(pw);
                 }
             } else if ("services".equals(cmd) || "s".equals(cmd)) {
                 if (dumpClient) {
@@ -10447,6 +10597,12 @@
                 synchronized (this) {
                     dumpUsersLocked(pw);
                 }
+            } else if ("exit-info".equals(cmd)) {
+                if (opti < args.length) {
+                    dumpPackage = args[opti];
+                    opti++;
+                }
+                mProcessList.mAppExitInfoTracker.dumpHistoryProcessExitInfo(pw, dumpPackage);
             } else {
                 // Dumping a single activity?
                 if (!mAtmInternal.dumpActivity(fd, pw, cmd, args, opti, dumpAll,
@@ -12046,7 +12202,7 @@
                         r.lastSwapPss*1024, new StringBuilder()));
                 proto.write(ProcessOomProto.Detail.LAST_CACHED_PSS, DebugUtils.sizeValueToString(
                         r.lastCachedPss*1024, new StringBuilder()));
-                proto.write(ProcessOomProto.Detail.CACHED, r.cached);
+                proto.write(ProcessOomProto.Detail.CACHED, r.isCached());
                 proto.write(ProcessOomProto.Detail.EMPTY, r.empty);
                 proto.write(ProcessOomProto.Detail.HAS_ABOVE_CLIENT, r.hasAboveClient);
 
@@ -12173,7 +12329,7 @@
                 pw.println();
                 pw.print(prefix);
                 pw.print("    ");
-                pw.print("cached="); pw.print(r.cached);
+                pw.print("cached="); pw.print(r.isCached());
                 pw.print(" empty="); pw.print(r.empty);
                 pw.print(" hasAboveClient="); pw.println(r.hasAboveClient);
 
@@ -14060,7 +14216,9 @@
                     capp.kill("depends on provider "
                             + cpr.name.flattenToShortString()
                             + " in dying proc " + (proc != null ? proc.processName : "??")
-                            + " (adj " + (proc != null ? proc.setAdj : "??") + ")", true);
+                            + " (adj " + (proc != null ? proc.setAdj : "??") + ")",
+                            ApplicationExitInfo.REASON_OTHER,
+                            true);
                 }
             } else if (capp.thread != null && conn.provider.provider != null) {
                 try {
@@ -14145,6 +14303,7 @@
 
         // Take care of any launching providers waiting for this process.
         if (cleanupAppInLaunchingProvidersLocked(app, false)) {
+            mProcessList.noteProcessDiedLocked(app);
             restart = true;
         }
 
@@ -14241,6 +14400,7 @@
         mProcessesOnHold.remove(app);
 
         mAtmInternal.onCleanUpApplicationRecord(app.getWindowProcessController());
+        mProcessList.noteProcessDiedLocked(app);
 
         if (restart && !app.isolated) {
             // We have components that still need to be running in the
@@ -14255,7 +14415,7 @@
             return true;
         } else if (app.pid > 0 && app.pid != MY_PID) {
             // Goodbye!
-            mPidsSelfLocked.remove(app);
+            removePidLocked(app);
             mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
             mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid);
             if (app.isolated) {
@@ -16408,6 +16568,22 @@
     }
 
     @Override
+    public boolean updateMccMncConfiguration(String mcc, String mnc) {
+        int mccInt, mncInt;
+        try {
+            mccInt = Integer.parseInt(mcc);
+            mncInt = Integer.parseInt(mnc);
+        } catch (NumberFormatException | StringIndexOutOfBoundsException ex) {
+            Slog.e(TAG, "Error parsing mcc: " + mcc + " mnc: " + mnc + ". ex=" + ex);
+            return false;
+        }
+        Configuration config = new Configuration();
+        config.mcc = mccInt;
+        config.mnc = mncInt == 0 ? Configuration.MNC_ZERO : mncInt;
+        return mActivityTaskManager.updateConfiguration(config);
+    }
+
+    @Override
     public int getLaunchedFromUid(IBinder activityToken) {
         return mActivityTaskManager.getLaunchedFromUid(activityToken);
     }
@@ -16951,7 +17127,10 @@
                                     uptimeSince, cputimeUsed);
                         }
                         app.kill("excessive cpu " + cputimeUsed + " during " + uptimeSince
-                                + " dur=" + checkDur + " limit=" + cpuLimit, true);
+                                + " dur=" + checkDur + " limit=" + cpuLimit,
+                                ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE,
+                                ApplicationExitInfo.SUBREASON_EXCESSIVE_CPU,
+                                true);
                         app.baseProcessTracker.reportExcessiveCpu(app.pkgList.mPkgList);
                         for (int ipkg = app.pkgList.size() - 1; ipkg >= 0; ipkg--) {
                             ProcessStats.ProcessStateHolder holder = app.pkgList.valueAt(ipkg);
@@ -17585,7 +17764,10 @@
                     + (app.thread != null ? app.thread.asBinder() : null)
                     + ")\n");
                 if (app.pid > 0 && app.pid != MY_PID) {
-                    app.kill("empty", false);
+                    app.kill("empty",
+                            ApplicationExitInfo.REASON_OTHER,
+                            ApplicationExitInfo.SUBREASON_TRIM_EMPTY,
+                            false);
                 } else if (app.thread != null) {
                     try {
                         app.thread.scheduleExit();
@@ -17926,7 +18108,31 @@
 
     @Override
     public int stopUser(final int userId, boolean force, final IStopUserCallback callback) {
-        return mUserController.stopUser(userId, force, callback, null /* keyEvictedCallback */);
+        return mUserController.stopUser(userId, force, /* allowDelayedLocking= */ false,
+                /* callback= */ callback, /* keyEvictedCallback= */ null);
+    }
+
+    /**
+     * Stops user but allow delayed locking. Delayed locking keeps user unlocked even after
+     * stopping only if {@code config_multiuserDelayUserDataLocking} overlay is set true.
+     *
+     * <p>When delayed locking is not enabled through the overlay, this call becomes the same
+     * with {@link #stopUser(int, boolean, IStopUserCallback)} call.
+     *
+     * @param userId User id to stop.
+     * @param force Force stop the user even if the user is related with system user or current
+     *              user.
+     * @param callback Callback called when user has stopped.
+     *
+     * @return {@link ActivityManager#USER_OP_SUCCESS} when user is stopped successfully. Returns
+     *         other {@code ActivityManager#USER_OP_*} codes for failure.
+     *
+     */
+    @Override
+    public int stopUserWithDelayedLocking(final int userId, boolean force,
+            final IStopUserCallback callback) {
+        return mUserController.stopUser(userId, force, /* allowDelayedLocking= */ true,
+                /* callback= */ callback, /* keyEvictedCallback= */ null);
     }
 
     @Override
@@ -18332,7 +18538,7 @@
 
         @Override
         public int getMaxRunningUsers() {
-            return mUserController.mMaxRunningUsers;
+            return mUserController.getMaxRunningUsers();
         }
 
         @Override
@@ -18391,7 +18597,7 @@
                     final ProcessRecord pr = (ProcessRecord) wpc.mOwner;
                     if (pr.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND
                             && pr.curReceivers.isEmpty()) {
-                        pr.kill("remove task", true);
+                        pr.kill("remove task", ApplicationExitInfo.REASON_OTHER, true);
                     } else {
                         // We delay killing processes that are not in the background or running a
                         // receiver.
@@ -19196,6 +19402,13 @@
             return false;
         }
 
+        /**
+         * Return the process list instance
+         */
+        public ProcessList getProcessList(ActivityManagerService service) {
+            return new ProcessList();
+        }
+
         private boolean ensureHasNetworkManagementInternal() {
             if (mNmi == null) {
                 mNmi = LocalServices.getService(NetworkManagementInternal.class);
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index d7ad1c2..53a967b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -91,7 +91,6 @@
 import com.android.internal.compat.CompatibilityChangeConfig;
 import com.android.internal.util.HexDump;
 import com.android.internal.util.MemInfoReader;
-import com.android.internal.util.Preconditions;
 import com.android.server.compat.PlatformCompat;
 
 import java.io.BufferedReader;
@@ -217,6 +216,8 @@
                     return runSetWatchHeap(pw);
                 case "clear-watch-heap":
                     return runClearWatchHeap(pw);
+                case "clear-exit-info":
+                    return runClearExitInfo(pw);
                 case "bug-report":
                     return runBugReport(pw);
                 case "force-stop":
@@ -1019,6 +1020,30 @@
         return 0;
     }
 
+    int runClearExitInfo(PrintWriter pw) throws RemoteException {
+        mInternal.enforceCallingPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS,
+                "runClearExitInfo()");
+        String opt;
+        int userId = UserHandle.USER_CURRENT;
+        String packageName = null;
+        while ((opt = getNextOption()) != null) {
+            if (opt.equals("--user")) {
+                userId = UserHandle.parseUserArg(getNextArgRequired());
+            } else {
+                packageName = opt;
+            }
+        }
+        if (userId == UserHandle.USER_CURRENT) {
+            UserInfo user = mInterface.getCurrentUser();
+            if (user == null) {
+                return -1;
+            }
+            userId = user.id;
+        }
+        mInternal.mProcessList.mAppExitInfoTracker.clearHistoryProcessExitInfo(packageName, userId);
+        return 0;
+    }
+
     int runBugReport(PrintWriter pw) throws RemoteException {
         String opt;
         boolean fullBugreport = true;
@@ -1145,7 +1170,7 @@
 
         static final int RESULT_ANR_DIALOG = 0;
         static final int RESULT_ANR_KILL = 1;
-        static final int RESULT_ANR_WAIT = 1;
+        static final int RESULT_ANR_WAIT = 2;
 
         int mResult;
 
@@ -1734,7 +1759,7 @@
         return 0;
     }
 
-    private void switchUserAndWaitForComplete(int userId) throws RemoteException {
+    private boolean switchUserAndWaitForComplete(int userId) throws RemoteException {
         // Register switch observer.
         final CountDownLatch switchLatch = new CountDownLatch(1);
         mInterface.registerUserSwitchObserver(
@@ -1748,7 +1773,7 @@
                 }, ActivityManagerShellCommand.class.getName());
 
         // Switch.
-        mInterface.switchUser(userId);
+        boolean switched = mInterface.switchUser(userId);
 
         // Wait.
         try {
@@ -1756,6 +1781,7 @@
         } catch (InterruptedException e) {
             getErrPrintWriter().println("Thread interrupted unexpectedly.");
         }
+        return switched;
     }
 
     int runSwitchUser(PrintWriter pw) throws RemoteException {
@@ -1777,12 +1803,18 @@
         }
 
         int userId = Integer.parseInt(getNextArgRequired());
+        boolean switched;
         if (wait) {
-            switchUserAndWaitForComplete(userId);
+            switched = switchUserAndWaitForComplete(userId);
         } else {
-            mInterface.switchUser(userId);
+            switched = mInterface.switchUser(userId);
         }
-        return 0;
+        if (switched) {
+            return 0;
+        } else {
+            pw.printf("Failed to switch to user %d\n", userId);
+            return 1;
+        }
     }
 
     int runGetCurrentUser(PrintWriter pw) throws RemoteException {
@@ -2359,6 +2391,8 @@
             return UsageStatsManager.STANDBY_BUCKET_FREQUENT;
         } else if (lower.startsWith("ra")) {
             return UsageStatsManager.STANDBY_BUCKET_RARE;
+        } else if (lower.startsWith("re")) {
+            return UsageStatsManager.STANDBY_BUCKET_RESTRICTED;
         } else if (lower.startsWith("ne")) {
             return UsageStatsManager.STANDBY_BUCKET_NEVER;
         } else {
@@ -3008,6 +3042,7 @@
             pw.println("    s[ervices] [COMP_SPEC ...]: service state");
             pw.println("    allowed-associations: current package association restrictions");
             pw.println("    as[sociations]: tracked app associations");
+            pw.println("    exit-info [PACKAGE_NAME]: historical process exit information");
             pw.println("    lmk: stats on low memory killer");
             pw.println("    lru: raw LRU process list");
             pw.println("    binder-proxies: stats on binder objects and IPCs");
@@ -3142,6 +3177,8 @@
             pw.println("      above <HEAP-LIMIT> then a heap dump is collected for the user to report.");
             pw.println("  clear-watch-heap");
             pw.println("      Clear the previously set-watch-heap.");
+            pw.println("  clear-exit-info [--user <USER_ID> | all | current] [package]");
+            pw.println("      Clear the process exit-info for given package");
             pw.println("  bug-report [--progress | --telephony]");
             pw.println("      Request bug report generation; will launch a notification");
             pw.println("        when done to select where it should be delivered. Options are:");
diff --git a/services/core/java/com/android/server/am/AppCompactor.java b/services/core/java/com/android/server/am/AppCompactor.java
deleted file mode 100644
index b7e2065..0000000
--- a/services/core/java/com/android/server/am/AppCompactor.java
+++ /dev/null
@@ -1,771 +0,0 @@
-/*
- * 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.am;
-
-import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
-
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_COMPACTION;
-import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
-
-import android.app.ActivityManager;
-import android.app.ActivityThread;
-import android.os.Debug;
-import android.os.Handler;
-import android.os.Message;
-import android.os.Process;
-import android.os.SystemClock;
-import android.os.Trace;
-import android.provider.DeviceConfig;
-import android.provider.DeviceConfig.OnPropertiesChangedListener;
-import android.provider.DeviceConfig.Properties;
-import android.text.TextUtils;
-import android.util.EventLog;
-import android.util.Slog;
-import android.util.StatsLog;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.ServiceThread;
-
-import java.io.FileOutputStream;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.Random;
-import java.util.Set;
-
-public final class AppCompactor {
-
-    // Flags stored in the DeviceConfig API.
-    @VisibleForTesting static final String KEY_USE_COMPACTION = "use_compaction";
-    @VisibleForTesting static final String KEY_COMPACT_ACTION_1 = "compact_action_1";
-    @VisibleForTesting static final String KEY_COMPACT_ACTION_2 = "compact_action_2";
-    @VisibleForTesting static final String KEY_COMPACT_THROTTLE_1 = "compact_throttle_1";
-    @VisibleForTesting static final String KEY_COMPACT_THROTTLE_2 = "compact_throttle_2";
-    @VisibleForTesting static final String KEY_COMPACT_THROTTLE_3 = "compact_throttle_3";
-    @VisibleForTesting static final String KEY_COMPACT_THROTTLE_4 = "compact_throttle_4";
-    @VisibleForTesting static final String KEY_COMPACT_THROTTLE_5 = "compact_throttle_5";
-    @VisibleForTesting static final String KEY_COMPACT_THROTTLE_6 = "compact_throttle_6";
-    @VisibleForTesting static final String KEY_COMPACT_STATSD_SAMPLE_RATE =
-            "compact_statsd_sample_rate";
-    @VisibleForTesting static final String KEY_COMPACT_FULL_RSS_THROTTLE_KB =
-            "compact_full_rss_throttle_kb";
-    @VisibleForTesting static final String KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB =
-            "compact_full_delta_rss_throttle_kb";
-    @VisibleForTesting static final String KEY_COMPACT_PROC_STATE_THROTTLE =
-            "compact_proc_state_throttle";
-
-    // Phenotype sends int configurations and we map them to the strings we'll use on device,
-    // preventing a weird string value entering the kernel.
-    private static final int COMPACT_ACTION_FILE_FLAG = 1;
-    private static final int COMPACT_ACTION_ANON_FLAG = 2;
-    private static final int COMPACT_ACTION_FULL_FLAG = 3;
-    private static final int COMPACT_ACTION_NONE_FLAG = 4;
-    private static final String COMPACT_ACTION_NONE = "";
-    private static final String COMPACT_ACTION_FILE = "file";
-    private static final String COMPACT_ACTION_ANON = "anon";
-    private static final String COMPACT_ACTION_FULL = "all";
-
-    // Defaults for phenotype flags.
-    @VisibleForTesting static final Boolean DEFAULT_USE_COMPACTION = false;
-    @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_1 = COMPACT_ACTION_FILE_FLAG;
-    @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_2 = COMPACT_ACTION_FULL_FLAG;
-    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_1 = 5_000;
-    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_2 = 10_000;
-    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_3 = 500;
-    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_4 = 10_000;
-    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_5 = 10 * 60 * 1000;
-    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_6 = 10 * 60 * 1000;
-    // The sampling rate to push app compaction events into statsd for upload.
-    @VisibleForTesting static final float DEFAULT_STATSD_SAMPLE_RATE = 0.1f;
-    @VisibleForTesting static final long DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB = 12_000L;
-    @VisibleForTesting static final long DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB = 8_000L;
-    // Format of this string should be a comma separated list of integers.
-    @VisibleForTesting static final String DEFAULT_COMPACT_PROC_STATE_THROTTLE =
-            String.valueOf(ActivityManager.PROCESS_STATE_RECEIVER);
-
-    @VisibleForTesting
-    interface PropertyChangedCallbackForTest {
-        void onPropertyChanged();
-    }
-    private PropertyChangedCallbackForTest mTestCallback;
-
-    // Handler constants.
-    static final int COMPACT_PROCESS_SOME = 1;
-    static final int COMPACT_PROCESS_FULL = 2;
-    static final int COMPACT_PROCESS_PERSISTENT = 3;
-    static final int COMPACT_PROCESS_BFGS = 4;
-    static final int COMPACT_PROCESS_MSG = 1;
-    static final int COMPACT_SYSTEM_MSG = 2;
-
-    /**
-     * This thread must be moved to the system background cpuset.
-     * If that doesn't happen, it's probably going to draw a lot of power.
-     * However, this has to happen after the first updateOomAdjLocked, because
-     * that will wipe out the cpuset assignment for system_server threads.
-     * Accordingly, this is in the AMS constructor.
-     */
-    final ServiceThread mCompactionThread;
-
-    private final ArrayList<ProcessRecord> mPendingCompactionProcesses =
-            new ArrayList<ProcessRecord>();
-    private final ActivityManagerService mAm;
-    private final OnPropertiesChangedListener mOnFlagsChangedListener =
-            new OnPropertiesChangedListener() {
-                @Override
-                public void onPropertiesChanged(Properties properties) {
-                    synchronized (mPhenotypeFlagLock) {
-                        for (String name : properties.getKeyset()) {
-                            if (KEY_USE_COMPACTION.equals(name)) {
-                                updateUseCompaction();
-                            } else if (KEY_COMPACT_ACTION_1.equals(name)
-                                    || KEY_COMPACT_ACTION_2.equals(name)) {
-                                updateCompactionActions();
-                            } else if (KEY_COMPACT_THROTTLE_1.equals(name)
-                                    || KEY_COMPACT_THROTTLE_2.equals(name)
-                                    || KEY_COMPACT_THROTTLE_3.equals(name)
-                                    || KEY_COMPACT_THROTTLE_4.equals(name)) {
-                                updateCompactionThrottles();
-                            } else if (KEY_COMPACT_STATSD_SAMPLE_RATE.equals(name)) {
-                                updateStatsdSampleRate();
-                            } else if (KEY_COMPACT_FULL_RSS_THROTTLE_KB.equals(name)) {
-                                updateFullRssThrottle();
-                            } else if (KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB.equals(name)) {
-                                updateFullDeltaRssThrottle();
-                            } else if (KEY_COMPACT_PROC_STATE_THROTTLE.equals(name)) {
-                                updateProcStateThrottle();
-                            }
-                        }
-                    }
-                    if (mTestCallback != null) {
-                        mTestCallback.onPropertyChanged();
-                    }
-                }
-            };
-
-    private final Object mPhenotypeFlagLock = new Object();
-
-    // Configured by phenotype. Updates from the server take effect immediately.
-    @GuardedBy("mPhenotypeFlagLock")
-    @VisibleForTesting volatile String mCompactActionSome =
-            compactActionIntToString(DEFAULT_COMPACT_ACTION_1);
-    @GuardedBy("mPhenotypeFlagLock")
-    @VisibleForTesting volatile String mCompactActionFull =
-            compactActionIntToString(DEFAULT_COMPACT_ACTION_2);
-    @GuardedBy("mPhenotypeFlagLock")
-    @VisibleForTesting volatile long mCompactThrottleSomeSome = DEFAULT_COMPACT_THROTTLE_1;
-    @GuardedBy("mPhenotypeFlagLock")
-    @VisibleForTesting volatile long mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2;
-    @GuardedBy("mPhenotypeFlagLock")
-    @VisibleForTesting volatile long mCompactThrottleFullSome = DEFAULT_COMPACT_THROTTLE_3;
-    @GuardedBy("mPhenotypeFlagLock")
-    @VisibleForTesting volatile long mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4;
-    @GuardedBy("mPhenotypeFlagLock")
-    @VisibleForTesting volatile long mCompactThrottleBFGS = DEFAULT_COMPACT_THROTTLE_5;
-    @GuardedBy("mPhenotypeFlagLock")
-    @VisibleForTesting volatile long mCompactThrottlePersistent = DEFAULT_COMPACT_THROTTLE_6;
-    @GuardedBy("mPhenotypeFlagLock")
-    private volatile boolean mUseCompaction = DEFAULT_USE_COMPACTION;
-    private final Random mRandom = new Random();
-    @GuardedBy("mPhenotypeFlagLock")
-    @VisibleForTesting volatile float mStatsdSampleRate = DEFAULT_STATSD_SAMPLE_RATE;
-    @GuardedBy("mPhenotypeFlagLock")
-    @VisibleForTesting volatile long mFullAnonRssThrottleKb =
-            DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB;
-    @GuardedBy("mPhenotypeFlagLock")
-    @VisibleForTesting volatile long mFullDeltaRssThrottleKb =
-            DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB;
-    @GuardedBy("mPhenotypeFlagLock")
-    @VisibleForTesting final Set<Integer> mProcStateThrottle;
-
-    // Handler on which compaction runs.
-    private Handler mCompactionHandler;
-
-    // Maps process ID to last compaction statistics for processes that we've fully compacted. Used
-    // when evaluating throttles that we only consider for "full" compaction, so we don't store
-    // data for "some" compactions.
-    private Map<Integer, LastCompactionStats> mLastCompactionStats =
-            new LinkedHashMap<Integer, LastCompactionStats>() {
-                @Override
-                protected boolean removeEldestEntry(Map.Entry eldest) {
-                    return size() > 100;
-                }
-    };
-
-    private int mSomeCompactionCount;
-    private int mFullCompactionCount;
-    private int mPersistentCompactionCount;
-    private int mBfgsCompactionCount;
-
-    public AppCompactor(ActivityManagerService am) {
-        mAm = am;
-        mCompactionThread = new ServiceThread("CompactionThread",
-                THREAD_PRIORITY_FOREGROUND, true);
-        mProcStateThrottle = new HashSet<>();
-    }
-
-    @VisibleForTesting
-    AppCompactor(ActivityManagerService am, PropertyChangedCallbackForTest callback) {
-        this(am);
-        mTestCallback = callback;
-    }
-
-    /**
-     * Reads phenotype config to determine whether app compaction is enabled or not and
-     * starts the background thread if necessary.
-     */
-    public void init() {
-        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                ActivityThread.currentApplication().getMainExecutor(), mOnFlagsChangedListener);
-        synchronized (mPhenotypeFlagLock) {
-            updateUseCompaction();
-            updateCompactionActions();
-            updateCompactionThrottles();
-            updateStatsdSampleRate();
-            updateFullRssThrottle();
-            updateFullDeltaRssThrottle();
-            updateProcStateThrottle();
-        }
-        Process.setThreadGroupAndCpuset(mCompactionThread.getThreadId(),
-                Process.THREAD_GROUP_SYSTEM);
-    }
-
-    /**
-     * Returns whether compaction is enabled.
-     */
-    public boolean useCompaction() {
-        synchronized (mPhenotypeFlagLock) {
-            return mUseCompaction;
-        }
-    }
-
-    @GuardedBy("mAm")
-    void dump(PrintWriter pw) {
-        pw.println("AppCompactor settings");
-        synchronized (mPhenotypeFlagLock) {
-            pw.println("  " + KEY_USE_COMPACTION + "=" + mUseCompaction);
-            pw.println("  " + KEY_COMPACT_ACTION_1 + "=" + mCompactActionSome);
-            pw.println("  " + KEY_COMPACT_ACTION_2 + "=" + mCompactActionFull);
-            pw.println("  " + KEY_COMPACT_THROTTLE_1 + "=" + mCompactThrottleSomeSome);
-            pw.println("  " + KEY_COMPACT_THROTTLE_2 + "=" + mCompactThrottleSomeFull);
-            pw.println("  " + KEY_COMPACT_THROTTLE_3 + "=" + mCompactThrottleFullSome);
-            pw.println("  " + KEY_COMPACT_THROTTLE_4 + "=" + mCompactThrottleFullFull);
-            pw.println("  " + KEY_COMPACT_THROTTLE_5 + "=" + mCompactThrottleBFGS);
-            pw.println("  " + KEY_COMPACT_THROTTLE_6 + "=" + mCompactThrottlePersistent);
-            pw.println("  " + KEY_COMPACT_STATSD_SAMPLE_RATE + "=" + mStatsdSampleRate);
-            pw.println("  " + KEY_COMPACT_FULL_RSS_THROTTLE_KB + "="
-                    + mFullAnonRssThrottleKb);
-            pw.println("  " + KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + "="
-                    + mFullDeltaRssThrottleKb);
-            pw.println("  "  + KEY_COMPACT_PROC_STATE_THROTTLE + "="
-                    + Arrays.toString(mProcStateThrottle.toArray(new Integer[0])));
-
-            pw.println("  " + mSomeCompactionCount + " some, " + mFullCompactionCount
-                    + " full, " + mPersistentCompactionCount + " persistent, "
-                    + mBfgsCompactionCount + " BFGS compactions.");
-
-            pw.println("  Tracking last compaction stats for " + mLastCompactionStats.size()
-                    + " processes.");
-            if (DEBUG_COMPACTION) {
-                for (Map.Entry<Integer, LastCompactionStats> entry
-                        : mLastCompactionStats.entrySet()) {
-                    int pid = entry.getKey();
-                    LastCompactionStats stats = entry.getValue();
-                    pw.println("    " + pid + ": "
-                            + Arrays.toString(stats.getRssAfterCompaction()));
-                }
-            }
-        }
-    }
-
-    @GuardedBy("mAm")
-    void compactAppSome(ProcessRecord app) {
-        app.reqCompactAction = COMPACT_PROCESS_SOME;
-        mPendingCompactionProcesses.add(app);
-        mCompactionHandler.sendMessage(
-            mCompactionHandler.obtainMessage(
-                COMPACT_PROCESS_MSG, app.setAdj, app.setProcState));
-    }
-
-    @GuardedBy("mAm")
-    void compactAppFull(ProcessRecord app) {
-        app.reqCompactAction = COMPACT_PROCESS_FULL;
-        mPendingCompactionProcesses.add(app);
-        mCompactionHandler.sendMessage(
-            mCompactionHandler.obtainMessage(
-                COMPACT_PROCESS_MSG, app.setAdj, app.setProcState));
-
-    }
-
-    @GuardedBy("mAm")
-    void compactAppPersistent(ProcessRecord app) {
-        app.reqCompactAction = COMPACT_PROCESS_PERSISTENT;
-        mPendingCompactionProcesses.add(app);
-        mCompactionHandler.sendMessage(
-                mCompactionHandler.obtainMessage(
-                    COMPACT_PROCESS_MSG, app.curAdj, app.setProcState));
-    }
-
-    @GuardedBy("mAm")
-    boolean shouldCompactPersistent(ProcessRecord app, long now) {
-        return (app.lastCompactTime == 0
-                || (now - app.lastCompactTime) > mCompactThrottlePersistent);
-    }
-
-    @GuardedBy("mAm")
-    void compactAppBfgs(ProcessRecord app) {
-        app.reqCompactAction = COMPACT_PROCESS_BFGS;
-        mPendingCompactionProcesses.add(app);
-        mCompactionHandler.sendMessage(
-                mCompactionHandler.obtainMessage(
-                    COMPACT_PROCESS_MSG, app.curAdj, app.setProcState));
-    }
-
-    @GuardedBy("mAm")
-    boolean shouldCompactBFGS(ProcessRecord app, long now) {
-        return (app.lastCompactTime == 0
-                || (now - app.lastCompactTime) > mCompactThrottleBFGS);
-    }
-
-    @GuardedBy("mAm")
-    void compactAllSystem() {
-        if (mUseCompaction) {
-            mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage(
-                                              COMPACT_SYSTEM_MSG));
-        }
-    }
-
-    private native void compactSystem();
-
-    /**
-     * Reads the flag value from DeviceConfig to determine whether app compaction
-     * should be enabled, and starts the compaction thread if needed.
-     */
-    @GuardedBy("mPhenotypeFlagLock")
-    private void updateUseCompaction() {
-        mUseCompaction = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                    KEY_USE_COMPACTION, DEFAULT_USE_COMPACTION);
-        if (mUseCompaction && !mCompactionThread.isAlive()) {
-            mCompactionThread.start();
-            mCompactionHandler = new MemCompactionHandler();
-        }
-    }
-
-    @GuardedBy("mPhenotypeFlagLock")
-    private void updateCompactionActions() {
-        int compactAction1 = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                KEY_COMPACT_ACTION_1, DEFAULT_COMPACT_ACTION_1);
-
-        int compactAction2 = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                KEY_COMPACT_ACTION_2, DEFAULT_COMPACT_ACTION_2);
-
-        mCompactActionSome = compactActionIntToString(compactAction1);
-        mCompactActionFull = compactActionIntToString(compactAction2);
-    }
-
-    @GuardedBy("mPhenotypeFlagLock")
-    private void updateCompactionThrottles() {
-        boolean useThrottleDefaults = false;
-        String throttleSomeSomeFlag =
-                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                    KEY_COMPACT_THROTTLE_1);
-        String throttleSomeFullFlag =
-                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                    KEY_COMPACT_THROTTLE_2);
-        String throttleFullSomeFlag =
-                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                    KEY_COMPACT_THROTTLE_3);
-        String throttleFullFullFlag =
-                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                    KEY_COMPACT_THROTTLE_4);
-        String throttleBFGSFlag =
-                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                    KEY_COMPACT_THROTTLE_5);
-        String throttlePersistentFlag =
-                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                    KEY_COMPACT_THROTTLE_6);
-
-        if (TextUtils.isEmpty(throttleSomeSomeFlag) || TextUtils.isEmpty(throttleSomeFullFlag)
-                || TextUtils.isEmpty(throttleFullSomeFlag)
-                || TextUtils.isEmpty(throttleFullFullFlag)
-                || TextUtils.isEmpty(throttleBFGSFlag)
-                || TextUtils.isEmpty(throttlePersistentFlag)) {
-            // Set defaults for all if any are not set.
-            useThrottleDefaults = true;
-        } else {
-            try {
-                mCompactThrottleSomeSome = Integer.parseInt(throttleSomeSomeFlag);
-                mCompactThrottleSomeFull = Integer.parseInt(throttleSomeFullFlag);
-                mCompactThrottleFullSome = Integer.parseInt(throttleFullSomeFlag);
-                mCompactThrottleFullFull = Integer.parseInt(throttleFullFullFlag);
-                mCompactThrottleBFGS = Integer.parseInt(throttleBFGSFlag);
-                mCompactThrottlePersistent = Integer.parseInt(throttlePersistentFlag);
-            } catch (NumberFormatException e) {
-                useThrottleDefaults = true;
-            }
-        }
-
-        if (useThrottleDefaults) {
-            mCompactThrottleSomeSome = DEFAULT_COMPACT_THROTTLE_1;
-            mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2;
-            mCompactThrottleFullSome = DEFAULT_COMPACT_THROTTLE_3;
-            mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4;
-            mCompactThrottleBFGS = DEFAULT_COMPACT_THROTTLE_5;
-            mCompactThrottlePersistent = DEFAULT_COMPACT_THROTTLE_6;
-        }
-    }
-
-    @GuardedBy("mPhenotypeFlagLock")
-    private void updateStatsdSampleRate() {
-        mStatsdSampleRate = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                KEY_COMPACT_STATSD_SAMPLE_RATE, DEFAULT_STATSD_SAMPLE_RATE);
-        mStatsdSampleRate = Math.min(1.0f, Math.max(0.0f, mStatsdSampleRate));
-    }
-
-    @GuardedBy("mPhenotypeFlagLock")
-    private void updateFullRssThrottle() {
-        mFullAnonRssThrottleKb = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                KEY_COMPACT_FULL_RSS_THROTTLE_KB, DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
-
-        // Don't allow negative values. 0 means don't apply the throttle.
-        if (mFullAnonRssThrottleKb < 0) {
-            mFullAnonRssThrottleKb = DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB;
-        }
-    }
-
-    @GuardedBy("mPhenotypeFlagLock")
-    private void updateFullDeltaRssThrottle() {
-        mFullDeltaRssThrottleKb = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
-
-        if (mFullDeltaRssThrottleKb < 0) {
-            mFullDeltaRssThrottleKb = DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB;
-        }
-    }
-
-    @GuardedBy("mPhenotypeFlagLock")
-    private void updateProcStateThrottle() {
-        String procStateThrottleString = DeviceConfig.getString(
-                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_COMPACT_PROC_STATE_THROTTLE,
-                DEFAULT_COMPACT_PROC_STATE_THROTTLE);
-        if (!parseProcStateThrottle(procStateThrottleString)) {
-            Slog.w(TAG_AM, "Unable to parse app compact proc state throttle \""
-                    + procStateThrottleString + "\" falling back to default.");
-            if (!parseProcStateThrottle(DEFAULT_COMPACT_PROC_STATE_THROTTLE)) {
-                Slog.wtf(TAG_AM,
-                        "Unable to parse default app compact proc state throttle "
-                                + DEFAULT_COMPACT_PROC_STATE_THROTTLE);
-            }
-        }
-    }
-
-    private boolean parseProcStateThrottle(String procStateThrottleString) {
-        String[] procStates = TextUtils.split(procStateThrottleString, ",");
-        mProcStateThrottle.clear();
-        for (String procState : procStates) {
-            try {
-                mProcStateThrottle.add(Integer.parseInt(procState));
-            } catch (NumberFormatException e) {
-                Slog.e(TAG_AM, "Failed to parse default app compaction proc state: "
-                        + procState);
-                return false;
-            }
-        }
-        return true;
-    }
-
-    @VisibleForTesting
-    static String compactActionIntToString(int action) {
-        switch(action) {
-            case COMPACT_ACTION_NONE_FLAG:
-                return COMPACT_ACTION_NONE;
-            case COMPACT_ACTION_FILE_FLAG:
-                return COMPACT_ACTION_FILE;
-            case COMPACT_ACTION_ANON_FLAG:
-                return COMPACT_ACTION_ANON;
-            case COMPACT_ACTION_FULL_FLAG:
-                return COMPACT_ACTION_FULL;
-            default:
-                return COMPACT_ACTION_NONE;
-        }
-    }
-
-    private static final class LastCompactionStats {
-        private final long[] mRssAfterCompaction;
-
-        LastCompactionStats(long[] rss) {
-            mRssAfterCompaction = rss;
-        }
-
-        long[] getRssAfterCompaction() {
-            return mRssAfterCompaction;
-        }
-    }
-
-    private final class MemCompactionHandler extends Handler {
-        private MemCompactionHandler() {
-            super(mCompactionThread.getLooper());
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case COMPACT_PROCESS_MSG: {
-                    long start = SystemClock.uptimeMillis();
-                    ProcessRecord proc;
-                    int pid;
-                    String action;
-                    final String name;
-                    int pendingAction, lastCompactAction;
-                    long lastCompactTime;
-                    LastCompactionStats lastCompactionStats;
-                    int lastOomAdj = msg.arg1;
-                    int procState = msg.arg2;
-                    synchronized (mAm) {
-                        proc = mPendingCompactionProcesses.remove(0);
-
-                        pendingAction = proc.reqCompactAction;
-                        pid = proc.pid;
-                        name = proc.processName;
-
-                        // don't compact if the process has returned to perceptible
-                        // and this is only a cached/home/prev compaction
-                        if ((pendingAction == COMPACT_PROCESS_SOME
-                                || pendingAction == COMPACT_PROCESS_FULL)
-                                && (proc.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ)) {
-                            if (DEBUG_COMPACTION) {
-                                Slog.d(TAG_AM,
-                                        "Skipping compaction as process " + name + " is "
-                                        + "now perceptible.");
-                            }
-                            return;
-                        }
-
-                        lastCompactAction = proc.lastCompactAction;
-                        lastCompactTime = proc.lastCompactTime;
-                        // remove rather than get so that insertion order will be updated when we
-                        // put the post-compaction stats back into the map.
-                        lastCompactionStats = mLastCompactionStats.remove(pid);
-                    }
-
-                    if (pid == 0) {
-                        // not a real process, either one being launched or one being killed
-                        return;
-                    }
-
-                    // basic throttling
-                    // use the Phenotype flag knobs to determine whether current/prevous
-                    // compaction combo should be throtted or not
-
-                    // Note that we explicitly don't take mPhenotypeFlagLock here as the flags
-                    // should very seldom change, and taking the risk of using the wrong action is
-                    // preferable to taking the lock for every single compaction action.
-                    if (lastCompactTime != 0) {
-                        if (pendingAction == COMPACT_PROCESS_SOME) {
-                            if ((lastCompactAction == COMPACT_PROCESS_SOME
-                                    && (start - lastCompactTime < mCompactThrottleSomeSome))
-                                    || (lastCompactAction == COMPACT_PROCESS_FULL
-                                        && (start - lastCompactTime
-                                                < mCompactThrottleSomeFull))) {
-                                if (DEBUG_COMPACTION) {
-                                    Slog.d(TAG_AM, "Skipping some compaction for " + name
-                                            + ": too soon. throttle=" + mCompactThrottleSomeSome
-                                            + "/" + mCompactThrottleSomeFull + " last="
-                                            + (start - lastCompactTime) + "ms ago");
-                                }
-                                return;
-                            }
-                        } else if (pendingAction == COMPACT_PROCESS_FULL) {
-                            if ((lastCompactAction == COMPACT_PROCESS_SOME
-                                    && (start - lastCompactTime < mCompactThrottleFullSome))
-                                    || (lastCompactAction == COMPACT_PROCESS_FULL
-                                        && (start - lastCompactTime
-                                                < mCompactThrottleFullFull))) {
-                                if (DEBUG_COMPACTION) {
-                                    Slog.d(TAG_AM, "Skipping full compaction for " + name
-                                            + ": too soon. throttle=" + mCompactThrottleFullSome
-                                            + "/" + mCompactThrottleFullFull + " last="
-                                            + (start - lastCompactTime) + "ms ago");
-                                }
-                                return;
-                            }
-                        } else if (pendingAction == COMPACT_PROCESS_PERSISTENT) {
-                            if (start - lastCompactTime < mCompactThrottlePersistent) {
-                                if (DEBUG_COMPACTION) {
-                                    Slog.d(TAG_AM, "Skipping persistent compaction for " + name
-                                            + ": too soon. throttle=" + mCompactThrottlePersistent
-                                            + " last=" + (start - lastCompactTime) + "ms ago");
-                                }
-                                return;
-                            }
-                        } else if (pendingAction == COMPACT_PROCESS_BFGS) {
-                            if (start - lastCompactTime < mCompactThrottleBFGS) {
-                                if (DEBUG_COMPACTION) {
-                                    Slog.d(TAG_AM, "Skipping bfgs compaction for " + name
-                                            + ": too soon. throttle=" + mCompactThrottleBFGS
-                                            + " last=" + (start - lastCompactTime) + "ms ago");
-                                }
-                                return;
-                            }
-                        }
-                    }
-
-                    switch (pendingAction) {
-                        case COMPACT_PROCESS_SOME:
-                            action = mCompactActionSome;
-                            break;
-                        // For the time being, treat these as equivalent.
-                        case COMPACT_PROCESS_FULL:
-                        case COMPACT_PROCESS_PERSISTENT:
-                        case COMPACT_PROCESS_BFGS:
-                            action = mCompactActionFull;
-                            break;
-                        default:
-                            action = COMPACT_ACTION_NONE;
-                            break;
-                    }
-
-                    if (COMPACT_ACTION_NONE.equals(action)) {
-                        return;
-                    }
-
-                    if (mProcStateThrottle.contains(procState)) {
-                        if (DEBUG_COMPACTION) {
-                            Slog.d(TAG_AM, "Skipping full compaction for process " + name
-                                    + "; proc state is " + procState);
-                        }
-                        return;
-                    }
-
-                    long[] rssBefore = Process.getRss(pid);
-                    long anonRssBefore = rssBefore[2];
-
-                    if (rssBefore[0] == 0 && rssBefore[1] == 0 && rssBefore[2] == 0
-                            && rssBefore[3] == 0) {
-                        if (DEBUG_COMPACTION) {
-                            Slog.d(TAG_AM, "Skipping compaction for" + "process " + pid
-                                    + " with no memory usage. Dead?");
-                        }
-                        return;
-                    }
-
-                    if (action.equals(COMPACT_ACTION_FULL) || action.equals(COMPACT_ACTION_ANON)) {
-                        if (mFullAnonRssThrottleKb > 0L
-                                && anonRssBefore < mFullAnonRssThrottleKb) {
-                            if (DEBUG_COMPACTION) {
-                                Slog.d(TAG_AM, "Skipping full compaction for process "
-                                        + name + "; anon RSS is too small: " + anonRssBefore
-                                        + "KB.");
-                            }
-                            return;
-                        }
-
-                        if (lastCompactionStats != null && mFullDeltaRssThrottleKb > 0L) {
-                            long[] lastRss = lastCompactionStats.getRssAfterCompaction();
-                            long absDelta = Math.abs(rssBefore[1] - lastRss[1])
-                                    + Math.abs(rssBefore[2] - lastRss[2])
-                                    + Math.abs(rssBefore[3] - lastRss[3]);
-                            if (absDelta <= mFullDeltaRssThrottleKb) {
-                                if (DEBUG_COMPACTION) {
-                                    Slog.d(TAG_AM, "Skipping full compaction for process "
-                                            + name + "; abs delta is too small: " + absDelta
-                                            + "KB.");
-                                }
-                                return;
-                            }
-                        }
-                    }
-
-                    // Now we've passed through all the throttles and are going to compact, update
-                    // bookkeeping.
-                    switch (pendingAction) {
-                        case COMPACT_PROCESS_SOME:
-                            mSomeCompactionCount++;
-                            break;
-                        case COMPACT_PROCESS_FULL:
-                            mFullCompactionCount++;
-                            break;
-                        case COMPACT_PROCESS_PERSISTENT:
-                            mPersistentCompactionCount++;
-                            break;
-                        case COMPACT_PROCESS_BFGS:
-                            mBfgsCompactionCount++;
-                            break;
-                        default:
-                            break;
-                    }
-
-                    try {
-                        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact "
-                                + ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full")
-                                + ": " + name);
-                        long zramFreeKbBefore = Debug.getZramFreeKb();
-                        FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim");
-                        fos.write(action.getBytes());
-                        fos.close();
-                        long[] rssAfter = Process.getRss(pid);
-                        long end = SystemClock.uptimeMillis();
-                        long time = end - start;
-                        long zramFreeKbAfter = Debug.getZramFreeKb();
-                        EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name, action,
-                                rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
-                                rssAfter[0] - rssBefore[0], rssAfter[1] - rssBefore[1],
-                                rssAfter[2] - rssBefore[2], rssAfter[3] - rssBefore[3], time,
-                                lastCompactAction, lastCompactTime, lastOomAdj, procState,
-                                zramFreeKbBefore, zramFreeKbAfter - zramFreeKbBefore);
-
-                        // Note that as above not taking mPhenoTypeFlagLock here to avoid locking
-                        // on every single compaction for a flag that will seldom change and the
-                        // impact of reading the wrong value here is low.
-                        if (mRandom.nextFloat() < mStatsdSampleRate) {
-                            StatsLog.write(StatsLog.APP_COMPACTED, pid, name, pendingAction,
-                                    rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
-                                    rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time,
-                                    lastCompactAction, lastCompactTime, lastOomAdj,
-                                    ActivityManager.processStateAmToProto(procState),
-                                    zramFreeKbBefore, zramFreeKbAfter);
-                        }
-
-                        synchronized (mAm) {
-                            proc.lastCompactTime = end;
-                            proc.lastCompactAction = pendingAction;
-                        }
-
-                        if (action.equals(COMPACT_ACTION_FULL)
-                                || action.equals(COMPACT_ACTION_ANON)) {
-                            mLastCompactionStats.put(pid, new LastCompactionStats(rssAfter));
-                        }
-                    } catch (Exception e) {
-                        // nothing to do, presumably the process died
-                    } finally {
-                        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-                    }
-                    break;
-                }
-                case COMPACT_SYSTEM_MSG: {
-                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "compactSystem");
-                    compactSystem();
-                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-                    break;
-                }
-            }
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 5e48dcf..145f91b 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -29,12 +29,11 @@
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.ApplicationErrorReport;
+import android.app.ApplicationExitInfo;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.ModuleInfo;
-import android.content.pm.PackageManager;
 import android.content.pm.VersionedPackage;
 import android.net.Uri;
 import android.os.Binder;
@@ -56,7 +55,6 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.server.PackageWatchdog;
-import com.android.server.RescueParty;
 import com.android.server.wm.WindowProcessController;
 
 import java.io.FileDescriptor;
@@ -318,11 +316,23 @@
     }
 
     void killAppAtUserRequestLocked(ProcessRecord app) {
-        app.getDialogController().clearAllErrorDialogs();
-        killAppImmediateLocked(app, "user-terminated", "user request after error");
+        ProcessRecord.ErrorDialogController controller =
+                app.getDialogController();
+
+        int reasonCode = ApplicationExitInfo.REASON_ANR;
+        int subReason = ApplicationExitInfo.SUBREASON_UNKNOWN;
+        if (controller.hasDebugWaitingDialog()) {
+            reasonCode = ApplicationExitInfo.REASON_OTHER;
+            subReason = ApplicationExitInfo.SUBREASON_WAIT_FOR_DEBUGGER;
+        }
+
+        controller.clearAllErrorDialogs();
+        killAppImmediateLocked(app, reasonCode, subReason,
+                "user-terminated", "user request after error");
     }
 
-    private void killAppImmediateLocked(ProcessRecord app, String reason, String killReason) {
+    private void killAppImmediateLocked(ProcessRecord app, int reasonCode, int subReason,
+            String reason, String killReason) {
         app.setCrashing(false);
         app.crashingReport = null;
         app.setNotResponding(false);
@@ -330,7 +340,7 @@
         if (app.pid > 0 && app.pid != MY_PID) {
             handleAppCrashLocked(app, reason,
                     null /*shortMsg*/, null /*longMsg*/, null /*stackTrace*/, null /*data*/);
-            app.kill(killReason, true);
+            app.kill(killReason, reasonCode, subReason, true);
         }
     }
 
@@ -384,7 +394,9 @@
             mService.mHandler.postDelayed(
                     () -> {
                         synchronized (mService) {
-                            killAppImmediateLocked(p, "forced", "killed for invalid state");
+                            killAppImmediateLocked(p, ApplicationExitInfo.REASON_OTHER,
+                                    ApplicationExitInfo.SUBREASON_UNKNOWN,
+                                    "forced", "killed for invalid state");
                         }
                     },
                     5000L);
@@ -423,30 +435,15 @@
         }
 
         if (r != null) {
-            boolean isApexModule = false;
-            try {
-                for (String androidPackage : r.getPackageList()) {
-                    ModuleInfo moduleInfo = mContext.getPackageManager().getModuleInfo(
-                            androidPackage, /*flags=*/ 0);
-                    if (moduleInfo != null) {
-                        isApexModule = true;
-                        break;
-                    }
-                }
-            } catch (IllegalStateException | PackageManager.NameNotFoundException e) {
-                // Call to PackageManager#getModuleInfo() can result in NameNotFoundException or
-                // IllegalStateException. In case they are thrown, there isn't much we can do
-                // other than proceed with app crash handling.
-            }
-
-            if (r.isPersistent() || isApexModule) {
-                // If a persistent app or apex module is stuck in a crash loop, the device isn't
-                // very usable, so we want to consider sending out a rescue party.
-                RescueParty.noteAppCrash(mContext, r.uid);
-            }
-
             mPackageWatchdog.onPackageFailure(r.getPackageListWithVersionCode(),
                     PackageWatchdog.FAILURE_REASON_APP_CRASH);
+
+            mService.mProcessList.noteAppKill(r, (crashInfo != null
+                      && "Native crash".equals(crashInfo.exceptionClassName))
+                      ? ApplicationExitInfo.REASON_CRASH_NATIVE
+                      : ApplicationExitInfo.REASON_CRASH,
+                      ApplicationExitInfo.SUBREASON_UNKNOWN,
+                    "crash");
         }
 
         final int relaunchReason = r != null
@@ -513,7 +510,8 @@
                 stopReportingCrashesLocked(r);
             }
             if (res == AppErrorDialog.RESTART) {
-                mService.mProcessList.removeProcessLocked(r, false, true, "crash");
+                mService.mProcessList.removeProcessLocked(r, false, true, "crash",
+                        ApplicationExitInfo.REASON_CRASH);
                 if (taskId != INVALID_TASK_ID) {
                     try {
                         mService.startActivityFromRecents(taskId,
@@ -531,7 +529,8 @@
                     // Kill it with fire!
                     mService.mAtmInternal.onHandleAppCrash(r.getWindowProcessController());
                     if (!r.isPersistent()) {
-                        mService.mProcessList.removeProcessLocked(r, false, false, "crash");
+                        mService.mProcessList.removeProcessLocked(r, false, false, "crash",
+                                ApplicationExitInfo.REASON_CRASH);
                         mService.mAtmInternal.resumeTopActivities(false /* scheduleIdle */);
                     }
                 } finally {
@@ -574,22 +573,26 @@
 
         return mService.mAtmInternal.handleAppCrashInActivityController(
                 name, pid, shortMsg, longMsg, timeMillis, crashInfo.stackTrace, () -> {
-            if ("1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0"))
-                    && "Native crash".equals(crashInfo.exceptionClassName)) {
-                Slog.w(TAG, "Skip killing native crashed app " + name
-                        + "(" + pid + ") during testing");
-            } else {
-                Slog.w(TAG, "Force-killing crashed app " + name + " at watcher's request");
-                if (r != null) {
-                    if (!makeAppCrashingLocked(r, shortMsg, longMsg, stackTrace, null)) {
-                        r.kill("crash", true);
-                    }
+                if ("1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0"))
+                        && "Native crash".equals(crashInfo.exceptionClassName)) {
+                    Slog.w(TAG, "Skip killing native crashed app " + name
+                            + "(" + pid + ") during testing");
                 } else {
-                    // Huh.
-                    Process.killProcess(pid);
-                    ProcessList.killProcessGroup(uid, pid);
+                    Slog.w(TAG, "Force-killing crashed app " + name + " at watcher's request");
+                    if (r != null) {
+                        if (!makeAppCrashingLocked(r, shortMsg, longMsg, stackTrace, null)) {
+                            r.kill("crash", ApplicationExitInfo.REASON_CRASH, true);
+                        }
+                    } else {
+                        // Huh.
+                        Process.killProcess(pid);
+                        ProcessList.killProcessGroup(uid, pid);
+                        mService.mProcessList.noteAppKill(pid, uid,
+                                ApplicationExitInfo.REASON_CRASH,
+                                ApplicationExitInfo.SUBREASON_UNKNOWN,
+                                "crash");
+                    }
                 }
-            }
         });
     }
 
@@ -744,7 +747,8 @@
                 // Don't let services in this process be restarted and potentially
                 // annoy the user repeatedly.  Unless it is persistent, since those
                 // processes run critical code.
-                mService.mProcessList.removeProcessLocked(app, false, tryAgain, "crash");
+                mService.mProcessList.removeProcessLocked(app, false, tryAgain, "crash",
+                        ApplicationExitInfo.REASON_CRASH);
                 mService.mAtmInternal.resumeTopActivities(false /* scheduleIdle */);
                 if (!showBackground) {
                     return false;
diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java
new file mode 100644
index 0000000..bab133f
--- /dev/null
+++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java
@@ -0,0 +1,1304 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.app.ActivityManager.RunningAppProcessInfo.procStateToImportance;
+
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.app.ApplicationExitInfo;
+import android.app.ApplicationExitInfo.Reason;
+import android.app.ApplicationExitInfo.SubReason;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.icu.text.SimpleDateFormat;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.system.OsConstants;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.AtomicFile;
+import android.util.Pair;
+import android.util.Pools.SynchronizedPool;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+import android.util.proto.WireTypeMismatchException;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.ProcessMap;
+import com.android.server.IoThread;
+import com.android.server.SystemServiceManager;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+
+/**
+ * A class to manage all the {@link android.app.ApplicationExitInfo} records.
+ */
+public final class AppExitInfoTracker {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "AppExitInfoTracker" : TAG_AM;
+
+    /**
+     * Interval of persisting the app exit info to persistent storage.
+     */
+    private static final long APP_EXIT_INFO_PERSIST_INTERVAL = TimeUnit.MINUTES.toMillis(30);
+
+    /** These are actions that the forEachPackage should take after each iteration */
+    private static final int FOREACH_ACTION_NONE = 0;
+    private static final int FOREACH_ACTION_REMOVE_PACKAGE = 1;
+    private static final int FOREACH_ACTION_STOP_ITERATION = 2;
+
+    private static final int APP_EXIT_RAW_INFO_POOL_SIZE = 8;
+
+    @VisibleForTesting
+    static final String APP_EXIT_INFO_FILE = "procexitinfo";
+
+    private final Object mLock = new Object();
+
+    /**
+     * Initialized in {@link #init} and read-only after that.
+     */
+    private ActivityManagerService mService;
+
+    /**
+     * Initialized in {@link #init} and read-only after that.
+     */
+    private KillHandler mKillHandler;
+
+    /**
+     * The task to persist app process exit info
+     */
+    @GuardedBy("mLock")
+    private Runnable mAppExitInfoPersistTask = null;
+
+    /**
+     * Last time(in ms) since epoch that the app exit info was persisted into persistent storage.
+     */
+    @GuardedBy("mLock")
+    private long mLastAppExitInfoPersistTimestamp = 0L;
+
+    /**
+     * Retention policy: keep up to X historical exit info per package.
+     *
+     * Initialized in {@link #init} and read-only after that.
+     * Not lock is needed.
+     */
+    private int mAppExitInfoHistoryListSize;
+
+    /*
+     * PackageName/uid -> [pid/info, ...] holder, the uid here is the package uid.
+     */
+    @GuardedBy("mLock")
+    private final ProcessMap<AppExitInfoContainer> mData;
+
+    /** A pool of raw {@link android.app.ApplicationExitInfo} records. */
+    @GuardedBy("mService")
+    private final SynchronizedPool<ApplicationExitInfo> mRawRecordsPool;
+
+    /**
+     * Wheather or not we've loaded the historical app process exit info from
+     * persistent storage.
+     */
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    boolean mAppExitInfoLoaded = false;
+
+    /**
+     * Temporary list being used to filter/sort intermediate results in {@link #getExitInfo}.
+     */
+    @GuardedBy("mLock")
+    final ArrayList<ApplicationExitInfo> mTmpInfoList = new ArrayList<ApplicationExitInfo>();
+
+    /**
+     * Temporary list being used to filter/sort intermediate results in {@link #getExitInfo}.
+     */
+    @GuardedBy("mLock")
+    final ArrayList<ApplicationExitInfo> mTmpInfoList2 = new ArrayList<ApplicationExitInfo>();
+
+    /**
+     * The path to the historical proc exit info file, persisted in the storage.
+     */
+    @VisibleForTesting
+    File mProcExitInfoFile;
+
+    /**
+     * Mapping between the isolated UID to its application uid.
+     */
+    final IsolatedUidRecords mIsolatedUidRecords =
+            new IsolatedUidRecords();
+
+    /**
+     * Bookkeeping app process exit info from Zygote.
+     */
+    final AppExitInfoExternalSource mAppExitInfoSourceZygote =
+            new AppExitInfoExternalSource("zygote", null);
+
+    /**
+     * Bookkeeping low memory kills info from lmkd.
+     */
+    final AppExitInfoExternalSource mAppExitInfoSourceLmkd =
+            new AppExitInfoExternalSource("lmkd", ApplicationExitInfo.REASON_LOW_MEMORY);
+
+    AppExitInfoTracker() {
+        mData = new ProcessMap<AppExitInfoContainer>();
+        mRawRecordsPool = new SynchronizedPool<ApplicationExitInfo>(APP_EXIT_RAW_INFO_POOL_SIZE);
+    }
+
+    void init(ActivityManagerService service, Looper looper) {
+        mService = service;
+        mKillHandler = new KillHandler(looper);
+        mProcExitInfoFile = new File(SystemServiceManager.ensureSystemDir(), APP_EXIT_INFO_FILE);
+
+        mAppExitInfoHistoryListSize = service.mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_app_exit_info_history_list_size);
+    }
+
+    void onSystemReady() {
+        registerForUserRemoval();
+        registerForPackageRemoval();
+        IoThread.getHandler().post(() -> {
+            // Read the sysprop set by lmkd and set this to persist so app could read it.
+            SystemProperties.set("persist.sys.lmk.reportkills",
+                    Boolean.toString(SystemProperties.getBoolean("sys.lmk.reportkills", false)));
+            loadExistingProcessExitInfo();
+        });
+    }
+
+    @GuardedBy("mService")
+    void scheduleNoteProcessDiedLocked(final ProcessRecord app) {
+        if (app == null || app.info == null) {
+            return;
+        }
+
+        synchronized (mLock) {
+            if (!mAppExitInfoLoaded) {
+                return;
+            }
+        }
+        // The current thread is holding the global lock, let's extract the info from it
+        // and schedule the info note task in the kill handler.
+        mKillHandler.obtainMessage(KillHandler.MSG_PROC_DIED, obtainRawRecordLocked(app))
+                .sendToTarget();
+    }
+
+    void scheduleNoteAppKill(final ProcessRecord app, final @Reason int reason,
+            final @SubReason int subReason, final String msg) {
+        synchronized (mLock) {
+            if (!mAppExitInfoLoaded) {
+                return;
+            }
+        }
+        synchronized (mService) {
+            if (app == null || app.info == null) {
+                return;
+            }
+
+            ApplicationExitInfo raw = obtainRawRecordLocked(app);
+            raw.setReason(reason);
+            raw.setSubReason(subReason);
+            raw.setDescription(msg);
+            mKillHandler.obtainMessage(KillHandler.MSG_APP_KILL, raw).sendToTarget();
+        }
+    }
+
+    void scheduleNoteAppKill(final int pid, final int uid, final @Reason int reason,
+            final @SubReason int subReason, final String msg) {
+        synchronized (mLock) {
+            if (!mAppExitInfoLoaded) {
+                return;
+            }
+        }
+        synchronized (mService) {
+            ProcessRecord app;
+            synchronized (mService.mPidsSelfLocked) {
+                app = mService.mPidsSelfLocked.get(pid);
+            }
+            if (app == null) {
+                if (DEBUG_PROCESSES) {
+                    Slog.w(TAG, "Skipping saving the kill reason for pid " + pid
+                            + "(uid=" + uid + ") since its process record is not found");
+                }
+            } else {
+                scheduleNoteAppKill(app, reason, subReason, msg);
+            }
+        }
+    }
+
+    interface LmkdKillListener {
+        /**
+         * Called when there is a process kill by lmkd.
+         */
+        void onLmkdKillOccurred(int pid, int uid);
+    }
+
+    void setLmkdKillListener(final LmkdKillListener listener) {
+        synchronized (mLock) {
+            mAppExitInfoSourceLmkd.setOnProcDiedListener((pid, uid) ->
+                    listener.onLmkdKillOccurred(pid, uid));
+        }
+    }
+
+    /** Called when there is a low memory kill */
+    void scheduleNoteLmkdProcKilled(final int pid, final int uid) {
+        mKillHandler.obtainMessage(KillHandler.MSG_LMKD_PROC_KILLED, pid, uid)
+                .sendToTarget();
+    }
+
+    private void scheduleChildProcDied(int pid, int uid, int status) {
+        mKillHandler.obtainMessage(KillHandler.MSG_CHILD_PROC_DIED, pid, uid, (Integer) status)
+                .sendToTarget();
+    }
+
+    /** Calls when zygote sends us SIGCHLD */
+    void handleZygoteSigChld(int pid, int uid, int status) {
+        if (DEBUG_PROCESSES) {
+            Slog.i(TAG, "Got SIGCHLD from zygote: pid=" + pid + ", uid=" + uid
+                    + ", status=" + Integer.toHexString(status));
+        }
+        scheduleChildProcDied(pid, uid, status);
+    }
+
+    /**
+     * Main routine to create or update the {@link android.app.ApplicationExitInfo} for the given
+     * ProcessRecord, also query the zygote and lmkd records to make the information more accurate.
+     */
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    void handleNoteProcessDiedLocked(final ApplicationExitInfo raw) {
+        if (raw != null) {
+            if (DEBUG_PROCESSES) {
+                Slog.i(TAG, "Update process exit info for " + raw.getPackageName()
+                        + "(" + raw.getPid() + "/u" + raw.getRealUid() + ")");
+            }
+
+            ApplicationExitInfo info = getExitInfo(raw.getPackageName(),
+                    raw.getPackageUid(), raw.getPid());
+
+            // query zygote and lmkd to get the exit info, and clear the saved info
+            Pair<Long, Object> zygote = mAppExitInfoSourceZygote.remove(
+                    raw.getPid(), raw.getRealUid());
+            Pair<Long, Object> lmkd = mAppExitInfoSourceLmkd.remove(
+                    raw.getPid(), raw.getRealUid());
+            mIsolatedUidRecords.removeIsolatedUid(raw.getRealUid());
+
+            if (info == null) {
+                info = addExitInfoLocked(raw);
+            }
+
+            if (lmkd != null) {
+                updateExistingExitInfoRecordLocked(info, null,
+                        ApplicationExitInfo.REASON_LOW_MEMORY);
+            } else if (zygote != null) {
+                updateExistingExitInfoRecordLocked(info, (Integer) zygote.second, null);
+            }
+        }
+    }
+
+    /**
+     * Make note when ActivityManagerService decides to kill an application process.
+     */
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    void handleNoteAppKillLocked(final ApplicationExitInfo raw) {
+        ApplicationExitInfo info = getExitInfo(
+                raw.getPackageName(), raw.getPackageUid(), raw.getPid());
+
+        if (info == null) {
+            addExitInfoLocked(raw);
+        } else {
+            // always override the existing info since we are now more informational.
+            info.setReason(raw.getReason());
+            info.setStatus(0);
+            info.setTimestamp(System.currentTimeMillis());
+            info.setDescription(raw.getDescription());
+        }
+    }
+
+    @GuardedBy("mLock")
+    private ApplicationExitInfo addExitInfoLocked(ApplicationExitInfo raw) {
+        if (!mAppExitInfoLoaded) {
+            Slog.w(TAG, "Skipping saving the exit info due to ongoing loading from storage");
+            return null;
+        }
+
+        final ApplicationExitInfo info = new ApplicationExitInfo(raw);
+        final String[] packages = raw.getPackageList();
+        final int uid = raw.getPackageUid();
+        for (int i = 0; i < packages.length; i++) {
+            addExitInfoInner(packages[i], uid, info);
+        }
+
+        schedulePersistProcessExitInfo(false);
+
+        return info;
+    }
+
+    /**
+     * Update an existing {@link android.app.ApplicationExitInfo} record with given information.
+     */
+    @GuardedBy("mLock")
+    private void updateExistingExitInfoRecordLocked(ApplicationExitInfo info,
+            Integer status, Integer reason) {
+        if (info == null || !isFresh(info.getTimestamp())) {
+            // if the record is way outdated, don't update it then (because of potential pid reuse)
+            return;
+        }
+        if (status != null) {
+            if (OsConstants.WIFEXITED(status)) {
+                info.setReason(ApplicationExitInfo.REASON_EXIT_SELF);
+                info.setStatus(OsConstants.WEXITSTATUS(status));
+            } else if (OsConstants.WIFSIGNALED(status)) {
+                if (info.getReason() == ApplicationExitInfo.REASON_UNKNOWN) {
+                    info.setReason(ApplicationExitInfo.REASON_SIGNALED);
+                    info.setStatus(OsConstants.WTERMSIG(status));
+                } else if (info.getReason() == ApplicationExitInfo.REASON_CRASH_NATIVE) {
+                    info.setStatus(OsConstants.WTERMSIG(status));
+                }
+            }
+        }
+        if (reason != null) {
+            info.setReason(reason);
+        }
+    }
+
+    /**
+     * Update an existing {@link android.app.ApplicationExitInfo} record with given information.
+     *
+     * @return true if a recond is updated
+     */
+    private boolean updateExitInfoIfNecessary(int pid, int uid, Integer status, Integer reason) {
+        synchronized (mLock) {
+            if (UserHandle.isIsolated(uid)) {
+                Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid);
+                if (k != null) {
+                    uid = k;
+                }
+            }
+            ArrayList<ApplicationExitInfo> tlist = mTmpInfoList;
+            tlist.clear();
+            final int targetUid = uid;
+            forEachPackage((packageName, records) -> {
+                AppExitInfoContainer container = records.get(targetUid);
+                if (container == null) {
+                    return FOREACH_ACTION_NONE;
+                }
+                tlist.clear();
+                container.getExitInfoLocked(pid, 1, tlist);
+                if (tlist.size() == 0) {
+                    return FOREACH_ACTION_NONE;
+                }
+                ApplicationExitInfo info = tlist.get(0);
+                if (info.getRealUid() != targetUid) {
+                    tlist.clear();
+                    return FOREACH_ACTION_NONE;
+                }
+                // Okay found it, update its reason.
+                updateExistingExitInfoRecordLocked(info, status, reason);
+
+                return FOREACH_ACTION_STOP_ITERATION;
+            });
+            return tlist.size() > 0;
+        }
+    }
+
+    /**
+     * Get the exit info with matching package name, filterUid and filterPid (if > 0)
+     */
+    @VisibleForTesting
+    void getExitInfo(final String packageName, final int filterUid,
+            final int filterPid, final int maxNum, final ArrayList<ApplicationExitInfo> results) {
+        synchronized (mLock) {
+            boolean emptyPackageName = TextUtils.isEmpty(packageName);
+            if (!emptyPackageName) {
+                // fast path
+                AppExitInfoContainer container = mData.get(packageName, filterUid);
+                if (container != null) {
+                    container.getExitInfoLocked(filterPid, maxNum, results);
+                }
+            } else {
+                // slow path
+                final ArrayList<ApplicationExitInfo> list = mTmpInfoList2;
+                list.clear();
+                // get all packages
+                forEachPackage((name, records) -> {
+                    AppExitInfoContainer container = records.get(filterUid);
+                    if (container != null) {
+                        mTmpInfoList.clear();
+                        results.addAll(container.toListLocked(mTmpInfoList, filterPid));
+                    }
+                    return AppExitInfoTracker.FOREACH_ACTION_NONE;
+                });
+
+                Collections.sort(list, (a, b) -> (int) (b.getTimestamp() - a.getTimestamp()));
+                int size = list.size();
+                if (maxNum > 0) {
+                    size = Math.min(size, maxNum);
+                }
+                for (int i = 0; i < size; i++) {
+                    results.add(list.get(i));
+                }
+                list.clear();
+            }
+        }
+    }
+
+    /**
+     * Return the first matching exit info record, for internal use, the parameters are not supposed
+     * to be empty.
+     */
+    private ApplicationExitInfo getExitInfo(final String packageName,
+            final int filterUid, final int filterPid) {
+        synchronized (mLock) {
+            ArrayList<ApplicationExitInfo> list = mTmpInfoList;
+            list.clear();
+            getExitInfo(packageName, filterUid, filterPid, 1, list);
+
+            ApplicationExitInfo info = list.size() > 0 ? list.get(0) : null;
+            list.clear();
+            return info;
+        }
+    }
+
+    @VisibleForTesting
+    void onUserRemoved(int userId) {
+        mAppExitInfoSourceZygote.removeByUserId(userId);
+        mAppExitInfoSourceLmkd.removeByUserId(userId);
+        mIsolatedUidRecords.removeByUserId(userId);
+        removeByUserId(userId);
+        schedulePersistProcessExitInfo(true);
+    }
+
+    @VisibleForTesting
+    void onPackageRemoved(String packageName, int uid, boolean allUsers) {
+        if (packageName != null) {
+            mAppExitInfoSourceZygote.removeByUid(uid, allUsers);
+            mAppExitInfoSourceLmkd.removeByUid(uid, allUsers);
+            mIsolatedUidRecords.removeAppUid(uid, allUsers);
+            removePackage(packageName, allUsers ? UserHandle.USER_ALL : UserHandle.getUserId(uid));
+            schedulePersistProcessExitInfo(true);
+        }
+    }
+
+    private void registerForUserRemoval() {
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_USER_REMOVED);
+        mService.mContext.registerReceiverForAllUsers(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                if (userId < 1) return;
+                onUserRemoved(userId);
+            }
+        }, filter, null, mKillHandler);
+    }
+
+    private void registerForPackageRemoval() {
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        filter.addDataScheme("package");
+        mService.mContext.registerReceiverForAllUsers(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                int uid = intent.getIntExtra(Intent.EXTRA_UID, UserHandle.USER_NULL);
+                boolean allUsers = intent.getBooleanExtra(
+                        Intent.EXTRA_REMOVED_FOR_ALL_USERS, false);
+                onPackageRemoved(intent.getData().getSchemeSpecificPart(), uid, allUsers);
+            }
+        }, filter, null, mKillHandler);
+    }
+
+    /**
+     * Load the existing {@link android.app.ApplicationExitInfo} records from persistent storage.
+     */
+    @VisibleForTesting
+    void loadExistingProcessExitInfo() {
+        if (!mProcExitInfoFile.canRead()) {
+            synchronized (mLock) {
+                mAppExitInfoLoaded = true;
+            }
+            return;
+        }
+
+        FileInputStream fin = null;
+        try {
+            AtomicFile af = new AtomicFile(mProcExitInfoFile);
+            fin = af.openRead();
+            ProtoInputStream proto = new ProtoInputStream(fin);
+            for (int next = proto.nextField();
+                    next != ProtoInputStream.NO_MORE_FIELDS;
+                    next = proto.nextField()) {
+                switch (next) {
+                    case (int) AppsExitInfoProto.LAST_UPDATE_TIMESTAMP:
+                        synchronized (mLock) {
+                            mLastAppExitInfoPersistTimestamp =
+                                    proto.readLong(AppsExitInfoProto.LAST_UPDATE_TIMESTAMP);
+                        }
+                        break;
+                    case (int) AppsExitInfoProto.PACKAGES:
+                        loadPackagesFromProto(proto, next);
+                        break;
+                }
+            }
+        } catch (IOException | IllegalArgumentException | WireTypeMismatchException e) {
+            Slog.w(TAG, "Error in loading historical app exit info from persistent storage: " + e);
+        } finally {
+            if (fin != null) {
+                try {
+                    fin.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+        synchronized (mLock) {
+            mAppExitInfoLoaded = true;
+        }
+    }
+
+    private void loadPackagesFromProto(ProtoInputStream proto, long fieldId)
+            throws IOException, WireTypeMismatchException {
+        long token = proto.start(fieldId);
+        String pkgName = "";
+        for (int next = proto.nextField();
+                next != ProtoInputStream.NO_MORE_FIELDS;
+                next = proto.nextField()) {
+            switch (next) {
+                case (int) AppsExitInfoProto.Package.PACKAGE_NAME:
+                    pkgName = proto.readString(AppsExitInfoProto.Package.PACKAGE_NAME);
+                    break;
+                case (int) AppsExitInfoProto.Package.USERS:
+                    AppExitInfoContainer container = new AppExitInfoContainer(
+                            mAppExitInfoHistoryListSize);
+                    int uid = container.readFromProto(proto, AppsExitInfoProto.Package.USERS);
+                    synchronized (mLock) {
+                        mData.put(pkgName, uid, container);
+                    }
+                    break;
+            }
+        }
+        proto.end(token);
+    }
+
+    /**
+     * Persist the existing {@link android.app.ApplicationExitInfo} records to storage.
+     */
+    @VisibleForTesting
+    void persistProcessExitInfo() {
+        AtomicFile af = new AtomicFile(mProcExitInfoFile);
+        FileOutputStream out = null;
+        long now = System.currentTimeMillis();
+        try {
+            out = af.startWrite();
+            ProtoOutputStream proto = new ProtoOutputStream(out);
+            proto.write(AppsExitInfoProto.LAST_UPDATE_TIMESTAMP, now);
+            synchronized (mLock) {
+                forEachPackage((packageName, records) -> {
+                    long token = proto.start(AppsExitInfoProto.PACKAGES);
+                    proto.write(AppsExitInfoProto.Package.PACKAGE_NAME, packageName);
+                    int uidArraySize = records.size();
+                    for (int j = 0; j < uidArraySize; j++) {
+                        records.valueAt(j).writeToProto(proto, AppsExitInfoProto.Package.USERS);
+                    }
+                    proto.end(token);
+                    return AppExitInfoTracker.FOREACH_ACTION_NONE;
+                });
+                mLastAppExitInfoPersistTimestamp = now;
+            }
+            proto.flush();
+            af.finishWrite(out);
+        } catch (IOException e) {
+            Slog.w(TAG, "Unable to write historical app exit info into persistent storage: " + e);
+            af.failWrite(out);
+        }
+        synchronized (mLock) {
+            mAppExitInfoPersistTask = null;
+        }
+    }
+
+    /**
+     * Schedule a task to persist the {@link android.app.ApplicationExitInfo} records to storage.
+     */
+    @VisibleForTesting
+    void schedulePersistProcessExitInfo(boolean immediately) {
+        synchronized (mLock) {
+            if (mAppExitInfoPersistTask == null || immediately) {
+                if (mAppExitInfoPersistTask != null) {
+                    IoThread.getHandler().removeCallbacks(mAppExitInfoPersistTask);
+                }
+                mAppExitInfoPersistTask = this::persistProcessExitInfo;
+                IoThread.getHandler().postDelayed(mAppExitInfoPersistTask,
+                        immediately ? 0 : APP_EXIT_INFO_PERSIST_INTERVAL);
+            }
+        }
+    }
+
+    /**
+     * Helper function for testing only.
+     */
+    @VisibleForTesting
+    void clearProcessExitInfo(boolean removeFile) {
+        synchronized (mLock) {
+            if (mAppExitInfoPersistTask != null) {
+                IoThread.getHandler().removeCallbacks(mAppExitInfoPersistTask);
+                mAppExitInfoPersistTask = null;
+            }
+            if (removeFile && mProcExitInfoFile != null) {
+                mProcExitInfoFile.delete();
+            }
+            mData.getMap().clear();
+        }
+    }
+
+    /**
+     * Helper function for shell command
+     */
+    void clearHistoryProcessExitInfo(String packageName, int userId) {
+        synchronized (mLock) {
+            if (TextUtils.isEmpty(packageName)) {
+                if (userId == UserHandle.USER_ALL) {
+                    mData.getMap().clear();
+                } else {
+                    removeByUserId(userId);
+                }
+            } else {
+                removePackage(packageName, userId);
+            }
+        }
+        schedulePersistProcessExitInfo(true);
+    }
+
+    void dumpHistoryProcessExitInfo(PrintWriter pw, String packageName) {
+        pw.println("ACTIVITY MANAGER LRU PROCESSES (dumpsys activity exit-info)");
+        SimpleDateFormat sdf = new SimpleDateFormat();
+        synchronized (mLock) {
+            pw.println("Last Timestamp of Persistence Into Persistent Storage: "
+                    + sdf.format(new Date(mLastAppExitInfoPersistTimestamp)));
+            if (TextUtils.isEmpty(packageName)) {
+                forEachPackage((name, records) -> {
+                    dumpHistoryProcessExitInfoLocked(pw, "  ", name, records, sdf);
+                    return AppExitInfoTracker.FOREACH_ACTION_NONE;
+                });
+            } else {
+                SparseArray<AppExitInfoContainer> array = mData.getMap().get(packageName);
+                if (array != null) {
+                    dumpHistoryProcessExitInfoLocked(pw, "  ", packageName, array, sdf);
+                }
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void dumpHistoryProcessExitInfoLocked(PrintWriter pw, String prefix,
+            String packageName, SparseArray<AppExitInfoContainer> array,
+            SimpleDateFormat sdf) {
+        pw.println(prefix + "package: " + packageName);
+        int size = array.size();
+        for (int i = 0; i < size; i++) {
+            pw.println(prefix + "  Historical Process Exit for userId=" + array.keyAt(i));
+            array.valueAt(i).dumpLocked(pw, prefix + "    ", sdf);
+        }
+    }
+
+    private void addExitInfoInner(String packageName, int userId, ApplicationExitInfo info) {
+        synchronized (mLock) {
+            AppExitInfoContainer container = mData.get(packageName, userId);
+            if (container == null) {
+                container = new AppExitInfoContainer(mAppExitInfoHistoryListSize);
+                if (UserHandle.isIsolated(info.getRealUid())) {
+                    Integer k = mIsolatedUidRecords.getUidByIsolatedUid(info.getRealUid());
+                    if (k != null) {
+                        container.mUid = k;
+                    }
+                } else {
+                    container.mUid = info.getRealUid();
+                }
+                mData.put(packageName, userId, container);
+            }
+            container.addExitInfoLocked(info);
+        }
+    }
+
+    private void forEachPackage(
+            BiFunction<String, SparseArray<AppExitInfoContainer>, Integer> callback) {
+        if (callback != null) {
+            synchronized (mLock) {
+                ArrayMap<String, SparseArray<AppExitInfoContainer>> map = mData.getMap();
+                for (int i = map.size() - 1; i >= 0; i--) {
+                    switch (callback.apply(map.keyAt(i), map.valueAt(i))) {
+                        case FOREACH_ACTION_REMOVE_PACKAGE:
+                            map.removeAt(i);
+                            break;
+                        case FOREACH_ACTION_STOP_ITERATION:
+                            i = 0;
+                            break;
+                        case FOREACH_ACTION_NONE:
+                        default:
+                            break;
+                    }
+                }
+            }
+        }
+    }
+
+    private void removePackage(String packageName, int userId) {
+        synchronized (mLock) {
+            if (userId == UserHandle.USER_ALL) {
+                mData.getMap().remove(packageName);
+            } else {
+                ArrayMap<String, SparseArray<AppExitInfoContainer>> map =
+                        mData.getMap();
+                SparseArray<AppExitInfoContainer> array = map.get(packageName);
+                if (array == null) {
+                    return;
+                }
+                for (int i = array.size() - 1; i >= 0; i--) {
+                    if (UserHandle.getUserId(array.keyAt(i)) == userId) {
+                        array.removeAt(i);
+                        break;
+                    }
+                }
+                if (array.size() == 0) {
+                    map.remove(packageName);
+                }
+            }
+        }
+    }
+
+    private void removeByUserId(final int userId) {
+        if (userId == UserHandle.USER_ALL) {
+            synchronized (mLock) {
+                mData.getMap().clear();
+            }
+            return;
+        }
+        forEachPackage((packageName, records) -> {
+            for (int i = records.size() - 1; i >= 0; i--) {
+                if (UserHandle.getUserId(records.keyAt(i)) == userId) {
+                    records.removeAt(i);
+                    break;
+                }
+            }
+            return records.size() == 0 ? FOREACH_ACTION_REMOVE_PACKAGE : FOREACH_ACTION_NONE;
+        });
+    }
+
+    @VisibleForTesting
+    @GuardedBy("mService")
+    ApplicationExitInfo obtainRawRecordLocked(ProcessRecord app) {
+        ApplicationExitInfo info = mRawRecordsPool.acquire();
+        if (info == null) {
+            info = new ApplicationExitInfo();
+        }
+
+        final int definingUid = app.hostingRecord != null ? app.hostingRecord.getDefiningUid() : 0;
+        info.setPid(app.pid);
+        info.setRealUid(app.uid);
+        info.setPackageUid(app.info.uid);
+        info.setDefiningUid(definingUid > 0 ? definingUid : app.info.uid);
+        info.setProcessName(app.processName);
+        info.setConnectionGroup(app.connectionGroup);
+        info.setPackageName(app.info.packageName);
+        info.setPackageList(app.getPackageList());
+        info.setReason(ApplicationExitInfo.REASON_UNKNOWN);
+        info.setStatus(0);
+        info.setImportance(procStateToImportance(app.setProcState));
+        info.setPss(app.lastMemInfo == null ? 0 : app.lastMemInfo.getTotalPss());
+        info.setRss(app.lastMemInfo == null ? 0 : app.lastMemInfo.getTotalRss());
+        info.setTimestamp(System.currentTimeMillis());
+
+        return info;
+    }
+
+    @VisibleForTesting
+    @GuardedBy("mService")
+    void recycleRawRecordLocked(ApplicationExitInfo info) {
+        info.setProcessName(null);
+        info.setDescription(null);
+        info.setPackageList(null);
+
+        mRawRecordsPool.release(info);
+    }
+
+    /**
+     * A container class of {@link android.app.ApplicationExitInfo}
+     */
+    final class AppExitInfoContainer {
+        private SparseArray<ApplicationExitInfo> mInfos; // index is pid
+        private int mMaxCapacity;
+        private int mUid; // Application uid, not isolated uid.
+
+        AppExitInfoContainer(final int maxCapacity) {
+            mInfos = new SparseArray<ApplicationExitInfo>();
+            mMaxCapacity = maxCapacity;
+        }
+
+        @GuardedBy("mLock")
+        void getExitInfoLocked(final int filterPid, final int maxNum,
+                ArrayList<ApplicationExitInfo> results) {
+            if (filterPid > 0) {
+                ApplicationExitInfo r = mInfos.get(filterPid);
+                if (r != null) {
+                    results.add(r);
+                }
+            } else {
+                final int numRep = mInfos.size();
+                if (maxNum <= 0 || numRep <= maxNum) {
+                    // Return all records.
+                    for (int i = 0; i < numRep; i++) {
+                        results.add(mInfos.valueAt(i));
+                    }
+                    Collections.sort(results,
+                            (a, b) -> (int) (b.getTimestamp() - a.getTimestamp()));
+                } else {
+                    if (maxNum == 1) {
+                        // Most of the caller might be only interested with the most recent one
+                        ApplicationExitInfo r = mInfos.valueAt(0);
+                        for (int i = 1; i < numRep; i++) {
+                            ApplicationExitInfo t = mInfos.valueAt(i);
+                            if (r.getTimestamp() < t.getTimestamp()) {
+                                r = t;
+                            }
+                        }
+                        results.add(r);
+                    } else {
+                        // Huh, need to sort it out then.
+                        ArrayList<ApplicationExitInfo> list = mTmpInfoList2;
+                        list.clear();
+                        for (int i = 0; i < numRep; i++) {
+                            list.add(mInfos.valueAt(i));
+                        }
+                        Collections.sort(list,
+                                (a, b) -> (int) (b.getTimestamp() - a.getTimestamp()));
+                        for (int i = 0; i < maxNum; i++) {
+                            results.add(list.get(i));
+                        }
+                        list.clear();
+                    }
+                }
+            }
+        }
+
+        @GuardedBy("mLock")
+        void addExitInfoLocked(ApplicationExitInfo info) {
+            int size;
+            if ((size = mInfos.size()) >= mMaxCapacity) {
+                int oldestIndex = -1;
+                long oldestTimeStamp = Long.MAX_VALUE;
+                for (int i = 0; i < size; i++) {
+                    ApplicationExitInfo r = mInfos.valueAt(i);
+                    if (r.getTimestamp() < oldestTimeStamp) {
+                        oldestTimeStamp = r.getTimestamp();
+                        oldestIndex = i;
+                    }
+                }
+                if (oldestIndex >= 0) {
+                    mInfos.removeAt(oldestIndex);
+                }
+            }
+            mInfos.append(info.getPid(), info);
+        }
+
+        @GuardedBy("mLock")
+        void dumpLocked(PrintWriter pw, String prefix, SimpleDateFormat sdf) {
+            ArrayList<ApplicationExitInfo> list = new ArrayList<ApplicationExitInfo>();
+            for (int i = mInfos.size() - 1; i >= 0; i--) {
+                list.add(mInfos.valueAt(i));
+            }
+            Collections.sort(list, (a, b) -> (int) (b.getTimestamp() - a.getTimestamp()));
+            int size = list.size();
+            for (int i = 0; i < size; i++) {
+                list.get(i).dump(pw, prefix + "  ", "#" + i, sdf);
+            }
+        }
+
+        @GuardedBy("mLock")
+        void writeToProto(ProtoOutputStream proto, long fieldId) {
+            long token = proto.start(fieldId);
+            proto.write(AppsExitInfoProto.Package.User.UID, mUid);
+            int size = mInfos.size();
+            for (int i = 0; i < size; i++) {
+                mInfos.valueAt(i).writeToProto(proto, AppsExitInfoProto.Package.User.APP_EXIT_INFO);
+            }
+            proto.end(token);
+        }
+
+        int readFromProto(ProtoInputStream proto, long fieldId)
+                throws IOException, WireTypeMismatchException {
+            long token = proto.start(fieldId);
+            for (int next = proto.nextField();
+                    next != ProtoInputStream.NO_MORE_FIELDS;
+                    next = proto.nextField()) {
+                switch (next) {
+                    case (int) AppsExitInfoProto.Package.User.UID:
+                        mUid = proto.readInt(AppsExitInfoProto.Package.User.UID);
+                        break;
+                    case (int) AppsExitInfoProto.Package.User.APP_EXIT_INFO:
+                        ApplicationExitInfo info = new ApplicationExitInfo();
+                        info.readFromProto(proto, AppsExitInfoProto.Package.User.APP_EXIT_INFO);
+                        mInfos.put(info.getPid(), info);
+                        break;
+                }
+            }
+            proto.end(token);
+            return mUid;
+        }
+
+        @GuardedBy("mLock")
+        List<ApplicationExitInfo> toListLocked(List<ApplicationExitInfo> list, int filterPid) {
+            if (list == null) {
+                list = new ArrayList<ApplicationExitInfo>();
+            }
+            for (int i = mInfos.size() - 1; i >= 0; i--) {
+                if (filterPid == 0 || filterPid == mInfos.keyAt(i)) {
+                    list.add(mInfos.valueAt(i));
+                }
+            }
+            return list;
+        }
+    }
+
+    /**
+     * Maintains the mapping between real UID and the application uid.
+     */
+    final class IsolatedUidRecords {
+        /**
+         * A mapping from application uid (with the userId) to isolated uids.
+         */
+        @GuardedBy("mLock")
+        private final SparseArray<ArraySet<Integer>> mUidToIsolatedUidMap;
+
+        /**
+         * A mapping from isolated uids to application uid (with the userId)
+         */
+        @GuardedBy("mLock")
+        private final SparseArray<Integer> mIsolatedUidToUidMap;
+
+        IsolatedUidRecords() {
+            mUidToIsolatedUidMap = new SparseArray<ArraySet<Integer>>();
+            mIsolatedUidToUidMap = new SparseArray<Integer>();
+        }
+
+        void addIsolatedUid(int isolatedUid, int uid) {
+            synchronized (mLock) {
+                ArraySet<Integer> set = mUidToIsolatedUidMap.get(uid);
+                if (set == null) {
+                    set = new ArraySet<Integer>();
+                    mUidToIsolatedUidMap.put(uid, set);
+                }
+                set.add(isolatedUid);
+
+                mIsolatedUidToUidMap.put(isolatedUid, uid);
+            }
+        }
+
+        Integer getUidByIsolatedUid(int isolatedUid) {
+            if (UserHandle.isIsolated(isolatedUid)) {
+                synchronized (mLock) {
+                    return mIsolatedUidToUidMap.get(isolatedUid);
+                }
+            }
+            return isolatedUid;
+        }
+
+        @GuardedBy("mLock")
+        private void removeAppUidLocked(int uid) {
+            ArraySet<Integer> set = mUidToIsolatedUidMap.get(uid);
+            if (set != null) {
+                for (int i = set.size() - 1; i >= 0; i--) {
+                    int isolatedUid = set.removeAt(i);
+                    mIsolatedUidToUidMap.remove(isolatedUid);
+                }
+            }
+        }
+
+        void removeAppUid(int uid, boolean allUsers) {
+            synchronized (mLock) {
+                if (allUsers) {
+                    uid = UserHandle.getAppId(uid);
+                    for (int i = mUidToIsolatedUidMap.size() - 1; i >= 0; i--) {
+                        int u = mUidToIsolatedUidMap.keyAt(i);
+                        if (uid == UserHandle.getAppId(u)) {
+                            removeAppUidLocked(u);
+                        }
+                        mUidToIsolatedUidMap.removeAt(i);
+                    }
+                } else {
+                    removeAppUidLocked(uid);
+                    mUidToIsolatedUidMap.remove(uid);
+                }
+            }
+        }
+
+        int removeIsolatedUid(int isolatedUid) {
+            if (!UserHandle.isIsolated(isolatedUid)) {
+                return isolatedUid;
+            }
+            synchronized (mLock) {
+                int uid = mIsolatedUidToUidMap.get(isolatedUid, -1);
+                if (uid == -1) {
+                    return isolatedUid;
+                }
+                mIsolatedUidToUidMap.remove(isolatedUid);
+                ArraySet<Integer> set = mUidToIsolatedUidMap.get(uid);
+                if (set != null) {
+                    set.remove(isolatedUid);
+                }
+                // let the ArraySet stay in the mUidToIsolatedUidMap even if it's empty
+                return uid;
+            }
+        }
+
+        void removeByUserId(int userId) {
+            if (userId == UserHandle.USER_CURRENT) {
+                userId = mService.mUserController.getCurrentUserId();
+            }
+            synchronized (mLock) {
+                if (userId == UserHandle.USER_ALL) {
+                    mIsolatedUidToUidMap.clear();
+                    mUidToIsolatedUidMap.clear();
+                    return;
+                }
+                for (int i = mIsolatedUidToUidMap.size() - 1; i >= 0; i--) {
+                    int isolatedUid = mIsolatedUidToUidMap.keyAt(i);
+                    int uid = mIsolatedUidToUidMap.valueAt(i);
+                    if (UserHandle.getUserId(uid) == userId) {
+                        mIsolatedUidToUidMap.removeAt(i);
+                        mUidToIsolatedUidMap.remove(uid);
+                    }
+                }
+            }
+        }
+    }
+
+    final class KillHandler extends Handler {
+        static final int MSG_LMKD_PROC_KILLED = 4101;
+        static final int MSG_CHILD_PROC_DIED = 4102;
+        static final int MSG_PROC_DIED = 4103;
+        static final int MSG_APP_KILL = 4104;
+
+        KillHandler(Looper looper) {
+            super(looper, null, true);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_LMKD_PROC_KILLED:
+                    mAppExitInfoSourceLmkd.onProcDied(msg.arg1 /* pid */, msg.arg2 /* uid */,
+                            null /* status */);
+                    break;
+                case MSG_CHILD_PROC_DIED:
+                    mAppExitInfoSourceZygote.onProcDied(msg.arg1 /* pid */, msg.arg2 /* uid */,
+                            (Integer) msg.obj /* status */);
+                    break;
+                case MSG_PROC_DIED: {
+                    ApplicationExitInfo raw = (ApplicationExitInfo) msg.obj;
+                    synchronized (mLock) {
+                        handleNoteProcessDiedLocked(raw);
+                    }
+                    synchronized (mService) {
+                        recycleRawRecordLocked(raw);
+                    }
+                }
+                break;
+                case MSG_APP_KILL: {
+                    ApplicationExitInfo raw = (ApplicationExitInfo) msg.obj;
+                    synchronized (mLock) {
+                        handleNoteAppKillLocked(raw);
+                    }
+                    synchronized (mService) {
+                        recycleRawRecordLocked(raw);
+                    }
+                }
+                break;
+                default:
+                    super.handleMessage(msg);
+            }
+        }
+    }
+
+    private static boolean isFresh(long timestamp) {
+        // A process could be dying but being stuck in some state, i.e.,
+        // being TRACED by tombstoned, thus the zygote receives SIGCHILD
+        // way after we already knew the kill (maybe because we did the kill :P),
+        // so here check if the last known kill information is "fresh" enough.
+        long now = System.currentTimeMillis();
+
+        return (timestamp + AppExitInfoExternalSource.APP_EXIT_INFO_FRESHNESS_MS) >= now;
+    }
+
+    /**
+     * Keep the raw information about app kills from external sources, i.e., lmkd
+     */
+    final class AppExitInfoExternalSource {
+        private static final long APP_EXIT_INFO_FRESHNESS_MS = 300 * 1000;
+
+        /**
+         * A mapping between uid -> pid -> {timestamp, extra info(Nullable)}.
+         * The uid here is the application uid, not the isolated uid.
+         */
+        @GuardedBy("mLock")
+        private final SparseArray<SparseArray<Pair<Long, Object>>> mData;
+
+        /** A tag for logging only */
+        private final String mTag;
+
+        /** A preset reason in case a proc dies */
+        private final Integer mPresetReason;
+
+        /** A callback that will be notified when a proc dies */
+        private BiConsumer<Integer, Integer> mProcDiedListener;
+
+        AppExitInfoExternalSource(String tag, Integer reason) {
+            mData = new SparseArray<SparseArray<Pair<Long, Object>>>();
+            mTag = tag;
+            mPresetReason = reason;
+        }
+
+        void add(int pid, int uid, Object extra) {
+            if (UserHandle.isIsolated(uid)) {
+                Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid);
+                if (k != null) {
+                    uid = k;
+                }
+            }
+
+            synchronized (mLock) {
+                SparseArray<Pair<Long, Object>> array = mData.get(uid);
+                if (array == null) {
+                    array = new SparseArray<Pair<Long, Object>>();
+                    mData.put(uid, array);
+                }
+                array.put(pid, new Pair<Long, Object>(System.currentTimeMillis(), extra));
+            }
+        }
+
+        Pair<Long, Object> remove(int pid, int uid) {
+            if (UserHandle.isIsolated(uid)) {
+                Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid);
+                if (k != null) {
+                    uid = k;
+                }
+            }
+
+            synchronized (mLock) {
+                SparseArray<Pair<Long, Object>> array = mData.get(uid);
+                if (array != null) {
+                    Pair<Long, Object> p = array.get(pid);
+                    if (p != null) {
+                        array.remove(pid);
+                        return isFresh(p.first) ? p : null;
+                    }
+                }
+            }
+            return null;
+        }
+
+        void removeByUserId(int userId) {
+            if (userId == UserHandle.USER_CURRENT) {
+                userId = mService.mUserController.getCurrentUserId();
+            }
+            synchronized (mLock) {
+                if (userId == UserHandle.USER_ALL) {
+                    mData.clear();
+                    return;
+                }
+                for (int i = mData.size() - 1; i >= 0; i--) {
+                    int uid = mData.keyAt(i);
+                    if (UserHandle.getUserId(uid) == userId) {
+                        mData.removeAt(i);
+                    }
+                }
+            }
+        }
+
+        void removeByUid(int uid, boolean allUsers) {
+            if (UserHandle.isIsolated(uid)) {
+                Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid);
+                if (k != null) {
+                    uid = k;
+                }
+            }
+
+            if (allUsers) {
+                uid = UserHandle.getAppId(uid);
+                synchronized (mLock) {
+                    for (int i = mData.size() - 1; i >= 0; i--) {
+                        if (UserHandle.getAppId(mData.keyAt(i)) == uid) {
+                            mData.removeAt(i);
+                        }
+                    }
+                }
+            } else {
+                synchronized (mLock) {
+                    mData.remove(uid);
+                }
+            }
+        }
+
+        void setOnProcDiedListener(BiConsumer<Integer, Integer> listener) {
+            synchronized (mLock) {
+                mProcDiedListener = listener;
+            }
+        }
+
+        void onProcDied(final int pid, final int uid, final Integer status) {
+            if (DEBUG_PROCESSES) {
+                Slog.i(TAG, mTag + ": proc died: pid=" + pid + " uid=" + uid
+                        + ", status=" + status);
+            }
+
+            if (mService == null) {
+                return;
+            }
+
+            // Unlikely but possible: the record has been created
+            // Let's update it if we could find a ApplicationExitInfo record
+            if (!updateExitInfoIfNecessary(pid, uid, status, mPresetReason)) {
+                add(pid, uid, status);
+            }
+
+            // Notify any interesed party regarding the lmkd kills
+            synchronized (mLock) {
+                final BiConsumer<Integer, Integer> listener = mProcDiedListener;
+                if (listener != null) {
+                    mService.mHandler.post(()-> listener.accept(pid, uid));
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 37026fd..a98b83b 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -402,7 +402,7 @@
     }
 
     public ParcelFileDescriptor getStatisticsStream() {
-        mContext.enforceCallingPermission(
+        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));
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
new file mode 100644
index 0000000..3ca5ebc
--- /dev/null
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -0,0 +1,771 @@
+/*
+ * 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.am;
+
+import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
+
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_COMPACTION;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+
+import android.app.ActivityManager;
+import android.app.ActivityThread;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Process;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.OnPropertiesChangedListener;
+import android.provider.DeviceConfig.Properties;
+import android.text.TextUtils;
+import android.util.EventLog;
+import android.util.Slog;
+import android.util.StatsLog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.ServiceThread;
+
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+public final class CachedAppOptimizer {
+
+    // Flags stored in the DeviceConfig API.
+    @VisibleForTesting static final String KEY_USE_COMPACTION = "use_compaction";
+    @VisibleForTesting static final String KEY_COMPACT_ACTION_1 = "compact_action_1";
+    @VisibleForTesting static final String KEY_COMPACT_ACTION_2 = "compact_action_2";
+    @VisibleForTesting static final String KEY_COMPACT_THROTTLE_1 = "compact_throttle_1";
+    @VisibleForTesting static final String KEY_COMPACT_THROTTLE_2 = "compact_throttle_2";
+    @VisibleForTesting static final String KEY_COMPACT_THROTTLE_3 = "compact_throttle_3";
+    @VisibleForTesting static final String KEY_COMPACT_THROTTLE_4 = "compact_throttle_4";
+    @VisibleForTesting static final String KEY_COMPACT_THROTTLE_5 = "compact_throttle_5";
+    @VisibleForTesting static final String KEY_COMPACT_THROTTLE_6 = "compact_throttle_6";
+    @VisibleForTesting static final String KEY_COMPACT_STATSD_SAMPLE_RATE =
+            "compact_statsd_sample_rate";
+    @VisibleForTesting static final String KEY_COMPACT_FULL_RSS_THROTTLE_KB =
+            "compact_full_rss_throttle_kb";
+    @VisibleForTesting static final String KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB =
+            "compact_full_delta_rss_throttle_kb";
+    @VisibleForTesting static final String KEY_COMPACT_PROC_STATE_THROTTLE =
+            "compact_proc_state_throttle";
+
+    // Phenotype sends int configurations and we map them to the strings we'll use on device,
+    // preventing a weird string value entering the kernel.
+    private static final int COMPACT_ACTION_FILE_FLAG = 1;
+    private static final int COMPACT_ACTION_ANON_FLAG = 2;
+    private static final int COMPACT_ACTION_FULL_FLAG = 3;
+    private static final int COMPACT_ACTION_NONE_FLAG = 4;
+    private static final String COMPACT_ACTION_NONE = "";
+    private static final String COMPACT_ACTION_FILE = "file";
+    private static final String COMPACT_ACTION_ANON = "anon";
+    private static final String COMPACT_ACTION_FULL = "all";
+
+    // Defaults for phenotype flags.
+    @VisibleForTesting static final Boolean DEFAULT_USE_COMPACTION = false;
+    @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_1 = COMPACT_ACTION_FILE_FLAG;
+    @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_2 = COMPACT_ACTION_FULL_FLAG;
+    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_1 = 5_000;
+    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_2 = 10_000;
+    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_3 = 500;
+    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_4 = 10_000;
+    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_5 = 10 * 60 * 1000;
+    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_6 = 10 * 60 * 1000;
+    // The sampling rate to push app compaction events into statsd for upload.
+    @VisibleForTesting static final float DEFAULT_STATSD_SAMPLE_RATE = 0.1f;
+    @VisibleForTesting static final long DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB = 12_000L;
+    @VisibleForTesting static final long DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB = 8_000L;
+    // Format of this string should be a comma separated list of integers.
+    @VisibleForTesting static final String DEFAULT_COMPACT_PROC_STATE_THROTTLE =
+            String.valueOf(ActivityManager.PROCESS_STATE_RECEIVER);
+
+    @VisibleForTesting
+    interface PropertyChangedCallbackForTest {
+        void onPropertyChanged();
+    }
+    private PropertyChangedCallbackForTest mTestCallback;
+
+    // Handler constants.
+    static final int COMPACT_PROCESS_SOME = 1;
+    static final int COMPACT_PROCESS_FULL = 2;
+    static final int COMPACT_PROCESS_PERSISTENT = 3;
+    static final int COMPACT_PROCESS_BFGS = 4;
+    static final int COMPACT_PROCESS_MSG = 1;
+    static final int COMPACT_SYSTEM_MSG = 2;
+
+    /**
+     * This thread must be moved to the system background cpuset.
+     * If that doesn't happen, it's probably going to draw a lot of power.
+     * However, this has to happen after the first updateOomAdjLocked, because
+     * that will wipe out the cpuset assignment for system_server threads.
+     * Accordingly, this is in the AMS constructor.
+     */
+    final ServiceThread mCachedAppOptimizerThread;
+
+    private final ArrayList<ProcessRecord> mPendingCompactionProcesses =
+            new ArrayList<ProcessRecord>();
+    private final ActivityManagerService mAm;
+    private final OnPropertiesChangedListener mOnFlagsChangedListener =
+            new OnPropertiesChangedListener() {
+                @Override
+                public void onPropertiesChanged(Properties properties) {
+                    synchronized (mPhenotypeFlagLock) {
+                        for (String name : properties.getKeyset()) {
+                            if (KEY_USE_COMPACTION.equals(name)) {
+                                updateUseCompaction();
+                            } else if (KEY_COMPACT_ACTION_1.equals(name)
+                                    || KEY_COMPACT_ACTION_2.equals(name)) {
+                                updateCompactionActions();
+                            } else if (KEY_COMPACT_THROTTLE_1.equals(name)
+                                    || KEY_COMPACT_THROTTLE_2.equals(name)
+                                    || KEY_COMPACT_THROTTLE_3.equals(name)
+                                    || KEY_COMPACT_THROTTLE_4.equals(name)) {
+                                updateCompactionThrottles();
+                            } else if (KEY_COMPACT_STATSD_SAMPLE_RATE.equals(name)) {
+                                updateStatsdSampleRate();
+                            } else if (KEY_COMPACT_FULL_RSS_THROTTLE_KB.equals(name)) {
+                                updateFullRssThrottle();
+                            } else if (KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB.equals(name)) {
+                                updateFullDeltaRssThrottle();
+                            } else if (KEY_COMPACT_PROC_STATE_THROTTLE.equals(name)) {
+                                updateProcStateThrottle();
+                            }
+                        }
+                    }
+                    if (mTestCallback != null) {
+                        mTestCallback.onPropertyChanged();
+                    }
+                }
+            };
+
+    private final Object mPhenotypeFlagLock = new Object();
+
+    // Configured by phenotype. Updates from the server take effect immediately.
+    @GuardedBy("mPhenotypeFlagLock")
+    @VisibleForTesting volatile String mCompactActionSome =
+            compactActionIntToString(DEFAULT_COMPACT_ACTION_1);
+    @GuardedBy("mPhenotypeFlagLock")
+    @VisibleForTesting volatile String mCompactActionFull =
+            compactActionIntToString(DEFAULT_COMPACT_ACTION_2);
+    @GuardedBy("mPhenotypeFlagLock")
+    @VisibleForTesting volatile long mCompactThrottleSomeSome = DEFAULT_COMPACT_THROTTLE_1;
+    @GuardedBy("mPhenotypeFlagLock")
+    @VisibleForTesting volatile long mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2;
+    @GuardedBy("mPhenotypeFlagLock")
+    @VisibleForTesting volatile long mCompactThrottleFullSome = DEFAULT_COMPACT_THROTTLE_3;
+    @GuardedBy("mPhenotypeFlagLock")
+    @VisibleForTesting volatile long mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4;
+    @GuardedBy("mPhenotypeFlagLock")
+    @VisibleForTesting volatile long mCompactThrottleBFGS = DEFAULT_COMPACT_THROTTLE_5;
+    @GuardedBy("mPhenotypeFlagLock")
+    @VisibleForTesting volatile long mCompactThrottlePersistent = DEFAULT_COMPACT_THROTTLE_6;
+    @GuardedBy("mPhenotypeFlagLock")
+    private volatile boolean mUseCompaction = DEFAULT_USE_COMPACTION;
+    private final Random mRandom = new Random();
+    @GuardedBy("mPhenotypeFlagLock")
+    @VisibleForTesting volatile float mStatsdSampleRate = DEFAULT_STATSD_SAMPLE_RATE;
+    @GuardedBy("mPhenotypeFlagLock")
+    @VisibleForTesting volatile long mFullAnonRssThrottleKb =
+            DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB;
+    @GuardedBy("mPhenotypeFlagLock")
+    @VisibleForTesting volatile long mFullDeltaRssThrottleKb =
+            DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB;
+    @GuardedBy("mPhenotypeFlagLock")
+    @VisibleForTesting final Set<Integer> mProcStateThrottle;
+
+    // Handler on which compaction runs.
+    private Handler mCompactionHandler;
+
+    // Maps process ID to last compaction statistics for processes that we've fully compacted. Used
+    // when evaluating throttles that we only consider for "full" compaction, so we don't store
+    // data for "some" compactions.
+    private Map<Integer, LastCompactionStats> mLastCompactionStats =
+            new LinkedHashMap<Integer, LastCompactionStats>() {
+                @Override
+                protected boolean removeEldestEntry(Map.Entry eldest) {
+                    return size() > 100;
+                }
+    };
+
+    private int mSomeCompactionCount;
+    private int mFullCompactionCount;
+    private int mPersistentCompactionCount;
+    private int mBfgsCompactionCount;
+
+    public CachedAppOptimizer(ActivityManagerService am) {
+        mAm = am;
+        mCachedAppOptimizerThread = new ServiceThread("CachedAppOptimizerThread",
+                THREAD_PRIORITY_FOREGROUND, true);
+        mProcStateThrottle = new HashSet<>();
+    }
+
+    @VisibleForTesting
+    CachedAppOptimizer(ActivityManagerService am, PropertyChangedCallbackForTest callback) {
+        this(am);
+        mTestCallback = callback;
+    }
+
+    /**
+     * Reads phenotype config to determine whether app compaction is enabled or not and
+     * starts the background thread if necessary.
+     */
+    public void init() {
+        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                ActivityThread.currentApplication().getMainExecutor(), mOnFlagsChangedListener);
+        synchronized (mPhenotypeFlagLock) {
+            updateUseCompaction();
+            updateCompactionActions();
+            updateCompactionThrottles();
+            updateStatsdSampleRate();
+            updateFullRssThrottle();
+            updateFullDeltaRssThrottle();
+            updateProcStateThrottle();
+        }
+        Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(),
+                Process.THREAD_GROUP_SYSTEM);
+    }
+
+    /**
+     * Returns whether compaction is enabled.
+     */
+    public boolean useCompaction() {
+        synchronized (mPhenotypeFlagLock) {
+            return mUseCompaction;
+        }
+    }
+
+    @GuardedBy("mAm")
+    void dump(PrintWriter pw) {
+        pw.println("CachedAppOptimizer settings");
+        synchronized (mPhenotypeFlagLock) {
+            pw.println("  " + KEY_USE_COMPACTION + "=" + mUseCompaction);
+            pw.println("  " + KEY_COMPACT_ACTION_1 + "=" + mCompactActionSome);
+            pw.println("  " + KEY_COMPACT_ACTION_2 + "=" + mCompactActionFull);
+            pw.println("  " + KEY_COMPACT_THROTTLE_1 + "=" + mCompactThrottleSomeSome);
+            pw.println("  " + KEY_COMPACT_THROTTLE_2 + "=" + mCompactThrottleSomeFull);
+            pw.println("  " + KEY_COMPACT_THROTTLE_3 + "=" + mCompactThrottleFullSome);
+            pw.println("  " + KEY_COMPACT_THROTTLE_4 + "=" + mCompactThrottleFullFull);
+            pw.println("  " + KEY_COMPACT_THROTTLE_5 + "=" + mCompactThrottleBFGS);
+            pw.println("  " + KEY_COMPACT_THROTTLE_6 + "=" + mCompactThrottlePersistent);
+            pw.println("  " + KEY_COMPACT_STATSD_SAMPLE_RATE + "=" + mStatsdSampleRate);
+            pw.println("  " + KEY_COMPACT_FULL_RSS_THROTTLE_KB + "="
+                    + mFullAnonRssThrottleKb);
+            pw.println("  " + KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + "="
+                    + mFullDeltaRssThrottleKb);
+            pw.println("  "  + KEY_COMPACT_PROC_STATE_THROTTLE + "="
+                    + Arrays.toString(mProcStateThrottle.toArray(new Integer[0])));
+
+            pw.println("  " + mSomeCompactionCount + " some, " + mFullCompactionCount
+                    + " full, " + mPersistentCompactionCount + " persistent, "
+                    + mBfgsCompactionCount + " BFGS compactions.");
+
+            pw.println("  Tracking last compaction stats for " + mLastCompactionStats.size()
+                    + " processes.");
+            if (DEBUG_COMPACTION) {
+                for (Map.Entry<Integer, LastCompactionStats> entry
+                        : mLastCompactionStats.entrySet()) {
+                    int pid = entry.getKey();
+                    LastCompactionStats stats = entry.getValue();
+                    pw.println("    " + pid + ": "
+                            + Arrays.toString(stats.getRssAfterCompaction()));
+                }
+            }
+        }
+    }
+
+    @GuardedBy("mAm")
+    void compactAppSome(ProcessRecord app) {
+        app.reqCompactAction = COMPACT_PROCESS_SOME;
+        mPendingCompactionProcesses.add(app);
+        mCompactionHandler.sendMessage(
+                mCompactionHandler.obtainMessage(
+                COMPACT_PROCESS_MSG, app.setAdj, app.setProcState));
+    }
+
+    @GuardedBy("mAm")
+    void compactAppFull(ProcessRecord app) {
+        app.reqCompactAction = COMPACT_PROCESS_FULL;
+        mPendingCompactionProcesses.add(app);
+        mCompactionHandler.sendMessage(
+                mCompactionHandler.obtainMessage(
+                COMPACT_PROCESS_MSG, app.setAdj, app.setProcState));
+
+    }
+
+    @GuardedBy("mAm")
+    void compactAppPersistent(ProcessRecord app) {
+        app.reqCompactAction = COMPACT_PROCESS_PERSISTENT;
+        mPendingCompactionProcesses.add(app);
+        mCompactionHandler.sendMessage(
+                mCompactionHandler.obtainMessage(
+                    COMPACT_PROCESS_MSG, app.curAdj, app.setProcState));
+    }
+
+    @GuardedBy("mAm")
+    boolean shouldCompactPersistent(ProcessRecord app, long now) {
+        return (app.lastCompactTime == 0
+                || (now - app.lastCompactTime) > mCompactThrottlePersistent);
+    }
+
+    @GuardedBy("mAm")
+    void compactAppBfgs(ProcessRecord app) {
+        app.reqCompactAction = COMPACT_PROCESS_BFGS;
+        mPendingCompactionProcesses.add(app);
+        mCompactionHandler.sendMessage(
+                mCompactionHandler.obtainMessage(
+                    COMPACT_PROCESS_MSG, app.curAdj, app.setProcState));
+    }
+
+    @GuardedBy("mAm")
+    boolean shouldCompactBFGS(ProcessRecord app, long now) {
+        return (app.lastCompactTime == 0
+                || (now - app.lastCompactTime) > mCompactThrottleBFGS);
+    }
+
+    @GuardedBy("mAm")
+    void compactAllSystem() {
+        if (mUseCompaction) {
+            mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage(
+                                              COMPACT_SYSTEM_MSG));
+        }
+    }
+
+    private native void compactSystem();
+
+    /**
+     * Reads the flag value from DeviceConfig to determine whether app compaction
+     * should be enabled, and starts the compaction thread if needed.
+     */
+    @GuardedBy("mPhenotypeFlagLock")
+    private void updateUseCompaction() {
+        mUseCompaction = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    KEY_USE_COMPACTION, DEFAULT_USE_COMPACTION);
+        if (mUseCompaction && !mCachedAppOptimizerThread.isAlive()) {
+            mCachedAppOptimizerThread.start();
+            mCompactionHandler = new MemCompactionHandler();
+        }
+    }
+
+    @GuardedBy("mPhenotypeFlagLock")
+    private void updateCompactionActions() {
+        int compactAction1 = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_COMPACT_ACTION_1, DEFAULT_COMPACT_ACTION_1);
+
+        int compactAction2 = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_COMPACT_ACTION_2, DEFAULT_COMPACT_ACTION_2);
+
+        mCompactActionSome = compactActionIntToString(compactAction1);
+        mCompactActionFull = compactActionIntToString(compactAction2);
+    }
+
+    @GuardedBy("mPhenotypeFlagLock")
+    private void updateCompactionThrottles() {
+        boolean useThrottleDefaults = false;
+        String throttleSomeSomeFlag =
+                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    KEY_COMPACT_THROTTLE_1);
+        String throttleSomeFullFlag =
+                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    KEY_COMPACT_THROTTLE_2);
+        String throttleFullSomeFlag =
+                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    KEY_COMPACT_THROTTLE_3);
+        String throttleFullFullFlag =
+                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    KEY_COMPACT_THROTTLE_4);
+        String throttleBFGSFlag =
+                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    KEY_COMPACT_THROTTLE_5);
+        String throttlePersistentFlag =
+                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    KEY_COMPACT_THROTTLE_6);
+
+        if (TextUtils.isEmpty(throttleSomeSomeFlag) || TextUtils.isEmpty(throttleSomeFullFlag)
+                || TextUtils.isEmpty(throttleFullSomeFlag)
+                || TextUtils.isEmpty(throttleFullFullFlag)
+                || TextUtils.isEmpty(throttleBFGSFlag)
+                || TextUtils.isEmpty(throttlePersistentFlag)) {
+            // Set defaults for all if any are not set.
+            useThrottleDefaults = true;
+        } else {
+            try {
+                mCompactThrottleSomeSome = Integer.parseInt(throttleSomeSomeFlag);
+                mCompactThrottleSomeFull = Integer.parseInt(throttleSomeFullFlag);
+                mCompactThrottleFullSome = Integer.parseInt(throttleFullSomeFlag);
+                mCompactThrottleFullFull = Integer.parseInt(throttleFullFullFlag);
+                mCompactThrottleBFGS = Integer.parseInt(throttleBFGSFlag);
+                mCompactThrottlePersistent = Integer.parseInt(throttlePersistentFlag);
+            } catch (NumberFormatException e) {
+                useThrottleDefaults = true;
+            }
+        }
+
+        if (useThrottleDefaults) {
+            mCompactThrottleSomeSome = DEFAULT_COMPACT_THROTTLE_1;
+            mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2;
+            mCompactThrottleFullSome = DEFAULT_COMPACT_THROTTLE_3;
+            mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4;
+            mCompactThrottleBFGS = DEFAULT_COMPACT_THROTTLE_5;
+            mCompactThrottlePersistent = DEFAULT_COMPACT_THROTTLE_6;
+        }
+    }
+
+    @GuardedBy("mPhenotypeFlagLock")
+    private void updateStatsdSampleRate() {
+        mStatsdSampleRate = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_COMPACT_STATSD_SAMPLE_RATE, DEFAULT_STATSD_SAMPLE_RATE);
+        mStatsdSampleRate = Math.min(1.0f, Math.max(0.0f, mStatsdSampleRate));
+    }
+
+    @GuardedBy("mPhenotypeFlagLock")
+    private void updateFullRssThrottle() {
+        mFullAnonRssThrottleKb = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_COMPACT_FULL_RSS_THROTTLE_KB, DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
+
+        // Don't allow negative values. 0 means don't apply the throttle.
+        if (mFullAnonRssThrottleKb < 0) {
+            mFullAnonRssThrottleKb = DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB;
+        }
+    }
+
+    @GuardedBy("mPhenotypeFlagLock")
+    private void updateFullDeltaRssThrottle() {
+        mFullDeltaRssThrottleKb = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
+
+        if (mFullDeltaRssThrottleKb < 0) {
+            mFullDeltaRssThrottleKb = DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB;
+        }
+    }
+
+    @GuardedBy("mPhenotypeFlagLock")
+    private void updateProcStateThrottle() {
+        String procStateThrottleString = DeviceConfig.getString(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_COMPACT_PROC_STATE_THROTTLE,
+                DEFAULT_COMPACT_PROC_STATE_THROTTLE);
+        if (!parseProcStateThrottle(procStateThrottleString)) {
+            Slog.w(TAG_AM, "Unable to parse app compact proc state throttle \""
+                    + procStateThrottleString + "\" falling back to default.");
+            if (!parseProcStateThrottle(DEFAULT_COMPACT_PROC_STATE_THROTTLE)) {
+                Slog.wtf(TAG_AM,
+                        "Unable to parse default app compact proc state throttle "
+                                + DEFAULT_COMPACT_PROC_STATE_THROTTLE);
+            }
+        }
+    }
+
+    private boolean parseProcStateThrottle(String procStateThrottleString) {
+        String[] procStates = TextUtils.split(procStateThrottleString, ",");
+        mProcStateThrottle.clear();
+        for (String procState : procStates) {
+            try {
+                mProcStateThrottle.add(Integer.parseInt(procState));
+            } catch (NumberFormatException e) {
+                Slog.e(TAG_AM, "Failed to parse default app compaction proc state: "
+                        + procState);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @VisibleForTesting
+    static String compactActionIntToString(int action) {
+        switch(action) {
+            case COMPACT_ACTION_NONE_FLAG:
+                return COMPACT_ACTION_NONE;
+            case COMPACT_ACTION_FILE_FLAG:
+                return COMPACT_ACTION_FILE;
+            case COMPACT_ACTION_ANON_FLAG:
+                return COMPACT_ACTION_ANON;
+            case COMPACT_ACTION_FULL_FLAG:
+                return COMPACT_ACTION_FULL;
+            default:
+                return COMPACT_ACTION_NONE;
+        }
+    }
+
+    private static final class LastCompactionStats {
+        private final long[] mRssAfterCompaction;
+
+        LastCompactionStats(long[] rss) {
+            mRssAfterCompaction = rss;
+        }
+
+        long[] getRssAfterCompaction() {
+            return mRssAfterCompaction;
+        }
+    }
+
+    private final class MemCompactionHandler extends Handler {
+        private MemCompactionHandler() {
+            super(mCachedAppOptimizerThread.getLooper());
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case COMPACT_PROCESS_MSG: {
+                    long start = SystemClock.uptimeMillis();
+                    ProcessRecord proc;
+                    int pid;
+                    String action;
+                    final String name;
+                    int pendingAction, lastCompactAction;
+                    long lastCompactTime;
+                    LastCompactionStats lastCompactionStats;
+                    int lastOomAdj = msg.arg1;
+                    int procState = msg.arg2;
+                    synchronized (mAm) {
+                        proc = mPendingCompactionProcesses.remove(0);
+
+                        pendingAction = proc.reqCompactAction;
+                        pid = proc.pid;
+                        name = proc.processName;
+
+                        // don't compact if the process has returned to perceptible
+                        // and this is only a cached/home/prev compaction
+                        if ((pendingAction == COMPACT_PROCESS_SOME
+                                || pendingAction == COMPACT_PROCESS_FULL)
+                                && (proc.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ)) {
+                            if (DEBUG_COMPACTION) {
+                                Slog.d(TAG_AM,
+                                        "Skipping compaction as process " + name + " is "
+                                        + "now perceptible.");
+                            }
+                            return;
+                        }
+
+                        lastCompactAction = proc.lastCompactAction;
+                        lastCompactTime = proc.lastCompactTime;
+                        // remove rather than get so that insertion order will be updated when we
+                        // put the post-compaction stats back into the map.
+                        lastCompactionStats = mLastCompactionStats.remove(pid);
+                    }
+
+                    if (pid == 0) {
+                        // not a real process, either one being launched or one being killed
+                        return;
+                    }
+
+                    // basic throttling
+                    // use the Phenotype flag knobs to determine whether current/prevous
+                    // compaction combo should be throtted or not
+
+                    // Note that we explicitly don't take mPhenotypeFlagLock here as the flags
+                    // should very seldom change, and taking the risk of using the wrong action is
+                    // preferable to taking the lock for every single compaction action.
+                    if (lastCompactTime != 0) {
+                        if (pendingAction == COMPACT_PROCESS_SOME) {
+                            if ((lastCompactAction == COMPACT_PROCESS_SOME
+                                    && (start - lastCompactTime < mCompactThrottleSomeSome))
+                                    || (lastCompactAction == COMPACT_PROCESS_FULL
+                                        && (start - lastCompactTime
+                                                < mCompactThrottleSomeFull))) {
+                                if (DEBUG_COMPACTION) {
+                                    Slog.d(TAG_AM, "Skipping some compaction for " + name
+                                            + ": too soon. throttle=" + mCompactThrottleSomeSome
+                                            + "/" + mCompactThrottleSomeFull + " last="
+                                            + (start - lastCompactTime) + "ms ago");
+                                }
+                                return;
+                            }
+                        } else if (pendingAction == COMPACT_PROCESS_FULL) {
+                            if ((lastCompactAction == COMPACT_PROCESS_SOME
+                                    && (start - lastCompactTime < mCompactThrottleFullSome))
+                                    || (lastCompactAction == COMPACT_PROCESS_FULL
+                                        && (start - lastCompactTime
+                                                < mCompactThrottleFullFull))) {
+                                if (DEBUG_COMPACTION) {
+                                    Slog.d(TAG_AM, "Skipping full compaction for " + name
+                                            + ": too soon. throttle=" + mCompactThrottleFullSome
+                                            + "/" + mCompactThrottleFullFull + " last="
+                                            + (start - lastCompactTime) + "ms ago");
+                                }
+                                return;
+                            }
+                        } else if (pendingAction == COMPACT_PROCESS_PERSISTENT) {
+                            if (start - lastCompactTime < mCompactThrottlePersistent) {
+                                if (DEBUG_COMPACTION) {
+                                    Slog.d(TAG_AM, "Skipping persistent compaction for " + name
+                                            + ": too soon. throttle=" + mCompactThrottlePersistent
+                                            + " last=" + (start - lastCompactTime) + "ms ago");
+                                }
+                                return;
+                            }
+                        } else if (pendingAction == COMPACT_PROCESS_BFGS) {
+                            if (start - lastCompactTime < mCompactThrottleBFGS) {
+                                if (DEBUG_COMPACTION) {
+                                    Slog.d(TAG_AM, "Skipping bfgs compaction for " + name
+                                            + ": too soon. throttle=" + mCompactThrottleBFGS
+                                            + " last=" + (start - lastCompactTime) + "ms ago");
+                                }
+                                return;
+                            }
+                        }
+                    }
+
+                    switch (pendingAction) {
+                        case COMPACT_PROCESS_SOME:
+                            action = mCompactActionSome;
+                            break;
+                        // For the time being, treat these as equivalent.
+                        case COMPACT_PROCESS_FULL:
+                        case COMPACT_PROCESS_PERSISTENT:
+                        case COMPACT_PROCESS_BFGS:
+                            action = mCompactActionFull;
+                            break;
+                        default:
+                            action = COMPACT_ACTION_NONE;
+                            break;
+                    }
+
+                    if (COMPACT_ACTION_NONE.equals(action)) {
+                        return;
+                    }
+
+                    if (mProcStateThrottle.contains(procState)) {
+                        if (DEBUG_COMPACTION) {
+                            Slog.d(TAG_AM, "Skipping full compaction for process " + name
+                                    + "; proc state is " + procState);
+                        }
+                        return;
+                    }
+
+                    long[] rssBefore = Process.getRss(pid);
+                    long anonRssBefore = rssBefore[2];
+
+                    if (rssBefore[0] == 0 && rssBefore[1] == 0 && rssBefore[2] == 0
+                            && rssBefore[3] == 0) {
+                        if (DEBUG_COMPACTION) {
+                            Slog.d(TAG_AM, "Skipping compaction for" + "process " + pid
+                                    + " with no memory usage. Dead?");
+                        }
+                        return;
+                    }
+
+                    if (action.equals(COMPACT_ACTION_FULL) || action.equals(COMPACT_ACTION_ANON)) {
+                        if (mFullAnonRssThrottleKb > 0L
+                                && anonRssBefore < mFullAnonRssThrottleKb) {
+                            if (DEBUG_COMPACTION) {
+                                Slog.d(TAG_AM, "Skipping full compaction for process "
+                                        + name + "; anon RSS is too small: " + anonRssBefore
+                                        + "KB.");
+                            }
+                            return;
+                        }
+
+                        if (lastCompactionStats != null && mFullDeltaRssThrottleKb > 0L) {
+                            long[] lastRss = lastCompactionStats.getRssAfterCompaction();
+                            long absDelta = Math.abs(rssBefore[1] - lastRss[1])
+                                    + Math.abs(rssBefore[2] - lastRss[2])
+                                    + Math.abs(rssBefore[3] - lastRss[3]);
+                            if (absDelta <= mFullDeltaRssThrottleKb) {
+                                if (DEBUG_COMPACTION) {
+                                    Slog.d(TAG_AM, "Skipping full compaction for process "
+                                            + name + "; abs delta is too small: " + absDelta
+                                            + "KB.");
+                                }
+                                return;
+                            }
+                        }
+                    }
+
+                    // Now we've passed through all the throttles and are going to compact, update
+                    // bookkeeping.
+                    switch (pendingAction) {
+                        case COMPACT_PROCESS_SOME:
+                            mSomeCompactionCount++;
+                            break;
+                        case COMPACT_PROCESS_FULL:
+                            mFullCompactionCount++;
+                            break;
+                        case COMPACT_PROCESS_PERSISTENT:
+                            mPersistentCompactionCount++;
+                            break;
+                        case COMPACT_PROCESS_BFGS:
+                            mBfgsCompactionCount++;
+                            break;
+                        default:
+                            break;
+                    }
+
+                    try {
+                        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact "
+                                + ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full")
+                                + ": " + name);
+                        long zramFreeKbBefore = Debug.getZramFreeKb();
+                        FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim");
+                        fos.write(action.getBytes());
+                        fos.close();
+                        long[] rssAfter = Process.getRss(pid);
+                        long end = SystemClock.uptimeMillis();
+                        long time = end - start;
+                        long zramFreeKbAfter = Debug.getZramFreeKb();
+                        EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name, action,
+                                rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
+                                rssAfter[0] - rssBefore[0], rssAfter[1] - rssBefore[1],
+                                rssAfter[2] - rssBefore[2], rssAfter[3] - rssBefore[3], time,
+                                lastCompactAction, lastCompactTime, lastOomAdj, procState,
+                                zramFreeKbBefore, zramFreeKbAfter - zramFreeKbBefore);
+
+                        // Note that as above not taking mPhenoTypeFlagLock here to avoid locking
+                        // on every single compaction for a flag that will seldom change and the
+                        // impact of reading the wrong value here is low.
+                        if (mRandom.nextFloat() < mStatsdSampleRate) {
+                            StatsLog.write(StatsLog.APP_COMPACTED, pid, name, pendingAction,
+                                    rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
+                                    rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time,
+                                    lastCompactAction, lastCompactTime, lastOomAdj,
+                                    ActivityManager.processStateAmToProto(procState),
+                                    zramFreeKbBefore, zramFreeKbAfter);
+                        }
+
+                        synchronized (mAm) {
+                            proc.lastCompactTime = end;
+                            proc.lastCompactAction = pendingAction;
+                        }
+
+                        if (action.equals(COMPACT_ACTION_FULL)
+                                || action.equals(COMPACT_ACTION_ANON)) {
+                            mLastCompactionStats.put(pid, new LastCompactionStats(rssAfter));
+                        }
+                    } catch (Exception e) {
+                        // nothing to do, presumably the process died
+                    } finally {
+                        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                    }
+                    break;
+                }
+                case COMPACT_SYSTEM_MSG: {
+                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "compactSystem");
+                    compactSystem();
+                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                    break;
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/CarUserSwitchingDialog.java b/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
index ebfc2a0..60754fb 100644
--- a/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
@@ -33,6 +33,7 @@
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.ImageView;
@@ -81,8 +82,19 @@
                     .setImageDrawable(drawable);
         }
 
-        ((TextView) view.findViewById(R.id.user_loading))
-                .setText(res.getString(R.string.car_loading_profile));
+        TextView msgView = view.findViewById(R.id.user_loading);
+
+        // TODO(b/145132885): use constant from CarSettings
+        boolean showInfo = "true".equals(Settings.Global.getString(
+                getContext().getContentResolver(),
+                "android.car.ENABLE_USER_SWITCH_DEVELOPER_MESSAGE"));
+
+        if (showInfo) {
+            msgView.setText(res.getString(R.string.car_loading_profile) + " user\n(from "
+                    + mOldUser.id + " to " + mNewUser.id + ")");
+        } else {
+            msgView.setText(res.getString(R.string.car_loading_profile));
+        }
         setView(view);
     }
 
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index aa8bc04..a03f0bb 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -16,15 +16,21 @@
 
 package com.android.server.am;
 
+import android.app.ActivityThread;
 import android.content.Context;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Bundle;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
+import android.widget.WidgetFlags;
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -36,6 +42,19 @@
 final class CoreSettingsObserver extends ContentObserver {
     private static final String LOG_TAG = CoreSettingsObserver.class.getSimpleName();
 
+    private static class DeviceConfigEntry {
+        String namespace;
+        String flag;
+        String coreSettingKey;
+        Class<?> type;
+        DeviceConfigEntry(String namespace, String flag, String coreSettingKey, Class<?> type) {
+            this.namespace = namespace;
+            this.flag = flag;
+            this.coreSettingKey = coreSettingKey;
+            this.type = type;
+        }
+    }
+
     // mapping form property name to its type
     @VisibleForTesting
     static final Map<String, Class<?>> sSecureSettingToTypeMap = new HashMap<
@@ -46,6 +65,7 @@
     @VisibleForTesting
     static final Map<String, Class<?>> sGlobalSettingToTypeMap = new HashMap<
             String, Class<?>>();
+    static final List<DeviceConfigEntry> sDeviceConfigEntries = new ArrayList<DeviceConfigEntry>();
     static {
         sSecureSettingToTypeMap.put(Settings.Secure.LONG_PRESS_TIMEOUT, int.class);
         sSecureSettingToTypeMap.put(Settings.Secure.MULTI_PRESS_TIMEOUT, int.class);
@@ -84,6 +104,26 @@
         sGlobalSettingToTypeMap.put(Settings.Global.GAME_DRIVER_BLACKLISTS, String.class);
         sGlobalSettingToTypeMap.put(Settings.Global.GAME_DRIVER_SPHAL_LIBRARIES, String.class);
         // add other global settings here...
+
+        sDeviceConfigEntries.add(new DeviceConfigEntry(
+                DeviceConfig.NAMESPACE_WIDGET, WidgetFlags.ENABLE_CURSOR_CONTROL,
+                WidgetFlags.KEY_ENABLE_CURSOR_CONTROL, boolean.class));
+        sDeviceConfigEntries.add(new DeviceConfigEntry(
+                DeviceConfig.NAMESPACE_WIDGET, WidgetFlags.INSERTION_HANDLE_DELTA_HEIGHT,
+                WidgetFlags.KEY_INSERTION_HANDLE_DELTA_HEIGHT, int.class));
+        sDeviceConfigEntries.add(new DeviceConfigEntry(
+                DeviceConfig.NAMESPACE_WIDGET, WidgetFlags.INSERTION_HANDLE_OPACITY,
+                WidgetFlags.KEY_INSERTION_HANDLE_OPACITY, int.class));
+        sDeviceConfigEntries.add(new DeviceConfigEntry(
+                DeviceConfig.NAMESPACE_WIDGET, WidgetFlags.ENABLE_NEW_MAGNIFIER,
+                WidgetFlags.KEY_ENABLE_NEW_MAGNIFIER, boolean.class));
+        sDeviceConfigEntries.add(new DeviceConfigEntry(
+                DeviceConfig.NAMESPACE_WIDGET, WidgetFlags.MAGNIFIER_ZOOM_FACTOR,
+                WidgetFlags.KEY_MAGNIFIER_ZOOM_FACTOR, float.class));
+        sDeviceConfigEntries.add(new DeviceConfigEntry(
+                DeviceConfig.NAMESPACE_WIDGET, WidgetFlags.MAGNIFIER_ASPECT_RATIO,
+                WidgetFlags.KEY_MAGNIFIER_ASPECT_RATIO, float.class));
+        // add other device configs here...
     }
 
     private final Bundle mCoreSettings = new Bundle();
@@ -112,6 +152,7 @@
         populateSettings(mCoreSettings, sSecureSettingToTypeMap);
         populateSettings(mCoreSettings, sSystemSettingToTypeMap);
         populateSettings(mCoreSettings, sGlobalSettingToTypeMap);
+        populateSettingsFromDeviceConfig();
         mActivityManagerService.onCoreSettingsChange(mCoreSettings);
     }
 
@@ -133,6 +174,16 @@
             mActivityManagerService.mContext.getContentResolver().registerContentObserver(
                     uri, false, this);
         }
+
+        HashSet<String> deviceConfigNamespaces = new HashSet<>();
+        for (DeviceConfigEntry entry : sDeviceConfigEntries) {
+            if (!deviceConfigNamespaces.contains(entry.namespace)) {
+                DeviceConfig.addOnPropertiesChangedListener(
+                        entry.namespace, ActivityThread.currentApplication().getMainExecutor(),
+                        (DeviceConfig.Properties prop) -> onChange(false));
+                deviceConfigNamespaces.add(entry.namespace);
+            }
+        }
     }
 
     @VisibleForTesting
@@ -164,4 +215,25 @@
             }
         }
     }
+
+    private void populateSettingsFromDeviceConfig() {
+        for (DeviceConfigEntry entry : sDeviceConfigEntries) {
+            if (entry.type == String.class) {
+                mCoreSettings.putString(entry.coreSettingKey,
+                        DeviceConfig.getString(entry.namespace, entry.flag, ""));
+            } else if (entry.type == int.class) {
+                mCoreSettings.putInt(entry.coreSettingKey,
+                        DeviceConfig.getInt(entry.namespace, entry.flag, 0));
+            } else if (entry.type == float.class) {
+                mCoreSettings.putFloat(entry.coreSettingKey,
+                        DeviceConfig.getFloat(entry.namespace, entry.flag, 0));
+            } else if (entry.type == long.class) {
+                mCoreSettings.putLong(entry.coreSettingKey,
+                        DeviceConfig.getLong(entry.namespace, entry.flag, 0));
+            } else if (entry.type == boolean.class) {
+                mCoreSettings.putInt(entry.coreSettingKey,
+                        DeviceConfig.getBoolean(entry.namespace, entry.flag, false) ? 1 : 0);
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 0fc885a..0429782 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -17,7 +17,9 @@
 package com.android.server.am;
 
 import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA;
 import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
 import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
 import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
 import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
@@ -33,6 +35,9 @@
 import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
 import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
 import static android.os.Process.SCHED_OTHER;
 import static android.os.Process.THREAD_GROUP_BACKGROUND;
 import static android.os.Process.THREAD_GROUP_DEFAULT;
@@ -64,7 +69,11 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
 
 import android.app.ActivityManager;
+import android.app.ApplicationExitInfo;
 import android.app.usage.UsageEvents;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.content.Context;
 import android.content.pm.ServiceInfo;
 import android.os.Build;
@@ -100,7 +109,6 @@
  */
 public final class OomAdjuster {
     private static final String TAG = "OomAdjuster";
-
     static final String OOM_ADJ_REASON_METHOD = "updateOomAdj";
     static final String OOM_ADJ_REASON_NONE = OOM_ADJ_REASON_METHOD + "_meh";
     static final String OOM_ADJ_REASON_ACTIVITY = OOM_ADJ_REASON_METHOD + "_activityChange";
@@ -117,14 +125,25 @@
     static final String OOM_ADJ_REASON_PROCESS_END = OOM_ADJ_REASON_METHOD + "_processEnd";
 
     /**
+     * Flag {@link android.content.Context#BIND_INCLUDE_CAPABILITIES} is used
+     * to pass while-in-use capabilities from client process to bound service. In targetSdkVersion
+     * R and above, if client is a TOP activity, when this flag is present, bound service gets all
+     * while-in-use capabilities; when this flag is not present, bound service gets no while-in-use
+     * capabilitiy from client.
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion=android.os.Build.VERSION_CODES.Q)
+    static final long PROCESS_CAPABILITY_CHANGE_ID = 136274596L;
+
+    /**
      * For some direct access we need to power manager.
      */
     PowerManagerInternal mLocalPowerManager;
 
     /**
-     * Service for compacting background apps.
+     * Service for optimizing resource usage from background apps.
      */
-    AppCompactor mAppCompact;
+    CachedAppOptimizer mCachedAppOptimizer;
 
     ActivityManagerConstants mConstants;
 
@@ -197,7 +216,7 @@
 
         mLocalPowerManager = LocalServices.getService(PowerManagerInternal.class);
         mConstants = mService.mConstants;
-        mAppCompact = new AppCompactor(mService);
+        mCachedAppOptimizer = new CachedAppOptimizer(mService);
 
         mProcessGroupHandler = new Handler(adjusterThread.getLooper(), msg -> {
             final int pid = msg.arg1;
@@ -224,7 +243,7 @@
     }
 
     void initSettings() {
-        mAppCompact.init();
+        mCachedAppOptimizer.init();
     }
 
     /**
@@ -242,7 +261,7 @@
             return updateOomAdjLocked(app, oomAdjReason);
         }
         final ProcessRecord TOP_APP = mService.getTopAppLocked();
-        final boolean wasCached = app.cached;
+        final boolean wasCached = app.isCached();
 
         mAdjSeq++;
 
@@ -255,7 +274,7 @@
         boolean success = updateOomAdjLocked(app, cachedAdj, TOP_APP, false,
                 SystemClock.uptimeMillis());
         if (oomAdjAll
-                && (wasCached != app.cached || app.getCurRawAdj() == ProcessList.UNKNOWN_ADJ)) {
+                && (wasCached != app.isCached() || app.getCurRawAdj() == ProcessList.UNKNOWN_ADJ)) {
             // Changed to/from cached state, so apps after it in the LRU
             // list may also be changed.
             updateOomAdjLocked(oomAdjReason);
@@ -331,7 +350,7 @@
         mAdjSeq++;
 
         // Firstly, try to see if the importance of itself gets changed
-        final boolean wasCached = app.cached;
+        final boolean wasCached = app.isCached();
         final int oldAdj = app.getCurRawAdj();
         final int cachedAdj = oldAdj >= ProcessList.CACHED_APP_MIN_ADJ
                 ? oldAdj : ProcessList.UNKNOWN_ADJ;
@@ -341,7 +360,7 @@
         app.resetCachedInfo();
         boolean success = updateOomAdjLocked(app, cachedAdj, topApp, false,
                 SystemClock.uptimeMillis());
-        if (!success || (wasCached == app.cached && oldAdj != ProcessList.INVALID_ADJ
+        if (!success || (wasCached == app.isCached() && oldAdj != ProcessList.INVALID_ADJ
                 && wasBackground == ActivityManager.isProcStateBackground(app.setProcState))) {
             // Okay, it's unchanged, it won't impact any service it binds to, we're done here.
             if (DEBUG_OOM_ADJ) {
@@ -757,7 +776,10 @@
                             lastCachedGroupUid = lastCachedGroup = 0;
                         }
                         if ((numCached - numCachedExtraGroup) > cachedProcessLimit) {
-                            app.kill("cached #" + numCached, true);
+                            app.kill("cached #" + numCached,
+                                    ApplicationExitInfo.REASON_OTHER,
+                                    ApplicationExitInfo.SUBREASON_TOO_MANY_CACHED,
+                                    true);
                         }
                         break;
                     case PROCESS_STATE_CACHED_EMPTY:
@@ -765,11 +787,17 @@
                                 && app.lastActivityTime < oldTime) {
                             app.kill("empty for "
                                     + ((oldTime + ProcessList.MAX_EMPTY_TIME - app.lastActivityTime)
-                                    / 1000) + "s", true);
+                                    / 1000) + "s",
+                                    ApplicationExitInfo.REASON_OTHER,
+                                    ApplicationExitInfo.SUBREASON_TRIM_EMPTY,
+                                    true);
                         } else {
                             numEmpty++;
                             if (numEmpty > emptyProcessLimit) {
-                                app.kill("empty #" + numEmpty, true);
+                                app.kill("empty #" + numEmpty,
+                                        ApplicationExitInfo.REASON_OTHER,
+                                        ApplicationExitInfo.SUBREASON_TOO_MANY_EMPTY,
+                                        true);
                             }
                         }
                         break;
@@ -786,7 +814,7 @@
                     // definition not re-use the same process again, and it is
                     // good to avoid having whatever code was running in them
                     // left sitting around after no longer needed.
-                    app.kill("isolated not needed", true);
+                    app.kill("isolated not needed", ApplicationExitInfo.REASON_OTHER, true);
                 } else {
                     // Keeping this process, update its uid.
                     updateAppUidRecLocked(app);
@@ -950,7 +978,7 @@
             if (schedGroup < ProcessList.SCHED_GROUP_DEFAULT) {
                 schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
             }
-            app.cached = false;
+            app.setCached(false);
             app.empty = false;
             foregroundActivities = true;
         }
@@ -975,7 +1003,7 @@
             if (schedGroup < ProcessList.SCHED_GROUP_DEFAULT) {
                 schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
             }
-            app.cached = false;
+            app.setCached(false);
             app.empty = false;
             foregroundActivities = true;
         }
@@ -1007,7 +1035,7 @@
                     }
                 }
             }
-            app.cached = false;
+            app.setCached(false);
             app.empty = false;
             foregroundActivities = true;
         }
@@ -1056,7 +1084,7 @@
         app.adjSource = null;
         app.adjTarget = null;
         app.empty = false;
-        app.cached = false;
+        app.setCached(false);
 
         final int appUid = app.info.uid;
         final int logUid = mService.mCurOomAdjUid;
@@ -1192,7 +1220,7 @@
             // value that the caller wants us to.
             adj = cachedAdj;
             procState = PROCESS_STATE_CACHED_EMPTY;
-            app.cached = true;
+            app.setCached(true);
             app.empty = true;
             app.adjType = "cch-empty";
             if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
@@ -1220,10 +1248,6 @@
             }
         }
 
-        if (app.hasLocationForegroundServices()) {
-            capability |= PROCESS_CAPABILITY_FOREGROUND_LOCATION;
-        }
-
         if (adj > ProcessList.PERCEPTIBLE_APP_ADJ
                 || procState > PROCESS_STATE_FOREGROUND_SERVICE) {
             if (app.hasForegroundServices()) {
@@ -1231,7 +1255,7 @@
                 adj = ProcessList.PERCEPTIBLE_APP_ADJ;
                 procState = PROCESS_STATE_FOREGROUND_SERVICE;
                 app.adjType = "fg-service";
-                app.cached = false;
+                app.setCached(false);
                 schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                     reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + app.adjType + ": "
@@ -1241,7 +1265,7 @@
                 // The process is display an overlay UI.
                 adj = ProcessList.PERCEPTIBLE_APP_ADJ;
                 procState = PROCESS_STATE_IMPORTANT_FOREGROUND;
-                app.cached = false;
+                app.setCached(false);
                 app.adjType = "has-overlay-ui";
                 schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
@@ -1271,7 +1295,7 @@
                 // thus out of background check), so we yes the best background level we can.
                 adj = ProcessList.PERCEPTIBLE_APP_ADJ;
                 procState = PROCESS_STATE_TRANSIENT_BACKGROUND;
-                app.cached = false;
+                app.setCached(false);
                 app.adjType = "force-imp";
                 app.adjSource = app.forcingToImportant;
                 schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
@@ -1286,7 +1310,7 @@
                 // We don't want to kill the current heavy-weight process.
                 adj = ProcessList.HEAVY_WEIGHT_APP_ADJ;
                 schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
-                app.cached = false;
+                app.setCached(false);
                 app.adjType = "heavy";
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                     reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to heavy: " + app);
@@ -1307,7 +1331,7 @@
                 // home app, so we don't want to let it go into the background.
                 adj = ProcessList.HOME_APP_ADJ;
                 schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
-                app.cached = false;
+                app.setCached(false);
                 app.adjType = "home";
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                     reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to home: " + app);
@@ -1329,7 +1353,7 @@
                 // a good experience around switching between two apps.
                 adj = ProcessList.PREVIOUS_APP_ADJ;
                 schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
-                app.cached = false;
+                app.setCached(false);
                 app.adjType = "previous";
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                     reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to prev: " + app);
@@ -1376,7 +1400,7 @@
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                     reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to backup: " + app);
                 }
-                app.cached = false;
+                app.setCached(false);
             }
             if (procState > ActivityManager.PROCESS_STATE_BACKUP) {
                 procState = ActivityManager.PROCESS_STATE_BACKUP;
@@ -1387,6 +1411,7 @@
             }
         }
 
+        int capabilityFromFGS = 0; // capability from foreground service.
         for (int is = app.services.size() - 1;
                 is >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
                         || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
@@ -1423,7 +1448,7 @@
                                 reportOomAdjMessageLocked(TAG_OOM_ADJ,
                                         "Raise adj to started service: " + app);
                             }
-                            app.cached = false;
+                            app.setCached(false);
                         }
                     }
                     // If we have let the service slide into the background
@@ -1435,6 +1460,24 @@
                 }
             }
 
+            if (s.isForeground) {
+                final int fgsType = s.foregroundServiceType;
+                capabilityFromFGS |=
+                        (fgsType & FOREGROUND_SERVICE_TYPE_LOCATION)
+                                != 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0;
+                if (s.appInfo.targetSdkVersion < Build.VERSION_CODES.R) {
+                    capabilityFromFGS |= PROCESS_CAPABILITY_FOREGROUND_CAMERA
+                            | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
+                } else {
+                    capabilityFromFGS |=
+                            (fgsType & FOREGROUND_SERVICE_TYPE_CAMERA)
+                                    != 0 ? PROCESS_CAPABILITY_FOREGROUND_CAMERA : 0;
+                    capabilityFromFGS |=
+                            (fgsType & FOREGROUND_SERVICE_TYPE_MICROPHONE)
+                                    != 0 ? PROCESS_CAPABILITY_FOREGROUND_MICROPHONE : 0;
+                }
+            }
+
             ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections = s.getConnections();
             for (int conni = serviceConnections.size() - 1;
                     conni >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
@@ -1495,7 +1538,7 @@
                                 if (adj > clientAdj) {
                                     adjType = "cch-bound-ui-services";
                                 }
-                                app.cached = false;
+                                app.setCached(false);
                                 clientAdj = adj;
                                 clientProcState = procState;
                             } else {
@@ -1558,8 +1601,8 @@
                                         newAdj = adj;
                                     }
                                 }
-                                if (!client.cached) {
-                                    app.cached = false;
+                                if (!client.isCached()) {
+                                    app.setCached(false);
                                 }
                                 if (adj >  newAdj) {
                                     adj = newAdj;
@@ -1601,7 +1644,7 @@
                                 // Go at most to BOUND_TOP, unless requested to elevate
                                 // to client's state.
                                 clientProcState = PROCESS_STATE_BOUND_TOP;
-                                if (client.info.targetSdkVersion >= Build.VERSION_CODES.R) {
+                                if (Compatibility.isChangeEnabled(PROCESS_CAPABILITY_CHANGE_ID)) {
                                     if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
                                         // TOP process passes all capabilities to the service.
                                         capability = PROCESS_CAPABILITY_ALL;
@@ -1683,7 +1726,7 @@
                                     schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
                                 }
                             }
-                            app.cached = false;
+                            app.setCached(false);
                             app.adjType = "service";
                             app.adjTypeCode = ActivityManager.RunningAppProcessInfo
                                     .REASON_SERVICE_IN_USE;
@@ -1748,7 +1791,7 @@
                         app.setCurRawAdj(adj);
                         adjType = "provider";
                     }
-                    app.cached &= client.cached;
+                    app.setCached(app.isCached() & client.isCached());
                 }
 
                 if (clientProcState <= PROCESS_STATE_FOREGROUND_SERVICE) {
@@ -1793,7 +1836,7 @@
                     adj = ProcessList.FOREGROUND_APP_ADJ;
                     app.setCurRawAdj(adj);
                     schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
-                    app.cached = false;
+                    app.setCached(false);
                     app.adjType = "ext-provider";
                     app.adjTarget = cpr.name;
                     if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
@@ -1817,7 +1860,7 @@
             if (adj > ProcessList.PREVIOUS_APP_ADJ) {
                 adj = ProcessList.PREVIOUS_APP_ADJ;
                 schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
-                app.cached = false;
+                app.setCached(false);
                 app.adjType = "recent-provider";
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                     reportOomAdjMessageLocked(TAG_OOM_ADJ,
@@ -1895,6 +1938,10 @@
             }
         }
 
+        // apply capability from FGS.
+        if (app.hasForegroundServices()) {
+            capability |= capabilityFromFGS;
+        }
         // TOP process has all capabilities.
         if (procState <= PROCESS_STATE_TOP) {
             capability = PROCESS_CAPABILITY_ALL;
@@ -1978,7 +2025,7 @@
         int changes = 0;
 
         // don't compact during bootup
-        if (mAppCompact.useCompaction() && mService.mBooted) {
+        if (mCachedAppOptimizer.useCompaction() && mService.mBooted) {
             // Cached and prev/home compaction
             if (app.curAdj != app.setAdj) {
                 // Perform a minor compaction when a perceptible app becomes the prev/home app
@@ -1987,26 +2034,26 @@
                 if (app.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ &&
                         (app.curAdj == ProcessList.PREVIOUS_APP_ADJ ||
                                 app.curAdj == ProcessList.HOME_APP_ADJ)) {
-                    mAppCompact.compactAppSome(app);
+                    mCachedAppOptimizer.compactAppSome(app);
                 } else if ((app.setAdj < ProcessList.CACHED_APP_MIN_ADJ
                                 || app.setAdj > ProcessList.CACHED_APP_MAX_ADJ)
                         && app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ
                         && app.curAdj <= ProcessList.CACHED_APP_MAX_ADJ) {
-                    mAppCompact.compactAppFull(app);
+                    mCachedAppOptimizer.compactAppFull(app);
                 }
             } else if (mService.mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE
                     && app.setAdj < ProcessList.FOREGROUND_APP_ADJ
                     // Because these can fire independent of oom_adj/procstate changes, we need
                     // to throttle the actual dispatch of these requests in addition to the
                     // processing of the requests. As a result, there is throttling both here
-                    // and in AppCompactor.
-                    && mAppCompact.shouldCompactPersistent(app, now)) {
-                mAppCompact.compactAppPersistent(app);
+                    // and in CachedAppOptimizer.
+                    && mCachedAppOptimizer.shouldCompactPersistent(app, now)) {
+                mCachedAppOptimizer.compactAppPersistent(app);
             } else if (mService.mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE
                     && app.getCurProcState()
                         == ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
-                    && mAppCompact.shouldCompactBFGS(app, now)) {
-                mAppCompact.compactAppBfgs(app);
+                    && mCachedAppOptimizer.shouldCompactBFGS(app, now)) {
+                mCachedAppOptimizer.compactAppBfgs(app);
             }
         }
 
@@ -2032,7 +2079,7 @@
             }
             if (app.waitingToKill != null && app.curReceivers.isEmpty()
                     && app.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND) {
-                app.kill(app.waitingToKill, true);
+                app.kill(app.waitingToKill, ApplicationExitInfo.REASON_OTHER, true);
                 success = false;
             } else {
                 int processGroup;
@@ -2439,7 +2486,7 @@
     }
 
     @GuardedBy("mService")
-    void dumpAppCompactorSettings(PrintWriter pw) {
-        mAppCompact.dump(pw);
+    void dumpCachedAppOptimizerSettings(PrintWriter pw) {
+        mCachedAppOptimizer.dump(pw);
     }
 }
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index a1e1f29..38cb501 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -22,6 +22,7 @@
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
 import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode;
 import static android.net.NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground;
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
 import static android.os.Process.getFreeMemory;
@@ -53,23 +54,33 @@
 import android.app.ActivityThread;
 import android.app.AppGlobals;
 import android.app.AppProtoEnums;
+import android.app.ApplicationExitInfo;
+import android.app.ApplicationExitInfo.Reason;
+import android.app.ApplicationExitInfo.SubReason;
 import android.app.IApplicationThread;
+import android.app.IUidObserver;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.res.Resources;
 import android.graphics.Point;
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
 import android.os.AppZygote;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.DropBoxManager;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.StrictMode;
@@ -80,9 +91,7 @@
 import android.os.storage.StorageManager;
 import android.os.storage.StorageManagerInternal;
 import android.provider.DeviceConfig;
-import android.system.ErrnoException;
 import android.system.Os;
-import android.system.OsConstants;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.EventLog;
@@ -103,6 +112,7 @@
 import com.android.internal.util.MemInfoReader;
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
+import com.android.server.SystemConfig;
 import com.android.server.Watchdog;
 import com.android.server.compat.PlatformCompat;
 import com.android.server.pm.dex.DexManager;
@@ -112,6 +122,7 @@
 import dalvik.system.VMRuntime;
 
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.PrintWriter;
@@ -281,6 +292,10 @@
     private static final String PROPERTY_USE_APP_IMAGE_STARTUP_CACHE =
             "persist.device_config.runtime_native.use_app_image_startup_cache";
 
+    // The socket path for zygote to send unsolicited msg.
+    // Must keep sync with com_android_internal_os_Zygote.cpp.
+    private static final String UNSOL_ZYGOTE_MSG_SOCKET_PATH = "/data/system/unsolzygotesocket";
+
     // Low Memory Killer Daemon command codes.
     // These must be kept in sync with lmk_cmd definitions in lmkd.h
     //
@@ -310,12 +325,7 @@
     /**
      * How long between a process kill and we actually receive its death recipient
      */
-    private static final long PROC_KILL_TIMEOUT = 2000; // 2 seconds;
-
-    /**
-     * How long between polls to check if the given process is dead or not.
-     */
-    private static final long PROC_DEATH_POLL_INTERVAL = 100;
+    private static final int PROC_KILL_TIMEOUT = 2000; // 2 seconds;
 
     ActivityManagerService mService = null;
 
@@ -357,6 +367,8 @@
 
     private boolean mAppDataIsolationEnabled = false;
 
+    private ArrayList<String> mAppDataIsolationWhitelistedApps;
+
     /**
      * Temporary to avoid allocations.  Protected by main lock.
      */
@@ -413,12 +425,6 @@
     ActiveUids mActiveUids;
 
     /**
-     * The listener who is intereted with the lmkd kills.
-     */
-    @GuardedBy("mService")
-    private LmkdKillListener mLmkdKillListener = null;
-
-    /**
      * The currently running isolated processes.
      */
     final SparseArray<ProcessRecord> mIsolatedProcesses = new SparseArray<>();
@@ -429,6 +435,12 @@
     final ProcessMap<AppZygote> mAppZygotes = new ProcessMap<AppZygote>();
 
     /**
+     * Managees the {@link android.app.ApplicationExitInfo} records.
+     */
+    @GuardedBy("mAppExitInfoTracker")
+    final AppExitInfoTracker mAppExitInfoTracker = new AppExitInfoTracker();
+
+    /**
      * The processes that are forked off an application zygote.
      */
     final ArrayMap<AppZygote, ArrayList<ProcessRecord>> mAppZygoteProcesses =
@@ -436,6 +448,28 @@
 
     private PlatformCompat mPlatformCompat = null;
 
+    /**
+     * The server socket in system_server, zygote will connect to it
+     * in order to send unsolicited messages to system_server.
+     */
+    private LocalSocket mSystemServerSocketForZygote;
+
+    /**
+     * Maximum number of bytes that an incoming unsolicited zygote message could be.
+     * To be updated if new message type needs to be supported.
+     */
+    private static final int MAX_ZYGOTE_UNSOLICITED_MESSAGE_SIZE = 16;
+
+    /**
+     * The buffer to be used to receive the incoming unsolicited zygote message.
+     */
+    private final byte[] mZygoteUnsolicitedMessage = new byte[MAX_ZYGOTE_UNSOLICITED_MESSAGE_SIZE];
+
+    /**
+     * The buffer to be used to receive the SIGCHLD data, it includes pid/uid/status.
+     */
+    private final int[] mZygoteSigChldMessage = new int[3];
+
     interface LmkdKillListener {
         /**
          * Called when there is a process kill by lmkd.
@@ -594,7 +628,6 @@
     final class KillHandler extends Handler {
         static final int KILL_PROCESS_GROUP_MSG = 4000;
         static final int LMKD_RECONNECT_MSG = 4001;
-        static final int LMKD_PROC_KILLED_MSG = 4002;
 
         public KillHandler(Looper looper) {
             super(looper, null, true);
@@ -617,16 +650,17 @@
                                 KillHandler.LMKD_RECONNECT_MSG), LMKD_RECONNECT_DELAY_MS);
                     }
                     break;
-                case LMKD_PROC_KILLED_MSG:
-                    handleLmkdProcKilled(msg.arg1 /* pid */, msg.arg2 /* uid */);
-                    break;
-
                 default:
                     super.handleMessage(msg);
             }
         }
     }
 
+    /**
+     * A runner to handle the imperceptible killings.
+     */
+    ImperceptibleKillRunner mImperceptibleKillRunner;
+
     ////////////////////  END FIELDS  ////////////////////
 
     ProcessList() {
@@ -644,7 +678,9 @@
         // Get this after boot, and won't be changed until it's rebooted, as we don't
         // want some apps enabled while some apps disabled
         mAppDataIsolationEnabled =
-                SystemProperties.getBoolean(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, false);
+                SystemProperties.getBoolean(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, true);
+        mAppDataIsolationWhitelistedApps = new ArrayList<>(
+                SystemConfig.getInstance().getAppDataIsolationWhitelistedApps());
 
         if (sKillHandler == null) {
             sKillThread = new ServiceThread(TAG + ":kill",
@@ -687,9 +723,8 @@
                                     if (receivedLen != 12) {
                                         return false;
                                     }
-                                    sKillHandler.obtainMessage(KillHandler.LMKD_PROC_KILLED_MSG,
-                                            dataReceived.getInt(4), dataReceived.getInt(8))
-                                            .sendToTarget();
+                                    mAppExitInfoTracker.scheduleNoteLmkdProcKilled(
+                                            dataReceived.getInt(4), dataReceived.getInt(8));
                                     return true;
                                 default:
                                     return false;
@@ -697,9 +732,22 @@
                         }
                     }
             );
+            // Start listening on incoming connections from zygotes.
+            mSystemServerSocketForZygote = createSystemServerSocketForZygote();
+            if (mSystemServerSocketForZygote != null) {
+                sKillHandler.getLooper().getQueue().addOnFileDescriptorEventListener(
+                        mSystemServerSocketForZygote.getFileDescriptor(),
+                        EVENT_INPUT, this::handleZygoteMessages);
+            }
+            mAppExitInfoTracker.init(mService, sKillThread.getLooper());
+            mImperceptibleKillRunner = new ImperceptibleKillRunner(sKillThread.getLooper());
         }
     }
 
+    void onSystemReady() {
+        mAppExitInfoTracker.onSystemReady();
+    }
+
     void applyDisplaySize(WindowManagerService wm) {
         if (!mHaveDisplaySize) {
             Point p = new Point();
@@ -1432,7 +1480,10 @@
                             proc.lastCachedPss, holder.appVersion);
                 }
             }
-            proc.kill(Long.toString(proc.lastCachedPss) + "k from cached", true);
+            proc.kill(Long.toString(proc.lastCachedPss) + "k from cached",
+                    ApplicationExitInfo.REASON_OTHER,
+                    ApplicationExitInfo.SUBREASON_LARGE_CACHED,
+                    true);
         } else if (proc != null && !keepIfLarge
                 && mService.mLastMemoryLevel > ProcessStats.ADJ_MEM_FACTOR_NORMAL
                 && proc.setProcState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY) {
@@ -1451,7 +1502,10 @@
                                 proc.lastCachedPss, holder.appVersion);
                     }
                 }
-                proc.kill(Long.toString(proc.lastCachedPss) + "k from cached", true);
+                proc.kill(Long.toString(proc.lastCachedPss) + "k from cached",
+                        ApplicationExitInfo.REASON_OTHER,
+                        ApplicationExitInfo.SUBREASON_LARGE_CACHED,
+                        true);
             }
         }
         return proc;
@@ -1510,7 +1564,7 @@
         long startTime = SystemClock.uptimeMillis();
         if (app.pid > 0 && app.pid != ActivityManagerService.MY_PID) {
             checkSlow(startTime, "startProcess: removing from pids map");
-            mService.mPidsSelfLocked.remove(app);
+            mService.removePidLocked(app);
             mService.mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
             checkSlow(startTime, "startProcess: done removing from pids map");
             app.setPid(0);
@@ -1556,20 +1610,50 @@
                     throw e.rethrowAsRuntimeException();
                 }
 
+                // Remove any gids needed if the process has been denied permissions.
+                // NOTE: eventually we should probably have the package manager pre-compute
+                // this for us?
+                if (app.processInfo != null && app.processInfo.deniedPermissions != null) {
+                    for (int i = app.processInfo.deniedPermissions.size() - 1; i >= 0; i--) {
+                        int[] denyGids = mService.mPackageManagerInt.getPermissionGids(
+                                app.processInfo.deniedPermissions.valueAt(i), app.userId);
+                        if (denyGids != null) {
+                            for (int gid : denyGids) {
+                                permGids = ArrayUtils.removeInt(permGids, gid);
+                            }
+                        }
+                    }
+                }
+
+                int numGids = 3;
+                if (mountExternal == Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE
+                        || app.info.packageName.equals("com.android.externalstorage")) {
+                    numGids++;
+                }
+
                 /*
                  * Add shared application and profile GIDs so applications can share some
                  * resources like shared libraries and access user-wide resources
                  */
                 if (ArrayUtils.isEmpty(permGids)) {
-                    gids = new int[3];
+                    gids = new int[numGids];
                 } else {
-                    gids = new int[permGids.length + 3];
-                    System.arraycopy(permGids, 0, gids, 3, permGids.length);
+                    gids = new int[permGids.length + numGids];
+                    System.arraycopy(permGids, 0, gids, numGids, permGids.length);
                 }
                 gids[0] = UserHandle.getSharedAppGid(UserHandle.getAppId(uid));
                 gids[1] = UserHandle.getCacheAppGid(UserHandle.getAppId(uid));
                 gids[2] = UserHandle.getUserGid(UserHandle.getUserId(uid));
 
+                if (numGids > 3) {
+                    if (app.info.packageName.equals("com.android.externalstorage")) {
+                        // Allows access to 'unreliable' (USB OTG) volumes via SAF
+                        gids[3] = Process.MEDIA_RW_GID;
+                    } else if (mountExternal == Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE) {
+                        gids[3] = Process.SDCARD_RW_GID;
+                    }
+                }
+
                 // Replace any invalid GIDs
                 if (gids[0] == UserHandle.ERR_GID) gids[0] = gids[2];
                 if (gids[1] == UserHandle.ERR_GID) gids[1] = gids[2];
@@ -1905,6 +1989,16 @@
                 result.put(packageName, Pair.create(volumeUuid, inode));
             }
         }
+        if (mAppDataIsolationWhitelistedApps != null) {
+            for (String packageName : mAppDataIsolationWhitelistedApps) {
+                String volumeUuid = pmInt.getPackage(packageName).getVolumeUuid();
+                long inode = pmInt.getCeDataInode(packageName, userId);
+                if (inode != 0) {
+                    result.put(packageName, Pair.create(volumeUuid, inode));
+                }
+            }
+        }
+
         return result;
     }
 
@@ -2092,28 +2186,6 @@
     }
 
     /**
-     * A lite version of checking if a process is alive or not, by using kill(2) with signal 0.
-     *
-     * <p>
-     * Note that, zombie processes are stil "alive" in this case, use the {@link
-     * ActivityManagerService#isProcessAliveLocked} if zombie processes need to be excluded.
-     * </p>
-     */
-    @GuardedBy("mService")
-    private boolean isProcessAliveLiteLocked(ProcessRecord app) {
-        // If somehow the pid is invalid, let's think it's dead.
-        if (app.pid <= 0) {
-            return false;
-        }
-        try {
-            Os.kill(app.pid, 0);
-        } catch (ErrnoException e) {
-            return e.errno != OsConstants.ESRCH;
-        }
-        return true;
-    }
-
-    /**
      * Kill (if asked to) and wait for the given process died if necessary
      * @param app - The process record to kill
      * @param doKill - Kill the given process record
@@ -2130,24 +2202,16 @@
         if (doKill) {
             // do the killing
             ProcessList.killProcessGroup(app.uid, app.pid);
+            noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
+                    ApplicationExitInfo.SUBREASON_UNKNOWN,
+                    String.format(formatString, ""));
         }
 
         // wait for the death
         if (wait) {
-            boolean isAlive = true;
-            // ideally we should use pidfd_open(2) but it's available on kernel 5.3 or later
-
-            final long timeout = SystemClock.uptimeMillis() + PROC_KILL_TIMEOUT;
-            isAlive = isProcessAliveLiteLocked(app);
-            while (timeout > SystemClock.uptimeMillis() && isAlive) {
-                try {
-                    Thread.sleep(PROC_DEATH_POLL_INTERVAL);
-                } catch (InterruptedException e) {
-                }
-                isAlive = isProcessAliveLiteLocked(app);
-            }
-
-            if (isAlive) {
+            try {
+                Process.waitForProcessDeath(app.pid, PROC_KILL_TIMEOUT);
+            } catch (Exception e) {
                 // Maybe the process goes into zombie, use an expensive API to check again.
                 if (mService.isProcessAliveLocked(app)) {
                     Slog.w(TAG, String.format(formatString,
@@ -2208,6 +2272,8 @@
             app.pendingStart = false;
             killProcessQuiet(pid);
             Process.killProcessGroup(app.uid, app.pid);
+            noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
+                    ApplicationExitInfo.SUBREASON_UNKNOWN, reason);
             return false;
         }
         mService.mBatteryStatsService.noteProcessStart(app.processName, app.info.uid);
@@ -2269,7 +2335,7 @@
             mService.cleanUpApplicationRecordLocked(oldApp, false, false, -1,
                     true /*replacingPid*/);
         }
-        mService.mPidsSelfLocked.put(app);
+        mService.addPidLocked(app);
         synchronized (mService.mPidsSelfLocked) {
             if (!procAttached) {
                 Message msg = mService.mHandler.obtainMessage(PROC_START_TIMEOUT_MSG);
@@ -2293,6 +2359,8 @@
                     if (app.pid > 0) {
                         killProcessQuiet(app.pid);
                         ProcessList.killProcessGroup(app.uid, app.pid);
+                        noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
+                                ApplicationExitInfo.SUBREASON_UNKNOWN, "hasn't been killed");
                     } else {
                         app.pendingStart = false;
                     }
@@ -2423,6 +2491,12 @@
     @GuardedBy("mService")
     boolean removeProcessLocked(ProcessRecord app,
             boolean callerWillRestart, boolean allowRestart, String reason) {
+        return removeProcessLocked(app, callerWillRestart, allowRestart, reason,
+                ApplicationExitInfo.REASON_OTHER);
+    }
+
+    boolean removeProcessLocked(ProcessRecord app,
+            boolean callerWillRestart, boolean allowRestart, String reason, int reasonCode) {
         final String name = app.processName;
         final int uid = app.uid;
         if (DEBUG_PROCESSES) Slog.d(TAG_PROCESSES,
@@ -2442,7 +2516,7 @@
                 .pendingStart)) {
             int pid = app.pid;
             if (pid > 0) {
-                mService.mPidsSelfLocked.remove(app);
+                mService.removePidLocked(app);
                 mService.mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
                 mService.mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid);
                 if (app.isolated) {
@@ -2458,7 +2532,7 @@
                     needRestart = true;
                 }
             }
-            app.kill(reason, true);
+            app.kill(reason, reasonCode, true);
             mService.handleAppDiedLocked(app, willRestart, allowRestart);
             if (willRestart) {
                 removeLruProcessLocked(app);
@@ -2543,6 +2617,7 @@
                 // the uid of the isolated process is specified by the caller.
                 uid = isolatedUid;
             }
+            mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(uid, info.uid);
             mService.getPackageManagerInternalLocked().addIsolatedUid(uid, info.uid);
 
             // Register the isolated UID with this application so BatteryStats knows to
@@ -3516,27 +3591,381 @@
         }
     }
 
-    void setLmkdKillListener(final LmkdKillListener listener) {
-        synchronized (mService) {
-            mLmkdKillListener = listener;
+    /**
+     * Create a server socket in system_server, zygote will connect to it
+     * in order to send unsolicited messages to system_server.
+     */
+    private LocalSocket createSystemServerSocketForZygote() {
+        // The file system entity for this socket is created with 0666 perms, owned
+        // by system:system. selinux restricts things so that only zygotes can
+        // access it.
+        final File socketFile = new File(UNSOL_ZYGOTE_MSG_SOCKET_PATH);
+        if (socketFile.exists()) {
+            socketFile.delete();
         }
+
+        LocalSocket serverSocket = null;
+        try {
+            serverSocket = new LocalSocket(LocalSocket.SOCKET_DGRAM);
+            serverSocket.bind(new LocalSocketAddress(
+                    UNSOL_ZYGOTE_MSG_SOCKET_PATH, LocalSocketAddress.Namespace.FILESYSTEM));
+            Os.chmod(UNSOL_ZYGOTE_MSG_SOCKET_PATH, 0666);
+        } catch (Exception e) {
+            if (serverSocket != null) {
+                try {
+                    serverSocket.close();
+                } catch (IOException ex) {
+                }
+                serverSocket = null;
+            }
+        }
+        return serverSocket;
     }
 
-    private void handleLmkdProcKilled(final int pid, final int uid) {
-        // Log only now
+    /**
+     * Handle the unsolicited message from zygote.
+     */
+    private int handleZygoteMessages(FileDescriptor fd, int events) {
+        final int eventFd = fd.getInt$();
+        if ((events & EVENT_INPUT) != 0) {
+            // An incoming message from zygote
+            try {
+                final int len = Os.read(fd, mZygoteUnsolicitedMessage, 0,
+                        mZygoteUnsolicitedMessage.length);
+                if (len > 0 && mZygoteSigChldMessage.length == Zygote.nativeParseSigChld(
+                        mZygoteUnsolicitedMessage, len, mZygoteSigChldMessage)) {
+                    mAppExitInfoTracker.handleZygoteSigChld(
+                            mZygoteSigChldMessage[0] /* pid */,
+                            mZygoteSigChldMessage[1] /* uid */,
+                            mZygoteSigChldMessage[2] /* status */);
+                }
+            } catch (Exception e) {
+                Slog.w(TAG, "Exception in reading unsolicited zygote message: " + e);
+            }
+        }
+        return EVENT_INPUT;
+    }
+
+    /**
+     * Called by ActivityManagerService when a process died.
+     */
+    @GuardedBy("mService")
+    void noteProcessDiedLocked(final ProcessRecord app) {
         if (DEBUG_PROCESSES) {
-            Slog.i(TAG, "lmkd kill: pid=" + pid + " uid=" + uid);
+            Slog.i(TAG, "note: " + app + " died, saving the exit info");
         }
 
-        if (mService == null) {
+        mAppExitInfoTracker.scheduleNoteProcessDiedLocked(app);
+    }
+
+    /**
+     * Called by ActivityManagerService when it decides to kill an application process.
+     */
+    void noteAppKill(final ProcessRecord app, final @Reason int reason,
+            final @SubReason int subReason, final String msg) {
+        if (DEBUG_PROCESSES) {
+            Slog.i(TAG, "note: " + app + " is being killed, reason: " + reason
+                    + ", sub-reason: " + subReason + ", message: " + msg);
+        }
+        mAppExitInfoTracker.scheduleNoteAppKill(app, reason, subReason, msg);
+    }
+
+    void noteAppKill(final int pid, final int uid, final @Reason int reason,
+            final @SubReason int subReason, final String msg) {
+        if (DEBUG_PROCESSES) {
+            Slog.i(TAG, "note: " + pid + " is being killed, reason: " + reason
+                    + ", sub-reason: " + subReason + ", message: " + msg);
+        }
+
+        mAppExitInfoTracker.scheduleNoteAppKill(pid, uid, reason, subReason, msg);
+    }
+
+    /**
+     * Schedule to kill the given pids when the device is idle
+     */
+    void killProcessesWhenImperceptible(int[] pids, String reason, int requester) {
+        if (ArrayUtils.isEmpty(pids)) {
             return;
         }
-        // Notify any interesed party regarding the lmkd kills
+
         synchronized (mService) {
-            final LmkdKillListener listener = mLmkdKillListener;
-            if (listener != null) {
-                mService.mHandler.post(()-> listener.onLmkdKillOccurred(pid, uid));
+            ProcessRecord app;
+            for (int i = 0; i < pids.length; i++) {
+                synchronized (mService.mPidsSelfLocked) {
+                    app = mService.mPidsSelfLocked.get(pids[i]);
+                }
+                if (app != null) {
+                    mImperceptibleKillRunner.enqueueLocked(app, reason, requester);
+                }
             }
         }
     }
+
+    private final class ImperceptibleKillRunner extends IUidObserver.Stub {
+        private static final String EXTRA_PID = "pid";
+        private static final String EXTRA_UID = "uid";
+        private static final String EXTRA_TIMESTAMP = "timestamp";
+        private static final String EXTRA_REASON = "reason";
+        private static final String EXTRA_REQUESTER = "requester";
+
+        private static final String DROPBOX_TAG_IMPERCEPTIBLE_KILL = "imperceptible_app_kill";
+
+        // uid -> killing information mapping
+        private SparseArray<List<Bundle>> mWorkItems = new SparseArray<List<Bundle>>();
+
+        // The last time the various processes have been killed by us.
+        private ProcessMap<Long> mLastProcessKillTimes = new ProcessMap<>();
+
+        // Device idle or not.
+        private volatile boolean mIdle;
+        private boolean mUidObserverEnabled;
+        private Handler mHandler;
+        private IdlenessReceiver mReceiver;
+
+        private final class H extends Handler {
+            static final int MSG_DEVICE_IDLE = 0;
+            static final int MSG_UID_GONE = 1;
+            static final int MSG_UID_STATE_CHANGED = 2;
+
+            H(Looper looper) {
+                super(looper);
+            }
+
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case MSG_DEVICE_IDLE:
+                        handleDeviceIdle();
+                        break;
+                    case MSG_UID_GONE:
+                        handleUidGone(msg.arg1 /* uid */);
+                        break;
+                    case MSG_UID_STATE_CHANGED:
+                        handleUidStateChanged(msg.arg1 /* uid */, msg.arg2 /* procState */);
+                        break;
+                }
+            }
+        }
+
+        private final class IdlenessReceiver extends BroadcastReceiver {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final PowerManager pm = mService.mContext.getSystemService(PowerManager.class);
+                switch (intent.getAction()) {
+                    case PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED:
+                        notifyDeviceIdleness(pm.isLightDeviceIdleMode());
+                        break;
+                    case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED:
+                        notifyDeviceIdleness(pm.isDeviceIdleMode());
+                        break;
+                }
+            }
+        }
+
+        ImperceptibleKillRunner(Looper looper) {
+            mHandler = new H(looper);
+        }
+
+        @GuardedBy("mService")
+        boolean enqueueLocked(ProcessRecord app, String reason, int requester) {
+            // Throttle the killing request for potential bad app to avoid cpu thrashing
+            Long last = app.isolated ? null : mLastProcessKillTimes.get(app.processName, app.uid);
+            if (last != null && SystemClock.uptimeMillis() < last + MIN_CRASH_INTERVAL) {
+                return false;
+            }
+
+            final Bundle bundle = new Bundle();
+            bundle.putInt(EXTRA_PID, app.pid);
+            bundle.putInt(EXTRA_UID, app.uid);
+            // Since the pid could be reused, let's get the actual start time of each process
+            bundle.putLong(EXTRA_TIMESTAMP, app.startTime);
+            bundle.putString(EXTRA_REASON, reason);
+            bundle.putInt(EXTRA_REQUESTER, requester);
+            List<Bundle> list = mWorkItems.get(app.uid);
+            if (list == null) {
+                list = new ArrayList<Bundle>();
+                mWorkItems.put(app.uid, list);
+            }
+            list.add(bundle);
+            if (mReceiver == null) {
+                mReceiver = new IdlenessReceiver();
+                IntentFilter filter = new IntentFilter(
+                        PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
+                filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+                mService.mContext.registerReceiver(mReceiver, filter);
+            }
+            return true;
+        }
+
+        void notifyDeviceIdleness(boolean idle) {
+            // No lock is held regarding mIdle, this function is the only updater and caller
+            // won't re-entry.
+            boolean diff = mIdle != idle;
+            mIdle = idle;
+            if (diff && idle) {
+                synchronized (this) {
+                    if (mWorkItems.size() > 0) {
+                        mHandler.sendEmptyMessage(H.MSG_DEVICE_IDLE);
+                    }
+                }
+            }
+        }
+
+        private void handleDeviceIdle() {
+            final DropBoxManager dbox = mService.mContext.getSystemService(DropBoxManager.class);
+            final boolean logToDropbox = dbox != null
+                    && dbox.isTagEnabled(DROPBOX_TAG_IMPERCEPTIBLE_KILL);
+
+            synchronized (mService) {
+                final int size = mWorkItems.size();
+                for (int i = size - 1; mIdle && i >= 0; i--) {
+                    List<Bundle> list = mWorkItems.valueAt(i);
+                    final int len = list.size();
+                    for (int j = len - 1; mIdle && j >= 0; j--) {
+                        Bundle bundle = list.get(j);
+                        if (killProcessLocked(
+                                bundle.getInt(EXTRA_PID),
+                                bundle.getInt(EXTRA_UID),
+                                bundle.getLong(EXTRA_TIMESTAMP),
+                                bundle.getString(EXTRA_REASON),
+                                bundle.getInt(EXTRA_REQUESTER),
+                                dbox, logToDropbox)) {
+                            list.remove(j);
+                        }
+                    }
+                    if (list.size() == 0) {
+                        mWorkItems.removeAt(i);
+                    }
+                }
+                registerUidObserverIfNecessaryLocked();
+            }
+        }
+
+        @GuardedBy("mService")
+        private void registerUidObserverIfNecessaryLocked() {
+            // If there are still works remaining, register UID observer
+            if (!mUidObserverEnabled && mWorkItems.size() > 0) {
+                mUidObserverEnabled = true;
+                mService.registerUidObserver(this,
+                        ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE,
+                        ActivityManager.PROCESS_STATE_UNKNOWN, "android");
+            } else if (mUidObserverEnabled && mWorkItems.size() == 0) {
+                mUidObserverEnabled = false;
+                mService.unregisterUidObserver(this);
+            }
+        }
+
+        /**
+         * Kill the given processes, if they are not exempted.
+         *
+         * @return True if the process is killed, or it's gone already, or we are not allowed to
+         *         kill it (one of the packages in this process is being exempted).
+         */
+        @GuardedBy("mService")
+        private boolean killProcessLocked(final int pid, final int uid, final long timestamp,
+                final String reason, final int requester, final DropBoxManager dbox,
+                final boolean logToDropbox) {
+            ProcessRecord app = null;
+            synchronized (mService.mPidsSelfLocked) {
+                app = mService.mPidsSelfLocked.get(pid);
+            }
+
+            if (app == null || app.pid != pid || app.uid != uid || app.startTime != timestamp) {
+                // This process record has been reused for another process, meaning the old process
+                // has been gone.
+                return true;
+            }
+
+            final int pkgSize = app.pkgList.size();
+            for (int ip = 0; ip < pkgSize; ip++) {
+                if (mService.mConstants.IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.contains(
+                        app.pkgList.keyAt(ip))) {
+                    // One of the packages in this process is exempted
+                    return true;
+                }
+            }
+
+            if (mService.mConstants.IMPERCEPTIBLE_KILL_EXEMPT_PROC_STATES.contains(
+                    app.getReportedProcState())) {
+                // We need to reschedule it.
+                return false;
+            }
+
+            app.kill(reason, ApplicationExitInfo.REASON_OTHER, true);
+
+            if (!app.isolated) {
+                mLastProcessKillTimes.put(app.processName, app.uid, SystemClock.uptimeMillis());
+            }
+
+            if (logToDropbox) {
+                final long now = SystemClock.elapsedRealtime();
+                final StringBuilder sb = new StringBuilder();
+                mService.appendDropBoxProcessHeaders(app, app.processName, sb);
+                sb.append("Reason: " + reason).append("\n");
+                sb.append("Requester UID: " + requester).append("\n");
+                dbox.addText(DROPBOX_TAG_IMPERCEPTIBLE_KILL, sb.toString());
+            }
+            return true;
+        }
+
+        private void handleUidStateChanged(int uid, int procState) {
+            final DropBoxManager dbox = mService.mContext.getSystemService(DropBoxManager.class);
+            final boolean logToDropbox = dbox != null
+                    && dbox.isTagEnabled(DROPBOX_TAG_IMPERCEPTIBLE_KILL);
+            synchronized (mService) {
+                if (mIdle && !mService.mConstants
+                        .IMPERCEPTIBLE_KILL_EXEMPT_PROC_STATES.contains(procState)) {
+                    List<Bundle> list = mWorkItems.get(uid);
+                    if (list != null) {
+                        final int len = list.size();
+                        for (int j = len - 1; mIdle && j >= 0; j--) {
+                            Bundle bundle = list.get(j);
+                            if (killProcessLocked(
+                                    bundle.getInt(EXTRA_PID),
+                                    bundle.getInt(EXTRA_UID),
+                                    bundle.getLong(EXTRA_TIMESTAMP),
+                                    bundle.getString(EXTRA_REASON),
+                                    bundle.getInt(EXTRA_REQUESTER),
+                                    dbox, logToDropbox)) {
+                                list.remove(j);
+                            }
+                        }
+                        if (list.size() == 0) {
+                            mWorkItems.remove(uid);
+                        }
+                        registerUidObserverIfNecessaryLocked();
+                    }
+                }
+            }
+        }
+
+        private void handleUidGone(int uid) {
+            synchronized (mService) {
+                mWorkItems.remove(uid);
+                registerUidObserverIfNecessaryLocked();
+            }
+        }
+
+        @Override
+        public void onUidGone(int uid, boolean disabled) {
+            mHandler.obtainMessage(H.MSG_UID_GONE, uid, 0).sendToTarget();
+        }
+
+        @Override
+        public void onUidActive(int uid) {
+        }
+
+        @Override
+        public void onUidIdle(int uid, boolean disabled) {
+        }
+
+        @Override
+        public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
+            mHandler.obtainMessage(H.MSG_UID_STATE_CHANGED, uid, procState).sendToTarget();
+        }
+
+        @Override
+        public void onUidCachedChanged(int uid, boolean cached) {
+        }
+    };
 }
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 1fef353..0e1e0f9 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -27,11 +27,15 @@
 
 import android.app.ActivityManager;
 import android.app.ApplicationErrorReport;
+import android.app.ApplicationExitInfo;
+import android.app.ApplicationExitInfo.Reason;
+import android.app.ApplicationExitInfo.SubReason;
 import android.app.Dialog;
 import android.app.IApplicationThread;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.ProcessInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.VersionedPackage;
 import android.content.res.CompatibilityInfo;
@@ -62,8 +66,6 @@
 import com.android.internal.os.BatteryStatsImpl;
 import com.android.internal.os.ProcessCpuTracker;
 import com.android.internal.os.Zygote;
-import com.android.server.LocalServices;
-import com.android.server.wm.WindowManagerInternal;
 import com.android.server.wm.WindowProcessController;
 import com.android.server.wm.WindowProcessListener;
 
@@ -83,6 +85,7 @@
 
     private final ActivityManagerService mService; // where we came from
     final ApplicationInfo info; // all about the first app in the process
+    final ProcessInfo processInfo; // if non-null, process-specific manifest info
     final boolean isolated;     // true if this is a special isolated process
     final boolean appZygote;    // true if this is forked from the app zygote
     final int uid;              // uid of process; may be different from 'info' if isolated
@@ -236,7 +239,7 @@
     long lastTopTime;           // The last time the process was in the TOP state or greater.
     boolean reportLowMemory;    // Set to true when waiting to report low mem
     boolean empty;              // Is this an empty background process?
-    boolean cached;             // Is this a cached process?
+    private volatile boolean mCached;    // Is this a cached process?
     String adjType;             // Debugging: primary thing impacting oom_adj.
     int adjTypeCode;            // Debugging: adj code to report to app.
     Object adjSource;           // Debugging: option dependent object.
@@ -409,7 +412,7 @@
                 pw.println();
         pw.print(prefix); pw.print("procStateMemTracker: ");
         procStateMemTracker.dumpLine(pw);
-        pw.print(prefix); pw.print("cached="); pw.print(cached);
+        pw.print(prefix); pw.print("cached="); pw.print(mCached);
                 pw.print(" empty="); pw.println(empty);
         if (serviceb) {
             pw.print(prefix); pw.print("serviceb="); pw.print(serviceb);
@@ -602,6 +605,13 @@
             int _uid) {
         mService = _service;
         info = _info;
+        if (_service.mPackageManagerInt != null) {
+            ArrayMap<String, ProcessInfo> processes =
+                    _service.mPackageManagerInt.getProcessesForUid(_uid);
+            processInfo = processes != null ? processes.get(_processName) : null;
+        } else {
+            processInfo = null;
+        }
         isolated = _info.uid != _uid;
         appZygote = (UserHandle.getAppId(_uid) >= Process.FIRST_APP_ZYGOTE_ISOLATED_UID
                 && UserHandle.getAppId(_uid) <= Process.LAST_APP_ZYGOTE_ISOLATED_UID);
@@ -688,6 +698,18 @@
         }
     }
 
+    void setCached(boolean cached) {
+        if (mCached != cached) {
+            mCached = cached;
+            mWindowProcessController.onProcCachedStateChanged(cached);
+        }
+    }
+
+    @Override
+    public boolean isCached() {
+        return mCached;
+    }
+
     boolean hasActivities() {
         return mWindowProcessController.hasActivities();
     }
@@ -776,7 +798,8 @@
                 } catch (RemoteException e) {
                     // If it's already dead our work is done. If it's wedged just kill it.
                     // We won't get the crash dialog or the error reporting.
-                    kill("scheduleCrash for '" + message + "' failed", true);
+                    kill("scheduleCrash for '" + message + "' failed",
+                            ApplicationExitInfo.REASON_CRASH, true);
                 } finally {
                     Binder.restoreCallingIdentity(ident);
                 }
@@ -784,7 +807,11 @@
         }
     }
 
-    void kill(String reason, boolean noisy) {
+    void kill(String reason, @Reason int reasonCode, boolean noisy) {
+        kill(reason, reasonCode, ApplicationExitInfo.SUBREASON_UNKNOWN, noisy);
+    }
+
+    void kill(String reason, @Reason int reasonCode, @SubReason int subReason, boolean noisy) {
         if (!killedByAm) {
             Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "kill");
             if (mService != null && (noisy || info.uid == mService.mCurOomAdjUid)) {
@@ -793,6 +820,7 @@
                         info.uid);
             }
             if (pid > 0) {
+                mService.mProcessList.noteAppKill(this, reasonCode, subReason, reason);
                 EventLog.writeEvent(EventLogTags.AM_KILL, userId, pid, processName, setAdj, reason);
                 Process.killProcessQuiet(pid);
                 ProcessList.killProcessGroup(uid, pid);
@@ -1374,9 +1402,9 @@
     }
 
     @Override
-    public void appDied() {
+    public void appDied(String reason) {
         synchronized (mService) {
-            mService.appDiedLocked(this);
+            mService.appDiedLocked(this, reason);
         }
     }
 
@@ -1441,7 +1469,8 @@
         ArrayList<Integer> firstPids = new ArrayList<>(5);
         SparseArray<Boolean> lastPids = new SparseArray<>(20);
 
-        mWindowProcessController.appEarlyNotResponding(annotation, () -> kill("anr", true));
+        mWindowProcessController.appEarlyNotResponding(annotation, () -> kill("anr",
+                  ApplicationExitInfo.REASON_ANR, true));
 
         long anrTime = SystemClock.uptimeMillis();
         if (isMonitorCpuUsage()) {
@@ -1592,7 +1621,8 @@
         mService.addErrorToDropBox("anr", this, processName, activityShortComponentName,
                 parentShortComponentName, parentPr, annotation, cpuInfo, tracesFile, null);
 
-        if (mWindowProcessController.appNotResponding(info.toString(), () -> kill("anr", true),
+        if (mWindowProcessController.appNotResponding(info.toString(), () -> kill("anr",
+                ApplicationExitInfo.REASON_ANR, true),
                 () -> {
                     synchronized (mService) {
                         mService.mServices.scheduleServiceTimeoutLocked(this);
@@ -1609,7 +1639,7 @@
             }
 
             if (isSilentAnr() && !isDebugging()) {
-                kill("bg anr", true);
+                kill("bg anr", ApplicationExitInfo.REASON_ANR, true);
                 return;
             }
 
@@ -1793,9 +1823,6 @@
         /** current wait for debugger dialog */
         private AppWaitingForDebuggerDialog mWaitDialog;
 
-        private final WindowManagerInternal mWmInternal =
-                LocalServices.getService(WindowManagerInternal.class);
-
         boolean hasCrashDialogs() {
             return mCrashDialogs != null;
         }
@@ -1921,7 +1948,9 @@
             }
             // If there is no foreground window display, fallback to last used display.
             if (displayContexts.isEmpty() || lastUsedOnly) {
-                displayContexts.add(mWmInternal.getTopFocusedDisplayUiContext());
+                displayContexts.add(mService.mWmInternal != null
+                        ? mService.mWmInternal.getTopFocusedDisplayUiContext()
+                        : mService.mUiContext);
             }
             return displayContexts;
         }
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 4a6e63f..0a8e70c 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -87,6 +87,7 @@
         DeviceConfig.NAMESPACE_RUNTIME_NATIVE,
         DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT,
         DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+        DeviceConfig.NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT,
     };
 
     private final String[] mGlobalSettings;
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index eb1ab38..afc3d91 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -16,12 +16,14 @@
 
 package com.android.server.am;
 
+import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.app.ActivityManager.USER_OP_ERROR_IS_SYSTEM;
 import static android.app.ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
 import static android.app.ActivityManager.USER_OP_IS_CURRENT;
 import static android.app.ActivityManager.USER_OP_SUCCESS;
+import static android.app.ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE;
 import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
 import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
 import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
@@ -52,6 +54,7 @@
 import android.content.Context;
 import android.content.IIntentReceiver;
 import android.content.Intent;
+import android.content.PermissionChecker;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
@@ -91,7 +94,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.Preconditions;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
@@ -159,7 +161,8 @@
      * <p>Note: Current and system user (and their related profiles) are never stopped when
      * switching users. Due to that, the actual number of running users can exceed mMaxRunningUsers
      */
-    int mMaxRunningUsers;
+    @GuardedBy("mLock")
+    private int mMaxRunningUsers;
 
     // Lock for internal state.
     private final Object mLock = new Object();
@@ -211,7 +214,8 @@
     private final RemoteCallbackList<IUserSwitchObserver> mUserSwitchObservers
             = new RemoteCallbackList<>();
 
-    boolean mUserSwitchUiEnabled = true;
+    @GuardedBy("mLock")
+    private boolean mUserSwitchUiEnabled = true;
 
     /**
      * Currently active user switch callbacks.
@@ -244,10 +248,11 @@
     /**
      * In this mode, user is always stopped when switched out but locking of user data is
      * postponed until total number of unlocked users in the system reaches mMaxRunningUsers.
-     * Once total number of unlocked users reach mMaxRunningUsers, least recentely used user
+     * Once total number of unlocked users reach mMaxRunningUsers, least recently used user
      * will be locked.
      */
-    boolean mDelayUserDataLocking;
+    @GuardedBy("mLock")
+    private boolean mDelayUserDataLocking;
     /**
      * Keep track of last active users for mDelayUserDataLocking.
      * The latest stopped user is placed in front while the least recently stopped user in back.
@@ -273,6 +278,33 @@
         updateStartedUserArrayLU();
     }
 
+    void setInitialConfig(boolean userSwitchUiEnabled, int maxRunningUsers,
+            boolean delayUserDataLocking) {
+        synchronized (mLock) {
+            mUserSwitchUiEnabled = userSwitchUiEnabled;
+            mMaxRunningUsers = maxRunningUsers;
+            mDelayUserDataLocking = delayUserDataLocking;
+        }
+    }
+
+    private boolean isUserSwitchUiEnabled() {
+        synchronized (mLock) {
+            return mUserSwitchUiEnabled;
+        }
+    }
+
+    int getMaxRunningUsers() {
+        synchronized (mLock) {
+            return mMaxRunningUsers;
+        }
+    }
+
+    private boolean isDelayUserDataLockingEnabled() {
+        synchronized (mLock) {
+            return mDelayUserDataLocking;
+        }
+    }
+
     void finishUserSwitch(UserState uss) {
         // This call holds the AM lock so we post to the handler.
         mHandler.post(() -> {
@@ -319,7 +351,11 @@
                 // Owner/System user and current user can't be stopped
                 continue;
             }
-            if (stopUsersLU(userId, false, null, null) == USER_OP_SUCCESS) {
+            // allowDelayedLocking set here as stopping user is done without any explicit request
+            // from outside.
+            if (stopUsersLU(userId, /* force= */ false, /* allowDelayedLocking= */ true,
+                    /* stopUserCallback= */ null, /* keyEvictedCallback= */ null)
+                    == USER_OP_SUCCESS) {
                 iterator.remove();
             }
         }
@@ -565,8 +601,8 @@
             // intialize it; it should be stopped right away as it's not really a "real" user.
             // TODO(b/143092698): in the long-term, it might be better to add a onCreateUser()
             // callback on SystemService instead.
-            stopUser(userInfo.id, /* force= */ true, /* stopUserCallback= */ null,
-                    /* keyEvictedCallback= */ null);
+            stopUser(userInfo.id, /* force= */ true, /* allowDelayedLocking= */ false,
+                    /* stopUserCallback= */ null, /* keyEvictedCallback= */ null);
             return;
         }
 
@@ -609,7 +645,8 @@
     }
 
     int restartUser(final int userId, final boolean foreground) {
-        return stopUser(userId, /* force */ true, null, new KeyEvictedCallback() {
+        return stopUser(userId, /* force= */ true, /* allowDelayedLocking= */ false,
+                /* stopUserCallback= */ null, new KeyEvictedCallback() {
             @Override
             public void keyEvicted(@UserIdInt int userId) {
                 // Post to the same handler that this callback is called from to ensure the user
@@ -619,15 +656,16 @@
         });
     }
 
-    int stopUser(final int userId, final boolean force, final IStopUserCallback stopUserCallback,
-            KeyEvictedCallback keyEvictedCallback) {
+    int stopUser(final int userId, final boolean force, boolean allowDelayedLocking,
+            final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
         checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "stopUser");
         if (userId < 0 || userId == UserHandle.USER_SYSTEM) {
             throw new IllegalArgumentException("Can't stop system user " + userId);
         }
         enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
         synchronized (mLock) {
-            return stopUsersLU(userId, force, stopUserCallback, keyEvictedCallback);
+            return stopUsersLU(userId, force, allowDelayedLocking, stopUserCallback,
+                    keyEvictedCallback);
         }
     }
 
@@ -636,7 +674,7 @@
      * {@link #getUsersToStopLU(int)} to determine the list of users that should be stopped.
      */
     @GuardedBy("mLock")
-    private int stopUsersLU(final int userId, boolean force,
+    private int stopUsersLU(final int userId, boolean force, boolean allowDelayedLocking,
             final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
         if (userId == UserHandle.USER_SYSTEM) {
             return USER_OP_ERROR_IS_SYSTEM;
@@ -655,7 +693,8 @@
                 if (force) {
                     Slog.i(TAG,
                             "Force stop user " + userId + ". Related users will not be stopped");
-                    stopSingleUserLU(userId, stopUserCallback, keyEvictedCallback);
+                    stopSingleUserLU(userId, allowDelayedLocking, stopUserCallback,
+                            keyEvictedCallback);
                     return USER_OP_SUCCESS;
                 }
                 return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
@@ -663,21 +702,64 @@
         }
         if (DEBUG_MU) Slog.i(TAG, "stopUsersLocked usersToStop=" + Arrays.toString(usersToStop));
         for (int userIdToStop : usersToStop) {
-            stopSingleUserLU(userIdToStop,
+            stopSingleUserLU(userIdToStop, allowDelayedLocking,
                     userIdToStop == userId ? stopUserCallback : null,
                     userIdToStop == userId ? keyEvictedCallback : null);
         }
         return USER_OP_SUCCESS;
     }
 
+    /**
+     * Stops a single User. This can also trigger locking user data out depending on device's
+     * config ({@code mDelayUserDataLocking}) and arguments.
+     * User will be unlocked when
+     * - {@code mDelayUserDataLocking} is not set.
+     * - {@code mDelayUserDataLocking} is set and {@code keyEvictedCallback} is non-null.
+     * -
+     *
+     * @param userId User Id to stop and lock the data.
+     * @param allowDelayedLocking When set, do not lock user after stopping. Locking can happen
+     *                            later when number of unlocked users reaches
+     *                            {@code mMaxRunnngUsers}. Note that this is respected only when
+     *                            {@code mDelayUserDataLocking} is set and {@keyEvictedCallback} is
+     *                            null. Otherwise the user will be locked.
+     * @param stopUserCallback Callback to notify that user has stopped.
+     * @param keyEvictedCallback Callback to notify that user has been unlocked.
+     */
     @GuardedBy("mLock")
-    private void stopSingleUserLU(final int userId, final IStopUserCallback stopUserCallback,
+    private void stopSingleUserLU(final int userId, boolean allowDelayedLocking,
+            final IStopUserCallback stopUserCallback,
             KeyEvictedCallback keyEvictedCallback) {
         if (DEBUG_MU) Slog.i(TAG, "stopSingleUserLocked userId=" + userId);
         final UserState uss = mStartedUsers.get(userId);
-        if (uss == null) {
-            // User is not started, nothing to do...  but we do need to
-            // callback if requested.
+        if (uss == null) {  // User is not started
+            // If mDelayUserDataLocking is set and allowDelayedLocking is not set, we need to lock
+            // the requested user as the client wants to stop and lock the user. On the other hand,
+            // having keyEvictedCallback set will lead into locking user if mDelayUserDataLocking
+            // is set as that means client wants to lock the user immediately.
+            // If mDelayUserDataLocking is not set, the user was already locked when it was stopped
+            // and no further action is necessary.
+            if (mDelayUserDataLocking) {
+                if (allowDelayedLocking && keyEvictedCallback != null) {
+                    Slog.wtf(TAG, "allowDelayedLocking set with KeyEvictedCallback, ignore it"
+                            + " and lock user:" + userId, new RuntimeException());
+                    allowDelayedLocking = false;
+                }
+                if (!allowDelayedLocking) {
+                    if (mLastActiveUsers.remove(Integer.valueOf(userId))) {
+                        // should lock the user, user is already gone
+                        final ArrayList<KeyEvictedCallback> keyEvictedCallbacks;
+                        if (keyEvictedCallback != null) {
+                            keyEvictedCallbacks = new ArrayList<>(1);
+                            keyEvictedCallbacks.add(keyEvictedCallback);
+                        } else {
+                            keyEvictedCallbacks = null;
+                        }
+                        dispatchUserLocking(userId, keyEvictedCallbacks);
+                    }
+                }
+            }
+            // We do need to post the stopped callback even though user is already stopped.
             if (stopUserCallback != null) {
                 mHandler.post(() -> {
                     try {
@@ -702,6 +784,7 @@
             mInjector.getUserManagerInternal().setUserState(userId, uss.state);
             updateStartedUserArrayLU();
 
+            final boolean allowDelayyLockingCopied = allowDelayedLocking;
             // Post to handler to obtain amLock
             mHandler.post(() -> {
                 // We are going to broadcast ACTION_USER_STOPPING and then
@@ -716,7 +799,8 @@
                     @Override
                     public void performReceive(Intent intent, int resultCode, String data,
                             Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
-                        mHandler.post(() -> finishUserStopping(userId, uss));
+                        mHandler.post(() -> finishUserStopping(userId, uss,
+                                allowDelayyLockingCopied));
                     }
                 };
 
@@ -732,7 +816,8 @@
         }
     }
 
-    void finishUserStopping(final int userId, final UserState uss) {
+    void finishUserStopping(final int userId, final UserState uss,
+            final boolean allowDelayedLocking) {
         Slog.d(TAG, "UserController event: finishUserStopping(" + userId + ")");
         // On to the next.
         final Intent shutdownIntent = new Intent(Intent.ACTION_SHUTDOWN);
@@ -744,7 +829,7 @@
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
-                        finishUserStopped(uss);
+                        finishUserStopped(uss, allowDelayedLocking);
                     }
                 });
             }
@@ -771,7 +856,7 @@
                 Binder.getCallingPid(), userId);
     }
 
-    void finishUserStopped(UserState uss) {
+    void finishUserStopped(UserState uss, boolean allowDelayedLocking) {
         final int userId = uss.mHandle.getIdentifier();
         Slog.d(TAG, "UserController event: finishUserStopped(" + userId + ")");
         final boolean stopped;
@@ -790,7 +875,13 @@
                 mStartedUsers.remove(userId);
                 mUserLru.remove(Integer.valueOf(userId));
                 updateStartedUserArrayLU();
-                userIdToLock = updateUserToLockLU(userId);
+                if (allowDelayedLocking && !keyEvictedCallbacks.isEmpty()) {
+                    Slog.wtf(TAG,
+                            "Delayed locking enabled while KeyEvictedCallbacks not empty, userId:"
+                                    + userId + " callbacks:" + keyEvictedCallbacks);
+                    allowDelayedLocking = false;
+                }
+                userIdToLock = updateUserToLockLU(userId, allowDelayedLocking);
                 if (userIdToLock == UserHandle.USER_NULL) {
                     lockUser = false;
                 }
@@ -824,31 +915,36 @@
             if (!lockUser) {
                 return;
             }
-            final int userIdToLockF = userIdToLock;
-            // Evict the user's credential encryption key. Performed on FgThread to make it
-            // serialized with call to UserManagerService.onBeforeUnlockUser in finishUserUnlocking
-            // to prevent data corruption.
-            FgThread.getHandler().post(() -> {
-                synchronized (mLock) {
-                    if (mStartedUsers.get(userIdToLockF) != null) {
-                        Slog.w(TAG, "User was restarted, skipping key eviction");
-                        return;
-                    }
-                }
-                try {
-                    mInjector.getStorageManager().lockUserKey(userIdToLockF);
-                } catch (RemoteException re) {
-                    throw re.rethrowAsRuntimeException();
-                }
-                if (userIdToLockF == userId) {
-                    for (final KeyEvictedCallback callback : keyEvictedCallbacks) {
-                        callback.keyEvicted(userId);
-                    }
-                }
-            });
+            dispatchUserLocking(userIdToLock, keyEvictedCallbacks);
         }
     }
 
+    private void dispatchUserLocking(@UserIdInt int userId,
+            @Nullable List<KeyEvictedCallback> keyEvictedCallbacks) {
+        // Evict the user's credential encryption key. Performed on FgThread to make it
+        // serialized with call to UserManagerService.onBeforeUnlockUser in finishUserUnlocking
+        // to prevent data corruption.
+        FgThread.getHandler().post(() -> {
+            synchronized (mLock) {
+                if (mStartedUsers.get(userId) != null) {
+                    Slog.w(TAG, "User was restarted, skipping key eviction");
+                    return;
+                }
+            }
+            try {
+                mInjector.getStorageManager().lockUserKey(userId);
+            } catch (RemoteException re) {
+                throw re.rethrowAsRuntimeException();
+            }
+            if (keyEvictedCallbacks == null) {
+                return;
+            }
+            for (int i = 0; i < keyEvictedCallbacks.size(); i++) {
+                keyEvictedCallbacks.get(i).keyEvicted(userId);
+            }
+        });
+    }
+
     /**
      * For mDelayUserDataLocking mode, storage once unlocked is kept unlocked.
      * Total number of unlocked user storage is limited by mMaxRunningUsers.
@@ -859,9 +955,9 @@
      * @return user id to lock. UserHandler.USER_NULL will be returned if no user should be locked.
      */
     @GuardedBy("mLock")
-    private int updateUserToLockLU(@UserIdInt int userId) {
+    private int updateUserToLockLU(@UserIdInt int userId, boolean allowDelayedLocking) {
         int userIdToLock = userId;
-        if (mDelayUserDataLocking && !getUserInfo(userId).isEphemeral()
+        if (mDelayUserDataLocking && allowDelayedLocking && !getUserInfo(userId).isEphemeral()
                 && !hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, userId)) {
             mLastActiveUsers.remove((Integer) userId); // arg should be object, not index
             mLastActiveUsers.add(0, userId);
@@ -943,7 +1039,8 @@
         if (userInfo.isGuest() || userInfo.isEphemeral()) {
             // This is a user to be stopped.
             synchronized (mLock) {
-                stopUsersLU(oldUserId, true, null, null);
+                stopUsersLU(oldUserId, /* force= */ true, /* allowDelayedLocking= */ false,
+                        null, null);
             }
         }
     }
@@ -975,7 +1072,7 @@
         }
         final int profilesToStartSize = profilesToStart.size();
         int i = 0;
-        for (; i < profilesToStartSize && i < (mMaxRunningUsers - 1); ++i) {
+        for (; i < profilesToStartSize && i < (getMaxRunningUsers() - 1); ++i) {
             startUser(profilesToStart.get(i).id, /* foreground= */ false);
         }
         if (i < profilesToStartSize) {
@@ -1092,7 +1189,7 @@
                 return false;
             }
 
-            if (foreground && mUserSwitchUiEnabled) {
+            if (foreground && isUserSwitchUiEnabled()) {
                 t.traceBegin("startFreezingScreen");
                 mInjector.getWindowManager().startFreezingScreen(
                         R.anim.screen_user_exit, R.anim.screen_user_enter);
@@ -1140,9 +1237,11 @@
             if (foreground) {
                 // Make sure the old user is no longer considering the display to be on.
                 mInjector.reportGlobalUsageEventLocked(UsageEvents.Event.SCREEN_NON_INTERACTIVE);
+                boolean userSwitchUiEnabled;
                 synchronized (mLock) {
                     mCurrentUserId = userId;
                     mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up
+                    userSwitchUiEnabled = mUserSwitchUiEnabled;
                 }
                 mInjector.updateUserConfiguration();
                 updateCurrentProfileIds();
@@ -1150,7 +1249,7 @@
                 mInjector.reportCurWakefulnessUsageEvent();
                 // Once the internal notion of the active user has switched, we lock the device
                 // with the option to show the user switcher on the keyguard.
-                if (mUserSwitchUiEnabled) {
+                if (userSwitchUiEnabled) {
                     mInjector.getWindowManager().setSwitchingUser(true);
                     mInjector.getWindowManager().lockNow(null);
                 }
@@ -1389,10 +1488,12 @@
             Slog.w(TAG, "Cannot switch to User #" + targetUserId + ": not a full user");
             return false;
         }
+        boolean userSwitchUiEnabled;
         synchronized (mLock) {
             mTargetUserId = targetUserId;
+            userSwitchUiEnabled = mUserSwitchUiEnabled;
         }
-        if (mUserSwitchUiEnabled) {
+        if (userSwitchUiEnabled) {
             UserInfo currentUserInfo = getUserInfo(currentUserId);
             Pair<UserInfo, UserInfo> userNames = new Pair<>(currentUserInfo, targetUserInfo);
             mUiHandler.removeMessages(START_USER_SWITCH_UI_MSG);
@@ -1456,14 +1557,15 @@
         }
         // If running in background is disabled or mDelayUserDataLocking mode, stop the user.
         boolean disallowRunInBg = hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND,
-                oldUserId) || mDelayUserDataLocking;
+                oldUserId) || isDelayUserDataLockingEnabled();
         if (!disallowRunInBg) {
             return;
         }
         synchronized (mLock) {
             if (DEBUG_MU) Slog.i(TAG, "stopBackgroundUsersIfEnforced stopping " + oldUserId
                     + " and related users");
-            stopUsersLU(oldUserId, false, null, null);
+            stopUsersLU(oldUserId, /* force= */ false, /* allowDelayedLocking= */ true,
+                    null, null);
         }
     }
 
@@ -1549,7 +1651,7 @@
 
     void continueUserSwitch(UserState uss, int oldUserId, int newUserId) {
         Slog.d(TAG, "Continue user switch oldUser #" + oldUserId + ", newUser #" + newUserId);
-        if (mUserSwitchUiEnabled) {
+        if (isUserSwitchUiEnabled()) {
             mInjector.getWindowManager().stopFreezingScreen();
         }
         uss.switching = false;
@@ -1641,11 +1743,11 @@
 
         if (callingUid != 0 && callingUid != SYSTEM_UID) {
             final boolean allow;
+            final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, targetUserId);
             if (mInjector.isCallerRecents(callingUid)
-                    && callingUserId == getCurrentUserId()
                     && isSameProfileGroup(callingUserId, targetUserId)) {
-                // If the caller is Recents and it is running in the current user, we then allow it
-                // to access its profiles.
+                // If the caller is Recents and the caller has ownership of the profile group,
+                // we then allow it to access its profiles.
                 allow = true;
             } else if (mInjector.checkComponentPermission(INTERACT_ACROSS_USERS_FULL, callingPid,
                     callingUid, -1, true) == PackageManager.PERMISSION_GRANTED) {
@@ -1654,6 +1756,9 @@
             } else if (allowMode == ALLOW_FULL_ONLY) {
                 // We require full access, sucks to be you.
                 allow = false;
+            } else if (canInteractWithAcrossProfilesPermission(
+                    allowMode, isSameProfileGroup, callingPid, callingUid, callerPackage)) {
+                allow = true;
             } else if (mInjector.checkComponentPermission(INTERACT_ACROSS_USERS, callingPid,
                     callingUid, -1, true) != PackageManager.PERMISSION_GRANTED) {
                 // If the caller does not have either permission, they are always doomed.
@@ -1661,10 +1766,11 @@
             } else if (allowMode == ALLOW_NON_FULL) {
                 // We are blanket allowing non-full access, you lucky caller!
                 allow = true;
-            } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE) {
+            } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE
+                        || allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) {
                 // We may or may not allow this depending on whether the two users are
                 // in the same profile.
-                allow = isSameProfileGroup(callingUserId, targetUserId);
+                allow = isSameProfileGroup;
             } else {
                 throw new IllegalArgumentException("Unknown mode: " + allowMode);
             }
@@ -1690,6 +1796,11 @@
                     if (allowMode != ALLOW_FULL_ONLY) {
                         builder.append(" or ");
                         builder.append(INTERACT_ACROSS_USERS);
+                        if (isSameProfileGroup
+                                && allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) {
+                            builder.append(" or ");
+                            builder.append(INTERACT_ACROSS_PROFILES);
+                        }
                     }
                     String msg = builder.toString();
                     Slog.w(TAG, msg);
@@ -1710,6 +1821,24 @@
         return targetUserId;
     }
 
+    private boolean canInteractWithAcrossProfilesPermission(
+            int allowMode, boolean isSameProfileGroup, int callingPid, int callingUid,
+            String callingPackage) {
+        if (allowMode != ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) {
+            return false;
+        }
+        if (!isSameProfileGroup) {
+            return false;
+        }
+        return  PermissionChecker.PERMISSION_GRANTED
+                == PermissionChecker.checkPermissionForPreflight(
+                        mInjector.getContext(),
+                        INTERACT_ACROSS_PROFILES,
+                        callingPid,
+                        callingUid,
+                        callingPackage);
+    }
+
     int unsafeConvertIncomingUser(@UserIdInt int userId) {
         return (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF)
                 ? getCurrentUserId(): userId;
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index a7593c7..08136a3 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -16,7 +16,9 @@
 
 package com.android.server.appop;
 
+import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA;
 import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
 import static android.app.AppOpsManager.FILTER_BY_FEATURE_ID;
 import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
 import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
@@ -467,6 +469,12 @@
                         case AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION:
                             return ((capability & PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0)
                                 ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED;
+                        case OP_CAMERA:
+                            return ((capability & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0)
+                                    ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED;
+                        case OP_RECORD_AUDIO:
+                            return ((capability & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0)
+                                    ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED;
                         default:
                             return AppOpsManager.MODE_ALLOWED;
                     }
@@ -1759,8 +1767,9 @@
                 ? opNames.toArray(new String[opNames.size()]) : null;
 
         // Must not hold the appops lock
-        mHistoricalRegistry.getHistoricalOps(uid, packageName, featureId, opNamesArray, filter,
-                beginTimeMillis, endTimeMillis, flags, callback);
+        mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOps,
+                mHistoricalRegistry, uid, packageName, featureId, opNamesArray, filter,
+                beginTimeMillis, endTimeMillis, flags, callback).recycleOnUse());
     }
 
     @Override
@@ -1778,8 +1787,9 @@
                 ? opNames.toArray(new String[opNames.size()]) : null;
 
         // Must not hold the appops lock
-        mHistoricalRegistry.getHistoricalOpsFromDiskRaw(uid, packageName, featureId, opNamesArray,
-                filter, beginTimeMillis, endTimeMillis, flags, callback);
+        mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOpsFromDiskRaw,
+                mHistoricalRegistry, uid, packageName, featureId, opNamesArray,
+                filter, beginTimeMillis, endTimeMillis, flags, callback).recycleOnUse());
     }
 
     @Override
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index bc4dc8f..8f6bd21 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -21,6 +21,7 @@
 import static android.media.AudioManager.RINGER_MODE_SILENT;
 import static android.media.AudioManager.RINGER_MODE_VIBRATE;
 import static android.media.AudioManager.STREAM_SYSTEM;
+import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE;
 import static android.os.Process.FIRST_APPLICATION_UID;
 import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE;
 import static android.provider.Settings.Secure.VOLUME_HUSH_OFF;
@@ -63,6 +64,7 @@
 import android.hardware.usb.UsbManager;
 import android.hidl.manager.V1_0.IServiceManager;
 import android.media.AudioAttributes;
+import android.media.AudioAttributes.AttributeSystemUsage;
 import android.media.AudioDeviceAddress;
 import android.media.AudioDeviceInfo;
 import android.media.AudioFocusInfo;
@@ -88,6 +90,7 @@
 import android.media.VolumePolicy;
 import android.media.audiofx.AudioEffect;
 import android.media.audiopolicy.AudioMix;
+import android.media.audiopolicy.AudioMixingRule.AudioMixMatchCriterion;
 import android.media.audiopolicy.AudioPolicy;
 import android.media.audiopolicy.AudioPolicyConfig;
 import android.media.audiopolicy.AudioProductStrategy;
@@ -570,6 +573,11 @@
     @GuardedBy("mSettingsLock")
     private int mAssistantUid;
 
+    private final Object mSupportedSystemUsagesLock = new Object();
+    @GuardedBy("mSupportedSystemUsagesLock")
+    private @AttributeSystemUsage int[] mSupportedSystemUsages =
+            new int[]{AudioAttributes.USAGE_CALL_ASSISTANT};
+
     // Defines the format for the connection "address" for ALSA devices
     public static String makeAlsaAddressString(int card, int device) {
         return "card=" + card + ";device=" + device + ";";
@@ -1043,6 +1051,10 @@
             }
         }
 
+        synchronized (mSupportedSystemUsagesLock) {
+            AudioSystem.setSupportedSystemUsages(mSupportedSystemUsages);
+        }
+
         synchronized (mAudioPolicies) {
             for (AudioPolicyProxy policy : mAudioPolicies.values()) {
                 final int status = policy.connectMixes();
@@ -1099,6 +1111,37 @@
     }
 
     /**
+     * @see AudioManager#setSupportedSystemUsages(int[])
+     */
+    public void setSupportedSystemUsages(@NonNull @AttributeSystemUsage int[] systemUsages) {
+        enforceModifyAudioRoutingPermission();
+        verifySystemUsages(systemUsages);
+
+        synchronized (mSupportedSystemUsagesLock) {
+            AudioSystem.setSupportedSystemUsages(systemUsages);
+            mSupportedSystemUsages = systemUsages;
+        }
+    }
+
+    /**
+     * @see AudioManager#getSupportedSystemUsages()
+     */
+    public @NonNull @AttributeSystemUsage int[] getSupportedSystemUsages() {
+        enforceModifyAudioRoutingPermission();
+        synchronized (mSupportedSystemUsagesLock) {
+            return Arrays.copyOf(mSupportedSystemUsages, mSupportedSystemUsages.length);
+        }
+    }
+
+    private void verifySystemUsages(@NonNull int[] systemUsages) {
+        for (int i = 0; i < systemUsages.length; i++) {
+            if (!AudioAttributes.isSystemUsage(systemUsages[i])) {
+                throw new IllegalArgumentException("Non-system usage provided: " + systemUsages[i]);
+            }
+        }
+    }
+
+    /**
      * @return the {@link android.media.audiopolicy.AudioProductStrategy} discovered from the
      * platform configuration file.
      */
@@ -1700,6 +1743,14 @@
         }
     }
 
+    /** @see AudioManager#getDevicesForAttributes(AudioAttributes) */
+    public @NonNull ArrayList<AudioDeviceAddress> getDevicesForAttributes(
+            @NonNull AudioAttributes attributes) {
+        Objects.requireNonNull(attributes);
+        enforceModifyAudioRoutingPermission();
+        return AudioSystem.getDevicesForAttributes(attributes);
+    }
+
 
     /** @see AudioManager#adjustVolume(int, int) */
     public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
@@ -2185,8 +2236,8 @@
     }
 
     private void enforceModifyAudioRoutingPermission() {
-        if (mContext.checkCallingPermission(
-                    android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.MODIFY_AUDIO_ROUTING)
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Missing MODIFY_AUDIO_ROUTING permission");
         }
@@ -5712,10 +5763,48 @@
         return false;
     }
 
+    private boolean isSupportedSystemUsage(@AudioAttributes.AttributeUsage int usage) {
+        synchronized (mSupportedSystemUsagesLock) {
+            for (int i = 0; i < mSupportedSystemUsages.length; i++) {
+                if (mSupportedSystemUsages[i] == usage) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    private void validateAudioAttributesUsage(@NonNull AudioAttributes audioAttributes) {
+        @AudioAttributes.AttributeUsage int usage = audioAttributes.getSystemUsage();
+        if (AudioAttributes.isSystemUsage(usage)) {
+            if (callerHasPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)) {
+                if (!isSupportedSystemUsage(usage)) {
+                    throw new IllegalArgumentException(
+                            "Unsupported usage " + AudioAttributes.usageToString(usage));
+                }
+            } else {
+                throw new SecurityException("Missing MODIFY_AUDIO_ROUTING permission");
+            }
+        }
+    }
+
+    private boolean isValidAudioAttributesUsage(@NonNull AudioAttributes audioAttributes) {
+        @AudioAttributes.AttributeUsage int usage = audioAttributes.getSystemUsage();
+        if (AudioAttributes.isSystemUsage(usage)) {
+            return callerHasPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)
+                    && isSupportedSystemUsage(usage);
+        }
+        return true;
+    }
+
     public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb,
             IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
             IAudioPolicyCallback pcb, int sdk) {
         // permission checks
+        if (aa != null && !isValidAudioAttributesUsage(aa)) {
+            Log.w(TAG, "Request using unsupported usage.");
+            return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
+        }
         if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) == AudioManager.AUDIOFOCUS_FLAG_LOCK) {
             if (AudioSystem.IN_VOICE_COMM_FOCUS_ID.equals(clientId)) {
                 if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(
@@ -5746,6 +5835,10 @@
 
     public int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, AudioAttributes aa,
             String callingPackageName) {
+        if (aa != null && !isValidAudioAttributesUsage(aa)) {
+            Log.w(TAG, "Request using unsupported usage.");
+            return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
+        }
         return mMediaFocusControl.abandonAudioFocus(fd, clientId, aa, callingPackageName);
     }
 
@@ -6305,6 +6398,17 @@
         sForceUseLogger.dump(pw);
         pw.println("\n");
         sVolumeLogger.dump(pw);
+        pw.println("\n");
+        dumpSupportedSystemUsage(pw);
+    }
+
+    private void dumpSupportedSystemUsage(PrintWriter pw) {
+        pw.println("Supported System Usages:");
+        synchronized (mSupportedSystemUsagesLock) {
+            for (int i = 0; i < mSupportedSystemUsages.length; i++) {
+                pw.printf("\t%s\n", AudioAttributes.usageToString(mSupportedSystemUsages[i]));
+            }
+        }
     }
 
     private static String safeMediaVolumeStateToString(int state) {
@@ -6419,7 +6523,7 @@
                 return false;
             }
             boolean suppress = false;
-            if (resolvedStream == DEFAULT_VOL_STREAM_NO_PLAYBACK && mController != null) {
+            if (resolvedStream != AudioSystem.STREAM_MUSIC && mController != null) {
                 final long now = SystemClock.uptimeMillis();
                 if ((flags & AudioManager.FLAG_SHOW_UI) != 0 && !mVisible) {
                     // ui will become visible
@@ -6672,6 +6776,7 @@
 
         boolean requireValidProjection = false;
         boolean requireCaptureAudioOrMediaOutputPerm = false;
+        boolean requireVoiceComunicationOutputPerm = false;
         boolean requireModifyRouting = false;
 
         if (hasFocusAccess || isVolumeController) {
@@ -6681,8 +6786,15 @@
             requireModifyRouting |= true;
         }
         for (AudioMix mix : policyConfig.getMixes()) {
-            // If mix is requesting a privileged capture
-            if (mix.getRule().allowPrivilegedPlaybackCapture()) {
+            // If mix is trying to capture USAGE_VOICE_COMMUNICATION using playback capture
+            if (isVoiceCommunicationPlaybackCaptureMix(mix)) {
+                // then it must have CAPTURE_USAGE_VOICE_COMMUNICATION_OUTPUT permission
+                requireVoiceComunicationOutputPerm |= true;
+            }
+            // If mix is requesting privileged capture and is capturing at
+            // least one usage which is not USAGE_VOICE_COMMUNICATION.
+            if (mix.getRule().allowPrivilegedPlaybackCapture()
+                    && isNonVoiceCommunicationCaptureMix(mix)) {
                 // then it must have CAPTURE_MEDIA_OUTPUT or CAPTURE_AUDIO_OUTPUT permission
                 requireCaptureAudioOrMediaOutputPerm |= true;
                 // and its format must be low quality enough
@@ -6710,6 +6822,14 @@
             return false;
         }
 
+        if (requireVoiceComunicationOutputPerm
+                && !callerHasPermission(
+                        android.Manifest.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT)) {
+            Log.e(TAG, "Privileged audio capture for voice communication requires "
+                      + "CAPTURE_VOICE_COMMUNICATION_OUTPUT system permission");
+            return false;
+        }
+
         if (requireValidProjection && !canProjectAudio(projection)) {
             return false;
         }
@@ -6723,6 +6843,41 @@
         return true;
     }
 
+    /**
+    * Checks whether a given AudioMix is used for playback capture
+    * (has the ROUTE_FLAG_LOOP_BACK_RENDER flag) and has a matching
+    * criterion for USAGE_VOICE_COMMUNICATION.
+    */
+    private boolean isVoiceCommunicationPlaybackCaptureMix(AudioMix mix) {
+        if (mix.getRouteFlags() != mix.ROUTE_FLAG_LOOP_BACK_RENDER) {
+            return false;
+        }
+
+        for (AudioMixMatchCriterion criterion : mix.getRule().getCriteria()) {
+            if (criterion.getRule() == RULE_MATCH_ATTRIBUTE_USAGE
+                    && criterion.getAudioAttributes().getUsage()
+                    == AudioAttributes.USAGE_VOICE_COMMUNICATION) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+    * Checks whether a given AudioMix has a matching
+    * criterion for a usage which is not USAGE_VOICE_COMMUNICATION.
+    */
+    private boolean isNonVoiceCommunicationCaptureMix(AudioMix mix) {
+        for (AudioMixMatchCriterion criterion : mix.getRule().getCriteria()) {
+            if (criterion.getRule() == RULE_MATCH_ATTRIBUTE_USAGE
+                    && criterion.getAudioAttributes().getUsage()
+                    != AudioAttributes.USAGE_VOICE_COMMUNICATION) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private boolean callerHasPermission(String permission) {
         return mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED;
     }
@@ -6886,6 +7041,26 @@
         }
     }
 
+    /** see AudioPolicy.setUserIdDeviceAffinity() */
+    public int setUserIdDeviceAffinity(IAudioPolicyCallback pcb, int userId,
+            @NonNull int[] deviceTypes, @NonNull String[] deviceAddresses) {
+        if (DEBUG_AP) {
+            Log.d(TAG, "setUserIdDeviceAffinity for " + pcb.asBinder() + " user:" + userId);
+        }
+
+        synchronized (mAudioPolicies) {
+            final AudioPolicyProxy app =
+                    checkUpdateForPolicy(pcb, "Cannot change device affinity in audio policy");
+            if (app == null) {
+                return AudioManager.ERROR;
+            }
+            if (!app.hasMixRoutedToDevices(deviceTypes, deviceAddresses)) {
+                return AudioManager.ERROR;
+            }
+            return app.setUserIdDeviceAffinities(userId, deviceTypes, deviceAddresses);
+        }
+    }
+
     /** see AudioPolicy.removeUidDeviceAffinity() */
     public int removeUidDeviceAffinity(IAudioPolicyCallback pcb, int uid) {
         if (DEBUG_AP) {
@@ -6901,6 +7076,22 @@
         }
     }
 
+    /** see AudioPolicy.removeUserIdDeviceAffinity() */
+    public int removeUserIdDeviceAffinity(IAudioPolicyCallback pcb, int userId) {
+        if (DEBUG_AP) {
+            Log.d(TAG, "removeUserIdDeviceAffinity for " + pcb.asBinder()
+                    + " userId:" + userId);
+        }
+        synchronized (mAudioPolicies) {
+            final AudioPolicyProxy app =
+                    checkUpdateForPolicy(pcb, "Cannot remove device affinity in audio policy");
+            if (app == null) {
+                return AudioManager.ERROR;
+            }
+            return app.removeUserIdDeviceAffinities(userId);
+        }
+    }
+
     public int setFocusPropertiesForPolicy(int duckingBehavior, IAudioPolicyCallback pcb) {
         if (DEBUG_AP) Log.d(TAG, "setFocusPropertiesForPolicy() duck behavior=" + duckingBehavior
                 + " policy " +  pcb.asBinder());
@@ -7082,10 +7273,16 @@
     }
 
     public int trackPlayer(PlayerBase.PlayerIdCard pic) {
+        if (pic != null && pic.mAttributes != null) {
+            validateAudioAttributesUsage(pic.mAttributes);
+        }
         return mPlaybackMonitor.trackPlayer(pic);
     }
 
     public void playerAttributes(int piid, AudioAttributes attr) {
+        if (attr != null) {
+            validateAudioAttributesUsage(attr);
+        }
         mPlaybackMonitor.playerAttributes(piid, attr, Binder.getCallingUid());
     }
 
@@ -7130,6 +7327,9 @@
         final HashMap<Integer, AudioDeviceArray> mUidDeviceAffinities =
                 new HashMap<Integer, AudioDeviceArray>();
 
+        final HashMap<Integer, AudioDeviceArray> mUserIdDeviceAffinities =
+                new HashMap<>();
+
         final IMediaProjection mProjection;
         private final class UnregisterOnStopCallback extends IMediaProjectionCallback.Stub {
             public void onStop() {
@@ -7321,6 +7521,45 @@
             return AudioManager.ERROR;
         }
 
+        int setUserIdDeviceAffinities(int userId,
+                @NonNull int[] types, @NonNull String[] addresses) {
+            final Integer UserId = new Integer(userId);
+            int res;
+            if (mUserIdDeviceAffinities.remove(UserId) != null) {
+                final long identity = Binder.clearCallingIdentity();
+                res = AudioSystem.removeUserIdDeviceAffinities(UserId);
+                Binder.restoreCallingIdentity(identity);
+                if (res != AudioSystem.SUCCESS) {
+                    Log.e(TAG, "AudioSystem. removeUserIdDeviceAffinities("
+                            + UserId + ") failed, "
+                            + " cannot call AudioSystem.setUserIdDeviceAffinities");
+                    return AudioManager.ERROR;
+                }
+            }
+            final long identity = Binder.clearCallingIdentity();
+            res = AudioSystem.setUserIdDeviceAffinities(userId, types, addresses);
+            Binder.restoreCallingIdentity(identity);
+            if (res == AudioSystem.SUCCESS) {
+                mUserIdDeviceAffinities.put(UserId, new AudioDeviceArray(types, addresses));
+                return AudioManager.SUCCESS;
+            }
+            Log.e(TAG, "AudioSystem.setUserIdDeviceAffinities(" + userId + ") failed");
+            return AudioManager.ERROR;
+        }
+
+        int removeUserIdDeviceAffinities(int userId) {
+            if (mUserIdDeviceAffinities.remove(new Integer(userId)) != null) {
+                final long identity = Binder.clearCallingIdentity();
+                final int res = AudioSystem.removeUserIdDeviceAffinities(userId);
+                Binder.restoreCallingIdentity(identity);
+                if (res == AudioSystem.SUCCESS) {
+                    return AudioManager.SUCCESS;
+                }
+            }
+            Log.e(TAG, "AudioSystem.removeUserIdDeviceAffinities failed");
+            return AudioManager.ERROR;
+        }
+
         /** @return human readable debug informations summarizing the state of the object. */
         public String toLogFriendlyString() {
             String textDump = super.toLogFriendlyString();
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index c845981..7bf2bc8 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -727,6 +727,7 @@
             case AudioAttributes.USAGE_ASSISTANT:
             case AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY:
             case AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
+            case AudioAttributes.USAGE_ANNOUNCEMENT:
                 return 700;
             case AudioAttributes.USAGE_VOICE_COMMUNICATION:
             case AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING:
@@ -736,7 +737,10 @@
             case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
             case AudioAttributes.USAGE_NOTIFICATION_EVENT:
             case AudioAttributes.USAGE_ASSISTANCE_SONIFICATION:
+            case AudioAttributes.USAGE_VEHICLE_STATUS:
                 return 500;
+            case AudioAttributes.USAGE_EMERGENCY:
+            case AudioAttributes.USAGE_SAFETY:
             case AudioAttributes.USAGE_UNKNOWN:
             default:
                 return 0;
diff --git a/services/core/java/com/android/server/backup/PeopleBackupHelper.java b/services/core/java/com/android/server/backup/PeopleBackupHelper.java
new file mode 100644
index 0000000..e58a051
--- /dev/null
+++ b/services/core/java/com/android/server/backup/PeopleBackupHelper.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.server.backup;
+
+import android.app.backup.BlobBackupHelper;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+import com.android.server.people.PeopleServiceInternal;
+
+class PeopleBackupHelper extends BlobBackupHelper {
+
+    private static final String TAG = PeopleBackupHelper.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    // Current schema of the backup state blob.
+    private static final int STATE_VERSION = 1;
+
+    // Key under which conversation infos state blob is committed to backup.
+    private static final String KEY_CONVERSATIONS = "people_conversation_infos";
+
+    private final int mUserId;
+
+    PeopleBackupHelper(int userId) {
+        super(STATE_VERSION, KEY_CONVERSATIONS);
+        mUserId = userId;
+    }
+
+    @Override
+    protected byte[] getBackupPayload(String key) {
+        if (!KEY_CONVERSATIONS.equals(key)) {
+            Slog.w(TAG, "Unexpected backup key " + key);
+            return new byte[0];
+        }
+        PeopleServiceInternal ps = LocalServices.getService(PeopleServiceInternal.class);
+        if (DEBUG) {
+            Slog.d(TAG, "Handling backup of " + key);
+        }
+        return ps.backupConversationInfos(mUserId);
+    }
+
+    @Override
+    protected void applyRestoredPayload(String key, byte[] payload) {
+        if (!KEY_CONVERSATIONS.equals(key)) {
+            Slog.w(TAG, "Unexpected restore key " + key);
+            return;
+        }
+        PeopleServiceInternal ps = LocalServices.getService(PeopleServiceInternal.class);
+        if (DEBUG) {
+            Slog.d(TAG, "Handling restore of " + key);
+        }
+        ps.restoreConversationInfos(mUserId, key, payload);
+    }
+}
diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java
index 35e8f56..1f4563b 100644
--- a/services/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -55,6 +55,7 @@
     private static final String SHORTCUT_MANAGER_HELPER = "shortcut_manager";
     private static final String ACCOUNT_MANAGER_HELPER = "account_manager";
     private static final String SLICES_HELPER = "slices";
+    private static final String PEOPLE_HELPER = "people";
 
     // These paths must match what the WallpaperManagerService uses.  The leaf *_FILENAME
     // are also used in the full-backup file format, so must not change unless steps are
@@ -99,6 +100,7 @@
         addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper());
         addHelper(ACCOUNT_MANAGER_HELPER, new AccountManagerBackupHelper());
         addHelper(SLICES_HELPER, new SliceBackupHelper(this));
+        addHelper(PEOPLE_HELPER, new PeopleBackupHelper(mUserId));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index e1a9f3b..8dd3242 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -1400,7 +1400,8 @@
                     if (mCurrentAuthSession.mTokenEscrow != null) {
                         mKeyStore.addAuthToken(mCurrentAuthSession.mTokenEscrow);
                     }
-                    mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded();
+                    mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded(
+                            Utils.getAuthenticationTypeForResult(reason));
                     break;
 
                 case BiometricPrompt.DISMISSED_REASON_NEGATIVE:
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 19f5358..389763b 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -22,6 +22,7 @@
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.BiometricPrompt.AuthenticationResultType;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.UserHandle;
@@ -210,4 +211,30 @@
         }
         return biometricManagerCode;
     }
+
+    /**
+     * Converts a {@link BiometricPrompt} dismissal reason to an authentication type at the level of
+     * granularity supported by {@link BiometricPrompt.AuthenticationResult}.
+     *
+     * @param reason The reason that the {@link BiometricPrompt} was dismissed. Must be one of:
+     *               {@link BiometricPrompt#DISMISSED_REASON_CREDENTIAL_CONFIRMED},
+     *               {@link BiometricPrompt#DISMISSED_REASON_BIOMETRIC_CONFIRMED}, or
+     *               {@link BiometricPrompt#DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED}
+     * @return An integer representing the authentication type for {@link
+     *         BiometricPrompt.AuthenticationResult}.
+     * @throws IllegalArgumentException if given an invalid dismissal reason.
+     */
+    public static @AuthenticationResultType int getAuthenticationTypeForResult(int reason) {
+        switch (reason) {
+            case BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED:
+                return BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL;
+
+            case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED:
+            case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED:
+                return BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC;
+
+            default:
+                throw new IllegalArgumentException("Unsupported dismissal reason: " + reason);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java
index a0c8e23..a0573db 100644
--- a/services/core/java/com/android/server/biometrics/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/face/FaceService.java
@@ -118,14 +118,16 @@
         private int mError;
         // Only valid if mError is ERROR_VENDOR
         private int mVendorError;
+        private int mUser;
 
         AuthenticationEvent(long startTime, long latency, boolean authenticated, int error,
-                int vendorError) {
+                int vendorError, int user) {
             mStartTime = startTime;
             mLatency = latency;
             mAuthenticated = authenticated;
             mError = error;
             mVendorError = vendorError;
+            mUser = user;
         }
 
         public String toString(Context context) {
@@ -134,6 +136,7 @@
                     + "\tAuthenticated: " + mAuthenticated
                     + "\tError: " + mError
                     + "\tVendorCode: " + mVendorError
+                    + "\tUser: " + mUser
                     + "\t" + FaceManager.getErrorString(context, mError, mVendorError);
         }
     }
@@ -242,7 +245,8 @@
                     System.currentTimeMillis() - getStartTimeMs() /* latency */,
                     authenticated,
                     0 /* error */,
-                    0 /* vendorError */));
+                    0 /* vendorError */,
+                    getTargetUserId()));
 
             // For face, the authentication lifecycle ends either when
             // 1) Authenticated == true
@@ -260,7 +264,8 @@
                     System.currentTimeMillis() - getStartTimeMs() /* latency */,
                     false /* authenticated */,
                     error,
-                    vendorCode));
+                    vendorCode,
+                    getTargetUserId()));
 
             return super.onError(deviceId, error, vendorCode);
         }
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index f15d999..8d26176 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -367,6 +367,8 @@
         CompatConfig config = new CompatConfig(androidBuildClassifier, context);
         config.initConfigFromLib(Environment.buildPath(
                 Environment.getRootDirectory(), "etc", "compatconfig"));
+        config.initConfigFromLib(Environment.buildPath(
+                Environment.getRootDirectory(), "system_ext", "etc", "compatconfig"));
         return config;
     }
 
diff --git a/services/core/java/com/android/server/compat/OverrideValidatorImpl.java b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java
index dfc0080..4bf606e 100644
--- a/services/core/java/com/android/server/compat/OverrideValidatorImpl.java
+++ b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java
@@ -53,6 +53,7 @@
     public OverrideAllowedState getOverrideAllowedState(long changeId, String packageName) {
         boolean debuggableBuild = false;
         boolean finalBuild = false;
+        int minTargetSdk = mCompatConfig.minTargetSdkForChangeId(changeId);
 
         debuggableBuild = mAndroidBuildClassifier.isDebuggableBuild();
         finalBuild = mAndroidBuildClassifier.isFinalBuild();
@@ -76,15 +77,14 @@
         if ((applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) {
             return new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1);
         }
-        int minTargetSdk = mCompatConfig.minTargetSdkForChangeId(changeId);
-        // Do not allow overriding non-target sdk gated changes on user builds
-        if (minTargetSdk == -1) {
-            return new OverrideAllowedState(DISABLED_NON_TARGET_SDK, appTargetSdk, minTargetSdk);
-        }
         // Allow overriding any change for debuggable apps on non-final builds.
         if (!finalBuild) {
             return new OverrideAllowedState(ALLOWED, appTargetSdk, minTargetSdk);
         }
+        // Do not allow overriding non-target sdk gated changes on user builds
+        if (minTargetSdk == -1) {
+            return new OverrideAllowedState(DISABLED_NON_TARGET_SDK, appTargetSdk, minTargetSdk);
+        }
         // Only allow to opt-in for a targetSdk gated change.
         if (applicationInfo.targetSdkVersion < minTargetSdk) {
             return new OverrideAllowedState(ALLOWED, appTargetSdk, minTargetSdk);
diff --git a/services/core/java/com/android/server/connectivity/DataConnectionStats.java b/services/core/java/com/android/server/connectivity/DataConnectionStats.java
index 1b1c546..5010e46 100644
--- a/services/core/java/com/android/server/connectivity/DataConnectionStats.java
+++ b/services/core/java/com/android/server/connectivity/DataConnectionStats.java
@@ -16,6 +16,9 @@
 
 package com.android.server.connectivity;
 
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -24,6 +27,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
@@ -91,7 +95,10 @@
         boolean visible = (simReadyOrUnknown || isCdma()) // we only check the sim state for GSM
                 && hasService()
                 && mDataState == TelephonyManager.DATA_CONNECTED;
-        int networkType = mServiceState.getDataNetworkType();
+        NetworkRegistrationInfo regInfo =
+                mServiceState.getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN);
+        int networkType = regInfo == null ? TelephonyManager.NETWORK_TYPE_UNKNOWN
+                : regInfo.getAccessNetworkTechnology();
         if (DEBUG) Log.d(TAG, String.format("Noting data connection for network type %s: %svisible",
                 networkType, visible ? "" : "not "));
         try {
diff --git a/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java b/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java
index 96a202f..4fb6607 100644
--- a/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java
+++ b/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java
@@ -81,10 +81,15 @@
         printEvent(localTimeMs, pw, mCurrentDefaultNetwork);
     }
 
-    public synchronized void listEventsAsProto(PrintWriter pw) {
+    /**
+     * Convert events in the ring buffer to a list of IpConnectivityEvent protos
+     */
+    public synchronized List<IpConnectivityEvent> listEventsAsProto() {
+        List<IpConnectivityEvent> list = new ArrayList<>();
         for (DefaultNetworkEvent ev : mEventsLog.toArray()) {
-            pw.print(IpConnectivityEventBuilder.toProto(ev));
+            list.add(IpConnectivityEventBuilder.toProto(ev));
         }
+        return list;
     }
 
     public synchronized void flushEvents(List<IpConnectivityEvent> out) {
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
index 33f6ed5..2c06d82 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
@@ -41,7 +41,9 @@
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
 
 import java.io.FileDescriptor;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -239,18 +241,37 @@
         mDefaultNetworkMetrics.listEvents(pw);
     }
 
+    private List<IpConnectivityEvent> listEventsAsProtos() {
+        final List<IpConnectivityEvent> events = IpConnectivityEventBuilder.toProto(getEvents());
+        if (mNetdListener != null) {
+            events.addAll(mNetdListener.listAsProtos());
+        }
+        events.addAll(mDefaultNetworkMetrics.listEventsAsProto());
+        return events;
+    }
+
     /*
      * Print the content of the rolling event buffer in text proto format.
      */
-    private void cmdListAsProto(PrintWriter pw) {
-        final List<ConnectivityMetricsEvent> events = getEvents();
-        for (IpConnectivityEvent ev : IpConnectivityEventBuilder.toProto(events)) {
-            pw.print(ev.toString());
+    private void cmdListAsTextProto(PrintWriter pw) {
+        listEventsAsProtos().forEach(e -> pw.print(e.toString()));
+    }
+
+    /*
+     * Write the content of the rolling event buffer in proto wire format to the given OutputStream.
+     */
+    private void cmdListAsBinaryProto(OutputStream out) {
+        final int dropped;
+        synchronized (mLock) {
+            dropped = mDropped;
         }
-        if (mNetdListener != null) {
-            mNetdListener.listAsProtos(pw);
+        try {
+            byte[] data = IpConnectivityEventBuilder.serialize(dropped, listEventsAsProtos());
+            out.write(data);
+            out.flush();
+        } catch (IOException e) {
+            Log.e(TAG, "could not serialize events", e);
         }
-        mDefaultNetworkMetrics.listEventsAsProto(pw);
     }
 
     /*
@@ -267,6 +288,9 @@
         static final String CMD_FLUSH = "flush";
         // Dump the rolling buffer of metrics event in human readable proto text format.
         static final String CMD_PROTO = "proto";
+        // Dump the rolling buffer of metrics event in proto wire format. See usage() of
+        // frameworks/native/cmds/dumpsys/dumpsys.cpp for details.
+        static final String CMD_PROTO_BIN = "--proto";
         // Dump the rolling buffer of metrics event and pretty print events using a human readable
         // format. Also print network dns/connect statistics and default network event time series.
         static final String CMD_LIST = "list";
@@ -291,7 +315,10 @@
                     cmdFlush(pw);
                     return;
                 case CMD_PROTO:
-                    cmdListAsProto(pw);
+                    cmdListAsTextProto(pw);
+                    return;
+                case CMD_PROTO_BIN:
+                    cmdListAsBinaryProto(new FileOutputStream(fd));
                     return;
                 case CMD_LIST:
                 default:
diff --git a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
index 6fa999c..04c792a 100644
--- a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
+++ b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
@@ -26,6 +26,7 @@
 import static android.net.NetworkPolicy.LIMIT_DISABLED;
 import static android.net.NetworkPolicy.WARNING_DISABLED;
 import static android.provider.Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
 import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH;
 import static com.android.server.net.NetworkPolicyManagerService.OPPORTUNISTIC_QUOTA_UNKNOWN;
@@ -46,19 +47,18 @@
 import android.net.NetworkPolicy;
 import android.net.NetworkPolicyManager;
 import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
 import android.net.NetworkStats;
 import android.net.NetworkTemplate;
-import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
+import android.net.Uri;
 import android.os.BestClock;
 import android.os.Handler;
 import android.os.SystemClock;
-import android.net.Uri;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
-import android.util.DataUnit;
 import android.util.DebugUtils;
-import android.util.Pair;
 import android.util.Range;
 import android.util.Slog;
 
@@ -74,7 +74,6 @@
 import java.time.ZoneOffset;
 import java.time.ZonedDateTime;
 import java.time.temporal.ChronoUnit;
-import java.util.Iterator;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeUnit;
 
@@ -185,7 +184,6 @@
     // Track information on mobile networks as they come and go.
     class MultipathTracker {
         final Network network;
-        final int subId;
         final String subscriberId;
 
         private long mQuota;
@@ -198,13 +196,14 @@
         public MultipathTracker(Network network, NetworkCapabilities nc) {
             this.network = network;
             this.mNetworkCapabilities = new NetworkCapabilities(nc);
-            try {
-                subId = Integer.parseInt(
-                        ((StringNetworkSpecifier) nc.getNetworkSpecifier()).toString());
-            } catch (ClassCastException | NullPointerException | NumberFormatException e) {
+            NetworkSpecifier specifier = nc.getNetworkSpecifier();
+            int subId = INVALID_SUBSCRIPTION_ID;
+            if (specifier instanceof TelephonyNetworkSpecifier) {
+                subId = ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
+            } else {
                 throw new IllegalStateException(String.format(
-                        "Can't get subId from mobile network %s (%s): %s",
-                        network, nc, e.getMessage()));
+                        "Can't get subId from mobile network %s (%s)",
+                        network, nc));
             }
 
             TelephonyManager tele = mContext.getSystemService(TelephonyManager.class);
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
index aea6d8d..f636d67 100644
--- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -116,7 +116,8 @@
                 && !lp.hasIpv4Address();
 
         // If the network tells us it doesn't use clat, respect that.
-        final boolean skip464xlat = (nai.netMisc() != null) && nai.netMisc().skip464xlat;
+        final boolean skip464xlat = (nai.netAgentConfig() != null)
+                && nai.netAgentConfig().skip464xlat;
 
         return supported && connected && isIpv6OnlyNetwork && !skip464xlat;
     }
diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
index dbc339b..f2892cc 100644
--- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
+++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
@@ -366,16 +366,21 @@
         }
     }
 
-    public synchronized void listAsProtos(PrintWriter pw) {
+    /**
+     * Convert events in the buffer to a list of IpConnectivityEvent protos
+     */
+    public synchronized List<IpConnectivityEvent> listAsProtos() {
+        List<IpConnectivityEvent> list = new ArrayList<>();
         for (int i = 0; i < mNetworkMetrics.size(); i++) {
-            pw.print(IpConnectivityEventBuilder.toProto(mNetworkMetrics.valueAt(i).connectMetrics));
+            list.add(IpConnectivityEventBuilder.toProto(mNetworkMetrics.valueAt(i).connectMetrics));
         }
         for (int i = 0; i < mNetworkMetrics.size(); i++) {
-            pw.print(IpConnectivityEventBuilder.toProto(mNetworkMetrics.valueAt(i).dnsMetrics));
+            list.add(IpConnectivityEventBuilder.toProto(mNetworkMetrics.valueAt(i).dnsMetrics));
         }
         for (int i = 0; i < mWakeupStats.size(); i++) {
-            pw.print(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i)));
+            list.add(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i)));
         }
+        return list;
     }
 
     private long getTransports(int netId) {
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 5e085ca..d66aec5 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -23,9 +23,9 @@
 import android.net.INetworkMonitor;
 import android.net.LinkProperties;
 import android.net.Network;
+import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
-import android.net.NetworkMisc;
 import android.net.NetworkMonitorManager;
 import android.net.NetworkRequest;
 import android.net.NetworkScore;
@@ -127,7 +127,7 @@
     // This should only be modified by ConnectivityService, via setNetworkCapabilities().
     // TODO: make this private with a getter.
     public NetworkCapabilities networkCapabilities;
-    public final NetworkMisc networkMisc;
+    public final NetworkAgentConfig networkAgentConfig;
     // Indicates if netd has been told to create this Network. From this point on the appropriate
     // routing rules are setup and routes are added so packets can begin flowing over the Network.
     // This is a sticky bit; once set it is never cleared.
@@ -261,7 +261,7 @@
 
     public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info,
             LinkProperties lp, NetworkCapabilities nc, @NonNull NetworkScore ns, Context context,
-            Handler handler, NetworkMisc misc, ConnectivityService connService, INetd netd,
+            Handler handler, NetworkAgentConfig config, ConnectivityService connService, INetd netd,
             IDnsResolver dnsResolver, INetworkManagementService nms, int factorySerialNumber) {
         this.messenger = messenger;
         asyncChannel = ac;
@@ -274,7 +274,7 @@
         mConnService = connService;
         mContext = context;
         mHandler = handler;
-        networkMisc = misc;
+        networkAgentConfig = config;
         this.factorySerialNumber = factorySerialNumber;
     }
 
@@ -309,8 +309,8 @@
         return mConnService;
     }
 
-    public NetworkMisc netMisc() {
-        return networkMisc;
+    public NetworkAgentConfig netAgentConfig() {
+        return networkAgentConfig;
     }
 
     public Handler handler() {
@@ -451,15 +451,6 @@
                 && !isLingering();
     }
 
-    /**
-     * Returns whether this network is currently suspended. A network is suspended if it is still
-     * connected but data temporarily fails to transfer. See {@link NetworkInfo.State#SUSPENDED}
-     * and {@link NetworkCapabilities#NET_CAPABILITY_NOT_SUSPENDED}.
-     */
-    public boolean isSuspended() {
-        return networkInfo.getState() == NetworkInfo.State.SUSPENDED;
-    }
-
     // Does this network satisfy request?
     public boolean satisfies(NetworkRequest request) {
         return created &&
@@ -487,7 +478,8 @@
         // selected and we're trying to see what its score could be. This ensures that we don't tear
         // down an explicitly selected network before the user gets a chance to prefer it when
         // a higher-scoring network (e.g., Ethernet) is available.
-        if (networkMisc.explicitlySelected && (networkMisc.acceptUnvalidated || pretendValidated)) {
+        if (networkAgentConfig.explicitlySelected
+                && (networkAgentConfig.acceptUnvalidated || pretendValidated)) {
             return ConnectivityConstants.EXPLICITLY_SELECTED_NETWORK_SCORE;
         }
 
@@ -533,7 +525,8 @@
         synchronized (this) {
             // Network objects are outwardly immutable so there is no point in duplicating.
             // Duplicating also precludes sharing socket factories and connection pools.
-            final String subscriberId = (networkMisc != null) ? networkMisc.subscriberId : null;
+            final String subscriberId = (networkAgentConfig != null)
+                    ? networkAgentConfig.subscriberId : null;
             return new NetworkState(new NetworkInfo(networkInfo),
                     new LinkProperties(linkProperties),
                     new NetworkCapabilities(networkCapabilities), network, subscriberId, null);
@@ -641,13 +634,13 @@
                 + "nc{" + networkCapabilities + "}  Score{" + getCurrentScore() + "}  "
                 + "everValidated{" + everValidated + "}  lastValidated{" + lastValidated + "}  "
                 + "created{" + created + "} lingering{" + isLingering() + "} "
-                + "explicitlySelected{" + networkMisc.explicitlySelected + "} "
-                + "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} "
+                + "explicitlySelected{" + networkAgentConfig.explicitlySelected + "} "
+                + "acceptUnvalidated{" + networkAgentConfig.acceptUnvalidated + "} "
                 + "everCaptivePortalDetected{" + everCaptivePortalDetected + "} "
                 + "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} "
                 + "captivePortalValidationPending{" + captivePortalValidationPending + "} "
                 + "partialConnectivity{" + partialConnectivity + "} "
-                + "acceptPartialConnectivity{" + networkMisc.acceptPartialConnectivity + "} "
+                + "acceptPartialConnectivity{" + networkAgentConfig.acceptPartialConnectivity + "} "
                 + "clat{" + clatd + "} "
                 + "}";
     }
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 2179518..2c41557 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -28,7 +28,7 @@
 import android.content.Intent;
 import android.content.res.Resources;
 import android.net.NetworkSpecifier;
-import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
 import android.net.wifi.WifiInfo;
 import android.os.UserHandle;
 import android.telephony.SubscriptionManager;
@@ -223,14 +223,8 @@
                     // name has been added to it
                     NetworkSpecifier specifier = nai.networkCapabilities.getNetworkSpecifier();
                     int subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
-                    if (specifier instanceof StringNetworkSpecifier) {
-                        try {
-                            subId = Integer.parseInt(
-                                    ((StringNetworkSpecifier) specifier).specifier);
-                        } catch (NumberFormatException e) {
-                            Slog.e(TAG, "NumberFormatException on "
-                                    + ((StringNetworkSpecifier) specifier).specifier);
-                        }
+                    if (specifier instanceof TelephonyNetworkSpecifier) {
+                        subId = ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
                     }
 
                     details = mTelephonyManager.createForSubscriptionId(subId)
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 6b70e5f..0c9abae 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -53,11 +53,11 @@
 import android.net.LocalSocketAddress;
 import android.net.Network;
 import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
-import android.net.NetworkFactory;
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
-import android.net.NetworkMisc;
+import android.net.NetworkProvider;
 import android.net.RouteInfo;
 import android.net.UidRange;
 import android.net.VpnService;
@@ -848,7 +848,11 @@
     }
 
     public int getNetId() {
-        return mNetworkAgent != null ? mNetworkAgent.netId : NETID_UNSET;
+        final NetworkAgent agent = mNetworkAgent;
+        if (null == agent) return NETID_UNSET;
+        final Network network = agent.getNetwork();
+        if (null == network) return NETID_UNSET;
+        return network.netId;
     }
 
     private LinkProperties makeLinkProperties() {
@@ -914,7 +918,7 @@
      * has certain changes, in which case this method would just return {@code false}.
      */
     private boolean updateLinkPropertiesInPlaceIfPossible(NetworkAgent agent, VpnConfig oldConfig) {
-        // NetworkMisc cannot be updated without registering a new NetworkAgent.
+        // NetworkAgentConfig cannot be updated without registering a new NetworkAgent.
         if (oldConfig.allowBypass != mConfig.allowBypass) {
             Log.i(TAG, "Handover not possible due to changes to allowBypass");
             return false;
@@ -947,8 +951,8 @@
 
         mNetworkInfo.setDetailedState(DetailedState.CONNECTING, null, null);
 
-        NetworkMisc networkMisc = new NetworkMisc();
-        networkMisc.allowBypass = mConfig.allowBypass && !mLockdown;
+        NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig();
+        networkAgentConfig.allowBypass = mConfig.allowBypass && !mLockdown;
 
         mNetworkCapabilities.setEstablishingVpnAppUid(Binder.getCallingUid());
         mNetworkCapabilities.setUids(createUserAndRestrictedProfilesRanges(mUserHandle,
@@ -957,8 +961,8 @@
         try {
             mNetworkAgent = new NetworkAgent(mLooper, mContext, NETWORKTYPE /* logtag */,
                     mNetworkInfo, mNetworkCapabilities, lp,
-                    ConnectivityConstants.VPN_DEFAULT_SCORE, networkMisc,
-                    NetworkFactory.SerialNumber.VPN) {
+                    ConnectivityConstants.VPN_DEFAULT_SCORE, networkAgentConfig,
+                    NetworkProvider.ID_VPN) {
                             @Override
                             public void unwanted() {
                                 // We are user controlled, not driven by NetworkRequest.
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index bc7307b..74c1e63 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.content;
 
+import static android.content.PermissionChecker.PERMISSION_GRANTED;
+
 import android.Manifest;
 import android.accounts.Account;
 import android.annotation.Nullable;
@@ -1212,7 +1214,7 @@
     @RequiresPermission(android.Manifest.permission.CACHE_CONTENT)
     public void putCache(String packageName, Uri key, Bundle value, int userId) {
         Bundle.setDefusable(value, true);
-        enforceCrossUserPermission(userId, TAG);
+        enforceNonFullCrossUserPermission(userId, TAG);
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CACHE_CONTENT, TAG);
         mContext.getSystemService(AppOpsManager.class).checkPackage(Binder.getCallingUid(),
                 packageName);
@@ -1234,7 +1236,7 @@
     @Override
     @RequiresPermission(android.Manifest.permission.CACHE_CONTENT)
     public Bundle getCache(String packageName, Uri key, int userId) {
-        enforceCrossUserPermission(userId, TAG);
+        enforceNonFullCrossUserPermission(userId, TAG);
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CACHE_CONTENT, TAG);
         mContext.getSystemService(AppOpsManager.class).checkPackage(Binder.getCallingUid(),
                 packageName);
@@ -1302,6 +1304,30 @@
         }
     }
 
+    /**
+     * Checks if the request is from the system or an app that has {@code INTERACT_ACROSS_USERS} or
+     * {@code INTERACT_ACROSS_USERS_FULL} permission, if the {@code userHandle} is not for the
+     * caller.
+     *
+     * @param userHandle the user handle of the user we want to act on behalf of.
+     * @param message the message to log on security exception.
+     */
+    private void enforceNonFullCrossUserPermission(int userHandle, String message) {
+        final int callingUser = UserHandle.getCallingUserId();
+        if (callingUser == userHandle) {
+            return;
+        }
+
+        int interactAcrossUsersState = mContext.checkCallingOrSelfPermission(
+                Manifest.permission.INTERACT_ACROSS_USERS);
+        if (interactAcrossUsersState == PERMISSION_GRANTED) {
+            return;
+        }
+
+        mContext.enforceCallingOrSelfPermission(
+                Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
+    }
+
     private static int normalizeSyncable(int syncable) {
         if (syncable > 0) {
             return SyncStorageEngine.AuthorityInfo.SYNCABLE;
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index 28f67fe..aa39926 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -109,11 +109,17 @@
         return levels;
     }
 
-    private static float[] getFloatArray(TypedArray array) {
+    /**
+     * Extracts a float array from the specified {@link TypedArray}.
+     *
+     * @param array The array to convert.
+     * @return the given array as a float array.
+     */
+    public static float[] getFloatArray(TypedArray array) {
         final int N = array.length();
         float[] vals = new float[N];
         for (int i = 0; i < N; i++) {
-            vals[i] = array.getFloat(i, -1.0f);
+            vals[i] = array.getFloat(i, PowerManager.BRIGHTNESS_OFF_FLOAT);
         }
         array.recycle();
         return vals;
@@ -335,7 +341,7 @@
         }
     }
 
-    protected float normalizeAbsoluteBrightness(int brightness) {
+    protected static float normalizeAbsoluteBrightness(int brightness) {
         brightness = MathUtils.constrain(brightness,
                 PowerManager.BRIGHTNESS_OFF, PowerManager.BRIGHTNESS_ON);
         return (float) brightness / PowerManager.BRIGHTNESS_ON;
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
new file mode 100644
index 0000000..e09cf61
--- /dev/null
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -0,0 +1,157 @@
+/*
+ * 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.display;
+
+import android.os.Environment;
+import android.util.Slog;
+
+import com.android.server.display.config.DisplayConfiguration;
+import com.android.server.display.config.NitsMap;
+import com.android.server.display.config.Point;
+import com.android.server.display.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.List;
+
+import javax.xml.datatype.DatatypeConfigurationException;
+
+/**
+ * Reads and stores display-specific configurations.
+ */
+public class DisplayDeviceConfig {
+    private static final String TAG = "DisplayDeviceConfig";
+
+    public static final float HIGH_BRIGHTNESS_MODE_UNSUPPORTED = Float.NaN;
+
+    private static final String ETC_DIR = "etc";
+    private static final String DISPLAY_CONFIG_DIR = "displayconfig";
+    private static final String CONFIG_FILE_FORMAT = "display_%d.xml";
+
+    private float[] mNits;
+    private float[] mBrightness;
+    private BigDecimal mHighBrightnessModeStart;
+
+    private DisplayDeviceConfig() {
+    }
+
+    /**
+     * Creates an instance for the specified display.
+     *
+     * @param physicalDisplayId The display ID for which to load the configuration.
+     * @return A configuration instance for the specified display.
+     */
+    public static DisplayDeviceConfig create(long physicalDisplayId) {
+        final DisplayDeviceConfig config = new DisplayDeviceConfig();
+        final String filename = String.format(CONFIG_FILE_FORMAT, physicalDisplayId);
+
+        config.initFromFile(Environment.buildPath(
+                Environment.getProductDirectory(), ETC_DIR, DISPLAY_CONFIG_DIR, filename));
+        return config;
+    }
+
+    /**
+     * Return the brightness mapping nits array if one is defined in the configuration file.
+     *
+     * @return The brightness mapping nits array.
+     */
+    public float[] getNits() {
+        return mNits;
+    }
+
+    /**
+     * Return the brightness mapping value array if one is defined in the configuration file.
+     *
+     * @return The brightness mapping value array.
+     */
+    public float[] getBrightness() {
+        return mBrightness;
+    }
+
+    /**
+     * Returns the point along the brightness value range {@link #getBrightness()} that
+     * high-brightness-mode begins. If high-brightness-mode is not supported, then
+     * Float.NaN is returned.
+     *
+     * @return The high brightness mode threshold, or Float.NaN if not supported.
+     */
+    public float getHighBrightnessModeStart() {
+        return mHighBrightnessModeStart != null
+                ? mHighBrightnessModeStart.floatValue() : HIGH_BRIGHTNESS_MODE_UNSUPPORTED;
+    }
+
+    private void initFromFile(File configFile) {
+        if (!configFile.exists()) {
+            // Display configuration files aren't required to exist.
+            return;
+        }
+
+        if (!configFile.isFile()) {
+            Slog.e(TAG, "Display configuration is not a file: " + configFile + ", skipping");
+            return;
+        }
+
+        try (InputStream in = new BufferedInputStream(new FileInputStream(configFile))) {
+            final DisplayConfiguration config = XmlParser.read(in);
+            loadBrightnessMap(config);
+        } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
+            Slog.e(TAG, "Encountered an error while reading/parsing display config file: "
+                    + configFile, e);
+        }
+    }
+
+    private void loadBrightnessMap(DisplayConfiguration config) {
+        final NitsMap map = config.getScreenBrightnessMap();
+        final List<Point> points = map.getPoint();
+        final int size = points.size();
+
+        float[] nits = new float[size];
+        float[] backlight = new float[size];
+
+        int i = 0;
+        for (Point point : points) {
+            nits[i] = point.getNits().floatValue();
+            backlight[i] = point.getValue().floatValue();
+            if (i > 0) {
+                if (nits[i] < nits[i - 1]) {
+                    Slog.e(TAG, "screenBrightnessMap must be non-decreasing, ignoring rest "
+                            + " of configuration. Nits: " +  nits[i] + " < " + nits[i - 1]);
+                    return;
+                }
+
+                if (backlight[i] < backlight[i - 1]) {
+                    Slog.e(TAG, "screenBrightnessMap must be non-decreasing, ignoring rest "
+                            + " of configuration. Value: " +  backlight[i] + " < "
+                            + backlight[i - 1]);
+                    return;
+                }
+            }
+            ++i;
+        }
+        final BigDecimal hbmStart = map.getHighBrightnessStart();
+
+        mHighBrightnessModeStart = hbmStart;
+        mNits = nits;
+        mBrightness = backlight;
+    }
+}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index d7ae5b5..71ade62 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -39,6 +39,7 @@
 import android.content.pm.ParceledListSlice;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.database.ContentObserver;
 import android.graphics.ColorSpace;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -60,6 +61,7 @@
 import android.hardware.input.InputManagerInternal;
 import android.media.projection.IMediaProjection;
 import android.media.projection.IMediaProjectionManager;
+import android.net.Uri;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -77,6 +79,7 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.IntArray;
 import android.util.Pair;
@@ -304,6 +307,13 @@
 
     private SensorManager mSensorManager;
 
+    // Whether minimal post processing is allowed by the user.
+    @GuardedBy("mSyncRoot")
+    private boolean mMinimalPostProcessingAllowed;
+
+    // Receives notifications about changes to Settings.
+    private SettingsObserver mSettingsObserver;
+
     public DisplayManagerService(Context context) {
         this(context, new Injector());
     }
@@ -403,6 +413,7 @@
                 BrightnessConfiguration config =
                         mPersistentDataStore.getBrightnessConfiguration(userSerial);
                 mDisplayPowerController.setBrightnessConfiguration(config);
+                handleSettingsChange();
             }
             mDisplayPowerController.onSwitchUser(newUserId);
         }
@@ -428,6 +439,8 @@
             // Just in case the top inset changed before the system was ready. At this point, any
             // relevant configuration should be in place.
             recordTopInsetLocked(mLogicalDisplays.get(Display.DEFAULT_DISPLAY));
+
+            updateSettingsLocked();
         }
 
         mDisplayModeDirector.setDesiredDisplayModeSpecsListener(
@@ -435,6 +448,8 @@
         mDisplayModeDirector.start(mSensorManager);
 
         mHandler.sendEmptyMessage(MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS);
+
+        mSettingsObserver = new SettingsObserver();
     }
 
     @VisibleForTesting
@@ -569,6 +584,33 @@
         }
     }
 
+    private class SettingsObserver extends ContentObserver {
+        SettingsObserver() {
+            super(mHandler);
+
+            mContext.getContentResolver().registerContentObserver(
+                    Settings.Secure.getUriFor(
+                        Settings.Secure.MINIMAL_POST_PROCESSING_ALLOWED), false, this);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            handleSettingsChange();
+        }
+    }
+
+    private void handleSettingsChange() {
+        synchronized (mSyncRoot) {
+            updateSettingsLocked();
+            scheduleTraversalLocked(false);
+        }
+    }
+
+    private void updateSettingsLocked() {
+        mMinimalPostProcessingAllowed = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                Settings.Secure.MINIMAL_POST_PROCESSING_ALLOWED, 1, UserHandle.USER_CURRENT) != 0;
+    }
+
     private DisplayInfo getDisplayInfoInternal(int displayId, int callingUid) {
         synchronized (mSyncRoot) {
             LogicalDisplay display = mLogicalDisplays.get(displayId);
@@ -1192,7 +1234,7 @@
     }
 
     private void setDisplayPropertiesInternal(int displayId, boolean hasContent,
-            float requestedRefreshRate, int requestedModeId, boolean requestedMinimalPostProcessing,
+            float requestedRefreshRate, int requestedModeId, boolean preferMinimalPostProcessing,
             boolean inTraversal) {
         synchronized (mSyncRoot) {
             LogicalDisplay display = mLogicalDisplays.get(displayId);
@@ -1220,14 +1262,13 @@
             mDisplayModeDirector.getAppRequestObserver().setAppRequestedMode(
                     displayId, requestedModeId);
 
+            if (display.getDisplayInfoLocked().minimalPostProcessingSupported) {
+                boolean mppRequest = mMinimalPostProcessingAllowed && preferMinimalPostProcessing;
 
-            if (display.getDisplayInfoLocked().minimalPostProcessingSupported
-                    && (display.getRequestedMinimalPostProcessingLocked()
-                    != requestedMinimalPostProcessing)) {
-
-                display.setRequestedMinimalPostProcessingLocked(requestedMinimalPostProcessing);
-
-                shouldScheduleTraversal = true;
+                if (display.getRequestedMinimalPostProcessingLocked() != mppRequest) {
+                    display.setRequestedMinimalPostProcessingLocked(mppRequest);
+                    shouldScheduleTraversal = true;
+                }
             }
 
             if (shouldScheduleTraversal) {
@@ -2252,6 +2293,13 @@
         }
 
         @Override // Binder call
+        public boolean isMinimalPostProcessingRequested(int displayId) {
+            synchronized (mSyncRoot) {
+                return mLogicalDisplays.get(displayId).getRequestedMinimalPostProcessingLocked();
+            }
+        }
+
+        @Override // Binder call
         public void setTemporaryBrightness(int brightness) {
             mContext.enforceCallingOrSelfPermission(
                     Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS,
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index ad728c1..decb1f9 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -73,7 +73,7 @@
     private static final int GLOBAL_ID = -1;
 
     // The tolerance within which we consider something approximately equals.
-    private static final float EPSILON = 0.001f;
+    private static final float EPSILON = 0.01f;
 
     private final Object mLock = new Object();
     private final Context mContext;
@@ -237,15 +237,15 @@
                 lowestConsideredPriority++;
             }
 
-            int defaultModeId = defaultMode.getModeId();
+            int baseModeId = defaultMode.getModeId();
             if (availableModes.length > 0) {
-                defaultModeId = availableModes[0];
+                baseModeId = availableModes[0];
             }
             // filterModes function is going to filter the modes based on the voting system. If
             // the application requests a given mode with preferredModeId function, it will be
-            // stored as defaultModeId.
+            // stored as baseModeId.
             return new DesiredDisplayModeSpecs(
-                    defaultModeId, new RefreshRateRange(minRefreshRate, maxRefreshRate));
+                    baseModeId, new RefreshRateRange(minRefreshRate, maxRefreshRate));
         }
     }
 
@@ -526,7 +526,7 @@
     }
 
     /**
-     * Information about the desired display mode to be set by the system. Includes the default
+     * Information about the desired display mode to be set by the system. Includes the base
      * mode ID and refresh rate range.
      *
      * We have this class in addition to SurfaceControl.DesiredDisplayConfigSpecs to make clear the
@@ -535,10 +535,10 @@
      */
     public static final class DesiredDisplayModeSpecs {
         /**
-         * Default mode ID. This is what system defaults to for all other settings, or
+         * Base mode ID. This is what system defaults to for all other settings, or
          * if the refresh rate range is not available.
          */
-        public int defaultModeId;
+        public int baseModeId;
         /**
          * The refresh rate range.
          */
@@ -548,9 +548,8 @@
             refreshRateRange = new RefreshRateRange();
         }
 
-        public DesiredDisplayModeSpecs(
-                int defaultModeId, @NonNull RefreshRateRange refreshRateRange) {
-            this.defaultModeId = defaultModeId;
+        public DesiredDisplayModeSpecs(int baseModeId, @NonNull RefreshRateRange refreshRateRange) {
+            this.baseModeId = baseModeId;
             this.refreshRateRange = refreshRateRange;
         }
 
@@ -559,7 +558,7 @@
          */
         @Override
         public String toString() {
-            return String.format("defaultModeId=%d min=%.0f max=%.0f", defaultModeId,
+            return String.format("baseModeId=%d min=%.0f max=%.0f", baseModeId,
                     refreshRateRange.min, refreshRateRange.max);
         }
         /**
@@ -577,7 +576,7 @@
 
             DesiredDisplayModeSpecs desiredDisplayModeSpecs = (DesiredDisplayModeSpecs) other;
 
-            if (defaultModeId != desiredDisplayModeSpecs.defaultModeId) {
+            if (baseModeId != desiredDisplayModeSpecs.baseModeId) {
                 return false;
             }
             if (!refreshRateRange.equals(desiredDisplayModeSpecs.refreshRateRange)) {
@@ -588,14 +587,14 @@
 
         @Override
         public int hashCode() {
-            return Objects.hash(defaultModeId, refreshRateRange);
+            return Objects.hash(baseModeId, refreshRateRange);
         }
 
         /**
          * Copy values from the other object.
          */
         public void copyFrom(DesiredDisplayModeSpecs other) {
-            defaultModeId = other.defaultModeId;
+            baseModeId = other.baseModeId;
             refreshRateRange.min = other.refreshRateRange.min;
             refreshRateRange.max = other.refreshRateRange.max;
         }
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index cf94d46..7e8fe3a 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -30,6 +30,7 @@
 import android.util.LongSparseArray;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.Spline;
 import android.view.Display;
 import android.view.DisplayAddress;
 import android.view.DisplayCutout;
@@ -37,10 +38,11 @@
 import android.view.Surface;
 import android.view.SurfaceControl;
 
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
-import com.android.server.lights.Light;
 import com.android.server.lights.LightsManager;
+import com.android.server.lights.LogicalLight;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -162,7 +164,7 @@
 
     private final class LocalDisplayDevice extends DisplayDevice {
         private final long mPhysicalDisplayId;
-        private final Light mBacklight;
+        private final LogicalLight mBacklight;
         private final SparseArray<DisplayModeRecord> mSupportedModes = new SparseArray<>();
         private final ArrayList<Integer> mSupportedColorModes = new ArrayList<>();
         private final boolean mIsInternal;
@@ -187,8 +189,10 @@
         private boolean mGameContentTypeRequested;
         private boolean mSidekickActive;
         private SidekickInternal mSidekickInternal;
-
         private SurfaceControl.PhysicalDisplayInfo[] mDisplayInfos;
+        private Spline mSystemBrightnessToNits;
+        private Spline mNitsToHalBrightness;
+        private boolean mHalBrightnessSupport;
 
         LocalDisplayDevice(IBinder displayToken, long physicalDisplayId,
                 SurfaceControl.PhysicalDisplayInfo[] physicalDisplayInfos, int activeDisplayInfo,
@@ -210,6 +214,11 @@
             mHdrCapabilities = SurfaceControl.getHdrCapabilities(displayToken);
             mAllmSupported = SurfaceControl.getAutoLowLatencyModeSupport(displayToken);
             mGameContentTypeSupported = SurfaceControl.getGameContentTypeSupport(displayToken);
+            mHalBrightnessSupport = SurfaceControl.getDisplayBrightnessSupport(displayToken);
+
+            // Defer configuration file loading
+            BackgroundThread.getHandler().sendMessage(PooledLambda.obtainMessage(
+                    LocalDisplayDevice::loadDisplayConfigurationBrightnessMapping, this));
         }
 
         @Override
@@ -272,14 +281,14 @@
 
             // Check whether surface flinger spontaneously changed display config specs out from
             // under us. If so, schedule a traversal to reapply our display config specs.
-            if (mDisplayModeSpecs.defaultModeId != 0) {
-                int activeDefaultMode =
+            if (mDisplayModeSpecs.baseModeId != 0) {
+                int activeBaseMode =
                         findMatchingModeIdLocked(physicalDisplayConfigSpecs.defaultConfig);
                 // If we can't map the defaultConfig index to a mode, then the physical display
                 // configs must have changed, and the code below for handling changes to the
                 // list of available modes will take care of updating display config specs.
-                if (activeDefaultMode != 0) {
-                    if (mDisplayModeSpecs.defaultModeId != activeDefaultMode
+                if (activeBaseMode != 0) {
+                    if (mDisplayModeSpecs.baseModeId != activeBaseMode
                             || mDisplayModeSpecs.refreshRateRange.min
                                     != physicalDisplayConfigSpecs.minRefreshRate
                             || mDisplayModeSpecs.refreshRateRange.max
@@ -312,14 +321,14 @@
                 mDefaultModeId = activeRecord.mMode.getModeId();
             }
 
-            // Determine whether the display mode specs' default mode is still there.
-            if (mSupportedModes.indexOfKey(mDisplayModeSpecs.defaultModeId) < 0) {
-                if (mDisplayModeSpecs.defaultModeId != 0) {
+            // Determine whether the display mode specs' base mode is still there.
+            if (mSupportedModes.indexOfKey(mDisplayModeSpecs.baseModeId) < 0) {
+                if (mDisplayModeSpecs.baseModeId != 0) {
                     Slog.w(TAG,
-                            "DisplayModeSpecs default mode no longer available, using currently"
-                                    + " active mode as default.");
+                            "DisplayModeSpecs base mode no longer available, using currently"
+                                    + " active mode.");
                 }
-                mDisplayModeSpecs.defaultModeId = activeRecord.mMode.getModeId();
+                mDisplayModeSpecs.baseModeId = activeRecord.mMode.getModeId();
                 mDisplayModeSpecsInvalid = true;
             }
 
@@ -338,6 +347,41 @@
             return true;
         }
 
+        private void loadDisplayConfigurationBrightnessMapping() {
+            Spline nitsToHal = null;
+            Spline sysToNits = null;
+
+            // Load the mapping from nits to HAL brightness range (display-device-config.xml)
+            DisplayDeviceConfig config = DisplayDeviceConfig.create(mPhysicalDisplayId);
+            if (config == null) {
+                return;
+            }
+            final float[] halNits = config.getNits();
+            final float[] halBrightness = config.getBrightness();
+            if (halNits == null || halBrightness == null) {
+                return;
+            }
+            nitsToHal = Spline.createSpline(halNits, halBrightness);
+
+            // Load the mapping from system brightness range to nits (config.xml)
+            final Resources res = getOverlayContext().getResources();
+            final float[] sysNits = BrightnessMappingStrategy.getFloatArray(res.obtainTypedArray(
+                            com.android.internal.R.array.config_screenBrightnessNits));
+            final int[] sysBrightness = res.getIntArray(
+                    com.android.internal.R.array.config_screenBrightnessBacklight);
+            if (sysNits.length == 0 || sysBrightness.length != sysNits.length) {
+                return;
+            }
+            final float[] sysBrightnessFloat = new float[sysBrightness.length];
+            for (int i = 0; i < sysBrightness.length; i++) {
+                sysBrightnessFloat[i] = sysBrightness[i];
+            }
+            sysToNits = Spline.createSpline(sysBrightnessFloat, sysNits);
+
+            mNitsToHalBrightness = nitsToHal;
+            mSystemBrightnessToNits = sysToNits;
+        }
+
         private boolean updateColorModesLocked(int[] colorModes,
                 int activeColorMode) {
             List<Integer> pendingColorModes = new ArrayList<>();
@@ -628,13 +672,37 @@
                         Trace.traceBegin(Trace.TRACE_TAG_POWER, "setDisplayBrightness("
                                 + "id=" + physicalDisplayId + ", brightness=" + brightness + ")");
                         try {
-                            mBacklight.setBrightness(brightness);
+                            if (mHalBrightnessSupport) {
+                                mBacklight.setBrightnessFloat(
+                                        displayBrightnessToHalBrightness(brightness));
+                            } else {
+                                mBacklight.setBrightness(brightness);
+                            }
                             Trace.traceCounter(Trace.TRACE_TAG_POWER,
                                     "ScreenBrightness", brightness);
                         } finally {
                             Trace.traceEnd(Trace.TRACE_TAG_POWER);
                         }
                     }
+
+                    /**
+                     * Converts brightness range from the framework's brightness space to the
+                     * Hal brightness space if the HAL brightness space has been provided via
+                     * a display device configuration file.
+                     */
+                    private float displayBrightnessToHalBrightness(int brightness) {
+                        if (mSystemBrightnessToNits == null || mNitsToHalBrightness == null) {
+                            return PowerManager.BRIGHTNESS_INVALID_FLOAT;
+                        }
+
+                        if (brightness == 0) {
+                            return PowerManager.BRIGHTNESS_OFF_FLOAT;
+                        }
+
+                        final float nits = mSystemBrightnessToNits.interpolate(brightness);
+                        final float halBrightness = mNitsToHalBrightness.interpolate(nits);
+                        return halBrightness;
+                    }
                 };
             }
             return null;
@@ -648,13 +716,13 @@
         @Override
         public void setDesiredDisplayModeSpecsLocked(
                 DisplayModeDirector.DesiredDisplayModeSpecs displayModeSpecs) {
-            if (displayModeSpecs.defaultModeId == 0) {
+            if (displayModeSpecs.baseModeId == 0) {
                 // Bail if the caller is requesting a null mode. We'll get called again shortly with
                 // a valid mode.
                 return;
             }
-            int defaultPhysIndex = findDisplayInfoIndexLocked(displayModeSpecs.defaultModeId);
-            if (defaultPhysIndex < 0) {
+            int basePhysIndex = findDisplayInfoIndexLocked(displayModeSpecs.baseModeId);
+            if (basePhysIndex < 0) {
                 // When a display is hotplugged, it's possible for a mode to be removed that was
                 // previously valid. Because of the way display changes are propagated through the
                 // framework, and the caching of the display mode specs in LogicalDisplay, it's
@@ -662,8 +730,7 @@
                 // mode. This should only happen in extremely rare cases. A followup call will
                 // contain a valid mode id.
                 Slog.w(TAG,
-                        "Ignoring request for invalid default mode id "
-                                + displayModeSpecs.defaultModeId);
+                        "Ignoring request for invalid base mode id " + displayModeSpecs.baseModeId);
                 updateDeviceInfoLocked();
                 return;
             }
@@ -673,7 +740,7 @@
                 getHandler().sendMessage(PooledLambda.obtainMessage(
                         LocalDisplayDevice::setDesiredDisplayModeSpecsAsync, this,
                         getDisplayTokenLocked(),
-                        new SurfaceControl.DesiredDisplayConfigSpecs(defaultPhysIndex,
+                        new SurfaceControl.DesiredDisplayConfigSpecs(basePhysIndex,
                                 mDisplayModeSpecs.refreshRateRange.min,
                                 mDisplayModeSpecs.refreshRateRange.max)));
             }
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index b6255d1..c8e5f6c 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -317,7 +317,7 @@
         @Override
         public void setDesiredDisplayModeSpecsLocked(
                 DisplayModeDirector.DesiredDisplayModeSpecs displayModeSpecs) {
-            final int id = displayModeSpecs.defaultModeId;
+            final int id = displayModeSpecs.baseModeId;
             int index = -1;
             if (id == 0) {
                 // Use the default.
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 1794df3..7c2ec78 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -491,7 +491,7 @@
             mIoThread.start();
             mIoLooper = mIoThread.getLooper();
         }
-        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
+        mPowerStatus = getInitialPowerStatus();
         mProhibitMode = false;
         mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
         mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true);
@@ -538,6 +538,28 @@
         mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED);
     }
 
+    private void bootCompleted() {
+        // on boot, if device is interactive, set HDMI CEC state as powered on as well
+        if (mPowerManager.isInteractive() && isPowerStandbyOrTransient()) {
+            onWakeUp();
+        }
+    }
+
+    /**
+     * Returns the initial power status used when the HdmiControlService starts.
+     */
+    @VisibleForTesting
+    int getInitialPowerStatus() {
+        // The initial power status is POWER_STATUS_TRANSIENT_TO_STANDBY.
+        // Once boot completes the service transitions to POWER_STATUS_ON if the device is
+        // interactive.
+        // Quiescent boot is a special boot mode, in which the screen stays off during boot
+        // and the device goes to sleep after boot has finished.
+        // We don't transition to POWER_STATUS_ON initially, as we might be booting in quiescent
+        // mode, during which we don't want to appear powered on to avoid being made active source.
+        return HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
+    }
+
     @VisibleForTesting
     void setCecController(HdmiCecController cecController) {
         mCecController = cecController;
@@ -553,7 +575,9 @@
         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
             mTvInputManager = (TvInputManager) getContext().getSystemService(
                     Context.TV_INPUT_SERVICE);
-            mPowerManager = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
+            mPowerManager = getContext().getSystemService(PowerManager.class);
+        } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
+            runOnServiceThread(this::bootCompleted);
         }
     }
 
@@ -579,9 +603,7 @@
      * Called when the initialization of local devices is complete.
      */
     private void onInitializeCecComplete(int initiatedBy) {
-        if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
-            mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
-        }
+        updatePowerStatusOnInitializeCecComplete();
         mWakeUpMessageReceived = false;
 
         if (isTvDeviceEnabled()) {
@@ -606,6 +628,17 @@
         }
     }
 
+    /**
+     * 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;
+        }
+    }
+
     private void registerContentObserver() {
         ContentResolver resolver = getContext().getContentResolver();
         String[] settings = new String[] {
@@ -2667,6 +2700,13 @@
     }
 
     @ServiceThreadOnly
+    @VisibleForTesting
+    void setPowerStatus(int powerStatus) {
+        assertRunOnServiceThread();
+        mPowerStatus = powerStatus;
+    }
+
+    @ServiceThreadOnly
     boolean isPowerOnOrTransient() {
         assertRunOnServiceThread();
         return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
diff --git a/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
index 5c18f58..5161a77 100644
--- a/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
+++ b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
@@ -16,6 +16,10 @@
 
 package com.android.server.incremental;
 
+import static android.content.pm.InstallationFile.FILE_TYPE_OBB;
+import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
+import static android.content.pm.PackageInstaller.LOCATION_MEDIA_OBB;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -171,7 +175,9 @@
             session = packageInstaller.openSession(sessionId);
             for (int i = 0; i < numFiles; i++) {
                 InstallationFile file = installationFiles.get(i);
-                session.addFile(file.getName(), file.getSize(), file.getMetadata());
+                final int location = file.getFileType() == FILE_TYPE_OBB ? LOCATION_MEDIA_OBB
+                        : LOCATION_DATA_APP;
+                session.addFile(location, file.getName(), file.getSize(), file.getMetadata(), null);
             }
             session.commit(localReceiver.getIntentSender());
             final Intent result = localReceiver.getResult();
diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
index c58000f..8206fef 100644
--- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
@@ -41,7 +41,6 @@
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.infra.AbstractRemoteService;
 import com.android.internal.os.BackgroundThread;
-import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 
@@ -713,8 +712,8 @@
     }
 
     /**
-     * Gets a list of all supported users (i.e., those that pass the {@link #isSupported(UserInfo)}
-     * check).
+     * Gets a list of all supported users (i.e., those that pass the
+     * {@link #isSupportedUser(TargetUser)}check).
      */
     @NonNull
     protected List<UserInfo> getSupportedUsers() {
@@ -723,7 +722,7 @@
         final List<UserInfo> supportedUsers = new ArrayList<>(size);
         for (int i = 0; i < size; i++) {
             final UserInfo userInfo = allUsers[i];
-            if (isSupported(userInfo)) {
+            if (isSupportedUser(new TargetUser(userInfo))) {
                 supportedUsers.add(userInfo);
             }
         }
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index ae6d63a..90358ca 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -83,11 +83,11 @@
 import android.widget.Toast;
 
 import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.DumpUtils;
-import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 import com.android.server.DisplayThread;
 import com.android.server.LocalServices;
@@ -186,6 +186,9 @@
     // The associations of input devices to displays by port. Maps from input device port (String)
     // to display id (int). Currently only accessed by InputReader.
     private final Map<String, Integer> mStaticAssociations;
+    private final Object mAssociationsLock = new Object();
+    @GuardedBy("mAssociationLock")
+    private final Map<String, Integer> mRuntimeAssociations = new HashMap<String, Integer>();
 
     private static native long nativeInit(InputManagerService service,
             Context context, MessageQueue messageQueue);
@@ -240,6 +243,7 @@
     private static native void nativeSetCustomPointerIcon(long ptr, PointerIcon icon);
     private static native void nativeSetPointerCapture(long ptr, boolean detached);
     private static native boolean nativeCanDispatchToDisplay(long ptr, int deviceId, int displayId);
+    private static native void nativeNotifyPortAssociationsChanged(long ptr);
 
     // Input event injection constants defined in InputDispatcher.h.
     private static final int INPUT_EVENT_INJECTION_SUCCEEDED = 0;
@@ -1723,6 +1727,49 @@
         nativeSetCustomPointerIcon(mPtr, icon);
     }
 
+    /**
+     * Add a runtime association between the input port and the display port. This overrides any
+     * static associations.
+     * @param inputPort The port of the input device.
+     * @param displayPort The physical port of the associated display.
+     */
+    @Override // Binder call
+    public void addPortAssociation(@NonNull String inputPort, int displayPort) {
+        if (!checkCallingPermission(
+                android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT,
+                "addPortAssociation()")) {
+            throw new SecurityException(
+                    "Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT permission");
+        }
+
+        Objects.requireNonNull(inputPort);
+        synchronized (mAssociationsLock) {
+            mRuntimeAssociations.put(inputPort, displayPort);
+        }
+        nativeNotifyPortAssociationsChanged(mPtr);
+    }
+
+    /**
+     * Remove the runtime association between the input port and the display port. Any existing
+     * static association for the cleared input port will be restored.
+     * @param inputPort The port of the input device to be cleared.
+     */
+    @Override // Binder call
+    public void removePortAssociation(@NonNull String inputPort) {
+        if (!checkCallingPermission(
+                android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT,
+                "clearPortAssociations()")) {
+            throw new SecurityException(
+                    "Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT permission");
+        }
+
+        Objects.requireNonNull(inputPort);
+        synchronized (mAssociationsLock) {
+            mRuntimeAssociations.remove(inputPort);
+        }
+        nativeNotifyPortAssociationsChanged(mPtr);
+    }
+
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
@@ -1743,6 +1790,16 @@
                 pw.println("  display: " + v);
             });
         }
+
+        synchronized (mAssociationsLock) {
+            if (!mRuntimeAssociations.isEmpty()) {
+                pw.println("Runtime Associations:");
+                mRuntimeAssociations.forEach((k, v) -> {
+                    pw.print("  port: " + k);
+                    pw.println("  display: " + v);
+                });
+            }
+        }
     }
 
     private boolean checkCallingPermission(String permission, String func) {
@@ -1766,6 +1823,7 @@
     @Override
     public void monitor() {
         synchronized (mInputFilterLock) { }
+        synchronized (mAssociationsLock) { /* Test if blocked by associations lock. */}
         nativeMonitor(mPtr);
     }
 
@@ -1930,7 +1988,7 @@
      * @return Flattened list
      */
     private static List<String> flatten(@NonNull Map<String, Integer> map) {
-        List<String> list = new ArrayList<>(map.size() * 2);
+        final List<String> list = new ArrayList<>(map.size() * 2);
         map.forEach((k, v)-> {
             list.add(k);
             list.add(v.toString());
@@ -1943,11 +2001,11 @@
      * directory.
      */
     private static Map<String, Integer> loadStaticInputPortAssociations() {
-        File baseDir = Environment.getVendorDirectory();
-        File confFile = new File(baseDir, PORT_ASSOCIATIONS_PATH);
+        final File baseDir = Environment.getVendorDirectory();
+        final File confFile = new File(baseDir, PORT_ASSOCIATIONS_PATH);
 
         try {
-            InputStream stream = new FileInputStream(confFile);
+            final InputStream stream = new FileInputStream(confFile);
             return ConfigurationProcessor.processInputPortAssociations(stream);
         } catch (FileNotFoundException e) {
             // Most of the time, file will not exist, which is expected.
@@ -1960,7 +2018,14 @@
 
     // Native callback
     private String[] getInputPortAssociations() {
-        List<String> associationList = flatten(mStaticAssociations);
+        final Map<String, Integer> associations = new HashMap<>(mStaticAssociations);
+
+        // merge the runtime associations.
+        synchronized (mAssociationsLock) {
+            associations.putAll(mRuntimeAssociations);
+        }
+
+        final List<String> associationList = flatten(associations);
         return associationList.toArray(new String[0]);
     }
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index c40e8af..9c42152 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -68,8 +68,21 @@
      * @param autofillId {@link AutofillId} of currently focused field.
      * @param cb {@link IInlineSuggestionsRequestCallback} used to pass back the request object.
      */
-    public abstract void onCreateInlineSuggestionsRequest(ComponentName componentName,
-            AutofillId autofillId, IInlineSuggestionsRequestCallback cb);
+    public abstract void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
+            ComponentName componentName, AutofillId autofillId,
+            IInlineSuggestionsRequestCallback cb);
+
+    /**
+     * Force switch to the enabled input method by {@code imeId} for current user. If the input
+     * method with {@code imeId} is not enabled or not installed, do nothing.
+     *
+     * @param imeId  The input method ID to be switched to.
+     * @param userId The user ID to be queried.
+     * @return {@code true} if the current input method was successfully switched to the input
+     * method by {@code imeId}; {@code false} the input method with {@code imeId} is not available
+     * to be switched.
+     */
+    public abstract boolean switchToInputMethod(String imeId, @UserIdInt int userId);
 
     /**
      * Fake implementation of {@link InputMethodManagerInternal}.  All the methods do nothing.
@@ -95,8 +108,14 @@
                 }
 
                 @Override
-                public void onCreateInlineSuggestionsRequest(ComponentName componentName,
-                        AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
+                public void onCreateInlineSuggestionsRequest(int userId,
+                        ComponentName componentName, AutofillId autofillId,
+                        IInlineSuggestionsRequestCallback cb) {
+                }
+
+                @Override
+                public boolean switchToInputMethod(String imeId, int userId) {
+                    return false;
                 }
             };
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 8b6b614..905a94f 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -111,6 +111,7 @@
 import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
 import android.view.autofill.AutofillId;
 import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InlineSuggestionsRequest;
 import android.view.inputmethod.InputBinding;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputConnectionInspector;
@@ -142,6 +143,7 @@
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.view.IInlineSuggestionsRequestCallback;
+import com.android.internal.view.IInlineSuggestionsResponseCallback;
 import com.android.internal.view.IInputContext;
 import com.android.internal.view.IInputMethod;
 import com.android.internal.view.IInputMethodClient;
@@ -756,6 +758,14 @@
     private final WeakHashMap<IBinder, IBinder> mImeTargetWindowMap = new WeakHashMap<>();
 
     /**
+     * Map of generated token to windowToken that is requesting
+     * {@link InputMethodManager#showSoftInput(View, int)}.
+     * This map tracks origin of showSoftInput requests.
+     */
+    @GuardedBy("mMethodMap")
+    private final WeakHashMap<IBinder, IBinder> mShowRequestWindowMap = new WeakHashMap<>();
+
+    /**
      * A ring buffer to store the history of {@link StartInputInfo}.
      */
     private static final class StartInputHistory {
@@ -972,7 +982,8 @@
                         hideCurrentInputLocked(0, null);
                         mShowRequested = showRequested;
                     } else if (mShowRequested) {
-                        showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
+                        showCurrentInputLocked(
+                                mCurFocusedWindow, InputMethodManager.SHOW_IMPLICIT, null);
                     }
                 } else {
                     boolean enabledChanged = false;
@@ -1790,15 +1801,18 @@
     }
 
     @GuardedBy("mMethodMap")
-    private void onCreateInlineSuggestionsRequestLocked(ComponentName componentName,
-            AutofillId autofillId, IInlineSuggestionsRequestCallback callback) {
-
+    private void onCreateInlineSuggestionsRequestLocked(@UserIdInt int userId,
+            ComponentName componentName, AutofillId autofillId,
+            IInlineSuggestionsRequestCallback callback) {
         final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
         try {
-            if (imi != null && imi.isInlineSuggestionsEnabled() && mCurMethod != null) {
+            if (userId == mSettings.getCurrentUserId() && imi != null
+                    && imi.isInlineSuggestionsEnabled() && mCurMethod != null) {
                 executeOrSendMessage(mCurMethod,
                         mCaller.obtainMessageOOOO(MSG_INLINE_SUGGESTIONS_REQUEST, mCurMethod,
-                                componentName, autofillId, callback));
+                                componentName, autofillId,
+                                new InlineSuggestionsRequestCallbackDecorator(callback,
+                                        imi.getPackageName())));
             } else {
                 callback.onInlineSuggestionsUnsupported();
             }
@@ -1808,6 +1822,42 @@
     }
 
     /**
+     * The decorator which validates the host package name in the
+     * {@link InlineSuggestionsRequest} argument to make sure it matches the IME package name.
+     */
+    private static final class InlineSuggestionsRequestCallbackDecorator
+            extends IInlineSuggestionsRequestCallback.Stub {
+        @NonNull
+        private final IInlineSuggestionsRequestCallback mCallback;
+        @NonNull
+        private final String mImePackageName;
+
+        InlineSuggestionsRequestCallbackDecorator(
+                @NonNull IInlineSuggestionsRequestCallback callback,
+                @NonNull String imePackageName) {
+            mCallback = callback;
+            mImePackageName = imePackageName;
+        }
+
+        @Override
+        public void onInlineSuggestionsUnsupported() throws RemoteException {
+            mCallback.onInlineSuggestionsUnsupported();
+        }
+
+        @Override
+        public void onInlineSuggestionsRequest(InlineSuggestionsRequest request,
+                IInlineSuggestionsResponseCallback callback) throws RemoteException {
+            if (!mImePackageName.equals(request.getHostPackageName())) {
+                throw new SecurityException(
+                        "Host package name in the provide request=[" + request.getHostPackageName()
+                                + "] doesn't match the IME package name=[" + mImePackageName
+                                + "].");
+            }
+            mCallback.onInlineSuggestionsRequest(request, callback);
+        }
+    }
+
+    /**
      * @param imiId if null, returns enabled subtypes for the current imi
      * @return enabled subtypes of the specified imi
      */
@@ -2034,7 +2084,7 @@
                 startInputToken, session, mCurInputContext, mCurAttribute));
         if (mShowRequested) {
             if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
-            showCurrentInputLocked(getAppShowFlags(), null);
+            showCurrentInputLocked(mCurFocusedWindow, getAppShowFlags(), null);
         }
         return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
                 session.session, (session.channel != null ? session.channel.dup() : null),
@@ -2748,7 +2798,7 @@
     }
 
     @Override
-    public boolean showSoftInput(IInputMethodClient client, int flags,
+    public boolean showSoftInput(IInputMethodClient client, IBinder windowToken, int flags,
             ResultReceiver resultReceiver) {
         int uid = Binder.getCallingUid();
         synchronized (mMethodMap) {
@@ -2773,7 +2823,7 @@
                     }
                 }
                 if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
-                return showCurrentInputLocked(flags, resultReceiver);
+                return showCurrentInputLocked(windowToken, flags, resultReceiver);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -2781,7 +2831,7 @@
     }
 
     @GuardedBy("mMethodMap")
-    boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
+    boolean showCurrentInputLocked(IBinder windowToken, int flags, ResultReceiver resultReceiver) {
         mShowRequested = true;
         if (mAccessibilityRequestingNoSoftKeyboard) {
             return false;
@@ -2801,9 +2851,12 @@
         boolean res = false;
         if (mCurMethod != null) {
             if (DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken);
-            executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
+            // create a dummy token for IMS so that IMS cannot inject windows into client app.
+            Binder showInputToken = new Binder();
+            mShowRequestWindowMap.put(showInputToken, windowToken);
+            executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOOO(
                     MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
-                    resultReceiver));
+                    resultReceiver, showInputToken));
             mInputShown = true;
             if (mHaveConnection && !mVisibleBound) {
                 bindCurrentInputMethodServiceLocked(
@@ -3104,7 +3157,7 @@
                                 attribute, startInputFlags, startInputReason);
                         didStart = true;
                     }
-                    showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
+                    showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null);
                 }
                 break;
             case LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
@@ -3130,7 +3183,7 @@
                                     attribute, startInputFlags, startInputReason);
                             didStart = true;
                         }
-                        showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
+                        showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null);
                     } else {
                         Slog.e(TAG, "SOFT_INPUT_STATE_VISIBLE is ignored because"
                                 + " there is no focused view that also returns true from"
@@ -3147,7 +3200,7 @@
                                 attribute, startInputFlags, startInputReason);
                         didStart = true;
                     }
-                    showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
+                    showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null);
                 } else {
                     Slog.e(TAG, "SOFT_INPUT_STATE_ALWAYS_VISIBLE is ignored because"
                             + " there is no focused view that also returns true from"
@@ -3586,7 +3639,7 @@
     }
 
     @BinderThread
-    private void applyImeVisibility(IBinder token, boolean setVisible) {
+    private void applyImeVisibility(IBinder token, IBinder windowToken, boolean setVisible) {
         synchronized (mMethodMap) {
             if (!calledWithValidTokenLocked(token)) {
                 return;
@@ -3603,7 +3656,7 @@
                 }
             } else {
                 // Send to window manager to show IME after IME layout finishes.
-                mWindowManagerInternal.showImePostLayout(mLastImeTargetWindow);
+                mWindowManagerInternal.showImePostLayout(mShowRequestWindowMap.get(windowToken));
             }
         }
     }
@@ -3654,7 +3707,7 @@
             }
             long ident = Binder.clearCallingIdentity();
             try {
-                showCurrentInputLocked(flags, null);
+                showCurrentInputLocked(mLastImeTargetWindow, flags, null);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -3739,7 +3792,8 @@
                 try {
                     if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput("
                             + msg.arg1 + ", " + args.arg2 + ")");
-                    ((IInputMethod)args.arg1).showSoftInput(msg.arg1, (ResultReceiver)args.arg2);
+                    ((IInputMethod) args.arg1).showSoftInput(
+                            (IBinder) args.arg3, msg.arg1, (ResultReceiver) args.arg2);
                 } catch (RemoteException e) {
                 }
                 args.recycle();
@@ -4471,10 +4525,43 @@
         }
     }
 
-    private void onCreateInlineSuggestionsRequest(ComponentName componentName,
-            AutofillId autofillId, IInlineSuggestionsRequestCallback callback) {
+    private void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
+            ComponentName componentName, AutofillId autofillId,
+            IInlineSuggestionsRequestCallback callback) {
         synchronized (mMethodMap) {
-            onCreateInlineSuggestionsRequestLocked(componentName, autofillId, callback);
+            onCreateInlineSuggestionsRequestLocked(userId, componentName, autofillId, callback);
+        }
+    }
+
+    private boolean switchToInputMethod(String imeId, @UserIdInt int userId) {
+        synchronized (mMethodMap) {
+            if (userId == mSettings.getCurrentUserId()) {
+                if (!mMethodMap.containsKey(imeId)
+                        || !mSettings.getEnabledInputMethodListLocked()
+                                .contains(mMethodMap.get(imeId))) {
+                    return false; // IME is not is found or not enabled.
+                }
+                setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID);
+                return true;
+            }
+            final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+            final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
+            final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
+                    new ArrayMap<>();
+            AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
+            queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
+                    methodMap, methodList);
+            final InputMethodSettings settings = new InputMethodSettings(
+                    mContext.getResources(), mContext.getContentResolver(), methodMap,
+                    userId, false);
+            if (!methodMap.containsKey(imeId)
+                    || !settings.getEnabledInputMethodListLocked()
+                            .contains(methodMap.get(imeId))) {
+                return false; // IME is not is found or not enabled.
+            }
+            settings.putSelectedInputMethod(imeId);
+            settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
+            return true;
         }
     }
 
@@ -4510,9 +4597,14 @@
         }
 
         @Override
-        public void onCreateInlineSuggestionsRequest(ComponentName componentName,
+        public void onCreateInlineSuggestionsRequest(int userId, ComponentName componentName,
                 AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
-            mService.onCreateInlineSuggestionsRequest(componentName, autofillId, cb);
+            mService.onCreateInlineSuggestionsRequest(userId, componentName, autofillId, cb);
+        }
+
+        @Override
+        public boolean switchToInputMethod(String imeId, int userId) {
+            return mService.switchToInputMethod(imeId, userId);
         }
     }
 
@@ -5065,31 +5157,7 @@
                 if (!userHasDebugPriv(userId, shellCommand)) {
                     continue;
                 }
-                boolean failedToSelectUnknownIme = false;
-                if (userId == mSettings.getCurrentUserId()) {
-                    if (mMethodMap.containsKey(imeId)) {
-                        setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID);
-                    } else {
-                        failedToSelectUnknownIme = true;
-                    }
-                } else {
-                    final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
-                    final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
-                    final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
-                            new ArrayMap<>();
-                    AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
-                    queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
-                            methodMap, methodList);
-                    final InputMethodSettings settings = new InputMethodSettings(
-                            mContext.getResources(), mContext.getContentResolver(), methodMap,
-                            userId, false);
-                    if (methodMap.containsKey(imeId)) {
-                        settings.putSelectedInputMethod(imeId);
-                        settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
-                    } else {
-                        failedToSelectUnknownIme = true;
-                    }
-                }
+                boolean failedToSelectUnknownIme = !switchToInputMethod(imeId, userId);
                 if (failedToSelectUnknownIme) {
                     error.print("Unknown input method ");
                     error.print(imeId);
@@ -5291,8 +5359,8 @@
 
         @BinderThread
         @Override
-        public void applyImeVisibility(boolean setVisible) {
-            mImms.applyImeVisibility(mToken, setVisible);
+        public void applyImeVisibility(IBinder windowToken, boolean setVisible) {
+            mImms.applyImeVisibility(mToken, windowToken, setVisible);
         }
     }
 }
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index 1f9379c..d09c478 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -191,8 +191,9 @@
                         }
 
                         @Override
-                        public void onCreateInlineSuggestionsRequest(ComponentName componentName,
-                                AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
+                        public void onCreateInlineSuggestionsRequest(int userId,
+                                ComponentName componentName, AutofillId autofillId,
+                                IInlineSuggestionsRequestCallback cb) {
                             try {
                                 //TODO(b/137800469): support multi client IMEs.
                                 cb.onInlineSuggestionsUnsupported();
@@ -200,6 +201,12 @@
                                 Slog.w(TAG, "Failed to call onInlineSuggestionsUnsupported.", e);
                             }
                         }
+
+                        @Override
+                        public boolean switchToInputMethod(String imeId, @UserIdInt int userId) {
+                            reportNotSupported();
+                            return false;
+                        }
                     });
         }
 
@@ -1442,7 +1449,8 @@
         @BinderThread
         @Override
         public boolean showSoftInput(
-                IInputMethodClient client, int flags, ResultReceiver resultReceiver) {
+                IInputMethodClient client, IBinder token, int flags,
+                ResultReceiver resultReceiver) {
             final int callingUid = Binder.getCallingUid();
             final int callingPid = Binder.getCallingPid();
             final int userId = UserHandle.getUserId(callingUid);
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index b1639a9..2926ec9 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -67,7 +67,9 @@
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /** Implementation of {@link AppIntegrityManagerService}. */
@@ -221,6 +223,15 @@
             }
 
             String installerPackageName = getInstallerPackageName(intent);
+
+            // Skip integrity verification if the verifier is doing the install.
+            if (isRuleProvider(installerPackageName)) {
+                Slog.i(TAG, "Verifier doing the install. Skipping integrity check.");
+                mPackageManagerInternal.setIntegrityVerificationResult(
+                        verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
+                return;
+            }
+
             String appCert = getCertificateFingerprint(packageInfo);
 
             AppInstallMetadata.Builder builder = new AppInstallMetadata.Builder();
@@ -271,7 +282,7 @@
      * Verify the UID and return the installer package name.
      *
      * @return the package name of the installer, or null if it cannot be determined or it is
-     *     installed via adb.
+     * installed via adb.
      */
     @Nullable
     private String getInstallerPackageName(Intent intent) {
@@ -518,9 +529,17 @@
     }
 
     private String getCallerPackageNameOrThrow() {
-        final String[] allowedRuleProviders =
-                mContext.getResources()
-                        .getStringArray(R.array.config_integrityRuleProviderPackages);
+        String callerPackageName = getCallerPackageName();
+        if (callerPackageName == null) {
+            throw new SecurityException(
+                    "Only system packages specified in config_integrityRuleProviderPackages are"
+                            + " allowed to call this method.");
+        }
+        return callerPackageName;
+    }
+
+    private String getCallerPackageName() {
+        final List<String> allowedRuleProviders = getAllowedRuleProviders();
         for (String packageName : allowedRuleProviders) {
             try {
                 // At least in tests, getPackageUid gives "NameNotFound" but getPackagesFromUid
@@ -537,9 +556,7 @@
                 Slog.i(TAG, "Rule provider package " + packageName + " not installed.");
             }
         }
-        throw new SecurityException(
-                "Only system packages specified in config_integrityRuleProviderPackages are"
-                        + " allowed to call this method.");
+        return null;
     }
 
     private boolean isSystemApp(String packageName) {
@@ -552,4 +569,14 @@
             return false;
         }
     }
+
+    private List<String> getAllowedRuleProviders() {
+        return Arrays.asList(mContext.getResources().getStringArray(
+                R.array.config_integrityRuleProviderPackages));
+    }
+
+    private boolean isRuleProvider(String installerPackageName) {
+        return getAllowedRuleProviders().stream().anyMatch(
+                ruleProvider -> ruleProvider.equals(installerPackageName));
+    }
 }
diff --git a/services/core/java/com/android/server/integrity/IntegrityFileManager.java b/services/core/java/com/android/server/integrity/IntegrityFileManager.java
index 31d4816..fffe7d9 100644
--- a/services/core/java/com/android/server/integrity/IntegrityFileManager.java
+++ b/services/core/java/com/android/server/integrity/IntegrityFileManager.java
@@ -24,6 +24,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.integrity.model.RuleMetadata;
+import com.android.server.integrity.parser.RandomAccessObject;
 import com.android.server.integrity.parser.RuleBinaryParser;
 import com.android.server.integrity.parser.RuleIndexRange;
 import com.android.server.integrity.parser.RuleIndexingController;
@@ -39,6 +40,7 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
 
@@ -63,10 +65,8 @@
     // update rules atomically.
     private final File mStagingDir;
 
-    @Nullable
-    private RuleMetadata mRuleMetadataCache;
-    @Nullable
-    private RuleIndexingController mRuleIndexingController;
+    @Nullable private RuleMetadata mRuleMetadataCache;
+    @Nullable private RuleIndexingController mRuleIndexingController;
 
     /** Get the singleton instance of this class. */
     public static synchronized IntegrityFileManager getInstance() {
@@ -131,9 +131,9 @@
         }
 
         try (FileOutputStream ruleFileOutputStream =
-                     new FileOutputStream(new File(mStagingDir, RULES_FILE));
-             FileOutputStream indexingFileOutputStream =
-                     new FileOutputStream(new File(mStagingDir, INDEXING_FILE))) {
+                        new FileOutputStream(new File(mStagingDir, RULES_FILE));
+                FileOutputStream indexingFileOutputStream =
+                        new FileOutputStream(new File(mStagingDir, INDEXING_FILE))) {
             mRuleSerializer.serialize(
                     rules, Optional.empty(), ruleFileOutputStream, indexingFileOutputStream);
         }
@@ -153,16 +153,20 @@
             throws IOException, RuleParseException {
         synchronized (RULES_LOCK) {
             // Try to identify indexes from the index file.
-            List<RuleIndexRange> ruleReadingIndexes =
-                    mRuleIndexingController.identifyRulesToEvaluate(appInstallMetadata);
-
-            // Read the rules based on the index information.
-            // TODO(b/145493956): Provide the identified indexes to the rule reader.
-            try (FileInputStream inputStream =
-                         new FileInputStream(new File(mRulesDir, RULES_FILE))) {
-                List<Rule> rules = mRuleParser.parse(inputStream);
-                return rules;
+            List<RuleIndexRange> ruleReadingIndexes;
+            try {
+                ruleReadingIndexes =
+                        mRuleIndexingController.identifyRulesToEvaluate(appInstallMetadata);
+            } catch (Exception e) {
+                Slog.w(TAG, "Error identifying the rule indexes. Trying unindexed.", e);
+                ruleReadingIndexes = Collections.emptyList();
             }
+
+            // Read the rules based on the index information when available.
+            File ruleFile = new File(mRulesDir, RULES_FILE);
+            List<Rule> rules =
+                    mRuleParser.parse(RandomAccessObject.ofFile(ruleFile), ruleReadingIndexes);
+            return rules;
         }
     }
 
@@ -181,6 +185,10 @@
                     && tmpDir.renameTo(mStagingDir))) {
                 throw new IOException("Error switching staging/rules directory");
             }
+
+            for (File file : mStagingDir.listFiles()) {
+                file.delete();
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/integrity/model/BitInputStream.java b/services/core/java/com/android/server/integrity/model/BitInputStream.java
index e768fe6..e7cc81e 100644
--- a/services/core/java/com/android/server/integrity/model/BitInputStream.java
+++ b/services/core/java/com/android/server/integrity/model/BitInputStream.java
@@ -19,26 +19,21 @@
 import java.io.IOException;
 import java.io.InputStream;
 
-/** A wrapper class for reading a stream of bits. */
+/** A wrapper class for reading a stream of bits.
+ *
+ * <p>Note: this class reads from underlying stream byte-by-byte. It is advised to apply buffering
+ * to underlying streams.
+ */
 public class BitInputStream {
 
-    private long mBitPointer;
-    private boolean mReadFromStream;
+    private long mBitsRead;
 
-    private byte[] mRuleBytes;
-    private InputStream mRuleInputStream;
+    private InputStream mInputStream;
 
-    private byte mCurrentRuleByte;
+    private byte mCurrentByte;
 
-    public BitInputStream(byte[] ruleBytes) {
-        this.mRuleBytes = ruleBytes;
-        this.mBitPointer = 0;
-        this.mReadFromStream = false;
-    }
-
-    public BitInputStream(InputStream ruleInputStream) {
-        this.mRuleInputStream = ruleInputStream;
-        this.mReadFromStream = true;
+    public BitInputStream(InputStream inputStream) {
+        mInputStream = inputStream;
     }
 
     /**
@@ -52,15 +47,15 @@
         int count = 0;
 
         while (count++ < numOfBits) {
-            if (mBitPointer % 8 == 0) {
-                mCurrentRuleByte = getNextByte();
+            if (mBitsRead % 8 == 0) {
+                mCurrentByte = getNextByte();
             }
-            int offset = 7 - (int) (mBitPointer % 8);
+            int offset = 7 - (int) (mBitsRead % 8);
 
             component <<= 1;
-            component |= (mCurrentRuleByte >>> offset) & 1;
+            component |= (mCurrentByte >>> offset) & 1;
 
-            mBitPointer++;
+            mBitsRead++;
         }
 
         return component;
@@ -68,22 +63,10 @@
 
     /** Check if there are bits left in the stream. */
     public boolean hasNext() throws IOException {
-        if (mReadFromStream) {
-            return mRuleInputStream.available() > 0;
-        } else {
-            return mBitPointer / 8 < mRuleBytes.length;
-        }
+        return mInputStream.available() > 0;
     }
 
     private byte getNextByte() throws IOException {
-        if (mReadFromStream) {
-            return (byte) mRuleInputStream.read();
-        } else {
-            int idx = (int) (mBitPointer / 8);
-            if (idx >= mRuleBytes.length) {
-                throw new IllegalArgumentException(String.format("Invalid byte index: %d", idx));
-            }
-            return mRuleBytes[idx];
-        }
+        return (byte) mInputStream.read();
     }
 }
diff --git a/services/core/java/com/android/server/integrity/model/BitOutputStream.java b/services/core/java/com/android/server/integrity/model/BitOutputStream.java
index b8ea041..7d1bb3f 100644
--- a/services/core/java/com/android/server/integrity/model/BitOutputStream.java
+++ b/services/core/java/com/android/server/integrity/model/BitOutputStream.java
@@ -16,17 +16,26 @@
 
 package com.android.server.integrity.model;
 
-import java.util.BitSet;
+import static com.android.server.integrity.model.ComponentBitSize.BYTE_BITS;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Arrays;
 
 /** A wrapper class for writing a stream of bits. */
 public class BitOutputStream {
 
-    private BitSet mBitSet;
-    private int mIndex;
+    private static final int BUFFER_SIZE = 4 * 1024;
 
-    public BitOutputStream() {
-        mBitSet = new BitSet();
-        mIndex = 0;
+    private int mNextBitIndex;
+
+    private final OutputStream mOutputStream;
+    private final byte[] mBuffer;
+
+    public BitOutputStream(OutputStream outputStream) {
+        mBuffer = new byte[BUFFER_SIZE];
+        mNextBitIndex = 0;
+        mOutputStream = outputStream;
     }
 
     /**
@@ -35,15 +44,17 @@
      * @param numOfBits The number of bits used to represent the value.
      * @param value The value to convert to bits.
      */
-    public void setNext(int numOfBits, int value) {
+    public void setNext(int numOfBits, int value) throws IOException {
         if (numOfBits <= 0) {
             return;
         }
-        int offset = 1 << (numOfBits - 1);
+
+        // optional: we can do some clever size checking to "OR" an entire segment of bits instead
+        // of setting bits one by one, but it is probably not worth it.
+        int nextBitMask = 1 << (numOfBits - 1);
         while (numOfBits-- > 0) {
-            mBitSet.set(mIndex, (value & offset) != 0);
-            offset >>>= 1;
-            mIndex++;
+            setNext((value & nextBitMask) != 0);
+            nextBitMask >>>= 1;
         }
     }
 
@@ -52,35 +63,43 @@
      *
      * @param value The value to set the bit to.
      */
-    public void setNext(boolean value) {
-        mBitSet.set(mIndex, value);
-        mIndex++;
+    public void setNext(boolean value) throws IOException {
+        int byteToWrite = mNextBitIndex / BYTE_BITS;
+        if (byteToWrite == BUFFER_SIZE) {
+            mOutputStream.write(mBuffer);
+            reset();
+            byteToWrite = 0;
+        }
+        if (value) {
+            mBuffer[byteToWrite] |= 1 << (BYTE_BITS - 1 - (mNextBitIndex % BYTE_BITS));
+        }
+        mNextBitIndex++;
     }
 
     /** Set the next bit in the stream to true. */
-    public void setNext() {
+    public void setNext() throws IOException {
         setNext(/* value= */ true);
     }
 
-    /** Convert BitSet in big-endian to ByteArray in big-endian. */
-    public byte[] toByteArray() {
-        int bitSetSize = mBitSet.length();
-        int numOfBytes = bitSetSize / 8;
-        if (bitSetSize % 8 != 0) {
-            numOfBytes++;
+    /**
+     * Flush the data written to the underlying {@link java.io.OutputStream}. Any unfinished bytes
+     * will be padded with 0.
+     */
+    public void flush() throws IOException {
+        int endByte = mNextBitIndex / BYTE_BITS;
+        if (mNextBitIndex % BYTE_BITS != 0) {
+            // If next bit is not the first bit of a byte, then mNextBitIndex / BYTE_BITS would be
+            // the byte that includes already written bits. We need to increment it so this byte
+            // gets written.
+            endByte++;
         }
-        byte[] bytes = new byte[numOfBytes];
-        for (int i = 0; i < mBitSet.length(); i++) {
-            if (mBitSet.get(i)) {
-                bytes[i / 8] |= 1 << (7 - (i % 8));
-            }
-        }
-        return bytes;
+        mOutputStream.write(mBuffer, 0, endByte);
+        reset();
     }
 
-    /** Clear the stream. */
-    public void clear() {
-        mBitSet.clear();
-        mIndex = 0;
+    /** Reset this output stream to start state. */
+    private void reset() {
+        mNextBitIndex = 0;
+        Arrays.fill(mBuffer, (byte) 0);
     }
 }
diff --git a/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java b/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java
new file mode 100644
index 0000000..ceed054
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java
@@ -0,0 +1,63 @@
+/*
+ * 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.integrity.model;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * An output stream that tracks the total number written bytes since construction and allows
+ * querying this value any time during the execution.
+ *
+ * <p>This class is used for constructing the rule indexing.
+ */
+public class ByteTrackedOutputStream extends OutputStream {
+
+    private int mWrittenBytesCount;
+    private final OutputStream mOutputStream;
+
+    public ByteTrackedOutputStream(OutputStream outputStream) {
+        mWrittenBytesCount = 0;
+        mOutputStream = outputStream;
+    }
+
+    @Override
+    public void write(int b) throws IOException {
+        mWrittenBytesCount++;
+        mOutputStream.write(b);
+    }
+
+    /**
+     * Writes the given bytes into the output stream provided in constructor and updates the total
+     * number of written bytes.
+     */
+    @Override
+    public void write(byte[] bytes) throws IOException {
+        write(bytes, 0, bytes.length);
+    }
+
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException {
+        mWrittenBytesCount += len;
+        mOutputStream.write(b, off, len);
+    }
+
+    /** Returns the total number of bytes written into the output stream at the requested time. */
+    public int getWrittenBytesCount() {
+        return mWrittenBytesCount;
+    }
+}
diff --git a/services/core/java/com/android/server/integrity/model/ComponentBitSize.java b/services/core/java/com/android/server/integrity/model/ComponentBitSize.java
index 6ec2d5f..c389963 100644
--- a/services/core/java/com/android/server/integrity/model/ComponentBitSize.java
+++ b/services/core/java/com/android/server/integrity/model/ComponentBitSize.java
@@ -23,13 +23,14 @@
  * components.
  */
 public final class ComponentBitSize {
-    public static final int FORMAT_VERSION_BITS = 5;
+    public static final int FORMAT_VERSION_BITS = 8;
+
     public static final int EFFECT_BITS = 3;
     public static final int KEY_BITS = 4;
     public static final int OPERATOR_BITS = 3;
     public static final int CONNECTOR_BITS = 2;
     public static final int SEPARATOR_BITS = 2;
-    public static final int VALUE_SIZE_BITS = 6;
+    public static final int VALUE_SIZE_BITS = 8;
     public static final int IS_HASHED_BITS = 1;
 
     public static final int ATOMIC_FORMULA_START = 0;
@@ -38,4 +39,6 @@
 
     public static final int DEFAULT_FORMAT_VERSION = 1;
     public static final int SIGNAL_BIT = 1;
+
+    public static final int BYTE_BITS = 8;
 }
diff --git a/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java b/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java
index 52df89870..0c4052a 100644
--- a/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java
+++ b/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java
@@ -18,9 +18,9 @@
 
 /**  A helper class containing special indexing file constants. */
 public final class IndexingFileConstants {
-    // The parsing time seems acceptable for this block size based on the tests in
-    // go/ic-rule-file-format.
-    public static final int INDEXING_BLOCK_SIZE = 100;
+    // We empirically experimented with different block sizes and identified that 50 is in the
+    // optimal range of efficient computation.
+    public static final int INDEXING_BLOCK_SIZE = 50;
 
     public static final String START_INDEXING_KEY = "START_KEY";
     public static final String END_INDEXING_KEY = "END_KEY";
diff --git a/services/core/java/com/android/server/integrity/parser/LimitInputStream.java b/services/core/java/com/android/server/integrity/parser/LimitInputStream.java
new file mode 100644
index 0000000..a91bbb7
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/parser/LimitInputStream.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.integrity.parser;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/** An {@link InputStream} that basically truncates another {@link InputStream} */
+public class LimitInputStream extends FilterInputStream {
+    private int mReadBytes;
+    private final int mLimit;
+
+    public LimitInputStream(InputStream in, int limit) {
+        super(in);
+        if (limit < 0) {
+            throw new IllegalArgumentException("limit " + limit + " cannot be negative");
+        }
+        mReadBytes = 0;
+        mLimit = limit;
+    }
+
+    @Override
+    public int available() throws IOException {
+        return Math.min(super.available(), mLimit - mReadBytes);
+    }
+
+    @Override
+    public int read() throws IOException {
+        if (mReadBytes == mLimit) {
+            return -1;
+        }
+        mReadBytes++;
+        return super.read();
+    }
+
+    @Override
+    public int read(byte[] b) throws IOException {
+        return read(b, 0, b.length);
+    }
+
+    @Override
+    public int read(byte[] b, int off, int len) throws IOException {
+        if (len <= 0) {
+            return 0;
+        }
+        int available = available();
+        if (available <= 0) {
+            return -1;
+        }
+        int result = super.read(b, off, Math.min(len, available));
+        mReadBytes += result;
+        return result;
+    }
+
+    @Override
+    public long skip(long n) throws IOException {
+        if (n <= 0) {
+            return 0;
+        }
+        int available = available();
+        if (available <= 0) {
+            return 0;
+        }
+        int bytesToSkip = (int) Math.min(available, n);
+        long bytesSkipped = super.skip(bytesToSkip);
+        mReadBytes += (int) bytesSkipped;
+        return bytesSkipped;
+    }
+}
diff --git a/services/core/java/com/android/server/integrity/parser/RandomAccessInputStream.java b/services/core/java/com/android/server/integrity/parser/RandomAccessInputStream.java
new file mode 100644
index 0000000..206e6a1
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/parser/RandomAccessInputStream.java
@@ -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.server.integrity.parser;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/** A wrapper around {@link RandomAccessObject} to turn it into a {@link InputStream}. */
+public class RandomAccessInputStream extends InputStream {
+
+    private final RandomAccessObject mRandomAccessObject;
+
+    private int mPosition;
+
+    public RandomAccessInputStream(RandomAccessObject object) throws IOException {
+        mRandomAccessObject = object;
+        mPosition = 0;
+    }
+
+    /** Returns the position of the file pointer. */
+    public int getPosition() {
+        return mPosition;
+    }
+
+    /** See {@link RandomAccessObject#seek(int)} */
+    public void seek(int position) throws IOException {
+        mRandomAccessObject.seek(position);
+        mPosition = position;
+    }
+
+    @Override
+    public int available() throws IOException {
+        return mRandomAccessObject.length() - mPosition;
+    }
+
+    @Override
+    public void close() throws IOException {
+        mRandomAccessObject.close();
+    }
+
+    @Override
+    public int read() throws IOException {
+        if (available() <= 0) {
+            return -1;
+        }
+        mPosition++;
+        return mRandomAccessObject.read();
+    }
+
+    @Override
+    public int read(byte[] b) throws IOException {
+        return read(b, 0, b.length);
+    }
+
+    @Override
+    public int read(byte[] b, int off, int len) throws IOException {
+        if (len <= 0) {
+            return 0;
+        }
+        int available = available();
+        if (available <= 0) {
+            return -1;
+        }
+        int result = mRandomAccessObject.read(b, off, Math.min(len, available));
+        mPosition += result;
+        return result;
+    }
+
+    @Override
+    public long skip(long n) throws IOException {
+        if (n <= 0) {
+            return 0;
+        }
+        int available = available();
+        if (available <= 0) {
+            return 0;
+        }
+        int skipAmount = (int) Math.min(available, n);
+        mPosition += skipAmount;
+        mRandomAccessObject.seek(mPosition);
+        return skipAmount;
+    }
+}
diff --git a/services/core/java/com/android/server/integrity/parser/RandomAccessObject.java b/services/core/java/com/android/server/integrity/parser/RandomAccessObject.java
new file mode 100644
index 0000000..d9b2e38
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/parser/RandomAccessObject.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.server.integrity.parser;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+
+/** An interface for random access objects like RandomAccessFile or byte arrays. */
+public abstract class RandomAccessObject {
+
+    /** See {@link RandomAccessFile#seek(long)}. */
+    public abstract void seek(int position) throws IOException;
+
+    /** See {@link RandomAccessFile#read()}. */
+    public abstract int read() throws IOException;
+
+    /** See {@link RandomAccessFile#read(byte[], int, int)}. */
+    public abstract int read(byte[] bytes, int off, int len) throws IOException;
+
+    /** See {@link RandomAccessFile#close()}. */
+    public abstract void close() throws IOException;
+
+    /** See {@link java.io.RandomAccessFile#length()}. */
+    public abstract int length();
+
+    /** Static constructor from a file. */
+    public static RandomAccessObject ofFile(File file) throws IOException {
+        return new RandomAccessFileObject(file);
+    }
+
+    /** Static constructor from a byte array. */
+    public static RandomAccessObject ofBytes(byte[] bytes) {
+        return new RandomAccessByteArrayObject(bytes);
+    }
+
+    private static class RandomAccessFileObject extends RandomAccessObject {
+        private final RandomAccessFile mRandomAccessFile;
+        // We cache the length since File.length() invokes file IO.
+        private final int mLength;
+
+        RandomAccessFileObject(File file) throws IOException {
+            long length = file.length();
+            if (length > Integer.MAX_VALUE) {
+                throw new IOException("Unsupported file size (too big) " + length);
+            }
+
+            mRandomAccessFile = new RandomAccessFile(file, /* mode= */ "r");
+            mLength = (int) length;
+        }
+
+        @Override
+        public void seek(int position) throws IOException {
+            mRandomAccessFile.seek(position);
+        }
+
+        @Override
+        public int read() throws IOException {
+            return mRandomAccessFile.read();
+        }
+
+        @Override
+        public int read(byte[] bytes, int off, int len) throws IOException {
+            return mRandomAccessFile.read(bytes, off, len);
+        }
+
+        @Override
+        public void close() throws IOException {
+            mRandomAccessFile.close();
+        }
+
+        @Override
+        public int length() {
+            return mLength;
+        }
+    }
+
+    private static class RandomAccessByteArrayObject extends RandomAccessObject {
+
+        private final ByteBuffer mBytes;
+
+        RandomAccessByteArrayObject(byte[] bytes) {
+            mBytes = ByteBuffer.wrap(bytes);
+        }
+
+        @Override
+        public void seek(int position) throws IOException {
+            mBytes.position(position);
+        }
+
+        @Override
+        public int read() throws IOException {
+            if (!mBytes.hasRemaining()) {
+                return -1;
+            }
+
+            return mBytes.get() & 0xFF;
+        }
+
+        @Override
+        public int read(byte[] bytes, int off, int len) throws IOException {
+            int bytesToCopy = Math.min(len, mBytes.remaining());
+            if (bytesToCopy <= 0) {
+                return 0;
+            }
+            mBytes.get(bytes, off, len);
+            return bytesToCopy;
+        }
+
+        @Override
+        public void close() throws IOException {}
+
+        @Override
+        public int length() {
+            return mBytes.capacity();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
index cbb6e4e..90954ff 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
@@ -17,6 +17,7 @@
 package com.android.server.integrity.parser;
 
 import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START;
+import static com.android.server.integrity.model.ComponentBitSize.BYTE_BITS;
 import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END;
 import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START;
 import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS;
@@ -39,45 +40,80 @@
 
 import com.android.server.integrity.model.BitInputStream;
 
+import java.io.BufferedInputStream;
 import java.io.IOException;
-import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /** A helper class to parse rules into the {@link Rule} model from Binary representation. */
 public class RuleBinaryParser implements RuleParser {
 
-    private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
-
     @Override
     public List<Rule> parse(byte[] ruleBytes) throws RuleParseException {
-        try {
-            BitInputStream bitInputStream = new BitInputStream(ruleBytes);
-            return parseRules(bitInputStream);
-        } catch (Exception e) {
-            throw new RuleParseException(e.getMessage(), e);
-        }
+        return parse(RandomAccessObject.ofBytes(ruleBytes), Collections.emptyList());
     }
 
     @Override
-    public List<Rule> parse(InputStream inputStream) throws RuleParseException {
-        try {
-            BitInputStream bitInputStream = new BitInputStream(inputStream);
-            return parseRules(bitInputStream);
+    public List<Rule> parse(RandomAccessObject randomAccessObject, List<RuleIndexRange> indexRanges)
+            throws RuleParseException {
+        try (RandomAccessInputStream randomAccessInputStream =
+                new RandomAccessInputStream(randomAccessObject)) {
+            return parseRules(randomAccessInputStream, indexRanges);
         } catch (Exception e) {
             throw new RuleParseException(e.getMessage(), e);
         }
     }
 
-    private List<Rule> parseRules(BitInputStream bitInputStream) throws IOException {
-        List<Rule> parsedRules = new ArrayList<>();
+    private List<Rule> parseRules(
+            RandomAccessInputStream randomAccessInputStream,
+            List<RuleIndexRange> indexRanges)
+            throws IOException {
 
         // Read the rule binary file format version.
-        bitInputStream.getNext(FORMAT_VERSION_BITS);
+        randomAccessInputStream.skip(FORMAT_VERSION_BITS / BYTE_BITS);
 
-        while (bitInputStream.hasNext()) {
-            if (bitInputStream.getNext(SIGNAL_BIT) == 1) {
-                parsedRules.add(parseRule(bitInputStream));
+        return indexRanges.isEmpty()
+                ? parseAllRules(randomAccessInputStream)
+                : parseIndexedRules(randomAccessInputStream, indexRanges);
+    }
+
+    private List<Rule> parseAllRules(RandomAccessInputStream randomAccessInputStream)
+            throws IOException {
+        List<Rule> parsedRules = new ArrayList<>();
+
+        BitInputStream inputStream =
+                new BitInputStream(new BufferedInputStream(randomAccessInputStream));
+        while (inputStream.hasNext()) {
+            if (inputStream.getNext(SIGNAL_BIT) == 1) {
+                parsedRules.add(parseRule(inputStream));
+            }
+        }
+
+        return parsedRules;
+    }
+
+    private List<Rule> parseIndexedRules(
+            RandomAccessInputStream randomAccessInputStream,
+            List<RuleIndexRange> indexRanges)
+            throws IOException {
+        List<Rule> parsedRules = new ArrayList<>();
+
+        for (RuleIndexRange range : indexRanges) {
+            randomAccessInputStream.seek(range.getStartIndex());
+
+            BitInputStream inputStream =
+                    new BitInputStream(
+                            new BufferedInputStream(
+                                    new LimitInputStream(
+                                            randomAccessInputStream,
+                                            range.getEndIndex() - range.getStartIndex())));
+
+            // Read the rules until we reach the end index. available() here is not reliable.
+            while (inputStream.hasNext()) {
+                if (inputStream.getNext(SIGNAL_BIT) == 1) {
+                    parsedRules.add(parseRule(inputStream));
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java b/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java
index 8c8450e..595a035 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java
@@ -23,28 +23,33 @@
  * RuleIndexingController}.
  */
 public class RuleIndexRange {
-    private static int sStartIndex;
-    private static int sEndIndex;
+    private int mStartIndex;
+    private int mEndIndex;
 
     /** Constructor with start and end indexes. */
     public RuleIndexRange(int startIndex, int endIndex) {
-        this.sStartIndex = startIndex;
-        this.sEndIndex = endIndex;
+        this.mStartIndex = startIndex;
+        this.mEndIndex = endIndex;
     }
 
     /** Returns the startIndex. */
     public int getStartIndex() {
-        return sStartIndex;
+        return mStartIndex;
     }
 
     /** Returns the end index. */
     public int getEndIndex() {
-        return sEndIndex;
+        return mEndIndex;
     }
 
     @Override
     public boolean equals(@Nullable Object object) {
-        return sStartIndex == ((RuleIndexRange) object).getStartIndex()
-                && sEndIndex == ((RuleIndexRange) object).getEndIndex();
+        return mStartIndex == ((RuleIndexRange) object).getStartIndex()
+                && mEndIndex == ((RuleIndexRange) object).getEndIndex();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("Range{%d, %d}", mStartIndex, mEndIndex);
     }
 }
diff --git a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java
index c971322..87eee4e 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java
@@ -28,9 +28,9 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.TreeSet;
 import java.util.stream.Collectors;
 
 /** Helper class to identify the necessary indexes that needs to be read. */
@@ -56,7 +56,7 @@
      * read and evaluated.
      */
     public List<RuleIndexRange> identifyRulesToEvaluate(AppInstallMetadata appInstallMetadata) {
-        ArrayList<RuleIndexRange> indexRanges = new ArrayList();
+        List<RuleIndexRange> indexRanges = new ArrayList<>();
 
         // Add the range for package name indexes rules.
         indexRanges.add(
@@ -93,19 +93,28 @@
         return keyToIndexMap;
     }
 
-    private RuleIndexRange searchIndexingKeysRangeContainingKey(
+    private static RuleIndexRange searchIndexingKeysRangeContainingKey(
             LinkedHashMap<String, Integer> indexMap, String searchedKey) {
-        TreeSet<String> keyTreeSet =
-                indexMap.keySet().stream()
-                        .filter(key -> !key.matches(START_INDEXING_KEY) && !key.matches(
-                                END_INDEXING_KEY))
-                        .collect(Collectors.toCollection(TreeSet::new));
-
-        String minIndex = keyTreeSet.floor(searchedKey);
-        String maxIndex = keyTreeSet.ceiling(searchedKey);
-
+        List<String> keys = indexMap.keySet().stream().collect(Collectors.toList());
+        List<String> identifiedKeyRange =
+                searchKeysRangeContainingKey(keys, searchedKey, 0, keys.size() - 1);
         return new RuleIndexRange(
-                indexMap.get(minIndex == null ? START_INDEXING_KEY : minIndex),
-                indexMap.get(maxIndex == null ? END_INDEXING_KEY : maxIndex));
+                indexMap.get(identifiedKeyRange.get(0)), indexMap.get(identifiedKeyRange.get(1)));
+    }
+
+    private static List<String> searchKeysRangeContainingKey(
+            List<String> sortedKeyList, String key, int startIndex, int endIndex) {
+        if (endIndex - startIndex == 1) {
+            return Arrays.asList(sortedKeyList.get(startIndex), sortedKeyList.get(endIndex));
+        }
+
+        int midKeyIndex = startIndex + ((endIndex - startIndex) / 2);
+        String midKey = sortedKeyList.get(midKeyIndex);
+
+        if (key.compareTo(midKey) >= 0) {
+            return searchKeysRangeContainingKey(sortedKeyList, key, midKeyIndex, endIndex);
+        } else {
+            return searchKeysRangeContainingKey(sortedKeyList, key, startIndex, midKeyIndex);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/integrity/parser/RuleParser.java b/services/core/java/com/android/server/integrity/parser/RuleParser.java
index 81783d5..126dacc 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleParser.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleParser.java
@@ -18,7 +18,6 @@
 
 import android.content.integrity.Rule;
 
-import java.io.InputStream;
 import java.util.List;
 
 /** A helper class to parse rules into the {@link Rule} model. */
@@ -28,5 +27,6 @@
     List<Rule> parse(byte[] ruleBytes) throws RuleParseException;
 
     /** Parse rules from an input stream. */
-    List<Rule> parse(InputStream inputStream) throws RuleParseException;
+    List<Rule> parse(RandomAccessObject randomAccessObject, List<RuleIndexRange> ruleIndexRanges)
+            throws RuleParseException;
 }
diff --git a/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java b/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java
index d405583..53b0c2e 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java
@@ -26,7 +26,6 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
-import java.io.InputStream;
 import java.io.StringReader;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
@@ -62,10 +61,13 @@
     }
 
     @Override
-    public List<Rule> parse(InputStream inputStream) throws RuleParseException {
+    public List<Rule> parse(RandomAccessObject randomAccessObject, List<RuleIndexRange> indexRanges)
+            throws RuleParseException {
         try {
             XmlPullParser xmlPullParser = Xml.newPullParser();
-            xmlPullParser.setInput(inputStream, StandardCharsets.UTF_8.name());
+            xmlPullParser.setInput(
+                    new RandomAccessInputStream(randomAccessObject),
+                    StandardCharsets.UTF_8.name());
             return parseRules(xmlPullParser);
         } catch (Exception e) {
             throw new RuleParseException(e.getMessage(), e);
diff --git a/services/core/java/com/android/server/integrity/serializer/ByteTrackedOutputStream.java b/services/core/java/com/android/server/integrity/serializer/ByteTrackedOutputStream.java
deleted file mode 100644
index 62815a9..0000000
--- a/services/core/java/com/android/server/integrity/serializer/ByteTrackedOutputStream.java
+++ /dev/null
@@ -1,53 +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.server.integrity.serializer;
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-/**
- * An output stream that tracks the total number written bytes since construction and allows
- * querying this value any time during the execution.
- *
- * This class is used for constructing the rule indexing.
- */
-public class ByteTrackedOutputStream {
-
-    private static int sWrittenBytesCount;
-    private static OutputStream sOutputStream;
-
-    public ByteTrackedOutputStream(OutputStream outputStream) {
-        sWrittenBytesCount = 0;
-        sOutputStream = outputStream;
-    }
-
-    /**
-     * Writes the given bytes into the output stream provided in constructor and updates the
-     * total number of written bytes.
-     */
-    public void write(byte[] bytes) throws IOException {
-        sWrittenBytesCount += bytes.length;
-        sOutputStream.write(bytes);
-    }
-
-    /**
-     * Returns the total number of bytes written into the output stream at the requested time.
-     */
-    public int getWrittenBytesCount() {
-        return sWrittenBytesCount;
-    }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
index b8791c3..f5ed975 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
@@ -42,18 +42,23 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.integrity.IntegrityUtils;
 import com.android.server.integrity.model.BitOutputStream;
+import com.android.server.integrity.model.ByteTrackedOutputStream;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.nio.charset.StandardCharsets;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
-import java.util.TreeMap;
+import java.util.stream.Collectors;
 
 /** A helper class to serialize rules from the {@link Rule} model to Binary representation. */
 public class RuleBinarySerializer implements RuleSerializer {
+    static final int TOTAL_RULE_SIZE_LIMIT = 200000;
+    static final int INDEXED_RULE_SIZE_LIMIT = 100000;
+    static final int NONINDEXED_RULE_SIZE_LIMIT = 1000;
 
     // Get the byte representation for a list of rules.
     @Override
@@ -77,67 +82,93 @@
             OutputStream indexingFileOutputStream)
             throws RuleSerializeException {
         try {
+            if (rules == null) {
+                throw new IllegalArgumentException("Null rules cannot be serialized.");
+            }
+
+            if (rules.size() > TOTAL_RULE_SIZE_LIMIT) {
+                throw new IllegalArgumentException("Too many rules provided.");
+            }
+
             // Determine the indexing groups and the order of the rules within each indexed group.
-            Map<Integer, TreeMap<String, List<Rule>>> indexedRules =
+            Map<Integer, Map<String, List<Rule>>> indexedRules =
                     RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets(rules);
 
+            // Validate the rule blocks are not larger than expected limits.
+            verifySize(indexedRules.get(PACKAGE_NAME_INDEXED), INDEXED_RULE_SIZE_LIMIT);
+            verifySize(indexedRules.get(APP_CERTIFICATE_INDEXED), INDEXED_RULE_SIZE_LIMIT);
+            verifySize(indexedRules.get(NOT_INDEXED), NONINDEXED_RULE_SIZE_LIMIT);
+
+            // Serialize the rules.
             ByteTrackedOutputStream ruleFileByteTrackedOutputStream =
                     new ByteTrackedOutputStream(rulesFileOutputStream);
-
             serializeRuleFileMetadata(formatVersion, ruleFileByteTrackedOutputStream);
-
-            Map<String, Integer> packageNameIndexes =
-                    serializeRuleList(indexedRules.get(PACKAGE_NAME_INDEXED),
+            LinkedHashMap<String, Integer> packageNameIndexes =
+                    serializeRuleList(
+                            indexedRules.get(PACKAGE_NAME_INDEXED),
                             ruleFileByteTrackedOutputStream);
-            indexingFileOutputStream.write(
-                    serializeIndexes(packageNameIndexes, /* isIndexed= */true));
-
-            Map<String, Integer> appCertificateIndexes =
-                    serializeRuleList(indexedRules.get(APP_CERTIFICATE_INDEXED),
+            LinkedHashMap<String, Integer> appCertificateIndexes =
+                    serializeRuleList(
+                            indexedRules.get(APP_CERTIFICATE_INDEXED),
                             ruleFileByteTrackedOutputStream);
-            indexingFileOutputStream.write(
-                    serializeIndexes(appCertificateIndexes, /* isIndexed= */true));
+            LinkedHashMap<String, Integer> unindexedRulesIndexes =
+                    serializeRuleList(
+                            indexedRules.get(NOT_INDEXED), ruleFileByteTrackedOutputStream);
 
-            Map<String, Integer> unindexedRulesIndexes =
-                    serializeRuleList(indexedRules.get(NOT_INDEXED),
-                            ruleFileByteTrackedOutputStream);
-            indexingFileOutputStream.write(
-                    serializeIndexes(unindexedRulesIndexes, /* isIndexed= */false));
+            // Serialize their indexes.
+            BitOutputStream indexingBitOutputStream = new BitOutputStream(indexingFileOutputStream);
+            serializeIndexGroup(packageNameIndexes, indexingBitOutputStream, /* isIndexed= */ true);
+            serializeIndexGroup(
+                    appCertificateIndexes, indexingBitOutputStream, /* isIndexed= */ true);
+            serializeIndexGroup(
+                    unindexedRulesIndexes, indexingBitOutputStream, /* isIndexed= */ false);
+            indexingBitOutputStream.flush();
         } catch (Exception e) {
             throw new RuleSerializeException(e.getMessage(), e);
         }
     }
 
-    private void serializeRuleFileMetadata(Optional<Integer> formatVersion,
-            ByteTrackedOutputStream outputStream) throws IOException {
-        int formatVersionValue = formatVersion.orElse(DEFAULT_FORMAT_VERSION);
-
-        BitOutputStream bitOutputStream = new BitOutputStream();
-        bitOutputStream.setNext(FORMAT_VERSION_BITS, formatVersionValue);
-        outputStream.write(bitOutputStream.toByteArray());
+    private void verifySize(Map<String, List<Rule>> ruleListMap, int ruleSizeLimit) {
+        int totalRuleCount =
+                ruleListMap.values().stream()
+                        .map(list -> list.size())
+                        .collect(Collectors.summingInt(Integer::intValue));
+        if (totalRuleCount > ruleSizeLimit) {
+            throw new IllegalArgumentException("Too many rules provided in the indexing group.");
+        }
     }
 
-    private Map<String, Integer> serializeRuleList(TreeMap<String, List<Rule>> rulesMap,
-            ByteTrackedOutputStream outputStream)
+    private void serializeRuleFileMetadata(
+            Optional<Integer> formatVersion, ByteTrackedOutputStream outputStream)
             throws IOException {
-        Preconditions.checkArgument(rulesMap != null,
-                "serializeRuleList should never be called with null rule list.");
+        int formatVersionValue = formatVersion.orElse(DEFAULT_FORMAT_VERSION);
 
-        BitOutputStream bitOutputStream = new BitOutputStream();
-        Map<String, Integer> indexMapping = new TreeMap();
-        int indexTracker = 0;
+        BitOutputStream bitOutputStream = new BitOutputStream(outputStream);
+        bitOutputStream.setNext(FORMAT_VERSION_BITS, formatVersionValue);
+        bitOutputStream.flush();
+    }
 
+    private LinkedHashMap<String, Integer> serializeRuleList(
+            Map<String, List<Rule>> rulesMap, ByteTrackedOutputStream outputStream)
+            throws IOException {
+        Preconditions.checkArgument(
+                rulesMap != null, "serializeRuleList should never be called with null rule list.");
+
+        BitOutputStream bitOutputStream = new BitOutputStream(outputStream);
+        LinkedHashMap<String, Integer> indexMapping = new LinkedHashMap();
         indexMapping.put(START_INDEXING_KEY, outputStream.getWrittenBytesCount());
-        for (Map.Entry<String, List<Rule>> entry : rulesMap.entrySet()) {
+
+        List<String> sortedKeys = rulesMap.keySet().stream().sorted().collect(Collectors.toList());
+        int indexTracker = 0;
+        for (String key : sortedKeys) {
             if (indexTracker >= INDEXING_BLOCK_SIZE) {
-                indexMapping.put(entry.getKey(), outputStream.getWrittenBytesCount());
+                indexMapping.put(key, outputStream.getWrittenBytesCount());
                 indexTracker = 0;
             }
 
-            for (Rule rule : entry.getValue()) {
-                bitOutputStream.clear();
+            for (Rule rule : rulesMap.get(key)) {
                 serializeRule(rule, bitOutputStream);
-                outputStream.write(bitOutputStream.toByteArray());
+                bitOutputStream.flush();
                 indexTracker++;
             }
         }
@@ -146,7 +177,7 @@
         return indexMapping;
     }
 
-    private void serializeRule(Rule rule, BitOutputStream bitOutputStream) {
+    private void serializeRule(Rule rule, BitOutputStream bitOutputStream) throws IOException {
         if (rule == null) {
             throw new IllegalArgumentException("Null rule can not be serialized");
         }
@@ -161,7 +192,8 @@
         bitOutputStream.setNext();
     }
 
-    private void serializeFormula(Formula formula, BitOutputStream bitOutputStream) {
+    private void serializeFormula(Formula formula, BitOutputStream bitOutputStream)
+            throws IOException {
         if (formula instanceof AtomicFormula) {
             serializeAtomicFormula((AtomicFormula) formula, bitOutputStream);
         } else if (formula instanceof CompoundFormula) {
@@ -173,7 +205,7 @@
     }
 
     private void serializeCompoundFormula(
-            CompoundFormula compoundFormula, BitOutputStream bitOutputStream) {
+            CompoundFormula compoundFormula, BitOutputStream bitOutputStream) throws IOException {
         if (compoundFormula == null) {
             throw new IllegalArgumentException("Null compound formula can not be serialized");
         }
@@ -187,7 +219,7 @@
     }
 
     private void serializeAtomicFormula(
-            AtomicFormula atomicFormula, BitOutputStream bitOutputStream) {
+            AtomicFormula atomicFormula, BitOutputStream bitOutputStream) throws IOException {
         if (atomicFormula == null) {
             throw new IllegalArgumentException("Null atomic formula can not be serialized");
         }
@@ -218,12 +250,13 @@
         }
     }
 
-    private byte[] serializeIndexes(Map<String, Integer> indexes, boolean isIndexed) {
-        BitOutputStream bitOutputStream = new BitOutputStream();
-
+    private void serializeIndexGroup(
+            LinkedHashMap<String, Integer> indexes,
+            BitOutputStream bitOutputStream,
+            boolean isIndexed)
+            throws IOException {
         // Output the starting location of this indexing group.
-        serializeStringValue(START_INDEXING_KEY, /* isHashedValue= */false,
-                bitOutputStream);
+        serializeStringValue(START_INDEXING_KEY, /* isHashedValue= */ false, bitOutputStream);
         serializeIntValue(indexes.get(START_INDEXING_KEY), bitOutputStream);
 
         // If the group is indexed, output the locations of the indexes.
@@ -231,8 +264,8 @@
             for (Map.Entry<String, Integer> entry : indexes.entrySet()) {
                 if (!entry.getKey().equals(START_INDEXING_KEY)
                         && !entry.getKey().equals(END_INDEXING_KEY)) {
-                    serializeStringValue(entry.getKey(), /* isHashedValue= */false,
-                            bitOutputStream);
+                    serializeStringValue(
+                            entry.getKey(), /* isHashedValue= */ false, bitOutputStream);
                     serializeIntValue(entry.getValue(), bitOutputStream);
                 }
             }
@@ -241,12 +274,11 @@
         // Output the end location of this indexing group.
         serializeStringValue(END_INDEXING_KEY, /*isHashedValue= */ false, bitOutputStream);
         serializeIntValue(indexes.get(END_INDEXING_KEY), bitOutputStream);
-
-        return bitOutputStream.toByteArray();
     }
 
     private void serializeStringValue(
-            String value, boolean isHashedValue, BitOutputStream bitOutputStream) {
+            String value, boolean isHashedValue, BitOutputStream bitOutputStream)
+            throws IOException {
         if (value == null) {
             throw new IllegalArgumentException("String value can not be null.");
         }
@@ -259,11 +291,12 @@
         }
     }
 
-    private void serializeIntValue(int value, BitOutputStream bitOutputStream) {
+    private void serializeIntValue(int value, BitOutputStream bitOutputStream) throws IOException {
         bitOutputStream.setNext(/* numOfBits= */ 32, value);
     }
 
-    private void serializeBooleanValue(boolean value, BitOutputStream bitOutputStream) {
+    private void serializeBooleanValue(boolean value, BitOutputStream bitOutputStream)
+            throws IOException {
         bitOutputStream.setNext(value);
     }
 
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java
index dd871e2..2cbd4ede 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java
@@ -28,6 +28,8 @@
     static final int PACKAGE_NAME_INDEXED = 1;
     static final int APP_CERTIFICATE_INDEXED = 2;
 
+    static final String DEFAULT_RULE_KEY = "N/A";
+
     /** Represents which indexed file the rule should be located. */
     @IntDef(
             value = {
@@ -45,7 +47,7 @@
     /** Constructor without a ruleKey for {@code NOT_INDEXED}. */
     RuleIndexingDetails(@IndexType int indexType) {
         this.mIndexType = indexType;
-        this.mRuleKey = null;
+        this.mRuleKey = DEFAULT_RULE_KEY;
     }
 
     /** Constructor with a ruleKey for indexed rules. */
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
index cbc365e..7d9a901 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
@@ -30,30 +30,27 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
-import java.util.TreeMap;
 
 /** A helper class for identifying the indexing type and key of a given rule. */
 class RuleIndexingDetailsIdentifier {
 
-    private static final String DEFAULT_RULE_KEY = "N/A";
-
     /**
      * Splits a given rule list into three indexing categories. Each rule category is returned as a
      * TreeMap that is sorted by their indexing keys -- where keys correspond to package name for
      * PACKAGE_NAME_INDEXED rules, app certificate for APP_CERTIFICATE_INDEXED rules and N/A for
      * NOT_INDEXED rules.
      */
-    public static Map<Integer, TreeMap<String, List<Rule>>> splitRulesIntoIndexBuckets(
+    public static Map<Integer, Map<String, List<Rule>>> splitRulesIntoIndexBuckets(
             List<Rule> rules) {
         if (rules == null) {
             throw new IllegalArgumentException(
                     "Index buckets cannot be created for null rule list.");
         }
 
-        Map<Integer, TreeMap<String, List<Rule>>> typeOrganizedRuleMap = new HashMap();
-        typeOrganizedRuleMap.put(NOT_INDEXED, new TreeMap());
-        typeOrganizedRuleMap.put(PACKAGE_NAME_INDEXED, new TreeMap());
-        typeOrganizedRuleMap.put(APP_CERTIFICATE_INDEXED, new TreeMap());
+        Map<Integer, Map<String, List<Rule>>> typeOrganizedRuleMap = new HashMap();
+        typeOrganizedRuleMap.put(NOT_INDEXED, new HashMap());
+        typeOrganizedRuleMap.put(PACKAGE_NAME_INDEXED, new HashMap<>());
+        typeOrganizedRuleMap.put(APP_CERTIFICATE_INDEXED, new HashMap<>());
 
         // Split the rules into the appropriate indexed pattern. The Tree Maps help us to keep the
         // entries sorted by their index key.
@@ -66,21 +63,14 @@
                         String.format("Malformed rule identified. [%s]", rule.toString()));
             }
 
-            String ruleKey =
-                    indexingDetails.getIndexType() != NOT_INDEXED
-                            ? indexingDetails.getRuleKey()
-                            : DEFAULT_RULE_KEY;
+            int ruleIndexType = indexingDetails.getIndexType();
+            String ruleKey = indexingDetails.getRuleKey();
 
-            if (!typeOrganizedRuleMap.get(indexingDetails.getIndexType()).containsKey(ruleKey)) {
-                typeOrganizedRuleMap
-                        .get(indexingDetails.getIndexType())
-                        .put(ruleKey, new ArrayList());
+            if (!typeOrganizedRuleMap.get(ruleIndexType).containsKey(ruleKey)) {
+                typeOrganizedRuleMap.get(ruleIndexType).put(ruleKey, new ArrayList());
             }
 
-            typeOrganizedRuleMap
-                    .get(indexingDetails.getIndexType())
-                    .get(ruleKey)
-                    .add(rule);
+            typeOrganizedRuleMap.get(ruleIndexType).get(ruleKey).add(rule);
         }
 
         return typeOrganizedRuleMap;
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java
index 4194432..8f164e6 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java
@@ -35,7 +35,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
-import java.util.TreeMap;
+import java.util.stream.Collectors;
 
 /** A helper class to serialize rules from the {@link Rule} model to Xml representation. */
 public class RuleXmlSerializer implements RuleSerializer {
@@ -90,7 +90,7 @@
             throws RuleSerializeException {
         try {
             // Determine the indexing groups and the order of the rules within each indexed group.
-            Map<Integer, TreeMap<String, List<Rule>>> indexedRules =
+            Map<Integer, Map<String, List<Rule>>> indexedRules =
                     RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets(rules);
 
             // Write the XML formatted rules in order.
@@ -107,11 +107,12 @@
         }
     }
 
-    private void serializeRuleList(TreeMap<String, List<Rule>> rulesMap,
-            XmlSerializer xmlSerializer)
+    private void serializeRuleList(Map<String, List<Rule>> rulesMap, XmlSerializer xmlSerializer)
             throws IOException {
-        for (Map.Entry<String, List<Rule>> entry : rulesMap.entrySet()) {
-            for (Rule rule : entry.getValue()) {
+        List<String> sortedKeyList =
+                rulesMap.keySet().stream().sorted().collect(Collectors.toList());
+        for (String key : sortedKeyList) {
+            for (Rule rule : rulesMap.get(key)) {
                 serializeRule(rule, xmlSerializer);
             }
         }
diff --git a/services/core/java/com/android/server/lights/Light.java b/services/core/java/com/android/server/lights/Light.java
deleted file mode 100644
index 717e3da..0000000
--- a/services/core/java/com/android/server/lights/Light.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2013 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.lights;
-
-import android.hardware.light.V2_0.Brightness;
-import android.hardware.light.V2_0.Flash;
-
-public abstract class Light {
-    public static final int LIGHT_FLASH_NONE = Flash.NONE;
-    public static final int LIGHT_FLASH_TIMED = Flash.TIMED;
-    public static final int LIGHT_FLASH_HARDWARE = Flash.HARDWARE;
-
-    /**
-     * Light brightness is managed by a user setting.
-     */
-    public static final int BRIGHTNESS_MODE_USER = Brightness.USER;
-
-    /**
-     * Light brightness is managed by a light sensor.
-     */
-    public static final int BRIGHTNESS_MODE_SENSOR = Brightness.SENSOR;
-
-    /**
-     * Low-persistence light mode.
-     */
-    public static final int BRIGHTNESS_MODE_LOW_PERSISTENCE = Brightness.LOW_PERSISTENCE;
-
-    /**
-     * Set the brightness of a display.
-     */
-    public abstract void setBrightness(int brightness);
-
-    /**
-     * Set the brightness and mode of a display.
-     */
-    public abstract void setBrightness(int brightness, int brightnessMode);
-
-    public abstract void setColor(int color);
-    public abstract void setFlashing(int color, int mode, int onMS, int offMS);
-    public abstract void pulse();
-    public abstract void pulse(int color, int onMS);
-    public abstract void turnOff();
-    public abstract void setVrMode(boolean enabled);
-}
diff --git a/services/core/java/com/android/server/lights/LightsManager.java b/services/core/java/com/android/server/lights/LightsManager.java
index be20a44..521913a 100644
--- a/services/core/java/com/android/server/lights/LightsManager.java
+++ b/services/core/java/com/android/server/lights/LightsManager.java
@@ -29,5 +29,8 @@
     public static final int LIGHT_ID_WIFI = Type.WIFI;
     public static final int LIGHT_ID_COUNT = Type.COUNT;
 
-    public abstract Light getLight(int id);
+    /**
+     * Returns the logical light with the given type.
+     */
+    public abstract LogicalLight getLight(int id);
 }
diff --git a/services/core/java/com/android/server/lights/LightsService.java b/services/core/java/com/android/server/lights/LightsService.java
index eaae2ed..5683e69 100644
--- a/services/core/java/com/android/server/lights/LightsService.java
+++ b/services/core/java/com/android/server/lights/LightsService.java
@@ -15,32 +15,223 @@
 
 package com.android.server.lights;
 
+import android.Manifest;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.Context;
+import android.hardware.light.HwLight;
+import android.hardware.light.HwLightState;
+import android.hardware.light.ILights;
+import android.hardware.lights.ILightsManager;
+import android.hardware.lights.Light;
+import android.hardware.lights.LightState;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.Message;
+import android.os.Looper;
 import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.Trace;
 import android.provider.Settings;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.view.SurfaceControl;
 
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
 import com.android.server.SystemService;
 
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
 public class LightsService extends SystemService {
     static final String TAG = "LightsService";
     static final boolean DEBUG = false;
 
-    final LightImpl mLights[] = new LightImpl[LightsManager.LIGHT_ID_COUNT];
+    private LightImpl[] mLights = null;
+    private SparseArray<LightImpl> mLightsById = null;
 
-    private final class LightImpl extends Light {
+    private ILights mVintfLights = null;
 
+    @VisibleForTesting
+    final LightsManagerBinderService mManagerService;
+
+    private Handler mH;
+
+    private final class LightsManagerBinderService extends ILightsManager.Stub {
+
+        private final class Session {
+            final IBinder mToken;
+            final SparseArray<LightState> mRequests = new SparseArray<>();
+
+            Session(IBinder token) {
+                mToken = token;
+            }
+
+            void setRequest(int lightId, LightState state) {
+                if (state != null) {
+                    mRequests.put(lightId, state);
+                } else {
+                    mRequests.remove(lightId);
+                }
+            }
+        }
+
+        @GuardedBy("LightsService.this")
+        private final List<Session> mSessions = new ArrayList<>();
+
+        /**
+         * Returns the lights available for apps to control on the device. Only lights that aren't
+         * reserved for system use are available to apps.
+         */
+        @Override
+        public List<Light> getLights() {
+            getContext().enforceCallingOrSelfPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS,
+                    "getLights requires CONTROL_DEVICE_LIGHTS_PERMISSION");
+
+            synchronized (LightsService.this) {
+                final List<Light> lights = new ArrayList<Light>();
+                for (int i = 0; i < mLightsById.size(); i++) {
+                    HwLight hwLight = mLightsById.valueAt(i).getHwLight();
+                    if (!isSystemLight(hwLight)) {
+                        lights.add(new Light(hwLight.id, hwLight.ordinal, hwLight.type));
+                    }
+                }
+                return lights;
+            }
+        }
+
+        /**
+         * Updates the set of light requests for {@param token} with additions and removals from
+         * {@param lightIds} and {@param lightStates}.
+         *
+         * <p>Null values mean that the request should be removed, and the light turned off if it
+         * is not being used by anything else.
+         */
+        @Override
+        public void setLightStates(IBinder token, int[] lightIds, LightState[] lightStates) {
+            getContext().enforceCallingOrSelfPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS,
+                    "setLightStates requires CONTROL_DEVICE_LIGHTS permission");
+            Preconditions.checkState(lightIds.length == lightStates.length);
+
+            synchronized (LightsService.this) {
+                Session session = getSessionLocked(Preconditions.checkNotNull(token));
+                Preconditions.checkState(session != null, "not registered");
+
+                checkRequestIsValid(lightIds);
+
+                for (int i = 0; i < lightIds.length; i++) {
+                    session.setRequest(lightIds[i], lightStates[i]);
+                }
+                invalidateLightStatesLocked();
+            }
+        }
+
+        @Override
+        public @Nullable LightState getLightState(int lightId) {
+            getContext().enforceCallingOrSelfPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS,
+                    "getLightState(@TestApi) requires CONTROL_DEVICE_LIGHTS permission");
+
+            synchronized (LightsService.this) {
+                final LightImpl light = mLightsById.get(lightId);
+                if (light == null || isSystemLight(light.getHwLight())) {
+                    throw new IllegalArgumentException("Invalid light: " + lightId);
+                }
+                return new LightState(light.getColor());
+            }
+        }
+
+        @Override
+        public void openSession(IBinder token) {
+            getContext().enforceCallingOrSelfPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS,
+                    "openSession requires CONTROL_DEVICE_LIGHTS permission");
+            Preconditions.checkNotNull(token);
+
+            synchronized (LightsService.this) {
+                Preconditions.checkState(getSessionLocked(token) == null, "already registered");
+                try {
+                    token.linkToDeath(() -> closeSessionInternal(token), 0);
+                    mSessions.add(new Session(token));
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Couldn't open session, client already died" , e);
+                    throw new IllegalArgumentException("Client is already dead.");
+                }
+            }
+        }
+
+        @Override
+        public void closeSession(IBinder token) {
+            getContext().enforceCallingOrSelfPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS,
+                    "closeSession requires CONTROL_DEVICE_LIGHTS permission");
+            Preconditions.checkNotNull(token);
+            closeSessionInternal(token);
+        }
+
+        private void closeSessionInternal(IBinder token) {
+            synchronized (LightsService.this) {
+                final Session session = getSessionLocked(token);
+                if (session != null) {
+                    mSessions.remove(session);
+                    invalidateLightStatesLocked();
+                }
+            }
+        }
+
+        private void checkRequestIsValid(int[] lightIds) {
+            for (int i = 0; i < lightIds.length; i++) {
+                final LightImpl light = mLightsById.get(lightIds[i]);
+                final HwLight hwLight = light.getHwLight();
+                Preconditions.checkState(light != null && !isSystemLight(hwLight),
+                        "invalid lightId " + hwLight.id);
+            }
+        }
+
+        /**
+         * Apply light state requests for all light IDs.
+         *
+         * <p>In case of conflict, the session that started earliest wins.
+         */
+        private void invalidateLightStatesLocked() {
+            final Map<Integer, LightState> states = new HashMap<>();
+            for (int i = mSessions.size() - 1; i >= 0; i--) {
+                SparseArray<LightState> requests = mSessions.get(i).mRequests;
+                for (int j = 0; j < requests.size(); j++) {
+                    states.put(requests.keyAt(j), requests.valueAt(j));
+                }
+            }
+            for (int i = 0; i < mLightsById.size(); i++) {
+                LightImpl light = mLightsById.valueAt(i);
+                HwLight hwLight = light.getHwLight();
+                if (!isSystemLight(hwLight)) {
+                    LightState state = states.get(hwLight.id);
+                    if (state != null) {
+                        light.setColor(state.getColor());
+                    } else {
+                        light.turnOff();
+                    }
+                }
+            }
+        }
+
+        private @Nullable Session getSessionLocked(IBinder token) {
+            for (int i = 0; i < mSessions.size(); i++) {
+                if (token.equals(mSessions.get(i).mToken)) {
+                    return mSessions.get(i);
+                }
+            }
+            return null;
+        }
+    }
+
+    private final class LightImpl extends LogicalLight {
         private final IBinder mDisplayToken;
         private final int mSurfaceControlMaximumBrightness;
 
-        private LightImpl(Context context, int id) {
-            mId = id;
+        private LightImpl(Context context, HwLight hwLight) {
+            mHwLight = hwLight;
             mDisplayToken = SurfaceControl.getInternalDisplayToken();
             final boolean brightnessSupport = SurfaceControl.getDisplayBrightnessSupport(
                     mDisplayToken);
@@ -58,17 +249,28 @@
         }
 
         @Override
+        public void setBrightnessFloat(float brightness) {
+            if (!Float.isNaN(brightness)) {
+                setBrightness(brightness, 0, BRIGHTNESS_MODE_USER);
+            }
+        }
+
+        @Override
         public void setBrightness(int brightness) {
             setBrightness(brightness, BRIGHTNESS_MODE_USER);
         }
 
         @Override
         public void setBrightness(int brightness, int brightnessMode) {
+            setBrightness(Float.NaN, brightness, brightnessMode);
+        }
+
+        private void setBrightness(float brightnessFloat, int brightness, int brightnessMode) {
             synchronized (this) {
                 // LOW_PERSISTENCE cannot be manually set
                 if (brightnessMode == BRIGHTNESS_MODE_LOW_PERSISTENCE) {
-                    Slog.w(TAG, "setBrightness with LOW_PERSISTENCE unexpected #" + mId +
-                            ": brightness=0x" + Integer.toHexString(brightness));
+                    Slog.w(TAG, "setBrightness with LOW_PERSISTENCE unexpected #" + mHwLight.id
+                            + ": brightness=0x" + Integer.toHexString(brightness));
                     return;
                 }
                 // Ideally, we'd like to set the brightness mode through the SF/HWC as well, but
@@ -84,7 +286,10 @@
                     if (DEBUG) {
                         Slog.d(TAG, "Using new setBrightness path!");
                     }
-                    if (brightness == 0) {
+
+                    if (!Float.isNaN(brightnessFloat)) {
+                        SurfaceControl.setDisplayBrightness(mDisplayToken, brightnessFloat);
+                    } else if (brightness == 0) {
                         SurfaceControl.setDisplayBrightness(mDisplayToken, -1.0f);
                     } else {
                         SurfaceControl.setDisplayBrightness(mDisplayToken,
@@ -124,7 +329,7 @@
                     setLightLocked(color, LIGHT_FLASH_HARDWARE, onMS, 1000,
                             BRIGHTNESS_MODE_USER);
                     mColor = 0;
-                    mH.sendMessageDelayed(Message.obtain(mH, 1, this), onMS);
+                    mH.postDelayed(this::stopFlashing, onMS);
                 }
             }
         }
@@ -171,8 +376,10 @@
 
             if (!mInitialized || color != mColor || mode != mMode || onMS != mOnMS ||
                     offMS != mOffMS || mBrightnessMode != brightnessMode) {
-                if (DEBUG) Slog.v(TAG, "setLight #" + mId + ": color=#"
-                        + Integer.toHexString(color) + ": brightnessMode=" + brightnessMode);
+                if (DEBUG) {
+                    Slog.v(TAG, "setLight #" + mHwLight.id + ": color=#"
+                            + Integer.toHexString(color) + ": brightnessMode=" + brightnessMode);
+                }
                 mInitialized = true;
                 mLastColor = mColor;
                 mColor = color;
@@ -180,10 +387,31 @@
                 mOnMS = onMS;
                 mOffMS = offMS;
                 mBrightnessMode = brightnessMode;
-                Trace.traceBegin(Trace.TRACE_TAG_POWER, "setLight(" + mId + ", 0x"
-                        + Integer.toHexString(color) + ")");
+                setLightUnchecked(color, mode, onMS, offMS, brightnessMode);
+            }
+        }
+
+        private void setLightUnchecked(int color, int mode, int onMS, int offMS,
+                int brightnessMode) {
+            Trace.traceBegin(Trace.TRACE_TAG_POWER, "setLightState(" + mHwLight.id + ", 0x"
+                    + Integer.toHexString(color) + ")");
+            if (mVintfLights != null) {
+                HwLightState lightState = new HwLightState();
+                lightState.color = color;
+                lightState.flashMode = (byte) mode;
+                lightState.flashOnMs = onMS;
+                lightState.flashOffMs = offMS;
+                lightState.brightnessMode = (byte) brightnessMode;
                 try {
-                    setLight_native(mId, color, mode, onMS, offMS, brightnessMode);
+                    mVintfLights.setLightState(mHwLight.id, lightState);
+                } catch (RemoteException | UnsupportedOperationException ex) {
+                    Slog.e(TAG, "Failed issuing setLightState", ex);
+                } finally {
+                    Trace.traceEnd(Trace.TRACE_TAG_POWER);
+                }
+            } else {
+                try {
+                    setLight_native(mHwLight.id, color, mode, onMS, offMS, brightnessMode);
                 } finally {
                     Trace.traceEnd(Trace.TRACE_TAG_POWER);
                 }
@@ -194,7 +422,15 @@
             return mVrModeEnabled && mUseLowPersistenceForVR;
         }
 
-        private int mId;
+        private HwLight getHwLight() {
+            return mHwLight;
+        }
+
+        private int getColor() {
+            return mColor;
+        }
+
+        private HwLight mHwLight;
         private int mColor;
         private int mMode;
         private int mOnMS;
@@ -209,16 +445,59 @@
     }
 
     public LightsService(Context context) {
-        super(context);
+        this(context,
+                ILights.Stub.asInterface(
+                        ServiceManager.getService("android.hardware.light.ILights/default")),
+                Looper.myLooper());
+    }
 
-        for (int i = 0; i < LightsManager.LIGHT_ID_COUNT; i++) {
-            mLights[i] = new LightImpl(context, i);
+    @VisibleForTesting
+    LightsService(Context context, ILights service, Looper looper) {
+        super(context);
+        mH = new Handler(looper);
+        mVintfLights = service;
+        mManagerService = new LightsManagerBinderService();
+        populateAvailableLights(context);
+    }
+
+    private void populateAvailableLights(Context context) {
+        mLights = new LightImpl[LightsManager.LIGHT_ID_COUNT];
+        mLightsById = new SparseArray<>();
+
+        if (mVintfLights != null) {
+            try {
+                for (HwLight availableLight : mVintfLights.getLights()) {
+                    LightImpl light = new LightImpl(context, availableLight);
+                    int type = (int) availableLight.type;
+                    if (0 <= type && type < mLights.length && mLights[type] == null) {
+                        mLights[type] = light;
+                    }
+                    mLightsById.put(availableLight.id, light);
+                }
+            } catch (RemoteException ex) {
+                Slog.e(TAG, "Unable to get lights for initialization", ex);
+            }
+        }
+
+        // In the case where only the old HAL is available, all lights will be initialized here
+        for (int i = 0; i < mLights.length; i++) {
+            if (mLights[i] == null) {
+                // The ordinal can be anything if there is only 1 light of each type. Set it to 1.
+                HwLight light = new HwLight();
+                light.id = (byte) i;
+                light.ordinal = 1;
+                light.type = (byte) i;
+
+                mLights[i] = new LightImpl(context, light);
+                mLightsById.put(i, mLights[i]);
+            }
         }
     }
 
     @Override
     public void onStart() {
         publishLocalService(LightsManager.class, mService);
+        publishBinderService(Context.LIGHTS_SERVICE, mManagerService);
     }
 
     @Override
@@ -235,22 +514,25 @@
 
     private final LightsManager mService = new LightsManager() {
         @Override
-        public Light getLight(int id) {
-            if (0 <= id && id < LIGHT_ID_COUNT) {
-                return mLights[id];
+        public LogicalLight getLight(int lightType) {
+            if (mLights != null && 0 <= lightType && lightType < mLights.length) {
+                return mLights[lightType];
             } else {
                 return null;
             }
         }
     };
 
-    private Handler mH = new Handler() {
-        @Override
-        public void handleMessage(Message msg) {
-            LightImpl light = (LightImpl)msg.obj;
-            light.stopFlashing();
-        }
-    };
+    /**
+     * Returns whether a light is system-use-only or should be accessible to
+     * applications using the {@link android.hardware.lights.LightsManager} API.
+     */
+    private static boolean isSystemLight(HwLight light) {
+        // LIGHT_ID_COUNT comes from the 2.0 HIDL HAL and only contains system
+        // lights. Newly added lights will be made available via the
+        // LightsManager API.
+        return 0 <= light.type && light.type < LightsManager.LIGHT_ID_COUNT;
+    }
 
     static native void setLight_native(int light, int color, int mode,
             int onMS, int offMS, int brightnessMode);
diff --git a/services/core/java/com/android/server/lights/LogicalLight.java b/services/core/java/com/android/server/lights/LogicalLight.java
new file mode 100644
index 0000000..33dfbb4
--- /dev/null
+++ b/services/core/java/com/android/server/lights/LogicalLight.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2013 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.lights;
+
+import android.hardware.light.V2_0.Brightness;
+import android.hardware.light.V2_0.Flash;
+
+/**
+ * Allow control over a logical light of a given type. The mapping of logical lights to physical
+ * lights is HAL implementation-dependent.
+ */
+public abstract class LogicalLight {
+    /**
+     * Keep the light steady on or off.
+     */
+    public static final int LIGHT_FLASH_NONE = Flash.NONE;
+
+    /**
+     * Flash the light at specified rate.
+     */
+    public static final int LIGHT_FLASH_TIMED = Flash.TIMED;
+
+    /**
+     * Flash the light using hardware assist.
+     */
+    public static final int LIGHT_FLASH_HARDWARE = Flash.HARDWARE;
+
+    /**
+     * Light brightness is managed by a user setting.
+     */
+    public static final int BRIGHTNESS_MODE_USER = Brightness.USER;
+
+    /**
+     * Light brightness is managed by a light sensor.
+     */
+    public static final int BRIGHTNESS_MODE_SENSOR = Brightness.SENSOR;
+
+    /**
+     * Low-persistence light mode.
+     */
+    public static final int BRIGHTNESS_MODE_LOW_PERSISTENCE = Brightness.LOW_PERSISTENCE;
+
+    /**
+     * Set the brightness of a display.
+     */
+    public abstract void setBrightness(int brightness);
+
+    /**
+     * Set the brightness and mode of a display.
+     */
+    public abstract void setBrightness(int brightness, int brightnessMode);
+
+    /**
+     * Set the brightness of a display using the brightness range defines in a
+     * display-device-configuration file.
+     */
+    public abstract void setBrightnessFloat(float brightness);
+
+    /**
+     * Set the color of a light.
+     */
+    public abstract void setColor(int color);
+
+    /**
+     * Set the color of a light and control flashing.
+     */
+    public abstract void setFlashing(int color, int mode, int onMS, int offMS);
+
+    /**
+     * Pulses the light.
+     */
+    public abstract void pulse();
+
+    /**
+     * Pulses the light with a specified color for a specified duration.
+     */
+    public abstract void pulse(int color, int onMS);
+
+    /**
+     * Turns off the light.
+     */
+    public abstract void turnOff();
+
+    /**
+     * Set the VR mode of a display.
+     */
+    public abstract void setVrMode(boolean enabled);
+}
diff --git a/services/core/java/com/android/server/lights/OWNERS b/services/core/java/com/android/server/lights/OWNERS
index c7c6d56..0e795b9 100644
--- a/services/core/java/com/android/server/lights/OWNERS
+++ b/services/core/java/com/android/server/lights/OWNERS
@@ -1,2 +1,3 @@
 michaelwr@google.com
-dangittik@google.com
+santoscordon@google.com
+flc@google.com
diff --git a/services/core/java/com/android/server/location/AbstractLocationProvider.java b/services/core/java/com/android/server/location/AbstractLocationProvider.java
index ccfc98e..ed6a759 100644
--- a/services/core/java/com/android/server/location/AbstractLocationProvider.java
+++ b/services/core/java/com/android/server/location/AbstractLocationProvider.java
@@ -16,11 +16,11 @@
 
 package com.android.server.location;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.location.Location;
 import android.os.Binder;
 import android.os.Bundle;
-import android.os.WorkSource;
 
 import com.android.internal.location.ProviderProperties;
 import com.android.internal.location.ProviderRequest;
@@ -29,127 +29,336 @@
 import java.io.PrintWriter;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.UnaryOperator;
 
 /**
- * Location Manager's interface for location providers. Always starts as disabled.
+ * Base class for all location providers.
  *
  * @hide
  */
 public abstract class AbstractLocationProvider {
 
     /**
-     * Interface for communicating from a location provider back to the location service.
+     * Interface for listening to location providers.
      */
-    public interface LocationProviderManager {
+    public interface Listener {
 
         /**
-         * May be called to inform the location service of a change in this location provider's
-         * enabled/disabled state.
+         * Called when a provider's state changes. May be invoked from any thread. Will be
+         * invoked with a cleared binder identity.
          */
-        void onSetEnabled(boolean enabled);
+        void onStateChanged(State oldState, State newState);
 
         /**
-         * May be called to inform the location service of a change in this location provider's
-         * properties.
-         */
-        void onSetProperties(ProviderProperties properties);
-
-        /**
-         * May be called to inform the location service that this provider has a new location
-         * available.
+         * Called when a provider has a new location available. May be invoked from any thread. Will
+         * be invoked with a cleared binder identity.
          */
         void onReportLocation(Location location);
 
         /**
-         * May be called to inform the location service that this provider has a new location
-         * available.
+         * Called when a provider has a new location available. May be invoked from any thread. Will
+         * be invoked with a cleared binder identity.
          */
         void onReportLocation(List<Location> locations);
     }
 
-    protected final Context mContext;
-    private final LocationProviderManager mLocationProviderManager;
+    /**
+     * Holds a representation of the public state of a provider.
+     */
+    public static final class State {
 
-    protected AbstractLocationProvider(
-            Context context, LocationProviderManager locationProviderManager) {
+        /**
+         * Default state value for a location provider that is disabled with no properties and an
+         * empty provider package list.
+         */
+        public static final State EMPTY_STATE = new State(false, null,
+                Collections.emptySet());
+
+        /**
+         * The provider's enabled state.
+         */
+        public final boolean enabled;
+
+        /**
+         * The provider's properties.
+         */
+        @Nullable public final ProviderProperties properties;
+
+        /**
+         * The provider's package name list - provider packages may be afforded special privileges.
+         */
+        public final Set<String> providerPackageNames;
+
+        private State(boolean enabled, ProviderProperties properties,
+                Set<String> providerPackageNames) {
+            this.enabled = enabled;
+            this.properties = properties;
+            this.providerPackageNames = Objects.requireNonNull(providerPackageNames);
+        }
+
+        private State withEnabled(boolean enabled) {
+            if (enabled == this.enabled) {
+                return this;
+            } else {
+                return new State(enabled, properties, providerPackageNames);
+            }
+        }
+
+        private State withProperties(ProviderProperties properties) {
+            if (properties.equals(this.properties)) {
+                return this;
+            } else {
+                return new State(enabled, properties, providerPackageNames);
+            }
+        }
+
+        private State withProviderPackageNames(Set<String> providerPackageNames) {
+            if (providerPackageNames.equals(this.providerPackageNames)) {
+                return this;
+            } else {
+                return new State(enabled, properties, providerPackageNames);
+            }
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (!(o instanceof State)) {
+                return false;
+            }
+            State state = (State) o;
+            return enabled == state.enabled && properties == state.properties
+                    && providerPackageNames.equals(state.providerPackageNames);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(enabled, properties, providerPackageNames);
+        }
+    }
+
+    // combines listener and state information so that they can be updated atomically with respect
+    // to each other and an ordering established.
+    private static class InternalState {
+        @Nullable public final Listener listener;
+        public final State state;
+
+        private InternalState(@Nullable Listener listener, State state) {
+            this.listener = listener;
+            this.state = state;
+        }
+
+        private InternalState withListener(Listener listener) {
+            if (listener == this.listener) {
+                return this;
+            } else {
+                return new InternalState(listener, state);
+            }
+        }
+
+        private InternalState withState(State state) {
+            if (state.equals(this.state)) {
+                return this;
+            } else {
+                return new InternalState(listener, state);
+            }
+        }
+
+        private InternalState withState(UnaryOperator<State> operator) {
+            return withState(operator.apply(state));
+        }
+    }
+
+    protected final Context mContext;
+    protected final Executor mExecutor;
+
+    // we use a lock-free implementation to update state to ensure atomicity between updating the
+    // provider state and setting the listener, so that the state updates a listener sees are
+    // consistent with when the listener was set (a listener should not see any updates that occur
+    // before it was set, and should not miss any updates that occur after it was set).
+    private final AtomicReference<InternalState> mInternalState;
+
+    protected AbstractLocationProvider(Context context, Executor executor) {
+        this(context, executor, Collections.singleton(context.getPackageName()));
+    }
+
+    protected AbstractLocationProvider(Context context, Executor executor,
+            Set<String> packageNames) {
         mContext = context;
-        mLocationProviderManager = locationProviderManager;
+        mExecutor = executor;
+        mInternalState = new AtomicReference<>(
+                new InternalState(null, State.EMPTY_STATE.withProviderPackageNames(packageNames)));
     }
 
     /**
-     * Call this method to report a change in provider enabled/disabled status. May be called from
-     * any thread.
+     * Sets the listener and returns the state at the moment the listener was set. The listener can
+     * expect to receive all state updates from after this point.
+     */
+    State setListener(@Nullable Listener listener) {
+        return mInternalState.updateAndGet(
+                internalState -> internalState.withListener(listener)).state;
+    }
+
+    /**
+     * Retrieves the state of the provider.
+     */
+    State getState() {
+        return mInternalState.get().state;
+    }
+
+    /**
+     * Sets the state of the provider to the new state.
+     */
+    void setState(State newState) {
+        InternalState oldInternalState = mInternalState.getAndUpdate(
+                internalState -> internalState.withState(newState));
+        if (newState.equals(oldInternalState.state)) {
+            return;
+        }
+
+        // we know that we only updated the state, so the listener for the old state is the same as
+        // the listener for the new state.
+        if (oldInternalState.listener != null) {
+            long identity = Binder.clearCallingIdentity();
+            try {
+                oldInternalState.listener.onStateChanged(oldInternalState.state, newState);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
+    private void setState(UnaryOperator<State> operator) {
+        InternalState oldInternalState = mInternalState.getAndUpdate(
+                internalState -> internalState.withState(operator));
+
+        // recreate the new state from our knowledge of the old state - unfortunately may result in
+        // an extra allocation, but oh well...
+        State newState = operator.apply(oldInternalState.state);
+
+        if (newState.equals(oldInternalState.state)) {
+            return;
+        }
+
+        // we know that we only updated the state, so the listener for the old state is the same as
+        // the listener for the new state.
+        if (oldInternalState.listener != null) {
+            long identity = Binder.clearCallingIdentity();
+            try {
+                oldInternalState.listener.onStateChanged(oldInternalState.state, newState);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
+    /**
+     * The current enabled state of this provider.
+     */
+    protected boolean isEnabled() {
+        return mInternalState.get().state.enabled;
+    }
+
+    /**
+     * The current provider properties of this provider.
+     */
+    @Nullable
+    protected ProviderProperties getProperties() {
+        return mInternalState.get().state.properties;
+    }
+
+    /**
+     * The current package set of this provider.
+     */
+    protected Set<String> getProviderPackages() {
+        return mInternalState.get().state.providerPackageNames;
+    }
+
+    /**
+     * Call this method to report a change in provider enabled/disabled status.
      */
     protected void setEnabled(boolean enabled) {
-        long identity = Binder.clearCallingIdentity();
-        try {
-            mLocationProviderManager.onSetEnabled(enabled);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
+        setState(state -> state.withEnabled(enabled));
     }
 
     /**
-     * Call this method to report a change in provider properties. May be called from
-     * any thread.
+     * Call this method to report a change in provider properties.
      */
     protected void setProperties(ProviderProperties properties) {
-        long identity = Binder.clearCallingIdentity();
-        try {
-            mLocationProviderManager.onSetProperties(properties);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
+        setState(state -> state.withProperties(properties));
     }
 
     /**
-     * Call this method to report a new location. May be called from any thread.
+     * Call this method to report a change in provider packages.
+     */
+    protected void setPackageNames(Set<String> packageNames) {
+        setState(state -> state.withProviderPackageNames(packageNames));
+    }
+
+    /**
+     * Call this method to report a new location.
      */
     protected void reportLocation(Location location) {
-        long identity = Binder.clearCallingIdentity();
-        try {
-            mLocationProviderManager.onReportLocation(location);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
+        Listener listener = mInternalState.get().listener;
+        if (listener != null) {
+            long identity = Binder.clearCallingIdentity();
+            try {
+                listener.onReportLocation(location);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
     }
 
     /**
-     * Call this method to report a new location. May be called from any thread.
+     * Call this method to report a new location.
      */
     protected void reportLocation(List<Location> locations) {
-        long identity = Binder.clearCallingIdentity();
-        try {
-            mLocationProviderManager.onReportLocation(locations);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
+        Listener listener = mInternalState.get().listener;
+        if (listener != null) {
+            long identity = Binder.clearCallingIdentity();
+            try {
+                listener.onReportLocation(locations);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
     }
 
     /**
-     * Invoked by the location service to return a list of packages currently associated with this
-     * provider. May be called from any thread.
+     * Sets a new request and worksource for the provider.
      */
-    public List<String> getProviderPackages() {
-        return Collections.singletonList(mContext.getPackageName());
+    public final void setRequest(ProviderRequest request) {
+        // all calls into the provider must be moved onto the provider thread to prevent deadlock
+        mExecutor.execute(() -> onSetRequest(request));
     }
 
     /**
-     * Invoked by the location service to deliver a new request for fulfillment to the provider.
-     * Replaces any previous requests completely. Will always be invoked from the location service
-     * thread with a cleared binder identity.
+     * Always invoked on the provider executor.
      */
-    public abstract void onSetRequest(ProviderRequest request, WorkSource source);
+    protected abstract void onSetRequest(ProviderRequest request);
 
     /**
-     * Invoked by the location service to deliver a custom command to this provider. Will always be
-     * invoked from the location service thread with a cleared binder identity.
+     * Sends an extra command to the provider for it to interpret as it likes.
      */
-    public void onSendExtraCommand(int uid, int pid, String command, Bundle extras) {}
+    public final void sendExtraCommand(int uid, int pid, String command, Bundle extras) {
+        // all calls into the provider must be moved onto the provider thread to prevent deadlock
+        mExecutor.execute(() -> onExtraCommand(uid, pid, command, extras));
+    }
 
     /**
-     * Invoked by the location service to dump debug or log information. May be invoked from any
-     * thread.
+     * Always invoked on the provider executor.
+     */
+    protected void onExtraCommand(int uid, int pid, String command, Bundle extras) {}
+
+    /**
+     * Dumps debug or log information. May be invoked from any thread.
      */
     public abstract void dump(FileDescriptor fd, PrintWriter pw, String[] args);
 }
diff --git a/services/core/java/com/android/server/location/ActivityRecognitionProxy.java b/services/core/java/com/android/server/location/ActivityRecognitionProxy.java
deleted file mode 100644
index 80ab790..0000000
--- a/services/core/java/com/android/server/location/ActivityRecognitionProxy.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.location;
-
-import android.content.Context;
-import android.hardware.location.ActivityRecognitionHardware;
-import android.hardware.location.IActivityRecognitionHardwareClient;
-import android.hardware.location.IActivityRecognitionHardwareWatcher;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.server.FgThread;
-import com.android.server.ServiceWatcher;
-
-/**
- * Proxy class to bind GmsCore to the ActivityRecognitionHardware.
- *
- * @hide
- */
-public class ActivityRecognitionProxy {
-
-    private static final String TAG = "ActivityRecognitionProxy";
-
-    /**
-     * Creates an instance of the proxy and binds it to the appropriate FusedProvider.
-     *
-     * @return An instance of the proxy if it could be bound, null otherwise.
-     */
-    public static ActivityRecognitionProxy createAndBind(
-            Context context,
-            boolean activityRecognitionHardwareIsSupported,
-            ActivityRecognitionHardware activityRecognitionHardware,
-            int overlaySwitchResId,
-            int defaultServicePackageNameResId,
-            int initialPackageNameResId) {
-        ActivityRecognitionProxy activityRecognitionProxy = new ActivityRecognitionProxy(
-                context,
-                activityRecognitionHardwareIsSupported,
-                activityRecognitionHardware,
-                overlaySwitchResId,
-                defaultServicePackageNameResId,
-                initialPackageNameResId);
-
-        if (activityRecognitionProxy.mServiceWatcher.start()) {
-            return activityRecognitionProxy;
-        } else {
-            return null;
-        }
-    }
-
-    private final ServiceWatcher mServiceWatcher;
-    private final boolean mIsSupported;
-    private final ActivityRecognitionHardware mInstance;
-
-    private ActivityRecognitionProxy(
-            Context context,
-            boolean activityRecognitionHardwareIsSupported,
-            ActivityRecognitionHardware activityRecognitionHardware,
-            int overlaySwitchResId,
-            int defaultServicePackageNameResId,
-            int initialPackageNameResId) {
-        mIsSupported = activityRecognitionHardwareIsSupported;
-        mInstance = activityRecognitionHardware;
-
-        mServiceWatcher = new ServiceWatcher(
-                context,
-                TAG,
-                "com.android.location.service.ActivityRecognitionProvider",
-                overlaySwitchResId,
-                defaultServicePackageNameResId,
-                initialPackageNameResId,
-                FgThread.getHandler()) {
-            @Override
-            protected void onBind() {
-                runOnBinder(ActivityRecognitionProxy.this::initializeService);
-            }
-        };
-    }
-
-    private void initializeService(IBinder binder) {
-        try {
-            String descriptor = binder.getInterfaceDescriptor();
-
-            if (IActivityRecognitionHardwareWatcher.class.getCanonicalName().equals(
-                    descriptor)) {
-                IActivityRecognitionHardwareWatcher watcher =
-                        IActivityRecognitionHardwareWatcher.Stub.asInterface(binder);
-                if (mInstance != null) {
-                    watcher.onInstanceChanged(mInstance);
-                }
-            } else if (IActivityRecognitionHardwareClient.class.getCanonicalName()
-                    .equals(descriptor)) {
-                IActivityRecognitionHardwareClient client =
-                        IActivityRecognitionHardwareClient.Stub.asInterface(binder);
-                client.onAvailabilityChanged(mIsSupported, mInstance);
-            } else {
-                Log.e(TAG, "Invalid descriptor found on connection: " + descriptor);
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, e);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/location/ContextHubClientBroker.java b/services/core/java/com/android/server/location/ContextHubClientBroker.java
index 45d9bae..bb96e98 100644
--- a/services/core/java/com/android/server/location/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/ContextHubClientBroker.java
@@ -33,6 +33,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
+import android.util.proto.ProtoOutputStream;
 
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Supplier;
@@ -449,6 +450,28 @@
         }
     }
 
+    /**
+     * Dump debugging info as ClientBrokerProto
+     *
+     * If the output belongs to a sub message, the caller is responsible for wrapping this function
+     * between {@link ProtoOutputStream#start(long)} and {@link ProtoOutputStream#end(long)}.
+     *
+     * @param proto the ProtoOutputStream to write to
+     */
+    void dump(ProtoOutputStream proto) {
+        proto.write(ClientBrokerProto.ENDPOINT_ID, getHostEndPointId());
+        proto.write(ClientBrokerProto.ATTACHED_CONTEXT_HUB_ID, getAttachedContextHubId());
+        proto.write(ClientBrokerProto.PACKAGE, mPackage);
+        if (mPendingIntentRequest.isValid()) {
+            proto.write(ClientBrokerProto.PENDING_INTENT_REQUEST_VALID, true);
+            proto.write(ClientBrokerProto.NANO_APP_ID, mPendingIntentRequest.getNanoAppId());
+        }
+        proto.write(ClientBrokerProto.HAS_PENDING_INTENT, mPendingIntentRequest.hasPendingIntent());
+        proto.write(ClientBrokerProto.PENDING_INTENT_CANCELLED, isPendingIntentCancelled());
+        proto.write(ClientBrokerProto.REGISTERED, mRegistered);
+
+    }
+
     @Override
     public String toString() {
         String out = "[ContextHubClient ";
diff --git a/services/core/java/com/android/server/location/ContextHubClientManager.java b/services/core/java/com/android/server/location/ContextHubClientManager.java
index 46db8dc..0f70bb8 100644
--- a/services/core/java/com/android/server/location/ContextHubClientManager.java
+++ b/services/core/java/com/android/server/location/ContextHubClientManager.java
@@ -27,10 +27,13 @@
 import android.hardware.location.NanoAppMessage;
 import android.os.RemoteException;
 import android.util.Log;
+import android.util.proto.ProtoOutputStream;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.Calendar;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
 import java.util.Iterator;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedDeque;
@@ -45,6 +48,11 @@
     private static final String TAG = "ContextHubClientManager";
 
     /*
+     * The DateFormat for printing RegistrationRecord.
+     */
+    private static final DateFormat DATE_FORMAT = new SimpleDateFormat("MM/dd HH:mm:ss.SSS");
+
+    /*
      * The maximum host endpoint ID value that a client can be assigned.
      */
     private static final int MAX_CLIENT_ID = 0x7fff;
@@ -123,24 +131,24 @@
     private class RegistrationRecord {
         private final String mBroker;
         private final int mAction;
-        private final String mDate;
+        private final long mTimestamp;
 
         RegistrationRecord(String broker, @Action int action) {
             mBroker = broker;
             mAction = action;
-            Calendar instance = Calendar.getInstance();
-            mDate = String.format("%02d", instance.get(Calendar.MONTH) + 1) // Jan == 0
-                + "/" + String.format("%02d", instance.get(Calendar.DAY_OF_MONTH))
-                + " " + String.format("%02d", instance.get(Calendar.HOUR_OF_DAY))
-                + ":" + String.format("%02d", instance.get(Calendar.MINUTE))
-                + ":" + String.format("%02d", instance.get(Calendar.SECOND))
-                + "." + String.format("%03d", instance.get(Calendar.MILLISECOND));
+            mTimestamp = System.currentTimeMillis();
+        }
+
+        void dump(ProtoOutputStream proto) {
+            proto.write(ClientManagerProto.RegistrationRecord.TIMESTAMP_MS, mTimestamp);
+            proto.write(ClientManagerProto.RegistrationRecord.ACTION, mAction);
+            proto.write(ClientManagerProto.RegistrationRecord.BROKER, mBroker);
         }
 
         @Override
         public String toString() {
             String out = "";
-            out += mDate + " ";
+            out += DATE_FORMAT.format(new Date(mTimestamp)) + " ";
             out += mAction == ACTION_REGISTERED ? "+ " : "- ";
             out += mBroker;
             if (mAction == ACTION_CANCELLED) {
@@ -376,6 +384,28 @@
         return null;
     }
 
+    /**
+     * Dump debugging info as ClientManagerProto
+     *
+     * If the output belongs to a sub message, the caller is responsible for wrapping this function
+     * between {@link ProtoOutputStream#start(long)} and {@link ProtoOutputStream#end(long)}.
+     *
+     * @param proto the ProtoOutputStream to write to
+     */
+    void dump(ProtoOutputStream proto) {
+        for (ContextHubClientBroker broker : mHostEndPointIdToClientMap.values()) {
+            long token = proto.start(ClientManagerProto.CLIENT_BROKERS);
+            broker.dump(proto);
+            proto.end(token);
+        }
+        Iterator<RegistrationRecord> it = mRegistrationRecordDeque.descendingIterator();
+        while (it.hasNext()) {
+            long token = proto.start(ClientManagerProto.REGISTRATION_RECORDS);
+            it.next().dump(proto);
+            proto.end(token);
+        }
+    }
+
     @Override
     public String toString() {
         String out = "";
diff --git a/services/core/java/com/android/server/location/ContextHubService.java b/services/core/java/com/android/server/location/ContextHubService.java
index 787a800..e79eddf 100644
--- a/services/core/java/com/android/server/location/ContextHubService.java
+++ b/services/core/java/com/android/server/location/ContextHubService.java
@@ -43,6 +43,7 @@
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.util.Log;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.DumpUtils;
 
@@ -782,6 +783,13 @@
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
 
+        for (String arg : args) {
+            if ("--proto".equals(arg)) {
+                dump(new ProtoOutputStream(fd));
+                return;
+            }
+        }
+
         pw.println("Dumping ContextHub Service");
 
         pw.println("");
@@ -802,6 +810,20 @@
         // dump eventLog
     }
 
+    private void dump(ProtoOutputStream proto) {
+        mContextHubIdToInfoMap.values().forEach(hubInfo -> {
+            long token = proto.start(ContextHubServiceProto.CONTEXT_HUB_INFO);
+            hubInfo.dump(proto);
+            proto.end(token);
+        });
+
+        long token = proto.start(ContextHubServiceProto.CLIENT_MANAGER);
+        mClientManager.dump(proto);
+        proto.end(token);
+
+        proto.flush();
+    }
+
     private void checkPermissions() {
         ContextHubServiceUtil.checkPermissions(mContext);
     }
diff --git a/services/core/java/com/android/server/location/GeocoderProxy.java b/services/core/java/com/android/server/location/GeocoderProxy.java
index e6f0ed9..536f95a 100644
--- a/services/core/java/com/android/server/location/GeocoderProxy.java
+++ b/services/core/java/com/android/server/location/GeocoderProxy.java
@@ -16,6 +16,7 @@
 
 package com.android.server.location;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.location.Address;
 import android.location.GeocoderParams;
@@ -28,40 +29,38 @@
 
 /**
  * Proxy for IGeocodeProvider implementations.
+ *
+ * @hide
  */
 public class GeocoderProxy {
-    private static final String TAG = "GeocoderProxy";
 
     private static final String SERVICE_ACTION = "com.android.location.service.GeocodeProvider";
 
-    private final ServiceWatcher mServiceWatcher;
-
-    public static GeocoderProxy createAndBind(Context context,
-            int overlaySwitchResId, int defaultServicePackageNameResId,
-            int initialPackageNamesResId) {
-        GeocoderProxy proxy = new GeocoderProxy(context, overlaySwitchResId,
-                defaultServicePackageNameResId, initialPackageNamesResId);
-        if (proxy.bind()) {
+    /**
+     * Creates and registers this proxy. If no suitable service is available for the proxy, returns
+     * null.
+     */
+    @Nullable
+    public static GeocoderProxy createAndRegister(Context context) {
+        GeocoderProxy proxy = new GeocoderProxy(context);
+        if (proxy.register()) {
             return proxy;
         } else {
             return null;
         }
     }
 
-    private GeocoderProxy(Context context,
-            int overlaySwitchResId, int defaultServicePackageNameResId,
-            int initialPackageNamesResId) {
-        mServiceWatcher = new ServiceWatcher(context, TAG, SERVICE_ACTION, overlaySwitchResId,
-                defaultServicePackageNameResId, initialPackageNamesResId,
-                BackgroundThread.getHandler());
+    private final ServiceWatcher mServiceWatcher;
+
+    private GeocoderProxy(Context context) {
+        mServiceWatcher = new ServiceWatcher(context, BackgroundThread.getHandler(), SERVICE_ACTION,
+                null, null,
+                com.android.internal.R.bool.config_enableGeocoderOverlay,
+                com.android.internal.R.string.config_geocoderProviderPackageName);
     }
 
-    private boolean bind() {
-        return mServiceWatcher.start();
-    }
-
-    public String getConnectedPackageName() {
-        return mServiceWatcher.getCurrentPackageName();
+    private boolean register() {
+        return mServiceWatcher.register();
     }
 
     public String getFromLocation(double latitude, double longitude, int maxResults,
@@ -83,5 +82,4 @@
                     maxResults, params, addrs);
         }, "Service not Available");
     }
-
 }
diff --git a/services/core/java/com/android/server/location/GeofenceProxy.java b/services/core/java/com/android/server/location/GeofenceProxy.java
index ce93661..f006fb1 100644
--- a/services/core/java/com/android/server/location/GeofenceProxy.java
+++ b/services/core/java/com/android/server/location/GeofenceProxy.java
@@ -22,7 +22,6 @@
 import android.content.ServiceConnection;
 import android.hardware.location.GeofenceHardwareService;
 import android.hardware.location.IGeofenceHardware;
-import android.location.IFusedGeofenceHardware;
 import android.location.IGeofenceProvider;
 import android.location.IGpsGeofenceHardware;
 import android.os.IBinder;
@@ -33,6 +32,8 @@
 import com.android.server.FgThread;
 import com.android.server.ServiceWatcher;
 
+import java.util.Objects;
+
 /**
  * @hide
  */
@@ -41,64 +42,41 @@
     private static final String TAG = "GeofenceProxy";
     private static final String SERVICE_ACTION = "com.android.location.service.GeofenceProvider";
 
-    private final Context mContext;
-    private final ServiceWatcher mServiceWatcher;
-
     @Nullable
-    private final IGpsGeofenceHardware mGpsGeofenceHardware;
-    @Nullable
-    private final IFusedGeofenceHardware mFusedGeofenceHardware;
-
-    private volatile IGeofenceHardware mGeofenceHardware;
-
-    private final ServiceWatcher.BinderRunner mUpdateGeofenceHardware = (binder) -> {
-        IGeofenceProvider provider = IGeofenceProvider.Stub.asInterface(binder);
-        try {
-            provider.setGeofenceHardware(mGeofenceHardware);
-        } catch (RemoteException e) {
-            Log.w(TAG, e);
-        }
-    };
-
-    public static GeofenceProxy createAndBind(Context context,
-            int overlaySwitchResId, int defaultServicePackageNameResId,
-            int initialPackageNamesResId, @Nullable IGpsGeofenceHardware gpsGeofence,
-            @Nullable IFusedGeofenceHardware fusedGeofenceHardware) {
-        GeofenceProxy proxy = new GeofenceProxy(context, overlaySwitchResId,
-                defaultServicePackageNameResId, initialPackageNamesResId, gpsGeofence,
-                fusedGeofenceHardware);
-
-        if (proxy.bind()) {
+    public static GeofenceProxy createAndBind(Context context, IGpsGeofenceHardware gpsGeofence) {
+        GeofenceProxy proxy = new GeofenceProxy(context, gpsGeofence);
+        if (proxy.register(context)) {
             return proxy;
         } else {
             return null;
         }
     }
 
-    private GeofenceProxy(Context context,
-            int overlaySwitchResId, int defaultServicePackageNameResId,
-            int initialPackageNamesResId, @Nullable IGpsGeofenceHardware gpsGeofence,
-            @Nullable IFusedGeofenceHardware fusedGeofenceHardware) {
-        mContext = context;
-        mServiceWatcher = new ServiceWatcher(context, TAG, SERVICE_ACTION, overlaySwitchResId,
-                defaultServicePackageNameResId, initialPackageNamesResId,
-                FgThread.getHandler()) {
-            @Override
-            protected void onBind() {
-                runOnBinder(mUpdateGeofenceHardware);
-            }
-        };
+    private final IGpsGeofenceHardware mGpsGeofenceHardware;
+    private final ServiceWatcher mServiceWatcher;
 
-        mGpsGeofenceHardware = gpsGeofence;
-        mFusedGeofenceHardware = fusedGeofenceHardware;
+    private volatile IGeofenceHardware mGeofenceHardware;
+
+    private GeofenceProxy(Context context, IGpsGeofenceHardware gpsGeofence) {
+        mGpsGeofenceHardware = Objects.requireNonNull(gpsGeofence);
+        mServiceWatcher = new ServiceWatcher(context, FgThread.getHandler(), SERVICE_ACTION,
+                this::updateGeofenceHardware, null,
+                com.android.internal.R.bool.config_enableGeofenceOverlay,
+                com.android.internal.R.string.config_geofenceProviderPackageName);
 
         mGeofenceHardware = null;
     }
 
-    private boolean bind() {
-        if (mServiceWatcher.start()) {
-            mContext.bindServiceAsUser(new Intent(mContext, GeofenceHardwareService.class),
-                    new GeofenceProxyServiceConnection(), Context.BIND_AUTO_CREATE,
+    private void updateGeofenceHardware(IBinder binder) throws RemoteException {
+        IGeofenceProvider.Stub.asInterface(binder).setGeofenceHardware(mGeofenceHardware);
+    }
+
+    private boolean register(Context context) {
+        if (mServiceWatcher.register()) {
+            context.bindServiceAsUser(
+                    new Intent(context, GeofenceHardwareService.class),
+                    new GeofenceProxyServiceConnection(),
+                    Context.BIND_AUTO_CREATE,
                     UserHandle.SYSTEM);
             return true;
         }
@@ -113,24 +91,18 @@
             IGeofenceHardware geofenceHardware = IGeofenceHardware.Stub.asInterface(service);
 
             try {
-                if (mGpsGeofenceHardware != null) {
-                    geofenceHardware.setGpsGeofenceHardware(mGpsGeofenceHardware);
-                }
-                if (mFusedGeofenceHardware != null) {
-                    geofenceHardware.setFusedGeofenceHardware(mFusedGeofenceHardware);
-                }
-
+                geofenceHardware.setGpsGeofenceHardware(mGpsGeofenceHardware);
                 mGeofenceHardware = geofenceHardware;
-                mServiceWatcher.runOnBinder(mUpdateGeofenceHardware);
-            } catch (Exception e) {
-                Log.w(TAG, e);
+                mServiceWatcher.runOnBinder(GeofenceProxy.this::updateGeofenceHardware);
+            } catch (RemoteException e) {
+                Log.w(TAG, "unable to initialize geofence hardware", e);
             }
         }
 
         @Override
         public void onServiceDisconnected(ComponentName name) {
             mGeofenceHardware = null;
-            mServiceWatcher.runOnBinder(mUpdateGeofenceHardware);
+            mServiceWatcher.runOnBinder(GeofenceProxy.this::updateGeofenceHardware);
         }
     }
 }
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index d8561b6..15cf190 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -43,6 +43,7 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PersistableBundle;
@@ -113,8 +114,15 @@
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
 
     private static final ProviderProperties PROPERTIES = new ProviderProperties(
-            true, true, false, false, true, true, true,
-            Criteria.POWER_HIGH, Criteria.ACCURACY_FINE);
+            /* requiresNetwork = */false,
+            /* requiresSatellite = */true,
+            /* requiresCell = */false,
+            /* hasMonetaryCost = */false,
+            /* supportAltitude = */true,
+            /* supportsSpeed = */true,
+            /* supportsBearing = */true,
+            Criteria.POWER_HIGH,
+            Criteria.ACCURACY_FINE);
 
     // these need to match GnssPositionMode enum in IGnss.hal
     private static final int GPS_POSITION_MODE_STANDALONE = 0;
@@ -616,13 +624,12 @@
         }
     }
 
-    public GnssLocationProvider(Context context, LocationProviderManager locationProviderManager,
-            Looper looper) {
-        super(context, locationProviderManager);
+    public GnssLocationProvider(Context context, Handler handler) {
+        super(context, new HandlerExecutor(handler));
 
         ensureInitialized();
 
-        mLooper = looper;
+        mLooper = handler.getLooper();
 
         // Create a wake lock
         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -639,7 +646,7 @@
         mTimeoutIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_TIMEOUT), 0);
 
         mNetworkConnectivityHandler = new GnssNetworkConnectivityHandler(context,
-                GnssLocationProvider.this::onNetworkAvailable, looper);
+                GnssLocationProvider.this::onNetworkAvailable, mLooper);
 
         // App ops service to keep track of who is accessing the GPS
         mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -649,7 +656,7 @@
                 BatteryStats.SERVICE_NAME));
 
         // Construct internal handler
-        mHandler = new ProviderHandler(looper);
+        mHandler = new ProviderHandler(mLooper);
 
         // Load GPS configuration and register listeners in the background:
         // some operations, such as opening files and registering broadcast receivers, can take a
@@ -693,10 +700,10 @@
         };
 
         mGnssMetrics = new GnssMetrics(mBatteryStats);
-        mNtpTimeHelper = new NtpTimeHelper(mContext, looper, this);
+        mNtpTimeHelper = new NtpTimeHelper(mContext, mLooper, this);
         GnssSatelliteBlacklistHelper gnssSatelliteBlacklistHelper =
                 new GnssSatelliteBlacklistHelper(mContext,
-                        looper, this);
+                        mLooper, this);
         mHandler.post(gnssSatelliteBlacklistHelper::updateSatelliteBlacklist);
         mGnssBatchingProvider = new GnssBatchingProvider();
         mGnssGeofenceProvider = new GnssGeofenceProvider();
@@ -1047,8 +1054,8 @@
     }
 
     @Override
-    public void onSetRequest(ProviderRequest request, WorkSource source) {
-        sendMessage(SET_REQUEST, 0, new GpsRequest(request, source));
+    public void onSetRequest(ProviderRequest request) {
+        sendMessage(SET_REQUEST, 0, new GpsRequest(request, request.workSource));
     }
 
     private void handleSetRequest(ProviderRequest request, WorkSource source) {
@@ -1185,7 +1192,7 @@
     }
 
     @Override
-    public void onSendExtraCommand(int uid, int pid, String command, Bundle extras) {
+    public void onExtraCommand(int uid, int pid, String command, Bundle extras) {
 
         long identity = Binder.clearCallingIdentity();
         try {
@@ -2064,10 +2071,8 @@
         }
 
         /**
-         * This method is bound to {@link #GnssLocationProvider(Context, LocationProviderManager,
-         * Looper)}.
-         * It is in charge of loading properties and registering for events that will be posted to
-         * this handler.
+         * This method is bound to the constructor. It is in charge of loading properties and
+         * registering for events that will be posted to this handler.
          */
         private void handleInitialize() {
             // class_init_native() already initializes the GNSS service handle during class loading.
diff --git a/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java b/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java
new file mode 100644
index 0000000..9d9852b
--- /dev/null
+++ b/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.location.ActivityRecognitionHardware;
+import android.hardware.location.IActivityRecognitionHardwareClient;
+import android.hardware.location.IActivityRecognitionHardwareWatcher;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.server.FgThread;
+import com.android.server.ServiceWatcher;
+
+/**
+ * Proxy class to bind GmsCore to the ActivityRecognitionHardware.
+ *
+ * @hide
+ */
+public class HardwareActivityRecognitionProxy {
+
+    private static final String TAG = "ARProxy";
+    private static final String SERVICE_ACTION =
+            "com.android.location.service.ActivityRecognitionProvider";
+
+    /**
+     * Creates and registers this proxy. If no suitable service is available for the proxy, returns
+     * null.
+     */
+    @Nullable
+    public static HardwareActivityRecognitionProxy createAndRegister(Context context) {
+        HardwareActivityRecognitionProxy arProxy = new HardwareActivityRecognitionProxy(context);
+        if (arProxy.register()) {
+            return arProxy;
+        } else {
+            return null;
+        }
+    }
+
+    private final boolean mIsSupported;
+    private final ActivityRecognitionHardware mInstance;
+
+    private final ServiceWatcher mServiceWatcher;
+
+    private HardwareActivityRecognitionProxy(Context context) {
+        mIsSupported = ActivityRecognitionHardware.isSupported();
+        if (mIsSupported) {
+            mInstance = ActivityRecognitionHardware.getInstance(context);
+        } else {
+            mInstance = null;
+        }
+
+        mServiceWatcher = new ServiceWatcher(context,
+                FgThread.getHandler(),
+                SERVICE_ACTION,
+                this::onBind,
+                null,
+                com.android.internal.R.bool.config_enableActivityRecognitionHardwareOverlay,
+                com.android.internal.R.string.config_activityRecognitionHardwarePackageName);
+    }
+
+    private boolean register() {
+        return mServiceWatcher.register();
+    }
+
+    private void onBind(IBinder binder) throws RemoteException {
+        String descriptor = binder.getInterfaceDescriptor();
+
+        if (IActivityRecognitionHardwareWatcher.class.getCanonicalName().equals(descriptor)) {
+            IActivityRecognitionHardwareWatcher watcher =
+                    IActivityRecognitionHardwareWatcher.Stub.asInterface(binder);
+            if (mInstance != null) {
+                watcher.onInstanceChanged(mInstance);
+            }
+        } else if (IActivityRecognitionHardwareClient.class.getCanonicalName().equals(descriptor)) {
+            IActivityRecognitionHardwareClient client =
+                    IActivityRecognitionHardwareClient.Stub.asInterface(binder);
+            client.onAvailabilityChanged(mIsSupported, mInstance);
+        } else {
+            Log.e(TAG, "Unknown descriptor: " + descriptor);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/location/LocationProviderProxy.java b/services/core/java/com/android/server/location/LocationProviderProxy.java
index 694f149..805b018 100644
--- a/services/core/java/com/android/server/location/LocationProviderProxy.java
+++ b/services/core/java/com/android/server/location/LocationProviderProxy.java
@@ -19,30 +19,27 @@
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 
 import android.annotation.Nullable;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.location.Location;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.WorkSource;
+import android.util.ArraySet;
 import android.util.Log;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.location.ILocationProvider;
 import com.android.internal.location.ILocationProviderManager;
 import com.android.internal.location.ProviderProperties;
 import com.android.internal.location.ProviderRequest;
 import com.android.server.FgThread;
-import com.android.server.LocationManagerService;
 import com.android.server.ServiceWatcher;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * Proxy for ILocationProvider implementations.
@@ -50,175 +47,129 @@
 public class LocationProviderProxy extends AbstractLocationProvider {
 
     private static final String TAG = "LocationProviderProxy";
-    private static final boolean D = LocationManagerService.D;
 
-    // used to ensure that updates to mProviderPackages are atomic
-    private final Object mProviderPackagesLock = new Object();
-
-    // used to ensure that updates to mRequest and mWorkSource are atomic
-    private final Object mRequestLock = new Object();
-
-    private final ILocationProviderManager.Stub mManager = new ILocationProviderManager.Stub() {
-        // executed on binder thread
-        @Override
-        public void onSetAdditionalProviderPackages(List<String> packageNames) {
-            LocationProviderProxy.this.onSetAdditionalProviderPackages(packageNames);
-        }
-
-        // executed on binder thread
-        @Override
-        public void onSetEnabled(boolean enabled) {
-            LocationProviderProxy.this.setEnabled(enabled);
-        }
-
-        // executed on binder thread
-        @Override
-        public void onSetProperties(ProviderProperties properties) {
-            LocationProviderProxy.this.setProperties(properties);
-        }
-
-        // executed on binder thread
-        @Override
-        public void onReportLocation(Location location) {
-            LocationProviderProxy.this.reportLocation(location);
-        }
-    };
-
-    private final ServiceWatcher mServiceWatcher;
-
-    @GuardedBy("mProviderPackagesLock")
-    private final CopyOnWriteArrayList<String> mProviderPackages = new CopyOnWriteArrayList<>();
-
-    @GuardedBy("mRequestLock")
-    @Nullable
-    private ProviderRequest mRequest;
-    @GuardedBy("mRequestLock")
-    private WorkSource mWorkSource;
+    private static final int MAX_ADDITIONAL_PACKAGES = 2;
 
     /**
-     * Creates a new LocationProviderProxy and immediately begins binding to the best applicable
-     * service.
+     * Creates and registers this proxy. If no suitable service is available for the proxy, returns
+     * null.
      */
     @Nullable
-    public static LocationProviderProxy createAndBind(
-            Context context, LocationProviderManager locationProviderManager, String action,
-            int overlaySwitchResId, int defaultServicePackageNameResId,
-            int initialPackageNamesResId) {
-        LocationProviderProxy proxy = new LocationProviderProxy(context, locationProviderManager,
-                action, overlaySwitchResId, defaultServicePackageNameResId,
-                initialPackageNamesResId);
-        if (proxy.bind()) {
+    public static LocationProviderProxy createAndRegister(Context context, String action,
+            int enableOverlayResId, int nonOverlayPackageResId) {
+        LocationProviderProxy proxy = new LocationProviderProxy(context, action, enableOverlayResId,
+                nonOverlayPackageResId);
+        if (proxy.register()) {
             return proxy;
         } else {
             return null;
         }
     }
 
-    private LocationProviderProxy(Context context, LocationProviderManager locationProviderManager,
-            String action, int overlaySwitchResId, int defaultServicePackageNameResId,
-            int initialPackageNamesResId) {
-        super(context, locationProviderManager);
+    private final ILocationProviderManager.Stub mManager = new ILocationProviderManager.Stub() {
+        // executed on binder thread
+        @Override
+        public void onSetAdditionalProviderPackages(List<String> packageNames) {
+            int maxCount = Math.min(MAX_ADDITIONAL_PACKAGES, packageNames.size());
+            ArraySet<String> allPackages = new ArraySet<>(maxCount);
+            for (String packageName : packageNames) {
+                if (packageNames.size() >= maxCount) {
+                    return;
+                }
 
-        mServiceWatcher = new ServiceWatcher(context, TAG, action, overlaySwitchResId,
-                defaultServicePackageNameResId, initialPackageNamesResId,
-                FgThread.getHandler()) {
-
-            @Override
-            protected void onBind() {
-                runOnBinder(LocationProviderProxy.this::initializeService);
+                try {
+                    mContext.getPackageManager().getPackageInfo(packageName, MATCH_SYSTEM_ONLY);
+                    allPackages.add(packageName);
+                } catch (PackageManager.NameNotFoundException e) {
+                    Log.w(TAG, mServiceWatcher + " specified unknown additional provider package: "
+                            + packageName);
+                }
             }
 
-            @Override
-            protected void onUnbind() {
-                resetProviderPackages(Collections.emptyList());
-                setEnabled(false);
-                setProperties(null);
+            // add the binder package
+            ComponentName service = mServiceWatcher.getBoundService().component;
+            if (service != null) {
+                allPackages.add(service.getPackageName());
             }
-        };
+
+            setPackageNames(allPackages);
+        }
+
+        // executed on binder thread
+        @Override
+        public void onSetEnabled(boolean enabled) {
+            setEnabled(enabled);
+        }
+
+        // executed on binder thread
+        @Override
+        public void onSetProperties(ProviderProperties properties) {
+            setProperties(properties);
+        }
+
+        // executed on binder thread
+        @Override
+        public void onReportLocation(Location location) {
+            reportLocation(location);
+        }
+    };
+
+    private final ServiceWatcher mServiceWatcher;
+
+    @Nullable private ProviderRequest mRequest;
+
+    private LocationProviderProxy(Context context, String action, int enableOverlayResId,
+            int nonOverlayPackageResId) {
+        super(context, FgThread.getExecutor());
+
+        mServiceWatcher = new ServiceWatcher(context, FgThread.getHandler(), action, this::onBind,
+                this::onUnbind, enableOverlayResId, nonOverlayPackageResId);
 
         mRequest = null;
-        mWorkSource = new WorkSource();
     }
 
-    private boolean bind() {
-        return mServiceWatcher.start();
+    private boolean register() {
+        return mServiceWatcher.register();
     }
 
-    private void initializeService(IBinder binder) throws RemoteException {
-        ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
-        if (D) Log.d(TAG, "applying state to connected service " + mServiceWatcher);
+    private void onBind(IBinder binder) throws RemoteException {
+        ILocationProvider provider = ILocationProvider.Stub.asInterface(binder);
 
-        resetProviderPackages(Collections.emptyList());
-
-        service.setLocationProviderManager(mManager);
-
-        synchronized (mRequestLock) {
-            if (mRequest != null) {
-                service.setRequest(mRequest, mWorkSource);
-            }
+        ComponentName service = mServiceWatcher.getBoundService().component;
+        if (service != null) {
+            setPackageNames(Collections.singleton(service.getPackageName()));
         }
+
+        provider.setLocationProviderManager(mManager);
+
+        if (mRequest != null) {
+            provider.setRequest(mRequest, mRequest.workSource);
+        }
+    }
+
+    private void onUnbind() {
+        setState(State.EMPTY_STATE);
     }
 
     @Override
-    public List<String> getProviderPackages() {
-        synchronized (mProviderPackagesLock) {
-            return mProviderPackages;
-        }
-    }
-
-    @Override
-    public void onSetRequest(ProviderRequest request, WorkSource source) {
-        synchronized (mRequestLock) {
-            mRequest = request;
-            mWorkSource = source;
-        }
+    public void onSetRequest(ProviderRequest request) {
         mServiceWatcher.runOnBinder(binder -> {
+            mRequest = request;
             ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
-            service.setRequest(request, source);
+            service.setRequest(request, request.workSource);
         });
     }
 
     @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("service=" + mServiceWatcher);
-        synchronized (mProviderPackagesLock) {
-            if (mProviderPackages.size() > 1) {
-                pw.println("additional packages=" + mProviderPackages);
-            }
-        }
-    }
-
-    @Override
-    public void onSendExtraCommand(int uid, int pid, String command, Bundle extras) {
+    public void onExtraCommand(int uid, int pid, String command, Bundle extras) {
         mServiceWatcher.runOnBinder(binder -> {
             ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
             service.sendExtraCommand(command, extras);
         });
     }
 
-    private void onSetAdditionalProviderPackages(List<String> packageNames) {
-        resetProviderPackages(packageNames);
-    }
-
-    private void resetProviderPackages(List<String> additionalPackageNames) {
-        ArrayList<String> permittedPackages = new ArrayList<>(additionalPackageNames.size());
-        for (String packageName : additionalPackageNames) {
-            try {
-                mContext.getPackageManager().getPackageInfo(packageName, MATCH_SYSTEM_ONLY);
-                permittedPackages.add(packageName);
-            } catch (PackageManager.NameNotFoundException e) {
-                Log.w(TAG, mServiceWatcher + " specified unknown additional provider package: "
-                        + packageName);
-            }
-        }
-
-        synchronized (mProviderPackagesLock) {
-            mProviderPackages.clear();
-            String myPackage = mServiceWatcher.getCurrentPackageName();
-            if (myPackage != null) {
-                mProviderPackages.add(myPackage);
-                mProviderPackages.addAll(permittedPackages);
-            }
-        }
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("service=" + mServiceWatcher);
     }
 }
diff --git a/services/core/java/com/android/server/location/LocationRequestStatistics.java b/services/core/java/com/android/server/location/LocationRequestStatistics.java
index b7ccb26..b1913389 100644
--- a/services/core/java/com/android/server/location/LocationRequestStatistics.java
+++ b/services/core/java/com/android/server/location/LocationRequestStatistics.java
@@ -1,8 +1,29 @@
+/*
+ * 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.location;
 
 import android.os.SystemClock;
 import android.util.Log;
+import android.util.TimeUtils;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.util.ArrayList;
 import java.util.HashMap;
 
 /**
@@ -17,6 +38,8 @@
     public final HashMap<PackageProviderKey, PackageStatistics> statistics
             = new HashMap<PackageProviderKey, PackageStatistics>();
 
+    public final RequestSummaryLimitedHistory history = new RequestSummaryLimitedHistory();
+
     /**
      * Signals that a package has started requesting locations.
      *
@@ -34,6 +57,7 @@
         }
         stats.startRequesting(intervalMs);
         stats.updateForeground(isForeground);
+        history.addRequest(packageName, providerName, intervalMs);
     }
 
     /**
@@ -48,6 +72,7 @@
         if (stats != null) {
             stats.stopRequesting();
         }
+        history.removeRequest(packageName, providerName);
     }
 
     /**
@@ -67,7 +92,7 @@
     /**
      * A key that holds both package and provider names.
      */
-    public static class PackageProviderKey {
+    public static class PackageProviderKey implements Comparable<PackageProviderKey> {
         /**
          * Name of package requesting location.
          */
@@ -77,12 +102,22 @@
          */
         public final String providerName;
 
-        public PackageProviderKey(String packageName, String providerName) {
+        PackageProviderKey(String packageName, String providerName) {
             this.packageName = packageName;
             this.providerName = providerName;
         }
 
         @Override
+        public int compareTo(PackageProviderKey other) {
+            final int providerCompare = providerName.compareTo(other.providerName);
+            if (providerCompare != 0) {
+                return providerCompare;
+            } else {
+                return packageName.compareTo(other.packageName);
+            }
+        }
+
+        @Override
         public boolean equals(Object other) {
             if (!(other instanceof PackageProviderKey)) {
                 return false;
@@ -100,6 +135,104 @@
     }
 
     /**
+     * A data structure to hold past requests
+     */
+    public static class RequestSummaryLimitedHistory {
+        @VisibleForTesting
+        static final int MAX_SIZE = 100;
+
+        final ArrayList<RequestSummary> mList = new ArrayList<>(MAX_SIZE);
+
+        /**
+         * Append an added location request to the history
+         */
+        @VisibleForTesting
+        void addRequest(String packageName, String providerName, long intervalMs) {
+            addRequestSummary(new RequestSummary(packageName, providerName, intervalMs));
+        }
+
+        /**
+         * Append a removed location request to the history
+         */
+        @VisibleForTesting
+        void removeRequest(String packageName, String providerName) {
+            addRequestSummary(new RequestSummary(
+                    packageName, providerName, RequestSummary.REQUEST_ENDED_INTERVAL));
+        }
+
+        private void addRequestSummary(RequestSummary summary) {
+            while (mList.size() >= MAX_SIZE) {
+                mList.remove(0);
+            }
+            mList.add(summary);
+        }
+
+        /**
+         * Dump history to a printwriter (for dumpsys location)
+         */
+        public void dump(IndentingPrintWriter ipw) {
+            long systemElapsedOffsetMillis = System.currentTimeMillis()
+                    - SystemClock.elapsedRealtime();
+
+            ipw.println("Last Several Location Requests:");
+            ipw.increaseIndent();
+
+            for (RequestSummary requestSummary : mList) {
+                requestSummary.dump(ipw, systemElapsedOffsetMillis);
+            }
+
+            ipw.decreaseIndent();
+        }
+    }
+
+    /**
+     * A data structure to hold a single request
+     */
+    static class RequestSummary {
+        /**
+         * Name of package requesting location.
+         */
+        private final String mPackageName;
+        /**
+         * Name of provider being requested (e.g. "gps").
+         */
+        private final String mProviderName;
+        /**
+         * Interval Requested, or REQUEST_ENDED_INTERVAL indicating request has ended
+         */
+        private final long mIntervalMillis;
+        /**
+         * Elapsed time of request
+         */
+        private final long mElapsedRealtimeMillis;
+
+        /**
+         * Placeholder for requested ending (other values indicate request started/changed)
+         */
+        static final long REQUEST_ENDED_INTERVAL = -1;
+
+        RequestSummary(String packageName, String providerName, long intervalMillis) {
+            this.mPackageName = packageName;
+            this.mProviderName = providerName;
+            this.mIntervalMillis = intervalMillis;
+            this.mElapsedRealtimeMillis = SystemClock.elapsedRealtime();
+        }
+
+        void dump(IndentingPrintWriter ipw, long systemElapsedOffsetMillis) {
+            StringBuilder s = new StringBuilder();
+            long systemTimeMillis = systemElapsedOffsetMillis + mElapsedRealtimeMillis;
+            s.append("At ").append(TimeUtils.logTimeOfDay(systemTimeMillis)).append(": ")
+                    .append(mIntervalMillis == REQUEST_ENDED_INTERVAL ? "- " : "+ ")
+                    .append(String.format("%7s", mProviderName)).append(" request from ")
+                    .append(mPackageName);
+            if (mIntervalMillis != REQUEST_ENDED_INTERVAL) {
+                s.append(" at interval ").append(mIntervalMillis / 1000).append(" seconds");
+            }
+            ipw.println(s);
+        }
+    }
+
+    /**
      * Usage statistics for a package/provider pair.
      */
     public static class PackageStatistics {
diff --git a/services/core/java/com/android/server/location/LocationSettingsStore.java b/services/core/java/com/android/server/location/LocationSettingsStore.java
index f625452..0e8720e 100644
--- a/services/core/java/com/android/server/location/LocationSettingsStore.java
+++ b/services/core/java/com/android/server/location/LocationSettingsStore.java
@@ -16,6 +16,8 @@
 
 package com.android.server.location;
 
+import static android.location.LocationManager.FUSED_PROVIDER;
+import static android.location.LocationManager.PASSIVE_PROVIDER;
 import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS;
 import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST;
 import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS;
@@ -28,6 +30,7 @@
 import android.content.Context;
 import android.database.ContentObserver;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -248,6 +251,9 @@
                 DEFAULT_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS);
     }
 
+    /**
+     * Retrieve maximum age of the last location.
+     */
     public long getMaxLastLocationAgeMs() {
         return Settings.Global.getLong(
                 mContext.getContentResolver(),
@@ -256,6 +262,29 @@
     }
 
     /**
+     * Set a value for the deprecated LOCATION_PROVIDERS_ALLOWED setting. This is used purely for
+     * backwards compatibility for old clients, and may be removed in the future.
+     */
+    public void setLocationProviderAllowed(String provider, boolean enabled, int userId) {
+        // fused and passive provider never get public updates for legacy reasons
+        if (FUSED_PROVIDER.equals(provider) || PASSIVE_PROVIDER.equals(provider)) {
+            return;
+        }
+
+        long identity = Binder.clearCallingIdentity();
+        try {
+            // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility
+            Settings.Secure.putStringForUser(
+                    mContext.getContentResolver(),
+                    Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+                    (enabled ? "+" : "-") + provider,
+                    userId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
      * Dump info for debugging.
      */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
diff --git a/services/core/java/com/android/server/location/MockProvider.java b/services/core/java/com/android/server/location/MockProvider.java
index 472876b..60c9fc1 100644
--- a/services/core/java/com/android/server/location/MockProvider.java
+++ b/services/core/java/com/android/server/location/MockProvider.java
@@ -19,7 +19,6 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.location.Location;
-import android.os.WorkSource;
 
 import com.android.internal.location.ProviderProperties;
 import com.android.internal.location.ProviderRequest;
@@ -34,41 +33,33 @@
  */
 public class MockProvider extends AbstractLocationProvider {
 
-    private boolean mEnabled;
     @Nullable private Location mLocation;
 
-    public MockProvider(Context context,
-            LocationProviderManager locationProviderManager, ProviderProperties properties) {
-        super(context, locationProviderManager);
-
-        mEnabled = true;
-        mLocation = null;
-
+    public MockProvider(Context context, ProviderProperties properties) {
+        // using a direct executor is only acceptable because this class is so simple it is trivial
+        // to verify that it does not acquire any locks or re-enter LMS from callbacks
+        super(context, Runnable::run);
         setProperties(properties);
     }
 
     /** Sets the enabled state of this mock provider. */
-    public void setEnabled(boolean enabled) {
-        mEnabled = enabled;
-        super.setEnabled(enabled);
+    public void setProviderEnabled(boolean enabled) {
+        setEnabled(enabled);
     }
 
     /** Sets the location to report for this mock provider. */
-    public void setLocation(Location l) {
-        mLocation = new Location(l);
-        if (!mLocation.isFromMockProvider()) {
-            mLocation.setIsFromMockProvider(true);
-        }
-        if (mEnabled) {
-            reportLocation(mLocation);
-        }
+    public void setProviderLocation(Location l) {
+        Location location = new Location(l);
+        location.setIsFromMockProvider(true);
+        mLocation = location;
+        reportLocation(location);
     }
 
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("last location=" + mLocation);
+        pw.println("last mock location=" + mLocation);
     }
 
     @Override
-    public void onSetRequest(ProviderRequest request, WorkSource source) {}
+    public void onSetRequest(ProviderRequest request) {}
 }
diff --git a/services/core/java/com/android/server/location/MockableLocationProvider.java b/services/core/java/com/android/server/location/MockableLocationProvider.java
new file mode 100644
index 0000000..f50dfe7
--- /dev/null
+++ b/services/core/java/com/android/server/location/MockableLocationProvider.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.location.Location;
+import android.os.Bundle;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.location.ProviderRequest;
+import com.android.internal.util.Preconditions;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Represents a location provider that may switch between a mock implementation and a real
+ * implementation. Requires owners to provide a lock object that will be used internally and held
+ * for the duration of all listener callbacks. Owners are reponsible for ensuring this cannot lead
+ * to deadlock.
+ *
+ * In order to ensure deadlock does not occur, the owner must validate that the ONLY lock which can
+ * be held BOTH when calling into this class AND when receiving a callback from this class is the
+ * lock given to this class via the constructor. Holding any other lock is ok as long as there is no
+ * possibility that it can be obtained within both codepaths.
+ *
+ * Holding the given lock guarantees atomicity of any operations on this class for the duration.
+ *
+ * @hide
+ */
+public class MockableLocationProvider extends AbstractLocationProvider {
+
+    private final Object mOwnerLock;
+
+    @GuardedBy("mOwnerLock")
+    @Nullable private AbstractLocationProvider mProvider;
+    @GuardedBy("mOwnerLock")
+    @Nullable private AbstractLocationProvider mRealProvider;
+    @GuardedBy("mOwnerLock")
+    @Nullable private MockProvider mMockProvider;
+
+    @GuardedBy("mOwnerLock")
+    private ProviderRequest mRequest;
+
+    /**
+     * The given lock object will be held any time the listener is invoked, and may also be acquired
+     * and released during the course of invoking any public methods. Holding the given lock ensures
+     * that provider state cannot change except as result of an explicit call by the owner of the
+     * lock into this class. The client is reponsible for ensuring this cannot cause deadlock.
+     *
+     * The client should expect that it may being to receive callbacks as soon as this constructor
+     * is invoked.
+     */
+    public MockableLocationProvider(Context context, Object ownerLock, Listener listener) {
+        // using a direct executor is acceptable because all inbound calls are delegated to the
+        // actual provider implementations which will use their own executors
+        super(context, Runnable::run, Collections.emptySet());
+        mOwnerLock = ownerLock;
+        mRequest = ProviderRequest.EMPTY_REQUEST;
+
+        setListener(listener);
+    }
+
+    /**
+     * Returns the current provider implementation. May be null if there is no current
+     * implementation.
+     */
+    @Nullable
+    public AbstractLocationProvider getProvider() {
+        synchronized (mOwnerLock) {
+            return mProvider;
+        }
+    }
+
+    /**
+     * Sets the real provider implementation, replacing any previous real provider implementation.
+     * May cause an inline invocation of {@link Listener#onStateChanged(State, State)} if this
+     * results in a state change.
+     */
+    public void setRealProvider(@Nullable AbstractLocationProvider provider) {
+        synchronized (mOwnerLock) {
+            if (mRealProvider == provider) {
+                return;
+            }
+
+            mRealProvider = provider;
+            if (!isMock()) {
+                setProviderLocked(mRealProvider);
+            }
+        }
+    }
+
+    /**
+     * Sets the mock provider implementation, replacing any previous mock provider implementation.
+     * Mock implementations are always used instead of real implementations if set. May cause an
+     * inline invocation of {@link Listener#onStateChanged(State, State)} if this results in a
+     * state change.
+     */
+    public void setMockProvider(@Nullable MockProvider provider) {
+        synchronized (mOwnerLock) {
+            if (mMockProvider == provider) {
+                return;
+            }
+
+            mMockProvider = provider;
+            if (mMockProvider != null) {
+                setProviderLocked(mMockProvider);
+            } else {
+                setProviderLocked(mRealProvider);
+            }
+        }
+    }
+
+    @GuardedBy("mOwnerLock")
+    private void setProviderLocked(@Nullable AbstractLocationProvider provider) {
+        if (mProvider == provider) {
+            return;
+        }
+
+        AbstractLocationProvider oldProvider = mProvider;
+        mProvider = provider;
+
+        if (oldProvider != null) {
+            // do this after switching the provider - so even if the old provider is using a direct
+            // executor, if it re-enters this class within setRequest(), it will be ignored
+            oldProvider.setListener(null);
+            oldProvider.setRequest(ProviderRequest.EMPTY_REQUEST);
+        }
+
+        State newState;
+        if (mProvider != null) {
+            newState = mProvider.setListener(new ListenerWrapper(mProvider));
+        } else {
+            newState = State.EMPTY_STATE;
+        }
+
+        ProviderRequest oldRequest = mRequest;
+        setState(newState);
+
+        if (mProvider != null && oldRequest == mRequest) {
+            mProvider.setRequest(mRequest);
+        }
+    }
+
+    /**
+     * Returns true if the current active provider implementation is the mock implementation, and
+     * false otherwise.
+     */
+    public boolean isMock() {
+        synchronized (mOwnerLock) {
+            return mMockProvider != null && mProvider == mMockProvider;
+        }
+    }
+
+    /**
+     * Sets the mock provider implementation's enabled state. Will throw an exception if the mock
+     * provider is not currently the active implementation.
+     */
+    public void setMockProviderEnabled(boolean enabled) {
+        synchronized (mOwnerLock) {
+            Preconditions.checkState(isMock());
+            mMockProvider.setProviderEnabled(enabled);
+        }
+    }
+    /**
+     * Sets the mock provider implementation's location. Will throw an exception if the mock
+     * provider is not currently the active implementation.
+     */
+    public void setMockProviderLocation(Location location) {
+        synchronized (mOwnerLock) {
+            Preconditions.checkState(isMock());
+            mMockProvider.setProviderLocation(location);
+        }
+    }
+
+    @Override
+    public State getState() {
+        return super.getState();
+    }
+
+    /**
+     * Returns the current location request.
+     */
+    public ProviderRequest getCurrentRequest() {
+        synchronized (mOwnerLock) {
+            return mRequest;
+        }
+    }
+
+    protected void onSetRequest(ProviderRequest request) {
+        synchronized (mOwnerLock) {
+            if (request == mRequest) {
+                return;
+            }
+
+            mRequest = request;
+
+            if (mProvider != null) {
+                mProvider.setRequest(request);
+            }
+        }
+    }
+
+    protected void onExtraCommand(int uid, int pid, String command, Bundle extras) {
+        synchronized (mOwnerLock) {
+            if (mProvider != null) {
+                mProvider.sendExtraCommand(uid, pid, command, extras);
+            }
+        }
+    }
+
+    /**
+     * Dumps the current provider implementation.
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        AbstractLocationProvider provider;
+        synchronized (mOwnerLock) {
+            provider = mProvider;
+            pw.println("request=" + mRequest);
+        }
+
+        if (provider != null) {
+            // dump outside the lock in case the provider wants to acquire its own locks, and since
+            // the default provider dump behavior does not move things onto the provider thread...
+            provider.dump(fd, pw, args);
+        }
+    }
+
+    // ensures that callbacks from the incorrect provider are never visible to clients - this
+    // requires holding the owner's lock for the duration of the callback
+    private class ListenerWrapper implements Listener {
+
+        private final AbstractLocationProvider mListenerProvider;
+
+        private ListenerWrapper(AbstractLocationProvider listenerProvider) {
+            mListenerProvider = listenerProvider;
+        }
+
+        @Override
+        public final void onStateChanged(State oldState, State newState) {
+            synchronized (mOwnerLock) {
+                if (mListenerProvider != mProvider) {
+                    return;
+                }
+
+                setState(newState);
+            }
+        }
+
+        @Override
+        public final void onReportLocation(Location location) {
+            synchronized (mOwnerLock) {
+                if (mListenerProvider != mProvider) {
+                    return;
+                }
+
+                reportLocation(location);
+            }
+        }
+
+        @Override
+        public final void onReportLocation(List<Location> locations) {
+            synchronized (mOwnerLock) {
+                if (mListenerProvider != mProvider) {
+                    return;
+                }
+
+                reportLocation(locations);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/location/NtpTimeHelper.java b/services/core/java/com/android/server/location/NtpTimeHelper.java
index 67841ac..d2296ea 100644
--- a/services/core/java/com/android/server/location/NtpTimeHelper.java
+++ b/services/core/java/com/android/server/location/NtpTimeHelper.java
@@ -130,7 +130,8 @@
 
         // force refresh NTP cache when outdated
         boolean refreshSuccess = true;
-        if (mNtpTime.getCacheAge() >= NTP_INTERVAL) {
+        NtpTrustedTime.TimeResult ntpResult = mNtpTime.getCachedTimeResult();
+        if (ntpResult == null || ntpResult.getAgeMillis() >= NTP_INTERVAL) {
             // Blocking network operation.
             refreshSuccess = mNtpTime.forceRefresh();
         }
@@ -140,17 +141,17 @@
 
             // only update when NTP time is fresh
             // If refreshSuccess is false, cacheAge does not drop down.
-            if (mNtpTime.getCacheAge() < NTP_INTERVAL) {
-                long time = mNtpTime.getCachedNtpTime();
-                long timeReference = mNtpTime.getCachedNtpTimeReference();
-                long certainty = mNtpTime.getCacheCertainty();
+            ntpResult = mNtpTime.getCachedTimeResult();
+            if (ntpResult != null && ntpResult.getAgeMillis() < NTP_INTERVAL) {
+                long time = ntpResult.getTimeMillis();
+                long timeReference = ntpResult.getElapsedRealtimeMillis();
+                long certainty = ntpResult.getCertaintyMillis();
 
                 if (DEBUG) {
                     long now = System.currentTimeMillis();
                     Log.d(TAG, "NTP server returned: "
-                            + time + " (" + new Date(time)
-                            + ") reference: " + timeReference
-                            + " certainty: " + certainty
+                            + time + " (" + new Date(time) + ")"
+                            + " ntpResult: " + ntpResult
                             + " system time offset: " + (time - now));
                 }
 
diff --git a/services/core/java/com/android/server/location/PassiveProvider.java b/services/core/java/com/android/server/location/PassiveProvider.java
index 639b1eb..b338770 100644
--- a/services/core/java/com/android/server/location/PassiveProvider.java
+++ b/services/core/java/com/android/server/location/PassiveProvider.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.location.Criteria;
 import android.location.Location;
-import android.os.WorkSource;
 
 import com.android.internal.location.ProviderProperties;
 import com.android.internal.location.ProviderRequest;
@@ -37,13 +36,22 @@
 public class PassiveProvider extends AbstractLocationProvider {
 
     private static final ProviderProperties PROPERTIES = new ProviderProperties(
-            false, false, false, false, false, false, false,
-            Criteria.POWER_LOW, Criteria.ACCURACY_COARSE);
+            /* requiresNetwork = */false,
+            /* requiresSatellite = */false,
+            /* requiresCell = */false,
+            /* hasMonetaryCost = */false,
+            /* supportsAltitude = */false,
+            /* supportsSpeed = */false,
+            /* supportsBearing = */false,
+            Criteria.POWER_LOW,
+            Criteria.ACCURACY_COARSE);
 
-    private boolean mReportLocation;
+    private volatile boolean mReportLocation;
 
-    public PassiveProvider(Context context, LocationProviderManager locationProviderManager) {
-        super(context, locationProviderManager);
+    public PassiveProvider(Context context) {
+        // using a direct executor is only acceptable because this class is so simple it is trivial
+        // to verify that it does not acquire any locks or re-enter LMS from callbacks
+        super(context, Runnable::run);
 
         mReportLocation = false;
 
@@ -52,7 +60,7 @@
     }
 
     @Override
-    public void onSetRequest(ProviderRequest request, WorkSource source) {
+    public void onSetRequest(ProviderRequest request) {
         mReportLocation = request.reportLocation;
     }
 
@@ -63,7 +71,5 @@
     }
 
     @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("report location=" + mReportLocation);
-    }
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {}
 }
diff --git a/services/core/java/com/android/server/location/UserInfoStore.java b/services/core/java/com/android/server/location/UserInfoStore.java
new file mode 100644
index 0000000..f282ed2
--- /dev/null
+++ b/services/core/java/com/android/server/location/UserInfoStore.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.UserInfo;
+import android.os.Binder;
+import android.os.Build;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
+import com.android.server.FgThread;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Provides accessors and listeners for all user info.
+ */
+public class UserInfoStore {
+
+    /**
+     * Listener for current user changes.
+     */
+    public interface UserChangedListener {
+        /**
+         * Called when the current user changes.
+         */
+        void onUserChanged(@UserIdInt int oldUserId, @UserIdInt int newUserId);
+    }
+
+    private final Context mContext;
+    private final CopyOnWriteArrayList<UserChangedListener> mListeners;
+
+    @GuardedBy("this")
+    @Nullable
+    private UserManager mUserManager;
+
+    @GuardedBy("this")
+    @UserIdInt
+    private int mCurrentUserId;
+
+    @GuardedBy("this")
+    @UserIdInt
+    private int mCachedParentUserId;
+    @GuardedBy("this")
+    private int[] mCachedProfileUserIds;
+
+    public UserInfoStore(Context context) {
+        mContext = context;
+        mListeners = new CopyOnWriteArrayList<>();
+
+        mCurrentUserId = UserHandle.USER_NULL;
+        mCachedParentUserId = UserHandle.USER_NULL;
+        mCachedProfileUserIds = new int[]{UserHandle.USER_NULL};
+    }
+
+    /** Called when system is ready. */
+    public synchronized void onSystemReady() {
+        if (mUserManager != null) {
+            return;
+        }
+
+        mUserManager = mContext.getSystemService(UserManager.class);
+
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
+        intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
+        intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
+
+        mContext.registerReceiverAsUser(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                if (action == null) {
+                    return;
+                }
+                switch (action) {
+                    case Intent.ACTION_USER_SWITCHED:
+                        int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
+                                UserHandle.USER_NULL);
+                        if (userId != UserHandle.USER_NULL) {
+                            onUserChanged(userId);
+                        }
+                        break;
+                    case Intent.ACTION_MANAGED_PROFILE_ADDED:
+                    case Intent.ACTION_MANAGED_PROFILE_REMOVED:
+                        onUserProfilesChanged();
+                        break;
+                }
+            }
+        }, UserHandle.ALL, intentFilter, null, FgThread.getHandler());
+
+        mCurrentUserId = ActivityManager.getCurrentUser();
+    }
+
+    /**
+     * Adds a listener for user changed events.
+     */
+    public void addListener(UserChangedListener listener) {
+        mListeners.add(listener);
+    }
+
+    /**
+     * Removes a listener for user changed events.
+     */
+    public void removeListener(UserChangedListener listener) {
+        mListeners.remove(listener);
+    }
+
+    private void onUserChanged(@UserIdInt int newUserId) {
+        int oldUserId;
+        synchronized (this) {
+            if (newUserId == mCurrentUserId) {
+                return;
+            }
+
+            oldUserId = mCurrentUserId;
+            mCurrentUserId = newUserId;
+        }
+
+        for (UserChangedListener listener : mListeners) {
+            listener.onUserChanged(oldUserId, newUserId);
+        }
+    }
+
+    private synchronized void onUserProfilesChanged() {
+        // this intent is only sent to the current user
+        if (mCachedParentUserId == mCurrentUserId) {
+            mCachedParentUserId = UserHandle.USER_NULL;
+            mCachedProfileUserIds = new int[]{UserHandle.USER_NULL};
+        }
+    }
+
+    /**
+     * Returns the user id of the current user.
+     */
+    @UserIdInt
+    public synchronized int getCurrentUserId() {
+        return mCurrentUserId;
+    }
+
+    /**
+     * Returns true if the given user id is either the current user or a profile of the current
+     * user.
+     */
+    public synchronized boolean isCurrentUserOrProfile(@UserIdInt int userId) {
+        return userId == mCurrentUserId || ArrayUtils.contains(
+                getProfileUserIdsForParentUser(mCurrentUserId), userId);
+    }
+
+    /**
+     * Returns the parent user id of the given user id, or the user id itself if the user id either
+     * is a parent or has no profiles.
+     */
+    @UserIdInt
+    public synchronized int getParentUserId(@UserIdInt int userId) {
+        int parentUserId;
+        if (userId == mCachedParentUserId || ArrayUtils.contains(mCachedProfileUserIds, userId)) {
+            parentUserId = mCachedParentUserId;
+        } else {
+            Preconditions.checkState(mUserManager != null);
+
+            long identity = Binder.clearCallingIdentity();
+            try {
+                UserInfo userInfo = mUserManager.getProfileParent(userId);
+                if (userInfo != null) {
+                    parentUserId = userInfo.id;
+                } else {
+                    // getProfileParent() returns null if the userId is already the parent...
+                    parentUserId = userId;
+                }
+
+                // force profiles into cache
+                getProfileUserIdsForParentUser(parentUserId);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        return parentUserId;
+    }
+
+    @GuardedBy("this")
+    private int[] getProfileUserIdsForParentUser(@UserIdInt int parentUserId) {
+        Preconditions.checkState(mUserManager != null);
+
+        // only assert on debug builds as this is a more expensive check
+        if (Build.IS_DEBUGGABLE) {
+            long identity = Binder.clearCallingIdentity();
+            try {
+                Preconditions.checkArgument(mUserManager.getProfileParent(parentUserId) == null);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        if (parentUserId != mCachedParentUserId) {
+            long identity = Binder.clearCallingIdentity();
+            try {
+                mCachedParentUserId = parentUserId;
+                mCachedProfileUserIds = mUserManager.getProfileIdsWithDisabled(parentUserId);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        return mCachedProfileUserIds;
+    }
+
+    /**
+     * Dump info for debugging.
+     */
+    public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("Current User: " + mCurrentUserId + " " + Arrays.toString(
+                getProfileUserIdsForParentUser(mCurrentUserId)));
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 7233f34..baae4ea 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -1622,7 +1622,7 @@
         }
 
         if (credential.isNone()) {
-            clearUserKeyProtection(userId);
+            clearUserKeyProtection(userId, null);
             gateKeeperClearSecureUserId(userId);
             mStorage.writeCredentialHash(CredentialHash.createEmptyHash(), userId);
             // Still update PASSWORD_TYPE_KEY if we are running in pre-synthetic password code path,
@@ -1813,9 +1813,17 @@
         addUserKeyAuth(userId, token, secretFromCredential(credential));
     }
 
-    private void clearUserKeyProtection(int userId) {
+    private void clearUserKeyProtection(int userId, byte[] secret) {
         if (DEBUG) Slog.d(TAG, "clearUserKeyProtection user=" + userId);
-        addUserKeyAuth(userId, null, null);
+        final UserInfo userInfo = mUserManager.getUserInfo(userId);
+        final long callingId = Binder.clearCallingIdentity();
+        try {
+            mStorageManager.clearUserKeyAuth(userId, userInfo.serialNumber, null, secret);
+        } catch (RemoteException e) {
+            throw new IllegalStateException("clearUserKeyAuth failed user=" + userId);
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
     }
 
     private static byte[] secretFromCredential(LockscreenCredential credential) {
@@ -2571,7 +2579,7 @@
             setAuthlessUserKeyProtection(userId, auth.deriveDiskEncryptionKey());
             setKeystorePassword(auth.deriveKeyStorePassword(), userId);
         } else {
-            clearUserKeyProtection(userId);
+            clearUserKeyProtection(userId, null);
             setKeystorePassword(null, userId);
             gateKeeperClearSecureUserId(userId);
         }
@@ -2765,7 +2773,7 @@
             // during boot. Vold storage needs to be unlocked before manipulation of the keys can
             // succeed.
             unlockUserKey(userId, null, auth.deriveDiskEncryptionKey());
-            clearUserKeyProtection(userId);
+            clearUserKeyProtection(userId, auth.deriveDiskEncryptionKey());
             fixateNewestUserKeyAuth(userId);
             unlockKeystore(auth.deriveKeyStorePassword(), userId);
             setKeystorePassword(null, userId);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index 0f8561e..4943c25 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -24,6 +24,7 @@
 import android.app.ActivityManager;
 import android.app.admin.PasswordMetrics;
 import android.os.ShellCommand;
+import android.text.TextUtils;
 
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
@@ -195,6 +196,9 @@
     }
 
     private LockscreenCredential getOldCredential() {
+        if (TextUtils.isEmpty(mOld)) {
+            return LockscreenCredential.createNone();
+        }
         if (mLockPatternUtils.isLockPasswordEnabled(mCurrentUserId)) {
             final int quality = mLockPatternUtils.getKeyguardStoredPasswordQuality(mCurrentUserId);
             if (LockPatternUtils.isQualityAlphabeticPassword(quality)) {
@@ -202,12 +206,15 @@
             } else {
                 return LockscreenCredential.createPin(mOld);
             }
-        } else if (mLockPatternUtils.isLockPatternEnabled(mCurrentUserId)) {
+        }
+        if (mLockPatternUtils.isLockPatternEnabled(mCurrentUserId)) {
             return LockscreenCredential.createPattern(LockPatternUtils.byteArrayToPattern(
                     mOld.getBytes()));
-        } else {
-            return LockscreenCredential.createNone();
         }
+        // User supplied some old credential but the device has neither password nor pattern,
+        // so just return a password credential (and let it be rejected during LSS verification)
+        return LockscreenCredential.createPassword(mOld);
+
     }
 
     private boolean runSetPattern() {
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
index d2e54f9..46ea9d1 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -25,11 +25,13 @@
 import android.os.ServiceManager;
 import android.os.UserManager;
 import android.util.Slog;
+import android.util.StatsLog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.RebootEscrowListener;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -109,20 +111,50 @@
     }
 
     void loadRebootEscrowDataIfAvailable() {
-        IRebootEscrow rebootEscrow = mInjector.getRebootEscrow();
-        if (rebootEscrow == null) {
+        List<UserInfo> users = mUserManager.getUsers();
+        List<UserInfo> rebootEscrowUsers = new ArrayList<>();
+        for (UserInfo user : users) {
+            if (mCallbacks.isUserSecure(user.id) && mStorage.hasRebootEscrow(user.id)) {
+                rebootEscrowUsers.add(user);
+            }
+        }
+
+        if (rebootEscrowUsers.isEmpty()) {
             return;
         }
 
-        final SecretKeySpec escrowKey;
+        SecretKeySpec escrowKey = getAndClearRebootEscrowKey();
+        if (escrowKey == null) {
+            Slog.w(TAG, "Had reboot escrow data for users, but no key; removing escrow storage.");
+            for (UserInfo user : users) {
+                mStorage.removeRebootEscrow(user.id);
+            }
+            StatsLog.write(StatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, false);
+            return;
+        }
+
+        boolean allUsersUnlocked = true;
+        for (UserInfo user : rebootEscrowUsers) {
+            allUsersUnlocked &= restoreRebootEscrowForUser(user.id, escrowKey);
+        }
+        StatsLog.write(StatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, allUsersUnlocked);
+    }
+
+    private SecretKeySpec getAndClearRebootEscrowKey() {
+        IRebootEscrow rebootEscrow = mInjector.getRebootEscrow();
+        if (rebootEscrow == null) {
+            return null;
+        }
+
         try {
             byte[] escrowKeyBytes = rebootEscrow.retrieveKey();
             if (escrowKeyBytes == null) {
-                return;
+                Slog.w(TAG, "Had reboot escrow data for users, but could not retrieve key");
+                return null;
             } else if (escrowKeyBytes.length != 32) {
                 Slog.e(TAG, "IRebootEscrow returned key of incorrect size "
                         + escrowKeyBytes.length);
-                return;
+                return null;
             }
 
             // Make sure we didn't get the null key.
@@ -132,29 +164,22 @@
             }
             if (zero == 0) {
                 Slog.w(TAG, "IRebootEscrow returned an all-zeroes key");
-                return;
+                return null;
             }
 
             // Overwrite the existing key with the null key
             rebootEscrow.storeKey(new byte[32]);
 
-            escrowKey = RebootEscrowData.fromKeyBytes(escrowKeyBytes);
+            return RebootEscrowData.fromKeyBytes(escrowKeyBytes);
         } catch (RemoteException e) {
             Slog.w(TAG, "Could not retrieve escrow data");
-            return;
-        }
-
-        List<UserInfo> users = mUserManager.getUsers();
-        for (UserInfo user : users) {
-            if (mCallbacks.isUserSecure(user.id)) {
-                restoreRebootEscrowForUser(user.id, escrowKey);
-            }
+            return null;
         }
     }
 
-    private void restoreRebootEscrowForUser(@UserIdInt int userId, SecretKeySpec escrowKey) {
+    private boolean restoreRebootEscrowForUser(@UserIdInt int userId, SecretKeySpec escrowKey) {
         if (!mStorage.hasRebootEscrow(userId)) {
-            return;
+            return false;
         }
 
         try {
@@ -165,9 +190,11 @@
 
             mCallbacks.onRebootEscrowRestored(escrowData.getSpVersion(),
                     escrowData.getSyntheticPassword(), userId);
+            return true;
         } catch (IOException e) {
             Slog.w(TAG, "Could not load reboot escrow data for user " + userId, e);
         }
+        return false;
     }
 
     void callToRebootEscrowIfNeeded(@UserIdInt int userId, byte spVersion,
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
index 29338ba0..52750f3 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
@@ -20,6 +20,7 @@
 
 import android.content.Context;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.security.Scrypt;
 import android.security.keystore.recovery.KeyChainProtectionParams;
 import android.security.keystore.recovery.KeyChainSnapshot;
@@ -163,16 +164,28 @@
     }
 
     private void syncKeys() throws RemoteException {
+        int generation = mPlatformKeyManager.getGenerationId(mUserId);
         if (mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
             // Application keys for the user will not be available for sync.
             Log.w(TAG, "Credentials are not set for user " + mUserId);
-            int generation = mPlatformKeyManager.getGenerationId(mUserId);
-            mPlatformKeyManager.invalidatePlatformKey(mUserId, generation);
+            if (generation < PlatformKeyManager.MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED
+                    || mUserId != UserHandle.USER_SYSTEM) {
+                // Only invalidate keys with legacy protection param.
+                mPlatformKeyManager.invalidatePlatformKey(mUserId, generation);
+            }
             return;
         }
         if (isCustomLockScreen()) {
-            Log.w(TAG, "Unsupported credential type " + mCredentialType + "for user " + mUserId);
-            mRecoverableKeyStoreDb.invalidateKeysForUserIdOnCustomScreenLock(mUserId);
+            Log.w(TAG, "Unsupported credential type " + mCredentialType + " for user " + mUserId);
+            // Keys will be synced when user starts using non custom screen lock.
+            if (generation < PlatformKeyManager.MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED
+                    || mUserId != UserHandle.USER_SYSTEM) {
+                mRecoverableKeyStoreDb.invalidateKeysForUserIdOnCustomScreenLock(mUserId);
+            }
+            return;
+        }
+        if (mPlatformKeyManager.isDeviceLocked(mUserId) && mUserId == UserHandle.USER_SYSTEM) {
+            Log.w(TAG, "Can't sync keys for locked user " + mUserId);
             return;
         }
 
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
index 0ad6c2a..0761cde 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
@@ -67,8 +67,9 @@
  * @hide
  */
 public class PlatformKeyManager {
-    private static final String TAG = "PlatformKeyManager";
+    static final int MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED = 1000000;
 
+    private static final String TAG = "PlatformKeyManager";
     private static final String KEY_ALGORITHM = "AES";
     private static final int KEY_SIZE_BITS = 256;
     private static final String KEY_ALIAS_PREFIX =
@@ -131,14 +132,14 @@
 
     /**
      * Returns {@code true} if the platform key is available. A platform key won't be available if
-     * the user has not set up a lock screen.
+     * device is locked.
      *
      * @param userId The ID of the user to whose lock screen the platform key must be bound.
      *
      * @hide
      */
-    public boolean isAvailable(int userId) {
-        return mContext.getSystemService(KeyguardManager.class).isDeviceSecure(userId);
+    public boolean isDeviceLocked(int userId) {
+        return mContext.getSystemService(KeyguardManager.class).isDeviceLocked(userId);
     }
 
     /**
@@ -169,7 +170,6 @@
      * @param userId The ID of the user to whose lock screen the platform key must be bound.
      * @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
      * @throws KeyStoreException if there is an error in AndroidKeyStore.
-     * @throws InsecureUserException if the user does not have a lock screen set.
      * @throws IOException if there was an issue with local database update.
      * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
      *
@@ -177,13 +177,8 @@
      */
     @VisibleForTesting
     void regenerate(int userId)
-            throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException, IOException,
+            throws NoSuchAlgorithmException, KeyStoreException, IOException,
                     RemoteException {
-        if (!isAvailable(userId)) {
-            throw new InsecureUserException(String.format(
-                    Locale.US, "%d does not have a lock screen set.", userId));
-        }
-
         int generationId = getGenerationId(userId);
         int nextId;
         if (generationId == -1) {
@@ -192,6 +187,7 @@
             invalidatePlatformKey(userId, generationId);
             nextId = generationId + 1;
         }
+        generationId = Math.max(generationId, MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED);
         generateAndLoadKey(userId, nextId);
     }
 
@@ -203,7 +199,6 @@
      * @throws KeyStoreException if there was an AndroidKeyStore error.
      * @throws UnrecoverableKeyException if the key could not be recovered.
      * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
-     * @throws InsecureUserException if the user does not have a lock screen set.
      * @throws IOException if there was an issue with local database update.
      * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
      *
@@ -211,7 +206,7 @@
      */
     public PlatformEncryptionKey getEncryptKey(int userId)
             throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException,
-                    InsecureUserException, IOException, RemoteException {
+                    IOException, RemoteException {
         init(userId);
         try {
             // Try to see if the decryption key is still accessible before using the encryption key.
@@ -234,12 +229,11 @@
      * @throws KeyStoreException if there was an AndroidKeyStore error.
      * @throws UnrecoverableKeyException if the key could not be recovered.
      * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
-     * @throws InsecureUserException if the user does not have a lock screen set.
      *
      * @hide
      */
     private PlatformEncryptionKey getEncryptKeyInternal(int userId) throws KeyStoreException,
-           UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
+            UnrecoverableKeyException, NoSuchAlgorithmException {
         int generationId = getGenerationId(userId);
         String alias = getEncryptAlias(userId, generationId);
         if (!isKeyLoaded(userId, generationId)) {
@@ -258,7 +252,6 @@
      * @throws KeyStoreException if there was an AndroidKeyStore error.
      * @throws UnrecoverableKeyException if the key could not be recovered.
      * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
-     * @throws InsecureUserException if the user does not have a lock screen set.
      * @throws IOException if there was an issue with local database update.
      * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
      *
@@ -266,7 +259,7 @@
      */
     public PlatformDecryptionKey getDecryptKey(int userId)
             throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException,
-                    InsecureUserException, IOException, RemoteException {
+                    IOException, RemoteException {
         init(userId);
         try {
             PlatformDecryptionKey decryptionKey = getDecryptKeyInternal(userId);
@@ -288,12 +281,11 @@
      * @throws KeyStoreException if there was an AndroidKeyStore error.
      * @throws UnrecoverableKeyException if the key could not be recovered.
      * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
-     * @throws InsecureUserException if the user does not have a lock screen set.
      *
      * @hide
      */
     private PlatformDecryptionKey getDecryptKeyInternal(int userId) throws KeyStoreException,
-           UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
+            UnrecoverableKeyException, NoSuchAlgorithmException {
         int generationId = getGenerationId(userId);
         String alias = getDecryptAlias(userId, generationId);
         if (!isKeyLoaded(userId, generationId)) {
@@ -340,13 +332,8 @@
      * @hide
      */
     void init(int userId)
-            throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException, IOException,
+            throws KeyStoreException, NoSuchAlgorithmException, IOException,
                     RemoteException {
-        if (!isAvailable(userId)) {
-            throw new InsecureUserException(String.format(
-                    Locale.US, "%d does not have a lock screen set.", userId));
-        }
-
         int generationId = getGenerationId(userId);
         if (isKeyLoaded(userId, generationId)) {
             Log.i(TAG, String.format(
@@ -363,6 +350,7 @@
             generationId++;
         }
 
+        generationId = Math.max(generationId, MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED);
         generateAndLoadKey(userId, generationId);
     }
 
@@ -440,12 +428,16 @@
 
         KeyProtection.Builder decryptionKeyProtection =
                 new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
-                    .setUserAuthenticationRequired(true)
-                    .setUserAuthenticationValidityDurationSeconds(
-                            USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS)
                     .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                     .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE);
-        if (userId != UserHandle.USER_SYSTEM) {
+        // Skip UserAuthenticationRequired for main user
+        if (userId ==  UserHandle.USER_SYSTEM) {
+            decryptionKeyProtection.setUnlockedDeviceRequired(true);
+        } else {
+            // With setUnlockedDeviceRequired, KeyStore thinks that device is locked .
+            decryptionKeyProtection.setUserAuthenticationRequired(true);
+            decryptionKeyProtection.setUserAuthenticationValidityDurationSeconds(
+                            USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS);
             // Bind decryption key to secondary profile lock screen secret.
             long secureUserId = getGateKeeperService().getSecureUserId(userId);
             // TODO(b/124095438): Propagate this failure instead of silently failing.
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index 383d5cf..6d97ed7 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -19,7 +19,6 @@
 import static android.security.keystore.recovery.RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT;
 import static android.security.keystore.recovery.RecoveryController.ERROR_DECRYPTION_FAILED;
 import static android.security.keystore.recovery.RecoveryController.ERROR_DOWNGRADE_CERTIFICATE;
-import static android.security.keystore.recovery.RecoveryController.ERROR_INSECURE_USER;
 import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_CERTIFICATE;
 import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_KEY_FORMAT;
 import static android.security.keystore.recovery.RecoveryController.ERROR_NO_SNAPSHOT_PENDING;
@@ -46,7 +45,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.HexDump;
-import com.android.internal.util.Preconditions;
 import com.android.server.locksettings.recoverablekeystore.certificate.CertParsingException;
 import com.android.server.locksettings.recoverablekeystore.certificate.CertUtils;
 import com.android.server.locksettings.recoverablekeystore.certificate.CertValidationException;
@@ -76,8 +74,9 @@
 import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
-import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
 
 import javax.crypto.AEADBadTagException;
 
@@ -89,13 +88,14 @@
  */
 public class RecoverableKeyStoreManager {
     private static final String TAG = "RecoverableKeyStoreMgr";
+    private static final long SYNC_DELAY_MILLIS = 2000;
 
     private static RecoverableKeyStoreManager mInstance;
 
     private final Context mContext;
     private final RecoverableKeyStoreDb mDatabase;
     private final RecoverySessionStorage mRecoverySessionStorage;
-    private final ExecutorService mExecutorService;
+    private final ScheduledExecutorService mExecutorService;
     private final RecoverySnapshotListenersStorage mListenersStorage;
     private final RecoverableKeyGenerator mRecoverableKeyGenerator;
     private final RecoverySnapshotStorage mSnapshotStorage;
@@ -136,7 +136,7 @@
                     context.getApplicationContext(),
                     db,
                     new RecoverySessionStorage(),
-                    Executors.newSingleThreadExecutor(),
+                    Executors.newScheduledThreadPool(1),
                     snapshotStorage,
                     new RecoverySnapshotListenersStorage(),
                     platformKeyManager,
@@ -152,7 +152,7 @@
             Context context,
             RecoverableKeyStoreDb recoverableKeyStoreDb,
             RecoverySessionStorage recoverySessionStorage,
-            ExecutorService executorService,
+            ScheduledExecutorService executorService,
             RecoverySnapshotStorage snapshotStorage,
             RecoverySnapshotListenersStorage listenersStorage,
             PlatformKeyManager platformKeyManager,
@@ -724,8 +724,6 @@
             throw new RuntimeException(e);
         } catch (KeyStoreException | UnrecoverableKeyException | IOException e) {
             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
-        } catch (InsecureUserException e) {
-            throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
         }
 
         try {
@@ -793,8 +791,6 @@
             throw new RuntimeException(e);
         } catch (KeyStoreException | UnrecoverableKeyException | IOException e) {
             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
-        } catch (InsecureUserException e) {
-            throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
         }
 
         try {
@@ -915,7 +911,7 @@
             int storedHashType, @NonNull byte[] credential, int userId) {
         // So as not to block the critical path unlocking the phone, defer to another thread.
         try {
-            mExecutorService.execute(KeySyncTask.newInstance(
+            mExecutorService.schedule(KeySyncTask.newInstance(
                     mContext,
                     mDatabase,
                     mSnapshotStorage,
@@ -923,7 +919,10 @@
                     userId,
                     storedHashType,
                     credential,
-                    /*credentialUpdated=*/ false));
+                    /*credentialUpdated=*/ false),
+                    SYNC_DELAY_MILLIS,
+                    TimeUnit.MILLISECONDS
+            );
         } catch (NoSuchAlgorithmException e) {
             Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
         } catch (KeyStoreException e) {
@@ -947,7 +946,7 @@
             int userId) {
         // So as not to block the critical path unlocking the phone, defer to another thread.
         try {
-            mExecutorService.execute(KeySyncTask.newInstance(
+            mExecutorService.schedule(KeySyncTask.newInstance(
                     mContext,
                     mDatabase,
                     mSnapshotStorage,
@@ -955,7 +954,10 @@
                     userId,
                     storedHashType,
                     credential,
-                    /*credentialUpdated=*/ true));
+                    /*credentialUpdated=*/ true),
+                    SYNC_DELAY_MILLIS,
+                    TimeUnit.MILLISECONDS
+            );
         } catch (NoSuchAlgorithmException e) {
             Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
         } catch (KeyStoreException e) {
diff --git a/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
index 1d39177..b0bccb8 100644
--- a/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
+++ b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
@@ -172,13 +172,14 @@
      */
     public void cleanUpAudioPlaybackUids(int mediaButtonSessionUid) {
         synchronized (mLock) {
-            int userId = UserHandle.getUserId(mediaButtonSessionUid);
+            int userId = UserHandle.getUserHandleForUid(mediaButtonSessionUid).getIdentifier();
             for (int i = mSortedAudioPlaybackClientUids.size() - 1; i >= 0; i--) {
                 if (mSortedAudioPlaybackClientUids.get(i) == mediaButtonSessionUid) {
                     break;
                 }
                 int uid = mSortedAudioPlaybackClientUids.get(i);
-                if (userId == UserHandle.getUserId(uid) && !isPlaybackActive(uid)) {
+                if (userId == UserHandle.getUserHandleForUid(uid).getIdentifier()
+                        && !isPlaybackActive(uid)) {
                     // Clean up unnecessary UIDs.
                     // It doesn't need to be managed profile aware because it's just to prevent
                     // the list from increasing indefinitely. The media button session updating
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
new file mode 100644
index 0000000..8179c32
--- /dev/null
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -0,0 +1,383 @@
+/*
+ * 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.media;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.MediaRoute2Info;
+import android.text.TextUtils;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+class BluetoothRouteProvider {
+    private static final String TAG = "BTRouteProvider";
+    private static BluetoothRouteProvider sInstance;
+
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+    final Map<String, BluetoothRouteInfo> mBluetoothRoutes = new HashMap<>();
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+    BluetoothA2dp mA2dpProfile;
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+    BluetoothHearingAid mHearingAidProfile;
+
+    private final Context mContext;
+    private final BluetoothAdapter mBluetoothAdapter;
+    private final BluetoothRoutesUpdatedListener mListener;
+    private final Map<String, BluetoothEventReceiver> mEventReceiverMap = new HashMap<>();
+    private final IntentFilter mIntentFilter = new IntentFilter();
+    private final BroadcastReceiver mBroadcastReceiver = new BluetoothBroadcastReceiver();
+    private final BluetoothProfileListener mProfileListener = new BluetoothProfileListener();
+
+    private BluetoothDevice mActiveDevice = null;
+
+    static synchronized BluetoothRouteProvider getInstance(@NonNull Context context,
+            @NonNull BluetoothRoutesUpdatedListener listener) {
+        Objects.requireNonNull(context);
+        Objects.requireNonNull(listener);
+
+        if (sInstance == null) {
+            BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
+            if (btAdapter == null) {
+                return null;
+            }
+            sInstance = new BluetoothRouteProvider(context, btAdapter, listener);
+        }
+        return sInstance;
+    }
+
+    private BluetoothRouteProvider(Context context, BluetoothAdapter btAdapter,
+            BluetoothRoutesUpdatedListener listener) {
+        mContext = context;
+        mBluetoothAdapter = btAdapter;
+        mListener = listener;
+        buildBluetoothRoutes();
+
+        mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP);
+        mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEARING_AID);
+
+        // Bluetooth on/off broadcasts
+        addEventReceiver(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedReceiver());
+
+        // Pairing broadcasts
+        addEventReceiver(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedReceiver());
+
+        DeviceStateChangedRecevier deviceStateChangedReceiver = new DeviceStateChangedRecevier();
+        addEventReceiver(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED, deviceStateChangedReceiver);
+        addEventReceiver(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED, deviceStateChangedReceiver);
+        addEventReceiver(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED,
+                deviceStateChangedReceiver);
+        addEventReceiver(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED,
+                deviceStateChangedReceiver);
+
+        mContext.registerReceiver(mBroadcastReceiver, mIntentFilter, null, null);
+    }
+
+    /**
+     * Clears the active device for all known profiles.
+     */
+    public void clearActiveDevices() {
+        BluetoothA2dp a2dpProfile = mA2dpProfile;
+        BluetoothHearingAid hearingAidProfile = mHearingAidProfile;
+        if (a2dpProfile != null) {
+            a2dpProfile.setActiveDevice(null);
+        }
+        if (hearingAidProfile != null) {
+            hearingAidProfile.setActiveDevice(null);
+        }
+    }
+
+    /**
+     * Sets the active device.
+     * @param deviceId the id of the Bluetooth device
+     */
+    public void setActiveDevice(@NonNull String deviceId) {
+        BluetoothRouteInfo btRouteInfo = mBluetoothRoutes.get(deviceId);
+        if (btRouteInfo == null) {
+            Slog.w(TAG, "setActiveDevice: unknown device id=" + deviceId);
+            return;
+        }
+        BluetoothA2dp a2dpProfile = mA2dpProfile;
+        BluetoothHearingAid hearingAidProfile = mHearingAidProfile;
+
+        if (a2dpProfile != null
+                && btRouteInfo.connectedProfiles.get(BluetoothProfile.A2DP, false)) {
+            a2dpProfile.setActiveDevice(btRouteInfo.btDevice);
+        }
+        if (hearingAidProfile != null
+                && btRouteInfo.connectedProfiles.get(BluetoothProfile.HEARING_AID, false)) {
+            hearingAidProfile.setActiveDevice(btRouteInfo.btDevice);
+        }
+    }
+
+    private void addEventReceiver(String action, BluetoothEventReceiver eventReceiver) {
+        mEventReceiverMap.put(action, eventReceiver);
+        mIntentFilter.addAction(action);
+    }
+
+    private void buildBluetoothRoutes() {
+        mBluetoothRoutes.clear();
+        for (BluetoothDevice device : mBluetoothAdapter.getBondedDevices()) {
+            if (device.isConnected()) {
+                BluetoothRouteInfo newBtRoute = createBluetoothRoute(device);
+                mBluetoothRoutes.put(device.getAddress(), newBtRoute);
+            }
+        }
+    }
+
+    @NonNull List<MediaRoute2Info> getBluetoothRoutes() {
+        ArrayList<MediaRoute2Info> routes = new ArrayList<>();
+        for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) {
+            routes.add(btRoute.route);
+        }
+        return routes;
+    }
+
+    @Nullable String getActiveDeviceAddress() {
+        BluetoothDevice device = mActiveDevice;
+        if (device == null) {
+            return null;
+        }
+        return device.getAddress();
+    }
+
+    private void notifyBluetoothRoutesUpdated() {
+        if (mListener != null) {
+            mListener.onBluetoothRoutesUpdated(getBluetoothRoutes());
+        }
+    }
+
+    private BluetoothRouteInfo createBluetoothRoute(BluetoothDevice device) {
+        BluetoothRouteInfo newBtRoute = new BluetoothRouteInfo();
+        newBtRoute.btDevice = device;
+        newBtRoute.route = new MediaRoute2Info.Builder(device.getAddress(), device.getName())
+                .addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO)
+                .setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED)
+                .setDescription(mContext.getResources().getText(
+                        R.string.bluetooth_a2dp_audio_route_name).toString())
+                .build();
+        newBtRoute.connectedProfiles = new SparseBooleanArray();
+        return newBtRoute;
+    }
+
+    private void setRouteConnectionStateForDevice(BluetoothDevice device,
+            @MediaRoute2Info.ConnectionState int state) {
+        if (device == null) {
+            Slog.w(TAG, "setRouteConnectionStateForDevice: device shouldn't be null");
+            return;
+        }
+        BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
+        if (btRoute == null) {
+            Slog.w(TAG, "setRouteConnectionStateForDevice: route shouldn't be null");
+            return;
+        }
+        if (btRoute.route.getConnectionState() != state) {
+            btRoute.route = new MediaRoute2Info.Builder(btRoute.route)
+                    .setConnectionState(state).build();
+        }
+    }
+
+    interface BluetoothRoutesUpdatedListener {
+        void onBluetoothRoutesUpdated(@NonNull List<MediaRoute2Info> routes);
+    }
+
+    private class BluetoothRouteInfo {
+        public BluetoothDevice btDevice;
+        public MediaRoute2Info route;
+        public SparseBooleanArray connectedProfiles;
+    }
+
+    // These callbacks run on the main thread.
+    private final class BluetoothProfileListener implements BluetoothProfile.ServiceListener {
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            List<BluetoothDevice> activeDevices;
+            switch (profile) {
+                case BluetoothProfile.A2DP:
+                    mA2dpProfile = (BluetoothA2dp) proxy;
+                    // It may contain null.
+                    activeDevices = Collections.singletonList(mA2dpProfile.getActiveDevice());
+                    break;
+                case BluetoothProfile.HEARING_AID:
+                    mHearingAidProfile = (BluetoothHearingAid) proxy;
+                    activeDevices = mHearingAidProfile.getActiveDevices();
+                    break;
+                default:
+                    return;
+            }
+            //TODO: Check a pair of HAP devices whether there exist two or more active devices.
+            for (BluetoothDevice device : proxy.getConnectedDevices()) {
+                BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
+                if (btRoute == null) {
+                    btRoute = createBluetoothRoute(device);
+                    mBluetoothRoutes.put(device.getAddress(), btRoute);
+                }
+                if (activeDevices.contains(device)) {
+                    mActiveDevice = device;
+                    setRouteConnectionStateForDevice(device,
+                            MediaRoute2Info.CONNECTION_STATE_CONNECTED);
+                }
+
+                btRoute.connectedProfiles.put(profile, true);
+            }
+            notifyBluetoothRoutesUpdated();
+        }
+
+        public void onServiceDisconnected(int profile) {
+            switch (profile) {
+                case BluetoothProfile.A2DP:
+                    mA2dpProfile = null;
+                    break;
+                case BluetoothProfile.HEARING_AID:
+                    mHearingAidProfile = null;
+                    break;
+                default:
+                    return;
+            }
+        }
+    }
+    private class BluetoothBroadcastReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+
+            BluetoothEventReceiver receiver = mEventReceiverMap.get(action);
+            if (receiver != null) {
+                receiver.onReceive(context, intent, device);
+            }
+        }
+    }
+
+    private interface BluetoothEventReceiver {
+        void onReceive(Context context, Intent intent, BluetoothDevice device);
+    }
+
+    private class AdapterStateChangedReceiver implements BluetoothEventReceiver {
+        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+            int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
+            if (state == BluetoothAdapter.STATE_OFF
+                    || state == BluetoothAdapter.STATE_TURNING_OFF) {
+                mBluetoothRoutes.clear();
+                notifyBluetoothRoutesUpdated();
+            } else if (state == BluetoothAdapter.STATE_ON) {
+                buildBluetoothRoutes();
+                if (!mBluetoothRoutes.isEmpty()) {
+                    notifyBluetoothRoutesUpdated();
+                }
+            }
+        }
+    }
+
+    private class BondStateChangedReceiver implements BluetoothEventReceiver {
+        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+            int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
+                    BluetoothDevice.ERROR);
+            BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
+            if (bondState == BluetoothDevice.BOND_BONDED && btRoute == null) {
+                btRoute = createBluetoothRoute(device);
+                if (mA2dpProfile != null && mA2dpProfile.getConnectedDevices().contains(device)) {
+                    btRoute.connectedProfiles.put(BluetoothProfile.A2DP, true);
+                }
+                if (mHearingAidProfile != null
+                        && mHearingAidProfile.getConnectedDevices().contains(device)) {
+                    btRoute.connectedProfiles.put(BluetoothProfile.HEARING_AID, true);
+                }
+                mBluetoothRoutes.put(device.getAddress(), btRoute);
+                notifyBluetoothRoutesUpdated();
+            } else if (bondState == BluetoothDevice.BOND_NONE
+                    && mBluetoothRoutes.remove(device.getAddress()) != null) {
+                notifyBluetoothRoutesUpdated();
+            }
+        }
+    }
+
+    private class DeviceStateChangedRecevier implements BluetoothEventReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+            switch (intent.getAction()) {
+                case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
+                    String prevActiveDeviceAddress =
+                            (mActiveDevice == null) ? null : mActiveDevice.getAddress();
+                    String curActiveDeviceAddress =
+                            (device == null) ? null : device.getAddress();
+                    if (!TextUtils.equals(prevActiveDeviceAddress, curActiveDeviceAddress)) {
+                        if (mActiveDevice != null) {
+                            setRouteConnectionStateForDevice(mActiveDevice,
+                                    MediaRoute2Info.CONNECTION_STATE_DISCONNECTED);
+                        }
+                        if (device != null) {
+                            setRouteConnectionStateForDevice(device,
+                                    MediaRoute2Info.CONNECTION_STATE_CONNECTED);
+                        }
+                        mActiveDevice = device;
+                        notifyBluetoothRoutesUpdated();
+                    }
+                    break;
+                case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:
+                    handleConnectionStateChanged(BluetoothProfile.A2DP, intent, device);
+                    break;
+            }
+        }
+
+        private void handleConnectionStateChanged(int profile, Intent intent,
+                BluetoothDevice device) {
+            int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+            BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
+            if (state == BluetoothProfile.STATE_CONNECTED) {
+                if (btRoute == null) {
+                    btRoute = createBluetoothRoute(device);
+                    mBluetoothRoutes.put(device.getAddress(), btRoute);
+                    btRoute.connectedProfiles.put(profile, true);
+                    notifyBluetoothRoutesUpdated();
+                } else {
+                    btRoute.connectedProfiles.put(profile, true);
+                }
+            } else if (state == BluetoothProfile.STATE_DISCONNECTING
+                    || state == BluetoothProfile.STATE_DISCONNECTED) {
+                if (btRoute != null) {
+                    btRoute.connectedProfiles.delete(profile);
+                    if (btRoute.connectedProfiles.size() == 0) {
+                        mBluetoothRoutes.remove(device.getAddress());
+                        if (mActiveDevice != null
+                                && TextUtils.equals(mActiveDevice.getAddress(),
+                                device.getAddress())) {
+                            mActiveDevice = null;
+                        }
+                        notifyBluetoothRoutesUpdated();
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index 55c4e21..1cd8aad 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -21,20 +21,25 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.media.MediaRoute2ProviderInfo;
-import android.media.RouteSessionInfo;
+import android.media.RoutingSessionInfo;
+import android.os.Bundle;
+
+import com.android.internal.annotations.GuardedBy;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 
 abstract class MediaRoute2Provider {
     final ComponentName mComponentName;
     final String mUniqueId;
+    final Object mLock = new Object();
 
     Callback mCallback;
     private volatile MediaRoute2ProviderInfo mProviderInfo;
-    private volatile List<RouteSessionInfo> mSessionInfos = Collections.emptyList();
+
+    @GuardedBy("mLock")
+    final List<RoutingSessionInfo> mSessionInfos = new ArrayList<>();
 
     MediaRoute2Provider(@NonNull ComponentName componentName) {
         mComponentName = Objects.requireNonNull(componentName, "Component name must not be null.");
@@ -45,13 +50,13 @@
         mCallback = callback;
     }
 
-    public abstract void requestCreateSession(String packageName, String routeId,
-            String controlCategory, long requestId);
-    public abstract void releaseSession(int sessionId);
+    public abstract void requestCreateSession(String packageName, String routeId, long requestId,
+            @Nullable Bundle sessionHints);
+    public abstract void releaseSession(String sessionId);
 
-    public abstract void selectRoute(int sessionId, String routeId);
-    public abstract void deselectRoute(int sessionId, String routeId);
-    public abstract void transferToRoute(int sessionId, String routeId);
+    public abstract void selectRoute(String sessionId, String routeId);
+    public abstract void deselectRoute(String sessionId, String routeId);
+    public abstract void transferToRoute(String sessionId, String routeId);
 
     public abstract void sendControlRequest(String routeId, Intent request);
     public abstract void requestSetVolume(String routeId, int volume);
@@ -68,12 +73,13 @@
     }
 
     @NonNull
-    public List<RouteSessionInfo> getSessionInfos() {
-        return mSessionInfos;
+    public List<RoutingSessionInfo> getSessionInfos() {
+        synchronized (mLock) {
+            return mSessionInfos;
+        }
     }
 
-    void setAndNotifyProviderState(MediaRoute2ProviderInfo providerInfo,
-            List<RouteSessionInfo> sessionInfos) {
+    void setProviderState(MediaRoute2ProviderInfo providerInfo) {
         if (providerInfo == null) {
             mProviderInfo = null;
         } else {
@@ -81,20 +87,19 @@
                     .setUniqueId(mUniqueId)
                     .build();
         }
-        List<RouteSessionInfo> sessionInfoWithProviderId = new ArrayList<RouteSessionInfo>();
-        for (RouteSessionInfo sessionInfo : sessionInfos) {
-            sessionInfoWithProviderId.add(
-                    new RouteSessionInfo.Builder(sessionInfo)
-                            .setProviderId(mUniqueId)
-                            .build());
-        }
-        mSessionInfos = sessionInfoWithProviderId;
+    }
 
+    void notifyProviderState() {
         if (mCallback != null) {
             mCallback.onProviderStateChanged(this);
         }
     }
 
+    void setAndNotifyProviderState(MediaRoute2ProviderInfo providerInfo) {
+        setProviderState(providerInfo);
+        notifyProviderState();
+    }
+
     public boolean hasComponentName(String packageName, String className) {
         return mComponentName.getPackageName().equals(packageName)
                 && mComponentName.getClassName().equals(className);
@@ -103,12 +108,11 @@
     public interface Callback {
         void onProviderStateChanged(@Nullable MediaRoute2Provider provider);
         void onSessionCreated(@NonNull MediaRoute2Provider provider,
-                @Nullable RouteSessionInfo sessionInfo, long requestId);
-        // TODO: Remove this when MediaRouter2ServiceImpl notifies clients of session changes.
-        void onSessionInfoChanged(@NonNull MediaRoute2Provider provider,
-                @NonNull RouteSessionInfo sessionInfo);
-        // TODO: Call this when service actually notifies of session release.
+                @Nullable RoutingSessionInfo sessionInfo, long requestId);
+        void onSessionCreationFailed(@NonNull MediaRoute2Provider provider, long requestId);
+        void onSessionUpdated(@NonNull MediaRoute2Provider provider,
+                @NonNull RoutingSessionInfo sessionInfo);
         void onSessionReleased(@NonNull MediaRoute2Provider provider,
-                @NonNull RouteSessionInfo sessionInfo);
+                @NonNull RoutingSessionInfo sessionInfo);
     }
 }
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
index 28bb034..9761461 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
@@ -17,7 +17,6 @@
 package com.android.server.media;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -26,7 +25,8 @@
 import android.media.IMediaRoute2ProviderClient;
 import android.media.MediaRoute2ProviderInfo;
 import android.media.MediaRoute2ProviderService;
-import android.media.RouteSessionInfo;
+import android.media.RoutingSessionInfo;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.IBinder.DeathRecipient;
@@ -37,8 +37,6 @@
 
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
-import java.util.Collections;
-import java.util.List;
 import java.util.Objects;
 
 /**
@@ -76,17 +74,17 @@
     }
 
     @Override
-    public void requestCreateSession(String packageName, String routeId, String controlCategory,
-            long requestId) {
+    public void requestCreateSession(String packageName, String routeId, long requestId,
+            Bundle sessionHints) {
         if (mConnectionReady) {
-            mActiveConnection.requestCreateSession(packageName, routeId, controlCategory,
-                    requestId);
+            mActiveConnection.requestCreateSession(
+                    packageName, routeId, requestId, sessionHints);
             updateBinding();
         }
     }
 
     @Override
-    public void releaseSession(int sessionId) {
+    public void releaseSession(String sessionId) {
         if (mConnectionReady) {
             mActiveConnection.releaseSession(sessionId);
             updateBinding();
@@ -94,21 +92,21 @@
     }
 
     @Override
-    public void selectRoute(int sessionId, String routeId) {
+    public void selectRoute(String sessionId, String routeId) {
         if (mConnectionReady) {
             mActiveConnection.selectRoute(sessionId, routeId);
         }
     }
 
     @Override
-    public void deselectRoute(int sessionId, String routeId) {
+    public void deselectRoute(String sessionId, String routeId) {
         if (mConnectionReady) {
             mActiveConnection.deselectRoute(sessionId, routeId);
         }
     }
 
     @Override
-    public void transferToRoute(int sessionId, String routeId) {
+    public void transferToRoute(String sessionId, String routeId) {
         if (mConnectionReady) {
             mActiveConnection.transferToRoute(sessionId, routeId);
         }
@@ -270,39 +268,126 @@
     }
 
     private void onProviderStateUpdated(Connection connection,
-            MediaRoute2ProviderInfo providerInfo, List<RouteSessionInfo> sessionInfos) {
+            MediaRoute2ProviderInfo providerInfo) {
         if (mActiveConnection != connection) {
             return;
         }
         if (DEBUG) {
             Slog.d(TAG, this + ": State changed ");
         }
-        setAndNotifyProviderState(providerInfo, sessionInfos);
+        setAndNotifyProviderState(providerInfo);
     }
 
-    private void onSessionCreated(Connection connection, @Nullable RouteSessionInfo sessionInfo,
+    private void onSessionCreated(Connection connection, RoutingSessionInfo sessionInfo,
             long requestId) {
         if (mActiveConnection != connection) {
             return;
         }
-        if (sessionInfo != null) {
-            sessionInfo = new RouteSessionInfo.Builder(sessionInfo)
-                    .setProviderId(getUniqueId())
-                    .build();
+
+        if (sessionInfo == null) {
+            Slog.w(TAG, "onSessionCreated: Ignoring null sessionInfo sent from " + mComponentName);
+            return;
         }
+
+        sessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+                .setProviderId(getUniqueId())
+                .build();
+
+        boolean duplicateSessionAlreadyExists = false;
+        synchronized (mLock) {
+            for (int i = 0; i < mSessionInfos.size(); i++) {
+                if (mSessionInfos.get(i).getId().equals(sessionInfo.getId())) {
+                    duplicateSessionAlreadyExists = true;
+                    break;
+                }
+            }
+            mSessionInfos.add(sessionInfo);
+        }
+
+        if (duplicateSessionAlreadyExists) {
+            Slog.w(TAG, "onSessionCreated: Duplicate session already exists. Ignoring.");
+            return;
+        }
+
         mCallback.onSessionCreated(this, sessionInfo, requestId);
     }
 
-    private void onSessionInfoChanged(Connection connection, RouteSessionInfo sessionInfo) {
+    private void onSessionCreationFailed(Connection connection, long requestId) {
+        if (mActiveConnection != connection) {
+            return;
+        }
+
+        if (requestId == MediaRoute2ProviderService.REQUEST_ID_UNKNOWN) {
+            Slog.w(TAG, "onSessionCreationFailed: Ignoring requestId REQUEST_ID_UNKNOWN");
+            return;
+        }
+
+        mCallback.onSessionCreationFailed(this, requestId);
+    }
+
+    private void onSessionUpdated(Connection connection, RoutingSessionInfo sessionInfo) {
         if (mActiveConnection != connection) {
             return;
         }
         if (sessionInfo == null) {
-            Slog.w(TAG, "onSessionInfoChanged: Ignoring null sessionInfo sent from "
+            Slog.w(TAG, "onSessionUpdated: Ignoring null sessionInfo sent from "
                     + mComponentName);
             return;
         }
-        mCallback.onSessionInfoChanged(this, sessionInfo);
+
+        sessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+                .setProviderId(getUniqueId())
+                .build();
+
+        boolean found = false;
+        synchronized (mLock) {
+            for (int i = 0; i < mSessionInfos.size(); i++) {
+                if (mSessionInfos.get(i).getId().equals(sessionInfo.getId())) {
+                    mSessionInfos.set(i, sessionInfo);
+                    found = true;
+                    break;
+                }
+            }
+        }
+
+        if (!found) {
+            Slog.w(TAG, "onSessionUpdated: Matching session info not found");
+            return;
+        }
+
+        mCallback.onSessionUpdated(this, sessionInfo);
+    }
+
+    private void onSessionReleased(Connection connection, RoutingSessionInfo sessionInfo) {
+        if (mActiveConnection != connection) {
+            return;
+        }
+        if (sessionInfo == null) {
+            Slog.w(TAG, "onSessionReleased: Ignoring null sessionInfo sent from " + mComponentName);
+            return;
+        }
+
+        sessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+                .setProviderId(getUniqueId())
+                .build();
+
+        boolean found = false;
+        synchronized (mLock) {
+            for (int i = 0; i < mSessionInfos.size(); i++) {
+                if (mSessionInfos.get(i).getId().equals(sessionInfo.getId())) {
+                    mSessionInfos.remove(i);
+                    found = true;
+                    break;
+                }
+            }
+        }
+
+        if (!found) {
+            Slog.w(TAG, "onSessionReleased: Matching session info not found");
+            return;
+        }
+
+        mCallback.onSessionReleased(this, sessionInfo);
     }
 
     private void disconnect() {
@@ -310,7 +395,7 @@
             mConnectionReady = false;
             mActiveConnection.dispose();
             mActiveConnection = null;
-            setAndNotifyProviderState(null, Collections.emptyList());
+            setAndNotifyProviderState(null);
         }
     }
 
@@ -345,17 +430,16 @@
             mClient.dispose();
         }
 
-        public void requestCreateSession(String packageName, String routeId, String controlCategory,
-                long requestId) {
+        public void requestCreateSession(String packageName, String routeId, long requestId,
+                Bundle sessionHints) {
             try {
-                mProvider.requestCreateSession(packageName, routeId,
-                        controlCategory, requestId);
+                mProvider.requestCreateSession(packageName, routeId, requestId, sessionHints);
             } catch (RemoteException ex) {
                 Slog.e(TAG, "Failed to deliver request to create a session.", ex);
             }
         }
 
-        public void releaseSession(int sessionId) {
+        public void releaseSession(String sessionId) {
             try {
                 mProvider.releaseSession(sessionId);
             } catch (RemoteException ex) {
@@ -363,7 +447,7 @@
             }
         }
 
-        public void selectRoute(int sessionId, String routeId) {
+        public void selectRoute(String sessionId, String routeId) {
             try {
                 mProvider.selectRoute(sessionId, routeId);
             } catch (RemoteException ex) {
@@ -371,7 +455,7 @@
             }
         }
 
-        public void deselectRoute(int sessionId, String routeId) {
+        public void deselectRoute(String sessionId, String routeId) {
             try {
                 mProvider.deselectRoute(sessionId, routeId);
             } catch (RemoteException ex) {
@@ -379,7 +463,7 @@
             }
         }
 
-        public void transferToRoute(int sessionId, String routeId) {
+        public void transferToRoute(String sessionId, String routeId) {
             try {
                 mProvider.transferToRoute(sessionId, routeId);
             } catch (RemoteException ex) {
@@ -416,19 +500,24 @@
             mHandler.post(() -> onConnectionDied(Connection.this));
         }
 
-        void postProviderStateUpdated(MediaRoute2ProviderInfo providerInfo,
-                List<RouteSessionInfo> sessionInfos) {
-            mHandler.post(() -> onProviderStateUpdated(Connection.this,
-                    providerInfo, sessionInfos));
+        void postProviderStateUpdated(MediaRoute2ProviderInfo providerInfo) {
+            mHandler.post(() -> onProviderStateUpdated(Connection.this, providerInfo));
         }
 
-        void postSessionCreated(@Nullable RouteSessionInfo sessionInfo, long requestId) {
-            mHandler.post(() -> onSessionCreated(Connection.this, sessionInfo,
-                    requestId));
+        void postSessionCreated(RoutingSessionInfo sessionInfo, long requestId) {
+            mHandler.post(() -> onSessionCreated(Connection.this, sessionInfo, requestId));
         }
 
-        void postSessionInfoChanged(RouteSessionInfo sessionInfo) {
-            mHandler.post(() -> onSessionInfoChanged(Connection.this, sessionInfo));
+        void postSessionCreationFailed(long requestId) {
+            mHandler.post(() -> onSessionCreationFailed(Connection.this, requestId));
+        }
+
+        void postSessionUpdated(RoutingSessionInfo sessionInfo) {
+            mHandler.post(() -> onSessionUpdated(Connection.this, sessionInfo));
+        }
+
+        void postSessionReleased(RoutingSessionInfo sessionInfo) {
+            mHandler.post(() -> onSessionReleased(Connection.this, sessionInfo));
         }
     }
 
@@ -444,16 +533,15 @@
         }
 
         @Override
-        public void updateState(MediaRoute2ProviderInfo providerInfo,
-                List<RouteSessionInfo> sessionInfos) {
+        public void updateState(MediaRoute2ProviderInfo providerInfo) {
             Connection connection = mConnectionRef.get();
             if (connection != null) {
-                connection.postProviderStateUpdated(providerInfo, sessionInfos);
+                connection.postProviderStateUpdated(providerInfo);
             }
         }
 
         @Override
-        public void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, long requestId) {
+        public void notifySessionCreated(RoutingSessionInfo sessionInfo, long requestId) {
             Connection connection = mConnectionRef.get();
             if (connection != null) {
                 connection.postSessionCreated(sessionInfo, requestId);
@@ -461,10 +549,26 @@
         }
 
         @Override
-        public void notifySessionInfoChanged(RouteSessionInfo sessionInfo) {
+        public void notifySessionCreationFailed(long requestId) {
             Connection connection = mConnectionRef.get();
             if (connection != null) {
-                connection.postSessionInfoChanged(sessionInfo);
+                connection.postSessionCreationFailed(requestId);
+            }
+        }
+
+        @Override
+        public void notifySessionUpdated(RoutingSessionInfo sessionInfo) {
+            Connection connection = mConnectionRef.get();
+            if (connection != null) {
+                connection.postSessionUpdated(sessionInfo);
+            }
+        }
+
+        @Override
+        public void notifySessionReleased(RoutingSessionInfo sessionInfo) {
+            Connection connection = mConnectionRef.get();
+            if (connection != null) {
+                connection.postSessionReleased(sessionInfo);
             }
         }
     }
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index a5ffbb8..9594659 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -16,6 +16,9 @@
 
 package com.android.server.media;
 
+import static android.media.MediaRouter2Utils.getOriginalId;
+import static android.media.MediaRouter2Utils.getProviderId;
+
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
 import android.annotation.NonNull;
@@ -28,7 +31,9 @@
 import android.media.IMediaRouter2Manager;
 import android.media.MediaRoute2Info;
 import android.media.MediaRoute2ProviderInfo;
-import android.media.RouteSessionInfo;
+import android.media.MediaRoute2ProviderService;
+import android.media.RouteDiscoveryPreference;
+import android.media.RoutingSessionInfo;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -59,12 +64,12 @@
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
- * TODO: Merge this to MediaRouterService once it's finished.
+ * Implements features related to {@link android.media.MediaRouter2} and
+ * {@link android.media.MediaRouter2Manager}.
  */
 class MediaRouter2ServiceImpl {
     private static final String TAG = "MR2ServiceImpl";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-    private static final long ROUTE_SELECTION_REQUEST_TIMEOUT_MS = 5000L;
 
     private final Context mContext;
     private final Object mLock = new Object();
@@ -86,7 +91,7 @@
     @NonNull
     public List<MediaRoute2Info> getSystemRoutes() {
         final int uid = Binder.getCallingUid();
-        final int userId = UserHandle.getUserId(uid);
+        final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
 
         final long token = Binder.clearCallingIdentity();
         try {
@@ -107,13 +112,37 @@
         }
     }
 
+    @NonNull
+    public RoutingSessionInfo getSystemSessionInfo() {
+        final int uid = Binder.getCallingUid();
+        final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            RoutingSessionInfo systemSessionInfo = null;
+            synchronized (mLock) {
+                UserRecord userRecord = getOrCreateUserRecordLocked(userId);
+                List<RoutingSessionInfo> sessionInfos =
+                        userRecord.mHandler.mSystemProvider.getSessionInfos();
+                if (sessionInfos != null && !sessionInfos.isEmpty()) {
+                    systemSessionInfo = sessionInfos.get(0);
+                } else {
+                    Slog.w(TAG, "System provider does not have any session info.");
+                }
+            }
+            return systemSessionInfo;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     public void registerClient(@NonNull IMediaRouter2Client client,
             @NonNull String packageName) {
         Objects.requireNonNull(client, "client must not be null");
 
         final int uid = Binder.getCallingUid();
         final int pid = Binder.getCallingPid();
-        final int userId = UserHandle.getUserId(uid);
+        final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
         final boolean trusted = mContext.checkCallingOrSelfPermission(
                 android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
                 == PackageManager.PERMISSION_GRANTED;
@@ -148,7 +177,7 @@
 
         final int uid = Binder.getCallingUid();
         final int pid = Binder.getCallingPid();
-        final int userId = UserHandle.getUserId(uid);
+        final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
 
         final long token = Binder.clearCallingIdentity();
         try {
@@ -174,18 +203,14 @@
     }
 
     public void requestCreateSession(IMediaRouter2Client client, MediaRoute2Info route,
-            String controlCategory, int requestId) {
+            int requestId, Bundle sessionHints) {
         Objects.requireNonNull(client, "client must not be null");
         Objects.requireNonNull(route, "route must not be null");
-        if (TextUtils.isEmpty(controlCategory)) {
-            throw new IllegalArgumentException("controlCategory must not be empty");
-        }
 
         final long token = Binder.clearCallingIdentity();
-
         try {
             synchronized (mLock) {
-                requestCreateSessionLocked(client, route, controlCategory, requestId);
+                requestCreateSessionLocked(client, route, requestId, sessionHints);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -196,6 +221,9 @@
             MediaRoute2Info route) {
         Objects.requireNonNull(client, "client must not be null");
         Objects.requireNonNull(route, "route must not be null");
+        if (TextUtils.isEmpty(uniqueSessionId)) {
+            throw new IllegalArgumentException("uniqueSessionId must not be empty");
+        }
 
         final long token = Binder.clearCallingIdentity();
         try {
@@ -212,6 +240,9 @@
             MediaRoute2Info route) {
         Objects.requireNonNull(client, "client must not be null");
         Objects.requireNonNull(route, "route must not be null");
+        if (TextUtils.isEmpty(uniqueSessionId)) {
+            throw new IllegalArgumentException("uniqueSessionId must not be empty");
+        }
 
         final long token = Binder.clearCallingIdentity();
         try {
@@ -227,6 +258,9 @@
             MediaRoute2Info route) {
         Objects.requireNonNull(client, "client must not be null");
         Objects.requireNonNull(route, "route must not be null");
+        if (TextUtils.isEmpty(uniqueSessionId)) {
+            throw new IllegalArgumentException("uniqueSessionId must not be empty");
+        }
 
         final long token = Binder.clearCallingIdentity();
         try {
@@ -240,6 +274,9 @@
 
     public void releaseSession(IMediaRouter2Client client, String uniqueSessionId) {
         Objects.requireNonNull(client, "client must not be null");
+        if (TextUtils.isEmpty(uniqueSessionId)) {
+            throw new IllegalArgumentException("uniqueSessionId must not be empty");
+        }
 
         final long token = Binder.clearCallingIdentity();
         try {
@@ -267,16 +304,16 @@
         }
     }
 
-    public void setControlCategories(@NonNull IMediaRouter2Client client,
-            @NonNull List<String> categories) {
+    public void setDiscoveryRequest2(@NonNull IMediaRouter2Client client,
+            @NonNull RouteDiscoveryPreference preference) {
         Objects.requireNonNull(client, "client must not be null");
-        Objects.requireNonNull(categories, "categories must not be null");
+        Objects.requireNonNull(preference, "preference must not be null");
 
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
                 Client2Record clientRecord = mAllClientRecords.get(client.asBinder());
-                setControlCategoriesLocked(clientRecord, categories);
+                setDiscoveryRequestLocked(clientRecord, preference);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -354,7 +391,7 @@
     }
 
     @NonNull
-    public List<RouteSessionInfo> getActiveSessions(IMediaRouter2Manager manager) {
+    public List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager) {
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
@@ -365,6 +402,53 @@
         }
     }
 
+    public void selectClientRoute(IMediaRouter2Manager manager, String sessionId,
+            MediaRoute2Info route) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                selectClientRouteLocked(manager, sessionId, route);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public void deselectClientRoute(IMediaRouter2Manager manager, String sessionId,
+            MediaRoute2Info route) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                deselectClientRouteLocked(manager, sessionId, route);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public void transferToClientRoute(IMediaRouter2Manager manager, String sessionId,
+            MediaRoute2Info route) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                transferClientRouteLocked(manager, sessionId, route);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public void releaseClientSession(IMediaRouter2Manager manager, String sessionId) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                releaseClientSessionLocked(manager, sessionId);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     //TODO: Review this is handling multi-user properly.
     void switchUser() {
         synchronized (mLock) {
@@ -405,6 +489,7 @@
             int uid, int pid, String packageName, int userId, boolean trusted) {
         final IBinder binder = client.asBinder();
         if (mAllClientRecords.get(binder) == null) {
+
             UserRecord userRecord = getOrCreateUserRecordLocked(userId);
             Client2Record clientRecord = new Client2Record(userRecord, client, uid, pid,
                     packageName, trusted);
@@ -434,7 +519,7 @@
     }
 
     private void requestCreateSessionLocked(@NonNull IMediaRouter2Client client,
-            @NonNull MediaRoute2Info route, @NonNull String controlCategory, long requestId) {
+            @NonNull MediaRoute2Info route, long requestId, @Nullable Bundle sessionHints) {
         final IBinder binder = client.asBinder();
         final Client2Record clientRecord = mAllClientRecords.get(binder);
 
@@ -447,7 +532,7 @@
             clientRecord.mUserRecord.mHandler.sendMessage(
                     obtainMessage(UserHandler::requestCreateSessionOnHandler,
                             clientRecord.mUserRecord.mHandler,
-                            clientRecord, route, controlCategory, requestId));
+                            clientRecord, route, requestId, sessionHints));
         }
     }
 
@@ -502,13 +587,14 @@
         }
     }
 
-    private void setControlCategoriesLocked(Client2Record clientRecord, List<String> categories) {
+    private void setDiscoveryRequestLocked(Client2Record clientRecord,
+            RouteDiscoveryPreference discoveryRequest) {
         if (clientRecord != null) {
-            if (clientRecord.mControlCategories.equals(categories)) {
+            if (clientRecord.mDiscoveryPreference.equals(discoveryRequest)) {
                 return;
             }
 
-            clientRecord.mControlCategories = categories;
+            clientRecord.mDiscoveryPreference = discoveryRequest;
             clientRecord.mUserRecord.mHandler.sendMessage(
                     obtainMessage(UserHandler::updateClientUsage,
                             clientRecord.mUserRecord.mHandler, clientRecord));
@@ -605,9 +691,9 @@
             }
             long uniqueRequestId = toUniqueRequestId(managerRecord.mClientId, requestId);
             if (clientRecord != null && managerRecord.mTrusted) {
-                //TODO: select category properly
+                //TODO: Use client's OnCreateSessionListener to send proper session hints.
                 requestCreateSessionLocked(clientRecord.mClient, route,
-                        route.getSupportedCategories().get(0), uniqueRequestId);
+                        uniqueRequestId, null /* sessionHints */);
             }
         }
     }
@@ -636,15 +722,16 @@
         }
     }
 
-    private List<RouteSessionInfo> getActiveSessionsLocked(IMediaRouter2Manager manager) {
+    private List<RoutingSessionInfo> getActiveSessionsLocked(IMediaRouter2Manager manager) {
         final IBinder binder = manager.asBinder();
         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
 
         if (managerRecord == null) {
+            Slog.w(TAG, "getActiveSessionLocked: Ignoring unknown manager");
             return Collections.emptyList();
         }
 
-        List<RouteSessionInfo> sessionInfos = new ArrayList<>();
+        List<RoutingSessionInfo> sessionInfos = new ArrayList<>();
         for (MediaRoute2Provider provider : managerRecord.mUserRecord.mHandler.mMediaProviders) {
             sessionInfos.addAll(provider.getSessionInfos());
         }
@@ -664,6 +751,78 @@
         return userRecord;
     }
 
+    private void selectClientRouteLocked(IMediaRouter2Manager manager, String sessionId,
+            MediaRoute2Info route) {
+        final IBinder binder = manager.asBinder();
+        ManagerRecord managerRecord = mAllManagerRecords.get(binder);
+
+        if (managerRecord == null) {
+            Slog.w(TAG, "selectClientRouteLocked: Ignoring unknown manager.");
+            return;
+        }
+        Client2Record clientRecord = managerRecord.mUserRecord.mHandler
+                .findClientforSessionLocked(sessionId);
+
+        managerRecord.mUserRecord.mHandler.sendMessage(
+                obtainMessage(UserHandler::selectRouteOnHandler,
+                            managerRecord.mUserRecord.mHandler,
+                            clientRecord, sessionId, route));
+    }
+
+    private void deselectClientRouteLocked(IMediaRouter2Manager manager, String sessionId,
+            MediaRoute2Info route) {
+        final IBinder binder = manager.asBinder();
+        ManagerRecord managerRecord = mAllManagerRecords.get(binder);
+
+        if (managerRecord == null) {
+            Slog.w(TAG, "deselectClientRouteLocked: Ignoring unknown manager.");
+            return;
+        }
+        Client2Record clientRecord = managerRecord.mUserRecord.mHandler
+                .findClientforSessionLocked(sessionId);
+
+        managerRecord.mUserRecord.mHandler.sendMessage(
+                obtainMessage(UserHandler::deselectRouteOnHandler,
+                        managerRecord.mUserRecord.mHandler,
+                        clientRecord, sessionId, route));
+    }
+
+    private void transferClientRouteLocked(IMediaRouter2Manager manager, String sessionId,
+            MediaRoute2Info route) {
+        final IBinder binder = manager.asBinder();
+        ManagerRecord managerRecord = mAllManagerRecords.get(binder);
+
+        if (managerRecord == null) {
+            Slog.w(TAG, "transferClientRouteLocked: Ignoring unknown manager.");
+            return;
+        }
+        Client2Record clientRecord = managerRecord.mUserRecord.mHandler
+                .findClientforSessionLocked(sessionId);
+
+        managerRecord.mUserRecord.mHandler.sendMessage(
+                obtainMessage(UserHandler::transferToRouteOnHandler,
+                        managerRecord.mUserRecord.mHandler,
+                        clientRecord, sessionId, route));
+    }
+
+    private void releaseClientSessionLocked(IMediaRouter2Manager manager, String sessionId) {
+        final IBinder binder = manager.asBinder();
+        ManagerRecord managerRecord = mAllManagerRecords.get(binder);
+
+        if (managerRecord == null) {
+            Slog.w(TAG, "releaseClientSessionLocked: Ignoring unknown manager.");
+            return;
+        }
+
+        Client2Record clientRecord = managerRecord.mUserRecord.mHandler
+                .findClientforSessionLocked(sessionId);
+
+        managerRecord.mUserRecord.mHandler.sendMessage(
+                obtainMessage(UserHandler::releaseSessionOnHandler,
+                        managerRecord.mUserRecord.mHandler,
+                        clientRecord, sessionId));
+    }
+
     private void disposeUserIfNeededLocked(UserRecord userRecord) {
         // If there are no records left and the user is no longer current then go ahead
         // and purge the user record and all of its associated state.  If the user is current
@@ -725,7 +884,7 @@
         public final boolean mTrusted;
         public final int mClientId;
 
-        public List<String> mControlCategories;
+        public RouteDiscoveryPreference mDiscoveryPreference;
         public boolean mIsManagerSelecting;
         public MediaRoute2Info mSelectingRoute;
         public MediaRoute2Info mSelectedRoute;
@@ -735,7 +894,7 @@
             mUserRecord = userRecord;
             mPackageName = packageName;
             mSelectRouteSequenceNumbers = new ArrayList<>();
-            mControlCategories = Collections.emptyList();
+            mDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
             mClient = client;
             mUid = uid;
             mPid = pid;
@@ -859,24 +1018,36 @@
 
         @Override
         public void onSessionCreated(@NonNull MediaRoute2Provider provider,
-                @Nullable RouteSessionInfo sessionInfo, long requestId) {
+                @NonNull RoutingSessionInfo sessionInfo, long requestId) {
             sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionCreatedOnHandler,
                     this, provider, sessionInfo, requestId));
         }
 
         @Override
-        public void onSessionInfoChanged(@NonNull MediaRoute2Provider provider,
-                @NonNull RouteSessionInfo sessionInfo) {
+        public void onSessionCreationFailed(@NonNull MediaRoute2Provider provider, long requestId) {
+            sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionCreationFailedOnHandler,
+                    this, provider, requestId));
+        }
+
+        @Override
+        public void onSessionUpdated(@NonNull MediaRoute2Provider provider,
+                @NonNull RoutingSessionInfo sessionInfo) {
             sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionInfoChangedOnHandler,
                     this, provider, sessionInfo));
         }
 
         @Override
-        public void onSessionReleased(MediaRoute2Provider provider, RouteSessionInfo sessionInfo) {
+        public void onSessionReleased(MediaRoute2Provider provider,
+                RoutingSessionInfo sessionInfo) {
             sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionReleasedOnHandler,
                     this, provider, sessionInfo));
         }
 
+        @Nullable
+        public Client2Record findClientforSessionLocked(@NonNull String sessionId) {
+            return mSessionToClientMap.get(sessionId);
+        }
+
         //TODO: notify session info updates
         private void onProviderStateChangedOnHandler(MediaRoute2Provider provider) {
             int providerIndex = getProviderInfoIndex(provider.getUniqueId());
@@ -915,7 +1086,7 @@
                         Slog.w(TAG, "Ignoring invalid route : " + route);
                         continue;
                     }
-                    MediaRoute2Info prevRoute = prevInfo.getRoute(route.getId());
+                    MediaRoute2Info prevRoute = prevInfo.getRoute(route.getOriginalId());
 
                     if (prevRoute != null) {
                         if (!Objects.equals(prevRoute, route)) {
@@ -961,7 +1132,7 @@
         }
 
         private void requestCreateSessionOnHandler(Client2Record clientRecord,
-                MediaRoute2Info route, String controlCategory, long requestId) {
+                MediaRoute2Info route, long requestId, @Nullable Bundle sessionHints) {
 
             final MediaRoute2Provider provider = findProvider(route.getProviderId());
             if (provider == null) {
@@ -971,23 +1142,16 @@
                 return;
             }
 
-            if (!route.getSupportedCategories().contains(controlCategory)) {
-                Slog.w(TAG, "Ignoring session creation request since the given route=" + route
-                        + " doesn't support the given category=" + controlCategory);
-                notifySessionCreationFailed(clientRecord, toClientRequestId(requestId));
-                return;
-            }
-
             // TODO: Apply timeout for each request (How many seconds should we wait?)
-            SessionCreationRequest request = new SessionCreationRequest(
-                    clientRecord, route, controlCategory, requestId);
+            SessionCreationRequest request =
+                    new SessionCreationRequest(clientRecord, route, requestId);
             mSessionCreationRequests.add(request);
 
             provider.requestCreateSession(clientRecord.mPackageName, route.getOriginalId(),
-                    controlCategory, requestId);
+                    requestId, sessionHints);
         }
 
-        private void selectRouteOnHandler(@NonNull Client2Record clientRecord,
+        private void selectRouteOnHandler(@Nullable Client2Record clientRecord,
                 String uniqueSessionId, MediaRoute2Info route) {
             if (!checkArgumentsForSessionControl(clientRecord, uniqueSessionId, route,
                     "selecting")) {
@@ -1000,11 +1164,10 @@
             if (provider == null) {
                 return;
             }
-            provider.selectRoute(RouteSessionInfo.getSessionId(uniqueSessionId),
-                    route.getOriginalId());
+            provider.selectRoute(getOriginalId(uniqueSessionId), route.getOriginalId());
         }
 
-        private void deselectRouteOnHandler(@NonNull Client2Record clientRecord,
+        private void deselectRouteOnHandler(@Nullable Client2Record clientRecord,
                 String uniqueSessionId, MediaRoute2Info route) {
             if (!checkArgumentsForSessionControl(clientRecord, uniqueSessionId, route,
                     "deselecting")) {
@@ -1017,11 +1180,10 @@
             if (provider == null) {
                 return;
             }
-            provider.deselectRoute(RouteSessionInfo.getSessionId(uniqueSessionId),
-                    route.getOriginalId());
+            provider.deselectRoute(getOriginalId(uniqueSessionId), route.getOriginalId());
         }
 
-        private void transferToRouteOnHandler(@NonNull Client2Record clientRecord,
+        private void transferToRouteOnHandler(Client2Record clientRecord,
                 String uniqueSessionId, MediaRoute2Info route) {
             if (!checkArgumentsForSessionControl(clientRecord, uniqueSessionId, route,
                     "transferring to")) {
@@ -1034,11 +1196,11 @@
             if (provider == null) {
                 return;
             }
-            provider.transferToRoute(RouteSessionInfo.getSessionId(uniqueSessionId),
+            provider.transferToRoute(getOriginalId(uniqueSessionId),
                     route.getOriginalId());
         }
 
-        private boolean checkArgumentsForSessionControl(@NonNull Client2Record clientRecord,
+        private boolean checkArgumentsForSessionControl(@Nullable Client2Record clientRecord,
                 String uniqueSessionId, MediaRoute2Info route, @NonNull String description) {
             if (route == null) {
                 Slog.w(TAG, "Ignoring " + description + " null route");
@@ -1059,6 +1221,17 @@
                 return false;
             }
 
+            // Bypass checking client if it's the system session (clientRecord should be null)
+            if (TextUtils.equals(getProviderId(uniqueSessionId), mSystemProvider.getUniqueId())) {
+                return true;
+            }
+
+            //TODO: Handle RCN case.
+            if (clientRecord == null) {
+                Slog.w(TAG, "Ignoring " + description + " route from unknown client.");
+                return false;
+            }
+
             Client2Record matchingRecord = mSessionToClientMap.get(uniqueSessionId);
             if (matchingRecord != clientRecord) {
                 Slog.w(TAG, "Ignoring " + description + " route from non-matching client. "
@@ -1066,9 +1239,9 @@
                 return false;
             }
 
-            final Integer sessionId = RouteSessionInfo.getSessionId(uniqueSessionId);
+            final String sessionId = getOriginalId(uniqueSessionId);
             if (sessionId == null) {
-                Slog.w(TAG, "Failed to get int session id from unique session id. "
+                Slog.w(TAG, "Failed to get original session id from unique session id. "
                         + "uniqueSessionId=" + uniqueSessionId);
                 return false;
             }
@@ -1091,14 +1264,14 @@
                 return;
             }
 
-            final String providerId = RouteSessionInfo.getProviderId(uniqueSessionId);
+            final String providerId = getProviderId(uniqueSessionId);
             if (providerId == null) {
                 Slog.w(TAG, "Ignoring releasing session with invalid unique session ID. "
                         + "uniqueSessionId=" + uniqueSessionId);
                 return;
             }
 
-            final Integer sessionId = RouteSessionInfo.getSessionId(uniqueSessionId);
+            final String sessionId = getOriginalId(uniqueSessionId);
             if (sessionId == null) {
                 Slog.w(TAG, "Ignoring releasing session with invalid unique session ID. "
                         + "uniqueSessionId=" + uniqueSessionId + " providerId=" + providerId);
@@ -1116,7 +1289,15 @@
         }
 
         private void onSessionCreatedOnHandler(@NonNull MediaRoute2Provider provider,
-                @Nullable RouteSessionInfo sessionInfo, long requestId) {
+                @NonNull RoutingSessionInfo sessionInfo, long requestId) {
+
+            notifySessionCreatedToManagers(getManagers(), sessionInfo);
+
+            if (requestId == MediaRoute2ProviderService.REQUEST_ID_UNKNOWN) {
+                // The session is created without any matching request.
+                return;
+            }
+
             SessionCreationRequest matchingRequest = null;
 
             for (SessionCreationRequest request : mSessionCreationRequests) {
@@ -1144,16 +1325,12 @@
             }
 
             String originalRouteId = matchingRequest.mRoute.getId();
-            String originalCategory = matchingRequest.mControlCategory;
             Client2Record client2Record = matchingRequest.mClientRecord;
 
-            if (!sessionInfo.getSelectedRoutes().contains(originalRouteId)
-                    || !TextUtils.equals(originalCategory,
-                        sessionInfo.getControlCategory())) {
+            if (!sessionInfo.getSelectedRoutes().contains(originalRouteId)) {
                 Slog.w(TAG, "Created session doesn't match the original request."
                         + " originalRouteId=" + originalRouteId
-                        + ", originalCategory=" + originalCategory + ", requestId=" + requestId
-                        + ", sessionInfo=" + sessionInfo);
+                        + ", requestId=" + requestId + ", sessionInfo=" + sessionInfo);
                 notifySessionCreationFailed(matchingRequest.mClientRecord,
                         toClientRequestId(requestId));
                 return;
@@ -1162,46 +1339,72 @@
             // Succeeded
             notifySessionCreated(matchingRequest.mClientRecord,
                     sessionInfo, toClientRequestId(requestId));
-            mSessionToClientMap.put(sessionInfo.getUniqueSessionId(), client2Record);
-            // TODO: Tell managers for the session creation
+            mSessionToClientMap.put(sessionInfo.getId(), client2Record);
+        }
+
+        private void onSessionCreationFailedOnHandler(@NonNull MediaRoute2Provider provider,
+                long requestId) {
+            SessionCreationRequest matchingRequest = null;
+
+            for (SessionCreationRequest request : mSessionCreationRequests) {
+                if (request.mRequestId == requestId
+                        && TextUtils.equals(
+                                request.mRoute.getProviderId(), provider.getUniqueId())) {
+                    matchingRequest = request;
+                    break;
+                }
+            }
+
+            if (matchingRequest == null) {
+                Slog.w(TAG, "Ignoring session creation failed result for unknown request. "
+                        + "requestId=" + requestId);
+                return;
+            }
+
+            mSessionCreationRequests.remove(matchingRequest);
+            notifySessionCreationFailed(matchingRequest.mClientRecord,
+                    toClientRequestId(requestId));
         }
 
         private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider,
-                @NonNull RouteSessionInfo sessionInfo) {
-            RouteSessionInfo sessionInfoWithProviderId = new RouteSessionInfo.Builder(sessionInfo)
-                    .setProviderId(provider.getUniqueId())
-                    .build();
+                @NonNull RoutingSessionInfo sessionInfo) {
+            List<IMediaRouter2Manager> managers = getManagers();
+            notifySessionInfosChangedToManagers(managers);
 
-            Client2Record client2Record = mSessionToClientMap.get(
-                    sessionInfoWithProviderId.getUniqueSessionId());
-            if (client2Record == null) {
-                Slog.w(TAG, "No matching client found for session=" + sessionInfoWithProviderId);
-                // TODO: Tell managers for the session update
+            // For system provider, notify all clients.
+            if (provider == mSystemProvider) {
+                MediaRouter2ServiceImpl service = mServiceRef.get();
+                if (service == null) {
+                    return;
+                }
+                notifySessionInfoChangedToClients(getClients(), sessionInfo);
                 return;
             }
-            notifySessionInfoChanged(client2Record, sessionInfoWithProviderId);
-            // TODO: Tell managers for the session update
+
+            Client2Record client2Record = mSessionToClientMap.get(
+                    sessionInfo.getId());
+            if (client2Record == null) {
+                Slog.w(TAG, "No matching client found for session=" + sessionInfo);
+                return;
+            }
+            notifySessionInfoChanged(client2Record, sessionInfo);
         }
 
         private void onSessionReleasedOnHandler(@NonNull MediaRoute2Provider provider,
-                @NonNull RouteSessionInfo sessionInfo) {
-            RouteSessionInfo sessionInfoWithProviderId = new RouteSessionInfo.Builder(sessionInfo)
-                    .setProviderId(provider.getUniqueId())
-                    .build();
+                @NonNull RoutingSessionInfo sessionInfo) {
+            List<IMediaRouter2Manager> managers = getManagers();
+            notifySessionInfosChangedToManagers(managers);
 
-            Client2Record client2Record = mSessionToClientMap.get(
-                    sessionInfoWithProviderId.getUniqueSessionId());
+            Client2Record client2Record = mSessionToClientMap.get(sessionInfo.getId());
             if (client2Record == null) {
-                Slog.w(TAG, "No matching client found for session=" + sessionInfoWithProviderId);
-                // TODO: Tell managers for the session release
+                Slog.w(TAG, "No matching client found for session=" + sessionInfo);
                 return;
             }
-            notifySessionReleased(client2Record, sessionInfoWithProviderId);
-            // TODO: Tell managers for the session release
+            notifySessionReleased(client2Record, sessionInfo);
         }
 
-        private void notifySessionCreated(Client2Record clientRecord, RouteSessionInfo sessionInfo,
-                int requestId) {
+        private void notifySessionCreated(Client2Record clientRecord,
+                RoutingSessionInfo sessionInfo, int requestId) {
             try {
                 clientRecord.mClient.notifySessionCreated(sessionInfo, requestId);
             } catch (RemoteException ex) {
@@ -1220,7 +1423,7 @@
         }
 
         private void notifySessionInfoChanged(Client2Record clientRecord,
-                RouteSessionInfo sessionInfo) {
+                RoutingSessionInfo sessionInfo) {
             try {
                 clientRecord.mClient.notifySessionInfoChanged(sessionInfo);
             } catch (RemoteException ex) {
@@ -1230,7 +1433,7 @@
         }
 
         private void notifySessionReleased(Client2Record clientRecord,
-                RouteSessionInfo sessionInfo) {
+                RoutingSessionInfo sessionInfo) {
             try {
                 clientRecord.mClient.notifySessionReleased(sessionInfo);
             } catch (RemoteException ex) {
@@ -1303,11 +1506,6 @@
             }
         }
 
-        // TODO: Remove notifyRouteSelected* methods
-        private void notifyRouteSelectedToClient(IMediaRouter2Client client,
-                MediaRoute2Info route, int reason, Bundle controlHints) {
-        }
-
         private void notifyRoutesAddedToClients(List<IMediaRouter2Client> clients,
                 List<MediaRoute2Info> routes) {
             for (IMediaRouter2Client client : clients) {
@@ -1341,6 +1539,17 @@
             }
         }
 
+        private void notifySessionInfoChangedToClients(List<IMediaRouter2Client> clients,
+                RoutingSessionInfo sessionInfo) {
+            for (IMediaRouter2Client client : clients) {
+                try {
+                    client.notifySessionInfoChanged(sessionInfo);
+                } catch (RemoteException ex) {
+                    Slog.w(TAG, "Failed to notify session info changed. Client probably died.", ex);
+                }
+            }
+        }
+
         private void notifyRoutesToManager(IMediaRouter2Manager manager) {
             List<MediaRoute2Info> routes = new ArrayList<>();
             for (MediaRoute2ProviderInfo providerInfo : mLastProviderInfos) {
@@ -1389,6 +1598,29 @@
             }
         }
 
+        private void notifySessionCreatedToManagers(List<IMediaRouter2Manager> managers,
+                RoutingSessionInfo sessionInfo) {
+            for (IMediaRouter2Manager manager : managers) {
+                try {
+                    manager.notifySessionCreated(sessionInfo);
+                } catch (RemoteException ex) {
+                    Slog.w(TAG, "notifySessionCreatedToManagers: "
+                            + "failed to notify. Manager probably died.", ex);
+                }
+            }
+        }
+
+        private void notifySessionInfosChangedToManagers(List<IMediaRouter2Manager> managers) {
+            for (IMediaRouter2Manager manager : managers) {
+                try {
+                    manager.notifySessionsUpdated();
+                } catch (RemoteException ex) {
+                    Slog.w(TAG, "notifySessionInfosChangedToManagers: "
+                            + "failed to notify. Manager probably died.", ex);
+                }
+            }
+        }
+
         private void updateClientUsage(Client2Record clientRecord) {
             MediaRouter2ServiceImpl service = mServiceRef.get();
             if (service == null) {
@@ -1402,10 +1634,8 @@
             }
             for (IMediaRouter2Manager manager : managers) {
                 try {
-                    manager.notifyRouteSelected(clientRecord.mPackageName,
-                            clientRecord.mSelectedRoute);
-                    manager.notifyControlCategoriesChanged(clientRecord.mPackageName,
-                            clientRecord.mControlCategories);
+                    manager.notifyPreferredFeaturesChanged(clientRecord.mPackageName,
+                            clientRecord.mDiscoveryPreference.getPreferredFeatures());
                 } catch (RemoteException ex) {
                     Slog.w(TAG, "Failed to update client usage. Manager probably died.", ex);
                 }
@@ -1424,15 +1654,12 @@
         final class SessionCreationRequest {
             public final Client2Record mClientRecord;
             public final MediaRoute2Info mRoute;
-            public final String mControlCategory;
             public final long mRequestId;
 
             SessionCreationRequest(@NonNull Client2Record clientRecord,
-                    @NonNull MediaRoute2Info route,
-                    @NonNull String controlCategory, long requestId) {
+                    @NonNull MediaRoute2Info route, long requestId) {
                 mClientRecord = clientRecord;
                 mRoute = route;
-                mControlCategory = controlCategory;
                 mRequestId = requestId;
             }
         }
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index d77f43b..e1d3803 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -39,8 +39,10 @@
 import android.media.MediaRouterClientState;
 import android.media.RemoteDisplayState;
 import android.media.RemoteDisplayState.RemoteDisplayInfo;
-import android.media.RouteSessionInfo;
+import android.media.RouteDiscoveryPreference;
+import android.media.RoutingSessionInfo;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -442,6 +444,12 @@
 
     // Binder call
     @Override
+    public RoutingSessionInfo getSystemSessionInfo() {
+        return mService2.getSystemSessionInfo();
+    }
+
+    // Binder call
+    @Override
     public void registerClient2(IMediaRouter2Client client, String packageName) {
         final int uid = Binder.getCallingUid();
         if (!validatePackageName(uid, packageName)) {
@@ -459,8 +467,8 @@
     // Binder call
     @Override
     public void requestCreateSession(IMediaRouter2Client client, MediaRoute2Info route,
-            String controlCategory, int requestId) {
-        mService2.requestCreateSession(client, route, controlCategory, requestId);
+            int requestId, Bundle sessionHints) {
+        mService2.requestCreateSession(client, route, requestId, sessionHints);
     }
 
     // Binder call
@@ -519,8 +527,8 @@
     }
     // Binder call
     @Override
-    public void setControlCategories(IMediaRouter2Client client, List<String> categories) {
-        mService2.setControlCategories(client, categories);
+    public void setDiscoveryRequest2(IMediaRouter2Client client, RouteDiscoveryPreference request) {
+        mService2.setDiscoveryRequest2(client, request);
     }
 
     // Binder call
@@ -551,10 +559,37 @@
 
     // Binder call
     @Override
-    public List<RouteSessionInfo> getActiveSessions(IMediaRouter2Manager manager) {
+    public List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager) {
         return mService2.getActiveSessions(manager);
     }
 
+    // Binder call
+    @Override
+    public void selectClientRoute(IMediaRouter2Manager manager, String sessionId,
+            MediaRoute2Info route) {
+        mService2.selectClientRoute(manager, sessionId, route);
+    }
+
+    // Binder call
+    @Override
+    public void deselectClientRoute(IMediaRouter2Manager manager, String sessionId,
+            MediaRoute2Info route) {
+        mService2.deselectClientRoute(manager, sessionId, route);
+    }
+
+    // Binder call
+    @Override
+    public void transferToClientRoute(IMediaRouter2Manager manager, String sessionId,
+            MediaRoute2Info route) {
+        mService2.transferToClientRoute(manager, sessionId, route);
+    }
+
+    // Binder call
+    @Override
+    public void releaseClientSession(IMediaRouter2Manager manager, String sessionId) {
+        mService2.releaseClientSession(manager, sessionId);
+    }
+
     void restoreBluetoothA2dp() {
         try {
             boolean a2dpOn;
@@ -578,7 +613,8 @@
     void restoreRoute(int uid) {
         ClientRecord clientRecord = null;
         synchronized (mLock) {
-            UserRecord userRecord = mUserRecords.get(UserHandle.getUserId(uid));
+            UserRecord userRecord = mUserRecords.get(
+                    UserHandle.getUserHandleForUid(uid).getIdentifier());
             if (userRecord != null && userRecord.mClientRecords != null) {
                 for (ClientRecord cr : userRecord.mClientRecords) {
                     if (validatePackageName(uid, cr.mPackageName)) {
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
new file mode 100644
index 0000000..b21d2e7
--- /dev/null
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import android.media.MediaController2;
+import android.media.Session2CommandGroup;
+import android.media.Session2Token;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.Looper;
+import android.os.ResultReceiver;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+
+/**
+ * Keeps the record of {@link Session2Token} helps to send command to the corresponding session.
+ */
+// TODO(jaewan): Do not call service method directly -- introduce listener instead.
+public class MediaSession2Record implements MediaSessionRecordImpl {
+    private static final String TAG = "MediaSession2Record";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private final Session2Token mSessionToken;
+    @GuardedBy("mLock")
+    private final HandlerExecutor mHandlerExecutor;
+    @GuardedBy("mLock")
+    private final MediaController2 mController;
+    @GuardedBy("mLock")
+    private final MediaSessionService mService;
+    @GuardedBy("mLock")
+    private boolean mIsConnected;
+
+    public MediaSession2Record(Session2Token sessionToken, MediaSessionService service,
+            Looper handlerLooper) {
+        mSessionToken = sessionToken;
+        mService = service;
+        mHandlerExecutor = new HandlerExecutor(new Handler(handlerLooper));
+        mController = new MediaController2.Builder(service.getContext(), sessionToken)
+                .setControllerCallback(mHandlerExecutor, new Controller2Callback())
+                .build();
+    }
+
+    @Override
+    public String getPackageName() {
+        return mSessionToken.getPackageName();
+    }
+
+    public Session2Token getSession2Token() {
+        return mSessionToken;
+    }
+
+    @Override
+    public int getUid() {
+        return mSessionToken.getUid();
+    }
+
+    @Override
+    public int getUserId() {
+        return UserHandle.getUserHandleForUid(mSessionToken.getUid()).getIdentifier();
+    }
+
+    @Override
+    public boolean isSystemPriority() {
+        // System priority session is currently only allowed for telephony, and it's OK to stick to
+        // the media1 API at this moment.
+        return false;
+    }
+
+    @Override
+    public void adjustVolume(String packageName, String opPackageName, int pid, int uid,
+            boolean asSystemService, int direction, int flags, boolean useSuggested) {
+        // TODO(jaewan): Add API to adjust volume.
+    }
+
+    @Override
+    public boolean isActive() {
+        synchronized (mLock) {
+            return mIsConnected;
+        }
+    }
+
+    @Override
+    public boolean checkPlaybackActiveState(boolean expected) {
+        synchronized (mLock) {
+            return mIsConnected && mController.isPlaybackActive() == expected;
+        }
+    }
+
+    @Override
+    public boolean isPlaybackTypeLocal() {
+        // TODO(jaewan): Implement -- need API to know whether the playback is remote or local.
+        return true;
+    }
+
+    @Override
+    public void close() {
+        synchronized (mLock) {
+            // Call close regardless of the mIsAvailable. This may be called when it's not yet
+            // connected.
+            mController.close();
+        }
+    }
+
+    @Override
+    public boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService,
+            KeyEvent ke, int sequenceId, ResultReceiver cb) {
+        // TODO(jaewan): Implement.
+        return false;
+    }
+
+    @Override
+    public void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + "token=" + mSessionToken);
+        pw.println(prefix + "controller=" + mController);
+
+        final String indent = prefix + "  ";
+        pw.println(indent + "playbackActive=" + mController.isPlaybackActive());
+    }
+
+    @Override
+    public String toString() {
+        // TODO(jaewan): Also add getId().
+        return getPackageName() + " (userId=" + getUserId() + ")";
+    }
+
+    private class Controller2Callback extends MediaController2.ControllerCallback {
+        @Override
+        public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) {
+            if (DEBUG) {
+                Log.d(TAG, "connected to " + mSessionToken + ", allowed=" + allowedCommands);
+            }
+            synchronized (mLock) {
+                mIsConnected = true;
+            }
+            mService.onSessionActiveStateChanged(MediaSession2Record.this);
+        }
+
+        @Override
+        public void onDisconnected(MediaController2 controller) {
+            if (DEBUG) {
+                Log.d(TAG, "disconnected from " + mSessionToken);
+            }
+            synchronized (mLock) {
+                mIsConnected = false;
+            }
+            mService.onSessionDied(MediaSession2Record.this);
+        }
+
+        @Override
+        public void onPlaybackActiveChanged(MediaController2 controller, boolean playbackActive) {
+            if (DEBUG) {
+                Log.d(TAG, "playback active changed, " + mSessionToken + ", active="
+                        + playbackActive);
+            }
+            mService.onSessionPlaybackStateChanged(MediaSession2Record.this, playbackActive);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index aa24ed2..1905571 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -56,13 +56,15 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
  * This is the system implementation of a Session. Apps will interact with the
  * MediaSession wrapper class instead.
  */
-public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable {
+// TODO(jaewan): Do not call service method directly -- introduce listener instead.
+public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionRecordImpl {
     private static final String TAG = "MediaSessionRecord";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
@@ -72,6 +74,24 @@
      */
     private static final int OPTIMISTIC_VOLUME_TIMEOUT = 1000;
 
+    /**
+     * These are states that usually indicate the user took an action and should
+     * bump priority regardless of the old state.
+     */
+    private static final List<Integer> ALWAYS_PRIORITY_STATES = Arrays.asList(
+            PlaybackState.STATE_FAST_FORWARDING,
+            PlaybackState.STATE_REWINDING,
+            PlaybackState.STATE_SKIPPING_TO_PREVIOUS,
+            PlaybackState.STATE_SKIPPING_TO_NEXT);
+    /**
+     * These are states that usually indicate the user took an action if they
+     * were entered from a non-priority state.
+     */
+    private static final List<Integer> TRANSITION_PRIORITY_STATES = Arrays.asList(
+            PlaybackState.STATE_BUFFERING,
+            PlaybackState.STATE_CONNECTING,
+            PlaybackState.STATE_PLAYING);
+
     private final MessageHandler mHandler;
 
     private final int mOwnerPid;
@@ -115,6 +135,7 @@
     private int mMaxVolume = 0;
     private int mCurrentVolume = 0;
     private int mOptimisticVolume = -1;
+    private String mVolumeControlId;
     // End volume handling fields
 
     private boolean mIsActive = false;
@@ -170,6 +191,7 @@
      *
      * @return Info that identifies this session.
      */
+    @Override
     public String getPackageName() {
         return mPackageName;
     }
@@ -188,6 +210,7 @@
      *
      * @return The UID for this session.
      */
+    @Override
     public int getUid() {
         return mOwnerUid;
     }
@@ -197,6 +220,7 @@
      *
      * @return The user id for this session.
      */
+    @Override
     public int getUserId() {
         return mUserId;
     }
@@ -207,6 +231,7 @@
      *
      * @return True if this is a system priority session, false otherwise
      */
+    @Override
     public boolean isSystemPriority() {
         return (mFlags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0;
     }
@@ -220,7 +245,6 @@
      * @param opPackageName The op package that made the original volume request.
      * @param pid The pid that made the original volume request.
      * @param uid The uid that made the original volume request.
-     * @param caller caller binder. can be {@code null} if it's from the volume key.
      * @param asSystemService {@code true} if the event sent to the session as if it was come from
      *          the system service instead of the app process. This helps sessions to distinguish
      *          between the key injection by the app and key events from the hardware devices.
@@ -318,9 +342,13 @@
 
     /**
      * Check if this session has been set to active by the app.
+     * <p>
+     * It's not used to prioritize sessions for dispatching media keys since API 26, but still used
+     * to filter session list in MediaSessionManager#getActiveSessions().
      *
      * @return True if the session is active, false otherwise.
      */
+    @Override
     public boolean isActive() {
         return mIsActive && !mDestroyed;
     }
@@ -333,6 +361,7 @@
      * @param expected True if playback is expected to be active. false otherwise.
      * @return True if the session's playback matches with the expectation. false otherwise.
      */
+    @Override
     public boolean checkPlaybackActiveState(boolean expected) {
         if (mPlaybackState == null) {
             return false;
@@ -345,13 +374,14 @@
      *
      * @return {@code true} if the playback is local.
      */
-    public boolean isPlaybackLocal() {
+    @Override
+    public boolean isPlaybackTypeLocal() {
         return mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL;
     }
 
     @Override
     public void binderDied() {
-        mService.sessionDied(this);
+        mService.onSessionDied(this);
     }
 
     /**
@@ -383,7 +413,7 @@
      * @param sequenceId (optional) sequence id. Use this only when a wake lock is needed.
      * @param cb (optional) result receiver to receive callback. Use this only when a wake lock is
      *           needed.
-     * @return {@code true} if the attempt to send media button was successfuly.
+     * @return {@code true} if the attempt to send media button was successfully.
      *         {@code false} otherwise.
      */
     public boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService,
@@ -392,6 +422,7 @@
                 cb);
     }
 
+    @Override
     public void dump(PrintWriter pw, String prefix) {
         pw.println(prefix + mTag + " " + this);
 
@@ -684,7 +715,7 @@
             if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
                 int current = mOptimisticVolume != -1 ? mOptimisticVolume : mCurrentVolume;
                 return new PlaybackInfo(mVolumeType, mVolumeControlType, mMaxVolume, current,
-                        mAudioAttrs);
+                        mAudioAttrs, mVolumeControlId);
             }
             volumeType = mVolumeType;
             attributes = mAudioAttrs;
@@ -693,7 +724,7 @@
         int max = mAudioManager.getStreamMaxVolume(stream);
         int current = mAudioManager.getStreamVolume(stream);
         return new PlaybackInfo(volumeType, VolumeProvider.VOLUME_CONTROL_ABSOLUTE, max,
-                current, attributes);
+                current, attributes, null);
     }
 
     private final Runnable mClearOptimisticVolumeRunnable = new Runnable() {
@@ -712,7 +743,7 @@
         public void destroySession() throws RemoteException {
             final long token = Binder.clearCallingIdentity();
             try {
-                mService.destroySession(MediaSessionRecord.this);
+                mService.onSessionDied(MediaSessionRecord.this);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -734,7 +765,7 @@
             mIsActive = active;
             final long token = Binder.clearCallingIdentity();
             try {
-                mService.updateSession(MediaSessionRecord.this);
+                mService.onSessionActiveStateChanged(MediaSessionRecord.this);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -801,12 +832,16 @@
                     ? PlaybackState.STATE_NONE : mPlaybackState.getState();
             int newState = state == null
                     ? PlaybackState.STATE_NONE : state.getState();
+            boolean shouldUpdatePriority = ALWAYS_PRIORITY_STATES.contains(newState)
+                    || (!TRANSITION_PRIORITY_STATES.contains(oldState)
+                    && TRANSITION_PRIORITY_STATES.contains(newState));
             synchronized (mLock) {
                 mPlaybackState = state;
             }
             final long token = Binder.clearCallingIdentity();
             try {
-                mService.onSessionPlaystateChanged(MediaSessionRecord.this, oldState, newState);
+                mService.onSessionPlaybackStateChanged(
+                        MediaSessionRecord.this, shouldUpdatePriority);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -852,6 +887,7 @@
             synchronized (mLock) {
                 typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE;
                 mVolumeType = PlaybackInfo.PLAYBACK_TYPE_LOCAL;
+                mVolumeControlId = null;
                 if (attributes != null) {
                     mAudioAttrs = attributes;
                 } else {
@@ -870,13 +906,15 @@
         }
 
         @Override
-        public void setPlaybackToRemote(int control, int max) throws RemoteException {
+        public void setPlaybackToRemote(int control, int max, String controlId)
+                throws RemoteException {
             boolean typeChanged;
             synchronized (mLock) {
                 typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL;
                 mVolumeType = PlaybackInfo.PLAYBACK_TYPE_REMOTE;
                 mVolumeControlType = control;
                 mMaxVolume = max;
+                mVolumeControlId = controlId;
             }
             if (typeChanged) {
                 final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
new file mode 100644
index 0000000..2cde89a7
--- /dev/null
+++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import android.media.AudioManager;
+import android.os.ResultReceiver;
+import android.view.KeyEvent;
+
+import java.io.PrintWriter;
+
+/**
+ * Common interfaces between {@link MediaSessionRecord} and {@link MediaSession2Record}.
+ */
+public interface MediaSessionRecordImpl extends AutoCloseable {
+
+    /**
+     * Get the info for this session.
+     *
+     * @return Info that identifies this session.
+     */
+    String getPackageName();
+
+    /**
+     * Get the UID this session was created for.
+     *
+     * @return The UID for this session.
+     */
+    int getUid();
+
+    /**
+     * Get the user id this session was created for.
+     *
+     * @return The user id for this session.
+     */
+    int getUserId();
+
+    /**
+     * Check if this session has system priorty and should receive media buttons
+     * before any other sessions.
+     *
+     * @return True if this is a system priority session, false otherwise
+     */
+    boolean isSystemPriority();
+
+    /**
+     * Send a volume adjustment to the session owner. Direction must be one of
+     * {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE},
+     * {@link AudioManager#ADJUST_SAME}.
+     *
+     * @param packageName The package that made the original volume request.
+     * @param opPackageName The op package that made the original volume request.
+     * @param pid The pid that made the original volume request.
+     * @param uid The uid that made the original volume request.
+     * @param asSystemService {@code true} if the event sent to the session as if it was come from
+     *          the system service instead of the app process. This helps sessions to distinguish
+     *          between the key injection by the app and key events from the hardware devices.
+     *          Should be used only when the volume key events aren't handled by foreground
+     *          activity. {@code false} otherwise to tell session about the real caller.
+     * @param direction The direction to adjust volume in.
+     * @param flags Any of the flags from {@link AudioManager}.
+     * @param useSuggested True to use adjustSuggestedStreamVolume instead of
+     */
+    void adjustVolume(String packageName, String opPackageName, int pid, int uid,
+            boolean asSystemService, int direction, int flags, boolean useSuggested);
+
+    /**
+     * Check if this session has been set to active by the app. (i.e. ready to receive command and
+     * getters are available).
+     *
+     * @return True if the session is active, false otherwise.
+     */
+    // TODO(jaewan): Find better naming, or remove this from the MediaSessionRecordImpl.
+    boolean isActive();
+
+    /**
+     * Check if the session's playback active state matches with the expectation. This always return
+     * {@code false} if the playback state is unknown (e.g. {@code null}), where we cannot know the
+     * actual playback state associated with the session.
+     *
+     * @param expected True if playback is expected to be active. false otherwise.
+     * @return True if the session's playback matches with the expectation. false otherwise.
+     */
+    boolean checkPlaybackActiveState(boolean expected);
+
+    /**
+     * Check whether the playback type is local or remote.
+     * <p>
+     * <ul>
+     *   <li>Local: volume changes the stream volume because playback happens on this device.</li>
+     *   <li>Remote: volume is sent to the apps callback because playback happens on the remote
+     *     device and we cannot know how to control the volume of it.</li>
+     * </ul>
+     *
+     * @return {@code true} if the playback is local. {@code false} if the playback is remote.
+     */
+    boolean isPlaybackTypeLocal();
+
+    /**
+     * Sends media button.
+     *
+     * @param packageName caller package name
+     * @param pid caller pid
+     * @param uid caller uid
+     * @param asSystemService {@code true} if the event sent to the session as if it was come from
+     *          the system service instead of the app process.
+     * @param ke key events
+     * @param sequenceId (optional) sequence id. Use this only when a wake lock is needed.
+     * @param cb (optional) result receiver to receive callback. Use this only when a wake lock is
+     *           needed.
+     * @return {@code true} if the attempt to send media button was successfully.
+     *         {@code false} otherwise.
+     */
+    boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService,
+            KeyEvent ke, int sequenceId, ResultReceiver cb);
+
+    /**
+     * Dumps internal state
+     *
+     * @param pw print writer
+     * @param prefix prefix
+     */
+    void dump(PrintWriter pw, String prefix);
+
+    /**
+     * Override {@link AutoCloseable#close} to tell not to throw exception.
+     */
+    @Override
+    void close();
+}
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 0f059db..a6ad57a 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -40,10 +40,7 @@
 import android.media.AudioManagerInternal;
 import android.media.AudioPlaybackConfiguration;
 import android.media.AudioSystem;
-import android.media.IAudioService;
 import android.media.IRemoteVolumeController;
-import android.media.MediaController2;
-import android.media.Session2CommandGroup;
 import android.media.Session2Token;
 import android.media.session.IActiveSessionsListener;
 import android.media.session.IOnMediaKeyEventDispatchedListener;
@@ -61,7 +58,6 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.Message;
 import android.os.PowerManager;
@@ -107,6 +103,7 @@
 
     private static final int WAKELOCK_TIMEOUT = 5000;
     private static final int MEDIA_KEY_LISTENER_TIMEOUT = 1000;
+    private static final int SESSION_CREATION_LIMIT_PER_UID = 100;
 
     private final Context mContext;
     private final SessionManagerImpl mSessionManagerImpl;
@@ -123,11 +120,6 @@
     @GuardedBy("mLock")
     private final ArrayList<SessionsListenerRecord> mSessionsListeners =
             new ArrayList<SessionsListenerRecord>();
-    // Map user id as index to list of Session2Tokens
-    // TODO: Keep session2 info in MediaSessionStack for prioritizing both session1 and session2 in
-    //       one place.
-    @GuardedBy("mLock")
-    private final SparseArray<List<Session2Token>> mSession2TokensPerUser = new SparseArray<>();
     @GuardedBy("mLock")
     private final List<Session2TokensListenerRecord> mSession2TokensListenerRecords =
             new ArrayList<>();
@@ -175,7 +167,8 @@
                     }
                     synchronized (mLock) {
                         FullUserRecord user = getFullUserRecordLocked(
-                                UserHandle.getUserId(config.getClientUid()));
+                                UserHandle.getUserHandleForUid(config.getClientUid())
+                                        .getIdentifier());
                         if (user != null) {
                             user.mPriorityStack.updateMediaButtonSessionIfNeeded();
                         }
@@ -189,16 +182,11 @@
         updateUser();
     }
 
-    private IAudioService getAudioService() {
-        IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
-        return IAudioService.Stub.asInterface(b);
-    }
-
     private boolean isGlobalPriorityActiveLocked() {
         return mGlobalPrioritySession != null && mGlobalPrioritySession.isActive();
     }
 
-    void updateSession(MediaSessionRecord record) {
+    void onSessionActiveStateChanged(MediaSessionRecordImpl record) {
         synchronized (mLock) {
             FullUserRecord user = getFullUserRecordLocked(record.getUserId());
             if (user == null) {
@@ -215,12 +203,14 @@
                     Log.w(TAG, "Unknown session updated. Ignoring.");
                     return;
                 }
-                user.mPriorityStack.onSessionStateChange(record);
+                user.mPriorityStack.onSessionActiveStateChanged(record);
             }
-            mHandler.postSessionsChanged(record.getUserId());
+
+            mHandler.postSessionsChanged(record);
         }
     }
 
+    // Currently only media1 can become global priority session.
     void setGlobalPrioritySession(MediaSessionRecord record) {
         synchronized (mLock) {
             FullUserRecord user = getFullUserRecordLocked(record.getUserId());
@@ -266,11 +256,13 @@
     List<Session2Token> getSession2TokensLocked(int userId) {
         List<Session2Token> list = new ArrayList<>();
         if (userId == USER_ALL) {
-            for (int i = 0; i < mSession2TokensPerUser.size(); i++) {
-                list.addAll(mSession2TokensPerUser.valueAt(i));
+            int size = mUserRecords.size();
+            for (int i = 0; i < size; i++) {
+                list.addAll(mUserRecords.valueAt(i).mPriorityStack.getSession2Tokens(userId));
             }
         } else {
-            list.addAll(mSession2TokensPerUser.get(userId));
+            FullUserRecord user = getFullUserRecordLocked(userId);
+            list.addAll(user.mPriorityStack.getSession2Tokens(userId));
         }
         return list;
     }
@@ -297,14 +289,15 @@
         }
     }
 
-    void onSessionPlaystateChanged(MediaSessionRecord record, int oldState, int newState) {
+    void onSessionPlaybackStateChanged(MediaSessionRecordImpl record,
+            boolean shouldUpdatePriority) {
         synchronized (mLock) {
             FullUserRecord user = getFullUserRecordLocked(record.getUserId());
             if (user == null || !user.mPriorityStack.contains(record)) {
                 Log.d(TAG, "Unknown session changed playback state. Ignoring.");
                 return;
             }
-            user.mPriorityStack.onPlaystateChanged(record, oldState, newState);
+            user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority);
         }
     }
 
@@ -347,7 +340,6 @@
                     user.destroySessionsForUserLocked(userId);
                 }
             }
-            mSession2TokensPerUser.remove(userId);
             updateUser();
         }
     }
@@ -366,13 +358,7 @@
         }
     }
 
-    void sessionDied(MediaSessionRecord session) {
-        synchronized (mLock) {
-            destroySessionLocked(session);
-        }
-    }
-
-    void destroySession(MediaSessionRecord session) {
+    void onSessionDied(MediaSessionRecordImpl session) {
         synchronized (mLock) {
             destroySessionLocked(session);
         }
@@ -393,9 +379,6 @@
                             mUserRecords.put(userInfo.id, new FullUserRecord(userInfo.id));
                         }
                     }
-                    if (mSession2TokensPerUser.get(userInfo.id) == null) {
-                        mSession2TokensPerUser.put(userInfo.id, new ArrayList<>());
-                    }
                 }
             }
             // Ensure that the current full user exists.
@@ -405,9 +388,6 @@
                 Log.w(TAG, "Cannot find FullUserInfo for the current user " + currentFullUserId);
                 mCurrentFullUserRecord = new FullUserRecord(currentFullUserId);
                 mUserRecords.put(currentFullUserId, mCurrentFullUserRecord);
-                if (mSession2TokensPerUser.get(currentFullUserId) == null) {
-                    mSession2TokensPerUser.put(currentFullUserId, new ArrayList<>());
-                }
             }
             mFullUserIds.put(currentFullUserId, currentFullUserId);
         }
@@ -444,11 +424,23 @@
      * 5. We need to unlink to death from the cb binder
      * 6. We need to tell the session to do any final cleanup (onDestroy)
      */
-    private void destroySessionLocked(MediaSessionRecord session) {
+    private void destroySessionLocked(MediaSessionRecordImpl session) {
         if (DEBUG) {
             Log.d(TAG, "Destroying " + session);
         }
         FullUserRecord user = getFullUserRecordLocked(session.getUserId());
+
+        if (user != null) {
+            final int uid = session.getUid();
+            final int sessionCount = user.mUidToSessionCount.get(uid, 0);
+            if (sessionCount <= 0) {
+                Log.w(TAG, "destroySessionLocked: sessionCount should be positive. "
+                        + "sessionCount=" + sessionCount);
+            } else {
+                user.mUidToSessionCount.put(uid, sessionCount - 1);
+            }
+        }
+
         if (mGlobalPrioritySession == session) {
             mGlobalPrioritySession = null;
             if (session.isActive() && user != null) {
@@ -461,7 +453,7 @@
         }
 
         session.close();
-        mHandler.postSessionsChanged(session.getUserId());
+        mHandler.postSessionsChanged(session);
     }
 
     private void enforcePackageName(String packageName, int uid) {
@@ -494,8 +486,8 @@
         if (mContext
                 .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
                 != PackageManager.PERMISSION_GRANTED
-                && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
-                resolvedUserId)) {
+                && !isEnabledNotificationListener(compName,
+                UserHandle.getUserHandleForUid(uid).getIdentifier(), resolvedUserId)) {
             throw new SecurityException("Missing permission to control media.");
         }
     }
@@ -511,6 +503,20 @@
         }
     }
 
+    private boolean hasMediaControlPermission(int pid, int uid) {
+        // Check if it's system server or has MEDIA_CONTENT_CONTROL.
+        // Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra
+        // check here.
+        if (uid == Process.SYSTEM_UID || mContext.checkPermission(
+                android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
+                == PackageManager.PERMISSION_GRANTED) {
+            return true;
+        } else if (DEBUG) {
+            Log.d(TAG, "uid(" + uid + ") hasn't granted MEDIA_CONTENT_CONTROL");
+        }
+        return false;
+    }
+
     /**
      * This checks if the component is an enabled notification listener for the
      * specified user. Enabled components may only operate on behalf of the user
@@ -541,15 +547,6 @@
         return false;
     }
 
-    private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
-            String callerPackageName, ISessionCallback cb, String tag, Bundle sessionInfo)
-            throws RemoteException {
-        synchronized (mLock) {
-            return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb,
-                    tag, sessionInfo);
-        }
-    }
-
     /*
      * When a session is created the following things need to happen.
      * 1. Its callback binder needs a link to death
@@ -557,29 +554,39 @@
      * 3. It needs to be added to the priority stack.
      * 4. It needs to be added to the relevant user record.
      */
-    private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
+    private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
             String callerPackageName, ISessionCallback cb, String tag, Bundle sessionInfo) {
-        FullUserRecord user = getFullUserRecordLocked(userId);
-        if (user == null) {
-            Log.w(TAG, "Request from invalid user: " +  userId + ", pkg=" + callerPackageName);
-            throw new RuntimeException("Session request from invalid user.");
-        }
+        synchronized (mLock) {
+            FullUserRecord user = getFullUserRecordLocked(userId);
+            if (user == null) {
+                Log.w(TAG, "Request from invalid user: " +  userId + ", pkg=" + callerPackageName);
+                throw new RuntimeException("Session request from invalid user.");
+            }
 
-        final MediaSessionRecord session;
-        try {
-            session = new MediaSessionRecord(callerPid, callerUid, userId,
-                    callerPackageName, cb, tag, sessionInfo, this, mHandler.getLooper());
-        } catch (RemoteException e) {
-            throw new RuntimeException("Media Session owner died prematurely.", e);
-        }
+            final MediaSessionRecord session;
+            try {
+                session = new MediaSessionRecord(callerPid, callerUid, userId,
+                        callerPackageName, cb, tag, sessionInfo, this, mHandler.getLooper());
+            } catch (RemoteException e) {
+                throw new RuntimeException("Media Session owner died prematurely.", e);
+            }
 
-        user.mPriorityStack.addSession(session);
-        mHandler.postSessionsChanged(userId);
+            final int sessionCount = user.mUidToSessionCount.get(callerUid, 0);
+            if (sessionCount >= SESSION_CREATION_LIMIT_PER_UID
+                    && !hasMediaControlPermission(callerPid, callerUid)) {
+                throw new RuntimeException("Created too many sessions. count="
+                        + sessionCount + ")");
+            }
+            user.mUidToSessionCount.put(callerUid, sessionCount + 1);
 
-        if (DEBUG) {
-            Log.d(TAG, "Created session for " + callerPackageName + " with tag " + tag);
+            user.mPriorityStack.addSession(session);
+            mHandler.postSessionsChanged(session);
+
+            if (DEBUG) {
+                Log.d(TAG, "Created session for " + callerPackageName + " with tag " + tag);
+            }
+            return session;
         }
-        return session;
     }
 
     private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) {
@@ -600,16 +607,16 @@
         return -1;
     }
 
-    private void pushSessionsChanged(int userId) {
+    private void pushSession1Changed(int userId) {
         synchronized (mLock) {
             FullUserRecord user = getFullUserRecordLocked(userId);
             if (user == null) {
-                Log.w(TAG, "pushSessionsChanged failed. No user with id=" + userId);
+                Log.w(TAG, "pushSession1ChangedOnHandler failed. No user with id=" + userId);
                 return;
             }
             List<MediaSessionRecord> records = getActiveSessionsLocked(userId);
             int size = records.size();
-            ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
+            ArrayList<MediaSession.Token> tokens = new ArrayList<>();
             for (int i = 0; i < size; i++) {
                 tokens.add(records.get(i).getSessionToken());
             }
@@ -629,6 +636,27 @@
         }
     }
 
+    void pushSession2Changed(int userId) {
+        synchronized (mLock) {
+            List<Session2Token> allSession2Tokens = getSession2TokensLocked(USER_ALL);
+            List<Session2Token> session2Tokens = getSession2TokensLocked(userId);
+
+            for (int i = mSession2TokensListenerRecords.size() - 1; i >= 0; i--) {
+                Session2TokensListenerRecord listenerRecord = mSession2TokensListenerRecords.get(i);
+                try {
+                    if (listenerRecord.userId == USER_ALL) {
+                        listenerRecord.listener.onSession2TokensChanged(allSession2Tokens);
+                    } else if (listenerRecord.userId == userId) {
+                        listenerRecord.listener.onSession2TokensChanged(session2Tokens);
+                    }
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Failed to notify Session2Token change. Removing listener.", e);
+                    mSession2TokensListenerRecords.remove(i);
+                }
+            }
+        }
+    }
+
     private void pushRemoteVolumeUpdateLocked(int userId) {
         FullUserRecord user = getFullUserRecordLocked(userId);
         if (user == null) {
@@ -638,8 +666,13 @@
 
         synchronized (mLock) {
             int size = mRemoteVolumeControllers.beginBroadcast();
-            MediaSessionRecord record = user.mPriorityStack.getDefaultRemoteSession(userId);
-            MediaSession.Token token = record == null ? null : record.getSessionToken();
+            MediaSessionRecordImpl record = user.mPriorityStack.getDefaultRemoteSession(userId);
+            if (record instanceof MediaSession2Record) {
+                // TODO(jaewan): Implement
+                return;
+            }
+            MediaSession.Token token = record == null
+                    ? null : ((MediaSessionRecord) record).getSessionToken();
 
             for (int i = size - 1; i >= 0; i--) {
                 try {
@@ -653,34 +686,15 @@
         }
     }
 
-    void pushSession2TokensChangedLocked(int userId) {
-        List<Session2Token> allSession2Tokens = getSession2TokensLocked(USER_ALL);
-        List<Session2Token> session2Tokens = getSession2TokensLocked(userId);
-
-        for (int i = mSession2TokensListenerRecords.size() - 1; i >= 0; i--) {
-            Session2TokensListenerRecord listenerRecord = mSession2TokensListenerRecords.get(i);
-            try {
-                if (listenerRecord.userId == USER_ALL) {
-                    listenerRecord.listener.onSession2TokensChanged(allSession2Tokens);
-                } else if (listenerRecord.userId == userId) {
-                    listenerRecord.listener.onSession2TokensChanged(session2Tokens);
-                }
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed to notify Session2Token change. Removing listener.", e);
-                mSession2TokensListenerRecords.remove(i);
-            }
-        }
-    }
-
     /**
      * Called when the media button receiver for the {@code record} is changed.
      *
      * @param record the media session whose media button receiver is updated.
      */
-    public void onMediaButtonReceiverChanged(MediaSessionRecord record) {
+    public void onMediaButtonReceiverChanged(MediaSessionRecordImpl record) {
         synchronized (mLock) {
             FullUserRecord user = getFullUserRecordLocked(record.getUserId());
-            MediaSessionRecord mediaButtonSession =
+            MediaSessionRecordImpl mediaButtonSession =
                     user.mPriorityStack.getMediaButtonSession();
             if (record == mediaButtonSession) {
                 user.rememberMediaButtonReceiverLocked(mediaButtonSession);
@@ -744,6 +758,7 @@
                 mOnMediaKeyEventDispatchedListeners = new HashMap<>();
         private final HashMap<IBinder, OnMediaKeyEventSessionChangedListenerRecord>
                 mOnMediaKeyEventSessionChangedListeners = new HashMap<>();
+        private final SparseIntArray mUidToSessionCount = new SparseIntArray();
 
         private PendingIntent mLastMediaButtonReceiver;
         private ComponentName mRestoredMediaButtonReceiver;
@@ -868,39 +883,34 @@
             pw.println(indent + "Restored MediaButtonReceiverComponentType: "
                     + mRestoredMediaButtonReceiverComponentType);
             mPriorityStack.dump(pw, indent);
-            pw.println(indent + "Session2Tokens:");
-            for (int i = 0; i < mSession2TokensPerUser.size(); i++) {
-                List<Session2Token> list = mSession2TokensPerUser.valueAt(i);
-                if (list == null || list.size() == 0) {
-                    continue;
-                }
-                for (Session2Token token : list) {
-                    pw.println(indent + "  " + token);
-                }
-            }
         }
 
         @Override
-        public void onMediaButtonSessionChanged(MediaSessionRecord oldMediaButtonSession,
-                MediaSessionRecord newMediaButtonSession) {
+        public void onMediaButtonSessionChanged(MediaSessionRecordImpl oldMediaButtonSession,
+                MediaSessionRecordImpl newMediaButtonSession) {
             if (DEBUG_KEY_EVENT) {
                 Log.d(TAG, "Media button session is changed to " + newMediaButtonSession);
             }
             synchronized (mLock) {
                 if (oldMediaButtonSession != null) {
-                    mHandler.postSessionsChanged(oldMediaButtonSession.getUserId());
+                    mHandler.postSessionsChanged(oldMediaButtonSession);
                 }
                 if (newMediaButtonSession != null) {
                     rememberMediaButtonReceiverLocked(newMediaButtonSession);
-                    mHandler.postSessionsChanged(newMediaButtonSession.getUserId());
+                    mHandler.postSessionsChanged(newMediaButtonSession);
                 }
                 pushAddressedPlayerChangedLocked();
             }
         }
 
         // Remember media button receiver and keep it in the persistent storage.
-        public void rememberMediaButtonReceiverLocked(MediaSessionRecord record) {
-            PendingIntent receiver = record.getMediaButtonReceiver();
+        public void rememberMediaButtonReceiverLocked(MediaSessionRecordImpl record) {
+            if (record instanceof MediaSession2Record) {
+                // TODO(jaewan): Implement
+                return;
+            }
+            MediaSessionRecord sessionRecord = (MediaSessionRecord) record;
+            PendingIntent receiver = sessionRecord.getMediaButtonReceiver();
             mLastMediaButtonReceiver = receiver;
             mRestoredMediaButtonReceiver = null;
             mRestoredMediaButtonReceiverComponentType = COMPONENT_TYPE_INVALID;
@@ -925,10 +935,15 @@
         private void pushAddressedPlayerChangedLocked(
                 IOnMediaKeyEventSessionChangedListener callback) {
             try {
-                MediaSessionRecord mediaButtonSession = getMediaButtonSessionLocked();
+                MediaSessionRecordImpl mediaButtonSession = getMediaButtonSessionLocked();
                 if (mediaButtonSession != null) {
-                    callback.onMediaKeyEventSessionChanged(mediaButtonSession.getPackageName(),
-                            mediaButtonSession.getSessionToken());
+                    if (mediaButtonSession instanceof MediaSessionRecord) {
+                        MediaSessionRecord session1 = (MediaSessionRecord) mediaButtonSession;
+                        callback.onMediaKeyEventSessionChanged(session1.getPackageName(),
+                                session1.getSessionToken());
+                    } else {
+                        // TODO(jaewan): Implement
+                    }
                 } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
                     callback.onMediaKeyEventSessionChanged(
                             mCurrentFullUserRecord.mLastMediaButtonReceiver
@@ -951,7 +966,7 @@
             }
         }
 
-        private MediaSessionRecord getMediaButtonSessionLocked() {
+        private MediaSessionRecordImpl getMediaButtonSessionLocked() {
             return isGlobalPriorityActiveLocked()
                     ? mGlobalPrioritySession : mPriorityStack.getMediaButtonSession();
         }
@@ -1132,14 +1147,13 @@
                     throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid
                             + " but actually=" + sessionToken.getUid());
                 }
-                Controller2Callback callback = new Controller2Callback(sessionToken);
-                // Note: It's safe not to keep controller here because it wouldn't be GC'ed until
-                //       it's closed.
-                // TODO: Keep controller as well for better readability
-                //       because the GC behavior isn't straightforward.
-                MediaController2 controller = new MediaController2.Builder(mContext, sessionToken)
-                        .setControllerCallback(new HandlerExecutor(mHandler), callback)
-                        .build();
+                MediaSession2Record record = new MediaSession2Record(
+                        sessionToken, MediaSessionService.this, mHandler.getLooper());
+                synchronized (mLock) {
+                    FullUserRecord user = getFullUserRecordLocked(record.getUserId());
+                    user.mPriorityStack.addSession(record);
+                }
+                // Do not immediately notify changes -- do so when framework can dispatch command
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -1180,7 +1194,8 @@
                         null /* optional packageName */);
                 List<Session2Token> result;
                 synchronized (mLock) {
-                    result = getSession2TokensLocked(resolvedUserId);
+                    FullUserRecord user = getFullUserRecordLocked(userId);
+                    result = user.mPriorityStack.getSession2Tokens(resolvedUserId);
                 }
                 return new ParceledListSlice(result);
             } finally {
@@ -1975,20 +1990,6 @@
             return resolvedUserId;
         }
 
-        private boolean hasMediaControlPermission(int pid, int uid) {
-            // Check if it's system server or has MEDIA_CONTENT_CONTROL.
-            // Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra
-            // check here.
-            if (uid == Process.SYSTEM_UID || mContext.checkPermission(
-                    android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
-                    == PackageManager.PERMISSION_GRANTED) {
-                return true;
-            } else if (DEBUG) {
-                Log.d(TAG, "uid(" + uid + ") hasn't granted MEDIA_CONTENT_CONTROL");
-            }
-            return false;
-        }
-
         private boolean hasEnabledNotificationListener(int resolvedUserId, String packageName)
                 throws RemoteException {
             // You may not access another user's content as an enabled listener.
@@ -2018,7 +2019,7 @@
 
         private void dispatchAdjustVolumeLocked(String packageName, String opPackageName, int pid,
                 int uid, boolean asSystemService, int suggestedStream, int direction, int flags) {
-            MediaSessionRecord session = isGlobalPriorityActiveLocked() ? mGlobalPrioritySession
+            MediaSessionRecordImpl session = isGlobalPriorityActiveLocked() ? mGlobalPrioritySession
                     : mCurrentFullUserRecord.mPriorityStack.getDefaultVolumeSession();
 
             boolean preferSuggestedStream = false;
@@ -2109,7 +2110,13 @@
 
         private void dispatchMediaKeyEventLocked(String packageName, int pid, int uid,
                 boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
-            MediaSessionRecord session = mCurrentFullUserRecord.getMediaButtonSessionLocked();
+            if (mCurrentFullUserRecord.getMediaButtonSessionLocked()
+                    instanceof MediaSession2Record) {
+                // TODO(jaewan): Implement
+                return;
+            }
+            MediaSessionRecord session =
+                    (MediaSessionRecord) mCurrentFullUserRecord.getMediaButtonSessionLocked();
             if (session != null) {
                 if (DEBUG_KEY_EVENT) {
                     Log.d(TAG, "Sending " + keyEvent + " to " + session);
@@ -2389,15 +2396,19 @@
     }
 
     final class MessageHandler extends Handler {
-        private static final int MSG_SESSIONS_CHANGED = 1;
-        private static final int MSG_VOLUME_INITIAL_DOWN = 2;
+        private static final int MSG_SESSIONS_1_CHANGED = 1;
+        private static final int MSG_SESSIONS_2_CHANGED = 2;
+        private static final int MSG_VOLUME_INITIAL_DOWN = 3;
         private final SparseArray<Integer> mIntegerCache = new SparseArray<>();
 
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case MSG_SESSIONS_CHANGED:
-                    pushSessionsChanged((int) msg.obj);
+                case MSG_SESSIONS_1_CHANGED:
+                    pushSession1Changed((int) msg.obj);
+                    break;
+                case MSG_SESSIONS_2_CHANGED:
+                    pushSession2Changed((int) msg.obj);
                     break;
                 case MSG_VOLUME_INITIAL_DOWN:
                     synchronized (mLock) {
@@ -2412,41 +2423,19 @@
             }
         }
 
-        public void postSessionsChanged(int userId) {
+        public void postSessionsChanged(MediaSessionRecordImpl record) {
             // Use object instead of the arguments when posting message to remove pending requests.
-            Integer userIdInteger = mIntegerCache.get(userId);
+            Integer userIdInteger = mIntegerCache.get(record.getUserId());
             if (userIdInteger == null) {
-                userIdInteger = Integer.valueOf(userId);
-                mIntegerCache.put(userId, userIdInteger);
+                userIdInteger = Integer.valueOf(record.getUserId());
+                mIntegerCache.put(record.getUserId(), userIdInteger);
             }
-            removeMessages(MSG_SESSIONS_CHANGED, userIdInteger);
-            obtainMessage(MSG_SESSIONS_CHANGED, userIdInteger).sendToTarget();
+
+            int msg = (record instanceof MediaSessionRecord)
+                    ? MSG_SESSIONS_1_CHANGED : MSG_SESSIONS_2_CHANGED;
+            removeMessages(msg, userIdInteger);
+            obtainMessage(msg, userIdInteger).sendToTarget();
         }
     }
 
-    private class Controller2Callback extends MediaController2.ControllerCallback {
-        private final Session2Token mToken;
-
-        Controller2Callback(Session2Token token) {
-            mToken = token;
-        }
-
-        @Override
-        public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) {
-            synchronized (mLock) {
-                int userId = UserHandle.getUserId(mToken.getUid());
-                mSession2TokensPerUser.get(userId).add(mToken);
-                pushSession2TokensChangedLocked(userId);
-            }
-        }
-
-        @Override
-        public void onDisconnected(MediaController2 controller) {
-            synchronized (mLock) {
-                int userId = UserHandle.getUserId(mToken.getUid());
-                mSession2TokensPerUser.get(userId).remove(mToken);
-                pushSession2TokensChangedLocked(userId);
-            }
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index 732563f..7bb7cf4 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -16,8 +16,8 @@
 
 package com.android.server.media;
 
+import android.media.Session2Token;
 import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
 import android.os.Debug;
 import android.os.UserHandle;
 import android.util.IntArray;
@@ -45,51 +45,30 @@
         /**
          * Called when the media button session is changed.
          */
-        void onMediaButtonSessionChanged(MediaSessionRecord oldMediaButtonSession,
-                MediaSessionRecord newMediaButtonSession);
+        void onMediaButtonSessionChanged(MediaSessionRecordImpl oldMediaButtonSession,
+                MediaSessionRecordImpl newMediaButtonSession);
     }
 
     /**
-     * These are states that usually indicate the user took an action and should
-     * bump priority regardless of the old state.
+     * Sorted list of the media sessions
      */
-    private static final int[] ALWAYS_PRIORITY_STATES = {
-            PlaybackState.STATE_FAST_FORWARDING,
-            PlaybackState.STATE_REWINDING,
-            PlaybackState.STATE_SKIPPING_TO_PREVIOUS,
-            PlaybackState.STATE_SKIPPING_TO_NEXT };
-    /**
-     * These are states that usually indicate the user took an action if they
-     * were entered from a non-priority state.
-     */
-    private static final int[] TRANSITION_PRIORITY_STATES = {
-            PlaybackState.STATE_BUFFERING,
-            PlaybackState.STATE_CONNECTING,
-            PlaybackState.STATE_PLAYING };
-
-    /**
-     * Sorted list of the media sessions.
-     * The session of which PlaybackState is changed to ALWAYS_PRIORITY_STATES or
-     * TRANSITION_PRIORITY_STATES comes first.
-     * @see #shouldUpdatePriority
-     */
-    private final List<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
+    private final List<MediaSessionRecordImpl> mSessions = new ArrayList<>();
 
     private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
     private final OnMediaButtonSessionChangedListener mOnMediaButtonSessionChangedListener;
 
     /**
      * The media button session which receives media key events.
-     * It could be null if the previous media buttion session is released.
+     * It could be null if the previous media button session is released.
      */
-    private MediaSessionRecord mMediaButtonSession;
+    private MediaSessionRecordImpl mMediaButtonSession;
 
-    private MediaSessionRecord mCachedVolumeDefault;
+    private MediaSessionRecordImpl mCachedVolumeDefault;
 
     /**
      * Cache the result of the {@link #getActiveSessions} per user.
      */
-    private final SparseArray<ArrayList<MediaSessionRecord>> mCachedActiveLists =
+    private final SparseArray<List<MediaSessionRecord>> mCachedActiveLists =
             new SparseArray<>();
 
     MediaSessionStack(AudioPlayerStateMonitor monitor, OnMediaButtonSessionChangedListener listener) {
@@ -102,7 +81,7 @@
      *
      * @param record The record to add.
      */
-    public void addSession(MediaSessionRecord record) {
+    public void addSession(MediaSessionRecordImpl record) {
         mSessions.add(record);
         clearCache(record.getUserId());
 
@@ -117,7 +96,7 @@
      *
      * @param record The record to remove.
      */
-    public void removeSession(MediaSessionRecord record) {
+    public void removeSession(MediaSessionRecordImpl record) {
         mSessions.remove(record);
         if (mMediaButtonSession == record) {
             // When the media button session is removed, nullify the media button session and do not
@@ -131,7 +110,7 @@
     /**
      * Return if the record exists in the priority tracker.
      */
-    public boolean contains(MediaSessionRecord record) {
+    public boolean contains(MediaSessionRecordImpl record) {
         return mSessions.contains(record);
     }
 
@@ -142,9 +121,12 @@
      * @return the MediaSessionRecord. Can be {@code null} if the session is gone meanwhile.
      */
     public MediaSessionRecord getMediaSessionRecord(MediaSession.Token sessionToken) {
-        for (MediaSessionRecord record : mSessions) {
-            if (Objects.equals(record.getSessionToken(), sessionToken)) {
-                return record;
+        for (MediaSessionRecordImpl record : mSessions) {
+            if (record instanceof MediaSessionRecord) {
+                MediaSessionRecord session1 = (MediaSessionRecord) record;
+                if (Objects.equals(session1.getSessionToken(), sessionToken)) {
+                    return session1;
+                }
             }
         }
         return null;
@@ -154,15 +136,15 @@
      * Notify the priority tracker that a session's playback state changed.
      *
      * @param record The record that changed.
-     * @param oldState Its old playback state.
-     * @param newState Its new playback state.
+     * @param shouldUpdatePriority {@code true} if the record needs to prioritized
      */
-    public void onPlaystateChanged(MediaSessionRecord record, int oldState, int newState) {
-        if (shouldUpdatePriority(oldState, newState)) {
+    public void onPlaybackStateChanged(
+            MediaSessionRecordImpl record, boolean shouldUpdatePriority) {
+        if (shouldUpdatePriority) {
             mSessions.remove(record);
             mSessions.add(0, record);
             clearCache(record.getUserId());
-        } else if (!MediaSession.isActiveState(newState)) {
+        } else if (record.checkPlaybackActiveState(false)) {
             // Just clear the volume cache when a state goes inactive
             mCachedVolumeDefault = null;
         }
@@ -172,7 +154,7 @@
         // In that case, we pick the media session whose PlaybackState matches
         // the audio playback configuration.
         if (mMediaButtonSession != null && mMediaButtonSession.getUid() == record.getUid()) {
-            MediaSessionRecord newMediaButtonSession =
+            MediaSessionRecordImpl newMediaButtonSession =
                     findMediaButtonSession(mMediaButtonSession.getUid());
             if (newMediaButtonSession != mMediaButtonSession) {
                 updateMediaButtonSession(newMediaButtonSession);
@@ -185,7 +167,7 @@
      *
      * @param record The record that changed.
      */
-    public void onSessionStateChange(MediaSessionRecord record) {
+    public void onSessionActiveStateChanged(MediaSessionRecordImpl record) {
         // For now just clear the cache. Eventually we'll selectively clear
         // depending on what changed.
         clearCache(record.getUserId());
@@ -203,7 +185,7 @@
         }
         IntArray audioPlaybackUids = mAudioPlayerStateMonitor.getSortedAudioPlaybackClientUids();
         for (int i = 0; i < audioPlaybackUids.size(); i++) {
-            MediaSessionRecord mediaButtonSession =
+            MediaSessionRecordImpl mediaButtonSession =
                     findMediaButtonSession(audioPlaybackUids.get(i));
             if (mediaButtonSession != null) {
                 // Found the media button session.
@@ -225,9 +207,9 @@
      * @return The media button session. Returns {@code null} if the app doesn't have a media
      *   session.
      */
-    private MediaSessionRecord findMediaButtonSession(int uid) {
-        MediaSessionRecord mediaButtonSession = null;
-        for (MediaSessionRecord session : mSessions) {
+    private MediaSessionRecordImpl findMediaButtonSession(int uid) {
+        MediaSessionRecordImpl mediaButtonSession = null;
+        for (MediaSessionRecordImpl session : mSessions) {
             if (uid == session.getUid()) {
                 if (session.checkPlaybackActiveState(
                         mAudioPlayerStateMonitor.isPlaybackActive(session.getUid()))) {
@@ -253,8 +235,8 @@
      *    for all users in this {@link MediaSessionStack}.
      * @return All the active sessions in priority order.
      */
-    public ArrayList<MediaSessionRecord> getActiveSessions(int userId) {
-        ArrayList<MediaSessionRecord> cachedActiveList = mCachedActiveLists.get(userId);
+    public List<MediaSessionRecord> getActiveSessions(int userId) {
+        List<MediaSessionRecord> cachedActiveList = mCachedActiveLists.get(userId);
         if (cachedActiveList == null) {
             cachedActiveList = getPriorityList(true, userId);
             mCachedActiveLists.put(userId, cachedActiveList);
@@ -263,26 +245,46 @@
     }
 
     /**
+     * Gets the session2 tokens.
+     *
+     * @param userId The user to check. It can be {@link UserHandle#USER_ALL} to get all session2
+     *    tokens for all users in this {@link MediaSessionStack}.
+     * @return All session2 tokens.
+     */
+    public List<Session2Token> getSession2Tokens(int userId) {
+        ArrayList<Session2Token> session2Records = new ArrayList<>();
+        for (MediaSessionRecordImpl record : mSessions) {
+            if ((userId == UserHandle.USER_ALL || record.getUserId() == userId)
+                    && record.isActive()
+                    && record instanceof MediaSession2Record) {
+                MediaSession2Record session2 = (MediaSession2Record) record;
+                session2Records.add(session2.getSession2Token());
+            }
+        }
+        return session2Records;
+    }
+
+    /**
      * Get the media button session which receives the media button events.
      *
      * @return The media button session or null.
      */
-    public MediaSessionRecord getMediaButtonSession() {
+    public MediaSessionRecordImpl getMediaButtonSession() {
         return mMediaButtonSession;
     }
 
-    private void updateMediaButtonSession(MediaSessionRecord newMediaButtonSession) {
-        MediaSessionRecord oldMediaButtonSession = mMediaButtonSession;
+    private void updateMediaButtonSession(MediaSessionRecordImpl newMediaButtonSession) {
+        MediaSessionRecordImpl oldMediaButtonSession = mMediaButtonSession;
         mMediaButtonSession = newMediaButtonSession;
         mOnMediaButtonSessionChangedListener.onMediaButtonSessionChanged(
                 oldMediaButtonSession, newMediaButtonSession);
     }
 
-    public MediaSessionRecord getDefaultVolumeSession() {
+    public MediaSessionRecordImpl getDefaultVolumeSession() {
         if (mCachedVolumeDefault != null) {
             return mCachedVolumeDefault;
         }
-        ArrayList<MediaSessionRecord> records = getPriorityList(true, UserHandle.USER_ALL);
+        List<MediaSessionRecord> records = getPriorityList(true, UserHandle.USER_ALL);
         int size = records.size();
         for (int i = 0; i < size; i++) {
             MediaSessionRecord record = records.get(i);
@@ -294,13 +296,13 @@
         return null;
     }
 
-    public MediaSessionRecord getDefaultRemoteSession(int userId) {
-        ArrayList<MediaSessionRecord> records = getPriorityList(true, userId);
+    public MediaSessionRecordImpl getDefaultRemoteSession(int userId) {
+        List<MediaSessionRecord> records = getPriorityList(true, userId);
 
         int size = records.size();
         for (int i = 0; i < size; i++) {
             MediaSessionRecord record = records.get(i);
-            if (!record.isPlaybackLocal()) {
+            if (!record.isPlaybackTypeLocal()) {
                 return record;
             }
         }
@@ -308,16 +310,11 @@
     }
 
     public void dump(PrintWriter pw, String prefix) {
-        ArrayList<MediaSessionRecord> sortedSessions = getPriorityList(false,
-                UserHandle.USER_ALL);
-        int count = sortedSessions.size();
         pw.println(prefix + "Media button session is " + mMediaButtonSession);
-        pw.println(prefix + "Sessions Stack - have " + count + " sessions:");
+        pw.println(prefix + "Sessions Stack - have " + mSessions.size() + " sessions:");
         String indent = prefix + "  ";
-        for (int i = 0; i < count; i++) {
-            MediaSessionRecord record = sortedSessions.get(i);
+        for (MediaSessionRecordImpl record : mSessions) {
             record.dump(pw, indent);
-            pw.println();
         }
     }
 
@@ -335,17 +332,19 @@
      *            will return sessions for all users.
      * @return The priority sorted list of sessions.
      */
-    public ArrayList<MediaSessionRecord> getPriorityList(boolean activeOnly, int userId) {
-        ArrayList<MediaSessionRecord> result = new ArrayList<MediaSessionRecord>();
+    public List<MediaSessionRecord> getPriorityList(boolean activeOnly, int userId) {
+        List<MediaSessionRecord> result = new ArrayList<MediaSessionRecord>();
         int lastPlaybackActiveIndex = 0;
         int lastActiveIndex = 0;
 
-        int size = mSessions.size();
-        for (int i = 0; i < size; i++) {
-            final MediaSessionRecord session = mSessions.get(i);
+        for (MediaSessionRecordImpl record : mSessions) {
+            if (!(record instanceof MediaSessionRecord)) {
+                continue;
+            }
+            final MediaSessionRecord session = (MediaSessionRecord) record;
 
-            if (userId != UserHandle.USER_ALL && userId != session.getUserId()) {
-                // Filter out sessions for the wrong user
+            if ((userId != UserHandle.USER_ALL && userId != session.getUserId())) {
+                // Filter out sessions for the wrong user or session2.
                 continue;
             }
 
@@ -369,26 +368,6 @@
         return result;
     }
 
-    private boolean shouldUpdatePriority(int oldState, int newState) {
-        if (containsState(newState, ALWAYS_PRIORITY_STATES)) {
-            return true;
-        }
-        if (!containsState(oldState, TRANSITION_PRIORITY_STATES)
-                && containsState(newState, TRANSITION_PRIORITY_STATES)) {
-            return true;
-        }
-        return false;
-    }
-
-    private boolean containsState(int state, int[] states) {
-        for (int i = 0; i < states.length; i++) {
-            if (states[i] == state) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     private void clearCache(int userId) {
         mCachedVolumeDefault = null;
         mCachedActiveLists.remove(userId);
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 5302765..558eb8d 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -16,6 +16,9 @@
 
 package com.android.server.media;
 
+import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
+import static android.media.MediaRoute2Info.FEATURE_LIVE_VIDEO;
+
 import android.annotation.NonNull;
 import android.content.ComponentName;
 import android.content.Context;
@@ -26,6 +29,8 @@
 import android.media.IAudioService;
 import android.media.MediaRoute2Info;
 import android.media.MediaRoute2ProviderInfo;
+import android.media.RoutingSessionInfo;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
@@ -36,6 +41,8 @@
 import com.android.internal.R;
 
 import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
 
 /**
  * Provides routes for local playbacks such as phone speaker, wired headset, or Bluetooth speakers.
@@ -45,24 +52,20 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     static final String DEFAULT_ROUTE_ID = "DEFAULT_ROUTE";
-    static final String BLUETOOTH_ROUTE_ID = "BLUETOOTH_ROUTE";
-
-    // TODO: Move these to a proper place
-    public static final String CATEGORY_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO";
-    public static final String CATEGORY_LIVE_VIDEO = "android.media.intent.category.LIVE_VIDEO";
+    static final String SYSTEM_SESSION_ID = "SYSTEM_SESSION";
 
     private final AudioManager mAudioManager;
     private final IAudioService mAudioService;
     private final Handler mHandler;
     private final Context mContext;
+    private final BluetoothRouteProvider mBtRouteProvider;
 
     private static ComponentName sComponentName = new ComponentName(
             SystemMediaRoute2Provider.class.getPackageName$(),
             SystemMediaRoute2Provider.class.getName());
 
-    //TODO: Clean up these when audio manager support multiple bt devices
     MediaRoute2Info mDefaultRoute;
-    MediaRoute2Info mBluetoothA2dpRoute;
+    @NonNull List<MediaRoute2Info> mBluetoothRoutes = Collections.EMPTY_LIST;
     final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
 
     final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() {
@@ -87,33 +90,50 @@
         mAudioService = IAudioService.Stub.asInterface(
                 ServiceManager.getService(Context.AUDIO_SERVICE));
 
-        initializeRoutes();
+        initializeDefaultRoute();
+        mBtRouteProvider = BluetoothRouteProvider.getInstance(context, (routes) -> {
+            mBluetoothRoutes = routes;
+            publishRoutes();
+
+            boolean sessionInfoChanged;
+            synchronized (mLock) {
+                sessionInfoChanged = updateSessionInfosIfNeededLocked();
+            }
+            if (sessionInfoChanged) {
+                notifySessionInfoUpdated();
+            }
+        });
+        initializeSessionInfo();
     }
 
     @Override
-    public void requestCreateSession(String packageName, String routeId, String controlCategory,
-            long requestId) {
+    public void requestCreateSession(String packageName, String routeId, long requestId,
+            Bundle sessionHints) {
         // Do nothing
     }
 
     @Override
-    public void releaseSession(int sessionId) {
+    public void releaseSession(String sessionId) {
         // Do nothing
     }
 
     @Override
-    public void selectRoute(int sessionId, String routeId) {
-        //TODO: implement method
+    public void selectRoute(String sessionId, String routeId) {
+        // Do nothing since we don't support multiple BT yet.
     }
 
     @Override
-    public void deselectRoute(int sessionId, String routeId) {
-        //TODO: implement method
+    public void deselectRoute(String sessionId, String routeId) {
+        // Do nothing since we don't support multiple BT yet.
     }
 
     @Override
-    public void transferToRoute(int sessionId, String routeId) {
-        //TODO: implement method
+    public void transferToRoute(String sessionId, String routeId) {
+        if (TextUtils.equals(routeId, mDefaultRoute.getId())) {
+            mBtRouteProvider.clearActiveDevices();
+        } else {
+            mBtRouteProvider.setActiveDevice(routeId);
+        }
     }
 
     //TODO: implement method
@@ -131,8 +151,7 @@
     public void requestUpdateVolume(String routeId, int delta) {
     }
 
-    void initializeRoutes() {
-        //TODO: adds necessary info
+    private void initializeDefaultRoute() {
         mDefaultRoute = new MediaRoute2Info.Builder(
                 DEFAULT_ROUTE_ID,
                 mContext.getResources().getText(R.string.default_audio_route_name).toString())
@@ -141,8 +160,8 @@
                         : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
                 .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
                 .setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC))
-                .addSupportedCategory(CATEGORY_LIVE_AUDIO)
-                .addSupportedCategory(CATEGORY_LIVE_VIDEO)
+                .addFeature(FEATURE_LIVE_AUDIO)
+                .addFeature(FEATURE_LIVE_VIDEO)
                 .build();
 
         AudioRoutesInfo newAudioRoutes = null;
@@ -156,11 +175,28 @@
             // route yet.
             updateAudioRoutes(newAudioRoutes);
         }
-
-        publishRoutes();
     }
 
-    void updateAudioRoutes(AudioRoutesInfo newRoutes) {
+    private void initializeSessionInfo() {
+        mBluetoothRoutes = mBtRouteProvider.getBluetoothRoutes();
+
+        MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
+        builder.addRoute(mDefaultRoute);
+        for (MediaRoute2Info route : mBluetoothRoutes) {
+            builder.addRoute(route);
+        }
+        setProviderState(builder.build());
+        mHandler.post(() -> notifyProviderState());
+
+        //TODO: clean up this
+        // This is required because it is not instantiated in the main thread and
+        // BluetoothRoutesUpdatedListener can be called before this function
+        synchronized (mLock) {
+            updateSessionInfosIfNeededLocked();
+        }
+    }
+
+    private void updateAudioRoutes(AudioRoutesInfo newRoutes) {
         int name = R.string.default_audio_route_name;
         mCurAudioRoutesInfo.mainType = newRoutes.mainType;
         if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0
@@ -181,41 +217,63 @@
                         : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
                 .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
                 .setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC))
-                .addSupportedCategory(CATEGORY_LIVE_AUDIO)
-                .addSupportedCategory(CATEGORY_LIVE_VIDEO)
+                .addFeature(FEATURE_LIVE_AUDIO)
+                .addFeature(FEATURE_LIVE_VIDEO)
                 .build();
 
-        if (!TextUtils.equals(newRoutes.bluetoothName, mCurAudioRoutesInfo.bluetoothName)) {
-            mCurAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName;
-            if (mCurAudioRoutesInfo.bluetoothName != null) {
-                //TODO: mark as bluetooth once MediaRoute2Info has device type
-                mBluetoothA2dpRoute = new MediaRoute2Info.Builder(BLUETOOTH_ROUTE_ID,
-                        mCurAudioRoutesInfo.bluetoothName)
-                        .setDescription(mContext.getResources().getText(
-                                R.string.bluetooth_a2dp_audio_route_name).toString())
-                        .addSupportedCategory(CATEGORY_LIVE_AUDIO)
-                        .build();
-            } else {
-                mBluetoothA2dpRoute = null;
-            }
-        }
-
         publishRoutes();
     }
 
     /**
-     * The first route should be the currently selected system route.
-     * For example, if there are two system routes (BT and device speaker),
-     * BT will be the first route in the list.
-     *
-     * TODO: Support multiple BT devices
+     * Updates the mSessionInfo. Returns true if the session info is changed.
      */
+    boolean updateSessionInfosIfNeededLocked() {
+        RoutingSessionInfo oldSessionInfo = mSessionInfos.isEmpty() ? null : mSessionInfos.get(0);
+
+        RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
+                SYSTEM_SESSION_ID, "" /* clientPackageName */)
+                .setSystemSession(true);
+        String activeBtDeviceAddress = mBtRouteProvider.getActiveDeviceAddress();
+
+        if (!TextUtils.isEmpty(activeBtDeviceAddress)) {
+            // Bluetooth route. Set the route ID with the device's address.
+            builder.addSelectedRoute(activeBtDeviceAddress);
+            builder.addTransferrableRoute(mDefaultRoute.getId());
+        } else {
+            // Default device
+            builder.addSelectedRoute(mDefaultRoute.getId());
+        }
+
+        for (MediaRoute2Info route : mBluetoothRoutes) {
+            if (!TextUtils.equals(activeBtDeviceAddress, route.getId())) {
+                builder.addTransferrableRoute(route.getId());
+            }
+        }
+
+        RoutingSessionInfo newSessionInfo = builder.setProviderId(mUniqueId).build();
+        if (Objects.equals(oldSessionInfo, newSessionInfo)) {
+            return false;
+        } else {
+            mSessionInfos.clear();
+            mSessionInfos.add(newSessionInfo);
+            return true;
+        }
+    }
+
     void publishRoutes() {
         MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
-        if (mBluetoothA2dpRoute != null) {
-            builder.addRoute(mBluetoothA2dpRoute);
-        }
         builder.addRoute(mDefaultRoute);
-        setAndNotifyProviderState(builder.build(), Collections.emptyList());
+        for (MediaRoute2Info route : mBluetoothRoutes) {
+            builder.addRoute(route);
+        }
+        setAndNotifyProviderState(builder.build());
+    }
+
+    void notifySessionInfoUpdated() {
+        RoutingSessionInfo sessionInfo;
+        synchronized (mLock) {
+            sessionInfo = mSessionInfos.get(0);
+        }
+        mCallback.onSessionUpdated(this, sessionInfo);
     }
 }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
index 7f650ee..b24a938 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
@@ -18,8 +18,10 @@
 
 import static com.android.server.net.NetworkPolicyManagerService.isUidNetworkingBlockedInternal;
 
+import android.annotation.NonNull;
 import android.net.Network;
 import android.net.NetworkTemplate;
+import android.net.netstats.provider.AbstractNetworkStatsProvider;
 import android.telephony.SubscriptionPlan;
 
 import java.util.Set;
@@ -126,4 +128,12 @@
      */
     public abstract void setMeteredRestrictedPackagesAsync(
             Set<String> packageNames, int userId);
+
+    /**
+     *  Notifies that any of the {@link AbstractNetworkStatsProvider} has reached its quota
+     *  which was set through {@link AbstractNetworkStatsProvider#setLimit(String, long)}.
+     *
+     * @param tag the human readable identifier of the custom network stats provider.
+     */
+    public abstract void onStatsProviderLimitReached(@NonNull String tag);
 }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 4a45730..bb954ab 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -75,6 +75,7 @@
 import static android.net.NetworkTemplate.MATCH_WIFI;
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
 import static android.net.TrafficStats.MB_IN_BYTES;
+import static android.net.netstats.provider.AbstractNetworkStatsProvider.QUOTA_UNLIMITED;
 import static android.os.Trace.TRACE_TAG_NETWORK;
 import static android.provider.Settings.Global.NETPOLICY_OVERRIDE_ENABLED;
 import static android.provider.Settings.Global.NETPOLICY_QUOTA_ENABLED;
@@ -160,7 +161,7 @@
 import android.net.NetworkState;
 import android.net.NetworkStats;
 import android.net.NetworkTemplate;
-import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
 import android.net.TrafficStats;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
@@ -168,6 +169,7 @@
 import android.os.Binder;
 import android.os.Environment;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.HandlerThread;
 import android.os.IDeviceIdleController;
 import android.os.INetworkManagementService;
@@ -391,6 +393,7 @@
     private static final int MSG_METERED_RESTRICTED_PACKAGES_CHANGED = 17;
     private static final int MSG_SET_NETWORK_TEMPLATE_ENABLED = 18;
     private static final int MSG_SUBSCRIPTION_PLANS_CHANGED = 19;
+    private static final int MSG_STATS_PROVIDER_LIMIT_REACHED = 20;
 
     private static final int UID_MSG_STATE_CHANGED = 100;
     private static final int UID_MSG_GONE = 101;
@@ -880,7 +883,8 @@
 
             // Listen for subscriber changes
             mContext.getSystemService(SubscriptionManager.class).addOnSubscriptionsChangedListener(
-                    new OnSubscriptionsChangedListener(mHandler.getLooper()) {
+                    new HandlerExecutor(mHandler),
+                    new OnSubscriptionsChangedListener() {
                         @Override
                         public void onSubscriptionsChanged() {
                             updateNetworksInternal();
@@ -2875,17 +2879,6 @@
     }
 
     @Override
-    public void onTetheringChanged(String iface, boolean tethering) {
-        // No need to enforce permission because setRestrictBackground() will do it.
-        synchronized (mUidRulesFirstLock) {
-            if (mRestrictBackground && tethering) {
-                Log.d(TAG, "Tethering on (" + iface +"); disable Data Saver");
-                setRestrictBackground(false);
-            }
-        }
-    }
-
-    @Override
     public void setRestrictBackground(boolean restrictBackground) {
         Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "setRestrictBackground");
         try {
@@ -4518,19 +4511,36 @@
                     mListeners.finishBroadcast();
                     return true;
                 }
-                case MSG_LIMIT_REACHED: {
-                    final String iface = (String) msg.obj;
+                case MSG_STATS_PROVIDER_LIMIT_REACHED: {
+                    mNetworkStats.forceUpdate();
 
                     synchronized (mNetworkPoliciesSecondLock) {
-                        if (mMeteredIfaces.contains(iface)) {
-                            // force stats update to make sure we have
-                            // numbers that caused alert to trigger.
-                            mNetworkStats.forceUpdate();
-
-                            updateNetworkEnabledNL();
-                            updateNotificationsNL();
+                        // Some providers might hit the limit reached event prior to others. Thus,
+                        // re-calculate and update interface quota for every provider is needed.
+                        updateNetworkRulesNL();
+                        updateNetworkEnabledNL();
+                        updateNotificationsNL();
+                    }
+                    return true;
+                }
+                case MSG_LIMIT_REACHED: {
+                    final String iface = (String) msg.obj;
+                    synchronized (mNetworkPoliciesSecondLock) {
+                        // fast return if not needed.
+                        if (!mMeteredIfaces.contains(iface)) {
+                            return true;
                         }
                     }
+
+                    // force stats update to make sure the service have the numbers that caused
+                    // alert to trigger.
+                    mNetworkStats.forceUpdate();
+
+                    synchronized (mNetworkPoliciesSecondLock) {
+                        updateNetworkRulesNL();
+                        updateNetworkEnabledNL();
+                        updateNotificationsNL();
+                    }
                     return true;
                 }
                 case MSG_RESTRICT_BACKGROUND_CHANGED: {
@@ -4573,14 +4583,18 @@
                     return true;
                 }
                 case MSG_UPDATE_INTERFACE_QUOTA: {
-                    removeInterfaceQuota((String) msg.obj);
+                    final String iface = (String) msg.obj;
                     // int params need to be stitched back into a long
-                    setInterfaceQuota((String) msg.obj,
-                            ((long) msg.arg1 << 32) | (msg.arg2 & 0xFFFFFFFFL));
+                    final long quota = ((long) msg.arg1 << 32) | (msg.arg2 & 0xFFFFFFFFL);
+                    removeInterfaceQuota(iface);
+                    setInterfaceQuota(iface, quota);
+                    mNetworkStats.setStatsProviderLimit(iface, quota);
                     return true;
                 }
                 case MSG_REMOVE_INTERFACE_QUOTA: {
-                    removeInterfaceQuota((String) msg.obj);
+                    final String iface = (String) msg.obj;
+                    removeInterfaceQuota(iface);
+                    mNetworkStats.setStatsProviderLimit(iface, QUOTA_UNLIMITED);
                     return true;
                 }
                 case MSG_RESET_FIREWALL_RULES_BY_UID: {
@@ -5235,6 +5249,12 @@
             mHandler.obtainMessage(MSG_METERED_RESTRICTED_PACKAGES_CHANGED,
                     userId, 0, packageNames).sendToTarget();
         }
+
+        @Override
+        public void onStatsProviderLimitReached(@NonNull String tag) {
+            Log.v(TAG, "onStatsProviderLimitReached: " + tag);
+            mHandler.obtainMessage(MSG_STATS_PROVIDER_LIMIT_REACHED).sendToTarget();
+        }
     }
 
     private void setMeteredRestrictedPackagesInternal(Set<String> packageNames, int userId) {
@@ -5263,16 +5283,12 @@
     }
 
     private int parseSubId(NetworkState state) {
-        // TODO: moved to using a legitimate NetworkSpecifier instead of string parsing
         int subId = INVALID_SUBSCRIPTION_ID;
         if (state != null && state.networkCapabilities != null
                 && state.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
             NetworkSpecifier spec = state.networkCapabilities.getNetworkSpecifier();
-            if (spec instanceof StringNetworkSpecifier) {
-                try {
-                    subId = Integer.parseInt(((StringNetworkSpecifier) spec).specifier);
-                } catch (NumberFormatException e) {
-                }
+            if (spec instanceof TelephonyNetworkSpecifier) {
+                subId = ((TelephonyNetworkSpecifier) spec).getSubscriptionId();
             }
         }
         return subId;
diff --git a/services/core/java/com/android/server/net/NetworkStatsFactory.java b/services/core/java/com/android/server/net/NetworkStatsFactory.java
index 3ca1803..22b01be 100644
--- a/services/core/java/com/android/server/net/NetworkStatsFactory.java
+++ b/services/core/java/com/android/server/net/NetworkStatsFactory.java
@@ -229,7 +229,7 @@
                     entry.txPackets += reader.nextLong();
                 }
 
-                stats.addValues(entry);
+                stats.addEntry(entry);
                 reader.finishLine();
             }
         } catch (NullPointerException|NumberFormatException e) {
@@ -279,7 +279,7 @@
                 entry.txBytes = reader.nextLong();
                 entry.txPackets = reader.nextLong();
 
-                stats.addValues(entry);
+                stats.addEntry(entry);
                 reader.finishLine();
             }
         } catch (NullPointerException|NumberFormatException e) {
@@ -439,7 +439,7 @@
                 if ((limitIfaces == null || ArrayUtils.contains(limitIfaces, entry.iface))
                         && (limitUid == UID_ALL || limitUid == entry.uid)
                         && (limitTag == TAG_ALL || limitTag == entry.tag)) {
-                    stats.addValues(entry);
+                    stats.addEntry(entry);
                 }
 
                 reader.finishLine();
diff --git a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
index 4843ede..6d72cb5 100644
--- a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
+++ b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
@@ -16,6 +16,7 @@
 
 package com.android.server.net;
 
+import android.annotation.NonNull;
 import android.net.NetworkStats;
 import android.net.NetworkTemplate;
 
@@ -34,4 +35,10 @@
 
     /** Force update of statistics. */
     public abstract void forceUpdate();
+
+    /**
+     * Set the quota limit to all registered custom network stats providers.
+     * Note that invocation of any interface will be sent to all providers.
+     */
+    public abstract void setStatsProviderLimit(@NonNull String iface, long quota);
 }
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index ec8a8e7..1dcff07 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.ACCESS_NETWORK_STATE;
 import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
+import static android.Manifest.permission.UPDATE_DEVICE_STATS;
 import static android.content.Intent.ACTION_SHUTDOWN;
 import static android.content.Intent.ACTION_UID_REMOVED;
 import static android.content.Intent.ACTION_USER_REMOVED;
@@ -27,6 +28,7 @@
 import static android.net.NetworkStack.checkNetworkStackPermission;
 import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
 import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.IFACE_VT;
 import static android.net.NetworkStats.INTERFACES_ALL;
 import static android.net.NetworkStats.METERED_ALL;
 import static android.net.NetworkStats.ROAMING_ALL;
@@ -70,6 +72,7 @@
 import static com.android.server.NetworkManagementSocketTagger.setKernelCounterSet;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.app.usage.NetworkStatsManager;
@@ -96,6 +99,9 @@
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
 import android.net.TrafficStats;
+import android.net.netstats.provider.INetworkStatsProvider;
+import android.net.netstats.provider.INetworkStatsProviderCallback;
+import android.net.netstats.provider.NetworkStatsProviderCallback;
 import android.os.BestClock;
 import android.os.Binder;
 import android.os.DropBoxManager;
@@ -108,6 +114,7 @@
 import android.os.Message;
 import android.os.Messenger;
 import android.os.PowerManager;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.Trace;
@@ -175,7 +182,7 @@
      * This avoids firing the global alert too often on devices with high transfer speeds and
      * high quota.
      */
-    private static final int PERFORM_POLL_DELAY_MS = 1000;
+    private static final int DEFAULT_PERFORM_POLL_DELAY_MS = 1000;
 
     private static final String TAG_NETSTATS_ERROR = "netstats_error";
 
@@ -211,13 +218,15 @@
     /**
      * Virtual network interface for video telephony. This is for VT data usage counting purpose.
      */
-    public static final String VT_INTERFACE = "vt_data0";
+    // TODO: Remove this after no one is using it.
+    public static final String VT_INTERFACE = NetworkStats.IFACE_VT;
 
     /**
      * Settings that can be changed externally.
      */
     public interface NetworkStatsSettings {
         public long getPollInterval();
+        public long getPollDelay();
         public boolean getSampleEnabled();
         public boolean getAugmentEnabled();
 
@@ -246,6 +255,7 @@
     }
 
     private final Object mStatsLock = new Object();
+    private final Object mStatsProviderLock = new Object();
 
     /** Set of currently active ifaces. */
     @GuardedBy("mStatsLock")
@@ -270,6 +280,9 @@
     private final DropBoxNonMonotonicObserver mNonMonotonicObserver =
             new DropBoxNonMonotonicObserver();
 
+    private final RemoteCallbackList<NetworkStatsProviderCallbackImpl> mStatsProviderCbList =
+            new RemoteCallbackList<>();
+
     @GuardedBy("mStatsLock")
     private NetworkStatsRecorder mDevRecorder;
     @GuardedBy("mStatsLock")
@@ -500,9 +513,9 @@
     }
 
     /**
-     * Register for a global alert that is delivered through
-     * {@link INetworkManagementEventObserver} once a threshold amount of data
-     * has been transferred.
+     * Register for a global alert that is delivered through {@link INetworkManagementEventObserver}
+     * or {@link NetworkStatsProviderCallback#onAlertReached()} once a threshold amount of data has
+     * been transferred.
      */
     private void registerGlobalAlert() {
         try {
@@ -512,6 +525,7 @@
         } catch (RemoteException e) {
             // ignored; service lives in system_server
         }
+        invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.setAlert(mGlobalAlertBytes));
     }
 
     @Override
@@ -712,7 +726,7 @@
         final NetworkStatsHistory.Entry entry = history.getValues(start, end, now, null);
 
         final NetworkStats stats = new NetworkStats(end - start, 1);
-        stats.addValues(new NetworkStats.Entry(IFACE_ALL, UID_ALL, SET_ALL, TAG_NONE,
+        stats.addEntry(new NetworkStats.Entry(IFACE_ALL, UID_ALL, SET_ALL, TAG_NONE,
                 METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, entry.rxBytes, entry.rxPackets,
                 entry.txBytes, entry.txPackets, entry.operations));
         return stats;
@@ -801,8 +815,7 @@
     @Override
     public void incrementOperationCount(int uid, int tag, int operationCount) {
         if (Binder.getCallingUid() != uid) {
-            mContext.enforceCallingOrSelfPermission(
-                    android.Manifest.permission.UPDATE_DEVICE_STATS, TAG);
+            mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG);
         }
 
         if (operationCount < 0) {
@@ -1093,7 +1106,7 @@
     /**
      * Observer that watches for {@link INetworkManagementService} alerts.
      */
-    private INetworkManagementEventObserver mAlertObserver = new BaseNetworkObserver() {
+    private final INetworkManagementEventObserver mAlertObserver = new BaseNetworkObserver() {
         @Override
         public void limitReached(String limitName, String iface) {
             // only someone like NMS should be calling us
@@ -1104,7 +1117,7 @@
                 // such a call pending; UID stats are handled during normal polling interval.
                 if (!mHandler.hasMessages(MSG_PERFORM_POLL_REGISTER_ALERT)) {
                     mHandler.sendEmptyMessageDelayed(MSG_PERFORM_POLL_REGISTER_ALERT,
-                            PERFORM_POLL_DELAY_MS);
+                            mSettings.getPollDelay());
                 }
             }
         }
@@ -1179,8 +1192,8 @@
                                 ident.getSubType(), ident.getSubscriberId(), ident.getNetworkId(),
                                 ident.getRoaming(), true /* metered */,
                                 true /* onDefaultNetwork */);
-                        findOrCreateNetworkIdentitySet(mActiveIfaces, VT_INTERFACE).add(vtIdent);
-                        findOrCreateNetworkIdentitySet(mActiveUidIfaces, VT_INTERFACE).add(vtIdent);
+                        findOrCreateNetworkIdentitySet(mActiveIfaces, IFACE_VT).add(vtIdent);
+                        findOrCreateNetworkIdentitySet(mActiveUidIfaces, IFACE_VT).add(vtIdent);
                     }
 
                     if (isMobile) {
@@ -1250,6 +1263,14 @@
         xtSnapshot.combineAllValues(tetherSnapshot);
         devSnapshot.combineAllValues(tetherSnapshot);
 
+        // Snapshot for dev/xt stats from all custom stats providers. Counts per-interface data
+        // from stats providers that isn't already counted by dev and XT stats.
+        Trace.traceBegin(TRACE_TAG_NETWORK, "snapshotStatsProvider");
+        final NetworkStats providersnapshot = getNetworkStatsFromProviders(STATS_PER_IFACE);
+        Trace.traceEnd(TRACE_TAG_NETWORK);
+        xtSnapshot.combineAllValues(providersnapshot);
+        devSnapshot.combineAllValues(providersnapshot);
+
         // For xt/dev, we pass a null VPN array because usage is aggregated by UID, so VPN traffic
         // can't be reattributed to responsible apps.
         Trace.traceBegin(TRACE_TAG_NETWORK, "recordDev");
@@ -1353,6 +1374,10 @@
             performSampleLocked();
         }
 
+        // request asynchronous stats update from all providers for next poll.
+        // TODO: request with a valid token.
+        invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.requestStatsUpdate(0 /* unused */));
+
         // finally, dispatch updated event to any listeners
         final Intent updatedIntent = new Intent(ACTION_NETWORK_STATS_UPDATED);
         updatedIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
@@ -1474,6 +1499,12 @@
         public void forceUpdate() {
             NetworkStatsService.this.forceUpdate();
         }
+
+        @Override
+        public void setStatsProviderLimit(@NonNull String iface, long quota) {
+            Slog.v(TAG, "setStatsProviderLimit(" + iface + "," + quota + ")");
+            invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.setLimit(iface, quota));
+        }
     }
 
     @Override
@@ -1688,6 +1719,12 @@
             uidSnapshot.combineAllValues(vtStats);
         }
 
+        // get a stale copy of uid stats snapshot provided by providers.
+        final NetworkStats providerStats = getNetworkStatsFromProviders(STATS_PER_UID);
+        providerStats.filter(UID_ALL, ifaces, TAG_ALL);
+        mStatsFactory.apply464xlatAdjustments(uidSnapshot, providerStats, mUseBpfTrafficStats);
+        uidSnapshot.combineAllValues(providerStats);
+
         uidSnapshot.combineAllValues(mUidOperations);
 
         return uidSnapshot;
@@ -1724,6 +1761,152 @@
         }
     }
 
+    /**
+     * Registers a custom provider of {@link android.net.NetworkStats} to combine the network
+     * statistics that cannot be seen by the kernel to system. To unregister, invoke the
+     * {@code unregister()} of the returned callback.
+     *
+     * @param tag a human readable identifier of the custom network stats provider.
+     * @param provider the binder interface of
+     *                 {@link android.net.netstats.provider.AbstractNetworkStatsProvider} that
+     *                 needs to be registered to the system.
+     *
+     * @return a binder interface of
+     *         {@link android.net.netstats.provider.NetworkStatsProviderCallback}, which can be
+     *         used to report events to the system.
+     */
+    public @NonNull INetworkStatsProviderCallback registerNetworkStatsProvider(
+            @NonNull String tag, @NonNull INetworkStatsProvider provider) {
+        mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG);
+        Objects.requireNonNull(provider, "provider is null");
+        Objects.requireNonNull(tag, "tag is null");
+        try {
+            NetworkStatsProviderCallbackImpl callback = new NetworkStatsProviderCallbackImpl(
+                            tag, provider, mAlertObserver, mStatsProviderCbList);
+            mStatsProviderCbList.register(callback);
+            Log.d(TAG, "registerNetworkStatsProvider from " + callback.mTag + " uid/pid="
+                    + getCallingUid() + "/" + getCallingPid());
+            return callback;
+        } catch (RemoteException e) {
+            Log.e(TAG, "registerNetworkStatsProvider failed", e);
+        }
+        return null;
+    }
+
+    // Collect stats from local cache of providers.
+    private @NonNull NetworkStats getNetworkStatsFromProviders(int how) {
+        final NetworkStats ret = new NetworkStats(0L, 0);
+        invokeForAllStatsProviderCallbacks((cb) -> ret.combineAllValues(cb.getCachedStats(how)));
+        return ret;
+    }
+
+    @FunctionalInterface
+    private interface ThrowingConsumer<S, T extends Throwable> {
+        void accept(S s) throws T;
+    }
+
+    private void invokeForAllStatsProviderCallbacks(
+            @NonNull ThrowingConsumer<NetworkStatsProviderCallbackImpl, RemoteException> task) {
+        synchronized (mStatsProviderCbList) {
+            final int length = mStatsProviderCbList.beginBroadcast();
+            try {
+                for (int i = 0; i < length; i++) {
+                    final NetworkStatsProviderCallbackImpl cb =
+                            mStatsProviderCbList.getBroadcastItem(i);
+                    try {
+                        task.accept(cb);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Fail to broadcast to provider: " + cb.mTag, e);
+                    }
+                }
+            } finally {
+                mStatsProviderCbList.finishBroadcast();
+            }
+        }
+    }
+
+    private static class NetworkStatsProviderCallbackImpl extends INetworkStatsProviderCallback.Stub
+            implements IBinder.DeathRecipient {
+        @NonNull final String mTag;
+        @NonNull private final Object mProviderStatsLock = new Object();
+        @NonNull final INetworkStatsProvider mProvider;
+        @NonNull final INetworkManagementEventObserver mAlertObserver;
+        @NonNull final RemoteCallbackList<NetworkStatsProviderCallbackImpl> mStatsProviderCbList;
+
+        @GuardedBy("mProviderStatsLock")
+        // STATS_PER_IFACE and STATS_PER_UID
+        private final NetworkStats mIfaceStats = new NetworkStats(0L, 0);
+        @GuardedBy("mProviderStatsLock")
+        private final NetworkStats mUidStats = new NetworkStats(0L, 0);
+
+        NetworkStatsProviderCallbackImpl(
+                @NonNull String tag, @NonNull INetworkStatsProvider provider,
+                @NonNull INetworkManagementEventObserver alertObserver,
+                @NonNull RemoteCallbackList<NetworkStatsProviderCallbackImpl> cbList)
+                throws RemoteException {
+            mTag = tag;
+            mProvider = provider;
+            mProvider.asBinder().linkToDeath(this, 0);
+            mAlertObserver = alertObserver;
+            mStatsProviderCbList = cbList;
+        }
+
+        @NonNull
+        public NetworkStats getCachedStats(int how) {
+            synchronized (mProviderStatsLock) {
+                NetworkStats stats;
+                switch (how) {
+                    case STATS_PER_IFACE:
+                        stats = mIfaceStats;
+                        break;
+                    case STATS_PER_UID:
+                        stats = mUidStats;
+                        break;
+                    default:
+                        throw new IllegalArgumentException("Invalid type: " + how);
+                }
+                // Return a defensive copy instead of local reference.
+                return stats.clone();
+            }
+        }
+
+        @Override
+        public void onStatsUpdated(int token, @Nullable NetworkStats ifaceStats,
+                @Nullable NetworkStats uidStats) {
+            // TODO: 1. Use token to map ifaces to correct NetworkIdentity.
+            //       2. Store the difference and store it directly to the recorder.
+            synchronized (mProviderStatsLock) {
+                if (ifaceStats != null) mIfaceStats.combineAllValues(ifaceStats);
+                if (uidStats != null) mUidStats.combineAllValues(uidStats);
+            }
+        }
+
+        @Override
+        public void onAlertReached() throws RemoteException {
+            mAlertObserver.limitReached(LIMIT_GLOBAL_ALERT, null /* unused */);
+        }
+
+        @Override
+        public void onLimitReached() {
+            Log.d(TAG, mTag + ": onLimitReached");
+            LocalServices.getService(NetworkPolicyManagerInternal.class)
+                    .onStatsProviderLimitReached(mTag);
+        }
+
+        @Override
+        public void binderDied() {
+            Log.d(TAG, mTag + ": binderDied");
+            mStatsProviderCbList.unregister(this);
+        }
+
+        @Override
+        public void unregister() {
+            Log.d(TAG, mTag + ": unregister");
+            mStatsProviderCbList.unregister(this);
+        }
+
+    }
+
     @VisibleForTesting
     static class HandlerCallback implements Handler.Callback {
         private final NetworkStatsService mService;
@@ -1813,6 +1996,10 @@
             return getGlobalLong(NETSTATS_POLL_INTERVAL, 30 * MINUTE_IN_MILLIS);
         }
         @Override
+        public long getPollDelay() {
+            return DEFAULT_PERFORM_POLL_DELAY_MS;
+        }
+        @Override
         public long getGlobalAlertBytes(long def) {
             return getGlobalLong(NETSTATS_GLOBAL_ALERT_BYTES, def);
         }
diff --git a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
index d66fd57..0e14364 100644
--- a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
@@ -15,7 +15,10 @@
 */
 package com.android.server.notification;
 
+import android.app.Notification;
+import android.app.NotificationChannel;
 import android.content.Context;
+import android.util.FeatureFlagUtils;
 import android.util.Slog;
 
 /**
@@ -26,8 +29,10 @@
     private static final boolean DBG = false;
 
     private RankingConfig mConfig;
+    private Context mContext;
 
     public void initialize(Context ctx, NotificationUsageStats usageStats) {
+        mContext = ctx;
         if (DBG) Slog.d(TAG, "Initializing  " + getClass().getSimpleName() + ".");
     }
 
@@ -41,9 +46,11 @@
             if (DBG) Slog.d(TAG, "missing config");
             return null;
         }
-
-        record.updateNotificationChannel(mConfig.getNotificationChannel(record.sbn.getPackageName(),
-                record.sbn.getUid(), record.getChannel().getId(), false));
+        NotificationChannel updatedChannel = mConfig.getConversationNotificationChannel(
+                record.sbn.getPackageName(),
+                record.sbn.getUid(), record.getChannel().getId(),
+                record.sbn.getShortcutId(mContext), true, false);
+        record.updateNotificationChannel(updatedChannel);
 
         return null;
     }
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
index 2247e54..e8cb163 100644
--- a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
+++ b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
@@ -62,7 +62,7 @@
 
     private static final String TAG = "NotiHistoryDatabase";
     private static final boolean DEBUG = NotificationManagerService.DBG;
-    private static final int HISTORY_RETENTION_DAYS = 2;
+    private static final int HISTORY_RETENTION_DAYS = 1;
     private static final int HISTORY_RETENTION_MS = 24 * 60 * 60 * 1000;
     private static final long WRITE_BUFFER_INTERVAL_MS = 1000 * 60 * 20;
 
@@ -172,7 +172,7 @@
 
     public void addNotification(final HistoricalNotification notification) {
         synchronized (mLock) {
-            mBuffer.addNotificationToWrite(notification);
+            mBuffer.addNewNotificationToWrite(notification);
             // Each time we have new history to write to disk, schedule a write in [interval] ms
             if (mBuffer.getHistoryCount() == 1) {
                 mFileWriteHandler.postDelayed(mWriteBufferRunnable, WRITE_BUFFER_INTERVAL_MS);
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryManager.java b/services/core/java/com/android/server/notification/NotificationHistoryManager.java
index 1b56c7b..41bc29f7 100644
--- a/services/core/java/com/android/server/notification/NotificationHistoryManager.java
+++ b/services/core/java/com/android/server/notification/NotificationHistoryManager.java
@@ -68,6 +68,8 @@
     private final SparseArray<List<String>> mUserPendingPackageRemovals = new SparseArray<>();
     @GuardedBy("mLock")
     private final SparseBooleanArray mHistoryEnabled = new SparseBooleanArray();
+    @GuardedBy("mLock")
+    private final SparseBooleanArray mUserPendingHistoryDisables = new SparseBooleanArray();
 
     public NotificationHistoryManager(Context context, Handler handler) {
         mContext = context;
@@ -75,6 +77,11 @@
         mSettingsObserver = new SettingsObserver(handler);
     }
 
+    @VisibleForTesting
+    void onDestroy() {
+        mSettingsObserver.stopObserving();
+    }
+
     void onBootPhaseAppsCanStart() {
         mSettingsObserver.observe();
     }
@@ -99,8 +106,8 @@
             }
 
             // delete history if it was disabled when the user was locked
-            if (!mHistoryEnabled.get(userId)) {
-                userHistory.disableHistory();
+            if (mUserPendingHistoryDisables.get(userId)) {
+                disableHistory(userHistory, userId);
             }
         }
     }
@@ -118,6 +125,7 @@
             // removed) - we just need clean up our internal state for GC
             mUserPendingPackageRemovals.put(userId, null);
             mHistoryEnabled.put(userId, false);
+            mUserPendingHistoryDisables.put(userId, false);
             onUserStopped(userId);
         }
     }
@@ -213,20 +221,29 @@
 
     void onHistoryEnabledChanged(@UserIdInt int userId, boolean historyEnabled) {
         synchronized (mLock) {
-            mHistoryEnabled.put(userId, historyEnabled);
-
-            // These requests might fail if the user is locked; onUserUnlocked will pick up those
-            // cases
+            if (historyEnabled) {
+                mHistoryEnabled.put(userId, historyEnabled);
+            }
             final NotificationHistoryDatabase userHistory =
                     getUserHistoryAndInitializeIfNeededLocked(userId);
             if (userHistory != null) {
                 if (!historyEnabled) {
-                    userHistory.disableHistory();
+                    disableHistory(userHistory, userId);
                 }
+            } else {
+                mUserPendingHistoryDisables.put(userId, !historyEnabled);
             }
         }
     }
 
+    private void disableHistory(NotificationHistoryDatabase userHistory, @UserIdInt int userId) {
+        userHistory.disableHistory();
+
+        mUserPendingHistoryDisables.put(userId, false);
+        mHistoryEnabled.put(userId, false);
+        mUserState.put(userId, null);
+    }
+
     @GuardedBy("mLock")
     private @Nullable NotificationHistoryDatabase getUserHistoryAndInitializeIfNeededLocked(
             int userId) {
@@ -316,6 +333,11 @@
             }
         }
 
+        void stopObserving() {
+            ContentResolver resolver = mContext.getContentResolver();
+            resolver.unregisterContentObserver(this);
+        }
+
         @Override
         public void onChange(boolean selfChange, Uri uri, int userId) {
             update(uri, userId);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index a4f4d07..7cc6732 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -25,6 +25,7 @@
 import static android.app.Notification.FLAG_NO_CLEAR;
 import static android.app.Notification.FLAG_ONGOING_EVENT;
 import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
+import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT;
 import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED;
 import static android.app.NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED;
 import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED;
@@ -50,6 +51,8 @@
 import static android.content.Context.BIND_AUTO_CREATE;
 import static android.content.Context.BIND_FOREGROUND_SERVICE;
 import static android.content.Context.BIND_NOT_PERCEPTIBLE;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
 import static android.content.pm.PackageManager.FEATURE_LEANBACK;
 import static android.content.pm.PackageManager.FEATURE_TELEVISION;
 import static android.content.pm.PackageManager.MATCH_ALL;
@@ -146,10 +149,12 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
+import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
+import android.content.pm.ShortcutInfo;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.database.ContentObserver;
@@ -207,6 +212,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
+import android.util.FeatureFlagUtils;
 import android.util.IntArray;
 import android.util.Log;
 import android.util.Pair;
@@ -236,6 +242,7 @@
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.util.function.TriPredicate;
 import com.android.server.DeviceIdleInternal;
@@ -244,8 +251,8 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.UiThread;
-import com.android.server.lights.Light;
 import com.android.server.lights.LightsManager;
+import com.android.server.lights.LogicalLight;
 import com.android.server.notification.ManagedServices.ManagedServiceInfo;
 import com.android.server.notification.ManagedServices.UserProfiles;
 import com.android.server.pm.PackageManagerService;
@@ -276,6 +283,7 @@
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map.Entry;
@@ -370,8 +378,9 @@
 
     /**
      * Apps that post custom toasts in the background will have those blocked. Apps can
-     * still post toasts created with {@link Toast#makeText(Context, CharSequence, int)} and its
-     * variants while in the background.
+     * still post toasts created with
+     * {@link android.widget.Toast#makeText(Context, CharSequence, int)} and its variants while
+     * in the background.
      *
      * TODO(b/144152069): Add @EnabledAfter(Q) to target R+ after assessing impact on dogfood
      */
@@ -396,6 +405,7 @@
     private RoleObserver mRoleObserver;
     private UserManager mUm;
     private IPlatformCompat mPlatformCompat;
+    private LauncherApps mLauncherAppsService;
 
     final IBinder mForegroundToken = new Binder();
     private WorkerHandler mHandler;
@@ -403,8 +413,8 @@
     private final HandlerThread mRankingThread = new HandlerThread("ranker",
             Process.THREAD_PRIORITY_BACKGROUND);
 
-    private Light mNotificationLight;
-    Light mAttentionLight;
+    private LogicalLight mNotificationLight;
+    LogicalLight mAttentionLight;
 
     private long[] mFallbackVibrationPattern;
     private boolean mUseAttentionLight;
@@ -470,7 +480,8 @@
     private static final String LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_VALUE = "value";
 
     private RankingHelper mRankingHelper;
-    private PreferencesHelper mPreferencesHelper;
+    @VisibleForTesting
+    PreferencesHelper mPreferencesHelper;
 
     private final UserProfiles mUserProfiles = new UserProfiles();
     private NotificationListeners mListeners;
@@ -840,20 +851,22 @@
 
     private static final class ToastRecord
     {
-        final int pid;
-        final String pkg;
-        final ITransientNotification callback;
-        int duration;
-        int displayId;
-        Binder token;
+        public final int pid;
+        public final String pkg;
+        public final IBinder token;
+        public final ITransientNotification callback;
+        public int duration;
+        public int displayId;
+        public Binder windowToken;
 
-        ToastRecord(int pid, String pkg, ITransientNotification callback, int duration,
-                Binder token, int displayId) {
+        ToastRecord(int pid, String pkg, IBinder token, ITransientNotification callback,
+                int duration, Binder windowToken, int displayId) {
             this.pid = pid;
             this.pkg = pkg;
+            this.token = token;
             this.callback = callback;
             this.duration = duration;
-            this.token = token;
+            this.windowToken = windowToken;
             this.displayId = displayId;
         }
 
@@ -872,8 +885,10 @@
             return "ToastRecord{"
                 + Integer.toHexString(System.identityHashCode(this))
                 + " pkg=" + pkg
+                + " token=" + token
                 + " callback=" + callback
-                + " duration=" + duration;
+                + " duration=" + duration
+                + "}";
         }
     }
 
@@ -1008,12 +1023,14 @@
             if (clearEffects) {
                 clearEffects();
             }
+            mAssistants.onPanelRevealed(items);
         }
 
         @Override
         public void onPanelHidden() {
             MetricsLogger.hidden(getContext(), MetricsEvent.NOTIFICATION_PANEL);
             EventLogTags.writeNotificationPanelHidden();
+            mAssistants.onPanelHidden();
         }
 
         @Override
@@ -1060,6 +1077,7 @@
                         reportSeen(r);
                     }
                     r.setVisibility(true, nv.rank, nv.count);
+                    mAssistants.notifyAssistantVisibilityChangedLocked(r.sbn, true);
                     boolean isHun = (nv.location
                             == NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP);
                     // hasBeenVisiblyExpanded must be called after updating the expansion state of
@@ -1078,6 +1096,7 @@
                     NotificationRecord r = mNotificationsByKey.get(nv.key);
                     if (r == null) continue;
                     r.setVisibility(false, nv.rank, nv.count);
+                    mAssistants.notifyAssistantVisibilityChangedLocked(r.sbn, false);
                     nv.recycle();
                 }
             }
@@ -1187,12 +1206,7 @@
                     final StatusBarNotification n = r.sbn;
                     final int callingUid = n.getUid();
                     final String pkg = n.getPackageName();
-                    if (isBubble && isNotificationAppropriateToBubble(r, pkg, callingUid,
-                            null /* oldEntry */)) {
-                        r.getNotification().flags |= FLAG_BUBBLE;
-                    } else {
-                        r.getNotification().flags &= ~FLAG_BUBBLE;
-                    }
+                    applyFlagBubble(r, pkg, callingUid, null /* oldEntry */, isBubble);
                 }
             }
         }
@@ -1567,6 +1581,80 @@
         }
     };
 
+    // Key: packageName Value: <shortcutId, notifId>
+    private HashMap<String, HashMap<String, String>> mActiveShortcutBubbles = new HashMap<>();
+
+    private boolean mLauncherAppsCallbackRegistered;
+
+    // Bubbles can be created based on a shortcut, we need to listen for changes to
+    // that shortcut so that we may update the bubble appropriately.
+    private final LauncherApps.Callback mLauncherAppsCallback = new LauncherApps.Callback() {
+        @Override
+        public void onPackageRemoved(String packageName, UserHandle user) {
+        }
+
+        @Override
+        public void onPackageAdded(String packageName, UserHandle user) {
+        }
+
+        @Override
+        public void onPackageChanged(String packageName, UserHandle user) {
+        }
+
+        @Override
+        public void onPackagesAvailable(String[] packageNames, UserHandle user,
+                boolean replacing) {
+        }
+
+        @Override
+        public void onPackagesUnavailable(String[] packageNames, UserHandle user,
+                boolean replacing) {
+        }
+
+        @Override
+        public void onShortcutsChanged(@NonNull String packageName,
+                @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
+            HashMap<String, String> shortcutBubbles = mActiveShortcutBubbles.get(packageName);
+            boolean isAppForeground = packageName != null
+                    && mActivityManager.getPackageImportance(packageName) == IMPORTANCE_FOREGROUND;
+            ArrayList<String> bubbleKeysToRemove = new ArrayList<>();
+            if (shortcutBubbles != null) {
+                // If we can't find one of our bubbles in the shortcut list, that bubble needs
+                // to be removed.
+                for (String shortcutId : shortcutBubbles.keySet()) {
+                    boolean foundShortcut = false;
+                    for (int i = 0; i < shortcuts.size(); i++) {
+                        if (shortcuts.get(i).getId().equals(shortcutId)) {
+                            foundShortcut = true;
+                            break;
+                        }
+                    }
+                    if (!foundShortcut) {
+                        bubbleKeysToRemove.add(shortcutBubbles.get(shortcutId));
+                    }
+                }
+            }
+
+            // Do the removals
+            for (int i = 0; i < bubbleKeysToRemove.size(); i++) {
+                // update flag bubble
+                String bubbleKey = bubbleKeysToRemove.get(i);
+                synchronized (mNotificationLock) {
+                    NotificationRecord r = mNotificationsByKey.get(bubbleKey);
+                    if (r != null) {
+                        final StatusBarNotification n = r.sbn;
+                        final int callingUid = n.getUid();
+                        final String pkg = n.getPackageName();
+                        applyFlagBubble(r, pkg, callingUid, null /* oldEntry */, isAppForeground);
+                        mHandler.post(new EnqueueNotificationRunnable(user.getIdentifier(), r,
+                                false /* isAppForeground */));
+                    }
+                }
+            }
+        }
+    };
+
+
     private final class SettingsObserver extends ContentObserver {
         private final Uri NOTIFICATION_BADGING_URI
                 = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BADGING);
@@ -1650,6 +1738,11 @@
     }
 
     @VisibleForTesting
+    void setLauncherApps(LauncherApps launcherApps) {
+        mLauncherAppsService = launcherApps;
+    }
+
+    @VisibleForTesting
     void setHints(int hints) {
         mListenerHints = hints;
     }
@@ -1660,7 +1753,7 @@
     }
 
     @VisibleForTesting
-    void setLights(Light light) {
+    void setLights(LogicalLight light) {
         mNotificationLight = light;
         mAttentionLight = light;
         mNotificationPulseEnabled = true;
@@ -2143,6 +2236,8 @@
             mRoleObserver = new RoleObserver(getContext().getSystemService(RoleManager.class),
                     mPackageManager, getContext().getMainExecutor());
             mRoleObserver.init();
+            mLauncherAppsService =
+                    (LauncherApps) getContext().getSystemService(Context.LAUNCHER_APPS_SERVICE);
         } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
             // This observer will force an update when observe is called, causing us to
             // bind to listener services.
@@ -2219,8 +2314,8 @@
         maybeNotifyChannelOwner(pkg, uid, preUpdate, channel);
 
         if (!fromListener) {
-            final NotificationChannel modifiedChannel =
-                    mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
+            final NotificationChannel modifiedChannel = mPreferencesHelper.getNotificationChannel(
+                    pkg, uid, channel.getId(), false);
             mListeners.notifyNotificationChannelChanged(
                     pkg, UserHandle.getUserHandleForUid(uid),
                     modifiedChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
@@ -2481,7 +2576,7 @@
                     .setUid(r.sbn.getUid())
                     .setChannelId(r.getChannel().getId())
                     .setChannelName(r.getChannel().getName().toString())
-                    .setPostedTimeMs(r.sbn.getPostTime())
+                    .setPostedTimeMs(System.currentTimeMillis())
                     .setTitle(getHistoryTitle(r.getNotification()))
                     .setText(getHistoryText(
                             r.sbn.getPackageContext(getContext()), r.getNotification()))
@@ -2551,26 +2646,27 @@
         // ============================================================================
 
         @Override
-        public void enqueueTextToast(String pkg, ITransientNotification callback, int duration,
-                int displayId) {
-            enqueueToast(pkg, callback, duration, displayId, false);
+        public void enqueueTextToast(String pkg, IBinder token, ITransientNotification callback,
+                int duration, int displayId) {
+            enqueueToast(pkg, token, callback, duration, displayId, false);
         }
 
         @Override
-        public void enqueueToast(String pkg, ITransientNotification callback, int duration,
-                int displayId) {
-            enqueueToast(pkg, callback, duration, displayId, true);
+        public void enqueueToast(String pkg, IBinder token, ITransientNotification callback,
+                int duration, int displayId) {
+            enqueueToast(pkg, token, callback, duration, displayId, true);
         }
 
-        private void enqueueToast(String pkg, ITransientNotification callback, int duration,
-                int displayId, boolean isCustomToast) {
+        private void enqueueToast(String pkg, IBinder token, ITransientNotification callback,
+                int duration, int displayId, boolean isCustomToast) {
             if (DBG) {
                 Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback
                         + " duration=" + duration + " displayId=" + displayId);
             }
 
-            if (pkg == null || callback == null) {
-                Slog.e(TAG, "Not enqueuing toast. pkg=" + pkg + " callback=" + callback);
+            if (pkg == null || callback == null || token == null) {
+                Slog.e(TAG, "Not enqueuing toast. pkg=" + pkg + " callback=" + callback + " token="
+                        + token);
                 return ;
             }
 
@@ -2628,7 +2724,7 @@
                 long callingId = Binder.clearCallingIdentity();
                 try {
                     ToastRecord record;
-                    int index = indexOfToastLocked(pkg, callback);
+                    int index = indexOfToastLocked(pkg, token);
                     // If it's already in the queue, we update it in place, we don't
                     // move it to the end of the queue.
                     if (index >= 0) {
@@ -2653,10 +2749,10 @@
                             }
                         }
 
-                        Binder token = new Binder();
-                        mWindowManagerInternal.addWindowToken(token, TYPE_TOAST, displayId);
-                        record = new ToastRecord(callingPid, pkg, callback, duration, token,
-                                displayId);
+                        Binder windowToken = new Binder();
+                        mWindowManagerInternal.addWindowToken(windowToken, TYPE_TOAST, displayId);
+                        record = new ToastRecord(callingPid, pkg, token, callback, duration,
+                                windowToken, displayId);
                         mToastQueue.add(record);
                         index = mToastQueue.size() - 1;
                         keepProcessAliveIfNeededLocked(callingPid);
@@ -2675,23 +2771,23 @@
         }
 
         @Override
-        public void cancelToast(String pkg, ITransientNotification callback) {
-            Slog.i(TAG, "cancelToast pkg=" + pkg + " callback=" + callback);
+        public void cancelToast(String pkg, IBinder token) {
+            Slog.i(TAG, "cancelToast pkg=" + pkg + " token=" + token);
 
-            if (pkg == null || callback == null) {
-                Slog.e(TAG, "Not cancelling notification. pkg=" + pkg + " callback=" + callback);
+            if (pkg == null || token == null) {
+                Slog.e(TAG, "Not cancelling notification. pkg=" + pkg + " token=" + token);
                 return ;
             }
 
             synchronized (mToastQueue) {
                 long callingId = Binder.clearCallingIdentity();
                 try {
-                    int index = indexOfToastLocked(pkg, callback);
+                    int index = indexOfToastLocked(pkg, token);
                     if (index >= 0) {
                         cancelToastLocked(index);
                     } else {
                         Slog.w(TAG, "Toast already cancelled. pkg=" + pkg
-                                + " callback=" + callback);
+                                + " token=" + token);
                     }
                 } finally {
                     Binder.restoreCallingIdentity(callingId);
@@ -2700,17 +2796,17 @@
         }
 
         @Override
-        public void finishToken(String pkg, ITransientNotification callback) {
+        public void finishToken(String pkg, IBinder token) {
             synchronized (mToastQueue) {
                 long callingId = Binder.clearCallingIdentity();
                 try {
-                    int index = indexOfToastLocked(pkg, callback);
+                    int index = indexOfToastLocked(pkg, token);
                     if (index >= 0) {
                         ToastRecord record = mToastQueue.get(index);
-                        finishTokenLocked(record.token, record.displayId);
+                        finishWindowTokenLocked(record.windowToken, record.displayId);
                     } else {
                         Slog.w(TAG, "Toast already killed. pkg=" + pkg
-                                + " callback=" + callback);
+                                + " token=" + token);
                     }
                 } finally {
                     Binder.restoreCallingIdentity(callingId);
@@ -3017,31 +3113,57 @@
 
         @Override
         public void createNotificationChannels(String pkg,
-                ParceledListSlice channelsList) throws RemoteException {
+                ParceledListSlice channelsList) {
             checkCallerIsSystemOrSameApp(pkg);
             createNotificationChannelsImpl(pkg, Binder.getCallingUid(), channelsList);
         }
 
         @Override
         public void createNotificationChannelsForPackage(String pkg, int uid,
-                ParceledListSlice channelsList) throws RemoteException {
-            checkCallerIsSystem();
+                ParceledListSlice channelsList) {
+            enforceSystemOrSystemUI("only system can call this");
             createNotificationChannelsImpl(pkg, uid, channelsList);
         }
 
         @Override
+        public void createConversationNotificationChannelForPackage(String pkg, int uid,
+                String triggeringKey, NotificationChannel parentChannel, String conversationId) {
+            enforceSystemOrSystemUI("only system can call this");
+            Preconditions.checkNotNull(parentChannel);
+            Preconditions.checkNotNull(conversationId);
+            String parentId = parentChannel.getId();
+            NotificationChannel conversationChannel = parentChannel;
+            conversationChannel.setId(String.format(
+                    CONVERSATION_CHANNEL_ID_FORMAT, parentId, conversationId));
+            conversationChannel.setConversationId(parentId, conversationId);
+            createNotificationChannelsImpl(
+                    pkg, uid, new ParceledListSlice(Arrays.asList(conversationChannel)));
+            mRankingHandler.requestSort();
+            handleSavePolicyFile();
+        }
+
+        @Override
         public NotificationChannel getNotificationChannel(String callingPkg, int userId,
                 String targetPkg, String channelId) {
+            return getConversationNotificationChannel(
+                    callingPkg, userId, targetPkg, channelId, true, null);
+        }
+
+        @Override
+        public NotificationChannel getConversationNotificationChannel(String callingPkg, int userId,
+                String targetPkg, String channelId, boolean returnParentIfNoConversationChannel,
+                String conversationId) {
             if (canNotifyAsPackage(callingPkg, targetPkg, userId)
-                    || isCallingUidSystem()) {
+                    || isCallerIsSystemOrSystemUi()) {
                 int targetUid = -1;
                 try {
                     targetUid = mPackageManagerClient.getPackageUidAsUser(targetPkg, userId);
                 } catch (NameNotFoundException e) {
                     /* ignore */
                 }
-                return mPreferencesHelper.getNotificationChannel(
-                        targetPkg, targetUid, channelId, false /* includeDeleted */);
+                return mPreferencesHelper.getConversationNotificationChannel(
+                        targetPkg, targetUid, channelId, conversationId,
+                        returnParentIfNoConversationChannel, false /* includeDeleted */);
             }
             throw new SecurityException("Pkg " + callingPkg
                     + " cannot read channels for " + targetPkg + " in " + userId);
@@ -3072,6 +3194,30 @@
         }
 
         @Override
+        public void deleteConversationNotificationChannels(String pkg, int uid,
+                String conversationId) {
+            checkCallerIsSystem();
+            final int callingUid = Binder.getCallingUid();
+            List<NotificationChannel> channels =
+                    mPreferencesHelper.getNotificationChannelsByConversationId(
+                            pkg, uid, conversationId);
+            if (!channels.isEmpty()) {
+                for (NotificationChannel nc : channels) {
+                    cancelAllNotificationsInt(MY_UID, MY_PID, pkg, nc.getId(), 0, 0, true,
+                            UserHandle.getUserId(callingUid), REASON_CHANNEL_BANNED, null);
+                    mPreferencesHelper.deleteNotificationChannel(pkg, callingUid, nc.getId());
+                    mListeners.notifyNotificationChannelChanged(pkg,
+                            UserHandle.getUserHandleForUid(callingUid),
+                            mPreferencesHelper.getNotificationChannel(
+                                    pkg, callingUid, nc.getId(), true),
+                            NOTIFICATION_CHANNEL_OR_GROUP_DELETED);
+                }
+                handleSavePolicyFile();
+            }
+        }
+
+
+        @Override
         public NotificationChannelGroup getNotificationChannelGroup(String pkg, String groupId) {
             checkCallerIsSystemOrSameApp(pkg);
             return mPreferencesHelper.getNotificationChannelGroupWithChannels(
@@ -5202,8 +5348,16 @@
         if (mIsTelevision && (new Notification.TvExtender(notification)).getChannelId() != null) {
             channelId = (new Notification.TvExtender(notification)).getChannelId();
         }
-        final NotificationChannel channel = mPreferencesHelper.getNotificationChannel(pkg,
-                notificationUid, channelId, false /* includeDeleted */);
+        String shortcutId = notification.getShortcutId();
+        if (FeatureFlagUtils.isEnabled(getContext(),
+                FeatureFlagUtils.NOTIF_CONVO_BYPASS_SHORTCUT_REQ)
+            && shortcutId == null
+            && notification.getNotificationStyle() == Notification.MessagingStyle.class) {
+            shortcutId = id + tag + NotificationChannel.PLACEHOLDER_CONVERSATION_ID;
+        }
+        final NotificationChannel channel = mPreferencesHelper.getConversationNotificationChannel(
+                pkg, notificationUid, channelId, shortcutId,
+                true /* parent ok */, false /* includeDeleted */);
         if (channel == null) {
             final String noChannelStr = "No Channel found for "
                     + "pkg=" + pkg
@@ -5373,19 +5527,18 @@
     }
 
     /**
-     * Updates the flags for this notification to reflect whether it is a bubble or not.
+     * Updates the flags for this notification to reflect whether it is a bubble or not. Some
+     * bubble specific flags only work if the app is foreground, this will strip those flags
+     * if the app wasn't foreground.
      */
-    private void flagNotificationForBubbles(NotificationRecord r, String pkg, int userId,
+    private void updateNotificationBubbleFlags(NotificationRecord r, String pkg, int userId,
             NotificationRecord oldRecord, boolean isAppForeground) {
         Notification notification = r.getNotification();
-        if (isNotificationAppropriateToBubble(r, pkg, userId, oldRecord)) {
-            notification.flags |= FLAG_BUBBLE;
-        } else {
-            notification.flags &= ~FLAG_BUBBLE;
-        }
+        applyFlagBubble(r, pkg, userId, oldRecord, true /* desiredFlag */);
+
+        // Remove any bubble specific flags that only work when foregrounded
         Notification.BubbleMetadata metadata = notification.getBubbleMetadata();
         if (!isAppForeground && metadata != null) {
-            // Remove any flags that only work when foregrounded
             int flags = metadata.getFlags();
             flags &= ~Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE;
             flags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
@@ -5394,6 +5547,54 @@
     }
 
     /**
+     * Handles actually applying or removing {@link Notification#FLAG_BUBBLE}. Performs necessary
+     * checks for the provided record to see if it can actually be a bubble.
+     * Tracks shortcut based bubbles so that we can find out if they've changed or been removed.
+     */
+    private void applyFlagBubble(NotificationRecord r, String pkg, int userId,
+            NotificationRecord oldRecord, boolean desiredFlag) {
+        boolean applyFlag = desiredFlag
+                && isNotificationAppropriateToBubble(r, pkg, userId, oldRecord);
+        final String shortcutId = r.getNotification().getBubbleMetadata() != null
+                ? r.getNotification().getBubbleMetadata().getShortcutId()
+                : null;
+        if (applyFlag) {
+            if (shortcutId != null) {
+                // Must track shortcut based bubbles in case the shortcut is removed
+                HashMap<String, String> packageBubbles = mActiveShortcutBubbles.get(
+                        r.sbn.getPackageName());
+                if (packageBubbles == null) {
+                    packageBubbles = new HashMap<>();
+                }
+                packageBubbles.put(shortcutId, r.getKey());
+                mActiveShortcutBubbles.put(r.sbn.getPackageName(), packageBubbles);
+                if (!mLauncherAppsCallbackRegistered) {
+                    mLauncherAppsService.registerCallback(mLauncherAppsCallback, mHandler);
+                    mLauncherAppsCallbackRegistered = true;
+                }
+            }
+            r.getNotification().flags |= FLAG_BUBBLE;
+        } else {
+            if (shortcutId != null) {
+                // No longer track shortcut
+                HashMap<String, String> packageBubbles = mActiveShortcutBubbles.get(
+                        r.sbn.getPackageName());
+                if (packageBubbles != null) {
+                    packageBubbles.remove(shortcutId);
+                }
+                if (packageBubbles != null && packageBubbles.isEmpty()) {
+                    mActiveShortcutBubbles.remove(r.sbn.getPackageName());
+                }
+                if (mLauncherAppsCallbackRegistered && mActiveShortcutBubbles.isEmpty()) {
+                    mLauncherAppsService.unregisterCallback(mLauncherAppsCallback);
+                    mLauncherAppsCallbackRegistered = false;
+                }
+            }
+            r.getNotification().flags &= ~FLAG_BUBBLE;
+        }
+    }
+
+    /**
      * @return whether the provided notification record is allowed to be represented as a bubble,
      * accounting for user choice & policy.
      */
@@ -5467,10 +5668,6 @@
             // no log: no need to inform dev if they didn't attach bubble metadata
             return false;
         }
-        if (!canLaunchInActivityView(getContext(), metadata.getIntent(), pkg)) {
-            // no log: method has the failure log
-            return false;
-        }
         if (!mPreferencesHelper.bubblesEnabled()) {
             logBubbleError(r.getKey(), "bubbles disabled for user: " + userId);
             return false;
@@ -5485,7 +5682,20 @@
                     "bubbles for channel " + r.getChannel().getId() + " disabled");
             return false;
         }
-        return true;
+
+        String shortcutId = metadata.getShortcutId();
+        boolean shortcutValid = shortcutId != null
+                && hasValidShortcutInfo(shortcutId, pkg, r.getUser());
+        if (metadata.getBubbleIntent() == null && !shortcutValid) {
+            // Should have a shortcut if intent is null
+            logBubbleError(r.getKey(), "couldn't find shortcutId for bubble: " + shortcutId);
+            return false;
+        }
+        if (shortcutValid) {
+            return true;
+        }
+        // no log: canLaunch method has the failure log
+        return canLaunchInActivityView(getContext(), metadata.getBubbleIntent(), pkg);
     }
 
     private boolean hasValidRemoteInput(Notification n) {
@@ -5504,6 +5714,22 @@
         return false;
     }
 
+    private boolean hasValidShortcutInfo(String shortcutId, String packageName, UserHandle user) {
+        LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery();
+        if (packageName != null) {
+            query.setPackage(packageName);
+        }
+        if (shortcutId != null) {
+            query.setShortcutIds(Arrays.asList(shortcutId));
+        }
+        query.setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED);
+        List<ShortcutInfo> shortcuts = mLauncherAppsService.getShortcuts(query, user);
+        ShortcutInfo shortcutInfo = shortcuts != null && shortcuts.size() > 0
+                ? shortcuts.get(0)
+                : null;
+        return shortcutInfo != null;
+    }
+
     private void logBubbleError(String key, String failureMessage) {
         if (DBG) {
             Log.w(TAG, "Bubble notification: " + key + " failed: " + failureMessage);
@@ -5959,7 +6185,7 @@
                 final String tag = n.getTag();
 
                 // We need to fix the notification up a little for bubbles
-                flagNotificationForBubbles(r, pkg, callingUid, old, isAppForeground);
+                updateNotificationBubbleFlags(r, pkg, callingUid, old, isAppForeground);
 
                 // Handle grouped notifications and bail out early if we
                 // can to avoid extracting signals.
@@ -6691,7 +6917,7 @@
         while (record != null) {
             if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
             try {
-                record.callback.show(record.token);
+                record.callback.show(record.windowToken);
                 scheduleDurationReachedLocked(record);
                 return;
             } catch (RemoteException e) {
@@ -6726,7 +6952,7 @@
 
         ToastRecord lastToast = mToastQueue.remove(index);
 
-        mWindowManagerInternal.removeWindowToken(lastToast.token, false /* removeWindows */,
+        mWindowManagerInternal.removeWindowToken(lastToast.windowToken, false /* removeWindows */,
                 lastToast.displayId);
         // We passed 'false' for 'removeWindows' so that the client has time to stop
         // rendering (as hide above is a one-way message), otherwise we could crash
@@ -6744,7 +6970,7 @@
         }
     }
 
-    void finishTokenLocked(IBinder t, int displayId) {
+    void finishWindowTokenLocked(IBinder t, int displayId) {
         mHandler.removeCallbacksAndMessages(t);
         // We pass 'true' for 'removeWindows' to let the WindowManager destroy any
         // remaining surfaces as either the client has called finishToken indicating
@@ -6769,9 +6995,9 @@
 
     private void handleDurationReached(ToastRecord record)
     {
-        if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);
+        if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " token=" + record.token);
         synchronized (mToastQueue) {
-            int index = indexOfToastLocked(record.pkg, record.callback);
+            int index = indexOfToastLocked(record.pkg, record.token);
             if (index >= 0) {
                 cancelToastLocked(index);
             }
@@ -6788,21 +7014,19 @@
 
     private void handleKillTokenTimeout(ToastRecord record)
     {
-        if (DBG) Slog.d(TAG, "Kill Token Timeout token=" + record.token);
+        if (DBG) Slog.d(TAG, "Kill Token Timeout token=" + record.windowToken);
         synchronized (mToastQueue) {
-            finishTokenLocked(record.token, record.displayId);
+            finishWindowTokenLocked(record.windowToken, record.displayId);
         }
     }
 
     @GuardedBy("mToastQueue")
-    int indexOfToastLocked(String pkg, ITransientNotification callback)
-    {
-        IBinder cbak = callback.asBinder();
+    int indexOfToastLocked(String pkg, IBinder token) {
         ArrayList<ToastRecord> list = mToastQueue;
         int len = list.size();
         for (int i=0; i<len; i++) {
             ToastRecord r = list.get(i);
-            if (r.pkg.equals(pkg) && r.callback.asBinder() == cbak) {
+            if (r.pkg.equals(pkg) && r.token == token) {
                 return i;
             }
         }
@@ -7651,7 +7875,7 @@
             NotificationRecord.Light light = ledNotification.getLight();
             if (light != null && mNotificationPulseEnabled) {
                 // pulse repeatedly
-                mNotificationLight.setFlashing(light.color, Light.LIGHT_FLASH_TIMED,
+                mNotificationLight.setFlashing(light.color, LogicalLight.LIGHT_FLASH_TIMED,
                         light.onMs, light.offMs);
             }
         }
@@ -7839,6 +8063,14 @@
         return isUidSystemOrPhone(Binder.getCallingUid());
     }
 
+    private boolean isCallerIsSystemOrSystemUi() {
+        if (isCallerSystemOrPhone()) {
+            return true;
+        }
+        return getContext().checkCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
+                == PERMISSION_GRANTED;
+    }
+
     private void checkCallerIsSystemOrShell() {
         int callingUid = Binder.getCallingUid();
         if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) {
@@ -8024,7 +8256,8 @@
                     record.getSystemGeneratedSmartActions(),
                     record.getSmartReplies(),
                     record.canBubble(),
-                    record.isInterruptive()
+                    record.isInterruptive(),
+                    record.isConversation()
             );
             rankings.add(ranking);
         }
@@ -8296,6 +8529,32 @@
             }
         }
 
+        protected void onPanelRevealed(int items) {
+            for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
+                mHandler.post(() -> {
+                    final INotificationListener assistant = (INotificationListener) info.service;
+                    try {
+                        assistant.onPanelRevealed(items);
+                    } catch (RemoteException ex) {
+                        Slog.e(TAG, "unable to notify assistant (panel revealed): " + info, ex);
+                    }
+                });
+            }
+        }
+
+        protected void onPanelHidden() {
+            for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
+                mHandler.post(() -> {
+                    final INotificationListener assistant = (INotificationListener) info.service;
+                    try {
+                        assistant.onPanelHidden();
+                    } catch (RemoteException ex) {
+                        Slog.e(TAG, "unable to notify assistant (panel hidden): " + info, ex);
+                    }
+                });
+            }
+        }
+
         boolean hasUserSet(int userId) {
             synchronized (mLock) {
                 return mUserSetMap.getOrDefault(userId, false);
@@ -8363,6 +8622,24 @@
         }
 
         @GuardedBy("mNotificationLock")
+        void notifyAssistantVisibilityChangedLocked(
+                final StatusBarNotification sbn,
+                final boolean isVisible) {
+            final String key = sbn.getKey();
+            Slog.d(TAG, "notifyAssistantVisibilityChangedLocked: " + key);
+            notifyAssistantLocked(
+                    sbn,
+                    false /* sameUserOnly */,
+                    (assistant, sbnHolder) -> {
+                        try {
+                            assistant.onNotificationVisibilityChanged(key, isVisible);
+                        } catch (RemoteException ex) {
+                            Slog.e(TAG, "unable to notify assistant (visible): " + assistant, ex);
+                        }
+                    });
+        }
+
+        @GuardedBy("mNotificationLock")
         void notifyAssistantExpansionChangedLocked(
                 final StatusBarNotification sbn,
                 final boolean isUserAction,
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index c8afcc9..2bea218 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -54,6 +54,7 @@
 import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
 import android.util.ArraySet;
+import android.util.FeatureFlagUtils;
 import android.util.Log;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
@@ -164,6 +165,7 @@
     private boolean mShowBadge;
     private boolean mAllowBubble;
     private Light mLight;
+    private boolean mIsNotConversationOverride;
     /**
      * This list contains system generated smart actions from NAS, app-generated smart actions are
      * stored in Notification.actions with isContextual() set to true.
@@ -466,6 +468,7 @@
         pw.println(prefix + "deleteIntent=" + notification.deleteIntent);
         pw.println(prefix + "number=" + notification.number);
         pw.println(prefix + "groupAlertBehavior=" + notification.getGroupAlertBehavior());
+        pw.println(prefix + "when=" + notification.when);
 
         pw.print(prefix + "tickerText=");
         if (!TextUtils.isEmpty(notification.tickerText)) {
@@ -659,6 +662,10 @@
                 if (signals.containsKey(Adjustment.KEY_RANKING_SCORE)) {
                     mRankingScore = signals.getFloat(Adjustment.KEY_RANKING_SCORE);
                 }
+                if (signals.containsKey(Adjustment.KEY_NOT_CONVERSATION)) {
+                    mIsNotConversationOverride = signals.getBoolean(
+                            Adjustment.KEY_NOT_CONVERSATION);
+                }
                 if (!signals.isEmpty() && adjustment.getIssuer() != null) {
                     mAdjustmentIssuer = adjustment.getIssuer();
                 }
@@ -1309,6 +1316,25 @@
         return hasCustomRemoteView && !hasDecoratedStyle;
     }
 
+    /** Whether this notification is a conversation notification. */
+    public boolean isConversation() {
+        Notification notification = getNotification();
+        if (mChannel.isDemoted()
+                || !Notification.MessagingStyle.class.equals(notification.getNotificationStyle())) {
+            return false;
+        }
+        if (notification.getShortcutId() == null
+                && !FeatureFlagUtils.isEnabled(
+                        mContext, FeatureFlagUtils.NOTIF_CONVO_BYPASS_SHORTCUT_REQ)) {
+            return false;
+        }
+        if (mIsNotConversationOverride) {
+            return false;
+        }
+        // STOPSHIP b/137397357: Check shortcut to make a further decision
+        return true;
+    }
+
     @VisibleForTesting
     static final class Light {
         public final int color;
diff --git a/services/core/java/com/android/server/notification/OWNERS b/services/core/java/com/android/server/notification/OWNERS
new file mode 100644
index 0000000..5a19656
--- /dev/null
+++ b/services/core/java/com/android/server/notification/OWNERS
@@ -0,0 +1,4 @@
+dsandler@android.com
+juliacr@google.com
+beverlyt@google.com
+pixel@google.com
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index cdb0a17..b0c1863 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -16,6 +16,7 @@
 
 package com.android.server.notification;
 
+import static android.app.NotificationChannel.PLACEHOLDER_CONVERSATION_ID;
 import static android.app.NotificationManager.IMPORTANCE_NONE;
 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
 
@@ -40,6 +41,7 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.FeatureFlagUtils;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
@@ -139,6 +141,8 @@
     private boolean mAreChannelsBypassingDnd;
     private boolean mHideSilentStatusBarIcons = DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS;
 
+    private boolean mAllowInvalidShortcuts = false;
+
     private static final String BADGING_FORCED_TRUE = "force_badging_true_for_bug";
 
     // STOPSHIP (b/142218092) this should be removed before ship
@@ -164,6 +168,8 @@
         updateBadgingEnabled();
         updateBubblesEnabled();
         syncChannelsBypassingDnd(mContext.getUserId());
+        mAllowInvalidShortcuts = FeatureFlagUtils.isEnabled(mContext,
+                FeatureFlagUtils.NOTIF_CONVO_BYPASS_SHORTCUT_REQ);
     }
 
     public void readXml(XmlPullParser parser, boolean forRestore, int userId)
@@ -266,7 +272,14 @@
                                         }
                                         channel.setImportanceLockedByCriticalDeviceFunction(
                                                 r.defaultAppLockedImportance);
-                                        r.channels.put(id, channel);
+                                        boolean isInvalidShortcutChannel =
+                                                channel.getConversationId() != null &&
+                                                        channel.getConversationId().contains(
+                                                                PLACEHOLDER_CONVERSATION_ID);
+                                        if (mAllowInvalidShortcuts || (!mAllowInvalidShortcuts
+                                                && !isInvalidShortcutChannel)) {
+                                            r.channels.put(id, channel);
+                                        }
                                     }
                                 }
                                 // Delegate
@@ -770,6 +783,13 @@
                 channel.setShowBadge(false);
             }
             channel.setOriginalImportance(channel.getImportance());
+
+            // validate parent
+            if (channel.getParentChannelId() != null) {
+                Preconditions.checkArgument(r.channels.containsKey(channel.getParentChannelId()),
+                        "Tried to create a conversation channel without a preexisting parent");
+            }
+
             r.channels.put(channel.getId(), channel);
             if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
                 updateChannelsBypassingDnd(mContext.getUserId());
@@ -851,6 +871,14 @@
     public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
             boolean includeDeleted) {
         Objects.requireNonNull(pkg);
+        return getConversationNotificationChannel(pkg, uid, channelId, null, true, includeDeleted);
+    }
+
+    @Override
+    public NotificationChannel getConversationNotificationChannel(String pkg, int uid,
+            String channelId, String conversationId, boolean returnParentIfNoConversationChannel,
+            boolean includeDeleted) {
+        Preconditions.checkNotNull(pkg);
         synchronized (mPackagePreferences) {
             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
             if (r == null) {
@@ -859,11 +887,51 @@
             if (channelId == null) {
                 channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
             }
-            final NotificationChannel nc = r.channels.get(channelId);
-            if (nc != null && (includeDeleted || !nc.isDeleted())) {
+            NotificationChannel channel = null;
+            if (conversationId != null) {
+                // look for an automatically created conversation specific channel
+                channel = findConversationChannel(r, channelId, conversationId, includeDeleted);
+            }
+            if (channel == null && returnParentIfNoConversationChannel) {
+                // look for it just based on its id
+                final NotificationChannel nc = r.channels.get(channelId);
+                if (nc != null && (includeDeleted || !nc.isDeleted())) {
+                    return nc;
+                }
+            }
+            return channel;
+        }
+    }
+
+    private NotificationChannel findConversationChannel(PackagePreferences p, String parentId,
+            String conversationId, boolean includeDeleted) {
+        for (NotificationChannel nc : p.channels.values()) {
+            if (conversationId.equals(nc.getConversationId())
+                    && parentId.equals(nc.getParentChannelId())
+                    && (includeDeleted || !nc.isDeleted())) {
                 return nc;
             }
-            return null;
+        }
+        return null;
+    }
+
+    public List<NotificationChannel> getNotificationChannelsByConversationId(String pkg, int uid,
+            String conversationId) {
+        Preconditions.checkNotNull(pkg);
+        Preconditions.checkNotNull(conversationId);
+        List<NotificationChannel> channels = new ArrayList<>();
+        synchronized (mPackagePreferences) {
+            PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
+            if (r == null) {
+                return channels;
+            }
+            for (NotificationChannel nc : r.channels.values()) {
+                if (conversationId.equals(nc.getConversationId())
+                        && !nc.isDeleted()) {
+                    channels.add(nc);
+                }
+            }
+            return channels;
         }
     }
 
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index 7816f36..7e98be7 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -41,10 +41,16 @@
             int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty);
     boolean createNotificationChannel(String pkg, int uid, NotificationChannel channel,
             boolean fromTargetApp, boolean hasDndAccess);
-    void updateNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromUser);
-    NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted);
+    void updateNotificationChannel(String pkg, int uid, NotificationChannel channel,
+            boolean fromUser);
+    NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
+            boolean includeDeleted);
+    NotificationChannel getConversationNotificationChannel(String pkg, int uid, String channelId,
+            String conversationId, boolean returnParentIfNoConversationChannel,
+            boolean includeDeleted);
     void deleteNotificationChannel(String pkg, int uid, String channelId);
     void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId);
     void permanentlyDeleteNotificationChannels(String pkg, int uid);
-    ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid, boolean includeDeleted);
+    ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
+            boolean includeDeleted);
 }
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index b782ca96..3c31f6a 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -83,6 +83,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -925,7 +926,7 @@
     /**
      * Updates the target packages' set of enabled overlays in PackageManager.
      */
-    private void updateOverlayPaths(int userId, List<String> targetPackageNames) {
+    private ArrayList<String> updateOverlayPaths(int userId, List<String> targetPackageNames) {
         try {
             traceBegin(TRACE_TAG_RRO, "OMS#updateOverlayPaths " + targetPackageNames);
             if (DEBUG) {
@@ -955,6 +956,7 @@
                 }
             }
 
+            final HashSet<String> updatedPackages = new HashSet<>();
             final int n = targetPackageNames.size();
             for (int i = 0; i < n; i++) {
                 final String targetPackageName = targetPackageNames.get(i);
@@ -965,11 +967,13 @@
                 }
 
                 if (!pm.setEnabledOverlayPackages(
-                        userId, targetPackageName, pendingChanges.get(targetPackageName))) {
+                        userId, targetPackageName, pendingChanges.get(targetPackageName),
+                        updatedPackages)) {
                     Slog.e(TAG, String.format("Failed to change enabled overlays for %s user %d",
                             targetPackageName, userId));
                 }
             }
+            return new ArrayList<>(updatedPackages);
         } finally {
             traceEnd(TRACE_TAG_RRO);
         }
@@ -980,10 +984,10 @@
     }
 
     private void updateAssets(final int userId, List<String> targetPackageNames) {
-        updateOverlayPaths(userId, targetPackageNames);
         final IActivityManager am = ActivityManager.getService();
         try {
-            am.scheduleApplicationInfoChanged(targetPackageNames, userId);
+            final ArrayList<String> updatedPaths = updateOverlayPaths(userId, targetPackageNames);
+            am.scheduleApplicationInfoChanged(updatedPaths, userId);
         } catch (RemoteException e) {
             // Intentionally left empty.
         }
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 019c952..9623542 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -685,7 +685,7 @@
         // Static RROs targeting to "android", ie framework-res.apk, are handled by native layers.
         if (targetPackage != null && overlayPackage != null
                 && !("android".equals(targetPackageName)
-                        && overlayPackage.isStaticOverlayPackage())) {
+                    && overlayPackage.isStaticOverlayPackage())) {
             mIdmapManager.createIdmap(targetPackage, overlayPackage, userId);
         }
 
@@ -703,9 +703,9 @@
         if (currentState != newState) {
             if (DEBUG) {
                 Slog.d(TAG, String.format("%s:%d: %s -> %s",
-                            overlayPackageName, userId,
-                            OverlayInfo.stateToString(currentState),
-                            OverlayInfo.stateToString(newState)));
+                        overlayPackageName, userId,
+                        OverlayInfo.stateToString(currentState),
+                        OverlayInfo.stateToString(newState)));
             }
             modified |= mSettings.setState(overlayPackageName, userId, newState);
         }
diff --git a/services/core/java/com/android/server/om/TEST_MAPPING b/services/core/java/com/android/server/om/TEST_MAPPING
new file mode 100644
index 0000000..52163a0
--- /dev/null
+++ b/services/core/java/com/android/server/om/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.om."
+        }
+      ]
+    }
+  ]
+}
diff --git a/services/core/java/com/android/server/people/PeopleServiceInternal.java b/services/core/java/com/android/server/people/PeopleServiceInternal.java
index 31d30362..c5b868f 100644
--- a/services/core/java/com/android/server/people/PeopleServiceInternal.java
+++ b/services/core/java/com/android/server/people/PeopleServiceInternal.java
@@ -16,9 +16,25 @@
 
 package com.android.server.people;
 
+import android.annotation.NonNull;
 import android.service.appprediction.IPredictionService;
 
 /**
  * @hide Only for use within the system server.
  */
-public abstract class PeopleServiceInternal extends IPredictionService.Stub {}
+public abstract class PeopleServiceInternal extends IPredictionService.Stub {
+
+    /**
+     * The number conversation infos will be dynamic, based on the currently installed apps on the
+     * device. All of which should be combined into a single blob to be backed up.
+     */
+    public abstract byte[] backupConversationInfos(@NonNull int userId);
+
+    /**
+     * Multiple conversation infos may exist in the restore payload, child classes are required to
+     * manage the restoration based on how individual conversation infos were originally combined
+     * during backup.
+     */
+    public abstract void restoreConversationInfos(@NonNull int userId, @NonNull String key,
+            @NonNull byte[] payload);
+}
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 307a07b..c6d2b33 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -32,10 +32,13 @@
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageParser;
+import android.content.pm.parsing.AndroidPackage;
 import android.os.Environment;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.sysprop.ApexProperties;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Singleton;
 import android.util.Slog;
 
@@ -44,22 +47,26 @@
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.IndentingPrintWriter;
 
+import com.google.android.collect.Lists;
+
 import java.io.File;
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 /**
  * ApexManager class handles communications with the apex service to perform operation and queries,
  * as well as providing caching to avoid unnecessary calls to the service.
  */
-abstract class ApexManager {
+public abstract class ApexManager {
 
     private static final String TAG = "ApexManager";
 
@@ -97,12 +104,27 @@
      * Minimal information about APEX mount points and the original APEX package they refer to.
      */
     static class ActiveApexInfo {
+        @Nullable public final String apexModuleName;
         public final File apexDirectory;
-        public final File preinstalledApexPath;
+        public final File preInstalledApexPath;
 
-        private ActiveApexInfo(File apexDirectory, File preinstalledApexPath) {
+        private ActiveApexInfo(File apexDirectory, File preInstalledApexPath) {
+            this(null, apexDirectory, preInstalledApexPath);
+        }
+
+        private ActiveApexInfo(@Nullable String apexModuleName, File apexDirectory,
+                File preInstalledApexPath) {
+            this.apexModuleName = apexModuleName;
             this.apexDirectory = apexDirectory;
-            this.preinstalledApexPath = preinstalledApexPath;
+            this.preInstalledApexPath = preInstalledApexPath;
+        }
+
+        private ActiveApexInfo(ApexInfo apexInfo) {
+            this(
+                    apexInfo.moduleName,
+                    new File(Environment.getApexDirectory() + File.separator
+                            + apexInfo.moduleName),
+                    new File(apexInfo.preinstalledModulePath));
         }
     }
 
@@ -232,6 +254,24 @@
     abstract boolean uninstallApex(String apexPackagePath);
 
     /**
+     * Registers an APK package as an embedded apk of apex.
+     */
+    abstract void registerApkInApex(AndroidPackage pkg);
+
+    /**
+     * Returns list of {@code packageName} of apks inside the given apex.
+     * @param apexPackageName Package name of the apk container of apex
+     */
+    abstract List<String> getApksInApex(String apexPackageName);
+
+    /**
+     * Returns the apex module name for the given package name, if the package is an APEX. Otherwise
+     * returns {@code null}.
+     */
+    @Nullable
+    public abstract String getApexModuleNameForPackageName(String apexPackageName);
+
+    /**
      * Dumps various state information to the provided {@link PrintWriter} object.
      *
      * @param pw the {@link PrintWriter} object to send information to.
@@ -255,16 +295,33 @@
     static class ApexManagerImpl extends ApexManager {
         private final IApexService mApexService;
         private final Object mLock = new Object();
+
+        @GuardedBy("mLock")
+        private Set<ActiveApexInfo> mActiveApexInfosCache;
+
         /**
-         * A map from {@code APEX packageName} to the {@Link PackageInfo} generated from the {@code
-         * AndroidManifest.xml}
-         *
-         * <p>Note that key of this map is {@code packageName} field of the corresponding {@code
-         * AndroidManifest.xml}.
-          */
+         * Contains the list of {@code packageName}s of apks-in-apex for given
+         * {@code apexModuleName}. See {@link #mPackageNameToApexModuleName} to understand the
+         * difference between {@code packageName} and {@code apexModuleName}.
+         */
+        @GuardedBy("mLock")
+        private Map<String, List<String>> mApksInApex = new ArrayMap<>();
+
         @GuardedBy("mLock")
         private List<PackageInfo> mAllPackagesCache;
 
+        /**
+         * An APEX is a file format that delivers the apex-payload wrapped in an apk container. The
+         * apk container has a reference name, called {@code packageName}, which is found inside the
+         * {@code AndroidManifest.xml}. The apex payload inside the container also has a reference
+         * name, called {@code apexModuleName}, which is found in {@code apex_manifest.json} file.
+         *
+         * {@link #mPackageNameToApexModuleName} contains the mapping from {@code packageName} of
+         * the apk container to {@code apexModuleName} of the apex-payload inside.
+         */
+        @GuardedBy("mLock")
+        private Map<String, String> mPackageNameToApexModuleName;
+
         ApexManagerImpl(IApexService apexService) {
             mApexService = apexService;
         }
@@ -291,18 +348,25 @@
 
         @Override
         List<ActiveApexInfo> getActiveApexInfos() {
-            try {
-                return Arrays.stream(mApexService.getActivePackages())
-                        .map(apexInfo -> new ActiveApexInfo(
-                                new File(
-                                Environment.getApexDirectory() + File.separator
-                                        + apexInfo.moduleName),
-                                new File(apexInfo.preinstalledModulePath))).collect(
-                                Collectors.toList());
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Unable to retrieve packages from apexservice", e);
+            synchronized (mLock) {
+                if (mActiveApexInfosCache == null) {
+                    try {
+                        mActiveApexInfosCache = new ArraySet<>();
+                        final ApexInfo[] activePackages = mApexService.getActivePackages();
+                        for (int i = 0; i < activePackages.length; i++) {
+                            ApexInfo apexInfo = activePackages[i];
+                            mActiveApexInfosCache.add(new ActiveApexInfo(apexInfo));
+                        }
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Unable to retrieve packages from apexservice", e);
+                    }
+                }
+                if (mActiveApexInfosCache != null) {
+                    return new ArrayList<>(mActiveApexInfosCache);
+                } else {
+                    return Collections.emptyList();
+                }
             }
-            return Collections.emptyList();
         }
 
         @Override
@@ -318,6 +382,33 @@
             }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
         }
 
+        private void populatePackageNameToApexModuleNameIfNeeded() {
+            synchronized (mLock) {
+                if (mPackageNameToApexModuleName != null) {
+                    return;
+                }
+                try {
+                    mPackageNameToApexModuleName = new ArrayMap<>();
+                    final ApexInfo[] allPkgs = mApexService.getAllPackages();
+                    for (int i = 0; i < allPkgs.length; i++) {
+                        ApexInfo ai = allPkgs[i];
+                        PackageParser.PackageLite pkgLite;
+                        try {
+                            File apexFile = new File(ai.modulePath);
+                            pkgLite = PackageParser.parsePackageLite(apexFile, 0);
+                        } catch (PackageParser.PackageParserException pe) {
+                            throw new IllegalStateException("Unable to parse: "
+                                    + ai.modulePath, pe);
+                        }
+                        mPackageNameToApexModuleName.put(pkgLite.packageName, ai.moduleName);
+                    }
+                } catch (RemoteException re) {
+                    Slog.e(TAG, "Unable to retrieve packages from apexservice: ", re);
+                    throw new RuntimeException(re);
+                }
+            }
+        }
+
         private void populateAllPackagesCacheIfNeeded() {
             synchronized (mLock) {
                 if (mAllPackagesCache != null) {
@@ -366,7 +457,6 @@
                             }
                             factoryPackagesSet.add(packageInfo.packageName);
                         }
-
                     }
                 } catch (RemoteException re) {
                     Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString());
@@ -533,6 +623,45 @@
             }
         }
 
+        @Override
+        void registerApkInApex(AndroidPackage pkg) {
+            synchronized (mLock) {
+                final Iterator<ActiveApexInfo> it = mActiveApexInfosCache.iterator();
+                while (it.hasNext()) {
+                    final ActiveApexInfo aai = it.next();
+                    if (pkg.getBaseCodePath().startsWith(aai.apexDirectory.getAbsolutePath())) {
+                        List<String> apks = mApksInApex.get(aai.apexModuleName);
+                        if (apks == null) {
+                            apks = Lists.newArrayList();
+                            mApksInApex.put(aai.apexModuleName, apks);
+                        }
+                        apks.add(pkg.getPackageName());
+                    }
+                }
+            }
+        }
+
+        @Override
+        List<String> getApksInApex(String apexPackageName) {
+            populatePackageNameToApexModuleNameIfNeeded();
+            synchronized (mLock) {
+                String moduleName = mPackageNameToApexModuleName.get(apexPackageName);
+                if (moduleName == null) {
+                    return Collections.emptyList();
+                }
+                return mApksInApex.getOrDefault(moduleName, Collections.emptyList());
+            }
+        }
+
+        @Override
+        @Nullable
+        public String getApexModuleNameForPackageName(String apexPackageName) {
+            populatePackageNameToApexModuleNameIfNeeded();
+            synchronized (mLock) {
+                return mPackageNameToApexModuleName.get(apexPackageName);
+            }
+        }
+
         /**
          * Dump information about the packages contained in a particular cache
          * @param packagesCache the cache to print information about.
@@ -614,7 +743,6 @@
      * updating APEX packages.
      */
     private static final class ApexManagerFlattenedApex extends ApexManager {
-
         @Override
         List<ActiveApexInfo> getActiveApexInfos() {
             // There is no apexd running in case of flattened apex
@@ -721,6 +849,22 @@
         }
 
         @Override
+        void registerApkInApex(AndroidPackage pkg) {
+            // No-op
+        }
+
+        @Override
+        List<String> getApksInApex(String apexPackageName) {
+            return Collections.emptyList();
+        }
+
+        @Override
+        @Nullable
+        public String getApexModuleNameForPackageName(String apexPackageName) {
+            return null;
+        }
+
+        @Override
         void dump(PrintWriter pw, String packageName) {
             // No-op
         }
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index c4bcf80..9dff775 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -53,6 +53,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import java.util.StringTokenizer;
 
 /**
  * The entity responsible for filtering visibility between apps based on declarations in their
@@ -212,6 +213,25 @@
         return false;
     }
 
+    private static boolean canQueryViaPackage(AndroidPackage querying,
+            AndroidPackage potentialTarget) {
+        return querying.getQueriesPackages() != null
+                && querying.getQueriesPackages().contains(potentialTarget.getPackageName());
+    }
+
+    private static boolean canQueryAsInstaller(PackageSetting querying,
+            AndroidPackage potentialTarget) {
+        final InstallSource installSource = querying.installSource;
+        if (potentialTarget.getPackageName().equals(installSource.installerPackageName)) {
+            return true;
+        }
+        if (!installSource.isInitiatingPackageUninstalled
+                && potentialTarget.getPackageName().equals(installSource.initiatingPackageName)) {
+            return true;
+        }
+        return false;
+    }
+
     private static boolean matches(Intent intent, AndroidPackage potentialTarget) {
         for (int p = ArrayUtils.size(potentialTarget.getProviders()) - 1; p >= 0; p--) {
             ParsedProvider provider = potentialTarget.getProviders().get(p);
@@ -219,10 +239,15 @@
                 continue;
             }
             final Uri data = intent.getData();
-            if ("content".equalsIgnoreCase(intent.getScheme())
-                    && data != null
-                    && Objects.equals(provider.getAuthority(), data.getAuthority())) {
-                return true;
+            if (!"content".equalsIgnoreCase(intent.getScheme()) || data == null
+                    || provider.getAuthority() == null) {
+                continue;
+            }
+            StringTokenizer authorities = new StringTokenizer(provider.getAuthority(), ";", false);
+            while (authorities.hasMoreElements()) {
+                if (Objects.equals(authorities.nextElement(), data.getAuthority())) {
+                    return true;
+                }
             }
         }
         for (int s = ArrayUtils.size(potentialTarget.getServices()) - 1; s >= 0; s--) {
@@ -277,8 +302,9 @@
      *                   initiating uid.
      */
     public void grantImplicitAccess(int callingUid, int targetUid) {
-        if (mImplicitlyQueryable.add(targetUid, callingUid) && DEBUG_LOGGING) {
-            Slog.wtf(TAG, "implicit access granted: " + callingUid + " -> " + targetUid);
+        if (targetUid != callingUid
+                && mImplicitlyQueryable.add(targetUid, callingUid) && DEBUG_LOGGING) {
+            Slog.wtf(TAG, "implicit access granted: " + targetUid + " -> " + callingUid);
         }
     }
 
@@ -325,8 +351,8 @@
                     if (canQueryViaIntent(existingPkg, newPkg)) {
                         mQueriesViaIntent.add(existingSetting.appId, newPkgSetting.appId);
                     }
-                    if (existingPkg.getQueriesPackages() != null
-                            && existingPkg.getQueriesPackages().contains(newPkg.getPackageName())) {
+                    if (canQueryViaPackage(existingPkg, newPkg)
+                            || canQueryAsInstaller(existingSetting, newPkg)) {
                         mQueriesViaPackage.add(existingSetting.appId, newPkgSetting.appId);
                     }
                 }
@@ -335,8 +361,8 @@
                     if (canQueryViaIntent(newPkg, existingPkg)) {
                         mQueriesViaIntent.add(newPkgSetting.appId, existingSetting.appId);
                     }
-                    if (newPkg.getQueriesPackages() != null
-                            && newPkg.getQueriesPackages().contains(existingPkg.getPackageName())) {
+                    if (canQueryViaPackage(newPkg, existingPkg)
+                            || canQueryAsInstaller(newPkgSetting, existingPkg)) {
                         mQueriesViaPackage.add(newPkgSetting.appId, existingSetting.appId);
                     }
                 }
@@ -449,6 +475,7 @@
             }
             final PackageSetting callingPkgSetting;
             final ArraySet<PackageSetting> callingSharedPkgSettings;
+            Trace.beginSection("callingSetting instanceof");
             if (callingSetting instanceof PackageSetting) {
                 callingPkgSetting = (PackageSetting) callingSetting;
                 callingSharedPkgSettings = null;
@@ -456,6 +483,7 @@
                 callingPkgSetting = null;
                 callingSharedPkgSettings = ((SharedUserSetting) callingSetting).packages;
             }
+            Trace.endSection();
 
             if (callingPkgSetting != null) {
                 if (!mFeatureConfig.packageIsEnabled(callingPkgSetting.pkg)) {
@@ -484,7 +512,12 @@
                 }
                 return true;
             }
+            if (targetPkg.isStaticSharedLibrary()) {
+                // not an app, this filtering takes place at a higher level
+                return false;
+            }
             final String targetName = targetPkg.getPackageName();
+            Trace.beginSection("getAppId");
             final int callingAppId;
             if (callingPkgSetting != null) {
                 callingAppId = callingPkgSetting.appId;
@@ -492,6 +525,7 @@
                 callingAppId = callingSharedPkgSettings.valueAt(0).appId; // all should be the same
             }
             final int targetAppId = targetPkgSetting.appId;
+            Trace.endSection();
             if (callingAppId == targetAppId) {
                 if (DEBUG_LOGGING) {
                     log(callingSetting, targetPkgSetting, "same app id");
@@ -499,38 +533,63 @@
                 return false;
             }
 
-            if (callingSetting.getPermissionsState().hasPermission(
-                    Manifest.permission.QUERY_ALL_PACKAGES, UserHandle.getUserId(callingUid))) {
-                if (DEBUG_LOGGING) {
-                    log(callingSetting, targetPkgSetting, "has query-all permission");
+            try {
+                Trace.beginSection("hasPermission");
+                if (callingSetting.getPermissionsState().hasPermission(
+                        Manifest.permission.QUERY_ALL_PACKAGES, UserHandle.getUserId(callingUid))) {
+                    if (DEBUG_LOGGING) {
+                        log(callingSetting, targetPkgSetting, "has query-all permission");
+                    }
+                    return false;
                 }
-                return false;
+            } finally {
+                Trace.endSection();
             }
-            if (mForceQueryable.contains(targetAppId)) {
-                if (DEBUG_LOGGING) {
-                    log(callingSetting, targetPkgSetting, "force queryable");
+            try {
+                Trace.beginSection("mForceQueryable");
+                if (mForceQueryable.contains(targetAppId)) {
+                    if (DEBUG_LOGGING) {
+                        log(callingSetting, targetPkgSetting, "force queryable");
+                    }
+                    return false;
                 }
-                return false;
+            } finally {
+                Trace.endSection();
             }
-            if (mQueriesViaPackage.contains(callingAppId, targetAppId)) {
-                // the calling package has explicitly declared the target package; allow
-                if (DEBUG_LOGGING) {
-                    log(callingSetting, targetPkgSetting, "queries package");
+            try {
+                Trace.beginSection("mQueriesViaPackage");
+                if (mQueriesViaPackage.contains(callingAppId, targetAppId)) {
+                    if (DEBUG_LOGGING) {
+                        log(callingSetting, targetPkgSetting, "queries package");
+                    }
+                    return false;
                 }
-                return false;
-            } else if (mQueriesViaIntent.contains(callingAppId, targetAppId)) {
-                if (DEBUG_LOGGING) {
-                    log(callingSetting, targetPkgSetting, "queries intent");
+            } finally {
+                Trace.endSection();
+            }
+            try {
+                Trace.beginSection("mQueriesViaIntent");
+                if (mQueriesViaIntent.contains(callingAppId, targetAppId)) {
+                    if (DEBUG_LOGGING) {
+                        log(callingSetting, targetPkgSetting, "queries intent");
+                    }
+                    return false;
                 }
-                return false;
+            } finally {
+                Trace.endSection();
             }
 
-            final int targetUid = UserHandle.getUid(userId, targetAppId);
-            if (mImplicitlyQueryable.contains(callingUid, targetUid)) {
-                if (DEBUG_LOGGING) {
-                    log(callingSetting, targetPkgSetting, "implicitly queryable for user");
+            try {
+                Trace.beginSection("mImplicitlyQueryable");
+                final int targetUid = UserHandle.getUid(userId, targetAppId);
+                if (mImplicitlyQueryable.contains(callingUid, targetUid)) {
+                    if (DEBUG_LOGGING) {
+                        log(callingSetting, targetPkgSetting, "implicitly queryable for user");
+                    }
+                    return false;
                 }
-                return false;
+            } finally {
+                Trace.endSection();
             }
             if (callingPkgSetting != null) {
                 if (callingPkgInstruments(callingPkgSetting, targetPkgSetting, targetName)) {
@@ -576,17 +635,22 @@
     private static boolean callingPkgInstruments(PackageSetting callingPkgSetting,
             PackageSetting targetPkgSetting,
             String targetName) {
-        final List<ComponentParseUtils.ParsedInstrumentation> inst =
-                callingPkgSetting.pkg.getInstrumentations();
-        for (int i = ArrayUtils.size(inst) - 1; i >= 0; i--) {
-            if (Objects.equals(inst.get(i).getTargetPackage(), targetName)) {
-                if (DEBUG_LOGGING) {
-                    log(callingPkgSetting, targetPkgSetting, "instrumentation");
+        try {
+            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "callingPkgInstruments");
+            final List<ComponentParseUtils.ParsedInstrumentation> inst =
+                    callingPkgSetting.pkg.getInstrumentations();
+            for (int i = ArrayUtils.size(inst) - 1; i >= 0; i--) {
+                if (Objects.equals(inst.get(i).getTargetPackage(), targetName)) {
+                    if (DEBUG_LOGGING) {
+                        log(callingPkgSetting, targetPkgSetting, "instrumentation");
+                    }
+                    return true;
                 }
-                return true;
             }
+            return false;
+        } finally {
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
-        return false;
     }
 
     private static void log(SettingBase callingPkgSetting, PackageSetting targetPkgSetting,
@@ -597,7 +661,7 @@
     private static void log(SettingBase callingPkgSetting, PackageSetting targetPkgSetting,
             String description, Throwable throwable) {
         Slog.wtf(TAG,
-                "interaction: " + callingPkgSetting.toString()
+                "interaction: " + callingPkgSetting
                         + " -> " + targetPkgSetting.name + " "
                         + description, throwable);
     }
diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
index b25e1e2..74d2efe 100644
--- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
+++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
@@ -15,35 +15,49 @@
  */
 package com.android.server.pm;
 
+import static android.app.AppOpsManager.OP_INTERACT_ACROSS_PROFILES;
+import static android.content.Intent.FLAG_RECEIVER_REGISTERED_ONLY;
+import static android.content.pm.CrossProfileApps.ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 
+import android.Manifest;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityOptions;
+import android.app.AppGlobals;
 import android.app.AppOpsManager;
+import android.app.AppOpsManager.Mode;
 import android.app.IApplicationThread;
 import android.app.admin.DevicePolicyEventLogger;
+import android.app.admin.DevicePolicyManagerInternal;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.PermissionChecker;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ICrossProfileApps;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
 import android.os.Binder;
+import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.stats.devicepolicy.DevicePolicyEnums;
 import android.text.TextUtils;
+import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
+import com.android.internal.app.IAppOpsService;
+import com.android.internal.util.ArrayUtils;
 import com.android.server.LocalServices;
+import com.android.server.appop.AppOpsService;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
 import java.util.ArrayList;
@@ -55,6 +69,7 @@
 
     private Context mContext;
     private Injector mInjector;
+    private AppOpsService mAppOpsService;
 
     public CrossProfileAppsServiceImpl(Context context) {
         this(context, new InjectorImpl(context));
@@ -100,6 +115,7 @@
 
         final int callerUserId = mInjector.getCallingUserId();
         final int callingUid = mInjector.getCallingUid();
+        final int callingPid = mInjector.getCallingPid();
 
         List<UserHandle> allowedTargetUsers = getTargetUserProfilesUnchecked(
                 callingPackage, callerUserId);
@@ -129,10 +145,13 @@
             // must have the required permission and the users must be in the same profile group
             // in order to launch any of its own activities.
             if (callerUserId != userId) {
-                final int permissionFlag = ActivityManager.checkComponentPermission(
-                        android.Manifest.permission.INTERACT_ACROSS_PROFILES, callingUid,
-                        -1, true);
-                if (permissionFlag != PackageManager.PERMISSION_GRANTED
+                final int permissionFlag =  PermissionChecker.checkPermissionForPreflight(
+                        mContext,
+                        android.Manifest.permission.INTERACT_ACROSS_PROFILES,
+                        callingPid,
+                        callingUid,
+                        callingPackage);
+                if (permissionFlag != PermissionChecker.PERMISSION_GRANTED
                         || !isSameProfileGroup(callerUserId, userId)) {
                     throw new SecurityException("Attempt to launch activity without required "
                             + android.Manifest.permission.INTERACT_ACROSS_PROFILES + " permission"
@@ -153,22 +172,135 @@
                 userId);
     }
 
+    @Override
+    public void startActivityAsUserByIntent(
+            IApplicationThread caller,
+            String callingPackage,
+            Intent intent,
+            @UserIdInt int userId) throws RemoteException {
+        Objects.requireNonNull(callingPackage);
+        Objects.requireNonNull(intent);
+        Objects.requireNonNull(intent.getComponent(), "The intent must have a Component set");
+
+        verifyCallingPackage(callingPackage);
+
+        final int callerUserId = mInjector.getCallingUserId();
+        final int callingUid = mInjector.getCallingUid();
+
+        List<UserHandle> allowedTargetUsers = getTargetUserProfilesUnchecked(
+                callingPackage, callerUserId);
+        if (callerUserId != userId && !allowedTargetUsers.contains(UserHandle.of(userId))) {
+            throw new SecurityException(callingPackage + " cannot access unrelated user " + userId);
+        }
+
+        Intent launchIntent = new Intent(intent);
+        launchIntent.setPackage(callingPackage);
+
+        if (!callingPackage.equals(launchIntent.getComponent().getPackageName())) {
+            throw new SecurityException(
+                    callingPackage + " attempts to start an activity in other package - "
+                            + launchIntent.getComponent().getPackageName());
+        }
+
+        if (callerUserId != userId) {
+            if (!hasInteractAcrossProfilesPermission(callingPackage)) {
+                throw new SecurityException("Attempt to launch activity without required "
+                        + android.Manifest.permission.INTERACT_ACROSS_PROFILES + " permission"
+                        + " or target user is not in the same profile group.");
+            }
+        }
+
+        verifyActivityCanHandleIntent(launchIntent, callingUid, userId);
+
+        mInjector.getActivityTaskManagerInternal().startActivityAsUser(
+                caller, callingPackage, launchIntent, /* options= */ null, userId);
+    }
+
+    @Override
+    public boolean canRequestInteractAcrossProfiles(String callingPackage) {
+        Objects.requireNonNull(callingPackage);
+        verifyCallingPackage(callingPackage);
+        return canRequestInteractAcrossProfilesUnchecked(
+                callingPackage, mInjector.getCallingUserId());
+    }
+
+    private boolean canRequestInteractAcrossProfilesUnchecked(
+            String packageName, @UserIdInt int userId) {
+        List<UserHandle> targetUserProfiles = getTargetUserProfilesUnchecked(packageName, userId);
+        if (targetUserProfiles.isEmpty()) {
+            return false;
+        }
+        if (!hasRequestedAppOpPermission(
+                AppOpsManager.opToPermission(OP_INTERACT_ACROSS_PROFILES), packageName)) {
+            return false;
+        }
+        return isCrossProfilePackageWhitelisted(packageName);
+    }
+
+    private boolean hasRequestedAppOpPermission(String permission, String packageName) {
+        try {
+            String[] packages =
+                    mInjector.getIPackageManager().getAppOpPermissionPackages(permission);
+            return ArrayUtils.contains(packages, packageName);
+        } catch (RemoteException exc) {
+            Slog.e(TAG, "PackageManager dead. Cannot get permission info");
+            return false;
+        }
+    }
+
+    @Override
+    public boolean canInteractAcrossProfiles(String callingPackage) {
+        Objects.requireNonNull(callingPackage);
+        verifyCallingPackage(callingPackage);
+
+        final List<UserHandle> targetUserProfiles = getTargetUserProfilesUnchecked(
+                callingPackage, mInjector.getCallingUserId());
+        if (targetUserProfiles.isEmpty()) {
+            return false;
+        }
+
+        return hasInteractAcrossProfilesPermission(callingPackage);
+    }
+
+    private boolean hasInteractAcrossProfilesPermission(String callingPackage) {
+        final int callingUid = mInjector.getCallingUid();
+        final int callingPid = mInjector.getCallingPid();
+        return isPermissionGranted(Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingUid)
+                || isPermissionGranted(Manifest.permission.INTERACT_ACROSS_USERS, callingUid)
+                || PermissionChecker.checkPermissionForPreflight(
+                        mContext,
+                        Manifest.permission.INTERACT_ACROSS_PROFILES,
+                        callingPid,
+                        callingUid,
+                        callingPackage) == PermissionChecker.PERMISSION_GRANTED;
+    }
+
+    private boolean isCrossProfilePackageWhitelisted(String packageName) {
+        final long ident = mInjector.clearCallingIdentity();
+        try {
+            return mInjector.getDevicePolicyManagerInternal()
+                    .getAllCrossProfilePackages().contains(packageName);
+        } finally {
+            mInjector.restoreCallingIdentity(ident);
+        }
+    }
+
     private List<UserHandle> getTargetUserProfilesUnchecked(
-            String callingPackage, @UserIdInt int callingUserId) {
+            String packageName, @UserIdInt int userId) {
         final long ident = mInjector.clearCallingIdentity();
         try {
             final int[] enabledProfileIds =
-                    mInjector.getUserManager().getEnabledProfileIds(callingUserId);
+                    mInjector.getUserManager().getEnabledProfileIds(userId);
 
             List<UserHandle> targetProfiles = new ArrayList<>();
-            for (final int userId : enabledProfileIds) {
-                if (userId == callingUserId) {
+            for (final int profileId : enabledProfileIds) {
+                if (profileId == userId) {
                     continue;
                 }
-                if (!isPackageEnabled(callingPackage, userId)) {
+                if (!isPackageEnabled(packageName, profileId)) {
                     continue;
                 }
-                targetProfiles.add(UserHandle.of(userId));
+                targetProfiles.add(UserHandle.of(profileId));
             }
             return targetProfiles;
         } finally {
@@ -192,6 +324,27 @@
         }
     }
 
+    private void verifyActivityCanHandleIntent(
+            Intent launchIntent, int callingUid, @UserIdInt int userId) {
+        final long ident = mInjector.clearCallingIdentity();
+        try {
+            final List<ResolveInfo> activities =
+                    mInjector.getPackageManagerInternal().queryIntentActivities(
+                            launchIntent,
+                            launchIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                            MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
+                            callingUid,
+                            userId);
+
+            if (!activities.isEmpty()) {
+                return;
+            }
+            throw new SecurityException("Activity cannot handle intent");
+        } finally {
+            mInjector.restoreCallingIdentity(ident);
+        }
+    }
+
     /**
      * Verify that the specified intent does resolved to the specified component and the resolved
      * activity is exported.
@@ -223,6 +376,154 @@
         }
     }
 
+    @Override
+    public void setInteractAcrossProfilesAppOp(String packageName, @Mode int newMode) {
+        final int callingUid = mInjector.getCallingUid();
+        if (!isPermissionGranted(Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingUid)
+                && !isPermissionGranted(Manifest.permission.INTERACT_ACROSS_USERS, callingUid)) {
+            throw new SecurityException(
+                    "INTERACT_ACROSS_USERS or INTERACT_ACROSS_USERS_FULL is required to set the"
+                            + " app-op for interacting across profiles.");
+        }
+        final int callingUserId = mInjector.getCallingUserId();
+        if (newMode == AppOpsManager.MODE_ALLOWED
+                && !canConfigureInteractAcrossProfiles(packageName)) {
+            // The user should not be prompted for apps that cannot request to interact across
+            // profiles. However, we return early here if required to avoid race conditions.
+            Slog.e(TAG, "Tried to turn on the appop for interacting across profiles for invalid"
+                    + " app " + packageName);
+            return;
+        }
+        final int[] profileIds =
+                mInjector.getUserManager().getProfileIds(callingUserId, /* enabledOnly= */ false);
+        for (int profileId : profileIds) {
+            if (!isPackageInstalled(packageName, profileId)) {
+                continue;
+            }
+            setInteractAcrossProfilesAppOpForUser(packageName, newMode, profileId);
+        }
+    }
+
+    private boolean isPackageInstalled(String packageName, @UserIdInt int userId) {
+        final int callingUid = mInjector.getCallingUid();
+        final long identity = mInjector.clearCallingIdentity();
+        try {
+            final PackageInfo info =
+                    mInjector.getPackageManagerInternal()
+                            .getPackageInfo(
+                                    packageName,
+                                    MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
+                                    callingUid,
+                                    userId);
+            return info != null;
+        } finally {
+            mInjector.restoreCallingIdentity(identity);
+        }
+    }
+
+    private void setInteractAcrossProfilesAppOpForUser(
+            String packageName, @Mode int newMode, @UserIdInt int userId) {
+        try {
+            setInteractAcrossProfilesAppOpForUserOrThrow(packageName, newMode, userId);
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.e(TAG, "Missing package " + packageName + " on user ID " + userId, e);
+        }
+    }
+
+    private void setInteractAcrossProfilesAppOpForUserOrThrow(
+            String packageName, @Mode int newMode, @UserIdInt int userId)
+            throws PackageManager.NameNotFoundException {
+        final int uid = mInjector.getPackageManager()
+                .getPackageUidAsUser(packageName, /* flags= */ 0, userId);
+        if (currentModeEquals(newMode, packageName, uid)) {
+            Slog.w(TAG, "Attempt to set mode to existing value of " + newMode + " for "
+                    + packageName + " on user ID " + userId);
+            return;
+        }
+        mInjector.getAppOpsManager()
+                .setMode(OP_INTERACT_ACROSS_PROFILES,
+                        uid,
+                        packageName,
+                        newMode);
+        sendCanInteractAcrossProfilesChangedBroadcast(packageName, uid, UserHandle.of(userId));
+    }
+
+    private boolean currentModeEquals(@Mode int otherMode, String packageName, int uid) {
+        final String op =
+                AppOpsManager.permissionToOp(Manifest.permission.INTERACT_ACROSS_PROFILES);
+        return otherMode ==
+                mInjector.getAppOpsManager().unsafeCheckOpNoThrow(op, uid, packageName);
+    }
+
+    private void sendCanInteractAcrossProfilesChangedBroadcast(
+            String packageName, int uid, UserHandle userHandle) {
+        final Intent intent =
+                new Intent(ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED).setPackage(packageName);
+        if (appDeclaresCrossProfileAttribute(uid)) {
+            intent.addFlags(
+                    Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND | Intent.FLAG_RECEIVER_FOREGROUND);
+        } else {
+            intent.addFlags(FLAG_RECEIVER_REGISTERED_ONLY);
+        }
+        for (ResolveInfo receiver : findBroadcastReceiversForUser(intent, userHandle)) {
+            intent.setComponent(receiver.getComponentInfo().getComponentName());
+            mInjector.sendBroadcastAsUser(intent, userHandle);
+        }
+    }
+
+    private List<ResolveInfo> findBroadcastReceiversForUser(Intent intent, UserHandle userHandle) {
+        return mInjector.getPackageManager()
+                .queryBroadcastReceiversAsUser(intent, /* flags= */ 0, userHandle);
+    }
+
+    private boolean appDeclaresCrossProfileAttribute(int uid) {
+        return mInjector.getPackageManagerInternal().getPackage(uid).isCrossProfile();
+    }
+
+    @Override
+    public boolean canConfigureInteractAcrossProfiles(String packageName) {
+        if (!hasOtherProfileWithPackageInstalled(packageName, mInjector.getCallingUserId())) {
+            return false;
+        }
+        if (!hasRequestedAppOpPermission(
+                AppOpsManager.opToPermission(OP_INTERACT_ACROSS_PROFILES), packageName)) {
+            return false;
+        }
+        return isCrossProfilePackageWhitelisted(packageName);
+    }
+
+    private boolean hasOtherProfileWithPackageInstalled(String packageName, @UserIdInt int userId) {
+        final long ident = mInjector.clearCallingIdentity();
+        try {
+            final int[] profileIds =
+                    mInjector.getUserManager().getProfileIds(userId, /* enabledOnly= */ false);
+            for (int profileId : profileIds) {
+                if (profileId != userId && isPackageInstalled(packageName, profileId)) {
+                    return true;
+                }
+            }
+        } finally {
+            mInjector.restoreCallingIdentity(ident);
+        }
+        return false;
+    }
+
+    @Override
+    public void resetInteractAcrossProfilesAppOps(List<String> packageNames) {
+        packageNames.forEach(this::resetInteractAcrossProfilesAppOp);
+    }
+
+    private void resetInteractAcrossProfilesAppOp(String packageName) {
+        if (canConfigureInteractAcrossProfiles(packageName)) {
+            Slog.w(TAG, "Not resetting app-op for package " + packageName
+                    + " since it is still configurable by users.");
+            return;
+        }
+        final String op =
+                AppOpsManager.permissionToOp(Manifest.permission.INTERACT_ACROSS_PROFILES);
+        setInteractAcrossProfilesAppOp(packageName, AppOpsManager.opToDefaultMode(op));
+    }
+
     private boolean isSameProfileGroup(@UserIdInt int callerUserId, @UserIdInt int userId) {
         final long ident = mInjector.clearCallingIdentity();
         try {
@@ -239,6 +540,19 @@
         mInjector.getAppOpsManager().checkPackage(mInjector.getCallingUid(), callingPackage);
     }
 
+    private boolean isPermissionGranted(String permission, int uid) {
+        return PackageManager.PERMISSION_GRANTED == mInjector.checkComponentPermission(
+                permission, uid, /* owningUid= */-1, /* exported= */ true);
+    }
+
+    private AppOpsService getAppOpsService() {
+        if (mAppOpsService == null) {
+            IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
+            mAppOpsService = (AppOpsService) IAppOpsService.Stub.asInterface(b);
+        }
+        return mAppOpsService;
+    }
+
     private static class InjectorImpl implements Injector {
         private Context mContext;
 
@@ -250,6 +564,10 @@
             return Binder.getCallingUid();
         }
 
+        public int getCallingPid() {
+            return Binder.getCallingPid();
+        }
+
         public int getCallingUserId() {
             return UserHandle.getCallingUserId();
         }
@@ -291,12 +609,35 @@
         public ActivityTaskManagerInternal getActivityTaskManagerInternal() {
             return LocalServices.getService(ActivityTaskManagerInternal.class);
         }
+
+        @Override
+        public IPackageManager getIPackageManager() {
+            return AppGlobals.getPackageManager();
+        }
+
+        @Override
+        public DevicePolicyManagerInternal getDevicePolicyManagerInternal() {
+            return LocalServices.getService(DevicePolicyManagerInternal.class);
+        }
+
+        @Override
+        public void sendBroadcastAsUser(Intent intent, UserHandle user) {
+            mContext.sendBroadcastAsUser(intent, user);
+        }
+
+        @Override
+        public int checkComponentPermission(
+                String permission, int uid, int owningUid, boolean exported) {
+            return ActivityManager.checkComponentPermission(permission, uid, owningUid, exported);
+        }
     }
 
     @VisibleForTesting
     public interface Injector {
         int getCallingUid();
 
+        int getCallingPid();
+
         int getCallingUserId();
 
         UserHandle getCallingUserHandle();
@@ -316,5 +657,13 @@
         ActivityManagerInternal getActivityManagerInternal();
 
         ActivityTaskManagerInternal getActivityTaskManagerInternal();
+
+        IPackageManager getIPackageManager();
+
+        DevicePolicyManagerInternal getDevicePolicyManagerInternal();
+
+        void sendBroadcastAsUser(Intent intent, UserHandle user);
+
+        int checkComponentPermission(String permission, int uid, int owningUid, boolean exported);
     }
 }
diff --git a/services/core/java/com/android/server/pm/InstallSource.java b/services/core/java/com/android/server/pm/InstallSource.java
index 0541797..6684e3f 100644
--- a/services/core/java/com/android/server/pm/InstallSource.java
+++ b/services/core/java/com/android/server/pm/InstallSource.java
@@ -18,6 +18,8 @@
 
 import android.annotation.Nullable;
 
+import com.android.internal.util.Preconditions;
+
 import java.util.Objects;
 
 /**
@@ -29,16 +31,27 @@
      * An instance of InstallSource representing an absence of knowledge of the source of
      * a package. Used in preference to null.
      */
-    static final InstallSource EMPTY = new InstallSource(null, null, null, false);
+    static final InstallSource EMPTY = new InstallSource(null, null, null, false, false, null);
 
     /** We also memoize this case because it is common - all un-updated system apps. */
-    private static final InstallSource EMPTY_ORPHANED = new InstallSource(null, null, null, true);
+    private static final InstallSource EMPTY_ORPHANED = new InstallSource(
+            null, null, null, true, false, null);
 
-    /** The package that requested the installation, if known. */
+    /**
+     * The package that requested the installation, if known. May not correspond to a currently
+     * installed package if {@link #isInitiatingPackageUninstalled} is true.
+     */
     @Nullable
     final String initiatingPackageName;
 
     /**
+     * The signing details of the initiating package, if known. Always null if
+     * {@link #initiatingPackageName} is null.
+     */
+    @Nullable
+    final PackageSignatures initiatingPackageSignatures;
+
+    /**
      * The package on behalf of which the initiating package requested the installation, if any.
      * For example if a downloaded APK is installed via the Package Installer this could be the
      * app that performed the download. This value is provided by the initiating package and not
@@ -57,76 +70,120 @@
     /** Indicates if the package that was the installerPackageName has been uninstalled. */
     final boolean isOrphaned;
 
+    /**
+     * Indicates if the package in initiatingPackageName has been uninstalled. Always false if
+     * {@link #initiatingPackageName} is null.
+     */
+    final boolean isInitiatingPackageUninstalled;
+
+    static InstallSource create(@Nullable String initiatingPackageName,
+            @Nullable String originatingPackageName, @Nullable String installerPackageName) {
+        return create(initiatingPackageName, originatingPackageName, installerPackageName,
+                false, false);
+    }
+
     static InstallSource create(@Nullable String initiatingPackageName,
             @Nullable String originatingPackageName, @Nullable String installerPackageName,
-            boolean isOrphaned) {
+            boolean isOrphaned, boolean isInitiatingPackageUninstalled) {
         return createInternal(
                 intern(initiatingPackageName),
                 intern(originatingPackageName),
                 intern(installerPackageName),
-                isOrphaned);
+                isOrphaned, isInitiatingPackageUninstalled, null);
     }
 
     private static InstallSource createInternal(@Nullable String initiatingPackageName,
             @Nullable String originatingPackageName, @Nullable String installerPackageName,
-            boolean isOrphaned) {
+            boolean isOrphaned, boolean isInitiatingPackageUninstalled,
+            @Nullable PackageSignatures initiatingPackageSignatures) {
         if (initiatingPackageName == null && originatingPackageName == null
-                && installerPackageName == null) {
+                && installerPackageName == null && initiatingPackageSignatures == null
+                && !isInitiatingPackageUninstalled) {
             return isOrphaned ? EMPTY_ORPHANED : EMPTY;
         }
         return new InstallSource(initiatingPackageName, originatingPackageName,
-                installerPackageName, isOrphaned);
+                installerPackageName, isOrphaned, isInitiatingPackageUninstalled,
+                initiatingPackageSignatures
+        );
     }
 
     private InstallSource(@Nullable String initiatingPackageName,
             @Nullable String originatingPackageName, @Nullable String installerPackageName,
-            boolean isOrphaned) {
+            boolean isOrphaned, boolean isInitiatingPackageUninstalled,
+            @Nullable PackageSignatures initiatingPackageSignatures) {
+        if (initiatingPackageName == null) {
+            Preconditions.checkArgument(initiatingPackageSignatures == null);
+            Preconditions.checkArgument(!isInitiatingPackageUninstalled);
+        }
         this.initiatingPackageName = initiatingPackageName;
         this.originatingPackageName = originatingPackageName;
         this.installerPackageName = installerPackageName;
         this.isOrphaned = isOrphaned;
+        this.isInitiatingPackageUninstalled = isInitiatingPackageUninstalled;
+        this.initiatingPackageSignatures = initiatingPackageSignatures;
     }
 
     /**
-     * Return an InstallSource the same as this one except with the specified installerPackageName.
+     * Return an InstallSource the same as this one except with the specified
+     * {@link #installerPackageName}.
      */
-    InstallSource setInstallerPackage(String installerPackageName) {
+    InstallSource setInstallerPackage(@Nullable String installerPackageName) {
         if (Objects.equals(installerPackageName, this.installerPackageName)) {
             return this;
         }
         return createInternal(initiatingPackageName, originatingPackageName,
-                intern(installerPackageName), isOrphaned);
+                intern(installerPackageName), isOrphaned, isInitiatingPackageUninstalled,
+                initiatingPackageSignatures
+        );
     }
 
     /**
-     * Return an InstallSource the same as this one except with the specified value for isOrphaned.
+     * Return an InstallSource the same as this one except with the specified value for
+     * {@link #isOrphaned}.
      */
     InstallSource setIsOrphaned(boolean isOrphaned) {
         if (isOrphaned == this.isOrphaned) {
             return this;
         }
         return createInternal(initiatingPackageName, originatingPackageName, installerPackageName,
-                isOrphaned);
+                isOrphaned, isInitiatingPackageUninstalled, initiatingPackageSignatures);
     }
 
     /**
-     * Return an InstallSource the same as this one except it does not refer to the specified
-     * installer package name (which is being uninstalled).
+     * Return an InstallSource the same as this one except with the specified
+     * {@link #initiatingPackageSignatures}.
      */
-    InstallSource removeInstallerPackage(String packageName) {
+    InstallSource setInitiatingPackageSignatures(@Nullable PackageSignatures signatures) {
+        if (signatures == initiatingPackageSignatures) {
+            return this;
+        }
+        return createInternal(initiatingPackageName, originatingPackageName, installerPackageName,
+                isOrphaned, isInitiatingPackageUninstalled, signatures);
+    }
+
+    /**
+     * Return an InstallSource the same as this one updated to reflect that the specified installer
+     * package name has been uninstalled.
+     */
+    InstallSource removeInstallerPackage(@Nullable String packageName) {
         if (packageName == null) {
             return this;
         }
 
         boolean modified = false;
-        String initiatingPackageName = this.initiatingPackageName;
+        boolean isInitiatingPackageUninstalled = this.isInitiatingPackageUninstalled;
         String originatingPackageName = this.originatingPackageName;
         String installerPackageName = this.installerPackageName;
         boolean isOrphaned = this.isOrphaned;
 
-        if (packageName.equals(initiatingPackageName)) {
-            initiatingPackageName = null;
-            modified = true;
+        if (packageName.equals(this.initiatingPackageName)) {
+            if (!isInitiatingPackageUninstalled) {
+                // In this case we deliberately do not clear the package name (and signatures).
+                // We allow an app to retrieve details of its own install initiator even after
+                // it has been uninstalled.
+                isInitiatingPackageUninstalled = true;
+                modified = true;
+            }
         }
         if (packageName.equals(originatingPackageName)) {
             originatingPackageName = null;
@@ -141,8 +198,9 @@
         if (!modified) {
             return this;
         }
+
         return createInternal(initiatingPackageName, originatingPackageName, installerPackageName,
-                isOrphaned);
+                isOrphaned, isInitiatingPackageUninstalled, initiatingPackageSignatures);
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
index ffcd6cf..bcfe577 100644
--- a/services/core/java/com/android/server/pm/InstantAppRegistry.java
+++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java
@@ -338,9 +338,8 @@
     }
 
     @GuardedBy("mService.mLock")
-    public void onPackageUninstalledLPw(@NonNull AndroidPackage pkg,
+    public void onPackageUninstalledLPw(@NonNull AndroidPackage pkg, @Nullable PackageSetting ps,
             @NonNull int[] userIds) {
-        PackageSetting ps = mService.getPackageSetting(pkg.getPackageName());
         if (ps == null) {
             return;
         }
diff --git a/services/core/java/com/android/server/pm/ModuleInfoProvider.java b/services/core/java/com/android/server/pm/ModuleInfoProvider.java
index 69510d9..06706cd 100644
--- a/services/core/java/com/android/server/pm/ModuleInfoProvider.java
+++ b/services/core/java/com/android/server/pm/ModuleInfoProvider.java
@@ -57,9 +57,9 @@
      */
     private static final String MODULE_METADATA_KEY = "android.content.pm.MODULE_METADATA";
 
-
     private final Context mContext;
     private final IPackageManager mPackageManager;
+    private final ApexManager mApexManager;
     private final Map<String, ModuleInfo> mModuleInfo;
 
     // TODO: Move this to an earlier boot phase if anybody requires it then.
@@ -69,13 +69,16 @@
     ModuleInfoProvider(Context context, IPackageManager packageManager) {
         mContext = context;
         mPackageManager = packageManager;
+        mApexManager = ApexManager.getInstance();
         mModuleInfo = new ArrayMap<>();
     }
 
     @VisibleForTesting
-    public ModuleInfoProvider(XmlResourceParser metadata, Resources resources) {
+    public ModuleInfoProvider(
+            XmlResourceParser metadata, Resources resources, ApexManager apexManager) {
         mContext = null;
         mPackageManager = null;
+        mApexManager = apexManager;
         mModuleInfo = new ArrayMap<>();
         loadModuleMetadata(metadata, resources);
     }
@@ -150,6 +153,8 @@
                 mi.setHidden(isHidden);
                 mi.setPackageName(modulePackageName);
                 mi.setName(moduleName);
+                mi.setApexModuleName(
+                        mApexManager.getApexModuleNameForPackageName(modulePackageName));
 
                 mModuleInfo.put(modulePackageName, mi);
             }
@@ -167,7 +172,7 @@
      *
      * @param flags Use {@link PackageManager#MATCH_ALL} flag to get all modules.
      */
-    List<ModuleInfo> getInstalledModules(@PackageManager.ModuleInfoFlags int flags) {
+    List<ModuleInfo> getInstalledModules(@PackageManager.InstalledModulesFlags int flags) {
         if (!mMetadataLoaded) {
             throw new IllegalStateException("Call to getInstalledModules before metadata loaded");
         }
@@ -195,12 +200,19 @@
         return installedModules;
     }
 
-    ModuleInfo getModuleInfo(String packageName, int flags) {
+    ModuleInfo getModuleInfo(String name, @PackageManager.ModuleInfoFlags int flags) {
         if (!mMetadataLoaded) {
             throw new IllegalStateException("Call to getModuleInfo before metadata loaded");
         }
-
-        return mModuleInfo.get(packageName);
+        if ((flags & PackageManager.MODULE_APEX_NAME) != 0) {
+            for (ModuleInfo moduleInfo : mModuleInfo.values()) {
+                if (name.equals(moduleInfo.getApexModuleName())) {
+                    return moduleInfo;
+                }
+            }
+            return null;
+        }
+        return mModuleInfo.get(name);
     }
 
     String getPackageName() {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index e2dfa12..10f46fd 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -188,7 +188,7 @@
         }
     };
 
-    public PackageInstallerService(Context context, PackageManagerService pm, ApexManager am) {
+    public PackageInstallerService(Context context, PackageManagerService pm) {
         mContext = context;
         mPm = pm;
         mPermissionManager = LocalServices.getService(PermissionManagerServiceInternal.class);
@@ -206,9 +206,8 @@
         mSessionsDir = new File(Environment.getDataSystemDirectory(), "install_sessions");
         mSessionsDir.mkdirs();
 
-        mApexManager = am;
-
-        mStagingManager = new StagingManager(this, am, context);
+        mApexManager = ApexManager.getInstance();
+        mStagingManager = new StagingManager(this, context);
     }
 
     boolean okToSendBroadcasts()  {
@@ -260,11 +259,13 @@
         // atomic install which needs to query sessions, which requires lock on mSessions.
         boolean isDeviceUpgrading = mPm.isDeviceUpgrading();
         for (PackageInstallerSession session : stagedSessionsToRestore) {
-            if (isDeviceUpgrading && !session.isStagedAndInTerminalState()) {
+            if (!session.isStagedAndInTerminalState() && session.hasParentSessionId()
+                    && getSession(session.getParentSessionId()) == null) {
                 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
-                        "Build fingerprint has changed");
+                        "An orphan staged session " + session.sessionId + " is found, "
+                                + "parent " + session.getParentSessionId() + " is missing");
             }
-            mStagingManager.restoreSession(session);
+            mStagingManager.restoreSession(session, isDeviceUpgrading);
         }
         // Broadcasts are not sent while we restore sessions on boot, since no processes would be
         // ready to listen to them. From now on, we greedily assume that broadcasts requests are
@@ -635,7 +636,7 @@
             }
         }
         InstallSource installSource = InstallSource.create(installerPackageName,
-                originatingPackageName, requestedInstallerPackageName, false);
+                originatingPackageName, requestedInstallerPackageName);
         session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
                 mInstallThread.getLooper(), mStagingManager, sessionId, userId, callingUid,
                 installSource, params, createdMillis,
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 426cd01..ef28733 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -18,6 +18,7 @@
 
 import static android.content.pm.DataLoaderType.INCREMENTAL;
 import static android.content.pm.DataLoaderType.STREAMING;
+import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
 import static android.content.pm.PackageManager.INSTALL_FAILED_ABORTED;
 import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_SIGNATURE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
@@ -60,6 +61,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.DataLoaderManager;
 import android.content.pm.DataLoaderParams;
+import android.content.pm.DataLoaderParamsParcel;
 import android.content.pm.FileSystemControlParcel;
 import android.content.pm.IDataLoader;
 import android.content.pm.IDataLoaderStatusListener;
@@ -140,6 +142,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
 
@@ -151,7 +154,6 @@
     private static final int MSG_COMMIT = 1;
     private static final int MSG_ON_PACKAGE_INSTALLED = 2;
     private static final int MSG_SEAL = 3;
-    private static final int MSG_TRANSFER = 4;
 
     /** XML constants used for persisting a session */
     static final String TAG_SESSION = "session";
@@ -203,12 +205,16 @@
     private static final String ATTR_DATALOADER_PACKAGE_NAME = "dataLoaderPackageName";
     private static final String ATTR_DATALOADER_CLASS_NAME = "dataLoaderClassName";
     private static final String ATTR_DATALOADER_ARGUMENTS = "dataLoaderArguments";
+    private static final String ATTR_LOCATION = "location";
     private static final String ATTR_LENGTH_BYTES = "lengthBytes";
     private static final String ATTR_METADATA = "metadata";
+    private static final String ATTR_SIGNATURE = "signature";
 
     private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill";
     private static final int[] EMPTY_CHILD_SESSION_ARRAY = {};
 
+    private static final String SYSTEM_DATA_LOADER_PACKAGE = "android";
+
     // TODO: enforce INSTALL_ALLOW_TEST
     // TODO: enforce INSTALL_ALLOW_DOWNGRADE
 
@@ -301,22 +307,27 @@
     private int mParentSessionId;
 
     static class FileInfo {
+        public final int location;
         public final String name;
         public final Long lengthBytes;
         public final byte[] metadata;
+        public final byte[] signature;
 
-        public static FileInfo added(String name, Long lengthBytes, byte[] metadata) {
-            return new FileInfo(name, lengthBytes, metadata);
+        public static FileInfo added(int location, String name, Long lengthBytes, byte[] metadata,
+                byte[] signature) {
+            return new FileInfo(location, name, lengthBytes, metadata, signature);
         }
 
-        public static FileInfo removed(String name) {
-            return new FileInfo(name, -1L, null);
+        public static FileInfo removed(int location, String name) {
+            return new FileInfo(location, name, -1L, null, null);
         }
 
-        FileInfo(String name, Long lengthBytes, byte[] metadata) {
+        FileInfo(int location, String name, Long lengthBytes, byte[] metadata, byte[] signature) {
+            this.location = location;
             this.name = name;
             this.lengthBytes = lengthBytes;
             this.metadata = metadata;
+            this.signature = signature;
         }
     }
 
@@ -385,32 +396,19 @@
     private final Handler.Callback mHandlerCallback = new Handler.Callback() {
         @Override
         public boolean handleMessage(Message msg) {
-            SomeArgs args;
-            String packageName;
-            IntentSender statusReceiver;
             switch (msg.what) {
                 case MSG_SEAL:
-                    statusReceiver = (IntentSender) msg.obj;
-
-                    handleSeal(statusReceiver);
+                    handleSeal((IntentSender) msg.obj);
                     break;
                 case MSG_COMMIT:
                     handleCommit();
                     break;
-                case MSG_TRANSFER:
-                    args = (SomeArgs) msg.obj;
-                    packageName = (String) args.arg1;
-                    statusReceiver = (IntentSender) args.arg2;
-                    args.recycle();
-
-                    handleTransfer(statusReceiver, packageName);
-                    break;
                 case MSG_ON_PACKAGE_INSTALLED:
-                    args = (SomeArgs) msg.obj;
-                    packageName = (String) args.arg1;
+                    final SomeArgs args = (SomeArgs) msg.obj;
+                    final String packageName = (String) args.arg1;
                     final String message = (String) args.arg2;
                     final Bundle extras = (Bundle) args.arg3;
-                    statusReceiver = (IntentSender) args.arg4;
+                    final IntentSender statusReceiver = (IntentSender) args.arg4;
                     final int returnCode = args.argi1;
                     args.recycle();
 
@@ -456,7 +454,7 @@
      * Checks if the permissions still need to be confirmed.
      *
      * <p>This is dependant on the identity of the installer, hence this cannot be cached if the
-     * installer might still {@link #transfer(String, IntentSender) change}.
+     * installer might still {@link #transfer(String) change}.
      *
      * @return {@code true} iff we need to ask to confirm the permissions?
      */
@@ -555,6 +553,12 @@
                                 params.dataLoaderParams);
             }
         }
+
+        if (isStreamingInstallation()
+                && this.params.dataLoaderParams.getComponentName().getPackageName()
+                == SYSTEM_DATA_LOADER_PACKAGE) {
+            assertShellOrSystemCalling("System data loaders");
+        }
     }
 
     public SessionInfo generateInfo() {
@@ -602,6 +606,7 @@
             info.isStagedSessionReady = mStagedSessionReady;
             info.isStagedSessionFailed = mStagedSessionFailed;
             info.setStagedSessionErrorCode(mStagedSessionErrorCode, mStagedSessionErrorMessage);
+            info.createdMillis = createdMillis;
             info.updatedMillis = updatedMillis;
         }
         return info;
@@ -770,6 +775,17 @@
         }
     }
 
+    private void assertShellOrSystemCalling(String operation) {
+        switch (Binder.getCallingUid()) {
+            case android.os.Process.SHELL_UID:
+            case android.os.Process.ROOT_UID:
+            case android.os.Process.SYSTEM_UID:
+                break;
+            default:
+                throw new SecurityException(operation + " only supported from shell or system");
+        }
+    }
+
     private void assertCanWrite(boolean reverseMode) {
         if (isDataLoaderInstallation()) {
             throw new IllegalStateException(
@@ -780,15 +796,7 @@
             assertPreparedAndNotSealedLocked("assertCanWrite");
         }
         if (reverseMode) {
-            switch (Binder.getCallingUid()) {
-                case android.os.Process.SHELL_UID:
-                case android.os.Process.ROOT_UID:
-                case android.os.Process.SYSTEM_UID:
-                    break;
-                default:
-                    throw new SecurityException(
-                            "Reverse mode only supported from shell or system");
-            }
+            assertShellOrSystemCalling("Reverse mode");
         }
     }
 
@@ -1025,13 +1033,24 @@
         mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
     }
 
-    private class FileSystemConnector extends IPackageInstallerSessionFileSystemConnector.Stub {
+    private final class FileSystemConnector extends
+            IPackageInstallerSessionFileSystemConnector.Stub {
+        final Set<String> mAddedFiles;
+
+        FileSystemConnector(List<InstallationFile> addedFiles) {
+            mAddedFiles = addedFiles.stream().map(file -> file.getName()).collect(
+                    Collectors.toSet());
+        }
+
         @Override
         public void writeData(String name, long offsetBytes, long lengthBytes,
                 ParcelFileDescriptor incomingFd) {
             if (incomingFd == null) {
                 throw new IllegalArgumentException("incomingFd can't be null");
             }
+            if (!mAddedFiles.contains(name)) {
+                throw new SecurityException("File name is not in the list of added files.");
+            }
             try {
                 doWriteInternal(name, offsetBytes, lengthBytes, incomingFd);
             } catch (IOException e) {
@@ -1384,11 +1403,13 @@
         }
     }
 
-    private int assertCanBeTransferredAndReturnNewOwner(String packageName)
-            throws PackageManager.NameNotFoundException {
+    @Override
+    public void transfer(String packageName) {
+        Objects.requireNonNull(packageName);
+
         ApplicationInfo newOwnerAppInfo = mPm.getApplicationInfo(packageName, 0, userId);
         if (newOwnerAppInfo == null) {
-            throw new PackageManager.NameNotFoundException(packageName);
+            throw new ParcelableException(new PackageManager.NameNotFoundException(packageName));
         }
 
         if (PackageManager.PERMISSION_GRANTED != mPm.checkUidPermission(
@@ -1403,106 +1424,31 @@
             throw new SecurityException("Can only transfer sessions that use public options");
         }
 
-        return newOwnerAppInfo.uid;
-    }
-
-    @Override
-    public void transfer(String packageName, IntentSender statusReceiver) {
-        Objects.requireNonNull(statusReceiver);
-        Objects.requireNonNull(packageName);
-
-        try {
-            assertCanBeTransferredAndReturnNewOwner(packageName);
-        } catch (PackageManager.NameNotFoundException e) {
-            throw new ParcelableException(e);
-        }
+        List<PackageInstallerSession> childSessions = getChildSessions();
 
         synchronized (mLock) {
             assertCallerIsOwnerOrRootLocked();
             assertPreparedAndNotSealedLocked("transfer");
-        }
 
-        final SomeArgs args = SomeArgs.obtain();
-        args.arg1 = packageName;
-        args.arg2 = statusReceiver;
-
-        mHandler.obtainMessage(MSG_TRANSFER, args).sendToTarget();
-    }
-
-    private void handleTransfer(IntentSender statusReceiver, String packageName) {
-        List<PackageInstallerSession> childSessions = getChildSessions();
-
-        try {
-            final int uid = assertCanBeTransferredAndReturnNewOwner(packageName);
-
-            synchronized (mLock) {
-                assertPreparedAndNotSealedLocked("transfer");
-
-                try {
-                    sealAndValidateLocked(childSessions);
-                } catch (StreamingException e) {
-                    throw new IllegalArgumentException("Streaming failed", e);
-                } catch (PackageManagerException e) {
-                    throw new IllegalArgumentException("Package is not valid", e);
-                }
-
-                if (!mPackageName.equals(mInstallSource.installerPackageName)) {
-                    throw new SecurityException(
-                            "Can only transfer sessions that update the original installer");
-                }
-
-                mInstallerUid = uid;
-                mInstallSource = InstallSource.create(packageName, null, packageName, false);
+            try {
+                sealLocked(childSessions);
+            } catch (PackageManagerException e) {
+                throw new IllegalArgumentException("Package is not valid", e);
             }
-        } catch (PackageManager.NameNotFoundException e) {
-            onSessionTransferStatus(statusReceiver, packageName,
-                    PackageInstaller.STATUS_FAILURE_NAME_NOT_FOUND, e.getMessage());
-            return;
-        } catch (IllegalStateException e) {
-            onSessionTransferStatus(statusReceiver, packageName,
-                    PackageInstaller.STATUS_FAILURE_ILLEGAL_STATE, e.getMessage());
-            return;
-        } catch (SecurityException e) {
-            onSessionTransferStatus(statusReceiver, packageName,
-                    PackageInstaller.STATUS_FAILURE_SECURITY, e.getMessage());
-            return;
-        } catch (Throwable e) {
-            onSessionTransferStatus(statusReceiver, packageName, PackageInstaller.STATUS_FAILURE,
-                    e.getMessage());
-            return;
+
+            if (!mPackageName.equals(mInstallSource.installerPackageName)) {
+                throw new SecurityException("Can only transfer sessions that update the original "
+                        + "installer");
+            }
+
+            mInstallerUid = newOwnerAppInfo.uid;
+            mInstallSource = InstallSource.create(packageName, null, packageName);
         }
 
         // Persist the fact that we've sealed ourselves to prevent
         // mutations of any hard links we create. We do this without holding
         // the session lock, since otherwise it's a lock inversion.
         mCallback.onSessionSealedBlocking(this);
-
-        // Report success.
-        onSessionTransferStatus(statusReceiver, packageName, PackageInstaller.STATUS_SUCCESS, null);
-    }
-
-    private void onSessionTransferStatus(IntentSender statusReceiver, String otherPackageName,
-            int status, String statusMessage) {
-        final String packageName;
-        synchronized (mLock) {
-            packageName = mPackageName;
-        }
-
-        final Intent fillIn = new Intent();
-        fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName);
-        fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
-        fillIn.putExtra(PackageInstaller.EXTRA_OTHER_PACKAGE_NAME, otherPackageName);
-
-        fillIn.putExtra(PackageInstaller.EXTRA_STATUS, status);
-        if (!TextUtils.isEmpty(statusMessage)) {
-            fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, statusMessage);
-        }
-
-        try {
-            statusReceiver.sendIntent(mContext, 0, fillIn, null, null);
-        } catch (IntentSender.SendIntentException ignored) {
-        }
-
     }
 
     private void handleCommit() {
@@ -2389,21 +2335,23 @@
     }
 
     @Override
-    public void addFile(String name, long lengthBytes, byte[] metadata) {
-        if (mIncrementalFileStorages != null) {
-            try {
-                mIncrementalFileStorages.addFile(new InstallationFile(name, lengthBytes, metadata));
-                //TODO(b/136132412): merge incremental and callback installation schemes
-                return;
-            } catch (IOException ex) {
-                throw new IllegalStateException(
-                        "Failed to add and configure Incremental File: " + name, ex);
-            }
-        }
+    public DataLoaderParamsParcel getDataLoaderParams() {
+        return params.dataLoaderParams != null ? params.dataLoaderParams.getData() : null;
+    }
+
+    @Override
+    public void addFile(int location, String name, long lengthBytes, byte[] metadata,
+            byte[] signature) {
         if (!isDataLoaderInstallation()) {
             throw new IllegalStateException(
                     "Cannot add files to non-data loader installation session.");
         }
+        if (!isIncrementalInstallation()) {
+            if (location != LOCATION_DATA_APP) {
+                throw new IllegalArgumentException(
+                        "Non-incremental installation only supports /data/app placement: " + name);
+            }
+        }
         // Use installer provided name for now; we always rename later
         if (!FileUtils.isValidExtFilename(name)) {
             throw new IllegalArgumentException("Invalid name: " + name);
@@ -2412,13 +2360,12 @@
         synchronized (mLock) {
             assertCallerIsOwnerOrRootLocked();
             assertPreparedAndNotSealedLocked("addFile");
-
-            mFiles.add(FileInfo.added(name, lengthBytes, metadata));
+            mFiles.add(FileInfo.added(location, name, lengthBytes, metadata, signature));
         }
     }
 
     @Override
-    public void removeFile(String name) {
+    public void removeFile(int location, String name) {
         if (!isDataLoaderInstallation()) {
             throw new IllegalStateException(
                     "Cannot add files to non-data loader installation session.");
@@ -2431,7 +2378,7 @@
             assertCallerIsOwnerOrRootLocked();
             assertPreparedAndNotSealedLocked("removeFile");
 
-            mFiles.add(FileInfo.removed(getRemoveMarkerName(name)));
+            mFiles.add(FileInfo.removed(location, getRemoveMarkerName(name)));
         }
     }
 
@@ -2463,12 +2410,10 @@
      */
     private void prepareDataLoader()
             throws PackageManagerException, StreamingException {
-        if (!isStreamingInstallation()) {
+        if (!isDataLoaderInstallation()) {
             return;
         }
 
-        FileSystemConnector connector = new FileSystemConnector();
-
         List<InstallationFile> addedFiles = mFiles.stream().filter(
                 file -> sAddedFilter.accept(new File(file.name))).map(
                     file -> new InstallationFile(
@@ -2479,6 +2424,20 @@
                     file -> file.name.substring(
                             0, file.name.length() - REMOVE_MARKER_EXTENSION.length())).collect(
                 Collectors.toList());
+        if (mIncrementalFileStorages != null) {
+            for (InstallationFile file : addedFiles) {
+                try {
+                    mIncrementalFileStorages.addFile(file);
+                } catch (IOException ex) {
+                    // TODO(b/146080380): add incremental-specific error code
+                    throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+                            "Failed to add and configure Incremental File: " + file.getName(), ex);
+                }
+            }
+            return;
+        }
+
+        final FileSystemConnector connector = new FileSystemConnector(addedFiles);
 
         DataLoaderManager dataLoaderManager = mContext.getSystemService(DataLoaderManager.class);
         if (dataLoaderManager == null) {
@@ -2515,11 +2474,11 @@
             }
         };
 
-        final DataLoaderParams params = this.params.dataLoaderParams;
-
         final FileSystemControlParcel control = new FileSystemControlParcel();
         control.callback = connector;
 
+        final DataLoaderParams params = this.params.dataLoaderParams;
+
         Bundle dataLoaderParams = new Bundle();
         dataLoaderParams.putParcelable("componentName", params.getComponentName());
         dataLoaderParams.putParcelable("control", control);
@@ -2954,9 +2913,11 @@
             }
             for (FileInfo fileInfo : mFiles) {
                 out.startTag(null, TAG_SESSION_FILE);
+                writeIntAttribute(out, ATTR_LOCATION, fileInfo.location);
                 writeStringAttribute(out, ATTR_NAME, fileInfo.name);
                 writeLongAttribute(out, ATTR_LENGTH_BYTES, fileInfo.lengthBytes);
                 writeByteArrayAttribute(out, ATTR_METADATA, fileInfo.metadata);
+                writeByteArrayAttribute(out, ATTR_SIGNATURE, fileInfo.signature);
                 out.endTag(null, TAG_SESSION_FILE);
             }
         }
@@ -3087,9 +3048,12 @@
                 childSessionIds.add(readIntAttribute(in, ATTR_SESSION_ID, SessionInfo.INVALID_ID));
             }
             if (TAG_SESSION_FILE.equals(in.getName())) {
-                files.add(new FileInfo(readStringAttribute(in, ATTR_NAME),
+                files.add(new FileInfo(
+                        readIntAttribute(in, ATTR_LOCATION, 0),
+                        readStringAttribute(in, ATTR_NAME),
                         readLongAttribute(in, ATTR_LENGTH_BYTES, -1),
-                        readByteArrayAttribute(in, ATTR_METADATA)));
+                        readByteArrayAttribute(in, ATTR_METADATA),
+                        readByteArrayAttribute(in, ATTR_SIGNATURE)));
             }
         }
 
@@ -3115,7 +3079,7 @@
         }
 
         InstallSource installSource = InstallSource.create(installInitiatingPackageName,
-                installOriginatingPackageName, installerPackageName, false);
+                installOriginatingPackageName, installerPackageName);
         return new PackageInstallerSession(callback, context, pm, sessionProvider,
                 installerThread, stagingManager, sessionId, userId, installerUid,
                 installSource, params, createdMillis, stageDir, stageCid, fileInfosArray,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6bd9c48..ca4ae02 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -124,6 +124,7 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
+import android.app.ApplicationPackageManager;
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
 import android.app.IActivityManager;
@@ -184,12 +185,14 @@
 import android.content.pm.ParceledListSlice;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
+import android.content.pm.ProcessInfo;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.SELinuxUtil;
 import android.content.pm.ServiceInfo;
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.Signature;
+import android.content.pm.SigningInfo;
 import android.content.pm.SuspendDialogInfo;
 import android.content.pm.UserInfo;
 import android.content.pm.VerifierDeviceIdentity;
@@ -492,6 +495,7 @@
     static final int SCAN_AS_PRODUCT = 1 << 20;
     static final int SCAN_AS_SYSTEM_EXT = 1 << 21;
     static final int SCAN_AS_ODM = 1 << 22;
+    static final int SCAN_AS_APK_IN_APEX = 1 << 23;
 
     @IntDef(flag = true, prefix = { "SCAN_" }, value = {
             SCAN_NO_DEX,
@@ -1532,7 +1536,7 @@
     final @Nullable String mAppPredictionServicePackage;
     final @Nullable String mIncidentReportApproverPackage;
     final @Nullable String[] mTelephonyPackages;
-    final @NonNull String mServicesSystemSharedLibraryPackageName;
+    final @NonNull String mServicesExtensionPackageName;
     final @NonNull String mSharedSystemSharedLibraryPackageName;
 
     private final PackageUsage mPackageUsage = new PackageUsage();
@@ -2589,6 +2593,9 @@
                     & (SCAN_AS_VENDOR | SCAN_AS_ODM | SCAN_AS_PRODUCT | SCAN_AS_SYSTEM_EXT)) != 0) {
                 return true;
             }
+            if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
+                return true;
+            }
             return false;
         }
 
@@ -2716,6 +2723,7 @@
         t.traceBegin("get system config");
         SystemConfig systemConfig = SystemConfig.getInstance();
         mAvailableFeatures = systemConfig.getAvailableFeatures();
+        ApplicationPackageManager.invalidateSysFeatureCache();
         t.traceEnd();
 
         mProtectedPackages = new ProtectedPackages(mContext);
@@ -3151,6 +3159,7 @@
                 // Adjust seInfo to ensure apps which share a sharedUserId are placed in the same
                 // SELinux domain.
                 setting.fixSeInfoLocked();
+                setting.updateProcesses();
             }
 
             // Now that we know all the packages we are keeping,
@@ -3298,9 +3307,7 @@
                 } else {
                     mIntentFilterVerifier = null;
                 }
-                mServicesSystemSharedLibraryPackageName = getRequiredSharedLibraryLPr(
-                        PackageManager.SYSTEM_SHARED_LIBRARY_SERVICES,
-                        SharedLibraryInfo.VERSION_UNDEFINED);
+                mServicesExtensionPackageName = getRequiredServicesExtensionPackageLPr();
                 mSharedSystemSharedLibraryPackageName = getRequiredSharedLibraryLPr(
                         PackageManager.SYSTEM_SHARED_LIBRARY_SHARED,
                         SharedLibraryInfo.VERSION_UNDEFINED);
@@ -3310,7 +3317,7 @@
                 mRequiredUninstallerPackage = null;
                 mIntentFilterVerifierComponent = null;
                 mIntentFilterVerifier = null;
-                mServicesSystemSharedLibraryPackageName = null;
+                mServicesExtensionPackageName = null;
                 mSharedSystemSharedLibraryPackageName = null;
             }
             // PermissionController hosts default permission granting and role management, so it's a
@@ -3332,7 +3339,7 @@
                 }
             }
 
-            mInstallerService = new PackageInstallerService(mContext, this, mApexManager);
+            mInstallerService = new PackageInstallerService(mContext, this);
             final Pair<ComponentName, String> instantAppResolverComponent =
                     getInstantAppResolverLPr();
             if (instantAppResolverComponent != null) {
@@ -3740,6 +3747,19 @@
         }
     }
 
+    @NonNull
+    private String getRequiredServicesExtensionPackageLPr() {
+        String servicesExtensionPackage =
+                ensureSystemPackageName(
+                        mContext.getString(R.string.config_servicesExtensionPackage));
+        if (TextUtils.isEmpty(servicesExtensionPackage)) {
+            throw new RuntimeException(
+                    "Required services extension package is missing, check "
+                            + "config_servicesExtensionPackage.");
+        }
+        return servicesExtensionPackage;
+    }
+
     private @NonNull String getRequiredInstallerLPr() {
         final Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
         intent.addCategory(Intent.CATEGORY_DEFAULT);
@@ -5344,8 +5364,9 @@
         if (!mUserManager.exists(userId)) return null;
         final int callingUid = Binder.getCallingUid();
         flags = updateFlagsForComponent(flags, userId);
-        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
-                false /* requireFullPermission */, false /* checkShell */, "get service info");
+        mPermissionManager.enforceCrossUserOrProfilePermission(
+                callingUid, userId, false /* requireFullPermission */, false /* checkShell */,
+                "get service info");
         synchronized (mLock) {
             ParsedService s = mComponentResolver.getService(component);
             if (DEBUG_PACKAGE_INFO) Log.v(
@@ -5461,7 +5482,7 @@
     public @NonNull String getServicesSystemSharedLibraryPackageName() {
         // allow instant applications
         synchronized (mLock) {
-            return mServicesSystemSharedLibraryPackageName;
+            return mServicesExtensionPackageName;
         }
     }
 
@@ -7795,8 +7816,10 @@
             String resolvedType, int flags, int userId, int callingUid,
             boolean includeInstantApps) {
         if (!mUserManager.exists(userId)) return Collections.emptyList();
-        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
-                false /*requireFullPermission*/, false /*checkShell*/,
+        mPermissionManager.enforceCrossUserOrProfilePermission(callingUid,
+                userId,
+                false /*requireFullPermission*/,
+                false /*checkShell*/,
                 "query intent receivers");
         final String instantAppPkgName = getInstantAppPackageName(callingUid);
         flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps);
@@ -11595,6 +11618,23 @@
             return false;
         }
         SharedLibraryInfo libraryInfo = versionedLib.valueAt(libIdx);
+
+        // Remove the shared library overlays from its dependent packages.
+        for (int currentUserId : UserManagerService.getInstance().getUserIds()) {
+            final List<VersionedPackage> dependents = getPackagesUsingSharedLibraryLPr(
+                    libraryInfo, 0, currentUserId);
+            if (dependents == null) {
+                continue;
+            }
+            for (VersionedPackage dependentPackage : dependents) {
+                final PackageSetting ps = mSettings.mPackages.get(
+                        dependentPackage.getPackageName());
+                if (ps != null) {
+                    ps.setOverlayPathsForLibrary(libraryInfo.getName(), null, currentUserId);
+                }
+            }
+        }
+
         versionedLib.remove(version);
         if (versionedLib.size() <= 0) {
             mSharedLibraries.remove(name);
@@ -11710,6 +11750,9 @@
             mSettings.insertPackageSettingLPw(pkgSetting, pkg);
             // Add the new setting to mPackages
             mPackages.put(pkg.getAppInfoPackageName(), pkg);
+            if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
+                mApexManager.registerApkInApex(pkg);
+            }
 
             // Add the package's KeySets to the global KeySetManagerService
             KeySetManagerService ksms = mSettings.mKeySetManagerService;
@@ -13453,6 +13496,7 @@
             // Okay!
             targetPackageSetting.setInstallerPackageName(installerPackageName);
             mSettings.addInstallerPackageNames(targetPackageSetting.installSource);
+            mAppsFilter.addPackage(targetPackageSetting, mSettings.mPackages);
             scheduleWriteSettingsLocked();
         }
     }
@@ -15158,10 +15202,10 @@
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings");
 
         final String pkgName = pkg.getPackageName();
-        final InstallSource installSource = installArgs.installSource;
-        final String installerPackageName = installSource.installerPackageName;
         final int[] installedForUsers = res.origUsers;
         final int installReason = installArgs.installReason;
+        InstallSource installSource = installArgs.installSource;
+        final String installerPackageName = installSource.installerPackageName;
 
         if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + pkg.getCodePath());
         synchronized (mLock) {
@@ -15200,6 +15244,29 @@
                         // upcoming call to mSettings.writeLPr().
                     }
                 }
+
+                // Retrieve the overlays for shared libraries of the package.
+                if (pkg.getUsesLibraryInfos() != null) {
+                    for (SharedLibraryInfo sharedLib : pkg.getUsesLibraryInfos()) {
+                        for (int currentUserId : UserManagerService.getInstance().getUserIds()) {
+                            if (!sharedLib.isDynamic()) {
+                                // TODO(146804378): Support overlaying static shared libraries
+                                continue;
+                            }
+                            final PackageSetting libPs = mSettings.mPackages.get(
+                                    sharedLib.getPackageName());
+                            if (libPs == null) {
+                                continue;
+                            }
+                            final String[] overlayPaths = libPs.getOverlayPaths(currentUserId);
+                            if (overlayPaths != null) {
+                                ps.setOverlayPathsForLibrary(sharedLib.getName(),
+                                        Arrays.asList(overlayPaths), currentUserId);
+                            }
+                        }
+                    }
+                }
+
                 // It's implied that when a user requests installation, they want the app to be
                 // installed and enabled.
                 if (userId != UserHandle.USER_ALL) {
@@ -15207,6 +15274,14 @@
                     ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, installerPackageName);
                 }
 
+                if (installSource.initiatingPackageName != null) {
+                    final PackageSetting ips = mSettings.mPackages.get(
+                            installSource.initiatingPackageName);
+                    if (ips != null) {
+                        installSource = installSource.setInitiatingPackageSignatures(
+                                ips.signatures);
+                    }
+                }
                 ps.setInstallSource(installSource);
                 mSettings.addInstallerPackageNames(installSource);
 
@@ -17444,7 +17519,8 @@
             synchronized (mLock) {
                 if (res) {
                     if (pkg != null) {
-                        mInstantAppRegistry.onPackageUninstalledLPw(pkg, info.removedUsers);
+                        mInstantAppRegistry.onPackageUninstalledLPw(pkg, uninstalledPs,
+                                info.removedUsers);
                     }
                     updateSequenceNumberLP(uninstalledPs, info.removedUsers);
                     updateInstantAppInstallerLocked(packageName);
@@ -17623,7 +17699,7 @@
     }
 
     /*
-     * This method deletes the package from internal data structures. If the DONT_DELETE_DATA
+     * This method deletes the package from internal data structures. If the DELETE_KEEP_DATA
      * flag is not set, the data directory is removed as well.
      * make sure this flag is set for partially installed apps. If not its meaningless to
      * delete a partially installed application.
@@ -17749,10 +17825,10 @@
             ApexManager.ActiveApexInfo apexInfo) {
         for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) {
             SystemPartition sp = SYSTEM_PARTITIONS.get(i);
-            if (apexInfo.preinstalledApexPath.getAbsolutePath().startsWith(
+            if (apexInfo.preInstalledApexPath.getAbsolutePath().startsWith(
                     sp.folder.getAbsolutePath())) {
-                return new SystemPartition(apexInfo.apexDirectory, sp.scanFlag,
-                        false /* hasOverlays */);
+                return new SystemPartition(apexInfo.apexDirectory,
+                        sp.scanFlag | SCAN_AS_APK_IN_APEX, false /* hasOverlays */);
             }
         }
         return null;
@@ -19372,7 +19448,7 @@
             // PermissionController manages default home directly.
             return false;
         }
-        mPermissionManager.setDefaultHome(currentPackageName, userId, (successful) -> {
+        mPermissionManager.setDefaultHome(packageName, userId, (successful) -> {
             if (successful) {
                 postPreferredActivityChangedBroadcast(userId);
             }
@@ -19962,8 +20038,9 @@
         String initiatingPackageName;
         String originatingPackageName;
 
+        final InstallSource installSource;
         synchronized (mLock) {
-            final InstallSource installSource = getInstallSourceLocked(packageName, callingUid);
+            installSource = getInstallSourceLocked(packageName, callingUid);
             if (installSource == null) {
                 return null;
             }
@@ -19976,19 +20053,32 @@
                 }
             }
 
-            // All installSource strings are interned, so == is ok here
-            if (installSource.initiatingPackageName == installSource.installerPackageName) {
-                // The installer and initiator will often be the same, and when they are
-                // we can skip doing the same check again.
-                initiatingPackageName = installerPackageName;
-            } else {
-                initiatingPackageName = installSource.initiatingPackageName;
-                final PackageSetting ps = mSettings.mPackages.get(initiatingPackageName);
-                if (ps == null || shouldFilterApplicationLocked(ps, callingUid, userId)) {
+            if (installSource.isInitiatingPackageUninstalled) {
+                // We can't check visibility in the usual way, since the initiating package is no
+                // longer present. So we apply simpler rules to whether to expose the info:
+                // 1. Instant apps can't see it.
+                // 2. Otherwise only the installed app itself can see it.
+                final boolean isInstantApp = getInstantAppPackageName(callingUid) != null;
+                if (!isInstantApp && isCallerSameApp(packageName, callingUid)) {
+                    initiatingPackageName = installSource.initiatingPackageName;
+                } else {
                     initiatingPackageName = null;
                 }
-
+            } else {
+                // All installSource strings are interned, so == is ok here
+                if (installSource.initiatingPackageName == installSource.installerPackageName) {
+                    // The installer and initiator will often be the same, and when they are
+                    // we can skip doing the same check again.
+                    initiatingPackageName = installerPackageName;
+                } else {
+                    initiatingPackageName = installSource.initiatingPackageName;
+                    final PackageSetting ps = mSettings.mPackages.get(initiatingPackageName);
+                    if (ps == null || shouldFilterApplicationLocked(ps, callingUid, userId)) {
+                        initiatingPackageName = null;
+                    }
+                }
             }
+
             originatingPackageName = installSource.originatingPackageName;
             if (originatingPackageName != null) {
                 final PackageSetting ps = mSettings.mPackages.get(originatingPackageName);
@@ -19998,13 +20088,27 @@
             }
         }
 
+        // Remaining work can safely be done outside the lock. (Note that installSource is
+        // immutable so it's ok to carry on reading from it.)
+
         if (originatingPackageName != null && mContext.checkCallingOrSelfPermission(
                 Manifest.permission.INSTALL_PACKAGES) != PackageManager.PERMISSION_GRANTED) {
             originatingPackageName = null;
         }
 
-        return new InstallSourceInfo(initiatingPackageName, originatingPackageName,
-                installerPackageName);
+        // If you can see the initiatingPackageName, and we have valid signing info for it,
+        // then we let you see that too.
+        final SigningInfo initiatingPackageSigningInfo;
+        final PackageSignatures signatures = installSource.initiatingPackageSignatures;
+        if (initiatingPackageName != null && signatures != null
+                && signatures.mSigningDetails != SigningDetails.UNKNOWN) {
+            initiatingPackageSigningInfo = new SigningInfo(signatures.mSigningDetails);
+        } else {
+            initiatingPackageSigningInfo = null;
+        }
+
+        return new InstallSourceInfo(initiatingPackageName, initiatingPackageSigningInfo,
+                originatingPackageName, installerPackageName);
     }
 
     @GuardedBy("mLock")
@@ -20108,8 +20212,7 @@
         // Disable any carrier apps. We do this very early in boot to prevent the apps from being
         // disabled after already being started.
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(), this,
-                mPermissionManagerService, mContext.getContentResolver(),
-                UserHandle.USER_SYSTEM);
+                mPermissionManagerService, UserHandle.USER_SYSTEM, mContext);
 
         disableSkuSpecificApps();
 
@@ -22203,6 +22306,13 @@
         mPermissionManager.onNewUserCreated(userId);
     }
 
+    boolean readPermissionStateForUser(@UserIdInt int userId) {
+        synchronized (mPackages) {
+            mSettings.readPermissionStateForUserSyncLPr(userId);
+            return mSettings.areDefaultRuntimePermissionsGrantedLPr(userId);
+        }
+    }
+
     @Override
     public VerifierDeviceIdentity getVerifierDeviceIdentity() throws RemoteException {
         mContext.enforceCallingOrSelfPermission(
@@ -22777,7 +22887,7 @@
             ArrayList<String> systemPackageNames = new ArrayList<>(pkgNames.length);
 
             for (String pkgName: pkgNames) {
-                synchronized (mPackages) {
+                synchronized (mLock) {
                     if (pkgName == null) {
                         continue;
                     }
@@ -23226,9 +23336,11 @@
 
         @Override
         public boolean setEnabledOverlayPackages(int userId, @NonNull String targetPackageName,
-                @Nullable List<String> overlayPackageNames) {
+                @Nullable List<String> overlayPackageNames,
+                @NonNull Collection<String> outUpdatedPackageNames) {
             synchronized (mLock) {
-                if (targetPackageName == null || mPackages.get(targetPackageName) == null) {
+                final AndroidPackage targetPkg = mPackages.get(targetPackageName);
+                if (targetPackageName == null || targetPkg == null) {
                     Slog.e(TAG, "failed to find package " + targetPackageName);
                     return false;
                 }
@@ -23247,8 +23359,41 @@
                     }
                 }
 
+                ArraySet<String> updatedPackageNames = null;
+                if (targetPkg.getLibraryNames() != null) {
+                    // Set the overlay paths for dependencies of the shared library.
+                    updatedPackageNames = new ArraySet<>();
+                    for (String libName : targetPkg.getLibraryNames()) {
+                        final SharedLibraryInfo info = getSharedLibraryInfoLPr(libName,
+                                SharedLibraryInfo.VERSION_UNDEFINED);
+                        if (info == null) {
+                            continue;
+                        }
+                        final List<VersionedPackage> dependents = getPackagesUsingSharedLibraryLPr(
+                                info, 0, userId);
+                        if (dependents == null) {
+                            continue;
+                        }
+                        for (VersionedPackage dependent : dependents) {
+                            final PackageSetting ps = mSettings.mPackages.get(
+                                    dependent.getPackageName());
+                            if (ps == null) {
+                                continue;
+                            }
+                            ps.setOverlayPathsForLibrary(libName, overlayPaths, userId);
+                            updatedPackageNames.add(dependent.getPackageName());
+                        }
+                    }
+                }
+
                 final PackageSetting ps = mSettings.mPackages.get(targetPackageName);
                 ps.setOverlayPaths(overlayPaths, userId);
+
+                outUpdatedPackageNames.add(targetPackageName);
+                if (updatedPackageNames != null) {
+                    outUpdatedPackageNames.addAll(updatedPackageNames);
+                }
+
                 return true;
             }
         }
@@ -23344,6 +23489,20 @@
         }
 
         @Override
+        public ArrayMap<String, ProcessInfo> getProcessesForUid(int uid) {
+            synchronized (mLock) {
+                return getProcessesForUidLocked(uid);
+            }
+        }
+
+        @Override
+        public int[] getPermissionGids(String permissionName, int userId) {
+            synchronized (mLock) {
+                return getPermissionGidsLocked(permissionName, userId);
+            }
+        }
+
+        @Override
         public boolean isOnlyCoreApps() {
             return PackageManagerService.this.isOnlyCoreApps();
         }
@@ -23438,6 +23597,11 @@
         }
 
         @Override
+        public List<String> getApksInApex(String apexPackageName) {
+            return PackageManagerService.this.mApexManager.getApksInApex(apexPackageName);
+        }
+
+        @Override
         public void uninstallApex(String packageName, long versionCode, int userId,
                 IntentSender intentSender, int flags) {
             final int callerUid = Binder.getCallingUid();
@@ -23593,6 +23757,30 @@
         return res != null ? res : EmptyArray.STRING;
     }
 
+    @GuardedBy("mLock")
+    public ArrayMap<String, ProcessInfo> getProcessesForUidLocked(int uid) {
+        final int appId = UserHandle.getAppId(uid);
+        final SettingBase obj = mSettings.getSettingLPr(appId);
+        if (obj instanceof SharedUserSetting) {
+            final SharedUserSetting sus = (SharedUserSetting) obj;
+            return PackageInfoUtils.generateProcessInfo(sus.processes, 0);
+        } else if (obj instanceof PackageSetting) {
+            final PackageSetting ps = (PackageSetting) obj;
+            return PackageInfoUtils.generateProcessInfo(ps.pkg.getProcesses(), 0);
+        }
+        return null;
+    }
+
+    @GuardedBy("mLock")
+    public int[] getPermissionGidsLocked(String permissionName, int userId) {
+        BasePermission perm
+                = mPermissionManager.getPermissionSettings().getPermission(permissionName);
+        if (perm != null) {
+            return perm.computeGids(userId);
+        }
+        return null;
+    }
+
     @Override
     public int getRuntimePermissionsVersion(@UserIdInt int userId) {
         Preconditions.checkArgumentNonnegative(userId);
@@ -23618,7 +23806,7 @@
 
     @Nullable
     public PackageSetting getPackageSetting(String packageName) {
-        synchronized (mPackages) {
+        synchronized (mLock) {
             packageName = resolveInternalPackageNameLPr(
                     packageName, PackageManager.VERSION_CODE_HIGHEST);
             return mSettings.mPackages.get(packageName);
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 10e2780..e7f6b89 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -16,6 +16,7 @@
 
 package com.android.server.pm;
 
+import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
 import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
 import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
 import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
@@ -34,7 +35,6 @@
 import android.content.Intent;
 import android.content.IntentSender;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.DataLoaderParams;
 import android.content.pm.FeatureInfo;
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageInstaller;
@@ -82,6 +82,7 @@
 import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
 import android.os.ShellCommand;
 import android.os.SystemClock;
 import android.os.SystemProperties;
@@ -133,14 +134,11 @@
 class PackageManagerShellCommand extends ShellCommand {
     /** Path for streaming APK content */
     private static final String STDIN_PATH = "-";
+    private static final byte[] STDIN_PATH_BYTES = "-".getBytes(StandardCharsets.UTF_8);
     /** Path where ART profiles snapshots are dumped for the shell user */
     private final static String ART_PROFILE_SNAPSHOT_DEBUG_LOCATION = "/data/misc/profman/";
     private static final int DEFAULT_WAIT_MS = 60 * 1000;
 
-    private static final String DATA_LOADER_PACKAGE = "android";
-    private static final String DATA_LOADER_CLASS =
-            "com.android.server.pm.PackageManagerShellCommandDataLoader";
-
     final IPackageManager mInterface;
     final IPermissionManager mPermissionManager;
     final private WeakHashMap<String, Resources> mResourceCache =
@@ -164,7 +162,7 @@
 
         final PrintWriter pw = getOutPrintWriter();
         try {
-            switch(cmd) {
+            switch (cmd) {
                 case "path":
                     return runPath();
                 case "dump":
@@ -477,6 +475,7 @@
                 }
             }
         }
+
         params.sessionParams.setSize(sessionSize);
     }
     /**
@@ -1163,9 +1162,8 @@
     private int runStreamingInstall() throws RemoteException {
         final InstallParams params = makeInstallParams();
         if (params.sessionParams.dataLoaderParams == null) {
-            final DataLoaderParams dataLoaderParams = DataLoaderParams.forStreaming(
-                    new ComponentName(DATA_LOADER_PACKAGE, DATA_LOADER_CLASS), "");
-            params.sessionParams.setDataLoaderParams(dataLoaderParams);
+            params.sessionParams.setDataLoaderParams(
+                    PackageManagerShellCommandDataLoader.getDataLoaderParams(this));
         }
         return doRunInstall(params);
     }
@@ -1176,57 +1174,52 @@
 
     private int doRunInstall(final InstallParams params) throws RemoteException {
         final PrintWriter pw = getOutPrintWriter();
-        final boolean streaming = params.sessionParams.dataLoaderParams != null;
 
-        ArrayList<String> inPaths = getRemainingArgs();
-        if (inPaths.isEmpty()) {
-            inPaths.add(STDIN_PATH);
-        }
-
-        final boolean hasSplits = inPaths.size() > 1;
-
-        if (STDIN_PATH.equals(inPaths.get(0))) {
-            if (hasSplits) {
-                pw.println("Error: can't specify SPLIT(s) along with STDIN");
-                return 1;
-            }
-            if (params.sessionParams.sizeBytes == -1) {
-                pw.println("Error: must either specify a package size or an APK file");
-                return 1;
-            }
-        }
-
+        final boolean isStreaming = params.sessionParams.dataLoaderParams != null;
         final boolean isApex =
                 (params.sessionParams.installFlags & PackageManager.INSTALL_APEX) != 0;
+
+        ArrayList<String> args = getRemainingArgs();
+
+        final boolean fromStdIn = args.isEmpty() || STDIN_PATH.equals(args.get(0));
+        final boolean hasSplits = args.size() > 1;
+
+        if (fromStdIn && params.sessionParams.sizeBytes == -1) {
+            pw.println("Error: must either specify a package size or an APK file");
+            return 1;
+        }
+
         if (isApex && hasSplits) {
             pw.println("Error: can't specify SPLIT(s) for APEX");
             return 1;
         }
 
-        if (!streaming) {
-            setParamsSize(params, inPaths);
+        if (!isStreaming) {
+            if (fromStdIn && hasSplits) {
+                pw.println("Error: can't specify SPLIT(s) along with STDIN");
+                return 1;
+            }
+
+            if (args.isEmpty()) {
+                args.add(STDIN_PATH);
+            } else {
+                setParamsSize(params, args);
+            }
         }
 
         final int sessionId = doCreateSession(params.sessionParams,
                 params.installerPackageName, params.userId);
         boolean abandonSession = true;
         try {
-            for (String inPath : inPaths) {
-                if (streaming) {
-                    String name = new File(inPath).getName();
-                    byte[] metadata = inPath.getBytes(StandardCharsets.UTF_8);
-                    if (doAddFile(sessionId, name, params.sessionParams.sizeBytes, metadata,
-                            false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
-                        return 1;
-                    }
-                } else {
-                    String splitName = hasSplits ? new File(inPath).getName()
-                            : "base." + (isApex ? "apex" : "apk");
-
-                    if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, splitName,
-                            false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
-                        return 1;
-                    }
+            if (isStreaming) {
+                if (doAddFiles(sessionId, args, params.sessionParams.sizeBytes, isApex)
+                        != PackageInstaller.STATUS_SUCCESS) {
+                    return 1;
+                }
+            } else {
+                if (doWriteSplits(sessionId, args, params.sessionParams.sizeBytes, isApex)
+                        != PackageInstaller.STATUS_SUCCESS) {
+                    return 1;
                 }
             }
             if (doCommitSession(sessionId, false /*logSuccess*/)
@@ -1235,7 +1228,7 @@
             }
             abandonSession = false;
 
-            if (!params.sessionParams.isStaged || !params.waitForStagedSessionReady) {
+            if (!params.sessionParams.isStaged || !params.mWaitForStagedSessionReady) {
                 pw.println("Success");
                 return 0;
             }
@@ -1273,7 +1266,7 @@
                         + si.getStagedSessionErrorMessage() + "]");
                 return 1;
             }
-            pw.println("Success");
+            pw.println("Success. Reboot device to apply staged session");
             return 0;
         } finally {
             if (abandonSession) {
@@ -2525,7 +2518,7 @@
         }
 
         name = arg;
-        UserInfo info;
+        UserInfo info = null;
         IUserManager um = IUserManager.Stub.asInterface(
                 ServiceManager.getService(Context.USER_SERVICE));
         IAccountManager accm = IAccountManager.Stub.asInterface(
@@ -2533,17 +2526,22 @@
         if (userType == null) {
             userType = UserInfo.getDefaultUserType(flags);
         }
-        if (UserManager.isUserTypeRestricted(userType)) {
-            // In non-split user mode, userId can only be SYSTEM
-            int parentUserId = userId >= 0 ? userId : UserHandle.USER_SYSTEM;
-            info = um.createRestrictedProfile(name, parentUserId);
-            accm.addSharedAccountsFromParentUser(parentUserId, userId,
-                    (Process.myUid() == Process.ROOT_UID) ? "root" : "com.android.shell");
-        } else if (userId < 0) {
-            info = preCreateOnly ?
-                    um.preCreateUser(userType) : um.createUser(name, userType, flags);
-        } else {
-            info = um.createProfileForUser(name, userType, flags, userId, null);
+        try {
+            if (UserManager.isUserTypeRestricted(userType)) {
+                // In non-split user mode, userId can only be SYSTEM
+                int parentUserId = userId >= 0 ? userId : UserHandle.USER_SYSTEM;
+                info = um.createRestrictedProfileWithThrow(name, parentUserId);
+                accm.addSharedAccountsFromParentUser(parentUserId, userId,
+                        (Process.myUid() == Process.ROOT_UID) ? "root" : "com.android.shell");
+            } else if (userId < 0) {
+                info = preCreateOnly ?
+                        um.preCreateUserWithThrow(userType) :
+                        um.createUserWithThrow(name, userType, flags);
+            } else {
+                info = um.createProfileForUserWithThrow(name, userType, flags, userId, null);
+            }
+        } catch (ServiceSpecificException e) {
+            getErrPrintWriter().println("Error: " + e);
         }
 
         if (info != null) {
@@ -2618,7 +2616,7 @@
         SessionParams sessionParams;
         String installerPackageName;
         int userId = UserHandle.USER_ALL;
-        boolean waitForStagedSessionReady = false;
+        boolean mWaitForStagedSessionReady = true;
         long timeoutMs = DEFAULT_WAIT_MS;
     }
 
@@ -2746,13 +2744,16 @@
                     sessionParams.installFlags |= PackageManager.INSTALL_ENABLE_ROLLBACK;
                     break;
                 case "--wait":
-                    params.waitForStagedSessionReady = true;
+                    params.mWaitForStagedSessionReady = true;
                     try {
                         params.timeoutMs = Long.parseLong(peekNextArg());
                         getNextArg();
                     } catch (NumberFormatException ignore) {
                     }
                     break;
+                case "--no-wait":
+                    params.mWaitForStagedSessionReady = false;
+                    break;
                 default:
                     throw new IllegalArgumentException("Unknown option " + opt);
             }
@@ -2962,23 +2963,71 @@
         return sessionId;
     }
 
-    private int doAddFile(int sessionId, String name, long sizeBytes, byte[] metadata,
-            boolean logSuccess) throws RemoteException {
+    private int doAddFiles(int sessionId, ArrayList<String> args, long sessionSizeBytes,
+            boolean isApex) throws RemoteException {
         PackageInstaller.Session session = new PackageInstaller.Session(
                 mInterface.getPackageInstaller().openSession(sessionId));
         try {
-            session.addFile(name, sizeBytes, metadata);
-
-            if (logSuccess) {
-                getOutPrintWriter().println("Success");
+            // 1. Single file from stdin.
+            if (args.isEmpty() || STDIN_PATH.equals(args.get(0))) {
+                String name = "base." + (isApex ? "apex" : "apk");
+                session.addFile(LOCATION_DATA_APP, name, sessionSizeBytes, STDIN_PATH_BYTES, null);
+                return 0;
             }
 
+            for (String arg : args) {
+                final int delimLocation = arg.indexOf(':');
+
+                // 2. File with specified size read from stdin.
+                if (delimLocation != -1) {
+                    String name = arg.substring(0, delimLocation);
+                    String sizeStr = arg.substring(delimLocation + 1);
+                    long sizeBytes;
+
+                    if (TextUtils.isEmpty(name)) {
+                        getErrPrintWriter().println("Empty file name in: " + arg);
+                        return 1;
+                    }
+                    try {
+                        sizeBytes = Long.parseUnsignedLong(sizeStr);
+                    } catch (NumberFormatException e) {
+                        getErrPrintWriter().println("Unable to parse size from: " + arg);
+                        return 1;
+                    }
+
+                    session.addFile(LOCATION_DATA_APP, name, sizeBytes, STDIN_PATH_BYTES, null);
+                    continue;
+                }
+
+                // 3. Local file.
+                final String inPath = arg;
+
+                String name = new File(inPath).getName();
+                byte[] metadata = inPath.getBytes(StandardCharsets.UTF_8);
+
+                session.addFile(LOCATION_DATA_APP, name, -1, metadata, null);
+            }
             return 0;
         } finally {
             IoUtils.closeQuietly(session);
         }
     }
 
+    private int doWriteSplits(int sessionId, ArrayList<String> splitPaths, long sessionSizeBytes,
+            boolean isApex) throws RemoteException {
+        final boolean multipleSplits = splitPaths.size() > 1;
+        for (String splitPath : splitPaths) {
+            String splitName = multipleSplits ? new File(splitPath).getName()
+                    : "base." + (isApex ? "apex" : "apk");
+
+            if (doWriteSplit(sessionId, splitPath, sessionSizeBytes, splitName,
+                    false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
+                return 1;
+            }
+        }
+        return 0;
+    }
+
     private int doWriteSplit(int sessionId, String inPath, long sizeBytes, String splitName,
             boolean logSuccess) throws RemoteException {
         PackageInstaller.Session session = null;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
index 1ee9ab8..281c44a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
@@ -17,18 +17,22 @@
 package com.android.server.pm;
 
 import android.annotation.NonNull;
+import android.content.ComponentName;
 import android.content.pm.DataLoaderParams;
 import android.content.pm.InstallationFile;
 import android.os.ParcelFileDescriptor;
+import android.os.ShellCommand;
 import android.service.dataloader.DataLoaderService;
 import android.text.TextUtils;
 import android.util.Slog;
+import android.util.SparseArray;
 
 import libcore.io.IoUtils;
 
-import java.io.File;
 import java.io.IOException;
+import java.lang.ref.WeakReference;
 import java.nio.charset.StandardCharsets;
+import java.security.SecureRandom;
 import java.util.Collection;
 
 /**
@@ -37,41 +41,109 @@
 public class PackageManagerShellCommandDataLoader extends DataLoaderService {
     public static final String TAG = "PackageManagerShellCommandDataLoader";
 
-    static class DataLoader implements DataLoaderService.DataLoader {
-        private ParcelFileDescriptor mInFd = null;
-        private FileSystemConnector mConnector = null;
+    private static final String PACKAGE = "android";
+    private static final String CLASS = PackageManagerShellCommandDataLoader.class.getName();
 
-        private static final String STDIN_PATH = "-";
+    static final SecureRandom sRandom = new SecureRandom();
+    static final SparseArray<WeakReference<ShellCommand>> sShellCommands = new SparseArray<>();
+
+    private static final char ARGS_DELIM = '&';
+    private static final String SHELL_COMMAND_ID_PREFIX = "shellCommandId=";
+    private static final int INVALID_SHELL_COMMAND_ID = -1;
+    private static final int TOO_MANY_PENDING_SHELL_COMMANDS = 10;
+
+    private static final String STDIN_PATH = "-";
+
+    static DataLoaderParams getDataLoaderParams(ShellCommand shellCommand) {
+        int commandId;
+        synchronized (sShellCommands) {
+            // Clean up old references.
+            for (int i = sShellCommands.size() - 1; i >= 0; i--) {
+                WeakReference<ShellCommand> oldRef = sShellCommands.valueAt(i);
+                if (oldRef.get() == null) {
+                    sShellCommands.removeAt(i);
+                }
+            }
+
+            // Sanity check.
+            if (sShellCommands.size() > TOO_MANY_PENDING_SHELL_COMMANDS) {
+                Slog.e(TAG, "Too many pending shell commands: " + sShellCommands.size());
+            }
+
+            // Generate new id and put ref to the array.
+            do {
+                commandId = sRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
+            } while (sShellCommands.contains(commandId));
+
+            sShellCommands.put(commandId, new WeakReference<>(shellCommand));
+        }
+
+        final String args = SHELL_COMMAND_ID_PREFIX + commandId;
+        return DataLoaderParams.forStreaming(new ComponentName(PACKAGE, CLASS), args);
+    }
+
+    private static int extractShellCommandId(String args) {
+        int sessionIdIdx = args.indexOf(SHELL_COMMAND_ID_PREFIX);
+        if (sessionIdIdx < 0) {
+            Slog.e(TAG, "Missing shell command id param.");
+            return INVALID_SHELL_COMMAND_ID;
+        }
+        sessionIdIdx += SHELL_COMMAND_ID_PREFIX.length();
+        int delimIdx = args.indexOf(ARGS_DELIM, sessionIdIdx);
+        try {
+            if (delimIdx < 0) {
+                return Integer.parseInt(args.substring(sessionIdIdx));
+            } else {
+                return Integer.parseInt(args.substring(sessionIdIdx, delimIdx));
+            }
+        } catch (NumberFormatException e) {
+            Slog.e(TAG, "Incorrect shell command id format.", e);
+            return INVALID_SHELL_COMMAND_ID;
+        }
+    }
+
+    static class DataLoader implements DataLoaderService.DataLoader {
+        private DataLoaderParams mParams = null;
+        private FileSystemConnector mConnector = null;
 
         @Override
         public boolean onCreate(@NonNull DataLoaderParams dataLoaderParams,
                 @NonNull FileSystemConnector connector) {
+            mParams = dataLoaderParams;
             mConnector = connector;
             return true;
         }
+
         @Override
-        public boolean onPrepareImage(Collection<InstallationFile> addedFiles,
-                Collection<String> removedFiles) {
+        public boolean onPrepareImage(@NonNull Collection<InstallationFile> addedFiles,
+                @NonNull Collection<String> removedFiles) {
+            final int commandId = extractShellCommandId(mParams.getArguments());
+            if (commandId == INVALID_SHELL_COMMAND_ID) {
+                return false;
+            }
+
+            final WeakReference<ShellCommand> shellCommandRef;
+            synchronized (sShellCommands) {
+                shellCommandRef = sShellCommands.get(commandId, null);
+            }
+            final ShellCommand shellCommand =
+                    shellCommandRef != null ? shellCommandRef.get() : null;
+            if (shellCommand == null) {
+                Slog.e(TAG, "Missing shell command.");
+                return false;
+            }
             try {
                 for (InstallationFile fileInfo : addedFiles) {
                     String filePath = new String(fileInfo.getMetadata(), StandardCharsets.UTF_8);
                     if (STDIN_PATH.equals(filePath) || TextUtils.isEmpty(filePath)) {
-                        // TODO(b/146080380): add support for STDIN installations.
-                        if (mInFd == null) {
-                            Slog.e(TAG, "Invalid stdin file descriptor.");
-                            return false;
-                        }
-                        ParcelFileDescriptor inFd = ParcelFileDescriptor.dup(
-                                mInFd.getFileDescriptor());
+                        final ParcelFileDescriptor inFd = ParcelFileDescriptor.dup(
+                                shellCommand.getInFileDescriptor());
                         mConnector.writeData(fileInfo.getName(), 0, fileInfo.getSize(), inFd);
                     } else {
-                        File localFile = new File(filePath);
                         ParcelFileDescriptor incomingFd = null;
                         try {
-                            // TODO(b/146080380): open files via callback into shell command.
-                            incomingFd = ParcelFileDescriptor.open(localFile,
-                                    ParcelFileDescriptor.MODE_READ_ONLY);
-                            mConnector.writeData(fileInfo.getName(), 0, localFile.length(),
+                            incomingFd = shellCommand.openFileForSystem(filePath, "r");
+                            mConnector.writeData(fileInfo.getName(), 0, incomingFd.getStatSize(),
                                     incomingFd);
                         } finally {
                             IoUtils.closeQuietly(incomingFd);
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 0c0b93b..f1ac0af 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -41,6 +41,7 @@
 import java.io.File;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
@@ -312,12 +313,21 @@
     }
 
     void setOverlayPaths(List<String> overlayPaths, int userId) {
-        modifyUserState(userId).overlayPaths = overlayPaths == null ? null :
-            overlayPaths.toArray(new String[overlayPaths.size()]);
+        modifyUserState(userId).setOverlayPaths(overlayPaths == null ? null :
+            overlayPaths.toArray(new String[overlayPaths.size()]));
     }
 
     String[] getOverlayPaths(int userId) {
-        return readUserState(userId).overlayPaths;
+        return readUserState(userId).getOverlayPaths();
+    }
+
+    void setOverlayPathsForLibrary(String libName, List<String> overlayPaths, int userId) {
+        modifyUserState(userId).setSharedLibraryOverlayPaths(libName,
+                overlayPaths == null ? null : overlayPaths.toArray(new String[0]));
+    }
+
+    Map<String, String[]> getOverlayPathsForLibrary(int userId) {
+        return readUserState(userId).getSharedLibraryOverlayPaths();
     }
 
     /** Only use for testing. Do NOT use in production code. */
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index f9a3361..5d948b2 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -92,6 +92,8 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.JournaledFile;
 import com.android.internal.util.XmlUtils;
+import com.android.permission.persistence.RuntimePermissionsPersistence;
+import com.android.permission.persistence.RuntimePermissionsState;
 import com.android.server.LocalServices;
 import com.android.server.pm.Installer.InstallerException;
 import com.android.server.pm.permission.BasePermission;
@@ -2819,6 +2821,9 @@
         if (installSource.initiatingPackageName != null) {
             serializer.attribute(null, "installInitiator", installSource.initiatingPackageName);
         }
+        if (installSource.isInitiatingPackageUninstalled) {
+            serializer.attribute(null, "installInitiatorUninstalled", "true");
+        }
         if (installSource.originatingPackageName != null) {
             serializer.attribute(null, "installOriginator", installSource.originatingPackageName);
         }
@@ -2836,6 +2841,11 @@
 
         pkg.signatures.writeXml(serializer, "sigs", mPastSignatures);
 
+        if (installSource.initiatingPackageSignatures != null) {
+            installSource.initiatingPackageSignatures.writeXml(
+                    serializer, "install-initiator-sigs", mPastSignatures);
+        }
+
         writePermissionsLPr(serializer, pkg.getPermissionsState().getInstallPermissionStates());
 
         writeSigningKeySetLPr(serializer, pkg.keySetData);
@@ -3117,6 +3127,10 @@
         return true;
     }
 
+    void readPermissionStateForUserSyncLPr(@UserIdInt int userId) {
+        mRuntimePermissionsPersistence.readStateForUserSyncLPr(userId);
+    }
+
     void applyDefaultPreferredAppsLPw(int userId) {
         // First pull data from any pre-installed apps.
         final PackageManagerInternal pmInternal =
@@ -3571,6 +3585,7 @@
         String isOrphaned = null;
         String installOriginatingPackageName = null;
         String installInitiatingPackageName = null;
+        String installInitiatorUninstalled = null;
         String volumeUuid = null;
         String categoryHintString = null;
         String updateAvailable = null;
@@ -3616,6 +3631,8 @@
             isOrphaned = parser.getAttributeValue(null, "isOrphaned");
             installInitiatingPackageName = parser.getAttributeValue(null, "installInitiator");
             installOriginatingPackageName = parser.getAttributeValue(null, "installOriginator");
+            installInitiatorUninstalled = parser.getAttributeValue(null,
+                    "installInitiatorUninstalled");
             volumeUuid = parser.getAttributeValue(null, "volumeUuid");
             categoryHintString = parser.getAttributeValue(null, "categoryHint");
             if (categoryHintString != null) {
@@ -3772,7 +3789,8 @@
             packageSetting.uidError = "true".equals(uidError);
             InstallSource installSource = InstallSource.create(
                     installInitiatingPackageName, installOriginatingPackageName,
-                    installerPackageName, "true".equals(isOrphaned));
+                    installerPackageName, "true".equals(isOrphaned),
+                    "true".equals(installInitiatorUninstalled));
             packageSetting.installSource = installSource;
             packageSetting.volumeUuid = volumeUuid;
             packageSetting.categoryHint = categoryHint;
@@ -3849,6 +3867,11 @@
                         mKeySetRefs.put(id, 1);
                     }
                     packageSetting.keySetData.addDefinedKeySet(id, alias);
+                } else if (tagName.equals("install-initiator-sigs")) {
+                    final PackageSignatures signatures = new PackageSignatures();
+                    signatures.readXml(parser, mPastSignatures);
+                    packageSetting.installSource =
+                            packageSetting.installSource.setInitiatingPackageSignatures(signatures);
                 } else if (tagName.equals(TAG_DOMAIN_VERIFICATION)) {
                     readDomainVerificationLPw(parser, packageSetting);
                 } else {
@@ -4725,6 +4748,22 @@
                 }
             }
 
+            Map<String, String[]> sharedLibraryOverlayPaths =
+                    ps.getOverlayPathsForLibrary(user.id);
+            if (sharedLibraryOverlayPaths != null) {
+                for (Map.Entry<String, String[]> libOverlayPaths :
+                        sharedLibraryOverlayPaths.entrySet()) {
+                    if (libOverlayPaths.getValue() == null) {
+                        continue;
+                    }
+                    pw.print(prefix); pw.print("  ");
+                    pw.print(libOverlayPaths.getKey()); pw.println(" overlay paths:");
+                    for (String path : libOverlayPaths.getValue()) {
+                        pw.print(prefix); pw.print("    "); pw.println(path);
+                    }
+                }
+            }
+
             String lastDisabledAppCaller = ps.getLastDisabledAppCaller(user.id);
             if (lastDisabledAppCaller != null) {
                 pw.print(prefix); pw.print("    lastDisabledCaller: ");
@@ -5059,6 +5098,9 @@
         private static final int UPGRADE_VERSION = -1;
         private static final int INITIAL_VERSION = 0;
 
+        private final RuntimePermissionsPersistence mPersistence =
+                RuntimePermissionsPersistence.createInstance();
+
         private final Handler mHandler = new MyHandler();
 
         private final Object mPersistenceLock;
@@ -5148,98 +5190,72 @@
         }
 
         private void writePermissionsSync(int userId) {
-            AtomicFile destination = new AtomicFile(getUserRuntimePermissionsFile(userId),
-                    "package-perms-" + userId);
-
-            ArrayMap<String, List<PermissionState>> permissionsForPackage = new ArrayMap<>();
-            ArrayMap<String, List<PermissionState>> permissionsForSharedUser = new ArrayMap<>();
-
+            RuntimePermissionsState runtimePermissions;
             synchronized (mPersistenceLock) {
                 mWriteScheduled.delete(userId);
 
-                final int packageCount = mPackages.size();
-                for (int i = 0; i < packageCount; i++) {
+                int version = mVersions.get(userId, INITIAL_VERSION);
+
+                String fingerprint = mFingerprints.get(userId);
+
+                Map<String, List<RuntimePermissionsState.PermissionState>> packagePermissions =
+                        new ArrayMap<>();
+                int packagesSize = mPackages.size();
+                for (int i = 0; i < packagesSize; i++) {
                     String packageName = mPackages.keyAt(i);
                     PackageSetting packageSetting = mPackages.valueAt(i);
                     if (packageSetting.sharedUser == null) {
-                        PermissionsState permissionsState = packageSetting.getPermissionsState();
-                        List<PermissionState> permissionsStates = permissionsState
-                                .getRuntimePermissionStates(userId);
-                        if (!permissionsStates.isEmpty()) {
-                            permissionsForPackage.put(packageName, permissionsStates);
+                        List<RuntimePermissionsState.PermissionState> permissions =
+                                getPermissionsFromPermissionsState(
+                                        packageSetting.getPermissionsState(), userId);
+                        if (permissions != null) {
+                            packagePermissions.put(packageName, permissions);
                         }
                     }
                 }
 
-                final int sharedUserCount = mSharedUsers.size();
-                for (int i = 0; i < sharedUserCount; i++) {
+                Map<String, List<RuntimePermissionsState.PermissionState>> sharedUserPermissions =
+                        new ArrayMap<>();
+                final int sharedUsersSize = mSharedUsers.size();
+                for (int i = 0; i < sharedUsersSize; i++) {
                     String sharedUserName = mSharedUsers.keyAt(i);
-                    SharedUserSetting sharedUser = mSharedUsers.valueAt(i);
-                    PermissionsState permissionsState = sharedUser.getPermissionsState();
-                    List<PermissionState> permissionsStates = permissionsState
-                            .getRuntimePermissionStates(userId);
-                    if (!permissionsStates.isEmpty()) {
-                        permissionsForSharedUser.put(sharedUserName, permissionsStates);
+                    SharedUserSetting sharedUserSetting = mSharedUsers.valueAt(i);
+                    List<RuntimePermissionsState.PermissionState> permissions =
+                            getPermissionsFromPermissionsState(
+                                    sharedUserSetting.getPermissionsState(), userId);
+                    if (permissions != null) {
+                        sharedUserPermissions.put(sharedUserName, permissions);
                     }
                 }
+
+                runtimePermissions = new RuntimePermissionsState(version, fingerprint,
+                        packagePermissions, sharedUserPermissions);
             }
 
-            FileOutputStream out = null;
-            try {
-                out = destination.startWrite();
+            mPersistence.write(runtimePermissions, UserHandle.of(userId));
+        }
 
-                XmlSerializer serializer = Xml.newSerializer();
-                serializer.setOutput(out, StandardCharsets.UTF_8.name());
-                serializer.setFeature(
-                        "http://xmlpull.org/v1/doc/features.html#indent-output", true);
-                serializer.startDocument(null, true);
-
-                serializer.startTag(null, TAG_RUNTIME_PERMISSIONS);
-
-                final int version = mVersions.get(userId, INITIAL_VERSION);
-                serializer.attribute(null, ATTR_VERSION, Integer.toString(version));
-
-                String fingerprint = mFingerprints.get(userId);
-                if (fingerprint != null) {
-                    serializer.attribute(null, ATTR_FINGERPRINT, fingerprint);
-                }
-
-                final int packageCount = permissionsForPackage.size();
-                for (int i = 0; i < packageCount; i++) {
-                    String packageName = permissionsForPackage.keyAt(i);
-                    List<PermissionState> permissionStates = permissionsForPackage.valueAt(i);
-                    serializer.startTag(null, TAG_PACKAGE);
-                    serializer.attribute(null, ATTR_NAME, packageName);
-                    writePermissions(serializer, permissionStates);
-                    serializer.endTag(null, TAG_PACKAGE);
-                }
-
-                final int sharedUserCount = permissionsForSharedUser.size();
-                for (int i = 0; i < sharedUserCount; i++) {
-                    String packageName = permissionsForSharedUser.keyAt(i);
-                    List<PermissionState> permissionStates = permissionsForSharedUser.valueAt(i);
-                    serializer.startTag(null, TAG_SHARED_USER);
-                    serializer.attribute(null, ATTR_NAME, packageName);
-                    writePermissions(serializer, permissionStates);
-                    serializer.endTag(null, TAG_SHARED_USER);
-                }
-
-                serializer.endTag(null, TAG_RUNTIME_PERMISSIONS);
-
-                serializer.endDocument();
-                destination.finishWrite(out);
-
-                if (Build.FINGERPRINT.equals(fingerprint)) {
-                    mDefaultPermissionsGranted.put(userId, true);
-                }
-            // Any error while writing is fatal.
-            } catch (Throwable t) {
-                Slog.wtf(PackageManagerService.TAG,
-                        "Failed to write settings, restoring backup", t);
-                destination.failWrite(out);
-            } finally {
-                IoUtils.closeQuietly(out);
+        @Nullable
+        private List<RuntimePermissionsState.PermissionState> getPermissionsFromPermissionsState(
+                @NonNull PermissionsState permissionsState, @UserIdInt int userId) {
+            List<PermissionState> permissionStates = permissionsState.getRuntimePermissionStates(
+                    userId);
+            if (permissionStates.isEmpty()) {
+                return null;
             }
+
+            List<RuntimePermissionsState.PermissionState> permissions =
+                    new ArrayList<>();
+            int permissionStatesSize = permissionStates.size();
+            for (int i = 0; i < permissionStatesSize; i++) {
+                PermissionState permissionState = permissionStates.get(i);
+
+                RuntimePermissionsState.PermissionState permission =
+                        new RuntimePermissionsState.PermissionState(permissionState.getName(),
+                                permissionState.isGranted(), permissionState.getFlags());
+                permissions.add(permission);
+            }
+            return permissions;
         }
 
         @GuardedBy("Settings.this.mLock")
@@ -5274,11 +5290,88 @@
         }
 
         public void deleteUserRuntimePermissionsFile(int userId) {
-            getUserRuntimePermissionsFile(userId).delete();
+            mPersistence.delete(UserHandle.of(userId));
         }
 
         @GuardedBy("Settings.this.mLock")
         public void readStateForUserSyncLPr(int userId) {
+            RuntimePermissionsState runtimePermissions = mPersistence.read(UserHandle.of(userId));
+            if (runtimePermissions == null) {
+                readLegacyStateForUserSyncLPr(userId);
+                writePermissionsForUserAsyncLPr(userId);
+                return;
+            }
+
+            // If the runtime permissions file exists but the version is not set this is
+            // an upgrade from P->Q. Hence mark it with the special UPGRADE_VERSION.
+            int version = runtimePermissions.getVersion();
+            if (version == RuntimePermissionsState.NO_VERSION) {
+                version = UPGRADE_VERSION;
+            }
+            mVersions.put(userId, version);
+
+            String fingerprint = runtimePermissions.getFingerprint();
+            mFingerprints.put(userId, fingerprint);
+            boolean defaultPermissionsGranted = Build.FINGERPRINT.equals(fingerprint);
+            mDefaultPermissionsGranted.put(userId, defaultPermissionsGranted);
+
+            for (Map.Entry<String, List<RuntimePermissionsState.PermissionState>> entry
+                    : runtimePermissions.getPackagePermissions().entrySet()) {
+                String packageName = entry.getKey();
+                List<RuntimePermissionsState.PermissionState> permissions = entry.getValue();
+
+                PackageSetting packageSetting = mPackages.get(packageName);
+                if (packageSetting == null) {
+                    Slog.w(PackageManagerService.TAG, "Unknown package:" + packageName);
+                    continue;
+                }
+                readPermissionsStateLpr(permissions, packageSetting.getPermissionsState(), userId);
+            }
+
+            for (Map.Entry<String, List<RuntimePermissionsState.PermissionState>> entry
+                    : runtimePermissions.getSharedUserPermissions().entrySet()) {
+                String sharedUserName = entry.getKey();
+                List<RuntimePermissionsState.PermissionState> permissions = entry.getValue();
+
+                SharedUserSetting sharedUserSetting = mSharedUsers.get(sharedUserName);
+                if (sharedUserSetting == null) {
+                    Slog.w(PackageManagerService.TAG, "Unknown shared user:" + sharedUserName);
+                    continue;
+                }
+                readPermissionsStateLpr(permissions, sharedUserSetting.getPermissionsState(),
+                        userId);
+            }
+        }
+
+        private void readPermissionsStateLpr(
+                @NonNull List<RuntimePermissionsState.PermissionState> permissions,
+                @NonNull PermissionsState permissionsState, @UserIdInt int userId) {
+            int permissionsSize = permissions.size();
+            for (int i = 0; i < permissionsSize; i++) {
+                RuntimePermissionsState.PermissionState permission = permissions.get(i);
+
+                String name = permission.getName();
+                BasePermission basePermission = mPermissions.getPermission(name);
+                if (basePermission == null) {
+                    Slog.w(PackageManagerService.TAG, "Unknown permission:" + name);
+                    continue;
+                }
+                boolean granted = permission.isGranted();
+                int flags = permission.getFlags();
+
+                if (granted) {
+                    permissionsState.grantRuntimePermission(basePermission, userId);
+                    permissionsState.updatePermissionFlags(basePermission, userId,
+                            PackageManager.MASK_PERMISSION_FLAGS_ALL, flags);
+                } else {
+                    permissionsState.updatePermissionFlags(basePermission, userId,
+                            PackageManager.MASK_PERMISSION_FLAGS_ALL, flags);
+                }
+            }
+        }
+
+        @GuardedBy("Settings.this.mLock")
+        private void readLegacyStateForUserSyncLPr(int userId) {
             File permissionsFile = getUserRuntimePermissionsFile(userId);
             if (!permissionsFile.exists()) {
                 return;
@@ -5398,19 +5491,6 @@
             }
         }
 
-        private void writePermissions(XmlSerializer serializer,
-                List<PermissionState> permissionStates) throws IOException {
-            for (PermissionState permissionState : permissionStates) {
-                serializer.startTag(null, TAG_ITEM);
-                serializer.attribute(null, ATTR_NAME,permissionState.getName());
-                serializer.attribute(null, ATTR_GRANTED,
-                        String.valueOf(permissionState.isGranted()));
-                serializer.attribute(null, ATTR_FLAGS,
-                        Integer.toHexString(permissionState.getFlags()));
-                serializer.endTag(null, TAG_ITEM);
-            }
-        }
-
         private final class MyHandler extends Handler {
             public MyHandler() {
                 super(BackgroundThread.getHandler().getLooper());
diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java
index 0a42ccf..b9bb9e0 100644
--- a/services/core/java/com/android/server/pm/SharedUserSetting.java
+++ b/services/core/java/com/android/server/pm/SharedUserSetting.java
@@ -19,7 +19,9 @@
 import android.annotation.Nullable;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.parsing.AndroidPackage;
+import android.content.pm.parsing.ComponentParseUtils;
 import android.service.pm.PackageServiceDumpProto;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.proto.ProtoOutputStream;
 
@@ -51,6 +53,8 @@
     final PackageSignatures signatures = new PackageSignatures();
     Boolean signaturesChanged;
 
+    ArrayMap<String, ComponentParseUtils.ParsedProcess> processes;
+
     SharedUserSetting(String _name, int _pkgFlags, int _pkgPrivateFlags) {
         super(_pkgFlags, _pkgPrivateFlags);
         uidFlags =  _pkgFlags;
@@ -72,6 +76,25 @@
         proto.end(token);
     }
 
+    void addProcesses(ArrayMap<String, ComponentParseUtils.ParsedProcess> newProcs) {
+        if (newProcs != null) {
+            final int numProcs = newProcs.size();
+            if (processes == null) {
+                processes = new ArrayMap<>(numProcs);
+            }
+            for (int i = 0; i < numProcs; i++) {
+                ComponentParseUtils.ParsedProcess newProc = newProcs.valueAt(i);
+                ComponentParseUtils.ParsedProcess proc = processes.get(newProc.name);
+                if (proc == null) {
+                    proc = new ComponentParseUtils.ParsedProcess(newProc);
+                    processes.put(newProc.name, proc);
+                } else {
+                    proc.addStateFrom(newProc);
+                }
+            }
+        }
+    }
+
     boolean removePackage(PackageSetting packageSetting) {
         if (!packages.remove(packageSetting)) {
             return false;
@@ -91,6 +114,8 @@
             }
             setPrivateFlags(aggregatedPrivateFlags);
         }
+        // recalculate processes.
+        updateProcesses();
         return true;
     }
 
@@ -104,6 +129,9 @@
             setFlags(this.pkgFlags | packageSetting.pkgFlags);
             setPrivateFlags(this.pkgPrivateFlags | packageSetting.pkgPrivateFlags);
         }
+        if (packageSetting.pkg != null) {
+            addProcesses(packageSetting.pkg.getProcesses());
+        }
     }
 
     public @Nullable List<AndroidPackage> getPackages() {
@@ -148,6 +176,16 @@
         }
     }
 
+    /**
+     * Update tracked data about processes based on all known packages in the shared user ID.
+     */
+    public void updateProcesses() {
+        processes = null;
+        for (int i = packages.size() - 1; i >= 0; i--) {
+            addProcesses(packages.valueAt(i).pkg.getProcesses());
+        }
+    }
+
     /** Returns userIds which doesn't have any packages with this sharedUserId */
     public int[] getNotInstalledUserIds() {
         int[] excludedUserIds = null;
@@ -176,6 +214,17 @@
         this.packages.clear();
         this.packages.addAll(sharedUser.packages);
         this.signaturesChanged = sharedUser.signaturesChanged;
+        if (sharedUser.processes != null) {
+            final int numProcs = sharedUser.processes.size();
+            this.processes = new ArrayMap<>(numProcs);
+            for (int i = 0; i < numProcs; i++) {
+                ComponentParseUtils.ParsedProcess proc =
+                        new ComponentParseUtils.ParsedProcess(sharedUser.processes.valueAt(i));
+                this.processes.put(proc.name, proc);
+            }
+        } else {
+            this.processes = null;
+        }
         return this;
     }
 }
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 9e462cd..74c98f9 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -33,11 +33,13 @@
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.PackageParserException;
 import android.content.pm.PackageParser.SigningDetails;
 import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
 import android.content.pm.ParceledListSlice;
+import android.content.pm.parsing.AndroidPackage;
 import android.content.rollback.IRollbackManager;
 import android.content.rollback.RollbackInfo;
 import android.content.rollback.RollbackManager;
@@ -47,9 +49,12 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
+import android.os.ParcelableException;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.UserManagerInternal;
 import android.os.storage.IStorageManager;
 import android.os.storage.StorageManager;
 import android.util.IntArray;
@@ -61,6 +66,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.content.PackageHelper;
 import com.android.internal.os.BackgroundThread;
+import com.android.server.LocalServices;
 
 import java.io.File;
 import java.io.IOException;
@@ -93,10 +99,11 @@
     @GuardedBy("mStagedSessions")
     private final SparseIntArray mSessionRollbackIds = new SparseIntArray();
 
-    StagingManager(PackageInstallerService pi, ApexManager am, Context context) {
+    StagingManager(PackageInstallerService pi, Context context) {
         mPi = pi;
-        mApexManager = am;
         mContext = context;
+
+        mApexManager = ApexManager.getInstance();
         mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
         mPreRebootVerificationHandler = new PreRebootVerificationHandler(
                 BackgroundThread.get().getLooper());
@@ -334,6 +341,88 @@
         return PackageHelper.getStorageManager().needsCheckpoint();
     }
 
+    /**
+     * Apks inside apex are not installed using apk-install flow. They are scanned from the system
+     * directory directly by PackageManager, as such, RollbackManager need to handle their data
+     * separately here.
+     */
+    private void snapshotAndRestoreApkInApexUserData(PackageInstallerSession session) {
+        // We want to process apks inside apex. So current session needs to contain apex.
+        if (!sessionContainsApex(session)) {
+            return;
+        }
+
+        boolean doSnapshotOrRestore =
+                (session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0
+                || session.params.installReason == PackageManager.INSTALL_REASON_ROLLBACK;
+        if (!doSnapshotOrRestore) {
+            return;
+        }
+
+        // Find all the apex sessions that needs processing
+        List<PackageInstallerSession> apexSessions = new ArrayList<>();
+        if (session.isMultiPackage()) {
+            List<PackageInstallerSession> childrenSessions = new ArrayList<>();
+            synchronized (mStagedSessions) {
+                for (int childSessionId : session.getChildSessionIds()) {
+                    PackageInstallerSession childSession = mStagedSessions.get(childSessionId);
+                    if (childSession != null) {
+                        childrenSessions.add(childSession);
+                    }
+                }
+            }
+            for (PackageInstallerSession childSession : childrenSessions) {
+                if (sessionContainsApex(childSession)) {
+                    apexSessions.add(childSession);
+                }
+            }
+        } else {
+            apexSessions.add(session);
+        }
+
+        // For each apex, process the apks inside it
+        for (PackageInstallerSession apexSession : apexSessions) {
+            List<String> apksInApex = mApexManager.getApksInApex(apexSession.getPackageName());
+            for (String apk: apksInApex) {
+                snapshotAndRestoreApkInApexUserData(apk);
+            }
+        }
+    }
+
+    private void snapshotAndRestoreApkInApexUserData(String packageName) {
+        IRollbackManager rm = IRollbackManager.Stub.asInterface(
+                    ServiceManager.getService(Context.ROLLBACK_SERVICE));
+
+        PackageManagerInternal mPmi = LocalServices.getService(PackageManagerInternal.class);
+        AndroidPackage pkg = mPmi.getPackage(packageName);
+        if (pkg == null) {
+            Slog.e(TAG, "Could not find package: " + packageName
+                    + "for snapshotting/restoring user data.");
+            return;
+        }
+        final String seInfo = pkg.getSeInfo();
+        final UserManagerInternal um = LocalServices.getService(UserManagerInternal.class);
+        final int[] allUsers = um.getUserIds();
+
+        int appId = -1;
+        long ceDataInode = -1;
+        final PackageSetting ps = (PackageSetting) mPmi.getPackageSetting(packageName);
+        if (ps != null && rm != null) {
+            appId = ps.appId;
+            ceDataInode = ps.getCeDataInode(UserHandle.USER_SYSTEM);
+            // NOTE: We ignore the user specified in the InstallParam because we know this is
+            // an update, and hence need to restore data for all installed users.
+            final int[] installedUsers = ps.queryInstalledUsers(allUsers, true);
+
+            try {
+                rm.snapshotAndRestoreUserData(packageName, installedUsers, appId, ceDataInode,
+                        seInfo, 0 /*token*/);
+            } catch (RemoteException re) {
+                Slog.e(TAG, "Error snapshotting/restoring user data: " + re);
+            }
+        }
+    }
+
     private void resumeSession(@NonNull PackageInstallerSession session) {
         Slog.d(TAG, "Resuming session " + session.sessionId);
 
@@ -407,6 +496,7 @@
                 abortCheckpoint();
                 return;
             }
+            snapshotAndRestoreApkInApexUserData(session);
             Slog.i(TAG, "APEX packages in session " + session.sessionId
                     + " were successfully activated. Proceeding with APK packages, if any");
         }
@@ -481,18 +571,17 @@
         } else {
             params.installFlags |= PackageManager.INSTALL_DISABLE_VERIFICATION;
         }
-        int apkSessionId = mPi.createSession(
-                params, originalSession.getInstallerPackageName(),
-                0 /* UserHandle.SYSTEM */);
-        PackageInstallerSession apkSession = mPi.getSession(apkSessionId);
-
         try {
+            int apkSessionId = mPi.createSession(
+                    params, originalSession.getInstallerPackageName(),
+                    0 /* UserHandle.SYSTEM */);
+            PackageInstallerSession apkSession = mPi.getSession(apkSessionId);
             apkSession.open();
             for (String apkFilePath : apkFilePaths) {
                 File apkFile = new File(apkFilePath);
                 ParcelFileDescriptor pfd = ParcelFileDescriptor.open(apkFile,
                         ParcelFileDescriptor.MODE_READ_ONLY);
-                long sizeBytes = pfd.getStatSize();
+                long sizeBytes = (pfd == null) ? -1 : pfd.getStatSize();
                 if (sizeBytes < 0) {
                     Slog.e(TAG, "Unable to get size of: " + apkFilePath);
                     throw new PackageManagerException(errorCode,
@@ -500,11 +589,11 @@
                 }
                 apkSession.write(apkFile.getName(), 0, sizeBytes, pfd);
             }
-        } catch (IOException e) {
+            return apkSession;
+        } catch (IOException | ParcelableException e) {
             Slog.e(TAG, "Failure to install APK staged session " + originalSession.sessionId, e);
-            throw new PackageManagerException(errorCode, "Failed to write APK session", e);
+            throw new PackageManagerException(errorCode, "Failed to create/write APK session", e);
         }
-        return apkSession;
     }
 
     /**
@@ -529,7 +618,7 @@
                         Arrays.stream(session.getChildSessionIds())
                                 // Retrieve cached sessions matching ids.
                                 .mapToObj(i -> mStagedSessions.get(i))
-                                // Filter only the ones containing APKs.s
+                                // Filter only the ones containing APKs.
                                 .filter(childSession -> !isApexSession(childSession))
                                 .collect(Collectors.toList());
             }
@@ -800,7 +889,7 @@
         return false;
     }
 
-    void restoreSession(@NonNull PackageInstallerSession session) {
+    void restoreSession(@NonNull PackageInstallerSession session, boolean isDeviceUpgrading) {
         PackageInstallerSession sessionToResume = session;
         synchronized (mStagedSessions) {
             mStagedSessions.append(session.sessionId, session);
@@ -817,6 +906,13 @@
                 }
             }
         }
+        // The preconditions used during pre-reboot verification might have changed when device
+        // is upgrading. Updated staged sessions to activation failed before we resume the session.
+        if (isDeviceUpgrading && !sessionToResume.isStagedAndInTerminalState()) {
+            sessionToResume.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+                        "Build fingerprint has changed");
+            return;
+        }
         checkStateAndResume(sessionToResume);
     }
 
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index e5d5b57..5511a54 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -67,6 +67,7 @@
 import android.os.ResultReceiver;
 import android.os.SELinux;
 import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
 import android.os.ShellCallback;
 import android.os.ShellCommand;
 import android.os.SystemClock;
@@ -74,6 +75,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.UserManager.EnforcingUser;
+import android.os.UserManager.QuietModeFlag;
 import android.os.UserManagerInternal;
 import android.os.UserManagerInternal.UserRestrictionsListener;
 import android.os.storage.StorageManager;
@@ -492,6 +494,10 @@
         public void onBootPhase(int phase) {
             if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
                 mUms.cleanupPartialUsers();
+
+                if (mUms.mPm.isDeviceUpgrading()) {
+                    mUms.cleanupPreCreatedUsers();
+                }
             }
         }
 
@@ -617,6 +623,33 @@
         }
     }
 
+    /**
+     * Removes any pre-created users from the system. Should be invoked after OTAs, to ensure
+     * pre-created users are not stale. New pre-created pool can be re-created after the update.
+     */
+    void cleanupPreCreatedUsers() {
+        final ArrayList<UserInfo> preCreatedUsers;
+        synchronized (mUsersLock) {
+            final int userSize = mUsers.size();
+            preCreatedUsers = new ArrayList<>(userSize);
+            for (int i = 0; i < userSize; i++) {
+                UserInfo ui = mUsers.valueAt(i).info;
+                if (ui.preCreated) {
+                    preCreatedUsers.add(ui);
+                    addRemovingUserIdLocked(ui.id);
+                    ui.flags |= UserInfo.FLAG_DISABLED;
+                    ui.partial = true;
+                }
+            }
+        }
+        final int preCreatedSize = preCreatedUsers.size();
+        for (int i = 0; i < preCreatedSize; i++) {
+            UserInfo ui = preCreatedUsers.get(i);
+            Slog.i(LOG_TAG, "Removing pre-created user " + ui.id);
+            removeUserState(ui.id);
+        }
+    }
+
     @Override
     public String getUserAccount(@UserIdInt int userId) {
         checkManageUserAndAcrossUsersFullPermission("get user account");
@@ -883,7 +916,7 @@
 
     @Override
     public boolean requestQuietModeEnabled(@NonNull String callingPackage, boolean enableQuietMode,
-            @UserIdInt int userId, @Nullable IntentSender target) {
+            @UserIdInt int userId, @Nullable IntentSender target, @QuietModeFlag int flags) {
         Objects.requireNonNull(callingPackage);
 
         if (enableQuietMode && target != null) {
@@ -894,24 +927,24 @@
         ensureCanModifyQuietMode(callingPackage, Binder.getCallingUid(), userId, target != null);
         final long identity = Binder.clearCallingIdentity();
         try {
-            boolean result = false;
             if (enableQuietMode) {
                 setQuietModeEnabled(
                         userId, true /* enableQuietMode */, target, callingPackage);
-                result = true;
-            } else {
-                boolean needToShowConfirmCredential =
-                        mLockPatternUtils.isSecure(userId)
-                                && !StorageManager.isUserKeyUnlocked(userId);
-                if (needToShowConfirmCredential) {
-                    showConfirmCredentialToDisableQuietMode(userId, target);
-                } else {
-                    setQuietModeEnabled(
-                            userId, false /* enableQuietMode */, target, callingPackage);
-                    result = true;
-                }
+                return true;
             }
-            return result;
+            boolean needToShowConfirmCredential =
+                    mLockPatternUtils.isSecure(userId)
+                            && !StorageManager.isUserKeyUnlocked(userId);
+            if (needToShowConfirmCredential) {
+                if ((flags & UserManager.QUIET_MODE_DISABLE_ONLY_IF_CREDENTIAL_NOT_REQUIRED) != 0) {
+                    return false;
+                }
+                showConfirmCredentialToDisableQuietMode(userId, target);
+                return false;
+            }
+            setQuietModeEnabled(
+                    userId, false /* enableQuietMode */, target, callingPackage);
+            return true;
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -1506,12 +1539,14 @@
 
     @Override
     public void setUserIcon(@UserIdInt int userId, Bitmap bitmap) {
-        checkManageUsersPermission("update users");
-        if (hasUserRestriction(UserManager.DISALLOW_SET_USER_ICON, userId)) {
-            Slog.w(LOG_TAG, "Cannot set user icon. DISALLOW_SET_USER_ICON is enabled.");
-            return;
+        try {
+            checkManageUsersPermission("update users");
+            enforceUserRestriction(UserManager.DISALLOW_SET_USER_ICON, userId,
+                    "Cannot set user icon");
+            mLocalService.setUserIcon(userId, bitmap);
+        } catch (UserManager.CheckedUserOperationException e) {
+            throw e.toServiceSpecificException();
         }
-        mLocalService.setUserIcon(userId, bitmap);
     }
 
 
@@ -3005,34 +3040,53 @@
 
     /**
      * Creates a profile user. Used for actual profiles, like
-     * {@link UserManager#USER_TYPE_PROFILE_MANAGED}, as well as for
-     * {@link UserManager#USER_TYPE_FULL_RESTRICTED}.
+     * {@link UserManager#USER_TYPE_PROFILE_MANAGED},
+     * as well as for {@link UserManager#USER_TYPE_FULL_RESTRICTED}.
      */
     @Override
-    public UserInfo createProfileForUser(String name, @NonNull String userType,
-            @UserInfoFlag int flags, @UserIdInt int userId, @Nullable String[] disallowedPackages) {
+    public UserInfo createProfileForUserWithThrow(String name, @NonNull String userType,
+            @UserInfoFlag int flags, @UserIdInt int userId, @Nullable String[] disallowedPackages)
+            throws ServiceSpecificException {
         checkManageOrCreateUsersPermission(flags);
-        return createUserInternal(name, userType, flags, userId, disallowedPackages);
+        try {
+            return createUserInternal(name, userType, flags, userId, disallowedPackages);
+        } catch (UserManager.CheckedUserOperationException e) {
+            throw e.toServiceSpecificException();
+        }
     }
 
-    /** @see #createProfileForUser */
+    /**
+     * @see #createProfileForUser
+     */
     @Override
-    public UserInfo createProfileForUserEvenWhenDisallowed(String name, @NonNull String userType,
-            @UserInfoFlag int flags, @UserIdInt int userId, @Nullable String[] disallowedPackages) {
+    public UserInfo createProfileForUserEvenWhenDisallowedWithThrow(String name,
+            @NonNull String userType,
+            @UserInfoFlag int flags, @UserIdInt int userId, @Nullable String[] disallowedPackages)
+            throws ServiceSpecificException {
         checkManageOrCreateUsersPermission(flags);
-        return createUserInternalUnchecked(name, userType, flags, userId,
-                /* preCreate= */ false, disallowedPackages);
+        try {
+            return createUserInternalUnchecked(name, userType, flags, userId,
+                    /* preCreate= */ false, disallowedPackages);
+        } catch (UserManager.CheckedUserOperationException e) {
+            throw e.toServiceSpecificException();
+        }
     }
 
     @Override
-    public UserInfo createUser(String name, @NonNull String userType, @UserInfoFlag int flags) {
+    public UserInfo createUserWithThrow(String name, @NonNull String userType,
+            @UserInfoFlag int flags)
+            throws ServiceSpecificException {
         checkManageOrCreateUsersPermission(flags);
-        return createUserInternal(name, userType, flags, UserHandle.USER_NULL,
-                /* disallowedPackages= */ null);
+        try {
+            return createUserInternal(name, userType, flags, UserHandle.USER_NULL,
+                    /* disallowedPackages= */ null);
+        } catch (UserManager.CheckedUserOperationException e) {
+            throw e.toServiceSpecificException();
+        }
     }
 
     @Override
-    public UserInfo preCreateUser(String userType) {
+    public UserInfo preCreateUserWithThrow(String userType) throws ServiceSpecificException {
         final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
         final int flags = userTypeDetails != null ? userTypeDetails.getDefaultUserInfoFlags() : 0;
 
@@ -3042,28 +3096,32 @@
                 "cannot pre-create user of type " + userType);
         Slog.i(LOG_TAG, "Pre-creating user of type " + userType);
 
-        return createUserInternalUnchecked(/* name= */ null, userType, flags,
-                /* parentId= */ UserHandle.USER_NULL, /* preCreate= */ true,
-                /* disallowedPackages= */ null);
+        try {
+            return createUserInternalUnchecked(/* name= */ null, userType, flags,
+                    /* parentId= */ UserHandle.USER_NULL, /* preCreate= */ true,
+                    /* disallowedPackages= */ null);
+        } catch (UserManager.CheckedUserOperationException e) {
+            throw e.toServiceSpecificException();
+        }
     }
 
     private UserInfo createUserInternal(@Nullable String name, @NonNull String userType,
             @UserInfoFlag int flags, @UserIdInt int parentId,
-            @Nullable String[] disallowedPackages) {
+            @Nullable String[] disallowedPackages)
+            throws UserManager.CheckedUserOperationException {
         String restriction = (UserManager.isUserTypeManagedProfile(userType))
                 ? UserManager.DISALLOW_ADD_MANAGED_PROFILE
                 : UserManager.DISALLOW_ADD_USER;
-        if (hasUserRestriction(restriction, UserHandle.getCallingUserId())) {
-            Slog.w(LOG_TAG, "Cannot add user. " + restriction + " is enabled.");
-            return null;
-        }
+        enforceUserRestriction(restriction, UserHandle.getCallingUserId(),
+                "Cannot add user");
         return createUserInternalUnchecked(name, userType, flags, parentId,
                 /* preCreate= */ false, disallowedPackages);
     }
 
     private UserInfo createUserInternalUnchecked(@Nullable String name,
             @NonNull String userType, @UserInfoFlag int flags, @UserIdInt int parentId,
-            boolean preCreate, @Nullable String[] disallowedPackages) {
+            boolean preCreate, @Nullable String[] disallowedPackages)
+            throws UserManager.CheckedUserOperationException {
         final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
         t.traceBegin("createUser-" + flags);
         try {
@@ -3077,8 +3135,7 @@
     private UserInfo createUserInternalUncheckedNoTracing(@Nullable String name,
             @NonNull String userType, @UserInfoFlag int flags, @UserIdInt int parentId,
             boolean preCreate, @Nullable String[] disallowedPackages,
-            @NonNull TimingsTraceAndSlog t) {
-
+            @NonNull TimingsTraceAndSlog t) throws UserManager.CheckedUserOperationException {
         final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
         if (userTypeDetails == null) {
             Slog.e(LOG_TAG, "Cannot create user of invalid user type: " + userType);
@@ -3113,8 +3170,8 @@
         DeviceStorageMonitorInternal dsm = LocalServices
                 .getService(DeviceStorageMonitorInternal.class);
         if (dsm.isMemoryLow()) {
-            Slog.w(LOG_TAG, "Cannot add user. Not enough space on disk.");
-            return null;
+            throwCheckedUserOperationException("Cannot add user. Not enough space on disk.",
+                    UserManager.USER_OPERATION_ERROR_LOW_STORAGE);
         }
 
         final boolean isProfile = userTypeDetails.isProfile();
@@ -3133,41 +3190,50 @@
                     synchronized (mUsersLock) {
                         parent = getUserDataLU(parentId);
                     }
-                    if (parent == null) return null;
+                    if (parent == null) {
+                        throwCheckedUserOperationException(
+                                "Cannot find user data for parent user " + parentId,
+                                UserManager.USER_OPERATION_ERROR_UNKNOWN);
+                    }
                 }
                 if (!preCreate && !canAddMoreUsersOfType(userTypeDetails)) {
-                    Slog.e(LOG_TAG, "Cannot add more users of type " + userType
-                            + ". Maximum number of that type already exists.");
-                    return null;
+                    throwCheckedUserOperationException("Cannot add more users of type " + userType
+                                    + ". Maximum number of that type already exists.",
+                            UserManager.USER_OPERATION_ERROR_MAX_USERS);
                 }
                 // TODO(b/142482943): Perhaps let the following code apply to restricted users too.
                 if (isProfile && !canAddMoreProfilesToUser(userType, parentId, false)) {
-                    Slog.e(LOG_TAG, "Cannot add more profiles of type " + userType
-                            + " for user " + parentId);
-                    return null;
+                    throwCheckedUserOperationException(
+                            "Cannot add more profiles of type " + userType
+                                    + " for user " + parentId,
+                            UserManager.USER_OPERATION_ERROR_MAX_USERS);
                 }
                 if (!isGuest && !isProfile && !isDemo && isUserLimitReached()) {
                     // If we're not adding a guest/demo user or a profile and the 'user limit' has
                     // been reached, cannot add a user.
-                    Slog.e(LOG_TAG, "Cannot add user. Maximum user limit is reached.");
-                    return null;
+                    throwCheckedUserOperationException(
+                            "Cannot add user. Maximum user limit is reached.",
+                            UserManager.USER_OPERATION_ERROR_MAX_USERS);
                 }
                 // In legacy mode, restricted profile's parent can only be the owner user
                 if (isRestricted && !UserManager.isSplitSystemUser()
                         && (parentId != UserHandle.USER_SYSTEM)) {
-                    Slog.w(LOG_TAG, "Cannot add restricted profile - parent user must be owner");
-                    return null;
+                    throwCheckedUserOperationException(
+                            "Cannot add restricted profile - parent user must be owner",
+                            UserManager.USER_OPERATION_ERROR_UNKNOWN);
                 }
                 if (isRestricted && UserManager.isSplitSystemUser()) {
                     if (parent == null) {
-                        Slog.w(LOG_TAG, "Cannot add restricted profile - parent user must be "
-                                + "specified");
-                        return null;
+                        throwCheckedUserOperationException(
+                                "Cannot add restricted profile - parent user must be specified",
+                                UserManager.USER_OPERATION_ERROR_UNKNOWN);
                     }
                     if (!parent.info.canHaveProfile()) {
-                        Slog.w(LOG_TAG, "Cannot add restricted profile - profiles cannot be "
-                                + "created for the specified parent user id " + parentId);
-                        return null;
+                        throwCheckedUserOperationException(
+                                "Cannot add restricted profile - profiles cannot be created for "
+                                        + "the specified parent user id "
+                                        + parentId,
+                                UserManager.USER_OPERATION_ERROR_UNKNOWN);
                     }
                 }
 
@@ -3254,9 +3320,9 @@
                 mBaseUserRestrictions.append(userId, restrictions);
             }
 
-            t.traceBegin("PM.onNewUserCreated");
+            t.traceBegin("PM.onNewUserCreated-" + userId);
             mPm.onNewUserCreated(userId);
-
+            t.traceEnd();
             if (preCreate) {
                 // Must start user (which will be stopped right away, through
                 // UserController.finishUserUnlockedCompleted) so services can properly
@@ -3323,11 +3389,16 @@
         preCreatedUser.preCreated = false;
         preCreatedUser.creationTime = getCreationTime();
 
-        dispatchUserAddedIntent(preCreatedUser);
         synchronized (mPackagesLock) {
             writeUserLP(preCreatedUserData);
             writeUserListLP();
         }
+        updateUserIds();
+        if (!mPm.readPermissionStateForUser(preCreatedUser.id)) {
+            // Could not read the existing permissions, re-grant them.
+            mPm.onNewUserCreated(preCreatedUser.id);
+        }
+        dispatchUserAddedIntent(preCreatedUser);
         return preCreatedUser;
     }
 
@@ -3360,6 +3431,9 @@
     private void dispatchUserAddedIntent(@NonNull UserInfo userInfo) {
         Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
         addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userInfo.id);
+        // Also, add the UserHandle for mainline modules which can't use the @hide
+        // EXTRA_USER_HANDLE.
+        addedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userInfo.id));
         mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
                 android.Manifest.permission.MANAGE_USERS);
         MetricsLogger.count(mContext, userInfo.isGuest() ? TRON_GUEST_CREATED
@@ -3425,9 +3499,9 @@
      * @hide
      */
     @Override
-    public UserInfo createRestrictedProfile(String name, int parentUserId) {
+    public UserInfo createRestrictedProfileWithThrow(String name, int parentUserId) {
         checkManageOrCreateUsersPermission("setupRestrictedProfile");
-        final UserInfo user = createProfileForUser(
+        final UserInfo user = createProfileForUserWithThrow(
                 name, UserManager.USER_TYPE_FULL_RESTRICTED, 0, parentUserId, null);
         if (user == null) {
             return null;
@@ -3643,9 +3717,12 @@
         // wiping the user's system directory and removing from the user list
         long ident = Binder.clearCallingIdentity();
         try {
-            Intent addedIntent = new Intent(Intent.ACTION_USER_REMOVED);
-            addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-            mContext.sendOrderedBroadcastAsUser(addedIntent, UserHandle.ALL,
+            Intent removedIntent = new Intent(Intent.ACTION_USER_REMOVED);
+            removedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+            // Also, add the UserHandle for mainline modules which can't use the @hide
+            // EXTRA_USER_HANDLE.
+            removedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId));
+            mContext.sendOrderedBroadcastAsUser(removedIntent, UserHandle.ALL,
                     android.Manifest.permission.MANAGE_USERS,
 
                     new BroadcastReceiver() {
@@ -4027,14 +4104,16 @@
         synchronized (mUsersLock) {
             final int userSize = mUsers.size();
             for (int i = 0; i < userSize; i++) {
-                if (!mUsers.valueAt(i).info.partial) {
+                UserInfo userInfo = mUsers.valueAt(i).info;
+                if (!userInfo.partial && !userInfo.preCreated) {
                     num++;
                 }
             }
             final int[] newUsers = new int[num];
             int n = 0;
             for (int i = 0; i < userSize; i++) {
-                if (!mUsers.valueAt(i).info.partial) {
+                UserInfo userInfo = mUsers.valueAt(i).info;
+                if (!userInfo.partial && !userInfo.preCreated) {
                     newUsers[n++] = mUsers.keyAt(i);
                 }
             }
@@ -4095,7 +4174,10 @@
      * recycled.
      */
     void reconcileUsers(String volumeUuid) {
-        mUserDataPreparer.reconcileUsers(volumeUuid, getUsers(true /* excludeDying */));
+        mUserDataPreparer.reconcileUsers(volumeUuid, getUsers(
+                /* excludePartial= */ true,
+                /* excludeDying= */ true,
+                /* excludePreCreated= */ false));
     }
 
     /**
@@ -4668,7 +4750,8 @@
 
         @Override
         public UserInfo createUserEvenWhenDisallowed(String name, @NonNull String userType,
-                @UserInfoFlag int flags, String[] disallowedPackages) {
+                @UserInfoFlag int flags, String[] disallowedPackages)
+                throws UserManager.CheckedUserOperationException {
             return createUserInternalUnchecked(name, userType, flags,
                     UserHandle.USER_NULL, /* preCreated= */ false, disallowedPackages);
         }
@@ -4828,6 +4911,38 @@
         }
     }
 
+    /**
+     * Check if user has restrictions
+     * @param restriction restrictions to check
+     * @param userId id of the user
+     *
+     * @throws {@link android.os.UserManager.CheckedUserOperationException} if user has any of the
+     *      specified restrictions
+     */
+    private void enforceUserRestriction(String restriction, @UserIdInt int userId, String message)
+            throws UserManager.CheckedUserOperationException {
+        if (hasUserRestriction(restriction, userId)) {
+            String errorMessage = (message != null ? (message + ": ") : "")
+                    + restriction + " is enabled.";
+            Slog.w(LOG_TAG, errorMessage);
+            throw new UserManager.CheckedUserOperationException(errorMessage,
+                    UserManager.USER_OPERATION_ERROR_UNKNOWN);
+        }
+    }
+
+    /**
+     * Throws CheckedUserOperationException and shows error log
+     * @param message message for exception and logging
+     * @param userOperationResult result/error code
+     * @throws UserManager.CheckedUserOperationException
+     */
+    private void throwCheckedUserOperationException(@NonNull String message,
+            @UserManager.UserOperationResult int userOperationResult)
+            throws UserManager.CheckedUserOperationException {
+        Slog.e(LOG_TAG, message);
+        throw new UserManager.CheckedUserOperationException(message, userOperationResult);
+    }
+
     /* Remove all the users except of the system one. */
     private void removeNonSystemUsers() {
         ArrayList<UserInfo> usersToRemove = new ArrayList<>();
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 815f7b4..89030ed 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -199,13 +199,31 @@
     );
 
     /**
-     * Special user restrictions that are applied globally when set by the profile owner of a
-     * managed profile that was created during the device provisioning flow.
+     * Special user restrictions that profile owner of an organization-owned managed profile can
+     * set on the parent profile instance to apply them globally.
      */
     private static final Set<String> PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS =
             Sets.newArraySet(
                     UserManager.DISALLOW_CONFIG_DATE_TIME,
-                    UserManager.DISALLOW_CAMERA
+                    UserManager.DISALLOW_CAMERA,
+                    UserManager.DISALLOW_ADD_USER,
+                    UserManager.DISALLOW_BLUETOOTH,
+                    UserManager.DISALLOW_BLUETOOTH_SHARING,
+                    UserManager.DISALLOW_CONFIG_BLUETOOTH,
+                    UserManager.DISALLOW_CONFIG_CELL_BROADCASTS,
+                    UserManager.DISALLOW_CONFIG_LOCATION,
+                    UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,
+                    UserManager.DISALLOW_CONFIG_PRIVATE_DNS,
+                    UserManager.DISALLOW_CONFIG_TETHERING,
+                    UserManager.DISALLOW_CONFIG_WIFI,
+                    UserManager.DISALLOW_CONTENT_CAPTURE,
+                    UserManager.DISALLOW_CONTENT_SUGGESTIONS,
+                    UserManager.DISALLOW_DATA_ROAMING,
+                    UserManager.DISALLOW_DEBUGGING_FEATURES,
+                    UserManager.DISALLOW_SAFE_BOOT,
+                    UserManager.DISALLOW_SHARE_LOCATION,
+                    UserManager.DISALLOW_SMS,
+                    UserManager.DISALLOW_USB_FILE_TRANSFER
     );
 
     /**
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 29183bb..df24c013 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -222,6 +222,7 @@
                     // If the dex file is the primary apk (or a split) and not isUsedByOtherApps
                     // do not record it. This case does not bring any new usable information
                     // and can be safely skipped.
+                    dexPathIndex++;
                     continue;
                 }
 
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 6d6ec25..46893b2 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -592,12 +592,6 @@
                 getDefaultSystemHandlerActivityPackageForCategory(Intent.CATEGORY_APP_MAPS, userId),
                 userId, ALWAYS_LOCATION_PERMISSIONS);
 
-        // Gallery
-        grantPermissionsToSystemPackage(
-                getDefaultSystemHandlerActivityPackageForCategory(
-                        Intent.CATEGORY_APP_GALLERY, userId),
-                userId, STORAGE_PERMISSIONS);
-
         // Email
         grantPermissionsToSystemPackage(
                 getDefaultSystemHandlerActivityPackageForCategory(
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 d921f31..6167a50 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -55,7 +55,10 @@
 import android.app.ActivityManager;
 import android.app.ApplicationPackageManager;
 import android.app.IActivityManager;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.content.Context;
+import android.content.PermissionChecker;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.PermissionGroupInfoFlags;
@@ -107,6 +110,7 @@
 import android.util.SparseBooleanArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.os.RoSystemProperties;
@@ -224,6 +228,8 @@
     private final Handler mHandler;
     private final Context mContext;
     private final MetricsLogger mMetricsLogger = new MetricsLogger();
+    private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface(
+            ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
 
     /** Internal storage for permissions and related settings */
     @GuardedBy("mLock")
@@ -1824,6 +1830,14 @@
         return true;
     }
 
+    /**
+     * This change makes it so that apps are told to show rationale for asking for background
+     * location access every time they request.
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+    private static final long BACKGROUND_RATIONALE_CHANGE_ID = 147316723L;
+
     @Override
     public boolean shouldShowRequestPermissionRationale(String permName,
             String packageName, int userId) {
@@ -1862,6 +1876,16 @@
             return false;
         }
 
+        try {
+            if (permName.equals(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
+                    && mPlatformCompat.isChangeEnabledByPackageName(BACKGROUND_RATIONALE_CHANGE_ID,
+                    packageName, userId)) {
+                return true;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to check if compatibility change is enabled.", e);
+        }
+
         return (flags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
     }
 
@@ -4005,21 +4029,133 @@
             PackageManagerServiceUtils.enforceShellRestriction(mUserManagerInt,
                     UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
         }
-        if (!requirePermissionWhenSameUser && userId == UserHandle.getUserId(callingUid)) return;
-        if (callingUid != Process.SYSTEM_UID && callingUid != Process.ROOT_UID) {
-            if (requireFullPermission) {
-                mContext.enforceCallingOrSelfPermission(
-                        android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
-            } else {
-                try {
-                    mContext.enforceCallingOrSelfPermission(
-                            android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
-                } catch (SecurityException se) {
-                    mContext.enforceCallingOrSelfPermission(
-                            android.Manifest.permission.INTERACT_ACROSS_USERS, message);
-                }
-            }
+        final int callingUserId = UserHandle.getUserId(callingUid);
+        if (hasCrossUserPermission(
+                callingUid, callingUserId, userId, requireFullPermission,
+                requirePermissionWhenSameUser)) {
+            return;
         }
+        String errorMessage = buildInvalidCrossUserPermissionMessage(
+                message, requireFullPermission);
+        Slog.w(TAG, errorMessage);
+        throw new SecurityException(errorMessage);
+    }
+
+    /**
+     * Checks if the request is from the system or an app that has the appropriate cross-user
+     * permissions defined as follows:
+     * <ul>
+     * <li>INTERACT_ACROSS_USERS_FULL if {@code requireFullPermission} is true.</li>
+     * <li>INTERACT_ACROSS_USERS if the given {@userId} is in a different profile group
+     * to the caller.</li>
+     * <li>Otherwise, INTERACT_ACROSS_PROFILES if the given {@userId} is in the same profile group
+     * as the caller.</li>
+     * </ul>
+     *
+     * @param checkShell whether to prevent shell from access if there's a debugging restriction
+     * @param message the message to log on security exception
+     */
+    private void enforceCrossUserOrProfilePermission(int callingUid, int userId,
+            boolean requireFullPermission, boolean checkShell,
+            String message) {
+        if (userId < 0) {
+            throw new IllegalArgumentException("Invalid userId " + userId);
+        }
+        if (checkShell) {
+            PackageManagerServiceUtils.enforceShellRestriction(mUserManagerInt,
+                    UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
+        }
+        final int callingUserId = UserHandle.getUserId(callingUid);
+        if (hasCrossUserPermission(callingUid, callingUserId, userId, requireFullPermission,
+                /*requirePermissionWhenSameUser= */ false)) {
+            return;
+        }
+        final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, userId);
+        if (isSameProfileGroup && PermissionChecker.checkPermissionForPreflight(
+                mContext,
+                android.Manifest.permission.INTERACT_ACROSS_PROFILES,
+                PermissionChecker.PID_UNKNOWN,
+                callingUid,
+                mPackageManagerInt.getPackage(callingUid).getPackageName())
+                == PermissionChecker.PERMISSION_GRANTED) {
+            return;
+        }
+        String errorMessage = buildInvalidCrossUserOrProfilePermissionMessage(
+                message, requireFullPermission, isSameProfileGroup);
+        Slog.w(TAG, errorMessage);
+        throw new SecurityException(errorMessage);
+    }
+
+    private boolean hasCrossUserPermission(
+            int callingUid, int callingUserId, int userId, boolean requireFullPermission,
+            boolean requirePermissionWhenSameUser) {
+        if (!requirePermissionWhenSameUser && userId == callingUserId) {
+            return true;
+        }
+        if (callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID) {
+            return true;
+        }
+        if (requireFullPermission) {
+            return hasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+        }
+        return hasPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+                || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS);
+    }
+
+    private boolean hasPermission(String permission) {
+        return mContext.checkCallingOrSelfPermission(permission)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    private boolean isSameProfileGroup(@UserIdInt int callerUserId, @UserIdInt int userId) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return UserManagerService.getInstance().isSameProfileGroup(callerUserId, userId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    private static String buildInvalidCrossUserPermissionMessage(
+            String message, boolean requireFullPermission) {
+        StringBuilder builder = new StringBuilder();
+        if (message != null) {
+            builder.append(message);
+            builder.append(": ");
+        }
+        builder.append("Requires ");
+        builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+        if (requireFullPermission) {
+            builder.append(".");
+            return builder.toString();
+        }
+        builder.append(" or ");
+        builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS);
+        builder.append(".");
+        return builder.toString();
+    }
+
+    private static String buildInvalidCrossUserOrProfilePermissionMessage(
+            String message, boolean requireFullPermission, boolean isSameProfileGroup) {
+        StringBuilder builder = new StringBuilder();
+        if (message != null) {
+            builder.append(message);
+            builder.append(": ");
+        }
+        builder.append("Requires ");
+        builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+        if (requireFullPermission) {
+            builder.append(".");
+            return builder.toString();
+        }
+        builder.append(" or ");
+        builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS);
+        if (isSameProfileGroup) {
+            builder.append(" or ");
+            builder.append(android.Manifest.permission.INTERACT_ACROSS_PROFILES);
+        }
+        builder.append(".");
+        return builder.toString();
     }
 
     @GuardedBy({"mSettings.mLock", "mLock"})
@@ -4215,6 +4351,18 @@
             PermissionManagerService.this.enforceCrossUserPermission(callingUid, userId,
                     requireFullPermission, checkShell, requirePermissionWhenSameUser, message);
         }
+
+        @Override
+        public void enforceCrossUserOrProfilePermission(int callingUid, int userId,
+                boolean requireFullPermission, boolean checkShell, String message) {
+            PermissionManagerService.this.enforceCrossUserOrProfilePermission(
+                    callingUid,
+                    userId,
+                    requireFullPermission,
+                    checkShell,
+                    message);
+        }
+
         @Override
         public void enforceGrantRevokeRuntimePermissionPermissions(String message) {
             PermissionManagerService.this.enforceGrantRevokeRuntimePermissionPermissions(message);
@@ -4453,8 +4601,8 @@
 
         @Override
         public void onNewUserCreated(int userId) {
+            mDefaultPermissionGrantPolicy.grantDefaultPermissions(userId);
             synchronized (mLock) {
-                mDefaultPermissionGrantPolicy.grantDefaultPermissions(userId);
                 // NOTE: This adds UPDATE_PERMISSIONS_REPLACE_PKG
                 PermissionManagerService.this.updateAllPermissions(
                         StorageManager.UUID_PRIVATE_INTERNAL, true, mDefaultPermissionCallback);
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 0f22619..58a9f42 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -271,6 +271,15 @@
      */
     public abstract void enforceCrossUserPermission(int callingUid, int userId,
             boolean requireFullPermission, boolean checkShell, @NonNull String message);
+
+    /**
+     * Similar to {@link #enforceCrossUserPermission(int, int, boolean, boolean, String)}
+     * but also allows INTERACT_ACROSS_PROFILES permission if calling user and {@code userId} are
+     * in the same profile group.
+     */
+    public abstract void enforceCrossUserOrProfilePermission(int callingUid, int userId,
+            boolean requireFullPermission, boolean checkShell, @NonNull String message);
+
     /**
      * @see #enforceCrossUserPermission(int, int, boolean, boolean, String)
      * @param requirePermissionWhenSameUser When {@code true}, still require the cross user
diff --git a/services/core/java/com/android/server/policy/GlobalKeyManager.java b/services/core/java/com/android/server/policy/GlobalKeyManager.java
index e08c004..157f825 100644
--- a/services/core/java/com/android/server/policy/GlobalKeyManager.java
+++ b/services/core/java/com/android/server/policy/GlobalKeyManager.java
@@ -74,7 +74,7 @@
                 Intent intent = new Intent(Intent.ACTION_GLOBAL_BUTTON)
                         .setComponent(component)
                         .setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
-                        .putExtra(Intent.EXTRA_KEY_EVENT, event);
+                        .putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(event));
                 context.sendBroadcastAsUser(intent, UserHandle.CURRENT, null);
                 return true;
             }
diff --git a/services/core/java/com/android/server/policy/LegacyGlobalActions.java b/services/core/java/com/android/server/policy/LegacyGlobalActions.java
index 5271493..6daf516 100644
--- a/services/core/java/com/android/server/policy/LegacyGlobalActions.java
+++ b/services/core/java/com/android/server/policy/LegacyGlobalActions.java
@@ -743,8 +743,8 @@
             } else if (TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) {
                 // Airplane mode can be changed after ECM exits if airplane toggle button
                 // is pressed during ECM mode
-                if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) &&
-                        mIsWaitingForEcmExit) {
+                if (!(intent.getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false))
+                        && mIsWaitingForEcmExit) {
                     mIsWaitingForEcmExit = false;
                     changeAirplaneModeSystemSetting(true);
                 }
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index e7269a6..a86c8d7 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -58,6 +58,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsService;
+import com.android.internal.infra.AndroidFuture;
 import com.android.internal.util.IntPair;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.FgThread;
@@ -68,7 +69,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
 
 /**
  * This is a permission policy that governs over all permission mechanism
@@ -280,7 +281,7 @@
             if (DEBUG) Slog.i(LOG_TAG, "defaultPermsWereGrantedSinceBoot(" + userId + ")");
 
             // Now call into the permission controller to apply policy around permissions
-            final CountDownLatch latch = new CountDownLatch(1);
+            final AndroidFuture<Boolean> future = new AndroidFuture<>();
 
             // We need to create a local manager that does not schedule work on the main
             // there as we are on the main thread and want to block until the work is
@@ -290,22 +291,22 @@
                             getUserContext(getContext(), UserHandle.of(userId)),
                             FgThread.getHandler());
             permissionControllerManager.grantOrUpgradeDefaultRuntimePermissions(
-                    FgThread.getExecutor(),
-                    (Boolean success) -> {
-                        if (!success) {
+                    FgThread.getExecutor(), successful -> {
+                        if (successful) {
+                            future.complete(null);
+                        } else {
                             // We are in an undefined state now, let us crash and have
                             // rescue party suggest a wipe to recover to a good one.
-                            final String message = "Error granting/upgrading runtime permissions";
+                            final String message = "Error granting/upgrading runtime permissions"
+                                    + " for user " + userId;
                             Slog.wtf(LOG_TAG, message);
-                            throw new IllegalStateException(message);
+                            future.completeExceptionally(new IllegalStateException(message));
                         }
-                        latch.countDown();
-                    }
-            );
+                    });
             try {
-                latch.await();
-            } catch (InterruptedException e) {
-                /* ignore */
+                future.get();
+            } catch (InterruptedException | ExecutionException e) {
+                throw new IllegalStateException(e);
             }
 
             permissionControllerManager.updateUserSensitive();
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index e04d10c..c3e7f62 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -46,7 +46,6 @@
 import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.LAST_SYSTEM_WINDOW;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR;
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -55,6 +54,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
 import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
 import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION;
 import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
@@ -562,10 +562,6 @@
     private boolean mScreenshotChordPowerKeyTriggered;
     private long mScreenshotChordPowerKeyTime;
 
-    private static final long MOVING_DISPLAY_TO_TOP_DURATION_MILLIS = 10;
-    private volatile boolean mMovingDisplayToTopKeyTriggered;
-    private volatile long mMovingDisplayToTopKeyTime;
-
     // Ringer toggle should reuse timing and triggering from screenshot power and a11y vol up
     private int mRingerToggleChord = VOLUME_HUSH_OFF;
 
@@ -633,7 +629,6 @@
     private static final int MSG_POWER_VERY_LONG_PRESS = 25;
     private static final int MSG_NOTIFY_USER_ACTIVITY = 26;
     private static final int MSG_RINGER_TOGGLE_CHORD = 27;
-    private static final int MSG_MOVE_DISPLAY_TO_TOP = 28;
 
     private class PolicyHandler extends Handler {
         @Override
@@ -723,10 +718,6 @@
                 case MSG_RINGER_TOGGLE_CHORD:
                     handleRingerChordGesture();
                     break;
-                case MSG_MOVE_DISPLAY_TO_TOP:
-                    mWindowManagerFuncs.moveDisplayToTop(msg.arg1);
-                    mMovingDisplayToTopKeyTriggered = false;
-                    break;
             }
         }
     }
@@ -1641,7 +1632,8 @@
                 // If a system window has focus, then it doesn't make sense
                 // right now to interact with applications.
                 if (info.layoutParamsType == TYPE_KEYGUARD_DIALOG
-                        || (info.layoutParamsPrivateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
+                        || (info.layoutParamsType == TYPE_NOTIFICATION_SHADE
+                        && isKeyguardShowing())) {
                     // the "app" is keyguard, so give it the key
                     return 0;
                 }
@@ -2214,12 +2206,12 @@
 
     @Override
     public int getMaxWallpaperLayer() {
-        return getWindowLayerFromTypeLw(TYPE_STATUS_BAR);
+        return getWindowLayerFromTypeLw(TYPE_NOTIFICATION_SHADE);
     }
 
     @Override
     public boolean isKeyguardHostWindow(WindowManager.LayoutParams attrs) {
-        return attrs.type == TYPE_STATUS_BAR;
+        return attrs.type == TYPE_NOTIFICATION_SHADE;
     }
 
     @Override
@@ -2230,6 +2222,7 @@
             return false;
         }
         switch (win.getAttrs().type) {
+            case TYPE_NOTIFICATION_SHADE:
             case TYPE_STATUS_BAR:
             case TYPE_NAVIGATION_BAR:
             case TYPE_WALLPAPER:
@@ -2237,7 +2230,7 @@
                 return false;
             default:
                 // Hide only windows below the keyguard host window.
-                return getWindowLayerLw(win) < getWindowLayerFromTypeLw(TYPE_STATUS_BAR);
+                return getWindowLayerLw(win) < getWindowLayerFromTypeLw(TYPE_NOTIFICATION_SHADE);
         }
     }
 
@@ -2545,36 +2538,6 @@
     @Override
     public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event,
             int policyFlags) {
-        final long result = interceptKeyBeforeDispatchingInner(focusedToken, event, policyFlags);
-        final int eventDisplayId = event.getDisplayId();
-        if (result == 0 && !mPerDisplayFocusEnabled
-                && eventDisplayId != INVALID_DISPLAY && eventDisplayId != mTopFocusedDisplayId) {
-            // An event is targeting a non-focused display. Try to move the display to top so that
-            // it can become the focused display to interact with the user.
-            final long eventDownTime = event.getDownTime();
-            if (mMovingDisplayToTopKeyTime < eventDownTime) {
-                // We have not handled this event yet. Move the display to top, and then tell
-                // dispatcher to try again later.
-                mMovingDisplayToTopKeyTime = eventDownTime;
-                mMovingDisplayToTopKeyTriggered = true;
-                mHandler.sendMessage(
-                        mHandler.obtainMessage(MSG_MOVE_DISPLAY_TO_TOP, eventDisplayId, 0));
-                return MOVING_DISPLAY_TO_TOP_DURATION_MILLIS;
-            } else if (mMovingDisplayToTopKeyTriggered) {
-                // The message has not been handled yet. Tell dispatcher to try again later.
-                return MOVING_DISPLAY_TO_TOP_DURATION_MILLIS;
-            }
-            // The target display is still not the top focused display. Drop the event because the
-            // display may not contain any window which can receive keys.
-            Slog.w(TAG, "Dropping key targeting non-focused display #" + eventDisplayId
-                    + " keyCode=" + KeyEvent.keyCodeToString(event.getKeyCode()));
-            return -1;
-        }
-        return result;
-    }
-
-    private long interceptKeyBeforeDispatchingInner(IBinder focusedToken, KeyEvent event,
-            int policyFlags) {
         final boolean keyguardOn = keyguardOn();
         final int keyCode = event.getKeyCode();
         final int repeatCount = event.getRepeatCount();
@@ -3484,7 +3447,6 @@
             mKeyguardOccluded = false;
             mKeyguardDelegate.setOccluded(false, true /* animate */);
             if (mKeyguardCandidate != null) {
-                mKeyguardCandidate.getAttrs().privateFlags |= PRIVATE_FLAG_KEYGUARD;
                 if (!mKeyguardDelegate.hasLockscreenWallpaper()) {
                     mKeyguardCandidate.getAttrs().flags |= FLAG_SHOW_WALLPAPER;
                 }
@@ -3494,7 +3456,6 @@
             mKeyguardOccluded = true;
             mKeyguardDelegate.setOccluded(true, false /* animate */);
             if (mKeyguardCandidate != null) {
-                mKeyguardCandidate.getAttrs().privateFlags &= ~PRIVATE_FLAG_KEYGUARD;
                 mKeyguardCandidate.getAttrs().flags &= ~FLAG_SHOW_WALLPAPER;
             }
             return true;
@@ -3615,7 +3576,6 @@
         final boolean canceled = event.isCanceled();
         final int keyCode = event.getKeyCode();
         final int displayId = event.getDisplayId();
-
         final boolean isInjected = (policyFlags & WindowManagerPolicy.FLAG_INJECTED) != 0;
 
         // If screen is off then we treat the case where the keyguard is open but hidden
@@ -4037,6 +3997,23 @@
                     PowerManager.WAKE_REASON_WAKE_KEY, "android.policy:KEY");
         }
 
+        if ((result & ACTION_PASS_TO_USER) != 0) {
+            // If the key event is targeted to a specific display, then the user is interacting with
+            // that display. Therefore, give focus to the display that the user is interacting with.
+            if (!mPerDisplayFocusEnabled
+                    && displayId != INVALID_DISPLAY && displayId != mTopFocusedDisplayId) {
+                // An event is targeting a non-focused display. Move the display to top so that
+                // it can become the focused display to interact with the user.
+                // This should be done asynchronously, once the focus logic is fully moved to input
+                // from windowmanager. Currently, we need to ensure the setInputWindows completes,
+                // which would force the focus event to be queued before the current key event.
+                // TODO(b/70668286): post call to 'moveDisplayToTop' to mHandler instead
+                Log.i(TAG, "Moving non-focused display " + displayId + " to top "
+                        + "because a key is targeting it");
+                mWindowManagerFuncs.moveDisplayToTop(displayId);
+            }
+        }
+
         return result;
     }
 
@@ -4681,6 +4658,12 @@
     }
 
     @Override
+    public boolean isKeyguardShowing() {
+        if (mKeyguardDelegate == null) return false;
+        return mKeyguardDelegate.isShowing();
+    }
+
+    @Override
     public boolean isKeyguardShowingAndNotOccluded() {
         if (mKeyguardDelegate == null) return false;
         return mKeyguardDelegate.isShowing() && !mKeyguardOccluded;
@@ -4893,7 +4876,7 @@
                     mBootMsgDialog.getWindow().setDimAmount(1);
                     WindowManager.LayoutParams lp = mBootMsgDialog.getWindow().getAttributes();
                     lp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
-                    lp.setFitWindowInsetsTypes(0 /* types */);
+                    lp.setFitInsetsTypes(0 /* types */);
                     mBootMsgDialog.getWindow().setAttributes(lp);
                     mBootMsgDialog.setCancelable(false);
                     mBootMsgDialog.show();
@@ -5215,7 +5198,7 @@
                     final Intent dock = createHomeDockIntent();
                     if (dock != null) {
                         int result = ActivityTaskManager.getService()
-                                .startActivityAsUser(null, null, dock,
+                                .startActivityAsUser(null, mContext.getBasePackageName(), dock,
                                         dock.resolveTypeIfNeeded(mContext.getContentResolver()),
                                         null, null, 0,
                                         ActivityManager.START_FLAG_ONLY_IF_NEEDED,
@@ -5226,7 +5209,7 @@
                     }
                 }
                 int result = ActivityTaskManager.getService()
-                        .startActivityAsUser(null, null, mHomeIntent,
+                        .startActivityAsUser(null, mContext.getBasePackageName(), mHomeIntent,
                                 mHomeIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
                                 null, null, 0,
                                 ActivityManager.START_FLAG_ONLY_IF_NEEDED,
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index b014372..c39da5f 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -39,6 +39,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
 import static android.view.WindowManager.LayoutParams.TYPE_PHONE;
 import static android.view.WindowManager.LayoutParams.TYPE_POINTER;
 import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
@@ -812,55 +813,57 @@
                 return  16;
             case TYPE_STATUS_BAR:
                 return  17;
-            case TYPE_STATUS_BAR_PANEL:
+            case TYPE_NOTIFICATION_SHADE:
                 return  18;
-            case TYPE_STATUS_BAR_SUB_PANEL:
+            case TYPE_STATUS_BAR_PANEL:
                 return  19;
-            case TYPE_KEYGUARD_DIALOG:
+            case TYPE_STATUS_BAR_SUB_PANEL:
                 return  20;
+            case TYPE_KEYGUARD_DIALOG:
+                return  21;
             case TYPE_VOLUME_OVERLAY:
                 // the on-screen volume indicator and controller shown when the user
                 // changes the device volume
-                return  21;
+                return  22;
             case TYPE_SYSTEM_OVERLAY:
                 // the on-screen volume indicator and controller shown when the user
                 // changes the device volume
-                return  canAddInternalSystemWindow ? 22 : 11;
+                return  canAddInternalSystemWindow ? 23 : 11;
             case TYPE_NAVIGATION_BAR:
                 // the navigation bar, if available, shows atop most things
-                return  23;
+                return  24;
             case TYPE_NAVIGATION_BAR_PANEL:
                 // some panels (e.g. search) need to show on top of the navigation bar
-                return  24;
+                return  25;
             case TYPE_SCREENSHOT:
                 // screenshot selection layer shouldn't go above system error, but it should cover
                 // navigation bars at the very least.
-                return  25;
+                return  26;
             case TYPE_SYSTEM_ERROR:
                 // system-level error dialogs
-                return  canAddInternalSystemWindow ? 26 : 10;
+                return  canAddInternalSystemWindow ? 27 : 10;
             case TYPE_MAGNIFICATION_OVERLAY:
                 // used to highlight the magnified portion of a display
-                return  27;
+                return  28;
             case TYPE_DISPLAY_OVERLAY:
                 // used to simulate secondary display devices
-                return  28;
+                return  29;
             case TYPE_DRAG:
                 // the drag layer: input for drag-and-drop is associated with this window,
                 // which sits above all other focusable windows
-                return  29;
+                return  30;
             case TYPE_ACCESSIBILITY_OVERLAY:
                 // overlay put by accessibility services to intercept user interaction
-                return  30;
+                return  31;
             case TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY:
-                return 31;
+                return 32;
             case TYPE_SECURE_SYSTEM_OVERLAY:
-                return  32;
-            case TYPE_BOOT_PROGRESS:
                 return  33;
+            case TYPE_BOOT_PROGRESS:
+                return  34;
             case TYPE_POINTER:
                 // the (mouse) pointer layer
-                return  34;
+                return  35;
             default:
                 Slog.e("WindowManager", "Unknown window type: " + type);
                 return APPLICATION_LAYER;
@@ -1195,6 +1198,11 @@
     public boolean isKeyguardOccluded();
 
     /**
+     * @return true if in keyguard is on.
+     */
+    boolean isKeyguardShowing();
+
+    /**
      * @return true if in keyguard is on and not occluded.
      */
     public boolean isKeyguardShowingAndNotOccluded();
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 7fc9fdc0..df8e30f 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -46,6 +46,7 @@
 import android.os.Vibrator;
 import android.os.WorkSource;
 import android.provider.Settings;
+import android.telephony.TelephonyManager;
 import android.util.EventLog;
 import android.util.Slog;
 import android.util.StatsLog;
@@ -671,6 +672,8 @@
             }
             mUserActivityPending = false;
         }
+        TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+        tm.notifyUserActivity();
         mPolicy.userActivity();
     }
 
@@ -678,9 +681,20 @@
         final int powerState;
         synchronized (mLock) {
             if (mBroadcastedInteractiveState == INTERACTIVE_STATE_UNKNOWN) {
-                // Broadcasted power state is unknown.  Send wake up.
-                mPendingWakeUpBroadcast = false;
-                mBroadcastedInteractiveState = INTERACTIVE_STATE_AWAKE;
+                // Broadcasted power state is unknown.
+                // Send wake up or go to sleep.
+                switch (mPendingInteractiveState) {
+                    case INTERACTIVE_STATE_ASLEEP:
+                        mPendingGoToSleepBroadcast = false;
+                        mBroadcastedInteractiveState = INTERACTIVE_STATE_ASLEEP;
+                        break;
+
+                    case INTERACTIVE_STATE_AWAKE:
+                    default:
+                        mPendingWakeUpBroadcast = false;
+                        mBroadcastedInteractiveState = INTERACTIVE_STATE_AWAKE;
+                        break;
+                }
             } else if (mBroadcastedInteractiveState == INTERACTIVE_STATE_AWAKE) {
                 // Broadcasted power state is awake.  Send asleep if needed.
                 if (mPendingWakeUpBroadcast || mPendingGoToSleepBroadcast
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 93d50b8..3f3a133 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.power;
 
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT;
 import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
 import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
 import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING;
@@ -72,6 +74,7 @@
 import android.service.dreams.DreamManagerInternal;
 import android.service.vr.IVrManager;
 import android.service.vr.IVrStateCallbacks;
+import android.util.ArraySet;
 import android.util.KeyValueListParser;
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
@@ -93,8 +96,8 @@
 import com.android.server.UiThread;
 import com.android.server.Watchdog;
 import com.android.server.am.BatteryStatsService;
-import com.android.server.lights.Light;
 import com.android.server.lights.LightsManager;
+import com.android.server.lights.LogicalLight;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.power.batterysaver.BatterySaverController;
 import com.android.server.power.batterysaver.BatterySaverPolicy;
@@ -109,6 +112,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * The power manager service is responsible for coordinating power management
@@ -253,7 +257,7 @@
     private WirelessChargerDetector mWirelessChargerDetector;
     private SettingsObserver mSettingsObserver;
     private DreamManagerInternal mDreamManager;
-    private Light mAttentionLight;
+    private LogicalLight mAttentionLight;
 
     private InattentiveSleepWarningController mInattentiveSleepWarningOverlayController;
 
@@ -346,7 +350,8 @@
     // True if systemReady() has been called.
     private boolean mSystemReady;
 
-    // True if boot completed occurred.  We keep the screen on until this happens.
+    // True if boot completed occurred. We keep the screen on until this happens.
+    // The screen will be off if we are in quiescent mode.
     private boolean mBootCompleted;
 
     // True if auto-suspend mode is enabled.
@@ -564,6 +569,9 @@
     // but the DreamService has not yet been told to start (it's an async process).
     private boolean mDozeStartInProgress;
 
+    // Set of all tokens suppressing ambient display.
+    private final Set<String> mAmbientDisplaySuppressionTokens = new ArraySet<>();
+
     private final class ForegroundProfileObserver extends SynchronousUserSwitchObserver {
         @Override
         public void onUserSwitching(@UserIdInt int newUserId) throws RemoteException {
@@ -843,7 +851,8 @@
 
     @Override
     public void onStart() {
-        publishBinderService(Context.POWER_SERVICE, mBinderService);
+        publishBinderService(Context.POWER_SERVICE, mBinderService, /* allowIsolated= */ false,
+                DUMP_FLAG_PRIORITY_DEFAULT | DUMP_FLAG_PRIORITY_CRITICAL);
         publishLocalService(PowerManagerInternal.class, mLocalService);
 
         Watchdog.getInstance().addMonitor(this);
@@ -864,6 +873,12 @@
                 mBatterySaverStateMachine.onBootCompleted();
                 userActivityNoUpdateLocked(
                         now, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID);
+
+                if (sQuiescent) {
+                    goToSleepNoUpdateLocked(SystemClock.uptimeMillis(),
+                            PowerManager.GO_TO_SLEEP_REASON_QUIESCENT,
+                            PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, Process.SYSTEM_UID);
+                }
                 updatePowerStateLocked();
             }
         }
@@ -1430,8 +1445,7 @@
                     + ", uid=" + uid);
         }
 
-        if (eventTime < mLastSleepTime || eventTime < mLastWakeTime
-                || !mBootCompleted || !mSystemReady) {
+        if (eventTime < mLastSleepTime || eventTime < mLastWakeTime || !mSystemReady) {
             return false;
         }
 
@@ -1508,7 +1522,7 @@
         }
 
         if (eventTime < mLastSleepTime || mWakefulness == WAKEFULNESS_AWAKE
-                || !mBootCompleted || !mSystemReady || mForceSuspendActive) {
+                || mForceSuspendActive || !mSystemReady) {
             return false;
         }
 
@@ -1530,6 +1544,10 @@
             mNotifier.onWakeUp(reason, details, reasonUid, opPackageName, opUid);
             userActivityNoUpdateLocked(
                     eventTime, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, reasonUid);
+
+            if (sQuiescent) {
+                mDirty |= DIRTY_QUIESCENT;
+            }
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_POWER);
         }
@@ -1561,7 +1579,8 @@
         if (eventTime < mLastWakeTime
                 || mWakefulness == WAKEFULNESS_ASLEEP
                 || mWakefulness == WAKEFULNESS_DOZING
-                || !mBootCompleted || !mSystemReady) {
+                || !mSystemReady
+                || !mBootCompleted) {
             return false;
         }
 
@@ -2645,6 +2664,10 @@
                 | DIRTY_ACTUAL_DISPLAY_POWER_STATE_UPDATED | DIRTY_BOOT_COMPLETED
                 | DIRTY_SETTINGS | DIRTY_SCREEN_BRIGHTNESS_BOOST | DIRTY_VR_MODE_CHANGED |
                 DIRTY_QUIESCENT)) != 0) {
+            if ((dirty & DIRTY_QUIESCENT) != 0) {
+                sQuiescent = false;
+            }
+
             mDisplayPowerRequest.policy = getDesiredScreenPolicyLocked();
 
             // Determine appropriate screen brightness and auto-brightness adjustments.
@@ -2694,9 +2717,6 @@
                     mRequestWaitForNegativeProximity);
             mRequestWaitForNegativeProximity = false;
 
-            if ((dirty & DIRTY_QUIESCENT) != 0) {
-                sQuiescent = false;
-            }
             if (DEBUG_SPEW) {
                 Slog.d(TAG, "updateDisplayPowerStateLocked: mDisplayReady=" + mDisplayReady
                         + ", policy=" + mDisplayPowerRequest.policy
@@ -3327,7 +3347,7 @@
     }
 
     private void setAttentionLightInternal(boolean on, int color) {
-        Light light;
+        LogicalLight light;
         synchronized (mLock) {
             if (!mSystemReady) {
                 return;
@@ -3336,7 +3356,7 @@
         }
 
         // Control light outside of lock.
-        light.setFlashing(color, Light.LIGHT_FLASH_HARDWARE, (on ? 3 : 0), 0);
+        light.setFlashing(color, LogicalLight.LIGHT_FLASH_HARDWARE, (on ? 3 : 0), 0);
     }
 
     private void setDozeAfterScreenOffInternal(boolean on) {
@@ -3345,6 +3365,26 @@
         }
     }
 
+    private void suppressAmbientDisplayInternal(String token, boolean suppress) {
+        if (DEBUG_SPEW) {
+            Slog.d(TAG, "Suppress ambient display for token " + token + ": " + suppress);
+        }
+
+        if (suppress) {
+            mAmbientDisplaySuppressionTokens.add(token);
+        } else {
+            mAmbientDisplaySuppressionTokens.remove(token);
+        }
+
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.SUPPRESS_DOZE,
+                Math.min(mAmbientDisplaySuppressionTokens.size(), 1));
+    }
+
+    private String createAmbientDisplayToken(String token, int callingUid) {
+        return callingUid + "_" + token;
+    }
+
     private void boostScreenBrightnessInternal(long eventTime, int uid) {
         synchronized (mLock) {
             if (!mSystemReady || mWakefulness == WAKEFULNESS_ASLEEP
@@ -4995,6 +5035,61 @@
         }
 
         @Override // Binder call
+        public boolean isAmbientDisplayAvailable() {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.READ_DREAM_STATE, null);
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                return mAmbientDisplayConfiguration.ambientDisplayAvailable();
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override // Binder call
+        public void suppressAmbientDisplay(@NonNull String token, boolean suppress) {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.WRITE_DREAM_STATE, null);
+
+            final int uid = Binder.getCallingUid();
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                suppressAmbientDisplayInternal(createAmbientDisplayToken(token, uid), suppress);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override // Binder call
+        public boolean isAmbientDisplaySuppressedForToken(@NonNull String token) {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.READ_DREAM_STATE, null);
+
+            final int uid = Binder.getCallingUid();
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                return mAmbientDisplaySuppressionTokens.contains(
+                        createAmbientDisplayToken(token, uid));
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override // Binder call
+        public boolean isAmbientDisplaySuppressed() {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.READ_DREAM_STATE, null);
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                return mAmbientDisplaySuppressionTokens.size() > 0;
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override // Binder call
         public void boostScreenBrightness(long eventTime) {
             if (eventTime > SystemClock.uptimeMillis()) {
                 throw new IllegalArgumentException("event time must not be in the future");
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
index a62bb74..4b3746b 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
@@ -45,7 +45,6 @@
 import com.android.server.power.BatterySaverStateMachineProto;
 
 import java.io.PrintWriter;
-import java.text.NumberFormat;
 
 /**
  * Decides when to enable / disable battery saver.
@@ -796,8 +795,7 @@
 
             manager.notifyAsUser(TAG, DYNAMIC_MODE_NOTIFICATION_ID,
                     buildNotification(DYNAMIC_MODE_NOTIF_CHANNEL_ID,
-                            mContext.getResources().getString(
-                                    R.string.dynamic_mode_notification_title),
+                            R.string.dynamic_mode_notification_title,
                             R.string.dynamic_mode_notification_summary,
                             Intent.ACTION_POWER_USAGE_SUMMARY),
                     UserHandle.ALL);
@@ -813,13 +811,10 @@
             ensureNotificationChannelExists(manager, BATTERY_SAVER_NOTIF_CHANNEL_ID,
                     R.string.battery_saver_notification_channel_name);
 
-            final String percentage = NumberFormat.getPercentInstance()
-                    .format((double) mBatteryLevel / 100.0);
             manager.notifyAsUser(TAG, STICKY_AUTO_DISABLED_NOTIFICATION_ID,
                     buildNotification(BATTERY_SAVER_NOTIF_CHANNEL_ID,
-                            mContext.getResources().getString(
-                                    R.string.battery_saver_charged_notification_title, percentage),
-                            R.string.battery_saver_off_notification_summary,
+                            R.string.battery_saver_off_notification_title,
+                            R.string.battery_saver_charged_notification_summary,
                             Settings.ACTION_BATTERY_SAVER_SETTINGS),
                     UserHandle.ALL);
         });
@@ -834,13 +829,14 @@
         manager.createNotificationChannel(channel);
     }
 
-    private Notification buildNotification(@NonNull String channelId, @NonNull String title,
+    private Notification buildNotification(@NonNull String channelId, @StringRes int titleId,
             @StringRes int summaryId, @NonNull String intentAction) {
         Resources res = mContext.getResources();
         Intent intent = new Intent(intentAction);
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
         PendingIntent batterySaverIntent = PendingIntent.getActivity(
                 mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+        final String title = res.getString(titleId);
         final String summary = res.getString(summaryId);
 
         return new Notification.Builder(mContext, channelId)
diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
index c36d5ef..7164a30 100644
--- a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
+++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
@@ -24,8 +24,11 @@
 import android.os.IRecoverySystem;
 import android.os.IRecoverySystemProgressListener;
 import android.os.PowerManager;
+import android.os.Process;
 import android.os.RecoverySystem;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
 import android.os.SystemProperties;
 import android.util.Slog;
 
@@ -39,6 +42,7 @@
 
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
+import java.io.FileDescriptor;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
@@ -550,4 +554,28 @@
             IoUtils.closeQuietly(mLocalSocket);
         }
     }
+
+    private boolean isCallerShell() {
+        final int callingUid = Binder.getCallingUid();
+        return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID;
+    }
+
+    private void enforceShell() {
+        if (!isCallerShell()) {
+            throw new SecurityException("Caller must be shell");
+        }
+    }
+
+    @Override
+    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+            String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
+        enforceShell();
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            new RecoverySystemShellCommand(this).exec(
+                    this, in, out, err, args, callback, resultReceiver);
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemShellCommand.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemShellCommand.java
new file mode 100644
index 0000000..c6905b5
--- /dev/null
+++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemShellCommand.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.recoverysystem;
+
+import android.os.IRecoverySystem;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+/**
+ * Shell commands to call to {@link RecoverySystemService} from ADB.
+ */
+public class RecoverySystemShellCommand extends ShellCommand {
+    private final IRecoverySystem mService;
+
+    public RecoverySystemShellCommand(RecoverySystemService service) {
+        mService = service;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+        try {
+            switch (cmd) {
+                case "request-lskf":
+                    return requestLskf();
+                case "clear-lskf":
+                    return clearLskf();
+                case "reboot-and-apply":
+                    return rebootAndApply();
+                default:
+                    return handleDefaultCommands(cmd);
+            }
+        } catch (Exception e) {
+            getErrPrintWriter().println("Error while executing command: " + cmd);
+            e.printStackTrace(getErrPrintWriter());
+            return -1;
+        }
+    }
+
+    private int requestLskf() throws RemoteException {
+        String updateToken = getNextArgRequired();
+        boolean success = mService.requestLskf(updateToken, null);
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("Request LSKF status: " + (success ? "success" : "failure"));
+        return 0;
+    }
+
+    private int clearLskf() throws RemoteException {
+        boolean success = mService.clearLskf();
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("Clear LSKF: " + (success ? "success" : "failure"));
+        return 0;
+    }
+
+    private int rebootAndApply() throws RemoteException {
+        String updateToken = getNextArgRequired();
+        String rebootReason = getNextArgRequired();
+        boolean success = mService.rebootWithLskf(updateToken, rebootReason);
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("Reboot and apply status: " + (success ? "success" : "failure"));
+        return 0;
+    }
+
+    @Override
+    public void onHelp() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("Recovery system commands:");
+        pw.println("  request-lskf <token>");
+        pw.println("  clear-lskf");
+        pw.println("  reboot-and-apply <token> <reason>");
+    }
+}
diff --git a/services/core/java/com/android/server/role/RoleUserState.java b/services/core/java/com/android/server/role/RoleUserState.java
index d33c10c..9f4ca3c 100644
--- a/services/core/java/com/android/server/role/RoleUserState.java
+++ b/services/core/java/com/android/server/role/RoleUserState.java
@@ -23,6 +23,7 @@
 import android.annotation.WorkerThread;
 import android.os.Environment;
 import android.os.Handler;
+import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
@@ -34,22 +35,21 @@
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.dump.DualDumpOutputStream;
 import com.android.internal.util.function.pooled.PooledLambda;
-
-import libcore.io.IoUtils;
+import com.android.role.persistence.RolesPersistence;
+import com.android.role.persistence.RolesState;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
 
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.IOException;
-import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * Stores the state of roles for a user.
@@ -71,6 +71,8 @@
     private static final String ATTRIBUTE_NAME = "name";
     private static final String ATTRIBUTE_PACKAGES_HASH = "packagesHash";
 
+    private final RolesPersistence mPersistence = RolesPersistence.createInstance();
+
     @UserIdInt
     private final int mUserId;
 
@@ -350,9 +352,7 @@
 
     @WorkerThread
     private void writeFile() {
-        int version;
-        String packagesHash;
-        ArrayMap<String, ArraySet<String>> roles;
+        RolesState roles;
         synchronized (mLock) {
             if (mDestroyed) {
                 return;
@@ -360,90 +360,45 @@
 
             mWriteScheduled = false;
 
-            version = mVersion;
-            packagesHash = mPackagesHash;
-            roles = snapshotRolesLocked();
+            roles = new RolesState(mVersion, mPackagesHash,
+                    (Map<String, Set<String>>) (Map<String, ?>) snapshotRolesLocked());
         }
 
-        AtomicFile atomicFile = new AtomicFile(getFile(mUserId), "roles-" + mUserId);
-        FileOutputStream out = null;
-        try {
-            out = atomicFile.startWrite();
-
-            XmlSerializer serializer = Xml.newSerializer();
-            serializer.setOutput(out, StandardCharsets.UTF_8.name());
-            serializer.setFeature(
-                    "http://xmlpull.org/v1/doc/features.html#indent-output", true);
-            serializer.startDocument(null, true);
-
-            serializeRoles(serializer, version, packagesHash, roles);
-
-            serializer.endDocument();
-            atomicFile.finishWrite(out);
-            Slog.i(LOG_TAG, "Wrote roles.xml successfully");
-        } catch (IllegalArgumentException | IllegalStateException | IOException e) {
-            Slog.wtf(LOG_TAG, "Failed to write roles.xml, restoring backup", e);
-            if (out != null) {
-                atomicFile.failWrite(out);
-            }
-        } finally {
-            IoUtils.closeQuietly(out);
-        }
+        mPersistence.write(roles, UserHandle.of(mUserId));
     }
 
-    @WorkerThread
-    private void serializeRoles(@NonNull XmlSerializer serializer, int version,
-            @Nullable String packagesHash, @NonNull ArrayMap<String, ArraySet<String>> roles)
-            throws IOException {
-        serializer.startTag(null, TAG_ROLES);
-
-        serializer.attribute(null, ATTRIBUTE_VERSION, Integer.toString(version));
-
-        if (packagesHash != null) {
-            serializer.attribute(null, ATTRIBUTE_PACKAGES_HASH, packagesHash);
-        }
-
-        for (int i = 0, size = roles.size(); i < size; ++i) {
-            String roleName = roles.keyAt(i);
-            ArraySet<String> roleHolders = roles.valueAt(i);
-
-            serializer.startTag(null, TAG_ROLE);
-            serializer.attribute(null, ATTRIBUTE_NAME, roleName);
-            serializeRoleHolders(serializer, roleHolders);
-            serializer.endTag(null, TAG_ROLE);
-        }
-
-        serializer.endTag(null, TAG_ROLES);
-    }
-
-    @WorkerThread
-    private void serializeRoleHolders(@NonNull XmlSerializer serializer,
-            @NonNull ArraySet<String> roleHolders) throws IOException {
-        for (int i = 0, size = roleHolders.size(); i < size; ++i) {
-            String roleHolder = roleHolders.valueAt(i);
-
-            serializer.startTag(null, TAG_HOLDER);
-            serializer.attribute(null, ATTRIBUTE_NAME, roleHolder);
-            serializer.endTag(null, TAG_HOLDER);
-        }
-    }
-
-    /**
-     * Read the state from file.
-     */
     private void readFile() {
         synchronized (mLock) {
-            File file = getFile(mUserId);
-            try (FileInputStream in = new AtomicFile(file).openRead()) {
-                XmlPullParser parser = Xml.newPullParser();
-                parser.setInput(in, null);
-                parseXmlLocked(parser);
-                Slog.i(LOG_TAG, "Read roles.xml successfully");
-            } catch (FileNotFoundException e) {
-                Slog.i(LOG_TAG, "roles.xml not found");
-            } catch (XmlPullParserException | IOException e) {
-                throw new IllegalStateException("Failed to parse roles.xml: " + file, e);
+            RolesState roles = mPersistence.read(UserHandle.of(mUserId));
+            if (roles == null) {
+                readLegacyFileLocked();
+                scheduleWriteFileLocked();
+                return;
             }
+
+            mVersion = roles.getVersion();
+            mPackagesHash = roles.getPackagesHash();
+
+            mRoles.clear();
+            for (Map.Entry<String, Set<String>> entry : roles.getRoles().entrySet()) {
+                String roleName = entry.getKey();
+                ArraySet<String> roleHolders = new ArraySet<>(entry.getValue());
+                mRoles.put(roleName, roleHolders);
+            }
+        }
+    }
+
+    private void readLegacyFileLocked() {
+        File file = getFile(mUserId);
+        try (FileInputStream in = new AtomicFile(file).openRead()) {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(in, null);
+            parseXmlLocked(parser);
+            Slog.i(LOG_TAG, "Read roles.xml successfully");
+        } catch (FileNotFoundException e) {
+            Slog.i(LOG_TAG, "roles.xml not found");
+        } catch (XmlPullParserException | IOException e) {
+            throw new IllegalStateException("Failed to parse roles.xml: " + file, e);
         }
     }
 
@@ -590,7 +545,7 @@
                 throw new IllegalStateException("This RoleUserState has already been destroyed");
             }
             mWriteHandler.removeCallbacksAndMessages(null);
-            getFile(mUserId).delete();
+            mPersistence.delete(UserHandle.of(mUserId));
             mDestroyed = true;
         }
     }
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index 88c1564..5c0dd9a 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -62,7 +62,7 @@
 
     private static final String TAG = "RollbackManager";
 
-    @IntDef(flag = true, prefix = { "ROLLBACK_STATE_" }, value = {
+    @IntDef(prefix = { "ROLLBACK_STATE_" }, value = {
             ROLLBACK_STATE_ENABLING,
             ROLLBACK_STATE_AVAILABLE,
             ROLLBACK_STATE_COMMITTED,
@@ -92,6 +92,19 @@
      */
     static final int ROLLBACK_STATE_DELETED = 4;
 
+    @IntDef(flag = true, prefix = { "MATCH_" }, value = {
+            MATCH_APK_IN_APEX,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface RollbackInfoFlags {}
+
+    /**
+     * {@link RollbackInfo} flag: include {@code RollbackInfo} packages that are apk-in-apex.
+     * These packages do not have their own sessions. They are embedded in an apex which has a
+     * session id.
+     */
+    static final int MATCH_APK_IN_APEX = 1;
+
     /**
      * The session ID for the staged session if this rollback data represents a staged session,
      * {@code -1} otherwise.
@@ -159,6 +172,13 @@
     @Nullable public final String mInstallerPackageName;
 
     /**
+     * This array holds all of the rollback tokens associated with package sessions included in
+     * this rollback.
+     */
+    @GuardedBy("mLock")
+    private final IntArray mTokens = new IntArray();
+
+    /**
      * Constructs a new, empty Rollback instance.
      *
      * @param rollbackId the id of the rollback.
@@ -323,8 +343,8 @@
                 new VersionedPackage(packageName, newVersion),
                 new VersionedPackage(packageName, installedVersion),
                 new IntArray() /* pendingBackups */, new ArrayList<>() /* pendingRestores */,
-                isApex, new IntArray(), new SparseLongArray() /* ceSnapshotInodes */,
-                rollbackDataPolicy);
+                isApex, false /* isApkInApex */, new IntArray(),
+                new SparseLongArray() /* ceSnapshotInodes */, rollbackDataPolicy);
 
         synchronized (mLock) {
             info.getPackages().add(packageRollbackInfo);
@@ -334,6 +354,30 @@
     }
 
     /**
+     * Enables this rollback for the provided apk-in-apex.
+     *
+     * @return boolean True if the rollback was enabled successfully for the specified package.
+     */
+    boolean enableForPackageInApex(String packageName, long installedVersion,
+            int rollbackDataPolicy) {
+        // TODO(b/147666157): Extract the new version number of apk-in-apex
+        // The new version for the apk-in-apex is set to 0 for now. If the package is then further
+        // updated via non-staged install flow, then RollbackManagerServiceImpl#onPackageReplaced()
+        // will be called and this rollback will be deleted. Other ways of package update have not
+        // been handled yet.
+        PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(
+                new VersionedPackage(packageName, 0 /* newVersion */),
+                new VersionedPackage(packageName, installedVersion),
+                new IntArray() /* pendingBackups */, new ArrayList<>() /* pendingRestores */,
+                false /* isApex */, true /* isApkInApex */, new IntArray(),
+                new SparseLongArray() /* ceSnapshotInodes */, rollbackDataPolicy);
+        synchronized (mLock) {
+            info.getPackages().add(packageRollbackInfo);
+        }
+        return true;
+    }
+
+    /**
      * Snapshots user data for the provided package and user ids. Does nothing if this rollback is
      * not in the ENABLING state.
      */
@@ -428,6 +472,11 @@
                         parentSessionId);
 
                 for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
+                    if (pkgRollbackInfo.isApkInApex()) {
+                        // No need to issue a downgrade install request for apk-in-apex. It will
+                        // be rolled back when its parent apex is downgraded.
+                        continue;
+                    }
                     PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                             PackageInstaller.SessionParams.MODE_FULL_INSTALL);
                     String installerPackageName = mInstallerPackageName;
@@ -453,7 +502,8 @@
                             this, pkgRollbackInfo.getPackageName());
                     if (packageCodePaths == null) {
                         sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE,
-                                "Backup copy of package inaccessible");
+                                "Backup copy of package: "
+                                        + pkgRollbackInfo.getPackageName() + " is inaccessible");
                         return;
                     }
 
@@ -696,9 +746,50 @@
         }
     }
 
-    int getPackageCount() {
+    /**
+     * Returns the number of {@link PackageRollbackInfo} we are storing in this {@link Rollback}
+     * instance. By default, this method does not include apk-in-apex package in the count.
+     *
+     * @param flags Apk-in-apex packages can be included in the count by passing
+     * {@link Rollback#MATCH_APK_IN_APEX}
+     *
+     * @return Counts number of {@link PackageRollbackInfo} stored in the {@link Rollback}
+     * according to {@code flags} passed
+     */
+    int getPackageCount(@RollbackInfoFlags int flags) {
         synchronized (mLock) {
-            return info.getPackages().size();
+            List<PackageRollbackInfo> packages = info.getPackages();
+            if ((flags & MATCH_APK_IN_APEX) != 0) {
+                return packages.size();
+            }
+
+            int packagesWithoutApkInApex = 0;
+            for (PackageRollbackInfo rollbackInfo : packages) {
+                if (!rollbackInfo.isApkInApex()) {
+                    packagesWithoutApkInApex++;
+                }
+            }
+            return packagesWithoutApkInApex;
+        }
+    }
+
+    /**
+     * Adds a rollback token to be associated with this rollback. This may be used to
+     * identify which rollback should be removed in case {@link PackageManager} sends an
+     * {@link Intent#ACTION_CANCEL_ENABLE_ROLLBACK} intent.
+     */
+    void addToken(int token) {
+        synchronized (mLock) {
+            mTokens.add(token);
+        }
+    }
+
+    /**
+     * Returns true if this rollback is associated with the provided {@code token}.
+     */
+    boolean hasToken(int token) {
+        synchronized (mLock) {
+            return mTokens.indexOf(token) != -1;
         }
     }
 
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index e29d1a7..de48939 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -43,6 +43,7 @@
 import android.os.Binder;
 import android.os.Environment;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.HandlerThread;
 import android.os.Process;
 import android.os.SystemClock;
@@ -78,6 +79,7 @@
 import java.util.Random;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
@@ -134,6 +136,7 @@
 
     private final Context mContext;
     private final HandlerThread mHandlerThread;
+    private final Executor mExecutor;
     private final Installer mInstaller;
     private final RollbackPackageHealthObserver mPackageHealthObserver;
     private final AppDataRollbackHelper mAppDataRollbackHelper;
@@ -173,6 +176,7 @@
         mHandlerThread = new HandlerThread("RollbackManagerServiceHandler");
         mHandlerThread.start();
         Watchdog.getInstance().addThread(getHandler(), HANDLER_THREAD_TIMEOUT_DURATION_MILLIS);
+        mExecutor = new HandlerExecutor(getHandler());
 
         for (UserInfo userInfo : UserManager.get(mContext).getUsers(true)) {
             registerUserCallbacks(userInfo.getUserHandle());
@@ -235,12 +239,17 @@
                         Slog.v(TAG, "broadcast=ACTION_CANCEL_ENABLE_ROLLBACK token=" + token);
                     }
                     synchronized (mLock) {
-                        for (NewRollback rollback : mNewRollbacks) {
-                            if (rollback.hasToken(token)) {
-                                rollback.setCancelled();
-                                return;
+                        NewRollback found = null;
+                        for (NewRollback newRollback : mNewRollbacks) {
+                            if (newRollback.rollback.hasToken(token)) {
+                                found = newRollback;
+                                break;
                             }
                         }
+                        if (found != null) {
+                            mNewRollbacks.remove(found);
+                            found.rollback.delete(mAppDataRollbackHelper);
+                        }
                     }
                 }
             }
@@ -404,7 +413,6 @@
 
         CountDownLatch latch = new CountDownLatch(1);
         getHandler().post(() -> {
-            updateRollbackLifetimeDurationInMillis();
             synchronized (mLock) {
                 mRollbacks.clear();
                 mRollbacks.addAll(mRollbackStore.loadRollbacks());
@@ -433,10 +441,14 @@
                     rollback.delete(mAppDataRollbackHelper);
                 }
             }
-            for (NewRollback newRollback : mNewRollbacks) {
+            Iterator<NewRollback> iter2 = mNewRollbacks.iterator();
+            while (iter2.hasNext()) {
+                NewRollback newRollback = iter2.next();
                 if (newRollback.rollback.includesPackage(packageName)) {
-                    newRollback.setCancelled();
+                    iter2.remove();
+                    newRollback.rollback.delete(mAppDataRollbackHelper);
                 }
+
             }
         }
     }
@@ -511,11 +523,13 @@
 
     @AnyThread
     void onBootCompleted() {
-        getHandler().post(() -> updateRollbackLifetimeDurationInMillis());
-        // Also posts to handler thread
-        scheduleExpiration(0);
+        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ROLLBACK_BOOT,
+                mExecutor, properties -> updateRollbackLifetimeDurationInMillis());
 
         getHandler().post(() -> {
+            updateRollbackLifetimeDurationInMillis();
+            runExpiration();
+
             // Check to see if any rollback-enabled staged sessions or staged
             // rollback sessions been applied.
             List<Rollback> enabling = new ArrayList<>();
@@ -798,7 +812,7 @@
                 mNewRollbacks.add(newRollback);
             }
         }
-        newRollback.addToken(token);
+        newRollback.rollback.addToken(token);
 
         return enableRollbackForPackageSession(newRollback.rollback, packageSession);
     }
@@ -891,9 +905,36 @@
         }
 
         ApplicationInfo appInfo = pkgInfo.applicationInfo;
-        return rollback.enableForPackage(packageName, newPackage.versionCode,
+        boolean success = rollback.enableForPackage(packageName, newPackage.versionCode,
                 pkgInfo.getLongVersionCode(), isApex, appInfo.sourceDir,
                 appInfo.splitSourceDirs, session.rollbackDataPolicy);
+        if (!success) {
+            return success;
+        }
+
+        if (isApex) {
+            // Check if this apex contains apks inside it. If true, then they should be added as
+            // a RollbackPackageInfo into this rollback
+            final PackageManagerInternal pmi = LocalServices.getService(
+                    PackageManagerInternal.class);
+            List<String> apksInApex = pmi.getApksInApex(packageName);
+            for (String apkInApex : apksInApex) {
+                // Get information about the currently installed package.
+                final PackageInfo apkPkgInfo;
+                try {
+                    apkPkgInfo = getPackageInfo(apkInApex);
+                } catch (PackageManager.NameNotFoundException e) {
+                    // TODO: Support rolling back fresh package installs rather than
+                    // fail here. Test this case.
+                    Slog.e(TAG, apkInApex + " is not installed");
+                    return false;
+                }
+                success = rollback.enableForPackageInApex(
+                        apkInApex, apkPkgInfo.getLongVersionCode(), session.rollbackDataPolicy);
+                if (!success) return success;
+            }
+        }
+        return true;
     }
 
     @Override
@@ -907,9 +948,13 @@
         getHandler().post(() -> {
             snapshotUserDataInternal(packageName, userIds);
             restoreUserDataInternal(packageName, userIds, appId, seInfo);
-            final PackageManagerInternal pmi = LocalServices.getService(
-                    PackageManagerInternal.class);
-            pmi.finishPackageInstall(token, false);
+            // When this method is called as part of the install flow, a positive token number is
+            // passed to it. Need to notify the PackageManager when we are done.
+            if (token > 0) {
+                final PackageManagerInternal pmi = LocalServices.getService(
+                        PackageManagerInternal.class);
+                pmi.finishPackageInstall(token, false);
+            }
         });
     }
 
@@ -1189,13 +1234,11 @@
             Slog.v(TAG, "completeEnableRollback id=" + rollback.info.getRollbackId());
         }
 
-        if (newRollback.isCancelled()) {
-            Slog.e(TAG, "Rollback has been cancelled by PackageManager");
-            rollback.delete(mAppDataRollbackHelper);
-            return null;
-        }
-
-        if (rollback.getPackageCount() != newRollback.getPackageSessionIdCount()) {
+        // We are checking if number of packages (excluding apk-in-apex) we enabled for rollback is
+        // equal to the number of sessions we are installing, to ensure we didn't skip enabling
+        // of any sessions. If we successfully enable an apex, then we can assume we enabled
+        // rollback for the embedded apk-in-apex, if any.
+        if (rollback.getPackageCount(0 /*flags*/) != newRollback.getPackageSessionIdCount()) {
             Slog.e(TAG, "Failed to enable rollback for all packages in session.");
             rollback.delete(mAppDataRollbackHelper);
             return null;
@@ -1300,22 +1343,12 @@
         public final Rollback rollback;
 
         /**
-         * This array holds all of the rollback tokens associated with package sessions included in
-         * this rollback.
-         */
-        @GuardedBy("mNewRollbackLock")
-        private final IntArray mTokens = new IntArray();
-
-        /**
          * Session ids for all packages in the install. For multi-package sessions, this is the list
          * of child session ids. For normal sessions, this list is a single element with the normal
          * session id.
          */
         private final int[] mPackageSessionIds;
 
-        @GuardedBy("mNewRollbackLock")
-        private boolean mIsCancelled = false;
-
         /**
          * The number of sessions in the install which are notified with success by
          * {@link PackageInstaller.SessionCallback#onFinished(int, boolean)}.
@@ -1332,52 +1365,6 @@
         }
 
         /**
-         * Adds a rollback token to be associated with this NewRollback. This may be used to
-         * identify which rollback should be cancelled in case {@link PackageManager} sends an
-         * {@link Intent#ACTION_CANCEL_ENABLE_ROLLBACK} intent.
-         */
-        void addToken(int token) {
-            synchronized (mNewRollbackLock) {
-                mTokens.add(token);
-            }
-        }
-
-        /**
-         * Returns true if this NewRollback is associated with the provided {@code token}.
-         */
-        boolean hasToken(int token) {
-            synchronized (mNewRollbackLock) {
-                return mTokens.indexOf(token) != -1;
-            }
-        }
-
-        /**
-         * Returns true if this NewRollback has been cancelled.
-         *
-         * <p>Rollback could be invalidated and cancelled if RollbackManager receives
-         * {@link Intent#ACTION_CANCEL_ENABLE_ROLLBACK} from {@link PackageManager}.
-         *
-         * <p>The main underlying assumption here is that if enabling the rollback times out, then
-         * {@link PackageManager} will NOT send
-         * {@link PackageInstaller.SessionCallback#onFinished(int, boolean)} before it broadcasts
-         * {@link Intent#ACTION_CANCEL_ENABLE_ROLLBACK}.
-         */
-        boolean isCancelled() {
-            synchronized (mNewRollbackLock) {
-                return mIsCancelled;
-            }
-        }
-
-        /**
-         * Sets this NewRollback to be marked as cancelled.
-         */
-        void setCancelled() {
-            synchronized (mNewRollbackLock) {
-                mIsCancelled = true;
-            }
-        }
-
-        /**
          * Returns true if this NewRollback contains the provided {@code packageSessionId}.
          */
         boolean containsSessionId(int packageSessionId) {
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index a0ef8cf..951f1a4 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -28,6 +28,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.VersionedPackage;
@@ -45,20 +46,21 @@
 import android.util.Slog;
 import android.util.StatsLog;
 
-import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.PackageWatchdog;
 import com.android.server.PackageWatchdog.FailureReasons;
 import com.android.server.PackageWatchdog.PackageHealthObserver;
 import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
 
-import libcore.io.IoUtils;
-
+import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileOutputStream;
+import java.io.FileReader;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 
@@ -72,11 +74,12 @@
 public final class RollbackPackageHealthObserver implements PackageHealthObserver {
     private static final String TAG = "RollbackPackageHealthObserver";
     private static final String NAME = "rollback-observer";
-    private static final int INVALID_ROLLBACK_ID = -1;
+
+    private static final String LOGGING_PARENT_KEY = "android.content.pm.LOGGING_PARENT";
 
     private final Context mContext;
     private final Handler mHandler;
-    private final File mLastStagedRollbackIdFile;
+    private final File mLastStagedRollbackIdsFile;
     // Staged rollback ids that have been committed but their session is not yet ready
     @GuardedBy("mPendingStagedRollbackIds")
     private final Set<Integer> mPendingStagedRollbackIds = new ArraySet<>();
@@ -88,7 +91,7 @@
         mHandler = handlerThread.getThreadHandler();
         File dataDir = new File(Environment.getDataDirectory(), "rollback-observer");
         dataDir.mkdirs();
-        mLastStagedRollbackIdFile = new File(dataDir, "last-staged-rollback-id");
+        mLastStagedRollbackIdsFile = new File(dataDir, "last-staged-rollback-ids");
         PackageWatchdog.getInstance(mContext).registerHealthObserver(this);
     }
 
@@ -150,22 +153,25 @@
 
     private void onBootCompleted() {
         RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
-        PackageInstaller packageInstaller = mContext.getPackageManager().getPackageInstaller();
-        String moduleMetadataPackageName = getModuleMetadataPackageName();
-
         if (!rollbackManager.getAvailableRollbacks().isEmpty()) {
             // TODO(gavincorkery): Call into Package Watchdog from outside the observer
             PackageWatchdog.getInstance(mContext).scheduleCheckAndMitigateNativeCrashes();
         }
 
-        int rollbackId = popLastStagedRollbackId();
-        if (rollbackId == INVALID_ROLLBACK_ID) {
-            // No staged rollback before reboot
-            return;
+        List<Integer> rollbackIds = popLastStagedRollbackIds();
+        Iterator<Integer> rollbackIterator = rollbackIds.iterator();
+        while (rollbackIterator.hasNext()) {
+            int rollbackId = rollbackIterator.next();
+            logRollbackStatusOnBoot(rollbackId, rollbackManager.getRecentlyCommittedRollbacks());
         }
+    }
+
+    private void logRollbackStatusOnBoot(int rollbackId,
+            List<RollbackInfo> recentlyCommittedRollbacks) {
+        PackageInstaller packageInstaller = mContext.getPackageManager().getPackageInstaller();
 
         RollbackInfo rollback = null;
-        for (RollbackInfo info : rollbackManager.getRecentlyCommittedRollbacks()) {
+        for (RollbackInfo info : recentlyCommittedRollbacks) {
             if (rollbackId == info.getRollbackId()) {
                 rollback = info;
                 break;
@@ -177,13 +183,26 @@
             return;
         }
 
-        // Use the version of the metadata package that was installed before
-        // we rolled back for logging purposes.
-        VersionedPackage oldModuleMetadataPackage = null;
+        // Identify the logging parent for this rollback. When all configurations are correct, each
+        // package in the rollback refers to the same logging parent, except for the logging parent
+        // itself. If a logging parent is missing for a package, we use the package itself for
+        // logging. This might result in over-logging, but we prefer this over no logging.
+        final Set<String> loggingPackageNames = new ArraySet<>();
         for (PackageRollbackInfo packageRollback : rollback.getPackages()) {
-            if (packageRollback.getPackageName().equals(moduleMetadataPackageName)) {
-                oldModuleMetadataPackage = packageRollback.getVersionRolledBackFrom();
-                break;
+            final String loggingParentName = getLoggingParentName(packageRollback.getPackageName());
+            if (loggingParentName != null) {
+                loggingPackageNames.add(loggingParentName);
+            } else {
+                loggingPackageNames.add(packageRollback.getPackageName());
+            }
+        }
+
+        // Use the version of the logging parent that was installed before
+        // we rolled back for logging purposes.
+        final List<VersionedPackage> oldLoggingPackages = new ArrayList<>();
+        for (PackageRollbackInfo packageRollback : rollback.getPackages()) {
+            if (loggingPackageNames.contains(packageRollback.getPackageName())) {
+                oldLoggingPackages.add(packageRollback.getVersionRolledBackFrom());
             }
         }
 
@@ -193,16 +212,17 @@
             Slog.e(TAG, "On boot completed, could not load session id " + sessionId);
             return;
         }
-        if (sessionInfo.isStagedSessionApplied()) {
-            logEvent(oldModuleMetadataPackage,
-                    StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
-                    WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, "");
-        } else if (sessionInfo.isStagedSessionReady()) {
-            // TODO: What do for staged session ready but not applied
-        } else {
-            logEvent(oldModuleMetadataPackage,
-                    StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
-                    WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, "");
+
+        for (VersionedPackage oldLoggingPackage : oldLoggingPackages) {
+            if (sessionInfo.isStagedSessionApplied()) {
+                logEvent(oldLoggingPackage,
+                        StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
+                        WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, "");
+            } else if (sessionInfo.isStagedSessionFailed()) {
+                logEvent(oldLoggingPackage,
+                        StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
+                        WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, "");
+            }
         }
     }
 
@@ -213,44 +233,51 @@
                 if (packageRollback.getVersionRolledBackFrom().equals(failedPackage)) {
                     return rollback;
                 }
+                // TODO(b/147666157): Extract version number of apk-in-apex so that we don't have
+                //  to rely on complicated reasoning as below
+
+                // Due to b/147666157, for apk in apex, we do not know the version we are rolling
+                // back from. But if a package X is embedded in apex A exclusively (not embedded in
+                // any other apex), which is not guaranteed, then it is sufficient to check only
+                // package names here, as the version of failedPackage and the PackageRollbackInfo
+                // can't be different. If failedPackage has a higher version, then it must have
+                // been updated somehow. There are two ways: it was updated by an update of apex A
+                // or updated directly as apk. In both cases, this rollback would have gotten
+                // expired when onPackageReplaced() was called. Since the rollback exists, it has
+                // same version as failedPackage.
+                if (packageRollback.isApkInApex()
+                        && packageRollback.getVersionRolledBackFrom().getPackageName()
+                        .equals(failedPackage.getPackageName())) {
+                    return rollback;
+                }
             }
         }
         return null;
     }
 
     @Nullable
-    private String getModuleMetadataPackageName() {
-        String packageName = mContext.getResources().getString(
-                R.string.config_defaultModuleMetadataProvider);
-        if (TextUtils.isEmpty(packageName)) {
-            return null;
-        }
-        return packageName;
-    }
-
-    @Nullable
-    private VersionedPackage getModuleMetadataPackage() {
-        String packageName = getModuleMetadataPackageName();
-        if (packageName == null) {
-            return null;
-        }
-
+    private String getLoggingParentName(String packageName) {
+        PackageManager packageManager = mContext.getPackageManager();
         try {
-            return new VersionedPackage(packageName, mContext.getPackageManager().getPackageInfo(
-                            packageName, 0 /* flags */).getLongVersionCode());
-        } catch (PackageManager.NameNotFoundException e) {
-            Slog.w(TAG, "Module metadata provider not found");
+            ApplicationInfo ai = packageManager.getApplicationInfo(packageName,
+                    PackageManager.GET_META_DATA);
+            if (ai.metaData == null) {
+                return null;
+            }
+            return ai.metaData.getString(LOGGING_PARENT_KEY);
+        } catch (Exception e) {
+            Slog.w(TAG, "Unable to discover logging parent package: " + packageName, e);
             return null;
         }
     }
 
     private BroadcastReceiver listenForStagedSessionReady(RollbackManager rollbackManager,
-            int rollbackId, @Nullable VersionedPackage moduleMetadataPackage) {
+            int rollbackId, @Nullable VersionedPackage logPackage) {
         BroadcastReceiver sessionUpdatedReceiver = new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
                 handleStagedSessionChange(rollbackManager,
-                        rollbackId, this /* BroadcastReceiver */, moduleMetadataPackage);
+                        rollbackId, this /* BroadcastReceiver */, logPackage);
             }
         };
         IntentFilter sessionUpdatedFilter =
@@ -260,7 +287,7 @@
     }
 
     private void handleStagedSessionChange(RollbackManager rollbackManager, int rollbackId,
-            BroadcastReceiver listener, @Nullable VersionedPackage moduleMetadataPackage) {
+            BroadcastReceiver listener, @Nullable VersionedPackage logPackage) {
         PackageInstaller packageInstaller =
                 mContext.getPackageManager().getPackageInstaller();
         List<RollbackInfo> recentRollbacks =
@@ -274,16 +301,19 @@
                         packageInstaller.getSessionInfo(sessionId);
                 if (sessionInfo.isStagedSessionReady() && markStagedSessionHandled(rollbackId)) {
                     mContext.unregisterReceiver(listener);
-                    saveLastStagedRollbackId(rollbackId);
-                    logEvent(moduleMetadataPackage,
+                    if (logPackage != null) {
+                        // We save the rollback id so that after reboot, we can log if rollback was
+                        // successful or not. If logPackage is null, then there is nothing to log.
+                        saveStagedRollbackId(rollbackId);
+                    }
+                    logEvent(logPackage,
                             StatsLog
                             .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED,
                             WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN,
                             "");
-                    mContext.getSystemService(PowerManager.class).reboot("Rollback staged install");
                 } else if (sessionInfo.isStagedSessionFailed()
                         && markStagedSessionHandled(rollbackId)) {
-                    logEvent(moduleMetadataPackage,
+                    logEvent(logPackage,
                             StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
                             WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN,
                             "");
@@ -291,6 +321,11 @@
                 }
             }
         }
+
+        // Wait for all pending staged sessions to get handled before rebooting.
+        if (isPendingStagedSessionsEmpty()) {
+            mContext.getSystemService(PowerManager.class).reboot("Rollback staged install");
+        }
     }
 
     /**
@@ -303,34 +338,49 @@
         }
     }
 
-    private void saveLastStagedRollbackId(int stagedRollbackId) {
+    /**
+     * Returns {@code true} if all pending staged rollback sessions were marked as handled,
+     * {@code false} if there is any left.
+     */
+    private boolean isPendingStagedSessionsEmpty() {
+        synchronized (mPendingStagedRollbackIds) {
+            return mPendingStagedRollbackIds.isEmpty();
+        }
+    }
+
+    private void saveStagedRollbackId(int stagedRollbackId) {
         try {
-            FileOutputStream fos = new FileOutputStream(mLastStagedRollbackIdFile);
+            FileOutputStream fos = new FileOutputStream(
+                    mLastStagedRollbackIdsFile, /*append*/true);
             PrintWriter pw = new PrintWriter(fos);
-            pw.println(stagedRollbackId);
+            pw.append(",").append(String.valueOf(stagedRollbackId));
             pw.flush();
             FileUtils.sync(fos);
             pw.close();
         } catch (IOException e) {
             Slog.e(TAG, "Failed to save last staged rollback id", e);
-            mLastStagedRollbackIdFile.delete();
+            mLastStagedRollbackIdsFile.delete();
         }
     }
 
-    private int popLastStagedRollbackId() {
-        int rollbackId = INVALID_ROLLBACK_ID;
-        if (!mLastStagedRollbackIdFile.exists()) {
-            return rollbackId;
+    private List<Integer> popLastStagedRollbackIds() {
+        try (BufferedReader reader =
+                     new BufferedReader(new FileReader(mLastStagedRollbackIdsFile))) {
+            String line = reader.readLine();
+            // line is of format : ",id1,id2,id3....,idn"
+            String[] sessionIdsStr = line.split(",");
+            ArrayList<Integer> result = new ArrayList<>();
+            for (String sessionIdStr: sessionIdsStr) {
+                if (!TextUtils.isEmpty(sessionIdStr.trim())) {
+                    result.add(Integer.parseInt(sessionIdStr));
+                }
+            }
+            return result;
+        } catch (Exception ignore) {
+            return Collections.emptyList();
+        } finally {
+            mLastStagedRollbackIdsFile.delete();
         }
-
-        try {
-            rollbackId = Integer.parseInt(
-                    IoUtils.readFileAsString(mLastStagedRollbackIdFile.getAbsolutePath()).trim());
-        } catch (IOException | NumberFormatException e) {
-            Slog.e(TAG, "Failed to retrieve last staged rollback id", e);
-        }
-        mLastStagedRollbackIdFile.delete();
-        return rollbackId;
     }
 
     private static String rollbackTypeToString(int type) {
@@ -348,15 +398,32 @@
         }
     }
 
-    private static void logEvent(@Nullable VersionedPackage moduleMetadataPackage, int type,
-            int rollbackReason, @NonNull String failingPackageName) {
-        Slog.i(TAG, "Watchdog event occurred of type: " + rollbackTypeToString(type));
-        if (moduleMetadataPackage != null) {
-            StatsLog.logWatchdogRollbackOccurred(type, moduleMetadataPackage.getPackageName(),
-                    moduleMetadataPackage.getVersionCode(), rollbackReason, failingPackageName);
+    private static String rollbackReasonToString(int reason) {
+        switch (reason) {
+            case StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH:
+                return "REASON_NATIVE_CRASH";
+            case StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK:
+                return "REASON_EXPLICIT_HEALTH_CHECK";
+            case StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH:
+                return "REASON_APP_CRASH";
+            case StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING:
+                return "REASON_APP_NOT_RESPONDING";
+            default:
+                return "UNKNOWN";
         }
     }
 
+    private static void logEvent(@Nullable VersionedPackage logPackage, int type,
+            int rollbackReason, @NonNull String failingPackageName) {
+        Slog.i(TAG, "Watchdog event occurred with type: " + rollbackTypeToString(type)
+                + " logPackage: " + logPackage
+                + " rollbackReason: " + rollbackReasonToString(rollbackReason)
+                + " failedPackageName: " + failingPackageName);
+        if (logPackage != null) {
+            StatsLog.logWatchdogRollbackOccurred(type, logPackage.getPackageName(),
+                    logPackage.getLongVersionCode(), rollbackReason, failingPackageName);
+        }
+    }
 
     /**
      * Returns true if the package name is the name of a module.
@@ -387,10 +454,21 @@
         } else {
             failedPackageToLog = failedPackage.getPackageName();
         }
-        final VersionedPackage logPackage = isModule(failedPackage.getPackageName())
-                ? getModuleMetadataPackage()
-                : null;
+        VersionedPackage logPackageTemp = null;
+        if (isModule(failedPackage.getPackageName())) {
+            String logPackageName = getLoggingParentName(failedPackage.getPackageName());
+            if (logPackageName == null) {
+                logPackageName = failedPackage.getPackageName();
+            }
+            try {
+                logPackageTemp = new VersionedPackage(logPackageName, mContext.getPackageManager()
+                        .getPackageInfo(logPackageName, 0 /* flags */).getLongVersionCode());
+            } catch (PackageManager.NameNotFoundException e) {
+                logPackageTemp = null;
+            }
+        }
 
+        final VersionedPackage logPackage = logPackageTemp;
         logEvent(logPackage,
                 StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE,
                 reasonToLog, failedPackageToLog);
@@ -414,6 +492,9 @@
                             reasonToLog, failedPackageToLog);
                 }
             } else {
+                if (rollback.isStaged()) {
+                    markStagedSessionHandled(rollback.getRollbackId());
+                }
                 logEvent(logPackage,
                         StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
                         reasonToLog, failedPackageToLog);
@@ -431,6 +512,16 @@
         RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
         List<RollbackInfo> rollbacks = rollbackManager.getAvailableRollbacks();
 
+        // Add all rollback ids to mPendingStagedRollbackIds, so that we do not reboot before all
+        // pending staged rollbacks are handled.
+        synchronized (mPendingStagedRollbackIds) {
+            for (RollbackInfo rollback : rollbacks) {
+                if (rollback.isStaged()) {
+                    mPendingStagedRollbackIds.add(rollback.getRollbackId());
+                }
+            }
+        }
+
         for (RollbackInfo rollback : rollbacks) {
             VersionedPackage sample = rollback.getPackages().get(0).getVersionRolledBackFrom();
             rollbackPackage(rollback, sample, PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index df75a29..bbcd0de 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -341,6 +341,7 @@
         json.put("pendingRestores", convertToJsonArray(pendingRestores));
 
         json.put("isApex", info.isApex());
+        json.put("isApkInApex", info.isApkInApex());
 
         // Field is named 'installedUsers' for legacy reasons.
         json.put("installedUsers", convertToJsonArray(snapshottedUsers));
@@ -364,6 +365,7 @@
                 json.getJSONArray("pendingRestores"));
 
         final boolean isApex = json.getBoolean("isApex");
+        final boolean isApkInApex = json.getBoolean("isApkInApex");
 
         // Field is named 'installedUsers' for legacy reasons.
         final IntArray snapshottedUsers = convertToIntArray(json.getJSONArray("installedUsers"));
@@ -375,8 +377,8 @@
                 PackageManager.RollbackDataPolicy.RESTORE);
 
         return new PackageRollbackInfo(versionRolledBackFrom, versionRolledBackTo,
-                pendingBackups, pendingRestores, isApex, snapshottedUsers, ceSnapshotInodes,
-                rollbackDataPolicy);
+                pendingBackups, pendingRestores, isApex, isApkInApex, snapshottedUsers,
+                ceSnapshotInodes, rollbackDataPolicy);
     }
 
     private static JSONArray versionedPackagesToJson(List<VersionedPackage> packages)
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
index 0b89646..a641f06 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
@@ -21,8 +21,10 @@
 import android.hardware.audio.common.V2_0.Uuid;
 import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback;
 import android.hardware.soundtrigger.V2_3.ISoundTriggerHw;
+import android.hardware.soundtrigger.V2_3.Properties;
 import android.media.audio.common.AudioConfig;
 import android.media.audio.common.AudioOffloadInfo;
+import android.media.soundtrigger_middleware.AudioCapabilities;
 import android.media.soundtrigger_middleware.ConfidenceLevel;
 import android.media.soundtrigger_middleware.ModelParameter;
 import android.media.soundtrigger_middleware.ModelParameterRange;
@@ -69,6 +71,15 @@
         return aidlProperties;
     }
 
+    static @NonNull SoundTriggerModuleProperties hidl2aidlProperties(
+            @NonNull Properties hidlProperties) {
+        SoundTriggerModuleProperties aidlProperties = hidl2aidlProperties(hidlProperties.base);
+        aidlProperties.supportedModelArch = hidlProperties.supportedModelArch;
+        aidlProperties.audioCapabilities =
+                hidl2aidlAudioCapabilities(hidlProperties.audioCapabilities);
+        return aidlProperties;
+    }
+
     static @NonNull
     String hidl2aidlUuid(@NonNull Uuid hidlUuid) {
         if (hidlUuid.node == null || hidlUuid.node.length != 6) {
@@ -201,16 +212,17 @@
         return hidlModel;
     }
 
-    static @NonNull
-    ISoundTriggerHw.RecognitionConfig aidl2hidlRecognitionConfig(
+    static @NonNull android.hardware.soundtrigger.V2_3.RecognitionConfig aidl2hidlRecognitionConfig(
             @NonNull RecognitionConfig aidlConfig) {
-        ISoundTriggerHw.RecognitionConfig hidlConfig = new ISoundTriggerHw.RecognitionConfig();
-        hidlConfig.header.captureRequested = aidlConfig.captureRequested;
+        android.hardware.soundtrigger.V2_3.RecognitionConfig hidlConfig =
+                new android.hardware.soundtrigger.V2_3.RecognitionConfig();
+        hidlConfig.base.header.captureRequested = aidlConfig.captureRequested;
         for (PhraseRecognitionExtra aidlPhraseExtra : aidlConfig.phraseRecognitionExtras) {
-            hidlConfig.header.phrases.add(aidl2hidlPhraseRecognitionExtra(aidlPhraseExtra));
+            hidlConfig.base.header.phrases.add(aidl2hidlPhraseRecognitionExtra(aidlPhraseExtra));
         }
-        hidlConfig.data = HidlMemoryUtil.byteArrayToHidlMemory(aidlConfig.data,
+        hidlConfig.base.data = HidlMemoryUtil.byteArrayToHidlMemory(aidlConfig.data,
                 "SoundTrigger RecognitionConfig");
+        hidlConfig.audioCapabilities = aidlConfig.audioCapabilities;
         return hidlConfig;
     }
 
@@ -387,4 +399,17 @@
                 return android.hardware.soundtrigger.V2_3.ModelParameter.INVALID;
         }
     }
+
+    static int hidl2aidlAudioCapabilities(int hidlCapabilities) {
+        int aidlCapabilities = 0;
+        if ((hidlCapabilities
+                & android.hardware.soundtrigger.V2_3.AudioCapabilities.ECHO_CANCELLATION) != 0) {
+            aidlCapabilities |= AudioCapabilities.ECHO_CANCELLATION;
+        }
+        if ((hidlCapabilities
+                & android.hardware.soundtrigger.V2_3.AudioCapabilities.NOISE_SUPPRESSION) != 0) {
+            aidlCapabilities |= AudioCapabilities.NOISE_SUPPRESSION;
+        }
+        return aidlCapabilities;
+    }
 }
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/HalFactory.java b/services/core/java/com/android/server/soundtrigger_middleware/HalFactory.java
new file mode 100644
index 0000000..b19e2ed
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/HalFactory.java
@@ -0,0 +1,31 @@
+/*
+ * 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.soundtrigger_middleware;
+
+import android.hardware.soundtrigger.V2_0.ISoundTriggerHw;
+
+/**
+ * A factory for creating instances of {@link ISoundTriggerHw}.
+ *
+ * @hide
+ */
+public interface HalFactory {
+    /**
+     * @return An instance of {@link ISoundTriggerHw}.
+     */
+    ISoundTriggerHw create();
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
index f0a0d83..a42d292 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
@@ -66,12 +66,25 @@
         return model_2_0;
     }
 
-    static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig convertRecognitionConfig_2_1_to_2_0(
-            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config) {
+    static android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig convertRecognitionConfig_2_3_to_2_1(
+            android.hardware.soundtrigger.V2_3.RecognitionConfig config) {
+        return config.base;
+    }
+
+    static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig convertRecognitionConfig_2_3_to_2_0(
+            android.hardware.soundtrigger.V2_3.RecognitionConfig config) {
         android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config_2_0 =
-                config.header;
+                config.base.header;
         // Note: this mutates the input!
-        config_2_0.data = HidlMemoryUtil.hidlMemoryToByteList(config.data);
+        config_2_0.data = HidlMemoryUtil.hidlMemoryToByteList(config.base.data);
         return config_2_0;
     }
+
+    static android.hardware.soundtrigger.V2_3.Properties convertProperties_2_0_to_2_3(
+            android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties properties) {
+        android.hardware.soundtrigger.V2_3.Properties properties_2_3 =
+                new android.hardware.soundtrigger.V2_3.Properties();
+        properties_2_3.base = properties;
+        return properties_2_3;
+    }
 }
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
index 81252c9..8b434bd 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
@@ -16,7 +16,6 @@
 
 package com.android.server.soundtrigger_middleware;
 
-import android.hardware.soundtrigger.V2_3.ISoundTriggerHw;
 import android.hardware.soundtrigger.V2_3.ModelParameterRange;
 import android.hidl.base.V1_0.IBase;
 import android.os.IHwBinder;
@@ -54,9 +53,10 @@
  */
 public interface ISoundTriggerHw2 {
     /**
-     * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#getProperties(android.hardware.soundtrigger.V2_0.ISoundTriggerHw.getPropertiesCallback
+     * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#getPropertiesEx(
+     * android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getPropertiesExCallback)
      */
-    android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties getProperties();
+    android.hardware.soundtrigger.V2_3.Properties getProperties();
 
     /**
      * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#loadSoundModel_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel,
@@ -92,12 +92,12 @@
     void stopAllRecognitions();
 
     /**
-     * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#startRecognition_2_1(int,
-     * android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig,
+     * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#startRecognition_2_3(int,
+     * android.hardware.soundtrigger.V2_3.RecognitionConfig,
      * android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback, int)
      */
     void startRecognition(int modelHandle,
-            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config,
+            android.hardware.soundtrigger.V2_3.RecognitionConfig config,
             SoundTriggerHw2Compat.Callback callback, int cookie);
 
     /**
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
index 4a852c4..2f087f4 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
@@ -112,18 +112,23 @@
     }
 
     @Override
-    public android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties getProperties() {
+    public android.hardware.soundtrigger.V2_3.Properties getProperties() {
         try {
             AtomicInteger retval = new AtomicInteger(-1);
-            AtomicReference<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties>
+            AtomicReference<android.hardware.soundtrigger.V2_3.Properties>
                     properties =
                     new AtomicReference<>();
-            as2_0().getProperties(
-                    (r, p) -> {
-                        retval.set(r);
-                        properties.set(p);
-                    });
-            handleHalStatus(retval.get(), "getProperties");
+            try {
+                as2_3().getProperties_2_3(
+                        (r, p) -> {
+                            retval.set(r);
+                            properties.set(p);
+                        });
+            } catch (NotSupported e) {
+                // Fall-back to the 2.0 version:
+                return getProperties_2_0();
+            }
+            handleHalStatus(retval.get(), "getProperties_2_3");
             return properties.get();
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
@@ -212,16 +217,15 @@
 
     @Override
     public void startRecognition(int modelHandle,
-            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config,
+            android.hardware.soundtrigger.V2_3.RecognitionConfig config,
             Callback callback, int cookie) {
         try {
             try {
-                int retval = as2_1().startRecognition_2_1(modelHandle, config,
-                        new SoundTriggerCallback(callback), cookie);
-                handleHalStatus(retval, "startRecognition_2_1");
+                int retval = as2_3().startRecognition_2_3(modelHandle, config);
+                handleHalStatus(retval, "startRecognition_2_3");
             } catch (NotSupported e) {
                 // Fall-back to the 2.0 version:
-                startRecognition_2_0(modelHandle, config, callback, cookie);
+                startRecognition_2_1(modelHandle, config, callback, cookie);
             }
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
@@ -312,6 +316,21 @@
         return as2_0().interfaceDescriptor();
     }
 
+    private android.hardware.soundtrigger.V2_3.Properties getProperties_2_0()
+            throws RemoteException {
+        AtomicInteger retval = new AtomicInteger(-1);
+        AtomicReference<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties>
+                properties =
+                new AtomicReference<>();
+        as2_0().getProperties(
+                (r, p) -> {
+                    retval.set(r);
+                    properties.set(p);
+                });
+        handleHalStatus(retval.get(), "getProperties");
+        return Hw2CompatUtil.convertProperties_2_0_to_2_3(properties.get());
+    }
+
     private int loadSoundModel_2_0(
             android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel,
             Callback callback, int cookie)
@@ -349,13 +368,31 @@
         return handle.get();
     }
 
+    private void startRecognition_2_1(int modelHandle,
+            android.hardware.soundtrigger.V2_3.RecognitionConfig config,
+            Callback callback, int cookie) {
+        try {
+            try {
+                android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config_2_1 =
+                        Hw2CompatUtil.convertRecognitionConfig_2_3_to_2_1(config);
+                int retval = as2_1().startRecognition_2_1(modelHandle, config_2_1,
+                        new SoundTriggerCallback(callback), cookie);
+                handleHalStatus(retval, "startRecognition_2_1");
+            } catch (NotSupported e) {
+                // Fall-back to the 2.0 version:
+                startRecognition_2_0(modelHandle, config, callback, cookie);
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
     private void startRecognition_2_0(int modelHandle,
-            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config,
+            android.hardware.soundtrigger.V2_3.RecognitionConfig config,
             Callback callback, int cookie)
             throws RemoteException {
-
         android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config_2_0 =
-                Hw2CompatUtil.convertRecognitionConfig_2_1_to_2_0(config);
+                Hw2CompatUtil.convertRecognitionConfig_2_3_to_2_0(config);
         int retval = as2_0().startRecognition(modelHandle, config_2_0,
                 new SoundTriggerCallback(callback), cookie);
         handleHalStatus(retval, "startRecognition");
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
index 9d51b65..9f4b09a 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
@@ -78,18 +78,17 @@
     }
 
     /**
-     * Most generic constructor - gets an array of HAL driver instances.
+     * Constructor - gets an array of HAL driver factories.
      */
-    public SoundTriggerMiddlewareImpl(@NonNull ISoundTriggerHw[] halServices,
+    public SoundTriggerMiddlewareImpl(@NonNull HalFactory[] halFactories,
             @NonNull AudioSessionProvider audioSessionProvider) {
-        List<SoundTriggerModule> modules = new ArrayList<>(halServices.length);
+        List<SoundTriggerModule> modules = new ArrayList<>(halFactories.length);
 
-        for (int i = 0; i < halServices.length; ++i) {
-            ISoundTriggerHw service = halServices[i];
+        for (int i = 0; i < halFactories.length; ++i) {
             try {
-                modules.add(new SoundTriggerModule(service, audioSessionProvider));
+                modules.add(new SoundTriggerModule(halFactories[i], audioSessionProvider));
             } catch (Exception e) {
-                Log.e(TAG, "Failed to a SoundTriggerModule instance", e);
+                Log.e(TAG, "Failed to add a SoundTriggerModule instance", e);
             }
         }
 
@@ -97,11 +96,11 @@
     }
 
     /**
-     * Convenience constructor - gets a single HAL driver instance.
+     * Convenience constructor - gets a single HAL factory.
      */
-    public SoundTriggerMiddlewareImpl(@NonNull ISoundTriggerHw halService,
+    public SoundTriggerMiddlewareImpl(@NonNull HalFactory factory,
             @NonNull AudioSessionProvider audioSessionProvider) {
-        this(new ISoundTriggerHw[]{halService}, audioSessionProvider);
+        this(new HalFactory[]{factory}, audioSessionProvider);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
index 987c05f..1ed97be 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.PermissionChecker;
 import android.hardware.soundtrigger.V2_0.ISoundTriggerHw;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
@@ -32,6 +33,7 @@
 import android.media.soundtrigger_middleware.RecognitionStatus;
 import android.media.soundtrigger_middleware.SoundModel;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.media.soundtrigger_middleware.Status;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.util.Log;
@@ -111,9 +113,9 @@
 public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareService.Stub {
     static private final String TAG = "SoundTriggerMiddlewareService";
 
-    final ISoundTriggerMiddlewareService mDelegate;
-    final Context mContext;
-    Set<Integer> mModuleHandles;
+    private final ISoundTriggerMiddlewareService mDelegate;
+    private final Context mContext;
+    private Set<Integer> mModuleHandles;
 
     /**
      * Constructor for internal use only. Could be exposed for testing purposes in the future.
@@ -223,23 +225,48 @@
     }
 
     /**
-     * Throws a {@link SecurityException} if caller doesn't have the right permissions to use this
-     * service.
+     * Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
+     * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
+     * caller temporarily doesn't have the right permissions to use this service.
      */
     private void checkPermissions() {
-        mContext.enforceCallingOrSelfPermission(Manifest.permission.RECORD_AUDIO,
-                "Caller must have the android.permission.RECORD_AUDIO permission.");
-        mContext.enforceCallingOrSelfPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD,
-                "Caller must have the android.permission.CAPTURE_AUDIO_HOTWORD permission.");
+        enforcePermission(Manifest.permission.RECORD_AUDIO);
+        enforcePermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD);
     }
 
     /**
-     * Throws a {@link SecurityException} if caller doesn't have the right permissions to preempt
-     * active sound trigger sessions.
+     * Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
+     * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
+     * caller temporarily doesn't have the right permissions to preempt active sound trigger
+     * sessions.
      */
     private void checkPreemptPermissions() {
-        mContext.enforceCallingOrSelfPermission(Manifest.permission.PREEMPT_SOUND_TRIGGER,
-                "Caller must have the android.permission.PREEMPT_SOUND_TRIGGER permission.");
+        enforcePermission(Manifest.permission.PREEMPT_SOUND_TRIGGER);
+    }
+
+    /**
+     * Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
+     * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
+     * caller temporarily doesn't have the given permission.
+     *
+     * @param permission The permission to check.
+     */
+    private void enforcePermission(String permission) {
+        final int status = PermissionChecker.checkCallingOrSelfPermissionForPreflight(mContext,
+                permission);
+        switch (status) {
+            case PermissionChecker.PERMISSION_GRANTED:
+                return;
+            case PermissionChecker.PERMISSION_HARD_DENIED:
+                throw new SecurityException(
+                        String.format("Caller must have the %s permission.", permission));
+            case PermissionChecker.PERMISSION_SOFT_DENIED:
+                throw new ServiceSpecificException(Status.TEMPORARY_PERMISSION_DENIED,
+                        String.format("Caller must have the %s permission.", permission));
+            default:
+                throw new InternalServerError(
+                        new RuntimeException("Unexpected perimission check result."));
+        }
     }
 
     /** State of a sound model. */
@@ -253,7 +280,7 @@
         }
 
         /** Activity state. */
-        public Activity activityState = Activity.LOADED;
+        Activity activityState = Activity.LOADED;
 
         /**
          * A map of known parameter support. A missing key means we don't know yet whether the
@@ -267,7 +294,7 @@
          *
          * @param modelParam The parameter key.
          */
-        public void checkSupported(int modelParam) {
+        void checkSupported(int modelParam) {
             if (!parameterSupport.containsKey(modelParam)) {
                 throw new IllegalStateException("Parameter has not been checked for support.");
             }
@@ -284,7 +311,7 @@
          * @param modelParam The parameter key.
          * @param value      The value.
          */
-        public void checkSupported(int modelParam, int value) {
+        void checkSupported(int modelParam, int value) {
             if (!parameterSupport.containsKey(modelParam)) {
                 throw new IllegalStateException("Parameter has not been checked for support.");
             }
@@ -302,7 +329,7 @@
          * @param modelParam The parameter key.
          * @param range      The parameter value range, or null if not supported.
          */
-        public void updateParameterSupport(int modelParam, @Nullable ModelParameterRange range) {
+        void updateParameterSupport(int modelParam, @Nullable ModelParameterRange range) {
             parameterSupport.put(modelParam, range);
         }
     }
@@ -311,27 +338,26 @@
      * Entry-point to this module: exposes the module as a {@link SystemService}.
      */
     public static final class Lifecycle extends SystemService {
-        private SoundTriggerMiddlewareService mService;
-
         public Lifecycle(Context context) {
             super(context);
         }
 
         @Override
         public void onStart() {
-            ISoundTriggerHw[] services;
-            try {
-                services = new ISoundTriggerHw[]{ISoundTriggerHw.getService(true)};
-                Log.d(TAG, "Connected to default ISoundTriggerHw");
-            } catch (Exception e) {
-                Log.e(TAG, "Failed to connect to default ISoundTriggerHw", e);
-                services = new ISoundTriggerHw[0];
-            }
+            HalFactory[] factories = new HalFactory[]{() -> {
+                try {
+                    Log.d(TAG, "Connecting to default ISoundTriggerHw");
+                    return ISoundTriggerHw.getService(true);
+                } catch (RemoteException e) {
+                    throw e.rethrowAsRuntimeException();
+                }
+            }};
 
-            mService = new SoundTriggerMiddlewareService(
-                    new SoundTriggerMiddlewareImpl(services, new AudioSessionProviderImpl()),
-                    getContext());
-            publishBinderService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE, mService);
+            publishBinderService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE,
+                    new SoundTriggerMiddlewareService(
+                            new SoundTriggerMiddlewareImpl(factories,
+                                    new AudioSessionProviderImpl()),
+                            getContext()));
         }
     }
 
@@ -343,7 +369,7 @@
             DeathRecipient {
         private final ISoundTriggerCallback mCallback;
         private ISoundTriggerModule mDelegate;
-        private Map<Integer, ModelState> mLoadedModels = new HashMap<>();
+        private @NonNull Map<Integer, ModelState> mLoadedModels = new HashMap<>();
 
         ModuleService(@NonNull ISoundTriggerCallback callback) {
             mCallback = callback;
@@ -653,7 +679,7 @@
                 } catch (RemoteException e) {
                     // Dead client will be handled by binderDied() - no need to handle here.
                     // In any case, client callbacks are considered best effort.
-                    Log.e(TAG, "Client callback execption.", e);
+                    Log.e(TAG, "Client callback exception.", e);
                 }
             }
         }
@@ -669,20 +695,33 @@
                 } catch (RemoteException e) {
                     // Dead client will be handled by binderDied() - no need to handle here.
                     // In any case, client callbacks are considered best effort.
-                    Log.e(TAG, "Client callback execption.", e);
+                    Log.e(TAG, "Client callback exception.", e);
                 }
             }
         }
 
         @Override
-        public void onRecognitionAvailabilityChange(boolean available) throws RemoteException {
+        public void onRecognitionAvailabilityChange(boolean available) {
             synchronized (this) {
                 try {
                     mCallback.onRecognitionAvailabilityChange(available);
                 } catch (RemoteException e) {
                     // Dead client will be handled by binderDied() - no need to handle here.
                     // In any case, client callbacks are considered best effort.
-                    Log.e(TAG, "Client callback execption.", e);
+                    Log.e(TAG, "Client callback exception.", e);
+                }
+            }
+        }
+
+        @Override
+        public void onModuleDied() {
+            synchronized (this) {
+                try {
+                    mCallback.onModuleDied();
+                } catch (RemoteException e) {
+                    // Dead client will be handled by binderDied() - no need to handle here.
+                    // In any case, client callbacks are considered best effort.
+                    Log.e(TAG, "Client callback exception.", e);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index 81789e1..372c1f5 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -30,7 +30,9 @@
 import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
 import android.media.soundtrigger_middleware.Status;
 import android.os.IBinder;
+import android.os.IHwBinder;
 import android.os.RemoteException;
+import android.os.ServiceSpecificException;
 import android.util.Log;
 
 import java.util.HashMap;
@@ -57,6 +59,7 @@
  * gracefully handle driver malfunction and such behavior will result in undefined behavior. If this
  * service is to used with an untrusted driver, the driver must be wrapped with validation / error
  * recovery code.
+ * <li>Recovery from driver death is supported.</li>
  * <li>RemoteExceptions thrown by the driver are treated as RuntimeExceptions - they are not
  * considered recoverable faults and should not occur in a properly functioning system.
  * <li>There is no binder instance associated with this implementation. Do not call asBinder().
@@ -79,27 +82,29 @@
  *
  * @hide
  */
-class SoundTriggerModule {
+class SoundTriggerModule implements IHwBinder.DeathRecipient {
     static private final String TAG = "SoundTriggerModule";
-    @NonNull private final ISoundTriggerHw2 mHalService;
+    @NonNull private HalFactory mHalFactory;
+    @NonNull private ISoundTriggerHw2 mHalService;
     @NonNull private final SoundTriggerMiddlewareImpl.AudioSessionProvider mAudioSessionProvider;
     private final Set<Session> mActiveSessions = new HashSet<>();
     private int mNumLoadedModels = 0;
-    private SoundTriggerModuleProperties mProperties = null;
+    private SoundTriggerModuleProperties mProperties;
     private boolean mRecognitionAvailable;
 
     /**
      * Ctor.
      *
-     * @param halService The underlying HAL driver.
+     * @param halFactory A factory for the underlying HAL driver.
      */
-    SoundTriggerModule(@NonNull android.hardware.soundtrigger.V2_0.ISoundTriggerHw halService,
+    SoundTriggerModule(@NonNull HalFactory halFactory,
             @NonNull SoundTriggerMiddlewareImpl.AudioSessionProvider audioSessionProvider) {
-        assert halService != null;
-        mHalService = new SoundTriggerHw2Compat(halService);
+        assert halFactory != null;
+        mHalFactory = halFactory;
         mAudioSessionProvider = audioSessionProvider;
-        mProperties = ConversionUtil.hidl2aidlProperties(mHalService.getProperties());
 
+        attachToHal();
+        mProperties = ConversionUtil.hidl2aidlProperties(mHalService.getProperties());
         // We conservatively assume that external capture is active until explicitly told otherwise.
         mRecognitionAvailable = mProperties.concurrentCapture;
     }
@@ -117,7 +122,7 @@
      * @return The interface through which this module can be controlled.
      */
     synchronized @NonNull
-    Session attach(@NonNull ISoundTriggerCallback callback) {
+    ISoundTriggerModule attach(@NonNull ISoundTriggerCallback callback) {
         Log.d(TAG, "attach()");
         Session session = new Session(callback);
         mActiveSessions.add(session);
@@ -145,6 +150,7 @@
      */
     synchronized void setExternalCaptureState(boolean active) {
         Log.d(TAG, String.format("setExternalCaptureState(active=%b)", active));
+
         if (mProperties.concurrentCapture) {
             // If we support concurrent capture, we don't care about any of this.
             return;
@@ -162,6 +168,33 @@
         }
     }
 
+    @Override
+    public synchronized  void serviceDied(long cookie) {
+        Log.w(TAG, String.format("Underlying HAL driver died."));
+        for (Session session : mActiveSessions) {
+            session.moduleDied();
+        }
+        reset();
+    }
+
+    /**
+     * Resets the transient state of this object.
+     */
+    private void reset() {
+        attachToHal();
+        // We conservatively assume that external capture is active until explicitly told otherwise.
+        mRecognitionAvailable = mProperties.concurrentCapture;
+        mNumLoadedModels = 0;
+    }
+
+    /**
+     * Attached to the HAL service via factory.
+     */
+    private void attachToHal() {
+        mHalService = new SoundTriggerHw2Compat(mHalFactory.create());
+        mHalService.linkToDeath(this, 0);
+    }
+
     /**
      * Remove session from the list of active sessions.
      *
@@ -204,7 +237,11 @@
         public void detach() {
             Log.d(TAG, "detach()");
             synchronized (SoundTriggerModule.this) {
+                if (mCallback == null) {
+                    return;
+                }
                 removeSession(this);
+                mCallback = null;
             }
         }
 
@@ -212,6 +249,7 @@
         public int loadModel(@NonNull SoundModel model) {
             Log.d(TAG, String.format("loadModel(model=%s)", model));
             synchronized (SoundTriggerModule.this) {
+                checkValid();
                 if (mNumLoadedModels == mProperties.maxSoundModels) {
                     throw new RecoverableException(Status.RESOURCE_CONTENTION,
                             "Maximum number of models loaded.");
@@ -227,6 +265,7 @@
         public int loadPhraseModel(@NonNull PhraseSoundModel model) {
             Log.d(TAG, String.format("loadPhraseModel(model=%s)", model));
             synchronized (SoundTriggerModule.this) {
+                checkValid();
                 if (mNumLoadedModels == mProperties.maxSoundModels) {
                     throw new RecoverableException(Status.RESOURCE_CONTENTION,
                             "Maximum number of models loaded.");
@@ -243,6 +282,7 @@
         public void unloadModel(int modelHandle) {
             Log.d(TAG, String.format("unloadModel(handle=%d)", modelHandle));
             synchronized (SoundTriggerModule.this) {
+                checkValid();
                 mLoadedModels.get(modelHandle).unload();
                 --mNumLoadedModels;
             }
@@ -253,6 +293,7 @@
             Log.d(TAG,
                     String.format("startRecognition(handle=%d, config=%s)", modelHandle, config));
             synchronized (SoundTriggerModule.this) {
+                checkValid();
                 mLoadedModels.get(modelHandle).startRecognition(config);
             }
         }
@@ -269,26 +310,28 @@
         public void forceRecognitionEvent(int modelHandle) {
             Log.d(TAG, String.format("forceRecognitionEvent(handle=%d)", modelHandle));
             synchronized (SoundTriggerModule.this) {
+                checkValid();
                 mLoadedModels.get(modelHandle).forceRecognitionEvent();
             }
         }
 
         @Override
-        public void setModelParameter(int modelHandle, int modelParam, int value)
-                throws RemoteException {
+        public void setModelParameter(int modelHandle, int modelParam, int value) {
             Log.d(TAG,
                     String.format("setModelParameter(handle=%d, param=%d, value=%d)", modelHandle,
                             modelParam, value));
             synchronized (SoundTriggerModule.this) {
+                checkValid();
                 mLoadedModels.get(modelHandle).setParameter(modelParam, value);
             }
         }
 
         @Override
-        public int getModelParameter(int modelHandle, int modelParam) throws RemoteException {
+        public int getModelParameter(int modelHandle, int modelParam) {
             Log.d(TAG, String.format("getModelParameter(handle=%d, param=%d)", modelHandle,
                     modelParam));
             synchronized (SoundTriggerModule.this) {
+                checkValid();
                 return mLoadedModels.get(modelHandle).getParameter(modelParam);
             }
         }
@@ -299,6 +342,7 @@
             Log.d(TAG, String.format("queryModelParameterSupport(handle=%d, param=%d)", modelHandle,
                     modelParam));
             synchronized (SoundTriggerModule.this) {
+                checkValid();
                 return mLoadedModels.get(modelHandle).queryModelParameterSupport(modelParam);
             }
         }
@@ -322,6 +366,25 @@
             }
         }
 
+        /**
+         * The underlying module HAL is dead.
+         */
+        private void moduleDied() {
+            try {
+                mCallback.onModuleDied();
+                removeSession(this);
+                mCallback = null;
+            } catch (RemoteException e) {
+                e.rethrowAsRuntimeException();
+            }
+        }
+
+        private void checkValid() {
+            if (mCallback == null) {
+                throw new ServiceSpecificException(Status.DEAD_OBJECT);
+            }
+        }
+
         @Override
         public @NonNull
         IBinder asBinder() {
@@ -350,10 +413,6 @@
                 SoundTriggerModule.this.notifyAll();
             }
 
-            private void waitStateChange() throws InterruptedException {
-                SoundTriggerModule.this.wait();
-            }
-
             private int load(@NonNull SoundModel model) {
                 mModelType = model.type;
                 ISoundTriggerHw.SoundModel hidlModel = ConversionUtil.aidl2hidlSoundModel(model);
@@ -401,10 +460,10 @@
                     notifyAbort();
                     return;
                 }
-                android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig hidlConfig =
+                android.hardware.soundtrigger.V2_3.RecognitionConfig hidlConfig =
                         ConversionUtil.aidl2hidlRecognitionConfig(config);
-                hidlConfig.header.captureDevice = mSession.mDeviceHandle;
-                hidlConfig.header.captureHandle = mSession.mIoHandle;
+                hidlConfig.base.header.captureDevice = mSession.mDeviceHandle;
+                hidlConfig.base.header.captureHandle = mSession.mIoHandle;
                 mHalService.startRecognition(mHandle, hidlConfig, this, 0);
                 setState(ModelState.ACTIVE);
             }
diff --git a/services/core/java/com/android/server/stats/IonMemoryUtil.java b/services/core/java/com/android/server/stats/IonMemoryUtil.java
deleted file mode 100644
index c9be96f..0000000
--- a/services/core/java/com/android/server/stats/IonMemoryUtil.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.stats;
-
-import android.os.FileUtils;
-import android.util.Slog;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/** Utility methods for reading ion memory stats. */
-final class IonMemoryUtil {
-    private static final String TAG = "IonMemoryUtil";
-
-    /** Path to debugfs file for the system ion heap. */
-    private static final String DEBUG_SYSTEM_ION_HEAP_FILE = "/sys/kernel/debug/ion/heaps/system";
-
-    private static final Pattern ION_HEAP_SIZE_IN_BYTES =
-            Pattern.compile("\n\\s*total\\s*(\\d+)\\s*\n");
-    private static final Pattern PROCESS_ION_HEAP_SIZE_IN_BYTES =
-            Pattern.compile("\n\\s+\\S+\\s+(\\d+)\\s+(\\d+)");
-
-    private IonMemoryUtil() {}
-
-    /**
-     * Reads size of the system ion heap from debugfs.
-     *
-     * Returns value of the total size in bytes of the system ion heap from
-     * /sys/kernel/debug/ion/heaps/system.
-     */
-    static long readSystemIonHeapSizeFromDebugfs() {
-        return parseIonHeapSizeFromDebugfs(readFile(DEBUG_SYSTEM_ION_HEAP_FILE));
-    }
-
-    /**
-     * Parses the ion heap size from the contents of a file under /sys/kernel/debug/ion/heaps in
-     * debugfs. The returned value is in bytes.
-     */
-    @VisibleForTesting
-    static long parseIonHeapSizeFromDebugfs(String contents) {
-        if (contents.isEmpty()) {
-            return 0;
-        }
-        final Matcher matcher = ION_HEAP_SIZE_IN_BYTES.matcher(contents);
-        try {
-            return matcher.find() ? Long.parseLong(matcher.group(1)) : 0;
-        } catch (NumberFormatException e) {
-            Slog.e(TAG, "Failed to parse value", e);
-            return 0;
-        }
-    }
-
-    /**
-     * Reads process allocation sizes on the system ion heap from debugfs.
-     *
-     * Returns values of allocation sizes in bytes on the system ion heap from
-     * /sys/kernel/debug/ion/heaps/system.
-     */
-    static List<IonAllocations> readProcessSystemIonHeapSizesFromDebugfs() {
-        return parseProcessIonHeapSizesFromDebugfs(readFile(DEBUG_SYSTEM_ION_HEAP_FILE));
-    }
-
-    /**
-     * Parses per-process allocation sizes on the ion heap from the contents of a file under
-     * /sys/kernel/debug/ion/heaps in debugfs.
-     */
-    @VisibleForTesting
-    static List<IonAllocations> parseProcessIonHeapSizesFromDebugfs(String contents) {
-        if (contents.isEmpty()) {
-            return Collections.emptyList();
-        }
-
-        final Matcher m = PROCESS_ION_HEAP_SIZE_IN_BYTES.matcher(contents);
-        final SparseArray<IonAllocations> entries = new SparseArray<>();
-        while (m.find()) {
-            try {
-                final int pid = Integer.parseInt(m.group(1));
-                final long sizeInBytes = Long.parseLong(m.group(2));
-                IonAllocations allocations = entries.get(pid);
-                if (allocations == null) {
-                    allocations = new IonAllocations();
-                    entries.put(pid, allocations);
-                }
-                allocations.pid = pid;
-                allocations.totalSizeInBytes += sizeInBytes;
-                allocations.count += 1;
-                allocations.maxSizeInBytes = Math.max(allocations.maxSizeInBytes, sizeInBytes);
-            } catch (NumberFormatException e) {
-                Slog.e(TAG, "Failed to parse value", e);
-            }
-        }
-
-        final List<IonAllocations> result = new ArrayList<>(entries.size());
-        for (int i = 0; i < entries.size(); i++) {
-            result.add(entries.valueAt(i));
-        }
-        return result;
-    }
-
-    private static String readFile(String path) {
-        try {
-            final File file = new File(path);
-            return FileUtils.readTextFile(file, 0 /* max */, null /* ellipsis */);
-        } catch (IOException e) {
-            Slog.e(TAG, "Failed to read file", e);
-            return "";
-        }
-    }
-
-    /** Summary information about process ion allocations. */
-    static final class IonAllocations {
-        /** PID these allocations belong to. */
-        public int pid;
-        /** Size of all individual allocations added together. */
-        public long totalSizeInBytes;
-        /** Number of allocations. */
-        public int count;
-        /** Size of the largest allocation. */
-        public long maxSizeInBytes;
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            IonAllocations that = (IonAllocations) o;
-            return pid == that.pid && totalSizeInBytes == that.totalSizeInBytes
-                    && count == that.count && maxSizeInBytes == that.maxSizeInBytes;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(pid, totalSizeInBytes, count, maxSizeInBytes);
-        }
-
-        @Override
-        public String toString() {
-            return "IonAllocations{"
-                    + "pid=" + pid
-                    + ", totalSizeInBytes=" + totalSizeInBytes
-                    + ", count=" + count
-                    + ", maxSizeInBytes=" + maxSizeInBytes
-                    + '}';
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/stats/OWNERS b/services/core/java/com/android/server/stats/OWNERS
index 8d7f882..fc7fd22 100644
--- a/services/core/java/com/android/server/stats/OWNERS
+++ b/services/core/java/com/android/server/stats/OWNERS
@@ -1,9 +1,8 @@
-bookatz@google.com
-cjyu@google.com
-dwchen@google.com
+jeffreyhuang@google.com
 joeo@google.com
+muhammadq@google.com
+ruchirr@google.com
 singhtejinder@google.com
-stlafon@google.com
+tsaichristine@google.com
 yaochen@google.com
-yanglu@google.com
-yro@google.com
\ No newline at end of file
+yro@google.com
diff --git a/services/core/java/com/android/server/stats/ProcfsMemoryUtil.java b/services/core/java/com/android/server/stats/ProcfsMemoryUtil.java
deleted file mode 100644
index c1eacce..0000000
--- a/services/core/java/com/android/server/stats/ProcfsMemoryUtil.java
+++ /dev/null
@@ -1,96 +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.server.stats;
-
-import static android.os.Process.PROC_OUT_STRING;
-
-import android.annotation.Nullable;
-import android.os.Process;
-
-import java.util.function.BiConsumer;
-
-final class ProcfsMemoryUtil {
-    private static final int[] CMDLINE_OUT = new int[] { PROC_OUT_STRING };
-    private static final String[] STATUS_KEYS = new String[] {
-            "Uid:",
-            "VmHWM:",
-            "VmRSS:",
-            "RssAnon:",
-            "VmSwap:"
-    };
-
-    private ProcfsMemoryUtil() {}
-
-    /**
-     * Reads memory stats of a process from procfs. Returns values of the VmHWM, VmRss, AnonRSS,
-     * VmSwap fields in /proc/pid/status in kilobytes or null if not available.
-     */
-    @Nullable
-    static MemorySnapshot readMemorySnapshotFromProcfs(int pid) {
-        long[] output = new long[STATUS_KEYS.length];
-        output[0] = -1;
-        Process.readProcLines("/proc/" + pid + "/status", STATUS_KEYS, output);
-        if (output[0] == -1 || (output[3] == 0 && output[4] == 0)) {
-            // Could not open file or anon rss / swap are 0 indicating the process is in a zombie
-            // state.
-            return null;
-        }
-        final MemorySnapshot snapshot = new MemorySnapshot();
-        snapshot.uid = (int) output[0];
-        snapshot.rssHighWaterMarkInKilobytes = (int) output[1];
-        snapshot.rssInKilobytes = (int) output[2];
-        snapshot.anonRssInKilobytes = (int) output[3];
-        snapshot.swapInKilobytes = (int) output[4];
-        return snapshot;
-    }
-
-    /**
-     * Reads cmdline of a process from procfs.
-     *
-     * Returns content of /proc/pid/cmdline (e.g. /system/bin/statsd) or an empty string
-     * if the file is not available.
-     */
-    static String readCmdlineFromProcfs(int pid) {
-        String[] cmdline = new String[1];
-        if (!Process.readProcFile("/proc/" + pid + "/cmdline", CMDLINE_OUT, cmdline, null, null)) {
-            return "";
-        }
-        return cmdline[0];
-    }
-
-    static void forEachPid(BiConsumer<Integer, String> func) {
-        int[] pids = new int[1024];
-        pids = Process.getPids("/proc", pids);
-        for (int pid : pids) {
-            if (pid < 0) {
-                return;
-            }
-            String cmdline = readCmdlineFromProcfs(pid);
-            if (cmdline.isEmpty()) {
-                continue;
-            }
-            func.accept(pid, cmdline);
-        }
-    }
-
-    static final class MemorySnapshot {
-        public int uid;
-        public int rssHighWaterMarkInKilobytes;
-        public int rssInKilobytes;
-        public int anonRssInKilobytes;
-        public int swapInKilobytes;
-    }
-}
diff --git a/services/core/java/com/android/server/stats/StatsPullAtomService.java b/services/core/java/com/android/server/stats/StatsPullAtomService.java
deleted file mode 100644
index e367f28..0000000
--- a/services/core/java/com/android/server/stats/StatsPullAtomService.java
+++ /dev/null
@@ -1,58 +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.server.stats;
-
-import android.content.Context;
-import android.util.Slog;
-
-import com.android.internal.os.BackgroundThread;
-import com.android.server.SystemService;
-
-/**
- * SystemService containing PullAtomCallbacks that are registered with statsd.
- *
- * @hide
- */
-public class StatsPullAtomService extends SystemService {
-    private static final String TAG = "StatsPullAtomService";
-    private static final boolean DEBUG = true;
-
-    public StatsPullAtomService(Context context) {
-        super(context);
-    }
-
-    @Override
-    public void onStart() {
-        // No op.
-    }
-
-    @Override
-    public void onBootPhase(int phase) {
-        super.onBootPhase(phase);
-        if (phase == PHASE_SYSTEM_SERVICES_READY) {
-            BackgroundThread.getHandler().post(() -> {
-                registerAllPullers();
-            });
-        }
-    }
-
-    void registerAllPullers() {
-        if (DEBUG) {
-            Slog.d(TAG, "Registering all pullers with statsd");
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/stats/pull/IonMemoryUtil.java b/services/core/java/com/android/server/stats/pull/IonMemoryUtil.java
new file mode 100644
index 0000000..fde0a59
--- /dev/null
+++ b/services/core/java/com/android/server/stats/pull/IonMemoryUtil.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.stats.pull;
+
+import android.os.FileUtils;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Utility methods for reading ion memory stats.
+ * TODO: Consider making package private after puller migration
+ */
+public final class IonMemoryUtil {
+    private static final String TAG = "IonMemoryUtil";
+
+    /** Path to debugfs file for the system ion heap. */
+    private static final String DEBUG_SYSTEM_ION_HEAP_FILE = "/sys/kernel/debug/ion/heaps/system";
+
+    private static final Pattern ION_HEAP_SIZE_IN_BYTES =
+            Pattern.compile("\n\\s*total\\s*(\\d+)\\s*\n");
+    private static final Pattern PROCESS_ION_HEAP_SIZE_IN_BYTES =
+            Pattern.compile("\n\\s+\\S+\\s+(\\d+)\\s+(\\d+)");
+
+    private IonMemoryUtil() {}
+
+    /**
+     * Reads size of the system ion heap from debugfs.
+     *
+     * Returns value of the total size in bytes of the system ion heap from
+     * /sys/kernel/debug/ion/heaps/system.
+     */
+    public static long readSystemIonHeapSizeFromDebugfs() {
+        return parseIonHeapSizeFromDebugfs(readFile(DEBUG_SYSTEM_ION_HEAP_FILE));
+    }
+
+    /**
+     * Parses the ion heap size from the contents of a file under /sys/kernel/debug/ion/heaps in
+     * debugfs. The returned value is in bytes.
+     */
+    @VisibleForTesting
+    static long parseIonHeapSizeFromDebugfs(String contents) {
+        if (contents.isEmpty()) {
+            return 0;
+        }
+        final Matcher matcher = ION_HEAP_SIZE_IN_BYTES.matcher(contents);
+        try {
+            return matcher.find() ? Long.parseLong(matcher.group(1)) : 0;
+        } catch (NumberFormatException e) {
+            Slog.e(TAG, "Failed to parse value", e);
+            return 0;
+        }
+    }
+
+    /**
+     * Reads process allocation sizes on the system ion heap from debugfs.
+     *
+     * Returns values of allocation sizes in bytes on the system ion heap from
+     * /sys/kernel/debug/ion/heaps/system.
+     */
+    public static List<IonAllocations> readProcessSystemIonHeapSizesFromDebugfs() {
+        return parseProcessIonHeapSizesFromDebugfs(readFile(DEBUG_SYSTEM_ION_HEAP_FILE));
+    }
+
+    /**
+     * Parses per-process allocation sizes on the ion heap from the contents of a file under
+     * /sys/kernel/debug/ion/heaps in debugfs.
+     */
+    @VisibleForTesting
+    static List<IonAllocations> parseProcessIonHeapSizesFromDebugfs(String contents) {
+        if (contents.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        final Matcher m = PROCESS_ION_HEAP_SIZE_IN_BYTES.matcher(contents);
+        final SparseArray<IonAllocations> entries = new SparseArray<>();
+        while (m.find()) {
+            try {
+                final int pid = Integer.parseInt(m.group(1));
+                final long sizeInBytes = Long.parseLong(m.group(2));
+                IonAllocations allocations = entries.get(pid);
+                if (allocations == null) {
+                    allocations = new IonAllocations();
+                    entries.put(pid, allocations);
+                }
+                allocations.pid = pid;
+                allocations.totalSizeInBytes += sizeInBytes;
+                allocations.count += 1;
+                allocations.maxSizeInBytes = Math.max(allocations.maxSizeInBytes, sizeInBytes);
+            } catch (NumberFormatException e) {
+                Slog.e(TAG, "Failed to parse value", e);
+            }
+        }
+
+        final List<IonAllocations> result = new ArrayList<>(entries.size());
+        for (int i = 0; i < entries.size(); i++) {
+            result.add(entries.valueAt(i));
+        }
+        return result;
+    }
+
+    private static String readFile(String path) {
+        try {
+            final File file = new File(path);
+            return FileUtils.readTextFile(file, 0 /* max */, null /* ellipsis */);
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to read file", e);
+            return "";
+        }
+    }
+
+    /** Summary information about process ion allocations. */
+    public static final class IonAllocations {
+        /** PID these allocations belong to. */
+        public int pid;
+        /** Size of all individual allocations added together. */
+        public long totalSizeInBytes;
+        /** Number of allocations. */
+        public int count;
+        /** Size of the largest allocation. */
+        public long maxSizeInBytes;
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            IonAllocations that = (IonAllocations) o;
+            return pid == that.pid && totalSizeInBytes == that.totalSizeInBytes
+                    && count == that.count && maxSizeInBytes == that.maxSizeInBytes;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(pid, totalSizeInBytes, count, maxSizeInBytes);
+        }
+
+        @Override
+        public String toString() {
+            return "IonAllocations{"
+                    + "pid=" + pid
+                    + ", totalSizeInBytes=" + totalSizeInBytes
+                    + ", count=" + count
+                    + ", maxSizeInBytes=" + maxSizeInBytes
+                    + '}';
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java b/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java
new file mode 100644
index 0000000..638dfd2
--- /dev/null
+++ b/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java
@@ -0,0 +1,96 @@
+/*
+ * 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.server.stats.pull;
+
+import static android.os.Process.PROC_OUT_STRING;
+
+import android.annotation.Nullable;
+import android.os.Process;
+
+import java.util.function.BiConsumer;
+
+public final class ProcfsMemoryUtil {
+    private static final int[] CMDLINE_OUT = new int[] { PROC_OUT_STRING };
+    private static final String[] STATUS_KEYS = new String[] {
+            "Uid:",
+            "VmHWM:",
+            "VmRSS:",
+            "RssAnon:",
+            "VmSwap:"
+    };
+
+    private ProcfsMemoryUtil() {}
+
+    /**
+     * Reads memory stats of a process from procfs. Returns values of the VmHWM, VmRss, AnonRSS,
+     * VmSwap fields in /proc/pid/status in kilobytes or null if not available.
+     */
+    @Nullable
+    public static MemorySnapshot readMemorySnapshotFromProcfs(int pid) {
+        long[] output = new long[STATUS_KEYS.length];
+        output[0] = -1;
+        Process.readProcLines("/proc/" + pid + "/status", STATUS_KEYS, output);
+        if (output[0] == -1 || (output[3] == 0 && output[4] == 0)) {
+            // Could not open file or anon rss / swap are 0 indicating the process is in a zombie
+            // state.
+            return null;
+        }
+        final MemorySnapshot snapshot = new MemorySnapshot();
+        snapshot.uid = (int) output[0];
+        snapshot.rssHighWaterMarkInKilobytes = (int) output[1];
+        snapshot.rssInKilobytes = (int) output[2];
+        snapshot.anonRssInKilobytes = (int) output[3];
+        snapshot.swapInKilobytes = (int) output[4];
+        return snapshot;
+    }
+
+    /**
+     * Reads cmdline of a process from procfs.
+     *
+     * Returns content of /proc/pid/cmdline (e.g. /system/bin/statsd) or an empty string
+     * if the file is not available.
+     */
+    public static String readCmdlineFromProcfs(int pid) {
+        String[] cmdline = new String[1];
+        if (!Process.readProcFile("/proc/" + pid + "/cmdline", CMDLINE_OUT, cmdline, null, null)) {
+            return "";
+        }
+        return cmdline[0];
+    }
+
+    public static void forEachPid(BiConsumer<Integer, String> func) {
+        int[] pids = new int[1024];
+        pids = Process.getPids("/proc", pids);
+        for (int pid : pids) {
+            if (pid < 0) {
+                return;
+            }
+            String cmdline = readCmdlineFromProcfs(pid);
+            if (cmdline.isEmpty()) {
+                continue;
+            }
+            func.accept(pid, cmdline);
+        }
+    }
+
+    public static final class MemorySnapshot {
+        public int uid;
+        public int rssHighWaterMarkInKilobytes;
+        public int rssInKilobytes;
+        public int anonRssInKilobytes;
+        public int swapInKilobytes;
+    }
+}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
new file mode 100644
index 0000000..a0e6be4
--- /dev/null
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -0,0 +1,2899 @@
+/*
+ * 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.stats.pull;
+
+import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED;
+import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
+import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
+import static android.os.Debug.getIonHeapsSizeKb;
+import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
+import static android.os.Process.getUidForPid;
+import static android.os.storage.VolumeInfo.TYPE_PRIVATE;
+import static android.os.storage.VolumeInfo.TYPE_PUBLIC;
+
+import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
+import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
+import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
+import static com.android.server.stats.pull.ProcfsMemoryUtil.forEachPid;
+import static com.android.server.stats.pull.ProcfsMemoryUtil.readCmdlineFromProcfs;
+import static com.android.server.stats.pull.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManagerInternal;
+import android.app.AlarmManager;
+import android.app.AlarmManager.OnAlarmListener;
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.HistoricalOps;
+import android.app.AppOpsManager.HistoricalOpsRequest;
+import android.app.AppOpsManager.HistoricalPackageOps;
+import android.app.AppOpsManager.HistoricalUidOps;
+import android.app.INotificationManager;
+import android.app.ProcessMemoryState;
+import android.app.StatsManager;
+import android.app.StatsManager.PullAtomMetadata;
+import android.bluetooth.BluetoothActivityEnergyInfo;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.UidTraffic;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.content.pm.UserInfo;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.FingerprintManager;
+import android.net.ConnectivityManager;
+import android.net.INetworkStatsService;
+import android.net.Network;
+import android.net.NetworkRequest;
+import android.net.NetworkStats;
+import android.net.wifi.WifiManager;
+import android.os.BatteryStats;
+import android.os.BatteryStatsInternal;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.CoolingDevice;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.IPullAtomCallback;
+import android.os.IStatsCompanionService;
+import android.os.IStatsd;
+import android.os.IStoraged;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
+import android.os.Looper;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.StatFs;
+import android.os.StatsLogEventWrapper;
+import android.os.SynchronousResultReceiver;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.Temperature;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.connectivity.WifiActivityEnergyInfo;
+import android.os.storage.DiskInfo;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+import android.provider.Settings;
+import android.stats.storage.StorageEnums;
+import android.telephony.ModemActivityInfo;
+import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+import android.util.StatsEvent;
+import android.util.StatsLog;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.procstats.IProcessStats;
+import com.android.internal.app.procstats.ProcessStats;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.os.BatterySipper;
+import com.android.internal.os.BatteryStatsHelper;
+import com.android.internal.os.BinderCallsStats.ExportedCallStat;
+import com.android.internal.os.KernelCpuSpeedReader;
+import com.android.internal.os.KernelCpuThreadReader;
+import com.android.internal.os.KernelCpuThreadReaderDiff;
+import com.android.internal.os.KernelCpuThreadReaderSettingsObserver;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader;
+import com.android.internal.os.KernelWakelockReader;
+import com.android.internal.os.KernelWakelockStats;
+import com.android.internal.os.LooperStats;
+import com.android.internal.os.PowerProfile;
+import com.android.internal.os.ProcessCpuTracker;
+import com.android.internal.os.StoragedUidIoStatsReader;
+import com.android.internal.util.DumpUtils;
+import com.android.server.BinderCallsStatsService;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.SystemServiceManager;
+import com.android.server.am.MemoryStatUtil.MemoryStat;
+import com.android.server.notification.NotificationManagerService;
+import com.android.server.role.RoleManagerInternal;
+import com.android.server.stats.pull.IonMemoryUtil.IonAllocations;
+import com.android.server.stats.pull.ProcfsMemoryUtil.MemorySnapshot;
+import com.android.server.storage.DiskStatsFileLogger;
+import com.android.server.storage.DiskStatsLoggingService;
+
+import com.google.android.collect.Sets;
+
+import libcore.io.IoUtils;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+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.Map.Entry;
+import java.util.MissingResourceException;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * SystemService containing PullAtomCallbacks that are registered with statsd.
+ *
+ * @hide
+ */
+public class StatsPullAtomService extends SystemService {
+    private static final String TAG = "StatsPullAtomService";
+    private static final boolean DEBUG = true;
+
+    private static final String RESULT_RECEIVER_CONTROLLER_KEY = "controller_activity";
+    /**
+     * How long to wait on an individual subsystem to return its stats.
+     */
+    private static final long EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS = 2000;
+
+    private final Object mNetworkStatsLock = new Object();
+    @GuardedBy("mNetworkStatsLock")
+    private INetworkStatsService mNetworkStatsService;
+
+    private final Object mThermalLock = new Object();
+    @GuardedBy("mThermalLock")
+    private IThermalService mThermalService;
+
+    private final Object mStoragedLock = new Object();
+    @GuardedBy("mStoragedLock")
+    private IStoraged mStorageService;
+
+    private final Object mNotificationStatsLock = new Object();
+    @GuardedBy("mNotificationStatsLock")
+    private INotificationManager mNotificationManagerService;
+
+    private final Object mProcessStatsLock = new Object();
+    @GuardedBy("mProcessStatsLock")
+    private IProcessStats mProcessStatsService;
+
+    private final Context mContext;
+    private StatsManager mStatsManager;
+    private StorageManager mStorageManager;
+
+    public StatsPullAtomService(Context context) {
+        super(context);
+        mContext = context;
+    }
+
+    @Override
+    public void onStart() {
+        mStatsManager = (StatsManager) mContext.getSystemService(Context.STATS_MANAGER);
+        mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+        mTelephony = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        mStorageManager = (StorageManager) mContext.getSystemService(StorageManager.class);
+
+        // Initialize state for CPU_TIME_PER_FREQ atom
+        PowerProfile powerProfile = new PowerProfile(mContext);
+        final int numClusters = powerProfile.getNumCpuClusters();
+        mKernelCpuSpeedReaders = new KernelCpuSpeedReader[numClusters];
+        int firstCpuOfCluster = 0;
+        for (int i = 0; i < numClusters; i++) {
+            final int numSpeedSteps = powerProfile.getNumSpeedStepsInCpuCluster(i);
+            mKernelCpuSpeedReaders[i] = new KernelCpuSpeedReader(firstCpuOfCluster,
+                    numSpeedSteps);
+            firstCpuOfCluster += powerProfile.getNumCoresInCpuCluster(i);
+        }
+
+        // Used for CPU_TIME_PER_THREAD_FREQ
+        mKernelCpuThreadReader =
+                KernelCpuThreadReaderSettingsObserver.getSettingsModifiedReader(mContext);
+
+        // Used by PROC_STATS and PROC_STATS_PKG_PROC atoms
+        mBaseDir.mkdirs();
+    }
+
+    @Override
+    public void onBootPhase(int phase) {
+        super.onBootPhase(phase);
+        if (phase == PHASE_SYSTEM_SERVICES_READY) {
+            BackgroundThread.getHandler().post(() -> {
+                registerAllPullers();
+            });
+        }
+    }
+
+    void registerAllPullers() {
+        if (DEBUG) {
+            Slog.d(TAG, "Registering all pullers with statsd");
+        }
+        registerWifiBytesTransfer();
+        registerWifiBytesTransferBackground();
+        registerMobileBytesTransfer();
+        registerMobileBytesTransferBackground();
+        registerBluetoothBytesTransfer();
+        registerKernelWakelock();
+        registerCpuTimePerFreq();
+        registerCpuTimePerUid();
+        registerCpuTimePerUidFreq();
+        registerCpuActiveTime();
+        registerCpuClusterTime();
+        registerWifiActivityInfo();
+        registerModemActivityInfo();
+        registerBluetoothActivityInfo();
+        registerSystemElapsedRealtime();
+        registerSystemUptime();
+        registerRemainingBatteryCapacity();
+        registerFullBatteryCapacity();
+        registerBatteryVoltage();
+        registerBatteryLevel();
+        registerBatteryCycleCount();
+        registerProcessMemoryState();
+        registerProcessMemoryHighWaterMark();
+        registerProcessMemorySnapshot();
+        registerSystemIonHeapSize();
+        registerIonHeapSize();
+        registerProcessSystemIonHeapSize();
+        registerTemperature();
+        registerCoolingDevice();
+        registerBinderCallsStats();
+        registerBinderCallsStatsExceptions();
+        registerLooperStats();
+        registerDiskStats();
+        registerDirectoryUsage();
+        registerAppSize();
+        registerCategorySize();
+        registerNumFingerprintsEnrolled();
+        registerNumFacesEnrolled();
+        registerProcStats();
+        registerProcStatsPkgProc();
+        registerDiskIO();
+        registerPowerProfile();
+        registerProcessCpuTime();
+        registerCpuTimePerThreadFreq();
+        registerDeviceCalculatedPowerUse();
+        registerDeviceCalculatedPowerBlameUid();
+        registerDeviceCalculatedPowerBlameOther();
+        registerDebugElapsedClock();
+        registerDebugFailingElapsedClock();
+        registerBuildInformation();
+        registerRoleHolder();
+        registerTimeZoneDataInfo();
+        registerExternalStorageInfo();
+        registerAppsOnExternalStorageInfo();
+        registerFaceSettings();
+        registerAppOps();
+        registerNotificationRemoteViews();
+        registerDangerousPermissionState();
+        registerDangerousPermissionStateSampled();
+    }
+
+    private INetworkStatsService getINetworkStatsService() {
+        synchronized (mNetworkStatsLock) {
+            if (mNetworkStatsService == null) {
+                mNetworkStatsService = INetworkStatsService.Stub.asInterface(
+                        ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
+                if (mNetworkStatsService != null) {
+                    try {
+                        mNetworkStatsService.asBinder().linkToDeath(() -> {
+                            synchronized (mNetworkStatsLock) {
+                                mNetworkStatsService = null;
+                            }
+                        }, /* flags */ 0);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "linkToDeath with NetworkStatsService failed", e);
+                        mNetworkStatsService = null;
+                    }
+                }
+
+            }
+            return mNetworkStatsService;
+        }
+    }
+
+    private IThermalService getIThermalService() {
+        synchronized (mThermalLock) {
+            if (mThermalService == null) {
+                mThermalService = IThermalService.Stub.asInterface(
+                        ServiceManager.getService(Context.THERMAL_SERVICE));
+                if (mThermalService != null) {
+                    try {
+                        mThermalService.asBinder().linkToDeath(() -> {
+                            synchronized (mThermalLock) {
+                                mThermalService = null;
+                            }
+                        }, /* flags */ 0);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "linkToDeath with thermalService failed", e);
+                        mThermalService = null;
+                    }
+                }
+            }
+            return mThermalService;
+        }
+    }
+
+    private IStoraged getIStoragedService() {
+        synchronized (mStoragedLock) {
+            if (mStorageService == null) {
+                mStorageService = IStoraged.Stub.asInterface(
+                        ServiceManager.getService("storaged"));
+            }
+            if (mStorageService != null) {
+                try {
+                    mStorageService.asBinder().linkToDeath(() -> {
+                        synchronized (mStoragedLock) {
+                            mStorageService = null;
+                        }
+                    }, /* flags */ 0);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "linkToDeath with storagedService failed", e);
+                    mStorageService = null;
+                }
+            }
+        }
+        return mStorageService;
+    }
+
+    private INotificationManager getINotificationManagerService() {
+        synchronized (mNotificationStatsLock) {
+            if (mNotificationManagerService == null) {
+                mNotificationManagerService = INotificationManager.Stub.asInterface(
+                        ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+            }
+            if (mNotificationManagerService != null) {
+                try {
+                    mNotificationManagerService.asBinder().linkToDeath(() -> {
+                        synchronized (mNotificationStatsLock) {
+                            mNotificationManagerService = null;
+                        }
+                    }, /* flags */ 0);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "linkToDeath with notificationManager failed", e);
+                    mNotificationManagerService = null;
+                }
+            }
+        }
+        return mNotificationManagerService;
+    }
+
+    private IProcessStats getIProcessStatsService() {
+        synchronized (mProcessStatsLock) {
+            if (mProcessStatsService == null) {
+                mProcessStatsService = IProcessStats.Stub.asInterface(
+                        ServiceManager.getService(ProcessStats.SERVICE_NAME));
+            }
+            if (mProcessStatsService != null) {
+                try {
+                    mProcessStatsService.asBinder().linkToDeath(() -> {
+                        synchronized (mProcessStatsLock) {
+                            mProcessStatsService = null;
+                        }
+                    }, /* flags */ 0);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "linkToDeath with ProcessStats failed", e);
+                    mProcessStatsService = null;
+                }
+            }
+        }
+        return mProcessStatsService;
+    }
+
+    private void registerWifiBytesTransfer() {
+        int tagId = StatsLog.WIFI_BYTES_TRANSFER;
+        PullAtomMetadata metadata = new PullAtomMetadata.Builder()
+                .setAdditiveFields(new int[] {2, 3, 4, 5})
+                .build();
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                metadata,
+                BackgroundThread.getExecutor(),
+                (atomTag, data) -> pullWifiBytesTransfer(atomTag, data)
+        );
+    }
+
+    private int pullWifiBytesTransfer(int atomTag, List<StatsEvent> pulledData) {
+        INetworkStatsService networkStatsService = getINetworkStatsService();
+        if (networkStatsService == null) {
+            Slog.e(TAG, "NetworkStats Service is not available!");
+            return StatsManager.PULL_SKIP;
+        }
+        long token = Binder.clearCallingIdentity();
+        try {
+            // TODO: Consider caching the following call to get BatteryStatsInternal.
+            BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+            String[] ifaces = bs.getWifiIfaces();
+            if (ifaces.length == 0) {
+                return StatsManager.PULL_SKIP;
+            }
+            // Combine all the metrics per Uid into one record.
+            NetworkStats stats = networkStatsService.getDetailedUidStats(ifaces).groupedByUid();
+            addNetworkStats(atomTag, pulledData, stats, false);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Pulling netstats for wifi bytes has error", e);
+            return StatsManager.PULL_SKIP;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void addNetworkStats(
+            int tag, List<StatsEvent> ret, NetworkStats stats, boolean withFGBG) {
+        int size = stats.size();
+        NetworkStats.Entry entry = new NetworkStats.Entry(); // For recycling
+        for (int j = 0; j < size; j++) {
+            stats.getValues(j, entry);
+            StatsEvent.Builder e = StatsEvent.newBuilder();
+            e.setAtomId(tag);
+            e.writeInt(entry.uid);
+            if (withFGBG) {
+                e.writeInt(entry.set);
+            }
+            e.writeLong(entry.rxBytes);
+            e.writeLong(entry.rxPackets);
+            e.writeLong(entry.txBytes);
+            e.writeLong(entry.txPackets);
+            ret.add(e.build());
+        }
+    }
+
+    /**
+     * Allows rollups per UID but keeping the set (foreground/background) slicing.
+     * Adapted from groupedByUid in frameworks/base/core/java/android/net/NetworkStats.java
+     */
+    private NetworkStats rollupNetworkStatsByFGBG(NetworkStats stats) {
+        final NetworkStats ret = new NetworkStats(stats.getElapsedRealtime(), 1);
+
+        final NetworkStats.Entry entry = new NetworkStats.Entry();
+        entry.iface = NetworkStats.IFACE_ALL;
+        entry.tag = NetworkStats.TAG_NONE;
+        entry.metered = NetworkStats.METERED_ALL;
+        entry.roaming = NetworkStats.ROAMING_ALL;
+
+        int size = stats.size();
+        NetworkStats.Entry recycle = new NetworkStats.Entry(); // Used for retrieving values
+        for (int i = 0; i < size; i++) {
+            stats.getValues(i, recycle);
+
+            // Skip specific tags, since already counted in TAG_NONE
+            if (recycle.tag != NetworkStats.TAG_NONE) continue;
+
+            entry.set = recycle.set; // Allows slicing by background/foreground
+            entry.uid = recycle.uid;
+            entry.rxBytes = recycle.rxBytes;
+            entry.rxPackets = recycle.rxPackets;
+            entry.txBytes = recycle.txBytes;
+            entry.txPackets = recycle.txPackets;
+            // Operations purposefully omitted since we don't use them for statsd.
+            ret.combineValues(entry);
+        }
+        return ret;
+    }
+
+    private void registerWifiBytesTransferBackground() {
+        int tagId = StatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG;
+        PullAtomMetadata metadata = new PullAtomMetadata.Builder()
+                .setAdditiveFields(new int[] {3, 4, 5, 6})
+                .build();
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                metadata,
+                BackgroundThread.getExecutor(),
+                (atomTag, data) -> pullWifiBytesTransferBackground(atomTag, data)
+        );
+    }
+
+    private int pullWifiBytesTransferBackground(int atomTag, List<StatsEvent> pulledData) {
+        INetworkStatsService networkStatsService = getINetworkStatsService();
+        if (networkStatsService == null) {
+            Slog.e(TAG, "NetworkStats Service is not available!");
+            return StatsManager.PULL_SKIP;
+        }
+        long token = Binder.clearCallingIdentity();
+        try {
+            BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+            String[] ifaces = bs.getWifiIfaces();
+            if (ifaces.length == 0) {
+                return StatsManager.PULL_SKIP;
+            }
+            NetworkStats stats = rollupNetworkStatsByFGBG(
+                    networkStatsService.getDetailedUidStats(ifaces));
+            addNetworkStats(atomTag, pulledData, stats, true);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Pulling netstats for wifi bytes w/ fg/bg has error", e);
+            return StatsManager.PULL_SKIP;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerMobileBytesTransfer() {
+        int tagId = StatsLog.MOBILE_BYTES_TRANSFER;
+        PullAtomMetadata metadata = new PullAtomMetadata.Builder()
+                .setAdditiveFields(new int[] {2, 3, 4, 5})
+                .build();
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                metadata,
+                BackgroundThread.getExecutor(),
+                (atomTag, data) -> pullMobileBytesTransfer(atomTag, data)
+        );
+    }
+
+    private int pullMobileBytesTransfer(int atomTag, List<StatsEvent> pulledData) {
+        INetworkStatsService networkStatsService = getINetworkStatsService();
+        if (networkStatsService == null) {
+            Slog.e(TAG, "NetworkStats Service is not available!");
+            return StatsManager.PULL_SKIP;
+        }
+        long token = Binder.clearCallingIdentity();
+        try {
+            BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+            String[] ifaces = bs.getMobileIfaces();
+            if (ifaces.length == 0) {
+                return StatsManager.PULL_SKIP;
+            }
+            // Combine all the metrics per Uid into one record.
+            NetworkStats stats = networkStatsService.getDetailedUidStats(ifaces).groupedByUid();
+            addNetworkStats(atomTag, pulledData, stats, false);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Pulling netstats for mobile bytes has error", e);
+            return StatsManager.PULL_SKIP;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerMobileBytesTransferBackground() {
+        int tagId = StatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG;
+        PullAtomMetadata metadata = new PullAtomMetadata.Builder()
+                .setAdditiveFields(new int[] {3, 4, 5, 6})
+                .build();
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                metadata,
+                BackgroundThread.getExecutor(),
+                (atomTag, data) -> pullMobileBytesTransferBackground(atomTag, data)
+        );
+    }
+
+    private int pullMobileBytesTransferBackground(int atomTag, List<StatsEvent> pulledData) {
+        INetworkStatsService networkStatsService = getINetworkStatsService();
+        if (networkStatsService == null) {
+            Slog.e(TAG, "NetworkStats Service is not available!");
+            return StatsManager.PULL_SKIP;
+        }
+        long token = Binder.clearCallingIdentity();
+        try {
+            BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+            String[] ifaces = bs.getMobileIfaces();
+            if (ifaces.length == 0) {
+                return StatsManager.PULL_SKIP;
+            }
+            NetworkStats stats = rollupNetworkStatsByFGBG(
+                    networkStatsService.getDetailedUidStats(ifaces));
+            addNetworkStats(atomTag, pulledData, stats, true);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Pulling netstats for mobile bytes w/ fg/bg has error", e);
+            return StatsManager.PULL_SKIP;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerBluetoothBytesTransfer() {
+        int tagId = StatsLog.BLUETOOTH_BYTES_TRANSFER;
+        PullAtomMetadata metadata = new PullAtomMetadata.Builder()
+                .setAdditiveFields(new int[] {2, 3})
+                .build();
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                metadata,
+                BackgroundThread.getExecutor(),
+                (atomTag, data) -> pullBluetoothBytesTransfer(atomTag, data)
+        );
+    }
+
+    /**
+     * Helper method to extract the Parcelable controller info from a
+     * SynchronousResultReceiver.
+     */
+    private static <T extends Parcelable> T awaitControllerInfo(
+            @Nullable SynchronousResultReceiver receiver) {
+        if (receiver == null) {
+            return null;
+        }
+
+        try {
+            final SynchronousResultReceiver.Result result =
+                    receiver.awaitResult(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS);
+            if (result.bundle != null) {
+                // This is the final destination for the Bundle.
+                result.bundle.setDefusable(true);
+
+                final T data = result.bundle.getParcelable(RESULT_RECEIVER_CONTROLLER_KEY);
+                if (data != null) {
+                    return data;
+                }
+            }
+            Slog.e(TAG, "no controller energy info supplied for " + receiver.getName());
+        } catch (TimeoutException e) {
+            Slog.w(TAG, "timeout reading " + receiver.getName() + " stats");
+        }
+        return null;
+    }
+
+    private synchronized BluetoothActivityEnergyInfo fetchBluetoothData() {
+        // TODO: Investigate whether the synchronized keyword is needed.
+        final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        if (adapter != null) {
+            SynchronousResultReceiver bluetoothReceiver = new SynchronousResultReceiver(
+                    "bluetooth");
+            adapter.requestControllerActivityEnergyInfo(bluetoothReceiver);
+            return awaitControllerInfo(bluetoothReceiver);
+        } else {
+            Slog.e(TAG, "Failed to get bluetooth adapter!");
+            return null;
+        }
+    }
+
+    private int pullBluetoothBytesTransfer(int atomTag, List<StatsEvent> pulledData) {
+        BluetoothActivityEnergyInfo info = fetchBluetoothData();
+        if (info == null || info.getUidTraffic() == null) {
+            return StatsManager.PULL_SKIP;
+        }
+        for (UidTraffic traffic : info.getUidTraffic()) {
+            StatsEvent e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeInt(traffic.getUid())
+                    .writeLong(traffic.getRxBytes())
+                    .writeLong(traffic.getTxBytes())
+                    .build();
+            pulledData.add(e);
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
+    private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
+
+    private void registerKernelWakelock() {
+        int tagId = StatsLog.KERNEL_WAKELOCK;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                /* PullAtomMetadata */ null,
+                BackgroundThread.getExecutor(),
+                (atomTag, data) -> pullKernelWakelock(atomTag, data)
+        );
+    }
+
+    private int pullKernelWakelock(int atomTag, List<StatsEvent> pulledData) {
+        final KernelWakelockStats wakelockStats =
+                mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats);
+        for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
+            String name = ent.getKey();
+            KernelWakelockStats.Entry kws = ent.getValue();
+            StatsEvent e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeString(name)
+                    .writeInt(kws.mCount)
+                    .writeInt(kws.mVersion)
+                    .writeLong(kws.mTotalTime)
+                    .build();
+            pulledData.add(e);
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
+    // Disables throttler on CPU time readers.
+    private KernelCpuUidUserSysTimeReader mCpuUidUserSysTimeReader =
+            new KernelCpuUidUserSysTimeReader(false);
+    private KernelCpuUidFreqTimeReader mCpuUidFreqTimeReader =
+            new KernelCpuUidFreqTimeReader(false);
+    private KernelCpuUidActiveTimeReader mCpuUidActiveTimeReader =
+            new KernelCpuUidActiveTimeReader(false);
+    private KernelCpuUidClusterTimeReader mCpuUidClusterTimeReader =
+            new KernelCpuUidClusterTimeReader(false);
+
+    private void registerCpuTimePerFreq() {
+        int tagId = StatsLog.CPU_TIME_PER_FREQ;
+        PullAtomMetadata metadata = new PullAtomMetadata.Builder()
+                .setAdditiveFields(new int[] {3})
+                .build();
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                metadata,
+                BackgroundThread.getExecutor(),
+                (atomTag, data) -> pullCpuTimePerFreq(atomTag, data)
+        );
+    }
+
+    private int pullCpuTimePerFreq(int atomTag, List<StatsEvent> pulledData) {
+        for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) {
+            long[] clusterTimeMs = mKernelCpuSpeedReaders[cluster].readAbsolute();
+            if (clusterTimeMs != null) {
+                for (int speed = clusterTimeMs.length - 1; speed >= 0; --speed) {
+                    StatsEvent e = StatsEvent.newBuilder()
+                            .setAtomId(atomTag)
+                            .writeInt(cluster)
+                            .writeInt(speed)
+                            .writeLong(clusterTimeMs[speed])
+                            .build();
+                    pulledData.add(e);
+                }
+            }
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerCpuTimePerUid() {
+        int tagId = StatsLog.CPU_TIME_PER_UID;
+        PullAtomMetadata metadata = new PullAtomMetadata.Builder()
+                .setAdditiveFields(new int[] {2, 3})
+                .build();
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                metadata,
+                BackgroundThread.getExecutor(),
+                (atomTag, data) -> pullCpuTimePerUid(atomTag, data)
+        );
+    }
+
+    private int pullCpuTimePerUid(int atomTag, List<StatsEvent> pulledData) {
+        mCpuUidUserSysTimeReader.readAbsolute((uid, timesUs) -> {
+            long userTimeUs = timesUs[0], systemTimeUs = timesUs[1];
+            StatsEvent e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeInt(uid)
+                    .writeLong(userTimeUs)
+                    .writeLong(systemTimeUs)
+                    .build();
+            pulledData.add(e);
+        });
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerCpuTimePerUidFreq() {
+        // the throttling is 3sec, handled in
+        // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
+        int tagId = StatsLog.CPU_TIME_PER_UID_FREQ;
+        PullAtomMetadata metadata = new PullAtomMetadata.Builder()
+                .setAdditiveFields(new int[] {4})
+                .build();
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                metadata,
+                BackgroundThread.getExecutor(),
+                (atomTag, data) -> pullCpuTimeperUidFreq(atomTag, data)
+        );
+    }
+
+    private int pullCpuTimeperUidFreq(int atomTag, List<StatsEvent> pulledData) {
+        mCpuUidFreqTimeReader.readAbsolute((uid, cpuFreqTimeMs) -> {
+            for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) {
+                if (cpuFreqTimeMs[freqIndex] != 0) {
+                    StatsEvent e = StatsEvent.newBuilder()
+                            .setAtomId(atomTag)
+                            .writeInt(uid)
+                            .writeInt(freqIndex)
+                            .writeLong(cpuFreqTimeMs[freqIndex])
+                            .build();
+                    pulledData.add(e);
+                }
+            }
+        });
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerCpuActiveTime() {
+        // the throttling is 3sec, handled in
+        // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
+        int tagId = StatsLog.CPU_ACTIVE_TIME;
+        PullAtomMetadata metadata = new PullAtomMetadata.Builder()
+                .setAdditiveFields(new int[] {2})
+                .build();
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                metadata,
+                BackgroundThread.getExecutor(),
+                (atomTag, data) -> pullCpuActiveTime(atomTag, data)
+        );
+    }
+
+    private int pullCpuActiveTime(int atomTag, List<StatsEvent> pulledData) {
+        mCpuUidActiveTimeReader.readAbsolute((uid, cpuActiveTimesMs) -> {
+            StatsEvent e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeInt(uid)
+                    .writeLong(cpuActiveTimesMs)
+                    .build();
+            pulledData.add(e);
+        });
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerCpuClusterTime() {
+        // the throttling is 3sec, handled in
+        // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
+        int tagId = StatsLog.CPU_CLUSTER_TIME;
+        PullAtomMetadata metadata = new PullAtomMetadata.Builder()
+                .setAdditiveFields(new int[] {3})
+                .build();
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                metadata,
+                BackgroundThread.getExecutor(),
+                (atomTag, data) -> pullCpuClusterTime(atomTag, data)
+        );
+    }
+
+    private int pullCpuClusterTime(int atomTag, List<StatsEvent> pulledData) {
+        mCpuUidClusterTimeReader.readAbsolute((uid, cpuClusterTimesMs) -> {
+            for (int i = 0; i < cpuClusterTimesMs.length; i++) {
+                StatsEvent e = StatsEvent.newBuilder()
+                        .setAtomId(atomTag)
+                        .writeInt(uid)
+                        .writeInt(i)
+                        .writeLong(cpuClusterTimesMs[i])
+                        .build();
+                pulledData.add(e);
+            }
+        });
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerWifiActivityInfo() {
+        int tagId = StatsLog.WIFI_ACTIVITY_INFO;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullWifiActivityInfo(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private WifiManager mWifiManager;
+    private TelephonyManager mTelephony;
+
+    private int pullWifiActivityInfo(int atomTag, List<StatsEvent> pulledData) {
+        long token = Binder.clearCallingIdentity();
+        try {
+            SynchronousResultReceiver wifiReceiver = new SynchronousResultReceiver("wifi");
+            mWifiManager.getWifiActivityEnergyInfoAsync(
+                    new Executor() {
+                        @Override
+                        public void execute(Runnable runnable) {
+                            // run the listener on the binder thread, if it was run on the main
+                            // thread it would deadlock since we would be waiting on ourselves
+                            runnable.run();
+                        }
+                    },
+                    info -> {
+                        Bundle bundle = new Bundle();
+                        bundle.putParcelable(BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY, info);
+                        wifiReceiver.send(0, bundle);
+                    }
+            );
+            final WifiActivityEnergyInfo wifiInfo = awaitControllerInfo(wifiReceiver);
+            if (wifiInfo == null) {
+                return StatsManager.PULL_SKIP;
+            }
+            StatsEvent e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeLong(wifiInfo.getTimeSinceBootMillis())
+                    .writeInt(wifiInfo.getStackState())
+                    .writeLong(wifiInfo.getControllerTxDurationMillis())
+                    .writeLong(wifiInfo.getControllerRxDurationMillis())
+                    .writeLong(wifiInfo.getControllerIdleDurationMillis())
+                    .writeLong(wifiInfo.getControllerEnergyUsedMicroJoules())
+                    .build();
+            pulledData.add(e);
+        } catch (RuntimeException e) {
+            Slog.e(TAG, "failed to getWifiActivityEnergyInfoAsync", e);
+            return StatsManager.PULL_SKIP;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerModemActivityInfo() {
+        int tagId = StatsLog.MODEM_ACTIVITY_INFO;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullModemActivityInfo(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullModemActivityInfo(int atomTag, List<StatsEvent> pulledData) {
+        long token = Binder.clearCallingIdentity();
+        try {
+            SynchronousResultReceiver modemReceiver = new SynchronousResultReceiver("telephony");
+            mTelephony.requestModemActivityInfo(modemReceiver);
+            final ModemActivityInfo modemInfo = awaitControllerInfo(modemReceiver);
+            if (modemInfo == null) {
+                return StatsManager.PULL_SKIP;
+            }
+            StatsEvent e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeLong(modemInfo.getTimestamp())
+                    .writeLong(modemInfo.getSleepTimeMillis())
+                    .writeLong(modemInfo.getIdleTimeMillis())
+                    .writeLong(modemInfo.getTransmitPowerInfo().get(0).getTimeInMillis())
+                    .writeLong(modemInfo.getTransmitPowerInfo().get(1).getTimeInMillis())
+                    .writeLong(modemInfo.getTransmitPowerInfo().get(2).getTimeInMillis())
+                    .writeLong(modemInfo.getTransmitPowerInfo().get(3).getTimeInMillis())
+                    .writeLong(modemInfo.getTransmitPowerInfo().get(4).getTimeInMillis())
+                    .writeLong(modemInfo.getReceiveTimeMillis())
+                    .build();
+            pulledData.add(e);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerBluetoothActivityInfo() {
+        int tagId = StatsLog.BLUETOOTH_ACTIVITY_INFO;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                /* metadata */ null,
+                BackgroundThread.getExecutor(),
+                (atomTag, data) -> pullBluetoothActivityInfo(atomTag, data)
+        );
+    }
+
+    private int pullBluetoothActivityInfo(int atomTag, List<StatsEvent> pulledData) {
+        BluetoothActivityEnergyInfo info = fetchBluetoothData();
+        if (info == null) {
+            return StatsManager.PULL_SKIP;
+        }
+        StatsEvent e = StatsEvent.newBuilder()
+                .setAtomId(atomTag)
+                .writeLong(info.getTimeStamp())
+                .writeInt(info.getBluetoothStackState())
+                .writeLong(info.getControllerTxTimeMillis())
+                .writeLong(info.getControllerRxTimeMillis())
+                .writeLong(info.getControllerIdleTimeMillis())
+                .writeLong(info.getControllerEnergyUsed())
+                .build();
+        pulledData.add(e);
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private static final long NS_PER_SEC = 1000000000;
+
+    private void registerSystemElapsedRealtime() {
+        int tagId = StatsLog.SYSTEM_ELAPSED_REALTIME;
+        PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+                .setCoolDownNs(NS_PER_SEC)
+                .setTimeoutNs(NS_PER_SEC / 2)
+                .build();
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                metadata,
+                (atomTag, data) -> pullSystemElapsedRealtime(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullSystemElapsedRealtime(int atomTag, List<StatsEvent> pulledData) {
+        StatsEvent e = StatsEvent.newBuilder()
+                .setAtomId(atomTag)
+                .writeLong(SystemClock.elapsedRealtime())
+                .build();
+        pulledData.add(e);
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerSystemUptime() {
+        int tagId = StatsLog.SYSTEM_UPTIME;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                BackgroundThread.getExecutor(),
+                (atomTag, data) -> pullSystemUptime(atomTag, data)
+        );
+    }
+
+    private int pullSystemUptime(int atomTag, List<StatsEvent> pulledData) {
+        StatsEvent e = StatsEvent.newBuilder()
+                .setAtomId(atomTag)
+                .writeLong(SystemClock.elapsedRealtime())
+                .build();
+        pulledData.add(e);
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerRemainingBatteryCapacity() {
+        // No op.
+    }
+
+    private void pullRemainingBatteryCapacity() {
+        // No op.
+    }
+
+    private void registerFullBatteryCapacity() {
+        // No op.
+    }
+
+    private void pullFullBatteryCapacity() {
+        // No op.
+    }
+
+    private void registerBatteryVoltage() {
+        // No op.
+    }
+
+    private void pullBatteryVoltage() {
+        // No op.
+    }
+
+    private void registerBatteryLevel() {
+        // No op.
+    }
+
+    private void pullBatteryLevel() {
+        // No op.
+    }
+
+    private void registerBatteryCycleCount() {
+        // No op.
+    }
+
+    private void pullBatteryCycleCount() {
+        // No op.
+    }
+
+    private void registerProcessMemoryState() {
+        int tagId = StatsLog.PROCESS_MEMORY_STATE;
+        PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+                .setAdditiveFields(new int[] {4, 5, 6, 7, 8})
+                .build();
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                metadata,
+                (atomTag, data) -> pullProcessMemoryState(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullProcessMemoryState(int atomTag, List<StatsEvent> pulledData) {
+        List<ProcessMemoryState> processMemoryStates =
+                LocalServices.getService(ActivityManagerInternal.class)
+                        .getMemoryStateForProcesses();
+        for (ProcessMemoryState processMemoryState : processMemoryStates) {
+            final MemoryStat memoryStat = readMemoryStatFromFilesystem(processMemoryState.uid,
+                    processMemoryState.pid);
+            if (memoryStat == null) {
+                continue;
+            }
+            StatsEvent e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeInt(processMemoryState.uid)
+                    .writeString(processMemoryState.processName)
+                    .writeInt(processMemoryState.oomScore)
+                    .writeLong(memoryStat.pgfault)
+                    .writeLong(memoryStat.pgmajfault)
+                    .writeLong(memoryStat.rssInBytes)
+                    .writeLong(memoryStat.cacheInBytes)
+                    .writeLong(memoryStat.swapInBytes)
+                    .writeLong(-1)  // unused
+                    .writeLong(-1)  // unused
+                    .writeInt(-1)  // unused
+                    .build();
+            pulledData.add(e);
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    /**
+     * Which native processes to snapshot memory for.
+     *
+     * <p>Processes are matched by their cmdline in procfs. Example: cat /proc/pid/cmdline returns
+     * /system/bin/statsd for the stats daemon.
+     */
+    private static final Set<String> MEMORY_INTERESTING_NATIVE_PROCESSES = Sets.newHashSet(
+            "/system/bin/statsd",  // Stats daemon.
+            "/system/bin/surfaceflinger",
+            "/system/bin/apexd",  // APEX daemon.
+            "/system/bin/audioserver",
+            "/system/bin/cameraserver",
+            "/system/bin/drmserver",
+            "/system/bin/healthd",
+            "/system/bin/incidentd",
+            "/system/bin/installd",
+            "/system/bin/lmkd",  // Low memory killer daemon.
+            "/system/bin/logd",
+            "media.codec",
+            "media.extractor",
+            "media.metrics",
+            "/system/bin/mediadrmserver",
+            "/system/bin/mediaserver",
+            "/system/bin/performanced",
+            "/system/bin/tombstoned",
+            "/system/bin/traced",  // Perfetto.
+            "/system/bin/traced_probes",  // Perfetto.
+            "webview_zygote",
+            "zygote",
+            "zygote64");
+
+    /**
+     * Lowest available uid for apps.
+     *
+     * <p>Used to quickly discard memory snapshots of the zygote forks from native process
+     * measurements.
+     */
+    private static final int MIN_APP_UID = 10_000;
+
+    private static boolean isAppUid(int uid) {
+        return uid >= MIN_APP_UID;
+    }
+
+    private void registerProcessMemoryHighWaterMark() {
+        int tagId = StatsLog.PROCESS_MEMORY_HIGH_WATER_MARK;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullProcessMemoryHighWaterMark(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullProcessMemoryHighWaterMark(int atomTag, List<StatsEvent> pulledData) {
+        List<ProcessMemoryState> managedProcessList =
+                LocalServices.getService(ActivityManagerInternal.class)
+                        .getMemoryStateForProcesses();
+        for (ProcessMemoryState managedProcess : managedProcessList) {
+            final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(managedProcess.pid);
+            if (snapshot == null) {
+                continue;
+            }
+            StatsEvent e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeInt(managedProcess.uid)
+                    .writeString(managedProcess.processName)
+                    // RSS high-water mark in bytes.
+                    .writeLong(snapshot.rssHighWaterMarkInKilobytes * 1024L)
+                    .writeInt(snapshot.rssHighWaterMarkInKilobytes)
+                    .build();
+            pulledData.add(e);
+        }
+        forEachPid((pid, cmdLine) -> {
+            if (!MEMORY_INTERESTING_NATIVE_PROCESSES.contains(cmdLine)) {
+                return;
+            }
+            final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(pid);
+            if (snapshot == null) {
+                return;
+            }
+            // Sometimes we get here a process that is not included in the whitelist. It comes
+            // from forking the zygote for an app. We can ignore that sample because this process
+            // is collected by ProcessMemoryState.
+            if (isAppUid(snapshot.uid)) {
+                return;
+            }
+            StatsEvent e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeInt(snapshot.uid)
+                    .writeString(cmdLine)
+                    // RSS high-water mark in bytes.
+                    .writeLong(snapshot.rssHighWaterMarkInKilobytes * 1024L)
+                    .writeInt(snapshot.rssHighWaterMarkInKilobytes)
+                    .build();
+            pulledData.add(e);
+        });
+        // Invoke rss_hwm_reset binary to reset RSS HWM counters for all processes.
+        SystemProperties.set("sys.rss_hwm_reset.on", "1");
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerProcessMemorySnapshot() {
+        int tagId = StatsLog.PROCESS_MEMORY_SNAPSHOT;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullProcessMemorySnapshot(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullProcessMemorySnapshot(int atomTag, List<StatsEvent> pulledData) {
+        List<ProcessMemoryState> managedProcessList =
+                LocalServices.getService(ActivityManagerInternal.class)
+                        .getMemoryStateForProcesses();
+        for (ProcessMemoryState managedProcess : managedProcessList) {
+            final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(managedProcess.pid);
+            if (snapshot == null) {
+                continue;
+            }
+            StatsEvent e = StatsEvent.newBuilder()
+                    .writeInt(managedProcess.uid)
+                    .writeString(managedProcess.processName)
+                    .writeInt(managedProcess.pid)
+                    .writeInt(managedProcess.oomScore)
+                    .writeInt(snapshot.rssInKilobytes)
+                    .writeInt(snapshot.anonRssInKilobytes)
+                    .writeInt(snapshot.swapInKilobytes)
+                    .writeInt(snapshot.anonRssInKilobytes + snapshot.swapInKilobytes)
+                    .build();
+            pulledData.add(e);
+        }
+        forEachPid((pid, cmdLine) -> {
+            if (!MEMORY_INTERESTING_NATIVE_PROCESSES.contains(cmdLine)) {
+                return;
+            }
+            final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(pid);
+            if (snapshot == null) {
+                return;
+            }
+            // Sometimes we get here a process that is not included in the whitelist. It comes
+            // from forking the zygote for an app. We can ignore that sample because this process
+            // is collected by ProcessMemoryState.
+            if (isAppUid(snapshot.uid)) {
+                return;
+            }
+            StatsEvent e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeInt(snapshot.uid)
+                    .writeString(cmdLine)
+                    .writeInt(pid)
+                    .writeInt(-1001)  // Placeholder for native processes, OOM_SCORE_ADJ_MIN - 1.
+                    .writeInt(snapshot.rssInKilobytes)
+                    .writeInt(snapshot.anonRssInKilobytes)
+                    .writeInt(snapshot.swapInKilobytes)
+                    .writeInt(snapshot.anonRssInKilobytes + snapshot.swapInKilobytes)
+                    .build();
+            pulledData.add(e);
+        });
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerSystemIonHeapSize() {
+        int tagId = StatsLog.SYSTEM_ION_HEAP_SIZE;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullSystemIonHeapSize(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullSystemIonHeapSize(int atomTag, List<StatsEvent> pulledData) {
+        final long systemIonHeapSizeInBytes = readSystemIonHeapSizeFromDebugfs();
+        StatsEvent e = StatsEvent.newBuilder()
+                .setAtomId(atomTag)
+                .writeLong(systemIonHeapSizeInBytes)
+                .build();
+        pulledData.add(e);
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerIonHeapSize() {
+        int tagId = StatsLog.ION_HEAP_SIZE;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                /* PullAtomMetadata */ null,
+                BackgroundThread.getExecutor(),
+                (atomTag, data) -> pullIonHeapSize(atomTag, data)
+        );
+    }
+
+    private int pullIonHeapSize(int atomTag, List<StatsEvent> pulledData) {
+        int ionHeapSizeInKilobytes = (int) getIonHeapsSizeKb();
+        StatsEvent e = StatsEvent.newBuilder()
+              .setAtomId(atomTag)
+              .writeInt(ionHeapSizeInKilobytes)
+              .build();
+        pulledData.add(e);
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerProcessSystemIonHeapSize() {
+        int tagId = StatsLog.PROCESS_SYSTEM_ION_HEAP_SIZE;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullProcessSystemIonHeapSize(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullProcessSystemIonHeapSize(int atomTag, List<StatsEvent> pulledData) {
+        List<IonAllocations> result = readProcessSystemIonHeapSizesFromDebugfs();
+        for (IonAllocations allocations : result) {
+            StatsEvent e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeInt(getUidForPid(allocations.pid))
+                    .writeString(readCmdlineFromProcfs(allocations.pid))
+                    .writeInt((int) (allocations.totalSizeInBytes / 1024))
+                    .writeInt(allocations.count)
+                    .writeInt((int) (allocations.maxSizeInBytes / 1024))
+                    .build();
+            pulledData.add(e);
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerTemperature() {
+        int tagId = StatsLog.TEMPERATURE;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullTemperature(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullTemperature(int atomTag, List<StatsEvent> pulledData) {
+        IThermalService thermalService = getIThermalService();
+        if (thermalService == null) {
+            return StatsManager.PULL_SKIP;
+        }
+        final long callingToken = Binder.clearCallingIdentity();
+        try {
+            List<Temperature> temperatures = thermalService.getCurrentTemperatures();
+            for (Temperature temp : temperatures) {
+                StatsEvent e = StatsEvent.newBuilder()
+                        .setAtomId(atomTag)
+                        .writeInt(temp.getType())
+                        .writeString(temp.getName())
+                        .writeInt((int) (temp.getValue() * 10))
+                        .writeInt(temp.getStatus())
+                        .build();
+                pulledData.add(e);
+            }
+        } catch (RemoteException e) {
+            // Should not happen.
+            Slog.e(TAG, "Disconnected from thermal service. Cannot pull temperatures.");
+            return StatsManager.PULL_SKIP;
+        } finally {
+            Binder.restoreCallingIdentity(callingToken);
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerCoolingDevice() {
+        int tagId = StatsLog.COOLING_DEVICE;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullCooldownDevice(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullCooldownDevice(int atomTag, List<StatsEvent> pulledData) {
+        IThermalService thermalService = getIThermalService();
+        if (thermalService == null) {
+            return StatsManager.PULL_SKIP;
+        }
+        final long callingToken = Binder.clearCallingIdentity();
+        try {
+            List<CoolingDevice> devices = thermalService.getCurrentCoolingDevices();
+            for (CoolingDevice device : devices) {
+                StatsEvent e = StatsEvent.newBuilder()
+                        .setAtomId(atomTag)
+                        .writeInt(device.getType())
+                        .writeString(device.getName())
+                        .writeInt((int) (device.getValue()))
+                        .build();
+                pulledData.add(e);
+            }
+        } catch (RemoteException e) {
+            // Should not happen.
+            Slog.e(TAG, "Disconnected from thermal service. Cannot pull temperatures.");
+            return StatsManager.PULL_SKIP;
+        } finally {
+            Binder.restoreCallingIdentity(callingToken);
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerBinderCallsStats() {
+        int tagId = StatsLog.BINDER_CALLS;
+        PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+                .setAdditiveFields(new int[] {4, 5, 6, 8, 12})
+                .build();
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                metadata,
+                (atomTag, data) -> pullBinderCallsStats(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullBinderCallsStats(int atomTag, List<StatsEvent> pulledData) {
+        BinderCallsStatsService.Internal binderStats =
+                LocalServices.getService(BinderCallsStatsService.Internal.class);
+        if (binderStats == null) {
+            Slog.e(TAG, "failed to get binderStats");
+            return StatsManager.PULL_SKIP;
+        }
+
+        List<ExportedCallStat> callStats = binderStats.getExportedCallStats();
+        binderStats.reset();
+        for (ExportedCallStat callStat : callStats) {
+            StatsEvent e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeInt(callStat.workSourceUid)
+                    .writeString(callStat.className)
+                    .writeString(callStat.methodName)
+                    .writeLong(callStat.callCount)
+                    .writeLong(callStat.exceptionCount)
+                    .writeLong(callStat.latencyMicros)
+                    .writeLong(callStat.maxLatencyMicros)
+                    .writeLong(callStat.cpuTimeMicros)
+                    .writeLong(callStat.maxCpuTimeMicros)
+                    .writeLong(callStat.maxReplySizeBytes)
+                    .writeLong(callStat.maxRequestSizeBytes)
+                    .writeLong(callStat.recordedCallCount)
+                    .writeInt(callStat.screenInteractive ? 1 : 0)
+                    .writeInt(callStat.callingUid)
+                    .build();
+            pulledData.add(e);
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerBinderCallsStatsExceptions() {
+        int tagId = StatsLog.BINDER_CALLS_EXCEPTIONS;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullBinderCallsStatsExceptions(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullBinderCallsStatsExceptions(int atomTag, List<StatsEvent> pulledData) {
+        BinderCallsStatsService.Internal binderStats =
+                LocalServices.getService(BinderCallsStatsService.Internal.class);
+        if (binderStats == null) {
+            Slog.e(TAG, "failed to get binderStats");
+            return StatsManager.PULL_SKIP;
+        }
+
+        ArrayMap<String, Integer> exceptionStats = binderStats.getExportedExceptionStats();
+        // TODO: decouple binder calls exceptions with the rest of the binder calls data so that we
+        // can reset the exception stats.
+        for (Map.Entry<String, Integer> entry : exceptionStats.entrySet()) {
+            StatsEvent e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeString(entry.getKey())
+                    .writeInt(entry.getValue())
+                    .build();
+            pulledData.add(e);
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerLooperStats() {
+        int tagId = StatsLog.LOOPER_STATS;
+        PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+                .setAdditiveFields(new int[] {5, 6, 7, 8, 9})
+                .build();
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                metadata,
+                (atomTag, data) -> pullLooperStats(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullLooperStats(int atomTag, List<StatsEvent> pulledData) {
+        LooperStats looperStats = LocalServices.getService(LooperStats.class);
+        if (looperStats == null) {
+            return StatsManager.PULL_SKIP;
+        }
+
+        List<LooperStats.ExportedEntry> entries = looperStats.getEntries();
+        looperStats.reset();
+        for (LooperStats.ExportedEntry entry : entries) {
+            StatsEvent e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeInt(entry.workSourceUid)
+                    .writeString(entry.handlerClassName)
+                    .writeString(entry.threadName)
+                    .writeString(entry.messageName)
+                    .writeLong(entry.messageCount)
+                    .writeLong(entry.exceptionCount)
+                    .writeLong(entry.recordedMessageCount)
+                    .writeLong(entry.totalLatencyMicros)
+                    .writeLong(entry.cpuUsageMicros)
+                    .writeBoolean(entry.isInteractive)
+                    .writeLong(entry.maxCpuUsageMicros)
+                    .writeLong(entry.maxLatencyMicros)
+                    .writeLong(entry.recordedDelayMessageCount)
+                    .writeLong(entry.delayMillis)
+                    .writeLong(entry.maxDelayMillis)
+                    .build();
+            pulledData.add(e);
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerDiskStats() {
+        int tagId = StatsLog.DISK_STATS;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullDiskStats(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullDiskStats(int atomTag, List<StatsEvent> pulledData) {
+        // Run a quick-and-dirty performance test: write 512 bytes
+        byte[] junk = new byte[512];
+        for (int i = 0; i < junk.length; i++) junk[i] = (byte) i;  // Write nonzero bytes
+
+        File tmp = new File(Environment.getDataDirectory(), "system/statsdperftest.tmp");
+        FileOutputStream fos = null;
+        IOException error = null;
+
+        long before = SystemClock.elapsedRealtime();
+        try {
+            fos = new FileOutputStream(tmp);
+            fos.write(junk);
+        } catch (IOException e) {
+            error = e;
+        } finally {
+            try {
+                if (fos != null) fos.close();
+            } catch (IOException e) {
+                // Do nothing.
+            }
+        }
+
+        long latency = SystemClock.elapsedRealtime() - before;
+        if (tmp.exists()) tmp.delete();
+
+        if (error != null) {
+            Slog.e(TAG, "Error performing diskstats latency test");
+            latency = -1;
+        }
+        // File based encryption.
+        boolean fileBased = StorageManager.isFileEncryptedNativeOnly();
+
+        //Recent disk write speed. Binder call to storaged.
+        int writeSpeed = -1;
+        IStoraged storaged = getIStoragedService();
+        if (storaged == null) {
+            return StatsManager.PULL_SKIP;
+        }
+        try {
+            writeSpeed = storaged.getRecentPerf();
+        } catch (RemoteException e) {
+            Slog.e(TAG, "storaged not found");
+        }
+
+        // Add info pulledData.
+        StatsEvent e = StatsEvent.newBuilder()
+                .setAtomId(atomTag)
+                .writeLong(latency)
+                .writeBoolean(fileBased)
+                .writeInt(writeSpeed)
+                .build();
+        pulledData.add(e);
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerDirectoryUsage() {
+        int tagId = StatsLog.DIRECTORY_USAGE;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullDirectoryUsage(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullDirectoryUsage(int atomTag, List<StatsEvent> pulledData) {
+        StatFs statFsData = new StatFs(Environment.getDataDirectory().getAbsolutePath());
+        StatFs statFsSystem = new StatFs(Environment.getRootDirectory().getAbsolutePath());
+        StatFs statFsCache = new StatFs(Environment.getDownloadCacheDirectory().getAbsolutePath());
+
+        StatsEvent e = StatsEvent.newBuilder()
+                .setAtomId(atomTag)
+                .writeInt(StatsLog.DIRECTORY_USAGE__DIRECTORY__DATA)
+                .writeLong(statFsData.getAvailableBytes())
+                .writeLong(statFsData.getTotalBytes())
+                .build();
+        pulledData.add(e);
+
+        e = StatsEvent.newBuilder()
+                .setAtomId(atomTag)
+                .writeInt(StatsLog.DIRECTORY_USAGE__DIRECTORY__CACHE)
+                .writeLong(statFsCache.getAvailableBytes())
+                .writeLong(statFsCache.getTotalBytes())
+                .build();
+        pulledData.add(e);
+
+        e = StatsEvent.newBuilder()
+                .setAtomId(atomTag)
+                .writeInt(StatsLog.DIRECTORY_USAGE__DIRECTORY__SYSTEM)
+                .writeLong(statFsSystem.getAvailableBytes())
+                .writeLong(statFsSystem.getTotalBytes())
+                .build();
+        pulledData.add(e);
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerAppSize() {
+        int tagId = StatsLog.APP_SIZE;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullAppSize(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullAppSize(int atomTag, List<StatsEvent> pulledData) {
+        try {
+            String jsonStr = IoUtils.readFileAsString(DiskStatsLoggingService.DUMPSYS_CACHE_PATH);
+            JSONObject json = new JSONObject(jsonStr);
+            long cache_time = json.optLong(DiskStatsFileLogger.LAST_QUERY_TIMESTAMP_KEY, -1L);
+            JSONArray pkg_names = json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY);
+            JSONArray app_sizes = json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY);
+            JSONArray app_data_sizes = json.getJSONArray(DiskStatsFileLogger.APP_DATA_KEY);
+            JSONArray app_cache_sizes = json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY);
+            // Sanity check: Ensure all 4 lists have the same length.
+            int length = pkg_names.length();
+            if (app_sizes.length() != length || app_data_sizes.length() != length
+                    || app_cache_sizes.length() != length) {
+                Slog.e(TAG, "formatting error in diskstats cache file!");
+                return StatsManager.PULL_SKIP;
+            }
+            for (int i = 0; i < length; i++) {
+                StatsEvent e = StatsEvent.newBuilder()
+                        .setAtomId(atomTag)
+                        .writeString(pkg_names.getString(i))
+                        .writeLong(app_sizes.optLong(i, /* fallback */ -1L))
+                        .writeLong(app_data_sizes.optLong(i, /* fallback */ -1L))
+                        .writeLong(app_cache_sizes.optLong(i, /* fallback */ -1L))
+                        .writeLong(cache_time)
+                        .build();
+                pulledData.add(e);
+            }
+        } catch (IOException | JSONException e) {
+            Slog.e(TAG, "exception reading diskstats cache file", e);
+            return StatsManager.PULL_SKIP;
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerCategorySize() {
+        int tagId = StatsLog.CATEGORY_SIZE;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullCategorySize(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullCategorySize(int atomTag, List<StatsEvent> pulledData) {
+        try {
+            String jsonStr = IoUtils.readFileAsString(DiskStatsLoggingService.DUMPSYS_CACHE_PATH);
+            JSONObject json = new JSONObject(jsonStr);
+            long cacheTime = json.optLong(
+                    DiskStatsFileLogger.LAST_QUERY_TIMESTAMP_KEY, /* fallback */ -1L);
+
+            StatsEvent e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeInt(StatsLog.CATEGORY_SIZE__CATEGORY__APP_SIZE)
+                    .writeLong(json.optLong(
+                            DiskStatsFileLogger.APP_SIZE_AGG_KEY, /* fallback */ -1L))
+                    .writeLong(cacheTime)
+                    .build();
+            pulledData.add(e);
+
+            e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeInt(StatsLog.CATEGORY_SIZE__CATEGORY__APP_DATA_SIZE)
+                    .writeLong(json.optLong(
+                            DiskStatsFileLogger.APP_DATA_SIZE_AGG_KEY, /* fallback */ -1L))
+                    .writeLong(cacheTime)
+                    .build();
+            pulledData.add(e);
+
+            e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeInt(StatsLog.CATEGORY_SIZE__CATEGORY__APP_CACHE_SIZE)
+                    .writeLong(json.optLong(
+                            DiskStatsFileLogger.APP_CACHE_AGG_KEY, /* fallback */ -1L))
+                    .writeLong(cacheTime)
+                    .build();
+            pulledData.add(e);
+
+            e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeInt(StatsLog.CATEGORY_SIZE__CATEGORY__PHOTOS)
+                    .writeLong(json.optLong(
+                            DiskStatsFileLogger.PHOTOS_KEY, /* fallback */ -1L))
+                    .writeLong(cacheTime)
+                    .build();
+            pulledData.add(e);
+
+            e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeInt(StatsLog.CATEGORY_SIZE__CATEGORY__VIDEOS)
+                    .writeLong(
+                            json.optLong(DiskStatsFileLogger.VIDEOS_KEY, /* fallback */ -1L))
+                    .writeLong(cacheTime)
+                    .build();
+            pulledData.add(e);
+
+            e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeInt(StatsLog.CATEGORY_SIZE__CATEGORY__AUDIO)
+                    .writeLong(json.optLong(
+                            DiskStatsFileLogger.AUDIO_KEY, /* fallback */ -1L))
+                    .writeLong(cacheTime)
+                    .build();
+            pulledData.add(e);
+
+            e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeInt(StatsLog.CATEGORY_SIZE__CATEGORY__DOWNLOADS)
+                    .writeLong(
+                            json.optLong(DiskStatsFileLogger.DOWNLOADS_KEY, /* fallback */ -1L))
+                    .writeLong(cacheTime)
+                    .build();
+            pulledData.add(e);
+
+            e = StatsEvent.newBuilder()
+                    .writeInt(StatsLog.CATEGORY_SIZE__CATEGORY__SYSTEM)
+                    .writeLong(json.optLong(
+                            DiskStatsFileLogger.SYSTEM_KEY, /* fallback */ -1L))
+                    .writeLong(cacheTime)
+                    .build();
+            pulledData.add(e);
+
+            e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeInt(StatsLog.CATEGORY_SIZE__CATEGORY__OTHER)
+                    .writeLong(json.optLong(
+                            DiskStatsFileLogger.MISC_KEY, /* fallback */ -1L))
+                    .writeLong(cacheTime)
+                    .build();
+            pulledData.add(e);
+        } catch (IOException | JSONException e) {
+            Slog.e(TAG, "exception reading diskstats cache file", e);
+            return StatsManager.PULL_SKIP;
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerNumFingerprintsEnrolled() {
+        int tagId = StatsLog.NUM_FINGERPRINTS_ENROLLED;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullNumBiometricsEnrolled(
+                        BiometricsProtoEnums.MODALITY_FINGERPRINT, atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private void registerNumFacesEnrolled() {
+        int tagId = StatsLog.NUM_FACES_ENROLLED;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullNumBiometricsEnrolled(
+                        BiometricsProtoEnums.MODALITY_FACE, atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullNumBiometricsEnrolled(int modality, int atomTag, List<StatsEvent> pulledData) {
+        final PackageManager pm = mContext.getPackageManager();
+        FingerprintManager fingerprintManager = null;
+        FaceManager faceManager = null;
+
+        if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+            fingerprintManager = mContext.getSystemService(FingerprintManager.class);
+        }
+        if (pm.hasSystemFeature(PackageManager.FEATURE_FACE)) {
+            faceManager = mContext.getSystemService(FaceManager.class);
+        }
+
+        if (modality == BiometricsProtoEnums.MODALITY_FINGERPRINT && fingerprintManager == null) {
+            return StatsManager.PULL_SKIP;
+        }
+        if (modality == BiometricsProtoEnums.MODALITY_FACE && faceManager == null) {
+            return StatsManager.PULL_SKIP;
+        }
+        UserManager userManager = mContext.getSystemService(UserManager.class);
+        if (userManager == null) {
+            return StatsManager.PULL_SKIP;
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            for (UserInfo user : userManager.getUsers()) {
+                final int userId = user.getUserHandle().getIdentifier();
+                int numEnrolled = 0;
+                if (modality == BiometricsProtoEnums.MODALITY_FINGERPRINT) {
+                    numEnrolled = fingerprintManager.getEnrolledFingerprints(userId).size();
+                } else if (modality == BiometricsProtoEnums.MODALITY_FACE) {
+                    numEnrolled = faceManager.getEnrolledFaces(userId).size();
+                } else {
+                    return StatsManager.PULL_SKIP;
+                }
+                StatsEvent e = StatsEvent.newBuilder()
+                        .setAtomId(atomTag)
+                        .writeInt(userId)
+                        .writeInt(numEnrolled)
+                        .build();
+                pulledData.add(e);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private File mBaseDir = new File(SystemServiceManager.ensureSystemDir(), "stats_companion");
+
+    private void registerProcStats() {
+        int tagId = StatsLog.PROC_STATS;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullProcStats(ProcessStats.REPORT_ALL, atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private void registerProcStatsPkgProc() {
+        int tagId = StatsLog.PROC_STATS_PKG_PROC;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullProcStats(ProcessStats.REPORT_PKG_PROC_STATS, atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullProcStats(int section, int atomTag, List<StatsEvent> pulledData) {
+        IProcessStats processStatsService = getIProcessStatsService();
+        if (processStatsService == null) {
+            return StatsManager.PULL_SKIP;
+        }
+
+        synchronized (mProcessStatsLock) {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                long lastHighWaterMark = readProcStatsHighWaterMark(section);
+                List<ParcelFileDescriptor> statsFiles = new ArrayList<>();
+                long highWaterMark = processStatsService.getCommittedStats(
+                        lastHighWaterMark, section, true, statsFiles);
+                if (statsFiles.size() != 1) {
+                    return StatsManager.PULL_SKIP;
+                }
+                unpackStreamedData(atomTag, pulledData, statsFiles);
+                new File(mBaseDir.getAbsolutePath() + "/" + section + "_" + lastHighWaterMark)
+                        .delete();
+                new File(mBaseDir.getAbsolutePath() + "/" + section + "_" + highWaterMark)
+                        .createNewFile();
+            } catch (IOException e) {
+                Slog.e(TAG, "Getting procstats failed: ", e);
+                return StatsManager.PULL_SKIP;
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Getting procstats failed: ", e);
+                return StatsManager.PULL_SKIP;
+            } catch (SecurityException e) {
+                Slog.e(TAG, "Getting procstats failed: ", e);
+                return StatsManager.PULL_SKIP;
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    // read high watermark for section
+    private long readProcStatsHighWaterMark(int section) {
+        try {
+            File[] files = mBaseDir.listFiles((d, name) -> {
+                return name.toLowerCase().startsWith(String.valueOf(section) + '_');
+            });
+            if (files == null || files.length == 0) {
+                return 0;
+            }
+            if (files.length > 1) {
+                Slog.e(TAG, "Only 1 file expected for high water mark. Found " + files.length);
+            }
+            return Long.valueOf(files[0].getName().split("_")[1]);
+        } catch (SecurityException e) {
+            Slog.e(TAG, "Failed to get procstats high watermark file.", e);
+        } catch (NumberFormatException e) {
+            Slog.e(TAG, "Failed to parse file name.", e);
+        }
+        return 0;
+    }
+
+
+    private StoragedUidIoStatsReader mStoragedUidIoStatsReader =
+            new StoragedUidIoStatsReader();
+
+    private void registerDiskIO() {
+        int tagId = StatsLog.DISK_IO;
+        PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+                .setAdditiveFields(new int[] {2, 3, 4, 5, 6, 7, 8, 9, 10, 11})
+                .setCoolDownNs(3 * NS_PER_SEC)
+                .build();
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                metadata,
+                (atomTag, data) -> pullDiskIO(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullDiskIO(int atomTag, List<StatsEvent> pulledData) {
+        mStoragedUidIoStatsReader.readAbsolute((uid, fgCharsRead, fgCharsWrite, fgBytesRead,
+                fgBytesWrite, bgCharsRead, bgCharsWrite, bgBytesRead, bgBytesWrite,
+                fgFsync, bgFsync) -> {
+            StatsEvent e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeInt(uid)
+                    .writeLong(fgCharsRead)
+                    .writeLong(fgCharsWrite)
+                    .writeLong(fgBytesRead)
+                    .writeLong(fgBytesWrite)
+                    .writeLong(bgCharsRead)
+                    .writeLong(bgCharsWrite)
+                    .writeLong(bgBytesRead)
+                    .writeLong(bgBytesWrite)
+                    .writeLong(fgFsync)
+                    .writeLong(bgFsync)
+                    .build();
+            pulledData.add(e);
+        });
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerPowerProfile() {
+        int tagId = StatsLog.POWER_PROFILE;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                /* PullAtomMetadata */ null,
+                BackgroundThread.getExecutor(),
+                (atomTag, data) -> pullPowerProfile(atomTag, data)
+        );
+    }
+
+    private int pullPowerProfile(int atomTag, List<StatsEvent> pulledData) {
+        PowerProfile powerProfile = new PowerProfile(mContext);
+        ProtoOutputStream proto = new ProtoOutputStream();
+        powerProfile.dumpDebug(proto);
+        proto.flush();
+        StatsEvent e = StatsEvent.newBuilder()
+                .setAtomId(atomTag)
+                .writeByteArray(proto.getBytes())
+                .build();
+        pulledData.add(e);
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private final Object mCpuTrackerLock = new Object();
+    @GuardedBy("mCpuTrackerLock")
+    private ProcessCpuTracker mProcessCpuTracker;
+
+    private void registerProcessCpuTime() {
+        int tagId = StatsLog.PROCESS_CPU_TIME;
+        // Min cool-down is 5 sec, in line with what ActivityManagerService uses.
+        PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+                .setCoolDownNs(5 * NS_PER_SEC)
+                .build();
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                metadata,
+                (atomTag, data) -> pullProcessCpuTime(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullProcessCpuTime(int atomTag, List<StatsEvent> pulledData) {
+        synchronized (mCpuTrackerLock) {
+            if (mProcessCpuTracker == null) {
+                mProcessCpuTracker = new ProcessCpuTracker(false);
+                mProcessCpuTracker.init();
+            }
+            mProcessCpuTracker.update();
+            for (int i = 0; i < mProcessCpuTracker.countStats(); i++) {
+                ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i);
+                StatsEvent e = StatsEvent.newBuilder()
+                        .setAtomId(atomTag)
+                        .writeInt(st.uid)
+                        .writeString(st.name)
+                        .writeLong(st.base_utime)
+                        .writeLong(st.base_stime)
+                        .build();
+                pulledData.add(e);
+            }
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    @Nullable
+    private KernelCpuThreadReaderDiff mKernelCpuThreadReader;
+    private static final int CPU_TIME_PER_THREAD_FREQ_MAX_NUM_FREQUENCIES = 8;
+
+    private void registerCpuTimePerThreadFreq() {
+        int tagId = StatsLog.CPU_TIME_PER_THREAD_FREQ;
+        PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+                .setAdditiveFields(new int[] {7, 9, 11, 13, 15, 17, 19, 21})
+                .build();
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                metadata,
+                (atomTag, data) -> pullCpuTimePerThreadFreq(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullCpuTimePerThreadFreq(int atomTag, List<StatsEvent> pulledData) {
+        if (this.mKernelCpuThreadReader == null) {
+            Slog.e(TAG, "mKernelCpuThreadReader is null");
+            return StatsManager.PULL_SKIP;
+        }
+        ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsages =
+                this.mKernelCpuThreadReader.getProcessCpuUsageDiffed();
+        if (processCpuUsages == null) {
+            Slog.e(TAG, "processCpuUsages is null");
+            return StatsManager.PULL_SKIP;
+        }
+        int[] cpuFrequencies = mKernelCpuThreadReader.getCpuFrequenciesKhz();
+        if (cpuFrequencies.length > CPU_TIME_PER_THREAD_FREQ_MAX_NUM_FREQUENCIES) {
+            String message = "Expected maximum " + CPU_TIME_PER_THREAD_FREQ_MAX_NUM_FREQUENCIES
+                    + " frequencies, but got " + cpuFrequencies.length;
+            Slog.w(TAG, message);
+            return StatsManager.PULL_SKIP;
+        }
+        for (int i = 0; i < processCpuUsages.size(); i++) {
+            KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = processCpuUsages.get(i);
+            ArrayList<KernelCpuThreadReader.ThreadCpuUsage> threadCpuUsages =
+                    processCpuUsage.threadCpuUsages;
+            for (int j = 0; j < threadCpuUsages.size(); j++) {
+                KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage = threadCpuUsages.get(j);
+                if (threadCpuUsage.usageTimesMillis.length != cpuFrequencies.length) {
+                    String message = "Unexpected number of usage times,"
+                            + " expected " + cpuFrequencies.length
+                            + " but got " + threadCpuUsage.usageTimesMillis.length;
+                    Slog.w(TAG, message);
+                    return StatsManager.PULL_SKIP;
+                }
+
+                StatsEvent.Builder e = StatsEvent.newBuilder();
+                e.setAtomId(atomTag);
+                e.writeInt(processCpuUsage.uid);
+                e.writeInt(processCpuUsage.processId);
+                e.writeInt(threadCpuUsage.threadId);
+                e.writeString(processCpuUsage.processName);
+                e.writeString(threadCpuUsage.threadName);
+                for (int k = 0; k < CPU_TIME_PER_THREAD_FREQ_MAX_NUM_FREQUENCIES; k++) {
+                    if (k < cpuFrequencies.length) {
+                        e.writeInt(cpuFrequencies[k]);
+                        e.writeInt(threadCpuUsage.usageTimesMillis[k]);
+                    } else {
+                        // If we have no more frequencies to write, we still must write empty data.
+                        // We know that this data is empty (and not just zero) because all
+                        // frequencies are expected to be greater than zero
+                        e.writeInt(0);
+                        e.writeInt(0);
+                    }
+                }
+                pulledData.add(e.build());
+            }
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    // TODO: move to top of file when all migrations are complete
+    private BatteryStatsHelper mBatteryStatsHelper = null;
+    private static final int MAX_BATTERY_STATS_HELPER_FREQUENCY_MS = 1000;
+    private long mBatteryStatsHelperTimestampMs = -MAX_BATTERY_STATS_HELPER_FREQUENCY_MS;
+    private static final long MILLI_AMP_HR_TO_NANO_AMP_SECS = 1_000_000L * 3600L;
+
+    private BatteryStatsHelper getBatteryStatsHelper() {
+        if (mBatteryStatsHelper == null) {
+            final long callingToken = Binder.clearCallingIdentity();
+            try {
+                // clearCallingIdentity required for BatteryStatsHelper.checkWifiOnly().
+                mBatteryStatsHelper = new BatteryStatsHelper(mContext, false);
+            } finally {
+                Binder.restoreCallingIdentity(callingToken);
+            }
+            mBatteryStatsHelper.create((Bundle) null);
+        }
+        long currentTime = SystemClock.elapsedRealtime();
+        if (currentTime - mBatteryStatsHelperTimestampMs >= MAX_BATTERY_STATS_HELPER_FREQUENCY_MS) {
+            // Load BatteryStats and do all the calculations.
+            mBatteryStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.USER_ALL);
+            // Calculations are done so we don't need to save the raw BatteryStats data in RAM.
+            mBatteryStatsHelper.clearStats();
+            mBatteryStatsHelperTimestampMs = currentTime;
+        }
+        return mBatteryStatsHelper;
+    }
+
+    private long milliAmpHrsToNanoAmpSecs(double mAh) {
+        return (long) (mAh * MILLI_AMP_HR_TO_NANO_AMP_SECS + 0.5);
+    }
+
+    private void registerDeviceCalculatedPowerUse() {
+        int tagId = StatsLog.DEVICE_CALCULATED_POWER_USE;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullDeviceCalculatedPowerUse(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullDeviceCalculatedPowerUse(int atomTag, List<StatsEvent> pulledData) {
+        BatteryStatsHelper bsHelper = getBatteryStatsHelper();
+        StatsEvent e = StatsEvent.newBuilder()
+                .setAtomId(atomTag)
+                .writeLong(milliAmpHrsToNanoAmpSecs(bsHelper.getComputedPower()))
+                .build();
+        pulledData.add(e);
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerDeviceCalculatedPowerBlameUid() {
+        int tagId = StatsLog.DEVICE_CALCULATED_POWER_BLAME_UID;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullDeviceCalculatedPowerBlameUid(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullDeviceCalculatedPowerBlameUid(int atomTag, List<StatsEvent> pulledData) {
+        final List<BatterySipper> sippers = getBatteryStatsHelper().getUsageList();
+        if (sippers == null) {
+            return StatsManager.PULL_SKIP;
+        }
+
+        for (BatterySipper bs : sippers) {
+            if (bs.drainType != bs.drainType.APP) {
+                continue;
+            }
+            StatsEvent e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeInt(bs.uidObj.getUid())
+                    .writeLong(milliAmpHrsToNanoAmpSecs(bs.totalPowerMah))
+                    .build();
+            pulledData.add(e);
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerDeviceCalculatedPowerBlameOther() {
+        int tagId = StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullDeviceCalculatedPowerBlameOther(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullDeviceCalculatedPowerBlameOther(int atomTag, List<StatsEvent> pulledData) {
+        final List<BatterySipper> sippers = getBatteryStatsHelper().getUsageList();
+        if (sippers == null) {
+            return StatsManager.PULL_SKIP;
+        }
+
+        for (BatterySipper bs : sippers) {
+            if (bs.drainType == bs.drainType.APP) {
+                continue; // This is a separate atom; see pullDeviceCalculatedPowerBlameUid().
+            }
+            if (bs.drainType == bs.drainType.USER) {
+                continue; // This is not supported. We purposefully calculate over USER_ALL.
+            }
+            StatsEvent e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeInt(bs.drainType.ordinal())
+                    .writeLong(milliAmpHrsToNanoAmpSecs(bs.totalPowerMah))
+                    .build();
+            pulledData.add(e);
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private final Object mDebugElapsedClockLock = new Object();
+    private long mDebugElapsedClockPreviousValue = 0;
+    private long mDebugElapsedClockPullCount = 0;
+
+    private void registerDebugElapsedClock() {
+        int tagId = StatsLog.DEBUG_ELAPSED_CLOCK;
+        PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+                .setAdditiveFields(new int[] {1, 2, 3, 4})
+                .build();
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                metadata,
+                (atomTag, data) -> pullDebugElapsedClock(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullDebugElapsedClock(int atomTag, List<StatsEvent> pulledData) {
+        final long elapsedMillis = SystemClock.elapsedRealtime();
+
+        synchronized (mDebugElapsedClockLock) {
+            final long clockDiffMillis = mDebugElapsedClockPreviousValue == 0
+                    ? 0 : elapsedMillis - mDebugElapsedClockPreviousValue;
+
+            StatsEvent e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeLong(mDebugElapsedClockPullCount)
+                    .writeLong(elapsedMillis)
+                    // Log it twice to be able to test multi-value aggregation from ValueMetric.
+                    .writeLong(elapsedMillis)
+                    .writeLong(clockDiffMillis)
+                    .writeInt(1 /* always set */)
+                    .build();
+            pulledData.add(e);
+
+            if (mDebugElapsedClockPullCount % 2 == 1) {
+                StatsEvent e2 = StatsEvent.newBuilder()
+                        .setAtomId(atomTag)
+                        .writeLong(mDebugElapsedClockPullCount)
+                        .writeLong(elapsedMillis)
+                        // Log it twice to be able to test multi-value aggregation from ValueMetric.
+                        .writeLong(elapsedMillis)
+                        .writeLong(clockDiffMillis)
+                        .writeInt(2 /* set on odd pulls */)
+                        .build();
+                pulledData.add(e2);
+            }
+
+            mDebugElapsedClockPullCount++;
+            mDebugElapsedClockPreviousValue = elapsedMillis;
+        }
+
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private final Object mDebugFailingElapsedClockLock = new Object();
+    private long mDebugFailingElapsedClockPreviousValue = 0;
+    private long mDebugFailingElapsedClockPullCount = 0;
+
+    private void registerDebugFailingElapsedClock() {
+        int tagId = StatsLog.DEBUG_FAILING_ELAPSED_CLOCK;
+        PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+                .setAdditiveFields(new int[] {1, 2, 3, 4})
+                .build();
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                metadata,
+                (atomTag, data) -> pullDebugFailingElapsedClock(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullDebugFailingElapsedClock(int atomTag, List<StatsEvent> pulledData) {
+        final long elapsedMillis = SystemClock.elapsedRealtime();
+
+        synchronized (mDebugFailingElapsedClockLock) {
+            // Fails every 5 buckets.
+            if (mDebugFailingElapsedClockPullCount++ % 5 == 0) {
+                mDebugFailingElapsedClockPreviousValue = elapsedMillis;
+                Slog.e(TAG, "Failing debug elapsed clock");
+                return StatsManager.PULL_SKIP;
+            }
+
+            StatsEvent e = StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeLong(mDebugFailingElapsedClockPullCount)
+                    .writeLong(elapsedMillis)
+                    // Log it twice to be able to test multi-value aggregation from ValueMetric.
+                    .writeLong(elapsedMillis)
+                    .writeLong(mDebugFailingElapsedClockPreviousValue == 0
+                            ? 0 : elapsedMillis - mDebugFailingElapsedClockPreviousValue)
+                    .build();
+            pulledData.add(e);
+
+            mDebugFailingElapsedClockPreviousValue = elapsedMillis;
+        }
+
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerBuildInformation() {
+        int tagId = StatsLog.BUILD_INFORMATION;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                BackgroundThread.getExecutor(),
+                (atomTag, data) -> pullBuildInformation(atomTag, data)
+        );
+    }
+
+    private int pullBuildInformation(int atomTag, List<StatsEvent> pulledData) {
+        StatsEvent e = StatsEvent.newBuilder()
+                .setAtomId(atomTag)
+                .writeString(Build.FINGERPRINT)
+                .writeString(Build.BRAND)
+                .writeString(Build.PRODUCT)
+                .writeString(Build.DEVICE)
+                .writeString(Build.VERSION.RELEASE)
+                .writeString(Build.ID)
+                .writeString(Build.VERSION.INCREMENTAL)
+                .writeString(Build.TYPE)
+                .writeString(Build.TAGS)
+                .build();
+        pulledData.add(e);
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerRoleHolder() {
+        int tagId = StatsLog.ROLE_HOLDER;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullRoleHolder(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    // Add a RoleHolder atom for each package that holds a role.
+    private int pullRoleHolder(int atomTag, List<StatsEvent> pulledData) {
+        long callingToken = Binder.clearCallingIdentity();
+        try {
+            PackageManager pm = mContext.getPackageManager();
+            RoleManagerInternal rmi = LocalServices.getService(RoleManagerInternal.class);
+
+            List<UserInfo> users = mContext.getSystemService(UserManager.class).getUsers();
+
+            int numUsers = users.size();
+            for (int userNum = 0; userNum < numUsers; userNum++) {
+                int userId = users.get(userNum).getUserHandle().getIdentifier();
+
+                ArrayMap<String, ArraySet<String>> roles = rmi.getRolesAndHolders(userId);
+
+                int numRoles = roles.size();
+                for (int roleNum = 0; roleNum < numRoles; roleNum++) {
+                    String roleName = roles.keyAt(roleNum);
+                    ArraySet<String> holders = roles.valueAt(roleNum);
+
+                    int numHolders = holders.size();
+                    for (int holderNum = 0; holderNum < numHolders; holderNum++) {
+                        String holderName = holders.valueAt(holderNum);
+
+                        PackageInfo pkg;
+                        try {
+                            pkg = pm.getPackageInfoAsUser(holderName, 0, userId);
+                        } catch (PackageManager.NameNotFoundException e) {
+                            Slog.w(TAG, "Role holder " + holderName + " not found");
+                            return StatsManager.PULL_SKIP;
+                        }
+
+                        StatsEvent e = StatsEvent.newBuilder()
+                                .setAtomId(atomTag)
+                                .writeInt(pkg.applicationInfo.uid)
+                                .writeString(holderName)
+                                .writeString(roleName)
+                                .build();
+                        pulledData.add(e);
+                    }
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingToken);
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerDangerousPermissionState() {
+        int tagId = StatsLog.DANGEROUS_PERMISSION_STATE;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullDangerousPermissionState(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullDangerousPermissionState(int atomTag, List<StatsEvent> pulledData) {
+        final long token = Binder.clearCallingIdentity();
+        Set<Integer> reportedUids = new HashSet<>();
+        try {
+            PackageManager pm = mContext.getPackageManager();
+
+            List<UserInfo> users = mContext.getSystemService(UserManager.class).getUsers();
+
+            int numUsers = users.size();
+            for (int userNum = 0; userNum < numUsers; userNum++) {
+                UserHandle user = users.get(userNum).getUserHandle();
+
+                List<PackageInfo> pkgs = pm.getInstalledPackagesAsUser(
+                        PackageManager.GET_PERMISSIONS, user.getIdentifier());
+
+                int numPkgs = pkgs.size();
+                for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
+                    PackageInfo pkg = pkgs.get(pkgNum);
+
+                    if (pkg.requestedPermissions == null) {
+                        continue;
+                    }
+
+                    if (reportedUids.contains(pkg.applicationInfo.uid)) {
+                        // do not report same uid twice
+                        continue;
+                    }
+                    reportedUids.add(pkg.applicationInfo.uid);
+
+                    if (atomTag == StatsLog.DANGEROUS_PERMISSION_STATE_SAMPLED
+                            && ThreadLocalRandom.current().nextFloat() > 0.2f) {
+                        continue;
+                    }
+
+                    int numPerms = pkg.requestedPermissions.length;
+                    for (int permNum  = 0; permNum < numPerms; permNum++) {
+                        String permName = pkg.requestedPermissions[permNum];
+
+                        PermissionInfo permissionInfo;
+                        int permissionFlags = 0;
+                        try {
+                            permissionInfo = pm.getPermissionInfo(permName, 0);
+                            permissionFlags =
+                                    pm.getPermissionFlags(permName, pkg.packageName, user);
+                        } catch (PackageManager.NameNotFoundException ignored) {
+                            continue;
+                        }
+
+                        if (permissionInfo.getProtection() != PROTECTION_DANGEROUS) {
+                            continue;
+                        }
+
+                        StatsEvent.Builder e = StatsEvent.newBuilder();
+                        e.setAtomId(atomTag);
+                        e.writeString(permName);
+                        e.writeInt(pkg.applicationInfo.uid);
+                        if (atomTag == StatsLog.DANGEROUS_PERMISSION_STATE) {
+                            e.writeString("");
+                        }
+                        e.writeBoolean((pkg.requestedPermissionsFlags[permNum]
+                                & REQUESTED_PERMISSION_GRANTED) != 0);
+                        e.writeInt(permissionFlags);
+
+                        pulledData.add(e.build());
+                    }
+                }
+            }
+        } catch (Throwable t) {
+            Log.e(TAG, "Could not read permissions", t);
+            return StatsManager.PULL_SKIP;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerTimeZoneDataInfo() {
+        int tagId = StatsLog.TIME_ZONE_DATA_INFO;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullTimeZoneDataInfo(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullTimeZoneDataInfo(int atomTag, List<StatsEvent> pulledData) {
+        String tzDbVersion = "Unknown";
+        try {
+            tzDbVersion = android.icu.util.TimeZone.getTZDataVersion();
+        } catch (MissingResourceException e) {
+            Slog.e(TAG, "Getting tzdb version failed: ", e);
+            return StatsManager.PULL_SKIP;
+        }
+
+        StatsEvent e = StatsEvent.newBuilder()
+                .setAtomId(atomTag)
+                .writeString(tzDbVersion)
+                .build();
+        pulledData.add(e);
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerExternalStorageInfo() {
+        int tagId = StatsLog.EXTERNAL_STORAGE_INFO;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullExternalStorageInfo(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullExternalStorageInfo(int atomTag, List<StatsEvent> pulledData) {
+        if (mStorageManager == null) {
+            return StatsManager.PULL_SKIP;
+        }
+
+        List<VolumeInfo> volumes = mStorageManager.getVolumes();
+        for (VolumeInfo vol : volumes) {
+            final String envState = VolumeInfo.getEnvironmentForState(vol.getState());
+            final DiskInfo diskInfo = vol.getDisk();
+            if (diskInfo != null && envState.equals(Environment.MEDIA_MOUNTED)) {
+                // Get the type of the volume, if it is adoptable or portable.
+                int volumeType = StatsLog.EXTERNAL_STORAGE_INFO__VOLUME_TYPE__OTHER;
+                if (vol.getType() == TYPE_PUBLIC) {
+                    volumeType = StatsLog.EXTERNAL_STORAGE_INFO__VOLUME_TYPE__PUBLIC;
+                } else if (vol.getType() == TYPE_PRIVATE) {
+                    volumeType = StatsLog.EXTERNAL_STORAGE_INFO__VOLUME_TYPE__PRIVATE;
+                }
+
+                // Get the type of external storage inserted in the device (sd cards, usb, etc.)
+                int externalStorageType;
+                if (diskInfo.isSd()) {
+                    externalStorageType = StorageEnums.SD_CARD;
+                } else if (diskInfo.isUsb()) {
+                    externalStorageType = StorageEnums.USB;
+                } else {
+                    externalStorageType = StorageEnums.OTHER;
+                }
+
+                StatsEvent e = StatsEvent.newBuilder()
+                        .setAtomId(atomTag)
+                        .writeInt(externalStorageType)
+                        .writeInt(volumeType)
+                        .writeLong(diskInfo.size)
+                        .build();
+                pulledData.add(e);
+            }
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerAppsOnExternalStorageInfo() {
+        int tagId = StatsLog.APPS_ON_EXTERNAL_STORAGE_INFO;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullAppsOnExternalStorageInfo(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullAppsOnExternalStorageInfo(int atomTag, List<StatsEvent> pulledData) {
+        if (mStorageManager == null) {
+            return StatsManager.PULL_SKIP;
+        }
+
+        PackageManager pm = mContext.getPackageManager();
+        List<ApplicationInfo> apps = pm.getInstalledApplications(/*flags=*/ 0);
+        for (ApplicationInfo appInfo : apps) {
+            UUID storageUuid = appInfo.storageUuid;
+            if (storageUuid == null) {
+                continue;
+            }
+
+            VolumeInfo volumeInfo = mStorageManager.findVolumeByUuid(
+                    appInfo.storageUuid.toString());
+            if (volumeInfo == null) {
+                continue;
+            }
+
+            DiskInfo diskInfo = volumeInfo.getDisk();
+            if (diskInfo == null) {
+                continue;
+            }
+
+            int externalStorageType = -1;
+            if (diskInfo.isSd()) {
+                externalStorageType = StorageEnums.SD_CARD;
+            } else if (diskInfo.isUsb()) {
+                externalStorageType = StorageEnums.USB;
+            } else if (appInfo.isExternal()) {
+                externalStorageType = StorageEnums.OTHER;
+            }
+
+            // App is installed on external storage.
+            if (externalStorageType != -1) {
+                StatsEvent e = StatsEvent.newBuilder()
+                        .setAtomId(atomTag)
+                        .writeInt(externalStorageType)
+                        .writeString(appInfo.packageName)
+                        .build();
+                pulledData.add(e);
+            }
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerFaceSettings() {
+        int tagId = StatsLog.FACE_SETTINGS;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullFaceSettings(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullFaceSettings(int atomTag, List<StatsEvent> pulledData) {
+        final long callingToken = Binder.clearCallingIdentity();
+        try {
+            List<UserInfo> users = mContext.getSystemService(UserManager.class).getUsers();
+            int numUsers = users.size();
+            for (int userNum = 0; userNum < numUsers; userNum++) {
+                int userId = users.get(userNum).getUserHandle().getIdentifier();
+
+                int unlockKeyguardEnabled = Settings.Secure.getIntForUser(
+                          mContext.getContentResolver(),
+                          Settings.Secure.FACE_UNLOCK_KEYGUARD_ENABLED, 1, userId);
+                int unlockDismissesKeyguard = Settings.Secure.getIntForUser(
+                          mContext.getContentResolver(),
+                          Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD, 0, userId);
+                int unlockAttentionRequired = Settings.Secure.getIntForUser(
+                          mContext.getContentResolver(),
+                          Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED, 1, userId);
+                int unlockAppEnabled = Settings.Secure.getIntForUser(
+                          mContext.getContentResolver(),
+                          Settings.Secure.FACE_UNLOCK_APP_ENABLED, 1, userId);
+                int unlockAlwaysRequireConfirmation = Settings.Secure.getIntForUser(
+                          mContext.getContentResolver(),
+                          Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, 0, userId);
+                int unlockDiversityRequired = Settings.Secure.getIntForUser(
+                          mContext.getContentResolver(),
+                          Settings.Secure.FACE_UNLOCK_DIVERSITY_REQUIRED, 1, userId);
+
+                StatsEvent e = StatsEvent.newBuilder()
+                        .setAtomId(atomTag)
+                        .writeBoolean(unlockKeyguardEnabled != 0)
+                        .writeBoolean(unlockDismissesKeyguard != 0)
+                        .writeBoolean(unlockAttentionRequired != 0)
+                        .writeBoolean(unlockAppEnabled != 0)
+                        .writeBoolean(unlockAlwaysRequireConfirmation != 0)
+                        .writeBoolean(unlockDiversityRequired != 0)
+                        .build();
+                pulledData.add(e);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingToken);
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerAppOps() {
+        int tagId = StatsLog.APP_OPS;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullAppOps(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+
+    }
+
+    private int pullAppOps(int atomTag, List<StatsEvent> pulledData) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
+
+            CompletableFuture<HistoricalOps> ops = new CompletableFuture<>();
+            HistoricalOpsRequest histOpsRequest =
+                    new HistoricalOpsRequest.Builder(0, Long.MAX_VALUE).build();
+            appOps.getHistoricalOps(histOpsRequest, mContext.getMainExecutor(), ops::complete);
+
+            HistoricalOps histOps = ops.get(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS,
+                    TimeUnit.MILLISECONDS);
+
+            for (int uidIdx = 0; uidIdx < histOps.getUidCount(); uidIdx++) {
+                final HistoricalUidOps uidOps = histOps.getUidOpsAt(uidIdx);
+                final int uid = uidOps.getUid();
+                for (int pkgIdx = 0; pkgIdx < uidOps.getPackageCount(); pkgIdx++) {
+                    final HistoricalPackageOps packageOps = uidOps.getPackageOpsAt(pkgIdx);
+                    for (int opIdx = 0; opIdx < packageOps.getOpCount(); opIdx++) {
+                        final AppOpsManager.HistoricalOp op  = packageOps.getOpAt(opIdx);
+
+                        StatsEvent.Builder e = StatsEvent.newBuilder();
+                        e.setAtomId(atomTag);
+                        e.writeInt(uid);
+                        e.writeString(packageOps.getPackageName());
+                        e.writeInt(op.getOpCode());
+                        e.writeLong(op.getForegroundAccessCount(OP_FLAGS_ALL_TRUSTED));
+                        e.writeLong(op.getBackgroundAccessCount(OP_FLAGS_ALL_TRUSTED));
+                        e.writeLong(op.getForegroundRejectCount(OP_FLAGS_ALL_TRUSTED));
+                        e.writeLong(op.getBackgroundRejectCount(OP_FLAGS_ALL_TRUSTED));
+                        e.writeLong(op.getForegroundAccessDuration(OP_FLAGS_ALL_TRUSTED));
+                        e.writeLong(op.getBackgroundAccessDuration(OP_FLAGS_ALL_TRUSTED));
+
+                        String perm = AppOpsManager.opToPermission(op.getOpCode());
+                        if (perm == null) {
+                            e.writeBoolean(false);
+                        } else {
+                            PermissionInfo permInfo;
+                            try {
+                                permInfo = mContext.getPackageManager().getPermissionInfo(perm, 0);
+                                e.writeBoolean(permInfo.getProtection() == PROTECTION_DANGEROUS);
+                            } catch (PackageManager.NameNotFoundException exception) {
+                                e.writeBoolean(false);
+                            }
+                        }
+
+                        pulledData.add(e.build());
+                    }
+                }
+            }
+        } catch (Throwable t) {
+            // TODO: catch exceptions at a more granular level
+            Slog.e(TAG, "Could not read appops", t);
+            return StatsManager.PULL_SKIP;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    static void unpackStreamedData(int atomTag, List<StatsEvent> pulledData,
+            List<ParcelFileDescriptor> statsFiles) throws IOException {
+        InputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(statsFiles.get(0));
+        int[] len = new int[1];
+        byte[] stats = readFully(stream, len);
+        StatsEvent e = StatsEvent.newBuilder()
+                .setAtomId(atomTag)
+                .writeByteArray(Arrays.copyOf(stats, len[0]))
+                .build();
+        pulledData.add(e);
+    }
+
+    static byte[] readFully(InputStream stream, int[] outLen) throws IOException {
+        int pos = 0;
+        final int initialAvail = stream.available();
+        byte[] data = new byte[initialAvail > 0 ? (initialAvail + 1) : 16384];
+        while (true) {
+            int amt = stream.read(data, pos, data.length - pos);
+            if (DEBUG) {
+                Slog.i(TAG, "Read " + amt + " bytes at " + pos + " of avail " + data.length);
+            }
+            if (amt < 0) {
+                if (DEBUG) {
+                    Slog.i(TAG, "**** FINISHED READING: pos=" + pos + " len=" + data.length);
+                }
+                outLen[0] = pos;
+                return data;
+            }
+            pos += amt;
+            if (pos >= data.length) {
+                byte[] newData = new byte[pos + 16384];
+                if (DEBUG) {
+                    Slog.i(TAG, "Copying " + pos + " bytes to new array len " + newData.length);
+                }
+                System.arraycopy(data, 0, newData, 0, pos);
+                data = newData;
+            }
+        }
+    }
+
+    private void registerNotificationRemoteViews() {
+        int tagId = StatsLog.NOTIFICATION_REMOTE_VIEWS;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullNotificationRemoteViews(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+
+    private int pullNotificationRemoteViews(int atomTag, List<StatsEvent> pulledData) {
+        INotificationManager notificationManagerService = getINotificationManagerService();
+        if (notificationManagerService == null) {
+            return StatsManager.PULL_SKIP;
+        }
+        final long callingToken = Binder.clearCallingIdentity();
+        try {
+            // determine last pull tine. Copy file trick from pullProcStats?
+            long wallClockNanos = SystemClock.currentTimeMicro() * 1000L;
+            long lastNotificationStatsNs = wallClockNanos -
+                    TimeUnit.NANOSECONDS.convert(1, TimeUnit.DAYS);
+
+            List<ParcelFileDescriptor> statsFiles = new ArrayList<>();
+            notificationManagerService.pullStats(lastNotificationStatsNs,
+                    NotificationManagerService.REPORT_REMOTE_VIEWS, true, statsFiles);
+            if (statsFiles.size() != 1) {
+                return StatsManager.PULL_SKIP;
+            }
+            unpackStreamedData(atomTag, pulledData, statsFiles);
+        } catch (IOException e) {
+            Slog.e(TAG, "Getting notistats failed: ", e);
+            return StatsManager.PULL_SKIP;
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Getting notistats failed: ", e);
+            return StatsManager.PULL_SKIP;
+        } catch (SecurityException e) {
+            Slog.e(TAG, "Getting notistats failed: ", e);
+            return StatsManager.PULL_SKIP;
+        } finally {
+            Binder.restoreCallingIdentity(callingToken);
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void registerDangerousPermissionStateSampled() {
+        int tagId = StatsLog.DANGEROUS_PERMISSION_STATE_SAMPLED;
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                (atomTag, data) -> pullDangerousPermissionState(atomTag, data),
+                BackgroundThread.getExecutor()
+        );
+    }
+}
diff --git a/services/core/java/com/android/server/storage/StorageSessionController.java b/services/core/java/com/android/server/storage/StorageSessionController.java
index f4fb93a..baef5d6 100644
--- a/services/core/java/com/android/server/storage/StorageSessionController.java
+++ b/services/core/java/com/android/server/storage/StorageSessionController.java
@@ -40,9 +40,7 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
 
-import java.io.FileDescriptor;
 import java.util.Objects;
 
 /**
@@ -82,7 +80,7 @@
      * @throws ExternalStorageServiceException if the session fails to start
      * @throws IllegalStateException if a session has already been created for {@code vol}
      */
-    public void onVolumeMount(FileDescriptor deviceFd, VolumeInfo vol)
+    public void onVolumeMount(ParcelFileDescriptor deviceFd, VolumeInfo vol)
             throws ExternalStorageServiceException {
         if (!shouldHandle(vol)) {
             return;
@@ -102,8 +100,8 @@
                 mConnections.put(userId, connection);
             }
             Slog.i(TAG, "Creating and starting session with id: " + sessionId);
-            connection.startSession(sessionId, new ParcelFileDescriptor(deviceFd),
-                    vol.getPath().getPath(), vol.getInternalPath().getPath());
+            connection.startSession(sessionId, deviceFd, vol.getPath().getPath(),
+                    vol.getInternalPath().getPath());
         }
     }
 
@@ -185,7 +183,7 @@
      * This call removes all sessions for the user that is being stopped;
      * this will make sure that we don't rebind to the service needlessly.
      */
-    public void onUserStopping(int userId) throws ExternalStorageServiceException {
+    public void onUserStopping(int userId) {
         if (!shouldHandle(null)) {
             return;
         }
diff --git a/services/core/java/com/android/server/storage/StorageUserConnection.java b/services/core/java/com/android/server/storage/StorageUserConnection.java
index c02ded8..dd18f4e 100644
--- a/services/core/java/com/android/server/storage/StorageUserConnection.java
+++ b/services/core/java/com/android/server/storage/StorageUserConnection.java
@@ -60,7 +60,8 @@
  */
 public final class StorageUserConnection {
     private static final String TAG = "StorageUserConnection";
-    private static final int REMOTE_TIMEOUT_SECONDS = 15;
+
+    public static final int REMOTE_TIMEOUT_SECONDS = 5;
 
     private final Object mLock = new Object();
     private final Context mContext;
diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
index 5283cc4..3dee853 100644
--- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
+++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
@@ -25,6 +25,7 @@
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Process;
 import android.os.RemoteException;
@@ -53,6 +54,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FunctionalUtils;
+import com.android.internal.util.FunctionalUtils.ThrowingConsumer;
 import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
@@ -74,6 +76,19 @@
 
     private static final String LOG_TAG = "TextClassificationManagerService";
 
+    private static final ITextClassifierCallback NO_OP_CALLBACK = new ITextClassifierCallback() {
+        @Override
+        public void onSuccess(Bundle result) {}
+
+        @Override
+        public void onFailure() {}
+
+        @Override
+        public IBinder asBinder() {
+            return null;
+        }
+    };
+
     public static final class Lifecycle extends SystemService {
 
         private final TextClassificationManagerService mManagerService;
@@ -158,28 +173,14 @@
             TextSelection.Request request, ITextClassifierCallback callback)
             throws RemoteException {
         Objects.requireNonNull(request);
-        Objects.requireNonNull(callback);
-        final int userId = request.getUserId();
-        validateInput(mContext, request.getCallingPackageName(), userId);
 
-        synchronized (mLock) {
-            UserState userState = getUserStateLocked(userId);
-            if (!userState.bindLocked()) {
-                Slog.d(LOG_TAG, "Unable to bind TextClassifierService at suggestSelection.");
-                callback.onFailure();
-            } else if (userState.isBoundLocked()) {
-                if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(),
-                        "suggestSelection")) {
-                    return;
-                }
-                userState.mService.onSuggestSelection(sessionId, request, callback);
-            } else {
-                userState.mPendingRequests.add(new PendingRequest("suggestSelection",
-                        () -> userState.mService.onSuggestSelection(sessionId, request, callback),
-                        callback::onFailure, callback.asBinder(), this, userState,
-                        Binder.getCallingUid()));
-            }
-        }
+        handleRequest(
+                request.getUserId(),
+                request.getCallingPackageName(),
+                /* attemptToBind= */ true,
+                service -> service.onSuggestSelection(sessionId, request, callback),
+                "onSuggestSelection",
+                callback);
     }
 
     @Override
@@ -188,27 +189,14 @@
             TextClassification.Request request, ITextClassifierCallback callback)
             throws RemoteException {
         Objects.requireNonNull(request);
-        Objects.requireNonNull(callback);
-        final int userId = request.getUserId();
-        validateInput(mContext, request.getCallingPackageName(), userId);
 
-        synchronized (mLock) {
-            UserState userState = getUserStateLocked(userId);
-            if (!userState.bindLocked()) {
-                Slog.d(LOG_TAG, "Unable to bind TextClassifierService at classifyText.");
-                callback.onFailure();
-            } else if (userState.isBoundLocked()) {
-                if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(), "classifyText")) {
-                    return;
-                }
-                userState.mService.onClassifyText(sessionId, request, callback);
-            } else {
-                userState.mPendingRequests.add(new PendingRequest("classifyText",
-                        () -> userState.mService.onClassifyText(sessionId, request, callback),
-                        callback::onFailure, callback.asBinder(), this, userState,
-                        Binder.getCallingUid()));
-            }
-        }
+        handleRequest(
+                request.getUserId(),
+                request.getCallingPackageName(),
+                /* attemptToBind= */ true,
+                service -> service.onClassifyText(sessionId, request, callback),
+                "onClassifyText",
+                callback);
     }
 
     @Override
@@ -217,28 +205,14 @@
             TextLinks.Request request, ITextClassifierCallback callback)
             throws RemoteException {
         Objects.requireNonNull(request);
-        Objects.requireNonNull(callback);
-        final int userId = request.getUserId();
-        validateInput(mContext, request.getCallingPackageName(), userId);
 
-        synchronized (mLock) {
-            UserState userState = getUserStateLocked(userId);
-            if (!userState.bindLocked()) {
-                Slog.d(LOG_TAG, "Unable to bind TextClassifierService at generateLinks.");
-                callback.onFailure();
-            } else if (userState.isBoundLocked()) {
-                if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(),
-                        "generateLinks")) {
-                    return;
-                }
-                userState.mService.onGenerateLinks(sessionId, request, callback);
-            } else {
-                userState.mPendingRequests.add(new PendingRequest("generateLinks",
-                        () -> userState.mService.onGenerateLinks(sessionId, request, callback),
-                        callback::onFailure, callback.asBinder(), this, userState,
-                        Binder.getCallingUid()));
-            }
-        }
+        handleRequest(
+                request.getUserId(),
+                request.getCallingPackageName(),
+                /* attemptToBind= */ true,
+                service -> service.onGenerateLinks(sessionId, request, callback),
+                "onGenerateLinks",
+                callback);
     }
 
     @Override
@@ -246,53 +220,34 @@
             @Nullable TextClassificationSessionId sessionId, SelectionEvent event)
             throws RemoteException {
         Objects.requireNonNull(event);
-        final int userId = event.getUserId();
-        validateInput(mContext, event.getPackageName(), userId);
 
-        synchronized (mLock) {
-            UserState userState = getUserStateLocked(userId);
-            if (userState.isBoundLocked()) {
-                if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(),
-                        "selectionEvent")) {
-                    return;
-                }
-                userState.mService.onSelectionEvent(sessionId, event);
-            } else {
-                userState.mPendingRequests.add(new PendingRequest("selectionEvent",
-                        () -> userState.mService.onSelectionEvent(sessionId, event),
-                        null /* onServiceFailure */, null /* binder */, this, userState,
-                        Binder.getCallingUid()));
-            }
-        }
+        handleRequest(
+                event.getUserId(),
+                event.getPackageName(),
+                /* attemptToBind= */ false,
+                service -> service.onSelectionEvent(sessionId, event),
+                "onSelectionEvent",
+                NO_OP_CALLBACK);
     }
     @Override
     public void onTextClassifierEvent(
             @Nullable TextClassificationSessionId sessionId,
             TextClassifierEvent event) throws RemoteException {
         Objects.requireNonNull(event);
+
         final String packageName = event.getEventContext() == null
                 ? null
                 : event.getEventContext().getPackageName();
         final int userId = event.getEventContext() == null
                 ? UserHandle.getCallingUserId()
                 : event.getEventContext().getUserId();
-        validateInput(mContext, packageName, userId);
-
-        synchronized (mLock) {
-            UserState userState = getUserStateLocked(userId);
-            if (userState.isBoundLocked()) {
-                if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(),
-                        "textClassifierEvent")) {
-                    return;
-                }
-                userState.mService.onTextClassifierEvent(sessionId, event);
-            } else {
-                userState.mPendingRequests.add(new PendingRequest("textClassifierEvent",
-                        () -> userState.mService.onTextClassifierEvent(sessionId, event),
-                        null /* onServiceFailure */, null /* binder */, this, userState,
-                        Binder.getCallingUid()));
-            }
-        }
+        handleRequest(
+                userId,
+                packageName,
+                /* attemptToBind= */ false,
+                service -> service.onTextClassifierEvent(sessionId, event),
+                "onTextClassifierEvent",
+                NO_OP_CALLBACK);
     }
 
     @Override
@@ -301,28 +256,14 @@
             TextLanguage.Request request,
             ITextClassifierCallback callback) throws RemoteException {
         Objects.requireNonNull(request);
-        Objects.requireNonNull(callback);
-        final int userId = request.getUserId();
-        validateInput(mContext, request.getCallingPackageName(), userId);
 
-        synchronized (mLock) {
-            UserState userState = getUserStateLocked(userId);
-            if (!userState.bindLocked()) {
-                Slog.d(LOG_TAG, "Unable to bind TextClassifierService at detectLanguage.");
-                callback.onFailure();
-            } else if (userState.isBoundLocked()) {
-                if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(),
-                        "detectLanguage")) {
-                    return;
-                }
-                userState.mService.onDetectLanguage(sessionId, request, callback);
-            } else {
-                userState.mPendingRequests.add(new PendingRequest("detectLanguage",
-                        () -> userState.mService.onDetectLanguage(sessionId, request, callback),
-                        callback::onFailure, callback.asBinder(), this, userState,
-                        Binder.getCallingUid()));
-            }
-        }
+        handleRequest(
+                request.getUserId(),
+                request.getCallingPackageName(),
+                /* attemptToBind= */ true,
+                service -> service.onDetectLanguage(sessionId, request, callback),
+                "onDetectLanguage",
+                callback);
     }
 
     @Override
@@ -331,30 +272,14 @@
             ConversationActions.Request request,
             ITextClassifierCallback callback) throws RemoteException {
         Objects.requireNonNull(request);
-        Objects.requireNonNull(callback);
-        final int userId = request.getUserId();
-        validateInput(mContext, request.getCallingPackageName(), userId);
 
-        synchronized (mLock) {
-            UserState userState = getUserStateLocked(userId);
-            if (!userState.bindLocked()) {
-                Slog.d(LOG_TAG,
-                        "Unable to bind TextClassifierService at suggestConversationActions.");
-                callback.onFailure();
-            } else if (userState.isBoundLocked()) {
-                if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(),
-                        "suggestConversationActions")) {
-                    return;
-                }
-                userState.mService.onSuggestConversationActions(sessionId, request, callback);
-            } else {
-                userState.mPendingRequests.add(new PendingRequest("suggestConversationActions",
-                        () -> userState.mService.onSuggestConversationActions(sessionId, request,
-                                callback),
-                        callback::onFailure, callback.asBinder(), this, userState,
-                        Binder.getCallingUid()));
-            }
-        }
+        handleRequest(
+                request.getUserId(),
+                request.getCallingPackageName(),
+                /* attemptToBind= */ true,
+                service -> service.onSuggestConversationActions(sessionId, request, callback),
+                "onSuggestConversationActions",
+                callback);
     }
 
     @Override
@@ -363,30 +288,18 @@
             throws RemoteException {
         Objects.requireNonNull(sessionId);
         Objects.requireNonNull(classificationContext);
-        final int userId = classificationContext.getUserId();
-        validateInput(mContext, classificationContext.getPackageName(), userId);
 
-        synchronized (mLock) {
-            UserState userState = getUserStateLocked(userId);
-            if (userState.isBoundLocked()) {
-                if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(),
-                        "createTextClassificationSession")) {
-                    return;
-                }
-                userState.mService.onCreateTextClassificationSession(
-                        classificationContext, sessionId);
-                mSessionUserIds.put(sessionId, userId);
-            } else {
-                userState.mPendingRequests.add(new PendingRequest("createTextClassificationSession",
-                        () -> {
-                            userState.mService.onCreateTextClassificationSession(
-                                    classificationContext, sessionId);
-                            mSessionUserIds.put(sessionId, userId);
-                        },
-                        null /* onServiceFailure */, null /* binder */, this, userState,
-                        Binder.getCallingUid()));
-            }
-        }
+        final int userId = classificationContext.getUserId();
+        handleRequest(
+                userId,
+                classificationContext.getPackageName(),
+                /* attemptToBind= */ false,
+                service -> {
+                    service.onCreateTextClassificationSession(classificationContext, sessionId);
+                    mSessionUserIds.put(sessionId, userId);
+                },
+                "onCreateTextClassificationSession",
+                NO_OP_CALLBACK);
     }
 
     @Override
@@ -398,27 +311,16 @@
             final int userId = mSessionUserIds.containsKey(sessionId)
                     ? mSessionUserIds.get(sessionId)
                     : UserHandle.getCallingUserId();
-            validateInput(mContext, null /* packageName */, userId);
-
-            UserState userState = getUserStateLocked(userId);
-            if (userState.isBoundLocked()) {
-                if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(),
-                        "destroyTextClassificationSession")) {
-                    return;
-                }
-                userState.mService.onDestroyTextClassificationSession(sessionId);
-                mSessionUserIds.remove(sessionId);
-            } else {
-                userState.mPendingRequests.add(
-                        new PendingRequest("destroyTextClassificationSession",
-                                () -> {
-                                    userState.mService.onDestroyTextClassificationSession(
-                                            sessionId);
-                                    mSessionUserIds.remove(sessionId);
-                                },
-                                null /* onServiceFailure */, null /* binder */, this, userState,
-                                Binder.getCallingUid()));
-            }
+            handleRequest(
+                    userId,
+                    /* callingPackageName= */ null,
+                    /* attemptToBind= */ false,
+                    service -> {
+                        service.onDestroyTextClassificationSession(sessionId);
+                        mSessionUserIds.remove(sessionId);
+                    },
+                    "onDestroyTextClassificationSession",
+                    NO_OP_CALLBACK);
         }
     }
 
@@ -466,6 +368,42 @@
         }
     }
 
+    private void handleRequest(
+            @UserIdInt int userId,
+            @Nullable String callingPackageName,
+            boolean attemptToBind,
+            @NonNull ThrowingConsumer<ITextClassifierService> textClassifierServiceConsumer,
+            @NonNull String methodName,
+            @NonNull ITextClassifierCallback callback)
+            throws RemoteException {
+        Objects.requireNonNull(textClassifierServiceConsumer);
+        Objects.requireNonNull(methodName);
+        Objects.requireNonNull(callback);
+
+        validateInput(mContext, callingPackageName, userId);
+        synchronized (mLock) {
+            UserState userState = getUserStateLocked(userId);
+            if (attemptToBind && !userState.bindLocked()) {
+                Slog.d(LOG_TAG, "Unable to bind TextClassifierService at " + methodName);
+                callback.onFailure();
+            } else if (userState.isBoundLocked()) {
+                if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(), methodName)) {
+                    return;
+                }
+                textClassifierServiceConsumer.accept(userState.mService);
+            } else {
+                userState.mPendingRequests.add(
+                        new PendingRequest(
+                                methodName,
+                                () -> textClassifierServiceConsumer.accept(userState.mService),
+                                callback::onFailure, callback.asBinder(),
+                                this,
+                                userState,
+                                Binder.getCallingUid()));
+            }
+        }
+    }
+
     private void unbindServiceIfNecessary() {
         final ComponentName serviceComponentName =
                 TextClassifierService.getServiceComponentName(mContext);
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index f661b5e..468b806 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -21,7 +21,6 @@
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
 import android.app.timedetector.PhoneTimeSuggestion;
-import android.content.Intent;
 import android.os.TimestampedValue;
 
 import java.io.PrintWriter;
@@ -62,10 +61,10 @@
         /** Acquire a suitable wake lock. Must be followed by {@link #releaseWakeLock()} */
         void acquireWakeLock();
 
-        /** Returns the elapsedRealtimeMillis clock value. The WakeLock must be held. */
+        /** Returns the elapsedRealtimeMillis clock value. */
         long elapsedRealtimeMillis();
 
-        /** Returns the system clock value. The WakeLock must be held. */
+        /** Returns the system clock value. */
         long systemClockMillis();
 
         /** Sets the device system clock. The WakeLock must be held. */
@@ -73,9 +72,6 @@
 
         /** Release the wake lock acquired by a call to {@link #acquireWakeLock()}. */
         void releaseWakeLock();
-
-        /** Send the supplied intent as a stick broadcast. */
-        void sendStickyBroadcast(@NonNull Intent intent);
     }
 
     /** Initialize the strategy. */
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
index 42d59d5..19484db 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
@@ -20,11 +20,9 @@
 import android.app.AlarmManager;
 import android.content.ContentResolver;
 import android.content.Context;
-import android.content.Intent;
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
-import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Slog;
 
@@ -90,13 +88,11 @@
 
     @Override
     public long elapsedRealtimeMillis() {
-        checkWakeLockHeld();
         return SystemClock.elapsedRealtime();
     }
 
     @Override
     public long systemClockMillis() {
-        checkWakeLockHeld();
         return System.currentTimeMillis();
     }
 
@@ -112,11 +108,6 @@
         mWakeLock.release();
     }
 
-    @Override
-    public void sendStickyBroadcast(@NonNull Intent intent) {
-        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
-    }
-
     private void checkWakeLockHeld() {
         if (!mWakeLock.isHeld()) {
             Slog.wtf(TAG, "WakeLock " + mWakeLock + " not held");
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index da848d8..e95fc4a 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -23,9 +23,7 @@
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
 import android.app.timedetector.PhoneTimeSuggestion;
-import android.content.Intent;
 import android.os.TimestampedValue;
-import android.telephony.TelephonyManager;
 import android.util.LocalLog;
 import android.util.Slog;
 
@@ -535,17 +533,6 @@
         } else {
             mLastAutoSystemClockTimeSet = null;
         }
-
-        // Historically, Android has sent a TelephonyManager.ACTION_NETWORK_SET_TIME broadcast only
-        // when setting the time using NITZ.
-        if (origin == ORIGIN_PHONE) {
-            // Send a broadcast that telephony code used to send after setting the clock.
-            // TODO Remove this broadcast as soon as there are no remaining listeners.
-            Intent intent = new Intent(TelephonyManager.ACTION_NETWORK_SET_TIME);
-            intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
-            intent.putExtra("time", newSystemClockMillis);
-            mCallback.sendStickyBroadcast(intent);
-        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
index 6ea274d..8f71943 100755
--- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java
+++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
@@ -44,6 +44,7 @@
 import android.media.tv.ITvInputHardwareCallback;
 import android.media.tv.TvInputHardwareInfo;
 import android.media.tv.TvInputInfo;
+import android.media.tv.TvInputService.PriorityHintUseCaseType;
 import android.media.tv.TvStreamConfig;
 import android.os.Handler;
 import android.os.IBinder;
@@ -363,7 +364,8 @@
      * release is notified via ITvInputHardwareCallback.onReleased().
      */
     public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback,
-            TvInputInfo info, int callingUid, int resolvedUserId) {
+            TvInputInfo info, int callingUid, int resolvedUserId,
+            String tvInputSessionId, @PriorityHintUseCaseType int priorityHint) {
         if (callback == null) {
             throw new NullPointerException();
         }
@@ -373,6 +375,8 @@
                 Slog.e(TAG, "Invalid deviceId : " + deviceId);
                 return null;
             }
+            // TODO: check with TRM to compare the client's priority with the current holder's
+            // priority. If lower, do nothing.
             if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
                 TvInputHardwareImpl hardware =
                         new TvInputHardwareImpl(connection.getHardwareInfoLocked());
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 5b58199..e8704ab 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -127,6 +127,9 @@
     // A map from user id to UserState.
     private final SparseArray<UserState> mUserStates = new SparseArray<>();
 
+    // A map from session id to session state saved in userstate
+    private final Map<String, SessionState> mSessionIdToSessionStateMap = new HashMap<>();
+
     private final WatchLogHandler mWatchLogHandler;
 
     public TvInputManagerService(Context context) {
@@ -632,7 +635,8 @@
         UserState userState = getOrCreateUserStateLocked(userId);
         SessionState sessionState = userState.sessionStateMap.get(sessionToken);
         if (DEBUG) {
-            Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.inputId + ")");
+            Slog.d(TAG, "createSessionInternalLocked(inputId="
+                    + sessionState.inputId + ", sessionId=" + sessionState.sessionId + ")");
         }
         InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
 
@@ -643,9 +647,11 @@
         // Create a session. When failed, send a null token immediately.
         try {
             if (sessionState.isRecordingSession) {
-                service.createRecordingSession(callback, sessionState.inputId);
+                service.createRecordingSession(
+                        callback, sessionState.inputId, sessionState.sessionId);
             } else {
-                service.createSession(channels[1], callback, sessionState.inputId);
+                service.createSession(
+                        channels[1], callback, sessionState.inputId, sessionState.sessionId);
             }
         } catch (RemoteException e) {
             Slog.e(TAG, "error in createSession", e);
@@ -715,6 +721,8 @@
             }
         }
 
+        mSessionIdToSessionStateMap.remove(sessionState.sessionId);
+
         ServiceState serviceState = userState.serviceStateMap.get(sessionState.componentName);
         if (serviceState != null) {
             serviceState.sessionTokens.remove(sessionToken);
@@ -1156,9 +1164,11 @@
         public void createSession(final ITvInputClient client, final String inputId,
                 boolean isRecordingSession, int seq, int userId) {
             final int callingUid = Binder.getCallingUid();
-            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+            final int callingPid = Binder.getCallingPid();
+            final int resolvedUserId = resolveCallingUserId(callingPid, callingUid,
                     userId, "createSession");
             final long identity = Binder.clearCallingIdentity();
+            StringBuilder sessionId = new StringBuilder();
             try {
                 synchronized (mLock) {
                     if (userId != mCurrentUserId && !isRecordingSession) {
@@ -1187,15 +1197,21 @@
                         return;
                     }
 
+                    // Create a unique session id with pid, uid and resolved user id
+                    sessionId.append(callingUid).append(callingPid).append(resolvedUserId);
+
                     // Create a new session token and a session state.
                     IBinder sessionToken = new Binder();
                     SessionState sessionState = new SessionState(sessionToken, info.getId(),
                             info.getComponent(), isRecordingSession, client, seq, callingUid,
-                            resolvedUserId);
+                            callingPid, resolvedUserId, sessionId.toString());
 
                     // Add them to the global session state map of the current user.
                     userState.sessionStateMap.put(sessionToken, sessionState);
 
+                    // Map the session id to the sessionStateMap in the user state
+                    mSessionIdToSessionStateMap.put(sessionId.toString(), sessionState);
+
                     // Also, add them to the session state map of the current service.
                     serviceState.sessionTokens.add(sessionToken);
 
@@ -1720,8 +1736,9 @@
 
         @Override
         public ITvInputHardware acquireTvInputHardware(int deviceId,
-                ITvInputHardwareCallback callback, TvInputInfo info, int userId)
-                throws RemoteException {
+                ITvInputHardwareCallback callback, TvInputInfo info, int userId,
+                String tvInputSessionId,
+                @TvInputService.PriorityHintUseCaseType int priorityHint) throws RemoteException {
             if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
                     != PackageManager.PERMISSION_GRANTED) {
                 return null;
@@ -1733,7 +1750,8 @@
                     userId, "acquireTvInputHardware");
             try {
                 return mTvInputHardwareManager.acquireHardware(
-                        deviceId, callback, info, callingUid, resolvedUserId);
+                        deviceId, callback, info, callingUid, resolvedUserId,
+                        tvInputSessionId, priorityHint);
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
@@ -2003,6 +2021,43 @@
         }
 
         @Override
+        public int getClientPid(String sessionId) {
+            ensureTunerResourceAccessPermission();
+            final long identity = Binder.clearCallingIdentity();
+
+            int clientPid = TvInputManager.UNKNOWN_CLIENT_PID;
+            try {
+                synchronized (mLock) {
+                    try {
+                        clientPid = getClientPidLocked(sessionId);
+                    } catch (ClientPidNotFoundException e) {
+                        Slog.e(TAG, "error in getClientPid", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+            return clientPid;
+        }
+
+        private int getClientPidLocked(String sessionId)
+                throws IllegalStateException {
+            if (mSessionIdToSessionStateMap.get(sessionId) == null) {
+                throw new IllegalStateException("Client Pid not found with sessionId "
+                        + sessionId);
+            }
+            return mSessionIdToSessionStateMap.get(sessionId).callingPid;
+        }
+
+        private void ensureTunerResourceAccessPermission() {
+            if (mContext.checkCallingPermission(
+                    android.Manifest.permission.TUNER_RESOURCE_ACCESS)
+                    != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("Requires TUNER_RESOURCE_ACCESS permission");
+            }
+        }
+
+        @Override
         @SuppressWarnings("resource")
         protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
             final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
@@ -2094,9 +2149,11 @@
 
                         pw.increaseIndent();
                         pw.println("inputId: " + session.inputId);
+                        pw.println("sessionId: " + session.sessionId);
                         pw.println("client: " + session.client);
                         pw.println("seq: " + session.seq);
                         pw.println("callingUid: " + session.callingUid);
+                        pw.println("callingPid: " + session.callingPid);
                         pw.println("userId: " + session.userId);
                         pw.println("sessionToken: " + session.sessionToken);
                         pw.println("session: " + session.session);
@@ -2226,11 +2283,13 @@
 
     private final class SessionState implements IBinder.DeathRecipient {
         private final String inputId;
+        private final String sessionId;
         private final ComponentName componentName;
         private final boolean isRecordingSession;
         private final ITvInputClient client;
         private final int seq;
         private final int callingUid;
+        private final int callingPid;
         private final int userId;
         private final IBinder sessionToken;
         private ITvInputSession session;
@@ -2240,7 +2299,7 @@
 
         private SessionState(IBinder sessionToken, String inputId, ComponentName componentName,
                 boolean isRecordingSession, ITvInputClient client, int seq, int callingUid,
-                int userId) {
+                int callingPid, int userId, String sessionId) {
             this.sessionToken = sessionToken;
             this.inputId = inputId;
             this.componentName = componentName;
@@ -2248,7 +2307,9 @@
             this.client = client;
             this.seq = seq;
             this.callingUid = callingUid;
+            this.callingPid = callingPid;
             this.userId = userId;
+            this.sessionId = sessionId;
         }
 
         @Override
@@ -2962,4 +3023,10 @@
             super(name);
         }
     }
+
+    private static class ClientPidNotFoundException extends IllegalArgumentException {
+        public ClientPidNotFoundException(String name) {
+            super(name);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/utils/FlagNamespaceUtils.java b/services/core/java/com/android/server/utils/FlagNamespaceUtils.java
deleted file mode 100644
index f8c7447..0000000
--- a/services/core/java/com/android/server/utils/FlagNamespaceUtils.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.utils;
-
-import android.annotation.Nullable;
-import android.provider.DeviceConfig;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.RescueParty;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Utilities for interacting with the {@link android.provider.DeviceConfig}.
- *
- * @hide
- */
-public final class FlagNamespaceUtils {
-    /**
-     * Special String used for communicating through {@link #RESET_PLATFORM_PACKAGE_FLAG} that
-     * Settings were reset by the RescueParty, no actual namespace with this name exists in
-     * {@link DeviceConfig}.
-     */
-    public static final String NAMESPACE_NO_PACKAGE = "no_package";
-
-    /**
-     * Name of the special namespace in DeviceConfig table used for communicating resets.
-     */
-    @VisibleForTesting
-    public static final String NAMESPACE_RESCUE_PARTY = "rescue_party_namespace";
-    /**
-     * Flag in the {@link DeviceConfig} in {@link #NAMESPACE_RESCUE_PARTY}, holding all known {@link
-     * DeviceConfig} namespaces, as a {@link #DELIMITER} separated String. It's updated after the
-     * first time flags are written to the new namespace in the {@link DeviceConfig}.
-     */
-    @VisibleForTesting
-    public static final String ALL_KNOWN_NAMESPACES_FLAG = "all_known_namespaces";
-    /**
-     * Flag in the {@link DeviceConfig} in {@link #NAMESPACE_RESCUE_PARTY} with integer counter
-     * suffix added to it, holding {@link DeviceConfig} namespace value whose flags were recently
-     * reset by the {@link RescueParty}. It's updated by {@link RescueParty} every time given
-     * namespace flags are reset.
-     */
-    @VisibleForTesting
-    public static final String RESET_PLATFORM_PACKAGE_FLAG = "reset_platform_package";
-    private static final String DELIMITER = ":";
-    /**
-     * Maximum value of the counter used in combination with {@link #RESET_PLATFORM_PACKAGE_FLAG}
-     * when communicating recently reset by the RescueParty namespace values.
-     */
-    private static final int MAX_COUNTER_VALUE = 50;
-
-    private static int sKnownResetNamespacesFlagCounter = -1;
-
-    /**
-     * Sets the union of {@link #RESET_PLATFORM_PACKAGE_FLAG} with
-     * {@link #sKnownResetNamespacesFlagCounter} in the DeviceConfig for each namespace
-     * in the consumed namespacesList. These flags are used for communicating the namespaces
-     * (aka platform packages) whose flags in {@link DeviceConfig} were just reset
-     * by the RescueParty.
-     */
-    public static void addToKnownResetNamespaces(@Nullable List<String> namespacesList) {
-        if (namespacesList == null) {
-            return;
-        }
-        for (String namespace : namespacesList) {
-            addToKnownResetNamespaces(namespace);
-        }
-    }
-
-    /**
-     * Sets the union of {@link #RESET_PLATFORM_PACKAGE_FLAG} with
-     * {@link #sKnownResetNamespacesFlagCounter} in the DeviceConfig for the consumed namespace.
-     * This flag is used for communicating the namespace (aka platform package) whose flags
-     * in {@link DeviceConfig} were just reset by the RescueParty.
-     */
-    public static void addToKnownResetNamespaces(String namespace) {
-        int nextFlagCounter = incrementAndRetrieveResetNamespacesFlagCounter();
-        DeviceConfig.setProperty(NAMESPACE_RESCUE_PARTY,
-                RESET_PLATFORM_PACKAGE_FLAG + nextFlagCounter,
-                namespace, /*makeDefault=*/ true);
-    }
-
-    /**
-     * Reset all namespaces in DeviceConfig with consumed resetMode.
-     */
-    public static void resetDeviceConfig(int resetMode) {
-        resetDeviceConfig(resetMode, getAllKnownDeviceConfigNamespacesList());
-    }
-
-    /**
-     * Reset all consumed namespaces in DeviceConfig with consumed resetMode.
-     */
-    public static void resetDeviceConfig(int resetMode, List<String> namespacesList) {
-        for (String namespace : namespacesList) {
-            DeviceConfig.resetToDefaults(resetMode, namespace);
-        }
-        addToKnownResetNamespaces(namespacesList);
-    }
-
-    /**
-     * Resets known reset namespaces flag counter for tests only.
-     */
-    @VisibleForTesting
-    public static void resetKnownResetNamespacesFlagCounterForTest() {
-        sKnownResetNamespacesFlagCounter = -1;
-    }
-
-    /**
-     * Returns a list of all known DeviceConfig namespaces, except for the special {@link
-     * #NAMESPACE_RESCUE_PARTY}
-     */
-    private static List<String> getAllKnownDeviceConfigNamespacesList() {
-        String namespacesStr = DeviceConfig.getProperty(NAMESPACE_RESCUE_PARTY,
-                ALL_KNOWN_NAMESPACES_FLAG);
-        List<String> namespacesList = toStringList(namespacesStr);
-        namespacesList.remove(NAMESPACE_RESCUE_PARTY);
-        return namespacesList;
-    }
-
-    private static List<String> toStringList(String serialized) {
-        if (serialized == null || serialized.length() == 0) {
-            return new ArrayList<>();
-        }
-        return Arrays.asList(serialized.split(DELIMITER));
-    }
-
-    private static int incrementAndRetrieveResetNamespacesFlagCounter() {
-        sKnownResetNamespacesFlagCounter++;
-        if (sKnownResetNamespacesFlagCounter == MAX_COUNTER_VALUE) {
-            sKnownResetNamespacesFlagCounter = 0;
-        }
-        return sKnownResetNamespacesFlagCounter;
-    }
-}
diff --git a/services/core/java/com/android/server/utils/quota/QuotaTracker.java b/services/core/java/com/android/server/utils/quota/QuotaTracker.java
index ef1f426..a8cf9f6 100644
--- a/services/core/java/com/android/server/utils/quota/QuotaTracker.java
+++ b/services/core/java/com/android/server/utils/quota/QuotaTracker.java
@@ -172,6 +172,16 @@
 
     // Exposed API to users.
 
+    /** Remove all saved events from the tracker. */
+    public void clear() {
+        synchronized (mLock) {
+            mInQuotaAlarmListener.clearLocked();
+            mFreeQuota.clear();
+
+            dropEverythingLocked();
+        }
+    }
+
     /**
      * @return true if the UPTC is within quota, false otherwise.
      * @throws IllegalStateException if given categorizer returns a Category that's not recognized.
@@ -245,10 +255,7 @@
             mIsEnabled = enable;
 
             if (!mIsEnabled) {
-                mInQuotaAlarmListener.clearLocked();
-                mFreeQuota.clear();
-
-                dropEverythingLocked();
+                clear();
             }
         }
     }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 03139d2e..36e9775 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -2180,6 +2180,47 @@
         }
     }
 
+    /**
+     * Called when the wallpaper needs to zoom out.
+     *
+     * @param zoom from 0 to 1 (inclusive) where 1 means fully zoomed out, 0 means fully zoomed in.
+     * @param callingPackage package name calling this API.
+     * @param displayId id of the display whose zoom is updating.
+     */
+    public void setWallpaperZoomOut(float zoom, String callingPackage, int displayId) {
+        if (!isWallpaperSupported(callingPackage)) {
+            return;
+        }
+        synchronized (mLock) {
+            if (!isValidDisplay(displayId)) {
+                throw new IllegalArgumentException("Cannot find display with id=" + displayId);
+            }
+            int userId = UserHandle.getCallingUserId();
+            if (mCurrentUserId != userId) {
+                return; // Don't change the properties now
+            }
+            WallpaperData wallpaper = getWallpaperSafeLocked(userId, FLAG_SYSTEM);
+            if (zoom < 0 || zoom > 1f) {
+                throw new IllegalArgumentException("zoom must be between 0 and one: " + zoom);
+            }
+
+            if (wallpaper.connection != null) {
+                final WallpaperConnection.DisplayConnector connector = wallpaper.connection
+                        .getDisplayConnectorOrCreate(displayId);
+                final IWallpaperEngine engine = connector != null ? connector.mEngine : null;
+                if (engine != null) {
+                    try {
+                        engine.setZoomOut(zoom);
+                    } catch (RemoteException e) {
+                        if (DEBUG) {
+                            Slog.w(TAG, "Couldn't set wallpaper zoom", e);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     @Deprecated
     @Override
     public ParcelFileDescriptor getWallpaper(String callingPkg, IWallpaperManagerCallback cb,
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index e281712..d1c8448 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -31,7 +31,6 @@
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
 import static android.app.ActivityOptions.ANIM_UNDEFINED;
-import static android.app.ActivityTaskManager.INVALID_STACK_ID;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
@@ -243,9 +242,9 @@
 import android.app.servertransaction.PauseActivityItem;
 import android.app.servertransaction.PipModeChangeItem;
 import android.app.servertransaction.ResumeActivityItem;
+import android.app.servertransaction.StartActivityItem;
 import android.app.servertransaction.StopActivityItem;
 import android.app.servertransaction.TopResumedActivityChangeItem;
-import android.app.servertransaction.WindowVisibilityItem;
 import android.app.usage.UsageEvents.Event;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -745,7 +744,8 @@
             synchronized (mAtmService.mGlobalLock) {
                 Slog.w(TAG, "Activity stop timeout for " + ActivityRecord.this);
                 if (isInHistory()) {
-                    activityStopped(null /*icicle*/, null /*persistentState*/, null /*description*/);
+                    activityStopped(
+                            null /*icicle*/, null /*persistentState*/, null /*description*/);
                 }
             }
         }
@@ -1286,17 +1286,11 @@
 
         updateColorTransform();
 
-        final ActivityStack oldStack = (oldTask != null) ? oldTask.getStack() : null;
-        final ActivityStack newStack = (newTask != null) ? newTask.getStack() : null;
-        // Inform old stack (if present) of activity removal and new stack (if set) of activity
-        // addition.
-        if (oldStack != newStack) {
-            if (oldStack !=  null) {
-                oldStack.onActivityRemovedFromStack(this);
-            }
-            if (newStack !=  null) {
-                newStack.onActivityAddedToStack(this);
-            }
+        if (oldTask != null) {
+            oldTask.cleanUpActivityReferences(this);
+        }
+        if (newTask != null && isState(RESUMED)) {
+            newTask.setResumedActivity(this, "onParentChanged");
         }
     }
 
@@ -1431,7 +1425,7 @@
                 return null;
             }
             ActivityRecord r = token.weakActivity.get();
-            if (r == null || r.getActivityStack() == null) {
+            if (r == null || r.getRootTask() == null) {
                 return null;
             }
             return r;
@@ -1633,12 +1627,7 @@
         requestedVrComponent = (aInfo.requestedVrComponent == null) ?
                 null : ComponentName.unflattenFromString(aInfo.requestedVrComponent);
 
-        lockTaskLaunchMode = aInfo.lockTaskLaunchMode;
-        if (info.applicationInfo.isPrivilegedApp()
-                && (lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_ALWAYS
-                || lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_NEVER)) {
-            lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
-        }
+        lockTaskLaunchMode = getLockTaskLaunchMode(aInfo, options);
 
         if (options != null) {
             pendingOptions = options;
@@ -1646,15 +1635,27 @@
             if (usageReport != null) {
                 appTimeTracker = new AppTimeTracker(usageReport);
             }
-            final boolean useLockTask = pendingOptions.getLockTaskMode();
-            if (useLockTask && lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_DEFAULT) {
-                lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
-            }
             // Gets launch display id from options. It returns INVALID_DISPLAY if not set.
             mHandoverLaunchDisplayId = options.getLaunchDisplayId();
         }
     }
 
+    static int getLockTaskLaunchMode(ActivityInfo aInfo, @Nullable ActivityOptions options) {
+        int lockTaskLaunchMode = aInfo.lockTaskLaunchMode;
+        if (aInfo.applicationInfo.isPrivilegedApp()
+                && (lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_ALWAYS
+                || lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_NEVER)) {
+            lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
+        }
+        if (options != null) {
+            final boolean useLockTask = options.getLockTaskMode();
+            if (useLockTask && lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_DEFAULT) {
+                lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
+            }
+        }
+        return lockTaskLaunchMode;
+    }
+
     @Override
     ActivityRecord asActivityRecord() {
         // I am an activity record!
@@ -1673,6 +1674,7 @@
         if (root == this) {
             task.setRootProcess(proc);
         }
+        proc.addActivityIfNeeded(this);
     }
 
     boolean hasProcess() {
@@ -2052,19 +2054,17 @@
         }
     }
 
-    /**
-     * @return Stack value from current task, null if there is no task.
-     */
-    ActivityStack getActivityStack() {
-        return task != null ? task.getStack() : null;
+    /** @return Root task of this activity, null if there is no task. */
+    ActivityStack getRootTask() {
+        return task != null ? (ActivityStack) task.getRootTask() : null;
     }
 
-    int getStackId() {
-        return getActivityStack() != null ? getActivityStack().mStackId : INVALID_STACK_ID;
+    int getRootTaskId() {
+        return task != null ? task.getRootTaskId() : INVALID_TASK_ID;
     }
 
     DisplayContent getDisplay() {
-        final ActivityStack stack = getActivityStack();
+        final ActivityStack stack = getRootTask();
         return stack != null ? stack.getDisplay() : null;
     }
 
@@ -2085,7 +2085,7 @@
         mWmService.mWindowPlacerLocked.requestTraversal();
 
         if (changed && task != null && !occludesParent) {
-            getActivityStack().convertActivityToTranslucent(this);
+            getRootTask().convertActivityToTranslucent(this);
         }
         // Always ensure visibility if this activity doesn't occlude parent, so the
         // {@link #returningOptions} of the activity under this one can be applied in
@@ -2120,7 +2120,7 @@
     }
 
     boolean isInStackLocked() {
-        final ActivityStack stack = getActivityStack();
+        final ActivityStack stack = getRootTask();
         return stack != null && stack.isInStackLocked(this) != null;
     }
 
@@ -2130,8 +2130,10 @@
                 (intent == null || (intent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0);
     }
 
+    @Override
     boolean isFocusable() {
-        return mRootWindowContainer.isFocusable(this, isAlwaysFocusable());
+        return super.isFocusable()
+                && (getWindowConfiguration().canReceiveKeys() || isAlwaysFocusable());
     }
 
     boolean isResizeable() {
@@ -2221,7 +2223,7 @@
         boolean isCurrentAppLocked =
                 mAtmService.getLockTaskModeState() != LOCK_TASK_MODE_NONE;
         final DisplayContent display = getDisplay();
-        boolean hasPinnedStack = display != null && display.hasPinnedStack();
+        boolean hasPinnedStack = display != null && display.hasPinnedTask();
         // Don't return early if !isNotLocked, since we want to throw an exception if the activity
         // is in an incorrect state
         boolean isNotLockedOrOnKeyguard = !isKeyguardLocked && !isCurrentAppLocked;
@@ -2320,7 +2322,7 @@
             return false;
         }
 
-        final ActivityStack stack = getActivityStack();
+        final ActivityStack stack = getRootTask();
         if (stack == null) {
             Slog.w(TAG, "moveActivityStackToFront: invalid task or stack: activity="
                     + this + " task=" + task);
@@ -2451,7 +2453,7 @@
             return FINISH_RESULT_CANCELLED;
         }
 
-        final ActivityStack stack = getActivityStack();
+        final ActivityStack stack = getRootTask();
         final boolean mayAdjustTop = (isState(RESUMED) || stack.mResumedActivity == null)
                 && stack.isFocusedStackOnDisplay();
         final boolean shouldAdjustGlobalFocus = mayAdjustTop
@@ -2482,7 +2484,7 @@
             // We are finishing the top focused activity and its stack has nothing to be focused so
             // the next focusable stack should be focused.
             if (mayAdjustTop
-                    && (stack.topRunningActivity() == null || !stack.isFocusable())) {
+                    && (stack.topRunningActivity() == null || !stack.isTopActivityFocusable())) {
                 if (shouldAdjustGlobalFocus) {
                     // Move the entire hierarchy to top with updating global top resumed activity
                     // and focused application if needed.
@@ -2492,7 +2494,7 @@
                     final DisplayContent display = stack.getDisplay();
                     next = display.topRunningActivity();
                     if (next != null) {
-                        display.positionStackAtTop(next.getActivityStack(),
+                        display.positionStackAtTop(next.getRootTask(),
                                 false /* includingParents */, "finish-display-top");
                     }
                 }
@@ -2661,13 +2663,13 @@
         mStackSupervisor.mStoppingActivities.remove(this);
         mStackSupervisor.mGoingToSleepActivities.remove(this);
 
-        final ActivityStack stack = getActivityStack();
+        final ActivityStack stack = getRootTask();
         final DisplayContent display = getDisplay();
         // TODO(b/137329632): Exclude current activity when looking for the next one with
         // DisplayContent#topRunningActivity().
         final ActivityRecord next = display.topRunningActivity();
         final boolean isLastStackOverEmptyHome =
-                next == null && stack.isFocusedStackOnDisplay() && display.getHomeStack() != null;
+                next == null && stack.isFocusedStackOnDisplay() && display.getRootHomeTask() != null;
         if (isLastStackOverEmptyHome) {
             // Don't destroy activity immediately if this is the last activity on the display and
             // the display contains home stack. Although there is no next activity at the moment,
@@ -2819,7 +2821,7 @@
     boolean safelyDestroy(String reason) {
         if (isDestroyable()) {
             if (DEBUG_SWITCH) {
-                final ActivityStack stack = getActivityStack();
+                final ActivityStack stack = getRootTask();
                 Slog.v(TAG_SWITCH, "Safely destroying " + this + " in state " + getState()
                         + " resumed=" + stack.mResumedActivity
                         + " pausing=" + stack.mPausingActivity
@@ -2894,8 +2896,7 @@
      * Note: Call before {@link #removeFromHistory(String)}.
      */
     void cleanUp(boolean cleanServices, boolean setState) {
-        final ActivityStack stack = getActivityStack();
-        stack.onActivityRemovedFromStack(this);
+        task.cleanUpActivityReferences(this);
 
         deferRelaunchUntilPaused = false;
         frozenBeforeDestroy = false;
@@ -3608,7 +3609,7 @@
     }
 
     final boolean isSleeping() {
-        final ActivityStack stack = getActivityStack();
+        final ActivityStack stack = getRootTask();
         return stack != null ? stack.shouldSleepActivities() : mAtmService.isSleepingLocked();
     }
 
@@ -3823,7 +3824,7 @@
 
                 // Notify the pinned stack upon all windows drawn. If there was an animation in
                 // progress then this signal will resume that animation.
-                final ActivityStack pinnedStack = mDisplayContent.getPinnedStack();
+                final ActivityStack pinnedStack = mDisplayContent.getRootPinnedTask();
                 if (pinnedStack != null) {
                     pinnedStack.onAllWindowsDrawn();
                 }
@@ -4079,11 +4080,12 @@
 
     @Override
     boolean applyAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
-            boolean isVoiceInteraction) {
+            boolean isVoiceInteraction, @Nullable Runnable animationFinishedCallback) {
         if (mUseTransferredAnimation) {
             return false;
         }
-        return super.applyAnimation(lp, transit, enter, isVoiceInteraction);
+        return super.applyAnimation(lp, transit, enter, isVoiceInteraction,
+                animationFinishedCallback);
     }
 
     /**
@@ -4444,7 +4446,7 @@
             return visibleIgnoringKeyguard;
         }
 
-        final ActivityStack stack = getActivityStack();
+        final ActivityStack stack = getRootTask();
         if (stack == null) {
             return false;
         }
@@ -4466,7 +4468,7 @@
     }
 
     boolean shouldBeVisible() {
-        final ActivityStack stack = getActivityStack();
+        final ActivityStack stack = getRootTask();
         if (stack == null) {
             return false;
         }
@@ -4487,7 +4489,7 @@
         // If this activity is paused, tell it to now show its window.
         if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
                 "Making visible and scheduling visibility: " + this);
-        final ActivityStack stack = getActivityStack();
+        final ActivityStack stack = getRootTask();
         try {
             if (stack.mTranslucentActivityWaiting != null) {
                 updateOptionsLocked(returningOptions);
@@ -4497,7 +4499,8 @@
             sleeping = false;
             app.postPendingUiCleanMsg(true);
             if (reportToClient) {
-                makeClientVisible();
+                mClientVisibilityDeferred = false;
+                makeActiveIfNeeded(starting);
             } else {
                 mClientVisibilityDeferred = true;
             }
@@ -4511,23 +4514,6 @@
         handleAlreadyVisible();
     }
 
-    /** Send visibility change message to the client and pause if needed. */
-    void makeClientVisible() {
-        mClientVisibilityDeferred = false;
-        try {
-            mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
-                    WindowVisibilityItem.obtain(true /* showWindow */));
-            makeActiveIfNeeded(null /* activeActivity*/);
-            if (isState(STOPPING, STOPPED)) {
-                // Set state to STARTED in order to have consistent state with client while
-                // making an non-active activity visible from stopped.
-                setState(STARTED, "makeClientVisible");
-            }
-        } catch (Exception e) {
-            Slog.w(TAG, "Exception thrown sending visibility update: " + intent.getComponent(), e);
-        }
-    }
-
     void makeInvisible() {
         if (!mVisibleRequested) {
             if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Already invisible: " + this);
@@ -4556,14 +4542,6 @@
             switch (getState()) {
                 case STOPPING:
                 case STOPPED:
-                    if (attachedToProcess()) {
-                        if (DEBUG_VISIBILITY) {
-                            Slog.v(TAG_VISIBILITY, "Scheduling invisibility: " + this);
-                        }
-                        mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(),
-                                appToken, WindowVisibilityItem.obtain(false /* showWindow */));
-                    }
-
                     // Reset the flag indicating that an app can enter picture-in-picture once the
                     // activity is hidden
                     supportsEnterPipOnTaskSwitch = false;
@@ -4595,17 +4573,17 @@
     boolean makeActiveIfNeeded(ActivityRecord activeActivity) {
         if (shouldResumeActivity(activeActivity)) {
             if (DEBUG_VISIBILITY) {
-                Slog.v("TAG_VISIBILITY", "Resume visible activity, " + this);
+                Slog.v(TAG_VISIBILITY, "Resume visible activity, " + this);
             }
-            return getActivityStack().resumeTopActivityUncheckedLocked(activeActivity /* prev */,
+            return getRootTask().resumeTopActivityUncheckedLocked(activeActivity /* prev */,
                     null /* options */);
         } else if (shouldPauseActivity(activeActivity)) {
             if (DEBUG_VISIBILITY) {
-                Slog.v("TAG_VISIBILITY", "Pause visible activity, " + this);
+                Slog.v(TAG_VISIBILITY, "Pause visible activity, " + this);
             }
             // An activity must be in the {@link PAUSING} state for the system to validate
             // the move to {@link PAUSED}.
-            setState(PAUSING, "makeVisibleIfNeeded");
+            setState(PAUSING, "makeActiveIfNeeded");
             try {
                 mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
                         PauseActivityItem.obtain(finishing, false /* userLeaving */,
@@ -4613,6 +4591,17 @@
             } catch (Exception e) {
                 Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e);
             }
+        } else if (shouldStartActivity()) {
+            if (DEBUG_VISIBILITY) {
+                Slog.v(TAG_VISIBILITY, "Start visible activity, " + this);
+            }
+            setState(STARTED, "makeActiveIfNeeded");
+            try {
+                mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+                        StartActivityItem.obtain());
+            } catch (Exception e) {
+                Slog.w(TAG, "Exception thrown sending start: " + intent.getComponent(), e);
+            }
         }
         return false;
     }
@@ -4651,11 +4640,21 @@
      */
     private boolean shouldBeResumed(ActivityRecord activeActivity) {
         return shouldMakeActive(activeActivity) && isFocusable()
-                && getActivityStack().getVisibility(activeActivity) == STACK_VISIBILITY_VISIBLE
+                && getRootTask().getVisibility(activeActivity) == STACK_VISIBILITY_VISIBLE
                 && canResumeByCompat();
     }
 
     /**
+     * Check if activity should be moved to STARTED state.
+     * NOTE: This will not check if activity should be made paused or resumed first, so it must only
+     * be called after checking with {@link #shouldResumeActivity(ActivityRecord)}
+     * and {@link #shouldPauseActivity(ActivityRecord)}.
+     */
+    private boolean shouldStartActivity() {
+        return mVisibleRequested && isState(STOPPED);
+    }
+
+    /**
      * Check if activity is eligible to be made active (resumed of paused). The activity:
      * - should be paused, stopped or stopping
      * - should not be the currently active one or launching behind other tasks
@@ -4670,7 +4669,7 @@
         // ActivityStack#ensureActivitiesVisible will bring the activity to a proper
         // active state.
         if (!isState(STARTED, RESUMED, PAUSED, STOPPED, STOPPING)
-                || getActivityStack().mTranslucentActivityWaiting != null) {
+                || getRootTask().mTranslucentActivityWaiting != null) {
             return false;
         }
 
@@ -4767,7 +4766,7 @@
         mStackSupervisor.reportResumedActivityLocked(this);
 
         resumeKeyDispatchingLocked();
-        final ActivityStack stack = getActivityStack();
+        final ActivityStack stack = getRootTask();
         mStackSupervisor.mNoAnimActivities.clear();
 
         // Mark the point when the activity is resuming
@@ -4860,7 +4859,7 @@
 
     void stopIfPossible() {
         if (DEBUG_SWITCH) Slog.d(TAG_SWITCH, "Stopping: " + this);
-        final ActivityStack stack = getActivityStack();
+        final ActivityStack stack = getRootTask();
         if (isNoHistory()) {
             if (!finishing) {
                 if (!stack.shouldSleepActivities()) {
@@ -4890,16 +4889,13 @@
             }
             setState(STOPPING, "stopIfPossible");
             if (DEBUG_VISIBILITY) {
-                Slog.v(TAG_VISIBILITY, "Stopping visibleRequested="
-                        + mVisibleRequested + " for " + this);
-            }
-            if (!mVisibleRequested) {
-                setVisibility(false);
+                Slog.v(TAG_VISIBILITY, "Stopping:" + this);
             }
             EventLogTags.writeWmStopActivity(
                     mUserId, System.identityHashCode(this), shortComponentName);
             mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
-                    StopActivityItem.obtain(mVisibleRequested, configChangeFlags));
+                    StopActivityItem.obtain(configChangeFlags));
+
             if (stack.shouldSleepOrShutDownActivities()) {
                 setSleeping(true);
             }
@@ -4920,7 +4916,7 @@
 
     void activityStopped(Bundle newIcicle, PersistableBundle newPersistentState,
             CharSequence description) {
-        final ActivityStack stack = getActivityStack();
+        final ActivityStack stack = getRootTask();
         final boolean isStopping = mState == STOPPING;
         if (!isStopping && mState != RESTARTING_PROCESS) {
             Slog.i(TAG, "Activity reported stop, but no longer stopping: " + this);
@@ -4970,7 +4966,7 @@
             mStackSupervisor.mStoppingActivities.add(this);
         }
 
-        final ActivityStack stack = getActivityStack();
+        final ActivityStack stack = getRootTask();
         // If we already have a few activities waiting to stop, then give up on things going idle
         // and start clearing them out. Or if r is the last of activity of the last task the stack
         // will be empty and must be cleared immediately.
@@ -5006,7 +5002,7 @@
             return false;
         }
 
-        final ActivityStack stack = getActivityStack();
+        final ActivityStack stack = getRootTask();
         if (stack == null) {
             return false;
         }
@@ -5022,7 +5018,7 @@
 
     void finishLaunchTickingLocked() {
         launchTickTime = 0;
-        final ActivityStack stack = getActivityStack();
+        final ActivityStack stack = getRootTask();
         if (stack == null) {
             return;
         }
@@ -5492,13 +5488,13 @@
 
     static ActivityRecord isInStackLocked(IBinder token) {
         final ActivityRecord r = ActivityRecord.forTokenLocked(token);
-        return (r != null) ? r.getActivityStack().isInStackLocked(r) : null;
+        return (r != null) ? r.getRootTask().isInStackLocked(r) : null;
     }
 
     static ActivityStack getStackLocked(IBinder token) {
         final ActivityRecord r = ActivityRecord.isInStackLocked(token);
         if (r != null) {
-            return r.getActivityStack();
+            return r.getRootTask();
         }
         return null;
     }
@@ -5508,7 +5504,7 @@
      *         {@link android.view.Display#INVALID_DISPLAY} if not attached.
      */
     int getDisplayId() {
-        final ActivityStack stack = getActivityStack();
+        final ActivityStack stack = getRootTask();
         if (stack == null) {
             return INVALID_DISPLAY;
         }
@@ -5520,7 +5516,7 @@
             // This would be redundant.
             return false;
         }
-        final ActivityStack stack = getActivityStack();
+        final ActivityStack stack = getRootTask();
         if (isState(RESUMED) || stack == null || this == stack.mPausingActivity || !mHaveState
                 || !stopped) {
             // We're not ready for this kind of thing.
@@ -5828,7 +5824,7 @@
     @Override
     boolean isWaitingForTransitionStart() {
         final DisplayContent dc = getDisplayContent();
-        return dc.mAppTransition.isTransitionSet()
+        return dc != null && dc.mAppTransition.isTransitionSet()
                 && (dc.mOpeningApps.contains(this)
                 || dc.mClosingApps.contains(this)
                 || dc.mChangingApps.contains(this));
@@ -5940,7 +5936,11 @@
             mAnimationBoundsLayer = createAnimationBoundsLayer(t);
 
             // Crop to stack bounds.
-            t.setWindowCrop(mAnimationBoundsLayer, mTmpRect);
+            if (!WindowManagerService.sHierarchicalAnimations) {
+                // For Hierarchical animation, we don't need to set window crop since the leash
+                // surface size has already same as the animating container.
+                t.setWindowCrop(mAnimationBoundsLayer, mTmpRect);
+            }
             t.setLayer(mAnimationBoundsLayer, layer);
 
             // Reparent leash to animation bounds layer.
@@ -5988,9 +5988,10 @@
             return;
         }
         clearThumbnail();
+        final Transaction transaction = getAnimatingContainer().getPendingTransaction();
         mThumbnail = new WindowContainerThumbnail(mWmService.mSurfaceFactory,
-                getPendingTransaction(), this, thumbnailHeader);
-        mThumbnail.startAnimation(getPendingTransaction(), loadThumbnailAnimation(thumbnailHeader));
+                transaction, getAnimatingContainer(), thumbnailHeader);
+        mThumbnail.startAnimation(transaction, loadThumbnailAnimation(thumbnailHeader));
     }
 
     /**
@@ -6017,13 +6018,13 @@
         if (thumbnail == null) {
             return;
         }
+        final Transaction transaction = getAnimatingContainer().getPendingTransaction();
         mThumbnail = new WindowContainerThumbnail(mWmService.mSurfaceFactory,
-                getPendingTransaction(), this, thumbnail);
+                transaction, getAnimatingContainer(), thumbnail);
         final Animation animation =
                 getDisplayContent().mAppTransition.createCrossProfileAppsThumbnailAnimationLocked(
                         win.getFrameLw());
-        mThumbnail.startAnimation(getPendingTransaction(), animation, new Point(frame.left,
-                frame.top));
+        mThumbnail.startAnimation(transaction, animation, new Point(frame.left, frame.top));
     }
 
     private Animation loadThumbnailAnimation(GraphicBuffer thumbnailHeader) {
@@ -6355,6 +6356,14 @@
      *         aspect ratio.
      */
     boolean shouldUseSizeCompatMode() {
+        if (inMultiWindowMode() || getWindowConfiguration().hasWindowDecorCaption()) {
+            final ActivityRecord root = task != null ? task.getRootActivity() : null;
+            if (root != null && root != this && !root.shouldUseSizeCompatMode()) {
+                // If the root activity doesn't use size compatibility mode, the activities above
+                // are forced to be the same for consistent visual appearance.
+                return false;
+            }
+        }
         return !isResizeable() && (info.isFixedOrientation() || info.hasFixedAspectRatio())
                 // The configuration of non-standard type should be enforced by system.
                 && isActivityTypeStandard()
@@ -6703,7 +6712,7 @@
     void savePinnedStackBounds() {
         // Leaving PiP to fullscreen, save the snap fraction based on the pre-animation bounds
         // for the next re-entry into PiP (assuming the activity is not hidden or destroyed)
-        final ActivityStack pinnedStack = mDisplayContent.getPinnedStack();
+        final ActivityStack pinnedStack = mDisplayContent.getRootPinnedTask();
         if (pinnedStack == null) return;
         final Rect stackBounds;
         if (pinnedStack.lastAnimatingBoundsWasToFullscreen()) {
@@ -6743,7 +6752,7 @@
     private void applyAspectRatio(Rect outBounds, Rect containingAppBounds,
             Rect containingBounds) {
         final float maxAspectRatio = info.maxAspectRatio;
-        final ActivityStack stack = getActivityStack();
+        final ActivityStack stack = getRootTask();
         final float minAspectRatio = info.minAspectRatio;
 
         if (task == null || stack == null || (inMultiWindowMode() && !shouldUseSizeCompatMode())
@@ -6849,7 +6858,7 @@
      */
     boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow,
             boolean ignoreVisibility) {
-        final ActivityStack stack = getActivityStack();
+        final ActivityStack stack = getRootTask();
         if (stack.mConfigWillChange) {
             if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
                     "Skipping config check (will change): " + this);
@@ -6902,7 +6911,7 @@
         // Update last reported values.
         final Configuration newMergedOverrideConfig = getMergedOverrideConfiguration();
 
-        setLastReportedConfiguration(mAtmService.getGlobalConfiguration(), newMergedOverrideConfig);
+        setLastReportedConfiguration(getProcessGlobalConfiguration(), newMergedOverrideConfig);
 
         if (mState == INITIALIZING) {
             // No need to relaunch or schedule new config for activity that hasn't been launched
@@ -7001,6 +7010,11 @@
         return true;
     }
 
+    /** Get process configuration, or global config if the process is not set. */
+    private Configuration getProcessGlobalConfiguration() {
+        return app != null ? app.getConfiguration() : mAtmService.getGlobalConfiguration();
+    }
+
     /**
      * When assessing a configuration change, decide if the changes flags and the new configurations
      * should cause the Activity to relaunch.
@@ -7113,7 +7127,7 @@
             startRelaunching();
             final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(pendingResults,
                     pendingNewIntents, configChangeFlags,
-                    new MergedConfiguration(mAtmService.getGlobalConfiguration(),
+                    new MergedConfiguration(getProcessGlobalConfiguration(),
                             getMergedOverrideConfiguration()),
                     preserveWindow);
             final ActivityLifecycleItem lifecycleItem;
@@ -7201,7 +7215,7 @@
         // {@link ActivityTaskManagerService.activityStopped}).
         try {
             mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
-                    StopActivityItem.obtain(false /* showWindow */, 0 /* configChanges */));
+                    StopActivityItem.obtain(0 /* configChanges */));
         } catch (RemoteException e) {
             Slog.w(TAG, "Exception thrown during restart " + this, e);
         }
@@ -7389,7 +7403,7 @@
         if (!getTurnScreenOnFlag()) {
             return false;
         }
-        final ActivityStack stack = getActivityStack();
+        final ActivityStack stack = getRootTask();
         return stack != null &&
                 stack.checkKeyguardVisibility(this, true /* shouldBeVisible */, true /* isTop */);
     }
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index c959439..942be84 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -80,7 +80,6 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PAUSE;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STACK;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STATES;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TRANSITION;
@@ -119,7 +118,6 @@
 import static com.android.server.wm.StackProto.WINDOW_CONTAINER;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import static java.lang.Integer.MAX_VALUE;
@@ -153,6 +151,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.Trace;
+import android.os.UserHandle;
 import android.service.voice.IVoiceInteractionSession;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -161,7 +160,7 @@
 import android.view.Display;
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
-import android.view.RemoteAnimationTarget;
+import android.view.ITaskOrganizer;
 import android.view.SurfaceControl;
 
 import com.android.internal.annotations.GuardedBy;
@@ -189,7 +188,7 @@
 /**
  * State and management of a single stack of activities.
  */
-class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAnimationTarget {
+class ActivityStack extends Task implements BoundsAnimationTarget {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStack" : TAG_ATM;
     static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE;
     private static final String TAG_APP = TAG + POSTFIX_APP;
@@ -250,33 +249,6 @@
         RESTARTING_PROCESS
     }
 
-    final ActivityTaskManagerService mAtmService;
-
-    /**
-     * When we are in the process of pausing an activity, before starting the
-     * next one, this variable holds the activity that is currently being paused.
-     */
-    ActivityRecord mPausingActivity = null;
-
-    /**
-     * This is the last activity that we put into the paused state.  This is
-     * used to determine if we need to do an activity transition while sleeping,
-     * when we normally hold the top activity paused.
-     */
-    ActivityRecord mLastPausedActivity = null;
-
-    /**
-     * Activities that specify No History must be removed once the user navigates away from them.
-     * If the device goes to sleep with such an activity in the paused state then we save it here
-     * and finish it later if another activity replaces it on wakeup.
-     */
-    ActivityRecord mLastNoHistoryActivity = null;
-
-    /**
-     * Current activity that is resumed, or null if there is none.
-     */
-    ActivityRecord mResumedActivity = null;
-
     // The topmost Activity passed to convertToTranslucent(). When non-null it means we are
     // waiting for all Activities in mUndrawnActivitiesBelowTopTranslucent to be removed as they
     // are drawn. When the last member of mUndrawnActivitiesBelowTopTranslucent is removed the
@@ -293,11 +265,6 @@
     boolean mConfigWillChange;
 
     /**
-     * When set, will force the stack to report as invisible.
-     */
-    boolean mForceHidden = false;
-
-    /**
      * Used to keep resumeTopActivityUncheckedLocked() from being entered recursively
      */
     boolean mInResumeTopActivity = false;
@@ -310,18 +277,9 @@
 
     int mCurrentUser;
 
-    /** The attached Display's unique identifier, or -1 if detached */
-    private int mDisplayId;
-    // Id of the previous display the stack was on.
-    int mPrevDisplayId = INVALID_DISPLAY;
-
-    /** Unique identifier */
-    final int mStackId;
-
     /** For comparison with DisplayContent bounds. */
     private Rect mTmpRect = new Rect();
     private Rect mTmpRect2 = new Rect();
-    private Rect mTmpRect3 = new Rect();
 
     /** For Pinned stack controlling. */
     private Rect mTmpToBounds = new Rect();
@@ -335,9 +293,6 @@
      */
     private final Rect mFullyAdjustedImeBounds = new Rect();
 
-    /** ActivityRecords that are exiting, but still on screen for animations. */
-    final ArrayList<ActivityRecord> mExitingActivities = new ArrayList<>();
-
     /** Detach this stack from its display when animation completes. */
     // TODO: maybe tie this to WindowContainer#removeChild some how...
     private boolean mDeferRemoval;
@@ -366,8 +321,6 @@
 
     Rect mPreAnimationBounds = new Rect();
 
-    private Dimmer mDimmer = new Dimmer(this);
-
     /**
      * For {@link #prepareSurfaces}.
      */
@@ -383,10 +336,6 @@
     /** List for processing through a set of activities */
     private final ArrayList<ActivityRecord> mTmpActivities = new ArrayList<>();
 
-    /** Run all ActivityStacks through this */
-    protected final ActivityStackSupervisor mStackSupervisor;
-    protected final RootWindowContainer mRootWindowContainer;
-
     private boolean mTopActivityOccludesKeyguard;
     private ActivityRecord mTopDismissingKeyguardActivity;
 
@@ -637,52 +586,67 @@
         }
     }
 
-    ActivityStack(DisplayContent display, int stackId, ActivityStackSupervisor supervisor,
-            int activityType) {
-        super(supervisor.mService.mWindowManager);
-        mStackId = stackId;
-        mDockedStackMinimizeThickness =
-                supervisor.mService.mWindowManager.mContext.getResources().getDimensionPixelSize(
-                        com.android.internal.R.dimen.docked_stack_minimize_thickness);
-        EventLogTags.writeWmStackCreated(stackId);
-        mStackSupervisor = supervisor;
-        mAtmService = supervisor.mService;
-        mRootWindowContainer = mAtmService.mRootWindowContainer;
-        mHandler = new ActivityStackHandler(supervisor.mLooper);
-        mRemoteToken = new RemoteToken(this);
-        mCurrentUser = mAtmService.mAmInternal.getCurrentUserId();
-        // Set display id before setting activity and window type to make sure it won't affect
-        // stacks on a wrong display.
-        mDisplayId = display.mDisplayId;
+    ActivityStack(DisplayContent display, int id, ActivityStackSupervisor supervisor,
+            int activityType, ActivityInfo info, Intent intent) {
+        this(supervisor.mService, id, info, intent, null /*voiceSession*/, null /*voiceInteractor*/,
+                null /*taskDescription*/, null /*stack*/);
+
         setActivityType(activityType);
     }
 
-    /**
-     * This should be called when an activity in a child task changes state. This should only
-     * be called from
-     * {@link Task#onActivityStateChanged(ActivityRecord, ActivityState, String)}.
-     * @param record The {@link ActivityRecord} whose state has changed.
-     * @param state The new state.
-     * @param reason The reason for the change.
-     */
-    void onActivityStateChanged(ActivityRecord record, ActivityState state, String reason) {
-        if (record == mResumedActivity && state != RESUMED) {
-            setResumedActivity(null, reason + " - onActivityStateChanged");
-        }
+    ActivityStack(ActivityTaskManagerService atmService, int id, ActivityInfo info, Intent _intent,
+            IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor,
+            ActivityManager.TaskDescription _taskDescription, ActivityStack stack) {
+        this(atmService, id, _intent,  null /*_affinityIntent*/, null /*_affinity*/,
+                null /*_rootAffinity*/, null /*_realActivity*/, null /*_origActivity*/,
+                false /*_rootWasReset*/, false /*_autoRemoveRecents*/, false /*_askedCompatMode*/,
+                UserHandle.getUserId(info.applicationInfo.uid), 0 /*_effectiveUid*/,
+                null /*_lastDescription*/, System.currentTimeMillis(),
+                true /*neverRelinquishIdentity*/,
+                _taskDescription != null ? _taskDescription : new ActivityManager.TaskDescription(),
+                id, INVALID_TASK_ID, INVALID_TASK_ID, 0 /*taskAffiliationColor*/,
+                info.applicationInfo.uid, info.packageName, info.resizeMode,
+                info.supportsPictureInPicture(), false /*_realActivitySuspended*/,
+                false /*userSetupComplete*/, INVALID_MIN_SIZE, INVALID_MIN_SIZE, info,
+                _voiceSession, _voiceInteractor, stack);
+    }
 
-        if (state == RESUMED) {
-            if (DEBUG_STACK) Slog.v(TAG_STACK, "set resumed activity to:" + record + " reason:"
-                    + reason);
-            setResumedActivity(record, reason + " - onActivityStateChanged");
-            if (record == mRootWindowContainer.getTopResumedActivity()) {
-                mAtmService.setResumedActivityUncheckLocked(record, reason);
-            }
-            mStackSupervisor.mRecentTasks.add(record.getTask());
-        }
+    ActivityStack(ActivityTaskManagerService atmService, int id, Intent _intent,
+            Intent _affinityIntent, String _affinity, String _rootAffinity,
+            ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset,
+            boolean _autoRemoveRecents, boolean _askedCompatMode, int _userId, int _effectiveUid,
+            String _lastDescription, long lastTimeMoved, boolean neverRelinquishIdentity,
+            ActivityManager.TaskDescription _lastTaskDescription, int taskAffiliation,
+            int prevTaskId, int nextTaskId, int taskAffiliationColor, int callingUid,
+            String callingPackage, int resizeMode, boolean supportsPictureInPicture,
+            boolean _realActivitySuspended, boolean userSetupComplete, int minWidth, int minHeight,
+            ActivityInfo info, IVoiceInteractionSession _voiceSession,
+            IVoiceInteractor _voiceInteractor, ActivityStack stack) {
+        super(atmService, id, _intent, _affinityIntent, _affinity, _rootAffinity,
+                _realActivity, _origActivity, _rootWasReset, _autoRemoveRecents, _askedCompatMode,
+                _userId, _effectiveUid, _lastDescription, lastTimeMoved, neverRelinquishIdentity,
+                _lastTaskDescription, taskAffiliation, prevTaskId, nextTaskId, taskAffiliationColor,
+                callingUid, callingPackage, resizeMode, supportsPictureInPicture,
+                _realActivitySuspended, userSetupComplete, minWidth, minHeight, info, _voiceSession,
+                _voiceInteractor, stack);
+
+        mDockedStackMinimizeThickness = mWmService.mContext.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.docked_stack_minimize_thickness);
+        EventLogTags.writeWmStackCreated(id);
+        mHandler = new ActivityStackHandler(mStackSupervisor.mLooper);
+        mCurrentUser = mAtmService.mAmInternal.getCurrentUserId();
     }
 
     @Override
     public void onConfigurationChanged(Configuration newParentConfig) {
+        // Calling Task#onConfigurationChanged() for leaf task since the ops in this method are
+        // particularly for ActivityStack, like preventing bounds changes when inheriting certain
+        // windowing mode.
+        if (!isRootTask()) {
+            super.onConfigurationChanged(newParentConfig);
+            return;
+        }
+
         final int prevWindowingMode = getWindowingMode();
         final boolean prevIsAlwaysOnTop = isAlwaysOnTop();
         final int prevRotation = getWindowConfiguration().getRotation();
@@ -764,11 +728,11 @@
                 setBounds(newBounds);
                 newBounds.set(newBounds);
             } else if (overrideWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
-                Rect dockedBounds = display.getSplitScreenPrimaryStack().getBounds();
+                Rect dockedBounds = display.getRootSplitScreenPrimaryTask().getBounds();
                 final boolean isMinimizedDock =
                         display.mDisplayContent.getDockedDividerController().isMinimizedDock();
                 if (isMinimizedDock) {
-                    Task topTask = display.getSplitScreenPrimaryStack().getTopMostTask();
+                    Task topTask = display.getRootSplitScreenPrimaryTask().getTopMostTask();
                     if (topTask != null) {
                         dockedBounds = topTask.getBounds();
                     }
@@ -800,9 +764,25 @@
 
     @Override
     public void setWindowingMode(int windowingMode) {
+        // Calling Task#setWindowingMode() for leaf task since this is the a specialization of
+        // {@link #setWindowingMode(int)} for ActivityStack.
+        if (!isRootTask()) {
+            super.setWindowingMode(windowingMode);
+            return;
+        }
+
         setWindowingMode(windowingMode, false /* animate */, false /* showRecents */,
                 false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */,
                 false /* creating */);
+        windowingMode = getWindowingMode();
+        /*
+         * Different windowing modes may be managed by different task organizers. If
+         * getTaskOrganizer returns null, we still call transferToTaskOrganizer to
+         * make sure we clear it.
+         */
+        final ITaskOrganizer org =
+            mWmService.mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode);
+        transferToTaskOrganizer(org);
     }
 
     /**
@@ -846,7 +826,7 @@
         final int currentOverrideMode = getRequestedOverrideWindowingMode();
         final DisplayContent display = getDisplay();
         final Task topTask = getTopMostTask();
-        final ActivityStack splitScreenStack = display.getSplitScreenPrimaryStack();
+        final ActivityStack splitScreenStack = display.getRootSplitScreenPrimaryTask();
         int windowingMode = preferredWindowingMode;
         if (preferredWindowingMode == WINDOWING_MODE_UNDEFINED
                 && isTransientWindowingMode(currentMode)) {
@@ -867,7 +847,7 @@
             windowingMode = mRestoreOverrideWindowingMode;
         }
 
-        final boolean alreadyInSplitScreenMode = display.hasSplitScreenPrimaryStack();
+        final boolean alreadyInSplitScreenMode = display.hasSplitScreenPrimaryTask();
 
         // Don't send non-resizeable notifications if the windowing mode changed was a side effect
         // of us entering split-screen mode.
@@ -882,8 +862,9 @@
                 // Looks like we can't launch in split screen mode or the stack we are launching
                 // doesn't support split-screen mode, go ahead an dismiss split-screen and display a
                 // warning toast about it.
-                mAtmService.getTaskChangeNotificationController().notifyActivityDismissingDockedStack();
-                final ActivityStack primarySplitStack = display.getSplitScreenPrimaryStack();
+                mAtmService.getTaskChangeNotificationController()
+                        .notifyActivityDismissingDockedStack();
+                final ActivityStack primarySplitStack = display.getRootSplitScreenPrimaryTask();
                 primarySplitStack.setWindowingModeInSurfaceTransaction(WINDOWING_MODE_UNDEFINED,
                         false /* animate */, false /* showRecents */,
                         false /* enteringSplitScreenMode */, true /* deferEnsuringVisibility */,
@@ -961,7 +942,7 @@
                         false /* preserveWindows */, true /* deferResume */);
             }
         } finally {
-            if (showRecents && !alreadyInSplitScreenMode && mDisplayId == DEFAULT_DISPLAY
+            if (showRecents && !alreadyInSplitScreenMode && isOnHomeDisplay()
                     && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                 // Make sure recents stack exist when creating a dock stack as it normally needs to
                 // be on the other side of the docked stack and we make visibility decisions based
@@ -1018,10 +999,6 @@
         return getDisplayContent();
     }
 
-    int getDisplayId() {
-        return mDisplayId;
-    }
-
     /**
      * Defers updating the bounds of the stack. If the stack was resized/repositioned while
      * deferring, the bounds will update in {@link #continueUpdateBounds()}.
@@ -1127,10 +1104,6 @@
         return r.getTask().mTaskId != taskId && r.appToken != notTop && r.canBeTopRunning();
     }
 
-    ActivityRecord getTopNonFinishingActivity() {
-        return getTopActivity(false /*includeFinishing*/, true /*includeOverlays*/);
-    }
-
     ActivityRecord isInStackLocked(IBinder token) {
         final ActivityRecord r = ActivityRecord.forTokenLocked(token);
         return isInStackLocked(r);
@@ -1141,7 +1114,7 @@
             return null;
         }
         final Task task = r.getTask();
-        final ActivityStack stack = r.getActivityStack();
+        final ActivityStack stack = r.getRootTask();
         if (stack != null && task.mChildren.contains(r) && mChildren.contains(task)) {
             if (stack != this) Slog.w(TAG,
                     "Illegal state! task does not point to stack it is in.");
@@ -1161,11 +1134,7 @@
     }
 
     final boolean isOnHomeDisplay() {
-        return mDisplayId == DEFAULT_DISPLAY;
-    }
-
-    private boolean returnsToHomeStack() {
-        return !inMultiWindowMode() && hasChild() && getBottomMostTask().returnsToHomeStack();
+        return getDisplayId() == DEFAULT_DISPLAY;
     }
 
     void moveToFront(String reason) {
@@ -1192,7 +1161,7 @@
             final ActivityStack topFullScreenStack =
                     display.getTopStackInWindowingMode(WINDOWING_MODE_FULLSCREEN);
             if (topFullScreenStack != null) {
-                final ActivityStack primarySplitScreenStack = display.getSplitScreenPrimaryStack();
+                final ActivityStack primarySplitScreenStack = display.getRootSplitScreenPrimaryTask();
                 if (display.getIndexOf(topFullScreenStack)
                         > display.getIndexOf(primarySplitScreenStack)) {
                     primarySplitScreenStack.moveToFront(reason + " splitScreenToTop");
@@ -1237,13 +1206,20 @@
         }
     }
 
+    @Override
     boolean isFocusable() {
+        return super.isFocusable() && !(inSplitScreenPrimaryWindowingMode()
+                && mRootWindowContainer.mIsDockMinimized);
+    }
+
+    boolean isTopActivityFocusable() {
         final ActivityRecord r = topRunningActivity();
-        return mRootWindowContainer.isFocusable(this, r != null && r.isFocusable());
+        return r != null ? r.isFocusable()
+                : (isFocusable() && getWindowConfiguration().canReceiveKeys());
     }
 
     boolean isFocusableAndVisible() {
-        return isFocusable() && shouldBeVisible(null /* starting */);
+        return isTopActivityFocusable() && shouldBeVisible(null /* starting */);
     }
 
     @Override
@@ -1262,11 +1238,11 @@
 
         super.switchUser(userId);
         forAllTasks((t) -> {
-            if (t.mWmService.isCurrentProfile(t.mUserId) || t.showForAllUsers()) {
+            if (t.showToCurrentUser()) {
                 mChildren.remove(t);
                 mChildren.add(t);
             }
-        });
+        }, true /* traverseTopToBottom */, this);
     }
 
     void minimalResumeActivityLocked(ActivityRecord r) {
@@ -1367,7 +1343,7 @@
 
     private boolean containsActivityFromStack(List<ActivityRecord> rs) {
         for (ActivityRecord r : rs) {
-            if (r.getActivityStack() == this) {
+            if (r.getRootTask() == this) {
                 return true;
             }
         }
@@ -1583,7 +1559,7 @@
         // focus). Also if there is an active pinned stack - we always want to notify it about
         // task stack changes, because its positioning may depend on it.
         if (mStackSupervisor.mAppVisibilitiesChangedSinceLastPause
-                || (getDisplay() != null && getDisplay().hasPinnedStack())) {
+                || (getDisplay() != null && getDisplay().hasPinnedTask())) {
             mAtmService.getTaskChangeNotificationController().notifyTaskStackChanged();
             mStackSupervisor.mAppVisibilitiesChangedSinceLastPause = false;
         }
@@ -1591,45 +1567,6 @@
         mRootWindowContainer.ensureActivitiesVisible(resuming, 0, !PRESERVE_WINDOWS);
     }
 
-    /**
-     * Returns true if the stack is translucent and can have other contents visible behind it if
-     * needed. A stack is considered translucent if it don't contain a visible or
-     * starting (about to be visible) activity that is fullscreen (opaque).
-     * @param starting The currently starting activity or null if there is none.
-     */
-    @VisibleForTesting
-    boolean isStackTranslucent(ActivityRecord starting) {
-        if (!isAttached() || mForceHidden) {
-            return true;
-        }
-        final PooledPredicate p = PooledLambda.obtainPredicate(ActivityStack::isOpaqueActivity,
-                PooledLambda.__(ActivityRecord.class), starting);
-        final ActivityRecord opaque = getActivity(p);
-        p.recycle();
-        return opaque == null;
-    }
-
-    private static boolean isOpaqueActivity(ActivityRecord r, ActivityRecord starting) {
-        if (r.finishing) {
-            // We don't factor in finishing activities when determining translucency since
-            // they will be gone soon.
-            return false;
-        }
-
-        if (!r.visibleIgnoringKeyguard && r != starting) {
-            // Also ignore invisible activities that are not the currently starting
-            // activity (about to be visible).
-            return false;
-        }
-
-        if (r.occludesParent() || r.hasWallpaper) {
-            // Stack isn't translucent if it has at least one fullscreen activity
-            // that is visible.
-            return true;
-        }
-        return false;
-    }
-
     boolean isTopStackOnDisplay() {
         final DisplayContent display = getDisplay();
         return display != null && display.isTopStack(this);
@@ -1649,11 +1586,30 @@
         return topActivity != null && topActivity.mVisibleRequested;
     }
 
+    private static void transferSingleTaskToOrganizer(Task tr, ITaskOrganizer organizer) {
+        tr.setTaskOrganizer(organizer);
+    }
+
+    /**
+     * Transfer control of the leashes and IWindowContainers to the given ITaskOrganizer.
+     * This will (or shortly there-after) invoke the taskAppeared callbacks.
+     * If the tasks had a previous TaskOrganizer, setTaskOrganizer will take care of
+     * emitting the taskVanished callbacks.
+     */
+    void transferToTaskOrganizer(ITaskOrganizer organizer) {
+        final PooledConsumer c = PooledLambda.obtainConsumer(
+                ActivityStack::transferSingleTaskToOrganizer,
+                PooledLambda.__(Task.class), organizer);
+        forAllTasks(c, true /* traverseTopToBottom */, this);
+        c.recycle();
+    }
+
     /**
      * Returns true if the stack should be visible.
      *
      * @param starting The currently starting activity or null if there is none.
      */
+    @Override
     boolean shouldBeVisible(ActivityRecord starting) {
         return getVisibility(starting) != STACK_VISIBILITY_INVISIBLE;
     }
@@ -1711,7 +1667,7 @@
                         break;
                     }
                 }
-                if (other.isStackTranslucent(starting)) {
+                if (other.isTranslucent(starting)) {
                     // Can be visible behind a translucent fullscreen stack.
                     gotTranslucentFullscreen = true;
                     continue;
@@ -1720,7 +1676,7 @@
             } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
                     && !gotOpaqueSplitScreenPrimary) {
                 gotSplitScreenStack = true;
-                gotTranslucentSplitScreenPrimary = other.isStackTranslucent(starting);
+                gotTranslucentSplitScreenPrimary = other.isTranslucent(starting);
                 gotOpaqueSplitScreenPrimary = !gotTranslucentSplitScreenPrimary;
                 if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
                         && gotOpaqueSplitScreenPrimary) {
@@ -1730,7 +1686,7 @@
             } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
                     && !gotOpaqueSplitScreenSecondary) {
                 gotSplitScreenStack = true;
-                gotTranslucentSplitScreenSecondary = other.isStackTranslucent(starting);
+                gotTranslucentSplitScreenSecondary = other.isTranslucent(starting);
                 gotOpaqueSplitScreenSecondary = !gotTranslucentSplitScreenSecondary;
                 if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
                         && gotOpaqueSplitScreenSecondary) {
@@ -1831,13 +1787,7 @@
         return inPinnedWindowingMode();
     }
 
-    @Override
-    public boolean supportsSplitScreenWindowingMode() {
-        final Task topTask = getTopMostTask();
-        return super.supportsSplitScreenWindowingMode()
-                && (topTask == null || topTask.supportsSplitScreenWindowingMode());
-    }
-
+    // TODO(NOW!)
     /**
      * Returns {@code true} if this is the top-most split-screen-primary or
      * split-screen-secondary stack, {@code false} otherwise.
@@ -1873,7 +1823,9 @@
      * @return true if {@param r} is visible taken Keyguard state into account, false otherwise
      */
     boolean checkKeyguardVisibility(ActivityRecord r, boolean shouldBeVisible, boolean isTop) {
-        final int displayId = mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY;
+        int displayId = getDisplayId();
+        if (displayId == INVALID_DISPLAY) displayId = DEFAULT_DISPLAY;
+
         final boolean keyguardOrAodShowing = mStackSupervisor.getKeyguardController()
                 .isKeyguardOrAodShowing(displayId);
         final boolean keyguardLocked = mStackSupervisor.getKeyguardController().isKeyguardLocked();
@@ -1917,7 +1869,7 @@
         final DisplayContent displayContent = getDisplay();
         if (displayContent == null) {
             throw new IllegalStateException("Stack is not attached to any display, stackId="
-                    + mStackId);
+                    + getRootTaskId());
         }
 
         final int flags = displayContent.mDisplay.getFlags();
@@ -2039,24 +1991,6 @@
         return result;
     }
 
-    /**
-     * Returns the currently resumed activity.
-     */
-    protected ActivityRecord getResumedActivity() {
-        return mResumedActivity;
-    }
-
-    private void setResumedActivity(ActivityRecord r, String reason) {
-        if (mResumedActivity == r) {
-            return;
-        }
-
-        if (DEBUG_STACK) Slog.d(TAG_STACK, "setResumedActivity stack:" + this + " + from: "
-                + mResumedActivity + " to:" + r + " reason:" + reason);
-        mResumedActivity = r;
-        mStackSupervisor.updateTopResumedActivityIfNeeded();
-    }
-
     @GuardedBy("mService")
     private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {
         if (!mAtmService.isBooting() && !mAtmService.isBooted()) {
@@ -2367,7 +2301,7 @@
                 // result of invisible window resize.
                 // TODO: Remove this once visibilities are set correctly immediately when
                 // starting an activity.
-                notUpdated = !mRootWindowContainer.ensureVisibilityAndConfig(next, mDisplayId,
+                notUpdated = !mRootWindowContainer.ensureVisibilityAndConfig(next, getDisplayId(),
                         true /* markFrozenIfConfigChanged */, false /* deferResume */);
             }
 
@@ -2508,7 +2442,7 @@
         ActivityOptions.abort(options);
         if (DEBUG_STATES) Slog.d(TAG_STATES,
                 "resumeNextFocusableActivityWhenStackIsEmpty: " + reason + ", go home");
-        return mRootWindowContainer.resumeHomeActivity(prev, reason, mDisplayId);
+        return mRootWindowContainer.resumeHomeActivity(prev, reason, getDisplayId());
     }
 
     void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
@@ -2654,7 +2588,7 @@
             return false;
         }
         final ActivityStack targetStack = toFrontTask != null
-                ? toFrontTask.getStack() : toFrontActivity.getActivityStack();
+                ? toFrontTask.getStack() : toFrontActivity.getRootTask();
         if (targetStack != null && targetStack.isActivityTypeAssistant()) {
             // Ensure the task/activity being brought forward is not the assistant
             return false;
@@ -2782,7 +2716,7 @@
     void finishVoiceTask(IVoiceInteractionSession session) {
         final PooledConsumer c = PooledLambda.obtainConsumer(ActivityStack::finishIfVoiceTask,
                 PooledLambda.__(Task.class), session.asBinder());
-        forAllTasks(c);
+        forAllTasks(c, true /* traverseTopToBottom */, this);
         c.recycle();
     }
 
@@ -2975,29 +2909,6 @@
         return foundParentInTask;
     }
 
-    /**
-     * Remove any state associated with the {@link ActivityRecord}. This should be called whenever
-     * an activity moves away from the stack.
-     */
-    void onActivityRemovedFromStack(ActivityRecord r) {
-        r.removeTimeouts();
-
-        mExitingActivities.remove(r);
-
-        if (mResumedActivity != null && mResumedActivity == r) {
-            setResumedActivity(null, "onActivityRemovedFromStack");
-        }
-        if (mPausingActivity != null && mPausingActivity == r) {
-            mPausingActivity = null;
-        }
-    }
-
-    void onActivityAddedToStack(ActivityRecord r) {
-        if (r.isState(RESUMED)) {
-            setResumedActivity(r, "onActivityAddedToStack");
-        }
-    }
-
     void removeLaunchTickMessages() {
         forAllActivities(ActivityRecord::removeLaunchTickRunnable);
     }
@@ -3096,7 +3007,8 @@
                 mRootWindowContainer.resumeFocusedStacksTopActivities();
             }
             EventLogTags.writeWmTaskToFront(tr.mUserId, tr.mTaskId);
-            mAtmService.getTaskChangeNotificationController().notifyTaskMovedToFront(tr.getTaskInfo());
+            mAtmService.getTaskChangeNotificationController()
+                    .notifyTaskMovedToFront(tr.getTaskInfo());
         } finally {
             getDisplay().continueUpdateImeTarget();
         }
@@ -3157,7 +3069,7 @@
         }
 
         ActivityRecord topActivity = getDisplay().topRunningActivity();
-        ActivityStack topStack = topActivity.getActivityStack();
+        ActivityStack topStack = topActivity.getRootTask();
         if (topStack != null && topStack != this && topActivity.isState(RESUMED)) {
             // The new top activity is already resumed, so there's a good chance that nothing will
             // get resumed below. So, update visibility now in case the transition is closed
@@ -3186,7 +3098,7 @@
             return;
         }
 
-        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "stack.resize_" + mStackId);
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "stack.resize_" + getRootTaskId());
         mAtmService.deferWindowLayout();
         try {
             // Update override configurations of all tasks in the stack.
@@ -3194,7 +3106,7 @@
             final PooledConsumer c = PooledLambda.obtainConsumer(
                     ActivityStack::processTaskResizeBounds, PooledLambda.__(Task.class),
                     taskBounds, tempTaskInsetBounds);
-            forAllTasks(c);
+            forAllTasks(c, true /* traverseTopToBottom */, this);
             c.recycle();
 
             setBounds(bounds);
@@ -3231,7 +3143,7 @@
 
         final PooledConsumer c = PooledLambda.obtainConsumer(ActivityStack::setTaskBounds,
                 PooledLambda.__(Task.class), bounds);
-        forAllTasks(c);
+        forAllTasks(c, true /* traverseTopToBottom */, this);
         c.recycle();
     }
 
@@ -3247,7 +3159,7 @@
 
         final PooledConsumer c = PooledLambda.obtainConsumer(ActivityStack::setTaskDisplayedBounds,
                 PooledLambda.__(Task.class), bounds);
-        forAllTasks(c);
+        forAllTasks(c, true /* traverseTopToBottom */, this);
         c.recycle();
     }
 
@@ -3303,7 +3215,7 @@
 
     boolean dump(FileDescriptor fd, PrintWriter pw, boolean dumpAll, boolean dumpClient,
             String dumpPackage, boolean needSep) {
-        pw.println("  Stack #" + mStackId
+        pw.println("  Stack #" + getRootTaskId()
                 + ": type=" + activityTypeToString(getActivityType())
                 + " mode=" + windowingModeToString(getWindowingMode()));
         pw.println("  isSleeping=" + shouldSleepActivities());
@@ -3339,7 +3251,6 @@
 
     private boolean dumpActivities(FileDescriptor fd, PrintWriter pw, boolean dumpAll,
             boolean dumpClient, String dumpPackage, boolean needSep) {
-
         if (!hasChild()) {
             return false;
         }
@@ -3358,11 +3269,11 @@
             final ArrayList<ActivityRecord> activities = new ArrayList<>();
             // Add activities by traversing the hierarchy from bottom to top, since activities
             // are dumped in reverse order in {@link ActivityStackSupervisor#dumpHistoryList()}.
-            forAllActivities((Consumer<ActivityRecord>) activities::add,
+            task.forAllActivities((Consumer<ActivityRecord>) activities::add,
                     false /* traverseTopToBottom */);
             dumpHistoryList(fd, pw, activities, prefix, "Hist", true, !dumpAll, dumpClient,
                     dumpPackage, false, null, task);
-        });
+        }, true /* traverseTopToBottom */, this);
         return true;
     }
 
@@ -3413,49 +3324,9 @@
         }
     }
 
-    /**
-     * Removes the input task from this stack.
-     *
-     * @param child to remove.
-     * @param reason for removal.
-     */
-    void removeChild(WindowContainer child, String reason) {
-        if (!mChildren.contains(child)) {
-            // Not really in this stack anymore...
-            return;
-        }
-
-        final DisplayContent display = getDisplay();
-        if (DEBUG_TASK_MOVEMENT) {
-            Slog.d(TAG_WM, "removeChild: task=" + child + " reason=" + reason);
-        }
-
-        super.removeChild(child);
-
-        EventLogTags.writeWmRemoveTask(((Task) child).mTaskId, mStackId);
-
-        if (display.isSingleTaskInstance()) {
-            mAtmService.notifySingleTaskDisplayEmpty(display.mDisplayId);
-        }
-
-        display.mDisplayContent.setLayoutNeeded();
-
-        if (!hasChild()) {
-            // Stack is now empty...
-          removeIfPossible();
-        }
-    }
-
-    @Override
-    void removeChild(WindowContainer child) {
-        removeChild(child, "removeChild");
-    }
-
-    Task createTask(int taskId, ActivityInfo info, Intent intent,
-            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
-            boolean toTop) {
-        return createTask(taskId, info, intent, voiceSession, voiceInteractor, toTop,
-                null /*activity*/, null /*source*/, null /*options*/);
+    Task createTask(int taskId, ActivityInfo info, Intent intent, boolean toTop) {
+        return createTask(taskId, info, intent, null /*voiceSession*/, null /*voiceInteractor*/,
+                toTop, null /*activity*/, null /*source*/, null /*options*/);
     }
 
     Task createTask(int taskId, ActivityInfo info, Intent intent,
@@ -3466,7 +3337,8 @@
                 mAtmService, taskId, info, intent, voiceSession, voiceInteractor, this);
         // add the task to stack first, mTaskPositioner might need the stack association
         addChild(task, toTop, (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0);
-        final int displayId = mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY;
+        int displayId = getDisplayId();
+        if (displayId == INVALID_DISPLAY) displayId = DEFAULT_DISPLAY;
         final boolean isLockscreenShown = mAtmService.mStackSupervisor.getKeyguardController()
                 .isKeyguardOrAodShowing(displayId);
         if (!mStackSupervisor.getLaunchParamsController()
@@ -3477,14 +3349,25 @@
         return task;
     }
 
-    void addChild(final Task task, final boolean toTop, boolean showForAllUsers) {
+    void addChild(WindowContainer child, final boolean toTop, boolean showForAllUsers) {
         if (isSingleTaskInstance() && hasChild()) {
             throw new IllegalStateException("Can only have one child on stack=" + this);
         }
 
-        // We only want to move the parents to the parents if we are creating this task at the
-        // top of its stack.
-        addChild(task, toTop ? MAX_VALUE : 0, showForAllUsers, toTop /*moveParents*/);
+        Task task = child.asTask();
+        try {
+
+            if (task != null) {
+                task.setForceShowForAllUsers(showForAllUsers);
+            }
+            // We only want to move the parents to the parents if we are creating this task at the
+            // top of its stack.
+            addChild(child, toTop ? MAX_VALUE : 0, toTop /*moveParents*/);
+        } finally {
+            if (task != null) {
+                task.setForceShowForAllUsers(false);
+            }
+        }
     }
 
     void positionChildAt(Task task, int position) {
@@ -3577,6 +3460,15 @@
     void animateResizePinnedStack(Rect toBounds, Rect sourceHintBounds, int animationDuration,
             boolean fromFullscreen) {
         if (!inPinnedWindowingMode()) return;
+
+        /**
+         * TODO(b/146594635): Remove all PIP animation code from WM once SysUI handles animation.
+         * If this PIP Task is controlled by a TaskOrganizer, the animation occurs entirely
+         * on the TaskOrganizer side, so we just hand over the leash without doing any animation.
+         * We have to be careful to not schedule the enter-pip callback as the TaskOrganizer
+         * needs to have flexibility to schedule that at an appropriate point in the animation.
+         */
+        if (isControlledByTaskOrganizer()) return;
         if (toBounds == null /* toFullscreen */) {
             final Configuration parentConfig = getParent().getConfiguration();
             final ActivityRecord top = topRunningNonOverlayTaskActivity();
@@ -3693,16 +3585,12 @@
         final PooledConsumer c = PooledLambda.obtainConsumer(
                 ActivityStackSupervisor::updatePictureInPictureMode, mStackSupervisor,
                 PooledLambda.__(Task.class), targetStackBounds, forceUpdate);
-        forAllTasks(c);
+        forAllTasks(c, true /* traverseTopToBottom */, this);
         c.recycle();
     }
 
-    public int getStackId() {
-        return mStackId;
-    }
-
     void prepareFreezingTaskBounds() {
-        forAllTasks(Task::prepareFreezingBounds);
+        forAllTasks(Task::prepareFreezingBounds, true /* traverseTopToBottom */, this);
     }
 
     /**
@@ -3734,7 +3622,7 @@
             final PooledConsumer c = PooledLambda.obtainConsumer(Task::alignToAdjustedBounds,
                     PooledLambda.__(Task.class), adjusted ? mAdjustedBounds : getRawBounds(),
                     insetBounds, alignBottom);
-            forAllTasks(c);
+            forAllTasks(c, true /* traverseTopToBottom */, this);
             c.recycle();
         }
 
@@ -3744,7 +3632,13 @@
 
     @Override
     public int setBounds(Rect bounds) {
-        return setBounds(getRequestedOverrideBounds(), bounds);
+        // Calling Task#setBounds() for leaf task since this is the a specialization of
+        // {@link #setBounds(int)} for ActivityStack.
+        if (!isRootTask()) {
+            return super.setBounds(bounds);
+        } else {
+            return setBounds(getRequestedOverrideBounds(), bounds);
+        }
     }
 
     private int setBounds(Rect existing, Rect bounds) {
@@ -3984,30 +3878,15 @@
      * Put a Task in this stack. Used for adding only.
      * When task is added to top of the stack, the entire branch of the hierarchy (including stack
      * and display) will be brought to top.
-     * @param task The task to add.
+     * @param child The child to add.
      * @param position Target position to add the task to.
-     * @param showForAllUsers Whether to show the task regardless of the current user.
      */
-    private void addChild(Task task, int position, boolean showForAllUsers, boolean moveParents) {
-        try {
-            // Force show for all user so task can be position correctly based on which user is
-            // active. We clear the force show below.
-            task.setForceShowForAllUsers(showForAllUsers);
-            // Add child task.
-            addChild(task, null);
+    private void addChild(WindowContainer child, int position, boolean moveParents) {
+        // Add child task.
+        addChild(child, null);
 
-            // Move child to a proper position, as some restriction for position might apply.
-            positionChildAt(position, task, moveParents /* includingParents */);
-
-        } finally {
-            task.setForceShowForAllUsers(false);
-        }
-    }
-
-    @Override
-    void addChild(WindowContainer child, int position) {
-        final Task task = (Task) child;
-        addChild(task, position, task.showForAllUsers(), false /* includingParents */);
+        // Move child to a proper position, as some restriction for position might apply.
+        positionChildAt(position, child, moveParents /* includingParents */);
     }
 
     void positionChildAtTop(Task child) {
@@ -4045,29 +3924,18 @@
     }
 
     @Override
-    void positionChildAt(int position, WindowContainer child, boolean includingParents) {
-        final Task task = (Task) child;
-        final int targetPosition = findPositionForTask(task, position);
-        super.positionChildAt(targetPosition, child, includingParents);
-
-        // Log positioning.
-        if (DEBUG_TASK_MOVEMENT) {
-            Slog.d(TAG_WM, "positionTask: task=" + this + " position=" + position);
-        }
-
-        final int toTop = targetPosition == mChildren.size() - 1 ? 1 : 0;
-        EventLogTags.writeWmTaskMoved(task.mTaskId, toTop, targetPosition);
-    }
-
-    @Override
     void onChildPositionChanged(WindowContainer child) {
         if (!mChildren.contains(child)) {
             return;
         }
 
-        final Task task = (Task) child;
-        final boolean isTop = getTopChild() == task;
-        task.updateTaskMovement(isTop);
+        final boolean isTop = getTopChild() == child;
+
+        final Task task = child.asTask();
+        if (task != null) {
+            task.updateTaskMovement(isTop);
+        }
+
         if (isTop) {
             final DisplayContent displayContent = getDisplayContent();
             displayContent.layoutAndAssignWindowLayersIfNeeded();
@@ -4075,34 +3943,17 @@
     }
 
     @Override
-    protected void onParentChanged(
-            ConfigurationContainer newParent, ConfigurationContainer oldParent) {
+    void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) {
         final DisplayContent display = newParent != null
                 ? ((WindowContainer) newParent).getDisplayContent() : null;
         final DisplayContent oldDisplay = oldParent != null
                 ? ((WindowContainer) oldParent).getDisplayContent() : null;
 
-        mDisplayId = (display != null) ? display.mDisplayId : INVALID_DISPLAY;
-        mPrevDisplayId = (oldDisplay != null) ? oldDisplay.mDisplayId : INVALID_DISPLAY;
-
-        if (display != null) {
-            // Rotations are relative to the display. This means if there are 2 displays rotated
-            // differently (eg. 2 monitors with one landscape and one portrait), moving a stack
-            // from one to the other could look like a rotation change. To prevent this
-            // apparent rotation change (and corresponding bounds rotation), pretend like our
-            // current rotation is already the same as the new display.
-            // Note, if ActivityStack or related logic ever gets nested, this logic will need
-            // to move to onConfigurationChanged.
-            getConfiguration().windowConfiguration.setRotation(
-                    display.getWindowConfiguration().getRotation());
-        }
         super.onParentChanged(newParent, oldParent);
-        if (getParent() == null && mDisplayContent != null) {
-            EventLogTags.writeWmStackRemoved(mStackId);
-            mDisplayContent = null;
-            mWmService.mWindowPlacerLocked.requestTraversal();
-        }
-        if (display != null && inSplitScreenPrimaryWindowingMode()) {
+
+        if (display != null && inSplitScreenPrimaryWindowingMode()
+                // only do this for the base stack
+                && !newParent.inSplitScreenPrimaryWindowingMode()) {
             // If we created a docked stack we want to resize it so it resizes all other stacks
             // in the system.
             getStackDockedModeBounds(null /* dockedBounds */, null /* currentTempTaskBounds */,
@@ -4110,7 +3961,6 @@
             mStackSupervisor.resizeDockedStackLocked(getRequestedOverrideBounds(), mTmpRect,
                     mTmpRect2, null, null, PRESERVE_WINDOWS);
         }
-        mRootWindowContainer.updateUIDsPresentOnDisplay();
 
         // Resume next focusable stack after reparenting to another display if we aren't removing
         // the prevous display.
@@ -4124,68 +3974,6 @@
         newParent.moveStackToDisplay(this, onTop);
     }
 
-    // TODO: We should really have users as a window container in the hierarchy so that we don't
-    // have to do complicated things like we are doing in this method.
-    int findPositionForTask(Task task, int targetPosition) {
-        final boolean canShowTask = task.showToCurrentUser();
-
-        final int stackSize = mChildren.size();
-        int minPosition = 0;
-        int maxPosition = stackSize - 1;
-
-        if (canShowTask) {
-            minPosition = computeMinPosition(minPosition, stackSize);
-        } else {
-            maxPosition = computeMaxPosition(maxPosition);
-        }
-
-        // preserve POSITION_BOTTOM/POSITION_TOP positions if they are still valid.
-        if (targetPosition == POSITION_BOTTOM && minPosition == 0) {
-            return POSITION_BOTTOM;
-        } else if (targetPosition == POSITION_TOP && maxPosition == (stackSize - 1)) {
-            return POSITION_TOP;
-        }
-        // Reset position based on minimum/maximum possible positions.
-        return Math.min(Math.max(targetPosition, minPosition), maxPosition);
-    }
-
-    /** Calculate the minimum possible position for a task that can be shown to the user.
-     *  The minimum position will be above all other tasks that can't be shown.
-     *  @param minPosition The minimum position the caller is suggesting.
-     *                  We will start adjusting up from here.
-     *  @param size The size of the current task list.
-     */
-    // TODO(task-hierarchy): Move user to their own window container.
-    private int computeMinPosition(int minPosition, int size) {
-        while (minPosition < size) {
-            final Task tmpTask = (Task) mChildren.get(minPosition);
-            final boolean canShowTmpTask = tmpTask.showToCurrentUser();
-            if (canShowTmpTask) {
-                break;
-            }
-            minPosition++;
-        }
-        return minPosition;
-    }
-
-    /** Calculate the maximum possible position for a task that can't be shown to the user.
-     *  The maximum position will be below all other tasks that can be shown.
-     *  @param maxPosition The maximum position the caller is suggesting.
-     *                  We will start adjusting down from here.
-     */
-    // TODO(task-hierarchy): Move user to their own window container.
-    private int computeMaxPosition(int maxPosition) {
-        while (maxPosition > 0) {
-            final Task tmpTask = (Task) mChildren.get(maxPosition);
-            final boolean canShowTmpTask = tmpTask.showToCurrentUser();
-            if (!canShowTmpTask) {
-                break;
-            }
-            maxPosition--;
-        }
-        return maxPosition;
-    }
-
     private void updateSurfaceBounds() {
         updateSurfaceSize(getPendingTransaction());
         updateSurfacePosition();
@@ -4402,15 +4190,6 @@
                 false /* deferResume */);
     }
 
-    @Override
-    void removeIfPossible() {
-        if (isAnimating(TRANSITION | CHILDREN)) {
-            mDeferRemoval = true;
-            return;
-        }
-        removeImmediately();
-    }
-
     /**
      * Adjusts the stack bounds if the IME is visible.
      *
@@ -4514,12 +4293,14 @@
                 t.setDragResizing(true, DRAG_RESIZE_MODE_DOCKED_DIVIDER);
                 t.setWaitingForDrawnIfResizingChanged();
             }
-        });
+        }, true /* traverseTopToBottom */, this);
     }
 
     /** Resets the resizing state of all windows. */
     void endImeAdjustAnimation() {
-        forAllTasks((t) -> { t.setDragResizing(false, DRAG_RESIZE_MODE_DOCKED_DIVIDER); });
+        forAllTasks((t) -> {
+            t.setDragResizing(false, DRAG_RESIZE_MODE_DOCKED_DIVIDER);
+        }, true /* traverseTopToBottom */, this);
     }
 
     private int getMinTopStackBottom(final Rect displayContentRect, int originalStackBottom) {
@@ -4697,17 +4478,9 @@
         return mMinimizeAmount != 0f;
     }
 
-    /**
-     * @return {@code true} if we have a {@link Task} that is animating (currently only used for the
-     *         recents animation); {@code false} otherwise.
-     */
-    boolean isTaskAnimating() {
-        return getTask(Task::isTaskAnimating) != null;
-    }
-
     @Override
     void dump(PrintWriter pw, String prefix, boolean dumpAll) {
-        pw.println(prefix + "mStackId=" + mStackId);
+        pw.println(prefix + "mStackId=" + getRootTaskId());
         pw.println(prefix + "mDeferRemoval=" + mDeferRemoval);
         pw.println(prefix + "mBounds=" + getRawBounds().toShortString());
         if (mMinimizeAmount != 0f) {
@@ -4738,19 +4511,6 @@
         mAnimatingActivityRegistry.dump(pw, "AnimatingApps:", prefix);
     }
 
-    @Override
-    boolean fillsParent() {
-        return matchParentBounds();
-    }
-
-    String getName() {
-        return toShortString();
-    }
-
-    public String toShortString() {
-        return "Stack=" + mStackId;
-    }
-
     /**
      * For docked workspace (or workspace that's side-by-side to the docked), provides
      * information which side of the screen was the dock anchored.
@@ -4892,18 +4652,6 @@
         }
     }
 
-    @Override
-    protected void onAnimationFinished() {
-        super.onAnimationFinished();
-        // TODO(b/142617871): we may need to add animation type parameter on onAnimationFinished to
-        //  identify if the callback is for launch animation finish and then calling
-        //  activity#onAnimationFinished.
-        final ActivityRecord activity = getTopMostActivity();
-        if (activity != null) {
-            activity.onAnimationFinished();
-        }
-    }
-
     /**
      * Sets the current picture-in-picture aspect ratio.
      */
@@ -4954,7 +4702,7 @@
     /** Called immediately prior to resizing the tasks at the end of the pinned stack animation. */
     void onPipAnimationEndResize() {
         mBoundsAnimating = false;
-        forAllTasks(Task::clearPreserveNonFloatingState, false);
+        forAllTasks(Task::clearPreserveNonFloatingState, false /* traverseTopToBottom */, this);
         mWmService.requestTraversal();
     }
 
@@ -4971,7 +4719,7 @@
             // activity early for the recents animation prior to the PiP animation starting, there
             // is no subsequent all-drawn signal. In this case, we can skip the pause when the home
             // stack is already visible and drawn.
-            final ActivityStack homeStack = mDisplayContent.getHomeStack();
+            final ActivityStack homeStack = mDisplayContent.getRootHomeTask();
             if (homeStack == null) {
                 return true;
             }
@@ -5054,24 +4802,6 @@
     }
 
     @Override
-    Dimmer getDimmer() {
-        return mDimmer;
-    }
-
-    @Override
-    void prepareSurfaces() {
-        mDimmer.resetDimStates();
-        super.prepareSurfaces();
-        getDimBounds(mTmpDimBoundsRect);
-
-        // Bounds need to be relative, as the dim layer is a child.
-        mTmpDimBoundsRect.offsetTo(0, 0);
-        if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
-            scheduleAnimation();
-        }
-    }
-
-    @Override
     public boolean setPinnedStackAlpha(float alpha) {
         // Hold the lock since this is called from the BoundsAnimator running on the UiThread
         synchronized (mWmService.mGlobalLock) {
@@ -5090,49 +4820,10 @@
         return mDisplayContent.getDisplayInfo();
     }
 
-    void dim(float alpha) {
-        mDimmer.dimAbove(getPendingTransaction(), alpha);
-        scheduleAnimation();
-    }
-
-    void stopDimming() {
-        mDimmer.stopDim(getPendingTransaction());
-        scheduleAnimation();
-    }
-
     AnimatingActivityRegistry getAnimatingActivityRegistry() {
         return mAnimatingActivityRegistry;
     }
 
-    @Override
-    void getAnimationFrames(Rect outFrame, Rect outInsets, Rect outStableInsets,
-            Rect outSurfaceInsets) {
-        final Task task = getTopMostTask();
-        if (task != null) {
-            task.getAnimationFrames(outFrame, outInsets, outStableInsets, outSurfaceInsets);
-        } else {
-            super.getAnimationFrames(outFrame, outInsets, outStableInsets, outSurfaceInsets);
-        }
-    }
-
-    @Override
-    RemoteAnimationTarget createRemoteAnimationTarget(
-            RemoteAnimationController.RemoteAnimationRecord record) {
-        final Task task = getTopMostTask();
-        return task != null ? task.createRemoteAnimationTarget(record) : null;
-    }
-
-    @Override
-    public String toString() {
-        return "ActivityStack{" + Integer.toHexString(System.identityHashCode(this))
-                + " stackId=" + mStackId + " type=" + activityTypeToString(getActivityType())
-                + " mode=" + windowingModeToString(getWindowingMode())
-                + " visible=" + shouldBeVisible(null /* starting */)
-                + " translucent=" + isStackTranslucent(null /* starting */)
-                + ", "
-                + getChildCount() + " tasks}";
-    }
-
     void executeAppTransition(ActivityOptions options) {
         getDisplay().mDisplayContent.executeAppTransition();
         ActivityOptions.abort(options);
@@ -5155,19 +4846,20 @@
         return shouldSleepActivities() || mAtmService.mShuttingDown;
     }
 
+    @Override
     public void dumpDebug(ProtoOutputStream proto, long fieldId,
             @WindowTraceLogLevel int logLevel) {
         final long token = proto.start(fieldId);
         dumpDebugInnerStackOnly(proto, STACK, logLevel);
-        proto.write(com.android.server.am.ActivityStackProto.ID, mStackId);
+        proto.write(com.android.server.am.ActivityStackProto.ID, getRootTaskId());
 
         forAllTasks((t) -> {
-            t.dumpDebug(proto, com.android.server.am.ActivityStackProto.TASKS, logLevel);
-        });
+            t.dumpDebugInner(proto, com.android.server.am.ActivityStackProto.TASKS, logLevel);
+        }, true /* traverseTopToBottom */, this);
         if (mResumedActivity != null) {
             mResumedActivity.writeIdentifierToProto(proto, RESUMED_ACTIVITY);
         }
-        proto.write(DISPLAY_ID, mDisplayId);
+        proto.write(DISPLAY_ID, getDisplayId());
         if (!matchParentBounds()) {
             final Rect bounds = getRequestedOverrideBounds();
             bounds.dumpDebug(proto, com.android.server.am.ActivityStackProto.BOUNDS);
@@ -5187,8 +4879,10 @@
 
         final long token = proto.start(fieldId);
         super.dumpDebug(proto, WINDOW_CONTAINER, logLevel);
-        proto.write(StackProto.ID, mStackId);
-        forAllTasks((t) -> { t.dumpDebugInnerTaskOnly(proto, StackProto.TASKS, logLevel); });
+        proto.write(StackProto.ID, getRootTaskId());
+        forAllTasks((t) -> {
+            t.dumpDebugInnerTaskOnly(proto, StackProto.TASKS, logLevel);
+        }, true /* traverseTopToBottom */, this);
         proto.write(FILLS_PARENT, matchParentBounds());
         getRawBounds().dumpDebug(proto, StackProto.BOUNDS);
         proto.write(DEFER_REMOVAL, mDeferRemoval);
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index fd871bd..2c0f3e6 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -83,6 +83,7 @@
 import static com.android.server.wm.Task.REPARENT_KEEP_STACK_AT_FRONT;
 import static com.android.server.wm.Task.REPARENT_LEAVE_STACK_IN_PLACE;
 import static com.android.server.wm.Task.REPARENT_MOVE_STACK_TO_FRONT;
+import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
@@ -418,7 +419,7 @@
 
             final PooledConsumer c = PooledLambda.obtainConsumer(
                     MoveTaskToFullscreenHelper::processTask, this, PooledLambda.__(Task.class));
-            fromStack.forAllTasks(c, false);
+            fromStack.forAllTasks(c, false /* traverseTopToBottom */, fromStack);
             c.recycle();
             mToDisplay = null;
             mTopTask = null;
@@ -823,7 +824,7 @@
                         false /* markFrozenIfConfigChanged */, true /* deferResume */);
             }
 
-            if (r.getActivityStack().checkKeyguardVisibility(r, true /* shouldBeVisible */,
+            if (r.getRootTask().checkKeyguardVisibility(r, true /* shouldBeVisible */,
                     true /* isTop */) && r.allowMoveToFront()) {
                 // We only set the visibility to true if the activity is not being launched in
                 // background, and is allowed to be visible based on keyguard state. This avoids
@@ -847,8 +848,6 @@
 
             if (DEBUG_ALL) Slog.v(TAG, "Launching: " + r);
 
-            proc.addActivityIfNeeded(r);
-
             final LockTaskController lockTaskController = mService.getLockTaskController();
             if (task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE
                     || task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE_PRIV
@@ -947,7 +946,7 @@
                     // This is the second time we failed -- finish activity and give up.
                     Slog.e(TAG, "Second failure launching "
                             + r.intent.getComponent().flattenToShortString() + ", giving up", e);
-                    proc.appDied();
+                    proc.appDied("2nd-crash");
                     r.finishIfPossible("2nd-crash", false /* oomAdj */);
                     return false;
                 }
@@ -1583,7 +1582,7 @@
         }
 
         final ActivityStack stack =
-                mRootWindowContainer.getDefaultDisplay().getSplitScreenPrimaryStack();
+                mRootWindowContainer.getDefaultDisplay().getRootSplitScreenPrimaryTask();
         if (stack == null) {
             Slog.w(TAG, "resizeDockedStackLocked: docked stack not found");
             return;
@@ -1671,7 +1670,7 @@
     void resizePinnedStackLocked(Rect pinnedBounds, Rect tempPinnedTaskBounds) {
         // TODO(multi-display): The display containing the stack should be passed in.
         final ActivityStack stack =
-                mRootWindowContainer.getDefaultDisplay().getPinnedStack();
+                mRootWindowContainer.getDefaultDisplay().getRootPinnedTask();
         if (stack == null) {
             Slog.w(TAG, "resizePinnedStackLocked: pinned stack not found");
             return;
@@ -1725,7 +1724,7 @@
         } else {
             final PooledConsumer c = PooledLambda.obtainConsumer(
                     ActivityStackSupervisor::processRemoveTask, this, PooledLambda.__(Task.class));
-            stack.forAllTasks(c);
+            stack.forAllTasks(c, true /* traverseTopToBottom */, stack);
             c.recycle();
         }
     }
@@ -1850,14 +1849,14 @@
     boolean restoreRecentTaskLocked(Task task, ActivityOptions aOptions, boolean onTop) {
         final ActivityStack stack =
                 mRootWindowContainer.getLaunchStack(null, aOptions, task, onTop);
-        final ActivityStack currentStack = task.getStack();
+        final WindowContainer parent = task.getParent();
 
-        if (currentStack == stack) {
+        if (parent == stack) {
             // Nothing else to do since it is already restored in the right stack.
             return true;
         }
 
-        if (currentStack != null) {
+        if (parent != null) {
             // Task has already been restored once. Just re-parent it to the new stack.
             task.reparent(stack, POSITION_TOP, true /*moveParents*/, "restoreRecentTaskLocked");
             return true;
@@ -1892,13 +1891,13 @@
     // TODO: Look into changing users to this method to DisplayContent.resolveWindowingMode()
     ActivityStack getReparentTargetStack(Task task, ActivityStack stack, boolean toTop) {
         final ActivityStack prevStack = task.getStack();
-        final int stackId = stack.mStackId;
+        final int rootTaskId = stack.mTaskId;
         final boolean inMultiWindowMode = stack.inMultiWindowMode();
 
         // Check that we aren't reparenting to the same stack that the task is already in
-        if (prevStack != null && prevStack.mStackId == stackId) {
+        if (prevStack != null && prevStack.mTaskId == rootTaskId) {
             Slog.w(TAG, "Can not reparent to same stack, task=" + task
-                    + " already in stackId=" + stackId);
+                    + " already in stackId=" + rootTaskId);
             return prevStack;
         }
 
@@ -1913,7 +1912,7 @@
         // multi-display.
         if (stack.getDisplayId() != DEFAULT_DISPLAY && !mService.mSupportsMultiDisplay) {
             throw new IllegalArgumentException("Device doesn't support multi-display, can not"
-                    + " reparent task=" + task + " to stackId=" + stackId);
+                    + " reparent task=" + task + " to stackId=" + rootTaskId);
         }
 
         // Ensure that we aren't trying to move into a freeform stack without freeform support
@@ -1994,7 +1993,7 @@
 
     void activitySleptLocked(ActivityRecord r) {
         mGoingToSleepActivities.remove(r);
-        final ActivityStack s = r.getActivityStack();
+        final ActivityStack s = r.getRootTask();
         if (s != null) {
             s.checkReadyForSleep();
         } else {
@@ -2030,7 +2029,7 @@
         // A resumed activity cannot be stopping. remove from list
         mStoppingActivities.remove(r);
 
-        final ActivityStack stack = r.getActivityStack();
+        final ActivityStack stack = r.getRootTask();
         if (stack.getDisplay().allResumedActivitiesComplete()) {
             mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
             // Make sure activity & window visibility should be identical
@@ -2079,11 +2078,11 @@
         ArrayList<ActivityRecord> readyToStopActivities = null;
         for (int i = mStoppingActivities.size() - 1; i >= 0; --i) {
             final ActivityRecord s = mStoppingActivities.get(i);
-            final boolean animating = s.isAnimating(TRANSITION);
+            final boolean animating = s.isAnimating(TRANSITION | PARENTS);
             if (DEBUG_STATES) Slog.v(TAG, "Stopping " + s + ": nowVisible=" + s.nowVisible
                     + " animating=" + animating + " finishing=" + s.finishing);
 
-            final ActivityStack stack = s.getActivityStack();
+            final ActivityStack stack = s.getRootTask();
             final boolean shouldSleepOrShutDown = stack != null
                     ? stack.shouldSleepOrShutDownActivities()
                     : mService.isSleepingOrShuttingDownLocked();
@@ -2396,7 +2395,7 @@
         final boolean isSecondaryDisplayPreferred =
                 (preferredDisplayId != DEFAULT_DISPLAY && preferredDisplayId != INVALID_DISPLAY);
         final boolean inSplitScreenMode = actualStack != null
-                && actualStack.getDisplay().hasSplitScreenPrimaryStack();
+                && actualStack.getDisplay().hasSplitScreenPrimaryTask();
         if (((!inSplitScreenMode && preferredWindowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
                 && !isSecondaryDisplayPreferred) || !task.isActivityTypeStandardOrUndefined()) {
             return;
@@ -2445,7 +2444,7 @@
             // we need to move it to top of fullscreen stack, otherwise it will be covered.
 
             final ActivityStack dockedStack =
-                    task.getStack().getDisplay().getSplitScreenPrimaryStack();
+                    task.getStack().getDisplay().getRootSplitScreenPrimaryTask();
             if (dockedStack != null) {
                 // Display a warning toast that we tried to put an app that doesn't support
                 // split-screen in split-screen.
@@ -2474,7 +2473,7 @@
         final ActivityRecord r = ActivityRecord.isInStackLocked(token);
         if (r != null) {
             r.finishRelaunching();
-            if (r.getActivityStack().shouldSleepOrShutDownActivities()) {
+            if (r.getRootTask().shouldSleepOrShutDownActivities()) {
                 r.setSleeping(true, true);
             }
         }
@@ -2547,6 +2546,7 @@
         final PooledConsumer c = PooledLambda.obtainConsumer(
                 ActivityRecord::updatePictureInPictureMode,
                 PooledLambda.__(ActivityRecord.class), targetStackBounds, forceUpdate);
+        task.getStack().setBounds(targetStackBounds);
         task.forAllActivities(c);
         c.recycle();
     }
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index df97caa..d61d29d 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -52,6 +52,7 @@
 import android.os.UserManager;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.BlockedAppActivity;
 import com.android.internal.app.HarmfulAppWarningActivity;
 import com.android.internal.app.SuspendedAppActivity;
 import com.android.internal.app.UnlaunchableAppActivity;
@@ -166,6 +167,9 @@
             // no user action can undo this.
             return true;
         }
+        if (interceptLockTaskModeViolationPackageIfNeeded()) {
+            return true;
+        }
         if (interceptHarmfulAppIfNeeded()) {
             // If the app has a "harmful app" warning associated with it, we should ask to uninstall
             // before issuing the work challenge.
@@ -262,6 +266,25 @@
         return true;
     }
 
+    private boolean interceptLockTaskModeViolationPackageIfNeeded() {
+        if (mAInfo == null || mAInfo.applicationInfo == null) {
+            return false;
+        }
+        LockTaskController controller = mService.getLockTaskController();
+        String packageName = mAInfo.applicationInfo.packageName;
+        int lockTaskLaunchMode = ActivityRecord.getLockTaskLaunchMode(mAInfo, mActivityOptions);
+        if (controller.isActivityAllowed(mUserId, packageName, lockTaskLaunchMode)) {
+            return false;
+        }
+        mIntent = BlockedAppActivity.createIntent(mUserId, mAInfo.applicationInfo.packageName);
+        mCallingPid = mRealCallingPid;
+        mCallingUid = mRealCallingUid;
+        mResolvedType = null;
+        mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, mUserId, 0, mRealCallingUid);
+        mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null /*profilerInfo*/);
+        return true;
+    }
+
     private boolean interceptWorkProfileChallengeIfNeeded() {
         final Intent interceptingIntent = interceptWithConfirmCredentialsIfNeeded(mAInfo, mUserId);
         if (interceptingIntent == null) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 61ba15c..26812f4 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -937,7 +937,7 @@
         }
 
         final ActivityStack resultStack = resultRecord == null
-                ? null : resultRecord.getActivityStack();
+                ? null : resultRecord.getRootTask();
 
         if (err != START_SUCCESS) {
             if (resultRecord != null) {
@@ -1365,7 +1365,7 @@
                     break;
                 case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY:
                     final ActivityStack homeStack =
-                            startedActivityStack.getDisplay().getHomeStack();
+                            startedActivityStack.getDisplay().getRootHomeTask();
                     if (homeStack != null && homeStack.shouldBeVisible(null /* starting */)) {
                         mService.mWindowManager.showRecentApps();
                     }
@@ -1425,7 +1425,7 @@
      * @return the stack where the successful started activity resides.
      */
     private @Nullable ActivityStack handleStartResult(@NonNull ActivityRecord started, int result) {
-        final ActivityStack currentStack = started.getActivityStack();
+        final ActivityStack currentStack = started.getRootTask();
         ActivityStack startedActivityStack = currentStack != null ? currentStack : mTargetStack;
 
         if (ActivityManager.isStartResultSuccessful(result)) {
@@ -1446,7 +1446,7 @@
         // If we are not able to proceed, disassociate the activity from the task. Leaving an
         // activity in an incomplete state can lead to issues, such as performing operations
         // without a window container.
-        final ActivityStack stack = mStartActivity.getActivityStack();
+        final ActivityStack stack = mStartActivity.getRootTask();
         if (stack != null) {
             mStartActivity.finishIfPossible("startActivity", true /* oomAdj */);
         }
@@ -1569,7 +1569,7 @@
         if (mDoResume) {
             final ActivityRecord topTaskActivity =
                     mStartActivity.getTask().topRunningActivityLocked();
-            if (!mTargetStack.isFocusable()
+            if (!mTargetStack.isTopActivityFocusable()
                     || (topTaskActivity != null && topTaskActivity.isTaskOverlay()
                     && mStartActivity != topTaskActivity)) {
                 // If the activity is not focusable, we can't resume it, but still would like to
@@ -1588,7 +1588,7 @@
                 // will not update the focused stack.  If starting the new activity now allows the
                 // task stack to be focusable, then ensure that we now update the focused stack
                 // accordingly.
-                if (mTargetStack.isFocusable()
+                if (mTargetStack.isTopActivityFocusable()
                         && !mRootWindowContainer.isTopDisplayFocusedStack(mTargetStack)) {
                     mTargetStack.moveToFront("startActivityInner");
                 }
@@ -2231,7 +2231,7 @@
             return;
         }
         if (!mSourceRecord.finishing) {
-            mSourceStack = mSourceRecord.getActivityStack();
+            mSourceStack = mSourceRecord.getRootTask();
             return;
         }
 
@@ -2314,7 +2314,7 @@
      * @return {@link ActivityRecord} brought to front.
      */
     private void setTargetStackIfNeeded(ActivityRecord intentActivity) {
-        mTargetStack = intentActivity.getActivityStack();
+        mTargetStack = intentActivity.getRootTask();
         mTargetStack.mLastPausedActivity = null;
         // If the target task is not in the front, then we need to bring it to the front...
         // except...  well, with SINGLE_TASK_LAUNCH it's not entirely clear. We'd like to have
@@ -2371,7 +2371,7 @@
         }
         // Need to update mTargetStack because if task was moved out of it, the original stack may
         // be destroyed.
-        mTargetStack = intentActivity.getActivityStack();
+        mTargetStack = intentActivity.getRootTask();
         mSupervisor.handleNonResizableTaskIfNeeded(intentActivity.getTask(),
                 WINDOWING_MODE_UNDEFINED, DEFAULT_DISPLAY, mTargetStack);
     }
@@ -2422,10 +2422,11 @@
             return;
         }
 
-        final ActivityStack stack = task.getStack();
-        if (stack != null && stack.inPinnedWindowingMode()) {
-            mService.animateResizePinnedStack(stack.mStackId, bounds, -1);
+        final Task rootTask = task.getRootTask();
+        if (rootTask != null && rootTask.inPinnedWindowingMode()) {
+            mService.animateResizePinnedStack(rootTask.mTaskId, bounds, -1);
         } else {
+            // TODO: I don't believe it is possible to reach this else condition anymore...
             task.setBounds(bounds);
         }
     }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 76c0e4e..31b7c68 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -28,7 +28,7 @@
 import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
 import static android.Manifest.permission.STOP_APP_SWITCHES;
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
-import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
+import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.ActivityTaskManager.RESIZE_MODE_PRESERVE_WINDOW;
 import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
@@ -226,6 +226,7 @@
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 import android.view.IRecentsAnimationRunner;
+import android.view.ITaskOrganizer;
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationDefinition;
 import android.view.WindowContainerTransaction;
@@ -247,7 +248,6 @@
 import com.android.internal.policy.KeyguardDismissCallback;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastPrintWriter;
-import com.android.internal.util.Preconditions;
 import com.android.internal.util.function.pooled.PooledConsumer;
 import com.android.internal.util.function.pooled.PooledFunction;
 import com.android.internal.util.function.pooled.PooledLambda;
@@ -306,7 +306,7 @@
  */
 public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityTaskManagerService" : TAG_ATM;
-    private static final String TAG_STACK = TAG + POSTFIX_STACK;
+    static final String TAG_STACK = TAG + POSTFIX_STACK;
     static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
     private static final String TAG_IMMERSIVE = TAG + POSTFIX_IMMERSIVE;
     private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS;
@@ -344,6 +344,10 @@
     /** This activity is being relaunched due to a free-resize operation. */
     public static final int RELAUNCH_REASON_FREE_RESIZE = 2;
 
+    /** Flag indicating that an applied transaction may have effected lifecycle */
+    private static final int TRANSACT_EFFECTS_CLIENT_CONFIG = 1;
+    private static final int TRANSACT_EFFECTS_LIFECYCLE = 1 << 1;
+
     Context mContext;
 
     /**
@@ -662,6 +666,12 @@
 
     private FontScaleSettingObserver mFontScaleSettingObserver;
 
+    /**
+     * Stores the registration and state of TaskOrganizers in use.
+     */
+    TaskOrganizerController mTaskOrganizerController =
+        new TaskOrganizerController(this, mGlobalLock);
+
     private int mDeviceOwnerUid = Process.INVALID_UID;
 
     private final class FontScaleSettingObserver extends ContentObserver {
@@ -1271,6 +1281,14 @@
                 .execute();
     }
 
+    @Override
+    public final void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) {
+        enforceCallerIsRecentsOrHasPermission(
+                MANAGE_ACTIVITY_STACKS, "registerTaskOrganizer()");
+        synchronized (mGlobalLock) {
+            mTaskOrganizerController.registerTaskOrganizer(organizer, windowingMode);
+        }
+    }
 
     @Override
     public IBinder requestStartActivityPermissionToken(IBinder delegatorToken) {
@@ -1421,7 +1439,7 @@
 
     int handleIncomingUser(int callingPid, int callingUid, int userId, String name) {
         return mAmInternal.handleIncomingUser(callingPid, callingUid, userId, false /* allowAll */,
-                ALLOW_FULL_ONLY, name, null /* callerPackage */);
+                ALLOW_NON_FULL, name, null /* callerPackage */);
     }
 
     @Override
@@ -1594,7 +1612,7 @@
             if (mController != null) {
                 // Find the first activity that is not finishing.
                 final ActivityRecord next =
-                        r.getActivityStack().topRunningActivity(token, INVALID_TASK_ID);
+                        r.getRootTask().topRunningActivity(token, INVALID_TASK_ID);
                 if (next != null) {
                     // ask watcher if this is allowed
                     boolean resumeOK = true;
@@ -2019,7 +2037,7 @@
         synchronized (mGlobalLock) {
             ActivityRecord r = mRootWindowContainer.isInAnyStack(token);
             if (r != null) {
-                r.getActivityStack().notifyActivityDrawnLocked(r);
+                r.getRootTask().notifyActivityDrawnLocked(r);
             }
         }
     }
@@ -2039,8 +2057,9 @@
     public int getDisplayId(IBinder activityToken) throws RemoteException {
         synchronized (mGlobalLock) {
             final ActivityStack stack = ActivityRecord.getStackLocked(activityToken);
-            if (stack != null && stack.getDisplayId() != INVALID_DISPLAY) {
-                return stack.getDisplayId();
+            if (stack != null) {
+                final int displayId = stack.getDisplayId();
+                return displayId != INVALID_DISPLAY ? displayId : DEFAULT_DISPLAY;
             }
             return DEFAULT_DISPLAY;
         }
@@ -2054,7 +2073,7 @@
             synchronized (mGlobalLock) {
                 ActivityStack focusedStack = getTopDisplayFocusedStack();
                 if (focusedStack != null) {
-                    return mRootWindowContainer.getStackInfo(focusedStack.mStackId);
+                    return mRootWindowContainer.getStackInfo(focusedStack.mTaskId);
                 }
                 return null;
             }
@@ -2156,7 +2175,7 @@
         synchronized (mGlobalLock) {
             final ActivityRecord srec = ActivityRecord.forTokenLocked(token);
             if (srec != null) {
-                return srec.getActivityStack().shouldUpRecreateTaskLocked(srec, destAffinity);
+                return srec.getRootTask().shouldUpRecreateTaskLocked(srec, destAffinity);
             }
         }
         return false;
@@ -2169,7 +2188,7 @@
         synchronized (mGlobalLock) {
             final ActivityRecord r = ActivityRecord.forTokenLocked(token);
             if (r != null) {
-                return r.getActivityStack().navigateUpTo(
+                return r.getRootTask().navigateUpTo(
                         r, destIntent, resultCode, resultData);
             }
             return false;
@@ -2335,7 +2354,7 @@
             if (r == null) {
                 return;
             }
-            ActivityStack stack = r.getActivityStack();
+            ActivityStack stack = r.getRootTask();
             if (stack != null && stack.isSingleTaskInstance()) {
                 // Single-task stacks are used for activities which are presented in floating
                 // windows above full screen activities. Instead of directly finishing the
@@ -2563,7 +2582,7 @@
                         r, resultWho, requestCode);
                 // TODO: This should probably only loop over the task since you need to be in the
                 // same task to return results.
-                r.getActivityStack().forAllActivities(c);
+                r.getRootTask().forAllActivities(c);
                 c.recycle();
 
                 updateOomAdj();
@@ -3196,10 +3215,9 @@
                                     + ainfo.applicationInfo.uid + ", calling uid=" + callingUid);
                 }
 
-                final ActivityStack stack = r.getActivityStack();
+                final ActivityStack stack = r.getRootTask();
                 final Task task = stack.createTask(
-                        mStackSupervisor.getNextTaskIdForUser(r.mUserId), ainfo, intent,
-                        null /* voiceSession */, null /* voiceInteractor */, !ON_TOP);
+                        mStackSupervisor.getNextTaskIdForUser(r.mUserId), ainfo, intent, !ON_TOP);
                 if (!mRecentTasks.addToBottom(task)) {
                     // The app has too many tasks already and we can't add any more
                     stack.removeChild(task, "addAppTask");
@@ -3285,7 +3303,7 @@
         }
     }
 
-    private void sanitizeAndApplyConfigChange(ConfigurationContainer container,
+    private int sanitizeAndApplyChange(ConfigurationContainer container,
             WindowContainerTransaction.Change change) {
         if (!(container instanceof Task || container instanceof ActivityStack)) {
             throw new RuntimeException("Invalid token in task transaction");
@@ -3297,12 +3315,22 @@
         configMask &= ActivityInfo.CONFIG_WINDOW_CONFIGURATION
                 | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
         windowMask &= WindowConfiguration.WINDOW_CONFIG_BOUNDS;
-        Configuration c = new Configuration(container.getRequestedOverrideConfiguration());
-        c.setTo(change.getConfiguration(), configMask, windowMask);
-        container.onRequestedOverrideConfigurationChanged(c);
-        // TODO(b/145675353): remove the following once we could apply new bounds to the
-        // pinned stack together with its children.
-        resizePinnedStackIfNeeded(container, configMask, windowMask, c);
+        int effects = 0;
+        if (configMask != 0) {
+            Configuration c = new Configuration(container.getRequestedOverrideConfiguration());
+            c.setTo(change.getConfiguration(), configMask, windowMask);
+            container.onRequestedOverrideConfigurationChanged(c);
+            // TODO(b/145675353): remove the following once we could apply new bounds to the
+            // pinned stack together with its children.
+            resizePinnedStackIfNeeded(container, configMask, windowMask, c);
+            effects |= TRANSACT_EFFECTS_CLIENT_CONFIG;
+        }
+        if ((change.getChangeMask() & WindowContainerTransaction.Change.CHANGE_FOCUSABLE) != 0) {
+            if (container.setFocusable(change.getFocusable())) {
+                effects |= TRANSACT_EFFECTS_LIFECYCLE;
+            }
+        }
+        return effects;
     }
 
     private void resizePinnedStackIfNeeded(ConfigurationContainer container, int configMask,
@@ -3319,23 +3347,68 @@
         }
     }
 
+    private int applyWindowContainerChange(ConfigurationContainer cc,
+            WindowContainerTransaction.Change c) {
+        int effects = sanitizeAndApplyChange(cc, c);
+
+        Rect enterPipBounds = c.getEnterPipBounds();
+        if (enterPipBounds != null) {
+            Task tr = (Task) cc;
+            mStackSupervisor.updatePictureInPictureMode(tr,
+                    enterPipBounds, true);
+        }
+        return effects;
+    }
+
     @Override
     public void applyContainerTransaction(WindowContainerTransaction t) {
         mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "applyContainerTransaction()");
+        if (t == null) {
+            return;
+        }
         long ident = Binder.clearCallingIdentity();
         try {
-            if (t == null) {
-                return;
-            }
             synchronized (mGlobalLock) {
-                Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
-                        t.getChanges().entrySet().iterator();
-                while (entries.hasNext()) {
-                    final Map.Entry<IBinder, WindowContainerTransaction.Change> entry =
-                            entries.next();
-                    final ConfigurationContainer cc = ConfigurationContainer.RemoteToken.fromBinder(
-                            entry.getKey()).getContainer();
-                    sanitizeAndApplyConfigChange(cc, entry.getValue());
+                int effects = 0;
+                deferWindowLayout();
+                try {
+                    ArraySet<WindowContainer> haveConfigChanges = new ArraySet<>();
+                    Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
+                            t.getChanges().entrySet().iterator();
+                    while (entries.hasNext()) {
+                        final Map.Entry<IBinder, WindowContainerTransaction.Change> entry =
+                                entries.next();
+                        final ConfigurationContainer cc =
+                                ConfigurationContainer.RemoteToken.fromBinder(
+                                        entry.getKey()).getContainer();
+                        int containerEffect = applyWindowContainerChange(cc, entry.getValue());
+                        effects |= containerEffect;
+                        // Lifecycle changes will trigger ensureConfig for everything.
+                        if ((effects & TRANSACT_EFFECTS_LIFECYCLE) == 0
+                                && (containerEffect & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
+                            if (cc instanceof WindowContainer) {
+                                haveConfigChanges.add((WindowContainer) cc);
+                            }
+                        }
+                    }
+                    if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) {
+                        // Already calls ensureActivityConfig
+                        mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+                    } else if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
+                        final PooledConsumer f = PooledLambda.obtainConsumer(
+                                ActivityRecord::ensureActivityConfiguration,
+                                PooledLambda.__(ActivityRecord.class), 0,
+                                false /* preserveWindow */);
+                        try {
+                            for (int i = haveConfigChanges.size() - 1; i >= 0; --i) {
+                                haveConfigChanges.valueAt(i).forAllActivities(f);
+                            }
+                        } finally {
+                            f.recycle();
+                        }
+                    }
+                } finally {
+                    continueWindowLayout();
                 }
             }
         } finally {
@@ -3478,7 +3551,7 @@
                                     + token);
                 }
 
-                final ActivityStack stack = r.getActivityStack();
+                final ActivityStack stack = r.getRootTask();
                 if (stack == null) {
                     throw new IllegalStateException("toggleFreeformWindowingMode: the activity "
                             + "doesn't have a stack");
@@ -4007,7 +4080,7 @@
         try {
             synchronized (mGlobalLock) {
                 final ActivityStack stack =
-                        mRootWindowContainer.getDefaultDisplay().getSplitScreenPrimaryStack();
+                        mRootWindowContainer.getDefaultDisplay().getRootSplitScreenPrimaryTask();
                 if (stack == null) {
                     Slog.w(TAG, "dismissSplitScreenMode: primary split-screen stack not found.");
                     return;
@@ -4048,7 +4121,7 @@
         try {
             synchronized (mGlobalLock) {
                 final ActivityStack stack =
-                        mRootWindowContainer.getDefaultDisplay().getPinnedStack();
+                        mRootWindowContainer.getDefaultDisplay().getRootPinnedTask();
                 if (stack == null) {
                     Slog.w(TAG, "dismissPip: pinned stack not found.");
                     return;
@@ -4057,7 +4130,11 @@
                     throw new IllegalArgumentException("Stack: " + stack
                             + " doesn't support animated resize.");
                 }
-                if (animate) {
+                /**
+                 * TODO(b/146594635): Remove all PIP animation code from WM
+                 * once SysUI handles animation. Don't even try to animate TaskOrganized tasks.
+                 */
+                if (animate && !stack.isControlledByTaskOrganizer()) {
                     stack.animateResizePinnedStack(null /* destBounds */,
                             null /* sourceHintBounds */, animationDuration,
                             false /* fromFullscreen */);
@@ -4158,14 +4235,14 @@
     }
 
     private boolean isInPictureInPictureMode(ActivityRecord r) {
-        if (r == null || r.getActivityStack() == null || !r.inPinnedWindowingMode()
-                || r.getActivityStack().isInStackLocked(r) == null) {
+        if (r == null || r.getRootTask() == null || !r.inPinnedWindowingMode()
+                || r.getRootTask().isInStackLocked(r) == null) {
             return false;
         }
 
         // If we are animating to fullscreen then we have already dispatched the PIP mode
         // changed, so we should reflect that check here as well.
-        final ActivityStack taskStack = r.getActivityStack();
+        final ActivityStack taskStack = r.getRootTask();
         return !taskStack.isAnimatingBoundsToFullscreen();
     }
 
@@ -4204,7 +4281,7 @@
                                 r.pictureInPictureArgs.getSourceRectHint());
                         mRootWindowContainer.moveActivityToPinnedStack(
                                 r, sourceBounds, aspectRatio, "enterPictureInPictureMode");
-                        final ActivityStack stack = r.getActivityStack();
+                        final ActivityStack stack = r.getRootTask();
                         stack.setPictureInPictureAspectRatio(aspectRatio);
                         stack.setPictureInPictureActions(actions);
                         MetricsLoggerWrapper.logPictureInPictureEnter(mContext,
@@ -4249,7 +4326,7 @@
                     // If the activity is already in picture-in-picture, update the pinned stack now
                     // if it is not already expanding to fullscreen. Otherwise, the arguments will
                     // be used the next time the activity enters PiP
-                    final ActivityStack stack = r.getActivityStack();
+                    final ActivityStack stack = r.getRootTask();
                     if (!stack.isAnimatingBoundsToFullscreen()) {
                         stack.setPictureInPictureAspectRatio(
                                 r.pictureInPictureArgs.getAspectRatio());
@@ -4308,7 +4385,7 @@
 
         if (params.hasSetAspectRatio()
                 && !mWindowManager.isValidPictureInPictureAspectRatio(
-                        r.getDisplayId(), params.getAspectRatio())) {
+                        r.getDisplay(), params.getAspectRatio())) {
             final float minAspectRatio = mContext.getResources().getFloat(
                     com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
             final float maxAspectRatio = mContext.getResources().getFloat(
@@ -4846,7 +4923,7 @@
             Slog.i(TAG, "Moving " + r.shortComponentName + " from display " + r.getDisplayId()
                     + " to main display for VR");
             mRootWindowContainer.moveStackToDisplay(
-                    r.getStackId(), DEFAULT_DISPLAY, true /* toTop */);
+                    r.getRootTaskId(), DEFAULT_DISPLAY, true /* toTop */);
         }
         mH.post(() -> {
             if (!mVrController.onVrModeChanged(r)) {
@@ -6666,7 +6743,7 @@
                     return;
                 }
                 process.mIsImeProcess = true;
-                process.registerDisplayConfigurationListenerLocked(displayContent);
+                process.registerDisplayConfigurationListener(displayContent);
             }
         }
 
@@ -6675,7 +6752,7 @@
                 int requestCode, int resultCode, Intent data) {
             synchronized (mGlobalLock) {
                 final ActivityRecord r = ActivityRecord.isInStackLocked(activityToken);
-                if (r != null && r.getActivityStack() != null) {
+                if (r != null && r.getRootTask() != null) {
                     r.sendResult(callingUid, resultWho, requestCode, resultCode, data);
                 }
             }
@@ -7203,7 +7280,8 @@
                             ActivityManagerServiceDumpProcessesProto.VR_CONTROLLER);
                     if (mController != null) {
                         final long token = proto.start(CONTROLLER);
-                        proto.write(CONTROLLER, mController.toString());
+                        proto.write(ActivityManagerServiceDumpProcessesProto.Controller.CONTROLLER,
+                                mController.toString());
                         proto.write(IS_A_MONKEY, mControllerIsAMonkey);
                         proto.end(token);
                     }
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 09111d0..014cb76 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -436,10 +436,9 @@
         mNextAppTransition = TRANSIT_UNSET;
         mNextAppTransitionFlags = 0;
         setAppTransitionState(APP_STATE_RUNNING);
-        final AnimationAdapter topOpeningAnim =
-                (topOpeningApp != null && topOpeningApp.getAnimatingContainer() != null)
-                        ? topOpeningApp.getAnimatingContainer().getAnimation()
-                        : null;
+        final WindowContainer wc =
+                topOpeningApp != null ? topOpeningApp.getAnimatingContainer() : null;
+        final AnimationAdapter topOpeningAnim = wc != null ? wc.getAnimation() : null;
 
         int redoLayout = notifyAppTransitionStartingLocked(transit,
                 topOpeningAnim != null ? topOpeningAnim.getDurationHint() : 0,
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 369dde6..3927d5f 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -352,6 +352,7 @@
      * Apply animation to the set of window containers.
      *
      * @param wcs The list of {@link WindowContainer}s to which an app transition animation applies.
+     * @param apps The list of {@link ActivityRecord}s being transitioning.
      * @param transit The current transition type.
      * @param visible {@code true} if the apps becomes visible, {@code false} if the apps becomes
      *                invisible.
@@ -359,12 +360,28 @@
      * @param voiceInteraction {@code true} if one of the apps in this transition belongs to a voice
      *                         interaction session driving task.
      */
-    private void applyAnimations(ArraySet<WindowContainer> wcs, @TransitionType int transit,
-            boolean visible, LayoutParams animLp, boolean voiceInteraction) {
-        final int appsCount = wcs.size();
-        for (int i = 0; i < appsCount; i++) {
+    private void applyAnimations(ArraySet<WindowContainer> wcs, ArraySet<ActivityRecord> apps,
+            @TransitionType int transit, boolean visible, LayoutParams animLp,
+            boolean voiceInteraction) {
+        final int wcsCount = wcs.size();
+        for (int i = 0; i < wcsCount; i++) {
             final WindowContainer wc = wcs.valueAt(i);
-            wc.applyAnimation(animLp, transit, visible, voiceInteraction);
+            // If app transition animation target is promoted to higher level, SurfaceAnimator
+            // triggers WC#onAnimationFinished only on the promoted target. So we need to take care
+            // of triggering AR#onAnimationFinished on each ActivityRecord which is a part of the
+            // app transition.
+            final ArrayList<ActivityRecord> transitioningDecendants = new ArrayList<>();
+            for (int j = 0; j < apps.size(); ++j) {
+                final ActivityRecord app = apps.valueAt(j);
+                if (app.isDescendantOf(wc)) {
+                    transitioningDecendants.add(app);
+                }
+            }
+            wc.applyAnimation(animLp, transit, visible, voiceInteraction, () -> {
+                for (int j = 0; j < transitioningDecendants.size(); ++j) {
+                    transitioningDecendants.get(j).onAnimationFinished();
+                }
+            });
         }
     }
 
@@ -495,8 +512,10 @@
                 openingApps, closingApps, true /* visible */);
         final ArraySet<WindowContainer> closingWcs = getAnimationTargets(
                 openingApps, closingApps, false /* visible */);
-        applyAnimations(openingWcs, transit, true /* visible */, animLp, voiceInteraction);
-        applyAnimations(closingWcs, transit, false /* visible */, animLp, voiceInteraction);
+        applyAnimations(openingWcs, openingApps, transit, true /* visible */, animLp,
+                voiceInteraction);
+        applyAnimations(closingWcs, closingApps, transit, false /* visible */, animLp,
+                voiceInteraction);
 
         final AccessibilityController accessibilityController =
                 mDisplayContent.mWmService.mAccessibilityController;
@@ -575,7 +594,8 @@
             ActivityRecord activity = apps.valueAt(i);
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Now changing app %s", activity);
             activity.cancelAnimationOnly();
-            activity.applyAnimation(null, transit, true, false);
+            activity.applyAnimation(null, transit, true, false,
+                    null /* animationFinishedCallback */);
             activity.updateReportedVisibilityLocked();
             mService.openSurfaceTransaction();
             try {
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index dd3365c..7b23e2d 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -134,8 +134,8 @@
         resolveOverrideConfiguration(newParentConfig);
         mFullConfiguration.setTo(newParentConfig);
         mFullConfiguration.updateFrom(mResolvedOverrideConfiguration);
+        onMergedOverrideConfigurationChanged();
         if (!mResolvedTmpConfig.equals(mResolvedOverrideConfiguration)) {
-            onMergedOverrideConfigurationChanged();
             // This depends on the assumption that change-listeners don't do
             // their own override resolution. This way, dependent hierarchies
             // can stay properly synced-up with a primary hierarchy's constraints.
@@ -147,6 +147,10 @@
                         mResolvedOverrideConfiguration);
             }
         }
+        for (int i = mChangeListeners.size() - 1; i >= 0; --i) {
+            mChangeListeners.get(i).onMergedOverrideConfigurationChanged(
+                    mMergedOverrideConfiguration);
+        }
         if (forwardToChildren) {
             for (int i = getChildCount() - 1; i >= 0; --i) {
                 final ConfigurationContainer child = getChildAt(i);
@@ -545,6 +549,7 @@
         }
         mChangeListeners.add(listener);
         listener.onRequestedOverrideConfigurationChanged(mResolvedOverrideConfiguration);
+        listener.onMergedOverrideConfigurationChanged(mMergedOverrideConfiguration);
     }
 
     void unregisterConfigurationChangeListener(ConfigurationContainerListener listener) {
@@ -624,6 +629,21 @@
         return mFullConfiguration.windowConfiguration.isAlwaysOnTop();
     }
 
+    /**
+     * Returns {@code true} if this container is focusable. Generally, if a parent is not focusable,
+     * this will not be focusable either.
+     */
+    boolean isFocusable() {
+        // TODO(split): Move this to WindowContainer once Split-screen is based on a WindowContainer
+        //              like DisplayArea vs. TaskTiles.
+        ConfigurationContainer parent = getParent();
+        return parent == null || parent.isFocusable();
+    }
+
+    boolean setFocusable(boolean focusable) {
+        return false;
+    }
+
     boolean hasChild() {
         return getChildCount() > 0;
     }
@@ -666,4 +686,8 @@
             return sb.toString();
         }
     }
+
+    RemoteToken getRemoteToken() {
+        return mRemoteToken;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainerListener.java b/services/core/java/com/android/server/wm/ConfigurationContainerListener.java
index dc4939d..3d84e17 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainerListener.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainerListener.java
@@ -24,5 +24,8 @@
 public interface ConfigurationContainerListener {
 
     /** {@see ConfigurationContainer#onRequestedOverrideConfigurationChanged} */
-    void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration);
+    default void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {}
+
+    /** Called when new merged override configuration is reported. */
+    default void onMergedOverrideConfigurationChanged(Configuration mergedOverrideConfiguration) {}
 }
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index af859d3..16aff9c 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -21,6 +21,7 @@
 import static com.android.server.wm.AlphaAnimationSpecProto.TO;
 import static com.android.server.wm.AnimationSpecProto.ALPHA;
 
+import android.annotation.Nullable;
 import android.graphics.Rect;
 import android.util.Log;
 import android.util.proto.ProtoOutputStream;
@@ -156,7 +157,8 @@
     @VisibleForTesting
     interface SurfaceAnimatorStarter {
         void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t,
-                AnimationAdapter anim, boolean hidden);
+                AnimationAdapter anim, boolean hidden,
+                @Nullable Runnable animationFinishedCallback);
     }
 
     Dimmer(WindowContainer host) {
@@ -342,7 +344,8 @@
             SurfaceControl.Transaction t, float startAlpha, float endAlpha) {
         mSurfaceAnimatorStarter.startAnimation(animator, t, new LocalAnimationAdapter(
                 new AlphaAnimationSpec(startAlpha, endAlpha, getDimDuration(container)),
-                mHost.mWmService.mSurfaceAnimationRunner), false /* hidden */);
+                mHost.mWmService.mSurfaceAnimationRunner), false /* hidden */,
+                null /* animationFinishedCallback */);
     }
 
     private long getDimDuration(WindowContainer container) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 472baf6..825f93c 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -19,6 +19,7 @@
 import static android.app.ActivityTaskManager.INVALID_STACK_ID;
 import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
@@ -65,17 +66,15 @@
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
 import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS;
-import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
-import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
@@ -168,8 +167,10 @@
 import android.app.ActivityOptions;
 import android.app.WindowConfiguration;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityInfo.ScreenOrientation;
+import android.content.pm.ApplicationInfo;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
@@ -205,6 +206,7 @@
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
 import android.view.Gravity;
+import android.view.IDisplayWindowInsetsController;
 import android.view.ISystemGestureExclusionListener;
 import android.view.IWindow;
 import android.view.InputChannel;
@@ -218,6 +220,8 @@
 import android.view.SurfaceControl.Transaction;
 import android.view.SurfaceSession;
 import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.WindowManagerPolicyConstants.PointerEventListener;
 
@@ -291,7 +295,7 @@
 
     /** The containers below are the only child containers {@link #mWindowContainers} can have. */
     // Contains all window containers that are related to apps (Activities)
-    private final TaskStackContainers mTaskStackContainers = new TaskStackContainers(mWmService);
+    private final TaskContainers mTaskContainers = new TaskContainers(mWmService);
     // Contains all non-app window containers that should be displayed above the app containers
     // (e.g. Status bar)
     private final AboveAppWindowContainers mAboveAppWindowsContainers =
@@ -304,8 +308,8 @@
     // on the IME target. We mainly have this container grouping so we can keep track of all the IME
     // window containers together and move them in-sync if/when needed. We use a subclass of
     // WindowContainer which is omitted from screen magnification, as the IME is never magnified.
-    private final NonAppWindowContainers mImeWindowsContainers =
-            new NonAppWindowContainers("mImeWindowsContainers", mWmService);
+    // TODO(display-area): is "no magnification" in the comment still true?
+    private final ImeContainer mImeWindowsContainers = new ImeContainer(mWmService);
 
     private WindowState mTmpWindow;
     private WindowState mTmpWindow2;
@@ -398,14 +402,6 @@
     private int mCurrentOverrideConfigurationChanges;
 
     /**
-     * Orientation forced by some window. If there is no visible window that specifies orientation
-     * it is set to {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED}.
-     *
-     * @see NonAppWindowContainers#getOrientation()
-     */
-    private int mLastWindowForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
-
-    /**
      * Last orientation forced by the keyguard. It is applied when keyguard is shown and is not
      * occluded.
      *
@@ -570,6 +566,8 @@
      */
     WindowState mInputMethodTarget;
 
+    InsetsControlTarget mInputMethodControlTarget;
+
     /** If true hold off on modifying the animation layer of mInputMethodTarget */
     boolean mInputMethodTargetWaitingAnim;
 
@@ -598,6 +596,13 @@
     private final float mWindowCornerRadius;
 
     private final SparseArray<ShellRoot> mShellRoots = new SparseArray<>();
+    RemoteInsetsControlTarget mRemoteInsetsControlTarget = null;
+    private final IBinder.DeathRecipient mRemoteInsetsDeath =
+            () -> {
+                synchronized (mWmService.mGlobalLock) {
+                    mRemoteInsetsControlTarget = null;
+                }
+            };
 
     private RootWindowContainer mRootWindowContainer;
 
@@ -864,7 +869,7 @@
             if (w.mHasSurface && isDisplayed) {
                 final int type = w.mAttrs.type;
                 if (type == TYPE_SYSTEM_DIALOG || type == TYPE_SYSTEM_ERROR
-                        || (w.mAttrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
+                        || mWmService.mPolicy.isKeyguardShowing()) {
                     mTmpApplySurfaceChangesTransactionState.syswin = true;
                 }
                 if (mTmpApplySurfaceChangesTransactionState.preferredRefreshRate == 0
@@ -1156,6 +1161,22 @@
         mShellRoots.remove(windowType);
     }
 
+    void setRemoteInsetsController(IDisplayWindowInsetsController controller) {
+        if (mRemoteInsetsControlTarget != null) {
+            mRemoteInsetsControlTarget.mRemoteInsetsController.asBinder().unlinkToDeath(
+                    mRemoteInsetsDeath, 0);
+            mRemoteInsetsControlTarget = null;
+        }
+        if (controller != null) {
+            try {
+                controller.asBinder().linkToDeath(mRemoteInsetsDeath, 0);
+                mRemoteInsetsControlTarget = new RemoteInsetsControlTarget(controller);
+            } catch (RemoteException e) {
+                return;
+            }
+        }
+    }
+
     /** Changes the display the input window token is housed on to this one. */
     void reParentWindowToken(WindowToken token) {
         final DisplayContent prevDc = token.getDisplayContent();
@@ -1255,11 +1276,6 @@
         return mDisplayRotation.getLastOrientation();
     }
 
-    @ScreenOrientation
-    int getLastWindowForcedOrientation() {
-        return mLastWindowForcedOrientation;
-    }
-
     void registerRemoteAnimations(RemoteAnimationDefinition definition) {
         mAppTransitionController.registerRemoteAnimations(definition);
     }
@@ -1918,39 +1934,25 @@
         return (mDisplay.getFlags() & FLAG_PRIVATE) != 0;
     }
 
-    ActivityStack getHomeStack() {
-        return mTaskStackContainers.getHomeStack();
+    ActivityStack getRootHomeTask() {
+        return mTaskContainers.getRootHomeTask();
     }
 
-    ActivityStack getRecentsStack() {
-        return mTaskStackContainers.getRecentsStack();
+    /** @return The primary split-screen task, and {@code null} otherwise. */
+    ActivityStack getRootSplitScreenPrimaryTask() {
+        return mTaskContainers.getRootSplitScreenPrimaryTask();
     }
 
-    /**
-     * @return The primary split-screen stack, and {@code null} otherwise.
-     */
-    ActivityStack getSplitScreenPrimaryStack() {
-        return mTaskStackContainers.getSplitScreenPrimaryStack();
+    boolean hasSplitScreenPrimaryTask() {
+        return getRootSplitScreenPrimaryTask() != null;
     }
 
-    boolean hasSplitScreenPrimaryStack() {
-        return getSplitScreenPrimaryStack() != null;
+    ActivityStack getRootPinnedTask() {
+        return mTaskContainers.getRootPinnedTask();
     }
 
-    /**
-     * Like {@link #getSplitScreenPrimaryStack}, but also returns the stack if it's currently
-     * not visible.
-     */
-    ActivityStack getSplitScreenPrimaryStackIgnoringVisibility() {
-        return mTaskStackContainers.getSplitScreenPrimaryStack();
-    }
-
-    ActivityStack getPinnedStack() {
-        return mTaskStackContainers.getPinnedStack();
-    }
-
-    boolean hasPinnedStack() {
-        return mTaskStackContainers.getPinnedStack() != null;
+    boolean hasPinnedTask() {
+        return mTaskContainers.getRootPinnedTask() != null;
     }
 
     /**
@@ -1966,41 +1968,45 @@
      * activity type. Null is no compatible stack on the display.
      */
     ActivityStack getStack(int windowingMode, int activityType) {
-        return mTaskStackContainers.getStack(windowingMode, activityType);
+        return mTaskContainers.getStack(windowingMode, activityType);
     }
 
     protected int getStackCount() {
-        return mTaskStackContainers.mChildren.size();
+        return mTaskContainers.mChildren.size();
     }
 
     protected ActivityStack getStackAt(int index) {
-        return mTaskStackContainers.mChildren.get(index);
+        return mTaskContainers.mChildren.get(index);
     }
 
     int getIndexOf(ActivityStack stack) {
-        return mTaskStackContainers.getIndexOf(stack);
+        return mTaskContainers.getIndexOf(stack);
     }
 
     void removeStack(ActivityStack stack) {
-        mTaskStackContainers.removeChild(stack);
+        mTaskContainers.removeChild(stack);
     }
 
     @VisibleForTesting
     WindowList<ActivityStack> getStacks() {
-        return mTaskStackContainers.mChildren;
+        return mTaskContainers.mChildren;
     }
 
     @VisibleForTesting
     ActivityStack getTopStack() {
-        return mTaskStackContainers.getTopStack();
+        return mTaskContainers.getTopStack();
     }
 
     ArrayList<Task> getVisibleTasks() {
-        return mTaskStackContainers.getVisibleTasks();
+        return mTaskContainers.getVisibleTasks();
+    }
+
+    SurfaceControl getSplitScreenDividerAnchor() {
+        return mTaskContainers.getSplitScreenDividerAnchor();
     }
 
     void onStackWindowingModeChanged(ActivityStack stack) {
-        mTaskStackContainers.onStackWindowingModeChanged(stack);
+        mTaskContainers.onStackWindowingModeChanged(stack);
     }
 
     /**
@@ -2033,7 +2039,7 @@
 
         // If there was no pinned stack, we still need to notify the controller of the display info
         // update as a result of the config change.
-        if (mPinnedStackControllerLocked != null && !hasPinnedStack()) {
+        if (mPinnedStackControllerLocked != null && !hasPinnedTask()) {
             mPinnedStackControllerLocked.onDisplayInfoChanged(getDisplayInfo());
         }
     }
@@ -2085,7 +2091,7 @@
     }
 
     boolean forAllImeWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
-        return mImeWindowsContainers.forAllWindows(callback, traverseTopToBottom);
+        return mImeWindowsContainers.forAllWindowForce(callback, traverseTopToBottom);
     }
 
     /**
@@ -2102,16 +2108,7 @@
         }
 
         if (mWmService.mDisplayFrozen) {
-            if (mLastWindowForcedOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
-                ProtoLog.v(WM_DEBUG_ORIENTATION,
-                        "Display id=%d is frozen, return %d", mDisplayId,
-                        mLastWindowForcedOrientation);
-                // If the display is frozen, some activities may be in the middle of restarting, and
-                // thus have removed their old window. If the window has the flag to hide the lock
-                // screen, then the lock screen can re-appear and inflict its own orientation on us.
-                // Keep the orientation stable until this all settles down.
-                return mLastWindowForcedOrientation;
-            } else if (policy.isKeyguardLocked()) {
+            if (policy.isKeyguardLocked()) {
                 // Use the last orientation the while the display is frozen with the keyguard
                 // locked. This could be the keyguard forced orientation or from a SHOW_WHEN_LOCKED
                 // window. We don't want to check the show when locked window directly though as
@@ -2122,15 +2119,14 @@
                         mDisplayId, getLastOrientation());
                 return getLastOrientation();
             }
-        } else {
-            final int orientation = mAboveAppWindowsContainers.getOrientation();
-            if (orientation != SCREEN_ORIENTATION_UNSET) {
-                return orientation;
-            }
+        }
+        final int orientation = mAboveAppWindowsContainers.getOrientation();
+        if (orientation != SCREEN_ORIENTATION_UNSET) {
+            return orientation;
         }
 
         // Top system windows are not requesting an orientation. Start searching from apps.
-        return mTaskStackContainers.getOrientation();
+        return mTaskContainers.getOrientation();
     }
 
     void updateDisplayInfo() {
@@ -2314,11 +2310,11 @@
 
     void setStackOnDisplay(ActivityStack stack, int position) {
         if (DEBUG_STACK) Slog.d(TAG_WM, "Set stack=" + stack + " on displayId=" + mDisplayId);
-        mTaskStackContainers.addChild(stack, position);
+        mTaskContainers.addChild(stack, position);
     }
 
     void moveStackToDisplay(ActivityStack stack, boolean onTop) {
-        stack.reparent(mTaskStackContainers, onTop ? POSITION_TOP: POSITION_BOTTOM);
+        stack.reparent(mTaskContainers, onTop ? POSITION_TOP: POSITION_BOTTOM);
     }
 
     @Override
@@ -2343,15 +2339,19 @@
         throw new UnsupportedOperationException("See DisplayChildWindowContainer");
     }
 
+    void positionDisplayAt(int position, boolean includingParents) {
+        getParent().positionChildAt(position, this, includingParents);
+    }
+
     @Override
     void positionChildAt(int position, DisplayChildWindowContainer child, boolean includingParents) {
         // Children of the display are statically ordered, so the real intention here is to perform
         // the operation on the display and not the static direct children.
-        getParent().positionChildAt(position, this, includingParents);
+        positionDisplayAt(position, includingParents);
     }
 
     void positionStackAt(int position, ActivityStack child, boolean includingParents) {
-        mTaskStackContainers.positionChildAt(position, child, includingParents);
+        mTaskContainers.positionChildAt(position, child, includingParents);
         layoutAndAssignWindowLayersIfNeeded();
     }
 
@@ -2382,7 +2382,7 @@
      */
     Task findTaskForResizePoint(int x, int y) {
         final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
-        return mTmpTaskForResizePointSearchResult.process(mTaskStackContainers, x, y, delta);
+        return mTmpTaskForResizePointSearchResult.process(mTaskContainers, x, y, delta);
     }
 
     void updateTouchExcludeRegion() {
@@ -2398,7 +2398,7 @@
             final PooledConsumer c = PooledLambda.obtainConsumer(
                     DisplayContent::processTaskForTouchExcludeRegion, this,
                     PooledLambda.__(Task.class), focusedTask, delta);
-            mTaskStackContainers.forAllTasks(c);
+            mTaskContainers.forAllTasks(c);
             c.recycle();
 
             // If we removed the focused task above, add it back and only leave its
@@ -2422,7 +2422,7 @@
         }
         amendWindowTapExcludeRegion(mTouchExcludeRegion);
         // TODO(multi-display): Support docked stacks on secondary displays.
-        if (mDisplayId == DEFAULT_DISPLAY && getSplitScreenPrimaryStack() != null) {
+        if (mDisplayId == DEFAULT_DISPLAY && getRootSplitScreenPrimaryTask() != null) {
             mDividerControllerLocked.getTouchRegion(mTmpRect);
             mTmpRegion.set(mTmpRect);
             mTouchExcludeRegion.op(mTmpRegion, Op.UNION);
@@ -2568,8 +2568,8 @@
             float dividerAnimationTarget) {
         boolean updated = false;
 
-        for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
-            final ActivityStack stack = mTaskStackContainers.getChildAt(i);
+        for (int i = mTaskContainers.getChildCount() - 1; i >= 0; --i) {
+            final ActivityStack stack = mTaskContainers.getChildAt(i);
             if (stack == null || !stack.isAdjustedForIme()) {
                 continue;
             }
@@ -2597,8 +2597,8 @@
 
     boolean clearImeAdjustAnimation() {
         boolean changed = false;
-        for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
-            final ActivityStack stack = mTaskStackContainers.getChildAt(i);
+        for (int i = mTaskContainers.getChildCount() - 1; i >= 0; --i) {
+            final ActivityStack stack = mTaskContainers.getChildAt(i);
             if (stack != null && stack.isAdjustedForIme()) {
                 stack.resetAdjustedForIme(true /* adjustBoundsNow */);
                 changed  = true;
@@ -2608,8 +2608,8 @@
     }
 
     void beginImeAdjustAnimation() {
-        for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
-            final ActivityStack stack = mTaskStackContainers.getChildAt(i);
+        for (int i = mTaskContainers.getChildCount() - 1; i >= 0; --i) {
+            final ActivityStack stack = mTaskContainers.getChildAt(i);
             if (stack.isVisible() && stack.isAdjustedForIme()) {
                 stack.beginImeAdjustAnimation();
             }
@@ -2620,7 +2620,7 @@
         final WindowState imeWin = mInputMethodWindow;
         final boolean imeVisible = imeWin != null && imeWin.isVisibleLw() && imeWin.isDisplayedLw()
                 && !mDividerControllerLocked.isImeHideRequested();
-        final ActivityStack dockedStack = getSplitScreenPrimaryStack();
+        final ActivityStack dockedStack = getRootSplitScreenPrimaryTask();
         final boolean dockVisible = dockedStack != null;
         final Task topDockedTask = dockVisible ? dockedStack.getTask((t) -> true): null;
         final ActivityStack imeTargetStack = mWmService.getImeFocusStackLocked();
@@ -2646,8 +2646,8 @@
         // - If IME is not visible, divider is not moved and is normal width.
 
         if (imeVisible && dockVisible && (imeOnTop || imeOnBottom) && !dockMinimized) {
-            for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
-                final ActivityStack stack = mTaskStackContainers.getChildAt(i);
+            for (int i = mTaskContainers.getChildCount() - 1; i >= 0; --i) {
+                final ActivityStack stack = mTaskContainers.getChildAt(i);
                 final boolean isDockedOnBottom = stack.getDockSide() == DOCKED_BOTTOM;
                 if (stack.isVisible() && (imeOnBottom || isDockedOnBottom)
                         && stack.inSplitScreenWindowingMode()) {
@@ -2659,8 +2659,8 @@
             mDividerControllerLocked.setAdjustedForIme(
                     imeOnBottom /*ime*/, true /*divider*/, true /*animate*/, imeWin, imeHeight);
         } else {
-            for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
-                final ActivityStack stack = mTaskStackContainers.getChildAt(i);
+            for (int i = mTaskContainers.getChildCount() - 1; i >= 0; --i) {
+                final ActivityStack stack = mTaskContainers.getChildAt(i);
                 stack.resetAdjustedForIme(!dockVisible);
             }
             mDividerControllerLocked.setAdjustedForIme(
@@ -2670,8 +2670,8 @@
     }
 
     void prepareFreezingTaskBounds() {
-        for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
-            final ActivityStack stack = mTaskStackContainers.getChildAt(stackNdx);
+        for (int stackNdx = mTaskContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+            final ActivityStack stack = mTaskContainers.getChildAt(stackNdx);
             stack.prepareFreezingTaskBounds();
         }
     }
@@ -2747,7 +2747,7 @@
         proto.write(SINGLE_TASK_INSTANCE, mSingleTaskInstance);
         final ActivityStack focusedStack = getFocusedStack();
         if (focusedStack != null) {
-            proto.write(FOCUSED_STACK_ID, focusedStack.mStackId);
+            proto.write(FOCUSED_STACK_ID, focusedStack.getRootTaskId());
             final ActivityRecord focusedActivity = focusedStack.getDisplay().getResumedActivity();
             if (focusedActivity != null) {
                 focusedActivity.writeIdentifierToProto(proto, RESUMED_ACTIVITY);
@@ -2773,8 +2773,8 @@
         final long token = proto.start(fieldId);
         super.dumpDebug(proto, WINDOW_CONTAINER, logLevel);
         proto.write(ID, mDisplayId);
-        for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
-            final ActivityStack stack = mTaskStackContainers.getChildAt(stackNdx);
+        for (int stackNdx = mTaskContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+            final ActivityStack stack = mTaskContainers.getChildAt(stackNdx);
             stack.dumpDebugInnerStackOnly(proto, STACKS, logLevel);
         }
         mDividerControllerLocked.dumpDebug(proto, DOCKED_STACK_DIVIDER_CONTROLLER);
@@ -2900,8 +2900,8 @@
 
         pw.println();
         pw.println(prefix + "Application tokens in top down Z order:");
-        for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
-            final ActivityStack stack = mTaskStackContainers.getChildAt(stackNdx);
+        for (int stackNdx = mTaskContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+            final ActivityStack stack = mTaskContainers.getChildAt(stackNdx);
             stack.dump(pw, prefix + "  ", dumpAll);
         }
 
@@ -2931,19 +2931,20 @@
         pw.println();
 
         // Dump stack references
-        final ActivityStack homeStack = getHomeStack();
+        final ActivityStack homeStack = getRootHomeTask();
         if (homeStack != null) {
             pw.println(prefix + "homeStack=" + homeStack.getName());
         }
-        final ActivityStack pinnedStack = getPinnedStack();
+        final ActivityStack pinnedStack = getRootPinnedTask();
         if (pinnedStack != null) {
             pw.println(prefix + "pinnedStack=" + pinnedStack.getName());
         }
-        final ActivityStack splitScreenPrimaryStack = getSplitScreenPrimaryStack();
+        final ActivityStack splitScreenPrimaryStack = getRootSplitScreenPrimaryTask();
         if (splitScreenPrimaryStack != null) {
             pw.println(prefix + "splitScreenPrimaryStack=" + splitScreenPrimaryStack.getName());
         }
-        final ActivityStack recentsStack = getRecentsStack();
+        final ActivityStack recentsStack =
+                getStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS);
         if (recentsStack != null) {
             pw.println(prefix + "recentsStack=" + recentsStack.getName());
         }
@@ -3383,6 +3384,14 @@
         }
     }
 
+    boolean isImeAttachedToApp() {
+        return (mInputMethodTarget != null && mInputMethodTarget.mActivityRecord != null
+                && mInputMethodTarget.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+                // An activity with override bounds should be letterboxed inside its parent bounds,
+                // so it doesn't fill the screen.
+                && mInputMethodTarget.mActivityRecord.matchParentBounds());
+    }
+
     private void setInputMethodTarget(WindowState target, boolean targetWaitingAnim) {
         if (target == mInputMethodTarget && mInputMethodTargetWaitingAnim == targetWaitingAnim) {
             return;
@@ -3391,7 +3400,8 @@
         mInputMethodTarget = target;
         mInputMethodTargetWaitingAnim = targetWaitingAnim;
         assignWindowLayers(false /* setLayoutNeeded */);
-        mInsetsStateController.onImeTargetChanged(target);
+        mInputMethodControlTarget = computeImeControlTarget();
+        mInsetsStateController.onImeTargetChanged(mInputMethodControlTarget);
         updateImeParent();
     }
 
@@ -3416,11 +3426,7 @@
         // Attach it to app if the target is part of an app and such app is covering the entire
         // screen. If it's not covering the entire screen the IME might extend beyond the apps
         // bounds.
-        if (mInputMethodTarget != null && mInputMethodTarget.mActivityRecord != null
-                && mInputMethodTarget.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
-                // An activity with override bounds should be letterboxed inside its parent bounds,
-                // so it doesn't fill the screen.
-                && mInputMethodTarget.mActivityRecord.matchParentBounds()) {
+        if (isImeAttachedToApp()) {
             return mInputMethodTarget.mActivityRecord.getSurfaceControl();
         }
 
@@ -3428,6 +3434,19 @@
         return mWindowContainers.getSurfaceControl();
     }
 
+    /**
+     * Computes which control-target the IME should be attached to.
+     */
+    @VisibleForTesting
+    InsetsControlTarget computeImeControlTarget() {
+        if (!isImeAttachedToApp() && mRemoteInsetsControlTarget != null) {
+            return mRemoteInsetsControlTarget;
+        }
+
+        // Otherwise, we just use the ime target
+        return mInputMethodTarget;
+    }
+
     void setLayoutNeeded() {
         if (DEBUG_LAYOUT) Slog.w(TAG_WM, "setLayoutNeeded: callers=" + Debug.getCallers(3));
         mLayoutNeeded = true;
@@ -3512,7 +3531,7 @@
         final SparseBooleanArray drawnWindowTypes = new SparseBooleanArray();
         // Presuppose keyguard is drawn because if its window isn't attached, we don't know if it
         // wants to be shown or hidden, then it should not delay enabling the screen.
-        drawnWindowTypes.put(TYPE_STATUS_BAR, true);
+        drawnWindowTypes.put(TYPE_NOTIFICATION_SHADE, true);
 
         final WindowState visibleNotDrawnWindow = getWindow(w -> {
             if (w.mViewVisibility == View.VISIBLE && !w.mObscured && !w.isDrawnLw()) {
@@ -3523,8 +3542,9 @@
                 if (type == TYPE_BOOT_PROGRESS || type == TYPE_BASE_APPLICATION
                         || type == TYPE_WALLPAPER) {
                     drawnWindowTypes.put(type, true);
-                } else if (type == TYPE_STATUS_BAR) {
-                    drawnWindowTypes.put(TYPE_STATUS_BAR, mWmService.mPolicy.isKeyguardDrawnLw());
+                } else if (type == TYPE_NOTIFICATION_SHADE) {
+                    drawnWindowTypes.put(TYPE_NOTIFICATION_SHADE,
+                            mWmService.mPolicy.isKeyguardDrawnLw());
                 }
             }
             return false;
@@ -3546,7 +3566,7 @@
         final boolean haveBootMsg = drawnWindowTypes.get(TYPE_BOOT_PROGRESS);
         final boolean haveApp = drawnWindowTypes.get(TYPE_BASE_APPLICATION);
         final boolean haveWallpaper = drawnWindowTypes.get(TYPE_WALLPAPER);
-        final boolean haveKeyguard = drawnWindowTypes.get(TYPE_STATUS_BAR);
+        final boolean haveKeyguard = drawnWindowTypes.get(TYPE_NOTIFICATION_SHADE);
 
         ProtoLog.i(WM_DEBUG_SCREEN_ON,
                 "******** booted=%b msg=%b haveBoot=%b haveApp=%b haveWall=%b "
@@ -3644,7 +3664,8 @@
                     w.mSeq++;
                     w.mSystemUiVisibility = newValue;
                 }
-                if (newValue != curValue || w.mAttrs.hasSystemUiListeners) {
+                if ((newValue != curValue || w.mAttrs.hasSystemUiListeners)
+                        && ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL) {
                     w.mClient.dispatchSystemUiVisibilityChanged(w.mSeq,
                             visibility, newValue, diff);
                 }
@@ -3961,7 +3982,7 @@
         }
 
         // Initialize state of exiting applications.
-        mTaskStackContainers.setExitingTokensHasVisible(hasVisible);
+        mTaskContainers.setExitingTokensHasVisible(hasVisible);
     }
 
     void removeExistingTokensIfPossible() {
@@ -3973,7 +3994,7 @@
         }
 
         // Time to remove any exiting applications?
-        mTaskStackContainers.removeExistingAppTokensIfPossible();
+        mTaskContainers.removeExistingAppTokensIfPossible();
     }
 
     @Override
@@ -4085,6 +4106,8 @@
 
         DisplayChildWindowContainer(WindowManagerService service) {
             super(service);
+            // TODO(display-area): move to ConfigurationContainer?
+            mOrientation = SCREEN_ORIENTATION_UNSET;
         }
 
         @Override
@@ -4102,7 +4125,7 @@
      * Window container class that contains all containers on this display relating to Apps.
      * I.e Activities.
      */
-    private final class TaskStackContainers extends DisplayChildWindowContainer<ActivityStack> {
+    private final class TaskContainers extends DisplayChildWindowContainer<ActivityStack> {
         /**
          * A control placed at the appropriate level for transitions to occur.
          */
@@ -4125,14 +4148,13 @@
          */
         SurfaceControl mSplitScreenDividerAnchor = null;
 
-        // Cached reference to some special stacks we tend to get a lot so we don't need to loop
+        // Cached reference to some special tasks we tend to get a lot so we don't need to loop
         // through the list to find them.
-        private ActivityStack mHomeStack = null;
-        private ActivityStack mRecentsStack = null;
-        private ActivityStack mPinnedStack = null;
-        private ActivityStack mSplitScreenPrimaryStack = null;
+        private ActivityStack mRootHomeTask = null;
+        private ActivityStack mRootPinnedTask = null;
+        private ActivityStack mRootSplitScreenPrimaryTask = null;
 
-        TaskStackContainers(WindowManagerService service) {
+        TaskContainers(WindowManagerService service) {
             super(service);
         }
 
@@ -4142,15 +4164,15 @@
          */
         ActivityStack getStack(int windowingMode, int activityType) {
             if (activityType == ACTIVITY_TYPE_HOME) {
-                return mHomeStack;
+                return mRootHomeTask;
             }
             if (windowingMode == WINDOWING_MODE_PINNED) {
-                return mPinnedStack;
+                return mRootPinnedTask;
             } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
-                return mSplitScreenPrimaryStack;
+                return mRootSplitScreenPrimaryTask;
             }
-            for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
-                final ActivityStack stack = mTaskStackContainers.getChildAt(i);
+            for (int i = mTaskContainers.getChildCount() - 1; i >= 0; --i) {
+                final ActivityStack stack = mTaskContainers.getChildAt(i);
                 if (activityType == ACTIVITY_TYPE_UNDEFINED
                         && windowingMode == stack.getWindowingMode()) {
                     // Passing in undefined type means we want to match the topmost stack with the
@@ -4166,37 +4188,33 @@
 
         @VisibleForTesting
         ActivityStack getTopStack() {
-            return mTaskStackContainers.getChildCount() > 0
-                    ? mTaskStackContainers.getChildAt(mTaskStackContainers.getChildCount() - 1) : null;
+            return mTaskContainers.getChildCount() > 0
+                    ? mTaskContainers.getChildAt(mTaskContainers.getChildCount() - 1) : null;
         }
 
         int getIndexOf(ActivityStack stack) {
-            return mTaskStackContainers.mChildren.indexOf(stack);
+            return mTaskContainers.mChildren.indexOf(stack);
         }
 
-        ActivityStack getHomeStack() {
-            if (mHomeStack == null && mDisplayId == DEFAULT_DISPLAY) {
+        ActivityStack getRootHomeTask() {
+            if (mRootHomeTask == null && mDisplayId == DEFAULT_DISPLAY) {
                 Slog.e(TAG_WM, "getHomeStack: Returning null from this=" + this);
             }
-            return mHomeStack;
+            return mRootHomeTask;
         }
 
-        ActivityStack getRecentsStack() {
-            return mRecentsStack;
+        ActivityStack getRootPinnedTask() {
+            return mRootPinnedTask;
         }
 
-        ActivityStack getPinnedStack() {
-            return mPinnedStack;
-        }
-
-        ActivityStack getSplitScreenPrimaryStack() {
-            return mSplitScreenPrimaryStack;
+        ActivityStack getRootSplitScreenPrimaryTask() {
+            return mRootSplitScreenPrimaryTask;
         }
 
         ArrayList<Task> getVisibleTasks() {
             final ArrayList<Task> visibleTasks = new ArrayList<>();
             forAllTasks(task -> {
-                if (task.isVisible()) {
+                if (!task.isRootTask() && task.isVisible()) {
                     visibleTasks.add(task);
                 }
             });
@@ -4206,7 +4224,7 @@
         void onStackWindowingModeChanged(ActivityStack stack) {
             removeStackReferenceIfNeeded(stack);
             addStackReferenceIfNeeded(stack);
-            if (stack == mPinnedStack && getTopStack() != stack) {
+            if (stack == mRootPinnedTask && getTopStack() != stack) {
                 // Looks like this stack changed windowing mode to pinned. Move it to the top.
                 positionChildAt(POSITION_TOP, stack, false /* includingParents */);
             }
@@ -4214,49 +4232,49 @@
 
         private void addStackReferenceIfNeeded(ActivityStack stack) {
             if (stack.isActivityTypeHome()) {
-                if (mHomeStack != null) {
-                    throw new IllegalArgumentException("addStackReferenceIfNeeded: home stack="
-                            + mHomeStack + " already exist on display=" + this + " stack=" + stack);
-
+                if (mRootHomeTask != null) {
+                    if (!stack.isDescendantOf(mRootHomeTask)) {
+                        throw new IllegalArgumentException("addStackReferenceIfNeeded: home stack="
+                                + mRootHomeTask + " already exist on display=" + this
+                                + " stack=" + stack);
+                    }
+                } else {
+                    mRootHomeTask = stack;
                 }
-                mHomeStack = stack;
-            } else if (stack.isActivityTypeRecents()) {
-                if (mRecentsStack != null && mRecentsStack != stack) {
-                    throw new IllegalArgumentException(
-                        "addStackReferenceIfNeeded: recents stack=" + mRecentsStack
-                            + " already exist on display=" + this + " stack=" + stack);
-                }
-                mRecentsStack = stack;
             }
             final int windowingMode = stack.getWindowingMode();
             if (windowingMode == WINDOWING_MODE_PINNED) {
-                if (mPinnedStack != null) {
-                    throw new IllegalArgumentException("addStackReferenceIfNeeded: pinned stack="
-                            + mPinnedStack + " already exist on display=" + this
-                            + " stack=" + stack);
+                if (mRootPinnedTask != null) {
+                    if (!stack.isDescendantOf(mRootPinnedTask)) {
+                        throw new IllegalArgumentException(
+                                "addStackReferenceIfNeeded: pinned stack=" + mRootPinnedTask
+                                        + " already exist on display=" + this + " stack=" + stack);
+                    }
+                } else {
+                    mRootPinnedTask = stack;
                 }
-                mPinnedStack = stack;
             } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
-                if (mSplitScreenPrimaryStack != null) {
-                    throw new IllegalArgumentException("addStackReferenceIfNeeded:"
-                            + " split-screen-primary" + " stack=" + mSplitScreenPrimaryStack
-                            + " already exist on display=" + this + " stack=" + stack);
+                if (mRootSplitScreenPrimaryTask != null) {
+                    if (!stack.isDescendantOf(mRootSplitScreenPrimaryTask)) {
+                        throw new IllegalArgumentException("addStackReferenceIfNeeded:"
+                                + " split-screen-primary" + " stack=" + mRootSplitScreenPrimaryTask
+                                + " already exist on display=" + this + " stack=" + stack);
+                    }
+                } else {
+                    mRootSplitScreenPrimaryTask = stack;
+                    mDisplayContent.onSplitScreenModeActivated();
+                    mDividerControllerLocked.notifyDockedStackExistsChanged(true);
                 }
-                mSplitScreenPrimaryStack = stack;
-                mDisplayContent.onSplitScreenModeActivated();
-                mDividerControllerLocked.notifyDockedStackExistsChanged(true);
             }
         }
 
         void removeStackReferenceIfNeeded(ActivityStack stack) {
-            if (stack == mHomeStack) {
-                mHomeStack = null;
-            } else if (stack == mRecentsStack) {
-                mRecentsStack = null;
-            } else if (stack == mPinnedStack) {
-                mPinnedStack = null;
-            } else if (stack == mSplitScreenPrimaryStack) {
-                mSplitScreenPrimaryStack = null;
+            if (stack == mRootHomeTask) {
+                mRootHomeTask = null;
+            } else if (stack == mRootPinnedTask) {
+                mRootPinnedTask = null;
+            } else if (stack == mRootSplitScreenPrimaryTask) {
+                mRootSplitScreenPrimaryTask = null;
                 mDisplayContent.onSplitScreenModeDismissed();
                 // Re-set the split-screen create mode whenever the split-screen stack is removed.
                 mWmService.setDockedStackCreateStateLocked(
@@ -4293,8 +4311,9 @@
 
         @Override
         void positionChildAt(int position, ActivityStack child, boolean includingParents) {
-            if (child.getWindowConfiguration().isAlwaysOnTop()
-                    && position != POSITION_TOP && position != mChildren.size()) {
+            final boolean moveToTop = (position == POSITION_TOP || position == getChildCount());
+            final boolean moveToBottom = (position == POSITION_BOTTOM || position == 0);
+            if (child.getWindowConfiguration().isAlwaysOnTop() && !moveToTop) {
                 // This stack is always-on-top, override the default behavior.
                 Slog.w(TAG_WM, "Ignoring move of always-on-top stack=" + this + " to bottom");
 
@@ -4310,18 +4329,14 @@
                 includingParents = false;
             }
             final int targetPosition = findPositionForStack(position, child, false /* adding */);
-            super.positionChildAt(targetPosition, child, includingParents);
+            super.positionChildAt(targetPosition, child, false /* includingParents */);
 
-            if (includingParents) {
-                // We still want to move the display of this stack container to top because even the
-                // target position is adjusted to non-top, the intention of the condition is to have
-                // higher z-order to gain focus (e.g. moving a task of a fullscreen stack to front
-                // in a non-top display which is using picture-in-picture mode).
-                final int topChildPosition = getChildCount() - 1;
-                if (targetPosition < topChildPosition && position >= topChildPosition) {
-                    getParent().positionChildAt(POSITION_TOP, this /* child */,
-                            true /* includingParents */);
-                }
+            if (includingParents && (moveToTop || moveToBottom)) {
+                // The DisplayContent children do not re-order, but we still want to move the
+                // display of this stack container because the intention of positioning is to have
+                // higher z-order to gain focus.
+                positionDisplayAt(moveToTop ? POSITION_TOP : POSITION_BOTTOM,
+                        true /* includingParents */);
             }
 
             setLayoutNeeded();
@@ -4361,9 +4376,9 @@
             int minPosition = POSITION_BOTTOM;
 
             if (stack.isAlwaysOnTop()) {
-                if (hasPinnedStack()) {
+                if (hasPinnedTask()) {
                     // Always-on-top stacks go below the pinned stack.
-                    maxPosition = getStacks().indexOf(mPinnedStack) - 1;
+                    maxPosition = getStacks().indexOf(mRootPinnedTask) - 1;
                 }
                 // Always-on-top stacks need to be above all other stacks.
                 minPosition = belowAlwaysOnTopPosition !=
@@ -4482,17 +4497,17 @@
         }
 
         @Override
-        int getOrientation() {
+        int getOrientation(int candidate) {
             if (isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)) {
                 // Apps and their containers are not allowed to specify an orientation while the
                 // docked stack is visible...except for the home stack if the docked stack is
                 // minimized and it actually set something and the bounds is different from  the
                 // display.
-                if (mHomeStack != null && mHomeStack.isVisible()
+                if (mRootHomeTask != null && mRootHomeTask.isVisible()
                         && mDividerControllerLocked.isMinimizedDock()
                         && !(mDividerControllerLocked.isHomeStackResizable()
-                        && mHomeStack.matchParentBounds())) {
-                    final int orientation = mHomeStack.getOrientation();
+                        && mRootHomeTask.matchParentBounds())) {
+                    final int orientation = mRootHomeTask.getOrientation();
                     if (orientation != SCREEN_ORIENTATION_UNSET) {
                         return orientation;
                     }
@@ -4500,7 +4515,7 @@
                 return SCREEN_ORIENTATION_UNSPECIFIED;
             }
 
-            final int orientation = super.getOrientation();
+            final int orientation = super.getOrientation(candidate);
             if (orientation != SCREEN_ORIENTATION_UNSET
                     && orientation != SCREEN_ORIENTATION_BEHIND) {
                 ProtoLog.v(WM_DEBUG_ORIENTATION,
@@ -4641,38 +4656,11 @@
         }
 
         @Override
-        SurfaceControl.Builder makeChildSurface(WindowContainer child) {
-            final SurfaceControl.Builder builder = super.makeChildSurface(child);
-            if (child instanceof WindowToken && ((WindowToken) child).mRoundedCornerOverlay) {
-                // To draw above the ColorFade layer during the screen off transition, the
-                // rounded corner overlays need to be at the root of the surface hierarchy.
-                // TODO: move the ColorLayer into the display overlay layer such that this is not
-                // necessary anymore.
-                builder.setParent(null);
-            }
-            return builder;
-        }
-
-        @Override
         void assignChildLayers(SurfaceControl.Transaction t) {
-            assignChildLayers(t, null /* imeContainer */);
-        }
-
-        void assignChildLayers(SurfaceControl.Transaction t, WindowContainer imeContainer) {
-            boolean needAssignIme = imeContainer != null
-                    && imeContainer.getSurfaceControl() != null;
+            boolean needAssignIme = mImeWindowsContainers.getSurfaceControl() != null;
             for (int j = 0; j < mChildren.size(); ++j) {
                 final WindowToken wt = mChildren.get(j);
 
-                // See {@link mSplitScreenDividerAnchor}
-                if (wt.windowType == TYPE_DOCK_DIVIDER) {
-                    wt.assignRelativeLayer(t, mTaskStackContainers.getSplitScreenDividerAnchor(), 1);
-                    continue;
-                }
-                if (wt.mRoundedCornerOverlay) {
-                    wt.assignLayer(t, WindowManagerPolicy.COLOR_FADE_LAYER + 1);
-                    continue;
-                }
                 wt.assignLayer(t, j);
                 wt.assignChildLayers(t);
 
@@ -4681,13 +4669,10 @@
 
                 if (needAssignIme && layer >= mWmService.mPolicy.getWindowLayerFromTypeLw(
                         TYPE_INPUT_METHOD_DIALOG, true)) {
-                    imeContainer.assignRelativeLayer(t, wt.getSurfaceControl(), -1);
+                    mImeWindowsContainers.assignRelativeLayer(t, wt.getSurfaceControl(), -1);
                     needAssignIme = false;
                 }
             }
-            if (needAssignIme) {
-                imeContainer.assignRelativeLayer(t, getSurfaceControl(), Integer.MAX_VALUE);
-            }
         }
     }
 
@@ -4701,8 +4686,9 @@
 
         @Override
         void assignChildLayers(SurfaceControl.Transaction t) {
+            mImeWindowsContainers.setNeedsLayer();
             mBelowAppWindowsContainers.assignLayer(t, 0);
-            mTaskStackContainers.assignLayer(t, 1);
+            mTaskContainers.assignLayer(t, 1);
             mAboveAppWindowsContainers.assignLayer(t, 2);
 
             final WindowState imeTarget = mInputMethodTarget;
@@ -4736,15 +4722,14 @@
                         // TODO: We need to use an extra level on the app surface to ensure
                         // this is always above SurfaceView but always below attached window.
                         1);
-                needAssignIme = false;
             }
 
             // Above we have assigned layers to our children, now we ask them to assign
             // layers to their children.
             mBelowAppWindowsContainers.assignChildLayers(t);
-            mTaskStackContainers.assignChildLayers(t);
-            mAboveAppWindowsContainers.assignChildLayers(t,
-                    needAssignIme ? mImeWindowsContainers : null);
+            mTaskContainers.assignChildLayers(t);
+            mAboveAppWindowsContainers.assignChildLayers(t);
+            mImeWindowsContainers.assignRelativeLayer(t, getSurfaceControl(), Integer.MAX_VALUE);
             mImeWindowsContainers.assignChildLayers(t);
         }
 
@@ -4755,52 +4740,11 @@
 
         void addChildren() {
             addChild(mBelowAppWindowsContainers, null);
-            addChild(mTaskStackContainers, null);
+            addChild(mTaskContainers, null);
             addChild(mAboveAppWindowsContainers, null);
             addChild(mImeWindowsContainers, null);
         }
 
-        /**
-         * In split-screen mode we process the IME containers above the docked divider
-         * rather than directly above their target.
-         */
-        private boolean skipTraverseChild(WindowContainer child) {
-            return child == mImeWindowsContainers && mInputMethodTarget != null
-                    && !hasSplitScreenPrimaryStack();
-        }
-
-        @Override
-        boolean forAllWindows(ToBooleanFunction<WindowState> callback,
-                boolean traverseTopToBottom) {
-            // Special handling so we can process IME windows with #forAllImeWindows above their IME
-            // target, or here in order if there isn't an IME target.
-            if (traverseTopToBottom) {
-                for (int i = mChildren.size() - 1; i >= 0; --i) {
-                    final WindowContainer child = mChildren.get(i);
-                    if (skipTraverseChild(child)) {
-                        continue;
-                    }
-
-                    if (child.forAllWindows(callback, traverseTopToBottom)) {
-                        return true;
-                    }
-                }
-            } else {
-                final int count = mChildren.size();
-                for (int i = 0; i < count; i++) {
-                    final WindowContainer child = mChildren.get(i);
-                    if (skipTraverseChild(child)) {
-                        continue;
-                    }
-
-                    if (child.forAllWindows(callback, traverseTopToBottom)) {
-                        return true;
-                    }
-                }
-            }
-            return false;
-        }
-
         @Override
         void positionChildAt(int position, WindowContainer child, boolean includingParents) {
             // Children of the WindowContainers are statically ordered, so the real intention here
@@ -4826,11 +4770,25 @@
                         token2.mOwnerCanManageAppTokens) ? -1 : 1;
 
         private final Predicate<WindowState> mGetOrientingWindow = w -> {
-            if (!w.isVisibleLw() || !w.mLegacyPolicyVisibilityAfterAnim) {
-                return false;
+            final WindowManagerPolicy policy = mWmService.mPolicy;
+            if (policy.isKeyguardHostWindow(w.mAttrs)) {
+                if (mWmService.mKeyguardGoingAway) {
+                    return false;
+                }
+                // Consider unoccluding only when all unknown visibilities have been
+                // resolved, as otherwise we just may be starting another occluding activity.
+                final boolean isUnoccluding =
+                        mDisplayContent.mAppTransition.getAppTransition()
+                                == TRANSIT_KEYGUARD_UNOCCLUDE
+                                && mDisplayContent.mUnknownAppVisibilityController.allResolved();
+                // If keyguard is showing, or we're unoccluding, force the keyguard's orientation,
+                // even if SystemUI hasn't updated the attrs yet.
+                if (policy.isKeyguardShowingAndNotOccluded() || isUnoccluding) {
+                    return true;
+                }
             }
             final int req = w.mAttrs.screenOrientation;
-            if(req == SCREEN_ORIENTATION_UNSPECIFIED || req == SCREEN_ORIENTATION_BEHIND
+            if (req == SCREEN_ORIENTATION_UNSPECIFIED || req == SCREEN_ORIENTATION_BEHIND
                     || req == SCREEN_ORIENTATION_UNSET) {
                 return false;
             }
@@ -4857,39 +4815,27 @@
         }
 
         @Override
-        int getOrientation() {
-            final WindowManagerPolicy policy = mWmService.mPolicy;
+        int getOrientation(int candidate) {
             // Find a window requesting orientation.
             final WindowState win = getWindow(mGetOrientingWindow);
 
             if (win != null) {
-                final int req = win.mAttrs.screenOrientation;
-                if (policy.isKeyguardHostWindow(win.mAttrs)) {
-                    mLastKeyguardForcedOrientation = req;
-                    if (mWmService.mKeyguardGoingAway) {
-                        // Keyguard can't affect the orientation if it is going away...
-                        mLastWindowForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
-                        return SCREEN_ORIENTATION_UNSET;
-                    }
-                }
+                int req = win.mAttrs.screenOrientation;
                 ProtoLog.v(WM_DEBUG_ORIENTATION,
                         "%s forcing orientation to %d for display id=%d", win, req,
                         mDisplayId);
-                return (mLastWindowForcedOrientation = req);
+                if (mWmService.mPolicy.isKeyguardHostWindow(win.mAttrs)) {
+                    // SystemUI controls the Keyguard orientation asynchronously, and mAttrs may be
+                    // stale. We record / use the last known override.
+                    if (req != SCREEN_ORIENTATION_UNSET && req != SCREEN_ORIENTATION_UNSPECIFIED) {
+                        mDisplayContent.mLastKeyguardForcedOrientation = req;
+                    } else {
+                        req = mDisplayContent.mLastKeyguardForcedOrientation;
+                    }
+                }
+                return req;
             }
-
-            mLastWindowForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
-
-            // Only allow force setting the orientation when all unknown visibilities have been
-            // resolved, as otherwise we just may be starting another occluding activity.
-            final boolean isUnoccluding =
-                    mAppTransition.getAppTransition() == TRANSIT_KEYGUARD_UNOCCLUDE
-                            && mUnknownAppVisibilityController.allResolved();
-            if (policy.isKeyguardShowingAndNotOccluded() || isUnoccluding) {
-                return mLastKeyguardForcedOrientation;
-            }
-
-            return SCREEN_ORIENTATION_UNSET;
+            return candidate;
         }
 
         @Override
@@ -4914,6 +4860,74 @@
         }
     }
 
+    /**
+     * Container for IME windows.
+     *
+     * This has some special behaviors:
+     * - layers assignment is ignored except if setNeedsLayer() has been called before (and no
+     *   layer has been assigned since), to facilitate assigning the layer from the IME target, or
+     *   fall back if there is no target.
+     * - the container doesn't always participate in window traversal, according to
+     *   {@link #skipImeWindowsDuringTraversal()}
+     */
+    private class ImeContainer extends NonAppWindowContainers {
+        boolean mNeedsLayer = false;
+
+        ImeContainer(WindowManagerService wms) {
+            super("ImeContainer", wms);
+        }
+
+        public void setNeedsLayer() {
+            mNeedsLayer = true;
+        }
+
+        @Override
+        int getOrientation(int candidate) {
+            // IME does not participate in orientation.
+            return candidate;
+        }
+
+        @Override
+        boolean forAllWindows(ToBooleanFunction<WindowState> callback,
+                boolean traverseTopToBottom) {
+            final DisplayContent dc = mDisplayContent;
+            if (skipImeWindowsDuringTraversal(dc)) {
+                return false;
+            }
+            return super.forAllWindows(callback, traverseTopToBottom);
+        }
+
+        private boolean skipImeWindowsDuringTraversal(DisplayContent dc) {
+            // We skip IME windows so they're processed just above their target, except
+            // in split-screen mode where we process the IME containers above the docked divider.
+            return dc.mInputMethodTarget != null && !dc.hasSplitScreenPrimaryTask();
+        }
+
+        /** Like {@link #forAllWindows}, but ignores {@link #skipImeWindowsDuringTraversal} */
+        boolean forAllWindowForce(ToBooleanFunction<WindowState> callback,
+                boolean traverseTopToBottom) {
+            return super.forAllWindows(callback, traverseTopToBottom);
+        }
+
+        @Override
+        void assignLayer(Transaction t, int layer) {
+            if (!mNeedsLayer) {
+                return;
+            }
+            super.assignLayer(t, layer);
+            mNeedsLayer = false;
+        }
+
+        @Override
+        void assignRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) {
+            if (!mNeedsLayer) {
+                return;
+            }
+            super.assignRelativeLayer(t, relativeTo, layer);
+            mNeedsLayer = false;
+        }
+    }
+
     @Override
     SurfaceSession getSession() {
         return mSession;
@@ -5017,6 +5031,7 @@
      * with {@link WindowState#assignLayer}
      */
     void assignRelativeLayerForImeTargetChild(SurfaceControl.Transaction t, WindowContainer child) {
+        mImeWindowsContainers.setNeedsLayer();
         child.assignRelativeLayer(t, mImeWindowsContainers.getSurfaceControl(), 1);
     }
 
@@ -5036,7 +5051,7 @@
     }
 
     void assignStackOrdering() {
-        mTaskStackContainers.assignStackOrdering(getPendingTransaction());
+        mTaskContainers.assignStackOrdering(getPendingTransaction());
     }
 
     /**
@@ -5400,7 +5415,7 @@
                 SYSTEM_UI_FLAG_HIDE_NAVIGATION | SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
         final boolean stickyHideNav =
                 (sysUiVisibility & stickyHideNavFlags) == stickyHideNavFlags;
-        return !stickyHideNav && type != TYPE_INPUT_METHOD && type != TYPE_STATUS_BAR
+        return !stickyHideNav && type != TYPE_INPUT_METHOD && type != TYPE_NOTIFICATION_SHADE
                 && win.getActivityType() != ACTIVITY_TYPE_HOME;
     }
 
@@ -5625,8 +5640,8 @@
             if (currentFocusedStack != prevFocusedStack) {
                 mLastFocusedStack = prevFocusedStack;
                 EventLogTags.writeWmFocusedStack(mRootWindowContainer.mCurrentUser, mDisplayId,
-                        currentFocusedStack == null ? -1 : currentFocusedStack.getStackId(),
-                        mLastFocusedStack == null ? -1 : mLastFocusedStack.getStackId(),
+                        currentFocusedStack == null ? -1 : currentFocusedStack.getRootTaskId(),
+                        mLastFocusedStack == null ? -1 : mLastFocusedStack.getRootTaskId(),
                         updateLastFocusedStackReason);
             }
         }
@@ -5634,10 +5649,10 @@
         onStackOrderChanged(stack);
     }
 
-    ActivityStack getStack(int stackId) {
+    ActivityStack getStack(int rootTaskId) {
         for (int i = getStackCount() - 1; i >= 0; --i) {
             final ActivityStack stack = getStackAt(i);
-            if (stack.mStackId == stackId) {
+            if (stack.getRootTaskId() == rootTaskId) {
                 return stack;
             }
         }
@@ -5693,6 +5708,10 @@
         return mAtmService.mStackSupervisor.getNextTaskIdForUser();
     }
 
+    ActivityStack createStack(int windowingMode, int activityType, boolean onTop) {
+        return createStack(windowingMode, activityType, onTop, null /*info*/, null /*intent*/);
+    }
+
     /**
      * Creates a stack matching the input windowing mode and activity type on this display.
      * @param windowingMode The windowing mode the stack should be created in. If
@@ -5704,13 +5723,14 @@
      * @param onTop If true the stack will be created at the top of the display, else at the bottom.
      * @return The newly created stack.
      */
-    ActivityStack createStack(int windowingMode, int activityType, boolean onTop) {
+    ActivityStack createStack(int windowingMode, int activityType, boolean onTop, ActivityInfo info,
+            Intent intent) {
         if (mSingleTaskInstance && getStackCount() > 0) {
             // Create stack on default display instead since this display can only contain 1 stack.
             // TODO: Kinda a hack, but better that having the decision at each call point. Hoping
             // this goes away once ActivityView is no longer using virtual displays.
             return mRootWindowContainer.getDefaultDisplay().createStack(
-                    windowingMode, activityType, onTop);
+                    windowingMode, activityType, onTop, info, intent);
         }
 
         if (activityType == ACTIVITY_TYPE_UNDEFINED) {
@@ -5738,18 +5758,23 @@
         }
 
         final int stackId = getNextStackId();
-        return createStackUnchecked(windowingMode, activityType, stackId, onTop);
+        return createStackUnchecked(windowingMode, activityType, stackId, onTop, info, intent);
     }
 
     @VisibleForTesting
     ActivityStack createStackUnchecked(int windowingMode, int activityType,
-            int stackId, boolean onTop) {
+            int stackId, boolean onTop, ActivityInfo info, Intent intent) {
         if (windowingMode == WINDOWING_MODE_PINNED && activityType != ACTIVITY_TYPE_STANDARD) {
             throw new IllegalArgumentException("Stack with windowing mode cannot with non standard "
                     + "activity type.");
         }
+        if (info == null) {
+            info = new ActivityInfo();
+            info.applicationInfo = new ApplicationInfo();
+        }
+
         final ActivityStack stack = new ActivityStack(this, stackId,
-                mRootWindowContainer.mStackSupervisor, activityType);
+                mRootWindowContainer.mStackSupervisor, activityType, info, intent);
         addStack(stack, onTop ? POSITION_TOP : POSITION_BOTTOM);
         stack.setWindowingMode(windowingMode, false /* animate */, false /* showRecents */,
                 false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */,
@@ -5870,7 +5895,7 @@
             final ActivityRecord resumedActivity = stack.getResumedActivity();
             if (resumedActivity != null
                     && (stack.getVisibility(resuming) != STACK_VISIBILITY_VISIBLE
-                    || !stack.isFocusable())) {
+                    || !stack.isTopActivityFocusable())) {
                 if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack
                         + " mResumedActivity=" + resumedActivity);
                 someActivityPaused |= stack.startPausingLocked(userLeaving, false /* uiSleeping*/,
@@ -5987,7 +6012,7 @@
         } finally {
             final ActivityStack topFullscreenStack =
                     getTopStackInWindowingMode(WINDOWING_MODE_FULLSCREEN);
-            final ActivityStack homeStack = getHomeStack();
+            final ActivityStack homeStack = getRootHomeTask();
             if (topFullscreenStack != null && homeStack != null && !isTopStack(homeStack)) {
                 // Whenever split-screen is dismissed we want the home stack directly behind the
                 // current top fullscreen stack so it shows up when the top stack is finished.
@@ -6005,7 +6030,7 @@
         mAtmService.deferWindowLayout();
         try {
             // Adjust the windowing mode of any affected by split-screen to split-screen secondary.
-            final ActivityStack splitScreenPrimaryStack = getSplitScreenPrimaryStack();
+            final ActivityStack splitScreenPrimaryStack = getRootSplitScreenPrimaryTask();
             for (int i = getStackCount() - 1; i >= 0; --i) {
                 final ActivityStack otherStack = getStackAt(i);
                 if (otherStack == splitScreenPrimaryStack
@@ -6134,7 +6159,7 @@
             }
         }
 
-        final boolean inSplitScreenMode = hasSplitScreenPrimaryStack();
+        final boolean inSplitScreenMode = hasSplitScreenPrimaryTask();
         if (!inSplitScreenMode
                 && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
             // Switch to the display's windowing mode if we are not in split-screen mode and we are
@@ -6193,7 +6218,7 @@
             for (int i = getStackCount() - 1; i >= 0; --i) {
                 final ActivityStack stack = getStackAt(i);
                 // Only consider focusable stacks other than the current focused one.
-                if (stack == focusedStack || !stack.isFocusable()) {
+                if (stack == focusedStack || !stack.isTopActivityFocusable()) {
                     continue;
                 }
                 topRunning = stack.topRunningActivity();
@@ -6380,7 +6405,7 @@
                     // If default display is in split-window mode, set windowing mode of the stack
                     // to split-screen secondary. Otherwise, set the windowing mode to undefined by
                     // default to let stack inherited the windowing mode from the new display.
-                    final int windowingMode = toDisplay.hasSplitScreenPrimaryStack()
+                    final int windowingMode = toDisplay.hasSplitScreenPrimaryTask()
                             ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
                             : WINDOWING_MODE_UNDEFINED;
                     stack.reparent(toDisplay, true /* onTop */);
@@ -6549,7 +6574,7 @@
     }
 
     void moveHomeStackToFront(String reason) {
-        final ActivityStack homeStack = getHomeStack();
+        final ActivityStack homeStack = getRootHomeTask();
         if (homeStack != null) {
             homeStack.moveToFront(reason);
         }
@@ -6575,7 +6600,7 @@
 
     @Nullable
     ActivityRecord getHomeActivityForUser(int userId) {
-        final ActivityStack homeStack = getHomeStack();
+        final ActivityStack homeStack = getRootHomeTask();
         if (homeStack == null) {
             return null;
         }
@@ -6664,7 +6689,7 @@
 
     public void dumpStacks(PrintWriter pw) {
         for (int i = getStackCount() - 1; i >= 0; --i) {
-            pw.print(getStackAt(i).mStackId);
+            pw.print(getStackAt(i).getRootTaskId());
             if (i > 0) {
                 pw.print(",");
             }
@@ -6688,4 +6713,50 @@
     Context getDisplayUiContext() {
         return mDisplayPolicy.getSystemUiContext();
     }
+
+    class RemoteInsetsControlTarget implements InsetsControlTarget {
+        private final IDisplayWindowInsetsController mRemoteInsetsController;
+
+        RemoteInsetsControlTarget(IDisplayWindowInsetsController controller) {
+            mRemoteInsetsController = controller;
+        }
+
+        void notifyInsetsChanged() {
+            try {
+                mRemoteInsetsController.insetsChanged(
+                        getInsetsStateController().getRawInsetsState());
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to deliver inset state change", e);
+            }
+        }
+
+        @Override
+        public void notifyInsetsControlChanged() {
+            final InsetsStateController stateController = getInsetsStateController();
+            try {
+                mRemoteInsetsController.insetsControlChanged(stateController.getRawInsetsState(),
+                        stateController.getControlsForDispatch(this));
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to deliver inset state change", e);
+            }
+        }
+
+        @Override
+        public void showInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme) {
+            try {
+                mRemoteInsetsController.showInsets(types, fromIme);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to deliver showInsets", e);
+            }
+        }
+
+        @Override
+        public void hideInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme) {
+            try {
+                mRemoteInsetsController.hideInsets(types, fromIme);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to deliver showInsets", e);
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index f9ad03f..f8df883 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -58,12 +58,11 @@
 import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
@@ -77,6 +76,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
 import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
 import static android.view.WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
@@ -289,6 +289,7 @@
 
     private final ArraySet<WindowState> mScreenDecorWindows = new ArraySet<>();
     private WindowState mStatusBar = null;
+    private WindowState mNotificationShade = null;
     private final int[] mStatusBarHeightForRotation = new int[4];
     private WindowState mNavigationBar = null;
     @NavigationBarPosition
@@ -360,7 +361,6 @@
     private static final Rect sTmpRect = new Rect();
     private static final Rect sTmpNavFrame = new Rect();
     private static final Rect sTmpLastParentFrame = new Rect();
-    private static final int[] sTmpTypesAndSides = new int[2];
 
     private WindowState mTopFullscreenOpaqueWindowState;
     private WindowState mTopFullscreenOpaqueOrDimmingWindowState;
@@ -368,8 +368,6 @@
     private WindowState mTopDockedOpaqueOrDimmingWindowState;
     private boolean mTopIsFullscreen;
     private boolean mForceStatusBar;
-    private boolean mForceStatusBarFromKeyguard;
-    private boolean mForceStatusBarTransparent;
     private int mNavBarOpacityMode = NAV_BAR_OPAQUE_WHEN_FREEFORM_OR_DOCKED;
     private boolean mForcingShowNavBar;
     private int mForcingShowNavBarLayer;
@@ -851,15 +849,13 @@
                 // letterboxed. Hence always let them extend under the cutout.
                 attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
                 break;
-            case TYPE_STATUS_BAR:
-
+            case TYPE_NOTIFICATION_SHADE:
                 // If the Keyguard is in a hidden state (occluded by another window), we force to
                 // remove the wallpaper and keyguard flag so that any change in-flight after setting
                 // the keyguard as occluded wouldn't set these flags again.
                 // See {@link #processKeyguardSetHiddenResultLw}.
                 if (mService.mPolicy.isKeyguardOccluded()) {
                     attrs.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
-                    attrs.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
                 }
                 break;
 
@@ -890,11 +886,21 @@
                 // Toasts can't be clickable
                 attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
                 break;
-        }
 
-        if (attrs.type != TYPE_STATUS_BAR) {
-            // The status bar is the only window allowed to exhibit keyguard behavior.
-            attrs.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
+            case TYPE_BASE_APPLICATION:
+
+                // A non-translucent main app window isn't allowed to fit insets, as it would create
+                // a hole on the display!
+                if (attrs.isFullscreen() && win.mActivityRecord != null
+                        && win.mActivityRecord.fillsParent()
+                        && (win.mAttrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0
+                        && attrs.getFitInsetsTypes() != 0) {
+                    throw new RuntimeException("Illegal attributes: Main activity window that isn't"
+                            + " translucent trying to fit insets: "
+                            + attrs.getFitInsetsTypes()
+                            + " attrs=" + attrs);
+                }
+                break;
         }
     }
 
@@ -914,6 +920,7 @@
      * Currently enforces that two window types are singletons per display:
      * <ul>
      * <li>{@link WindowManager.LayoutParams#TYPE_STATUS_BAR}</li>
+     * <li>{@link WindowManager.LayoutParams#TYPE_NOTIFICATION_SHADE}</li>
      * <li>{@link WindowManager.LayoutParams#TYPE_NAVIGATION_BAR}</li>
      * </ul>
      *
@@ -940,6 +947,16 @@
                     }
                 }
                 break;
+            case TYPE_NOTIFICATION_SHADE:
+                mContext.enforceCallingOrSelfPermission(
+                        android.Manifest.permission.STATUS_BAR_SERVICE,
+                        "DisplayPolicy");
+                if (mNotificationShade != null) {
+                    if (mNotificationShade.isAlive()) {
+                        return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
+                    }
+                }
+                break;
             case TYPE_NAVIGATION_BAR:
                 mContext.enforceCallingOrSelfPermission(
                         android.Manifest.permission.STATUS_BAR_SERVICE,
@@ -974,12 +991,15 @@
         }
 
         switch (attrs.type) {
-            case TYPE_STATUS_BAR:
-                mStatusBar = win;
-                mStatusBarController.setWindow(win);
+            case TYPE_NOTIFICATION_SHADE:
+                mNotificationShade = win;
                 if (mDisplayContent.isDefaultDisplay) {
                     mService.mPolicy.setKeyguardCandidateLw(win);
                 }
+                break;
+            case TYPE_STATUS_BAR:
+                mStatusBar = win;
+                mStatusBarController.setWindow(win);
                 final TriConsumer<DisplayFrames, WindowState, Rect> frameProvider =
                         (displayFrames, windowState, rect) -> {
                             rect.top = 0;
@@ -1036,14 +1056,16 @@
         if (mStatusBar == win) {
             mStatusBar = null;
             mStatusBarController.setWindow(null);
-            if (mDisplayContent.isDefaultDisplay) {
-                mService.mPolicy.setKeyguardCandidateLw(null);
-            }
             mDisplayContent.setInsetProvider(ITYPE_STATUS_BAR, null, null);
         } else if (mNavigationBar == win) {
             mNavigationBar = null;
             mNavigationBarController.setWindow(null);
             mDisplayContent.setInsetProvider(ITYPE_NAVIGATION_BAR, null, null);
+        } else if (mNotificationShade == win) {
+            mNotificationShade = null;
+            if (mDisplayContent.isDefaultDisplay) {
+                mService.mPolicy.setKeyguardCandidateLw(null);
+            }
         }
         if (mLastFocusedWindow == win) {
             mLastFocusedWindow = null;
@@ -1060,6 +1082,10 @@
         return mStatusBar;
     }
 
+    WindowState getNotificationShade() {
+        return mNotificationShade;
+    }
+
     WindowState getNavigationBar() {
         return mNavigationBar;
     }
@@ -1082,12 +1108,6 @@
         if (DEBUG_ANIM) Slog.i(TAG, "selectAnimation in " + win
                 + ": transit=" + transit);
         if (win == mStatusBar) {
-            final boolean isKeyguard = (win.getAttrs().privateFlags & PRIVATE_FLAG_KEYGUARD) != 0;
-            final boolean expanded = win.getAttrs().height == MATCH_PARENT
-                    && win.getAttrs().width == MATCH_PARENT;
-            if (isKeyguard || expanded) {
-                return ANIMATION_NONE;
-            }
             if (transit == TRANSIT_EXIT
                     || transit == TRANSIT_HIDE) {
                 return R.anim.dock_top_exit;
@@ -1240,7 +1260,7 @@
 
         if (layoutInScreenAndInsetDecor && !screenDecor) {
             if ((sysUiVis & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
-                    || (attrs.getFitWindowInsetsTypes() & Type.navigationBars()) == 0) {
+                    || (attrs.getFitInsetsTypes() & Type.navigationBars()) == 0) {
                 outFrame.set(displayFrames.mUnrestricted);
             } else {
                 outFrame.set(displayFrames.mRestricted);
@@ -1293,21 +1313,6 @@
         }
     }
 
-    private static void getImpliedTypesAndSidesToFit(LayoutParams attrs, int[] typesAndSides) {
-        typesAndSides[0] = attrs.getFitWindowInsetsTypes();
-        typesAndSides[1] = attrs.getFitWindowInsetsSides();
-        final boolean forceDrawsBarBackgrounds =
-                (attrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0
-                        && attrs.height == MATCH_PARENT && attrs.width == MATCH_PARENT;
-        if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 || forceDrawsBarBackgrounds) {
-            if ((attrs.privateFlags & PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND) != 0) {
-                typesAndSides[1] &= ~Side.BOTTOM;
-            } else {
-                typesAndSides[0] &= ~Type.systemBars();
-            }
-        }
-    }
-
     // TODO(b/118118435): remove after migration
     private static int getImpliedSysUiFlagsForLayout(LayoutParams attrs) {
         int impliedFlags = 0;
@@ -1425,10 +1430,10 @@
                 || (behavior & BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE) != 0;
         boolean navAllowedHidden = immersive || immersiveSticky;
         navTranslucent &= !immersiveSticky;  // transient trumps translucent
-        boolean isKeyguardShowing = isStatusBarKeyguard()
-                && !mService.mPolicy.isKeyguardOccluded();
-        boolean statusBarForcesShowingNavigation = !isKeyguardShowing && mStatusBar != null
-                && (mStatusBar.getAttrs().privateFlags
+        boolean isKeyguardShowing = isKeyguardShowing() && !isKeyguardOccluded();
+        boolean notificationShadeForcesShowingNavigation =
+                !isKeyguardShowing && mNotificationShade != null
+                && (mNotificationShade.getAttrs().privateFlags
                 & PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION) != 0;
 
         // When the navigation bar isn't visible, we put up a fake input window to catch all
@@ -1455,7 +1460,7 @@
         navVisible |= !canHideNavigationBar();
 
         boolean updateSysUiVisibility = layoutNavigationBar(displayFrames, uiMode, navVisible,
-                navTranslucent, navAllowedHidden, statusBarForcesShowingNavigation);
+                navTranslucent, navAllowedHidden, notificationShadeForcesShowingNavigation);
         if (DEBUG_LAYOUT) Slog.i(TAG, "mDock rect:" + displayFrames.mDock);
         updateSysUiVisibility |= layoutStatusBar(displayFrames, sysui, isKeyguardShowing);
         if (updateSysUiVisibility) {
@@ -1871,16 +1876,15 @@
         sf.set(displayFrames.mStable);
 
         if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_FULL) {
-            getImpliedTypesAndSidesToFit(attrs, sTmpTypesAndSides);
-            final @InsetsType int typesToFit = sTmpTypesAndSides[0];
-            final @InsetsSide int sidesToFit = sTmpTypesAndSides[1];
+            final @InsetsType int typesToFit = attrs.getFitInsetsTypes();
+            final @InsetsSide int sidesToFit = attrs.getFitInsetsSides();
             final ArraySet<Integer> types = InsetsState.toInternalType(typesToFit);
             final Rect dfu = displayFrames.mUnrestricted;
             Insets insets = Insets.of(0, 0, 0, 0);
             for (int i = types.size() - 1; i >= 0; i--) {
                 insets = Insets.max(insets, mDisplayContent.getInsetsPolicy()
                         .getInsetsForDispatch(win).getSource(types.valueAt(i))
-                        .calculateInsets(dfu, attrs.getFitIgnoreVisibility()));
+                        .calculateInsets(dfu, attrs.isFitInsetsIgnoringVisibility()));
             }
             final int left = (sidesToFit & Side.LEFT) != 0 ? insets.left : 0;
             final int top = (sidesToFit & Side.TOP) != 0 ? insets.top : 0;
@@ -1951,7 +1955,7 @@
             }
         } else if (type == TYPE_WALLPAPER) {
             layoutWallpaper(displayFrames, pf, df, cf);
-        } else if (win == mStatusBar) {
+        } else if (win == mStatusBar || type == TYPE_NOTIFICATION_SHADE) {
             df.set(displayFrames.mUnrestricted);
             pf.set(displayFrames.mUnrestricted);
             cf.set(displayFrames.mStable);
@@ -2061,7 +2065,8 @@
                         cf.set(displayFrames.mRestricted);
                     }
                     applyStableConstraints(sysUiFl, fl, cf, displayFrames);
-                    if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
+                    if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE
+                            && adjust != SOFT_INPUT_ADJUST_NOTHING) {
                         vf.set(displayFrames.mCurrent);
                     } else {
                         vf.set(cf);
@@ -2103,7 +2108,7 @@
                     df.set(displayFrames.mUnrestricted);
                     pf.set(displayFrames.mUnrestricted);
                 } else if ((sysUiFl & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
-                        && (type == TYPE_STATUS_BAR
+                        && (type == TYPE_NOTIFICATION_SHADE
                         || type == TYPE_TOAST
                         || type == TYPE_DOCK_DIVIDER
                         || type == TYPE_VOICE_INTERACTION_STARTING
@@ -2138,7 +2143,8 @@
 
                 applyStableConstraints(sysUiFl, fl, cf, displayFrames);
 
-                if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
+                if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE
+                        && adjust != SOFT_INPUT_ADJUST_NOTHING) {
                     vf.set(displayFrames.mCurrent);
                 } else {
                     vf.set(cf);
@@ -2179,7 +2185,8 @@
                         cf.set(displayFrames.mContent);
                         df.set(displayFrames.mContent);
                     }
-                    if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
+                    if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE
+                            && adjust != SOFT_INPUT_ADJUST_NOTHING) {
                         vf.set(displayFrames.mCurrent);
                     } else {
                         vf.set(cf);
@@ -2207,7 +2214,8 @@
 
         // Ensure that windows with a DEFAULT or NEVER display cutout mode are laid out in
         // the cutout safe zone.
-        if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES) {
+        if (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
+                || cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER) {
             final Rect displayCutoutSafeExceptMaybeBars = sTmpDisplayCutoutSafeExceptMaybeBarsRect;
             displayCutoutSafeExceptMaybeBars.set(displayFrames.mDisplayCutoutSafe);
             if (layoutInScreen && layoutInsetDecor && !requestedFullscreen
@@ -2352,8 +2360,6 @@
         mTopDockedOpaqueWindowState = null;
         mTopDockedOpaqueOrDimmingWindowState = null;
         mForceStatusBar = false;
-        mForceStatusBarFromKeyguard = false;
-        mForceStatusBarTransparent = false;
         mForcingShowNavBar = false;
         mForcingShowNavBarLayer = -1;
 
@@ -2381,14 +2387,6 @@
             mForcingShowNavBar = true;
             mForcingShowNavBarLayer = win.getSurfaceLayer();
         }
-        if (attrs.type == TYPE_STATUS_BAR) {
-            if ((attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
-                mForceStatusBarFromKeyguard = true;
-            }
-            if ((attrs.privateFlags & PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT) != 0) {
-                mForceStatusBarTransparent = true;
-            }
-        }
 
         boolean appWindow = attrs.type >= FIRST_APPLICATION_WINDOW
                 && attrs.type < FIRST_SYSTEM_WINDOW;
@@ -2510,32 +2508,25 @@
 
         if (mStatusBar != null) {
             if (DEBUG_LAYOUT) Slog.i(TAG, "force=" + mForceStatusBar
-                    + " forcefkg=" + mForceStatusBarFromKeyguard
                     + " top=" + mTopFullscreenOpaqueWindowState);
-            boolean shouldBeTransparent = mForceStatusBarTransparent
-                    && !mForceStatusBar
-                    && !mForceStatusBarFromKeyguard;
-            if (!shouldBeTransparent) {
-                mStatusBarController.setShowTransparent(false /* transparent */);
-            } else if (!mStatusBar.isVisibleLw()) {
-                mStatusBarController.setShowTransparent(true /* transparent */);
-            }
-
-            boolean statusBarForcesShowingNavigation =
-                    (mStatusBar.getAttrs().privateFlags
+            final boolean forceShowStatusBar = (mStatusBar.getAttrs().privateFlags
+                    & PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR) != 0;
+            final boolean notificationShadeForcesShowingNavigation =
+                    mNotificationShade != null
+                            && (mNotificationShade.getAttrs().privateFlags
                             & PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION) != 0;
+
             boolean topAppHidesStatusBar = topAppHidesStatusBar();
-            if (mForceStatusBar || mForceStatusBarFromKeyguard || mForceStatusBarTransparent
-                    || statusBarForcesShowingNavigation) {
+            if (mForceStatusBar || forceShowStatusBar) {
                 if (DEBUG_LAYOUT) Slog.v(TAG, "Showing status bar: forced");
                 if (mStatusBarController.setBarShowingLw(true)) {
                     changes |= FINISH_LAYOUT_REDO_LAYOUT;
                 }
                 // Maintain fullscreen layout until incoming animation is complete.
                 topIsFullscreen = mTopIsFullscreen && mStatusBar.isAnimatingLw();
-                // Transient status bar is not allowed if status bar is on lockscreen or status bar
-                // is expecting the navigation keys from the user.
-                if ((mForceStatusBarFromKeyguard || statusBarForcesShowingNavigation)
+                // Transient status bar is not allowed if notification shade is expecting the
+                // navigation keys from the user.
+                if (notificationShadeForcesShowingNavigation
                         && mStatusBarController.isTransientShowing()) {
                     mStatusBarController.updateVisibilityLw(false /*transientAllowed*/,
                             mLastSystemUiFlags, mLastSystemUiFlags);
@@ -3099,11 +3090,9 @@
         }
     }
 
-    private boolean isStatusBarKeyguard() {
-        return mStatusBar != null
-                && (mStatusBar.getAttrs().privateFlags & PRIVATE_FLAG_KEYGUARD) != 0;
+    boolean isKeyguardShowing() {
+        return mService.mPolicy.isKeyguardShowing();
     }
-
     private boolean isKeyguardOccluded() {
         // TODO (b/113840485): Handle per display keyguard.
         return mService.mPolicy.isKeyguardOccluded();
@@ -3136,7 +3125,7 @@
             // keys, we let it keep controlling the visibility.
             final boolean lastFocusCanReceiveKeys =
                     (mLastFocusedWindow != null && mLastFocusedWindow.canReceiveKeys());
-            winCandidate = isStatusBarKeyguard() ? mStatusBar
+            winCandidate = isKeyguardShowing() ? mNotificationShade
                     : lastFocusCanReceiveKeys ? mLastFocusedWindow
                             : mTopFullscreenOpaqueWindowState;
             if (winCandidate == null) {
@@ -3144,7 +3133,8 @@
             }
         }
         final WindowState win = winCandidate;
-        if ((win.getAttrs().privateFlags & PRIVATE_FLAG_KEYGUARD) != 0 && isKeyguardOccluded()) {
+        if (win.getAttrs().type == TYPE_NOTIFICATION_SHADE && isKeyguardShowing()
+                && isKeyguardOccluded()) {
             // We are updating at a point where the keyguard has gotten
             // focus, but we were last in a state where the top window is
             // hiding it.  This is probably because the keyguard as been
@@ -3278,8 +3268,8 @@
 
     private int updateLightStatusBarAppearanceLw(@Appearance int appearance, WindowState opaque,
             WindowState opaqueOrDimming) {
-        final boolean onKeyguard = isStatusBarKeyguard() && !isKeyguardOccluded();
-        final WindowState statusColorWin = onKeyguard ? mStatusBar : opaqueOrDimming;
+        final boolean onKeyguard = isKeyguardShowing() && !isKeyguardOccluded();
+        final WindowState statusColorWin = onKeyguard ? mNotificationShade : opaqueOrDimming;
         if (statusColorWin != null && (statusColorWin == opaque || onKeyguard)) {
             // If the top fullscreen-or-dimming window is also the top fullscreen, respect
             // its light flag.
@@ -3367,11 +3357,11 @@
         // visibility changes.
         mForceShowSystemBars = dockedStackVisible || win.inFreeformWindowingMode() || resizing
                 || mForceShowSystemBarsFromExternal;
-        final boolean forceOpaqueStatusBar = mForceShowSystemBars && !mForceStatusBarFromKeyguard;
+        final boolean forceOpaqueStatusBar = mForceShowSystemBars && !isKeyguardShowing();
 
         // apply translucent bar vis flags
-        WindowState fullscreenTransWin = isStatusBarKeyguard() && !isKeyguardOccluded()
-                ? mStatusBar
+        WindowState fullscreenTransWin = isKeyguardShowing() && !isKeyguardOccluded()
+                ? mNotificationShade
                 : mTopFullscreenOpaqueWindowState;
         vis = mStatusBarController.applyTranslucentFlagLw(fullscreenTransWin, vis, oldVis);
         vis = mNavigationBarController.applyTranslucentFlagLw(fullscreenTransWin, vis, oldVis);
@@ -3391,8 +3381,8 @@
 
         // prevent status bar interaction from clearing certain flags
         int type = win.getAttrs().type;
-        boolean statusBarHasFocus = type == TYPE_STATUS_BAR;
-        if (statusBarHasFocus && !isStatusBarKeyguard()) {
+        boolean notificationShadeHasFocus = type == TYPE_NOTIFICATION_SHADE;
+        if (notificationShadeHasFocus && !isKeyguardShowing()) {
             int flags = View.SYSTEM_UI_FLAG_FULLSCREEN
                     | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                     | View.SYSTEM_UI_FLAG_IMMERSIVE
@@ -3427,7 +3417,7 @@
                 (vis & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0;
 
         final boolean transientStatusBarAllowed = mStatusBar != null
-                && (statusBarHasFocus || (!mForceShowSystemBars
+                && (notificationShadeHasFocus || (!mForceShowSystemBars
                 && (hideStatusBarWM || (hideStatusBarSysui && immersiveSticky))));
 
         final boolean transientNavBarAllowed = mNavigationBar != null
@@ -3438,7 +3428,7 @@
                 && now - mPendingPanicGestureUptime <= PANIC_GESTURE_EXPIRATION;
         final DisplayPolicy defaultDisplayPolicy =
                 mService.getDefaultDisplayContentLocked().getDisplayPolicy();
-        if (pendingPanic && hideNavBarSysui && !isStatusBarKeyguard()
+        if (pendingPanic && hideNavBarSysui && !isKeyguardShowing()
                 // TODO (b/111955725): Show keyguard presentation on all external displays
                 && defaultDisplayPolicy.isKeyguardDrawComplete()) {
             // The user performed the panic gesture recently, we're about to hide the bars,
@@ -3700,8 +3690,11 @@
         pw.print(" mDreamingSleepToken="); pw.println(mDreamingSleepToken);
         if (mStatusBar != null) {
             pw.print(prefix); pw.print("mStatusBar="); pw.print(mStatusBar);
-                    pw.print(" isStatusBarKeyguard="); pw.println(isStatusBarKeyguard());
         }
+        if (mNotificationShade != null) {
+            pw.print(prefix); pw.print("mExpandedPanel="); pw.print(mNotificationShade);
+        }
+        pw.print(" isKeyguardShowing="); pw.println(isKeyguardShowing());
         if (mNavigationBar != null) {
             pw.print(prefix); pw.print("mNavigationBar="); pw.println(mNavigationBar);
             pw.print(prefix); pw.print("mNavBarOpacityMode="); pw.println(mNavBarOpacityMode);
@@ -3730,7 +3723,6 @@
         }
         pw.print(prefix); pw.print("mTopIsFullscreen="); pw.println(mTopIsFullscreen);
         pw.print(prefix); pw.print("mForceStatusBar="); pw.print(mForceStatusBar);
-        pw.print(" mForceStatusBarFromKeyguard="); pw.println(mForceStatusBarFromKeyguard);
         pw.print(prefix); pw.print("mForceShowSystemBarsFromExternal=");
         pw.print(mForceShowSystemBarsFromExternal);
         pw.print(" mAllowLockscreenWhenOn="); pw.println(mAllowLockscreenWhenOn);
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index b59c4e3..f90f224 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -582,7 +582,7 @@
 
         // In the presence of the PINNED stack or System Alert windows we unfortunately can not
         // seamlessly rotate.
-        if (mDisplayContent.hasPinnedStack() || mDisplayContent.hasAlertWindowSurfaces()) {
+        if (mDisplayContent.hasPinnedTask() || mDisplayContent.hasAlertWindowSurfaces()) {
             return false;
         }
 
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 6b5859d..872379e 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -255,7 +255,7 @@
     }
 
     boolean isHomeStackResizable() {
-        final ActivityStack homeStack = mDisplayContent.getHomeStack();
+        final ActivityStack homeStack = mDisplayContent.getRootHomeTask();
         if (homeStack == null) {
             return false;
         }
@@ -371,7 +371,7 @@
         if (mWindow == null) {
             return;
         }
-        ActivityStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
+        ActivityStack stack = mDisplayContent.getRootSplitScreenPrimaryTask();
 
         // If the stack is invisible, we policy force hide it in WindowAnimator.shouldForceHide
         final boolean visible = stack != null;
@@ -415,7 +415,7 @@
     }
 
     void positionDockedStackedDivider(Rect frame) {
-        ActivityStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
+        ActivityStack stack = mDisplayContent.getRootSplitScreenPrimaryTask();
         if (stack == null) {
             // Unfortunately we might end up with still having a divider, even though the underlying
             // stack was already removed. This is because we are on AM thread and the removal of the
@@ -524,7 +524,7 @@
             // If a primary stack was just created, it will not have access to display content at
             // this point so pass it from here to get a valid dock side.
             final ActivityStack stack =
-                    mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
+                    mDisplayContent.getRootSplitScreenPrimaryTask();
             mOriginalDockedSide = stack.getDockSideForDisplay(mDisplayContent);
             return;
         }
@@ -560,7 +560,7 @@
         long animDuration = 0;
         if (animate) {
             final ActivityStack stack =
-                    mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
+                    mDisplayContent.getRootSplitScreenPrimaryTask();
             final long transitionDuration = isAnimationMaximizing()
                     ? mDisplayContent.mAppTransition.getLastClipRevealTransitionDuration()
                     : DEFAULT_APP_TRANSITION_DURATION;
@@ -615,7 +615,7 @@
         mDockedStackListeners.register(listener);
         notifyDockedDividerVisibilityChanged(wasVisible());
         notifyDockedStackExistsChanged(
-                mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility() != null);
+                mDisplayContent.getRootSplitScreenPrimaryTask() != null);
         notifyDockedStackMinimizedChanged(mMinimizedDock, false /* animate */,
                 isHomeStackResizable());
         notifyAdjustedForImeChanged(mAdjustedForIme, 0 /* animDuration */);
@@ -633,7 +633,7 @@
         final ActivityStack stack = targetWindowingMode != WINDOWING_MODE_UNDEFINED
                 ? mDisplayContent.getTopStackInWindowingMode(targetWindowingMode)
                 : null;
-        final ActivityStack dockedStack = mDisplayContent.getSplitScreenPrimaryStack();
+        final ActivityStack dockedStack = mDisplayContent.getRootSplitScreenPrimaryTask();
         boolean visibleAndValid = visible && stack != null && dockedStack != null;
 
         // Ensure an old dim that was shown for the docked stack divider is removed so we don't end
@@ -701,10 +701,10 @@
     }
 
     void checkMinimizeChanged(boolean animate) {
-        if (mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility() == null) {
+        if (mDisplayContent.getRootSplitScreenPrimaryTask() == null) {
             return;
         }
-        final ActivityStack homeStack = mDisplayContent.getHomeStack();
+        final ActivityStack homeStack = mDisplayContent.getRootHomeTask();
         if (homeStack == null) {
             return;
         }
@@ -873,7 +873,7 @@
     }
 
     private boolean setMinimizedDockedStack(boolean minimized) {
-        final ActivityStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
+        final ActivityStack stack = mDisplayContent.getRootSplitScreenPrimaryTask();
         notifyDockedStackMinimizedChanged(minimized, false /* animate */, isHomeStackResizable());
         return stack != null && stack.setAdjustedForMinimizedDock(minimized ? 1f : 0f);
     }
@@ -923,7 +923,7 @@
     }
 
     private boolean animateForMinimizedDockedStack(long now) {
-        final ActivityStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
+        final ActivityStack stack = mDisplayContent.getRootSplitScreenPrimaryTask();
         if (!mAnimationStarted) {
             mAnimationStarted = true;
             mAnimationStartTime = now;
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index 55f5e28..9d985d7 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -75,16 +75,18 @@
         // activities are actually behind other fullscreen activities, but still required
         // to be visible (such as performing Recents animation).
         final boolean resumeTopActivity = mTop != null && !mTop.mLaunchTaskBehind
-                && mContiner.isFocusable() && mContiner.isInStackLocked(starting) == null;
+                && mContiner.isTopActivityFocusable()
+                && mContiner.isInStackLocked(starting) == null;
 
         final PooledConsumer f = PooledLambda.obtainConsumer(
                 EnsureActivitiesVisibleHelper::setActivityVisibilityState, this,
-                PooledLambda.__(ActivityRecord.class), resumeTopActivity);
+                PooledLambda.__(ActivityRecord.class), starting, resumeTopActivity);
         mContiner.forAllActivities(f);
         f.recycle();
     }
 
-    private void setActivityVisibilityState(ActivityRecord r, final boolean resumeTopActivity) {
+    private void setActivityVisibilityState(ActivityRecord r, ActivityRecord starting,
+            final boolean resumeTopActivity) {
         final boolean isTop = r == mTop;
         if (mAboveTop && !isTop) {
             return;
@@ -129,7 +131,8 @@
                         "Skipping: already visible at " + r);
 
                 if (r.mClientVisibilityDeferred && mNotifyClients) {
-                    r.makeClientVisible();
+                    r.makeActiveIfNeeded(r.mClientVisibilityDeferred ? null : starting);
+                    r.mClientVisibilityDeferred = false;
                 }
 
                 r.handleAlreadyVisible();
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 05ede21..fb97ecf 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -69,7 +69,7 @@
         mShowImeRunner = () -> {
             // Target should still be the same.
             if (isImeTargetFromDisplayContentAndImeSame()) {
-                mDisplayContent.mInputMethodTarget.showInsets(
+                mDisplayContent.mInputMethodControlTarget.showInsets(
                         WindowInsets.Type.ime(), true /* fromIme */);
             }
             abortShowImePostLayout();
diff --git a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
index bef1442..ef6f847 100644
--- a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
+++ b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
@@ -190,7 +190,7 @@
                         | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
                         | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
                 PixelFormat.TRANSLUCENT);
-        lp.setFitWindowInsetsTypes(lp.getFitWindowInsetsTypes() & ~Type.statusBars());
+        lp.setFitInsetsTypes(lp.getFitInsetsTypes() & ~Type.statusBars());
         lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
         lp.setTitle("ImmersiveModeConfirmation");
         lp.windowAnimations = com.android.internal.R.style.Animation_ImmersiveModeConfirmation;
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index c4b67d7..4c5914b 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -23,7 +23,6 @@
 import static android.view.WindowManager.INPUT_CONSUMER_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
@@ -279,6 +278,19 @@
         // we avoid reintroducing this concept by just choosing one of them here.
         inputWindowHandle.surfaceInset = child.getAttrs().surfaceInsets.left;
 
+        /**
+         * If the window is in a TaskManaged by a TaskOrganizer then most cropping
+         * will be applied using the SurfaceControl hierarchy from the Organizer.
+         * This means we need to make sure that these changes in crop are reflected
+         * in the input windows, and so ensure this flag is set so that
+         * the input crop always reflects the surface hierarchy.
+         * we may have some issues with modal-windows, but I guess we can
+         * cross that bridge when we come to implementing full-screen TaskOrg
+         */
+        if (child.getTask() != null && child.getTask().isControlledByTaskOrganizer()) {
+            inputWindowHandle.replaceTouchableRegionWithCrop(null /* Use this surfaces crop */);
+        }
+
         if (child.mGlobalScale != 1) {
             // If we are scaling the window, input coordinates need
             // to be inversely scaled to map from what is on screen
@@ -514,7 +526,7 @@
                 mDisableWallpaperTouchEvents = true;
             }
             final boolean hasWallpaper = wallpaperController.isWallpaperTarget(w)
-                    && (privateFlags & PRIVATE_FLAG_KEYGUARD) == 0
+                    && !mService.mPolicy.isKeyguardShowing()
                     && !mDisableWallpaperTouchEvents;
 
             // If there's a drag in progress and 'child' is a potential drop target,
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index a008963..fa764e3 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -22,8 +22,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
 
 import android.annotation.Nullable;
@@ -188,7 +187,7 @@
         if (mShowingTransientTypes.indexOf(ITYPE_STATUS_BAR) != -1) {
             return mTransientControlTarget;
         }
-        if (areSystemBarsForciblyVisible() || isStatusBarForciblyVisible()) {
+        if (areSystemBarsForciblyVisible() || isKeyguardOrStatusBarForciblyVisible()) {
             return null;
         }
         return focusedWin;
@@ -204,29 +203,27 @@
         return focusedWin;
     }
 
-    private boolean isStatusBarForciblyVisible() {
+    private boolean isKeyguardOrStatusBarForciblyVisible() {
+        if (mPolicy.isKeyguardShowing()) {
+            return true;
+        }
         final WindowState statusBar = mPolicy.getStatusBar();
-        if (statusBar == null) {
-            return false;
-        }
-        final int privateFlags = statusBar.mAttrs.privateFlags;
-
-        // TODO(b/118118435): Pretend to the app that it's still able to control it?
-        if ((privateFlags & PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT) != 0) {
-            return true;
-        }
-        if ((privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
-            return true;
+        if (statusBar != null) {
+            // TODO(b/118118435): Pretend to the app that it's still able to control it?
+            if ((statusBar.mAttrs.privateFlags & PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR) != 0) {
+                return true;
+            }
         }
         return false;
     }
 
     private boolean isNavBarForciblyVisible() {
-        final WindowState statusBar = mPolicy.getStatusBar();
-        if (statusBar == null) {
+        final WindowState notificationShade = mPolicy.getNotificationShade();
+        if (notificationShade == null) {
             return false;
         }
-        if ((statusBar.mAttrs.privateFlags & PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION) != 0) {
+        if ((notificationShade.mAttrs.privateFlags
+                & PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION) != 0) {
             return true;
         }
         return false;
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 184e7d6..5a591ec 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -70,6 +70,9 @@
      */
     private boolean mServerVisible;
 
+    private boolean mSeamlessRotating;
+    private long mFinishSeamlessRotateFrameNumber = -1;
+
     private final boolean mControllable;
 
     InsetsSourceProvider(InsetsSource source, InsetsStateController stateController,
@@ -126,6 +129,7 @@
         if (win == null) {
             setServerVisible(false);
             mSource.setFrame(new Rect());
+            mSource.setVisibleFrame(null);
         } else if (mControllable) {
             mWin.setControllableInsetProvider(this);
             if (mControlTarget != null) {
@@ -157,6 +161,15 @@
             mTmpRect.inset(mWin.mGivenContentInsets);
         }
         mSource.setFrame(mTmpRect);
+
+        if (mWin.mGivenVisibleInsets.left != 0 || mWin.mGivenVisibleInsets.top != 0
+                || mWin.mGivenVisibleInsets.right != 0 || mWin.mGivenVisibleInsets.bottom != 0) {
+            mTmpRect.set(mWin.getFrameLw());
+            mTmpRect.inset(mWin.mGivenVisibleInsets);
+            mSource.setVisibleFrame(mTmpRect);
+        } else {
+            mSource.setVisibleFrame(null);
+        }
     }
 
     /**
@@ -170,7 +183,9 @@
         updateSourceFrame();
         if (mControl != null) {
             final Rect frame = mWin.getWindowFrames().mFrame;
-            if (mControl.setSurfacePosition(frame.left, frame.top)) {
+            if (mControl.setSurfacePosition(frame.left, frame.top) && mControlTarget != null) {
+                // The leash has been stale, we need to create a new one for the client.
+                updateControlForTarget(mControlTarget, true /* force */);
                 mStateController.notifyControlChanged(mControlTarget);
             }
         }
@@ -189,6 +204,11 @@
     }
 
     void updateControlForTarget(@Nullable InsetsControlTarget target, boolean force) {
+        if (mSeamlessRotating) {
+            // We are un-rotating the window against the display rotation. We don't want the target
+            // to control the window for now.
+            return;
+        }
         if (mWin == null) {
             mControlTarget = target;
             return;
@@ -203,14 +223,42 @@
         }
         mAdapter = new ControlAdapter();
         setClientVisible(InsetsState.getDefaultVisibility(mSource.getType()));
-        mWin.startAnimation(mDisplayContent.getPendingTransaction(), mAdapter,
-                !mClientVisible /* hidden */);
+        final Transaction t = mDisplayContent.getPendingTransaction();
+        mWin.startAnimation(t, mAdapter, !mClientVisible /* hidden */);
+        final SurfaceControl leash = mAdapter.mCapturedLeash;
+        final long frameNumber = mFinishSeamlessRotateFrameNumber;
+        mFinishSeamlessRotateFrameNumber = -1;
+        if (frameNumber >= 0 && mWin.mHasSurface && leash != null) {
+            // We just finished the seamless rotation. We don't want to change the position or the
+            // window crop of the surface controls (including the leash) until the client finishes
+            // drawing the new frame of the new orientation. Although we cannot defer the reparent
+            // operation, it is fine, because reparent won't cause any visual effect.
+            final SurfaceControl barrier = mWin.mWinAnimator.mSurfaceController.mSurfaceControl;
+            t.deferTransactionUntil(mWin.getSurfaceControl(), barrier, frameNumber);
+            t.deferTransactionUntil(leash, barrier, frameNumber);
+        }
         mControlTarget = target;
-        mControl = new InsetsSourceControl(mSource.getType(), mAdapter.mCapturedLeash,
+        mControl = new InsetsSourceControl(mSource.getType(), leash,
                 new Point(mWin.getWindowFrames().mFrame.left, mWin.getWindowFrames().mFrame.top));
     }
 
-    boolean onInsetsModified(WindowState caller, InsetsSource modifiedSource) {
+    void startSeamlessRotation() {
+        if (!mSeamlessRotating) {
+            mSeamlessRotating = true;
+
+            // This will revoke the leash and clear the control target.
+            mWin.cancelAnimation();
+        }
+    }
+
+    void finishSeamlessRotation(boolean timeout) {
+        if (mSeamlessRotating) {
+            mSeamlessRotating = false;
+            mFinishSeamlessRotateFrameNumber = timeout ? -1 : mWin.getFrameNumber();
+        }
+    }
+
+    boolean onInsetsModified(InsetsControlTarget caller, InsetsSource modifiedSource) {
         if (mControlTarget != caller || modifiedSource.isVisible() == mClientVisible) {
             return false;
         }
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 1526074..b2234d1 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -91,6 +91,10 @@
         return state;
     }
 
+    InsetsState getRawInsetsState() {
+        return mState;
+    }
+
     @Nullable InsetsSourceControl[] getControlsForDispatch(InsetsControlTarget target) {
         ArrayList<Integer> controlled = mControlTargetTypeMap.get(target);
         if (controlled == null) {
@@ -144,7 +148,7 @@
         getImeSourceProvider().onPostInsetsDispatched();
     }
 
-    void onInsetsModified(WindowState windowState, InsetsState state) {
+    void onInsetsModified(InsetsControlTarget windowState, InsetsState state) {
         boolean changed = false;
         for (int i = state.getSourcesCount() - 1; i >= 0; i--) {
             final InsetsSource source = state.sourceAt(i);
@@ -199,7 +203,7 @@
         if (target == previous) {
             return;
         }
-        final InsetsSourceProvider provider = getSourceProvider(type);
+        final InsetsSourceProvider provider = mProviders.get(type);
         if (provider == null) {
             return;
         }
@@ -207,6 +211,7 @@
             return;
         }
         provider.updateControlForTarget(target, false /* force */);
+        target = provider.getControlTarget();
         if (previous != null) {
             removeFromControlMaps(previous, type, false /* fake */);
             mPendingControlChanged.add(previous);
@@ -296,6 +301,9 @@
 
     void notifyInsetsChanged() {
         mDisplayContent.forAllWindows(mDispatchInsetsChanged, true /* traverseTopToBottom */);
+        if (mDisplayContent.mRemoteInsetsControlTarget != null) {
+            mDisplayContent.mRemoteInsetsControlTarget.notifyInsetsChanged();
+        }
     }
 
     void dump(String prefix, PrintWriter pw) {
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 7db30f6..ad65e10 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -407,7 +407,7 @@
             // stack since it will be complicated/risky to try to put the activity on top
             // of the lock screen in the right fullscreen configuration.
             final ActivityStack stack =
-                    mRootWindowContainer.getDefaultDisplay().getSplitScreenPrimaryStack();
+                    mRootWindowContainer.getDefaultDisplay().getRootSplitScreenPrimaryTask();
             if (stack == null) {
                 return;
             }
diff --git a/services/core/java/com/android/server/wm/LaunchParamsController.java b/services/core/java/com/android/server/wm/LaunchParamsController.java
index 03e1322..a9820ef 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsController.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsController.java
@@ -138,7 +138,7 @@
         try {
             if (mTmpParams.hasPreferredDisplay()
                     && mTmpParams.mPreferredDisplayId != task.getStack().getDisplay().mDisplayId) {
-                mService.moveStackToDisplay(task.getStackId(), mTmpParams.mPreferredDisplayId);
+                mService.moveStackToDisplay(task.getRootTaskId(), mTmpParams.mPreferredDisplayId);
             }
 
             if (mTmpParams.hasWindowingMode()
diff --git a/services/core/java/com/android/server/wm/LaunchParamsPersister.java b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
index f4e608e..660706e 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsPersister.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
@@ -197,6 +197,10 @@
     }
 
     void saveTask(Task task) {
+        saveTask(task, task.getDisplayContent());
+    }
+
+    void saveTask(Task task, DisplayContent display) {
         final ComponentName name = task.realActivity;
         final int userId = task.mUserId;
         PersistableLaunchParams params;
@@ -211,7 +215,7 @@
             params = new PersistableLaunchParams();
             map.put(name, params);
         }
-        final boolean changed = saveTaskToLaunchParam(task, params);
+        final boolean changed = saveTaskToLaunchParam(task, display, params);
 
         if (changed) {
             mPersisterQueue.updateLastOrAddItem(
@@ -220,17 +224,16 @@
         }
     }
 
-    private boolean saveTaskToLaunchParam(Task task, PersistableLaunchParams params) {
-        final ActivityStack stack = task.getStack();
-        final DisplayContent display = stack.getDisplayContent();
+    private boolean saveTaskToLaunchParam(
+            Task task, DisplayContent display, PersistableLaunchParams params) {
         final DisplayInfo info = new DisplayInfo();
         display.mDisplay.getDisplayInfo(info);
 
         boolean changed = !Objects.equals(params.mDisplayUniqueId, info.uniqueId);
         params.mDisplayUniqueId = info.uniqueId;
 
-        changed |= params.mWindowingMode != stack.getWindowingMode();
-        params.mWindowingMode = stack.getWindowingMode();
+        changed |= params.mWindowingMode != task.getWindowingMode();
+        params.mWindowingMode = task.getWindowingMode();
 
         if (task.mLastNonFullscreenBounds != null) {
             changed |= !Objects.equals(params.mBounds, task.mLastNonFullscreenBounds);
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index 02413bb..33b0453 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -23,6 +23,8 @@
 import static android.content.Context.DEVICE_POLICY_SERVICE;
 import static android.content.Context.STATUS_BAR_SERVICE;
 import static android.content.Intent.ACTION_CALL_EMERGENCY;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
 import static android.os.UserHandle.USER_ALL;
 import static android.os.UserHandle.USER_CURRENT;
 import static android.telecom.TelecomManager.EMERGENCY_DIALER_COMPONENT;
@@ -339,6 +341,20 @@
                 & DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD) != 0;
     }
 
+    boolean isActivityAllowed(int userId, String packageName, int lockTaskLaunchMode) {
+        if (mLockTaskModeState != LOCK_TASK_MODE_LOCKED) {
+            return true;
+        }
+        switch (lockTaskLaunchMode) {
+            case LOCK_TASK_LAUNCH_MODE_ALWAYS:
+                return true;
+            case LOCK_TASK_LAUNCH_MODE_NEVER:
+                return false;
+            default:
+        }
+        return isPackageWhitelisted(userId, packageName);
+    }
+
     private boolean isEmergencyCallTask(Task task) {
         final Intent intent = task.intent;
         if (intent == null) {
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index b4f75e5..1d5b5d1 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -125,7 +125,7 @@
         public void startAnimation(Rect destinationBounds, Rect sourceRectHint,
                 int animationDuration) {
             synchronized (mService.mGlobalLock) {
-                final ActivityStack pinnedStack = mDisplayContent.getPinnedStack();
+                final ActivityStack pinnedStack = mDisplayContent.getRootPinnedTask();
                 pinnedStack.animateResizePinnedStack(destinationBounds,
                         sourceRectHint, animationDuration, true /* fromFullscreen */);
             }
@@ -134,7 +134,7 @@
         @Override
         public void resetBoundsAnimation(Rect bounds) {
             synchronized (mService.mGlobalLock) {
-                if (mDisplayContent.hasPinnedStack()) {
+                if (mDisplayContent.hasPinnedTask()) {
                     final ActivityStack pinnedStack = mDisplayContent.getTopStackInWindowingMode(
                             WINDOWING_MODE_PINNED);
                     if (pinnedStack != null) {
@@ -383,7 +383,7 @@
             }
             try {
                 final Rect animatingBounds = new Rect();
-                final ActivityStack pinnedStack = mDisplayContent.getPinnedStack();
+                final ActivityStack pinnedStack = mDisplayContent.getRootPinnedTask();
                 if (pinnedStack != null) {
                     pinnedStack.getAnimationOrCurrentBounds(animatingBounds);
                 }
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 5df80fc..292e8aa 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -32,7 +32,6 @@
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.os.Process.SYSTEM_UID;
-import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.server.wm.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS;
@@ -1382,13 +1381,13 @@
 
         // Ignore tasks from different displays
         // TODO (b/115289124): No Recents on non-default displays.
-        if (stack.getDisplayId() != DEFAULT_DISPLAY) {
+        if (!stack.isOnHomeDisplay()) {
             return false;
         }
 
         // Trim tasks that are in stacks that are behind the home stack
         final DisplayContent display = stack.getDisplay();
-        return display.getIndexOf(stack) < display.getIndexOf(display.getHomeStack());
+        return display.getIndexOf(stack) < display.getIndexOf(display.getRootHomeTask());
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index b255b5e..da9d074 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -374,7 +374,7 @@
             final PooledConsumer c = PooledLambda.obtainConsumer((t, outList) ->
 	            { if (!outList.contains(t)) outList.add(t); }, PooledLambda.__(Task.class),
                     visibleTasks);
-            targetStack.forAllTasks(c);
+            targetStack.forAllTasks(c, true /* traverseTopToBottom */, targetStack);
             c.recycle();
         }
 
@@ -413,7 +413,7 @@
 
         // Save the minimized home height
         final ActivityStack dockedStack =
-                mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
+                mDisplayContent.getRootSplitScreenPrimaryTask();
         mDisplayContent.getDockedDividerController().getHomeStackBoundsInDockedMode(
                 mDisplayContent.getConfiguration(),
                 dockedStack == null ? DOCKED_INVALID : dockedStack.getDockSide(),
diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
index e0a7b18..2cb7d5a 100644
--- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java
+++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
@@ -33,6 +33,27 @@
     private final HighRefreshRateBlacklist mHighRefreshRateBlacklist;
     private final WindowManagerService mWmService;
 
+    /**
+     * The following constants represent priority of the window. SF uses this information when
+     * deciding which window has a priority when deciding about the refresh rate of the screen.
+     * Priority 0 is considered the highest priority. -1 means that the priority is unset.
+     */
+    static final int LAYER_PRIORITY_UNSET = -1;
+    /** Windows that are in focus and voted for the preferred mode ID have the highest priority. */
+    static final int LAYER_PRIORITY_FOCUSED_WITH_MODE = 0;
+    /**
+     * This is a default priority for all windows that are in focus, but have not requested a
+     * specific mode ID.
+     */
+    static final int LAYER_PRIORITY_FOCUSED_WITHOUT_MODE = 1;
+    /**
+     * Windows that are not in focus, but voted for a specific mode ID should be
+     * acknowledged by SF. For example, there are two applications in a split screen.
+     * One voted for a given mode ID, and the second one doesn't care. Even though the
+     * second one might be in focus, we can honor the mode ID of the first one.
+     */
+    static final int LAYER_PRIORITY_NOT_FOCUSED_WITH_MODE = 2;
+
     RefreshRatePolicy(WindowManagerService wmService, DisplayInfo displayInfo,
             HighRefreshRateBlacklist blacklist) {
         mLowRefreshRateId = findLowRefreshRateModeId(displayInfo);
@@ -92,4 +113,28 @@
         }
         return 0;
     }
+
+    /**
+     * Calculate the priority based on whether the window is in focus and whether the application
+     * voted for a specific refresh rate.
+     *
+     * TODO(b/144307188): This is a very basic algorithm version. Explore other signals that might
+     * be useful in edge cases when we are deciding which layer should get priority when deciding
+     * about the refresh rate.
+     */
+    int calculatePriority(WindowState w) {
+        boolean isFocused = w.isFocused();
+        int preferredModeId = getPreferredModeId(w);
+
+        if (!isFocused && preferredModeId > 0) {
+            return LAYER_PRIORITY_NOT_FOCUSED_WITH_MODE;
+        }
+        if (isFocused && preferredModeId == 0) {
+            return LAYER_PRIORITY_FOCUSED_WITHOUT_MODE;
+        }
+        if (isFocused && preferredModeId > 0) {
+            return LAYER_PRIORITY_FOCUSED_WITH_MODE;
+        }
+        return LAYER_PRIORITY_UNSET;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
index e310fc1..2f61ca0 100644
--- a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
+++ b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
@@ -247,8 +247,7 @@
             } else {
                 targetTask = mTargetStack.createTask(
                         atmService.mStackSupervisor.getNextTaskIdForUser(r.mUserId), r.info,
-                        null /* intent */, null /* voiceSession */, null /* voiceInteractor */,
-                        false /* toTop */);
+                        null /* intent */, false /* toTop */);
                 targetTask.affinityIntent = r.intent;
                 createdTasks.add(targetTask);
                 if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity "
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index a7bf660..0b54245 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -35,7 +35,6 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE;
 import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
 import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
@@ -1147,7 +1146,7 @@
                 // While a dream or keyguard is showing, obscure ordinary application content on
                 // secondary displays (by forcibly enabling mirroring unless there is other content
                 // we want to show) but still allow opaque keyguard dialogs to be shown.
-                if (type == TYPE_DREAM || (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
+                if (type == TYPE_DREAM || mWmService.mPolicy.isKeyguardShowing()) {
                     mObscureApplicationContentOnSecondaryDisplays = true;
                 }
                 displayHasContent = true;
@@ -1657,7 +1656,7 @@
         // Only resume home activity if isn't finishing.
         if (r != null && !r.finishing) {
             r.moveFocusableActivityToTop(myReason);
-            return resumeFocusedStacksTopActivities(r.getActivityStack(), prev, null);
+            return resumeFocusedStacksTopActivities(r.getRootTask(), prev, null);
         }
         return startHomeOnDisplay(mCurrentUser, myReason, displayId);
     }
@@ -1860,14 +1859,6 @@
         return null;
     }
 
-    boolean isFocusable(ConfigurationContainer container, boolean alwaysFocusable) {
-        if (container.inSplitScreenPrimaryWindowingMode() && mIsDockMinimized) {
-            return false;
-        }
-
-        return container.getWindowConfiguration().canReceiveKeys() || alwaysFocusable;
-    }
-
     boolean isTopDisplayFocusedStack(ActivityStack stack) {
         return stack != null && stack == getTopDisplayFocusedStack();
     }
@@ -1989,9 +1980,9 @@
     }
 
     boolean switchUser(int userId, UserState uss) {
-        final int focusStackId = getTopDisplayFocusedStack().getStackId();
+        final int focusStackId = getTopDisplayFocusedStack().getRootTaskId();
         // We dismiss the docked stack whenever we switch users.
-        final ActivityStack dockedStack = getDefaultDisplay().getSplitScreenPrimaryStack();
+        final ActivityStack dockedStack = getDefaultDisplay().getRootSplitScreenPrimaryTask();
         if (dockedStack != null) {
             mStackSupervisor.moveTasksToFullscreenStackLocked(
                     dockedStack, dockedStack.isFocusedStackOnDisplay());
@@ -2003,7 +1994,7 @@
 
         mUserStackInFront.put(mCurrentUser, focusStackId);
         final int restoreStackId =
-                mUserStackInFront.get(userId, getDefaultDisplay().getHomeStack().mStackId);
+                mUserStackInFront.get(userId, getDefaultDisplay().getRootHomeTask().getRootTaskId());
         mCurrentUser = userId;
 
         mStackSupervisor.mStartingUsers.add(uss);
@@ -2013,7 +2004,7 @@
                 final ActivityStack stack = display.getStackAt(stackNdx);
                 stack.switchUser(userId);
                 Task task = stack.getTopMostTask();
-                if (task != null) {
+                if (task != null && task != stack) {
                     stack.positionChildAtTop(task);
                 }
             }
@@ -2021,7 +2012,7 @@
 
         ActivityStack stack = getStack(restoreStackId);
         if (stack == null) {
-            stack = getDefaultDisplay().getHomeStack();
+            stack = getDefaultDisplay().getRootHomeTask();
         }
         final boolean homeInFront = stack.isActivityTypeHome();
         if (stack.isOnHomeDisplay()) {
@@ -2043,8 +2034,8 @@
      */
     void updateUserStack(int userId, ActivityStack stack) {
         if (userId != mCurrentUser) {
-            mUserStackInFront.put(userId, stack != null ? stack.getStackId()
-                    : getDefaultDisplay().getHomeStack().mStackId);
+            mUserStackInFront.put(userId, stack != null ? stack.getRootTaskId()
+                    : getDefaultDisplay().getRootHomeTask().getRootTaskId());
         }
     }
 
@@ -2117,12 +2108,12 @@
             String reason) {
         mService.deferWindowLayout();
 
-        final DisplayContent display = r.getActivityStack().getDisplay();
+        final DisplayContent display = r.getRootTask().getDisplay();
 
         try {
             final Task task = r.getTask();
 
-            final ActivityStack pinnedStack = display.getPinnedStack();
+            final ActivityStack pinnedStack = display.getRootPinnedTask();
             // This will change the pinned stack's windowing mode to its original mode, ensuring
             // we only have one stack that is in pinned mode.
             if (pinnedStack != null) {
@@ -2133,30 +2124,20 @@
 
             final ActivityStack stack;
             if (singleActivity) {
-                stack = r.getActivityStack();
+                stack = r.getRootTask();
             } else {
                 // In the case of multiple activities, we will create a new stack for it and then
                 // move the PIP activity into the stack.
                 // We will then perform a windowing mode change for both scenarios.
                 stack = display.createStack(
-                        r.getActivityStack().getRequestedOverrideWindowingMode(),
-                        r.getActivityType(), ON_TOP);
+                        r.getRootTask().getRequestedOverrideWindowingMode(),
+                        r.getActivityType(), ON_TOP, r.info, r.intent);
                 // There are multiple activities in the task and moving the top activity should
                 // reveal/leave the other activities in their original task.
 
-                // Currently, we don't support reparenting activities across tasks in two different
-                // stacks, so instead, just create a new task in the same stack, reparent the
-                // activity into that task, and then reparent the whole task to the new stack. This
-                // ensures that all the necessary work to migrate states in the old and new stacks
-                // is also done.
-                final Task newTask = task.getStack().createTask(
-                        mStackSupervisor.getNextTaskIdForUser(r.mUserId), r.info,
-                        r.intent, null, null, true);
+                Task newTask = stack.createTask(mStackSupervisor.getNextTaskIdForUser(r.mUserId),
+                        r.info, r.intent, true);
                 r.reparent(newTask, MAX_VALUE, "moveActivityToStack");
-
-                // Defer resume until below, and do not schedule PiP changes until we animate below
-                newTask.reparent(stack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE,
-                        DEFER_RESUME, false /* schedulePictureInPictureModeChange */, reason);
             }
 
             stack.setWindowingMode(WINDOWING_MODE_PINNED);
@@ -2168,12 +2149,18 @@
             mService.continueWindowLayout();
         }
 
+        // TODO(b/146594635): Remove all PIP animation code from WM once SysUI handles animation.
         // Notify the pinned stack controller to prepare the PiP animation, expect callback
-        // delivered from SystemUI to WM to start the animation.
-        final PinnedStackController pinnedStackController =
+        // delivered from SystemUI to WM to start the animation. Unless we are using
+        // the TaskOrganizer in which case the animation will be entirely handled
+        // on that side.
+        if (mService.mTaskOrganizerController.getTaskOrganizer(WINDOWING_MODE_PINNED)
+                == null) {
+            final PinnedStackController pinnedStackController =
                 display.mDisplayContent.getPinnedStackController();
-        pinnedStackController.prepareAnimation(sourceHintBounds, aspectRatio,
-                null /* stackBounds */);
+            pinnedStackController.prepareAnimation(sourceHintBounds, aspectRatio,
+                    null /* stackBounds */);
+        }
 
         // TODO: revisit the following statement after the animation is moved from WM to SysUI.
         // Update the visibility of all activities after the they have been reparented to the new
@@ -2411,7 +2398,7 @@
         ActivityManager.StackInfo info = new ActivityManager.StackInfo();
         stack.getBounds(info.bounds);
         info.displayId = display.mDisplayId;
-        info.stackId = stack.mStackId;
+        info.stackId = stack.mTaskId;
         info.stackToken = stack.mRemoteToken;
         info.userId = stack.mCurrentUser;
         info.visible = stack.shouldBeVisible(null);
@@ -2419,17 +2406,17 @@
         info.position = display != null ? display.getIndexOf(stack) : 0;
         info.configuration.setTo(stack.getConfiguration());
 
-        final int numTasks = stack.getChildCount();
+        final int numTasks = stack.getDescendantTaskCount();
         info.taskIds = new int[numTasks];
         info.taskNames = new String[numTasks];
         info.taskBounds = new Rect[numTasks];
         info.taskUserIds = new int[numTasks];
-        final int[] currenIndex = {0};
+        final int[] currentIndex = {0};
 
         final PooledConsumer c = PooledLambda.obtainConsumer(
                 RootWindowContainer::processTaskForStackInfo, PooledLambda.__(Task.class), info,
-                currenIndex);
-        stack.forAllTasks(c, false);
+                currentIndex);
+        stack.forAllTasks(c, false /* traverseTopToBottom */, stack);
         c.recycle();
 
         final ActivityRecord top = stack.topRunningActivity();
@@ -2572,7 +2559,7 @@
     }
 
     ActivityStack findStackBehind(ActivityStack stack) {
-        final DisplayContent display = getDisplayContent(stack.getDisplayId());
+        final DisplayContent display = stack.getDisplayContent();
         if (display != null) {
             for (int i = display.getStackCount() - 1; i >= 0; i--) {
                 if (display.getStackAt(i) == stack && i > 0) {
@@ -2866,7 +2853,7 @@
             stack = candidateTask.getStack();
         }
         if (stack == null && r != null) {
-            stack = r.getActivityStack();
+            stack = r.getRootTask();
         }
         if (stack != null) {
             display = stack.getDisplay();
@@ -2881,7 +2868,7 @@
                     return stack;
                 }
                 if (windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY
-                        && display.getSplitScreenPrimaryStack() == stack
+                        && display.getRootSplitScreenPrimaryTask() == stack
                         && candidateTask == stack.getTopMostTask()) {
                     // This is a special case when we try to launch an activity that is currently on
                     // top of split-screen primary stack, but is targeting split-screen secondary.
@@ -3257,7 +3244,7 @@
             if (resumedActivity == null || !resumedActivity.idle) {
                 if (DEBUG_STATES) {
                     Slog.d(TAG_STATES, "allResumedActivitiesIdle: stack="
-                            + stack.mStackId + " " + resumedActivity + " not idle");
+                            + stack.getRootTaskId() + " " + resumedActivity + " not idle");
                 }
                 return false;
             }
@@ -3612,7 +3599,7 @@
         // TODO(b/111541062): Update tests to look for resumed activities on all displays
         final ActivityStack focusedStack = getTopDisplayFocusedStack();
         if (focusedStack != null) {
-            proto.write(FOCUSED_STACK_ID, focusedStack.mStackId);
+            proto.write(FOCUSED_STACK_ID, focusedStack.getRootTaskId());
             final ActivityRecord focusedActivity = focusedStack.getDisplay().getResumedActivity();
             if (focusedActivity != null) {
                 focusedActivity.writeIdentifierToProto(proto, RESUMED_ACTIVITY);
diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java
index 98127ab..6ebbf77 100644
--- a/services/core/java/com/android/server/wm/RunningTasks.java
+++ b/services/core/java/com/android/server/wm/RunningTasks.java
@@ -93,6 +93,9 @@
     }
 
     private void processTask(Task task) {
+        if (task.isRootTask()) {
+            return;
+        }
         if (task.getTopNonFinishingActivity() == null) {
             // Skip if there are no activities in the task
             return;
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index eaa0ea7..bfb69172 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -450,7 +450,9 @@
                 mEnterBlackFrameLayer = null;
             }
             if (mBackColorSurface != null) {
-                t.remove(mBackColorSurface);
+                if (mBackColorSurface.isValid()) {
+                    t.remove(mBackColorSurface);
+                }
                 mBackColorSurface = null;
             }
             t.apply();
@@ -733,6 +735,10 @@
             if (mDisplayAnimator != null) {
                 mDisplayAnimator.cancelAnimation();
             }
+
+            if (mBackColorSurface != null) {
+                mService.mSurfaceAnimationRunner.onAnimationCancelled(mBackColorSurface);
+            }
         }
 
         public boolean isAnimating() {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 3b349b8..5babdaf 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -31,6 +31,7 @@
 
 import android.annotation.Nullable;
 import android.content.ClipData;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Binder;
@@ -189,7 +190,8 @@
             Rect outFrame, Rect outContentInsets, Rect outVisibleInsets,
             Rect outStableInsets, Rect outBackdropFrame,
             DisplayCutout.ParcelableWrapper cutout, MergedConfiguration mergedConfiguration,
-            SurfaceControl outSurfaceControl, InsetsState outInsetsState) {
+            SurfaceControl outSurfaceControl, InsetsState outInsetsState,
+            Point outSurfaceSize) {
         if (false) Slog.d(TAG_WM, ">>>>>> ENTERED relayout from "
                 + Binder.getCallingPid());
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mRelayoutTag);
@@ -197,7 +199,7 @@
                 requestedWidth, requestedHeight, viewFlags, flags, frameNumber,
                 outFrame, outContentInsets, outVisibleInsets,
                 outStableInsets, outBackdropFrame, cutout,
-                mergedConfiguration, outSurfaceControl, outInsetsState);
+                mergedConfiguration, outSurfaceControl, outInsetsState, outSurfaceSize);
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         if (false) Slog.d(TAG_WM, "<<<<<< EXITING relayout to "
                 + Binder.getCallingPid());
diff --git a/services/core/java/com/android/server/wm/StatusBarController.java b/services/core/java/com/android/server/wm/StatusBarController.java
index f4260d3..cac992a 100644
--- a/services/core/java/com/android/server/wm/StatusBarController.java
+++ b/services/core/java/com/android/server/wm/StatusBarController.java
@@ -17,7 +17,6 @@
 package com.android.server.wm;
 
 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
-import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
 
 import static com.android.server.wm.WindowManagerInternal.AppTransitionListener;
 
@@ -102,11 +101,6 @@
         }
     }
 
-    @Override
-    protected boolean skipAnimation() {
-        return mWin.getAttrs().height == MATCH_PARENT;
-    }
-
     AppTransitionListener getAppTransitionListener() {
         return mAppTransitionListener;
     }
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 976730e..cb1676f 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -55,7 +55,9 @@
     private final OnAnimationFinishedCallback mInnerAnimationFinishedCallback;
     @VisibleForTesting
     @Nullable
-    final Runnable mAnimationFinishedCallback;
+    final Runnable mStaticAnimationFinishedCallback;
+    @Nullable
+    private Runnable mAnimationFinishedCallback;
     private boolean mAnimationStartDelayed;
 
     /**
@@ -66,7 +68,7 @@
             WindowManagerService service) {
         mAnimatable = animatable;
         mService = service;
-        mAnimationFinishedCallback = animationFinishedCallback;
+        mStaticAnimationFinishedCallback = animationFinishedCallback;
         mInnerAnimationFinishedCallback = getFinishedCallback(animationFinishedCallback);
     }
 
@@ -89,10 +91,14 @@
                     if (anim != mAnimation) {
                         return;
                     }
+                    final Runnable animationFinishCallback = mAnimationFinishedCallback;
                     reset(mAnimatable.getPendingTransaction(), true /* destroyLeash */);
                     if (animationFinishedCallback != null) {
                         animationFinishedCallback.run();
                     }
+                    if (animationFinishCallback != null) {
+                        animationFinishCallback.run();
+                    }
                 };
                 if (!mAnimatable.shouldDeferAnimationFinish(resetAndInvokeFinish)) {
                     resetAndInvokeFinish.run();
@@ -111,10 +117,13 @@
      * @param hidden Whether the container holding the child surfaces is currently visible or not.
      *               This is important as it will start with the leash hidden or visible before
      *               handing it to the component that is responsible to run the animation.
+     * @param animationFinishedCallback The callback being triggered when the animation finishes.
      */
-    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden) {
+    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
+            @Nullable Runnable animationFinishedCallback) {
         cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
         mAnimation = anim;
+        mAnimationFinishedCallback = animationFinishedCallback;
         final SurfaceControl surface = mAnimatable.getSurfaceControl();
         if (surface == null) {
             Slog.w(TAG, "Unable to start animation, surface is null or no children.");
@@ -131,6 +140,10 @@
         mAnimation.startAnimation(mLeash, t, mInnerAnimationFinishedCallback);
     }
 
+    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden) {
+        startAnimation(t, anim, hidden, null /* animationFinishedCallback */);
+    }
+
     /**
      * Begins with delaying all animations to start. Any subsequent call to {@link #startAnimation}
      * will not start the animation until {@link #endDelayingAnimationStart} is called. When an
@@ -232,6 +245,7 @@
         cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
         mLeash = from.mLeash;
         mAnimation = from.mAnimation;
+        mAnimationFinishedCallback = from.mAnimationFinishedCallback;
 
         // Cancel source animation, but don't let animation runner cancel the animation.
         from.cancelAnimation(t, false /* restarting */, false /* forwardCancel */);
@@ -258,13 +272,19 @@
         if (DEBUG_ANIM) Slog.i(TAG, "Cancelling animation restarting=" + restarting);
         final SurfaceControl leash = mLeash;
         final AnimationAdapter animation = mAnimation;
+        final Runnable animationFinishedCallback = mAnimationFinishedCallback;
         reset(t, false);
         if (animation != null) {
             if (!mAnimationStartDelayed && forwardCancel) {
                 animation.onAnimationCancelled(leash);
             }
-            if (!restarting && mAnimationFinishedCallback != null) {
-                mAnimationFinishedCallback.run();
+            if (!restarting) {
+                if (mStaticAnimationFinishedCallback != null) {
+                    mStaticAnimationFinishedCallback.run();
+                }
+                if (animationFinishedCallback != null) {
+                    animationFinishedCallback.run();
+                }
             }
         }
 
@@ -304,6 +324,7 @@
         }
         mLeash = null;
         mAnimation = null;
+        mAnimationFinishedCallback = null;
 
         if (reparent) {
             // Make sure to inform the animatable after the surface was reparented (or reparent
@@ -322,12 +343,16 @@
         if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to leash");
         final SurfaceControl.Builder builder = mAnimatable.makeAnimationLeash()
                 .setParent(mAnimatable.getAnimationLeashParent())
+                .setHidden(hidden)
                 .setName(surface + " - animation-leash");
         final SurfaceControl leash = builder.build();
         t.setWindowCrop(leash, width, height);
+
+        // TODO: rely on builder.setHidden(hidden) instead of show and setAlpha when b/138459974 is
+        //       fixed.
         t.show(leash);
-        // TODO: change this back to use show instead of alpha when b/138459974 is fixed.
         t.setAlpha(leash, hidden ? 0 : 1);
+
         t.reparent(surface, leash);
         return leash;
     }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9a140da..917b437 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -30,6 +30,8 @@
 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;
+import static android.app.WindowConfiguration.activityTypeToString;
+import static android.app.WindowConfiguration.windowingModeToString;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
@@ -69,6 +71,7 @@
 import static com.android.server.am.TaskRecordProto.STACK_ID;
 import static com.android.server.am.TaskRecordProto.TASK;
 import static com.android.server.wm.ActivityRecord.STARTING_WINDOW_SHOWN;
+import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
 import static com.android.server.wm.ActivityStackSupervisor.ON_TOP;
 import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
@@ -82,6 +85,7 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TASKS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.ActivityTaskManagerService.TAG_STACK;
 import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
 import static com.android.server.wm.TaskProto.APP_WINDOW_TOKENS;
@@ -93,6 +97,7 @@
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM;
 
@@ -117,6 +122,7 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Debug;
 import android.os.IBinder;
@@ -130,6 +136,7 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 import android.view.DisplayInfo;
+import android.view.ITaskOrganizer;
 import android.view.RemoteAnimationTarget;
 import android.view.Surface;
 import android.view.SurfaceControl;
@@ -200,9 +207,9 @@
 
     // Current version of the task record we persist. Used to check if we need to run any upgrade
     // code.
-    private static final int PERSIST_TASK_VERSION = 1;
+    static final int PERSIST_TASK_VERSION = 1;
 
-    private static final int INVALID_MIN_SIZE = -1;
+    static final int INVALID_MIN_SIZE = -1;
 
     /**
      * The modes to control how the stack is moved to the front when calling {@link Task#reparent}.
@@ -329,6 +336,8 @@
     final TaskActivitiesReport mReuseActivitiesReport = new TaskActivitiesReport();
 
     final ActivityTaskManagerService mAtmService;
+    final ActivityStackSupervisor mStackSupervisor;
+    final RootWindowContainer mRootWindowContainer;
 
     /* Unique identifier for this task. */
     final int mTaskId;
@@ -343,8 +352,12 @@
     // TODO(b/119687367): This member is temporary.
     private final Rect mOverrideDisplayedBounds = new Rect();
 
+    // Id of the previous display the stack was on.
+    int mPrevDisplayId = INVALID_DISPLAY;
+
     /** ID of the display which rotation {@link #mRotation} has. */
     private int mLastRotationDisplayId = INVALID_DISPLAY;
+
     /**
      * Display rotation as of the last time {@link #setBounds(Rect)} was called or this task was
      * moved to a new display.
@@ -386,8 +399,37 @@
 
     private static Exception sTmpException;
 
+    /** ActivityRecords that are exiting, but still on screen for animations. */
+    final ArrayList<ActivityRecord> mExitingActivities = new ArrayList<>();
+
+    /**
+     * When we are in the process of pausing an activity, before starting the
+     * next one, this variable holds the activity that is currently being paused.
+     */
+    ActivityRecord mPausingActivity = null;
+
+    /**
+     * This is the last activity that we put into the paused state.  This is
+     * used to determine if we need to do an activity transition while sleeping,
+     * when we normally hold the top activity paused.
+     */
+    ActivityRecord mLastPausedActivity = null;
+
+    /**
+     * Activities that specify No History must be removed once the user navigates away from them.
+     * If the device goes to sleep with such an activity in the paused state then we save it here
+     * and finish it later if another activity replaces it on wakeup.
+     */
+    ActivityRecord mLastNoHistoryActivity = null;
+
+    /** Current activity that is resumed, or null if there is none. */
+    ActivityRecord mResumedActivity = null;
+
     private boolean mForceShowForAllUsers;
 
+    /** When set, will force the task to report as invisible. */
+    boolean mForceHidden = false;
+
     private final FindRootHelper mFindRootHelper = new FindRootHelper();
     private class FindRootHelper {
         private ActivityRecord mRoot;
@@ -425,6 +467,14 @@
     }
 
     /**
+     * The TaskOrganizer which is delegated presentation of this task. If set the Task will
+     * emit an IWindowContainer (allowing access to it's SurfaceControl leash) to the organizers
+     * taskAppeared callback, and emit a taskRemoved callback when the Task is vanished.
+     */
+    ITaskOrganizer mTaskOrganizer;
+
+
+    /**
      * Don't use constructor directly. Use {@link #create(ActivityTaskManagerService, int,
      * ActivityInfo, Intent, TaskDescription)} instead.
      */
@@ -445,6 +495,22 @@
                 _voiceSession, _voiceInteractor, stack);
     }
 
+    class TaskToken extends RemoteToken {
+        TaskToken(ConfigurationContainer container) {
+            super(container);
+        }
+
+        @Override
+        public SurfaceControl getLeash() {
+            // We need to copy the SurfaceControl instead of returning the original
+            // because the Parcel FLAGS PARCELABLE_WRITE_RETURN_VALUE cause SurfaceControls
+            // to release themselves.
+            SurfaceControl sc = new SurfaceControl();
+            sc.copyFrom(getSurfaceControl());
+            return sc;
+        }
+    }
+
     /** Don't use constructor directly. This is only used by XML parser. */
     Task(ActivityTaskManagerService atmService, int _taskId, Intent _intent,
             Intent _affinityIntent, String _affinity, String _rootAffinity,
@@ -460,8 +526,10 @@
             ActivityStack stack) {
         super(atmService.mWindowManager);
 
-        EventLogTags.writeWmTaskCreated(_taskId, stack != null ? stack.mStackId : INVALID_STACK_ID);
+        EventLogTags.writeWmTaskCreated(_taskId, stack != null ? getRootTaskId() : INVALID_TASK_ID);
         mAtmService = atmService;
+        mStackSupervisor = atmService.mStackSupervisor;
+        mRootWindowContainer = mAtmService.mRootWindowContainer;
         mTaskId = _taskId;
         mUserId = _userId;
         mResizeMode = resizeMode;
@@ -469,7 +537,7 @@
         mTaskDescription = _lastTaskDescription;
         // Tasks have no set orientation value (including SCREEN_ORIENTATION_UNSPECIFIED).
         setOrientation(SCREEN_ORIENTATION_UNSET);
-        mRemoteToken = new RemoteToken(this);
+        mRemoteToken = new TaskToken(this);
         affinityIntent = _affinityIntent;
         affinity = _affinity;
         rootAffinity = _rootAffinity;
@@ -506,13 +574,19 @@
         mAtmService.getTaskChangeNotificationController().notifyTaskCreated(_taskId, realActivity);
     }
 
-    private void cleanUpResourcesForDestroy() {
+    private void cleanUpResourcesForDestroy(ConfigurationContainer oldParent) {
         if (hasChild()) {
             return;
         }
 
-        // This task is going away, so save the last state if necessary.
-        saveLaunchingStateIfNeeded();
+        // TODO(xutan): Removed type check after stack and task is merged.
+        // Before the real merge of stack and task, we need to avoid saving state of stacks. Once
+        // the merge is finished we can just pass DisplayContent because both windowing mode and
+        // bounds are set in the merged task.
+        if (oldParent instanceof ActivityStack) {
+            // This task is going away, so save the last state if necessary.
+            saveLaunchingStateIfNeeded(((WindowContainer) oldParent).getDisplayContent());
+        }
 
         // TODO: VI what about activity?
         final boolean isVoiceSession = voiceSession != null;
@@ -525,7 +599,7 @@
         if (autoRemoveFromRecents() || isVoiceSession) {
             // Task creator asked to remove this when done, or this task was a voice
             // interaction, so it should not remain on the recent tasks list.
-            mAtmService.mStackSupervisor.mRecentTasks.remove(this);
+            mStackSupervisor.mRecentTasks.remove(this);
         }
 
         removeIfPossible();
@@ -534,13 +608,18 @@
     @VisibleForTesting
     @Override
     void removeIfPossible() {
-        mAtmService.getLockTaskController().clearLockedTask(this);
+        final boolean isRootTask = isRootTask();
+        if (!isRootTask) {
+            mAtmService.getLockTaskController().clearLockedTask(this);
+        }
         if (shouldDeferRemoval()) {
             if (DEBUG_STACK) Slog.i(TAG, "removeTask: deferring removing taskId=" + mTaskId);
             return;
         }
         removeImmediately();
-        mAtmService.getTaskChangeNotificationController().notifyTaskRemoved(mTaskId);
+        if (!isRootTask) {
+            mAtmService.getTaskChangeNotificationController().notifyTaskRemoved(mTaskId);
+        }
     }
 
     void setResizeMode(int resizeMode) {
@@ -548,8 +627,8 @@
             return;
         }
         mResizeMode = resizeMode;
-        mAtmService.mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
-        mAtmService.mRootWindowContainer.resumeFocusedStacksTopActivities();
+        mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+        mRootWindowContainer.resumeFocusedStacksTopActivities();
         updateTaskDescription();
     }
 
@@ -566,7 +645,7 @@
                 setBounds(bounds);
                 if (!inFreeformWindowingMode()) {
                     // re-restore the task so it can have the proper stack association.
-                    mAtmService.mStackSupervisor.restoreRecentTaskLocked(this, null, !ON_TOP);
+                    mStackSupervisor.restoreRecentTaskLocked(this, null, !ON_TOP);
                 }
                 return true;
             }
@@ -602,10 +681,9 @@
                     // this won't cause tons of irrelevant windows being preserved because only
                     // activities in this task may experience a bounds change. Configs for other
                     // activities stay the same.
-                    mAtmService.mRootWindowContainer.ensureActivitiesVisible(r, 0,
-                            preserveWindow);
+                    mRootWindowContainer.ensureActivitiesVisible(r, 0, preserveWindow);
                     if (!kept) {
-                        mAtmService.mRootWindowContainer.resumeFocusedStacksTopActivities();
+                        mRootWindowContainer.resumeFocusedStacksTopActivities();
                     }
                 }
             }
@@ -669,8 +747,8 @@
     boolean reparent(ActivityStack preferredStack, int position,
             @ReparentMoveStackMode int moveStackMode, boolean animate, boolean deferResume,
             boolean schedulePictureInPictureModeChange, String reason) {
-        final ActivityStackSupervisor supervisor = mAtmService.mStackSupervisor;
-        final RootWindowContainer root = mAtmService.mRootWindowContainer;
+        final ActivityStackSupervisor supervisor = mStackSupervisor;
+        final RootWindowContainer root = mRootWindowContainer;
         final WindowManagerService windowManager = mAtmService.mWindowManager;
         final ActivityStack sourceStack = getStack();
         final ActivityStack toStack = supervisor.getReparentTargetStack(this, preferredStack,
@@ -739,7 +817,7 @@
                         wasPaused, reason);
             }
             if (!animate) {
-                mAtmService.mStackSupervisor.mNoAnimActivities.add(topActivity);
+                mStackSupervisor.mNoAnimActivities.add(topActivity);
             }
 
             // We might trigger a configuration change. Save the current task bounds for freezing.
@@ -758,7 +836,7 @@
             } else if (toStackWindowingMode == WINDOWING_MODE_FREEFORM) {
                 Rect bounds = getLaunchBounds();
                 if (bounds == null) {
-                    mAtmService.mStackSupervisor.getLaunchParamsController().layoutTask(this, null);
+                    mStackSupervisor.getLaunchParamsController().layoutTask(this, null);
                     bounds = configBounds;
                 }
                 kept = resize(bounds, RESIZE_MODE_FORCED, !mightReplaceWindow, deferResume);
@@ -766,7 +844,7 @@
                 if (toStackSplitScreenPrimary && moveStackMode == REPARENT_KEEP_STACK_AT_FRONT) {
                     // Move recents to front so it is not behind home stack when going into docked
                     // mode
-                    mAtmService.mStackSupervisor.moveRecentsStackToFront(reason);
+                    mStackSupervisor.moveRecentsStackToFront(reason);
                 }
                 kept = resize(toStack.getRequestedOverrideBounds(), RESIZE_MODE_SYSTEM,
                         !mightReplaceWindow, deferResume);
@@ -832,6 +910,14 @@
         mCallingPackage = r.launchedFromPackage;
         setIntent(r.intent, r.info);
         setLockTaskAuth(r);
+
+        final WindowContainer parent = getParent();
+        if (parent != null) {
+            final Task t = parent.asTask();
+            if (t != null) {
+                t.setIntent(r);
+            }
+        }
     }
 
     /** Sets the original intent, _without_ updating the calling uid or package. */
@@ -945,8 +1031,13 @@
     }
 
     boolean returnsToHomeStack() {
-        final int returnHomeFlags = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME;
-        return intent != null && (intent.getFlags() & returnHomeFlags) == returnHomeFlags;
+        if (inMultiWindowMode() || !hasChild()) return false;
+        if (intent != null) {
+            final int returnHomeFlags = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME;
+            return intent != null && (intent.getFlags() & returnHomeFlags) == returnHomeFlags;
+        }
+        final Task bottomTask = getBottomMostTask();
+        return bottomTask != this && bottomTask.returnsToHomeStack();
     }
 
     void setPrevAffiliate(Task prevAffiliate) {
@@ -961,37 +1052,71 @@
 
     @Override
     void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) {
-        final ActivityStack oldStack = ((ActivityStack) oldParent);
-        final ActivityStack newStack = ((ActivityStack) newParent);
+        final DisplayContent display = newParent != null
+                ? ((WindowContainer) newParent).getDisplayContent() : null;
+        final DisplayContent oldDisplay = oldParent != null
+                ? ((WindowContainer) oldParent).getDisplayContent() : null;
 
-        // Task is going to be removed, clean it up before detaching from hierarchy.
+        mPrevDisplayId = (oldDisplay != null) ? oldDisplay.mDisplayId : INVALID_DISPLAY;
+
         if (oldParent != null && newParent == null) {
-            cleanUpResourcesForDestroy();
+            cleanUpResourcesForDestroy(oldParent);
+        }
+
+        if (display != null) {
+            // TODO(NOW!): Chat with the erosky@ of this code to see if this really makes sense here...
+            // Rotations are relative to the display. This means if there are 2 displays rotated
+            // differently (eg. 2 monitors with one landscape and one portrait), moving a stack
+            // from one to the other could look like a rotation change. To prevent this
+            // apparent rotation change (and corresponding bounds rotation), pretend like our
+            // current rotation is already the same as the new display.
+            // Note, if ActivityStack or related logic ever gets nested, this logic will need
+            // to move to onConfigurationChanged.
+            getConfiguration().windowConfiguration.setRotation(
+                    display.getWindowConfiguration().getRotation());
         }
 
         super.onParentChanged(newParent, oldParent);
 
-        if (oldStack != null) {
-            final PooledConsumer c = PooledLambda.obtainConsumer(
-                    ActivityStack::onActivityRemovedFromStack, oldStack,
-                    PooledLambda.__(ActivityRecord.class));
-            forAllActivities(c);
-            c.recycle();
+        // TODO(NOW): The check for null display content and setting it to null doesn't really
+        //  make sense here...
 
-            if (oldStack.inPinnedWindowingMode()
-                    && (newStack == null || !newStack.inPinnedWindowingMode())) {
+        // TODO(stack-merge): This is mostly taking care of the case where the stask is removing from
+        // the display, so we should probably consolidate it there instead.
+
+        if (getParent() == null && mDisplayContent != null) {
+            EventLogTags.writeWmStackRemoved(getRootTaskId());
+            mDisplayContent = null;
+            mWmService.mWindowPlacerLocked.requestTraversal();
+        }
+
+        if (oldParent != null) {
+            final Task oldParentTask = ((WindowContainer) oldParent).asTask();
+            if (oldParentTask != null) {
+                final PooledConsumer c = PooledLambda.obtainConsumer(
+                        Task::cleanUpActivityReferences, oldParentTask,
+                        PooledLambda.__(ActivityRecord.class));
+                forAllActivities(c);
+                c.recycle();
+            }
+
+            if (oldParent.inPinnedWindowingMode()
+                    && (newParent == null || !newParent.inPinnedWindowingMode())) {
                 // Notify if a task from the pinned stack is being removed
                 // (or moved depending on the mode).
                 mAtmService.getTaskChangeNotificationController().notifyActivityUnpinned();
             }
         }
 
-        if (newStack != null) {
-            final PooledConsumer c = PooledLambda.obtainConsumer(
-                    ActivityStack::onActivityAddedToStack, newStack,
-                    PooledLambda.__(ActivityRecord.class));
-            forAllActivities(c);
-            c.recycle();
+        if (newParent != null) {
+            final Task newParentTask = ((WindowContainer) newParent).asTask();
+            if (newParentTask != null) {
+                final ActivityRecord top = newParentTask.getTopNonFinishingActivity(
+                        false /* includeOverlays */);
+                if (top != null && top.isState(RESUMED)) {
+                    newParentTask.setResumedActivity(top, "addedToTask");
+                }
+            }
 
             // TODO: Ensure that this is actually necessary here
             // Notify the voice session if required
@@ -1022,7 +1147,41 @@
             forceWindowsScaleable(false /* force */);
         }
 
-        mAtmService.mRootWindowContainer.updateUIDsPresentOnDisplay();
+        mRootWindowContainer.updateUIDsPresentOnDisplay();
+    }
+
+    void cleanUpActivityReferences(ActivityRecord r) {
+        final WindowContainer parent = getParent();
+        if (parent != null && parent.asTask() != null) {
+            parent.asTask().cleanUpActivityReferences(r);
+            return;
+        }
+        r.removeTimeouts();
+        mExitingActivities.remove(r);
+
+        if (mResumedActivity != null && mResumedActivity == r) {
+            setResumedActivity(null, "cleanUpActivityReferences");
+        }
+        if (mPausingActivity != null && mPausingActivity == r) {
+            mPausingActivity = null;
+        }
+    }
+
+    /** @return the currently resumed activity. */
+    ActivityRecord getResumedActivity() {
+        return mResumedActivity;
+    }
+
+    void setResumedActivity(ActivityRecord r, String reason) {
+        if (mResumedActivity == r) {
+            return;
+        }
+
+        if (ActivityTaskManagerDebugConfig.DEBUG_STACK) Slog.d(TAG_STACK,
+                "setResumedActivity stack:" + this + " + from: "
+                + mResumedActivity + " to:" + r + " reason:" + reason);
+        mResumedActivity = r;
+        mStackSupervisor.updateTopResumedActivityIfNeeded();
     }
 
     void updateTaskMovement(boolean toFront) {
@@ -1035,7 +1194,7 @@
                 mLastTimeMoved *= -1;
             }
         }
-        mAtmService.mRootWindowContainer.invalidateTaskLayers();
+        mRootWindowContainer.invalidateTaskLayers();
     }
 
     // Close up recents linked list.
@@ -1088,7 +1247,11 @@
 
     /** Returns the intent for the root activity for this task */
     Intent getBaseIntent() {
-        return intent != null ? intent : affinityIntent;
+        if (intent != null) return intent;
+        if (affinityIntent != null) return affinityIntent;
+        // Probably a task that contains other tasks, so return the intent for the top task?
+        final Task topTask = getTopMostTask();
+        return topTask != null ? topTask.getBaseIntent() : null;
     }
 
     /** Returns the first non-finishing activity from the bottom. */
@@ -1173,11 +1336,18 @@
         // If this task had any child before we added this one.
         boolean hadChild = hasChild();
 
-        final ActivityRecord r = (ActivityRecord) child;
-        index = getAdjustedAddPosition(r, index);
-        super.addChild(r, index);
+        index = getAdjustedChildPosition(child, index);
+        super.addChild(child, index);
 
         ProtoLog.v(WM_DEBUG_ADD_REMOVE, "addChild: %s at top.", this);
+
+        // Make sure the list of display UID whitelists is updated
+        // now that this record is in a new task.
+        mRootWindowContainer.updateUIDsPresentOnDisplay();
+
+        final ActivityRecord r = child.asActivityRecord();
+        if (r == null) return;
+
         r.inHistory = true;
 
         // Only set this based on the first activity
@@ -1202,10 +1372,6 @@
         }
 
         updateEffectiveIntent();
-
-        // Make sure the list of display UID whitelists is updated
-        // now that this record is in a new task.
-        mAtmService.mRootWindowContainer.updateUIDsPresentOnDisplay();
     }
 
     void addChild(ActivityRecord r) {
@@ -1213,12 +1379,19 @@
     }
 
     @Override
-    void removeChild(WindowContainer r) {
+    void removeChild(WindowContainer child) {
+        removeChild(child, "removeChild");
+    }
+
+    void removeChild(WindowContainer r, String reason) {
         if (!mChildren.contains(r)) {
             Slog.e(TAG, "removeChild: r=" + r + " not found in t=" + this);
             return;
         }
 
+        if (DEBUG_TASK_MOVEMENT) {
+            Slog.d(TAG_WM, "removeChild: child=" + r + " reason=" + reason);
+        }
         super.removeChild(r);
 
         if (inPinnedWindowingMode()) {
@@ -1228,7 +1401,15 @@
             mAtmService.getTaskChangeNotificationController().notifyTaskStackChanged();
         }
 
-        final String reason = "removeChild";
+        final boolean isRootTask = isRootTask();
+        if (isRootTask) {
+            final DisplayContent display = getDisplayContent();
+            if (display.isSingleTaskInstance()) {
+                mAtmService.notifySingleTaskDisplayEmpty(display.mDisplayId);
+            }
+            display.mDisplayContent.setLayoutNeeded();
+        }
+
         if (hasChild()) {
             updateEffectiveIntent();
 
@@ -1243,12 +1424,14 @@
                 // work.
                 // TODO: If the callers to removeChild() changes such that we have multiple places
                 //       where we are destroying the task, move this back into removeChild()
-                mAtmService.mStackSupervisor.removeTask(this, false /* killProcess */,
+                mStackSupervisor.removeTask(this, false /* killProcess */,
                         !REMOVE_FROM_RECENTS, reason);
             }
         } else if (!mReuseTask) {
             // Remove entire task if it doesn't have any activity left and it isn't marked for reuse
-            getStack().removeChild(this, reason);
+            if (!isRootTask) {
+                getStack().removeChild(this, reason);
+            }
             EventLogTags.writeWmTaskRemoved(mTaskId,
                     "removeChild: last r=" + r + " in t=" + this);
             removeIfPossible();
@@ -1419,11 +1602,15 @@
 
     @Override
     public boolean supportsSplitScreenWindowingMode() {
+        final Task topTask = getTopMostTask();
+        return super.supportsSplitScreenWindowingMode()
+                && (topTask == null || topTask.supportsSplitScreenWindowingModeInner());
+    }
+
+    private boolean supportsSplitScreenWindowingModeInner() {
         // A task can not be docked even if it is considered resizeable because it only supports
         // picture-in-picture mode but has a non-resizeable resizeMode
         return super.supportsSplitScreenWindowingMode()
-                // TODO(task-group): Probably makes sense to move this and associated code into
-                // WindowContainer so it affects every node.
                 && mAtmService.mSupportsSplitScreenMultiWindow
                 && (mAtmService.mForceResizableActivities
                         || (isResizeable(false /* checkSupportsPip */)
@@ -1438,7 +1625,7 @@
      *         secondary display.
      */
     boolean canBeLaunchedOnDisplay(int displayId) {
-        return mAtmService.mStackSupervisor.canPlaceEntityOnDisplay(displayId,
+        return mStackSupervisor.canPlaceEntityOnDisplay(displayId,
                 -1 /* don't check PID */, -1 /* don't check UID */, null /* activityInfo */);
     }
 
@@ -1508,6 +1695,14 @@
         }
         mAtmService.getTaskChangeNotificationController().notifyTaskDescriptionChanged(
                 getTaskInfo());
+
+        final WindowContainer parent = getParent();
+        if (parent != null) {
+            final Task t = parent.asTask();
+            if (t != null) {
+                t.updateTaskDescription();
+            }
+        }
     }
 
     private static boolean setTaskDescriptionFromActivityAboveRoot(
@@ -1550,9 +1745,11 @@
     @VisibleForTesting
     void updateEffectiveIntent() {
         final ActivityRecord root = getRootActivity(true /*setToBottomIfNone*/);
-        setIntent(root);
-        // Update the task description when the activities change
-        updateTaskDescription();
+        if (root != null) {
+            setIntent(root);
+            // Update the task description when the activities change
+            updateTaskDescription();
+        }
     }
 
     void adjustForMinimalTaskDimensions(Rect bounds, Rect previousBounds) {
@@ -1566,9 +1763,8 @@
         // If the task has no requested minimal size, we'd like to enforce a minimal size
         // so that the user can not render the task too small to manipulate. We don't need
         // to do this for the pinned stack as the bounds are controlled by the system.
-        if (!inPinnedWindowingMode() && getDisplayContent() != null) {
-            final int defaultMinSizeDp =
-                    mAtmService.mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp;
+        if (!inPinnedWindowingMode() && getStack() != null) {
+            final int defaultMinSizeDp = mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp;
             final DisplayContent display = getDisplayContent();
             final float density =
                     (float) display.getConfiguration().densityDpi / DisplayMetrics.DENSITY_DEFAULT;
@@ -1632,10 +1828,25 @@
      * @param reason The reason for the change.
      */
     void onActivityStateChanged(ActivityRecord record, ActivityState state, String reason) {
-        final ActivityStack parent = getStack();
+        final Task parentTask = getParent().asTask();
+        if (parentTask != null) {
+            parentTask.onActivityStateChanged(record, state, reason);
+            return;
+        }
 
-        if (parent != null) {
-            parent.onActivityStateChanged(record, state, reason);
+        if (record == mResumedActivity && state != RESUMED) {
+            setResumedActivity(null, reason + " - onActivityStateChanged");
+        }
+
+        if (state == RESUMED) {
+            if (ActivityTaskManagerDebugConfig.DEBUG_STACK) {
+                Slog.v(TAG_STACK, "set resumed activity to:" + record + " reason:" + reason);
+            }
+            setResumedActivity(record, reason + " - onActivityStateChanged");
+            if (record == mRootWindowContainer.getTopResumedActivity()) {
+                mAtmService.setResumedActivityUncheckLocked(record, reason);
+            }
+            mStackSupervisor.mRecentTasks.add(record.getTask());
         }
     }
 
@@ -1657,7 +1868,7 @@
         final boolean wasInMultiWindowMode = inMultiWindowMode();
         super.onConfigurationChanged(newParentConfig);
         if (wasInMultiWindowMode != inMultiWindowMode()) {
-            mAtmService.mStackSupervisor.scheduleUpdateMultiWindowMode(this);
+            mStackSupervisor.scheduleUpdateMultiWindowMode(this);
         }
 
         // If the configuration supports persistent bounds (eg. Freeform), keep track of the
@@ -1678,7 +1889,11 @@
      * It only saves state if this task has been shown to user and it's in fullscreen or freeform
      * mode on freeform displays.
      */
-    void saveLaunchingStateIfNeeded() {
+    private void saveLaunchingStateIfNeeded() {
+        saveLaunchingStateIfNeeded(getDisplayContent());
+    }
+
+    private void saveLaunchingStateIfNeeded(DisplayContent display) {
         if (!hasBeenVisible) {
             // Not ever visible to user.
             return;
@@ -1698,7 +1913,7 @@
         }
 
         // Saves the new state so that we can launch the activity at the same location.
-        mAtmService.mStackSupervisor.mLaunchParamsPersister.saveTask(this);
+        mStackSupervisor.mLaunchParamsPersister.saveTask(this, display);
     }
 
     /**
@@ -1960,6 +2175,10 @@
 
     @Override
     void resolveOverrideConfiguration(Configuration newParentConfig) {
+        if (isRootTask()) {
+            super.resolveOverrideConfiguration(newParentConfig);
+            return;
+        }
         mTmpBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds());
         super.resolveOverrideConfiguration(newParentConfig);
         int windowingMode =
@@ -1984,12 +2203,12 @@
             final Rect parentBounds =
                     new Rect(newParentConfig.windowConfiguration.getBounds());
             final DisplayContent display = getDisplayContent();
-            if (display != null && display.mDisplayContent != null) {
+            if (display != null) {
                 // If a freeform window moves below system bar, there is no way to move it again
                 // by touch. Because its caption is covered by system bar. So we exclude them
                 // from stack bounds. and then caption will be shown inside stable area.
                 final Rect stableBounds = new Rect();
-                display.mDisplayContent.getStableRect(stableBounds);
+                display.getStableRect(stableBounds);
                 parentBounds.intersect(stableBounds);
             }
 
@@ -2064,6 +2283,7 @@
      * input stack. */
     void updateOverrideConfigurationForStack(ActivityStack inStack) {
         final ActivityStack stack = getStack();
+
         if (stack != null && stack == inStack) {
             return;
         }
@@ -2079,7 +2299,7 @@
             if (mLastNonFullscreenBounds != null) {
                 setBounds(mLastNonFullscreenBounds);
             } else {
-                mAtmService.mStackSupervisor.getLaunchParamsController().layoutTask(this, null);
+                mStackSupervisor.getLaunchParamsController().layoutTask(this, null);
             }
         } else {
             setBounds(inStack.getRequestedOverrideBounds());
@@ -2122,7 +2342,10 @@
 
     @Override
     DisplayContent getDisplayContent() {
-        return getStack() != null ? getStack().getDisplayContent() : null;
+        // TODO: Why aren't we just using our own display content vs. parent's???
+        final ActivityStack stack = getStack();
+        return stack != null && stack != this
+                ? stack.getDisplayContent() : super.getDisplayContent();
     }
 
     int getDisplayId() {
@@ -2130,39 +2353,122 @@
         return dc != null ? dc.mDisplayId : INVALID_DISPLAY;
     }
 
+    // TODO: Migrate callers to getRootTask()
     ActivityStack getStack() {
-        return (ActivityStack) getParent();
+        return (ActivityStack) getRootTask();
     }
 
-    /**
-     * @return Id of current stack, {@link ActivityTaskManager#INVALID_STACK_ID} if no stack is set.
+    /** @return Id of root task. */
+    int getRootTaskId() {
+        return getRootTask().mTaskId;
+    }
+
+    Task getRootTask() {
+        final WindowContainer parent = getParent();
+        if (parent == null) return this;
+
+        final Task parentTask = parent.asTask();
+        return parentTask == null ? this : parentTask.getRootTask();
+    }
+
+    // TODO(task-merge): Figure out what's the right thing to do for places that used it.
+    boolean isRootTask() {
+        return getRootTask() == this;
+    }
+
+    int getDescendantTaskCount() {
+        final int[] currentCount = {0};
+        final PooledConsumer c = PooledLambda.obtainConsumer((t, count) -> { count[0]++; },
+                PooledLambda.__(Task.class), currentCount);
+        forAllTasks(c, false /* traverseTopToBottom */, this);
+        c.recycle();
+        return currentCount[0];
+    }
+
+    /** Calculate the minimum possible position for a task that can be shown to the user.
+     *  The minimum position will be above all other tasks that can't be shown.
+     *  @param minPosition The minimum position the caller is suggesting.
+     *                  We will start adjusting up from here.
+     *  @param size The size of the current task list.
      */
-    int getStackId() {
-        final ActivityStack stack = getStack();
-        return stack != null ? stack.mStackId : INVALID_STACK_ID;
+    // TODO: Move user to their own window container.
+    private int computeMinUserPosition(int minPosition, int size) {
+        while (minPosition < size) {
+            final WindowContainer child = mChildren.get(minPosition);
+            final boolean canShow = child.showToCurrentUser();
+            if (canShow) {
+                break;
+            }
+            minPosition++;
+        }
+        return minPosition;
     }
 
-    // TODO(task-hierarchy): Needs to take a generic WindowManager when task contains other tasks.
-    int getAdjustedAddPosition(ActivityRecord r, int suggestedPosition) {
-        int maxPosition = mChildren.size();
-        if (!r.isTaskOverlay()) {
-            // We want to place all non-overlay activities below overlays.
-            final ActivityRecord bottomMostOverlay = getActivity((ar) -> ar.isTaskOverlay(), false);
-            if (bottomMostOverlay != null) {
-                maxPosition = Math.max(mChildren.indexOf(bottomMostOverlay) - 1, 0);
+    /** Calculate the maximum possible position for a task that can't be shown to the user.
+     *  The maximum position will be below all other tasks that can be shown.
+     *  @param maxPosition The maximum position the caller is suggesting.
+     *                  We will start adjusting down from here.
+     */
+    // TODO: Move user to their own window container.
+    private int computeMaxUserPosition(int maxPosition) {
+        while (maxPosition > 0) {
+            final WindowContainer child = mChildren.get(maxPosition);
+            final boolean canShow = child.showToCurrentUser();
+            if (!canShow) {
+                break;
+            }
+            maxPosition--;
+        }
+        return maxPosition;
+    }
+
+    private int getAdjustedChildPosition(WindowContainer wc, int suggestedPosition) {
+        final boolean canShowChild = wc.showToCurrentUser();
+
+        final int size = mChildren.size();
+
+        // Figure-out min/max possible position depending on if child can show for current user.
+        int minPosition = (canShowChild) ? computeMinUserPosition(0, size) : 0;
+        int maxPosition = (canShowChild) ? size : computeMaxUserPosition(size - 1);
+
+        // Factor in always-on-top children in max possible position.
+        if (!wc.isAlwaysOnTop()) {
+
+            // We want to place all non-always-on-top containers below always-on-top ones.
+            while (maxPosition > minPosition) {
+                if (!mChildren.get(maxPosition - 1).isAlwaysOnTop()) break;
+                --maxPosition;
             }
         }
 
-        return Math.min(maxPosition, suggestedPosition);
+        // preserve POSITION_BOTTOM/POSITION_TOP positions if they are still valid.
+        if (suggestedPosition == POSITION_BOTTOM && minPosition == 0) {
+            return POSITION_BOTTOM;
+        } else if (suggestedPosition == POSITION_TOP && maxPosition == (size - 1)) {
+            return POSITION_TOP;
+        }
+        // Reset position based on minimum/maximum possible positions.
+        return Math.min(Math.max(suggestedPosition, minPosition), maxPosition);
     }
 
     @Override
     void positionChildAt(int position, WindowContainer child, boolean includingParents) {
-        position = getAdjustedAddPosition((ActivityRecord) child, position);
+        position = getAdjustedChildPosition(child, position);
         super.positionChildAt(position, child, includingParents);
+
+        // Log positioning.
+        if (DEBUG_TASK_MOVEMENT) Slog.d(TAG_WM, "positionChildAt: child=" + child
+                + " position=" + position + " parent=" + this);
+
+        final int toTop = position >= (mChildren.size() - 1) ? 1 : 0;
+        final Task task = child.asTask();
+        if (task != null) {
+            EventLogTags.writeWmTaskMoved(task.mTaskId, toTop, position);
+        }
     }
 
-    private boolean hasWindowsAlive() {
+    @VisibleForTesting
+    boolean hasWindowsAlive() {
         return getActivity(ActivityRecord::hasWindowsAlive) != null;
     }
 
@@ -2179,6 +2485,10 @@
     void removeImmediately() {
         if (DEBUG_STACK) Slog.i(TAG, "removeTask: removing taskId=" + mTaskId);
         EventLogTags.writeWmTaskRemoved(mTaskId, "removeTask");
+
+        // If applicable let the TaskOrganizer know the Task is vanishing.
+        setTaskOrganizer(null);
+
         super.removeImmediately();
     }
 
@@ -2188,8 +2498,6 @@
                 + " from stack=" + getStack());
         EventLogTags.writeWmTaskRemoved(mTaskId, "reParentTask:" + reason);
 
-        position = stack.findPositionForTask(this, position);
-
         reparent(stack, position);
 
         stack.positionChildAt(position, this, moveParents);
@@ -2255,11 +2563,16 @@
 
     @Override
     void onDisplayChanged(DisplayContent dc) {
-        adjustBoundsForDisplayChangeIfNeeded(dc);
+        final boolean isRootTask = isRootTask();
+        if (!isRootTask) {
+            adjustBoundsForDisplayChangeIfNeeded(dc);
+        }
         super.onDisplayChanged(dc);
-        final int displayId = (dc != null) ? dc.getDisplayId() : INVALID_DISPLAY;
-        mWmService.mAtmService.getTaskChangeNotificationController().notifyTaskDisplayChanged(
-                mTaskId, displayId);
+        if (!isRootTask) {
+            final int displayId = (dc != null) ? dc.getDisplayId() : INVALID_DISPLAY;
+            mWmService.mAtmService.getTaskChangeNotificationController().notifyTaskDisplayChanged(
+                    mTaskId, displayId);
+        }
     }
 
     /**
@@ -2401,7 +2714,7 @@
     }
 
     /** Bounds of the task to be used for dimming, as well as touch related tests. */
-    public void getDimBounds(Rect out) {
+    void getDimBounds(Rect out) {
         final DisplayContent displayContent = getStack().getDisplayContent();
         // It doesn't matter if we in particular are part of the resize, since we couldn't have
         // a DimLayer anyway if we weren't visible.
@@ -2442,7 +2755,7 @@
             // No need to check if the mode is allowed if it's leaving dragResize
             if (dragResizing && !DragResizeMode.isModeAllowedForStack(getStack(), dragResizeMode)) {
                 throw new IllegalArgumentException("Drag resize mode not allow for stack stackId="
-                        + getStack().mStackId + " dragResizeMode=" + dragResizeMode);
+                        + getRootTaskId() + " dragResizeMode=" + dragResizeMode);
             }
             mDragResizing = dragResizing;
             mDragResizeMode = dragResizeMode;
@@ -2534,6 +2847,11 @@
         mForceShowForAllUsers = forceShowForAllUsers;
     }
 
+    public boolean isAttached() {
+        final DisplayContent display = getDisplayContent();
+        return display != null && !display.isRemoved();
+    }
+
     /**
      * When we are in a floating stack (Freeform, Pinned, ...) we calculate
      * insets differently. However if we are animating to the fullscreen stack
@@ -2545,6 +2863,45 @@
                 && !getStack().isAnimatingBoundsToFullscreen() && !mPreserveNonFloatingState;
     }
 
+    /**
+     * Returns true if the stack is translucent and can have other contents visible behind it if
+     * needed. A stack is considered translucent if it don't contain a visible or
+     * starting (about to be visible) activity that is fullscreen (opaque).
+     * @param starting The currently starting activity or null if there is none.
+     */
+    @VisibleForTesting
+    boolean isTranslucent(ActivityRecord starting) {
+        if (!isAttached() || mForceHidden) {
+            return true;
+        }
+        final PooledPredicate p = PooledLambda.obtainPredicate(Task::isOpaqueActivity,
+                PooledLambda.__(ActivityRecord.class), starting);
+        final ActivityRecord opaque = getActivity(p);
+        p.recycle();
+        return opaque == null;
+    }
+
+    private static boolean isOpaqueActivity(ActivityRecord r, ActivityRecord starting) {
+        if (r.finishing) {
+            // We don't factor in finishing activities when determining translucency since
+            // they will be gone soon.
+            return false;
+        }
+
+        if (!r.visibleIgnoringKeyguard && r != starting) {
+            // Also ignore invisible activities that are not the currently starting
+            // activity (about to be visible).
+            return false;
+        }
+
+        if (r.occludesParent() || r.hasWallpaper) {
+            // Stack isn't translucent if it has at least one fullscreen activity
+            // that is visible.
+            return true;
+        }
+        return false;
+    }
+
     @Override
     public SurfaceControl getAnimationLeashParent() {
         if (WindowManagerService.sHierarchicalAnimations) {
@@ -2567,6 +2924,12 @@
     }
 
     boolean shouldAnimate() {
+        /**
+         * Animations are handled by the TaskOrganizer implementation.
+         */
+        if (isControlledByTaskOrganizer()) {
+            return false;
+        }
         // Don't animate while the task runs recents animation but only if we are in the mode
         // where we cancel with deferred screenshot, which means that the controller has
         // transformed the task.
@@ -2579,18 +2942,6 @@
     }
 
     @Override
-    protected void onAnimationFinished() {
-        super.onAnimationFinished();
-        // TODO(b/142617871): we may need to add animation type parameter on onAnimationFinished to
-        //  identify if the callback is for launch animation finish and then calling
-        //  activity#onAnimationFinished.
-        final ActivityRecord activity = getTopMostActivity();
-        if (activity != null) {
-            activity.onAnimationFinished();
-        }
-    }
-
-    @Override
     SurfaceControl.Builder makeSurface() {
         return super.makeSurface().setMetadata(METADATA_TASK_ID, mTaskId);
     }
@@ -2602,7 +2953,7 @@
                 return true;
             }
         }
-        return false;
+        return forAllTasks((t) -> { return t != this && t.isTaskAnimating(); });
     }
 
     /**
@@ -2680,24 +3031,38 @@
 
     @Override
     boolean fillsParent() {
-        return matchParentBounds() || !getWindowConfiguration().canResizeTask();
+        return matchParentBounds();
+    }
+
+    void forAllTasks(Consumer<Task> callback, boolean traverseTopToBottom, Task excludedTask) {
+        if (traverseTopToBottom) {
+            super.forAllTasks(callback, traverseTopToBottom);
+            if (excludedTask != this) {
+                callback.accept(this);
+            }
+        } else {
+            super.forAllTasks(callback, traverseTopToBottom);
+            if (excludedTask != this) {
+                callback.accept(this);
+            }
+        }
     }
 
     @Override
     void forAllTasks(Consumer<Task> callback, boolean traverseTopToBottom) {
-        // TODO(task-hierarchy): Change to traverse children when tasks can contain other tasks.
-        callback.accept(this);
+        forAllTasks(callback, traverseTopToBottom, null /* excludedTask */);
     }
 
     @Override
     boolean forAllTasks(Function<Task, Boolean> callback) {
+        if (super.forAllTasks(callback)) return true;
         return callback.apply(this);
     }
 
     @Override
     Task getTask(Predicate<Task> callback, boolean traverseTopToBottom) {
-        // I'm a task!
-        // TODO(task-hierarchy): Change to traverse children when tasks can contain other tasks.
+        final Task t = super.getTask(callback, traverseTopToBottom);
+        if (t != null) return t;
         return callback.test(this) ? this : null;
     }
 
@@ -2733,6 +3098,16 @@
         return mDimmer;
     }
 
+    void dim(float alpha) {
+        mDimmer.dimAbove(getPendingTransaction(), alpha);
+        scheduleAnimation();
+    }
+
+    void stopDimming() {
+        mDimmer.stopDim(getPendingTransaction());
+        scheduleAnimation();
+    }
+
     boolean isTaskForUser(int userId) {
         return mUserId == userId;
     }
@@ -2780,7 +3155,7 @@
     }
 
     @Override
-    public void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+    void dump(PrintWriter pw, String prefix, boolean dumpAll) {
         super.dump(pw, prefix, dumpAll);
         final String doublePrefix = prefix + "  ";
 
@@ -2806,7 +3181,7 @@
     void fillTaskInfo(TaskInfo info) {
         getNumRunningActivities(mReuseActivitiesReport);
         info.userId = mUserId;
-        info.stackId = getStackId();
+        info.stackId = getRootTaskId();
         info.taskId = mTaskId;
         info.displayId = getDisplayId();
         info.isRunning = getTopNonFinishingActivity() != null;
@@ -2840,6 +3215,17 @@
         return mTaskId == taskId;
     }
 
+    @Override
+    Task asTask() {
+        // I'm a task!
+        return this;
+    }
+
+    // TODO(task-merge): Figure-out how this should work with hierarchy tasks.
+    boolean shouldBeVisible(ActivityRecord starting) {
+        return true;
+    }
+
     void dump(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.print("userId="); pw.print(mUserId);
         pw.print(" effectiveUid="); UserHandle.formatUid(pw, effectiveUid);
@@ -2926,7 +3312,7 @@
         if (mRootProcess != null) {
             pw.print(prefix); pw.print("mRootProcess="); pw.println(mRootProcess);
         }
-        pw.print(prefix); pw.print("stackId="); pw.println(getStackId());
+        pw.print(prefix); pw.print("taskId=" + mTaskId); pw.println(" stackId=" + getRootTaskId());
         pw.print(prefix + "hasBeenVisible=" + hasBeenVisible);
         pw.print(" mResizeMode=" + ActivityInfo.resizeModeToString(mResizeMode));
         pw.print(" mSupportsPictureInPicture=" + mSupportsPictureInPicture);
@@ -2943,7 +3329,7 @@
             sb.append(" U=");
             sb.append(mUserId);
             sb.append(" StackId=");
-            sb.append(getStackId());
+            sb.append(getRootTaskId());
             sb.append(" sz=");
             sb.append(getChildCount());
             sb.append('}');
@@ -2953,6 +3339,10 @@
         sb.append(Integer.toHexString(System.identityHashCode(this)));
         sb.append(" #");
         sb.append(mTaskId);
+        sb.append(" visible=" + shouldBeVisible(null /* starting */));
+        sb.append(" type=" + activityTypeToString(getActivityType()));
+        sb.append(" mode=" + windowingModeToString(getWindowingMode()));
+        sb.append(" translucent=" + isTranslucent(null /* starting */));
         if (affinity != null) {
             sb.append(" A=");
             sb.append(affinity);
@@ -2969,8 +3359,7 @@
         return toString();
     }
 
-    @Override
-    public void dumpDebug(ProtoOutputStream proto, long fieldId,
+    void dumpDebugInner(ProtoOutputStream proto, long fieldId,
             @WindowTraceLogLevel int logLevel) {
         if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible()) {
             return;
@@ -2983,7 +3372,7 @@
         forAllActivities((r) -> {
             r.dumpDebug(proto, ACTIVITIES);
         });
-        proto.write(STACK_ID, getStackId());
+        proto.write(STACK_ID, getRootTaskId());
         if (mLastNonFullscreenBounds != null) {
             mLastNonFullscreenBounds.dumpDebug(proto, LAST_NON_FULLSCREEN_BOUNDS);
         }
@@ -3181,13 +3570,13 @@
         Task create(ActivityTaskManagerService service, int taskId, ActivityInfo info,
                 Intent intent, IVoiceInteractionSession voiceSession,
                 IVoiceInteractor voiceInteractor, ActivityStack stack) {
-            return new Task(service, taskId, info, intent, voiceSession, voiceInteractor,
+            return new ActivityStack(service, taskId, info, intent, voiceSession, voiceInteractor,
                     null /*taskDescription*/, stack);
         }
 
         Task create(ActivityTaskManagerService service, int taskId, ActivityInfo info,
                 Intent intent, TaskDescription taskDescription, ActivityStack stack) {
-            return new Task(service, taskId, info, intent, null /*voiceSession*/,
+            return new ActivityStack(service, taskId, info, intent, null /*voiceSession*/,
                     null /*voiceInteractor*/, taskDescription, stack);
         }
 
@@ -3204,7 +3593,7 @@
                 int nextTaskId, int taskAffiliationColor, int callingUid, String callingPackage,
                 int resizeMode, boolean supportsPictureInPicture, boolean realActivitySuspended,
                 boolean userSetupComplete, int minWidth, int minHeight, ActivityStack stack) {
-            return new Task(service, taskId, intent, affinityIntent, affinity,
+            return new ActivityStack(service, taskId, intent, affinityIntent, affinity,
                     rootAffinity, realActivity, origActivity, rootWasReset, autoRemoveRecents,
                     askedCompatMode, userId, effectiveUid, lastDescription,
                     lastTimeMoved, neverRelinquishIdentity, lastTaskDescription, taskAffiliation,
@@ -3444,4 +3833,96 @@
             XmlUtils.skipCurrentTag(in);
         }
     }
+
+    boolean isControlledByTaskOrganizer() {
+        // TODO(b/147849315): Clean-up relationship between task-org and task-hierarchy. Ideally
+        //  we only give control of the root task.
+        return getTopMostTask().mTaskOrganizer != null;
+    }
+
+    @Override
+    protected void reparentSurfaceControl(SurfaceControl.Transaction t, SurfaceControl newParent) {
+        /**
+         * Avoid yanking back control from the TaskOrganizer, which has presumably reparented the
+         * Surface in to its own hierarchy.
+         */
+        if (isControlledByTaskOrganizer()) {
+            return;
+        }
+        super.reparentSurfaceControl(t, newParent);
+    }
+
+    private void sendTaskAppeared() {
+        if (mSurfaceControl != null && mTaskOrganizer != null) {
+            mAtmService.mTaskOrganizerController.onTaskAppeared(mTaskOrganizer, this);
+        }
+    }
+
+    private void sendTaskVanished() {
+        if (mTaskOrganizer != null) {
+            mAtmService.mTaskOrganizerController.onTaskVanished(mTaskOrganizer, this);
+        }
+   }
+
+    void setTaskOrganizer(ITaskOrganizer organizer) {
+        // Let the old organizer know it has lost control.
+        if (mTaskOrganizer != null) {
+            sendTaskVanished();
+        }
+        mTaskOrganizer = organizer;
+        sendTaskAppeared();
+    }
+
+    // Called on Binder death.
+    void taskOrganizerDied() {
+        mTaskOrganizer = null;
+    }
+
+    @Override
+    void setSurfaceControl(SurfaceControl sc) {
+        super.setSurfaceControl(sc);
+        // If the TaskOrganizer was set before we created the SurfaceControl, we need to
+        // emit the callbacks now.
+        sendTaskAppeared();
+    }
+
+    @Override
+    public void updateSurfacePosition() {
+        // Avoid fighting with the TaskOrganizer over Surface position.
+        if (isControlledByTaskOrganizer()) {
+            getPendingTransaction().setPosition(mSurfaceControl, 0, 0);
+            scheduleAnimation();
+            return;
+        } else {
+            super.updateSurfacePosition();
+        }
+    }
+
+    @Override
+    void getRelativeDisplayedPosition(Point outPos) {
+        // In addition to updateSurfacePosition, we keep other code that sets
+        // position from fighting with the TaskOrganizer
+        if (isControlledByTaskOrganizer()) {
+            outPos.set(0, 0);
+            return;
+        }
+        super.getRelativeDisplayedPosition(outPos);
+    }
+
+    @Override
+    public void setWindowingMode(int windowingMode) {
+        super.setWindowingMode(windowingMode);
+        windowingMode = getWindowingMode();
+
+        // TODO(b/147849315): Clean-up relationship between task-org and task-hierarchy. Ideally
+        //  we only give control of the root task.
+        // Different windowing modes may be managed by different task organizers. If
+        // getTaskOrganizer returns null, we still call transferToTaskOrganizer to make sure we
+        // clear it.
+        if (!isRootTask()) {
+            final ITaskOrganizer org =
+                    mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode);
+            setTaskOrganizer(org);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
index 688fe12..2dde0ba 100644
--- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
@@ -350,7 +350,7 @@
     void notifyActivityPinned(ActivityRecord r) {
         mHandler.removeMessages(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG);
         final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG,
-                r.getTask().mTaskId, r.getStackId(), r.packageName);
+                r.getTask().mTaskId, r.getRootTaskId(), r.packageName);
         msg.sendingUid = r.mUserId;
         forAllLocalListeners(mNotifyActivityPinned, msg);
         msg.sendToTarget();
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
new file mode 100644
index 0000000..283be40
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.view.ITaskOrganizer;
+import android.view.SurfaceControl;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * Stores the TaskOrganizers associated with a given windowing mode and
+ * their associated state.
+ */
+class TaskOrganizerController {
+    private static final String TAG = "TaskOrganizerController";
+
+    private WindowManagerGlobalLock mGlobalLock;
+
+    private class DeathRecipient implements IBinder.DeathRecipient {
+        int mWindowingMode;
+        ITaskOrganizer mTaskOrganizer;
+
+        DeathRecipient(ITaskOrganizer organizer, int windowingMode) {
+            mTaskOrganizer = organizer;
+            mWindowingMode = windowingMode;
+        }
+
+        @Override
+        public void binderDied() {
+            synchronized (mGlobalLock) {
+                final TaskOrganizerState state = mTaskOrganizerStates.get(mTaskOrganizer);
+                for (int i = 0; i < state.mOrganizedTasks.size(); i++) {
+                    state.mOrganizedTasks.get(i).taskOrganizerDied();
+                }
+                mTaskOrganizerStates.remove(mTaskOrganizer);
+                if (mTaskOrganizersForWindowingMode.get(mWindowingMode) == mTaskOrganizer) {
+                    mTaskOrganizersForWindowingMode.remove(mWindowingMode);
+                }
+            }
+        }
+    };
+
+    class TaskOrganizerState {
+        ITaskOrganizer mOrganizer;
+        DeathRecipient mDeathRecipient;
+
+        ArrayList<Task> mOrganizedTasks = new ArrayList<>();
+
+        void addTask(Task t) {
+            mOrganizedTasks.add(t);
+        }
+
+        void removeTask(Task t) {
+            mOrganizedTasks.remove(t);
+        }
+
+        TaskOrganizerState(ITaskOrganizer organizer, DeathRecipient deathRecipient) {
+            mOrganizer = organizer;
+            mDeathRecipient = deathRecipient;
+        }
+    };
+
+
+    final HashMap<Integer, TaskOrganizerState> mTaskOrganizersForWindowingMode = new HashMap();
+    final HashMap<ITaskOrganizer, TaskOrganizerState> mTaskOrganizerStates = new HashMap();
+
+    final HashMap<Integer, ITaskOrganizer> mTaskOrganizersByPendingSyncId = new HashMap();
+
+    final ActivityTaskManagerService mService;
+
+    TaskOrganizerController(ActivityTaskManagerService atm, WindowManagerGlobalLock lock) {
+        mService = atm;
+        mGlobalLock = lock;
+    }
+
+    private void clearIfNeeded(int windowingMode) {
+        final TaskOrganizerState oldState = mTaskOrganizersForWindowingMode.get(windowingMode);
+        if (oldState != null) {
+            oldState.mOrganizer.asBinder().unlinkToDeath(oldState.mDeathRecipient, 0);
+        }
+    }
+
+    /**
+     * Register a TaskOrganizer to manage tasks as they enter the given windowing mode.
+     * If there was already a TaskOrganizer for this windowing mode it will be evicted
+     * and receive taskVanished callbacks in the process.
+     */
+    void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) {
+        if (windowingMode != WINDOWING_MODE_PINNED) {
+            throw new UnsupportedOperationException(
+                    "As of now only Pinned windowing mode is supported for registerTaskOrganizer");
+
+        }
+        clearIfNeeded(windowingMode);
+        DeathRecipient dr = new DeathRecipient(organizer, windowingMode);
+        try {
+            organizer.asBinder().linkToDeath(dr, 0);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "TaskOrganizer failed to register death recipient");
+        }
+
+        final TaskOrganizerState state = new TaskOrganizerState(organizer, dr);
+        mTaskOrganizersForWindowingMode.put(windowingMode, state);
+
+        mTaskOrganizerStates.put(organizer, state);
+    }
+
+    ITaskOrganizer getTaskOrganizer(int windowingMode) {
+        final TaskOrganizerState state = mTaskOrganizersForWindowingMode.get(windowingMode);
+        if (state == null) {
+            return null;
+        }
+        return state.mOrganizer;
+    }
+
+    private void sendTaskAppeared(ITaskOrganizer organizer, Task task) {
+        try {
+            organizer.taskAppeared(task.getRemoteToken(), task.getTaskInfo());
+        } catch (Exception e) {
+            Slog.e(TAG, "Exception sending taskAppeared callback" + e);
+        }
+    }
+
+    private void sendTaskVanished(ITaskOrganizer organizer, Task task) {
+        try {
+            organizer.taskVanished(task.getRemoteToken());
+        } catch (Exception e) {
+            Slog.e(TAG, "Exception sending taskVanished callback" + e);
+        }
+    }
+
+    void onTaskAppeared(ITaskOrganizer organizer, Task task) {
+        TaskOrganizerState state = mTaskOrganizerStates.get(organizer);
+
+        state.addTask(task);
+        sendTaskAppeared(organizer, task);
+    }
+
+    void onTaskVanished(ITaskOrganizer organizer, Task task) {
+        final TaskOrganizerState state = mTaskOrganizerStates.get(organizer);
+        sendTaskVanished(organizer, task);
+
+        // This could trigger TaskAppeared for other tasks in the same stack so make sure
+        // we do this AFTER sending taskVanished.
+        state.removeTask(task);
+    }
+}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index d36a5d4..38a7000 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -53,7 +53,7 @@
     private static final String SNAPSHOTS_DIRNAME = "snapshots";
     private static final String REDUCED_POSTFIX = "_reduced";
     private static final float REDUCED_SCALE = .5f;
-    private static final float LOW_RAM_REDUCED_SCALE = .6f;
+    private static final float LOW_RAM_REDUCED_SCALE = .8f;
     static final boolean DISABLE_FULL_SIZED_BITMAPS = ActivityManager.isLowRamDeviceStatic();
     private static final long DELAY_MS = 100;
     private static final int QUALITY = 95;
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index e2a21a9..57de753 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -55,6 +55,7 @@
 import android.graphics.Color;
 import android.graphics.GraphicBuffer;
 import android.graphics.Paint;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.Looper;
@@ -115,6 +116,7 @@
     private static final String TAG = TAG_WITH_CLASS_NAME ? "SnapshotStartingWindow" : TAG_WM;
     private static final int MSG_REPORT_DRAW = 0;
     private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=%s";
+    private static final Point sSurfaceSize = new Point(); //tmp var for unused relayout param
     private final Window mWindow;
     private final Surface mSurface;
     private SurfaceControl mSurfaceControl;
@@ -227,7 +229,8 @@
         try {
             session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, -1,
                     tmpFrame, tmpContentInsets, tmpRect, tmpStableInsets, tmpRect,
-                    tmpCutout, tmpMergedConfiguration, surfaceControl, mTmpInsetsState);
+                    tmpCutout, tmpMergedConfiguration, surfaceControl, mTmpInsetsState,
+                    sSurfaceSize);
         } catch (RemoteException e) {
             // Local call.
         }
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index cefef37..1c876d9 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -42,6 +42,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerService.logWithStack;
+import static com.android.server.wm.WindowManagerService.sHierarchicalAnimations;
 import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
 
 import android.annotation.CallSuper;
@@ -246,6 +247,8 @@
 
     private MagnificationSpec mLastMagnificationSpec;
 
+    private boolean mIsFocusable = true;
+
     WindowContainer(WindowManagerService wms) {
         mWmService = wms;
         mPendingTransaction = wms.mTransactionFactory.get();
@@ -342,7 +345,7 @@
         if (mSurfaceControl == null) {
             // If we don't yet have a surface, but we now have a parent, we should
             // build a surface.
-            mSurfaceControl = makeSurface().build();
+            setSurfaceControl(makeSurface().build());
             getPendingTransaction().show(mSurfaceControl);
             updateSurfacePosition();
         } else {
@@ -496,7 +499,7 @@
                 mParent.getPendingTransaction().merge(getPendingTransaction());
             }
 
-            mSurfaceControl = null;
+            setSurfaceControl(null);
             mLastSurfacePosition.set(0, 0);
             scheduleAnimation();
         }
@@ -559,6 +562,13 @@
         return false;
     }
 
+    /** @return true if this window container is a descendant of the input container. */
+    boolean isDescendantOf(WindowContainer ancestor) {
+        final WindowContainer parent = getParent();
+        if (parent == ancestor) return true;
+        return (parent != null) && parent.isDescendantOf(ancestor);
+    }
+
     /**
      * Move a child from it's current place in siblings list to the specified position,
      * with an option to move all its parents to top.
@@ -777,7 +787,7 @@
      *         otherwise.
      */
     boolean isWaitingForTransitionStart() {
-        return getActivity(app -> app.isWaitingForTransitionStart()) != null;
+        return false;
     }
 
     /**
@@ -850,6 +860,21 @@
         return false;
     }
 
+    @Override
+    boolean isFocusable() {
+        return super.isFocusable() && mIsFocusable;
+    }
+
+    /** Set whether this container or its children can be focusable */
+    @Override
+    boolean setFocusable(boolean focusable) {
+        if (mIsFocusable == focusable) {
+            return false;
+        }
+        mIsFocusable = focusable;
+        return true;
+    }
+
     /**
      * @return Whether this child is on top of the window hierarchy.
      */
@@ -1814,13 +1839,19 @@
      * @param anim The animation to run.
      * @param hidden Whether our container is currently hidden. TODO This should use isVisible at
      *               some point but the meaning is too weird to work for all containers.
+     * @param animationFinishedCallback The callback being triggered when the animation finishes.
      */
-    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden) {
+    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
+            @Nullable Runnable animationFinishedCallback) {
         if (DEBUG_ANIM) Slog.v(TAG, "Starting animation on " + this + ": " + anim);
 
         // TODO: This should use isVisible() but because isVisible has a really weird meaning at
         // the moment this doesn't work for all animatable window containers.
-        mSurfaceAnimator.startAnimation(t, anim, hidden);
+        mSurfaceAnimator.startAnimation(t, anim, hidden, animationFinishedCallback);
+    }
+
+    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden) {
+        startAnimation(t, anim, hidden, null /* animationFinishedCallback */);
     }
 
     void transferAnimation(WindowContainer from) {
@@ -1855,7 +1886,7 @@
     // TODO: Remove this and use #getBounds() instead once we set an app transition animation
     // on TaskStack.
     Rect getAnimationBounds(int appStackClipMode) {
-        return getBounds();
+        return getDisplayedBounds();
     }
 
     /**
@@ -1873,7 +1904,7 @@
      * @see #getAnimationAdapter
      */
     boolean applyAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
-                           boolean isVoiceInteraction) {
+            boolean isVoiceInteraction, @Nullable Runnable animationFinishedCallback) {
         if (mWmService.mDisableTransitionAnimation) {
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
                     "applyAnimation: transition animation is disabled or skipped. "
@@ -1893,7 +1924,8 @@
                 AnimationAdapter adapter = adapters.first;
                 AnimationAdapter thumbnailAdapter = adapters.second;
                 if (adapter != null) {
-                    startAnimation(getPendingTransaction(), adapter, !isVisible());
+                    startAnimation(getPendingTransaction(), adapter, !isVisible(),
+                            animationFinishedCallback);
                     if (adapter.getShowWallpaper()) {
                         getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                     }
@@ -1929,7 +1961,11 @@
 
         // Separate position and size for use in animators.
         mTmpRect.set(getAnimationBounds(appStackClipMode));
-        mTmpPoint.set(mTmpRect.left, mTmpRect.top);
+        if (sHierarchicalAnimations) {
+            getRelativeDisplayedPosition(mTmpPoint);
+        } else {
+            mTmpPoint.set(mTmpRect.left, mTmpRect.top);
+        }
         mTmpRect.offsetTo(0, 0);
 
         final RemoteAnimationController controller =
@@ -2209,4 +2245,18 @@
         }
         return mParent.getDimmer();
     }
+
+    void setSurfaceControl(SurfaceControl sc) {
+        mSurfaceControl = sc;
+    }
+
+    /** Cheap way of doing cast and instanceof. */
+    Task asTask() {
+        return null;
+    }
+
+    /** Cheap way of doing cast and instanceof. */
+    ActivityRecord asActivityRecord() {
+        return null;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
index 8e070f1..8948f6f 100644
--- a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
+++ b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
@@ -85,7 +85,8 @@
             // We can't use a delegating constructor since we need to
             // reference this::onAnimationFinished
             mSurfaceAnimator =
-                new SurfaceAnimator(this, this::onAnimationFinished, container.mWmService);
+                new SurfaceAnimator(this, null /* animationFinishedCallback */,
+                        container.mWmService);
         }
         mWidth = thumbnailHeader.getWidth();
         mHeight = thumbnailHeader.getHeight();
@@ -139,9 +140,6 @@
         mSurfaceAnimator.startAnimation(t, anim, hidden);
     }
 
-    private void onAnimationFinished() {
-    }
-
     void setShowing(Transaction pendingTransaction, boolean show) {
         // TODO: Not needed anymore once thumbnail is attached to the app.
         if (show) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 223e9b9..8d4ad28 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -63,6 +63,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
 import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION;
 import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
@@ -70,6 +71,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
+import static android.view.WindowManagerGlobal.ADD_OKAY;
 import static android.view.WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID;
@@ -205,6 +207,7 @@
 import android.view.Gravity;
 import android.view.IAppTransitionAnimationSpecsFuture;
 import android.view.IDisplayFoldListener;
+import android.view.IDisplayWindowInsetsController;
 import android.view.IDisplayWindowListener;
 import android.view.IDisplayWindowRotationController;
 import android.view.IDockedStackListener;
@@ -1282,6 +1285,7 @@
     static boolean excludeWindowTypeFromTapOutTask(int windowType) {
         switch (windowType) {
             case TYPE_STATUS_BAR:
+            case TYPE_NOTIFICATION_SHADE:
             case TYPE_NAVIGATION_BAR:
             case TYPE_INPUT_METHOD_DIALOG:
                 return true;
@@ -1364,52 +1368,10 @@
             boolean addToastWindowRequiresToken = false;
 
             if (token == null) {
-                if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
-                    ProtoLog.w(WM_ERROR, "Attempted to add application window with unknown token "
-                            + "%s.  Aborting.", attrs.token);
+                if (!unprivilegedAppCanCreateTokenWith(parentWindow, callingUid, type,
+                        rootType, attrs.token, attrs.packageName)) {
                     return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                 }
-                if (rootType == TYPE_INPUT_METHOD) {
-                    ProtoLog.w(WM_ERROR, "Attempted to add input method window with unknown token "
-                            + "%s.  Aborting.", attrs.token);
-                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
-                }
-                if (rootType == TYPE_VOICE_INTERACTION) {
-                    ProtoLog.w(WM_ERROR,
-                            "Attempted to add voice interaction window with unknown token "
-                                    + "%s.  Aborting.", attrs.token);
-                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
-                }
-                if (rootType == TYPE_WALLPAPER) {
-                    ProtoLog.w(WM_ERROR, "Attempted to add wallpaper window with unknown token "
-                            + "%s.  Aborting.", attrs.token);
-                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
-                }
-                if (rootType == TYPE_DREAM) {
-                    ProtoLog.w(WM_ERROR, "Attempted to add Dream window with unknown token "
-                            + "%s.  Aborting.", attrs.token);
-                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
-                }
-                if (rootType == TYPE_QS_DIALOG) {
-                    ProtoLog.w(WM_ERROR, "Attempted to add QS dialog window with unknown token "
-                            + "%s.  Aborting.", attrs.token);
-                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
-                }
-                if (rootType == TYPE_ACCESSIBILITY_OVERLAY) {
-                    ProtoLog.w(WM_ERROR,
-                            "Attempted to add Accessibility overlay window with unknown token "
-                                    + "%s.  Aborting.", attrs.token);
-                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
-                }
-                if (type == TYPE_TOAST) {
-                    // Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
-                    if (doesAddToastWindowRequireToken(attrs.packageName, callingUid,
-                            parentWindow)) {
-                        ProtoLog.w(WM_ERROR, "Attempted to add a toast window with unknown token "
-                                + "%s.  Aborting.", attrs.token);
-                        return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
-                    }
-                }
                 final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
                 final boolean isRoundedCornerOverlay =
                         (attrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0;
@@ -1694,6 +1656,56 @@
         return res;
     }
 
+    private boolean unprivilegedAppCanCreateTokenWith(WindowState parentWindow,
+            int callingUid, int type, int rootType, IBinder tokenForLog, String packageName) {
+        if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
+            ProtoLog.w(WM_ERROR, "Attempted to add application window with unknown token "
+                    + "%s.  Aborting.", tokenForLog);
+            return false;
+        }
+        if (rootType == TYPE_INPUT_METHOD) {
+            ProtoLog.w(WM_ERROR, "Attempted to add input method window with unknown token "
+                    + "%s.  Aborting.", tokenForLog);
+            return false;
+        }
+        if (rootType == TYPE_VOICE_INTERACTION) {
+            ProtoLog.w(WM_ERROR,
+                    "Attempted to add voice interaction window with unknown token "
+                            + "%s.  Aborting.", tokenForLog);
+            return false;
+        }
+        if (rootType == TYPE_WALLPAPER) {
+            ProtoLog.w(WM_ERROR, "Attempted to add wallpaper window with unknown token "
+                    + "%s.  Aborting.", tokenForLog);
+            return false;
+        }
+        if (rootType == TYPE_DREAM) {
+            ProtoLog.w(WM_ERROR, "Attempted to add Dream window with unknown token "
+                    + "%s.  Aborting.", tokenForLog);
+            return false;
+        }
+        if (rootType == TYPE_QS_DIALOG) {
+            ProtoLog.w(WM_ERROR, "Attempted to add QS dialog window with unknown token "
+                    + "%s.  Aborting.", tokenForLog);
+            return false;
+        }
+        if (rootType == TYPE_ACCESSIBILITY_OVERLAY) {
+            ProtoLog.w(WM_ERROR,
+                    "Attempted to add Accessibility overlay window with unknown token "
+                            + "%s.  Aborting.", tokenForLog);
+            return false;
+        }
+        if (type == TYPE_TOAST) {
+            // Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
+            if (doesAddToastWindowRequireToken(packageName, callingUid, parentWindow)) {
+                ProtoLog.w(WM_ERROR, "Attempted to add a toast window with unknown token "
+                        + "%s.  Aborting.", tokenForLog);
+                return false;
+            }
+        }
+        return true;
+    }
+
     /**
      * Get existing {@link DisplayContent} or create a new one if the display is registered in
      * DisplayManager.
@@ -2036,7 +2048,8 @@
             long frameNumber, Rect outFrame, Rect outContentInsets,
             Rect outVisibleInsets, Rect outStableInsets, Rect outBackdropFrame,
             DisplayCutout.ParcelableWrapper outCutout, MergedConfiguration mergedConfiguration,
-            SurfaceControl outSurfaceControl, InsetsState outInsetsState) {
+            SurfaceControl outSurfaceControl, InsetsState outInsetsState,
+            Point outSurfaceSize) {
         int result = 0;
         boolean configChanged;
         final int pid = Binder.getCallingPid();
@@ -2358,6 +2371,10 @@
                 displayContent.sendNewConfiguration();
                 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
             }
+            if (winAnimator.mSurfaceController != null) {
+                outSurfaceSize.set(winAnimator.mSurfaceController.getWidth(),
+                                         winAnimator.mSurfaceController.getHeight());
+            }
         }
 
         Binder.restoreCallingIdentity(origId);
@@ -2411,8 +2428,8 @@
         return focusMayChange;
     }
 
-    private int createSurfaceControl(SurfaceControl outSurfaceControl, int result, WindowState win,
-            WindowStateAnimator winAnimator) {
+    private int createSurfaceControl(SurfaceControl outSurfaceControl,
+            int result, WindowState win, WindowStateAnimator winAnimator) {
         if (!win.mHasSurface) {
             result |= RELAYOUT_RES_SURFACE_CHANGED;
         }
@@ -2493,16 +2510,36 @@
 
     @Override
     public void addWindowToken(IBinder binder, int type, int displayId) {
-        if (!checkCallingPermission(MANAGE_APP_TOKENS, "addWindowToken()")) {
-            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+        addWindowContextToken(binder, type, displayId, null);
+    }
+
+    @Override
+    public int addWindowContextToken(IBinder binder, int type, int displayId, String packageName) {
+        final boolean callerCanManageAppTokens =
+                checkCallingPermission(MANAGE_APP_TOKENS, "addWindowToken()");
+        if (!callerCanManageAppTokens) {
+            // TODO(window-context): refactor checkAddPermission to not take attrs.
+            LayoutParams attrs = new LayoutParams(type);
+            attrs.packageName = packageName;
+            final int res = mPolicy.checkAddPermission(attrs, new int[1]);
+            if (res != ADD_OKAY) {
+                return res;
+            }
         }
 
         synchronized (mGlobalLock) {
+            if (!callerCanManageAppTokens) {
+                if (!unprivilegedAppCanCreateTokenWith(null, Binder.getCallingUid(), type, type,
+                        null, packageName) || packageName == null) {
+                    throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+                }
+            }
+
             final DisplayContent dc = getDisplayContentOrCreate(displayId, null /* token */);
             if (dc == null) {
                 ProtoLog.w(WM_ERROR, "addWindowToken: Attempted to add token: %s"
                         + " for non-exiting displayId=%d", binder, displayId);
-                return;
+                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
             }
 
             WindowToken token = dc.getWindowToken(binder);
@@ -2510,15 +2547,28 @@
                 ProtoLog.w(WM_ERROR, "addWindowToken: Attempted to add binder token: %s"
                         + " for already created window token: %s"
                         + " displayId=%d", binder, token, displayId);
-                return;
+                return WindowManagerGlobal.ADD_DUPLICATE_ADD;
             }
+            // TODO(window-container): Clean up dead tokens
             if (type == TYPE_WALLPAPER) {
-                new WallpaperWindowToken(this, binder, true, dc,
-                        true /* ownerCanManageAppTokens */);
+                new WallpaperWindowToken(this, binder, true, dc, callerCanManageAppTokens);
             } else {
-                new WindowToken(this, binder, type, true, dc, true /* ownerCanManageAppTokens */);
+                new WindowToken(this, binder, type, true, dc, callerCanManageAppTokens);
             }
         }
+        return WindowManagerGlobal.ADD_OKAY;
+    }
+
+    @Override
+    public boolean isWindowToken(IBinder binder) {
+        synchronized (mGlobalLock) {
+            final WindowToken windowToken = mRoot.getWindowToken(binder);
+            if (windowToken == null) {
+                return false;
+            }
+            // We don't allow activity tokens in WindowContext. TODO(window-context): rename method
+            return windowToken.asActivityRecord() == null;
+        }
     }
 
     @Override
@@ -2691,8 +2741,7 @@
         displayContent.getDockedDividerController().checkMinimizeChanged(animate);
     }
 
-    boolean isValidPictureInPictureAspectRatio(int displayId, float aspectRatio) {
-        final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+    boolean isValidPictureInPictureAspectRatio(DisplayContent displayContent, float aspectRatio) {
         return displayContent.getPinnedStackController().isValidPictureInPictureAspectRatio(
                 aspectRatio);
     }
@@ -2764,10 +2813,11 @@
         synchronized (mGlobalLock) {
             final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
             if (displayContent != null && mRoot.getTopChild() != displayContent) {
-                mRoot.positionChildAt(WindowContainer.POSITION_TOP, displayContent,
+                displayContent.positionDisplayAt(WindowContainer.POSITION_TOP,
                         true /* includingParents */);
             }
         }
+        syncInputTransactions();
     }
 
     /**
@@ -3155,7 +3205,7 @@
             // Notify whether the docked stack exists for the current user
             final DisplayContent displayContent = getDefaultDisplayContentLocked();
             final ActivityStack stack =
-                    displayContent.getSplitScreenPrimaryStackIgnoringVisibility();
+                    displayContent.getRootSplitScreenPrimaryTask();
             displayContent.mDividerControllerLocked.notifyDockedStackExistsChanged(
                     stack != null && stack.hasTaskForUser(newUserId));
 
@@ -3727,6 +3777,48 @@
     }
 
     @Override
+    public void setDisplayWindowInsetsController(
+            int displayId, IDisplayWindowInsetsController insetsController) {
+        if (mContext.checkCallingOrSelfPermission(MANAGE_APP_TOKENS)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Must hold permission " + MANAGE_APP_TOKENS);
+        }
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                final DisplayContent dc = mRoot.getDisplayContent(displayId);
+                if (dc == null) {
+                    return;
+                }
+                dc.setRemoteInsetsController(insetsController);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    @Override
+    public void modifyDisplayWindowInsets(int displayId, InsetsState state) {
+        if (mContext.checkCallingOrSelfPermission(MANAGE_APP_TOKENS)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Must hold permission " + MANAGE_APP_TOKENS);
+        }
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                final DisplayContent dc = mRoot.getDisplayContent(displayId);
+                if (dc == null || dc.mRemoteInsetsControlTarget == null) {
+                    return;
+                }
+                dc.getInsetsStateController().onInsetsModified(
+                        dc.mRemoteInsetsControlTarget, state);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    @Override
     public int watchRotation(IRotationWatcher watcher, int displayId) {
         final DisplayContent displayContent;
         synchronized (mGlobalLock) {
@@ -4558,13 +4650,13 @@
 
                     if (newFocus != null) {
                         ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Gaining focus: %s", newFocus);
-                        newFocus.reportFocusChangedSerialized(true, mInTouchMode);
+                        newFocus.reportFocusChangedSerialized(true);
                         notifyFocusChanged();
                     }
 
                     if (lastFocus != null) {
                         ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Losing focus: %s", lastFocus);
-                        lastFocus.reportFocusChangedSerialized(false, mInTouchMode);
+                        lastFocus.reportFocusChangedSerialized(false);
                     }
                     break;
                 }
@@ -4582,7 +4674,7 @@
                     for (int i = 0; i < N; i++) {
                         ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Losing delayed focus: %s",
                                 losers.get(i));
-                        losers.get(i).reportFocusChangedSerialized(false, mInTouchMode);
+                        losers.get(i).reportFocusChangedSerialized(false);
                     }
                     break;
                 }
@@ -5948,12 +6040,10 @@
                     pw.print(" apps="); pw.print(mAppsFreezingScreen);
             final DisplayContent defaultDisplayContent = getDefaultDisplayContentLocked();
             pw.print("  mRotation="); pw.print(defaultDisplayContent.getRotation());
-            pw.print("  mLastWindowForcedOrientation=");
-                    pw.print(defaultDisplayContent.getLastWindowForcedOrientation());
-                    pw.print(" mLastOrientation=");
-                            pw.println(defaultDisplayContent.getLastOrientation());
-                    pw.print(" waitingForConfig=");
-                            pw.println(defaultDisplayContent.mWaitingForConfig);
+            pw.print("  mLastOrientation=");
+                    pw.println(defaultDisplayContent.getLastOrientation());
+            pw.print(" waitingForConfig=");
+                    pw.println(defaultDisplayContent.mWaitingForConfig);
 
             pw.print("  Animation settings: disabled="); pw.print(mAnimationsDisabled);
                     pw.print(" window="); pw.print(mWindowAnimationScaleSetting);
@@ -6366,7 +6456,7 @@
     public int getDockedStackSide() {
         synchronized (mGlobalLock) {
             final ActivityStack dockedStack = getDefaultDisplayContentLocked()
-                    .getSplitScreenPrimaryStackIgnoringVisibility();
+                    .getRootSplitScreenPrimaryTask();
             return dockedStack == null ? DOCKED_INVALID : dockedStack.getDockSide();
         }
     }
@@ -7314,7 +7404,8 @@
                     // If there was a pending IME show(), reset it as IME has been
                     // requested to be hidden.
                     dc.getInsetsStateController().getImeSourceProvider().abortShowImePostLayout();
-                    dc.mInputMethodTarget.hideInsets(WindowInsets.Type.ime(), true /* fromIme */);
+                    dc.mInputMethodControlTarget.hideInsets(WindowInsets.Type.ime(),
+                            true /* fromIme */);
                 }
             }
         }
@@ -7647,7 +7738,7 @@
 
         final DisplayContent displayContent = touchedWindow.getDisplayContent();
         if (!displayContent.isOnTop()) {
-            displayContent.getParent().positionChildAt(WindowContainer.POSITION_TOP, displayContent,
+            displayContent.positionDisplayAt(WindowContainer.POSITION_TOP,
                     true /* includingParents */);
         }
         handleTaskFocusChange(touchedWindow.getTask());
@@ -7836,4 +7927,33 @@
         return mDisplayWindowSettings.shouldShowImeLocked(displayContent)
                 || mForceDesktopModeOnExternalDisplays;
     }
+
+    @Override
+    public void getWindowInsets(WindowManager.LayoutParams attrs,
+            int displayId, Rect outContentInsets, Rect outStableInsets,
+            DisplayCutout.ParcelableWrapper displayCutout) {
+        synchronized (mGlobalLock) {
+            final DisplayContent dc = mRoot.getDisplayContent(displayId);
+            final WindowToken windowToken = dc.getWindowToken(attrs.token);
+            final ActivityRecord activity;
+            if (windowToken != null && windowToken.asActivityRecord() != null) {
+                activity = windowToken.asActivityRecord();
+            } else {
+                activity = null;
+            }
+            final Rect taskBounds = new Rect();
+            final boolean floatingStack;
+            if (activity != null && activity.getTask() != null) {
+                final Task task = activity.getTask();
+                task.getBounds(taskBounds);
+                floatingStack = task.isFloating();
+            } else {
+                floatingStack = false;
+            }
+            final DisplayFrames displayFrames = dc.mDisplayFrames;
+            final DisplayPolicy policy = dc.getDisplayPolicy();
+            policy.getLayoutHintLw(attrs, taskBounds, displayFrames, floatingStack,
+                    new Rect(), outContentInsets, outStableInsets, displayCutout);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 9a40b1b1..87b04b2 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.os.Build.VERSION_CODES.Q;
 import static android.view.Display.INVALID_DISPLAY;
 
@@ -178,8 +179,12 @@
 
     // Last configuration that was reported to the process.
     private final Configuration mLastReportedConfiguration;
+    // Configuration that is waiting to be dispatched to the process.
+    private Configuration mPendingConfiguration;
+    private final Configuration mNewOverrideConfig = new Configuration();
     // Registered display id as a listener to override config change
     private int mDisplayId;
+    private ActivityRecord mConfigActivityRecord;
 
     /** Whether our process is currently running a {@link RecentsAnimation} */
     private boolean mRunningRecentsAnimation;
@@ -327,6 +332,12 @@
         return mDisplayId != INVALID_DISPLAY;
     }
 
+    /** @return {@code true} if the process registered to an activity as a config listener. */
+    @VisibleForTesting
+    boolean registeredForActivityConfigChanges() {
+        return mConfigActivityRecord != null;
+    }
+
     void postPendingUiCleanMsg(boolean pendingUiClean) {
         if (mListener == null) return;
         // Posting on handler so WM lock isn't held when we call into AM.
@@ -483,7 +494,10 @@
 
     @Override
     protected ConfigurationContainer getParent() {
-        return null;
+        // Returning RootWindowContainer as the parent, so that this process controller always
+        // has full configuration and overrides (e.g. from display) are always added on top of
+        // global config.
+        return mAtm.mRootWindowContainer;
     }
 
     @HotPath(caller = HotPath.PROCESS_CHANGE)
@@ -507,10 +521,12 @@
             return;
         }
         mActivities.add(r);
+        updateActivityConfigurationListener();
     }
 
     void removeActivity(ActivityRecord r) {
         mActivities.remove(r);
+        updateActivityConfigurationListener();
     }
 
     void makeFinishingForProcessRemoved() {
@@ -521,6 +537,7 @@
 
     void clearActivities() {
         mActivities.clear();
+        updateActivityConfigurationListener();
     }
 
     @HotPath(caller = HotPath.OOM_ADJUSTMENT)
@@ -603,15 +620,15 @@
 
         // Compare the z-order of ActivityStacks if both activities landed on same display.
         if (display == topDisplay
-                && mPreQTopResumedActivity.getActivityStack().compareTo(
-                        activity.getActivityStack()) <= 0) {
+                && mPreQTopResumedActivity.getRootTask().compareTo(
+                        activity.getRootTask()) <= 0) {
             canUpdate = true;
         }
 
         if (canUpdate) {
             // Make sure the previous top activity in the process no longer be resumed.
             if (mPreQTopResumedActivity != null && mPreQTopResumedActivity.isState(RESUMED)) {
-                final ActivityStack stack = mPreQTopResumedActivity.getActivityStack();
+                final ActivityStack stack = mPreQTopResumedActivity.getRootTask();
                 if (stack != null) {
                     stack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */,
                             activity);
@@ -956,27 +973,28 @@
         mAtm.mH.sendMessageAtFrontOfQueue(m);
     }
 
-    public void appDied() {
+    void appDied(String reason) {
         if (mListener == null) return;
         // Posting on handler so WM lock isn't held when we call into AM.
         final Message m = PooledLambda.obtainMessage(
-                WindowProcessListener::appDied, mListener);
+                WindowProcessListener::appDied, mListener, reason);
         mAtm.mH.sendMessage(m);
     }
 
-    void registerDisplayConfigurationListenerLocked(DisplayContent displayContent) {
+    void registerDisplayConfigurationListener(DisplayContent displayContent) {
         if (displayContent == null) {
             return;
         }
-        // A process can only register to one display to listener to the override configuration
+        // A process can only register to one display to listen to the override configuration
         // change. Unregister existing listener if it has one before register the new one.
-        unregisterDisplayConfigurationListenerLocked();
+        unregisterDisplayConfigurationListener();
+        unregisterActivityConfigurationListener();
         mDisplayId = displayContent.mDisplayId;
         displayContent.registerConfigurationChangeListener(this);
     }
 
     @VisibleForTesting
-    void unregisterDisplayConfigurationListenerLocked() {
+    void unregisterDisplayConfigurationListener() {
         if (mDisplayId == INVALID_DISPLAY) {
             return;
         }
@@ -986,6 +1004,48 @@
             displayContent.unregisterConfigurationChangeListener(this);
         }
         mDisplayId = INVALID_DISPLAY;
+        onMergedOverrideConfigurationChanged(Configuration.EMPTY);
+    }
+
+    private void registerActivityConfigurationListener(ActivityRecord activityRecord) {
+        if (activityRecord == null) {
+            return;
+        }
+        // A process can only register to one activityRecord to listen to the override configuration
+        // change. Unregister existing listener if it has one before register the new one.
+        unregisterDisplayConfigurationListener();
+        unregisterActivityConfigurationListener();
+        mConfigActivityRecord = activityRecord;
+        activityRecord.registerConfigurationChangeListener(this);
+    }
+
+    private void unregisterActivityConfigurationListener() {
+        if (mConfigActivityRecord == null) {
+            return;
+        }
+        mConfigActivityRecord.unregisterConfigurationChangeListener(this);
+        mConfigActivityRecord = null;
+        onMergedOverrideConfigurationChanged(Configuration.EMPTY);
+    }
+
+    /**
+     * Check if activity configuration override for the activity process needs an update and perform
+     * if needed. By default we try to override the process configuration to match the top activity
+     * config to increase app compatibility with multi-window and multi-display. The process will
+     * always track the configuration of the non-finishing activity last added to the process.
+     */
+    private void updateActivityConfigurationListener() {
+        for (int i = mActivities.size() - 1; i >= 0; i--) {
+            final ActivityRecord activityRecord = mActivities.get(i);
+            if (!activityRecord.finishing && !activityRecord.containsListener(this)) {
+                // Eligible activity is found, update listener.
+                registerActivityConfigurationListener(activityRecord);
+                return;
+            }
+        }
+
+        // No eligible activities found, let's remove the configuration listener.
+        unregisterActivityConfigurationListener();
     }
 
     @Override
@@ -995,8 +1055,11 @@
     }
 
     @Override
-    public void onRequestedOverrideConfigurationChanged(Configuration newOverrideConfig) {
-        super.onRequestedOverrideConfigurationChanged(newOverrideConfig);
+    public void onMergedOverrideConfigurationChanged(Configuration mergedOverrideConfig) {
+        // Make sure that we don't accidentally override the activity type.
+        mNewOverrideConfig.setTo(mergedOverrideConfig);
+        mNewOverrideConfig.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
+        super.onRequestedOverrideConfigurationChanged(mNewOverrideConfig);
         updateConfiguration();
     }
 
@@ -1012,24 +1075,38 @@
             return;
         }
 
-        try {
-            if (mThread == null) {
-                if (Build.IS_DEBUGGABLE && mIsImeProcess) {
-                    // TODO (b/135719017): Temporary log for debugging IME service.
-                    Slog.w(TAG_CONFIGURATION, "Unable to send config for IME proc " + mName
-                            + ": no app thread");
-                }
-                return;
+        if (mListener.isCached()) {
+            // This process is in a cached state. We will delay delivering the config change to the
+            // process until the process is no longer cached.
+            if (mPendingConfiguration == null) {
+                mPendingConfiguration = new Configuration(config);
+            } else {
+                mPendingConfiguration.setTo(config);
             }
-            if (DEBUG_CONFIGURATION) {
-                Slog.v(TAG_CONFIGURATION, "Sending to proc " + mName
-                        + " new config " + config);
-            }
+            return;
+        }
+
+        dispatchConfigurationChange(config);
+    }
+
+    private void dispatchConfigurationChange(Configuration config) {
+        if (mThread == null) {
             if (Build.IS_DEBUGGABLE && mIsImeProcess) {
                 // TODO (b/135719017): Temporary log for debugging IME service.
-                Slog.v(TAG_CONFIGURATION, "Sending to IME proc " + mName
-                        + " new config " + config);
+                Slog.w(TAG_CONFIGURATION, "Unable to send config for IME proc " + mName
+                        + ": no app thread");
             }
+            return;
+        }
+        if (DEBUG_CONFIGURATION) {
+            Slog.v(TAG_CONFIGURATION, "Sending to proc " + mName + " new config " + config);
+        }
+        if (Build.IS_DEBUGGABLE && mIsImeProcess) {
+            // TODO (b/135719017): Temporary log for debugging IME service.
+            Slog.v(TAG_CONFIGURATION, "Sending to IME proc " + mName + " new config " + config);
+        }
+
+        try {
             config.seq = mAtm.increaseConfigurationSeqLocked();
             mAtm.getLifecycleManager().scheduleTransaction(mThread,
                     ConfigurationChangeItem.obtain(config));
@@ -1075,6 +1152,7 @@
     }
 
     public void appEarlyNotResponding(String annotation, Runnable killAppCallback) {
+        Runnable targetRunnable = null;
         synchronized (mAtm.mGlobalLock) {
             if (mAtm.mController == null) {
                 return;
@@ -1084,13 +1162,16 @@
                 // 0 == continue, -1 = kill process immediately
                 int res = mAtm.mController.appEarlyNotResponding(mName, mPid, annotation);
                 if (res < 0 && mPid != MY_PID) {
-                    killAppCallback.run();
+                    targetRunnable = killAppCallback;
                 }
             } catch (RemoteException e) {
                 mAtm.mController = null;
                 Watchdog.getInstance().setActivityController(null);
             }
         }
+        if (targetRunnable != null) {
+            targetRunnable.run();
+        }
     }
 
     public boolean appNotResponding(String info, Runnable killAppCallback,
@@ -1118,12 +1199,28 @@
             }
         }
         if (targetRunnable != null) {
+            // Execute runnable outside WM lock since the runnable will hold AM lock
             targetRunnable.run();
             return true;
         }
         return false;
     }
 
+    /**
+     * Called to notify WindowProcessController of a change in the process's cached state.
+     *
+     * @param isCached whether or not the process is cached.
+     */
+    public void onProcCachedStateChanged(boolean isCached) {
+        synchronized (mAtm.mGlobalLock) {
+            if (!isCached && mPendingConfiguration != null) {
+                final Configuration config = mPendingConfiguration;
+                mPendingConfiguration = null;
+                dispatchConfigurationChange(config);
+            }
+        }
+    }
+
     @HotPath(caller = HotPath.OOM_ADJUSTMENT)
     public void onTopProcChanged() {
         synchronized (mAtm.mGlobalLockWithoutBoost) {
diff --git a/services/core/java/com/android/server/wm/WindowProcessListener.java b/services/core/java/com/android/server/wm/WindowProcessListener.java
index 870cbb0..88c215b 100644
--- a/services/core/java/com/android/server/wm/WindowProcessListener.java
+++ b/services/core/java/com/android/server/wm/WindowProcessListener.java
@@ -51,6 +51,11 @@
      */
     boolean isRemoved();
 
+    /**
+     * Returns true if the process is in a cached state.
+     */
+    boolean isCached();
+
     /** Returns the total time (in milliseconds) spent executing in both user and system code. */
     long getCpuTime();
 
@@ -59,7 +64,7 @@
             long versionCode);
 
     /** App died :(...oh well */
-    void appDied();
+    void appDied(String reason);
     void dumpDebug(ProtoOutputStream proto, long fieldId);
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 96bc8e9..73984fd 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -16,7 +16,7 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityTaskManager.INVALID_STACK_ID;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_DEFAULT;
 import static android.app.AppOpsManager.OP_NONE;
@@ -73,6 +73,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
 import static android.view.WindowManager.LayoutParams.TYPE_PHONE;
 import static android.view.WindowManager.LayoutParams.TYPE_POINTER;
 import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
@@ -657,6 +658,13 @@
     private KeyInterceptionInfo mKeyInterceptionInfo;
 
     /**
+     * This information is passed to SurfaceFlinger to decide which window should have a priority
+     * when deciding about the refresh rate of the display. All windows have the lowest priority by
+     * default. The variable is cached, so we do not send too many updates to SF.
+     */
+    int mFrameRateSelectionPriority = RefreshRatePolicy.LAYER_PRIORITY_UNSET;
+
+    /**
      * @return The insets state as requested by the client, i.e. the dispatched insets state
      *         for which the visibilities are overridden with what the client requested.
      */
@@ -688,6 +696,9 @@
         }
 
         if (mForceSeamlesslyRotate || requested) {
+            if (mControllableInsetProvider != null) {
+                mControllableInsetProvider.startSeamlessRotation();
+            }
             mPendingSeamlessRotate = new SeamlessRotator(oldRotation, rotation, getDisplayInfo());
             mPendingSeamlessRotate.unrotate(transaction, this);
             getDisplayContent().getDisplayRotation().markForSeamlessRotation(this,
@@ -702,6 +713,9 @@
             mPendingSeamlessRotate = null;
             getDisplayContent().getDisplayRotation().markForSeamlessRotation(this,
                     false /* seamlesslyRotated */);
+            if (mControllableInsetProvider != null) {
+                mControllableInsetProvider.finishSeamlessRotation(timeout);
+            }
         }
     }
 
@@ -1041,7 +1055,7 @@
                 }
             }
 
-            final ActivityStack stack = getStack();
+            final ActivityStack stack = getRootTask();
             if (inPinnedWindowingMode() && stack != null
                     && stack.lastAnimatingBoundsWasToFullscreen()) {
                 // PIP edge case: When going from pinned to fullscreen, we apply a
@@ -1455,17 +1469,15 @@
         return mActivityRecord != null ? mActivityRecord.getTask() : null;
     }
 
-    ActivityStack getStack() {
-        Task task = getTask();
+    ActivityStack getRootTask() {
+        final Task task = getTask();
         if (task != null) {
-            if (task.getStack() != null) {
-                return task.getStack();
-            }
+            return (ActivityStack) task.getRootTask();
         }
         // Some system windows (e.g. "Power off" dialog) don't have a task, but we would still
         // associate them with some stack to enable dimming.
         final DisplayContent dc = getDisplayContent();
-        return mAttrs.type >= FIRST_SYSTEM_WINDOW && dc != null ? dc.getHomeStack() : null;
+        return mAttrs.type >= FIRST_SYSTEM_WINDOW && dc != null ? dc.getRootHomeTask() : null;
     }
 
     /**
@@ -2215,7 +2227,7 @@
             return false;
         }
 
-        final ActivityStack stack = getStack();
+        final ActivityStack stack = getRootTask();
         if (stack != null && stack.shouldIgnoreInput()) {
             // Ignore when the stack shouldn't receive input event.
             // (i.e. the minimized stack in split screen mode.)
@@ -2458,7 +2470,7 @@
             if (task != null) {
                 task.getDimBounds(mTmpRect);
             } else {
-                getStack().getDimBounds(mTmpRect);
+                getRootTask().getDimBounds(mTmpRect);
             }
         }
         if (inFreeformWindowingMode()) {
@@ -2639,7 +2651,7 @@
                             // also reset drag resizing state, because the owner can't do it
                             // anymore.
                             final ActivityStack stack =
-                                    dc.getSplitScreenPrimaryStackIgnoringVisibility();
+                                    dc.getRootSplitScreenPrimaryTask();
                             if (stack != null) {
                                 stack.resetDockedStackToMiddle();
                             }
@@ -3197,6 +3209,7 @@
             case TYPE_PRIORITY_PHONE:
             case TYPE_SEARCH_BAR:
             case TYPE_STATUS_BAR:
+            case TYPE_NOTIFICATION_SHADE:
             case TYPE_STATUS_BAR_PANEL:
             case TYPE_STATUS_BAR_SUB_PANEL:
             case TYPE_SYSTEM_DIALOG:
@@ -3333,11 +3346,7 @@
      * Report a focus change.  Must be called with no locks held, and consistently
      * from the same serialized thread (such as dispatched from a handler).
      */
-    void reportFocusChangedSerialized(boolean focused, boolean inTouchMode) {
-        try {
-            mClient.windowFocusChanged(focused, inTouchMode);
-        } catch (RemoteException e) {
-        }
+    void reportFocusChangedSerialized(boolean focused) {
         if (mFocusCallbacks != null) {
             final int N = mFocusCallbacks.beginBroadcast();
             for (int i=0; i<N; i++) {
@@ -3393,7 +3402,7 @@
                     "Reporting new frame to %s: %s", this,
                             mWindowFrames.mCompatFrame);
             final MergedConfiguration mergedConfiguration =
-                    new MergedConfiguration(mWmService.mRoot.getConfiguration(),
+                    new MergedConfiguration(getProcessGlobalConfiguration(),
                     getMergedOverrideConfiguration());
 
             setLastReportedMergedConfiguration(mergedConfiguration);
@@ -3552,12 +3561,12 @@
         return mTmpRect;
     }
 
-    private int getStackId() {
-        final ActivityStack stack = getStack();
+    private int getRootTaskId() {
+        final ActivityStack stack = getRootTask();
         if (stack == null) {
-            return INVALID_STACK_ID;
+            return INVALID_TASK_ID;
         }
-        return stack.mStackId;
+        return stack.mTaskId;
     }
 
     private void dispatchResized(Rect frame, Rect contentInsets,
@@ -3742,7 +3751,7 @@
         super.dumpDebug(proto, WINDOW_CONTAINER, logLevel);
         writeIdentifierToProto(proto, IDENTIFIER);
         proto.write(DISPLAY_ID, getDisplayId());
-        proto.write(STACK_ID, getStackId());
+        proto.write(STACK_ID, getRootTaskId());
         mAttrs.dumpDebug(proto, ATTRIBUTES);
         mGivenContentInsets.dumpDebug(proto, GIVEN_CONTENT_INSETS);
         mWindowFrames.dumpDebug(proto, WINDOW_FRAMES);
@@ -3784,10 +3793,9 @@
 
     @Override
     void dump(PrintWriter pw, String prefix, boolean dumpAll) {
-        final ActivityStack stack = getStack();
         pw.print(prefix + "mDisplayId=" + getDisplayId());
-        if (stack != null) {
-            pw.print(" stackId=" + stack.mStackId);
+        if (getRootTask() != null) {
+            pw.print(" rootTaskId=" + getRootTaskId());
         }
         pw.println(" mSession=" + mSession
                 + " mClient=" + mClient.asBinder());
@@ -5002,7 +5010,7 @@
     }
 
     private void startAnimation(Transaction t, AnimationAdapter adapter) {
-        startAnimation(t, adapter, mWinAnimator.mLastHidden);
+        startAnimation(t, adapter, mWinAnimator.mLastHidden, null /* animationFinishedCallback */);
     }
 
     @Override
@@ -5163,6 +5171,24 @@
         }
     }
 
+
+    /**
+     * Notifies SF about the priority of the window, if it changed. SF then uses this information
+     * to decide which window's desired rendering rate should have a priority when deciding about
+     * the refresh rate of the screen. Priority
+     * {@link RefreshRatePolicy#LAYER_PRIORITY_FOCUSED_WITH_MODE} is considered the highest.
+     */
+    @VisibleForTesting
+    void updateFrameRateSelectionPriorityIfNeeded() {
+        final int priority = getDisplayContent().getDisplayPolicy().getRefreshRatePolicy()
+                .calculatePriority(this);
+        if (mFrameRateSelectionPriority != priority) {
+            mFrameRateSelectionPriority = priority;
+            getPendingTransaction().setFrameRateSelectionPriority(mSurfaceControl,
+                    mFrameRateSelectionPriority);
+        }
+    }
+
     @Override
     void prepareSurfaces() {
         final Dimmer dimmer = getDimmer();
@@ -5171,6 +5197,8 @@
             applyDims(dimmer);
         }
         updateSurfacePosition();
+        // Send information to SufaceFlinger about the priority of the current window.
+        updateFrameRateSelectionPriorityIfNeeded();
 
         mWinAnimator.prepareSurfaceLocked(true);
         super.prepareSurfaces();
@@ -5240,7 +5268,7 @@
             outPoint.offset(-parentBounds.left, -parentBounds.top);
         }
 
-        ActivityStack stack = getStack();
+        ActivityStack stack = getRootTask();
 
         // If we have stack outsets, that means the top-left
         // will be outset, and we need to inset ourselves
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 486616d..069ee4f 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -272,7 +272,8 @@
 
         mWin.checkPolicyVisibilityChange();
         final DisplayContent displayContent = mWin.getDisplayContent();
-        if (mAttrType == LayoutParams.TYPE_STATUS_BAR && mWin.isVisibleByPolicy()) {
+        if ((mAttrType == LayoutParams.TYPE_STATUS_BAR
+                || mAttrType == LayoutParams.TYPE_NOTIFICATION_SHADE) && mWin.isVisibleByPolicy()) {
             // Upon completion of a not-visible to visible status bar animation a relayout is
             // required.
             displayContent.setLayoutNeeded();
@@ -1012,7 +1013,7 @@
                         mSurfaceController.deferTransactionUntil(mSurfaceController.mSurfaceControl,
                                 mWin.getFrameNumber());
                     } else {
-                        final ActivityStack stack = mWin.getStack();
+                        final ActivityStack stack = mWin.getRootTask();
                         mTmpPos.x = 0;
                         mTmpPos.y = 0;
                         if (stack != null) {
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 2a1e980..43cd66d 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
@@ -38,7 +39,9 @@
 import android.os.Debug;
 import android.os.IBinder;
 import android.util.proto.ProtoOutputStream;
+import android.view.SurfaceControl;
 
+import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.protolog.common.ProtoLog;
 
 import java.io.PrintWriter;
@@ -228,12 +231,6 @@
         return false;
     }
 
-    ActivityRecord asActivityRecord() {
-        // TODO: Not sure if this is the best way to handle this vs. using instanceof and casting.
-        // I am not an app window token!
-        return null;
-    }
-
     @Override
     void removeImmediately() {
         if (mDisplayContent != null) {
@@ -256,6 +253,27 @@
         super.onDisplayChanged(dc);
     }
 
+    @Override
+    void assignLayer(SurfaceControl.Transaction t, int layer) {
+        if (windowType == TYPE_DOCK_DIVIDER) {
+            // See {@link DisplayContent#mSplitScreenDividerAnchor}
+            super.assignRelativeLayer(t, mDisplayContent.getSplitScreenDividerAnchor(), 1);
+        } else if (mRoundedCornerOverlay) {
+            super.assignLayer(t, WindowManagerPolicy.COLOR_FADE_LAYER + 1);
+        } else {
+            super.assignLayer(t, layer);
+        }
+    }
+
+    @Override
+    SurfaceControl.Builder makeSurface() {
+        final SurfaceControl.Builder builder = super.makeSurface();
+        if (mRoundedCornerOverlay) {
+            builder.setParent(null);
+        }
+        return builder;
+    }
+
     @CallSuper
     @Override
     public void dumpDebug(ProtoOutputStream proto, long fieldId,
@@ -315,4 +333,8 @@
                 mOwnerCanManageAppTokens);
         return mOwnerCanManageAppTokens && (layer > navLayer);
     }
+
+    int getWindowLayerFromType() {
+        return mWmService.mPolicy.getWindowLayerFromTypeLw(windowType, mOwnerCanManageAppTokens);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java b/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java
index 94f6676..0c09c88 100644
--- a/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java
+++ b/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java
@@ -71,6 +71,10 @@
         Rect crop = new Rect(0, 0, size.x, size.y);
         SurfaceControl.ScreenshotGraphicBuffer buffer =
                 SurfaceControl.captureLayers(surfaceControl, crop, 1);
+        if (buffer == null) {
+            return 0;
+        }
+
         return RotationAnimationUtils.getAvgBorderLuma(buffer.getGraphicBuffer(),
                 buffer.getColorSpace());
     }
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 1ad6e86..77d814e 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -13,6 +13,7 @@
     ],
 
     srcs: [
+        ":graphicsstats_proto",
         "BroadcastRadio/JavaRef.cpp",
         "BroadcastRadio/NativeCallbackThread.cpp",
         "BroadcastRadio/BroadcastRadioService.cpp",
@@ -50,7 +51,7 @@
         "com_android_server_VibratorService.cpp",
         "com_android_server_PersistentDataBlockService.cpp",
         "com_android_server_GraphicsStatsService.cpp",
-        "com_android_server_am_AppCompactor.cpp",
+        "com_android_server_am_CachedAppOptimizer.cpp",
         "com_android_server_am_LowMemDetector.cpp",
         "com_android_server_incremental_IncrementalManagerService.cpp",
         "onload.cpp",
@@ -103,6 +104,11 @@
         "libinputflinger",
         "libinputflinger_base",
         "libinputservice",
+        "libprotobuf-cpp-lite",
+        "libprotoutil",
+        "libstatspull",
+        "libstatssocket",
+        "libstatslog",
         "libschedulerservicehidl",
         "libsensorservice",
         "libsensorservicehidl",
diff --git a/services/core/jni/com_android_server_GraphicsStatsService.cpp b/services/core/jni/com_android_server_GraphicsStatsService.cpp
index d1d253b..7644ade 100644
--- a/services/core/jni/com_android_server_GraphicsStatsService.cpp
+++ b/services/core/jni/com_android_server_GraphicsStatsService.cpp
@@ -23,6 +23,16 @@
 #include <nativehelper/ScopedUtfChars.h>
 #include <JankTracker.h>
 #include <service/GraphicsStatsService.h>
+#include <stats_pull_atom_callback.h>
+#include <stats_event.h>
+#include <statslog.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+#include <android/util/ProtoOutputStream.h>
+#include "android/graphics/Utils.h"
+#include "core_jni_helpers.h"
+#include "protos/graphicsstats.pb.h"
+#include <cstring>
+#include <memory>
 
 namespace android {
 
@@ -77,6 +87,20 @@
     GraphicsStatsService::finishDump(dump);
 }
 
+static jlong finishDumpInMemory(JNIEnv* env, jobject, jlong dumpPtr) {
+    GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr);
+    std::vector<uint8_t>* result = new std::vector<uint8_t>();
+    GraphicsStatsService::finishDumpInMemory(dump,
+        [](void* buffer, int bufferOffset, int bufferSize, int totalSize, void* param1, void* param2) {
+            std::vector<uint8_t>* outBuffer = reinterpret_cast<std::vector<uint8_t>*>(param2);
+            if (outBuffer->size() < totalSize) {
+                outBuffer->resize(totalSize);
+            }
+            std::memcpy(outBuffer->data() + bufferOffset, buffer, bufferSize);
+        }, env, result);
+    return reinterpret_cast<jlong>(result);
+}
+
 static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpackage,
         jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) {
     ScopedByteArrayRO buffer(env, jdata);
@@ -93,19 +117,174 @@
     GraphicsStatsService::saveBuffer(path, package, versionCode, startTime, endTime, data);
 }
 
+static jobject gGraphicsStatsServiceObject = nullptr;
+static jmethodID gGraphicsStatsService_pullGraphicsStatsMethodID;
+
+static JNIEnv* getJNIEnv() {
+    JavaVM* vm = AndroidRuntime::getJavaVM();
+    JNIEnv* env = nullptr;
+    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+        if (vm->AttachCurrentThreadAsDaemon(&env, nullptr) != JNI_OK) {
+            LOG_ALWAYS_FATAL("Failed to AttachCurrentThread!");
+        }
+    }
+    return env;
+}
+
+using namespace google::protobuf;
+
+// Field ids taken from FrameTimingHistogram message in atoms.proto
+#define TIME_MILLIS_BUCKETS_FIELD_NUMBER 1
+#define FRAME_COUNTS_FIELD_NUMBER 2
+
+static void writeCpuHistogram(stats_event* event,
+                              const uirenderer::protos::GraphicsStatsProto& stat) {
+    util::ProtoOutputStream proto;
+    for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) {
+        auto& bucket = stat.histogram(bucketIndex);
+        proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED |
+                            TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */,
+                    (int)bucket.render_millis());
+    }
+    for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) {
+        auto& bucket = stat.histogram(bucketIndex);
+        proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED |
+                            FRAME_COUNTS_FIELD_NUMBER /* field id */,
+                    (long long)bucket.frame_count());
+    }
+    std::vector<uint8_t> outVector;
+    proto.serializeToVector(&outVector);
+    stats_event_write_byte_array(event, outVector.data(), outVector.size());
+}
+
+static void writeGpuHistogram(stats_event* event,
+                              const uirenderer::protos::GraphicsStatsProto& stat) {
+    util::ProtoOutputStream proto;
+    for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) {
+        auto& bucket = stat.gpu_histogram(bucketIndex);
+        proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED |
+                            TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */,
+                    (int)bucket.render_millis());
+    }
+    for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) {
+        auto& bucket = stat.gpu_histogram(bucketIndex);
+        proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED |
+                            FRAME_COUNTS_FIELD_NUMBER /* field id */,
+                    (long long)bucket.frame_count());
+    }
+    std::vector<uint8_t> outVector;
+    proto.serializeToVector(&outVector);
+    stats_event_write_byte_array(event, outVector.data(), outVector.size());
+}
+
+// graphicsStatsPullCallback is invoked by statsd service to pull GRAPHICS_STATS atom.
+static status_pull_atom_return_t graphicsStatsPullCallback(int32_t atom_tag,
+                                                           pulled_stats_event_list* data,
+                                                           void* cookie) {
+    JNIEnv* env = getJNIEnv();
+    if (!env) {
+        return false;
+    }
+    if (gGraphicsStatsServiceObject == nullptr) {
+        ALOGE("Failed to get graphicsstats service");
+        return STATS_PULL_SKIP;
+    }
+
+    for (bool lastFullDay : {true, false}) {
+        jlong jdata = (jlong) env->CallLongMethod(
+                    gGraphicsStatsServiceObject,
+                    gGraphicsStatsService_pullGraphicsStatsMethodID,
+                    (jboolean)(lastFullDay ? JNI_TRUE : JNI_FALSE));
+        if (env->ExceptionCheck()) {
+            env->ExceptionDescribe();
+            env->ExceptionClear();
+            ALOGE("Failed to invoke graphicsstats service");
+            return STATS_PULL_SKIP;
+        }
+        if (!jdata) {
+            // null means data is not available for that day.
+            continue;
+        }
+        android::uirenderer::protos::GraphicsStatsServiceDumpProto serviceDump;
+        std::vector<uint8_t>* buffer = reinterpret_cast<std::vector<uint8_t>*>(jdata);
+        std::unique_ptr<std::vector<uint8_t>> bufferRelease(buffer);
+        int dataSize = buffer->size();
+        if (!dataSize) {
+            // Data is not available for that day.
+            continue;
+        }
+        io::ArrayInputStream input{buffer->data(), dataSize};
+        bool success = serviceDump.ParseFromZeroCopyStream(&input);
+        if (!success) {
+            ALOGW("Parse failed on GraphicsStatsPuller error='%s' dataSize='%d'",
+                  serviceDump.InitializationErrorString().c_str(), dataSize);
+            return STATS_PULL_SKIP;
+        }
+
+        for (int stat_index = 0; stat_index < serviceDump.stats_size(); stat_index++) {
+            auto& stat = serviceDump.stats(stat_index);
+            stats_event* event = add_stats_event_to_pull_data(data);
+            stats_event_set_atom_id(event, android::util::GRAPHICS_STATS);
+            stats_event_write_string8(event, stat.package_name().c_str());
+            stats_event_write_int64(event, (int64_t)stat.version_code());
+            stats_event_write_int64(event, (int64_t)stat.stats_start());
+            stats_event_write_int64(event, (int64_t)stat.stats_end());
+            stats_event_write_int32(event, (int32_t)stat.pipeline());
+            stats_event_write_int32(event, (int32_t)stat.summary().total_frames());
+            stats_event_write_int32(event, (int32_t)stat.summary().missed_vsync_count());
+            stats_event_write_int32(event, (int32_t)stat.summary().high_input_latency_count());
+            stats_event_write_int32(event, (int32_t)stat.summary().slow_ui_thread_count());
+            stats_event_write_int32(event, (int32_t)stat.summary().slow_bitmap_upload_count());
+            stats_event_write_int32(event, (int32_t)stat.summary().slow_draw_count());
+            stats_event_write_int32(event, (int32_t)stat.summary().missed_deadline_count());
+            writeCpuHistogram(event, stat);
+            writeGpuHistogram(event, stat);
+            // TODO: fill in UI mainline module version, when the feature is available.
+            stats_event_write_int64(event, (int64_t)0);
+            stats_event_write_bool(event, !lastFullDay);
+            stats_event_build(event);
+        }
+    }
+    return STATS_PULL_SUCCESS;
+}
+
+// Register a puller for GRAPHICS_STATS atom with the statsd service.
+static void nativeInit(JNIEnv* env, jobject javaObject) {
+    gGraphicsStatsServiceObject = env->NewGlobalRef(javaObject);
+    pull_atom_metadata metadata = {.cool_down_ns = 10 * 1000000, // 10 milliseconds
+                                   .timeout_ns = 2 * NS_PER_SEC, // 2 seconds
+                                   .additive_fields = nullptr,
+                                   .additive_fields_size = 0};
+    register_stats_pull_atom_callback(android::util::GRAPHICS_STATS, &graphicsStatsPullCallback,
+            &metadata, nullptr);
+}
+
+static void nativeDestructor(JNIEnv* env, jobject javaObject) {
+    //TODO: Unregister the puller callback when a new API is available.
+    env->DeleteGlobalRef(gGraphicsStatsServiceObject);
+    gGraphicsStatsServiceObject = nullptr;
+}
+
 static const JNINativeMethod sMethods[] = {
     { "nGetAshmemSize", "()I", (void*) getAshmemSize },
     { "nCreateDump", "(IZ)J", (void*) createDump },
     { "nAddToDump", "(JLjava/lang/String;Ljava/lang/String;JJJ[B)V", (void*) addToDump },
     { "nAddToDump", "(JLjava/lang/String;)V", (void*) addFileToDump },
     { "nFinishDump", "(J)V", (void*) finishDump },
+    { "nFinishDumpInMemory", "(J)J", (void*) finishDumpInMemory },
     { "nSaveBuffer", "(Ljava/lang/String;Ljava/lang/String;JJJ[B)V", (void*) saveBuffer },
+    { "nativeInit", "()V", (void*) nativeInit },
+    { "nativeDestructor",   "()V",     (void*)nativeDestructor }
 };
 
 int register_android_server_GraphicsStatsService(JNIEnv* env)
 {
+    jclass graphicsStatsService_class = FindClassOrDie(env,
+            "com/android/server/GraphicsStatsService");
+    gGraphicsStatsService_pullGraphicsStatsMethodID = GetMethodIDOrDie(env,
+            graphicsStatsService_class, "pullGraphicsStats", "(Z)J");
     return jniRegisterNativeMethods(env, "com/android/server/GraphicsStatsService",
                                     sMethods, NELEM(sMethods));
 }
 
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/services/core/jni/com_android_server_am_AppCompactor.cpp b/services/core/jni/com_android_server_am_AppCompactor.cpp
deleted file mode 100644
index de6aa8b..0000000
--- a/services/core/jni/com_android_server_am_AppCompactor.cpp
+++ /dev/null
@@ -1,88 +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.
- */
-
-#define LOG_TAG "AppCompactor"
-//#define LOG_NDEBUG 0
-
-#include <dirent.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <android-base/stringprintf.h>
-#include <android-base/file.h>
-
-#include <nativehelper/JNIHelp.h>
-#include <android_runtime/AndroidRuntime.h>
-#include <jni.h>
-
-using android::base::StringPrintf;
-using android::base::WriteStringToFile;
-
-namespace android {
-
-// This performs per-process reclaim on all processes belonging to non-app UIDs.
-// For the most part, these are non-zygote processes like Treble HALs, but it
-// also includes zygote-derived processes that run in system UIDs, like bluetooth
-// or potentially some mainline modules. The only process that should definitely
-// not be compacted is system_server, since compacting system_server around the
-// time of BOOT_COMPLETE could result in perceptible issues.
-static void com_android_server_am_AppCompactor_compactSystem(JNIEnv *, jobject) {
-    std::unique_ptr<DIR, decltype(&closedir)> proc(opendir("/proc"), closedir);
-    struct dirent* current;
-    while ((current = readdir(proc.get()))) {
-        if (current->d_type != DT_DIR) {
-            continue;
-        }
-
-        // don't compact system_server, rely on persistent compaction during screen off
-        // in order to avoid mmap_sem-related stalls
-        if (atoi(current->d_name) == getpid()) {
-            continue;
-        }
-
-        std::string status_name = StringPrintf("/proc/%s/status", current->d_name);
-        struct stat status_info;
-
-        if (stat(status_name.c_str(), &status_info) != 0) {
-            // must be some other directory that isn't a pid
-            continue;
-        }
-
-        // android.os.Process.FIRST_APPLICATION_UID
-        if (status_info.st_uid >= 10000) {
-            continue;
-        }
-
-        std::string reclaim_path = StringPrintf("/proc/%s/reclaim", current->d_name);
-        WriteStringToFile(std::string("all"), reclaim_path);
-    }
-}
-
-static const JNINativeMethod sMethods[] = {
-    /* name, signature, funcPtr */
-    {"compactSystem", "()V", (void*)com_android_server_am_AppCompactor_compactSystem},
-};
-
-int register_android_server_am_AppCompactor(JNIEnv* env)
-{
-    return jniRegisterNativeMethods(env, "com/android/server/am/AppCompactor",
-                                    sMethods, NELEM(sMethods));
-}
-
-}
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
new file mode 100644
index 0000000..6a6da0e
--- /dev/null
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "CachedAppOptimizer"
+//#define LOG_NDEBUG 0
+
+#include <dirent.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <android-base/stringprintf.h>
+#include <android-base/file.h>
+
+#include <nativehelper/JNIHelp.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <jni.h>
+
+using android::base::StringPrintf;
+using android::base::WriteStringToFile;
+
+namespace android {
+
+// This performs per-process reclaim on all processes belonging to non-app UIDs.
+// For the most part, these are non-zygote processes like Treble HALs, but it
+// also includes zygote-derived processes that run in system UIDs, like bluetooth
+// or potentially some mainline modules. The only process that should definitely
+// not be compacted is system_server, since compacting system_server around the
+// time of BOOT_COMPLETE could result in perceptible issues.
+static void com_android_server_am_CachedAppOptimizer_compactSystem(JNIEnv *, jobject) {
+    std::unique_ptr<DIR, decltype(&closedir)> proc(opendir("/proc"), closedir);
+    struct dirent* current;
+    while ((current = readdir(proc.get()))) {
+        if (current->d_type != DT_DIR) {
+            continue;
+        }
+
+        // don't compact system_server, rely on persistent compaction during screen off
+        // in order to avoid mmap_sem-related stalls
+        if (atoi(current->d_name) == getpid()) {
+            continue;
+        }
+
+        std::string status_name = StringPrintf("/proc/%s/status", current->d_name);
+        struct stat status_info;
+
+        if (stat(status_name.c_str(), &status_info) != 0) {
+            // must be some other directory that isn't a pid
+            continue;
+        }
+
+        // android.os.Process.FIRST_APPLICATION_UID
+        if (status_info.st_uid >= 10000) {
+            continue;
+        }
+
+        std::string reclaim_path = StringPrintf("/proc/%s/reclaim", current->d_name);
+        WriteStringToFile(std::string("all"), reclaim_path);
+    }
+}
+
+static const JNINativeMethod sMethods[] = {
+    /* name, signature, funcPtr */
+    {"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem},
+};
+
+int register_android_server_am_CachedAppOptimizer(JNIEnv* env)
+{
+    return jniRegisterNativeMethods(env, "com/android/server/am/CachedAppOptimizer",
+                                    sMethods, NELEM(sMethods));
+}
+
+}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index d1a0501..212a3a6 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -1729,6 +1729,12 @@
     return im->getInputManager()->getReader()->canDispatchToDisplay(deviceId, displayId);
 }
 
+static void nativeNotifyPortAssociationsChanged(JNIEnv* env, jclass /* clazz */, jlong ptr) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+    im->getInputManager()->getReader()->requestRefreshConfiguration(
+            InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gInputManagerMethods[] = {
@@ -1814,6 +1820,8 @@
             (void*) nativeSetCustomPointerIcon },
     { "nativeCanDispatchToDisplay", "(JII)Z",
             (void*) nativeCanDispatchToDisplay },
+    { "nativeNotifyPortAssociationsChanged", "(J)V",
+            (void*) nativeNotifyPortAssociationsChanged },
 };
 
 #define FIND_CLASS(var, className) \
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 6504e31..00436bb 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -129,7 +129,6 @@
 using android::hardware::hidl_string;
 using android::hardware::hidl_death_recipient;
 
-using android::hardware::gnss::V1_0::GnssConstellationType;
 using android::hardware::gnss::V1_0::GnssLocationFlags;
 using android::hardware::gnss::V1_0::IAGnssRilCallback;
 using android::hardware::gnss::V1_0::IGnssGeofenceCallback;
@@ -149,6 +148,8 @@
 
 using android::hidl::base::V1_0::IBase;
 
+using GnssConstellationType_V1_0 = android::hardware::gnss::V1_0::GnssConstellationType;
+using GnssConstellationType_V2_0 = android::hardware::gnss::V2_0::GnssConstellationType;
 using GnssLocation_V1_0 = android::hardware::gnss::V1_0::GnssLocation;
 using GnssLocation_V2_0 = android::hardware::gnss::V2_0::GnssLocation;
 using IGnss_V1_0 = android::hardware::gnss::V1_0::IGnss;
@@ -161,6 +162,7 @@
 using IGnssConfiguration_V1_0 = android::hardware::gnss::V1_0::IGnssConfiguration;
 using IGnssConfiguration_V1_1 = android::hardware::gnss::V1_1::IGnssConfiguration;
 using IGnssConfiguration_V2_0 = android::hardware::gnss::V2_0::IGnssConfiguration;
+using IGnssConfiguration_V2_1 = android::hardware::gnss::V2_1::IGnssConfiguration;
 using IGnssDebug_V1_0 = android::hardware::gnss::V1_0::IGnssDebug;
 using IGnssDebug_V2_0 = android::hardware::gnss::V2_0::IGnssDebug;
 using IGnssMeasurement_V1_0 = android::hardware::gnss::V1_0::IGnssMeasurement;
@@ -202,6 +204,7 @@
 
 // Must match the value from GnssMeasurement.java
 static const uint32_t ADR_STATE_HALF_CYCLE_REPORTED = (1<<4);
+static const uint32_t SVID_FLAGS_HAS_BASEBAND_CN0 = (1<<4);
 
 sp<GnssDeathRecipient> gnssHalDeathRecipient = nullptr;
 sp<IGnss_V1_0> gnssHal = nullptr;
@@ -221,6 +224,7 @@
 sp<IGnssConfiguration_V1_0> gnssConfigurationIface = nullptr;
 sp<IGnssConfiguration_V1_1> gnssConfigurationIface_V1_1 = nullptr;
 sp<IGnssConfiguration_V2_0> gnssConfigurationIface_V2_0 = nullptr;
+sp<IGnssConfiguration_V2_1> gnssConfigurationIface_V2_1 = nullptr;
 sp<IGnssNi> gnssNiIface = nullptr;
 sp<IGnssMeasurement_V1_0> gnssMeasurementIface = nullptr;
 sp<IGnssMeasurement_V1_1> gnssMeasurementIface_V1_1 = nullptr;
@@ -631,6 +635,16 @@
     template<class T>
     Return<void> gnssSvStatusCbImpl(const T& svStatus);
 
+    template<class T>
+    uint32_t getHasBasebandCn0DbHzFlag(const T& svStatus) {
+        return 0;
+    }
+
+    template<class T>
+    double getBasebandCn0DbHz(const T& svStatus, size_t i) {
+        return 0.0;
+    }
+
     uint32_t getGnssSvInfoListSize(const IGnssCallback_V1_0::GnssSvStatus& svStatus) {
         return svStatus.numSvs;
     }
@@ -655,8 +669,6 @@
 
     const IGnssCallback_V1_0::GnssSvInfo& getGnssSvInfoOfIndex(
             const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& svInfoList, size_t i) {
-        // TODO(b/144850155): fill baseband CN0 after it's available in Java object.
-        ALOGD("getGnssSvInfoOfIndex %d: baseband C/N0: %f", (int) i, svInfoList[i].basebandCN0DbHz);
         return svInfoList[i].v2_0.v1_0;
     }
 
@@ -718,6 +730,18 @@
     return Void();
 }
 
+template<>
+uint32_t GnssCallback::getHasBasebandCn0DbHzFlag(const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>&
+        svStatus) {
+    return SVID_FLAGS_HAS_BASEBAND_CN0;
+}
+
+template<>
+double GnssCallback::getBasebandCn0DbHz(const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& svInfoList,
+        size_t i) {
+    return svInfoList[i].basebandCN0DbHz;
+}
+
 template<class T>
 Return<void> GnssCallback::gnssSvStatusCbImpl(const T& svStatus) {
     JNIEnv* env = getJniEnv();
@@ -755,8 +779,8 @@
         elev[i] = info.elevationDegrees;
         azim[i] = info.azimuthDegrees;
         carrierFreq[i] = info.carrierFrequencyHz;
-        // TODO(b/144850155): fill svidWithFlags with hasBasebandCn0DbHz based on HAL versions
-        basebandCn0s[i] = 0.0;
+        svidWithFlags[i] |= getHasBasebandCn0DbHzFlag(svStatus);
+        basebandCn0s[i] = getBasebandCn0DbHz(svStatus, i);
     }
 
     env->ReleaseIntArrayElements(svidWithFlagArray, svidWithFlags, 0);
@@ -1182,8 +1206,8 @@
         const IGnssMeasurementCallback_V2_1::GnssMeasurement* measurement_V2_1,
         JavaObject& object) {
     translateSingleGnssMeasurement(&(measurement_V2_1->v2_0), object);
-    // TODO(b/144850155): fill baseband CN0 after it's available in Java object
-    ALOGD("baseband CN0DbHz = %f\n", measurement_V2_1->basebandCN0DbHz);
+
+    SET(BasebandCn0DbHz, measurement_V2_1->basebandCN0DbHz);
 }
 
 template<class T>
@@ -1888,7 +1912,17 @@
         gnssNiIface = gnssNi;
     }
 
-    if (gnssHal_V2_0 != nullptr) {
+    if (gnssHal_V2_1 != nullptr) {
+        auto gnssConfiguration = gnssHal_V2_1->getExtensionGnssConfiguration_2_1();
+        if (!gnssConfiguration.isOk()) {
+            ALOGD("Unable to get a handle to GnssConfiguration_V2_1");
+        } else {
+            gnssConfigurationIface_V2_1 = gnssConfiguration;
+            gnssConfigurationIface_V2_0 = gnssConfigurationIface_V2_1;
+            gnssConfigurationIface_V1_1 = gnssConfigurationIface_V2_1;
+            gnssConfigurationIface = gnssConfigurationIface_V2_1;
+        }
+    } else if (gnssHal_V2_0 != nullptr) {
         auto gnssConfiguration = gnssHal_V2_0->getExtensionGnssConfiguration_2_0();
         if (!gnssConfiguration.isOk()) {
             ALOGD("Unable to get a handle to GnssConfiguration_V2_0");
@@ -1962,7 +1996,11 @@
 static jobject android_location_GnssConfiguration_get_gnss_configuration_version(
         JNIEnv* env, jclass /* jclazz */) {
     jint major, minor;
-    if (gnssConfigurationIface_V2_0 != nullptr) {
+    if (gnssConfigurationIface_V2_1 != nullptr) {
+        major = 2;
+        minor = 1;
+    }
+    else if (gnssConfigurationIface_V2_0 != nullptr) {
         major = 2;
         minor = 0;
     } else if (gnssConfigurationIface_V1_1 != nullptr) {
@@ -2768,7 +2806,7 @@
 
         SingleSatCorrection singleSatCorrection = {
             .singleSatCorrectionFlags = corrFlags,
-            .constellation = static_cast<GnssConstellationType>(constType),
+            .constellation = static_cast<GnssConstellationType_V1_0>(constType),
             .svid = static_cast<uint16_t>(satId),
             .carrierFrequencyHz = carrierFreqHz,
             .probSatIsLos = probSatIsLos,
@@ -2863,8 +2901,8 @@
 static jboolean android_location_GnssConfiguration_set_supl_es(JNIEnv*,
                                                                jobject,
                                                                jint suplEs) {
-    if (gnssConfigurationIface_V2_0 != nullptr) {
-        ALOGI("Config parameter SUPL_ES is deprecated in IGnssConfiguration.hal version 2.0.");
+    if (gnssConfigurationIface_V2_0 != nullptr || gnssConfigurationIface_V2_1 != nullptr) {
+        ALOGI("Config parameter SUPL_ES is deprecated in IGnssConfiguration.hal version 2.0 and higher.");
         return JNI_FALSE;
     }
 
@@ -2892,7 +2930,7 @@
 static jboolean android_location_GnssConfiguration_set_gps_lock(JNIEnv*,
                                                                 jobject,
                                                                 jint gpsLock) {
-    if (gnssConfigurationIface_V2_0 != nullptr) {
+    if (gnssConfigurationIface_V2_0 != nullptr || gnssConfigurationIface_V2_1 != nullptr) {
         ALOGI("Config parameter GPS_LOCK is deprecated in IGnssConfiguration.hal version 2.0.");
         return JNI_FALSE;
     }
@@ -2932,7 +2970,7 @@
 
 static jboolean android_location_GnssConfiguration_set_satellite_blacklist(
         JNIEnv* env, jobject, jintArray constellations, jintArray sv_ids) {
-    if (gnssConfigurationIface_V1_1 == nullptr) {
+    if (gnssConfigurationIface_V1_1 == nullptr && gnssConfigurationIface_V2_1 == nullptr) {
         ALOGI("IGnssConfiguration interface does not support satellite blacklist.");
         return JNI_FALSE;
     }
@@ -2955,11 +2993,24 @@
         return JNI_FALSE;
     }
 
+    if (gnssConfigurationIface_V2_1 != nullptr) {
+        hidl_vec<IGnssConfiguration_V2_1::BlacklistedSource> sources;
+        sources.resize(length);
+
+        for (int i = 0; i < length; i++) {
+            sources[i].constellation = static_cast<GnssConstellationType_V2_0>(constellation_array[i]);
+            sources[i].svid = sv_id_array[i];
+        }
+
+        auto result = gnssConfigurationIface_V2_1->setBlacklist_2_1(sources);
+        return checkHidlReturn(result, "IGnssConfiguration_V2_1 setBlacklist_2_1() failed.");
+    }
+
     hidl_vec<IGnssConfiguration_V1_1::BlacklistedSource> sources;
     sources.resize(length);
 
     for (int i = 0; i < length; i++) {
-        sources[i].constellation = static_cast<GnssConstellationType>(constellation_array[i]);
+        sources[i].constellation = static_cast<GnssConstellationType_V1_0>(constellation_array[i]);
         sources[i].svid = sv_id_array[i];
     }
 
diff --git a/services/core/jni/com_android_server_net_NetworkStatsFactory.cpp b/services/core/jni/com_android_server_net_NetworkStatsFactory.cpp
index 9cd743b..8b6526ff 100644
--- a/services/core/jni/com_android_server_net_NetworkStatsFactory.cpp
+++ b/services/core/jni/com_android_server_net_NetworkStatsFactory.cpp
@@ -20,6 +20,7 @@
 #include <inttypes.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <vector>
 
 #include <jni.h>
 
diff --git a/services/core/jni/com_android_server_net_NetworkStatsService.cpp b/services/core/jni/com_android_server_net_NetworkStatsService.cpp
index 4696dd0..0275f3e 100644
--- a/services/core/jni/com_android_server_net_NetworkStatsService.cpp
+++ b/services/core/jni/com_android_server_net_NetworkStatsService.cpp
@@ -33,7 +33,6 @@
 #include "bpf/BpfUtils.h"
 #include "netdbpf/BpfNetworkStats.h"
 
-using android::bpf::Stats;
 using android::bpf::bpfGetUidStats;
 using android::bpf::bpfGetIfaceStats;
 
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index c0a6e4e..19fa062 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -54,7 +54,7 @@
 int register_android_server_net_NetworkStatsFactory(JNIEnv* env);
 int register_android_server_net_NetworkStatsService(JNIEnv* env);
 int register_android_server_security_VerityUtils(JNIEnv* env);
-int register_android_server_am_AppCompactor(JNIEnv* env);
+int register_android_server_am_CachedAppOptimizer(JNIEnv* env);
 int register_android_server_am_LowMemDetector(JNIEnv* env);
 int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
         JNIEnv* env);
@@ -106,7 +106,7 @@
     register_android_server_net_NetworkStatsFactory(env);
     register_android_server_net_NetworkStatsService(env);
     register_android_server_security_VerityUtils(env);
-    register_android_server_am_AppCompactor(env);
+    register_android_server_am_CachedAppOptimizer(env);
     register_android_server_am_LowMemDetector(env);
     register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
             env);
diff --git a/services/core/xsd/Android.bp b/services/core/xsd/Android.bp
index 8b2cbbd..b7d6424 100644
--- a/services/core/xsd/Android.bp
+++ b/services/core/xsd/Android.bp
@@ -12,3 +12,11 @@
     api_dir: "platform-compat-schema",
     package_name: "com.android.server.compat.config",
 }
+
+
+xsd_config {
+    name: "display-device-config",
+    srcs: ["display-device-config/display-device-config.xsd"],
+    api_dir: "display-device-config/schema",
+    package_name: "com.android.server.display.config",
+}
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
new file mode 100644
index 0000000..5c7f305
--- /dev/null
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -0,0 +1,54 @@
+<?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.
+  -->
+
+<!-- This defines the format of the XML file generated by
+  ~ com.android.compat.annotation.ChangeIdProcessor annotation processor (from
+  ~ tools/platform-compat), and is parsed in com/android/server/compat/CompatConfig.java.
+-->
+<xs:schema version="2.0"
+           elementFormDefault="qualified"
+           xmlns:xs="http://www.w3.org/2001/XMLSchema">
+    <xs:element name="displayConfiguration">
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element type="nitsMap" name="screenBrightnessMap"/>
+            </xs:sequence>
+        </xs:complexType>
+    </xs:element>
+
+    <!-- Type definitions -->
+
+    <xs:complexType name="nitsMap">
+        <xs:sequence>
+            <xs:element name="point" type="point" maxOccurs="unbounded" minOccurs="2"/>
+            <xs:element name="highBrightnessStart" minOccurs="0" type="nonNegativeDecimal"/>
+        </xs:sequence>
+    </xs:complexType>
+
+    <xs:complexType name="point">
+        <xs:sequence>
+            <xs:element type="nonNegativeDecimal" name="value"/>
+            <xs:element type="nonNegativeDecimal" name="nits"/>
+        </xs:sequence>
+    </xs:complexType>
+
+    <xs:simpleType name="nonNegativeDecimal">
+        <xs:restriction base="xs:decimal">
+            <xs:minInclusive value="0.0"/>
+        </xs:restriction>
+    </xs:simpleType>
+</xs:schema>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
new file mode 100644
index 0000000..5a9c945
--- /dev/null
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -0,0 +1,33 @@
+// Signature format: 2.0
+package com.android.server.display.config {
+
+  public class DisplayConfiguration {
+    ctor public DisplayConfiguration();
+    method public com.android.server.display.config.NitsMap getScreenBrightnessMap();
+    method public void setScreenBrightnessMap(com.android.server.display.config.NitsMap);
+  }
+
+  public class NitsMap {
+    ctor public NitsMap();
+    method public java.math.BigDecimal getHighBrightnessStart();
+    method public java.util.List<com.android.server.display.config.Point> getPoint();
+    method public void setHighBrightnessStart(java.math.BigDecimal);
+  }
+
+  public class Point {
+    ctor public Point();
+    method public java.math.BigDecimal getNits();
+    method public java.math.BigDecimal getValue();
+    method public void setNits(java.math.BigDecimal);
+    method public void setValue(java.math.BigDecimal);
+  }
+
+  public class XmlParser {
+    ctor public XmlParser();
+    method public static com.android.server.display.config.DisplayConfiguration 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/display-device-config/schema/last_current.txt b/services/core/xsd/display-device-config/schema/last_current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/services/core/xsd/display-device-config/schema/last_current.txt
diff --git a/services/core/xsd/display-device-config/schema/last_removed.txt b/services/core/xsd/display-device-config/schema/last_removed.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/services/core/xsd/display-device-config/schema/last_removed.txt
diff --git a/services/core/xsd/display-device-config/schema/removed.txt b/services/core/xsd/display-device-config/schema/removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/services/core/xsd/display-device-config/schema/removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/services/devicepolicy/Android.bp b/services/devicepolicy/Android.bp
index 380ee94..cdbe77a 100644
--- a/services/devicepolicy/Android.bp
+++ b/services/devicepolicy/Android.bp
@@ -18,8 +18,3 @@
         "compat-changeid-annotation-processor",
     ],
 }
-
-platform_compat_config {
-    name: "services-devicepolicy-platform-compat-config",
-    src: ":services.devicepolicy",
-}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 8641059..43ee97d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -68,4 +68,11 @@
     public boolean isOrganizationOwnedDeviceWithManagedProfile() {
         return false;
     }
+
+    public int getPersonalAppsSuspendedReasons(ComponentName admin) {
+        return 0;
+    }
+
+    public void setPersonalAppsSuspended(ComponentName admin, boolean suspended) {
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2a08f5c..da107d0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -19,11 +19,11 @@
 import static android.Manifest.permission.BIND_DEVICE_ADMIN;
 import static android.Manifest.permission.MANAGE_CA_CERTIFICATES;
 import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY;
+import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
 import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE;
 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER;
 import static android.app.admin.DevicePolicyManager.CODE_ACCOUNTS_NOT_EMPTY;
-import static android.app.admin.DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED;
 import static android.app.admin.DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE;
 import static android.app.admin.DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED;
 import static android.app.admin.DevicePolicyManager.CODE_HAS_DEVICE_OWNER;
@@ -127,8 +127,10 @@
 import android.app.admin.DevicePolicyEventLogger;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManager.PasswordComplexity;
+import android.app.admin.DevicePolicyManager.PersonalAppSuspensionReason;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.app.admin.DeviceStateCache;
+import android.app.admin.FactoryResetProtectionPolicy;
 import android.app.admin.NetworkEvent;
 import android.app.admin.PasswordMetrics;
 import android.app.admin.PasswordPolicy;
@@ -156,6 +158,7 @@
 import android.content.PermissionChecker;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.CrossProfileApps;
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
@@ -253,6 +256,7 @@
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.telephony.SmsApplication;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
@@ -268,6 +272,7 @@
 import com.android.internal.widget.PasswordValidationError;
 import com.android.server.LocalServices;
 import com.android.server.LockGuard;
+import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemService;
 import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo;
@@ -301,6 +306,7 @@
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -309,6 +315,7 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Function;
+import java.util.stream.Collectors;
 
 /**
  * Implementation of the device policy APIs.
@@ -369,6 +376,10 @@
 
     private static final String TAG_PROTECTED_PACKAGES = "protected-packages";
 
+    private static final String TAG_SECONDARY_LOCK_SCREEN = "secondary-lock-screen";
+
+    private static final String TAG_PERSONAL_APPS_SUSPENDED = "personal-apps-suspended";
+
     private static final int REQUEST_EXPIRE_PASSWORD = 5571;
 
     private static final long MS_PER_DAY = TimeUnit.DAYS.toMillis(1);
@@ -439,6 +450,8 @@
     // A collection of user restrictions that are deprecated and should simply be ignored.
     private static final Set<String> DEPRECATED_USER_RESTRICTIONS;
     private static final String AB_DEVICE_KEY = "ro.build.ab_update";
+    // Permissions related to location which must not be granted automatically
+    private static  final Set<String> LOCATION_PERMISSIONS;
 
     static {
         SECURE_SETTINGS_WHITELIST = new ArraySet<>();
@@ -483,6 +496,11 @@
         DEPRECATED_USER_RESTRICTIONS = Sets.newHashSet(
                 UserManager.DISALLOW_ADD_MANAGED_PROFILE,
                 UserManager.DISALLOW_REMOVE_MANAGED_PROFILE);
+
+        LOCATION_PERMISSIONS = Sets.newHashSet(
+                permission.ACCESS_FINE_LOCATION,
+                permission.ACCESS_BACKGROUND_LOCATION,
+                permission.ACCESS_COARSE_LOCATION);
     }
 
     /**
@@ -521,11 +539,11 @@
     /**
      * For admin apps targeting R+, throw when the app sets password requirement
      * that is not taken into account at given quality. For example when quality is set
-     * to {@link DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}, it doesn't make sense to
-     * require certain password length. If the intent is to require a password of certain length
-     * having at least NUMERIC quality, the admin should first call
-     * {@link #setPasswordQuality(ComponentName, int, boolean)} and only then call
-     * {@link #setPasswordMinimumLength(ComponentName, int, boolean)}.
+     * to {@link android.app.admin.DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}, it doesn't
+     * make sense to require certain password length. If the intent is to require a password of
+     * certain length having at least NUMERIC quality, the admin should first call
+     * {@link android.app.admin.DevicePolicyManager#setPasswordQuality} and only then call
+     * {@link android.app.admin.DevicePolicyManager#setPasswordMinimumLength}.
      *
      * <p>Conversely when an admin app targeting R+ lowers password quality, those
      * requirements that stop making sense are reset to default values.
@@ -770,6 +788,8 @@
 
         boolean mCurrentInputMethodSet = false;
 
+        boolean mSecondaryLockscreenEnabled = false;
+
         // TODO(b/35385311): Keep track of metadata in TrustedCertificateStore instead.
         Set<String> mOwnerInstalledCaCerts = new ArraySet<>();
 
@@ -779,6 +799,10 @@
 
         long mPasswordTokenHandle = 0;
 
+        // Flag reflecting the current state of the personal apps suspension. This flag should
+        // only be written AFTER all the needed apps were suspended or unsuspended.
+        boolean mPersonalAppsSuspended = false;
+
         public DevicePolicyData(int userHandle) {
             mUserHandle = userHandle;
         }
@@ -1006,6 +1030,9 @@
         private static final String TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL =
                 "cross-profile-calendar-packages-null";
         private static final String TAG_CROSS_PROFILE_PACKAGES = "cross-profile-packages";
+        private static final String TAG_FACTORY_RESET_PROTECTION_POLICY =
+                "factory_reset_protection_policy";
+        private static final String TAG_SUSPEND_PERSONAL_APPS = "suspend-personal-apps";
 
         DeviceAdminInfo info;
 
@@ -1016,6 +1043,9 @@
         @NonNull
         PasswordPolicy mPasswordPolicy = new PasswordPolicy();
 
+        @Nullable
+        FactoryResetProtectionPolicy mFactoryResetProtectionPolicy = null;
+
         static final long DEF_MAXIMUM_TIME_TO_UNLOCK = 0;
         long maximumTimeToUnlock = DEF_MAXIMUM_TIME_TO_UNLOCK;
 
@@ -1089,7 +1119,7 @@
         String globalProxySpec = null;
         String globalProxyExclusionList = null;
 
-        ArrayMap<String, TrustAgentInfo> trustAgentInfos = new ArrayMap<>();
+        @NonNull ArrayMap<String, TrustAgentInfo> trustAgentInfos = new ArrayMap<>();
 
         List<String> crossProfileWidgetProviders;
 
@@ -1125,6 +1155,9 @@
         // represented as an empty list.
         List<String> mCrossProfilePackages = Collections.emptyList();
 
+        // Whether the admin explicitly requires personal apps to be suspended
+        boolean mSuspendPersonalApps = false;
+
         ActiveAdmin(DeviceAdminInfo _info, boolean parent) {
             info = _info;
             isParent = parent;
@@ -1334,8 +1367,7 @@
                 writeTextToXml(out, TAG_ORGANIZATION_NAME, organizationName);
             }
             if (isLogoutEnabled) {
-                writeAttributeValueToXml(
-                        out, TAG_IS_LOGOUT_ENABLED, isLogoutEnabled);
+                writeAttributeValueToXml(out, TAG_IS_LOGOUT_ENABLED, isLogoutEnabled);
             }
             if (startUserSessionMessage != null) {
                 writeTextToXml(out, TAG_START_USER_SESSION_MESSAGE, startUserSessionMessage);
@@ -1351,6 +1383,14 @@
                         mCrossProfileCalendarPackages);
             }
             writePackageListToXml(out, TAG_CROSS_PROFILE_PACKAGES, mCrossProfilePackages);
+            if (mFactoryResetProtectionPolicy != null) {
+                out.startTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
+                mFactoryResetProtectionPolicy.writeToXml(out);
+                out.endTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
+            }
+            if (mSuspendPersonalApps) {
+                writeAttributeValueToXml(out, TAG_SUSPEND_PERSONAL_APPS, mSuspendPersonalApps);
+            }
         }
 
         void writeTextToXml(XmlSerializer out, String tag, String text) throws IOException {
@@ -1584,6 +1624,12 @@
                     mCrossProfileCalendarPackages = null;
                 } else if (TAG_CROSS_PROFILE_PACKAGES.equals(tag)) {
                     mCrossProfilePackages = readPackageList(parser, tag);
+                } else if (TAG_FACTORY_RESET_PROTECTION_POLICY.equals(tag)) {
+                    mFactoryResetProtectionPolicy = FactoryResetProtectionPolicy.readFromXml(
+                                parser);
+                } else if (TAG_SUSPEND_PERSONAL_APPS.equals(tag)) {
+                    mSuspendPersonalApps = Boolean.parseBoolean(
+                            parser.getAttributeValue(null, ATTR_VALUE));
                 } else {
                     Slog.w(LOG_TAG, "Unknown admin tag: " + tag);
                     XmlUtils.skipCurrentTag(parser);
@@ -1636,6 +1682,7 @@
             }
         }
 
+        @NonNull
         private ArrayMap<String, TrustAgentInfo> getAllTrustAgentInfos(
                 XmlPullParser parser, String tag) throws XmlPullParserException, IOException {
             int outerDepthDAM = parser.getDepth();
@@ -2036,6 +2083,10 @@
             return IAudioService.Stub.asInterface(ServiceManager.getService(Context.AUDIO_SERVICE));
         }
 
+        PersistentDataBlockManagerInternal getPersistentDataBlockManagerInternal() {
+            return LocalServices.getService(PersistentDataBlockManagerInternal.class);
+        }
+
         LockSettingsInternal getLockSettingsInternal() {
             return LocalServices.getService(LockSettingsInternal.class);
         }
@@ -2416,11 +2467,133 @@
             migrateUserRestrictionsIfNecessaryLocked();
 
             // TODO PO may not have a class name either due to b/17652534.  Address that too.
-
             updateDeviceOwnerLocked();
         }
     }
 
+    /**
+     * Checks if the device is in COMP mode, and if so migrates it to managed profile on a
+     * corporate owned device.
+     */
+    @GuardedBy("getLockObject()")
+    private void maybeMigrateToProfileOnOrganizationOwnedDeviceLocked() {
+        logIfVerbose("Checking whether we need to migrate COMP ");
+        final int doUserId = mOwners.getDeviceOwnerUserId();
+        if (doUserId == UserHandle.USER_NULL) {
+            logIfVerbose("No DO found, skipping migration.");
+            return;
+        }
+
+        final List<UserInfo> profiles = mUserManager.getProfiles(doUserId);
+        if (profiles.size() != 2) {
+            if (profiles.size() == 1) {
+                logIfVerbose("Profile not found, skipping migration.");
+            } else {
+                Slog.wtf(LOG_TAG, "Found " + profiles.size() + " profiles, skipping migration");
+            }
+            return;
+        }
+
+        final int poUserId = getManagedUserId(doUserId);
+        if (poUserId < 0) {
+            Slog.wtf(LOG_TAG, "Found DO and a profile, but it is not managed, skipping migration");
+            return;
+        }
+
+        final ActiveAdmin doAdmin = getDeviceOwnerAdminLocked();
+        final ActiveAdmin poAdmin = getProfileOwnerAdminLocked(poUserId);
+        if (doAdmin == null || poAdmin == null) {
+            Slog.wtf(LOG_TAG, "Failed to get either PO or DO admin, aborting migration.");
+            return;
+        }
+
+        final ComponentName doAdminComponent = mOwners.getDeviceOwnerComponent();
+        final ComponentName poAdminComponent = mOwners.getProfileOwnerComponent(poUserId);
+        if (doAdminComponent == null || poAdminComponent == null) {
+            Slog.wtf(LOG_TAG, "Cannot find PO or DO component name, aborting migration.");
+            return;
+        }
+        if (!doAdminComponent.getPackageName().equals(poAdminComponent.getPackageName())) {
+            Slog.e(LOG_TAG, "DO and PO are different packages, aborting migration.");
+            return;
+        }
+
+        Slog.i(LOG_TAG, String.format(
+                "Migrating COMP to PO on a corp owned device; primary user: %d; profile: %d",
+                doUserId, poUserId));
+
+        Slog.i(LOG_TAG, "Giving the PO additional power...");
+        markProfileOwnerOnOrganizationOwnedDeviceUncheckedLocked(poAdminComponent, poUserId);
+        Slog.i(LOG_TAG, "Migrating DO policies to PO...");
+        moveDoPoliciesToProfileParentAdmin(doAdmin, poAdmin.getParentActiveAdmin());
+        saveSettingsLocked(poUserId);
+        Slog.i(LOG_TAG, "Clearing the DO...");
+        final ComponentName doAdminReceiver = doAdmin.info.getComponent();
+        clearDeviceOwnerLocked(doAdmin, doUserId);
+        // TODO(b/143516163): If we have a power cut here, we might leave active admin. Consider if
+        // it is worth the complexity to make it more robust.
+        Slog.i(LOG_TAG, "Removing admin artifacts...");
+        // TODO(b/143516163): Clean up application restrictions in UserManager.
+        removeAdminArtifacts(doAdminReceiver, doUserId);
+        Slog.i(LOG_TAG, "Migration complete.");
+
+        // Note: KeyChain keys are not removed and will remain accessible for the apps that have
+        // been given grants to use them.
+    }
+
+    private void moveDoPoliciesToProfileParentAdmin(ActiveAdmin doAdmin, ActiveAdmin parentAdmin) {
+        // The following policies can be already controlled via parent instance, skip if so.
+        if (parentAdmin.mPasswordPolicy.quality == PASSWORD_QUALITY_UNSPECIFIED) {
+            parentAdmin.mPasswordPolicy = doAdmin.mPasswordPolicy;
+        }
+        if (parentAdmin.passwordHistoryLength == ActiveAdmin.DEF_PASSWORD_HISTORY_LENGTH) {
+            parentAdmin.passwordHistoryLength = doAdmin.passwordHistoryLength;
+        }
+        if (parentAdmin.passwordExpirationTimeout == ActiveAdmin.DEF_PASSWORD_HISTORY_LENGTH) {
+            parentAdmin.passwordExpirationTimeout = doAdmin.passwordExpirationTimeout;
+        }
+        if (parentAdmin.maximumFailedPasswordsForWipe
+                == ActiveAdmin.DEF_MAXIMUM_FAILED_PASSWORDS_FOR_WIPE) {
+            parentAdmin.maximumFailedPasswordsForWipe = doAdmin.maximumFailedPasswordsForWipe;
+        }
+        if (parentAdmin.maximumTimeToUnlock == ActiveAdmin.DEF_MAXIMUM_TIME_TO_UNLOCK) {
+            parentAdmin.maximumTimeToUnlock = doAdmin.maximumTimeToUnlock;
+        }
+        if (parentAdmin.strongAuthUnlockTimeout
+                == DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS) {
+            parentAdmin.strongAuthUnlockTimeout = doAdmin.strongAuthUnlockTimeout;
+        }
+        parentAdmin.disabledKeyguardFeatures |=
+                doAdmin.disabledKeyguardFeatures & PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
+
+        parentAdmin.trustAgentInfos.putAll(doAdmin.trustAgentInfos);
+
+        // The following policies weren't available to PO, but will be available after migration.
+        parentAdmin.disableCamera = doAdmin.disableCamera;
+
+        // TODO(b/143516163): Uncomment once corresponding APIs are available via parent instance.
+        // parentAdmin.disableScreenCapture = doAdmin.disableScreenCapture;
+        // parentAdmin.accountTypesWithManagementDisabled.addAll(
+        //         doAdmin.accountTypesWithManagementDisabled);
+
+        moveDoUserRestrictionsToCopeParent(doAdmin, parentAdmin);
+
+        // TODO(b/143516163): migrate network and security logging state, currently they are
+        // turned off when DO is removed.
+    }
+
+    private void moveDoUserRestrictionsToCopeParent(ActiveAdmin doAdmin, ActiveAdmin parentAdmin) {
+        if (doAdmin.userRestrictions == null) {
+            return;
+        }
+        for (final String restriction : doAdmin.userRestrictions.keySet()) {
+            if (UserRestrictionsUtils.canProfileOwnerOfOrganizationOwnedDeviceChange(restriction)) {
+                parentAdmin.ensureUserRestrictions().putBoolean(
+                        restriction, doAdmin.userRestrictions.getBoolean(restriction));
+            }
+        }
+    }
+
     /** Apply default restrictions that haven't been applied to profile owners yet. */
     private void maybeSetDefaultProfileOwnerUserRestrictions() {
         synchronized (getLockObject()) {
@@ -3181,6 +3354,12 @@
                 out.endTag(null, TAG_LOCK_TASK_FEATURES);
             }
 
+            if (policy.mSecondaryLockscreenEnabled) {
+                out.startTag(null, TAG_SECONDARY_LOCK_SCREEN);
+                out.attribute(null, ATTR_VALUE, Boolean.toString(true));
+                out.endTag(null, TAG_SECONDARY_LOCK_SCREEN);
+            }
+
             if (policy.mStatusBarDisabled) {
                 out.startTag(null, TAG_STATUS_BAR);
                 out.attribute(null, ATTR_DISABLED, Boolean.toString(policy.mStatusBarDisabled));
@@ -3257,6 +3436,13 @@
                 out.endTag(null, TAG_PROTECTED_PACKAGES);
             }
 
+            if (policy.mPersonalAppsSuspended) {
+                out.startTag(null, TAG_PERSONAL_APPS_SUSPENDED);
+                out.attribute(null, ATTR_VALUE,
+                        Boolean.toString(policy.mPersonalAppsSuspended));
+                out.endTag(null, TAG_PERSONAL_APPS_SUSPENDED);
+            }
+
             out.endTag(null, "policies");
 
             out.endDocument();
@@ -3430,6 +3616,9 @@
                 } else if (TAG_LOCK_TASK_FEATURES.equals(tag)) {
                     policy.mLockTaskFeatures = Integer.parseInt(
                             parser.getAttributeValue(null, ATTR_VALUE));
+                } else if (TAG_SECONDARY_LOCK_SCREEN.equals(tag)) {
+                    policy.mSecondaryLockscreenEnabled = Boolean.parseBoolean(
+                            parser.getAttributeValue(null, ATTR_VALUE));
                 } else if (TAG_STATUS_BAR.equals(tag)) {
                     policy.mStatusBarDisabled = Boolean.parseBoolean(
                             parser.getAttributeValue(null, ATTR_DISABLED));
@@ -3470,6 +3659,9 @@
                     policy.mOwnerInstalledCaCerts.add(parser.getAttributeValue(null, ATTR_ALIAS));
                 } else if (TAG_PROTECTED_PACKAGES.equals(tag)) {
                     policy.mProtectedPackages.add(parser.getAttributeValue(null, ATTR_NAME));
+                } else if (TAG_PERSONAL_APPS_SUSPENDED.equals(tag)) {
+                    policy.mPersonalAppsSuspended =
+                            Boolean.parseBoolean(parser.getAttributeValue(null, ATTR_VALUE));
                 } else {
                     Slog.w(LOG_TAG, "Unknown tag: " + tag);
                     XmlUtils.skipCurrentTag(parser);
@@ -3607,6 +3799,10 @@
                 break;
             case SystemService.PHASE_ACTIVITY_MANAGER_READY:
                 maybeStartSecurityLogMonitorOnActivityManagerReady();
+                synchronized (getLockObject()) {
+                    maybeMigrateToProfileOnOrganizationOwnedDeviceLocked();
+                }
+                checkPackageSuspensionOnBoot();
                 break;
             case SystemService.PHASE_BOOT_COMPLETED:
                 ensureDeviceOwnerUserStarted(); // TODO Consider better place to do this.
@@ -3614,6 +3810,34 @@
         }
     }
 
+    private void checkPackageSuspensionOnBoot() {
+        int profileUserId = UserHandle.USER_NULL;
+        final boolean shouldSuspend;
+        synchronized (getLockObject()) {
+            for (final int userId : mOwners.getProfileOwnerKeys()) {
+                if (mOwners.isProfileOwnerOfOrganizationOwnedDevice(userId)) {
+                    profileUserId = userId;
+                    break;
+                }
+            }
+
+            if (profileUserId == UserHandle.USER_NULL) {
+                shouldSuspend = false;
+            } else {
+                shouldSuspend = getProfileOwnerAdminLocked(profileUserId).mSuspendPersonalApps;
+            }
+        }
+
+        final boolean suspended = getUserData(UserHandle.USER_SYSTEM).mPersonalAppsSuspended;
+        if (suspended != shouldSuspend) {
+            suspendPersonalAppsInternal(shouldSuspend, UserHandle.USER_SYSTEM);
+        }
+
+        if (shouldSuspend) {
+            sendPersonalAppsSuspendedNotification(profileUserId);
+        }
+    }
+
     private void onLockSettingsReady() {
         getUserData(UserHandle.USER_SYSTEM);
         loadOwners();
@@ -3726,11 +3950,13 @@
     @Override
     void handleUnlockUser(int userId) {
         startOwnerService(userId, "unlock-user");
+        maybeUpdatePersonalAppsSuspendedNotification(userId);
     }
 
     @Override
     void handleStopUser(int userId) {
         stopOwnerService(userId, "stop-user");
+        maybeUpdatePersonalAppsSuspendedNotification(userId);
     }
 
     private void startOwnerService(int userId, String actionForLog) {
@@ -4099,6 +4325,12 @@
         if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, userHandle)) {
             mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, false, userHandle);
         }
+        // When a device owner is set, the system automatically restricts adding a managed profile.
+        // Remove this restriction when the device owner is cleared.
+        if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, userHandle)) {
+            mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, false,
+                    userHandle);
+        }
     }
 
     /**
@@ -6736,6 +6968,67 @@
     }
 
     @Override
+    public void setFactoryResetProtectionPolicy(ComponentName who,
+            @Nullable FactoryResetProtectionPolicy policy) {
+        if (!mHasFeature) {
+            return;
+        }
+        Preconditions.checkNotNull(who, "ComponentName is null");
+
+        final int frpManagementAgentUid = getFrpManagementAgentUidOrThrow();
+        final int userId = mInjector.userHandleGetCallingUserId();
+        synchronized (getLockObject()) {
+            ActiveAdmin admin = getActiveAdminForCallerLocked(
+                    who, DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER);
+            admin.mFactoryResetProtectionPolicy = policy;
+            saveSettingsLocked(userId);
+        }
+
+        mInjector.binderWithCleanCallingIdentity(() -> mContext.sendBroadcastAsUser(
+                new Intent(DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED),
+                UserHandle.getUserHandleForUid(frpManagementAgentUid)));
+
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_FACTORY_RESET_PROTECTION)
+                .setAdmin(who)
+                .write();
+    }
+
+    @Override
+    public FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(
+            @Nullable ComponentName who) {
+        if (!mHasFeature) {
+            return null;
+        }
+
+        final int frpManagementAgentUid = getFrpManagementAgentUidOrThrow();
+        ActiveAdmin admin;
+        synchronized (getLockObject()) {
+            if (who == null) {
+                if ((frpManagementAgentUid != mInjector.binderGetCallingUid())) {
+                    throw new SecurityException(
+                            "Must be called by the FRP management agent on device");
+                }
+                admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
+                        UserHandle.getUserId(frpManagementAgentUid));
+            } else {
+                admin = getActiveAdminForCallerLocked(
+                        who, DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER);
+            }
+        }
+        return admin != null ? admin.mFactoryResetProtectionPolicy : null;
+    }
+
+    private int getFrpManagementAgentUidOrThrow() {
+        PersistentDataBlockManagerInternal pdb = mInjector.getPersistentDataBlockManagerInternal();
+        if ((pdb == null) || (pdb.getAllowedUid() == -1)) {
+            throw new UnsupportedOperationException(
+                    "The persistent data block service is not supported on this device");
+        }
+        return pdb.getAllowedUid();
+    }
+
+    @Override
     public void getRemoveWarning(ComponentName comp, final RemoteCallback result, int userHandle) {
         if (!mHasFeature) {
             return;
@@ -7371,8 +7664,7 @@
             return;
         }
         Objects.requireNonNull(who, "ComponentName is null");
-        // TODO (b/145286957) Refactor security checks
-        enforceDeviceOwnerOrProfileOwnerOnUser0OrProfileOwnerOrganizationOwned();
+        enforceProfileOwnerOnUser0OrProfileOwnerOrganizationOwned();
 
         mInjector.binderWithCleanCallingIdentity(() ->
                 mInjector.settingsGlobalPutInt(Settings.Global.AUTO_TIME, enabled ? 1 : 0));
@@ -7393,7 +7685,7 @@
             return false;
         }
         Objects.requireNonNull(who, "ComponentName is null");
-        enforceDeviceOwnerOrProfileOwnerOnUser0OrProfileOwnerOrganizationOwned();
+        enforceProfileOwnerOnUser0OrProfileOwnerOrganizationOwned();
 
         return mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) > 0;
     }
@@ -7407,8 +7699,7 @@
             return;
         }
         Objects.requireNonNull(who, "ComponentName is null");
-        // TODO (b/145286957) Refactor security checks
-        enforceDeviceOwnerOrProfileOwnerOnUser0OrProfileOwnerOrganizationOwned();
+        enforceProfileOwnerOnUser0OrProfileOwnerOrganizationOwned();
 
         mInjector.binderWithCleanCallingIdentity(() ->
                 mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0));
@@ -7429,7 +7720,7 @@
             return false;
         }
         Objects.requireNonNull(who, "ComponentName is null");
-        enforceDeviceOwnerOrProfileOwnerOnUser0OrProfileOwnerOrganizationOwned();
+        enforceProfileOwnerOnUser0OrProfileOwnerOrganizationOwned();
 
         return mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) > 0;
     }
@@ -7774,12 +8065,12 @@
         if (!mHasFeature) {
             return false;
         }
-        if (parent) {
-            ActiveAdmin ap = getActiveAdminForCallerLocked(who,
-                    DeviceAdminInfo.USES_POLICY_DISABLE_CAMERA, parent);
-            enforceProfileOwnerOfOrganizationOwnedDevice(ap);
-        }
         synchronized (getLockObject()) {
+            if (parent) {
+                final ActiveAdmin ap = getActiveAdminForCallerLocked(who,
+                        DeviceAdminInfo.USES_POLICY_DISABLE_CAMERA, parent);
+                enforceProfileOwnerOfOrganizationOwnedDevice(ap);
+            }
             if (who != null) {
                 ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent);
                 return (admin != null) ? admin.disableCamera : false;
@@ -7976,10 +8267,19 @@
             updateDeviceOwnerLocked();
             setDeviceOwnerSystemPropertyLocked();
 
-            // TODO Send to system too?
-            mInjector.binderWithCleanCallingIdentity(
-                    () -> sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED,
-                            userId));
+            mInjector.binderWithCleanCallingIdentity(() -> {
+                // Restrict adding a managed profile when a device owner is set on the device.
+                // That is to prevent the co-existence of a managed profile and a device owner
+                // on the same device.
+                // Instead, the device may be provisioned with an organization-owned managed
+                // profile, such that the admin on that managed profile has extended management
+                // capabilities that can affect the entire device (but not access private data
+                // on the primary profile).
+                mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, true,
+                        UserHandle.of(userId));
+                // TODO Send to system too?
+                sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED, userId);
+            });
             mDeviceAdminServiceController.startServiceForOwner(
                     admin.getPackageName(), userId, "set-device-owner");
 
@@ -8131,6 +8431,14 @@
         return null;
     }
 
+    ActiveAdmin getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(int userId) {
+        ActiveAdmin admin = getDeviceOwnerAdminLocked();
+        if (admin == null) {
+            admin = getProfileOwnerOfOrganizationOwnedDeviceLocked(userId);
+        }
+        return admin;
+    }
+
     @Override
     public void clearDeviceOwner(String packageName) {
         Objects.requireNonNull(packageName, "packageName is null");
@@ -8234,6 +8542,17 @@
                 throw new IllegalArgumentException("Not active admin: " + who);
             }
 
+            final int parentUserId = getProfileParentId(userHandle);
+            // When trying to set a profile owner on a new user, it may be that this user is
+            // a profile - but it may not be a managed profile if there's a restriction on the
+            // parent to add managed profiles (e.g. if the device has a device owner).
+            if (parentUserId != userHandle && mUserManager.hasUserRestriction(
+                    UserManager.DISALLOW_ADD_MANAGED_PROFILE,
+                    UserHandle.of(parentUserId))) {
+                Slog.i(LOG_TAG, "Cannot set profile owner because of restriction.");
+                return false;
+            }
+
             if (isAdb()) {
                 // Log profile owner provisioning was started using adb.
                 MetricsLogger.action(mContext, PROVISIONING_ENTRY_POINT_ADB, LOG_TAG_PROFILE_OWNER);
@@ -8364,6 +8683,7 @@
         // Clear delegations.
         policy.mDelegationMap.clear();
         policy.mStatusBarDisabled = false;
+        policy.mSecondaryLockscreenEnabled = false;
         policy.mUserProvisioningState = DevicePolicyManager.STATE_USER_UNMANAGED;
         policy.mAffiliationIds.clear();
         policy.mLockTaskPackages.clear();
@@ -8611,7 +8931,6 @@
         if (!mHasFeature) {
             return false;
         }
-        enforceManageUsers();
 
         return mInjector.binderWithCleanCallingIdentity(() -> {
             for (UserInfo ui : mUserManager.getUsers()) {
@@ -8866,11 +9185,16 @@
     }
 
     private void enforceAcrossUsersPermissions() {
-        if (isCallerWithSystemUid() || mInjector.binderGetCallingUid() == Process.ROOT_UID) {
+        final int callingUid = mInjector.binderGetCallingUid();
+        final int callingPid = mInjector.binderGetCallingPid();
+        final String packageName = mContext.getPackageName();
+
+        if (isCallerWithSystemUid() || callingUid == Process.ROOT_UID) {
             return;
         }
-        if (mContext.checkCallingPermission(permission.INTERACT_ACROSS_PROFILES)
-                == PackageManager.PERMISSION_GRANTED) {
+        if (PermissionChecker.checkPermissionForPreflight(
+                mContext, permission.INTERACT_ACROSS_PROFILES, callingPid, callingUid,
+                packageName) == PermissionChecker.PERMISSION_GRANTED) {
             return;
         }
         if (mContext.checkCallingPermission(permission.INTERACT_ACROSS_USERS)
@@ -8948,23 +9272,22 @@
                 "Only profile owner, device owner and system may call this method.");
     }
 
-    private ActiveAdmin enforceDeviceOwnerOrProfileOwnerOnUser0OrProfileOwnerOrganizationOwned() {
+    private void enforceProfileOwnerOnUser0OrProfileOwnerOrganizationOwned() {
         synchronized (getLockObject()) {
-            // Check if there is a device owner
-            ActiveAdmin deviceOwner = getActiveAdminWithPolicyForUidLocked(null,
-                    DeviceAdminInfo.USES_POLICY_DEVICE_OWNER, mInjector.binderGetCallingUid());
-            if (deviceOwner != null) return deviceOwner;
+            // Check if there is a device owner or profile owner of an organization-owned device
+            ActiveAdmin owner = getActiveAdminWithPolicyForUidLocked(null,
+                    DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER,
+                    mInjector.binderGetCallingUid());
+            if (owner != null) {
+                return;
+            }
 
-            ActiveAdmin profileOwner = getActiveAdminWithPolicyForUidLocked(null,
+            // Checks whether the caller is a profile owner on user 0 rather than
+            // checking whether the active admin is on user 0
+            owner = getActiveAdminWithPolicyForUidLocked(null,
                     DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, mInjector.binderGetCallingUid());
-
-            // Check if there is a profile owner of an organization owned device
-            if (isProfileOwnerOfOrganizationOwnedDevice(profileOwner)) return profileOwner;
-
-            // Check if there is a profile owner called on user 0
-            if (profileOwner != null) {
-                enforceCallerSystemUserHandle();
-                return profileOwner;
+            if (owner != null && owner.getUserHandle().isSystem()) {
+                return;
             }
         }
         throw new SecurityException("No active admin found");
@@ -9492,7 +9815,7 @@
                 }
                 AccessibilityManager accessibilityManager = getAccessibilityManagerForUser(userId);
                 enabledServices = accessibilityManager.getEnabledAccessibilityServiceList(
-                        AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+                        FEEDBACK_ALL_MASK);
             } finally {
                 mInjector.binderRestoreCallingIdentity(id);
             }
@@ -9916,10 +10239,14 @@
                             UserHandle.myUserId(), ACTION_PROVISION_MANAGED_USER).toArray(
                             new String[0]);
                 }
-                UserInfo userInfo = mUserManagerInternal.createUserEvenWhenDisallowed(name,
-                        userType, userInfoFlags, disallowedPackages);
-                if (userInfo != null) {
-                    user = userInfo.getUserHandle();
+                try {
+                    UserInfo userInfo = mUserManagerInternal.createUserEvenWhenDisallowed(name,
+                            userType, userInfoFlags, disallowedPackages);
+                    if (userInfo != null) {
+                        user = userInfo.getUserHandle();
+                    }
+                } catch (UserManager.CheckedUserOperationException e) {
+                    Log.e(LOG_TAG, "Couldn't createUserEvenWhenDisallowed", e);
                 }
             } finally {
                 mInjector.binderRestoreCallingIdentity(id);
@@ -10410,30 +10737,35 @@
 
     @Override
     public boolean setApplicationHidden(ComponentName who, String callerPackage, String packageName,
-            boolean hidden) {
-        int callingUserId = UserHandle.getCallingUserId();
-        boolean result = false;
+            boolean hidden, boolean parent) {
+        final int userId = parent ? getProfileParentId(UserHandle.getCallingUserId())
+                : UserHandle.getCallingUserId();
+        boolean result;
+
         synchronized (getLockObject()) {
             // Ensure the caller is a DO/PO or a package access delegate.
             enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
                     DELEGATION_PACKAGE_ACCESS);
 
-            long id = mInjector.binderClearCallingIdentity();
-            try {
-                result = mIPackageManager
-                        .setApplicationHiddenSettingAsUser(packageName, hidden, callingUserId);
-            } catch (RemoteException re) {
-                // shouldn't happen
-                Slog.e(LOG_TAG, "Failed to setApplicationHiddenSetting", re);
-            } finally {
-                mInjector.binderRestoreCallingIdentity(id);
+            if (parent) {
+                getActiveAdminForCallerLocked(who,
+                        DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER, parent);
+                // Ensure the package provided is a system package, this is to ensure that this
+                // API cannot be used to leak if certain non-system package exists in the person
+                // profile.
+                mInjector.binderWithCleanCallingIdentity(() ->
+                        enforcePackageIsSystemPackage(packageName, hidden, userId));
             }
+
+            result = mInjector.binderWithCleanCallingIdentity(() -> mIPackageManager
+                    .setApplicationHiddenSettingAsUser(packageName, hidden, userId));
         }
         final boolean isDelegate = (who == null);
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_APPLICATION_HIDDEN)
                 .setAdmin(callerPackage)
                 .setBoolean(isDelegate)
+                .setBoolean(parent)
                 .setStrings(packageName, hidden ? "hidden" : "not_hidden")
                 .write();
         return result;
@@ -10441,24 +10773,40 @@
 
     @Override
     public boolean isApplicationHidden(ComponentName who, String callerPackage,
-            String packageName) {
-        int callingUserId = UserHandle.getCallingUserId();
+            String packageName, boolean parent) {
+        final int userId = parent ? getProfileParentId(UserHandle.getCallingUserId())
+                : UserHandle.getCallingUserId();
+
         synchronized (getLockObject()) {
             // Ensure the caller is a DO/PO or a package access delegate.
             enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
                     DELEGATION_PACKAGE_ACCESS);
 
-            long id = mInjector.binderClearCallingIdentity();
-            try {
-                return mIPackageManager.getApplicationHiddenSettingAsUser(
-                        packageName, callingUserId);
-            } catch (RemoteException re) {
-                // shouldn't happen
-                Slog.e(LOG_TAG, "Failed to getApplicationHiddenSettingAsUser", re);
-            } finally {
-                mInjector.binderRestoreCallingIdentity(id);
+            if (parent) {
+                getActiveAdminForCallerLocked(who,
+                        DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER, parent);
+                // Ensure the package provided is a system package.
+                mInjector.binderWithCleanCallingIdentity(() ->
+                        enforcePackageIsSystemPackage(packageName, false, userId));
             }
-            return false;
+
+            return mInjector.binderWithCleanCallingIdentity(
+                    () -> mIPackageManager.getApplicationHiddenSettingAsUser(packageName, userId));
+        }
+    }
+
+    private void enforcePackageIsSystemPackage(String packageName, boolean hidden, int userId)
+            throws RemoteException {
+        int flags = PackageManager.MATCH_SYSTEM_ONLY;
+        // If the package is currently hidden then it is considered uninstalled and
+        // the MATCH_UNINSTALLED_PACKAGES flag has to be added.
+        if (!hidden) {
+            flags |= PackageManager.MATCH_UNINSTALLED_PACKAGES;
+        }
+        PackageInfo packageInfo = mIPackageManager.getPackageInfo(packageName, flags, userId);
+        if (packageInfo == null || !packageInfo.applicationInfo.isSystemApp()) {
+            throw new IllegalArgumentException(
+                    "The provided package is not a system package");
         }
     }
 
@@ -10910,6 +11258,33 @@
     }
 
     @Override
+    public void setSecondaryLockscreenEnabled(ComponentName who, boolean enabled) {
+        enforceCanSetSecondaryLockscreenEnabled(who);
+        synchronized (getLockObject()) {
+            final int userId = mInjector.userHandleGetCallingUserId();
+            DevicePolicyData policy = getUserData(userId);
+            policy.mSecondaryLockscreenEnabled = enabled;
+            saveSettingsLocked(userId);
+        }
+    }
+
+    @Override
+    public boolean isSecondaryLockscreenEnabled(int userId) {
+        synchronized (getLockObject()) {
+            return getUserData(userId).mSecondaryLockscreenEnabled;
+        }
+    }
+
+    private void enforceCanSetSecondaryLockscreenEnabled(ComponentName who) {
+        enforceProfileOrDeviceOwner(who);
+        final int userId = mInjector.userHandleGetCallingUserId();
+        if (isManagedProfile(userId)) {
+            throw new SecurityException(
+                    "User " + userId + " is not allowed to call setSecondaryLockscreenEnabled");
+        }
+    }
+
+    @Override
     public void setLockTaskPackages(ComponentName who, String[] packages)
             throws SecurityException {
         Objects.requireNonNull(who, "ComponentName is null");
@@ -11109,6 +11484,37 @@
     }
 
     @Override
+    public void setLockdownAdminConfiguredNetworks(ComponentName who, boolean lockdown) {
+        if (!mHasFeature) {
+            return;
+        }
+        Preconditions.checkNotNull(who, "ComponentName is null");
+        enforceDeviceOwnerOrProfileOwnerOnOrganizationOwnedDevice(who);
+
+        mInjector.binderWithCleanCallingIdentity(() ->
+                mInjector.settingsGlobalPutInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN,
+                        lockdown ? 1 : 0));
+
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.ALLOW_MODIFICATION_OF_ADMIN_CONFIGURED_NETWORKS)
+                .setAdmin(who)
+                .setBoolean(lockdown)
+                .write();
+    }
+
+    @Override
+    public boolean isLockdownAdminConfiguredNetworks(ComponentName who) {
+        if (!mHasFeature) {
+            return false;
+        }
+        Preconditions.checkNotNull(who, "ComponentName is null");
+        enforceDeviceOwnerOrProfileOwnerOnOrganizationOwnedDevice(who);
+
+        return mInjector.binderWithCleanCallingIdentity(() ->
+                mInjector.settingsGlobalGetInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) > 0);
+    }
+
+    @Override
     public void setLocationEnabled(ComponentName who, boolean locationEnabled) {
         Objects.requireNonNull(who, "ComponentName is null");
         enforceDeviceOwner(who);
@@ -11723,6 +12129,11 @@
             return mStateCache;
         }
 
+        @Override
+        public List<String> getAllCrossProfilePackages() {
+            return DevicePolicyManagerService.this.getAllCrossProfilePackages();
+        }
+
     }
 
     private Intent createShowAdminSupportIntent(ComponentName admin, int userId) {
@@ -12066,6 +12477,14 @@
                             true);
                 }
 
+                // Prevent granting location-related permissions without user consent.
+                if (LOCATION_PERMISSIONS.contains(permission)
+                        && grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
+                        && !isUnattendedManagedKioskUnchecked()) {
+                    callback.sendResult(null);
+                    return;
+                }
+
                 if (grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
                         || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
                         || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) {
@@ -12207,6 +12626,7 @@
                 case DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE:
                     return checkManagedProfileProvisioningPreCondition(packageName, callingUserId);
                 case DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE:
+                case DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE:
                     return checkDeviceOwnerProvisioningPreCondition(callingUserId);
                 case DevicePolicyManager.ACTION_PROVISION_MANAGED_USER:
                     return checkManagedUserProvisioningPreCondition(callingUserId);
@@ -12293,25 +12713,41 @@
         final long ident = mInjector.binderClearCallingIdentity();
         try {
             final UserHandle callingUserHandle = UserHandle.of(callingUserId);
-            final ComponentName ownerAdmin = getOwnerComponent(packageName, callingUserId);
-            if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE,
-                    callingUserHandle)) {
-                // An admin can initiate provisioning if it has set the restriction.
-                if (ownerAdmin == null || isAdminAffectedByRestriction(ownerAdmin,
-                        UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserId)) {
-                    return CODE_ADD_MANAGED_PROFILE_DISALLOWED;
-                }
+            final boolean hasDeviceOwner;
+            synchronized (getLockObject()) {
+                hasDeviceOwner = getDeviceOwnerAdminLocked() != null;
             }
-            boolean canRemoveProfile = true;
-            if (mUserManager.hasUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
-                    callingUserHandle)) {
-                // We can remove a profile if the admin itself has set the restriction.
-                if (ownerAdmin == null || isAdminAffectedByRestriction(ownerAdmin,
-                        UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
-                        callingUserId)) {
-                    canRemoveProfile = false;
-                }
+
+            final boolean addingProfileRestricted = mUserManager.hasUserRestriction(
+                    UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserHandle);
+
+            UserInfo parentUser = mUserManager.getProfileParent(callingUserId);
+            final boolean addingProfileRestrictedOnParent = (parentUser != null)
+                    && mUserManager.hasUserRestriction(
+                            UserManager.DISALLOW_ADD_MANAGED_PROFILE,
+                            UserHandle.of(parentUser.id));
+
+            Slog.i(LOG_TAG, String.format(
+                    "When checking for managed profile provisioning: Has device owner? %b, adding"
+                            + " profile restricted? %b, adding profile restricted on parent? %b",
+                    hasDeviceOwner, addingProfileRestricted, addingProfileRestrictedOnParent));
+
+            // If there's a device owner, the restriction on adding a managed profile must be set
+            // somewhere.
+            if (hasDeviceOwner && !addingProfileRestricted && !addingProfileRestrictedOnParent) {
+                Slog.wtf(LOG_TAG, "Has a device owner but no restriction on adding a profile.");
             }
+
+            // Do not allow adding a managed profile if there's a restriction, either on the current
+            // user or its parent user.
+            if (addingProfileRestricted || addingProfileRestrictedOnParent) {
+                return CODE_CANNOT_ADD_MANAGED_PROFILE;
+            }
+            // If there's a restriction on removing the managed profile then we have to take it
+            // into account when checking whether more profiles can be added.
+            boolean canRemoveProfile =
+                    !mUserManager.hasUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
+                    callingUserHandle);
             if (!mUserManager.canAddMoreManagedProfiles(callingUserId, canRemoveProfile)) {
                 return CODE_CANNOT_ADD_MANAGED_PROFILE;
             }
@@ -12788,37 +13224,43 @@
 
         // Grant access under lock.
         synchronized (getLockObject()) {
-            // Sanity check: Make sure that the user has a profile owner and that the specified
-            // component is the profile owner of that user.
-            if (!isProfileOwner(who, userId)) {
-                throw new IllegalArgumentException(String.format(
-                        "Component %s is not a Profile Owner of user %d",
-                        who.flattenToString(), userId));
-            }
+            markProfileOwnerOnOrganizationOwnedDeviceUncheckedLocked(who, userId);
+        }
+    }
 
-            Slog.i(LOG_TAG, String.format(
-                    "Marking %s as profile owner on organization-owned device for user %d",
+    @GuardedBy("getLockObject()")
+    private void markProfileOwnerOnOrganizationOwnedDeviceUncheckedLocked(
+            ComponentName who, int userId) {
+        // Sanity check: Make sure that the user has a profile owner and that the specified
+        // component is the profile owner of that user.
+        if (!isProfileOwner(who, userId)) {
+            throw new IllegalArgumentException(String.format(
+                    "Component %s is not a Profile Owner of user %d",
                     who.flattenToString(), userId));
+        }
 
-            // First, set restriction on removing the profile.
-            mInjector.binderWithCleanCallingIdentity(() -> {
-                // Clear restriction as user.
-                UserHandle parentUser = mUserManager.getProfileParent(UserHandle.of(userId));
-                if (!parentUser.isSystem()) {
-                    throw new IllegalStateException(
-                            String.format("Only the profile owner of a managed profile on the"
+        Slog.i(LOG_TAG, String.format(
+                "Marking %s as profile owner on organization-owned device for user %d",
+                who.flattenToString(), userId));
+
+        // First, set restriction on removing the profile.
+        mInjector.binderWithCleanCallingIdentity(() -> {
+            // Clear restriction as user.
+            final UserHandle parentUser = mUserManager.getProfileParent(UserHandle.of(userId));
+            if (!parentUser.isSystem()) {
+                throw new IllegalStateException(
+                        String.format("Only the profile owner of a managed profile on the"
                                 + " primary user can be granted access to device identifiers, not"
                                 + " on user %d", parentUser.getIdentifier()));
-                }
+            }
 
-                mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, true,
-                        parentUser);
-            });
+            mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, true,
+                    parentUser);
+        });
 
-            // markProfileOwnerOfOrganizationOwnedDevice will trigger writing of the profile owner
-            // data, no need to do it manually.
-            mOwners.markProfileOwnerOfOrganizationOwnedDevice(userId);
-        }
+        // markProfileOwnerOfOrganizationOwnedDevice will trigger writing of the profile owner
+        // data, no need to do it manually.
+        mOwners.markProfileOwnerOfOrganizationOwnedDevice(userId);
     }
 
     private void pushMeteredDisabledPackagesLocked(int userId) {
@@ -14543,12 +14985,18 @@
         }
         Objects.requireNonNull(who, "ComponentName is null");
         Objects.requireNonNull(packageNames, "Package names is null");
+        final List<String> previousCrossProfilePackages;
         synchronized (getLockObject()) {
             final ActiveAdmin admin =
                     getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            previousCrossProfilePackages = admin.mCrossProfilePackages;
             admin.mCrossProfilePackages = packageNames;
             saveSettingsLocked(mInjector.userHandleGetCallingUserId());
         }
+        final CrossProfileApps crossProfileApps = mContext.getSystemService(CrossProfileApps.class);
+        mInjector.binderWithCleanCallingIdentity(
+                () -> crossProfileApps.resetInteractAcrossProfilesAppOps(
+                        previousCrossProfilePackages, new HashSet<>(packageNames)));
     }
 
     @Override
@@ -14628,21 +15076,22 @@
         }
     }
 
+    private boolean isUnattendedManagedKioskUnchecked() {
+        try {
+            return isManagedKioskInternal()
+                    && getPowerManagerInternal().wasDeviceIdleFor(UNATTENDED_MANAGED_KIOSK_MS);
+        } catch (RemoteException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
     @Override
     public boolean isUnattendedManagedKiosk() {
         if (!mHasFeature) {
             return false;
         }
         enforceManageUsers();
-        long id = mInjector.binderClearCallingIdentity();
-        try {
-            return isManagedKioskInternal()
-                    && getPowerManagerInternal().wasDeviceIdleFor(UNATTENDED_MANAGED_KIOSK_MS);
-        } catch (RemoteException e) {
-            throw new IllegalStateException(e);
-        } finally {
-            mInjector.binderRestoreCallingIdentity(id);
-        }
+        return mInjector.binderWithCleanCallingIdentity(() -> isUnattendedManagedKioskUnchecked());
     }
 
     /**
@@ -14782,4 +15231,152 @@
             return packages == null ? Collections.EMPTY_LIST : packages;
         }
     }
+
+    private void logIfVerbose(String message) {
+        if (VERBOSE_LOG) {
+            Slog.d(LOG_TAG, message);
+        }
+    }
+
+    @Override
+    public void setCommonCriteriaModeEnabled(ComponentName admin, boolean enabled) {
+        synchronized (getLockObject()) {
+            getActiveAdminForCallerLocked(admin,
+                    DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER);
+        }
+        mInjector.binderWithCleanCallingIdentity(
+                () -> mInjector.settingsGlobalPutInt(Settings.Global.COMMON_CRITERIA_MODE,
+                        enabled ? 1 : 0));
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_COMMON_CRITERIA_MODE)
+                .setAdmin(admin)
+                .setBoolean(enabled)
+                .write();
+    }
+
+    @Override
+    public boolean isCommonCriteriaModeEnabled(ComponentName admin) {
+        synchronized (getLockObject()) {
+            getActiveAdminForCallerLocked(admin,
+                    DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER);
+        }
+        return mInjector.settingsGlobalGetInt(Settings.Global.COMMON_CRITERIA_MODE, 0) != 0;
+    }
+
+    @Override
+    public @PersonalAppSuspensionReason int getPersonalAppsSuspendedReasons(ComponentName who) {
+        synchronized (getLockObject()) {
+            final ActiveAdmin admin = getActiveAdminForCallerLocked(who,
+                    DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER,
+                    false /* parent */);
+            // DO shouldn't be able to use this method.
+            enforceProfileOwnerOfOrganizationOwnedDevice(admin);
+            if (admin.mSuspendPersonalApps) {
+                return DevicePolicyManager.PERSONAL_APPS_SUSPENDED_EXPLICITLY;
+            } else {
+                return DevicePolicyManager.PERSONAL_APPS_NOT_SUSPENDED;
+            }
+        }
+    }
+
+    @Override
+    public void setPersonalAppsSuspended(ComponentName who, boolean suspended) {
+        final int callingUserId = mInjector.userHandleGetCallingUserId();
+        synchronized (getLockObject()) {
+            final ActiveAdmin admin = getActiveAdminForCallerLocked(who,
+                    DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER,
+                    false /* parent */);
+            // DO shouldn't be able to use this method.
+            enforceProfileOwnerOfOrganizationOwnedDevice(admin);
+            if (admin.mSuspendPersonalApps != suspended) {
+                admin.mSuspendPersonalApps = suspended;
+                saveSettingsLocked(callingUserId);
+            }
+        }
+
+        if (getUserData(UserHandle.USER_SYSTEM).mPersonalAppsSuspended == suspended) {
+            // Admin request matches current state, nothing to do.
+            return;
+        }
+
+        suspendPersonalAppsInternal(suspended, UserHandle.USER_SYSTEM);
+
+        mInjector.binderWithCleanCallingIdentity(() -> {
+            if (suspended) {
+                sendPersonalAppsSuspendedNotification(callingUserId);
+            } else {
+                clearPersonalAppsSuspendedNotification(callingUserId);
+            }
+        });
+    }
+
+    private void suspendPersonalAppsInternal(boolean suspended, int userId) {
+        Slog.i(LOG_TAG, String.format("%s personal apps for user %d",
+                suspended ? "Suspending" : "Unsuspending", userId));
+        mInjector.binderWithCleanCallingIdentity(() -> {
+            try {
+                final String[] appsToSuspend =
+                        new PersonalAppsSuspensionHelper(mContext, mInjector.getPackageManager())
+                                .getPersonalAppsForSuspension(userId);
+                final String[] failedPackages = mIPackageManager.setPackagesSuspendedAsUser(
+                        appsToSuspend, suspended, null, null, null, PLATFORM_PACKAGE_NAME, userId);
+                if (!ArrayUtils.isEmpty(failedPackages)) {
+                    Slog.wtf(LOG_TAG, String.format("Failed to %s packages: %s",
+                            suspended ? "suspend" : "unsuspend", String.join(",", failedPackages)));
+                }
+            } catch (RemoteException re) {
+                // Shouldn't happen.
+                Slog.e(LOG_TAG, "Failed talking to the package manager", re);
+            }
+        });
+
+        synchronized (getLockObject()) {
+            getUserData(userId).mPersonalAppsSuspended = suspended;
+            saveSettingsLocked(userId);
+        }
+    }
+
+    private void maybeUpdatePersonalAppsSuspendedNotification(int profileUserId) {
+        // TODO(b/147414651): Unless updated, the notification stops working after turning the
+        //  profile off and back on, so it has to be updated more often than necessary.
+        if (getUserData(UserHandle.USER_SYSTEM).mPersonalAppsSuspended
+                && getProfileParentId(profileUserId) == UserHandle.USER_SYSTEM) {
+            sendPersonalAppsSuspendedNotification(profileUserId);
+        }
+    }
+
+    private void clearPersonalAppsSuspendedNotification(int userId) {
+        mInjector.binderWithCleanCallingIdentity(() ->
+                mInjector.getNotificationManager().cancel(
+                        SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED));
+    }
+
+    private void sendPersonalAppsSuspendedNotification(int userId) {
+        final String profileOwnerPackageName;
+        synchronized (getLockObject()) {
+            profileOwnerPackageName = mOwners.getProfileOwnerComponent(userId).getPackageName();
+        }
+
+        final Intent intent = new Intent(DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE);
+        intent.setPackage(profileOwnerPackageName);
+
+        final PendingIntent pendingIntent = mInjector.pendingIntentGetActivityAsUser(mContext,
+                0 /* requestCode */, intent, PendingIntent.FLAG_UPDATE_CURRENT, null /* options */,
+                UserHandle.of(userId));
+
+        final Notification notification =
+                new Notification.Builder(mContext, SystemNotificationChannels.DEVICE_ADMIN)
+                        .setSmallIcon(android.R.drawable.stat_sys_warning)
+                        .setOngoing(true)
+                        .setContentTitle(
+                                mContext.getString(
+                                        R.string.personal_apps_suspended_notification_title))
+                        .setContentText(mContext.getString(
+                                R.string.personal_apps_suspended_notification_text))
+                        .setColor(mContext.getColor(R.color.system_notification_accent_color))
+                        .setContentIntent(pendingIntent)
+                        .build();
+        mInjector.getNotificationManager().notify(
+                SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED, notification);
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
new file mode 100644
index 0000000..180acc8
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
@@ -0,0 +1,201 @@
+/*
+ * 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.devicepolicy;
+
+import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.IBinder;
+import android.os.ServiceManager;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Slog;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.IAccessibilityManager;
+import android.view.inputmethod.InputMethodInfo;
+
+import com.android.internal.R;
+import com.android.server.inputmethod.InputMethodManagerInternal;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Utility class to find what personal apps should be suspended to limit personal device use.
+ */
+public class PersonalAppsSuspensionHelper {
+    private static final String LOG_TAG = DevicePolicyManagerService.LOG_TAG;
+
+    private final Context mContext;
+    private final PackageManager mPackageManager;
+
+    public PersonalAppsSuspensionHelper(Context context, PackageManager packageManager) {
+        mContext = context;
+        mPackageManager = packageManager;
+    }
+
+    /**
+     * @return List of packages that should be suspended to limit personal use.
+     */
+    String[] getPersonalAppsForSuspension(@UserIdInt int userId) {
+        final List<PackageInfo> installedPackageInfos =
+                mPackageManager.getInstalledPackagesAsUser(0 /* flags */, userId);
+        final Set<String> result = new HashSet<>();
+        for (final PackageInfo packageInfo : installedPackageInfos) {
+            final ApplicationInfo info = packageInfo.applicationInfo;
+            if ((!info.isSystemApp() && !info.isUpdatedSystemApp())
+                    || hasLauncherIntent(packageInfo.packageName)) {
+                result.add(packageInfo.packageName);
+            }
+        }
+        result.removeAll(getCriticalPackages());
+        result.removeAll(getSystemLauncherPackages());
+        result.removeAll(getAccessibilityServices(userId));
+        result.removeAll(getInputMethodPackages(userId));
+        result.remove(getActiveLauncherPackages(userId));
+        result.remove(getDialerPackage(userId));
+        result.remove(getSettingsPackageName(userId));
+
+        Slog.i(LOG_TAG, "Packages subject to suspension: " + String.join(",", result));
+        return result.toArray(new String[0]);
+    }
+
+    private List<String> getSystemLauncherPackages() {
+        final List<String> result = new ArrayList<>();
+        final Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.addCategory(Intent.CATEGORY_HOME);
+        final List<ResolveInfo> matchingActivities =
+                mPackageManager.queryIntentActivities(intent, 0);
+        for (final ResolveInfo resolveInfo : matchingActivities) {
+            if (resolveInfo.activityInfo == null
+                    || TextUtils.isEmpty(resolveInfo.activityInfo.packageName)) {
+                Slog.wtf(LOG_TAG, "Could not find package name for launcher app" + resolveInfo);
+                continue;
+            }
+            final String packageName = resolveInfo.activityInfo.packageName;
+            try {
+                final ApplicationInfo applicationInfo =
+                        mPackageManager.getApplicationInfo(packageName, 0);
+                if (applicationInfo.isSystemApp() || applicationInfo.isUpdatedSystemApp()) {
+                    Log.d(LOG_TAG, "Not suspending system launcher package: " + packageName);
+                    result.add(packageName);
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                Slog.e(LOG_TAG, "Could not find application info for launcher app: " + packageName);
+            }
+        }
+        return result;
+    }
+
+    private List<String> getAccessibilityServices(int userId) {
+        final List<AccessibilityServiceInfo> accessibilityServiceInfos =
+                getAccessibilityManagerForUser(userId)
+                        .getEnabledAccessibilityServiceList(FEEDBACK_ALL_MASK);
+        final List<String> result = new ArrayList<>();
+        for (final AccessibilityServiceInfo serviceInfo : accessibilityServiceInfos) {
+            final ComponentName componentName =
+                    ComponentName.unflattenFromString(serviceInfo.getId());
+            if (componentName != null) {
+                final String packageName = componentName.getPackageName();
+                Slog.d(LOG_TAG, "Not suspending a11y service: " + packageName);
+                result.add(packageName);
+            }
+        }
+        return result;
+    }
+
+    private List<String> getInputMethodPackages(int userId) {
+        final List<InputMethodInfo> enabledImes =
+                InputMethodManagerInternal.get().getEnabledInputMethodListAsUser(userId);
+        final List<String> result = new ArrayList<>();
+        for (final InputMethodInfo info : enabledImes) {
+            Slog.d(LOG_TAG, "Not suspending IME: " + info.getPackageName());
+            result.add(info.getPackageName());
+        }
+        return result;
+    }
+
+    @Nullable
+    private String getActiveLauncherPackages(int userId) {
+        final Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.addCategory(Intent.CATEGORY_HOME);
+        intent.addCategory(Intent.CATEGORY_DEFAULT);
+        return getPackageNameForIntent("active launcher", intent, userId);
+    }
+
+    @Nullable
+    private String getSettingsPackageName(int userId) {
+        final Intent intent = new Intent(Settings.ACTION_SETTINGS);
+        intent.addCategory(Intent.CATEGORY_DEFAULT);
+        return getPackageNameForIntent("settings", intent, userId);
+    }
+
+    @Nullable
+    private String getDialerPackage(int userId) {
+        final Intent intent = new Intent(Intent.ACTION_DIAL);
+        intent.addCategory(Intent.CATEGORY_DEFAULT);
+        return getPackageNameForIntent("dialer", intent, userId);
+    }
+
+    @Nullable
+    private String getPackageNameForIntent(String name, Intent intent, int userId) {
+        final ResolveInfo resolveInfo =
+                mPackageManager.resolveActivityAsUser(intent, /* flags= */ 0, userId);
+        if (resolveInfo != null) {
+            final String packageName = resolveInfo.activityInfo.packageName;
+            Slog.d(LOG_TAG, "Not suspending " + name + " package: " + packageName);
+            return packageName;
+        }
+        return null;
+    }
+
+    private List<String> getCriticalPackages() {
+        final List<String> result = Arrays.asList(mContext.getResources()
+                .getStringArray(R.array.config_packagesExemptFromSuspension));
+        Slog.d(LOG_TAG, "Not suspending critical packages: " + String.join(",", result));
+        return result;
+    }
+
+    private boolean hasLauncherIntent(String packageName) {
+        final Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
+        intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
+        intentToResolve.setPackage(packageName);
+        final List<ResolveInfo> resolveInfos = mPackageManager.queryIntentActivities(
+                intentToResolve, PackageManager.GET_UNINSTALLED_PACKAGES);
+        return resolveInfos != null && !resolveInfos.isEmpty();
+    }
+
+    private AccessibilityManager getAccessibilityManagerForUser(int userId) {
+        final IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
+        final IAccessibilityManager service =
+                iBinder == null ? null : IAccessibilityManager.Stub.asInterface(iBinder);
+        return new AccessibilityManager(mContext, service, userId);
+    }
+}
diff --git a/services/java/com/android/server/SystemConfigService.java b/services/java/com/android/server/SystemConfigService.java
new file mode 100644
index 0000000..e8ab101
--- /dev/null
+++ b/services/java/com/android/server/SystemConfigService.java
@@ -0,0 +1,62 @@
+/*
+ * 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;
+
+import android.Manifest;
+import android.content.Context;
+import android.os.ISystemConfig;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Service class that runs inside the system_server process to handle queries to
+ * {@link com.android.server.SystemConfig}.
+ * @hide
+ */
+public class SystemConfigService extends SystemService {
+    private final Context mContext;
+
+    private final ISystemConfig.Stub mInterface = new ISystemConfig.Stub() {
+        @Override
+        public List<String> getDisabledUntilUsedPreinstalledCarrierApps() {
+            mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_CARRIER_APP_INFO,
+                    "getDisabledUntilUsedPreInstalledCarrierApps requires READ_CARRIER_APP_INFO");
+            return new ArrayList<>(
+                    SystemConfig.getInstance().getDisabledUntilUsedPreinstalledCarrierApps());
+        }
+
+        @Override
+        public Map getDisabledUntilUsedPreinstalledCarrierAssociatedApps() {
+            mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_CARRIER_APP_INFO,
+                    "getDisabledUntilUsedPreInstalledCarrierAssociatedApps requires"
+                            + " READ_CARRIER_APP_INFO");
+            return SystemConfig.getInstance()
+                    .getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
+        }
+    };
+
+    public SystemConfigService(Context context) {
+        super(context);
+        mContext = context;
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService(Context.SYSTEM_CONFIG_SERVICE, mInterface);
+    }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index bfec51c..258d762 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -71,11 +71,11 @@
 import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.Slog;
+import android.util.StatsLog;
 import android.view.WindowManager;
 import android.view.contentcapture.ContentCaptureManager;
 
 import com.android.internal.R;
-import com.android.internal.logging.MetricsLogger;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.os.BinderInternal;
 import com.android.internal.util.ConcurrentUtils;
@@ -213,10 +213,12 @@
             "com.android.server.print.PrintManagerService";
     private static final String COMPANION_DEVICE_MANAGER_SERVICE_CLASS =
             "com.android.server.companion.CompanionDeviceManagerService";
+    private static final String STATS_COMPANION_APEX_PATH =
+            "/apex/com.android.os.statsd/javalib/service-statsd.jar";
     private static final String STATS_COMPANION_LIFECYCLE_CLASS =
             "com.android.server.stats.StatsCompanion$Lifecycle";
     private static final String STATS_PULL_ATOM_SERVICE_CLASS =
-            "com.android.server.stats.StatsPullAtomService";
+            "com.android.server.stats.pull.StatsPullAtomService";
     private static final String USB_SERVICE_CLASS =
             "com.android.server.usb.UsbService$Lifecycle";
     private static final String MIDI_SERVICE_CLASS =
@@ -441,10 +443,12 @@
 
             // Here we go!
             Slog.i(TAG, "Entered the Android system server!");
-            int uptimeMillis = (int) SystemClock.elapsedRealtime();
+            final long uptimeMillis = SystemClock.elapsedRealtime();
             EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_SYSTEM_RUN, uptimeMillis);
             if (!mRuntimeRestart) {
-                MetricsLogger.histogram(null, "boot_system_server_init", uptimeMillis);
+                StatsLog.write(StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
+                        StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__SYSTEM_SERVER_INIT_START,
+                        uptimeMillis);
             }
 
             // In case the runtime switched since last boot (such as when
@@ -553,10 +557,12 @@
         StrictMode.initVmDefaults(null);
 
         if (!mRuntimeRestart && !isFirstBootOrUpgrade()) {
-            int uptimeMillis = (int) SystemClock.elapsedRealtime();
-            MetricsLogger.histogram(null, "boot_system_server_ready", uptimeMillis);
-            final int MAX_UPTIME_MILLIS = 60 * 1000;
-            if (uptimeMillis > MAX_UPTIME_MILLIS) {
+            final long uptimeMillis = SystemClock.elapsedRealtime();
+            StatsLog.write(StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
+                    StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__SYSTEM_SERVER_READY,
+                    uptimeMillis);
+            final long maxUptimeMillis = 60 * 1000;
+            if (uptimeMillis > maxUptimeMillis) {
                 Slog.wtf(SYSTEM_SERVER_TIMING_TAG,
                         "SystemServer init took too long. uptimeMillis=" + uptimeMillis);
             }
@@ -751,7 +757,8 @@
         // Now that we have the bare essentials of the OS up and running, take
         // note that we just booted, which might send out a rescue party if
         // we're stuck in a runtime restart loop.
-        RescueParty.noteBoot(mSystemContext);
+        RescueParty.registerHealthObserver(mSystemContext);
+        PackageWatchdog.getInstance(mSystemContext).noteBoot();
 
         // Manages LEDs and display backlight so we need it to bring up the display.
         t.traceBegin("StartLightsService");
@@ -788,8 +795,9 @@
 
         // Start the package manager.
         if (!mRuntimeRestart) {
-            MetricsLogger.histogram(null, "boot_package_manager_init_start",
-                    (int) SystemClock.elapsedRealtime());
+            StatsLog.write(StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
+                    StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__PACKAGE_MANAGER_INIT_START,
+                    SystemClock.elapsedRealtime());
         }
 
         t.traceBegin("StartPackageManagerService");
@@ -805,8 +813,9 @@
         mPackageManager = mSystemContext.getPackageManager();
         t.traceEnd();
         if (!mRuntimeRestart && !isFirstBootOrUpgrade()) {
-            MetricsLogger.histogram(null, "boot_package_manager_init_ready",
-                    (int) SystemClock.elapsedRealtime());
+            StatsLog.write(StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
+                    StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__PACKAGE_MANAGER_INIT_READY,
+                    SystemClock.elapsedRealtime());
         }
         // Manages A/B OTA dexopting. This is a bootstrap service as we need it to rename
         // A/B artifacts after boot, before anything else might touch/need them.
@@ -887,6 +896,11 @@
     private void startCoreServices(@NonNull TimingsTraceAndSlog t) {
         t.traceBegin("startCoreServices");
 
+        // Service for system config
+        t.traceBegin("StartSystemConfigService");
+        mSystemServiceManager.startService(SystemConfigService.class);
+        t.traceEnd();
+
         t.traceBegin("StartBatteryService");
         // Tracks the battery level.  Requires LightService.
         mSystemServiceManager.startService(BatteryService.class);
@@ -1746,7 +1760,7 @@
             if (!isWatch && !disableNetworkTime) {
                 t.traceBegin("StartNetworkTimeUpdateService");
                 try {
-                    networkTimeUpdater = new NetworkTimeUpdateServiceImpl(context);
+                    networkTimeUpdater = new NetworkTimeUpdateService(context);
                     ServiceManager.addService("network_time_update_service", networkTimeUpdater);
                 } catch (Throwable e) {
                     reportWtf("starting NetworkTimeUpdate service", e);
@@ -1979,7 +1993,8 @@
 
         // Statsd helper
         t.traceBegin("StartStatsCompanion");
-        mSystemServiceManager.startService(STATS_COMPANION_LIFECYCLE_CLASS);
+        mSystemServiceManager.startServiceFromJar(
+                STATS_COMPANION_LIFECYCLE_CLASS, STATS_COMPANION_APEX_PATH);
         t.traceEnd();
 
         // Statsd pulled atoms
diff --git a/services/net/Android.bp b/services/net/Android.bp
index 9c7cfc1..dbc2df8 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -16,6 +16,7 @@
         "netd_aidl_interface-unstable-java",
         "netlink-client",
         "networkstack-client",
+        "net-utils-services-common",
     ],
 }
 
@@ -23,7 +24,6 @@
     name: "services-tethering-shared-srcs",
     srcs: [
         ":framework-annotations",
-        "java/android/net/util/NetdService.java",
         "java/android/net/util/NetworkConstants.java",
     ],
     visibility: ["//frameworks/base/packages/Tethering"],
diff --git a/services/net/java/android/net/ip/IpClientCallbacks.java b/services/net/java/android/net/ip/IpClientCallbacks.java
index 61cd88a..c93e5c5 100644
--- a/services/net/java/android/net/ip/IpClientCallbacks.java
+++ b/services/net/java/android/net/ip/IpClientCallbacks.java
@@ -17,6 +17,7 @@
 package android.net.ip;
 
 import android.net.DhcpResults;
+import android.net.DhcpResultsParcelable;
 import android.net.Layer2PacketParcelable;
 import android.net.LinkProperties;
 
@@ -69,6 +70,18 @@
     public void onNewDhcpResults(DhcpResults dhcpResults) {}
 
     /**
+     * Callback called when new DHCP results are available.
+     *
+     * <p>This is purely advisory and not an indication of provisioning success or failure.  This is
+     * only here for callers that want to expose DHCPv4 results to other APIs
+     * (e.g., WifiInfo#setInetAddress).
+     *
+     * <p>DHCPv4 or static IPv4 configuration failure or success can be determined by whether or not
+     * the passed-in DhcpResults object is null.
+     */
+    public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) {}
+
+    /**
      * Indicates that provisioning was successful.
      */
     public void onProvisioningSuccess(LinkProperties newLp) {}
diff --git a/services/net/java/android/net/ip/IpClientUtil.java b/services/net/java/android/net/ip/IpClientUtil.java
index 4d60e62..7f723b1 100644
--- a/services/net/java/android/net/ip/IpClientUtil.java
+++ b/services/net/java/android/net/ip/IpClientUtil.java
@@ -119,6 +119,7 @@
         @Override
         public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) {
             mCb.onNewDhcpResults(fromStableParcelable(dhcpResults));
+            mCb.onNewDhcpResults(dhcpResults);
         }
 
         @Override
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index ef3da60..9569c6e 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -137,5 +137,14 @@
                 Slog.e(TAG, "Failed to calling callback" + e);
             }
         }
+
+        @Override
+        public byte[] backupConversationInfos(int userId) {
+            return new byte[0];
+        }
+
+        @Override
+        public void restoreConversationInfos(int userId, String key, byte[] payload) {
+        }
     }
 }
diff --git a/services/robotests/Android.bp b/services/robotests/Android.bp
index 3ce514a..d2f86ee 100644
--- a/services/robotests/Android.bp
+++ b/services/robotests/Android.bp
@@ -43,6 +43,9 @@
         "platform-test-annotations",
         "testng",
     ],
+    static_libs: [
+        "androidx.test.ext.truth",
+    ],
 
     instrumentation_for: "FrameworksServicesLib",
 }
diff --git a/services/robotests/backup/Android.bp b/services/robotests/backup/Android.bp
index 9d384e9..ef0ca66 100644
--- a/services/robotests/backup/Android.bp
+++ b/services/robotests/backup/Android.bp
@@ -44,8 +44,10 @@
 
     // Include the testing libraries
     libs: [
+        "mockito-robolectric-prebuilt",
         "platform-test-annotations",
         "testng",
+        "truth-prebuilt",
     ],
 
     instrumentation_for: "BackupFrameworksServicesLib",
diff --git a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
index 8632ca4..8b2f15c 100644
--- a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -81,7 +81,10 @@
 import org.robolectric.shadows.ShadowPackageManager;
 
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.List;
 
 /**
@@ -1238,13 +1241,49 @@
         assertThat(service.getAncestralSerialNumber()).isEqualTo(testSerialNumber2);
     }
 
+    /**
+     * Test that {@link UserBackupManagerService#dump()} for system user does not prefix dump with
+     * "User 0:".
+     */
+    @Test
+    public void testDump_forSystemUser_DoesNotHaveUserPrefix() throws Exception {
+        mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+        UserBackupManagerService service =
+                BackupManagerServiceTestUtils.createUserBackupManagerServiceAndRunTasks(
+                        UserHandle.USER_SYSTEM,
+                        mContext,
+                        mBackupThread,
+                        mBaseStateDir,
+                        mDataDir,
+                        mTransportManager);
+
+        StringWriter dump = new StringWriter();
+        service.dump(new FileDescriptor(), new PrintWriter(dump), new String[0]);
+
+        assertThat(dump.toString()).startsWith("Backup Manager is ");
+    }
+
+    /**
+     * Test that {@link UserBackupManagerService#dump()} for non-system user prefixes dump with
+     * "User <userid>:".
+     */
+    @Test
+    public void testDump_forNonSystemUser_HasUserPrefix() throws Exception {
+        mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+        UserBackupManagerService service = createUserBackupManagerServiceAndRunTasks();
+
+        StringWriter dump = new StringWriter();
+        service.dump(new FileDescriptor(), new PrintWriter(dump), new String[0]);
+
+        assertThat(dump.toString()).startsWith("User " + USER_ID + ":" + "Backup Manager is ");
+    }
+
     private File createTestFile() throws IOException {
         File testFile = new File(mContext.getFilesDir(), "test");
         testFile.createNewFile();
         return testFile;
     }
 
-
     /**
      * We can't mock the void method {@link #schedule(Context, long, BackupManagerConstants)} so we
      * extend {@link ShadowKeyValueBackupJob} and throw an exception at the end of the method.
diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
index 2510b60..62ff3a1 100644
--- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
@@ -96,6 +96,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.util.FeatureFlagUtils;
 import android.util.Pair;
 
 import com.android.internal.backup.IBackupTransport;
@@ -131,6 +132,7 @@
 import org.mockito.ArgumentMatcher;
 import org.mockito.InOrder;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
@@ -257,6 +259,9 @@
     public void tearDown() throws Exception {
         ShadowBackupDataInput.reset();
         ShadowApplicationPackageManager.reset();
+        // False by default.
+        FeatureFlagUtils.setEnabled(
+                mContext, FeatureFlagUtils.BACKUP_NO_KV_DATA_CHANGE_CALLS, false);
     }
 
     @Test
@@ -2339,6 +2344,91 @@
         assertThat(mBackupManagerService.getCurrentToken()).isEqualTo(0L);
     }
 
+    /** Do not inform transport of an empty backup if the app hasn't backed up before */
+    @Test
+    public void testRunTask_whenNoDataToBackupOnFirstBackup_doesNotTellTransportOfBackup()
+            throws Exception {
+        FeatureFlagUtils.setEnabled(
+                mContext, FeatureFlagUtils.BACKUP_NO_KV_DATA_CHANGE_CALLS, true);
+
+        TransportMock transportMock = setUpInitializedTransport(mTransport);
+        mBackupManagerService.setCurrentToken(0L);
+        when(transportMock.transport.getCurrentRestoreSet()).thenReturn(1234L);
+        setUpAgent(PACKAGE_1);
+        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, true, PACKAGE_1);
+
+        runTask(task);
+
+        verify(transportMock.transport, never())
+                .performBackup(
+                        argThat(packageInfo(PACKAGE_1)), any(ParcelFileDescriptor.class), anyInt());
+    }
+
+    /** Let the transport know if there are no changes for a KV backed-up package. */
+    @Test
+    public void testRunTask_whenBackupHasCompletedAndThenNoDataChanges_transportGetsNotified()
+            throws Exception {
+        FeatureFlagUtils.setEnabled(
+                mContext, FeatureFlagUtils.BACKUP_NO_KV_DATA_CHANGE_CALLS, true);
+
+        TransportMock transportMock = setUpInitializedTransport(mTransport);
+        when(transportMock.transport.getCurrentRestoreSet()).thenReturn(1234L);
+        when(transportMock.transport.isAppEligibleForBackup(
+                        argThat(packageInfo(PACKAGE_1)), eq(false)))
+                .thenReturn(true);
+        when(transportMock.transport.isAppEligibleForBackup(
+                        argThat(packageInfo(PACKAGE_2)), eq(false)))
+                .thenReturn(true);
+        setUpAgentWithData(PACKAGE_1);
+        setUpAgentWithData(PACKAGE_2);
+
+        PackageInfo endSentinel = new PackageInfo();
+        endSentinel.packageName = KeyValueBackupTask.NO_DATA_END_SENTINEL;
+
+        // Perform First Backup run, which should backup both packages
+        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1, PACKAGE_2);
+        runTask(task);
+        InOrder order = Mockito.inOrder(transportMock.transport);
+        order.verify(transportMock.transport)
+                .performBackup(
+                        argThat(packageInfo(PACKAGE_1)),
+                        any(),
+                        eq(BackupTransport.FLAG_NON_INCREMENTAL));
+        order.verify(transportMock.transport).finishBackup();
+        order.verify(transportMock.transport)
+                .performBackup(
+                        argThat(packageInfo(PACKAGE_2)),
+                        any(),
+                        eq(BackupTransport.FLAG_NON_INCREMENTAL));
+        order.verify(transportMock.transport).finishBackup();
+
+        // Run again with new data for package 1, but nothing new for package 2
+        task = createKeyValueBackupTask(transportMock, PACKAGE_1);
+        runTask(task);
+
+        // Now for the second run we performed one incremental backup (package 1) and
+        // made one "no change" call (package 2) before sending the end sentinel.
+        order.verify(transportMock.transport)
+                .performBackup(
+                        argThat(packageInfo(PACKAGE_1)),
+                        any(),
+                        eq(BackupTransport.FLAG_INCREMENTAL));
+        order.verify(transportMock.transport).finishBackup();
+        order.verify(transportMock.transport)
+                .performBackup(
+                        argThat(packageInfo(PACKAGE_2)),
+                        any(),
+                        eq(BackupTransport.FLAG_DATA_NOT_CHANGED));
+        order.verify(transportMock.transport).finishBackup();
+        order.verify(transportMock.transport)
+                .performBackup(
+                        argThat(packageInfo(endSentinel)),
+                        any(),
+                        eq(BackupTransport.FLAG_DATA_NOT_CHANGED));
+        order.verify(transportMock.transport).finishBackup();
+        order.verifyNoMoreInteractions();
+    }
+
     private void runTask(KeyValueBackupTask task) {
         // Pretend we are not on the main-thread to prevent RemoteCall from complaining
         mShadowMainLooper.setCurrentThread(false);
@@ -2576,6 +2666,20 @@
                 packageInfo != null && packageData.packageName.equals(packageInfo.packageName);
     }
 
+    /** Matches {@link PackageInfo} whose package name is {@code packageData.packageName}. */
+    private static ArgumentMatcher<PackageInfo> packageInfo(PackageInfo packageData) {
+        // We have to test for packageInfo nulity because of Mockito's own stubbing with argThat().
+        // E.g. if you do:
+        //
+        //   1. when(object.method(argThat(str -> str.equals("foo")))).thenReturn(0)
+        //   2. when(object.method(argThat(str -> str.equals("bar")))).thenReturn(2)
+        //
+        // The second line will throw NPE because it will call lambda 1 with null, since argThat()
+        // returns null. So we guard against that by checking for null.
+        return packageInfo ->
+                packageInfo != null && packageInfo.packageName.equals(packageInfo.packageName);
+    }
+
     /** Matches {@link ApplicationInfo} whose package name is {@code packageData.packageName}. */
     private static ArgumentMatcher<ApplicationInfo> applicationInfo(PackageData packageData) {
         return applicationInfo ->
diff --git a/services/robotests/src/com/android/server/location/LocationRequestStatisticsTest.java b/services/robotests/src/com/android/server/location/LocationRequestStatisticsTest.java
new file mode 100644
index 0000000..4cbdbd17
--- /dev/null
+++ b/services/robotests/src/com/android/server/location/LocationRequestStatisticsTest.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.location;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * Unit tests for {@link LocationRequestStatistics}.
+ */
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class LocationRequestStatisticsTest {
+
+    /**
+     * Check adding and removing requests & strings
+     */
+    @Test
+    public void testRequestSummary() {
+        LocationRequestStatistics.RequestSummary summary =
+                new LocationRequestStatistics.RequestSummary(
+                "com.example", "gps", 1000);
+        StringWriter stringWriter = new StringWriter();
+        summary.dump(new IndentingPrintWriter(new PrintWriter(stringWriter), "  "), 1234);
+        assertThat(stringWriter.toString()).startsWith("At");
+
+        StringWriter stringWriterRemove = new StringWriter();
+        summary = new LocationRequestStatistics.RequestSummary(
+                "com.example", "gps",
+                LocationRequestStatistics.RequestSummary.REQUEST_ENDED_INTERVAL);
+        summary.dump(new IndentingPrintWriter(new PrintWriter(stringWriterRemove), "  "), 2345);
+        assertThat(stringWriterRemove.toString()).contains("-");
+    }
+
+    /**
+     * Check summary list size capping
+     */
+    @Test
+    public void testSummaryList() {
+        LocationRequestStatistics statistics = new LocationRequestStatistics();
+        statistics.history.addRequest("com.example", "gps", 1000);
+        assertThat(statistics.history.mList.size()).isEqualTo(1);
+        // Try (not) to overflow
+        for (int i = 0; i < LocationRequestStatistics.RequestSummaryLimitedHistory.MAX_SIZE; i++) {
+            statistics.history.addRequest("com.example", "gps", 1000);
+        }
+        assertThat(statistics.history.mList.size()).isEqualTo(
+                LocationRequestStatistics.RequestSummaryLimitedHistory.MAX_SIZE);
+    }
+}
diff --git a/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java b/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java
index a8a258f..9c5d4ad 100644
--- a/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java
+++ b/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java
@@ -3,6 +3,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 
 import android.os.Looper;
 import android.os.SystemClock;
@@ -52,8 +53,10 @@
 
     @Test
     public void handleInjectNtpTime_cachedAgeLow_injectTime() throws InterruptedException {
-        doReturn(NtpTimeHelper.NTP_INTERVAL - 1).when(mMockNtpTrustedTime).getCacheAge();
-        doReturn(MOCK_NTP_TIME).when(mMockNtpTrustedTime).getCachedNtpTime();
+        NtpTrustedTime.TimeResult result = mock(NtpTrustedTime.TimeResult.class);
+        doReturn(NtpTimeHelper.NTP_INTERVAL - 1).when(result).getAgeMillis();
+        doReturn(MOCK_NTP_TIME).when(result).getTimeMillis();
+        doReturn(result).when(mMockNtpTrustedTime).getCachedTimeResult();
 
         mNtpTimeHelper.retrieveAndInjectNtpTime();
 
@@ -64,7 +67,9 @@
     @Test
     public void handleInjectNtpTime_injectTimeFailed_injectTimeDelayed()
             throws InterruptedException {
-        doReturn(NtpTimeHelper.NTP_INTERVAL + 1).when(mMockNtpTrustedTime).getCacheAge();
+        NtpTrustedTime.TimeResult result1 = mock(NtpTrustedTime.TimeResult.class);
+        doReturn(NtpTimeHelper.NTP_INTERVAL + 1).when(result1).getAgeMillis();
+        doReturn(result1).when(mMockNtpTrustedTime).getCachedTimeResult();
         doReturn(false).when(mMockNtpTrustedTime).forceRefresh();
 
         mNtpTimeHelper.retrieveAndInjectNtpTime();
@@ -72,8 +77,10 @@
         assertThat(mCountDownLatch.await(2, TimeUnit.SECONDS)).isFalse();
 
         doReturn(true).when(mMockNtpTrustedTime).forceRefresh();
-        doReturn(1L).when(mMockNtpTrustedTime).getCacheAge();
-        doReturn(MOCK_NTP_TIME).when(mMockNtpTrustedTime).getCachedNtpTime();
+        NtpTrustedTime.TimeResult result2 = mock(NtpTrustedTime.TimeResult.class);
+        doReturn(1L).when(result2).getAgeMillis();
+        doReturn(MOCK_NTP_TIME).when(result2).getTimeMillis();
+        doReturn(result2).when(mMockNtpTrustedTime).getCachedTimeResult();
         SystemClock.sleep(NtpTimeHelper.RETRY_INTERVAL);
 
         waitForTasksToBePostedOnHandlerAndRunThem();
diff --git a/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java b/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java
new file mode 100644
index 0000000..1a7b1d3
--- /dev/null
+++ b/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java
@@ -0,0 +1,542 @@
+/*
+ * 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.pm;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.OP_INTERACT_ACROSS_PROFILES;
+import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
+import static android.content.Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND;
+import static android.content.Intent.FLAG_RECEIVER_REGISTERED_ONLY;
+import static android.content.pm.CrossProfileApps.ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.Manifest;
+import android.annotation.UserIdInt;
+import android.app.ActivityManagerInternal;
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.Mode;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.ResolveInfo;
+import android.content.pm.parsing.AndroidPackage;
+import android.content.pm.parsing.PackageImpl;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.server.LocalServices;
+import com.android.server.testing.shadows.ShadowApplicationPackageManager;
+import com.android.server.testing.shadows.ShadowUserManager;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import com.google.android.collect.Lists;
+
+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.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/** Unit tests for {@link CrossProfileAppsServiceImpl}. */
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+@Config(shadows = {ShadowUserManager.class, ShadowApplicationPackageManager.class})
+public class CrossProfileAppsServiceImplRoboTest {
+    private static final int CALLING_UID = 1111;
+    private static final int CALLING_PID = 1000;
+    private static final String CROSS_PROFILE_APP_PACKAGE_NAME =
+            "com.android.server.pm.crossprofileappsserviceimplrobotest.crossprofileapp";
+    private static final int PERSONAL_PROFILE_USER_ID = 0;
+    private static final int PERSONAL_PROFILE_UID = 2222;
+    private static final int WORK_PROFILE_USER_ID = 10;
+    private static final int WORK_PROFILE_UID = 3333;
+    private static final int OTHER_PROFILE_WITHOUT_CROSS_PROFILE_APP_USER_ID = 20;
+
+    private final ContextWrapper mContext = ApplicationProvider.getApplicationContext();
+    private final UserManager mUserManager = mContext.getSystemService(UserManager.class);
+    private final AppOpsManager mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
+    private final PackageManager mPackageManager = mContext.getPackageManager();
+    private final TestInjector mInjector = new TestInjector();
+    private final CrossProfileAppsServiceImpl mCrossProfileAppsServiceImpl =
+            new CrossProfileAppsServiceImpl(mContext, mInjector);
+    private final Map<UserHandle, Set<Intent>> mSentUserBroadcasts = new HashMap<>();
+
+    @Mock private PackageManagerInternal mPackageManagerInternal;
+    @Mock private IPackageManager mIPackageManager;
+    @Mock private DevicePolicyManagerInternal mDevicePolicyManagerInternal;
+
+    @Before
+    public void initializeMocks() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mockCrossProfileAppInstalledAndEnabledOnEachProfile();
+        mockCrossProfileAppRequestsInteractAcrossProfiles();
+        mockCrossProfileAppRegistersBroadcastReceiver();
+        mockCrossProfileAppWhitelisted();
+    }
+
+    private void mockCrossProfileAppInstalledAndEnabledOnEachProfile() {
+        // They are enabled by default, so we simply have to ensure that a package info with an
+        // application info is returned.
+        final PackageInfo packageInfo = buildTestPackageInfo();
+        mockCrossProfileAppInstalledOnProfile(
+                packageInfo, PERSONAL_PROFILE_USER_ID, PERSONAL_PROFILE_UID);
+        mockCrossProfileAppInstalledOnProfile(packageInfo, WORK_PROFILE_USER_ID, WORK_PROFILE_UID);
+    }
+
+    private void mockCrossProfileAppInstalledOnProfile(
+            PackageInfo packageInfo, @UserIdInt int userId, int uid) {
+        when(mPackageManagerInternal.getPackageInfo(
+                        eq(CROSS_PROFILE_APP_PACKAGE_NAME),
+                        /* flags= */ anyInt(),
+                        /* filterCallingUid= */ anyInt(),
+                        eq(userId)))
+                .thenReturn(packageInfo);
+        when(mPackageManagerInternal.getPackage(uid))
+                .thenReturn(PackageImpl.forParsing(CROSS_PROFILE_APP_PACKAGE_NAME));
+    }
+
+    private PackageInfo buildTestPackageInfo() {
+        PackageInfo packageInfo = new PackageInfo();
+        packageInfo.applicationInfo = new ApplicationInfo();
+        return packageInfo;
+    }
+
+    private void mockCrossProfileAppRequestsInteractAcrossProfiles() throws Exception {
+        final String permissionName = Manifest.permission.INTERACT_ACROSS_PROFILES;
+        when(mIPackageManager.getAppOpPermissionPackages(permissionName))
+                .thenReturn(new String[] {CROSS_PROFILE_APP_PACKAGE_NAME});
+    }
+
+    private void mockCrossProfileAppRegistersBroadcastReceiver() {
+        final ShadowApplicationPackageManager shadowApplicationPackageManager =
+                Shadow.extract(mPackageManager);
+        final Intent baseIntent =
+                new Intent(ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED)
+                        .setPackage(CROSS_PROFILE_APP_PACKAGE_NAME);
+        final Intent manifestIntent =
+                new Intent(baseIntent)
+                        .setFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
+                                | Intent.FLAG_RECEIVER_FOREGROUND);
+        final Intent registeredIntent =
+                new Intent(baseIntent).setFlags(FLAG_RECEIVER_REGISTERED_ONLY);
+        final List<ResolveInfo> resolveInfos = Lists.newArrayList(buildTestResolveInfo());
+        shadowApplicationPackageManager.setResolveInfosForIntent(manifestIntent, resolveInfos);
+        shadowApplicationPackageManager.setResolveInfosForIntent(registeredIntent, resolveInfos);
+    }
+
+    private ResolveInfo buildTestResolveInfo() {
+        final ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.activityInfo = new ActivityInfo();
+        resolveInfo.activityInfo.packageName = CROSS_PROFILE_APP_PACKAGE_NAME;
+        resolveInfo.activityInfo.name = CROSS_PROFILE_APP_PACKAGE_NAME + ".Receiver";
+        return resolveInfo;
+    }
+
+    private void mockCrossProfileAppWhitelisted() {
+        when(mDevicePolicyManagerInternal.getAllCrossProfilePackages())
+                .thenReturn(Lists.newArrayList(CROSS_PROFILE_APP_PACKAGE_NAME));
+    }
+
+    @Before
+    public void setUpCrossProfileAppUidsAndPackageNames() {
+        ShadowApplicationPackageManager.setPackageUidAsUser(
+                CROSS_PROFILE_APP_PACKAGE_NAME, PERSONAL_PROFILE_UID, PERSONAL_PROFILE_USER_ID);
+        ShadowApplicationPackageManager.setPackageUidAsUser(
+                CROSS_PROFILE_APP_PACKAGE_NAME, WORK_PROFILE_UID, WORK_PROFILE_USER_ID);
+    }
+
+    @Before
+    public void grantPermissions() {
+        grantPermissions(
+                Manifest.permission.MANAGE_APP_OPS_MODES,
+                Manifest.permission.INTERACT_ACROSS_USERS,
+                Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+    }
+
+    @Before
+    public void setUpProfiles() {
+        final ShadowUserManager shadowUserManager = Shadow.extract(mUserManager);
+        shadowUserManager.addProfileIds(
+                PERSONAL_PROFILE_USER_ID,
+                WORK_PROFILE_USER_ID,
+                OTHER_PROFILE_WITHOUT_CROSS_PROFILE_APP_USER_ID);
+    }
+
+    @Before
+    public void setInteractAcrossProfilesAppOpDefault() {
+        // It seems to be necessary to provide the shadow with the default already specified in
+        // AppOpsManager.
+        final int defaultMode = AppOpsManager.opToDefaultMode(OP_INTERACT_ACROSS_PROFILES);
+        explicitlySetInteractAcrossProfilesAppOp(PERSONAL_PROFILE_UID, defaultMode);
+        explicitlySetInteractAcrossProfilesAppOp(WORK_PROFILE_UID, defaultMode);
+    }
+
+    @Test
+    public void setInteractAcrossProfilesAppOp_missingInteractAcrossUsersAndFull_throwsSecurityException() {
+        denyPermissions(Manifest.permission.INTERACT_ACROSS_USERS);
+        denyPermissions(Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+        try {
+            mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp(
+                    CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED);
+            fail();
+        } catch (SecurityException expected) {}
+    }
+
+    @Test
+    public void setInteractAcrossProfilesAppOp_setsAppOp() {
+        mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp(
+                CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED);
+        assertThat(getCrossProfileAppOp()).isEqualTo(MODE_ALLOWED);
+    }
+
+    @Test
+    public void setInteractAcrossProfilesAppOp_setsAppOpWithUsersAndWithoutFull() {
+        denyPermissions(Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+        mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp(
+                CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED);
+        assertThat(getCrossProfileAppOp()).isEqualTo(MODE_ALLOWED);
+    }
+
+    @Test
+    public void setInteractAcrossProfilesAppOp_setsAppOpWithFullAndWithoutUsers() {
+        denyPermissions(Manifest.permission.INTERACT_ACROSS_USERS);
+        mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp(
+                CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED);
+        assertThat(getCrossProfileAppOp()).isEqualTo(MODE_ALLOWED);
+    }
+
+    @Test
+    public void setInteractAcrossProfilesAppOp_setsAppOpOnOtherProfile() {
+        mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp(
+                CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED);
+        assertThat(getCrossProfileAppOp(WORK_PROFILE_UID)).isEqualTo(MODE_ALLOWED);
+    }
+
+    @Test
+    public void setInteractAcrossProfilesAppOp_sendsBroadcast() {
+        mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp(
+                CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED);
+        assertThat(receivedCanInteractAcrossProfilesChangedBroadcast()).isTrue();
+    }
+
+    @Test
+    public void setInteractAcrossProfilesAppOp_sendsBroadcastToOtherProfile() {
+        mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp(
+                CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED);
+        assertThat(receivedCanInteractAcrossProfilesChangedBroadcast(WORK_PROFILE_USER_ID))
+                .isTrue();
+    }
+
+    @Test
+    public void setInteractAcrossProfilesAppOp_doesNotSendBroadcastToProfileWithoutPackage() {
+        mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp(
+                CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED);
+        assertThat(receivedCanInteractAcrossProfilesChangedBroadcast(
+                        OTHER_PROFILE_WITHOUT_CROSS_PROFILE_APP_USER_ID))
+                .isFalse();
+    }
+
+    @Test
+    public void setInteractAcrossProfilesAppOp_toSameAsCurrent_doesNotSendBroadcast() {
+        explicitlySetInteractAcrossProfilesAppOp(MODE_ALLOWED);
+        mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp(
+                CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED);
+        assertThat(receivedCanInteractAcrossProfilesChangedBroadcast()).isFalse();
+    }
+
+    @Test
+    public void setInteractAcrossProfilesAppOp_toAllowed_whenNotAbleToRequest_doesNotSet() {
+        mockCrossProfileAppNotWhitelisted();
+        mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp(
+                CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED);
+        assertThat(getCrossProfileAppOp()).isNotEqualTo(MODE_ALLOWED);
+    }
+
+    @Test
+    public void setInteractAcrossProfilesAppOp_toAllowed_whenNotAbleToRequest_doesNotSendBroadcast() {
+        mockCrossProfileAppNotWhitelisted();
+        mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp(
+                CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED);
+        assertThat(receivedCanInteractAcrossProfilesChangedBroadcast()).isFalse();
+    }
+
+    @Test
+    public void setInteractAcrossProfilesAppOp_withoutCrossProfileAttribute_manifestReceiversDoNotGetBroadcast() {
+        declareCrossProfileAttributeOnCrossProfileApp(false);
+        mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp(
+                CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED);
+        assertThat(receivedManifestCanInteractAcrossProfilesChangedBroadcast()).isFalse();
+    }
+
+    @Test
+    public void setInteractAcrossProfilesAppOp_withCrossProfileAttribute_manifestReceiversGetBroadcast() {
+        declareCrossProfileAttributeOnCrossProfileApp(true);
+        mCrossProfileAppsServiceImpl.setInteractAcrossProfilesAppOp(
+                CROSS_PROFILE_APP_PACKAGE_NAME, MODE_ALLOWED);
+        assertThat(receivedManifestCanInteractAcrossProfilesChangedBroadcast()).isTrue();
+    }
+
+    @Test
+    public void canConfigureInteractAcrossProfiles_packageNotInstalledInProfile_returnsFalse() {
+        mockUninstallCrossProfileAppFromWorkProfile();
+        assertThat(mCrossProfileAppsServiceImpl
+                .canConfigureInteractAcrossProfiles(CROSS_PROFILE_APP_PACKAGE_NAME))
+                .isFalse();
+    }
+
+    private void mockUninstallCrossProfileAppFromWorkProfile() {
+        when(mPackageManagerInternal.getPackageInfo(
+                        eq(CROSS_PROFILE_APP_PACKAGE_NAME),
+                        /* flags= */ anyInt(),
+                        /* filterCallingUid= */ anyInt(),
+                        eq(WORK_PROFILE_USER_ID)))
+                .thenReturn(null);
+        when(mPackageManagerInternal.getPackage(WORK_PROFILE_UID)).thenReturn(null);
+    }
+
+    @Test
+    public void canConfigureInteractAcrossProfiles_packageDoesNotRequestInteractAcrossProfiles_returnsFalse()
+            throws Exception {
+        mockCrossProfileAppDoesNotRequestInteractAcrossProfiles();
+        assertThat(mCrossProfileAppsServiceImpl
+                .canConfigureInteractAcrossProfiles(CROSS_PROFILE_APP_PACKAGE_NAME))
+                .isFalse();
+    }
+
+    private void mockCrossProfileAppDoesNotRequestInteractAcrossProfiles() throws Exception {
+        final String permissionName = Manifest.permission.INTERACT_ACROSS_PROFILES;
+        when(mIPackageManager.getAppOpPermissionPackages(permissionName))
+                .thenReturn(new String[] {});
+    }
+
+    @Test
+    public void canConfigureInteractAcrossProfiles_packageNotWhitelisted_returnsFalse() {
+        mockCrossProfileAppNotWhitelisted();
+        assertThat(mCrossProfileAppsServiceImpl
+                .canConfigureInteractAcrossProfiles(CROSS_PROFILE_APP_PACKAGE_NAME))
+                .isFalse();
+    }
+
+    @Test
+    public void canConfigureInteractAcrossProfiles_returnsTrue() {
+        assertThat(mCrossProfileAppsServiceImpl
+                .canConfigureInteractAcrossProfiles(CROSS_PROFILE_APP_PACKAGE_NAME))
+                .isTrue();
+    }
+
+    private void explicitlySetInteractAcrossProfilesAppOp(@Mode int mode) {
+        explicitlySetInteractAcrossProfilesAppOp(PERSONAL_PROFILE_UID, mode);
+    }
+
+    private void explicitlySetInteractAcrossProfilesAppOp(int uid, @Mode int mode) {
+        shadowOf(mAppOpsManager).setMode(
+                OP_INTERACT_ACROSS_PROFILES, uid, CROSS_PROFILE_APP_PACKAGE_NAME, mode);
+    }
+
+    private void grantPermissions(String... permissions) {
+        shadowOf(mContext).grantPermissions(Process.myPid(), CALLING_UID, permissions);
+    }
+
+    private void denyPermissions(String... permissions) {
+        shadowOf(mContext).denyPermissions(Process.myPid(), CALLING_UID, permissions);
+    }
+
+    private @Mode int getCrossProfileAppOp() {
+        return getCrossProfileAppOp(PERSONAL_PROFILE_UID);
+    }
+
+    private @Mode int getCrossProfileAppOp(int uid) {
+        return mAppOpsManager.unsafeCheckOpNoThrow(
+                AppOpsManager.permissionToOp(Manifest.permission.INTERACT_ACROSS_PROFILES),
+                uid,
+                CROSS_PROFILE_APP_PACKAGE_NAME);
+    }
+
+    private boolean receivedCanInteractAcrossProfilesChangedBroadcast() {
+        return receivedCanInteractAcrossProfilesChangedBroadcast(PERSONAL_PROFILE_USER_ID);
+    }
+
+    private boolean receivedCanInteractAcrossProfilesChangedBroadcast(@UserIdInt int userId) {
+        final UserHandle userHandle = UserHandle.of(userId);
+        if (!mSentUserBroadcasts.containsKey(userHandle)) {
+            return false;
+        }
+        return mSentUserBroadcasts.get(userHandle)
+                .stream()
+                .anyMatch(this::isBroadcastCanInteractAcrossProfilesChanged);
+    }
+
+    private boolean isBroadcastCanInteractAcrossProfilesChanged(Intent intent) {
+        return intent.getAction().equals(ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED)
+                && CROSS_PROFILE_APP_PACKAGE_NAME.equals(intent.getPackage());
+    }
+
+    private void mockCrossProfileAndroidPackage(AndroidPackage androidPackage) {
+        when(mPackageManagerInternal.getPackage(CROSS_PROFILE_APP_PACKAGE_NAME))
+                .thenReturn(androidPackage);
+        when(mPackageManagerInternal.getPackage(PERSONAL_PROFILE_UID)).thenReturn(androidPackage);
+        when(mPackageManagerInternal.getPackage(WORK_PROFILE_UID)).thenReturn(androidPackage);
+    }
+
+    private void mockCrossProfileAppNotWhitelisted() {
+        when(mDevicePolicyManagerInternal.getAllCrossProfilePackages())
+                .thenReturn(new ArrayList<>());
+    }
+
+    private boolean receivedManifestCanInteractAcrossProfilesChangedBroadcast() {
+        final UserHandle userHandle = UserHandle.of(PERSONAL_PROFILE_USER_ID);
+        if (!mSentUserBroadcasts.containsKey(userHandle)) {
+            return false;
+        }
+        return mSentUserBroadcasts.get(userHandle)
+                .stream()
+                .anyMatch(this::isBroadcastManifestCanInteractAcrossProfilesChanged);
+    }
+
+    private boolean isBroadcastManifestCanInteractAcrossProfilesChanged(Intent intent) {
+        return isBroadcastCanInteractAcrossProfilesChanged(intent)
+                && (intent.getFlags() & FLAG_RECEIVER_REGISTERED_ONLY) == 0
+                && (intent.getFlags() & FLAG_RECEIVER_INCLUDE_BACKGROUND) != 0
+                && (intent.getFlags() & FLAG_RECEIVER_FOREGROUND) != 0
+                && intent.getComponent() != null
+                && intent.getComponent().getPackageName().equals(CROSS_PROFILE_APP_PACKAGE_NAME);
+    }
+
+    private void declareCrossProfileAttributeOnCrossProfileApp(boolean value) {
+        mockCrossProfileAndroidPackage(
+                PackageImpl.forParsing(CROSS_PROFILE_APP_PACKAGE_NAME).setCrossProfile(value));
+    }
+
+    private class TestInjector implements CrossProfileAppsServiceImpl.Injector {
+
+        @Override
+        public int getCallingUid() {
+            return CALLING_UID;
+        }
+
+        @Override
+        public int getCallingPid() {
+            return CALLING_PID;
+        }
+
+        @Override
+        public @UserIdInt int getCallingUserId() {
+            return PERSONAL_PROFILE_USER_ID;
+        }
+
+        @Override
+        public UserHandle getCallingUserHandle() {
+            return UserHandle.of(getCallingUserId());
+        }
+
+        @Override
+        public long clearCallingIdentity() {
+            return 0;
+        }
+
+        @Override
+        public void restoreCallingIdentity(long token) {}
+
+        @Override
+        public UserManager getUserManager() {
+            return mUserManager;
+        }
+
+        @Override
+        public PackageManagerInternal getPackageManagerInternal() {
+            return mPackageManagerInternal;
+        }
+
+        @Override
+        public PackageManager getPackageManager() {
+            return mPackageManager;
+        }
+
+        @Override
+        public AppOpsManager getAppOpsManager() {
+            return mAppOpsManager;
+        }
+
+        @Override
+        public ActivityManagerInternal getActivityManagerInternal() {
+            return LocalServices.getService(ActivityManagerInternal.class);
+        }
+
+        @Override
+        public ActivityTaskManagerInternal getActivityTaskManagerInternal() {
+            return LocalServices.getService(ActivityTaskManagerInternal.class);
+        }
+
+        @Override
+        public IPackageManager getIPackageManager() {
+            return mIPackageManager;
+        }
+
+        @Override
+        public DevicePolicyManagerInternal getDevicePolicyManagerInternal() {
+            return mDevicePolicyManagerInternal;
+        }
+
+        @Override
+        public void sendBroadcastAsUser(Intent intent, UserHandle user) {
+            // Robolectric's shadows do not currently support sendBroadcastAsUser.
+            final Set<Intent> broadcasts =
+                    mSentUserBroadcasts.containsKey(user)
+                            ? mSentUserBroadcasts.get(user)
+                            : new HashSet<>();
+            broadcasts.add(intent);
+            mSentUserBroadcasts.put(user, broadcasts);
+            mContext.sendBroadcastAsUser(intent, user);
+        }
+
+        @Override
+        public int checkComponentPermission(
+                String permission, int uid, int owningUid, boolean exported) {
+            // ActivityManager#checkComponentPermission calls through to
+            // AppGlobals.getPackageManager()#checkUidPermission, which calls through to
+            // ShadowActivityThread with Robolectric. This method is currently not supported there.
+            return mContext.checkPermission(permission, Process.myPid(), uid);
+        }
+    }
+}
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java b/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java
index ab121ed..aea36e5 100644
--- a/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java
@@ -19,13 +19,17 @@
 import static android.content.pm.PackageManager.NameNotFoundException;
 
 import android.app.ApplicationPackageManager;
+import android.content.Intent;
 import android.content.pm.PackageInfo;
+import android.content.pm.ResolveInfo;
+import android.os.UserHandle;
 import android.util.ArrayMap;
 
 import org.robolectric.annotation.Implements;
 import org.robolectric.annotation.Resetter;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -39,6 +43,7 @@
     private static final Map<String, PackageInfo> sPackageInfos = new ArrayMap<>();
     private static final List<PackageInfo> sInstalledPackages = new ArrayList<>();
     private static final Map<String, Integer> sPackageUids = new ArrayMap<>();
+    private static final Map<Integer, Map<String, Integer>> sUserPackageUids = new ArrayMap<>();
 
     /**
      * Registers the package {@code packageName} to be returned when invoking {@link
@@ -58,6 +63,19 @@
         sPackageUids.put(packageName, packageUid);
     }
 
+    /**
+     * Sets the package uid {@code packageUid} for the package {@code packageName} to be returned
+     * when invoking {@link ApplicationPackageManager#getPackageUidAsUser(String, int, int)}.
+     */
+    public static void setPackageUidAsUser(String packageName, int packageUid, int userId) {
+        final Map<String, Integer> userPackageUids =
+                sUserPackageUids.containsKey(userId)
+                        ? sUserPackageUids.get(userId)
+                        : new HashMap<>();
+        userPackageUids.put(packageName, packageUid);
+        sUserPackageUids.put(userId, userPackageUids);
+    }
+
     @Override
     protected PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId)
             throws NameNotFoundException {
@@ -75,12 +93,23 @@
     @Override
     protected int getPackageUidAsUser(String packageName, int flags, int userId)
             throws NameNotFoundException {
+        if (sUserPackageUids.containsKey(userId)
+                && sUserPackageUids.get(userId).containsKey(packageName)) {
+            return sUserPackageUids.get(userId).get(packageName);
+        }
         if (!sPackageUids.containsKey(packageName)) {
             throw new NameNotFoundException(packageName);
         }
         return sPackageUids.get(packageName);
     }
 
+    @Override
+    protected List<ResolveInfo> queryBroadcastReceiversAsUser(
+            Intent intent, int flags, UserHandle userHandle) {
+        // Currently does not handle multi-user.
+        return queryBroadcastReceivers(intent, flags);
+    }
+
     /** Clear package state. */
     @Resetter
     public static void reset() {
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowUserManager.java b/services/robotests/src/com/android/server/testing/shadows/ShadowUserManager.java
index c6ae1a1..a9e4ee5 100644
--- a/services/robotests/src/com/android/server/testing/shadows/ShadowUserManager.java
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowUserManager.java
@@ -16,18 +16,46 @@
 
 package com.android.server.testing.shadows;
 
+import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.os.UserManager;
 
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
 
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
 /** Shadow for {@link UserManager}. */
 @Implements(UserManager.class)
 public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager {
+    private final Map<Integer, Set<Integer>> profileIds = new HashMap<>();
+
     /** @see UserManager#isUserUnlocked() */
     @Implementation
     public boolean isUserUnlocked(@UserIdInt int userId) {
         return false;
     }
+
+    /** @see UserManager#getProfileIds(int, boolean) () */
+    @Implementation
+    @NonNull
+    public int[] getProfileIds(@UserIdInt int userId, boolean enabledOnly) {
+        // Currently, enabledOnly is ignored.
+        if (!profileIds.containsKey(userId)) {
+            return new int[] {userId};
+        }
+        return profileIds.get(userId).stream().mapToInt(Number::intValue).toArray();
+    }
+
+    /** Add a collection of profile IDs, all within the same profile group. */
+    public void addProfileIds(@UserIdInt int... userIds) {
+        final Set<Integer> profileGroup = new HashSet<>();
+        for (int userId : userIds) {
+            profileGroup.add(userId);
+            profileIds.put(userId, profileGroup);
+        }
+    }
 }
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 96fedf9..3d9f11f 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -21,6 +21,7 @@
         "services.core",
         "services.net",
         "service-jobscheduler",
+        "service-permission",
         "androidx.test.runner",
         "mockito-target-extended-minus-junit4",
         "platform-test-annotations",
diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml
index b3b5af0..0e24b03 100644
--- a/services/tests/mockingservicestests/AndroidManifest.xml
+++ b/services/tests/mockingservicestests/AndroidManifest.xml
@@ -21,6 +21,7 @@
     <uses-permission android:name="android.permission.HARDWARE_TEST"/>
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
     <uses-permission android:name="android.permission.MANAGE_APPOPS"/>
+    <uses-permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS"/>
 
     <application android:testOnly="true"
                  android:debuggable="true">
diff --git a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
index 556f96a..6a5de84 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
@@ -345,8 +345,8 @@
     }
 
     /**
-     * Lowers quotas to make testing feasible.
-     * Careful while calling as this will replace any existing settings for the calling test.
+     * Lowers quotas to make testing feasible. Careful while calling as this will replace any
+     * existing settings for the calling test.
      */
     private void setTestableQuotas() {
         final StringBuilder constantsBuilder = new StringBuilder();
@@ -981,6 +981,25 @@
         assertEquals(0, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
     }
 
+    @Test
+    public void alarmCountOnListenerBinderDied() {
+        final int numAlarms = 10;
+        final IAlarmListener[] listeners = new IAlarmListener[numAlarms];
+        for (int i = 0; i < numAlarms; i++) {
+            listeners[i] = new IAlarmListener.Stub() {
+                @Override
+                public void doAlarm(IAlarmCompleteListener callback) throws RemoteException {
+                }
+            };
+            setTestAlarmWithListener(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + i, listeners[i]);
+        }
+        assertEquals(numAlarms, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
+        for (int i = 0; i < numAlarms; i++) {
+            mService.mListenerDeathRecipient.binderDied(listeners[i].asBinder());
+            assertEquals(numAlarms - i - 1, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
+        }
+    }
+
     @After
     public void tearDown() {
         if (mMockingSession != null) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index 0d6020c..2080fdf 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -24,55 +24,76 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.RescueParty.LEVEL_FACTORY_RESET;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.times;
 
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.pm.VersionedPackage;
+import android.os.Bundle;
 import android.os.RecoverySystem;
+import android.os.RemoteCallback;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
+import com.android.server.RescueParty.RescuePartyObserver;
 import com.android.server.am.SettingsToPropertiesMapper;
-import com.android.server.utils.FlagNamespaceUtils;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoSession;
 import org.mockito.quality.Strictness;
 import org.mockito.stubbing.Answer;
 
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.List;
 
 /**
  * Test RescueParty.
  */
 public class RescuePartyTest {
-    private static final int PERSISTENT_APP_UID = 12;
     private static final long CURRENT_NETWORK_TIME_MILLIS = 0L;
     private static final String FAKE_NATIVE_NAMESPACE1 = "native1";
     private static final String FAKE_NATIVE_NAMESPACE2 = "native2";
     private static final String[] FAKE_RESET_NATIVE_NAMESPACES =
             {FAKE_NATIVE_NAMESPACE1, FAKE_NATIVE_NAMESPACE2};
 
+    private static VersionedPackage sFailingPackage = new VersionedPackage("com.package.name", 1);
+    private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
+    private static final String CALLING_PACKAGE1 = "com.package.name1";
+    private static final String CALLING_PACKAGE2 = "com.package.name2";
+    private static final String NAMESPACE1 = "namespace1";
+    private static final String NAMESPACE2 = "namespace2";
+
     private MockitoSession mSession;
+    private HashMap<String, String> mSystemSettingsMap;
 
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private Context mMockContext;
-
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private PackageWatchdog mMockPackageWatchdog;
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private ContentResolver mMockContentResolver;
 
-    private HashMap<String, String> mSystemSettingsMap;
+    @Captor
+    private ArgumentCaptor<RemoteCallback> mMonitorCallbackCaptor;
+    @Captor
+    private ArgumentCaptor<List<String>> mPackageListCaptor;
 
     @Before
     public void setUp() throws Exception {
@@ -84,14 +105,17 @@
                         .spyStatic(SystemProperties.class)
                         .spyStatic(Settings.Global.class)
                         .spyStatic(Settings.Secure.class)
+                        .spyStatic(Settings.Config.class)
                         .spyStatic(SettingsToPropertiesMapper.class)
                         .spyStatic(RecoverySystem.class)
                         .spyStatic(RescueParty.class)
+                        .spyStatic(PackageWatchdog.class)
                         .startMocking();
         mSystemSettingsMap = new HashMap<>();
 
         when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
-
+        // Reset observer instance to get new mock context on every run
+        RescuePartyObserver.reset();
 
         // Mock SystemProperties setter and various getters
         doAnswer((Answer<Void>) invocationOnMock -> {
@@ -137,10 +161,11 @@
         doAnswer((Answer<Void>) invocationOnMock -> null)
                 .when(() -> DeviceConfig.resetToDefaults(anyInt(), anyString()));
 
+        // Mock PackageWatchdog
+        doAnswer((Answer<PackageWatchdog>) invocationOnMock -> mMockPackageWatchdog)
+                .when(() -> PackageWatchdog.getInstance(mMockContext));
 
         doReturn(CURRENT_NETWORK_TIME_MILLIS).when(() -> RescueParty.getElapsedRealtime());
-        RescueParty.resetAllThresholds();
-        FlagNamespaceUtils.resetKnownResetNamespacesFlagCounterForTest();
 
         SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL,
                 Integer.toString(RescueParty.LEVEL_NONE));
@@ -155,132 +180,111 @@
 
     @Test
     public void testBootLoopDetectionWithExecutionForAllRescueLevels() {
-        noteBoot(RescueParty.TRIGGER_COUNT);
+        noteBoot();
 
-        verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
+        verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null);
         assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
                 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
 
-        noteBoot(RescueParty.TRIGGER_COUNT);
+        noteBoot();
 
-        verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES);
+        verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, /*resetNamespaces=*/ null);
         assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES,
                 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
 
-        noteBoot(RescueParty.TRIGGER_COUNT);
+        noteBoot();
 
-        verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS);
+        verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, /*resetNamespaces=*/ null);
         assertEquals(RescueParty.LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS,
                 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
 
-        noteBoot(RescueParty.TRIGGER_COUNT);
+        noteBoot();
 
         verify(() -> RecoverySystem.rebootPromptAndWipeUserData(mMockContext, RescueParty.TAG));
-        assertEquals(RescueParty.LEVEL_FACTORY_RESET,
+        assertEquals(LEVEL_FACTORY_RESET,
                 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
     }
 
     @Test
     public void testPersistentAppCrashDetectionWithExecutionForAllRescueLevels() {
-        notePersistentAppCrash(RescueParty.TRIGGER_COUNT);
+        notePersistentAppCrash();
 
-        verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
+        verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null);
         assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
                 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
 
-        notePersistentAppCrash(RescueParty.TRIGGER_COUNT);
+        notePersistentAppCrash();
 
-        verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES);
+        verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, /*resetNamespaces=*/ null);
         assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES,
                 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
 
-        notePersistentAppCrash(RescueParty.TRIGGER_COUNT);
+        notePersistentAppCrash();
 
-        verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS);
+        verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, /*resetNamespaces=*/ null);
         assertEquals(RescueParty.LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS,
                 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
 
-        notePersistentAppCrash(RescueParty.TRIGGER_COUNT);
+        notePersistentAppCrash();
 
         verify(() -> RecoverySystem.rebootPromptAndWipeUserData(mMockContext, RescueParty.TAG));
-        assertEquals(RescueParty.LEVEL_FACTORY_RESET,
+        assertEquals(LEVEL_FACTORY_RESET,
                 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
     }
 
     @Test
-    public void testBootLoopDetectionWithWrongInterval() {
-        noteBoot(RescueParty.TRIGGER_COUNT - 1);
+    public void testNonPersistentAppCrashDetectionWithScopedResets() {
+        RescueParty.onSettingsProviderPublished(mMockContext);
+        verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver),
+                mMonitorCallbackCaptor.capture()));
 
-        // last boot is just outside of the boot loop detection window
-        doReturn(CURRENT_NETWORK_TIME_MILLIS + RescueParty.BOOT_TRIGGER_WINDOW_MILLIS + 1).when(
-                () -> RescueParty.getElapsedRealtime());
-        noteBoot(/*numTimes=*/1);
-
-        assertEquals(RescueParty.LEVEL_NONE,
-                SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
-    }
-
-    @Test
-    public void testPersistentAppCrashDetectionWithWrongInterval() {
-        notePersistentAppCrash(RescueParty.TRIGGER_COUNT - 1);
-
-        // last persistent app crash is just outside of the boot loop detection window
-        doReturn(CURRENT_NETWORK_TIME_MILLIS
-                + RescueParty.PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS + 1)
-                .when(() -> RescueParty.getElapsedRealtime());
-        notePersistentAppCrash(/*numTimes=*/1);
-
-        assertEquals(RescueParty.LEVEL_NONE,
-                SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
-    }
-
-    @Test
-    public void testBootLoopDetectionWithProperInterval() {
-        noteBoot(RescueParty.TRIGGER_COUNT - 1);
-
-        // last boot is just inside of the boot loop detection window
-        doReturn(CURRENT_NETWORK_TIME_MILLIS + RescueParty.BOOT_TRIGGER_WINDOW_MILLIS).when(
-                () -> RescueParty.getElapsedRealtime());
-        noteBoot(/*numTimes=*/1);
-
-        verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
+        // Record DeviceConfig accesses
+        RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
+        RemoteCallback monitorCallback = mMonitorCallbackCaptor.getValue();
+        monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE1));
+        monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE2));
+        monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE2));
+        // Fake DeviceConfig value changes
+        monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE1));
+        verify(mMockPackageWatchdog).startObservingHealth(observer,
+                Arrays.asList(CALLING_PACKAGE1), RescueParty.DEFAULT_OBSERVING_DURATION_MS);
+        monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE2));
+        verify(mMockPackageWatchdog, times(2)).startObservingHealth(eq(observer),
+                mPackageListCaptor.capture(),
+                eq(RescueParty.DEFAULT_OBSERVING_DURATION_MS));
+        assertTrue(mPackageListCaptor.getValue().containsAll(
+                Arrays.asList(CALLING_PACKAGE1, CALLING_PACKAGE2)));
+        // Perform and verify scoped resets
+        final String[] expectedResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2};
+        observer.execute(new VersionedPackage(
+                CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+        verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, expectedResetNamespaces);
         assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
                 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
-    }
 
-    @Test
-    public void testPersistentAppCrashDetectionWithProperInterval() {
-        notePersistentAppCrash(RescueParty.TRIGGER_COUNT - 1);
-
-        // last persistent app crash is just inside of the boot loop detection window
-        doReturn(CURRENT_NETWORK_TIME_MILLIS
-                + RescueParty.PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS)
-                .when(() -> RescueParty.getElapsedRealtime());
-        notePersistentAppCrash(/*numTimes=*/1);
-
-        verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
-        assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
+        observer.execute(new VersionedPackage(
+                CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);
+        verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, expectedResetNamespaces);
+        assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES,
                 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
-    }
 
-    @Test
-    public void testBootLoopDetectionWithWrongTriggerCount() {
-        noteBoot(RescueParty.TRIGGER_COUNT - 1);
-        assertEquals(RescueParty.LEVEL_NONE,
+        observer.execute(new VersionedPackage(
+                CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);
+        verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, /*resetNamespaces=*/null);
+        assertEquals(RescueParty.LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS,
                 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
-    }
 
-    @Test
-    public void testPersistentAppCrashDetectionWithWrongTriggerCount() {
-        notePersistentAppCrash(RescueParty.TRIGGER_COUNT - 1);
-        assertEquals(RescueParty.LEVEL_NONE,
-                SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
+        observer.execute(new VersionedPackage(
+                CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+        verify(() -> RecoverySystem.rebootPromptAndWipeUserData(mMockContext, RescueParty.TAG));
+        assertTrue(RescueParty.isAttemptingFactoryReset());
     }
 
     @Test
     public void testIsAttemptingFactoryReset() {
-        noteBoot(RescueParty.TRIGGER_COUNT * 4);
-
+        for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
+            noteBoot();
+        }
         verify(() -> RecoverySystem.rebootPromptAndWipeUserData(mMockContext, RescueParty.TAG));
         assertTrue(RescueParty.isAttemptingFactoryReset());
     }
@@ -291,7 +295,7 @@
 
         RescueParty.onSettingsProviderPublished(mMockContext);
 
-        verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
+        verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null);
         assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
                 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
     }
@@ -308,33 +312,116 @@
                 FAKE_NATIVE_NAMESPACE1));
         verify(() -> DeviceConfig.resetToDefaults(Settings.RESET_MODE_TRUSTED_DEFAULTS,
                 FAKE_NATIVE_NAMESPACE2));
-
-        ExtendedMockito.verify(
-                () -> DeviceConfig.setProperty(FlagNamespaceUtils.NAMESPACE_RESCUE_PARTY,
-                        FlagNamespaceUtils.RESET_PLATFORM_PACKAGE_FLAG + 0,
-                        FAKE_NATIVE_NAMESPACE1, /*makeDefault=*/true));
-        ExtendedMockito.verify(
-                () -> DeviceConfig.setProperty(FlagNamespaceUtils.NAMESPACE_RESCUE_PARTY,
-                        FlagNamespaceUtils.RESET_PLATFORM_PACKAGE_FLAG + 1,
-                        FAKE_NATIVE_NAMESPACE2, /*makeDefault=*/true));
     }
 
-    private void verifySettingsResets(int resetMode) {
+    @Test
+    public void testExplicitlyEnablingAndDisablingRescue() {
+        SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false));
+        SystemProperties.set(PROP_DISABLE_RESCUE, Boolean.toString(true));
+        assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
+                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING), false);
+
+        SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
+        assertTrue(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
+                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING));
+    }
+
+    @Test
+    public void testHealthCheckLevels() {
+        RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
+
+        // Ensure that no action is taken for cases where the failure reason is unknown
+        SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
+                LEVEL_FACTORY_RESET));
+        assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_UNKNOWN),
+                PackageHealthObserverImpact.USER_IMPACT_NONE);
+
+        /*
+        For the following cases, ensure that the returned user impact corresponds with the user
+        impact of the next available rescue level, not the current one.
+         */
+        SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
+                RescueParty.LEVEL_NONE));
+        assertEquals(observer.onHealthCheckFailed(null,
+                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING),
+                PackageHealthObserverImpact.USER_IMPACT_LOW);
+
+        SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
+                RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS));
+        assertEquals(observer.onHealthCheckFailed(null,
+                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING),
+                PackageHealthObserverImpact.USER_IMPACT_LOW);
+
+
+        SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
+                RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES));
+        assertEquals(observer.onHealthCheckFailed(null,
+                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING),
+                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+
+
+        SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
+                RescueParty.LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS));
+        assertEquals(observer.onHealthCheckFailed(null,
+                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING),
+                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+
+
+        SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
+                LEVEL_FACTORY_RESET));
+        assertEquals(observer.onHealthCheckFailed(null,
+                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING),
+                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+    }
+
+    @Test
+    public void testRescueLevelIncrementsWhenExecuted() {
+        RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
+        SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
+                RescueParty.LEVEL_NONE));
+        observer.execute(sFailingPackage,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH);
+        assertEquals(SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, -1),
+                RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS);
+    }
+
+    private void verifySettingsResets(int resetMode, String[] resetNamespaces) {
         verify(() -> Settings.Global.resetToDefaultsAsUser(mMockContentResolver, null,
                 resetMode, UserHandle.USER_SYSTEM));
         verify(() -> Settings.Secure.resetToDefaultsAsUser(eq(mMockContentResolver), isNull(),
                 eq(resetMode), anyInt()));
-    }
-
-    private void noteBoot(int numTimes) {
-        for (int i = 0; i < numTimes; i++) {
-            RescueParty.noteBoot(mMockContext);
+        // Verify DeviceConfig resets
+        if (resetNamespaces == null) {
+            verify(() -> DeviceConfig.resetToDefaults(resetMode, /*namespace=*/ null));
+        } else {
+            for (String namespace : resetNamespaces) {
+                verify(() -> DeviceConfig.resetToDefaults(resetMode, namespace));
+            }
         }
     }
 
-    private void notePersistentAppCrash(int numTimes) {
-        for (int i = 0; i < numTimes; i++) {
-            RescueParty.noteAppCrash(mMockContext, PERSISTENT_APP_UID);
-        }
+    private void noteBoot() {
+        RescuePartyObserver.getInstance(mMockContext).executeBootLoopMitigation();
+    }
+
+    private void notePersistentAppCrash() {
+        RescuePartyObserver.getInstance(mMockContext).execute(new VersionedPackage(
+                "com.package.name", 1), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+    }
+
+    private Bundle getConfigAccessBundle(String callingPackage, String namespace) {
+        Bundle result = new Bundle();
+        result.putString(Settings.EXTRA_MONITOR_CALLBACK_TYPE, Settings.EXTRA_ACCESS_CALLBACK);
+        result.putString(Settings.EXTRA_CALLING_PACKAGE, callingPackage);
+        result.putString(Settings.EXTRA_NAMESPACE, namespace);
+        return result;
+    }
+
+    private Bundle getConfigNamespaceUpdateBundle(String updatedNamespace) {
+        Bundle result = new Bundle();
+        result.putString(Settings.EXTRA_MONITOR_CALLBACK_TYPE,
+                Settings.EXTRA_NAMESPACE_UPDATED_CALLBACK);
+        result.putString(Settings.EXTRA_NAMESPACE, updatedNamespace);
+        return result;
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java
deleted file mode 100644
index 48e459f..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java
+++ /dev/null
@@ -1,680 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.am;
-
-import static com.android.server.am.ActivityManagerService.Injector;
-import static com.android.server.am.AppCompactor.compactActionIntToString;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.Context;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Process;
-import android.platform.test.annotations.Presubmit;
-import android.provider.DeviceConfig;
-import android.text.TextUtils;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.server.ServiceThread;
-import com.android.server.appop.AppOpsService;
-import com.android.server.testables.TestableDeviceConfig;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-import java.io.File;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests for {@link AppCompactor}.
- *
- * Build/Install/Run:
- * atest FrameworksMockingServicesTests:AppCompactorTest
- */
-@Presubmit
-@RunWith(MockitoJUnitRunner.class)
-public final class AppCompactorTest {
-
-    private ServiceThread mThread;
-
-    @Mock
-    private AppOpsService mAppOpsService;
-    private AppCompactor mCompactorUnderTest;
-    private HandlerThread mHandlerThread;
-    private Handler mHandler;
-    private CountDownLatch mCountDown;
-
-    @Rule
-    public TestableDeviceConfig.TestableDeviceConfigRule
-            mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
-
-    @Before
-    public void setUp() {
-        mHandlerThread = new HandlerThread("");
-        mHandlerThread.start();
-        mHandler = new Handler(mHandlerThread.getLooper());
-
-        mThread = new ServiceThread("TestServiceThread", Process.THREAD_PRIORITY_DEFAULT,
-                true /* allowIo */);
-        mThread.start();
-
-        ActivityManagerService ams = new ActivityManagerService(
-                new TestInjector(InstrumentationRegistry.getInstrumentation().getContext()),
-                mThread);
-        mCompactorUnderTest = new AppCompactor(ams,
-                new AppCompactor.PropertyChangedCallbackForTest() {
-                    @Override
-                    public void onPropertyChanged() {
-                        if (mCountDown != null) {
-                            mCountDown.countDown();
-                        }
-                    }
-                });
-    }
-
-    @After
-    public void tearDown() {
-        mHandlerThread.quit();
-        mThread.quit();
-        mCountDown = null;
-    }
-
-    @Test
-    public void init_setsDefaults() {
-        mCompactorUnderTest.init();
-        assertThat(mCompactorUnderTest.useCompaction()).isEqualTo(
-                AppCompactor.DEFAULT_USE_COMPACTION);
-        assertThat(mCompactorUnderTest.mCompactActionSome).isEqualTo(
-                compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_1));
-        assertThat(mCompactorUnderTest.mCompactActionFull).isEqualTo(
-                compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_2));
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
-        assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(
-                AppCompactor.DEFAULT_STATSD_SAMPLE_RATE);
-        assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
-        assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
-        assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
-        assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
-        assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
-
-        Set<Integer> expected = new HashSet<>();
-        for (String s : TextUtils.split(AppCompactor.DEFAULT_COMPACT_PROC_STATE_THROTTLE, ",")) {
-            expected.add(Integer.parseInt(s));
-        }
-        assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
-    }
-
-    @Test
-    public void init_withDeviceConfigSetsParameters() {
-        // When the DeviceConfig already has a flag value stored (note this test will need to
-        // change if the default value changes from false).
-        assertThat(AppCompactor.DEFAULT_USE_COMPACTION).isFalse();
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_USE_COMPACTION, "true", false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_ACTION_1,
-                Integer.toString((AppCompactor.DEFAULT_COMPACT_ACTION_1 + 1 % 4) + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_ACTION_2,
-                Integer.toString((AppCompactor.DEFAULT_COMPACT_ACTION_2 + 1 % 4) + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_1,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_2,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_3,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_4,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_5,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_6,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE,
-                Float.toString(AppCompactor.DEFAULT_STATSD_SAMPLE_RATE + 0.1f), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false);
-
-        // Then calling init will read and set that flag.
-        mCompactorUnderTest.init();
-        assertThat(mCompactorUnderTest.useCompaction()).isTrue();
-        assertThat(mCompactorUnderTest.mCompactionThread.isAlive()).isTrue();
-
-        assertThat(mCompactorUnderTest.mCompactActionSome).isEqualTo(
-                compactActionIntToString((AppCompactor.DEFAULT_COMPACT_ACTION_1 + 1 % 4) + 1));
-        assertThat(mCompactorUnderTest.mCompactActionFull).isEqualTo(
-                compactActionIntToString((AppCompactor.DEFAULT_COMPACT_ACTION_2 + 1 % 4) + 1));
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1);
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1);
-        assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1);
-        assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1);
-        assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(
-                AppCompactor.DEFAULT_STATSD_SAMPLE_RATE + 0.1f);
-        assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1);
-        assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1);
-        assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1);
-        assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactly(1, 2, 3);
-    }
-
-    @Test
-    public void useCompaction_listensToDeviceConfigChanges() throws InterruptedException {
-        assertThat(mCompactorUnderTest.useCompaction()).isEqualTo(
-                AppCompactor.DEFAULT_USE_COMPACTION);
-        // When we call init and change some the flag value...
-        mCompactorUnderTest.init();
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_USE_COMPACTION, "true", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        // Then that new flag value is updated in the implementation.
-        assertThat(mCompactorUnderTest.useCompaction()).isTrue();
-        assertThat(mCompactorUnderTest.mCompactionThread.isAlive()).isTrue();
-
-        // And again, setting the flag the other way.
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_USE_COMPACTION, "false", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-        assertThat(mCompactorUnderTest.useCompaction()).isFalse();
-    }
-
-    @Test
-    public void useCompaction_listensToDeviceConfigChangesBadValues() throws InterruptedException {
-        assertThat(mCompactorUnderTest.useCompaction()).isEqualTo(
-                AppCompactor.DEFAULT_USE_COMPACTION);
-        mCompactorUnderTest.init();
-
-        // When we push an invalid flag value...
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_USE_COMPACTION, "foobar", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        // Then we set the default.
-        assertThat(mCompactorUnderTest.useCompaction()).isEqualTo(
-                AppCompactor.DEFAULT_USE_COMPACTION);
-    }
-
-    @Test
-    public void compactAction_listensToDeviceConfigChanges() throws InterruptedException {
-        mCompactorUnderTest.init();
-
-        // When we override new values for the compaction action with reasonable values...
-
-        // There are four possible values for compactAction[Some|Full].
-        for (int i = 1; i < 5; i++) {
-            mCountDown = new CountDownLatch(2);
-            int expectedSome = (AppCompactor.DEFAULT_COMPACT_ACTION_1 + i) % 4 + 1;
-            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                    AppCompactor.KEY_COMPACT_ACTION_1, Integer.toString(expectedSome), false);
-            int expectedFull = (AppCompactor.DEFAULT_COMPACT_ACTION_2 + i) % 4 + 1;
-            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                    AppCompactor.KEY_COMPACT_ACTION_2, Integer.toString(expectedFull), false);
-            assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-            // Then the updates are reflected in the flags.
-            assertThat(mCompactorUnderTest.mCompactActionSome).isEqualTo(
-                    compactActionIntToString(expectedSome));
-            assertThat(mCompactorUnderTest.mCompactActionFull).isEqualTo(
-                    compactActionIntToString(expectedFull));
-        }
-    }
-
-    @Test
-    public void compactAction_listensToDeviceConfigChangesBadValues() throws InterruptedException {
-        mCompactorUnderTest.init();
-
-        // When we override new values for the compaction action with bad values ...
-        mCountDown = new CountDownLatch(2);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_ACTION_1, "foo", false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_ACTION_2, "foo", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        // Then the default values are reflected in the flag
-        assertThat(mCompactorUnderTest.mCompactActionSome).isEqualTo(
-                compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_1));
-        assertThat(mCompactorUnderTest.mCompactActionFull).isEqualTo(
-                compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_2));
-
-        mCountDown = new CountDownLatch(2);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_ACTION_1, "", false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_ACTION_2, "", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        assertThat(mCompactorUnderTest.mCompactActionSome).isEqualTo(
-                compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_1));
-        assertThat(mCompactorUnderTest.mCompactActionFull).isEqualTo(
-                compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_2));
-    }
-
-    @Test
-    public void compactThrottle_listensToDeviceConfigChanges() throws InterruptedException {
-        mCompactorUnderTest.init();
-
-        // When we override new reasonable throttle values after init...
-        mCountDown = new CountDownLatch(6);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_1,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_2,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_3,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_4,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_5,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_6,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1), false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        // Then those flags values are reflected in the compactor.
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1);
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1);
-        assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1);
-        assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1);
-    }
-
-    @Test
-    public void compactThrottle_listensToDeviceConfigChangesBadValues()
-            throws InterruptedException {
-        mCompactorUnderTest.init();
-
-        // When one of the throttles is overridden with a bad value...
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_1, "foo", false);
-        // Then all the throttles have the defaults set.
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
-        assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
-        assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
-
-        // Repeat for each of the throttle keys.
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_2, "foo", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
-        assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
-        assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
-
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_3, "foo", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
-        assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
-        assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
-
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_4, "foo", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
-        assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
-        assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
-
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_5, "foo", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
-        assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
-        assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
-
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_THROTTLE_6, "foo", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
-        assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
-        assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
-        assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
-        assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
-    }
-
-    @Test
-    public void statsdSampleRate_listensToDeviceConfigChanges() throws InterruptedException {
-        mCompactorUnderTest.init();
-
-        // When we override mStatsdSampleRate with a reasonable value ...
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE,
-                Float.toString(AppCompactor.DEFAULT_STATSD_SAMPLE_RATE + 0.1f), false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        // Then that override is reflected in the compactor.
-        assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(
-                AppCompactor.DEFAULT_STATSD_SAMPLE_RATE + 0.1f);
-    }
-
-    @Test
-    public void statsdSampleRate_listensToDeviceConfigChangesBadValues()
-            throws InterruptedException {
-        mCompactorUnderTest.init();
-
-        // When we override mStatsdSampleRate with an unreasonable value ...
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE, "foo", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        // Then that override is reflected in the compactor.
-        assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(
-                AppCompactor.DEFAULT_STATSD_SAMPLE_RATE);
-    }
-
-    @Test
-    public void statsdSampleRate_listensToDeviceConfigChangesOutOfRangeValues()
-            throws InterruptedException {
-        mCompactorUnderTest.init();
-
-        // When we override mStatsdSampleRate with an value outside of [0..1]...
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE,
-                Float.toString(-1.0f), false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        // Then the values is capped in the range.
-        assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(0.0f);
-
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE,
-                Float.toString(1.01f), false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        // Then the values is capped in the range.
-        assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(1.0f);
-    }
-
-    @Test
-    public void fullCompactionRssThrottleKb_listensToDeviceConfigChanges()
-            throws InterruptedException {
-        mCompactorUnderTest.init();
-
-        // When we override mStatsdSampleRate with a reasonable value ...
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1), false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        // Then that override is reflected in the compactor.
-        assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1);
-    }
-
-    @Test
-    public void fullCompactionRssThrottleKb_listensToDeviceConfigChangesBadValues()
-            throws InterruptedException {
-        mCompactorUnderTest.init();
-
-        // When we override mStatsdSampleRate with an unreasonable value ...
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "foo", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        // Then that override is reflected in the compactor.
-        assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
-
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "-100", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        // Then that override is reflected in the compactor.
-        assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
-    }
-
-    @Test
-    public void fullCompactionDeltaRssThrottleKb_listensToDeviceConfigChanges()
-            throws InterruptedException {
-        mCompactorUnderTest.init();
-
-        // When we override mStatsdSampleRate with a reasonable value ...
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB,
-                Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        // Then that override is reflected in the compactor.
-        assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1);
-    }
-
-    @Test
-    public void fullCompactionDeltaRssThrottleKb_listensToDeviceConfigChangesBadValues()
-            throws InterruptedException {
-        mCompactorUnderTest.init();
-
-        // When we override mStatsdSampleRate with an unreasonable value ...
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "foo", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        // Then that override is reflected in the compactor.
-        assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
-
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "-100", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        // Then that override is reflected in the compactor.
-        assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
-                AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
-    }
-
-    @Test
-    public void procStateThrottle_listensToDeviceConfigChanges()
-            throws InterruptedException {
-        mCompactorUnderTest.init();
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-        assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactly(1, 2, 3);
-
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-        assertThat(mCompactorUnderTest.mProcStateThrottle).isEmpty();
-    }
-
-    @Test
-    public void procStateThrottle_listensToDeviceConfigChangesBadValues()
-            throws InterruptedException {
-        mCompactorUnderTest.init();
-
-        Set<Integer> expected = new HashSet<>();
-        for (String s : TextUtils.split(AppCompactor.DEFAULT_COMPACT_PROC_STATE_THROTTLE, ",")) {
-            expected.add(Integer.parseInt(s));
-        }
-
-        // Not numbers
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "foo", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-        assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,foo", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-        assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
-
-        // Empty splits
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, ",", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-        assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, ",,3", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-        assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
-        mCountDown = new CountDownLatch(1);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,,3", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-        assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
-    }
-
-    private class TestInjector extends Injector {
-
-        TestInjector(Context context) {
-            super(context);
-        }
-
-        @Override
-        public AppOpsService getAppOpsService(File file, Handler handler) {
-            return mAppOpsService;
-        }
-
-        @Override
-        public Handler getUiHandler(ActivityManagerService service) {
-            return mHandler;
-        }
-    }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
new file mode 100644
index 0000000..8d2a152
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
@@ -0,0 +1,954 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
+import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
+import static android.app.ActivityManager.PROCESS_STATE_RECEIVER;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.server.am.ActivityManagerService.Injector;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.app.ApplicationExitInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManagerInternal;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+import android.system.OsConstants;
+import android.text.TextUtils;
+import android.util.Pair;
+
+import com.android.server.ServiceThread;
+import com.android.server.appop.AppOpsService;
+import com.android.server.wm.ActivityTaskManagerService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+
+/**
+ * Test class for {@link android.app.ApplicationExitInfo}.
+ *
+ * Build/Install/Run:
+ *  atest ApplicationExitInfoTest
+ */
+@Presubmit
+public class ApplicationExitInfoTest {
+    private static final String TAG = ApplicationExitInfoTest.class.getSimpleName();
+
+    @Rule public ServiceThreadRule mServiceThreadRule = new ServiceThreadRule();
+    @Mock private AppOpsService mAppOpsService;
+    @Mock private PackageManagerInternal mPackageManagerInt;
+
+    private Context mContext = getInstrumentation().getTargetContext();
+    private TestInjector mInjector;
+    private ActivityManagerService mAms;
+    private ProcessList mProcessList;
+    private AppExitInfoTracker mAppExitInfoTracker;
+    private Handler mHandler;
+    private HandlerThread mHandlerThread;
+
+    @BeforeClass
+    public static void setUpOnce() {
+        System.setProperty("dexmaker.share_classloader", "true");
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+        mProcessList = spy(new ProcessList());
+        mAppExitInfoTracker = spy(new AppExitInfoTracker());
+        setFieldValue(AppExitInfoTracker.class, mAppExitInfoTracker, "mIsolatedUidRecords",
+                spy(mAppExitInfoTracker.new IsolatedUidRecords()));
+        setFieldValue(AppExitInfoTracker.class, mAppExitInfoTracker, "mAppExitInfoSourceZygote",
+                spy(mAppExitInfoTracker.new AppExitInfoExternalSource("zygote", null)));
+        setFieldValue(AppExitInfoTracker.class, mAppExitInfoTracker, "mAppExitInfoSourceLmkd",
+                spy(mAppExitInfoTracker.new AppExitInfoExternalSource("lmkd",
+                ApplicationExitInfo.REASON_LOW_MEMORY)));
+        setFieldValue(ProcessList.class, mProcessList, "mAppExitInfoTracker", mAppExitInfoTracker);
+        mInjector = new TestInjector(mContext);
+        mAms = new ActivityManagerService(mInjector, mServiceThreadRule.getThread());
+        mAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
+        mAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
+        mAms.mAtmInternal = spy(mAms.mActivityTaskManager.getAtmInternal());
+        mAms.mPackageManagerInt = mPackageManagerInt;
+    }
+
+    @After
+    public void tearDown() {
+        mHandlerThread.quit();
+    }
+
+    private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
+        try {
+            Field field = clazz.getDeclaredField(fieldName);
+            field.setAccessible(true);
+            Field mfield = Field.class.getDeclaredField("accessFlags");
+            mfield.setAccessible(true);
+            mfield.setInt(field, mfield.getInt(field) & ~(Modifier.FINAL | Modifier.PRIVATE));
+            field.set(obj, val);
+        } catch (NoSuchFieldException | IllegalAccessException e) {
+        }
+    }
+
+    private void updateExitInfo(ProcessRecord app) {
+        ApplicationExitInfo raw = mAppExitInfoTracker.obtainRawRecordLocked(app);
+        mAppExitInfoTracker.handleNoteProcessDiedLocked(raw);
+        mAppExitInfoTracker.recycleRawRecordLocked(raw);
+    }
+
+    private void noteAppKill(ProcessRecord app, int reason, int subReason, String msg) {
+        ApplicationExitInfo raw = mAppExitInfoTracker.obtainRawRecordLocked(app);
+        raw.setReason(reason);
+        raw.setSubReason(subReason);
+        raw.setDescription(msg);
+        mAppExitInfoTracker.handleNoteAppKillLocked(raw);
+        mAppExitInfoTracker.recycleRawRecordLocked(raw);
+    }
+
+    @Test
+    public void testApplicationExitInfo() throws Exception {
+        mAppExitInfoTracker.clearProcessExitInfo(true);
+        mAppExitInfoTracker.mAppExitInfoLoaded = true;
+
+        // Test application calls System.exit()
+        doNothing().when(mAppExitInfoTracker).schedulePersistProcessExitInfo(anyBoolean());
+
+        final int app1Uid = 10123;
+        final int app1Pid1 = 12345;
+        final int app1Pid2 = 12346;
+        final int app1DefiningUid = 23456;
+        final int app1ConnectiongGroup = 10;
+        final int app1UidUser2 = 1010123;
+        final int app1PidUser2 = 12347;
+        final int app1Pss1 = 34567;
+        final int app1Rss1 = 45678;
+        final int app1Pss2 = 34568;
+        final int app1Rss2 = 45679;
+        final int app1Pss3 = 34569;
+        final int app1Rss3 = 45680;
+        final String app1ProcessName = "com.android.test.stub1:process";
+        final String app1PackageName = "com.android.test.stub1";
+
+        final long now1 = System.currentTimeMillis();
+        ProcessRecord app = makeProcessRecord(
+                app1Pid1,                    // pid
+                app1Uid,                     // uid
+                app1Uid,                     // packageUid
+                null,                        // definingUid
+                0,                           // connectionGroup
+                PROCESS_STATE_LAST_ACTIVITY, // procstate
+                app1Pss1,                    // pss
+                app1Rss1,                    // rss
+                app1ProcessName,             // processName
+                app1PackageName);            // packageName
+
+        // Case 1: basic System.exit() test
+        int exitCode = 5;
+        doReturn(new Pair<Long, Object>(now1, Integer.valueOf(makeExitStatus(exitCode))))
+                .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
+                .remove(anyInt(), anyInt());
+        doReturn(null)
+                .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
+                .remove(anyInt(), anyInt());
+        updateExitInfo(app);
+
+        ArrayList<ApplicationExitInfo> list = new ArrayList<ApplicationExitInfo>();
+        mAppExitInfoTracker.getExitInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+        assertEquals(1, list.size());
+
+        ApplicationExitInfo info = list.get(0);
+
+        verifyApplicationExitInfo(
+                info,                                 // info
+                now1,                                 // timestamp
+                app1Pid1,                             // pid
+                app1Uid,                              // uid
+                app1Uid,                              // packageUid
+                null,                                 // definingUid
+                app1ProcessName,                      // processName
+                0,                                    // connectionGroup
+                ApplicationExitInfo.REASON_EXIT_SELF, // reason
+                null,                                 // subReason
+                exitCode,                             // status
+                app1Pss1,                             // pss
+                app1Rss1,                             // rss
+                IMPORTANCE_CACHED,                    // importance
+                null);                                // description
+
+        // Case 2: create another app1 process record with a different pid
+        sleep(1);
+        final long now2 = System.currentTimeMillis();
+        app = makeProcessRecord(
+                app1Pid2,               // pid
+                app1Uid,                // uid
+                app1Uid,                // packageUid
+                app1DefiningUid,        // definingUid
+                app1ConnectiongGroup,   // connectionGroup
+                PROCESS_STATE_RECEIVER, // procstate
+                app1Pss2,               // pss
+                app1Rss2,               // rss
+                app1ProcessName,        // processName
+                app1PackageName);       // packageName
+        exitCode = 6;
+        doReturn(new Pair<Long, Object>(now2, Integer.valueOf(makeExitStatus(exitCode))))
+                .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
+                .remove(anyInt(), anyInt());
+        updateExitInfo(app);
+        list.clear();
+
+        // Get all the records for app1Uid
+        mAppExitInfoTracker.getExitInfo(app1PackageName, app1Uid, 0, 0, list);
+        assertEquals(2, list.size());
+
+        info = list.get(0);
+
+        // Verify the most recent one
+        verifyApplicationExitInfo(
+                info,                                 // info
+                now2,                                 // timestamp
+                app1Pid2,                             // pid
+                app1Uid,                              // uid
+                app1Uid,                              // packageUid
+                app1DefiningUid,                      // definingUid
+                app1ProcessName,                      // processName
+                app1ConnectiongGroup,                 // connectionGroup
+                ApplicationExitInfo.REASON_EXIT_SELF, // reason
+                null,                                 // subReason
+                exitCode,                             // status
+                app1Pss2,                             // pss
+                app1Rss2,                             // rss
+                IMPORTANCE_SERVICE,                   // importance
+                null);                                // description
+
+        // Case 3: Create an instance of app1 with different user, and died because of SIGKILL
+        sleep(1);
+        final long now3 = System.currentTimeMillis();
+        int sigNum = OsConstants.SIGKILL;
+        app = makeProcessRecord(
+                app1PidUser2,                           // pid
+                app1UidUser2,                           // uid
+                app1UidUser2,                           // packageUid
+                null,                                   // definingUid
+                0,                                      // connectionGroup
+                PROCESS_STATE_BOUND_FOREGROUND_SERVICE, // procstate
+                app1Pss3,                               // pss
+                app1Rss3,                               // rss
+                app1ProcessName,                        // processName
+                app1PackageName);                       // packageName
+        doReturn(new Pair<Long, Object>(now3, Integer.valueOf(makeSignalStatus(sigNum))))
+                .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
+                .remove(anyInt(), anyInt());
+        updateExitInfo(app);
+        list.clear();
+        mAppExitInfoTracker.getExitInfo(app1PackageName, app1UidUser2, app1PidUser2, 0, list);
+
+        assertEquals(1, list.size());
+
+        info = list.get(0);
+
+        verifyApplicationExitInfo(
+                info,                                // info
+                now3,                                // timestamp
+                app1PidUser2,                        // pid
+                app1UidUser2,                        // uid
+                app1UidUser2,                        // packageUid
+                null,                                // definingUid
+                app1ProcessName,                     // processName
+                0,                                   // connectionGroup
+                ApplicationExitInfo.REASON_SIGNALED, // reason
+                null,                                 // subReason
+                sigNum,                              // status
+                app1Pss3,                            // pss
+                app1Rss3,                            // rss
+                IMPORTANCE_FOREGROUND_SERVICE,       // importance
+                null);                               // description
+
+        // try go get all from app1UidUser2
+        list.clear();
+        mAppExitInfoTracker.getExitInfo(app1PackageName, app1UidUser2, 0, 0, list);
+        assertEquals(1, list.size());
+
+        info = list.get(0);
+
+        verifyApplicationExitInfo(
+                info,                                // info
+                now3,                                // timestamp
+                app1PidUser2,                        // pid
+                app1UidUser2,                        // uid
+                app1UidUser2,                        // packageUid
+                null,                                // definingUid
+                app1ProcessName,                     // processName
+                0,                                   // connectionGroup
+                ApplicationExitInfo.REASON_SIGNALED, // reason
+                null,                                // subReason
+                sigNum,                              // status
+                app1Pss3,                            // pss
+                app1Rss3,                            // rss
+                IMPORTANCE_FOREGROUND_SERVICE,       // importance
+                null);                               // description
+
+        // Case 4: Create a process from another package with kill from lmkd
+        final int app2UidUser2 = 1010234;
+        final int app2PidUser2 = 12348;
+        final int app2Pss1 = 54321;
+        final int app2Rss1 = 65432;
+        final String app2ProcessName = "com.android.test.stub2:process";
+        final String app2PackageName = "com.android.test.stub2";
+
+        sleep(1);
+        final long now4 = System.currentTimeMillis();
+        doReturn(new Pair<Long, Object>(now4, Integer.valueOf(0)))
+                .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
+                .remove(anyInt(), anyInt());
+        doReturn(new Pair<Long, Object>(now4, null))
+                .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
+                .remove(anyInt(), anyInt());
+
+        app = makeProcessRecord(
+                app2PidUser2,                // pid
+                app2UidUser2,                // uid
+                app2UidUser2,                // packageUid
+                null,                        // definingUid
+                0,                           // connectionGroup
+                PROCESS_STATE_CACHED_EMPTY,  // procstate
+                app2Pss1,                    // pss
+                app2Rss1,                    // rss
+                app2ProcessName,             // processName
+                app2PackageName);            // packageName
+        updateExitInfo(app);
+        list.clear();
+        mAppExitInfoTracker.getExitInfo(app2PackageName, app2UidUser2, app2PidUser2, 0, list);
+        assertEquals(1, list.size());
+
+        info = list.get(0);
+
+        verifyApplicationExitInfo(
+                info,                                     // info
+                now4,                                     // timestamp
+                app2PidUser2,                             // pid
+                app2UidUser2,                             // uid
+                app2UidUser2,                             // packageUid
+                null,                                     // definingUid
+                app2ProcessName,                          // processName
+                0,                                        // connectionGroup
+                ApplicationExitInfo.REASON_LOW_MEMORY,    // reason
+                null,                                     // subReason
+                0,                                        // status
+                app2Pss1,                                 // pss
+                app2Rss1,                                 // rss
+                IMPORTANCE_CACHED,                        // importance
+                null);                                    // description
+
+        // Verify to get all from User2 regarding app2
+        list.clear();
+        mAppExitInfoTracker.getExitInfo(null, app2UidUser2, 0, 0, list);
+        assertEquals(1, list.size());
+
+        // Case 5: App native crash
+        final int app3UidUser2 = 1010345;
+        final int app3PidUser2 = 12349;
+        final int app3ConnectiongGroup = 4;
+        final int app3Pss1 = 54320;
+        final int app3Rss1 = 65430;
+        final String app3ProcessName = "com.android.test.stub3:process";
+        final String app3PackageName = "com.android.test.stub3";
+        final String app3Description = "native crash";
+
+        sleep(1);
+        final long now5 = System.currentTimeMillis();
+        sigNum = OsConstants.SIGABRT;
+        doReturn(new Pair<Long, Object>(now5, Integer.valueOf(makeSignalStatus(sigNum))))
+                .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
+                .remove(anyInt(), anyInt());
+        doReturn(null)
+                .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
+                .remove(anyInt(), anyInt());
+        app = makeProcessRecord(
+                app3PidUser2,            // pid
+                app3UidUser2,            // uid
+                app3UidUser2,            // packageUid
+                null,                    // definingUid
+                app3ConnectiongGroup,    // connectionGroup
+                PROCESS_STATE_BOUND_TOP, // procstate
+                app3Pss1,                // pss
+                app3Rss1,                // rss
+                app3ProcessName,         // processName
+                app3PackageName);        // packageName
+        noteAppKill(app, ApplicationExitInfo.REASON_CRASH_NATIVE,
+                ApplicationExitInfo.SUBREASON_UNKNOWN, app3Description);
+
+        updateExitInfo(app);
+        list.clear();
+        mAppExitInfoTracker.getExitInfo(app3PackageName, app3UidUser2, app3PidUser2, 0, list);
+        assertEquals(1, list.size());
+
+        info = list.get(0);
+
+        verifyApplicationExitInfo(
+                info,                                            // info
+                now5,                                            // timestamp
+                app3PidUser2,                                    // pid
+                app3UidUser2,                                    // uid
+                app3UidUser2,                                    // packageUid
+                null,                                            // definingUid
+                app3ProcessName,                                 // processName
+                app3ConnectiongGroup,                            // connectionGroup
+                ApplicationExitInfo.REASON_CRASH_NATIVE,         // reason
+                null,                                            // subReason
+                sigNum,                                          // status
+                app3Pss1,                                        // pss
+                app3Rss1,                                        // rss
+                IMPORTANCE_FOREGROUND,                           // importance
+                app3Description);                                // description
+
+        // Verify the most recent kills, sorted by timestamp
+        int maxNum = 3;
+        list.clear();
+        mAppExitInfoTracker.getExitInfo(null, app3UidUser2, 0, maxNum, list);
+        assertEquals(1, list.size());
+
+        info = list.get(0);
+
+        verifyApplicationExitInfo(
+                info,                                            // info
+                now5,                                            // timestamp
+                app3PidUser2,                                    // pid
+                app3UidUser2,                                    // uid
+                app3UidUser2,                                    // packageUid
+                null,                                            // definingUid
+                app3ProcessName,                                 // processName
+                app3ConnectiongGroup,                            // connectionGroup
+                ApplicationExitInfo.REASON_CRASH_NATIVE,         // reason
+                null,                                            // subReason
+                sigNum,                                          // status
+                app3Pss1,                                        // pss
+                app3Rss1,                                        // rss
+                IMPORTANCE_FOREGROUND,                           // importance
+                app3Description);                                // description
+
+        list.clear();
+        mAppExitInfoTracker.getExitInfo(null, app2UidUser2, 0, maxNum, list);
+        assertEquals(1, list.size());
+        info = list.get(0);
+
+        verifyApplicationExitInfo(
+                info,                                     // info
+                now4,                                     // timestamp
+                app2PidUser2,                             // pid
+                app2UidUser2,                             // uid
+                app2UidUser2,                             // packageUid
+                null,                                     // definingUid
+                app2ProcessName,                          // processName
+                0,                                        // connectionGroup
+                ApplicationExitInfo.REASON_LOW_MEMORY,    // reason
+                null,                                     // subReason
+                0,                                        // status
+                app2Pss1,                                 // pss
+                app2Rss1,                                 // rss
+                IMPORTANCE_CACHED,                        // importance
+                null);                                    // description
+
+        list.clear();
+        mAppExitInfoTracker.getExitInfo(null, app1UidUser2, 0, maxNum, list);
+        assertEquals(1, list.size());
+        info = list.get(0);
+
+        sigNum = OsConstants.SIGKILL;
+        verifyApplicationExitInfo(
+                info,                                // info
+                now3,                                // timestamp
+                app1PidUser2,                        // pid
+                app1UidUser2,                        // uid
+                app1UidUser2,                        // packageUid
+                null,                                // definingUid
+                app1ProcessName,                     // processName
+                0,                                   // connectionGroup
+                ApplicationExitInfo.REASON_SIGNALED, // reason
+                null,                                // subReason
+                sigNum,                              // status
+                app1Pss3,                            // pss
+                app1Rss3,                            // rss
+                IMPORTANCE_FOREGROUND_SERVICE,       // importance
+                null);                               // description
+
+        // Case 6: App Java crash
+        final int app3Uid = 10345;
+        final int app3IsolatedUid = 99001; // it's an isolated process
+        final int app3Pid = 12350;
+        final int app3Pss2 = 23232;
+        final int app3Rss2 = 32323;
+        final String app3Description2 = "force close";
+
+        sleep(1);
+        final long now6 = System.currentTimeMillis();
+        doReturn(null)
+                .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
+                .remove(anyInt(), anyInt());
+        doReturn(null)
+                .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
+                .remove(anyInt(), anyInt());
+        app = makeProcessRecord(
+                app3Pid,                     // pid
+                app3IsolatedUid,             // uid
+                app3Uid,                     // packageUid
+                null,                        // definingUid
+                0,                           // connectionGroup
+                PROCESS_STATE_CACHED_EMPTY,  // procstate
+                app3Pss2,                    // pss
+                app3Rss2,                    // rss
+                app3ProcessName,             // processName
+                app3PackageName);            // packageName
+        mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(app3IsolatedUid, app3Uid);
+        noteAppKill(app, ApplicationExitInfo.REASON_CRASH,
+                ApplicationExitInfo.SUBREASON_UNKNOWN, app3Description2);
+
+        assertEquals(app3Uid, mAppExitInfoTracker.mIsolatedUidRecords
+                .getUidByIsolatedUid(app3IsolatedUid).longValue());
+        updateExitInfo(app);
+        assertNull(mAppExitInfoTracker.mIsolatedUidRecords.getUidByIsolatedUid(app3IsolatedUid));
+
+        list.clear();
+        mAppExitInfoTracker.getExitInfo(app3PackageName, app3Uid, 0, 1, list);
+        assertEquals(1, list.size());
+
+        info = list.get(0);
+
+        verifyApplicationExitInfo(
+                info,                                     // info
+                now6,                                     // timestamp
+                app3Pid,                                  // pid
+                app3IsolatedUid,                          // uid
+                app3Uid,                                  // packageUid
+                null,                                     // definingUid
+                app3ProcessName,                          // processName
+                0,                                        // connectionGroup
+                ApplicationExitInfo.REASON_CRASH,         // reason
+                null,                                     // subReason
+                0,                                        // status
+                app3Pss2,                                 // pss
+                app3Rss2,                                 // rss
+                IMPORTANCE_CACHED,                        // importance
+                app3Description2);                        // description
+
+        // Case 7: App1 is "uninstalled" from User2
+        mAppExitInfoTracker.onPackageRemoved(app1PackageName, app1UidUser2, false);
+        list.clear();
+        mAppExitInfoTracker.getExitInfo(app1PackageName, app1UidUser2, 0, 0, list);
+        assertEquals(0, list.size());
+
+        list.clear();
+        mAppExitInfoTracker.getExitInfo(app1PackageName, app1Uid, 0, 0, list);
+        assertEquals(2, list.size());
+
+        info = list.get(0);
+
+        verifyApplicationExitInfo(
+                info,                                 // info
+                now2,                                 // timestamp
+                app1Pid2,                             // pid
+                app1Uid,                              // uid
+                app1Uid,                              // packageUid
+                app1DefiningUid,                      // definingUid
+                app1ProcessName,                      // processName
+                app1ConnectiongGroup,                 // connectionGroup
+                ApplicationExitInfo.REASON_EXIT_SELF, // reason
+                null,                                 // subReason
+                exitCode,                             // status
+                app1Pss2,                             // pss
+                app1Rss2,                             // rss
+                IMPORTANCE_SERVICE,                   // importance
+                null);                                // description
+
+        // Case 8: App1 gets "remove task"
+        final String app1Description = "remove task";
+
+        sleep(1);
+        final int app1IsolatedUidUser2 = 1099002; // isolated uid
+        final int app1Pss4 = 34343;
+        final int app1Rss4 = 43434;
+        final long now8 = System.currentTimeMillis();
+        sigNum = OsConstants.SIGKILL;
+        doReturn(new Pair<Long, Object>(now8, makeSignalStatus(sigNum)))
+                .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
+                .remove(anyInt(), anyInt());
+        doReturn(null)
+                .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
+                .remove(anyInt(), anyInt());
+        app = makeProcessRecord(
+                app1PidUser2,                 // pid
+                app1IsolatedUidUser2,         // uid
+                app1UidUser2,                 // packageUid
+                null,                         // definingUid
+                0,                            // connectionGroup
+                PROCESS_STATE_CACHED_EMPTY,   // procstate
+                app1Pss4,                     // pss
+                app1Rss4,                     // rss
+                app1ProcessName,              // processName
+                app1PackageName);             // packageName
+
+        mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(app1IsolatedUidUser2, app1UidUser2);
+        noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
+                ApplicationExitInfo.SUBREASON_UNKNOWN, app1Description);
+
+        updateExitInfo(app);
+        list.clear();
+        mAppExitInfoTracker.getExitInfo(app1PackageName, app1UidUser2, app1PidUser2, 1, list);
+        assertEquals(1, list.size());
+
+        info = list.get(0);
+
+        verifyApplicationExitInfo(
+                info,                                     // info
+                now8,                                     // timestamp
+                app1PidUser2,                             // pid
+                app1IsolatedUidUser2,                     // uid
+                app1UidUser2,                             // packageUid
+                null,                                     // definingUid
+                app1ProcessName,                          // processName
+                0,                                        // connectionGroup
+                ApplicationExitInfo.REASON_OTHER,         // reason
+                ApplicationExitInfo.SUBREASON_UNKNOWN,    // subReason
+                0,                                        // status
+                app1Pss4,                                 // pss
+                app1Rss4,                                 // rss
+                IMPORTANCE_CACHED,                        // importance
+                app1Description);                         // description
+
+        // App1 gets "too many empty"
+        final String app1Description2 = "too many empty";
+        sleep(1);
+        final int app1Pid2User2 = 56565;
+        final int app1IsolatedUid2User2 = 1099003; // isolated uid
+        final int app1Pss5 = 34344;
+        final int app1Rss5 = 43435;
+        final long now9 = System.currentTimeMillis();
+        sigNum = OsConstants.SIGKILL;
+        doReturn(new Pair<Long, Object>(now9, makeSignalStatus(sigNum)))
+                .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
+                .remove(anyInt(), anyInt());
+        doReturn(null)
+                .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
+                .remove(anyInt(), anyInt());
+        app = makeProcessRecord(
+                app1Pid2User2,                // pid
+                app1IsolatedUid2User2,        // uid
+                app1UidUser2,                 // packageUid
+                null,                         // definingUid
+                0,                            // connectionGroup
+                PROCESS_STATE_CACHED_EMPTY,   // procstate
+                app1Pss5,                     // pss
+                app1Rss5,                     // rss
+                app1ProcessName,              // processName
+                app1PackageName);             // packageName
+
+        mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(app1IsolatedUid2User2, app1UidUser2);
+        noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
+                ApplicationExitInfo.SUBREASON_TOO_MANY_EMPTY, app1Description2);
+
+        updateExitInfo(app);
+        list.clear();
+        mAppExitInfoTracker.getExitInfo(app1PackageName, app1UidUser2, app1Pid2User2, 1, list);
+        assertEquals(1, list.size());
+
+        info = list.get(0);
+
+        verifyApplicationExitInfo(
+                info,                                         // info
+                now9,                                         // timestamp
+                app1Pid2User2,                                // pid
+                app1IsolatedUid2User2,                        // uid
+                app1UidUser2,                                 // packageUid
+                null,                                         // definingUid
+                app1ProcessName,                              // processName
+                0,                                            // connectionGroup
+                ApplicationExitInfo.REASON_OTHER,             // reason
+                ApplicationExitInfo.SUBREASON_TOO_MANY_EMPTY, // subReason
+                0,                                            // status
+                app1Pss5,                                     // pss
+                app1Rss5,                                     // rss
+                IMPORTANCE_CACHED,                            // importance
+                app1Description2);                            // description
+
+
+        // Case 9: User2 gets removed
+        sleep(1);
+        mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(app1IsolatedUidUser2, app1UidUser2);
+        mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(app3IsolatedUid, app3Uid);
+
+        mAppExitInfoTracker.onUserRemoved(UserHandle.getUserId(app1UidUser2));
+
+        assertNull(mAppExitInfoTracker.mIsolatedUidRecords.getUidByIsolatedUid(
+                app1IsolatedUidUser2));
+        assertNotNull(mAppExitInfoTracker.mIsolatedUidRecords.getUidByIsolatedUid(
+                app3IsolatedUid));
+        mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(
+                app1IsolatedUidUser2, app1UidUser2);
+        mAppExitInfoTracker.mIsolatedUidRecords.removeAppUid(app1UidUser2, false);
+        assertNull(mAppExitInfoTracker.mIsolatedUidRecords.getUidByIsolatedUid(
+                app1IsolatedUidUser2));
+        mAppExitInfoTracker.mIsolatedUidRecords.removeAppUid(app3Uid, true);
+        assertNull(mAppExitInfoTracker.mIsolatedUidRecords.getUidByIsolatedUid(app3IsolatedUid));
+
+        list.clear();
+        mAppExitInfoTracker.getExitInfo(null, app1UidUser2, 0, 0, list);
+        assertEquals(0, list.size());
+
+        list.clear();
+        mAppExitInfoTracker.getExitInfo(null, app1Uid, 0, 0, list);
+        assertEquals(2, list.size());
+
+        info = list.get(0);
+
+        exitCode = 6;
+        verifyApplicationExitInfo(
+                info,                                 // info
+                now2,                                 // timestamp
+                app1Pid2,                             // pid
+                app1Uid,                              // uid
+                app1Uid,                              // packageUid
+                app1DefiningUid,                      // definingUid
+                app1ProcessName,                      // processName
+                app1ConnectiongGroup,                 // connectionGroup
+                ApplicationExitInfo.REASON_EXIT_SELF, // reason
+                null,                                 // subReason
+                exitCode,                             // status
+                app1Pss2,                             // pss
+                app1Rss2,                             // rss
+                IMPORTANCE_SERVICE,                   // importance
+                null);                                // description
+
+        info = list.get(1);
+        exitCode = 5;
+        verifyApplicationExitInfo(
+                info,                                 // info
+                now1,                                 // timestamp
+                app1Pid1,                             // pid
+                app1Uid,                              // uid
+                app1Uid,                              // packageUid
+                null,                                 // definingUid
+                app1ProcessName,                      // processName
+                0,                                    // connectionGroup
+                ApplicationExitInfo.REASON_EXIT_SELF, // reason
+                null,                                 // subReason
+                exitCode,                             // status
+                app1Pss1,                             // pss
+                app1Rss1,                             // rss
+                IMPORTANCE_CACHED,                    // importance
+                null);                                // description
+
+        // Case 10: Save the info and load them again
+        ArrayList<ApplicationExitInfo> original = new ArrayList<ApplicationExitInfo>();
+        mAppExitInfoTracker.getExitInfo(null, app1Uid, 0, 0, original);
+        assertTrue(original.size() > 0);
+
+        mAppExitInfoTracker.mProcExitInfoFile = new File(mContext.getFilesDir(),
+                AppExitInfoTracker.APP_EXIT_INFO_FILE);
+        mAppExitInfoTracker.persistProcessExitInfo();
+        assertTrue(mAppExitInfoTracker.mProcExitInfoFile.exists());
+
+        mAppExitInfoTracker.clearProcessExitInfo(false);
+        list.clear();
+        mAppExitInfoTracker.getExitInfo(null, app1Uid, 0, 0, list);
+        assertEquals(0, list.size());
+
+        mAppExitInfoTracker.loadExistingProcessExitInfo();
+        list.clear();
+        mAppExitInfoTracker.getExitInfo(null, app1Uid, 0, 0, list);
+        assertEquals(original.size(), list.size());
+
+        for (int i = list.size() - 1; i >= 0; i--) {
+            assertTrue(list.get(i).equals(original.get(i)));
+        }
+    }
+
+    private static int makeExitStatus(int exitCode) {
+        return (exitCode << 8) & 0xff00;
+    }
+
+    private static int makeSignalStatus(int sigNum) {
+        return sigNum & 0x7f;
+    }
+
+    private void sleep(long ms) {
+        try {
+            Thread.sleep(ms);
+        } catch (InterruptedException e) {
+        }
+    }
+
+    private ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid,
+            int connectionGroup, int procState, int pss, int rss,
+            String processName, String packageName) {
+        ApplicationInfo ai = new ApplicationInfo();
+        ai.packageName = packageName;
+        ProcessRecord app = new ProcessRecord(mAms, ai, processName, uid);
+        app.pid = pid;
+        app.info.uid = packageUid;
+        if (definingUid != null) {
+            final String dummyPackageName = "com.android.test";
+            final String dummyClassName = ".Foo";
+            app.hostingRecord = HostingRecord.byAppZygote(new ComponentName(
+                    dummyPackageName, dummyClassName), "", definingUid);
+        }
+        app.connectionGroup = connectionGroup;
+        app.setProcState = procState;
+        app.lastMemInfo = spy(new Debug.MemoryInfo());
+        doReturn(pss).when(app.lastMemInfo).getTotalPss();
+        doReturn(rss).when(app.lastMemInfo).getTotalRss();
+        return app;
+    }
+
+    private void verifyApplicationExitInfo(ApplicationExitInfo info,
+            Long timestamp, Integer pid, Integer uid, Integer packageUid,
+            Integer definingUid, String processName, Integer connectionGroup,
+            Integer reason, Integer subReason, Integer status,
+            Integer pss, Integer rss, Integer importance, String description) {
+        assertNotNull(info);
+
+        if (timestamp != null) {
+            final long tolerance = 1000; // ms
+            assertTrue(timestamp - tolerance <= info.getTimestamp());
+            assertTrue(timestamp + tolerance >= info.getTimestamp());
+        }
+        if (pid != null) {
+            assertEquals(pid.intValue(), info.getPid());
+        }
+        if (uid != null) {
+            assertEquals(uid.intValue(), info.getRealUid());
+        }
+        if (packageUid != null) {
+            assertEquals(packageUid.intValue(), info.getPackageUid());
+        }
+        if (definingUid != null) {
+            assertEquals(definingUid.intValue(), info.getDefiningUid());
+        }
+        if (processName != null) {
+            assertTrue(TextUtils.equals(processName, info.getProcessName()));
+        }
+        if (connectionGroup != null) {
+            assertEquals(connectionGroup.intValue(), info.getConnectionGroup());
+        }
+        if (reason != null) {
+            assertEquals(reason.intValue(), info.getReason());
+        }
+        if (subReason != null) {
+            assertEquals(subReason.intValue(), info.getSubReason());
+        }
+        if (status != null) {
+            assertEquals(status.intValue(), info.getStatus());
+        }
+        if (pss != null) {
+            assertEquals(pss.intValue(), info.getPss());
+        }
+        if (rss != null) {
+            assertEquals(rss.intValue(), info.getRss());
+        }
+        if (importance != null) {
+            assertEquals(importance.intValue(), info.getImportance());
+        }
+        if (description != null) {
+            assertTrue(TextUtils.equals(description, info.getDescription()));
+        }
+    }
+
+    private class TestInjector extends Injector {
+        TestInjector(Context context) {
+            super(context);
+        }
+
+        @Override
+        public AppOpsService getAppOpsService(File file, Handler handler) {
+            return mAppOpsService;
+        }
+
+        @Override
+        public Handler getUiHandler(ActivityManagerService service) {
+            return mHandler;
+        }
+
+        @Override
+        public ProcessList getProcessList(ActivityManagerService service) {
+            return mProcessList;
+        }
+    }
+
+    static class ServiceThreadRule implements TestRule {
+
+        private ServiceThread mThread;
+
+        ServiceThread getThread() {
+            return mThread;
+        }
+
+        @Override
+        public Statement apply(Statement base, Description description) {
+            return new Statement() {
+                @Override
+                public void evaluate() throws Throwable {
+                    mThread = new ServiceThread("TestServiceThread",
+                            Process.THREAD_PRIORITY_DEFAULT, true /* allowIo */);
+                    mThread.start();
+                    try {
+                        base.evaluate();
+                    } finally {
+                        mThread.getThreadHandler().runWithScissors(mThread::quit, 0 /* timeout */);
+                    }
+                }
+            };
+        }
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
new file mode 100644
index 0000000..f037692
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -0,0 +1,692 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static com.android.server.am.ActivityManagerService.Injector;
+import static com.android.server.am.CachedAppOptimizer.compactActionIntToString;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
+import android.text.TextUtils;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.ServiceThread;
+import com.android.server.appop.AppOpsService;
+import com.android.server.testables.TestableDeviceConfig;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.io.File;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for {@link CachedAppOptimizer}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksMockingServicesTests:CachedAppOptimizerTest
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public final class CachedAppOptimizerTest {
+
+    private ServiceThread mThread;
+
+    @Mock
+    private AppOpsService mAppOpsService;
+    private CachedAppOptimizer mCachedAppOptimizerUnderTest;
+    private HandlerThread mHandlerThread;
+    private Handler mHandler;
+    private CountDownLatch mCountDown;
+
+    @Rule
+    public TestableDeviceConfig.TestableDeviceConfigRule
+            mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
+
+    @Before
+    public void setUp() {
+        mHandlerThread = new HandlerThread("");
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+
+        mThread = new ServiceThread("TestServiceThread", Process.THREAD_PRIORITY_DEFAULT,
+                true /* allowIo */);
+        mThread.start();
+
+        ActivityManagerService ams = new ActivityManagerService(
+                new TestInjector(InstrumentationRegistry.getInstrumentation().getContext()),
+                mThread);
+        mCachedAppOptimizerUnderTest = new CachedAppOptimizer(ams,
+                new CachedAppOptimizer.PropertyChangedCallbackForTest() {
+                    @Override
+                    public void onPropertyChanged() {
+                        if (mCountDown != null) {
+                            mCountDown.countDown();
+                        }
+                    }
+                });
+    }
+
+    @After
+    public void tearDown() {
+        mHandlerThread.quit();
+        mThread.quit();
+        mCountDown = null;
+    }
+
+    @Test
+    public void init_setsDefaults() {
+        mCachedAppOptimizerUnderTest.init();
+        assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isEqualTo(
+                CachedAppOptimizer.DEFAULT_USE_COMPACTION);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome).isEqualTo(
+                compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1));
+        assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull).isEqualTo(
+                compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2));
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+        assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(
+                CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE);
+        assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+        assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
+        assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
+
+        Set<Integer> expected = new HashSet<>();
+        for (String s : TextUtils.split(
+                CachedAppOptimizer.DEFAULT_COMPACT_PROC_STATE_THROTTLE, ",")) {
+            expected.add(Integer.parseInt(s));
+        }
+        assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
+                .containsExactlyElementsIn(expected);
+    }
+
+    @Test
+    public void init_withDeviceConfigSetsParameters() {
+        // When the DeviceConfig already has a flag value stored (note this test will need to
+        // change if the default value changes from false).
+        assertThat(CachedAppOptimizer.DEFAULT_USE_COMPACTION).isFalse();
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_USE_COMPACTION, "true", false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_ACTION_1,
+                Integer.toString((CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1 + 1 % 4) + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_ACTION_2,
+                Integer.toString((CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2 + 1 % 4) + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_1,
+                Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_2,
+                Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2 + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_3,
+                Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3 + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_4,
+                Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4 + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_5,
+                Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_6,
+                Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_STATSD_SAMPLE_RATE,
+                Float.toString(CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_FULL_RSS_THROTTLE_KB,
+                Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB,
+                Long.toString(
+                        CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false);
+
+        // Then calling init will read and set that flag.
+        mCachedAppOptimizerUnderTest.init();
+        assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.mCachedAppOptimizerThread.isAlive()).isTrue();
+
+        assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome).isEqualTo(
+                compactActionIntToString(
+                        (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1 + 1 % 4) + 1));
+        assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull).isEqualTo(
+                compactActionIntToString(
+                        (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2 + 1 % 4) + 1));
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2 + 1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3 + 1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4 + 1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1);
+        assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(
+                CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1);
+        assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1);
+        assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle).containsExactly(1, 2, 3);
+    }
+
+    @Test
+    public void useCompaction_listensToDeviceConfigChanges() throws InterruptedException {
+        assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isEqualTo(
+                CachedAppOptimizer.DEFAULT_USE_COMPACTION);
+        // When we call init and change some the flag value...
+        mCachedAppOptimizerUnderTest.init();
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_USE_COMPACTION, "true", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then that new flag value is updated in the implementation.
+        assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.mCachedAppOptimizerThread.isAlive()).isTrue();
+
+        // And again, setting the flag the other way.
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_USE_COMPACTION, "false", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isFalse();
+    }
+
+    @Test
+    public void useCompaction_listensToDeviceConfigChangesBadValues() throws InterruptedException {
+        assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isEqualTo(
+                CachedAppOptimizer.DEFAULT_USE_COMPACTION);
+        mCachedAppOptimizerUnderTest.init();
+
+        // When we push an invalid flag value...
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_USE_COMPACTION, "foobar", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then we set the default.
+        assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isEqualTo(
+                CachedAppOptimizer.DEFAULT_USE_COMPACTION);
+    }
+
+    @Test
+    public void compactAction_listensToDeviceConfigChanges() throws InterruptedException {
+        mCachedAppOptimizerUnderTest.init();
+
+        // When we override new values for the compaction action with reasonable values...
+
+        // There are four possible values for compactAction[Some|Full].
+        for (int i = 1; i < 5; i++) {
+            mCountDown = new CountDownLatch(2);
+            int expectedSome = (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1 + i) % 4 + 1;
+            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    CachedAppOptimizer.KEY_COMPACT_ACTION_1, Integer.toString(expectedSome), false);
+            int expectedFull = (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2 + i) % 4 + 1;
+            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    CachedAppOptimizer.KEY_COMPACT_ACTION_2, Integer.toString(expectedFull), false);
+            assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+            // Then the updates are reflected in the flags.
+            assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome).isEqualTo(
+                    compactActionIntToString(expectedSome));
+            assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull).isEqualTo(
+                    compactActionIntToString(expectedFull));
+        }
+    }
+
+    @Test
+    public void compactAction_listensToDeviceConfigChangesBadValues() throws InterruptedException {
+        mCachedAppOptimizerUnderTest.init();
+
+        // When we override new values for the compaction action with bad values ...
+        mCountDown = new CountDownLatch(2);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_ACTION_1, "foo", false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_ACTION_2, "foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then the default values are reflected in the flag
+        assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome).isEqualTo(
+                compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1));
+        assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull).isEqualTo(
+                compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2));
+
+        mCountDown = new CountDownLatch(2);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_ACTION_1, "", false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_ACTION_2, "", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome).isEqualTo(
+                compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1));
+        assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull).isEqualTo(
+                compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2));
+    }
+
+    @Test
+    public void compactThrottle_listensToDeviceConfigChanges() throws InterruptedException {
+        mCachedAppOptimizerUnderTest.init();
+
+        // When we override new reasonable throttle values after init...
+        mCountDown = new CountDownLatch(6);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_1,
+                Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_2,
+                Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2 + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_3,
+                Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3 + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_4,
+                Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4 + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_5,
+                Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_6,
+                Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1), false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then those flags values are reflected in the compactor.
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2 + 1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3 + 1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4 + 1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1);
+    }
+
+    @Test
+    public void compactThrottle_listensToDeviceConfigChangesBadValues()
+            throws InterruptedException {
+        mCachedAppOptimizerUnderTest.init();
+
+        // When one of the throttles is overridden with a bad value...
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_1, "foo", false);
+        // Then all the throttles have the defaults set.
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+
+        // Repeat for each of the throttle keys.
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_2, "foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_3, "foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_4, "foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_5, "foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_THROTTLE_6, "foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+    }
+
+    @Test
+    public void statsdSampleRate_listensToDeviceConfigChanges() throws InterruptedException {
+        mCachedAppOptimizerUnderTest.init();
+
+        // When we override mStatsdSampleRate with a reasonable value ...
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_STATSD_SAMPLE_RATE,
+                Float.toString(CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f), false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then that override is reflected in the compactor.
+        assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(
+                CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f);
+    }
+
+    @Test
+    public void statsdSampleRate_listensToDeviceConfigChangesBadValues()
+            throws InterruptedException {
+        mCachedAppOptimizerUnderTest.init();
+
+        // When we override mStatsdSampleRate with an unreasonable value ...
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_STATSD_SAMPLE_RATE, "foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then that override is reflected in the compactor.
+        assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(
+                CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE);
+    }
+
+    @Test
+    public void statsdSampleRate_listensToDeviceConfigChangesOutOfRangeValues()
+            throws InterruptedException {
+        mCachedAppOptimizerUnderTest.init();
+
+        // When we override mStatsdSampleRate with an value outside of [0..1]...
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_STATSD_SAMPLE_RATE,
+                Float.toString(-1.0f), false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then the values is capped in the range.
+        assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(0.0f);
+
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_STATSD_SAMPLE_RATE,
+                Float.toString(1.01f), false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then the values is capped in the range.
+        assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(1.0f);
+    }
+
+    @Test
+    public void fullCompactionRssThrottleKb_listensToDeviceConfigChanges()
+            throws InterruptedException {
+        mCachedAppOptimizerUnderTest.init();
+
+        // When we override mStatsdSampleRate with a reasonable value ...
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_FULL_RSS_THROTTLE_KB,
+                Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1), false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then that override is reflected in the compactor.
+        assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1);
+    }
+
+    @Test
+    public void fullCompactionRssThrottleKb_listensToDeviceConfigChangesBadValues()
+            throws InterruptedException {
+        mCachedAppOptimizerUnderTest.init();
+
+        // When we override mStatsdSampleRate with an unreasonable value ...
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then that override is reflected in the compactor.
+        assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
+
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "-100", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then that override is reflected in the compactor.
+        assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
+    }
+
+    @Test
+    public void fullCompactionDeltaRssThrottleKb_listensToDeviceConfigChanges()
+            throws InterruptedException {
+        mCachedAppOptimizerUnderTest.init();
+
+        // When we override mStatsdSampleRate with a reasonable value ...
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB,
+                Long.toString(
+                        CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then that override is reflected in the compactor.
+        assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1);
+    }
+
+    @Test
+    public void fullCompactionDeltaRssThrottleKb_listensToDeviceConfigChangesBadValues()
+            throws InterruptedException {
+        mCachedAppOptimizerUnderTest.init();
+
+        // When we override mStatsdSampleRate with an unreasonable value ...
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then that override is reflected in the compactor.
+        assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
+
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "-100", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then that override is reflected in the compactor.
+        assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
+                CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
+    }
+
+    @Test
+    public void procStateThrottle_listensToDeviceConfigChanges()
+            throws InterruptedException {
+        mCachedAppOptimizerUnderTest.init();
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle).containsExactly(1, 2, 3);
+
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle).isEmpty();
+    }
+
+    @Test
+    public void procStateThrottle_listensToDeviceConfigChangesBadValues()
+            throws InterruptedException {
+        mCachedAppOptimizerUnderTest.init();
+
+        Set<Integer> expected = new HashSet<>();
+        for (String s : TextUtils.split(
+                CachedAppOptimizer.DEFAULT_COMPACT_PROC_STATE_THROTTLE, ",")) {
+            expected.add(Integer.parseInt(s));
+        }
+
+        // Not numbers
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
+                .containsExactlyElementsIn(expected);
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "1,foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
+                .containsExactlyElementsIn(expected);
+
+        // Empty splits
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, ",", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
+                .containsExactlyElementsIn(expected);
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, ",,3", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
+                .containsExactlyElementsIn(expected);
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "1,,3", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
+                .containsExactlyElementsIn(expected);
+    }
+
+    private class TestInjector extends Injector {
+
+        TestInjector(Context context) {
+            super(context);
+        }
+
+        @Override
+        public AppOpsService getAppOpsService(File file, Handler handler) {
+            return mAppOpsService;
+        }
+
+        @Override
+        public Handler getUiHandler(ActivityManagerService service) {
+            return mHandler;
+        }
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 4635c08..fc2ae40 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -546,7 +546,7 @@
     public void testUpdateOomAdj_DoOne_NonCachedToCached() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
-        app.cached = false;
+        app.setCached(false);
         app.setCurRawAdj(SERVICE_ADJ);
         doReturn(null).when(sService).getTopAppLocked();
         sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
@@ -1662,7 +1662,7 @@
         }
         app.lastProviderTime = lastProviderTime;
         app.lastTopTime = lastTopTime;
-        app.cached = cached;
+        app.setCached(cached);
         for (int i = 0; i < numOfExecutingServices; i++) {
             app.executingServices.add(mock(ServiceRecord.class));
         }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index f2e118d..e0e374b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -50,6 +50,7 @@
 import android.os.BatteryManagerInternal;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
 
 import com.android.server.AppStateTracker;
@@ -95,6 +96,7 @@
                 .initMocks(this)
                 .strictness(Strictness.LENIENT)
                 .mockStatic(LocalServices.class)
+                .mockStatic(ServiceManager.class)
                 .startMocking();
 
         // Called in JobSchedulerService constructor.
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/UserInfoStoreTest.java b/services/tests/mockingservicestests/src/com/android/server/location/UserInfoStoreTest.java
new file mode 100644
index 0000000..06fb102
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/UserInfoStoreTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.location;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.UserInfo;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.quality.Strictness;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class UserInfoStoreTest {
+
+    private static final int USER1_ID = 1;
+    private static final int USER1_MANAGED_ID = 11;
+    private static final int[] USER1_PROFILES = new int[]{USER1_ID, USER1_MANAGED_ID};
+    private static final int USER2_ID = 2;
+    private static final int USER2_MANAGED_ID = 12;
+    private static final int[] USER2_PROFILES = new int[]{USER2_ID, USER2_MANAGED_ID};
+
+    @Mock private Context mContext;
+    @Mock private UserManager mUserManager;
+
+    private StaticMockitoSession mMockingSession;
+    private List<BroadcastReceiver> mBroadcastReceivers = new ArrayList<>();
+
+    private UserInfoStore mStore;
+
+    @Before
+    public void setUp() {
+        mMockingSession = mockitoSession()
+                .initMocks(this)
+                .spyStatic(ActivityManager.class)
+                .strictness(Strictness.WARN)
+                .startMocking();
+
+        doReturn(mUserManager).when(mContext).getSystemService(UserManager.class);
+        doAnswer(invocation -> {
+            mBroadcastReceivers.add(invocation.getArgument(0));
+            return null;
+        }).when(mContext).registerReceiverAsUser(any(BroadcastReceiver.class), any(
+                UserHandle.class), any(IntentFilter.class), isNull(), any(Handler.class));
+        doReturn(USER1_PROFILES).when(mUserManager).getProfileIdsWithDisabled(USER1_ID);
+        doReturn(USER2_PROFILES).when(mUserManager).getProfileIdsWithDisabled(USER2_ID);
+        doReturn(new UserInfo(USER1_ID, "", 0)).when(mUserManager).getProfileParent(
+                USER1_MANAGED_ID);
+        doReturn(new UserInfo(USER2_ID, "", 0)).when(mUserManager).getProfileParent(
+                USER2_MANAGED_ID);
+
+        doReturn(USER1_ID).when(ActivityManager::getCurrentUser);
+
+        mStore = new UserInfoStore(mContext);
+        mStore.onSystemReady();
+    }
+
+    @After
+    public void tearDown() {
+        if (mMockingSession != null) {
+            mMockingSession.finishMocking();
+        }
+    }
+
+    private void switchUser(int userId) {
+        doReturn(userId).when(ActivityManager::getCurrentUser);
+        Intent intent = new Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE,
+                userId);
+        for (BroadcastReceiver broadcastReceiver : mBroadcastReceivers) {
+            broadcastReceiver.onReceive(mContext, intent);
+        }
+    }
+
+    @Test
+    public void testListeners() {
+        UserInfoStore.UserChangedListener listener = mock(UserInfoStore.UserChangedListener.class);
+        mStore.addListener(listener);
+
+        switchUser(USER1_ID);
+        verify(listener, never()).onUserChanged(anyInt(), anyInt());
+
+        switchUser(USER2_ID);
+        verify(listener).onUserChanged(USER1_ID, USER2_ID);
+
+        switchUser(USER1_ID);
+        verify(listener).onUserChanged(USER2_ID, USER1_ID);
+    }
+
+    @Test
+    public void testCurrentUser() {
+        assertThat(mStore.getCurrentUserId()).isEqualTo(USER1_ID);
+
+        switchUser(USER2_ID);
+
+        assertThat(mStore.getCurrentUserId()).isEqualTo(USER2_ID);
+
+        switchUser(USER1_ID);
+
+        assertThat(mStore.getCurrentUserId()).isEqualTo(USER1_ID);
+    }
+
+    @Test
+    public void testIsCurrentUserOrProfile() {
+        assertThat(mStore.isCurrentUserOrProfile(USER1_ID)).isTrue();
+        assertThat(mStore.isCurrentUserOrProfile(USER1_MANAGED_ID)).isTrue();
+        assertThat(mStore.isCurrentUserOrProfile(USER2_ID)).isFalse();
+        assertThat(mStore.isCurrentUserOrProfile(USER2_MANAGED_ID)).isFalse();
+
+        switchUser(USER2_ID);
+
+        assertThat(mStore.isCurrentUserOrProfile(USER1_ID)).isFalse();
+        assertThat(mStore.isCurrentUserOrProfile(USER2_ID)).isTrue();
+        assertThat(mStore.isCurrentUserOrProfile(USER1_MANAGED_ID)).isFalse();
+        assertThat(mStore.isCurrentUserOrProfile(USER2_MANAGED_ID)).isTrue();
+    }
+
+    @Test
+    public void testGetParentUserId() {
+        assertThat(mStore.getParentUserId(USER1_ID)).isEqualTo(USER1_ID);
+        assertThat(mStore.getParentUserId(USER1_MANAGED_ID)).isEqualTo(USER1_ID);
+        assertThat(mStore.getParentUserId(USER2_ID)).isEqualTo(USER2_ID);
+        assertThat(mStore.getParentUserId(USER2_MANAGED_ID)).isEqualTo(USER2_ID);
+
+        switchUser(USER2_ID);
+
+        assertThat(mStore.getParentUserId(USER1_ID)).isEqualTo(USER1_ID);
+        assertThat(mStore.getParentUserId(USER2_ID)).isEqualTo(USER2_ID);
+        assertThat(mStore.getParentUserId(USER1_MANAGED_ID)).isEqualTo(USER1_ID);
+        assertThat(mStore.getParentUserId(USER2_MANAGED_ID)).isEqualTo(USER2_ID);
+    }
+}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index ace15eb..556f636 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -44,6 +44,7 @@
         "servicestests-utils",
         "service-appsearch",
         "service-jobscheduler",
+        "service-permission",
         // TODO: remove once Android migrates to JUnit 4.12,
         // which provides assertThrows
         "testng",
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 3a07a69..d2ddff3 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -61,6 +61,7 @@
     <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
     <uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.STORAGE_INTERNAL" />
     <uses-permission android:name="android.permission.WATCH_APPOPS" />
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
     <uses-permission android:name="android.permission.SUSPEND_APPS"/>
@@ -71,6 +72,10 @@
     <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" />
     <uses-permission android:name="android.permission.HARDWARE_TEST"/>
     <uses-permission android:name="android.permission.BLUETOOTH"/>
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+    <uses-permission android:name="android.permission.DUMP" />
+    <uses-permission android:name="android.permission.READ_DREAM_STATE"/>
+    <uses-permission android:name="android.permission.WRITE_DREAM_STATE"/>
 
     <!-- Uses API introduced in O (26) -->
     <uses-sdk android:minSdkVersion="1"
diff --git a/services/tests/servicestests/res/raw/comp_device_owner.xml b/services/tests/servicestests/res/raw/comp_device_owner.xml
new file mode 100644
index 0000000..0a10242
--- /dev/null
+++ b/services/tests/servicestests/res/raw/comp_device_owner.xml
@@ -0,0 +1,8 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<root>
+    <device-owner package="com.android.frameworks.servicestests"
+        name=""
+        component="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1"
+        userRestrictionsMigrated="true" />
+    <device-owner-context userId="0" />
+</root>
diff --git a/services/tests/servicestests/res/raw/comp_policies_primary.xml b/services/tests/servicestests/res/raw/comp_policies_primary.xml
new file mode 100644
index 0000000..d30f479
--- /dev/null
+++ b/services/tests/servicestests/res/raw/comp_policies_primary.xml
@@ -0,0 +1,8 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true" provisioning-state="3">
+    <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1">
+        <policies flags="991"/>
+        <password-history-length value="33" />
+        <user-restrictions no_bluetooth="true" />
+    </admin>
+</policies>
diff --git a/services/tests/servicestests/res/raw/comp_policies_profile_another_package.xml b/services/tests/servicestests/res/raw/comp_policies_profile_another_package.xml
new file mode 100644
index 0000000..141315e
--- /dev/null
+++ b/services/tests/servicestests/res/raw/comp_policies_profile_another_package.xml
@@ -0,0 +1,6 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true" provisioning-state="3">
+    <admin name="com.another.package.name/whatever.random.class">
+        <policies flags="991"/>
+    </admin>
+</policies>
diff --git a/services/tests/servicestests/res/raw/comp_policies_profile_same_package.xml b/services/tests/servicestests/res/raw/comp_policies_profile_same_package.xml
new file mode 100644
index 0000000..c874dcc
--- /dev/null
+++ b/services/tests/servicestests/res/raw/comp_policies_profile_same_package.xml
@@ -0,0 +1,6 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true" provisioning-state="3">
+    <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1">
+        <policies flags="991"/>
+    </admin>
+</policies>
diff --git a/services/tests/servicestests/res/raw/comp_profile_owner_another_package.xml b/services/tests/servicestests/res/raw/comp_profile_owner_another_package.xml
new file mode 100644
index 0000000..d65ba78
--- /dev/null
+++ b/services/tests/servicestests/res/raw/comp_profile_owner_another_package.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<root>
+    <profile-owner package="com.another.package.name"
+        name="com.another.package.name"
+        component="com.another.package.name/whatever.random.class"
+        userRestrictionsMigrated="true"/>
+</root>
diff --git a/services/tests/servicestests/res/raw/comp_profile_owner_same_package.xml b/services/tests/servicestests/res/raw/comp_profile_owner_same_package.xml
new file mode 100644
index 0000000..7f98c91c
--- /dev/null
+++ b/services/tests/servicestests/res/raw/comp_profile_owner_same_package.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<root>
+    <profile-owner package="com.android.frameworks.servicestests"
+        name="com.android.frameworks.servicestests"
+        component="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1"
+        userRestrictionsMigrated="true"/>
+</root>
diff --git a/services/tests/servicestests/src/com/android/server/BluetoothAirplaneModeListenerTest.java b/services/tests/servicestests/src/com/android/server/BluetoothAirplaneModeListenerTest.java
new file mode 100644
index 0000000..968a402
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/BluetoothAirplaneModeListenerTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static org.mockito.Mockito.*;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.os.Looper;
+import android.provider.Settings;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.BluetoothAirplaneModeListener.AirplaneModeHelper;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothAirplaneModeListenerTest {
+    private Context mContext;
+    private BluetoothAirplaneModeListener mBluetoothAirplaneModeListener;
+    private BluetoothAdapter mBluetoothAdapter;
+    private AirplaneModeHelper mHelper;
+
+    @Mock BluetoothManagerService mBluetoothManagerService;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+
+        mHelper = mock(AirplaneModeHelper.class);
+        when(mHelper.getSettingsInt(BluetoothAirplaneModeListener.TOAST_COUNT))
+                .thenReturn(BluetoothAirplaneModeListener.MAX_TOAST_COUNT);
+        doNothing().when(mHelper).setSettingsInt(anyString(), anyInt());
+        doNothing().when(mHelper).showToastMessage();
+        doNothing().when(mHelper).onAirplaneModeChanged(any(BluetoothManagerService.class));
+
+        mBluetoothAirplaneModeListener = new BluetoothAirplaneModeListener(
+                    mBluetoothManagerService, Looper.getMainLooper(), mContext);
+        mBluetoothAirplaneModeListener.start(mHelper);
+    }
+
+    @Test
+    public void testIgnoreOnAirplanModeChange() {
+        Assert.assertFalse(mBluetoothAirplaneModeListener.shouldSkipAirplaneModeChange());
+
+        when(mHelper.isBluetoothOn()).thenReturn(true);
+        Assert.assertFalse(mBluetoothAirplaneModeListener.shouldSkipAirplaneModeChange());
+
+        when(mHelper.isA2dpOrHearingAidConnected()).thenReturn(true);
+        Assert.assertFalse(mBluetoothAirplaneModeListener.shouldSkipAirplaneModeChange());
+
+        when(mHelper.isAirplaneModeOn()).thenReturn(true);
+        Assert.assertTrue(mBluetoothAirplaneModeListener.shouldSkipAirplaneModeChange());
+    }
+
+    @Test
+    public void testHandleAirplaneModeChange_InvokeAirplaneModeChanged() {
+        mBluetoothAirplaneModeListener.handleAirplaneModeChange();
+        verify(mHelper).onAirplaneModeChanged(mBluetoothManagerService);
+    }
+
+    @Test
+    public void testHandleAirplaneModeChange_NotInvokeAirplaneModeChanged_NotPopToast() {
+        mBluetoothAirplaneModeListener.mToastCount = BluetoothAirplaneModeListener.MAX_TOAST_COUNT;
+        when(mHelper.isBluetoothOn()).thenReturn(true);
+        when(mHelper.isA2dpOrHearingAidConnected()).thenReturn(true);
+        when(mHelper.isAirplaneModeOn()).thenReturn(true);
+        mBluetoothAirplaneModeListener.handleAirplaneModeChange();
+
+        verify(mHelper).setSettingsInt(Settings.Global.BLUETOOTH_ON,
+                BluetoothManagerService.BLUETOOTH_ON_AIRPLANE);
+        verify(mHelper, times(0)).showToastMessage();
+        verify(mHelper, times(0)).onAirplaneModeChanged(mBluetoothManagerService);
+    }
+
+    @Test
+    public void testHandleAirplaneModeChange_NotInvokeAirplaneModeChanged_PopToast() {
+        mBluetoothAirplaneModeListener.mToastCount = 0;
+        when(mHelper.isBluetoothOn()).thenReturn(true);
+        when(mHelper.isA2dpOrHearingAidConnected()).thenReturn(true);
+        when(mHelper.isAirplaneModeOn()).thenReturn(true);
+        mBluetoothAirplaneModeListener.handleAirplaneModeChange();
+
+        verify(mHelper).setSettingsInt(Settings.Global.BLUETOOTH_ON,
+                BluetoothManagerService.BLUETOOTH_ON_AIRPLANE);
+        verify(mHelper).showToastMessage();
+        verify(mHelper, times(0)).onAirplaneModeChanged(mBluetoothManagerService);
+    }
+
+    @Test
+    public void testIsPopToast_PopToast() {
+        mBluetoothAirplaneModeListener.mToastCount = 0;
+        Assert.assertTrue(mBluetoothAirplaneModeListener.shouldPopToast());
+        verify(mHelper).setSettingsInt(BluetoothAirplaneModeListener.TOAST_COUNT, 1);
+    }
+
+    @Test
+    public void testIsPopToast_NotPopToast() {
+        mBluetoothAirplaneModeListener.mToastCount = BluetoothAirplaneModeListener.MAX_TOAST_COUNT;
+        Assert.assertFalse(mBluetoothAirplaneModeListener.shouldPopToast());
+        verify(mHelper, times(0)).setSettingsInt(anyString(), anyInt());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/DynamicSystemServiceTest.java b/services/tests/servicestests/src/com/android/server/DynamicSystemServiceTest.java
index 50437b4..d367f71 100644
--- a/services/tests/servicestests/src/com/android/server/DynamicSystemServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/DynamicSystemServiceTest.java
@@ -36,7 +36,7 @@
     public void test1() {
         assertTrue("dynamic_system service available", mService != null);
         try {
-            mService.startInstallation();
+            mService.startInstallation("dsu");
             fail("DynamicSystemService did not throw SecurityException as expected");
         } catch (SecurityException e) {
             // expected
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
index 1829fb7..e609adc 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -16,7 +16,7 @@
 
 package com.android.server;
 
-import static android.net.NetworkScoreManager.CACHE_FILTER_NONE;
+import static android.net.NetworkScoreManager.SCORE_FILTER_NONE;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
@@ -29,6 +29,7 @@
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
@@ -56,7 +57,6 @@
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
-import android.net.wifi.WifiSsid;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -182,8 +182,8 @@
     }
 
     private ScanResult createScanResult(String ssid, String bssid) {
-        ScanResult result = new ScanResult();
-        result.wifiSsid = WifiSsid.createFromAsciiEncoded(ssid);
+        ScanResult result = mock(ScanResult.class);
+        result.SSID = ssid;
         result.BSSID = bssid;
         return result;
     }
@@ -306,7 +306,7 @@
         bindToScorer(true /*callerIsScorer*/);
 
         mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI,
-                mNetworkScoreCache, CACHE_FILTER_NONE);
+                mNetworkScoreCache, SCORE_FILTER_NONE);
 
         mNetworkScoreService.updateScores(new ScoredNetwork[]{SCORED_NETWORK});
 
@@ -321,9 +321,9 @@
         bindToScorer(true /*callerIsScorer*/);
 
         mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI,
-                mNetworkScoreCache, CACHE_FILTER_NONE);
+                mNetworkScoreCache, SCORE_FILTER_NONE);
         mNetworkScoreService.registerNetworkScoreCache(
-                NetworkKey.TYPE_WIFI, mNetworkScoreCache2, CACHE_FILTER_NONE);
+                NetworkKey.TYPE_WIFI, mNetworkScoreCache2, SCORE_FILTER_NONE);
 
         // updateScores should update both caches
         mNetworkScoreService.updateScores(new ScoredNetwork[]{SCORED_NETWORK});
@@ -378,7 +378,7 @@
         bindToScorer(true /*callerIsScorer*/);
 
         mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache,
-                CACHE_FILTER_NONE);
+                SCORE_FILTER_NONE);
         mNetworkScoreService.clearScores();
 
         verify(mNetworkScoreCache).clearScores();
@@ -392,7 +392,7 @@
                 .thenReturn(PackageManager.PERMISSION_GRANTED);
 
         mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache,
-                CACHE_FILTER_NONE);
+                SCORE_FILTER_NONE);
         mNetworkScoreService.clearScores();
 
         verify(mNetworkScoreCache).clearScores();
@@ -472,7 +472,7 @@
 
         try {
             mNetworkScoreService.registerNetworkScoreCache(
-                NetworkKey.TYPE_WIFI, mNetworkScoreCache, CACHE_FILTER_NONE);
+                    NetworkKey.TYPE_WIFI, mNetworkScoreCache, SCORE_FILTER_NONE);
             fail("SecurityException expected");
         } catch (SecurityException e) {
             // expected
@@ -615,7 +615,7 @@
                 new ArrayList<>(scoredNetworkList),
                 NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter);
 
-        consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_NONE);
+        consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_NONE);
 
         verify(mNetworkScoreCache).updateScores(scoredNetworkList);
         verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter);
@@ -656,7 +656,7 @@
                 Collections.emptyList(),
                 NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter);
 
-        consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_NONE);
+        consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_NONE);
 
         verifyZeroInteractions(mNetworkScoreCache, mCurrentNetworkFilter, mScanResultsFilter);
     }
@@ -673,7 +673,7 @@
         List<ScoredNetwork> filteredList = new ArrayList<>(scoredNetworkList);
         filteredList.remove(SCORED_NETWORK);
         when(mCurrentNetworkFilter.apply(scoredNetworkList)).thenReturn(filteredList);
-        consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK);
+        consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_CURRENT_NETWORK);
 
         verify(mNetworkScoreCache).updateScores(filteredList);
         verifyZeroInteractions(mScanResultsFilter);
@@ -691,7 +691,7 @@
         List<ScoredNetwork> filteredList = new ArrayList<>(scoredNetworkList);
         filteredList.remove(SCORED_NETWORK);
         when(mScanResultsFilter.apply(scoredNetworkList)).thenReturn(filteredList);
-        consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS);
+        consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_SCAN_RESULTS);
 
         verify(mNetworkScoreCache).updateScores(filteredList);
         verifyZeroInteractions(mCurrentNetworkFilter);
@@ -794,7 +794,7 @@
     @Test
     public void testScanResultsScoreCacheFilter_invalidScanResults() throws Exception {
         List<ScanResult> invalidScanResults = Lists.newArrayList(
-                new ScanResult(),
+                mock(ScanResult.class),
                 createScanResult("", SCORED_NETWORK.networkKey.wifiKey.bssid),
                 createScanResult(WifiManager.UNKNOWN_SSID, SCORED_NETWORK.networkKey.wifiKey.bssid),
                 createScanResult(SSID, null),
diff --git a/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java
new file mode 100644
index 0000000..ed74947
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java
@@ -0,0 +1,259 @@
+/*
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManagerInternal;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Looper;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.io.BufferedReader;
+import java.io.CharArrayWriter;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class PinnerServiceTest {
+    private static final int KEY_CAMERA = 0;
+    private static final int KEY_HOME = 1;
+    private static final int KEY_ASSISTANT = 2;
+
+    private static final long WAIT_FOR_PINNER_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
+
+    @Rule
+    public TestableContext mContext =
+            new TestableContext(InstrumentationRegistry.getContext(), null);
+
+    private final ArraySet<String> mUpdatedPackages = new ArraySet<>();
+    private ResolveInfo mHomePackageResolveInfo;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+
+        LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
+        LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+
+        ActivityTaskManagerInternal mockActivityTaskManagerInternal = mock(
+                ActivityTaskManagerInternal.class);
+        Intent homeIntent = getHomeIntent();
+
+        doReturn(homeIntent).when(mockActivityTaskManagerInternal).getHomeIntent();
+        LocalServices.addService(ActivityTaskManagerInternal.class,
+                mockActivityTaskManagerInternal);
+
+        ActivityManagerInternal mockActivityManagerInternal = mock(ActivityManagerInternal.class);
+        doReturn(true).when(mockActivityManagerInternal).isUidActive(anyInt());
+        LocalServices.addService(ActivityManagerInternal.class, mockActivityManagerInternal);
+
+        mContext = spy(mContext);
+
+        // Get HOME (Launcher) package
+        mHomePackageResolveInfo = mContext.getPackageManager().resolveActivityAsUser(homeIntent,
+                PackageManager.MATCH_DEFAULT_ONLY | PackageManager.MATCH_DIRECT_BOOT_AWARE
+                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, 0);
+        mUpdatedPackages.add(mHomePackageResolveInfo.activityInfo.applicationInfo.packageName);
+    }
+
+    @After
+    public void tearDown() {
+        Mockito.framework().clearInlineMocks();
+    }
+
+    private Intent getHomeIntent() {
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.addCategory(Intent.CATEGORY_HOME);
+        intent.addCategory(Intent.CATEGORY_DEFAULT);
+        return intent;
+    }
+
+    private void unpinAll(PinnerService pinnerService) throws Exception {
+        // unpin all packages
+        Method unpinAppMethod = PinnerService.class.getDeclaredMethod("unpinApp", int.class);
+        unpinAppMethod.setAccessible(true);
+        unpinAppMethod.invoke(pinnerService, KEY_HOME);
+        unpinAppMethod.invoke(pinnerService, KEY_CAMERA);
+        unpinAppMethod.invoke(pinnerService, KEY_ASSISTANT);
+    }
+
+    private void waitForPinnerService(PinnerService pinnerService)
+            throws NoSuchFieldException, IllegalAccessException {
+        // There's no notification/callback when pinning finished
+        // Block until pinner handler is done pinning and runs this empty runnable
+        Field pinnerHandlerField = PinnerService.class.getDeclaredField("mPinnerHandler");
+        pinnerHandlerField.setAccessible(true);
+        Handler pinnerServiceHandler = (Handler) pinnerHandlerField.get(pinnerService);
+        pinnerServiceHandler.runWithScissors(() -> {
+        }, WAIT_FOR_PINNER_TIMEOUT);
+    }
+
+    private ArraySet<Integer> getPinKeys(PinnerService pinnerService)
+            throws NoSuchFieldException, IllegalAccessException {
+        Field pinKeysArrayField = PinnerService.class.getDeclaredField("mPinKeys");
+        pinKeysArrayField.setAccessible(true);
+        return (ArraySet<Integer>) pinKeysArrayField.get(pinnerService);
+    }
+
+    private ArrayMap<Integer, Object> getPinnedApps(PinnerService pinnerService)
+            throws NoSuchFieldException, IllegalAccessException {
+        Field pinnedAppsField = PinnerService.class.getDeclaredField("mPinnedApps");
+        pinnedAppsField.setAccessible(true);
+        return (ArrayMap<Integer, Object>) pinnedAppsField.get(
+                pinnerService);
+    }
+
+    private String getPinnerServiceDump(PinnerService pinnerService) throws Exception {
+        Class<?> innerClass = Class.forName(PinnerService.class.getName() + "$BinderService");
+        Constructor<?> ctor = innerClass.getDeclaredConstructor(PinnerService.class);
+        ctor.setAccessible(true);
+        Binder innerInstance = (Binder) ctor.newInstance(pinnerService);
+        CharArrayWriter cw = new CharArrayWriter();
+        PrintWriter pw = new PrintWriter(cw, true);
+        Method dumpMethod = Binder.class.getDeclaredMethod("dump", FileDescriptor.class,
+                PrintWriter.class, String[].class);
+        dumpMethod.setAccessible(true);
+        dumpMethod.invoke(innerInstance, null, pw, null);
+        return cw.toString();
+    }
+
+    private int getPinnedSize(PinnerService pinnerService) throws Exception {
+        final String totalSizeToken = "Total size: ";
+        String dumpOutput = getPinnerServiceDump(pinnerService);
+        BufferedReader bufReader = new BufferedReader(new StringReader(dumpOutput));
+        Optional<Integer> size = bufReader.lines().filter(s -> s.contains(totalSizeToken))
+                .map(s -> Integer.valueOf(s.substring(totalSizeToken.length()))).findAny();
+        return size.orElse(-1);
+    }
+
+    @Test
+    public void testPinHomeApp() throws Exception {
+        // Enable HOME app pinning
+        Resources res = mock(Resources.class);
+        doReturn(true).when(res).getBoolean(com.android.internal.R.bool.config_pinnerHomeApp);
+        when(mContext.getResources()).thenReturn(res);
+        PinnerService pinnerService = new PinnerService(mContext);
+
+        ArraySet<Integer> pinKeys = getPinKeys(pinnerService);
+        assertThat(pinKeys.valueAt(0)).isEqualTo(KEY_HOME);
+
+        pinnerService.update(mUpdatedPackages, true);
+
+        waitForPinnerService(pinnerService);
+
+        ArrayMap<Integer, Object> pinnedApps = getPinnedApps(pinnerService);
+        assertThat(pinnedApps.get(KEY_HOME)).isNotNull();
+
+        // Check if dump() reports total pinned bytes
+        int totalPinnedSizeBytes = getPinnedSize(pinnerService);
+        assertThat(totalPinnedSizeBytes).isGreaterThan(0);
+
+        // Make sure pinned files are unmapped
+        unpinAll(pinnerService);
+    }
+
+    @Test
+    public void testPinHomeAppOnBootCompleted() throws Exception {
+        // Enable HOME app pinning
+        Resources res = mock(Resources.class);
+        doReturn(true).when(res).getBoolean(com.android.internal.R.bool.config_pinnerHomeApp);
+        when(mContext.getResources()).thenReturn(res);
+        PinnerService pinnerService = new PinnerService(mContext);
+
+        ArraySet<Integer> pinKeys = getPinKeys(pinnerService);
+        assertThat(pinKeys.valueAt(0)).isEqualTo(KEY_HOME);
+
+        pinnerService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+
+        waitForPinnerService(pinnerService);
+
+        ArrayMap<Integer, Object> pinnedApps = getPinnedApps(pinnerService);
+        assertThat(pinnedApps.get(KEY_HOME)).isNotNull();
+
+        // Check if dump() reports total pinned bytes
+        int totalPinnedSizeBytes = getPinnedSize(pinnerService);
+        assertThat(totalPinnedSizeBytes).isGreaterThan(0);
+
+        // Make sure pinned files are unmapped
+        unpinAll(pinnerService);
+    }
+
+    @Test
+    public void testNothingToPin() throws Exception {
+        // No package enabled for pinning
+        Resources res = mock(Resources.class);
+        when(mContext.getResources()).thenReturn(res);
+        PinnerService pinnerService = new PinnerService(mContext);
+
+        ArraySet<Integer> pinKeys = getPinKeys(pinnerService);
+        assertThat(pinKeys).isEmpty();
+
+        pinnerService.update(mUpdatedPackages, true);
+
+        waitForPinnerService(pinnerService);
+
+        ArrayMap<Integer, Object> pinnedApps = getPinnedApps(pinnerService);
+        assertThat(pinnedApps).isEmpty();
+
+        // Check if dump() reports total pinned bytes
+        int totalPinnedSizeBytes = getPinnedSize(pinnerService);
+        assertThat(totalPinnedSizeBytes).isEqualTo(0);
+
+        // Make sure pinned files are unmapped
+        unpinAll(pinnerService);
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index d11d987..cf10559 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -75,6 +75,7 @@
 import android.os.IPowerManager;
 import android.os.PowerManager;
 import android.os.Process;
+import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.testing.DexmakerShareClassLoaderRule;
 import android.view.Display;
@@ -85,6 +86,7 @@
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
 
 import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection;
+import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.wm.WindowManagerInternal;
 
 import org.junit.Before;
@@ -493,6 +495,13 @@
     }
 
     @Test
+    public void getSystemActions() {
+        List<AccessibilityNodeInfo.AccessibilityAction> actions =
+                mServiceConnection.getSystemActions();
+        verify(mMockSystemActionPerformer).getSystemActions();
+    }
+
+    @Test
     public void isFingerprintGestureDetectionAvailable_hasFingerPrintSupport_returnTrue() {
         when(mMockFingerprintGestureDispatcher.isFingerprintGestureDetectionAvailable())
                 .thenReturn(true);
@@ -692,6 +701,18 @@
         assertThat(result, is(false));
     }
 
+    @Test
+    public void takeScreenshot_returnNull() {
+        // no canTakeScreenshot, should return null.
+        when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(false);
+        assertThat(mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY), is(nullValue()));
+
+        // no checkAccessibilityAccess, should return null.
+        when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(true);
+        when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(false);
+        assertThat(mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY), is(nullValue()));
+    }
+
     private void updateServiceInfo(AccessibilityServiceInfo serviceInfo, int eventType,
             int feedbackType, int flags, String[] packageNames, int notificationTimeout) {
         serviceInfo.eventTypes = eventType;
@@ -803,6 +824,11 @@
         }
 
         @Override
+        public boolean switchToInputMethod(String imeId) {
+            return false;
+        }
+
+        @Override
         public boolean isAccessibilityButtonAvailable() throws RemoteException {
             return false;
         }
@@ -826,5 +852,8 @@
 
         @Override
         public void onFingerprintGesture(int gesture) {}
+
+        @Override
+        public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {}
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
index 193f540..87b5658 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
@@ -80,7 +80,7 @@
     // The expected order of EventStreamTransformations.
     private final Class[] mExpectedEventHandlerTypes =
             {KeyboardInterceptor.class, MotionEventInjector.class,
-                    MagnificationGestureHandler.class, TouchExplorer.class,
+                    FullScreenMagnificationGestureHandler.class, TouchExplorer.class,
                     AutoclickController.class, AccessibilityInputFilter.class};
 
     private MagnificationController mMockMagnificationController;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
new file mode 100644
index 0000000..ec928fb
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+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.Manifest;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.IAccessibilityServiceClient;
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.graphics.drawable.Icon;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.server.LocalServices;
+import com.android.server.accessibility.AccessibilityManagerService.AccessibilityDisplayListener;
+import com.android.server.accessibility.test.MessageCapturingHandler;
+import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.server.wm.WindowManagerInternal;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * APCT tests for {@link AccessibilityManagerService}.
+ */
+public class AccessibilityManagerServiceTest extends AndroidTestCase {
+    private static final String TAG = "A11Y_MANAGER_SERVICE_TEST";
+    private static final int ACTION_ID = 20;
+    private static final String LABEL = "label";
+    private static final String INTENT_ACTION = "TESTACTION";
+    private static final String DESCRIPTION = "description";
+    private static final PendingIntent TEST_PENDING_INTENT = PendingIntent.getBroadcast(
+            InstrumentationRegistry.getTargetContext(), 0, new Intent(INTENT_ACTION), 0);
+    private static final RemoteAction TEST_ACTION = new RemoteAction(
+            Icon.createWithContentUri("content://test"),
+            LABEL,
+            DESCRIPTION,
+            TEST_PENDING_INTENT);
+    private static final AccessibilityAction NEW_ACCESSIBILITY_ACTION =
+            new AccessibilityAction(ACTION_ID, LABEL);
+
+    static final ComponentName COMPONENT_NAME = new ComponentName(
+            "com.android.server.accessibility", "AccessibilityManagerServiceTest");
+    static final int SERVICE_ID = 42;
+
+    @Mock private Context mMockContext;
+    @Mock private AccessibilityServiceInfo mMockServiceInfo;
+    @Mock private ResolveInfo mMockResolveInfo;
+    @Mock private AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport;
+    @Mock private PackageManager mMockPackageManager;
+    @Mock private WindowManagerInternal mMockWindowManagerService;
+    @Mock private AccessibilitySecurityPolicy mMockSecurityPolicy;
+    @Mock private SystemActionPerformer mMockSystemActionPerformer;
+    @Mock private AccessibilityWindowManager mMockA11yWindowManager;
+    @Mock private AccessibilityDisplayListener mMockA11yDisplayListener;
+    @Mock private ActivityTaskManagerInternal mMockActivityTaskManagerInternal;
+    @Mock private IBinder mMockBinder;
+    @Mock private IAccessibilityServiceClient mMockServiceClient;
+    private AccessibilityUserState mUserState;
+
+    private MessageCapturingHandler mHandler = new MessageCapturingHandler(null);
+    private AccessibilityServiceConnection mAccessibilityServiceConnection;
+    private AccessibilityManagerService mA11yms;
+
+    @Override
+    protected void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        LocalServices.removeServiceForTest(WindowManagerInternal.class);
+        LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
+        LocalServices.addService(
+                WindowManagerInternal.class, mMockWindowManagerService);
+        LocalServices.addService(
+                ActivityTaskManagerInternal.class, mMockActivityTaskManagerInternal);
+
+        mA11yms = new AccessibilityManagerService(
+            InstrumentationRegistry.getContext(),
+            mMockPackageManager,
+            mMockSecurityPolicy,
+            mMockSystemActionPerformer,
+            mMockA11yWindowManager,
+            mMockA11yDisplayListener);
+    }
+
+    private void setupAccessibilityServiceConnection() {
+        when(mMockContext.getSystemService(Context.DISPLAY_SERVICE)).thenReturn(
+                InstrumentationRegistry.getContext().getSystemService(
+                        Context.DISPLAY_SERVICE));
+        mUserState = new AccessibilityUserState(UserHandle.USER_SYSTEM, mMockContext, mA11yms);
+
+        when(mMockServiceInfo.getResolveInfo()).thenReturn(mMockResolveInfo);
+        mMockResolveInfo.serviceInfo = mock(ServiceInfo.class);
+        mMockResolveInfo.serviceInfo.applicationInfo = mock(ApplicationInfo.class);
+
+        when(mMockBinder.queryLocalInterface(any())).thenReturn(mMockServiceClient);
+        mAccessibilityServiceConnection = new AccessibilityServiceConnection(
+                mUserState,
+                mMockContext,
+                COMPONENT_NAME,
+                mMockServiceInfo,
+                SERVICE_ID,
+                mHandler,
+                new Object(),
+                mMockSecurityPolicy,
+                mMockSystemSupport,
+                mMockWindowManagerService,
+                mMockSystemActionPerformer,
+                mMockA11yWindowManager,
+                mMockActivityTaskManagerInternal);
+        mAccessibilityServiceConnection.bindLocked();
+        mAccessibilityServiceConnection.onServiceConnected(COMPONENT_NAME, mMockBinder);
+    }
+
+    @SmallTest
+    public void testRegisterSystemActionWithoutPermission() throws Exception {
+        doThrow(SecurityException.class).when(mMockSecurityPolicy).enforceCallingPermission(
+                Manifest.permission.MANAGE_ACCESSIBILITY,
+                AccessibilityManagerService.FUNCTION_REGISTER_SYSTEM_ACTION);
+
+        try {
+            mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID);
+            fail();
+        } catch (SecurityException expected) {
+        }
+        verify(mMockSystemActionPerformer, never()).registerSystemAction(ACTION_ID, TEST_ACTION);
+    }
+
+    @SmallTest
+    public void testRegisterSystemAction() throws Exception {
+        mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID);
+        verify(mMockSystemActionPerformer).registerSystemAction(ACTION_ID, TEST_ACTION);
+    }
+
+    @SmallTest
+    public void testUnregisterSystemActionWithoutPermission() throws Exception {
+        doThrow(SecurityException.class).when(mMockSecurityPolicy).enforceCallingPermission(
+                Manifest.permission.MANAGE_ACCESSIBILITY,
+                AccessibilityManagerService.FUNCTION_UNREGISTER_SYSTEM_ACTION);
+
+        try {
+            mA11yms.unregisterSystemAction(ACTION_ID);
+            fail();
+        } catch (SecurityException expected) {
+        }
+        verify(mMockSystemActionPerformer, never()).unregisterSystemAction(ACTION_ID);
+    }
+
+    @SmallTest
+    public void testUnregisterSystemAction() throws Exception {
+        mA11yms.unregisterSystemAction(ACTION_ID);
+        verify(mMockSystemActionPerformer).unregisterSystemAction(ACTION_ID);
+    }
+
+    @SmallTest
+    public void testOnSystemActionsChanged() throws Exception {
+        setupAccessibilityServiceConnection();
+        mA11yms.notifySystemActionsChangedLocked(mUserState);
+        verify(mMockServiceClient).onSystemActionsChanged();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerTest.java
deleted file mode 100644
index b9b6d55..0000000
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerTest.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright (C) 2010 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;
-
-import static junit.framework.TestCase.assertFalse;
-import static junit.framework.TestCase.assertSame;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.accessibilityservice.AccessibilityServiceInfo;
-import android.app.Instrumentation;
-import android.os.UserHandle;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.IAccessibilityManager;
-import android.view.accessibility.IAccessibilityManagerClient;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.util.IntPair;
-
-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;
-
-/**
- * Tests for the AccessibilityManager by mocking the backing service.
- */
-@RunWith(AndroidJUnit4.class)
-public class AccessibilityManagerTest {
-    private static final boolean WITH_A11Y_ENABLED = true;
-    private static final boolean WITH_A11Y_DISABLED = false;
-
-    @Mock private IAccessibilityManager mMockService;
-    private MessageCapturingHandler mHandler;
-    private Instrumentation mInstrumentation;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        mHandler = new MessageCapturingHandler(null);
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
-    }
-
-    @After
-    public void tearDown() {
-        mHandler.removeAllMessages();
-    }
-
-
-    private AccessibilityManager createManager(boolean enabled) throws Exception {
-        long serviceReturnValue = IntPair.of(
-                (enabled) ? AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED : 0,
-                AccessibilityEvent.TYPES_ALL_MASK);
-        when(mMockService.addClient(any(IAccessibilityManagerClient.class), anyInt()))
-                .thenReturn(serviceReturnValue);
-
-        AccessibilityManager manager =
-                new AccessibilityManager(mHandler, mMockService, UserHandle.USER_CURRENT);
-
-        verify(mMockService).addClient(any(IAccessibilityManagerClient.class), anyInt());
-        mHandler.setCallback(manager.getCallback());
-        mHandler.sendAllMessages();
-        return manager;
-    }
-
-    @Test
-    public void testGetAccessibilityServiceList() throws Exception {
-        // create a list of installed accessibility services the mock service returns
-        List<AccessibilityServiceInfo> expectedServices = new ArrayList<>();
-        AccessibilityServiceInfo accessibilityServiceInfo = new AccessibilityServiceInfo();
-        accessibilityServiceInfo.packageNames = new String[] { "foo.bar" };
-        expectedServices.add(accessibilityServiceInfo);
-
-        // configure the mock service behavior
-        when(mMockService.getInstalledAccessibilityServiceList(anyInt()))
-                .thenReturn(expectedServices);
-
-        // invoke the method under test
-        AccessibilityManager manager = createManager(true);
-        List<AccessibilityServiceInfo> receivedServices =
-                manager.getInstalledAccessibilityServiceList();
-
-        verify(mMockService).getInstalledAccessibilityServiceList(UserHandle.USER_CURRENT);
-        // check expected result (list equals() compares it contents as well)
-        assertEquals("All expected services must be returned", expectedServices, receivedServices);
-    }
-
-    @Test
-    public void testInterrupt() throws Exception {
-        AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
-        manager.interrupt();
-
-        verify(mMockService).interrupt(UserHandle.USER_CURRENT);
-    }
-
-    @Test
-    public void testIsEnabled() throws Exception {
-        // Create manager with a11y enabled
-        AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
-        assertTrue("Must be enabled since the mock service is enabled", manager.isEnabled());
-
-        // Disable accessibility
-        manager.getClient().setState(0);
-        mHandler.sendAllMessages();
-        assertFalse("Must be disabled since the mock service is disabled", manager.isEnabled());
-    }
-
-    @Test
-    public void testSendAccessibilityEvent_AccessibilityEnabled() throws Exception {
-        AccessibilityEvent sentEvent = AccessibilityEvent.obtain(
-                AccessibilityEvent.TYPE_ANNOUNCEMENT);
-
-        AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
-        manager.sendAccessibilityEvent(sentEvent);
-
-        assertSame("The event should be recycled.", sentEvent, AccessibilityEvent.obtain());
-    }
-
-    @Test
-    public void testSendAccessibilityEvent_AccessibilityDisabled() throws Exception {
-        AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
-
-        AccessibilityManager manager = createManager(WITH_A11Y_DISABLED);
-        mInstrumentation.runOnMainSync(() -> {
-            try {
-                manager.sendAccessibilityEvent(sentEvent);
-                fail("No accessibility events are sent if accessibility is disabled");
-            } catch (IllegalStateException ise) {
-                // check expected result
-                assertEquals("Accessibility off. Did you forget to check that?", ise.getMessage());
-            }
-        });
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java
index 04ac7fe..1504097 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java
@@ -346,6 +346,14 @@
     }
 
     @Test
+    public void canTakeScreenshot_hasCapability_returnTrue() {
+        when(mMockA11yServiceConnection.getCapabilities())
+                .thenReturn(AccessibilityServiceInfo.CAPABILITY_CAN_TAKE_SCREENSHOT);
+
+        assertTrue(mA11ySecurityPolicy.canTakeScreenshotLocked(mMockA11yServiceConnection));
+    }
+
+    @Test
     public void resolveProfileParent_userIdIsCurrentUser_returnCurrentUser() {
         final int currentUserId = 10;
         final int userId = currentUserId;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
index 99dd9a1..2ce70b6f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -44,6 +44,7 @@
 import android.testing.DexmakerShareClassLoaderRule;
 import android.view.Display;
 
+import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
index 96d9c47..ac5169c 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -113,7 +113,6 @@
         mUserState.mAccessibilityButtonTargets.add(COMPONENT_NAME.flattenToString());
         mUserState.setTouchExplorationEnabledLocked(true);
         mUserState.setDisplayMagnificationEnabledLocked(true);
-        mUserState.setNavBarMagnificationEnabledLocked(true);
         mUserState.setAutoclickEnabledLocked(true);
         mUserState.setUserNonInteractiveUiTimeoutLocked(30);
         mUserState.setUserInteractiveUiTimeoutLocked(30);
@@ -132,7 +131,6 @@
         assertTrue(mUserState.mAccessibilityButtonTargets.isEmpty());
         assertFalse(mUserState.isTouchExplorationEnabledLocked());
         assertFalse(mUserState.isDisplayMagnificationEnabledLocked());
-        assertFalse(mUserState.isNavBarMagnificationEnabledLocked());
         assertFalse(mUserState.isAutoclickEnabledLocked());
         assertEquals(0, mUserState.getUserNonInteractiveUiTimeoutLocked());
         assertEquals(0, mUserState.getUserInteractiveUiTimeoutLocked());
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
index 67075ed..9db5a08 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
@@ -53,6 +53,7 @@
 import android.view.accessibility.IAccessibilityInteractionConnection;
 
 import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection;
+import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.wm.WindowManagerInternal;
 import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback;
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java
index 44a514f..96ae102e5 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java
@@ -31,6 +31,8 @@
 import android.accessibilityservice.FingerprintGestureController.FingerprintGestureCallback;
 import android.accessibilityservice.IAccessibilityServiceConnection;
 
+import com.android.server.accessibility.test.MessageCapturingHandler;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java
index de7bc44..30d00ad 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java
@@ -31,6 +31,7 @@
 import android.view.KeyEvent;
 
 import com.android.server.accessibility.FingerprintGestureDispatcher.FingerprintGestureClient;
+import com.android.server.accessibility.test.MessageCapturingHandler;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/FullScreenMagnificationGestureHandlerTest.java
new file mode 100644
index 0000000..2007d4f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/FullScreenMagnificationGestureHandlerTest.java
@@ -0,0 +1,658 @@
+/*
+ * 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.server.accessibility;
+
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_POINTER_DOWN;
+import static android.view.MotionEvent.ACTION_POINTER_UP;
+
+import static com.android.server.testutils.TestUtils.strictMock;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.util.DebugUtils;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.testutils.OffsettableClock;
+import com.android.server.testutils.TestHandler;
+import com.android.server.wm.WindowManagerInternal;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.IntConsumer;
+
+/**
+ * Tests the state transitions of {@link FullScreenMagnificationGestureHandler}
+ *
+ * Here's a dot graph describing the transitions being tested:
+ * {@code
+ *      digraph {
+ *          IDLE -> SHORTCUT_TRIGGERED [label="a11y\nbtn"]
+ *          SHORTCUT_TRIGGERED -> IDLE [label="a11y\nbtn"]
+ *          IDLE -> DOUBLE_TAP [label="2tap"]
+ *          DOUBLE_TAP -> IDLE [label="timeout"]
+ *          DOUBLE_TAP -> TRIPLE_TAP_AND_HOLD [label="down"]
+ *          SHORTCUT_TRIGGERED -> TRIPLE_TAP_AND_HOLD [label="down"]
+ *          TRIPLE_TAP_AND_HOLD -> ZOOMED [label="up"]
+ *          TRIPLE_TAP_AND_HOLD -> DRAGGING_TMP [label="hold/\nswipe"]
+ *          DRAGGING_TMP -> IDLE [label="release"]
+ *          ZOOMED -> ZOOMED_DOUBLE_TAP [label="2tap"]
+ *          ZOOMED_DOUBLE_TAP -> ZOOMED [label="timeout"]
+ *          ZOOMED_DOUBLE_TAP -> DRAGGING [label="hold"]
+ *          ZOOMED_DOUBLE_TAP -> IDLE [label="tap"]
+ *          DRAGGING -> ZOOMED [label="release"]
+ *          ZOOMED -> IDLE [label="a11y\nbtn"]
+ *          ZOOMED -> PANNING [label="2hold"]
+ *          PANNING -> PANNING_SCALING [label="pinch"]
+ *          PANNING_SCALING -> ZOOMED [label="release"]
+ *          PANNING -> ZOOMED [label="release"]
+ *      }
+ * }
+ */
+@RunWith(AndroidJUnit4.class)
+public class FullScreenMagnificationGestureHandlerTest {
+
+    public static final int STATE_IDLE = 1;
+    public static final int STATE_ZOOMED = 2;
+    public static final int STATE_2TAPS = 3;
+    public static final int STATE_ZOOMED_2TAPS = 4;
+    public static final int STATE_SHORTCUT_TRIGGERED = 5;
+    public static final int STATE_DRAGGING_TMP = 6;
+    public static final int STATE_DRAGGING = 7;
+    public static final int STATE_PANNING = 8;
+    public static final int STATE_SCALING_AND_PANNING = 9;
+
+
+    public static final int FIRST_STATE = STATE_IDLE;
+    public static final int LAST_STATE = STATE_SCALING_AND_PANNING;
+
+    // Co-prime x and y, to potentially catch x-y-swapped errors
+    public static final float DEFAULT_X = 301;
+    public static final float DEFAULT_Y = 299;
+
+    private static final int DISPLAY_0 = 0;
+
+    private Context mContext;
+    MagnificationController mMagnificationController;
+
+    private OffsettableClock mClock;
+    private FullScreenMagnificationGestureHandler mMgh;
+    private TestHandler mHandler;
+
+    private long mLastDownTime = Integer.MIN_VALUE;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getContext();
+        final MagnificationController.ControllerContext mockController =
+                mock(MagnificationController.ControllerContext.class);
+        final WindowManagerInternal mockWindowManager = mock(WindowManagerInternal.class);
+        when(mockController.getContext()).thenReturn(mContext);
+        when(mockController.getAms()).thenReturn(mock(AccessibilityManagerService.class));
+        when(mockController.getWindowManager()).thenReturn(mockWindowManager);
+        when(mockController.getHandler()).thenReturn(new Handler(mContext.getMainLooper()));
+        when(mockController.newValueAnimator()).thenReturn(new ValueAnimator());
+        when(mockController.getAnimationDuration()).thenReturn(1000L);
+        when(mockWindowManager.setMagnificationCallbacks(eq(DISPLAY_0), any())).thenReturn(true);
+        mMagnificationController = new MagnificationController(mockController, new Object()) {
+            @Override
+            public boolean magnificationRegionContains(int displayId, float x, float y) {
+                return true;
+            }
+
+            @Override
+            void setForceShowMagnifiableBounds(int displayId, boolean show) {}
+        };
+        mMagnificationController.register(DISPLAY_0);
+        mClock = new OffsettableClock.Stopped();
+
+        boolean detectTripleTap = true;
+        boolean detectShortcutTrigger = true;
+        mMgh = newInstance(detectTripleTap, detectShortcutTrigger);
+    }
+
+    @After
+    public void tearDown() {
+        mMagnificationController.unregister(DISPLAY_0);
+    }
+
+    @NonNull
+    private FullScreenMagnificationGestureHandler newInstance(boolean detectTripleTap,
+            boolean detectShortcutTrigger) {
+        FullScreenMagnificationGestureHandler h = new FullScreenMagnificationGestureHandler(
+                mContext, mMagnificationController,
+                detectTripleTap, detectShortcutTrigger, DISPLAY_0);
+        mHandler = new TestHandler(h.mDetectingState, mClock) {
+            @Override
+            protected String messageToString(Message m) {
+                return DebugUtils.valueToString(
+                        FullScreenMagnificationGestureHandler.DetectingState.class, "MESSAGE_",
+                        m.what);
+            }
+        };
+        h.mDetectingState.mHandler = mHandler;
+        h.setNext(strictMock(EventStreamTransformation.class));
+        return h;
+    }
+
+    @Test
+    public void testInitialState_isIdle() {
+        assertIn(STATE_IDLE);
+    }
+
+    /**
+     * Covers paths to get to and back between each state and {@link #STATE_IDLE}
+     * This navigates between states using "canonical" paths, specified in
+     * {@link #goFromStateIdleTo} (for traversing away from {@link #STATE_IDLE}) and
+     * {@link #returnToNormalFrom} (for navigating back to {@link #STATE_IDLE})
+     */
+    @Test
+    public void testEachState_isReachableAndRecoverable() {
+        forEachState(state -> {
+            goFromStateIdleTo(state);
+            assertIn(state);
+
+            returnToNormalFrom(state);
+            try {
+                assertIn(STATE_IDLE);
+            } catch (AssertionError e) {
+                throw new AssertionError("Failed while testing state " + stateToString(state), e);
+            }
+        });
+    }
+
+    @Test
+    public void testStates_areMutuallyExclusive() {
+        forEachState(state1 -> {
+            forEachState(state2 -> {
+                if (state1 < state2) {
+                    goFromStateIdleTo(state1);
+                    try {
+                        assertIn(state2);
+                        fail("State " + stateToString(state1) + " also implies state "
+                                + stateToString(state2) + stateDump());
+                    } catch (AssertionError e) {
+                        // expected
+                        returnToNormalFrom(state1);
+                    }
+                }
+            });
+        });
+    }
+
+    @Test
+    public void testTransitionToDelegatingStateAndClear_preservesShortcutTriggeredState() {
+        mMgh.mDetectingState.transitionToDelegatingStateAndClear();
+        assertFalse(mMgh.mDetectingState.mShortcutTriggered);
+
+        goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED);
+        mMgh.mDetectingState.transitionToDelegatingStateAndClear();
+        assertTrue(mMgh.mDetectingState.mShortcutTriggered);
+    }
+
+    /**
+     * Covers edges of the graph not covered by "canonical" transitions specified in
+     * {@link #goFromStateIdleTo} and {@link #returnToNormalFrom}
+     */
+    @SuppressWarnings("Convert2MethodRef")
+    @Test
+    public void testAlternativeTransitions_areWorking() {
+        // A11y button followed by a tap&hold turns temporary "viewport dragging" zoom on
+        assertTransition(STATE_SHORTCUT_TRIGGERED, () -> {
+            send(downEvent());
+            fastForward1sec();
+        }, STATE_DRAGGING_TMP);
+
+        // A11y button followed by a tap turns zoom on
+        assertTransition(STATE_SHORTCUT_TRIGGERED, () -> tap(), STATE_ZOOMED);
+
+        // A11y button pressed second time negates the 1st press
+        assertTransition(STATE_SHORTCUT_TRIGGERED, () -> triggerShortcut(), STATE_IDLE);
+
+        // A11y button turns zoom off
+        assertTransition(STATE_ZOOMED, () -> triggerShortcut(), STATE_IDLE);
+
+
+        // Double tap times out while zoomed
+        assertTransition(STATE_ZOOMED_2TAPS, () -> {
+            allowEventDelegation();
+            fastForward1sec();
+        }, STATE_ZOOMED);
+
+        // tap+tap+swipe doesn't get delegated
+        assertTransition(STATE_2TAPS, () -> swipe(), STATE_IDLE);
+
+        // tap+tap+swipe initiates viewport dragging immediately
+        assertTransition(STATE_2TAPS, () -> swipeAndHold(), STATE_DRAGGING_TMP);
+    }
+
+    @Test
+    public void testNonTransitions_dontChangeState() {
+        // ACTION_POINTER_DOWN triggers event delegation if not magnifying
+        assertStaysIn(STATE_IDLE, () -> {
+            allowEventDelegation();
+            send(downEvent());
+            send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_X * 2, DEFAULT_Y));
+        });
+
+        // Long tap breaks the triple-tap detection sequence
+        Runnable tapAndLongTap = () -> {
+            allowEventDelegation();
+            tap();
+            longTap();
+        };
+        assertStaysIn(STATE_IDLE, tapAndLongTap);
+        assertStaysIn(STATE_ZOOMED, tapAndLongTap);
+
+        // Triple tap with delays in between doesn't count
+        Runnable slow3tap = () -> {
+            tap();
+            fastForward1sec();
+            tap();
+            fastForward1sec();
+            tap();
+        };
+        assertStaysIn(STATE_IDLE, slow3tap);
+        assertStaysIn(STATE_ZOOMED, slow3tap);
+    }
+
+    @Test
+    public void testDisablingTripleTap_removesInputLag() {
+        mMgh = newInstance(/* detect3tap */ false, /* detectShortcut */ true);
+        goFromStateIdleTo(STATE_IDLE);
+        allowEventDelegation();
+        tap();
+        // no fast forward
+        verify(mMgh.getNext(), times(2)).onMotionEvent(any(), any(), anyInt());
+    }
+
+    @Test
+    public void testTripleTapAndHold_zoomsImmediately() {
+        assertZoomsImmediatelyOnSwipeFrom(STATE_2TAPS);
+        assertZoomsImmediatelyOnSwipeFrom(STATE_SHORTCUT_TRIGGERED);
+    }
+
+    @Test
+    public void testMultiTap_outOfDistanceSlop_shouldInIdle() {
+        // All delay motion events should be sent, if multi-tap with out of distance slop.
+        // STATE_IDLE will check if tapCount() < 2.
+        allowEventDelegation();
+        assertStaysIn(STATE_IDLE, () -> {
+            tap();
+            tap(DEFAULT_X * 2, DEFAULT_Y * 2);
+        });
+        assertStaysIn(STATE_IDLE, () -> {
+            tap();
+            tap(DEFAULT_X * 2, DEFAULT_Y * 2);
+            tap();
+            tap(DEFAULT_X * 2, DEFAULT_Y * 2);
+            tap();
+        });
+    }
+
+    private void assertZoomsImmediatelyOnSwipeFrom(int state) {
+        goFromStateIdleTo(state);
+        swipeAndHold();
+        assertIn(STATE_DRAGGING_TMP);
+        returnToNormalFrom(STATE_DRAGGING_TMP);
+    }
+
+    private void assertTransition(int fromState, Runnable transitionAction, int toState) {
+        goFromStateIdleTo(fromState);
+        transitionAction.run();
+        assertIn(toState);
+        returnToNormalFrom(toState);
+    }
+
+    private void assertStaysIn(int state, Runnable action) {
+        assertTransition(state, action, state);
+    }
+
+    private void forEachState(IntConsumer action) {
+        for (int state = FIRST_STATE; state <= LAST_STATE; state++) {
+            action.accept(state);
+        }
+    }
+
+    private void allowEventDelegation() {
+        doNothing().when(mMgh.getNext()).onMotionEvent(any(), any(), anyInt());
+    }
+
+    private void fastForward1sec() {
+        fastForward(1000);
+    }
+
+    private void fastForward(int ms) {
+        mClock.fastForward(ms);
+        mHandler.timeAdvance();
+    }
+
+    /**
+     * Asserts that {@link #mMgh the handler} is in the given {@code state}
+     */
+    private void assertIn(int state) {
+        switch (state) {
+
+            // Asserts on separate lines for accurate stack traces
+
+            case STATE_IDLE: {
+                check(tapCount() < 2, state);
+                check(!mMgh.mDetectingState.mShortcutTriggered, state);
+                check(!isZoomed(), state);
+            } break;
+            case STATE_ZOOMED: {
+                check(isZoomed(), state);
+                check(tapCount() < 2, state);
+            } break;
+            case STATE_2TAPS: {
+                check(!isZoomed(), state);
+                check(tapCount() == 2, state);
+            } break;
+            case STATE_ZOOMED_2TAPS: {
+                check(isZoomed(), state);
+                check(tapCount() == 2, state);
+            } break;
+            case STATE_DRAGGING: {
+                check(isZoomed(), state);
+                check(mMgh.mCurrentState == mMgh.mViewportDraggingState,
+                        state);
+                check(mMgh.mViewportDraggingState.mZoomedInBeforeDrag, state);
+            } break;
+            case STATE_DRAGGING_TMP: {
+                check(isZoomed(), state);
+                check(mMgh.mCurrentState == mMgh.mViewportDraggingState,
+                        state);
+                check(!mMgh.mViewportDraggingState.mZoomedInBeforeDrag, state);
+            } break;
+            case STATE_SHORTCUT_TRIGGERED: {
+                check(mMgh.mDetectingState.mShortcutTriggered, state);
+                check(!isZoomed(), state);
+            } break;
+            case STATE_PANNING: {
+                check(isZoomed(), state);
+                check(mMgh.mCurrentState == mMgh.mPanningScalingState,
+                        state);
+                check(!mMgh.mPanningScalingState.mScaling, state);
+            } break;
+            case STATE_SCALING_AND_PANNING: {
+                check(isZoomed(), state);
+                check(mMgh.mCurrentState == mMgh.mPanningScalingState,
+                        state);
+                check(mMgh.mPanningScalingState.mScaling, state);
+            } break;
+            default: throw new IllegalArgumentException("Illegal state: " + state);
+        }
+    }
+
+    /**
+     * Defines a "canonical" path from {@link #STATE_IDLE} to {@code state}
+     */
+    private void goFromStateIdleTo(int state) {
+        try {
+            switch (state) {
+                case STATE_IDLE: {
+                    mMgh.clearAndTransitionToStateDetecting();
+                } break;
+                case STATE_2TAPS: {
+                    goFromStateIdleTo(STATE_IDLE);
+                    tap();
+                    tap();
+                } break;
+                case STATE_ZOOMED: {
+                    if (mMgh.mDetectTripleTap) {
+                        goFromStateIdleTo(STATE_2TAPS);
+                        tap();
+                    } else {
+                        goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED);
+                        tap();
+                    }
+                } break;
+                case STATE_ZOOMED_2TAPS: {
+                    goFromStateIdleTo(STATE_ZOOMED);
+                    tap();
+                    tap();
+                } break;
+                case STATE_DRAGGING: {
+                    goFromStateIdleTo(STATE_ZOOMED_2TAPS);
+                    send(downEvent());
+                    fastForward1sec();
+                } break;
+                case STATE_DRAGGING_TMP: {
+                    goFromStateIdleTo(STATE_2TAPS);
+                    send(downEvent());
+                    fastForward1sec();
+                } break;
+                case STATE_SHORTCUT_TRIGGERED: {
+                    goFromStateIdleTo(STATE_IDLE);
+                    triggerShortcut();
+                } break;
+                case STATE_PANNING: {
+                    goFromStateIdleTo(STATE_ZOOMED);
+                    send(downEvent());
+                    send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_X * 2, DEFAULT_Y));
+                } break;
+                case STATE_SCALING_AND_PANNING: {
+                    goFromStateIdleTo(STATE_PANNING);
+                    send(pointerEvent(ACTION_MOVE, DEFAULT_X * 2, DEFAULT_Y * 3));
+                    send(pointerEvent(ACTION_MOVE, DEFAULT_X * 2, DEFAULT_Y * 4));
+                } break;
+                default:
+                    throw new IllegalArgumentException("Illegal state: " + state);
+            }
+        } catch (Throwable t) {
+            throw new RuntimeException("Failed to go to state " + stateToString(state), t);
+        }
+    }
+
+    /**
+     * Defines a "canonical" path from {@code state} to {@link #STATE_IDLE}
+     */
+    private void returnToNormalFrom(int state) {
+        switch (state) {
+            case STATE_IDLE: {
+                // no op
+            } break;
+            case STATE_2TAPS: {
+                allowEventDelegation();
+                fastForward1sec();
+            } break;
+            case STATE_ZOOMED: {
+                if (mMgh.mDetectTripleTap) {
+                    tap();
+                    tap();
+                    returnToNormalFrom(STATE_ZOOMED_2TAPS);
+                } else {
+                    triggerShortcut();
+                }
+            } break;
+            case STATE_ZOOMED_2TAPS: {
+                tap();
+            } break;
+            case STATE_DRAGGING: {
+                send(upEvent());
+                returnToNormalFrom(STATE_ZOOMED);
+            } break;
+            case STATE_DRAGGING_TMP: {
+                send(upEvent());
+            } break;
+            case STATE_SHORTCUT_TRIGGERED: {
+                triggerShortcut();
+            } break;
+            case STATE_PANNING: {
+                send(pointerEvent(ACTION_POINTER_UP, DEFAULT_X * 2, DEFAULT_Y));
+                send(upEvent());
+                returnToNormalFrom(STATE_ZOOMED);
+            } break;
+            case STATE_SCALING_AND_PANNING: {
+                returnToNormalFrom(STATE_PANNING);
+            } break;
+            default: throw new IllegalArgumentException("Illegal state: " + state);
+        }
+    }
+
+    private void check(boolean condition, int expectedState) {
+        if (!condition) {
+            fail("Expected to be in state " + stateToString(expectedState) + stateDump());
+        }
+    }
+
+    private boolean isZoomed() {
+        return mMgh.mMagnificationController.isMagnifying(DISPLAY_0);
+    }
+
+    private int tapCount() {
+        return mMgh.mDetectingState.tapCount();
+    }
+
+    private static String stateToString(int state) {
+        return DebugUtils.valueToString(FullScreenMagnificationGestureHandlerTest.class, "STATE_",
+                state);
+    }
+
+    private void tap() {
+        send(downEvent());
+        send(upEvent());
+    }
+
+    private void tap(float x, float y) {
+        send(downEvent(x, y));
+        send(upEvent(x, y));
+    }
+
+    private void swipe() {
+        swipeAndHold();
+        send(upEvent());
+    }
+
+    private void swipeAndHold() {
+        send(downEvent());
+        send(moveEvent(DEFAULT_X * 2, DEFAULT_Y * 2));
+    }
+
+    private void longTap() {
+        send(downEvent());
+        fastForward(2000);
+        send(upEvent());
+    }
+
+    private void triggerShortcut() {
+        mMgh.notifyShortcutTriggered();
+    }
+
+    private void send(MotionEvent event) {
+        event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+        try {
+            mMgh.onMotionEvent(event, event, /* policyFlags */ 0);
+        } catch (Throwable t) {
+            throw new RuntimeException("Exception while handling " + event, t);
+        }
+        fastForward(1);
+    }
+
+    private static MotionEvent fromTouchscreen(MotionEvent ev) {
+        ev.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+        return ev;
+    }
+
+    private MotionEvent moveEvent(float x, float y) {
+        return fromTouchscreen(
+                MotionEvent.obtain(mLastDownTime, mClock.now(), ACTION_MOVE, x, y, 0));
+    }
+
+    private MotionEvent downEvent() {
+        return downEvent(DEFAULT_X, DEFAULT_Y);
+    }
+
+    private MotionEvent downEvent(float x, float y) {
+        mLastDownTime = mClock.now();
+        return fromTouchscreen(MotionEvent.obtain(mLastDownTime, mLastDownTime,
+                ACTION_DOWN, x, y, 0));
+    }
+
+    private MotionEvent upEvent() {
+        return upEvent(DEFAULT_X, DEFAULT_Y, mLastDownTime);
+    }
+
+    private MotionEvent upEvent(float x, float y) {
+        return upEvent(x, y, mLastDownTime);
+    }
+
+    private MotionEvent upEvent(float x, float y, long downTime) {
+        return fromTouchscreen(MotionEvent.obtain(downTime, mClock.now(),
+                MotionEvent.ACTION_UP, x, y, 0));
+    }
+
+    private MotionEvent pointerEvent(int action, float x, float y) {
+        MotionEvent.PointerProperties defPointerProperties = new MotionEvent.PointerProperties();
+        defPointerProperties.id = 0;
+        defPointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER;
+        MotionEvent.PointerProperties pointerProperties = new MotionEvent.PointerProperties();
+        pointerProperties.id = 1;
+        pointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER;
+
+        MotionEvent.PointerCoords defPointerCoords = new MotionEvent.PointerCoords();
+        defPointerCoords.x = DEFAULT_X;
+        defPointerCoords.y = DEFAULT_Y;
+        MotionEvent.PointerCoords pointerCoords = new MotionEvent.PointerCoords();
+        pointerCoords.x = x;
+        pointerCoords.y = y;
+
+        return MotionEvent.obtain(
+            /* downTime */ mClock.now(),
+            /* eventTime */ mClock.now(),
+            /* action */ action,
+            /* pointerCount */ 2,
+            /* pointerProperties */ new MotionEvent.PointerProperties[] {
+                    defPointerProperties, pointerProperties},
+            /* pointerCoords */ new MotionEvent.PointerCoords[] { defPointerCoords, pointerCoords },
+            /* metaState */ 0,
+            /* buttonState */ 0,
+            /* xPrecision */ 1.0f,
+            /* yPrecision */ 1.0f,
+            /* deviceId */ 0,
+            /* edgeFlags */ 0,
+            /* source */ InputDevice.SOURCE_TOUCHSCREEN,
+            /* flags */ 0);
+    }
+
+    private String stateDump() {
+        return "\nCurrent state dump:\n" + mMgh + "\n" + mHandler.getPendingMessages();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java b/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java
index 23ce483..4123556 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java
@@ -44,6 +44,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.accessibility.KeyEventDispatcher.KeyEventFilter;
+import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.policy.WindowManagerPolicy;
 
 import org.hamcrest.Description;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java
index 322653b..78e651b 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java
@@ -33,6 +33,7 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.policy.WindowManagerPolicy;
 
 import org.hamcrest.Description;
@@ -212,4 +213,4 @@
             description.appendText("Matches key event");
         }
     }
-}
\ No newline at end of file
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
index 773b877..82c6498 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
@@ -47,6 +47,7 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.wm.WindowManagerInternal;
 import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
deleted file mode 100644
index de7d77d..0000000
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
+++ /dev/null
@@ -1,656 +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.server.accessibility;
-
-import static android.view.MotionEvent.ACTION_DOWN;
-import static android.view.MotionEvent.ACTION_MOVE;
-import static android.view.MotionEvent.ACTION_POINTER_DOWN;
-import static android.view.MotionEvent.ACTION_POINTER_UP;
-
-import static com.android.server.testutils.TestUtils.strictMock;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.animation.ValueAnimator;
-import android.annotation.NonNull;
-import android.content.Context;
-import android.os.Handler;
-import android.os.Message;
-import android.util.DebugUtils;
-import android.view.InputDevice;
-import android.view.MotionEvent;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.testutils.OffsettableClock;
-import com.android.server.testutils.TestHandler;
-import com.android.server.wm.WindowManagerInternal;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.function.IntConsumer;
-
-/**
- * Tests the state transitions of {@link MagnificationGestureHandler}
- *
- * Here's a dot graph describing the transitions being tested:
- * {@code
- *      digraph {
- *          IDLE -> SHORTCUT_TRIGGERED [label="a11y\nbtn"]
- *          SHORTCUT_TRIGGERED -> IDLE [label="a11y\nbtn"]
- *          IDLE -> DOUBLE_TAP [label="2tap"]
- *          DOUBLE_TAP -> IDLE [label="timeout"]
- *          DOUBLE_TAP -> TRIPLE_TAP_AND_HOLD [label="down"]
- *          SHORTCUT_TRIGGERED -> TRIPLE_TAP_AND_HOLD [label="down"]
- *          TRIPLE_TAP_AND_HOLD -> ZOOMED [label="up"]
- *          TRIPLE_TAP_AND_HOLD -> DRAGGING_TMP [label="hold/\nswipe"]
- *          DRAGGING_TMP -> IDLE [label="release"]
- *          ZOOMED -> ZOOMED_DOUBLE_TAP [label="2tap"]
- *          ZOOMED_DOUBLE_TAP -> ZOOMED [label="timeout"]
- *          ZOOMED_DOUBLE_TAP -> DRAGGING [label="hold"]
- *          ZOOMED_DOUBLE_TAP -> IDLE [label="tap"]
- *          DRAGGING -> ZOOMED [label="release"]
- *          ZOOMED -> IDLE [label="a11y\nbtn"]
- *          ZOOMED -> PANNING [label="2hold"]
- *          PANNING -> PANNING_SCALING [label="pinch"]
- *          PANNING_SCALING -> ZOOMED [label="release"]
- *          PANNING -> ZOOMED [label="release"]
- *      }
- * }
- */
-@RunWith(AndroidJUnit4.class)
-public class MagnificationGestureHandlerTest {
-
-    public static final int STATE_IDLE = 1;
-    public static final int STATE_ZOOMED = 2;
-    public static final int STATE_2TAPS = 3;
-    public static final int STATE_ZOOMED_2TAPS = 4;
-    public static final int STATE_SHORTCUT_TRIGGERED = 5;
-    public static final int STATE_DRAGGING_TMP = 6;
-    public static final int STATE_DRAGGING = 7;
-    public static final int STATE_PANNING = 8;
-    public static final int STATE_SCALING_AND_PANNING = 9;
-
-
-    public static final int FIRST_STATE = STATE_IDLE;
-    public static final int LAST_STATE = STATE_SCALING_AND_PANNING;
-
-    // Co-prime x and y, to potentially catch x-y-swapped errors
-    public static final float DEFAULT_X = 301;
-    public static final float DEFAULT_Y = 299;
-
-    private static final int DISPLAY_0 = 0;
-
-    private Context mContext;
-    MagnificationController mMagnificationController;
-
-    private OffsettableClock mClock;
-    private MagnificationGestureHandler mMgh;
-    private TestHandler mHandler;
-
-    private long mLastDownTime = Integer.MIN_VALUE;
-
-    @Before
-    public void setUp() {
-        mContext = InstrumentationRegistry.getContext();
-        final MagnificationController.ControllerContext mockController =
-                mock(MagnificationController.ControllerContext.class);
-        final WindowManagerInternal mockWindowManager = mock(WindowManagerInternal.class);
-        when(mockController.getContext()).thenReturn(mContext);
-        when(mockController.getAms()).thenReturn(mock(AccessibilityManagerService.class));
-        when(mockController.getWindowManager()).thenReturn(mockWindowManager);
-        when(mockController.getHandler()).thenReturn(new Handler(mContext.getMainLooper()));
-        when(mockController.newValueAnimator()).thenReturn(new ValueAnimator());
-        when(mockController.getAnimationDuration()).thenReturn(1000L);
-        when(mockWindowManager.setMagnificationCallbacks(eq(DISPLAY_0), any())).thenReturn(true);
-        mMagnificationController = new MagnificationController(mockController, new Object()) {
-            @Override
-            public boolean magnificationRegionContains(int displayId, float x, float y) {
-                return true;
-            }
-
-            @Override
-            void setForceShowMagnifiableBounds(int displayId, boolean show) {}
-        };
-        mMagnificationController.register(DISPLAY_0);
-        mClock = new OffsettableClock.Stopped();
-
-        boolean detectTripleTap = true;
-        boolean detectShortcutTrigger = true;
-        mMgh = newInstance(detectTripleTap, detectShortcutTrigger);
-    }
-
-    @After
-    public void tearDown() {
-        mMagnificationController.unregister(DISPLAY_0);
-    }
-
-    @NonNull
-    private MagnificationGestureHandler newInstance(boolean detectTripleTap,
-            boolean detectShortcutTrigger) {
-        MagnificationGestureHandler h = new MagnificationGestureHandler(
-                mContext, mMagnificationController,
-                detectTripleTap, detectShortcutTrigger, DISPLAY_0);
-        mHandler = new TestHandler(h.mDetectingState, mClock) {
-            @Override
-            protected String messageToString(Message m) {
-                return DebugUtils.valueToString(
-                        MagnificationGestureHandler.DetectingState.class, "MESSAGE_", m.what);
-            }
-        };
-        h.mDetectingState.mHandler = mHandler;
-        h.setNext(strictMock(EventStreamTransformation.class));
-        return h;
-    }
-
-    @Test
-    public void testInitialState_isIdle() {
-        assertIn(STATE_IDLE);
-    }
-
-    /**
-     * Covers paths to get to and back between each state and {@link #STATE_IDLE}
-     * This navigates between states using "canonical" paths, specified in
-     * {@link #goFromStateIdleTo} (for traversing away from {@link #STATE_IDLE}) and
-     * {@link #returnToNormalFrom} (for navigating back to {@link #STATE_IDLE})
-     */
-    @Test
-    public void testEachState_isReachableAndRecoverable() {
-        forEachState(state -> {
-            goFromStateIdleTo(state);
-            assertIn(state);
-
-            returnToNormalFrom(state);
-            try {
-                assertIn(STATE_IDLE);
-            } catch (AssertionError e) {
-                throw new AssertionError("Failed while testing state " + stateToString(state), e);
-            }
-        });
-    }
-
-    @Test
-    public void testStates_areMutuallyExclusive() {
-        forEachState(state1 -> {
-            forEachState(state2 -> {
-                if (state1 < state2) {
-                    goFromStateIdleTo(state1);
-                    try {
-                        assertIn(state2);
-                        fail("State " + stateToString(state1) + " also implies state "
-                                + stateToString(state2) + stateDump());
-                    } catch (AssertionError e) {
-                        // expected
-                        returnToNormalFrom(state1);
-                    }
-                }
-            });
-        });
-    }
-
-    @Test
-    public void testTransitionToDelegatingStateAndClear_preservesShortcutTriggeredState() {
-        mMgh.mDetectingState.transitionToDelegatingStateAndClear();
-        assertFalse(mMgh.mDetectingState.mShortcutTriggered);
-
-        goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED);
-        mMgh.mDetectingState.transitionToDelegatingStateAndClear();
-        assertTrue(mMgh.mDetectingState.mShortcutTriggered);
-    }
-
-    /**
-     * Covers edges of the graph not covered by "canonical" transitions specified in
-     * {@link #goFromStateIdleTo} and {@link #returnToNormalFrom}
-     */
-    @SuppressWarnings("Convert2MethodRef")
-    @Test
-    public void testAlternativeTransitions_areWorking() {
-        // A11y button followed by a tap&hold turns temporary "viewport dragging" zoom on
-        assertTransition(STATE_SHORTCUT_TRIGGERED, () -> {
-            send(downEvent());
-            fastForward1sec();
-        }, STATE_DRAGGING_TMP);
-
-        // A11y button followed by a tap turns zoom on
-        assertTransition(STATE_SHORTCUT_TRIGGERED, () -> tap(), STATE_ZOOMED);
-
-        // A11y button pressed second time negates the 1st press
-        assertTransition(STATE_SHORTCUT_TRIGGERED, () -> triggerShortcut(), STATE_IDLE);
-
-        // A11y button turns zoom off
-        assertTransition(STATE_ZOOMED, () -> triggerShortcut(), STATE_IDLE);
-
-
-        // Double tap times out while zoomed
-        assertTransition(STATE_ZOOMED_2TAPS, () -> {
-            allowEventDelegation();
-            fastForward1sec();
-        }, STATE_ZOOMED);
-
-        // tap+tap+swipe doesn't get delegated
-        assertTransition(STATE_2TAPS, () -> swipe(), STATE_IDLE);
-
-        // tap+tap+swipe initiates viewport dragging immediately
-        assertTransition(STATE_2TAPS, () -> swipeAndHold(), STATE_DRAGGING_TMP);
-    }
-
-    @Test
-    public void testNonTransitions_dontChangeState() {
-        // ACTION_POINTER_DOWN triggers event delegation if not magnifying
-        assertStaysIn(STATE_IDLE, () -> {
-            allowEventDelegation();
-            send(downEvent());
-            send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_X * 2, DEFAULT_Y));
-        });
-
-        // Long tap breaks the triple-tap detection sequence
-        Runnable tapAndLongTap = () -> {
-            allowEventDelegation();
-            tap();
-            longTap();
-        };
-        assertStaysIn(STATE_IDLE, tapAndLongTap);
-        assertStaysIn(STATE_ZOOMED, tapAndLongTap);
-
-        // Triple tap with delays in between doesn't count
-        Runnable slow3tap = () -> {
-            tap();
-            fastForward1sec();
-            tap();
-            fastForward1sec();
-            tap();
-        };
-        assertStaysIn(STATE_IDLE, slow3tap);
-        assertStaysIn(STATE_ZOOMED, slow3tap);
-    }
-
-    @Test
-    public void testDisablingTripleTap_removesInputLag() {
-        mMgh = newInstance(/* detect3tap */ false, /* detectShortcut */ true);
-        goFromStateIdleTo(STATE_IDLE);
-        allowEventDelegation();
-        tap();
-        // no fast forward
-        verify(mMgh.getNext(), times(2)).onMotionEvent(any(), any(), anyInt());
-    }
-
-    @Test
-    public void testTripleTapAndHold_zoomsImmediately() {
-        assertZoomsImmediatelyOnSwipeFrom(STATE_2TAPS);
-        assertZoomsImmediatelyOnSwipeFrom(STATE_SHORTCUT_TRIGGERED);
-    }
-
-    @Test
-    public void testMultiTap_outOfDistanceSlop_shouldInIdle() {
-        // All delay motion events should be sent, if multi-tap with out of distance slop.
-        // STATE_IDLE will check if tapCount() < 2.
-        allowEventDelegation();
-        assertStaysIn(STATE_IDLE, () -> {
-            tap();
-            tap(DEFAULT_X * 2, DEFAULT_Y * 2);
-        });
-        assertStaysIn(STATE_IDLE, () -> {
-            tap();
-            tap(DEFAULT_X * 2, DEFAULT_Y * 2);
-            tap();
-            tap(DEFAULT_X * 2, DEFAULT_Y * 2);
-            tap();
-        });
-    }
-
-    private void assertZoomsImmediatelyOnSwipeFrom(int state) {
-        goFromStateIdleTo(state);
-        swipeAndHold();
-        assertIn(STATE_DRAGGING_TMP);
-        returnToNormalFrom(STATE_DRAGGING_TMP);
-    }
-
-    private void assertTransition(int fromState, Runnable transitionAction, int toState) {
-        goFromStateIdleTo(fromState);
-        transitionAction.run();
-        assertIn(toState);
-        returnToNormalFrom(toState);
-    }
-
-    private void assertStaysIn(int state, Runnable action) {
-        assertTransition(state, action, state);
-    }
-
-    private void forEachState(IntConsumer action) {
-        for (int state = FIRST_STATE; state <= LAST_STATE; state++) {
-            action.accept(state);
-        }
-    }
-
-    private void allowEventDelegation() {
-        doNothing().when(mMgh.getNext()).onMotionEvent(any(), any(), anyInt());
-    }
-
-    private void fastForward1sec() {
-        fastForward(1000);
-    }
-
-    private void fastForward(int ms) {
-        mClock.fastForward(ms);
-        mHandler.timeAdvance();
-    }
-
-    /**
-     * Asserts that {@link #mMgh the handler} is in the given {@code state}
-     */
-    private void assertIn(int state) {
-        switch (state) {
-
-            // Asserts on separate lines for accurate stack traces
-
-            case STATE_IDLE: {
-                check(tapCount() < 2, state);
-                check(!mMgh.mDetectingState.mShortcutTriggered, state);
-                check(!isZoomed(), state);
-            } break;
-            case STATE_ZOOMED: {
-                check(isZoomed(), state);
-                check(tapCount() < 2, state);
-            } break;
-            case STATE_2TAPS: {
-                check(!isZoomed(), state);
-                check(tapCount() == 2, state);
-            } break;
-            case STATE_ZOOMED_2TAPS: {
-                check(isZoomed(), state);
-                check(tapCount() == 2, state);
-            } break;
-            case STATE_DRAGGING: {
-                check(isZoomed(), state);
-                check(mMgh.mCurrentState == mMgh.mViewportDraggingState,
-                        state);
-                check(mMgh.mViewportDraggingState.mZoomedInBeforeDrag, state);
-            } break;
-            case STATE_DRAGGING_TMP: {
-                check(isZoomed(), state);
-                check(mMgh.mCurrentState == mMgh.mViewportDraggingState,
-                        state);
-                check(!mMgh.mViewportDraggingState.mZoomedInBeforeDrag, state);
-            } break;
-            case STATE_SHORTCUT_TRIGGERED: {
-                check(mMgh.mDetectingState.mShortcutTriggered, state);
-                check(!isZoomed(), state);
-            } break;
-            case STATE_PANNING: {
-                check(isZoomed(), state);
-                check(mMgh.mCurrentState == mMgh.mPanningScalingState,
-                        state);
-                check(!mMgh.mPanningScalingState.mScaling, state);
-            } break;
-            case STATE_SCALING_AND_PANNING: {
-                check(isZoomed(), state);
-                check(mMgh.mCurrentState == mMgh.mPanningScalingState,
-                        state);
-                check(mMgh.mPanningScalingState.mScaling, state);
-            } break;
-            default: throw new IllegalArgumentException("Illegal state: " + state);
-        }
-    }
-
-    /**
-     * Defines a "canonical" path from {@link #STATE_IDLE} to {@code state}
-     */
-    private void goFromStateIdleTo(int state) {
-        try {
-            switch (state) {
-                case STATE_IDLE: {
-                    mMgh.clearAndTransitionToStateDetecting();
-                } break;
-                case STATE_2TAPS: {
-                    goFromStateIdleTo(STATE_IDLE);
-                    tap();
-                    tap();
-                } break;
-                case STATE_ZOOMED: {
-                    if (mMgh.mDetectTripleTap) {
-                        goFromStateIdleTo(STATE_2TAPS);
-                        tap();
-                    } else {
-                        goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED);
-                        tap();
-                    }
-                } break;
-                case STATE_ZOOMED_2TAPS: {
-                    goFromStateIdleTo(STATE_ZOOMED);
-                    tap();
-                    tap();
-                } break;
-                case STATE_DRAGGING: {
-                    goFromStateIdleTo(STATE_ZOOMED_2TAPS);
-                    send(downEvent());
-                    fastForward1sec();
-                } break;
-                case STATE_DRAGGING_TMP: {
-                    goFromStateIdleTo(STATE_2TAPS);
-                    send(downEvent());
-                    fastForward1sec();
-                } break;
-                case STATE_SHORTCUT_TRIGGERED: {
-                    goFromStateIdleTo(STATE_IDLE);
-                    triggerShortcut();
-                } break;
-                case STATE_PANNING: {
-                    goFromStateIdleTo(STATE_ZOOMED);
-                    send(downEvent());
-                    send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_X * 2, DEFAULT_Y));
-                } break;
-                case STATE_SCALING_AND_PANNING: {
-                    goFromStateIdleTo(STATE_PANNING);
-                    send(pointerEvent(ACTION_MOVE, DEFAULT_X * 2, DEFAULT_Y * 3));
-                    send(pointerEvent(ACTION_MOVE, DEFAULT_X * 2, DEFAULT_Y * 4));
-                } break;
-                default:
-                    throw new IllegalArgumentException("Illegal state: " + state);
-            }
-        } catch (Throwable t) {
-            throw new RuntimeException("Failed to go to state " + stateToString(state), t);
-        }
-    }
-
-    /**
-     * Defines a "canonical" path from {@code state} to {@link #STATE_IDLE}
-     */
-    private void returnToNormalFrom(int state) {
-        switch (state) {
-            case STATE_IDLE: {
-                // no op
-            } break;
-            case STATE_2TAPS: {
-                allowEventDelegation();
-                fastForward1sec();
-            } break;
-            case STATE_ZOOMED: {
-                if (mMgh.mDetectTripleTap) {
-                    tap();
-                    tap();
-                    returnToNormalFrom(STATE_ZOOMED_2TAPS);
-                } else {
-                    triggerShortcut();
-                }
-            } break;
-            case STATE_ZOOMED_2TAPS: {
-                tap();
-            } break;
-            case STATE_DRAGGING: {
-                send(upEvent());
-                returnToNormalFrom(STATE_ZOOMED);
-            } break;
-            case STATE_DRAGGING_TMP: {
-                send(upEvent());
-            } break;
-            case STATE_SHORTCUT_TRIGGERED: {
-                triggerShortcut();
-            } break;
-            case STATE_PANNING: {
-                send(pointerEvent(ACTION_POINTER_UP, DEFAULT_X * 2, DEFAULT_Y));
-                send(upEvent());
-                returnToNormalFrom(STATE_ZOOMED);
-            } break;
-            case STATE_SCALING_AND_PANNING: {
-                returnToNormalFrom(STATE_PANNING);
-            } break;
-            default: throw new IllegalArgumentException("Illegal state: " + state);
-        }
-    }
-
-    private void check(boolean condition, int expectedState) {
-        if (!condition) {
-            fail("Expected to be in state " + stateToString(expectedState) + stateDump());
-        }
-    }
-
-    private boolean isZoomed() {
-        return mMgh.mMagnificationController.isMagnifying(DISPLAY_0);
-    }
-
-    private int tapCount() {
-        return mMgh.mDetectingState.tapCount();
-    }
-
-    private static String stateToString(int state) {
-        return DebugUtils.valueToString(MagnificationGestureHandlerTest.class, "STATE_", state);
-    }
-
-    private void tap() {
-        send(downEvent());
-        send(upEvent());
-    }
-
-    private void tap(float x, float y) {
-        send(downEvent(x, y));
-        send(upEvent(x, y));
-    }
-
-    private void swipe() {
-        swipeAndHold();
-        send(upEvent());
-    }
-
-    private void swipeAndHold() {
-        send(downEvent());
-        send(moveEvent(DEFAULT_X * 2, DEFAULT_Y * 2));
-    }
-
-    private void longTap() {
-        send(downEvent());
-        fastForward(2000);
-        send(upEvent());
-    }
-
-    private void triggerShortcut() {
-        mMgh.notifyShortcutTriggered();
-    }
-
-    private void send(MotionEvent event) {
-        event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
-        try {
-            mMgh.onMotionEvent(event, event, /* policyFlags */ 0);
-        } catch (Throwable t) {
-            throw new RuntimeException("Exception while handling " + event, t);
-        }
-        fastForward(1);
-    }
-
-    private static MotionEvent fromTouchscreen(MotionEvent ev) {
-        ev.setSource(InputDevice.SOURCE_TOUCHSCREEN);
-        return ev;
-    }
-
-    private MotionEvent moveEvent(float x, float y) {
-        return fromTouchscreen(
-        	    MotionEvent.obtain(mLastDownTime, mClock.now(), ACTION_MOVE, x, y, 0));
-    }
-
-    private MotionEvent downEvent() {
-        return downEvent(DEFAULT_X, DEFAULT_Y);
-    }
-
-    private MotionEvent downEvent(float x, float y) {
-        mLastDownTime = mClock.now();
-        return fromTouchscreen(MotionEvent.obtain(mLastDownTime, mLastDownTime,
-                ACTION_DOWN, x, y, 0));
-    }
-
-    private MotionEvent upEvent() {
-        return upEvent(DEFAULT_X, DEFAULT_Y, mLastDownTime);
-    }
-
-    private MotionEvent upEvent(float x, float y) {
-        return upEvent(x, y, mLastDownTime);
-    }
-
-    private MotionEvent upEvent(float x, float y, long downTime) {
-        return fromTouchscreen(MotionEvent.obtain(downTime, mClock.now(),
-                MotionEvent.ACTION_UP, x, y, 0));
-    }
-
-    private MotionEvent pointerEvent(int action, float x, float y) {
-        MotionEvent.PointerProperties defPointerProperties = new MotionEvent.PointerProperties();
-        defPointerProperties.id = 0;
-        defPointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER;
-        MotionEvent.PointerProperties pointerProperties = new MotionEvent.PointerProperties();
-        pointerProperties.id = 1;
-        pointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER;
-
-        MotionEvent.PointerCoords defPointerCoords = new MotionEvent.PointerCoords();
-        defPointerCoords.x = DEFAULT_X;
-        defPointerCoords.y = DEFAULT_Y;
-        MotionEvent.PointerCoords pointerCoords = new MotionEvent.PointerCoords();
-        pointerCoords.x = x;
-        pointerCoords.y = y;
-
-        return MotionEvent.obtain(
-            /* downTime */ mClock.now(),
-            /* eventTime */ mClock.now(),
-            /* action */ action,
-            /* pointerCount */ 2,
-            /* pointerProperties */ new MotionEvent.PointerProperties[] {
-                        defPointerProperties, pointerProperties },
-            /* pointerCoords */ new MotionEvent.PointerCoords[] { defPointerCoords, pointerCoords },
-            /* metaState */ 0,
-            /* buttonState */ 0,
-            /* xPrecision */ 1.0f,
-            /* yPrecision */ 1.0f,
-            /* deviceId */ 0,
-            /* edgeFlags */ 0,
-            /* source */ InputDevice.SOURCE_TOUCHSCREEN,
-            /* flags */ 0);
-    }
-
-    private String stateDump() {
-        return "\nCurrent state dump:\n" + mMgh + "\n" + mHandler.getPendingMessages();
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java b/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java
deleted file mode 100644
index e2b517f..0000000
--- a/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java
+++ /dev/null
@@ -1,118 +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.server.accessibility;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Pair;
-
-import androidx.test.InstrumentationRegistry;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Utility class to capture messages dispatched through a handler and control when they arrive
- * at their target.
- */
-public class MessageCapturingHandler extends Handler {
-    List<Pair<Message, Long>> timedMessages = new ArrayList<>();
-
-    Handler.Callback mCallback;
-
-    public MessageCapturingHandler(Handler.Callback callback) {
-        this(InstrumentationRegistry.getContext().getMainLooper(), callback);
-    }
-
-    public MessageCapturingHandler(Looper looper, Callback callback) {
-        super(looper);
-        mCallback = callback;
-    }
-
-    /**
-     * Holding messages in queue, but never dispatching.
-     * @see #removeAllMessages()
-     */
-    @Override
-    public boolean sendMessageAtTime(Message message, long uptimeMillis) {
-        timedMessages.add(new Pair<>(Message.obtain(message), uptimeMillis));
-        return super.sendMessageAtTime(message, Long.MAX_VALUE);
-    }
-
-    public void setCallback(Handler.Callback callback) {
-        mCallback = callback;
-    }
-
-    public void sendOneMessage() {
-        Message message = timedMessages.remove(0).first;
-        removeMessages(message.what, message.obj);
-        dispatchMessage(message);
-        removeStaleMessages();
-    }
-
-    public void sendAllMessages() {
-        while (!timedMessages.isEmpty()) {
-            sendOneMessage();
-        }
-    }
-
-    public void sendLastMessage() {
-        Message message = timedMessages.remove(timedMessages.size() - 1).first;
-        removeMessages(message.what, message.obj);
-        dispatchMessage(message);
-        removeStaleMessages();
-    }
-
-    /**
-     * Clear messages sent from this handler in queue.
-     * <p>
-     * If main looper is used, this method should be called in tear down function
-     * to ensure messages isolation between test cases.
-     * </p>
-     */
-    public void removeAllMessages() {
-        if (hasMessages()) {
-            for (int i = 0; i < timedMessages.size(); i++) {
-                Message message = timedMessages.get(i).first;
-                removeMessages(message.what, message.obj);
-            }
-        }
-    }
-
-    public boolean hasMessages() {
-        removeStaleMessages();
-        return !timedMessages.isEmpty();
-    }
-
-    private void removeStaleMessages() {
-        for (int i = 0; i < timedMessages.size(); i++) {
-            Message message = timedMessages.get(i).first;
-            if (!hasMessages(message.what, message.obj)) {
-                timedMessages.remove(i--);
-            }
-        }
-    }
-
-    public void dispatchMessage(Message m) {
-        if (mCallback != null) {
-            mCallback.handleMessage(m);
-            return;
-        }
-        super.dispatchMessage(m);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
index 36e854c..1ac4a8e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
@@ -54,6 +54,7 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.accessibility.utils.MotionEventMatcher;
 
 import org.hamcrest.Description;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
index 8da927d..c8baca6 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
@@ -37,6 +37,7 @@
 import android.os.IBinder;
 import android.view.accessibility.AccessibilityEvent;
 
+import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.wm.WindowManagerInternal;
 
 import org.junit.After;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
new file mode 100644
index 0000000..09466e7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.magnification;
+
+
+import static org.mockito.Mockito.verify;
+
+import android.os.RemoteException;
+import android.view.Display;
+import android.view.accessibility.IWindowMagnificationConnection;
+import android.view.accessibility.IWindowMagnificationConnectionCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for WindowMagnificationConnectionWrapper. We don't test {@code
+ * WindowMagnificationConnectionWrapper#linkToDeath(IBinder.DeathRecipient)} since it's tested in
+ * {@link WindowMagnificationManagerTest}.
+ */
+public class WindowMagnificationConnectionWrapperTest {
+
+    private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY;
+
+    @Mock
+    private IWindowMagnificationConnection mConnection;
+    @Mock
+    private IWindowMagnificationConnectionCallback mCallback;
+    private WindowMagnificationConnectionWrapper mConnectionWrapper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mConnectionWrapper = new WindowMagnificationConnectionWrapper(mConnection);
+    }
+
+    @Test
+    public void enableWindowMagnification() throws RemoteException {
+        mConnectionWrapper.enableWindowMagnification(TEST_DISPLAY, 2, 100f, 200f);
+        verify(mConnection).enableWindowMagnification(TEST_DISPLAY, 2, 100f, 200f);
+    }
+
+    @Test
+    public void setScale() throws RemoteException {
+        mConnectionWrapper.setScale(TEST_DISPLAY, 3.0f);
+        verify(mConnection).setScale(TEST_DISPLAY, 3.0f);
+    }
+
+    @Test
+    public void disableWindowMagnification() throws RemoteException {
+        mConnectionWrapper.disableWindowMagnification(TEST_DISPLAY);
+        verify(mConnection).disableWindowMagnification(TEST_DISPLAY);
+    }
+
+    @Test
+    public void moveWindowMagnifier() throws RemoteException {
+        mConnectionWrapper.moveWindowMagnifier(0, 100, 150);
+        verify(mConnection).moveWindowMagnifier(0, 100, 150);
+    }
+
+    @Test
+    public void setMirrorWindowCallback() throws RemoteException {
+        mConnectionWrapper.setConnectionCallback(mCallback);
+        verify(mConnection).setConnectionCallback(mCallback);
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
new file mode 100644
index 0000000..780a6c0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.magnification;
+
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+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 static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.view.accessibility.IWindowMagnificationConnection;
+import android.view.accessibility.IWindowMagnificationConnectionCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests for WindowMagnificationManager.
+ */
+public class WindowMagnificationManagerTest {
+
+    private MockWindowMagnificationConnection mMockConnection;
+    private WindowMagnificationManager mWindowMagnificationManager;
+
+    @Before
+    public void setUp() {
+        mMockConnection = new MockWindowMagnificationConnection();
+        mWindowMagnificationManager = new WindowMagnificationManager();
+    }
+
+    @Test
+    public void setConnection_connectionIsNull_wrapperIsNullAndLinkToDeath() {
+        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+        assertNotNull(mWindowMagnificationManager.mConnectionWrapper);
+        verify(mMockConnection.asBinder()).linkToDeath(any(IBinder.DeathRecipient.class), eq(0));
+    }
+
+    @Test
+    public void setConnection_connectionIsNull_setMirrorWindowCallbackAndHasWrapper()
+            throws RemoteException {
+        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+
+        assertNotNull(mWindowMagnificationManager.mConnectionWrapper);
+        verify(mMockConnection.asBinder()).linkToDeath(any(IBinder.DeathRecipient.class), eq(0));
+        verify(mMockConnection.getConnection()).setConnectionCallback(
+                any(IWindowMagnificationConnectionCallback.class));
+    }
+
+    @Test
+    public void binderDied_hasConnection_wrapperIsNullAndUnlinkToDeath() {
+        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+
+        mMockConnection.getDeathRecipient().binderDied();
+
+        assertNull(mWindowMagnificationManager.mConnectionWrapper);
+        verify(mMockConnection.asBinder()).unlinkToDeath(mMockConnection.getDeathRecipient(),
+                0);
+    }
+
+    /**
+     * This test simulates {@link WindowMagnificationManager#setConnection} is called by thread A
+     * and then the former connection is called by thread B. In this situation we should keep the
+     * new connection.
+     */
+    @Test
+    public void
+            setSecondConnectionAndFormerConnectionBinderDead_hasWrapperAndNotCallUnlinkToDeath() {
+        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+        MockWindowMagnificationConnection secondConnection =
+                new MockWindowMagnificationConnection();
+
+        mWindowMagnificationManager.setConnection(secondConnection.getConnection());
+        mMockConnection.getDeathRecipient().binderDied();
+
+        assertNotNull(mWindowMagnificationManager.mConnectionWrapper);
+        verify(mMockConnection.asBinder()).unlinkToDeath(mMockConnection.getDeathRecipient(), 0);
+        verify(secondConnection.asBinder(), never()).unlinkToDeath(
+                secondConnection.getDeathRecipient(), 0);
+    }
+
+    @Test
+    public void setNullConnection_hasConnection_wrapperIsNull() throws RemoteException {
+        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+
+        mWindowMagnificationManager.setConnection(null);
+
+        assertNull(mWindowMagnificationManager.mConnectionWrapper);
+        verify(mMockConnection.getConnection()).setConnectionCallback(null);
+    }
+
+    private static class MockWindowMagnificationConnection  {
+
+        private final IWindowMagnificationConnection mConnection;
+        private final Binder mBinder;
+        private IBinder.DeathRecipient mDeathRecipient;
+
+        MockWindowMagnificationConnection() {
+            mConnection = mock(IWindowMagnificationConnection.class);
+            mBinder = mock(Binder.class);
+            when(mConnection.asBinder()).thenReturn(mBinder);
+            doAnswer((invocation) -> {
+                mDeathRecipient = invocation.getArgument(0);
+                return null;
+            }).when(mBinder).linkToDeath(
+                    any(IBinder.DeathRecipient.class), eq(0));
+        }
+
+        IWindowMagnificationConnection getConnection() {
+            return mConnection;
+        }
+
+        public IBinder.DeathRecipient getDeathRecipient() {
+            return mDeathRecipient;
+        }
+
+        Binder asBinder() {
+            return mBinder;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index 0f11566..39a3aae 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -212,7 +212,8 @@
 
         String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE};
         when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list);
-        Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName());
+        Account[] accounts = mAms.getAccountsAsUser(null,
+                UserHandle.getCallingUserId(), mContext.getOpPackageName());
         Arrays.sort(accounts, new AccountSorter());
         assertEquals(6, accounts.length);
         assertEquals(a11, accounts[0]);
@@ -222,8 +223,8 @@
         assertEquals(a22, accounts[4]);
         assertEquals(a32, accounts[5]);
 
-        accounts = mAms.getAccounts(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
-                mContext.getOpPackageName());
+        accounts = mAms.getAccountsAsUser(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
+                UserHandle.getCallingUserId(), mContext.getOpPackageName());
         Arrays.sort(accounts, new AccountSorter());
         assertEquals(3, accounts.length);
         assertEquals(a11, accounts[0]);
@@ -232,8 +233,8 @@
 
         mAms.removeAccountInternal(a21);
 
-        accounts = mAms.getAccounts(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
-                mContext.getOpPackageName());
+        accounts = mAms.getAccountsAsUser(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
+                UserHandle.getCallingUserId(), mContext.getOpPackageName());
         Arrays.sort(accounts, new AccountSorter());
         assertEquals(2, accounts.length);
         assertEquals(a11, accounts[0]);
@@ -373,7 +374,8 @@
         unlockSystemUser();
         String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE};
         when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list);
-        Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName());
+        Account[] accounts = mAms.getAccountsAsUser(null, UserHandle.getCallingUserId(),
+                mContext.getOpPackageName());
         assertEquals("1 account should be migrated", 1, accounts.length);
         assertEquals(PreNTestDatabaseHelper.ACCOUNT_NAME, accounts[0].name);
         assertEquals(PreNTestDatabaseHelper.ACCOUNT_PASSWORD, mAms.getPassword(accounts[0]));
@@ -2980,7 +2982,8 @@
                     Log.d(TAG, logPrefix + " getAccounts started");
                     long ti = System.currentTimeMillis();
                     try {
-                        Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName());
+                        Account[] accounts = mAms.getAccountsAsUser(null,
+                                UserHandle.getCallingUserId(), mContext.getOpPackageName());
                         if (accounts == null || accounts.length != 1
                                 || !AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1.equals(
                                 accounts[0].type)) {
@@ -3051,7 +3054,8 @@
                     Log.d(TAG, logPrefix + " getAccounts started");
                     long ti = System.currentTimeMillis();
                     try {
-                        Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName());
+                        Account[] accounts = mAms.getAccountsAsUser(null,
+                                UserHandle.getCallingUserId(), mContext.getOpPackageName());
                         if (accounts == null || accounts.length != 1
                                 || !AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1.equals(
                                 accounts[0].type)) {
diff --git a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
index d4182f3..5a1ad86 100644
--- a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
@@ -672,6 +672,30 @@
                 connectionTime2, mKeyStore.getLastConnectionTime(TEST_KEY_2));
     }
 
+    @Test
+    public void testClearAuthorizationsBeforeAdbEnabled() throws Exception {
+        // The adb key store is not instantiated until adb is enabled; however if the user attempts
+        // to clear the adb authorizations when adb is disabled after a boot a NullPointerException
+        // was thrown as deleteKeyStore is invoked against the key store. This test ensures the
+        // key store can be successfully cleared when adb is disabled.
+        mHandler = mManager.new AdbDebuggingHandler(FgThread.get().getLooper());
+
+        clearKeyStore();
+    }
+
+    @Test
+    public void testClearAuthorizationsDeletesKeyFiles() throws Exception {
+        mAdbKeyFile.createNewFile();
+        mAdbKeyXmlFile.createNewFile();
+
+        clearKeyStore();
+
+        assertFalse("The adb key file should have been deleted after revocation of the grants",
+                mAdbKeyFile.exists());
+        assertFalse("The adb xml key file should have been deleted after revocation of the grants",
+                mAdbKeyXmlFile.exists());
+    }
+
     /**
      * Runs an adb test with the provided configuration.
      *
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 79cc3db..f122014 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -31,6 +31,7 @@
 
 import static com.google.android.collect.Lists.newArrayList;
 import static com.google.android.collect.Sets.newHashSet;
+import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertEquals;
@@ -54,6 +55,7 @@
 import static org.mockito.Mockito.when;
 
 import android.annotation.UserIdInt;
+import android.app.ActivityManager;
 import android.app.IUserSwitchObserver;
 import android.content.Context;
 import android.content.IIntentReceiver;
@@ -74,10 +76,10 @@
 import android.platform.test.annotations.Presubmit;
 import android.util.Log;
 
-
 import androidx.test.filters.SmallTest;
 
 import com.android.server.FgThread;
+import com.android.server.am.UserState.KeyEvictedCallback;
 import com.android.server.pm.UserManagerService;
 import com.android.server.wm.WindowManagerService;
 
@@ -107,6 +109,7 @@
     private static final int TEST_USER_ID = 100;
     private static final int TEST_USER_ID1 = 101;
     private static final int TEST_USER_ID2 = 102;
+    private static final int TEST_USER_ID3 = 103;
     private static final int NONEXIST_USER_ID = 2;
     private static final int TEST_PRE_CREATED_USER_ID = 103;
 
@@ -120,6 +123,8 @@
     private TestInjector mInjector;
     private final HashMap<Integer, UserState> mUserStates = new HashMap<>();
 
+    private final KeyEvictedCallback mKeyEvictedCallback = (userId) -> { /* ignore */ };
+
     private static final List<String> START_FOREGROUND_USER_ACTIONS = newArrayList(
             Intent.ACTION_USER_STARTED,
             Intent.ACTION_USER_SWITCHED,
@@ -153,6 +158,7 @@
             doNothing().when(mInjector).activityManagerOnUserStopped(anyInt());
             doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt());
             doNothing().when(mInjector).stackSupervisorRemoveUser(anyInt());
+            // All UserController params are set to default.
             mUserController = new UserController(mInjector);
             setUpUser(TEST_USER_ID, NO_USERINFO_FLAGS);
             setUpUser(TEST_PRE_CREATED_USER_ID, NO_USERINFO_FLAGS, /* preCreated=*/ true);
@@ -187,7 +193,9 @@
 
     @Test
     public void testStartUserUIDisabled() {
-        mUserController.mUserSwitchUiEnabled = false;
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+
         mUserController.startUser(TEST_USER_ID, true /* foreground */);
         verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
         verify(mInjector.getWindowManager(), never()).stopFreezingScreen();
@@ -245,7 +253,9 @@
 
     @Test
     public void testFailedStartUserInForeground() {
-        mUserController.mUserSwitchUiEnabled = false;
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+
         mUserController.startUserInForeground(NONEXIST_USER_ID);
         verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean());
         verify(mInjector.getWindowManager()).setSwitchingUser(false);
@@ -326,7 +336,9 @@
 
     @Test
     public void testContinueUserSwitchUIDisabled() throws RemoteException {
-        mUserController.mUserSwitchUiEnabled = false;
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+
         // Start user -- this will update state of mUserController
         mUserController.startUser(TEST_USER_ID, true);
         Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
@@ -389,11 +401,13 @@
      * Test stopping of user from max running users limit.
      */
     @Test
-    public void testUserStoppingForMultipleUsersNormalMode()
+    public void testUserLockingFromUserSwitchingForMultipleUsersNonDelayedLocking()
             throws InterruptedException, RemoteException {
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+
         setUpUser(TEST_USER_ID1, 0);
         setUpUser(TEST_USER_ID2, 0);
-        mUserController.mMaxRunningUsers = 3;
         int numerOfUserSwitches = 1;
         addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
                 numerOfUserSwitches, false);
@@ -430,12 +444,13 @@
      * all middle steps which takes too much work to mock.
      */
     @Test
-    public void testUserStoppingForMultipleUsersDelayedLockingMode()
+    public void testUserLockingFromUserSwitchingForMultipleUsersDelayedLockingMode()
             throws InterruptedException, RemoteException {
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true);
+
         setUpUser(TEST_USER_ID1, 0);
         setUpUser(TEST_USER_ID2, 0);
-        mUserController.mMaxRunningUsers = 3;
-        mUserController.mDelayUserDataLocking = true;
         int numerOfUserSwitches = 1;
         addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
                 numerOfUserSwitches, false);
@@ -456,7 +471,7 @@
         // Skip all other steps and test unlock delaying only
         UserState uss = mUserStates.get(TEST_USER_ID);
         uss.setState(UserState.STATE_SHUTDOWN); // necessary state change from skipped part
-        mUserController.finishUserStopped(uss);
+        mUserController.finishUserStopped(uss, /* allowDelayedLocking= */ true);
         // Cannot mock FgThread handler, so confirm that there is no posted message left before
         // checking.
         waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS);
@@ -473,12 +488,87 @@
                 mUserController.getRunningUsersLU());
         UserState ussUser1 = mUserStates.get(TEST_USER_ID1);
         ussUser1.setState(UserState.STATE_SHUTDOWN);
-        mUserController.finishUserStopped(ussUser1);
+        mUserController.finishUserStopped(ussUser1, /* allowDelayedLocking= */ true);
         waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS);
         verify(mInjector.mStorageManagerMock, times(1))
                 .lockUserKey(TEST_USER_ID);
     }
 
+    /**
+     * Test locking user with mDelayUserDataLocking false.
+     */
+    @Test
+    public void testUserLockingWithStopUserForNonDelayedLockingMode() throws Exception {
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+
+        setUpAndStartUserInBackground(TEST_USER_ID);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* delayedLocking= */ true,
+                /* keyEvictedCallback= */ null, /* expectLocking= */ true);
+
+        setUpAndStartUserInBackground(TEST_USER_ID1);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+                /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
+
+        setUpAndStartUserInBackground(TEST_USER_ID2);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ false,
+                /* keyEvictedCallback= */ null, /* expectLocking= */ true);
+
+        setUpAndStartUserInBackground(TEST_USER_ID3);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID3, /* delayedLocking= */ false,
+                /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
+    }
+
+    /**
+     * Test conditional delayed locking with mDelayUserDataLocking true.
+     */
+    @Test
+    public void testUserLockingForDelayedLockingMode() throws Exception {
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true);
+
+        // delayedLocking set and no KeyEvictedCallback, so it should not lock.
+        setUpAndStartUserInBackground(TEST_USER_ID);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* delayedLocking= */ true,
+                /* keyEvictedCallback= */ null, /* expectLocking= */ false);
+
+        setUpAndStartUserInBackground(TEST_USER_ID1);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+                /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
+
+        setUpAndStartUserInBackground(TEST_USER_ID2);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ false,
+                /* keyEvictedCallback= */ null, /* expectLocking= */ true);
+
+        setUpAndStartUserInBackground(TEST_USER_ID3);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID3, /* delayedLocking= */ false,
+                /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
+    }
+
+    private void setUpAndStartUserInBackground(int userId) throws Exception {
+        setUpUser(userId, 0);
+        mUserController.startUser(userId, /* foreground= */ false);
+        verify(mInjector.mStorageManagerMock, times(1))
+                .unlockUserKey(TEST_USER_ID, 0, null, null);
+        mUserStates.put(userId, mUserController.getStartedUserState(userId));
+    }
+
+    private void assertUserLockedOrUnlockedAfterStopping(int userId, boolean delayedLocking,
+            KeyEvictedCallback keyEvictedCallback, boolean expectLocking)  throws Exception {
+        int r = mUserController.stopUser(userId, /* force= */ true, /* delayedLocking= */
+                delayedLocking, null, keyEvictedCallback);
+        assertThat(r).isEqualTo(ActivityManager.USER_OP_SUCCESS);
+        // fake all interim steps
+        UserState ussUser = mUserStates.get(userId);
+        ussUser.setState(UserState.STATE_SHUTDOWN);
+        // Passing delayedLocking invalidates incorrect internal data passing but currently there is
+        // no easy way to get that information passed through lambda.
+        mUserController.finishUserStopped(ussUser, delayedLocking);
+        waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS);
+        verify(mInjector.mStorageManagerMock, times(expectLocking ? 1 : 0))
+                .lockUserKey(userId);
+    }
+
     private void addForegroundUserAndContinueUserSwitch(int newUserId, int expectedOldUserId,
             int expectedNumberOfCalls, boolean expectOldUserStopping)
             throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/impl/AppSearchImplTest.java b/services/tests/servicestests/src/com/android/server/appsearch/impl/AppSearchImplTest.java
new file mode 100644
index 0000000..34ade81
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appsearch/impl/AppSearchImplTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.appsearch.impl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.os.UserHandle;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.android.icing.proto.IndexingConfig;
+import com.google.android.icing.proto.PropertyConfigProto;
+import com.google.android.icing.proto.SchemaProto;
+import com.google.android.icing.proto.SchemaTypeConfigProto;
+import com.google.android.icing.proto.TermMatchType;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class AppSearchImplTest {
+    private final Context mContext = InstrumentationRegistry.getContext();
+    private final @UserIdInt int mUserId = UserHandle.getCallingUserId();
+
+    @Test
+    public void testRewriteSchemaTypes() {
+        SchemaProto inSchema = SchemaProto.newBuilder()
+                .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();
+
+        SchemaProto expectedSchema = SchemaProto.newBuilder()
+                .addTypes(SchemaTypeConfigProto.newBuilder()
+                        .setSchemaType("com.android.server.appsearch.impl@42: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("com.android.server.appsearch.impl@42:RefType")
+                                .build()
+                        ).build()
+                ).build();
+
+        AppSearchImpl impl = new AppSearchImpl(mContext, mUserId);
+        SchemaProto.Builder actualSchema = inSchema.toBuilder();
+        impl.rewriteSchemaTypes("com.android.server.appsearch.impl@42:", actualSchema);
+
+        assertThat(actualSchema.build()).isEqualTo(expectedSchema);
+    }
+
+    @Test
+    public void testPackageNotFound() {
+        AppSearchImpl impl = new AppSearchImpl(mContext, mUserId);
+        IllegalStateException e = expectThrows(
+                IllegalStateException.class,
+                () -> impl.setSchema(
+                        /*callingUid=*/Integer.MAX_VALUE,
+                        SchemaProto.getDefaultInstance(),
+                        /*forceOverride=*/false));
+        assertThat(e).hasMessageThat().contains("Failed to look up package name");
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/impl/FakeIcingTest.java b/services/tests/servicestests/src/com/android/server/appsearch/impl/FakeIcingTest.java
index 6f2de7f..07b6556 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/impl/FakeIcingTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/impl/FakeIcingTest.java
@@ -22,6 +22,7 @@
 import com.google.android.icing.proto.DocumentProto;
 import com.google.android.icing.proto.PropertyProto;
 import com.google.android.icing.proto.SearchResultProto;
+import com.google.android.icing.proto.StatusProto;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -117,6 +118,7 @@
     private static List<String> queryGetUris(FakeIcing icing, String term) {
         List<String> uris = new ArrayList<>();
         SearchResultProto results = icing.query(term);
+        assertThat(results.getStatus().getCode()).isEqualTo(StatusProto.Code.OK);
         for (SearchResultProto.ResultProto result : results.getResultsList()) {
             uris.add(result.getDocument().getUri());
         }
diff --git a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
index 2326dfd..3de006c 100644
--- a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
@@ -56,9 +57,9 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -83,6 +84,8 @@
     @Mock
     private UserBackupManagerService mUserBackupManagerService;
     @Mock
+    private UserBackupManagerService mNonSystemUserBackupManagerService;
+    @Mock
     private Context mContextMock;
     @Mock
     private PrintWriter mPrintWriterMock;
@@ -105,7 +108,7 @@
 
         mUserServices = new SparseArray<>();
         mUserServices.append(UserHandle.USER_SYSTEM, mUserBackupManagerService);
-        mUserServices.append(NON_USER_SYSTEM, mUserBackupManagerService);
+        mUserServices.append(NON_USER_SYSTEM, mNonSystemUserBackupManagerService);
 
         when(mUserManagerMock.getUserInfo(UserHandle.USER_SYSTEM)).thenReturn(mUserInfoMock);
         when(mUserManagerMock.getUserInfo(NON_USER_SYSTEM)).thenReturn(mUserInfoMock);
@@ -512,19 +515,53 @@
         mService.dump(mFileDescriptorStub, mPrintWriterMock, new String[0]);
 
         verifyNoMoreInteractions(mUserBackupManagerService);
+        verifyNoMoreInteractions(mNonSystemUserBackupManagerService);
+    }
+
+    /**
+     * Test that {@link BackupManagerService#dump()} dumps system user information before non-system
+     * user information.
+     */
+    @Test
+    public void testDump_systemUserFirst() {
+        String[] args = new String[0];
+        mService.dumpWithoutCheckingPermission(mFileDescriptorStub, mPrintWriterMock, args);
+
+        InOrder inOrder =
+                inOrder(mUserBackupManagerService, mNonSystemUserBackupManagerService);
+        inOrder.verify(mUserBackupManagerService)
+                .dump(mFileDescriptorStub, mPrintWriterMock, args);
+        inOrder.verify(mNonSystemUserBackupManagerService)
+                .dump(mFileDescriptorStub, mPrintWriterMock, args);
+        inOrder.verifyNoMoreInteractions();
     }
 
     @Test
-    @Ignore("b/147012496")
-    public void testGetUserForAncestralSerialNumber() {
+    public void testGetUserForAncestralSerialNumber_forSystemUser() {
         BackupManagerServiceTestable.sBackupDisabled = false;
         BackupManagerService backupManagerService =
                 new BackupManagerServiceTestable(mContextMock, mUserServices);
+        when(mUserManagerMock.getProfileIds(UserHandle.getCallingUserId(), false))
+                .thenReturn(new int[] {UserHandle.USER_SYSTEM, NON_USER_SYSTEM});
         when(mUserBackupManagerService.getAncestralSerialNumber()).thenReturn(11L);
 
         UserHandle user = backupManagerService.getUserForAncestralSerialNumber(11L);
 
-        assertThat(user).isEqualTo(UserHandle.of(1));
+        assertThat(user).isEqualTo(UserHandle.of(UserHandle.USER_SYSTEM));
+    }
+
+    @Test
+    public void testGetUserForAncestralSerialNumber_forNonSystemUser() {
+        BackupManagerServiceTestable.sBackupDisabled = false;
+        BackupManagerService backupManagerService =
+                new BackupManagerServiceTestable(mContextMock, mUserServices);
+        when(mUserManagerMock.getProfileIds(UserHandle.getCallingUserId(), false))
+                .thenReturn(new int[] {UserHandle.USER_SYSTEM, NON_USER_SYSTEM});
+        when(mNonSystemUserBackupManagerService.getAncestralSerialNumber()).thenReturn(11L);
+
+        UserHandle user = backupManagerService.getUserForAncestralSerialNumber(11L);
+
+        assertThat(user).isEqualTo(UserHandle.of(NON_USER_SYSTEM));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index f96d996..bec265e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -411,7 +411,8 @@
         // HAT sent to keystore
         verify(mBiometricService.mKeyStore).addAuthToken(any(byte[].class));
         // Send onAuthenticated to client
-        verify(mReceiver1).onAuthenticationSucceeded();
+        verify(mReceiver1).onAuthenticationSucceeded(
+                BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC);
         // Current session becomes null
         assertNull(mBiometricService.mCurrentAuthSession);
     }
@@ -461,7 +462,8 @@
                 BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED);
         waitForIdle();
         verify(mBiometricService.mKeyStore).addAuthToken(any(byte[].class));
-        verify(mReceiver1).onAuthenticationSucceeded();
+        verify(mReceiver1).onAuthenticationSucceeded(
+                BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
index abe39f0..312ff2c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
@@ -223,4 +223,22 @@
                     Utils.biometricConstantsToBiometricManager(testCases[i][0]));
         }
     }
+
+    @Test
+    public void testGetAuthenticationTypeForResult_getsCorrectType() {
+        assertEquals(Utils.getAuthenticationTypeForResult(
+                BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED),
+                BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL);
+        assertEquals(Utils.getAuthenticationTypeForResult(
+                BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED),
+                BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC);
+        assertEquals(Utils.getAuthenticationTypeForResult(
+                BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED),
+                BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetAuthResultType_throwsForInvalidReason() {
+        Utils.getAuthenticationTypeForResult(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java b/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java
index ecd07bd..b14291b 100644
--- a/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java
@@ -188,7 +188,7 @@
     }
 
     @Test
-    public void getOverrideAllowedState_betaBuildEnabledChangeDebugApp_rejectOverride()
+    public void getOverrideAllowedState_betaBuildEnabledChangeDebugApp_allowOverride()
             throws Exception {
         CompatConfig config = CompatConfigBuilder.create(betaBuild(), mContext)
                         .addEnabledChangeWithId(1).build();
@@ -203,11 +203,11 @@
                 overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
 
         assertThat(allowedState)
-                .isEqualTo(new OverrideAllowedState(DISABLED_NON_TARGET_SDK, -1, -1));
+                .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
     }
 
     @Test
-    public void getOverrideAllowedState_betaBuildDisabledChangeDebugApp_rejectOverride()
+    public void getOverrideAllowedState_betaBuildDisabledChangeDebugApp_allowOverride()
             throws Exception {
         CompatConfig config = CompatConfigBuilder.create(betaBuild(), mContext)
                         .addDisabledChangeWithId(1).build();
@@ -221,7 +221,7 @@
                 overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
 
         assertThat(allowedState)
-                .isEqualTo(new OverrideAllowedState(DISABLED_NON_TARGET_SDK, -1, -1));
+                .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
index 5f1f308..9574a08 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
@@ -15,6 +15,10 @@
  */
 package com.android.server.devicepolicy;
 
+import static android.os.UserHandle.USER_SYSTEM;
+
+import static com.android.server.devicepolicy.DpmTestUtils.writeInputStreamToFile;
+
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.eq;
@@ -22,12 +26,14 @@
 import static org.mockito.Mockito.when;
 
 import android.app.admin.DevicePolicyManagerInternal;
+import android.content.ComponentName;
 import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
 
+import com.android.frameworks.servicestests.R;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.devicepolicy.DevicePolicyManagerServiceTestable.OwnersTestable;
@@ -37,9 +43,13 @@
 import java.util.Map;
 import java.util.Set;
 
+// TODO (b/143516163): Fix old test cases and put into presubmit.
 public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase {
 
     private static final String USER_TYPE_EMPTY = "";
+    private static final int COPE_ADMIN1_APP_ID = 123;
+    private static final int COPE_ANOTHER_ADMIN_APP_ID = 125;
+    private static final int COPE_PROFILE_USER_ID = 11;
 
     private DpmMockContext mContext;
 
@@ -85,7 +95,7 @@
 
         // Set up UserManager
         when(getServices().userManagerInternal.getBaseUserRestrictions(
-                eq(UserHandle.USER_SYSTEM))).thenReturn(DpmTestUtils.newRestrictions(
+                eq(USER_SYSTEM))).thenReturn(DpmTestUtils.newRestrictions(
                 UserManager.DISALLOW_ADD_USER,
                 UserManager.DISALLOW_RECORD_AUDIO));
 
@@ -137,7 +147,7 @@
         }
 
         assertTrue(dpms.mOwners.hasDeviceOwner());
-        assertFalse(dpms.mOwners.hasProfileOwner(UserHandle.USER_SYSTEM));
+        assertFalse(dpms.mOwners.hasProfileOwner(USER_SYSTEM));
         assertTrue(dpms.mOwners.hasProfileOwner(10));
         assertTrue(dpms.mOwners.hasProfileOwner(11));
         assertFalse(dpms.mOwners.hasProfileOwner(12));
@@ -145,7 +155,7 @@
         // Now all information should be migrated.
         assertFalse(dpms.mOwners.getDeviceOwnerUserRestrictionsNeedsMigration());
         assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration(
-                UserHandle.USER_SYSTEM));
+                USER_SYSTEM));
         assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration(10));
         assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration(11));
         assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration(12));
@@ -155,7 +165,7 @@
                 DpmTestUtils.newRestrictions(
                         UserManager.DISALLOW_RECORD_AUDIO
                 ),
-                newBaseRestrictions.get(UserHandle.USER_SYSTEM));
+                newBaseRestrictions.get(USER_SYSTEM));
 
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(
@@ -214,7 +224,7 @@
 
         // Set up UserManager
         when(getServices().userManagerInternal.getBaseUserRestrictions(
-                eq(UserHandle.USER_SYSTEM))).thenReturn(DpmTestUtils.newRestrictions(
+                eq(USER_SYSTEM))).thenReturn(DpmTestUtils.newRestrictions(
                 UserManager.DISALLOW_ADD_USER,
                 UserManager.DISALLOW_RECORD_AUDIO,
                 UserManager.DISALLOW_SMS,
@@ -249,19 +259,19 @@
             mContext.binder.restoreCallingIdentity(ident);
         }
         assertFalse(dpms.mOwners.hasDeviceOwner());
-        assertTrue(dpms.mOwners.hasProfileOwner(UserHandle.USER_SYSTEM));
+        assertTrue(dpms.mOwners.hasProfileOwner(USER_SYSTEM));
 
         // Now all information should be migrated.
         assertFalse(dpms.mOwners.getDeviceOwnerUserRestrictionsNeedsMigration());
         assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration(
-                UserHandle.USER_SYSTEM));
+                USER_SYSTEM));
 
         // Check the new base restrictions.
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(
                         UserManager.DISALLOW_RECORD_AUDIO
                 ),
-                newBaseRestrictions.get(UserHandle.USER_SYSTEM));
+                newBaseRestrictions.get(USER_SYSTEM));
 
         // Check the new owner restrictions.
         DpmTestUtils.assertRestrictions(
@@ -270,7 +280,7 @@
                         UserManager.DISALLOW_SMS,
                         UserManager.DISALLOW_OUTGOING_CALLS
                 ),
-                dpms.getProfileOwnerAdminLocked(UserHandle.USER_SYSTEM).ensureUserRestrictions());
+                dpms.getProfileOwnerAdminLocked(USER_SYSTEM).ensureUserRestrictions());
     }
 
     // Test setting default restrictions for managed profile.
@@ -332,4 +342,98 @@
         assertEquals(alreadySet.size(), 1);
         assertTrue(alreadySet.contains(UserManager.DISALLOW_BLUETOOTH_SHARING));
     }
+
+    public void testCompMigrationUnAffiliated_skipped() throws Exception {
+        prepareAdmin1AsDo();
+        prepareAdminAnotherPackageAsPo(COPE_PROFILE_USER_ID);
+
+        final DevicePolicyManagerServiceTestable dpms;
+        dpms = bootDpmsUp();
+
+        // DO should still be DO since no migration should happen.
+        assertTrue(dpms.mOwners.hasDeviceOwner());
+    }
+
+    public void testCompMigrationAffiliated() throws Exception {
+        prepareAdmin1AsDo();
+        prepareAdmin1AsPo(COPE_PROFILE_USER_ID);
+
+        // Secure lock screen is needed for password policy APIs to work.
+        when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(true);
+
+        final DevicePolicyManagerServiceTestable dpms;
+        dpms = bootDpmsUp();
+
+        // DO should cease to be DO.
+        assertFalse(dpms.mOwners.hasDeviceOwner());
+
+        final DpmMockContext poContext = new DpmMockContext(getServices(), mRealTestContext);
+        poContext.binder.callingUid = UserHandle.getUid(COPE_PROFILE_USER_ID, COPE_ADMIN1_APP_ID);
+
+        runAsCaller(poContext, dpms, dpm -> {
+            assertEquals("Password history policy wasn't migrated to PO parent instance",
+                    33, dpm.getParentProfileInstance(admin1).getPasswordHistoryLength(admin1));
+            assertEquals("Password history policy was put into non-parent PO instance",
+                    0, dpm.getPasswordHistoryLength(admin1));
+
+            assertTrue("User restriction wasn't migrated to PO parent instance",
+                    dpm.getParentProfileInstance(admin1).getUserRestrictions(admin1)
+                            .containsKey(UserManager.DISALLOW_BLUETOOTH));
+            assertFalse("User restriction was put into non-parent PO instance",
+                    dpm.getUserRestrictions(admin1).containsKey(UserManager.DISALLOW_BLUETOOTH));
+
+            // TODO(b/143516163): verify more policies.
+        });
+    }
+
+    private DevicePolicyManagerServiceTestable bootDpmsUp() {
+        DevicePolicyManagerServiceTestable dpms;
+        final long ident = mContext.binder.clearCallingIdentity();
+        try {
+            LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+
+            dpms = new DevicePolicyManagerServiceTestable(getServices(), mContext);
+
+            dpms.systemReady(SystemService.PHASE_LOCK_SETTINGS_READY);
+            dpms.systemReady(SystemService.PHASE_ACTIVITY_MANAGER_READY);
+            dpms.systemReady(SystemService.PHASE_BOOT_COMPLETED);
+        } finally {
+            mContext.binder.restoreCallingIdentity(ident);
+        }
+        return dpms;
+    }
+
+    private void prepareAdmin1AsDo() throws Exception {
+        setUpPackageManagerForAdmin(admin1, UserHandle.getUid(USER_SYSTEM, COPE_ADMIN1_APP_ID));
+        final int xmlResource = R.raw.comp_policies_primary;
+        writeInputStreamToFile(getRawStream(xmlResource),
+                (new File(getServices().systemUserDataDir, "device_policies.xml"))
+                        .getAbsoluteFile());
+        writeInputStreamToFile(getRawStream(R.raw.comp_device_owner),
+                (new File(getServices().dataDir, "device_owner_2.xml"))
+                        .getAbsoluteFile());
+    }
+
+    private void prepareAdmin1AsPo(int profileUserId) throws Exception {
+        preparePo(profileUserId, admin1, R.raw.comp_profile_owner_same_package,
+                R.raw.comp_policies_profile_same_package, COPE_ADMIN1_APP_ID);
+    }
+
+    private void prepareAdminAnotherPackageAsPo(int profileUserId) throws Exception {
+        preparePo(profileUserId, adminAnotherPackage, R.raw.comp_profile_owner_another_package,
+                R.raw.comp_policies_profile_another_package, COPE_ANOTHER_ADMIN_APP_ID);
+    }
+
+    private void preparePo(int profileUserId, ComponentName admin, int profileOwnerXmlResId,
+            int policyXmlResId, int adminAppId) throws Exception {
+        final File profileDir = getServices().addUser(profileUserId, 0,
+                UserManager.USER_TYPE_PROFILE_MANAGED, USER_SYSTEM /* profile group */);
+        setUpPackageManagerForFakeAdmin(
+                admin, UserHandle.getUid(profileUserId, adminAppId), admin1);
+        writeInputStreamToFile(getRawStream(policyXmlResId),
+                (new File(profileDir, "device_policies.xml")).getAbsoluteFile());
+        writeInputStreamToFile(getRawStream(profileOwnerXmlResId),
+                (new File(profileDir, "profile_owner.xml")).getAbsoluteFile());
+    }
+
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index ac555fd..3a8258b 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -51,6 +51,7 @@
 import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockSettingsInternal;
+import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.net.NetworkPolicyManagerInternal;
 
 import java.io.File;
@@ -223,6 +224,11 @@
         }
 
         @Override
+        PersistentDataBlockManagerInternal getPersistentDataBlockManagerInternal() {
+            return services.persistentDataBlockManagerInternal;
+        }
+
+        @Override
         Looper getMyLooper() {
             return Looper.getMainLooper();
         }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 175c756..def5b61 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -31,7 +31,10 @@
 import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
 import static com.android.server.testutils.TestUtils.assertExpectException;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.anyObject;
@@ -55,13 +58,13 @@
 import static org.testng.Assert.assertThrows;
 
 import android.Manifest.permission;
-import android.annotation.RawRes;
 import android.app.Activity;
 import android.app.AppOpsManager;
 import android.app.Notification;
 import android.app.admin.DeviceAdminReceiver;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManagerInternal;
+import android.app.admin.FactoryResetProtectionPolicy;
 import android.app.admin.PasswordMetrics;
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
@@ -108,7 +111,6 @@
 import org.mockito.stubbing.Answer;
 
 import java.io.File;
-import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -272,6 +274,29 @@
         }).when(getServices().userManager).getApplicationRestrictions(
                 anyString(), any(UserHandle.class));
 
+        // Emulate UserManager.setUserRestriction/getUserRestrictions
+        final Map<UserHandle, Bundle> userRestrictions = new HashMap<>();
+
+        doAnswer((Answer<Void>) invocation -> {
+            String key = (String) invocation.getArguments()[0];
+            boolean value = (Boolean) invocation.getArguments()[1];
+            UserHandle user = (UserHandle) invocation.getArguments()[2];
+            Bundle userBundle = userRestrictions.getOrDefault(user, new Bundle());
+            userBundle.putBoolean(key, value);
+
+            userRestrictions.put(user, userBundle);
+            return null;
+        }).when(getServices().userManager).setUserRestriction(
+                anyString(), anyBoolean(), any(UserHandle.class));
+
+        doAnswer((Answer<Boolean>) invocation -> {
+            String key = (String) invocation.getArguments()[0];
+            UserHandle user = (UserHandle) invocation.getArguments()[1];
+            Bundle userBundle = userRestrictions.getOrDefault(user, new Bundle());
+            return userBundle.getBoolean(key);
+        }).when(getServices().userManager).hasUserRestriction(
+                anyString(), any(UserHandle.class));
+
         // Add the first secondary user.
         getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, 0,
                 UserManager.USER_TYPE_FULL_SECONDARY);
@@ -819,10 +844,8 @@
         final int MANAGED_PROFILE_ADMIN_UID =
                 UserHandle.getUid(MANAGED_PROFILE_USER_ID, DpmMockContext.SYSTEM_UID);
 
-        // Setup device owner.
         mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
         mContext.packageName = admin1.getPackageName();
-        setupDeviceOwner();
 
         // Add a managed profile belonging to the system user.
         addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
@@ -830,18 +853,13 @@
         // Change the parent user's password.
         dpm.reportPasswordChanged(UserHandle.USER_SYSTEM);
 
-        // Both the device owner and the managed profile owner should receive this broadcast.
+        // The managed profile owner should receive this broadcast.
         final Intent intent = new Intent(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED);
         intent.setComponent(admin1);
         intent.putExtra(Intent.EXTRA_USER, UserHandle.of(UserHandle.USER_SYSTEM));
 
         verify(mContext.spiedContext, times(1)).sendBroadcastAsUser(
                 MockUtils.checkIntent(intent),
-                MockUtils.checkUserHandle(UserHandle.USER_SYSTEM),
-                eq(null),
-                any(Bundle.class));
-        verify(mContext.spiedContext, times(1)).sendBroadcastAsUser(
-                MockUtils.checkIntent(intent),
                 MockUtils.checkUserHandle(MANAGED_PROFILE_USER_ID),
                 eq(null),
                 any(Bundle.class));
@@ -861,12 +879,11 @@
         final int MANAGED_PROFILE_ADMIN_UID =
                 UserHandle.getUid(MANAGED_PROFILE_USER_ID, DpmMockContext.SYSTEM_UID);
 
-        // Setup device owner.
+        // Configure system as having separate profile challenge.
         mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
         mContext.packageName = admin1.getPackageName();
         doReturn(true).when(getServices().lockPatternUtils)
                 .isSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID);
-        setupDeviceOwner();
 
         // Add a managed profile belonging to the system user.
         addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
@@ -951,6 +968,10 @@
         verify(getServices().iactivityManager, times(1)).updateDeviceOwner(
                 eq(admin1.getPackageName()));
 
+        verify(getServices().userManager, times(1)).setUserRestriction(
+                eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
+                eq(true), eq(UserHandle.SYSTEM));
+
         verify(mContext.spiedContext, times(1)).sendBroadcastAsUser(
                 MockUtils.checkIntentAction(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED),
                 MockUtils.checkUserHandle(UserHandle.USER_SYSTEM));
@@ -1945,6 +1966,29 @@
         // TODO Make sure restrictions are written to the file.
     }
 
+    private static final Set<String> PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS =
+            Sets.newSet(
+                    UserManager.DISALLOW_CONFIG_DATE_TIME,
+                    UserManager.DISALLOW_ADD_USER,
+                    UserManager.DISALLOW_BLUETOOTH,
+                    UserManager.DISALLOW_BLUETOOTH_SHARING,
+                    UserManager.DISALLOW_CONFIG_BLUETOOTH,
+                    UserManager.DISALLOW_CONFIG_CELL_BROADCASTS,
+                    UserManager.DISALLOW_CONFIG_LOCATION,
+                    UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,
+                    UserManager.DISALLOW_CONFIG_PRIVATE_DNS,
+                    UserManager.DISALLOW_CONFIG_TETHERING,
+                    UserManager.DISALLOW_CONFIG_WIFI,
+                    UserManager.DISALLOW_CONTENT_CAPTURE,
+                    UserManager.DISALLOW_CONTENT_SUGGESTIONS,
+                    UserManager.DISALLOW_DATA_ROAMING,
+                    UserManager.DISALLOW_DEBUGGING_FEATURES,
+                    UserManager.DISALLOW_SAFE_BOOT,
+                    UserManager.DISALLOW_SHARE_LOCATION,
+                    UserManager.DISALLOW_SMS,
+                    UserManager.DISALLOW_USB_FILE_TRANSFER
+            );
+
     public void testSetUserRestriction_asPoOfOrgOwnedDevice() throws Exception {
         final int MANAGED_PROFILE_USER_ID = DpmMockContext.CALLER_USER_HANDLE;
         final int MANAGED_PROFILE_ADMIN_UID =
@@ -1957,15 +2001,9 @@
         when(getServices().userManager.getProfileParent(MANAGED_PROFILE_USER_ID))
                 .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
 
-        parentDpm.addUserRestriction(admin1, UserManager.DISALLOW_CONFIG_DATE_TIME);
-        verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
-                eq(MANAGED_PROFILE_USER_ID),
-                MockUtils.checkUserRestrictions(UserManager.DISALLOW_CONFIG_DATE_TIME),
-                eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE));
-        reset(getServices().userManagerInternal);
-
-        parentDpm.clearUserRestriction(admin1, UserManager.DISALLOW_CONFIG_DATE_TIME);
-        reset(getServices().userManagerInternal);
+        for (String restriction : PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS) {
+            addAndRemoveUserRestrictionOnParentDpm(restriction);
+        }
 
         parentDpm.setCameraDisabled(admin1, true);
         verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
@@ -1982,6 +2020,20 @@
         reset(getServices().userManagerInternal);
     }
 
+    private void addAndRemoveUserRestrictionOnParentDpm(String restriction) {
+        parentDpm.addUserRestriction(admin1, restriction);
+        verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(DpmMockContext.CALLER_USER_HANDLE),
+                MockUtils.checkUserRestrictions(restriction),
+                eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE));
+        parentDpm.clearUserRestriction(admin1, restriction);
+        DpmTestUtils.assertRestrictions(
+                DpmTestUtils.newRestrictions(),
+                parentDpm.getUserRestrictions(admin1)
+        );
+        reset(getServices().userManagerInternal);
+    }
+
     public void testNoDefaultEnabledUserRestrictions() throws Exception {
         mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
         mContext.callerPermissions.add(permission.MANAGE_USERS);
@@ -2002,12 +2054,11 @@
 
         assertNoDeviceOwnerRestrictions();
 
-        // Initialize DPMS again and check that the user restriction wasn't enabled again.
         reset(getServices().userManagerInternal);
-        initializeDpms();
-        assertTrue(dpm.isDeviceOwnerApp(admin1.getPackageName()));
-        assertNotNull(dpms.getDeviceOwnerAdminLocked());
 
+        // Ensure the DISALLOW_REMOVE_MANAGED_PROFILES restriction doesn't show up as a
+        // restriction to the device owner.
+        dpm.addUserRestriction(admin1, UserManager.DISALLOW_REMOVE_MANAGED_PROFILE);
         assertNoDeviceOwnerRestrictions();
     }
 
@@ -2022,6 +2073,173 @@
         );
     }
 
+    public void testSetFactoryResetProtectionPolicyWithDO() throws Exception {
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+        setupDeviceOwner();
+
+        when(getServices().persistentDataBlockManagerInternal.getAllowedUid()).thenReturn(
+                DpmMockContext.CALLER_UID);
+
+        FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+                .setFactoryResetProtectionAccounts(new ArrayList<>())
+                .setFactoryResetProtectionDisabled(true)
+                .build();
+        dpm.setFactoryResetProtectionPolicy(admin1, policy);
+
+        FactoryResetProtectionPolicy result = dpm.getFactoryResetProtectionPolicy(admin1);
+        assertThat(result).isEqualTo(policy);
+        assertPoliciesAreEqual(policy, result);
+
+        verify(mContext.spiedContext).sendBroadcastAsUser(
+                MockUtils.checkIntentAction(
+                        DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED),
+                MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+    }
+
+    public void testSetFactoryResetProtectionPolicyFailWithPO() throws Exception {
+        setupProfileOwner();
+
+        FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+                .setFactoryResetProtectionDisabled(true)
+                .build();
+
+        assertExpectException(SecurityException.class, null,
+                () -> dpm.setFactoryResetProtectionPolicy(admin1, policy));
+    }
+
+    public void testSetFactoryResetProtectionPolicyWithPOOfOrganizationOwnedDevice()
+            throws Exception {
+        setupProfileOwner();
+        configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE);
+
+        when(getServices().persistentDataBlockManagerInternal.getAllowedUid()).thenReturn(
+                DpmMockContext.CALLER_UID);
+
+        List<String> accounts = new ArrayList<>();
+        accounts.add("Account 1");
+        accounts.add("Account 2");
+
+        FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+                .setFactoryResetProtectionAccounts(accounts)
+                .build();
+
+        dpm.setFactoryResetProtectionPolicy(admin1, policy);
+
+        FactoryResetProtectionPolicy result = dpm.getFactoryResetProtectionPolicy(admin1);
+        assertThat(result).isEqualTo(policy);
+        assertPoliciesAreEqual(policy, result);
+
+        verify(mContext.spiedContext, times(2)).sendBroadcastAsUser(
+                MockUtils.checkIntentAction(
+                        DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
+                MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+        verify(mContext.spiedContext).sendBroadcastAsUser(
+                MockUtils.checkIntentAction(
+                        DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED),
+                MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+        verify(mContext.spiedContext).sendBroadcastAsUser(
+                MockUtils.checkIntentAction(
+                        DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED),
+                MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+    }
+
+    public void testGetFactoryResetProtectionPolicyWithFrpManagementAgent()
+            throws Exception {
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+        setupDeviceOwner();
+        when(getServices().persistentDataBlockManagerInternal.getAllowedUid()).thenReturn(
+                DpmMockContext.CALLER_UID);
+
+        FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+                .setFactoryResetProtectionAccounts(new ArrayList<>())
+                .setFactoryResetProtectionDisabled(true)
+                .build();
+
+        dpm.setFactoryResetProtectionPolicy(admin1, policy);
+
+        mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
+        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+        dpm.setActiveAdmin(admin1, /*replace=*/ false);
+        FactoryResetProtectionPolicy result = dpm.getFactoryResetProtectionPolicy(null);
+        assertThat(result).isEqualTo(policy);
+        assertPoliciesAreEqual(policy, result);
+
+        verify(mContext.spiedContext).sendBroadcastAsUser(
+                MockUtils.checkIntentAction(
+                        DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED),
+                MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+    }
+
+    private void assertPoliciesAreEqual(FactoryResetProtectionPolicy expectedPolicy,
+            FactoryResetProtectionPolicy actualPolicy) {
+        assertThat(actualPolicy.isFactoryResetProtectionDisabled()).isEqualTo(
+                expectedPolicy.isFactoryResetProtectionDisabled());
+        assertAccountsAreEqual(expectedPolicy.getFactoryResetProtectionAccounts(),
+                actualPolicy.getFactoryResetProtectionAccounts());
+    }
+
+    private void assertAccountsAreEqual(List<String> expectedAccounts,
+            List<String> actualAccounts) {
+        assertThat(actualAccounts).containsExactlyElementsIn(expectedAccounts);
+    }
+
+    public void testSetApplicationHiddenWithDO() throws Exception {
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+        setupDeviceOwner();
+        mContext.packageName = admin1.getPackageName();
+        setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
+
+        String packageName = "com.google.android.test";
+
+        dpm.setApplicationHidden(admin1, packageName, true);
+        verify(getServices().ipackageManager).setApplicationHiddenSettingAsUser(packageName,
+                true, UserHandle.USER_SYSTEM);
+
+        dpm.setApplicationHidden(admin1, packageName, false);
+        verify(getServices().ipackageManager).setApplicationHiddenSettingAsUser(packageName,
+                false, UserHandle.USER_SYSTEM);
+
+        verify(getServices().ipackageManager, never()).getPackageInfo(packageName,
+                PackageManager.MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM);
+        verify(getServices().ipackageManager, never()).getPackageInfo(packageName,
+                PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.MATCH_SYSTEM_ONLY,
+                UserHandle.USER_SYSTEM);
+    }
+
+    public void testSetApplicationHiddenWithPOOfOrganizationOwnedDevice() throws Exception {
+        final int MANAGED_PROFILE_USER_ID = DpmMockContext.CALLER_USER_HANDLE;
+        final int MANAGED_PROFILE_ADMIN_UID =
+                UserHandle.getUid(MANAGED_PROFILE_USER_ID, DpmMockContext.SYSTEM_UID);
+        mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+
+        addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+        configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE);
+        mContext.packageName = admin1.getPackageName();
+        setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
+
+        String packageName = "com.google.android.test";
+
+        PackageInfo packageInfo = new PackageInfo();
+        packageInfo.applicationInfo = new ApplicationInfo();
+        packageInfo.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
+        when(getServices().userManager.getProfileParent(MANAGED_PROFILE_USER_ID))
+                .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
+        when(getServices().ipackageManager.getPackageInfo(packageName,
+                PackageManager.MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM)).thenReturn(
+                packageInfo);
+        when(getServices().ipackageManager.getPackageInfo(packageName,
+                PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.MATCH_SYSTEM_ONLY,
+                UserHandle.USER_SYSTEM)).thenReturn(packageInfo);
+
+        parentDpm.setApplicationHidden(admin1, packageName, true);
+        verify(getServices().ipackageManager).setApplicationHiddenSettingAsUser(packageName,
+                true, UserHandle.USER_SYSTEM);
+
+        parentDpm.setApplicationHidden(admin1, packageName, false);
+        verify(getServices().ipackageManager).setApplicationHiddenSettingAsUser(packageName,
+                false, UserHandle.USER_SYSTEM);
+    }
+
     public void testGetMacAddress() throws Exception {
         mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
@@ -2845,6 +3063,7 @@
         mContext.packageName = admin1.getPackageName();
         setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, false);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, false);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
                 false);
@@ -2856,6 +3075,8 @@
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
                 DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED);
+        assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+                DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED);
         assertCheckProvisioningPreCondition(
@@ -2882,6 +3103,7 @@
         mContext.packageName = admin1.getPackageName();
         setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
                 false);
@@ -2890,6 +3112,7 @@
         // Test again when split user is on
         when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
                 true);
@@ -2901,6 +3124,8 @@
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
                 DevicePolicyManager.CODE_OK);
+        assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+                DevicePolicyManager.CODE_OK);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 DevicePolicyManager.CODE_MANAGED_USERS_NOT_SUPPORTED);
         assertCheckProvisioningPreCondition(
@@ -2913,6 +3138,8 @@
         when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
                 DevicePolicyManager.CODE_OK);
+        assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+                DevicePolicyManager.CODE_OK);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 DevicePolicyManager.CODE_MANAGED_USERS_NOT_SUPPORTED);
         assertCheckProvisioningPreCondition(
@@ -2938,6 +3165,7 @@
         mContext.packageName = admin1.getPackageName();
         setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
                 false /* because of non-split user */);
@@ -2951,6 +3179,8 @@
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
                 DevicePolicyManager.CODE_OK);
+        assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+                DevicePolicyManager.CODE_OK);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 DevicePolicyManager.CODE_OK);
         assertCheckProvisioningPreCondition(
@@ -2981,7 +3211,6 @@
         setup_nonSplitUser_withDo_primaryUser();
         final int MANAGED_PROFILE_USER_ID = 18;
         final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 1308);
-        addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
         when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM,
                 false /* we can't remove a managed profile */)).thenReturn(false);
         when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM,
@@ -2995,6 +3224,8 @@
         setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
                 false/* because of completed device setup */);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+                false/* because of completed device setup */);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
                 false/* because of non-split user */);
@@ -3008,6 +3239,8 @@
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
                 DevicePolicyManager.CODE_USER_SETUP_COMPLETED);
+        assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+                DevicePolicyManager.CODE_USER_SETUP_COMPLETED);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 DevicePolicyManager.CODE_OK);
         assertCheckProvisioningPreCondition(
@@ -3024,43 +3257,21 @@
 
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
                 DevicePolicyManager.CODE_HAS_DEVICE_OWNER);
+        assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+                DevicePolicyManager.CODE_HAS_DEVICE_OWNER);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, false);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, false);
 
-        // COMP mode is allowed.
+        // COMP mode NOT is allowed.
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
-                DevicePolicyManager.CODE_OK);
-        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
+                DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
 
-        // And other DPCs can also provision a managed profile (DO + BYOD case).
+        // And other DPCs can NOT provision a managed profile.
         assertCheckProvisioningPreCondition(
                 DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 DpmMockContext.ANOTHER_PACKAGE_NAME,
-                DevicePolicyManager.CODE_OK);
-        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true,
-                DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
-    }
-
-    public void testProvisioning_nonSplitUser_withDo_primaryUser_restrictedByDo() throws Exception {
-        setup_nonSplitUser_withDo_primaryUser();
-        mContext.packageName = admin1.getPackageName();
-        mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
-        // The DO should be allowed to initiate provisioning if it set the restriction itself, but
-        // other packages should be forbidden.
-        when(getServices().userManager.hasUserRestriction(
-                eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
-                eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
-                .thenReturn(true);
-        when(getServices().userManager.getUserRestrictionSource(
-                eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
-                eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
-                .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
-        assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
-                DevicePolicyManager.CODE_OK);
-        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
-        assertCheckProvisioningPreCondition(
-                DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
-                DpmMockContext.ANOTHER_PACKAGE_NAME,
-                DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED);
+                DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false,
                 DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
     }
@@ -3081,31 +3292,46 @@
                 eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
                 .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
-                DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED);
+                DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
 
         assertCheckProvisioningPreCondition(
                 DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 DpmMockContext.ANOTHER_PACKAGE_NAME,
-                DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED);
+                DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false,
                 DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
     }
 
-    public void testCheckProvisioningPreCondition_nonSplitUser_comp() throws Exception {
+    public void testCheckCannotSetProfileOwnerWithDeviceOwner() throws Exception {
+        setup_nonSplitUser_withDo_primaryUser();
+        final int managedProfileUserId = 18;
+        final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 1308);
+
+        final int userId = UserHandle.getUserId(managedProfileAdminUid);
+        getServices().addUser(userId, 0, UserManager.USER_TYPE_PROFILE_MANAGED,
+                UserHandle.USER_SYSTEM);
+        mContext.callerPermissions.addAll(OWNER_SETUP_PERMISSIONS);
+        setUpPackageManagerForFakeAdmin(admin1, managedProfileAdminUid, admin1);
+        dpm.setActiveAdmin(admin1, false, userId);
+        assertFalse(dpm.setProfileOwner(admin1, null, userId));
+        mContext.callerPermissions.removeAll(OWNER_SETUP_PERMISSIONS);
+    }
+
+    public void testCheckProvisioningPreCondition_nonSplitUser_attemptingComp() throws Exception {
         setup_nonSplitUser_withDo_primaryUser_ManagedProfile();
         mContext.packageName = admin1.getPackageName();
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
 
         // We can delete the managed profile to create a new one, so provisioning is allowed.
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
-                DevicePolicyManager.CODE_OK);
-        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
+                DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
         assertCheckProvisioningPreCondition(
                 DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 DpmMockContext.ANOTHER_PACKAGE_NAME,
-                DevicePolicyManager.CODE_OK);
-        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true,
+                DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false,
                 DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
     }
 
@@ -3133,8 +3359,8 @@
 
         // But the device owner can still do it because it has set the restriction itself.
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
-                DevicePolicyManager.CODE_OK);
-        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
+                DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
     }
 
     private void setup_splitUser_firstBoot_systemUser() throws Exception {
@@ -3153,6 +3379,7 @@
         mContext.packageName = admin1.getPackageName();
         setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 false /* because canAddMoreManagedProfiles returns false */);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
@@ -3167,6 +3394,8 @@
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
                 DevicePolicyManager.CODE_OK);
+        assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+                DevicePolicyManager.CODE_OK);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 DevicePolicyManager.CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER);
         assertCheckProvisioningPreCondition(
@@ -3193,6 +3422,8 @@
         setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
                 true/* it's undefined behavior. Can be changed into false in the future */);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+                true/* it's undefined behavior. Can be changed into false in the future */);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 false /* because canAddMoreManagedProfiles returns false */);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
@@ -3207,6 +3438,8 @@
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
                 DevicePolicyManager.CODE_OK);
+        assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+                DevicePolicyManager.CODE_OK);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 DevicePolicyManager.CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER);
         assertCheckProvisioningPreCondition(
@@ -3232,6 +3465,7 @@
         mContext.packageName = admin1.getPackageName();
         setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
                 true);
@@ -3244,6 +3478,8 @@
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
                 DevicePolicyManager.CODE_OK);
+        assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+                DevicePolicyManager.CODE_OK);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 DevicePolicyManager.CODE_OK);
         assertCheckProvisioningPreCondition(
@@ -3271,6 +3507,8 @@
         setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
                 true/* it's undefined behavior. Can be changed into false in the future */);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+                true/* it's undefined behavior. Can be changed into false in the future */);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
                 true/* it's undefined behavior. Can be changed into false in the future */);
@@ -3284,6 +3522,8 @@
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
                 DevicePolicyManager.CODE_OK);
+        assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+                DevicePolicyManager.CODE_OK);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 DevicePolicyManager.CODE_OK);
         assertCheckProvisioningPreCondition(
@@ -3329,6 +3569,8 @@
         when(getServices().ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
                 .thenReturn(true);
         when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true);
+        when(getServices().userManager.getProfileParent(DpmMockContext.CALLER_USER_HANDLE))
+            .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
         when(getServices().userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE,
                 true)).thenReturn(true);
         setUserSetupCompleteForUser(false, DpmMockContext.CALLER_USER_HANDLE);
@@ -3341,7 +3583,7 @@
         setup_provisionManagedProfileWithDeviceOwner_primaryUser();
         setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
         mContext.packageName = admin1.getPackageName();
-        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
     }
 
     public void testCheckProvisioningPreCondition_provisionManagedProfileWithDeviceOwner_primaryUser()
@@ -3349,9 +3591,9 @@
         setup_provisionManagedProfileWithDeviceOwner_primaryUser();
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
 
-        // COMP mode is allowed.
+        // COMP mode is NOT allowed.
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
-                DevicePolicyManager.CODE_OK);
+                DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
     }
 
     private void setup_provisionManagedProfileCantRemoveUser_primaryUser() throws Exception {
@@ -3538,6 +3780,39 @@
         assertEquals(-1, dpm.getLastSecurityLogRetrievalTime());
     }
 
+    public void testSetLockdownAdminConfiguredNetworksWithDO() throws Exception {
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+        setupDeviceOwner();
+        dpm.setLockdownAdminConfiguredNetworks(admin1, true);
+        verify(getServices().settings).settingsGlobalPutInt(
+                Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 1);
+
+        dpm.setLockdownAdminConfiguredNetworks(admin1, false);
+        verify(getServices().settings).settingsGlobalPutInt(
+                Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0);
+    }
+
+    public void testSetLockdownAdminConfiguredNetworksWithPO() throws Exception {
+        setupProfileOwner();
+        assertExpectException(SecurityException.class, null,
+                () -> dpm.setLockdownAdminConfiguredNetworks(admin1, false));
+        verify(getServices().settings, never()).settingsGlobalPutInt(
+                Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0);
+    }
+
+    public void testSetLockdownAdminConfiguredNetworksWithPOOfOrganizationOwnedDevice()
+            throws Exception {
+        setupProfileOwner();
+        configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE);
+        dpm.setLockdownAdminConfiguredNetworks(admin1, true);
+        verify(getServices().settings).settingsGlobalPutInt(
+                Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 1);
+
+        dpm.setLockdownAdminConfiguredNetworks(admin1, false);
+        verify(getServices().settings).settingsGlobalPutInt(
+                Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0);
+    }
+
     public void testSetSystemSettingFailWithNonWhitelistedSettings() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
@@ -3645,11 +3920,7 @@
         when(getServices().userManager.getUsers())
                 .thenReturn(Arrays.asList(managedProfileUserInfo));
 
-        // Any caller without the MANAGE_USERS permission should get a security exception.
-        assertExpectException(SecurityException.class, null, () ->
-                dpm.isOrganizationOwnedDeviceWithManagedProfile());
-        // But when the right permission is granted, this should succeed.
-        mContext.permissions.add(android.Manifest.permission.MANAGE_USERS);
+        // Any caller should be able to call this method.
         assertFalse(dpm.isOrganizationOwnedDeviceWithManagedProfile());
         configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE);
         assertTrue(dpm.isOrganizationOwnedDeviceWithManagedProfile());
@@ -3868,11 +4139,6 @@
         List<UserHandle> targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1);
         MoreAsserts.assertEmpty(targetUsers);
 
-        // Setup a managed profile managed by the same admin.
-        final int MANAGED_PROFILE_USER_ID = 15;
-        final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 20456);
-        addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
-
         // Add a secondary user, it should never talk with.
         final int ANOTHER_USER_ID = 36;
         getServices().addUser(ANOTHER_USER_ID, 0, UserManager.USER_TYPE_FULL_SECONDARY);
@@ -3882,30 +4148,11 @@
         targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1);
         MoreAsserts.assertEmpty(targetUsers);
 
-        mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
-        targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1);
-        MoreAsserts.assertEmpty(targetUsers);
-
         // Setting affiliation ids
         final Set<String> userAffiliationIds = Collections.singleton("some.affiliation-id");
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         dpm.setAffiliationIds(admin1, userAffiliationIds);
 
-        mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
-        dpm.setAffiliationIds(admin1, userAffiliationIds);
-
-        // Calling from device owner admin, the result list should just contain the managed
-        // profile user id.
-        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
-        targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1);
-        MoreAsserts.assertContentsInAnyOrder(targetUsers, UserHandle.of(MANAGED_PROFILE_USER_ID));
-
-        // Calling from managed profile admin, the result list should just contain the system
-        // user id.
-        mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
-        targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1);
-        MoreAsserts.assertContentsInAnyOrder(targetUsers, UserHandle.SYSTEM);
-
         // Changing affiliation ids in one
         dpm.setAffiliationIds(admin1, Collections.singleton("some-different-affiliation-id"));
 
@@ -3919,38 +4166,6 @@
         MoreAsserts.assertEmpty(targetUsers);
     }
 
-    public void testGetBindDeviceAdminTargetUsers_differentPackage() throws Exception {
-        // Setup a device owner.
-        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
-        setupDeviceOwner();
-
-        // Set up a managed profile managed by different package.
-        final int MANAGED_PROFILE_USER_ID = 15;
-        final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 20456);
-        final ComponentName adminDifferentPackage =
-                new ComponentName("another.package", "whatever.class");
-        addManagedProfile(adminDifferentPackage, MANAGED_PROFILE_ADMIN_UID, admin2);
-
-        // Setting affiliation ids
-        final Set<String> userAffiliationIds = Collections.singleton("some-affiliation-id");
-        dpm.setAffiliationIds(admin1, userAffiliationIds);
-
-        mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
-        dpm.setAffiliationIds(adminDifferentPackage, userAffiliationIds);
-
-        // Calling from device owner admin, we should get zero bind device admin target users as
-        // their packages are different.
-        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
-        List<UserHandle> targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1);
-        MoreAsserts.assertEmpty(targetUsers);
-
-        // Calling from managed profile admin, we should still get zero target users for the same
-        // reason.
-        mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
-        targetUsers = dpm.getBindDeviceAdminTargetUsers(adminDifferentPackage);
-        MoreAsserts.assertEmpty(targetUsers);
-    }
-
     private void verifyLockTaskState(int userId) throws Exception {
         verifyLockTaskState(userId, new String[0],
                 DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS);
@@ -3987,79 +4202,6 @@
                 () -> dpm.setLockTaskFeatures(who, flags));
     }
 
-    public void testLockTaskPolicyAllowedForAffiliatedUsers() throws Exception {
-        // Setup a device owner.
-        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
-        setupDeviceOwner();
-        // Lock task policy is updated when loading user data.
-        verifyLockTaskState(UserHandle.USER_SYSTEM);
-
-        // Set up a managed profile managed by different package (package name shouldn't matter)
-        final int MANAGED_PROFILE_USER_ID = 15;
-        final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 20456);
-        final ComponentName adminDifferentPackage =
-                new ComponentName("another.package", "whatever.class");
-        addManagedProfile(adminDifferentPackage, MANAGED_PROFILE_ADMIN_UID, admin2);
-        verifyLockTaskState(MANAGED_PROFILE_USER_ID);
-
-        // Setup a PO on the secondary user
-        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
-        setAsProfileOwner(admin3);
-        verifyLockTaskState(DpmMockContext.CALLER_USER_HANDLE);
-
-        // The DO can still set lock task packages
-        final String[] doPackages = {"doPackage1", "doPackage2"};
-        final int flags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
-                | DevicePolicyManager.LOCK_TASK_FEATURE_HOME
-                | DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
-        verifyCanSetLockTask(DpmMockContext.CALLER_SYSTEM_USER_UID, UserHandle.USER_SYSTEM, admin1, doPackages, flags);
-
-        final String[] secondaryPoPackages = {"secondaryPoPackage1", "secondaryPoPackage2"};
-        final int secondaryPoFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
-                | DevicePolicyManager.LOCK_TASK_FEATURE_HOME
-                | DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
-        verifyCanNotSetLockTask(DpmMockContext.CALLER_UID, admin3, secondaryPoPackages, secondaryPoFlags);
-
-        // Managed profile is unaffiliated - shouldn't be able to setLockTaskPackages.
-        mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
-        final String[] poPackages = {"poPackage1", "poPackage2"};
-        final int poFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
-                | DevicePolicyManager.LOCK_TASK_FEATURE_HOME
-                | DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
-        verifyCanNotSetLockTask(MANAGED_PROFILE_ADMIN_UID, adminDifferentPackage, poPackages, poFlags);
-
-        // Setting same affiliation ids
-        final Set<String> userAffiliationIds = Collections.singleton("some-affiliation-id");
-        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
-        dpm.setAffiliationIds(admin1, userAffiliationIds);
-
-        mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
-        dpm.setAffiliationIds(adminDifferentPackage, userAffiliationIds);
-
-        // Now the managed profile can set lock task packages.
-        dpm.setLockTaskPackages(adminDifferentPackage, poPackages);
-        MoreAsserts.assertEquals(poPackages, dpm.getLockTaskPackages(adminDifferentPackage));
-        assertTrue(dpm.isLockTaskPermitted("poPackage1"));
-        assertFalse(dpm.isLockTaskPermitted("doPackage2"));
-        // And it can set lock task features.
-        dpm.setLockTaskFeatures(adminDifferentPackage, poFlags);
-        verifyLockTaskState(MANAGED_PROFILE_USER_ID, poPackages, poFlags);
-
-        // Unaffiliate the profile, lock task mode no longer available on the profile.
-        dpm.setAffiliationIds(adminDifferentPackage, Collections.emptySet());
-        assertFalse(dpm.isLockTaskPermitted("poPackage1"));
-        // Lock task packages cleared when loading user data and when the user becomes unaffiliated.
-        verify(getServices().iactivityManager, times(2)).updateLockTaskPackages(
-                MANAGED_PROFILE_USER_ID, new String[0]);
-        verify(getServices().iactivityTaskManager, times(2)).updateLockTaskFeatures(
-                MANAGED_PROFILE_USER_ID, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
-
-        // Verify that lock task packages were not cleared for the DO
-        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
-        assertTrue(dpm.isLockTaskPermitted("doPackage1"));
-
-    }
-
     public void testLockTaskPolicyForProfileOwner() throws Exception {
         // Setup a PO
         mContext.binder.callingUid = DpmMockContext.CALLER_UID;
@@ -4104,6 +4246,52 @@
                 () -> dpm.setLockTaskFeatures(admin1, flags));
     }
 
+    public void testSecondaryLockscreen_profileOwner() throws Exception {
+        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+
+        // Initial state is disabled.
+        assertFalse(dpm.isSecondaryLockscreenEnabled(DpmMockContext.CALLER_USER_HANDLE));
+
+        // Profile owner can set enabled state.
+        setAsProfileOwner(admin1);
+        dpm.setSecondaryLockscreenEnabled(admin1, true);
+        assertTrue(dpm.isSecondaryLockscreenEnabled(DpmMockContext.CALLER_USER_HANDLE));
+
+        // Managed profile managed by different package is unaffiliated - cannot set enabled.
+        final int managedProfileUserId = 15;
+        final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 20456);
+        final ComponentName adminDifferentPackage =
+                new ComponentName("another.package", "whatever.class");
+        addManagedProfile(adminDifferentPackage, managedProfileAdminUid, admin2);
+        mContext.binder.callingUid = managedProfileAdminUid;
+        assertExpectException(SecurityException.class, /* messageRegex= */ null,
+                () -> dpm.setSecondaryLockscreenEnabled(adminDifferentPackage, false));
+    }
+
+    public void testSecondaryLockscreen_deviceOwner() throws Exception {
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+
+        // Initial state is disabled.
+        assertFalse(dpm.isSecondaryLockscreenEnabled(UserHandle.USER_SYSTEM));
+
+        // Device owners can set enabled state.
+        setupDeviceOwner();
+        dpm.setSecondaryLockscreenEnabled(admin1, true);
+        assertTrue(dpm.isSecondaryLockscreenEnabled(UserHandle.USER_SYSTEM));
+    }
+
+    public void testSecondaryLockscreen_nonOwner() throws Exception {
+        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+
+        // Initial state is disabled.
+        assertFalse(dpm.isSecondaryLockscreenEnabled(DpmMockContext.CALLER_USER_HANDLE));
+
+        // Non-DO/PO cannot set enabled state.
+        assertExpectException(SecurityException.class, /* messageRegex= */ null,
+                () -> dpm.setSecondaryLockscreenEnabled(admin1, true));
+        assertFalse(dpm.isSecondaryLockscreenEnabled(DpmMockContext.CALLER_USER_HANDLE));
+    }
+
     public void testIsDeviceManaged() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
@@ -5626,6 +5814,7 @@
 
     public void testGetAllCrossProfilePackages_notSet_returnsEmpty() throws Exception {
         addManagedProfile(admin1, mServiceContext.binder.callingUid, admin1);
+        mContext.packageName = admin1.getPackageName();
 
         setCrossProfileAppsList();
 
@@ -5635,6 +5824,7 @@
     public void testGetAllCrossProfilePackages_notSet_dpmsReinitialized_returnsEmpty()
             throws Exception {
         addManagedProfile(admin1, mServiceContext.binder.callingUid, admin1);
+        mContext.packageName = admin1.getPackageName();
 
         setCrossProfileAppsList();
         initializeDpms();
@@ -5645,6 +5835,7 @@
     public void testGetAllCrossProfilePackages_whenSet_returnsCombinedSet() throws Exception {
         addManagedProfile(admin1, mServiceContext.binder.callingUid, admin1);
         final Set<String> packages = Sets.newSet("TEST_PACKAGE", "TEST_COMMON_PACKAGE");
+        mContext.packageName = admin1.getPackageName();
 
         dpm.setCrossProfilePackages(admin1, packages);
         setCrossProfileAppsList("TEST_DEFAULT_PACKAGE", "TEST_COMMON_PACKAGE");
@@ -5659,6 +5850,7 @@
             throws Exception {
         addManagedProfile(admin1, mServiceContext.binder.callingUid, admin1);
         final Set<String> packages = Sets.newSet("TEST_PACKAGE", "TEST_COMMON_PACKAGE");
+        mContext.packageName = admin1.getPackageName();
 
         dpm.setCrossProfilePackages(admin1, packages);
         setCrossProfileAppsList("TEST_DEFAULT_PACKAGE", "TEST_COMMON_PACKAGE");
@@ -5669,6 +5861,31 @@
                 dpm.getAllCrossProfilePackages());
     }
 
+    public void testSetCommonCriteriaMode_asDeviceOwner() throws Exception {
+        setDeviceOwner();
+
+        dpm.setCommonCriteriaModeEnabled(admin1, true);
+        verify(getServices().settings).settingsGlobalPutInt(
+                Settings.Global.COMMON_CRITERIA_MODE, 1);
+
+        when(getServices().settings.settingsGlobalGetInt(Settings.Global.COMMON_CRITERIA_MODE, 0))
+                .thenReturn(1);
+        assertTrue(dpm.isCommonCriteriaModeEnabled(admin1));
+    }
+
+    public void testSetCommonCriteriaMode_asPoOfOrgOwnedDevice() throws Exception {
+        setupProfileOwner();
+        configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE);
+
+        dpm.setCommonCriteriaModeEnabled(admin1, true);
+        verify(getServices().settings).settingsGlobalPutInt(
+                Settings.Global.COMMON_CRITERIA_MODE, 1);
+
+        when(getServices().settings.settingsGlobalGetInt(Settings.Global.COMMON_CRITERIA_MODE, 0))
+                .thenReturn(1);
+        assertTrue(dpm.isCommonCriteriaModeEnabled(admin1));
+    }
+
     private void setCrossProfileAppsList(String... packages) {
         when(mContext.getResources()
                 .getStringArray(eq(R.array.cross_profile_apps)))
@@ -5756,10 +5973,6 @@
         return new File(parentDir, "device_policies.xml");
     }
 
-    private InputStream getRawStream(@RawRes int id) {
-        return mRealTestContext.getResources().openRawResource(id);
-    }
-
     private void setUserSetupCompleteForUser(boolean isUserSetupComplete, int userhandle) {
         when(getServices().settings.settingsSecureGetIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0,
                 userhandle)).thenReturn(isUserSetupComplete ? 1 : 0);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index 960f670..12228c1 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -219,6 +219,10 @@
                 return mMockSystemServices.accountManager;
             case Context.TELEPHONY_SERVICE:
                 return mMockSystemServices.telephonyManager;
+            case Context.APP_OPS_SERVICE:
+                return mMockSystemServices.appOpsManager;
+            case Context.CROSS_PROFILE_APPS_SERVICE:
+                return mMockSystemServices.crossProfileApps;
         }
         throw new UnsupportedOperationException();
     }
@@ -256,6 +260,22 @@
     }
 
     @Override
+    public int checkPermission(String permission, int pid, int uid) {
+        if (UserHandle.isSameApp(binder.getCallingUid(), SYSTEM_UID)) {
+            return PackageManager.PERMISSION_GRANTED; // Assume system has all permissions.
+        }
+        List<String> permissions = binder.callingPermissions.get(binder.getCallingUid());
+        if (permissions == null) {
+            permissions = callerPermissions;
+        }
+        if (permissions.contains(permission)) {
+            return PackageManager.PERMISSION_GRANTED;
+        } else {
+            return PackageManager.PERMISSION_DENIED;
+        }
+    }
+
+    @Override
     public void sendBroadcast(Intent intent) {
         spiedContext.sendBroadcast(intent);
     }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
index a34c2ff..9a1a5fb 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
@@ -24,6 +24,7 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.when;
 
+import android.annotation.RawRes;
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -36,6 +37,7 @@
 import android.os.UserHandle;
 import android.test.AndroidTestCase;
 
+import java.io.InputStream;
 import java.util.List;
 
 public abstract class DpmTestBase extends AndroidTestCase {
@@ -256,4 +258,8 @@
                 invocation -> invocation.getArguments()[1]
         );
     }
+
+    protected InputStream getRawStream(@RawRes int id) {
+        return mRealTestContext.getResources().openRawResource(id);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java
new file mode 100644
index 0000000..bc853c6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.FactoryResetProtectionPolicy;
+import android.os.Parcel;
+import android.util.Xml;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.FastXmlSerializer;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for {@link android.app.admin.FactoryResetProtectionPolicy}.
+ *
+ * atest com.android.server.devicepolicy.FactoryResetProtectionPolicyTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class FactoryResetProtectionPolicyTest {
+
+    private static final String TAG_FACTORY_RESET_PROTECTION_POLICY =
+            "factory_reset_protection_policy";
+
+    @Test
+    public void testNonDefaultFactoryResetProtectionPolicyObject() throws Exception {
+        List<String> accounts = new ArrayList<>();
+        accounts.add("Account 1");
+        accounts.add("Account 2");
+
+        FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+                .setFactoryResetProtectionAccounts(accounts)
+                .setFactoryResetProtectionDisabled(true)
+                .build();
+
+        testParcelAndUnparcel(policy);
+        testSerializationAndDeserialization(policy);
+    }
+
+    @Test
+    public void testInvalidXmlFactoryResetProtectionPolicyObject() throws Exception {
+        List<String> accounts = new ArrayList<>();
+        accounts.add("Account 1");
+        accounts.add("Account 2");
+
+        FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+                .setFactoryResetProtectionAccounts(accounts)
+                .setFactoryResetProtectionDisabled(true)
+                .build();
+
+        testParcelAndUnparcel(policy);
+        testInvalidXmlSerializationAndDeserialization(policy);
+    }
+
+    private void testParcelAndUnparcel(FactoryResetProtectionPolicy policy) {
+        Parcel parcel = Parcel.obtain();
+        policy.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        FactoryResetProtectionPolicy actualPolicy =
+                FactoryResetProtectionPolicy.CREATOR.createFromParcel(parcel);
+        assertPoliciesAreEqual(policy, actualPolicy);
+        parcel.recycle();
+    }
+
+    private void testSerializationAndDeserialization(FactoryResetProtectionPolicy policy)
+            throws Exception {
+        ByteArrayOutputStream outStream = serialize(policy);
+        ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray());
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(new InputStreamReader(inStream));
+        assertEquals(XmlPullParser.START_TAG, parser.next());
+
+        assertPoliciesAreEqual(policy, policy.readFromXml(parser));
+    }
+
+    private void testInvalidXmlSerializationAndDeserialization(FactoryResetProtectionPolicy policy)
+            throws Exception {
+        ByteArrayOutputStream outStream = serialize(policy);
+        ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray());
+        XmlPullParser parser = mock(XmlPullParser.class);
+        when(parser.next()).thenThrow(XmlPullParserException.class);
+        parser.setInput(new InputStreamReader(inStream));
+
+        // If deserialization fails, then null is returned.
+        assertNull(policy.readFromXml(parser));
+    }
+
+    private ByteArrayOutputStream serialize(FactoryResetProtectionPolicy policy)
+            throws IOException {
+        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+        final XmlSerializer outXml = new FastXmlSerializer();
+        outXml.setOutput(outStream, StandardCharsets.UTF_8.name());
+        outXml.startDocument(null, true);
+        outXml.startTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
+        policy.writeToXml(outXml);
+        outXml.endTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
+        outXml.endDocument();
+        outXml.flush();
+        return outStream;
+    }
+
+    private void assertPoliciesAreEqual(FactoryResetProtectionPolicy expectedPolicy,
+            FactoryResetProtectionPolicy actualPolicy) {
+        assertEquals(expectedPolicy.isFactoryResetProtectionDisabled(),
+                actualPolicy.isFactoryResetProtectionDisabled());
+        assertAccountsAreEqual(expectedPolicy.getFactoryResetProtectionAccounts(),
+                actualPolicy.getFactoryResetProtectionAccounts());
+    }
+
+    private void assertAccountsAreEqual(List<String> expectedAccounts,
+            List<String> actualAccounts) {
+        assertEquals(expectedAccounts.size(), actualAccounts.size());
+        for (int i = 0; i < expectedAccounts.size(); i++) {
+            assertEquals(expectedAccounts.get(i), actualAccounts.get(i));
+        }
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 919a3f6..37d4081 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.devicepolicy;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -27,6 +28,7 @@
 import android.accounts.AccountManager;
 import android.app.ActivityManagerInternal;
 import android.app.AlarmManager;
+import android.app.AppOpsManager;
 import android.app.IActivityManager;
 import android.app.IActivityTaskManager;
 import android.app.NotificationManager;
@@ -40,6 +42,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.CrossProfileApps;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
@@ -68,6 +71,7 @@
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockSettingsInternal;
+import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
@@ -117,6 +121,9 @@
     public final TimeDetector timeDetector;
     public final TimeZoneDetector timeZoneDetector;
     public final KeyChain.KeyChainConnection keyChainConnection;
+    public final CrossProfileApps crossProfileApps;
+    public final PersistentDataBlockManagerInternal persistentDataBlockManagerInternal;
+    public final AppOpsManager appOpsManager;
     /** Note this is a partial mock, not a real mock. */
     public final PackageManager packageManager;
     public final BuildMock buildMock = new BuildMock();
@@ -160,6 +167,9 @@
         timeDetector = mock(TimeDetector.class);
         timeZoneDetector = mock(TimeZoneDetector.class);
         keyChainConnection = mock(KeyChain.KeyChainConnection.class, RETURNS_DEEP_STUBS);
+        crossProfileApps = mock(CrossProfileApps.class);
+        persistentDataBlockManagerInternal = mock(PersistentDataBlockManagerInternal.class);
+        appOpsManager = mock(AppOpsManager.class);
 
         // Package manager is huge, so we use a partial mock instead.
         packageManager = spy(realContext.getPackageManager());
@@ -233,6 +243,13 @@
                     return ui == null ? null : getUserInfo(ui.profileGroupId);
                 }
         );
+        when(userManager.getProfileParent(any(UserHandle.class))).thenAnswer(
+                invocation -> {
+                    final UserHandle userHandle = (UserHandle) invocation.getArguments()[0];
+                    final UserInfo ui = getUserInfo(userHandle.getIdentifier());
+                    return ui == null ? UserHandle.USER_NULL : UserHandle.of(ui.profileGroupId);
+                }
+        );
         when(userManager.getProfiles(anyInt())).thenAnswer(
                 invocation -> {
                     final int userId12 = (int) invocation.getArguments()[0];
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index ebca240..25d0778 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -78,7 +78,7 @@
         int displayId = 0;
 
         // With no votes present, DisplayModeDirector should allow any refresh rate.
-        assertEquals(new DisplayModeDirector.DesiredDisplayModeSpecs(/*defaultModeId=*/60,
+        assertEquals(new DisplayModeDirector.DesiredDisplayModeSpecs(/*baseModeId=*/60,
                              new DisplayModeDirector.RefreshRateRange(0f, Float.POSITIVE_INFINITY)),
                 createDisplayModeDirectorWithDisplayFpsRange(60, 90).getDesiredDisplayModeSpecs(
                         displayId));
@@ -105,7 +105,7 @@
                 director.injectVotesByDisplay(votesByDisplay);
                 assertEquals(
                         new DisplayModeDirector.DesiredDisplayModeSpecs(
-                                /*defaultModeId=*/minFps + i,
+                                /*baseModeId=*/minFps + i,
                                 new DisplayModeDirector.RefreshRateRange(minFps + i, maxFps - i)),
                         director.getDesiredDisplayModeSpecs(displayId));
             }
@@ -126,7 +126,7 @@
             votes.put(DisplayModeDirector.Vote.MIN_PRIORITY,
                     DisplayModeDirector.Vote.forRefreshRates(70, 80));
             director.injectVotesByDisplay(votesByDisplay);
-            assertEquals(new DisplayModeDirector.DesiredDisplayModeSpecs(/*defaultModeId=*/70,
+            assertEquals(new DisplayModeDirector.DesiredDisplayModeSpecs(/*baseModeId=*/70,
                                  new DisplayModeDirector.RefreshRateRange(70, 80)),
                     director.getDesiredDisplayModeSpecs(displayId));
         }
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 1f66074..9e98427 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -18,6 +18,7 @@
 import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM;
 import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_PLAYBACK;
 
+import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -25,8 +26,17 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
+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.IPowerManager;
 import android.os.Looper;
+import android.os.PowerManager;
+import android.os.RemoteException;
 import android.os.test.TestLooper;
 
 import androidx.test.InstrumentationRegistry;
@@ -36,6 +46,8 @@
 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;
 
@@ -98,6 +110,7 @@
     }
 
     private static final String TAG = "HdmiControlServiceTest";
+    private Context mContextSpy;
     private HdmiControlService mHdmiControlService;
     private HdmiCecController mHdmiCecController;
     private HdmiCecLocalDeviceMyDevice mMyAudioSystemDevice;
@@ -109,15 +122,24 @@
     private boolean mStandbyMessageReceived;
     private HdmiPortInfo[] mHdmiPortInfo;
 
+    @Mock private IPowerManager mIPowerManagerMock;
+
     @Before
-    public void SetUp() {
-        mHdmiControlService =
-                new HdmiControlService(InstrumentationRegistry.getTargetContext()) {
-                    @Override
-                    boolean isStandbyMessageReceived() {
-                        return mStandbyMessageReceived;
-                    }
-                };
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+
+        PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock, null);
+        when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
+        when(mIPowerManagerMock.isInteractive()).thenReturn(true);
+
+        mHdmiControlService = new HdmiControlService(mContextSpy) {
+            @Override
+            boolean isStandbyMessageReceived() {
+                return mStandbyMessageReceived;
+            }
+        };
         mMyLooper = mTestLooper.getLooper();
 
         mMyAudioSystemDevice =
@@ -198,4 +220,33 @@
         mHdmiControlService.initPortInfo();
         assertThat(mHdmiControlService.pathToPortId(0x1000)).isEqualTo(Constants.INVALID_PORT_ID);
     }
+
+    @Test
+    public void initialPowerStatus_normalBoot_isTransientToStandby() {
+        assertThat(mHdmiControlService.getInitialPowerStatus()).isEqualTo(
+                HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY);
+    }
+
+    @Test
+    public void initialPowerStatus_quiescentBoot_isTransientToStandby() throws RemoteException {
+        when(mIPowerManagerMock.isInteractive()).thenReturn(false);
+        assertThat(mHdmiControlService.getInitialPowerStatus()).isEqualTo(
+                HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY);
+    }
+
+    @Test
+    public void powerStatusAfterBootComplete_normalBoot_isOn() {
+        mHdmiControlService.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON);
+        mHdmiControlService.onBootPhase(PHASE_BOOT_COMPLETED);
+        assertThat(mHdmiControlService.getPowerStatus()).isEqualTo(
+                HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON);
+    }
+
+    @Test
+    public void powerStatusAfterBootComplete_quiescentBoot_isStandby() throws RemoteException {
+        when(mIPowerManagerMock.isInteractive()).thenReturn(false);
+        mHdmiControlService.onBootPhase(PHASE_BOOT_COMPLETED);
+        assertThat(mHdmiControlService.getPowerStatus()).isEqualTo(
+                HdmiControlManager.POWER_STATUS_STANDBY);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
index 604efc4..a6af9a9 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
@@ -92,7 +92,7 @@
 
     private static final String PACKAGE_NAME = "com.test.app";
     private static final int VERSION_CODE = 100;
-    private static final String INSTALLER = TEST_FRAMEWORK_PACKAGE;
+    private static final String INSTALLER = "com.long.random.test.installer.name";
     // These are obtained by running the test and checking logcat.
     private static final String APP_CERT =
             "301AA3CB081134501C45F1422ABC66C24224FD5DED5FDC8F17E697176FD866AA";
@@ -100,7 +100,7 @@
             "301AA3CB081134501C45F1422ABC66C24224FD5DED5FDC8F17E697176FD866AA";
     // We use SHA256 for package names longer than 32 characters.
     private static final String INSTALLER_SHA256 =
-            "786933C28839603EB48C50B2A688DC6BE52C833627CB2731FF8466A2AE9F94CD";
+            "30F41A7CBF96EE736A54DD6DF759B50ED3CC126ABCEF694E167C324F5976C227";
 
     private static final String PLAY_STORE_PKG = "com.android.vending";
     private static final String ADB_INSTALLER = "adb";
@@ -140,7 +140,7 @@
         // setup mocks to prevent NPE
         when(mMockContext.getPackageManager()).thenReturn(mSpyPackageManager);
         when(mMockContext.getResources()).thenReturn(mMockResources);
-        when(mMockResources.getStringArray(anyInt())).thenReturn(new String[] {});
+        when(mMockResources.getStringArray(anyInt())).thenReturn(new String[]{});
         when(mIntegrityFileManager.initialized()).thenReturn(true);
     }
 
@@ -267,6 +267,8 @@
 
     @Test
     public void broadcastReceiverRegistration() throws Exception {
+        whitelistUsAsRuleProvider();
+        makeUsSystemApp();
         ArgumentCaptor<IntentFilter> intentFilterCaptor =
                 ArgumentCaptor.forClass(IntentFilter.class);
 
@@ -281,6 +283,8 @@
 
     @Test
     public void handleBroadcast_correctArgs() throws Exception {
+        whitelistUsAsRuleProvider();
+        makeUsSystemApp();
         ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
         verify(mMockContext)
@@ -314,6 +318,8 @@
 
     @Test
     public void handleBroadcast_allow() throws Exception {
+        whitelistUsAsRuleProvider();
+        makeUsSystemApp();
         ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
         verify(mMockContext)
@@ -331,6 +337,8 @@
 
     @Test
     public void handleBroadcast_reject() throws Exception {
+        whitelistUsAsRuleProvider();
+        makeUsSystemApp();
         ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
         verify(mMockContext)
@@ -354,6 +362,8 @@
 
     @Test
     public void handleBroadcast_notInitialized() throws Exception {
+        whitelistUsAsRuleProvider();
+        makeUsSystemApp();
         when(mIntegrityFileManager.initialized()).thenReturn(false);
         ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
@@ -371,10 +381,30 @@
         verify(mSpyPackageManager, never()).getPackageArchiveInfo(any(), anyInt());
     }
 
+    @Test
+    public void verifierAsInstaller_skipIntegrityVerification() throws Exception {
+        whitelistUsAsRuleProvider();
+        makeUsSystemApp();
+        ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mMockContext)
+                .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
+        Intent intent = makeVerificationIntent(TEST_FRAMEWORK_PACKAGE);
+        when(mRuleEvaluationEngine.evaluate(any(), any())).thenReturn(
+                IntegrityCheckResult.deny(/* rule= */ null));
+
+        broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
+        runJobInHandler();
+
+        verify(mPackageManagerInternal)
+                .setIntegrityVerificationResult(
+                        1, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
+    }
+
     private void whitelistUsAsRuleProvider() {
         Resources mockResources = mock(Resources.class);
         when(mockResources.getStringArray(R.array.config_integrityRuleProviderPackages))
-                .thenReturn(new String[] {TEST_FRAMEWORK_PACKAGE});
+                .thenReturn(new String[]{TEST_FRAMEWORK_PACKAGE});
         when(mMockContext.getResources()).thenReturn(mockResources);
     }
 
@@ -395,15 +425,28 @@
     }
 
     private Intent makeVerificationIntent() throws Exception {
+        PackageInfo packageInfo =
+                mRealContext.getPackageManager().getPackageInfo(TEST_FRAMEWORK_PACKAGE,
+                        PackageManager.GET_SIGNATURES);
+        doReturn(packageInfo)
+                .when(mSpyPackageManager)
+                .getPackageInfo(eq(INSTALLER), anyInt());
+        doReturn(1)
+                .when(mSpyPackageManager)
+                .getPackageUid(eq(INSTALLER), anyInt());
+        return makeVerificationIntent(INSTALLER);
+    }
+
+    private Intent makeVerificationIntent(String installer) throws Exception {
         Intent intent = new Intent();
         intent.setDataAndType(Uri.fromFile(mTestApk), PACKAGE_MIME_TYPE);
         intent.setAction(Intent.ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION);
         intent.putExtra(EXTRA_VERIFICATION_ID, 1);
         intent.putExtra(Intent.EXTRA_PACKAGE_NAME, PACKAGE_NAME);
-        intent.putExtra(EXTRA_VERIFICATION_INSTALLER_PACKAGE, INSTALLER);
+        intent.putExtra(EXTRA_VERIFICATION_INSTALLER_PACKAGE, installer);
         intent.putExtra(
                 EXTRA_VERIFICATION_INSTALLER_UID,
-                mRealContext.getPackageManager().getPackageUid(INSTALLER, /* flags= */ 0));
+                mMockContext.getPackageManager().getPackageUid(installer, /* flags= */ 0));
         intent.putExtra(Intent.EXTRA_VERSION_CODE, VERSION_CODE);
         return intent;
     }
diff --git a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
index 63189e7..a1810b9 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
@@ -16,14 +16,9 @@
 
 package com.android.server.integrity;
 
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.hasItems;
-import static org.junit.Assert.assertThat;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
-import static org.testng.Assert.assertTrue;
+import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE;
+
+import static com.google.common.truth.Truth.assertThat;
 
 import android.content.integrity.AppInstallMetadata;
 import android.content.integrity.AtomicFormula;
@@ -33,26 +28,26 @@
 import android.content.integrity.Rule;
 import android.util.Slog;
 
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.integrity.parser.RuleXmlParser;
-import com.android.server.integrity.serializer.RuleXmlSerializer;
+import com.android.server.integrity.parser.RuleBinaryParser;
+import com.android.server.integrity.serializer.RuleBinarySerializer;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
 
 import java.io.File;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 
 /** Unit test for {@link IntegrityFileManager} */
-@RunWith(AndroidJUnit4.class)
+@RunWith(JUnit4.class)
 public class IntegrityFileManagerTest {
     private static final String TAG = "IntegrityFileManagerTest";
 
@@ -72,7 +67,7 @@
         // Use Xml Parser/Serializer to help with debugging since we can just print the file.
         mIntegrityFileManager =
                 new IntegrityFileManager(
-                        new RuleXmlParser(), new RuleXmlSerializer(), mTmpDir);
+                        new RuleBinaryParser(), new RuleBinarySerializer(), mTmpDir);
         Files.walk(mTmpDir.toPath())
                 .forEach(
                         path -> {
@@ -97,12 +92,19 @@
 
     @Test
     public void testGetMetadata() throws Exception {
-        assertNull(mIntegrityFileManager.readMetadata());
+        assertThat(mIntegrityFileManager.readMetadata()).isNull();
         mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST);
 
-        assertNotNull(mIntegrityFileManager.readMetadata());
-        assertEquals(VERSION, mIntegrityFileManager.readMetadata().getVersion());
-        assertEquals(RULE_PROVIDER, mIntegrityFileManager.readMetadata().getRuleProvider());
+        assertThat(mIntegrityFileManager.readMetadata()).isNotNull();
+        assertThat(mIntegrityFileManager.readMetadata().getVersion()).isEqualTo(VERSION);
+        assertThat(mIntegrityFileManager.readMetadata().getRuleProvider()).isEqualTo(RULE_PROVIDER);
+    }
+
+    @Test
+    public void testIsInitialized() throws Exception {
+        assertThat(mIntegrityFileManager.initialized()).isFalse();
+        mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST);
+        assertThat(mIntegrityFileManager.initialized()).isTrue();
     }
 
     @Test
@@ -110,20 +112,8 @@
         String packageName = "package";
         String packageCert = "cert";
         int version = 123;
-        Rule packageNameRule =
-                new Rule(
-                        new StringAtomicFormula(
-                                AtomicFormula.PACKAGE_NAME,
-                                packageName,
-                                /* isHashedValue= */ false),
-                        Rule.DENY);
-        Rule packageCertRule =
-                new Rule(
-                        new StringAtomicFormula(
-                                AtomicFormula.APP_CERTIFICATE,
-                                packageCert,
-                                /* isHashedValue= */ false),
-                        Rule.DENY);
+        Rule packageNameRule = getPackageNameIndexedRule(packageName);
+        Rule packageCertRule = getAppCertificateIndexedRule(packageCert);
         Rule versionCodeRule =
                 new Rule(
                         new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.LE, version),
@@ -142,34 +132,107 @@
                                                 AtomicFormula.LE,
                                                 version))),
                         Rule.DENY);
-        // We will test the specifics of indexing in other classes. Here, we just require that all
-        // rules that are related to the given AppInstallMetadata are returned and do not assert
-        // anything on other rules.
+
         List<Rule> rules =
                 Arrays.asList(packageNameRule, packageCertRule, versionCodeRule, randomRule);
         mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, rules);
 
-        AppInstallMetadata appInstallMetadata = new AppInstallMetadata.Builder()
-                .setPackageName(packageName)
-                .setAppCertificate(packageCert)
-                .setVersionCode(version)
-                .setInstallerName("abc")
-                .setInstallerCertificate("abc")
-                .setIsPreInstalled(true)
-                .build();
+        AppInstallMetadata appInstallMetadata =
+                new AppInstallMetadata.Builder()
+                        .setPackageName(packageName)
+                        .setAppCertificate(packageCert)
+                        .setVersionCode(version)
+                        .setInstallerName("abc")
+                        .setInstallerCertificate("abc")
+                        .setIsPreInstalled(true)
+                        .build();
         List<Rule> rulesFetched = mIntegrityFileManager.readRules(appInstallMetadata);
 
-        assertThat(rulesFetched, hasItems(
-                equalTo(packageNameRule),
-                equalTo(packageCertRule),
-                equalTo(versionCodeRule)
-        ));
+        assertThat(rulesFetched)
+                .containsExactly(packageNameRule, packageCertRule, versionCodeRule, randomRule);
     }
 
     @Test
-    public void testIsInitialized() throws Exception {
-        assertFalse(mIntegrityFileManager.initialized());
+    public void testGetRules_indexedForManyRules() throws Exception {
+        String packageName = "package";
+        String installerName = "installer";
+        String appCertificate = "cert";
+
+        // Create a rule set with 2500 package name indexed, 2500 app certificate indexed and
+        // 500 unindexed rules.
+        List<Rule> rules = new ArrayList<>();
+        int unindexedRuleCount = 70;
+
+        for (int i = 0; i < 2500; i++) {
+            rules.add(getPackageNameIndexedRule(String.format("%s%04d", packageName, i)));
+            rules.add(getAppCertificateIndexedRule(String.format("%s%04d", appCertificate, i)));
+        }
+
+        for (int i = 0; i < unindexedRuleCount; i++) {
+            rules.add(getInstallerCertificateRule(String.format("%s%04d", installerName, i)));
+        }
+
+        // Write the rules and get them indexed.
+        mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, rules);
+
+        // Read the rules for a specific rule.
+        String installedPackageName = String.format("%s%04d", packageName, 264);
+        String installedAppCertificate = String.format("%s%04d", appCertificate, 1264);
+        AppInstallMetadata appInstallMetadata =
+                new AppInstallMetadata.Builder()
+                        .setPackageName(installedPackageName)
+                        .setAppCertificate(installedAppCertificate)
+                        .setVersionCode(250)
+                        .setInstallerName("abc")
+                        .setInstallerCertificate("abc")
+                        .setIsPreInstalled(true)
+                        .build();
+        List<Rule> rulesFetched = mIntegrityFileManager.readRules(appInstallMetadata);
+
+        // Verify that we do not load all the rules and we have the necessary rules to evaluate.
+        assertThat(rulesFetched.size())
+                .isEqualTo(INDEXING_BLOCK_SIZE * 2 + unindexedRuleCount);
+        assertThat(rulesFetched)
+                .containsAllOf(
+                        getPackageNameIndexedRule(installedPackageName),
+                        getAppCertificateIndexedRule(installedAppCertificate));
+    }
+
+    private Rule getPackageNameIndexedRule(String packageName) {
+        return new Rule(
+                new StringAtomicFormula(
+                        AtomicFormula.PACKAGE_NAME, packageName, /* isHashedValue= */ false),
+                Rule.DENY);
+    }
+
+    private Rule getAppCertificateIndexedRule(String appCertificate) {
+        return new Rule(
+                new StringAtomicFormula(
+                        AtomicFormula.APP_CERTIFICATE, appCertificate, /* isHashedValue= */ false),
+                Rule.DENY);
+    }
+
+    private Rule getInstallerCertificateRule(String installerCert) {
+        return new Rule(
+                new StringAtomicFormula(
+                        AtomicFormula.INSTALLER_NAME, installerCert, /* isHashedValue= */ false),
+                Rule.DENY);
+    }
+
+    @Test
+    public void testStagingDirectoryCleared() throws Exception {
+        // We must push rules two times to ensure that staging directory is empty because we cleared
+        // it, rather than because original rules directory is empty.
         mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST);
-        assertTrue(mIntegrityFileManager.initialized());
+        mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST);
+
+        assertStagingDirectoryCleared();
+    }
+
+    private void assertStagingDirectoryCleared() {
+        File stagingDir = new File(mTmpDir, "integrity_staging");
+        assertThat(stagingDir.exists()).isTrue();
+        assertThat(stagingDir.isDirectory()).isTrue();
+        assertThat(stagingDir.listFiles()).isEmpty();
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
index 7a070ee..eda2b70 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
@@ -188,7 +188,7 @@
     }
 
     @Test
-    public void testEvaluateRules_ruleNotInDNF_ignoreAndAllow() {
+    public void testEvaluateRules_orRules() {
         CompoundFormula compoundFormula =
                 new CompoundFormula(
                         CompoundFormula.OR,
@@ -206,11 +206,11 @@
         IntegrityCheckResult result =
                 RuleEvaluator.evaluateRules(Collections.singletonList(rule), APP_INSTALL_METADATA);
 
-        assertEquals(ALLOW, result.getEffect());
+        assertEquals(DENY, result.getEffect());
     }
 
     @Test
-    public void testEvaluateRules_compoundFormulaWithNot_allow() {
+    public void testEvaluateRules_compoundFormulaWithNot_deny() {
         CompoundFormula openSubFormula =
                 new CompoundFormula(
                         CompoundFormula.AND,
@@ -230,7 +230,7 @@
         IntegrityCheckResult result =
                 RuleEvaluator.evaluateRules(Collections.singletonList(rule), APP_INSTALL_METADATA);
 
-        assertEquals(ALLOW, result.getEffect());
+        assertEquals(DENY, result.getEffect());
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java
new file mode 100644
index 0000000..57274bf
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.integrity.model;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.ByteArrayOutputStream;
+
+@RunWith(JUnit4.class)
+public class ByteTrackedOutputStreamTest {
+
+    @Test
+    public void testConstructorStartsWithZeroBytesWritten() {
+        ByteTrackedOutputStream byteTrackedOutputStream =
+                new ByteTrackedOutputStream(new ByteArrayOutputStream());
+
+        assertThat(byteTrackedOutputStream.getWrittenBytesCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void testSuccessfulWriteAndValidateWrittenBytesCount_directFromByteArray()
+            throws Exception {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        ByteTrackedOutputStream byteTrackedOutputStream = new ByteTrackedOutputStream(outputStream);
+
+        byte[] outputContent = "This is going to be outputed for tests.".getBytes();
+        byteTrackedOutputStream.write(outputContent);
+
+        assertThat(byteTrackedOutputStream.getWrittenBytesCount()).isEqualTo(outputContent.length);
+        assertThat(outputStream.toByteArray().length).isEqualTo(outputContent.length);
+    }
+
+    @Test
+    public void testSuccessfulWriteAndValidateWrittenBytesCount_fromBitStream() throws Exception {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        ByteTrackedOutputStream byteTrackedOutputStream = new ByteTrackedOutputStream(outputStream);
+
+        BitOutputStream bitOutputStream = new BitOutputStream(byteTrackedOutputStream);
+        bitOutputStream.setNext(/* numOfBits= */5, /* value= */1);
+        bitOutputStream.flush();
+
+        // Even though we wrote 5 bits, this will complete to 1 byte.
+        assertThat(byteTrackedOutputStream.getWrittenBytesCount()).isEqualTo(1);
+
+        // Add a bit less than 2 bytes (10 bits).
+        bitOutputStream.setNext(/* numOfBits= */10, /* value= */1);
+        bitOutputStream.flush();
+        assertThat(byteTrackedOutputStream.getWrittenBytesCount()).isEqualTo(3);
+
+        assertThat(outputStream.toByteArray().length).isEqualTo(3);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/BinaryFileOperationsTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/BinaryFileOperationsTest.java
index 94f68a5..cfa1de3 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/parser/BinaryFileOperationsTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/parser/BinaryFileOperationsTest.java
@@ -33,8 +33,8 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
-import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
 
 @RunWith(JUnit4.class)
@@ -52,9 +52,7 @@
                         IS_NOT_HASHED
                                 + getBits(PACKAGE_NAME.length(), VALUE_SIZE_BITS)
                                 + getValueBits(PACKAGE_NAME));
-        ByteBuffer rule = ByteBuffer.allocate(stringBytes.length);
-        rule.put(stringBytes);
-        BitInputStream inputStream = new BitInputStream(rule.array());
+        BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(stringBytes));
 
         String resultString = getStringValue(inputStream);
 
@@ -68,9 +66,7 @@
                         IS_HASHED
                                 + getBits(APP_CERTIFICATE.length(), VALUE_SIZE_BITS)
                                 + getValueBits(APP_CERTIFICATE));
-        ByteBuffer rule = ByteBuffer.allocate(ruleBytes.length);
-        rule.put(ruleBytes);
-        BitInputStream inputStream = new BitInputStream(rule.array());
+        BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(ruleBytes));
 
         String resultString = getStringValue(inputStream);
 
@@ -82,9 +78,7 @@
     @Test
     public void testGetStringValue_withSizeAndHashingInfo() throws IOException {
         byte[] ruleBytes = getBytes(getValueBits(PACKAGE_NAME));
-        ByteBuffer rule = ByteBuffer.allocate(ruleBytes.length);
-        rule.put(ruleBytes);
-        BitInputStream inputStream = new BitInputStream(rule.array());
+        BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(ruleBytes));
 
         String resultString = getStringValue(inputStream,
                 PACKAGE_NAME.length(), /* isHashedValue= */false);
@@ -96,9 +90,7 @@
     public void testGetIntValue() throws IOException {
         int randomValue = 15;
         byte[] ruleBytes = getBytes(getBits(randomValue, /* numOfBits= */ 32));
-        ByteBuffer rule = ByteBuffer.allocate(ruleBytes.length);
-        rule.put(ruleBytes);
-        BitInputStream inputStream = new BitInputStream(rule.array());
+        BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(ruleBytes));
 
         assertThat(getIntValue(inputStream)).isEqualTo(randomValue);
     }
@@ -107,9 +99,7 @@
     public void testGetBooleanValue_true() throws IOException {
         String booleanValue = "1";
         byte[] ruleBytes = getBytes(booleanValue);
-        ByteBuffer rule = ByteBuffer.allocate(ruleBytes.length);
-        rule.put(ruleBytes);
-        BitInputStream inputStream = new BitInputStream(rule.array());
+        BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(ruleBytes));
 
         assertThat(getBooleanValue(inputStream)).isEqualTo(true);
     }
@@ -118,9 +108,7 @@
     public void testGetBooleanValue_false() throws IOException {
         String booleanValue = "0";
         byte[] ruleBytes = getBytes(booleanValue);
-        ByteBuffer rule = ByteBuffer.allocate(ruleBytes.length);
-        rule.put(ruleBytes);
-        BitInputStream inputStream = new BitInputStream(rule.array());
+        BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(ruleBytes));
 
         assertThat(getBooleanValue(inputStream)).isEqualTo(false);
     }
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java
index 9cc0ed8..e0b2e22 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java
@@ -44,8 +44,6 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
 import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
@@ -97,8 +95,10 @@
     private static final byte[] DEFAULT_FORMAT_VERSION_BYTES =
             getBytes(getBits(DEFAULT_FORMAT_VERSION, FORMAT_VERSION_BITS));
 
+    private static final List<RuleIndexRange> NO_INDEXING = Collections.emptyList();
+
     @Test
-    public void testBinaryStream_validCompoundFormula() throws Exception {
+    public void testBinaryStream_validCompoundFormula_noIndexing() throws Exception {
         String packageName = "com.test.app";
         String ruleBits =
                 START_BIT
@@ -119,7 +119,6 @@
         rule.put(DEFAULT_FORMAT_VERSION_BYTES);
         rule.put(ruleBytes);
         RuleParser binaryParser = new RuleBinaryParser();
-        InputStream inputStream = new ByteArrayInputStream(rule.array());
         Rule expectedRule =
                 new Rule(
                         new CompoundFormula(
@@ -131,13 +130,14 @@
                                                 /* isHashedValue= */ false))),
                         Rule.DENY);
 
-        List<Rule> rules = binaryParser.parse(inputStream);
+        List<Rule> rules =
+                binaryParser.parse(RandomAccessObject.ofBytes(rule.array()), NO_INDEXING);
 
         assertThat(rules).isEqualTo(Collections.singletonList(expectedRule));
     }
 
     @Test
-    public void testBinaryString_validCompoundFormula_notConnector() throws Exception {
+    public void testBinaryString_validCompoundFormula_notConnector_noIndexing() throws Exception {
         String packageName = "com.test.app";
         String ruleBits =
                 START_BIT
@@ -175,7 +175,7 @@
     }
 
     @Test
-    public void testBinaryString_validCompoundFormula_andConnector() throws Exception {
+    public void testBinaryString_validCompoundFormula_andConnector_noIndexing() throws Exception {
         String packageName = "com.test.app";
         String appCertificate = "test_cert";
         String ruleBits =
@@ -223,7 +223,7 @@
     }
 
     @Test
-    public void testBinaryString_validCompoundFormula_orConnector() throws Exception {
+    public void testBinaryString_validCompoundFormula_orConnector_noIndexing() throws Exception {
         String packageName = "com.test.app";
         String appCertificate = "test_cert";
         String ruleBits =
@@ -272,7 +272,7 @@
     }
 
     @Test
-    public void testBinaryString_validAtomicFormula_stringValue() throws Exception {
+    public void testBinaryString_validAtomicFormula_stringValue_noIndexing() throws Exception {
         String packageName = "com.test.app";
         String ruleBits =
                 START_BIT
@@ -304,7 +304,7 @@
     }
 
     @Test
-    public void testBinaryString_validAtomicFormula_hashedValue() throws Exception {
+    public void testBinaryString_validAtomicFormula_hashedValue_noIndexing() throws Exception {
         String appCertificate = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
         String ruleBits =
                 START_BIT
@@ -321,6 +321,7 @@
                 ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length);
         rule.put(DEFAULT_FORMAT_VERSION_BYTES);
         rule.put(ruleBytes);
+
         RuleParser binaryParser = new RuleBinaryParser();
         Rule expectedRule =
                 new Rule(
@@ -337,7 +338,7 @@
     }
 
     @Test
-    public void testBinaryString_validAtomicFormula_integerValue() throws Exception {
+    public void testBinaryString_validAtomicFormula_integerValue_noIndexing() throws Exception {
         int versionCode = 1;
         String ruleBits =
                 START_BIT
@@ -365,7 +366,7 @@
     }
 
     @Test
-    public void testBinaryString_validAtomicFormula_booleanValue() throws Exception {
+    public void testBinaryString_validAtomicFormula_booleanValue_noIndexing() throws Exception {
         String isPreInstalled = "1";
         String ruleBits =
                 START_BIT
@@ -392,7 +393,7 @@
     }
 
     @Test
-    public void testBinaryString_invalidAtomicFormula() throws Exception {
+    public void testBinaryString_invalidAtomicFormula_noIndexing() {
         int versionCode = 1;
         String ruleBits =
                 START_BIT
@@ -410,12 +411,12 @@
 
         assertExpectException(
                 RuleParseException.class,
-                /* expectedExceptionMessageRegex */ "A rule must end with a '1' bit.",
+                /* expectedExceptionMessageRegex= */ "A rule must end with a '1' bit.",
                 () -> binaryParser.parse(rule.array()));
     }
 
     @Test
-    public void testBinaryString_withNoRuleList() throws RuleParseException {
+    public void testBinaryString_withNoRuleList_noIndexing() throws RuleParseException {
         ByteBuffer rule = ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length);
         rule.put(DEFAULT_FORMAT_VERSION_BYTES);
         RuleParser binaryParser = new RuleBinaryParser();
@@ -426,7 +427,7 @@
     }
 
     @Test
-    public void testBinaryString_withEmptyRule() throws RuleParseException {
+    public void testBinaryString_withEmptyRule_noIndexing() {
         String ruleBits = START_BIT;
         byte[] ruleBytes = getBytes(ruleBits);
         ByteBuffer rule =
@@ -437,12 +438,12 @@
 
         assertExpectException(
                 RuleParseException.class,
-                /* expectedExceptionMessageRegex */ "Invalid byte index",
+                /* expectedExceptionMessageRegex */ "",
                 () -> binaryParser.parse(rule.array()));
     }
 
     @Test
-    public void testBinaryString_invalidCompoundFormula_invalidNumberOfFormulas() throws Exception {
+    public void testBinaryString_invalidCompoundFormula_invalidNumberOfFormulas_noIndexing() {
         String packageName = "com.test.app";
         String appCertificate = "test_cert";
         String ruleBits =
@@ -478,7 +479,7 @@
     }
 
     @Test
-    public void testBinaryString_invalidRule_invalidOperator() throws Exception {
+    public void testBinaryString_invalidRule_invalidOperator_noIndexing() {
         int versionCode = 1;
         String ruleBits =
                 START_BIT
@@ -506,7 +507,7 @@
     }
 
     @Test
-    public void testBinaryString_invalidRule_invalidEffect() throws Exception {
+    public void testBinaryString_invalidRule_invalidEffect_noIndexing() {
         String packageName = "com.test.app";
         String ruleBits =
                 START_BIT
@@ -536,7 +537,7 @@
     }
 
     @Test
-    public void testBinaryString_invalidRule_invalidConnector() throws Exception {
+    public void testBinaryString_invalidRule_invalidConnector_noIndexing() {
         String packageName = "com.test.app";
         String ruleBits =
                 START_BIT
@@ -566,7 +567,7 @@
     }
 
     @Test
-    public void testBinaryString_invalidRule_invalidKey() throws Exception {
+    public void testBinaryString_invalidRule_invalidKey_noIndexing() {
         String packageName = "com.test.app";
         String ruleBits =
                 START_BIT
@@ -596,7 +597,7 @@
     }
 
     @Test
-    public void testBinaryString_invalidRule_invalidSeparator() throws Exception {
+    public void testBinaryString_invalidRule_invalidSeparator_noIndexing() {
         String packageName = "com.test.app";
         String ruleBits =
                 START_BIT
@@ -626,7 +627,7 @@
     }
 
     @Test
-    public void testBinaryString_invalidRule_invalidEndMarker() throws Exception {
+    public void testBinaryString_invalidRule_invalidEndMarker_noIndexing() {
         String packageName = "com.test.app";
         String ruleBits =
                 START_BIT
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java
index a14197b..0f0dee9 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java
@@ -28,8 +28,6 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.Collections;
@@ -61,7 +59,6 @@
                         + "</R>"
                         + "</RL>";
         RuleParser xmlParser = new RuleXmlParser();
-        InputStream inputStream = new ByteArrayInputStream(ruleXmlCompoundFormula.getBytes());
         Rule expectedRule =
                 new Rule(
                         new CompoundFormula(
@@ -73,7 +70,7 @@
                                                 /* isHashedValue= */ false))),
                         Rule.DENY);
 
-        List<Rule> rules = xmlParser.parse(inputStream);
+        List<Rule> rules = xmlParser.parse(ruleXmlCompoundFormula.getBytes());
 
         assertThat(rules).isEqualTo(Collections.singletonList(expectedRule));
     }
@@ -617,13 +614,12 @@
                                 /* tag= */ "AF", atomicFormulaAttrs, /* closed= */ true)
                         + "</OF>"
                         + "</R>";
-        InputStream inputStream = new ByteArrayInputStream(ruleXmlWithNoRuleList.getBytes());
         RuleParser xmlParser = new RuleXmlParser();
 
         assertExpectException(
                 RuleParseException.class,
                 /* expectedExceptionMessageRegex */ "Rules must start with RuleList <RL> tag",
-                () -> xmlParser.parse(inputStream));
+                () -> xmlParser.parse(ruleXmlWithNoRuleList.getBytes()));
     }
 
     private String generateTagWithAttribute(
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/ByteTrackedOutputStreamTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/ByteTrackedOutputStreamTest.java
deleted file mode 100644
index 5ecb8b5c..0000000
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/ByteTrackedOutputStreamTest.java
+++ /dev/null
@@ -1,72 +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.server.integrity.serializer;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.android.server.integrity.model.BitOutputStream;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.ByteArrayOutputStream;
-
-@RunWith(JUnit4.class)
-public class ByteTrackedOutputStreamTest {
-
-    @Test
-    public void testConstructorStartsWithZeroBytesWritten() {
-        ByteTrackedOutputStream byteTrackedOutputStream =
-                new ByteTrackedOutputStream(new ByteArrayOutputStream());
-
-        assertThat(byteTrackedOutputStream.getWrittenBytesCount()).isEqualTo(0);
-    }
-
-    @Test
-    public void testSuccessfulWriteAndValidateWrittenBytesCount_directFromByteArray()
-            throws Exception {
-        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-        ByteTrackedOutputStream byteTrackedOutputStream = new ByteTrackedOutputStream(outputStream);
-
-        byte[] outputContent = "This is going to be outputed for tests.".getBytes();
-        byteTrackedOutputStream.write(outputContent);
-
-        assertThat(byteTrackedOutputStream.getWrittenBytesCount()).isEqualTo(outputContent.length);
-        assertThat(outputStream.toByteArray().length).isEqualTo(outputContent.length);
-    }
-
-    @Test
-    public void testSuccessfulWriteAndValidateWrittenBytesCount_fromBitStream() throws Exception {
-        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-        ByteTrackedOutputStream byteTrackedOutputStream = new ByteTrackedOutputStream(outputStream);
-
-        BitOutputStream bitOutputStream = new BitOutputStream();
-        bitOutputStream.setNext(/* numOfBits= */5, /* value= */1);
-        byteTrackedOutputStream.write(bitOutputStream.toByteArray());
-
-        // Even though we wrote 5 bits, this will complete to 1 byte.
-        assertThat(byteTrackedOutputStream.getWrittenBytesCount()).isEqualTo(1);
-
-        // Add a bit less than 2 bytes (10 bits).
-        bitOutputStream.clear();
-        bitOutputStream.setNext(/* numOfBits= */10, /* value= */1);
-        byteTrackedOutputStream.write(bitOutputStream.toByteArray());
-
-        assertThat(outputStream.toByteArray().length).isEqualTo(3);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
index eb6698b..e5cbeee 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
@@ -30,6 +30,8 @@
 import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY;
 import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE;
 import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY;
+import static com.android.server.integrity.serializer.RuleBinarySerializer.INDEXED_RULE_SIZE_LIMIT;
+import static com.android.server.integrity.serializer.RuleBinarySerializer.NONINDEXED_RULE_SIZE_LIMIT;
 import static com.android.server.integrity.utils.TestUtils.getBits;
 import static com.android.server.integrity.utils.TestUtils.getBytes;
 import static com.android.server.integrity.utils.TestUtils.getValueBits;
@@ -112,8 +114,7 @@
 
         assertExpectException(
                 RuleSerializeException.class,
-                /* expectedExceptionMessageRegex= */ "Index buckets cannot be created for null"
-                        + " rule list.",
+                /* expectedExceptionMessageRegex= */ "Null rules cannot be serialized.",
                 () -> binarySerializer.serialize(null, /* formatVersion= */ Optional.empty()));
     }
 
@@ -135,15 +136,16 @@
                 .isEqualTo(expectedRuleOutputStream.toByteArray());
 
         ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream();
+        String serializedIndexingBytes =
+                SERIALIZED_START_INDEXING_KEY
+                        + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
+                        + SERIALIZED_END_INDEXING_KEY
+                        + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32);
         byte[] expectedIndexingBytes =
                 getBytes(
-                        SERIALIZED_START_INDEXING_KEY
-                                + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
-                                + SERIALIZED_END_INDEXING_KEY
-                                + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */
-                                32));
-        expectedIndexingOutputStream.write(expectedIndexingBytes);
-        expectedIndexingOutputStream.write(expectedIndexingBytes);
+                        serializedIndexingBytes
+                                + serializedIndexingBytes
+                                + serializedIndexingBytes);
         expectedIndexingOutputStream.write(expectedIndexingBytes);
         assertThat(indexingOutputStream.toByteArray())
                 .isEqualTo(expectedIndexingOutputStream.toByteArray());
@@ -197,15 +199,19 @@
                         + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
                         + SERIALIZED_END_INDEXING_KEY
                         + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32);
-        expectedIndexingOutputStream.write(getBytes(expectedIndexingBitsForIndexed));
-        expectedIndexingOutputStream.write(getBytes(expectedIndexingBitsForIndexed));
         String expectedIndexingBitsForUnindexed =
                 SERIALIZED_START_INDEXING_KEY
                         + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
                         + SERIALIZED_END_INDEXING_KEY
-                        + getBits(DEFAULT_FORMAT_VERSION_BYTES.length + getBytes(
-                        expectedBits).length, /* numOfBits= */ 32);
-        expectedIndexingOutputStream.write(getBytes(expectedIndexingBitsForUnindexed));
+                        + getBits(
+                        DEFAULT_FORMAT_VERSION_BYTES.length + getBytes(expectedBits).length,
+                        /* numOfBits= */ 32);
+        expectedIndexingOutputStream.write(
+                getBytes(
+                        expectedIndexingBitsForIndexed
+                                + expectedIndexingBitsForIndexed
+                                + expectedIndexingBitsForUnindexed));
+
         assertThat(indexingOutputStream.toByteArray())
                 .isEqualTo(expectedIndexingOutputStream.toByteArray());
     }
@@ -513,16 +519,19 @@
         // and 225 non-indexed rules..
         List<Rule> ruleList = new ArrayList();
         for (int count = 0; count < ruleCount; count++) {
-            ruleList.add(getRuleWithPackageNameAndSampleInstallerName(
-                    String.format("%s%04d", packagePrefix, count)));
+            ruleList.add(
+                    getRuleWithPackageNameAndSampleInstallerName(
+                            String.format("%s%04d", packagePrefix, count)));
         }
         for (int count = 0; count < ruleCount; count++) {
-            ruleList.add(getRuleWithAppCertificateAndSampleInstallerName(
-                    String.format("%s%04d", appCertificatePrefix, count)));
+            ruleList.add(
+                    getRuleWithAppCertificateAndSampleInstallerName(
+                            String.format("%s%04d", appCertificatePrefix, count)));
         }
         for (int count = 0; count < ruleCount; count++) {
-            ruleList.add(getNonIndexedRuleWithInstallerName(
-                    String.format("%s%04d", installerNamePrefix, count)));
+            ruleList.add(
+                    getNonIndexedRuleWithInstallerName(
+                            String.format("%s%04d", installerNamePrefix, count)));
         }
 
         // Serialize the rules.
@@ -543,8 +552,7 @@
         int totalBytesWritten = DEFAULT_FORMAT_VERSION_BYTES.length;
 
         String expectedIndexingBytesForPackageNameIndexed =
-                SERIALIZED_START_INDEXING_KEY
-                        + getBits(totalBytesWritten, /* numOfBits= */ 32);
+                SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
         for (int count = 0; count < ruleCount; count++) {
             String packageName = String.format("%s%04d", packagePrefix, count);
             if (count > 0 && count % INDEXING_BLOCK_SIZE == 0) {
@@ -556,19 +564,17 @@
             }
 
             byte[] bytesForPackage =
-                    getBytes(getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
-                            packageName));
+                    getBytes(
+                            getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
+                                    packageName));
             expectedOrderedRuleOutputStream.write(bytesForPackage);
             totalBytesWritten += bytesForPackage.length;
         }
         expectedIndexingBytesForPackageNameIndexed +=
-                SERIALIZED_END_INDEXING_KEY
-                        + getBits(totalBytesWritten, /* numOfBits= */ 32);
-        expectedIndexingOutputStream.write(getBytes(expectedIndexingBytesForPackageNameIndexed));
+                SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
 
         String expectedIndexingBytesForAppCertificateIndexed =
-                SERIALIZED_START_INDEXING_KEY
-                        + getBits(totalBytesWritten, /* numOfBits= */ 32);
+                SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
         for (int count = 0; count < ruleCount; count++) {
             String appCertificate = String.format("%s%04d", appCertificatePrefix, count);
             if (count > 0 && count % INDEXING_BLOCK_SIZE == 0) {
@@ -580,31 +586,32 @@
             }
 
             byte[] bytesForPackage =
-                    getBytes(getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
-                            appCertificate));
+                    getBytes(
+                            getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
+                                    appCertificate));
             expectedOrderedRuleOutputStream.write(bytesForPackage);
             totalBytesWritten += bytesForPackage.length;
         }
         expectedIndexingBytesForAppCertificateIndexed +=
-                SERIALIZED_END_INDEXING_KEY
-                        + getBits(totalBytesWritten, /* numOfBits= */ 32);
-        expectedIndexingOutputStream.write(getBytes(expectedIndexingBytesForAppCertificateIndexed));
+                SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
 
         String expectedIndexingBytesForUnindexed =
-                SERIALIZED_START_INDEXING_KEY
-                        + getBits(totalBytesWritten, /* numOfBits= */ 32);
+                SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
         for (int count = 0; count < ruleCount; count++) {
             byte[] bytesForPackage =
-                    getBytes(getSerializedCompoundRuleWithInstallerNameAndInstallerCert(
-                            String.format("%s%04d", installerNamePrefix, count)));
+                    getBytes(
+                            getSerializedCompoundRuleWithInstallerNameAndInstallerCert(
+                                    String.format("%s%04d", installerNamePrefix, count)));
             expectedOrderedRuleOutputStream.write(bytesForPackage);
             totalBytesWritten += bytesForPackage.length;
         }
         expectedIndexingBytesForUnindexed +=
-                SERIALIZED_END_INDEXING_KEY
-                        + getBits(totalBytesWritten, /* numOfBits= */ 32);
-        expectedIndexingOutputStream.write(getBytes(expectedIndexingBytesForUnindexed));
-
+                SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
+        expectedIndexingOutputStream.write(
+                getBytes(
+                        expectedIndexingBytesForPackageNameIndexed
+                                + expectedIndexingBytesForAppCertificateIndexed
+                                + expectedIndexingBytesForUnindexed));
 
         assertThat(ruleOutputStream.toByteArray())
                 .isEqualTo(expectedOrderedRuleOutputStream.toByteArray());
@@ -612,6 +619,131 @@
                 .isEqualTo(expectedIndexingOutputStream.toByteArray());
     }
 
+    @Test
+    public void testBinaryString_totalRuleSizeLimitReached() {
+        int ruleCount = INDEXED_RULE_SIZE_LIMIT - 1;
+        String packagePrefix = "package.name.";
+        String appCertificatePrefix = "app.cert.";
+        String installerNamePrefix = "installer.";
+
+        // Create the rule set with more rules than the system can handle in total.
+        List<Rule> ruleList = new ArrayList();
+        for (int count = 0; count < ruleCount; count++) {
+            ruleList.add(
+                    getRuleWithPackageNameAndSampleInstallerName(
+                            String.format("%s%04d", packagePrefix, count)));
+        }
+        for (int count = 0; count < ruleCount; count++) {
+            ruleList.add(
+                    getRuleWithAppCertificateAndSampleInstallerName(
+                            String.format("%s%04d", appCertificatePrefix, count)));
+        }
+        for (int count = 0; count < NONINDEXED_RULE_SIZE_LIMIT - 1; count++) {
+            ruleList.add(
+                    getNonIndexedRuleWithInstallerName(
+                            String.format("%s%04d", installerNamePrefix, count)));
+        }
+
+        // Serialize the rules.
+        ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
+        ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
+        RuleSerializer binarySerializer = new RuleBinarySerializer();
+
+        assertExpectException(
+                RuleSerializeException.class,
+                "Too many rules provided",
+                () ->
+                        binarySerializer.serialize(
+                                ruleList,
+                                /* formatVersion= */ Optional.empty(),
+                                ruleOutputStream,
+                                indexingOutputStream));
+    }
+
+    @Test
+    public void testBinaryString_tooManyPackageNameIndexedRules() {
+        String packagePrefix = "package.name.";
+
+        // Create a rule set with too many package name indexed rules.
+        List<Rule> ruleList = new ArrayList();
+        for (int count = 0; count < INDEXED_RULE_SIZE_LIMIT + 1; count++) {
+            ruleList.add(
+                    getRuleWithPackageNameAndSampleInstallerName(
+                            String.format("%s%04d", packagePrefix, count)));
+        }
+
+        // Serialize the rules.
+        ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
+        ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
+        RuleSerializer binarySerializer = new RuleBinarySerializer();
+
+        assertExpectException(
+                RuleSerializeException.class,
+                "Too many rules provided in the indexing group.",
+                () ->
+                        binarySerializer.serialize(
+                                ruleList,
+                                /* formatVersion= */ Optional.empty(),
+                                ruleOutputStream,
+                                indexingOutputStream));
+    }
+
+    @Test
+    public void testBinaryString_tooManyAppCertificateIndexedRules() {
+        String appCertificatePrefix = "app.cert.";
+
+        // Create a rule set with too many app certificate indexed rules.
+        List<Rule> ruleList = new ArrayList();
+        for (int count = 0; count < INDEXED_RULE_SIZE_LIMIT + 1; count++) {
+            ruleList.add(
+                    getRuleWithAppCertificateAndSampleInstallerName(
+                            String.format("%s%04d", appCertificatePrefix, count)));
+        }
+
+        // Serialize the rules.
+        ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
+        ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
+        RuleSerializer binarySerializer = new RuleBinarySerializer();
+
+        assertExpectException(
+                RuleSerializeException.class,
+                "Too many rules provided in the indexing group.",
+                () ->
+                        binarySerializer.serialize(
+                                ruleList,
+                                /* formatVersion= */ Optional.empty(),
+                                ruleOutputStream,
+                                indexingOutputStream));
+    }
+
+    @Test
+    public void testBinaryString_tooManyNonIndexedRules() {
+        String installerNamePrefix = "installer.";
+
+        // Create a rule set with too many unindexed rules.
+        List<Rule> ruleList = new ArrayList();
+        for (int count = 0; count < NONINDEXED_RULE_SIZE_LIMIT + 1; count++) {
+            ruleList.add(
+                    getNonIndexedRuleWithInstallerName(
+                            String.format("%s%04d", installerNamePrefix, count)));
+        }
+
+        // Serialize the rules.
+        ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
+        ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
+        RuleSerializer binarySerializer = new RuleBinarySerializer();
+
+        assertExpectException(
+                RuleSerializeException.class,
+                "Too many rules provided in the indexing group.",
+                () ->
+                        binarySerializer.serialize(
+                                ruleList,
+                                /* formatVersion= */ Optional.empty(),
+                                ruleOutputStream,
+                                indexingOutputStream));
+    }
+
     private Rule getRuleWithPackageNameAndSampleInstallerName(String packageName) {
         return new Rule(
                 new CompoundFormula(
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
index 55fada4..1674422 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
@@ -38,10 +38,8 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.TreeMap;
 
 /** Unit tests for {@link RuleIndexingDetailsIdentifier}. */
 @RunWith(JUnit4.class)
@@ -140,7 +138,7 @@
         List<Rule> ruleList = new ArrayList();
         ruleList.add(RULE_WITH_PACKAGE_NAME);
 
-        Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
+        Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
 
         // Verify the resulting map content.
         assertThat(result.keySet())
@@ -157,7 +155,7 @@
         List<Rule> ruleList = new ArrayList();
         ruleList.add(RULE_WITH_APP_CERTIFICATE);
 
-        Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
+        Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
 
         assertThat(result.keySet())
                 .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
@@ -174,7 +172,7 @@
         List<Rule> ruleList = new ArrayList();
         ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS);
 
-        Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
+        Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
 
         assertThat(result.keySet())
                 .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
@@ -189,7 +187,7 @@
         List<Rule> ruleList = new ArrayList();
         ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS);
 
-        Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
+        Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
 
         assertThat(result.keySet())
                 .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
@@ -215,7 +213,7 @@
         List<Rule> ruleList = new ArrayList();
         ruleList.add(negatedRule);
 
-        Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
+        Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
 
         assertThat(result.keySet())
                 .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
@@ -225,7 +223,7 @@
     }
 
     @Test
-    public void getIndexType_allRulesTogetherInValidOrder() {
+    public void getIndexType_allRulesTogetherSplitCorrectly() {
         Rule packageNameRuleA = getRuleWithPackageName("aaa");
         Rule packageNameRuleB = getRuleWithPackageName("bbb");
         Rule packageNameRuleC = getRuleWithPackageName("ccc");
@@ -243,38 +241,20 @@
         ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS);
         ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS);
 
-        Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
+        Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
 
         assertThat(result.keySet())
                 .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
 
         // We check asserts this way to ensure ordering based on package name.
         assertThat(result.get(PACKAGE_NAME_INDEXED).keySet()).containsExactly("aaa", "bbb", "ccc");
-        Iterator<String> keySetIterator = result.get(PACKAGE_NAME_INDEXED).keySet().iterator();
-        assertThat(keySetIterator.next()).isEqualTo("aaa");
-        assertThat(keySetIterator.next()).isEqualTo("bbb");
-        assertThat(keySetIterator.next()).isEqualTo("ccc");
-        assertThat(result.get(PACKAGE_NAME_INDEXED).get("aaa")).containsExactly(packageNameRuleA);
-        assertThat(result.get(PACKAGE_NAME_INDEXED).get("bbb")).containsExactly(packageNameRuleB);
-        assertThat(result.get(PACKAGE_NAME_INDEXED).get("ccc")).containsExactly(packageNameRuleC);
 
         // We check asserts this way to ensure ordering based on app certificate.
         assertThat(result.get(APP_CERTIFICATE_INDEXED).keySet()).containsExactly("cert1", "cert2",
                 "cert3");
-        keySetIterator = result.get(APP_CERTIFICATE_INDEXED).keySet().iterator();
-        assertThat(keySetIterator.next()).isEqualTo("cert1");
-        assertThat(keySetIterator.next()).isEqualTo("cert2");
-        assertThat(keySetIterator.next()).isEqualTo("cert3");
-        assertThat(result.get(APP_CERTIFICATE_INDEXED).get("cert1")).containsExactly(
-                certificateRule1);
-        assertThat(result.get(APP_CERTIFICATE_INDEXED).get("cert2")).containsExactly(
-                certificateRule2);
-        assertThat(result.get(APP_CERTIFICATE_INDEXED).get("cert3")).containsExactly(
-                certificateRule3);
 
         assertThat(result.get(NOT_INDEXED).get("N/A"))
-                .containsExactly(
-                        RULE_WITH_INSTALLER_RESTRICTIONS,
+                .containsExactly(RULE_WITH_INSTALLER_RESTRICTIONS,
                         RULE_WITH_NONSTRING_RESTRICTIONS);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/job/PrioritySchedulingTest.java b/services/tests/servicestests/src/com/android/server/job/PrioritySchedulingTest.java
index 63bccfa..9ecba59 100644
--- a/services/tests/servicestests/src/com/android/server/job/PrioritySchedulingTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/PrioritySchedulingTest.java
@@ -22,6 +22,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.test.AndroidTestCase;
+
 import com.android.server.job.MockPriorityJobService.TestEnvironment;
 import com.android.server.job.MockPriorityJobService.TestEnvironment.Event;
 
@@ -35,6 +36,11 @@
     static ComponentName kJobServiceComponent;
     JobScheduler mJobScheduler;
 
+    // The system overrides the test app priority to be a minimum of FOREGROUND_SERVICE. We can
+    // bypass that override by using a priority of at least bound foreground service.
+    private static final int HIGH_PRIORITY = JobInfo.PRIORITY_BOUND_FOREGROUND_SERVICE + 1;
+    private static final int LOW_PRIORITY = JobInfo.PRIORITY_BOUND_FOREGROUND_SERVICE;
+
     @Override
     public void setUp() throws Exception {
         super.setUp();
@@ -51,32 +57,25 @@
     }
 
     public void testLowerPriorityJobPreempted() throws Exception {
-        JobInfo job1 = new JobInfo.Builder(111, kJobServiceComponent)
-                .setPriority(1)
-                .setOverrideDeadline(7000L)
+        for (int i = 0; i < JobSchedulerService.MAX_JOB_CONTEXTS_COUNT; ++i) {
+            JobInfo job = new JobInfo.Builder(100 + i, kJobServiceComponent)
+                    .setPriority(LOW_PRIORITY)
+                    .setOverrideDeadline(0)
+                    .build();
+            mJobScheduler.schedule(job);
+        }
+        final int higherPriorityJobId = 100 + JobSchedulerService.MAX_JOB_CONTEXTS_COUNT;
+        JobInfo jobHigher = new JobInfo.Builder(higherPriorityJobId, kJobServiceComponent)
+                .setPriority(HIGH_PRIORITY)
+                .setMinimumLatency(2000)
+                .setOverrideDeadline(4000)
                 .build();
-        JobInfo job2 = new JobInfo.Builder(222, kJobServiceComponent)
-                .setPriority(1)
-                .setOverrideDeadline(7000L)
-                .build();
-        JobInfo job3 = new JobInfo.Builder(333, kJobServiceComponent)
-                .setPriority(1)
-                .setOverrideDeadline(7000L)
-                .build();
-        JobInfo job4 = new JobInfo.Builder(444, kJobServiceComponent)
-                .setPriority(2)
-                .setMinimumLatency(2000L)
-                .setOverrideDeadline(7000L)
-                .build();
-        mJobScheduler.schedule(job1);
-        mJobScheduler.schedule(job2);
-        mJobScheduler.schedule(job3);
-        mJobScheduler.schedule(job4);
-        Thread.sleep(10000);  // Wait for job 4 to preempt one of the lower priority jobs
+        mJobScheduler.schedule(jobHigher);
+        Thread.sleep(10000);  // Wait for jobHigher to preempt one of the lower priority jobs
 
-        Event job4Execution = new Event(TestEnvironment.EVENT_START_JOB, 444);
+        Event jobHigherExecution = new Event(TestEnvironment.EVENT_START_JOB, higherPriorityJobId);
         ArrayList<Event> executedEvents = kTestEnvironment.getExecutedEvents();
-        boolean wasJob4Executed = executedEvents.contains(job4Execution);
+        boolean wasJobHigherExecuted = executedEvents.contains(jobHigherExecution);
         boolean wasSomeJobPreempted = false;
         for (Event event: executedEvents) {
             if (event.event == TestEnvironment.EVENT_PREEMPT_JOB) {
@@ -85,35 +84,28 @@
             }
         }
         assertTrue("No job was preempted.", wasSomeJobPreempted);
-        assertTrue("Lower priority jobs were not preempted.",  wasJob4Executed);
+        assertTrue("Lower priority jobs were not preempted.", wasJobHigherExecuted);
     }
 
     public void testHigherPriorityJobNotPreempted() throws Exception {
-        JobInfo job1 = new JobInfo.Builder(111, kJobServiceComponent)
-                .setPriority(2)
-                .setOverrideDeadline(7000L)
+        for (int i = 0; i < JobSchedulerService.MAX_JOB_CONTEXTS_COUNT; ++i) {
+            JobInfo job = new JobInfo.Builder(100 + i, kJobServiceComponent)
+                    .setPriority(HIGH_PRIORITY)
+                    .setOverrideDeadline(0)
+                    .build();
+            mJobScheduler.schedule(job);
+        }
+        final int lowerPriorityJobId = 100 + JobSchedulerService.MAX_JOB_CONTEXTS_COUNT;
+        JobInfo jobLower = new JobInfo.Builder(lowerPriorityJobId, kJobServiceComponent)
+                .setPriority(LOW_PRIORITY)
+                .setMinimumLatency(2000)
+                .setOverrideDeadline(3000)
                 .build();
-        JobInfo job2 = new JobInfo.Builder(222, kJobServiceComponent)
-                .setPriority(2)
-                .setOverrideDeadline(7000L)
-                .build();
-        JobInfo job3 = new JobInfo.Builder(333, kJobServiceComponent)
-                .setPriority(2)
-                .setOverrideDeadline(7000L)
-                .build();
-        JobInfo job4 = new JobInfo.Builder(444, kJobServiceComponent)
-                .setPriority(1)
-                .setMinimumLatency(2000L)
-                .setOverrideDeadline(7000L)
-                .build();
-        mJobScheduler.schedule(job1);
-        mJobScheduler.schedule(job2);
-        mJobScheduler.schedule(job3);
-        mJobScheduler.schedule(job4);
-        Thread.sleep(10000);  // Wait for job 4 to preempt one of the higher priority jobs
+        mJobScheduler.schedule(jobLower);
+        Thread.sleep(10000);
 
-        Event job4Execution = new Event(TestEnvironment.EVENT_START_JOB, 444);
-        boolean wasJob4Executed = kTestEnvironment.getExecutedEvents().contains(job4Execution);
-        assertFalse("Higher priority job was preempted.", wasJob4Executed);
+        Event jobLowerExecution = new Event(TestEnvironment.EVENT_START_JOB, lowerPriorityJobId);
+        boolean wasLowerExecuted = kTestEnvironment.getExecutedEvents().contains(jobLowerExecution);
+        assertFalse("Higher priority job was preempted.", wasLowerExecuted);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java b/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java
new file mode 100644
index 0000000..b0def60
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java
@@ -0,0 +1,173 @@
+/*
+ * 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.lights;
+
+import static android.hardware.lights.LightsRequest.Builder;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.hardware.light.HwLight;
+import android.hardware.light.HwLightState;
+import android.hardware.light.ILights;
+import android.hardware.lights.Light;
+import android.hardware.lights.LightState;
+import android.hardware.lights.LightsManager;
+import android.os.Looper;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class LightsServiceTest {
+
+    private final ILights mHal = new ILights.Stub() {
+        @Override
+        public void setLightState(int id, HwLightState state) {
+            return;
+        }
+
+        @Override
+        public HwLight[] getLights() {
+            return new HwLight[] {
+                fakeHwLight(101, 3, 1),
+                fakeHwLight(102, LightsManager.LIGHT_TYPE_MICROPHONE, 4),
+                fakeHwLight(103, LightsManager.LIGHT_TYPE_MICROPHONE, 3),
+                fakeHwLight(104, LightsManager.LIGHT_TYPE_MICROPHONE, 1),
+                fakeHwLight(105, LightsManager.LIGHT_TYPE_MICROPHONE, 2)
+            };
+        }
+    };
+
+    private static HwLight fakeHwLight(int id, int type, int ordinal) {
+        HwLight light = new HwLight();
+        light.id = id;
+        light.type = (byte) type;
+        light.ordinal = ordinal;
+        return light;
+    }
+
+    @Mock
+    Context mContext;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testGetLights_filtersSystemLights() {
+        LightsService service = new LightsService(mContext, mHal, Looper.getMainLooper());
+        LightsManager manager = new LightsManager(mContext, service.mManagerService);
+
+        // When lights are listed, only the 4 MICROPHONE lights should be visible.
+        assertThat(manager.getLights().size()).isEqualTo(4);
+    }
+
+    @Test
+    public void testControlMultipleLights() {
+        LightsService service = new LightsService(mContext, mHal, Looper.getMainLooper());
+        LightsManager manager = new LightsManager(mContext, service.mManagerService);
+
+        // When the session requests to turn 3/4 lights on:
+        LightsManager.LightsSession session = manager.openSession();
+        session.setLights(new Builder()
+                .setLight(manager.getLights().get(0), new LightState(0xf1))
+                .setLight(manager.getLights().get(1), new LightState(0xf2))
+                .setLight(manager.getLights().get(2), new LightState(0xf3))
+                .build());
+
+        // Then all 3 should turn on.
+        assertThat(manager.getLightState(manager.getLights().get(0)).getColor()).isEqualTo(0xf1);
+        assertThat(manager.getLightState(manager.getLights().get(1)).getColor()).isEqualTo(0xf2);
+        assertThat(manager.getLightState(manager.getLights().get(2)).getColor()).isEqualTo(0xf3);
+
+        // And the 4th should remain off.
+        assertThat(manager.getLightState(manager.getLights().get(3)).getColor()).isEqualTo(0x00);
+    }
+
+    @Test
+    public void testControlLights_onlyEffectiveForLifetimeOfClient() {
+        LightsService service = new LightsService(mContext, mHal, Looper.getMainLooper());
+        LightsManager manager = new LightsManager(mContext, service.mManagerService);
+        Light micLight = manager.getLights().get(0);
+
+        // The light should begin by being off.
+        assertThat(manager.getLightState(micLight).getColor()).isEqualTo(0x00000000);
+
+        // When a session commits changes:
+        LightsManager.LightsSession session = manager.openSession();
+        session.setLights(new Builder().setLight(micLight, new LightState(0xff00ff00)).build());
+        // Then the light should turn on.
+        assertThat(manager.getLightState(micLight).getColor()).isEqualTo(0xff00ff00);
+
+        // When the session goes away:
+        session.close();
+        // Then the light should turn off.
+        assertThat(manager.getLightState(micLight).getColor()).isEqualTo(0x00000000);
+    }
+
+    @Test
+    public void testControlLights_firstCallerWinsContention() {
+        LightsService service = new LightsService(mContext, mHal, Looper.getMainLooper());
+        LightsManager manager = new LightsManager(mContext, service.mManagerService);
+        Light micLight = manager.getLights().get(0);
+
+        LightsManager.LightsSession session1 = manager.openSession();
+        LightsManager.LightsSession session2 = manager.openSession();
+
+        // When session1 and session2 both request the same light:
+        session1.setLights(new Builder().setLight(micLight, new LightState(0xff0000ff)).build());
+        session2.setLights(new Builder().setLight(micLight, new LightState(0xffffffff)).build());
+        // Then session1 should win because it was created first.
+        assertThat(manager.getLightState(micLight).getColor()).isEqualTo(0xff0000ff);
+
+        // When session1 goes away:
+        session1.close();
+        // Then session2 should have its request go into effect.
+        assertThat(manager.getLightState(micLight).getColor()).isEqualTo(0xffffffff);
+
+        // When session2 goes away:
+        session2.close();
+        // Then the light should turn off because there are no more sessions.
+        assertThat(manager.getLightState(micLight).getColor()).isEqualTo(0);
+    }
+
+    @Test
+    public void testClearLight() {
+        LightsService service = new LightsService(mContext, mHal, Looper.getMainLooper());
+        LightsManager manager = new LightsManager(mContext, service.mManagerService);
+        Light micLight = manager.getLights().get(0);
+
+        // When the session turns a light on:
+        LightsManager.LightsSession session = manager.openSession();
+        session.setLights(new Builder().setLight(micLight, new LightState(0xffffffff)).build());
+
+        // And then the session clears it again:
+        session.setLights(new Builder().clearLight(micLight).build());
+
+        // Then the light should turn back off.
+        assertThat(manager.getLightState(micLight).getColor()).isEqualTo(0);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index a3d15dd..1b5c56a 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -230,6 +230,18 @@
             }
         }).when(sm).addUserKeyAuth(anyInt(), anyInt(), any(), any());
 
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                Object[] args = invocation.getArguments();
+                mStorageManager.clearUserKeyAuth((int) args[0] /* userId */,
+                        (int) args[1] /* serialNumber */,
+                        (byte[]) args[2] /* token */,
+                        (byte[]) args[3] /* secret */);
+                return null;
+            }
+        }).when(sm).clearUserKeyAuth(anyInt(), anyInt(), any(), any());
+
         doAnswer(
                 new Answer<Void>() {
             @Override
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/FakeStorageManager.java b/services/tests/servicestests/src/com/android/server/locksettings/FakeStorageManager.java
index 1ae1fa6..102bac1 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/FakeStorageManager.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/FakeStorageManager.java
@@ -36,6 +36,15 @@
         getUserAuth(userId).add(new Pair<>(token, secret));
     }
 
+    public void clearUserKeyAuth(int userId, int serialNumber, byte[] token, byte[] secret) {
+        ArrayList<Pair<byte[], byte[]>> auths = getUserAuth(userId);
+        if (token == null && secret == null) {
+            return;
+        }
+        auths.remove(new Pair<>(token, secret));
+        auths.add(new Pair<>(null, null));
+    }
+
     public void fixateNewestUserKeyAuth(int userId) {
         ArrayList<Pair<byte[], byte[]>> auths = mAuth.get(userId);
         Pair<byte[], byte[]> latest = auths.get(auths.size() - 1);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
index 821d97a..670bd81 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
@@ -67,13 +67,25 @@
 public class PlatformKeyManagerTest {
 
     private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
-    private static final int USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 15;
+    private static final int MIN_GENERATION_ID = 1000000;
+    private static final int PRIMARY_USER_ID_FIXTURE = 0;
     private static final int USER_ID_FIXTURE = 42;
     private static final long USER_SID = 4200L;
     private static final String KEY_ALGORITHM = "AES";
     private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
     private static final String TESTING_KEYSTORE_KEY_ALIAS = "testing-key-store-key-alias";
 
+    private static final String ENCRYPTION_KEY_ALIAS_1 =
+             "com.android.server.locksettings.recoverablekeystore/platform/42/1000000/encrypt";
+    private static final String DECRYPTION_KEY_ALIAS_1 =
+             "com.android.server.locksettings.recoverablekeystore/platform/42/1000000/decrypt";
+    private static final String DECRYPTION_KEY_FOR_ALIAS_PRIMARY_USER_1 =
+             "com.android.server.locksettings.recoverablekeystore/platform/0/1000000/decrypt";
+    private static final String ENCRYPTION_KEY_ALIAS_2 =
+             "com.android.server.locksettings.recoverablekeystore/platform/42/1000001/encrypt";
+    private static final String DECRYPTION_KEY_ALIAS_2 =
+             "com.android.server.locksettings.recoverablekeystore/platform/42/1000001/decrypt";
+
     @Mock private Context mContext;
     @Mock private KeyStoreProxy mKeyStoreProxy;
     @Mock private KeyguardManager mKeyguardManager;
@@ -114,7 +126,7 @@
         mPlatformKeyManager.init(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).setEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+                eq(ENCRYPTION_KEY_ALIAS_1),
                 any(),
                 any());
     }
@@ -156,7 +168,7 @@
         mPlatformKeyManager.init(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).setEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_1),
                 any(),
                 any());
     }
@@ -187,19 +199,33 @@
     }
 
     @Test
-    public void init_createsDecryptKeyWithAuthenticationRequired() throws Exception {
-        mPlatformKeyManager.init(USER_ID_FIXTURE);
+    public void init_primaryUser_createsDecryptKeyWithUnlockedDeviceRequired() throws Exception {
+        mPlatformKeyManager.init(PRIMARY_USER_ID_FIXTURE);
 
-        assertTrue(getDecryptKeyProtection().isUserAuthenticationRequired());
+        assertTrue(getDecryptKeyProtectionForPrimaryUser().isUnlockedDeviceRequired());
     }
 
     @Test
-    public void init_createsDecryptKeyWithAuthenticationValidFor15Seconds() throws Exception {
+    public void init_primaryUser_createsDecryptKeyWithoutAuthenticationRequired() throws Exception {
+        mPlatformKeyManager.init(PRIMARY_USER_ID_FIXTURE);
+
+        assertFalse(getDecryptKeyProtectionForPrimaryUser().isUserAuthenticationRequired());
+    }
+
+    @Test
+    public void init_secondaryUser_createsDecryptKeyWithoutUnlockedDeviceRequired()
+            throws Exception {
         mPlatformKeyManager.init(USER_ID_FIXTURE);
 
-        assertEquals(
-                USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS,
-                getDecryptKeyProtection().getUserAuthenticationValidityDurationSeconds());
+        assertFalse(getDecryptKeyProtection().isUnlockedDeviceRequired());
+    }
+
+    @Test
+    public void init_secondaryUserUser_createsDecryptKeyWithAuthenticationRequired()
+            throws Exception {
+        mPlatformKeyManager.init(USER_ID_FIXTURE);
+
+        assertTrue(getDecryptKeyProtection().isUserAuthenticationRequired());
     }
 
     @Test
@@ -219,7 +245,7 @@
         mPlatformKeyManager.init(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy, never()).setEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_1),
                 any(),
                 any());
     }
@@ -231,7 +257,7 @@
         expectThrows(RemoteException.class, () -> mPlatformKeyManager.init(USER_ID_FIXTURE));
 
         verify(mKeyStoreProxy, never()).setEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_1),
                 any(),
                 any());
     }
@@ -251,15 +277,15 @@
     public void init_savesGenerationIdToDatabase() throws Exception {
         mPlatformKeyManager.init(USER_ID_FIXTURE);
 
-        assertEquals(1,
+        assertEquals(MIN_GENERATION_ID,
                 mRecoverableKeyStoreDb.getPlatformKeyGenerationId(USER_ID_FIXTURE));
     }
 
     @Test
-    public void init_setsGenerationIdTo1() throws Exception {
+    public void init_setsGenerationId() throws Exception {
         mPlatformKeyManager.init(USER_ID_FIXTURE);
 
-        assertEquals(1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
+        assertEquals(MIN_GENERATION_ID, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
     }
 
     @Test
@@ -268,22 +294,20 @@
 
         mPlatformKeyManager.init(USER_ID_FIXTURE);
 
-        assertEquals(2, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
+        assertEquals(MIN_GENERATION_ID + 1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
     }
 
     @Test
     public void init_doesNotIncrementGenerationIdIfKeyAvailable() throws Exception {
         mPlatformKeyManager.init(USER_ID_FIXTURE);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
 
         mPlatformKeyManager.init(USER_ID_FIXTURE);
 
-        assertEquals(1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
+        assertEquals(MIN_GENERATION_ID, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
     }
 
     @Test
@@ -294,226 +318,194 @@
     @Test
     public void getDecryptKey_getsDecryptKeyWithCorrectAlias() throws Exception {
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy.getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_1),
                 any())).thenReturn(generateAndroidKeyStoreKey());
 
         mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_1),
                 any());
     }
 
     @Test
     public void getDecryptKey_generatesNewKeyIfOldDecryptKeyWasRemoved() throws Exception {
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(false); // was removed
+                .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/encrypt")).thenReturn(true); // new version is available
+                .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); // new version
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
         when(mKeyStoreProxy.getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_2),
                 any())).thenReturn(generateAndroidKeyStoreKey());
 
         mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).containsAlias(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"));
+                eq(DECRYPTION_KEY_ALIAS_1));
         // Attempt to get regenerated key.
         verify(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_2),
                 any());
     }
 
     @Test
     public void getDecryptKey_generatesNewKeyIfOldEncryptKeyWasRemoved() throws Exception {
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(false); // was removed
+                .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
 
         mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).containsAlias(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"));
+                eq(ENCRYPTION_KEY_ALIAS_1));
         // Attempt to get regenerated key.
         verify(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_2),
                 any());
     }
 
     @Test
     public void getEncryptKey_generatesNewKeyIfOldOneIsInvalid() throws Exception {
         doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+                eq(ENCRYPTION_KEY_ALIAS_1),
                 any());
 
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
 
         mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+                eq(ENCRYPTION_KEY_ALIAS_1),
                 any());
         // Attempt to get regenerated key.
         verify(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+                eq(ENCRYPTION_KEY_ALIAS_2),
                 any());
     }
 
     @Test
     public void getDecryptKey_generatesNewKeyIfOldOneIsInvalid() throws Exception {
         doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_1),
                 any());
         when(mKeyStoreProxy.getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_2),
                 any())).thenReturn(generateAndroidKeyStoreKey());
 
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
 
         mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).containsAlias(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"));
+                eq(DECRYPTION_KEY_ALIAS_1));
         // Attempt to get regenerated key.
         verify(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_2),
                 any());
     }
 
     @Test
     public void getEncryptKey_generatesNewKeyIfDecryptKeyIsUnrecoverable() throws Exception {
         doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_1),
                 any());
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(false); // was removed
+                .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/encrypt")).thenReturn(true); // new version is available
+                .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); // new version
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
 
         mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
 
         // Attempt to get regenerated key.
         verify(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+                eq(ENCRYPTION_KEY_ALIAS_2),
                 any());
     }
 
     @Test
     public void getEncryptKey_generatesNewKeyIfOldDecryptKeyWasRemoved() throws Exception {
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(false); // was removed
+                .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/encrypt")).thenReturn(true); // new version is available
+                .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); // new version
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
 
         mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).containsAlias(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"));
+                eq(ENCRYPTION_KEY_ALIAS_1));
         // Attempt to get regenerated key.
         verify(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+                eq(ENCRYPTION_KEY_ALIAS_2),
                 any());
     }
 
     @Test
     public void getEncryptKey_generatesNewKeyIfOldEncryptKeyWasRemoved() throws Exception {
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(false); // was removed
+                .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
 
         mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).containsAlias(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"));
+                eq(ENCRYPTION_KEY_ALIAS_1));
         // Attempt to get regenerated key.
         verify(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+                eq(ENCRYPTION_KEY_ALIAS_2),
                 any());
     }
 
     @Test
     public void getEncryptKey_getsEncryptKeyWithCorrectAlias() throws Exception {
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
 
         mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+                eq(ENCRYPTION_KEY_ALIAS_1),
                 any());
     }
 
@@ -523,7 +515,7 @@
 
         mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
 
-        assertEquals(2, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
+        assertEquals(MIN_GENERATION_ID + 1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
     }
 
     @Test
@@ -533,17 +525,17 @@
         mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).deleteEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"));
+                eq(ENCRYPTION_KEY_ALIAS_1));
         verify(mKeyStoreProxy).deleteEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"));
+                eq(DECRYPTION_KEY_ALIAS_1));
 
         mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
 
         // Removes second generation keys.
         verify(mKeyStoreProxy).deleteEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"));
+                eq(ENCRYPTION_KEY_ALIAS_2));
         verify(mKeyStoreProxy).deleteEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"));
+                eq(DECRYPTION_KEY_ALIAS_2));
     }
 
     @Test
@@ -553,7 +545,7 @@
         mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).setEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+                eq(ENCRYPTION_KEY_ALIAS_2),
                 any(),
                 any());
     }
@@ -565,14 +557,14 @@
         mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).setEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_2),
                 any(),
                 any());
     }
 
     private KeyProtection getEncryptKeyProtection() throws Exception {
         verify(mKeyStoreProxy).setEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+                eq(ENCRYPTION_KEY_ALIAS_1),
                 any(),
                 mProtectionParameterCaptor.capture());
         return (KeyProtection) mProtectionParameterCaptor.getValue();
@@ -580,7 +572,15 @@
 
     private KeyProtection getDecryptKeyProtection() throws Exception {
         verify(mKeyStoreProxy).setEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_1),
+                any(),
+                mProtectionParameterCaptor.capture());
+        return (KeyProtection) mProtectionParameterCaptor.getValue();
+    }
+
+    private KeyProtection getDecryptKeyProtectionForPrimaryUser() throws Exception {
+        verify(mKeyStoreProxy).setEntry(
+                eq(DECRYPTION_KEY_FOR_ALIAS_PRIMARY_USER_1),
                 any(),
                 mProtectionParameterCaptor.capture());
         return (KeyProtection) mProtectionParameterCaptor.getValue();
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index 6890017..ac74470 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -29,6 +29,7 @@
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeast;
@@ -84,7 +85,7 @@
 import java.util.ArrayList;
 import java.util.Map;
 import java.util.Random;
-import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledExecutorService;
 
 import javax.crypto.KeyGenerator;
 import javax.crypto.SecretKey;
@@ -157,7 +158,7 @@
     @Mock private PlatformKeyManager mPlatformKeyManager;
     @Mock private ApplicationKeyStorage mApplicationKeyStorage;
     @Mock private CleanupManager mCleanupManager;
-    @Mock private ExecutorService mExecutorService;
+    @Mock private ScheduledExecutorService mExecutorService;
     @Spy private TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper;
 
     private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
@@ -1253,7 +1254,7 @@
         mRecoverableKeyStoreManager.lockScreenSecretAvailable(
                 LockPatternUtils.CREDENTIAL_TYPE_PATTERN, "password".getBytes(), 11);
 
-        verify(mExecutorService).execute(any());
+        verify(mExecutorService).schedule(any(Runnable.class), anyLong(), any());
     }
 
     @Test
@@ -1263,7 +1264,7 @@
                 "password".getBytes(),
                 11);
 
-        verify(mExecutorService).execute(any());
+        verify(mExecutorService).schedule(any(Runnable.class), anyLong(), any());
     }
 
     private static byte[] encryptedApplicationKey(
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index 28e6830..a1baf0e 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -37,6 +37,7 @@
 import static android.net.NetworkStats.IFACE_ALL;
 import static android.net.NetworkStats.SET_ALL;
 import static android.net.NetworkStats.TAG_ALL;
+import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
 import static android.net.NetworkTemplate.buildTemplateWifi;
 import static android.net.TrafficStats.MB_IN_BYTES;
@@ -111,7 +112,7 @@
 import android.net.NetworkStats;
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
-import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.INetworkManagementService;
@@ -137,6 +138,7 @@
 import android.util.RecurrenceRule;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.test.BroadcastInterceptingContext;
@@ -1054,6 +1056,7 @@
                 computeLastCycleBoundary(parseTime("2013-01-14T15:11:00.000-08:00"), policy));
     }
 
+    @FlakyTest
     @Test
     public void testNetworkPolicyAppliedCycleLastMonth() throws Exception {
         NetworkState[] state = null;
@@ -1273,11 +1276,11 @@
             history.recordData(start, end,
                     new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1440), 0L, 0L, 0L, 0));
             stats.clear();
-            stats.addValues(IFACE_ALL, UID_A, SET_ALL, TAG_ALL,
+            stats.addEntry(IFACE_ALL, UID_A, SET_ALL, TAG_ALL,
                     DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0);
-            stats.addValues(IFACE_ALL, UID_B, SET_ALL, TAG_ALL,
+            stats.addEntry(IFACE_ALL, UID_B, SET_ALL, TAG_ALL,
                     DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0);
-            stats.addValues(IFACE_ALL, UID_C, SET_ALL, TAG_ALL,
+            stats.addEntry(IFACE_ALL, UID_C, SET_ALL, TAG_ALL,
                     DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0);
 
             reset(mNotifManager);
@@ -1301,9 +1304,9 @@
             history.recordData(start, end,
                     new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1440), 0L, 0L, 0L, 0));
             stats.clear();
-            stats.addValues(IFACE_ALL, UID_A, SET_ALL, TAG_ALL,
+            stats.addEntry(IFACE_ALL, UID_A, SET_ALL, TAG_ALL,
                     DataUnit.MEGABYTES.toBytes(960), 0, 0, 0, 0);
-            stats.addValues(IFACE_ALL, UID_B, SET_ALL, TAG_ALL,
+            stats.addEntry(IFACE_ALL, UID_B, SET_ALL, TAG_ALL,
                     DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0);
 
             reset(mNotifManager);
@@ -1758,6 +1761,59 @@
     }
 
     /**
+     * Test that when StatsProvider triggers limit reached, new limit will be calculated and
+     * re-armed.
+     */
+    @Test
+    public void testStatsProviderLimitReached() throws Exception {
+        final int CYCLE_DAY = 15;
+
+        final NetworkStats stats = new NetworkStats(0L, 1);
+        stats.addEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE,
+                2999, 1, 2000, 1, 0);
+        when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
+                .thenReturn(stats.getTotalBytes());
+        when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong()))
+                .thenReturn(stats);
+
+        // Get active mobile network in place
+        expectMobileDefaults();
+        mService.updateNetworks();
+        verify(mStatsService).setStatsProviderLimit(TEST_IFACE, Long.MAX_VALUE);
+
+        // Set limit to 10KB.
+        setNetworkPolicies(new NetworkPolicy(
+                sTemplateMobileAll, CYCLE_DAY, TIMEZONE_UTC, WARNING_DISABLED, 10000L,
+                true));
+        postMsgAndWaitForCompletion();
+
+        // Verifies that remaining quota is set to providers.
+        verify(mStatsService).setStatsProviderLimit(TEST_IFACE, 10000L - 4999L);
+
+        reset(mStatsService);
+
+        // Increase the usage.
+        stats.addEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE,
+                1000, 1, 999, 1, 0);
+        when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
+                .thenReturn(stats.getTotalBytes());
+        when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong()))
+                .thenReturn(stats);
+
+        // Simulates that limit reached fires earlier by provider, but actually the quota is not
+        // yet reached.
+        final NetworkPolicyManagerInternal npmi = LocalServices
+                .getService(NetworkPolicyManagerInternal.class);
+        npmi.onStatsProviderLimitReached("TEST");
+
+        // Verifies that the limit reached leads to a force update and new limit should be set.
+        postMsgAndWaitForCompletion();
+        verify(mStatsService).forceUpdate();
+        postMsgAndWaitForCompletion();
+        verify(mStatsService).setStatsProviderLimit(TEST_IFACE, 10000L - 4999L - 1999L);
+    }
+
+    /**
      * Exhaustively test isUidNetworkingBlocked to output the expected results based on external
      * conditions.
      */
@@ -1859,7 +1915,8 @@
         if (!roaming) {
             nc.addCapability(NET_CAPABILITY_NOT_ROAMING);
         }
-        nc.setNetworkSpecifier(new StringNetworkSpecifier(String.valueOf(subId)));
+        nc.setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+                .setSubscriptionId(subId).build());
         return nc;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
index 82bbdcb..7f66f3c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
@@ -28,10 +28,12 @@
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.parsing.AndroidPackage;
+import android.content.pm.parsing.ComponentParseUtils;
 import android.content.pm.parsing.ComponentParseUtils.ParsedActivity;
 import android.content.pm.parsing.ComponentParseUtils.ParsedActivityIntentInfo;
 import android.content.pm.parsing.PackageImpl;
 import android.content.pm.parsing.ParsingPackage;
+import android.net.Uri;
 import android.os.Build;
 import android.os.Process;
 import android.util.ArrayMap;
@@ -116,6 +118,15 @@
                 .addActivity(activity);
     }
 
+    private static ParsingPackage pkgWithProvider(String packageName, String authority) {
+        ComponentParseUtils.ParsedProvider provider = new ComponentParseUtils.ParsedProvider();
+        provider.setPackageName(packageName);
+        provider.setExported(true);
+        provider.setAuthority(authority);
+        return pkg(packageName)
+                .addProvider(provider);
+    }
+
     @Before
     public void setup() throws Exception {
         mExisting = new ArrayMap<>();
@@ -149,6 +160,55 @@
     }
 
     @Test
+    public void testQueriesProvider_FilterMatches() {
+        final AppsFilter appsFilter =
+                new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+        appsFilter.onSystemReady();
+
+        PackageSetting target = simulateAddPackage(appsFilter,
+                pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_UID);
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package",
+                        new Intent().setData(Uri.parse("content://com.some.authority"))),
+                DUMMY_CALLING_UID);
+
+        assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
+    }
+
+    @Test
+    public void testQueriesDifferentProvider_Filters() {
+        final AppsFilter appsFilter =
+                new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+        appsFilter.onSystemReady();
+
+        PackageSetting target = simulateAddPackage(appsFilter,
+                pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_UID);
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package",
+                        new Intent().setData(Uri.parse("content://com.some.other.authority"))),
+                DUMMY_CALLING_UID);
+
+        assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
+    }
+
+    @Test
+    public void testQueriesProviderWithSemiColon_FilterMatches() {
+        final AppsFilter appsFilter =
+                new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+        appsFilter.onSystemReady();
+
+        PackageSetting target = simulateAddPackage(appsFilter,
+                pkgWithProvider("com.some.package", "com.some.authority;com.some.other.authority"),
+                DUMMY_TARGET_UID);
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package",
+                        new Intent().setData(Uri.parse("content://com.some.authority"))),
+                DUMMY_CALLING_UID);
+
+        assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
+    }
+
+    @Test
     public void testQueriesAction_NoMatchingAction_Filters() {
         final AppsFilter appsFilter =
                 new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
@@ -450,6 +510,62 @@
                 overlaySetting, 0));
     }
 
+    @Test
+    public void testInitiatingApp_DoesntFilter() {
+        final AppsFilter appsFilter =
+                new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+        appsFilter.onSystemReady();
+
+        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
+                DUMMY_TARGET_UID);
+        PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"),
+                DUMMY_CALLING_UID, withInstallSource(target.name, null, null, false));
+
+        assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
+    }
+
+    @Test
+    public void testUninstalledInitiatingApp_Filters() {
+        final AppsFilter appsFilter =
+                new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+        appsFilter.onSystemReady();
+
+        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
+                DUMMY_TARGET_UID);
+        PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"),
+                DUMMY_CALLING_UID, withInstallSource(target.name, null, null, true));
+
+        assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
+    }
+
+    @Test
+    public void testOriginatingApp_Filters() {
+        final AppsFilter appsFilter =
+                new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+        appsFilter.onSystemReady();
+
+        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
+                DUMMY_TARGET_UID);
+        PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"),
+                DUMMY_CALLING_UID, withInstallSource(null, target.name, null, false));
+
+        assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
+    }
+
+    @Test
+    public void testInstallingApp_DoesntFilter() {
+        final AppsFilter appsFilter =
+                new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+        appsFilter.onSystemReady();
+
+        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
+                DUMMY_TARGET_UID);
+        PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"),
+                DUMMY_CALLING_UID, withInstallSource(null, null, target.name, false));
+
+        assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
+    }
+
     private interface WithSettingBuilder {
         PackageSettingBuilder withBuilder(PackageSettingBuilder builder);
     }
@@ -477,5 +593,13 @@
         return setting;
     }
 
+    private WithSettingBuilder withInstallSource(String initiatingPackageName,
+            String originatingPackageName, String installerPackageName,
+            boolean isInitiatingPackageUninstalled) {
+        final InstallSource installSource = InstallSource.create(initiatingPackageName,
+                originatingPackageName, installerPackageName,
+                /* isOrphaned= */ false, isInitiatingPackageUninstalled);
+        return setting -> setting.setInstallSource(installSource);
+    }
 }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
index e18ff61..91cc9f3 100644
--- a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
@@ -13,17 +13,21 @@
 import static org.mockito.Mockito.when;
 import static org.testng.Assert.assertThrows;
 
+import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.AppOpsManager;
 import android.app.IApplicationThread;
+import android.app.admin.DevicePolicyManagerInternal;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.PermissionInfo;
 import android.content.pm.ResolveInfo;
 import android.os.Bundle;
 import android.os.UserHandle;
@@ -76,6 +80,10 @@
     private ActivityManagerInternal mActivityManagerInternal;
     @Mock
     private ActivityTaskManagerInternal mActivityTaskManagerInternal;
+    @Mock
+    private IPackageManager mIPackageManager;
+    @Mock
+    private DevicePolicyManagerInternal mDevicePolicyManagerInternal;
 
     private TestInjector mTestInjector;
     private ActivityInfo mActivityInfo;
@@ -88,6 +96,7 @@
     public void initCrossProfileAppsServiceImpl() {
         mTestInjector = new TestInjector();
         mCrossProfileAppsServiceImpl = new CrossProfileAppsServiceImpl(mContext, mTestInjector);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
     }
 
     @Before
@@ -358,6 +367,11 @@
 
     @Test
     public void startAnyActivityAsUser_profile_notExported() {
+        try {
+            when(mPackageManager.getPermissionInfo(anyString(), anyInt()))
+                    .thenReturn(new PermissionInfo());
+        } catch (PackageManager.NameNotFoundException ignored) {
+        }
         mActivityInfo.exported = false;
 
         assertThrows(
@@ -516,11 +530,16 @@
     private class TestInjector implements CrossProfileAppsServiceImpl.Injector {
         private int mCallingUid;
         private int mCallingUserId;
+        private int mCallingPid;
 
         public void setCallingUid(int uid) {
             mCallingUid = uid;
         }
 
+        public void setCallingPid(int pid) {
+            mCallingPid = pid;
+        }
+
         public void setCallingUserId(int userId) {
             mCallingUserId = userId;
         }
@@ -531,6 +550,11 @@
         }
 
         @Override
+        public int getCallingPid() {
+            return mCallingPid;
+        }
+
+        @Override
         public int getCallingUserId() {
             return mCallingUserId;
         }
@@ -578,5 +602,26 @@
         public ActivityTaskManagerInternal getActivityTaskManagerInternal() {
             return mActivityTaskManagerInternal;
         }
+
+        @Override
+        public IPackageManager getIPackageManager() {
+            return mIPackageManager;
+        }
+
+        @Override
+        public DevicePolicyManagerInternal getDevicePolicyManagerInternal() {
+            return mDevicePolicyManagerInternal;
+        }
+
+        @Override
+        public void sendBroadcastAsUser(Intent intent, UserHandle user) {
+            mContext.sendBroadcastAsUser(intent, user);
+        }
+
+        @Override
+        public int checkComponentPermission(
+                String permission, int uid, int owningUid, boolean exported) {
+            return ActivityManager.checkComponentPermission(permission, uid, owningUid, exported);
+        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java b/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java
index 3852b9f..6a9ef8a 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java
@@ -15,6 +15,9 @@
  */
 package com.android.server.pm;
 
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
 import android.content.Context;
 import android.content.pm.ModuleInfo;
 import android.content.pm.PackageManager;
@@ -22,11 +25,25 @@
 
 import com.android.frameworks.servicestests.R;
 
+import org.mockito.Mock;
+
 import java.util.Collections;
 import java.util.List;
 
 public class ModuleInfoProviderTest extends InstrumentationTestCase {
+
+    @Mock private ApexManager mApexManager;
+
+    public void setUp() {
+        initMocks(this);
+    }
+
     public void testSuccessfulParse() {
+        when(mApexManager.getApexModuleNameForPackageName("com.android.module1"))
+                .thenReturn("com.module1.apex");
+        when(mApexManager.getApexModuleNameForPackageName("com.android.module2"))
+                .thenReturn("com.module2.apex");
+
         ModuleInfoProvider provider = getProvider(R.xml.well_formed_metadata);
 
         List<ModuleInfo> mi = provider.getInstalledModules(PackageManager.MATCH_ALL);
@@ -40,11 +57,13 @@
         ModuleInfo mi1 = provider.getModuleInfo("com.android.module1", 0);
         assertEquals("com.android.module1", mi1.getPackageName());
         assertEquals("module_1_name", mi1.getName());
+        assertEquals("com.module1.apex", mi1.getApexModuleName());
         assertEquals(false, mi1.isHidden());
 
         ModuleInfo mi2 = provider.getModuleInfo("com.android.module2", 0);
         assertEquals("com.android.module2", mi2.getPackageName());
         assertEquals("module_2_name", mi2.getName());
+        assertEquals("com.module2.apex", mi2.getApexModuleName());
         assertEquals(true, mi2.isHidden());
     }
 
@@ -75,6 +94,7 @@
      */
     private ModuleInfoProvider getProvider(int resourceId) {
         final Context ctx = getInstrumentation().getContext();
-        return new ModuleInfoProvider(ctx.getResources().getXml(resourceId), ctx.getResources());
+        return new ModuleInfoProvider(
+                ctx.getResources().getXml(resourceId), ctx.getResources(), mApexManager);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
index c478ec4..a8674a8 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
@@ -156,8 +156,7 @@
         if (isMultiPackage) {
             params.isMultiPackage = true;
         }
-        InstallSource installSource = InstallSource.create("testInstaller", null, "testInstaller",
-                false);
+        InstallSource installSource = InstallSource.create("testInstaller", null, "testInstaller");
         return new PackageInstallerSession(
                 /* callback */ null,
                 /* context */null,
@@ -310,6 +309,7 @@
                 actual.getStagedSessionErrorMessage());
         assertEquals(expected.isPrepared(), actual.isPrepared());
         assertEquals(expected.isCommitted(), actual.isCommitted());
+        assertEquals(expected.createdMillis, actual.createdMillis);
         assertEquals(expected.isSealed(), actual.isSealed());
         assertEquals(expected.isMultiPackage(), actual.isMultiPackage());
         assertEquals(expected.hasParentSessionId(), actual.hasParentSessionId());
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
index 2473997..8441494 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
@@ -41,6 +41,7 @@
     private SparseArray<PackageUserState> mUserStates = new SparseArray<>();
     private AndroidPackage mPkg;
     private int mAppId;
+    private InstallSource mInstallSource;
 
     public PackageSettingBuilder setPackage(AndroidPackage pkg) {
         this.mPkg = pkg;
@@ -137,6 +138,11 @@
         return this;
     }
 
+    public PackageSettingBuilder setInstallSource(InstallSource installSource) {
+        mInstallSource = installSource;
+        return this;
+    }
+
     public PackageSetting build() {
         final PackageSetting packageSetting = new PackageSetting(mName, mRealName,
                 new File(mCodePath), new File(mResourcePath),
@@ -146,6 +152,9 @@
         packageSetting.pkg = mPkg;
         packageSetting.appId = mAppId;
         packageSetting.volumeUuid = this.mVolumeUuid;
+        if (mInstallSource != null) {
+            packageSetting.installSource = mInstallSource;
+        }
         for (int i = 0; i < mUserStates.size(); i++) {
             packageSetting.setUserState(mUserStates.keyAt(i), mUserStates.valueAt(i));
         }
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
index e375aef..9eaf8b6 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
@@ -23,7 +23,9 @@
 
 import android.content.pm.UserInfo;
 import android.os.Looper;
+import android.os.ServiceSpecificException;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.UserManagerInternal;
 
 import androidx.test.InstrumentationRegistry;
@@ -31,11 +33,13 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.LocalServices;
+import com.android.server.storage.DeviceStorageMonitorInternal;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mockito;
 
 import java.util.List;
 
@@ -243,6 +247,65 @@
                         true /* allow remove */));
     }
 
+    @Test
+    public void testCreateProfileForUser_lowStorageException() {
+        DeviceStorageMonitorInternal dsmMock = Mockito.mock(DeviceStorageMonitorInternal.class);
+        Mockito.when(dsmMock.isMemoryLow()).thenReturn(true);
+        LocalServices.addService(DeviceStorageMonitorInternal.class, dsmMock);
+
+        try {
+            mUserManagerService.createProfileForUserWithThrow("user2", USER_TYPE_PROFILE_MANAGED, 0,
+                    UserHandle.USER_SYSTEM, null);
+        } catch (ServiceSpecificException e) {
+            assertEquals(UserManager.USER_OPERATION_ERROR_LOW_STORAGE,
+                    UserManager.UserOperationException.from(e).getUserOperationResult());
+        } finally {
+            LocalServices.removeServiceForTest(DeviceStorageMonitorInternal.class);
+        }
+    }
+
+    @Test
+    public void testCreateProfileForUser_unknownParentUser() {
+        DeviceStorageMonitorInternal dsmMock = Mockito.mock(DeviceStorageMonitorInternal.class);
+        Mockito.when(dsmMock.isMemoryLow()).thenReturn(false);
+        LocalServices.addService(DeviceStorageMonitorInternal.class, dsmMock);
+
+        try {
+            final int badParentUserId = 1234;
+            mUserManagerService.createProfileForUserWithThrow("profile", USER_TYPE_PROFILE_MANAGED,
+                    0, badParentUserId, null);
+        } catch (ServiceSpecificException e) {
+            assertEquals(UserManager.USER_OPERATION_ERROR_UNKNOWN,
+                    UserManager.UserOperationException.from(e).getUserOperationResult());
+        } finally {
+            LocalServices.removeServiceForTest(DeviceStorageMonitorInternal.class);
+        }
+    }
+
+    @Test
+    public void testCreateManagedProfileForUser_maxManagedUsersException() {
+        DeviceStorageMonitorInternal dsmMock = Mockito.mock(DeviceStorageMonitorInternal.class);
+        Mockito.when(dsmMock.isMemoryLow()).thenReturn(false);
+        LocalServices.addService(DeviceStorageMonitorInternal.class, dsmMock);
+
+        UserManagerService userManagerServiceSpy = Mockito.spy(mUserManagerService);
+        Mockito.doReturn(false).when(userManagerServiceSpy).canAddMoreManagedProfiles(
+                Mockito.anyInt(), Mockito.anyBoolean());
+
+        Mockito.doReturn(false).when(userManagerServiceSpy).canAddMoreProfilesToUser(
+                Mockito.anyString(), Mockito.anyInt(), Mockito.anyBoolean());
+
+        try {
+            userManagerServiceSpy.createProfileForUserWithThrow("profile",
+                    USER_TYPE_PROFILE_MANAGED, 0, UserHandle.USER_SYSTEM, null);
+        } catch (ServiceSpecificException e) {
+            assertEquals(UserManager.USER_OPERATION_ERROR_MAX_USERS,
+                    UserManager.UserOperationException.from(e).getUserOperationResult());
+        } finally {
+            LocalServices.removeServiceForTest(DeviceStorageMonitorInternal.class);
+        }
+    }
+
     private void removeUsers() {
         List<UserInfo> users = mUserManagerService.getUsers(/* excludeDying */ false);
         for (UserInfo user: users) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 77376f0..2469cec 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -39,6 +39,7 @@
 import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Slog;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -69,8 +70,10 @@
 
     // Packages which are used during tests.
     private static final String[] PACKAGES = new String[] {
-            "com.android.egg"
+            "com.android.egg",
+            "com.google.android.webview"
     };
+    private static final String TAG = UserManagerTest.class.getSimpleName();
 
     private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
 
@@ -333,6 +336,9 @@
         assertThat(userInfo).isNotNull();
         final int userId = userInfo.id;
 
+        UserManager userManagerForUser = (UserManager) mContext.createPackageContextAsUser(
+                "android", 0, asHandle(userId)).getSystemService(Context.USER_SERVICE);
+
         assertThat(mUserManager.hasBadge(userId)).isEqualTo(userTypeDetails.hasBadge());
         assertThat(mUserManager.getUserIconBadgeResId(userId))
                 .isEqualTo(userTypeDetails.getIconBadge());
@@ -340,9 +346,11 @@
                 .isEqualTo(userTypeDetails.getBadgePlain());
         assertThat(mUserManager.getUserBadgeNoBackgroundResId(userId))
                 .isEqualTo(userTypeDetails.getBadgeNoBackground());
-        assertThat(mUserManager.isProfile(userId)).isEqualTo(userTypeDetails.isProfile());
         assertThat(mUserManager.isUserOfType(asHandle(userId), userTypeDetails.getName()))
                 .isTrue();
+        assertThat(userManagerForUser.isProfile()).isEqualTo(userTypeDetails.isProfile());
+        assertThat(userManagerForUser.isUserOfType(asHandle(userId), userTypeDetails.getName()))
+                .isTrue();
 
         final int badgeIndex = userInfo.profileBadge;
         assertThat(mUserManager.getUserBadgeColor(userId)).isEqualTo(
@@ -351,7 +359,7 @@
                 Resources.getSystem().getString(userTypeDetails.getBadgeLabel(badgeIndex), "Test"));
     }
 
-    // Make sure only one managed profile can be created
+    // Make sure only max managed profiles can be created
     @MediumTest
     @Test
     public void testAddManagedProfile() throws Exception {
@@ -384,6 +392,11 @@
                 UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId);
         // Verify that the packagesToVerify are installed by default.
         for (String pkg : PACKAGES) {
+            if (!mPackageManager.isPackageAvailable(pkg)) {
+                Slog.w(TAG, "Package is not available " + pkg);
+                continue;
+            }
+
             assertWithMessage("Package should be installed in managed profile: %s", pkg)
                     .that(isPackageInstalledForUser(pkg, userInfo1.id)).isTrue();
         }
@@ -393,6 +406,11 @@
                 UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId, PACKAGES);
         // Verify that the packagesToVerify are not installed by default.
         for (String pkg : PACKAGES) {
+            if (!mPackageManager.isPackageAvailable(pkg)) {
+                Slog.w(TAG, "Package is not available " + pkg);
+                continue;
+            }
+
             assertWithMessage(
                     "Package should not be installed in managed profile when disallowed: %s", pkg)
                             .that(isPackageInstalledForUser(pkg, userInfo2.id)).isFalse();
@@ -410,12 +428,22 @@
                 UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId, PACKAGES);
         // Verify that the packagesToVerify are not installed by default.
         for (String pkg : PACKAGES) {
+            if (!mPackageManager.isPackageAvailable(pkg)) {
+                Slog.w(TAG, "Package is not available " + pkg);
+                continue;
+            }
+
             assertWithMessage("Pkg should not be installed in managed profile when disallowed: %s",
                     pkg).that(isPackageInstalledForUser(pkg, userInfo.id)).isFalse();
         }
 
         // Verify that the disallowed packages during profile creation can be installed now.
         for (String pkg : PACKAGES) {
+            if (!mPackageManager.isPackageAvailable(pkg)) {
+                Slog.w(TAG, "Package is not available " + pkg);
+                continue;
+            }
+
             assertWithMessage("Package could not be installed: %s", pkg)
                     .that(mPackageManager.installExistingPackageAsUser(pkg, userInfo.id))
                     .isEqualTo(PackageManager.INSTALL_SUCCEEDED);
@@ -774,6 +802,78 @@
         assertThat(found).isTrue();
     }
 
+    @Test
+    public void testCreateProfile_withContextUserId() throws Exception {
+        final int primaryUserId = mUserManager.getPrimaryUser().id;
+
+        UserInfo userProfile = createProfileForUser("Managed 1",
+                UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId);
+        assertThat(userProfile).isNotNull();
+
+        UserManager um = (UserManager) mContext.createPackageContextAsUser(
+                "android", 0, mUserManager.getPrimaryUser().getUserHandle())
+                .getSystemService(Context.USER_SERVICE);
+
+        List<UserHandle> profiles = um.getUserProfiles(false);
+        assertThat(profiles.size()).isEqualTo(2);
+        assertThat(profiles.get(0).equals(userProfile.getUserHandle())
+                || profiles.get(1).equals(userProfile.getUserHandle())).isTrue();
+    }
+
+    @Test
+    public void testSetUserName_withContextUserId() throws Exception {
+        final int primaryUserId = mUserManager.getPrimaryUser().id;
+
+        UserInfo userInfo1 = createProfileForUser("Managed 1",
+                UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId);
+        assertThat(userInfo1).isNotNull();
+
+        UserManager um = (UserManager) mContext.createPackageContextAsUser(
+                "android", 0, userInfo1.getUserHandle())
+                .getSystemService(Context.USER_SERVICE);
+
+        final String newName = "Managed_user 1";
+        um.setUserName(newName);
+
+        UserInfo userInfo = mUserManager.getUserInfo(userInfo1.id);
+        assertThat(userInfo.name).isEqualTo(newName);
+
+        // get user name from getUserName using context.getUserId
+        assertThat(um.getUserName()).isEqualTo(newName);
+    }
+
+    @Test
+    public void testGetUserName_withContextUserId() throws Exception {
+        final String userName = "User 2";
+        UserInfo user2 = createUser(userName, 0);
+        assertThat(user2).isNotNull();
+
+        UserManager um = (UserManager) mContext.createPackageContextAsUser(
+                "android", 0, user2.getUserHandle())
+                .getSystemService(Context.USER_SERVICE);
+
+        assertThat(um.getUserName()).isEqualTo(userName);
+    }
+
+    @Test
+    public void testGetUserIcon_withContextUserId() throws Exception {
+        final int primaryUserId = mUserManager.getPrimaryUser().id;
+
+        UserInfo userInfo1 = createProfileForUser("Managed 1",
+                UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId);
+        assertThat(userInfo1).isNotNull();
+
+        UserManager um = (UserManager) mContext.createPackageContextAsUser(
+                "android", 0, userInfo1.getUserHandle())
+                .getSystemService(Context.USER_SERVICE);
+
+        final String newName = "Managed_user 1";
+        um.setUserName(newName);
+
+        UserInfo userInfo = mUserManager.getUserInfo(userInfo1.id);
+        assertThat(userInfo.name).isEqualTo(newName);
+    }
+
     private boolean isPackageInstalledForUser(String packageName, int userId) {
         try {
             return mPackageManager.getPackageInfoAsUser(packageName, 0, userId) != null;
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
index f08044c..f5e5e2a 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
@@ -160,6 +160,31 @@
     }
 
     @Test
+    public void testNotifyPrimaryAndSecondary() {
+        List<String> dexFiles = mFooUser0.getBaseAndSplitDexPaths();
+        List<String> secondaries = mFooUser0.getSecondaryDexPaths();
+        int baseAndSplitCount = dexFiles.size();
+        dexFiles.addAll(secondaries);
+
+        notifyDexLoad(mFooUser0, dexFiles, mUser0);
+
+        PackageUseInfo pui = getPackageUseInfo(mFooUser0);
+        assertIsUsedByOtherApps(mFooUser0, pui, false);
+        assertEquals(secondaries.size(), pui.getDexUseInfoMap().size());
+
+        String[] allExpectedContexts = DexoptUtils.processContextForDexLoad(
+                Arrays.asList(mFooUser0.mClassLoader),
+                Arrays.asList(String.join(File.pathSeparator, dexFiles)));
+        String[] secondaryExpectedContexts = Arrays.copyOfRange(allExpectedContexts,
+                baseAndSplitCount, dexFiles.size());
+
+        assertSecondaryUse(mFooUser0, pui, secondaries, /*isUsedByOtherApps*/false, mUser0,
+                secondaryExpectedContexts);
+
+        assertHasDclInfo(mFooUser0, mFooUser0, secondaries);
+    }
+
+    @Test
     public void testNotifySecondaryForeign() {
         // Foo loads bar secondary files.
         List<String> barSecondaries = mBarUser0.getSecondaryDexPaths();
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 13643a0..6eef41a 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -28,6 +28,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
@@ -754,4 +755,224 @@
         SystemClock.sleep(11);
         assertThat(mService.getWakefulness()).isEqualTo(WAKEFULNESS_ASLEEP);
     }
+
+    @Test
+    public void testBoot_ShouldBeAwake() throws Exception {
+        createService();
+        startSystem();
+
+        assertThat(mService.getWakefulness()).isEqualTo(WAKEFULNESS_AWAKE);
+        verify(mNotifierMock, never()).onWakefulnessChangeStarted(anyInt(), anyInt(), anyLong());
+    }
+
+    @Test
+    public void testBoot_DesiredScreenPolicyShouldBeBright() throws Exception {
+        createService();
+        startSystem();
+
+        assertThat(mService.getDesiredScreenPolicyLocked()).isEqualTo(
+                DisplayPowerRequest.POLICY_BRIGHT);
+    }
+
+    @Test
+    public void testQuiescentBoot_ShouldBeAsleep() throws Exception {
+        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
+        createService();
+        startSystem();
+
+        assertThat(mService.getWakefulness()).isEqualTo(WAKEFULNESS_ASLEEP);
+        verify(mNotifierMock).onWakefulnessChangeStarted(eq(WAKEFULNESS_ASLEEP), anyInt(),
+                anyLong());
+    }
+
+    @Test
+    public void testQuiescentBoot_DesiredScreenPolicyShouldBeOff() throws Exception {
+        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
+        createService();
+        assertThat(mService.getDesiredScreenPolicyLocked()).isEqualTo(
+                DisplayPowerRequest.POLICY_OFF);
+
+        startSystem();
+        assertThat(mService.getDesiredScreenPolicyLocked()).isEqualTo(
+                DisplayPowerRequest.POLICY_OFF);
+    }
+
+    @Test
+    public void testQuiescentBoot_WakeUp_DesiredScreenPolicyShouldBeBright() throws Exception {
+        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
+        createService();
+        startSystem();
+        forceAwake();
+        assertThat(mService.getDesiredScreenPolicyLocked()).isEqualTo(
+                DisplayPowerRequest.POLICY_BRIGHT);
+    }
+
+    @Test
+    public void testIsAmbientDisplayAvailable_available() throws Exception {
+        createService();
+        when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(true);
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplayAvailable()).isTrue();
+    }
+
+    @Test
+    public void testIsAmbientDisplayAvailable_unavailable() throws Exception {
+        createService();
+        when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(false);
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplayAvailable()).isFalse();
+    }
+
+    @Test
+    public void testSuppressAmbientDisplay_suppressed() throws Exception {
+        createService();
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test", true);
+
+        assertThat(Settings.Secure.getInt(mContextSpy.getContentResolver(),
+            Settings.Secure.SUPPRESS_DOZE)).isEqualTo(1);
+    }
+
+    @Test
+    public void testSuppressAmbientDisplay_multipleCallers_suppressed() throws Exception {
+        createService();
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test1", true);
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test2", false);
+
+        assertThat(Settings.Secure.getInt(mContextSpy.getContentResolver(),
+            Settings.Secure.SUPPRESS_DOZE)).isEqualTo(1);
+    }
+
+    @Test
+    public void testSuppressAmbientDisplay_suppressTwice_suppressed() throws Exception {
+        createService();
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test", true);
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test", true);
+
+        assertThat(Settings.Secure.getInt(mContextSpy.getContentResolver(),
+            Settings.Secure.SUPPRESS_DOZE)).isEqualTo(1);
+    }
+
+    @Test
+    public void testSuppressAmbientDisplay_suppressTwiceThenUnsuppress_notSuppressed()
+            throws Exception {
+        createService();
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test", true);
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test", true);
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test", false);
+
+        assertThat(Settings.Secure.getInt(mContextSpy.getContentResolver(),
+            Settings.Secure.SUPPRESS_DOZE)).isEqualTo(0);
+    }
+
+    @Test
+    public void testSuppressAmbientDisplay_notSuppressed() throws Exception {
+        createService();
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test", false);
+
+        assertThat(Settings.Secure.getInt(mContextSpy.getContentResolver(),
+            Settings.Secure.SUPPRESS_DOZE)).isEqualTo(0);
+    }
+
+    @Test
+    public void testSuppressAmbientDisplay_unsuppressTwice_notSuppressed() throws Exception {
+        createService();
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test", false);
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test", false);
+
+        assertThat(Settings.Secure.getInt(mContextSpy.getContentResolver(),
+            Settings.Secure.SUPPRESS_DOZE)).isEqualTo(0);
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressed_default_notSuppressed() throws Exception {
+        createService();
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressed()).isFalse();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressed_suppressed() throws Exception {
+        createService();
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test", true);
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressed()).isTrue();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressed_notSuppressed() throws Exception {
+        createService();
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test", false);
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressed()).isFalse();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressed_multipleTokens_suppressed() throws Exception {
+        createService();
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test1", false);
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test2", true);
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressed()).isTrue();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressed_multipleTokens_notSuppressed() throws Exception {
+        createService();
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test1", false);
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test2", false);
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressed()).isFalse();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressedForToken_default_notSuppressed() throws Exception {
+        createService();
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test"))
+            .isFalse();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressedForToken_suppressed() throws Exception {
+        createService();
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test", true);
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test"))
+            .isTrue();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressedForToken_notSuppressed() throws Exception {
+        createService();
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test", false);
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test"))
+            .isFalse();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressedForToken_multipleTokens_suppressed()
+            throws Exception {
+        createService();
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test1", true);
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test2", true);
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test1"))
+            .isTrue();
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test2"))
+            .isTrue();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressedForToken_multipleTokens_notSuppressed()
+            throws Exception {
+        createService();
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test1", true);
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test2", false);
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test1"))
+            .isTrue();
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test2"))
+            .isFalse();
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
index a83d940..f871203 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
@@ -98,7 +98,7 @@
             final int[] installedUsers) {
         return new PackageRollbackInfo(
                 new VersionedPackage(packageName, 2), new VersionedPackage(packageName, 1),
-                new IntArray(), new ArrayList<>(), false, IntArray.wrap(installedUsers),
+                new IntArray(), new ArrayList<>(), false, false, IntArray.wrap(installedUsers),
                 new SparseLongArray());
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
index 757a884..d0d2edc 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
@@ -87,13 +87,15 @@
             + "[{'versionRolledBackFrom':{'packageName':'blah','longVersionCode':55},"
             + "'versionRolledBackTo':{'packageName':'blah1','longVersionCode':50},'pendingBackups':"
             + "[59,1245,124544],'pendingRestores':[{'userId':498,'appId':32322,'seInfo':'wombles'},"
-            + "{'userId':-895,'appId':1,'seInfo':'pingu'}],'isApex':false,'installedUsers':"
+            + "{'userId':-895,'appId':1,'seInfo':'pingu'}],'isApex':false,'isApkInApex':false,"
+            + "'installedUsers':"
             + "[498468432,1111,98464],'ceSnapshotInodes':[{'userId':1,'ceSnapshotInode':-6},"
             + "{'userId':2222,'ceSnapshotInode':81641654445},{'userId':546546,"
             + "'ceSnapshotInode':345689375}]},{'versionRolledBackFrom':{'packageName':'chips',"
             + "'longVersionCode':28},'versionRolledBackTo':{'packageName':'com.chips.test',"
             + "'longVersionCode':48},'pendingBackups':[5],'pendingRestores':[{'userId':18,"
-            + "'appId':-12,'seInfo':''}],'isApex':false,'installedUsers':[55,79],"
+            + "'appId':-12,'seInfo':''}],'isApex':false,'isApkInApex':false,"
+            + "'installedUsers':[55,79],"
             + "'ceSnapshotInodes':[]}],'isStaged':false,'causePackages':[{'packageName':'hello',"
             + "'longVersionCode':23},{'packageName':'something','longVersionCode':999}],"
             + "'committedSessionId':45654465},'timestamp':'2019-10-01T12:29:08.855Z',"
@@ -155,7 +157,7 @@
         PackageRollbackInfo pkgInfo1 =
                 new PackageRollbackInfo(new VersionedPackage("com.made.up", 18),
                         new VersionedPackage("com.something.else", 5), new IntArray(),
-                        new ArrayList<>(), false, new IntArray(), new SparseLongArray());
+                        new ArrayList<>(), false, false, new IntArray(), new SparseLongArray());
         pkgInfo1.getPendingBackups().add(8);
         pkgInfo1.getPendingBackups().add(888);
         pkgInfo1.getPendingBackups().add(88885);
@@ -175,7 +177,7 @@
         PackageRollbackInfo pkgInfo2 = new PackageRollbackInfo(
                 new VersionedPackage("another.package", 2),
                 new VersionedPackage("com.test.ing", 48888), new IntArray(), new ArrayList<>(),
-                false, new IntArray(), new SparseLongArray());
+                false, false, new IntArray(), new SparseLongArray());
         pkgInfo2.getPendingBackups().add(57);
 
         pkgInfo2.getPendingRestores().add(
@@ -205,7 +207,7 @@
 
         PackageRollbackInfo pkgInfo1 = new PackageRollbackInfo(new VersionedPackage("blah", 55),
                 new VersionedPackage("blah1", 50), new IntArray(), new ArrayList<>(),
-                false, new IntArray(), new SparseLongArray());
+                false, false, new IntArray(), new SparseLongArray());
         pkgInfo1.getPendingBackups().add(59);
         pkgInfo1.getPendingBackups().add(1245);
         pkgInfo1.getPendingBackups().add(124544);
@@ -224,7 +226,7 @@
 
         PackageRollbackInfo pkgInfo2 = new PackageRollbackInfo(new VersionedPackage("chips", 28),
                 new VersionedPackage("com.chips.test", 48), new IntArray(), new ArrayList<>(),
-                false, new IntArray(), new SparseLongArray());
+                false, false, new IntArray(), new SparseLongArray());
         pkgInfo2.getPendingBackups().add(5);
 
         pkgInfo2.getPendingRestores().add(
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
index e368d63..164c883 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
@@ -295,7 +295,8 @@
             String packageName, long fromVersion, long toVersion, boolean isApex) {
         return new PackageRollbackInfo(new VersionedPackage(packageName, fromVersion),
                 new VersionedPackage(packageName, toVersion),
-                new IntArray(), new ArrayList<>(), isApex, new IntArray(), new SparseLongArray());
+                new IntArray(), new ArrayList<>(), isApex, false, new IntArray(),
+                new SparseLongArray());
     }
 
     private static class PackageRollbackInfoForPackage implements
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
index f8915c0..c56034a 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
@@ -37,6 +37,7 @@
 import android.hardware.soundtrigger.V2_3.OptionalModelParameterRange;
 import android.media.audio.common.AudioChannelMask;
 import android.media.audio.common.AudioFormat;
+import android.media.soundtrigger_middleware.AudioCapabilities;
 import android.media.soundtrigger_middleware.ConfidenceLevel;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
@@ -59,6 +60,7 @@
 import android.os.IHwBinder;
 import android.os.IHwInterface;
 import android.os.RemoteException;
+import android.util.Pair;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -155,7 +157,19 @@
         return properties;
     }
 
-    private static void validateDefaultProperties(SoundTriggerModuleProperties properties,
+    private static android.hardware.soundtrigger.V2_3.Properties createDefaultProperties_2_3(
+            boolean supportConcurrentCapture) {
+        android.hardware.soundtrigger.V2_3.Properties properties =
+                new android.hardware.soundtrigger.V2_3.Properties();
+        properties.base = createDefaultProperties(supportConcurrentCapture);
+        properties.supportedModelArch = "supportedModelArch";
+        properties.audioCapabilities =
+                android.hardware.soundtrigger.V2_3.AudioCapabilities.ECHO_CANCELLATION
+                        | android.hardware.soundtrigger.V2_3.AudioCapabilities.NOISE_SUPPRESSION;
+        return properties;
+    }
+
+    private void validateDefaultProperties(SoundTriggerModuleProperties properties,
             boolean supportConcurrentCapture) {
         assertEquals("implementor", properties.implementor);
         assertEquals("description", properties.description);
@@ -173,8 +187,23 @@
         assertEquals(supportConcurrentCapture, properties.concurrentCapture);
         assertTrue(properties.triggerInEvent);
         assertEquals(432, properties.powerConsumptionMw);
+
+        if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+            assertEquals("supportedModelArch", properties.supportedModelArch);
+            assertEquals(AudioCapabilities.ECHO_CANCELLATION | AudioCapabilities.NOISE_SUPPRESSION,
+                    properties.audioCapabilities);
+        } else {
+            assertEquals("", properties.supportedModelArch);
+            assertEquals(0, properties.audioCapabilities);
+        }
     }
 
+    private void verifyNotGetProperties() throws RemoteException {
+        if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+            verify((android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver,
+                    never()).getProperties(any());
+        }
+    }
 
     private static android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent createRecognitionEvent_2_0(
             int hwHandle,
@@ -290,15 +319,38 @@
                     properties);
             return null;
         }).when(mHalDriver).getProperties(any());
-        mService = new SoundTriggerMiddlewareImpl(mHalDriver, mAudioSessionProvider);
+
+        if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+            android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+                    (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+            doAnswer(invocation -> {
+                android.hardware.soundtrigger.V2_3.Properties properties =
+                        createDefaultProperties_2_3(
+                                supportConcurrentCapture);
+                ((android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getProperties_2_3Callback)
+                        invocation.getArgument(
+                        0)).onValues(0,
+                        properties);
+                return null;
+            }).when(driver).getProperties_2_3(any());
+        }
+
+        mService = new SoundTriggerMiddlewareImpl(() -> {
+            return mHalDriver;
+        }, mAudioSessionProvider);
     }
 
-    private int loadGenericModel_2_0(ISoundTriggerModule module, int hwHandle)
-            throws RemoteException {
+    private Pair<Integer, SoundTriggerHwCallback> loadGenericModel_2_0(ISoundTriggerModule module,
+            int hwHandle) throws RemoteException {
         SoundModel model = createGenericSoundModel();
         ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel> modelCaptor =
                 ArgumentCaptor.forClass(
                         android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel.class);
+        ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor =
+                ArgumentCaptor.forClass(
+                        android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class);
+        ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+
         doAnswer(invocation -> {
             android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback =
                     invocation.getArgument(1);
@@ -317,7 +369,8 @@
             modelEvent.model = hwHandle;
             callback.soundModelCallback(modelEvent, callbackCookie);
             return null;
-        }).when(mHalDriver).loadSoundModel(modelCaptor.capture(), any(), anyInt(), any());
+        }).when(mHalDriver).loadSoundModel(modelCaptor.capture(), callbackCaptor.capture(),
+                cookieCaptor.capture(), any());
 
         when(mAudioSessionProvider.acquireSession()).thenReturn(
                 new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
@@ -334,17 +387,23 @@
         assertEquals(model.vendorUuid, ConversionUtil.hidl2aidlUuid(hidlModel.vendorUuid));
         assertArrayEquals(new Byte[]{91, 92, 93, 94, 95}, hidlModel.data.toArray());
 
-        return handle;
+        return new Pair<>(handle,
+                new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()));
     }
 
-    private int loadGenericModel_2_1(ISoundTriggerModule module, int hwHandle)
-            throws RemoteException {
+    private Pair<Integer, SoundTriggerHwCallback> loadGenericModel_2_1(ISoundTriggerModule module,
+            int hwHandle) throws RemoteException {
         android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
                 (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
         SoundModel model = createGenericSoundModel();
         ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel> modelCaptor =
                 ArgumentCaptor.forClass(
                         android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel.class);
+        ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor =
+                ArgumentCaptor.forClass(
+                        android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class);
+        ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+
         doAnswer(invocation -> {
             android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback =
                     invocation.getArgument(1);
@@ -363,7 +422,8 @@
             modelEvent.header.model = hwHandle;
             callback.soundModelCallback_2_1(modelEvent, callbackCookie);
             return null;
-        }).when(driver).loadSoundModel_2_1(modelCaptor.capture(), any(), anyInt(), any());
+        }).when(driver).loadSoundModel_2_1(modelCaptor.capture(), callbackCaptor.capture(),
+                cookieCaptor.capture(), any());
 
         when(mAudioSessionProvider.acquireSession()).thenReturn(
                 new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
@@ -381,10 +441,12 @@
         assertArrayEquals(new byte[]{91, 92, 93, 94, 95},
                 HidlMemoryUtil.hidlMemoryToByteArray(hidlModel.data));
 
-        return handle;
+        return new Pair<>(handle,
+                new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()));
     }
 
-    private int loadGenericModel(ISoundTriggerModule module, int hwHandle) throws RemoteException {
+    private Pair<Integer, SoundTriggerHwCallback> loadGenericModel(ISoundTriggerModule module,
+            int hwHandle) throws RemoteException {
         if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
             return loadGenericModel_2_1(module, hwHandle);
         } else {
@@ -392,12 +454,17 @@
         }
     }
 
-    private int loadPhraseModel_2_0(ISoundTriggerModule module, int hwHandle)
-            throws RemoteException {
+    private Pair<Integer, SoundTriggerHwCallback> loadPhraseModel_2_0(ISoundTriggerModule module,
+            int hwHandle) throws RemoteException {
         PhraseSoundModel model = createPhraseSoundModel();
         ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel>
                 modelCaptor = ArgumentCaptor.forClass(
                 android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel.class);
+        ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor =
+                ArgumentCaptor.forClass(
+                        android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class);
+        ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+
         doAnswer(invocation -> {
             android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback =
                     invocation.getArgument(
@@ -419,7 +486,8 @@
             modelEvent.model = hwHandle;
             callback.soundModelCallback(modelEvent, callbackCookie);
             return null;
-        }).when(mHalDriver).loadPhraseSoundModel(modelCaptor.capture(), any(), anyInt(), any());
+        }).when(mHalDriver).loadPhraseSoundModel(modelCaptor.capture(), callbackCaptor.capture(),
+                cookieCaptor.capture(), any());
 
         when(mAudioSessionProvider.acquireSession()).thenReturn(
                 new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
@@ -451,11 +519,12 @@
                         | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION,
                 hidlPhrase.recognitionModes);
 
-        return handle;
+        return new Pair<>(handle,
+                new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()));
     }
 
-    private int loadPhraseModel_2_1(ISoundTriggerModule module, int hwHandle)
-            throws RemoteException {
+    private Pair<Integer, SoundTriggerHwCallback> loadPhraseModel_2_1(ISoundTriggerModule module,
+            int hwHandle) throws RemoteException {
         android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
                 (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
 
@@ -463,6 +532,11 @@
         ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel>
                 modelCaptor = ArgumentCaptor.forClass(
                 android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel.class);
+        ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor =
+                ArgumentCaptor.forClass(
+                        android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class);
+        ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+
         doAnswer(invocation -> {
             android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback =
                     invocation.getArgument(
@@ -484,7 +558,8 @@
             modelEvent.header.model = hwHandle;
             callback.soundModelCallback_2_1(modelEvent, callbackCookie);
             return null;
-        }).when(driver).loadPhraseSoundModel_2_1(modelCaptor.capture(), any(), anyInt(), any());
+        }).when(driver).loadPhraseSoundModel_2_1(modelCaptor.capture(), callbackCaptor.capture(),
+                cookieCaptor.capture(), any());
 
         when(mAudioSessionProvider.acquireSession()).thenReturn(
                 new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
@@ -517,10 +592,12 @@
                         | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION,
                 hidlPhrase.recognitionModes);
 
-        return handle;
+        return new Pair<>(handle,
+                new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()));
     }
 
-    private int loadPhraseModel(ISoundTriggerModule module, int hwHandle) throws RemoteException {
+    private Pair<Integer, SoundTriggerHwCallback> loadPhraseModel(
+            ISoundTriggerModule module, int hwHandle) throws RemoteException {
         if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
             return loadPhraseModel_2_1(module, hwHandle);
         } else {
@@ -535,18 +612,14 @@
         verify(mAudioSessionProvider).releaseSession(101);
     }
 
-    private SoundTriggerHwCallback startRecognition_2_0(ISoundTriggerModule module, int handle,
+    private void startRecognition_2_0(ISoundTriggerModule module, int handle,
             int hwHandle) throws RemoteException {
         ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig>
                 configCaptor = ArgumentCaptor.forClass(
                 android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig.class);
-        ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor =
-                ArgumentCaptor.forClass(
-                        android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class);
-        ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
 
-        when(mHalDriver.startRecognition(eq(hwHandle), configCaptor.capture(),
-                callbackCaptor.capture(), cookieCaptor.capture())).thenReturn(0);
+        when(mHalDriver.startRecognition(eq(hwHandle), configCaptor.capture(), any(), anyInt()))
+                .thenReturn(0);
 
         RecognitionConfig config = createRecognitionConfig();
 
@@ -569,11 +642,9 @@
         assertEquals(234, halLevel.userId);
         assertEquals(34, halLevel.levelPercent);
         assertArrayEquals(new Byte[]{5, 4, 3, 2, 1}, halConfig.data.toArray());
-
-        return new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue());
     }
 
-    private SoundTriggerHwCallback startRecognition_2_1(ISoundTriggerModule module, int handle,
+    private void startRecognition_2_1(ISoundTriggerModule module, int handle,
             int hwHandle) throws RemoteException {
         android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
                 (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
@@ -581,13 +652,9 @@
         ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig>
                 configCaptor = ArgumentCaptor.forClass(
                 android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig.class);
-        ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor =
-                ArgumentCaptor.forClass(
-                        android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class);
-        ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
 
-        when(driver.startRecognition_2_1(eq(hwHandle), configCaptor.capture(),
-                callbackCaptor.capture(), cookieCaptor.capture())).thenReturn(0);
+        when(driver.startRecognition_2_1(eq(hwHandle), configCaptor.capture(), any(), anyInt()))
+                .thenReturn(0);
 
         RecognitionConfig config = createRecognitionConfig();
 
@@ -611,16 +678,56 @@
         assertEquals(34, halLevel.levelPercent);
         assertArrayEquals(new byte[]{5, 4, 3, 2, 1},
                 HidlMemoryUtil.hidlMemoryToByteArray(halConfig.data));
-
-        return new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue());
     }
 
-    private SoundTriggerHwCallback startRecognition(ISoundTriggerModule module, int handle,
+    private void startRecognition_2_3(ISoundTriggerModule module, int handle,
             int hwHandle) throws RemoteException {
-        if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
-            return startRecognition_2_1(module, handle, hwHandle);
+        android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+                (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+
+        ArgumentCaptor<android.hardware.soundtrigger.V2_3.RecognitionConfig>
+                configCaptor = ArgumentCaptor.forClass(
+                android.hardware.soundtrigger.V2_3.RecognitionConfig.class);
+
+        when(driver.startRecognition_2_3(eq(hwHandle), configCaptor.capture())).thenReturn(0);
+
+        RecognitionConfig config = createRecognitionConfig();
+
+        module.startRecognition(handle, config);
+        verify(driver).startRecognition_2_3(eq(hwHandle), any());
+
+        android.hardware.soundtrigger.V2_3.RecognitionConfig halConfigExtended =
+                configCaptor.getValue();
+        android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig halConfig_2_1 =
+                halConfigExtended.base;
+
+        assertTrue(halConfig_2_1.header.captureRequested);
+        assertEquals(102, halConfig_2_1.header.captureHandle);
+        assertEquals(103, halConfig_2_1.header.captureDevice);
+        assertEquals(1, halConfig_2_1.header.phrases.size());
+        android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halPhraseExtra =
+                halConfig_2_1.header.phrases.get(0);
+        assertEquals(123, halPhraseExtra.id);
+        assertEquals(4, halPhraseExtra.confidenceLevel);
+        assertEquals(5, halPhraseExtra.recognitionModes);
+        assertEquals(1, halPhraseExtra.levels.size());
+        android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel = halPhraseExtra.levels.get(0);
+        assertEquals(234, halLevel.userId);
+        assertEquals(34, halLevel.levelPercent);
+        assertArrayEquals(new byte[]{5, 4, 3, 2, 1},
+                HidlMemoryUtil.hidlMemoryToByteArray(halConfig_2_1.data));
+        assertEquals(AudioCapabilities.ECHO_CANCELLATION
+                | AudioCapabilities.NOISE_SUPPRESSION, halConfigExtended.audioCapabilities);
+    }
+
+    private void startRecognition(ISoundTriggerModule module, int handle,
+            int hwHandle) throws RemoteException {
+        if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+            startRecognition_2_3(module, handle, hwHandle);
+        } else if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
+            startRecognition_2_1(module, handle, hwHandle);
         } else {
-            return startRecognition_2_0(module, handle, hwHandle);
+            startRecognition_2_0(module, handle, hwHandle);
         }
     }
 
@@ -635,6 +742,8 @@
         config.phraseRecognitionExtras[0].levels[0].userId = 234;
         config.phraseRecognitionExtras[0].levels[0].levelPercent = 34;
         config.data = new byte[]{5, 4, 3, 2, 1};
+        config.audioCapabilities = AudioCapabilities.ECHO_CANCELLATION
+                | AudioCapabilities.NOISE_SUPPRESSION;
         return config;
     }
 
@@ -650,6 +759,9 @@
         if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
             verify((android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver,
                     never()).startRecognition_2_1(anyInt(), any(), any(), anyInt());
+        } else if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+            verify((android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver,
+                    never()).startRecognition_2_3(anyInt(), any());
         }
     }
 
@@ -688,12 +800,12 @@
 
             @Override
             public boolean linkToDeath(DeathRecipient recipient, long cookie) {
-                throw new UnsupportedOperationException();
+                return true;
             }
 
             @Override
             public boolean unlinkToDeath(DeathRecipient recipient) {
-                throw new UnsupportedOperationException();
+                return true;
             }
         };
 
@@ -716,6 +828,7 @@
         SoundTriggerModuleProperties properties = allDescriptors[0].properties;
 
         validateDefaultProperties(properties, true);
+        verifyNotGetProperties();
     }
 
     @Test
@@ -760,7 +873,7 @@
         ISoundTriggerModule module = mService.attach(0, callback);
 
         final int hwHandle = 7;
-        int handle = loadGenericModel(module, hwHandle);
+        int handle = loadGenericModel(module, hwHandle).first;
         unloadModel(module, handle, hwHandle);
         module.detach();
     }
@@ -772,7 +885,7 @@
         ISoundTriggerModule module = mService.attach(0, callback);
 
         final int hwHandle = 73;
-        int handle = loadPhraseModel(module, hwHandle);
+        int handle = loadPhraseModel(module, hwHandle).first;
         unloadModel(module, handle, hwHandle);
         module.detach();
     }
@@ -785,7 +898,7 @@
 
         // Load the model.
         final int hwHandle = 7;
-        int handle = loadGenericModel(module, hwHandle);
+        int handle = loadGenericModel(module, hwHandle).first;
 
         // Initiate a recognition.
         startRecognition(module, handle, hwHandle);
@@ -806,7 +919,7 @@
 
         // Load the model.
         final int hwHandle = 67;
-        int handle = loadPhraseModel(module, hwHandle);
+        int handle = loadPhraseModel(module, hwHandle).first;
 
         // Initiate a recognition.
         startRecognition(module, handle, hwHandle);
@@ -827,10 +940,12 @@
 
         // Load the model.
         final int hwHandle = 7;
-        int handle = loadGenericModel(module, hwHandle);
+        Pair<Integer, SoundTriggerHwCallback> modelHandles = loadGenericModel(module, hwHandle);
+        int handle = modelHandles.first;
+        SoundTriggerHwCallback hwCallback = modelHandles.second;
 
         // Initiate a recognition.
-        SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+        startRecognition(module, handle, hwHandle);
 
         // Signal a capture from the driver.
         hwCallback.sendRecognitionEvent(hwHandle,
@@ -856,10 +971,12 @@
 
         // Load the model.
         final int hwHandle = 7;
-        int handle = loadPhraseModel(module, hwHandle);
+        Pair<Integer, SoundTriggerHwCallback> modelHandles = loadPhraseModel(module, hwHandle);
+        int handle = modelHandles.first;
+        SoundTriggerHwCallback hwCallback = modelHandles.second;
 
         // Initiate a recognition.
-        SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+        startRecognition(module, handle, hwHandle);
 
         // Signal a capture from the driver.
         hwCallback.sendPhraseRecognitionEvent(hwHandle,
@@ -892,10 +1009,12 @@
 
         // Load the model.
         final int hwHandle = 17;
-        int handle = loadGenericModel(module, hwHandle);
+        Pair<Integer, SoundTriggerHwCallback> modelHandles = loadGenericModel(module, hwHandle);
+        int handle = modelHandles.first;
+        SoundTriggerHwCallback hwCallback = modelHandles.second;
 
         // Initiate a recognition.
-        SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+        startRecognition(module, handle, hwHandle);
 
         // Force a trigger.
         module.forceRecognitionEvent(handle);
@@ -935,10 +1054,12 @@
 
         // Load the model.
         final int hwHandle = 17;
-        int handle = loadPhraseModel(module, hwHandle);
+        Pair<Integer, SoundTriggerHwCallback> modelHandles = loadPhraseModel(module, hwHandle);
+        int handle = modelHandles.first;
+        SoundTriggerHwCallback hwCallback = modelHandles.second;
 
         // Initiate a recognition.
-        SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+        startRecognition(module, handle, hwHandle);
 
         // Force a trigger.
         module.forceRecognitionEvent(handle);
@@ -975,7 +1096,7 @@
 
         // Load the model.
         final int hwHandle = 11;
-        int handle = loadGenericModel(module, hwHandle);
+        int handle = loadGenericModel(module, hwHandle).first;
 
         // Initiate a recognition.
         startRecognition(module, handle, hwHandle);
@@ -1023,7 +1144,7 @@
 
         // Load the model.
         final int hwHandle = 11;
-        int handle = loadPhraseModel(module, hwHandle);
+        int handle = loadPhraseModel(module, hwHandle).first;
 
         // Initiate a recognition.
         startRecognition(module, handle, hwHandle);
@@ -1071,7 +1192,7 @@
 
         // Load the model.
         final int hwHandle = 13;
-        int handle = loadGenericModel(module, hwHandle);
+        int handle = loadGenericModel(module, hwHandle).first;
 
         // Initiate a recognition.
         startRecognition(module, handle, hwHandle);
@@ -1107,7 +1228,7 @@
 
         // Load the model.
         final int hwHandle = 13;
-        int handle = loadPhraseModel(module, hwHandle);
+        int handle = loadPhraseModel(module, hwHandle).first;
 
         // Initiate a recognition.
         startRecognition(module, handle, hwHandle);
@@ -1144,7 +1265,7 @@
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
         final int hwHandle = 12;
-        int modelHandle = loadGenericModel(module, hwHandle);
+        int modelHandle = loadGenericModel(module, hwHandle).first;
 
         doAnswer((Answer<Void>) invocation -> {
             android.hardware.soundtrigger.V2_3.ISoundTriggerHw.queryParameterCallback
@@ -1180,7 +1301,7 @@
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
         final int hwHandle = 13;
-        int modelHandle = loadGenericModel(module, hwHandle);
+        int modelHandle = loadGenericModel(module, hwHandle).first;
 
         ModelParameterRange range = module.queryModelParameterSupport(modelHandle,
                 ModelParameter.THRESHOLD_FACTOR);
@@ -1201,7 +1322,7 @@
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
         final int hwHandle = 13;
-        int modelHandle = loadGenericModel(module, hwHandle);
+        int modelHandle = loadGenericModel(module, hwHandle).first;
 
         doAnswer(invocation -> {
             android.hardware.soundtrigger.V2_3.ISoundTriggerHw.queryParameterCallback
@@ -1234,7 +1355,7 @@
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
         final int hwHandle = 14;
-        int modelHandle = loadGenericModel(module, hwHandle);
+        int modelHandle = loadGenericModel(module, hwHandle).first;
 
         doAnswer(invocation -> {
             android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getParameterCallback
@@ -1266,7 +1387,7 @@
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
         final int hwHandle = 17;
-        int modelHandle = loadGenericModel(module, hwHandle);
+        int modelHandle = loadGenericModel(module, hwHandle).first;
 
         when(driver.setParameter(hwHandle,
                 android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR,
diff --git a/services/tests/servicestests/src/com/android/server/stats/IonMemoryUtilTest.java b/services/tests/servicestests/src/com/android/server/stats/IonMemoryUtilTest.java
deleted file mode 100644
index 8cbf8e5..0000000
--- a/services/tests/servicestests/src/com/android/server/stats/IonMemoryUtilTest.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.stats;
-
-import static com.android.server.stats.IonMemoryUtil.parseIonHeapSizeFromDebugfs;
-import static com.android.server.stats.IonMemoryUtil.parseProcessIonHeapSizesFromDebugfs;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.server.stats.IonMemoryUtil.IonAllocations;
-
-import org.junit.Test;
-
-/**
- * Build/Install/Run:
- *  atest FrameworksServicesTests:IonMemoryUtilTest
- */
-@SmallTest
-public class IonMemoryUtilTest {
-    // Repeated lines have been removed.
-    private static final String DEBUG_SYSTEM_ION_HEAP_CONTENTS = String.join(
-            "\n",
-            "          client              pid             size",
-            "----------------------------------------------------",
-            " audio@2.0-servi              765             4096",
-            " audio@2.0-servi              765            61440",
-            " audio@2.0-servi              765             4096",
-            "     voip_client               96             8192",
-            "     voip_client               96             4096",
-            "   system_server             1232         16728064",
-            "  surfaceflinger              611         50642944",
-            "----------------------------------------------------",
-            "orphaned allocations (info is from last known client):",
-            "----------------------------------------------------",
-            "  total orphaned                0",
-            "          total          55193600",
-            "   deferred free                0",
-            "----------------------------------------------------",
-            "0 order 4 highmem pages in uncached pool = 0 total",
-            "0 order 4 lowmem pages in uncached pool = 0 total",
-            "1251 order 4 lowmem pages in cached pool = 81985536 total",
-            "VMID 8: 0 order 4 highmem pages in secure pool = 0 total",
-            "VMID  8: 0 order 4 lowmem pages in secure pool = 0 total",
-            "--------------------------------------------",
-            "uncached pool = 4096 cached pool = 83566592 secure pool = 0",
-            "pool total (uncached + cached + secure) = 83570688",
-            "--------------------------------------------");
-
-    // Repeated lines have been removed.
-    private static final String DEBUG_SYSTEM_ION_HEAP_CONTENTS_SARGO = String.join(
-            "\n",
-            "          client              pid             size      page counts"
-                    + "--------------------------------------------------       4K       8K      "
-                    + "16K      32K      64K     128K     256K     512K       1M       2M       "
-                    + "4M      >=8M",
-            "   system_server             1705         58097664    13120      532        "
-                    + "0        0        0        0        0        0        0        0        "
-                    + "0        0M",
-            " audio@2.0-servi              851            16384        0        2        0        "
-                    + "0        0        0        0        0        0        0        "
-                    + "0        0M",
-            " audio@2.0-servi              851             4096        1        0        0       "
-                    + " 0        0        0        0        0        0        0        0        "
-                    + "0M",
-            " audio@2.0-servi              851             4096        1        0      "
-                    + "  0        0        0        0        0        0        0        0        "
-                    + "0        0M",
-            "----------------------------------------------------",
-            "orphaned allocations (info is from last known client):",
-            "----------------------------------------------------",
-            "  total orphaned                0",
-            "          total         159928320",
-            "   deferred free                0",
-            "----------------------------------------------------",
-            "0 order 4 highmem pages in uncached pool = 0 total",
-            "0 order 4 lowmem pages in uncached pool = 0 total",
-            "1251 order 4 lowmem pages in cached pool = 81985536 total",
-            "VMID 8: 0 order 4 highmem pages in secure pool = 0 total",
-            "VMID  8: 0 order 4 lowmem pages in secure pool = 0 total",
-            "--------------------------------------------",
-            "uncached pool = 4096 cached pool = 83566592 secure pool = 0",
-            "pool total (uncached + cached + secure) = 83570688",
-            "--------------------------------------------");
-
-    @Test
-    public void testParseIonHeapSizeFromDebugfs_emptyContents() {
-        assertThat(parseIonHeapSizeFromDebugfs("")).isEqualTo(0);
-    }
-
-    @Test
-    public void testParseIonHeapSizeFromDebugfs_invalidValue() {
-        assertThat(parseIonHeapSizeFromDebugfs("<<no-value>>")).isEqualTo(0);
-
-        assertThat(parseIonHeapSizeFromDebugfs("\ntotal 12345678901234567890\n")).isEqualTo(0);
-    }
-
-    @Test
-    public void testParseIonHeapSizeFromDebugfs_correctValue() {
-        assertThat(parseIonHeapSizeFromDebugfs(DEBUG_SYSTEM_ION_HEAP_CONTENTS))
-                .isEqualTo(55193600);
-
-        assertThat(parseIonHeapSizeFromDebugfs(DEBUG_SYSTEM_ION_HEAP_CONTENTS_SARGO))
-                .isEqualTo(159928320);
-    }
-
-    @Test
-    public void testParseProcessIonHeapSizesFromDebugfs_emptyContents() {
-        assertThat(parseProcessIonHeapSizesFromDebugfs("")).hasSize(0);
-    }
-
-    @Test
-    public void testParseProcessIonHeapSizesFromDebugfs_invalidValue() {
-        assertThat(parseProcessIonHeapSizesFromDebugfs("<<no-value>>").size()).isEqualTo(0);
-    }
-
-    @Test
-    public void testParseProcessIonHeapSizesFromDebugfs_correctValue1() {
-        assertThat(parseProcessIonHeapSizesFromDebugfs(DEBUG_SYSTEM_ION_HEAP_CONTENTS))
-                .containsExactly(
-                        createIonAllocations(765, 61440 + 4096 + 4096, 3, 61440),
-                        createIonAllocations(96, 8192 + 4096, 2, 8192),
-                        createIonAllocations(1232, 16728064, 1, 16728064),
-                        createIonAllocations(611, 50642944, 1, 50642944));
-    }
-
-    @Test
-    public void testParseProcessIonHeapSizesFromDebugfs_correctValue2() {
-        assertThat(parseProcessIonHeapSizesFromDebugfs(DEBUG_SYSTEM_ION_HEAP_CONTENTS_SARGO))
-                .containsExactly(
-                        createIonAllocations(1705, 58097664, 1, 58097664),
-                        createIonAllocations(851, 16384 + 4096 + 4096, 3, 16384));
-    }
-
-    private static IonAllocations createIonAllocations(int pid, long totalSizeInBytes, int count,
-            long maxSizeInBytes) {
-        IonAllocations allocations = new IonAllocations();
-        allocations.pid = pid;
-        allocations.totalSizeInBytes = totalSizeInBytes;
-        allocations.count = count;
-        allocations.maxSizeInBytes = maxSizeInBytes;
-        return allocations;
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/stats/pull/IonMemoryUtilTest.java b/services/tests/servicestests/src/com/android/server/stats/pull/IonMemoryUtilTest.java
new file mode 100644
index 0000000..d4d4b4d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/stats/pull/IonMemoryUtilTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.stats.pull;
+
+import static com.android.server.stats.pull.IonMemoryUtil.parseIonHeapSizeFromDebugfs;
+import static com.android.server.stats.pull.IonMemoryUtil.parseProcessIonHeapSizesFromDebugfs;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.stats.pull.IonMemoryUtil.IonAllocations;
+
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:IonMemoryUtilTest
+ */
+@SmallTest
+public class IonMemoryUtilTest {
+    // Repeated lines have been removed.
+    private static final String DEBUG_SYSTEM_ION_HEAP_CONTENTS = String.join(
+            "\n",
+            "          client              pid             size",
+            "----------------------------------------------------",
+            " audio@2.0-servi              765             4096",
+            " audio@2.0-servi              765            61440",
+            " audio@2.0-servi              765             4096",
+            "     voip_client               96             8192",
+            "     voip_client               96             4096",
+            "   system_server             1232         16728064",
+            "  surfaceflinger              611         50642944",
+            "----------------------------------------------------",
+            "orphaned allocations (info is from last known client):",
+            "----------------------------------------------------",
+            "  total orphaned                0",
+            "          total          55193600",
+            "   deferred free                0",
+            "----------------------------------------------------",
+            "0 order 4 highmem pages in uncached pool = 0 total",
+            "0 order 4 lowmem pages in uncached pool = 0 total",
+            "1251 order 4 lowmem pages in cached pool = 81985536 total",
+            "VMID 8: 0 order 4 highmem pages in secure pool = 0 total",
+            "VMID  8: 0 order 4 lowmem pages in secure pool = 0 total",
+            "--------------------------------------------",
+            "uncached pool = 4096 cached pool = 83566592 secure pool = 0",
+            "pool total (uncached + cached + secure) = 83570688",
+            "--------------------------------------------");
+
+    // Repeated lines have been removed.
+    private static final String DEBUG_SYSTEM_ION_HEAP_CONTENTS_SARGO = String.join(
+            "\n",
+            "          client              pid             size      page counts"
+                    + "--------------------------------------------------       4K       8K      "
+                    + "16K      32K      64K     128K     256K     512K       1M       2M       "
+                    + "4M      >=8M",
+            "   system_server             1705         58097664    13120      532        "
+                    + "0        0        0        0        0        0        0        0        "
+                    + "0        0M",
+            " audio@2.0-servi              851            16384        0        2        0        "
+                    + "0        0        0        0        0        0        0        "
+                    + "0        0M",
+            " audio@2.0-servi              851             4096        1        0        0       "
+                    + " 0        0        0        0        0        0        0        0        "
+                    + "0M",
+            " audio@2.0-servi              851             4096        1        0      "
+                    + "  0        0        0        0        0        0        0        0        "
+                    + "0        0M",
+            "----------------------------------------------------",
+            "orphaned allocations (info is from last known client):",
+            "----------------------------------------------------",
+            "  total orphaned                0",
+            "          total         159928320",
+            "   deferred free                0",
+            "----------------------------------------------------",
+            "0 order 4 highmem pages in uncached pool = 0 total",
+            "0 order 4 lowmem pages in uncached pool = 0 total",
+            "1251 order 4 lowmem pages in cached pool = 81985536 total",
+            "VMID 8: 0 order 4 highmem pages in secure pool = 0 total",
+            "VMID  8: 0 order 4 lowmem pages in secure pool = 0 total",
+            "--------------------------------------------",
+            "uncached pool = 4096 cached pool = 83566592 secure pool = 0",
+            "pool total (uncached + cached + secure) = 83570688",
+            "--------------------------------------------");
+
+    @Test
+    public void testParseIonHeapSizeFromDebugfs_emptyContents() {
+        assertThat(parseIonHeapSizeFromDebugfs("")).isEqualTo(0);
+    }
+
+    @Test
+    public void testParseIonHeapSizeFromDebugfs_invalidValue() {
+        assertThat(parseIonHeapSizeFromDebugfs("<<no-value>>")).isEqualTo(0);
+
+        assertThat(parseIonHeapSizeFromDebugfs("\ntotal 12345678901234567890\n")).isEqualTo(0);
+    }
+
+    @Test
+    public void testParseIonHeapSizeFromDebugfs_correctValue() {
+        assertThat(parseIonHeapSizeFromDebugfs(DEBUG_SYSTEM_ION_HEAP_CONTENTS))
+                .isEqualTo(55193600);
+
+        assertThat(parseIonHeapSizeFromDebugfs(DEBUG_SYSTEM_ION_HEAP_CONTENTS_SARGO))
+                .isEqualTo(159928320);
+    }
+
+    @Test
+    public void testParseProcessIonHeapSizesFromDebugfs_emptyContents() {
+        assertThat(parseProcessIonHeapSizesFromDebugfs("")).hasSize(0);
+    }
+
+    @Test
+    public void testParseProcessIonHeapSizesFromDebugfs_invalidValue() {
+        assertThat(parseProcessIonHeapSizesFromDebugfs("<<no-value>>").size()).isEqualTo(0);
+    }
+
+    @Test
+    public void testParseProcessIonHeapSizesFromDebugfs_correctValue1() {
+        assertThat(parseProcessIonHeapSizesFromDebugfs(DEBUG_SYSTEM_ION_HEAP_CONTENTS))
+                .containsExactly(
+                        createIonAllocations(765, 61440 + 4096 + 4096, 3, 61440),
+                        createIonAllocations(96, 8192 + 4096, 2, 8192),
+                        createIonAllocations(1232, 16728064, 1, 16728064),
+                        createIonAllocations(611, 50642944, 1, 50642944));
+    }
+
+    @Test
+    public void testParseProcessIonHeapSizesFromDebugfs_correctValue2() {
+        assertThat(parseProcessIonHeapSizesFromDebugfs(DEBUG_SYSTEM_ION_HEAP_CONTENTS_SARGO))
+                .containsExactly(
+                        createIonAllocations(1705, 58097664, 1, 58097664),
+                        createIonAllocations(851, 16384 + 4096 + 4096, 3, 16384));
+    }
+
+    private static IonAllocations createIonAllocations(int pid, long totalSizeInBytes, int count,
+            long maxSizeInBytes) {
+        IonAllocations allocations = new IonAllocations();
+        allocations.pid = pid;
+        allocations.totalSizeInBytes = totalSizeInBytes;
+        allocations.count = count;
+        allocations.maxSizeInBytes = maxSizeInBytes;
+        return allocations;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index aaf9799..d940a6a 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -18,7 +18,6 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -26,7 +25,6 @@
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
 import android.app.timedetector.PhoneTimeSuggestion;
-import android.content.Intent;
 import android.icu.util.Calendar;
 import android.icu.util.GregorianCalendar;
 import android.icu.util.TimeZone;
@@ -78,8 +76,7 @@
 
         long expectedSystemClockMillis =
                 mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime());
-        mScript.verifySystemClockWasSetAndResetCallTracking(
-                expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+        mScript.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
                 .assertLatestPhoneSuggestion(phoneId, timeSuggestion);
     }
 
@@ -118,8 +115,7 @@
                     mScript.calculateTimeInMillisForNow(timeSuggestion1.getUtcTime());
 
             mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
-                    .verifySystemClockWasSetAndResetCallTracking(
-                            expectedSystemClockMillis1, true /* expectNetworkBroadcast */)
+                    .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1)
                     .assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
         }
 
@@ -146,8 +142,7 @@
                     mScript.calculateTimeInMillisForNow(timeSuggestion3.getUtcTime());
 
             mScript.simulatePhoneTimeSuggestion(timeSuggestion3)
-                    .verifySystemClockWasSetAndResetCallTracking(
-                            expectedSystemClockMillis3, true /* expectNetworkBroadcast */)
+                    .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis3)
                     .assertLatestPhoneSuggestion(phoneId, timeSuggestion3);
         }
     }
@@ -175,8 +170,7 @@
                     mScript.calculateTimeInMillisForNow(phone2TimeSuggestion.getUtcTime());
 
             mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion)
-                    .verifySystemClockWasSetAndResetCallTracking(
-                            expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+                    .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
                     .assertLatestPhoneSuggestion(phone1Id, null)
                     .assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion);
         }
@@ -193,8 +187,7 @@
                     mScript.calculateTimeInMillisForNow(phone1TimeSuggestion.getUtcTime());
 
             mScript.simulatePhoneTimeSuggestion(phone1TimeSuggestion)
-                    .verifySystemClockWasSetAndResetCallTracking(
-                            expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+                    .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
                     .assertLatestPhoneSuggestion(phone1Id, phone1TimeSuggestion);
 
         }
@@ -227,8 +220,7 @@
                     mScript.calculateTimeInMillisForNow(phone2TimeSuggestion.getUtcTime());
 
             mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion)
-                    .verifySystemClockWasSetAndResetCallTracking(
-                            expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+                    .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
                     .assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion);
         }
     }
@@ -265,8 +257,7 @@
         mScript.simulateTimePassing();
         long expectedSystemClockMillis1 = mScript.calculateTimeInMillisForNow(utcTime1);
         mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectedSystemClockMillis1, true /* expectNetworkBroadcast */)
+                .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1)
                 .assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
 
         // The UTC time increment should be larger than the system clock update threshold so we
@@ -304,8 +295,7 @@
         PhoneTimeSuggestion timeSuggestion4 =
                 createPhoneTimeSuggestion(phoneId, utcTime4);
         mScript.simulatePhoneTimeSuggestion(timeSuggestion4)
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectedSystemClockMillis4, true /* expectNetworkBroadcast */)
+                .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis4)
                 .assertLatestPhoneSuggestion(phoneId, timeSuggestion4);
     }
 
@@ -339,8 +329,7 @@
 
         // Turn on auto time detection.
         mScript.simulateAutoTimeDetectionToggle()
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectedSystemClockMillis1, true /* expectNetworkBroadcast */)
+                .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1)
                 .assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
 
         // Turn off auto time detection.
@@ -367,8 +356,7 @@
 
         // Turn on auto time detection.
         mScript.simulateAutoTimeDetectionToggle()
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectedSystemClockMillis2, true /* expectNetworkBroadcast */)
+                .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis2)
                 .assertLatestPhoneSuggestion(phoneId, timeSuggestion2);
     }
 
@@ -388,7 +376,7 @@
                 mScript.calculateTimeInMillisForNow(phoneSuggestion.getUtcTime());
         mScript.simulatePhoneTimeSuggestion(phoneSuggestion)
                 .verifySystemClockWasSetAndResetCallTracking(
-                        expectedSystemClockMillis, true /* expectedNetworkBroadcast */)
+                        expectedSystemClockMillis  /* expectedNetworkBroadcast */)
                 .assertLatestPhoneSuggestion(phoneId, phoneSuggestion);
 
         // Look inside and check what the strategy considers the current best phone suggestion.
@@ -416,8 +404,7 @@
         long expectedSystemClockMillis =
                 mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime());
         mScript.simulateManualTimeSuggestion(timeSuggestion)
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectedSystemClockMillis, false /* expectNetworkBroadcast */);
+                .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis);
     }
 
     @Test
@@ -439,8 +426,7 @@
         long expectedAutoClockMillis =
                 mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime());
         mScript.simulatePhoneTimeSuggestion(phoneTimeSuggestion)
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectedAutoClockMillis, true /* expectNetworkBroadcast */)
+                .verifySystemClockWasSetAndResetCallTracking(expectedAutoClockMillis)
                 .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
 
         // Simulate the passage of time.
@@ -463,8 +449,7 @@
         long expectedManualClockMillis =
                 mScript.calculateTimeInMillisForNow(manualTimeSuggestion.getUtcTime());
         mScript.simulateManualTimeSuggestion(manualTimeSuggestion)
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectedManualClockMillis, false /* expectNetworkBroadcast */)
+                .verifySystemClockWasSetAndResetCallTracking(expectedManualClockMillis)
                 .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
 
         // Simulate the passage of time.
@@ -475,8 +460,7 @@
 
         expectedAutoClockMillis =
                 mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime());
-        mScript.verifySystemClockWasSetAndResetCallTracking(
-                        expectedAutoClockMillis, true /* expectNetworkBroadcast */)
+        mScript.verifySystemClockWasSetAndResetCallTracking(expectedAutoClockMillis)
                 .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
 
         // Switch back to manual - nothing should happen to the clock.
@@ -514,8 +498,7 @@
         long expectedSystemClockMillis =
                 mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime());
         mScript.simulateNetworkTimeSuggestion(timeSuggestion)
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectedSystemClockMillis, false /* expectNetworkBroadcast */);
+                .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis);
     }
 
     @Test
@@ -550,8 +533,7 @@
         mScript.simulateTimePassing(smallTimeIncrementMillis)
                 .simulateNetworkTimeSuggestion(networkTimeSuggestion1)
                 .verifySystemClockWasSetAndResetCallTracking(
-                        mScript.calculateTimeInMillisForNow(networkTimeSuggestion1.getUtcTime()),
-                        false /* expectNetworkBroadcast */);
+                        mScript.calculateTimeInMillisForNow(networkTimeSuggestion1.getUtcTime()));
 
         // Check internal state.
         mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, null)
@@ -570,8 +552,7 @@
         mScript.simulateTimePassing(smallTimeIncrementMillis)
                 .simulatePhoneTimeSuggestion(phoneTimeSuggestion)
                 .verifySystemClockWasSetAndResetCallTracking(
-                        mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime()),
-                        true /* expectNetworkBroadcast */);
+                        mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime()));
 
         // Check internal state.
         mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion)
@@ -622,8 +603,7 @@
 
         // Verify the latest network time now wins.
         mScript.verifySystemClockWasSetAndResetCallTracking(
-                mScript.calculateTimeInMillisForNow(networkTimeSuggestion2.getUtcTime()),
-                false /* expectNetworkTimeBroadcast */);
+                mScript.calculateTimeInMillisForNow(networkTimeSuggestion2.getUtcTime()));
 
         // Check internal state.
         mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion)
@@ -645,7 +625,6 @@
 
         // Tracking operations.
         private boolean mSystemClockWasSet;
-        private Intent mBroadcastSent;
 
         @Override
         public int systemClockUpdateThresholdMillis() {
@@ -672,7 +651,6 @@
 
         @Override
         public long systemClockMillis() {
-            assertWakeLockAcquired();
             return mSystemClockMillis;
         }
 
@@ -689,12 +667,6 @@
             mWakeLockAcquired = false;
         }
 
-        @Override
-        public void sendStickyBroadcast(Intent intent) {
-            assertNotNull(intent);
-            mBroadcastSent = intent;
-        }
-
         // Methods below are for managing the fake's behavior.
 
         void pokeSystemClockUpdateThreshold(int thresholdMillis) {
@@ -739,17 +711,8 @@
             assertEquals(expectedSystemClockMillis, mSystemClockMillis);
         }
 
-        void verifyIntentWasBroadcast() {
-            assertTrue(mBroadcastSent != null);
-        }
-
-        void verifyIntentWasNotBroadcast() {
-            assertNull(mBroadcastSent);
-        }
-
         void resetCallTracking() {
             mSystemClockWasSet = false;
-            mBroadcastSent = null;
         }
 
         private void assertWakeLockAcquired() {
@@ -832,17 +795,12 @@
 
         Script verifySystemClockWasNotSetAndResetCallTracking() {
             mFakeCallback.verifySystemClockNotSet();
-            mFakeCallback.verifyIntentWasNotBroadcast();
             mFakeCallback.resetCallTracking();
             return this;
         }
 
-        Script verifySystemClockWasSetAndResetCallTracking(
-                long expectedSystemClockMillis, boolean expectNetworkBroadcast) {
+        Script verifySystemClockWasSetAndResetCallTracking(long expectedSystemClockMillis) {
             mFakeCallback.verifySystemClockWasSet(expectedSystemClockMillis);
-            if (expectNetworkBroadcast) {
-                mFakeCallback.verifyIntentWasBroadcast();
-            }
             mFakeCallback.resetCallTracking();
             return this;
         }
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java
index 4a13dce..7af3ec6 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java
@@ -16,18 +16,18 @@
 
 package com.android.server.usage;
 
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER;
 import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT;
 import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_RESTRICT_BACKGROUND_RESOURCE_USAGE;
 import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_MOVE_TO_FOREGROUND;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
 import android.os.FileUtils;
 import android.test.AndroidTestCase;
 
@@ -37,10 +37,12 @@
 
     File mStorageDir;
 
-    final static String PACKAGE_1 = "com.android.testpackage1";
-    final static String PACKAGE_2 = "com.android.testpackage2";
+    private static final String PACKAGE_1 = "com.android.testpackage1";
+    private static final String PACKAGE_2 = "com.android.testpackage2";
+    private static final String PACKAGE_3 = "com.android.testpackage3";
+    private static final String PACKAGE_4 = "com.android.testpackage4";
 
-    final static int USER_ID = 0;
+    private static final int USER_ID = 0;
 
     @Override
     protected void setUp() throws Exception {
@@ -100,16 +102,27 @@
 
         aih.setAppStandbyBucket(PACKAGE_2, USER_ID, 2000, STANDBY_BUCKET_ACTIVE,
                 REASON_MAIN_USAGE);
+        aih.setAppStandbyBucket(PACKAGE_3, USER_ID, 2500, STANDBY_BUCKET_RESTRICTED,
+                REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_RESTRICT_BACKGROUND_RESOURCE_USAGE);
+        aih.setAppStandbyBucket(PACKAGE_4, USER_ID, 2750, STANDBY_BUCKET_RESTRICTED,
+                REASON_MAIN_FORCED_BY_USER);
         aih.setAppStandbyBucket(PACKAGE_1, USER_ID, 3000, STANDBY_BUCKET_RARE,
                 REASON_MAIN_TIMEOUT);
 
         assertEquals(aih.getAppStandbyBucket(PACKAGE_1, USER_ID, 3000), STANDBY_BUCKET_RARE);
         assertEquals(aih.getAppStandbyBucket(PACKAGE_2, USER_ID, 3000), STANDBY_BUCKET_ACTIVE);
         assertEquals(aih.getAppStandbyReason(PACKAGE_1, USER_ID, 3000), REASON_MAIN_TIMEOUT);
+        assertEquals(aih.getAppStandbyBucket(PACKAGE_3, USER_ID, 3000), STANDBY_BUCKET_RESTRICTED);
+        assertEquals(aih.getAppStandbyReason(PACKAGE_3, USER_ID, 3000),
+                REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_RESTRICT_BACKGROUND_RESOURCE_USAGE);
+        assertEquals(aih.getAppStandbyReason(PACKAGE_4, USER_ID, 3000),
+                REASON_MAIN_FORCED_BY_USER);
 
-        // RARE is considered idle
+        // RARE and RESTRICTED are considered idle
         assertTrue(aih.isIdle(PACKAGE_1, USER_ID, 3000));
         assertFalse(aih.isIdle(PACKAGE_2, USER_ID, 3000));
+        assertTrue(aih.isIdle(PACKAGE_3, USER_ID, 3000));
+        assertTrue(aih.isIdle(PACKAGE_4, USER_ID, 3000));
 
         // Check persistence
         aih.writeAppIdleDurations();
@@ -118,6 +131,11 @@
         assertEquals(aih.getAppStandbyBucket(PACKAGE_1, USER_ID, 5000), STANDBY_BUCKET_RARE);
         assertEquals(aih.getAppStandbyBucket(PACKAGE_2, USER_ID, 5000), STANDBY_BUCKET_ACTIVE);
         assertEquals(aih.getAppStandbyReason(PACKAGE_1, USER_ID, 5000), REASON_MAIN_TIMEOUT);
+        assertEquals(aih.getAppStandbyBucket(PACKAGE_3, USER_ID, 3000), STANDBY_BUCKET_RESTRICTED);
+        assertEquals(aih.getAppStandbyReason(PACKAGE_3, USER_ID, 3000),
+                REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_RESTRICT_BACKGROUND_RESOURCE_USAGE);
+        assertEquals(aih.getAppStandbyReason(PACKAGE_4, USER_ID, 3000),
+                REASON_MAIN_FORCED_BY_USER);
 
         assertTrue(aih.shouldInformListeners(PACKAGE_1, USER_ID, 5000, STANDBY_BUCKET_RARE));
         assertFalse(aih.shouldInformListeners(PACKAGE_1, USER_ID, 5000, STANDBY_BUCKET_RARE));
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 12ba219..03dc213 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -23,15 +23,22 @@
 import static android.app.usage.UsageEvents.Event.SYSTEM_INTERACTION;
 import static android.app.usage.UsageEvents.Event.USER_INTERACTION;
 import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT;
-import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER;
 import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED;
 import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT;
 import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_EXEMPTED_SYNC_SCHEDULED_NON_DOZE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_MOVE_TO_FOREGROUND;
+import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SYNC_ADAPTER;
+import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SYSTEM_INTERACTION;
+import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_USER_INTERACTION;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_EXEMPTED;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
 
 import static org.junit.Assert.assertEquals;
@@ -45,6 +52,8 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 
+import android.annotation.NonNull;
+import android.app.ActivityManager;
 import android.app.usage.AppStandbyInfo;
 import android.app.usage.UsageEvents;
 import android.appwidget.AppWidgetManager;
@@ -123,6 +132,13 @@
         public PackageManager getPackageManager() {
             return mockPm;
         }
+
+        public Object getSystemService(@NonNull String name) {
+            if (Context.ACTIVITY_SERVICE.equals(name)) {
+                return mock(ActivityManager.class);
+            }
+            return super.getSystemService(name);
+        }
     }
 
     static class MyInjector extends AppStandbyController.Injector {
@@ -252,8 +268,11 @@
 
         doReturn(packages).when(mockPm).getInstalledPackagesAsUser(anyInt(), anyInt());
         try {
+            doReturn(UID_1).when(mockPm).getPackageUidAsUser(eq(PACKAGE_1), anyInt());
             doReturn(UID_1).when(mockPm).getPackageUidAsUser(eq(PACKAGE_1), anyInt(), anyInt());
             doReturn(UID_EXEMPTED_1).when(mockPm).getPackageUidAsUser(eq(PACKAGE_EXEMPTED_1),
+                    anyInt());
+            doReturn(UID_EXEMPTED_1).when(mockPm).getPackageUidAsUser(eq(PACKAGE_EXEMPTED_1),
                     anyInt(), anyInt());
             doReturn(pi.applicationInfo).when(mockPm).getApplicationInfo(eq(pi.packageName),
                     anyInt());
@@ -365,8 +384,9 @@
     public void testSetAppStandbyBucket() throws Exception {
         // For a known package, standby bucket should be set properly
         reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
+        mInjector.mElapsedRealtime = HOUR_MS;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
-                REASON_MAIN_TIMEOUT, HOUR_MS);
+                REASON_MAIN_TIMEOUT);
         assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
 
         // For an unknown package, standby bucket should not be set, hence NEVER is returned
@@ -374,7 +394,7 @@
         mController.clearAppIdleForPackage(PACKAGE_UNKNOWN, USER_ID);
         isPackageInstalled = false; // Mock package is not installed
         mController.setAppStandbyBucket(PACKAGE_UNKNOWN, USER_ID, STANDBY_BUCKET_ACTIVE,
-                REASON_MAIN_TIMEOUT, HOUR_MS);
+                REASON_MAIN_TIMEOUT);
         isPackageInstalled = true; // Reset mocked variable for other tests
         assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_UNKNOWN));
     }
@@ -466,14 +486,15 @@
     }
 
     @Test
-    public void testPredictionTimedout() throws Exception {
+    public void testPredictionTimedOut() throws Exception {
         // Set it to timeout or usage, so that prediction can override it
+        mInjector.mElapsedRealtime = HOUR_MS;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
-                REASON_MAIN_TIMEOUT, HOUR_MS);
+                REASON_MAIN_TIMEOUT);
         assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
 
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
-                REASON_MAIN_PREDICTED, HOUR_MS);
+                REASON_MAIN_PREDICTED);
         assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
 
         // Fast forward 12 hours
@@ -497,29 +518,110 @@
     @Test
     public void testOverrides() throws Exception {
         // Can force to NEVER
+        mInjector.mElapsedRealtime = HOUR_MS;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
-                REASON_MAIN_FORCED, 1 * HOUR_MS);
+                REASON_MAIN_FORCED_BY_USER);
         assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_1));
 
-        // Prediction can't override FORCED reason
-        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
-                REASON_MAIN_FORCED, 1 * HOUR_MS);
+        // Prediction can't override FORCED reasons
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
+                REASON_MAIN_FORCED_BY_SYSTEM);
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
-                REASON_MAIN_PREDICTED, 1 * HOUR_MS);
+                REASON_MAIN_PREDICTED);
+        assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
+                REASON_MAIN_FORCED_BY_USER);
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
+                REASON_MAIN_PREDICTED);
         assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1));
 
         // Prediction can't override NEVER
+        mInjector.mElapsedRealtime = 2 * HOUR_MS;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
-                REASON_MAIN_DEFAULT, 2 * HOUR_MS);
+                REASON_MAIN_DEFAULT);
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
-                REASON_MAIN_PREDICTED, 2 * HOUR_MS);
+                REASON_MAIN_PREDICTED);
         assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_1));
 
         // Prediction can't set to NEVER
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
-                REASON_MAIN_USAGE, 2 * HOUR_MS);
+                REASON_MAIN_USAGE);
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
-                REASON_MAIN_PREDICTED, 2 * HOUR_MS);
+                REASON_MAIN_PREDICTED);
+        assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+
+        // Prediction can't remove from RESTRICTED
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
+                REASON_MAIN_FORCED_BY_USER);
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
+                REASON_MAIN_PREDICTED);
+        assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
+                REASON_MAIN_FORCED_BY_SYSTEM);
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
+                REASON_MAIN_PREDICTED);
+        assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+
+        // Force from user can remove from RESTRICTED
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
+                REASON_MAIN_FORCED_BY_USER);
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
+                REASON_MAIN_FORCED_BY_USER);
+        assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
+                REASON_MAIN_FORCED_BY_SYSTEM);
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
+                REASON_MAIN_FORCED_BY_USER);
+        assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+
+        // Force from system can remove from RESTRICTED if it was put it in due to system
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
+                REASON_MAIN_FORCED_BY_SYSTEM);
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
+                REASON_MAIN_FORCED_BY_SYSTEM);
+        assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
+                REASON_MAIN_FORCED_BY_USER);
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
+                REASON_MAIN_FORCED_BY_SYSTEM);
+        assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
+                REASON_MAIN_PREDICTED);
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
+                REASON_MAIN_FORCED_BY_SYSTEM);
+        assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+
+        // Non-user usage can't remove from RESTRICTED
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
+                REASON_MAIN_FORCED_BY_SYSTEM);
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
+                REASON_MAIN_USAGE);
+        assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
+                REASON_MAIN_USAGE | REASON_SUB_USAGE_SYSTEM_INTERACTION);
+        assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
+                REASON_MAIN_USAGE | REASON_SUB_USAGE_SYNC_ADAPTER);
+        assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
+                REASON_MAIN_USAGE | REASON_SUB_USAGE_EXEMPTED_SYNC_SCHEDULED_NON_DOZE);
+        assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+
+        // Explicit user usage can remove from RESTRICTED
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
+                REASON_MAIN_FORCED_BY_USER);
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
+                REASON_MAIN_USAGE | REASON_SUB_USAGE_USER_INTERACTION);
+        assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
+                REASON_MAIN_FORCED_BY_SYSTEM);
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
+                REASON_MAIN_USAGE | REASON_SUB_USAGE_MOVE_TO_FOREGROUND);
         assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
     }
 
@@ -530,7 +632,7 @@
 
         mInjector.mElapsedRealtime = 2000;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
-                REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
+                REASON_MAIN_PREDICTED);
         assertBucket(STANDBY_BUCKET_ACTIVE);
 
         // bucketing works after timeout
@@ -545,6 +647,55 @@
         assertBucket(STANDBY_BUCKET_RARE);
     }
 
+    /**
+     * Test that setAppStandbyBucket to RESTRICTED doesn't change the bucket until the usage
+     * timeout has passed.
+     */
+    @Test
+    public void testTimeoutBeforeRestricted() throws Exception {
+        reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
+        assertBucket(STANDBY_BUCKET_ACTIVE);
+
+        mInjector.mElapsedRealtime += WORKING_SET_THRESHOLD;
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
+                REASON_MAIN_FORCED_BY_SYSTEM);
+        // Bucket shouldn't change
+        assertBucket(STANDBY_BUCKET_ACTIVE);
+
+        // bucketing works after timeout
+        mInjector.mElapsedRealtime += DAY_MS;
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
+                REASON_MAIN_FORCED_BY_SYSTEM);
+        assertBucket(STANDBY_BUCKET_RESTRICTED);
+
+        // Way past all timeouts. Make sure timeout processing doesn't raise bucket.
+        mInjector.mElapsedRealtime += RARE_THRESHOLD * 4;
+        mController.checkIdleStates(USER_ID);
+        assertBucket(STANDBY_BUCKET_RESTRICTED);
+    }
+
+    /**
+     * Test that an app is put into the RESTRICTED bucket after enough time has passed.
+     */
+    @Test
+    public void testRestrictedDelay() throws Exception {
+        reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
+        assertBucket(STANDBY_BUCKET_ACTIVE);
+
+        mInjector.mElapsedRealtime += mInjector.getRestrictedBucketDelayMs() - 5000;
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
+                REASON_MAIN_FORCED_BY_SYSTEM);
+        // Bucket shouldn't change
+        assertBucket(STANDBY_BUCKET_ACTIVE);
+
+        // bucketing works after timeout
+        mInjector.mElapsedRealtime += 6000;
+
+        Thread.sleep(6000);
+        // Enough time has passed. The app should automatically be put into the RESTRICTED bucket.
+        assertBucket(STANDBY_BUCKET_RESTRICTED);
+    }
+
     @Test
     public void testCascadingTimeouts() throws Exception {
         reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
@@ -554,15 +705,17 @@
         assertBucket(STANDBY_BUCKET_ACTIVE);
 
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
-                REASON_MAIN_PREDICTED, 1000);
+                REASON_MAIN_PREDICTED);
         assertBucket(STANDBY_BUCKET_ACTIVE);
 
+        mInjector.mElapsedRealtime = 2000 + mController.mStrongUsageTimeoutMillis;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
-                REASON_MAIN_PREDICTED, 2000 + mController.mStrongUsageTimeoutMillis);
+                REASON_MAIN_PREDICTED);
         assertBucket(STANDBY_BUCKET_WORKING_SET);
 
+        mInjector.mElapsedRealtime = 2000 + mController.mNotificationSeenTimeoutMillis;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
-                REASON_MAIN_PREDICTED, 2000 + mController.mNotificationSeenTimeoutMillis);
+                REASON_MAIN_PREDICTED);
         assertBucket(STANDBY_BUCKET_FREQUENT);
     }
 
@@ -582,18 +735,18 @@
         // Still in ACTIVE after first USER_INTERACTION times out
         mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis + 1000;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
-                REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
+                REASON_MAIN_PREDICTED);
         assertBucket(STANDBY_BUCKET_ACTIVE);
 
         // Both timed out, so NOTIFICATION_SEEN timeout should be effective
         mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis * 2 + 2000;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
-                REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
+                REASON_MAIN_PREDICTED);
         assertBucket(STANDBY_BUCKET_WORKING_SET);
 
         mInjector.mElapsedRealtime = mController.mNotificationSeenTimeoutMillis + 2000;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
-                REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
+                REASON_MAIN_PREDICTED);
         assertBucket(STANDBY_BUCKET_RARE);
     }
 
@@ -625,7 +778,7 @@
         mInjector.mElapsedRealtime = 1 * RARE_THRESHOLD + 100;
         // Make sure app is in NEVER bucket
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
-                REASON_MAIN_FORCED, mInjector.mElapsedRealtime);
+                REASON_MAIN_FORCED_BY_USER);
         mController.checkIdleStates(USER_ID);
         assertBucket(STANDBY_BUCKET_NEVER);
 
@@ -670,7 +823,7 @@
         // Predict to ACTIVE
         mInjector.mElapsedRealtime += 1000;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
-                REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
+                REASON_MAIN_PREDICTED);
         assertBucket(STANDBY_BUCKET_ACTIVE);
 
         // CheckIdleStates should not change the prediction
@@ -687,7 +840,7 @@
         // Predict to FREQUENT
         mInjector.mElapsedRealtime = RARE_THRESHOLD;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
-                REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
+                REASON_MAIN_PREDICTED);
         assertBucket(STANDBY_BUCKET_FREQUENT);
 
         // Add a short timeout event
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index 03c10f3..22046a51 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -16,38 +16,54 @@
 
 package com.android.server;
 
+import android.app.AlarmManager;
 import android.app.IUiModeManager;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.os.Handler;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import com.android.server.twilight.TwilightManager;
+import com.android.server.twilight.TwilightState;
 import com.android.server.wm.WindowManagerInternal;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 
-import java.util.HashSet;
-import java.util.Set;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
 
 import static android.app.UiModeManager.MODE_NIGHT_AUTO;
+import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
 import static android.app.UiModeManager.MODE_NIGHT_NO;
 import static android.app.UiModeManager.MODE_NIGHT_YES;
 import static junit.framework.TestCase.assertFalse;
 import static junit.framework.TestCase.assertTrue;
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.notNull;
 import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
 
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -66,22 +82,51 @@
     TwilightManager mTwilightManager;
     @Mock
     PowerManager.WakeLock mWakeLock;
-    private Set<BroadcastReceiver> mScreenOffRecievers;
+    @Mock
+    AlarmManager mAlarmManager;
+    @Mock
+    PowerManager mPowerManager;
+    @Mock
+    TwilightState mTwilightState;
+
+    private BroadcastReceiver mScreenOffCallback;
+    private BroadcastReceiver mTimeChangedCallback;
+    private AlarmManager.OnAlarmListener mCustomListener;
 
     @Before
     public void setUp() {
-        mUiManagerService = new UiModeManagerService(mContext, mWindowManager, mWakeLock,
-                mTwilightManager, true);
-        mScreenOffRecievers = new HashSet<>();
+        initMocks(this);
+        mUiManagerService = new UiModeManagerService(mContext,
+                mWindowManager, mAlarmManager, mPowerManager,
+                mWakeLock, mTwilightManager, true);
         mService = mUiManagerService.getService();
         when(mContext.checkCallingOrSelfPermission(anyString()))
                 .thenReturn(PackageManager.PERMISSION_GRANTED);
         when(mContext.getResources()).thenReturn(mResources);
         when(mContext.getContentResolver()).thenReturn(mContentResolver);
-        when(mContext.registerReceiver(any(), any())).then(inv -> {
-            mScreenOffRecievers.add(inv.getArgument(0));
+        when(mPowerManager.isInteractive()).thenReturn(true);
+        when(mTwilightManager.getLastTwilightState()).thenReturn(mTwilightState);
+        when(mTwilightState.isNight()).thenReturn(true);
+        when(mContext.registerReceiver(notNull(), notNull())).then(inv -> {
+            IntentFilter filter = inv.getArgument(1);
+            if (filter.hasAction(Intent.ACTION_TIMEZONE_CHANGED)) {
+                mTimeChangedCallback = inv.getArgument(0);
+            }
+            if (filter.hasAction(Intent.ACTION_SCREEN_OFF)) {
+                mScreenOffCallback = inv.getArgument(0);
+            }
             return null;
         });
+        doAnswer(inv -> {
+            mCustomListener = inv.getArgument(3);
+            return null;
+        }).when(mAlarmManager).setExact(anyInt(), anyLong(), anyString(),
+                any(AlarmManager.OnAlarmListener.class), any(Handler.class));
+
+        doAnswer(inv -> {
+            mCustomListener = () -> {};
+            return null;
+        }).when(mAlarmManager).cancel(eq(mCustomListener));
     }
 
     @Test
@@ -102,7 +147,7 @@
             mService.setNightMode(MODE_NIGHT_NO);
         } catch (SecurityException e) { /*we should ignore this update config exception*/ }
         given(mContext.registerReceiver(any(), any())).willThrow(SecurityException.class);
-        verify(mContext).unregisterReceiver(any(BroadcastReceiver.class));
+        verify(mContext, atLeastOnce()).unregisterReceiver(any(BroadcastReceiver.class));
     }
 
     @Test
@@ -165,6 +210,132 @@
         assertFalse(isNightModeActivated());
     }
 
+    @Test
+    public void customTime_darkThemeOn() throws RemoteException {
+        LocalTime now = LocalTime.now();
+        mService.setNightMode(MODE_NIGHT_NO);
+        mService.setCustomNightModeStart(now.minusHours(1L).toNanoOfDay() / 1000);
+        mService.setCustomNightModeEnd(now.plusHours(1L).toNanoOfDay() / 1000);
+        mService.setNightMode(MODE_NIGHT_CUSTOM);
+        mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
+        assertTrue(isNightModeActivated());
+    }
+
+    @Test
+    public void customTime_darkThemeOff() throws RemoteException {
+        LocalTime now = LocalTime.now();
+        mService.setNightMode(MODE_NIGHT_YES);
+        mService.setCustomNightModeStart(now.plusHours(1L).toNanoOfDay() / 1000);
+        mService.setCustomNightModeEnd(now.minusHours(1L).toNanoOfDay() / 1000);
+        mService.setNightMode(MODE_NIGHT_CUSTOM);
+        mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
+        assertFalse(isNightModeActivated());
+    }
+
+    @Test
+    public void customTime_darkThemeOff_afterStartEnd() throws RemoteException {
+        LocalTime now = LocalTime.now();
+        mService.setNightMode(MODE_NIGHT_YES);
+        mService.setCustomNightModeStart(now.plusHours(1L).toNanoOfDay() / 1000);
+        mService.setCustomNightModeEnd(now.plusHours(2L).toNanoOfDay() / 1000);
+        mService.setNightMode(MODE_NIGHT_CUSTOM);
+        mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
+        assertFalse(isNightModeActivated());
+    }
+
+    @Test
+    public void customTime_darkThemeOn_afterStartEnd() throws RemoteException {
+        LocalTime now = LocalTime.now();
+        mService.setNightMode(MODE_NIGHT_YES);
+        mService.setCustomNightModeStart(now.plusHours(1L).toNanoOfDay() / 1000);
+        mService.setCustomNightModeEnd(now.plusHours(2L).toNanoOfDay() / 1000);
+        mService.setNightMode(MODE_NIGHT_CUSTOM);
+        mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
+        assertFalse(isNightModeActivated());
+    }
+
+
+
+    @Test
+    public void customTime_darkThemeOn_beforeStartEnd() throws RemoteException {
+        LocalTime now = LocalTime.now();
+        mService.setNightMode(MODE_NIGHT_YES);
+        mService.setCustomNightModeStart(now.minusHours(1L).toNanoOfDay() / 1000);
+        mService.setCustomNightModeEnd(now.minusHours(2L).toNanoOfDay() / 1000);
+        mService.setNightMode(MODE_NIGHT_CUSTOM);
+        mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
+        assertTrue(isNightModeActivated());
+    }
+
+    @Test
+    public void customTime_darkThemeOff_beforeStartEnd() throws RemoteException {
+        LocalTime now = LocalTime.now();
+        mService.setNightMode(MODE_NIGHT_YES);
+        mService.setCustomNightModeStart(now.minusHours(2L).toNanoOfDay() / 1000);
+        mService.setCustomNightModeEnd(now.minusHours(1L).toNanoOfDay() / 1000);
+        mService.setNightMode(MODE_NIGHT_CUSTOM);
+        mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
+        assertFalse(isNightModeActivated());
+    }
+
+    @Test
+    public void customTIme_customAlarmSetWhenScreenTimeChanges() throws RemoteException {
+        when(mPowerManager.isInteractive()).thenReturn(false);
+        mService.setNightMode(MODE_NIGHT_CUSTOM);
+        verify(mAlarmManager, times(1))
+                .setExact(anyInt(), anyLong(), anyString(), any(), any());
+        mTimeChangedCallback.onReceive(mContext, new Intent(Intent.ACTION_TIME_CHANGED));
+        verify(mAlarmManager, atLeast(2))
+                .setExact(anyInt(), anyLong(), anyString(), any(), any());
+    }
+
+    @Test
+    public void customTime_alarmSetInTheFutureWhenOn() throws RemoteException {
+        LocalDateTime now = LocalDateTime.now();
+        when(mPowerManager.isInteractive()).thenReturn(false);
+        mService.setNightMode(MODE_NIGHT_YES);
+        mService.setCustomNightModeStart(now.toLocalTime().minusHours(1L).toNanoOfDay() / 1000);
+        mService.setCustomNightModeEnd(now.toLocalTime().plusHours(1L).toNanoOfDay() / 1000);
+        LocalDateTime next = now.plusHours(1L);
+        final long millis = next.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
+        mService.setNightMode(MODE_NIGHT_CUSTOM);
+        verify(mAlarmManager)
+                .setExact(anyInt(), eq(millis), anyString(), any(), any());
+    }
+
+    @Test
+    public void customTime_appliesImmediatelyWhenScreenOff() throws RemoteException {
+        when(mPowerManager.isInteractive()).thenReturn(false);
+        LocalTime now = LocalTime.now();
+        mService.setNightMode(MODE_NIGHT_NO);
+        mService.setCustomNightModeStart(now.minusHours(1L).toNanoOfDay() / 1000);
+        mService.setCustomNightModeEnd(now.plusHours(1L).toNanoOfDay() / 1000);
+        mService.setNightMode(MODE_NIGHT_CUSTOM);
+        assertTrue(isNightModeActivated());
+    }
+
+    @Test
+    public void customTime_appliesOnlyWhenScreenOff() throws RemoteException {
+        LocalTime now = LocalTime.now();
+        mService.setNightMode(MODE_NIGHT_NO);
+        mService.setCustomNightModeStart(now.minusHours(1L).toNanoOfDay() / 1000);
+        mService.setCustomNightModeEnd(now.plusHours(1L).toNanoOfDay() / 1000);
+        mService.setNightMode(MODE_NIGHT_CUSTOM);
+        assertFalse(isNightModeActivated());
+        mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
+        assertTrue(isNightModeActivated());
+    }
+
+    @Test
+    public void nightAuto_appliesOnlyWhenScreenOff() throws RemoteException {
+        when(mTwilightState.isNight()).thenReturn(true);
+        mService.setNightMode(MODE_NIGHT_NO);
+        mService.setNightMode(MODE_NIGHT_AUTO);
+        assertFalse(isNightModeActivated());
+        mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
+        assertTrue(isNightModeActivated());
+    }
+
     private boolean isNightModeActivated() {
         return (mUiManagerService.getConfiguration().uiMode
                 & Configuration.UI_MODE_NIGHT_YES) != 0;
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 1e55b15..587cfbf 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -74,7 +74,7 @@
 
 import com.android.internal.util.IntPair;
 import com.android.server.UiServiceTestCase;
-import com.android.server.lights.Light;
+import com.android.server.lights.LogicalLight;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -91,7 +91,7 @@
     @Mock AudioManager mAudioManager;
     @Mock Vibrator mVibrator;
     @Mock android.media.IRingtonePlayer mRingtonePlayer;
-    @Mock Light mLight;
+    @Mock LogicalLight mLight;
     @Mock
     NotificationManagerService.WorkerHandler mHandler;
     @Mock
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
index eb45960..d16c232a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
@@ -18,9 +18,9 @@
 
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.util.FeatureFlagUtils.NOTIF_CONVO_BYPASS_SHORTCUT_REQ;
 
 import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
 
 import static org.mockito.Matchers.any;
@@ -31,6 +31,7 @@
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
 
 import com.android.server.UiServiceTestCase;
@@ -53,6 +54,7 @@
     public void testExtractsUpdatedChannel() {
         NotificationChannelExtractor extractor = new NotificationChannelExtractor();
         extractor.setConfig(mConfig);
+        extractor.initialize(mContext, null);
 
         NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW);
         final Notification.Builder builder = new Notification.Builder(getContext())
@@ -65,11 +67,69 @@
 
         NotificationChannel updatedChannel =
                 new NotificationChannel("a", "", IMPORTANCE_HIGH);
-        when(mConfig.getNotificationChannel(any(), anyInt(), eq("a"), eq(false)))
+        when(mConfig.getConversationNotificationChannel(
+                any(), anyInt(), eq("a"), eq(null), eq(true), eq(false)))
                 .thenReturn(updatedChannel);
 
         assertNull(extractor.process(r));
         assertEquals(updatedChannel, r.getChannel());
     }
 
+    @Test
+    public void testInvalidShortcutFlagEnabled_looksUpCorrectChannel() {
+        Settings.Global.putString(
+                mContext.getContentResolver(), NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "true");
+
+        NotificationChannelExtractor extractor = new NotificationChannelExtractor();
+        extractor.setConfig(mConfig);
+        extractor.initialize(mContext, null);
+
+        NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW);
+        final Notification.Builder builder = new Notification.Builder(getContext())
+                .setContentTitle("foo")
+                .setStyle(new Notification.MessagingStyle("name"))
+                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+        Notification n = builder.build();
+        StatusBarNotification sbn = new StatusBarNotification("", "", 0, "tag", 0,
+                0, n, UserHandle.ALL, null, System.currentTimeMillis());
+        NotificationRecord r = new NotificationRecord(getContext(), sbn, channel);
+
+        NotificationChannel updatedChannel =
+                new NotificationChannel("a", "", IMPORTANCE_HIGH);
+        when(mConfig.getConversationNotificationChannel(
+                any(), anyInt(), eq("a"), eq(r.sbn.getShortcutId(mContext)), eq(true), eq(false)))
+                .thenReturn(updatedChannel);
+
+        assertNull(extractor.process(r));
+        assertEquals(updatedChannel, r.getChannel());
+    }
+
+    @Test
+    public void testInvalidShortcutFlagDisabled_looksUpCorrectChannel() {
+        Settings.Global.putString(
+                mContext.getContentResolver(), NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "false");
+
+        NotificationChannelExtractor extractor = new NotificationChannelExtractor();
+        extractor.setConfig(mConfig);
+        extractor.initialize(mContext, null);
+
+        NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW);
+        final Notification.Builder builder = new Notification.Builder(getContext())
+                .setContentTitle("foo")
+                .setStyle(new Notification.MessagingStyle("name"))
+                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+        Notification n = builder.build();
+        StatusBarNotification sbn = new StatusBarNotification("", "", 0, "tag", 0,
+                0, n, UserHandle.ALL, null, System.currentTimeMillis());
+        NotificationRecord r = new NotificationRecord(getContext(), sbn, channel);
+
+        NotificationChannel updatedChannel =
+                new NotificationChannel("a", "", IMPORTANCE_HIGH);
+        when(mConfig.getConversationNotificationChannel(
+                any(), anyInt(), eq("a"), eq(null), eq(true), eq(false)))
+                .thenReturn(updatedChannel);
+
+        assertNull(extractor.process(r));
+        assertEquals(updatedChannel, r.getChannel());
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
index 9ad6986..5b5ad87 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
@@ -186,6 +186,20 @@
     }
 
     @Test
+    public void testAddNotification_newestFirst() {
+        HistoricalNotification n = getHistoricalNotification(1);
+        HistoricalNotification n2 = getHistoricalNotification(2);
+
+        mDataBase.addNotification(n);
+
+        // second add should not trigger another write
+        mDataBase.addNotification(n2);
+
+        assertThat(mDataBase.mBuffer.getNotificationsToWrite().get(0)).isEqualTo(n2);
+        assertThat(mDataBase.mBuffer.getNotificationsToWrite().get(1)).isEqualTo(n);
+    }
+
+    @Test
     public void testReadNotificationHistory_readsAllFiles() throws Exception {
         for (long i = 10; i >= 5; i--) {
             AtomicFile af = mock(AtomicFile.class);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java
index 3b6a4bd..b5eb1bf 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java
@@ -40,8 +40,8 @@
 
 import com.android.server.UiServiceTestCase;
 
+import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -115,6 +115,11 @@
         mHistoryManager.onBootPhaseAppsCanStart();
     }
 
+    @After
+    public void tearDown() {
+        mHistoryManager.onDestroy();
+    }
+
     @Test
     public void testOnUserUnlocked() {
         assertThat(mHistoryManager.doesHistoryExistForUser(USER_SYSTEM)).isFalse();
@@ -126,22 +131,52 @@
     }
 
     @Test
-    @Ignore("b/147012298")
     public void testOnUserUnlocked_historyDisabled() {
+        // create a history
+        mHistoryManager.onUserUnlocked(USER_SYSTEM);
+        assertThat(mHistoryManager.doesHistoryExistForUser(USER_SYSTEM)).isTrue();
+        // lock user
+        mHistoryManager.onUserStopped(USER_SYSTEM);
+
+        // turn off history
         Settings.Secure.putIntForUser(getContext().getContentResolver(),
                 Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, USER_SYSTEM);
         mHistoryManager.mSettingsObserver.update(null, USER_SYSTEM);
-        assertThat(mHistoryManager.doesHistoryExistForUser(USER_SYSTEM)).isFalse();
-        assertThat(mHistoryManager.isUserUnlocked(USER_SYSTEM)).isFalse();
 
+        // unlock user, verify that history is disabled
         mHistoryManager.onUserUnlocked(USER_SYSTEM);
 
         assertThat(mHistoryManager.doesHistoryExistForUser(USER_SYSTEM)).isFalse();
-        assertThat(mHistoryManager.isUserUnlocked(USER_SYSTEM)).isFalse();
         verify(mDb, times(1)).disableHistory();
     }
 
     @Test
+    public void testOnUserUnlocked_historyDisabledThenEnabled() {
+        // create a history
+        mHistoryManager.onUserUnlocked(USER_SYSTEM);
+        assertThat(mHistoryManager.doesHistoryExistForUser(USER_SYSTEM)).isTrue();
+
+        // lock user
+        mHistoryManager.onUserStopped(USER_SYSTEM);
+
+        // turn off history
+        Settings.Secure.putIntForUser(getContext().getContentResolver(),
+                Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, USER_SYSTEM);
+        mHistoryManager.mSettingsObserver.update(null, USER_SYSTEM);
+
+        // turn on history
+        Settings.Secure.putIntForUser(getContext().getContentResolver(),
+                Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 1, USER_SYSTEM);
+        mHistoryManager.mSettingsObserver.update(null, USER_SYSTEM);
+
+        // unlock user, verify that history is NOT disabled
+        mHistoryManager.onUserUnlocked(USER_SYSTEM);
+
+        assertThat(mHistoryManager.doesHistoryExistForUser(USER_SYSTEM)).isTrue();
+        verify(mDb, never()).disableHistory();
+    }
+
+    @Test
     public void testOnUserUnlocked_cleansUpRemovedPackages() {
         String pkg = "pkg";
         mHistoryManager.onPackageRemoved(USER_SYSTEM, pkg);
@@ -223,6 +258,8 @@
 
     @Test
     public void testOnPackageRemoved_historyDisabled() {
+        mHistoryManager.onUserUnlocked(USER_SYSTEM);
+
         Settings.Secure.putIntForUser(getContext().getContentResolver(),
                 Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, USER_SYSTEM);
         mHistoryManager.mSettingsObserver.update(null, USER_SYSTEM);
@@ -427,6 +464,8 @@
     public void testIsHistoryEnabled() {
         assertThat(mHistoryManager.isHistoryEnabled(USER_SYSTEM)).isTrue();
 
+        mHistoryManager.onUserUnlocked(USER_SYSTEM);
+
         Settings.Secure.putIntForUser(getContext().getContentResolver(),
                 Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0,  USER_SYSTEM);
         mHistoryManager.mSettingsObserver.update(null, USER_SYSTEM);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index a9fe1a6..da0e03d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -117,6 +117,7 @@
             assertEquals(getSmartReplies(key, i), ranking.getSmartReplies());
             assertEquals(canBubble(i), ranking.canBubble());
             assertEquals(visuallyInterruptive(i), ranking.visuallyInterruptive());
+            assertEquals(isConversation(i), ranking.isConversation());
         }
     }
 
@@ -184,7 +185,8 @@
                 (ArrayList) tweak.getSmartActions(),
                 (ArrayList) tweak.getSmartReplies(),
                 tweak.canBubble(),
-                tweak.visuallyInterruptive()
+                tweak.visuallyInterruptive(),
+                tweak.isConversation()
         );
         assertNotEquals(nru, nru2);
     }
@@ -261,7 +263,8 @@
                     getSmartActions(key, i),
                     getSmartReplies(key, i),
                     canBubble(i),
-                    visuallyInterruptive(i)
+                    visuallyInterruptive(i),
+                    isConversation(i)
             );
             rankings[i] = ranking;
         }
@@ -370,6 +373,10 @@
         return index % 4 == 0;
     }
 
+    private boolean isConversation(int index) {
+        return index % 4 == 0;
+    }
+
     private void assertActionsEqual(
             List<Notification.Action> expecteds, List<Notification.Action> actuals) {
         assertEquals(expecteds.size(), actuals.size());
@@ -403,6 +410,7 @@
         assertEquals(comment, a.isNoisy(), b.isNoisy());
         assertEquals(comment, a.getSmartReplies(), b.getSmartReplies());
         assertEquals(comment, a.canBubble(), b.canBubble());
+        assertEquals(comment, a.isConversation(), b.isConversation());
         assertActionsEqual(a.getSmartActions(), b.getSmartActions());
     }
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 172df99..93e09df 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -100,8 +100,10 @@
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
+import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
+import android.content.pm.ShortcutInfo;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.graphics.Color;
@@ -112,6 +114,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.Parcel;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -133,6 +136,7 @@
 import android.testing.TestablePermissions;
 import android.testing.TestableResources;
 import android.text.Html;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
@@ -149,8 +153,8 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.UiServiceTestCase;
-import com.android.server.lights.Light;
 import com.android.server.lights.LightsManager;
+import com.android.server.lights.LogicalLight;
 import com.android.server.notification.NotificationManagerService.NotificationAssistants;
 import com.android.server.notification.NotificationManagerService.NotificationListeners;
 import com.android.server.uri.UriGrantsManagerInternal;
@@ -212,6 +216,8 @@
     @Mock
     private AudioManager mAudioManager;
     @Mock
+    private LauncherApps mLauncherApps;
+    @Mock
     ActivityManager mActivityManager;
     @Mock
     Resources mResources;
@@ -366,7 +372,7 @@
                 });
         when(mPackageManagerClient.getPackageUidAsUser(any(), anyInt())).thenReturn(mUid);
         final LightsManager mockLightsManager = mock(LightsManager.class);
-        when(mockLightsManager.getLight(anyInt())).thenReturn(mock(Light.class));
+        when(mockLightsManager.getLight(anyInt())).thenReturn(mock(LogicalLight.class));
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
         when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(false);
         when(mUgmInternal.newUriPermissionOwner(anyString())).thenReturn(mPermOwner);
@@ -418,6 +424,7 @@
         mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
 
         mService.setAudioManager(mAudioManager);
+        mService.setLauncherApps(mLauncherApps);
 
         // Tests call directly into the Binder.
         mBinderService = mService.getBinderService();
@@ -469,16 +476,17 @@
         mTestableLooper.processAllMessages();
     }
 
-    private void setUpPrefsForBubbles(boolean globalEnabled, boolean pkgEnabled,
-            boolean channelEnabled) {
-        mService.setPreferencesHelper(mPreferencesHelper);
-        when(mPreferencesHelper.bubblesEnabled()).thenReturn(globalEnabled);
-        when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(pkgEnabled);
-        when(mPreferencesHelper.getNotificationChannel(
-                anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
-                mTestNotificationChannel);
-        when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(
-                mTestNotificationChannel.getImportance());
+    private void setUpPrefsForBubbles(String pkg, int uid, boolean globalEnabled,
+            boolean pkgEnabled, boolean channelEnabled) {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.NOTIFICATION_BUBBLES, globalEnabled ? 1 : 0);
+        mService.mPreferencesHelper.updateBubblesEnabled();
+        assertEquals(globalEnabled, mService.mPreferencesHelper.bubblesEnabled());
+        try {
+            mBinderService.setBubblesAllowed(pkg, uid, pkgEnabled);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
         mTestNotificationChannel.setAllowBubbles(channelEnabled);
     }
 
@@ -615,8 +623,8 @@
     private Notification.BubbleMetadata.Builder getBubbleMetadataBuilder() {
         PendingIntent pi = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
         return new Notification.BubbleMetadata.Builder()
-                .setIntent(pi)
-                .setIcon(Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon));
+                .createIntentBubble(pi,
+                        Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon));
     }
 
     private Notification.Builder getMessageStyleNotifBuilder(boolean addBubbleMetadata,
@@ -1761,8 +1769,8 @@
         Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
         mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_onTv", 0,
                 generateNotificationRecord(null, tv).getNotification(), 0);
-        verify(mPreferencesHelper, times(1)).getNotificationChannel(
-                anyString(), anyInt(), eq("foo"), anyBoolean());
+        verify(mPreferencesHelper, times(1)).getConversationNotificationChannel(
+                anyString(), anyInt(), eq("foo"), eq(null), anyBoolean(), anyBoolean());
     }
 
     @Test
@@ -1776,8 +1784,9 @@
         Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
         mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_notOnTv",
                 0, generateNotificationRecord(null, tv).getNotification(), 0);
-        verify(mPreferencesHelper, times(1)).getNotificationChannel(
-                anyString(), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean());
+        verify(mPreferencesHelper, times(1)).getConversationNotificationChannel(
+                anyString(), anyInt(), eq(mTestNotificationChannel.getId()), eq(null),
+                anyBoolean(), anyBoolean());
     }
 
     @Test
@@ -3210,9 +3219,11 @@
         final NotificationVisibility nv = NotificationVisibility.obtain(r.getKey(), 1, 2, true);
         mService.mNotificationDelegate.onNotificationVisibilityChanged(
                 new NotificationVisibility[] {nv}, new NotificationVisibility[]{});
+        verify(mAssistants).notifyAssistantVisibilityChangedLocked(eq(r.sbn), eq(true));
         assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasSeen());
         mService.mNotificationDelegate.onNotificationVisibilityChanged(
                 new NotificationVisibility[] {}, new NotificationVisibility[]{nv});
+        verify(mAssistants).notifyAssistantVisibilityChangedLocked(eq(r.sbn), eq(false));
         assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasSeen());
     }
 
@@ -4391,7 +4402,7 @@
         when(mActivityManager.getUidImportance(mUid)).thenReturn(IMPORTANCE_FOREGROUND);
 
         // enqueue toast -> toast should still enqueue
-        ((INotificationManager)mService.mService).enqueueToast(testPackage,
+        ((INotificationManager) mService.mService).enqueueToast(testPackage, new Binder(),
                 new TestableToastCallback(), 2000, 0);
         assertEquals(1, mService.mToastQueue.size());
     }
@@ -4411,7 +4422,7 @@
         when(mPreferencesHelper.getImportance(testPackage, mUid)).thenReturn(IMPORTANCE_LOW);
 
         // enqueue toast -> no toasts enqueued
-        ((INotificationManager)mService.mService).enqueueToast(testPackage,
+        ((INotificationManager) mService.mService).enqueueToast(testPackage, new Binder(),
                 new TestableToastCallback(), 2000, 0);
         assertEquals(0, mService.mToastQueue.size());
     }
@@ -4434,7 +4445,7 @@
         when(mActivityManager.getUidImportance(mUid)).thenReturn(IMPORTANCE_GONE);
 
         // enqueue toast -> no toasts enqueued
-        ((INotificationManager)mService.mService).enqueueToast(testPackage,
+        ((INotificationManager) mService.mService).enqueueToast(testPackage, new Binder(),
                 new TestableToastCallback(), 2000, 0);
         assertEquals(0, mService.mToastQueue.size());
     }
@@ -4457,12 +4468,22 @@
         when(mActivityManager.getUidImportance(mUid)).thenReturn(IMPORTANCE_GONE);
 
         // enqueue toast -> system toast can still be enqueued
-        ((INotificationManager)mService.mService).enqueueToast(testPackage,
+        ((INotificationManager) mService.mService).enqueueToast(testPackage, new Binder(),
                 new TestableToastCallback(), 2000, 0);
         assertEquals(1, mService.mToastQueue.size());
     }
 
     @Test
+    public void testOnPanelRevealedAndHidden() {
+        int items = 5;
+        mService.mNotificationDelegate.onPanelRevealed(false, items);
+        verify(mAssistants, times(1)).onPanelRevealed(eq(items));
+
+        mService.mNotificationDelegate.onPanelHidden();
+        verify(mAssistants, times(1)).onPanelHidden();
+    }
+
+    @Test
     public void testOnNotificationSmartReplySent() {
         final int replyIndex = 2;
         final String reply = "Hello";
@@ -4744,7 +4765,7 @@
     @Test
     public void testFlagBubble() throws RemoteException {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         NotificationRecord nr =
                 generateMessageBubbleNotifRecord(mTestNotificationChannel, "testFlagBubble");
@@ -4763,7 +4784,7 @@
     @Test
     public void testFlagBubble_noFlag_appNotAllowed() throws RemoteException {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, false /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, false /* app */, true /* channel */);
 
         NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
                         "testFlagBubble_noFlag_appNotAllowed");
@@ -4782,7 +4803,7 @@
     @Test
     public void testFlagBubbleNotifs_noFlag_whenAppForeground() throws RemoteException {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         // Notif with bubble metadata but not our other misc requirements
         Notification.Builder nb = new Notification.Builder(mContext,
@@ -4810,7 +4831,7 @@
     @Test
     public void testFlagBubbleNotifs_flag_messaging() throws RemoteException {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
                 "testFlagBubbleNotifs_flag_messaging");
@@ -4827,7 +4848,7 @@
     @Test
     public void testFlagBubbleNotifs_flag_phonecall() throws RemoteException {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         // Give it bubble metadata
         Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
@@ -4863,7 +4884,7 @@
     @Test
     public void testFlagBubbleNotifs_noFlag_phonecall_noForegroundService() throws RemoteException {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         // Give it bubble metadata
         Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
@@ -4896,7 +4917,7 @@
     @Test
     public void testFlagBubbleNotifs_noFlag_phonecall_noPerson() throws RemoteException {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         // Give it bubble metadata
         Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
@@ -4927,7 +4948,7 @@
     @Test
     public void testFlagBubbleNotifs_noFlag_phonecall_noCategory() throws RemoteException {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         // Give it bubble metadata
         Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
@@ -4962,7 +4983,7 @@
     @Test
     public void testFlagBubbleNotifs_noFlag_messaging_appNotAllowed() throws RemoteException {
         // Bubbles are NOT allowed!
-        setUpPrefsForBubbles(false /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, false /* app */, true /* channel */);
 
         NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
                 "testFlagBubbleNotifs_noFlag_messaging_appNotAllowed");
@@ -4980,7 +5001,7 @@
     @Test
     public void testFlagBubbleNotifs_noFlag_notBubble() throws RemoteException {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         // Messaging notif WITHOUT bubble metadata
         Notification.Builder nb = getMessageStyleNotifBuilder(false /* addBubbleMetadata */,
@@ -5004,7 +5025,7 @@
     @Test
     public void testFlagBubbleNotifs_noFlag_messaging_channelNotAllowed() throws RemoteException {
         // Bubbles are allowed except on this channel
-        setUpPrefsForBubbles(true /* global */, true /* app */, false /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, false /* channel */);
 
         NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
                 "testFlagBubbleNotifs_noFlag_messaging_channelNotAllowed");
@@ -5022,7 +5043,7 @@
     @Test
     public void testFlagBubbleNotifs_noFlag_phonecall_notAllowed() throws RemoteException {
         // Bubbles are not allowed!
-        setUpPrefsForBubbles(false /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, false /* global */, true /* app */, true /* channel */);
 
         // Give it bubble metadata
         Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
@@ -5044,10 +5065,9 @@
                 nb.build(), new UserHandle(mUid), null, 0);
         // Make sure it has foreground service
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
-        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+                sbn.getId(), sbn.getNotification(), sbn.getUserId());
         waitForIdle();
 
         // yes phone call, yes person, yes foreground service, but not allowed, no bubble
@@ -5058,7 +5078,7 @@
     @Test
     public void testFlagBubbleNotifs_noFlag_phonecall_channelNotAllowed() throws RemoteException {
         // Bubbles are allowed, but not on channel.
-        setUpPrefsForBubbles(true /* global */, true /* app */, false /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, false /* channel */);
 
         // Give it bubble metadata
         Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
@@ -5266,7 +5286,7 @@
     @Test
     public void testNotificationBubbleChanged_false() throws Exception {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         // Notif with bubble metadata
         NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
@@ -5297,7 +5317,7 @@
     @Test
     public void testNotificationBubbleChanged_true() throws Exception {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         // Notif that is not a bubble
         NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
@@ -5334,7 +5354,7 @@
     @Test
     public void testNotificationBubbleChanged_true_notAllowed() throws Exception {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         // Notif that is not a bubble
         NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
@@ -5533,7 +5553,7 @@
     @Test
     public void testNotificationBubbles_disabled_lowRamDevice() throws Exception {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         // And we are low ram
         when(mActivityManager.isLowRamDevice()).thenReturn(true);
@@ -5617,7 +5637,7 @@
     public void testNotificationBubbles_flagAutoExpandForeground_fails_notForeground()
             throws Exception {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
                 "testNotificationBubbles_flagAutoExpandForeground_fails_notForeground");
@@ -5647,7 +5667,7 @@
     public void testNotificationBubbles_flagAutoExpandForeground_succeeds_foreground()
             throws RemoteException {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
                 "testNotificationBubbles_flagAutoExpandForeground_succeeds_foreground");
@@ -5674,10 +5694,62 @@
     }
 
     @Test
+    public void testNotificationBubbles_flagRemoved_whenShortcutRemoved()
+            throws RemoteException {
+        // Bubbles are allowed!
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
+
+        ArgumentCaptor<LauncherApps.Callback> launcherAppsCallback =
+                ArgumentCaptor.forClass(LauncherApps.Callback.class);
+
+        // Messaging notification with shortcut info
+        Notification.BubbleMetadata metadata =
+                getBubbleMetadataBuilder().createShortcutBubble("someshortcutId").build();
+        Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
+                null /* groupKey */, false /* isSummary */);
+        nb.setBubbleMetadata(metadata);
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "tag", mUid, 0, nb.build(), new UserHandle(mUid), null, 0);
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        // Pretend the shortcut exists
+        List<ShortcutInfo> shortcutInfos = new ArrayList<>();
+        shortcutInfos.add(mock(ShortcutInfo.class));
+        when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcutInfos);
+
+        // Test: Send the bubble notification
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
+                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        waitForIdle();
+
+        // Verify:
+
+        // Make sure we register the callback for shortcut changes
+        verify(mLauncherApps, times(1)).registerCallback(launcherAppsCallback.capture(), any());
+
+        // yes allowed, yes messaging w/shortcut, yes bubble
+        Notification notif = mService.getNotificationRecord(nr.sbn.getKey()).getNotification();
+        assertTrue(notif.isBubbleNotification());
+
+        // Test: Remove the shortcut
+        launcherAppsCallback.getValue().onShortcutsChanged(PKG, Collections.emptyList(),
+                new UserHandle(mUid));
+
+        // Verify:
+
+        // Make sure callback is unregistered
+        verify(mLauncherApps, times(1)).unregisterCallback(launcherAppsCallback.getValue());
+
+        // We're no longer a bubble
+        Notification notif2 = mService.getNotificationRecord(nr.sbn.getKey()).getNotification();
+        assertFalse(notif2.isBubbleNotification());
+    }
+
+    @Test
     public void testNotificationBubbles_bubbleChildrenStay_whenGroupSummaryDismissed()
             throws Exception {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         NotificationRecord nrSummary = addGroupWithBubblesAndValidateAdded(
                 true /* summaryAutoCancel */);
@@ -5700,7 +5772,7 @@
     public void testNotificationBubbles_bubbleChildrenStay_whenGroupSummaryClicked()
             throws Exception {
         // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
 
         NotificationRecord nrSummary = addGroupWithBubblesAndValidateAdded(
                 true /* summaryAutoCancel */);
@@ -5802,4 +5874,81 @@
 
         verify(mHistoryManager, times(1)).addNotification(any());
     }
+
+    @Test
+    public void createConversationNotificationChannel() throws Exception {
+        NotificationChannel original = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+        original.setAllowBubbles(!original.canBubble());
+        original.setShowBadge(!original.canShowBadge());
+
+        Parcel parcel = Parcel.obtain();
+        original.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        NotificationChannel orig = NotificationChannel.CREATOR.createFromParcel(parcel);
+        assertEquals(original, orig);
+        assertFalse(TextUtils.isEmpty(orig.getName()));
+
+        mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList(
+                orig)));
+
+        mBinderService.createConversationNotificationChannelForPackage(
+                PKG, mUid, "key", orig, "friend");
+
+        NotificationChannel friendChannel = mBinderService.getConversationNotificationChannel(
+                PKG, 0, PKG, original.getId(), false, "friend");
+
+        assertEquals(original.getName(), friendChannel.getName());
+        assertEquals(original.getId(), friendChannel.getParentChannelId());
+        assertEquals("friend", friendChannel.getConversationId());
+        assertEquals(null, original.getConversationId());
+        assertEquals(original.canShowBadge(), friendChannel.canShowBadge());
+        assertEquals(original.canBubble(), friendChannel.canBubble());
+        assertFalse(original.getId().equals(friendChannel.getId()));
+        assertNotNull(friendChannel.getId());
+    }
+
+    @Test
+    public void deleteConversationNotificationChannels() throws Exception {
+        NotificationChannel messagesParent =
+                new NotificationChannel("messages", "messages", IMPORTANCE_HIGH);
+        Parcel msgParcel = Parcel.obtain();
+        messagesParent.writeToParcel(msgParcel, 0);
+        msgParcel.setDataPosition(0);
+
+        NotificationChannel callsParent =
+                new NotificationChannel("calls", "calls", IMPORTANCE_HIGH);
+        Parcel callParcel = Parcel.obtain();
+        callsParent.writeToParcel(callParcel, 0);
+        callParcel.setDataPosition(0);
+
+        mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList(
+                messagesParent, callsParent)));
+
+        String conversationId = "friend";
+
+        mBinderService.createConversationNotificationChannelForPackage(
+                PKG, mUid, "key", NotificationChannel.CREATOR.createFromParcel(msgParcel),
+                conversationId);
+        mBinderService.createConversationNotificationChannelForPackage(
+                PKG, mUid, "key", NotificationChannel.CREATOR.createFromParcel(callParcel),
+                conversationId);
+
+        NotificationChannel messagesChild = mBinderService.getConversationNotificationChannel(
+                PKG, 0, PKG, messagesParent.getId(), false, conversationId);
+        NotificationChannel callsChild = mBinderService.getConversationNotificationChannel(
+                PKG, 0, PKG, callsParent.getId(), false, conversationId);
+
+        assertEquals(messagesParent.getId(), messagesChild.getParentChannelId());
+        assertEquals(conversationId, messagesChild.getConversationId());
+
+        assertEquals(callsParent.getId(), callsChild.getParentChannelId());
+        assertEquals(conversationId, callsChild.getConversationId());
+
+        mBinderService.deleteConversationNotificationChannels(PKG, mUid, conversationId);
+
+        assertNull(mBinderService.getConversationNotificationChannel(
+                PKG, 0, PKG, messagesParent.getId(), false, conversationId));
+        assertNull(mBinderService.getConversationNotificationChannel(
+                PKG, 0, PKG, callsParent.getId(), false, conversationId));
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index fab6b7f..2d4b5a7 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -20,6 +20,7 @@
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.service.notification.Adjustment.KEY_IMPORTANCE;
+import static android.service.notification.Adjustment.KEY_NOT_CONVERSATION;
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE;
@@ -38,12 +39,15 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.IActivityManager;
 import android.app.Notification;
 import android.app.Notification.Builder;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
+import android.app.Person;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -58,6 +62,7 @@
 import android.provider.Settings;
 import android.service.notification.Adjustment;
 import android.service.notification.StatusBarNotification;
+import android.util.FeatureFlagUtils;
 import android.widget.RemoteViews;
 
 import androidx.test.filters.SmallTest;
@@ -83,6 +88,7 @@
 
     private final Context mMockContext = mock(Context.class);
     @Mock private PackageManager mPm;
+    @Mock private ContentResolver mContentResolver;
 
     private final String pkg = PKG_N_MR1;
     private final int uid = 9583;
@@ -116,6 +122,9 @@
 
         when(mMockContext.getResources()).thenReturn(getContext().getResources());
         when(mMockContext.getPackageManager()).thenReturn(mPm);
+        when(mMockContext.getContentResolver()).thenReturn(mContentResolver);
+        Settings.Global.putString(mContentResolver,
+                FeatureFlagUtils.NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "false");
         ApplicationInfo appInfo = new ApplicationInfo();
         appInfo.targetSdkVersion = Build.VERSION_CODES.O;
         when(mMockContext.getApplicationInfo()).thenReturn(appInfo);
@@ -194,6 +203,21 @@
         return new StatusBarNotification(pkg, pkg, id1, tag1, uid, uid, n, mUser, null, uid);
     }
 
+    private StatusBarNotification getMessagingStyleNotification(@Nullable String shortcutId) {
+        final Builder builder = new Builder(mMockContext)
+                .setContentTitle("foo")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+
+        Person person = new Person.Builder().setName("Bob").build();
+        builder.setStyle(new Notification.MessagingStyle(person));
+        if (shortcutId != null) {
+            builder.setShortcutId(shortcutId);
+        }
+
+        Notification n = builder.build();
+        return new StatusBarNotification(pkg, pkg, id1, tag1, uid, uid, n, mUser, null, uid);
+    }
+
     //
     // Tests
     //
@@ -1095,4 +1119,55 @@
 
         assertTrue("false negative detection", record.hasUndecoratedRemoteView());
     }
+
+    @Test
+    public void testIsConversation() {
+        StatusBarNotification sbn = getMessagingStyleNotification("test_shortcut_id");
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+        assertTrue(record.isConversation());
+    }
+
+    @Test
+    public void testIsConversation_nullShortcutId() {
+        StatusBarNotification sbn = getMessagingStyleNotification(null);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+        assertFalse(record.isConversation());
+    }
+
+    @Test
+    public void testIsConversation_bypassShortcutFlagEnabled() {
+        Settings.Global.putString(mContentResolver,
+                FeatureFlagUtils.NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "true");
+        StatusBarNotification sbn = getMessagingStyleNotification(null);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+        assertTrue(record.isConversation());
+    }
+
+    @Test
+    public void testIsConversation_channelDemoted() {
+        StatusBarNotification sbn = getMessagingStyleNotification("test_shortcut_id");
+        channel.setDemoted(true);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+        assertFalse(record.isConversation());
+    }
+
+    @Test
+    public void testIsConversation_withAdjustmentOverride() {
+        StatusBarNotification sbn = getMessagingStyleNotification("test_shortcut_id");
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+        Bundle bundle = new Bundle();
+        bundle.putBoolean(KEY_NOT_CONVERSATION, true);
+        Adjustment adjustment = new Adjustment(
+                PKG_O, record.getKey(), bundle, "", record.getUser().getIdentifier());
+
+        record.addAdjustment(adjustment);
+        record.applyAdjustments();
+
+        assertFalse(record.isConversation());
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 8961796..c1c74da 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -15,12 +15,14 @@
  */
 package com.android.server.notification;
 
+import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_MAX;
 import static android.app.NotificationManager.IMPORTANCE_NONE;
 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+import static android.util.FeatureFlagUtils.NOTIF_CONVO_BYPASS_SHORTCUT_REQ;
 
 import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT;
 
@@ -224,6 +226,27 @@
         assertEquals(expected.getGroup(), actual.getGroup());
         assertEquals(expected.getAudioAttributes(), actual.getAudioAttributes());
         assertEquals(expected.getLightColor(), actual.getLightColor());
+        assertEquals(expected.getParentChannelId(), actual.getParentChannelId());
+        assertEquals(expected.getConversationId(), actual.getConversationId());
+        assertEquals(expected.isDemoted(), actual.isDemoted());
+    }
+
+    private void compareChannelsParentChild(NotificationChannel parent,
+            NotificationChannel actual, String conversationId) {
+        assertEquals(parent.getName(), actual.getName());
+        assertEquals(parent.getDescription(), actual.getDescription());
+        assertEquals(parent.shouldVibrate(), actual.shouldVibrate());
+        assertEquals(parent.shouldShowLights(), actual.shouldShowLights());
+        assertEquals(parent.getImportance(), actual.getImportance());
+        assertEquals(parent.getLockscreenVisibility(), actual.getLockscreenVisibility());
+        assertEquals(parent.getSound(), actual.getSound());
+        assertEquals(parent.canBypassDnd(), actual.canBypassDnd());
+        assertTrue(Arrays.equals(parent.getVibrationPattern(), actual.getVibrationPattern()));
+        assertEquals(parent.getGroup(), actual.getGroup());
+        assertEquals(parent.getAudioAttributes(), actual.getAudioAttributes());
+        assertEquals(parent.getLightColor(), actual.getLightColor());
+        assertEquals(parent.getId(), actual.getParentChannelId());
+        assertEquals(conversationId, actual.getConversationId());
     }
 
     private void compareGroups(NotificationChannelGroup expected, NotificationChannelGroup actual) {
@@ -333,6 +356,8 @@
         channel2.setGroup(ncg.getId());
         channel2.setVibrationPattern(new long[]{100, 67, 145, 156});
         channel2.setLightColor(Color.BLUE);
+        channel2.setConversationId("id1", "conversation");
+        channel2.setDemoted(true);
 
         mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true);
         mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg2, true);
@@ -2786,4 +2811,132 @@
         assertEquals(user10Importance, mHelper.getNotificationChannel(
                 pkg, uidList10[0], channelId, false).getImportance());
     }
+
+    @Test
+    public void testGetConversationNotificationChannel() {
+        String conversationId = "friend";
+
+        NotificationChannel parent =
+                new NotificationChannel("parent", "messages", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false);
+
+        NotificationChannel friend = new NotificationChannel(String.format(
+                CONVERSATION_CHANNEL_ID_FORMAT, parent.getId(), conversationId),
+                "messages", IMPORTANCE_DEFAULT);
+        friend.setConversationId(parent.getId(), conversationId);
+        mHelper.createNotificationChannel(PKG_O, UID_O, friend, true, false);
+
+        compareChannelsParentChild(parent, mHelper.getConversationNotificationChannel(
+                PKG_O, UID_O, parent.getId(), conversationId, false, false), conversationId);
+    }
+
+    @Test
+    public void testGetNotificationChannel_conversationProvidedByNotCustomizedYet() {
+        String conversationId = "friend";
+
+        NotificationChannel parent =
+                new NotificationChannel("parent", "messages", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false);
+
+        compareChannels(parent, mHelper.getConversationNotificationChannel(
+                PKG_O, UID_O, parent.getId(), conversationId, true, false));
+    }
+
+    @Test
+    public void testConversationNotificationChannelsRequireParents() {
+        String parentId = "does not exist";
+        String conversationId = "friend";
+
+        NotificationChannel friend = new NotificationChannel(String.format(
+                CONVERSATION_CHANNEL_ID_FORMAT, parentId, conversationId),
+                "messages", IMPORTANCE_DEFAULT);
+        friend.setConversationId(parentId, conversationId);
+
+        try {
+            mHelper.createNotificationChannel(PKG_O, UID_O, friend, true, false);
+            fail("allowed creation of conversation channel without a parent");
+        } catch (IllegalArgumentException e) {
+            // good
+        }
+    }
+
+    @Test
+    public void testPlaceholderConversationId_flagOn() throws Exception {
+        Settings.Global.putString(
+                mContext.getContentResolver(), NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "true");
+        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
+
+        final String xml = "<ranking version=\"1\">\n"
+                + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n"
+                + "<channel id=\"id\" name=\"hi\" importance=\"3\" conv_id=\"foo:placeholder_id\"/>"
+                + "</package>"
+                + "</ranking>";
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())),
+                null);
+        parser.nextTag();
+        mHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+        assertNotNull(mHelper.getNotificationChannel(PKG_O, UID_O, "id", true));
+    }
+
+    @Test
+    public void testPlaceholderConversationId_flagOff() throws Exception {
+        Settings.Global.putString(
+                mContext.getContentResolver(), NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "false");
+        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
+
+        final String xml = "<ranking version=\"1\">\n"
+                + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n"
+                + "<channel id=\"id\" name=\"hi\" importance=\"3\" conv_id=\"foo:placeholder_id\"/>"
+                + "</package>"
+                + "</ranking>";
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())),
+                null);
+        parser.nextTag();
+        mHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+        assertNull(mHelper.getNotificationChannel(PKG_O, UID_O, "id", true));
+    }
+
+    @Test
+    public void testNormalConversationId_flagOff() throws Exception {
+        Settings.Global.putString(
+                mContext.getContentResolver(), NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "false");
+        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
+
+        final String xml = "<ranking version=\"1\">\n"
+                + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n"
+                + "<channel id=\"id\" name=\"hi\" importance=\"3\" conv_id=\"other\"/>"
+                + "</package>"
+                + "</ranking>";
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())),
+                null);
+        parser.nextTag();
+        mHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+        assertNotNull(mHelper.getNotificationChannel(PKG_O, UID_O, "id", true));
+    }
+
+    @Test
+    public void testNoConversationId_flagOff() throws Exception {
+        Settings.Global.putString(
+                mContext.getContentResolver(), NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "false");
+        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
+
+        final String xml = "<ranking version=\"1\">\n"
+                + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n"
+                + "<channel id=\"id\" name=\"hi\" importance=\"3\"/>"
+                + "</package>"
+                + "</ranking>";
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())),
+                null);
+        parser.nextTag();
+        mHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+        assertNotNull(mHelper.getNotificationChannel(PKG_O, UID_O, "id", true));
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
index ed9cdf4..4f84ee1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
@@ -233,7 +233,7 @@
 
         final ActivityStack pinnedStack = display.createStack(
                 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        assertEquals(pinnedStack, display.getPinnedStack());
+        assertEquals(pinnedStack, display.getRootPinnedTask());
         assertEquals(pinnedStack, display.getTopStack());
 
         final ActivityStack anotherAlwaysOnTopStack = display.createStack(
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 73b2f6b..ebe4ab9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+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.SCREEN_ORIENTATION_LANDSCAPE;
@@ -56,6 +58,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -120,28 +123,27 @@
     @Test
     public void testStackCleanupOnClearingTask() {
         mActivity.onParentChanged(null /*newParent*/, mActivity.getTask());
-        verify(mStack, times(1)).onActivityRemovedFromStack(any());
+        verify(mStack, times(1)).cleanUpActivityReferences(any());
     }
 
     @Test
     public void testStackCleanupOnActivityRemoval() {
         mTask.removeChild(mActivity);
-        verify(mStack, times(1)).onActivityRemovedFromStack(any());
+        verify(mStack, times(1)).cleanUpActivityReferences(any());
     }
 
     @Test
     public void testStackCleanupOnTaskRemoval() {
         mStack.removeChild(mTask, null /*reason*/);
         // Stack should be gone on task removal.
-        assertNull(mService.mRootWindowContainer.getStack(mStack.mStackId));
+        assertNull(mService.mRootWindowContainer.getStack(mStack.mTaskId));
     }
 
     @Test
     public void testNoCleanupMovingActivityInSameStack() {
-        final Task newTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack)
-                .build();
+        final Task newTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build();
         mActivity.reparent(newTask, 0, null /*reason*/);
-        verify(mStack, times(0)).onActivityRemovedFromStack(any());
+        verify(mStack, times(0)).cleanUpActivityReferences(any());
     }
 
     @Test
@@ -487,7 +489,7 @@
 
         final ActivityStack stack = new StackBuilder(mRootWindowContainer).build();
         try {
-            doReturn(false).when(stack).isStackTranslucent(any());
+            doReturn(false).when(stack).isTranslucent(any());
             assertFalse(mStack.shouldBeVisible(null /* starting */));
 
             mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
@@ -521,11 +523,12 @@
     }
 
     @Test
-    public void testShouldPauseWhenMakeClientVisible() {
+    public void testShouldStartWhenMakeClientActive() {
         ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build();
         topActivity.setOccludesParent(false);
         mActivity.setState(ActivityStack.ActivityState.STOPPED, "Testing");
-        mActivity.makeClientVisible();
+        mActivity.setVisibility(true);
+        mActivity.makeActiveIfNeeded(null /* activeActivity */);
         assertEquals(STARTED, mActivity.getState());
     }
 
@@ -609,8 +612,7 @@
         // 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");
+        mActivity.activityStopped(null /* savedState */, null /* persistentSavedState */, "desc");
         assertTrue(mActivity.hasSavedState());
         assertEquals(savedState, mActivity.getSavedState());
         assertEquals(persistentSavedState, mActivity.getPersistentSavedState());
@@ -1008,8 +1010,10 @@
     @Test
     public void testDestroyIfPossible_lastActivityAboveEmptyHomeStack() {
         // Empty the home stack.
-        final ActivityStack homeStack = mActivity.getDisplay().getHomeStack();
-        homeStack.forAllTasks((t) -> { homeStack.removeChild(t, "test"); });
+        final ActivityStack homeStack = mActivity.getDisplay().getRootHomeTask();
+        homeStack.forAllTasks((t) -> {
+            homeStack.removeChild(t, "test");
+        }, true /* traverseTopToBottom */, homeStack);
         mActivity.finishing = true;
         doReturn(false).when(mRootWindowContainer).resumeFocusedStacksTopActivities();
         spyOn(mStack);
@@ -1032,8 +1036,10 @@
     @Test
     public void testCompleteFinishing_lastActivityAboveEmptyHomeStack() {
         // Empty the home stack.
-        final ActivityStack homeStack = mActivity.getDisplay().getHomeStack();
-        homeStack.forAllTasks((t) -> { homeStack.removeChild(t, "test"); });
+        final ActivityStack homeStack = mActivity.getDisplay().getRootHomeTask();
+        homeStack.forAllTasks((t) -> {
+            homeStack.removeChild(t, "test");
+        }, true /* traverseTopToBottom */, homeStack);
         mActivity.finishing = true;
         spyOn(mStack);
 
@@ -1130,7 +1136,7 @@
 
     @Test
     public void testRemoveFromHistory() {
-        final ActivityStack stack = mActivity.getActivityStack();
+        final ActivityStack stack = mActivity.getRootTask();
         final Task task = mActivity.getTask();
 
         mActivity.removeFromHistory("test");
@@ -1139,7 +1145,7 @@
         assertNull(mActivity.app);
         assertNull(mActivity.getTask());
         assertEquals(0, task.getChildCount());
-        assertNull(task.getStack());
+        assertEquals(task.getStack(), task);
         assertEquals(0, stack.getChildCount());
     }
 
@@ -1174,4 +1180,160 @@
 
         verify(mActivity).removeFromHistory(anyString());
     }
+
+    @Test
+    public void testActivityOverridesProcessConfig() {
+        final WindowProcessController wpc = mActivity.app;
+        assertTrue(wpc.registeredForActivityConfigChanges());
+        assertFalse(wpc.registeredForDisplayConfigChanges());
+
+        final ActivityRecord secondaryDisplayActivity =
+                createActivityOnDisplay(false /* defaultDisplay */, null /* process */);
+
+        assertTrue(wpc.registeredForActivityConfigChanges());
+        assertEquals(0, mActivity.getMergedOverrideConfiguration()
+                .diff(wpc.getRequestedOverrideConfiguration()));
+        assertNotEquals(mActivity.getConfiguration(),
+                secondaryDisplayActivity.getConfiguration());
+    }
+
+    @Test
+    public void testActivityOverridesProcessConfig_TwoActivities() {
+        final WindowProcessController wpc = mActivity.app;
+        assertTrue(wpc.registeredForActivityConfigChanges());
+
+        final Task firstTaskRecord = mActivity.getTask();
+        final ActivityRecord secondActivityRecord =
+                new ActivityBuilder(mService).setTask(firstTaskRecord).setUseProcess(wpc).build();
+
+        assertTrue(wpc.registeredForActivityConfigChanges());
+        assertEquals(0, secondActivityRecord.getMergedOverrideConfiguration()
+                .diff(wpc.getRequestedOverrideConfiguration()));
+    }
+
+    @Test
+    public void testActivityOverridesProcessConfig_TwoActivities_SecondaryDisplay() {
+        final WindowProcessController wpc = mActivity.app;
+        assertTrue(wpc.registeredForActivityConfigChanges());
+
+        final ActivityRecord secondActivityRecord =
+                new ActivityBuilder(mService).setTask(mTask).setUseProcess(wpc).build();
+
+        assertTrue(wpc.registeredForActivityConfigChanges());
+        assertEquals(0, secondActivityRecord.getMergedOverrideConfiguration()
+                .diff(wpc.getRequestedOverrideConfiguration()));
+    }
+
+    @Test
+    public void testActivityOverridesProcessConfig_TwoActivities_DifferentTasks() {
+        final WindowProcessController wpc = mActivity.app;
+        assertTrue(wpc.registeredForActivityConfigChanges());
+
+        final ActivityRecord secondActivityRecord =
+                createActivityOnDisplay(true /* defaultDisplay */, wpc);
+
+        assertTrue(wpc.registeredForActivityConfigChanges());
+        assertEquals(0, secondActivityRecord.getMergedOverrideConfiguration()
+                .diff(wpc.getRequestedOverrideConfiguration()));
+    }
+
+    @Test
+    public void testActivityOnDifferentDisplayUpdatesProcessOverride() {
+        final ActivityRecord secondaryDisplayActivity =
+                createActivityOnDisplay(false /* defaultDisplay */, null /* process */);
+        final WindowProcessController wpc = secondaryDisplayActivity.app;
+        assertTrue(wpc.registeredForActivityConfigChanges());
+
+        final ActivityRecord secondActivityRecord =
+                createActivityOnDisplay(true /* defaultDisplay */, wpc);
+
+        assertTrue(wpc.registeredForActivityConfigChanges());
+        assertEquals(0, secondActivityRecord.getMergedOverrideConfiguration()
+                .diff(wpc.getRequestedOverrideConfiguration()));
+        assertFalse(wpc.registeredForDisplayConfigChanges());
+    }
+
+    @Test
+    public void testActivityReparentChangesProcessOverride() {
+        final WindowProcessController wpc = mActivity.app;
+        final Task initialTask = mActivity.getTask();
+        final Configuration initialConf =
+                new Configuration(mActivity.getMergedOverrideConfiguration());
+        assertEquals(0, mActivity.getMergedOverrideConfiguration()
+                .diff(wpc.getRequestedOverrideConfiguration()));
+        assertTrue(wpc.registeredForActivityConfigChanges());
+
+        // Create a new task with custom config to reparent the activity to.
+        final Task newTask =
+                new TaskBuilder(mSupervisor).setStack(initialTask.getStack()).build();
+        final Configuration newConfig = newTask.getConfiguration();
+        newConfig.densityDpi += 100;
+        newTask.onRequestedOverrideConfigurationChanged(newConfig);
+        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);
+
+        assertTrue(wpc.registeredForActivityConfigChanges());
+        assertNotEquals(initialConf, wpc.getRequestedOverrideConfiguration());
+        assertEquals(0, mActivity.getMergedOverrideConfiguration()
+                .diff(wpc.getRequestedOverrideConfiguration()));
+    }
+
+    @Test
+    public void testActivityReparentDoesntClearProcessOverride_TwoActivities() {
+        final WindowProcessController wpc = mActivity.app;
+        final Configuration initialConf =
+                new Configuration(mActivity.getMergedOverrideConfiguration());
+        final Task initialTask = mActivity.getTask();
+        final ActivityRecord secondActivity = new ActivityBuilder(mService).setTask(initialTask)
+                .setUseProcess(wpc).build();
+
+        assertTrue(wpc.registeredForActivityConfigChanges());
+        assertEquals(0, secondActivity.getMergedOverrideConfiguration()
+                .diff(wpc.getRequestedOverrideConfiguration()));
+
+        // Create a new task with custom config to reparent the second activity to.
+        final Task newTask =
+                new TaskBuilder(mSupervisor).setStack(initialTask.getStack()).build();
+        final Configuration newConfig = newTask.getConfiguration();
+        newConfig.densityDpi += 100;
+        newTask.onRequestedOverrideConfigurationChanged(newConfig);
+
+        // Reparent the activity and verify that config override changed.
+        secondActivity.reparent(newTask, 0 /* top */, "test");
+
+        assertTrue(wpc.registeredForActivityConfigChanges());
+        assertEquals(0, secondActivity.getMergedOverrideConfiguration()
+                .diff(wpc.getRequestedOverrideConfiguration()));
+        assertNotEquals(initialConf, wpc.getRequestedOverrideConfiguration());
+
+        // Reparent the first activity and verify that config override didn't change.
+        mActivity.reparent(newTask, 1 /* top */, "test");
+        assertTrue(wpc.registeredForActivityConfigChanges());
+        assertEquals(0, secondActivity.getMergedOverrideConfiguration()
+                .diff(wpc.getRequestedOverrideConfiguration()));
+        assertNotEquals(initialConf, wpc.getRequestedOverrideConfiguration());
+    }
+
+    /**
+     * Creates an activity on display. For non-default display request it will also create a new
+     * display with custom DisplayInfo.
+     */
+    private ActivityRecord createActivityOnDisplay(boolean defaultDisplay,
+            WindowProcessController process) {
+        final DisplayContent display;
+        if (defaultDisplay) {
+            display = mRootWindowContainer.getDefaultDisplay();
+        } else {
+            display = new TestDisplayContent.Builder(mService, 2000, 1000).setDensityDpi(300)
+                    .setPosition(DisplayContent.POSITION_TOP).build();
+        }
+        final ActivityStack stack = display.createStack(WINDOWING_MODE_UNDEFINED,
+                ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        final Task task = new TaskBuilder(mSupervisor).setStack(stack).build();
+        return new ActivityBuilder(mService).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 a5157fe9..393d8b8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -313,13 +313,13 @@
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
         // Home stack shouldn't be visible behind an opaque fullscreen stack, but pinned stack
         // should be visible since it is always on-top.
-        doReturn(false).when(fullscreenStack).isStackTranslucent(any());
+        doReturn(false).when(fullscreenStack).isTranslucent(any());
         assertFalse(homeStack.shouldBeVisible(null /* starting */));
         assertTrue(pinnedStack.shouldBeVisible(null /* starting */));
         assertTrue(fullscreenStack.shouldBeVisible(null /* starting */));
 
         // Home stack should be visible behind a translucent fullscreen stack.
-        doReturn(true).when(fullscreenStack).isStackTranslucent(any());
+        doReturn(true).when(fullscreenStack).isTranslucent(any());
         assertTrue(homeStack.shouldBeVisible(null /* starting */));
         assertTrue(pinnedStack.shouldBeVisible(null /* starting */));
     }
@@ -338,8 +338,8 @@
                 WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
 
         // Home stack shouldn't be visible if both halves of split-screen are opaque.
-        doReturn(false).when(splitScreenPrimary).isStackTranslucent(any());
-        doReturn(false).when(splitScreenSecondary).isStackTranslucent(any());
+        doReturn(false).when(splitScreenPrimary).isTranslucent(any());
+        doReturn(false).when(splitScreenSecondary).isTranslucent(any());
         assertFalse(homeStack.shouldBeVisible(null /* starting */));
         assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
         assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
@@ -350,7 +350,7 @@
                 splitScreenSecondary.getVisibility(null /* starting */));
 
         // Home stack should be visible if one of the halves of split-screen is translucent.
-        doReturn(true).when(splitScreenPrimary).isStackTranslucent(any());
+        doReturn(true).when(splitScreenPrimary).isTranslucent(any());
         assertTrue(homeStack.shouldBeVisible(null /* starting */));
         assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
         assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
@@ -366,7 +366,7 @@
                 WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
         // First split-screen secondary shouldn't be visible behind another opaque split-split
         // secondary.
-        doReturn(false).when(splitScreenSecondary2).isStackTranslucent(any());
+        doReturn(false).when(splitScreenSecondary2).isTranslucent(any());
         assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */));
         assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
         assertEquals(STACK_VISIBILITY_INVISIBLE,
@@ -376,7 +376,7 @@
 
         // First split-screen secondary should be visible behind another translucent split-screen
         // secondary.
-        doReturn(true).when(splitScreenSecondary2).isStackTranslucent(any());
+        doReturn(true).when(splitScreenSecondary2).isTranslucent(any());
         assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
         assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
         assertEquals(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
@@ -388,7 +388,7 @@
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */);
 
         // Split-screen stacks shouldn't be visible behind an opaque fullscreen stack.
-        doReturn(false).when(assistantStack).isStackTranslucent(any());
+        doReturn(false).when(assistantStack).isTranslucent(any());
         assertTrue(assistantStack.shouldBeVisible(null /* starting */));
         assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */));
         assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */));
@@ -403,7 +403,7 @@
                 splitScreenSecondary2.getVisibility(null /* starting */));
 
         // Split-screen stacks should be visible behind a translucent fullscreen stack.
-        doReturn(true).when(assistantStack).isStackTranslucent(any());
+        doReturn(true).when(assistantStack).isTranslucent(any());
         assertTrue(assistantStack.shouldBeVisible(null /* starting */));
         assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
         assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
@@ -418,9 +418,9 @@
                 splitScreenSecondary2.getVisibility(null /* starting */));
 
         // Assistant stack shouldn't be visible behind translucent split-screen stack
-        doReturn(false).when(assistantStack).isStackTranslucent(any());
-        doReturn(true).when(splitScreenPrimary).isStackTranslucent(any());
-        doReturn(true).when(splitScreenSecondary2).isStackTranslucent(any());
+        doReturn(false).when(assistantStack).isTranslucent(any());
+        doReturn(true).when(splitScreenPrimary).isTranslucent(any());
+        doReturn(true).when(splitScreenSecondary2).isTranslucent(any());
         splitScreenSecondary2.moveToFront("testShouldBeVisible_SplitScreen");
         splitScreenPrimary.moveToFront("testShouldBeVisible_SplitScreen");
         assertFalse(assistantStack.shouldBeVisible(null /* starting */));
@@ -555,7 +555,7 @@
         final ActivityStack translucentStack = createStackForShouldBeVisibleTest(
                 mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
                 true /* onTop */);
-        doReturn(true).when(translucentStack).isStackTranslucent(any());
+        doReturn(true).when(translucentStack).isTranslucent(any());
 
         assertTrue(homeStack.shouldBeVisible(null /* starting */));
         assertTrue(translucentStack.shouldBeVisible(null /* starting */));
@@ -603,8 +603,8 @@
         final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
 
-        doReturn(false).when(homeStack).isStackTranslucent(any());
-        doReturn(false).when(fullscreenStack).isStackTranslucent(any());
+        doReturn(false).when(homeStack).isTranslucent(any());
+        doReturn(false).when(fullscreenStack).isTranslucent(any());
 
         // Ensure that we don't move the home stack if it is already behind the top fullscreen stack
         int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack);
@@ -622,8 +622,8 @@
         final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
 
-        doReturn(false).when(homeStack).isStackTranslucent(any());
-        doReturn(true).when(fullscreenStack).isStackTranslucent(any());
+        doReturn(false).when(homeStack).isTranslucent(any());
+        doReturn(true).when(fullscreenStack).isTranslucent(any());
 
         // Ensure that we don't move the home stack if it is already behind the top fullscreen stack
         int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack);
@@ -641,8 +641,8 @@
         final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
 
-        doReturn(false).when(homeStack).isStackTranslucent(any());
-        doReturn(false).when(fullscreenStack).isStackTranslucent(any());
+        doReturn(false).when(homeStack).isTranslucent(any());
+        doReturn(false).when(fullscreenStack).isTranslucent(any());
 
         // Ensure we don't move the home stack if it is already on top
         int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack);
@@ -666,9 +666,9 @@
         final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
                 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
 
-        doReturn(false).when(homeStack).isStackTranslucent(any());
-        doReturn(false).when(fullscreenStack1).isStackTranslucent(any());
-        doReturn(false).when(fullscreenStack2).isStackTranslucent(any());
+        doReturn(false).when(homeStack).isTranslucent(any());
+        doReturn(false).when(fullscreenStack1).isTranslucent(any());
+        doReturn(false).when(fullscreenStack2).isTranslucent(any());
 
         // Ensure that we move the home stack behind the bottom most fullscreen stack, ignoring the
         // pinned stack
@@ -691,9 +691,9 @@
                 mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
                 true /* onTop */);
 
-        doReturn(false).when(homeStack).isStackTranslucent(any());
-        doReturn(false).when(fullscreenStack1).isStackTranslucent(any());
-        doReturn(true).when(fullscreenStack2).isStackTranslucent(any());
+        doReturn(false).when(homeStack).isTranslucent(any());
+        doReturn(false).when(fullscreenStack1).isTranslucent(any());
+        doReturn(true).when(fullscreenStack2).isTranslucent(any());
 
         // Ensure that we move the home stack behind the bottom most non-translucent fullscreen
         // stack
@@ -715,9 +715,9 @@
         final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
 
-        doReturn(false).when(homeStack).isStackTranslucent(any());
-        doReturn(false).when(fullscreenStack1).isStackTranslucent(any());
-        doReturn(false).when(fullscreenStack2).isStackTranslucent(any());
+        doReturn(false).when(homeStack).isTranslucent(any());
+        doReturn(false).when(fullscreenStack1).isTranslucent(any());
+        doReturn(false).when(fullscreenStack2).isTranslucent(any());
 
         // Ensure we don't move the home stack behind itself
         int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack);
@@ -810,9 +810,9 @@
         final ActivityStack assistantStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */);
 
-        doReturn(false).when(splitScreenPrimary).isStackTranslucent(any());
-        doReturn(false).when(splitScreenSecondary).isStackTranslucent(any());
-        doReturn(false).when(assistantStack).isStackTranslucent(any());
+        doReturn(false).when(splitScreenPrimary).isTranslucent(any());
+        doReturn(false).when(splitScreenSecondary).isTranslucent(any());
+        doReturn(false).when(assistantStack).isTranslucent(any());
 
         assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */));
         assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */));
@@ -829,7 +829,7 @@
             boolean translucent) {
         final ActivityStack stack = createStackForShouldBeVisibleTest(mDefaultDisplay,
                 windowingMode, ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        doReturn(translucent).when(stack).isStackTranslucent(any());
+        doReturn(translucent).when(stack).isTranslucent(any());
         return stack;
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
index 399cf49..135d005 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
 import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
@@ -44,6 +45,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.app.BlockedAppActivity;
 import com.android.internal.app.HarmfulAppWarningActivity;
 import com.android.internal.app.SuspendedAppActivity;
 import com.android.internal.app.UnlaunchableAppActivity;
@@ -105,6 +107,8 @@
     private PackageManagerService mPackageManager;
     @Mock
     private ActivityManagerInternal mAmInternal;
+    @Mock
+    private LockTaskController mLockTaskController;
 
     private ActivityStartInterceptor mInterceptor;
     private ActivityInfo mAInfo = new ActivityInfo();
@@ -145,6 +149,13 @@
         when(mPackageManager.getHarmfulAppWarning(TEST_PACKAGE_NAME, TEST_USER_ID))
                 .thenReturn(null);
 
+        // Mock LockTaskController
+        mAInfo.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
+        when(mService.getLockTaskController()).thenReturn(mLockTaskController);
+        when(mLockTaskController.isActivityAllowed(
+                TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT))
+                .thenReturn(true);
+
         // Initialise activity info
         mAInfo.applicationInfo = new ApplicationInfo();
         mAInfo.packageName = mAInfo.applicationInfo.packageName = TEST_PACKAGE_NAME;
@@ -196,6 +207,18 @@
     }
 
     @Test
+    public void testInterceptLockTaskModeViolationPackage() {
+        when(mLockTaskController.isActivityAllowed(
+                TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT))
+                .thenReturn(false);
+
+        assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
+
+        assertTrue(BlockedAppActivity.createIntent(TEST_USER_ID, TEST_PACKAGE_NAME)
+                .filterEquals(mInterceptor.mIntent));
+    }
+
+    @Test
     public void testInterceptQuietProfile() {
         // GIVEN that the user the activity is starting as is currently in quiet mode
         when(mUserManager.isQuietModeEnabled(eq(UserHandle.of(TEST_USER_ID)))).thenReturn(true);
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 9e54f40..0fc2bc5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -153,7 +153,7 @@
         assertThat((Object) task2.getStack()).isInstanceOf(ActivityStack.class);
         mStarter.updateBounds(task2, bounds);
 
-        verify(mService, times(1)).animateResizePinnedStack(eq(task2.getStack().mStackId),
+        verify(mService, times(1)).animateResizePinnedStack(eq(task2.getRootTaskId()),
                 eq(bounds), anyInt());
 
         // In the case of no animation, the stack and task bounds should be set immediately.
@@ -229,11 +229,12 @@
                 service.mStackSupervisor, mock(ActivityStartInterceptor.class));
         prepareStarter(launchFlags);
         final IApplicationThread caller = mock(IApplicationThread.class);
+        final WindowProcessListener listener = mock(WindowProcessListener.class);
 
         final WindowProcessController wpc =
                 containsConditions(preconditions, PRECONDITION_NO_CALLER_APP)
                 ? null : new WindowProcessController(
-                        service, mock(ApplicationInfo.class), null, 0, -1, null, null);
+                        service, mock(ApplicationInfo.class), null, 0, -1, null, listener);
         doReturn(wpc).when(service).getProcessController(anyObject());
 
         final Intent intent = new Intent();
@@ -446,13 +447,13 @@
                 new ActivityBuilder(mService).setCreateTask(true).build();
         final ActivityRecord splitSecondReusableActivity =
                 new ActivityBuilder(mService).setCreateTask(true).build();
-        splitPrimaryFocusActivity.getActivityStack()
+        splitPrimaryFocusActivity.getRootTask()
                 .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-        splitSecondReusableActivity.getActivityStack()
+        splitSecondReusableActivity.getRootTask()
                 .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
 
         // Set focus back to primary.
-        splitPrimaryFocusActivity.getActivityStack().moveToFront("testSplitScreenDeliverToTop");
+        splitPrimaryFocusActivity.getRootTask().moveToFront("testSplitScreenDeliverToTop");
 
         // Start activity and delivered new intent.
         starter.getIntent().setComponent(splitSecondReusableActivity.mActivityComponent);
@@ -477,18 +478,18 @@
                 new ActivityBuilder(mService).setCreateTask(true).build();
         final ActivityRecord splitPrimaryFocusActivity =
                 new ActivityBuilder(mService).setCreateTask(true).build();
-        splitPrimaryFocusActivity.getActivityStack()
+        splitPrimaryFocusActivity.getRootTask()
                 .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-        splitSecondReusableActivity.getActivityStack()
+        splitSecondReusableActivity.getRootTask()
                 .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-        splitSecondTopActivity.getActivityStack()
+        splitSecondTopActivity.getRootTask()
                 .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
 
         // Make it on top of split-screen-secondary.
-        splitSecondTopActivity.getActivityStack().moveToFront("testSplitScreenTaskToFront");
+        splitSecondTopActivity.getRootTask().moveToFront("testSplitScreenTaskToFront");
 
         // Let primary stack has focus.
-        splitPrimaryFocusActivity.getActivityStack().moveToFront("testSplitScreenTaskToFront");
+        splitPrimaryFocusActivity.getRootTask().moveToFront("testSplitScreenTaskToFront");
 
         // Start activity and delivered new intent.
         starter.getIntent().setComponent(splitSecondReusableActivity.mActivityComponent);
@@ -680,10 +681,11 @@
         doReturn(realCallingUidProcState).when(mService).getUidState(realCallingUid);
         // foreground activities
         final IApplicationThread caller = mock(IApplicationThread.class);
+        final WindowProcessListener listener = mock(WindowProcessListener.class);
         final ApplicationInfo ai = new ApplicationInfo();
         ai.uid = callingUid;
         final WindowProcessController callerApp =
-                new WindowProcessController(mService, ai, null, callingUid, -1, null, null);
+                new WindowProcessController(mService, ai, null, callingUid, -1, null, listener);
         callerApp.setHasForegroundActivities(hasForegroundActivities);
         doReturn(callerApp).when(mService).getProcessController(caller);
         // caller is recents
@@ -732,13 +734,13 @@
         final ActivityRecord activity = new ActivityBuilder(mService)
                 .setCreateTask(true).build();
         new ActivityBuilder(mService)
-                .setStack(activity.getActivityStack())
+                .setStack(activity.getRootTask())
                 .setCreateTask(true).build();
 
         // Create a top finishing activity.
         final ActivityRecord finishingTopActivity = new ActivityBuilder(mService)
                 .setCreateTask(true).build();
-        finishingTopActivity.getActivityStack().moveToFront("finishingTopActivity");
+        finishingTopActivity.getRootTask().moveToFront("finishingTopActivity");
 
         assertEquals(finishingTopActivity, mRootWindowContainer.topRunningActivity());
         finishingTopActivity.finishing = true;
@@ -997,8 +999,8 @@
         assertThat(outActivity[0].inSplitScreenWindowingMode()).isFalse();
 
         // Move activity to split-screen-primary stack and make sure it has the focus.
-        top.getActivityStack().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-        top.getActivityStack().moveToFront("testWindowingModeOptionsLaunchAdjacent");
+        top.getRootTask().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        top.getRootTask().moveToFront("testWindowingModeOptionsLaunchAdjacent");
 
         // Activity must landed on split-screen-secondary when launch adjacent.
         starter.setActivityOptions(options.toBundle())
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 079c49f..d22502d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -27,6 +27,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -153,6 +154,19 @@
     }
 
     @Test
+    public void testContainerChanges() {
+        removeGlobalMinSizeRestriction();
+        final ActivityStack stack = new StackBuilder(mRootWindowContainer)
+                .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+        final Task task = stack.getTopMostTask();
+        WindowContainerTransaction t = new WindowContainerTransaction();
+        assertTrue(task.isFocusable());
+        t.setFocusable(stack.mRemoteToken, false);
+        mService.applyContainerTransaction(t);
+        assertFalse(task.isFocusable());
+    }
+
+    @Test
     public void testDisplayWindowListener() {
         final ArrayList<Integer> added = new ArrayList<>();
         final ArrayList<Integer> changed = new ArrayList<>();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index 0f22724..eb84d0a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -111,6 +111,7 @@
         private int mConfigChanges;
         private int mLaunchedFromPid;
         private int mLaunchedFromUid;
+        private WindowProcessController mWpc;
 
         ActivityBuilder(ActivityTaskManagerService service) {
             mService = service;
@@ -201,6 +202,11 @@
             return this;
         }
 
+        ActivityBuilder setUseProcess(WindowProcessController wpc) {
+            mWpc = wpc;
+            return this;
+        }
+
         ActivityRecord build() {
             try {
                 mService.deferWindowLayout();
@@ -263,10 +269,16 @@
                 activity.setVisible(true);
             }
 
-            final WindowProcessController wpc = new WindowProcessController(mService,
-                    mService.mContext.getApplicationInfo(), mProcessName, mUid,
-                    UserHandle.getUserId(12345), mock(Object.class),
-                    mock(WindowProcessListener.class));
+            final WindowProcessController wpc;
+            if (mWpc != null) {
+                wpc = mWpc;
+            } else {
+                wpc = new WindowProcessController(mService,
+                        mService.mContext.getApplicationInfo(), mProcessName, mUid,
+                        UserHandle.getUserId(12345), mock(Object.class),
+                        mock(WindowProcessListener.class));
+                wpc.setThread(mock(IApplicationThread.class));
+            }
             wpc.setThread(mock(IApplicationThread.class));
             activity.setProcess(wpc);
             doReturn(wpc).when(mService).getProcessController(
@@ -363,7 +375,7 @@
             intent.setComponent(mComponent);
             intent.setFlags(mFlags);
 
-            final Task task = new Task(mSupervisor.mService, mTaskId, aInfo,
+            final Task task = new ActivityStack(mSupervisor.mService, mTaskId, aInfo,
                     intent /*intent*/, mVoiceSession, null /*_voiceInteractor*/,
                     null /*taskDescription*/, mStack);
             spyOn(task);
@@ -386,6 +398,8 @@
         private int mActivityType = ACTIVITY_TYPE_STANDARD;
         private boolean mOnTop = true;
         private boolean mCreateActivity = true;
+        private ActivityInfo mInfo;
+        private Intent mIntent;
 
         StackBuilder(RootWindowContainer root) {
             mRootWindowContainer = root;
@@ -422,13 +436,22 @@
             return this;
         }
 
+        StackBuilder setActivityInfo(ActivityInfo info) {
+            mInfo = info;
+            return this;
+        }
+
+        StackBuilder setIntent(Intent intent) {
+            mIntent = intent;
+            return this;
+        }
+
         ActivityStack build() {
             final int stackId = mStackId >= 0 ? mStackId : mDisplay.getNextStackId();
-            final ActivityStack stack;
+            final ActivityStack stack = mDisplay.createStackUnchecked(mWindowingMode,
+                    mActivityType, stackId, mOnTop, mInfo, mIntent);
             final ActivityStackSupervisor supervisor = mRootWindowContainer.mStackSupervisor;
 
-            stack = mDisplay.createStackUnchecked(mWindowingMode, mActivityType, stackId, mOnTop);
-
             if (mCreateActivity) {
                 new ActivityBuilder(supervisor.mService)
                         .setCreateTask(true)
diff --git a/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java b/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java
index 574517a..71390db 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java
@@ -40,7 +40,7 @@
  * Tests for the {@link ActivityStack} class.
  *
  * Build/Install/Run:
- *  atest FrameworksServicesTests:AnimatingActivityRegistryTest
+ *  atest WmTests:AnimatingActivityRegistryTest
  */
 @SmallTest
 @Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 1311889..f6213bd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -42,7 +42,7 @@
 
 /**
  * Build/Install/Run:
- *  atest FrameworksServicesTests:AppTransitionControllerTest
+ *  atest WmTests:AppTransitionControllerTest
  */
 @SmallTest
 @Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java
index 0ad0f95..1dda535 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java
@@ -60,7 +60,7 @@
  * appropriately.
  *
  * Build/Install/Run:
- *  atest FrameworksServicesTests:BoundsAnimationControllerTests
+ *  atest WmTests:BoundsAnimationControllerTests
  */
 @SmallTest
 @Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index 0aa6961..7344fa4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -27,8 +27,11 @@
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.when;
 
+import android.annotation.Nullable;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.view.SurfaceControl;
@@ -40,7 +43,7 @@
 
 /**
  * Build/Install/Run:
- *  atest FrameworksServicesTests:DimmerTests
+ *  atest WmTests:DimmerTests
  */
 @Presubmit
 @RunWith(WindowTestRunner.class)
@@ -111,8 +114,12 @@
     private static class SurfaceAnimatorStarterImpl implements Dimmer.SurfaceAnimatorStarter {
         @Override
         public void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t,
-                AnimationAdapter anim, boolean hidden) {
-            surfaceAnimator.mAnimationFinishedCallback.run();
+                AnimationAdapter anim, boolean hidden,
+                @Nullable Runnable animationFinishedCallback) {
+            surfaceAnimator.mStaticAnimationFinishedCallback.run();
+            if (animationFinishedCallback != null) {
+                animationFinishedCallback.run();
+            }
         }
     }
 
@@ -216,7 +223,8 @@
 
         mDimmer.updateDims(mTransaction, new Rect());
         verify(mSurfaceAnimatorStarter).startAnimation(any(SurfaceAnimator.class), any(
-                SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean());
+                SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(),
+                isNull());
         verify(mHost.getPendingTransaction()).remove(dimLayer);
     }
 
@@ -273,7 +281,8 @@
         mDimmer.resetDimStates();
         mDimmer.updateDims(mTransaction, new Rect());
         verify(mSurfaceAnimatorStarter, never()).startAnimation(any(SurfaceAnimator.class), any(
-                SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean());
+                SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(),
+                isNull());
         verify(mTransaction).remove(dimLayer);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index f2ba97c..1637370 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -39,6 +39,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+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_VOICE_INTERACTION;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
@@ -139,6 +140,7 @@
                 mChildAppWindowAbove,
                 mDockedDividerWindow,
                 mStatusBarWindow,
+                mNotificationShadeWindow,
                 mNavBarWindow,
                 mImeWindow,
                 mImeDialogWindow));
@@ -161,6 +163,7 @@
                 mImeDialogWindow,
                 mDockedDividerWindow,
                 mStatusBarWindow,
+                mNotificationShadeWindow,
                 mNavBarWindow));
     }
 
@@ -177,6 +180,7 @@
                 mImeDialogWindow,
                 mDockedDividerWindow,
                 mStatusBarWindow,
+                mNotificationShadeWindow,
                 mNavBarWindow));
     }
 
@@ -193,6 +197,24 @@
                 mStatusBarWindow,
                 mImeWindow,
                 mImeDialogWindow,
+                mNotificationShadeWindow,
+                mNavBarWindow));
+    }
+
+    @Test
+    public void testForAllWindows_WithNotificationShadeImeTarget() throws Exception {
+        mDisplayContent.mInputMethodTarget = mNotificationShadeWindow;
+
+        assertForAllWindowsOrder(Arrays.asList(
+                mWallpaperWindow,
+                mChildAppWindowBelow,
+                mAppWindow,
+                mChildAppWindowAbove,
+                mDockedDividerWindow,
+                mStatusBarWindow,
+                mNotificationShadeWindow,
+                mImeWindow,
+                mImeDialogWindow,
                 mNavBarWindow));
     }
 
@@ -211,6 +233,7 @@
                 mDockedDividerWindow,
                 voiceInteractionWindow,
                 mStatusBarWindow,
+                mNotificationShadeWindow,
                 mNavBarWindow,
                 mImeWindow,
                 mImeDialogWindow));
@@ -585,7 +608,7 @@
         final WindowState window = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w");
         window.mActivityRecord.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
 
-        final WindowState keyguard = createWindow(null, TYPE_STATUS_BAR, dc, "keyguard");
+        final WindowState keyguard = createWindow(null, TYPE_NOTIFICATION_SHADE , dc, "keyguard");
         keyguard.mHasSurface = true;
         keyguard.mAttrs.screenOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 0527561..5ba676d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -36,7 +36,6 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -141,7 +140,7 @@
     public void layoutWindowLw_fitStatusBars() {
         assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
 
-        mWindow.mAttrs.setFitWindowInsetsTypes(Type.statusBars());
+        mWindow.mAttrs.setFitInsetsTypes(Type.statusBars());
         addWindow(mWindow);
 
         mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -159,7 +158,7 @@
     public void layoutWindowLw_fitNavigationBars() {
         assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
 
-        mWindow.mAttrs.setFitWindowInsetsTypes(Type.navigationBars());
+        mWindow.mAttrs.setFitInsetsTypes(Type.navigationBars());
         addWindow(mWindow);
 
         mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -178,7 +177,7 @@
         assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
 
         mWindow.mAttrs.flags = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
-        mWindow.mAttrs.setFitWindowInsetsTypes(Type.systemBars());
+        mWindow.mAttrs.setFitInsetsTypes(Type.systemBars());
         addWindow(mWindow);
 
         mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -197,7 +196,7 @@
         assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
 
         mWindow.mAttrs.privateFlags = PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
-        mWindow.mAttrs.setFitWindowInsetsTypes(Type.systemBars());
+        mWindow.mAttrs.setFitInsetsTypes(Type.systemBars());
         addWindow(mWindow);
 
         mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -216,8 +215,7 @@
         assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
 
         mWindow.mAttrs.flags = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
-        mWindow.mAttrs.privateFlags = PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND;
-        mWindow.mAttrs.setFitWindowInsetsTypes(Type.systemBars());
+        mWindow.mAttrs.setFitInsetsTypes(Type.systemBars());
         addWindow(mWindow);
 
         mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -235,7 +233,7 @@
     public void layoutWindowLw_fitAllSides() {
         assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
 
-        mWindow.mAttrs.setFitWindowInsetsSides(Side.all());
+        mWindow.mAttrs.setFitInsetsSides(Side.all());
         addWindow(mWindow);
 
         mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -253,7 +251,7 @@
     public void layoutWindowLw_fitTopOnly() {
         assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
 
-        mWindow.mAttrs.setFitWindowInsetsSides(Side.TOP);
+        mWindow.mAttrs.setFitInsetsSides(Side.TOP);
         addWindow(mWindow);
 
         mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -275,7 +273,7 @@
                 mDisplayContent.getInsetsPolicy().getInsetsForDispatch(mWindow);
         state.getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
         state.getSource(InsetsState.ITYPE_NAVIGATION_BAR).setVisible(false);
-        mWindow.mAttrs.setFitIgnoreVisibility(true);
+        mWindow.mAttrs.setFitInsetsIgnoringVisibility(true);
         addWindow(mWindow);
 
         mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -297,7 +295,7 @@
                 mDisplayContent.getInsetsPolicy().getInsetsForDispatch(mWindow);
         state.getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
         state.getSource(InsetsState.ITYPE_NAVIGATION_BAR).setVisible(false);
-        mWindow.mAttrs.setFitIgnoreVisibility(false);
+        mWindow.mAttrs.setFitInsetsIgnoringVisibility(false);
         addWindow(mWindow);
 
         mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -374,7 +372,7 @@
         mWindow.mAttrs.flags =
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
         mWindow.mAttrs.systemUiVisibility = SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
-        mWindow.mAttrs.setFitWindowInsetsTypes(0 /* types */);
+        mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
         addWindow(mWindow);
 
         mDisplayPolicy.beginLayoutLw(mFrames, 0 /* uiMode */);
@@ -431,8 +429,8 @@
         mWindow.mAttrs.flags =
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
         mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
-        mWindow.mAttrs.setFitWindowInsetsTypes(
-                mWindow.mAttrs.getFitWindowInsetsTypes() & ~Type.statusBars());
+        mWindow.mAttrs.setFitInsetsTypes(
+                mWindow.mAttrs.getFitInsetsTypes() & ~Type.statusBars());
         addWindow(mWindow);
 
         mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -537,8 +535,8 @@
         mWindow.mAttrs.flags =
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
         mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
-        mWindow.mAttrs.setFitWindowInsetsTypes(
-                mWindow.mAttrs.getFitWindowInsetsTypes() & ~Type.statusBars());
+        mWindow.mAttrs.setFitInsetsTypes(
+                mWindow.mAttrs.getFitInsetsTypes() & ~Type.statusBars());
         addWindow(mWindow);
 
         mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -556,7 +554,7 @@
         addDisplayCutout();
 
         mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN;
-        mWindow.mAttrs.setFitWindowInsetsTypes(Type.systemBars() & ~Type.statusBars());
+        mWindow.mAttrs.setFitInsetsTypes(Type.systemBars() & ~Type.statusBars());
         mWindow.mAttrs.type = TYPE_APPLICATION_OVERLAY;
         mWindow.mAttrs.width = DISPLAY_WIDTH;
         mWindow.mAttrs.height = DISPLAY_HEIGHT;
@@ -577,8 +575,8 @@
         mWindow.mAttrs.flags =
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
         mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
-        mWindow.mAttrs.setFitWindowInsetsTypes(
-                mWindow.mAttrs.getFitWindowInsetsTypes() & ~Type.statusBars());
+        mWindow.mAttrs.setFitInsetsTypes(
+                mWindow.mAttrs.getFitInsetsTypes() & ~Type.statusBars());
         mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         addWindow(mWindow);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index e699b52..c370d6c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -28,6 +28,7 @@
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
@@ -233,6 +234,16 @@
         assertNotEquals(0, toast.getAttrs().flags & FLAG_SHOW_WHEN_LOCKED);
     }
 
+    @Test(expected = RuntimeException.class)
+    public void testMainAppWindowDisallowFitSystemWindowTypes() {
+        final DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
+        final WindowState activity = createBaseApplicationWindow();
+        activity.mAttrs.privateFlags |= PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
+
+        policy.adjustWindowParamsLw(activity, activity.mAttrs, 0 /* callingPid */,
+                0 /* callingUid */);
+    }
+
     private WindowState createToastWindow() {
         final WindowState win = createWindow(null, TYPE_TOAST, "Toast");
         final WindowManager.LayoutParams attrs = win.mAttrs;
@@ -254,6 +265,17 @@
         return win;
     }
 
+    private WindowState createBaseApplicationWindow() {
+        final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, "Application");
+        final WindowManager.LayoutParams attrs = win.mAttrs;
+        attrs.width = MATCH_PARENT;
+        attrs.height = MATCH_PARENT;
+        attrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
+        attrs.format = PixelFormat.OPAQUE;
+        win.mHasSurface = true;
+        return win;
+    }
+
     @Test
     @FlakyTest(bugId = 131005232)
     public void testOverlappingWithNavBar() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
new file mode 100644
index 0000000..032edde
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
@@ -0,0 +1,161 @@
+/*
+ * 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.wm;
+
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * This file tests WM setting the priority on windows that is used in SF to determine at what
+ * frame rate the Display should run. Any changes to the algorithm should be reflected in these
+ * tests.
+ *
+ * Build/Install/Run: atest FrameRateSelectionPriority
+ */
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class FrameRateSelectionPriorityTests extends WindowTestsBase {
+
+    @Test
+    public void basicTest() {
+        final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
+        assertNotNull("Window state is created", appWindow);
+        assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+
+        appWindow.updateFrameRateSelectionPriorityIfNeeded();
+        // Priority doesn't change.
+        assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+
+        // Call the function a few times.
+        appWindow.updateFrameRateSelectionPriorityIfNeeded();
+        appWindow.updateFrameRateSelectionPriorityIfNeeded();
+
+        // Since nothing changed in the priority state, the transaction should not be updating.
+        verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority(
+                appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+    }
+
+    @Test
+    public void testApplicationInFocusWithoutModeId() {
+        final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
+        assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+        assertEquals(appWindow.getDisplayContent().getDisplayPolicy().getRefreshRatePolicy()
+                .getPreferredModeId(appWindow), 0);
+
+        appWindow.updateFrameRateSelectionPriorityIfNeeded();
+        // Priority stays MAX_VALUE.
+        assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+        verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority(
+                appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+
+        // Application is in focus.
+        appWindow.mToken.mDisplayContent.mCurrentFocus = appWindow;
+        appWindow.updateFrameRateSelectionPriorityIfNeeded();
+        // Priority changes to 1.
+        assertEquals(appWindow.mFrameRateSelectionPriority, 1);
+        verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
+                appWindow.getSurfaceControl(), 1);
+    }
+
+    @Test
+    public void testApplicationInFocusWithModeId() {
+        final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
+        assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+
+        // Application is in focus.
+        appWindow.mToken.mDisplayContent.mCurrentFocus = appWindow;
+        appWindow.updateFrameRateSelectionPriorityIfNeeded();
+        // Priority changes.
+        assertEquals(appWindow.mFrameRateSelectionPriority, 1);
+        // Update the mode ID to a requested number.
+        appWindow.mAttrs.preferredDisplayModeId = 1;
+        appWindow.updateFrameRateSelectionPriorityIfNeeded();
+        // Priority changes.
+        assertEquals(appWindow.mFrameRateSelectionPriority, 0);
+
+        // Remove the mode ID request.
+        appWindow.mAttrs.preferredDisplayModeId = 0;
+        appWindow.updateFrameRateSelectionPriorityIfNeeded();
+        // Priority changes.
+        assertEquals(appWindow.mFrameRateSelectionPriority, 1);
+
+        // Verify we called actions on Transactions correctly.
+        verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority(
+                appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+        verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
+                appWindow.getSurfaceControl(), 0);
+        verify(appWindow.getPendingTransaction(), times(2)).setFrameRateSelectionPriority(
+                appWindow.getSurfaceControl(), 1);
+    }
+
+    @Test
+    public void testApplicationNotInFocusWithModeId() {
+        final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
+        assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+
+        final WindowState inFocusWindow = createWindow(null, TYPE_APPLICATION, "inFocus");
+        appWindow.mToken.mDisplayContent.mCurrentFocus = inFocusWindow;
+
+        appWindow.updateFrameRateSelectionPriorityIfNeeded();
+        // The window is not in focus.
+        assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+
+        // Update the mode ID to a requested number.
+        appWindow.mAttrs.preferredDisplayModeId = 1;
+        appWindow.updateFrameRateSelectionPriorityIfNeeded();
+        // Priority changes.
+        assertEquals(appWindow.mFrameRateSelectionPriority, 2);
+
+        verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
+                appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+        verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
+                appWindow.getSurfaceControl(), 2);
+    }
+
+    @Test
+    public void testApplicationNotInFocusWithoutModeId() {
+        final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
+        assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+
+        final WindowState inFocusWindow = createWindow(null, TYPE_APPLICATION, "inFocus");
+        appWindow.mToken.mDisplayContent.mCurrentFocus = inFocusWindow;
+
+        appWindow.updateFrameRateSelectionPriorityIfNeeded();
+        // The window is not in focus.
+        assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+
+        // Make sure that the mode ID is not set.
+        appWindow.mAttrs.preferredDisplayModeId = 0;
+        appWindow.updateFrameRateSelectionPriorityIfNeeded();
+        // Priority doesn't change.
+        assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+
+        verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
+                appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index df34c7c..c3bead7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -22,13 +22,16 @@
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
@@ -118,11 +121,11 @@
 
     @Test
     public void testControlsForDispatch_keyguard() {
-        addWindow(TYPE_STATUS_BAR, "statusBar").mAttrs.privateFlags |= PRIVATE_FLAG_KEYGUARD;
+        addWindow(TYPE_NOTIFICATION_SHADE, "notificationShade");
         addWindow(TYPE_NAVIGATION_BAR, "navBar");
+        mockKeyguardShowing();
 
         final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
-
         // The app must not control the top bar.
         assertNotNull(controls);
         assertEquals(1, controls.length);
@@ -130,9 +133,9 @@
 
     // TODO: adjust this test if we pretend to the app that it's still able to control it.
     @Test
-    public void testControlsForDispatch_forceStatusBarVisibleTransparent() {
-        addWindow(TYPE_STATUS_BAR, "statusBar").mAttrs.privateFlags |=
-                PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT;
+    public void testControlsForDispatch_forceStatusBarVisible() {
+        addWindow(TYPE_STATUS_BAR, "topBar").mAttrs.privateFlags |=
+                PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
         addWindow(TYPE_NAVIGATION_BAR, "navBar");
 
         final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
@@ -144,7 +147,7 @@
 
     @Test
     public void testControlsForDispatch_statusBarForceShowNavigation() {
-        addWindow(TYPE_STATUS_BAR, "statusBar").mAttrs.privateFlags |=
+        addWindow(TYPE_NOTIFICATION_SHADE, "notificationShade").mAttrs.privateFlags |=
                 PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
         addWindow(TYPE_NAVIGATION_BAR, "navBar");
 
@@ -257,4 +260,10 @@
         mDisplayContent.getInsetsPolicy().updateBarControlTarget(win);
         return mDisplayContent.getInsetsStateController().getControlsForDispatch(win);
     }
+
+    private void mockKeyguardShowing() {
+        final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
+        spyOn(displayPolicy);
+        doReturn(true).when(displayPolicy).isKeyguardShowing();
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
index 09ac9ce..d819b1a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
@@ -63,6 +63,26 @@
         assertEquals(Insets.of(0, 100, 0, 0),
                 mProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500),
                         false /* ignoreVisibility */));
+        assertEquals(Insets.of(0, 100, 0, 0),
+                mProvider.getSource().calculateVisibleInsets(new Rect(0, 0, 500, 500)));
+    }
+
+    @Test
+    public void testPostLayout_givenInsets() {
+        final WindowState ime = createWindow(null, TYPE_APPLICATION, "ime");
+        ime.getFrameLw().set(0, 0, 500, 100);
+        ime.getGivenContentInsetsLw().set(0, 0, 0, 60);
+        ime.getGivenVisibleInsetsLw().set(0, 0, 0, 75);
+        ime.mHasSurface = true;
+        mProvider.setWindow(ime, null);
+        mProvider.onPostLayout();
+        assertEquals(new Rect(0, 0, 500, 40), mProvider.getSource().getFrame());
+        assertEquals(new Rect(0, 0, 500, 25), mProvider.getSource().getVisibleFrame());
+        assertEquals(Insets.of(0, 40, 0, 0),
+                mProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500),
+                        false /* ignoreVisibility */));
+        assertEquals(Insets.of(0, 25, 0, 0),
+                mProvider.getSource().calculateVisibleInsets(new Rect(0, 0, 500, 500)));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java
index cf1f0a8..1144272a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java
@@ -284,7 +284,7 @@
 
         doNothing().when(mService).moveStackToDisplay(anyInt(), anyInt());
         mController.layoutTask(task, null /* windowLayout */);
-        verify(mService, times(1)).moveStackToDisplay(eq(task.getStackId()),
+        verify(mService, times(1)).moveStackToDisplay(eq(task.getRootTaskId()),
                 eq(params.mPreferredDisplayId));
     }
 
@@ -421,7 +421,7 @@
         }
 
         @Override
-        void saveTask(Task task) {
+        void saveTask(Task task, DisplayContent display) {
             final int userId = task.mUserId;
             final ComponentName realActivity = task.realActivity;
             mTmpParams.mPreferredDisplayId = task.getDisplayId();
diff --git a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
index 039ff60..75ec53d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
@@ -29,6 +29,9 @@
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD;
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
 import static android.os.Process.SYSTEM_UID;
 import static android.telecom.TelecomManager.EMERGENCY_DIALER_COMPONENT;
 
@@ -693,6 +696,38 @@
         assertTrue((StatusBarManager.DISABLE2_QUICK_SETTINGS & flags.second) != 0);
     }
 
+    @Test
+    public void testIsActivityAllowed() {
+        // WHEN lock task mode is not enabled
+        assertTrue(mLockTaskController.isActivityAllowed(
+                TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT));
+
+        // WHEN lock task mode is enabled
+        Task tr = getTask(Task.LOCK_TASK_AUTH_WHITELISTED);
+        mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
+
+
+        // package with LOCK_TASK_LAUNCH_MODE_ALWAYS should always be allowed
+        assertTrue(mLockTaskController.isActivityAllowed(
+                TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_ALWAYS));
+
+        // unwhitelisted package should not be allowed
+        assertFalse(mLockTaskController.isActivityAllowed(
+                TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT));
+
+        // update the whitelist
+        String[] whitelist = new String[] { TEST_PACKAGE_NAME };
+        mLockTaskController.updateLockTaskPackages(TEST_USER_ID, whitelist);
+
+        // whitelisted package should be allowed
+        assertTrue(mLockTaskController.isActivityAllowed(
+                TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT));
+
+        // package with LOCK_TASK_LAUNCH_MODE_NEVER should never be allowed
+        assertFalse(mLockTaskController.isActivityAllowed(
+                TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_NEVER));
+    }
+
     private Task getTask(int lockTaskAuth) {
         return getTask(TEST_PACKAGE_NAME, lockTaskAuth);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 79f808e..b5e7dd5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -303,7 +303,7 @@
         // other task
         Task task1 = createTaskBuilder(".Task1")
                 .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
-                .setStack(mDisplay.getHomeStack()).build();
+                .setStack(mDisplay.getRootHomeTask()).build();
         Task task2 = createTaskBuilder(".Task1")
                 .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
                 .setStack(mStack).build();
@@ -792,7 +792,7 @@
     public void testBackStackTasks_expectNoTrim() {
         mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */);
 
-        final ActivityStack homeStack = mDisplay.getHomeStack();
+        final ActivityStack homeStack = mDisplay.getRootHomeTask();
         final ActivityStack aboveHomeStack = mDisplay.createStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
 
@@ -812,7 +812,7 @@
 
         final ActivityStack behindHomeStack = mDisplay.createStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        final ActivityStack homeStack = mDisplay.getHomeStack();
+        final ActivityStack homeStack = mDisplay.getRootHomeTask();
         final ActivityStack aboveHomeStack = mDisplay.createStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
 
@@ -832,7 +832,7 @@
     public void testOtherDisplayTasks_expectNoTrim() {
         mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */);
 
-        final ActivityStack homeStack = mDisplay.getHomeStack();
+        final ActivityStack homeStack = mDisplay.getRootHomeTask();
         final DisplayContent otherDisplay = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
         final ActivityStack otherDisplayStack = otherDisplay.createStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index 7e42c62..b78107e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -97,7 +97,7 @@
                 .setStack(recentsStack)
                 .build();
         ActivityRecord topActivity = new ActivityBuilder(mService).setCreateTask(true).build();
-        topActivity.getActivityStack().moveToFront("testRecentsActivityVisiblility");
+        topActivity.getRootTask().moveToFront("testRecentsActivityVisiblility");
 
         doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible(
                 any() /* starting */, anyInt() /* configChanges */,
@@ -187,7 +187,7 @@
         recentActivity.app = null;
 
         // Start an activity on top.
-        new ActivityBuilder(mService).setCreateTask(true).build().getActivityStack().moveToFront(
+        new ActivityBuilder(mService).setCreateTask(true).build().getRootTask().moveToFront(
                 "testRestartRecentsActivity");
 
         doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible(
@@ -209,7 +209,7 @@
     public void testSetLaunchTaskBehindOfTargetActivity() {
         DisplayContent display = mRootWindowContainer.getDefaultDisplay();
         display.mDisplayContent.mBoundsAnimationController = mock(BoundsAnimationController.class);
-        ActivityStack homeStack = display.getHomeStack();
+        ActivityStack homeStack = display.getRootHomeTask();
         // Assume the home activity support recents.
         ActivityRecord targetActivity = homeStack.getTopNonFinishingActivity();
         if (targetActivity == null) {
@@ -229,7 +229,7 @@
         new ActivityBuilder(mService)
                 .setCreateTask(true)
                 .build()
-                .getActivityStack()
+                .getRootTask()
                 .moveToFront("Activity start");
 
         // Start the recents animation.
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 112479b..1a57596 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -61,7 +61,7 @@
 
 /**
  * Build/Install/Run:
- *  atest FrameworksServicesTests:RemoteAnimationControllerTest
+ *  atest WmTests:RemoteAnimationControllerTest
  */
 @SmallTest
 @Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
index 04d79ca..79db927 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
@@ -131,7 +131,7 @@
                 0f /*aspectRatio*/, "initialMove");
 
         final DisplayContent display = mFullscreenStack.getDisplay();
-        ActivityStack pinnedStack = display.getPinnedStack();
+        ActivityStack pinnedStack = display.getRootPinnedTask();
         // Ensure a task has moved over.
         ensureStackPlacement(pinnedStack, firstActivity);
         ensureStackPlacement(mFullscreenStack, secondActivity);
@@ -141,7 +141,7 @@
                 0f /*aspectRatio*/, "secondMove");
 
         // Need to get stacks again as a new instance might have been created.
-        pinnedStack = display.getPinnedStack();
+        pinnedStack = display.getRootPinnedTask();
         mFullscreenStack = display.getStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
         // Ensure stacks have swapped tasks.
         ensureStackPlacement(pinnedStack, secondActivity);
@@ -253,12 +253,12 @@
 
         // Under split screen primary we should be focusable when not minimized
         mRootWindowContainer.setDockedStackMinimized(false);
-        assertTrue(stack.isFocusable());
+        assertTrue(stack.isTopActivityFocusable());
         assertTrue(activity.isFocusable());
 
         // Under split screen primary we should not be focusable when minimized
         mRootWindowContainer.setDockedStackMinimized(true);
-        assertFalse(stack.isFocusable());
+        assertFalse(stack.isTopActivityFocusable());
         assertFalse(activity.isFocusable());
 
         final ActivityStack pinnedStack = mRootWindowContainer.getDefaultDisplay().createStack(
@@ -267,19 +267,19 @@
                 .setStack(pinnedStack).build();
 
         // We should not be focusable when in pinned mode
-        assertFalse(pinnedStack.isFocusable());
+        assertFalse(pinnedStack.isTopActivityFocusable());
         assertFalse(pinnedActivity.isFocusable());
 
         // Add flag forcing focusability.
         pinnedActivity.info.flags |= FLAG_ALWAYS_FOCUSABLE;
 
         // We should not be focusable when in pinned mode
-        assertTrue(pinnedStack.isFocusable());
+        assertTrue(pinnedStack.isTopActivityFocusable());
         assertTrue(pinnedActivity.isFocusable());
 
         // Without the overridding activity, stack should not be focusable.
         pinnedStack.removeChild(pinnedActivity.getTask(), "testFocusability");
-        assertFalse(pinnedStack.isFocusable());
+        assertFalse(pinnedStack.isTopActivityFocusable());
     }
 
     /**
@@ -417,7 +417,7 @@
     @Test
     public void testResumeFocusedStacksStartsHomeActivity_NoActivities() {
         mFullscreenStack.removeIfPossible();
-        mService.mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY).getHomeStack()
+        mService.mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY).getRootHomeTask()
                 .removeIfPossible();
         mService.mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY)
                 .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
@@ -440,7 +440,7 @@
     @Test
     public void testResumeFocusedStacksStartsHomeActivity_ActivityOnSecondaryScreen() {
         mFullscreenStack.removeIfPossible();
-        mService.mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY).getHomeStack()
+        mService.mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY).getRootHomeTask()
                 .removeIfPossible();
         mService.mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY)
                 .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
@@ -638,7 +638,7 @@
         final ActivityRecord resolverActivity = mRootWindowContainer.topRunningActivity();
 
         assertEquals(info, resolverActivity.info);
-        assertEquals(ACTIVITY_TYPE_STANDARD, resolverActivity.getActivityStack().getActivityType());
+        assertEquals(ACTIVITY_TYPE_STANDARD, resolverActivity.getRootTask().getActivityType());
     }
 
     /**
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 894890a..277bc41 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -18,6 +18,7 @@
 
 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;
 
@@ -85,9 +86,12 @@
     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));
     }
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 64db897..890e4ab 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -391,6 +391,33 @@
         assertEquals(null, compatTokens.get(0));
     }
 
+    @Test
+    public void testShouldUseSizeCompatModeOnResizableTask() {
+        setUpApp(new TestDisplayContent.Builder(mService, 1000, 2500).build());
+
+        // Make the task root resizable.
+        mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
+
+        // Create a size compat activity on the same task.
+        final ActivityRecord activity = new ActivityBuilder(mService)
+                .setTask(mTask)
+                .setResizeMode(ActivityInfo.RESIZE_MODE_UNRESIZEABLE)
+                .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
+                .build();
+        assertTrue(activity.shouldUseSizeCompatMode());
+
+        // 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);
+        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);
+        assertFalse(activity.shouldUseSizeCompatMode());
+    }
+
     /**
      * Setup {@link #mActivity} as a size-compat-mode-able activity with fixed aspect and/or
      * orientation.
diff --git a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
index f5d08dc..eda1fb8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
+++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
@@ -250,4 +250,9 @@
         return this;
     }
 
+    @Override
+    public SurfaceControl.Transaction setFrameRateSelectionPriority(SurfaceControl sc,
+            int priority) {
+        return this;
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
index e011280..bac2bca 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
@@ -60,7 +60,7 @@
  * Test class for {@link SurfaceAnimationRunner}.
  *
  * Build/Install/Run:
- *  atest FrameworksServicesTests:SurfaceAnimationRunnerTest
+ *  atest WmTests:SurfaceAnimationRunnerTest
  */
 @SmallTest
 @Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskContainersTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskContainersTests.java
new file mode 100644
index 0000000..f228397
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskContainersTests.java
@@ -0,0 +1,137 @@
+/*
+ * 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.server.wm;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for the {@link DisplayContent.TaskStackContainers} container in {@link DisplayContent}.
+ *
+ * Build/Install/Run:
+ *  atest WmTests:TaskStackContainersTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class TaskContainersTests extends WindowTestsBase {
+
+    private ActivityStack mPinnedStack;
+
+    @Before
+    public void setUp() throws Exception {
+        mPinnedStack = 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());
+        final ActivityRecord pinnedApp =
+                WindowTestUtils.createTestActivityRecord(mDisplayContent);
+        pinnedTask.addChild(pinnedApp, 0 /* addPos */);
+        assertTrue(mPinnedStack.isVisible());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mPinnedStack.removeImmediately();
+    }
+
+    @Test
+    public void testStackPositionChildAt() {
+        // Test that always-on-top stack can't be moved to position other than top.
+        final ActivityStack stack1 = createTaskStackOnDisplay(mDisplayContent);
+        final ActivityStack stack2 = createTaskStackOnDisplay(mDisplayContent);
+
+        final WindowContainer taskStackContainer = stack1.getParent();
+
+        final int stack1Pos = taskStackContainer.mChildren.indexOf(stack1);
+        final int stack2Pos = taskStackContainer.mChildren.indexOf(stack2);
+        final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedStack);
+        assertThat(pinnedStackPos).isGreaterThan(stack2Pos);
+        assertThat(stack2Pos).isGreaterThan(stack1Pos);
+
+        taskStackContainer.positionChildAt(WindowContainer.POSITION_BOTTOM, mPinnedStack, false);
+        assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1);
+        assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2);
+        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
+
+        taskStackContainer.positionChildAt(1, mPinnedStack, false);
+        assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1);
+        assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2);
+        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
+    }
+
+    @Test
+    public void testStackPositionBelowPinnedStack() {
+        // Test that no stack can be above pinned stack.
+        final ActivityStack stack1 = createTaskStackOnDisplay(mDisplayContent);
+
+        final WindowContainer taskStackContainer = stack1.getParent();
+
+        final int stackPos = taskStackContainer.mChildren.indexOf(stack1);
+        final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedStack);
+        assertThat(pinnedStackPos).isGreaterThan(stackPos);
+
+        taskStackContainer.positionChildAt(WindowContainer.POSITION_TOP, stack1, false);
+        assertEquals(taskStackContainer.mChildren.get(stackPos), stack1);
+        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
+
+        taskStackContainer.positionChildAt(taskStackContainer.mChildren.size() - 1, stack1, false);
+        assertEquals(taskStackContainer.mChildren.get(stackPos), stack1);
+        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
+    }
+
+    @Test
+    public void testDisplayPositionWithPinnedStack() {
+        // Make sure the display is system owned display which capable to move the stack to top.
+        spyOn(mDisplayContent);
+        doReturn(false).when(mDisplayContent).isUntrustedVirtualDisplay();
+
+        // The display contains pinned stack that was added in {@link #setUp}.
+        final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+        final Task task = createTaskInStack(stack, 0 /* userId */);
+
+        // Add another display at top.
+        mWm.mRoot.positionChildAt(WindowContainer.POSITION_TOP, createNewDisplay(),
+                false /* includingParents */);
+
+        // Move the task of {@code mDisplayContent} to top.
+        stack.positionChildAt(WindowContainer.POSITION_TOP, task, true /* includingParents */);
+        final int indexOfDisplayWithPinnedStack = mWm.mRoot.mChildren.indexOf(mDisplayContent);
+
+        assertEquals("The testing DisplayContent should be moved to top with task",
+                mWm.mRoot.getChildCount() - 1, indexOfDisplayWithPinnedStack);
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
new file mode 100644
index 0000000..8d2da1e
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
@@ -0,0 +1,170 @@
+/*
+ * 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.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.view.ITaskOrganizer;
+import android.view.SurfaceControl;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test class for {@link TaskOrganizer}.
+ *
+ * Build/Install/Run:
+ *  atest WmTests:TaskOrganizerTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class TaskOrganizerTests extends WindowTestsBase {
+    private ITaskOrganizer makeAndRegisterMockOrganizer() {
+        final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
+        when(organizer.asBinder()).thenReturn(new Binder());
+
+        mWm.mAtmService.registerTaskOrganizer(organizer, WINDOWING_MODE_PINNED);
+
+        return organizer;
+    }
+
+    @Test
+    public void testAppearVanish() throws RemoteException {
+        final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+        final Task task = createTaskInStack(stack, 0 /* userId */);
+        final ITaskOrganizer organizer = makeAndRegisterMockOrganizer();
+
+        task.setTaskOrganizer(organizer);
+        verify(organizer).taskAppeared(any(), any());
+        assertTrue(task.isControlledByTaskOrganizer());
+
+        task.removeImmediately();
+        verify(organizer).taskVanished(any());
+    }
+
+    @Test
+    public void testSwapOrganizer() throws RemoteException {
+        final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+        final Task task = createTaskInStack(stack, 0 /* userId */);
+        final ITaskOrganizer organizer = makeAndRegisterMockOrganizer();
+        final ITaskOrganizer organizer2 = makeAndRegisterMockOrganizer();
+
+        task.setTaskOrganizer(organizer);
+        verify(organizer).taskAppeared(any(), any());
+        task.setTaskOrganizer(organizer2);
+        verify(organizer).taskVanished(any());
+        verify(organizer2).taskAppeared(any(), any());
+    }
+
+    @Test
+    public void testClearOrganizer() throws RemoteException {
+        final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+        final Task task = createTaskInStack(stack, 0 /* userId */);
+        final ITaskOrganizer organizer = makeAndRegisterMockOrganizer();
+
+        task.setTaskOrganizer(organizer);
+        verify(organizer).taskAppeared(any(), any());
+        assertTrue(task.isControlledByTaskOrganizer());
+
+        task.setTaskOrganizer(null);
+        verify(organizer).taskVanished(any());
+        assertFalse(task.isControlledByTaskOrganizer());
+    }
+
+    @Test
+    public void testTransferStackToOrganizer() throws RemoteException {
+        final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+        final Task task = createTaskInStack(stack, 0 /* userId */);
+        final Task task2 = createTaskInStack(stack, 0 /* userId */);
+        final ITaskOrganizer organizer = makeAndRegisterMockOrganizer();
+
+        stack.transferToTaskOrganizer(organizer);
+
+        verify(organizer, times(2)).taskAppeared(any(), any());
+        assertTrue(task.isControlledByTaskOrganizer());
+        assertTrue(task2.isControlledByTaskOrganizer());
+
+        stack.transferToTaskOrganizer(null);
+
+        verify(organizer, times(2)).taskVanished(any());
+        assertFalse(task.isControlledByTaskOrganizer());
+        assertFalse(task2.isControlledByTaskOrganizer());
+    }
+
+    @Test
+    public void testRegisterTaskOrganizerTaskWindowingModeChanges() throws RemoteException {
+        final ITaskOrganizer organizer = makeAndRegisterMockOrganizer();
+
+        final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+        final Task task = createTaskInStack(stack, 0 /* userId */);
+        task.setWindowingMode(WINDOWING_MODE_PINNED);
+        verify(organizer).taskAppeared(any(), any());
+        assertTrue(task.isControlledByTaskOrganizer());
+
+        task.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        verify(organizer).taskVanished(any());
+        assertFalse(task.isControlledByTaskOrganizer());
+    }
+
+    @Test
+    public void testRegisterTaskOrganizerStackWindowingModeChanges() throws RemoteException {
+        final ITaskOrganizer organizer = makeAndRegisterMockOrganizer();
+
+        final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+        final Task task = createTaskInStack(stack, 0 /* userId */);
+        final Task task2 = createTaskInStack(stack, 0 /* userId */);
+        stack.setWindowingMode(WINDOWING_MODE_PINNED);
+        verify(organizer, times(2)).taskAppeared(any(), any());
+        assertTrue(task.isControlledByTaskOrganizer());
+        assertTrue(task2.isControlledByTaskOrganizer());
+
+        stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        verify(organizer, times(2)).taskVanished(any());
+        assertFalse(task.isControlledByTaskOrganizer());
+        assertFalse(task2.isControlledByTaskOrganizer());
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
index 8970571..7aaf3fb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
@@ -85,7 +85,7 @@
 
         mPositioner.register(mDisplayContent, win);
 
-        win.getStack().setWindowingMode(WINDOWING_MODE_FREEFORM);
+        win.getRootTask().setWindowingMode(WINDOWING_MODE_FREEFORM);
     }
 
     @After
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
index 9562fa4..ebf14d2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
@@ -20,6 +20,8 @@
 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_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
 import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
@@ -34,6 +36,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.server.policy.WindowManagerPolicy.USER_ROTATION_FREE;
@@ -51,10 +54,13 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 
 import android.app.ActivityManager;
 import android.app.TaskInfo;
+import android.app.WindowConfiguration;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -152,6 +158,8 @@
     @Test
     public void testReturnsToHomeStack() throws Exception {
         final Task task = createTask(1);
+        spyOn(task);
+        doReturn(true).when(task).hasChild();
         assertFalse(task.returnsToHomeStack());
         task.intent = null;
         assertFalse(task.returnsToHomeStack());
@@ -857,6 +865,78 @@
         verify(task).setIntent(eq(activity0));
     }
 
+    @Test
+    public void testSaveLaunchingStateWhenConfigurationChanged() {
+        LaunchParamsPersister persister = mService.mStackSupervisor.mLaunchParamsPersister;
+        spyOn(persister);
+
+        final Task task = getTestTask();
+        task.hasBeenVisible = false;
+        task.getDisplayContent().setDisplayWindowingMode(WINDOWING_MODE_FREEFORM);
+        task.getStack().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+        task.hasBeenVisible = true;
+        task.onConfigurationChanged(task.getParent().getConfiguration());
+
+        verify(persister).saveTask(task, task.getDisplayContent());
+    }
+
+    @Test
+    public void testSaveLaunchingStateWhenClearingParent() {
+        LaunchParamsPersister persister = mService.mStackSupervisor.mLaunchParamsPersister;
+        spyOn(persister);
+
+        final Task task = getTestTask();
+        task.hasBeenVisible = false;
+        task.getDisplayContent().setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
+        task.getStack().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        final DisplayContent oldDisplay = task.getDisplayContent();
+
+        LaunchParamsController.LaunchParams params = new LaunchParamsController.LaunchParams();
+        params.mWindowingMode = WINDOWING_MODE_UNDEFINED;
+        persister.getLaunchParams(task, null, params);
+        assertEquals(WINDOWING_MODE_UNDEFINED, params.mWindowingMode);
+
+        task.hasBeenVisible = true;
+        task.removeImmediately();
+
+        verify(persister).saveTask(task, oldDisplay);
+
+        persister.getLaunchParams(task, null, params);
+        assertEquals(WINDOWING_MODE_FULLSCREEN, params.mWindowingMode);
+    }
+
+    @Test
+    public void testNotSaveLaunchingStateNonFreeformDisplay() {
+        LaunchParamsPersister persister = mService.mStackSupervisor.mLaunchParamsPersister;
+        spyOn(persister);
+
+        final Task task = getTestTask();
+        task.hasBeenVisible = false;
+        task.getStack().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+        task.hasBeenVisible = true;
+        task.onConfigurationChanged(task.getParent().getConfiguration());
+
+        verify(persister, never()).saveTask(same(task), any());
+    }
+
+    @Test
+    public void testNotSaveLaunchingStateWhenNotFullscreenOrFreeformWindow() {
+        LaunchParamsPersister persister = mService.mStackSupervisor.mLaunchParamsPersister;
+        spyOn(persister);
+
+        final Task task = getTestTask();
+        task.hasBeenVisible = false;
+        task.getDisplayContent().setDisplayWindowingMode(WINDOWING_MODE_FREEFORM);
+        task.getStack().setWindowingMode(WINDOWING_MODE_PINNED);
+
+        task.hasBeenVisible = true;
+        task.onConfigurationChanged(task.getParent().getConfiguration());
+
+        verify(persister, never()).saveTask(same(task), any());
+    }
+
     private Task getTestTask() {
         final ActivityStack stack = new StackBuilder(mRootWindowContainer).build();
         return stack.getBottomMostTask();
@@ -906,7 +986,7 @@
     }
 
     private Task createTask(int taskId) {
-        return new Task(mService, taskId, new Intent(), null, null, null,
+        return new ActivityStack(mService, taskId, new Intent(), null, null, null,
                 ActivityBuilder.getDefaultComponent(), null, false, false, false, 0, 10050, null,
                 0, false, null, 0, 0, 0, 0, 0, null, 0, false, false, false, 0,
                 0, null /*ActivityInfo*/, null /*_voiceSession*/, null /*_voiceInteractor*/,
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java
index 0274b7d..e712471 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java
@@ -38,7 +38,7 @@
  * Test class for {@link TaskSnapshotCache}.
  *
  * Build/Install/Run:
- *  atest FrameworksServicesTests:TaskSnapshotCacheTest
+ *  atest WmTests:TaskSnapshotCacheTest
  */
 @SmallTest
 @Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
index 8fe0cdb..2e485dd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
@@ -53,7 +53,7 @@
  * Test class for {@link TaskSnapshotController}.
  *
  * Build/Install/Run:
- *  *  atest FrameworksServicesTests:TaskSnapshotControllerTest
+ *  *  atest WmTests:TaskSnapshotControllerTest
  */
 @SmallTest
 @Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
index b5a5790..eb8eb98 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
@@ -47,7 +47,7 @@
  * Test class for {@link TaskSnapshotPersister} and {@link TaskSnapshotLoader}
  *
  * Build/Install/Run:
- *  atest FrameworksServicesTests:TaskSnapshotPersisterLoaderTest
+ *  atest WmTests:TaskSnapshotPersisterLoaderTest
  */
 @MediumTest
 @Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
index 2d418ff..ed87f3a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
@@ -53,7 +53,7 @@
  * Test class for {@link TaskSnapshotSurface}.
  *
  * Build/Install/Run:
- *  atest FrameworksServicesTests:TaskSnapshotSurfaceTest
+ *  atest WmTests:TaskSnapshotSurfaceTest
  */
 @SmallTest
 @Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackContainersTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackContainersTests.java
deleted file mode 100644
index 6ad9f74..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackContainersTests.java
+++ /dev/null
@@ -1,137 +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.server.wm;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests for the {@link DisplayContent.TaskStackContainers} container in {@link DisplayContent}.
- *
- * Build/Install/Run:
- *  atest WmTests:TaskStackContainersTests
- */
-@SmallTest
-@Presubmit
-@RunWith(WindowTestRunner.class)
-public class TaskStackContainersTests extends WindowTestsBase {
-
-    private ActivityStack mPinnedStack;
-
-    @Before
-    public void setUp() throws Exception {
-        mPinnedStack = 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());
-        final ActivityRecord pinnedApp =
-                WindowTestUtils.createTestActivityRecord(mDisplayContent);
-        pinnedTask.addChild(pinnedApp, 0 /* addPos */);
-        assertTrue(mPinnedStack.isVisible());
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        mPinnedStack.removeImmediately();
-    }
-
-    @Test
-    public void testStackPositionChildAt() {
-        // Test that always-on-top stack can't be moved to position other than top.
-        final ActivityStack stack1 = createTaskStackOnDisplay(mDisplayContent);
-        final ActivityStack stack2 = createTaskStackOnDisplay(mDisplayContent);
-
-        final WindowContainer taskStackContainer = stack1.getParent();
-
-        final int stack1Pos = taskStackContainer.mChildren.indexOf(stack1);
-        final int stack2Pos = taskStackContainer.mChildren.indexOf(stack2);
-        final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedStack);
-        assertThat(pinnedStackPos).isGreaterThan(stack2Pos);
-        assertThat(stack2Pos).isGreaterThan(stack1Pos);
-
-        taskStackContainer.positionChildAt(WindowContainer.POSITION_BOTTOM, mPinnedStack, false);
-        assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1);
-        assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2);
-        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
-
-        taskStackContainer.positionChildAt(1, mPinnedStack, false);
-        assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1);
-        assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2);
-        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
-    }
-
-    @Test
-    public void testStackPositionBelowPinnedStack() {
-        // Test that no stack can be above pinned stack.
-        final ActivityStack stack1 = createTaskStackOnDisplay(mDisplayContent);
-
-        final WindowContainer taskStackContainer = stack1.getParent();
-
-        final int stackPos = taskStackContainer.mChildren.indexOf(stack1);
-        final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedStack);
-        assertThat(pinnedStackPos).isGreaterThan(stackPos);
-
-        taskStackContainer.positionChildAt(WindowContainer.POSITION_TOP, stack1, false);
-        assertEquals(taskStackContainer.mChildren.get(stackPos), stack1);
-        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
-
-        taskStackContainer.positionChildAt(taskStackContainer.mChildren.size() - 1, stack1, false);
-        assertEquals(taskStackContainer.mChildren.get(stackPos), stack1);
-        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
-    }
-
-    @Test
-    public void testDisplayPositionWithPinnedStack() {
-        // Make sure the display is system owned display which capable to move the stack to top.
-        spyOn(mDisplayContent);
-        doReturn(false).when(mDisplayContent).isUntrustedVirtualDisplay();
-
-        // The display contains pinned stack that was added in {@link #setUp}.
-        final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
-        final Task task = createTaskInStack(stack, 0 /* userId */);
-
-        // Add another display at top.
-        mWm.mRoot.positionChildAt(WindowContainer.POSITION_TOP, createNewDisplay(),
-                false /* includingParents */);
-
-        // Move the task of {@code mDisplayContent} to top.
-        stack.positionChildAt(WindowContainer.POSITION_TOP, task, true /* includingParents */);
-        final int indexOfDisplayWithPinnedStack = mWm.mRoot.mChildren.indexOf(mDisplayContent);
-
-        assertEquals("The testing DisplayContent should be moved to top with task",
-                mWm.mRoot.getChildCount() - 1, indexOfDisplayWithPinnedStack);
-    }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
index b4f5751..6e4be88 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
@@ -115,7 +115,7 @@
         // Remove stack and check if its child is also removed.
         stack.removeImmediately();
         assertNull(stack.getDisplayContent());
-        assertNull(task.getStack());
+        assertNull(task.getParent());
     }
 
     @Test
@@ -131,7 +131,7 @@
         assertEquals(0, stack.getChildCount());
         assertNull(stack.getDisplayContent());
         assertNull(task.getDisplayContent());
-        assertNull(task.getStack());
+        assertNull(task.getParent());
     }
 
     @Test
@@ -140,6 +140,7 @@
         final Task task = createTaskInStack(stack, 0 /* userId */);
 
         // Stack removal is deferred if one of its child is animating.
+        doReturn(true).when(stack).hasWindowsAlive();
         doReturn(true).when(task).isAnimating(TRANSITION | CHILDREN);
 
         stack.removeIfPossible();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 08ee0eb..9f45044 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -17,7 +17,7 @@
 package com.android.server.wm;
 
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
-import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
@@ -91,7 +91,7 @@
 
     @Override
     public boolean isKeyguardHostWindow(WindowManager.LayoutParams attrs) {
-        return attrs.type == TYPE_STATUS_BAR;
+        return attrs.type == TYPE_NOTIFICATION_SHADE;
     }
 
     @Override
@@ -263,6 +263,11 @@
     }
 
     @Override
+    public boolean isKeyguardShowing() {
+        return mKeyguardShowingAndNotOccluded;
+    }
+
+    @Override
     public boolean isKeyguardShowingAndNotOccluded() {
         return mKeyguardShowingAndNotOccluded;
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index d3cd3cb..05d048d3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -829,12 +829,15 @@
         wc.getDisplayContent().mAppTransition.overridePendingAppTransitionRemote(adapter);
         spyOn(wc);
         doReturn(true).when(wc).okToAnimate();
+        final Runnable onAnimationFinishedCallback = mock(Runnable.class);
 
         // Make sure animating state is as expected after applied animation.
-        assertTrue(wc.applyAnimation(null, TRANSIT_TASK_OPEN, true, false));
+        assertTrue(wc.applyAnimation(null, TRANSIT_TASK_OPEN, true, false,
+                onAnimationFinishedCallback));
         assertEquals(wc.getTopMostActivity(), act);
         assertTrue(wc.isAnimating());
         assertTrue(act.isAnimating(PARENTS));
+        verify(onAnimationFinishedCallback, times(0)).run();
 
         // Make sure animation finish callback will be received and reset animating state after
         // animation finish.
@@ -843,6 +846,7 @@
         verify(wc).onAnimationFinished();
         assertFalse(wc.isAnimating());
         assertFalse(act.isAnimating(PARENTS));
+        verify(onAnimationFinishedCallback, times(1)).run();
     }
 
     /* Used so we can gain access to some protected members of the {@link WindowContainer} class */
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
index 8936aad..3c0dd1e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
@@ -39,7 +39,7 @@
  * Tests for {@link WindowContainer#forAllWindows} and various implementations.
  *
  * Build/Install/Run:
- *  atest FrameworksServicesTests:WindowContainerTraversalTests
+ *  atest WmTests:WindowContainerTraversalTests
  */
 @SmallTest
 @Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index 421a4582..34e487b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wm;
 
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.view.Display.INVALID_DISPLAY;
 
 import static org.junit.Assert.assertEquals;
@@ -24,8 +26,11 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
 
+import android.app.IApplicationThread;
 import android.content.pm.ApplicationInfo;
+import android.content.res.Configuration;
 import android.platform.test.annotations.Presubmit;
 
 import org.junit.Before;
@@ -52,6 +57,7 @@
         mMockListener = mock(WindowProcessListener.class);
         mWpc = new WindowProcessController(
                 mService, mock(ApplicationInfo.class), null, 0, -1, null, mMockListener);
+        mWpc.setThread(mock(IApplicationThread.class));
     }
 
     @Test
@@ -62,33 +68,33 @@
 
         // Register to display 1 as a listener.
         TestDisplayContent testDisplayContent1 = createTestDisplayContentInContainer();
-        mWpc.registerDisplayConfigurationListenerLocked(testDisplayContent1);
+        mWpc.registerDisplayConfigurationListener(testDisplayContent1);
         assertTrue(testDisplayContent1.containsListener(mWpc));
         assertEquals(testDisplayContent1.mDisplayId, mWpc.getDisplayId());
 
         // Move to display 2.
         TestDisplayContent testDisplayContent2 = createTestDisplayContentInContainer();
-        mWpc.registerDisplayConfigurationListenerLocked(testDisplayContent2);
+        mWpc.registerDisplayConfigurationListener(testDisplayContent2);
         assertFalse(testDisplayContent1.containsListener(mWpc));
         assertTrue(testDisplayContent2.containsListener(mWpc));
         assertEquals(testDisplayContent2.mDisplayId, mWpc.getDisplayId());
 
         // Null DisplayContent will not change anything.
-        mWpc.registerDisplayConfigurationListenerLocked(null);
+        mWpc.registerDisplayConfigurationListener(null);
         assertTrue(testDisplayContent2.containsListener(mWpc));
         assertEquals(testDisplayContent2.mDisplayId, mWpc.getDisplayId());
 
         // Unregister listener will remove the wpc from registered displays.
-        mWpc.unregisterDisplayConfigurationListenerLocked();
+        mWpc.unregisterDisplayConfigurationListener();
         assertFalse(testDisplayContent1.containsListener(mWpc));
         assertFalse(testDisplayContent2.containsListener(mWpc));
         assertEquals(INVALID_DISPLAY, mWpc.getDisplayId());
 
         // Unregistration still work even if the display was removed.
-        mWpc.registerDisplayConfigurationListenerLocked(testDisplayContent1);
+        mWpc.registerDisplayConfigurationListener(testDisplayContent1);
         assertEquals(testDisplayContent1.mDisplayId, mWpc.getDisplayId());
         mRootWindowContainer.removeChild(testDisplayContent1);
-        mWpc.unregisterDisplayConfigurationListenerLocked();
+        mWpc.unregisterDisplayConfigurationListener();
         assertEquals(INVALID_DISPLAY, mWpc.getDisplayId());
     }
 
@@ -129,7 +135,53 @@
         orderVerifier.verifyNoMoreInteractions();
     }
 
+    @Test
+    public void testConfigurationForSecondaryScreen() {
+        // By default, the process should not listen to any display.
+        assertEquals(INVALID_DISPLAY, mWpc.getDisplayId());
+
+        // Register to a new display as a listener.
+        final DisplayContent display = new TestDisplayContent.Builder(mService, 2000, 1000)
+                .setDensityDpi(300).setPosition(DisplayContent.POSITION_TOP).build();
+        mWpc.registerDisplayConfigurationListener(display);
+
+        assertEquals(display.mDisplayId, mWpc.getDisplayId());
+        final Configuration expectedConfig = mService.mRootWindowContainer.getConfiguration();
+        expectedConfig.updateFrom(display.getConfiguration());
+        assertEquals(expectedConfig, mWpc.getConfiguration());
+    }
+
+    @Test
+    public void testDelayingConfigurationChange() {
+        when(mMockListener.isCached()).thenReturn(false);
+
+        Configuration tmpConfig = new Configuration(mWpc.getConfiguration());
+        invertOrientation(tmpConfig);
+        mWpc.onConfigurationChanged(tmpConfig);
+
+        // The last reported config should be the current config as the process is not cached.
+        Configuration originalConfig = new Configuration(mWpc.getConfiguration());
+        assertEquals(mWpc.getLastReportedConfiguration(), originalConfig);
+
+        when(mMockListener.isCached()).thenReturn(true);
+        invertOrientation(tmpConfig);
+        mWpc.onConfigurationChanged(tmpConfig);
+
+        Configuration newConfig = new Configuration(mWpc.getConfiguration());
+
+        // Last reported config hasn't changed because the process is in a cached state.
+        assertEquals(mWpc.getLastReportedConfiguration(), originalConfig);
+
+        mWpc.onProcCachedStateChanged(false);
+        assertEquals(mWpc.getLastReportedConfiguration(), newConfig);
+    }
+
     private TestDisplayContent createTestDisplayContentInContainer() {
         return new TestDisplayContent.Builder(mService, 1000, 1500).build();
     }
+
+    private static void invertOrientation(Configuration config) {
+        config.orientation = config.orientation == ORIENTATION_PORTRAIT
+                ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
+    }
 }
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 e081ca3..5cf9c44 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -92,7 +92,7 @@
  * Tests for the {@link WindowState} class.
  *
  * Build/Install/Run:
- *  atest FrameworksServicesTests:WindowStateTests
+ *  atest WmTests:WindowStateTests
  */
 @SmallTest
 @Presubmit
@@ -278,7 +278,7 @@
         spyOn(stack);
         doReturn(true).when(controller).isMinimizedDock();
         doReturn(true).when(controller).isHomeStackResizable();
-        doReturn(stack).when(appWindow).getStack();
+        doReturn(stack).when(appWindow).getRootTask();
 
         // Make sure canBeImeTarget is false due to shouldIgnoreInput is true;
         assertFalse(appWindow.canBeImeTarget());
@@ -545,7 +545,7 @@
         final WindowState startingWindow = createWindow(null /* parent */,
                 TYPE_APPLICATION_STARTING, startingApp.mToken, "starting");
         startingApp.mActivityRecord.startingWindow = startingWindow;
-        final WindowState keyguardHostWindow = mStatusBarWindow;
+        final WindowState keyguardHostWindow = mNotificationShadeWindow;
         final WindowState allDrawnApp = mAppWindow;
         allDrawnApp.mActivityRecord.allDrawn = true;
 
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 31a7f24..20eab5a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -31,6 +31,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+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_WALLPAPER;
 
@@ -76,6 +77,7 @@
     WindowState mImeWindow;
     WindowState mImeDialogWindow;
     WindowState mStatusBarWindow;
+    WindowState mNotificationShadeWindow;
     WindowState mDockedDividerWindow;
     WindowState mNavBarWindow;
     WindowState mAppWindow;
@@ -119,6 +121,8 @@
                 mImeDialogWindow = createCommonWindow(null, TYPE_INPUT_METHOD_DIALOG,
                         "mImeDialogWindow");
                 mStatusBarWindow = createCommonWindow(null, TYPE_STATUS_BAR, "mStatusBarWindow");
+                mNotificationShadeWindow = createCommonWindow(null, TYPE_NOTIFICATION_SHADE,
+                        "mNotificationShadeWindow");
                 mNavBarWindow = createCommonWindow(null, TYPE_NAVIGATION_BAR, "mNavBarWindow");
                 mDockedDividerWindow = createCommonWindow(null, TYPE_DOCK_DIVIDER,
                         "mDockedDividerWindow");
diff --git a/services/usage/java/com/android/server/usage/StorageStatsManagerInternal.java b/services/usage/java/com/android/server/usage/StorageStatsManagerInternal.java
new file mode 100644
index 0000000..a532548
--- /dev/null
+++ b/services/usage/java/com/android/server/usage/StorageStatsManagerInternal.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usage;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.pm.PackageStats;
+
+/**
+ * StorageStatsManager local system service interface.
+ *
+ * Only for use within the system server.
+ */
+public abstract class StorageStatsManagerInternal {
+    /**
+     * Class used to augment {@link PackageStats} with the data stored by the system on
+     * behalf of apps in system specific directories
+     * ({@link android.os.Environment#getDataSystemDirectory},
+     * {@link android.os.Environment#getDataSystemCeDirectory}, etc).
+     */
+    public interface StorageStatsAugmenter {
+        void augmentStatsForPackage(@NonNull PackageStats stats,
+                @NonNull String packageName, @UserIdInt int userId,
+                @NonNull String callingPackage);
+        void augmentStatsForUid(@NonNull PackageStats stats, int uid,
+                @NonNull String callingPackage);
+        void augmentStatsForUser(@NonNull PackageStats stats, @UserIdInt int userId,
+                @NonNull String callingPackage);
+    }
+
+    /**
+     * Register a {@link StorageStatsAugmenter}.
+     *
+     * @param augmenter the {@link StorageStatsAugmenter} object to be registered.
+     * @param tag the identifier to be used for debugging in logs/trace.
+     */
+    public abstract void registerStorageStatsAugmenter(@NonNull StorageStatsAugmenter augmenter,
+            @NonNull String tag);
+}
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index 531a931..18b640f 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -18,6 +18,7 @@
 
 import static com.android.internal.util.ArrayUtils.defeatNullable;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+import static com.android.server.usage.StorageStatsManagerInternal.StorageStatsAugmenter;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -46,6 +47,7 @@
 import android.os.ParcelableException;
 import android.os.StatFs;
 import android.os.SystemProperties;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.CrateInfo;
@@ -58,6 +60,7 @@
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
 import android.util.DataUnit;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseLongArray;
 
@@ -77,6 +80,8 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Consumer;
 
 public class StorageStatsService extends IStorageStatsManager.Stub {
     private static final String TAG = "StorageStatsService";
@@ -111,6 +116,9 @@
     private final Installer mInstaller;
     private final H mHandler;
 
+    private final CopyOnWriteArrayList<Pair<String, StorageStatsAugmenter>>
+            mStorageStatsAugmenters = new CopyOnWriteArrayList<>();
+
     public StorageStatsService(Context context) {
         mContext = Preconditions.checkNotNull(context);
         mAppOps = Preconditions.checkNotNull(context.getSystemService(AppOpsManager.class));
@@ -139,6 +147,8 @@
                 }
             }
         });
+
+        LocalServices.addService(StorageStatsManagerInternal.class, new LocalService());
     }
 
     private void invalidateMounts() {
@@ -300,6 +310,12 @@
             } catch (InstallerException e) {
                 throw new ParcelableException(new IOException(e.getMessage()));
             }
+            if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) {
+                forEachStorageStatsAugmenter((storageStatsAugmenter) -> {
+                    storageStatsAugmenter.augmentStatsForPackage(stats,
+                            packageName, userId, callingPackage);
+                }, "queryStatsForPackage");
+            }
             return translate(stats);
         }
     }
@@ -353,6 +369,12 @@
         } catch (InstallerException e) {
             throw new ParcelableException(new IOException(e.getMessage()));
         }
+
+        if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) {
+            forEachStorageStatsAugmenter((storageStatsAugmenter) -> {
+                storageStatsAugmenter.augmentStatsForUid(stats, uid, callingPackage);
+            }, "queryStatsForUid");
+        }
         return translate(stats);
     }
 
@@ -379,6 +401,12 @@
         } catch (InstallerException e) {
             throw new ParcelableException(new IOException(e.getMessage()));
         }
+
+        if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) {
+            forEachStorageStatsAugmenter((storageStatsAugmenter) -> {
+                storageStatsAugmenter.augmentStatsForUser(stats, userId, callingPackage);
+            }, "queryStatsForUser");
+        }
         return translate(stats);
     }
 
@@ -705,4 +733,29 @@
             throw new ParcelableException(new IOException(e.getMessage()));
         }
     }
+
+    void forEachStorageStatsAugmenter(@NonNull Consumer<StorageStatsAugmenter> consumer,
+                @NonNull String queryTag) {
+        for (int i = 0, count = mStorageStatsAugmenters.size(); i < count; ++i) {
+            final Pair<String, StorageStatsAugmenter> pair = mStorageStatsAugmenters.get(i);
+            final String augmenterTag = pair.first;
+            final StorageStatsAugmenter storageStatsAugmenter = pair.second;
+
+            Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, queryTag + ":" + augmenterTag);
+            try {
+                consumer.accept(storageStatsAugmenter);
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+            }
+        }
+    }
+
+    private class LocalService extends StorageStatsManagerInternal {
+        @Override
+        public void registerStorageStatsAugmenter(
+                @NonNull StorageStatsAugmenter storageStatsAugmenter,
+                @NonNull String tag) {
+            mStorageStatsAugmenters.add(Pair.create(tag, storageStatsAugmenter));
+        }
+    }
 }
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index c900f38..5119e58 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -369,7 +369,7 @@
     /**
      * Fetches a map (package_name:install_time) of installed packages for the given user. This
      * map contains all installed packages, including those packages which have been uninstalled
-     * with the DONT_DELETE_DATA flag.
+     * with the DELETE_KEEP_DATA flag.
      * This is a helper method which should only be called when the given user's usage stats service
      * is initialized; it performs a heavy query to package manager so do not call it otherwise.
      * <br/>
@@ -1568,44 +1568,16 @@
         }
 
         @Override
-        public void setAppStandbyBucket(String packageName,
-                int bucket, int userId) {
+        public void setAppStandbyBucket(String packageName, int bucket, int userId) {
             getContext().enforceCallingPermission(Manifest.permission.CHANGE_APP_IDLE_STATE,
                     "No permission to change app standby state");
 
-            if (bucket < UsageStatsManager.STANDBY_BUCKET_ACTIVE
-                    || bucket > UsageStatsManager.STANDBY_BUCKET_NEVER) {
-                throw new IllegalArgumentException("Cannot set the standby bucket to " + bucket);
-            }
             final int callingUid = Binder.getCallingUid();
-            try {
-                userId = ActivityManager.getService().handleIncomingUser(
-                        Binder.getCallingPid(), callingUid, userId, false, true,
-                        "setAppStandbyBucket", null);
-            } catch (RemoteException re) {
-                throw re.rethrowFromSystemServer();
-            }
-            final boolean shellCaller = callingUid == 0 || callingUid == Process.SHELL_UID;
-            final boolean systemCaller = UserHandle.isCore(callingUid);
-            final int reason = systemCaller
-                    ? UsageStatsManager.REASON_MAIN_FORCED
-                    : UsageStatsManager.REASON_MAIN_PREDICTED;
+            final int callingPid = Binder.getCallingPid();
             final long token = Binder.clearCallingIdentity();
             try {
-                final int packageUid = mPackageManagerInternal.getPackageUid(packageName,
-                        PackageManager.MATCH_ANY_USER | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                        | PackageManager.MATCH_DIRECT_BOOT_AWARE, userId);
-                // Caller cannot set their own standby state
-                if (packageUid == callingUid) {
-                    throw new IllegalArgumentException("Cannot set your own standby bucket");
-                }
-                if (packageUid < 0) {
-                    throw new IllegalArgumentException(
-                            "Cannot set standby bucket for non existent package (" + packageName
-                                    + ")");
-                }
-                mAppStandby.setAppStandbyBucket(packageName, userId, bucket, reason,
-                        SystemClock.elapsedRealtime(), shellCaller);
+                mAppStandby.setAppStandbyBucket(packageName, bucket, userId,
+                        callingUid, callingPid);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -1643,37 +1615,11 @@
                     "No permission to change app standby state");
 
             final int callingUid = Binder.getCallingUid();
-            try {
-                userId = ActivityManager.getService().handleIncomingUser(
-                        Binder.getCallingPid(), callingUid, userId, false, true,
-                        "setAppStandbyBucket", null);
-            } catch (RemoteException re) {
-                throw re.rethrowFromSystemServer();
-            }
-            final boolean shellCaller = callingUid == 0 || callingUid == Process.SHELL_UID;
-            final int reason = shellCaller
-                    ? UsageStatsManager.REASON_MAIN_FORCED
-                    : UsageStatsManager.REASON_MAIN_PREDICTED;
+            final int callingPid = Binder.getCallingPid();
             final long token = Binder.clearCallingIdentity();
             try {
-                final long elapsedRealtime = SystemClock.elapsedRealtime();
-                List<AppStandbyInfo> bucketList = appBuckets.getList();
-                for (AppStandbyInfo bucketInfo : bucketList) {
-                    final String packageName = bucketInfo.mPackageName;
-                    final int bucket = bucketInfo.mStandbyBucket;
-                    if (bucket < UsageStatsManager.STANDBY_BUCKET_ACTIVE
-                            || bucket > UsageStatsManager.STANDBY_BUCKET_NEVER) {
-                        throw new IllegalArgumentException(
-                                "Cannot set the standby bucket to " + bucket);
-                    }
-                    // Caller cannot set their own standby state
-                    if (mPackageManagerInternal.getPackageUid(packageName,
-                            PackageManager.MATCH_ANY_USER, userId) == callingUid) {
-                        throw new IllegalArgumentException("Cannot set your own standby bucket");
-                    }
-                    mAppStandby.setAppStandbyBucket(packageName, userId, bucket, reason,
-                            elapsedRealtime, shellCaller);
-                }
+                mAppStandby.setAppStandbyBuckets(appBuckets.getList(), userId,
+                        callingUid, callingPid);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -2123,6 +2069,13 @@
         }
 
         @Override
+        public UsageEvents queryEventsForUser(int userId, long beginTime, long endTime,
+                boolean shouldObfuscateInstantApps) {
+            return UsageStatsService.this.queryEvents(
+                    userId, beginTime, endTime, shouldObfuscateInstantApps);
+        }
+
+        @Override
         public void setLastJobRunTime(String packageName, int userId, long elapsedRealtime) {
             mAppStandby.setLastJobRunTime(packageName, userId, elapsedRealtime);
         }
diff --git a/services/usb/Android.bp b/services/usb/Android.bp
index d2c973a..a9474c1 100644
--- a/services/usb/Android.bp
+++ b/services/usb/Android.bp
@@ -19,5 +19,6 @@
         "android.hardware.usb-V1.1-java",
         "android.hardware.usb-V1.2-java",
         "android.hardware.usb.gadget-V1.0-java",
+        "android.hardware.usb.gadget-V1.1-java",
     ],
 }
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 9f3b07b..6407ec7 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -161,6 +161,7 @@
     private static final int MSG_GET_CURRENT_USB_FUNCTIONS = 16;
     private static final int MSG_FUNCTION_SWITCH_TIMEOUT = 17;
     private static final int MSG_GADGET_HAL_REGISTERED = 18;
+    private static final int MSG_RESET_USB_GADGET = 19;
 
     private static final int AUDIO_MODE_SOURCE = 1;
 
@@ -1846,6 +1847,23 @@
                         }
                     }
                     break;
+                case MSG_RESET_USB_GADGET:
+                    synchronized (mGadgetProxyLock) {
+                        if (mGadgetProxy == null) {
+                            Slog.e(TAG, "reset Usb Gadget mGadgetProxy is null");
+                            break;
+                        }
+
+                        try {
+                            android.hardware.usb.gadget.V1_1.IUsbGadget gadgetProxy =
+                                    android.hardware.usb.gadget.V1_1.IUsbGadget
+                                            .castFrom(mGadgetProxy);
+                            gadgetProxy.reset();
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "reset Usb Gadget failed", e);
+                        }
+                    }
+                    break;
                 default:
                     super.handleMessage(msg);
             }
@@ -2054,6 +2072,17 @@
         mHandler.sendMessage(MSG_SET_SCREEN_UNLOCKED_FUNCTIONS, functions);
     }
 
+    /**
+     * Resets the USB Gadget.
+     */
+    public void resetUsbGadget() {
+        if (DEBUG) {
+            Slog.d(TAG, "reset Usb Gadget");
+        }
+
+        mHandler.sendMessage(MSG_RESET_USB_GADGET, null);
+    }
+
     private void onAdbEnabled(boolean enabled) {
         mHandler.sendMessage(MSG_ENABLE_ADB, enabled);
     }
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 27531949..61f2c50 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -56,6 +56,8 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.dump.DualDumpOutputStream;
+import com.android.server.FgThread;
+import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemService;
 
 import java.io.File;
@@ -64,6 +66,8 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
 
 /**
  * UsbService manages all USB related state, including both host and device support.
@@ -74,6 +78,9 @@
 
     public static class Lifecycle extends SystemService {
         private UsbService mUsbService;
+        private final CompletableFuture<Void> mOnStartFinished = new CompletableFuture<>();
+        private final CompletableFuture<Void> mOnActivityManagerPhaseFinished =
+                new CompletableFuture<>();
 
         public Lifecycle(Context context) {
             super(context);
@@ -81,32 +88,41 @@
 
         @Override
         public void onStart() {
-            mUsbService = new UsbService(getContext());
-            publishBinderService(Context.USB_SERVICE, mUsbService);
+            SystemServerInitThreadPool.submit(() -> {
+                mUsbService = new UsbService(getContext());
+                publishBinderService(Context.USB_SERVICE, mUsbService);
+                mOnStartFinished.complete(null);
+            }, "UsbService$Lifecycle#onStart");
         }
 
         @Override
         public void onBootPhase(int phase) {
             if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
-                mUsbService.systemReady();
+                SystemServerInitThreadPool.submit(() -> {
+                    mOnStartFinished.join();
+                    mUsbService.systemReady();
+                    mOnActivityManagerPhaseFinished.complete(null);
+                }, "UsbService$Lifecycle#onBootPhase");
             } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
+                mOnActivityManagerPhaseFinished.join();
                 mUsbService.bootCompleted();
             }
         }
 
         @Override
-        public void onSwitchUser(int newUserId) {
-            mUsbService.onSwitchUser(newUserId);
+        public void onSwitchUser(TargetUser from, TargetUser to) {
+            FgThread.getHandler()
+                    .postAtFrontOfQueue(() -> mUsbService.onSwitchUser(to.getUserIdentifier()));
         }
 
         @Override
-        public void onStopUser(int userHandle) {
-            mUsbService.onStopUser(UserHandle.of(userHandle));
+        public void onStopUser(TargetUser userInfo) {
+            mUsbService.onStopUser(userInfo.getUserHandle());
         }
 
         @Override
-        public void onUnlockUser(int userHandle) {
-            mUsbService.onUnlockUser(userHandle);
+        public void onUnlockUser(TargetUser userInfo) {
+            mUsbService.onUnlockUser(userInfo.getUserIdentifier());
         }
     }
 
@@ -330,7 +346,7 @@
 
     @Override
     public void setDevicePackage(UsbDevice device, String packageName, int userId) {
-        device = Preconditions.checkNotNull(device);
+        Objects.requireNonNull(device);
 
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
 
@@ -346,7 +362,7 @@
 
     @Override
     public void setAccessoryPackage(UsbAccessory accessory, String packageName, int userId) {
-        accessory = Preconditions.checkNotNull(accessory);
+        Objects.requireNonNull(accessory);
 
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
 
@@ -364,9 +380,9 @@
     @Override
     public void addDevicePackagesToPreferenceDenied(UsbDevice device, String[] packageNames,
             UserHandle user) {
-        device = Preconditions.checkNotNull(device);
+        Objects.requireNonNull(device);
         packageNames = Preconditions.checkArrayElementsNotNull(packageNames, "packageNames");
-        user = Preconditions.checkNotNull(user);
+        Objects.requireNonNull(user);
 
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
 
@@ -382,9 +398,9 @@
     @Override
     public void addAccessoryPackagesToPreferenceDenied(UsbAccessory accessory,
             String[] packageNames, UserHandle user) {
-        accessory = Preconditions.checkNotNull(accessory);
+        Objects.requireNonNull(accessory);
         packageNames = Preconditions.checkArrayElementsNotNull(packageNames, "packageNames");
-        user = Preconditions.checkNotNull(user);
+        Objects.requireNonNull(user);
 
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
 
@@ -400,9 +416,9 @@
     @Override
     public void removeDevicePackagesFromPreferenceDenied(UsbDevice device, String[] packageNames,
             UserHandle user) {
-        device = Preconditions.checkNotNull(device);
+        Objects.requireNonNull(device);
         packageNames = Preconditions.checkArrayElementsNotNull(packageNames, "packageNames");
-        user = Preconditions.checkNotNull(user);
+        Objects.requireNonNull(user);
 
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
 
@@ -418,9 +434,9 @@
     @Override
     public void removeAccessoryPackagesFromPreferenceDenied(UsbAccessory accessory,
             String[] packageNames, UserHandle user) {
-        accessory = Preconditions.checkNotNull(accessory);
+        Objects.requireNonNull(accessory);
         packageNames = Preconditions.checkArrayElementsNotNull(packageNames, "packageNames");
-        user = Preconditions.checkNotNull(user);
+        Objects.requireNonNull(user);
 
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
 
@@ -436,8 +452,8 @@
     @Override
     public void setDevicePersistentPermission(UsbDevice device, int uid, UserHandle user,
             boolean shouldBeGranted) {
-        device = Preconditions.checkNotNull(device);
-        user = Preconditions.checkNotNull(user);
+        Objects.requireNonNull(device);
+        Objects.requireNonNull(user);
 
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
 
@@ -453,8 +469,8 @@
     @Override
     public void setAccessoryPersistentPermission(UsbAccessory accessory, int uid,
             UserHandle user, boolean shouldBeGranted) {
-        accessory = Preconditions.checkNotNull(accessory);
-        user = Preconditions.checkNotNull(user);
+        Objects.requireNonNull(accessory);
+        Objects.requireNonNull(user);
 
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
 
@@ -622,6 +638,19 @@
     }
 
     @Override
+    public void resetUsbGadget() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        Preconditions.checkNotNull(mDeviceManager, "DeviceManager must not be null");
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mDeviceManager.resetUsbGadget();
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
     public List<ParcelableUsbPort> getPorts() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
 
@@ -648,7 +677,7 @@
 
     @Override
     public UsbPortStatus getPortStatus(String portId) {
-        Preconditions.checkNotNull(portId, "portId must not be null");
+        Objects.requireNonNull(portId, "portId must not be null");
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
 
         final long ident = Binder.clearCallingIdentity();
@@ -661,7 +690,7 @@
 
     @Override
     public void setPortRoles(String portId, int powerRole, int dataRole) {
-        Preconditions.checkNotNull(portId, "portId must not be null");
+        Objects.requireNonNull(portId, "portId must not be null");
         UsbPort.checkRoles(powerRole, dataRole);
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
 
@@ -677,7 +706,7 @@
 
     @Override
     public void enableContaminantDetection(String portId, boolean enable) {
-        Preconditions.checkNotNull(portId, "portId must not be null");
+        Objects.requireNonNull(portId, "portId must not be null");
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
 
         final long ident = Binder.clearCallingIdentity();
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java
index f7cd6a3..9906585 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java
@@ -21,12 +21,10 @@
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
-import android.hardware.soundtrigger.SoundTrigger;
 import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
-import android.text.TextUtils;
 import android.util.Slog;
 
-import java.util.Locale;
+import java.io.PrintWriter;
 import java.util.UUID;
 
 /**
@@ -39,7 +37,7 @@
     static final boolean DBG = false;
 
     private static final String NAME = "st_sound_model.db";
-    private static final int VERSION = 1;
+    private static final int VERSION = 2;
 
     // Sound trigger-based sound models.
     public static interface GenericSoundModelContract {
@@ -47,15 +45,16 @@
         public static final String KEY_MODEL_UUID = "model_uuid";
         public static final String KEY_VENDOR_UUID = "vendor_uuid";
         public static final String KEY_DATA = "data";
+        public static final String KEY_MODEL_VERSION = "model_version";
     }
 
-
     // Table Create Statement for the sound trigger table
     private static final String CREATE_TABLE_ST_SOUND_MODEL = "CREATE TABLE "
             + GenericSoundModelContract.TABLE + "("
             + GenericSoundModelContract.KEY_MODEL_UUID + " TEXT PRIMARY KEY,"
             + GenericSoundModelContract.KEY_VENDOR_UUID + " TEXT,"
-            + GenericSoundModelContract.KEY_DATA + " BLOB" + " )";
+            + GenericSoundModelContract.KEY_DATA + " BLOB,"
+            + GenericSoundModelContract.KEY_MODEL_VERSION + " INTEGER" + " )";
 
 
     public SoundTriggerDbHelper(Context context) {
@@ -70,9 +69,13 @@
 
     @Override
     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-        // TODO: For now, drop older tables and recreate new ones.
-        db.execSQL("DROP TABLE IF EXISTS " + GenericSoundModelContract.TABLE);
-        onCreate(db);
+        if (oldVersion == 1) {
+            // In version 2, a model version number was added.
+            Slog.d(TAG, "Adding model version column");
+            db.execSQL("ALTER TABLE " + GenericSoundModelContract.TABLE + " ADD COLUMN "
+                    + GenericSoundModelContract.KEY_MODEL_VERSION + " INTEGER DEFAULT -1");
+            oldVersion++;
+        }
     }
 
     /**
@@ -86,6 +89,7 @@
             values.put(GenericSoundModelContract.KEY_MODEL_UUID, soundModel.uuid.toString());
             values.put(GenericSoundModelContract.KEY_VENDOR_UUID, soundModel.vendorUuid.toString());
             values.put(GenericSoundModelContract.KEY_DATA, soundModel.data);
+            values.put(GenericSoundModelContract.KEY_MODEL_VERSION, soundModel.version);
 
             try {
                 return db.insertWithOnConflict(GenericSoundModelContract.TABLE, null, values,
@@ -113,8 +117,10 @@
                                 GenericSoundModelContract.KEY_DATA));
                         String vendor_uuid = c.getString(
                                 c.getColumnIndex(GenericSoundModelContract.KEY_VENDOR_UUID));
+                        int version = c.getInt(
+                                c.getColumnIndex(GenericSoundModelContract.KEY_MODEL_VERSION));
                         return new GenericSoundModel(model_uuid, UUID.fromString(vendor_uuid),
-                                data);
+                                data, version);
                     } while (c.moveToNext());
                 }
             } finally {
@@ -142,4 +148,48 @@
             }
         }
     }
+
+    public void dump(PrintWriter pw) {
+        synchronized(this) {
+            String selectQuery = "SELECT  * FROM " + GenericSoundModelContract.TABLE;
+            SQLiteDatabase db = getReadableDatabase();
+            Cursor c = db.rawQuery(selectQuery, null);
+            try {
+                pw.println("  Enrolled GenericSoundModels:");
+                if (c.moveToFirst()) {
+                    String[] columnNames = c.getColumnNames();
+                    do {
+                        for (String name : columnNames) {
+                            int colNameIndex = c.getColumnIndex(name);
+                            int type = c.getType(colNameIndex);
+                            switch (type) {
+                                case Cursor.FIELD_TYPE_STRING:
+                                    pw.printf("    %s: %s\n", name,
+                                            c.getString(colNameIndex));
+                                    break;
+                                case Cursor.FIELD_TYPE_BLOB:
+                                    pw.printf("    %s: data blob\n", name);
+                                    break;
+                                case Cursor.FIELD_TYPE_INTEGER:
+                                    pw.printf("    %s: %d\n", name,
+                                            c.getInt(colNameIndex));
+                                    break;
+                                case Cursor.FIELD_TYPE_FLOAT:
+                                    pw.printf("    %s: %f\n", name,
+                                            c.getFloat(colNameIndex));
+                                    break;
+                                case Cursor.FIELD_TYPE_NULL:
+                                    pw.printf("    %s: null\n", name);
+                                    break;
+                            }
+                        }
+                        pw.println();
+                    } while (c.moveToNext());
+                }
+            } finally {
+                c.close();
+                db.close();
+            }
+        }
+    }
 }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 198b4c3..7099c09 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -611,65 +611,91 @@
 
     int setParameter(UUID modelId, @ModelParams int modelParam, int value) {
         synchronized (mLock) {
-            MetricsLogger.count(mContext, "sth_set_parameter", 1);
-            if (modelId == null || mModule == null) {
-                return SoundTrigger.STATUS_ERROR;
-            }
-            ModelData modelData = mModelDataMap.get(modelId);
-            if (modelData == null) {
-                Slog.w(TAG, "SetParameter: Invalid model id:" + modelId);
-                return SoundTrigger.STATUS_BAD_VALUE;
-            }
-            if (!modelData.isModelLoaded()) {
-                Slog.i(TAG, "SetParameter: Given model is not loaded:" + modelId);
-                return SoundTrigger.STATUS_BAD_VALUE;
-            }
-
-            return mModule.setParameter(modelData.getHandle(), modelParam, value);
+            return setParameterLocked(mModelDataMap.get(modelId), modelParam, value);
         }
     }
 
-    int getParameter(@NonNull UUID modelId, @ModelParams int modelParam)
-            throws UnsupportedOperationException, IllegalArgumentException {
+    int setKeyphraseParameter(int keyphraseId, @ModelParams int modelParam, int value) {
         synchronized (mLock) {
-            MetricsLogger.count(mContext, "sth_get_parameter", 1);
-            if (mModule == null) {
-                throw new UnsupportedOperationException("SoundTriggerModule not initialized");
-            }
-
-            ModelData modelData = mModelDataMap.get(modelId);
-            if (modelData == null) {
-                throw new IllegalArgumentException("Invalid model id:" + modelId);
-            }
-            if (!modelData.isModelLoaded()) {
-                throw new UnsupportedOperationException("Given model is not loaded:" + modelId);
-            }
-
-            return mModule.getParameter(modelData.getHandle(), modelParam);
+            return setParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam, value);
         }
     }
 
+    private int setParameterLocked(@Nullable ModelData modelData, @ModelParams int modelParam,
+            int value) {
+        MetricsLogger.count(mContext, "sth_set_parameter", 1);
+        if (mModule == null) {
+            return SoundTrigger.STATUS_NO_INIT;
+        }
+        if (modelData == null || !modelData.isModelLoaded()) {
+            Slog.i(TAG, "SetParameter: Given model is not loaded:" + modelData);
+            return SoundTrigger.STATUS_BAD_VALUE;
+        }
+
+        return mModule.setParameter(modelData.getHandle(), modelParam, value);
+    }
+
+    int getParameter(@NonNull UUID modelId, @ModelParams int modelParam) {
+        synchronized (mLock) {
+            return getParameterLocked(mModelDataMap.get(modelId), modelParam);
+        }
+    }
+
+    int getKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) {
+        synchronized (mLock) {
+            return getParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam);
+        }
+    }
+
+    private int getParameterLocked(@Nullable ModelData modelData, @ModelParams int modelParam) {
+        MetricsLogger.count(mContext, "sth_get_parameter", 1);
+        if (mModule == null) {
+            throw new UnsupportedOperationException("SoundTriggerModule not initialized");
+        }
+
+        if (modelData == null) {
+            throw new IllegalArgumentException("Invalid model id");
+        }
+        if (!modelData.isModelLoaded()) {
+            throw new UnsupportedOperationException("Given model is not loaded:" + modelData);
+        }
+
+        return mModule.getParameter(modelData.getHandle(), modelParam);
+    }
+
     @Nullable
     ModelParamRange queryParameter(@NonNull UUID modelId, @ModelParams int modelParam) {
         synchronized (mLock) {
-            MetricsLogger.count(mContext, "sth_query_parameter", 1);
-            if (mModule == null) {
-                return null;
-            }
-            ModelData modelData = mModelDataMap.get(modelId);
-            if (modelData == null) {
-                Slog.w(TAG, "queryParameter: Invalid model id:" + modelId);
-                return null;
-            }
-            if (!modelData.isModelLoaded()) {
-                Slog.i(TAG, "queryParameter: Given model is not loaded:" + modelId);
-                return null;
-            }
-
-            return mModule.queryParameter(modelData.getHandle(), modelParam);
+            return queryParameterLocked(mModelDataMap.get(modelId), modelParam);
         }
     }
 
+    @Nullable
+    ModelParamRange queryKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) {
+        synchronized (mLock) {
+            return queryParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam);
+        }
+    }
+
+    @Nullable
+    private ModelParamRange queryParameterLocked(@Nullable ModelData modelData,
+            @ModelParams int modelParam) {
+        MetricsLogger.count(mContext, "sth_query_parameter", 1);
+        if (mModule == null) {
+            return null;
+        }
+        if (modelData == null) {
+            Slog.w(TAG, "queryParameter: Invalid model id");
+            return null;
+        }
+        if (!modelData.isModelLoaded()) {
+            Slog.i(TAG, "queryParameter: Given model is not loaded:" + modelData);
+            return null;
+        }
+
+        return mModule.queryParameter(modelData.getHandle(), modelParam);
+    }
+
     //---- SoundTrigger.StatusListener methods
     @Override
     public void onRecognition(RecognitionEvent event) {
@@ -730,7 +756,9 @@
             return;
         }
 
-        model.setStopped();
+        if (event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
+            model.setStopped();
+        }
 
         try {
             callback.onGenericSoundTriggerDetected((GenericRecognitionEvent) event);
@@ -874,7 +902,9 @@
             return;
         }
 
-        modelData.setStopped();
+        if (event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
+            modelData.setStopped();
+        }
 
         try {
             modelData.getCallback().onKeyphraseDetected((KeyphraseRecognitionEvent) event);
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
index d05e044..54dffdc 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
@@ -16,17 +16,17 @@
 
 package com.android.server.soundtrigger;
 
+import android.annotation.Nullable;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.ModelParams;
 import android.hardware.soundtrigger.SoundTrigger;
 import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
-import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent;
-import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra;
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
-import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
-import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent;
-import android.hardware.soundtrigger.SoundTriggerModule;
+
+import com.android.server.voiceinteraction.VoiceInteractionManagerService;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -71,6 +71,58 @@
     public abstract ModuleProperties getModuleProperties();
 
     /**
+     * Set a model specific {@link ModelParams} with the given value. This
+     * parameter will keep its value for the duration the model is loaded regardless of starting and
+     * stopping recognition. Once the model is unloaded, the value will be lost.
+     * {@link SoundTriggerInternal#queryParameter} should be checked first before calling this
+     * method.
+     *
+     * @param keyphraseId The identifier of the keyphrase for which
+     *        to modify model parameters
+     * @param modelParam   {@link ModelParams}
+     * @param value        Value to set
+     * @return - {@link SoundTrigger#STATUS_OK} in case of success
+     *         - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
+     *         - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
+     *         - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
+     *           if API is not supported by HAL
+     */
+    public abstract int setParameter(int keyphraseId, @ModelParams int modelParam, int value);
+
+    /**
+     * Get a model specific {@link ModelParams}. This parameter will keep its value
+     * for the duration the model is loaded regardless of starting and stopping recognition.
+     * Once the model is unloaded, the value will be lost. If the value is not set, a default
+     * value is returned. See ModelParams for parameter default values.
+     * {@link SoundTriggerInternal#queryParameter} should be checked first before calling this
+     * method.
+     *
+     * @param keyphraseId The identifier of the keyphrase for which
+     *        to modify model parameters
+     * @param modelParam   {@link ModelParams}
+     * @return value of parameter
+     * @throws UnsupportedOperationException if hal or model do not support this API.
+     *         queryParameter should be checked first.
+     * @throws IllegalArgumentException if invalid model handle or parameter is passed.
+     *         queryParameter should be checked first.
+     */
+    public abstract int getParameter(int keyphraseId, @ModelParams int modelParam);
+
+    /**
+     * Determine if parameter control is supported for the given model handle.
+     * This method should be checked prior to calling {@link SoundTriggerInternal#setParameter}
+     * or {@link SoundTriggerInternal#getParameter}.
+     *
+     * @param keyphraseId The identifier of the keyphrase for which
+     *        to modify model parameters
+     * @param modelParam {@link ModelParams}
+     * @return supported range of parameter, null if not supported
+     */
+    @Nullable
+    public abstract ModelParamRange queryParameter(int keyphraseId,
+            @ModelParams int modelParam);
+
+    /**
      * Unloads (and stops if running) the given keyphraseId
      */
     public abstract int unloadKeyphraseModel(int keyphaseId);
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 68b16f3..767010a 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -1446,26 +1446,45 @@
         @Override
         public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
                 IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) {
-            if (!isInitialized()) return STATUS_ERROR;
+            if (!isInitialized()) throw new UnsupportedOperationException();
             return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener,
                     recognitionConfig);
         }
 
         @Override
         public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
-            if (!isInitialized()) return STATUS_ERROR;
+            if (!isInitialized()) throw new UnsupportedOperationException();
             return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener);
         }
 
         @Override
         public ModuleProperties getModuleProperties() {
-            if (!isInitialized()) return null;
+            if (!isInitialized()) throw new UnsupportedOperationException();
             return mSoundTriggerHelper.getModuleProperties();
         }
 
         @Override
+        public int setParameter(int keyphraseId, @ModelParams int modelParam, int value) {
+            if (!isInitialized()) throw new UnsupportedOperationException();
+            return mSoundTriggerHelper.setKeyphraseParameter(keyphraseId, modelParam, value);
+        }
+
+        @Override
+        public int getParameter(int keyphraseId, @ModelParams int modelParam) {
+            if (!isInitialized()) throw new UnsupportedOperationException();
+            return mSoundTriggerHelper.getKeyphraseParameter(keyphraseId, modelParam);
+        }
+
+        @Override
+        @Nullable
+        public ModelParamRange queryParameter(int keyphraseId, @ModelParams int modelParam) {
+            if (!isInitialized()) throw new UnsupportedOperationException();
+            return mSoundTriggerHelper.queryKeyphraseParameter(keyphraseId, modelParam);
+        }
+
+        @Override
         public int unloadKeyphraseModel(int keyphraseId) {
-            if (!isInitialized()) return STATUS_ERROR;
+            if (!isInitialized()) throw new UnsupportedOperationException();
             return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId);
         }
 
@@ -1476,6 +1495,9 @@
             // log
             sEventLogger.dump(pw);
 
+            // enrolled models
+            mDbHelper.dump(pw);
+
             // stats
             mSoundModelStatTracker.dump(pw);
         }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
index dd7b5a8..af81ab6 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
@@ -27,6 +27,7 @@
 import android.text.TextUtils;
 import android.util.Slog;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -43,19 +44,23 @@
     static final boolean DBG = false;
 
     private static final String NAME = "sound_model.db";
-    private static final int VERSION = 6;
+    private static final int VERSION = 7;
 
-    public static interface SoundModelContract {
-        public static final String TABLE = "sound_model";
-        public static final String KEY_MODEL_UUID = "model_uuid";
-        public static final String KEY_VENDOR_UUID = "vendor_uuid";
-        public static final String KEY_KEYPHRASE_ID = "keyphrase_id";
-        public static final String KEY_TYPE = "type";
-        public static final String KEY_DATA = "data";
-        public static final String KEY_RECOGNITION_MODES = "recognition_modes";
-        public static final String KEY_LOCALE = "locale";
-        public static final String KEY_HINT_TEXT = "hint_text";
-        public static final String KEY_USERS = "users";
+    /**
+     * Keyphrase sound model database columns
+     */
+    public interface SoundModelContract {
+        String TABLE = "sound_model";
+        String KEY_MODEL_UUID = "model_uuid";
+        String KEY_VENDOR_UUID = "vendor_uuid";
+        String KEY_KEYPHRASE_ID = "keyphrase_id";
+        String KEY_TYPE = "type";
+        String KEY_DATA = "data";
+        String KEY_RECOGNITION_MODES = "recognition_modes";
+        String KEY_LOCALE = "locale";
+        String KEY_HINT_TEXT = "hint_text";
+        String KEY_USERS = "users";
+        String KEY_MODEL_VERSION = "model_version";
     }
 
     // Table Create Statement
@@ -70,6 +75,7 @@
             + SoundModelContract.KEY_LOCALE + " TEXT,"
             + SoundModelContract.KEY_HINT_TEXT + " TEXT,"
             + SoundModelContract.KEY_USERS + " TEXT,"
+            + SoundModelContract.KEY_MODEL_VERSION + " INTEGER,"
             + "PRIMARY KEY (" + SoundModelContract.KEY_KEYPHRASE_ID + ","
                               + SoundModelContract.KEY_LOCALE + ","
                               + SoundModelContract.KEY_USERS + ")"
@@ -138,6 +144,13 @@
             }
             oldVersion++;
         }
+        if (oldVersion == 6) {
+            // In version 7, a model version number was added.
+            Slog.d(TAG, "Adding model version column");
+            db.execSQL("ALTER TABLE " + SoundModelContract.TABLE + " ADD COLUMN "
+                    + SoundModelContract.KEY_MODEL_VERSION + " INTEGER DEFAULT -1");
+            oldVersion++;
+        }
     }
 
     /**
@@ -155,6 +168,7 @@
             }
             values.put(SoundModelContract.KEY_TYPE, SoundTrigger.SoundModel.TYPE_KEYPHRASE);
             values.put(SoundModelContract.KEY_DATA, soundModel.data);
+            values.put(SoundModelContract.KEY_MODEL_VERSION, soundModel.version);
 
             if (soundModel.keyphrases != null && soundModel.keyphrases.length == 1) {
                 values.put(SoundModelContract.KEY_KEYPHRASE_ID, soundModel.keyphrases[0].id);
@@ -162,7 +176,8 @@
                         soundModel.keyphrases[0].recognitionModes);
                 values.put(SoundModelContract.KEY_USERS,
                         getCommaSeparatedString(soundModel.keyphrases[0].users));
-                values.put(SoundModelContract.KEY_LOCALE, soundModel.keyphrases[0].locale);
+                values.put(SoundModelContract.KEY_LOCALE,
+                        soundModel.keyphrases[0].locale.toLanguageTag());
                 values.put(SoundModelContract.KEY_HINT_TEXT, soundModel.keyphrases[0].text);
                 try {
                     return db.insertWithOnConflict(SoundModelContract.TABLE, null, values,
@@ -179,7 +194,7 @@
      * Deletes the sound model and associated keyphrases.
      */
     public boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale) {
-        // Sanitize the locale to guard against SQL injection.
+        // Normalize the locale to guard against SQL injection.
         bcp47Locale = Locale.forLanguageTag(bcp47Locale).toLanguageTag();
         synchronized(this) {
             KeyphraseSoundModel soundModel = getKeyphraseSoundModel(keyphraseId, userHandle,
@@ -215,90 +230,119 @@
             String selectQuery = "SELECT  * FROM " + SoundModelContract.TABLE
                     + " WHERE " + SoundModelContract.KEY_KEYPHRASE_ID + "= '" + keyphraseId
                     + "' AND " + SoundModelContract.KEY_LOCALE + "='" + bcp47Locale + "'";
-            SQLiteDatabase db = getReadableDatabase();
-            Cursor c = db.rawQuery(selectQuery, null);
-
-            try {
-                if (c.moveToFirst()) {
-                    do {
-                        int type = c.getInt(c.getColumnIndex(SoundModelContract.KEY_TYPE));
-                        if (type != SoundTrigger.SoundModel.TYPE_KEYPHRASE) {
-                            if (DBG) {
-                                Slog.w(TAG, "Ignoring SoundModel since it's type is incorrect");
-                            }
-                            continue;
-                        }
-
-                        String modelUuid = c.getString(
-                                c.getColumnIndex(SoundModelContract.KEY_MODEL_UUID));
-                        if (modelUuid == null) {
-                            Slog.w(TAG, "Ignoring SoundModel since it doesn't specify an ID");
-                            continue;
-                        }
-
-                        String vendorUuidString = null;
-                        int vendorUuidColumn = c.getColumnIndex(SoundModelContract.KEY_VENDOR_UUID);
-                        if (vendorUuidColumn != -1) {
-                            vendorUuidString = c.getString(vendorUuidColumn);
-                        }
-                        byte[] data = c.getBlob(c.getColumnIndex(SoundModelContract.KEY_DATA));
-                        int recognitionModes = c.getInt(
-                                c.getColumnIndex(SoundModelContract.KEY_RECOGNITION_MODES));
-                        int[] users = getArrayForCommaSeparatedString(
-                                c.getString(c.getColumnIndex(SoundModelContract.KEY_USERS)));
-                        String modelLocale = c.getString(
-                                c.getColumnIndex(SoundModelContract.KEY_LOCALE));
-                        String text = c.getString(
-                                c.getColumnIndex(SoundModelContract.KEY_HINT_TEXT));
-
-                        // Only add keyphrases meant for the current user.
-                        if (users == null) {
-                            // No users present in the keyphrase.
-                            Slog.w(TAG, "Ignoring SoundModel since it doesn't specify users");
-                            continue;
-                        }
-
-                        boolean isAvailableForCurrentUser = false;
-                        for (int user : users) {
-                            if (userHandle == user) {
-                                isAvailableForCurrentUser = true;
-                                break;
-                            }
-                        }
-                        if (!isAvailableForCurrentUser) {
-                            if (DBG) {
-                                Slog.w(TAG, "Ignoring SoundModel since user handles don't match");
-                            }
-                            continue;
-                        } else {
-                            if (DBG) Slog.d(TAG, "Found a SoundModel for user: " + userHandle);
-                        }
-
-                        Keyphrase[] keyphrases = new Keyphrase[1];
-                        keyphrases[0] = new Keyphrase(
-                                keyphraseId, recognitionModes, modelLocale, text, users);
-                        UUID vendorUuid = null;
-                        if (vendorUuidString != null) {
-                            vendorUuid = UUID.fromString(vendorUuidString);
-                        }
-                        KeyphraseSoundModel model = new KeyphraseSoundModel(
-                                UUID.fromString(modelUuid), vendorUuid, data, keyphrases);
-                        if (DBG) {
-                            Slog.d(TAG, "Found SoundModel for the given keyphrase/locale/user: "
-                                    + model);
-                        }
-                        return model;
-                    } while (c.moveToNext());
-                }
-                Slog.w(TAG, "No SoundModel available for the given keyphrase");
-            } finally {
-                c.close();
-                db.close();
-            }
-            return null;
+            return getValidKeyphraseSoundModelForUser(selectQuery, userHandle);
         }
     }
 
+    /**
+     * Returns a matching {@link KeyphraseSoundModel} for the keyphrase string.
+     * Returns null if a match isn't found.
+     *
+     * TODO: We only support one keyphrase currently.
+     */
+    public KeyphraseSoundModel getKeyphraseSoundModel(String keyphrase, int userHandle,
+            String bcp47Locale) {
+        // Sanitize the locale to guard against SQL injection.
+        bcp47Locale = Locale.forLanguageTag(bcp47Locale).toLanguageTag();
+        synchronized (this) {
+            // Find the corresponding sound model ID for the keyphrase.
+            String selectQuery = "SELECT  * FROM " + SoundModelContract.TABLE
+                    + " WHERE " + SoundModelContract.KEY_HINT_TEXT + "= '" + keyphrase
+                    + "' AND " + SoundModelContract.KEY_LOCALE + "='" + bcp47Locale + "'";
+            return getValidKeyphraseSoundModelForUser(selectQuery, userHandle);
+        }
+    }
+
+    private KeyphraseSoundModel getValidKeyphraseSoundModelForUser(String selectQuery,
+            int userHandle) {
+        SQLiteDatabase db = getReadableDatabase();
+        Cursor c = db.rawQuery(selectQuery, null);
+
+        try {
+            if (c.moveToFirst()) {
+                do {
+                    int type = c.getInt(c.getColumnIndex(SoundModelContract.KEY_TYPE));
+                    if (type != SoundTrigger.SoundModel.TYPE_KEYPHRASE) {
+                        if (DBG) {
+                            Slog.w(TAG, "Ignoring SoundModel since its type is incorrect");
+                        }
+                        continue;
+                    }
+
+                    String modelUuid = c.getString(
+                            c.getColumnIndex(SoundModelContract.KEY_MODEL_UUID));
+                    if (modelUuid == null) {
+                        Slog.w(TAG, "Ignoring SoundModel since it doesn't specify an ID");
+                        continue;
+                    }
+
+                    String vendorUuidString = null;
+                    int vendorUuidColumn = c.getColumnIndex(SoundModelContract.KEY_VENDOR_UUID);
+                    if (vendorUuidColumn != -1) {
+                        vendorUuidString = c.getString(vendorUuidColumn);
+                    }
+                    int keyphraseId = c.getInt(
+                            c.getColumnIndex(SoundModelContract.KEY_KEYPHRASE_ID));
+                    byte[] data = c.getBlob(c.getColumnIndex(SoundModelContract.KEY_DATA));
+                    int recognitionModes = c.getInt(
+                            c.getColumnIndex(SoundModelContract.KEY_RECOGNITION_MODES));
+                    int[] users = getArrayForCommaSeparatedString(
+                            c.getString(c.getColumnIndex(SoundModelContract.KEY_USERS)));
+                    Locale modelLocale = Locale.forLanguageTag(c.getString(
+                            c.getColumnIndex(SoundModelContract.KEY_LOCALE)));
+                    String text = c.getString(
+                            c.getColumnIndex(SoundModelContract.KEY_HINT_TEXT));
+                    int version = c.getInt(
+                            c.getColumnIndex(SoundModelContract.KEY_MODEL_VERSION));
+
+                    // Only add keyphrases meant for the current user.
+                    if (users == null) {
+                        // No users present in the keyphrase.
+                        Slog.w(TAG, "Ignoring SoundModel since it doesn't specify users");
+                        continue;
+                    }
+
+                    boolean isAvailableForCurrentUser = false;
+                    for (int user : users) {
+                        if (userHandle == user) {
+                            isAvailableForCurrentUser = true;
+                            break;
+                        }
+                    }
+                    if (!isAvailableForCurrentUser) {
+                        if (DBG) {
+                            Slog.w(TAG, "Ignoring SoundModel since user handles don't match");
+                        }
+                        continue;
+                    } else {
+                        if (DBG) Slog.d(TAG, "Found a SoundModel for user: " + userHandle);
+                    }
+
+                    Keyphrase[] keyphrases = new Keyphrase[1];
+                    keyphrases[0] = new Keyphrase(
+                            keyphraseId, recognitionModes, modelLocale, text, users);
+                    UUID vendorUuid = null;
+                    if (vendorUuidString != null) {
+                        vendorUuid = UUID.fromString(vendorUuidString);
+                    }
+                    KeyphraseSoundModel model = new KeyphraseSoundModel(
+                            UUID.fromString(modelUuid), vendorUuid, data, keyphrases, version);
+                    if (DBG) {
+                        Slog.d(TAG, "Found SoundModel for the given keyphrase/locale/user: "
+                                + model);
+                    }
+                    return model;
+                } while (c.moveToNext());
+            }
+            Slog.w(TAG, "No SoundModel available for the given keyphrase");
+        } finally {
+            c.close();
+            db.close();
+        }
+
+        return null;
+    }
+
     private static String getCommaSeparatedString(int[] users) {
         if (users == null) {
             return "";
@@ -325,6 +369,10 @@
         return users;
     }
 
+    /**
+     * SoundModelRecord is no longer used, and it should only be used on database migration.
+     * This class does not need to be modified when modifying the database scheme.
+     */
     private static class SoundModelRecord {
         public final String modelUuid;
         public final String vendorUuid;
@@ -413,4 +461,51 @@
           return a == b;
         }
     }
+
+    /**
+     * Dumps contents of database for dumpsys
+     */
+    public void dump(PrintWriter pw) {
+        synchronized (this) {
+            String selectQuery = "SELECT  * FROM " + SoundModelContract.TABLE;
+            SQLiteDatabase db = getReadableDatabase();
+            Cursor c = db.rawQuery(selectQuery, null);
+            try {
+                pw.println("  Enrolled KeyphraseSoundModels:");
+                if (c.moveToFirst()) {
+                    String[] columnNames = c.getColumnNames();
+                    do {
+                        for (String name : columnNames) {
+                            int colNameIndex = c.getColumnIndex(name);
+                            int type = c.getType(colNameIndex);
+                            switch (type) {
+                                case Cursor.FIELD_TYPE_STRING:
+                                    pw.printf("    %s: %s\n", name,
+                                            c.getString(colNameIndex));
+                                    break;
+                                case Cursor.FIELD_TYPE_BLOB:
+                                    pw.printf("    %s: data blob\n", name);
+                                    break;
+                                case Cursor.FIELD_TYPE_INTEGER:
+                                    pw.printf("    %s: %d\n", name,
+                                            c.getInt(colNameIndex));
+                                    break;
+                                case Cursor.FIELD_TYPE_FLOAT:
+                                    pw.printf("    %s: %f\n", name,
+                                            c.getFloat(colNameIndex));
+                                    break;
+                                case Cursor.FIELD_TYPE_NULL:
+                                    pw.printf("    %s: null\n", name);
+                                    break;
+                            }
+                        }
+                        pw.println();
+                    } while (c.moveToNext());
+                }
+            } finally {
+                c.close();
+                db.close();
+            }
+        }
+    }
 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index e9db31b..d5eec33 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -41,7 +41,11 @@
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.KeyphraseMetadata;
+import android.hardware.soundtrigger.ModelParams;
+import android.hardware.soundtrigger.SoundTrigger;
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
 import android.os.Binder;
@@ -88,6 +92,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.List;
+import java.util.Locale;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -157,9 +162,13 @@
         }
     }
 
+    private boolean isSupported(UserInfo user) {
+        return user.isFull();
+    }
+
     @Override
-    public boolean isSupported(UserInfo userInfo) {
-        return userInfo.isFull();
+    public boolean isSupportedUser(TargetUser user) {
+        return isSupported(user.getUserInfo());
     }
 
     @Override
@@ -917,6 +926,8 @@
         }
 
         //----------------- Model management APIs --------------------------------//
+        // TODO: add check to only allow active voice interaction service or keyphrase enrollment
+        //       application to manage voice models
 
         @Override
         public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, String bcp47Locale) {
@@ -1016,6 +1027,41 @@
             }
         }
 
+        @Nullable
+        public KeyphraseMetadata getEnrolledKeyphraseMetadata(IVoiceInteractionService service,
+                String keyphrase, String bcp47Locale) {
+            synchronized (this) {
+                enforceIsCurrentVoiceInteractionService(service);
+            }
+
+            if (bcp47Locale == null) {
+                throw new IllegalArgumentException("Illegal argument(s) in isEnrolledForKeyphrase");
+            }
+
+            final int callingUid = UserHandle.getCallingUserId();
+            final long caller = Binder.clearCallingIdentity();
+            try {
+                KeyphraseSoundModel model =
+                        mDbHelper.getKeyphraseSoundModel(keyphrase, callingUid, bcp47Locale);
+                if (model == null) {
+                    return null;
+                }
+
+                for (SoundTrigger.Keyphrase phrase : model.keyphrases) {
+                    if (keyphrase.equals(phrase.text)) {
+                        ArraySet<Locale> locales = new ArraySet<>();
+                        locales.add(phrase.locale);
+                        return new KeyphraseMetadata(phrase.id, phrase.text, locales,
+                                phrase.recognitionModes);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(caller);
+            }
+
+            return null;
+        }
+
         @Override
         public ModuleProperties getDspModuleProperties(IVoiceInteractionService service) {
             // Allow the call if this is the current voice interaction service.
@@ -1084,6 +1130,55 @@
             }
         }
 
+        @Override
+        public int setParameter(IVoiceInteractionService service, int keyphraseId,
+                @ModelParams int modelParam, int value) {
+            // Allow the call if this is the current voice interaction service.
+            synchronized (this) {
+                enforceIsCurrentVoiceInteractionService(service);
+            }
+
+            final long caller = Binder.clearCallingIdentity();
+            try {
+                return mSoundTriggerInternal.setParameter(keyphraseId, modelParam, value);
+            } finally {
+                Binder.restoreCallingIdentity(caller);
+            }
+        }
+
+        @Override
+        public int getParameter(IVoiceInteractionService service, int keyphraseId,
+                @ModelParams int modelParam) {
+            // Allow the call if this is the current voice interaction service.
+            synchronized (this) {
+                enforceIsCurrentVoiceInteractionService(service);
+            }
+
+            final long caller = Binder.clearCallingIdentity();
+            try {
+                return mSoundTriggerInternal.getParameter(keyphraseId, modelParam);
+            } finally {
+                Binder.restoreCallingIdentity(caller);
+            }
+        }
+
+        @Override
+        @Nullable
+        public ModelParamRange queryParameter(IVoiceInteractionService service,
+                int keyphraseId, @ModelParams int modelParam) {
+            // Allow the call if this is the current voice interaction service.
+            synchronized (this) {
+                enforceIsCurrentVoiceInteractionService(service);
+            }
+
+            final long caller = Binder.clearCallingIdentity();
+            try {
+                return mSoundTriggerInternal.queryParameter(keyphraseId, modelParam);
+            } finally {
+                Binder.restoreCallingIdentity(caller);
+            }
+        }
+
         private synchronized void unloadAllKeyphraseModels() {
             for (int i = 0; i < mLoadedKeyphraseIds.size(); i++) {
                 final long caller = Binder.clearCallingIdentity();
@@ -1292,6 +1387,7 @@
                 pw.println("  mCurUserUnlocked: " + mCurUserUnlocked);
                 pw.println("  mCurUserSupported: " + mCurUserSupported);
                 dumpSupportedUsers(pw, "  ");
+                mDbHelper.dump(pw);
                 if (mImpl == null) {
                     pw.println("  (No active implementation)");
                     return;
diff --git a/telecomm/TEST_MAPPING b/telecomm/TEST_MAPPING
new file mode 100644
index 0000000..d585666
--- /dev/null
+++ b/telecomm/TEST_MAPPING
@@ -0,0 +1,29 @@
+{
+  "presubmit": [
+    {
+      "name": "TeleServiceTests",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
+      "name": "TelecomUnitTests",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
+      "name": "TelephonyProviderTests",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
+  ]
+}
+
diff --git a/telecomm/java/android/telecom/AudioState.java b/telecomm/java/android/telecom/AudioState.java
index 8b8c86b..ea641f8 100644
--- a/telecomm/java/android/telecom/AudioState.java
+++ b/telecomm/java/android/telecom/AudioState.java
@@ -19,7 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 86ad795..acf51f3 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -20,12 +20,11 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 
 import com.android.internal.telecom.IVideoProvider;
@@ -548,8 +547,14 @@
          */
         public static final int PROPERTY_VOIP_AUDIO_MODE = 0x00001000;
 
+        /**
+         * Indicates that the call is an adhoc conference call. This property can be set for both
+         * incoming and outgoing calls.
+         */
+        public static final int PROPERTY_IS_ADHOC_CONFERENCE = 0x00002000;
+
         //******************************************************************************************
-        // Next PROPERTY value: 0x00002000
+        // Next PROPERTY value: 0x00004000
         //******************************************************************************************
 
         private final String mTelecomCallId;
@@ -569,6 +574,7 @@
         private final Bundle mExtras;
         private final Bundle mIntentExtras;
         private final long mCreationTimeMillis;
+        private final String mContactDisplayName;
         private final @CallDirection int mCallDirection;
         private final @Connection.VerificationStatus int mCallerNumberVerificationStatus;
 
@@ -726,6 +732,9 @@
             if (hasProperty(properties, PROPERTY_VOIP_AUDIO_MODE)) {
                 builder.append(" PROPERTY_VOIP_AUDIO_MODE");
             }
+            if (hasProperty(properties, PROPERTY_IS_ADHOC_CONFERENCE)) {
+                builder.append(" PROPERTY_IS_ADHOC_CONFERENCE");
+            }
             builder.append("]");
             return builder.toString();
         }
@@ -873,6 +882,17 @@
         }
 
         /**
+         * Returns the name of the caller on the remote end, as derived from a
+         * {@link android.provider.ContactsContract} lookup of the call's handle.
+         * @return The name of the caller, or {@code null} if the lookup is not yet complete, if
+         *         there's no contacts entry for the caller, or if the {@link InCallService} does
+         *         not hold the {@link android.Manifest.permission#READ_CONTACTS} permission.
+         */
+        public @Nullable String getContactDisplayName() {
+            return mContactDisplayName;
+        }
+
+        /**
          * Indicates whether the call is an incoming or outgoing call.
          * @return The call's direction.
          */
@@ -910,6 +930,7 @@
                         areBundlesEqual(mExtras, d.mExtras) &&
                         areBundlesEqual(mIntentExtras, d.mIntentExtras) &&
                         Objects.equals(mCreationTimeMillis, d.mCreationTimeMillis) &&
+                        Objects.equals(mContactDisplayName, d.mContactDisplayName) &&
                         Objects.equals(mCallDirection, d.mCallDirection) &&
                         Objects.equals(mCallerNumberVerificationStatus,
                                 d.mCallerNumberVerificationStatus);
@@ -934,6 +955,7 @@
                             mExtras,
                             mIntentExtras,
                             mCreationTimeMillis,
+                            mContactDisplayName,
                             mCallDirection,
                             mCallerNumberVerificationStatus);
         }
@@ -956,6 +978,7 @@
                 Bundle extras,
                 Bundle intentExtras,
                 long creationTimeMillis,
+                String contactDisplayName,
                 int callDirection,
                 int callerNumberVerificationStatus) {
             mTelecomCallId = telecomCallId;
@@ -974,6 +997,7 @@
             mExtras = extras;
             mIntentExtras = intentExtras;
             mCreationTimeMillis = creationTimeMillis;
+            mContactDisplayName = contactDisplayName;
             mCallDirection = callDirection;
             mCallerNumberVerificationStatus = callerNumberVerificationStatus;
         }
@@ -997,6 +1021,7 @@
                     parcelableCall.getExtras(),
                     parcelableCall.getIntentExtras(),
                     parcelableCall.getCreationTimeMillis(),
+                    parcelableCall.getContactDisplayName(),
                     parcelableCall.getCallDirection(),
                     parcelableCall.getCallerNumberVerificationStatus());
         }
@@ -1446,6 +1471,7 @@
 
     private boolean mChildrenCached;
     private String mParentId = null;
+    private String mActiveGenericConferenceChild = null;
     private int mState;
     private List<String> mCannedTextResponses = null;
     private String mCallingPackage;
@@ -1944,6 +1970,20 @@
     }
 
     /**
+     * Returns the child {@link Call} in a generic conference that is currently active.
+     * For calls that are not generic conferences, or when the generic conference has more than
+     * 2 children, returns {@code null}.
+     * @see Details#PROPERTY_GENERIC_CONFERENCE
+     * @return The active child call.
+     */
+    public @Nullable Call getGenericConferenceActiveChildCall() {
+        if (mActiveGenericConferenceChild != null) {
+            return mPhone.internalGetCallByTelecomId(mActiveGenericConferenceChild);
+        }
+        return null;
+    }
+
+    /**
      * Obtains a list of canned, pre-configured message responses to present to the user as
      * ways of rejecting this {@code Call} using via a text message.
      *
@@ -2191,6 +2231,13 @@
             mChildrenCached = false;
         }
 
+        String activeChildCallId = parcelableCall.getActiveChildCallId();
+        boolean activeChildChanged = !Objects.equals(activeChildCallId,
+                mActiveGenericConferenceChild);
+        if (activeChildChanged) {
+            mActiveGenericConferenceChild = activeChildCallId;
+        }
+
         List<String> conferenceableCallIds = parcelableCall.getConferenceableCallIds();
         List<Call> conferenceableCalls = new ArrayList<Call>(conferenceableCallIds.size());
         for (String otherId : conferenceableCallIds) {
@@ -2250,7 +2297,7 @@
         if (parentChanged) {
             fireParentChanged(getParent());
         }
-        if (childrenChanged) {
+        if (childrenChanged || activeChildChanged) {
             fireChildrenChanged(getChildren());
         }
         if (isRttChanged) {
diff --git a/telecomm/java/android/telecom/CallerInfo.java b/telecomm/java/android/telecom/CallerInfo.java
index a5d25e2..fb6f994 100644
--- a/telecomm/java/android/telecom/CallerInfo.java
+++ b/telecomm/java/android/telecom/CallerInfo.java
@@ -17,7 +17,7 @@
 package android.telecom;
 
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -41,8 +41,8 @@
 import com.android.i18n.phonenumbers.PhoneNumberUtil;
 import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber;
 import com.android.i18n.phonenumbers.geocoding.PhoneNumberOfflineGeocoder;
-
 import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.Locale;
 
 
diff --git a/telecomm/java/android/telecom/CallerInfoAsyncQuery.java b/telecomm/java/android/telecom/CallerInfoAsyncQuery.java
index 4a50e98..4a81a8e 100644
--- a/telecomm/java/android/telecom/CallerInfoAsyncQuery.java
+++ b/telecomm/java/android/telecom/CallerInfoAsyncQuery.java
@@ -17,6 +17,7 @@
 package android.telecom;
 
 import android.app.ActivityManager;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.AsyncQueryHandler;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -35,8 +36,6 @@
 import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
 import java.util.ArrayList;
 import java.util.List;
 
diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java
index 456290c..6b0845f 100644
--- a/telecomm/java/android/telecom/Conference.java
+++ b/telecomm/java/android/telecom/Conference.java
@@ -69,6 +69,7 @@
         public void onConnectionEvent(Conference c, String event, Bundle extras) {}
         public void onCallerDisplayNameChanged(
                 Conference c, String callerDisplayName, int presentation) {}
+        public void onRingbackRequested(Conference c, boolean ringback) {}
     }
 
     private final Set<Listener> mListeners = new CopyOnWriteArraySet<>();
@@ -97,6 +98,7 @@
     private int mAddressPresentation;
     private String mCallerDisplayName;
     private int mCallerDisplayNamePresentation;
+    private boolean mRingbackRequested = false;
 
     private final Connection.Listener mConnectionDeathListener = new Connection.Listener() {
         @Override
@@ -170,6 +172,14 @@
     }
 
     /**
+     * Returns whether this conference is requesting that the system play a ringback tone
+     * on its behalf.
+     */
+    public final boolean isRingbackRequested() {
+        return mRingbackRequested;
+    }
+
+    /**
      * Returns the capabilities of the conference. See {@code CAPABILITY_*} constants in class
      * {@link Connection} for valid values.
      *
@@ -308,6 +318,35 @@
     public void onConnectionAdded(Connection connection) {}
 
     /**
+     * Notifies this Conference, which is in {@code STATE_RINGING}, of
+     * a request to accept.
+     * For managed {@link ConnectionService}s, this will be called when the user answers a call via
+     * the default dialer's {@link InCallService}.
+     *
+     * @param videoState The video state in which to answer the connection.
+     */
+    public void onAnswer(int videoState) {}
+
+    /**
+     * Notifies this Conference, which is in {@code STATE_RINGING}, of
+     * a request to accept.
+     * For managed {@link ConnectionService}s, this will be called when the user answers a call via
+     * the default dialer's {@link InCallService}.
+     * @hide
+     */
+    public final void onAnswer() {
+         onAnswer(VideoProfile.STATE_AUDIO_ONLY);
+    }
+
+    /**
+     * Notifies this Conference, which is in {@code STATE_RINGING}, of
+     * a request to reject.
+     * For managed {@link ConnectionService}s, this will be called when the user rejects a call via
+     * the default dialer's {@link InCallService}.
+     */
+    public void onReject() {}
+
+    /**
      * Sets state to be on hold.
      */
     public final void setOnHold() {
@@ -322,9 +361,17 @@
     }
 
     /**
+     * Sets state to be ringing.
+     */
+    public final void setRinging() {
+        setState(Connection.STATE_RINGING);
+    }
+
+    /**
      * Sets state to be active.
      */
     public final void setActive() {
+        setRingbackRequested(false);
         setState(Connection.STATE_ACTIVE);
     }
 
@@ -436,6 +483,21 @@
     }
 
     /**
+     * Requests that the framework play a ringback tone. This is to be invoked by implementations
+     * that do not play a ringback tone themselves in the conference's audio stream.
+     *
+     * @param ringback Whether the ringback tone is to be played.
+     */
+    public final void setRingbackRequested(boolean ringback) {
+        if (mRingbackRequested != ringback) {
+            mRingbackRequested = ringback;
+            for (Listener l : mListeners) {
+                l.onRingbackRequested(this, ringback);
+            }
+        }
+    }
+
+    /**
      * Set the video state for the conference.
      * Valid values: {@link VideoProfile#STATE_AUDIO_ONLY},
      * {@link VideoProfile#STATE_BIDIRECTIONAL},
@@ -640,14 +702,6 @@
     }
 
     private void setState(int newState) {
-        if (newState != Connection.STATE_ACTIVE &&
-                newState != Connection.STATE_HOLDING &&
-                newState != Connection.STATE_DISCONNECTED) {
-            Log.w(this, "Unsupported state transition for Conference call.",
-                    Connection.stateToString(newState));
-            return;
-        }
-
         if (mState != newState) {
             int oldState = mState;
             mState = newState;
@@ -657,6 +711,37 @@
         }
     }
 
+    private static class FailureSignalingConference extends Conference {
+        private boolean mImmutable = false;
+        public FailureSignalingConference(DisconnectCause disconnectCause,
+                PhoneAccountHandle phoneAccount) {
+            super(phoneAccount);
+            setDisconnected(disconnectCause);
+            mImmutable = true;
+        }
+        public void checkImmutable() {
+            if (mImmutable) {
+                throw new UnsupportedOperationException("Conference is immutable");
+            }
+        }
+    }
+
+    /**
+     * Return a {@code Conference} which represents a failed conference attempt. The returned
+     * {@code Conference} will have a {@link android.telecom.DisconnectCause} and as specified,
+     * and a {@link #getState()} of {@code STATE_DISCONNECTED}.
+     * <p>
+     * The returned {@code Conference} can be assumed to {@link #destroy()} itself when appropriate,
+     * so users of this method need not maintain a reference to its return value to destroy it.
+     *
+     * @param disconnectCause The disconnect cause, ({@see android.telecomm.DisconnectCause}).
+     * @return A {@code Conference} which indicates failure.
+     */
+    public @NonNull static Conference createFailedConference(
+            @NonNull DisconnectCause disconnectCause, @NonNull PhoneAccountHandle phoneAccount) {
+        return new FailureSignalingConference(disconnectCause, phoneAccount);
+    }
+
     private final void clearConferenceableList() {
         for (Connection c : mConferenceableConnections) {
             c.removeConnectionListener(mConnectionDeathListener);
@@ -667,11 +752,13 @@
     @Override
     public String toString() {
         return String.format(Locale.US,
-                "[State: %s,Capabilites: %s, VideoState: %s, VideoProvider: %s, ThisObject %s]",
+                "[State: %s,Capabilites: %s, VideoState: %s, VideoProvider: %s,"
+                + "isRingbackRequested: %s, ThisObject %s]",
                 Connection.stateToString(mState),
                 Call.Details.capabilitiesToString(mConnectionCapabilities),
                 getVideoState(),
                 getVideoProvider(),
+                isRingbackRequested() ? "Y" : "N",
                 super.toString());
     }
 
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 8808339..c934625 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -21,9 +21,9 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
 import android.app.Notification;
 import android.bluetooth.BluetoothDevice;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Intent;
 import android.hardware.camera2.CameraManager;
 import android.net.Uri;
@@ -497,8 +497,17 @@
     @TestApi
     public static final int PROPERTY_REMOTELY_HOSTED = 1 << 11;
 
+    /**
+     * Set by the framework to indicate that it is an adhoc conference call.
+     * <p>
+     * This is used for Outgoing and incoming conference calls.
+     *
+     */
+    public static final int PROPERTY_IS_ADHOC_CONFERENCE = 1 << 12;
+
+
     //**********************************************************************************************
-    // Next PROPERTY value: 1<<12
+    // Next PROPERTY value: 1<<13
     //**********************************************************************************************
 
     /**
@@ -1018,6 +1027,10 @@
             builder.append(isLong ? " PROPERTY_REMOTELY_HOSTED" : " remote_hst");
         }
 
+        if ((properties & PROPERTY_IS_ADHOC_CONFERENCE) == PROPERTY_IS_ADHOC_CONFERENCE) {
+            builder.append(isLong ? " PROPERTY_IS_ADHOC_CONFERENCE" : " adhoc_conf");
+        }
+
         builder.append("]");
         return builder.toString();
     }
diff --git a/telecomm/java/android/telecom/ConnectionRequest.java b/telecomm/java/android/telecom/ConnectionRequest.java
index 221f8f1..6d7ceca 100644
--- a/telecomm/java/android/telecom/ConnectionRequest.java
+++ b/telecomm/java/android/telecom/ConnectionRequest.java
@@ -26,6 +26,9 @@
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Simple data container encapsulating a request to some entity to
  * create a new {@link Connection}.
@@ -46,6 +49,8 @@
         private boolean mShouldShowIncomingCallUi = false;
         private ParcelFileDescriptor mRttPipeToInCall;
         private ParcelFileDescriptor mRttPipeFromInCall;
+        private List<Uri> mParticipants;
+        private boolean mIsAdhocConference = false;
 
         public Builder() { }
 
@@ -59,6 +64,15 @@
         }
 
         /**
+         * Sets the participants for the resulting {@link ConnectionRequest}
+         * @param participants The participants to which the {@link Connection} is to connect.
+         */
+        public @NonNull Builder setParticipants(@Nullable List<Uri> participants) {
+            this.mParticipants = participants;
+            return this;
+        }
+
+        /**
          * Sets the address for the resulting {@link ConnectionRequest}
          * @param address The address(e.g., phone number) to which the {@link Connection} is to
          *                connect.
@@ -108,6 +122,16 @@
         }
 
         /**
+         * Sets isAdhocConference for the resulting {@link ConnectionRequest}
+         * @param isAdhocConference {@code true} if it is a adhoc conference call
+         *                          {@code false}, if not a adhoc conference call
+         */
+        public @NonNull Builder setIsAdhocConferenceCall(boolean isAdhocConference) {
+            this.mIsAdhocConference = isAdhocConference;
+            return this;
+        }
+
+        /**
          * Sets the RTT pipe for transferring text into the {@link ConnectionService} for the
          * resulting {@link ConnectionRequest}
          * @param rttPipeFromInCall The data pipe to read from.
@@ -141,7 +165,9 @@
                     mTelecomCallId,
                     mShouldShowIncomingCallUi,
                     mRttPipeFromInCall,
-                    mRttPipeToInCall);
+                    mRttPipeToInCall,
+                    mParticipants,
+                    mIsAdhocConference);
         }
     }
 
@@ -155,6 +181,8 @@
     private final ParcelFileDescriptor mRttPipeFromInCall;
     // Cached return value of getRttTextStream -- we don't want to wrap it more than once.
     private Connection.RttTextStream mRttTextStream;
+    private List<Uri> mParticipants;
+    private final boolean mIsAdhocConference;
 
     /**
      * @param accountHandle The accountHandle which should be used to place the call.
@@ -214,6 +242,21 @@
             boolean shouldShowIncomingCallUi,
             ParcelFileDescriptor rttPipeFromInCall,
             ParcelFileDescriptor rttPipeToInCall) {
+        this(accountHandle, handle, extras, videoState, telecomCallId,
+                shouldShowIncomingCallUi, rttPipeFromInCall, rttPipeToInCall, null, false);
+    }
+
+    private ConnectionRequest(
+            PhoneAccountHandle accountHandle,
+            Uri handle,
+            Bundle extras,
+            int videoState,
+            String telecomCallId,
+            boolean shouldShowIncomingCallUi,
+            ParcelFileDescriptor rttPipeFromInCall,
+            ParcelFileDescriptor rttPipeToInCall,
+            List<Uri> participants,
+            boolean isAdhocConference) {
         mAccountHandle = accountHandle;
         mAddress = handle;
         mExtras = extras;
@@ -222,6 +265,8 @@
         mShouldShowIncomingCallUi = shouldShowIncomingCallUi;
         mRttPipeFromInCall = rttPipeFromInCall;
         mRttPipeToInCall = rttPipeToInCall;
+        mParticipants = participants;
+        mIsAdhocConference = isAdhocConference;
     }
 
     private ConnectionRequest(Parcel in) {
@@ -233,6 +278,11 @@
         mShouldShowIncomingCallUi = in.readInt() == 1;
         mRttPipeFromInCall = in.readParcelable(getClass().getClassLoader());
         mRttPipeToInCall = in.readParcelable(getClass().getClassLoader());
+
+        mParticipants = new ArrayList<Uri>();
+        in.readList(mParticipants, getClass().getClassLoader());
+
+        mIsAdhocConference = in.readInt() == 1;
     }
 
     /**
@@ -246,6 +296,11 @@
     public Uri getAddress() { return mAddress; }
 
     /**
+     * The participants to which the {@link Connection} is to connect.
+     */
+    public @Nullable List<Uri> getParticipants() { return mParticipants; }
+
+    /**
      * Application-specific extra data. Used for passing back information from an incoming
      * call {@code Intent}, and for any proprietary extensions arranged between a client
      * and servant {@code ConnectionService} which agree on a vocabulary for such data.
@@ -290,6 +345,13 @@
     }
 
     /**
+     * @return {@code true} if the call is a adhoc conference call else @return {@code false}
+     */
+    public boolean isAdhocConferenceCall() {
+        return mIsAdhocConference;
+    }
+
+    /**
      * Gets the {@link ParcelFileDescriptor} that is used to send RTT text from the connection
      * service to the in-call UI. In order to obtain an
      * {@link java.io.InputStream} from this {@link ParcelFileDescriptor}, use
@@ -345,11 +407,12 @@
 
     @Override
     public String toString() {
-        return String.format("ConnectionRequest %s %s",
+        return String.format("ConnectionRequest %s %s isAdhocConf: %s",
                 mAddress == null
                         ? Uri.EMPTY
                         : Connection.toLogSafePhoneNumber(mAddress.toString()),
-                bundleToString(mExtras));
+                bundleToString(mExtras),
+                isAdhocConferenceCall() ? "Y" : "N");
     }
 
     private static String bundleToString(Bundle extras){
@@ -406,5 +469,7 @@
         destination.writeInt(mShouldShowIncomingCallUi ? 1 : 0);
         destination.writeParcelable(mRttPipeFromInCall, 0);
         destination.writeParcelable(mRttPipeToInCall, 0);
+        destination.writeList(mParticipants);
+        destination.writeInt(mIsAdhocConference ? 1 : 0);
     }
 }
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 3a0494e..440f044 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -154,6 +154,9 @@
     private static final String SESSION_CONNECTION_SERVICE_FOCUS_LOST = "CS.cSFL";
     private static final String SESSION_CONNECTION_SERVICE_FOCUS_GAINED = "CS.cSFG";
     private static final String SESSION_HANDOVER_FAILED = "CS.haF";
+    private static final String SESSION_CREATE_CONF = "CS.crConf";
+    private static final String SESSION_CREATE_CONF_COMPLETE = "CS.crConfC";
+    private static final String SESSION_CREATE_CONF_FAILED = "CS.crConfF";
 
     private static final int MSG_ADD_CONNECTION_SERVICE_ADAPTER = 1;
     private static final int MSG_CREATE_CONNECTION = 2;
@@ -188,6 +191,9 @@
     private static final int MSG_HANDOVER_FAILED = 32;
     private static final int MSG_HANDOVER_COMPLETE = 33;
     private static final int MSG_DEFLECT = 34;
+    private static final int MSG_CREATE_CONFERENCE = 35;
+    private static final int MSG_CREATE_CONFERENCE_COMPLETE = 36;
+    private static final int MSG_CREATE_CONFERENCE_FAILED = 37;
 
     private static Connection sNullConnection;
 
@@ -291,6 +297,63 @@
         }
 
         @Override
+        public void createConference(
+                PhoneAccountHandle connectionManagerPhoneAccount,
+                String id,
+                ConnectionRequest request,
+                boolean isIncoming,
+                boolean isUnknown,
+                Session.Info sessionInfo) {
+            Log.startSession(sessionInfo, SESSION_CREATE_CONF);
+            try {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = connectionManagerPhoneAccount;
+                args.arg2 = id;
+                args.arg3 = request;
+                args.arg4 = Log.createSubsession();
+                args.argi1 = isIncoming ? 1 : 0;
+                args.argi2 = isUnknown ? 1 : 0;
+                mHandler.obtainMessage(MSG_CREATE_CONFERENCE, args).sendToTarget();
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
+        public void createConferenceComplete(String id, Session.Info sessionInfo) {
+            Log.startSession(sessionInfo, SESSION_CREATE_CONF_COMPLETE);
+            try {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = id;
+                args.arg2 = Log.createSubsession();
+                mHandler.obtainMessage(MSG_CREATE_CONFERENCE_COMPLETE, args).sendToTarget();
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
+        public void createConferenceFailed(
+                PhoneAccountHandle connectionManagerPhoneAccount,
+                String callId,
+                ConnectionRequest request,
+                boolean isIncoming,
+                Session.Info sessionInfo) {
+            Log.startSession(sessionInfo, SESSION_CREATE_CONF_FAILED);
+            try {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = callId;
+                args.arg2 = request;
+                args.arg3 = Log.createSubsession();
+                args.arg4 = connectionManagerPhoneAccount;
+                args.argi1 = isIncoming ? 1 : 0;
+                mHandler.obtainMessage(MSG_CREATE_CONFERENCE_FAILED, args).sendToTarget();
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
         public void handoverFailed(String callId, ConnectionRequest request, int reason,
                                    Session.Info sessionInfo) {
             Log.startSession(sessionInfo, SESSION_HANDOVER_FAILED);
@@ -802,6 +865,106 @@
                     }
                     break;
                 }
+                case MSG_CREATE_CONFERENCE: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    Log.continueSession((Session) args.arg4, SESSION_HANDLER + SESSION_CREATE_CONN);
+                    try {
+                        final PhoneAccountHandle connectionManagerPhoneAccount =
+                                (PhoneAccountHandle) args.arg1;
+                        final String id = (String) args.arg2;
+                        final ConnectionRequest request = (ConnectionRequest) args.arg3;
+                        final boolean isIncoming = args.argi1 == 1;
+                        final boolean isUnknown = args.argi2 == 1;
+                        if (!mAreAccountsInitialized) {
+                            Log.d(this, "Enqueueing pre-initconference request %s", id);
+                            mPreInitializationConnectionRequests.add(
+                                    new android.telecom.Logging.Runnable(
+                                            SESSION_HANDLER + SESSION_CREATE_CONF + ".pIConfR",
+                                            null /*lock*/) {
+                                @Override
+                                public void loggedRun() {
+                                    createConference(connectionManagerPhoneAccount,
+                                            id,
+                                            request,
+                                            isIncoming,
+                                            isUnknown);
+                                }
+                            }.prepare());
+                        } else {
+                            createConference(connectionManagerPhoneAccount,
+                                    id,
+                                    request,
+                                    isIncoming,
+                                    isUnknown);
+                        }
+                    } finally {
+                        args.recycle();
+                        Log.endSession();
+                    }
+                    break;
+                }
+                case MSG_CREATE_CONFERENCE_COMPLETE: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    Log.continueSession((Session) args.arg2,
+                            SESSION_HANDLER + SESSION_CREATE_CONN_COMPLETE);
+                    try {
+                        final String id = (String) args.arg1;
+                        if (!mAreAccountsInitialized) {
+                            Log.d(this, "Enqueueing pre-init conference request %s", id);
+                            mPreInitializationConnectionRequests.add(
+                                    new android.telecom.Logging.Runnable(
+                                            SESSION_HANDLER + SESSION_CREATE_CONF_COMPLETE
+                                                    + ".pIConfR",
+                                            null /*lock*/) {
+                                        @Override
+                                        public void loggedRun() {
+                                            notifyCreateConferenceComplete(id);
+                                        }
+                                    }.prepare());
+                        } else {
+                            notifyCreateConferenceComplete(id);
+                        }
+                    } finally {
+                        args.recycle();
+                        Log.endSession();
+                    }
+                    break;
+                }
+                case MSG_CREATE_CONFERENCE_FAILED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    Log.continueSession((Session) args.arg3, SESSION_HANDLER +
+                            SESSION_CREATE_CONN_FAILED);
+                    try {
+                        final String id = (String) args.arg1;
+                        final ConnectionRequest request = (ConnectionRequest) args.arg2;
+                        final boolean isIncoming = args.argi1 == 1;
+                        final PhoneAccountHandle connectionMgrPhoneAccount =
+                                (PhoneAccountHandle) args.arg4;
+                        if (!mAreAccountsInitialized) {
+                            Log.d(this, "Enqueueing pre-init conference request %s", id);
+                            mPreInitializationConnectionRequests.add(
+                                    new android.telecom.Logging.Runnable(
+                                            SESSION_HANDLER + SESSION_CREATE_CONF_FAILED
+                                                    + ".pIConfR",
+                                            null /*lock*/) {
+                                        @Override
+                                        public void loggedRun() {
+                                            createConferenceFailed(connectionMgrPhoneAccount, id,
+                                                    request, isIncoming);
+                                        }
+                                    }.prepare());
+                        } else {
+                            Log.i(this, "createConferenceFailed %s", id);
+                            createConferenceFailed(connectionMgrPhoneAccount, id, request,
+                                    isIncoming);
+                        }
+                    } finally {
+                        args.recycle();
+                        Log.endSession();
+                    }
+                    break;
+                }
+
                 case MSG_HANDOVER_FAILED: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     Log.continueSession((Session) args.arg3, SESSION_HANDLER +
@@ -1162,6 +1325,12 @@
         public void onStateChanged(Conference conference, int oldState, int newState) {
             String id = mIdByConference.get(conference);
             switch (newState) {
+                case Connection.STATE_RINGING:
+                    mAdapter.setRinging(id);
+                    break;
+                case Connection.STATE_DIALING:
+                    mAdapter.setDialing(id);
+                    break;
                 case Connection.STATE_ACTIVE:
                     mAdapter.setActive(id);
                     break;
@@ -1292,6 +1461,13 @@
                 mAdapter.onConnectionEvent(id, event, extras);
             }
         }
+
+        @Override
+        public void onRingbackRequested(Conference c, boolean ringback) {
+            String id = mIdByConference.get(c);
+            Log.d(this, "Adapter conference onRingback %b", ringback);
+            mAdapter.setRingbackRequested(id, ringback);
+        }
     };
 
     private final Connection.Listener mConnectionListener = new Connection.Listener() {
@@ -1534,6 +1710,70 @@
         return super.onUnbind(intent);
     }
 
+
+    /**
+     * This can be used by telecom to either create a new outgoing conference call or attach
+     * to an existing incoming conference call. In either case, telecom will cycle through a
+     * set of services and call createConference until a connection service cancels the process
+     * or completes it successfully.
+     */
+    private void createConference(
+            final PhoneAccountHandle callManagerAccount,
+            final String callId,
+            final ConnectionRequest request,
+            boolean isIncoming,
+            boolean isUnknown) {
+
+        Conference conference = null;
+        conference = isIncoming ? onCreateIncomingConference(callManagerAccount, request)
+                    : onCreateOutgoingConference(callManagerAccount, request);
+
+        Log.d(this, "createConference, conference: %s", conference);
+        if (conference == null) {
+            Log.i(this, "createConference, implementation returned null conference.");
+            conference = Conference.createFailedConference(
+                    new DisconnectCause(DisconnectCause.ERROR, "IMPL_RETURNED_NULL_CONFERENCE"),
+                    request.getAccountHandle());
+        }
+        if (conference.getExtras() != null) {
+            conference.getExtras().putString(Connection.EXTRA_ORIGINAL_CONNECTION_ID, callId);
+        }
+        mConferenceById.put(callId, conference);
+        mIdByConference.put(conference, callId);
+        conference.addListener(mConferenceListener);
+        ParcelableConference parcelableConference = new ParcelableConference(
+                request.getAccountHandle(),
+                conference.getState(),
+                conference.getConnectionCapabilities(),
+                conference.getConnectionProperties(),
+                Collections.<String>emptyList(), //connectionIds
+                conference.getVideoProvider() == null ?
+                        null : conference.getVideoProvider().getInterface(),
+                conference.getVideoState(),
+                conference.getConnectTimeMillis(),
+                conference.getConnectionStartElapsedRealTime(),
+                conference.getStatusHints(),
+                conference.getExtras(),
+                conference.getAddress(),
+                conference.getAddressPresentation(),
+                conference.getCallerDisplayName(),
+                conference.getCallerDisplayNamePresentation(),
+                conference.getDisconnectCause(),
+                conference.isRingbackRequested());
+        if (conference.getState() != Connection.STATE_DISCONNECTED) {
+            conference.setTelecomCallId(callId);
+            mAdapter.setVideoProvider(callId, conference.getVideoProvider());
+            mAdapter.setVideoState(callId, conference.getVideoState());
+            onConferenceAdded(conference);
+        }
+
+        Log.d(this, "createConference, calling handleCreateConferenceSuccessful %s", callId);
+        mAdapter.handleCreateConferenceComplete(
+                callId,
+                request,
+                parcelableConference);
+    }
+
     /**
      * This can be used by telecom to either create a new outgoing call or attach to an existing
      * incoming call. In either case, telecom will cycle through a set of services and call
@@ -1645,6 +1885,18 @@
         }
     }
 
+    private void createConferenceFailed(final PhoneAccountHandle callManagerAccount,
+                                        final String callId, final ConnectionRequest request,
+                                        boolean isIncoming) {
+
+        Log.i(this, "createConferenceFailed %s", callId);
+        if (isIncoming) {
+            onCreateIncomingConferenceFailed(callManagerAccount, request);
+        } else {
+            onCreateOutgoingConferenceFailed(callManagerAccount, request);
+        }
+    }
+
     private void handoverFailed(final String callId, final ConnectionRequest request,
                                         int reason) {
 
@@ -1669,6 +1921,24 @@
                 "notifyCreateConnectionComplete"));
     }
 
+    /**
+     * Called by Telecom when the creation of a new Conference has completed and it is now added
+     * to Telecom.
+     * @param callId The ID of the connection.
+     */
+    private void notifyCreateConferenceComplete(final String callId) {
+        Log.i(this, "notifyCreateConferenceComplete %s", callId);
+        if (callId == null) {
+            // This could happen if the conference fails quickly and is removed from the
+            // ConnectionService before Telecom sends the create conference complete callback.
+            Log.w(this, "notifyCreateConferenceComplete: callId is null.");
+            return;
+        }
+        onCreateConferenceComplete(findConferenceForAction(callId,
+                "notifyCreateConferenceComplete"));
+    }
+
+
     private void abort(String callId) {
         Log.d(this, "abort %s", callId);
         findConnectionForAction(callId, "abort").onAbort();
@@ -1676,12 +1946,20 @@
 
     private void answerVideo(String callId, int videoState) {
         Log.d(this, "answerVideo %s", callId);
-        findConnectionForAction(callId, "answer").onAnswer(videoState);
+        if (mConnectionById.containsKey(callId)) {
+            findConnectionForAction(callId, "answer").onAnswer(videoState);
+        } else {
+            findConferenceForAction(callId, "answer").onAnswer(videoState);
+        }
     }
 
     private void answer(String callId) {
         Log.d(this, "answer %s", callId);
-        findConnectionForAction(callId, "answer").onAnswer();
+        if (mConnectionById.containsKey(callId)) {
+            findConnectionForAction(callId, "answer").onAnswer();
+        } else {
+            findConferenceForAction(callId, "answer").onAnswer();
+        }
     }
 
     private void deflect(String callId, Uri address) {
@@ -1691,7 +1969,11 @@
 
     private void reject(String callId) {
         Log.d(this, "reject %s", callId);
-        findConnectionForAction(callId, "reject").onReject();
+        if (mConnectionById.containsKey(callId)) {
+            findConnectionForAction(callId, "reject").onReject();
+        } else {
+            findConferenceForAction(callId, "reject").onReject();
+        }
     }
 
     private void reject(String callId, String rejectWithMessage) {
@@ -2198,6 +2480,21 @@
             ConnectionRequest request) {
         return null;
     }
+    /**
+     * Create a {@code Connection} given an incoming request. This is used to attach to existing
+     * incoming conference call.
+     *
+     * @param connectionManagerPhoneAccount See description at
+     *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
+     * @param request Details about the incoming call.
+     * @return The {@code Connection} object to satisfy this call, or {@code null} to
+     *         not handle the call.
+     */
+    public @Nullable Conference onCreateIncomingConference(
+            @Nullable PhoneAccountHandle connectionManagerPhoneAccount,
+            @Nullable ConnectionRequest request) {
+        return null;
+    }
 
     /**
      * Called after the {@link Connection} returned by
@@ -2212,6 +2509,19 @@
     }
 
     /**
+     * Called after the {@link Conference} returned by
+     * {@link #onCreateIncomingConference(PhoneAccountHandle, ConnectionRequest)}
+     * or {@link #onCreateOutgoingConference(PhoneAccountHandle, ConnectionRequest)} has been
+     * added to the {@link ConnectionService} and sent to Telecom.
+     *
+     * @param conference the {@link Conference}.
+     * @hide
+     */
+    public void onCreateConferenceComplete(Conference conference) {
+    }
+
+
+    /**
      * Called by Telecom to inform the {@link ConnectionService} that its request to create a new
      * incoming {@link Connection} was denied.
      * <p>
@@ -2250,6 +2560,47 @@
     }
 
     /**
+     * Called by Telecom to inform the {@link ConnectionService} that its request to create a new
+     * incoming {@link Conference} was denied.
+     * <p>
+     * Used when a self-managed {@link ConnectionService} attempts to create a new incoming
+     * {@link Conference}, but Telecom has determined that the call cannot be allowed at this time.
+     * The {@link ConnectionService} is responsible for silently rejecting the new incoming
+     * {@link Conference}.
+     * <p>
+     * See {@link TelecomManager#isIncomingCallPermitted(PhoneAccountHandle)} for more information.
+     *
+     * @param connectionManagerPhoneAccount See description at
+     *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
+     * @param request The incoming connection request.
+     */
+    public void onCreateIncomingConferenceFailed(
+            @Nullable PhoneAccountHandle connectionManagerPhoneAccount,
+            @Nullable ConnectionRequest request) {
+    }
+
+    /**
+     * Called by Telecom to inform the {@link ConnectionService} that its request to create a new
+     * outgoing {@link Conference} was denied.
+     * <p>
+     * Used when a self-managed {@link ConnectionService} attempts to create a new outgoing
+     * {@link Conference}, but Telecom has determined that the call cannot be placed at this time.
+     * The {@link ConnectionService} is responisible for informing the user that the
+     * {@link Conference} cannot be made at this time.
+     * <p>
+     * See {@link TelecomManager#isOutgoingCallPermitted(PhoneAccountHandle)} for more information.
+     *
+     * @param connectionManagerPhoneAccount See description at
+     *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
+     * @param request The outgoing connection request.
+     */
+    public void onCreateOutgoingConferenceFailed(
+            @Nullable PhoneAccountHandle connectionManagerPhoneAccount,
+            @Nullable ConnectionRequest request) {
+    }
+
+
+    /**
      * Trigger recalculate functinality for conference calls. This is used when a Telephony
      * Connection is part of a conference controller but is not yet added to Connection
      * Service and hence cannot be added to the conference call.
@@ -2289,6 +2640,36 @@
     }
 
     /**
+     * Create a {@code Conference} given an outgoing request. This is used to initiate new
+     * outgoing conference call.
+     *
+     * @param connectionManagerPhoneAccount The connection manager account to use for managing
+     *         this call.
+     *         <p>
+     *         If this parameter is not {@code null}, it means that this {@code ConnectionService}
+     *         has registered one or more {@code PhoneAccount}s having
+     *         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}. This parameter will contain
+     *         one of these {@code PhoneAccount}s, while the {@code request} will contain another
+     *         (usually but not always distinct) {@code PhoneAccount} to be used for actually
+     *         making the connection.
+     *         <p>
+     *         If this parameter is {@code null}, it means that this {@code ConnectionService} is
+     *         being asked to make a direct connection. The
+     *         {@link ConnectionRequest#getAccountHandle()} of parameter {@code request} will be
+     *         a {@code PhoneAccount} registered by this {@code ConnectionService} to use for
+     *         making the connection.
+     * @param request Details about the outgoing call.
+     * @return The {@code Conference} object to satisfy this call, or the result of an invocation
+     *         of {@link Connection#createFailedConnection(DisconnectCause)} to not handle the call.
+     */
+    public @Nullable Conference onCreateOutgoingConference(
+            @Nullable PhoneAccountHandle connectionManagerPhoneAccount,
+            @Nullable ConnectionRequest request) {
+        return null;
+    }
+
+
+    /**
      * Called by Telecom to request that a {@link ConnectionService} creates an instance of an
      * outgoing handover {@link Connection}.
      * <p>
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapter.java b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
index 04e930c..8f27323 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapter.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
@@ -100,6 +100,19 @@
         }
     }
 
+    void handleCreateConferenceComplete(
+            String id,
+            ConnectionRequest request,
+            ParcelableConference conference) {
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.handleCreateConferenceComplete(id, request, conference,
+                        Log.getExternalSession());
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
     /**
      * Sets a call's state to active (e.g., an ongoing call where two parties can actively
      * communicate).
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
index 60b2172..79ad51b 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
@@ -75,6 +75,7 @@
     private static final int MSG_SET_PHONE_ACCOUNT_CHANGED = 34;
     private static final int MSG_CONNECTION_SERVICE_FOCUS_RELEASED = 35;
     private static final int MSG_SET_CONFERENCE_STATE = 36;
+    private static final int MSG_HANDLE_CREATE_CONFERENCE_COMPLETE = 37;
 
     private final IConnectionServiceAdapter mDelegate;
 
@@ -103,6 +104,19 @@
                     }
                     break;
                 }
+                case MSG_HANDLE_CREATE_CONFERENCE_COMPLETE: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        mDelegate.handleCreateConferenceComplete(
+                                (String) args.arg1,
+                                (ConnectionRequest) args.arg2,
+                                (ParcelableConference) args.arg3,
+                                null /*Session.Info*/);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
                 case MSG_SET_ACTIVE:
                     mDelegate.setActive((String) msg.obj, null /*Session.Info*/);
                     break;
@@ -366,6 +380,20 @@
         }
 
         @Override
+        public void handleCreateConferenceComplete(
+                String id,
+                ConnectionRequest request,
+                ParcelableConference conference,
+                Session.Info sessionInfo) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = id;
+            args.arg2 = request;
+            args.arg3 = conference;
+            mHandler.obtainMessage(MSG_HANDLE_CREATE_CONFERENCE_COMPLETE, args).sendToTarget();
+        }
+
+
+        @Override
         public void setActive(String connectionId, Session.Info sessionInfo) {
             mHandler.obtainMessage(MSG_SET_ACTIVE, connectionId).sendToTarget();
         }
diff --git a/telecomm/java/android/telecom/Log.java b/telecomm/java/android/telecom/Log.java
index 7d4ee76..4f6a9d6 100644
--- a/telecomm/java/android/telecom/Log.java
+++ b/telecomm/java/android/telecom/Log.java
@@ -16,7 +16,7 @@
 
 package android.telecom;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.net.Uri;
 import android.os.Build;
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index a234bb0..415a817 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -16,27 +16,286 @@
 
 package android.telecom;
 
-import android.annotation.UnsupportedAppUsage;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.telecom.Call.Details.CallDirection;
 
+import com.android.internal.telecom.IVideoProvider;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
-import com.android.internal.telecom.IVideoProvider;
-
 /**
  * Information about a call that is used between InCallService and Telecom.
  * @hide
  */
 public final class ParcelableCall implements Parcelable {
+
+    public static class ParcelableCallBuilder {
+        private String mId;
+        private int mState;
+        private DisconnectCause mDisconnectCause;
+        private List<String> mCannedSmsResponses;
+        private int mCapabilities;
+        private int mProperties;
+        private int mSupportedAudioRoutes;
+        private long mConnectTimeMillis;
+        private Uri mHandle;
+        private int mHandlePresentation;
+        private String mCallerDisplayName;
+        private int mCallerDisplayNamePresentation;
+        private GatewayInfo mGatewayInfo;
+        private PhoneAccountHandle mAccountHandle;
+        private boolean mIsVideoCallProviderChanged;
+        private IVideoProvider mVideoCallProvider;
+        private boolean mIsRttCallChanged;
+        private ParcelableRttCall mRttCall;
+        private String mParentCallId;
+        private List<String> mChildCallIds;
+        private StatusHints mStatusHints;
+        private int mVideoState;
+        private List<String> mConferenceableCallIds;
+        private Bundle mIntentExtras;
+        private Bundle mExtras;
+        private long mCreationTimeMillis;
+        private int mCallDirection;
+        private int mCallerNumberVerificationStatus;
+        private String mContactDisplayName;
+        private String mActiveChildCallId;
+
+        public ParcelableCallBuilder setId(String id) {
+            mId = id;
+            return this;
+        }
+
+        public ParcelableCallBuilder setState(int state) {
+            mState = state;
+            return this;
+        }
+
+        public ParcelableCallBuilder setDisconnectCause(DisconnectCause disconnectCause) {
+            mDisconnectCause = disconnectCause;
+            return this;
+        }
+
+        public ParcelableCallBuilder setCannedSmsResponses(List<String> cannedSmsResponses) {
+            mCannedSmsResponses = cannedSmsResponses;
+            return this;
+        }
+
+        public ParcelableCallBuilder setCapabilities(int capabilities) {
+            mCapabilities = capabilities;
+            return this;
+        }
+
+        public ParcelableCallBuilder setProperties(int properties) {
+            mProperties = properties;
+            return this;
+        }
+
+        public ParcelableCallBuilder setSupportedAudioRoutes(int supportedAudioRoutes) {
+            mSupportedAudioRoutes = supportedAudioRoutes;
+            return this;
+        }
+
+        public ParcelableCallBuilder setConnectTimeMillis(long connectTimeMillis) {
+            mConnectTimeMillis = connectTimeMillis;
+            return this;
+        }
+
+        public ParcelableCallBuilder setHandle(Uri handle) {
+            mHandle = handle;
+            return this;
+        }
+
+        public ParcelableCallBuilder setHandlePresentation(int handlePresentation) {
+            mHandlePresentation = handlePresentation;
+            return this;
+        }
+
+        public ParcelableCallBuilder setCallerDisplayName(String callerDisplayName) {
+            mCallerDisplayName = callerDisplayName;
+            return this;
+        }
+
+        public ParcelableCallBuilder setCallerDisplayNamePresentation(
+                int callerDisplayNamePresentation) {
+            mCallerDisplayNamePresentation = callerDisplayNamePresentation;
+            return this;
+        }
+
+        public ParcelableCallBuilder setGatewayInfo(GatewayInfo gatewayInfo) {
+            mGatewayInfo = gatewayInfo;
+            return this;
+        }
+
+        public ParcelableCallBuilder setAccountHandle(PhoneAccountHandle accountHandle) {
+            mAccountHandle = accountHandle;
+            return this;
+        }
+
+        public ParcelableCallBuilder setIsVideoCallProviderChanged(
+                boolean isVideoCallProviderChanged) {
+            mIsVideoCallProviderChanged = isVideoCallProviderChanged;
+            return this;
+        }
+
+        public ParcelableCallBuilder setVideoCallProvider(IVideoProvider videoCallProvider) {
+            mVideoCallProvider = videoCallProvider;
+            return this;
+        }
+
+        public ParcelableCallBuilder setIsRttCallChanged(boolean isRttCallChanged) {
+            mIsRttCallChanged = isRttCallChanged;
+            return this;
+        }
+
+        public ParcelableCallBuilder setRttCall(ParcelableRttCall rttCall) {
+            mRttCall = rttCall;
+            return this;
+        }
+
+        public ParcelableCallBuilder setParentCallId(String parentCallId) {
+            mParentCallId = parentCallId;
+            return this;
+        }
+
+        public ParcelableCallBuilder setChildCallIds(List<String> childCallIds) {
+            mChildCallIds = childCallIds;
+            return this;
+        }
+
+        public ParcelableCallBuilder setStatusHints(StatusHints statusHints) {
+            mStatusHints = statusHints;
+            return this;
+        }
+
+        public ParcelableCallBuilder setVideoState(int videoState) {
+            mVideoState = videoState;
+            return this;
+        }
+
+        public ParcelableCallBuilder setConferenceableCallIds(
+                List<String> conferenceableCallIds) {
+            mConferenceableCallIds = conferenceableCallIds;
+            return this;
+        }
+
+        public ParcelableCallBuilder setIntentExtras(Bundle intentExtras) {
+            mIntentExtras = intentExtras;
+            return this;
+        }
+
+        public ParcelableCallBuilder setExtras(Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        public ParcelableCallBuilder setCreationTimeMillis(long creationTimeMillis) {
+            mCreationTimeMillis = creationTimeMillis;
+            return this;
+        }
+
+        public ParcelableCallBuilder setCallDirection(int callDirection) {
+            mCallDirection = callDirection;
+            return this;
+        }
+
+        public ParcelableCallBuilder setCallerNumberVerificationStatus(
+                int callerNumberVerificationStatus) {
+            mCallerNumberVerificationStatus = callerNumberVerificationStatus;
+            return this;
+        }
+
+        public ParcelableCallBuilder setContactDisplayName(String contactDisplayName) {
+            mContactDisplayName = contactDisplayName;
+            return this;
+        }
+
+        public ParcelableCallBuilder setActiveChildCallId(String activeChildCallId) {
+            mActiveChildCallId = activeChildCallId;
+            return this;
+        }
+
+        public ParcelableCall createParcelableCall() {
+            return new ParcelableCall(
+                    mId,
+                    mState,
+                    mDisconnectCause,
+                    mCannedSmsResponses,
+                    mCapabilities,
+                    mProperties,
+                    mSupportedAudioRoutes,
+                    mConnectTimeMillis,
+                    mHandle,
+                    mHandlePresentation,
+                    mCallerDisplayName,
+                    mCallerDisplayNamePresentation,
+                    mGatewayInfo,
+                    mAccountHandle,
+                    mIsVideoCallProviderChanged,
+                    mVideoCallProvider,
+                    mIsRttCallChanged,
+                    mRttCall,
+                    mParentCallId,
+                    mChildCallIds,
+                    mStatusHints,
+                    mVideoState,
+                    mConferenceableCallIds,
+                    mIntentExtras,
+                    mExtras,
+                    mCreationTimeMillis,
+                    mCallDirection,
+                    mCallerNumberVerificationStatus,
+                    mContactDisplayName,
+                    mActiveChildCallId);
+        }
+
+        public static ParcelableCallBuilder fromParcelableCall(ParcelableCall parcelableCall) {
+            ParcelableCallBuilder newBuilder = new ParcelableCallBuilder();
+            newBuilder.mId = parcelableCall.mId;
+            newBuilder.mState = parcelableCall.mState;
+            newBuilder.mDisconnectCause = parcelableCall.mDisconnectCause;
+            newBuilder.mCannedSmsResponses = parcelableCall.mCannedSmsResponses;
+            newBuilder.mCapabilities = parcelableCall.mCapabilities;
+            newBuilder.mProperties = parcelableCall.mProperties;
+            newBuilder.mSupportedAudioRoutes = parcelableCall.mSupportedAudioRoutes;
+            newBuilder.mConnectTimeMillis = parcelableCall.mConnectTimeMillis;
+            newBuilder.mHandle = parcelableCall.mHandle;
+            newBuilder.mHandlePresentation = parcelableCall.mHandlePresentation;
+            newBuilder.mCallerDisplayName = parcelableCall.mCallerDisplayName;
+            newBuilder.mCallerDisplayNamePresentation =
+                    parcelableCall.mCallerDisplayNamePresentation;
+            newBuilder.mGatewayInfo = parcelableCall.mGatewayInfo;
+            newBuilder.mAccountHandle = parcelableCall.mAccountHandle;
+            newBuilder.mIsVideoCallProviderChanged = parcelableCall.mIsVideoCallProviderChanged;
+            newBuilder.mVideoCallProvider = parcelableCall.mVideoCallProvider;
+            newBuilder.mIsRttCallChanged = parcelableCall.mIsRttCallChanged;
+            newBuilder.mRttCall = parcelableCall.mRttCall;
+            newBuilder.mParentCallId = parcelableCall.mParentCallId;
+            newBuilder.mChildCallIds = parcelableCall.mChildCallIds;
+            newBuilder.mStatusHints = parcelableCall.mStatusHints;
+            newBuilder.mVideoState = parcelableCall.mVideoState;
+            newBuilder.mConferenceableCallIds = parcelableCall.mConferenceableCallIds;
+            newBuilder.mIntentExtras = parcelableCall.mIntentExtras;
+            newBuilder.mExtras = parcelableCall.mExtras;
+            newBuilder.mCreationTimeMillis = parcelableCall.mCreationTimeMillis;
+            newBuilder.mCallDirection = parcelableCall.mCallDirection;
+            newBuilder.mCallerNumberVerificationStatus =
+                    parcelableCall.mCallerNumberVerificationStatus;
+            newBuilder.mContactDisplayName = parcelableCall.mContactDisplayName;
+            newBuilder.mActiveChildCallId = parcelableCall.mActiveChildCallId;
+            return newBuilder;
+        }
+    }
+
     private final String mId;
     private final int mState;
     private final DisconnectCause mDisconnectCause;
@@ -66,6 +325,8 @@
     private final long mCreationTimeMillis;
     private final int mCallDirection;
     private final int mCallerNumberVerificationStatus;
+    private final String mContactDisplayName;
+    private final String mActiveChildCallId; // Only valid for CDMA conferences
 
     public ParcelableCall(
             String id,
@@ -95,7 +356,10 @@
             Bundle extras,
             long creationTimeMillis,
             int callDirection,
-            int callerNumberVerificationStatus) {
+            int callerNumberVerificationStatus,
+            String contactDisplayName,
+            String activeChildCallId
+    ) {
         mId = id;
         mState = state;
         mDisconnectCause = disconnectCause;
@@ -124,6 +388,8 @@
         mCreationTimeMillis = creationTimeMillis;
         mCallDirection = callDirection;
         mCallerNumberVerificationStatus = callerNumberVerificationStatus;
+        mContactDisplayName = contactDisplayName;
+        mActiveChildCallId = activeChildCallId;
     }
 
     /** The unique ID of the call. */
@@ -333,6 +599,21 @@
         return mCallerNumberVerificationStatus;
     }
 
+    /**
+     * @return the name of the remote party as derived from a contacts DB lookup.
+     */
+    public @Nullable String getContactDisplayName() {
+        return mContactDisplayName;
+    }
+
+    /**
+     * @return On a CDMA conference with two participants, returns the ID of the child call that's
+     *         currently active.
+     */
+    public @Nullable String getActiveChildCallId() {
+        return mActiveChildCallId;
+    }
+
     /** Responsible for creating ParcelableCall objects for deserialized Parcels. */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public static final @android.annotation.NonNull Parcelable.Creator<ParcelableCall> CREATOR =
@@ -372,35 +653,40 @@
             long creationTimeMillis = source.readLong();
             int callDirection = source.readInt();
             int callerNumberVerificationStatus = source.readInt();
-            return new ParcelableCall(
-                    id,
-                    state,
-                    disconnectCause,
-                    cannedSmsResponses,
-                    capabilities,
-                    properties,
-                    supportedAudioRoutes,
-                    connectTimeMillis,
-                    handle,
-                    handlePresentation,
-                    callerDisplayName,
-                    callerDisplayNamePresentation,
-                    gatewayInfo,
-                    accountHandle,
-                    isVideoCallProviderChanged,
-                    videoCallProvider,
-                    isRttCallChanged,
-                    rttCall,
-                    parentCallId,
-                    childCallIds,
-                    statusHints,
-                    videoState,
-                    conferenceableCallIds,
-                    intentExtras,
-                    extras,
-                    creationTimeMillis,
-                    callDirection,
-                    callerNumberVerificationStatus);
+            String contactDisplayName = source.readString();
+            String activeChildCallId = source.readString();
+            return new ParcelableCallBuilder()
+                    .setId(id)
+                    .setState(state)
+                    .setDisconnectCause(disconnectCause)
+                    .setCannedSmsResponses(cannedSmsResponses)
+                    .setCapabilities(capabilities)
+                    .setProperties(properties)
+                    .setSupportedAudioRoutes(supportedAudioRoutes)
+                    .setConnectTimeMillis(connectTimeMillis)
+                    .setHandle(handle)
+                    .setHandlePresentation(handlePresentation)
+                    .setCallerDisplayName(callerDisplayName)
+                    .setCallerDisplayNamePresentation(callerDisplayNamePresentation)
+                    .setGatewayInfo(gatewayInfo)
+                    .setAccountHandle(accountHandle)
+                    .setIsVideoCallProviderChanged(isVideoCallProviderChanged)
+                    .setVideoCallProvider(videoCallProvider)
+                    .setIsRttCallChanged(isRttCallChanged)
+                    .setRttCall(rttCall)
+                    .setParentCallId(parentCallId)
+                    .setChildCallIds(childCallIds)
+                    .setStatusHints(statusHints)
+                    .setVideoState(videoState)
+                    .setConferenceableCallIds(conferenceableCallIds)
+                    .setIntentExtras(intentExtras)
+                    .setExtras(extras)
+                    .setCreationTimeMillis(creationTimeMillis)
+                    .setCallDirection(callDirection)
+                    .setCallerNumberVerificationStatus(callerNumberVerificationStatus)
+                    .setContactDisplayName(contactDisplayName)
+                    .setActiveChildCallId(activeChildCallId)
+                    .createParcelableCall();
         }
 
         @Override
@@ -447,6 +733,8 @@
         destination.writeLong(mCreationTimeMillis);
         destination.writeInt(mCallDirection);
         destination.writeInt(mCallerNumberVerificationStatus);
+        destination.writeString(mContactDisplayName);
+        destination.writeString(mActiveChildCallId);
     }
 
     @Override
diff --git a/telecomm/java/android/telecom/ParcelableConference.java b/telecomm/java/android/telecom/ParcelableConference.java
index ede0594..90b69a3 100644
--- a/telecomm/java/android/telecom/ParcelableConference.java
+++ b/telecomm/java/android/telecom/ParcelableConference.java
@@ -47,6 +47,34 @@
     private final int mAddressPresentation;
     private final String mCallerDisplayName;
     private final int mCallerDisplayNamePresentation;
+    private DisconnectCause mDisconnectCause;
+    private boolean mRingbackRequested;
+
+    public ParcelableConference(
+            PhoneAccountHandle phoneAccount,
+            int state,
+            int connectionCapabilities,
+            int connectionProperties,
+            List<String> connectionIds,
+            IVideoProvider videoProvider,
+            int videoState,
+            long connectTimeMillis,
+            long connectElapsedTimeMillis,
+            StatusHints statusHints,
+            Bundle extras,
+            Uri address,
+            int addressPresentation,
+            String callerDisplayName,
+            int callerDisplayNamePresentation,
+            DisconnectCause disconnectCause,
+            boolean ringbackRequested) {
+        this(phoneAccount, state, connectionCapabilities, connectionProperties, connectionIds,
+                videoProvider, videoState, connectTimeMillis, connectElapsedTimeMillis,
+                statusHints, extras, address, addressPresentation, callerDisplayName,
+                callerDisplayNamePresentation);
+        mDisconnectCause = disconnectCause;
+        mRingbackRequested = ringbackRequested;
+    }
 
     public ParcelableConference(
             PhoneAccountHandle phoneAccount,
@@ -79,6 +107,8 @@
         mAddressPresentation = addressPresentation;
         mCallerDisplayName = callerDisplayName;
         mCallerDisplayNamePresentation = callerDisplayNamePresentation;
+        mDisconnectCause = null;
+        mRingbackRequested = false;
     }
 
     @Override
@@ -100,6 +130,10 @@
                 .append(mVideoState)
                 .append(", VideoProvider: ")
                 .append(mVideoProvider)
+                .append(", isRingbackRequested: ")
+                .append(mRingbackRequested)
+                .append(", disconnectCause: ")
+                .append(mDisconnectCause)
                 .toString();
     }
 
@@ -151,6 +185,13 @@
         return mAddress;
     }
 
+    public final DisconnectCause getDisconnectCause() {
+        return mDisconnectCause;
+    }
+
+    public boolean isRingbackRequested() {
+        return mRingbackRequested;
+    }
     public int getHandlePresentation() {
         return mAddressPresentation;
     }
@@ -177,11 +218,14 @@
             int addressPresentation = source.readInt();
             String callerDisplayName = source.readString();
             int callerDisplayNamePresentation = source.readInt();
+            DisconnectCause disconnectCause = source.readParcelable(classLoader);
+            boolean isRingbackRequested = source.readInt() == 1;
 
             return new ParcelableConference(phoneAccount, state, capabilities, properties,
                     connectionIds, videoCallProvider, videoState, connectTimeMillis,
                     connectElapsedTimeMillis, statusHints, extras, address, addressPresentation,
-                    callerDisplayName, callerDisplayNamePresentation);
+                    callerDisplayName, callerDisplayNamePresentation, disconnectCause,
+                    isRingbackRequested);
         }
 
         @Override
@@ -215,5 +259,7 @@
         destination.writeInt(mAddressPresentation);
         destination.writeString(mCallerDisplayName);
         destination.writeInt(mCallerDisplayNamePresentation);
+        destination.writeParcelable(mDisconnectCause, 0);
+        destination.writeInt(mRingbackRequested ? 1 : 0);
     }
 }
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index 61a639a1..a427ed61 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -17,8 +17,8 @@
 package android.telecom;
 
 import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
 import android.bluetooth.BluetoothDevice;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Bundle;
 import android.util.ArrayMap;
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index bb858cb..abb210f 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -331,7 +331,17 @@
      */
     public static final int CAPABILITY_EMERGENCY_PREFERRED = 0x2000;
 
-    /* NEXT CAPABILITY: 0x4000 */
+    /**
+     * An adhoc conference call is established by providing a list of addresses to
+     * {@code TelecomManager#startConference(List<Uri>, int videoState)} where the
+     * {@link ConnectionService} is responsible for connecting all indicated participants
+     * to a conference simultaneously.
+     * This is in contrast to conferences formed by merging calls together (e.g. using
+     * {@link android.telecom.Call#mergeConference()}).
+     */
+    public static final int CAPABILITY_ADHOC_CONFERENCE_CALLING = 0x4000;
+
+    /* NEXT CAPABILITY: 0x8000 */
 
     /**
      * URI scheme for telephone number URIs.
@@ -1054,6 +1064,9 @@
         if (hasCapabilities(CAPABILITY_RTT)) {
             sb.append("Rtt");
         }
+        if (hasCapabilities(CAPABILITY_ADHOC_CONFERENCE_CALLING)) {
+            sb.append("AdhocConf");
+        }
         return sb.toString();
     }
 
diff --git a/telecomm/java/android/telecom/PhoneAccountHandle.java b/telecomm/java/android/telecom/PhoneAccountHandle.java
index eb568e0..e1bcb5f 100644
--- a/telecomm/java/android/telecom/PhoneAccountHandle.java
+++ b/telecomm/java/android/telecom/PhoneAccountHandle.java
@@ -18,7 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.os.Build;
 import android.os.Parcel;
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index 1e73bd6..76640e0 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -101,6 +101,14 @@
         }
 
         @Override
+        public void handleCreateConferenceComplete(
+                String id,
+                ConnectionRequest request,
+                ParcelableConference parcel,
+                Session.Info info) {
+        }
+
+        @Override
         public void setActive(String callId, Session.Info sessionInfo) {
             if (mConnectionById.containsKey(callId)) {
                 findConnectionForAction(callId, "setActive")
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index af3c55a..49b74c6 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -26,7 +26,7 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -49,6 +49,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
@@ -848,6 +849,17 @@
      */
     public static final int PRESENTATION_PAYPHONE = 4;
 
+
+    /*
+     * Values for the adb property "persist.radio.videocall.audio.output"
+     */
+    /** @hide */
+    public static final int AUDIO_OUTPUT_ENABLE_SPEAKER = 0;
+    /** @hide */
+    public static final int AUDIO_OUTPUT_DISABLE_SPEAKER = 1;
+    /** @hide */
+    public static final int AUDIO_OUTPUT_DEFAULT = AUDIO_OUTPUT_ENABLE_SPEAKER;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(
@@ -882,7 +894,8 @@
      */
     public TelecomManager(Context context, ITelecomService telecomServiceImpl) {
         Context appContext = context.getApplicationContext();
-        if (appContext != null) {
+        if (appContext != null && Objects.equals(context.getFeatureId(),
+                appContext.getFeatureId())) {
             mContext = appContext;
         } else {
             mContext = context;
@@ -916,7 +929,7 @@
         try {
             if (isServiceConnected()) {
                 return getTelecomService().getDefaultOutgoingPhoneAccount(uriScheme,
-                        mContext.getOpPackageName());
+                        mContext.getOpPackageName(), mContext.getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling ITelecomService#getDefaultOutgoingPhoneAccount", e);
@@ -1113,7 +1126,8 @@
     public List<PhoneAccountHandle> getSelfManagedPhoneAccounts() {
         try {
             if (isServiceConnected()) {
-                return getTelecomService().getSelfManagedPhoneAccounts(mContext.getOpPackageName());
+                return getTelecomService().getSelfManagedPhoneAccounts(mContext.getOpPackageName(),
+                        mContext.getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling ITelecomService#getSelfManagedPhoneAccounts()", e);
@@ -1138,8 +1152,8 @@
             boolean includeDisabledAccounts) {
         try {
             if (isServiceConnected()) {
-                return getTelecomService().getCallCapablePhoneAccounts(
-                        includeDisabledAccounts, mContext.getOpPackageName());
+                return getTelecomService().getCallCapablePhoneAccounts(includeDisabledAccounts,
+                        mContext.getOpPackageName(), mContext.getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling ITelecomService#getCallCapablePhoneAccounts(" +
@@ -1442,7 +1456,7 @@
         try {
             if (isServiceConnected()) {
                 return getTelecomService().isVoiceMailNumber(accountHandle, number,
-                        mContext.getOpPackageName());
+                        mContext.getOpPackageName(), mContext.getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException calling ITelecomService#isVoiceMailNumber.", e);
@@ -1464,7 +1478,7 @@
         try {
             if (isServiceConnected()) {
                 return getTelecomService().getVoiceMailNumber(accountHandle,
-                        mContext.getOpPackageName());
+                        mContext.getOpPackageName(), mContext.getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException calling ITelecomService#hasVoiceMailNumber.", e);
@@ -1485,7 +1499,7 @@
         try {
             if (isServiceConnected()) {
                 return getTelecomService().getLine1Number(accountHandle,
-                        mContext.getOpPackageName());
+                        mContext.getOpPackageName(), mContext.getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException calling ITelecomService#getLine1Number.", e);
@@ -1506,7 +1520,8 @@
     public boolean isInCall() {
         try {
             if (isServiceConnected()) {
-                return getTelecomService().isInCall(mContext.getOpPackageName());
+                return getTelecomService().isInCall(mContext.getOpPackageName(),
+                        mContext.getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException calling isInCall().", e);
@@ -1531,7 +1546,8 @@
     public boolean isInManagedCall() {
         try {
             if (isServiceConnected()) {
-                return getTelecomService().isInManagedCall(mContext.getOpPackageName());
+                return getTelecomService().isInManagedCall(mContext.getOpPackageName(),
+                        mContext.getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException calling isInManagedCall().", e);
@@ -1711,7 +1727,8 @@
     public boolean isTtySupported() {
         try {
             if (isServiceConnected()) {
-                return getTelecomService().isTtySupported(mContext.getOpPackageName());
+                return getTelecomService().isTtySupported(mContext.getOpPackageName(),
+                        mContext.getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException attempting to get TTY supported state.", e);
@@ -1735,7 +1752,8 @@
     public @TtyMode int getCurrentTtyMode() {
         try {
             if (isServiceConnected()) {
-                return getTelecomService().getCurrentTtyMode(mContext.getOpPackageName());
+                return getTelecomService().getCurrentTtyMode(mContext.getOpPackageName(),
+                        mContext.getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException attempting to get the current TTY mode.", e);
@@ -1792,6 +1810,45 @@
     }
 
     /**
+     * Registers a new incoming conference. A {@link ConnectionService} should invoke this method
+     * when it has an incoming conference. For managed {@link ConnectionService}s, the specified
+     * {@link PhoneAccountHandle} must have been registered with {@link #registerPhoneAccount} and
+     * the user must have enabled the corresponding {@link PhoneAccount}.  This can be checked using
+     * {@link #getPhoneAccount}. Self-managed {@link ConnectionService}s must have
+     * {@link android.Manifest.permission#MANAGE_OWN_CALLS} to add a new incoming call.
+     * <p>
+     * The incoming conference you are adding is assumed to have a video state of
+     * {@link VideoProfile#STATE_AUDIO_ONLY}, unless the extra value
+     * {@link #EXTRA_INCOMING_VIDEO_STATE} is specified.
+     * <p>
+     * Once invoked, this method will cause the system to bind to the {@link ConnectionService}
+     * associated with the {@link PhoneAccountHandle} and request additional information about the
+     * call (See {@link ConnectionService#onCreateIncomingConference}) before starting the incoming
+     * call UI.
+     * <p>
+     * For a managed {@link ConnectionService}, a {@link SecurityException} will be thrown if either
+     * the {@link PhoneAccountHandle} does not correspond to a registered {@link PhoneAccount} or
+     * the associated {@link PhoneAccount} is not currently enabled by the user.
+     *
+     * @param phoneAccount A {@link PhoneAccountHandle} registered with
+     *            {@link #registerPhoneAccount}.
+     * @param extras A bundle that will be passed through to
+     *            {@link ConnectionService#onCreateIncomingConference}.
+     */
+
+    public void addNewIncomingConference(@NonNull PhoneAccountHandle phoneAccount,
+            @NonNull Bundle extras) {
+        try {
+            if (isServiceConnected()) {
+                getTelecomService().addNewIncomingConference(
+                        phoneAccount, extras == null ? new Bundle() : extras);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException adding a new incoming conference: " + phoneAccount, e);
+        }
+    }
+
+    /**
      * Registers a new unknown call with Telecom. This can only be called by the system Telephony
      * service. This is invoked when Telephony detects a new unknown connection that was neither
      * a new incoming call, nor an user-initiated outgoing call.
@@ -1925,7 +1982,8 @@
         ITelecomService service = getTelecomService();
         if (service != null) {
             try {
-                service.showInCallScreen(showDialpad, mContext.getOpPackageName());
+                service.showInCallScreen(showDialpad, mContext.getOpPackageName(),
+                        mContext.getFeatureId());
             } catch (RemoteException e) {
                 Log.e(TAG, "Error calling ITelecomService#showCallScreen", e);
             }
@@ -1988,6 +2046,42 @@
             }
             try {
                 service.placeCall(address, extras == null ? new Bundle() : extras,
+                        mContext.getOpPackageName(), mContext.getFeatureId());
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling ITelecomService#placeCall", e);
+            }
+        }
+    }
+
+
+    /**
+     * Place a new conference call with the provided participants using the system telecom service
+     * This method doesn't support placing of emergency calls.
+     *
+     * An adhoc conference call is established by providing a list of addresses to
+     * {@code TelecomManager#startConference(List<Uri>, int videoState)} where the
+     * {@link ConnectionService} is responsible for connecting all indicated participants
+     * to a conference simultaneously.
+     * This is in contrast to conferences formed by merging calls together (e.g. using
+     * {@link android.telecom.Call#mergeConference()}).
+     *
+     * The following keys are supported in the supplied extras.
+     * <ul>
+     *   <li>{@link #EXTRA_PHONE_ACCOUNT_HANDLE}</li>
+     *   <li>{@link #EXTRA_START_CALL_WITH_SPEAKERPHONE}</li>
+     *   <li>{@link #EXTRA_START_CALL_WITH_VIDEO_STATE}</li>
+     * </ul>
+     *
+     * @param participants List of participants to start conference with
+     * @param extras Bundle of extras to use with the call
+     */
+    @RequiresPermission(android.Manifest.permission.CALL_PHONE)
+    public void startConference(@NonNull List<Uri> participants,
+            @NonNull Bundle extras) {
+        ITelecomService service = getTelecomService();
+        if (service != null) {
+            try {
+                service.startConference(participants, extras,
                         mContext.getOpPackageName());
             } catch (RemoteException e) {
                 Log.e(TAG, "Error calling ITelecomService#placeCall", e);
diff --git a/telecomm/java/android/telecom/VideoCallImpl.java b/telecomm/java/android/telecom/VideoCallImpl.java
index 4a1aa0a..109e7f8 100644
--- a/telecomm/java/android/telecom/VideoCallImpl.java
+++ b/telecomm/java/android/telecom/VideoCallImpl.java
@@ -16,7 +16,7 @@
 
 package android.telecom;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Handler;
diff --git a/telecomm/java/android/telecom/VideoProfile.java b/telecomm/java/android/telecom/VideoProfile.java
index 64e6ca3..4197f3c 100644
--- a/telecomm/java/android/telecom/VideoProfile.java
+++ b/telecomm/java/android/telecom/VideoProfile.java
@@ -19,7 +19,6 @@
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
-import android.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
 
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index e35093c..96f2483 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -53,6 +53,20 @@
     void createConnectionFailed(in PhoneAccountHandle connectionManagerPhoneAccount, String callId,
             in ConnectionRequest request, boolean isIncoming, in Session.Info sessionInfo);
 
+    void createConference(
+            in PhoneAccountHandle connectionManagerPhoneAccount,
+            String callId,
+            in ConnectionRequest request,
+            boolean isIncoming,
+            boolean isUnknown,
+            in Session.Info sessionInfo);
+
+    void createConferenceComplete(String callId, in Session.Info sessionInfo);
+
+    void createConferenceFailed(in PhoneAccountHandle connectionManagerPhoneAccount, String callId,
+            in ConnectionRequest request, boolean isIncoming, in Session.Info sessionInfo);
+
+
     void abort(String callId, in Session.Info sessionInfo);
 
     void answerVideo(String callId, int videoState, in Session.Info sessionInfo);
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
index 9cf098c..4f63e08 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
@@ -44,6 +44,12 @@
             in ParcelableConnection connection,
             in Session.Info sessionInfo);
 
+    void handleCreateConferenceComplete(
+            String callId,
+            in ConnectionRequest request,
+            in ParcelableConference connection,
+            in Session.Info sessionInfo);
+
     void setActive(String callId, in Session.Info sessionInfo);
 
     void setRinging(String callId, in Session.Info sessionInfo);
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 204c37e..285cf43 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -35,12 +35,13 @@
      *
      * @param showDialpad if true, make the dialpad visible initially.
      */
-    void showInCallScreen(boolean showDialpad, String callingPackage);
+    void showInCallScreen(boolean showDialpad, String callingPackage, String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#getDefaultOutgoingPhoneAccount
      */
-    PhoneAccountHandle getDefaultOutgoingPhoneAccount(in String uriScheme, String callingPackage);
+    PhoneAccountHandle getDefaultOutgoingPhoneAccount(in String uriScheme, String callingPackage,
+            String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#getUserSelectedOutgoingPhoneAccount
@@ -56,12 +57,13 @@
      * @see TelecomServiceImpl#getCallCapablePhoneAccounts
      */
     List<PhoneAccountHandle> getCallCapablePhoneAccounts(
-            boolean includeDisabledAccounts, String callingPackage);
+            boolean includeDisabledAccounts, String callingPackage, String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#getSelfManagedPhoneAccounts
      */
-    List<PhoneAccountHandle> getSelfManagedPhoneAccounts(String callingPackage);
+    List<PhoneAccountHandle> getSelfManagedPhoneAccounts(String callingPackage,
+            String callingFeatureId);
 
     /**
      * @see TelecomManager#getPhoneAccountsSupportingScheme
@@ -123,17 +125,19 @@
      * @see TelecomServiceImpl#isVoiceMailNumber
      */
     boolean isVoiceMailNumber(in PhoneAccountHandle accountHandle, String number,
-            String callingPackage);
+            String callingPackage, String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#getVoiceMailNumber
      */
-    String getVoiceMailNumber(in PhoneAccountHandle accountHandle, String callingPackage);
+    String getVoiceMailNumber(in PhoneAccountHandle accountHandle, String callingPackage,
+            String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#getLine1Number
      */
-    String getLine1Number(in PhoneAccountHandle accountHandle, String callingPackage);
+    String getLine1Number(in PhoneAccountHandle accountHandle, String callingPackage,
+            String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#getDefaultPhoneApp
@@ -172,12 +176,12 @@
     /**
      * @see TelecomServiceImpl#isInCall
      */
-    boolean isInCall(String callingPackage);
+    boolean isInCall(String callingPackage, String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#isInManagedCall
      */
-    boolean isInManagedCall(String callingPackage);
+    boolean isInManagedCall(String callingPackage, String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#isRinging
@@ -229,12 +233,12 @@
     /**
      * @see TelecomServiceImpl#isTtySupported
      */
-    boolean isTtySupported(String callingPackage);
+    boolean isTtySupported(String callingPackage, String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#getCurrentTtyMode
      */
-    int getCurrentTtyMode(String callingPackage);
+    int getCurrentTtyMode(String callingPackage, String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#addNewIncomingCall
@@ -242,14 +246,25 @@
     void addNewIncomingCall(in PhoneAccountHandle phoneAccount, in Bundle extras);
 
     /**
+     * @see TelecomServiceImpl#addNewIncomingConference
+     */
+    void addNewIncomingConference(in PhoneAccountHandle phoneAccount, in Bundle extras);
+
+    /**
      * @see TelecomServiceImpl#addNewUnknownCall
      */
     void addNewUnknownCall(in PhoneAccountHandle phoneAccount, in Bundle extras);
 
     /**
+     * @see TelecomServiceImpl#startConference
+     */
+    void startConference(in List<Uri> participants, in Bundle extras,
+            String callingPackage);
+
+    /**
      * @see TelecomServiceImpl#placeCall
      */
-    void placeCall(in Uri handle, in Bundle extras, String callingPackage);
+    void placeCall(in Uri handle, in Bundle extras, String callingPackage, String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#enablePhoneAccount
diff --git a/telephony/TEST_MAPPING b/telephony/TEST_MAPPING
new file mode 100644
index 0000000..d585666
--- /dev/null
+++ b/telephony/TEST_MAPPING
@@ -0,0 +1,29 @@
+{
+  "presubmit": [
+    {
+      "name": "TeleServiceTests",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
+      "name": "TelecomUnitTests",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
+      "name": "TelephonyProviderTests",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
+  ]
+}
+
diff --git a/telephony/common/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java
index f39981f..f3e9de0 100644
--- a/telephony/common/android/telephony/LocationAccessPolicy.java
+++ b/telephony/common/android/telephony/LocationAccessPolicy.java
@@ -24,20 +24,16 @@
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
 import android.location.LocationManager;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Process;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.util.Log;
 import android.widget.Toast;
 
 import com.android.internal.telephony.util.TelephonyUtils;
 
-import java.util.List;
-
 /**
  * Helper for performing location access checks.
  * @hide
@@ -309,7 +305,7 @@
     }
 
     private static boolean checkSystemLocationAccess(@NonNull Context context, int uid, int pid) {
-        if (!isLocationModeEnabled(context, UserHandle.getUserId(uid))) {
+        if (!isLocationModeEnabled(context, UserHandle.getUserHandleForUid(uid).getIdentifier())) {
             if (DBG) Log.w(TAG, "Location disabled, failed, (" + uid + ")");
             return false;
         }
@@ -336,20 +332,17 @@
     private static boolean isCurrentProfile(@NonNull Context context, int uid) {
         long token = Binder.clearCallingIdentity();
         try {
-            final int currentUser = ActivityManager.getCurrentUser();
-            final int callingUserId = UserHandle.getUserId(uid);
-            if (callingUserId == currentUser) {
+            if (UserHandle.getUserHandleForUid(uid).getIdentifier()
+                    == ActivityManager.getCurrentUser()) {
                 return true;
-            } else {
-                List<UserInfo> userProfiles = context.getSystemService(
-                        UserManager.class).getProfiles(currentUser);
-                for (UserInfo user : userProfiles) {
-                    if (user.id == callingUserId) {
-                        return true;
-                    }
-                }
             }
-            return false;
+            ActivityManager activityManager = context.getSystemService(ActivityManager.class);
+            if (activityManager != null) {
+                return activityManager.isProfileForeground(
+                        UserHandle.getUserHandleForUid(ActivityManager.getCurrentUser()));
+            } else {
+                return false;
+            }
         } finally {
             Binder.restoreCallingIdentity(token);
         }
diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
index eb02ea6f..9bc534c 100644
--- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
+++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
@@ -18,17 +18,19 @@
 
 import android.annotation.Nullable;
 import android.content.ContentResolver;
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.permission.IPermissionManager;
 import android.provider.Settings;
-import android.telephony.Rlog;
 import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.Log;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
@@ -76,15 +78,16 @@
      */
     public static synchronized void disableCarrierAppsUntilPrivileged(String callingPackage,
             IPackageManager packageManager, IPermissionManager permissionManager,
-            TelephonyManager telephonyManager, ContentResolver contentResolver, int userId) {
+            TelephonyManager telephonyManager, int userId, Context context) {
         if (DEBUG) {
-            Rlog.d(TAG, "disableCarrierAppsUntilPrivileged");
+            Log.d(TAG, "disableCarrierAppsUntilPrivileged");
         }
         SystemConfig config = SystemConfig.getInstance();
         ArraySet<String> systemCarrierAppsDisabledUntilUsed =
                 config.getDisabledUntilUsedPreinstalledCarrierApps();
         ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed =
                 config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
+        ContentResolver contentResolver = getContentResolverForUser(context, userId);
         disableCarrierAppsUntilPrivileged(callingPackage, packageManager, permissionManager,
                 telephonyManager, contentResolver, userId, systemCarrierAppsDisabledUntilUsed,
                 systemCarrierAssociatedAppsDisabledUntilUsed);
@@ -102,10 +105,10 @@
      * Manager can kill it, and this can lead to crashes as the app is in an unexpected state.
      */
     public static synchronized void disableCarrierAppsUntilPrivileged(String callingPackage,
-            IPackageManager packageManager, IPermissionManager permissionManager,
-            ContentResolver contentResolver, int userId) {
+            IPackageManager packageManager, IPermissionManager permissionManager, int userId,
+            Context context) {
         if (DEBUG) {
-            Rlog.d(TAG, "disableCarrierAppsUntilPrivileged");
+            Log.d(TAG, "disableCarrierAppsUntilPrivileged");
         }
         SystemConfig config = SystemConfig.getInstance();
         ArraySet<String> systemCarrierAppsDisabledUntilUsed =
@@ -114,15 +117,23 @@
 
         ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed =
                 config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
+        ContentResolver contentResolver = getContentResolverForUser(context, userId);
         disableCarrierAppsUntilPrivileged(callingPackage, packageManager, permissionManager,
                 null /* telephonyManager */, contentResolver, userId,
                 systemCarrierAppsDisabledUntilUsed, systemCarrierAssociatedAppsDisabledUntilUsed);
     }
 
+    private static ContentResolver getContentResolverForUser(Context context, int userId) {
+        Context userContext = context.createContextAsUser(UserHandle.getUserHandleForUid(userId),
+                0);
+        return userContext.getContentResolver();
+    }
+
     /**
      * Disable carrier apps until they are privileged
      * Must be public b/c framework unit tests can't access package-private methods.
      */
+    // Must be public b/c framework unit tests can't access package-private methods.
     @VisibleForTesting
     public static void disableCarrierAppsUntilPrivileged(String callingPackage,
             IPackageManager packageManager, IPermissionManager permissionManager,
@@ -130,8 +141,8 @@
             ContentResolver contentResolver, int userId,
             ArraySet<String> systemCarrierAppsDisabledUntilUsed,
             ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed) {
-        List<ApplicationInfo> candidates = getDefaultCarrierAppCandidatesHelper(packageManager,
-                userId, systemCarrierAppsDisabledUntilUsed);
+        List<ApplicationInfo> candidates = getDefaultNotUpdatedCarrierAppCandidatesHelper(
+                packageManager, userId, systemCarrierAppsDisabledUntilUsed);
         if (candidates == null || candidates.isEmpty()) {
             return;
         }
@@ -142,9 +153,8 @@
                 systemCarrierAssociatedAppsDisabledUntilUsed);
 
         List<String> enabledCarrierPackages = new ArrayList<>();
-
-        boolean hasRunOnce = Settings.Secure.getIntForUser(
-                contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 0, userId) == 1;
+        boolean hasRunOnce = Settings.Secure.getInt(contentResolver,
+                Settings.Secure.CARRIER_APPS_HANDLED, 0) == 1;
 
         try {
             for (ApplicationInfo ai : candidates) {
@@ -168,16 +178,17 @@
                     }
                 }
 
+                int enabledSetting = packageManager.getApplicationEnabledSetting(packageName,
+                        userId);
                 if (hasPrivileges) {
                     // Only update enabled state for the app on /system. Once it has been
                     // updated we shouldn't touch it.
-                    if (!ai.isUpdatedSystemApp()
-                            && (ai.enabledSetting
+                    if (enabledSetting
                             == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
-                            || ai.enabledSetting
+                            || enabledSetting
                             == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
-                            || (ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0)) {
-                        Rlog.i(TAG, "Update state(" + packageName + "): ENABLED for user "
+                            || (ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
+                        Log.i(TAG, "Update state(" + packageName + "): ENABLED for user "
                                 + userId);
                         packageManager.setSystemAppInstallState(
                                 packageName,
@@ -194,13 +205,16 @@
                     // Also enable any associated apps for this carrier app.
                     if (associatedAppList != null) {
                         for (ApplicationInfo associatedApp : associatedAppList) {
-                            if (associatedApp.enabledSetting
+                            int associatedAppEnabledSetting =
+                                    packageManager.getApplicationEnabledSetting(
+                                    associatedApp.packageName, userId);
+                            if (associatedAppEnabledSetting
                                     == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
-                                    || associatedApp.enabledSetting
+                                    || associatedAppEnabledSetting
                                     == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
                                     || (associatedApp.flags
                                     & ApplicationInfo.FLAG_INSTALLED) == 0) {
-                                Rlog.i(TAG, "Update associated state(" + associatedApp.packageName
+                                Log.i(TAG, "Update associated state(" + associatedApp.packageName
                                         + "): ENABLED for user " + userId);
                                 packageManager.setSystemAppInstallState(
                                         associatedApp.packageName,
@@ -221,11 +235,10 @@
                 } else {  // No carrier privileges
                     // Only update enabled state for the app on /system. Once it has been
                     // updated we shouldn't touch it.
-                    if (!ai.isUpdatedSystemApp()
-                            && ai.enabledSetting
+                    if (enabledSetting
                             == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
                             && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0) {
-                        Rlog.i(TAG, "Update state(" + packageName
+                        Log.i(TAG, "Update state(" + packageName
                                 + "): DISABLED_UNTIL_USED for user " + userId);
                         packageManager.setSystemAppInstallState(
                                 packageName,
@@ -239,11 +252,14 @@
                     if (!hasRunOnce) {
                         if (associatedAppList != null) {
                             for (ApplicationInfo associatedApp : associatedAppList) {
-                                if (associatedApp.enabledSetting
+                                int associatedAppEnabledSetting =
+                                        packageManager.getApplicationEnabledSetting(
+                                        associatedApp.packageName, userId);
+                                if (associatedAppEnabledSetting
                                         == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
                                         && (associatedApp.flags
                                         & ApplicationInfo.FLAG_INSTALLED) != 0) {
-                                    Rlog.i(TAG,
+                                    Log.i(TAG,
                                             "Update associated state(" + associatedApp.packageName
                                                     + "): DISABLED_UNTIL_USED for user " + userId);
                                     packageManager.setSystemAppInstallState(
@@ -259,8 +275,7 @@
 
             // Mark the execution so we do not disable apps again.
             if (!hasRunOnce) {
-                Settings.Secure.putIntForUser(
-                        contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 1, userId);
+                Settings.Secure.putInt(contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 1);
             }
 
             if (!enabledCarrierPackages.isEmpty()) {
@@ -271,7 +286,7 @@
                 permissionManager.grantDefaultPermissionsToEnabledCarrierApps(packageNames, userId);
             }
         } catch (RemoteException e) {
-            Rlog.w(TAG, "Could not reach PackageManager", e);
+            Log.w(TAG, "Could not reach PackageManager", e);
         }
     }
 
@@ -351,6 +366,31 @@
         return apps;
     }
 
+    private static List<ApplicationInfo> getDefaultNotUpdatedCarrierAppCandidatesHelper(
+            IPackageManager packageManager,
+            int userId,
+            ArraySet<String> systemCarrierAppsDisabledUntilUsed) {
+        if (systemCarrierAppsDisabledUntilUsed == null) {
+            return null;
+        }
+
+        int size = systemCarrierAppsDisabledUntilUsed.size();
+        if (size == 0) {
+            return null;
+        }
+
+        List<ApplicationInfo> apps = new ArrayList<>(size);
+        for (int i = 0; i < size; i++) {
+            String packageName = systemCarrierAppsDisabledUntilUsed.valueAt(i);
+            ApplicationInfo ai =
+                    getApplicationInfoIfNotUpdatedSystemApp(packageManager, userId, packageName);
+            if (ai != null) {
+                apps.add(ai);
+            }
+        }
+        return apps;
+    }
+
     private static Map<String, List<ApplicationInfo>> getDefaultCarrierAssociatedAppsHelper(
             IPackageManager packageManager,
             int userId,
@@ -363,11 +403,11 @@
                     systemCarrierAssociatedAppsDisabledUntilUsed.valueAt(i);
             for (int j = 0; j < associatedAppPackages.size(); j++) {
                 ApplicationInfo ai =
-                        getApplicationInfoIfSystemApp(
+                        getApplicationInfoIfNotUpdatedSystemApp(
                                 packageManager, userId, associatedAppPackages.get(j));
                 // Only update enabled state for the app on /system. Once it has been updated we
                 // shouldn't touch it.
-                if (ai != null && !ai.isUpdatedSystemApp()) {
+                if (ai != null) {
                     List<ApplicationInfo> appList = associatedApps.get(carrierAppPackage);
                     if (appList == null) {
                         appList = new ArrayList<>();
@@ -381,6 +421,26 @@
     }
 
     @Nullable
+    private static ApplicationInfo getApplicationInfoIfNotUpdatedSystemApp(
+            IPackageManager packageManager,
+            int userId,
+            String packageName) {
+        try {
+            ApplicationInfo ai = packageManager.getApplicationInfo(packageName,
+                    PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+                            | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                            | PackageManager.MATCH_SYSTEM_ONLY
+                            | PackageManager.MATCH_FACTORY_ONLY, userId);
+            if (ai != null) {
+                return ai;
+            }
+        } catch (RemoteException e) {
+            Log.w(TAG, "Could not reach PackageManager", e);
+        }
+        return null;
+    }
+
+    @Nullable
     private static ApplicationInfo getApplicationInfoIfSystemApp(
             IPackageManager packageManager,
             int userId,
@@ -388,12 +448,13 @@
         try {
             ApplicationInfo ai = packageManager.getApplicationInfo(packageName,
                     PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                    | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS, userId);
-            if (ai != null && ai.isSystemApp()) {
+                    | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                    | PackageManager.MATCH_SYSTEM_ONLY, userId);
+            if (ai != null) {
                 return ai;
             }
         } catch (RemoteException e) {
-            Rlog.w(TAG, "Could not reach PackageManager", e);
+            Log.w(TAG, "Could not reach PackageManager", e);
         }
         return null;
     }
diff --git a/telephony/common/com/android/internal/telephony/GsmAlphabet.java b/telephony/common/com/android/internal/telephony/GsmAlphabet.java
index 5fb4e90..5c53f7e 100644
--- a/telephony/common/com/android/internal/telephony/GsmAlphabet.java
+++ b/telephony/common/com/android/internal/telephony/GsmAlphabet.java
@@ -16,10 +16,10 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.Resources;
 import android.os.Build;
-import android.telephony.Rlog;
+import android.util.Log;
 import android.text.TextUtils;
 import android.util.SparseIntArray;
 
@@ -498,11 +498,11 @@
         StringBuilder ret = new StringBuilder(lengthSeptets);
 
         if (languageTable < 0 || languageTable > sLanguageTables.length) {
-            Rlog.w(TAG, "unknown language table " + languageTable + ", using default");
+            Log.w(TAG, "unknown language table " + languageTable + ", using default");
             languageTable = 0;
         }
         if (shiftTable < 0 || shiftTable > sLanguageShiftTables.length) {
-            Rlog.w(TAG, "unknown single shift table " + shiftTable + ", using default");
+            Log.w(TAG, "unknown single shift table " + shiftTable + ", using default");
             shiftTable = 0;
         }
 
@@ -512,11 +512,11 @@
             String shiftTableToChar = sLanguageShiftTables[shiftTable];
 
             if (languageTableToChar.isEmpty()) {
-                Rlog.w(TAG, "no language table for code " + languageTable + ", using default");
+                Log.w(TAG, "no language table for code " + languageTable + ", using default");
                 languageTableToChar = sLanguageTables[0];
             }
             if (shiftTableToChar.isEmpty()) {
-                Rlog.w(TAG, "no single shift table for code " + shiftTable + ", using default");
+                Log.w(TAG, "no single shift table for code " + shiftTable + ", using default");
                 shiftTableToChar = sLanguageShiftTables[0];
             }
 
@@ -556,7 +556,7 @@
                 }
             }
         } catch (RuntimeException ex) {
-            Rlog.e(TAG, "Error GSM 7 bit packed: ", ex);
+            Log.e(TAG, "Error GSM 7 bit packed: ", ex);
             return null;
         }
 
@@ -813,7 +813,7 @@
         for (int i = 0; i < sz; i++) {
             char c = s.charAt(i);
             if (c == GSM_EXTENDED_ESCAPE) {
-                Rlog.w(TAG, "countGsmSeptets() string contains Escape character, skipping.");
+                Log.w(TAG, "countGsmSeptets() string contains Escape character, skipping.");
                 continue;
             }
             if (charToLanguageTable.get(c, -1) != -1) {
@@ -892,7 +892,7 @@
         for (int i = 0; i < sz && !lpcList.isEmpty(); i++) {
             char c = s.charAt(i);
             if (c == GSM_EXTENDED_ESCAPE) {
-                Rlog.w(TAG, "countGsmSeptets() string contains Escape character, ignoring!");
+                Log.w(TAG, "countGsmSeptets() string contains Escape character, ignoring!");
                 continue;
             }
             // iterate through enabled locking shift tables
@@ -1496,7 +1496,7 @@
         int numTables = sLanguageTables.length;
         int numShiftTables = sLanguageShiftTables.length;
         if (numTables != numShiftTables) {
-            Rlog.e(TAG, "Error: language tables array length " + numTables +
+            Log.e(TAG, "Error: language tables array length " + numTables +
                     " != shift tables array length " + numShiftTables);
         }
 
@@ -1506,7 +1506,7 @@
 
             int tableLen = table.length();
             if (tableLen != 0 && tableLen != 128) {
-                Rlog.e(TAG, "Error: language tables index " + i +
+                Log.e(TAG, "Error: language tables index " + i +
                         " length " + tableLen + " (expected 128 or 0)");
             }
 
@@ -1524,7 +1524,7 @@
 
             int shiftTableLen = shiftTable.length();
             if (shiftTableLen != 0 && shiftTableLen != 128) {
-                Rlog.e(TAG, "Error: language shift tables index " + i +
+                Log.e(TAG, "Error: language shift tables index " + i +
                         " length " + shiftTableLen + " (expected 128 or 0)");
             }
 
diff --git a/telephony/common/com/android/internal/telephony/HbpcdUtils.java b/telephony/common/com/android/internal/telephony/HbpcdUtils.java
index 2f31942..714f5a6 100644
--- a/telephony/common/com/android/internal/telephony/HbpcdUtils.java
+++ b/telephony/common/com/android/internal/telephony/HbpcdUtils.java
@@ -19,7 +19,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.Cursor;
-import android.telephony.Rlog;
+import android.util.Log;
 
 import com.android.internal.telephony.HbpcdLookup.ArbitraryMccSidMatch;
 import com.android.internal.telephony.HbpcdLookup.MccIdd;
@@ -54,16 +54,16 @@
         if (c2 != null) {
             int c2Counter = c2.getCount();
             if (DBG) {
-                Rlog.d(LOG_TAG, "Query unresolved arbitrary table, entries are " + c2Counter);
+                Log.d(LOG_TAG, "Query unresolved arbitrary table, entries are " + c2Counter);
             }
             if (c2Counter == 1) {
                 if (DBG) {
-                    Rlog.d(LOG_TAG, "Query Unresolved arbitrary returned the cursor " + c2);
+                    Log.d(LOG_TAG, "Query Unresolved arbitrary returned the cursor " + c2);
                 }
                 c2.moveToFirst();
                 tmpMcc = c2.getInt(0);
                 if (DBG) {
-                    Rlog.d(LOG_TAG, "MCC found in arbitrary_mcc_sid_match: " + tmpMcc);
+                    Log.d(LOG_TAG, "MCC found in arbitrary_mcc_sid_match: " + tmpMcc);
                 }
                 c2.close();
                 return tmpMcc;
@@ -85,18 +85,18 @@
             int c3Counter = c3.getCount();
             if (c3Counter > 0) {
                 if (c3Counter > 1) {
-                    Rlog.w(LOG_TAG, "something wrong, get more results for 1 conflict SID: " + c3);
+                    Log.w(LOG_TAG, "something wrong, get more results for 1 conflict SID: " + c3);
                 }
-                if (DBG) Rlog.d(LOG_TAG, "Query conflict sid returned the cursor " + c3);
+                if (DBG) Log.d(LOG_TAG, "Query conflict sid returned the cursor " + c3);
                 c3.moveToFirst();
                 tmpMcc = c3.getInt(0);
                 if (DBG) {
-                    Rlog.d(LOG_TAG, "MCC found in mcc_lookup_table. Return tmpMcc = " + tmpMcc);
+                    Log.d(LOG_TAG, "MCC found in mcc_lookup_table. Return tmpMcc = " + tmpMcc);
                 }
                 if (!isNitzTimeZone) {
                     // time zone is not accurate, it may get wrong mcc, ignore it.
                     if (DBG) {
-                        Rlog.d(LOG_TAG, "time zone is not accurate, mcc may be " + tmpMcc);
+                        Log.d(LOG_TAG, "time zone is not accurate, mcc may be " + tmpMcc);
                     }
                     tmpMcc = 0;
                 }
@@ -115,18 +115,18 @@
                 null, null);
         if (c5 != null) {
             if (c5.getCount() > 0) {
-                if (DBG) Rlog.d(LOG_TAG, "Query Range returned the cursor " + c5);
+                if (DBG) Log.d(LOG_TAG, "Query Range returned the cursor " + c5);
                 c5.moveToFirst();
                 tmpMcc = c5.getInt(0);
-                if (DBG) Rlog.d(LOG_TAG, "SID found in mcc_sid_range. Return tmpMcc = " + tmpMcc);
+                if (DBG) Log.d(LOG_TAG, "SID found in mcc_sid_range. Return tmpMcc = " + tmpMcc);
                 c5.close();
                 return tmpMcc;
             }
             c5.close();
         }
-        if (DBG) Rlog.d(LOG_TAG, "SID NOT found in mcc_sid_range.");
+        if (DBG) Log.d(LOG_TAG, "SID NOT found in mcc_sid_range.");
 
-        if (DBG) Rlog.d(LOG_TAG, "Exit getMccByOtherFactors. Return tmpMcc =  " + tmpMcc);
+        if (DBG) Log.d(LOG_TAG, "Exit getMccByOtherFactors. Return tmpMcc =  " + tmpMcc);
         // If unknown MCC still could not be resolved,
         return tmpMcc;
     }
@@ -135,7 +135,7 @@
      *  Gets country information with given MCC.
     */
     public String getIddByMcc(int mcc) {
-        if (DBG) Rlog.d(LOG_TAG, "Enter getHbpcdInfoByMCC.");
+        if (DBG) Log.d(LOG_TAG, "Enter getHbpcdInfoByMCC.");
         String idd = "";
 
         Cursor c = null;
@@ -145,19 +145,19 @@
                 MccIdd.MCC + "=" + mcc, null, null);
         if (cur != null) {
             if (cur.getCount() > 0) {
-                if (DBG) Rlog.d(LOG_TAG, "Query Idd returned the cursor " + cur);
+                if (DBG) Log.d(LOG_TAG, "Query Idd returned the cursor " + cur);
                 // TODO: for those country having more than 1 IDDs, need more information
                 // to decide which IDD would be used. currently just use the first 1.
                 cur.moveToFirst();
                 idd = cur.getString(0);
-                if (DBG) Rlog.d(LOG_TAG, "IDD = " + idd);
+                if (DBG) Log.d(LOG_TAG, "IDD = " + idd);
 
             }
             cur.close();
         }
         if (c != null) c.close();
 
-        if (DBG) Rlog.d(LOG_TAG, "Exit getHbpcdInfoByMCC.");
+        if (DBG) Log.d(LOG_TAG, "Exit getHbpcdInfoByMCC.");
         return idd;
     }
 }
diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java
index b302589..32f9d53 100644
--- a/telephony/common/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/common/com/android/internal/telephony/SmsApplication.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.app.role.RoleManager;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -40,7 +41,6 @@
 import android.provider.Telephony;
 import android.provider.Telephony.Sms.Intents;
 import android.telephony.PackageChangeReceiver;
-import android.telephony.Rlog;
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
@@ -48,8 +48,6 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
@@ -205,7 +203,7 @@
                 < android.os.Process.FIRST_APPLICATION_UID) {
             return contextUserId;
         } else {
-            return UserHandle.getUserId(callingUid);
+            return UserHandle.getUserHandleForUid(callingUid).getIdentifier();
         }
     }
 
@@ -565,7 +563,7 @@
             int mode = appOps.unsafeCheckOp(opStr, applicationData.mUid,
                     applicationData.mPackageName);
             if (mode != AppOpsManager.MODE_ALLOWED) {
-                Rlog.e(LOG_TAG, applicationData.mPackageName + " lost "
+                Log.e(LOG_TAG, applicationData.mPackageName + " lost "
                         + opStr + ": "
                         + (updateIfNeeded ? " (fixing)" : " (no permission to fix)"));
                 if (updateIfNeeded) {
@@ -643,7 +641,7 @@
                     int uid = packageManager.getPackageInfo(oldPackageName, 0).applicationInfo.uid;
                     setExclusiveAppops(oldPackageName, appOps, uid, AppOpsManager.MODE_DEFAULT);
                 } catch (NameNotFoundException e) {
-                    Rlog.w(LOG_TAG, "Old SMS package not found: " + oldPackageName);
+                    Log.w(LOG_TAG, "Old SMS package not found: " + oldPackageName);
                 }
             }
 
@@ -750,7 +748,7 @@
         // the package signature matches system signature.
         final int result = packageManager.checkSignatures(context.getPackageName(), packageName);
         if (result != PackageManager.SIGNATURE_MATCH) {
-            Rlog.e(LOG_TAG, packageName + " does not have system signature");
+            Log.e(LOG_TAG, packageName + " does not have system signature");
             return;
         }
         try {
@@ -758,13 +756,13 @@
             int mode = appOps.unsafeCheckOp(AppOpsManager.OPSTR_WRITE_SMS, info.applicationInfo.uid,
                     packageName);
             if (mode != AppOpsManager.MODE_ALLOWED) {
-                Rlog.w(LOG_TAG, packageName + " does not have OP_WRITE_SMS:  (fixing)");
+                Log.w(LOG_TAG, packageName + " does not have OP_WRITE_SMS:  (fixing)");
                 setExclusiveAppops(packageName, appOps, info.applicationInfo.uid,
                         AppOpsManager.MODE_ALLOWED);
             }
         } catch (NameNotFoundException e) {
             // No whitelisted system app on this device
-            Rlog.e(LOG_TAG, "Package not found: " + packageName);
+            Log.e(LOG_TAG, "Package not found: " + packageName);
         }
 
     }
@@ -811,10 +809,10 @@
                 // This should never happen in prod -- unit tests will put the receiver into a
                 // unusual state where the pending result is null, which produces a NPE when calling
                 // getSendingUserId. Just pretend like it's the system user for testing.
-                userId = UserHandle.USER_SYSTEM;
+                userId = UserHandle.SYSTEM.getIdentifier();
             }
             Context userContext = mContext;
-            if (userId != UserHandle.USER_SYSTEM) {
+            if (userId != UserHandle.SYSTEM.getIdentifier()) {
                 try {
                     userContext = mContext.createPackageContextAsUser(mContext.getPackageName(), 0,
                         UserHandle.of(userId));
diff --git a/telephony/common/com/android/internal/telephony/SmsConstants.java b/telephony/common/com/android/internal/telephony/SmsConstants.java
index 19f52b0..3aa8bbf 100644
--- a/telephony/common/com/android/internal/telephony/SmsConstants.java
+++ b/telephony/common/com/android/internal/telephony/SmsConstants.java
@@ -15,7 +15,7 @@
  */
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * SMS Constants and must be the same as the corresponding
diff --git a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java
index 06c08f5..cd365a1 100644
--- a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java
+++ b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java
@@ -24,13 +24,16 @@
 import android.os.SystemProperties;
 import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
 
 import com.android.internal.telephony.HbpcdLookup.MccIdd;
 import com.android.internal.telephony.HbpcdLookup.MccLookup;
 
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
 import java.util.HashMap;
 
@@ -143,7 +146,7 @@
 
         // First check whether the number is a NANP number.
         int nanpState = checkNANP(numberEntry, allIDDs);
-        if (DBG) Rlog.d(TAG, "NANP type: " + getNumberPlanType(nanpState));
+        if (DBG) Log.d(TAG, "NANP type: " + getNumberPlanType(nanpState));
 
         if ((nanpState == NP_NANP_LOCAL)
             || (nanpState == NP_NANP_AREA_LOCAL)
@@ -173,7 +176,7 @@
 
         int internationalState = checkInternationalNumberPlan(context, numberEntry, allIDDs,
                 NANP_IDD);
-        if (DBG) Rlog.d(TAG, "International type: " + getNumberPlanType(internationalState));
+        if (DBG) Log.d(TAG, "International type: " + getNumberPlanType(internationalState));
         String returnNumber = null;
 
         switch (internationalState) {
@@ -272,7 +275,7 @@
                 }
             }
         } catch (SQLException e) {
-            Rlog.e(TAG, "Can't access HbpcdLookup database", e);
+            Log.e(TAG, "Can't access HbpcdLookup database", e);
         } finally {
             if (cursor != null) {
                 cursor.close();
@@ -281,7 +284,7 @@
 
         IDDS_MAPS.put(mcc, allIDDs);
 
-        if (DBG) Rlog.d(TAG, "MCC = " + mcc + ", all IDDs = " + allIDDs);
+        if (DBG) Log.d(TAG, "MCC = " + mcc + ", all IDDs = " + allIDDs);
         return allIDDs;
     }
 
@@ -472,7 +475,7 @@
                 int tempCC = allCCs[i];
                 for (int j = 0; j < MAX_COUNTRY_CODES_LENGTH; j ++) {
                     if (tempCC == ccArray[j]) {
-                        if (DBG) Rlog.d(TAG, "Country code = " + tempCC);
+                        if (DBG) Log.d(TAG, "Country code = " + tempCC);
                         return tempCC;
                     }
                 }
@@ -509,7 +512,7 @@
                 }
             }
         } catch (SQLException e) {
-            Rlog.e(TAG, "Can't access HbpcdLookup database", e);
+            Log.e(TAG, "Can't access HbpcdLookup database", e);
         } finally {
             if (cursor != null) {
                 cursor.close();
@@ -561,10 +564,10 @@
      * Filter the destination number if using VZW sim card.
      */
     public static String filterDestAddr(Context context, int subId, String destAddr) {
-        if (DBG) Rlog.d(TAG, "enter filterDestAddr. destAddr=\"" + Rlog.pii(TAG, destAddr) + "\"" );
+        if (DBG) Log.d(TAG, "enter filterDestAddr. destAddr=\"" + pii(TAG, destAddr) + "\"" );
 
         if (destAddr == null || !PhoneNumberUtils.isGlobalPhoneNumber(destAddr)) {
-            Rlog.w(TAG, "destAddr" + Rlog.pii(TAG, destAddr) +
+            Log.w(TAG, "destAddr" + pii(TAG, destAddr) +
                     " is not a global phone number! Nothing changed.");
             return destAddr;
         }
@@ -585,9 +588,9 @@
         }
 
         if (DBG) {
-            Rlog.d(TAG, "destAddr is " + ((result != null)?"formatted.":"not formatted."));
-            Rlog.d(TAG, "leave filterDestAddr, new destAddr=\"" + (result != null ? Rlog.pii(TAG,
-                    result) : Rlog.pii(TAG, destAddr)) + "\"");
+            Log.d(TAG, "destAddr is " + ((result != null)?"formatted.":"not formatted."));
+            Log.d(TAG, "leave filterDestAddr, new destAddr=\"" + (result != null ? pii(TAG,
+                    result) : pii(TAG, destAddr)) + "\"");
         }
         return result != null ? result : destAddr;
     }
@@ -608,7 +611,7 @@
                 networkType = CDMA_HOME_NETWORK;
             }
         } else {
-            if (DBG) Rlog.w(TAG, "warning! unknown mPhoneType value=" + phoneType);
+            if (DBG) Log.w(TAG, "warning! unknown mPhoneType value=" + phoneType);
         }
 
         return networkType;
@@ -650,4 +653,44 @@
         // by default this value is false
         return false;
     }
+
+    /**
+     * Redact personally identifiable information for production users.
+     * @param tag used to identify the source of a log message
+     * @param pii the personally identifiable information we want to apply secure hash on.
+     * @return If tag is loggable in verbose mode or pii is null, return the original input.
+     * otherwise return a secure Hash of input pii
+     */
+    private static String pii(String tag, Object pii) {
+        String val = String.valueOf(pii);
+        if (pii == null || TextUtils.isEmpty(val) || Log.isLoggable(tag, Log.VERBOSE)) {
+            return val;
+        }
+        return "[" + secureHash(val.getBytes()) + "]";
+    }
+
+    /**
+     * Returns a secure hash (using the SHA1 algorithm) of the provided input.
+     *
+     * @return "****" if the build type is user, otherwise the hash
+     * @param input the bytes for which the secure hash should be computed.
+     */
+    private static String secureHash(byte[] input) {
+        // Refrain from logging user personal information in user build.
+        if (android.os.Build.IS_USER) {
+            return "****";
+        }
+
+        MessageDigest messageDigest;
+
+        try {
+            messageDigest = MessageDigest.getInstance("SHA-1");
+        } catch (NoSuchAlgorithmException e) {
+            return "####";
+        }
+
+        byte[] result = messageDigest.digest(input);
+        return Base64.encodeToString(
+                result, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP);
+    }
 }
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index 80a55b2..89cd461 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -28,7 +28,6 @@
 import android.os.Build;
 import android.os.Process;
 import android.os.UserHandle;
-import android.telephony.Rlog;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.Log;
@@ -521,7 +520,7 @@
             return;
         }
 
-        if (DBG) Rlog.d(LOG_TAG, "No modify permission, check carrier privilege next.");
+        if (DBG) Log.d(LOG_TAG, "No modify permission, check carrier privilege next.");
         enforceCallingOrSelfCarrierPrivilege(context, subId, message);
     }
 
@@ -539,7 +538,7 @@
         }
 
         if (DBG) {
-            Rlog.d(LOG_TAG, "No READ_PHONE_STATE permission, check carrier privilege next.");
+            Log.d(LOG_TAG, "No READ_PHONE_STATE permission, check carrier privilege next.");
         }
 
         enforceCallingOrSelfCarrierPrivilege(context, subId, message);
@@ -559,7 +558,7 @@
         }
 
         if (DBG) {
-            Rlog.d(LOG_TAG, "No READ_PRIVILEDED_PHONE_STATE permission, "
+            Log.d(LOG_TAG, "No READ_PRIVILEGED_PHONE_STATE permission, "
                     + "check carrier privilege next.");
         }
 
@@ -567,6 +566,33 @@
     }
 
     /**
+     * Ensure the caller (or self, if not processing an IPC) has
+     * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE} or
+     * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} or carrier privileges.
+     *
+     * @throws SecurityException if the caller does not have the required permission/privileges
+     */
+    public static void enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+            Context context, int subId, String message) {
+        if (context.checkCallingOrSelfPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+                == PERMISSION_GRANTED) {
+            return;
+        }
+
+        if (context.checkCallingOrSelfPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
+                == PERMISSION_GRANTED) {
+            return;
+        }
+
+        if (DBG) {
+            Log.d(LOG_TAG, "No READ_PRIVILEGED_PHONE_STATE nor READ_PRECISE_PHONE_STATE permission"
+                    + ", check carrier privilege next.");
+        }
+
+        enforceCallingOrSelfCarrierPrivilege(context, subId, message);
+    }
+
+    /**
      * Make sure the caller (or self, if not processing an IPC) has carrier privileges.
      *
      * @throws SecurityException if the caller does not have the required privileges
@@ -584,7 +610,7 @@
             Context context, int subId, int uid, String message) {
         if (getCarrierPrivilegeStatus(context, subId, uid)
                 != TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
-            if (DBG) Rlog.e(LOG_TAG, "No Carrier Privilege.");
+            if (DBG) Log.e(LOG_TAG, "No Carrier Privilege.");
             throw new SecurityException(message);
         }
     }
@@ -593,7 +619,7 @@
     private static boolean checkCarrierPrivilegeForAnySubId(Context context, int uid) {
         SubscriptionManager sm = (SubscriptionManager) context.getSystemService(
                 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
-        int[] activeSubIds = sm.getActiveSubscriptionIdList(/* visibleOnly */ false);
+        int[] activeSubIds = sm.getActiveAndHiddenSubscriptionIdList();
         for (int activeSubId : activeSubIds) {
             if (getCarrierPrivilegeStatus(context, activeSubId, uid)
                     == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
index 2abcc76..a7ad884 100644
--- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
+++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
@@ -28,6 +28,8 @@
 import android.os.SystemProperties;
 
 import java.io.PrintWriter;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.function.Supplier;
 
 /**
@@ -137,4 +139,12 @@
         }
         return ret;
     }
+
+    /** Wait for latch to trigger */
+    public static void waitUntilReady(CountDownLatch latch, long timeoutMs) {
+        try {
+            latch.await(timeoutMs, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException ignored) {
+        }
+    }
 }
diff --git a/telephony/common/com/google/android/mms/ContentType.java b/telephony/common/com/google/android/mms/ContentType.java
index 12e4b7e..4a971dd 100644
--- a/telephony/common/com/google/android/mms/ContentType.java
+++ b/telephony/common/com/google/android/mms/ContentType.java
@@ -17,7 +17,7 @@
 
 package com.google.android.mms;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import java.util.ArrayList;
 
diff --git a/telephony/common/com/google/android/mms/InvalidHeaderValueException.java b/telephony/common/com/google/android/mms/InvalidHeaderValueException.java
index 2836c30..55087ff 100644
--- a/telephony/common/com/google/android/mms/InvalidHeaderValueException.java
+++ b/telephony/common/com/google/android/mms/InvalidHeaderValueException.java
@@ -17,7 +17,7 @@
 
 package com.google.android.mms;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * Thrown when an invalid header value was set.
diff --git a/telephony/common/com/google/android/mms/MmsException.java b/telephony/common/com/google/android/mms/MmsException.java
index 5be33ed..24bceb3 100644
--- a/telephony/common/com/google/android/mms/MmsException.java
+++ b/telephony/common/com/google/android/mms/MmsException.java
@@ -17,7 +17,7 @@
 
 package com.google.android.mms;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * A generic exception that is thrown by the Mms client.
diff --git a/telephony/common/com/google/android/mms/pdu/AcknowledgeInd.java b/telephony/common/com/google/android/mms/pdu/AcknowledgeInd.java
index ae447d7..8693385 100644
--- a/telephony/common/com/google/android/mms/pdu/AcknowledgeInd.java
+++ b/telephony/common/com/google/android/mms/pdu/AcknowledgeInd.java
@@ -17,7 +17,7 @@
 
 package com.google.android.mms.pdu;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import com.google.android.mms.InvalidHeaderValueException;
 
diff --git a/telephony/common/com/google/android/mms/pdu/Base64.java b/telephony/common/com/google/android/mms/pdu/Base64.java
index 483fa7f..0d6a46a 100644
--- a/telephony/common/com/google/android/mms/pdu/Base64.java
+++ b/telephony/common/com/google/android/mms/pdu/Base64.java
@@ -17,7 +17,7 @@
 
 package com.google.android.mms.pdu;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 public class Base64 {
     /**
diff --git a/telephony/common/com/google/android/mms/pdu/CharacterSets.java b/telephony/common/com/google/android/mms/pdu/CharacterSets.java
index 27da35e..5172b7b 100644
--- a/telephony/common/com/google/android/mms/pdu/CharacterSets.java
+++ b/telephony/common/com/google/android/mms/pdu/CharacterSets.java
@@ -17,7 +17,7 @@
 
 package com.google.android.mms.pdu;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import java.io.UnsupportedEncodingException;
 import java.util.HashMap;
diff --git a/telephony/common/com/google/android/mms/pdu/DeliveryInd.java b/telephony/common/com/google/android/mms/pdu/DeliveryInd.java
index 7093ac6..8fb6a75 100644
--- a/telephony/common/com/google/android/mms/pdu/DeliveryInd.java
+++ b/telephony/common/com/google/android/mms/pdu/DeliveryInd.java
@@ -17,7 +17,7 @@
 
 package com.google.android.mms.pdu;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import com.google.android.mms.InvalidHeaderValueException;
 
diff --git a/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java b/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java
index 4166275..8c0380f7 100644
--- a/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java
+++ b/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java
@@ -17,10 +17,9 @@
 
 package com.google.android.mms.pdu;
 
+import android.compat.annotation.UnsupportedAppUsage;
 import android.util.Log;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
diff --git a/telephony/common/com/google/android/mms/pdu/GenericPdu.java b/telephony/common/com/google/android/mms/pdu/GenericPdu.java
index ebf16ac..320b13f 100644
--- a/telephony/common/com/google/android/mms/pdu/GenericPdu.java
+++ b/telephony/common/com/google/android/mms/pdu/GenericPdu.java
@@ -17,7 +17,7 @@
 
 package com.google.android.mms.pdu;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import com.google.android.mms.InvalidHeaderValueException;
 
diff --git a/telephony/common/com/google/android/mms/pdu/MultimediaMessagePdu.java b/telephony/common/com/google/android/mms/pdu/MultimediaMessagePdu.java
index e108f76..42a89c6 100644
--- a/telephony/common/com/google/android/mms/pdu/MultimediaMessagePdu.java
+++ b/telephony/common/com/google/android/mms/pdu/MultimediaMessagePdu.java
@@ -17,7 +17,7 @@
 
 package com.google.android.mms.pdu;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import com.google.android.mms.InvalidHeaderValueException;
 
diff --git a/telephony/common/com/google/android/mms/pdu/NotificationInd.java b/telephony/common/com/google/android/mms/pdu/NotificationInd.java
index b561bd4..ca4615c 100644
--- a/telephony/common/com/google/android/mms/pdu/NotificationInd.java
+++ b/telephony/common/com/google/android/mms/pdu/NotificationInd.java
@@ -17,7 +17,7 @@
 
 package com.google.android.mms.pdu;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import com.google.android.mms.InvalidHeaderValueException;
 
diff --git a/telephony/common/com/google/android/mms/pdu/NotifyRespInd.java b/telephony/common/com/google/android/mms/pdu/NotifyRespInd.java
index 3c70f86..ebd81af 100644
--- a/telephony/common/com/google/android/mms/pdu/NotifyRespInd.java
+++ b/telephony/common/com/google/android/mms/pdu/NotifyRespInd.java
@@ -17,7 +17,7 @@
 
 package com.google.android.mms.pdu;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import com.google.android.mms.InvalidHeaderValueException;
 
diff --git a/telephony/common/com/google/android/mms/pdu/PduBody.java b/telephony/common/com/google/android/mms/pdu/PduBody.java
index 51914e4..f7f285f 100644
--- a/telephony/common/com/google/android/mms/pdu/PduBody.java
+++ b/telephony/common/com/google/android/mms/pdu/PduBody.java
@@ -17,7 +17,7 @@
 
 package com.google.android.mms.pdu;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import java.util.HashMap;
 import java.util.Map;
diff --git a/telephony/common/com/google/android/mms/pdu/PduComposer.java b/telephony/common/com/google/android/mms/pdu/PduComposer.java
index e24bf21..b8b212c 100644
--- a/telephony/common/com/google/android/mms/pdu/PduComposer.java
+++ b/telephony/common/com/google/android/mms/pdu/PduComposer.java
@@ -17,12 +17,11 @@
 
 package com.google.android.mms.pdu;
 
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.text.TextUtils;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
 import java.io.ByteArrayOutputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
diff --git a/telephony/common/com/google/android/mms/pdu/PduContentTypes.java b/telephony/common/com/google/android/mms/pdu/PduContentTypes.java
index 8551b2f..57141fe 100644
--- a/telephony/common/com/google/android/mms/pdu/PduContentTypes.java
+++ b/telephony/common/com/google/android/mms/pdu/PduContentTypes.java
@@ -17,7 +17,7 @@
 
 package com.google.android.mms.pdu;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 public class PduContentTypes {
     /**
diff --git a/telephony/common/com/google/android/mms/pdu/PduHeaders.java b/telephony/common/com/google/android/mms/pdu/PduHeaders.java
index b524464..3e62184 100644
--- a/telephony/common/com/google/android/mms/pdu/PduHeaders.java
+++ b/telephony/common/com/google/android/mms/pdu/PduHeaders.java
@@ -17,7 +17,7 @@
 
 package com.google.android.mms.pdu;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import com.google.android.mms.InvalidHeaderValueException;
 
diff --git a/telephony/common/com/google/android/mms/pdu/PduParser.java b/telephony/common/com/google/android/mms/pdu/PduParser.java
index f483994..5340245 100755
--- a/telephony/common/com/google/android/mms/pdu/PduParser.java
+++ b/telephony/common/com/google/android/mms/pdu/PduParser.java
@@ -17,10 +17,9 @@
 
 package com.google.android.mms.pdu;
 
+import android.compat.annotation.UnsupportedAppUsage;
 import android.util.Log;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
 import com.google.android.mms.ContentType;
 import com.google.android.mms.InvalidHeaderValueException;
 
diff --git a/telephony/common/com/google/android/mms/pdu/PduPart.java b/telephony/common/com/google/android/mms/pdu/PduPart.java
index 09b7751..8dd976b 100644
--- a/telephony/common/com/google/android/mms/pdu/PduPart.java
+++ b/telephony/common/com/google/android/mms/pdu/PduPart.java
@@ -17,10 +17,9 @@
 
 package com.google.android.mms.pdu;
 
+import android.compat.annotation.UnsupportedAppUsage;
 import android.net.Uri;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
 import java.util.HashMap;
 import java.util.Map;
 
diff --git a/telephony/common/com/google/android/mms/pdu/PduPersister.java b/telephony/common/com/google/android/mms/pdu/PduPersister.java
index 8efca0e..fcd5b8f 100755
--- a/telephony/common/com/google/android/mms/pdu/PduPersister.java
+++ b/telephony/common/com/google/android/mms/pdu/PduPersister.java
@@ -17,6 +17,7 @@
 
 package com.google.android.mms.pdu;
 
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
@@ -40,8 +41,6 @@
 import android.text.TextUtils;
 import android.util.Log;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
 import com.google.android.mms.ContentType;
 import com.google.android.mms.InvalidHeaderValueException;
 import com.google.android.mms.MmsException;
diff --git a/telephony/common/com/google/android/mms/pdu/QuotedPrintable.java b/telephony/common/com/google/android/mms/pdu/QuotedPrintable.java
index 9d6535c..4e1d7f5 100644
--- a/telephony/common/com/google/android/mms/pdu/QuotedPrintable.java
+++ b/telephony/common/com/google/android/mms/pdu/QuotedPrintable.java
@@ -17,7 +17,7 @@
 
 package com.google.android.mms.pdu;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import java.io.ByteArrayOutputStream;
 
diff --git a/telephony/common/com/google/android/mms/pdu/ReadOrigInd.java b/telephony/common/com/google/android/mms/pdu/ReadOrigInd.java
index e38c62d..4ba3c71 100644
--- a/telephony/common/com/google/android/mms/pdu/ReadOrigInd.java
+++ b/telephony/common/com/google/android/mms/pdu/ReadOrigInd.java
@@ -17,7 +17,7 @@
 
 package com.google.android.mms.pdu;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import com.google.android.mms.InvalidHeaderValueException;
 
diff --git a/telephony/common/com/google/android/mms/pdu/ReadRecInd.java b/telephony/common/com/google/android/mms/pdu/ReadRecInd.java
index 9696bc2..37ccfb9 100644
--- a/telephony/common/com/google/android/mms/pdu/ReadRecInd.java
+++ b/telephony/common/com/google/android/mms/pdu/ReadRecInd.java
@@ -17,7 +17,7 @@
 
 package com.google.android.mms.pdu;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import com.google.android.mms.InvalidHeaderValueException;
 
diff --git a/telephony/common/com/google/android/mms/pdu/RetrieveConf.java b/telephony/common/com/google/android/mms/pdu/RetrieveConf.java
index 03755af..260adfc 100644
--- a/telephony/common/com/google/android/mms/pdu/RetrieveConf.java
+++ b/telephony/common/com/google/android/mms/pdu/RetrieveConf.java
@@ -17,7 +17,7 @@
 
 package com.google.android.mms.pdu;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import com.google.android.mms.InvalidHeaderValueException;
 
diff --git a/telephony/common/com/google/android/mms/pdu/SendConf.java b/telephony/common/com/google/android/mms/pdu/SendConf.java
index b8598279..7799238 100644
--- a/telephony/common/com/google/android/mms/pdu/SendConf.java
+++ b/telephony/common/com/google/android/mms/pdu/SendConf.java
@@ -17,7 +17,7 @@
 
 package com.google.android.mms.pdu;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import com.google.android.mms.InvalidHeaderValueException;
 
diff --git a/telephony/common/com/google/android/mms/pdu/SendReq.java b/telephony/common/com/google/android/mms/pdu/SendReq.java
index c1b7f93..6e2f2da 100644
--- a/telephony/common/com/google/android/mms/pdu/SendReq.java
+++ b/telephony/common/com/google/android/mms/pdu/SendReq.java
@@ -17,10 +17,9 @@
 
 package com.google.android.mms.pdu;
 
+import android.compat.annotation.UnsupportedAppUsage;
 import android.util.Log;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
 import com.google.android.mms.InvalidHeaderValueException;
 
 public class SendReq extends MultimediaMessagePdu {
diff --git a/telephony/common/com/google/android/mms/util/AbstractCache.java b/telephony/common/com/google/android/mms/util/AbstractCache.java
index ab5d48a..25862e7 100644
--- a/telephony/common/com/google/android/mms/util/AbstractCache.java
+++ b/telephony/common/com/google/android/mms/util/AbstractCache.java
@@ -17,10 +17,9 @@
 
 package com.google.android.mms.util;
 
+import android.compat.annotation.UnsupportedAppUsage;
 import android.util.Log;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
 import java.util.HashMap;
 
 public abstract class AbstractCache<K, V> {
diff --git a/telephony/common/com/google/android/mms/util/DownloadDrmHelper.java b/telephony/common/com/google/android/mms/util/DownloadDrmHelper.java
index 118de46..0f9390d 100644
--- a/telephony/common/com/google/android/mms/util/DownloadDrmHelper.java
+++ b/telephony/common/com/google/android/mms/util/DownloadDrmHelper.java
@@ -17,12 +17,11 @@
 
 package com.google.android.mms.util;
 
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.drm.DrmManagerClient;
 import android.util.Log;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
 public class DownloadDrmHelper {
     private static final String TAG = "DownloadDrmHelper";
 
diff --git a/telephony/common/com/google/android/mms/util/DrmConvertSession.java b/telephony/common/com/google/android/mms/util/DrmConvertSession.java
index 0e8ec91..156c7ad 100644
--- a/telephony/common/com/google/android/mms/util/DrmConvertSession.java
+++ b/telephony/common/com/google/android/mms/util/DrmConvertSession.java
@@ -16,14 +16,13 @@
  */
 package com.google.android.mms.util;
 
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.drm.DrmConvertedStatus;
 import android.drm.DrmManagerClient;
 import android.provider.Downloads;
 import android.util.Log;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.RandomAccessFile;
diff --git a/telephony/common/com/google/android/mms/util/PduCache.java b/telephony/common/com/google/android/mms/util/PduCache.java
index 94e3894..c380d6b 100644
--- a/telephony/common/com/google/android/mms/util/PduCache.java
+++ b/telephony/common/com/google/android/mms/util/PduCache.java
@@ -17,14 +17,13 @@
 
 package com.google.android.mms.util;
 
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ContentUris;
 import android.content.UriMatcher;
 import android.net.Uri;
 import android.provider.Telephony.Mms;
 import android.util.Log;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
 import java.util.HashMap;
 import java.util.HashSet;
 
diff --git a/telephony/common/com/google/android/mms/util/PduCacheEntry.java b/telephony/common/com/google/android/mms/util/PduCacheEntry.java
index 1ecd1bf..a4a25d2 100644
--- a/telephony/common/com/google/android/mms/util/PduCacheEntry.java
+++ b/telephony/common/com/google/android/mms/util/PduCacheEntry.java
@@ -17,7 +17,7 @@
 
 package com.google.android.mms.util;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import com.google.android.mms.pdu.GenericPdu;
 
diff --git a/telephony/common/com/google/android/mms/util/SqliteWrapper.java b/telephony/common/com/google/android/mms/util/SqliteWrapper.java
index 2dd1dc1..31fe4d7 100644
--- a/telephony/common/com/google/android/mms/util/SqliteWrapper.java
+++ b/telephony/common/com/google/android/mms/util/SqliteWrapper.java
@@ -18,6 +18,7 @@
 package com.google.android.mms.util;
 
 import android.app.ActivityManager;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
@@ -27,8 +28,6 @@
 import android.util.Log;
 import android.widget.Toast;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
 public final class SqliteWrapper {
     private static final String TAG = "SqliteWrapper";
     private static final String SQLITE_EXCEPTION_DETAIL_MESSAGE
diff --git a/telephony/framework-telephony-jarjar-rules.txt b/telephony/framework-telephony-jarjar-rules.txt
new file mode 100644
index 0000000..212eba1
--- /dev/null
+++ b/telephony/framework-telephony-jarjar-rules.txt
@@ -0,0 +1,9 @@
+rule android.telephony.Annotation* android.telephony.framework.Annotation@1
+rule android.util.RecurrenceRule* android.telephony.RecurrenceRule@1
+rule com.android.i18n.phonenumbers.** com.android.telephony.framework.phonenumbers.@1
+rule com.android.internal.os.SomeArgs* android.telephony.SomeArgs@1
+rule com.android.internal.util.BitwiseInputStream* android.telephony.BitwiseInputStream@1
+rule com.android.internal.util.BitwiseOutputStream* android.telephony.BitwiseOutputStream@1
+rule com.android.internal.util.Preconditions* android.telephony.Preconditions@1
+rule com.android.internal.util.IndentingPrintWriter* android.telephony.IndentingPrintWriter@1
+rule com.android.internal.util.HexDump* android.telephony.HexDump@1
diff --git a/telephony/java/android/service/carrier/CarrierIdentifier.java b/telephony/java/android/service/carrier/CarrierIdentifier.java
index af5bf74..7957c25 100644
--- a/telephony/java/android/service/carrier/CarrierIdentifier.java
+++ b/telephony/java/android/service/carrier/CarrierIdentifier.java
@@ -20,7 +20,7 @@
 import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.telephony.TelephonyManager;
 
 import com.android.internal.telephony.uicc.IccUtils;
diff --git a/telephony/java/android/service/euicc/EuiccProfileInfo.java b/telephony/java/android/service/euicc/EuiccProfileInfo.java
index 6c357cc..8450a90 100644
--- a/telephony/java/android/service/euicc/EuiccProfileInfo.java
+++ b/telephony/java/android/service/euicc/EuiccProfileInfo.java
@@ -19,7 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.service.carrier.CarrierIdentifier;
diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java
index bc6a9e8..9315586 100644
--- a/telephony/java/android/service/euicc/EuiccService.java
+++ b/telephony/java/android/service/euicc/EuiccService.java
@@ -31,9 +31,13 @@
 import android.telephony.TelephonyManager;
 import android.telephony.euicc.DownloadableSubscription;
 import android.telephony.euicc.EuiccInfo;
+import android.telephony.euicc.EuiccManager;
 import android.telephony.euicc.EuiccManager.OtaStatus;
+import android.text.TextUtils;
 import android.util.Log;
 
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.concurrent.LinkedBlockingQueue;
@@ -309,6 +313,65 @@
         mStubWrapper = new IEuiccServiceWrapper();
     }
 
+    /**
+     * Given a SubjectCode[5.2.6.1] and ReasonCode[5.2.6.2] from GSMA (SGP.22 v2.2), encode it to
+     * the format described in
+     * {@link android.telephony.euicc.EuiccManager#OPERATION_SMDX_SUBJECT_REASON_CODE}
+     *
+     * @param subjectCode SubjectCode[5.2.6.1] from GSMA (SGP.22 v2.2)
+     * @param reasonCode  ReasonCode[5.2.6.2] from GSMA (SGP.22 v2.2)
+     * @return encoded error code described in
+     * {@link android.telephony.euicc.EuiccManager#OPERATION_SMDX_SUBJECT_REASON_CODE}
+     * @throws NumberFormatException         when the Subject/Reason code contains non digits
+     * @throws IllegalArgumentException      when Subject/Reason code is null/empty
+     * @throws UnsupportedOperationException when sections has more than four layers (e.g 5.8.1.2)
+     *                                       or when an number is bigger than 15
+     */
+    public int encodeSmdxSubjectAndReasonCode(@Nullable String subjectCode,
+            @Nullable String reasonCode)
+            throws NumberFormatException, IllegalArgumentException, UnsupportedOperationException {
+        final int maxSupportedSection = 3;
+        final int maxSupportedDigit = 15;
+        final int bitsPerSection = 4;
+
+        if (TextUtils.isEmpty(subjectCode) || TextUtils.isEmpty(reasonCode)) {
+            throw new IllegalArgumentException("SubjectCode/ReasonCode is empty");
+        }
+
+        final String[] subjectCodeToken = subjectCode.split("\\.");
+        final String[] reasonCodeToken = reasonCode.split("\\.");
+
+        if (subjectCodeToken.length > maxSupportedSection
+                || reasonCodeToken.length > maxSupportedSection) {
+            throw new UnsupportedOperationException("Only three nested layer is supported.");
+        }
+
+        int result = EuiccManager.OPERATION_SMDX_SUBJECT_REASON_CODE;
+
+        // Pad the 0s needed for subject code
+        result = result << (maxSupportedSection - subjectCodeToken.length) * bitsPerSection;
+
+        for (String digitString : subjectCodeToken) {
+            int num = Integer.parseInt(digitString);
+            if (num > maxSupportedDigit) {
+                throw new UnsupportedOperationException("SubjectCode exceeds " + maxSupportedDigit);
+            }
+            result = (result << bitsPerSection) + num;
+        }
+
+        // Pad the 0s needed for reason code
+        result = result << (maxSupportedSection - reasonCodeToken.length) * bitsPerSection;
+        for (String digitString : reasonCodeToken) {
+            int num = Integer.parseInt(digitString);
+            if (num > maxSupportedDigit) {
+                throw new UnsupportedOperationException("ReasonCode exceeds " + maxSupportedDigit);
+            }
+            result = (result << bitsPerSection) + num;
+        }
+
+        return result;
+    }
+
     @Override
     @CallSuper
     public void onCreate() {
@@ -583,6 +646,13 @@
     public abstract int onRetainSubscriptionsForFactoryReset(int slotId);
 
     /**
+     * Dump to a provided printWriter.
+     */
+    public void dump(@NonNull PrintWriter printWriter) {
+        printWriter.println("The connected LPA does not implement EuiccService#dump()");
+    }
+
+    /**
      * Wrapper around IEuiccService that forwards calls to implementations of {@link EuiccService}.
      */
     private class IEuiccServiceWrapper extends IEuiccService.Stub {
@@ -834,5 +904,22 @@
                 }
             });
         }
+
+        @Override
+        public void dump(IEuiccServiceDumpResultCallback callback) throws RemoteException {
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        final StringWriter sw = new StringWriter();
+                        final PrintWriter pw = new PrintWriter(sw);
+                        EuiccService.this.dump(pw);
+                        callback.onComplete(sw.toString());
+                    } catch (RemoteException e) {
+                        // Can't communicate with the phone process; ignore.
+                    }
+                }
+            });
+        }
     }
 }
diff --git a/telephony/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java b/telephony/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java
index c7a9851..2382f65 100644
--- a/telephony/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java
+++ b/telephony/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java
@@ -17,7 +17,7 @@
 
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.euicc.DownloadableSubscription;
diff --git a/telephony/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java b/telephony/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java
index abd4065..d0fb511 100644
--- a/telephony/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java
+++ b/telephony/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java
@@ -17,7 +17,7 @@
 
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.euicc.DownloadableSubscription;
diff --git a/telephony/java/android/service/euicc/IEuiccService.aidl b/telephony/java/android/service/euicc/IEuiccService.aidl
index 2acc47a..bb7b569 100644
--- a/telephony/java/android/service/euicc/IEuiccService.aidl
+++ b/telephony/java/android/service/euicc/IEuiccService.aidl
@@ -29,6 +29,7 @@
 import android.service.euicc.IRetainSubscriptionsForFactoryResetCallback;
 import android.service.euicc.ISwitchToSubscriptionCallback;
 import android.service.euicc.IUpdateSubscriptionNicknameCallback;
+import android.service.euicc.IEuiccServiceDumpResultCallback;
 import android.telephony.euicc.DownloadableSubscription;
 import android.os.Bundle;
 
@@ -56,4 +57,5 @@
             int slotIndex, int options, in IEraseSubscriptionsCallback callback);
     void retainSubscriptionsForFactoryReset(
             int slotId, in IRetainSubscriptionsForFactoryResetCallback callback);
+    void dump(in IEuiccServiceDumpResultCallback callback);
 }
\ No newline at end of file
diff --git a/telephony/java/android/telephony/AccessNetworkConstants.java b/telephony/java/android/telephony/AccessNetworkConstants.java
index bb28df2..610eef8 100644
--- a/telephony/java/android/telephony/AccessNetworkConstants.java
+++ b/telephony/java/android/telephony/AccessNetworkConstants.java
@@ -50,18 +50,12 @@
 
     /**
      * Transport type for Wireless Wide Area Networks (i.e. Cellular)
-     * @hide
      */
-    @SystemApi
-    @TestApi
     public static final int TRANSPORT_TYPE_WWAN = 1;
 
     /**
      * Transport type for Wireless Local Area Networks (i.e. Wifi)
-     * @hide
      */
-    @SystemApi
-    @TestApi
     public static final int TRANSPORT_TYPE_WLAN = 2;
 
     /** @hide */
@@ -73,6 +67,22 @@
         }
     }
 
+    /**
+     * Access network type
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"RADIO_ACCESS_NETWORK_TYPE_"},
+            value = {
+                    AccessNetworkType.UNKNOWN,
+                    AccessNetworkType.GERAN,
+                    AccessNetworkType.UTRAN,
+                    AccessNetworkType.EUTRAN,
+                    AccessNetworkType.CDMA2000,
+                    AccessNetworkType.IWLAN,
+                    AccessNetworkType.NGRAN})
+    public @interface RadioAccessNetworkType {}
+
     public static final class AccessNetworkType {
         public static final int UNKNOWN = 0;
         public static final int GERAN = 1;
@@ -121,11 +131,11 @@
         public static final int BAND_ER900 = 14;
 
         /** @hide */
-        private GeranBand() {};
+        private GeranBand() {}
     }
 
     /**
-     * Frenquency bands for UTRAN.
+     * Frequency bands for UTRAN.
      * http://www.etsi.org/deliver/etsi_ts/125100_125199/125104/13.03.00_60/ts_125104v130p.pdf
      */
     public static final class UtranBand {
@@ -152,12 +162,19 @@
         public static final int BAND_25 = 25;
         public static final int BAND_26 = 26;
 
+        /** Frequency bands for TD-SCDMA. Defined in 3GPP TS 25.102, Table 5.2. */
+        public static final int BAND_A = 101;
+        public static final int BAND_B = 102;
+        public static final int BAND_C = 103;
+        public static final int BAND_D = 104;
+        public static final int BAND_E = 105;
+        public static final int BAND_F = 106;
         /** @hide */
-        private UtranBand() {};
+        private UtranBand() {}
     }
 
     /**
-     * Frenquency bands for EUTRAN.
+     * Frequency bands for EUTRAN.
      * http://www.etsi.org/deliver/etsi_ts/136100_136199/136101/14.03.00_60/ts_136101v140p.pdf
      */
     public static final class EutranBand {
@@ -215,7 +232,7 @@
     }
 
     /**
-     * Frenquency bands for CDMA2000.
+     * Frequency bands for CDMA2000.
      * http://www.3gpp2.org/Public_html/Specs/C.S0057-E_v1.0_Bandclass_Specification.pdf
      * @hide
      *
@@ -246,7 +263,7 @@
         public static final int BAND_21 = 22;
 
         /** @hide */
-        private CdmaBands() {};
+        private CdmaBands() {}
     }
 
     /**
@@ -300,8 +317,161 @@
         public static final int BAND_260 = 260;
         public static final int BAND_261 = 261;
 
+        /**
+         * NR Bands
+         *
+         * @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(prefix = {"BAND_"},
+                value = {BAND_1,
+                        BAND_2,
+                        BAND_3,
+                        BAND_5,
+                        BAND_7,
+                        BAND_8,
+                        BAND_12,
+                        BAND_14,
+                        BAND_18,
+                        BAND_20,
+                        BAND_25,
+                        BAND_28,
+                        BAND_29,
+                        BAND_30,
+                        BAND_34,
+                        BAND_38,
+                        BAND_39,
+                        BAND_40,
+                        BAND_41,
+                        BAND_48,
+                        BAND_50,
+                        BAND_51,
+                        BAND_65,
+                        BAND_66,
+                        BAND_70,
+                        BAND_71,
+                        BAND_74,
+                        BAND_75,
+                        BAND_76,
+                        BAND_77,
+                        BAND_78,
+                        BAND_79,
+                        BAND_80,
+                        BAND_81,
+                        BAND_82,
+                        BAND_83,
+                        BAND_84,
+                        BAND_86,
+                        BAND_90,
+                        BAND_257,
+                        BAND_258,
+                        BAND_260,
+                        BAND_261})
+        public @interface NgranBand {}
+
+        /**
+         * Unknown NR frequency.
+         *
+         * @hide
+         */
+        @SystemApi
+        @TestApi
+        public static final int FREQUENCY_RANGE_GROUP_UNKNOWN = 0;
+
+        /**
+         * NR frequency group 1 defined in 3GPP TS 38.101-1 table 5.2-1
+         *
+         * @hide
+         */
+        @SystemApi
+        @TestApi
+        public static final int FREQUENCY_RANGE_GROUP_1 = 1;
+
+        /**
+         * NR frequency group 2 defined in 3GPP TS 38.101-2 table 5.2-1
+         *
+         * @hide
+         */
+        @SystemApi
+        @TestApi
+        public static final int FREQUENCY_RANGE_GROUP_2 = 2;
+
+        /**
+         * Radio frequency range group
+         *
+         * @hide
+         */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(prefix = {"FREQUENCY_RANGE_GROUP_"},
+                value = {
+                        FREQUENCY_RANGE_GROUP_UNKNOWN,
+                        FREQUENCY_RANGE_GROUP_1,
+                        FREQUENCY_RANGE_GROUP_2})
+        public @interface FrequencyRangeGroup {}
+
+        /**
+         * Get frequency range group
+         *
+         * @param band NR band
+         * @return The frequency range group
+         *
+         * @hide
+         */
+        @SystemApi
+        @TestApi
+        public static @FrequencyRangeGroup int getFrequencyRangeGroup(@NgranBand int band) {
+            switch (band) {
+                case BAND_1:
+                case BAND_2:
+                case BAND_3:
+                case BAND_5:
+                case BAND_7:
+                case BAND_8:
+                case BAND_12:
+                case BAND_14:
+                case BAND_18:
+                case BAND_20:
+                case BAND_25:
+                case BAND_28:
+                case BAND_29:
+                case BAND_30:
+                case BAND_34:
+                case BAND_38:
+                case BAND_39:
+                case BAND_40:
+                case BAND_41:
+                case BAND_48:
+                case BAND_50:
+                case BAND_51:
+                case BAND_65:
+                case BAND_66:
+                case BAND_70:
+                case BAND_71:
+                case BAND_74:
+                case BAND_75:
+                case BAND_76:
+                case BAND_77:
+                case BAND_78:
+                case BAND_79:
+                case BAND_80:
+                case BAND_81:
+                case BAND_82:
+                case BAND_83:
+                case BAND_84:
+                case BAND_86:
+                case BAND_90:
+                    return FREQUENCY_RANGE_GROUP_1;
+                case BAND_257:
+                case BAND_258:
+                case BAND_260:
+                case BAND_261:
+                    return FREQUENCY_RANGE_GROUP_2;
+                default:
+                    return FREQUENCY_RANGE_GROUP_UNKNOWN;
+            }
+        };
+
         /** @hide */
-        private NgranBands() {};
+        private NgranBands() {}
     }
 
     /** @hide */
diff --git a/telephony/java/android/telephony/AnomalyReporter.java b/telephony/java/android/telephony/AnomalyReporter.java
index 9753d8b..097041f 100644
--- a/telephony/java/android/telephony/AnomalyReporter.java
+++ b/telephony/java/android/telephony/AnomalyReporter.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.content.Context;
diff --git a/telephony/java/android/telephony/BarringInfo.aidl b/telephony/java/android/telephony/BarringInfo.aidl
new file mode 100644
index 0000000..50ddf6b
--- /dev/null
+++ b/telephony/java/android/telephony/BarringInfo.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright 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.
+ */
+
+/** @hide */
+package android.telephony;
+
+parcelable BarringInfo;
diff --git a/telephony/java/android/telephony/BarringInfo.java b/telephony/java/android/telephony/BarringInfo.java
new file mode 100644
index 0000000..5419c3c
--- /dev/null
+++ b/telephony/java/android/telephony/BarringInfo.java
@@ -0,0 +1,398 @@
+/*
+ * Copyright 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.telephony;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.SparseArray;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Provides the barring configuration for a particular service type.
+ *
+ * Provides indication about the barring of a particular service for use. Certain barring types
+ * are only valid for certain technology families. Any service that does not have a barring
+ * configuration is unbarred by default.
+ */
+public final class BarringInfo implements Parcelable {
+
+    /**
+     * Barring Service Type
+     *
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "BARRING_SERVICE_TYPE_", value = {
+            BARRING_SERVICE_TYPE_CS_SERVICE,
+            BARRING_SERVICE_TYPE_PS_SERVICE,
+            BARRING_SERVICE_TYPE_CS_VOICE,
+            BARRING_SERVICE_TYPE_MO_SIGNALLING,
+            BARRING_SERVICE_TYPE_MO_DATA,
+            BARRING_SERVICE_TYPE_CS_FALLBACK,
+            BARRING_SERVICE_TYPE_MMTEL_VOICE,
+            BARRING_SERVICE_TYPE_MMTEL_VIDEO,
+            BARRING_SERVICE_TYPE_EMERGENCY,
+            BARRING_SERVICE_TYPE_SMS})
+    public @interface BarringServiceType {}
+
+    /* Applicabe to UTRAN */
+    /** Barring indicator for circuit-switched service; applicable to UTRAN */
+    public static final int BARRING_SERVICE_TYPE_CS_SERVICE =
+            android.hardware.radio.V1_5.BarringServiceType.CS_SERVICE;
+    /** Barring indicator for packet-switched service; applicable to UTRAN */
+    public static final int BARRING_SERVICE_TYPE_PS_SERVICE =
+            android.hardware.radio.V1_5.BarringServiceType.PS_SERVICE;
+    /** Barring indicator for circuit-switched voice service; applicable to UTRAN */
+    public static final int BARRING_SERVICE_TYPE_CS_VOICE =
+            android.hardware.radio.V1_5.BarringServiceType.CS_VOICE;
+
+    /* Applicable to EUTRAN, NGRAN */
+    /** Barring indicator for mobile-originated signalling; applicable to EUTRAN and NGRAN */
+    public static final int BARRING_SERVICE_TYPE_MO_SIGNALLING =
+            android.hardware.radio.V1_5.BarringServiceType.MO_SIGNALLING;
+    /** Barring indicator for mobile-originated data traffic; applicable to EUTRAN and NGRAN */
+    public static final int BARRING_SERVICE_TYPE_MO_DATA =
+            android.hardware.radio.V1_5.BarringServiceType.MO_DATA;
+    /** Barring indicator for circuit-switched fallback for voice; applicable to EUTRAN and NGRAN */
+    public static final int BARRING_SERVICE_TYPE_CS_FALLBACK =
+            android.hardware.radio.V1_5.BarringServiceType.CS_FALLBACK;
+    /** Barring indicator for MMTEL (IMS) voice; applicable to EUTRAN and NGRAN */
+    public static final int BARRING_SERVICE_TYPE_MMTEL_VOICE =
+            android.hardware.radio.V1_5.BarringServiceType.MMTEL_VOICE;
+    /** Barring indicator for MMTEL (IMS) video; applicable to EUTRAN and NGRAN */
+    public static final int BARRING_SERVICE_TYPE_MMTEL_VIDEO =
+            android.hardware.radio.V1_5.BarringServiceType.MMTEL_VIDEO;
+
+    /* Applicable to UTRAN, EUTRAN, NGRAN */
+    /** Barring indicator for emergency services; applicable to UTRAN, EUTRAN, and NGRAN */
+    public static final int BARRING_SERVICE_TYPE_EMERGENCY =
+            android.hardware.radio.V1_5.BarringServiceType.EMERGENCY;
+    /** Barring indicator for SMS sending; applicable to UTRAN, EUTRAN, and NGRAN */
+    public static final int BARRING_SERVICE_TYPE_SMS =
+            android.hardware.radio.V1_5.BarringServiceType.SMS;
+
+    //TODO: add barring constants for Operator-Specific barring codes
+
+    /** Describe the current barring configuration of a cell */
+    public static final class BarringServiceInfo implements Parcelable {
+        /**
+         * Barring Type
+         * @hide
+         */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(prefix = "BARRING_TYPE_", value =
+                    {BARRING_TYPE_NONE,
+                    BARRING_TYPE_UNCONDITIONAL,
+                    BARRING_TYPE_CONDITIONAL,
+                    BARRING_TYPE_UNKNOWN})
+        public @interface BarringType {}
+
+        /** Barring is inactive */
+        public static final int BARRING_TYPE_NONE = android.hardware.radio.V1_5.BarringType.NONE;
+        /** The service is barred */
+        public static final int BARRING_TYPE_UNCONDITIONAL =
+                android.hardware.radio.V1_5.BarringType.UNCONDITIONAL;
+        /** The service may be barred based on additional factors */
+        public static final int BARRING_TYPE_CONDITIONAL =
+                android.hardware.radio.V1_5.BarringType.CONDITIONAL;
+
+        /** If a modem does not report barring info, then the barring type will be UNKNOWN */
+        public static final int BARRING_TYPE_UNKNOWN = -1;
+
+        private final @BarringType int mBarringType;
+
+        private final boolean mIsConditionallyBarred;
+        private final int mConditionalBarringFactor;
+        private final int mConditionalBarringTimeSeconds;
+
+        /** @hide */
+        public BarringServiceInfo(@BarringType int type) {
+            this(type, false, 0, 0);
+        }
+
+        /** @hide */
+        @TestApi
+        public BarringServiceInfo(@BarringType int barringType, boolean isConditionallyBarred,
+                int conditionalBarringFactor, int conditionalBarringTimeSeconds) {
+            mBarringType = barringType;
+            mIsConditionallyBarred = isConditionallyBarred;
+            mConditionalBarringFactor = conditionalBarringFactor;
+            mConditionalBarringTimeSeconds = conditionalBarringTimeSeconds;
+        }
+
+        public @BarringType int getBarringType() {
+            return mBarringType;
+        }
+
+        /**
+         * @return true if the conditional barring parameters have resulted in the service being
+         *         barred; false if the service has either not been evaluated for conditional
+         *         barring or has been evaluated and isn't barred.
+         */
+        public boolean isConditionallyBarred() {
+            return mIsConditionallyBarred;
+        }
+
+        /**
+         * @return the conditional barring factor as a percentage 0-100, which is the probability of
+         *         a random device being barred for the service type.
+         */
+        public int getConditionalBarringFactor() {
+            return mConditionalBarringFactor;
+        }
+
+        /**
+         * @return the conditional barring time seconds, which is the interval between successive
+         *         evaluations for conditional barring based on the barring factor.
+         */
+        @SuppressLint("MethodNameUnits")
+        public int getConditionalBarringTimeSeconds() {
+            return mConditionalBarringTimeSeconds;
+        }
+
+        /**
+         * Return whether a service is currently barred based on the BarringInfo
+         *
+         * @return true if the service is currently being barred, otherwise false
+         */
+        public boolean isBarred() {
+            return mBarringType == BarringServiceInfo.BARRING_TYPE_UNCONDITIONAL
+                    || (mBarringType == BarringServiceInfo.BARRING_TYPE_CONDITIONAL
+                            && mIsConditionallyBarred);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mBarringType, mIsConditionallyBarred,
+                    mConditionalBarringFactor, mConditionalBarringTimeSeconds);
+        }
+
+        @Override
+        public boolean equals(Object rhs) {
+            if (!(rhs instanceof BarringServiceInfo)) return false;
+
+            BarringServiceInfo other = (BarringServiceInfo) rhs;
+            return mBarringType == other.mBarringType
+                    && mIsConditionallyBarred == other.mIsConditionallyBarred
+                    && mConditionalBarringFactor == other.mConditionalBarringFactor
+                    && mConditionalBarringTimeSeconds == other.mConditionalBarringTimeSeconds;
+        }
+
+        /** @hide */
+        public BarringServiceInfo(Parcel p) {
+            mBarringType = p.readInt();
+            mIsConditionallyBarred = p.readBoolean();
+            mConditionalBarringFactor = p.readInt();
+            mConditionalBarringTimeSeconds = p.readInt();
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeInt(mBarringType);
+            dest.writeBoolean(mIsConditionallyBarred);
+            dest.writeInt(mConditionalBarringFactor);
+            dest.writeInt(mConditionalBarringTimeSeconds);
+        }
+
+        /* @inheritDoc */
+        public static final @NonNull Parcelable.Creator<BarringServiceInfo> CREATOR =
+                new Parcelable.Creator<BarringServiceInfo>() {
+                    @Override
+                    public BarringServiceInfo createFromParcel(Parcel source) {
+                        return new BarringServiceInfo(source);
+                    }
+
+                    @Override
+                    public BarringServiceInfo[] newArray(int size) {
+                        return new BarringServiceInfo[size];
+                    }
+                };
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+    }
+
+    private CellIdentity mCellIdentity;
+
+    // A SparseArray potentially mapping each BarringService type to a BarringServiceInfo config
+    // that describes the current barring status of that particular service.
+    private SparseArray<BarringServiceInfo> mBarringServiceInfos;
+
+    /** @hide */
+    @TestApi
+    @SystemApi
+    public BarringInfo() {
+        mBarringServiceInfos = new SparseArray<>();
+    }
+
+    /**
+     * Constructor for new BarringInfo instances.
+     *
+     * @hide
+     */
+    @TestApi
+    public BarringInfo(@Nullable CellIdentity barringCellId,
+            @NonNull SparseArray<BarringServiceInfo> barringServiceInfos) {
+        mCellIdentity = barringCellId;
+        mBarringServiceInfos = barringServiceInfos;
+    }
+
+    /** @hide */
+    public static BarringInfo create(
+            @NonNull android.hardware.radio.V1_5.CellIdentity halBarringCellId,
+            @NonNull List<android.hardware.radio.V1_5.BarringInfo> halBarringInfos) {
+        CellIdentity ci = CellIdentity.create(halBarringCellId);
+        SparseArray<BarringServiceInfo> serviceInfos = new SparseArray<>();
+
+        for (android.hardware.radio.V1_5.BarringInfo halBarringInfo : halBarringInfos) {
+            if (halBarringInfo.type == android.hardware.radio.V1_5.BarringType.CONDITIONAL) {
+                if (halBarringInfo.typeSpecificInfo.getDiscriminator()
+                        != android.hardware.radio.V1_5.BarringTypeSpecificInfo
+                                .hidl_discriminator.conditionalBarringInfo) {
+                    // this is an error case where the barring info is conditional but the
+                    // conditional barring fields weren't included
+                    continue;
+                }
+                android.hardware.radio.V1_5.ConditionalBarringInfo conditionalInfo =
+                        halBarringInfo.typeSpecificInfo.conditionalBarringInfo();
+                serviceInfos.put(
+                        halBarringInfo.service, new BarringServiceInfo(
+                                halBarringInfo.type, // will always be CONDITIONAL here
+                                conditionalInfo.isBarred,
+                                conditionalInfo.barringFactor,
+                                conditionalInfo.barringTimeSeconds));
+            } else {
+                // Barring type is either NONE or UNCONDITIONAL
+                serviceInfos.put(
+                        halBarringInfo.service, new BarringServiceInfo(halBarringInfo.type,
+                                false, 0, 0));
+            }
+        }
+        return new BarringInfo(ci, serviceInfos);
+    }
+
+    /**
+     * Return whether a service is currently barred based on the BarringInfo
+     *
+     * @param service the service to be checked.
+     * @return true if the service is currently being barred, otherwise false
+     */
+    public boolean isServiceBarred(@BarringServiceType int service) {
+        BarringServiceInfo bsi = mBarringServiceInfos.get(service);
+        return bsi != null && (bsi.isBarred());
+    }
+
+    /**
+     * Get the BarringServiceInfo for a specified service.
+     *
+     * @return a BarringServiceInfo struct describing the current barring status for a service
+     */
+    public @NonNull BarringServiceInfo getBarringServiceInfo(@BarringServiceType int service) {
+        BarringServiceInfo bsi = mBarringServiceInfos.get(service);
+        // If barring is reported but not for a particular service, then we report the barring
+        // type as UNKNOWN; if the modem reports barring info but doesn't report for a particular
+        // service then we can safely assume that the service isn't barred (for instance because
+        // that particular service isn't applicable to the current RAN).
+        return (bsi != null) ? bsi : new BarringServiceInfo(
+                mBarringServiceInfos.size() > 0 ? BarringServiceInfo.BARRING_TYPE_NONE :
+                        BarringServiceInfo.BARRING_TYPE_UNKNOWN);
+    }
+
+    /** @hide */
+    @SystemApi
+    public @NonNull BarringInfo createLocationInfoSanitizedCopy() {
+        return new BarringInfo(mCellIdentity.sanitizeLocationInfo(), mBarringServiceInfos);
+    }
+
+    /** @hide */
+    public BarringInfo(Parcel p) {
+        mCellIdentity = p.readParcelable(CellIdentity.class.getClassLoader());
+        mBarringServiceInfos = p.readSparseArray(BarringServiceInfo.class.getClassLoader());
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeParcelable(mCellIdentity, flags);
+        dest.writeSparseArray(mBarringServiceInfos);
+    }
+
+    public static final @NonNull Parcelable.Creator<BarringInfo> CREATOR =
+            new Parcelable.Creator<BarringInfo>() {
+                @Override
+                public BarringInfo createFromParcel(Parcel source) {
+                    return new BarringInfo(source);
+                }
+
+                @Override
+                public BarringInfo[] newArray(int size) {
+                    return new BarringInfo[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = mCellIdentity != null ? mCellIdentity.hashCode() : 7;
+        for (int i = 0; i < mBarringServiceInfos.size(); i++) {
+            hash = hash + 15 * mBarringServiceInfos.keyAt(i);
+            hash = hash + 31 * mBarringServiceInfos.valueAt(i).hashCode();
+        }
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object rhs) {
+        if (!(rhs instanceof BarringInfo)) return false;
+
+        BarringInfo bi = (BarringInfo) rhs;
+
+        if (hashCode() != bi.hashCode()) return false;
+
+        if (mBarringServiceInfos.size() != bi.mBarringServiceInfos.size()) return false;
+
+        for (int i = 0; i < mBarringServiceInfos.size(); i++) {
+            if (mBarringServiceInfos.keyAt(i) != bi.mBarringServiceInfos.keyAt(i)) return false;
+            if (!Objects.equals(mBarringServiceInfos.valueAt(i),
+                        bi.mBarringServiceInfos.valueAt(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "BarringInfo {mCellIdentity=" + mCellIdentity
+               + ", mBarringServiceInfos=" + mBarringServiceInfos + "}";
+    }
+}
diff --git a/telephony/java/android/telephony/CallQuality.java b/telephony/java/android/telephony/CallQuality.java
index e01deb2..1e1cdba 100644
--- a/telephony/java/android/telephony/CallQuality.java
+++ b/telephony/java/android/telephony/CallQuality.java
@@ -80,6 +80,9 @@
     private int mMaxRelativeJitter;
     private int mAverageRoundTripTime;
     private int mCodecType;
+    private boolean mRtpInactivityDetected;
+    private boolean mRxSilenceDetected;
+    private boolean mTxSilenceDetected;
 
     /** @hide **/
     public CallQuality(Parcel in) {
@@ -94,6 +97,9 @@
         mMaxRelativeJitter = in.readInt();
         mAverageRoundTripTime = in.readInt();
         mCodecType = in.readInt();
+        mRtpInactivityDetected = in.readBoolean();
+        mRxSilenceDetected = in.readBoolean();
+        mTxSilenceDetected = in.readBoolean();
     }
 
     /** @hide **/
@@ -109,7 +115,7 @@
      * @param numRtpPacketsReceived RTP packets received from network
      * @param numRtpPacketsTransmittedLost RTP packets which were lost in network and never
      * transmitted
-     * @param numRtpPacketsNotReceived RTP packets which were lost in network and never recieved
+     * @param numRtpPacketsNotReceived RTP packets which were lost in network and never received
      * @param averageRelativeJitter average relative jitter in milliseconds
      * @param maxRelativeJitter maximum relative jitter in milliseconds
      * @param averageRoundTripTime average round trip delay in milliseconds
@@ -127,6 +133,48 @@
             int maxRelativeJitter,
             int averageRoundTripTime,
             int codecType) {
+        this(downlinkCallQualityLevel, uplinkCallQualityLevel, callDuration,
+            numRtpPacketsTransmitted, numRtpPacketsReceived, numRtpPacketsTransmittedLost,
+            numRtpPacketsNotReceived, averageRelativeJitter, maxRelativeJitter,
+            averageRoundTripTime, codecType, false, false, false);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param callQualityLevel the call quality level (see #CallQualityLevel)
+     * @param callDuration the call duration in milliseconds
+     * @param numRtpPacketsTransmitted RTP packets sent to network
+     * @param numRtpPacketsReceived RTP packets received from network
+     * @param numRtpPacketsTransmittedLost RTP packets which were lost in network and never
+     * transmitted
+     * @param numRtpPacketsNotReceived RTP packets which were lost in network and never received
+     * @param averageRelativeJitter average relative jitter in milliseconds
+     * @param maxRelativeJitter maximum relative jitter in milliseconds
+     * @param averageRoundTripTime average round trip delay in milliseconds
+     * @param codecType the codec type
+     * @param rtpInactivityDetected True if no incoming RTP is received for a continuous duration of
+     * 4 seconds
+     * @param rxSilenceDetected True if only silence RTP packets are received for 20 seconds
+     * immediately after call is connected
+     * @param txSilenceDetected True if only silence RTP packets are sent for 20 seconds immediately
+     * after call is connected
+     */
+    public CallQuality(
+            @CallQualityLevel int downlinkCallQualityLevel,
+            @CallQualityLevel int uplinkCallQualityLevel,
+            int callDuration,
+            int numRtpPacketsTransmitted,
+            int numRtpPacketsReceived,
+            int numRtpPacketsTransmittedLost,
+            int numRtpPacketsNotReceived,
+            int averageRelativeJitter,
+            int maxRelativeJitter,
+            int averageRoundTripTime,
+            int codecType,
+            boolean rtpInactivityDetected,
+            boolean rxSilenceDetected,
+            boolean txSilenceDetected) {
         this.mDownlinkCallQualityLevel = downlinkCallQualityLevel;
         this.mUplinkCallQualityLevel = uplinkCallQualityLevel;
         this.mCallDuration = callDuration;
@@ -138,6 +186,9 @@
         this.mMaxRelativeJitter = maxRelativeJitter;
         this.mAverageRoundTripTime = averageRoundTripTime;
         this.mCodecType = codecType;
+        this.mRtpInactivityDetected = rtpInactivityDetected;
+        this.mRxSilenceDetected = rxSilenceDetected;
+        this.mTxSilenceDetected = txSilenceDetected;
     }
 
     // getters
@@ -226,6 +277,29 @@
     }
 
     /**
+     * Returns true if no rtp packets are received continuously for the last 4 seconds
+     */
+    public boolean isRtpInactivityDetected() {
+        return mRtpInactivityDetected;
+    }
+
+    /**
+     * Returns true if only silence rtp packets are received for a duration of 20 seconds starting
+     * at call setup
+     */
+    public boolean isIncomingSilenceDetected() {
+        return mRxSilenceDetected;
+    }
+
+    /**
+      * Returns true if only silence rtp packets are sent for a duration of 20 seconds starting at
+      * call setup
+      */
+    public boolean isOutgoingSilenceDetected() {
+        return mTxSilenceDetected;
+    }
+
+    /**
      * Returns the codec type. This value corresponds to the AUDIO_QUALITY_* constants in
      * {@link ImsStreamMediaProfile}.
      *
@@ -270,6 +344,9 @@
                 + " maxRelativeJitter=" + mMaxRelativeJitter
                 + " averageRoundTripTime=" + mAverageRoundTripTime
                 + " codecType=" + mCodecType
+                + " rtpInactivityDetected=" + mRtpInactivityDetected
+                + " txSilenceDetected=" + mRxSilenceDetected
+                + " rxSilenceDetected=" + mTxSilenceDetected
                 + "}";
     }
 
@@ -286,7 +363,10 @@
                 mAverageRelativeJitter,
                 mMaxRelativeJitter,
                 mAverageRoundTripTime,
-                mCodecType);
+                mCodecType,
+                mRtpInactivityDetected,
+                mRxSilenceDetected,
+                mTxSilenceDetected);
     }
 
     @Override
@@ -311,7 +391,10 @@
                 && mAverageRelativeJitter == s.mAverageRelativeJitter
                 && mMaxRelativeJitter == s.mMaxRelativeJitter
                 && mAverageRoundTripTime == s.mAverageRoundTripTime
-                && mCodecType == s.mCodecType);
+                && mCodecType == s.mCodecType
+                && mRtpInactivityDetected == s.mRtpInactivityDetected
+                && mRxSilenceDetected == s.mRxSilenceDetected
+                && mTxSilenceDetected == s.mTxSilenceDetected);
     }
 
     /**
@@ -336,6 +419,9 @@
         dest.writeInt(mMaxRelativeJitter);
         dest.writeInt(mAverageRoundTripTime);
         dest.writeInt(mCodecType);
+        dest.writeBoolean(mRtpInactivityDetected);
+        dest.writeBoolean(mRxSilenceDetected);
+        dest.writeBoolean(mTxSilenceDetected);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<CallQuality> CREATOR = new Parcelable.Creator() {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index fdf8849..b30f586 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -17,6 +17,7 @@
 package android.telephony;
 
 import android.Manifest;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -24,17 +25,18 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
+import android.net.ipsec.ike.SaProposal;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.service.carrier.CarrierService;
 import android.telecom.TelecomManager;
 import android.telephony.ims.ImsReasonInfo;
 
 import com.android.internal.telephony.ICarrierConfigLoader;
+import com.android.telephony.Rlog;
 
 /**
  * Provides access to telephony configuration values that are carrier-specific.
@@ -299,7 +301,6 @@
 
     /**
      * A string array containing numbers that shouldn't be included in the call log.
-     * @hide
      */
     public static final String KEY_UNLOGGABLE_NUMBERS_STRING_ARRAY =
             "unloggable_numbers_string_array";
@@ -312,12 +313,11 @@
             KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL = "hide_carrier_network_settings_bool";
 
     /**
-     * Do only allow auto selection in Advanced Network Settings when in home network.
+     * Only allow auto selection in Advanced Network Settings when in home network.
      * Manual selection is allowed when in roaming network.
-     * @hide
      */
-    public static final String
-            KEY_ONLY_AUTO_SELECT_IN_HOME_NETWORK_BOOL = "only_auto_select_in_home_network";
+    public static final String KEY_ONLY_AUTO_SELECT_IN_HOME_NETWORK_BOOL =
+            "only_auto_select_in_home_network";
 
     /**
      * Control whether users receive a simplified network settings UI and improved network
@@ -581,9 +581,6 @@
      * registration state to change.  That is, turning on or off mobile data will not cause VT to be
      * enabled or disabled.
      * When {@code false}, disabling mobile data will cause VT to be de-registered.
-     * <p>
-     * See also {@link #KEY_VILTE_DATA_IS_METERED_BOOL}.
-     * @hide
      */
     public static final String KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS =
             "ignore_data_enabled_changed_for_video_calls";
@@ -647,7 +644,6 @@
     /**
      * Default WFC_IMS_enabled: true VoWiFi by default is on
      *                          false VoWiFi by default is off
-     * @hide
      */
     public static final String KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL =
             "carrier_default_wfc_ims_enabled_bool";
@@ -671,6 +667,12 @@
             "carrier_promote_wfc_on_call_fail_bool";
 
     /**
+     * Flag specifying whether provisioning is required for RCS.
+     */
+    public static final String KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL =
+            "carrier_rcs_provisioning_required_bool";
+
+    /**
      * Flag specifying whether provisioning is required for VoLTE, Video Telephony, and WiFi
      * Calling.
      */
@@ -713,9 +715,7 @@
      *
      * As of now, Verizon is the only carrier enforcing this dependency in their
      * WFC awareness and activation requirements.
-     *
-     * @hide
-     *  */
+     */
     public static final String KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL
             = "carrier_volte_override_wfc_provisioning_bool";
 
@@ -847,9 +847,12 @@
             "carrier_force_disable_etws_cmas_test_bool";
 
     /**
-     * The default flag specifying whether "Turn on Notifications" option will be always shown in
-     * Settings->More->Emergency broadcasts menu regardless developer options is turned on or not.
+     * The default flag specifying whether "Allow alerts" option will be always shown in
+     * emergency alerts settings regardless developer options is turned on or not.
+     *
+     * @deprecated The allow alerts option is always shown now. No longer need a config for that.
      */
+    @Deprecated
     public static final String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL =
             "always_show_emergency_alert_onoff_bool";
 
@@ -1073,11 +1076,18 @@
      *
      * When {@code false}, the old behavior is used, where the toggle in accessibility settings is
      * used to set the IMS stack's RTT enabled state.
-     * @hide
      */
     public static final String KEY_IGNORE_RTT_MODE_SETTING_BOOL =
             "ignore_rtt_mode_setting_bool";
 
+
+    /**
+     * Determines whether adhoc conference calls are supported by a carrier.  When {@code true},
+     * adhoc conference calling is supported, {@code false otherwise}.
+     */
+    public static final String KEY_SUPPORT_ADHOC_CONFERENCE_CALLS_BOOL =
+            "support_adhoc_conference_calls_bool";
+
     /**
      * Determines whether conference calls are supported by a carrier.  When {@code true},
      * conference calling is supported, {@code false otherwise}.
@@ -1114,7 +1124,6 @@
      * Determines whether the IMS conference merge process supports and returns its participants
      * data. When {@code true}, on merge complete, conference call would have a list of its
      * participants returned in XML format, {@code false otherwise}.
-     * @hide
      */
     public static final String KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL =
             "support_ims_conference_event_package_bool";
@@ -1187,20 +1196,18 @@
     public static final String KEY_ENABLE_APPS_STRING_ARRAY = "enable_apps_string_array";
 
     /**
-     * Determine whether user can switch Wi-Fi preferred or Cellular preferred in calling preference.
+     * Determine whether user can switch Wi-Fi preferred or Cellular preferred
+     * in calling preference.
      * Some operators support Wi-Fi Calling only, not VoLTE.
      * They don't need "Cellular preferred" option.
-     * In this case, set uneditalbe attribute for preferred preference.
-     * @hide
+     * In this case, set uneditable attribute for preferred preference.
      */
     public static final String KEY_EDITABLE_WFC_MODE_BOOL = "editable_wfc_mode_bool";
 
-     /**
-      * Flag to indicate if Wi-Fi needs to be disabled in ECBM
-      * @hide
-      **/
-     public static final String
-              KEY_CONFIG_WIFI_DISABLE_IN_ECBM = "config_wifi_disable_in_ecbm";
+    /**
+     * Flag to indicate if Wi-Fi needs to be disabled in ECBM.
+     */
+    public static final String KEY_CONFIG_WIFI_DISABLE_IN_ECBM = "config_wifi_disable_in_ecbm";
 
     /**
      * List operator-specific error codes and indices of corresponding error strings in
@@ -1264,9 +1271,8 @@
     public static final String KEY_WFC_SPN_USE_ROOT_LOCALE = "wfc_spn_use_root_locale";
 
     /**
-     * The Component Name of the activity that can setup the emergency addrees for WiFi Calling
+     * The Component Name of the activity that can setup the emergency address for WiFi Calling
      * as per carrier requirement.
-     * @hide
      */
      public static final String KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING =
             "wfc_emergency_address_carrier_app_string";
@@ -1430,27 +1436,68 @@
     public static final String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool";
 
     /**
-     * APN types that user is not allowed to modify
-     * @hide
+     * APN types that user is not allowed to modify.
      */
     public static final String KEY_READ_ONLY_APN_TYPES_STRING_ARRAY =
             "read_only_apn_types_string_array";
 
     /**
-     * APN fields that user is not allowed to modify
-     * @hide
+     * APN fields that user is not allowed to modify.
      */
     public static final String KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY =
             "read_only_apn_fields_string_array";
 
     /**
      * Default value of APN types field if not specified by user when adding/modifying an APN.
-     * @hide
      */
     public static final String KEY_APN_SETTINGS_DEFAULT_APN_TYPES_STRING_ARRAY =
             "apn_settings_default_apn_types_string_array";
 
     /**
+     * Configs used for APN setup.
+     */
+    public static final class Apn {
+        /** Prefix of all Apn.KEY_* constants. */
+        public static final String KEY_PREFIX = "apn.";
+
+        /** IPv4 internet protocol */
+        public static final String PROTOCOL_IPV4 = "IP";
+        /** IPv6 internet protocol */
+        public static final String PROTOCOL_IPV6 = "IPV6";
+        /** IPv4 or IPv6 internet protocol */
+        public static final String PROTOCOL_IPV4V6 = "IPV4V6";
+
+        /**
+         * Default value of APN protocol field if not specified by user when adding/modifying
+         * an APN.
+         *
+         * Available options are: {@link #PROTOCOL_IPV4}, {@link #PROTOCOL_IPV6},
+         * {@link #PROTOCOL_IPV4V6}
+         */
+        public static final String KEY_SETTINGS_DEFAULT_PROTOCOL_STRING =
+                KEY_PREFIX + "settings_default_protocol_string";
+
+        /**
+         * Default value of APN roaming protocol field if not specified by user when
+         * adding/modifying an APN.
+         *
+         * Available options are: {@link #PROTOCOL_IPV4}, {@link #PROTOCOL_IPV6},
+         * {@link #PROTOCOL_IPV4V6}
+         */
+        public static final String KEY_SETTINGS_DEFAULT_ROAMING_PROTOCOL_STRING =
+                KEY_PREFIX + "settings_default_roaming_protocol_string";
+
+        private Apn() {}
+
+        private static PersistableBundle getDefaults() {
+            PersistableBundle defaults = new PersistableBundle();
+            defaults.putString(KEY_SETTINGS_DEFAULT_PROTOCOL_STRING, "");
+            defaults.putString(KEY_SETTINGS_DEFAULT_ROAMING_PROTOCOL_STRING, "");
+            return defaults;
+        }
+    }
+
+    /**
      * Boolean indicating if intent for emergency call state changes should be broadcast
      * @hide
      */
@@ -1476,29 +1523,25 @@
             "hide_digits_helper_text_on_stk_input_screen_bool";
 
     /**
-     * Boolean indicating if show data RAT icon on status bar even when data is disabled
-     * @hide
+     * Boolean indicating if show data RAT icon on status bar even when data is disabled.
      */
     public static final String KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL =
             "always_show_data_rat_icon_bool";
 
     /**
-     * Boolean indicating if default data account should show LTE or 4G icon
-     * @hide
+     * Boolean indicating if default data account should show LTE or 4G icon.
      */
     public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL =
             "show_4g_for_lte_data_icon_bool";
 
     /**
      * Boolean indicating if default data account should show 4G icon when in 3G.
-     * @hide
      */
     public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL =
             "show_4g_for_3g_data_icon_bool";
 
     /**
-     * Boolean indicating if lte+ icon should be shown if available
-     * @hide
+     * Boolean indicating if LTE+ icon should be shown if available.
      */
     public static final String KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL =
             "hide_lte_plus_data_icon_bool";
@@ -1513,10 +1556,8 @@
             "operator_name_filter_pattern_string";
 
     /**
-     * The string is used to compare with operator name. If it matches the pattern then show
-     * specific data icon.
-     *
-     * @hide
+     * The string is used to compare with operator name.
+     * If it matches the pattern then show specific data icon.
      */
     public static final String KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING =
             "show_carrier_data_icon_pattern_string";
@@ -1529,33 +1570,28 @@
             "show_precise_failed_cause_bool";
 
     /**
-     * Boolean to decide whether lte is enabled.
-     * @hide
+     * Boolean to decide whether LTE is enabled.
      */
     public static final String KEY_LTE_ENABLED_BOOL = "lte_enabled_bool";
 
     /**
      * Boolean to decide whether TD-SCDMA is supported.
-     * @hide
      */
     public static final String KEY_SUPPORT_TDSCDMA_BOOL = "support_tdscdma_bool";
 
     /**
      * A list of mcc/mnc that support TD-SCDMA for device when connect to the roaming network.
-     * @hide
      */
     public static final String KEY_SUPPORT_TDSCDMA_ROAMING_NETWORKS_STRING_ARRAY =
             "support_tdscdma_roaming_networks_string_array";
 
     /**
      * Boolean to decide whether world mode is enabled.
-     * @hide
      */
     public static final String KEY_WORLD_MODE_ENABLED_BOOL = "world_mode_enabled_bool";
 
     /**
      * Flatten {@link android.content.ComponentName} of the carrier's settings activity.
-     * @hide
      */
     public static final String KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING =
             "carrier_settings_activity_component_name_string";
@@ -1593,7 +1629,10 @@
     public static final String KEY_MMS_UA_PROF_TAG_NAME_STRING = "uaProfTagName";
     public static final String KEY_MMS_UA_PROF_URL_STRING = "uaProfUrl";
     public static final String KEY_MMS_USER_AGENT_STRING = "userAgent";
-    /** @hide */
+    /**
+     * If true, add "Connection: close" header to MMS HTTP requests so the connection
+     * is immediately closed (disabling keep-alive).
+     */
     public static final String KEY_MMS_CLOSE_CONNECTION_BOOL = "mmsCloseConnection";
 
     /**
@@ -1609,25 +1648,23 @@
     /**
      * Defines carrier-specific actions which act upon
      * com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED, used for customization of the
-     * default carrier app
+     * default carrier app.
      * Format: "CARRIER_ACTION_IDX, ..."
      * Where {@code CARRIER_ACTION_IDX} is an integer defined in
-     * {@link com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils}
+     * com.android.carrierdefaultapp.CarrierActionUtils
      * Example:
-     * {@link com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_DISABLE_METERED_APNS
-     * disable_metered_apns}
-     * @hide
+     * com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_DISABLE_METERED_APNS
+     * disables metered APNs
      */
-    @UnsupportedAppUsage
+    @SuppressLint("IntentName")
     public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY =
             "carrier_default_actions_on_redirection_string_array";
 
     /**
-     * Defines carrier-specific actions which act upon
-     * com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED
+     * Defines carrier-specific actions which act upon CARRIER_SIGNAL_REQUEST_NETWORK_FAILED
      * and configured signal args:
-     * {@link com.android.internal.telephony.TelephonyIntents#EXTRA_APN_TYPE_KEY apnType},
-     * {@link com.android.internal.telephony.TelephonyIntents#EXTRA_ERROR_CODE_KEY errorCode}
+     * android.telephony.TelephonyManager#EXTRA_APN_TYPE,
+     * android.telephony.TelephonyManager#EXTRA_ERROR_CODE
      * used for customization of the default carrier app
      * Format:
      * {
@@ -1635,42 +1672,41 @@
      *     "APN_1, ERROR_CODE_2 : CARRIER_ACTION_IDX_1 "
      * }
      * Where {@code APN_1} is a string defined in
-     * {@link com.android.internal.telephony.PhoneConstants PhoneConstants}
+     * com.android.internal.telephony.PhoneConstants
      * Example: "default"
      *
-     * {@code ERROR_CODE_1} is an integer defined in
-     * {@link DataFailCause DcFailure}
+     * {@code ERROR_CODE_1} is an integer defined in android.telephony.DataFailCause
      * Example:
-     * {@link DataFailCause#MISSING_UNKNOWN_APN}
+     * android.telephony.DataFailCause#MISSING_UNKNOWN_APN
      *
      * {@code CARRIER_ACTION_IDX_1} is an integer defined in
-     * {@link com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils}
+     * com.android.carrierdefaultapp.CarrierActionUtils
      * Example:
-     * {@link com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_DISABLE_METERED_APNS}
-     * @hide
+     * com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_DISABLE_METERED_APNS
+     * disables metered APNs
      */
+    @SuppressLint("IntentName")
     public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DCFAILURE_STRING_ARRAY =
             "carrier_default_actions_on_dcfailure_string_array";
 
     /**
-     * Defines carrier-specific actions which act upon
-     * com.android.internal.telephony.CARRIER_SIGNAL_RESET, used for customization of the
-     * default carrier app
+     * Defines carrier-specific actions which act upon CARRIER_SIGNAL_RESET,
+     * used for customization of the default carrier app.
      * Format: "CARRIER_ACTION_IDX, ..."
      * Where {@code CARRIER_ACTION_IDX} is an integer defined in
-     * {@link com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils}
+     * com.android.carrierdefaultapp.CarrierActionUtils
      * Example:
-     * {@link com.android.carrierdefaultapp.CarrierActionUtils
-     * #CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS clear all notifications on reset}
-     * @hide
+     * com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS
+     * clears all notifications on reset
      */
+    @SuppressLint("IntentName")
     public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_RESET =
             "carrier_default_actions_on_reset_string_array";
 
     /**
      * Defines carrier-specific actions which act upon
      * com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE,
-     * used for customization of the default carrier app
+     * used for customization of the default carrier app.
      * Format:
      * {
      *     "true : CARRIER_ACTION_IDX_1",
@@ -1678,17 +1714,17 @@
      * }
      * Where {@code true} is a boolean indicates default network available/unavailable
      * Where {@code CARRIER_ACTION_IDX} is an integer defined in
-     * {@link com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils}
+     * com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils
      * Example:
-     * {@link com.android.carrierdefaultapp.CarrierActionUtils
-     * #CARRIER_ACTION_ENABLE_DEFAULT_URL_HANDLER enable the app as the default URL handler}
-     * @hide
+     * com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_ENABLE_DEFAULT_URL_HANDLER
+     * enables the app as the default URL handler
      */
+    @SuppressLint("IntentName")
     public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE =
             "carrier_default_actions_on_default_network_available_string_array";
+
     /**
-     * Defines a list of acceptable redirection url for default carrier app
-     * @hides
+     * Defines a list of acceptable redirection url for default carrier app.
      */
     public static final String KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY =
             "carrier_default_redirection_url_string_array";
@@ -1816,10 +1852,10 @@
 
     /**
      * Determines whether to enable enhanced call blocking feature on the device.
-     * @see SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED
-     * @see SystemContract#ENHANCED_SETTING_KEY_BLOCK_PRIVATE
-     * @see SystemContract#ENHANCED_SETTING_KEY_BLOCK_PAYPHONE
-     * @see SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNKNOWN
+     * android.provider.BlockedNumberContract.SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED
+     * android.provider.BlockedNumberContract.SystemContract#ENHANCED_SETTING_KEY_BLOCK_PRIVATE
+     * android.provider.BlockedNumberContract.SystemContract#ENHANCED_SETTING_KEY_BLOCK_PAYPHONE
+     * android.provider.BlockedNumberContract.SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNKNOWN
      *
      * <p>
      * 1. For Single SIM(SS) device, it can be customized in both carrier_config_mccmnc.xml
@@ -1829,7 +1865,6 @@
      *    function is used regardless of SIM.
      * <p>
      * If {@code true} enable enhanced call blocking feature on the device, {@code false} otherwise.
-     * @hide
      */
     public static final String KEY_SUPPORT_ENHANCED_CALL_BLOCKING_BOOL =
             "support_enhanced_call_blocking_bool";
@@ -1940,7 +1975,6 @@
 
     /**
      * Flag indicating whether the carrier supports call deflection for an incoming IMS call.
-     * @hide
      */
     public static final String KEY_CARRIER_ALLOW_DEFLECT_IMS_CALL_BOOL =
             "carrier_allow_deflect_ims_call_bool";
@@ -2005,8 +2039,6 @@
     /**
      * Whether system apps are allowed to use fallback if carrier video call is not available.
      * Defaults to {@code true}.
-     *
-     * @hide
      */
     public static final String KEY_ALLOW_VIDEO_CALLING_FALLBACK_BOOL =
             "allow_video_calling_fallback_bool";
@@ -2044,9 +2076,8 @@
             "enhanced_4g_lte_title_variant_bool";
 
     /**
-     * The index indicates the carrier specified title string of Enahnce 4G LTE Mode settings.
+     * The index indicates the carrier specified title string of Enhanced 4G LTE Mode settings.
      * Default value is 0, which indicates the default title string.
-     * @hide
      */
     public static final String KEY_ENHANCED_4G_LTE_TITLE_VARIANT_INT =
             "enhanced_4g_lte_title_variant_int";
@@ -2090,15 +2121,13 @@
      *                 {@link #KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL} is false. If
      *                 {@link #KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL} is true, this
      *                 configuration is ignored and roaming preference cannot be changed.
-     * @hide
      */
     public static final String KEY_EDITABLE_WFC_ROAMING_MODE_BOOL =
             "editable_wfc_roaming_mode_bool";
 
     /**
-     * Flag specifying wether to show blocking pay phone option in blocked numbers screen. Only show
-     * the option if payphone call presentation represents in the carrier's region.
-     * @hide
+     * Flag specifying whether to show blocking pay phone option in blocked numbers screen.
+     * Only show the option if payphone call presentation is present in the carrier's region.
      */
     public static final java.lang.String KEY_SHOW_BLOCKING_PAY_PHONE_OPTION_BOOL =
             "show_blocking_pay_phone_option_bool";
@@ -2108,7 +2137,6 @@
      * {@code false} - roaming preference can be selected separately from the home preference.
      * {@code true}  - roaming preference is the same as home preference and
      *                 {@link #KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT} is used as the default value.
-     * @hide
      */
     public static final String KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL =
             "use_wfc_home_network_mode_in_roaming_network_bool";
@@ -2136,7 +2164,6 @@
      * while the device is registered over WFC. Default value is -1, which indicates
      * that this notification is not pertinent for a particular carrier. We've added a delay
      * to prevent false positives.
-     * @hide
      */
     public static final String KEY_EMERGENCY_NOTIFICATION_DELAY_INT =
             "emergency_notification_delay_int";
@@ -2187,7 +2214,6 @@
     /**
      * Flag specifying whether to show an alert dialog for video call charges.
      * By default this value is {@code false}.
-     * @hide
      */
     public static final String KEY_SHOW_VIDEO_CALL_CHARGES_ALERT_DIALOG_BOOL =
             "show_video_call_charges_alert_dialog_bool";
@@ -2474,10 +2500,9 @@
     /**
      * Identifies if the key is available for WLAN or EPDG or both. The value is a bitmask.
      * 0 indicates that neither EPDG or WLAN is enabled.
-     * 1 indicates that key type {@link TelephonyManager#KEY_TYPE_EPDG} is enabled.
-     * 2 indicates that key type {@link TelephonyManager#KEY_TYPE_WLAN} is enabled.
+     * 1 indicates that key type TelephonyManager#KEY_TYPE_EPDG is enabled.
+     * 2 indicates that key type TelephonyManager#KEY_TYPE_WLAN is enabled.
      * 3 indicates that both are enabled.
-     * @hide
      */
     public static final String IMSI_KEY_AVAILABILITY_INT = "imsi_key_availability_int";
 
@@ -2495,7 +2520,6 @@
     /**
      * Flag specifying whether IMS registration state menu is shown in Status Info setting,
      * default to false.
-     * @hide
      */
     public static final String KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL =
             "show_ims_registration_status_bool";
@@ -2551,7 +2575,6 @@
 
     /**
      * The flag to disable the popup dialog which warns the user of data charges.
-     * @hide
      */
     public static final String KEY_DISABLE_CHARGE_INDICATION_BOOL =
             "disable_charge_indication_bool";
@@ -2616,15 +2639,13 @@
     /**
      * Determines whether any carrier has been identified and its specific config has been applied,
      * default to false.
-     * @hide
      */
     public static final String KEY_CARRIER_CONFIG_APPLIED_BOOL = "carrier_config_applied_bool";
 
     /**
      * Determines whether we should show a warning asking the user to check with their carrier
-     * on pricing when the user enabled data roaming.
+     * on pricing when the user enabled data roaming,
      * default to false.
-     * @hide
      */
     public static final String KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL =
             "check_pricing_with_carrier_data_roaming_bool";
@@ -2776,10 +2797,10 @@
      * Specifies a carrier-defined {@link android.telecom.CallRedirectionService} which Telecom
      * will bind to for outgoing calls.  An empty string indicates that no carrier-defined
      * {@link android.telecom.CallRedirectionService} is specified.
-     * @hide
      */
     public static final String KEY_CALL_REDIRECTION_SERVICE_COMPONENT_NAME_STRING =
             "call_redirection_service_component_name_string";
+
     /**
      * Support for the original string display of CDMA MO call.
      * By default, it is disabled.
@@ -2892,8 +2913,8 @@
             "call_waiting_service_class_int";
 
     /**
-     * This configuration allow the system UI to display different 5G icon for different 5G
-     * scenario.
+     * This configuration allows the system UI to display different 5G icons for different 5G
+     * scenarios.
      *
      * There are five 5G scenarios:
      * 1. connected_mmwave: device currently connected to 5G cell as the secondary cell and using
@@ -2910,24 +2931,22 @@
      *    5G cell as a secondary cell) but the use of 5G is restricted.
      *
      * The configured string contains multiple key-value pairs separated by comma. For each pair,
-     * the key and value is separated by a colon. The key is corresponded to a 5G status above and
+     * the key and value are separated by a colon. The key corresponds to a 5G status above and
      * the value is the icon name. Use "None" as the icon name if no icon should be shown in a
      * specific 5G scenario. If the scenario is "None", config can skip this key and value.
      *
      * Icon name options: "5G_Plus", "5G".
      *
      * Here is an example:
-     * UE want to display 5G_Plus icon for scenario#1, and 5G icon for scenario#2; otherwise no
+     * UE wants to display 5G_Plus icon for scenario#1, and 5G icon for scenario#2; otherwise not
      * define.
      * The configuration is: "connected_mmwave:5G_Plus,connected:5G"
-     *
-     * @hide
      */
     public static final String KEY_5G_ICON_CONFIGURATION_STRING =
             "5g_icon_configuration_string";
 
     /**
-     * Timeout in second for displaying 5G icon, default value is 0 which means the timer is
+     * Timeout in seconds for displaying 5G icon, default value is 0 which means the timer is
      * disabled.
      *
      * System UI will show the 5G icon and start a timer with the timeout from this config when the
@@ -2936,8 +2955,6 @@
      *
      * If 5G is reacquired during this timer, the timer is canceled and restarted when 5G is next
      * lost. Allows us to momentarily lose 5G without blinking the icon.
-     *
-     * @hide
      */
     public static final String KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT =
             "5g_icon_display_grace_period_sec_int";
@@ -3002,7 +3019,6 @@
     /**
      * Controls hysteresis time in milli seconds for which OpportunisticNetworkService
      * will wait before switching data from opportunistic network to primary network.
-     * @hide
      */
     public static final String KEY_OPPORTUNISTIC_NETWORK_DATA_SWITCH_EXIT_HYSTERESIS_TIME_LONG =
             "opportunistic_network_data_switch_exit_hysteresis_time_long";
@@ -3010,14 +3026,12 @@
     /**
      * Controls whether to do ping test before switching data to opportunistic network.
      * This carrier config is used to disable this feature.
-     * @hide
      */
     public static final String KEY_PING_TEST_BEFORE_DATA_SWITCH_BOOL =
             "ping_test_before_data_switch_bool";
 
     /**
      * Controls time in milliseconds until DcTracker reevaluates 5G connection state.
-     * @hide
      */
     public static final String KEY_5G_WATCHDOG_TIME_MS_LONG =
             "5g_watchdog_time_long";
@@ -3026,7 +3040,6 @@
      * if primary is out of service. This control only affects system or 1st party app
      * initiated data switch, but will not override data switch initiated by privileged carrier apps
      * This carrier config is used to disable this feature.
-     * @hide
      */
     public static final String KEY_SWITCH_DATA_TO_PRIMARY_IF_PRIMARY_IS_OOS_BOOL =
             "switch_data_to_primary_if_primary_is_oos_bool";
@@ -3038,7 +3051,6 @@
      * #KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSSNR_INT within
      * #KEY_OPPORTUNISTIC_NETWORK_PING_PONG_TIME_LONG of switching to opportunistic network,
      * it will be determined as ping pong situation by system app or 1st party app.
-     * @hide
      */
     public static final String KEY_OPPORTUNISTIC_NETWORK_PING_PONG_TIME_LONG =
             "opportunistic_network_ping_pong_time_long";
@@ -3052,7 +3064,6 @@
      * #KEY_OPPORTUNISTIC_NETWORK_PING_PONG_TIME_LONG.
      * If ping pong situation continuous #KEY_OPPORTUNISTIC_NETWORK_BACKOFF_TIME_LONG
      * will be added to previously determined hysteresis time.
-     * @hide
      */
     public static final String KEY_OPPORTUNISTIC_NETWORK_BACKOFF_TIME_LONG =
             "opportunistic_network_backoff_time_long";
@@ -3064,7 +3075,6 @@
      * continuous ping pong situation or not as described in
      * #KEY_OPPORTUNISTIC_NETWORK_PING_PONG_TIME_LONG and
      * #KEY_OPPORTUNISTIC_NETWORK_BACKOFF_TIME_LONG.
-     * @hide
      */
     public static final String KEY_OPPORTUNISTIC_NETWORK_MAX_BACKOFF_TIME_LONG =
             "opportunistic_network_max_backoff_time_long";
@@ -3102,8 +3112,6 @@
      * signal bar of primary network. By default it will be false, meaning whenever data
      * is going over opportunistic network, signal bar will reflect signal strength and rat
      * icon of that network.
-     *
-     * @hide
      */
     public static final String KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN =
             "always_show_primary_signal_bar_in_opportunistic_network_boolean";
@@ -3113,7 +3121,6 @@
      * validation result, this value defines customized value of how long we wait for validation
      * success before we fail and revoke the switch.
      * Time out is in milliseconds.
-     * @hide
      */
     public static final String KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG =
             "data_switch_validation_timeout_long";
@@ -3124,7 +3131,6 @@
      *   EAP-AKA: "0"
      *   EAP-SIM: "1"
      *   EAP-AKA_PRIME: "6"
-     * @hide
      */
     public static final String ENABLE_EAP_METHOD_PREFIX_BOOL = "enable_eap_method_prefix_bool";
 
@@ -3326,8 +3332,6 @@
 
     /**
      * Determines whether wifi calling location privacy policy is shown.
-     *
-     * @hide
      */
     public static final String KEY_SHOW_WFC_LOCATION_PRIVACY_POLICY_BOOL =
             "show_wfc_location_privacy_policy_bool";
@@ -3399,12 +3403,17 @@
         /** Prefix of all Ims.KEY_* constants. */
         public static final String KEY_PREFIX = "ims.";
 
-        //TODO: Add configs related to IMS.
+        /**
+         * Delay in milliseconds to turn off wifi when IMS is registered over wifi.
+         */
+        public static final String KEY_WIFI_OFF_DEFERRING_TIME_INT =
+                KEY_PREFIX + "wifi_off_deferring_time_int";
 
         private Ims() {}
 
         private static PersistableBundle getDefaults() {
             PersistableBundle defaults = new PersistableBundle();
+            defaults.putInt(KEY_WIFI_OFF_DEFERRING_TIME_INT, 0);
             return defaults;
         }
     }
@@ -3433,8 +3442,8 @@
             "support_wps_over_ims_bool";
 
     /**
-     * Holds the list of carrier certificate hashes. Note that each carrier has its own certificates
-     * @hide
+     * Holds the list of carrier certificate hashes.
+     * Note that each carrier has its own certificates.
      */
     public static final String KEY_CARRIER_CERTIFICATE_STRING_ARRAY =
             "carrier_certificate_string_array";
@@ -3454,6 +3463,377 @@
     public static final String KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL =
             "prevent_clir_activation_and_deactivation_code_bool";
 
+    /**
+     * Flag specifying whether to show forwarded number on call-in-progress screen.
+     * When true, forwarded number is shown.
+     * When false, forwarded number is not shown.
+     */
+    public static final String KEY_SHOW_FORWARDED_NUMBER_BOOL =
+            "show_forwarded_number_bool";
+
+    /**
+     * Configs used for epdg tunnel bring up.
+     *
+     * @see <a href="https://tools.ietf.org/html/rfc7296">RFC 7296, Internet Key Exchange
+     *        Protocol Version 2 (IKEv2)</a>
+     */
+    public static final class Iwlan {
+        /** Prefix of all Epdg.KEY_* constants. */
+        public static final String KEY_PREFIX = "iwlan.";
+
+        /**
+         * Time in seconds after which the child security association session is terminated if
+         * rekey procedure is not successful. If not set or set to <= 0, the default value is
+         * 3600 seconds.
+         */
+        public static final String KEY_CHILD_SA_REKEY_HARD_TIMER_SEC_INT =
+                KEY_PREFIX + "child_sa_rekey_hard_timer_sec_int";
+
+        /**
+         * Time in seconds after which the child session rekey procedure is started. If not set or
+         * set to <= 0, default value is 3000 seconds.
+         */
+        public static final String KEY_CHILD_SA_REKEY_SOFT_TIMER_SEC_INT =
+                KEY_PREFIX + "child_sa_rekey_soft_timer_sec_int";
+
+        /** Supported DH groups for IKE negotiation.
+         * Possible values are {@link #DH_GROUP_NONE}, {@link #DH_GROUP_1024_BIT_MODP},
+         * {@link #DH_GROUP_2048_BIT_MODP}
+         */
+        public static final String KEY_DIFFIE_HELLMAN_GROUPS_INT_ARRAY =
+                KEY_PREFIX + "diffie_hellman_groups_int_array";
+
+        /**
+         * Time in seconds after which a dead peer detection (DPD) request is sent.
+         * If not set or set to <= 0, default value is 120 seconds.
+         */
+        public static final String KEY_DPD_TIMER_SEC_INT = KEY_PREFIX + "dpd_timer_sec_int";
+
+        /**
+         * Method used to authenticate epdg server.
+         * Possible values are {@link #AUTHENTICATION_METHOD_EAP_ONLY},
+         * {@link #AUTHENTICATION_METHOD_CERT}
+         */
+        public static final String KEY_EPDG_AUTHENTICATION_METHOD_INT =
+                KEY_PREFIX + "epdg_authentication_method_int";
+
+        /**
+         * A priority list of ePDG addresses to be used.
+         * Possible values are {@link #EPDG_ADDRESS_STATIC}, {@link #EPDG_ADDRESS_PLMN},
+         * {@link #EPDG_ADDRESS_PCO}
+         */
+        public static final String KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY =
+                KEY_PREFIX + "epdg_address_priority_int_array";
+
+        /** Epdg static IP address or FQDN */
+        public static final String KEY_EPDG_STATIC_ADDRESS_STRING =
+                KEY_PREFIX + "epdg_static_address_string";
+
+        /** Epdg static IP address or FQDN for roaming */
+        public static final String KEY_EPDG_STATIC_ADDRESS_ROAMING_STRING =
+                KEY_PREFIX + "epdg_static_address_roaming_string";
+
+        /**
+         * List of supported key sizes for AES Cipher Block Chaining (CBC) encryption mode of child
+         * session.
+         * Possible values are {@link #KEY_LEN_UNUSED}, {@link #KEY_LEN_AES_128},
+         * {@link #KEY_LEN_AES_192}, {@link #KEY_LEN_AES_256}
+         */
+        public static final String KEY_CHILD_SESSION_AES_CBC_KEY_SIZE_INT_ARRAY =
+                KEY_PREFIX + "child_session_aes_cbc_key_size_int_array";
+
+        /**
+         * List of supported key sizes for AES counter (CTR) encryption mode of child session.
+         * Possible values are {@link #KEY_LEN_UNUSED}, {@link #KEY_LEN_AES_128},
+         * {@link #KEY_LEN_AES_192}, {@link #KEY_LEN_AES_256}
+         */
+        public static final String KEY_CHILD_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY =
+                KEY_PREFIX + "child_encryption_aes_ctr_key_size_int_array";
+
+        /**
+         * List of supported encryption algorithms for child session.
+         * Possible values are {@link #ENCRYPTION_ALGORITHM_3DES},
+         * {@link #ENCRYPTION_ALGORITHM_AES_CBC}, {@link #ENCRYPTION_ALGORITHM_AES_GCM_8},
+         * {@link #ENCRYPTION_ALGORITHM_AES_GCM_12}, {@link #ENCRYPTION_ALGORITHM_AES_GCM_16}
+         */
+        public static final String KEY_SUPPORTED_CHILD_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY =
+                KEY_PREFIX + "supported_child_session_encryption_algorithms_int_array";
+
+        /** Controls if IKE message fragmentation is enabled. */
+        public static final String KEY_IKE_FRAGMENTATION_ENABLED_BOOL =
+                KEY_PREFIX + "ike_fragmentation_enabled_bool";
+
+        /**
+         * Time in seconds after which the IKE session is terminated if rekey procedure is not
+         * successful. If not set or set to <= 0, default value is 3600 seconds.
+         */
+        public static final String KEY_IKE_REKEY_HARD_TIMER_SEC_INT =
+                KEY_PREFIX + "ike_rekey_hard_timer_in_sec";
+
+        /**
+         * Time in seconds after which the IKE session rekey procedure is started. If not set or
+         * set to <= 0, default value is 3000 seconds.
+         */
+        public static final String KEY_IKE_REKEY_SOFT_TIMER_SEC_INT =
+                KEY_PREFIX + "ike_rekey_soft_timer_sec_int";
+
+        /**
+         * List of supported key sizes for AES Cipher Block Chaining (CBC) encryption mode of IKE
+         * session.
+         * Possible values - {@link #KEY_LEN_UNUSED}, {@link #KEY_LEN_AES_128},
+         *         {@link #KEY_LEN_AES_192}, {@link #KEY_LEN_AES_256}
+         */
+        public static final String KEY_IKE_SESSION_AES_CBC_KEY_SIZE_INT_ARRAY =
+                KEY_PREFIX + "ike_session_encryption_aes_cbc_key_size_int_array";
+
+        /**
+         * List of supported key sizes for AES counter (CTR) encryption mode of IKE session.
+         * Possible values - {@link #KEY_LEN_UNUSED}, {@link #KEY_LEN_AES_128},
+         *         {@link #KEY_LEN_AES_192}, {@link #KEY_LEN_AES_256}
+         */
+        public static final String KEY_IKE_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY =
+                KEY_PREFIX + "ike_session_aes_ctr_key_size_int_array";
+
+        /**
+        * List of supported encryption algorithms for IKE session.
+        * Possible values are {@link #ENCRYPTION_ALGORITHM_3DES},
+         * {@link #ENCRYPTION_ALGORITHM_AES_CBC}, {@link #ENCRYPTION_ALGORITHM_AES_GCM_8},
+         * {@link #ENCRYPTION_ALGORITHM_AES_GCM_12}, {@link #ENCRYPTION_ALGORITHM_AES_GCM_16}
+        */
+        public static final String KEY_SUPPORTED_IKE_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY =
+                KEY_PREFIX + "supported_ike_session_encryption_algorithms_int_array";
+
+        /**
+         * List of supported integrity algorithms for IKE session
+         * Possible values are {@link #INTEGRITY_ALGORITHM_NONE},
+         * {@link #INTEGRITY_ALGORITHM_HMAC_SHA1_96}, {@link #INTEGRITY_ALGORITHM_AES_XCBC_96},
+         * {@link #INTEGRITY_ALGORITHM_HMAC_SHA2_256_128},
+         * {@link #INTEGRITY_ALGORITHM_HMAC_SHA2_384_192},
+         * {@link #INTEGRITY_ALGORITHM_HMAC_SHA2_512_256}
+         */
+        public static final String KEY_SUPPORTED_INTEGRITY_ALGORITHMS_INT_ARRAY =
+                KEY_PREFIX + "supported_integrity_algorithms_int_array";
+
+        /** Maximum number of retries for tunnel establishment. */
+        public static final String KEY_MAX_RETRIES_INT = KEY_PREFIX + "max_retries_int";
+
+        /** Controls if nat traversal should be enabled. */
+        public static final String KEY_NATT_ENABLED_BOOL = KEY_PREFIX + "natt_enabled_bool";
+
+        /**
+         * Time in seconds after which a NATT keep alive message is sent. If not set or set to <= 0,
+         * default value is 20 seconds.
+         */
+        public static final String KEY_NATT_KEEP_ALIVE_TIMER_SEC_INT =
+                KEY_PREFIX + "natt_keep_alive_timer_sec_int";
+
+        /** List of comma separated MCC/MNCs used to create ePDG FQDN as per 3GPP TS 23.003 */
+        public static final String KEY_MCC_MNCS_STRING_ARRAY = KEY_PREFIX + "mcc_mncs_string_array";
+
+        /**
+         * List of supported pseudo random function algorithms for IKE session
+         * Possible values are {@link #PSEUDORANDOM_FUNCTION_HMAC_SHA1},
+         * {@link #PSEUDORANDOM_FUNCTION_AES128_XCBC}
+         */
+        public static final String KEY_SUPPORTED_PRF_ALGORITHMS_INT_ARRAY = KEY_PREFIX +
+                "supported_prf_algorithms_int_array";
+
+        /**
+         * Time in seconds after which IKE message is retransmitted. If not set or set to <= 0,
+         * default value is 2 seconds.
+         */
+        public static final String KEY_RETRANSMIT_TIMER_SEC_INT =
+                KEY_PREFIX + "retransmit_timer_sec_int";
+
+        /** @hide */
+        @IntDef({
+                AUTHENTICATION_METHOD_EAP_ONLY,
+                AUTHENTICATION_METHOD_CERT
+        })
+        public @interface AuthenticationMethodType {}
+
+        /**
+         * Certificate sent from the server is ignored. Only Extensible Authentication Protocol
+         * (EAP) is used to authenticate the server.
+         * EAP_ONLY_AUTH payload is added to IKE_AUTH request if supported.
+         * @see <a href="https://tools.ietf.org/html/rfc5998">RFC 5998</a>
+         */
+        public static final int AUTHENTICATION_METHOD_EAP_ONLY = 0;
+        /** Server is authenticated using its certificate. */
+        public static final int AUTHENTICATION_METHOD_CERT = 1;
+
+        /** @hide */
+        @IntDef({
+                EPDG_ADDRESS_STATIC,
+                EPDG_ADDRESS_PLMN,
+                EPDG_ADDRESS_PCO
+        })
+        public @interface EpdgAddressType {}
+
+        /** Use static epdg address. */
+        public static final int EPDG_ADDRESS_STATIC = 0;
+        /** Construct the epdg address using plmn. */
+        public static final int EPDG_ADDRESS_PLMN = 1;
+        /**
+         * Use the epdg address received in protocol configuration options (PCO) from the
+         * network.
+         */
+        public static final int EPDG_ADDRESS_PCO = 2;
+
+        /** @hide */
+        @IntDef({
+                KEY_LEN_UNUSED,
+                KEY_LEN_AES_128,
+                KEY_LEN_AES_192,
+                KEY_LEN_AES_256
+        })
+        public @interface EncrpytionKeyLengthType {}
+
+        public static final int KEY_LEN_UNUSED = SaProposal.KEY_LEN_UNUSED;
+        /** AES Encryption/Ciphering Algorithm key length 128 bits. */
+        public static final int KEY_LEN_AES_128 = SaProposal.KEY_LEN_AES_128;
+        /** AES Encryption/Ciphering Algorithm key length 192 bits. */
+        public static final int KEY_LEN_AES_192 = SaProposal.KEY_LEN_AES_192;
+        /** AES Encryption/Ciphering Algorithm key length 256 bits. */
+        public static final int KEY_LEN_AES_256 = SaProposal.KEY_LEN_AES_256;
+
+        /** @hide */
+        @IntDef({
+                DH_GROUP_NONE,
+                DH_GROUP_1024_BIT_MODP,
+                DH_GROUP_2048_BIT_MODP
+        })
+        public @interface DhGroup {}
+
+        /** None Diffie-Hellman Group. */
+        public static final int DH_GROUP_NONE = SaProposal.DH_GROUP_NONE;
+        /** 1024-bit MODP Diffie-Hellman Group. */
+        public static final int DH_GROUP_1024_BIT_MODP = SaProposal.DH_GROUP_1024_BIT_MODP;
+        /** 2048-bit MODP Diffie-Hellman Group. */
+        public static final int DH_GROUP_2048_BIT_MODP = SaProposal.DH_GROUP_2048_BIT_MODP;
+
+        /** @hide */
+        @IntDef({
+                ENCRYPTION_ALGORITHM_3DES,
+                ENCRYPTION_ALGORITHM_AES_CBC,
+                ENCRYPTION_ALGORITHM_AES_GCM_8,
+                ENCRYPTION_ALGORITHM_AES_GCM_12,
+                ENCRYPTION_ALGORITHM_AES_GCM_16
+        })
+        public @interface EncryptionAlgorithm {}
+
+        /** 3DES Encryption/Ciphering Algorithm. */
+        public static final int ENCRYPTION_ALGORITHM_3DES = SaProposal.ENCRYPTION_ALGORITHM_3DES;
+        /** AES-CBC Encryption/Ciphering Algorithm. */
+        public static final int ENCRYPTION_ALGORITHM_AES_CBC =
+                SaProposal.ENCRYPTION_ALGORITHM_AES_CBC;
+
+        /**
+         * AES-GCM Authentication/Integrity + Encryption/Ciphering Algorithm with 8-octet ICV
+         * (truncation).
+         */
+        public static final int ENCRYPTION_ALGORITHM_AES_GCM_8 =
+                SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8;
+        /**
+         * AES-GCM Authentication/Integrity + Encryption/Ciphering Algorithm with 12-octet ICV
+         * (truncation).
+         */
+        public static final int ENCRYPTION_ALGORITHM_AES_GCM_12 =
+                SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12;
+        /**
+         * AES-GCM Authentication/Integrity + Encryption/Ciphering Algorithm with 16-octet ICV
+         * (truncation).
+         */
+        public static final int ENCRYPTION_ALGORITHM_AES_GCM_16 =
+                SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16;
+
+        /** @hide */
+        @IntDef({
+                INTEGRITY_ALGORITHM_NONE,
+                INTEGRITY_ALGORITHM_HMAC_SHA1_96,
+                INTEGRITY_ALGORITHM_AES_XCBC_96,
+                INTEGRITY_ALGORITHM_HMAC_SHA2_256_128,
+                INTEGRITY_ALGORITHM_HMAC_SHA2_384_192,
+                INTEGRITY_ALGORITHM_HMAC_SHA2_512_256
+        })
+        public @interface IntegrityAlgorithm {}
+
+        /** None Authentication/Integrity Algorithm. */
+        public static final int INTEGRITY_ALGORITHM_NONE = SaProposal.INTEGRITY_ALGORITHM_NONE;
+        /** HMAC-SHA1 Authentication/Integrity Algorithm. */
+        public static final int INTEGRITY_ALGORITHM_HMAC_SHA1_96 =
+                SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96;
+        /** AES-XCBC-96 Authentication/Integrity Algorithm. */
+        public static final int INTEGRITY_ALGORITHM_AES_XCBC_96 =
+                SaProposal.INTEGRITY_ALGORITHM_AES_XCBC_96;
+        /** HMAC-SHA256 Authentication/Integrity Algorithm with 128-bit truncation. */
+        public static final int INTEGRITY_ALGORITHM_HMAC_SHA2_256_128 =
+                SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128;
+        /** HMAC-SHA384 Authentication/Integrity Algorithm with 192-bit truncation. */
+        public static final int INTEGRITY_ALGORITHM_HMAC_SHA2_384_192 =
+                SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_384_192;
+        /** HMAC-SHA512 Authentication/Integrity Algorithm with 256-bit truncation. */
+        public static final int INTEGRITY_ALGORITHM_HMAC_SHA2_512_256 =
+                SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_512_256;
+
+        /** @hide */
+        @IntDef({
+                PSEUDORANDOM_FUNCTION_HMAC_SHA1,
+                PSEUDORANDOM_FUNCTION_AES128_XCBC
+        })
+        public @interface PseudorandomFunction {}
+
+        /** HMAC-SHA1 Pseudorandom Function. */
+        public static final int PSEUDORANDOM_FUNCTION_HMAC_SHA1 =
+                SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1;
+        /** AES128-XCBC Pseudorandom Function. */
+        public static final int PSEUDORANDOM_FUNCTION_AES128_XCBC =
+                SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC;
+
+        private Iwlan() {}
+
+        private static PersistableBundle getDefaults() {
+            PersistableBundle defaults = new PersistableBundle();
+            defaults.putInt(KEY_IKE_REKEY_SOFT_TIMER_SEC_INT, 3000);
+            defaults.putInt(KEY_IKE_REKEY_HARD_TIMER_SEC_INT, 3600);
+            defaults.putInt(KEY_CHILD_SA_REKEY_SOFT_TIMER_SEC_INT, 3000);
+            defaults.putInt(KEY_CHILD_SA_REKEY_HARD_TIMER_SEC_INT, 3600);
+            defaults.putInt(KEY_RETRANSMIT_TIMER_SEC_INT, 2);
+            defaults.putInt(KEY_DPD_TIMER_SEC_INT, 120);
+            defaults.putInt(KEY_MAX_RETRIES_INT, 3);
+            defaults.putIntArray(KEY_DIFFIE_HELLMAN_GROUPS_INT_ARRAY,
+                    new int[]{DH_GROUP_1024_BIT_MODP, DH_GROUP_2048_BIT_MODP});
+            defaults.putIntArray(KEY_SUPPORTED_IKE_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY,
+                    new int[]{ENCRYPTION_ALGORITHM_3DES, ENCRYPTION_ALGORITHM_AES_CBC});
+            defaults.putIntArray(KEY_SUPPORTED_CHILD_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY,
+                    new int[]{ENCRYPTION_ALGORITHM_3DES, ENCRYPTION_ALGORITHM_AES_CBC});
+            defaults.putIntArray(KEY_SUPPORTED_INTEGRITY_ALGORITHMS_INT_ARRAY,
+                    new int[]{INTEGRITY_ALGORITHM_AES_XCBC_96, INTEGRITY_ALGORITHM_HMAC_SHA1_96,
+                            INTEGRITY_ALGORITHM_HMAC_SHA2_256_128});
+            defaults.putIntArray(KEY_SUPPORTED_PRF_ALGORITHMS_INT_ARRAY,
+                    new int[]{PSEUDORANDOM_FUNCTION_HMAC_SHA1, PSEUDORANDOM_FUNCTION_AES128_XCBC});
+            defaults.putBoolean(KEY_NATT_ENABLED_BOOL, true);
+            defaults.putInt(KEY_EPDG_AUTHENTICATION_METHOD_INT, AUTHENTICATION_METHOD_CERT);
+            defaults.putString(KEY_EPDG_STATIC_ADDRESS_STRING, "");
+            defaults.putString(KEY_EPDG_STATIC_ADDRESS_ROAMING_STRING, "");
+            defaults.putInt(KEY_NATT_KEEP_ALIVE_TIMER_SEC_INT, 20);
+            defaults.putIntArray(KEY_IKE_SESSION_AES_CBC_KEY_SIZE_INT_ARRAY,
+                    new int[]{KEY_LEN_AES_128, KEY_LEN_AES_256});
+            defaults.putIntArray(KEY_IKE_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY,
+                    new int[]{KEY_LEN_AES_128});
+            defaults.putIntArray(KEY_CHILD_SESSION_AES_CBC_KEY_SIZE_INT_ARRAY,
+                    new int[]{KEY_LEN_AES_128, KEY_LEN_AES_256});
+            defaults.putIntArray(KEY_CHILD_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY,
+                    new int[]{KEY_LEN_AES_128});
+            defaults.putBoolean(KEY_IKE_FRAGMENTATION_ENABLED_BOOL, false);
+            defaults.putIntArray(KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY, new int[]{EPDG_ADDRESS_PLMN,
+                    EPDG_ADDRESS_STATIC});
+            defaults.putStringArray(KEY_MCC_MNCS_STRING_ARRAY, new String[]{});
+
+            return defaults;
+        }
+    }
+
     /** The default value for every variable. */
     private final static PersistableBundle sDefaults;
 
@@ -3492,6 +3872,7 @@
         sDefaults.putInt(KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT, 2);
         sDefaults.putInt(KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT, 2);
         sDefaults.putBoolean(KEY_CARRIER_FORCE_DISABLE_ETWS_CMAS_TEST_BOOL, false);
+        sDefaults.putBoolean(KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL, true);
         sDefaults.putBoolean(KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL, false);
@@ -3574,6 +3955,8 @@
         sDefaults.putStringArray(KEY_READ_ONLY_APN_TYPES_STRING_ARRAY, new String[] {"dun"});
         sDefaults.putStringArray(KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY, null);
         sDefaults.putStringArray(KEY_APN_SETTINGS_DEFAULT_APN_TYPES_STRING_ARRAY, null);
+        sDefaults.putAll(Apn.getDefaults());
+
         sDefaults.putBoolean(KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL, false);
         sDefaults.putBoolean(KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL, false);
         sDefaults.putStringArray(KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS, new String[]{
@@ -3619,6 +4002,7 @@
         sDefaults.putBoolean(KEY_CALL_FORWARDING_MAP_NON_NUMBER_TO_VOICEMAIL_BOOL, false);
         sDefaults.putBoolean(KEY_IGNORE_RTT_MODE_SETTING_BOOL, false);
         sDefaults.putInt(KEY_CDMA_3WAYCALL_FLASH_DELAY_INT , 0);
+        sDefaults.putBoolean(KEY_SUPPORT_ADHOC_CONFERENCE_CALLS_BOOL, false);
         sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_MANAGE_IMS_CONFERENCE_CALL_BOOL, true);
@@ -3944,6 +4328,8 @@
         // Default wifi configurations.
         sDefaults.putAll(Wifi.getDefaults());
         sDefaults.putBoolean(ENABLE_EAP_METHOD_PREFIX_BOOL, false);
+        sDefaults.putBoolean(KEY_SHOW_FORWARDED_NUMBER_BOOL, false);
+        sDefaults.putAll(Iwlan.getDefaults());
     }
 
     /**
@@ -4205,8 +4591,11 @@
     /** @hide */
     @Nullable
     private ICarrierConfigLoader getICarrierConfigLoader() {
-        return ICarrierConfigLoader.Stub
-                .asInterface(ServiceManager.getService(Context.CARRIER_CONFIG_SERVICE));
+        return ICarrierConfigLoader.Stub.asInterface(
+                TelephonyFrameworkInitializer
+                        .getTelephonyServiceManager()
+                        .getCarrierConfigServiceRegisterer()
+                        .get());
     }
 
     /**
diff --git a/telephony/java/android/telephony/CbGeoUtils.java b/telephony/java/android/telephony/CbGeoUtils.java
index 84be4e8..719ba8d 100644
--- a/telephony/java/android/telephony/CbGeoUtils.java
+++ b/telephony/java/android/telephony/CbGeoUtils.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.text.TextUtils;
diff --git a/telephony/java/android/telephony/CdmaEriInformation.java b/telephony/java/android/telephony/CdmaEriInformation.java
new file mode 100644
index 0000000..1cd9d30
--- /dev/null
+++ b/telephony/java/android/telephony/CdmaEriInformation.java
@@ -0,0 +1,170 @@
+/**
+ * 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;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * CDMA ERI (Enhanced Roaming Indicator) information.
+ *
+ * This contains the following ERI information
+ *
+ * 1. ERI (Enhanced Roaming Indicator) icon index. The number is assigned by
+ *    3GPP2 C.R1001-H v1.0 Table 8.1-1. Additionally carriers define their own
+ *    ERI icon index.
+ * 2. CDMA ERI icon mode. This represents how the icon should be displayed.
+ *    Its one of the following CDMA ERI icon mode
+ *    {@link android.telephony.CdmaEriInformation#ERI_ICON_MODE_NORMAL}
+ *    {@link android.telephony.CdmaEriInformation#ERI_ICON_MODE_FLASH}
+ *
+ * @hide
+ */
+@SystemApi
+public final class CdmaEriInformation implements Parcelable {
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"ERI_"}, value = {
+                ERI_ON,
+                ERI_OFF,
+                ERI_FLASH
+            })
+    public @interface EriIconIndex {}
+
+    /**
+     * ERI (Enhanced Roaming Indicator) is ON i.e value 0 defined by
+     * 3GPP2 C.R1001-H v1.0 Table 8.1-1.
+     */
+    public static final int ERI_ON = 0;
+
+    /**
+     * ERI (Enhanced Roaming Indicator) is OFF i.e value 1 defined by
+     * 3GPP2 C.R1001-H v1.0 Table 8.1-1.
+     */
+    public static final int ERI_OFF = 1;
+
+    /**
+     * ERI (Enhanced Roaming Indicator) is FLASH i.e value 2 defined by
+     * 3GPP2 C.R1001-H v1.0 Table 8.1-1.
+     */
+    public static final int ERI_FLASH = 2;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"ERI_ICON_MODE_"}, value = {
+                ERI_ICON_MODE_NORMAL,
+                ERI_ICON_MODE_FLASH
+            })
+    public @interface EriIconMode {}
+
+    /**
+     * ERI (Enhanced Roaming Indicator) icon mode is normal. This constant represents that
+     * the ERI icon should be displayed normally.
+     *
+     * Note: ERI is defined 3GPP2 C.R1001-H Table 8.1-1
+     */
+    public static final int ERI_ICON_MODE_NORMAL = 0;
+
+    /**
+     * ERI (Enhanced Roaming Indicator) icon mode flash. This constant represents that
+     * the ERI icon should be flashing.
+     *
+     * Note: ERI is defined 3GPP2 C.R1001-H Table 8.1-1
+     */
+    public static final int ERI_ICON_MODE_FLASH = 1;
+
+    private @EriIconIndex int mIconIndex;
+    private @EriIconMode int mIconMode;
+
+    /**
+     * Creates CdmaEriInformation from iconIndex and iconMode
+     *
+     * @hide
+     */
+    public CdmaEriInformation(@EriIconIndex int iconIndex, @EriIconMode int iconMode) {
+        mIconIndex = iconIndex;
+        mIconMode = iconMode;
+    }
+
+    /** Gets the ERI icon index */
+    public @EriIconIndex int getEriIconIndex() {
+        return mIconIndex;
+    }
+
+    /**
+     * Sets the ERI icon index
+     *
+     * @hide
+     */
+    public void setEriIconIndex(@EriIconIndex int iconIndex) {
+        mIconIndex = iconIndex;
+    }
+
+    /** Gets the ERI icon mode */
+    public @EriIconMode int getEriIconMode() {
+        return mIconMode;
+    }
+
+    /**
+     * Sets the ERI icon mode
+     *
+     * @hide
+     */
+    public void setEriIconMode(@EriIconMode int iconMode) {
+        mIconMode = iconMode;
+    }
+    /** Implement the Parcelable interface */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mIconIndex);
+        dest.writeInt(mIconMode);
+    }
+
+    /** Implement the Parcelable interface */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Construct a CdmaEriInformation object from the given parcel
+     */
+    private CdmaEriInformation(Parcel in) {
+        mIconIndex = in.readInt();
+        mIconMode = in.readInt();
+    }
+
+    /** Implement the Parcelable interface */
+    public static final @android.annotation.NonNull Parcelable.Creator<CdmaEriInformation> CREATOR =
+            new Parcelable.Creator<CdmaEriInformation>() {
+        @Override
+        public CdmaEriInformation createFromParcel(Parcel in) {
+            return new CdmaEriInformation(in);
+        }
+
+        @Override
+        public CdmaEriInformation[] newArray(int size) {
+            return new CdmaEriInformation[size];
+        }
+    };
+}
diff --git a/telephony/java/android/telephony/CellIdentity.java b/telephony/java/android/telephony/CellIdentity.java
index 5345469..3f0aeb5 100644
--- a/telephony/java/android/telephony/CellIdentity.java
+++ b/telephony/java/android/telephony/CellIdentity.java
@@ -20,10 +20,13 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.hardware.radio.V1_0.CellInfoType;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import com.android.telephony.Rlog;
+
 import java.util.Objects;
 import java.util.UUID;
 
@@ -322,6 +325,86 @@
     }
 
     /** @hide */
+    public static CellIdentity create(android.hardware.radio.V1_0.CellIdentity cellIdentity) {
+        if (cellIdentity == null)  return null;
+        switch(cellIdentity.cellInfoType) {
+            case CellInfoType.GSM: {
+                if (cellIdentity.cellIdentityGsm.size() == 1) {
+                    return new CellIdentityGsm(cellIdentity.cellIdentityGsm.get(0));
+                }
+                break;
+            }
+            case CellInfoType.WCDMA: {
+                if (cellIdentity.cellIdentityWcdma.size() == 1) {
+                    return new CellIdentityWcdma(cellIdentity.cellIdentityWcdma.get(0));
+                }
+                break;
+            }
+            case CellInfoType.TD_SCDMA: {
+                if (cellIdentity.cellIdentityTdscdma.size() == 1) {
+                    return new  CellIdentityTdscdma(cellIdentity.cellIdentityTdscdma.get(0));
+                }
+                break;
+            }
+            case CellInfoType.LTE: {
+                if (cellIdentity.cellIdentityLte.size() == 1) {
+                    return new CellIdentityLte(cellIdentity.cellIdentityLte.get(0));
+                }
+                break;
+            }
+            case CellInfoType.CDMA: {
+                if (cellIdentity.cellIdentityCdma.size() == 1) {
+                    return new CellIdentityCdma(cellIdentity.cellIdentityCdma.get(0));
+                }
+                break;
+            }
+            case CellInfoType.NONE: break;
+            default: break;
+        }
+        return null;
+    }
+
+    /** @hide */
+    public static CellIdentity create(android.hardware.radio.V1_2.CellIdentity cellIdentity) {
+        if (cellIdentity == null)  return null;
+        switch(cellIdentity.cellInfoType) {
+            case CellInfoType.GSM: {
+                if (cellIdentity.cellIdentityGsm.size() == 1) {
+                    return new CellIdentityGsm(cellIdentity.cellIdentityGsm.get(0));
+                }
+                break;
+            }
+            case CellInfoType.WCDMA: {
+                if (cellIdentity.cellIdentityWcdma.size() == 1) {
+                    return new CellIdentityWcdma(cellIdentity.cellIdentityWcdma.get(0));
+                }
+                break;
+            }
+            case CellInfoType.TD_SCDMA: {
+                if (cellIdentity.cellIdentityTdscdma.size() == 1) {
+                    return new  CellIdentityTdscdma(cellIdentity.cellIdentityTdscdma.get(0));
+                }
+                break;
+            }
+            case CellInfoType.LTE: {
+                if (cellIdentity.cellIdentityLte.size() == 1) {
+                    return new CellIdentityLte(cellIdentity.cellIdentityLte.get(0));
+                }
+                break;
+            }
+            case CellInfoType.CDMA: {
+                if (cellIdentity.cellIdentityCdma.size() == 1) {
+                    return new CellIdentityCdma(cellIdentity.cellIdentityCdma.get(0));
+                }
+                break;
+            }
+            case CellInfoType.NONE: break;
+            default: break;
+        }
+        return null;
+    }
+
+    /** @hide */
     public static CellIdentity create(android.hardware.radio.V1_5.CellIdentity ci) {
         if (ci == null) return null;
         switch (ci.getDiscriminator()) {
diff --git a/telephony/java/android/telephony/CellIdentityGsm.java b/telephony/java/android/telephony/CellIdentityGsm.java
index 2ecdfce..dc73cbf 100644
--- a/telephony/java/android/telephony/CellIdentityGsm.java
+++ b/telephony/java/android/telephony/CellIdentityGsm.java
@@ -18,11 +18,13 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.telephony.gsm.GsmCellLocation;
 import android.text.TextUtils;
 
+import java.util.Collections;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -46,6 +48,9 @@
     // 6-bit Base Station Identity Code
     private final int mBsic;
 
+    // a list of additional PLMN-IDs reported for this cell
+    private final List<String> mAdditionalPlmns;
+
     /**
      * @hide
      */
@@ -56,6 +61,7 @@
         mCid = CellInfo.UNAVAILABLE;
         mArfcn = CellInfo.UNAVAILABLE;
         mBsic = CellInfo.UNAVAILABLE;
+        mAdditionalPlmns = Collections.emptyList();
     }
 
     /**
@@ -68,35 +74,48 @@
      * @param mncStr 2 or 3-digit Mobile Network Code in string format
      * @param alphal long alpha Operator Name String or Enhanced Operator Name String
      * @param alphas short alpha Operator Name String or Enhanced Operator Name String
+     * @param additionalPlmns a list of additional PLMN IDs broadcast by the cell
      *
      * @hide
      */
     public CellIdentityGsm(int lac, int cid, int arfcn, int bsic, String mccStr,
-                            String mncStr, String alphal, String alphas) {
+                            String mncStr, String alphal, String alphas,
+                            List<String> additionalPlmns) {
         super(TAG, CellInfo.TYPE_GSM, mccStr, mncStr, alphal, alphas);
         mLac = inRangeOrUnavailable(lac, 0, MAX_LAC);
         mCid = inRangeOrUnavailable(cid, 0, MAX_CID);
         mArfcn = inRangeOrUnavailable(arfcn, 0, MAX_ARFCN);
         mBsic = inRangeOrUnavailable(bsic, 0, MAX_BSIC);
+        mAdditionalPlmns = additionalPlmns;
     }
 
     /** @hide */
     public CellIdentityGsm(android.hardware.radio.V1_0.CellIdentityGsm cid) {
         this(cid.lac, cid.cid, cid.arfcn,
                 cid.bsic == (byte) 0xFF ? CellInfo.UNAVAILABLE : cid.bsic,
-                cid.mcc, cid.mnc, "", "");
+                cid.mcc, cid.mnc, "", "", Collections.emptyList());
     }
 
     /** @hide */
     public CellIdentityGsm(android.hardware.radio.V1_2.CellIdentityGsm cid) {
         this(cid.base.lac, cid.base.cid, cid.base.arfcn,
                 cid.base.bsic == (byte) 0xFF ? CellInfo.UNAVAILABLE : cid.base.bsic, cid.base.mcc,
-                cid.base.mnc, cid.operatorNames.alphaLong, cid.operatorNames.alphaShort);
+                cid.base.mnc, cid.operatorNames.alphaLong, cid.operatorNames.alphaShort,
+                Collections.emptyList());
+    }
+
+    /** @hide */
+    public CellIdentityGsm(android.hardware.radio.V1_5.CellIdentityGsm cid) {
+        this(cid.base.base.lac, cid.base.base.cid, cid.base.base.arfcn,
+                cid.base.base.bsic == (byte) 0xFF ? CellInfo.UNAVAILABLE
+                        : cid.base.base.bsic, cid.base.base.mcc,
+                cid.base.base.mnc, cid.base.operatorNames.alphaLong,
+                cid.base.operatorNames.alphaShort, cid.additionalPlmns);
     }
 
     private CellIdentityGsm(CellIdentityGsm cid) {
         this(cid.mLac, cid.mCid, cid.mArfcn, cid.mBsic, cid.mMccStr,
-                cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort);
+                cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort, cid.mAdditionalPlmns);
     }
 
     CellIdentityGsm copy() {
@@ -107,7 +126,7 @@
     @Override
     public @NonNull CellIdentityGsm sanitizeLocationInfo() {
         return new CellIdentityGsm(CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE,
-                CellInfo.UNAVAILABLE, mMccStr, mMncStr, mAlphaLong, mAlphaShort);
+                CellInfo.UNAVAILABLE, mMccStr, mMncStr, mAlphaLong, mAlphaShort, mAdditionalPlmns);
     }
 
     /**
@@ -193,6 +212,14 @@
     }
 
     /**
+     * @return a list of additional PLMN IDs supported by this cell.
+     */
+    @NonNull
+    public List<String> getAdditionalPlmns() {
+        return mAdditionalPlmns;
+    }
+
+    /**
      * @deprecated Primary Scrambling Code is not applicable to GSM.
      * @return {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} - undefined for GSM
      */
@@ -215,7 +242,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mLac, mCid, super.hashCode());
+        return Objects.hash(mLac, mCid, mAdditionalPlmns.hashCode(), super.hashCode());
     }
 
     @Override
@@ -235,6 +262,7 @@
                 && mBsic == o.mBsic
                 && TextUtils.equals(mMccStr, o.mMccStr)
                 && TextUtils.equals(mMncStr, o.mMncStr)
+                && mAdditionalPlmns.equals(o.mAdditionalPlmns)
                 && super.equals(other);
     }
 
@@ -249,6 +277,7 @@
         .append(" mMnc=").append(mMncStr)
         .append(" mAlphaLong=").append(mAlphaLong)
         .append(" mAlphaShort=").append(mAlphaShort)
+        .append(" mAdditionalPlmns=").append(mAdditionalPlmns)
         .append("}").toString();
     }
 
@@ -261,6 +290,7 @@
         dest.writeInt(mCid);
         dest.writeInt(mArfcn);
         dest.writeInt(mBsic);
+        dest.writeList(mAdditionalPlmns);
     }
 
     /** Construct from Parcel, type has already been processed */
@@ -270,6 +300,7 @@
         mCid = in.readInt();
         mArfcn = in.readInt();
         mBsic = in.readInt();
+        mAdditionalPlmns = in.readArrayList(null);
 
         if (DBG) log(toString());
     }
diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java
index 15c9175..cf8fe6a 100644
--- a/telephony/java/android/telephony/CellIdentityLte.java
+++ b/telephony/java/android/telephony/CellIdentityLte.java
@@ -18,12 +18,14 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Parcel;
 import android.telephony.gsm.GsmCellLocation;
 import android.text.TextUtils;
 
+import java.util.Collections;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -50,6 +52,11 @@
     // cell bandwidth, in kHz
     private final int mBandwidth;
 
+    // a list of additional PLMN-IDs reported for this cell
+    private final List<String> mAdditionalPlmns;
+
+    private ClosedSubscriberGroupInfo mCsgInfo;
+
     /**
      * @hide
      */
@@ -61,6 +68,8 @@
         mTac = CellInfo.UNAVAILABLE;
         mEarfcn = CellInfo.UNAVAILABLE;
         mBandwidth = CellInfo.UNAVAILABLE;
+        mAdditionalPlmns = Collections.emptyList();
+        mCsgInfo = null;
     }
 
     /**
@@ -76,7 +85,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public CellIdentityLte(int mcc, int mnc, int ci, int pci, int tac) {
         this(ci, pci, tac, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, String.valueOf(mcc),
-                String.valueOf(mnc), null, null);
+                String.valueOf(mnc), null, null, Collections.emptyList(), null);
     }
 
     /**
@@ -90,34 +99,49 @@
      * @param mncStr 2 or 3-digit Mobile Network Code in string format
      * @param alphal long alpha Operator Name String or Enhanced Operator Name String
      * @param alphas short alpha Operator Name String or Enhanced Operator Name String
+     * @param additionalPlmns a list of additional PLMN IDs broadcast by the cell
+     * @param csgInfo info about the closed subscriber group broadcast by the cell
      *
      * @hide
      */
     public CellIdentityLte(int ci, int pci, int tac, int earfcn, int bandwidth, String mccStr,
-            String mncStr, String alphal, String alphas) {
+            String mncStr, String alphal, String alphas, List<String> additionalPlmns,
+            ClosedSubscriberGroupInfo csgInfo) {
         super(TAG, CellInfo.TYPE_LTE, mccStr, mncStr, alphal, alphas);
         mCi = inRangeOrUnavailable(ci, 0, MAX_CI);
         mPci = inRangeOrUnavailable(pci, 0, MAX_PCI);
         mTac = inRangeOrUnavailable(tac, 0, MAX_TAC);
         mEarfcn = inRangeOrUnavailable(earfcn, 0, MAX_EARFCN);
         mBandwidth = inRangeOrUnavailable(bandwidth, 0, MAX_BANDWIDTH);
+        mAdditionalPlmns = additionalPlmns;
+        mCsgInfo = csgInfo;
     }
 
     /** @hide */
     public CellIdentityLte(android.hardware.radio.V1_0.CellIdentityLte cid) {
-        this(cid.ci, cid.pci, cid.tac, cid.earfcn, CellInfo.UNAVAILABLE, cid.mcc, cid.mnc, "", "");
+        this(cid.ci, cid.pci, cid.tac, cid.earfcn,
+                CellInfo.UNAVAILABLE, cid.mcc, cid.mnc, "", "", Collections.emptyList(), null);
     }
 
     /** @hide */
     public CellIdentityLte(android.hardware.radio.V1_2.CellIdentityLte cid) {
         this(cid.base.ci, cid.base.pci, cid.base.tac, cid.base.earfcn, cid.bandwidth,
                 cid.base.mcc, cid.base.mnc, cid.operatorNames.alphaLong,
-                cid.operatorNames.alphaShort);
+                cid.operatorNames.alphaShort, Collections.emptyList(), null);
+    }
+
+    /** @hide */
+    public CellIdentityLte(android.hardware.radio.V1_5.CellIdentityLte cid) {
+        this(cid.base.base.ci, cid.base.base.pci, cid.base.base.tac, cid.base.base.earfcn,
+                cid.base.bandwidth, cid.base.base.mcc, cid.base.base.mnc,
+                cid.base.operatorNames.alphaLong, cid.base.operatorNames.alphaShort,
+                cid.additionalPlmns, cid.optionalCsgInfo.csgInfo() != null
+                        ? new ClosedSubscriberGroupInfo(cid.optionalCsgInfo.csgInfo()) : null);
     }
 
     private CellIdentityLte(CellIdentityLte cid) {
         this(cid.mCi, cid.mPci, cid.mTac, cid.mEarfcn, cid.mBandwidth, cid.mMccStr,
-                cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort);
+                cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort, cid.mAdditionalPlmns, cid.mCsgInfo);
     }
 
     /** @hide */
@@ -125,7 +149,7 @@
     public @NonNull CellIdentityLte sanitizeLocationInfo() {
         return new CellIdentityLte(CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE,
                 CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE,
-                mMccStr, mMncStr, mAlphaLong, mAlphaShort);
+                mMccStr, mMncStr, mAlphaLong, mAlphaShort, mAdditionalPlmns, null);
     }
 
     CellIdentityLte copy() {
@@ -185,6 +209,19 @@
     }
 
     /**
+     * Get bands of the cell
+     *
+     * Reference: 3GPP TS 36.101 section 5.5
+     *
+     * @return List of band number or empty list if not available.
+     */
+    @NonNull
+    public List<Integer> getBands() {
+        // Todo: Add actual support
+        return Collections.emptyList();
+    }
+
+    /**
      * @return Cell bandwidth in kHz,
      *         {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
      */
@@ -223,6 +260,22 @@
     }
 
     /**
+     * @return a list of additional PLMN IDs supported by this cell.
+     */
+    @NonNull
+    public List<String> getAdditionalPlmns() {
+        return mAdditionalPlmns;
+    }
+
+    /**
+     * @return closed subscriber group information about the cell if available, otherwise null.
+     */
+    @Nullable
+    public ClosedSubscriberGroupInfo getClosedSubscriberGroupInfo() {
+        return mCsgInfo;
+    }
+
+    /**
      * A hack to allow tunneling of LTE information via GsmCellLocation
      * so that older Network Location Providers can return some information
      * on LTE only networks, see bug 9228974.
@@ -247,7 +300,8 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mCi, mPci, mTac, super.hashCode());
+        return Objects.hash(mCi, mPci, mTac,
+                mAdditionalPlmns.hashCode(), mCsgInfo, super.hashCode());
     }
 
     @Override
@@ -268,6 +322,8 @@
                 && mBandwidth == o.mBandwidth
                 && TextUtils.equals(mMccStr, o.mMccStr)
                 && TextUtils.equals(mMncStr, o.mMncStr)
+                && mAdditionalPlmns.equals(o.mAdditionalPlmns)
+                && Objects.equals(mCsgInfo, o.mCsgInfo)
                 && super.equals(other);
     }
 
@@ -283,6 +339,8 @@
         .append(" mMnc=").append(mMncStr)
         .append(" mAlphaLong=").append(mAlphaLong)
         .append(" mAlphaShort=").append(mAlphaShort)
+        .append(" mAdditionalPlmns=").append(mAdditionalPlmns)
+        .append(" mCsgInfo=").append(mCsgInfo)
         .append("}").toString();
     }
 
@@ -296,6 +354,8 @@
         dest.writeInt(mTac);
         dest.writeInt(mEarfcn);
         dest.writeInt(mBandwidth);
+        dest.writeList(mAdditionalPlmns);
+        dest.writeParcelable(mCsgInfo, flags);
     }
 
     /** Construct from Parcel, type has already been processed */
@@ -306,7 +366,8 @@
         mTac = in.readInt();
         mEarfcn = in.readInt();
         mBandwidth = in.readInt();
-
+        mAdditionalPlmns = in.readArrayList(null);
+        mCsgInfo = in.readParcelable(null);
         if (DBG) log(toString());
     }
 
diff --git a/telephony/java/android/telephony/CellIdentityNr.java b/telephony/java/android/telephony/CellIdentityNr.java
index f08a580..d4f181f 100644
--- a/telephony/java/android/telephony/CellIdentityNr.java
+++ b/telephony/java/android/telephony/CellIdentityNr.java
@@ -20,8 +20,12 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Parcel;
+import android.telephony.AccessNetworkConstants.NgranBands.NgranBand;
 import android.telephony.gsm.GsmCellLocation;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -39,40 +43,58 @@
     private final int mPci;
     private final int mTac;
     private final long mNci;
+    private final List<Integer> mBands;
+
+    // a list of additional PLMN-IDs reported for this cell
+    private final List<String> mAdditionalPlmns;
 
     /**
      *
      * @param pci Physical Cell Id in range [0, 1007].
      * @param tac 16-bit Tracking Area Code.
      * @param nrArfcn NR Absolute Radio Frequency Channel Number, in range [0, 3279165].
+     * @param bands Bands used by the cell. Band number defined in 3GPP TS 38.101-1 and TS 38.101-2.
      * @param mccStr 3-digit Mobile Country Code in string format.
      * @param mncStr 2 or 3-digit Mobile Network Code in string format.
      * @param nci The 36-bit NR Cell Identity in range [0, 68719476735].
      * @param alphal long alpha Operator Name String or Enhanced Operator Name String.
      * @param alphas short alpha Operator Name String or Enhanced Operator Name String.
+     * @param additionalPlmns a list of additional PLMN IDs broadcast by the cell
      *
      * @hide
      */
-    public CellIdentityNr(int pci, int tac, int nrArfcn, String mccStr, String mncStr,
-            long nci, String alphal, String alphas) {
+    public CellIdentityNr(int pci, int tac, int nrArfcn, @NgranBand List<Integer> bands,
+                          String mccStr, String mncStr, long nci, String alphal, String alphas,
+                          List<String> additionalPlmns) {
         super(TAG, CellInfo.TYPE_NR, mccStr, mncStr, alphal, alphas);
         mPci = inRangeOrUnavailable(pci, 0, MAX_PCI);
         mTac = inRangeOrUnavailable(tac, 0, MAX_TAC);
         mNrArfcn = inRangeOrUnavailable(nrArfcn, 0, MAX_NRARFCN);
+        mBands = new ArrayList<>(bands);
         mNci = inRangeOrUnavailable(nci, 0, MAX_NCI);
+        mAdditionalPlmns = new ArrayList<>(additionalPlmns);
     }
 
     /** @hide */
     public CellIdentityNr(android.hardware.radio.V1_4.CellIdentityNr cid) {
-        this(cid.pci, cid.tac, cid.nrarfcn, cid.mcc, cid.mnc, cid.nci, cid.operatorNames.alphaLong,
-                cid.operatorNames.alphaShort);
+        this(cid.pci, cid.tac, cid.nrarfcn, Collections.emptyList(), cid.mcc, cid.mnc, cid.nci,
+                cid.operatorNames.alphaLong, cid.operatorNames.alphaShort,
+                Collections.emptyList());
+    }
+
+    /** @hide */
+    public CellIdentityNr(android.hardware.radio.V1_5.CellIdentityNr cid) {
+        this(cid.base.pci, cid.base.tac, cid.base.nrarfcn, cid.bands, cid.base.mcc, cid.base.mnc,
+                cid.base.nci, cid.base.operatorNames.alphaLong,
+                cid.base.operatorNames.alphaShort, cid.additionalPlmns);
     }
 
     /** @hide */
     @Override
     public @NonNull CellIdentityNr sanitizeLocationInfo() {
-        return new CellIdentityNr(CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE,
-                mMccStr, mMncStr, CellInfo.UNAVAILABLE, mAlphaLong, mAlphaShort);
+        return new CellIdentityNr(CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, mNrArfcn,
+                mBands, mMccStr, mMncStr, CellInfo.UNAVAILABLE, mAlphaLong, mAlphaShort,
+                mAdditionalPlmns);
     }
 
     /**
@@ -87,7 +109,8 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(super.hashCode(), mPci, mTac, mNrArfcn, mNci);
+        return Objects.hash(super.hashCode(), mPci, mTac,
+                mNrArfcn, mBands.hashCode(), mNci, mAdditionalPlmns.hashCode());
     }
 
     @Override
@@ -98,7 +121,8 @@
 
         CellIdentityNr o = (CellIdentityNr) other;
         return super.equals(o) && mPci == o.mPci && mTac == o.mTac && mNrArfcn == o.mNrArfcn
-                && mNci == o.mNci;
+                && mBands.equals(o.mBands) && mNci == o.mNci
+                && mAdditionalPlmns.equals(o.mAdditionalPlmns);
     }
 
     /**
@@ -125,6 +149,20 @@
     }
 
     /**
+     * Get bands of the cell
+     *
+     * Reference: TS 38.101-1 table 5.2-1
+     * Reference: TS 38.101-2 table 5.2-1
+     *
+     * @return List of band number or empty list if not available.
+     */
+    @NgranBand
+    @NonNull
+    public List<Integer> getBands() {
+        return Collections.unmodifiableList(mBands);
+    }
+
+    /**
      * Get the physical cell id.
      * @return Integer value in range [0, 1007] or {@link CellInfo#UNAVAILABLE} if unknown.
      */
@@ -158,17 +196,33 @@
         return mMncStr;
     }
 
+    /** @hide */
+    @Override
+    public int getChannelNumber() {
+        return mNrArfcn;
+    }
+
+    /**
+     * @return a list of additional PLMN IDs supported by this cell.
+     */
+    @NonNull
+    public List<String> getAdditionalPlmns() {
+        return Collections.unmodifiableList(mAdditionalPlmns);
+    }
+
     @Override
     public String toString() {
         return new StringBuilder(TAG + ":{")
                 .append(" mPci = ").append(mPci)
                 .append(" mTac = ").append(mTac)
                 .append(" mNrArfcn = ").append(mNrArfcn)
+                .append(" mBands = ").append(mBands)
                 .append(" mMcc = ").append(mMccStr)
                 .append(" mMnc = ").append(mMncStr)
                 .append(" mNci = ").append(mNci)
                 .append(" mAlphaLong = ").append(mAlphaLong)
                 .append(" mAlphaShort = ").append(mAlphaShort)
+                .append(" mAdditionalPlmns = ").append(mAdditionalPlmns)
                 .append(" }")
                 .toString();
     }
@@ -179,7 +233,9 @@
         dest.writeInt(mPci);
         dest.writeInt(mTac);
         dest.writeInt(mNrArfcn);
+        dest.writeList(mBands);
         dest.writeLong(mNci);
+        dest.writeList(mAdditionalPlmns);
     }
 
     /** Construct from Parcel, type has already been processed */
@@ -188,7 +244,9 @@
         mPci = in.readInt();
         mTac = in.readInt();
         mNrArfcn = in.readInt();
+        mBands = in.readArrayList(null);
         mNci = in.readLong();
+        mAdditionalPlmns = in.readArrayList(null);
     }
 
     /** Implement the Parcelable interface */
diff --git a/telephony/java/android/telephony/CellIdentityTdscdma.java b/telephony/java/android/telephony/CellIdentityTdscdma.java
index 4bb3a95..2ff351c 100644
--- a/telephony/java/android/telephony/CellIdentityTdscdma.java
+++ b/telephony/java/android/telephony/CellIdentityTdscdma.java
@@ -21,6 +21,8 @@
 import android.os.Parcel;
 import android.telephony.gsm.GsmCellLocation;
 
+import java.util.Collections;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -46,6 +48,11 @@
     // 16-bit UMTS Absolute RF Channel Number described in TS 25.101 sec. 5.4.3
     private final int mUarfcn;
 
+    // a list of additional PLMN-IDs reported for this cell
+    private final List<String> mAdditionalPlmns;
+
+    private ClosedSubscriberGroupInfo mCsgInfo;
+
     /**
      * @hide
      */
@@ -55,6 +62,8 @@
         mCid = CellInfo.UNAVAILABLE;
         mCpid = CellInfo.UNAVAILABLE;
         mUarfcn = CellInfo.UNAVAILABLE;
+        mAdditionalPlmns = Collections.emptyList();
+        mCsgInfo = null;
     }
 
     /**
@@ -68,39 +77,57 @@
      * @param uarfcn 16-bit UMTS Absolute RF Channel Number described in TS 25.101 sec. 5.4.3
      * @param alphal long alpha Operator Name String or Enhanced Operator Name String
      * @param alphas short alpha Operator Name String or Enhanced Operator Name String
+     * @param additionalPlmns a list of additional PLMN IDs broadcast by the cell
+     * @param csgInfo info about the closed subscriber group broadcast by the cell
      *
      * @hide
      */
     public CellIdentityTdscdma(String mcc, String mnc, int lac, int cid, int cpid, int uarfcn,
-            String alphal, String alphas) {
+            String alphal, String alphas, @NonNull List<String> additionalPlmns,
+            ClosedSubscriberGroupInfo csgInfo) {
         super(TAG, CellInfo.TYPE_TDSCDMA, mcc, mnc, alphal, alphas);
         mLac = inRangeOrUnavailable(lac, 0, MAX_LAC);
         mCid = inRangeOrUnavailable(cid, 0, MAX_CID);
         mCpid = inRangeOrUnavailable(cpid, 0, MAX_CPID);
         mUarfcn = inRangeOrUnavailable(uarfcn, 0, MAX_UARFCN);
+        mAdditionalPlmns = additionalPlmns;
+        mCsgInfo = csgInfo;
     }
 
     private CellIdentityTdscdma(CellIdentityTdscdma cid) {
         this(cid.mMccStr, cid.mMncStr, cid.mLac, cid.mCid,
-                cid.mCpid, cid.mUarfcn, cid.mAlphaLong, cid.mAlphaShort);
+                cid.mCpid, cid.mUarfcn, cid.mAlphaLong,
+                cid.mAlphaShort, cid.mAdditionalPlmns, cid.mCsgInfo);
     }
 
     /** @hide */
     public CellIdentityTdscdma(android.hardware.radio.V1_0.CellIdentityTdscdma cid) {
-        this(cid.mcc, cid.mnc, cid.lac, cid.cid, cid.cpid, CellInfo.UNAVAILABLE, "", "");
+        this(cid.mcc, cid.mnc, cid.lac, cid.cid, cid.cpid, CellInfo.UNAVAILABLE, "", "",
+                Collections.emptyList(), null);
     }
 
     /** @hide */
     public CellIdentityTdscdma(android.hardware.radio.V1_2.CellIdentityTdscdma cid) {
         this(cid.base.mcc, cid.base.mnc, cid.base.lac, cid.base.cid, cid.base.cpid,
-                cid.uarfcn, cid.operatorNames.alphaLong, cid.operatorNames.alphaShort);
+                cid.uarfcn, cid.operatorNames.alphaLong, cid.operatorNames.alphaShort,
+                Collections.emptyList(), null);
+    }
+
+    /** @hide */
+    public CellIdentityTdscdma(android.hardware.radio.V1_5.CellIdentityTdscdma cid) {
+        this(cid.base.base.mcc, cid.base.base.mnc, cid.base.base.lac, cid.base.base.cid,
+                cid.base.base.cpid, cid.base.uarfcn, cid.base.operatorNames.alphaLong,
+                cid.base.operatorNames.alphaShort,
+                cid.additionalPlmns, cid.optionalCsgInfo.csgInfo() != null
+                        ? new ClosedSubscriberGroupInfo(cid.optionalCsgInfo.csgInfo()) : null);
     }
 
     /** @hide */
     @Override
     public @NonNull CellIdentityTdscdma sanitizeLocationInfo() {
         return new CellIdentityTdscdma(mMccStr, mMncStr, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE,
-                CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, mAlphaLong, mAlphaShort);
+                CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, mAlphaLong, mAlphaShort,
+                mAdditionalPlmns, null);
     }
 
     CellIdentityTdscdma copy() {
@@ -171,6 +198,22 @@
         return mUarfcn;
     }
 
+    /**
+     * @return a list of additional PLMN IDs supported by this cell.
+     */
+    @NonNull
+    public List<String> getAdditionalPlmns() {
+        return mAdditionalPlmns;
+    }
+
+    /**
+     * @return closed subscriber group information about the cell if available, otherwise null.
+     */
+    @Nullable
+    public ClosedSubscriberGroupInfo getClosedSubscriberGroupInfo() {
+        return mCsgInfo;
+    }
+
     /** @hide */
     @NonNull
     @Override
@@ -198,12 +241,15 @@
                 && mCid == o.mCid
                 && mCpid == o.mCpid
                 && mUarfcn == o.mUarfcn
+                && mAdditionalPlmns.equals(o.mAdditionalPlmns)
+                && Objects.equals(mCsgInfo, o.mCsgInfo)
                 && super.equals(other);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mLac, mCid, mCpid, mUarfcn, super.hashCode());
+        return Objects.hash(mLac, mCid, mCpid, mUarfcn,
+                mAdditionalPlmns.hashCode(), mCsgInfo, super.hashCode());
     }
 
     @Override
@@ -217,6 +263,8 @@
         .append(" mCid=").append(mCid)
         .append(" mCpid=").append(mCpid)
         .append(" mUarfcn=").append(mUarfcn)
+        .append(" mAdditionalPlmns=").append(mAdditionalPlmns)
+        .append(" mCsgInfo=").append(mCsgInfo)
         .append("}").toString();
     }
 
@@ -235,6 +283,8 @@
         dest.writeInt(mCid);
         dest.writeInt(mCpid);
         dest.writeInt(mUarfcn);
+        dest.writeList(mAdditionalPlmns);
+        dest.writeParcelable(mCsgInfo, flags);
     }
 
     /** Construct from Parcel, type has already been processed */
@@ -244,6 +294,8 @@
         mCid = in.readInt();
         mCpid = in.readInt();
         mUarfcn = in.readInt();
+        mAdditionalPlmns = in.readArrayList(null);
+        mCsgInfo = in.readParcelable(null);
         if (DBG) log(toString());
     }
 
diff --git a/telephony/java/android/telephony/CellIdentityWcdma.java b/telephony/java/android/telephony/CellIdentityWcdma.java
index 4ecd134..9be42a1 100644
--- a/telephony/java/android/telephony/CellIdentityWcdma.java
+++ b/telephony/java/android/telephony/CellIdentityWcdma.java
@@ -23,6 +23,8 @@
 import android.telephony.gsm.GsmCellLocation;
 import android.text.TextUtils;
 
+import java.util.Collections;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -47,6 +49,12 @@
     @UnsupportedAppUsage
     private final int mUarfcn;
 
+    // a list of additional PLMN-IDs reported for this cell
+    private final List<String> mAdditionalPlmns;
+
+    @Nullable
+    private final ClosedSubscriberGroupInfo mCsgInfo;
+
     /**
      * @hide
      */
@@ -56,6 +64,8 @@
         mCid = CellInfo.UNAVAILABLE;
         mPsc = CellInfo.UNAVAILABLE;
         mUarfcn = CellInfo.UNAVAILABLE;
+        mAdditionalPlmns = Collections.emptyList();
+        mCsgInfo = null;
     }
 
     /**
@@ -68,33 +78,49 @@
      * @param mncStr 2 or 3-digit Mobile Network Code in string format
      * @param alphal long alpha Operator Name String or Enhanced Operator Name String
      * @param alphas short alpha Operator Name String or Enhanced Operator Name String
+     * @param additionalPlmns a list of additional PLMN IDs broadcast by the cell
+     * @param csgInfo info about the closed subscriber group broadcast by the cell
      *
      * @hide
      */
     public CellIdentityWcdma (int lac, int cid, int psc, int uarfcn,
-                              String mccStr, String mncStr, String alphal, String alphas) {
+                              String mccStr, String mncStr, String alphal, String alphas,
+                              @NonNull List<String> additionalPlmns,
+                              @Nullable ClosedSubscriberGroupInfo csgInfo) {
         super(TAG, CellInfo.TYPE_WCDMA, mccStr, mncStr, alphal, alphas);
         mLac = inRangeOrUnavailable(lac, 0, MAX_LAC);
         mCid = inRangeOrUnavailable(cid, 0, MAX_CID);
         mPsc = inRangeOrUnavailable(psc, 0, MAX_PSC);
         mUarfcn = inRangeOrUnavailable(uarfcn, 0, MAX_UARFCN);
+        mAdditionalPlmns = additionalPlmns;
+        mCsgInfo = csgInfo;
     }
 
     /** @hide */
     public CellIdentityWcdma(android.hardware.radio.V1_0.CellIdentityWcdma cid) {
-        this(cid.lac, cid.cid, cid.psc, cid.uarfcn, cid.mcc, cid.mnc, "", "");
+        this(cid.lac, cid.cid, cid.psc, cid.uarfcn, cid.mcc, cid.mnc, "", "",
+                Collections.emptyList(), null);
     }
 
     /** @hide */
     public CellIdentityWcdma(android.hardware.radio.V1_2.CellIdentityWcdma cid) {
         this(cid.base.lac, cid.base.cid, cid.base.psc, cid.base.uarfcn,
                 cid.base.mcc, cid.base.mnc, cid.operatorNames.alphaLong,
-                cid.operatorNames.alphaShort);
+                cid.operatorNames.alphaShort, Collections.emptyList(), null);
+    }
+
+    /** @hide */
+    public CellIdentityWcdma(android.hardware.radio.V1_5.CellIdentityWcdma cid) {
+        this(cid.base.base.lac, cid.base.base.cid, cid.base.base.psc, cid.base.base.uarfcn,
+                cid.base.base.mcc, cid.base.base.mnc, cid.base.operatorNames.alphaLong,
+                cid.base.operatorNames.alphaShort, cid.additionalPlmns,
+                cid.optionalCsgInfo.csgInfo() != null
+                        ? new ClosedSubscriberGroupInfo(cid.optionalCsgInfo.csgInfo()) : null);
     }
 
     private CellIdentityWcdma(CellIdentityWcdma cid) {
         this(cid.mLac, cid.mCid, cid.mPsc, cid.mUarfcn, cid.mMccStr,
-                cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort);
+                cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort, cid.mAdditionalPlmns, cid.mCsgInfo);
     }
 
     /** @hide */
@@ -102,7 +128,7 @@
     public @NonNull CellIdentityWcdma sanitizeLocationInfo() {
         return new CellIdentityWcdma(CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE,
                 CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, mMccStr, mMncStr,
-                mAlphaLong, mAlphaShort);
+                mAlphaLong, mAlphaShort, mAdditionalPlmns, null);
     }
 
     CellIdentityWcdma copy() {
@@ -180,7 +206,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mLac, mCid, mPsc, super.hashCode());
+        return Objects.hash(mLac, mCid, mPsc, mAdditionalPlmns.hashCode(), super.hashCode());
     }
 
     /**
@@ -197,6 +223,22 @@
         return mUarfcn;
     }
 
+    /**
+     * @return a list of additional PLMN IDs supported by this cell.
+     */
+    @NonNull
+    public List<String> getAdditionalPlmns() {
+        return mAdditionalPlmns;
+    }
+
+    /**
+     * @return closed subscriber group information about the cell if available, otherwise null.
+     */
+    @Nullable
+    public ClosedSubscriberGroupInfo getClosedSubscriberGroupInfo() {
+        return mCsgInfo;
+    }
+
     /** @hide */
     @NonNull
     @Override
@@ -228,6 +270,8 @@
                 && mUarfcn == o.mUarfcn
                 && TextUtils.equals(mMccStr, o.mMccStr)
                 && TextUtils.equals(mMncStr, o.mMncStr)
+                && mAdditionalPlmns.equals(o.mAdditionalPlmns)
+                && Objects.equals(mCsgInfo, o.mCsgInfo)
                 && super.equals(other);
     }
 
@@ -242,6 +286,8 @@
         .append(" mMnc=").append(mMncStr)
         .append(" mAlphaLong=").append(mAlphaLong)
         .append(" mAlphaShort=").append(mAlphaShort)
+        .append(" mAdditionalPlmns=").append(mAdditionalPlmns)
+        .append(" mCsgInfo=").append(mCsgInfo)
         .append("}").toString();
     }
 
@@ -254,6 +300,8 @@
         dest.writeInt(mCid);
         dest.writeInt(mPsc);
         dest.writeInt(mUarfcn);
+        dest.writeList(mAdditionalPlmns);
+        dest.writeParcelable(mCsgInfo, flags);
     }
 
     /** Construct from Parcel, type has already been processed */
@@ -263,6 +311,8 @@
         mCid = in.readInt();
         mPsc = in.readInt();
         mUarfcn = in.readInt();
+        mAdditionalPlmns = in.readArrayList(null);
+        mCsgInfo = in.readParcelable(null);
         if (DBG) log(toString());
     }
 
diff --git a/telephony/java/android/telephony/CellInfo.java b/telephony/java/android/telephony/CellInfo.java
index 475c99b..ec86c14 100644
--- a/telephony/java/android/telephony/CellInfo.java
+++ b/telephony/java/android/telephony/CellInfo.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.SuppressLint;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.hardware.radio.V1_4.CellInfo.Info;
 import android.os.Parcel;
@@ -179,6 +180,18 @@
      *
      * @return a time stamp in nanos since boot.
      */
+    @SuppressLint("MethodNameUnits")
+    public long getTimestampNanos() {
+        return mTimeStamp;
+    }
+
+    /**
+     * Approximate time this cell information was received from the modem.
+     *
+     * @return a time stamp in nanos since boot.
+     * @deprecated Use {@link #getTimestampNanos} instead.
+     */
+    @Deprecated
     public long getTimeStamp() {
         return mTimeStamp;
     }
diff --git a/telephony/java/android/telephony/CellInfoCdma.java b/telephony/java/android/telephony/CellInfoCdma.java
index 2b1387c..acb21f4 100644
--- a/telephony/java/android/telephony/CellInfoCdma.java
+++ b/telephony/java/android/telephony/CellInfoCdma.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
diff --git a/telephony/java/android/telephony/CellInfoGsm.java b/telephony/java/android/telephony/CellInfoGsm.java
index 4f7c7a9..79a9d44 100644
--- a/telephony/java/android/telephony/CellInfoGsm.java
+++ b/telephony/java/android/telephony/CellInfoGsm.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
diff --git a/telephony/java/android/telephony/CellInfoLte.java b/telephony/java/android/telephony/CellInfoLte.java
index 6d19261..fed3ebf 100644
--- a/telephony/java/android/telephony/CellInfoLte.java
+++ b/telephony/java/android/telephony/CellInfoLte.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
diff --git a/telephony/java/android/telephony/CellInfoTdscdma.java b/telephony/java/android/telephony/CellInfoTdscdma.java
index f1305f5..58ff8c9 100644
--- a/telephony/java/android/telephony/CellInfoTdscdma.java
+++ b/telephony/java/android/telephony/CellInfoTdscdma.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
diff --git a/telephony/java/android/telephony/CellInfoWcdma.java b/telephony/java/android/telephony/CellInfoWcdma.java
index ee5fec8..33f6a55 100644
--- a/telephony/java/android/telephony/CellInfoWcdma.java
+++ b/telephony/java/android/telephony/CellInfoWcdma.java
@@ -18,7 +18,7 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 
 import java.util.Objects;
 
diff --git a/telephony/java/android/telephony/CellLocation.java b/telephony/java/android/telephony/CellLocation.java
index 64776e3..2d0bd52 100644
--- a/telephony/java/android/telephony/CellLocation.java
+++ b/telephony/java/android/telephony/CellLocation.java
@@ -19,7 +19,6 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Bundle;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.telephony.cdma.CdmaCellLocation;
 import android.telephony.gsm.GsmCellLocation;
 
@@ -38,7 +37,11 @@
      */
     public static void requestLocationUpdate() {
         try {
-            ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
+            ITelephony phone = ITelephony.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getTelephonyServiceRegisterer()
+                            .get());
             if (phone != null) {
                 phone.updateServiceLocation();
             }
@@ -50,8 +53,7 @@
     /**
      * Create a new CellLocation from a intent notifier Bundle
      *
-     * This method is used by PhoneStateIntentReceiver and maybe by
-     * external applications.
+     * This method maybe used by external applications.
      *
      * @param bundle Bundle from intent notifier
      * @return newly created CellLocation
diff --git a/telephony/java/android/telephony/CellSignalStrengthCdma.java b/telephony/java/android/telephony/CellSignalStrengthCdma.java
index 1998439..cab3b0c 100644
--- a/telephony/java/android/telephony/CellSignalStrengthCdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthCdma.java
@@ -20,7 +20,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 
 import java.util.Objects;
 
diff --git a/telephony/java/android/telephony/CellSignalStrengthGsm.java b/telephony/java/android/telephony/CellSignalStrengthGsm.java
index a9f3487..28052aa 100644
--- a/telephony/java/android/telephony/CellSignalStrengthGsm.java
+++ b/telephony/java/android/telephony/CellSignalStrengthGsm.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.IntRange;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java
index a6ba9c2..2ef2a52 100644
--- a/telephony/java/android/telephony/CellSignalStrengthLte.java
+++ b/telephony/java/android/telephony/CellSignalStrengthLte.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.IntRange;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
diff --git a/telephony/java/android/telephony/CellSignalStrengthNr.java b/telephony/java/android/telephony/CellSignalStrengthNr.java
index d28d750..4d67bcf 100644
--- a/telephony/java/android/telephony/CellSignalStrengthNr.java
+++ b/telephony/java/android/telephony/CellSignalStrengthNr.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.os.Parcel;
diff --git a/telephony/java/android/telephony/CellSignalStrengthTdscdma.java b/telephony/java/android/telephony/CellSignalStrengthTdscdma.java
index f4a3dbb..3bd9d58 100644
--- a/telephony/java/android/telephony/CellSignalStrengthTdscdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthTdscdma.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.os.Parcel;
diff --git a/telephony/java/android/telephony/CellSignalStrengthWcdma.java b/telephony/java/android/telephony/CellSignalStrengthWcdma.java
index 34b1385..535e952 100644
--- a/telephony/java/android/telephony/CellSignalStrengthWcdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthWcdma.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.IntRange;
 import android.annotation.StringDef;
 import android.compat.annotation.UnsupportedAppUsage;
diff --git a/telephony/java/android/telephony/ClosedSubscriberGroupInfo.aidl b/telephony/java/android/telephony/ClosedSubscriberGroupInfo.aidl
new file mode 100644
index 0000000..cbe7638
--- /dev/null
+++ b/telephony/java/android/telephony/ClosedSubscriberGroupInfo.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** 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;
+
+parcelable ClosedSubscriberGroupInfo;
diff --git a/telephony/java/android/telephony/ClosedSubscriberGroupInfo.java b/telephony/java/android/telephony/ClosedSubscriberGroupInfo.java
new file mode 100644
index 0000000..e7dfe634
--- /dev/null
+++ b/telephony/java/android/telephony/ClosedSubscriberGroupInfo.java
@@ -0,0 +1,156 @@
+/*
+ * 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;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Information to represent a closed subscriber group.
+ */
+public final class ClosedSubscriberGroupInfo implements Parcelable {
+    private static final String TAG = "ClosedSubscriberGroupInfo";
+
+    private final boolean mCsgIndicator;
+
+    private final String mHomeNodebName;
+
+    private final int mCsgIdentity;
+
+    /** @hide */
+    public ClosedSubscriberGroupInfo(boolean csgIndicator, @Nullable String homeNodebName,
+            int csgIdentity) {
+        mCsgIndicator = csgIndicator;
+        mHomeNodebName = (homeNodebName == null) ? "" : homeNodebName;
+        mCsgIdentity = csgIdentity;
+    }
+
+    /** @hide */
+    public ClosedSubscriberGroupInfo(
+            @NonNull android.hardware.radio.V1_5.ClosedSubscriberGroupInfo csgInfo) {
+        this(csgInfo.csgIndication, csgInfo.homeNodebName, csgInfo.csgIdentity);
+    }
+
+    /**
+     * Indicates whether the cell is restricted to only CSG members.
+     *
+     * A cell not broadcasting the CSG Indication but reporting CSG information is considered a
+     * Hybrid Cell.
+     * Refer to the "csg-Indication" field in 3GPP TS 36.331 section 6.2.2
+     * SystemInformationBlockType1.
+     * Also refer to "CSG Indicator" in 3GPP TS 25.331 section 10.2.48.8.1 and TS 25.304.
+     *
+     * @return true if the cell is restricted to group members only.
+     */
+    public boolean getCsgIndicator() {
+        return mCsgIndicator;
+    }
+
+    /**
+     * Returns human-readable name of the closed subscriber group operating this cell (Node-B).
+     *
+     * Refer to "hnb-Name" in TS 36.331 section 6.2.2 SystemInformationBlockType9.
+     * Also refer to "HNB Name" in 3GPP TS25.331 section 10.2.48.8.23 and TS 23.003 section 4.8.
+     *
+     * @return the home Node-B name if available.
+     */
+    public @NonNull String getHomeNodebName() {
+        return mHomeNodebName;
+    }
+
+    /**
+     * The identity of the closed subscriber group that the cell belongs to.
+     *
+     * Refer to "CSG-Identity" in TS 36.336 section 6.3.4.
+     * Also refer to "CSG Identity" in 3GPP TS 25.331 section 10.3.2.8 and TS 23.003 section 4.7.
+     *
+     * @return the unique 27-bit CSG Identity.
+     */
+    @IntRange(from = 0, to = 0x7FFFFFF)
+    public int getCsgIdentity() {
+        return mCsgIdentity;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mCsgIndicator, mHomeNodebName, mCsgIdentity);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof ClosedSubscriberGroupInfo)) {
+            return false;
+        }
+
+        ClosedSubscriberGroupInfo o = (ClosedSubscriberGroupInfo) other;
+        return mCsgIndicator == o.mCsgIndicator && mHomeNodebName == o.mHomeNodebName
+                && mCsgIdentity == o.mCsgIdentity;
+    }
+
+    @Override
+    public String toString() {
+        return new StringBuilder(TAG + ":{")
+                .append(" mCsgIndicator = ").append(mCsgIndicator)
+                .append(" mHomeNodebName = ").append(mHomeNodebName)
+                .append(" mCsgIdentity = ").append(mCsgIdentity)
+                .toString();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int type) {
+        dest.writeBoolean(mCsgIndicator);
+        dest.writeString(mHomeNodebName);
+        dest.writeInt(mCsgIdentity);
+    }
+
+    /** Construct from Parcel, type has already been processed */
+    private ClosedSubscriberGroupInfo(Parcel in) {
+        this(in.readBoolean(), in.readString(), in.readInt());
+    }
+
+    /**
+     * Implement the Parcelable interface
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Implement the Parcelable interface */
+    public static final @android.annotation.NonNull Creator<ClosedSubscriberGroupInfo> CREATOR =
+            new Creator<ClosedSubscriberGroupInfo>() {
+                @Override
+                public ClosedSubscriberGroupInfo createFromParcel(Parcel in) {
+                    return createFromParcelBody(in);
+                }
+
+                @Override
+                public ClosedSubscriberGroupInfo[] newArray(int size) {
+                    return new ClosedSubscriberGroupInfo[size];
+                }
+            };
+
+    /** @hide */
+    protected static ClosedSubscriberGroupInfo createFromParcelBody(Parcel in) {
+        return new ClosedSubscriberGroupInfo(in);
+    }
+}
diff --git a/telephony/java/android/telephony/ImsManager.java b/telephony/java/android/telephony/ImsManager.java
index 39af34c..9b4292f 100644
--- a/telephony/java/android/telephony/ImsManager.java
+++ b/telephony/java/android/telephony/ImsManager.java
@@ -17,6 +17,8 @@
 package android.telephony.ims;
 
 import android.annotation.NonNull;
+import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
@@ -35,7 +37,63 @@
 
     private Context mContext;
 
-    /** @hide */
+    /**
+     * <p>Broadcast Action: Indicates that an IMS operation was rejected by the network due to it
+     * not being authorized on the network.
+     * May include the {@link SubscriptionManager#EXTRA_SUBSCRIPTION_INDEX} extra to also specify
+     * which subscription the operation was rejected for.
+     * <p class="note">
+     * Carrier applications may listen to this broadcast to be notified of possible IMS provisioning
+     * issues.
+     */
+    // Moved from TelephonyIntents, need to keep backwards compatibility with OEM apps that have
+    // this value hard-coded in BroadcastReceiver.
+    @SuppressLint("ActionValue")
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION =
+            "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION";
+
+    /**
+     * An intent action indicating that IMS registration for WiFi calling has resulted in an error.
+     * Contains error information that should be displayed to the user.
+     * <p>
+     * This intent will contain the following extra key/value pairs:
+     * {@link #EXTRA_WFC_REGISTRATION_FAILURE_TITLE}
+     * and {@link #EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE}, which contain carrier specific
+     * error information that should be displayed to the user.
+     * <p>
+     * Usage: This intent is sent as an ordered broadcast. If the settings application is going
+     * to show the error information specified to the user, it should respond to
+     * {@link android.content.BroadcastReceiver#setResultCode(int)} with
+     * {@link android.app.Activity#RESULT_CANCELED}, which will signal to the framework that the
+     * event was handled. If the framework does not receive a response to the ordered broadcast,
+     * it will then show a notification to the user indicating that there was a registration
+     * failure.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_WFC_IMS_REGISTRATION_ERROR =
+            "android.telephony.ims.action.WFC_IMS_REGISTRATION_ERROR";
+
+    /**
+     * An extra key corresponding to a String value which contains the carrier specific title to be
+     * displayed as part of the message shown to the user when there is an error registering for
+     * WiFi calling.
+     */
+    public static final String EXTRA_WFC_REGISTRATION_FAILURE_TITLE =
+            "android.telephony.ims.extra.WFC_REGISTRATION_FAILURE_TITLE";
+
+    /**
+     * An extra key corresponding to a String value which contains the carrier specific message to
+     * be displayed as part of the message shown to the user when there is an error registering for
+     * WiFi calling.
+     */
+    public static final String EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE =
+            "android.telephony.ims.extra.WFC_REGISTRATION_FAILURE_MESSAGE";
+
+    /**
+     * Use {@link Context#getSystemService(String)} to get an instance of this class.
+     * @hide
+     */
     public ImsManager(@NonNull Context context) {
         mContext = context;
     }
diff --git a/telephony/java/android/telephony/ModemInfo.java b/telephony/java/android/telephony/ModemInfo.java
deleted file mode 100644
index c0833af..0000000
--- a/telephony/java/android/telephony/ModemInfo.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * 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 android.telephony;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.util.Objects;
-
-/**
- * Information of a single logical modem indicating
- * its id, supported rats and whether it supports voice or data, etc.
- * @hide
- */
-public class ModemInfo implements Parcelable {
-    public final int modemId;
-    public final int rat; /* bitset */
-    public final boolean isVoiceSupported;
-    public final boolean isDataSupported;
-
-    // TODO b/121394331: Clean up this class after V1_1.PhoneCapability cleanup.
-    public ModemInfo(int modemId) {
-        this(modemId, 0, true, true);
-    }
-
-    public ModemInfo(int modemId, int rat, boolean isVoiceSupported, boolean isDataSupported) {
-        this.modemId = modemId;
-        this.rat = rat;
-        this.isVoiceSupported = isVoiceSupported;
-        this.isDataSupported = isDataSupported;
-    }
-
-    public ModemInfo(Parcel in) {
-        modemId = in.readInt();
-        rat = in.readInt();
-        isVoiceSupported = in.readBoolean();
-        isDataSupported = in.readBoolean();
-    }
-
-    @Override
-    public String toString() {
-        return "modemId=" + modemId + " rat=" + rat + " isVoiceSupported:" + isVoiceSupported
-                + " isDataSupported:" + isDataSupported;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(modemId, rat, isVoiceSupported, isDataSupported);
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (o == null || !(o instanceof ModemInfo) || hashCode() != o.hashCode()) {
-            return false;
-        }
-
-        if (this == o) {
-            return true;
-        }
-
-        ModemInfo s = (ModemInfo) o;
-
-        return (modemId == s.modemId
-                && rat == s.rat
-                && isVoiceSupported == s.isVoiceSupported
-                && isDataSupported == s.isDataSupported);
-    }
-
-    /**
-     * {@link Parcelable#describeContents}
-     */
-    public @ContentsFlags int describeContents() {
-        return 0;
-    }
-
-    /**
-     * {@link Parcelable#writeToParcel}
-     */
-    public void writeToParcel(Parcel dest, @WriteFlags int flags) {
-        dest.writeInt(modemId);
-        dest.writeInt(rat);
-        dest.writeBoolean(isVoiceSupported);
-        dest.writeBoolean(isDataSupported);
-    }
-
-    public static final @android.annotation.NonNull Parcelable.Creator<ModemInfo> CREATOR = new Parcelable.Creator() {
-        public ModemInfo createFromParcel(Parcel in) {
-            return new ModemInfo(in);
-        }
-
-        public ModemInfo[] newArray(int size) {
-            return new ModemInfo[size];
-        }
-    };
-}
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index cbd5ed6..32ffb75 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -36,10 +36,7 @@
 
 /**
  * Description of a mobile network registration info
- * @hide
  */
-@SystemApi
-@TestApi
 public final class NetworkRegistrationInfo implements Parcelable {
     /**
      * Network domain
@@ -51,9 +48,9 @@
 
     /** Unknown / Unspecified domain */
     public static final int DOMAIN_UNKNOWN = 0;
-    /** Circuit switching domain */
+    /** Circuit switched domain */
     public static final int DOMAIN_CS = android.hardware.radio.V1_5.Domain.CS;
-    /** Packet switching domain */
+    /** Packet switched domain */
     public static final int DOMAIN_PS = android.hardware.radio.V1_5.Domain.PS;
     /** Applicable to both CS and PS Domain */
     public static final int DOMAIN_CS_PS = DOMAIN_CS | DOMAIN_PS;
@@ -69,17 +66,41 @@
                     REGISTRATION_STATE_UNKNOWN, REGISTRATION_STATE_ROAMING})
     public @interface RegistrationState {}
 
-    /** Not registered. The device is not currently searching a new operator to register. */
+    /**
+     * Not registered. The device is not currently searching a new operator to register.
+     * @hide
+     */
+    @SystemApi @TestApi
     public static final int REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING = 0;
-    /** Registered on home network. */
+    /**
+     * Registered on home network.
+     * @hide
+     */
+    @SystemApi @TestApi
     public static final int REGISTRATION_STATE_HOME = 1;
-    /** Not registered. The device is currently searching a new operator to register. */
+    /**
+     * Not registered. The device is currently searching a new operator to register.
+     * @hide
+     */
+    @SystemApi @TestApi
     public static final int REGISTRATION_STATE_NOT_REGISTERED_SEARCHING = 2;
-    /** Registration denied. */
+    /**
+     * Registration denied.
+     * @hide
+     */
+    @SystemApi @TestApi
     public static final int REGISTRATION_STATE_DENIED = 3;
-    /** Registration state is unknown. */
+    /**
+     * Registration state is unknown.
+     * @hide
+     */
+    @SystemApi @TestApi
     public static final int REGISTRATION_STATE_UNKNOWN = 4;
-    /** Registered on roaming network. */
+    /**
+     * Registered on roaming network.
+     * @hide
+     */
+    @SystemApi @TestApi
     public static final int REGISTRATION_STATE_ROAMING = 5;
 
     /** @hide */
@@ -92,7 +113,6 @@
     /**
      * The device isn't camped on an LTE cell or the LTE cell doesn't support E-UTRA-NR
      * Dual Connectivity(EN-DC).
-     * @hide
      */
     public static final int NR_STATE_NONE = 0;
 
@@ -100,7 +120,6 @@
      * The device is camped on an LTE cell that supports E-UTRA-NR Dual Connectivity(EN-DC) but
      * either the use of dual connectivity with NR(DCNR) is restricted or NR is not supported by
      * the selected PLMN.
-     * @hide
      */
     public static final int NR_STATE_RESTRICTED = 1;
 
@@ -108,14 +127,12 @@
      * The device is camped on an LTE cell that supports E-UTRA-NR Dual Connectivity(EN-DC) and both
      * the use of dual connectivity with NR(DCNR) is not restricted and NR is supported by the
      * selected PLMN.
-     * @hide
      */
     public static final int NR_STATE_NOT_RESTRICTED = 2;
 
     /**
      * The device is camped on an LTE cell that supports E-UTRA-NR Dual Connectivity(EN-DC) and
      * also connected to at least one 5G cell as a secondary serving cell.
-     * @hide
      */
     public static final int NR_STATE_CONNECTED = 3;
 
@@ -129,22 +146,34 @@
                     SERVICE_TYPE_VIDEO, SERVICE_TYPE_EMERGENCY})
     public @interface ServiceType {}
 
-    /** Unkown service */
+    /**
+     * Unknown service
+     */
     public static final int SERVICE_TYPE_UNKNOWN    = 0;
 
-    /** Voice service */
+    /**
+     * Voice service
+     */
     public static final int SERVICE_TYPE_VOICE      = 1;
 
-    /** Data service */
+    /**
+     * Data service
+     */
     public static final int SERVICE_TYPE_DATA       = 2;
 
-    /** SMS service */
+    /**
+     * SMS service
+     */
     public static final int SERVICE_TYPE_SMS        = 3;
 
-    /** Video service */
+    /**
+     * Video service
+     */
     public static final int SERVICE_TYPE_VIDEO      = 4;
 
-    /** Emergency service */
+    /**
+     * Emergency service
+     */
     public static final int SERVICE_TYPE_EMERGENCY  = 5;
 
     @Domain
@@ -330,9 +359,7 @@
      * Get the 5G NR connection state.
      *
      * @return the 5G NR connection state.
-     * @hide
      */
-    @SystemApi
     public @NRState int getNrState() {
         return mNrState;
     }
@@ -344,7 +371,10 @@
 
     /**
      * @return The registration state.
+     *
+     * @hide
      */
+    @SystemApi @TestApi
     public @RegistrationState int getRegistrationState() {
         return mRegistrationState;
     }
@@ -352,6 +382,21 @@
     /**
      * @return {@code true} if registered on roaming network, {@code false} otherwise.
      */
+    public boolean isRegistered() {
+        return mRegistrationState == REGISTRATION_STATE_HOME
+                || mRegistrationState == REGISTRATION_STATE_ROAMING;
+    }
+
+    /**
+     * @return {@code true} if registered on roaming network, {@code false} otherwise.
+     */
+    public boolean isSearching() {
+        return mRegistrationState == REGISTRATION_STATE_NOT_REGISTERED_SEARCHING;
+    }
+
+    /**
+     * @return {@code true} if registered on roaming network, {@code false} otherwise.
+     */
     public boolean isRoaming() {
         return mRoamingType != ServiceState.ROAMING_TYPE_NOT_ROAMING;
     }
@@ -376,15 +421,18 @@
 
     /**
      * @return the current network roaming type.
+     * @hide
      */
-
+    @SystemApi @TestApi
     public @ServiceState.RoamingType int getRoamingType() {
         return mRoamingType;
     }
 
     /**
      * @return Whether emergency is enabled.
+     * @hide
      */
+    @SystemApi @TestApi
     public boolean isEmergencyEnabled() { return mEmergencyOnly; }
 
     /**
@@ -422,7 +470,9 @@
      * @return Reason for denial if the registration state is {@link #REGISTRATION_STATE_DENIED}.
      * Depending on {@code accessNetworkTechnology}, the values are defined in 3GPP TS 24.008
      * 10.5.3.6 for UMTS, 3GPP TS 24.301 9.9.3.9 for LTE, and 3GPP2 A.S0001 6.2.2.44 for CDMA
+     * @hide
      */
+    @SystemApi @TestApi
     public int getRejectCause() {
         return mRejectCause;
     }
@@ -445,8 +495,10 @@
 
     /**
      * @return Data registration related info
+     * @hide
      */
     @Nullable
+    @SystemApi @TestApi
     public DataSpecificRegistrationInfo getDataSpecificInfo() {
         return mDataSpecificInfo;
     }
@@ -571,7 +623,11 @@
                 && mNrState == other.mNrState;
     }
 
+    /**
+     * @hide
+     */
     @Override
+    @SystemApi @TestApi
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mDomain);
         dest.writeInt(mTransportType);
@@ -659,7 +715,9 @@
      *     .setRegistrationState(REGISTRATION_STATE_HOME)
      *     .build();
      * </code></pre>
+     * @hide
      */
+    @SystemApi @TestApi
     public static final class Builder {
         @Domain
         private int mDomain;
@@ -759,7 +817,9 @@
          * @param emergencyOnly True if this network registration is for emergency use only.
          *
          * @return The same instance of the builder.
+         * @hide
          */
+        @SystemApi @TestApi
         public @NonNull Builder setEmergencyOnly(boolean emergencyOnly) {
             mEmergencyOnly = emergencyOnly;
             return this;
@@ -771,7 +831,9 @@
          * @param availableServices Available services.
          *
          * @return The same instance of the builder.
+         * @hide
          */
+        @SystemApi @TestApi
         public @NonNull Builder setAvailableServices(
                 @NonNull @ServiceType List<Integer> availableServices) {
             mAvailableServices = availableServices;
@@ -784,7 +846,9 @@
          * @param cellIdentity The cell identity.
          *
          * @return The same instance of the builder.
+         * @hide
          */
+        @SystemApi @TestApi
         public @NonNull Builder setCellIdentity(@Nullable CellIdentity cellIdentity) {
             mCellIdentity = cellIdentity;
             return this;
@@ -792,9 +856,10 @@
 
         /**
          * Build the NetworkRegistrationInfo.
-         *
          * @return the NetworkRegistrationInfo object.
+         * @hide
          */
+        @SystemApi @TestApi
         public @NonNull NetworkRegistrationInfo build() {
             return new NetworkRegistrationInfo(mDomain, mTransportType, mRegistrationState,
                     mAccessNetworkTechnology, mRejectCause, mEmergencyOnly, mAvailableServices,
diff --git a/telephony/java/android/telephony/NetworkScan.java b/telephony/java/android/telephony/NetworkScan.java
index 202da68..a6dedf7 100644
--- a/telephony/java/android/telephony/NetworkScan.java
+++ b/telephony/java/android/telephony/NetworkScan.java
@@ -16,10 +16,10 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.IntDef;
-import android.content.Context;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 
 import com.android.internal.telephony.ITelephony;
 
@@ -148,6 +148,9 @@
 
     private ITelephony getITelephony() {
         return ITelephony.Stub.asInterface(
-            ServiceManager.getService(Context.TELEPHONY_SERVICE));
+            TelephonyFrameworkInitializer
+                    .getTelephonyServiceManager()
+                    .getTelephonyServiceRegisterer()
+                    .get());
     }
 }
diff --git a/telephony/java/android/telephony/NetworkService.java b/telephony/java/android/telephony/NetworkService.java
index 8c5e107..844289c 100644
--- a/telephony/java/android/telephony/NetworkService.java
+++ b/telephony/java/android/telephony/NetworkService.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
diff --git a/telephony/java/android/telephony/NetworkServiceCallback.java b/telephony/java/android/telephony/NetworkServiceCallback.java
index 89b9665..214ab41 100644
--- a/telephony/java/android/telephony/NetworkServiceCallback.java
+++ b/telephony/java/android/telephony/NetworkServiceCallback.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
diff --git a/telephony/java/android/telephony/PhoneCapability.java b/telephony/java/android/telephony/PhoneCapability.java
index 8a75831..90244b3 100644
--- a/telephony/java/android/telephony/PhoneCapability.java
+++ b/telephony/java/android/telephony/PhoneCapability.java
@@ -16,13 +16,20 @@
 
 package android.telephony;
 
+import android.annotation.LongDef;
 import android.annotation.NonNull;
-import android.annotation.SystemApi;
+import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
+import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
+import android.telephony.TelephonyManager.NetworkTypeBitMask;
 
+import com.android.internal.util.CollectionUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
 
@@ -31,68 +38,365 @@
  * are shared between those modems defined by list of modem IDs.
  */
 public final class PhoneCapability implements Parcelable {
-    // Hardcoded default DSDS capability.
+    /** Modem feature indicating 3GPP2 capability. */
+    public static final long MODEM_FEATURE_3GPP2_REG = 1 << 0;
+    /** Modem feature indicating 3GPP capability. */
+    public static final long MODEM_FEATURE_3GPP_REG = 1 << 1;
+    /** Modem feature indicating CDMA 2000 with EHRPD capability. */
+    public static final long MODEM_FEATURE_CDMA2000_EHRPD_REG = 1 << 2;
+    /** Modem feature indicating GSM capability. */
+    public static final long MODEM_FEATURE_GERAN_REG = 1 << 3;
+    /** Modem feature indicating UMTS capability. */
+    public static final long MODEM_FEATURE_UTRAN_REG = 1 << 4;
+    /** Modem feature indicating LTE capability. */
+    public static final long MODEM_FEATURE_EUTRAN_REG = 1 << 5;
+    /** Modem feature indicating 5G capability.*/
+    public static final long MODEM_FEATURE_NGRAN_REG = 1 << 6;
+    /** Modem feature indicating EN-DC capability. */
+    public static final long MODEM_FEATURE_EUTRA_NR_DUAL_CONNECTIVITY_REG = 1 << 7;
+    /** Modem feature indicating VoLTE capability (IMS registered). */
+    public static final long MODEM_FEATURE_PS_VOICE_REG = 1 << 8;
+    /** Modem feature indicating CS voice call capability. */
+    public static final long MODEM_FEATURE_CS_VOICE_SESSION = 1 << 9;
+    /** Modem feature indicating Internet connection capability. */
+    public static final long MODEM_FEATURE_INTERACTIVE_DATA_SESSION = 1 << 10;
+    /**
+     * Modem feature indicating dedicated bearer capability.
+     * For services that require a high level QoS (eg. VoLTE), the network can create
+     * a dedicated bearer with the required QoS on top of an established default bearer.
+     * This will provide a dedicated tunnel for one or more specific traffic types.
+     */
+    public static final long MODEM_FEATURE_DEDICATED_BEARER = 1 << 11;
+    /** Modem feature indicating network scan capability. */
+    public static final long MODEM_FEATURE_NETWORK_SCAN = 1 << 12;
+    /** Modem feature indicating corresponding SIM has CDMA capability. */
+    public static final long MODEM_FEATURE_CSIM = 1 << 13;
+
     /** @hide */
+    @LongDef(flag = true, prefix = {"MODEM_FEATURE_" }, value = {
+            MODEM_FEATURE_3GPP2_REG,
+            MODEM_FEATURE_3GPP_REG,
+            MODEM_FEATURE_CDMA2000_EHRPD_REG,
+            MODEM_FEATURE_GERAN_REG,
+            MODEM_FEATURE_UTRAN_REG,
+            MODEM_FEATURE_EUTRAN_REG,
+            MODEM_FEATURE_NGRAN_REG,
+            MODEM_FEATURE_EUTRA_NR_DUAL_CONNECTIVITY_REG,
+            MODEM_FEATURE_PS_VOICE_REG,
+            MODEM_FEATURE_CS_VOICE_SESSION,
+            MODEM_FEATURE_INTERACTIVE_DATA_SESSION,
+            MODEM_FEATURE_DEDICATED_BEARER,
+            MODEM_FEATURE_NETWORK_SCAN,
+            MODEM_FEATURE_CSIM,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ModemFeature {
+    }
+
+    /**
+     * Hardcoded default DSDS capability.
+     * @hide
+     */
     public static final PhoneCapability DEFAULT_DSDS_CAPABILITY;
-    // Hardcoded default Single SIM single standby capability.
-    /** @hide */
+    /**
+     * Hardcoded default Single SIM single standby capability.
+     * @hide
+     */
     public static final PhoneCapability DEFAULT_SSSS_CAPABILITY;
 
     static {
-        ModemInfo modemInfo1 = new ModemInfo(0, 0, true, true);
-        ModemInfo modemInfo2 = new ModemInfo(1, 0, true, true);
+        List<List<Long>> capabilities = new ArrayList<>();
+        List<Long> modem1 = new ArrayList<>();
+        List<Long> modem2 = new ArrayList<>();
+        modem1.add(MODEM_FEATURE_GERAN_REG | MODEM_FEATURE_UTRAN_REG | MODEM_FEATURE_EUTRAN_REG
+                | MODEM_FEATURE_PS_VOICE_REG | MODEM_FEATURE_CS_VOICE_SESSION
+                | MODEM_FEATURE_INTERACTIVE_DATA_SESSION | MODEM_FEATURE_DEDICATED_BEARER);
+        modem2.add(MODEM_FEATURE_GERAN_REG | MODEM_FEATURE_UTRAN_REG | MODEM_FEATURE_EUTRAN_REG
+                | MODEM_FEATURE_PS_VOICE_REG | MODEM_FEATURE_INTERACTIVE_DATA_SESSION
+                | MODEM_FEATURE_DEDICATED_BEARER);
+        capabilities.add(modem1);
+        capabilities.add(modem2);
+        List<String> uuids = new ArrayList<>();
+        uuids.add("com.xxxx.lm0");
+        uuids.add("com.xxxx.lm1");
+        long rats = TelephonyManager.NETWORK_TYPE_BITMASK_GSM
+                | TelephonyManager.NETWORK_TYPE_BITMASK_GPRS
+                | TelephonyManager.NETWORK_TYPE_BITMASK_EDGE
+                | TelephonyManager.NETWORK_TYPE_BITMASK_UMTS
+                | TelephonyManager.NETWORK_TYPE_BITMASK_LTE;
+        DEFAULT_DSDS_CAPABILITY = new PhoneCapability(0, 0, 0, 0, 0, rats, null, null, null, null,
+                uuids, null, capabilities);
 
-        List<ModemInfo> logicalModemList = new ArrayList<>();
-        logicalModemList.add(modemInfo1);
-        logicalModemList.add(modemInfo2);
-        DEFAULT_DSDS_CAPABILITY = new PhoneCapability(1, 1, 0, logicalModemList, false);
-
-        logicalModemList = new ArrayList<>();
-        logicalModemList.add(modemInfo1);
-        DEFAULT_SSSS_CAPABILITY = new PhoneCapability(1, 1, 0, logicalModemList, false);
+        capabilities = new ArrayList<>();
+        capabilities.add(modem1);
+        uuids = new ArrayList<>();
+        uuids.add("com.xxxx.lm0");
+        DEFAULT_SSSS_CAPABILITY = new PhoneCapability(0, 0, 0, 0, 0, rats, null, null, null, null,
+                uuids, null, capabilities);
     }
-    /** @hide */
-    public final int maxActiveVoiceCalls;
-    /** @hide */
-    public final int maxActiveData;
-    /** @hide */
-    public final int max5G;
-    /** @hide */
-    public final boolean validationBeforeSwitchSupported;
-    /** @hide */
-    public final List<ModemInfo> logicalModemList;
 
-    /** @hide */
-    public PhoneCapability(int maxActiveVoiceCalls, int maxActiveData, int max5G,
-            List<ModemInfo> logicalModemList, boolean validationBeforeSwitchSupported) {
-        this.maxActiveVoiceCalls = maxActiveVoiceCalls;
-        this.maxActiveData = maxActiveData;
-        this.max5G = max5G;
-        // Make sure it's not null.
-        this.logicalModemList = logicalModemList == null ? new ArrayList<>() : logicalModemList;
-        this.validationBeforeSwitchSupported = validationBeforeSwitchSupported;
+    private final int mUtranUeCategoryDl;
+    private final int mUtranUeCategoryUl;
+    private final int mEutranUeCategoryDl;
+    private final int mEutranUeCategoryUl;
+    private final long mPsDataConnectionLingerTimeMillis;
+    private final @NetworkTypeBitMask long mSupportedRats;
+    private final List<Integer> mGeranBands;
+    private final List<Integer> mUtranBands;
+    private final List<Integer> mEutranBands;
+    private final List<Integer> mNgranBands;
+    private final List<String> mLogicalModemUuids;
+    private final List<SimSlotCapability> mSimSlotCapabilities;
+    private final @ModemFeature List<List<Long>> mConcurrentFeaturesSupport;
+
+    /**
+     * Default constructor to create a PhoneCapability object.
+     * @param utranUeCategoryDl 3GPP UE category for UTRAN downlink.
+     * @param utranUeCategoryUl 3GPP UE category for UTRAN uplink.
+     * @param eutranUeCategoryDl 3GPP UE category for EUTRAN downlink.
+     * @param eutranUeCategoryUl 3GPP UE category for EUTRAN uplink.
+     * @param psDataConnectionLingerTimeMillis length of the grace period to allow a smooth
+     *                                         "handover" between data connections.
+     * @param supportedRats all radio access technologies this phone is capable of supporting.
+     * @param geranBands list of supported {@link AccessNetworkConstants.GeranBand}.
+     * @param utranBands list of supported {@link AccessNetworkConstants.UtranBand}.
+     * @param eutranBands list of supported {@link AccessNetworkConstants.EutranBand}.
+     * @param ngranBands list of supported {@link AccessNetworkConstants.NgranBands}.
+     * @param logicalModemUuids list of logical modem UUIDs, typically of the form
+     *                          "com.xxxx.lmX", where X is the logical modem ID.
+     * @param simSlotCapabilities list of {@link SimSlotCapability} for the device
+     * @param concurrentFeaturesSupport list of list of concurrently supportable modem feature sets.
+     * @hide
+     */
+    public PhoneCapability(int utranUeCategoryDl, int utranUeCategoryUl, int eutranUeCategoryDl,
+            int eutranUeCategoryUl, long psDataConnectionLingerTimeMillis,
+            @NetworkTypeBitMask long supportedRats, @Nullable List<Integer> geranBands,
+            @Nullable List<Integer> utranBands, @Nullable List<Integer> eutranBands,
+            @Nullable List<Integer> ngranBands, @Nullable List<String> logicalModemUuids,
+            @Nullable List<SimSlotCapability> simSlotCapabilities,
+            @Nullable @ModemFeature List<List<Long>> concurrentFeaturesSupport) {
+        this.mUtranUeCategoryDl = utranUeCategoryDl;
+        this.mUtranUeCategoryUl = utranUeCategoryUl;
+        this.mEutranUeCategoryDl = eutranUeCategoryDl;
+        this.mEutranUeCategoryUl = eutranUeCategoryUl;
+        this.mPsDataConnectionLingerTimeMillis = psDataConnectionLingerTimeMillis;
+        this.mSupportedRats = supportedRats;
+        this.mGeranBands = CollectionUtils.emptyIfNull(geranBands);
+        this.mUtranBands = CollectionUtils.emptyIfNull(utranBands);
+        this.mEutranBands = CollectionUtils.emptyIfNull(eutranBands);
+        this.mNgranBands = CollectionUtils.emptyIfNull(ngranBands);
+        this.mLogicalModemUuids = CollectionUtils.emptyIfNull(logicalModemUuids);
+        this.mSimSlotCapabilities = CollectionUtils.emptyIfNull(simSlotCapabilities);
+        this.mConcurrentFeaturesSupport = CollectionUtils.emptyIfNull(concurrentFeaturesSupport);
+    }
+
+    private PhoneCapability(Parcel in) {
+        mUtranUeCategoryDl = in.readInt();
+        mUtranUeCategoryUl = in.readInt();
+        mEutranUeCategoryDl = in.readInt();
+        mEutranUeCategoryUl = in.readInt();
+        mPsDataConnectionLingerTimeMillis = in.readLong();
+        mSupportedRats = in.readLong();
+        mGeranBands = new ArrayList<>();
+        in.readList(mGeranBands, Integer.class.getClassLoader());
+        mUtranBands = new ArrayList<>();
+        in.readList(mUtranBands, Integer.class.getClassLoader());
+        mEutranBands = new ArrayList<>();
+        in.readList(mEutranBands, Integer.class.getClassLoader());
+        mNgranBands = new ArrayList<>();
+        in.readList(mNgranBands, Integer.class.getClassLoader());
+        mLogicalModemUuids = in.createStringArrayList();
+        mSimSlotCapabilities = in.createTypedArrayList(SimSlotCapability.CREATOR);
+        int length = in.readInt();
+        mConcurrentFeaturesSupport = new ArrayList<>();
+        for (int i = 0; i < length; i++) {
+            ArrayList<Long> feature = new ArrayList<>();
+            in.readList(feature, Long.class.getClassLoader());
+            mConcurrentFeaturesSupport.add(feature);
+        }
+    }
+
+    /**
+     * 3GPP UE category for a given Radio Access Network and direction.
+     *
+     * References are:
+     * TS 25.306 Table 4.1a     EUTRAN downlink
+     * TS 25.306 Table 5.1a-2   EUTRAN uplink
+     * TS 25.306 Table 5.1a     UTRAN downlink
+     * TS 25.306 Table 5.1g     UTRAN uplink
+     *
+     * @param uplink true for uplink direction and false for downlink direction.
+     * @param accessNetworkType accessNetworkType, defined in {@link AccessNetworkType}.
+     * @return the UE category, or -1 if it is not supported.
+     */
+    public int getUeCategory(boolean uplink, @RadioAccessNetworkType int accessNetworkType) {
+        if (uplink) {
+            switch (accessNetworkType) {
+                case AccessNetworkType.UTRAN: return mUtranUeCategoryUl;
+                case AccessNetworkType.EUTRAN: return mEutranUeCategoryUl;
+                default: return -1;
+            }
+        } else {
+            switch (accessNetworkType) {
+                case AccessNetworkType.UTRAN: return mUtranUeCategoryDl;
+                case AccessNetworkType.EUTRAN: return mEutranUeCategoryDl;
+                default: return -1;
+            }
+        }
+    }
+
+    /**
+     * In cellular devices that support a greater number of logical modems than
+     * Internet connections, some devices support a grace period to allow a smooth "handover"
+     * between those connections. If that feature is supported, then this API will provide
+     * the length of that grace period in milliseconds. If it is not supported, the default value
+     * for the grace period is 0.
+     * @return handover linger time in milliseconds, or 0 if it is not supported.
+     */
+    public long getPsDataConnectionLingerTimeMillis() {
+        return mPsDataConnectionLingerTimeMillis;
+    }
+
+    /**
+     * The radio access technologies this device is capable of supporting.
+     * @return a bitfield of all supported network types, defined in {@link TelephonyManager}
+     */
+    public @NetworkTypeBitMask long getSupportedRats() {
+        return mSupportedRats;
+    }
+
+    /**
+     * List of supported cellular bands for the given accessNetworkType.
+     * @param accessNetworkType accessNetworkType, defined in {@link AccessNetworkType}.
+     * @return a list of bands, or an empty list if the access network type is unsupported.
+     */
+    public @NonNull List<Integer> getBands(@RadioAccessNetworkType int accessNetworkType) {
+        switch (accessNetworkType) {
+            case AccessNetworkType.GERAN: return mGeranBands;
+            case AccessNetworkType.UTRAN: return mUtranBands;
+            case AccessNetworkType.EUTRAN: return mEutranBands;
+            case AccessNetworkType.NGRAN: return mNgranBands;
+            default: return new ArrayList<>();
+        }
+    }
+
+    /**
+     * List of logical modem UUIDs, each typically "com.xxxx.lmX", where X is the logical modem ID.
+     * @return a list of modem UUIDs, one for every logical modem the device has.
+     */
+    public @NonNull List<String> getLogicalModemUuids() {
+        return mLogicalModemUuids;
+    }
+
+    /**
+     * List of {@link SimSlotCapability} for the device. The order of SIMs corresponds to the
+     * order of modems in {@link #getLogicalModemUuids}.
+     * @return a list of SIM slot capabilities, one for every SIM slot the device has.
+     */
+    public @NonNull List<SimSlotCapability> getSimSlotCapabilities() {
+        return mSimSlotCapabilities;
+    }
+
+    /**
+     * A List of Lists of concurrently supportable modem feature sets.
+     *
+     * Each entry in the top-level list is an independent configuration across all modems
+     * that describes the capabilities of the device as a whole.
+     *
+     * Each entry in the second-level list is a bitfield of ModemFeatures that describes
+     * the capabilities for a single modem. In the second-level list, the order of the modems
+     * corresponds to order of the UUIDs in {@link #getLogicalModemUuids}.
+     *
+     * For symmetric capabilities that can only be active on one modem at a time, there will be
+     * multiple configurations (equal to the number of modems) that shows it active on each modem.
+     * For asymmetric capabilities that are only available on one of the modems, all configurations
+     * will have that capability on just that one modem.
+     *
+     * The example below shows the concurrentFeaturesSupport for a 3-modem device with
+     * theoretical capabilities SYMMETRIC (available on all modems, but only one at a time) and
+     * ASYMMETRIC (only available on the first modem):
+     * {
+     *      Configuration 1: ASYMMETRIC and SYMMETRIC on modem 1, modem 2 empty, modem 3 empty
+     *      {(ASYMMETRIC | SYMMETRIC), (), ()},
+     *
+     *      Configuration 2: ASYMMETRIC on modem 1, SYMMETRIC on modem 2, modem 3 empty
+     *      {(ASYMMETRIC), (SYMMETRIC), ()},
+     *
+     *      Configuration 3: ASYMMETRIC on modem 1, modem 2 empty, SYMMETRIC on modem 3
+     *      {(ASYMMETRIC), (), (SYMMETRIC)}
+     * }
+     *
+     * @return List of all concurrently supportable modem features.
+     */
+    public @NonNull @ModemFeature List<List<Long>> getConcurrentFeaturesSupport() {
+        return mConcurrentFeaturesSupport;
+    }
+
+    /**
+     * How many modems can simultaneously have PS attached.
+     * @return maximum number of active PS voice connections.
+     */
+    public int getMaxActivePsVoice() {
+        return countFeature(MODEM_FEATURE_PS_VOICE_REG);
+    }
+
+    /**
+     * How many modems can simultaneously support active data connections.
+     * For DSDS, this will be 1, and for DSDA this will be 2.
+     * @return maximum number of active Internet data sessions.
+     */
+    public int getMaxActiveInternetData() {
+        return countFeature(MODEM_FEATURE_INTERACTIVE_DATA_SESSION);
+    }
+
+    /**
+     * How many modems can simultaneously have dedicated bearer capability.
+     * @return maximum number of active dedicated bearers.
+     */
+    public int getMaxActiveDedicatedBearers() {
+        return countFeature(MODEM_FEATURE_DEDICATED_BEARER);
+    }
+
+    /**
+     * Whether the CBRS band 48 is supported or not.
+     * @return true if any RadioAccessNetwork supports CBRS and false if none do.
+     * @hide
+     */
+    public boolean isCbrsSupported() {
+        return mEutranBands.contains(AccessNetworkConstants.EutranBand.BAND_48)
+                || mNgranBands.contains(AccessNetworkConstants.NgranBands.BAND_48);
+    }
+
+    private int countFeature(@ModemFeature long feature) {
+        int count = 0;
+        for (long featureSet : mConcurrentFeaturesSupport.get(0)) {
+            if ((featureSet & feature) != 0) {
+                count++;
+            }
+        }
+        return count;
     }
 
     @Override
     public String toString() {
-        return "maxActiveVoiceCalls=" + maxActiveVoiceCalls + " maxActiveData=" + maxActiveData
-                + " max5G=" + max5G + "logicalModemList:"
-                + Arrays.toString(logicalModemList.toArray());
-    }
-
-    private PhoneCapability(Parcel in) {
-        maxActiveVoiceCalls = in.readInt();
-        maxActiveData = in.readInt();
-        max5G = in.readInt();
-        validationBeforeSwitchSupported = in.readBoolean();
-        logicalModemList = new ArrayList<>();
-        in.readList(logicalModemList, ModemInfo.class.getClassLoader());
+        return "utranUeCategoryDl=" + mUtranUeCategoryDl
+                + " utranUeCategoryUl=" + mUtranUeCategoryUl
+                + " eutranUeCategoryDl=" + mEutranUeCategoryDl
+                + " eutranUeCategoryUl=" + mEutranUeCategoryUl
+                + " psDataConnectionLingerTimeMillis=" + mPsDataConnectionLingerTimeMillis
+                + " supportedRats=" + mSupportedRats + " geranBands=" + mGeranBands
+                + " utranBands=" + mUtranBands + " eutranBands=" + mEutranBands
+                + " ngranBands=" + mNgranBands + " logicalModemUuids=" + mLogicalModemUuids
+                + " simSlotCapabilities=" + mSimSlotCapabilities
+                + " concurrentFeaturesSupport=" + mConcurrentFeaturesSupport;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(maxActiveVoiceCalls, maxActiveData, max5G, logicalModemList,
-                validationBeforeSwitchSupported);
+        return Objects.hash(mUtranUeCategoryDl, mUtranUeCategoryUl, mEutranUeCategoryDl,
+                mEutranUeCategoryUl, mPsDataConnectionLingerTimeMillis, mSupportedRats, mGeranBands,
+                mUtranBands, mEutranBands, mNgranBands, mLogicalModemUuids, mSimSlotCapabilities,
+                mConcurrentFeaturesSupport);
     }
 
     @Override
@@ -107,11 +411,19 @@
 
         PhoneCapability s = (PhoneCapability) o;
 
-        return (maxActiveVoiceCalls == s.maxActiveVoiceCalls
-                && maxActiveData == s.maxActiveData
-                && max5G == s.max5G
-                && validationBeforeSwitchSupported == s.validationBeforeSwitchSupported
-                && logicalModemList.equals(s.logicalModemList));
+        return (mUtranUeCategoryDl == s.mUtranUeCategoryDl
+                && mUtranUeCategoryUl == s.mUtranUeCategoryUl
+                && mEutranUeCategoryDl == s.mEutranUeCategoryDl
+                && mEutranUeCategoryUl == s.mEutranUeCategoryUl
+                && mPsDataConnectionLingerTimeMillis == s.mPsDataConnectionLingerTimeMillis
+                && mSupportedRats == s.mSupportedRats
+                && mGeranBands.equals(s.mGeranBands)
+                && mUtranBands.equals(s.mUtranBands)
+                && mEutranBands.equals(s.mEutranBands)
+                && mNgranBands.equals(s.mNgranBands)
+                && mLogicalModemUuids.equals(s.mLogicalModemUuids)
+                && mSimSlotCapabilities.equals(s.mSimSlotCapabilities)
+                && mConcurrentFeaturesSupport.equals(s.mConcurrentFeaturesSupport));
     }
 
     /**
@@ -125,20 +437,32 @@
      * {@link Parcelable#writeToParcel}
      */
     public void writeToParcel(@NonNull Parcel dest, @Parcelable.WriteFlags int flags) {
-        dest.writeInt(maxActiveVoiceCalls);
-        dest.writeInt(maxActiveData);
-        dest.writeInt(max5G);
-        dest.writeBoolean(validationBeforeSwitchSupported);
-        dest.writeList(logicalModemList);
+        dest.writeInt(mUtranUeCategoryDl);
+        dest.writeInt(mUtranUeCategoryUl);
+        dest.writeInt(mEutranUeCategoryDl);
+        dest.writeInt(mEutranUeCategoryUl);
+        dest.writeLong(mPsDataConnectionLingerTimeMillis);
+        dest.writeLong(mSupportedRats);
+        dest.writeList(mGeranBands);
+        dest.writeList(mUtranBands);
+        dest.writeList(mEutranBands);
+        dest.writeList(mNgranBands);
+        dest.writeStringList(mLogicalModemUuids);
+        dest.writeTypedList(mSimSlotCapabilities);
+        dest.writeInt(mConcurrentFeaturesSupport.size());
+        for (List<Long> feature : mConcurrentFeaturesSupport) {
+            dest.writeList(feature);
+        }
     }
 
-    public static final @android.annotation.NonNull Parcelable.Creator<PhoneCapability> CREATOR = new Parcelable.Creator() {
-        public PhoneCapability createFromParcel(Parcel in) {
-            return new PhoneCapability(in);
-        }
+    public static final @NonNull Parcelable.Creator<PhoneCapability> CREATOR =
+            new Parcelable.Creator() {
+                public PhoneCapability createFromParcel(Parcel in) {
+                    return new PhoneCapability(in);
+                }
 
-        public PhoneCapability[] newArray(int size) {
-            return new PhoneCapability[size];
-        }
-    };
+                public PhoneCapability[] newArray(int size) {
+                    return new PhoneCapability[size];
+                }
+            };
 }
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index 6e86a42..0074772 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -26,7 +28,6 @@
 import android.content.Intent;
 import android.content.res.Resources;
 import android.database.Cursor;
-import android.location.CountryDetector;
 import android.net.Uri;
 import android.os.PersistableBundle;
 import android.provider.Contacts;
@@ -2030,6 +2031,7 @@
     private static boolean isEmergencyNumberInternal(int subId, String number,
                                                      String defaultCountryIso,
                                                      boolean useExactMatch) {
+        // TODO: clean up all the callers that pass in a defaultCountryIso, since it's ignored now.
         try {
             if (useExactMatch) {
                 return TelephonyManager.getDefault().isEmergencyNumber(number);
@@ -2191,18 +2193,7 @@
     private static boolean isLocalEmergencyNumberInternal(int subId, String number,
                                                           Context context,
                                                           boolean useExactMatch) {
-        String countryIso;
-        CountryDetector detector = (CountryDetector) context.getSystemService(
-                Context.COUNTRY_DETECTOR);
-        if (detector != null && detector.detectCountry() != null) {
-            countryIso = detector.detectCountry().getCountryIso();
-        } else {
-            Locale locale = context.getResources().getConfiguration().locale;
-            countryIso = locale.getCountry();
-            Rlog.w(LOG_TAG, "No CountryDetector; falling back to countryIso based on locale: "
-                    + countryIso);
-        }
-        return isEmergencyNumberInternal(subId, number, countryIso, useExactMatch);
+        return isEmergencyNumberInternal(subId, number, null /* unused */, useExactMatch);
     }
 
     /**
diff --git a/telephony/java/android/telephony/PinResult.java b/telephony/java/android/telephony/PinResult.java
new file mode 100644
index 0000000..c14bd91
--- /dev/null
+++ b/telephony/java/android/telephony/PinResult.java
@@ -0,0 +1,173 @@
+/*
+ * 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;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.telephony.PhoneConstants;
+
+import java.util.Objects;
+
+/**
+ * Holds the result from a pin attempt.
+ *
+ * @hide
+ */
+@SystemApi
+public final class PinResult implements Parcelable {
+    /** @hide */
+    @IntDef({
+            PIN_RESULT_TYPE_SUCCESS,
+            PIN_RESULT_TYPE_INCORRECT,
+            PIN_RESULT_TYPE_FAILURE,
+    })
+    public @interface PinResultType {}
+
+    /**
+     * Indicates that the pin attempt was a success.
+     */
+    public static final int PIN_RESULT_TYPE_SUCCESS = PhoneConstants.PIN_RESULT_SUCCESS;
+
+    /**
+     * Indicates that the pin attempt was incorrect.
+     */
+    public static final int PIN_RESULT_TYPE_INCORRECT = PhoneConstants.PIN_PASSWORD_INCORRECT;
+
+    /**
+     * Indicates that the pin attempt was a failure.
+     */
+    public static final int PIN_RESULT_TYPE_FAILURE = PhoneConstants.PIN_GENERAL_FAILURE;
+
+    private static final PinResult sFailedResult =
+            new PinResult(PinResult.PIN_RESULT_TYPE_FAILURE, -1);
+
+    private final @PinResultType int mType;
+
+    private final int mAttemptsRemaining;
+
+    /**
+     * Returns either success, incorrect or failure.
+     *
+     * @see #PIN_RESULT_TYPE_SUCCESS
+     * @see #PIN_RESULT_TYPE_INCORRECT
+     * @see #PIN_RESULT_TYPE_FAILURE
+     * @return The result type of the pin attempt.
+     */
+    public @PinResultType int getType() {
+        return mType;
+    }
+
+    /**
+     * The number of pin attempts remaining.
+     *
+     * @return Number of attempts remaining.
+     */
+    public int getAttemptsRemaining() {
+        return mAttemptsRemaining;
+    }
+
+    @NonNull
+    public static PinResult getDefaultFailedResult() {
+        return sFailedResult;
+    }
+
+    /**
+     * PinResult constructor
+     *
+     * @param type The type of pin result.
+     * @see #PIN_RESULT_TYPE_SUCCESS
+     * @see #PIN_RESULT_TYPE_INCORRECT
+     * @see #PIN_RESULT_TYPE_FAILURE
+     * @param attemptsRemaining Number of pin attempts remaining.
+     */
+    public PinResult(@PinResultType int type, int attemptsRemaining) {
+        mType = type;
+        mAttemptsRemaining = attemptsRemaining;
+    }
+
+    /**
+     * Construct a PinResult object from the given parcel.
+     *
+     * @hide
+     */
+    private PinResult(Parcel in) {
+        mType = in.readInt();
+        mAttemptsRemaining = in.readInt();
+    }
+
+    /**
+     * String representation of the Pin Result.
+     */
+    @NonNull
+    @Override
+    public String toString() {
+        return "type: " + getType() + ", attempts remaining: " + getAttemptsRemaining();
+    }
+
+    /**
+     * Required to be Parcelable
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Required to be Parcelable
+     */
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeInt(mType);
+        out.writeInt(mAttemptsRemaining);
+    }
+
+    /** Required to be Parcelable */
+    public static final @NonNull Parcelable.Creator<PinResult> CREATOR = new Creator<PinResult>() {
+        public PinResult createFromParcel(Parcel in) {
+            return new PinResult(in);
+        }
+        public PinResult[] newArray(int size) {
+            return new PinResult[size];
+        }
+    };
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mAttemptsRemaining, mType);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        PinResult other = (PinResult) obj;
+        return (mType == other.mType
+                && mAttemptsRemaining == other.mAttemptsRemaining);
+    }
+}
diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java
index 31434c1..094b8b0 100644
--- a/telephony/java/android/telephony/PreciseDataConnectionState.java
+++ b/telephony/java/android/telephony/PreciseDataConnectionState.java
@@ -20,6 +20,9 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.net.LinkProperties;
 import android.os.Build;
@@ -31,8 +34,6 @@
 import android.telephony.Annotation.NetworkType;
 import android.telephony.data.ApnSetting;
 
-import dalvik.system.VMRuntime;
-
 import java.util.Objects;
 
 
@@ -134,6 +135,13 @@
     }
 
     /**
+     * To check the SDK version for {@code PreciseDataConnectionState#getDataConnectionState}.
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+    private static final long GET_DATA_CONNECTION_STATE_CODE_CHANGE = 147600208L;
+
+    /**
      * Returns the state of data connection that supported the apn types returned by
      * {@link #getDataConnectionApnTypeBitMask()}
      *
@@ -144,7 +152,7 @@
     @SystemApi
     public @DataState int getDataConnectionState() {
         if (mState == TelephonyManager.DATA_DISCONNECTING
-                && VMRuntime.getRuntime().getTargetSdkVersion() < Build.VERSION_CODES.R) {
+                && !Compatibility.isChangeEnabled(GET_DATA_CONNECTION_STATE_CODE_CHANGE)) {
             return TelephonyManager.DATA_CONNECTED;
         }
 
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 5b12aed..1e64a81 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -34,6 +34,8 @@
 import android.telephony.NetworkRegistrationInfo.NRState;
 import android.text.TextUtils;
 
+import com.android.telephony.Rlog;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -351,6 +353,7 @@
 
     private String mOperatorAlphaLongRaw;
     private String mOperatorAlphaShortRaw;
+    private boolean mIsDataRoamingFromRegistration;
     private boolean mIsIwlanPreferred;
 
     /**
@@ -436,6 +439,7 @@
         mNrFrequencyRange = s.mNrFrequencyRange;
         mOperatorAlphaLongRaw = s.mOperatorAlphaLongRaw;
         mOperatorAlphaShortRaw = s.mOperatorAlphaShortRaw;
+        mIsDataRoamingFromRegistration = s.mIsDataRoamingFromRegistration;
         mIsIwlanPreferred = s.mIsIwlanPreferred;
     }
 
@@ -470,6 +474,7 @@
         mNrFrequencyRange = in.readInt();
         mOperatorAlphaLongRaw = in.readString();
         mOperatorAlphaShortRaw = in.readString();
+        mIsDataRoamingFromRegistration = in.readBoolean();
         mIsIwlanPreferred = in.readBoolean();
     }
 
@@ -497,6 +502,7 @@
         out.writeInt(mNrFrequencyRange);
         out.writeString(mOperatorAlphaLongRaw);
         out.writeString(mOperatorAlphaShortRaw);
+        out.writeBoolean(mIsDataRoamingFromRegistration);
         out.writeBoolean(mIsIwlanPreferred);
     }
 
@@ -582,8 +588,8 @@
      */
     @DuplexMode
     public int getDuplexMode() {
-        // only support LTE duplex mode
-        if (!isLte(getRilDataRadioTechnology())) {
+        // support LTE/NR duplex mode
+        if (!isPsOnlyTech(getRilDataRadioTechnology())) {
             return DUPLEX_MODE_UNKNOWN;
         }
 
@@ -647,7 +653,9 @@
     }
 
     /**
-     * Get current data network roaming type
+     * Get whether the current data network is roaming.
+     * This value may be overwritten by resource overlay or carrier configuration.
+     * @see #getDataRoamingFromRegistration() to get the value from the network registration.
      * @return roaming type
      * @hide
      */
@@ -657,18 +665,25 @@
     }
 
     /**
-     * Get whether data network registration state is roaming
+     * Set whether the data network registration state is roaming.
+     * This should only be set to the roaming value received
+     * once the data registration phase has completed.
+     * @hide
+     */
+    public void setDataRoamingFromRegistration(boolean dataRoaming) {
+        mIsDataRoamingFromRegistration = dataRoaming;
+    }
+
+    /**
+     * Get whether data network registration state is roaming.
+     * This value is set directly from the modem and will not be overwritten
+     * by resource overlay or carrier configuration.
      * @return true if registration indicates roaming, false otherwise
      * @hide
      */
+    @SystemApi
     public boolean getDataRoamingFromRegistration() {
-        final NetworkRegistrationInfo regState = getNetworkRegistrationInfo(
-                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
-        if (regState != null) {
-            return regState.getRegistrationState()
-                    == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING;
-        }
-        return false;
+        return mIsDataRoamingFromRegistration;
     }
 
     /**
@@ -871,6 +886,7 @@
                     mNrFrequencyRange,
                     mOperatorAlphaLongRaw,
                     mOperatorAlphaShortRaw,
+                    mIsDataRoamingFromRegistration,
                     mIsIwlanPreferred);
         }
     }
@@ -901,6 +917,7 @@
                     && mNetworkRegistrationInfos.size() == s.mNetworkRegistrationInfos.size()
                     && mNetworkRegistrationInfos.containsAll(s.mNetworkRegistrationInfos)
                     && mNrFrequencyRange == s.mNrFrequencyRange
+                    && mIsDataRoamingFromRegistration == s.mIsDataRoamingFromRegistration
                     && mIsIwlanPreferred == s.mIsIwlanPreferred;
         }
     }
@@ -1060,6 +1077,8 @@
                     .append(", mNrFrequencyRange=").append(mNrFrequencyRange)
                     .append(", mOperatorAlphaLongRaw=").append(mOperatorAlphaLongRaw)
                     .append(", mOperatorAlphaShortRaw=").append(mOperatorAlphaShortRaw)
+                    .append(", mIsDataRoamingFromRegistration=")
+                    .append(mIsDataRoamingFromRegistration)
                     .append(", mIsIwlanPreferred=").append(mIsIwlanPreferred)
                     .append("}").toString();
         }
@@ -1100,6 +1119,7 @@
         }
         mOperatorAlphaLongRaw = null;
         mOperatorAlphaShortRaw = null;
+        mIsDataRoamingFromRegistration = false;
         mIsIwlanPreferred = false;
     }
 
@@ -1622,7 +1642,8 @@
      * @return Current data network type
      * @hide
      */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    @SystemApi
+    @TestApi
     public @NetworkType int getDataNetworkType() {
         final NetworkRegistrationInfo iwlanRegInfo = getNetworkRegistrationInfo(
                 NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
@@ -1715,9 +1736,10 @@
     }
 
     /** @hide */
-    public static boolean isLte(int radioTechnology) {
-        return radioTechnology == RIL_RADIO_TECHNOLOGY_LTE ||
-                radioTechnology == RIL_RADIO_TECHNOLOGY_LTE_CA;
+    public static boolean isPsOnlyTech(int radioTechnology) {
+        return radioTechnology == RIL_RADIO_TECHNOLOGY_LTE
+                || radioTechnology == RIL_RADIO_TECHNOLOGY_LTE_CA
+                || radioTechnology == RIL_RADIO_TECHNOLOGY_NR;
     }
 
     /** @hide */
@@ -1833,10 +1855,8 @@
      * Get all of the available network registration info.
      *
      * @return List of {@link NetworkRegistrationInfo}
-     * @hide
      */
     @NonNull
-    @SystemApi
     public List<NetworkRegistrationInfo> getNetworkRegistrationInfoList() {
         synchronized (mNetworkRegistrationInfos) {
             List<NetworkRegistrationInfo> newList = new ArrayList<>();
@@ -2044,4 +2064,27 @@
     public boolean isIwlanPreferred() {
         return mIsIwlanPreferred;
     }
+     /**
+     * @return {@code true}Returns True whenever the modem is searching for service.
+     * To check both CS and PS domain
+     */
+
+    public boolean isSearching() {
+        NetworkRegistrationInfo psRegState = getNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        if (psRegState != null && psRegState.getRegistrationState()
+                == NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_SEARCHING) {
+            return true;
+        }
+
+        NetworkRegistrationInfo csRegState = getNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        if (csRegState != null && csRegState.getRegistrationState()
+                == NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_SEARCHING) {
+            return true;
+        }
+        return false;
+    }
 }
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index 3350a33..5a4dac5 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -17,6 +17,7 @@
 package android.telephony;
 
 import android.annotation.NonNull;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
@@ -24,6 +25,9 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
+import android.os.SystemClock;
+
+import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -75,6 +79,9 @@
     /* The type of signal measurement */
     private static final String MEASUREMENT_TYPE_RSCP = "rscp";
 
+    // timeStamp of signalStrength in nanoseconds since boot
+    private long mTimestamp = Long.MAX_VALUE;
+
     CellSignalStrengthCdma mCdma;
     CellSignalStrengthGsm mGsm;
     CellSignalStrengthWcdma mWcdma;
@@ -85,8 +92,7 @@
     /**
      * Create a new SignalStrength from a intent notifier Bundle
      *
-     * This method is used by PhoneStateIntentReceiver and maybe by
-     * external applications.
+     * This method may be used by external applications.
      *
      * @param m Bundle from intent notifier
      * @return newly created SignalStrength
@@ -133,6 +139,7 @@
         mTdscdma = tdscdma;
         mLte = lte;
         mNr = nr;
+        mTimestamp = SystemClock.elapsedRealtimeNanos();
     }
 
     /**
@@ -267,6 +274,7 @@
         mTdscdma.updateLevel(cc, ss);
         mLte.updateLevel(cc, ss);
         mNr.updateLevel(cc, ss);
+        mTimestamp = SystemClock.elapsedRealtimeNanos();
     }
 
     /**
@@ -292,6 +300,7 @@
         mTdscdma = new CellSignalStrengthTdscdma(s.mTdscdma);
         mLte = new CellSignalStrengthLte(s.mLte);
         mNr = new CellSignalStrengthNr(s.mNr);
+        mTimestamp = s.getTimestampNanos();
     }
 
     /**
@@ -309,6 +318,7 @@
         mTdscdma = in.readParcelable(CellSignalStrengthTdscdma.class.getClassLoader());
         mLte = in.readParcelable(CellSignalStrengthLte.class.getClassLoader());
         mNr = in.readParcelable(CellSignalStrengthLte.class.getClassLoader());
+        mTimestamp = in.readLong();
     }
 
     /**
@@ -321,9 +331,18 @@
         out.writeParcelable(mTdscdma, flags);
         out.writeParcelable(mLte, flags);
         out.writeParcelable(mNr, flags);
+        out.writeLong(mTimestamp);
     }
 
     /**
+     * @return mTimestamp in nanoseconds
+     */
+    @SuppressLint("MethodNameUnits")
+    public long getTimestampNanos() {
+        return mTimestamp;
+    }
+
+   /**
      * {@link Parcelable#describeContents}
      */
     public int describeContents() {
diff --git a/telephony/java/android/telephony/SimSlotCapability.java b/telephony/java/android/telephony/SimSlotCapability.java
new file mode 100644
index 0000000..3d38d04
--- /dev/null
+++ b/telephony/java/android/telephony/SimSlotCapability.java
@@ -0,0 +1,130 @@
+/*
+ * 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.telephony;
+
+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;
+
+/**
+ * Capabilities for a SIM Slot.
+ */
+public final class SimSlotCapability implements Parcelable {
+    /** Slot type for UICC (removable SIM). */
+    public static final int SLOT_TYPE_UICC = 1;
+    /** Slot type for iUICC/iSIM (integrated SIM). */
+    public static final int SLOT_TYPE_IUICC = 2;
+    /** Slot type for eUICC/eSIM (embedded SIM). */
+    public static final int SLOT_TYPE_EUICC = 3;
+    /** Slot type for soft SIM (no physical SIM). */
+    public static final int SLOT_TYPE_SOFT_SIM = 4;
+
+    /** @hide */
+    @IntDef(prefix = {"SLOT_TYPE_" }, value = {
+            SLOT_TYPE_UICC,
+            SLOT_TYPE_IUICC,
+            SLOT_TYPE_EUICC,
+            SLOT_TYPE_SOFT_SIM,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SlotType {
+    }
+
+    private final int mPhysicalSlotIndex;
+    private final int mSlotType;
+
+    /** @hide */
+    public SimSlotCapability(int physicalSlotId, int slotType) {
+        this.mPhysicalSlotIndex = physicalSlotId;
+        this.mSlotType = slotType;
+    }
+
+    private SimSlotCapability(Parcel in) {
+        mPhysicalSlotIndex = in.readInt();
+        mSlotType = in.readInt();
+    }
+
+    /**
+     * @return physical SIM slot index
+     */
+    public int getPhysicalSlotIndex() {
+        return mPhysicalSlotIndex;
+    }
+
+    /**
+     * @return type of SIM {@link SlotType}
+     */
+    public @SlotType int getSlotType() {
+        return mSlotType;
+    }
+
+    @Override
+    public String toString() {
+        return "mPhysicalSlotIndex=" + mPhysicalSlotIndex + " slotType=" + mSlotType;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mPhysicalSlotIndex, mSlotType);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == null || !(o instanceof SimSlotCapability) || hashCode() != o.hashCode()) {
+            return false;
+        }
+
+        if (this == o) {
+            return true;
+        }
+
+        SimSlotCapability s = (SimSlotCapability) o;
+
+        return (mPhysicalSlotIndex == s.mPhysicalSlotIndex && mSlotType == s.mSlotType);
+    }
+
+    /**
+     * {@link Parcelable#describeContents}
+     */
+    public @ContentsFlags int describeContents() {
+        return 0;
+    }
+
+    /**
+     * {@link Parcelable#writeToParcel}
+     */
+    public void writeToParcel(@NonNull Parcel dest, @WriteFlags int flags) {
+        dest.writeInt(mPhysicalSlotIndex);
+        dest.writeInt(mSlotType);
+    }
+
+    public static final @NonNull Parcelable.Creator<SimSlotCapability> CREATOR =
+            new Parcelable.Creator() {
+                public SimSlotCapability createFromParcel(Parcel in) {
+                    return new SimSlotCapability(in);
+                }
+
+                public SimSlotCapability[] newArray(int size) {
+                    return new SimSlotCapability[size];
+                }
+            };
+}
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index fbe355e..5cd7cf8 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -25,17 +25,17 @@
 import android.annotation.SuppressAutoDoc;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
-import android.app.ActivityThread;
 import android.app.PendingIntent;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.database.CursorWindow;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -282,6 +282,42 @@
      */
     public static final int SMS_MESSAGE_PERIOD_NOT_SPECIFIED = -1;
 
+    /** @hide */
+    @IntDef(prefix = { "PREMIUM_SMS_CONSENT" }, value = {
+        SmsManager.PREMIUM_SMS_CONSENT_UNKNOWN,
+        SmsManager.PREMIUM_SMS_CONSENT_ASK_USER,
+        SmsManager.PREMIUM_SMS_CONSENT_NEVER_ALLOW,
+        SmsManager.PREMIUM_SMS_CONSENT_ALWAYS_ALLOW
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PremiumSmsConsent {}
+
+    /** Premium SMS Consent for the package is unknown. This indicates that the user
+     *  has not set a permission for this package, because this package has never tried
+     *  to send a premium SMS.
+     * @hide
+     */
+    @SystemApi
+    public static final int PREMIUM_SMS_CONSENT_UNKNOWN = 0;
+
+    /** Default premium SMS Consent (ask user for each premium SMS sent).
+     * @hide
+     */
+    @SystemApi
+    public static final int PREMIUM_SMS_CONSENT_ASK_USER = 1;
+
+    /** Premium SMS Consent when the owner has denied the app from sending premium SMS.
+     * @hide
+     */
+    @SystemApi
+    public static final int PREMIUM_SMS_CONSENT_NEVER_ALLOW = 2;
+
+    /** Premium SMS Consent when the owner has allowed the app to send premium SMS.
+     * @hide
+     */
+    @SystemApi
+    public static final int PREMIUM_SMS_CONSENT_ALWAYS_ALLOW = 3;
+
     // result of asking the user for a subscription to perform an operation.
     private interface SubscriptionResolverResult {
         void onSuccess(int subId);
@@ -388,7 +424,26 @@
             String destinationAddress, String scAddress, String text,
             PendingIntent sentIntent, PendingIntent deliveryIntent) {
         sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
-                true /* persistMessage*/, ActivityThread.currentPackageName());
+                true /* persistMessage*/, null, 0L /* messageId */);
+    }
+
+
+    /**
+     * Send a text based SMS. Same as {@link #sendTextMessage( String destinationAddress,
+     * String scAddress, String text, PendingIntent sentIntent, PendingIntent deliveryIntent)}, but
+     * adds an optional messageId.
+     * @param messageId An id that uniquely identifies the message requested to be sent.
+     * Used for logging and diagnostics purposes. The id may be 0.
+     *
+     * @throws IllegalArgumentException if destinationAddress or text are empty
+     *
+     */
+    public void sendTextMessage(
+            @NonNull String destinationAddress, @Nullable String scAddress, @NonNull String text,
+            @Nullable PendingIntent sentIntent, @Nullable PendingIntent deliveryIntent,
+            long messageId) {
+        sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
+                true /* persistMessage*/, null, messageId);
     }
 
     /**
@@ -506,7 +561,7 @@
 
     private void sendTextMessageInternal(String destinationAddress, String scAddress,
             String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
-            boolean persistMessage, String packageName) {
+            boolean persistMessage, String packageName, long messageId) {
         if (TextUtils.isEmpty(destinationAddress)) {
             throw new IllegalArgumentException("Invalid destinationAddress");
         }
@@ -533,10 +588,10 @@
                     try {
                         iSms.sendTextForSubscriber(subId, packageName,
                                 destinationAddress, scAddress, text, sentIntent, deliveryIntent,
-                                persistMessage);
+                                persistMessage, messageId);
                     } catch (RemoteException e) {
                         Log.e(TAG, "sendTextMessageInternal: Couldn't send SMS, exception - "
-                                + e.getMessage());
+                                + e.getMessage() + " id: " + messageId);
                         notifySmsError(sentIntent, RESULT_REMOTE_EXCEPTION);
                     }
                 }
@@ -553,10 +608,10 @@
             try {
                 iSms.sendTextForSubscriber(getSubscriptionId(), packageName,
                         destinationAddress, scAddress, text, sentIntent, deliveryIntent,
-                        persistMessage);
+                        persistMessage, messageId);
             } catch (RemoteException e) {
                 Log.e(TAG, "sendTextMessageInternal (no persist): Couldn't send SMS, exception - "
-                        + e.getMessage());
+                        + e.getMessage() + " id: " + messageId);
                 notifySmsError(sentIntent, RESULT_REMOTE_EXCEPTION);
             }
         }
@@ -598,7 +653,8 @@
             String destinationAddress, String scAddress, String text,
             PendingIntent sentIntent, PendingIntent deliveryIntent) {
         sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
-                false /* persistMessage */, ActivityThread.currentPackageName());
+                false /* persistMessage */, null,
+                0L /* messageId */);
     }
 
     private void sendTextMessageInternal(
@@ -641,7 +697,7 @@
                         ISms iSms = getISmsServiceOrThrow();
                         if (iSms != null) {
                             iSms.sendTextForSubscriberWithOptions(subId,
-                                    ActivityThread.currentPackageName(), destinationAddress,
+                                    null, destinationAddress,
                                     scAddress,
                                     text, sentIntent, deliveryIntent, persistMessage, finalPriority,
                                     expectMore, finalValidity);
@@ -663,7 +719,7 @@
                 ISms iSms = getISmsServiceOrThrow();
                 if (iSms != null) {
                     iSms.sendTextForSubscriberWithOptions(getSubscriptionId(),
-                            ActivityThread.currentPackageName(), destinationAddress,
+                            null, destinationAddress,
                             scAddress,
                             text, sentIntent, deliveryIntent, persistMessage, finalPriority,
                             expectMore, finalValidity);
@@ -745,7 +801,11 @@
                     "Invalid pdu format. format must be either 3gpp or 3gpp2");
         }
         try {
-            ISms iSms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+            ISms iSms = ISms.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSmsServiceRegisterer()
+                            .get());
             if (iSms != null) {
                 iSms.injectSmsPduForSubscriber(
                         getSubscriptionId(), pdu, format, receivedIntent);
@@ -881,7 +941,26 @@
             String destinationAddress, String scAddress, ArrayList<String> parts,
             ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents) {
         sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
-                deliveryIntents, true /* persistMessage*/, ActivityThread.currentPackageName());
+                deliveryIntents, true /* persistMessage*/, null,
+                0L /* messageId */);
+    }
+
+    /**
+     * Send a multi-part text based SMS. Same as #sendMultipartTextMessage(String, String,
+     * ArrayList, ArrayList, ArrayList), but adds an optional messageId.
+     * @param messageId An id that uniquely identifies the message requested to be sent.
+     * Used for logging and diagnostics purposes. The id may be 0.
+     *
+     * @throws IllegalArgumentException if destinationAddress or data are empty
+     *
+     */
+    public void sendMultipartTextMessage(
+            @NonNull String destinationAddress, @Nullable String scAddress,
+            @NonNull List<String> parts, @Nullable List<PendingIntent> sentIntents,
+            @Nullable List<PendingIntent> deliveryIntents, long messageId) {
+        sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
+                deliveryIntents, true /* persistMessage*/, null,
+                messageId);
     }
 
     /**
@@ -898,26 +977,25 @@
      * subscription.
      * </p>
      *
-     * @param packageName serves as the default package name if
-     * {@link ActivityThread#currentPackageName()} is null.
+     * @param packageName serves as the default package name if the package name that is
+     *        associated with the user id is null.
+     *
      * @hide
      */
     @SystemApi
     @TestApi
     public void sendMultipartTextMessage(
-            @NonNull String destinationAddress, @NonNull String scAddress,
+            @NonNull String destinationAddress, @Nullable String scAddress,
             @NonNull List<String> parts, @Nullable List<PendingIntent> sentIntents,
             @Nullable List<PendingIntent> deliveryIntents, @NonNull String packageName) {
         sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
-                deliveryIntents, true /* persistMessage*/,
-                ActivityThread.currentPackageName() == null
-                        ? packageName : ActivityThread.currentPackageName());
+                deliveryIntents, true /* persistMessage*/, packageName, 0L /* messageId */);
     }
 
     private void sendMultipartTextMessageInternal(
             String destinationAddress, String scAddress, List<String> parts,
             List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents,
-            boolean persistMessage, String packageName) {
+            boolean persistMessage, String packageName, long messageId) {
         if (TextUtils.isEmpty(destinationAddress)) {
             throw new IllegalArgumentException("Invalid destinationAddress");
         }
@@ -944,10 +1022,11 @@
                             ISms iSms = getISmsServiceOrThrow();
                             iSms.sendMultipartTextForSubscriber(subId, packageName,
                                     destinationAddress, scAddress, parts, sentIntents,
-                                    deliveryIntents, persistMessage);
+                                    deliveryIntents, persistMessage, messageId);
                         } catch (RemoteException e) {
                             Log.e(TAG, "sendMultipartTextMessageInternal: Couldn't send SMS - "
-                                    + e.getMessage());
+                                    + e.getMessage() + " id: "
+                                    + messageId);
                             notifySmsError(sentIntents, RESULT_REMOTE_EXCEPTION);
                         }
                     }
@@ -964,11 +1043,11 @@
                     if (iSms != null) {
                         iSms.sendMultipartTextForSubscriber(getSubscriptionId(), packageName,
                                 destinationAddress, scAddress, parts, sentIntents, deliveryIntents,
-                                persistMessage);
+                                persistMessage, messageId);
                     }
                 } catch (RemoteException e) {
                     Log.e(TAG, "sendMultipartTextMessageInternal: Couldn't send SMS - "
-                            + e.getMessage());
+                            + e.getMessage() + " id: " + messageId);
                     notifySmsError(sentIntents, RESULT_REMOTE_EXCEPTION);
                 }
             }
@@ -982,7 +1061,7 @@
                 deliveryIntent = deliveryIntents.get(0);
             }
             sendTextMessageInternal(destinationAddress, scAddress, parts.get(0),
-                    sentIntent, deliveryIntent, true, packageName);
+                    sentIntent, deliveryIntent, true, packageName, messageId);
         }
     }
 
@@ -1012,7 +1091,8 @@
             String destinationAddress, String scAddress, List<String> parts,
             List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents) {
         sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
-                deliveryIntents, false /* persistMessage*/, ActivityThread.currentPackageName());
+                deliveryIntents, false /* persistMessage*/, null,
+                0L /* messageId */);
     }
 
     /**
@@ -1174,7 +1254,7 @@
                             ISms iSms = getISmsServiceOrThrow();
                             if (iSms != null) {
                                 iSms.sendMultipartTextForSubscriberWithOptions(subId,
-                                        ActivityThread.currentPackageName(), destinationAddress,
+                                        null, destinationAddress,
                                         scAddress, parts, sentIntents, deliveryIntents,
                                         persistMessage, finalPriority, expectMore, finalValidity);
                             }
@@ -1196,7 +1276,7 @@
                     ISms iSms = getISmsServiceOrThrow();
                     if (iSms != null) {
                         iSms.sendMultipartTextForSubscriberWithOptions(getSubscriptionId(),
-                                ActivityThread.currentPackageName(), destinationAddress,
+                                null, destinationAddress,
                                 scAddress, parts, sentIntents, deliveryIntents,
                                 persistMessage, finalPriority, expectMore, finalValidity);
                     }
@@ -1327,7 +1407,7 @@
             public void onSuccess(int subId) {
                 try {
                     ISms iSms = getISmsServiceOrThrow();
-                    iSms.sendDataForSubscriber(subId, ActivityThread.currentPackageName(),
+                    iSms.sendDataForSubscriber(subId, null,
                             destinationAddress, scAddress, destinationPort & 0xFFFF, data,
                             sentIntent, deliveryIntent);
                 } catch (RemoteException e) {
@@ -1453,7 +1533,6 @@
     private void resolveSubscriptionForOperation(SubscriptionResolverResult resolverResult) {
         int subId = getSubscriptionId();
         boolean isSmsSimPickActivityNeeded = false;
-        final Context context = ActivityThread.currentApplication().getApplicationContext();
         try {
             ISms iSms = getISmsService();
             if (iSms != null) {
@@ -1475,14 +1554,14 @@
             return;
         }
         // We need to ask the user pick an appropriate subid for the operation.
-        Log.d(TAG, "resolveSubscriptionForOperation isSmsSimPickActivityNeeded is true for package "
-                + context.getPackageName());
+        Log.d(TAG, "resolveSubscriptionForOperation isSmsSimPickActivityNeeded is true for calling"
+                + " package. ");
         try {
             // Create the SMS pick activity and call back once the activity is complete. Can't do
             // it here because we do not have access to the activity context that is performing this
             // operation.
             // Requires that the calling process has the SEND_SMS permission.
-            getITelephony().enqueueSmsPickResult(context.getOpPackageName(),
+            getITelephony().enqueueSmsPickResult(null,
                     new IIntegerConsumer.Stub() {
                         @Override
                         public void accept(int subId) {
@@ -1500,6 +1579,13 @@
         }
     }
 
+    /**
+     * To check the SDK version for SmsManager.sendResolverResult method.
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.P)
+    private static final long GET_TARGET_SDK_VERSION_CODE_CHANGE = 145147528L;
+
     private void sendResolverResult(SubscriptionResolverResult resolverResult, int subId,
             boolean pickActivityShown) {
         if (SubscriptionManager.isValidSubscriptionId(subId)) {
@@ -1507,7 +1593,8 @@
             return;
         }
 
-        if (getTargetSdkVersion() <= Build.VERSION_CODES.P && !pickActivityShown) {
+        if (!Compatibility.isChangeEnabled(GET_TARGET_SDK_VERSION_CODE_CHANGE)
+                && !pickActivityShown) {
             // Do not fail, return a success with an INVALID subid for apps targeting P or below
             // that tried to perform an operation and the SMS disambiguation dialog was never shown,
             // as these applications may not have been written to handle the failure case properly.
@@ -1520,22 +1607,12 @@
         }
     }
 
-    private static int getTargetSdkVersion() {
-        final Context context = ActivityThread.currentApplication().getApplicationContext();
-        int targetSdk;
-        try {
-            targetSdk = context.getPackageManager().getApplicationInfo(
-                    context.getOpPackageName(), 0).targetSdkVersion;
-        } catch (PackageManager.NameNotFoundException e) {
-            // Default to old behavior if we can not find this.
-            targetSdk = -1;
-        }
-        return targetSdk;
-    }
-
     private static ITelephony getITelephony() {
         ITelephony binder = ITelephony.Stub.asInterface(
-                ServiceManager.getService(Context.TELEPHONY_SERVICE));
+                TelephonyFrameworkInitializer
+                        .getTelephonyServiceManager()
+                        .getTelephonyServiceRegisterer()
+                        .get());
         if (binder == null) {
             throw new RuntimeException("Could not find Telephony Service.");
         }
@@ -1573,11 +1650,15 @@
     }
 
     private static ISms getISmsService() {
-        return ISms.Stub.asInterface(ServiceManager.getService("isms"));
+        return ISms.Stub.asInterface(
+                TelephonyFrameworkInitializer
+                        .getTelephonyServiceManager()
+                        .getSmsServiceRegisterer()
+                        .get());
     }
 
     /**
-     * Copy a raw SMS PDU to the ICC.
+     * Copies a raw SMS PDU to the ICC.
      * ICC (Integrated Circuit Card) is the card of the device.
      * For example, this can be the SIM or USIM for GSM.
      *
@@ -1591,27 +1672,32 @@
      * operation is performed on the correct subscription.
      * </p>
      *
-     * @param smsc the SMSC for this message, or NULL for the default SMSC
-     * @param pdu the raw PDU to store
-     * @param status message status (STATUS_ON_ICC_READ, STATUS_ON_ICC_UNREAD,
-     *               STATUS_ON_ICC_SENT, STATUS_ON_ICC_UNSENT)
-     * @return true for success
+     * @param smsc the SMSC for this messag or null for the default SMSC.
+     * @param pdu the raw PDU to store.
+     * @param status message status. One of these status:
+     *               <code>STATUS_ON_ICC_READ</code>
+     *               <code>STATUS_ON_ICC_UNREAD</code>
+     *               <code>STATUS_ON_ICC_SENT</code>
+     *               <code>STATUS_ON_ICC_UNSENT</code>
+     * @return true for success. Otherwise false.
      *
-     * @throws IllegalArgumentException if pdu is NULL
-     * {@hide}
+     * @throws IllegalArgumentException if pdu is null.
+     * @hide
      */
-    @UnsupportedAppUsage
-    public boolean copyMessageToIcc(byte[] smsc, byte[] pdu,int status) {
+    @SystemApi
+    @RequiresPermission(Manifest.permission.ACCESS_MESSAGES_ON_ICC)
+    public boolean copyMessageToIcc(
+            @Nullable byte[] smsc, @NonNull byte[] pdu, @StatusOnIcc int status) {
         boolean success = false;
 
-        if (null == pdu) {
-            throw new IllegalArgumentException("pdu is NULL");
+        if (pdu == null) {
+            throw new IllegalArgumentException("pdu is null");
         }
         try {
             ISms iSms = getISmsService();
             if (iSms != null) {
                 success = iSms.copyMessageToIccEfForSubscriber(getSubscriptionId(),
-                        ActivityThread.currentPackageName(),
+                        null,
                         status, pdu, smsc);
             }
         } catch (RemoteException ex) {
@@ -1622,7 +1708,7 @@
     }
 
     /**
-     * Delete the specified message from the ICC.
+     * Deletes the specified message from the ICC.
      * ICC (Integrated Circuit Card) is the card of the device.
      * For example, this can be the SIM or USIM for GSM.
      *
@@ -1652,7 +1738,7 @@
             ISms iSms = getISmsService();
             if (iSms != null) {
                 success = iSms.updateMessageOnIccEfForSubscriber(getSubscriptionId(),
-                        ActivityThread.currentPackageName(),
+                        null,
                         messageIndex, STATUS_ON_ICC_FREE, null /* pdu */);
             }
         } catch (RemoteException ex) {
@@ -1695,7 +1781,7 @@
             ISms iSms = getISmsService();
             if (iSms != null) {
                 success = iSms.updateMessageOnIccEfForSubscriber(getSubscriptionId(),
-                        ActivityThread.currentPackageName(),
+                        null,
                         messageIndex, newStatus, pdu);
             }
         } catch (RemoteException ex) {
@@ -1706,7 +1792,7 @@
     }
 
     /**
-     * Retrieves all messages currently stored on ICC.
+     * Retrieves all messages currently stored on the ICC.
      * ICC (Integrated Circuit Card) is the card of the device.
      * For example, this can be the SIM or USIM for GSM.
      *
@@ -1747,7 +1833,7 @@
             if (iSms != null) {
                 records = iSms.getAllMessagesFromIccEfForSubscriber(
                         getSubscriptionId(),
-                        ActivityThread.currentPackageName());
+                        null);
             }
         } catch (RemoteException ex) {
             // ignore it
@@ -1872,8 +1958,7 @@
     }
 
     /**
-     * Create a list of <code>SmsMessage</code>s from a list of RawSmsData
-     * records returned by <code>getAllMessagesFromIcc()</code>
+     * Creates a list of <code>SmsMessage</code>s from a list of SmsRawData records.
      *
      * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier
      * applications or the Telephony framework and will never trigger an SMS disambiguation
@@ -1885,8 +1970,7 @@
      * operation is performed on the correct subscription.
      * </p>
      *
-     * @param records SMS EF records, returned by
-     *   <code>getAllMessagesFromIcc</code>
+     * @param records SMS EF records.
      * @return <code>ArrayList</code> of <code>SmsMessage</code> objects.
      */
     private ArrayList<SmsMessage> createMessageListFromRawRecords(List<SmsRawData> records) {
@@ -1897,7 +1981,7 @@
                 SmsRawData data = records.get(i);
                 // List contains all records, including "free" records (null)
                 if (data != null) {
-                    SmsMessage sms = SmsMessage.createFromEfRecord(i+1, data.getBytes(),
+                    SmsMessage sms = SmsMessage.createFromEfRecord(i + 1, data.getBytes(),
                             getSubscriptionId());
                     if (sms != null) {
                         messages.add(sms);
@@ -2007,7 +2091,11 @@
     public boolean isSMSPromptEnabled() {
         ISms iSms = null;
         try {
-            iSms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+            iSms = ISms.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSmsServiceRegisterer()
+                            .get());
             return iSms.isSMSPromptEnabled();
         } catch (RemoteException ex) {
             return false;
@@ -2037,6 +2125,17 @@
         return ret;
     }
 
+    /** @hide */
+    @IntDef(prefix = { "STATUS_ON_ICC_" }, value = {
+            STATUS_ON_ICC_FREE,
+            STATUS_ON_ICC_READ,
+            STATUS_ON_ICC_UNREAD,
+            STATUS_ON_ICC_SENT,
+            STATUS_ON_ICC_UNSENT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface StatusOnIcc {}
+
     // see SmsMessage.getStatusOnIcc
 
     /** Free space (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
@@ -2434,14 +2533,18 @@
      * @param sentIntent if not NULL this <code>PendingIntent</code> is
      *  broadcast when the message is successfully sent, or failed
      * @throws IllegalArgumentException if contentUri is empty
+     * @deprecated use {@link MmsManager#sendMultimediaMessage} instead.
      */
     public void sendMultimediaMessage(Context context, Uri contentUri, String locationUrl,
             Bundle configOverrides, PendingIntent sentIntent) {
         if (contentUri == null) {
             throw new IllegalArgumentException("Uri contentUri null");
         }
-        MmsManager.getInstance().sendMultimediaMessage(getSubscriptionId(), contentUri,
-                    locationUrl, configOverrides, sentIntent);
+        MmsManager m = (MmsManager) context.getSystemService(Context.MMS_SERVICE);
+        if (m != null) {
+            m.sendMultimediaMessage(getSubscriptionId(), contentUri, locationUrl, configOverrides,
+                    sentIntent);
+        }
     }
 
     /**
@@ -2465,6 +2568,7 @@
      * @param downloadedIntent if not NULL this <code>PendingIntent</code> is
      *  broadcast when the message is downloaded, or the download is failed
      * @throws IllegalArgumentException if locationUrl or contentUri is empty
+     * @deprecated use {@link MmsManager#downloadMultimediaMessage} instead.
      */
     public void downloadMultimediaMessage(Context context, String locationUrl, Uri contentUri,
             Bundle configOverrides, PendingIntent downloadedIntent) {
@@ -2474,8 +2578,11 @@
         if (contentUri == null) {
             throw new IllegalArgumentException("Uri contentUri null");
         }
-        MmsManager.getInstance().downloadMultimediaMessage(getSubscriptionId(), locationUrl,
-                contentUri, configOverrides, downloadedIntent);
+        MmsManager m = (MmsManager) context.getSystemService(Context.MMS_SERVICE);
+        if (m != null) {
+            m.downloadMultimediaMessage(getSubscriptionId(), locationUrl, contentUri,
+                    configOverrides, downloadedIntent);
+        }
     }
 
     // MMS send/download failure result codes
@@ -2493,16 +2600,6 @@
     /** Intent extra name for HTTP status code for MMS HTTP failure in integer type */
     public static final String EXTRA_MMS_HTTP_STATUS = "android.telephony.extra.MMS_HTTP_STATUS";
 
-    /** Represents the received SMS message for importing {@hide} */
-    public static final int SMS_TYPE_INCOMING = 0;
-    /** Represents the sent SMS message for importing {@hide} */
-    public static final int SMS_TYPE_OUTGOING = 1;
-
-    /** Message status property: whether the message has been seen. 1 means seen, 0 not {@hide} */
-    public static final String MESSAGE_STATUS_SEEN = "seen";
-    /** Message status property: whether the message has been read. 1 means read, 0 not {@hide} */
-    public static final String MESSAGE_STATUS_READ = "read";
-
     /**
      * Get carrier-dependent MMS configuration values.
      *
@@ -2517,9 +2614,9 @@
      * </p>
      *
      * @return the bundle key/values pairs that contains MMS configuration values
+     *  or an empty Bundle if they cannot be found.
      */
-    @Nullable
-    public Bundle getCarrierConfigValues() {
+    @NonNull public Bundle getCarrierConfigValues() {
         try {
             ISms iSms = getISmsService();
             if (iSms != null) {
@@ -2528,7 +2625,7 @@
         } catch (RemoteException ex) {
             // ignore it
         }
-        return null;
+        return new Bundle();
     }
 
     /**
@@ -2559,7 +2656,7 @@
         try {
             ISms iccSms = getISmsServiceOrThrow();
             return iccSms.createAppSpecificSmsToken(getSubscriptionId(),
-                    ActivityThread.currentPackageName(), intent);
+                    null, intent);
 
         } catch (RemoteException ex) {
             ex.rethrowFromSystemServer();
@@ -2679,7 +2776,7 @@
         try {
             ISms iccSms = getISmsServiceOrThrow();
             return iccSms.createAppSpecificSmsTokenWithPackageInfo(getSubscriptionId(),
-                    ActivityThread.currentPackageName(), prefixes, intent);
+                    null, prefixes, intent);
 
         } catch (RemoteException ex) {
             ex.rethrowFromSystemServer();
@@ -2770,7 +2867,7 @@
             ISms iccISms = getISmsServiceOrThrow();
             if (iccISms != null) {
                 return iccISms.checkSmsShortCodeDestination(getSubscriptionId(),
-                        ActivityThread.currentPackageName(), null, destAddress, countryIso);
+                        null, null, destAddress, countryIso);
             }
         } catch (RemoteException e) {
             Log.e(TAG, "checkSmsShortCodeDestination() RemoteException", e);
@@ -2806,7 +2903,7 @@
             ISms iSms = getISmsService();
             if (iSms != null) {
                 smsc = iSms.getSmscAddressFromIccEfForSubscriber(
-                        getSubscriptionId(), ActivityThread.currentPackageName());
+                        getSubscriptionId(), null);
             }
         } catch (RemoteException ex) {
             // ignore it
@@ -2840,11 +2937,60 @@
             ISms iSms = getISmsService();
             if (iSms != null) {
                 return iSms.setSmscAddressOnIccEfForSubscriber(
-                        smsc, getSubscriptionId(), ActivityThread.currentPackageName());
+                        smsc, getSubscriptionId(), null);
             }
         } catch (RemoteException ex) {
             // ignore it
         }
         return false;
     }
+
+    /**
+     * Gets the premium SMS permission for the specified package. If the package has never
+     * been seen before, the default {@link SmsManager#PREMIUM_SMS_PERMISSION_ASK_USER}
+     * will be returned.
+     * @param packageName the name of the package to query permission
+     * @return one of {@link SmsManager#PREMIUM_SMS_CONSENT_UNKNOWN},
+     *  {@link SmsManager#PREMIUM_SMS_CONSENT_ASK_USER},
+     *  {@link SmsManager#PREMIUM_SMS_CONSENT_NEVER_ALLOW}, or
+     *  {@link SmsManager#PREMIUM_SMS_CONSENT_ALWAYS_ALLOW}
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public @PremiumSmsConsent int getPremiumSmsConsent(@NonNull String packageName) {
+        int permission = 0;
+        try {
+            ISms iSms = getISmsService();
+            if (iSms != null) {
+                permission = iSms.getPremiumSmsPermission(packageName);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "getPremiumSmsPermission() RemoteException", e);
+        }
+        return permission;
+    }
+
+    /**
+     * Sets the premium SMS permission for the specified package and save the value asynchronously
+     * to persistent storage.
+     * @param packageName the name of the package to set permission
+     * @param permission one of {@link SmsManager#PREMIUM_SMS_CONSENT_ASK_USER},
+     *  {@link SmsManager#PREMIUM_SMS_CONSENT_NEVER_ALLOW}, or
+     *  {@link SmsManager#PREMIUM_SMS_CONSENT_ALWAYS_ALLOW}
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public void setPremiumSmsConsent(
+            @NonNull String packageName, @PremiumSmsConsent int permission) {
+        try {
+            ISms iSms = getISmsService();
+            if (iSms != null) {
+                iSms.setPremiumSmsPermission(packageName, permission);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "setPremiumSmsPermission() RemoteException", e);
+        }
+    }
 }
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index c0bc29d..7a30f14 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -16,10 +16,17 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
 
+import android.Manifest;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.StringDef;
+import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.Resources;
 import android.os.Binder;
@@ -29,8 +36,10 @@
 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
 import com.android.internal.telephony.Sms7BitEncodingTranslator;
 import com.android.internal.telephony.SmsConstants;
+import com.android.internal.telephony.SmsHeader;
 import com.android.internal.telephony.SmsMessageBase;
 import com.android.internal.telephony.SmsMessageBase.SubmitPduBase;
+import com.android.internal.telephony.cdma.sms.UserData;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -54,6 +63,16 @@
         UNKNOWN, CLASS_0, CLASS_1, CLASS_2, CLASS_3;
     }
 
+    /** @hide */
+    @IntDef(prefix = { "ENCODING_" }, value = {
+            ENCODING_UNKNOWN,
+            ENCODING_7BIT,
+            ENCODING_8BIT,
+            ENCODING_16BIT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface EncodingSize {}
+
     /** User data text encoding code unit size */
     public static final int ENCODING_UNKNOWN = 0;
     public static final int ENCODING_7BIT = 1;
@@ -259,41 +278,24 @@
     }
 
     /**
-     * Create an SmsMessage from an SMS EF record.
+     * Creates an SmsMessage from an SMS EF record.
      *
-     * @param index Index of SMS record. This should be index in ArrayList
-     *              returned by SmsManager.getAllMessagesFromSim + 1.
+     * @param index Index of SMS EF record.
      * @param data Record data.
      * @return An SmsMessage representing the record.
      *
      * @hide
      */
     public static SmsMessage createFromEfRecord(int index, byte[] data) {
-        SmsMessageBase wrappedMessage;
-
-        if (isCdmaVoice()) {
-            wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromEfRecord(
-                    index, data);
-        } else {
-            wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromEfRecord(
-                    index, data);
-        }
-
-        if (wrappedMessage != null) {
-            return new SmsMessage(wrappedMessage);
-        } else {
-            Rlog.e(LOG_TAG, "createFromEfRecord(): wrappedMessage is null");
-            return null;
-        }
+        return createFromEfRecord(index, data, SmsManager.getDefaultSmsSubscriptionId());
     }
 
     /**
-     * Create an SmsMessage from an SMS EF record.
+     * Creates an SmsMessage from an SMS EF record.
      *
-     * @param index Index of SMS record. This should be index in ArrayList
-     *              returned by SmsManager.getAllMessagesFromSim + 1.
+     * @param index Index of SMS EF record.
      * @param data Record data.
-     * @param subId Subscription Id of the SMS
+     * @param subId Subscription Id associated with the record.
      * @return An SmsMessage representing the record.
      *
      * @hide
@@ -313,6 +315,34 @@
     }
 
     /**
+     * Create an SmsMessage from a native SMS-Submit PDU, specified by Bluetooth Message Access
+     * Profile Specification v1.4.2 5.8.
+     * This is used by Bluetooth MAP profile to decode message when sending non UTF-8 SMS messages.
+     *
+     * @param data Message data.
+     * @param isCdma Indicates weather the type of the SMS is CDMA.
+     * @return An SmsMessage representing the message.
+     *
+     * @hide
+     */
+    @SystemApi
+    @Nullable
+    public static SmsMessage createFromNativeSmsSubmitPdu(@NonNull byte[] data, boolean isCdma) {
+        SmsMessageBase wrappedMessage;
+
+        if (isCdma) {
+            wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromEfRecord(
+                    0, data);
+        } else {
+            // Bluetooth uses its own method to decode GSM PDU so this part is not called.
+            wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromEfRecord(
+                    0, data);
+        }
+
+        return wrappedMessage != null ? new SmsMessage(wrappedMessage) : null;
+    }
+
+    /**
      * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the
      * length in bytes (not hex chars) less the SMSC header
      *
@@ -555,13 +585,15 @@
      */
 
     /**
-     * Get an SMS-SUBMIT PDU for a destination address and a message.
+     * Gets an SMS-SUBMIT PDU for a destination address and a message.
      * This method will not attempt to use any GSM national language 7 bit encodings.
      *
-     * @param scAddress Service Centre address.  Null means use default.
-     * @return a <code>SubmitPdu</code> containing the encoded SC
-     *         address, if applicable, and the encoded message.
-     *         Returns null on encode error.
+     * @param scAddress Service Centre address. Null means use default.
+     * @param destinationAddress the address of the destination for the message.
+     * @param message string representation of the message payload.
+     * @param statusReportRequested indicates whether a report is requested for this message.
+     * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the
+     *         encoded message. Returns null on encode error.
      */
     public static SubmitPdu getSubmitPdu(String scAddress,
             String destinationAddress, String message, boolean statusReportRequested) {
@@ -574,17 +606,16 @@
     }
 
     /**
-     * Get an SMS-SUBMIT PDU for a destination address and a message.
+     * Gets an SMS-SUBMIT PDU for a destination address and a message.
      * This method will not attempt to use any GSM national language 7 bit encodings.
      *
-     * @param scAddress Service Centre address.  Null means use default.
+     * @param scAddress Service Centre address. Null means use default.
      * @param destinationAddress the address of the destination for the message.
-     * @param message String representation of the message payload.
-     * @param statusReportRequested Indicates whether a report is requested for this message.
-     * @param subId Subscription of the message
-     * @return a <code>SubmitPdu</code> containing the encoded SC
-     *         address, if applicable, and the encoded message.
-     *         Returns null on encode error.
+     * @param message string representation of the message payload.
+     * @param statusReportRequested indicates whether a report is requested for this message.
+     * @param subId subscription of the message.
+     * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the
+     *         encoded message. Returns null on encode error.
      * @hide
      */
     public static SubmitPdu getSubmitPdu(String scAddress,
@@ -602,17 +633,16 @@
     }
 
     /**
-     * Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port.
+     * Gets an SMS-SUBMIT PDU for a data message to a destination address &amp; port.
      * This method will not attempt to use any GSM national language 7 bit encodings.
      *
-     * @param scAddress Service Centre address. null == use default
-     * @param destinationAddress the address of the destination for the message
-     * @param destinationPort the port to deliver the message to at the
-     *        destination
-     * @param data the data for the message
-     * @return a <code>SubmitPdu</code> containing the encoded SC
-     *         address, if applicable, and the encoded message.
-     *         Returns null on encode error.
+     * @param scAddress Service Centre address. Null means use default.
+     * @param destinationAddress the address of the destination for the message.
+     * @param destinationPort the port to deliver the message to at the destination.
+     * @param data the data for the message.
+     * @param statusReportRequested indicates whether a report is requested for this message.
+     * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the
+     *         encoded message. Returns null on encode error.
      */
     public static SubmitPdu getSubmitPdu(String scAddress,
             String destinationAddress, short destinationPort, byte[] data,
@@ -630,6 +660,132 @@
         return new SubmitPdu(spb);
     }
 
+    // TODO: SubmitPdu class is used for SMS-DELIVER also now. Refactor for SubmitPdu and new
+    // DeliverPdu accordingly.
+
+    /**
+     * Gets an SMS PDU to store in the ICC.
+     *
+     * @param subId subscription of the message.
+     * @param status message status. One of these status:
+     *               <code>SmsManager.STATUS_ON_ICC_READ</code>
+     *               <code>SmsManager.STATUS_ON_ICC_UNREAD</code>
+     *               <code>SmsManager.STATUS_ON_ICC_SENT</code>
+     *               <code>SmsManager.STATUS_ON_ICC_UNSENT</code>
+     * @param scAddress Service Centre address. Null means use default.
+     * @param address destination or originating address.
+     * @param message string representation of the message payload.
+     * @param date the time stamp the message was received.
+     * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the
+     *         encoded message. Returns null on encode error.
+     * @hide
+     */
+    @SystemApi
+    @Nullable
+    public static SubmitPdu getSmsPdu(int subId, @SmsManager.StatusOnIcc int status,
+            @Nullable String scAddress, @NonNull String address, @NonNull String message,
+            long date) {
+        SubmitPduBase spb;
+        if (isCdmaVoice(subId)) { // 3GPP2 format
+            if (status == SmsManager.STATUS_ON_ICC_READ
+                    || status == SmsManager.STATUS_ON_ICC_UNREAD) { // Deliver PDU
+                spb = com.android.internal.telephony.cdma.SmsMessage.getDeliverPdu(address,
+                        message, date);
+            } else { // Submit PDU
+                spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress,
+                        address, message, false /* statusReportRequested */, null /* smsHeader */);
+            }
+        } else { // 3GPP format
+            if (status == SmsManager.STATUS_ON_ICC_READ
+                    || status == SmsManager.STATUS_ON_ICC_UNREAD) { // Deliver PDU
+                spb = com.android.internal.telephony.gsm.SmsMessage.getDeliverPdu(scAddress,
+                        address, message, date);
+            } else { // Submit PDU
+                spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress,
+                        address, message, false /* statusReportRequested */, null /* header */);
+            }
+        }
+
+        return spb != null ? new SubmitPdu(spb) : null;
+    }
+
+    /**
+     * Get an SMS-SUBMIT PDU's encoded message.
+     * This is used by Bluetooth MAP profile to handle long non UTF-8 SMS messages.
+     *
+     * @param isTypeGsm true when message's type is GSM, false when type is CDMA
+     * @param destinationAddress the address of the destination for the message
+     * @param message message content
+     * @param encoding User data text encoding code unit size
+     * @param languageTable GSM national language table to use, specified by 3GPP
+     *                      23.040 9.2.3.24.16
+     * @param languageShiftTable GSM national language shift table to use, specified by 3GPP
+     *                           23.040 9.2.3.24.15
+     * @param refNumber parameter to create SmsHeader
+     * @param seqNumber parameter to create SmsHeader
+     * @param msgCount parameter to create SmsHeader
+     * @return a byte[] containing the encoded message
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @SystemApi
+    @NonNull
+    public static byte[] getSubmitPduEncodedMessage(boolean isTypeGsm,
+            @NonNull String destinationAddress,
+            @NonNull String message,
+            @EncodingSize int encoding, int languageTable,
+            int languageShiftTable, int refNumber,
+            int seqNumber, int msgCount) {
+        byte[] data;
+        SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
+        concatRef.refNumber = refNumber;
+        concatRef.seqNumber = seqNumber;  // 1-based sequence
+        concatRef.msgCount = msgCount;
+        // We currently set this to true since our messaging app will never
+        // send more than 255 parts (it converts the message to MMS well before that).
+        // However, we should support 3rd party messaging apps that might need 16-bit
+        // references
+        // Note:  It's not sufficient to just flip this bit to true; it will have
+        // ripple effects (several calculations assume 8-bit ref).
+        concatRef.isEightBits = true;
+        SmsHeader smsHeader = new SmsHeader();
+        smsHeader.concatRef = concatRef;
+
+        /* Depending on the type, call either GSM or CDMA getSubmitPdu(). The encoding
+         * will be determined(again) by getSubmitPdu().
+         * All packets need to be encoded using the same encoding, as the bMessage
+         * only have one filed to describe the encoding for all messages in a concatenated
+         * SMS... */
+        if (encoding == ENCODING_7BIT) {
+            smsHeader.languageTable = languageTable;
+            smsHeader.languageShiftTable = languageShiftTable;
+        }
+
+        if (isTypeGsm) {
+            data = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(null,
+                    destinationAddress, message, false,
+                    SmsHeader.toByteArray(smsHeader), encoding, languageTable,
+                    languageShiftTable).encodedMessage;
+        } else { // SMS_TYPE_CDMA
+            UserData uData = new UserData();
+            uData.payloadStr = message;
+            uData.userDataHeader = smsHeader;
+            if (encoding == ENCODING_7BIT) {
+                uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET;
+            } else { // assume UTF-16
+                uData.msgEncoding = UserData.ENCODING_UNICODE_16;
+            }
+            uData.msgEncodingSet = true;
+            data = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(
+                    destinationAddress, uData, false).encodedMessage;
+        }
+        if (data == null) {
+            return new byte[0];
+        }
+        return data;
+    }
+
     /**
      * Returns the address of the SMS service center that relayed this message
      * or null if there is none.
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 2c0a1c9..c24eeb7 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 22eed6e..63a85fa2 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -16,8 +16,8 @@
 
 package android.telephony;
 
-import static android.net.NetworkPolicyManager.OVERRIDE_CONGESTED;
-import static android.net.NetworkPolicyManager.OVERRIDE_UNMETERED;
+import static android.net.NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_CONGESTED;
+import static android.net.NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_UNMETERED;
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
@@ -45,6 +45,7 @@
 import android.database.ContentObserver;
 import android.net.INetworkPolicyManager;
 import android.net.NetworkCapabilities;
+import android.net.NetworkPolicyManager;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -53,7 +54,6 @@
 import android.os.ParcelUuid;
 import android.os.Process;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.provider.Telephony.SimInfo;
 import android.telephony.euicc.EuiccManager;
 import android.telephony.ims.ImsMmTelManager;
@@ -66,6 +66,7 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.util.HandlerExecutor;
 import com.android.internal.util.Preconditions;
+import com.android.telephony.Rlog;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -265,7 +266,7 @@
      * <P>Type: TEXT (String)</P>
      */
     /** @hide */
-    public static final String UNIQUE_KEY_SUBSCRIPTION_ID = "_id";
+    public static final String UNIQUE_KEY_SUBSCRIPTION_ID = SimInfo.UNIQUE_KEY_SUBSCRIPTION_ID;
 
     /**
      * TelephonyProvider column name for a unique identifier for the subscription within the
@@ -274,18 +275,18 @@
      * <P>Type: TEXT (String)</P>
      */
     /** @hide */
-    public static final String ICC_ID = "icc_id";
+    public static final String ICC_ID = SimInfo.ICC_ID;
 
     /**
      * TelephonyProvider column name for user SIM_SlOT_INDEX
      * <P>Type: INTEGER (int)</P>
      */
     /** @hide */
-    public static final String SIM_SLOT_INDEX = "sim_id";
+    public static final String SIM_SLOT_INDEX = SimInfo.SIM_SLOT_INDEX;
 
     /** SIM is not inserted */
     /** @hide */
-    public static final int SIM_NOT_INSERTED = -1;
+    public static final int SIM_NOT_INSERTED = SimInfo.SIM_NOT_INSERTED;
 
     /**
      * The slot-index for Bluetooth Remote-SIM subscriptions
@@ -300,23 +301,7 @@
      * Default value is 0.
      */
     /** @hide */
-    public static final String SUBSCRIPTION_TYPE = "subscription_type";
-
-    /**
-     * TelephonyProvider column name white_listed_apn_data.
-     * It's a bitmask of APN types that will be allowed on this subscription even if it's metered
-     * and mobile data is turned off by the user.
-     * <P>Type: INTEGER (int)</P> For example, if TYPE_MMS is is true, Telephony will allow MMS
-     * data connection to setup even if MMS is metered and mobile_data is turned off on that
-     * subscription.
-     *
-     * Default value is 0.
-     *
-     * @deprecated Replaced by {@link #DATA_ENABLED_OVERRIDE_RULES}
-     * @hide
-     */
-    @Deprecated
-    public static final String WHITE_LISTED_APN_DATA = "white_listed_apn_data";
+    public static final String SUBSCRIPTION_TYPE = SimInfo.SUBSCRIPTION_TYPE;
 
     /**
      * TelephonyProvider column name data_enabled_override_rules.
@@ -329,7 +314,15 @@
      *
      * @hide
      */
-    public static final String DATA_ENABLED_OVERRIDE_RULES = "data_enabled_override_rules";
+    public static final String DATA_ENABLED_OVERRIDE_RULES = SimInfo.DATA_ENABLED_OVERRIDE_RULES;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"SUBSCRIPTION_TYPE_"},
+        value = {
+            SUBSCRIPTION_TYPE_LOCAL_SIM,
+            SUBSCRIPTION_TYPE_REMOTE_SIM})
+    public @interface SubscriptionType {}
 
     /**
      * This constant is to designate a subscription as a Local-SIM Subscription.
@@ -337,7 +330,7 @@
      * device.
      * </p>
      */
-    public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0;
+    public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = SimInfo.SUBSCRIPTION_TYPE_LOCAL_SIM;
 
     /**
      * This constant is to designate a subscription as a Remote-SIM Subscription.
@@ -363,29 +356,21 @@
      * was never seen before.
      * </p>
      */
-    public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1;
-
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = {"SUBSCRIPTION_TYPE_"},
-        value = {
-            SUBSCRIPTION_TYPE_LOCAL_SIM,
-            SUBSCRIPTION_TYPE_REMOTE_SIM})
-    public @interface SubscriptionType {}
+    public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = SimInfo.SUBSCRIPTION_TYPE_REMOTE_SIM;
 
     /**
      * TelephonyProvider column name for user displayed name.
      * <P>Type: TEXT (String)</P>
      */
     /** @hide */
-    public static final String DISPLAY_NAME = "display_name";
+    public static final String DISPLAY_NAME = SimInfo.DISPLAY_NAME;
 
     /**
      * TelephonyProvider column name for the service provider name for the SIM.
      * <P>Type: TEXT (String)</P>
      */
     /** @hide */
-    public static final String CARRIER_NAME = "carrier_name";
+    public static final String CARRIER_NAME = SimInfo.CARRIER_NAME;
 
     /**
      * Default name resource
@@ -399,44 +384,44 @@
      *
      * @hide
      */
-    public static final String NAME_SOURCE = "name_source";
+    public static final String NAME_SOURCE = SimInfo.NAME_SOURCE;
 
     /**
      * The name_source is the default, which is from the carrier id.
      * @hide
      */
-    public static final int NAME_SOURCE_DEFAULT_SOURCE = 0;
+    public static final int NAME_SOURCE_DEFAULT = SimInfo.NAME_SOURCE_DEFAULT;
 
     /**
      * The name_source is from SIM EF_SPN.
      * @hide
      */
-    public static final int NAME_SOURCE_SIM_SPN = 1;
+    public static final int NAME_SOURCE_SIM_SPN = SimInfo.NAME_SOURCE_SIM_SPN;
 
     /**
      * The name_source is from user input
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    public static final int NAME_SOURCE_USER_INPUT = 2;
+    public static final int NAME_SOURCE_USER_INPUT = SimInfo.NAME_SOURCE_USER_INPUT;
 
     /**
      * The name_source is carrier (carrier app, carrier config, etc.)
      * @hide
      */
-    public static final int NAME_SOURCE_CARRIER = 3;
+    public static final int NAME_SOURCE_CARRIER = SimInfo.NAME_SOURCE_CARRIER;
 
     /**
      * The name_source is from SIM EF_PNN.
      * @hide
      */
-    public static final int NAME_SOURCE_SIM_PNN = 4;
+    public static final int NAME_SOURCE_SIM_PNN = SimInfo.NAME_SOURCE_SIM_PNN;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"NAME_SOURCE_"},
             value = {
-                    NAME_SOURCE_DEFAULT_SOURCE,
+                    NAME_SOURCE_DEFAULT,
                     NAME_SOURCE_SIM_SPN,
                     NAME_SOURCE_USER_INPUT,
                     NAME_SOURCE_CARRIER,
@@ -449,67 +434,30 @@
      * <P>Type: INTEGER (int)</P>
      */
     /** @hide */
-    public static final String COLOR = "color";
-
-    /** @hide */
-    public static final int COLOR_1 = 0;
-
-    /** @hide */
-    public static final int COLOR_2 = 1;
-
-    /** @hide */
-    public static final int COLOR_3 = 2;
-
-    /** @hide */
-    public static final int COLOR_4 = 3;
-
-    /** @hide */
-    public static final int COLOR_DEFAULT = COLOR_1;
+    public static final String COLOR = SimInfo.COLOR;
 
     /**
      * TelephonyProvider column name for the phone number of a SIM.
      * <P>Type: TEXT (String)</P>
      */
     /** @hide */
-    public static final String NUMBER = "number";
+    public static final String NUMBER = SimInfo.NUMBER;
 
     /**
-     * TelephonyProvider column name for the number display format of a SIM.
+     * TelephonyProvider column name for whether data roaming is enabled.
      * <P>Type: INTEGER (int)</P>
      */
     /** @hide */
-    public static final String DISPLAY_NUMBER_FORMAT = "display_number_format";
-
-    /** @hide */
-    public static final int DISPLAY_NUMBER_NONE = 0;
-
-    /** @hide */
-    public static final int DISPLAY_NUMBER_FIRST = 1;
-
-    /** @hide */
-    public static final int DISPLAY_NUMBER_LAST = 2;
-
-    /** @hide */
-    public static final int DISPLAY_NUMBER_DEFAULT = DISPLAY_NUMBER_FIRST;
-
-    /**
-     * TelephonyProvider column name for permission for data roaming of a SIM.
-     * <P>Type: INTEGER (int)</P>
-     */
-    /** @hide */
-    public static final String DATA_ROAMING = "data_roaming";
+    public static final String DATA_ROAMING = SimInfo.DATA_ROAMING;
 
     /** Indicates that data roaming is enabled for a subscription */
-    public static final int DATA_ROAMING_ENABLE = 1;
+    public static final int DATA_ROAMING_ENABLE = SimInfo.DATA_ROAMING_ENABLE;
 
     /** Indicates that data roaming is disabled for a subscription */
-    public static final int DATA_ROAMING_DISABLE = 0;
+    public static final int DATA_ROAMING_DISABLE = SimInfo.DATA_ROAMING_DISABLE;
 
     /** @hide */
-    public static final int DATA_ROAMING_DEFAULT = DATA_ROAMING_DISABLE;
-
-    /** @hide */
-    public static final int SIM_PROVISIONED = 0;
+    public static final int DATA_ROAMING_DEFAULT = SimInfo.DATA_ROAMING_DEFAULT;
 
     /**
      * TelephonyProvider column name for subscription carrier id.
@@ -517,61 +465,61 @@
      * <p>Type: INTEGER (int) </p>
      * @hide
      */
-    public static final String CARRIER_ID = "carrier_id";
+    public static final String CARRIER_ID = SimInfo.CARRIER_ID;
 
     /**
      * @hide A comma-separated list of EHPLMNs associated with the subscription
      * <P>Type: TEXT (String)</P>
      */
-    public static final String EHPLMNS = "ehplmns";
+    public static final String EHPLMNS = SimInfo.EHPLMNS;
 
     /**
      * @hide A comma-separated list of HPLMNs associated with the subscription
      * <P>Type: TEXT (String)</P>
      */
-    public static final String HPLMNS = "hplmns";
+    public static final String HPLMNS = SimInfo.HPLMNS;
 
     /**
      * TelephonyProvider column name for the MCC associated with a SIM, stored as a string.
      * <P>Type: TEXT (String)</P>
      * @hide
      */
-    public static final String MCC_STRING = "mcc_string";
+    public static final String MCC_STRING = SimInfo.MCC_STRING;
 
     /**
      * TelephonyProvider column name for the MNC associated with a SIM, stored as a string.
      * <P>Type: TEXT (String)</P>
      * @hide
      */
-    public static final String MNC_STRING = "mnc_string";
+    public static final String MNC_STRING = SimInfo.MNC_STRING;
 
     /**
      * TelephonyProvider column name for the MCC associated with a SIM.
      * <P>Type: INTEGER (int)</P>
      * @hide
      */
-    public static final String MCC = "mcc";
+    public static final String MCC = SimInfo.MCC;
 
     /**
      * TelephonyProvider column name for the MNC associated with a SIM.
      * <P>Type: INTEGER (int)</P>
      * @hide
      */
-    public static final String MNC = "mnc";
+    public static final String MNC = SimInfo.MNC;
 
     /**
      * TelephonyProvider column name for the iso country code associated with a SIM.
      * <P>Type: TEXT (String)</P>
      * @hide
      */
-    public static final String ISO_COUNTRY_CODE = "iso_country_code";
+    public static final String ISO_COUNTRY_CODE = SimInfo.ISO_COUNTRY_CODE;
 
     /**
      * TelephonyProvider column name for the sim provisioning status associated with a SIM.
      * <P>Type: INTEGER (int)</P>
      * @hide
      */
-    public static final String SIM_PROVISIONING_STATUS = "sim_provisioning_status";
+    public static final String SIM_PROVISIONING_STATUS = SimInfo.SIM_PROVISIONING_STATUS;
 
     /**
      * TelephonyProvider column name for whether a subscription is embedded (that is, present on an
@@ -579,7 +527,7 @@
      * <p>Type: INTEGER (int), 1 for embedded or 0 for non-embedded.
      * @hide
      */
-    public static final String IS_EMBEDDED = "is_embedded";
+    public static final String IS_EMBEDDED = SimInfo.IS_EMBEDDED;
 
     /**
      * TelephonyProvider column name for SIM card identifier. For UICC card it is the ICCID of the
@@ -587,7 +535,7 @@
      * <P>Type: TEXT (String)</P>
      * @hide
      */
-    public static final String CARD_ID = "card_id";
+    public static final String CARD_ID = SimInfo.CARD_ID;
 
     /**
      * TelephonyProvider column name for the encoded {@link UiccAccessRule}s from
@@ -595,7 +543,7 @@
      * <p>TYPE: BLOB
      * @hide
      */
-    public static final String ACCESS_RULES = "access_rules";
+    public static final String ACCESS_RULES = SimInfo.ACCESS_RULES;
 
     /**
      * TelephonyProvider column name for the encoded {@link UiccAccessRule}s from
@@ -605,7 +553,7 @@
      * @hide
      */
     public static final String ACCESS_RULES_FROM_CARRIER_CONFIGS =
-            "access_rules_from_carrier_configs";
+            SimInfo.ACCESS_RULES_FROM_CARRIER_CONFIGS;
 
     /**
      * TelephonyProvider column name identifying whether an embedded subscription is on a removable
@@ -615,79 +563,79 @@
      * <p>TYPE: INTEGER (int), 1 for removable or 0 for non-removable.
      * @hide
      */
-    public static final String IS_REMOVABLE = "is_removable";
+    public static final String IS_REMOVABLE = SimInfo.IS_REMOVABLE;
 
     /**
      *  TelephonyProvider column name for extreme threat in CB settings
      * @hide
      */
-    public static final String CB_EXTREME_THREAT_ALERT = "enable_cmas_extreme_threat_alerts";
+    public static final String CB_EXTREME_THREAT_ALERT = SimInfo.CB_EXTREME_THREAT_ALERT;
 
     /**
      * TelephonyProvider column name for severe threat in CB settings
      *@hide
      */
-    public static final String CB_SEVERE_THREAT_ALERT = "enable_cmas_severe_threat_alerts";
+    public static final String CB_SEVERE_THREAT_ALERT = SimInfo.CB_SEVERE_THREAT_ALERT;
 
     /**
      * TelephonyProvider column name for amber alert in CB settings
      *@hide
      */
-    public static final String CB_AMBER_ALERT = "enable_cmas_amber_alerts";
+    public static final String CB_AMBER_ALERT = SimInfo.CB_AMBER_ALERT;
 
     /**
      * TelephonyProvider column name for emergency alert in CB settings
      *@hide
      */
-    public static final String CB_EMERGENCY_ALERT = "enable_emergency_alerts";
+    public static final String CB_EMERGENCY_ALERT = SimInfo.CB_EMERGENCY_ALERT;
 
     /**
      * TelephonyProvider column name for alert sound duration in CB settings
      *@hide
      */
-    public static final String CB_ALERT_SOUND_DURATION = "alert_sound_duration";
+    public static final String CB_ALERT_SOUND_DURATION = SimInfo.CB_ALERT_SOUND_DURATION;
 
     /**
      * TelephonyProvider column name for alert reminder interval in CB settings
      *@hide
      */
-    public static final String CB_ALERT_REMINDER_INTERVAL = "alert_reminder_interval";
+    public static final String CB_ALERT_REMINDER_INTERVAL = SimInfo.CB_ALERT_REMINDER_INTERVAL;
 
     /**
      * TelephonyProvider column name for enabling vibrate in CB settings
      *@hide
      */
-    public static final String CB_ALERT_VIBRATE = "enable_alert_vibrate";
+    public static final String CB_ALERT_VIBRATE = SimInfo.CB_ALERT_VIBRATE;
 
     /**
      * TelephonyProvider column name for enabling alert speech in CB settings
      *@hide
      */
-    public static final String CB_ALERT_SPEECH = "enable_alert_speech";
+    public static final String CB_ALERT_SPEECH = SimInfo.CB_ALERT_SPEECH;
 
     /**
      * TelephonyProvider column name for ETWS test alert in CB settings
      *@hide
      */
-    public static final String CB_ETWS_TEST_ALERT = "enable_etws_test_alerts";
+    public static final String CB_ETWS_TEST_ALERT = SimInfo.CB_ETWS_TEST_ALERT;
 
     /**
      * TelephonyProvider column name for enable channel50 alert in CB settings
      *@hide
      */
-    public static final String CB_CHANNEL_50_ALERT = "enable_channel_50_alerts";
+    public static final String CB_CHANNEL_50_ALERT = SimInfo.CB_CHANNEL_50_ALERT;
 
     /**
      * TelephonyProvider column name for CMAS test alert in CB settings
      *@hide
      */
-    public static final String CB_CMAS_TEST_ALERT= "enable_cmas_test_alerts";
+    public static final String CB_CMAS_TEST_ALERT = SimInfo.CB_CMAS_TEST_ALERT;
 
     /**
      * TelephonyProvider column name for Opt out dialog in CB settings
      *@hide
      */
-    public static final String CB_OPT_OUT_DIALOG = "show_cmas_opt_out_dialog";
+    public static final String CB_OPT_OUT_DIALOG = SimInfo.CB_OPT_OUT_DIALOG;
 
     /**
      * TelephonyProvider column name for enable Volte.
@@ -696,37 +644,37 @@
      * {@link CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}.
      *@hide
      */
-    public static final String ENHANCED_4G_MODE_ENABLED = "volte_vt_enabled";
+    public static final String ENHANCED_4G_MODE_ENABLED = SimInfo.ENHANCED_4G_MODE_ENABLED;
 
     /**
      * TelephonyProvider column name for enable VT (Video Telephony over IMS)
      *@hide
      */
-    public static final String VT_IMS_ENABLED = "vt_ims_enabled";
+    public static final String VT_IMS_ENABLED = SimInfo.VT_IMS_ENABLED;
 
     /**
      * TelephonyProvider column name for enable Wifi calling
      *@hide
      */
-    public static final String WFC_IMS_ENABLED = "wfc_ims_enabled";
+    public static final String WFC_IMS_ENABLED = SimInfo.WFC_IMS_ENABLED;
 
     /**
      * TelephonyProvider column name for Wifi calling mode
      *@hide
      */
-    public static final String WFC_IMS_MODE = "wfc_ims_mode";
+    public static final String WFC_IMS_MODE = SimInfo.WFC_IMS_MODE;
 
     /**
      * TelephonyProvider column name for Wifi calling mode in roaming
      *@hide
      */
-    public static final String WFC_IMS_ROAMING_MODE = "wfc_ims_roaming_mode";
+    public static final String WFC_IMS_ROAMING_MODE = SimInfo.WFC_IMS_ROAMING_MODE;
 
     /**
      * TelephonyProvider column name for enable Wifi calling in roaming
      *@hide
      */
-    public static final String WFC_IMS_ROAMING_ENABLED = "wfc_ims_roaming_enabled";
+    public static final String WFC_IMS_ROAMING_ENABLED = SimInfo.WFC_IMS_ROAMING_ENABLED;
 
     /**
      * TelephonyProvider column name for whether a subscription is opportunistic, that is,
@@ -735,7 +683,7 @@
      * <p>Type: INTEGER (int), 1 for opportunistic or 0 for non-opportunistic.
      * @hide
      */
-    public static final String IS_OPPORTUNISTIC = "is_opportunistic";
+    public static final String IS_OPPORTUNISTIC = SimInfo.IS_OPPORTUNISTIC;
 
     /**
      * TelephonyProvider column name for group ID. Subscriptions with same group ID
@@ -744,7 +692,7 @@
      *
      * @hide
      */
-    public static final String GROUP_UUID = "group_uuid";
+    public static final String GROUP_UUID = SimInfo.GROUP_UUID;
 
     /**
      * TelephonyProvider column name for group owner. It's the package name who created
@@ -752,14 +700,7 @@
      *
      * @hide
      */
-    public static final String GROUP_OWNER = "group_owner";
-
-    /**
-     * TelephonyProvider column name for whether a subscription is metered or not, that is, whether
-     * the network it connects to charges for subscription or not. For example, paid CBRS or unpaid.
-     * @hide
-     */
-    public static final String IS_METERED = "is_metered";
+    public static final String GROUP_OWNER = SimInfo.GROUP_OWNER;
 
     /**
      * TelephonyProvider column name for the profile class of a subscription
@@ -767,7 +708,7 @@
      * <P>Type: INTEGER (int)</P>
      * @hide
      */
-    public static final String PROFILE_CLASS = "profile_class";
+    public static final String PROFILE_CLASS = SimInfo.PROFILE_CLASS;
 
     /**
      * Profile class of the subscription
@@ -775,11 +716,11 @@
      */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = { "PROFILE_CLASS_" }, value = {
-            PROFILE_CLASS_TESTING,
-            PROFILE_CLASS_PROVISIONING,
-            PROFILE_CLASS_OPERATIONAL,
-            PROFILE_CLASS_UNSET,
-            PROFILE_CLASS_DEFAULT
+            SimInfo.PROFILE_CLASS_TESTING,
+            SimInfo.PROFILE_CLASS_PROVISIONING,
+            SimInfo.PROFILE_CLASS_OPERATIONAL,
+            SimInfo.PROFILE_CLASS_UNSET,
+            SimInfo.PROFILE_CLASS_DEFAULT
     })
     public @interface ProfileClass {}
 
@@ -791,7 +732,7 @@
      * @hide
      */
     @SystemApi
-    public static final int PROFILE_CLASS_TESTING = 0;
+    public static final int PROFILE_CLASS_TESTING = SimInfo.PROFILE_CLASS_TESTING;
 
     /**
      * A provisioning profile is pre-loaded onto the eUICC and
@@ -800,7 +741,7 @@
      * @hide
      */
     @SystemApi
-    public static final int PROFILE_CLASS_PROVISIONING = 1;
+    public static final int PROFILE_CLASS_PROVISIONING = SimInfo.PROFILE_CLASS_PROVISIONING;
 
     /**
      * An operational profile can be pre-loaded or downloaded
@@ -809,7 +750,7 @@
      * @hide
      */
     @SystemApi
-    public static final int PROFILE_CLASS_OPERATIONAL = 2;
+    public static final int PROFILE_CLASS_OPERATIONAL = SimInfo.PROFILE_CLASS_OPERATIONAL;
 
     /**
      * The profile class is unset. This occurs when profile class
@@ -818,14 +759,14 @@
      * @hide
      */
     @SystemApi
-    public static final int PROFILE_CLASS_UNSET = -1;
+    public static final int PROFILE_CLASS_UNSET = SimInfo.PROFILE_CLASS_UNSET;
 
     /**
      * Default profile class
      * @hide
      */
     @SystemApi
-    public static final int PROFILE_CLASS_DEFAULT = PROFILE_CLASS_UNSET;
+    public static final int PROFILE_CLASS_DEFAULT = SimInfo.PROFILE_CLASS_DEFAULT;
 
     /**
      * IMSI (International Mobile Subscriber Identity).
@@ -833,13 +774,19 @@
      * @hide
      */
     //TODO: add @SystemApi
-    public static final String IMSI = "imsi";
+    public static final String IMSI = SimInfo.IMSI;
 
     /**
      * Whether uicc applications is set to be enabled or disabled. By default it's enabled.
      * @hide
      */
-    public static final String UICC_APPLICATIONS_ENABLED = "uicc_applications_enabled";
+    public static final String UICC_APPLICATIONS_ENABLED = SimInfo.UICC_APPLICATIONS_ENABLED;
+
+    /**
+     * Indicate which network type is allowed. By default it's enabled.
+     * @hide
+     */
+    public static final String ALLOWED_NETWORK_TYPES = SimInfo.ALLOWED_NETWORK_TYPES;
 
     /**
      * Broadcast Action: The user has changed one of the default subs related to
@@ -1011,6 +958,11 @@
         mContext = context;
     }
 
+    private NetworkPolicyManager getNetworkPolicyManager() {
+        return (NetworkPolicyManager) mContext
+                .getSystemService(Context.NETWORK_POLICY_SERVICE);
+    }
+
     /**
      * @deprecated developers should always obtain references directly from
      *             {@link Context#getSystemService(Class)}.
@@ -1021,10 +973,13 @@
                 .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
     }
 
-    private final INetworkPolicyManager getNetworkPolicy() {
+    private INetworkPolicyManager getINetworkPolicyManager() {
         if (mNetworkPolicy == null) {
-            mNetworkPolicy = INetworkPolicyManager.Stub
-                    .asInterface(ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
+            mNetworkPolicy = INetworkPolicyManager.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getNetworkPolicyServiceRegisterer()
+                            .get());
         }
         return mNetworkPolicy;
     }
@@ -1041,6 +996,23 @@
      */
     public void addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) {
         if (listener == null) return;
+        addOnSubscriptionsChangedListener(listener.mExecutor, listener);
+    }
+
+    /**
+     * Register for changes to the list of active {@link SubscriptionInfo} records or to the
+     * individual records themselves. When a change occurs the onSubscriptionsChanged method of
+     * the listener will be invoked immediately if there has been a notification. The
+     * onSubscriptionChanged method will also be triggered once initially when calling this
+     * function.
+     *
+     * @param listener an instance of {@link OnSubscriptionsChangedListener} with
+     *                 onSubscriptionsChanged overridden.
+     * @param executor the executor that will execute callbacks.
+     */
+    public void addOnSubscriptionsChangedListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnSubscriptionsChangedListener listener) {
         String pkgName = mContext != null ? mContext.getOpPackageName() : "<unknown>";
         if (DBG) {
             logd("register OnSubscriptionsChangedListener pkgName=" + pkgName
@@ -1052,7 +1024,7 @@
                 mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
         if (telephonyRegistryManager != null) {
             telephonyRegistryManager.addOnSubscriptionsChangedListener(listener,
-                    listener.mExecutor);
+                    executor);
         }
     }
 
@@ -1185,7 +1157,11 @@
         SubscriptionInfo subInfo = null;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 subInfo = iSub.getActiveSubscriptionInfo(subId, mContext.getOpPackageName(),
                         mContext.getFeatureId());
@@ -1219,7 +1195,11 @@
         SubscriptionInfo result = null;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 result = iSub.getActiveSubscriptionInfoForIccId(iccId, mContext.getOpPackageName(),
                         mContext.getFeatureId());
@@ -1253,7 +1233,11 @@
         SubscriptionInfo result = null;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 result = iSub.getActiveSubscriptionInfoForSimSlotIndex(slotIndex,
                         mContext.getOpPackageName(), mContext.getFeatureId());
@@ -1277,7 +1261,11 @@
         List<SubscriptionInfo> result = null;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 result = iSub.getAllSubInfoList(mContext.getOpPackageName(),
                         mContext.getFeatureId());
@@ -1358,7 +1346,11 @@
         List<SubscriptionInfo> activeList = null;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 activeList = iSub.getActiveSubscriptionInfoList(mContext.getOpPackageName(),
                         mContext.getFeatureId());
@@ -1409,7 +1401,11 @@
         List<SubscriptionInfo> result = null;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 result = iSub.getAvailableSubscriptionInfoList(mContext.getOpPackageName(),
                         mContext.getFeatureId());
@@ -1448,7 +1444,11 @@
         List<SubscriptionInfo> result = null;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 result = iSub.getAccessibleSubscriptionInfoList(mContext.getOpPackageName());
             }
@@ -1477,7 +1477,11 @@
     public void requestEmbeddedSubscriptionInfoListRefresh() {
         int cardId = TelephonyManager.from(mContext).getCardIdForDefaultEuicc();
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 iSub.requestEmbeddedSubscriptionInfoListRefresh(cardId);
             }
@@ -1506,7 +1510,11 @@
     @SystemApi
     public void requestEmbeddedSubscriptionInfoListRefresh(int cardId) {
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 iSub.requestEmbeddedSubscriptionInfoListRefresh(cardId);
             }
@@ -1527,7 +1535,11 @@
         int result = 0;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 result = iSub.getAllSubInfoCount(mContext.getOpPackageName(),
                         mContext.getFeatureId());
@@ -1556,7 +1568,11 @@
         int result = 0;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 result = iSub.getActiveSubInfoCount(mContext.getOpPackageName(),
                         mContext.getFeatureId());
@@ -1577,7 +1593,11 @@
         int result = 0;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 result = iSub.getActiveSubInfoCountMax();
             }
@@ -1634,7 +1654,11 @@
         }
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub == null) {
                 Log.e(LOG_TAG, "[addSubscriptionInfoRecord]- ISub service is null");
                 return;
@@ -1668,7 +1692,11 @@
         }
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub == null) {
                 Log.e(LOG_TAG, "[removeSubscriptionInfoRecord]- ISub service is null");
                 return;
@@ -1771,7 +1799,11 @@
         int result = INVALID_SIM_SLOT_INDEX;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 result = iSub.getSlotIndex(subscriptionId);
             }
@@ -1805,7 +1837,11 @@
         int[] subId = null;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 subId = iSub.getSubId(slotIndex);
             }
@@ -1829,7 +1865,11 @@
         int result = INVALID_PHONE_INDEX;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 result = iSub.getPhoneId(subId);
             }
@@ -1863,7 +1903,11 @@
         int subId = INVALID_SUBSCRIPTION_ID;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 subId = iSub.getDefaultSubId();
             }
@@ -1886,7 +1930,11 @@
         int subId = INVALID_SUBSCRIPTION_ID;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 subId = iSub.getDefaultVoiceSubId();
             }
@@ -1916,7 +1964,11 @@
     public void setDefaultVoiceSubscriptionId(int subscriptionId) {
         if (VDBG) logd("setDefaultVoiceSubId sub id = " + subscriptionId);
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 iSub.setDefaultVoiceSubId(subscriptionId);
             }
@@ -1964,7 +2016,11 @@
         int subId = INVALID_SUBSCRIPTION_ID;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 subId = iSub.getDefaultSmsSubId();
             }
@@ -1990,7 +2046,11 @@
     public void setDefaultSmsSubId(int subscriptionId) {
         if (VDBG) logd("setDefaultSmsSubId sub id = " + subscriptionId);
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 iSub.setDefaultSmsSubId(subscriptionId);
             }
@@ -2028,7 +2088,11 @@
         int subId = INVALID_SUBSCRIPTION_ID;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 subId = iSub.getDefaultDataSubId();
             }
@@ -2054,7 +2118,11 @@
     public void setDefaultDataSubId(int subscriptionId) {
         if (VDBG) logd("setDataSubscription sub id = " + subscriptionId);
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 iSub.setDefaultDataSubId(subscriptionId);
             }
@@ -2085,7 +2153,11 @@
     /** @hide */
     public void clearSubscriptionInfo() {
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 iSub.clearSubInfo();
             }
@@ -2172,6 +2244,7 @@
         } else {
             logd("putPhoneIdAndSubIdExtra: no valid subs");
             intent.putExtra(PhoneConstants.PHONE_KEY, phoneId);
+            intent.putExtra(EXTRA_SLOT_INDEX, phoneId);
         }
     }
 
@@ -2179,10 +2252,9 @@
     @UnsupportedAppUsage
     public static void putPhoneIdAndSubIdExtra(Intent intent, int phoneId, int subId) {
         if (VDBG) logd("putPhoneIdAndSubIdExtra: phoneId=" + phoneId + " subId=" + subId);
-        intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
-        intent.putExtra(EXTRA_SUBSCRIPTION_INDEX, subId);
         intent.putExtra(EXTRA_SLOT_INDEX, phoneId);
         intent.putExtra(PhoneConstants.PHONE_KEY, phoneId);
+        putSubscriptionIdExtra(intent, subId);
     }
 
     /**
@@ -2221,7 +2293,11 @@
      */
     public @NonNull int[] getActiveSubscriptionIdList(boolean visibleOnly) {
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 int[] subId = iSub.getActiveSubIdList(visibleOnly);
                 if (subId != null) return subId;
@@ -2272,7 +2348,11 @@
         int simState = TelephonyManager.SIM_STATE_UNKNOWN;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 simState = iSub.getSimStateForSlotIndex(slotIndex);
             }
@@ -2291,7 +2371,11 @@
      */
     public static void setSubscriptionProperty(int subId, String propKey, String propValue) {
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 iSub.setSubscriptionProperty(subId, propKey, propValue);
             }
@@ -2311,7 +2395,11 @@
             Context context) {
         String resultValue = null;
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 resultValue = iSub.getSubscriptionProperty(subId, propKey,
                         context.getOpPackageName(), context.getFeatureId());
@@ -2358,7 +2446,28 @@
             try {
                 return Integer.parseInt(result);
             } catch (NumberFormatException err) {
-                logd("getBooleanSubscriptionProperty NumberFormat exception");
+                logd("getIntegerSubscriptionProperty NumberFormat exception");
+            }
+        }
+        return defValue;
+    }
+
+    /**
+     * Returns long value corresponding to query result.
+     * @param subId Subscription Id of Subscription
+     * @param propKey Column name in SubscriptionInfo database
+     * @param defValue Default long value to be returned
+     * @return long result value to be returned
+     * @hide
+     */
+    public static long getLongSubscriptionProperty(int subId, String propKey, long defValue,
+                                                     Context context) {
+        String result = getSubscriptionProperty(subId, propKey, context);
+        if (result != null) {
+            try {
+                return Long.parseLong(result);
+            } catch (NumberFormatException err) {
+                logd("getLongSubscriptionProperty NumberFormat exception");
             }
         }
         return defValue;
@@ -2453,7 +2562,11 @@
     @UnsupportedAppUsage
     public boolean isActiveSubId(int subId) {
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 return iSub.isActiveSubId(subId, mContext.getOpPackageName(),
                         mContext.getFeatureId());
@@ -2480,14 +2593,10 @@
      *             outlined above.
      */
     public @NonNull List<SubscriptionPlan> getSubscriptionPlans(int subId) {
-        try {
-            SubscriptionPlan[] subscriptionPlans =
-                    getNetworkPolicy().getSubscriptionPlans(subId, mContext.getOpPackageName());
-            return subscriptionPlans == null
-                    ? Collections.emptyList() : Arrays.asList(subscriptionPlans);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        SubscriptionPlan[] subscriptionPlans =
+                getNetworkPolicyManager().getSubscriptionPlans(subId, mContext.getOpPackageName());
+        return subscriptionPlans == null
+                ? Collections.emptyList() : Arrays.asList(subscriptionPlans);
     }
 
     /**
@@ -2513,18 +2622,14 @@
      *             defined in {@link SubscriptionPlan}.
      */
     public void setSubscriptionPlans(int subId, @NonNull List<SubscriptionPlan> plans) {
-        try {
-            getNetworkPolicy().setSubscriptionPlans(subId,
-                    plans.toArray(new SubscriptionPlan[plans.size()]), mContext.getOpPackageName());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        getNetworkPolicyManager().setSubscriptionPlans(subId,
+                plans.toArray(new SubscriptionPlan[plans.size()]), mContext.getOpPackageName());
     }
 
     /** @hide */
     private String getSubscriptionPlansOwner(int subId) {
         try {
-            return getNetworkPolicy().getSubscriptionPlansOwner(subId);
+            return getINetworkPolicyManager().getSubscriptionPlansOwner(subId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2555,13 +2660,10 @@
      */
     public void setSubscriptionOverrideUnmetered(int subId, boolean overrideUnmetered,
             @DurationMillisLong long timeoutMillis) {
-        try {
-            final int overrideValue = overrideUnmetered ? OVERRIDE_UNMETERED : 0;
-            getNetworkPolicy().setSubscriptionOverride(subId, OVERRIDE_UNMETERED, overrideValue,
-                    timeoutMillis, mContext.getOpPackageName());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+
+        final int overrideValue = overrideUnmetered ? SUBSCRIPTION_OVERRIDE_UNMETERED : 0;
+        getNetworkPolicyManager().setSubscriptionOverride(subId, SUBSCRIPTION_OVERRIDE_UNMETERED,
+                overrideValue, timeoutMillis, mContext.getOpPackageName());
     }
 
     /**
@@ -2590,13 +2692,9 @@
      */
     public void setSubscriptionOverrideCongested(int subId, boolean overrideCongested,
             @DurationMillisLong long timeoutMillis) {
-        try {
-            final int overrideValue = overrideCongested ? OVERRIDE_CONGESTED : 0;
-            getNetworkPolicy().setSubscriptionOverride(subId, OVERRIDE_CONGESTED, overrideValue,
-                    timeoutMillis, mContext.getOpPackageName());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        final int overrideValue = overrideCongested ? SUBSCRIPTION_OVERRIDE_CONGESTED : 0;
+        getNetworkPolicyManager().setSubscriptionOverride(subId, SUBSCRIPTION_OVERRIDE_CONGESTED,
+                overrideValue, timeoutMillis, mContext.getOpPackageName());
     }
 
     /**
@@ -2756,7 +2854,11 @@
             @TelephonyManager.SetOpportunisticSubscriptionResult Consumer<Integer> callback) {
         if (VDBG) logd("[setPreferredDataSubscriptionId]+ subId:" + subId);
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub == null) return;
 
             ISetOpportunisticDataCallback callbackStub = new ISetOpportunisticDataCallback.Stub() {
@@ -2799,7 +2901,11 @@
     public int getPreferredDataSubscriptionId() {
         int preferredSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 preferredSubId = iSub.getPreferredDataSubscriptionId();
             }
@@ -2830,7 +2936,11 @@
         List<SubscriptionInfo> subInfoList = null;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 subInfoList = iSub.getOpportunisticSubscriptions(contextPkg, contextFeature);
             }
@@ -2931,7 +3041,11 @@
         ParcelUuid groupUuid = null;
         int[] subIdArray = subIdList.stream().mapToInt(i->i).toArray();
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 groupUuid = iSub.createSubscriptionGroup(subIdArray, pkgForDebug);
             } else {
@@ -2981,7 +3095,11 @@
         int[] subIdArray = subIdList.stream().mapToInt(i->i).toArray();
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 iSub.addSubscriptionsIntoGroup(subIdArray, groupUuid, pkgForDebug);
             } else {
@@ -3033,7 +3151,11 @@
         int[] subIdArray = subIdList.stream().mapToInt(i->i).toArray();
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 iSub.removeSubscriptionsFromGroup(subIdArray, groupUuid, pkgForDebug);
             } else {
@@ -3078,7 +3200,11 @@
 
         List<SubscriptionInfo> result = null;
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 result = iSub.getSubscriptionsInGroup(groupUuid, contextPkg, contextFeature);
             } else {
@@ -3191,7 +3317,11 @@
             logd("setSubscriptionActivated subId= " + subscriptionId + " enable " + enable);
         }
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 return iSub.setSubscriptionEnabled(enable, subscriptionId);
             }
@@ -3221,7 +3351,11 @@
             logd("setUiccApplicationsEnabled subId= " + subscriptionId + " enable " + enabled);
         }
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 iSub.setUiccApplicationsEnabled(enabled, subscriptionId);
             }
@@ -3251,7 +3385,11 @@
             logd("canDisablePhysicalSubscription");
         }
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 return iSub.canDisablePhysicalSubscription();
             }
@@ -3272,7 +3410,11 @@
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public boolean isSubscriptionEnabled(int subscriptionId) {
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 return iSub.isSubscriptionEnabled(subscriptionId);
             }
@@ -3295,7 +3437,11 @@
         int subId = INVALID_SUBSCRIPTION_ID;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 subId = iSub.getEnabledSubscriptionId(slotIndex);
             }
@@ -3321,7 +3467,11 @@
         int result = 0;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 result = helper.callMethod(iSub);
             }
@@ -3344,7 +3494,11 @@
      */
     public static int getActiveDataSubscriptionId() {
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 return iSub.getActiveDataSubscriptionId();
             }
@@ -3352,4 +3506,19 @@
         }
         return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     }
+
+    /**
+     * Helper method that puts a subscription id on an intent with the constants:
+     * PhoneConstant.SUBSCRIPTION_KEY and SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX.
+     * Both constants are used to support backwards compatibility.  Once we know we got all places,
+     * we can remove PhoneConstants.SUBSCRIPTION_KEY.
+     * @param intent Intent to put sub id on.
+     * @param subId SubscriptionId to put on intent.
+     *
+     * @hide
+     */
+    public static void putSubscriptionIdExtra(Intent intent, int subId) {
+        intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
+        intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
+    }
 }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 4a42b6f..903be6f 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -37,7 +37,6 @@
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.annotation.WorkerThread;
-import android.app.ActivityThread;
 import android.app.PendingIntent;
 import android.compat.Compatibility;
 import android.compat.annotation.ChangeId;
@@ -51,7 +50,6 @@
 import android.net.NetworkStats;
 import android.net.Uri;
 import android.os.AsyncTask;
-import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -60,7 +58,6 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
-import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.os.WorkSource;
 import android.provider.Settings.SettingNotFoundException;
@@ -96,6 +93,7 @@
 import com.android.ims.internal.IImsServiceFeatureCallback;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.CellNetworkScanResult;
+import com.android.internal.telephony.IBooleanConsumer;
 import com.android.internal.telephony.INumberVerificationCallback;
 import com.android.internal.telephony.IOns;
 import com.android.internal.telephony.IPhoneSubInfo;
@@ -109,8 +107,6 @@
 import com.android.internal.telephony.SmsApplication;
 import com.android.telephony.Rlog;
 
-import dalvik.system.VMRuntime;
-
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.lang.annotation.Retention;
@@ -164,8 +160,8 @@
      * into the ResultReceiver Bundle.
      * @hide
      */
-    public static final String MODEM_ACTIVITY_RESULT_KEY =
-            BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY;
+    @SystemApi
+    public static final String MODEM_ACTIVITY_RESULT_KEY = "controller_activity";
 
     /**
      * The process name of the Phone app as well as many other apps that use this process name, such
@@ -197,11 +193,8 @@
                     NETWORK_SELECTION_MODE_MANUAL})
     public @interface NetworkSelectionMode {}
 
-    /** @hide */
     public static final int NETWORK_SELECTION_MODE_UNKNOWN = 0;
-    /** @hide */
     public static final int NETWORK_SELECTION_MODE_AUTO = 1;
-    /** @hide */
     public static final int NETWORK_SELECTION_MODE_MANUAL = 2;
 
     /** The otaspMode passed to PhoneStateListener#onOtaspChanged */
@@ -382,8 +375,17 @@
         // effort and get the context from the current activity thread.
         if (mContext != null) {
             return mContext.getOpPackageName();
+        } else {
+            ITelephony telephony = getITelephony();
+            if (telephony == null) return null;
+            try {
+                return telephony.getCurrentPackageName();
+            } catch (RemoteException ex) {
+                return null;
+            } catch (NullPointerException ex) {
+                return null;
+            }
         }
-        return ActivityThread.currentOpPackageName();
     }
 
     private String getFeatureId() {
@@ -450,12 +452,8 @@
             case UNKNOWN:
                 modemCount = MODEM_COUNT_SINGLE_MODEM;
                 // check for voice and data support, 0 if not supported
-                if (!isVoiceCapable() && !isSmsCapable() && mContext != null) {
-                    ConnectivityManager cm = (ConnectivityManager) mContext
-                            .getSystemService(Context.CONNECTIVITY_SERVICE);
-                    if (cm != null && !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)) {
-                        modemCount = MODEM_COUNT_NO_MODEM;
-                    }
+                if (!isVoiceCapable() && !isSmsCapable() && !isDataCapable()) {
+                    modemCount = MODEM_COUNT_NO_MODEM;
                 }
                 break;
             case DSDS:
@@ -786,30 +784,6 @@
     public static final String EXTRA_PRECISE_DISCONNECT_CAUSE = "precise_disconnect_cause";
 
     /**
-     * The lookup key used with the {@link #ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED} broadcast
-     * for an String containing the data APN type.
-     *
-     * <p class="note">
-     * Retrieve with
-     * {@link android.content.Intent#getStringExtra(String name)}.
-     *
-     * @hide
-     */
-    public static final String EXTRA_DATA_APN_TYPE = PhoneConstants.DATA_APN_TYPE_KEY;
-
-    /**
-     * The lookup key used with the {@link #ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED} broadcast
-     * for an String containing the data APN.
-     *
-     * <p class="note">
-     * Retrieve with
-     * {@link android.content.Intent#getStringExtra(String name)}.
-     *
-     * @hide
-     */
-    public static final String EXTRA_DATA_APN = PhoneConstants.DATA_APN_KEY;
-
-    /**
      * Broadcast intent action for letting the default dialer to know to show voicemail
      * notification.
      *
@@ -1134,6 +1108,16 @@
      */
     public static final int CDMA_ROAMING_MODE_ANY = 2;
 
+    /** @hide */
+    @IntDef(prefix = { "CDMA_ROAMING_MODE_" }, value = {
+            CDMA_ROAMING_MODE_RADIO_DEFAULT,
+            CDMA_ROAMING_MODE_HOME,
+            CDMA_ROAMING_MODE_AFFILIATED,
+            CDMA_ROAMING_MODE_ANY
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CdmaRoamingMode{}
+
     /**
      * An unknown carrier id. It could either be subscription unavailable or the subscription
      * carrier cannot be recognized. Unrecognized carriers here means
@@ -1260,6 +1244,80 @@
     public static final String EXTRA_SUBSCRIPTION_ID = "android.telephony.extra.SUBSCRIPTION_ID";
 
     /**
+     * Broadcast Action: The Service Provider string(s) have been updated. Activities or
+     * services that use these strings should update their display.
+     *
+     * <p>The intent will have the following extra values:
+     * <dl>
+     *   <dt>{@link #EXTRA_SHOW_PLMN}</dt>
+     *   <dd>Boolean that indicates whether the PLMN should be shown.</dd>
+     *   <dt>{@link #EXTRA_PLMN}</dt>
+     *   <dd>The operator name of the registered network, as a string.</dd>
+     *   <dt>{@link #EXTRA_SHOW_SPN}</dt>
+     *   <dd>Boolean that indicates whether the SPN should be shown.</dd>
+     *   <dt>{@link #EXTRA_SPN}</dt>
+     *   <dd>The service provider name, as a string.</dd>
+     *   <dt>{@link #EXTRA_DATA_SPN}</dt>
+     *   <dd>The service provider name for data service, as a string.</dd>
+     * </dl>
+     *
+     * Note that {@link #EXTRA_SHOW_PLMN} may indicate that {@link #EXTRA_PLMN} should be displayed,
+     * even though the value for {@link #EXTRA_PLMN} is null. This can happen, for example, if the
+     * phone has not registered to a network yet. In this case the receiver may substitute an
+     * appropriate placeholder string (eg, "No service").
+     *
+     * It is recommended to display {@link #EXTRA_PLMN} before / above {@link #EXTRA_SPN} if
+     * both are displayed.
+     *
+     * <p>Note: this is a protected intent that can only be sent by the system.
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_SERVICE_PROVIDERS_UPDATED =
+            "android.telephony.action.SERVICE_PROVIDERS_UPDATED";
+
+    /**
+     * String intent extra to be used with {@link ACTION_SERVICE_PROVIDERS_UPDATED} to indicate
+     * whether the PLMN should be shown.
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_SHOW_PLMN = "android.telephony.extra.SHOW_PLMN";
+
+    /**
+     * String intent extra to be used with {@link ACTION_SERVICE_PROVIDERS_UPDATED} to indicate
+     * the operator name of the registered network.
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_PLMN = "android.telephony.extra.PLMN";
+
+    /**
+     * String intent extra to be used with {@link ACTION_SERVICE_PROVIDERS_UPDATED} to indicate
+     * whether the PLMN should be shown.
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_SHOW_SPN = "android.telephony.extra.SHOW_SPN";
+
+    /**
+     * String intent extra to be used with {@link ACTION_SERVICE_PROVIDERS_UPDATED} to indicate
+     * the service provider name.
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_SPN = "android.telephony.extra.SPN";
+
+    /**
+     * String intent extra to be used with {@link ACTION_SERVICE_PROVIDERS_UPDATED} to indicate
+     * the service provider name for data service.
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_DATA_SPN = "android.telephony.extra.DATA_SPN";
+
+    /**
      * Broadcast intent action indicating that when data stall recovery is attempted by Telephony,
      * intended for report every data stall recovery step attempted.
      *
@@ -1377,6 +1435,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final String EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE =
             "android.telephony.extra.DEFAULT_SUBSCRIPTION_SELECT_TYPE";
 
@@ -1396,6 +1455,7 @@
      * to indicate there's no need to re-select any default subscription.
      * @hide
      */
+    @SystemApi
     public static final int EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_NONE = 0;
 
     /**
@@ -1403,6 +1463,7 @@
      * to indicate there's a need to select default data subscription.
      * @hide
      */
+    @SystemApi
     public static final int EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_DATA = 1;
 
     /**
@@ -1410,6 +1471,7 @@
      * to indicate there's a need to select default voice call subscription.
      * @hide
      */
+    @SystemApi
     public static final int EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_VOICE = 2;
 
     /**
@@ -1417,6 +1479,7 @@
      * to indicate there's a need to select default sms subscription.
      * @hide
      */
+    @SystemApi
     public static final int EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_SMS = 3;
 
     /**
@@ -1426,6 +1489,7 @@
      * which subscription should be the default subscription.
      * @hide
      */
+    @SystemApi
     public static final int EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_ALL = 4;
 
     /**
@@ -1435,6 +1499,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final String EXTRA_SIM_COMBINATION_WARNING_TYPE =
             "android.telephony.extra.SIM_COMBINATION_WARNING_TYPE";
 
@@ -1451,6 +1516,7 @@
      * to indicate there's no SIM combination warning.
      * @hide
      */
+    @SystemApi
     public static final int EXTRA_SIM_COMBINATION_WARNING_TYPE_NONE = 0;
 
     /**
@@ -1458,6 +1524,7 @@
      * to indicate two active SIMs are both CDMA hence there might be functional limitation.
      * @hide
      */
+    @SystemApi
     public static final int EXTRA_SIM_COMBINATION_WARNING_TYPE_DUAL_CDMA = 1;
 
     /**
@@ -1468,31 +1535,15 @@
      *
      * @hide
      */
+    @SystemApi
     public static final String EXTRA_SIM_COMBINATION_NAMES =
             "android.telephony.extra.SIM_COMBINATION_NAMES";
 
     /**
-     * Broadcast Action: The time was set by the carrier (typically by the NITZ string).
-     * This is a sticky broadcast.
-     * The intent will have the following extra values:</p>
-     * <ul>
-     *   <li><em>time</em> - The time as a long in UTC milliseconds.</li>
-     * </ul>
-     *
-     * <p class="note">
-     * Requires the READ_PHONE_STATE permission.
-     *
-     * <p class="note">This is a protected intent that can only be sent by the system.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String ACTION_NETWORK_SET_TIME = "android.telephony.action.NETWORK_SET_TIME";
-
-    /**
      * <p>Broadcast Action: The emergency callback mode is changed.
      * <ul>
-     *   <li><em>phoneinECMState</em> - A boolean value,true=phone in ECM, false=ECM off</li>
+     *   <li><em>EXTRA_PHONE_IN_ECM_STATE</em> - A boolean value,true=phone in ECM,
+     *   false=ECM off</li>
      * </ul>
      * <p class="note">
      * You can <em>not</em> receive this through components declared
@@ -1502,18 +1553,218 @@
      *
      * <p class="note">This is a protected intent that can only be sent by the system.
      *
+     * @see #EXTRA_PHONE_IN_ECM_STATE
+     *
      * @hide
      */
     @SystemApi
     @SuppressLint("ActionValue")
-    public static final String ACTION_EMERGENCY_CALLBACK_MODE_CHANGED
-            = "android.intent.action.EMERGENCY_CALLBACK_MODE_CHANGED";
+    public static final String ACTION_EMERGENCY_CALLBACK_MODE_CHANGED =
+            "android.intent.action.EMERGENCY_CALLBACK_MODE_CHANGED";
+
+
+    /**
+     * Extra included in {@link #ACTION_EMERGENCY_CALLBACK_MODE_CHANGED}.
+     * Indicates whether the phone is in an emergency phone state.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_PHONE_IN_ECM_STATE =
+            "android.telephony.extra.PHONE_IN_ECM_STATE";
+
+    /**
+     * <p>Broadcast Action: when data connections get redirected with validation failure.
+     * intended for sim/account status checks and only sent to the specified carrier app
+     * The intent will have the following extra values:</p>
+     * <ul>
+     *   <li>{@link #EXTRA_APN_TYPE}</li><dd>A string with the apn type.</dd>
+     *   <li>{@link #EXTRA_APN_TYPE_INT}</li><dd>A integer with the apn type.</dd>
+     *   <li>{@link #EXTRA_REDIRECTION_URL}</li><dd>redirection url string</dd>
+     *   <li>subId</li><dd>Sub Id which associated the data connection failure.</dd>
+     * </ul>
+     * <p class="note">This is a protected intent that can only be sent by the system.</p>
+     * @hide
+     */
+    @SystemApi
+    @SuppressLint("ActionValue")
+    public static final String ACTION_CARRIER_SIGNAL_REDIRECTED =
+            "com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED";
+
+    /**
+     * <p>Broadcast Action: when data connections setup fails.
+     * intended for sim/account status checks and only sent to the specified carrier app
+     * The intent will have the following extra values:</p>
+     * <ul>
+     *   <li>{@link #EXTRA_APN_TYPE}</li><dd>A string with the apn type.</dd>
+     *   <li>{@link #EXTRA_APN_TYPE_INT}</li><dd>A integer with the apn type.</dd>
+     *   <li>{@link #EXTRA_ERROR_CODE}</li><dd>A integer with dataFailCause.</dd>
+     *   <li>subId</li><dd>Sub Id which associated the data connection failure.</dd>
+     * </ul>
+     * <p class="note">This is a protected intent that can only be sent by the system. </p>
+     * @hide
+     */
+    @SystemApi
+    @SuppressLint("ActionValue")
+    public static final String ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED =
+            "com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED";
+
+    /**
+     * <p>Broadcast Action: when pco value is available.
+     * intended for sim/account status checks and only sent to the specified carrier app
+     * The intent will have the following extra values:</p>
+     * <ul>
+     *   <li>{@link #EXTRA_APN_TYPE}</li><dd>A string with the apn type.</dd>
+     *   <li>{@link #EXTRA_APN_TYPE_INT}</li><dd>A integer with the apn type.</dd>
+     *   <li>{@link #EXTRA_APN_PROTOCOL}</li><dd>A string with the protocol of the apn connection
+     *      (IP,IPV6, IPV4V6)</dd>
+     *   <li>{@link #EXTRA_APN_PROTOCOL_INT}</li><dd>A integer with the protocol of the apn
+     *      connection (IP,IPV6, IPV4V6)</dd>
+     *   <li>{@link #EXTRA_PCO_ID}</li><dd>An integer indicating the pco id for the data.</dd>
+     *   <li>{@link #EXTRA_PCO_VALUE}</li><dd>A byte array of pco data read from modem.</dd>
+     *   <li>subId</li><dd>Sub Id which associated the data connection.</dd>
+     * </ul>
+     * <p class="note">This is a protected intent that can only be sent by the system. </p>
+     * @hide
+     */
+    @SystemApi
+    @SuppressLint("ActionValue")
+    public static final String ACTION_CARRIER_SIGNAL_PCO_VALUE =
+            "com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE";
+
+    /**
+     * <p>Broadcast Action: when system default network available/unavailable with
+     * carrier-disabled mobile data. Intended for carrier apps to set/reset carrier actions when
+     * other network becomes system default network, Wi-Fi for example.
+     * The intent will have the following extra values:</p>
+     * <ul>
+     *   <li>{@link #EXTRA_DEFAULT_NETWORK_AVAILABLE}</li>
+     *   <dd>A boolean indicates default network available.</dd>
+     *   <li>subId</li><dd>Sub Id which associated the default data.</dd>
+     * </ul>
+     * <p class="note">This is a protected intent that can only be sent by the system. </p>
+     * @hide
+     */
+    @SystemApi
+    @SuppressLint("ActionValue")
+    public static final String ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE =
+            "com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE";
+
+    /**
+     * <p>Broadcast Action: when framework reset all carrier actions on sim load or absent.
+     * intended for carrier apps clean up (clear UI e.g.) and only sent to the specified carrier app
+     * The intent will have the following extra values:</p>
+     * <ul>
+     *   <li>subId</li><dd>Sub Id which associated the data connection failure.</dd>
+     * </ul>
+     * <p class="note">This is a protected intent that can only be sent by the system.</p>
+     * @hide
+     */
+    @SystemApi
+    @SuppressLint("ActionValue")
+    public static final String ACTION_CARRIER_SIGNAL_RESET =
+            "com.android.internal.telephony.CARRIER_SIGNAL_RESET";
+
+    // CARRIER_SIGNAL_ACTION extra keys
+    /**
+     *  An string extra of redirected url upon {@link #ACTION_CARRIER_SIGNAL_REDIRECTED}.
+     *  @hide
+     */
+    @SystemApi
+    @SuppressLint("ActionValue")
+    public static final String EXTRA_REDIRECTION_URL = "redirectionUrl";
+
+    /**
+     *  An integer extra of error code upon {@link #ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED}.
+     *  Check {@link DataFailCause} for all possible values.
+     *  @hide
+     */
+    @SystemApi
+    @SuppressLint("ActionValue")
+    public static final String EXTRA_ERROR_CODE = "errorCode";
+
+    /**
+     *  An string extra of corresponding apn type upon
+     *  {@link #ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED},
+     *  {@link #ACTION_CARRIER_SIGNAL_REDIRECTED} and
+     *  {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts.
+     *  @deprecated This is kept for backward compatibility reason. Use {@link #EXTRA_APN_TYPE_INT}
+     *  instead.
+     *
+     *  @hide
+     */
+    @SystemApi
+    @Deprecated
+    @SuppressLint("ActionValue")
+    public static final String EXTRA_APN_TYPE = "apnType";
+
+    /**
+     *  An string integer of corresponding apn type upon
+     *  {@link #ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED},
+     *  {@link #ACTION_CARRIER_SIGNAL_REDIRECTED} and
+     *  {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts.
+     *  Check {@link ApnSetting} TYPE_* for its values.
+     *  @hide
+     */
+    @SystemApi
+    @SuppressLint("ActionValue")
+    public static final String EXTRA_APN_TYPE_INT = "apnTypeInt";
+
+    /**
+     *  An string extra with the protocol of the apn connection (IP,IPV6, IPV4V6) upon
+     *  {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts.
+     *  @deprecated This is kept for backward compatibility reason.
+     *  Use {@link #EXTRA_APN_PROTOCOL_INT} instead.
+     *
+     *  @hide
+     */
+    @SystemApi
+    @Deprecated
+    @SuppressLint("ActionValue")
+    public static final String EXTRA_APN_PROTOCOL = "apnProto";
+
+    /**
+     *  An integer extra with the protocol of the apn connection (IP,IPV6, IPV4V6) upon
+     *  {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts.
+     *  Check {@link ApnSetting} PROTOCOL_* for its values.
+     *  @hide
+     */
+    @SystemApi
+    @SuppressLint("ActionValue")
+    public static final String EXTRA_APN_PROTOCOL_INT = "apnProtoInt";
+
+    /**
+     *  An integer extra indicating the pco id for the data upon
+     *  {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts.
+     *  @hide
+     */
+    @SystemApi
+    @SuppressLint("ActionValue")
+    public static final String EXTRA_PCO_ID = "pcoId";
+
+    /**
+     *  An extra of byte array of pco data read from modem upon
+     *  {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts.
+     *  @hide
+     */
+    @SystemApi
+    @SuppressLint("ActionValue")
+    public static final String EXTRA_PCO_VALUE = "pcoValue";
+
+    /**
+     *  An boolean extra indicating default network available upon
+     *  {@link #ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE} broadcasts.
+     *  @hide
+     */
+    @SystemApi
+    @SuppressLint("ActionValue")
+    public static final String EXTRA_DEFAULT_NETWORK_AVAILABLE = "defaultNetworkAvailable";
 
     /**
      * <p>Broadcast Action: The emergency call state is changed.
      * <ul>
-     *   <li><em>phoneInEmergencyCall</em> - A boolean value, true if phone in emergency call,
-     *   false otherwise</li>
+     *   <li><em>EXTRA_PHONE_IN_EMERGENCY_CALL</em> - A boolean value, true if phone in emergency
+     *   call, false otherwise</li>
      * </ul>
      * <p class="note">
      * You can <em>not</em> receive this through components declared
@@ -1523,12 +1774,25 @@
      *
      * <p class="note">This is a protected intent that can only be sent by the system.
      *
+     * @see #EXTRA_PHONE_IN_EMERGENCY_CALL
+     *
      * @hide
      */
     @SystemApi
     @SuppressLint("ActionValue")
-    public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED
-            = "android.intent.action.EMERGENCY_CALL_STATE_CHANGED";
+    public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED =
+            "android.intent.action.EMERGENCY_CALL_STATE_CHANGED";
+
+
+    /**
+     * Extra included in {@link #ACTION_EMERGENCY_CALL_STATE_CHANGED}.
+     * It indicates whether the phone is making an emergency call.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_PHONE_IN_EMERGENCY_CALL =
+            "android.telephony.extra.PHONE_IN_EMERGENCY_CALL";
 
     /**
      * <p>Broadcast Action: It indicates the Emergency callback mode blocks datacall/sms
@@ -1541,8 +1805,8 @@
      * @hide
      */
     @SystemApi
-    public static final String ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS
-            = "android.telephony.action.SHOW_NOTICE_ECM_BLOCK_OTHERS";
+    public static final String ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS =
+            "android.telephony.action.SHOW_NOTICE_ECM_BLOCK_OTHERS";
 
     /**
      * Broadcast Action: The default data subscription has changed in a multi-SIM device.
@@ -1555,8 +1819,8 @@
      */
     @SystemApi
     @SuppressLint("ActionValue")
-    public static final String ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED
-            = "android.intent.action.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED";
+    public static final String ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED =
+            "android.intent.action.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED";
 
     /**
      * Broadcast Action: The default voice subscription has changed in a mult-SIm device.
@@ -1569,8 +1833,8 @@
      */
     @SystemApi
     @SuppressLint("ActionValue")
-    public static final String ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED
-            = "android.intent.action.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED";
+    public static final String ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED =
+            "android.intent.action.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED";
 
     /**
      * Broadcast Action: This triggers a client initiated OMA-DM session to the OMA server.
@@ -1583,8 +1847,8 @@
      */
     @SystemApi
     @SuppressLint("ActionValue")
-    public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE
-            = "com.android.omadm.service.CONFIGURATION_UPDATE";
+    public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE =
+            "com.android.omadm.service.CONFIGURATION_UPDATE";
 
     //
     //
@@ -1593,6 +1857,24 @@
     //
 
     /**
+     * Returns the {@link PhoneCapability} for the device or null if it is not available.
+     * <p>
+     * Requires Permission: READ_PHONE_STATE or that the calling app has
+     * carrier privileges (see {@link #hasCarrierPrivileges}).
+     */
+    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+    @Nullable
+    public PhoneCapability getPhoneCapability() {
+        try {
+            ITelephony telephony = getITelephony();
+            return telephony == null ? null :
+                    telephony.getPhoneCapability(getSubId(), getOpPackageName(), getFeatureId());
+        } catch (RemoteException ex) {
+            return null;
+        }
+    }
+
+    /**
      * Returns the software version number for the device, for example,
      * the IMEI/SV for GSM phones. Return null if the software version is
      * not available.
@@ -2100,6 +2382,16 @@
     public static final int PHONE_TYPE_CDMA = PhoneConstants.PHONE_TYPE_CDMA;
     /** Phone is via SIP. */
     public static final int PHONE_TYPE_SIP = PhoneConstants.PHONE_TYPE_SIP;
+    /** Phone is via IMS. */
+    public static final int PHONE_TYPE_IMS = PhoneConstants.PHONE_TYPE_IMS;
+
+    /**
+     * Phone is via Third Party.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int PHONE_TYPE_THIRD_PARTY = PhoneConstants.PHONE_TYPE_THIRD_PARTY;
 
     /**
      * Returns the current phone type.
@@ -2489,7 +2781,7 @@
     @UnsupportedAppUsage
     public boolean isNetworkRoaming(int subId) {
         int phoneId = SubscriptionManager.getPhoneId(subId);
-        return getTelephonyProperty(subId, TelephonyProperties.operator_is_roaming(), false);
+        return getTelephonyProperty(phoneId, TelephonyProperties.operator_is_roaming(), false);
     }
 
     /**
@@ -3665,21 +3957,20 @@
     }
 
     /**
-     * Return if the current radio is LTE on CDMA. This is a tri-state return value as for a period
-     * of time the mode may be unknown.
+     * Return if the current radio has global mode enabled, meaning it supports
+     * both 3GPP and 3GPP2 radio technologies at the same time.
      *
      * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
-     * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}
+     * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}.
      *
-     * @return {@link PhoneConstants#LTE_ON_CDMA_UNKNOWN}, {@link PhoneConstants#LTE_ON_CDMA_FALSE}
-     * or {@link PhoneConstants#LTE_ON_CDMA_TRUE}
-     *
+     * @return {@code true} if global mode is enabled
+     *         {@code false} if global mode is not enabled or unknown
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
-    @UnsupportedAppUsage
-    public int getLteOnCdmaMode() {
-        return getLteOnCdmaMode(getSubId());
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public boolean isGlobalModeEnabled() {
+        return getLteOnCdmaMode(getSubId()) == PhoneConstants.LTE_ON_CDMA_TRUE;
     }
 
     /**
@@ -3692,7 +3983,7 @@
      * or {@link PhoneConstants#LTE_ON_CDMA_TRUE}
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     @UnsupportedAppUsage
     public int getLteOnCdmaMode(int subId) {
         try {
@@ -5086,7 +5377,9 @@
      *      not present or not loaded
      * @hide
      */
-    @UnsupportedAppUsage
+    @Nullable
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public String[] getIsimImpu() {
         try {
             IPhoneSubInfo info = getSubscriberInfo();
@@ -5108,7 +5401,11 @@
     @UnsupportedAppUsage
     private IPhoneSubInfo getSubscriberInfo() {
         // get it each time because that process crashes a lot
-        return IPhoneSubInfo.Stub.asInterface(ServiceManager.getService("iphonesubinfo"));
+        return IPhoneSubInfo.Stub.asInterface(
+                TelephonyFrameworkInitializer
+                        .getTelephonyServiceManager()
+                        .getPhoneSubServiceRegisterer()
+                        .get());
     }
 
     /**
@@ -5264,6 +5561,13 @@
     public static final int DATA_DISCONNECTING = 4;
 
     /**
+     * To check the SDK version for {@link TelephonyManager#getDataState}.
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+    private static final long GET_DATA_STATE_CODE_CHANGE = 147600208L;
+
+    /**
      * Returns a constant indicating the current data connection state
      * (cellular).
      *
@@ -5281,7 +5585,7 @@
             int state = telephony.getDataStateForSubId(
                     getSubId(SubscriptionManager.getActiveDataSubscriptionId()));
             if (state == TelephonyManager.DATA_DISCONNECTING
-                    && VMRuntime.getRuntime().getTargetSdkVersion() < Build.VERSION_CODES.R) {
+                    && !Compatibility.isChangeEnabled(GET_DATA_STATE_CODE_CHANGE)) {
                 return TelephonyManager.DATA_CONNECTED;
             }
 
@@ -5321,11 +5625,19 @@
     }
 
     private ITelephonyRegistry getTelephonyRegistry() {
-        return ITelephonyRegistry.Stub.asInterface(ServiceManager.getService("telephony.registry"));
+        return ITelephonyRegistry.Stub.asInterface(
+                TelephonyFrameworkInitializer
+                        .getTelephonyServiceManager()
+                        .getTelephonyRegistryServiceRegisterer()
+                        .get());
     }
 
     private IOns getIOns() {
-        return IOns.Stub.asInterface(ServiceManager.getService("ions"));
+        return IOns.Stub.asInterface(
+                TelephonyFrameworkInitializer
+                        .getTelephonyServiceManager()
+                        .getOpportunisticNetworkServiceRegisterer()
+                        .get());
     }
 
     //
@@ -5335,6 +5647,13 @@
     //
 
     /**
+     * To check the SDK version for {@link TelephonyManager#listen}.
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.P)
+    private static final long LISTEN_CODE_CHANGE = 147600208L;
+
+    /**
      * Registers a listener object to receive notification of changes
      * in specified telephony states.
      * <p>
@@ -5373,7 +5692,7 @@
                 // subId from PhoneStateListener is deprecated Q on forward, use the subId from
                 // TelephonyManager instance. keep using subId from PhoneStateListener for pre-Q.
                 int subId = mSubId;
-                if (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.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.
                     //  TODO: remove this once we completely get rid of mSubId in PhoneStateListener
@@ -5393,19 +5712,25 @@
     }
 
     /**
-     * Returns the CDMA ERI icon index to display
+     * Get the CDMA ERI (Enhanced Roaming Indicator) information
+     *
+     * Returns {@link android.telephony#CdmaEriInformation}
+     *
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
-    public int getCdmaEriIconIndex() {
-        return getCdmaEriIconIndex(getSubId());
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @SystemApi
+    @NonNull
+    public CdmaEriInformation getCdmaEriInformation() {
+        return new CdmaEriInformation(
+               getCdmaEriIconMode(getSubId()), getCdmaEriIconIndex(getSubId()));
     }
 
     /**
      * Returns the CDMA ERI icon index to display for a subscription
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     @UnsupportedAppUsage
     public int getCdmaEriIconIndex(int subId) {
         try {
@@ -5423,25 +5748,13 @@
     }
 
     /**
-     * Returns the CDMA ERI icon mode,
-     * 0 - ON
-     * 1 - FLASHING
-     *
-     * @hide
-     */
-    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
-    public int getCdmaEriIconMode() {
-        return getCdmaEriIconMode(getSubId());
-    }
-
-    /**
      * Returns the CDMA ERI icon mode for a subscription.
      * 0 - ON
      * 1 - FLASHING
      *
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     @UnsupportedAppUsage
     public int getCdmaEriIconMode(int subId) {
         try {
@@ -5881,7 +6194,10 @@
      * @param AID Application id. See ETSI 102.221 and 101.220.
      * @param p2 P2 parameter (described in ISO 7816-4).
      * @return an IccOpenLogicalChannelResponse object.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public IccOpenLogicalChannelResponse iccOpenLogicalChannel(String AID, int p2) {
         return iccOpenLogicalChannel(getSubId(), AID, p2);
     }
@@ -5912,7 +6228,10 @@
      * @param p2 P2 parameter (described in ISO 7816-4).
      * @return an IccOpenLogicalChannelResponse object.
      * @hide
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public IccOpenLogicalChannelResponse iccOpenLogicalChannel(int subId, String AID, int p2) {
         try {
             ITelephony telephony = getITelephony();
@@ -5940,7 +6259,10 @@
      *            iccOpenLogicalChannel.
      * @return true if the channel was closed successfully.
      * @hide
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     @SystemApi
     public boolean iccCloseLogicalChannelBySlot(int slotIndex, int channel) {
@@ -5967,7 +6289,10 @@
      * @param channel is the channel id to be closed as returned by a successful
      *            iccOpenLogicalChannel.
      * @return true if the channel was closed successfully.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public boolean iccCloseLogicalChannel(int channel) {
         return iccCloseLogicalChannel(getSubId(), channel);
     }
@@ -5986,7 +6311,10 @@
      *            iccOpenLogicalChannel.
      * @return true if the channel was closed successfully.
      * @hide
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public boolean iccCloseLogicalChannel(int subId, int channel) {
         try {
             ITelephony telephony = getITelephony();
@@ -6022,7 +6350,10 @@
      * @return The APDU response from the ICC card with the status appended at the end, or null if
      * there is an issue connecting to the Telephony service.
      * @hide
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     @SystemApi
     @Nullable
@@ -6060,7 +6391,10 @@
      * @param data Data to be sent with the APDU.
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public String iccTransmitApduLogicalChannel(int channel, int cla,
             int instruction, int p1, int p2, int p3, String data) {
         return iccTransmitApduLogicalChannel(getSubId(), channel, cla,
@@ -6089,7 +6423,10 @@
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
      * @hide
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public String iccTransmitApduLogicalChannel(int subId, int channel, int cla,
             int instruction, int p1, int p2, int p3, String data) {
         try {
@@ -6125,7 +6462,10 @@
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
      * @hide
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     @SystemApi
     @NonNull
@@ -6161,7 +6501,10 @@
      * @param data Data to be sent with the APDU.
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public String iccTransmitApduBasicChannel(int cla,
             int instruction, int p1, int p2, int p3, String data) {
         return iccTransmitApduBasicChannel(getSubId(), cla,
@@ -6188,7 +6531,10 @@
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
      * @hide
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public String iccTransmitApduBasicChannel(int subId, int cla,
             int instruction, int p1, int p2, int p3, String data) {
         try {
@@ -6216,7 +6562,10 @@
      * @param p3 P3 value of the APDU command.
      * @param filePath
      * @return The APDU response.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public byte[] iccExchangeSimIO(int fileID, int command, int p1, int p2, int p3,
             String filePath) {
         return iccExchangeSimIO(getSubId(), fileID, command, p1, p2, p3, filePath);
@@ -6238,7 +6587,10 @@
      * @param filePath
      * @return The APDU response.
      * @hide
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public byte[] iccExchangeSimIO(int subId, int fileID, int command, int p1, int p2,
             int p3, String filePath) {
         try {
@@ -6264,7 +6616,10 @@
      * @return The APDU response from the ICC card in hexadecimal format
      *         with the last 4 bytes being the status word. If the command fails,
      *         returns an empty string.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public String sendEnvelopeWithStatus(String content) {
         return sendEnvelopeWithStatus(getSubId(), content);
     }
@@ -6284,7 +6639,10 @@
      *         with the last 4 bytes being the status word. If the command fails,
      *         returns an empty string.
      * @hide
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public String sendEnvelopeWithStatus(int subId, String content) {
         try {
             ITelephony telephony = getITelephony();
@@ -7400,6 +7758,18 @@
             RILConstants.NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA;
 
     /**
+     * The default preferred network mode constant.
+     *
+     * <p> This constant is used in case of nothing is set in
+     * TelephonyProperties#default_network().
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int DEFAULT_PREFERRED_NETWORK_MODE =
+            RILConstants.DEFAULT_PREFERRED_NETWORK_MODE;
+
+    /**
      * Get the preferred network type.
      * Used for device configuration by some CDMA operators.
      *
@@ -7409,7 +7779,9 @@
      *
      * @return the preferred network type.
      * @hide
+     * @deprecated Use {@link #getPreferredNetworkTypeBitmask} instead.
      */
+    @Deprecated
     @RequiresPermission((android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE))
     @UnsupportedAppUsage
     public @PrefNetworkMode int getPreferredNetworkType(int subId) {
@@ -7454,6 +7826,30 @@
     }
 
     /**
+     * Get the allowed network types.
+     *
+     * <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 the allowed network type bitmask
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @SystemApi
+    public @NetworkTypeBitMask long getAllowedNetworkTypes() {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.getAllowedNetworkTypes(getSubId());
+            }
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "getAllowedNetworkTypes RemoteException", ex);
+        }
+        return -1;
+    }
+
+    /**
      * Sets the network selection mode to automatic.
      *
      * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
@@ -7616,6 +8012,36 @@
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
      * app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *
+     * @param operatorNumeric the PLMN ID of the network to select.
+     * @param ran the initial suggested radio access network type.
+     *         If registration fails, the RAN is not available after, the RAN is not within the
+     *         network types specified by {@link #setPreferredNetworkTypeBitmask}, or the value is
+     *         {@link AccessNetworkConstants.AccessNetworkType#UNKNOWN}, modem will select
+     *         the next best RAN for network registration.
+     * @param persistSelection whether the selection will persist until reboot.
+     *         If true, only allows attaching to the selected PLMN until reboot; otherwise,
+     *         attach to the chosen PLMN and resume normal network selection next time.
+     * @return {@code true} on success; {@code false} on any failure.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @SystemApi
+    public boolean setNetworkSelectionModeManual(@NonNull String operatorNumeric,
+            @AccessNetworkConstants.RadioAccessNetworkType int ran, boolean persistSelection) {
+        return setNetworkSelectionModeManual(new OperatorInfo("" /* operatorAlphaLong */,
+                "" /* operatorAlphaShort */, operatorNumeric, ran), persistSelection);
+    }
+
+    /**
+     * Ask the radio to connect to the input network and change selection mode to manual.
+     *
+     * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
+     * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
+     * app has carrier privileges (see {@link #hasCarrierPrivileges}).
+     *
      * @param operatorInfo included the PLMN id, long name, short name of the operator to attach to.
      * @param persistSelection whether the selection will persist until reboot. If true, only allows
      * attaching to the selected PLMN until reboot; otherwise, attach to the chosen PLMN and resume
@@ -7644,14 +8070,18 @@
      *
      * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
      * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}
-
-     * @return the network selection mode.
+     *  <p>Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE
+     * READ_PRECISE_PHONE_STATE}
+     * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *
-     * @hide
+     * @return the network selection mode.
      */
-    @NetworkSelectionMode
-    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
-    public int getNetworkSelectionMode() {
+    @SuppressAutoDoc // No support for carrier privileges (b/72967236).
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+            android.Manifest.permission.READ_PRECISE_PHONE_STATE
+    })
+    public @NetworkSelectionMode int getNetworkSelectionMode() {
         int mode = NETWORK_SELECTION_MODE_UNKNOWN;
         try {
             ITelephony telephony = getITelephony();
@@ -7665,6 +8095,30 @@
     }
 
     /**
+     * Get the PLMN chosen for Manual Network Selection if active.
+     * Return empty string if in automatic selection.
+     *
+     * <p>Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE
+     * READ_PRECISE_PHONE_STATE} or that the calling app has carrier privileges
+     * (see {@link #hasCarrierPrivileges})
+     *
+     * @return manually selected network info on success or empty string on failure
+     */
+    @SuppressAutoDoc // No support carrier privileges (b/72967236).
+    @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
+    public @NonNull String getManualNetworkSelectionPlmn() {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null && isManualNetworkSelectionAllowed()) {
+                return telephony.getManualNetworkSelectionPlmn(getSubId());
+            }
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "getManualNetworkSelectionPlmn RemoteException", ex);
+        }
+        return "";
+    }
+
+    /**
      * Query Telephony to see if there has recently been an emergency SMS sent to the network by the
      * user and we are still within the time interval after the emergency SMS was sent that we are
      * considered in Emergency SMS mode.
@@ -7704,7 +8158,9 @@
      * @param networkType the preferred network type
      * @return true on success; false on any failure.
      * @hide
+     * @deprecated Use {@link #setPreferredNetworkTypeBitmask} instead.
      */
+    @Deprecated
     @UnsupportedAppUsage
     public boolean setPreferredNetworkType(int subId, @PrefNetworkMode int networkType) {
         try {
@@ -7719,7 +8175,8 @@
     }
 
     /**
-     * Set the preferred network type bitmask.
+     * Set the preferred network type bitmask but if {@link #setAllowedNetworkTypes} has been set,
+     * only the allowed network type will set to the modem.
      *
      * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
      * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}
@@ -7749,6 +8206,29 @@
     }
 
     /**
+     * Set the allowed network types of the device. This is for carrier or privileged apps to
+     * enable/disable certain network types on the device. The user preferred network types should
+     * be set through {@link #setPreferredNetworkTypeBitmask}.
+     *
+     * @param allowedNetworkTypes The bitmask of allowed network types.
+     * @return true on success; false on any failure.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @SystemApi
+    public boolean setAllowedNetworkTypes(@NetworkTypeBitMask long allowedNetworkTypes) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.setAllowedNetworkTypes(getSubId(), allowedNetworkTypes);
+            }
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "setAllowedNetworkTypes RemoteException", ex);
+        }
+        return false;
+    }
+
+    /**
      * Set the preferred network type to global mode which includes LTE, CDMA, EvDo and GSM/WCDMA.
      *
      * <p>Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
@@ -8269,9 +8749,13 @@
         return false;
     }
 
-    /** @hide */
+    /**
+     * @deprecated use {@link #supplyPinReportPinResult(String pin)} instead.
+     *
+     * @hide */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @Deprecated
     public int[] supplyPinReportResult(String pin) {
         try {
             ITelephony telephony = getITelephony();
@@ -8283,9 +8767,13 @@
         return new int[0];
     }
 
-    /** @hide */
+    /**
+     * @deprecated use {@link #supplyPukReportPinResult(String puk, String pin)} instead.
+     *
+     * @hide */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @Deprecated
     public int[] supplyPukReportResult(String puk, String pin) {
         try {
             ITelephony telephony = getITelephony();
@@ -8298,6 +8786,55 @@
     }
 
     /**
+     * Used when the user attempts to enter their pin.
+     *
+     * @param pin The user entered pin.
+     * @return The result of the pin.
+     *
+     * @hide
+     */
+    @SystemApi
+    @Nullable
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public PinResult supplyPinReportPinResult(@NonNull String pin) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                int[] result = telephony.supplyPinReportResultForSubscriber(getSubId(), pin);
+                return new PinResult(result[0], result[1]);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#supplyPinReportResultForSubscriber", e);
+        }
+        return null;
+    }
+
+    /**
+     * Used when the user attempts to enter the puk or their pin.
+     *
+     * @param puk The product unblocking key.
+     * @param pin The user entered pin.
+     * @return The result of the pin.
+     *
+     * @hide
+     */
+    @SystemApi
+    @Nullable
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public PinResult supplyPukReportPinResult(@NonNull String puk, @NonNull String pin) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                int[] result = telephony.supplyPukReportResultForSubscriber(getSubId(), puk, pin);
+                return new PinResult(result[0], result[1]);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#]", e);
+        }
+        return null;
+    }
+
+    /**
      * Used to notify callers of
      * {@link TelephonyManager#sendUssdRequest(String, UssdResponseCallback, Handler)} when the
      * network either successfully executes a USSD request, or if there was a failure while
@@ -8735,8 +9272,9 @@
      *
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
-    public int getCdmaRoamingMode() {
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public @CdmaRoamingMode int getCdmaRoamingMode() {
         int mode = CDMA_ROAMING_MODE_RADIO_DEFAULT;
         try {
             ITelephony telephony = getITelephony();
@@ -8763,8 +9301,9 @@
      *
      * @hide
      */
+    @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-    public boolean setCdmaRoamingMode(int mode) {
+    public boolean setCdmaRoamingMode(@CdmaRoamingMode int mode) {
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
@@ -8776,6 +9315,36 @@
         return false;
     }
 
+    /** @hide */
+    @IntDef(flag = true, prefix = { "CDMA_SUBSCRIPTION_" }, value = {
+            CDMA_SUBSCRIPTION_UNKNOWN,
+            CDMA_SUBSCRIPTION_RUIM_SIM,
+            CDMA_SUBSCRIPTION_NV
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CdmaSubscription{}
+
+    /** Used for CDMA subscription mode, it'll be UNKNOWN if there is no Subscription source.
+     * @hide
+     */
+    @SystemApi
+    public static final int CDMA_SUBSCRIPTION_UNKNOWN  = -1;
+
+    /** Used for CDMA subscription mode: RUIM/SIM (default)
+     * @hide
+     */
+    @SystemApi
+    public static final int CDMA_SUBSCRIPTION_RUIM_SIM = 0;
+
+    /** Used for CDMA subscription mode: NV -> non-volatile memory
+     * @hide
+     */
+    @SystemApi
+    public static final int CDMA_SUBSCRIPTION_NV       = 1;
+
+    /** @hide */
+    public static final int PREFERRED_CDMA_SUBSCRIPTION = CDMA_SUBSCRIPTION_RUIM_SIM;
+
     /**
      * Sets the subscription mode for CDMA phone to the given mode {@code mode}.
      *
@@ -8783,14 +9352,15 @@
      *
      * @return {@code true} if successed.
      *
-     * @see Phone#CDMA_SUBSCRIPTION_UNKNOWN
-     * @see Phone#CDMA_SUBSCRIPTION_RUIM_SIM
-     * @see Phone#CDMA_SUBSCRIPTION_NV
+     * @see #CDMA_SUBSCRIPTION_UNKNOWN
+     * @see #CDMA_SUBSCRIPTION_RUIM_SIM
+     * @see #CDMA_SUBSCRIPTION_NV
      *
      * @hide
      */
+    @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-    public boolean setCdmaSubscriptionMode(int mode) {
+    public boolean setCdmaSubscriptionMode(@CdmaSubscription int mode) {
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
@@ -9223,15 +9793,20 @@
     }
 
     /**
-     * Requested state of SIM
-     *
-     * CARD_POWER_DOWN
      * Powers down the SIM. SIM must be up prior.
-     *
-     * CARD_POWER_UP
+     * @hide
+     */
+    @SystemApi
+    public static final int CARD_POWER_DOWN = 0;
+
+    /**
      * Powers up the SIM normally. SIM must be down prior.
-     *
-     * CARD_POWER_UP_PASS_THROUGH
+     * @hide
+     */
+    @SystemApi
+    public static final int CARD_POWER_UP = 1;
+
+    /**
      * Powers up the SIM in PASS_THROUGH mode. SIM must be down prior.
      * When SIM is powered up in PASS_THOUGH mode, the modem does not send
      * any command to it (for example SELECT of MF, or TERMINAL CAPABILITY),
@@ -9244,12 +9819,9 @@
      * is activated, and normal behavior occurs at the next SIM initialization,
      * unless PASS_THROUGH mode is requested again. Hence, the last power-up mode
      * is NOT persistent across boots. On reboot, SIM will power up normally.
+     * @hide
      */
-    /** @hide */
-    public static final int CARD_POWER_DOWN = 0;
-    /** @hide */
-    public static final int CARD_POWER_UP = 1;
-    /** @hide */
+    @SystemApi
     public static final int CARD_POWER_UP_PASS_THROUGH = 2;
 
     /**
@@ -9761,7 +10333,8 @@
      * Requests the modem activity info. The recipient will place the result
      * in `result`.
      * @param result The object on which the recipient will send the resulting
-     * {@link android.telephony.ModemActivityInfo} object.
+     * {@link android.telephony.ModemActivityInfo} object with key of
+     * {@link #MODEM_ACTIVITY_RESULT_KEY}.
      * @hide
      */
     @SystemApi
@@ -10403,6 +10976,7 @@
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
      *
      * @param enabled control enable or disable carrier data.
+     * @see #resetAllCarrierActions()
      * @hide
      */
     @SystemApi
@@ -10429,6 +11003,7 @@
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
      *
      * @param enabled control enable or disable radio.
+     * @see #resetAllCarrierActions()
      * @hide
      */
     @SystemApi
@@ -10455,6 +11030,7 @@
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
      *
      * @param report control start/stop reporting network status.
+     * @see #resetAllCarrierActions()
      * @hide
      */
     @SystemApi
@@ -10595,15 +11171,18 @@
     /**
      * Checks if manual network selection is allowed.
      *
+     * <p>Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE
+     * READ_PRECISE_PHONE_STATE} or that the calling app has carrier privileges
+     * (see {@link #hasCarrierPrivileges})
+     *
      * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
      * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}.
      *
      * @return {@code true} if manual network selection is allowed, otherwise return {@code false}.
-     *
-     * @hide
      */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @SuppressAutoDoc // No support carrier privileges (b/72967236).
+    @RequiresPermission(anyOf = {android.Manifest.permission.READ_PRECISE_PHONE_STATE,
+            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE})
     public boolean isManualNetworkSelectionAllowed() {
         try {
             ITelephony telephony = getITelephony();
@@ -10617,6 +11196,27 @@
     }
 
     /**
+     * Enable or disable signal strength changes from radio will always be reported in any
+     * condition (e.g. screen is off). This is only allowed for System caller.
+     *
+     * @param isEnabled {@code true} for enabling; {@code false} for disabling.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public void setAlwaysReportSignalStrength(boolean isEnabled) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                telephony.setAlwaysReportSignalStrength(getSubId(), isEnabled);
+            }
+        } catch (RemoteException ex) {
+            Log.e(TAG, "setAlwaysReportSignalStrength RemoteException", ex);
+            ex.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
      * Get the most recently available signal strength information.
      *
      * Get the most recent SignalStrength information reported by the modem. Due
@@ -10637,12 +11237,21 @@
     }
 
     /**
+     * Checks whether cellular data connection is enabled in the device.
+     *
+     * Whether cellular data connection is enabled, meaning upon request whether will try to setup
+     * metered data connection considering all factors below:
+     * 1) User turned on data setting {@link #isDataEnabled}.
+     * 2) Carrier allows data to be on.
+     * 3) Network policy.
+     * And possibly others.
+     *
+     * @return {@code true} if the overall data connection is capable; {@code false} if not.
      * @hide
-     * It's similar to isDataEnabled, but unlike isDataEnabled, this API also evaluates
-     * carrierDataEnabled, policyDataEnabled etc to give a final decision of whether mobile data is
-     * capable of using.
      */
-    public boolean isDataCapable() {
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public boolean isDataConnectionEnabled() {
         boolean retVal = false;
         try {
             int subId = getSubId(SubscriptionManager.getDefaultDataSubscriptionId());
@@ -10650,13 +11259,30 @@
             if (telephony != null)
                 retVal = telephony.isDataEnabled(subId);
         } catch (RemoteException e) {
-            Log.e(TAG, "Error calling ITelephony#isDataEnabled", e);
+            Log.e(TAG, "Error isDataConnectionEnabled", e);
         } catch (NullPointerException e) {
         }
         return retVal;
     }
 
     /**
+     * @return true if the current device is "data capable" over a radio on the device.
+     * <p>
+     * "Data capable" means that this device supports packet-switched
+     * data connections over the telephony network.
+     * <p>
+     * Note: the meaning of this flag is subtly different from the
+     * PackageManager.FEATURE_TELEPHONY system feature, which is available
+     * on any device with a telephony radio, even if the device is
+     * voice-only.
+     */
+    public boolean isDataCapable() {
+        if (mContext == null) return true;
+        return mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_mobile_data_capable);
+    }
+
+    /**
      * In this mode, modem will not send specified indications when screen is off.
      * @hide
      */
@@ -12050,6 +12676,51 @@
     }
 
     /**
+     * Specify which bands modem's background scan must act on.
+     * If {@code specifiers} is non-empty, the scan will be restricted to the bands specified.
+     * Otherwise, it scans all bands.
+     *
+     * For example, CBRS is only on LTE band 48. By specifying this band,
+     * modem saves more power.
+     *
+     * @param specifiers which bands to scan.
+     * @param executor The executor to execute the callback on
+     * @param callback The callback that gets invoked when the radio responds to the request. Called
+     *                 with {@code true} if the request succeeded, {@code false} otherwise.
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public void setSystemSelectionChannels(@NonNull List<RadioAccessSpecifier> specifiers,
+            @Nullable @CallbackExecutor Executor executor,
+            @Nullable Consumer<Boolean> callback) {
+        Objects.requireNonNull(specifiers, "Specifiers must not be null.");
+        if (callback != null) {
+            Objects.requireNonNull(executor, "Executor must not be null when"
+                    + " the callback is nonnull");
+        }
+
+        IBooleanConsumer aidlConsumer = callback == null ? null : new IBooleanConsumer.Stub() {
+            @Override
+            public void accept(boolean result) {
+                executor.execute(() -> callback.accept(result));
+            }
+        };
+
+        try {
+            ITelephony service = getITelephony();
+            if (service != null) {
+                service.setSystemSelectionChannels(specifiers, getSubId(), aidlConsumer);
+            }
+        } catch (RemoteException ex) {
+            if (!isSystemProcess()) {
+                ex.rethrowAsRuntimeException();
+            }
+        }
+    }
+
+    /**
      * Verifies whether the input MCC/MNC and MVNO correspond to the current carrier.
      *
      * @param mccmnc the carrier's mccmnc that you want to match
@@ -12081,15 +12752,21 @@
     }
 
     /**
-     * Set allowing mobile data during voice call.
+     * Set allowing mobile data during voice call. This is used for allowing data on the non-default
+     * data SIM. When a voice call is placed on the non-default data SIM on DSDS devices, users will
+     * not be able to use mobile data. By calling this API, data will be temporarily enabled on the
+     * non-default data SIM during the life cycle of the voice call.
      *
      * @param allow {@code true} if allowing using data during voice call, {@code false} if
-     * disallowed
+     * disallowed.
      *
-     * @return {@code false} if the setting is changed.
+     * @return {@code true} if operation is successful. otherwise {@code false}.
+     *
+     * @throws SecurityException if the caller doesn't have the permission.
      *
      * @hide
      */
+    @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public boolean setDataAllowedDuringVoiceCall(boolean allow) {
         try {
@@ -12098,6 +12775,7 @@
                 return service.setDataAllowedDuringVoiceCall(getSubId(), allow);
             }
         } catch (RemoteException ex) {
+            // This could happen if binder process crashes.
             if (!isSystemProcess()) {
                 ex.rethrowAsRuntimeException();
             }
@@ -12106,13 +12784,18 @@
     }
 
     /**
-     * Check whether data is allowed during voice call. Note this is for dual sim device that
-     * data might be disabled on non-default data subscription but explicitly turned on by settings.
+     * Check whether data is allowed during voice call. This is used for allowing data on the
+     * non-default data SIM. When a voice call is placed on the non-default data SIM on DSDS
+     * devices, users will not be able to use mobile data. By calling this API, data will be
+     * temporarily enabled on the non-default data SIM during the life cycle of the voice call.
      *
      * @return {@code true} if data is allowed during voice call.
      *
+     * @throws SecurityException if the caller doesn't have the permission.
+     *
      * @hide
      */
+    @SystemApi
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public boolean isDataAllowedInVoiceCall() {
         try {
@@ -12121,6 +12804,7 @@
                 return service.isDataAllowedInVoiceCall(getSubId());
             }
         } catch (RemoteException ex) {
+            // This could happen if binder process crashes.
             if (!isSystemProcess()) {
                 ex.rethrowAsRuntimeException();
             }
@@ -12153,4 +12837,126 @@
         }
         return false;
     }
+
+    /**
+     * The IccLock state or password was changed successfully.
+     * @hide
+     */
+    @SystemApi
+    public static final int CHANGE_ICC_LOCK_SUCCESS = Integer.MAX_VALUE;
+
+    /**
+     * Check whether ICC pin lock is enabled.
+     * This is a sync call which returns the cached pin enabled state.
+     *
+     * @return {@code true} if ICC lock enabled, {@code false} if ICC lock disabled.
+     *
+     * @throws SecurityException if the caller doesn't have the permission.
+     *
+     * @hide
+     */
+    @SystemApi
+    @WorkerThread
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public boolean isIccLockEnabled() {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.isIccLockEnabled(getSubId());
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "isIccLockEnabled RemoteException", e);
+        }
+        return false;
+    }
+
+    /**
+     * Set the ICC pin lock enabled or disabled.
+     *
+     * If enable/disable ICC pin lock successfully, a value of {@link Integer#MAX_VALUE} is
+     * returned.
+     * If an incorrect old password is specified, the return value will indicate how many more
+     * attempts the user can make to change the password before the SIM is locked.
+     * Using PUK code to unlock SIM if enter the incorrect old password 3 times.
+     *
+     * @param enabled    "true" for locked, "false" for unlocked.
+     * @param password   needed to change the ICC pin state, aka. Pin1
+     * @return an integer representing the status of IccLock enabled or disabled in the following
+     * three cases:
+     *   - {@link TelephonyManager#CHANGE_ICC_LOCK_SUCCESS} if enabled or disabled IccLock
+     *   successfully.
+     *   - Positive number and zero for remaining password attempts.
+     *   - Negative number for other failure cases (such like enabling/disabling PIN failed).
+     *
+     * @throws SecurityException if the caller doesn't have the permission.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public int setIccLockEnabled(boolean enabled, @NonNull String password) {
+        checkNotNull(password, "setIccLockEnabled password can't be null.");
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.setIccLockEnabled(getSubId(), enabled, password);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "setIccLockEnabled RemoteException", e);
+        }
+        return 0;
+    }
+
+    /**
+     * Change the ICC password used in ICC pin lock.
+     *
+     * If the password was changed successfully, a value of {@link Integer#MAX_VALUE} is returned.
+     * If an incorrect old password is specified, the return value will indicate how many more
+     * attempts the user can make to change the password before the SIM is locked.
+     * Using PUK code to unlock SIM if enter the incorrect old password 3 times.
+     *
+     * @param oldPassword is the old password
+     * @param newPassword is the new password
+     * @return an integer representing the status of IccLock changed in the following three cases:
+     *   - {@link TelephonyManager#CHANGE_ICC_LOCK_SUCCESS} if changed IccLock successfully.
+     *   - Positive number and zero for remaining password attempts.
+     *   - Negative number for other failure cases (such like enabling/disabling PIN failed).
+     *
+     * @throws SecurityException if the caller doesn't have the permission.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public int changeIccLockPassword(@NonNull String oldPassword, @NonNull String newPassword) {
+        checkNotNull(oldPassword, "changeIccLockPassword oldPassword can't be null.");
+        checkNotNull(newPassword, "changeIccLockPassword newPassword can't be null.");
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.changeIccLockPassword(getSubId(), oldPassword, newPassword);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "changeIccLockPassword RemoteException", e);
+        }
+        return 0;
+    }
+
+    /**
+     * Called when userActivity is signalled in the power manager.
+     * This should only be called from system Uid.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public void notifyUserActivity() {
+        try {
+            ITelephony service = getITelephony();
+            if (service != null) {
+                service.userActivity();
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/telephony/java/android/telephony/TelephonyScanManager.java b/telephony/java/android/telephony/TelephonyScanManager.java
index 96b6db7..a1d40e8 100644
--- a/telephony/java/android/telephony/TelephonyScanManager.java
+++ b/telephony/java/android/telephony/TelephonyScanManager.java
@@ -16,10 +16,11 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import static com.android.internal.util.Preconditions.checkNotNull;
 
 import android.annotation.Nullable;
-import android.content.Context;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -29,7 +30,6 @@
 import android.os.Messenger;
 import android.os.Parcelable;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.util.SparseArray;
 
 import com.android.internal.telephony.ITelephony;
@@ -234,6 +234,9 @@
 
     private ITelephony getITelephony() {
         return ITelephony.Stub.asInterface(
-            ServiceManager.getService(Context.TELEPHONY_SERVICE));
+            TelephonyFrameworkInitializer
+                    .getTelephonyServiceManager()
+                    .getTelephonyServiceRegisterer()
+                    .get());
     }
 }
diff --git a/telephony/java/android/telephony/UiccAccessRule.java b/telephony/java/android/telephony/UiccAccessRule.java
index 93ccba1..81a09c6 100644
--- a/telephony/java/android/telephony/UiccAccessRule.java
+++ b/telephony/java/android/telephony/UiccAccessRule.java
@@ -15,6 +15,8 @@
  */
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
diff --git a/telephony/java/android/telephony/VoLteServiceState.java b/telephony/java/android/telephony/VoLteServiceState.java
index 414b999..d4a27d9 100644
--- a/telephony/java/android/telephony/VoLteServiceState.java
+++ b/telephony/java/android/telephony/VoLteServiceState.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Bundle;
@@ -51,8 +53,7 @@
     /**
      * Create a new VoLteServiceState from a intent notifier Bundle
      *
-     * This method is used by PhoneStateIntentReceiver and maybe by
-     * external applications.
+     * This method is maybe used by external applications.
      *
      * @param m Bundle from intent notifier
      * @return newly created VoLteServiceState
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index dbfb6a2..7896320 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -18,6 +18,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.hardware.radio.V1_5.ApnTypes;
@@ -28,13 +29,14 @@
 import android.provider.Telephony.Carriers;
 import android.telephony.Annotation.ApnType;
 import android.telephony.Annotation.NetworkType;
-import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
 
+import com.android.telephony.Rlog;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.net.InetAddress;
@@ -123,6 +125,122 @@
     /** Authentication type for PAP or CHAP. */
     public static final int AUTH_TYPE_PAP_OR_CHAP = 3;
 
+    /**
+     * APN types for data connections.  These are usage categories for an APN
+     * entry.  One APN entry may support multiple APN types, eg, a single APN
+     * may service regular internet traffic ("default") as well as MMS-specific
+     * connections.<br/>
+     * APN_TYPE_ALL is a special type to indicate that this APN entry can
+     * service all data connections.
+     * <p>
+     * Note: The goal is to deprecate this.  Due to the Carrier Table being used
+     * directly, this isn't feasible right now.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String TYPE_ALL_STRING = "*";
+
+    /**
+     * APN type for default data traffic
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String TYPE_DEFAULT_STRING = "default";
+
+
+    /**
+     * APN type for MMS traffic
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String TYPE_MMS_STRING = "mms";
+
+
+    /**
+     * APN type for SUPL assisted GPS
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String TYPE_SUPL_STRING = "supl";
+
+    /**
+     * APN type for DUN traffic
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String TYPE_DUN_STRING = "dun";
+
+    /**
+     * APN type for HiPri traffic
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String TYPE_HIPRI_STRING = "hipri";
+
+    /**
+     * APN type for FOTA
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String TYPE_FOTA_STRING = "fota";
+
+    /**
+     * APN type for IMS
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String TYPE_IMS_STRING = "ims";
+
+    /**
+     * APN type for CBS
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String TYPE_CBS_STRING = "cbs";
+
+    /**
+     * APN type for IA Initial Attach APN
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String TYPE_IA_STRING = "ia";
+
+    /**
+     * APN type for Emergency PDN. This is not an IA apn, but is used
+     * for access to carrier services in an emergency call situation.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String TYPE_EMERGENCY_STRING = "emergency";
+
+    /**
+     * APN type for Mission Critical Services
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String TYPE_MCX_STRING = "mcx";
+
+    /**
+     * APN type for XCAP
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String TYPE_XCAP_STRING = "xcap";
+
+
     /** @hide */
     @IntDef(prefix = { "AUTH_TYPE_" }, value = {
         AUTH_TYPE_NONE,
@@ -187,42 +305,44 @@
     private static final Map<Integer, String> MVNO_TYPE_INT_MAP;
 
     static {
-        APN_TYPE_STRING_MAP = new ArrayMap<String, Integer>();
-        APN_TYPE_STRING_MAP.put("*", TYPE_ALL);
-        APN_TYPE_STRING_MAP.put("default", TYPE_DEFAULT);
-        APN_TYPE_STRING_MAP.put("mms", TYPE_MMS);
-        APN_TYPE_STRING_MAP.put("supl", TYPE_SUPL);
-        APN_TYPE_STRING_MAP.put("dun", TYPE_DUN);
-        APN_TYPE_STRING_MAP.put("hipri", TYPE_HIPRI);
-        APN_TYPE_STRING_MAP.put("fota", TYPE_FOTA);
-        APN_TYPE_STRING_MAP.put("ims", TYPE_IMS);
-        APN_TYPE_STRING_MAP.put("cbs", TYPE_CBS);
-        APN_TYPE_STRING_MAP.put("ia", TYPE_IA);
-        APN_TYPE_STRING_MAP.put("emergency", TYPE_EMERGENCY);
-        APN_TYPE_STRING_MAP.put("mcx", TYPE_MCX);
-        APN_TYPE_STRING_MAP.put("xcap", TYPE_XCAP);
-        APN_TYPE_INT_MAP = new ArrayMap<Integer, String>();
-        APN_TYPE_INT_MAP.put(TYPE_DEFAULT, "default");
-        APN_TYPE_INT_MAP.put(TYPE_MMS, "mms");
-        APN_TYPE_INT_MAP.put(TYPE_SUPL, "supl");
-        APN_TYPE_INT_MAP.put(TYPE_DUN, "dun");
-        APN_TYPE_INT_MAP.put(TYPE_HIPRI, "hipri");
-        APN_TYPE_INT_MAP.put(TYPE_FOTA, "fota");
-        APN_TYPE_INT_MAP.put(TYPE_IMS, "ims");
-        APN_TYPE_INT_MAP.put(TYPE_CBS, "cbs");
-        APN_TYPE_INT_MAP.put(TYPE_IA, "ia");
-        APN_TYPE_INT_MAP.put(TYPE_EMERGENCY, "emergency");
-        APN_TYPE_INT_MAP.put(TYPE_MCX, "mcx");
-        APN_TYPE_INT_MAP.put(TYPE_XCAP, "xcap");
+        APN_TYPE_STRING_MAP = new ArrayMap<>();
+        APN_TYPE_STRING_MAP.put(TYPE_ALL_STRING, TYPE_ALL);
+        APN_TYPE_STRING_MAP.put(TYPE_DEFAULT_STRING, TYPE_DEFAULT);
+        APN_TYPE_STRING_MAP.put(TYPE_MMS_STRING, TYPE_MMS);
+        APN_TYPE_STRING_MAP.put(TYPE_SUPL_STRING, TYPE_SUPL);
+        APN_TYPE_STRING_MAP.put(TYPE_DUN_STRING, TYPE_DUN);
+        APN_TYPE_STRING_MAP.put(TYPE_HIPRI_STRING, TYPE_HIPRI);
+        APN_TYPE_STRING_MAP.put(TYPE_FOTA_STRING, TYPE_FOTA);
+        APN_TYPE_STRING_MAP.put(TYPE_IMS_STRING, TYPE_IMS);
+        APN_TYPE_STRING_MAP.put(TYPE_CBS_STRING, TYPE_CBS);
+        APN_TYPE_STRING_MAP.put(TYPE_IA_STRING, TYPE_IA);
+        APN_TYPE_STRING_MAP.put(TYPE_EMERGENCY_STRING, TYPE_EMERGENCY);
+        APN_TYPE_STRING_MAP.put(TYPE_MCX_STRING, TYPE_MCX);
+        APN_TYPE_STRING_MAP.put(TYPE_XCAP_STRING, TYPE_XCAP);
 
-        PROTOCOL_STRING_MAP = new ArrayMap<String, Integer>();
+        APN_TYPE_INT_MAP = new ArrayMap<>();
+        APN_TYPE_INT_MAP.put(TYPE_DEFAULT, TYPE_DEFAULT_STRING);
+        APN_TYPE_INT_MAP.put(TYPE_MMS, TYPE_MMS_STRING);
+        APN_TYPE_INT_MAP.put(TYPE_SUPL, TYPE_SUPL_STRING);
+        APN_TYPE_INT_MAP.put(TYPE_DUN, TYPE_DUN_STRING);
+        APN_TYPE_INT_MAP.put(TYPE_HIPRI, TYPE_HIPRI_STRING);
+        APN_TYPE_INT_MAP.put(TYPE_FOTA, TYPE_FOTA_STRING);
+        APN_TYPE_INT_MAP.put(TYPE_IMS, TYPE_IMS_STRING);
+        APN_TYPE_INT_MAP.put(TYPE_CBS, TYPE_CBS_STRING);
+        APN_TYPE_INT_MAP.put(TYPE_IA, TYPE_IA_STRING);
+        APN_TYPE_INT_MAP.put(TYPE_EMERGENCY, TYPE_EMERGENCY_STRING);
+        APN_TYPE_INT_MAP.put(TYPE_MCX, TYPE_MCX_STRING);
+        APN_TYPE_INT_MAP.put(TYPE_XCAP, TYPE_XCAP_STRING);
+
+        PROTOCOL_STRING_MAP = new ArrayMap<>();
         PROTOCOL_STRING_MAP.put("IP", PROTOCOL_IP);
         PROTOCOL_STRING_MAP.put("IPV6", PROTOCOL_IPV6);
         PROTOCOL_STRING_MAP.put("IPV4V6", PROTOCOL_IPV4V6);
         PROTOCOL_STRING_MAP.put("PPP", PROTOCOL_PPP);
         PROTOCOL_STRING_MAP.put("NON-IP", PROTOCOL_NON_IP);
         PROTOCOL_STRING_MAP.put("UNSTRUCTURED", PROTOCOL_UNSTRUCTURED);
-        PROTOCOL_INT_MAP = new ArrayMap<Integer, String>();
+
+        PROTOCOL_INT_MAP = new ArrayMap<>();
         PROTOCOL_INT_MAP.put(PROTOCOL_IP, "IP");
         PROTOCOL_INT_MAP.put(PROTOCOL_IPV6, "IPV6");
         PROTOCOL_INT_MAP.put(PROTOCOL_IPV4V6, "IPV4V6");
@@ -230,12 +350,13 @@
         PROTOCOL_INT_MAP.put(PROTOCOL_NON_IP, "NON-IP");
         PROTOCOL_INT_MAP.put(PROTOCOL_UNSTRUCTURED, "UNSTRUCTURED");
 
-        MVNO_TYPE_STRING_MAP = new ArrayMap<String, Integer>();
+        MVNO_TYPE_STRING_MAP = new ArrayMap<>();
         MVNO_TYPE_STRING_MAP.put("spn", MVNO_TYPE_SPN);
         MVNO_TYPE_STRING_MAP.put("imsi", MVNO_TYPE_IMSI);
         MVNO_TYPE_STRING_MAP.put("gid", MVNO_TYPE_GID);
         MVNO_TYPE_STRING_MAP.put("iccid", MVNO_TYPE_ICCID);
-        MVNO_TYPE_INT_MAP = new ArrayMap<Integer, String>();
+
+        MVNO_TYPE_INT_MAP = new ArrayMap<>();
         MVNO_TYPE_INT_MAP.put(MVNO_TYPE_SPN, "spn");
         MVNO_TYPE_INT_MAP.put(MVNO_TYPE_IMSI, "imsi");
         MVNO_TYPE_INT_MAP.put(MVNO_TYPE_GID, "gid");
@@ -1289,10 +1410,13 @@
     }
 
     /**
+     * Converts the integer value of an APN type to the string version.
      * @param apnTypeBitmask bitmask of APN types.
      * @return comma delimited list of APN types.
      * @hide
      */
+    @SystemApi
+    @NonNull
     public static String getApnTypesStringFromBitmask(int apnTypeBitmask) {
         List<String> types = new ArrayList<>();
         for (Integer type : APN_TYPE_INT_MAP.keySet()) {
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index 49625bb..a116c07 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -78,6 +78,9 @@
     private final List<InetAddress> mGatewayAddresses;
     private final List<InetAddress> mPcscfAddresses;
     private final int mMtu;
+    private final int mMtuV4;
+    private final int mMtuV6;
+    private final int mVersion;
 
     /**
      * @param cause Data call fail cause. {@link DataFailCause#NONE} indicates no error.
@@ -97,9 +100,8 @@
      * "192.0.1.11 2001:db8::1". When null, the addresses represent point to point connections.
      * @param pcscfAddresses A list of Proxy Call State Control Function address via PCO (Protocol
      * Configuration Option) for IMS client.
-     * @param mtu MTU (maximum transmission unit) in bytes received from network. Zero or negative
-     * values means network has either not sent a value or sent an invalid value.
-     * either not sent a value or sent an invalid value.
+     * @param mtu MTU (maximum transmission unit) in bytes received from network.
+     * Zero or negative values means network has either not sent a value or sent an invalid value.
      *
      * @removed Use the {@link Builder()} instead.
      */
@@ -125,6 +127,34 @@
         mPcscfAddresses = (pcscfAddresses == null)
                 ? new ArrayList<>() : new ArrayList<>(pcscfAddresses);
         mMtu = mtu;
+        mMtuV4 = mMtuV6 = 0;
+        mVersion = 0;
+    }
+
+    /** @hide */
+    private DataCallResponse(@DataFailureCause int cause, int suggestedRetryTime, int id,
+            @LinkStatus int linkStatus, @ProtocolType int protocolType,
+            @Nullable String interfaceName, @Nullable List<LinkAddress> addresses,
+            @Nullable List<InetAddress> dnsAddresses, @Nullable List<InetAddress> gatewayAddresses,
+            @Nullable List<InetAddress> pcscfAddresses, int mtuV4, int mtuV6, int version) {
+        mCause = cause;
+        mSuggestedRetryTime = suggestedRetryTime;
+        mId = id;
+        mLinkStatus = linkStatus;
+        mProtocolType = protocolType;
+        mInterfaceName = (interfaceName == null) ? "" : interfaceName;
+        mAddresses = (addresses == null)
+                ? new ArrayList<>() : new ArrayList<>(addresses);
+        mDnsAddresses = (dnsAddresses == null)
+                ? new ArrayList<>() : new ArrayList<>(dnsAddresses);
+        mGatewayAddresses = (gatewayAddresses == null)
+                ? new ArrayList<>() : new ArrayList<>(gatewayAddresses);
+        mPcscfAddresses = (pcscfAddresses == null)
+                ? new ArrayList<>() : new ArrayList<>(pcscfAddresses);
+        mMtu = 0;
+        mMtuV4 = mtuV4;
+        mMtuV6 = mtuV6;
+        mVersion = version;
     }
 
     /** @hide */
@@ -145,6 +175,9 @@
         mPcscfAddresses = new ArrayList<>();
         source.readList(mPcscfAddresses, InetAddress.class.getClassLoader());
         mMtu = source.readInt();
+        mMtuV4 = source.readInt();
+        mMtuV6 = source.readInt();
+        mVersion = source.readInt();
     }
 
     /**
@@ -210,8 +243,29 @@
     /**
      * @return MTU (maximum transmission unit) in bytes received from network. Zero or negative
      * values means network has either not sent a value or sent an invalid value.
+     * @deprecated For IRadio 1.5 and up, use {@link #getMtuV4} or {@link #getMtuV6} instead.
      */
-    public int getMtu() { return mMtu; }
+    @Deprecated
+    public int getMtu() {
+        return mVersion < 5 ? mMtu : 0;
+    }
+
+    /**
+     * This replaces the deprecated method getMtu.
+     * @return MTU (maximum transmission unit) in bytes received from network, for IPv4.
+     * Zero or negative values means network has either not sent a value or sent an invalid value.
+     */
+    public int getMtuV4() {
+        return mVersion < 5 ? 0 : mMtuV4;
+    }
+
+    /**
+     * @return MTU (maximum transmission unit) in bytes received from network, for IPv6.
+     * Zero or negative values means network has either not sent a value or sent an invalid value.
+     */
+    public int getMtuV6() {
+        return mVersion < 5 ? 0 : mMtuV6;
+    }
 
     @NonNull
     @Override
@@ -229,6 +283,9 @@
            .append(" gateways=").append(mGatewayAddresses)
            .append(" pcscf=").append(mPcscfAddresses)
            .append(" mtu=").append(mMtu)
+           .append(" mtuV4=").append(mMtuV4)
+           .append(" mtuV6=").append(mMtuV6)
+           .append(" version=").append(mVersion)
            .append("}");
         return sb.toString();
     }
@@ -256,14 +313,17 @@
                 && mGatewayAddresses.containsAll(other.mGatewayAddresses)
                 && mPcscfAddresses.size() == other.mPcscfAddresses.size()
                 && mPcscfAddresses.containsAll(other.mPcscfAddresses)
-                && mMtu == other.mMtu;
+                && mMtu == other.mMtu
+                && mMtuV4 == other.mMtuV4
+                && mMtuV6 == other.mMtuV6
+                && mVersion == other.mVersion;
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mCause, mSuggestedRetryTime, mId, mLinkStatus, mProtocolType,
                 mInterfaceName, mAddresses, mDnsAddresses, mGatewayAddresses, mPcscfAddresses,
-                mMtu);
+                mMtu, mMtuV4, mMtuV6, mVersion);
     }
 
     @Override
@@ -284,6 +344,9 @@
         dest.writeList(mGatewayAddresses);
         dest.writeList(mPcscfAddresses);
         dest.writeInt(mMtu);
+        dest.writeInt(mMtuV4);
+        dest.writeInt(mMtuV6);
+        dest.writeInt(mVersion);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<DataCallResponse> CREATOR =
@@ -336,6 +399,12 @@
 
         private int mMtu;
 
+        private int mMtuV4;
+
+        private int mMtuV6;
+
+        private int mVersion;
+
         /**
          * Default constructor for Builder.
          */
@@ -460,6 +529,7 @@
          * negative values means network has either not sent a value or sent an invalid value.
          *
          * @return The same instance of the builder.
+         * @deprecated For IRadio 1.5 and up, use {@link #setMtuV4} or {@link #setMtuV6} instead.
          */
         public @NonNull Builder setMtu(int mtu) {
             mMtu = mtu;
@@ -467,14 +537,55 @@
         }
 
         /**
+         * Set maximum transmission unit of the data connection, for IPv4.
+         *
+         * @param mtu MTU (maximum transmission unit) in bytes received from network. Zero or
+         * negative values means network has either not sent a value or sent an invalid value.
+         *
+         * @return The same instance of the builder.
+         */
+        public @NonNull Builder setMtuV4(int mtu) {
+            mMtuV4 = mtu;
+            return this;
+        }
+
+        /**
+         * Set maximum transmission unit of the data connection, for IPv6.
+         *
+         * @param mtu MTU (maximum transmission unit) in bytes received from network. Zero or
+         * negative values means network has either not sent a value or sent an invalid value.
+         *
+         * @return The same instance of the builder.
+         */
+        public @NonNull Builder setMtuV6(int mtu) {
+            mMtuV6 = mtu;
+            return this;
+        }
+
+        /**
+         * Set the IRadio version for this DataCallResponse
+         * @hide
+         */
+        public @NonNull Builder setVersion(int version) {
+            mVersion = version;
+            return this;
+        }
+
+        /**
          * Build the DataCallResponse.
          *
          * @return the DataCallResponse object.
          */
         public @NonNull DataCallResponse build() {
-            return new DataCallResponse(mCause, mSuggestedRetryTime, mId, mLinkStatus,
-                    mProtocolType, mInterfaceName, mAddresses, mDnsAddresses, mGatewayAddresses,
-                    mPcscfAddresses, mMtu);
+            if (mVersion >= 5) {
+                return new DataCallResponse(mCause, mSuggestedRetryTime, mId, mLinkStatus,
+                        mProtocolType, mInterfaceName, mAddresses, mDnsAddresses, mGatewayAddresses,
+                        mPcscfAddresses, mMtuV4, mMtuV6, mVersion);
+            } else {
+                return new DataCallResponse(mCause, mSuggestedRetryTime, mId, mLinkStatus,
+                        mProtocolType, mInterfaceName, mAddresses, mDnsAddresses, mGatewayAddresses,
+                        mPcscfAddresses, mMtu);
+            }
         }
     }
 }
diff --git a/telephony/java/android/telephony/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java
index 96a5a81..f2a1249 100644
--- a/telephony/java/android/telephony/data/DataProfile.java
+++ b/telephony/java/android/telephony/data/DataProfile.java
@@ -96,7 +96,9 @@
     @NetworkTypeBitMask
     private final int mBearerBitmask;
 
-    private final int mMtu;
+    private final int mMtuV4;
+
+    private final int mMtuV6;
 
     private final boolean mPersistent;
 
@@ -104,12 +106,11 @@
 
     /** @hide */
     private DataProfile(int profileId, String apn, @ProtocolType int protocolType, int authType,
-                        String userName, String password, int type, int maxConnectionsTime,
-                        int maxConnections, int waitTime, boolean enabled,
-                        @ApnType int supportedApnTypesBitmask,
-                        @ProtocolType int roamingProtocolType,
-                        @NetworkTypeBitMask int bearerBitmask, int mtu, boolean persistent,
-                        boolean preferred) {
+            String userName, String password, int type, int maxConnectionsTime,
+            int maxConnections, int waitTime, boolean enabled,
+            @ApnType int supportedApnTypesBitmask, @ProtocolType int roamingProtocolType,
+            @NetworkTypeBitMask int bearerBitmask, int mtuV4, int mtuV6, boolean persistent,
+            boolean preferred) {
         this.mProfileId = profileId;
         this.mApn = apn;
         this.mProtocolType = protocolType;
@@ -128,7 +129,8 @@
         this.mSupportedApnTypesBitmask = supportedApnTypesBitmask;
         this.mRoamingProtocolType = roamingProtocolType;
         this.mBearerBitmask = bearerBitmask;
-        this.mMtu = mtu;
+        this.mMtuV4 = mtuV4;
+        this.mMtuV6 = mtuV6;
         this.mPersistent = persistent;
         this.mPreferred = preferred;
     }
@@ -148,7 +150,8 @@
         mSupportedApnTypesBitmask = source.readInt();
         mRoamingProtocolType = source.readInt();
         mBearerBitmask = source.readInt();
-        mMtu = source.readInt();
+        mMtuV4 = source.readInt();
+        mMtuV6 = source.readInt();
         mPersistent = source.readBoolean();
         mPreferred = source.readBoolean();
     }
@@ -237,8 +240,21 @@
 
     /**
      * @return The maximum transmission unit (MTU) size in bytes.
+     * @deprecated use {@link #getMtuV4} or {@link #getMtuV6} instead.
      */
-    public int getMtu() { return mMtu; }
+    @Deprecated
+    public int getMtu() { return mMtuV4; }
+
+    /**
+     * This replaces the deprecated method getMtu.
+     * @return The maximum transmission unit (MTU) size in bytes, for IPv4.
+     */
+    public int getMtuV4() { return mMtuV4; }
+
+    /**
+     * @return The maximum transmission unit (MTU) size in bytes, for IPv6.
+     */
+    public int getMtuV6() { return mMtuV6; }
 
     /**
      * @return {@code true} if modem must persist this data profile.
@@ -265,8 +281,8 @@
                          (mApn + "/" + mUserName + "/" + mPassword)) + "/" + mType + "/"
                 + mMaxConnectionsTime + "/" + mMaxConnections + "/"
                 + mWaitTime + "/" + mEnabled + "/" + mSupportedApnTypesBitmask + "/"
-                + mRoamingProtocolType + "/" + mBearerBitmask + "/" + mMtu + "/" + mPersistent + "/"
-                + mPreferred;
+                + mRoamingProtocolType + "/" + mBearerBitmask + "/" + mMtuV4 + "/" + mMtuV6 + "/"
+                + mPersistent + "/" + mPreferred;
     }
 
     @Override
@@ -285,7 +301,8 @@
         dest.writeInt(mSupportedApnTypesBitmask);
         dest.writeInt(mRoamingProtocolType);
         dest.writeInt(mBearerBitmask);
-        dest.writeInt(mMtu);
+        dest.writeInt(mMtuV4);
+        dest.writeInt(mMtuV6);
         dest.writeBoolean(mPersistent);
         dest.writeBoolean(mPreferred);
     }
@@ -319,7 +336,8 @@
                 && mSupportedApnTypesBitmask == that.mSupportedApnTypesBitmask
                 && mRoamingProtocolType == that.mRoamingProtocolType
                 && mBearerBitmask == that.mBearerBitmask
-                && mMtu == that.mMtu
+                && mMtuV4 == that.mMtuV4
+                && mMtuV6 == that.mMtuV6
                 && mPersistent == that.mPersistent
                 && mPreferred == that.mPreferred
                 && Objects.equals(mApn, that.mApn)
@@ -331,8 +349,8 @@
     public int hashCode() {
         return Objects.hash(mProfileId, mApn, mProtocolType, mAuthType, mUserName, mPassword, mType,
                 mMaxConnectionsTime, mMaxConnections, mWaitTime, mEnabled,
-                mSupportedApnTypesBitmask, mRoamingProtocolType, mBearerBitmask, mMtu, mPersistent,
-                mPreferred);
+                mSupportedApnTypesBitmask, mRoamingProtocolType, mBearerBitmask, mMtuV4, mMtuV6,
+                mPersistent, mPreferred);
     }
 
     /**
@@ -384,7 +402,9 @@
         @NetworkTypeBitMask
         private int mBearerBitmask;
 
-        private int mMtu;
+        private int mMtuV4;
+
+        private int mMtuV6;
 
         private boolean mPersistent;
 
@@ -567,9 +587,33 @@
          *
          * @param mtu The maximum transmission unit (MTU) size in bytes.
          * @return The same instance of the builder.
+         * @deprecated use {@link #setMtuV4} or {@link #setMtuV6} instead.
          */
         public @NonNull Builder setMtu(int mtu) {
-            mMtu = mtu;
+            mMtuV4 = mMtuV6 = mtu;
+            return this;
+        }
+
+        /**
+         * Set the maximum transmission unit (MTU) size in bytes, for IPv4.
+         * This replaces the deprecated method setMtu.
+         *
+         * @param mtu The maximum transmission unit (MTU) size in bytes.
+         * @return The same instance of the builder.
+         */
+        public @NonNull Builder setMtuV4(int mtu) {
+            mMtuV4 = mtu;
+            return this;
+        }
+
+        /**
+         * Set the maximum transmission unit (MTU) size in bytes, for IPv6.
+         *
+         * @param mtu The maximum transmission unit (MTU) size in bytes.
+         * @return The same instance of the builder.
+         */
+        public @NonNull Builder setMtuV6(int mtu) {
+            mMtuV6 = mtu;
             return this;
         }
 
@@ -606,7 +650,7 @@
         public @NonNull DataProfile build() {
             return new DataProfile(mProfileId, mApn, mProtocolType, mAuthType, mUserName, mPassword,
                     mType, mMaxConnectionsTime, mMaxConnections, mWaitTime, mEnabled,
-                    mSupportedApnTypesBitmask, mRoamingProtocolType, mBearerBitmask, mMtu,
+                    mSupportedApnTypesBitmask, mRoamingProtocolType, mBearerBitmask, mMtuV4, mMtuV6,
                     mPersistent, mPreferred);
         }
     }
diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java
index 372bdf1..bff12b6 100644
--- a/telephony/java/android/telephony/data/DataService.java
+++ b/telephony/java/android/telephony/data/DataService.java
@@ -31,7 +31,7 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.telephony.AccessNetworkConstants;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
diff --git a/telephony/java/android/telephony/data/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java
index 11dc78a..d33d3f9 100644
--- a/telephony/java/android/telephony/data/DataServiceCallback.java
+++ b/telephony/java/android/telephony/data/DataServiceCallback.java
@@ -22,7 +22,7 @@
 import android.annotation.SystemApi;
 import android.net.LinkProperties;
 import android.os.RemoteException;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.telephony.data.DataService.DataServiceProvider;
 
 import java.lang.annotation.Retention;
diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java
index e793979..8220b16 100644
--- a/telephony/java/android/telephony/data/QualifiedNetworksService.java
+++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java
@@ -28,7 +28,7 @@
 import android.os.RemoteException;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.Annotation.ApnType;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
diff --git a/telephony/java/android/telephony/emergency/EmergencyNumber.java b/telephony/java/android/telephony/emergency/EmergencyNumber.java
index 1666265..cd3fc95 100644
--- a/telephony/java/android/telephony/emergency/EmergencyNumber.java
+++ b/telephony/java/android/telephony/emergency/EmergencyNumber.java
@@ -25,7 +25,7 @@
 import android.os.Parcelable;
 import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
diff --git a/telephony/java/android/telephony/euicc/EuiccCardManager.java b/telephony/java/android/telephony/euicc/EuiccCardManager.java
index 994c49c..e16fffa 100644
--- a/telephony/java/android/telephony/euicc/EuiccCardManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccCardManager.java
@@ -21,8 +21,8 @@
 import android.annotation.SystemApi;
 import android.content.Context;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.service.euicc.EuiccProfileInfo;
+import android.telephony.TelephonyFrameworkInitializer;
 import android.util.Log;
 
 import com.android.internal.telephony.euicc.IAuthenticateServerCallback;
@@ -148,7 +148,10 @@
 
     private IEuiccCardController getIEuiccCardController() {
         return IEuiccCardController.Stub.asInterface(
-                ServiceManager.getService("euicc_card_controller"));
+                TelephonyFrameworkInitializer
+                        .getTelephonyServiceManager()
+                        .getEuiccCardControllerServiceRegisterer()
+                        .get());
     }
 
     /**
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index cb66a96..27a7022 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -30,7 +30,7 @@
 import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.telephony.TelephonyFrameworkInitializer;
 import android.telephony.TelephonyManager;
 import android.telephony.euicc.EuiccCardManager.ResetOption;
 
@@ -246,13 +246,56 @@
      * Key for an extra set on {@link PendingIntent} result callbacks providing a detailed result
      * code.
      *
-     * <p>This code is an implementation detail of the embedded subscription manager and is only
-     * intended for logging or debugging purposes.
+     * <p>The value of this key is an integer and contains two portions. The first byte is
+     * OperationCode and the reaming three bytes is the ErrorCode.
+     *
+     * OperationCode is the first byte of the result code and is a categorization which defines what
+     * type of operation took place when an error occurred. e.g {@link #OPERATION_DOWNLOAD} means
+     * the error is related to download.Since the OperationCode only uses at most one byte, the
+     * maximum allowed quantity is 255(0xFF).
+     *
+     * ErrorCode is the remaing three bytes of the result code, and it denotes what happened.
+     * e.g a combination of {@link #OPERATION_DOWNLOAD} and {@link #ERROR_TIME_OUT} will suggest the
+     * download operation has timed out. The only exception here is
+     * {@link #OPERATION_SMDX_SUBJECT_REASON_CODE}, where instead of ErrorCode, SubjectCode[5.2.6.1
+     * from GSMA (SGP.22 v2.2) and ReasonCode[5.2.6.2] from GSMA (SGP.22 v2.2) are encoded. @see
+     * {@link #EXTRA_EMBEDDED_SUBSCRIPTION_SMDX_SUBJECT_CODE} and
+     * {@link #EXTRA_EMBEDDED_SUBSCRIPTION_SMDX_REASON_CODE}
      */
     public static final String EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE =
             "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_DETAILED_CODE";
 
     /**
+     * Key for an extra set on {@link PendingIntent} result callbacks providing a
+     * OperationCode of {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE}.
+     */
+    public static final String EXTRA_EMBEDDED_SUBSCRIPTION_OPERATION_CODE =
+            "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_OPERATION_CODE";
+
+    /**
+     * Key for an extra set on {@link PendingIntent} result callbacks providing a
+     * ErrorCode of {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE}.
+     */
+    public static final String EXTRA_EMBEDDED_SUBSCRIPTION_ERROR_CODE =
+            "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_ERROR_CODE";
+
+    /**
+     * Key for an extra set on {@link PendingIntent} result callbacks providing a
+     * SubjectCode[5.2.6.1] from GSMA (SGP.22 v2.2) decoded from
+     * {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE}.
+     */
+    public static final String EXTRA_EMBEDDED_SUBSCRIPTION_SMDX_SUBJECT_CODE =
+            "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_SMDX_SUBJECT_CODE";
+
+    /**
+     * Key for an extra set on {@link PendingIntent} result callbacks providing a
+     * ReasonCode[5.2.6.2] from GSMA (SGP.22 v2.2) decoded from
+     * {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE}.
+     */
+    public static final String EXTRA_EMBEDDED_SUBSCRIPTION_SMDX_REASON_CODE =
+            "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_SMDX_REASON_CODE";
+
+    /**
      * Key for an extra set on {@code #getDownloadableSubscriptionMetadata} PendingIntent result
      * callbacks providing the downloadable subscription metadata.
      */
@@ -491,6 +534,259 @@
     @SystemApi
     public static final int EUICC_OTA_STATUS_UNAVAILABLE = 5;
 
+    /**
+     * List of OperationCode corresponding to {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE}'s
+     * value, an integer. @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details
+     *
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"OPERATION_"}, value = {
+            OPERATION_SYSTEM,
+            OPERATION_SIM_SLOT,
+            OPERATION_EUICC_CARD,
+            OPERATION_SWITCH,
+            OPERATION_DOWNLOAD,
+            OPERATION_METADATA,
+            OPERATION_EUICC_GSMA,
+            OPERATION_APDU,
+            OPERATION_SMDX,
+            OPERATION_HTTP,
+            OPERATION_SMDX_SUBJECT_REASON_CODE,
+    })
+    public @interface OperationCode {
+    }
+
+    /**
+     * Internal system error.
+     * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details
+     */
+    public static final int OPERATION_SYSTEM = 1;
+
+    /**
+     * SIM slot error. Failed to switch slot, failed to access the physical slot etc.
+     * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details
+     */
+    public static final int OPERATION_SIM_SLOT = 2;
+
+    /**
+     * eUICC card error.
+     * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details
+     */
+    public static final int OPERATION_EUICC_CARD = 3;
+
+    /**
+     * Generic switching profile error
+     * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details
+     */
+    public static final int OPERATION_SWITCH = 4;
+
+    /**
+     * Download profile error.
+     * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details
+     */
+    public static final int OPERATION_DOWNLOAD = 5;
+
+    /**
+     * Subscription's metadata error
+     * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details
+     */
+    public static final int OPERATION_METADATA = 6;
+
+    /**
+     * eUICC returned an error defined in GSMA (SGP.22 v2.2) while running one of the ES10x
+     * functions.
+     * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details
+     */
+    public static final int OPERATION_EUICC_GSMA = 7;
+
+    /**
+     * The exception of failing to execute an APDU command. It can be caused by an error
+     * happening on opening the basic or logical channel, or the response of the APDU command is
+     * not success (0x9000).
+     * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details
+     */
+    public static final int OPERATION_APDU = 8;
+
+    /**
+     * SMDX(SMDP/SMDS) error
+     * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details
+     */
+    public static final int OPERATION_SMDX = 9;
+
+    /**
+     * SubjectCode[5.2.6.1] and ReasonCode[5.2.6.2] error from GSMA (SGP.22 v2.2)
+     * When {@link #OPERATION_SMDX_SUBJECT_REASON_CODE} is used as the
+     * {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE}, the remaining three bytes of the integer
+     * result from {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} will be used to stored the
+     * SubjectCode and ReasonCode from the GSMA spec and NOT ErrorCode.
+     *
+     * The encoding will follow the format of:
+     * 1. The first byte of the result will be 255(0xFF).
+     * 2. Remaining three bytes(24 bits) will be split into six sections, 4 bits in each section.
+     * 3. A SubjectCode/ReasonCode will take 12 bits each.
+     * 4. The maximum number can be represented per section is 15, as that is the maximum number
+     * allowed to be stored into 4 bits
+     * 5. Maximum supported nested category from GSMA is three layers. E.g 8.11.1.2 is not
+     * supported.
+     *
+     * E.g given SubjectCode(8.11.1) and ReasonCode(5.1)
+     *
+     * Base10:  0       10      8       11      1       0       5       1
+     * Base2:   0000    1010    1000    1011    0001    0000    0101    0001
+     * Base16:  0       A       8       B       1       0       5       1
+     *
+     * Thus the integer stored in {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} is
+     * 0xA8B1051(176885841)
+     *
+     * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details
+     */
+    public static final int OPERATION_SMDX_SUBJECT_REASON_CODE = 10;
+
+    /**
+     * HTTP error
+     * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details
+     */
+    public static final int OPERATION_HTTP = 11;
+
+    /**
+     * List of ErrorCode corresponding to {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE}
+     * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"ERROR_"}, value = {
+            ERROR_CARRIER_LOCKED,
+            ERROR_INVALID_ACTIVATION_CODE,
+            ERROR_INVALID_CONFIRMATION_CODE,
+            ERROR_INCOMPATIBLE_CARRIER,
+            ERROR_EUICC_INSUFFICIENT_MEMORY,
+            ERROR_TIME_OUT,
+            ERROR_EUICC_MISSING,
+            ERROR_UNSUPPORTED_VERSION,
+            ERROR_SIM_MISSING,
+            ERROR_EUICC_GSMA_INSTALL_ERROR,
+            ERROR_DISALLOWED_BY_PPR,
+            ERROR_ADDRESS_MISSING,
+            ERROR_CERTIFICATE_ERROR,
+            ERROR_NO_PROFILES_AVAILABLE,
+            ERROR_CONNECTION_ERROR,
+            ERROR_INVALID_RESPONSE,
+            ERROR_OPERATION_BUSY,
+    })
+    public @interface ErrorCode{}
+
+    /**
+     * Operation such as downloading/switching to another profile failed due to device being
+     * carrier locked.
+     * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details
+     */
+    public static final int ERROR_CARRIER_LOCKED = 10000;
+
+    /**
+     * The activation code(SGP.22 v2.2 section[4.1]) is invalid.
+     * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details
+     */
+    public static final int ERROR_INVALID_ACTIVATION_CODE = 10001;
+
+    /**
+     * The confirmation code(SGP.22 v2.2 section[4.7]) is invalid.
+     * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details
+     */
+    public static final int ERROR_INVALID_CONFIRMATION_CODE = 10002;
+
+    /**
+     * The profile's carrier is incompatible with the LPA.
+     * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details
+     */
+    public static final int ERROR_INCOMPATIBLE_CARRIER = 10003;
+
+    /**
+     * There is no more space available on the eUICC for new profiles.
+     * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details
+     */
+    public static final int ERROR_EUICC_INSUFFICIENT_MEMORY = 10004;
+
+    /**
+     * Timed out while waiting for an operation to complete. i.e restart, disable,
+     * switch reset etc.
+     * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details
+     */
+    public static final int ERROR_TIME_OUT = 10005;
+
+    /**
+     * eUICC is missing or defective on the device.
+     * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details
+     */
+    public static final int ERROR_EUICC_MISSING = 10006;
+
+    /**
+     * The eUICC card(hardware) version is incompatible with the software
+     * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details
+     */
+    public static final int ERROR_UNSUPPORTED_VERSION = 10007;
+
+    /**
+     * No SIM card is available in the device.
+     * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details
+     */
+    public static final int ERROR_SIM_MISSING = 10008;
+
+    /**
+     * Failure to load the profile onto the eUICC card. i.e
+     * 1. iccid of the profile already exists on the eUICC.
+     * 2. GSMA(.22 v2.2) Profile Install Result - installFailedDueToDataMismatch
+     * 3. operation was interrupted
+     * 4. SIMalliance error in PEStatus(SGP.22 v2.2 section 2.5.6.1)
+     * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details
+     */
+    public static final int ERROR_EUICC_GSMA_INSTALL_ERROR = 10009;
+
+    /**
+     * Failed to load profile onto eUICC due to Profile Poicly Rules.
+     * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details
+     */
+    public static final int ERROR_DISALLOWED_BY_PPR = 10010;
+
+
+    /**
+     * Address is missing e.g SMDS/SMDP address is missing.
+     * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details
+     */
+    public static final int ERROR_ADDRESS_MISSING = 10011;
+
+    /**
+     * Certificate needed for authentication is not valid or missing. E.g  SMDP/SMDS authentication
+     * failed.
+     * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details
+     */
+    public static final int ERROR_CERTIFICATE_ERROR = 10012;
+
+
+    /**
+     * No profiles available.
+     * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details
+     */
+    public static final int ERROR_NO_PROFILES_AVAILABLE = 10013;
+
+    /**
+     * Failure to create a connection.
+     * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details
+     */
+    public static final int ERROR_CONNECTION_ERROR = 10014;
+
+    /**
+     * Response format is invalid. e.g SMDP/SMDS response contains invalid json, header or/and ASN1.
+     * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details
+     */
+    public static final int ERROR_INVALID_RESPONSE = 10015;
+
+    /**
+     * The operation is currently busy, try again later.
+     * @see {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} for details
+     */
+    public static final int ERROR_OPERATION_BUSY = 10016;
+
     private final Context mContext;
     private int mCardId;
 
@@ -968,6 +1264,10 @@
     }
 
     private static IEuiccController getIEuiccController() {
-        return IEuiccController.Stub.asInterface(ServiceManager.getService("econtroller"));
+        return IEuiccController.Stub.asInterface(
+                TelephonyFrameworkInitializer
+                        .getTelephonyServiceManager()
+                        .getEuiccControllerService()
+                        .get());
     }
 }
diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java
index bc60d81..9c1be48 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -40,10 +41,11 @@
 import java.util.List;
 
 /**
- * Parcelable object to handle IMS call profile.
- * It is created from GSMA IR.92/IR.94, 3GPP TS 24.229/TS 26.114/TS26.111.
- * It provides the service and call type, the additional information related to the call.
- *
+ * A Parcelable object to handle the IMS call profile, which provides the service, call type, and
+ * additional information related to the call.
+ * <p>
+ * See the following specifications for more information about this class: GSMA IR.92/IR.94,
+ * 3GPP TS 24.229/TS 26.114/TS26.111.
  * @hide
  */
 @SystemApi
@@ -151,12 +153,13 @@
      */
     public static final String EXTRA_CONFERENCE_AVAIL = "conference_avail";
 
-    // Extra string for internal use only. OEMs should not use
-    // this for packing extras.
     /**
+     * Extra key used to store a Bundle containing proprietary extras to send to the ImsService.
+     * Use {@link #getProprietaryCallExtras()} instead.
      * @hide
      */
-    public static final String EXTRA_OEM_EXTRAS = "OemCallExtras";
+    @TestApi
+    public static final String EXTRA_OEM_EXTRAS = "android.telephony.ims.extra.OEM_EXTRAS";
 
     /**
      * Rule for originating identity (number) presentation, MO/MT.
@@ -299,17 +302,40 @@
      *      updateImsCallRatFromExtras(Bundle)} to determine whether to set the
      * {@link android.telecom.TelecomManager#EXTRA_CALL_NETWORK_TYPE} extra value and
      * {@link android.telecom.Connection#PROPERTY_WIFI} property on a connection.
+     * @deprecated the constants associated with this extra are hidden, instead use
+     * {@link #EXTRA_CALL_NETWORK_TYPE}.
      */
+    @Deprecated
     public static final String EXTRA_CALL_RAT_TYPE = "CallRadioTech";
 
     /**
+     * Extra key with an {@code int} value which can be set in {@link #setCallExtraInt(String, int)}
+     * to indicate the network type used for a call.
+     * <p>
+     * Valid values are defined by {@code TelephonyManager.NETWORK_TYPE_*} constants. An example may
+     * be {@link android.telephony.TelephonyManager#NETWORK_TYPE_LTE}.
+     */
+    public static final String EXTRA_CALL_NETWORK_TYPE =
+            "android.telephony.ims.extra.CALL_NETWORK_TYPE";
+
+    /**
      * Similar to {@link #EXTRA_CALL_RAT_TYPE}, except with a lowercase 'c'.  Used to ensure
      * compatibility with modems that are non-compliant with the {@link #EXTRA_CALL_RAT_TYPE}
      * extra key.  Should be removed when the non-compliant modems are fixed.
      * @hide
+     * @deprecated Use {@link #EXTRA_CALL_NETWORK_TYPE} instead.
      */
+    @Deprecated
     public static final String EXTRA_CALL_RAT_TYPE_ALT = "callRadioTech";
 
+    /**
+     * String extra property containing forwarded numbers associated with the current connection
+     * for an IMS call. The value is string array, and it can include multiple numbers, and
+     * the array values are expected E164 (e.g. +1 (650) 253-0000) format.
+     */
+    public static final String EXTRA_FORWARDED_NUMBER =
+            "android.telephony.ims.extra.FORWARDED_NUMBER";
+
     /** @hide */
     public int mServiceType;
     /** @hide */
@@ -687,6 +713,18 @@
         return mCallExtras;
     }
 
+    /**
+     * Get the proprietary extras set for this ImsCallProfile.
+     * @return A {@link Bundle} containing proprietary call extras that were not set by the
+     * platform.
+     */
+    public @Nullable Bundle getProprietaryCallExtras() {
+        if (mCallExtras == null) {
+            return null;
+        }
+        return mCallExtras.getBundle(EXTRA_OEM_EXTRAS);
+    }
+
     public ImsStreamMediaProfile getMediaProfile() {
         return mMediaProfile;
     }
diff --git a/telephony/java/android/telephony/ims/ImsCallSession.java b/telephony/java/android/telephony/ims/ImsCallSession.java
index 5adc99e..1b583fd 100644
--- a/telephony/java/android/telephony/ims/ImsCallSession.java
+++ b/telephony/java/android/telephony/ims/ImsCallSession.java
@@ -25,8 +25,6 @@
 import com.android.ims.internal.IImsCallSession;
 import com.android.ims.internal.IImsVideoCallProvider;
 
-import java.util.Objects;
-
 /**
  * Provides the call initiation/termination, and media exchange between two IMS endpoints.
  * It directly communicates with IMS service which implements the IMS protocol behavior.
@@ -346,7 +344,7 @@
         }
 
         /**
-         * Called when an {@link ImsCallSession} may handover from one radio technology to another.
+         * Called when an {@link ImsCallSession} may handover from one network type to another.
          * For example, the session may handover from WIFI to LTE if conditions are right.
          * <p>
          * If handover is attempted,
@@ -355,24 +353,24 @@
          * called to indicate the success or failure of the handover.
          *
          * @param session IMS session object
-         * @param srcAccessTech original access technology
-         * @param targetAccessTech new access technology
+         * @param srcNetworkType original network type
+         * @param targetNetworkType new network type
          */
-        public void callSessionMayHandover(ImsCallSession session, int srcAccessTech,
-                int targetAccessTech) {
+        public void callSessionMayHandover(ImsCallSession session, int srcNetworkType,
+                int targetNetworkType) {
             // no-op
         }
 
         /**
-         * Called when session access technology changes
+         * Called when session network type changes
          *
          * @param session IMS session object
-         * @param srcAccessTech original access technology
-         * @param targetAccessTech new access technology
+         * @param srcNetworkType original network type
+         * @param targetNetworkType new network type
          * @param reasonInfo
          */
         public void callSessionHandover(ImsCallSession session,
-                                 int srcAccessTech, int targetAccessTech,
+                                 int srcNetworkType, int targetNetworkType,
                                  ImsReasonInfo reasonInfo) {
             // no-op
         }
@@ -381,12 +379,12 @@
          * Called when session access technology change fails
          *
          * @param session IMS session object
-         * @param srcAccessTech original access technology
-         * @param targetAccessTech new access technology
+         * @param srcNetworkType original access technology
+         * @param targetNetworkType new access technology
          * @param reasonInfo handover failure reason
          */
         public void callSessionHandoverFailed(ImsCallSession session,
-                                       int srcAccessTech, int targetAccessTech,
+                                       int srcNetworkType, int targetNetworkType,
                                        ImsReasonInfo reasonInfo) {
             // no-op
         }
@@ -1303,20 +1301,19 @@
         /**
          * Notifies of a case where a {@link ImsCallSession} may
          * potentially handover from one radio technology to another.
-         * @param srcAccessTech The source radio access technology; one of the access technology
-         *                      constants defined in {@link android.telephony.ServiceState}.  For
-         *                      example
-         *                      {@link android.telephony.ServiceState#RIL_RADIO_TECHNOLOGY_LTE}.
-         * @param targetAccessTech The target radio access technology; one of the access technology
-         *                      constants defined in {@link android.telephony.ServiceState}.  For
-         *                      example
-         *                      {@link android.telephony.ServiceState#RIL_RADIO_TECHNOLOGY_LTE}.
+         * @param srcNetworkType The source network type; one of the network type constants defined
+         *                       in {@link android.telephony.TelephonyManager}.  For example
+         *                      {@link android.telephony.TelephonyManager#NETWORK_TYPE_LTE}.
+         * @param targetNetworkType The target radio access technology; one of the network type
+         *                          constants defined in {@link android.telephony.TelephonyManager}.
+         *                          For example
+         *                          {@link android.telephony.TelephonyManager#NETWORK_TYPE_LTE}.
          */
         @Override
-        public void callSessionMayHandover(int srcAccessTech, int targetAccessTech) {
+        public void callSessionMayHandover(int srcNetworkType, int targetNetworkType) {
             if (mListener != null) {
-                mListener.callSessionMayHandover(ImsCallSession.this, srcAccessTech,
-                        targetAccessTech);
+                mListener.callSessionMayHandover(ImsCallSession.this, srcNetworkType,
+                        targetNetworkType);
             }
         }
 
@@ -1324,11 +1321,11 @@
          * Notifies of handover information for this call
          */
         @Override
-        public void callSessionHandover(int srcAccessTech, int targetAccessTech,
+        public void callSessionHandover(int srcNetworkType, int targetNetworkType,
                 ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionHandover(ImsCallSession.this, srcAccessTech,
-                        targetAccessTech, reasonInfo);
+                mListener.callSessionHandover(ImsCallSession.this, srcNetworkType,
+                        targetNetworkType, reasonInfo);
             }
         }
 
@@ -1336,11 +1333,11 @@
          * Notifies of handover failure info for this call
          */
         @Override
-        public void callSessionHandoverFailed(int srcAccessTech, int targetAccessTech,
+        public void callSessionHandoverFailed(int srcNetworkType, int targetNetworkType,
                 ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionHandoverFailed(ImsCallSession.this, srcAccessTech,
-                        targetAccessTech, reasonInfo);
+                mListener.callSessionHandoverFailed(ImsCallSession.this, srcNetworkType,
+                        targetNetworkType, reasonInfo);
             }
         }
 
diff --git a/telephony/java/android/telephony/ims/ImsCallSessionListener.java b/telephony/java/android/telephony/ims/ImsCallSessionListener.java
index e11886f..025721c 100644
--- a/telephony/java/android/telephony/ims/ImsCallSessionListener.java
+++ b/telephony/java/android/telephony/ims/ImsCallSessionListener.java
@@ -17,10 +17,13 @@
 package android.telephony.ims;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.RemoteException;
+import android.telephony.Annotation;
 import android.telephony.CallQuality;
+import android.telephony.ServiceState;
 import android.telephony.ims.aidl.IImsCallSessionListener;
 import android.telephony.ims.stub.ImsCallSessionImplBase;
 
@@ -476,11 +479,27 @@
      * @param targetAccessTech The target radio access technology; one of the access technology
      * constants defined in {@link android.telephony.ServiceState}. For example
      * {@link android.telephony.ServiceState#RIL_RADIO_TECHNOLOGY_LTE}.
+     * @deprecated Uses hidden constants for radio access technology, use
+     * {@link #onMayHandover(int, int)} instead.
      */
-    public void callSessionMayHandover(int srcAccessTech, int targetAccessTech)
-    {
+    @Deprecated
+    public void callSessionMayHandover(int srcAccessTech, int targetAccessTech) {
+        // Use new API internally.
+        onMayHandover(ServiceState.rilRadioTechnologyToNetworkType(srcAccessTech),
+                ServiceState.rilRadioTechnologyToNetworkType(targetAccessTech));
+    }
+
+    /**
+     * Notify the framework that the associated {@link ImsCallSession} may handover from one network
+     * type to another.
+     *
+     * @param srcNetworkType The source network type.
+     * @param targetNetworkType The target network type.
+     */
+    public void onMayHandover(@Annotation.NetworkType int srcNetworkType,
+            @Annotation.NetworkType int targetNetworkType) {
         try {
-            mListener.callSessionMayHandover(srcAccessTech, targetAccessTech);
+            mListener.callSessionMayHandover(srcNetworkType, targetNetworkType);
         } catch (RemoteException e) {
             throw new RuntimeException(e);
         }
@@ -494,11 +513,29 @@
      * @param targetAccessTech new access technology, defined in
      * {@link android.telephony.ServiceState}.
      * @param reasonInfo The {@link ImsReasonInfo} associated with this handover.
+     * @deprecated Uses hidden radio access technology constants, use
+     * {@link #onHandover(int, int, ImsReasonInfo)} instead.
      */
+    @Deprecated
     public void callSessionHandover(int srcAccessTech, int targetAccessTech,
             ImsReasonInfo reasonInfo) {
+        // Use new API internally.
+        onHandover(ServiceState.rilRadioTechnologyToNetworkType(srcAccessTech),
+                ServiceState.rilRadioTechnologyToNetworkType(targetAccessTech), reasonInfo);
+    }
+
+    /**
+     * Notify the framework that the associated {@link ImsCallSession} has handed over from one
+     * network type to another.
+     *
+     * @param srcNetworkType original network type.
+     * @param targetNetworkType target network type after handover..
+     * @param reasonInfo An optional {@link ImsReasonInfo} associated with this handover.
+     */
+    public void onHandover(@Annotation.NetworkType int srcNetworkType,
+            @Annotation.NetworkType int targetNetworkType, @Nullable ImsReasonInfo reasonInfo) {
         try {
-            mListener.callSessionHandover(srcAccessTech, targetAccessTech, reasonInfo);
+            mListener.callSessionHandover(srcNetworkType, targetNetworkType, reasonInfo);
         } catch (RemoteException e) {
             throw new RuntimeException(e);
         }
@@ -510,11 +547,28 @@
      * @param srcAccessTech original access technology
      * @param targetAccessTech new access technology
      * @param reasonInfo An {@link ImsReasonInfo} detailing the reason for the failure.
+     * @deprecated Uses hidden radio access technology constants, use
+     * {@link #onHandoverFailed(int, int, ImsReasonInfo)} instead
      */
+    @Deprecated
     public void callSessionHandoverFailed(int srcAccessTech, int targetAccessTech,
             ImsReasonInfo reasonInfo) {
+        // Use new API internally.
+        onHandoverFailed(ServiceState.rilRadioTechnologyToNetworkType(srcAccessTech),
+                ServiceState.rilRadioTechnologyToNetworkType(targetAccessTech), reasonInfo);
+    }
+
+    /**
+     * The IMS call session's access technology change has failed..
+     *
+     * @param srcNetworkType original network type.
+     * @param targetNetworkType target network type that the handover failed for.
+     * @param reasonInfo An {@link ImsReasonInfo} detailing the reason for the failure.
+     */
+    public void onHandoverFailed(@Annotation.NetworkType int srcNetworkType,
+            @Annotation.NetworkType int targetNetworkType, @NonNull ImsReasonInfo reasonInfo) {
         try {
-            mListener.callSessionHandoverFailed(srcAccessTech, targetAccessTech, reasonInfo);
+            mListener.callSessionHandoverFailed(srcNetworkType, targetNetworkType, reasonInfo);
         } catch (RemoteException e) {
             throw new RuntimeException(e);
         }
diff --git a/telephony/java/android/telephony/ims/ImsConferenceState.java b/telephony/java/android/telephony/ims/ImsConferenceState.java
index 8d2049b..abfee61 100644
--- a/telephony/java/android/telephony/ims/ImsConferenceState.java
+++ b/telephony/java/android/telephony/ims/ImsConferenceState.java
@@ -24,7 +24,7 @@
 import android.os.Parcelable;
 import android.telecom.Call;
 import android.telecom.Connection;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.util.Log;
 
 import java.util.HashMap;
diff --git a/telephony/java/android/telephony/ims/ImsException.java b/telephony/java/android/telephony/ims/ImsException.java
index 39af2e7..cb3f0f9 100644
--- a/telephony/java/android/telephony/ims/ImsException.java
+++ b/telephony/java/android/telephony/ims/ImsException.java
@@ -30,10 +30,7 @@
 /**
  * This class defines an IMS-related exception that has been thrown while interacting with a
  * device or carrier provided ImsService implementation.
- * @hide
  */
-@SystemApi
-@TestApi
 public final class ImsException extends Exception {
 
     /**
@@ -83,7 +80,10 @@
     /**
      * A new {@link ImsException} with an unspecified {@link ImsErrorCode} code.
      * @param message an optional message to detail the error condition more specifically.
+     * @hide
      */
+    @SystemApi
+    @TestApi
     public ImsException(@Nullable String message) {
         super(getMessage(message, CODE_ERROR_UNSPECIFIED));
     }
@@ -91,7 +91,10 @@
     /**
      * A new {@link ImsException} that includes an {@link ImsErrorCode} error code.
      * @param message an optional message to detail the error condition more specifically.
+     * @hide
      */
+    @SystemApi
+    @TestApi
     public ImsException(@Nullable String message, @ImsErrorCode int code) {
         super(getMessage(message, code));
         mCode = code;
@@ -102,7 +105,10 @@
      * {@link Throwable} that contains the original error that was thrown to lead to this Exception.
      * @param message an optional message to detail the error condition more specifically.
      * @param cause the {@link Throwable} that caused this {@link ImsException} to be created.
+     * @hide
      */
+    @SystemApi
+    @TestApi
     public ImsException(@Nullable String message, @ImsErrorCode  int code,
             @Nullable Throwable cause) {
         super(getMessage(message, code), cause);
diff --git a/telephony/java/android/telephony/ims/ImsExternalCallState.java b/telephony/java/android/telephony/ims/ImsExternalCallState.java
index dcb9c9d..136a83e 100644
--- a/telephony/java/android/telephony/ims/ImsExternalCallState.java
+++ b/telephony/java/android/telephony/ims/ImsExternalCallState.java
@@ -24,7 +24,7 @@
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java
index 057d22c..5fdef83 100644
--- a/telephony/java/android/telephony/ims/ImsMmTelManager.java
+++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java
@@ -23,16 +23,17 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressAutoDoc;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
-import android.content.Context;
 import android.os.Binder;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.ServiceSpecificException;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyFrameworkInitializer;
 import android.telephony.ims.aidl.IImsCapabilityCallback;
 import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.feature.MmTelFeature;
@@ -56,10 +57,7 @@
  * associated subscription.
  *
  * @see #createForSubscriptionId(int)
- * @hide
  */
-@SystemApi
-@TestApi
 public class ImsMmTelManager implements RegistrationManager {
 
     /**
@@ -94,9 +92,11 @@
      * @see #registerImsRegistrationCallback(Executor, RegistrationCallback) (RegistrationCallback)
      * @see #unregisterImsRegistrationCallback(RegistrationCallback)
      * @deprecated Use {@link RegistrationManager.RegistrationCallback} instead.
+     * @hide
      */
     // Do not add to this class, add to RegistrationManager.RegistrationCallback instead.
     @Deprecated
+    @SystemApi @TestApi
     public static class RegistrationCallback extends RegistrationManager.RegistrationCallback {
 
         /**
@@ -141,7 +141,7 @@
 
     /**
      * Receives IMS capability status updates from the ImsService. This information is also
-     * available via the {@link #isAvailable(int, int)} method below.
+     * available via the {@see #isAvailable(int, int)} method below.
      *
      * @see #registerMmTelCapabilityCallback(Executor, CapabilityCallback) (CapabilityCallback)
      * @see #unregisterMmTelCapabilityCallback(CapabilityCallback)
@@ -194,7 +194,7 @@
          * If unavailable, the feature is not able to support the unavailable capability at this
          * time.
          *
-         * This information can also be queried using the {@link #isAvailable(int, int)} API.
+         * This information can also be queried using the {@see #isAvailable(int, int)} API.
          *
          * @param capabilities The new availability of the capabilities.
          */
@@ -222,8 +222,20 @@
      *
      * @param subId The ID of the subscription that this ImsMmTelManager will use.
      * @see android.telephony.SubscriptionManager#getActiveSubscriptionInfoList()
+     *
+     * <p>Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE
+     * READ_PRECISE_PHONE_STATE} or that the calling app has carrier privileges
+     * (see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}).
+     *
      * @throws IllegalArgumentException if the subscription is invalid.
+     *
      */
+    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+            android.Manifest.permission.READ_PRECISE_PHONE_STATE
+    })
+    @SuppressLint("ManagerLookup")
     public static @NonNull ImsMmTelManager createForSubscriptionId(int subId) {
         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
             throw new IllegalArgumentException("Invalid subscription ID");
@@ -262,8 +274,10 @@
      * reason.
      * @deprecated Use {@link RegistrationManager#registerImsRegistrationCallback(Executor,
      * RegistrationManager.RegistrationCallback)} instead.
+     * @hide
      */
     @Deprecated
+    @SystemApi @TestApi
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public void registerImsRegistrationCallback(@NonNull @CallbackExecutor Executor executor,
             @NonNull RegistrationCallback c) throws ImsException {
@@ -288,9 +302,20 @@
         }
     }
 
-    /**{@inheritDoc}*/
+     /**
+     *
+     * <p>Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE
+     * READ_PRECISE_PHONE_STATE} or that the calling app has carrier privileges
+     * (see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}).
+     *
+     * {@inheritDoc}
+     *
+     */
     @Override
-    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+            android.Manifest.permission.READ_PRECISE_PHONE_STATE})
     public void registerImsRegistrationCallback(@NonNull @CallbackExecutor Executor executor,
             @NonNull RegistrationManager.RegistrationCallback c) throws ImsException {
         if (c == null) {
@@ -321,8 +346,10 @@
      * @see #registerImsRegistrationCallback(Executor, RegistrationCallback)
      * @deprecated Use {@link #unregisterImsRegistrationCallback(
      * RegistrationManager.RegistrationCallback)}.
+     * @hide
      */
     @Deprecated
+    @SystemApi @TestApi
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public void unregisterImsRegistrationCallback(@NonNull RegistrationCallback c) {
         if (c == null) {
@@ -335,9 +362,20 @@
         }
     }
 
-    /**{@inheritDoc}*/
+     /**
+     *
+     * <p>Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE
+     * READ_PRECISE_PHONE_STATE} or that the calling app has carrier privileges
+     * (see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}).
+     * Access by profile owners is deprecated and will be removed in a future release.
+     *
+     *{@inheritDoc}
+     */
     @Override
-    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+            android.Manifest.permission.READ_PRECISE_PHONE_STATE})
     public void unregisterImsRegistrationCallback(
             @NonNull RegistrationManager.RegistrationCallback c) {
         if (c == null) {
@@ -350,9 +388,13 @@
         }
     }
 
-    /**{@inheritDoc}*/
+    /**
+     * {@inheritDoc}
+     * @hide
+     */
     @Override
-    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @SystemApi @TestApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public void getRegistrationState(@NonNull @CallbackExecutor Executor executor,
             @NonNull @ImsRegistrationState Consumer<Integer> stateCallback) {
         if (stateCallback == null) {
@@ -373,9 +415,19 @@
         }
     }
 
-    /**{@inheritDoc}*/
+    /**
+     * <p>Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE
+     * READ_PRECISE_PHONE_STATE} or that the calling app has carrier privileges
+     * (see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}).
+     * Access by profile owners is deprecated and will be removed in a future release.
+     *
+     *{@inheritDoc}
+     */
     @Override
-    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+            android.Manifest.permission.READ_PRECISE_PHONE_STATE})
     public void getRegistrationTransportType(@NonNull @CallbackExecutor Executor executor,
             @NonNull @AccessNetworkConstants.TransportType
                     Consumer<Integer> transportTypeCallback) {
@@ -401,12 +453,25 @@
     /**
      * Registers a {@link CapabilityCallback} with the system, which will provide MmTel service
      * availability updates for the subscription specified in
-     * {@link #createForSubscriptionId(int)}. The method {@link #isAvailable(int, int)}
+     * {@link #createForSubscriptionId(int)}. The method {@see #isAvailable(int, int)}
      * can also be used to query this information at any time.
      *
      * Use {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to
      * subscription changed events and call
      * {@link #unregisterMmTelCapabilityCallback(CapabilityCallback)} to clean up.
+     * <p>This API requires one of the following:
+     * <ul>
+     *     <li>The caller holds the READ_PRECISE_PHONE_STATE permission.</li>
+     *     <li>If the caller is the device or profile owner, the caller holds the
+     *     {@link Manifest.permission#READ_PRECISE_PHONE_STATE} permission.</li>
+     *     <li>The caller has carrier privileges (see
+     *     {@link android.telephony.TelephonyManager#hasCarrierPrivileges}) on any
+     *     active subscription.</li>
+     *     <li>The caller is the default SMS app for the device.</li>
+     * </ul>
+     * <p>The profile owner is an app that owns a managed profile on the device; for more details
+     * see <a href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
+     * Access by profile owners is deprecated and will be removed in a future release.
      *
      * When the callback is registered, it will initiate the callback c to be called with the
      * current capabilities.
@@ -422,7 +487,10 @@
      * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
      * reason.
      */
-    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+            android.Manifest.permission.READ_PRECISE_PHONE_STATE})
     public void registerMmTelCapabilityCallback(@NonNull @CallbackExecutor Executor executor,
             @NonNull CapabilityCallback c) throws ImsException {
         if (c == null) {
@@ -454,10 +522,27 @@
      * When the subscription associated with this callback is removed (SIM removed, ESIM swap,
      * etc...), this callback will automatically be removed. If this method is called for an
      * inactive subscription, it will result in a no-op.
+     * <p>This API requires one of the following:
+     * <ul>
+     *     <li>The caller holds the READ_PRECISE_PHONE_STATE permission.</li>
+     *     <li>If the caller is the device or profile owner, the caller holds the
+     *     {@link Manifest.permission#READ_PRECISE_PHONE_STATE} permission.</li>
+     *     <li>The caller has carrier privileges (see
+     *     {@link android.telephony.TelephonyManager#hasCarrierPrivileges}) on any
+     *     active subscription.</li>
+     *     <li>The caller is the default SMS app for the device.</li>
+     * </ul>
+     * <p>The profile owner is an app that owns a managed profile on the device; for more details
+     * see <a href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
+     * Access by profile owners is deprecated and will be removed in a future release.
+     *
      * @param c The MmTel {@link CapabilityCallback} to be removed.
      * @see #registerMmTelCapabilityCallback(Executor, CapabilityCallback)
      */
-    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+            android.Manifest.permission.READ_PRECISE_PHONE_STATE})
     public void unregisterMmTelCapabilityCallback(@NonNull CapabilityCallback c) {
         if (c == null) {
             throw new IllegalArgumentException("Must include a non-null RegistrationCallback.");
@@ -479,6 +564,19 @@
      * <p>
      * Note: If the carrier configuration for advanced calling is not editable or hidden, this
      * method will always return the default value.
+     * <p>This API requires one of the following:
+     * <ul>
+     *     <li>The caller holds the READ_PRECISE_PHONE_STATE permission.</li>
+     *     <li>If the caller is the device or profile owner, the caller holds the
+     *     {@link Manifest.permission#READ_PRECISE_PHONE_STATE} permission.</li>
+     *     <li>The caller has carrier privileges (see
+     *     {@link android.telephony.TelephonyManager#hasCarrierPrivileges}) on any
+     *     active subscription.</li>
+     *     <li>The caller is the default SMS app for the device.</li>
+     * </ul>
+     * <p>The profile owner is an app that owns a managed profile on the device; for more details
+     * see <a href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
+     * Access by profile owners is deprecated and will be removed in a future release.
      *
      * @see android.telephony.CarrierConfigManager#KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL
      * @see android.telephony.CarrierConfigManager#KEY_EDITABLE_ENHANCED_4G_LTE_BOOL
@@ -490,7 +588,10 @@
      * active (SIM is not inserted, ESIM inactive) or invalid.
      * @return true if the user's setting for advanced calling is enabled, false otherwise.
      */
-    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+            android.Manifest.permission.READ_PRECISE_PHONE_STATE})
     public boolean isAdvancedCallingSettingEnabled() {
         try {
             return getITelephony().isAdvancedCallingSettingEnabled(mSubId);
@@ -528,8 +629,10 @@
      * @see #isAdvancedCallingSettingEnabled()
      * @throws IllegalArgumentException if the subscription associated with this operation is not
      * active (SIM is not inserted, ESIM inactive) or invalid.
+     * @hide
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    @SystemApi @TestApi
     public void setAdvancedCallingSettingEnabled(boolean isEnabled) {
         try {
             getITelephony().setAdvancedCallingSettingEnabled(mSubId, isEnabled);
@@ -560,13 +663,15 @@
      *         {@link ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN}
      * @param capability The IMS MmTel capability to query, can be one of the following:
      *         {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE},
-     *         {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO,
+     *         {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO},
      *         {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_UT},
      *         {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_SMS}
      * @return {@code true} if the MmTel IMS capability is capable for this subscription, false
      *         otherwise.
+     * @hide
      */
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @SystemApi @TestApi
     public boolean isCapable(@MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
             @ImsRegistrationImplBase.ImsRegistrationTech int imsRegTech) {
         try {
@@ -587,12 +692,14 @@
      *         {@link ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN}
      * @param capability The IMS MmTel capability to query, can be one of the following:
      *         {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE},
-     *         {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO,
+     *         {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO},
      *         {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_UT},
      *         {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_SMS}
      * @return {@code true} if the MmTel IMS capability is available for this subscription, false
      *         otherwise.
+     * @hide
      */
+    @SystemApi @TestApi
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public boolean isAvailable(@MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
             @ImsRegistrationImplBase.ImsRegistrationTech int imsRegTech) {
@@ -617,7 +724,9 @@
      *                 capability is supported on this carrier network for the transport specified.
      * @throws ImsException if the subscription is no longer valid or the IMS service is not
      * available.
+     * @hide
      */
+    @SystemApi @TestApi
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public void isSupported(@MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
             @AccessNetworkConstants.TransportType int transportType,
@@ -646,12 +755,32 @@
     /**
      * The user's setting for whether or not they have enabled the "Video Calling" setting.
      *
+     * <p>
+     * Note: If the carrier configuration for advanced calling is not editable or hidden, this
+     * method will always return the default value.
+     * <p>This API requires one of the following:
+     * <ul>
+     *     <li>The caller holds the READ_PRECISE_PHONE_STATE permission.</li>
+     *     <li>If the caller is the device or profile owner, the caller holds the
+     *     {@link Manifest.permission#READ_PRECISE_PHONE_STATE} permission.</li>
+     *     <li>The caller has carrier privileges (see
+     *     {@link android.telephony.TelephonyManager#hasCarrierPrivileges}) on any
+     *     active subscription.</li>
+     *     <li>The caller is the default SMS app for the device.</li>
+     * </ul>
+     * <p>The profile owner is an app that owns a managed profile on the device; for more details
+     * see <a href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
+     * Access by profile owners is deprecated and will be removed in a future release.
+     *
      * @throws IllegalArgumentException if the subscription associated with this operation is not
      * active (SIM is not inserted, ESIM inactive) or invalid.
      * @return true if the user’s “Video Calling” setting is currently enabled.
      * @see #setVtSettingEnabled(boolean)
      */
-    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+            android.Manifest.permission.READ_PRECISE_PHONE_STATE})
+    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
     public boolean isVtSettingEnabled() {
         try {
             return getITelephony().isVtSettingEnabled(mSubId);
@@ -673,7 +802,9 @@
      * @throws IllegalArgumentException if the subscription associated with this operation is not
      * active (SIM is not inserted, ESIM inactive) or invalid.
      * @see #isVtSettingEnabled()
+     * @hide
      */
+    @SystemApi @TestApi
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public void setVtSettingEnabled(boolean isEnabled) {
         try {
@@ -693,11 +824,28 @@
     /**
      * @return true if the user's setting for Voice over WiFi is enabled and false if it is not.
      *
+     * <p>This API requires one of the following:
+     * <ul>
+     *     <li>The caller holds the READ_PRECISE_PHONE_STATE permission.</li>
+     *     <li>If the caller is the device or profile owner, the caller holds the
+     *     {@link Manifest.permission#READ_PRECISE_PHONE_STATE} permission.</li>
+     *     <li>The caller has carrier privileges (see
+     *     {@link android.telephony.TelephonyManager#hasCarrierPrivileges}) on any
+     *     active subscription.</li>
+     *     <li>The caller is the default SMS app for the device.</li>
+     * </ul>
+     * <p>The profile owner is an app that owns a managed profile on the device; for more details
+     * see <a href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
+     * Access by profile owners is deprecated and will be removed in a future release.
+     *
      * @throws IllegalArgumentException if the subscription associated with this operation is not
      * active (SIM is not inserted, ESIM inactive) or invalid.
      * @see #setVoWiFiSettingEnabled(boolean)
      */
-    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+            android.Manifest.permission.READ_PRECISE_PHONE_STATE})
     public boolean isVoWiFiSettingEnabled() {
         try {
             return getITelephony().isVoWiFiSettingEnabled(mSubId);
@@ -720,7 +868,9 @@
      * active (SIM is not inserted, ESIM inactive) or invalid.
      * @param isEnabled true if the user's setting for Voice over WiFi is enabled, false otherwise=
      * @see #isVoWiFiSettingEnabled()
+     * @hide
      */
+    @SystemApi @TestApi
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public void setVoWiFiSettingEnabled(boolean isEnabled) {
         try {
@@ -740,13 +890,30 @@
     /**
      * Returns the user's voice over WiFi roaming setting associated with the current subscription.
      *
+     * <p>This API requires one of the following:
+     * <ul>
+     *     <li>The caller holds the READ_PRECISE_PHONE_STATE permission.</li>
+     *     <li>If the caller is the device or profile owner, the caller holds the
+     *     {@link Manifest.permission#READ_PRECISE_PHONE_STATE} permission.</li>
+     *     <li>The caller has carrier privileges (see
+     *     {@link android.telephony.TelephonyManager#hasCarrierPrivileges}) on any
+     *     active subscription.</li>
+     *     <li>The caller is the default SMS app for the device.</li>
+     * </ul>
+     * <p>The profile owner is an app that owns a managed profile on the device; for more details
+     * see <a href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
+     * Access by profile owners is deprecated and will be removed in a future release.
+     *
      * @throws IllegalArgumentException if the subscription associated with this operation is not
      * active (SIM is not inserted, ESIM inactive) or invalid.
      * @return true if the user's setting for Voice over WiFi while roaming is enabled, false
      * if disabled.
      * @see #setVoWiFiRoamingSettingEnabled(boolean)
      */
-    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+            android.Manifest.permission.READ_PRECISE_PHONE_STATE})
     public boolean isVoWiFiRoamingSettingEnabled() {
         try {
             return getITelephony().isVoWiFiRoamingSettingEnabled(mSubId);
@@ -770,7 +937,9 @@
      * @throws IllegalArgumentException if the subscription associated with this operation is not
      * active (SIM is not inserted, ESIM inactive) or invalid.
      * @see #isVoWiFiRoamingSettingEnabled()
+     * @hide
      */
+    @SystemApi @TestApi
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public void setVoWiFiRoamingSettingEnabled(boolean isEnabled) {
         try {
@@ -800,7 +969,9 @@
      * @throws IllegalArgumentException if the subscription associated with this operation is not
      * active (SIM is not inserted, ESIM inactive) or invalid.
      * @see #setVoWiFiSettingEnabled(boolean)
+     * @hide
      */
+    @SystemApi @TestApi
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public void setVoWiFiNonPersistent(boolean isCapable, int mode) {
         try {
@@ -820,6 +991,20 @@
     /**
      * Returns the user's voice over WiFi Roaming mode setting associated with the device.
      *
+     * <p>This API requires one of the following:
+     * <ul>
+     *     <li>The caller holds the READ_PRECISE_PHONE_STATE permission.</li>
+     *     <li>If the caller is the device or profile owner, the caller holds the
+     *     {@link Manifest.permission#READ_PRECISE_PHONE_STATE} permission.</li>
+     *     <li>The caller has carrier privileges (see
+     *     {@link android.telephony.TelephonyManager#hasCarrierPrivileges}) on any
+     *     active subscription.</li>
+     *     <li>The caller is the default SMS app for the device.</li>
+     * </ul>
+     * <p>The profile owner is an app that owns a managed profile on the device; for more details
+     * see <a href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
+     * Access by profile owners is deprecated and will be removed in a future release.
+     *
      * @throws IllegalArgumentException if the subscription associated with this operation is not
      * active (SIM is not inserted, ESIM inactive) or invalid.
      * @return The Voice over WiFi Mode preference set by the user, which can be one of the
@@ -829,7 +1014,10 @@
      * - {@link #WIFI_MODE_WIFI_PREFERRED}
      * @see #setVoWiFiSettingEnabled(boolean)
      */
-    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+            android.Manifest.permission.READ_PRECISE_PHONE_STATE})
     public @WiFiCallingMode int getVoWiFiModeSetting() {
         try {
             return getITelephony().getVoWiFiModeSetting(mSubId);
@@ -855,7 +1043,9 @@
      * @throws IllegalArgumentException if the subscription associated with this operation is not
      * active (SIM is not inserted, ESIM inactive) or invalid.
      * @see #getVoWiFiModeSetting()
+     * @hide
      */
+    @SystemApi @TestApi
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public void setVoWiFiModeSetting(@WiFiCallingMode int mode) {
         try {
@@ -884,7 +1074,9 @@
      * @throws IllegalArgumentException if the subscription associated with this operation is not
      * active (SIM is not inserted, ESIM inactive) or invalid.
      * @see #setVoWiFiRoamingSettingEnabled(boolean)
+     * @hide
      */
+    @SystemApi @TestApi
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public @WiFiCallingMode int getVoWiFiRoamingModeSetting() {
         try {
@@ -913,7 +1105,9 @@
      * @throws IllegalArgumentException if the subscription associated with this operation is not
      * active (SIM is not inserted, ESIM inactive) or invalid.
      * @see #getVoWiFiRoamingModeSetting()
+     * @hide
      */
+    @SystemApi @TestApi
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public void setVoWiFiRoamingModeSetting(@WiFiCallingMode int mode) {
         try {
@@ -940,7 +1134,9 @@
      * @throws IllegalArgumentException if the subscription associated with this operation is not
      * active (SIM is not inserted, ESIM inactive) or invalid.
      * @param isEnabled if true RTT should be enabled during calls made on this subscription.
+     * @hide
      */
+    @SystemApi @TestApi
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public void setRttCapabilitySetting(boolean isEnabled) {
         try {
@@ -960,12 +1156,29 @@
     /**
      * @return true if TTY over VoLTE is supported
      *
+     * <p>This API requires one of the following:
+     * <ul>
+     *     <li>The caller holds the READ_PRECISE_PHONE_STATE permission.</li>
+     *     <li>If the caller is the device or profile owner, the caller holds the
+     *     {@link Manifest.permission#READ_PRECISE_PHONE_STATE} permission.</li>
+     *     <li>The caller has carrier privileges (see
+     *     {@link android.telephony.TelephonyManager#hasCarrierPrivileges}) on any
+     *     active subscription.</li>
+     *     <li>The caller is the default SMS app for the device.</li>
+     * </ul>
+     * <p>The profile owner is an app that owns a managed profile on the device; for more details
+     * see <a href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
+     * Access by profile owners is deprecated and will be removed in a future release.
+     *
      * @throws IllegalArgumentException if the subscription associated with this operation is not
      * active (SIM is not inserted, ESIM inactive) or invalid.
      * @see android.telecom.TelecomManager#getCurrentTtyMode
      * @see android.telephony.CarrierConfigManager#KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL
      */
-    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+            android.Manifest.permission.READ_PRECISE_PHONE_STATE})
     public boolean isTtyOverVolteEnabled() {
         try {
             return getITelephony().isTtyOverVolteEnabled(mSubId);
@@ -992,7 +1205,9 @@
      *                 specified when the service state has been retrieved from the IMS service.
      * @throws ImsException if the IMS service associated with this subscription is not available or
      * the IMS service is not available.
+     * @hide
      */
+    @SystemApi @TestApi
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public void getFeatureState(@NonNull @CallbackExecutor Executor executor,
             @NonNull @ImsFeature.ImsState Consumer<Integer> callback) throws ImsException {
@@ -1018,7 +1233,10 @@
 
     private static ITelephony getITelephony() {
         ITelephony binder = ITelephony.Stub.asInterface(
-                ServiceManager.getService(Context.TELEPHONY_SERVICE));
+                TelephonyFrameworkInitializer
+                        .getTelephonyServiceManager()
+                        .getTelephonyServiceRegisterer()
+                        .get());
         if (binder == null) {
             throw new RuntimeException("Could not find Telephony Service.");
         }
diff --git a/telephony/java/android/telephony/ims/ImsRcsManager.java b/telephony/java/android/telephony/ims/ImsRcsManager.java
index d3fb37f..d483291 100644
--- a/telephony/java/android/telephony/ims/ImsRcsManager.java
+++ b/telephony/java/android/telephony/ims/ImsRcsManager.java
@@ -26,8 +26,9 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.telephony.AccessNetworkConstants;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyFrameworkInitializer;
 import android.telephony.ims.aidl.IImsCapabilityCallback;
 import android.telephony.ims.aidl.IImsRcsController;
 import android.telephony.ims.feature.ImsFeature;
@@ -35,6 +36,8 @@
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.util.Log;
 
+import com.android.internal.telephony.IIntegerConsumer;
+
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
@@ -158,9 +161,20 @@
         if (executor == null) {
             throw new IllegalArgumentException("Must include a non-null Executor.");
         }
+
+        IImsRcsController imsRcsController = getIImsRcsController();
+        if (imsRcsController == null) {
+            Log.e(TAG, "Register registration callback: IImsRcsController is null");
+            throw new ImsException("Cannot find remote IMS service",
+                    ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+        }
+
         c.setExecutor(executor);
-        throw new UnsupportedOperationException("registerImsRegistrationCallback is not"
-                + "supported.");
+        try {
+            imsRcsController.registerImsRegistrationCallback(mSubId, c.getBinder());
+        } catch (RemoteException | IllegalStateException e) {
+            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+        }
     }
 
     /**{@inheritDoc}*/
@@ -171,8 +185,18 @@
         if (c == null) {
             throw new IllegalArgumentException("Must include a non-null RegistrationCallback.");
         }
-        throw new UnsupportedOperationException("unregisterImsRegistrationCallback is not"
-                + "supported.");
+
+        IImsRcsController imsRcsController = getIImsRcsController();
+        if (imsRcsController == null) {
+            Log.e(TAG, "Unregister registration callback: IImsRcsController is null");
+            throw new IllegalStateException("Cannot find remote IMS service");
+        }
+
+        try {
+            imsRcsController.unregisterImsRegistrationCallback(mSubId, c.getBinder());
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
     }
 
     /**{@inheritDoc}*/
@@ -186,8 +210,23 @@
         if (executor == null) {
             throw new IllegalArgumentException("Must include a non-null Executor.");
         }
-        throw new UnsupportedOperationException("getRegistrationState is not"
-                + "supported.");
+
+        IImsRcsController imsRcsController = getIImsRcsController();
+        if (imsRcsController == null) {
+            Log.e(TAG, "Get registration state error: IImsRcsController is null");
+            throw new IllegalStateException("Cannot find remote IMS service");
+        }
+
+        try {
+            imsRcsController.getImsRcsRegistrationState(mSubId, new IIntegerConsumer.Stub() {
+                @Override
+                public void accept(int result) {
+                    executor.execute(() -> stateCallback.accept(result));
+                }
+            });
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
     }
 
     /**{@inheritDoc}*/
@@ -202,10 +241,25 @@
         if (executor == null) {
             throw new IllegalArgumentException("Must include a non-null Executor.");
         }
-        throw new UnsupportedOperationException("getRegistrationTransportType is not"
-                + "supported.");
-    }
 
+        IImsRcsController imsRcsController = getIImsRcsController();
+        if (imsRcsController == null) {
+            Log.e(TAG, "Get registration transport type error: IImsRcsController is null");
+            throw new IllegalStateException("Cannot find remote IMS service");
+        }
+
+        try {
+            imsRcsController.getImsRcsRegistrationTransportType(mSubId,
+                    new IIntegerConsumer.Stub() {
+                        @Override
+                        public void accept(int result) {
+                            executor.execute(() -> transportTypeCallback.accept(result));
+                        }
+                    });
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
 
     /**
      * Registers an {@link AvailabilityCallback} with the system, which will provide RCS
@@ -362,7 +416,10 @@
     }
 
     private IImsRcsController getIImsRcsController() {
-        IBinder binder = ServiceManager.getService(Context.TELEPHONY_IMS_SERVICE);
+        IBinder binder = TelephonyFrameworkInitializer
+                .getTelephonyServiceManager()
+                .getTelephonyImsServiceRegisterer()
+                .get();
         return IImsRcsController.Stub.asInterface(binder);
     }
 }
diff --git a/telephony/java/android/telephony/ims/ImsSsData.java b/telephony/java/android/telephony/ims/ImsSsData.java
index 6b72859..2d2e638 100644
--- a/telephony/java/android/telephony/ims/ImsSsData.java
+++ b/telephony/java/android/telephony/ims/ImsSsData.java
@@ -22,7 +22,7 @@
 import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
index e4d6335..0370846 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -25,16 +25,15 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.annotation.WorkerThread;
-import android.content.Context;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
 import android.os.Binder;
 import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyFrameworkInitializer;
 import android.telephony.ims.aidl.IImsConfigCallback;
 import android.telephony.ims.feature.MmTelFeature;
+import android.telephony.ims.feature.RcsFeature;
 import android.telephony.ims.stub.ImsConfigImplBase;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 
@@ -85,6 +84,11 @@
             "STRING_QUERY_RESULT_ERROR_NOT_READY";
 
     /**
+     * There is no existing configuration for the queried provisioning key.
+     */
+    public static final int PROVISIONING_RESULT_UNKNOWN = -1;
+
+    /**
      * The integer result of provisioning for the queried key is disabled.
      */
     public static final int PROVISIONING_VALUE_DISABLED = 0;
@@ -95,6 +99,313 @@
     public static final int PROVISIONING_VALUE_ENABLED = 1;
 
 
+    // Inheriting values from ImsConfig for backwards compatibility.
+    /**
+     * AMR CODEC Mode Value set, 0-7 in comma separated sequence.
+     * <p>
+     * This corresponds to the {@code mode-set} parameter for the AMR codec.
+     * See 3GPP TS 26.101 Table 1A for more information.
+     * <p>
+     * <UL>
+     *     <LI>0 - AMR 4.75 kbit/s</LI>
+     *     <LI>1 - AMR 5.15 kbit/s</LI>
+     *     <LI>2 - AMR 5.90 kbit/s</LI>
+     *     <LI>3 - AMR 6.70 kbit/s (PDC-EFR)</LI>
+     *     <LI>4 - AMR 7.40 kbit/s (TDMA-EFR)</LI>
+     *     <LI>5 - AMR 7.95 kbit/s</LI>
+     *     <LI>6 - AMR 10.2 kbit/s</LI>
+     *     <LI>7 - AMR 12.2 kbit/s (GSM-EFR)</LI>
+     * </UL>
+     * <p>
+     * Value is in String format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_AMR_CODEC_MODE_SET_VALUES = 0;
+
+    /**
+     * Wide Band AMR CODEC Mode Value set,0-7 in comma separated sequence.
+     * <p>
+     * This corresponds to the {@code mode-set} parameter for the AMR wideband codec.
+     * See 3GPP TS 26.101 Table 1A for more information.
+     * <p>
+     * <UL>
+     *     <LI>0 - AMR 4.75 kbit/s</LI>
+     *     <LI>1 - AMR 5.15 kbit/s</LI>
+     *     <LI>2 - AMR 5.90 kbit/s</LI>
+     *     <LI>3 - AMR 6.70 kbit/s (PDC-EFR)</LI>
+     *     <LI>4 - AMR 7.40 kbit/s (TDMA-EFR)</LI>
+     *     <LI>5 - AMR 7.95 kbit/s</LI>
+     *     <LI>6 - AMR 10.2 kbit/s</LI>
+     *     <LI>7 - AMR 12.2 kbit/s (GSM-EFR)</LI>
+     * </UL>
+     * <p>
+     * Value is in String format.
+     * @see #setProvisioningStringValue(int, String)
+     * @see #getProvisioningStringValue(int)
+     */
+    public static final int KEY_AMR_WB_CODEC_MODE_SET_VALUES = 1;
+
+    /**
+     * SIP Session Timer value (seconds).
+     * <p>
+     * See RFC4028 for more information.
+     * <p>
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_SIP_SESSION_TIMER_SEC = 2;
+
+    /**
+     * Minimum SIP Session Expiration Timer in (seconds).
+     * <p>
+     * See RFC4028 for more information.
+     * <p>
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_MINIMUM_SIP_SESSION_EXPIRATION_TIMER_SEC = 3;
+
+    /**
+     * SIP_INVITE cancellation time out value (in milliseconds). Integer format.
+     * <p>
+     * See RFC4028 for more information.
+     * <p>
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_SIP_INVITE_CANCELLATION_TIMER_MS = 4;
+
+    /**
+     * Delay time when an iRAT transitions from eHRPD/HRPD/1xRTT to LTE.
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_TRANSITION_TO_LTE_DELAY_MS = 5;
+
+    /**
+     * Silent redial status of Enabled (True), or Disabled (False).
+     * Value is in boolean format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_ENABLE_SILENT_REDIAL = 6;
+
+    /**
+     * An integer key representing the SIP T1 timer value in milliseconds for the associated
+     * subscription.
+     * <p>
+     * The SIP T1 timer is an estimate of the round-trip time and will retransmit
+     * INVITE transactions that are longer than T1 milliseconds over unreliable transports, doubling
+     * the time before retransmission every time there is no response. See RFC3261, section 17.1.1.1
+     * for more details.
+     * <p>
+     * The value is an integer.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_T1_TIMER_VALUE_MS = 7;
+
+    /**
+     * SIP T2 timer value in milliseconds.  See RFC 3261 for information.
+     * <p>
+     * The T2 timer is the maximum retransmit interval for non-INVITE requests and INVITE responses.
+     * <p>
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_T2_TIMER_VALUE_MS = 8;
+
+    /**
+     * SIP TF timer value in milliseconds.  See RFC 3261 for information.
+     * <p>
+     * The TF timer is the non-INVITE transaction timeout timer.
+     * <p>
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_TF_TIMER_VALUE_MS = 9;
+
+    /**
+     * An integer key representing the voice over LTE (VoLTE) provisioning status for the
+     * associated subscription. Determines whether the user can register for voice services over
+     * LTE.
+     * <p>
+     * Use {@link #PROVISIONING_VALUE_ENABLED} to enable VoLTE provisioning and
+     * {@link #PROVISIONING_VALUE_DISABLED} to disable VoLTE provisioning.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_VOLTE_PROVISIONING_STATUS = 10;
+
+    /**
+     * An integer key representing the video telephony (VT) provisioning status for the
+     * associated subscription. Determines whether the user can register for video services over
+     * LTE.
+     * <p>
+     * Use {@link #PROVISIONING_VALUE_ENABLED} to enable VT provisioning and
+     * {@link #PROVISIONING_VALUE_DISABLED} to disable VT provisioning.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_VT_PROVISIONING_STATUS = 11;
+
+    /**
+     * Domain Name for the device to populate the request URI for REGISTRATION.
+     * Value is in String format.
+     * @see #setProvisioningStringValue(int, String)
+     * @see #getProvisioningStringValue(int)
+     */
+    public static final int KEY_REGISTRATION_DOMAIN_NAME = 12;
+
+    /**
+     * Device Outgoing SMS based on either 3GPP or 3GPP2 standards.
+     * Value is in Integer format.
+     * Valid values are {@link #SMS_FORMAT_3GPP} and {@link #SMS_FORMAT_3GPP2}.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_SMS_FORMAT = 13;
+
+    /**
+     * Value used with {@link #KEY_SMS_FORMAT} to indicate 3GPP2 SMS format is used.
+     * See {@link android.telephony.SmsMessage#FORMAT_3GPP2} for more information.
+     */
+    public static final int SMS_FORMAT_3GPP2 = 0;
+
+    /**
+     * Value used with {@link #KEY_SMS_FORMAT} to indicate 3GPP SMS format is used.
+     * See {@link android.telephony.SmsMessage#FORMAT_3GPP} for more information.
+     */
+    public static final int SMS_FORMAT_3GPP = 1;
+
+    /**
+     * Turns SMS over IMS ON/OFF on the device.
+     * Value is in Integer format. ON (1), OFF(0).
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_SMS_OVER_IP_ENABLED = 14;
+
+    /**
+     * An integer key associated with the carrier configured SIP PUBLISH timer, which dictates the
+     * expiration time in seconds for published online availability in RCS presence.
+     * <p>
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_RCS_PUBLISH_TIMER_SEC = 15;
+
+    /**
+     * An integer key associated with the carrier configured expiration time in seconds for
+     * RCS presence published offline availability in RCS presence.
+     * <p>
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC = 16;
+
+    /**
+     * An integer key associated with whether or not capability discovery is provisioned for this
+     * subscription. Any capability requests will be ignored by the RCS service.
+     * <p>
+     * The value is an integer, either {@link #PROVISIONING_VALUE_DISABLED} if capability
+     * discovery is disabled or {@link #PROVISIONING_VALUE_ENABLED} if capability discovery is
+     * enabled.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_RCS_CAPABILITY_DISCOVERY_ENABLED = 17;
+
+    /**
+     * An integer key associated with the period of time the capability information of each contact
+     * is cached on the device.
+     * <p>
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC = 18;
+
+    /**
+     * An integer key associated with the period of time in seconds that the availability
+     * information of a contact is cached on the device.
+     * <p>
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC = 19;
+
+    /**
+     * An integer key associated with the carrier configured interval in seconds expected between
+     * successive capability polling attempts.
+     * <p>
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC = 20;
+
+    /**
+     * An integer key representing the minimum time allowed between two consecutive presence publish
+     * messages from the device.
+     * <p>
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS = 21;
+
+    /**
+     * An integer key associated with the maximum number of MDNs contained in one SIP Request
+     * Contained List (RCS) used to retrieve the RCS capabilities of the contacts book.
+     * <p>
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_RCS_MAX_NUM_ENTRIES_IN_RCL = 22;
+
+    /**
+     * An integer associated with the expiration timer used during the SIP subscription of a
+     * Request Contained List (RCL), which is used to retrieve the RCS capabilities of the contact
+     * book.
+     * <p>
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC = 23;
+
+    /**
+     * Applies compression to LIST Subscription.
+     * Value is in Integer format. Enable (1), Disable(0).
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_USE_GZIP_FOR_LIST_SUBSCRIPTION = 24;
+
+    /**
+     * An integer key representing the RCS enhanced address book (EAB) provisioning status for the
+     * associated subscription. Determines whether or not SIP OPTIONS or presence will be used to
+     * retrieve RCS capabilities for the user's contacts.
+     * <p>
+     * Use {@link #PROVISIONING_VALUE_ENABLED} to enable EAB provisioning and
+     * {@link #PROVISIONING_VALUE_DISABLED} to disable EAB provisioning.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_EAB_PROVISIONING_STATUS = 25;
+
     /**
      * Override the user-defined WiFi Roaming enabled setting for this subscription, defined in
      * {@link SubscriptionManager#WFC_ROAMING_ENABLED_CONTENT_URI}, for the purposes of provisioning
@@ -121,6 +432,349 @@
     public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27;
 
     /**
+     * Enable voice over wifi.  Enabled (1), or Disabled (0).
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE = 28;
+
+    /**
+     * Mobile data enabled.
+     * Value is in Integer format. On (1), OFF(0).
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_MOBILE_DATA_ENABLED = 29;
+
+    /**
+     * VoLTE user opted in status.
+     * Value is in Integer format. Opted-in (1) Opted-out (0).
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_VOLTE_USER_OPT_IN_STATUS = 30;
+
+    /**
+     * Proxy for Call Session Control Function(P-CSCF) address for Local-BreakOut(LBO).
+     * Value is in String format.
+     */
+    public static final int KEY_LOCAL_BREAKOUT_PCSCF_ADDRESS = 31;
+
+    /**
+     * Keep Alive Enabled for SIP.
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_SIP_KEEP_ALIVE_ENABLED = 32;
+
+    /**
+     * Registration retry Base Time value in seconds.
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_REGISTRATION_RETRY_BASE_TIME_SEC = 33;
+
+    /**
+     * Registration retry Max Time value in seconds.
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_REGISTRATION_RETRY_MAX_TIME_SEC = 34;
+
+    /**
+     * Smallest RTP port for speech codec.
+     * Value is in integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+
+    public static final int KEY_RTP_SPEECH_START_PORT = 35;
+
+    /**
+     * Largest RTP port for speech code.
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_RTP_SPEECH_END_PORT = 36;
+
+    /**
+     * SIP Timer A's value in milliseconds. Timer A is the INVITE request retransmit interval (in
+     * milliseconds), for UDP only.
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_SIP_INVITE_REQUEST_TRANSMIT_INTERVAL_MS = 37;
+
+    /**
+     * SIP Timer B's value in milliseconds. Timer B is the wait time for INVITE message to be,
+     * in milliseconds.
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_SIP_INVITE_ACK_WAIT_TIME_MS = 38;
+
+    /**
+     * SIP Timer D's value in milliseconds. Timer D is the wait time for response retransmits of
+     * the invite client transactions, in milliseconds.
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_SIP_INVITE_RESPONSE_RETRANSMIT_WAIT_TIME_MS = 39;
+
+    /**
+     * SIP Timer E's value in milliseconds. Timer E is the value Non-INVITE request retransmit
+     * interval (in milliseconds), for UDP only.
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_SIP_NON_INVITE_REQUEST_RETRANSMIT_INTERVAL_MS = 40;
+
+    /**
+     * SIP Timer F's value in milliseconds. Timer F is the Non-INVITE transaction timeout timer,
+     * in milliseconds.
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_SIP_NON_INVITE_TRANSACTION_TIMEOUT_TIMER_MS = 41;
+
+    /**
+     * SIP Timer G's value in milliseconds. Timer G is the value of INVITE response
+     * retransmit interval.
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_SIP_INVITE_RESPONSE_RETRANSMIT_INTERVAL_MS = 42;
+
+    /**
+     * SIP Timer H's value in milliseconds. Timer H is the value of wait time for
+     * ACK receipt.
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_SIP_ACK_RECEIPT_WAIT_TIME_MS = 43;
+
+    /**
+     * SIP Timer I's value in milliseconds. Timer I is the value of wait time for
+     * ACK retransmits.
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_SIP_ACK_RETRANSMIT_WAIT_TIME_MS = 44;
+
+    /**
+     * SIP Timer J's value in milliseconds. Timer J is the value of wait time for
+     * non-invite request retransmission.
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_SIP_NON_INVITE_REQUEST_RETRANSMISSION_WAIT_TIME_MS = 45;
+
+    /**
+     * SIP Timer K's value in milliseconds. Timer K is the value of wait time for
+     * non-invite response retransmits.
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_SIP_NON_INVITE_RESPONSE_RETRANSMISSION_WAIT_TIME_MS = 46;
+
+    /**
+     * AMR WB octet aligned dynamic payload type.
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_AMR_WB_OCTET_ALIGNED_PAYLOAD_TYPE = 47;
+
+    /**
+     * AMR WB bandwidth efficient payload type.
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_AMR_WB_BANDWIDTH_EFFICIENT_PAYLOAD_TYPE = 48;
+
+    /**
+     * AMR octet aligned dynamic payload type.
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_AMR_OCTET_ALIGNED_PAYLOAD_TYPE = 49;
+
+    /**
+     * AMR bandwidth efficient payload type.
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_AMR_BANDWIDTH_EFFICIENT_PAYLOAD_TYPE = 50;
+
+    /**
+     * DTMF WB payload type.
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_DTMF_WB_PAYLOAD_TYPE = 51;
+
+    /**
+     * DTMF NB payload type.
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_DTMF_NB_PAYLOAD_TYPE = 52;
+
+    /**
+     * AMR Default encoding mode.
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_AMR_DEFAULT_ENCODING_MODE = 53;
+
+    /**
+     * SMS Public Service Identity.
+     * Value is in String format.
+     */
+    public static final int KEY_SMS_PUBLIC_SERVICE_IDENTITY = 54;
+
+    /**
+     * Video Quality - VideoQualityFeatureValuesConstants.
+     * Valid values are: {@link #VIDEO_QUALITY_HIGH} and {@link #VIDEO_QUALITY_LOW}.
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_VIDEO_QUALITY = 55;
+
+    /**
+     * Used with {@link #KEY_VIDEO_QUALITY} to indicate low video quality.
+     */
+    public static final int VIDEO_QUALITY_LOW = 0;
+
+    /**
+     * Used with {@link #KEY_VIDEO_QUALITY} to indicate high video quality.
+     */
+    public static final int VIDEO_QUALITY_HIGH = 1;
+
+    /**
+     * LTE to WIFI handover threshold.
+     * Handover from LTE to WiFi if LTE < THLTE1 and WiFi >= {@link #KEY_WIFI_THRESHOLD_A}.
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_LTE_THRESHOLD_1 = 56;
+
+    /**
+     * WIFI to LTE handover threshold.
+     * Handover from WiFi to LTE if LTE >= {@link #KEY_LTE_THRESHOLD_3} or (WiFi < {@link
+     * #KEY_WIFI_THRESHOLD_B} and LTE >= {@link #KEY_LTE_THRESHOLD_2}).
+     * Value is in Integer format.
+     *
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_LTE_THRESHOLD_2 = 57;
+
+    /**
+     * LTE to WIFI handover threshold.
+     * Handover from WiFi to LTE if LTE >= {@link #KEY_LTE_THRESHOLD_3} or (WiFi < {@link
+     * #KEY_WIFI_THRESHOLD_B} and LTE >= {@link #KEY_LTE_THRESHOLD_2}).
+     * Value is in Integer format.
+     *
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_LTE_THRESHOLD_3 = 58;
+
+    /**
+     * 1x to WIFI handover threshold.
+     * Handover from 1x to WiFi if 1x < {@link #KEY_1X_THRESHOLD}.
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_1X_THRESHOLD = 59;
+
+    /**
+     * LTE to WIFI threshold A.
+     * Handover from LTE to WiFi if LTE < {@link #KEY_LTE_THRESHOLD_1} and WiFi >= {@link
+     * #KEY_WIFI_THRESHOLD_A}.
+     * Value is in Integer format.
+     *
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_WIFI_THRESHOLD_A = 60;
+
+    /**
+     * WiFi to LTRE handover threshold B.
+     * Handover from WiFi to LTE if LTE >= {@link #KEY_LTE_THRESHOLD_3} or (WiFi <
+     * {@link #KEY_WIFI_THRESHOLD_B} and LTE >= {@link #KEY_LTE_THRESHOLD_2}).
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_WIFI_THRESHOLD_B = 61;
+
+    /**
+     * LTE ePDG timer (in seconds).
+     * Device shall not handover back to LTE until the T_ePDG_LTE timer expires.
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_LTE_EPDG_TIMER_SEC = 62;
+
+    /**
+     * WiFi ePDG timer (in seconds).
+     * Device shall not handover back to WiFi until the T_ePDG_WiFi timer expires.
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_WIFI_EPDG_TIMER_SEC = 63;
+
+    /**
+     * 1x ePDG timer (in seconds).
+     * Device shall not re-register on 1x until the T_ePDG_1x timer expires.
+     */
+    public static final int KEY_1X_EPDG_TIMER_SEC = 64;
+
+    /**
+     * MultiEndpoint status: Enabled (1), or Disabled (0).
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_MULTIENDPOINT_ENABLED = 65;
+
+    /**
+     * RTT status: Enabled (1), or Disabled (0).
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_RTT_ENABLED = 66;
+
+    /**
      * Callback for IMS provisioning changes.
      */
     public static class Callback {
@@ -232,13 +886,11 @@
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public void registerProvisioningChangedCallback(@NonNull @CallbackExecutor Executor executor,
             @NonNull Callback callback) throws ImsException {
-        if (!isImsAvailableOnDevice()) {
-            throw new ImsException("IMS not available on device.",
-                    ImsException.CODE_ERROR_UNSUPPORTED_OPERATION);
-        }
         callback.setExecutor(executor);
         try {
             getITelephony().registerImsProvisioningChangedCallback(mSubId, callback.getBinder());
+        } catch (ServiceSpecificException e) {
+            throw new ImsException(e.getMessage(), e.errorCode);
         } catch (RemoteException | IllegalStateException e) {
             throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
         }
@@ -270,7 +922,7 @@
      *
      * @param key An integer that represents the provisioning key, which is defined by the OEM.
      * @return an integer value for the provided key, or
-     * {@link ImsConfigImplBase#CONFIG_RESULT_UNKNOWN} if the key doesn't exist.
+     * {@link #PROVISIONING_RESULT_UNKNOWN} if the key doesn't exist.
      * @throws IllegalArgumentException if the key provided was invalid.
      */
     @WorkerThread
@@ -397,42 +1049,83 @@
     }
 
     /**
+     * Get the provisioning status for the IMS RCS capability specified.
+     *
+     * If provisioning is not required for the queried
+     * {@link RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag} this method will always return
+     * {@code true}.
+     *
+     * @see CarrierConfigManager#KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL
+     * @return true if the device is provisioned for the capability or does not require
+     * provisioning, false if the capability does require provisioning and has not been
+     * provisioned yet.
+     */
+    @WorkerThread
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public boolean getRcsProvisioningStatusForCapability(
+            @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability) {
+        try {
+            return getITelephony().getRcsProvisioningStatusForCapability(mSubId, capability);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Set the provisioning status for the IMS RCS capability using the specified subscription.
+     *
+     * Provisioning may or may not be required, depending on the carrier configuration. If
+     * provisioning is not required for the carrier associated with this subscription or the device
+     * does not support the capability/technology combination specified, this operation will be a
+     * no-op.
+     *
+     * @see CarrierConfigManager#KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL
+     * @param isProvisioned true if the device is provisioned for the RCS capability specified,
+     *                      false otherwise.
+     */
+    @WorkerThread
+    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    public void setRcsProvisioningStatusForCapability(
+            @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability,
+            boolean isProvisioned) {
+        try {
+            getITelephony().setRcsProvisioningStatusForCapability(mSubId, capability,
+                    isProvisioned);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
      * Notify the framework that an RCS autoconfiguration XML file has been received for
      * provisioning.
+     * <p>
+     * Requires Permission: Manifest.permission.MODIFY_PHONE_STATE or that the calling app has
+     * carrier privileges (see {@link #hasCarrierPrivileges}).
      * @param config The XML file to be read. ASCII/UTF8 encoded text if not compressed.
      * @param isCompressed The XML file is compressed in gzip format and must be decompressed
      *         before being read.
-     * @hide
+     *
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public void notifyRcsAutoConfigurationReceived(@NonNull byte[] config, boolean isCompressed) {
         if (config == null) {
             throw new IllegalArgumentException("Must include a non-null config XML file.");
         }
-        // TODO: Connect to ImsConfigImplBase.
-        throw new UnsupportedOperationException("notifyRcsAutoConfigurationReceived is not"
-                + "supported");
-    }
-
-    private static boolean isImsAvailableOnDevice() {
-        IPackageManager pm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
-        if (pm == null) {
-            // For some reason package manger is not available.. This will fail internally anyways,
-            // so do not throw error and allow.
-            return true;
-        }
         try {
-            return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS, 0);
+            getITelephony().notifyRcsAutoConfigurationReceived(mSubId, config, isCompressed);
         } catch (RemoteException e) {
-            // For some reason package manger is not available.. This will fail internally anyways,
-            // so do not throw error and allow.
+            throw e.rethrowAsRuntimeException();
         }
-        return true;
+
     }
 
     private static ITelephony getITelephony() {
         ITelephony binder = ITelephony.Stub.asInterface(
-                ServiceManager.getService(Context.TELEPHONY_SERVICE));
+                TelephonyFrameworkInitializer
+                        .getTelephonyServiceManager()
+                        .getTelephonyServiceRegisterer()
+                        .get());
         if (binder == null) {
             throw new RuntimeException("Could not find Telephony Service.");
         }
diff --git a/telephony/java/android/telephony/ims/RcsContactUceCapability.java b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
index 492170b..3e2903f 100644
--- a/telephony/java/android/telephony/ims/RcsContactUceCapability.java
+++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -35,6 +36,7 @@
  * Contains the User Capability Exchange capabilities corresponding to a contact's URI.
  * @hide
  */
+@SystemApi
 public final class RcsContactUceCapability implements Parcelable {
 
     /** Supports 1-to-1 chat */
@@ -81,8 +83,23 @@
     public static final int CAPABILITY_RCS_VOICE_CALL = (1 << 19);
     /** Supports RCS video calling */
     public static final int CAPABILITY_RCS_VIDEO_CALL = (1 << 20);
-    /** Supports RCS video calling, where video media can not be dropped */
+    /** Supports RCS video calling, where video media can not be dropped. */
     public static final int CAPABILITY_RCS_VIDEO_ONLY_CALL = (1 << 21);
+    /** Supports call composer, where outgoing calls can be enriched with pre-call content.*/
+    public static final int CAPABILITY_CALL_COMPOSER = (1 << 22);
+    /** Supports post call information that is included in the call if the call is missed.*/
+    public static final int CAPABILITY_POST_CALL = (1 << 23);
+    /** Supports sharing a map where the user can draw, share markers, and share their position. */
+    public static final int CAPABILITY_SHARED_MAP = (1 << 24);
+    /** Supports sharing a canvas, where users can draw, add images, and change background colors.*/
+    public static final int CAPABILITY_SHARED_SKETCH = (1 << 25);
+    /** Supports communication with Chatbots. */
+    public static final int CAPABILITY_CHAT_BOT = (1 << 26);
+    /** Supports Chatbot roles. */
+    public static final int CAPABILITY_CHAT_BOT_ROLE = (1 << 27);
+    /** Supports the unidirectional plug-ins framework. */
+    public static final int CAPABILITY_PLUG_IN = (1 << 28);
+
 
     /** @hide*/
     @Retention(RetentionPolicy.SOURCE)
@@ -108,7 +125,14 @@
             CAPABILITY_GEOLOCATION_PULL_FILE_TRANSFER,
             CAPABILITY_RCS_VOICE_CALL,
             CAPABILITY_RCS_VIDEO_CALL,
-            CAPABILITY_RCS_VIDEO_ONLY_CALL
+            CAPABILITY_RCS_VIDEO_ONLY_CALL,
+            CAPABILITY_CALL_COMPOSER,
+            CAPABILITY_POST_CALL,
+            CAPABILITY_SHARED_MAP,
+            CAPABILITY_SHARED_SKETCH,
+            CAPABILITY_CHAT_BOT,
+            CAPABILITY_CHAT_BOT_ROLE,
+            CAPABILITY_PLUG_IN
     })
     public @interface CapabilityFlag {}
 
@@ -135,7 +159,7 @@
          * @param type The capability to map to a service URI that is different from the contact's
          *         URI.
          */
-        public Builder add(@CapabilityFlag int type, @NonNull Uri serviceUri) {
+        public @NonNull Builder add(@CapabilityFlag int type, @NonNull Uri serviceUri) {
             mCapabilities.mCapabilities |= type;
             // Put each of these capabilities into the map separately.
             for (int shift = 0; shift < Integer.SIZE; shift++) {
@@ -157,7 +181,7 @@
          * Add a UCE capability flag that this contact supports.
          * @param type the capability that the contact supports.
          */
-        public Builder add(@CapabilityFlag int type) {
+        public @NonNull Builder add(@CapabilityFlag int type) {
             mCapabilities.mCapabilities |= type;
             return this;
         }
@@ -167,7 +191,7 @@
          * @param extension A string containing a carrier specific service tag that is an extension
          *         of the {@link CapabilityFlag}s that are defined here.
          */
-        public Builder add(@NonNull String extension) {
+        public @NonNull Builder add(@NonNull String extension) {
             mCapabilities.mExtensionTags.add(extension);
             return this;
         }
@@ -175,13 +199,13 @@
         /**
          * @return the constructed instance.
          */
-        public RcsContactUceCapability build() {
+        public @NonNull RcsContactUceCapability build() {
             return mCapabilities;
         }
     }
 
     private final Uri mContactUri;
-    private int mCapabilities;
+    private long mCapabilities;
     private List<String> mExtensionTags = new ArrayList<>();
     private Map<Integer, Uri> mServiceMap = new HashMap<>();
 
@@ -196,7 +220,7 @@
 
     private RcsContactUceCapability(Parcel in) {
         mContactUri = in.readParcelable(Uri.class.getClassLoader());
-        mCapabilities = in.readInt();
+        mCapabilities = in.readLong();
         in.readStringList(mExtensionTags);
         // read mServiceMap as key,value pair
         int mapSize = in.readInt();
@@ -205,7 +229,7 @@
         }
     }
 
-    public static final Creator<RcsContactUceCapability> CREATOR =
+    public static final @NonNull Creator<RcsContactUceCapability> CREATOR =
             new Creator<RcsContactUceCapability>() {
         @Override
         public RcsContactUceCapability createFromParcel(Parcel in) {
@@ -219,9 +243,9 @@
     };
 
     @Override
-    public void writeToParcel(Parcel out, int flags) {
+    public void writeToParcel(@NonNull Parcel out, int flags) {
         out.writeParcelable(mContactUri, 0);
-        out.writeInt(mCapabilities);
+        out.writeLong(mCapabilities);
         out.writeStringList(mExtensionTags);
         // write mServiceMap as key,value pairs
         int mapSize = mServiceMap.keySet().size();
diff --git a/telephony/java/android/telephony/ims/RcsControllerCall.java b/telephony/java/android/telephony/ims/RcsControllerCall.java
index ce03c3c..1e93437 100644
--- a/telephony/java/android/telephony/ims/RcsControllerCall.java
+++ b/telephony/java/android/telephony/ims/RcsControllerCall.java
@@ -18,7 +18,7 @@
 
 import android.content.Context;
 import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.telephony.TelephonyFrameworkInitializer;
 import android.telephony.ims.aidl.IRcsMessage;
 
 /**
@@ -35,8 +35,11 @@
     }
 
     <R> R call(RcsServiceCall<R> serviceCall) throws RcsMessageStoreException {
-        IRcsMessage iRcsMessage = IRcsMessage.Stub.asInterface(ServiceManager.getService(
-                Context.TELEPHONY_RCS_MESSAGE_SERVICE));
+        IRcsMessage iRcsMessage = IRcsMessage.Stub.asInterface(
+                TelephonyFrameworkInitializer
+                        .getTelephonyServiceManager()
+                        .getTelephonyRcsMessageServiceRegisterer()
+                        .get());
         if (iRcsMessage == null) {
             throw new RcsMessageStoreException("Could not connect to RCS storage service");
         }
diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java
index 75e3f0a..2e3f59a 100644
--- a/telephony/java/android/telephony/ims/RcsUceAdapter.java
+++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java
@@ -21,12 +21,11 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
-import android.content.Context;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.telephony.TelephonyFrameworkInitializer;
 import android.telephony.ims.aidl.IImsRcsController;
 import android.telephony.ims.aidl.IRcsUceControllerCallback;
 import android.util.Log;
@@ -365,7 +364,10 @@
     }
 
     private IImsRcsController getIImsRcsController() {
-        IBinder binder = ServiceManager.getService(Context.TELEPHONY_IMS_SERVICE);
+        IBinder binder = TelephonyFrameworkInitializer
+                .getTelephonyServiceManager()
+                .getTelephonyImsServiceRegisterer()
+                .get();
         return IImsRcsController.Stub.asInterface(binder);
     }
 }
diff --git a/telephony/java/android/telephony/ims/RegistrationManager.java b/telephony/java/android/telephony/ims/RegistrationManager.java
index ca081ec..5c86ba7 100644
--- a/telephony/java/android/telephony/ims/RegistrationManager.java
+++ b/telephony/java/android/telephony/ims/RegistrationManager.java
@@ -22,8 +22,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
-import android.annotation.SystemApi;
-import android.annotation.TestApi;
 import android.net.Uri;
 import android.os.Binder;
 import android.telephony.AccessNetworkConstants;
@@ -41,10 +39,7 @@
 
 /**
  * Manages IMS Service registration state for associated {@link ImsFeature}s.
- * @hide
  */
-@SystemApi
-@TestApi
 public interface RegistrationManager {
 
     /**
@@ -157,7 +152,6 @@
                 }
             }
 
-            @Override
             public void onSubscriberAssociatedUriChanged(Uri[] uris) {
                 if (mLocalCallback == null) return;
 
@@ -246,7 +240,11 @@
 
     /**
      * Registers a {@link RegistrationCallback} with the system. Use
-     * {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to Subscription changed
+     * @param executor The {@link Executor} that will be used to call the IMS registration state
+     *                 callback.
+     * @param c A callback called on the supplied {@link Executor} that will contain the
+     *                      registration state of the IMS service, which will be one of the
+     * {@see  SubscriptionManager.OnSubscriptionsChangedListener} to listen to Subscription changed
      * events and call {@link #unregisterImsRegistrationCallback(RegistrationCallback)} to clean up.
      *
      * When the callback is registered, it will initiate the callback c to be called with the
@@ -296,10 +294,10 @@
      * Gets the Transport Type associated with the current IMS registration.
      * @param executor The {@link Executor} that will be used to call the transportTypeCallback.
      * @param transportTypeCallback The transport type associated with the current IMS registration,
- *                              which will be one of following:
- *                              {@link AccessNetworkConstants#TRANSPORT_TYPE_WWAN},
- *                              {@link AccessNetworkConstants#TRANSPORT_TYPE_WLAN}, or
- *                              {@link AccessNetworkConstants#TRANSPORT_TYPE_INVALID}.
+     * which will be one of following:
+     * {@see AccessNetworkConstants#TRANSPORT_TYPE_WWAN},
+     * {@see AccessNetworkConstants#TRANSPORT_TYPE_WLAN}, or
+     * {@see AccessNetworkConstants#TRANSPORT_TYPE_INVALID}.
      */
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     void getRegistrationTransportType(
diff --git a/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl b/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl
index d64e67a..cc2ebb9 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl
@@ -92,11 +92,11 @@
     /**
      * Notifies of handover information for this call
      */
-    void callSessionHandover(int srcAccessTech, int targetAccessTech,
+    void callSessionHandover(int srcNetworkType, int targetNetworkType,
             in ImsReasonInfo reasonInfo);
-    void callSessionHandoverFailed(int srcAccessTech, int targetAccessTech,
+    void callSessionHandoverFailed(int srcNetworkType, int targetNetworkType,
             in ImsReasonInfo reasonInfo);
-    void callSessionMayHandover(int srcAccessTech, int targetAccessTech);
+    void callSessionMayHandover(int srcNetworkType, int targetNetworkType);
 
     /**
      * Notifies the TTY mode change by remote party.
diff --git a/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl b/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl
index 53e4596..57206c9 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl
@@ -40,4 +40,5 @@
     // Return result code defined in ImsConfig#OperationStatusConstants
     int setConfigString(int item, String value);
     void updateImsCarrierConfigs(in PersistableBundle bundle);
+    void notifyRcsAutoConfigurationReceived(in byte[] config, boolean isCompressed);
 }
diff --git a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
index e81bac0..6f6aa44 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
@@ -19,6 +19,9 @@
 import android.net.Uri;
 import android.telephony.ims.aidl.IImsCapabilityCallback;
 import android.telephony.ims.aidl.IRcsUceControllerCallback;
+import android.telephony.ims.aidl.IImsRegistrationCallback;
+
+import com.android.internal.telephony.IIntegerConsumer;
 
 /**
  * Interface used to interact with the Telephony IMS.
@@ -26,6 +29,13 @@
  * {@hide}
  */
 interface IImsRcsController {
+    // IMS RCS registration commands
+    void registerImsRegistrationCallback(int subId, IImsRegistrationCallback c);
+    void unregisterImsRegistrationCallback(int subId, IImsRegistrationCallback c);
+    void getImsRcsRegistrationState(int subId, IIntegerConsumer consumer);
+    void getImsRcsRegistrationTransportType(int subId, IIntegerConsumer consumer);
+
+    // IMS RCS capability commands
     void registerRcsAvailabilityCallback(int subId, IImsCapabilityCallback c);
     void unregisterRcsAvailabilityCallback(int subId, IImsCapabilityCallback c);
     boolean isCapable(int subId, int capability, int radioTech);
diff --git a/telephony/java/android/telephony/ims/aidl/IRcsFeatureListener.aidl b/telephony/java/android/telephony/ims/aidl/IRcsFeatureListener.aidl
index 881b477..70cf651 100644
--- a/telephony/java/android/telephony/ims/aidl/IRcsFeatureListener.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IRcsFeatureListener.aidl
@@ -32,11 +32,11 @@
     oneway void onNetworkResponse(int code, in String reason, int operationToken);
     oneway void onCapabilityRequestResponsePresence(in List<RcsContactUceCapability> infos,
     int operationToken);
-    oneway void onNotifyUpdateCapabilities();
+    oneway void onNotifyUpdateCapabilities(int publishTriggerType);
     oneway void onUnpublish();
     // RcsSipOptionsImplBase specific
     oneway void onCapabilityRequestResponseOptions(int code, in String reason,
             in RcsContactUceCapability info, int operationToken);
     oneway void onRemoteCapabilityRequest(in Uri contactUri, in RcsContactUceCapability remoteInfo,
             int operationToken);
-}
\ No newline at end of file
+}
diff --git a/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java b/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java
index acab738..75bd6a7 100644
--- a/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java
+++ b/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java
@@ -20,6 +20,7 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.telephony.CallQuality;
+import android.telephony.ServiceState;
 import android.telephony.ims.ImsCallProfile;
 import android.telephony.ims.ImsCallSession;
 import android.telephony.ims.ImsConferenceState;
@@ -547,19 +548,25 @@
         @Override
         public void callSessionHandover(IImsCallSession i, int srcAccessTech, int targetAccessTech,
                 ImsReasonInfo reasonInfo) throws RemoteException {
-            mNewListener.callSessionHandover(srcAccessTech, targetAccessTech, reasonInfo);
+            mNewListener.callSessionHandover(
+                    ServiceState.rilRadioTechnologyToNetworkType(srcAccessTech),
+                    ServiceState.rilRadioTechnologyToNetworkType(targetAccessTech), reasonInfo);
         }
 
         @Override
         public void callSessionHandoverFailed(IImsCallSession i, int srcAccessTech,
                 int targetAccessTech, ImsReasonInfo reasonInfo) throws RemoteException {
-            mNewListener.callSessionHandoverFailed(srcAccessTech, targetAccessTech, reasonInfo);
+            mNewListener.callSessionHandoverFailed(
+                    ServiceState.rilRadioTechnologyToNetworkType(srcAccessTech),
+                    ServiceState.rilRadioTechnologyToNetworkType(targetAccessTech), reasonInfo);
         }
 
         @Override
         public void callSessionMayHandover(IImsCallSession i, int srcAccessTech, int targetAccessTech)
                 throws RemoteException {
-            mNewListener.callSessionMayHandover(srcAccessTech, targetAccessTech);
+            mNewListener.callSessionMayHandover(
+                    ServiceState.rilRadioTechnologyToNetworkType(srcAccessTech),
+                    ServiceState.rilRadioTechnologyToNetworkType(targetAccessTech));
         }
 
         @Override
diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
index 4cb8575..e5779b3 100644
--- a/telephony/java/android/telephony/ims/feature/ImsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -60,15 +60,21 @@
      * This feature supports emergency calling over MMTEL. If defined, the framework will try to
      * place an emergency call over IMS first. If it is not defined, the framework will only use
      * CSFB for emergency calling.
+     * @hide
      */
+    @SystemApi @TestApi
     public static final int FEATURE_EMERGENCY_MMTEL = 0;
     /**
      * This feature supports the MMTEL feature.
+     * @hide
      */
+    @SystemApi @TestApi
     public static final int FEATURE_MMTEL = 1;
     /**
      * This feature supports the RCS feature.
+     * @hide
      */
+    @SystemApi @TestApi
     public static final int FEATURE_RCS = 2;
     /**
      * Total number of features defined
@@ -116,18 +122,24 @@
      * This {@link ImsFeature}'s state is unavailable and should not be communicated with. This will
      * remove all bindings back to the framework. Any attempt to communicate with the framework
      * during this time will result in an {@link IllegalStateException}.
+     * @hide
      */
+    @SystemApi @TestApi
     public static final int STATE_UNAVAILABLE = 0;
     /**
      * This {@link ImsFeature} state is initializing and should not be communicated with. This will
      * remove all bindings back to the framework. Any attempt to communicate with the framework
      * during this time will result in an {@link IllegalStateException}.
+     * @hide
      */
+    @SystemApi @TestApi
     public static final int STATE_INITIALIZING = 1;
     /**
      * This {@link ImsFeature} is ready for communication. Do not attempt to call framework methods
-     * until {@link #onFeatureReady()} is called.
+     * until {@see #onFeatureReady()} is called.
+     * @hide
      */
+    @SystemApi @TestApi
     public static final int STATE_READY = 2;
 
     /**
@@ -155,11 +167,15 @@
 
     /**
      * The capability was unable to be changed.
+     * @hide
      */
+    @SystemApi @TestApi
     public static final int CAPABILITY_ERROR_GENERIC = -1;
     /**
      * The capability was able to be changed.
+     * @hide
      */
+    @SystemApi @TestApi
     public static final int CAPABILITY_SUCCESS = 0;
 
     /**
@@ -331,17 +347,20 @@
      *
      * @see SubscriptionManager#getSubscriptionIds(int) for more information on getting the
      * subscription IDs associated with this slot.
+     * @hide
      */
+    @SystemApi @TestApi
     public final int getSlotIndex() {
         return mSlotId;
     }
 
     /**
-     * @return The current state of the feature, defined as {@link #STATE_UNAVAILABLE},
-     * {@link #STATE_INITIALIZING}, or {@link #STATE_READY}.
+     * @return The current state of the ImsFeature, set previously by {@link #setFeatureState(int)}
+     * or {@link #STATE_UNAVAILABLE} if it has not been updated  yet.
      * @hide
      */
-    public int getFeatureState() {
+    @SystemApi @TestApi
+    public @ImsState int getFeatureState() {
         synchronized (mLock) {
             return mState;
         }
@@ -352,7 +371,9 @@
      * stop communication, depending on the state sent.
      * @param state The ImsFeature's state, defined as {@link #STATE_UNAVAILABLE},
      * {@link #STATE_INITIALIZING}, or {@link #STATE_READY}.
+     * @hide
      */
+    @SystemApi @TestApi
     public final void setFeatureState(@ImsState int state) {
         synchronized (mLock) {
             if (mState != state) {
diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
index 56c8771..0d5a979 100644
--- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
@@ -52,14 +52,18 @@
  *
  * Any class wishing to use MmTelFeature should extend this class and implement all methods that the
  * service supports.
- * @hide
  */
-@SystemApi
-@TestApi
 public class MmTelFeature extends ImsFeature {
 
     private static final String LOG_TAG = "MmTelFeature";
 
+    /**
+     * @hide
+     */
+    @SystemApi @TestApi
+    public MmTelFeature() {
+    }
+
     private final IImsMmTelFeature mImsMMTelBinder = new IImsMmTelFeature.Stub() {
 
         @Override
@@ -215,11 +219,11 @@
      * {@link MmTelCapabilities#CAPABILITY_TYPE_SMS}.
      *
      * The capabilities of this MmTelFeature will be set by the framework and can be queried with
-     * {@link #queryCapabilityStatus()}.
+     * {@see #queryCapabilityStatus()}.
      *
      * This MmTelFeature can then return the status of each of these capabilities (enabled or not)
-     * by sending a {@link #notifyCapabilitiesStatusChanged} callback to the framework. The current
-     * status can also be queried using {@link #queryCapabilityStatus()}.
+     * by sending a {@see #notifyCapabilitiesStatusChanged} callback to the framework. The current
+     * status can also be queried using {@see #queryCapabilityStatus()}.
      * @see #isCapable(int)
      */
     public static class MmTelCapabilities extends Capabilities {
@@ -228,13 +232,18 @@
          * Create a new empty {@link MmTelCapabilities} instance.
          * @see #addCapabilities(int)
          * @see #removeCapabilities(int)
+         * @hide
          */
+        @SystemApi @TestApi
         public MmTelCapabilities() {
             super();
         }
 
-        /**@deprecated Use {@link MmTelCapabilities} to construct a new instance instead.*/
+        /**@deprecated Use {@link MmTelCapabilities} to construct a new instance instead.
+         * @hide
+         */
         @Deprecated
+        @SystemApi @TestApi
         public MmTelCapabilities(Capabilities c) {
             mCapabilities = c.mCapabilities;
         }
@@ -243,11 +252,17 @@
          * Create a new {link @MmTelCapabilities} instance with the provided capabilities.
          * @param capabilities The capabilities that are supported for MmTel in the form of a
          *                     bitfield.
+         * @hide
          */
+        @SystemApi @TestApi
         public MmTelCapabilities(@MmTelCapability int capabilities) {
             super(capabilities);
         }
 
+        /**
+         * @hide
+         */
+        @SystemApi @TestApi
         @IntDef(flag = true,
                 value = {
                         CAPABILITY_TYPE_VOICE,
@@ -278,23 +293,39 @@
          */
         public static final int CAPABILITY_TYPE_SMS = 1 << 3;
 
+        /**
+        * @hide
+        */
         @Override
+        @SystemApi @TestApi
         public final void addCapabilities(@MmTelCapability int capabilities) {
             super.addCapabilities(capabilities);
         }
 
+        /**
+        * @hide
+        */
         @Override
+        @SystemApi @TestApi
         public final void removeCapabilities(@MmTelCapability int capability) {
             super.removeCapabilities(capability);
         }
 
+        /**
+        * @hide
+        */
         @Override
+        @SystemApi @TestApi
         public final boolean isCapable(@MmTelCapability int capabilities) {
             return super.isCapable(capabilities);
         }
 
+        /**
+        * @hide
+        */
         @NonNull
         @Override
+        @SystemApi @TestApi
         public String toString() {
             StringBuilder builder = new StringBuilder("MmTel Capabilities - [");
             builder.append("Voice: ");
@@ -319,8 +350,10 @@
         /**
          * Called when the IMS provider receives an incoming call.
          * @param c The {@link ImsCallSession} associated with the new call.
+         * @hide
          */
         @Override
+        @SystemApi @TestApi
         public void onIncomingCall(IImsCallSession c, Bundle extras) {
 
         }
@@ -329,8 +362,10 @@
          * Called when the IMS provider implicitly rejects an incoming call during setup.
          * @param callProfile An {@link ImsCallProfile} with the call details.
          * @param reason The {@link ImsReasonInfo} reason for call rejection.
+         * @hide
          */
         @Override
+        @SystemApi @TestApi
         public void onRejectedCall(ImsCallProfile callProfile, ImsReasonInfo reason) {
 
         }
@@ -338,8 +373,10 @@
         /**
          * Updates the Listener when the voice message count for IMS has changed.
          * @param count an integer representing the new message count.
+         * @hide
          */
         @Override
+        @SystemApi @TestApi
         public void onVoiceMessageCountUpdate(int count) {
 
         }
@@ -348,14 +385,22 @@
     /**
      * To be returned by {@link #shouldProcessCall(String[])} when the ImsService should process the
      * outgoing call as IMS.
+     * @hide
      */
+    @SystemApi @TestApi
     public static final int PROCESS_CALL_IMS = 0;
     /**
      * To be returned by {@link #shouldProcessCall(String[])} when the telephony framework should
      * not process the outgoing call as IMS and should instead use circuit switch.
+     * @hide
      */
+    @SystemApi @TestApi
     public static final int PROCESS_CALL_CSFB = 1;
 
+    /**
+    * @hide
+    */
+    @SystemApi @TestApi
     @IntDef(flag = true,
             value = {
                     PROCESS_CALL_IMS,
@@ -368,7 +413,9 @@
      * If the flag is present and true, it indicates that the incoming call is for USSD.
      * <p>
      * This is an optional boolean flag.
+     * @hide
      */
+    @SystemApi @TestApi
     public static final String EXTRA_IS_USSD = "android.telephony.ims.feature.extra.IS_USSD";
 
     /**
@@ -379,7 +426,9 @@
      * certain situations.
      * <p>
      * This is an optional boolean flag.
+     * @hide
      */
+    @SystemApi @TestApi
     public static final String EXTRA_IS_UNKNOWN_CALL =
             "android.telephony.ims.feature.extra.IS_UNKNOWN_CALL";
 
@@ -388,6 +437,7 @@
     /**
      * @param listener A {@link Listener} used when the MmTelFeature receives an incoming call and
      *     notifies the framework.
+     * @hide
      */
     private void setListener(IImsMmTelListener listener) {
         synchronized (mLock) {
@@ -406,9 +456,11 @@
      * Should be a subset of the capabilities that are enabled by the framework in
      * {@link #changeEnabledCapabilities}.
      * @return A copy of the current MmTelFeature capability status.
+     * @hide
      */
     @Override
-    public final MmTelCapabilities queryCapabilityStatus() {
+    @SystemApi @TestApi
+    public @NonNull final MmTelCapabilities queryCapabilityStatus() {
         return new MmTelCapabilities(super.queryCapabilityStatus());
     }
 
@@ -420,7 +472,9 @@
      * the status of that capability is disabled. This can happen if the network does not currently
      * support the capability that is enabled. A capability that is disabled by the framework (via
      * {@link #changeEnabledCapabilities}) should also show the status as disabled.
+     * @hide
      */
+    @SystemApi @TestApi
     public final void notifyCapabilitiesStatusChanged(@NonNull MmTelCapabilities c) {
         if (c == null) {
             throw new IllegalArgumentException("MmTelCapabilities must be non-null!");
@@ -433,7 +487,9 @@
      * @param c The {@link ImsCallSessionImplBase} of the new incoming call.
      * @param extras A bundle containing extra parameters related to the call. See
      * {@link #EXTRA_IS_UNKNOWN_CALL} and {@link #EXTRA_IS_USSD} above.
+      * @hide
      */
+    @SystemApi @TestApi
     public final void notifyIncomingCall(@NonNull ImsCallSessionImplBase c,
             @NonNull Bundle extras) {
         if (c == null || extras == null) {
@@ -458,7 +514,9 @@
      * @param callProfile The {@link ImsCallProfile} IMS call profile with details.
      *        This can be null if no call information is available for the rejected call.
      * @param reason The {@link ImsReasonInfo} call rejection reason.
+     * * @hide
      */
+    @SystemApi @TestApi
     public final void notifyRejectedCall(@NonNull ImsCallProfile callProfile,
             @NonNull ImsReasonInfo reason) {
         if (callProfile == null || reason == null) {
@@ -497,7 +555,9 @@
     /**
      * Notify the framework of a change in the Voice Message count.
      * @link count the new Voice Message count.
+     * @hide
      */
+    @SystemApi @TestApi
     public final void notifyVoiceMessageCountUpdate(int count) {
         synchronized (mLock) {
             if (mListener == null) {
@@ -518,8 +578,10 @@
      * status for capability A.
      * @param capability The capability that we are querying the configuration for.
      * @return true if the capability is enabled, false otherwise.
+     * @hide
      */
     @Override
+    @SystemApi @TestApi
     public boolean queryCapabilityConfiguration(@MmTelCapabilities.MmTelCapability int capability,
             @ImsRegistrationImplBase.ImsRegistrationTech int radioTech) {
         // Base implementation - Override to provide functionality
@@ -537,8 +599,10 @@
      * Enabling/Disabling a capability here indicates that the capability should be registered or
      * deregistered (depending on the capability change) and become available or unavailable to
      * the framework.
+     * * @hide
      */
     @Override
+    @SystemApi @TestApi
     public void changeEnabledCapabilities(@NonNull CapabilityChangeRequest request,
             @NonNull CapabilityCallbackProxy c) {
         // Base implementation, no-op
@@ -561,7 +625,9 @@
      *        {@link ImsCallProfile#CALL_TYPE_VS_TX}
      *        {@link ImsCallProfile#CALL_TYPE_VS_RX}
      * @return a {@link ImsCallProfile} object
+     * @hide
      */
+    @SystemApi @TestApi
     public @Nullable ImsCallProfile createCallProfile(int callSessionType, int callType) {
         // Base Implementation - Should be overridden
         return null;
@@ -582,7 +648,9 @@
      * {@link ImsCallSession} directly.
      *
      * @param profile a call profile to make the call
+     * @hide
      */
+    @SystemApi @TestApi
     public @Nullable ImsCallSessionImplBase createCallSession(@NonNull ImsCallProfile profile) {
         // Base Implementation - Should be overridden
         return null;
@@ -599,7 +667,9 @@
      *         call as a conference.
      * @return a {@link ProcessCallResult} to the framework, which will be used to determine if the
      *        call will be placed over IMS or via CSFB.
+     * @hide
      */
+    @SystemApi @TestApi
     public @ProcessCallResult int shouldProcessCall(@NonNull String[] numbers) {
         return PROCESS_CALL_IMS;
     }
@@ -632,7 +702,9 @@
     /**
      * @return The {@link ImsUtImplBase} Ut interface implementation for the supplementary service
      * configuration.
+     * @hide
      */
+    @SystemApi @TestApi
     public @NonNull ImsUtImplBase getUt() {
         // Base Implementation - Should be overridden
         return new ImsUtImplBase();
@@ -641,7 +713,9 @@
     /**
      * @return The {@link ImsEcbmImplBase} Emergency call-back mode interface for emergency VoLTE
      * calls that support it.
+     * @hide
      */
+    @SystemApi @TestApi
     public @NonNull ImsEcbmImplBase getEcbm() {
         // Base Implementation - Should be overridden
         return new ImsEcbmImplBase();
@@ -650,7 +724,9 @@
     /**
      * @return The {@link ImsMultiEndpointImplBase} implementation for implementing Dialog event
      * package processing for multi-endpoint.
+     * @hide
      */
+    @SystemApi @TestApi
     public @NonNull ImsMultiEndpointImplBase getMultiEndpoint() {
         // Base Implementation - Should be overridden
         return new ImsMultiEndpointImplBase();
@@ -676,7 +752,9 @@
      *         // Remote side is dead
      *     }
      * }
+     * @hide
      */
+    @SystemApi @TestApi
     public void setUiTtyMode(int mode, @Nullable Message onCompleteMessage) {
         // Base Implementation - Should be overridden
     }
@@ -710,7 +788,9 @@
      *
      * @return an instance of {@link ImsSmsImplBase} which should be implemented by the IMS
      * Provider.
+     * @hide
      */
+    @SystemApi @TestApi
     public @NonNull ImsSmsImplBase getSmsImplementation() {
         return new ImsSmsImplBase();
     }
@@ -719,14 +799,22 @@
         return getSmsImplementation().getSmsFormat();
     }
 
-    /**{@inheritDoc}*/
+    /**
+     * {@inheritDoc}
+     * @hide
+     */
     @Override
+    @SystemApi @TestApi
     public void onFeatureRemoved() {
         // Base Implementation - Should be overridden
     }
 
-    /**{@inheritDoc}*/
+    /**
+     * {@inheritDoc}
+     * @hide
+     */
     @Override
+    @SystemApi @TestApi
     public void onFeatureReady() {
         // Base Implementation - Should be overridden
     }
diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
index d18e93c..6a2638b 100644
--- a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
@@ -17,11 +17,13 @@
 package android.telephony.ims.stub;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.content.Context;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
+import android.telephony.ims.ProvisioningManager;
 import android.telephony.ims.aidl.IImsConfig;
 import android.telephony.ims.aidl.IImsConfigCallback;
 import android.util.Log;
@@ -199,6 +201,12 @@
             }
         }
 
+        @Override
+        public void notifyRcsAutoConfigurationReceived(byte[] config, boolean isCompressed)
+                throws RemoteException {
+            getImsConfigImpl().notifyRcsAutoConfigurationReceived(config, isCompressed);
+        }
+
         private void notifyImsConfigChanged(int item, int value) throws RemoteException {
             getImsConfigImpl().notifyConfigChanged(item, value);
         }
@@ -228,7 +236,8 @@
      * The configuration requested resulted in an unknown result. This may happen if the
      * IMS configurations are unavailable.
      */
-    public static final int CONFIG_RESULT_UNKNOWN = -1;
+    public static final int CONFIG_RESULT_UNKNOWN = ProvisioningManager.PROVISIONING_RESULT_UNKNOWN;
+
     /**
      * Setting the configuration value completed.
      */
@@ -356,9 +365,9 @@
      * @param config The XML file to be read, if not compressed, it should be in ASCII/UTF8 format.
      * @param isCompressed The XML file is compressed in gzip format and must be decompressed
      *         before being read.
-     * @hide
+     *
      */
-    public void notifyRcsAutoConfigurationReceived(byte[] config, boolean isCompressed) {
+    public void notifyRcsAutoConfigurationReceived(@NonNull byte[] config, boolean isCompressed) {
     }
 
     /**
diff --git a/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java b/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java
index feac3c2..f13371c 100644
--- a/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java
@@ -16,6 +16,9 @@
 
 package android.telephony.ims.stub;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Bundle;
@@ -25,6 +28,9 @@
 import com.android.ims.internal.IImsUt;
 import com.android.ims.internal.IImsUtListener;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Base implementation of IMS UT interface, which implements stubs. Override these methods to
  * implement functionality.
@@ -36,6 +42,70 @@
 @SystemApi
 @TestApi
 public class ImsUtImplBase {
+    /**
+     * Bar all incoming calls. (See 3GPP TS 24.611)
+     */
+    public static final int CALL_BARRING_ALL_INCOMING = 1;
+
+    /**
+     * Bar all outgoing calls. (See 3GPP TS 24.611)
+     */
+    public static final int CALL_BARRING_ALL_OUTGOING = 2;
+
+    /**
+     * Bar all outgoing international calls. (See 3GPP TS 24.611)
+     */
+    public static final int CALL_BARRING_OUTGOING_INTL = 3;
+
+    /**
+     * Bar all outgoing international calls, excluding those to the home PLMN country
+     * (See 3GPP TS 24.611)
+     */
+    public static final int CALL_BARRING_OUTGOING_INTL_EXCL_HOME = 4;
+
+    /**
+     * Bar all incoming calls when roaming (See 3GPP TS 24.611)
+     */
+    public static final int CALL_BLOCKING_INCOMING_WHEN_ROAMING = 5;
+
+    /**
+     * Enable Anonymous Communication Rejection (See 3GPP TS 24.611)
+     */
+    public static final int CALL_BARRING_ANONYMOUS_INCOMING = 6;
+
+    /**
+     * Bar all incoming and outgoing calls. (See 3GPP TS 24.611)
+     */
+    public static final int CALL_BARRING_ALL = 7;
+
+    /**
+     * Bar all outgoing service requests, including calls. (See 3GPP TS 24.611)
+     */
+    public static final int CALL_BARRING_OUTGOING_ALL_SERVICES = 8;
+
+    /**
+     * Bar all incoming service requests, including calls. (See 3GPP TS 24.611)
+     */
+    public static final int CALL_BARRING_INCOMING_ALL_SERVICES = 9;
+
+    /**
+     * Bar specific incoming calls. (See 3GPP TS 24.611)
+     */
+    public static final int CALL_BARRING_SPECIFIC_INCOMING_CALLS = 10;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "CALL_BARRING_", value = {CALL_BARRING_ALL_INCOMING, CALL_BARRING_ALL_OUTGOING,
+            CALL_BARRING_OUTGOING_INTL, CALL_BARRING_OUTGOING_INTL_EXCL_HOME,
+            CALL_BLOCKING_INCOMING_WHEN_ROAMING, CALL_BARRING_ANONYMOUS_INCOMING,
+            CALL_BARRING_ALL, CALL_BARRING_OUTGOING_ALL_SERVICES,
+            CALL_BARRING_INCOMING_ALL_SERVICES, CALL_BARRING_SPECIFIC_INCOMING_CALLS})
+    public @interface CallBarringMode {}
+
+    /**
+     * Constant used to denote an invalid return value.
+     */
+    public static final int INVALID_RESULT = -1;
 
     private IImsUt.Stub mServiceImpl = new IImsUt.Stub() {
         @Override
@@ -138,6 +208,13 @@
             return ImsUtImplBase.this.updateCallBarringForServiceClass(
                     cbType, action, barrList, serviceClass);
         }
+
+        @Override
+        public int updateCallBarringWithPassword(int cbType, int action, String[] barrList,
+                int serviceClass, String password) throws RemoteException {
+            return ImsUtImplBase.this.updateCallBarringWithPassword(
+                    cbType, action, barrList, serviceClass, password);
+        }
     };
 
     /**
@@ -247,15 +324,23 @@
     /**
      * Updates the configuration of the call barring.
      */
-    public int updateCallBarring(int cbType, int action, String[] barrList) {
+    public int updateCallBarring(@CallBarringMode int cbType, int action, String[] barrList) {
         return -1;
     }
 
     /**
      * Updates the configuration of the call barring for specified service class.
      */
-    public int updateCallBarringForServiceClass(int cbType, int action, String[] barrList,
-            int serviceClass) {
+    public int updateCallBarringForServiceClass(@CallBarringMode int cbType, int action,
+            String[] barrList, int serviceClass) {
+        return -1;
+    }
+
+    /**
+     * Updates the configuration of the call barring for specified service class with password.
+     */
+    public int updateCallBarringWithPassword(int cbType, int action, @Nullable String[] barrList,
+            int serviceClass, @NonNull String password) {
         return -1;
     }
 
diff --git a/telephony/java/android/telephony/ims/stub/RcsPresenceExchangeImplBase.java b/telephony/java/android/telephony/ims/stub/RcsPresenceExchangeImplBase.java
index 055fca5..bb03448 100644
--- a/telephony/java/android/telephony/ims/stub/RcsPresenceExchangeImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/RcsPresenceExchangeImplBase.java
@@ -113,6 +113,51 @@
     })
     public @interface PresenceResponseCode {}
 
+
+    /** A capability update has been requested due to the Entity Tag (ETag) expiring. */
+    public static final int CAPABILITY_UPDATE_TRIGGER_ETAG_EXPIRED = 0;
+    /** A capability update has been requested due to moving to LTE with VoPS disabled. */
+    public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_LTE_VOPS_DISABLED = 1;
+    /** A capability update has been requested due to moving to LTE with VoPS enabled. */
+    public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_LTE_VOPS_ENABLED = 2;
+    /** A capability update has been requested due to moving to eHRPD. */
+    public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_EHRPD = 3;
+    /** A capability update has been requested due to moving to HSPA+. */
+    public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_HSPAPLUS = 4;
+    /** A capability update has been requested due to moving to 3G. */
+    public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_3G = 5;
+    /** A capability update has been requested due to moving to 2G. */
+    public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_2G = 6;
+    /** A capability update has been requested due to moving to WLAN */
+    public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN = 7;
+    /** A capability update has been requested due to moving to IWLAN */
+    public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_IWLAN = 8;
+    /** A capability update has been requested but the reason is unknown. */
+    public static final int CAPABILITY_UPDATE_TRIGGER_UNKNOWN = 9;
+    /** A capability update has been requested due to moving to 5G NR with VoPS disabled. */
+    public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_DISABLED = 10;
+    /** A capability update has been requested due to moving to 5G NR with VoPS enabled. */
+    public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_ENABLED = 11;
+
+    /** @hide*/
+    @IntDef(value = {
+            CAPABILITY_UPDATE_TRIGGER_ETAG_EXPIRED,
+            CAPABILITY_UPDATE_TRIGGER_MOVE_TO_LTE_VOPS_DISABLED,
+            CAPABILITY_UPDATE_TRIGGER_MOVE_TO_LTE_VOPS_ENABLED,
+            CAPABILITY_UPDATE_TRIGGER_MOVE_TO_EHRPD,
+            CAPABILITY_UPDATE_TRIGGER_MOVE_TO_HSPAPLUS,
+            CAPABILITY_UPDATE_TRIGGER_MOVE_TO_3G,
+            CAPABILITY_UPDATE_TRIGGER_MOVE_TO_2G,
+            CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN,
+            CAPABILITY_UPDATE_TRIGGER_MOVE_TO_IWLAN,
+            CAPABILITY_UPDATE_TRIGGER_UNKNOWN,
+            CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_DISABLED,
+            CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_ENABLED
+    }, prefix = "CAPABILITY_UPDATE_TRIGGER_")
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface StackPublishTriggerType {
+    }
+
     /**
      * Provide the framework with a subsequent network response update to
      * {@link #updateCapabilities(RcsContactUceCapability, int)} and
@@ -164,15 +209,18 @@
      * This is typically used when trying to generate an initial PUBLISH for a new subscription to
      * the network. The device will cache all presence publications after boot until this method is
      * called once.
+     * @param publishTriggerType {@link StackPublishTriggerType} The reason for the capability
+     *         update request.
      * @throws ImsException If this {@link RcsPresenceExchangeImplBase} instance is not currently
      * connected to the framework. This can happen if the {@link RcsFeature} is not
      * {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received the
      * {@link ImsFeature#onFeatureReady()} callback. This may also happen in rare cases when the
      * Telephony stack has crashed.
      */
-    public final void onNotifyUpdateCapabilites() throws ImsException {
+    public final void onNotifyUpdateCapabilites(@StackPublishTriggerType int publishTriggerType)
+            throws ImsException {
         try {
-            getListener().onNotifyUpdateCapabilities();
+            getListener().onNotifyUpdateCapabilities(publishTriggerType);
         } catch (RemoteException e) {
             throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
         }
diff --git a/telephony/java/com/android/ims/ImsConfig.java b/telephony/java/com/android/ims/ImsConfig.java
index cfc803c..0f6ce13 100644
--- a/telephony/java/com/android/ims/ImsConfig.java
+++ b/telephony/java/com/android/ims/ImsConfig.java
@@ -19,7 +19,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.ProvisioningManager;
 import android.telephony.ims.aidl.IImsConfig;
@@ -135,363 +135,596 @@
         /**
          * AMR CODEC Mode Value set, 0-7 in comma separated sequence.
          * Value is in String format.
+         * @deprecated use {@link ProvisioningManager#KEY_AMR_CODEC_MODE_SET_VALUES} instead.
          */
-        public static final int VOCODER_AMRMODESET = CONFIG_START;
+        @Deprecated
+        public static final int VOCODER_AMRMODESET =
+                ProvisioningManager.KEY_AMR_CODEC_MODE_SET_VALUES;
 
         /**
          * Wide Band AMR CODEC Mode Value set,0-7 in comma separated sequence.
          * Value is in String format.
+         * @deprecated use {@link ProvisioningManager#KEY_AMR_WB_CODEC_MODE_SET_VALUES} instead.
          */
-        public static final int VOCODER_AMRWBMODESET = 1;
+        @Deprecated
+        public static final int VOCODER_AMRWBMODESET =
+                ProvisioningManager.KEY_AMR_WB_CODEC_MODE_SET_VALUES;
 
         /**
          * SIP Session Timer value (seconds).
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_SIP_SESSION_TIMER_SEC} instead.
          */
-        public static final int SIP_SESSION_TIMER = 2;
+        @Deprecated
+        public static final int SIP_SESSION_TIMER = ProvisioningManager.KEY_SIP_SESSION_TIMER_SEC;
 
         /**
          * Minimum SIP Session Expiration Timer in (seconds).
          * Value is in Integer format.
+         * @deprecated use
+         * {@link ProvisioningManager#KEY_MINIMUM_SIP_SESSION_EXPIRATION_TIMER_SEC} instead.
          */
-        public static final int MIN_SE = 3;
+        @Deprecated
+        public static final int MIN_SE =
+                ProvisioningManager.KEY_MINIMUM_SIP_SESSION_EXPIRATION_TIMER_SEC;
 
         /**
          * SIP_INVITE cancellation time out value (in milliseconds). Integer format.
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_SIP_INVITE_CANCELLATION_TIMER_MS} instead.
          */
-        public static final int CANCELLATION_TIMER = 4;
+        @Deprecated
+        public static final int CANCELLATION_TIMER =
+                ProvisioningManager.KEY_SIP_INVITE_CANCELLATION_TIMER_MS;
 
         /**
          * Delay time when an iRAT transition from eHRPD/HRPD/1xRTT to LTE.
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_TRANSITION_TO_LTE_DELAY_MS} instead.
          */
-        public static final int TDELAY = 5;
+        @Deprecated
+        public static final int TDELAY = ProvisioningManager.KEY_TRANSITION_TO_LTE_DELAY_MS;
 
         /**
          * Silent redial status of Enabled (True), or Disabled (False).
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_ENABLE_SILENT_REDIAL} instead.
          */
-        public static final int SILENT_REDIAL_ENABLE = 6;
+        @Deprecated
+        public static final int SILENT_REDIAL_ENABLE = ProvisioningManager.KEY_ENABLE_SILENT_REDIAL;
 
         /**
          * SIP T1 timer value in milliseconds. See RFC 3261 for define.
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_T1_TIMER_VALUE_MS} instead.
          */
-        public static final int SIP_T1_TIMER = 7;
+        @Deprecated
+        public static final int SIP_T1_TIMER = ProvisioningManager.KEY_T1_TIMER_VALUE_MS;
 
         /**
          * SIP T2 timer value in milliseconds.  See RFC 3261 for define.
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_T2_TIMER_VALUE_MS} instead.
          */
-        public static final int SIP_T2_TIMER  = 8;
+        @Deprecated
+        public static final int SIP_T2_TIMER  = ProvisioningManager.KEY_T2_TIMER_VALUE_MS;
 
-         /**
+        /**
          * SIP TF timer value in milliseconds.  See RFC 3261 for define.
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_TF_TIMER_VALUE_MS} instead.
          */
-        public static final int SIP_TF_TIMER = 9;
+        @Deprecated
+        public static final int SIP_TF_TIMER = ProvisioningManager.KEY_TF_TIMER_VALUE_MS;
 
         /**
          * VoLTE status for VLT/s status of Enabled (1), or Disabled (0).
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_VOLTE_PROVISIONING_STATUS} instead.
          */
-        public static final int VLT_SETTING_ENABLED = 10;
+        @Deprecated
+        public static final int VLT_SETTING_ENABLED =
+                ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS;
 
         /**
          * VoLTE status for LVC/s status of Enabled (1), or Disabled (0).
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_VT_PROVISIONING_STATUS} instead.
          */
-        public static final int LVC_SETTING_ENABLED = 11;
+        @Deprecated
+        public static final int LVC_SETTING_ENABLED =
+                ProvisioningManager.KEY_VT_PROVISIONING_STATUS;
+
         /**
          * Domain Name for the device to populate the request URI for REGISTRATION.
          * Value is in String format.
+         * @deprecated use {@link ProvisioningManager#KEY_REGISTRATION_DOMAIN_NAME}.
          */
-        public static final int DOMAIN_NAME = 12;
+        @Deprecated
+        public static final int DOMAIN_NAME = ProvisioningManager.KEY_REGISTRATION_DOMAIN_NAME;
+
          /**
          * Device Outgoing SMS based on either 3GPP or 3GPP2 standards.
          * Value is in Integer format. 3GPP2(0), 3GPP(1)
-         */
-        public static final int SMS_FORMAT = 13;
+          * @deprecated use {@link ProvisioningManager#KEY_SMS_FORMAT}.
+          */
+         @Deprecated
+         public static final int SMS_FORMAT = ProvisioningManager.KEY_SMS_FORMAT;
+
          /**
          * Turns IMS ON/OFF on the device.
          * Value is in Integer format. ON (1), OFF(0).
-         */
-        public static final int SMS_OVER_IP = 14;
+          * @deprecated use {@link ProvisioningManager#KEY_SMS_OVER_IP_ENABLED}.
+          */
+         @Deprecated
+         public static final int SMS_OVER_IP = ProvisioningManager.KEY_SMS_OVER_IP_ENABLED;
+
         /**
          * Requested expiration for Published Online availability.
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_RCS_PUBLISH_TIMER_SEC}.
          */
-        public static final int PUBLISH_TIMER = 15;
+        @Deprecated
+        public static final int PUBLISH_TIMER = ProvisioningManager.KEY_RCS_PUBLISH_TIMER_SEC;
+
         /**
          * Requested expiration for Published Offline availability.
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC}.
          */
-        public static final int PUBLISH_TIMER_EXTENDED = 16;
+        @Deprecated
+        public static final int PUBLISH_TIMER_EXTENDED =
+                ProvisioningManager.KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC;
+
         /**
          *
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_RCS_CAPABILITY_DISCOVERY_ENABLED}.
          */
-        public static final int CAPABILITY_DISCOVERY_ENABLED = 17;
+        @Deprecated
+        public static final int CAPABILITY_DISCOVERY_ENABLED =
+                ProvisioningManager.KEY_RCS_CAPABILITY_DISCOVERY_ENABLED;
+
         /**
-         * Period of time the capability information of the  contact is cached on handset.
+         * Period of time the capability information of the contact is cached on handset.
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC}.
          */
-        public static final int CAPABILITIES_CACHE_EXPIRATION = 18;
+        @Deprecated
+        public static final int CAPABILITIES_CACHE_EXPIRATION =
+                ProvisioningManager.KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC;
+
         /**
          * Peiod of time the availability information of a contact is cached on device.
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC}.
          */
-        public static final int AVAILABILITY_CACHE_EXPIRATION = 19;
+        @Deprecated
+        public static final int AVAILABILITY_CACHE_EXPIRATION =
+                ProvisioningManager.KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC;
+
         /**
          * Interval between successive capabilities polling.
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC}.
          */
-        public static final int CAPABILITIES_POLL_INTERVAL = 20;
+        @Deprecated
+        public static final int CAPABILITIES_POLL_INTERVAL =
+                ProvisioningManager.KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC;
+
         /**
          * Minimum time between two published messages from the device.
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS}.
          */
-        public static final int SOURCE_THROTTLE_PUBLISH = 21;
+        @Deprecated
+        public static final int SOURCE_THROTTLE_PUBLISH =
+                ProvisioningManager.KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS;
+
         /**
          * The Maximum number of MDNs contained in one Request Contained List.
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_RCS_MAX_NUM_ENTRIES_IN_RCL}.
          */
-        public static final int MAX_NUMENTRIES_IN_RCL = 22;
+        @Deprecated
+        public static final int MAX_NUMENTRIES_IN_RCL =
+                ProvisioningManager.KEY_RCS_MAX_NUM_ENTRIES_IN_RCL;
+
         /**
          * Expiration timer for subscription of a Request Contained List, used in capability
          * polling.
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC}.
          */
-        public static final int CAPAB_POLL_LIST_SUB_EXP = 23;
+        @Deprecated
+        public static final int CAPAB_POLL_LIST_SUB_EXP =
+                ProvisioningManager.KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC;
+
         /**
          * Applies compression to LIST Subscription.
          * Value is in Integer format. Enable (1), Disable(0).
+         * @deprecated use {@link ProvisioningManager#KEY_USE_GZIP_FOR_LIST_SUBSCRIPTION}.
          */
-        public static final int GZIP_FLAG = 24;
+        @Deprecated
+        public static final int GZIP_FLAG = ProvisioningManager.KEY_USE_GZIP_FOR_LIST_SUBSCRIPTION;
+
         /**
          * VOLTE Status for EAB/s status of Enabled (1), or Disabled (0).
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_EAB_PROVISIONING_STATUS}.
          */
-        public static final int EAB_SETTING_ENABLED = 25;
+        @Deprecated
+        public static final int EAB_SETTING_ENABLED =
+                ProvisioningManager.KEY_EAB_PROVISIONING_STATUS;
+
         /**
          * Wi-Fi calling roaming status.
          * Value is in Integer format. ON (1), OFF(0).
+         * @deprecated use {@link ProvisioningManager#KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE}
+         * instead.
          */
+        @Deprecated
         public static final int VOICE_OVER_WIFI_ROAMING =
                 ProvisioningManager.KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE;
+
         /**
-         * Wi-Fi calling modem - WfcModeFeatureValueConstants.
+         * Wi-Fi calling mode - WfcModeFeatureValueConstants.
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_VOICE_OVER_WIFI_MODE_OVERRIDE}
+         * instead.
          */
+        @Deprecated
         public static final int VOICE_OVER_WIFI_MODE =
                 ProvisioningManager.KEY_VOICE_OVER_WIFI_MODE_OVERRIDE;
+
         /**
          * VOLTE Status for voice over wifi status of Enabled (1), or Disabled (0).
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE}.
          */
-        public static final int VOICE_OVER_WIFI_SETTING_ENABLED = 28;
+        @Deprecated
+        public static final int VOICE_OVER_WIFI_SETTING_ENABLED =
+                ProvisioningManager.KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE;
+
+
         /**
          * Mobile data enabled.
          * Value is in Integer format. On (1), OFF(0).
+         * @deprecated use {@link ProvisioningManager#KEY_MOBILE_DATA_ENABLED}.
          */
-        public static final int MOBILE_DATA_ENABLED = 29;
+        @Deprecated
+        public static final int MOBILE_DATA_ENABLED = ProvisioningManager.KEY_MOBILE_DATA_ENABLED;
+
         /**
          * VoLTE user opted in status.
          * Value is in Integer format. Opted-in (1) Opted-out (0).
+         * @deprecated use {@link ProvisioningManager#KEY_VOLTE_USER_OPT_IN_STATUS}.
          */
-        public static final int VOLTE_USER_OPT_IN_STATUS = 30;
+        @Deprecated
+        public static final int VOLTE_USER_OPT_IN_STATUS =
+                ProvisioningManager.KEY_VOLTE_USER_OPT_IN_STATUS;
+
         /**
          * Proxy for Call Session Control Function(P-CSCF) address for Local-BreakOut(LBO).
          * Value is in String format.
+         * @deprecated use {@link ProvisioningManager#KEY_LOCAL_BREAKOUT_PCSCF_ADDRESS}.
          */
-        public static final int LBO_PCSCF_ADDRESS = 31;
+        @Deprecated
+        public static final int LBO_PCSCF_ADDRESS =
+                ProvisioningManager.KEY_LOCAL_BREAKOUT_PCSCF_ADDRESS;
+
         /**
          * Keep Alive Enabled for SIP.
          * Value is in Integer format. On(1), OFF(0).
+         * @deprecated use {@link ProvisioningManager#KEY_SIP_KEEP_ALIVE_ENABLED}.
          */
-        public static final int KEEP_ALIVE_ENABLED = 32;
+        @Deprecated
+        public static final int KEEP_ALIVE_ENABLED = ProvisioningManager.KEY_SIP_KEEP_ALIVE_ENABLED;
+
         /**
          * Registration retry Base Time value in seconds.
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_REGISTRATION_RETRY_BASE_TIME_SEC}.
          */
-        public static final int REGISTRATION_RETRY_BASE_TIME_SEC = 33;
+        @Deprecated
+        public static final int REGISTRATION_RETRY_BASE_TIME_SEC =
+                ProvisioningManager.KEY_REGISTRATION_RETRY_BASE_TIME_SEC;
+
         /**
          * Registration retry Max Time value in seconds.
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_REGISTRATION_RETRY_MAX_TIME_SEC}.
          */
-        public static final int REGISTRATION_RETRY_MAX_TIME_SEC = 34;
+        @Deprecated
+        public static final int REGISTRATION_RETRY_MAX_TIME_SEC =
+                ProvisioningManager.KEY_REGISTRATION_RETRY_MAX_TIME_SEC;
+
         /**
          * Smallest RTP port for speech codec.
          * Value is in integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_RTP_SPEECH_START_PORT}.
          */
-        public static final int SPEECH_START_PORT = 35;
+        @Deprecated
+        public static final int SPEECH_START_PORT = ProvisioningManager.KEY_RTP_SPEECH_START_PORT;
+
         /**
          * Largest RTP port for speech code.
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_RTP_SPEECH_END_PORT}.
          */
-        public static final int SPEECH_END_PORT = 36;
+        @Deprecated
+        public static final int SPEECH_END_PORT = ProvisioningManager.KEY_RTP_SPEECH_END_PORT;
+
         /**
          * SIP Timer A's value in milliseconds. Timer A is the INVITE request
          * retransmit interval, for UDP only.
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_SIP_INVITE_REQUEST_TRANSMIT_INTERVAL_MS}.
          */
-        public static final int SIP_INVITE_REQ_RETX_INTERVAL_MSEC = 37;
+        @Deprecated
+        public static final int SIP_INVITE_REQ_RETX_INTERVAL_MSEC =
+                ProvisioningManager.KEY_SIP_INVITE_REQUEST_TRANSMIT_INTERVAL_MS;
+
         /**
          * SIP Timer B's value in milliseconds. Timer B is the wait time for
          * INVITE message to be acknowledged.
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_SIP_INVITE_ACK_WAIT_TIME_MS}.
          */
-        public static final int SIP_INVITE_RSP_WAIT_TIME_MSEC = 38;
+        @Deprecated
+        public static final int SIP_INVITE_RSP_WAIT_TIME_MSEC =
+                ProvisioningManager.KEY_SIP_INVITE_ACK_WAIT_TIME_MS;
+
         /**
          * SIP Timer D's value in milliseconds. Timer D is the wait time for
          * response retransmits of the invite client transactions.
          * Value is in Integer format.
+         * @deprecated use
+         * {@link ProvisioningManager#KEY_SIP_INVITE_RESPONSE_RETRANSMIT_WAIT_TIME_MS}.
          */
-        public static final int SIP_INVITE_RSP_RETX_WAIT_TIME_MSEC = 39;
+        @Deprecated
+        public static final int SIP_INVITE_RSP_RETX_WAIT_TIME_MSEC =
+                ProvisioningManager.KEY_SIP_INVITE_RESPONSE_RETRANSMIT_WAIT_TIME_MS;
+
         /**
          * SIP Timer E's value in milliseconds. Timer E is the value Non-INVITE
          * request retransmit interval, for UDP only.
          * Value is in Integer format.
+         * @deprecated use
+         * {@link ProvisioningManager#KEY_SIP_NON_INVITE_REQUEST_RETRANSMIT_INTERVAL_MS}.
          */
-        public static final int SIP_NON_INVITE_REQ_RETX_INTERVAL_MSEC = 40;
+        @Deprecated
+        public static final int SIP_NON_INVITE_REQ_RETX_INTERVAL_MSEC =
+                ProvisioningManager.KEY_SIP_NON_INVITE_REQUEST_RETRANSMIT_INTERVAL_MS;
+
         /**
          * SIP Timer F's value in milliseconds. Timer F is the Non-INVITE transaction
          * timeout timer.
          * Value is in Integer format.
+         * @deprecated use
+         * {@link ProvisioningManager#KEY_SIP_NON_INVITE_TRANSACTION_TIMEOUT_TIMER_MS}.
          */
-        public static final int SIP_NON_INVITE_TXN_TIMEOUT_TIMER_MSEC = 41;
+        @Deprecated
+        public static final int SIP_NON_INVITE_TXN_TIMEOUT_TIMER_MSEC =
+                ProvisioningManager.KEY_SIP_NON_INVITE_TRANSACTION_TIMEOUT_TIMER_MS;
+
         /**
          * SIP Timer G's value in milliseconds. Timer G is the value of INVITE response
          * retransmit interval.
          * Value is in Integer format.
+         * @deprecated use
+         * {@link ProvisioningManager#KEY_SIP_INVITE_RESPONSE_RETRANSMIT_INTERVAL_MS}.
          */
-        public static final int SIP_INVITE_RSP_RETX_INTERVAL_MSEC = 42;
+        @Deprecated
+        public static final int SIP_INVITE_RSP_RETX_INTERVAL_MSEC =
+                ProvisioningManager.KEY_SIP_INVITE_RESPONSE_RETRANSMIT_INTERVAL_MS;
+
         /**
          * SIP Timer H's value in milliseconds. Timer H is the value of wait time for
          * ACK receipt.
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_SIP_ACK_RECEIPT_WAIT_TIME_MS}.
          */
-        public static final int SIP_ACK_RECEIPT_WAIT_TIME_MSEC = 43;
+        @Deprecated
+        public static final int SIP_ACK_RECEIPT_WAIT_TIME_MSEC =
+                ProvisioningManager.KEY_SIP_ACK_RECEIPT_WAIT_TIME_MS;
+
         /**
          * SIP Timer I's value in milliseconds. Timer I is the value of wait time for
          * ACK retransmits.
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_SIP_ACK_RETRANSMIT_WAIT_TIME_MS}.
          */
-        public static final int SIP_ACK_RETX_WAIT_TIME_MSEC = 44;
+        @Deprecated
+        public static final int SIP_ACK_RETX_WAIT_TIME_MSEC =
+                ProvisioningManager.KEY_SIP_ACK_RETRANSMIT_WAIT_TIME_MS;
+
         /**
          * SIP Timer J's value in milliseconds. Timer J is the value of wait time for
          * non-invite request retransmission.
          * Value is in Integer format.
+         * @deprecated use
+         * {@link ProvisioningManager#KEY_SIP_NON_INVITE_REQUEST_RETRANSMISSION_WAIT_TIME_MS}.
          */
-        public static final int SIP_NON_INVITE_REQ_RETX_WAIT_TIME_MSEC = 45;
+        @Deprecated
+        public static final int SIP_NON_INVITE_REQ_RETX_WAIT_TIME_MSEC =
+                ProvisioningManager.KEY_SIP_NON_INVITE_REQUEST_RETRANSMISSION_WAIT_TIME_MS;
+
         /**
          * SIP Timer K's value in milliseconds. Timer K is the value of wait time for
          * non-invite response retransmits.
          * Value is in Integer format.
+         * @deprecated use
+         * {@link ProvisioningManager#KEY_SIP_NON_INVITE_RESPONSE_RETRANSMISSION_WAIT_TIME_MS}.
          */
-        public static final int SIP_NON_INVITE_RSP_RETX_WAIT_TIME_MSEC = 46;
+        @Deprecated
+        public static final int SIP_NON_INVITE_RSP_RETX_WAIT_TIME_MSEC =
+                ProvisioningManager.KEY_SIP_NON_INVITE_RESPONSE_RETRANSMISSION_WAIT_TIME_MS;
+
         /**
          * AMR WB octet aligned dynamic payload type.
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_AMR_WB_OCTET_ALIGNED_PAYLOAD_TYPE}.
          */
-        public static final int AMR_WB_OCTET_ALIGNED_PT = 47;
+        @Deprecated
+        public static final int AMR_WB_OCTET_ALIGNED_PT =
+                ProvisioningManager.KEY_AMR_WB_OCTET_ALIGNED_PAYLOAD_TYPE;
+
         /**
          * AMR WB bandwidth efficient payload type.
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_AMR_WB_BANDWIDTH_EFFICIENT_PAYLOAD_TYPE}.
          */
-        public static final int AMR_WB_BANDWIDTH_EFFICIENT_PT = 48;
+        @Deprecated
+        public static final int AMR_WB_BANDWIDTH_EFFICIENT_PT =
+                ProvisioningManager.KEY_AMR_WB_BANDWIDTH_EFFICIENT_PAYLOAD_TYPE;
+
         /**
          * AMR octet aligned dynamic payload type.
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_AMR_OCTET_ALIGNED_PAYLOAD_TYPE}.
          */
-        public static final int AMR_OCTET_ALIGNED_PT = 49;
+        @Deprecated
+        public static final int AMR_OCTET_ALIGNED_PT =
+                ProvisioningManager.KEY_AMR_OCTET_ALIGNED_PAYLOAD_TYPE;
+
         /**
          * AMR bandwidth efficient payload type.
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_AMR_BANDWIDTH_EFFICIENT_PAYLOAD_TYPE}.
          */
-        public static final int AMR_BANDWIDTH_EFFICIENT_PT = 50;
+        @Deprecated
+        public static final int AMR_BANDWIDTH_EFFICIENT_PT =
+                ProvisioningManager.KEY_AMR_BANDWIDTH_EFFICIENT_PAYLOAD_TYPE;
+
         /**
          * DTMF WB payload type.
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_DTMF_WB_PAYLOAD_TYPE}.
          */
-        public static final int DTMF_WB_PT = 51;
+        @Deprecated
+        public static final int DTMF_WB_PT = ProvisioningManager.KEY_DTMF_WB_PAYLOAD_TYPE;
+
         /**
          * DTMF NB payload type.
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_DTMF_NB_PAYLOAD_TYPE}.
          */
-        public static final int DTMF_NB_PT = 52;
+        @Deprecated
+        public static final int DTMF_NB_PT = ProvisioningManager.KEY_DTMF_NB_PAYLOAD_TYPE;
+
         /**
          * AMR Default encoding mode.
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_AMR_DEFAULT_ENCODING_MODE}.
          */
-        public static final int AMR_DEFAULT_MODE = 53;
+        @Deprecated
+        public static final int AMR_DEFAULT_MODE =
+                ProvisioningManager.KEY_AMR_DEFAULT_ENCODING_MODE;
+
         /**
          * SMS Public Service Identity.
          * Value is in String format.
+         * @deprecated use {@link ProvisioningManager#KEY_SMS_PUBLIC_SERVICE_IDENTITY}.
          */
-        public static final int SMS_PSI = 54;
+        @Deprecated
+        public static final int SMS_PSI = ProvisioningManager.KEY_SMS_PUBLIC_SERVICE_IDENTITY;
+
         /**
          * Video Quality - VideoQualityFeatureValuesConstants.
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_VIDEO_QUALITY}.
          */
-        public static final int VIDEO_QUALITY = 55;
+        @Deprecated
+        public static final int VIDEO_QUALITY = ProvisioningManager.KEY_VIDEO_QUALITY;
+
         /**
          * LTE threshold.
          * Handover from LTE to WiFi if LTE < THLTE1 and WiFi >= VOWT_A.
+         * @deprecated use {@link ProvisioningManager#KEY_LTE_THRESHOLD_1}.
          */
-        public static final int TH_LTE1 = 56;
+        @Deprecated
+        public static final int TH_LTE1 = ProvisioningManager.KEY_LTE_THRESHOLD_1;
+
         /**
          * LTE threshold.
          * Handover from WiFi to LTE if LTE >= THLTE3 or (WiFi < VOWT_B and LTE >= THLTE2).
+         * @deprecated use {@link ProvisioningManager#KEY_LTE_THRESHOLD_2}.
          */
-        public static final int TH_LTE2 = 57;
+        @Deprecated
+        public static final int TH_LTE2 = ProvisioningManager.KEY_LTE_THRESHOLD_2;
+
         /**
          * LTE threshold.
          * Handover from WiFi to LTE if LTE >= THLTE3 or (WiFi < VOWT_B and LTE >= THLTE2).
+         * @deprecated use {@link ProvisioningManager#KEY_LTE_THRESHOLD_3}.
          */
-        public static final int TH_LTE3 = 58;
+        @Deprecated
+        public static final int TH_LTE3 = ProvisioningManager.KEY_LTE_THRESHOLD_3;
+
         /**
          * 1x threshold.
          * Handover from 1x to WiFi if 1x < TH1x
+         * @deprecated use {@link ProvisioningManager#KEY_1X_THRESHOLD}.
          */
-        public static final int TH_1x = 59;
+        @Deprecated
+        public static final int TH_1x = ProvisioningManager.KEY_1X_THRESHOLD;
+
         /**
          * WiFi threshold.
          * Handover from LTE to WiFi if LTE < THLTE1 and WiFi >= VOWT_A.
+         * @deprecated use {@link ProvisioningManager#KEY_WIFI_THRESHOLD_A}.
          */
-        public static final int VOWT_A = 60;
+        @Deprecated
+        public static final int VOWT_A = ProvisioningManager.KEY_WIFI_THRESHOLD_A;
+
         /**
          * WiFi threshold.
          * Handover from WiFi to LTE if LTE >= THLTE3 or (WiFi < VOWT_B and LTE >= THLTE2).
+         * @deprecated use {@link ProvisioningManager#KEY_WIFI_THRESHOLD_B}.
          */
-        public static final int VOWT_B = 61;
+        @Deprecated
+        public static final int VOWT_B = ProvisioningManager.KEY_WIFI_THRESHOLD_B;
+
         /**
          * LTE ePDG timer.
          * Device shall not handover back to LTE until the T_ePDG_LTE timer expires.
+         * @deprecated use {@link ProvisioningManager#KEY_LTE_EPDG_TIMER_SEC}.
          */
-        public static final int T_EPDG_LTE = 62;
+        @Deprecated
+        public static final int T_EPDG_LTE = ProvisioningManager.KEY_LTE_EPDG_TIMER_SEC;
+
         /**
          * WiFi ePDG timer.
          * Device shall not handover back to WiFi until the T_ePDG_WiFi timer expires.
+         * @deprecated use {@link ProvisioningManager#KEY_WIFI_EPDG_TIMER_SEC}.
          */
-        public static final int T_EPDG_WIFI = 63;
+        @Deprecated
+        public static final int T_EPDG_WIFI = ProvisioningManager.KEY_WIFI_EPDG_TIMER_SEC;
+
         /**
          * 1x ePDG timer.
          * Device shall not re-register on 1x until the T_ePDG_1x timer expires.
+         * @deprecated use {@link ProvisioningManager#KEY_1X_EPDG_TIMER_SEC}.
          */
-        public static final int T_EPDG_1X = 64;
+        @Deprecated
+        public static final int T_EPDG_1X = ProvisioningManager.KEY_1X_EPDG_TIMER_SEC;
+
         /**
          * MultiEndpoint status: Enabled (1), or Disabled (0).
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_MULTIENDPOINT_ENABLED}.
          */
-        public static final int VICE_SETTING_ENABLED = 65;
+        @Deprecated
+        public static final int VICE_SETTING_ENABLED = ProvisioningManager.KEY_MULTIENDPOINT_ENABLED;
 
         /**
          * RTT status: Enabled (1), or Disabled (0).
          * Value is in Integer format.
+         * @deprecated use {@link ProvisioningManager#KEY_RTT_ENABLED}.
          */
-        public static final int RTT_SETTING_ENABLED = 66;
+        @Deprecated
+        public static final int RTT_SETTING_ENABLED = ProvisioningManager.KEY_RTT_ENABLED;
 
         // Expand the operator config items as needed here, need to change
         // PROVISIONED_CONFIG_END after that.
diff --git a/telephony/java/com/android/ims/ImsUtInterface.java b/telephony/java/com/android/ims/ImsUtInterface.java
index 50b63bd..4a5380e 100644
--- a/telephony/java/com/android/ims/ImsUtInterface.java
+++ b/telephony/java/com/android/ims/ImsUtInterface.java
@@ -21,6 +21,7 @@
 import android.os.Message;
 import android.telephony.ims.ImsCallForwardInfo;
 import android.telephony.ims.ImsSsInfo;
+import android.telephony.ims.stub.ImsUtImplBase;
 
 /**
  * Provides APIs for the supplementary service settings using IMS (Ut interface).
@@ -58,47 +59,48 @@
      * CDIV (Communication Diversion, 3GPP TS 24.604)
      *     actions: target, no reply timer
      */
-    public static final int CDIV_CF_UNCONDITIONAL = 0;
-    public static final int CDIV_CF_BUSY = 1;
-    public static final int CDIV_CF_NO_REPLY = 2;
-    public static final int CDIV_CF_NOT_REACHABLE = 3;
+    public static final int CDIV_CF_UNCONDITIONAL = ImsCallForwardInfo.CDIV_CF_REASON_UNCONDITIONAL;
+    public static final int CDIV_CF_BUSY = ImsCallForwardInfo.CDIV_CF_REASON_BUSY;
+    public static final int CDIV_CF_NO_REPLY = ImsCallForwardInfo.CDIV_CF_REASON_NO_REPLY;
+    public static final int CDIV_CF_NOT_REACHABLE = ImsCallForwardInfo.CDIV_CF_REASON_NOT_REACHABLE;
     // For CS service code: 002
-    public static final int CDIV_CF_ALL = 4;
+    public static final int CDIV_CF_ALL = ImsCallForwardInfo.CDIV_CF_REASON_ALL;
     // For CS service code: 004
-    public static final int CDIV_CF_ALL_CONDITIONAL = 5;
+    public static final int CDIV_CF_ALL_CONDITIONAL =
+            ImsCallForwardInfo.CDIV_CF_REASON_ALL_CONDITIONAL;
     // It's only supported in the IMS service (CS does not define it).
     // IR.92 recommends that an UE activates both the CFNRc and the CFNL
     // (CDIV using condition not-registered) to the same target.
-    public static final int CDIV_CF_NOT_LOGGED_IN = 6;
+    public static final int CDIV_CF_NOT_LOGGED_IN = ImsCallForwardInfo.CDIV_CF_REASON_NOT_LOGGED_IN;
 
     /**
      * CB (Communication Barring, 3GPP TS 24.611)
      */
     // Barring of All Incoming Calls
-    public static final int CB_BAIC = 1;
+    public static final int CB_BAIC = ImsUtImplBase.CALL_BARRING_ALL_INCOMING;
     // Barring of All Outgoing Calls
-    public static final int CB_BAOC = 2;
+    public static final int CB_BAOC = ImsUtImplBase.CALL_BARRING_ALL_OUTGOING;
     // Barring of Outgoing International Calls
-    public static final int CB_BOIC = 3;
+    public static final int CB_BOIC = ImsUtImplBase.CALL_BARRING_OUTGOING_INTL;
     // Barring of Outgoing International Calls - excluding Home Country
-    public static final int CB_BOIC_EXHC = 4;
+    public static final int CB_BOIC_EXHC = ImsUtImplBase.CALL_BARRING_OUTGOING_INTL_EXCL_HOME;
     // Barring of Incoming Calls - when roaming
-    public static final int CB_BIC_WR = 5;
+    public static final int CB_BIC_WR = ImsUtImplBase.CALL_BLOCKING_INCOMING_WHEN_ROAMING;
     // Barring of Anonymous Communication Rejection (ACR) - a particular case of ICB service
-    public static final int CB_BIC_ACR = 6;
+    public static final int CB_BIC_ACR = ImsUtImplBase.CALL_BARRING_ANONYMOUS_INCOMING;
     // Barring of All Calls
-    public static final int CB_BA_ALL = 7;
+    public static final int CB_BA_ALL = ImsUtImplBase.CALL_BARRING_ALL;
     // Barring of Outgoing Services (Service Code 333 - 3GPP TS 22.030 Table B-1)
-    public static final int CB_BA_MO = 8;
+    public static final int CB_BA_MO = ImsUtImplBase.CALL_BARRING_OUTGOING_ALL_SERVICES;
     // Barring of Incoming Services (Service Code 353 - 3GPP TS 22.030 Table B-1)
-    public static final int CB_BA_MT = 9;
+    public static final int CB_BA_MT = ImsUtImplBase.CALL_BARRING_INCOMING_ALL_SERVICES;
     // Barring of Specific Incoming calls
-    public static final int CB_BS_MT = 10;
+    public static final int CB_BS_MT = ImsUtImplBase.CALL_BARRING_SPECIFIC_INCOMING_CALLS;
 
     /**
      * Invalid result value.
      */
-    public static final int INVALID = (-1);
+    public static final int INVALID = ImsUtImplBase.INVALID_RESULT;
 
 
 
@@ -164,6 +166,12 @@
             String[] barrList, int serviceClass);
 
     /**
+     * Modifies the configuration of the call barring for specified service class with password.
+     */
+    public void updateCallBarring(int cbType, int action, Message result,
+            String[] barrList, int serviceClass, String password);
+
+    /**
      * Modifies the configuration of the call forward.
      */
     public void updateCallForward(int action, int condition, String number,
diff --git a/telephony/java/com/android/ims/internal/IImsUt.aidl b/telephony/java/com/android/ims/internal/IImsUt.aidl
index 4f97cc5..302be65 100644
--- a/telephony/java/com/android/ims/internal/IImsUt.aidl
+++ b/telephony/java/com/android/ims/internal/IImsUt.aidl
@@ -122,4 +122,10 @@
      */
     int updateCallBarringForServiceClass(int cbType, int action, in String[] barrList,
             int serviceClass);
+
+    /**
+     * Updates the configuration of the call barring for specified service class with password.
+     */
+    int updateCallBarringWithPassword(int cbType, int action, in String[] barrList,
+            int serviceClass, String password);
 }
diff --git a/telephony/java/com/android/internal/telephony/IBooleanConsumer.aidl b/telephony/java/com/android/internal/telephony/IBooleanConsumer.aidl
new file mode 100644
index 0000000..eb5beda
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/IBooleanConsumer.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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.telephony;
+
+// Copies consumer pattern for an operation that requires a boolean result from another process to
+// finish.
+oneway interface IBooleanConsumer {
+    void accept(boolean result);
+}
\ No newline at end of file
diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl
index c07a171..79cdce8 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -145,10 +145,13 @@
      *   be automatically persisted in the SMS db. It only affects messages sent
      *   by a non-default SMS app. Currently only the carrier app can set this
      *   parameter to false to skip auto message persistence.
+     * @param messageId An id that uniquely identifies the message requested to be sent.
+     *   Used for logging and diagnostics purposes. The id may be 0.
      */
     void sendTextForSubscriber(in int subId, String callingPkg, in String destAddr,
             in String scAddr, in String text, in PendingIntent sentIntent,
-            in PendingIntent deliveryIntent, in boolean persistMessageForNonDefaultSmsApp);
+            in PendingIntent deliveryIntent, in boolean persistMessageForNonDefaultSmsApp,
+            in long messageId);
 
     /**
      * Send an SMS. Internal use only.
@@ -270,11 +273,14 @@
      *   be automatically persisted in the SMS db. It only affects messages sent
      *   by a non-default SMS app. Currently only the carrier app can set this
      *   parameter to false to skip auto message persistence.
+     * @param messageId An id that uniquely identifies the message requested to be sent.
+     *   Used for logging and diagnostics purposes. The id may be 0.
      */
     void sendMultipartTextForSubscriber(in int subId, String callingPkg,
             in String destinationAddress, in String scAddress,
             in List<String> parts, in List<PendingIntent> sentIntents,
-            in List<PendingIntent> deliveryIntents, in boolean persistMessageForNonDefaultSmsApp);
+            in List<PendingIntent> deliveryIntents, in boolean persistMessageForNonDefaultSmsApp,
+            in long messageId);
 
     /**
      * Send a multi-part text based SMS with options using Subscription Id.
diff --git a/telephony/java/com/android/internal/telephony/ISmsImplBase.java b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
index ddd3457..db0b8e5 100644
--- a/telephony/java/com/android/internal/telephony/ISmsImplBase.java
+++ b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
@@ -61,7 +61,8 @@
     @Override
     public void sendTextForSubscriber(int subId, String callingPkg, String destAddr,
             String scAddr, String text, PendingIntent sentIntent,
-            PendingIntent deliveryIntent, boolean persistMessageForNonDefaultSmsApp) {
+            PendingIntent deliveryIntent, boolean persistMessageForNonDefaultSmsApp,
+            long messageId) {
         throw new UnsupportedOperationException();
     }
 
@@ -90,7 +91,8 @@
     public void sendMultipartTextForSubscriber(int subId, String callingPkg,
             String destinationAddress, String scAddress,
             List<String> parts, List<PendingIntent> sentIntents,
-            List<PendingIntent> deliveryIntents, boolean persistMessageForNonDefaultSmsApp) {
+            List<PendingIntent> deliveryIntents, boolean persistMessageForNonDefaultSmsApp,
+            long messageId) {
         throw new UnsupportedOperationException();
     }
 
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index b846a10..6aa5a52 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -38,8 +38,10 @@
 import android.telephony.ModemActivityInfo;
 import android.telephony.NeighboringCellInfo;
 import android.telephony.NetworkScanRequest;
+import android.telephony.PhoneCapability;
 import android.telephony.PhoneNumberRange;
 import android.telephony.RadioAccessFamily;
+import android.telephony.RadioAccessSpecifier;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.TelephonyHistogram;
@@ -54,6 +56,7 @@
 import android.telephony.ims.aidl.IImsRegistrationCallback;
 import com.android.ims.internal.IImsServiceFeatureCallback;
 import com.android.internal.telephony.CellNetworkScanResult;
+import com.android.internal.telephony.IBooleanConsumer;
 import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.INumberVerificationCallback;
 import com.android.internal.telephony.OperatorInfo;
@@ -922,6 +925,23 @@
             int subId, in OperatorInfo operatorInfo, boolean persisSelection);
 
     /**
+     * Get the allowed network types that store in the telephony provider.
+     *
+     * @param subId the id of the subscription.
+     * @return allowedNetworkTypes the allowed network types.
+     */
+    long getAllowedNetworkTypes(int subId);
+
+    /**
+     * Set the allowed network types.
+     *
+     * @param subId the id of the subscription.
+     * @param allowedNetworkTypes the allowed network types.
+     * @return true on success; false on any failure.
+     */
+    boolean setAllowedNetworkTypes(int subId, long allowedNetworkTypes);
+
+    /**
      * Set the preferred network type.
      * Used for device configuration by some CDMA operators.
      *
@@ -973,6 +993,11 @@
      boolean isManualNetworkSelectionAllowed(int subId);
 
     /**
+     * Enable or disable always reporting signal strength changes from radio.
+     */
+     void setAlwaysReportSignalStrength(int subId, boolean isEnable);
+
+    /**
      * Get P-CSCF address from PCO after data connection is established or modified.
      * @param apnType the apnType, "ims" for IMS APN, "emergency" for EMERGENCY APN
      * @param callingPackage The package making the call.
@@ -1803,12 +1828,17 @@
     /**
      * Return the network selection mode on the subscription with id {@code subId}.
      */
-     int getNetworkSelectionMode(int subId);
+    int getNetworkSelectionMode(int subId);
 
-     /**
+    /**
+     * Return the PhoneCapability for the device.
+     */
+    PhoneCapability getPhoneCapability(int subId, String callingPackage, String callingFeatureId);
+
+    /**
      * Return true if the device is in emergency sms mode, false otherwise.
      */
-     boolean isInEmergencySmsMode();
+    boolean isInEmergencySmsMode();
 
     /**
      * Return the modem radio power state for slot index.
@@ -1977,6 +2007,17 @@
      */
     boolean getImsProvisioningStatusForCapability(int subId, int capability, int tech);
 
+    /**
+     * Get the provisioning status for the IMS Rcs capability specified.
+     */
+    boolean getRcsProvisioningStatusForCapability(int subId, int capability);
+
+    /**
+     * Set the provisioning status for the IMS Rcs capability using the specified subscription.
+     */
+    void setRcsProvisioningStatusForCapability(int subId, int capability,
+            boolean isProvisioned);
+
     /** Is the capability and tech flagged as provisioned in the cache */
     boolean isMmTelCapabilityProvisionedInCache(int subId, int capability, int tech);
 
@@ -2076,6 +2117,11 @@
     int getRadioHalVersion();
 
     /**
+     * Get the current calling package name.
+     */
+    String getCurrentPackageName();
+
+    /**
      * Returns true if the specified type of application (e.g. {@link #APPTYPE_CSIM} is present
      * on the UICC card.
      * @hide
@@ -2088,6 +2134,9 @@
 
     boolean isApnMetered(int apnType, int subId);
 
+    oneway void setSystemSelectionChannels(in List<RadioAccessSpecifier> specifiers,
+            int subId, IBooleanConsumer resultCallback);
+
     boolean isMvnoMatched(int subId, int mvnoType, String mvnoMatchData);
 
     /**
@@ -2126,4 +2175,35 @@
      * Command line command to enable or disable handling of CEP data for test purposes.
      */
     oneway void setCepEnabled(boolean isCepEnabled);
+
+    /**
+     * Notify Rcs auto config received.
+     */
+    void notifyRcsAutoConfigurationReceived(int subId, in byte[] config, boolean isCompressed);
+
+    boolean isIccLockEnabled(int subId);
+
+    int setIccLockEnabled(int subId, boolean enabled, String password);
+
+    int changeIccLockPassword(int subId, String oldPassword, String newPassword);
+
+    /**
+     * Request for receiving user activity notification
+     */
+    oneway void requestUserActivityNotification();
+
+    /**
+     * Called when userActivity is signalled in the power manager.
+     * This is safe to call from any thread, with any window manager locks held or not.
+     */
+    oneway void userActivity();
+
+    /**
+     * Get the user manual network selection.
+     * Return empty string if in automatic selection.
+     *
+     * @param subId the id of the subscription
+     * @return operatorinfo on success
+     */
+    String getManualNetworkSelectionPlmn(int subId);
 }
diff --git a/telephony/java/com/android/internal/telephony/IWapPushManager.aidl b/telephony/java/com/android/internal/telephony/IWapPushManager.aidl
deleted file mode 100644
index 1c3df6533..0000000
--- a/telephony/java/com/android/internal/telephony/IWapPushManager.aidl
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2010 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.telephony;
-
-import android.content.Intent;
-
-interface IWapPushManager {
-    /**
-     * Processes WAP push message and triggers the receiver application registered
-     * in the application ID table.
-     */
-    int processMessage(String app_id, String content_type, in Intent intent);
-
-    /**
-     * Add receiver application into the application ID table.
-     * Returns true if inserting the information is successfull. Inserting the duplicated
-     * record in the application ID table is not allowed. Use update/delete method.
-     */
-    @UnsupportedAppUsage
-    boolean addPackage(String x_app_id, String content_type,
-            String package_name, String class_name,
-            int app_type, boolean need_signature, boolean further_processing);
-
-    /**
-     * Updates receiver application that is last added.
-     * Returns true if updating the information is successfull.
-     */
-    @UnsupportedAppUsage
-    boolean updatePackage(String x_app_id, String content_type,
-            String package_name, String class_name,
-            int app_type, boolean need_signature, boolean further_processing);
-
-    /**
-     * Delites receiver application information.
-     * Returns true if deleting is successfull.
-     */
-    @UnsupportedAppUsage
-    boolean deletePackage(String x_app_id, String content_type,
-                            String package_name, String class_name);
-}
-
diff --git a/telephony/java/com/android/internal/telephony/OperatorInfo.java b/telephony/java/com/android/internal/telephony/OperatorInfo.java
index 64d7863..2ca4598 100644
--- a/telephony/java/com/android/internal/telephony/OperatorInfo.java
+++ b/telephony/java/com/android/internal/telephony/OperatorInfo.java
@@ -20,6 +20,7 @@
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
 
 /**
  * @hide
@@ -43,6 +44,7 @@
 
     @UnsupportedAppUsage
     private State mState = State.UNKNOWN;
+    private int mRan = AccessNetworkType.UNKNOWN;
 
 
     @UnsupportedAppUsage
@@ -69,6 +71,10 @@
         return mState;
     }
 
+    public int getRan() {
+        return mRan;
+    }
+
     @UnsupportedAppUsage
     OperatorInfo(String operatorAlphaLong,
                 String operatorAlphaShort,
@@ -82,6 +88,14 @@
         mState = state;
     }
 
+    OperatorInfo(String operatorAlphaLong,
+                String operatorAlphaShort,
+                String operatorNumeric,
+                State state,
+                int ran) {
+        this (operatorAlphaLong, operatorAlphaShort, operatorNumeric, state);
+        mRan = ran;
+    }
 
     @UnsupportedAppUsage
     public OperatorInfo(String operatorAlphaLong,
@@ -92,6 +106,14 @@
                 operatorNumeric, rilStateToState(stateString));
     }
 
+    public OperatorInfo(String operatorAlphaLong,
+                String operatorAlphaShort,
+                String operatorNumeric,
+                int ran) {
+        this (operatorAlphaLong, operatorAlphaShort, operatorNumeric);
+        mRan = ran;
+    }
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public OperatorInfo(String operatorAlphaLong,
             String operatorAlphaShort,
@@ -124,7 +146,8 @@
         return "OperatorInfo " + mOperatorAlphaLong
                 + "/" + mOperatorAlphaShort
                 + "/" + mOperatorNumeric
-                + "/" + mState;
+                + "/" + mState
+                + "/" + mRan;
     }
 
     /**
@@ -150,6 +173,7 @@
         dest.writeString(mOperatorAlphaShort);
         dest.writeString(mOperatorNumeric);
         dest.writeSerializable(mState);
+        dest.writeInt(mRan);
     }
 
     /**
@@ -158,20 +182,21 @@
      */
     @UnsupportedAppUsage
     public static final Creator<OperatorInfo> CREATOR =
-        new Creator<OperatorInfo>() {
-            @Override
-            public OperatorInfo createFromParcel(Parcel in) {
-                OperatorInfo opInfo = new OperatorInfo(
-                        in.readString(), /*operatorAlphaLong*/
-                        in.readString(), /*operatorAlphaShort*/
-                        in.readString(), /*operatorNumeric*/
-                        (State) in.readSerializable()); /*state*/
-                return opInfo;
-            }
+            new Creator<OperatorInfo>() {
+                @Override
+                public OperatorInfo createFromParcel(Parcel in) {
+                    OperatorInfo opInfo = new OperatorInfo(
+                            in.readString(), /*operatorAlphaLong*/
+                            in.readString(), /*operatorAlphaShort*/
+                            in.readString(), /*operatorNumeric*/
+                            (State) in.readSerializable(), /*state*/
+                            in.readInt()); /*ran*/
+                    return opInfo;
+                }
 
-            @Override
-            public OperatorInfo[] newArray(int size) {
-                return new OperatorInfo[size];
-            }
-        };
+                @Override
+                public OperatorInfo[] newArray(int size) {
+                    return new OperatorInfo[size];
+                }
+            };
 }
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index 51701eb..4d67754 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -16,6 +16,7 @@
 package com.android.internal.telephony;
 
 import android.compat.annotation.UnsupportedAppUsage;
+import android.telephony.data.ApnSetting;
 
 /**
  * @hide
@@ -100,9 +101,6 @@
     public static final String DATA_APN_TYPE_KEY = "apnType";
     public static final String DATA_APN_KEY = "apn";
 
-    public static final String PHONE_IN_ECM_STATE = "phoneinECMState";
-    public static final String PHONE_IN_EMERGENCY_CALL = "phoneInEmergencyCall";
-
     /**
      * Return codes for supplyPinReturnResult and
      * supplyPukReturnResult APIs
@@ -128,32 +126,32 @@
      * APN_TYPE_ALL is a special type to indicate that this APN entry can
      * service all data connections.
      */
-    public static final String APN_TYPE_ALL = "*";
+    public static final String APN_TYPE_ALL = ApnSetting.TYPE_ALL_STRING;
     /** APN type for default data traffic */
-    public static final String APN_TYPE_DEFAULT = "default";
+    public static final String APN_TYPE_DEFAULT = ApnSetting.TYPE_DEFAULT_STRING;
     /** APN type for MMS traffic */
-    public static final String APN_TYPE_MMS = "mms";
+    public static final String APN_TYPE_MMS = ApnSetting.TYPE_MMS_STRING;
     /** APN type for SUPL assisted GPS */
-    public static final String APN_TYPE_SUPL = "supl";
+    public static final String APN_TYPE_SUPL = ApnSetting.TYPE_SUPL_STRING;
     /** APN type for DUN traffic */
-    public static final String APN_TYPE_DUN = "dun";
+    public static final String APN_TYPE_DUN = ApnSetting.TYPE_DUN_STRING;
     /** APN type for HiPri traffic */
-    public static final String APN_TYPE_HIPRI = "hipri";
+    public static final String APN_TYPE_HIPRI = ApnSetting.TYPE_HIPRI_STRING;
     /** APN type for FOTA */
-    public static final String APN_TYPE_FOTA = "fota";
+    public static final String APN_TYPE_FOTA = ApnSetting.TYPE_FOTA_STRING;
     /** APN type for IMS */
-    public static final String APN_TYPE_IMS = "ims";
+    public static final String APN_TYPE_IMS = ApnSetting.TYPE_IMS_STRING;
     /** APN type for CBS */
-    public static final String APN_TYPE_CBS = "cbs";
+    public static final String APN_TYPE_CBS = ApnSetting.TYPE_CBS_STRING;
     /** APN type for IA Initial Attach APN */
-    public static final String APN_TYPE_IA = "ia";
+    public static final String APN_TYPE_IA = ApnSetting.TYPE_IA_STRING;
     /** APN type for Emergency PDN. This is not an IA apn, but is used
      * for access to carrier services in an emergency call situation. */
-    public static final String APN_TYPE_EMERGENCY = "emergency";
+    public static final String APN_TYPE_EMERGENCY = ApnSetting.TYPE_EMERGENCY_STRING;
     /** APN type for Mission Critical Services */
-    public static final String APN_TYPE_MCX = "mcx";
+    public static final String APN_TYPE_MCX = ApnSetting.TYPE_MCX_STRING;
     /** APN type for XCAP */
-    public static final String APN_TYPE_XCAP = "xcap";
+    public static final String APN_TYPE_XCAP = ApnSetting.TYPE_XCAP_STRING;
     /** Array of all APN types */
     public static final String[] APN_TYPES = {APN_TYPE_DEFAULT,
             APN_TYPE_MMS,
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 284544b..0db86d6 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -233,11 +233,14 @@
     /** NR 5G, LTE, TD-SCDMA, CDMA, EVDO, GSM and WCDMA */
     int NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA = 33;
 
+    /** Default preferred network mode */
+    int DEFAULT_PREFERRED_NETWORK_MODE = NETWORK_MODE_WCDMA_PREF;
+
     @UnsupportedAppUsage
     int PREFERRED_NETWORK_MODE = Optional.of(TelephonyProperties.default_network())
             .filter(list -> !list.isEmpty())
             .map(list -> list.get(0))
-            .orElse(NETWORK_MODE_WCDMA_PREF);
+            .orElse(DEFAULT_PREFERRED_NETWORK_MODE);
 
     int BAND_MODE_UNSPECIFIED = 0;      //"unspecified" (selected by baseband automatically)
     int BAND_MODE_EURO = 1;             //"EURO band" (GSM-900 / DCS-1800 / WCDMA-IMT-2000)
@@ -490,6 +493,7 @@
     int RIL_REQUEST_SWITCH_DUAL_SIM_CONFIG = 207;
     int RIL_REQUEST_ENABLE_UICC_APPLICATIONS = 208;
     int RIL_REQUEST_GET_UICC_APPLICATIONS_ENABLEMENT = 209;
+    int RIL_REQUEST_SET_SYSTEM_SELECTION_CHANNELS = 210;
 
     /* Responses begin */
     int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
@@ -555,4 +559,5 @@
     int RIL_UNSOL_EMERGENCY_NUMBER_LIST = 1102;
     int RIL_UNSOL_UICC_APPLICATIONS_ENABLEMENT_CHANGED = 1103;
     int RIL_UNSOL_REGISTRATION_FAILED = 1104;
+    int RIL_UNSOL_BARRING_INFO_CHANGED = 1105;
 }
diff --git a/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java b/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java
index 1d6ec2d..8e86ff7 100644
--- a/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java
+++ b/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java
@@ -19,7 +19,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.util.SparseIntArray;
 
 import com.android.internal.telephony.cdma.sms.UserData;
diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index f78c65f..48f7850 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -19,6 +19,7 @@
 import android.content.Intent;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.ims.ImsManager;
 
 /**
  * The intents that the telephony services broadcast.
@@ -123,32 +124,6 @@
     public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED
             = TelephonyManager.ACTION_EMERGENCY_CALL_STATE_CHANGED;
 
-    /**
-     * Broadcast Action: The phone's signal strength has changed. The intent will have the
-     * following extra values:</p>
-     * <ul>
-     *   <li><em>phoneName</em> - A string version of the phone name.</li>
-     *   <li><em>asu</em> - A numeric value for the signal strength.
-     *          An ASU is 0-31 or -1 if unknown (for GSM, dBm = -113 - 2 * asu).
-     *          The following special values are defined:
-     *          <ul><li>0 means "-113 dBm or less".</li><li>31 means "-51 dBm or greater".</li></ul>
-     *   </li>
-     * </ul>
-     *
-     * <p class="note">
-     * You can <em>not</em> receive this through components declared
-     * in manifests, only by exlicitly registering for it with
-     * {@link android.content.Context#registerReceiver(android.content.BroadcastReceiver,
-     * android.content.IntentFilter) Context.registerReceiver()}.
-     *
-     * <p class="note">
-     * Requires the READ_PHONE_STATE permission.
-     *
-     * <p class="note">This is a protected intent that can only be sent
-     * by the system.
-     */
-    public static final String ACTION_SIGNAL_STRENGTH_CHANGED = "android.intent.action.SIG_STR";
-
 
     /**
      * Broadcast Action: The data connection state has changed for any one of the
@@ -205,8 +180,6 @@
     public static final String ACTION_SIM_STATE_CHANGED
             = Intent.ACTION_SIM_STATE_CHANGED;
 
-    public static final String EXTRA_REBROADCAST_ON_UNLOCK= "rebroadcastOnUnlock";
-
     /**
      * <p>Broadcast Action: It indicates the Emergency callback mode blocks datacall/sms
      * <p class="note">.
@@ -224,9 +197,11 @@
      * <p class="note">
      * This is for the OEM applications to understand about possible provisioning issues.
      * Used in OMA-DM applications.
+     * @deprecated Use {@link ImsManager#ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION} instead.
      */
-    public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION
-            = "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION";
+    @Deprecated
+    public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION =
+            ImsManager.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION;
 
     /**
      * Broadcast Action: A "secret code" has been entered in the dialer. Secret codes are
@@ -237,37 +212,6 @@
     public static final String SECRET_CODE_ACTION = "android.provider.Telephony.SECRET_CODE";
 
     /**
-     * Broadcast Action: The Service Provider string(s) have been updated.  Activities or
-     * services that use these strings should update their display.
-     * The intent will have the following extra values:</p>
-     *
-     * <dl>
-     *   <dt>showPlmn</dt><dd>Boolean that indicates whether the PLMN should be shown.</dd>
-     *   <dt>plmn</dt><dd>The operator name of the registered network, as a string.</dd>
-     *   <dt>showSpn</dt><dd>Boolean that indicates whether the SPN should be shown.</dd>
-     *   <dt>spn</dt><dd>The service provider name, as a string.</dd>
-     * </dl>
-     *
-     * Note that <em>showPlmn</em> may indicate that <em>plmn</em> should be displayed, even
-     * though the value for <em>plmn</em> is null.  This can happen, for example, if the phone
-     * has not registered to a network yet.  In this case the receiver may substitute an
-     * appropriate placeholder string (eg, "No service").
-     *
-     * It is recommended to display <em>plmn</em> before / above <em>spn</em> if
-     * both are displayed.
-     *
-     * <p>Note: this is a protected intent that can only be sent by the system.
-     */
-    public static final String SPN_STRINGS_UPDATED_ACTION =
-            "android.provider.Telephony.SPN_STRINGS_UPDATED";
-
-    public static final String EXTRA_SHOW_PLMN  = "showPlmn";
-    public static final String EXTRA_PLMN       = "plmn";
-    public static final String EXTRA_SHOW_SPN   = "showSpn";
-    public static final String EXTRA_SPN        = "spn";
-    public static final String EXTRA_DATA_SPN   = "spnData";
-
-    /**
      * <p>Broadcast Action: It indicates one column of a subinfo record has been changed
      * <p class="note">This is a protected intent that can only be sent
      * by the system.
@@ -351,85 +295,6 @@
             "android.intent.action.ACTION_SET_RADIO_CAPABILITY_FAILED";
 
     /**
-     * <p>Broadcast Action: when data connections get redirected with validation failure.
-     * intended for sim/account status checks and only sent to the specified carrier app
-     * The intent will have the following extra values:</p>
-     * <ul>
-     *   <li>apnType</li><dd>A string with the apn type.</dd>
-     *   <li>redirectionUrl</li><dd>redirection url string</dd>
-     *   <li>subId</li><dd>Sub Id which associated the data connection failure.</dd>
-     * </ul>
-     * <p class="note">This is a protected intent that can only be sent by the system.</p>
-     */
-    public static final String ACTION_CARRIER_SIGNAL_REDIRECTED =
-            "com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED";
-    /**
-     * <p>Broadcast Action: when data connections setup fails.
-     * intended for sim/account status checks and only sent to the specified carrier app
-     * The intent will have the following extra values:</p>
-     * <ul>
-     *   <li>apnType</li><dd>A string with the apn type.</dd>
-     *   <li>errorCode</li><dd>A integer with dataFailCause.</dd>
-     *   <li>subId</li><dd>Sub Id which associated the data connection failure.</dd>
-     * </ul>
-     * <p class="note">This is a protected intent that can only be sent by the system. </p>
-     */
-    public static final String ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED =
-            "com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED";
-
-    /**
-     * <p>Broadcast Action: when pco value is available.
-     * intended for sim/account status checks and only sent to the specified carrier app
-     * The intent will have the following extra values:</p>
-     * <ul>
-     *   <li>apnType</li><dd>A string with the apn type.</dd>
-     *   <li>apnProto</li><dd>A string with the protocol of the apn connection (IP,IPV6,
-     *                        IPV4V6)</dd>
-     *   <li>pcoId</li><dd>An integer indicating the pco id for the data.</dd>
-     *   <li>pcoValue</li><dd>A byte array of pco data read from modem.</dd>
-     *   <li>subId</li><dd>Sub Id which associated the data connection.</dd>
-     * </ul>
-     * <p class="note">This is a protected intent that can only be sent by the system. </p>
-     */
-    public static final String ACTION_CARRIER_SIGNAL_PCO_VALUE =
-            "com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE";
-
-    /**
-     * <p>Broadcast Action: when system default network available/unavailable with
-     * carrier-disabled mobile data. Intended for carrier apps to set/reset carrier actions when
-     * other network becomes system default network, Wi-Fi for example.
-     * The intent will have the following extra values:</p>
-     * <ul>
-     *   <li>defaultNetworkAvailable</li><dd>A boolean indicates default network available.</dd>
-     *   <li>subId</li><dd>Sub Id which associated the default data.</dd>
-     * </ul>
-     * <p class="note">This is a protected intent that can only be sent by the system. </p>
-     */
-    public static final String ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE =
-            "com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE";
-
-    /**
-     * <p>Broadcast Action: when framework reset all carrier actions on sim load or absent.
-     * intended for carrier apps clean up (clear UI e.g.) and only sent to the specified carrier app
-     * The intent will have the following extra values:</p>
-     * <ul>
-     *   <li>subId</li><dd>Sub Id which associated the data connection failure.</dd>
-     * </ul>
-     * <p class="note">This is a protected intent that can only be sent by the system.</p>
-     */
-    public static final String ACTION_CARRIER_SIGNAL_RESET =
-            "com.android.internal.telephony.CARRIER_SIGNAL_RESET";
-
-    // CARRIER_SIGNAL_ACTION extra keys
-    public static final String EXTRA_REDIRECTION_URL_KEY = "redirectionUrl";
-    public static final String EXTRA_ERROR_CODE_KEY = "errorCode";
-    public static final String EXTRA_APN_TYPE_KEY = "apnType";
-    public static final String EXTRA_APN_PROTO_KEY = "apnProto";
-    public static final String EXTRA_PCO_ID_KEY = "pcoId";
-    public static final String EXTRA_PCO_VALUE_KEY = "pcoValue";
-    public static final String EXTRA_DEFAULT_NETWORK_AVAILABLE_KEY = "defaultNetworkAvailable";
-
-    /**
      * Broadcast action to trigger CI OMA-DM Session.
      */
     public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE =
diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
index e75c593..d0c8024 100644
--- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
@@ -20,7 +20,7 @@
 import android.content.res.Resources;
 import android.sysprop.TelephonyProperties;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.telephony.SmsCbLocation;
 import android.telephony.SmsCbMessage;
 import android.telephony.cdma.CdmaSmsCbProgramData;
@@ -148,10 +148,9 @@
     }
 
     /**
-     * Create an SmsMessage from an SMS EF record.
+     * Creates an SmsMessage from an SMS EF record.
      *
-     * @param index Index of SMS record. This should be index in ArrayList
-     *              returned by RuimSmsInterfaceManager.getAllMessagesFromIcc + 1.
+     * @param index Index of SMS EF record.
      * @param data Record data.
      * @return An SmsMessage representing the record.
      *
@@ -202,26 +201,16 @@
     }
 
     /**
-     * TODO(cleanup): why do getSubmitPdu methods take an scAddr input
-     * and do nothing with it?  GSM allows us to specify a SC (eg,
-     * when responding to an SMS that explicitly requests the response
-     * is sent to a specific SC), or pass null to use the default
-     * value.  Is there no similar notion in CDMA? Or do we just not
-     * have it hooked up?
-     */
-
-    /**
-     * Get an SMS-SUBMIT PDU for a destination address and a message
+     * Gets an SMS-SUBMIT PDU for a destination address and a message.
      *
-     * @param scAddr                Service Centre address.  Null means use default.
-     * @param destAddr              Address of the recipient.
-     * @param message               String representation of the message payload.
-     * @param statusReportRequested Indicates whether a report is requested for this message.
-     * @param smsHeader             Array containing the data for the User Data Header, preceded
-     *                              by the Element Identifiers.
-     * @return a <code>SubmitPdu</code> containing the encoded SC
-     *         address, if applicable, and the encoded message.
-     *         Returns null on encode error.
+     * @param scAddr Service Centre address. No use for this message.
+     * @param destAddr the address of the destination for the message.
+     * @param message string representation of the message payload.
+     * @param statusReportRequested indicates whether a report is requested for this message.
+     * @param smsHeader array containing the data for the User Data Header, preceded by the Element
+     *                  Identifiers.
+     * @return a <code>SubmitPdu</code> containing null SC address and the encoded message. Returns
+     *         null on encode error.
      * @hide
      */
     @UnsupportedAppUsage
@@ -231,18 +220,17 @@
     }
 
     /**
-     * Get an SMS-SUBMIT PDU for a destination address and a message
+     * Gets an SMS-SUBMIT PDU for a destination address and a message.
      *
-     * @param scAddr                Service Centre address.  Null means use default.
-     * @param destAddr              Address of the recipient.
-     * @param message               String representation of the message payload.
-     * @param statusReportRequested Indicates whether a report is requested for this message.
-     * @param smsHeader             Array containing the data for the User Data Header, preceded
-     *                              by the Element Identifiers.
-     * @param priority              Priority level of the message
-     * @return a <code>SubmitPdu</code> containing the encoded SC
-     *         address, if applicable, and the encoded message.
-     *         Returns null on encode error.
+     * @param scAddr Service Centre address. No use for this message.
+     * @param destAddr the address of the destination for the message.
+     * @param message string representation of the message payload.
+     * @param statusReportRequested indicates whether a report is requested for this message.
+     * @param smsHeader array containing the data for the User Data Header, preceded by the Element
+     *                  Identifiers.
+     * @param priority priority level of the message.
+     * @return a <code>SubmitPdu</code> containing null SC address and the encoded message. Returns
+     *         null on encode error.
      * @hide
      */
     @UnsupportedAppUsage
@@ -265,16 +253,15 @@
     }
 
     /**
-     * Get an SMS-SUBMIT PDU for a data message to a destination address and port.
+     * Gets an SMS-SUBMIT PDU for a data message to a destination address &amp; port.
      *
-     * @param scAddr Service Centre address. null == use default
-     * @param destAddr the address of the destination for the message
-     * @param destPort the port to deliver the message to at the
-     *        destination
-     * @param data the data for the message
-     * @return a <code>SubmitPdu</code> containing the encoded SC
-     *         address, if applicable, and the encoded message.
-     *         Returns null on encode error.
+     * @param scAddr Service Centre address. No use for this message.
+     * @param destAddr the address of the destination for the message.
+     * @param destPort the port to deliver the message to at the destination.
+     * @param data the data for the message.
+     * @param statusReportRequested indicates whether a report is requested for this message.
+     * @return a <code>SubmitPdu</code> containing null SC address and the encoded message. Returns
+     *         null on encode error.
      */
     @UnsupportedAppUsage
     public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, int destPort,
@@ -305,14 +292,13 @@
     }
 
     /**
-     * Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port
+     * Gets an SMS-SUBMIT PDU for a data message to a destination address &amp; port.
      *
-     * @param destAddr the address of the destination for the message
-     * @param userData the data for the message
-     * @param statusReportRequested Indicates whether a report is requested for this message.
-     * @return a <code>SubmitPdu</code> containing the encoded SC
-     *         address, if applicable, and the encoded message.
-     *         Returns null on encode error.
+     * @param destAddr the address of the destination for the message.
+     * @param userData the data for the message.
+     * @param statusReportRequested indicates whether a report is requested for this message.
+     * @return a <code>SubmitPdu</code> containing null SC address and the encoded message. Returns
+     *         null on encode error.
      */
     @UnsupportedAppUsage
     public static SubmitPdu getSubmitPdu(String destAddr, UserData userData,
@@ -321,15 +307,14 @@
     }
 
     /**
-     * Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port
+     * Gets an SMS-SUBMIT PDU for a data message to a destination address &amp; port.
      *
-     * @param destAddr the address of the destination for the message
-     * @param userData the data for the message
-     * @param statusReportRequested Indicates whether a report is requested for this message.
-     * @param priority Priority level of the message
-     * @return a <code>SubmitPdu</code> containing the encoded SC
-     *         address, if applicable, and the encoded message.
-     *         Returns null on encode error.
+     * @param destAddr the address of the destination for the message.
+     * @param userData the data for the message.
+     * @param statusReportRequested indicates whether a report is requested for this message.
+     * @param priority Priority level of the message.
+     * @return a <code>SubmitPdu</code> containing null SC address and the encoded message. Returns
+     *         null on encode error.
      */
     @UnsupportedAppUsage
     public static SubmitPdu getSubmitPdu(String destAddr, UserData userData,
@@ -1059,6 +1044,72 @@
     }
 
     /**
+     * Gets an SMS-DELIVER PDU for a originating address and a message.
+     *
+     * @param origAddr the address of the originating for the message.
+     * @param message string representation of the message payload.
+     * @param date the time stamp the message was received.
+     * @return a <code>SubmitPdu</code> containing null SC address and the encoded message. Returns
+     *         null on encode error.
+     * @hide
+     */
+    public static SubmitPdu getDeliverPdu(String origAddr, String message, long date) {
+        if (origAddr == null || message == null) {
+            return null;
+        }
+
+        CdmaSmsAddress addr = CdmaSmsAddress.parse(origAddr);
+        if (addr == null) return null;
+
+        BearerData bearerData = new BearerData();
+        bearerData.messageType = BearerData.MESSAGE_TYPE_DELIVER;
+
+        bearerData.messageId = 0;
+
+        bearerData.deliveryAckReq = false;
+        bearerData.userAckReq = false;
+        bearerData.readAckReq = false;
+        bearerData.reportReq = false;
+
+        bearerData.userData = new UserData();
+        bearerData.userData.payloadStr = message;
+
+        bearerData.msgCenterTimeStamp = BearerData.TimeStamp.fromMillis(date);
+
+        byte[] encodedBearerData = BearerData.encode(bearerData);
+        if (encodedBearerData == null) return null;
+
+        try {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
+            DataOutputStream dos = new DataOutputStream(baos);
+            dos.writeInt(SmsEnvelope.TELESERVICE_WMT);
+            dos.writeInt(0); // servicePresent
+            dos.writeInt(0); // serviceCategory
+            dos.write(addr.digitMode);
+            dos.write(addr.numberMode);
+            dos.write(addr.ton); // number_type
+            dos.write(addr.numberPlan);
+            dos.write(addr.numberOfDigits);
+            dos.write(addr.origBytes, 0, addr.origBytes.length); // digits
+            // Subaddress is not supported.
+            dos.write(0); // subaddressType
+            dos.write(0); // subaddr_odd
+            dos.write(0); // subaddr_nbr_of_digits
+            dos.write(encodedBearerData.length);
+            dos.write(encodedBearerData, 0, encodedBearerData.length);
+            dos.close();
+
+            SubmitPdu pdu = new SubmitPdu();
+            pdu.encodedMessage = baos.toByteArray();
+            pdu.encodedScAddress = null;
+            return pdu;
+        } catch (IOException ex) {
+            Rlog.e(LOG_TAG, "creating Deliver PDU failed: " + ex);
+        }
+        return null;
+    }
+
+    /**
      * Creates byte array (pseudo pdu) from SMS object.
      * Note: Do not call this method more than once per object!
      * @hide
diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
index b5af646..6ad6dd1 100644
--- a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
+++ b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
@@ -18,7 +18,7 @@
 
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.Resources;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.telephony.SmsCbCmasInfo;
 import android.telephony.cdma.CdmaSmsCbProgramData;
 import android.telephony.cdma.CdmaSmsCbProgramResults;
@@ -32,6 +32,7 @@
 import com.android.internal.util.BitwiseInputStream;
 import com.android.internal.util.BitwiseOutputStream;
 
+import java.io.ByteArrayOutputStream;
 import java.time.Instant;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
@@ -284,6 +285,33 @@
             return ts;
         }
 
+        public static TimeStamp fromMillis(long timeInMillis) {
+            TimeStamp ts = new TimeStamp();
+            LocalDateTime localDateTime =
+                    Instant.ofEpochMilli(timeInMillis).atZone(ts.mZoneId).toLocalDateTime();
+            int year = localDateTime.getYear();
+            if (year < 1996 || year > 2095) return null;
+            ts.year = year;
+            ts.month = localDateTime.getMonthValue();
+            ts.monthDay = localDateTime.getDayOfMonth();
+            ts.hour = localDateTime.getHour();
+            ts.minute = localDateTime.getMinute();
+            ts.second = localDateTime.getSecond();
+            return ts;
+        }
+
+        public byte[] toByteArray() {
+            int year = this.year % 100; // 00 - 99
+            ByteArrayOutputStream outStream = new ByteArrayOutputStream(6);
+            outStream.write((((year / 10) & 0x0F) << 4) | ((year % 10) & 0x0F));
+            outStream.write((((month / 10) << 4) & 0xF0) | ((month % 10) & 0x0F));
+            outStream.write((((monthDay / 10) << 4) & 0xF0) | ((monthDay % 10) & 0x0F));
+            outStream.write((((hour / 10) << 4) & 0xF0) | ((hour % 10) & 0x0F));
+            outStream.write((((minute / 10) << 4) & 0xF0) | ((minute % 10) & 0x0F));
+            outStream.write((((second / 10) << 4) & 0xF0) | ((second % 10) & 0x0F));
+            return outStream.toByteArray();
+        }
+
         public long toMillis() {
             LocalDateTime localDateTime =
                     LocalDateTime.of(year, month + 1, monthDay, hour, minute, second);
@@ -957,6 +985,12 @@
         }
     }
 
+    private static void encodeMsgCenterTimeStamp(BearerData bData, BitwiseOutputStream outStream)
+            throws BitwiseOutputStream.AccessException {
+        outStream.write(8, 6);
+        outStream.writeByteArray(8 * 6, bData.msgCenterTimeStamp.toByteArray());
+    };
+
     /**
      * Create serialized representation for BearerData object.
      * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details)
@@ -1021,6 +1055,10 @@
                 outStream.write(8, SUBPARAM_SERVICE_CATEGORY_PROGRAM_RESULTS);
                 encodeScpResults(bData, outStream);
             }
+            if (bData.msgCenterTimeStamp != null) {
+                outStream.write(8, SUBPARAM_MESSAGE_CENTER_TIME_STAMP);
+                encodeMsgCenterTimeStamp(bData, outStream);
+            }
             return outStream.toByteArray();
         } catch (BitwiseOutputStream.AccessException ex) {
             Rlog.e(LOG_TAG, "BearerData encode failed: " + ex);
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
index 0681dc1..c91ea69 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
@@ -28,7 +28,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.Resources;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.text.TextUtils;
 
 import com.android.internal.telephony.EncodeException;
@@ -42,8 +42,11 @@
 import java.io.ByteArrayOutputStream;
 import java.io.UnsupportedEncodingException;
 import java.text.ParseException;
+import java.time.Instant;
 import java.time.LocalDateTime;
+import java.time.ZoneId;
 import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
 
 /**
  * A Short Message Service message.
@@ -167,10 +170,9 @@
     }
 
     /**
-     * Create an SmsMessage from an SMS EF record.
+     * Creates an SmsMessage from an SMS EF record.
      *
-     * @param index Index of SMS record. This should be index in ArrayList
-     *              returned by SmsManager.getAllMessagesFromSim + 1.
+     * @param index Index of SMS EF record.
      * @param data Record data.
      * @return An SmsMessage representing the record.
      *
@@ -259,12 +261,15 @@
     }
 
     /**
-     * Get an SMS-SUBMIT PDU for a destination address and a message
+     * Gets an SMS-SUBMIT PDU for a destination address and a message.
      *
-     * @param scAddress Service Centre address.  Null means use default.
-     * @return a <code>SubmitPdu</code> containing the encoded SC
-     *         address, if applicable, and the encoded message.
-     *         Returns null on encode error.
+     * @param scAddress Service Centre address. Null means use default.
+     * @param destinationAddress the address of the destination for the message.
+     * @param message string representation of the message payload.
+     * @param statusReportRequested indicates whether a report is reuested for this message.
+     * @param header a byte array containing the data for the User Data Header.
+     * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the
+     *         encoded message. Returns null on encode error.
      * @hide
      */
     @UnsupportedAppUsage
@@ -277,17 +282,19 @@
 
 
     /**
-     * Get an SMS-SUBMIT PDU for a destination address and a message using the
-     * specified encoding.
+     * Gets an SMS-SUBMIT PDU for a destination address and a message using the specified encoding.
      *
-     * @param scAddress Service Centre address.  Null means use default.
-     * @param encoding Encoding defined by constants in
-     *        com.android.internal.telephony.SmsConstants.ENCODING_*
+     * @param scAddress Service Centre address. Null means use default.
+     * @param destinationAddress the address of the destination for the message.
+     * @param message string representation of the message payload.
+     * @param statusReportRequested indicates whether a report is reuested for this message.
+     * @param header a byte array containing the data for the User Data Header.
+     * @param encoding encoding defined by constants in
+     *                 com.android.internal.telephony.SmsConstants.ENCODING_*
      * @param languageTable
      * @param languageShiftTable
-     * @return a <code>SubmitPdu</code> containing the encoded SC
-     *         address, if applicable, and the encoded message.
-     *         Returns null on encode error.
+     * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the
+     *         encoded message. Returns null on encode error.
      * @hide
      */
     @UnsupportedAppUsage
@@ -300,18 +307,20 @@
     }
 
     /**
-     * Get an SMS-SUBMIT PDU for a destination address and a message using the
-     * specified encoding.
+     * Gets an SMS-SUBMIT PDU for a destination address and a message using the specified encoding.
      *
-     * @param scAddress Service Centre address.  Null means use default.
-     * @param encoding Encoding defined by constants in
-     *        com.android.internal.telephony.SmsConstants.ENCODING_*
+     * @param scAddress Service Centre address. Null means use default.
+     * @param destinationAddress the address of the destination for the message.
+     * @param message string representation of the message payload.
+     * @param statusReportRequested indicates whether a report is reuested for this message.
+     * @param header a byte array containing the data for the User Data Header.
+     * @param encoding encoding defined by constants in
+     *                 com.android.internal.telephony.SmsConstants.ENCODING_*
      * @param languageTable
      * @param languageShiftTable
      * @param validityPeriod Validity Period of the message in Minutes.
-     * @return a <code>SubmitPdu</code> containing the encoded SC
-     *         address, if applicable, and the encoded message.
-     *         Returns null on encode error.
+     * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the
+     *         encoded message. Returns null on encode error.
      * @hide
      */
     @UnsupportedAppUsage
@@ -483,12 +492,14 @@
     }
 
     /**
-     * Get an SMS-SUBMIT PDU for a destination address and a message
+     * Gets an SMS-SUBMIT PDU for a destination address and a message.
      *
-     * @param scAddress Service Centre address.  Null means use default.
-     * @return a <code>SubmitPdu</code> containing the encoded SC
-     *         address, if applicable, and the encoded message.
-     *         Returns null on encode error.
+     * @param scAddress Service Centre address. Null means use default.
+     * @param destinationAddress the address of the destination for the message.
+     * @param message string representation of the message payload.
+     * @param statusReportRequested indicates whether a report is reuested for this message.
+     * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the
+     *         encoded message. Returns null on encode error.
      */
     @UnsupportedAppUsage
     public static SubmitPdu getSubmitPdu(String scAddress,
@@ -499,15 +510,15 @@
     }
 
     /**
-     * Get an SMS-SUBMIT PDU for a destination address and a message
+     * Gets an SMS-SUBMIT PDU for a destination address and a message.
      *
-     * @param scAddress Service Centre address.  Null means use default.
-     * @param destinationAddress the address of the destination for the message
-     * @param statusReportRequested staus report of the message Requested
+     * @param scAddress Service Centre address. Null means use default.
+     * @param destinationAddress the address of the destination for the message.
+     * @param message string representation of the message payload.
+     * @param statusReportRequested indicates whether a report is reuested for this message.
      * @param validityPeriod Validity Period of the message in Minutes.
-     * @return a <code>SubmitPdu</code> containing the encoded SC
-     *         address, if applicable, and the encoded message.
-     *         Returns null on encode error.
+     * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the
+     *         encoded message. Returns null on encode error.
      */
     @UnsupportedAppUsage
     public static SubmitPdu getSubmitPdu(String scAddress,
@@ -518,16 +529,15 @@
     }
 
     /**
-     * Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port
+     * Gets an SMS-SUBMIT PDU for a data message to a destination address &amp; port.
      *
-     * @param scAddress Service Centre address. null == use default
-     * @param destinationAddress the address of the destination for the message
-     * @param destinationPort the port to deliver the message to at the
-     *        destination
-     * @param data the data for the message
-     * @return a <code>SubmitPdu</code> containing the encoded SC
-     *         address, if applicable, and the encoded message.
-     *         Returns null on encode error.
+     * @param scAddress Service Centre address. Null means use default.
+     * @param destinationAddress the address of the destination for the message.
+     * @param destinationPort the port to deliver the message to at the destination.
+     * @param data the data for the message.
+     * @param statusReportRequested indicates whether a report is reuested for this message.
+     * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the
+     *         encoded message. Returns null on encode error.
      */
     public static SubmitPdu getSubmitPdu(String scAddress,
             String destinationAddress, int destinationPort, byte[] data,
@@ -551,8 +561,7 @@
 
         SubmitPdu ret = new SubmitPdu();
         ByteArrayOutputStream bo = getSubmitPduHead(
-                scAddress, destinationAddress, (byte) 0x41, // MTI = SMS-SUBMIT,
-                                                            // TP-UDHI = true
+                scAddress, destinationAddress, (byte) 0x41, /* TP-MTI=SMS-SUBMIT, TP-UDHI=true */
                 statusReportRequested, ret);
         // Skip encoding pdu if error occurs when create pdu head and the error will be handled
         // properly later on encodedMessage sanity check.
@@ -579,16 +588,18 @@
     }
 
     /**
-     * Create the beginning of a SUBMIT PDU.  This is the part of the
-     * SUBMIT PDU that is common to the two versions of {@link #getSubmitPdu},
-     * one of which takes a byte array and the other of which takes a
+     * Creates the beginning of a SUBMIT PDU.
+     *
+     * This is the part of the SUBMIT PDU that is common to the two versions of
+     * {@link #getSubmitPdu}, one of which takes a byte array and the other of which takes a
      * <code>String</code>.
      *
-     * @param scAddress Service Centre address. null == use default
-     * @param destinationAddress the address of the destination for the message
+     * @param scAddress Service Centre address. Null means use default.
+     * @param destinationAddress the address of the destination for the message.
      * @param mtiByte
-     * @param ret <code>SubmitPdu</code> containing the encoded SC
-     *        address, if applicable, and the encoded message. Returns null on encode error.
+     * @param statusReportRequested indicates whether a report is reuested for this message.
+     * @param ret <code>SubmitPdu</code>.
+     * @return a byte array of the beginning of a SUBMIT PDU. Null for invalid destinationAddress.
      */
     @UnsupportedAppUsage
     private static ByteArrayOutputStream getSubmitPduHead(
@@ -636,6 +647,161 @@
         return bo;
     }
 
+    /**
+     * Gets an SMS-DELIVER PDU for an originating address and a message.
+     *
+     * @param scAddress Service Centre address. Null means use default.
+     * @param originatingAddress the address of the originating for the message.
+     * @param message string representation of the message payload.
+     * @param date the time stamp the message was received.
+     * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the
+     *         encoded message. Returns null on encode error.
+     * @hide
+     */
+    public static SubmitPdu getDeliverPdu(
+            String scAddress, String originatingAddress, String message, long date) {
+        if (originatingAddress == null || message == null) {
+            return null;
+        }
+
+        // Find the best encoding to use
+        TextEncodingDetails ted = calculateLength(message, false);
+        int encoding = ted.codeUnitSize;
+        int languageTable = ted.languageTable;
+        int languageShiftTable = ted.languageShiftTable;
+        byte[] header = null;
+
+        if (encoding == ENCODING_7BIT && (languageTable != 0 || languageShiftTable != 0)) {
+            SmsHeader smsHeader = new SmsHeader();
+            smsHeader.languageTable = languageTable;
+            smsHeader.languageShiftTable = languageShiftTable;
+            header = SmsHeader.toByteArray(smsHeader);
+        }
+
+        SubmitPdu ret = new SubmitPdu();
+
+        ByteArrayOutputStream bo = new ByteArrayOutputStream(MAX_USER_DATA_BYTES + 40);
+
+        // SMSC address with length octet, or 0
+        if (scAddress == null) {
+            ret.encodedScAddress = null;
+        } else {
+            ret.encodedScAddress =
+                    PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(scAddress);
+        }
+
+        // TP-Message-Type-Indicator
+        bo.write(0); // SMS-DELIVER
+
+        byte[] oaBytes;
+
+        oaBytes = PhoneNumberUtils.networkPortionToCalledPartyBCD(originatingAddress);
+
+        // Return null for invalid originating address
+        if (oaBytes == null) return null;
+
+        // Originating address length in BCD digits, ignoring TON byte and pad
+        // TODO Should be better.
+        bo.write((oaBytes.length - 1) * 2 - ((oaBytes[oaBytes.length - 1] & 0xf0) == 0xf0 ? 1 : 0));
+
+        // Originating Address
+        bo.write(oaBytes, 0, oaBytes.length);
+
+        // TP-Protocol-Identifier
+        bo.write(0);
+
+        // User Data (and length)
+        byte[] userData;
+        try {
+            if (encoding == ENCODING_7BIT) {
+                userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message, header,
+                        languageTable, languageShiftTable);
+            } else { // Assume UCS-2
+                try {
+                    userData = encodeUCS2(message, header);
+                } catch (UnsupportedEncodingException uex) {
+                    Rlog.e(LOG_TAG, "Implausible UnsupportedEncodingException ", uex);
+                    return null;
+                }
+            }
+        } catch (EncodeException ex) {
+            if (ex.getError() == EncodeException.ERROR_EXCEED_SIZE) {
+                Rlog.e(LOG_TAG, "Exceed size limitation EncodeException", ex);
+                return null;
+            } else {
+                // Encoding to the 7-bit alphabet failed. Let's see if we can send it as a UCS-2
+                // encoded message
+                try {
+                    userData = encodeUCS2(message, header);
+                    encoding = ENCODING_16BIT;
+                } catch (EncodeException ex1) {
+                    Rlog.e(LOG_TAG, "Exceed size limitation EncodeException", ex1);
+                    return null;
+                } catch (UnsupportedEncodingException uex) {
+                    Rlog.e(LOG_TAG, "Implausible UnsupportedEncodingException ", uex);
+                    return null;
+                }
+            }
+        }
+
+        if (encoding == ENCODING_7BIT) {
+            if ((0xff & userData[0]) > MAX_USER_DATA_SEPTETS) {
+                // Message too long
+                Rlog.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " septets)");
+                return null;
+            }
+            // TP-Data-Coding-Scheme
+            // Default encoding, uncompressed
+            bo.write(0x00);
+        } else { // Assume UCS-2
+            if ((0xff & userData[0]) > MAX_USER_DATA_BYTES) {
+                // Message too long
+                Rlog.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " bytes)");
+                return null;
+            }
+            // TP-Data-Coding-Scheme
+            // UCS-2 encoding, uncompressed
+            bo.write(0x08);
+        }
+
+        // TP-Service-Centre-Time-Stamp
+        byte[] scts = new byte[7];
+
+        ZonedDateTime zoneDateTime = Instant.ofEpochMilli(date).atZone(ZoneId.systemDefault());
+        LocalDateTime localDateTime = zoneDateTime.toLocalDateTime();
+
+        // It indicates the difference, expressed in quarters of an hour, between the local time and
+        // GMT.
+        int timezoneOffset = zoneDateTime.getOffset().getTotalSeconds() / 60 / 15;
+        boolean negativeOffset = timezoneOffset < 0;
+        if (negativeOffset) {
+            timezoneOffset = -timezoneOffset;
+        }
+        int year = localDateTime.getYear();
+        int month = localDateTime.getMonthValue();
+        int day = localDateTime.getDayOfMonth();
+        int hour = localDateTime.getHour();
+        int minute = localDateTime.getMinute();
+        int second = localDateTime.getSecond();
+
+        year = year > 2000 ? year - 2000 : year - 1900;
+        scts[0] = (byte) ((((year % 10) & 0x0F) << 4) | ((year / 10) & 0x0F));
+        scts[1] = (byte) ((((month % 10) & 0x0F) << 4) | ((month / 10) & 0x0F));
+        scts[2] = (byte) ((((day % 10) & 0x0F) << 4) | ((day / 10) & 0x0F));
+        scts[3] = (byte) ((((hour % 10) & 0x0F) << 4) | ((hour / 10) & 0x0F));
+        scts[4] = (byte) ((((minute % 10) & 0x0F) << 4) | ((minute / 10) & 0x0F));
+        scts[5] = (byte) ((((second % 10) & 0x0F) << 4) | ((second / 10) & 0x0F));
+        scts[6] = (byte) ((((timezoneOffset % 10) & 0x0F) << 4) | ((timezoneOffset / 10) & 0x0F));
+        if (negativeOffset) {
+            scts[0] |= 0x08; // Negative offset
+        }
+        bo.write(scts, 0, scts.length);
+
+        bo.write(userData, 0, userData.length);
+        ret.encodedMessage = bo.toByteArray();
+        return ret;
+    }
+
     private static class PduParser {
         @UnsupportedAppUsage
         byte mPdu[];
diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
index eed9a86..0dc7401 100644
--- a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
+++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
@@ -21,7 +21,7 @@
 import android.content.res.Resources.NotFoundException;
 import android.graphics.Bitmap;
 import android.graphics.Color;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.GsmAlphabet;
diff --git a/test-mock/api/test-current.txt b/test-mock/api/test-current.txt
index cc260ac..32ca250 100644
--- a/test-mock/api/test-current.txt
+++ b/test-mock/api/test-current.txt
@@ -2,7 +2,6 @@
 package android.test.mock {
 
   public class MockContext extends android.content.Context {
-    method public android.view.Display getDisplay();
     method public int getDisplayId();
   }
 
diff --git a/test-mock/src/android/test/mock/MockContext.java b/test-mock/src/android/test/mock/MockContext.java
index 9d913b9..36074ed 100644
--- a/test-mock/src/android/test/mock/MockContext.java
+++ b/test-mock/src/android/test/mock/MockContext.java
@@ -17,6 +17,7 @@
 package android.test.mock;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.app.IApplicationThread;
 import android.app.IServiceConnection;
@@ -811,6 +812,11 @@
     }
 
     @Override
+    public @NonNull Context createWindowContext(int type) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public boolean isRestricted() {
         throw new UnsupportedOperationException();
     }
@@ -821,7 +827,6 @@
         throw new UnsupportedOperationException();
     }
 
-    /** @hide */
     @Override
     public Display getDisplay() {
         throw new UnsupportedOperationException();
diff --git a/tests/ApkVerityTest/block_device_writer/Android.bp b/tests/ApkVerityTest/block_device_writer/Android.bp
index e7a63e4..78850c5 100644
--- a/tests/ApkVerityTest/block_device_writer/Android.bp
+++ b/tests/ApkVerityTest/block_device_writer/Android.bp
@@ -30,7 +30,23 @@
         "-g",
     ],
     shared_libs: ["libbase", "libutils"],
+    // For some reasons, cuttlefish (x86) uses x86_64 test suites for testing. Unfortunately, when
+    // the uploader does not pick up the executable from correct output location. The following
+    // workaround allows the test to:
+    //  * upload the 32-bit exectuable for both 32 and 64 bits devices to use
+    //  * refer to the same executable name in Java
+    //  * no need to force the Java test to be archiecture specific.
+    //
+    // See b/145573317 for details.
+    multilib: {
+        lib32: {
+            suffix: "",
+        },
+        lib64: {
+            suffix: "64",  // not really used
+        },
+    },
 
-    test_suites: ["general-tests"],
+    test_suites: ["general-tests", "pts"],
     gtest: false,
 }
diff --git a/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java b/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java
index 592aa3a..153ca79 100644
--- a/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java
+++ b/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java
@@ -58,4 +58,31 @@
                 ambientModeChangedCount[0], 2);
     }
 
+    @Test
+    public void testDeliversZoomChanged() {
+        int[] zoomChangedCount = {0};
+        WallpaperService service = new WallpaperService() {
+            @Override
+            public Engine onCreateEngine() {
+                return new Engine() {
+                    @Override
+                    public void onZoomChanged(float zoom) {
+                        super.onZoomChanged(zoom);
+                        zoomChangedCount[0]++;
+                    }
+                };
+            }
+        };
+        WallpaperService.Engine engine = service.onCreateEngine();
+        engine.setCreated(true);
+
+        engine.setZoom(.5f);
+        assertEquals("engine scale was not updated", .5f, engine.getZoom(), .001f);
+        assertEquals("onZoomChanged should have been called", 1, zoomChangedCount[0]);
+
+        engine.setZoom(0);
+        assertEquals("engine scale was not updated", 0, engine.getZoom(), .001f);
+        assertEquals("onAmbientModeChanged should have been called", 2, zoomChangedCount[0]);
+    }
+
 }
diff --git a/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java
index 653282d..1361df3 100644
--- a/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java
+++ b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java
@@ -320,8 +320,9 @@
                             UserHandle.USER_CURRENT);
                 }
 
-                mAtm.startActivityAndWait(null, null, mLaunchIntent, mimeType,
-                        null, null, 0, mLaunchIntent.getFlags(), null, null,
+                mAtm.startActivityAndWait(null,
+                        getInstrumentation().getContext().getBasePackageName(), mLaunchIntent,
+                        mimeType, null, null, 0, mLaunchIntent.getFlags(), null, null,
                         UserHandle.USER_CURRENT_OR_SELF);
             } catch (RemoteException e) {
                 Log.w(TAG, "Error launching app", e);
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index b4cafe4..8cc8cf4 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -18,10 +18,13 @@
 
 import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
@@ -36,12 +39,14 @@
 import android.net.ConnectivityModuleConnector;
 import android.net.ConnectivityModuleConnector.ConnectivityModuleHealthListener;
 import android.os.Handler;
+import android.os.SystemProperties;
 import android.os.test.TestLooper;
 import android.provider.DeviceConfig;
 import android.util.AtomicFile;
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.server.PackageWatchdog.HealthCheckState;
 import com.android.server.PackageWatchdog.MonitoredPackage;
 import com.android.server.PackageWatchdog.PackageHealthObserver;
@@ -54,11 +59,15 @@
 import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
 
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
@@ -88,6 +97,8 @@
     private PackageManager mMockPackageManager;
     @Captor
     private ArgumentCaptor<ConnectivityModuleHealthListener> mConnectivityModuleCallbackCaptor;
+    private MockitoSession mSession;
+    private HashMap<String, String> mSystemSettingsMap;
 
     @Before
     public void setUp() throws Exception {
@@ -104,11 +115,47 @@
             res.setLongVersionCode(VERSION_CODE);
             return res;
         });
+        mSession = ExtendedMockito.mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.LENIENT)
+                .spyStatic(SystemProperties.class)
+                .startMocking();
+        mSystemSettingsMap = new HashMap<>();
+
+
+        // Mock SystemProperties setter and various getters
+        doAnswer((Answer<Void>) invocationOnMock -> {
+                    String key = invocationOnMock.getArgument(0);
+                    String value = invocationOnMock.getArgument(1);
+
+                    mSystemSettingsMap.put(key, value);
+                    return null;
+                }
+        ).when(() -> SystemProperties.set(anyString(), anyString()));
+
+        doAnswer((Answer<Integer>) invocationOnMock -> {
+                    String key = invocationOnMock.getArgument(0);
+                    int defaultValue = invocationOnMock.getArgument(1);
+
+                    String storedValue = mSystemSettingsMap.get(key);
+                    return storedValue == null ? defaultValue : Integer.parseInt(storedValue);
+                }
+        ).when(() -> SystemProperties.getInt(anyString(), anyInt()));
+
+        doAnswer((Answer<Long>) invocationOnMock -> {
+                    String key = invocationOnMock.getArgument(0);
+                    long defaultValue = invocationOnMock.getArgument(1);
+
+                    String storedValue = mSystemSettingsMap.get(key);
+                    return storedValue == null ? defaultValue : Long.parseLong(storedValue);
+                }
+        ).when(() -> SystemProperties.getLong(anyString(), anyLong()));
     }
 
     @After
     public void tearDown() throws Exception {
         dropShellPermissions();
+        mSession.finishMocking();
     }
 
     @Test
@@ -896,37 +943,124 @@
         assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
     }
 
-    /** Test that observers execute correctly for different failure reasons */
+    /** Test that observers execute correctly for failures reasons that go through thresholding. */
     @Test
-    public void testFailureReasons() {
+    public void testNonImmediateFailureReasons() {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
-        TestObserver observer3 = new TestObserver(OBSERVER_NAME_3);
-        TestObserver observer4 = new TestObserver(OBSERVER_NAME_4);
 
         watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
         watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION);
-        watchdog.startObservingHealth(observer3, Arrays.asList(APP_C), SHORT_DURATION);
-        watchdog.startObservingHealth(observer4, Arrays.asList(APP_D), SHORT_DURATION);
+
+        raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
+                VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+        raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_B,
+                VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);
+
+        assertThat(observer1.getLastFailureReason()).isEqualTo(
+                PackageWatchdog.FAILURE_REASON_APP_CRASH);
+        assertThat(observer2.getLastFailureReason()).isEqualTo(
+                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);
+    }
+
+    /** Test that observers execute correctly for failures reasons that skip thresholding. */
+    @Test
+    public void testImmediateFailures() {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
+
+        watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
 
         raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
                 VERSION_CODE)), PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
         raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_B,
                 VERSION_CODE)), PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
-        raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_C,
-                VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_CRASH);
-        raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_D,
-                VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);
 
-        assertThat(observer1.getLastFailureReason()).isEqualTo(
-                PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
-        assertThat(observer2.getLastFailureReason()).isEqualTo(
-                PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
-        assertThat(observer3.getLastFailureReason()).isEqualTo(
-                PackageWatchdog.FAILURE_REASON_APP_CRASH);
-        assertThat(observer4.getLastFailureReason()).isEqualTo(
-                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);
+        assertThat(observer1.mMitigatedPackages).containsExactly(APP_A, APP_B);
+    }
+
+    /**
+     * Test that a persistent observer will mitigate failures if it wishes to observe a package.
+     */
+    @Test
+    public void testPersistentObserverWatchesPackage() {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver persistentObserver = new TestObserver(OBSERVER_NAME_1);
+        persistentObserver.setPersistent(true);
+        persistentObserver.setMayObservePackages(true);
+
+        watchdog.startObservingHealth(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
+
+        raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
+                VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN);
+        assertThat(persistentObserver.mHealthCheckFailedPackages).containsExactly(APP_A);
+    }
+
+    /**
+     * Test that a persistent observer will not mitigate failures if it does not wish to observe
+     * a given package.
+     */
+    @Test
+    public void testPersistentObserverDoesNotWatchPackage() {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver persistentObserver = new TestObserver(OBSERVER_NAME_1);
+        persistentObserver.setPersistent(true);
+        persistentObserver.setMayObservePackages(false);
+
+        watchdog.startObservingHealth(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
+
+        raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
+                VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN);
+        assertThat(persistentObserver.mHealthCheckFailedPackages).isEmpty();
+    }
+
+
+    /** Ensure that boot loop mitigation is done when the number of boots meets the threshold. */
+    @Test
+    public void testBootLoopDetection_meetsThreshold() {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
+        watchdog.registerHealthObserver(bootObserver);
+        for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
+            watchdog.noteBoot();
+        }
+        assertThat(bootObserver.mitigatedBootLoop()).isTrue();
+    }
+
+
+    /**
+     * Ensure that boot loop mitigation is not done when the number of boots does not meet the
+     * threshold.
+     */
+    @Test
+    public void testBootLoopDetection_doesNotMeetThreshold() {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
+        watchdog.registerHealthObserver(bootObserver);
+        for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; i++) {
+            watchdog.noteBoot();
+        }
+        assertThat(bootObserver.mitigatedBootLoop()).isFalse();
+    }
+
+    /**
+     * Ensure that boot loop mitigation is done for the observer with the lowest user impact
+     */
+    @Test
+    public void testBootLoopMitigationDoneForLowestUserImpact() {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver bootObserver1 = new TestObserver(OBSERVER_NAME_1);
+        bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LOW);
+        TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2);
+        bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+        watchdog.registerHealthObserver(bootObserver1);
+        watchdog.registerHealthObserver(bootObserver2);
+        for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
+            watchdog.noteBoot();
+        }
+        assertThat(bootObserver1.mitigatedBootLoop()).isTrue();
+        assertThat(bootObserver2.mitigatedBootLoop()).isFalse();
     }
 
     private void adoptShellPermissions(String... permissions) {
@@ -964,7 +1098,12 @@
     /** Trigger package failures above the threshold. */
     private void raiseFatalFailureAndDispatch(PackageWatchdog watchdog,
             List<VersionedPackage> packages, int failureReason) {
-        for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
+        long triggerFailureCount = watchdog.getTriggerFailureCount();
+        if (failureReason == PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK
+                || failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
+            triggerFailureCount = 1;
+        }
+        for (int i = 0; i < triggerFailureCount; i++) {
             watchdog.onPackageFailure(packages, failureReason);
         }
         mTestLooper.dispatchAll();
@@ -1000,6 +1139,9 @@
         private final String mName;
         private int mImpact;
         private int mLastFailureReason;
+        private boolean mIsPersistent = false;
+        private boolean mMayObservePackages = false;
+        private boolean mMitigatedBootLoop = false;
         final List<String> mHealthCheckFailedPackages = new ArrayList<>();
         final List<String> mMitigatedPackages = new ArrayList<>();
 
@@ -1028,9 +1170,42 @@
             return mName;
         }
 
+        public boolean isPersistent() {
+            return mIsPersistent;
+        }
+
+        public boolean mayObservePackage(String packageName) {
+            return mMayObservePackages;
+        }
+
+        public int onBootLoop() {
+            return mImpact;
+        }
+
+        public boolean executeBootLoopMitigation() {
+            mMitigatedBootLoop = true;
+            return true;
+        }
+
+        public boolean mitigatedBootLoop() {
+            return mMitigatedBootLoop;
+        }
+
         public int getLastFailureReason() {
             return mLastFailureReason;
         }
+
+        public void setPersistent(boolean persistent) {
+            mIsPersistent = persistent;
+        }
+
+        public void setImpact(int impact) {
+            mImpact = impact;
+        }
+
+        public void setMayObservePackages(boolean mayObservePackages) {
+            mMayObservePackages = mayObservePackages;
+        }
     }
 
     private static class TestController extends ExplicitHealthCheckController {
diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp
index 2bc129a..98e7b4e 100644
--- a/tests/RollbackTest/Android.bp
+++ b/tests/RollbackTest/Android.bp
@@ -19,15 +19,20 @@
     static_libs: ["androidx.test.rules", "cts-rollback-lib", "cts-install-lib"],
     test_suites: ["general-tests"],
     test_config: "RollbackTest.xml",
+    java_resources: [
+        ":com.android.apex.apkrollback.test_v2",
+        ":com.android.apex.apkrollback.test_v2Crashing"
+    ],
 }
 
 java_test_host {
     name: "StagedRollbackTest",
     srcs: ["StagedRollbackTest/src/**/*.java"],
     libs: ["tradefed"],
-    static_libs: ["testng"],
+    static_libs: ["testng", "compatibility-tradefed"],
     test_suites: ["general-tests"],
     test_config: "StagedRollbackTest.xml",
+    data: [":com.android.apex.apkrollback.test_v1"],
 }
 
 java_test_host {
@@ -37,3 +42,54 @@
     test_suites: ["general-tests"],
     test_config: "MultiUserRollbackTest.xml",
 }
+
+genrule {
+  name: "com.android.apex.apkrollback.test.pem",
+  out: ["com.android.apex.apkrollback.test.pem"],
+  cmd: "openssl genrsa -out $(out) 4096",
+}
+
+genrule {
+  name: "com.android.apex.apkrollback.test.pubkey",
+  srcs: [":com.android.apex.apkrollback.test.pem"],
+  out: ["com.android.apex.apkrollback.test.pubkey"],
+  tools: ["avbtool"],
+  cmd: "$(location avbtool) extract_public_key --key $(in) --output $(out)",
+}
+
+apex_key {
+  name: "com.android.apex.apkrollback.test.key",
+  private_key: ":com.android.apex.apkrollback.test.pem",
+  public_key: ":com.android.apex.apkrollback.test.pubkey",
+  installable: false,
+}
+
+apex {
+  name: "com.android.apex.apkrollback.test_v1",
+  manifest: "testdata/manifest_v1.json",
+  androidManifest: "testdata/AndroidManifest.xml",
+  file_contexts: ":apex.test-file_contexts",
+  key: "com.android.apex.apkrollback.test.key",
+  apps: ["TestAppAv1"],
+  installable: false,
+}
+
+apex {
+  name: "com.android.apex.apkrollback.test_v2",
+  manifest: "testdata/manifest_v2.json",
+  androidManifest: "testdata/AndroidManifest.xml",
+  file_contexts: ":apex.test-file_contexts",
+  key: "com.android.apex.apkrollback.test.key",
+  apps: ["TestAppAv2"],
+  installable: false,
+}
+
+apex {
+  name: "com.android.apex.apkrollback.test_v2Crashing",
+  manifest: "testdata/manifest_v2.json",
+  androidManifest: "testdata/AndroidManifest.xml",
+  file_contexts: ":apex.test-file_contexts",
+  key: "com.android.apex.apkrollback.test.key",
+  apps: ["TestAppACrashingV2"],
+  installable: false,
+}
\ No newline at end of file
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index f6699fa..5a92d68 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -392,9 +392,6 @@
                     RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS,
                     Long.toString(expirationTime), false /* makeDefault*/);
 
-            // Pull the new expiration time from DeviceConfig
-            rm.reloadPersistedData();
-
             // Uninstall TestApp.A
             Uninstall.packages(TestApp.A);
             assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
@@ -457,9 +454,6 @@
                     RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS,
                     Long.toString(expirationTime), false /* makeDefault*/);
 
-            // Pull the new expiration time from DeviceConfig
-            rm.reloadPersistedData();
-
             // Install app A with rollback enabled
             Uninstall.packages(TestApp.A);
             Install.single(TestApp.A1).commit();
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index 40169b8..80491cd 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -23,12 +23,14 @@
 
 import android.Manifest;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.rollback.RollbackInfo;
 import android.content.rollback.RollbackManager;
 import android.os.ParcelFileDescriptor;
+import android.os.storage.StorageManager;
 import android.provider.DeviceConfig;
 
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -185,12 +187,6 @@
      */
     @Test
     public void testNativeWatchdogTriggersRollback_Phase1() throws Exception {
-        // When multiple staged sessions are installed on a device which doesn't support checkpoint,
-        // only the 1st one will prevail. We have to check no other rollbacks available to ensure
-        // TestApp.A is always the 1st and the only one to commit so rollback can work as intended.
-        // If there are leftover rollbacks from previous tests, this assertion will fail.
-        assertThat(RollbackUtils.getRollbackManager().getAvailableRollbacks()).isEmpty();
-
         Uninstall.packages(TestApp.A);
         Install.single(TestApp.A1).commit();
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
@@ -220,6 +216,64 @@
                 TestApp.A)).isNotNull();
     }
 
+    /**
+     * Stage install an apk with rollback that will be later triggered by unattributable crash.
+     */
+    @Test
+    public void testNativeWatchdogTriggersRollbackForAll_Phase1() throws Exception {
+        Uninstall.packages(TestApp.A);
+        Install.single(TestApp.A1).commit();
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+
+        Install.single(TestApp.A2).setEnableRollback().setStaged().commit();
+    }
+
+    /**
+     * Verify the rollback is available and then install another package with rollback.
+     */
+    @Test
+    public void testNativeWatchdogTriggersRollbackForAll_Phase2() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+        RollbackManager rm = RollbackUtils.getRollbackManager();
+        assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
+                TestApp.A)).isNotNull();
+
+        // Install another package with rollback
+        Uninstall.packages(TestApp.B);
+        Install.single(TestApp.B1).commit();
+        assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1);
+
+        Install.single(TestApp.B2).setEnableRollback().setStaged().commit();
+    }
+
+    /**
+     * Verify the rollbacks are available.
+     */
+    @Test
+    public void testNativeWatchdogTriggersRollbackForAll_Phase3() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+        assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
+        RollbackManager rm = RollbackUtils.getRollbackManager();
+        assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
+                TestApp.A)).isNotNull();
+        assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
+                TestApp.B)).isNotNull();
+    }
+
+    /**
+     * Verify the rollbacks are committed after crashing.
+     */
+    @Test
+    public void testNativeWatchdogTriggersRollbackForAll_Phase4() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1);
+        RollbackManager rm = RollbackUtils.getRollbackManager();
+        assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
+                TestApp.A)).isNotNull();
+        assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
+                TestApp.B)).isNotNull();
+    }
+
     @Test
     public void testNetworkFailedRollback_Phase1() throws Exception {
         // Remove available rollbacks and uninstall NetworkStack on /data/
@@ -324,6 +378,7 @@
 
     @Test
     public void testNetworkPassedDoesNotRollback_Phase1() throws Exception {
+        // Remove available rollbacks and uninstall NetworkStack on /data/
         RollbackManager rm = RollbackUtils.getRollbackManager();
         String networkStack = getNetworkStackPackageName();
 
@@ -332,6 +387,15 @@
 
         assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
                         networkStack)).isNull();
+
+        // Reduce health check deadline, here unlike the network failed case, we use
+        // a longer deadline because joining a network can take a much longer time for
+        // reasons external to the device than 'not joining'
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+                PROPERTY_WATCHDOG_REQUEST_TIMEOUT_MILLIS,
+                Integer.toString(300000), false);
+        // Simulate re-installation of new NetworkStack with rollbacks enabled
+        installNetworkStackPackage();
     }
 
     @Test
@@ -343,6 +407,9 @@
 
     @Test
     public void testNetworkPassedDoesNotRollback_Phase3() throws Exception {
+        // Sleep for > health check deadline. We expect no rollback should happen during sleeping.
+        // If the device reboots for rollback, this device test will fail as well as the host test.
+        Thread.sleep(TimeUnit.SECONDS.toMillis(310));
         RollbackManager rm = RollbackUtils.getRollbackManager();
         assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
                         getNetworkStackPackageName())).isNull();
@@ -380,11 +447,6 @@
     }
 
     @Test
-    public void testRollbackWhitelistedApp_cleanUp() throws Exception {
-        RollbackUtils.getRollbackManager().expireRollbackForPackage(getModuleMetadataPackageName());
-    }
-
-    @Test
     public void testRollbackDataPolicy_Phase1() throws Exception {
         Uninstall.packages(TestApp.A, TestApp.B);
         Install.multi(TestApp.A1, TestApp.B1).commit();
@@ -422,9 +484,116 @@
         assertThat(InstallUtils.getUserDataVersion(TestApp.B)).isEqualTo(1);
     }
 
+    @Test
+    public void testCleanUp() throws Exception {
+        // testNativeWatchdogTriggersRollback will fail if multiple staged sessions are
+        // committed on a device which doesn't support checkpoint. Let's clean up all rollbacks
+        // so there is only one rollback to commit when testing native crashes.
+        RollbackManager rm = RollbackUtils.getRollbackManager();
+        rm.getAvailableRollbacks().stream().flatMap(info -> info.getPackages().stream())
+                .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage);
+        assertThat(RollbackUtils.getRollbackManager().getAvailableRollbacks()).isEmpty();
+    }
+
+    private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test";
+    private static final TestApp TEST_APEX_WITH_APK_V1 = new TestApp("TestApexWithApkV1",
+            APK_IN_APEX_TESTAPEX_NAME, 1, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v1.apex");
+    private static final TestApp TEST_APEX_WITH_APK_V2 = new TestApp("TestApexWithApkV2",
+            APK_IN_APEX_TESTAPEX_NAME, 2, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v2.apex");
+    private static final TestApp TEST_APEX_WITH_APK_V2_CRASHING = new TestApp(
+            "TestApexWithApkV2Crashing", APK_IN_APEX_TESTAPEX_NAME, 2, /*isApex*/true,
+            APK_IN_APEX_TESTAPEX_NAME + "_v2Crashing.apex");
+
+    @Test
+    public void testRollbackApexWithApk_Phase1() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        InstallUtils.processUserData(TestApp.A);
+
+        int sessionId = Install.single(TEST_APEX_WITH_APK_V2).setStaged().setEnableRollback()
+                .commit();
+        InstallUtils.waitForSessionReady(sessionId);
+    }
+
+    @Test
+    public void testRollbackApexWithApk_Phase2() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(2);
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+        InstallUtils.processUserData(TestApp.A);
+
+        RollbackInfo available = RollbackUtils.getAvailableRollback(APK_IN_APEX_TESTAPEX_NAME);
+        assertThat(available).isStaged();
+        assertThat(available).packagesContainsExactly(
+                Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1),
+                Rollback.from(TestApp.A, 0).to(TestApp.A1));
+
+        RollbackUtils.rollback(available.getRollbackId(), TEST_APEX_WITH_APK_V2);
+        RollbackInfo committed = RollbackUtils.getCommittedRollbackById(available.getRollbackId());
+        assertThat(committed).isNotNull();
+        assertThat(committed).isStaged();
+        assertThat(committed).packagesContainsExactly(
+                Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1),
+                Rollback.from(TestApp.A, 0).to(TestApp.A1));
+        assertThat(committed).causePackagesContainsExactly(TEST_APEX_WITH_APK_V2);
+        assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
+
+        // Note: The app is not rolled back until after the rollback is staged
+        // and the device has been rebooted.
+        InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
+        assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(2);
+    }
+
+    @Test
+    public void testRollbackApexWithApk_Phase3() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(1);
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        InstallUtils.processUserData(TestApp.A);
+    }
+
+    /**
+     * Installs an apex with an apk that can crash.
+     */
+    @Test
+    public void testRollbackApexWithApkCrashing_Phase1() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        int sessionId = Install.single(TEST_APEX_WITH_APK_V2_CRASHING).setStaged()
+                .setEnableRollback().commit();
+        InstallUtils.waitForSessionReady(sessionId);
+    }
+
+    /**
+     * Verifies rollback has been enabled successfully. Then makes TestApp.A crash.
+     */
+    @Test
+    public void testRollbackApexWithApkCrashing_Phase2() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(2);
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+
+        RollbackInfo available = RollbackUtils.getAvailableRollback(APK_IN_APEX_TESTAPEX_NAME);
+        assertThat(available).isStaged();
+        assertThat(available).packagesContainsExactly(
+                Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1),
+                Rollback.from(TestApp.A, 0).to(TestApp.A1));
+
+        // Crash TestApp.A PackageWatchdog#TRIGGER_FAILURE_COUNT times to trigger rollback
+        RollbackUtils.sendCrashBroadcast(TestApp.A, 5);
+    }
+
+    @Test
+    public void testRollbackApexWithApkCrashing_Phase3() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(1);
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+    }
+
     private static void runShellCommand(String cmd) {
         ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation().getUiAutomation()
                 .executeShellCommand(cmd);
         IoUtils.closeQuietly(pfd);
     }
+
+    @Test
+    public void isCheckpointSupported() {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
+        assertThat(sm.isCheckpointSupported()).isTrue();
+    }
 }
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index 4644d8a..672cbb0 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -17,16 +17,19 @@
 package com.android.tests.rollback.host;
 
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 import static org.testng.Assert.assertThrows;
 
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
+import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.File;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -48,8 +51,32 @@
                     phase));
     }
 
+    private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test";
+
     @Before
     public void setUp() throws Exception {
+        if (!getDevice().isAdbRoot()) {
+            getDevice().enableAdbRoot();
+        }
+        getDevice().remountSystemWritable();
+        getDevice().executeShellCommand(
+                "rm -f /system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex "
+                        + "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex");
+        getDevice().reboot();
+        runPhase("testCleanUp");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        runPhase("testCleanUp");
+
+        if (!getDevice().isAdbRoot()) {
+            getDevice().enableAdbRoot();
+        }
+        getDevice().remountSystemWritable();
+        getDevice().executeShellCommand(
+                "rm -f /system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex "
+                        + "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex");
         getDevice().reboot();
     }
 
@@ -70,7 +97,6 @@
 
     @Test
     public void testNativeWatchdogTriggersRollback() throws Exception {
-        //Stage install ModuleMetadata package - this simulates a Mainline module update
         runPhase("testNativeWatchdogTriggersRollback_Phase1");
 
         // Reboot device to activate staged package
@@ -96,6 +122,40 @@
         runPhase("testNativeWatchdogTriggersRollback_Phase3");
     }
 
+    @Test
+    public void testNativeWatchdogTriggersRollbackForAll() throws Exception {
+        // This test requires committing multiple staged rollbacks
+        assumeTrue(isCheckpointSupported());
+
+        // Install a package with rollback enabled.
+        runPhase("testNativeWatchdogTriggersRollbackForAll_Phase1");
+        getDevice().reboot();
+
+        // Once previous staged install is applied, install another package
+        runPhase("testNativeWatchdogTriggersRollbackForAll_Phase2");
+        getDevice().reboot();
+
+        // Verify the new staged install has also been applied successfully.
+        runPhase("testNativeWatchdogTriggersRollbackForAll_Phase3");
+
+        // crash system_server enough times to trigger a rollback
+        crashProcess("system_server", NATIVE_CRASHES_THRESHOLD);
+
+        // Rollback should be committed automatically now.
+        // Give time for rollback to be committed. This could take a while,
+        // because we need all of the following to happen:
+        // 1. system_server comes back up and boot completes.
+        // 2. Rollback health observer detects updatable crashing signal.
+        // 3. Staged rollback session becomes ready.
+        // 4. Device actually reboots.
+        // So we give a generous timeout here.
+        assertTrue(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(5)));
+        getDevice().waitForDeviceAvailable();
+
+        // verify all available rollbacks have been committed
+        runPhase("testNativeWatchdogTriggersRollbackForAll_Phase4");
+    }
+
     /**
      * Tests failed network health check triggers watchdog staged rollbacks.
      */
@@ -128,21 +188,8 @@
      * Tests passed network health check does not trigger watchdog staged rollbacks.
      */
     @Test
-    @Ignore("b/143514090")
     public void testNetworkPassedDoesNotRollback() throws Exception {
-        // Remove available rollbacks and uninstall NetworkStack on /data/
         runPhase("testNetworkPassedDoesNotRollback_Phase1");
-        // Reduce health check deadline, here unlike the network failed case, we use
-        // a longer deadline because joining a network can take a much longer time for
-        // reasons external to the device than 'not joining'
-        getDevice().executeShellCommand("device_config put rollback "
-                + "watchdog_request_timeout_millis 300000");
-        // Simulate re-installation of new NetworkStack with rollbacks enabled
-        getDevice().executeShellCommand("pm install -r --staged --enable-rollback "
-                + getNetworkStackPath());
-
-        // Sleep to allow writes to disk before reboot
-        Thread.sleep(5000);
         // Reboot device to activate staged package
         getDevice().reboot();
 
@@ -157,8 +204,6 @@
         // on mobile data
         getDevice().waitForDeviceAvailable();
 
-        // Sleep for > health check deadline
-        Thread.sleep(310000);
         // Verify rollback was not executed after health check deadline
         runPhase("testNetworkPassedDoesNotRollback_Phase3");
     }
@@ -180,16 +225,9 @@
      */
     @Test
     public void testRollbackWhitelistedApp() throws Exception {
-        try {
-            runPhase("testRollbackWhitelistedApp_Phase1");
-            getDevice().reboot();
-            runPhase("testRollbackWhitelistedApp_Phase2");
-        } finally {
-            // testNativeWatchdogTriggersRollback will fail if multiple staged sessions are
-            // committed on a device which doesn't support checkpoint. Let's clean up the rollback
-            // so there is only one rollback to commit when testing native crashes.
-            runPhase("testRollbackWhitelistedApp_cleanUp");
-        }
+        runPhase("testRollbackWhitelistedApp_Phase1");
+        getDevice().reboot();
+        runPhase("testRollbackWhitelistedApp_Phase2");
     }
 
     @Test
@@ -201,6 +239,56 @@
         runPhase("testRollbackDataPolicy_Phase3");
     }
 
+    /**
+     * Tests that userdata of apk-in-apex is restored when apex is rolled back.
+     */
+    @Test
+    public void testRollbackApexWithApk() throws Exception {
+        getDevice().uninstallPackage("com.android.cts.install.lib.testapp.A");
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+        final String fileName = APK_IN_APEX_TESTAPEX_NAME + "_v1.apex";
+        final File apex = buildHelper.getTestFile(fileName);
+        if (!getDevice().isAdbRoot()) {
+            getDevice().enableAdbRoot();
+        }
+        getDevice().remountSystemWritable();
+        assertTrue(getDevice().pushFile(apex, "/system/apex/" + fileName));
+        getDevice().reboot();
+        runPhase("testRollbackApexWithApk_Phase1");
+        getDevice().reboot();
+        runPhase("testRollbackApexWithApk_Phase2");
+        getDevice().reboot();
+        runPhase("testRollbackApexWithApk_Phase3");
+    }
+
+    /**
+     * Tests that RollbackPackageHealthObserver is observing apk-in-apex.
+     */
+    @Test
+    public void testRollbackApexWithApkCrashing() throws Exception {
+        getDevice().uninstallPackage("com.android.cts.install.lib.testapp.A");
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+        final String fileName = APK_IN_APEX_TESTAPEX_NAME + "_v1.apex";
+        final File apex = buildHelper.getTestFile(fileName);
+        if (!getDevice().isAdbRoot()) {
+            getDevice().enableAdbRoot();
+        }
+        getDevice().remountSystemWritable();
+        assertTrue(getDevice().pushFile(apex, "/system/apex/" + fileName));
+        getDevice().reboot();
+
+        // Install an apex with apk that crashes
+        runPhase("testRollbackApexWithApkCrashing_Phase1");
+        getDevice().reboot();
+        // Verify apex was installed and then crash the apk
+        runPhase("testRollbackApexWithApkCrashing_Phase2");
+        // Wait for crash to trigger rollback
+        assertTrue(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(5)));
+        getDevice().waitForDeviceAvailable();
+        // Verify rollback occurred due to crash of apk-in-apex
+        runPhase("testRollbackApexWithApkCrashing_Phase3");
+    }
+
     private void crashProcess(String processName, int numberOfCrashes) throws Exception {
         String pid = "";
         String lastPid = "invalid";
@@ -219,4 +307,13 @@
         // Find the NetworkStack path (can be NetworkStack.apk or NetworkStackNext.apk)
         return getDevice().executeShellCommand("ls /system/priv-app/NetworkStack*/*.apk");
     }
+
+    private boolean isCheckpointSupported() throws Exception {
+        try {
+            runPhase("isCheckpointSupported");
+            return true;
+        } catch (AssertionError ignore) {
+            return false;
+        }
+    }
 }
diff --git a/tests/RollbackTest/testdata/AndroidManifest.xml b/tests/RollbackTest/testdata/AndroidManifest.xml
new file mode 100644
index 0000000..f21ec89
--- /dev/null
+++ b/tests/RollbackTest/testdata/AndroidManifest.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.apex.apkrollback.test">
+    <!-- APEX does not have classes.dex -->
+    <application android:hasCode="false" />
+    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="29"/>
+</manifest>
+
diff --git a/tests/RollbackTest/testdata/manifest_v1.json b/tests/RollbackTest/testdata/manifest_v1.json
new file mode 100644
index 0000000..1762fc6
--- /dev/null
+++ b/tests/RollbackTest/testdata/manifest_v1.json
@@ -0,0 +1,4 @@
+{
+  "name": "com.android.apex.apkrollback.test",
+  "version": 1
+}
diff --git a/tests/RollbackTest/testdata/manifest_v2.json b/tests/RollbackTest/testdata/manifest_v2.json
new file mode 100644
index 0000000..c5127b9c
--- /dev/null
+++ b/tests/RollbackTest/testdata/manifest_v2.json
@@ -0,0 +1,4 @@
+{
+  "name": "com.android.apex.apkrollback.test",
+  "version": 2
+}
diff --git a/tests/SoundTriggerTests/src/android/hardware/soundtrigger/SoundTriggerTest.java b/tests/SoundTriggerTests/src/android/hardware/soundtrigger/SoundTriggerTest.java
index 65a3d8a..c900eae 100644
--- a/tests/SoundTriggerTests/src/android/hardware/soundtrigger/SoundTriggerTest.java
+++ b/tests/SoundTriggerTests/src/android/hardware/soundtrigger/SoundTriggerTest.java
@@ -30,6 +30,7 @@
 import android.test.suitebuilder.annotation.SmallTest;
 
 import java.util.Arrays;
+import java.util.Locale;
 import java.util.Random;
 import java.util.UUID;
 
@@ -38,7 +39,8 @@
 
     @SmallTest
     public void testKeyphraseParcelUnparcel_noUsers() throws Exception {
-        Keyphrase keyphrase = new Keyphrase(1, 0, "en-US", "hello", null);
+        Keyphrase keyphrase = new Keyphrase(1, 0,
+                Locale.forLanguageTag("en-US"), "hello", null);
 
         // Write to a parcel
         Parcel parcel = Parcel.obtain();
@@ -57,7 +59,8 @@
 
     @SmallTest
     public void testKeyphraseParcelUnparcel_zeroUsers() throws Exception {
-        Keyphrase keyphrase = new Keyphrase(1, 0, "en-US", "hello", new int[0]);
+        Keyphrase keyphrase = new Keyphrase(1, 0,
+                Locale.forLanguageTag("en-US"), "hello", new int[0]);
 
         // Write to a parcel
         Parcel parcel = Parcel.obtain();
@@ -76,7 +79,8 @@
 
     @SmallTest
     public void testKeyphraseParcelUnparcel_pos() throws Exception {
-        Keyphrase keyphrase = new Keyphrase(1, 0, "en-US", "hello", new int[] {1, 2, 3, 4, 5});
+        Keyphrase keyphrase = new Keyphrase(1, 0,
+                Locale.forLanguageTag("en-US"), "hello", new int[] {1, 2, 3, 4, 5});
 
         // Write to a parcel
         Parcel parcel = Parcel.obtain();
@@ -96,8 +100,10 @@
     @SmallTest
     public void testKeyphraseSoundModelParcelUnparcel_noData() throws Exception {
         Keyphrase[] keyphrases = new Keyphrase[2];
-        keyphrases[0] = new Keyphrase(1, 0, "en-US", "hello", new int[] {0});
-        keyphrases[1] = new Keyphrase(2, 0, "fr-FR", "there", new int[] {1, 2});
+        keyphrases[0] = new Keyphrase(1, 0, Locale.forLanguageTag("en-US"),
+                "hello", new int[] {0});
+        keyphrases[1] = new Keyphrase(2, 0, Locale.forLanguageTag("fr-FR"),
+                "there", new int[] {1, 2});
         KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), UUID.randomUUID(),
                 null, keyphrases);
 
@@ -119,8 +125,10 @@
     @SmallTest
     public void testKeyphraseSoundModelParcelUnparcel_zeroData() throws Exception {
         Keyphrase[] keyphrases = new Keyphrase[2];
-        keyphrases[0] = new Keyphrase(1, 0, "en-US", "hello", new int[] {0});
-        keyphrases[1] = new Keyphrase(2, 0, "fr-FR", "there", new int[] {1, 2});
+        keyphrases[0] = new Keyphrase(1, 0, Locale.forLanguageTag("en-US"),
+                "hello", new int[] {0});
+        keyphrases[1] = new Keyphrase(2, 0, Locale.forLanguageTag("fr-FR"),
+                "there", new int[] {1, 2});
         KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), UUID.randomUUID(),
                 new byte[0], keyphrases);
 
@@ -186,8 +194,10 @@
     @LargeTest
     public void testKeyphraseSoundModelParcelUnparcel_largeData() throws Exception {
         Keyphrase[] keyphrases = new Keyphrase[2];
-        keyphrases[0] = new Keyphrase(1, 0, "en-US", "hello", new int[] {0});
-        keyphrases[1] = new Keyphrase(2, 0, "fr-FR", "there", new int[] {1, 2});
+        keyphrases[0] = new Keyphrase(1, 0, Locale.forLanguageTag("en-US"),
+                "hello", new int[] {0});
+        keyphrases[1] = new Keyphrase(2, 0, Locale.forLanguageTag("fr-FR"),
+                "there", new int[] {1, 2});
         byte[] data = new byte[200 * 1024];
         mRandom.nextBytes(data);
         KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), UUID.randomUUID(),
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java
index 6687f83..6169671 100644
--- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java
@@ -46,15 +46,15 @@
 
         mView.setZOrderOnTop(true);
         mView.getHolder().addCallback(this);
+
+        addEmbeddedView();
     }
 
-    @Override
-    public void surfaceCreated(SurfaceHolder holder) {
+    void addEmbeddedView() {
         mVr = new SurfaceControlViewHost(this, this.getDisplay(),
-                mView.getInputToken());
+                mView.getHostToken());
 
-        final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-        t.reparent(mVr.getSurfacePackage().getSurfaceControl(), mView.getSurfaceControl()).apply();
+        mView.setChildSurfacePackage(mVr.getSurfacePackage());
 
         Button v = new Button(this);
         v.setBackgroundColor(Color.BLUE);
@@ -70,6 +70,10 @@
     }
 
     @Override
+    public void surfaceCreated(SurfaceHolder holder) {
+    }
+
+    @Override
     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
         Canvas canvas = holder.lockCanvas();
         canvas.drawColor(Color.GREEN);
diff --git a/tests/TaskOrganizerTest/Android.bp b/tests/TaskOrganizerTest/Android.bp
new file mode 100644
index 0000000..8a13dbc
--- /dev/null
+++ b/tests/TaskOrganizerTest/Android.bp
@@ -0,0 +1,22 @@
+//
+// 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.
+//
+
+android_test {
+    name: "TaskOrganizerTest",
+    srcs: ["**/*.java"],
+    platform_apis: true,
+    certificate: "platform",
+}
diff --git a/tests/TaskOrganizerTest/AndroidManifest.xml b/tests/TaskOrganizerTest/AndroidManifest.xml
new file mode 100644
index 0000000..0cb6c10
--- /dev/null
+++ b/tests/TaskOrganizerTest/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.test.taskembed">
+    <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
+    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+    <application>
+      <service android:name=".TaskOrganizerPipTest"
+               android:exported="true">
+      </service>
+    </application>
+</manifest>
diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java
new file mode 100644
index 0000000..6ffa19d
--- /dev/null
+++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.test.taskembed;
+
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.Service;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.view.ITaskOrganizer;
+import android.view.IWindowContainer;
+import android.view.WindowContainerTransaction;
+import android.view.SurfaceControl;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+public class TaskOrganizerPipTest extends Service {
+    static final int PIP_WIDTH  = 640;
+    static final int PIP_HEIGHT = 360;
+
+    class PipOrgView extends SurfaceView implements SurfaceHolder.Callback {
+        PipOrgView(Context c) {
+            super(c);
+            getHolder().addCallback(this);
+            setZOrderOnTop(true);
+        }
+        @Override
+        public void surfaceCreated(SurfaceHolder holder) {
+            try {
+                ActivityTaskManager.getService().registerTaskOrganizer(mOrganizer,
+                        WindowConfiguration.WINDOWING_MODE_PINNED);
+            } catch (Exception e) {
+            }
+        }
+
+        @Override
+        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+        }
+
+        @Override
+        public void surfaceDestroyed(SurfaceHolder holder) {
+        }
+
+        void reparentTask(IWindowContainer wc) {
+            SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            SurfaceControl leash = null;
+            try {
+                leash = wc.getLeash();
+            } catch (Exception e) {
+                // System server died.. oh well
+            }
+            t.reparent(leash, getSurfaceControl())
+                .setPosition(leash, 0, 0)
+                .apply();
+        }
+    }
+
+    PipOrgView mPipView;
+
+    class Organizer extends ITaskOrganizer.Stub {
+        public void taskAppeared(IWindowContainer wc, ActivityManager.RunningTaskInfo ti) {
+            mPipView.reparentTask(wc);
+
+            final WindowContainerTransaction wct = new WindowContainerTransaction();
+            wct.scheduleFinishEnterPip(wc, new Rect(0, 0, PIP_WIDTH, PIP_HEIGHT));
+            try {
+                ActivityTaskManager.getService().applyContainerTransaction(wct);
+            } catch (Exception e) {
+            }
+        }
+        public void taskVanished(IWindowContainer wc) {
+        }
+        public void transactionReady(int id, SurfaceControl.Transaction t) {
+        }
+    }
+
+    Organizer mOrganizer = new Organizer();
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+
+        final WindowManager.LayoutParams wlp = new WindowManager.LayoutParams();
+        wlp.setTitle("TaskOrganizerPipTest");
+        wlp.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+        wlp.width = wlp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+
+        FrameLayout layout = new FrameLayout(this);
+        ViewGroup.LayoutParams lp =
+            new ViewGroup.LayoutParams(PIP_WIDTH, PIP_HEIGHT);
+        mPipView = new PipOrgView(this);
+        layout.addView(mPipView, lp);
+
+        WindowManager wm = getSystemService(WindowManager.class);
+        wm.addView(layout, wlp);
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+    }
+}
diff --git a/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java b/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java
index 54c944f9..b357ad0 100644
--- a/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java
+++ b/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java
@@ -16,9 +16,6 @@
 
 package com.android.test.voiceenrollment;
 
-import java.util.Random;
-import java.util.UUID;
-
 import android.app.Activity;
 import android.hardware.soundtrigger.SoundTrigger;
 import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
@@ -29,6 +26,13 @@
 import android.view.View;
 import android.widget.Toast;
 
+import java.util.Locale;
+import java.util.Random;
+import java.util.UUID;
+
+/**
+ * TODO: must be transitioned to a service.
+ */
 public class TestEnrollmentActivity extends Activity {
     private static final String TAG = "TestEnrollmentActivity";
     private static final boolean DBG = false;
@@ -56,7 +60,8 @@
      * Performs a fresh enrollment.
      */
     public void onEnrollButtonClicked(View v) {
-        Keyphrase kp = new Keyphrase(KEYPHRASE_ID, RECOGNITION_MODES, BCP47_LOCALE, TEXT,
+        Keyphrase kp = new Keyphrase(KEYPHRASE_ID, RECOGNITION_MODES,
+                Locale.forLanguageTag(BCP47_LOCALE), TEXT,
                 new int[] { UserManager.get(this).getUserHandle() /* current user */});
         UUID modelUuid = UUID.randomUUID();
         // Generate a fake model to push.
diff --git a/tests/WindowInsetsTests/Android.bp b/tests/WindowInsetsTests/Android.bp
new file mode 100644
index 0000000..12395e7
--- /dev/null
+++ b/tests/WindowInsetsTests/Android.bp
@@ -0,0 +1,22 @@
+// 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: "WindowInsetsTests",
+    srcs: ["src/**/*.java"],
+    resource_dirs: ["res"],
+    certificate: "platform",
+    platform_apis: true,
+}
+
diff --git a/tests/WindowInsetsTests/AndroidManifest.xml b/tests/WindowInsetsTests/AndroidManifest.xml
new file mode 100644
index 0000000..8d33f70
--- /dev/null
+++ b/tests/WindowInsetsTests/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (018C) 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
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.google.android.test.windowinsetstests">
+
+    <application android:label="@string/activity_title">
+        <activity android:name=".WindowInsetsActivity"
+            android:theme="@android:style/Theme.Material"
+            android:windowSoftInputMode="adjustResize">
+
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/WindowInsetsTests/res/layout/window_inset_activity.xml b/tests/WindowInsetsTests/res/layout/window_inset_activity.xml
new file mode 100644
index 0000000..38e0029
--- /dev/null
+++ b/tests/WindowInsetsTests/res/layout/window_inset_activity.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center"
+    android:id="@+id/root">
+
+    <Button
+        android:id="@+id/button"
+        android:layout_width="wrap_content"
+        android:layout_height="48dp"
+        android:text="Hello insets" />
+
+</LinearLayout>
+
diff --git a/tests/WindowInsetsTests/res/values/strings.xml b/tests/WindowInsetsTests/res/values/strings.xml
new file mode 100644
index 0000000..242823d
--- /dev/null
+++ b/tests/WindowInsetsTests/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<resources>
+    <string name="activity_title">Window Insets Tests</string>
+</resources>
diff --git a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java
new file mode 100644
index 0000000..01e212d
--- /dev/null
+++ b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java
@@ -0,0 +1,152 @@
+/*
+ * 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.google.android.test.windowinsetstests;
+
+import android.animation.ObjectAnimator;
+import android.animation.TypeEvaluator;
+import android.animation.ValueAnimator;
+import android.app.Activity;
+import android.graphics.Insets;
+import android.os.Bundle;
+import android.util.Log;
+import android.util.Property;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowInsets.Type;
+import android.view.WindowInsetsAnimationCallback;
+import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
+import android.view.WindowInsetsAnimationControlListener;
+import android.view.WindowInsetsAnimationController;
+
+import com.google.android.test.windowinsetstests.R;
+
+public class WindowInsetsActivity extends Activity {
+
+    private View mRoot;
+    private View mButton;
+
+    private static class InsetsProperty extends Property<WindowInsetsAnimationController, Insets> {
+
+        private final View mViewToAnimate;
+        private final Insets mShowingInsets;
+
+        public InsetsProperty(View viewToAnimate, Insets showingInsets) {
+            super(Insets.class, "Insets");
+            mViewToAnimate = viewToAnimate;
+            mShowingInsets = showingInsets;
+        }
+
+        @Override
+        public Insets get(WindowInsetsAnimationController object) {
+            return object.getCurrentInsets();
+        }
+
+        @Override
+        public void set(WindowInsetsAnimationController object, Insets value) {
+            object.setInsetsAndAlpha(value, 1.0f, 0.5f);
+            if (mShowingInsets.bottom != 0) {
+                mViewToAnimate.setTranslationY(mShowingInsets.bottom - value.bottom);
+            } else if (mShowingInsets.right != 0) {
+                mViewToAnimate.setTranslationX(mShowingInsets.right - value.right);
+            } else if (mShowingInsets.left != 0) {
+                mViewToAnimate.setTranslationX(value.left - mShowingInsets.left);
+            }
+        }
+    };
+
+    float startY;
+    float endY;
+    InsetsAnimation imeAnim;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.window_inset_activity);
+        mRoot = findViewById(R.id.root);
+        mButton = findViewById(R.id.button);
+        mButton.setOnClickListener(v -> {
+            if (!v.getRootWindowInsets().isVisible(Type.ime())) {
+                v.getWindowInsetsController().show(Type.ime());
+            } else {
+                v.getWindowInsetsController().hide(Type.ime());
+            }
+        });
+        mRoot.setWindowInsetsAnimationCallback(new WindowInsetsAnimationCallback() {
+
+            @Override
+            public int getDispatchMode() {
+                return DISPATCH_MODE_STOP;
+            }
+
+            @Override
+            public void onPrepare(InsetsAnimation animation) {
+                if ((animation.getTypeMask() & Type.ime()) != 0) {
+                    imeAnim = animation;
+                }
+                startY = mButton.getTop();
+            }
+
+            @Override
+            public WindowInsets onProgress(WindowInsets insets) {
+                mButton.setY(startY + (endY - startY) * imeAnim.getInterpolatedFraction());
+                return insets;
+            }
+
+            @Override
+            public AnimationBounds onStart(InsetsAnimation animation,
+                    AnimationBounds bounds) {
+                endY = mButton.getTop();
+                return bounds;
+            }
+
+            @Override
+            public void onFinish(InsetsAnimation animation) {
+                imeAnim = null;
+            }
+        });
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        TypeEvaluator<Insets> evaluator = (fraction, startValue, endValue) -> Insets.of(
+                (int)(startValue.left + fraction * (endValue.left - startValue.left)),
+                (int)(startValue.top + fraction * (endValue.top - startValue.top)),
+                (int)(startValue.right + fraction * (endValue.right - startValue.right)),
+                (int)(startValue.bottom + fraction * (endValue.bottom - startValue.bottom)));
+
+        WindowInsetsAnimationControlListener listener = new WindowInsetsAnimationControlListener() {
+            @Override
+            public void onReady(WindowInsetsAnimationController controller, int types) {
+                ObjectAnimator animator = ObjectAnimator.ofObject(controller,
+                        new InsetsProperty(findViewById(R.id.button),
+                                controller.getShownStateInsets()),
+                        evaluator, controller.getShownStateInsets(),
+                        controller.getHiddenStateInsets());
+                animator.setRepeatCount(ValueAnimator.INFINITE);
+                animator.setRepeatMode(ValueAnimator.REVERSE);
+                animator.start();
+            }
+
+            @Override
+            public void onCancelled() {
+
+            }
+        };
+    }
+}
diff --git a/tests/net/common/java/android/net/CaptivePortalTest.java b/tests/net/common/java/android/net/CaptivePortalTest.java
index eed7159f..ca4ba63 100644
--- a/tests/net/common/java/android/net/CaptivePortalTest.java
+++ b/tests/net/common/java/android/net/CaptivePortalTest.java
@@ -44,6 +44,11 @@
         }
 
         @Override
+        public void appRequest(final int request) throws RemoteException {
+            mCode = request;
+        }
+
+        @Override
         public void logEvent(int eventId, String packageName) throws RemoteException {
             mCode = eventId;
             mPackageName = packageName;
@@ -80,6 +85,12 @@
     }
 
     @Test
+    public void testReevaluateNetwork() {
+        final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.reevaluateNetwork());
+        assertEquals(result.mCode, CaptivePortal.APP_REQUEST_REEVALUATION_REQUIRED);
+    }
+
+    @Test
     public void testLogEvent() {
         final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.logEvent(
                 MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_ACTIVITY,
diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java
index a7328ac..f25fd4d 100644
--- a/tests/net/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/common/java/android/net/LinkPropertiesTest.java
@@ -27,8 +27,8 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import android.net.LinkProperties.CompareResult;
 import android.net.LinkProperties.ProvisioningChange;
+import android.net.util.LinkPropertiesUtils.CompareResult;
 import android.system.OsConstants;
 import android.util.ArraySet;
 
@@ -75,6 +75,9 @@
     private static final LinkAddress LINKADDRV4 = new LinkAddress(ADDRV4, 32);
     private static final LinkAddress LINKADDRV6 = new LinkAddress(ADDRV6, 128);
     private static final LinkAddress LINKADDRV6LINKLOCAL = new LinkAddress("fe80::1/64");
+    private static final Uri CAPPORT_API_URL = Uri.parse("https://test.example.com/capportapi");
+    private static final CaptivePortalData CAPPORT_DATA = new CaptivePortalData.Builder()
+            .setVenueInfoUrl(Uri.parse("https://test.example.com/venue")).build();
 
     private static InetAddress address(String addrString) {
         return InetAddresses.parseNumericAddress(addrString);
@@ -101,6 +104,8 @@
         assertFalse(lp.isIpv6Provisioned());
         assertFalse(lp.isPrivateDnsActive());
         assertFalse(lp.isWakeOnLanSupported());
+        assertNull(lp.getCaptivePortalApiUrl());
+        assertNull(lp.getCaptivePortalData());
     }
 
     private LinkProperties makeTestObject() {
@@ -124,6 +129,8 @@
         lp.setNat64Prefix(new IpPrefix("2001:db8:0:64::/96"));
         lp.setDhcpServerAddress(DHCPSERVER);
         lp.setWakeOnLanSupported(true);
+        lp.setCaptivePortalApiUrl(CAPPORT_API_URL);
+        lp.setCaptivePortalData(CAPPORT_DATA);
         return lp;
     }
 
@@ -165,6 +172,12 @@
         assertTrue(source.isIdenticalWakeOnLan(target));
         assertTrue(target.isIdenticalWakeOnLan(source));
 
+        assertTrue(source.isIdenticalCaptivePortalApiUrl(target));
+        assertTrue(target.isIdenticalCaptivePortalApiUrl(source));
+
+        assertTrue(source.isIdenticalCaptivePortalData(target));
+        assertTrue(target.isIdenticalCaptivePortalData(source));
+
         // Check result of equals().
         assertTrue(source.equals(target));
         assertTrue(target.equals(source));
@@ -963,6 +976,8 @@
         source.setNat64Prefix(new IpPrefix("2001:db8:1:2:64:64::/96"));
 
         source.setWakeOnLanSupported(true);
+        source.setCaptivePortalApiUrl(CAPPORT_API_URL);
+        source.setCaptivePortalData(CAPPORT_DATA);
 
         source.setDhcpServerAddress((Inet4Address) GATEWAY1);
 
@@ -970,7 +985,13 @@
         stacked.setInterfaceName("test-stacked");
         source.addStackedLink(stacked);
 
-        assertParcelSane(source, 16 /* fieldCount */);
+        assertParcelSane(source.makeSensitiveFieldsParcelingCopy(), 18 /* fieldCount */);
+
+        // Verify that without using a sensitiveFieldsParcelingCopy, sensitive fields are cleared.
+        final LinkProperties sanitized = new LinkProperties(source);
+        sanitized.setCaptivePortalApiUrl(null);
+        sanitized.setCaptivePortalData(null);
+        assertEquals(sanitized, parcelingRoundTrip(source));
     }
 
     @Test
@@ -1113,4 +1134,22 @@
         lp.clear();
         assertFalse(lp.isWakeOnLanSupported());
     }
+
+    @Test
+    public void testCaptivePortalApiUrl() {
+        final LinkProperties lp = makeTestObject();
+        assertEquals(CAPPORT_API_URL, lp.getCaptivePortalApiUrl());
+
+        lp.clear();
+        assertNull(lp.getCaptivePortalApiUrl());
+    }
+
+    @Test
+    public void testCaptivePortalData() {
+        final LinkProperties lp = makeTestObject();
+        assertEquals(CAPPORT_DATA, lp.getCaptivePortalData());
+
+        lp.clear();
+        assertNull(lp.getCaptivePortalData());
+    }
 }
diff --git a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
new file mode 100644
index 0000000..d250ad3
--- /dev/null
+++ b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.net
+
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.testutils.assertParcelSane
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class NetworkAgentConfigTest {
+    @Test
+    fun testParcelNetworkAgentConfig() {
+        val config = NetworkAgentConfig.Builder().apply {
+            setExplicitlySelected(true)
+            setLegacyType(ConnectivityManager.TYPE_ETHERNET)
+            setSubscriberId("MySubId")
+            setPartialConnectivityAcceptable(false)
+            setUnvalidatedConnectivityAcceptable(true)
+        }.build()
+        assertParcelSane(config, 9)
+    }
+}
diff --git a/tests/net/common/java/android/net/RouteInfoTest.java b/tests/net/common/java/android/net/RouteInfoTest.java
index 5ce8436..fe51b3a 100644
--- a/tests/net/common/java/android/net/RouteInfoTest.java
+++ b/tests/net/common/java/android/net/RouteInfoTest.java
@@ -258,6 +258,16 @@
         assertParcelingIsLossless(r);
 
         r = new RouteInfo(Prefix("192.0.2.0/24"), null, "wlan0");
-        assertParcelSane(r, 6);
+        assertParcelSane(r, 7);
+    }
+
+    public void testMtu() {
+        RouteInfo r;
+        r = new RouteInfo(Prefix("0.0.0.0/0"), Address("0.0.0.0"), "wlan0",
+                RouteInfo.RTN_UNICAST, 1500);
+        assertEquals(1500, r.getMtu());
+
+        r = new RouteInfo(Prefix("0.0.0.0/0"), Address("0.0.0.0"), "wlan0");
+        assertEquals(0, r.getMtu());
     }
 }
diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
index 1e8d83c..a35fb40 100644
--- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
+++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
@@ -16,6 +16,7 @@
 
 package com.android.server;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
@@ -35,10 +36,10 @@
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
-import android.net.NetworkFactory;
 import android.net.NetworkInfo;
-import android.net.NetworkMisc;
+import android.net.NetworkProvider;
 import android.net.NetworkSpecifier;
 import android.net.SocketKeepalive;
 import android.net.UidRange;
@@ -74,6 +75,7 @@
         final String typeName = ConnectivityManager.getNetworkTypeName(type);
         mNetworkInfo = new NetworkInfo(type, 0, typeName, "Mock");
         mNetworkCapabilities = new NetworkCapabilities();
+        mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
         mNetworkCapabilities.addTransportType(transport);
         switch (transport) {
             case TRANSPORT_ETHERNET:
@@ -114,7 +116,7 @@
         public InstrumentedNetworkAgent(NetworkAgentWrapper wrapper, LinkProperties lp) {
             super(wrapper.mHandlerThread.getLooper(), wrapper.mContext, wrapper.mLogTag,
                     wrapper.mNetworkInfo, wrapper.mNetworkCapabilities, lp, wrapper.mScore,
-                    new NetworkMisc(), NetworkFactory.SerialNumber.NONE);
+                    new NetworkAgentConfig(), NetworkProvider.ID_NONE);
             mWrapper = wrapper;
         }
 
@@ -206,13 +208,11 @@
     }
 
     public void suspend() {
-        mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.SUSPENDED, null, null);
-        mNetworkAgent.sendNetworkInfo(mNetworkInfo);
+        removeCapability(NET_CAPABILITY_NOT_SUSPENDED);
     }
 
     public void resume() {
-        mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null, null);
-        mNetworkAgent.sendNetworkInfo(mNetworkInfo);
+        addCapability(NET_CAPABILITY_NOT_SUSPENDED);
     }
 
     public void disconnect() {
@@ -222,7 +222,7 @@
 
     @Override
     public Network getNetwork() {
-        return new Network(mNetworkAgent.netId);
+        return mNetworkAgent.getNetwork();
     }
 
     public void expectPreventReconnectReceived(long timeoutMs) {
diff --git a/tests/net/java/android/net/CaptivePortalDataTest.kt b/tests/net/java/android/net/CaptivePortalDataTest.kt
new file mode 100644
index 0000000..0071438
--- /dev/null
+++ b/tests/net/java/android/net/CaptivePortalDataTest.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.net
+
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.testutils.assertParcelSane
+import com.android.testutils.assertParcelingIsLossless
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+import kotlin.test.assertNotEquals
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CaptivePortalDataTest {
+    private val data = CaptivePortalData.Builder()
+            .setRefreshTime(123L)
+            .setUserPortalUrl(Uri.parse("https://portal.example.com/test"))
+            .setVenueInfoUrl(Uri.parse("https://venue.example.com/test"))
+            .setSessionExtendable(true)
+            .setBytesRemaining(456L)
+            .setExpiryTime(789L)
+            .setCaptive(true)
+            .build()
+
+    private fun makeBuilder() = CaptivePortalData.Builder(data)
+
+    @Test
+    fun testParcelUnparcel() {
+        assertParcelSane(data, fieldCount = 7)
+
+        assertParcelingIsLossless(makeBuilder().setUserPortalUrl(null).build())
+        assertParcelingIsLossless(makeBuilder().setVenueInfoUrl(null).build())
+    }
+
+    @Test
+    fun testEquals() {
+        assertEquals(data, makeBuilder().build())
+
+        assertNotEqualsAfterChange { it.setRefreshTime(456L) }
+        assertNotEqualsAfterChange { it.setUserPortalUrl(Uri.parse("https://example.com/")) }
+        assertNotEqualsAfterChange { it.setUserPortalUrl(null) }
+        assertNotEqualsAfterChange { it.setVenueInfoUrl(Uri.parse("https://example.com/")) }
+        assertNotEqualsAfterChange { it.setVenueInfoUrl(null) }
+        assertNotEqualsAfterChange { it.setSessionExtendable(false) }
+        assertNotEqualsAfterChange { it.setBytesRemaining(789L) }
+        assertNotEqualsAfterChange { it.setExpiryTime(12L) }
+        assertNotEqualsAfterChange { it.setCaptive(false) }
+    }
+
+    private fun CaptivePortalData.mutate(mutator: (CaptivePortalData.Builder) -> Unit) =
+            CaptivePortalData.Builder(this).apply { mutator(this) }.build()
+
+    private fun assertNotEqualsAfterChange(mutator: (CaptivePortalData.Builder) -> Unit) {
+        assertNotEquals(data, data.mutate(mutator))
+    }
+}
\ No newline at end of file
diff --git a/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java
new file mode 100644
index 0000000..065add4
--- /dev/null
+++ b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java
@@ -0,0 +1,196 @@
+/*
+ * 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;
+
+import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
+import static android.net.ConnectivityDiagnosticsManager.DataStallReport;
+
+import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.PersistableBundle;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ConnectivityDiagnosticsManagerTest {
+    private static final int NET_ID = 1;
+    private static final int DETECTION_METHOD = 2;
+    private static final long TIMESTAMP = 10L;
+    private static final String INTERFACE_NAME = "interface";
+    private static final String BUNDLE_KEY = "key";
+    private static final String BUNDLE_VALUE = "value";
+
+    private ConnectivityReport createSampleConnectivityReport() {
+        final LinkProperties linkProperties = new LinkProperties();
+        linkProperties.setInterfaceName(INTERFACE_NAME);
+
+        final NetworkCapabilities networkCapabilities = new NetworkCapabilities();
+        networkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
+
+        final PersistableBundle bundle = new PersistableBundle();
+        bundle.putString(BUNDLE_KEY, BUNDLE_VALUE);
+
+        return new ConnectivityReport(
+                new Network(NET_ID), TIMESTAMP, linkProperties, networkCapabilities, bundle);
+    }
+
+    private ConnectivityReport createDefaultConnectivityReport() {
+        return new ConnectivityReport(
+                new Network(0),
+                0L,
+                new LinkProperties(),
+                new NetworkCapabilities(),
+                PersistableBundle.EMPTY);
+    }
+
+    @Test
+    public void testPersistableBundleEquals() {
+        assertFalse(
+                ConnectivityDiagnosticsManager.persistableBundleEquals(
+                        null, PersistableBundle.EMPTY));
+        assertFalse(
+                ConnectivityDiagnosticsManager.persistableBundleEquals(
+                        PersistableBundle.EMPTY, null));
+        assertTrue(
+                ConnectivityDiagnosticsManager.persistableBundleEquals(
+                        PersistableBundle.EMPTY, PersistableBundle.EMPTY));
+
+        final PersistableBundle a = new PersistableBundle();
+        a.putString(BUNDLE_KEY, BUNDLE_VALUE);
+
+        final PersistableBundle b = new PersistableBundle();
+        b.putString(BUNDLE_KEY, BUNDLE_VALUE);
+
+        final PersistableBundle c = new PersistableBundle();
+        c.putString(BUNDLE_KEY, null);
+
+        assertFalse(
+                ConnectivityDiagnosticsManager.persistableBundleEquals(PersistableBundle.EMPTY, a));
+        assertFalse(
+                ConnectivityDiagnosticsManager.persistableBundleEquals(a, PersistableBundle.EMPTY));
+
+        assertTrue(ConnectivityDiagnosticsManager.persistableBundleEquals(a, b));
+        assertTrue(ConnectivityDiagnosticsManager.persistableBundleEquals(b, a));
+
+        assertFalse(ConnectivityDiagnosticsManager.persistableBundleEquals(a, c));
+        assertFalse(ConnectivityDiagnosticsManager.persistableBundleEquals(c, a));
+    }
+
+    @Test
+    public void testConnectivityReportEquals() {
+        assertEquals(createSampleConnectivityReport(), createSampleConnectivityReport());
+        assertEquals(createDefaultConnectivityReport(), createDefaultConnectivityReport());
+
+        final LinkProperties linkProperties = new LinkProperties();
+        linkProperties.setInterfaceName(INTERFACE_NAME);
+
+        final NetworkCapabilities networkCapabilities = new NetworkCapabilities();
+        networkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
+
+        final PersistableBundle bundle = new PersistableBundle();
+        bundle.putString(BUNDLE_KEY, BUNDLE_VALUE);
+
+        assertNotEquals(
+                createDefaultConnectivityReport(),
+                new ConnectivityReport(
+                        new Network(NET_ID),
+                        0L,
+                        new LinkProperties(),
+                        new NetworkCapabilities(),
+                        PersistableBundle.EMPTY));
+        assertNotEquals(
+                createDefaultConnectivityReport(),
+                new ConnectivityReport(
+                        new Network(0),
+                        TIMESTAMP,
+                        new LinkProperties(),
+                        new NetworkCapabilities(),
+                        PersistableBundle.EMPTY));
+        assertNotEquals(
+                createDefaultConnectivityReport(),
+                new ConnectivityReport(
+                        new Network(0),
+                        0L,
+                        linkProperties,
+                        new NetworkCapabilities(),
+                        PersistableBundle.EMPTY));
+        assertNotEquals(
+                createDefaultConnectivityReport(),
+                new ConnectivityReport(
+                        new Network(0),
+                        TIMESTAMP,
+                        new LinkProperties(),
+                        networkCapabilities,
+                        PersistableBundle.EMPTY));
+        assertNotEquals(
+                createDefaultConnectivityReport(),
+                new ConnectivityReport(
+                        new Network(0),
+                        TIMESTAMP,
+                        new LinkProperties(),
+                        new NetworkCapabilities(),
+                        bundle));
+    }
+
+    @Test
+    public void testConnectivityReportParcelUnparcel() {
+        assertParcelSane(createSampleConnectivityReport(), 5);
+    }
+
+    private DataStallReport createSampleDataStallReport() {
+        final PersistableBundle bundle = new PersistableBundle();
+        bundle.putString(BUNDLE_KEY, BUNDLE_VALUE);
+        return new DataStallReport(new Network(NET_ID), TIMESTAMP, DETECTION_METHOD, bundle);
+    }
+
+    private DataStallReport createDefaultDataStallReport() {
+        return new DataStallReport(new Network(0), 0L, 0, PersistableBundle.EMPTY);
+    }
+
+    @Test
+    public void testDataStallReportEquals() {
+        assertEquals(createSampleDataStallReport(), createSampleDataStallReport());
+        assertEquals(createDefaultDataStallReport(), createDefaultDataStallReport());
+
+        final PersistableBundle bundle = new PersistableBundle();
+        bundle.putString(BUNDLE_KEY, BUNDLE_VALUE);
+
+        assertNotEquals(
+                createDefaultDataStallReport(),
+                new DataStallReport(new Network(NET_ID), 0L, 0, PersistableBundle.EMPTY));
+        assertNotEquals(
+                createDefaultDataStallReport(),
+                new DataStallReport(new Network(0), TIMESTAMP, 0, PersistableBundle.EMPTY));
+        assertNotEquals(
+                createDefaultDataStallReport(),
+                new DataStallReport(new Network(0), 0L, DETECTION_METHOD, PersistableBundle.EMPTY));
+        assertNotEquals(
+                createDefaultDataStallReport(), new DataStallReport(new Network(0), 0L, 0, bundle));
+    }
+
+    @Test
+    public void testDataStallReportParcelUnparcel() {
+        assertParcelSane(createSampleDataStallReport(), 4);
+    }
+}
diff --git a/tests/net/java/android/net/MacAddressTest.java b/tests/net/java/android/net/MacAddressTest.java
index daf187d..91c9a2a 100644
--- a/tests/net/java/android/net/MacAddressTest.java
+++ b/tests/net/java/android/net/MacAddressTest.java
@@ -22,6 +22,8 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.net.util.MacAddressUtils;
+
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -122,11 +124,11 @@
 
         for (MacAddress mac : multicastAddresses) {
             String msg = mac.toString() + " expected to be a multicast address";
-            assertTrue(msg, mac.isMulticastAddress());
+            assertTrue(msg, MacAddressUtils.isMulticastAddress(mac));
         }
         for (MacAddress mac : unicastAddresses) {
             String msg = mac.toString() + " expected not to be a multicast address";
-            assertFalse(msg, mac.isMulticastAddress());
+            assertFalse(msg, MacAddressUtils.isMulticastAddress(mac));
         }
     }
 
@@ -156,7 +158,7 @@
     public void testMacAddressConversions() {
         final int iterations = 10000;
         for (int i = 0; i < iterations; i++) {
-            MacAddress mac = MacAddress.createRandomUnicastAddress();
+            MacAddress mac = MacAddressUtils.createRandomUnicastAddress();
 
             String stringRepr = mac.toString();
             byte[] bytesRepr = mac.toByteArray();
@@ -188,7 +190,7 @@
         final String expectedLocalOui = "26:5f:78";
         final MacAddress base = MacAddress.fromString(anotherOui + ":0:0:0");
         for (int i = 0; i < iterations; i++) {
-            MacAddress mac = MacAddress.createRandomUnicastAddress(base, r);
+            MacAddress mac = MacAddressUtils.createRandomUnicastAddress(base, r);
             String stringRepr = mac.toString();
 
             assertTrue(stringRepr + " expected to be a locally assigned address",
@@ -199,7 +201,7 @@
         }
 
         for (int i = 0; i < iterations; i++) {
-            MacAddress mac = MacAddress.createRandomUnicastAddress();
+            MacAddress mac = MacAddressUtils.createRandomUnicastAddress();
             String stringRepr = mac.toString();
 
             assertTrue(stringRepr + " expected to be a locally assigned address",
diff --git a/tests/net/java/android/net/NetworkStatsTest.java b/tests/net/java/android/net/NetworkStatsTest.java
index c16a0f4..33d77d2 100644
--- a/tests/net/java/android/net/NetworkStatsTest.java
+++ b/tests/net/java/android/net/NetworkStatsTest.java
@@ -64,15 +64,15 @@
     @Test
     public void testFindIndex() throws Exception {
         final NetworkStats stats = new NetworkStats(TEST_START, 5)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 1024L, 8L, 0L, 0L, 10)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 0L, 0L, 1024L, 8L, 11)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 0L, 0L, 1024L, 8L, 11)
-                .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 1024L, 8L, 1024L, 8L, 12)
-                .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES,
+                .addEntry(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES,
                         DEFAULT_NETWORK_YES, 1024L, 8L, 1024L, 8L, 12);
 
         assertEquals(4, stats.findIndex(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_YES,
@@ -94,21 +94,21 @@
     @Test
     public void testFindIndexHinted() {
         final NetworkStats stats = new NetworkStats(TEST_START, 3)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 1024L, 8L, 0L, 0L, 10)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 0L, 0L, 1024L, 8L, 11)
-                .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 1024L, 8L, 1024L, 8L, 12)
-                .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 1024L, 8L, 0L, 0L, 10)
-                .addValues(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 0L, 0L, 1024L, 8L, 11)
-                .addValues(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO,
+                .addEntry(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 0L, 0L, 1024L, 8L, 11)
-                .addValues(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 1024L, 8L, 1024L, 8L, 12)
-                .addValues(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES,
+                .addEntry(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES,
                         DEFAULT_NETWORK_NO, 1024L, 8L, 1024L, 8L, 12);
 
         // verify that we correctly find across regardless of hinting
@@ -143,27 +143,27 @@
         assertEquals(0, stats.size());
         assertEquals(4, stats.internalSize());
 
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+        stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                 DEFAULT_NETWORK_YES, 1L, 1L, 2L, 2L, 3);
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+        stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                 DEFAULT_NETWORK_NO, 2L, 2L, 2L, 2L, 4);
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
+        stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
                 DEFAULT_NETWORK_YES, 3L, 3L, 2L, 2L, 5);
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES,
+        stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES,
                 DEFAULT_NETWORK_NO, 3L, 3L, 2L, 2L, 5);
 
         assertEquals(4, stats.size());
         assertEquals(4, stats.internalSize());
 
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+        stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                 DEFAULT_NETWORK_NO, 4L, 40L, 4L, 40L, 7);
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+        stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                 DEFAULT_NETWORK_YES, 5L, 50L, 4L, 40L, 8);
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+        stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                 DEFAULT_NETWORK_NO, 6L, 60L, 5L, 50L, 10);
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
+        stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
                 DEFAULT_NETWORK_YES, 7L, 70L, 5L, 50L, 11);
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES,
+        stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES,
                 DEFAULT_NETWORK_NO, 7L, 70L, 5L, 50L, 11);
 
         assertEquals(9, stats.size());
@@ -193,8 +193,8 @@
     public void testCombineExisting() throws Exception {
         final NetworkStats stats = new NetworkStats(TEST_START, 10);
 
-        stats.addValues(TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 10);
-        stats.addValues(TEST_IFACE, 1001, SET_DEFAULT, 0xff, 128L, 1L, 128L, 1L, 2);
+        stats.addEntry(TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 10);
+        stats.addEntry(TEST_IFACE, 1001, SET_DEFAULT, 0xff, 128L, 1L, 128L, 1L, 2);
         stats.combineValues(TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, -128L, -1L,
                 -128L, -1L, -1);
 
@@ -215,12 +215,12 @@
     @Test
     public void testSubtractIdenticalData() throws Exception {
         final NetworkStats before = new NetworkStats(TEST_START, 2)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
 
         final NetworkStats after = new NetworkStats(TEST_START, 2)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
 
         final NetworkStats result = after.subtract(before);
 
@@ -234,12 +234,12 @@
     @Test
     public void testSubtractIdenticalRows() throws Exception {
         final NetworkStats before = new NetworkStats(TEST_START, 2)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
 
         final NetworkStats after = new NetworkStats(TEST_START, 2)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1025L, 9L, 2L, 1L, 15)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 3L, 1L, 1028L, 9L, 20);
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1025L, 9L, 2L, 1L, 15)
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 3L, 1L, 1028L, 9L, 20);
 
         final NetworkStats result = after.subtract(before);
 
@@ -253,13 +253,13 @@
     @Test
     public void testSubtractNewRows() throws Exception {
         final NetworkStats before = new NetworkStats(TEST_START, 2)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
 
         final NetworkStats after = new NetworkStats(TEST_START, 3)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12)
-                .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 20);
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12)
+                .addEntry(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 20);
 
         final NetworkStats result = after.subtract(before);
 
@@ -275,11 +275,11 @@
     @Test
     public void testSubtractMissingRows() throws Exception {
         final NetworkStats before = new NetworkStats(TEST_START, 2)
-                .addValues(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 1024L, 0L, 0L, 0L, 0)
-                .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 2048L, 0L, 0L, 0L, 0);
+                .addEntry(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 1024L, 0L, 0L, 0L, 0)
+                .addEntry(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 2048L, 0L, 0L, 0L, 0);
 
         final NetworkStats after = new NetworkStats(TEST_START, 1)
-                .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 2049L, 2L, 3L, 4L, 0);
+                .addEntry(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 2049L, 2L, 3L, 4L, 0);
 
         final NetworkStats result = after.subtract(before);
 
@@ -293,40 +293,40 @@
     @Test
     public void testTotalBytes() throws Exception {
         final NetworkStats iface = new NetworkStats(TEST_START, 2)
-                .addValues(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 128L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 256L, 0L, 0L, 0L, 0L);
+                .addEntry(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 128L, 0L, 0L, 0L, 0L)
+                .addEntry(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 256L, 0L, 0L, 0L, 0L);
         assertEquals(384L, iface.getTotalBytes());
 
         final NetworkStats uidSet = new NetworkStats(TEST_START, 3)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_FOREGROUND, TAG_NONE, 32L, 0L, 0L, 0L, 0L);
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L)
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L)
+                .addEntry(TEST_IFACE, 101, SET_FOREGROUND, TAG_NONE, 32L, 0L, 0L, 0L, 0L);
         assertEquals(96L, uidSet.getTotalBytes());
 
         final NetworkStats uidTag = new NetworkStats(TEST_START, 6)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L);
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
+                .addEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
+                .addEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L)
+                .addEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L);
         assertEquals(64L, uidTag.getTotalBytes());
 
         final NetworkStats uidMetered = new NetworkStats(TEST_START, 3)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L);
         assertEquals(96L, uidMetered.getTotalBytes());
 
         final NetworkStats uidRoaming = new NetworkStats(TEST_START, 3)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
                         DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L);
         assertEquals(96L, uidRoaming.getTotalBytes());
     }
@@ -343,11 +343,11 @@
     @Test
     public void testGroupedByIfaceAll() throws Exception {
         final NetworkStats uidStats = new NetworkStats(TEST_START, 3)
-                .addValues(IFACE_ALL, 100, SET_ALL, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(IFACE_ALL, 100, SET_ALL, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 128L, 8L, 0L, 2L, 20L)
-                .addValues(IFACE_ALL, 101, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_NO,
+                .addEntry(IFACE_ALL, 101, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 128L, 8L, 0L, 2L, 20L)
-                .addValues(IFACE_ALL, 101, SET_ALL, TAG_NONE, METERED_NO, ROAMING_YES,
+                .addEntry(IFACE_ALL, 101, SET_ALL, TAG_NONE, METERED_NO, ROAMING_YES,
                         DEFAULT_NETWORK_YES, 128L, 8L, 0L, 2L, 20L);
         final NetworkStats grouped = uidStats.groupedByIface();
 
@@ -361,19 +361,19 @@
     @Test
     public void testGroupedByIface() throws Exception {
         final NetworkStats uidStats = new NetworkStats(TEST_START, 7)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 128L, 8L, 0L, 2L, 20L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 512L, 32L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 64L, 4L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 512L, 32L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 128L, 8L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO,
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 128L, 8L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
                         DEFAULT_NETWORK_YES, 128L, 8L, 0L, 0L, 0L);
 
         final NetworkStats grouped = uidStats.groupedByIface();
@@ -390,19 +390,19 @@
     @Test
     public void testAddAllValues() {
         final NetworkStats first = new NetworkStats(TEST_START, 5)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES,
+                .addEntry(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES,
                         DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L);
 
         final NetworkStats second = new NetworkStats(TEST_START, 2)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES,
+                .addEntry(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES,
                         DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L);
 
         first.combineAllValues(second);
@@ -421,19 +421,19 @@
     @Test
     public void testGetTotal() {
         final NetworkStats stats = new NetworkStats(TEST_START, 7)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 128L, 8L, 0L, 2L, 20L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 512L, 32L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 64L, 4L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 512L,32L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 128L, 8L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 128L, 8L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
                         DEFAULT_NETWORK_NO, 128L, 8L, 0L, 0L, 0L);
 
         assertValues(stats.getTotal(null), 1408L, 88L, 0L, 2L, 20L);
@@ -459,7 +459,7 @@
         assertEquals(0, after.size());
 
         // Test 1 item stats.
-        before.addValues(TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, 1L, 128L, 0L, 2L, 20L);
+        before.addEntry(TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, 1L, 128L, 0L, 2L, 20L);
         after = before.clone();
         after.removeUids(new int[0]);
         assertEquals(1, after.size());
@@ -469,12 +469,12 @@
         assertEquals(0, after.size());
 
         // Append remaining test items.
-        before.addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 2L, 64L, 0L, 2L, 20L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 4L, 32L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 16L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 16L, 8L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 32L, 4L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 64L, 2L, 0L, 0L, 0L);
+        before.addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 2L, 64L, 0L, 2L, 20L)
+                .addEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 4L, 32L, 0L, 0L, 0L)
+                .addEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 16L, 0L, 0L, 0L)
+                .addEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 16L, 8L, 0L, 0L, 0L)
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 32L, 4L, 0L, 0L, 0L)
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 64L, 2L, 0L, 0L, 0L);
         assertEquals(7, before.size());
 
         // Test remove with empty uid list.
@@ -505,12 +505,12 @@
     @Test
     public void testClone() throws Exception {
         final NetworkStats original = new NetworkStats(TEST_START, 5)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L);
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
+                .addEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L);
 
         // make clone and mutate original
         final NetworkStats clone = original.clone();
-        original.addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 0L, 0L);
+        original.addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 0L, 0L);
 
         assertEquals(3, original.size());
         assertEquals(2, clone.size());
@@ -523,8 +523,8 @@
     public void testAddWhenEmpty() throws Exception {
         final NetworkStats red = new NetworkStats(TEST_START, -1);
         final NetworkStats blue = new NetworkStats(TEST_START, 5)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L);
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
+                .addEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L);
 
         // We're mostly checking that we don't crash
         red.combineAllValues(blue);
@@ -537,39 +537,39 @@
         final String underlyingIface = "wlan0";
         final int testTag1 = 8888;
         NetworkStats delta = new NetworkStats(TEST_START, 17)
-            .addValues(tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 39605L, 46L, 12259L, 55L, 0L)
-            .addValues(tunIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L)
-            .addValues(tunIface, 10120, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 72667L, 197L, 43909L, 241L, 0L)
-            .addValues(tunIface, 10120, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 9297L, 17L, 4128L, 21L, 0L)
-            // VPN package also uses some traffic through unprotected network.
-            .addValues(tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 4983L, 10L, 1801L, 12L, 0L)
-            .addValues(tunIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L)
-            // Tag entries
-            .addValues(tunIface, 10120, SET_DEFAULT, testTag1, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 21691L, 41L, 13820L, 51L, 0L)
-            .addValues(tunIface, 10120, SET_FOREGROUND, testTag1, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 1281L, 2L, 665L, 2L, 0L)
-            // Irrelevant entries
-            .addValues(TEST_IFACE, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 1685L, 5L, 2070L, 6L, 0L)
-            // Underlying Iface entries
-            .addValues(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 5178L, 8L, 2139L, 11L, 0L)
-            .addValues(underlyingIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L)
-            .addValues(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 149873L, 287L, 59217L /* smaller than sum(tun0) */,
-                    299L /* smaller than sum(tun0) */, 0L)
-            .addValues(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L);
+                .addEntry(tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 39605L, 46L, 12259L, 55L, 0L)
+                .addEntry(tunIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L)
+                .addEntry(tunIface, 10120, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 72667L, 197L, 43909L, 241L, 0L)
+                .addEntry(tunIface, 10120, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 9297L, 17L, 4128L, 21L, 0L)
+                // VPN package also uses some traffic through unprotected network.
+                .addEntry(tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 4983L, 10L, 1801L, 12L, 0L)
+                .addEntry(tunIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L)
+                // Tag entries
+                .addEntry(tunIface, 10120, SET_DEFAULT, testTag1, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 21691L, 41L, 13820L, 51L, 0L)
+                .addEntry(tunIface, 10120, SET_FOREGROUND, testTag1, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 1281L, 2L, 665L, 2L, 0L)
+                // Irrelevant entries
+                .addEntry(TEST_IFACE, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 1685L, 5L, 2070L, 6L, 0L)
+                // Underlying Iface entries
+                .addEntry(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 5178L, 8L, 2139L, 11L, 0L)
+                .addEntry(underlyingIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L)
+                .addEntry(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 149873L, 287L, 59217L /* smaller than sum(tun0) */,
+                        299L /* smaller than sum(tun0) */, 0L)
+                .addEntry(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L);
 
-        delta.migrateTun(tunUid, tunIface, new String[] {underlyingIface});
+        delta.migrateTun(tunUid, tunIface, new String[]{underlyingIface});
         assertEquals(20, delta.size());
 
         // tunIface and TEST_IFACE entries are not changed.
@@ -634,21 +634,21 @@
         final String tunIface = "tun0";
         final String underlyingIface = "wlan0";
         NetworkStats delta = new NetworkStats(TEST_START, 9)
-            // 2 different apps sent/receive data via tun0.
-            .addValues(tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L)
-            .addValues(tunIface, 20100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 500L, 2L, 200L, 5L, 0L)
-            // VPN package resends data through the tunnel (with exaggerated overhead)
-            .addValues(tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 240000, 100L, 120000L, 60L, 0L)
-            // 1 app already has some traffic on the underlying interface, the other doesn't yet
-            .addValues(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 1000L, 10L, 2000L, 20L, 0L)
-            // Traffic through the underlying interface via the vpn app.
-            // This test should redistribute this data correctly.
-            .addValues(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO,  75500L, 37L, 130000L, 70L, 0L);
+                // 2 different apps sent/receive data via tun0.
+                .addEntry(tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L)
+                .addEntry(tunIface, 20100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 500L, 2L, 200L, 5L, 0L)
+                // VPN package resends data through the tunnel (with exaggerated overhead)
+                .addEntry(tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 240000, 100L, 120000L, 60L, 0L)
+                // 1 app already has some traffic on the underlying interface, the other doesn't yet
+                .addEntry(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 1000L, 10L, 2000L, 20L, 0L)
+                // Traffic through the underlying interface via the vpn app.
+                // This test should redistribute this data correctly.
+                .addEntry(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 75500L, 37L, 130000L, 70L, 0L);
 
         delta.migrateTun(tunUid, tunIface, new String[]{underlyingIface});
         assertEquals(9, delta.size());
@@ -697,9 +697,9 @@
                 DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
 
         NetworkStats stats = new NetworkStats(TEST_START, 3)
-                .addValues(entry1)
-                .addValues(entry2)
-                .addValues(entry3);
+                .addEntry(entry1)
+                .addEntry(entry2)
+                .addEntry(entry3);
 
         stats.filter(UID_ALL, INTERFACES_ALL, TAG_ALL);
         assertEquals(3, stats.size());
@@ -724,9 +724,9 @@
                 DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
 
         NetworkStats stats = new NetworkStats(TEST_START, 3)
-                .addValues(entry1)
-                .addValues(entry2)
-                .addValues(entry3);
+                .addEntry(entry1)
+                .addEntry(entry2)
+                .addEntry(entry3);
 
         stats.filter(testUid, INTERFACES_ALL, TAG_ALL);
         assertEquals(2, stats.size());
@@ -755,10 +755,10 @@
                 DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
 
         NetworkStats stats = new NetworkStats(TEST_START, 4)
-                .addValues(entry1)
-                .addValues(entry2)
-                .addValues(entry3)
-                .addValues(entry4);
+                .addEntry(entry1)
+                .addEntry(entry2)
+                .addEntry(entry3)
+                .addEntry(entry4);
 
         stats.filter(UID_ALL, new String[] { testIf1, testIf2 }, TAG_ALL);
         assertEquals(3, stats.size());
@@ -778,8 +778,8 @@
                 DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
 
         NetworkStats stats = new NetworkStats(TEST_START, 3)
-                .addValues(entry1)
-                .addValues(entry2);
+                .addEntry(entry1)
+                .addEntry(entry2);
 
         stats.filter(UID_ALL, new String[] { }, TAG_ALL);
         assertEquals(0, stats.size());
@@ -802,9 +802,9 @@
                 DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
 
         NetworkStats stats = new NetworkStats(TEST_START, 3)
-                .addValues(entry1)
-                .addValues(entry2)
-                .addValues(entry3);
+                .addEntry(entry1)
+                .addEntry(entry2)
+                .addEntry(entry3);
 
         stats.filter(UID_ALL, INTERFACES_ALL, testTag);
         assertEquals(2, stats.size());
@@ -831,10 +831,10 @@
                 DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
 
         NetworkStats stats = new NetworkStats(TEST_START, 4)
-                .addValues(entry1)
-                .addValues(entry2)
-                .addValues(entry3)
-                .addValues(entry4);
+                .addEntry(entry1)
+                .addEntry(entry2)
+                .addEntry(entry3)
+                .addEntry(entry4);
 
         stats.filterDebugEntries();
 
@@ -891,14 +891,14 @@
                 0 /* operations */);
 
         final NetworkStats statsXt = new NetworkStats(TEST_START, 3)
-                .addValues(appEntry)
-                .addValues(xtRootUidEntry)
-                .addValues(otherEntry);
+                .addEntry(appEntry)
+                .addEntry(xtRootUidEntry)
+                .addEntry(otherEntry);
 
         final NetworkStats statsEbpf = new NetworkStats(TEST_START, 3)
-                .addValues(appEntry)
-                .addValues(ebpfRootUidEntry)
-                .addValues(otherEntry);
+                .addEntry(appEntry)
+                .addEntry(ebpfRootUidEntry)
+                .addEntry(otherEntry);
 
         statsXt.apply464xlatAdjustments(stackedIface, false);
         statsEbpf.apply464xlatAdjustments(stackedIface, true);
@@ -945,8 +945,8 @@
                 0 /* operations */);
 
         NetworkStats stats = new NetworkStats(TEST_START, 2)
-                .addValues(firstEntry)
-                .addValues(secondEntry);
+                .addEntry(firstEntry)
+                .addEntry(secondEntry);
 
         // Empty map: no adjustment
         stats.apply464xlatAdjustments(new ArrayMap<>(), false);
diff --git a/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java b/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java
new file mode 100644
index 0000000..47afed4
--- /dev/null
+++ b/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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 android.net;
+
+import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
+
+import static org.junit.Assert.assertEquals;
+
+import android.telephony.SubscriptionManager;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Unit test for {@link android.net.TelephonyNetworkSpecifier}.
+ */
+@SmallTest
+public class TelephonyNetworkSpecifierTest {
+    private static final int TEST_SUBID = 5;
+
+    /**
+     * Validate that IllegalArgumentException will be thrown if build TelephonyNetworkSpecifier
+     * without calling {@link TelephonyNetworkSpecifier.Builder#setSubscriptionId(int)}.
+     */
+    @Test
+    public void testBuilderBuildWithDefault() {
+        try {
+            new TelephonyNetworkSpecifier.Builder().build();
+        } catch (IllegalArgumentException iae) {
+            // expected, test pass
+        }
+    }
+
+    /**
+     * Validate that no exception will be thrown even if pass invalid subscription id to
+     * {@link TelephonyNetworkSpecifier.Builder#setSubscriptionId(int)}.
+     */
+    @Test
+    public void testBuilderBuildWithInvalidSubId() {
+        TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier.Builder()
+                .setSubscriptionId(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+                .build();
+        assertEquals(specifier.getSubscriptionId(), SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+    }
+
+    /**
+     * Validate the correctness of TelephonyNetworkSpecifier when provide valid subId.
+     */
+    @Test
+    public void testBuilderBuildWithValidSubId() {
+        final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier.Builder()
+                .setSubscriptionId(TEST_SUBID)
+                .build();
+        assertEquals(TEST_SUBID, specifier.getSubscriptionId());
+    }
+
+    /**
+     * Validate that parcel marshalling/unmarshalling works.
+     */
+    @Test
+    public void testParcel() {
+        TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier.Builder()
+                .setSubscriptionId(TEST_SUBID)
+                .build();
+        assertParcelSane(specifier, 1 /* fieldCount */);
+    }
+}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index b2d363e..09cc69e 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -21,6 +21,8 @@
 import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
 import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_SUPL;
@@ -114,6 +116,7 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.app.AlarmManager;
 import android.app.NotificationManager;
@@ -129,6 +132,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
+import android.net.CaptivePortalData;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.ConnectivityManager.PacketKeepalive;
@@ -165,6 +169,7 @@
 import android.net.RouteInfo;
 import android.net.SocketKeepalive;
 import android.net.UidRange;
+import android.net.Uri;
 import android.net.metrics.IpConnectivityLog;
 import android.net.shared.NetworkMonitorUtils;
 import android.net.shared.PrivateDnsConfig;
@@ -243,8 +248,10 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
@@ -347,6 +354,8 @@
 
         @Spy private Resources mResources;
         private final LinkedBlockingQueue<Intent> mStartedActivities = new LinkedBlockingQueue<>();
+        // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant
+        private final HashMap<String, Integer> mMockedPermissions = new HashMap<>();
 
         MockContext(Context base, ContentProvider settingsProvider) {
             super(base);
@@ -417,13 +426,39 @@
         }
 
         @Override
+        public int checkPermission(String permission, int pid, int uid) {
+            final Integer granted = mMockedPermissions.get(permission);
+            if (granted == null) {
+                // All non-mocked permissions should be held by the test or unnecessary: check as
+                // normal to make sure the code does not rely on unexpected permissions.
+                return super.checkPermission(permission, pid, uid);
+            }
+            return granted;
+        }
+
+        @Override
         public void enforceCallingOrSelfPermission(String permission, String message) {
-            // The mainline permission can only be held if signed with the network stack certificate
-            // Skip testing for this permission.
-            if (NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK.equals(permission)) return;
-            // All other permissions should be held by the test or unnecessary: check as normal to
-            // make sure the code does not rely on unexpected permissions.
-            super.enforceCallingOrSelfPermission(permission, message);
+            final Integer granted = mMockedPermissions.get(permission);
+            if (granted == null) {
+                super.enforceCallingOrSelfPermission(permission, message);
+                return;
+            }
+
+            if (!granted.equals(PERMISSION_GRANTED)) {
+                throw new SecurityException("[Test] permission denied: " + permission);
+            }
+        }
+
+        /**
+         * Mock checks for the specified permission, and have them behave as per {@code granted}.
+         *
+         * <p>Passing null reverts to default behavior, which does a real permission check on the
+         * test package.
+         * @param granted One of {@link PackageManager#PERMISSION_GRANTED} or
+         *                {@link PackageManager#PERMISSION_DENIED}.
+         */
+        public void setPermission(String permission, Integer granted) {
+            mMockedPermissions.put(permission, granted);
         }
 
         @Override
@@ -575,7 +610,7 @@
                 }
             };
 
-            assertEquals(na.netId, nmNetworkCaptor.getValue().netId);
+            assertEquals(na.getNetwork().netId, nmNetworkCaptor.getValue().netId);
             mNmCallbacks = nmCbCaptor.getValue();
 
             mNmCallbacks.onNetworkMonitorCreated(mNetworkMonitor);
@@ -1750,6 +1785,66 @@
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
     }
 
+    private void doNetworkCallbacksSanitizationTest(boolean sanitized) throws Exception {
+        final TestNetworkCallback callback = new TestNetworkCallback();
+        final TestNetworkCallback defaultCallback = new TestNetworkCallback();
+        final NetworkRequest wifiRequest = new NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_WIFI).build();
+        mCm.registerNetworkCallback(wifiRequest, callback);
+        mCm.registerDefaultNetworkCallback(defaultCallback);
+
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(false);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+
+        final LinkProperties newLp = new LinkProperties();
+        final Uri capportUrl = Uri.parse("https://capport.example.com/api");
+        final CaptivePortalData capportData = new CaptivePortalData.Builder()
+                .setCaptive(true).build();
+        newLp.setCaptivePortalApiUrl(capportUrl);
+        newLp.setCaptivePortalData(capportData);
+        mWiFiNetworkAgent.sendLinkProperties(newLp);
+
+        final Uri expectedCapportUrl = sanitized ? null : capportUrl;
+        final CaptivePortalData expectedCapportData = sanitized ? null : capportData;
+        callback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp ->
+                Objects.equals(expectedCapportUrl, lp.getCaptivePortalApiUrl())
+                && Objects.equals(expectedCapportData, lp.getCaptivePortalData()));
+        defaultCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp ->
+                Objects.equals(expectedCapportUrl, lp.getCaptivePortalApiUrl())
+                && Objects.equals(expectedCapportData, lp.getCaptivePortalData()));
+
+        final LinkProperties lp = mCm.getLinkProperties(mWiFiNetworkAgent.getNetwork());
+        assertEquals(expectedCapportUrl, lp.getCaptivePortalApiUrl());
+        assertEquals(expectedCapportData, lp.getCaptivePortalData());
+    }
+
+    @Test
+    public void networkCallbacksSanitizationTest_Sanitize() throws Exception {
+        mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+                PERMISSION_DENIED);
+        mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS,
+                PERMISSION_DENIED);
+        doNetworkCallbacksSanitizationTest(true /* sanitized */);
+    }
+
+    @Test
+    public void networkCallbacksSanitizationTest_NoSanitize_NetworkStack() throws Exception {
+        mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+                PERMISSION_GRANTED);
+        mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS, PERMISSION_DENIED);
+        doNetworkCallbacksSanitizationTest(false /* sanitized */);
+    }
+
+    @Test
+    public void networkCallbacksSanitizationTest_NoSanitize_Settings() throws Exception {
+        mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+                PERMISSION_DENIED);
+        mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
+        doNetworkCallbacksSanitizationTest(false /* sanitized */);
+    }
+
     @Test
     public void testMultipleLingering() throws Exception {
         // This test would be flaky with the default 120ms timer: that is short enough that
@@ -2628,6 +2723,8 @@
         final String testKey = "testkey";
         final String testValue = "testvalue";
         testBundle.putString(testKey, testValue);
+        mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+                PERMISSION_GRANTED);
         mCm.startCaptivePortalApp(wifiNetwork, testBundle);
         final Intent signInIntent = mServiceContext.expectStartActivityIntent(TIMEOUT_MS);
         assertEquals(ACTION_CAPTIVE_PORTAL_SIGN_IN, signInIntent.getAction());
diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
index 535298f..e863266 100644
--- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
@@ -36,9 +36,8 @@
 import android.net.INetd;
 import android.net.Network;
 import android.net.NetworkCapabilities;
-import android.net.NetworkFactory;
 import android.net.NetworkInfo;
-import android.net.NetworkMisc;
+import android.net.NetworkProvider;
 import android.net.NetworkScore;
 import android.os.INetworkManagementService;
 import android.text.format.DateUtils;
@@ -75,7 +74,6 @@
     @Mock INetd mNetd;
     @Mock INetworkManagementService mNMS;
     @Mock Context mCtx;
-    @Mock NetworkMisc mMisc;
     @Mock NetworkNotificationManager mNotifier;
     @Mock Resources mResources;
 
@@ -358,8 +356,8 @@
         NetworkScore ns = new NetworkScore();
         ns.putIntExtension(NetworkScore.LEGACY_SCORE, 50);
         NetworkAgentInfo nai = new NetworkAgentInfo(null, null, new Network(netId), info, null,
-                caps, ns, mCtx, null, mMisc, mConnService, mNetd, mDnsResolver, mNMS,
-                NetworkFactory.SerialNumber.NONE);
+                caps, ns, mCtx, null, null /* config */, mConnService, mNetd, mDnsResolver, mNMS,
+                NetworkProvider.ID_NONE);
         nai.everValidated = true;
         return nai;
     }
diff --git a/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java b/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
index b783467..de1028c 100644
--- a/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
+++ b/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
@@ -51,6 +51,7 @@
 import android.net.NetworkPolicyManager;
 import android.net.NetworkTemplate;
 import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
 import android.os.Handler;
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
@@ -229,7 +230,7 @@
         verify(mCM).registerNetworkCallback(any(), networkCallback.capture(), any());
 
         // Simulate callback after capability changes
-        final NetworkCapabilities capabilities = new NetworkCapabilities()
+        NetworkCapabilities capabilities = new NetworkCapabilities()
                 .addCapability(NET_CAPABILITY_INTERNET)
                 .addTransportType(TRANSPORT_CELLULAR)
                 .setNetworkSpecifier(new StringNetworkSpecifier("234"));
@@ -239,6 +240,19 @@
         networkCallback.getValue().onCapabilitiesChanged(
                 TEST_NETWORK,
                 capabilities);
+
+        // make sure it also works with the new introduced  TelephonyNetworkSpecifier
+        capabilities = new NetworkCapabilities()
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .addTransportType(TRANSPORT_CELLULAR)
+                .setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+                        .setSubscriptionId(234).build());
+        if (!roaming) {
+            capabilities.addCapability(NET_CAPABILITY_NOT_ROAMING);
+        }
+        networkCallback.getValue().onCapabilitiesChanged(
+                TEST_NETWORK,
+                capabilities);
     }
 
     @Test
diff --git a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
index b709af1..9b24887 100644
--- a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
+++ b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
@@ -33,8 +33,8 @@
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
+import android.net.NetworkAgentConfig;
 import android.net.NetworkInfo;
-import android.net.NetworkMisc;
 import android.os.Handler;
 import android.os.INetworkManagementService;
 import android.os.test.TestLooper;
@@ -63,7 +63,6 @@
     static final int NETID = 42;
 
     @Mock ConnectivityService mConnectivity;
-    @Mock NetworkMisc mMisc;
     @Mock IDnsResolver mDnsResolver;
     @Mock INetd mNetd;
     @Mock INetworkManagementService mNms;
@@ -72,6 +71,7 @@
 
     TestLooper mLooper;
     Handler mHandler;
+    NetworkAgentConfig mAgentConfig = new NetworkAgentConfig();
 
     Nat464Xlat makeNat464Xlat() {
         return new Nat464Xlat(mNai, mNetd, mDnsResolver, mNms) {
@@ -93,7 +93,7 @@
         mNai.networkInfo = new NetworkInfo(null);
         mNai.networkInfo.setType(ConnectivityManager.TYPE_WIFI);
         when(mNai.connService()).thenReturn(mConnectivity);
-        when(mNai.netMisc()).thenReturn(mMisc);
+        when(mNai.netAgentConfig()).thenReturn(mAgentConfig);
         when(mNai.handler()).thenReturn(mHandler);
 
         when(mNms.getInterfaceConfig(eq(STACKED_IFACE))).thenReturn(mConfig);
@@ -104,7 +104,7 @@
         String msg = String.format("requiresClat expected %b for type=%d state=%s skip=%b "
                 + "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(),
                 nai.networkInfo.getDetailedState(),
-                mMisc.skip464xlat, nai.linkProperties.getNat64Prefix(),
+                mAgentConfig.skip464xlat, nai.linkProperties.getNat64Prefix(),
                 nai.linkProperties.getLinkAddresses());
         assertEquals(msg, expected, Nat464Xlat.requiresClat(nai));
     }
@@ -113,7 +113,7 @@
         String msg = String.format("shouldStartClat expected %b for type=%d state=%s skip=%b "
                 + "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(),
                 nai.networkInfo.getDetailedState(),
-                mMisc.skip464xlat, nai.linkProperties.getNat64Prefix(),
+                mAgentConfig.skip464xlat, nai.linkProperties.getNat64Prefix(),
                 nai.linkProperties.getLinkAddresses());
         assertEquals(msg, expected, Nat464Xlat.shouldStartClat(nai));
     }
@@ -151,11 +151,11 @@
                 assertRequiresClat(true, mNai);
                 assertShouldStartClat(true, mNai);
 
-                mMisc.skip464xlat = true;
+                mAgentConfig.skip464xlat = true;
                 assertRequiresClat(false, mNai);
                 assertShouldStartClat(false, mNai);
 
-                mMisc.skip464xlat = false;
+                mAgentConfig.skip464xlat = false;
                 assertRequiresClat(true, mNai);
                 assertShouldStartClat(true, mNai);
 
diff --git a/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java b/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
index c0f9dc1..f0e5774 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
@@ -326,14 +326,14 @@
         // Baseline
         NetworkStats xtSnapshot = null;
         NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
 
         // Delta
         uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_NO, BASE_BYTES + THRESHOLD_BYTES, 2L,
                         BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
@@ -359,14 +359,14 @@
         // Baseline
         NetworkStats xtSnapshot = null;
         NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_NO, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
 
         // Delta
         uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_NO, BASE_BYTES + THRESHOLD_BYTES, 2L,
                         BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
@@ -391,14 +391,14 @@
         // Baseline
         NetworkStats xtSnapshot = null;
         NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
 
         // Delta
         uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, BASE_BYTES + THRESHOLD_BYTES, 2L,
                         BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
@@ -424,14 +424,14 @@
         // Baseline
         NetworkStats xtSnapshot = null;
         NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO,
+                .addEntry(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO,
                         ROAMING_NO, DEFAULT_NETWORK_YES, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
 
         // Delta
         uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO,
+                .addEntry(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO,
                         ROAMING_NO, DEFAULT_NETWORK_NO, BASE_BYTES + THRESHOLD_BYTES, 2L,
                         BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
index 4d42a61..a9e0b9a 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -80,6 +80,7 @@
 import android.net.NetworkStats;
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
+import android.net.netstats.provider.INetworkStatsProviderCallback;
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -102,6 +103,7 @@
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
 import com.android.testutils.HandlerUtilsKt;
+import com.android.testutils.TestableNetworkStatsProvider;
 
 import libcore.io.IoUtils;
 
@@ -298,11 +300,11 @@
         expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
                 .addIfaceValues(TEST_IFACE, 1024L, 8L, 2048L, 16L));
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
-                .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 0L));
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
+                .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 0L));
         mService.setUidForeground(UID_RED, false);
         mService.incrementOperationCount(UID_RED, 0xFAAD, 4);
         mService.setUidForeground(UID_RED, true);
@@ -407,9 +409,9 @@
         expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
                 .addIfaceValues(TEST_IFACE, 2048L, 16L, 512L, 4L));
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
-                .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
+                .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
         mService.incrementOperationCount(UID_RED, 0xF00D, 10);
 
         forcePollAndWaitForIdle();
@@ -429,9 +431,9 @@
         expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
                 .addIfaceValues(TEST_IFACE, 2048L, 16L, 512L, 4L));
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
-                .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
+                .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
 
         mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]);
         forcePollAndWaitForIdle();
@@ -443,10 +445,10 @@
         expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
                 .addIfaceValues(TEST_IFACE, 2176L, 17L, 1536L, 12L));
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
-                .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 640L, 5L, 1024L, 8L, 0L)
-                .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, 0xFAAD, 128L, 1L, 1024L, 8L, 0L));
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
+                .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 640L, 5L, 1024L, 8L, 0L)
+                .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, 0xFAAD, 128L, 1L, 1024L, 8L, 0L));
         mService.incrementOperationCount(UID_BLUE, 0xFAAD, 10);
 
         forcePollAndWaitForIdle();
@@ -480,10 +482,10 @@
         expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
                 .addIfaceValues(TEST_IFACE, 4128L, 258L, 544L, 34L));
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L)
-                .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L)
-                .addValues(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L));
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L)
+                .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L)
+                .addEntry(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L));
         mService.incrementOperationCount(UID_RED, 0xFAAD, 10);
 
         forcePollAndWaitForIdle();
@@ -501,10 +503,10 @@
         expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
                 .addIfaceValues(TEST_IFACE, 4128L, 258L, 544L, 34L));
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L)
-                .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L)
-                .addValues(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L));
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L)
+                .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L)
+                .addEntry(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L));
         final Intent intent = new Intent(ACTION_UID_REMOVED);
         intent.putExtra(EXTRA_UID, UID_BLUE);
         mServiceContext.sendBroadcast(intent);
@@ -536,8 +538,8 @@
         expectDefaultSettings();
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L));
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L));
         mService.incrementOperationCount(UID_RED, 0xF00D, 5);
 
         forcePollAndWaitForIdle();
@@ -552,8 +554,8 @@
         states = new NetworkState[] {buildMobile4gState(TEST_IFACE2)};
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L));
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L));
 
         mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]);
         forcePollAndWaitForIdle();
@@ -564,10 +566,10 @@
         expectDefaultSettings();
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
-                .addValues(TEST_IFACE2, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
-                .addValues(TEST_IFACE2, UID_RED, SET_DEFAULT, 0xFAAD, 512L, 4L, 256L, 2L, 0L));
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
+                .addEntry(TEST_IFACE2, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
+                .addEntry(TEST_IFACE2, UID_RED, SET_DEFAULT, 0xFAAD, 512L, 4L, 256L, 2L, 0L));
         mService.incrementOperationCount(UID_RED, 0xFAAD, 5);
 
         forcePollAndWaitForIdle();
@@ -591,9 +593,9 @@
         expectDefaultSettings();
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L)
-                .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1024L, 8L, 512L, 4L, 0L));
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L)
+                .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1024L, 8L, 512L, 4L, 0L));
         mService.incrementOperationCount(UID_RED, 0xF00D, 1);
 
         forcePollAndWaitForIdle();
@@ -608,9 +610,9 @@
         expectDefaultSettings();
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L)
-                .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 2048L, 16L, 1024L, 8L, 0L));
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L)
+                .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 2048L, 16L, 1024L, 8L, 0L));
         forcePollAndWaitForIdle();
 
         // first verify entire history present
@@ -654,9 +656,9 @@
         expectDefaultSettings();
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
-                .addValues(entry1)
-                .addValues(entry2)
-                .addValues(entry3));
+                .addEntry(entry1)
+                .addEntry(entry2)
+                .addEntry(entry3));
         mService.incrementOperationCount(UID_RED, 0xF00D, 1);
 
         NetworkStats stats = mService.getDetailedUidStats(INTERFACES_ALL);
@@ -704,11 +706,11 @@
                 .thenReturn(augmentedIfaceFilter);
         when(mStatsFactory.readNetworkStatsDetail(eq(UID_ALL), any(), eq(TAG_ALL)))
                 .thenReturn(new NetworkStats(getElapsedRealtime(), 1)
-                        .addValues(uidStats));
+                        .addEntry(uidStats));
         when(mNetManager.getNetworkStatsTethering(STATS_PER_UID))
                 .thenReturn(new NetworkStats(getElapsedRealtime(), 2)
-                        .addValues(tetheredStats1)
-                        .addValues(tetheredStats2));
+                        .addEntry(tetheredStats1)
+                        .addEntry(tetheredStats2));
 
         NetworkStats stats = mService.getDetailedUidStats(ifaceFilter);
 
@@ -745,8 +747,8 @@
         expectDefaultSettings();
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L));
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L));
         mService.incrementOperationCount(UID_RED, 0xF00D, 1);
 
         forcePollAndWaitForIdle();
@@ -760,10 +762,10 @@
         expectDefaultSettings();
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 32L, 2L, 32L, 2L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 1L, 1L, 1L, 1L, 0L));
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 32L, 2L, 32L, 2L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 1L, 1L, 1L, 1L, 0L));
         mService.setUidForeground(UID_RED, true);
         mService.incrementOperationCount(UID_RED, 0xFAAD, 1);
 
@@ -804,9 +806,9 @@
         // and DEFAULT_NETWORK_YES, because these three properties aren't tracked at that layer.
         // We layer them on top by inspecting the iface properties.
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 128L, 2L, 128L, 2L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 64L, 1L, 64L, 1L, 0L));
         mService.incrementOperationCount(UID_RED, 0xF00D, 1);
 
@@ -843,9 +845,9 @@
         // ROAMING_NO, because metered and roaming isn't tracked at that layer. We layer it
         // on top by inspecting the iface properties.
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_ALL, ROAMING_NO,
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_ALL, ROAMING_NO,
                         DEFAULT_NETWORK_YES,  128L, 2L, 128L, 2L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_ALL, ROAMING_NO,
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_ALL, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 64L, 1L, 64L, 1L, 0L));
         forcePollAndWaitForIdle();
 
@@ -885,10 +887,10 @@
 
         // Traffic for UID_RED.
         final NetworkStats uidStats = new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L);
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L);
         // All tethering traffic, both hardware and software.
         final NetworkStats tetherStats = new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1920L, 14L, 384L, 2L,
+                .addEntry(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1920L, 14L, 384L, 2L,
                         0L);
 
         expectNetworkStatsSummary(ifaceStats, tetherStatsHardware);
@@ -1001,6 +1003,88 @@
         mService.unregisterUsageRequest(unknownRequest);
     }
 
+    @Test
+    public void testStatsProviderUpdateStats() throws Exception {
+        // Pretend that network comes online.
+        expectDefaultSettings();
+        final NetworkState[] states = new NetworkState[]{buildWifiState(true /* isMetered */)};
+        expectNetworkStatsSummary(buildEmptyStats());
+        expectNetworkStatsUidDetail(buildEmptyStats());
+
+        // Register custom provider and retrieve callback.
+        final TestableNetworkStatsProvider provider = new TestableNetworkStatsProvider();
+        final INetworkStatsProviderCallback cb =
+                mService.registerNetworkStatsProvider("TEST", provider);
+        assertNotNull(cb);
+
+        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
+
+        // Verifies that one requestStatsUpdate will be called during iface update.
+        provider.expectStatsUpdate(0 /* unused */);
+
+        // Create some initial traffic and report to the service.
+        incrementCurrentTime(HOUR_IN_MILLIS);
+        final NetworkStats expectedStats = new NetworkStats(0L, 1)
+                .addValues(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT,
+                        TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES,
+                        128L, 2L, 128L, 2L, 1L))
+                .addValues(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT,
+                        0xF00D, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES,
+                        64L, 1L, 64L, 1L, 1L));
+        cb.onStatsUpdated(0 /* unused */, expectedStats, expectedStats);
+
+        // Make another empty mutable stats object. This is necessary since the new NetworkStats
+        // object will be used to compare with the old one in NetworkStatsRecoder, two of them
+        // cannot be the same object.
+        expectNetworkStatsUidDetail(buildEmptyStats());
+
+        forcePollAndWaitForIdle();
+
+        // Verifies that one requestStatsUpdate and setAlert will be called during polling.
+        provider.expectStatsUpdate(0 /* unused */);
+        provider.expectSetAlert(MB_IN_BYTES);
+
+        // Verifies that service recorded history, does not verify uid tag part.
+        assertUidTotal(sTemplateWifi, UID_RED, 128L, 2L, 128L, 2L, 1);
+
+        // Verifies that onStatsUpdated updates the stats accordingly.
+        final NetworkStats stats = mSession.getSummaryForAllUid(
+                sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true);
+        assertEquals(2, stats.size());
+        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+                DEFAULT_NETWORK_YES, 128L, 2L, 128L, 2L, 1L);
+        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO,
+                DEFAULT_NETWORK_YES, 64L, 1L, 64L, 1L, 1L);
+
+        // Verifies that unregister the callback will remove the provider from service.
+        cb.unregister();
+        forcePollAndWaitForIdle();
+        provider.assertNoCallback();
+    }
+
+    @Test
+    public void testStatsProviderSetAlert() throws Exception {
+        // Pretend that network comes online.
+        expectDefaultSettings();
+        NetworkState[] states = new NetworkState[]{buildWifiState(true /* isMetered */)};
+        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
+
+        // Register custom provider and retrieve callback.
+        final TestableNetworkStatsProvider provider = new TestableNetworkStatsProvider();
+        final INetworkStatsProviderCallback cb =
+                mService.registerNetworkStatsProvider("TEST", provider);
+        assertNotNull(cb);
+
+        // Simulates alert quota of the provider has been reached.
+        cb.onAlertReached();
+        HandlerUtilsKt.waitForIdle(mHandler, WAIT_TIMEOUT);
+
+        // Verifies that polling is triggered by alert reached.
+        provider.expectStatsUpdate(0 /* unused */);
+        // Verifies that global alert will be re-armed.
+        provider.expectSetAlert(MB_IN_BYTES);
+    }
+
     private static File getBaseDir(File statsDir) {
         File baseDir = new File(statsDir, "netstats");
         baseDir.mkdirs();
@@ -1102,6 +1186,7 @@
     private void expectSettings(long persistBytes, long bucketDuration, long deleteAge)
             throws Exception {
         when(mSettings.getPollInterval()).thenReturn(HOUR_IN_MILLIS);
+        when(mSettings.getPollDelay()).thenReturn(0L);
         when(mSettings.getSampleEnabled()).thenReturn(true);
 
         final Config config = new Config(bucketDuration, deleteAge, deleteAge);
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index 8d99ac71..fe0224a 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -234,6 +234,9 @@
             try {
                 mLooper = setAsMain ? Looper.getMainLooper() : createLooper();
                 mTestableLooper = new TestableLooper(mLooper, false);
+                if (!setAsMain) {
+                    mTestableLooper.getLooper().getThread().setName(test.getClass().getName());
+                }
             } catch (Exception e) {
                 throw new RuntimeException(e);
             }
diff --git a/tests/utils/testutils/Android.bp b/tests/utils/testutils/Android.bp
index f71be7b..a6625ab 100644
--- a/tests/utils/testutils/Android.bp
+++ b/tests/utils/testutils/Android.bp
@@ -22,6 +22,7 @@
     static_libs: [
         "junit",
         "hamcrest-library",
+        "androidx.test.runner",
     ],
 
     libs: [
diff --git a/tests/utils/testutils/java/com/android/server/accessibility/test/MessageCapturingHandler.java b/tests/utils/testutils/java/com/android/server/accessibility/test/MessageCapturingHandler.java
new file mode 100644
index 0000000..bce2ab5
--- /dev/null
+++ b/tests/utils/testutils/java/com/android/server/accessibility/test/MessageCapturingHandler.java
@@ -0,0 +1,118 @@
+/*
+ * 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.server.accessibility.test;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Pair;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility class to capture messages dispatched through a handler and control when they arrive
+ * at their target.
+ */
+public class MessageCapturingHandler extends Handler {
+    public List<Pair<Message, Long>> timedMessages = new ArrayList<>();
+
+    Handler.Callback mCallback;
+
+    public MessageCapturingHandler(Handler.Callback callback) {
+        this(InstrumentationRegistry.getContext().getMainLooper(), callback);
+    }
+
+    public MessageCapturingHandler(Looper looper, Callback callback) {
+        super(looper);
+        mCallback = callback;
+    }
+
+    /**
+     * Holding messages in queue, but never dispatching.
+     * @see #removeAllMessages()
+     */
+    @Override
+    public boolean sendMessageAtTime(Message message, long uptimeMillis) {
+        timedMessages.add(new Pair<>(Message.obtain(message), uptimeMillis));
+        return super.sendMessageAtTime(message, Long.MAX_VALUE);
+    }
+
+    public void setCallback(Handler.Callback callback) {
+        mCallback = callback;
+    }
+
+    public void sendOneMessage() {
+        Message message = timedMessages.remove(0).first;
+        removeMessages(message.what, message.obj);
+        dispatchMessage(message);
+        removeStaleMessages();
+    }
+
+    public void sendAllMessages() {
+        while (!timedMessages.isEmpty()) {
+            sendOneMessage();
+        }
+    }
+
+    public void sendLastMessage() {
+        Message message = timedMessages.remove(timedMessages.size() - 1).first;
+        removeMessages(message.what, message.obj);
+        dispatchMessage(message);
+        removeStaleMessages();
+    }
+
+    /**
+     * Clear messages sent from this handler in queue.
+     * <p>
+     * If main looper is used, this method should be called in tear down function
+     * to ensure messages isolation between test cases.
+     * </p>
+     */
+    public void removeAllMessages() {
+        if (hasMessages()) {
+            for (int i = 0; i < timedMessages.size(); i++) {
+                Message message = timedMessages.get(i).first;
+                removeMessages(message.what, message.obj);
+            }
+        }
+    }
+
+    public boolean hasMessages() {
+        removeStaleMessages();
+        return !timedMessages.isEmpty();
+    }
+
+    private void removeStaleMessages() {
+        for (int i = 0; i < timedMessages.size(); i++) {
+            Message message = timedMessages.get(i).first;
+            if (!hasMessages(message.what, message.obj)) {
+                timedMessages.remove(i--);
+            }
+        }
+    }
+
+    public void dispatchMessage(Message m) {
+        if (mCallback != null) {
+            mCallback.handleMessage(m);
+            return;
+        }
+        super.dispatchMessage(m);
+    }
+}
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 3623b11..469128b 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -800,7 +800,12 @@
       }
 
       // This is a normal reference.
-      return util::make_unique<Reference>(data, ref_type);
+      auto reference = util::make_unique<Reference>(data, ref_type);
+      if (res_value.dataType == android::Res_value::TYPE_DYNAMIC_REFERENCE ||
+          res_value.dataType == android::Res_value::TYPE_DYNAMIC_ATTRIBUTE) {
+        reference->is_dynamic = true;
+      }
+      return reference;
     } break;
   }
 
diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp
index c016cb4..b08bf9a 100644
--- a/tools/aapt2/ResourceUtils_test.cpp
+++ b/tools/aapt2/ResourceUtils_test.cpp
@@ -109,6 +109,20 @@
   EXPECT_TRUE(private_ref);
 }
 
+TEST(ResourceUtilsTest, ParseBinaryDynamicReference) {
+  android::Res_value value = {};
+  value.data = util::HostToDevice32(0x01);
+  value.dataType = android::Res_value::TYPE_DYNAMIC_REFERENCE;
+  std::unique_ptr<Item> item = ResourceUtils::ParseBinaryResValue(ResourceType::kId,
+                                                                  android::ConfigDescription(),
+                                                                  android::ResStringPool(), value,
+                                                                  nullptr);
+
+  Reference* ref = ValueCast<Reference>(item.get());
+  EXPECT_TRUE(ref->is_dynamic);
+  EXPECT_EQ(ref->id.value().id, 0x01);
+}
+
 TEST(ResourceUtilsTest, FailToParseAutoCreateNonIdReference) {
   bool create = false;
   bool private_ref = false;
diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto
index 7498e13..8a2f5af 100644
--- a/tools/aapt2/Resources.proto
+++ b/tools/aapt2/Resources.proto
@@ -269,6 +269,11 @@
   }
 }
 
+// Message holding a boolean, so it can be optionally encoded.
+message Boolean {
+  bool value = 1;
+}
+
 // A value that is a reference to another resource. This reference can be by name or resource ID.
 message Reference {
   enum Type {
@@ -289,6 +294,9 @@
 
   // Whether this reference is referencing a private resource (@*package:type/entry).
   bool private = 4;
+
+  // Whether this reference is dynamic.
+  Boolean is_dynamic = 5;
 }
 
 // A value that represents an ID. This is just a placeholder, as ID values are used to occupy a
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 4555caa..5b6935b 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -895,7 +895,7 @@
           // android:versionCode from the framework AndroidManifest.xml.
           ExtractCompileSdkVersions(asset_source->GetAssetManager());
         }
-      } else if (asset_source->IsPackageDynamic(entry.first)) {
+      } else if (asset_source->IsPackageDynamic(entry.first, entry.second)) {
         final_table_.included_packages_[entry.first] = entry.second;
       }
     }
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 58e232c..cbce8a5 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -59,10 +59,22 @@
   dst[i] = 0;
 }
 
+static bool cmp_style_ids(ResourceId a, ResourceId b) {
+  // If one of a and b is from the framework package (package ID 0x01), and the
+  // other is a dynamic ID (package ID 0x00), then put the dynamic ID after the
+  // framework ID. This ensures that when AssetManager resolves the dynamic IDs,
+  // they will be in sorted order as expected by AssetManager.
+  if ((a.package_id() == kFrameworkPackageId && b.package_id() == 0x00) ||
+      (a.package_id() == 0x00 && b.package_id() == kFrameworkPackageId)) {
+    return b < a;
+  }
+  return a < b;
+}
+
 static bool cmp_style_entries(const Style::Entry& a, const Style::Entry& b) {
   if (a.key.id) {
     if (b.key.id) {
-      return a.key.id.value() < b.key.id.value();
+      return cmp_style_ids(a.key.id.value(), b.key.id.value());
     }
     return true;
   } else if (!b.key.id) {
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index 8fbdd7f..af2293f 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -431,6 +431,47 @@
   EXPECT_EQ("lib", iter->second);
 }
 
+TEST_F(TableFlattenerTest, FlattenSharedLibraryWithStyle) {
+  std::unique_ptr<IAaptContext> context =
+      test::ContextBuilder().SetCompilationPackage("lib").SetPackageId(0x00).Build();
+  std::unique_ptr<ResourceTable> table =
+      test::ResourceTableBuilder()
+          .SetPackageId("lib", 0x00)
+          .AddValue("lib:style/Theme",
+                    ResourceId(0x00030001),
+                    test::StyleBuilder()
+                    .AddItem("lib:attr/bar", ResourceId(0x00010002),
+                             ResourceUtils::TryParseInt("2"))
+                    .AddItem("lib:attr/foo", ResourceId(0x00010001),
+                             ResourceUtils::TryParseInt("1"))
+                    .AddItem("android:attr/bar", ResourceId(0x01010002),
+                             ResourceUtils::TryParseInt("4"))
+                    .AddItem("android:attr/foo", ResourceId(0x01010001),
+                             ResourceUtils::TryParseInt("3"))
+                    .Build())
+          .Build();
+  ResourceTable result;
+  ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result));
+
+  Maybe<ResourceTable::SearchResult> search_result =
+      result.FindResource(test::ParseNameOrDie("lib:style/Theme"));
+  ASSERT_TRUE(search_result);
+  EXPECT_EQ(0x00u, search_result.value().package->id.value());
+  EXPECT_EQ(0x03u, search_result.value().type->id.value());
+  EXPECT_EQ(0x01u, search_result.value().entry->id.value());
+  ASSERT_EQ(1u, search_result.value().entry->values.size());
+  Value* value = search_result.value().entry->values[0]->value.get();
+  Style* style = ValueCast<Style>(value);
+  ASSERT_TRUE(style);
+  ASSERT_EQ(4u, style->entries.size());
+  // Ensure the attributes from the shared library come after the items from
+  // android.
+  EXPECT_EQ(0x01010001, style->entries[0].key.id.value());
+  EXPECT_EQ(0x01010002, style->entries[1].key.id.value());
+  EXPECT_EQ(0x00010001, style->entries[2].key.id.value());
+  EXPECT_EQ(0x00010002, style->entries[3].key.id.value());
+}
+
 TEST_F(TableFlattenerTest, FlattenTableReferencingSharedLibraries) {
   std::unique_ptr<IAaptContext> context =
       test::ContextBuilder().SetCompilationPackage("app").SetPackageId(0x7f).Build();
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index efbf636..4cd6e93 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -634,6 +634,7 @@
                                        std::string* out_error) {
   out_ref->reference_type = DeserializeReferenceTypeFromPb(pb_ref.type());
   out_ref->private_reference = pb_ref.private_();
+  out_ref->is_dynamic = pb_ref.is_dynamic().value();
 
   if (pb_ref.id() != 0) {
     out_ref->id = ResourceId(pb_ref.id());
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index e4b3fce..d9f6c19 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -418,6 +418,9 @@
 
   pb_ref->set_private_(ref.private_reference);
   pb_ref->set_type(SerializeReferenceTypeToPb(ref.reference_type));
+  if (ref.is_dynamic) {
+    pb_ref->mutable_is_dynamic()->set_value(ref.is_dynamic);
+  }
 }
 
 template <typename T>
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index e7f2330..61a8335 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -608,4 +608,41 @@
   ASSERT_FALSE(search_result.value().entry->overlayable_item);
 }
 
+TEST(ProtoSerializeTest, SerializeAndDeserializeDynamicReference) {
+  Reference ref(ResourceId(0x00010001));
+  ref.is_dynamic = true;
+
+  pb::Item pb_item;
+  SerializeItemToPb(ref, &pb_item);
+
+  ASSERT_TRUE(pb_item.has_ref());
+  EXPECT_EQ(pb_item.ref().id(), ref.id.value().id);
+  EXPECT_TRUE(pb_item.ref().is_dynamic().value());
+
+  std::unique_ptr<Item> item = DeserializeItemFromPb(pb_item, android::ResStringPool(),
+                                                     android::ConfigDescription(), nullptr,
+                                                     nullptr, nullptr);
+  Reference* actual_ref = ValueCast<Reference>(item.get());
+  EXPECT_EQ(actual_ref->id.value().id, ref.id.value().id);
+  EXPECT_TRUE(actual_ref->is_dynamic);
+}
+
+TEST(ProtoSerializeTest, SerializeAndDeserializeNonDynamicReference) {
+  Reference ref(ResourceId(0x00010001));
+
+  pb::Item pb_item;
+  SerializeItemToPb(ref, &pb_item);
+
+  ASSERT_TRUE(pb_item.has_ref());
+  EXPECT_EQ(pb_item.ref().id(), ref.id.value().id);
+  EXPECT_FALSE(pb_item.ref().has_is_dynamic());
+
+  std::unique_ptr<Item> item = DeserializeItemFromPb(pb_item, android::ResStringPool(),
+                                                     android::ConfigDescription(), nullptr,
+                                                     nullptr, nullptr);
+  Reference* actual_ref = ValueCast<Reference>(item.get());
+  EXPECT_EQ(actual_ref->id.value().id, ref.id.value().id);
+  EXPECT_FALSE(actual_ref->is_dynamic);
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 27960c8..954d401 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -349,6 +349,7 @@
     }
     return true;
   });
+  manifest_action["uses-sdk"]["extension-sdk"];
 
   // Instrumentation actions.
   manifest_action["instrumentation"].Action(RequiredNameIsJavaClassName);
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index 83e20b5..897fa80 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -245,7 +245,8 @@
   return package_map;
 }
 
-bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId) const {
+bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId,
+    const std::string& package_name) const {
   if (packageId == 0) {
     return true;
   }
@@ -253,7 +254,7 @@
   for (const std::unique_ptr<const ApkAssets>& assets : apk_assets_) {
     for (const std::unique_ptr<const android::LoadedPackage>& loaded_package
          : assets->GetLoadedArsc()->GetPackages()) {
-      if (packageId == loaded_package->GetPackageId() && loaded_package->IsDynamic()) {
+      if (package_name == loaded_package->GetPackageName() && loaded_package->IsDynamic()) {
         return true;
       }
     }
@@ -328,12 +329,12 @@
   bool found = false;
   ResourceId res_id = 0;
   uint32_t type_spec_flags;
+  ResourceName real_name;
 
   // There can be mangled resources embedded within other packages. Here we will
   // look into each package and look-up the mangled name until we find the resource.
   asset_manager_.ForEachPackage([&](const std::string& package_name, uint8_t id) -> bool {
-    ResourceName real_name(name.package, name.type, name.entry);
-
+    real_name = ResourceName(name.package, name.type, name.entry);
     if (package_name != name.package) {
       real_name.entry = mangled_entry;
       real_name.package = package_name;
@@ -353,12 +354,12 @@
   }
 
   std::unique_ptr<SymbolTable::Symbol> s;
-  if (name.type == ResourceType::kAttr) {
+  if (real_name.type == ResourceType::kAttr) {
     s = LookupAttributeInTable(asset_manager_, res_id);
   } else {
     s = util::make_unique<SymbolTable::Symbol>();
     s->id = res_id;
-    s->is_dynamic = IsPackageDynamic(ResourceId(res_id).package_id());
+    s->is_dynamic = IsPackageDynamic(ResourceId(res_id).package_id(), real_name.package);
   }
 
   if (s) {
@@ -406,7 +407,7 @@
   } else {
     s = util::make_unique<SymbolTable::Symbol>();
     s->id = id;
-    s->is_dynamic = IsPackageDynamic(ResourceId(id).package_id());
+    s->is_dynamic = IsPackageDynamic(ResourceId(id).package_id(), name.package);
   }
 
   if (s) {
diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h
index 6997cd6..06eaf63 100644
--- a/tools/aapt2/process/SymbolTable.h
+++ b/tools/aapt2/process/SymbolTable.h
@@ -194,7 +194,7 @@
 
   bool AddAssetPath(const android::StringPiece& path);
   std::map<size_t, std::string> GetAssignedPackageIds() const;
-  bool IsPackageDynamic(uint32_t packageId) const;
+  bool IsPackageDynamic(uint32_t packageId, const std::string& package_name) const;
 
   std::unique_ptr<SymbolTable::Symbol> FindByName(
       const ResourceName& name) override;
diff --git a/tools/hiddenapi/generate_hiddenapi_lists.py b/tools/hiddenapi/generate_hiddenapi_lists.py
index 46105f4..0b2077d 100755
--- a/tools/hiddenapi/generate_hiddenapi_lists.py
+++ b/tools/hiddenapi/generate_hiddenapi_lists.py
@@ -149,7 +149,12 @@
         The package name of the class containing the field/method.
     """
     full_class_name = signature.split(";->")[0]
-    package_name = full_class_name[1:full_class_name.rindex("/")]
+    # Example: Landroid/hardware/radio/V1_2/IRadio$Proxy
+    if (full_class_name[0] != "L"):
+        raise ValueError("Expected to start with 'L': %s" % full_class_name)
+    full_class_name = full_class_name[1:]
+    # If full_class_name doesn't contain '/', then package_name will be ''.
+    package_name = full_class_name.rpartition("/")[0]
     return package_name.replace('/', '.')
 
 class FlagsDict:
diff --git a/tools/lock_agent/Android.bp b/tools/lock_agent/Android.bp
index 79dce4a..7b2ca9a 100644
--- a/tools/lock_agent/Android.bp
+++ b/tools/lock_agent/Android.bp
@@ -25,6 +25,7 @@
     srcs: ["agent.cpp"],
     static_libs: [
         "libbase",
+        "liblog",
         "libz",
         "slicer",
     ],
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
index 99a26dc..3c55237 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
@@ -53,36 +53,38 @@
 
         val executor = newThreadPool()
 
-        command.javaSourceArgs.map { path ->
-            executor.submitCallable {
-                val transformer = SourceTransformer(command.protoLogImplClassNameArg,
-                        command.protoLogCacheClassNameArg, processor)
-                val file = File(path)
-                val text = injector.readText(file)
-                val outSrc = try {
-                    val code = tryParse(text, path)
-                    if (containsProtoLogText(text, command.protoLogClassNameArg)) {
-                        transformer.processClass(text, path, packagePath(file, code), code)
-                    } else {
+        try {
+            command.javaSourceArgs.map { path ->
+                executor.submitCallable {
+                    val transformer = SourceTransformer(command.protoLogImplClassNameArg,
+                            command.protoLogCacheClassNameArg, processor)
+                    val file = File(path)
+                    val text = injector.readText(file)
+                    val outSrc = try {
+                        val code = tryParse(text, path)
+                        if (containsProtoLogText(text, command.protoLogClassNameArg)) {
+                            transformer.processClass(text, path, packagePath(file, code), code)
+                        } else {
+                            text
+                        }
+                    } catch (ex: ParsingException) {
+                        // If we cannot parse this file, skip it (and log why). Compilation will
+                        // fail in a subsequent build step.
+                        injector.reportParseError(ex)
                         text
                     }
-                } catch (ex: ParsingException) {
-                    // If we cannot parse this file, skip it (and log why). Compilation will fail
-                    // in a subsequent build step.
-                    injector.reportParseError(ex)
-                    text
+                    path to outSrc
                 }
-                path to outSrc
+            }.map { future ->
+                val (path, outSrc) = future.get()
+                outJar.putNextEntry(ZipEntry(path))
+                outJar.write(outSrc.toByteArray())
+                outJar.closeEntry()
             }
-        }.map { future ->
-            val (path, outSrc) = future.get()
-            outJar.putNextEntry(ZipEntry(path))
-            outJar.write(outSrc.toByteArray())
-            outJar.closeEntry()
+        } finally {
+            executor.shutdown()
         }
 
-        executor.shutdown()
-
         val cacheSplit = command.protoLogCacheClassNameArg.split(".")
         val cacheName = cacheSplit.last()
         val cachePackage = cacheSplit.dropLast(1).joinToString(".")
@@ -153,30 +155,32 @@
 
         val executor = newThreadPool()
 
-        command.javaSourceArgs.map { path ->
-            executor.submitCallable {
-                val file = File(path)
-                val text = injector.readText(file)
-                if (containsProtoLogText(text, command.protoLogClassNameArg)) {
-                    try {
-                        val code = tryParse(text, path)
-                        builder.findLogCalls(code, path, packagePath(file, code))
-                    } catch (ex: ParsingException) {
-                        // If we cannot parse this file, skip it (and log why). Compilation will fail
-                        // in a subsequent build step.
-                        injector.reportParseError(ex)
+        try {
+            command.javaSourceArgs.map { path ->
+                executor.submitCallable {
+                    val file = File(path)
+                    val text = injector.readText(file)
+                    if (containsProtoLogText(text, command.protoLogClassNameArg)) {
+                        try {
+                            val code = tryParse(text, path)
+                            builder.findLogCalls(code, path, packagePath(file, code))
+                        } catch (ex: ParsingException) {
+                            // If we cannot parse this file, skip it (and log why). Compilation will
+                            // fail in a subsequent build step.
+                            injector.reportParseError(ex)
+                            null
+                        }
+                    } else {
                         null
                     }
-                } else {
-                    null
                 }
+            }.forEach { future ->
+                builder.addLogCalls(future.get() ?: return@forEach)
             }
-        }.forEach { future ->
-            builder.addLogCalls(future.get() ?: return@forEach)
+        } finally {
+            executor.shutdown()
         }
 
-        executor.shutdown()
-
         val out = injector.fileOutputStream(command.viewerConfigJsonArg)
         out.write(builder.build().toByteArray())
         out.close()
diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp
index 0b82a3d..d290aca 100644
--- a/tools/stats_log_api_gen/Collation.cpp
+++ b/tools/stats_log_api_gen/Collation.cpp
@@ -291,6 +291,15 @@
     }
 
     if (field->options().GetExtension(os::statsd::state_field_option).option() ==
+        os::statsd::StateField::PRIMARY_FIELD_FIRST_UID) {
+        if (javaType != JAVA_TYPE_ATTRIBUTION_CHAIN) {
+            errorCount++;
+        } else {
+            atomDecl->primaryFields.push_back(FIRST_UID_IN_CHAIN_ID);
+        }
+    }
+
+    if (field->options().GetExtension(os::statsd::state_field_option).option() ==
         os::statsd::StateField::EXCLUSIVE) {
         if (javaType == JAVA_TYPE_UNKNOWN ||
             javaType == JAVA_TYPE_ATTRIBUTION_CHAIN ||
@@ -434,7 +443,7 @@
     AtomDecl nonChainedAtomDecl(atomField->number(), atomField->name(), atom->name());
     vector<java_type_t> nonChainedSignature;
     if (get_non_chained_node(atom, &nonChainedAtomDecl, &nonChainedSignature)) {
-        auto it = atoms->non_chained_signatures_to_modules.find(signature);
+        auto it = atoms->non_chained_signatures_to_modules.find(nonChainedSignature);
         if (it == atoms->non_chained_signatures_to_modules.end()) {
             set<string> modules_non_chained;
             if (atomDecl.hasModule) {
diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h
index 3efdd52..87d4d5d 100644
--- a/tools/stats_log_api_gen/Collation.h
+++ b/tools/stats_log_api_gen/Collation.h
@@ -36,6 +36,8 @@
 
 const int PULL_ATOM_START_ID = 10000;
 
+const int FIRST_UID_IN_CHAIN_ID = 0;
+
 /**
  * The types for atom parameters.
  */
diff --git a/tools/stats_log_api_gen/atoms_info_writer.cpp b/tools/stats_log_api_gen/atoms_info_writer.cpp
index 54a9982..66ae964 100644
--- a/tools/stats_log_api_gen/atoms_info_writer.cpp
+++ b/tools/stats_log_api_gen/atoms_info_writer.cpp
@@ -25,6 +25,8 @@
 namespace stats_log_api_gen {
 
 static void write_atoms_info_header_body(FILE* out, const Atoms& atoms) {
+    fprintf(out, "static int FIRST_UID_IN_CHAIN = 0;\n");
+
     fprintf(out, "struct StateAtomFieldOptions {\n");
     fprintf(out, "  std::vector<int> primaryFields;\n");
     fprintf(out, "  int exclusiveField;\n");
diff --git a/tools/stats_log_api_gen/java_writer.cpp b/tools/stats_log_api_gen/java_writer.cpp
index 7f0872c..c29936b 100644
--- a/tools/stats_log_api_gen/java_writer.cpp
+++ b/tools/stats_log_api_gen/java_writer.cpp
@@ -73,7 +73,7 @@
                         java_type_name(chainField.javaType), chainField.name.c_str());
                 }
             } else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) {
-                fprintf(out, ", SparseArray<Object> valueMap");
+                fprintf(out, ", android.util.SparseArray<Object> valueMap");
             } else {
                 fprintf(out, ", %s arg%d", java_type_name(*arg), argIndex);
             }
@@ -85,8 +85,9 @@
         string indent("");
         if (supportQ) {
             // TODO(b/146235828): Use just SDK_INT check once it is incremented from Q.
-            fprintf(out, "        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q ||\n");
-            fprintf(out, "                Build.VERSION.CODENAME.equals(\"R\")) {\n");
+            fprintf(out, "        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q\n");
+            fprintf(out, "                || (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q\n");
+            fprintf(out, "                    && Build.VERSION.PREVIEW_SDK_INT > 0)) {\n");
             indent = "    ";
         }
 
@@ -141,16 +142,16 @@
                 fprintf(out,
                         "%s        final int count = valueMap.size();\n", indent.c_str());
                 fprintf(out,
-                        "%s        final SparseIntArray intMap = new SparseIntArray();\n",
+                        "%s        android.util.SparseIntArray intMap = null;\n",
                         indent.c_str());
                 fprintf(out,
-                        "%s        final SparseLongArray longMap = new SparseLongArray();\n",
+                        "%s        android.util.SparseLongArray longMap = null;\n",
                         indent.c_str());
                 fprintf(out,
-                        "%s        final SparseArray<String> stringMap = new SparseArray<>();\n",
+                        "%s        android.util.SparseArray<String> stringMap = null;\n",
                         indent.c_str());
                 fprintf(out,
-                        "%s        final SparseArray<Float> floatMap = new SparseArray<>();\n",
+                        "%s        android.util.SparseArray<Float> floatMap = null;\n",
                         indent.c_str());
                 fprintf(out,
                         "%s        for (int i = 0; i < count; i++) {\n", indent.c_str());
@@ -162,18 +163,42 @@
                 fprintf(out,
                         "%s            if (value instanceof Integer) {\n", indent.c_str());
                 fprintf(out,
+                        "%s                if (null == intMap) {\n", indent.c_str());
+                fprintf(out,
+                        "%s                    intMap = new android.util.SparseIntArray();\n", indent.c_str());
+                fprintf(out,
+                        "%s                }\n", indent.c_str());
+                fprintf(out,
                         "%s                intMap.put(key, (Integer) value);\n", indent.c_str());
                 fprintf(out,
                         "%s            } else if (value instanceof Long) {\n", indent.c_str());
                 fprintf(out,
+                        "%s                if (null == longMap) {\n", indent.c_str());
+                fprintf(out,
+                        "%s                    longMap = new android.util.SparseLongArray();\n", indent.c_str());
+                fprintf(out,
+                        "%s                }\n", indent.c_str());
+                fprintf(out,
                         "%s                longMap.put(key, (Long) value);\n", indent.c_str());
                 fprintf(out,
                         "%s            } else if (value instanceof String) {\n", indent.c_str());
                 fprintf(out,
+                        "%s                if (null == stringMap) {\n", indent.c_str());
+                fprintf(out,
+                        "%s                    stringMap = new android.util.SparseArray<>();\n", indent.c_str());
+                fprintf(out,
+                        "%s                }\n", indent.c_str());
+                fprintf(out,
                         "%s                stringMap.put(key, (String) value);\n", indent.c_str());
                 fprintf(out,
                         "%s            } else if (value instanceof Float) {\n", indent.c_str());
                 fprintf(out,
+                        "%s                if (null == floatMap) {\n", indent.c_str());
+                fprintf(out,
+                        "%s                    floatMap = new android.util.SparseArray<>();\n", indent.c_str());
+                fprintf(out,
+                        "%s                }\n", indent.c_str());
+                fprintf(out,
                         "%s                floatMap.put(key, (Float) value);\n", indent.c_str());
                 fprintf(out,
                         "%s            }\n", indent.c_str());
@@ -228,7 +253,8 @@
 
 int write_stats_log_java(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl,
                                     const string& moduleName, const string& javaClass,
-                                    const string& javaPackage, const bool supportQ) {
+                                    const string& javaPackage, const bool supportQ,
+                                    const bool supportWorkSource) {
     // Print prelude
     fprintf(out, "// This file is autogenerated\n");
     fprintf(out, "\n");
@@ -240,25 +266,9 @@
         fprintf(out, "import android.os.SystemClock;\n");
     }
 
-    if (DEFAULT_MODULE_NAME == moduleName) {
-        // Mainline modules don't use WorkSource logging.
-        fprintf(out, "import android.os.WorkSource;\n");
-
-        // SparseArray is used for writing KeyValuePairs; not supported for Mainline modules.
-        fprintf(out, "import android.util.SparseArray;\n");
-        fprintf(out, "import android.util.SparseIntArray;\n");
-        fprintf(out, "import android.util.SparseLongArray;\n");
-    }
-
     fprintf(out, "import android.util.StatsEvent;\n");
     fprintf(out, "import android.util.StatsLog;\n");
 
-    if (DEFAULT_MODULE_NAME == moduleName) {
-        // List is used for WorkSource writing. Only needed for default module.
-        fprintf(out, "\n");
-        fprintf(out, "import java.util.List;\n");
-    }
-
     fprintf(out, "\n");
     fprintf(out, "\n");
     fprintf(out, "/**\n");
@@ -280,7 +290,7 @@
             out, atoms.signatures_to_modules, attributionDecl, moduleName, supportQ);
     errors += write_java_non_chained_methods(
             out, atoms.non_chained_signatures_to_modules, moduleName);
-    if (DEFAULT_MODULE_NAME == moduleName) {
+    if (supportWorkSource) {
         errors += write_java_work_source_methods(out, atoms.signatures_to_modules, moduleName);
     }
 
diff --git a/tools/stats_log_api_gen/java_writer.h b/tools/stats_log_api_gen/java_writer.h
index 9324b23..5b78f05 100644
--- a/tools/stats_log_api_gen/java_writer.h
+++ b/tools/stats_log_api_gen/java_writer.h
@@ -31,8 +31,9 @@
 using namespace std;
 
 int write_stats_log_java(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl,
-                                    const string& moduleName, const string& javaClass,
-                                    const string& javaPackage, const bool supportQ);
+                         const string& moduleName, const string& javaClass,
+                         const string& javaPackage, const bool supportQ,
+                         const bool supportWorkSource);
 
 }  // namespace stats_log_api_gen
 }  // namespace android
diff --git a/tools/stats_log_api_gen/java_writer_q.cpp b/tools/stats_log_api_gen/java_writer_q.cpp
index af5055a..f866129 100644
--- a/tools/stats_log_api_gen/java_writer_q.cpp
+++ b/tools/stats_log_api_gen/java_writer_q.cpp
@@ -382,7 +382,7 @@
                         java_type_name(chainField.javaType), chainField.name.c_str());
                 }
             } else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) {
-                fprintf(out, ", SparseArray<Object> value_map");
+                fprintf(out, ", android.util.SparseArray<Object> value_map");
             } else {
                 fprintf(out, ", %s arg%d", java_type_name(*arg), argIndex);
             }
@@ -393,16 +393,13 @@
     }
 }
 
-int write_stats_log_java_q(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl) {
+int write_stats_log_java_q(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl,
+                           const bool supportWorkSource) {
     // Print prelude
     fprintf(out, "// This file is autogenerated\n");
     fprintf(out, "\n");
     fprintf(out, "package android.util;\n");
     fprintf(out, "\n");
-    fprintf(out, "import android.os.WorkSource;\n");
-    fprintf(out, "import android.util.SparseArray;\n");
-    fprintf(out, "import java.util.List;\n");
-    fprintf(out, "\n");
     fprintf(out, "\n");
     fprintf(out, "/**\n");
     fprintf(out, " * API For logging statistics events.\n");
@@ -418,16 +415,19 @@
     write_java_method(out, "write", atoms.signatures_to_modules, attributionDecl);
     write_java_method(out, "write_non_chained", atoms.non_chained_signatures_to_modules,
             attributionDecl);
-    write_java_work_source_methods(out, atoms.signatures_to_modules, DEFAULT_MODULE_NAME);
+    if (supportWorkSource) {
+        write_java_work_source_methods(out, atoms.signatures_to_modules, DEFAULT_MODULE_NAME);
+    }
 
     fprintf(out, "}\n");
 
     return 0;
 }
 
-int write_stats_log_java_q_for_module(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl,
-                                    const string& moduleName, const string& javaClass,
-                                    const string& javaPackage) {
+int write_stats_log_java_q_for_module(FILE* out, const Atoms& atoms,
+                                      const AtomDecl &attributionDecl, const string& moduleName,
+                                      const string& javaClass, const string& javaPackage,
+                                      const bool supportWorkSource) {
     // Print prelude
     fprintf(out, "// This file is autogenerated\n");
     fprintf(out, "\n");
@@ -438,8 +438,6 @@
     fprintf(out, "import android.util.StatsLog;\n");
     fprintf(out, "import android.os.SystemClock;\n");
     fprintf(out, "\n");
-    fprintf(out, "import java.util.ArrayList;\n");
-    fprintf(out, "\n");
     fprintf(out, "\n");
     fprintf(out, "/**\n");
     fprintf(out, " * Utility class for logging statistics events.\n");
@@ -459,6 +457,9 @@
             moduleName, "    ");
     errors += write_java_non_chained_methods(out, atoms.non_chained_signatures_to_modules,
             moduleName);
+    if (supportWorkSource) {
+        errors += write_java_work_source_methods(out, atoms.signatures_to_modules, moduleName);
+    }
 
     fprintf(out, "}\n");
 
diff --git a/tools/stats_log_api_gen/java_writer_q.h b/tools/stats_log_api_gen/java_writer_q.h
index 96ac745..36df1d8 100644
--- a/tools/stats_log_api_gen/java_writer_q.h
+++ b/tools/stats_log_api_gen/java_writer_q.h
@@ -46,11 +46,12 @@
         const string& indent);
 
 #if defined(STATS_SCHEMA_LEGACY)
-int write_stats_log_java_q(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl);
+int write_stats_log_java_q(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl,
+                           const bool supportWorkSource);
 
 int write_stats_log_java_q_for_module(FILE* out, const Atoms& atoms,
         const AtomDecl &attributionDecl, const string& moduleName, const string& javaClass,
-        const string& javaPackage);
+        const string& javaPackage, const bool supportWorkSource);
 #endif
 }  // namespace stats_log_api_gen
 }  // namespace android
diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp
index 00a3704..6089532 100644
--- a/tools/stats_log_api_gen/main.cpp
+++ b/tools/stats_log_api_gen/main.cpp
@@ -512,6 +512,7 @@
     fprintf(stderr, "                       Optional for Java with module.\n");
     fprintf(stderr, "                       Default is \"StatsLogInternal\"\n");
     fprintf(stderr, "  --supportQ           Include support for Android Q.\n");
+    fprintf(stderr, "  --worksource         Include support for logging WorkSource objects.\n");
 }
 
 /**
@@ -534,6 +535,7 @@
     string javaPackage = DEFAULT_JAVA_PACKAGE;
     string javaClass = DEFAULT_JAVA_CLASS;
     bool supportQ = false;
+    bool supportWorkSource = false;
 
     int index = 1;
     while (index < argc) {
@@ -626,6 +628,8 @@
             atomsInfoCppHeaderImport = argv[index];
         } else if (0 == strcmp("--supportQ", argv[index])) {
             supportQ = true;
+        } else if (0 == strcmp("--worksource", argv[index])) {
+            supportWorkSource = true;
         }
 
         index++;
@@ -728,19 +732,15 @@
             fprintf(stderr, "Unable to open file for write: %s\n", javaFilename.c_str());
             return 1;
         }
-        // If this is for a specific module, the java package must also be provided.
-        if (moduleName != DEFAULT_MODULE_NAME && javaPackage== DEFAULT_JAVA_PACKAGE) {
-            fprintf(stderr, "Must supply --javaPackage if supplying a specific module\n");
-            return 1;
-        }
 
 #if defined(STATS_SCHEMA_LEGACY)
         if (moduleName == DEFAULT_MODULE_NAME) {
             errorCount = android::stats_log_api_gen::write_stats_log_java_q(
-                    out, atoms, attributionDecl);
+                    out, atoms, attributionDecl, supportWorkSource);
         } else {
             errorCount = android::stats_log_api_gen::write_stats_log_java_q_for_module(
-                    out, atoms, attributionDecl, moduleName, javaClass, javaPackage);
+                    out, atoms, attributionDecl, moduleName, javaClass, javaPackage,
+                    supportWorkSource);
 
         }
 #else
@@ -749,7 +749,8 @@
             javaPackage = "android.util";
         }
         errorCount = android::stats_log_api_gen::write_stats_log_java(
-                out, atoms, attributionDecl, moduleName, javaClass, javaPackage, supportQ);
+                out, atoms, attributionDecl, moduleName, javaClass, javaPackage, supportQ,
+                supportWorkSource);
 #endif
 
         fclose(out);
diff --git a/tools/stats_log_api_gen/utils.cpp b/tools/stats_log_api_gen/utils.cpp
index c65d190..8c4abe4 100644
--- a/tools/stats_log_api_gen/utils.cpp
+++ b/tools/stats_log_api_gen/utils.cpp
@@ -334,7 +334,7 @@
         if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
             fprintf(out, ", android.os.WorkSource workSource");
         } else if (field->javaType == JAVA_TYPE_KEY_VALUE_PAIR) {
-            fprintf(out, ", SparseArray<Object> value_map");
+            fprintf(out, ", android.util.SparseArray<Object> value_map");
         } else if (field->javaType == JAVA_TYPE_BYTE_ARRAY) {
             fprintf(out, ", byte[] %s", field->name.c_str());
         } else {
@@ -442,7 +442,7 @@
         for (vector<java_type_t>::const_iterator arg = signature.begin();
                 arg != signature.end(); arg++) {
             if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
-                fprintf(out, ", WorkSource ws");
+                fprintf(out, ", android.os.WorkSource ws");
             } else {
                 fprintf(out, ", %s arg%d", java_type_name(*arg), argIndex);
             }
@@ -464,9 +464,10 @@
         fprintf(out, "        }\n"); // close for-loop
 
         // write() component.
-        fprintf(out, "        List<WorkSource.WorkChain> workChains = ws.getWorkChains();\n");
+        fprintf(out, "        java.util.List<android.os.WorkSource.WorkChain> workChains = "
+                "ws.getWorkChains();\n");
         fprintf(out, "        if (workChains != null) {\n");
-        fprintf(out, "            for (WorkSource.WorkChain wc : workChains) {\n");
+        fprintf(out, "            for (android.os.WorkSource.WorkChain wc : workChains) {\n");
         fprintf(out, "                write(code");
         for (int argIndex = 1; argIndex <= argIndexMax; argIndex++) {
             if (argIndex == attributionArg) {
diff --git a/wifi/Android.bp b/wifi/Android.bp
index 09d5386..dae04c6 100644
--- a/wifi/Android.bp
+++ b/wifi/Android.bp
@@ -12,9 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+filegroup {
+    name: "framework-wifi-updatable-exported-aidl-sources",
+    srcs: ["aidl-export/**/*.aidl"],
+    path: "aidl-export",
+    visibility: ["//visibility:private"],
+}
 
 filegroup {
-    name: "framework-wifi-updatable-sources",
+    name: "framework-wifi-updatable-java-sources",
     srcs: [
         "java/**/*.java",
         "java/**/*.aidl",
@@ -23,6 +29,15 @@
         ":framework-wifi-non-updatable-sources"
     ],
     path: "java",
+    visibility: ["//visibility:private"],
+}
+
+filegroup {
+    name: "framework-wifi-updatable-sources",
+    srcs: [
+        ":framework-wifi-updatable-java-sources",
+        ":framework-wifi-updatable-exported-aidl-sources",
+    ],
 }
 
 filegroup {
@@ -32,6 +47,7 @@
         // framework-wifi.jar. This is not a good idea, should move WifiNetworkScoreCache
         // to a separate package.
         "java/android/net/wifi/WifiNetworkScoreCache.java",
+        "java/android/net/wifi/WifiOemConfigStoreMigrationHook.java",
         "java/android/net/wifi/wificond/*.java",
         ":libwificond_ipc_aidl",
     ],
@@ -49,29 +65,71 @@
 
     "//frameworks/opt/net/wifi/libs/WifiTrackerLib/tests",
     "//external/robolectric-shadows:__subpackages__",
+    "//frameworks/base/packages/SettingsLib/tests/integ",
+    "//external/sl4a:__subpackages__",
 ]
 
+// wifi-service needs pre-jarjared version of framework-wifi so it can reference copied utility
+// classes before they are renamed.
 java_library {
-    name: "framework-wifi",
-    // TODO(b/140299412) should be core_current once we build against framework-system-stubs
-    sdk_version: "core_platform",
+    name: "framework-wifi-pre-jarjar",
+    // TODO(b/146757305): sdk_version should be "module_lib_current"
+    sdk_version: "core_current",
+    static_libs: [
+        "framework-wifi-util-lib",
+        "android.hardware.wifi-V1.0-java-constants",
+    ],
     libs: [
-        // TODO(b/140299412) should be framework-system-stubs once we fix all @hide dependencies
-        "framework-minus-apex",
-        "unsupportedappusage",
+        "framework-annotations-lib",
+        "unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage
+        "unsupportedappusage-annotation", // for dalvik.annotation.compat.UnsupportedAppUsage
+        "framework-telephony-stubs",
+        // TODO(b/146757305): should be unnecessary once
+        // sdk_version="module_lib_current"
+        "android_system_stubs_current",
     ],
     srcs: [
         ":framework-wifi-updatable-sources",
     ],
+    // java_api_finder must accompany `srcs`
+    plugins: ["java_api_finder"],
+    installable: false,
+    visibility: [
+        "//frameworks/opt/net/wifi/service",
+        "//frameworks/opt/net/wifi/tests/wifitests",
+    ],
+
+    // TODO(b/146757305): should be unnecessary once
+    // sdk_version="module_lib_current"
+    aidl: {
+        include_dirs: [
+            "frameworks/base/core/java",
+        ],
+    },
+}
+
+// post-jarjar version of framework-wifi
+java_library {
+    name: "framework-wifi",
+    // TODO(b/146757305): sdk_version should be "module_lib_current"
+    sdk_version: "core_current",
+    static_libs: [
+        "framework-wifi-pre-jarjar",
+    ],
+    jarjar_rules: ":wifi-jarjar-rules",
+
     installable: true,
     optimize: {
         enabled: false
     },
+    hostdex: true, // for hiddenapi check
     visibility: [
-        "//frameworks/base", // TODO(b/140299412) remove once all dependencies are fixed
         "//frameworks/opt/net/wifi/service:__subpackages__",
     ] + test_access_hidden_api_whitelist,
-    plugins: ["java_api_finder"],
+    apex_available: [
+        "com.android.wifi",
+        "test_com.android.wifi",
+    ],
 }
 
 droidstubs {
@@ -80,6 +138,8 @@
         ":framework-annotations",
         ":framework-wifi-updatable-sources",
     ],
+    // This is needed as IOnWifiActivityEnergyInfoListener.aidl in framework-wifi depends on
+    // WifiActivityEnergyInfo.aidl in framework-minus-apex
     aidl: {
         include_dirs: ["frameworks/base/core/java"],
     },
@@ -91,11 +151,6 @@
 java_library {
     name: "framework-wifi-stubs",
     srcs: [":framework-wifi-stubs-srcs"],
-    aidl: {
-        export_include_dirs: [
-            "java",
-        ],
-    },
     sdk_version: "core_current",
     libs: ["android_system_stubs_current"],
     installable: false,
@@ -116,3 +171,8 @@
     ],
     visibility: test_access_hidden_api_whitelist,
 }
+
+filegroup {
+    name: "wifi-jarjar-rules",
+    srcs: ["jarjar-rules.txt"],
+}
diff --git a/wifi/aidl-export/android/net/wifi/ScanResult.aidl b/wifi/aidl-export/android/net/wifi/ScanResult.aidl
new file mode 100644
index 0000000..b30689c
--- /dev/null
+++ b/wifi/aidl-export/android/net/wifi/ScanResult.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2008, 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.wifi;
+
+@JavaOnlyStableParcelable parcelable ScanResult;
diff --git a/wifi/java/android/net/wifi/SoftApCapability.aidl b/wifi/aidl-export/android/net/wifi/SoftApCapability.aidl
similarity index 100%
rename from wifi/java/android/net/wifi/SoftApCapability.aidl
rename to wifi/aidl-export/android/net/wifi/SoftApCapability.aidl
diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.aidl b/wifi/aidl-export/android/net/wifi/SoftApConfiguration.aidl
similarity index 100%
rename from wifi/java/android/net/wifi/SoftApConfiguration.aidl
rename to wifi/aidl-export/android/net/wifi/SoftApConfiguration.aidl
diff --git a/wifi/java/android/net/wifi/SoftApInfo.aidl b/wifi/aidl-export/android/net/wifi/SoftApInfo.aidl
similarity index 100%
rename from wifi/java/android/net/wifi/SoftApInfo.aidl
rename to wifi/aidl-export/android/net/wifi/SoftApInfo.aidl
diff --git a/wifi/java/android/net/wifi/WifiClient.aidl b/wifi/aidl-export/android/net/wifi/WifiClient.aidl
similarity index 100%
rename from wifi/java/android/net/wifi/WifiClient.aidl
rename to wifi/aidl-export/android/net/wifi/WifiClient.aidl
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.aidl b/wifi/aidl-export/android/net/wifi/WifiConfiguration.aidl
similarity index 100%
rename from wifi/java/android/net/wifi/WifiConfiguration.aidl
rename to wifi/aidl-export/android/net/wifi/WifiConfiguration.aidl
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.aidl b/wifi/aidl-export/android/net/wifi/WifiEnterpriseConfig.aidl
similarity index 100%
rename from wifi/java/android/net/wifi/WifiEnterpriseConfig.aidl
rename to wifi/aidl-export/android/net/wifi/WifiEnterpriseConfig.aidl
diff --git a/wifi/java/android/net/wifi/WifiInfo.aidl b/wifi/aidl-export/android/net/wifi/WifiInfo.aidl
similarity index 100%
rename from wifi/java/android/net/wifi/WifiInfo.aidl
rename to wifi/aidl-export/android/net/wifi/WifiInfo.aidl
diff --git a/wifi/java/android/net/wifi/WifiNetworkConnectionStatistics.aidl b/wifi/aidl-export/android/net/wifi/WifiNetworkConnectionStatistics.aidl
similarity index 100%
rename from wifi/java/android/net/wifi/WifiNetworkConnectionStatistics.aidl
rename to wifi/aidl-export/android/net/wifi/WifiNetworkConnectionStatistics.aidl
diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.aidl b/wifi/aidl-export/android/net/wifi/WifiNetworkSuggestion.aidl
similarity index 100%
rename from wifi/java/android/net/wifi/WifiNetworkSuggestion.aidl
rename to wifi/aidl-export/android/net/wifi/WifiNetworkSuggestion.aidl
diff --git a/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.aidl b/wifi/aidl-export/android/net/wifi/WifiUsabilityStatsEntry.aidl
similarity index 100%
rename from wifi/java/android/net/wifi/WifiUsabilityStatsEntry.aidl
rename to wifi/aidl-export/android/net/wifi/WifiUsabilityStatsEntry.aidl
diff --git a/wifi/java/android/net/wifi/WpsInfo.aidl b/wifi/aidl-export/android/net/wifi/WpsInfo.aidl
similarity index 100%
rename from wifi/java/android/net/wifi/WpsInfo.aidl
rename to wifi/aidl-export/android/net/wifi/WpsInfo.aidl
diff --git a/wifi/java/android/net/wifi/aware/Characteristics.aidl b/wifi/aidl-export/android/net/wifi/aware/Characteristics.aidl
similarity index 100%
rename from wifi/java/android/net/wifi/aware/Characteristics.aidl
rename to wifi/aidl-export/android/net/wifi/aware/Characteristics.aidl
diff --git a/wifi/java/android/net/wifi/aware/PublishConfig.aidl b/wifi/aidl-export/android/net/wifi/aware/PublishConfig.aidl
similarity index 100%
rename from wifi/java/android/net/wifi/aware/PublishConfig.aidl
rename to wifi/aidl-export/android/net/wifi/aware/PublishConfig.aidl
diff --git a/wifi/java/android/net/wifi/aware/SubscribeConfig.aidl b/wifi/aidl-export/android/net/wifi/aware/SubscribeConfig.aidl
similarity index 100%
rename from wifi/java/android/net/wifi/aware/SubscribeConfig.aidl
rename to wifi/aidl-export/android/net/wifi/aware/SubscribeConfig.aidl
diff --git a/wifi/java/android/net/wifi/hotspot2/OsuProvider.aidl b/wifi/aidl-export/android/net/wifi/hotspot2/OsuProvider.aidl
similarity index 100%
rename from wifi/java/android/net/wifi/hotspot2/OsuProvider.aidl
rename to wifi/aidl-export/android/net/wifi/hotspot2/OsuProvider.aidl
diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.aidl b/wifi/aidl-export/android/net/wifi/hotspot2/PasspointConfiguration.aidl
similarity index 100%
rename from wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.aidl
rename to wifi/aidl-export/android/net/wifi/hotspot2/PasspointConfiguration.aidl
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.aidl b/wifi/aidl-export/android/net/wifi/hotspot2/pps/Credential.aidl
similarity index 100%
rename from wifi/java/android/net/wifi/hotspot2/pps/Credential.aidl
rename to wifi/aidl-export/android/net/wifi/hotspot2/pps/Credential.aidl
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.aidl b/wifi/aidl-export/android/net/wifi/hotspot2/pps/HomeSp.aidl
similarity index 100%
rename from wifi/java/android/net/wifi/hotspot2/pps/HomeSp.aidl
rename to wifi/aidl-export/android/net/wifi/hotspot2/pps/HomeSp.aidl
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.aidl b/wifi/aidl-export/android/net/wifi/p2p/WifiP2pConfig.aidl
similarity index 100%
rename from wifi/java/android/net/wifi/p2p/WifiP2pConfig.aidl
rename to wifi/aidl-export/android/net/wifi/p2p/WifiP2pConfig.aidl
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pDevice.aidl b/wifi/aidl-export/android/net/wifi/p2p/WifiP2pDevice.aidl
similarity index 100%
rename from wifi/java/android/net/wifi/p2p/WifiP2pDevice.aidl
rename to wifi/aidl-export/android/net/wifi/p2p/WifiP2pDevice.aidl
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.aidl b/wifi/aidl-export/android/net/wifi/p2p/WifiP2pDeviceList.aidl
similarity index 100%
rename from wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.aidl
rename to wifi/aidl-export/android/net/wifi/p2p/WifiP2pDeviceList.aidl
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pGroup.aidl b/wifi/aidl-export/android/net/wifi/p2p/WifiP2pGroup.aidl
similarity index 100%
rename from wifi/java/android/net/wifi/p2p/WifiP2pGroup.aidl
rename to wifi/aidl-export/android/net/wifi/p2p/WifiP2pGroup.aidl
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pInfo.aidl b/wifi/aidl-export/android/net/wifi/p2p/WifiP2pInfo.aidl
similarity index 100%
rename from wifi/java/android/net/wifi/p2p/WifiP2pInfo.aidl
rename to wifi/aidl-export/android/net/wifi/p2p/WifiP2pInfo.aidl
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.aidl b/wifi/aidl-export/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.aidl
similarity index 100%
rename from wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.aidl
rename to wifi/aidl-export/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.aidl
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.aidl b/wifi/aidl-export/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.aidl
similarity index 100%
rename from wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.aidl
rename to wifi/aidl-export/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.aidl
diff --git a/wifi/java/android/net/wifi/rtt/RangingRequest.aidl b/wifi/aidl-export/android/net/wifi/rtt/RangingRequest.aidl
similarity index 100%
rename from wifi/java/android/net/wifi/rtt/RangingRequest.aidl
rename to wifi/aidl-export/android/net/wifi/rtt/RangingRequest.aidl
diff --git a/wifi/java/android/net/wifi/rtt/RangingResult.aidl b/wifi/aidl-export/android/net/wifi/rtt/RangingResult.aidl
similarity index 100%
rename from wifi/java/android/net/wifi/rtt/RangingResult.aidl
rename to wifi/aidl-export/android/net/wifi/rtt/RangingResult.aidl
diff --git a/wifi/java/android/net/wifi/rtt/ResponderConfig.aidl b/wifi/aidl-export/android/net/wifi/rtt/ResponderConfig.aidl
similarity index 100%
rename from wifi/java/android/net/wifi/rtt/ResponderConfig.aidl
rename to wifi/aidl-export/android/net/wifi/rtt/ResponderConfig.aidl
diff --git a/wifi/jarjar-rules.txt b/wifi/jarjar-rules.txt
new file mode 100644
index 0000000..950361c
--- /dev/null
+++ b/wifi/jarjar-rules.txt
@@ -0,0 +1,44 @@
+rule android.net.InterfaceConfigurationParcel* @0
+rule android.net.InterfaceConfiguration* com.android.server.x.wifi.net.InterfaceConfiguration@1
+rule android.net.NetworkFactory* com.android.server.x.wifi.net.NetworkFactory@1
+rule android.net.util.NetUtils* com.android.server.x.wifi.net.util.NetUtils@1
+
+# We don't jar-jar the entire package because, we still use some classes (like
+# AsyncChannel in com.android.internal.util) from these packages which are not
+# inside our jar (currently in framework.jar, but will be in wifisdk.jar in the future).
+rule com.android.internal.util.FastXmlSerializer* com.android.server.x.wifi.util.FastXmlSerializer@1
+rule com.android.internal.util.HexDump* com.android.server.x.wifi.util.HexDump@1
+rule com.android.internal.util.IState* com.android.server.x.wifi.util.IState@1
+rule com.android.internal.util.MessageUtils* com.android.server.x.wifi.util.MessageUtils@1
+rule com.android.internal.util.State* com.android.server.x.wifi.util.State@1
+rule com.android.internal.util.StateMachine* com.android.server.x.wifi.util.StateMachine@1
+rule com.android.internal.util.WakeupMessage* com.android.server.x.wifi.util.WakeupMessage@1
+
+rule android.util.BackupUtils* com.android.server.x.wifi.util.BackupUtils@1
+rule android.util.LocalLog* com.android.server.x.wifi.util.LocalLog@1
+rule android.util.Rational* com.android.server.x.wifi.util.Rational@1
+
+rule android.os.BasicShellCommandHandler* com.android.server.x.wifi.os.BasicShellCommandHandler@1
+
+# Use our statically linked bouncy castle library
+rule org.bouncycastle.** com.android.server.x.wifi.bouncycastle.@1
+# Use our statically linked protobuf library
+rule com.google.protobuf.** com.android.server.x.wifi.protobuf.@1
+# use statically linked SystemMessageProto
+rule com.android.internal.messages.SystemMessageProto* com.android.server.x.wifi.messages.SystemMessageProto@1
+# Use our statically linked PlatformProperties library
+rule android.sysprop.** com.android.server.x.wifi.sysprop.@1
+
+
+# used by both framework-wifi and wifi-service
+rule android.content.pm.BaseParceledListSlice* android.x.net.wifi.util.BaseParceledListSlice@1
+rule android.content.pm.ParceledListSlice* android.x.net.wifi.util.ParceledListSlice@1
+rule android.net.shared.Inet4AddressUtils* android.x.net.wifi.util.Inet4AddressUtils@1
+rule android.net.util.MacAddressUtils* android.x.net.wifi.util.MacAddressUtils@1
+rule android.net.util.nsd.DnsSdTxtRecord* android.x.net.wifi.util.nsd.DnsSdTxtRecord@1
+rule android.os.HandlerExecutor* android.x.net.wifi.util.HandlerExecutor@1
+rule android.telephony.Annotation* android.x.net.wifi.util.TelephonyAnnotation@1
+rule com.android.internal.util.AsyncChannel* android.x.net.wifi.util.AsyncChannel@1
+rule com.android.internal.util.AsyncService* android.x.net.wifi.util.AsyncService@1
+rule com.android.internal.util.Preconditions* android.x.net.wifi.util.Preconditions@1
+rule com.android.internal.util.Protocol* android.x.net.wifi.util.Protocol@1
diff --git a/wifi/java/android/net/wifi/ILocalOnlyHotspotCallback.aidl b/wifi/java/android/net/wifi/ILocalOnlyHotspotCallback.aidl
index b83b594..b567f29 100644
--- a/wifi/java/android/net/wifi/ILocalOnlyHotspotCallback.aidl
+++ b/wifi/java/android/net/wifi/ILocalOnlyHotspotCallback.aidl
@@ -16,7 +16,7 @@
 
 package android.net.wifi;
 
-import android.net.wifi.WifiConfiguration;
+import android.net.wifi.SoftApConfiguration;
 
 /**
  * Communicates LOHS status back to the application process.
@@ -24,7 +24,7 @@
  * @hide
  */
 oneway interface ILocalOnlyHotspotCallback {
-    void onHotspotStarted(in WifiConfiguration config);
+    void onHotspotStarted(in SoftApConfiguration config);
     void onHotspotStopped();
     void onHotspotFailed(int reason);
 }
diff --git a/wifi/java/android/net/wifi/IScoreChangeCallback.aidl b/wifi/java/android/net/wifi/IScoreChangeCallback.aidl
new file mode 100644
index 0000000..fd23610
--- /dev/null
+++ b/wifi/java/android/net/wifi/IScoreChangeCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.wifi;
+
+/**
+ * Interface for Wi-Fi network score callback.
+ *
+ * @hide
+ */
+oneway interface IScoreChangeCallback
+{
+    void onStatusChange(int sessionId, boolean exiting);
+
+    void onTriggerUpdateOfWifiUsabilityStats(int sessionId);
+}
diff --git a/wifi/java/android/net/wifi/ISoftApCallback.aidl b/wifi/java/android/net/wifi/ISoftApCallback.aidl
index 482b491..f81bcb9 100644
--- a/wifi/java/android/net/wifi/ISoftApCallback.aidl
+++ b/wifi/java/android/net/wifi/ISoftApCallback.aidl
@@ -55,9 +55,17 @@
 
 
     /**
-     * Service to manager callback providing information of softap.
+     * Service to manager callback providing capability of softap.
      *
      * @param capability is the softap capability. {@link SoftApCapability}
      */
     void onCapabilityChanged(in SoftApCapability capability);
+
+    /**
+     * Service to manager callback providing blocked client of softap with specific reason code.
+     *
+     * @param client the currently blocked client.
+     * @param blockedReason one of blocked reason from {@link WifiManager.SapClientBlockedReason}
+     */
+    void onBlockedClientConnecting(in WifiClient client, int blockedReason);
 }
diff --git a/wifi/java/android/net/wifi/IWifiConnectedNetworkScorer.aidl b/wifi/java/android/net/wifi/IWifiConnectedNetworkScorer.aidl
new file mode 100644
index 0000000..d9a3b01
--- /dev/null
+++ b/wifi/java/android/net/wifi/IWifiConnectedNetworkScorer.aidl
@@ -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.
+ */
+
+package android.net.wifi;
+
+import android.net.wifi.IScoreChangeCallback;
+
+/**
+ * Interface for Wi-Fi connected network scorer.
+ *
+ * @hide
+ */
+oneway interface IWifiConnectedNetworkScorer
+{
+    void start(int sessionId);
+
+    void stop(int sessionId);
+
+    void setScoreChangeCallback(IScoreChangeCallback cbImpl);
+}
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 1678d5a..558de7c 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -35,6 +35,7 @@
 import android.net.wifi.ISuggestionConnectionStatusListener;
 import android.net.wifi.ITrafficStateCallback;
 import android.net.wifi.ITxPacketCountListener;
+import android.net.wifi.IWifiConnectedNetworkScorer;
 import android.net.wifi.ScanResult;
 import android.net.wifi.SoftApConfiguration;
 import android.net.wifi.WifiConfiguration;
@@ -88,8 +89,14 @@
 
     boolean disableNetwork(int netId, String packageName);
 
+    void allowAutojoinGlobal(boolean choice);
+
     void allowAutojoin(int netId, boolean choice);
 
+    void allowAutojoinPasspoint(String fqdn, boolean enableAutoJoin);
+
+    void setMacRandomizationSettingPasspointEnabled(String fqdn, boolean enable);
+
     boolean startScan(String packageName, String featureId);
 
     List<ScanResult> getScanResults(String callingPackage, String callingFeatureId);
@@ -112,6 +119,8 @@
 
     boolean is6GHzBandSupported();
 
+    boolean isWifiStandardSupported(int standard);
+
     boolean needs5GHzToAnyApBandConversion();
 
     DhcpInfo getDhcpInfo();
@@ -173,8 +182,6 @@
 
     int getVerboseLoggingLevel();
 
-    void enableWifiConnectivityManager(boolean enabled);
-
     void disableEphemeralNetwork(String SSID, String packageName);
 
     void factoryReset(String packageName);
@@ -248,4 +255,15 @@
     void unregisterSuggestionConnectionStatusListener(int listenerIdentifier, String packageName);
 
     int calculateSignalLevel(int rssi);
+
+    List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(in List<ScanResult> scanResults);
+
+    boolean setWifiConnectedNetworkScorer(in IBinder binder, in IWifiConnectedNetworkScorer scorer);
+
+    void clearWifiConnectedNetworkScorer();
+
+    /**
+     * Return the Map of {@link WifiNetworkSuggestion} and the list of <ScanResult>
+     */
+    Map getMatchingScanResults(in List<WifiNetworkSuggestion> networkSuggestions, in List<ScanResult> scanResults, String callingPackage, String callingFeatureId);
 }
diff --git a/wifi/java/android/net/wifi/ScanResult.aidl b/wifi/java/android/net/wifi/ScanResult.aidl
deleted file mode 100644
index bb66722..0000000
--- a/wifi/java/android/net/wifi/ScanResult.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (c) 2008, 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.wifi;
-
-parcelable ScanResult;
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index c6aca07..3fda6cd 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -17,16 +17,21 @@
 package android.net.wifi;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 
@@ -574,67 +579,120 @@
     @UnsupportedAppUsage
     public List<String> anqpLines;
 
-    /** information elements from beacon
-     * @hide
+    /**
+     * information elements from beacon.
      */
     public static class InformationElement {
+        /** @hide */
         @UnsupportedAppUsage
         public static final int EID_SSID = 0;
+        /** @hide */
         @UnsupportedAppUsage
         public static final int EID_SUPPORTED_RATES = 1;
+        /** @hide */
         @UnsupportedAppUsage
         public static final int EID_TIM = 5;
+        /** @hide */
         @UnsupportedAppUsage
         public static final int EID_BSS_LOAD = 11;
+        /** @hide */
         @UnsupportedAppUsage
         public static final int EID_ERP = 42;
+        /** @hide */
         public static final int EID_HT_CAPABILITIES = 45;
+        /** @hide */
         @UnsupportedAppUsage
         public static final int EID_RSN = 48;
+        /** @hide */
         @UnsupportedAppUsage
         public static final int EID_EXTENDED_SUPPORTED_RATES = 50;
+        /** @hide */
         @UnsupportedAppUsage
         public static final int EID_HT_OPERATION = 61;
+        /** @hide */
         @UnsupportedAppUsage
         public static final int EID_INTERWORKING = 107;
+        /** @hide */
         @UnsupportedAppUsage
         public static final int EID_ROAMING_CONSORTIUM = 111;
+        /** @hide */
         @UnsupportedAppUsage
         public static final int EID_EXTENDED_CAPS = 127;
+        /** @hide */
         public static final int EID_VHT_CAPABILITIES = 191;
+        /** @hide */
         @UnsupportedAppUsage
         public static final int EID_VHT_OPERATION = 192;
+        /** @hide */
         @UnsupportedAppUsage
         public static final int EID_VSA = 221;
+        /** @hide */
         public static final int EID_EXTENSION_PRESENT = 255;
 
-        /**
-         * Extension IDs
-         */
+        // Extension IDs
+        /** @hide */
         public static final int EID_EXT_HE_CAPABILITIES = 35;
+        /** @hide */
         public static final int EID_EXT_HE_OPERATION = 36;
 
+        /** @hide */
         @UnsupportedAppUsage
         public int id;
+        /** @hide */
         public int idExt;
+
+        /** @hide */
         @UnsupportedAppUsage
         public byte[] bytes;
 
+        /** @hide */
         public InformationElement() {
         }
 
-        public InformationElement(InformationElement rhs) {
+        public InformationElement(@NonNull InformationElement rhs) {
             this.id = rhs.id;
             this.idExt = rhs.idExt;
             this.bytes = rhs.bytes.clone();
         }
+
+        /**
+         * The element ID of the information element. Defined in the IEEE 802.11-2016 spec
+         * Table 9-77.
+         */
+        public int getId() {
+            return id;
+        }
+
+        /**
+         * The element ID Extension of the information element. Defined in the IEEE 802.11-2016 spec
+         * Table 9-77.
+         */
+        public int getIdExt() {
+            return idExt;
+        }
+
+        /**
+         * Get the specific content of the information element.
+         */
+        @NonNull
+        public ByteBuffer getBytes() {
+            return ByteBuffer.wrap(bytes).asReadOnlyBuffer();
+        }
     }
 
-    /** information elements found in the beacon
+    /**
+     * information elements found in the beacon.
      * @hide
      */
     @UnsupportedAppUsage
     public InformationElement[] informationElements;
+    /**
+     * Get all information elements found in the beacon.
+     */
+    @NonNull
+    public List<InformationElement> getInformationElements() {
+        return Collections.unmodifiableList(Arrays.asList(informationElements));
+    }
 
     /** ANQP response elements.
      * @hide
@@ -760,8 +818,8 @@
         this.wifiSsid = wifiSsid;
     }
 
-    /** copy constructor {@hide} */
-    public ScanResult(ScanResult source) {
+    /** copy constructor */
+    public ScanResult(@NonNull ScanResult source) {
         if (source != null) {
             wifiSsid = source.wifiSsid;
             SSID = source.SSID;
@@ -793,10 +851,19 @@
         }
     }
 
-    /** empty scan result
+    /**
+     * Construct an empty scan result.
      *
-     * {@hide}
-     * */
+     * Test code has a need to construct a ScanResult in a specific state.
+     * (Note that mocking using Mockito does not work if the object needs to be parceled and
+     * unparceled.)
+     * Export a @SystemApi default constructor to allow tests to construct an empty ScanResult
+     * object. The test can then directly set the fields it cares about.
+     *
+     * @hide
+     */
+    @SystemApi
+    @VisibleForTesting
     public ScanResult() {
     }
 
@@ -918,9 +985,8 @@
         }
     }
 
-    /** Implement the Parcelable interface {@hide} */
-    @UnsupportedAppUsage
-    public static final @android.annotation.NonNull Creator<ScanResult> CREATOR =
+    /** Implement the Parcelable interface */
+    public static final @NonNull Creator<ScanResult> CREATOR =
         new Creator<ScanResult>() {
             public ScanResult createFromParcel(Parcel in) {
                 WifiSsid wifiSsid = null;
diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java
index 65e9b79..c8fd243 100644
--- a/wifi/java/android/net/wifi/SoftApConfiguration.java
+++ b/wifi/java/android/net/wifi/SoftApConfiguration.java
@@ -24,6 +24,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
@@ -32,8 +33,10 @@
 import java.lang.annotation.RetentionPolicy;
 import java.nio.charset.CharsetEncoder;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
 import java.util.Objects;
-import java.util.concurrent.Executor;
 
 /**
  * Configuration for a soft access point (a.k.a. Soft AP, SAP, Hotspot).
@@ -42,22 +45,23 @@
  * framework how it should configure a hotspot.
  *
  * System apps can use this to configure a tethered hotspot using
- * {@link WifiManager#startTetheredHotspot(SoftApConfiguration)} and
- * {@link WifiManager#setSoftApConfiguration(SoftApConfiguration)}
+ * {@code WifiManager#startTetheredHotspot(SoftApConfiguration)} and
+ * {@code WifiManager#setSoftApConfiguration(SoftApConfiguration)}
  * or local-only hotspot using
- * {@link WifiManager#startLocalOnlyHotspot(SoftApConfiguration, Executor,
+ * {@code WifiManager#startLocalOnlyHotspot(SoftApConfiguration, Executor,
  * WifiManager.LocalOnlyHotspotCallback)}.
  *
  * Instances of this class are immutable; use {@link SoftApConfiguration.Builder} and its methods to
  * create a new instance.
  *
- * @hide
  */
-@SystemApi
 public final class SoftApConfiguration implements Parcelable {
 
+    private static final String TAG = "SoftApConfiguration";
+
     @VisibleForTesting
     static final int PSK_MIN_LEN = 8;
+
     @VisibleForTesting
     static final int PSK_MAX_LEN = 63;
 
@@ -182,22 +186,45 @@
     private final @SecurityType int mSecurityType;
 
     /**
-     * Security types we support.
+     * The flag to indicate client need to authorize by user
+     * when client is connecting to AP.
      */
-    /** @hide */
-    @SystemApi
+    private final boolean mClientControlByUser;
+
+    /**
+     * The list of blocked client that can't associate to the AP.
+     */
+    private final List<MacAddress> mBlockedClientList;
+
+    /**
+     * The list of allowed client that can associate to the AP.
+     */
+    private final List<MacAddress> mAllowedClientList;
+
+    /**
+     * Delay in milliseconds before shutting down soft AP when
+     * there are no connected devices.
+     */
+    private final int mShutdownTimeoutMillis;
+
+    /**
+     * THe definition of security type OPEN.
+     */
     public static final int SECURITY_TYPE_OPEN = 0;
 
-    /** @hide */
-    @SystemApi
+    /**
+     * The definition of security type WPA2-PSK.
+     */
     public static final int SECURITY_TYPE_WPA2_PSK = 1;
 
-    /** @hide */
-    @SystemApi
+    /**
+     * The definition of security type WPA3-SAE Transition mode.
+     */
     public static final int SECURITY_TYPE_WPA3_SAE_TRANSITION = 2;
 
-    /** @hide */
-    @SystemApi
+    /**
+     * The definition of security type WPA3-SAE.
+     */
     public static final int SECURITY_TYPE_WPA3_SAE = 3;
 
     /** @hide */
@@ -213,7 +240,9 @@
     /** Private constructor for Builder and Parcelable implementation. */
     private SoftApConfiguration(@Nullable String ssid, @Nullable MacAddress bssid,
             @Nullable String passphrase, boolean hiddenSsid, @BandType int band, int channel,
-            @SecurityType int securityType, int maxNumberOfClients) {
+            @SecurityType int securityType, int maxNumberOfClients, int shutdownTimeoutMillis,
+            boolean clientControlByUser, @NonNull List<MacAddress> blockedList,
+            @NonNull List<MacAddress> allowedList) {
         mSsid = ssid;
         mBssid = bssid;
         mPassphrase = passphrase;
@@ -222,6 +251,10 @@
         mChannel = channel;
         mSecurityType = securityType;
         mMaxNumberOfClients = maxNumberOfClients;
+        mShutdownTimeoutMillis = shutdownTimeoutMillis;
+        mClientControlByUser = clientControlByUser;
+        mBlockedClientList = new ArrayList<>(blockedList);
+        mAllowedClientList = new ArrayList<>(allowedList);
     }
 
     @Override
@@ -240,13 +273,18 @@
                 && mBand == other.mBand
                 && mChannel == other.mChannel
                 && mSecurityType == other.mSecurityType
-                && mMaxNumberOfClients == other.mMaxNumberOfClients;
+                && mMaxNumberOfClients == other.mMaxNumberOfClients
+                && mShutdownTimeoutMillis == other.mShutdownTimeoutMillis
+                && mClientControlByUser == other.mClientControlByUser
+                && Objects.equals(mBlockedClientList, other.mBlockedClientList)
+                && Objects.equals(mAllowedClientList, other.mAllowedClientList);
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mSsid, mBssid, mPassphrase, mHiddenSsid,
-                mBand, mChannel, mSecurityType, mMaxNumberOfClients);
+                mBand, mChannel, mSecurityType, mMaxNumberOfClients, mShutdownTimeoutMillis,
+                mClientControlByUser, mBlockedClientList, mAllowedClientList);
     }
 
     @Override
@@ -261,6 +299,10 @@
         sbuf.append(" \n Channel =").append(mChannel);
         sbuf.append(" \n SecurityType=").append(getSecurityType());
         sbuf.append(" \n MaxClient=").append(mMaxNumberOfClients);
+        sbuf.append(" \n ShutdownTimeoutMillis=").append(mShutdownTimeoutMillis);
+        sbuf.append(" \n ClientControlByUser=").append(mClientControlByUser);
+        sbuf.append(" \n BlockedClientList=").append(mBlockedClientList);
+        sbuf.append(" \n AllowedClientList=").append(mAllowedClientList);
         return sbuf.toString();
     }
 
@@ -274,6 +316,10 @@
         dest.writeInt(mChannel);
         dest.writeInt(mSecurityType);
         dest.writeInt(mMaxNumberOfClients);
+        dest.writeInt(mShutdownTimeoutMillis);
+        dest.writeBoolean(mClientControlByUser);
+        dest.writeTypedList(mBlockedClientList);
+        dest.writeTypedList(mAllowedClientList);
     }
 
     @Override
@@ -289,7 +335,9 @@
                     in.readString(),
                     in.readParcelable(MacAddress.class.getClassLoader()),
                     in.readString(), in.readBoolean(), in.readInt(), in.readInt(), in.readInt(),
-                    in.readInt());
+                    in.readInt(), in.readInt(), in.readBoolean(),
+                    in.createTypedArrayList(MacAddress.CREATOR),
+                    in.createTypedArrayList(MacAddress.CREATOR));
         }
 
         @Override
@@ -300,7 +348,7 @@
 
     /**
      * Return String set to be the SSID for the AP.
-     * {@link #setSsid(String)}.
+     * {@link Builder#setSsid(String)}.
      */
     @Nullable
     public String getSsid() {
@@ -316,22 +364,9 @@
         return mBssid;
     }
 
-    // TODO: Remove it after update the caller
-    /**
-     * Returns String set to be passphrase for the WPA2-PSK AP.
-     * {@link #setWpa2Passphrase(String)}.
-     */
-    @Nullable
-    public String getWpa2Passphrase() {
-        if (mSecurityType == SECURITY_TYPE_WPA2_PSK) {
-            return mPassphrase;
-        }
-        return null;
-    }
-
     /**
      * Returns String set to be passphrase for current AP.
-     * {@link #setPassphrase(String, @SecurityType int)}.
+     * {@link Builder#setPassphrase(String, int)}.
      */
     @Nullable
     public String getPassphrase() {
@@ -350,7 +385,10 @@
     /**
      * Returns {@link BandType} set to be the band for the AP.
      * {@link Builder#setBand(@BandType int)}.
+     *
+     * @hide
      */
+    @SystemApi
     public @BandType int getBand() {
         return mBand;
     }
@@ -358,7 +396,10 @@
     /**
      * Returns Integer set to be the channel for the AP.
      * {@link Builder#setChannel(int)}.
+     *
+     * @hide
      */
+    @SystemApi
     public int getChannel() {
         return mChannel;
     }
@@ -366,7 +407,11 @@
     /**
      * Get security type params which depends on which security passphrase to set.
      *
-     * @return One of the security types from {@link SecurityType}.
+     * @return One of:
+     * {@link #SECURITY_TYPE_OPEN},
+     * {@link #SECURITY_TYPE_WPA2_PSK},
+     * {@link #SECURITY_TYPE_WPA3_SAE_TRANSITION},
+     * {@link #SECURITY_TYPE_WPA3_SAE}
      */
     public @SecurityType int getSecurityType() {
         return mSecurityType;
@@ -375,18 +420,125 @@
     /**
      * Returns the maximum number of clients that can associate to the AP.
      * {@link Builder#setMaxNumberOfClients(int)}.
+     *
+     * @hide
      */
+    @SystemApi
     public int getMaxNumberOfClients() {
         return mMaxNumberOfClients;
     }
 
     /**
+     * Returns the shutdown timeout in milliseconds.
+     * The Soft AP will shutdown when there are no devices associated to it for
+     * the timeout duration. See {@link Builder#setShutdownTimeoutMillis(int)}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public int getShutdownTimeoutMillis() {
+        return mShutdownTimeoutMillis;
+    }
+
+    /**
+     * Returns a flag indicating whether clients need to be pre-approved by the user.
+     * (true: authorization required) or not (false: not required).
+     * {@link Builder#enableClientControlByUser(Boolean)}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public boolean isClientControlByUserEnabled() {
+        return mClientControlByUser;
+    }
+
+    /**
+     * Returns List of clients which aren't allowed to associate to the AP.
+     *
+     * Clients are configured using {@link Builder#setClientList(List, List)}
+     *
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    public List<MacAddress> getBlockedClientList() {
+        return mBlockedClientList;
+    }
+
+    /**
+     * List of clients which are allowed to associate to the AP.
+     * Clients are configured using {@link Builder#setClientList(List, List)}
+     *
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    public List<MacAddress> getAllowedClientList() {
+        return mAllowedClientList;
+    }
+
+    /**
+     * Returns a {@link WifiConfiguration} representation of this {@link SoftApConfiguration}.
+     * Note that SoftApConfiguration may contain configuration which is cannot be represented
+     * by the legacy WifiConfiguration, in such cases a null will be returned.
+     *
+     * <li> SoftAp band in {@link WifiConfiguration.apBand} only supports
+     * 2GHz, 5GHz, 2GHz+5GHz bands, so conversion is limited to these bands. </li>
+     *
+     * <li> SoftAp security type in {@link WifiConfiguration.KeyMgmt} only supports
+     * NONE, WPA2_PSK, so conversion is limited to these security type.</li>
+     * @hide
+     */
+    @Nullable
+    @SystemApi
+    public WifiConfiguration toWifiConfiguration() {
+        WifiConfiguration wifiConfig = new WifiConfiguration();
+        wifiConfig.SSID = mSsid;
+        if (mBssid != null) {
+            wifiConfig.BSSID = mBssid.toString();
+        }
+        wifiConfig.preSharedKey = mPassphrase;
+        wifiConfig.hiddenSSID = mHiddenSsid;
+        wifiConfig.apChannel = mChannel;
+        switch (mSecurityType) {
+            case SECURITY_TYPE_OPEN:
+                wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+                break;
+            case SECURITY_TYPE_WPA2_PSK:
+                wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA2_PSK);
+                break;
+            default:
+                Log.e(TAG, "Convert fail, unsupported security type :" + mSecurityType);
+                return null;
+        }
+
+        switch (mBand) {
+            case BAND_2GHZ:
+                wifiConfig.apBand  = WifiConfiguration.AP_BAND_2GHZ;
+                break;
+            case BAND_5GHZ:
+                wifiConfig.apBand  = WifiConfiguration.AP_BAND_5GHZ;
+                break;
+            case BAND_ANY:
+                wifiConfig.apBand  = WifiConfiguration.AP_BAND_ANY;
+                break;
+            default:
+                Log.e(TAG, "Convert fail, unsupported band setting :" + mBand);
+                return null;
+        }
+        return wifiConfig;
+    }
+
+    /**
      * Builds a {@link SoftApConfiguration}, which allows an app to configure various aspects of a
      * Soft AP.
      *
      * All fields are optional. By default, SSID and BSSID are automatically chosen by the
      * framework, and an open network is created.
+     *
+     * @hide
      */
+    @SystemApi
     public static final class Builder {
         private String mSsid;
         private MacAddress mBssid;
@@ -396,6 +548,10 @@
         private int mChannel;
         private int mMaxNumberOfClients;
         private int mSecurityType;
+        private int mShutdownTimeoutMillis;
+        private boolean mClientControlByUser;
+        private List<MacAddress> mBlockedClientList;
+        private List<MacAddress> mAllowedClientList;
 
         /**
          * Constructs a Builder with default values (see {@link Builder}).
@@ -409,6 +565,10 @@
             mChannel = 0;
             mMaxNumberOfClients = 0;
             mSecurityType = SECURITY_TYPE_OPEN;
+            mShutdownTimeoutMillis = 0;
+            mClientControlByUser = false;
+            mBlockedClientList = new ArrayList<>();
+            mAllowedClientList = new ArrayList<>();
         }
 
         /**
@@ -425,6 +585,10 @@
             mChannel = other.mChannel;
             mMaxNumberOfClients = other.mMaxNumberOfClients;
             mSecurityType = other.mSecurityType;
+            mShutdownTimeoutMillis = other.mShutdownTimeoutMillis;
+            mClientControlByUser = other.mClientControlByUser;
+            mBlockedClientList = new ArrayList<>(other.mBlockedClientList);
+            mAllowedClientList = new ArrayList<>(other.mAllowedClientList);
         }
 
         /**
@@ -435,7 +599,9 @@
         @NonNull
         public SoftApConfiguration build() {
             return new SoftApConfiguration(mSsid, mBssid, mPassphrase,
-                mHiddenSsid, mBand, mChannel, mSecurityType, mMaxNumberOfClients);
+                    mHiddenSsid, mBand, mChannel, mSecurityType, mMaxNumberOfClients,
+                    mShutdownTimeoutMillis, mClientControlByUser, mBlockedClientList,
+                    mAllowedClientList);
         }
 
         /**
@@ -482,22 +648,6 @@
             return this;
         }
 
-        // TODO: Remove it after update the caller
-        /**
-         * Specifies that this AP should use WPA2-PSK with the given ASCII WPA2 passphrase.
-         * When set to null, an open network is created.
-         * <p>
-         *
-         * @param passphrase The passphrase to use, or null to unset a previously-set WPA2-PSK
-         *                   configuration.
-         * @return Builder for chaining.
-         * @throws IllegalArgumentException when the passphrase is the empty string
-         */
-        @NonNull
-        public Builder setWpa2Passphrase(@Nullable String passphrase) {
-            return setPassphrase(passphrase, SECURITY_TYPE_WPA2_PSK);
-        }
-
         /**
          * Specifies that this AP should use specific security type with the given ASCII passphrase.
          *
@@ -643,5 +793,107 @@
             mMaxNumberOfClients = maxNumberOfClients;
             return this;
         }
+
+        /**
+         * Specifies the shutdown timeout in milliseconds.
+         * The Soft AP will shut down when there are no devices connected to it for
+         * the timeout duration.
+         *
+         * Specify a value of 0 to have the framework automatically use default timeout
+         * setting which defined in {@link R.integer.config_wifi_framework_soft_ap_timeout_delay}
+         *
+         * <p>
+         * <li>If not set, defaults to 0</li>
+         * <li>The shut down timout will apply when
+         * {@link Settings.Global.SOFT_AP_TIMEOUT_ENABLED} is true</li>
+         *
+         * @param timeoutMillis milliseconds of the timeout delay.
+         * @return Builder for chaining.
+         */
+        @NonNull
+        public Builder setShutdownTimeoutMillis(int timeoutMillis) {
+            if (timeoutMillis < 0) {
+                throw new IllegalArgumentException("Invalid timeout value");
+            }
+            mShutdownTimeoutMillis = timeoutMillis;
+            return this;
+        }
+
+        /**
+         * Configure the Soft AP to require manual user control of client association.
+         * If disabled (the default) then any client can associate to this Soft AP using the
+         * correct credentials until the Soft AP capacity is reached (capacity is hardware, carrier,
+         * or user limited - using {@link #setMaxNumberOfClients(int)}).
+         *
+         * If manual user control is enabled then clients will be accepted, rejected, or require
+         * a user approval based on the configuration provided by
+         * {@link #setClientList(List, List)}.
+         *
+         * <p>
+         * This method requires hardware support. Hardware support can be determined using
+         * {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)} and
+         * {@link SoftApCapability#isFeatureSupported(int)}
+         * with {@link SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT}
+         *
+         * <p>
+         * If the method is called on a device without hardware support then starting the soft AP
+         * using {@link WifiManager#startTetheredHotspot(SoftApConfiguration)} will fail with
+         * {@link WifiManager#SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}.
+         *
+         * <p>
+         * <li>If not set, defaults to false (i.e The authoriztion is not required).</li>
+         *
+         * @param enabled true for enabling the control by user, false otherwise.
+         * @return Builder for chaining.
+         */
+        @NonNull
+        public Builder enableClientControlByUser(boolean enabled) {
+            mClientControlByUser = enabled;
+            return this;
+        }
+
+
+        /**
+         * This method together with {@link enableClientControlByUser(boolean)} control client
+         * connections to the AP. If {@link enableClientControlByUser(false)} is configured than
+         * this API has no effect and clients are allowed to associate to the AP (within limit of
+         * max number of clients).
+         *
+         * If {@link enableClientControlByUser(true)} is configured then this API configures
+         * 2 lists:
+         * <ul>
+         * <li>List of clients which are blocked. These are rejected.</li>
+         * <li>List of clients which are explicitly allowed. These are auto-accepted.</li>
+         * </ul>
+         *
+         * <p>
+         * All other clients which attempt to associate, whose MAC addresses are on neither list,
+         * are:
+         * <ul>
+         * <li>Rejected</li>
+         * <li>A callback {@link WifiManager.SoftApCallback#onBlockedClientConnecting(WifiClient)}
+         * is issued (which allows the user to add them to the allowed client list if desired).<li>
+         * </ul>
+         *
+         * @param blockedClientList list of clients which are not allowed to associate to the AP.
+         * @param allowedClientList list of clients which are allowed to associate to the AP
+         *                          without user pre-approval.
+         * @return Builder for chaining.
+         */
+        @NonNull
+        public Builder setClientList(@NonNull List<MacAddress> blockedClientList,
+                @NonNull List<MacAddress> allowedClientList) {
+            mBlockedClientList = new ArrayList<>(blockedClientList);
+            mAllowedClientList = new ArrayList<>(allowedClientList);
+            Iterator<MacAddress> iterator = mAllowedClientList.iterator();
+            while (iterator.hasNext()) {
+                MacAddress client = iterator.next();
+                int index = mBlockedClientList.indexOf(client);
+                if (index != -1) {
+                    throw new IllegalArgumentException("A MacAddress exist in both list");
+                }
+            }
+            return this;
+        }
     }
 }
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index a379c75..114e0fa 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -29,6 +29,7 @@
 import android.net.ProxyInfo;
 import android.net.StaticIpConfiguration;
 import android.net.Uri;
+import android.net.util.MacAddressUtils;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -39,6 +40,8 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
@@ -253,9 +256,12 @@
         /** LEAP/Network EAP (only used with LEAP) */
         public static final int LEAP = 2;
 
+        /** SAE (Used only for WPA3-Personal) */
+        public static final int SAE = 3;
+
         public static final String varName = "auth_alg";
 
-        public static final String[] strings = { "OPEN", "SHARED", "LEAP" };
+        public static final String[] strings = { "OPEN", "SHARED", "LEAP", "SAE" };
     }
 
     /**
@@ -367,7 +373,6 @@
      * ECDHE_ECDSA
      * ECDHE_RSA
      * </pre>
-     * @hide
      */
     public static class SuiteBCipher {
         private SuiteBCipher() { }
@@ -397,29 +402,29 @@
         public static final String[] strings = { "current", "disabled", "enabled" };
     }
 
-    /**
-     * Security types we support.
-     */
-    /** @hide */
+    /** Security type for an open network. */
     public static final int SECURITY_TYPE_OPEN = 0;
-    /** @hide */
+    /** Security type for a WEP network. */
     public static final int SECURITY_TYPE_WEP = 1;
-    /** @hide */
+    /** Security type for a PSK network. */
     public static final int SECURITY_TYPE_PSK = 2;
-    /** @hide */
+    /** Security type for an EAP network. */
     public static final int SECURITY_TYPE_EAP = 3;
-    /** @hide */
+    /** Security type for an SAE network. */
     public static final int SECURITY_TYPE_SAE = 4;
-    /** @hide */
+    /** Security type for an EAP Suite B network. */
     public static final int SECURITY_TYPE_EAP_SUITE_B = 5;
-    /** @hide */
+    /** Security type for an OWE network. */
     public static final int SECURITY_TYPE_OWE = 6;
-    /** @hide */
+    /** Security type for a WAPI PSK network. */
     public static final int SECURITY_TYPE_WAPI_PSK = 7;
-    /** @hide */
+    /** Security type for a WAPI Certificate network. */
     public static final int SECURITY_TYPE_WAPI_CERT = 8;
 
-    /** @hide */
+    /**
+     * Security types we support.
+     * @hide
+     */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = { "SECURITY_TYPE_" }, value = {
             SECURITY_TYPE_OPEN,
@@ -435,10 +440,19 @@
     public @interface SecurityType {}
 
     /**
-     * @hide
-     * Set security params (sets the various bitsets exposed in WifiConfiguration).
+     * Set the various security params to correspond to the provided security type.
+     * This is accomplished by setting the various BitSets exposed in WifiConfiguration.
      *
-     * @param securityType One of the security types from {@link SecurityType}.
+     * @param securityType One of the following security types:
+     * {@link #SECURITY_TYPE_OPEN},
+     * {@link #SECURITY_TYPE_WEP},
+     * {@link #SECURITY_TYPE_PSK},
+     * {@link #SECURITY_TYPE_EAP},
+     * {@link #SECURITY_TYPE_SAE},
+     * {@link #SECURITY_TYPE_EAP_SUITE_B},
+     * {@link #SECURITY_TYPE_OWE},
+     * {@link #SECURITY_TYPE_WAPI_PSK}, or
+     * {@link #SECURITY_TYPE_WAPI_CERT}
      */
     public void setSecurityParams(@SecurityType int securityType) {
         // Clear all the bitsets.
@@ -468,10 +482,13 @@
                 break;
             case SECURITY_TYPE_SAE:
                 allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SAE);
+                allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
+                allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
                 requirePMF = true;
                 break;
             case SECURITY_TYPE_EAP_SUITE_B:
                 allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SUITE_B_192);
+                allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.GCMP_256);
                 allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GCMP_256);
                 allowedGroupManagementCiphers.set(WifiConfiguration.GroupMgmtCipher.BIP_GMAC_256);
                 // Note: allowedSuiteBCiphers bitset will be set by the service once the
@@ -480,6 +497,8 @@
                 break;
             case SECURITY_TYPE_OWE:
                 allowedKeyManagement.set(WifiConfiguration.KeyMgmt.OWE);
+                allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
+                allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
                 requirePMF = true;
                 break;
             case SECURITY_TYPE_WAPI_PSK:
@@ -846,18 +865,6 @@
 
     /**
      * @hide
-     * For debug: date at which the config was last updated
-     */
-    public String updateTime;
-
-    /**
-     * @hide
-     * For debug: date at which the config was last updated
-     */
-    public String creationTime;
-
-    /**
-     * @hide
      * The WiFi configuration is considered to have no internet access for purpose of autojoining
      * if there has been a report of it having no internet access, and, it never have had
      * internet access in the past.
@@ -940,8 +947,10 @@
     }
 
     /**
-     * Indicate whther the network is trusted or not. Networks are considered trusted
+     * Indicate whether the network is trusted or not. Networks are considered trusted
      * if the user explicitly allowed this network connection.
+     * This bit can be used by suggestion network, see
+     * {@link WifiNetworkSuggestion.Builder#setUnTrusted(boolean)}
      * @hide
      */
     public boolean trusted;
@@ -1156,7 +1165,7 @@
      * @return true if mac is good to use
      */
     public static boolean isValidMacAddressForRandomization(MacAddress mac) {
-        return mac != null && !mac.isMulticastAddress() && mac.isLocallyAssigned()
+        return mac != null && !MacAddressUtils.isMulticastAddress(mac) && mac.isLocallyAssigned()
                 && !MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS).equals(mac);
     }
 
@@ -1200,22 +1209,27 @@
      */
     @SystemApi
     public static class NetworkSelectionStatus {
-        // Quality Network Selection Status enable, temporary disabled, permanently disabled
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(prefix = "NETWORK_SELECTION_",
+                value = {
+                NETWORK_SELECTION_ENABLED,
+                NETWORK_SELECTION_TEMPORARY_DISABLED,
+                NETWORK_SELECTION_PERMANENTLY_DISABLED})
+        public @interface NetworkEnabledStatus {}
         /**
-         * This network is allowed to join Quality Network Selection
-         * @hide
+         * This network will be considered as a potential candidate to connect to during network
+         * selection.
          */
         public static final int NETWORK_SELECTION_ENABLED = 0;
         /**
-         * network was temporary disabled. Can be re-enabled after a time period expire
-         * @hide
+         * This network was temporary disabled. May be re-enabled after a time out.
          */
-        public static final int NETWORK_SELECTION_TEMPORARY_DISABLED  = 1;
+        public static final int NETWORK_SELECTION_TEMPORARY_DISABLED = 1;
         /**
-         * network was permanently disabled.
-         * @hide
+         * This network was permanently disabled.
          */
-        public static final int NETWORK_SELECTION_PERMANENTLY_DISABLED  = 2;
+        public static final int NETWORK_SELECTION_PERMANENTLY_DISABLED = 2;
         /**
          * Maximum Network selection status
          * @hide
@@ -1447,6 +1461,7 @@
          * Network selection status, should be in one of three status: enable, temporaily disabled
          * or permanently disabled
          */
+        @NetworkEnabledStatus
         private int mStatus;
 
         /**
@@ -1477,12 +1492,6 @@
         private String mConnectChoice;
 
         /**
-         * The system timestamp when we records the connectChoice. This value is obtained from
-         * System.currentTimeMillis
-         */
-        private long mConnectChoiceTimestamp = INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP;
-
-        /**
          * Used to cache the temporary candidate during the network selection procedure. It will be
          * kept updating once a new scan result has a higher score than current one
          */
@@ -1585,25 +1594,6 @@
             mConnectChoice = newConnectChoice;
         }
 
-        /**
-         * get the timeStamp when user select a choice over this configuration
-         * @return returns when current connectChoice is set (time from System.currentTimeMillis)
-         * @hide
-         */
-        public long getConnectChoiceTimestamp() {
-            return mConnectChoiceTimestamp;
-        }
-
-        /**
-         * set the timeStamp when user select a choice over this configuration
-         * @param timeStamp, the timestamp set to connectChoiceTimestamp, expected timestamp should
-         *        be obtained from System.currentTimeMillis
-         * @hide
-         */
-        public void setConnectChoiceTimestamp(long timeStamp) {
-            mConnectChoiceTimestamp = timeStamp;
-        }
-
         /** Get the current Quality network selection status as a String (for debugging). */
         @NonNull
         public String getNetworkStatusString() {
@@ -1627,6 +1617,58 @@
         }
 
         /**
+         * NetworkSelectionStatus exports an immutable public API.
+         * However, test code has a need to construct a NetworkSelectionStatus in a specific state.
+         * (Note that mocking using Mockito does not work if the object needs to be parceled and
+         * unparceled.)
+         * Export a @SystemApi Builder to allow tests to construct a NetworkSelectionStatus object
+         * in the desired state, without sacrificing NetworkSelectionStatus's immutability.
+         */
+        @VisibleForTesting
+        public static final class Builder {
+            private final NetworkSelectionStatus mNetworkSelectionStatus =
+                    new NetworkSelectionStatus();
+
+            /**
+             * Set the current network selection status.
+             * One of:
+             * {@link #NETWORK_SELECTION_ENABLED},
+             * {@link #NETWORK_SELECTION_TEMPORARY_DISABLED},
+             * {@link #NETWORK_SELECTION_PERMANENTLY_DISABLED}
+             * @see NetworkSelectionStatus#getNetworkSelectionStatus()
+             */
+            @NonNull
+            public Builder setNetworkSelectionStatus(@NetworkEnabledStatus int status) {
+                mNetworkSelectionStatus.setNetworkSelectionStatus(status);
+                return this;
+            }
+
+            /**
+             *
+             * Set the current network's disable reason.
+             * One of the {@link #NETWORK_SELECTION_ENABLE} or DISABLED_* constants.
+             * e.g. {@link #DISABLED_ASSOCIATION_REJECTION}.
+             * @see NetworkSelectionStatus#getNetworkSelectionDisableReason()
+             */
+            @NonNull
+            public Builder setNetworkSelectionDisableReason(
+                    @NetworkSelectionDisableReason int reason) {
+                mNetworkSelectionStatus.setNetworkSelectionDisableReason(reason);
+                return this;
+            }
+
+            /**
+             * Build a NetworkSelectionStatus object.
+             */
+            @NonNull
+            public NetworkSelectionStatus build() {
+                NetworkSelectionStatus status = new NetworkSelectionStatus();
+                status.copy(mNetworkSelectionStatus);
+                return status;
+            }
+        }
+
+        /**
          * Get the network disable reason string for a reason code (for debugging).
          * @param reason specific error reason. One of the {@link #NETWORK_SELECTION_ENABLE} or
          *               DISABLED_* constants e.g. {@link #DISABLED_ASSOCIATION_REJECTION}.
@@ -1652,10 +1694,13 @@
         }
 
         /**
-         * get current network network selection status
-         * @return return current network network selection status
-         * @hide
+         * Get the current network network selection status.
+         * One of:
+         * {@link #NETWORK_SELECTION_ENABLED},
+         * {@link #NETWORK_SELECTION_TEMPORARY_DISABLED},
+         * {@link #NETWORK_SELECTION_PERMANENTLY_DISABLED}
          */
+        @NetworkEnabledStatus
         public int getNetworkSelectionStatus() {
             return mStatus;
         }
@@ -1833,7 +1878,6 @@
             setCandidate(source.getCandidate());
             setCandidateScore(source.getCandidateScore());
             setConnectChoice(source.getConnectChoice());
-            setConnectChoiceTimestamp(source.getConnectChoiceTimestamp());
             setHasEverConnected(source.getHasEverConnected());
         }
 
@@ -1850,7 +1894,6 @@
             if (getConnectChoice() != null) {
                 dest.writeInt(CONNECT_CHOICE_EXISTS);
                 dest.writeString(getConnectChoice());
-                dest.writeLong(getConnectChoiceTimestamp());
             } else {
                 dest.writeInt(CONNECT_CHOICE_NOT_EXISTS);
             }
@@ -1869,10 +1912,8 @@
             setNetworkSelectionBSSID(in.readString());
             if (in.readInt() == CONNECT_CHOICE_EXISTS) {
                 setConnectChoice(in.readString());
-                setConnectChoiceTimestamp(in.readLong());
             } else {
                 setConnectChoice(null);
-                setConnectChoiceTimestamp(INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
             }
             setHasEverConnected(in.readInt() != 0);
         }
@@ -1957,10 +1998,11 @@
     }
 
     /**
-     * Set the network selection status
+     * Set the network selection status.
      * @hide
      */
-    public void setNetworkSelectionStatus(NetworkSelectionStatus status) {
+    @SystemApi
+    public void setNetworkSelectionStatus(@NonNull NetworkSelectionStatus status) {
         mNetworkSelectionStatus = status;
     }
 
@@ -2095,9 +2137,6 @@
         }
         if (mNetworkSelectionStatus.getConnectChoice() != null) {
             sbuf.append(" connect choice: ").append(mNetworkSelectionStatus.getConnectChoice());
-            sbuf.append(" connect choice set time: ")
-                    .append(logTimeOfDay(
-                            mNetworkSelectionStatus.getConnectChoiceTimestamp()));
         }
         sbuf.append(" hasEverConnected: ")
                 .append(mNetworkSelectionStatus.getHasEverConnected()).append("\n");
@@ -2109,12 +2148,6 @@
             sbuf.append(" numNoInternetAccessReports ");
             sbuf.append(this.numNoInternetAccessReports).append("\n");
         }
-        if (this.updateTime != null) {
-            sbuf.append(" update ").append(this.updateTime).append("\n");
-        }
-        if (this.creationTime != null) {
-            sbuf.append(" creation ").append(this.creationTime).append("\n");
-        }
         if (this.validatedInternetAccess) sbuf.append(" validatedInternetAccess");
         if (this.ephemeral) sbuf.append(" ephemeral");
         if (this.osu) sbuf.append(" osu");
@@ -2661,8 +2694,6 @@
             allowAutojoin = source.allowAutojoin;
             numNoInternetAccessReports = source.numNoInternetAccessReports;
             noInternetAccessExpected = source.noInternetAccessExpected;
-            creationTime = source.creationTime;
-            updateTime = source.updateTime;
             shared = source.shared;
             recentFailure.setAssociationStatus(source.recentFailure.getAssociationStatus());
             mRandomizedMacAddress = source.mRandomizedMacAddress;
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index 41f7c6e..04016b6 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -856,11 +856,11 @@
      * like /etc/ssl/certs. If configured, these certificates are added to the
      * list of trusted CAs. ca_cert may also be included in that case, but it is
      * not required.
-     * @param path The path for CA certificate files, or null/empty string to clear.
+     * @param path The path for CA certificate files, or empty string to clear.
      * @hide
      */
     @SystemApi
-    public void setCaPath(@Nullable String path) {
+    public void setCaPath(@NonNull String path) {
         setFieldValue(CA_PATH_KEY, path);
     }
 
@@ -881,11 +881,11 @@
      * <p> See the {@link android.security.KeyChain} for details on installing or choosing
      * a certificate
      * </p>
-     * @param alias identifies the certificate, or null/empty string to clear.
+     * @param alias identifies the certificate, or empty string to clear.
      * @hide
      */
     @SystemApi
-    public void setClientCertificateAlias(@Nullable String alias) {
+    public void setClientCertificateAlias(@NonNull String alias) {
         setFieldValue(CLIENT_CERT_KEY, alias, CLIENT_CERT_PREFIX);
         setFieldValue(PRIVATE_KEY_ID_KEY, alias, USER_PRIVATE_KEY);
         // Also, set engine parameters
@@ -1028,8 +1028,10 @@
     }
 
     /**
-     * @hide
+     * Get the client private key as supplied in {@link #setClientKeyEntryWithCertificateChain}, or
+     * null if unset.
      */
+    @Nullable
     public PrivateKey getClientPrivateKey() {
         return mClientPrivateKey;
     }
@@ -1358,11 +1360,11 @@
      * If this field is not specified, WAPI-CERT uses ASU ID from WAI packet
      * as the certificate suite name automatically.
      *
-     * @param wapiCertSuite The name for WAPI certificate suite, or null/empty string to clear.
+     * @param wapiCertSuite The name for WAPI certificate suite, or empty string to clear.
      * @hide
      */
     @SystemApi
-    public void setWapiCertSuite(@Nullable String wapiCertSuite) {
+    public void setWapiCertSuite(@NonNull String wapiCertSuite) {
         setFieldValue(WAPI_CERT_SUITE_KEY, wapiCertSuite);
     }
 
@@ -1371,7 +1373,7 @@
      * @return the certificate suite name
      * @hide
      */
-    @Nullable
+    @NonNull
     @SystemApi
     public String getWapiCertSuite() {
         return getFieldValue(WAPI_CERT_SUITE_KEY);
diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java
index 62337cb..419bcb1 100644
--- a/wifi/java/android/net/wifi/WifiInfo.java
+++ b/wifi/java/android/net/wifi/WifiInfo.java
@@ -17,6 +17,7 @@
 package android.net.wifi;
 
 import android.annotation.IntRange;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -27,6 +28,8 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
@@ -121,11 +124,21 @@
     private int mTxLinkSpeed;
 
     /**
+     * Max supported Tx(transmit) link speed in Mbps
+     */
+    private int mMaxSupportedTxLinkSpeed;
+
+    /**
      * Rx(receive) Link speed in Mbps
      */
     private int mRxLinkSpeed;
 
     /**
+     * Max supported Rx(receive) link speed in Mbps
+     */
+    private int mMaxSupportedRxLinkSpeed;
+
+    /**
      * Frequency in MHz
      */
     public static final String FREQUENCY_UNITS = "MHz";
@@ -300,6 +313,8 @@
         setLinkSpeed(LINK_SPEED_UNKNOWN);
         setTxLinkSpeedMbps(LINK_SPEED_UNKNOWN);
         setRxLinkSpeedMbps(LINK_SPEED_UNKNOWN);
+        setMaxSupportedTxLinkSpeedMbps(LINK_SPEED_UNKNOWN);
+        setMaxSupportedRxLinkSpeedMbps(LINK_SPEED_UNKNOWN);
         setFrequency(-1);
         setMeteredHint(false);
         setEphemeral(false);
@@ -353,6 +368,74 @@
             mRxSuccessRate = source.mRxSuccessRate;
             score = source.score;
             mWifiStandard = source.mWifiStandard;
+            mMaxSupportedTxLinkSpeed = source.mMaxSupportedTxLinkSpeed;
+            mMaxSupportedRxLinkSpeed = source.mMaxSupportedRxLinkSpeed;
+        }
+    }
+
+    /**
+     * WifiInfo exports an immutable public API.
+     * However, test code has a need to construct a WifiInfo in a specific state.
+     * (Note that mocking using Mockito does not work if the object needs to be parceled and
+     * unparceled.)
+     * Export a @SystemApi Builder to allow tests to construct a WifiInfo object
+     * in the desired state, without sacrificing WifiInfo's immutability.
+     *
+     * @hide
+     */
+    // This builder was not made public to reduce confusion for external developers as there are
+    // no legitimate uses for this builder except for testing.
+    @SystemApi
+    @VisibleForTesting
+    public static final class Builder {
+        private final WifiInfo mWifiInfo = new WifiInfo();
+
+        /**
+         * Set the SSID, in the form of a raw byte array.
+         * @see WifiInfo#getSSID()
+         */
+        @NonNull
+        public Builder setSsid(@NonNull byte[] ssid) {
+            mWifiInfo.setSSID(WifiSsid.createFromByteArray(ssid));
+            return this;
+        }
+
+        /**
+         * Set the BSSID.
+         * @see WifiInfo#getBSSID()
+         */
+        @NonNull
+        public Builder setBssid(@NonNull String bssid) {
+            mWifiInfo.setBSSID(bssid);
+            return this;
+        }
+
+        /**
+         * Set the RSSI, in dBm.
+         * @see WifiInfo#getRssi()
+         */
+        @NonNull
+        public Builder setRssi(int rssi) {
+            mWifiInfo.setRssi(rssi);
+            return this;
+        }
+
+        /**
+         * Set the network ID.
+         * @see WifiInfo#getNetworkId()
+         */
+        @NonNull
+        public Builder setNetworkId(int networkId) {
+            mWifiInfo.setNetworkId(networkId);
+            return this;
+        }
+
+        /**
+         * Build a WifiInfo object.
+         */
+        @NonNull
+        public WifiInfo build() {
+            return new WifiInfo(mWifiInfo);
         }
     }
 
@@ -483,6 +566,15 @@
     }
 
     /**
+     * Returns the maximum supported transmit link speed in Mbps
+     * @return the max supported tx link speed or {@link #LINK_SPEED_UNKNOWN} if link speed is
+     * unknown. @see #LINK_SPEED_UNKNOWN
+     */
+    public int getMaxSupportedTxLinkSpeedMbps() {
+        return mMaxSupportedTxLinkSpeed;
+    }
+
+    /**
      * Update the last transmitted packet bit rate in Mbps.
      * @hide
      */
@@ -491,6 +583,14 @@
     }
 
     /**
+     * Set the maximum supported transmit link speed in Mbps
+     * @hide
+     */
+    public void setMaxSupportedTxLinkSpeedMbps(int maxSupportedTxLinkSpeed) {
+        mMaxSupportedTxLinkSpeed = maxSupportedTxLinkSpeed;
+    }
+
+    /**
      * Returns the current receive link speed in Mbps.
      * @return the Rx link speed or {@link #LINK_SPEED_UNKNOWN} if link speed is unknown.
      * @see #LINK_SPEED_UNKNOWN
@@ -501,6 +601,15 @@
     }
 
     /**
+     * Returns the maximum supported receive link speed in Mbps
+     * @return the max supported Rx link speed or {@link #LINK_SPEED_UNKNOWN} if link speed is
+     * unknown. @see #LINK_SPEED_UNKNOWN
+     */
+    public int getMaxSupportedRxLinkSpeedMbps() {
+        return mMaxSupportedRxLinkSpeed;
+    }
+
+    /**
      * Update the last received packet bit rate in Mbps.
      * @hide
      */
@@ -509,6 +618,14 @@
     }
 
     /**
+     * Set the maximum supported receive link speed in Mbps
+     * @hide
+     */
+    public void setMaxSupportedRxLinkSpeedMbps(int maxSupportedRxLinkSpeed) {
+        mMaxSupportedRxLinkSpeed = maxSupportedRxLinkSpeed;
+    }
+
+    /**
      * Returns the current frequency in {@link #FREQUENCY_UNITS}.
      * @return the frequency.
      * @see #FREQUENCY_UNITS
@@ -631,6 +748,11 @@
 
     /**
      * Returns the Fully Qualified Domain Name of the network if it is a Passpoint network.
+     * <p>
+     * The FQDN may be
+     * <lt>{@code null} if no network currently connected, currently connected network is not
+     * passpoint network or the caller has insufficient permissions to access the FQDN.</lt>
+     * </p>
      */
     public @Nullable String getPasspointFqdn() {
         return mFqdn;
@@ -643,6 +765,12 @@
 
     /**
      * Returns the Provider Friendly Name of the network if it is a Passpoint network.
+     * <p>
+     * The Provider Friendly Name may be
+     * <lt>{@code null} if no network currently connected, currently connected network is not
+     * passpoint network or the caller has insufficient permissions to access the Provider Friendly
+     * Name. </lt>
+     * </p>
      */
     public @Nullable String getPasspointProviderFriendlyName() {
         return mProviderFriendlyName;
@@ -784,7 +912,11 @@
                 .append(", RSSI: ").append(mRssi)
                 .append(", Link speed: ").append(mLinkSpeed).append(LINK_SPEED_UNITS)
                 .append(", Tx Link speed: ").append(mTxLinkSpeed).append(LINK_SPEED_UNITS)
+                .append(", Max Supported Tx Link speed: ")
+                .append(mMaxSupportedTxLinkSpeed).append(LINK_SPEED_UNITS)
                 .append(", Rx Link speed: ").append(mRxLinkSpeed).append(LINK_SPEED_UNITS)
+                .append(", Max Supported Rx Link speed: ")
+                .append(mMaxSupportedRxLinkSpeed).append(LINK_SPEED_UNITS)
                 .append(", Frequency: ").append(mFrequency).append(FREQUENCY_UNITS)
                 .append(", Net ID: ").append(mNetworkId)
                 .append(", Metered hint: ").append(mMeteredHint)
@@ -837,6 +969,8 @@
         dest.writeString(mFqdn);
         dest.writeString(mProviderFriendlyName);
         dest.writeInt(mWifiStandard);
+        dest.writeInt(mMaxSupportedTxLinkSpeed);
+        dest.writeInt(mMaxSupportedRxLinkSpeed);
     }
 
     /** Implement the Parcelable interface {@hide} */
@@ -879,6 +1013,8 @@
                 info.mFqdn = in.readString();
                 info.mProviderFriendlyName = in.readString();
                 info.mWifiStandard = in.readInt();
+                info.mMaxSupportedTxLinkSpeed = in.readInt();
+                info.mMaxSupportedRxLinkSpeed = in.readInt();
                 return info;
             }
 
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index c4ec44e..0e8c6ed 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -22,6 +22,7 @@
 
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -193,6 +194,21 @@
      */
     public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID = 5;
 
+    /**
+     * Reason code if one or more of the network suggestions added is not allowed.
+     *
+     * This error may be caused by suggestion is using SIM-based encryption method, but calling app
+     * is not carrier privileged.
+     */
+    public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_NOT_ALLOWED = 6;
+
+    /**
+     * Reason code if one or more of the network suggestions added is invalid.
+     *
+     * Please user {@link WifiNetworkSuggestion.Builder} to create network suggestions.
+     */
+    public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_INVALID = 7;
+
     /** @hide */
     @IntDef(prefix = { "STATUS_NETWORK_SUGGESTIONS_" }, value = {
             STATUS_NETWORK_SUGGESTIONS_SUCCESS,
@@ -201,6 +217,8 @@
             STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_DUPLICATE,
             STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_EXCEEDS_MAX_PER_APP,
             STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID,
+            STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_NOT_ALLOWED,
+            STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_INVALID,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface NetworkSuggestionsStatusCode {}
@@ -233,16 +251,20 @@
     public @interface SuggestionConnectionStatusCode {}
 
     /**
-     * Broadcast intent action indicating whether Wi-Fi scanning is allowed currently
-     * @hide
+     * Broadcast intent action indicating whether Wi-Fi scanning is currently available.
+     * Available extras:
+     * - {@link #EXTRA_SCAN_AVAILABLE}
      */
-    public static final String WIFI_SCAN_AVAILABLE = "wifi_scan_available";
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_WIFI_SCAN_AVAILABLE =
+            "android.net.wifi.action.WIFI_SCAN_AVAILABLE";
 
     /**
-     * Extra int indicating scan availability, WIFI_STATE_ENABLED and WIFI_STATE_DISABLED
-     * @hide
+     * A boolean extra indicating whether scanning is currently available.
+     * Sent in the broadcast {@link #ACTION_WIFI_SCAN_AVAILABLE}.
+     * Its value is true if scanning is currently available, false otherwise.
      */
-    public static final String EXTRA_SCAN_AVAILABLE = "scan_enabled";
+    public static final String EXTRA_SCAN_AVAILABLE = "android.net.wifi.extra.SCAN_AVAILABLE";
 
     /**
      * Broadcast intent action indicating that the credential of a Wi-Fi network
@@ -666,7 +688,8 @@
     public @interface SapStartFailure {}
 
     /**
-     *  All other reasons for AP start failure besides {@link #SAP_START_FAILURE_NO_CHANNEL}.
+     *  All other reasons for AP start failure besides {@link #SAP_START_FAILURE_NO_CHANNEL} and
+     *  {@link #SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}.
      *
      *  @hide
      */
@@ -691,6 +714,37 @@
     @SystemApi
     public static final int SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION = 2;
 
+
+    /** @hide */
+    @IntDef(flag = false, prefix = { "SAP_CLIENT_BLOCKED_REASON_" }, value = {
+        SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER,
+        SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SapClientBlockedReason {}
+
+    /**
+     *  If Soft Ap client is blocked, this reason code means that client doesn't exist in the
+     *  specified configuration {@link SoftApConfiguration.Builder#setClientList(List, List)}
+     *  and the {@link SoftApConfiguration.Builder#enableClientControlByUser(true)}
+     *  is configured as well.
+     *  @hide
+     */
+    @SystemApi
+    public static final int SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER = 0;
+
+    /**
+     *  If Soft Ap client is blocked, this reason code means that no more clients can be
+     *  associated to this AP since it reached maximum capacity. The maximum capacity is
+     *  the minimum of {@link SoftApConfiguration.Builder#setMaxNumberOfClients(int)} and
+     *  {@link SoftApCapability#getMaxSupportedClients} which get from
+     *  {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)}.
+     *
+     *  @hide
+     */
+    @SystemApi
+    public static final int SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS = 1;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"IFACE_IP_MODE_"}, value = {
@@ -1366,6 +1420,36 @@
     }
 
     /**
+     * Retrieve a list of {@link WifiConfiguration} for available {@link WifiNetworkSuggestion}
+     * matching the given list of {@link ScanResult}.
+     *
+     * An available {@link WifiNetworkSuggestion} must satisfy:
+     * <ul>
+     * <li> Matching one of the {@link ScanResult} from the given list.
+     * <li> and {@link WifiNetworkSuggestion.Builder#setIsUserAllowedToManuallyConnect(boolean)} set
+     * to true.
+     * </ul>
+     *
+     * @param scanResults a list of scanResult.
+     * @return a list of @link WifiConfiguration} for available {@link WifiNetworkSuggestion}
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_SETTINGS,
+            android.Manifest.permission.NETWORK_SETUP_WIZARD
+    })
+    @NonNull
+    public List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(
+            @NonNull List<ScanResult> scanResults) {
+        try {
+            return mService.getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(scanResults);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
      * Returns a list of unique Hotspot 2.0 OSU (Online Sign-Up) providers associated with a given
      * list of ScanResult.
      *
@@ -1882,6 +1966,15 @@
      * @param config The Passpoint configuration to be added
      * @throws IllegalArgumentException if configuration is invalid or Passpoint is not enabled on
      *                                  the device.
+     *
+     * Deprecated for general app usage - except DO/PO apps.
+     * See {@link WifiNetworkSuggestion.Builder#setPasspointConfig(PasspointConfiguration)} to
+     * create a passpoint suggestion.
+     * See {@link #addNetworkSuggestions(List)}, {@link #removeNetworkSuggestions(List)} for new
+     * API to add Wi-Fi networks for consideration when auto-connecting to wifi.
+     * <b>Compatibility Note:</b> For applications targeting
+     * {@link android.os.Build.VERSION_CODES#R} or above, except for system of DO/PO apps, this API
+     * will throw {@link IllegalArgumentException}
      */
     public void addOrUpdatePasspointConfiguration(PasspointConfiguration config) {
         try {
@@ -2261,7 +2354,7 @@
         return (getSupportedFeatures() & feature) == feature;
     }
 
-   /**
+    /**
      * @return true if this adapter supports Passpoint
      * @hide
      */
@@ -2303,6 +2396,15 @@
     }
 
     /**
+     * Query whether the device supports Station (STA) + Access point (AP) concurrency or not.
+     *
+     * @return true if this device supports STA + AP concurrency, false otherwise.
+     */
+    public boolean isStaApConcurrencySupported() {
+        return isFeatureSupported(WIFI_FEATURE_AP_STA);
+    }
+
+    /**
      * @deprecated Please use {@link android.content.pm.PackageManager#hasSystemFeature(String)}
      * with {@link android.content.pm.PackageManager#FEATURE_WIFI_RTT} and
      * {@link android.content.pm.PackageManager#FEATURE_WIFI_AWARE}.
@@ -2407,6 +2509,20 @@
     }
 
     /**
+     * Check if the chipset supports a certain Wi-Fi standard.
+     * @param standard the IEEE 802.11 standard to check on.
+     *        valid values from {@link ScanResult}'s {@code WIFI_STANDARD_}
+     * @return {@code true} if supported, {@code false} otherwise.
+     */
+    public boolean isWifiStandardSupported(@ScanResult.WifiStandard int standard) {
+        try {
+            return mService.isWifiStandardSupported(standard);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Interface for Wi-Fi activity energy info listener. Should be implemented by applications and
      * set when calling {@link WifiManager#getWifiActivityEnergyInfoAsync}.
      *
@@ -2540,6 +2656,8 @@
      * the same permissions as {@link #getScanResults}. If such access is not allowed,
      * {@link WifiInfo#getSSID} will return {@link #UNKNOWN_SSID} and
      * {@link WifiInfo#getBSSID} will return {@code "02:00:00:00:00:00"}.
+     * {@link WifiInfo#getPasspointFqdn()} will return null.
+     * {@link WifiInfo#getPasspointProviderFriendlyName()} will return null.
      *
      * @return the Wi-Fi information, contained in {@link WifiInfo}.
      */
@@ -2568,6 +2686,34 @@
     }
 
     /**
+     * Return the filtered ScanResults which may be authenticated by the suggested network
+     * configurations.
+     * @param networkSuggestions The list of {@link WifiNetworkSuggestion}
+     * @param scanResults The scan results to be filtered, this is optional, if it is null or
+     * empty, wifi system would use the recent scan results in the system.
+     * @return The map of {@link WifiNetworkSuggestion} and the list of {@link ScanResult} which
+     * may be authenticated by the corresponding network configuration.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(allOf = {ACCESS_FINE_LOCATION, ACCESS_WIFI_STATE})
+    @NonNull
+    public Map<WifiNetworkSuggestion, List<ScanResult>> getMatchingScanResults(
+            @NonNull List<WifiNetworkSuggestion> networkSuggestions,
+            @Nullable List<ScanResult> scanResults) {
+        if (networkSuggestions == null) {
+            throw new IllegalArgumentException("networkSuggestions must not be null.");
+        }
+        try {
+            return mService.getMatchingScanResults(
+                    networkSuggestions, scanResults,
+                    mContext.getOpPackageName(), mContext.getFeatureId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Check if scanning is always available.
      *
      * If this return {@code true}, apps can issue {@link #startScan} and fetch scan results
@@ -2760,6 +2906,7 @@
      * [0, {@link #getMaxSignalLevel()}], where 0 is the lowest (worst signal) RSSI
      * rating and {@link #getMaxSignalLevel()} is the highest (best signal) RSSI rating.
      */
+    @IntRange(from = 0)
     public int calculateSignalLevel(int rssi) {
         try {
             return mService.calculateSignalLevel(rssi);
@@ -2772,6 +2919,7 @@
      * Get the system default maximum signal level.
      * This is the maximum RSSI level returned by {@link #calculateSignalLevel(int)}.
      */
+    @IntRange(from = 0)
     public int getMaxSignalLevel() {
         return calculateSignalLevel(Integer.MAX_VALUE);
     }
@@ -2894,7 +3042,7 @@
      * Each application can make a single active call to this method. The {@link
      * LocalOnlyHotspotCallback#onStarted(LocalOnlyHotspotReservation)} callback supplies the
      * requestor with a {@link LocalOnlyHotspotReservation} that contains a
-     * {@link WifiConfiguration} with the SSID, security type and credentials needed to connect
+     * {@link SoftApConfiguration} with the SSID, security type and credentials needed to connect
      * to the hotspot.  Communicating this information is up to the application.
      * <p>
      * If the LocalOnlyHotspot cannot be created, the {@link LocalOnlyHotspotCallback#onFailed(int)}
@@ -3059,7 +3207,7 @@
      * Allow callers (Settings UI) to watch LocalOnlyHotspot state changes.  Callers will
      * receive a {@link LocalOnlyHotspotSubscription} object as a parameter of the
      * {@link LocalOnlyHotspotObserver#onRegistered(LocalOnlyHotspotSubscription)}. The registered
-     * callers will receive the {@link LocalOnlyHotspotObserver#onStarted(WifiConfiguration)} and
+     * callers will receive the {@link LocalOnlyHotspotObserver#onStarted(SoftApConfiguration)} and
      * {@link LocalOnlyHotspotObserver#onStopped()} callbacks.
      * <p>
      * Applications should have the
@@ -3146,9 +3294,13 @@
      * Gets the Wi-Fi AP Configuration.
      * @return AP details in WifiConfiguration
      *
+     * Note that AP detail may contain configuration which is cannot be represented
+     * by the legacy WifiConfiguration, in such cases a null will be returned.
+     *
      * @deprecated This API is deprecated. Use {@link #getSoftApConfiguration()} instead.
      * @hide
      */
+    @Nullable
     @SystemApi
     @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE)
     @Deprecated
@@ -3199,8 +3351,17 @@
     /**
      * Sets the Wi-Fi AP Configuration.
      *
+     * If the API is called while the soft AP is enabled, the configuration will apply to
+     * the current soft AP if the new configuration only includes
+     * {@link SoftApConfiguration.Builder#setMaxNumberOfClients(int)}
+     * or {@link SoftApConfiguration.Builder#setShutdownTimeoutMillis(int)}
+     * or {@link SoftApConfiguration.Builder#enableClientControlByUser(boolean)}
+     * or {@link SoftApConfiguration.Builder#setClientList(List, List)}.
+     *
+     * Otherwise, the configuration changes will be applied when the Soft AP is next started
+     * (the framework will not stop/start the AP).
+     *
      * @param softApConfig  A valid SoftApConfiguration specifying the configuration of the SAP.
-
      * @return {@code true} if the operation succeeded, {@code false} otherwise
      *
      * @hide
@@ -3430,7 +3591,8 @@
          *                      {@link #WIFI_AP_STATE_ENABLING}, {@link #WIFI_AP_STATE_FAILED}
          * @param failureReason reason when in failed state. One of
          *                      {@link #SAP_START_FAILURE_GENERAL},
-         *                      {@link #SAP_START_FAILURE_NO_CHANNEL}
+         *                      {@link #SAP_START_FAILURE_NO_CHANNEL},
+         *                      {@link #SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}
          */
         default void onStateChanged(@WifiApState int state, @SapStartFailure int failureReason) {}
 
@@ -3459,6 +3621,22 @@
             // Do nothing: can be updated to add SoftApCapability details (e.g. meximum supported
             // client number) to the UI.
         }
+
+        /**
+         * Called when client trying to connect but device blocked the client with specific reason.
+         *
+         * Can be used to ask user to update client to allowed list or blocked list
+         * when reason is {@link SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER}, or
+         * indicate the block due to maximum supported client number limitation when reason is
+         * {@link SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS}.
+         *
+         * @param client the currently blocked client.
+         * @param blockedReason one of blocked reason from {@link SapClientBlockedReason}
+         */
+        default void onBlockedClientConnecting(@NonNull WifiClient client,
+                @SapClientBlockedReason int blockedReason) {
+            // Do nothing: can be used to ask user to update client to allowed list or blocked list.
+        }
     }
 
     /**
@@ -3525,6 +3703,19 @@
                 mCallback.onCapabilityChanged(capability);
             });
         }
+
+        @Override
+        public void onBlockedClientConnecting(@NonNull WifiClient client, int blockedReason) {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "SoftApCallbackProxy: onBlockedClientConnecting: client=" + client
+                        + " with reason = " + blockedReason);
+            }
+
+            Binder.clearCallingIdentity();
+            mExecutor.execute(() -> {
+                mCallback.onBlockedClientConnecting(client, blockedReason);
+            });
+        }
     }
 
     /**
@@ -3595,13 +3786,13 @@
     }
 
     /**
-     * LocalOnlyHotspotReservation that contains the {@link WifiConfiguration} for the active
+     * LocalOnlyHotspotReservation that contains the {@link SoftApConfiguration} for the active
      * LocalOnlyHotspot request.
      * <p>
      * Applications requesting LocalOnlyHotspot for sharing will receive an instance of the
      * LocalOnlyHotspotReservation in the
      * {@link LocalOnlyHotspotCallback#onStarted(LocalOnlyHotspotReservation)} call.  This
-     * reservation contains the relevant {@link WifiConfiguration}.
+     * reservation contains the relevant {@link SoftApConfiguration}.
      * When an application is done with the LocalOnlyHotspot, they should call {@link
      * LocalOnlyHotspotReservation#close()}.  Once this happens, the application will not receive
      * any further callbacks. If the LocalOnlyHotspot is stopped due to a
@@ -3611,18 +3802,38 @@
     public class LocalOnlyHotspotReservation implements AutoCloseable {
 
         private final CloseGuard mCloseGuard = new CloseGuard();
-        private final WifiConfiguration mConfig;
+        private final SoftApConfiguration mSoftApConfig;
+        private final WifiConfiguration mWifiConfig;
         private boolean mClosed = false;
 
         /** @hide */
         @VisibleForTesting
-        public LocalOnlyHotspotReservation(WifiConfiguration config) {
-            mConfig = config;
+        public LocalOnlyHotspotReservation(SoftApConfiguration config) {
+            mSoftApConfig = config;
+            mWifiConfig = config.toWifiConfiguration();
             mCloseGuard.open("close");
         }
 
+        /**
+         * Returns the {@link WifiConfiguration} of the current Local Only Hotspot (LOHS).
+         * May be null if hotspot enabled and security type is not
+         * {@code WifiConfiguration.KeyMgmt.None} or {@code WifiConfiguration.KeyMgmt.WPA2_PSK}.
+         *
+         * @deprecated Use {@code WifiManager#getSoftApConfiguration()} to get the
+         * LOHS configuration.
+         */
+        @Deprecated
+        @Nullable
         public WifiConfiguration getWifiConfiguration() {
-            return mConfig;
+            return mWifiConfig;
+        }
+
+        /**
+         * Returns the {@link SoftApConfiguration} of the current Local Only Hotspot (LOHS).
+         */
+        @NonNull
+        public SoftApConfiguration getSoftApConfiguration() {
+            return mSoftApConfig;
         }
 
         @Override
@@ -3719,7 +3930,7 @@
         }
 
         @Override
-        public void onHotspotStarted(WifiConfiguration config) {
+        public void onHotspotStarted(SoftApConfiguration config) {
             WifiManager manager = mWifiManager.get();
             if (manager == null) return;
 
@@ -3811,7 +4022,7 @@
         /**
          * LocalOnlyHotspot started with the supplied config.
          */
-        public void onStarted(WifiConfiguration config) {};
+        public void onStarted(SoftApConfiguration config) {};
 
         /**
          * LocalOnlyHotspot stopped.
@@ -3851,7 +4062,7 @@
         }
 
         @Override
-        public void onHotspotStarted(WifiConfiguration config) {
+        public void onHotspotStarted(SoftApConfiguration config) {
             WifiManager manager = mWifiManager.get();
             if (manager == null) return;
 
@@ -4091,6 +4302,23 @@
     }
 
     /**
+     * Allows the OEM to enable/disable auto-join globally.
+     *
+     * @param choice true to allow autojoin, false to disallow autojoin
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+    public void allowAutojoinGlobal(boolean choice) {
+        try {
+            mService.allowAutojoinGlobal(choice);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
+    /**
      * Sets the user choice for allowing auto-join to a network.
      * The updated choice will be made available through the updated config supplied by the
      * CONFIGURED_NETWORKS_CHANGED broadcast.
@@ -4110,6 +4338,41 @@
     }
 
     /**
+     * Configure auto-join settings for a Passpoint profile.
+     *
+     * @param fqdn the FQDN (fully qualified domain name) of the passpoint profile.
+     * @param enableAutoJoin true to enable autojoin, false to disable autojoin.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+    public void allowAutojoinPasspoint(@NonNull String fqdn, boolean enableAutoJoin) {
+        try {
+            mService.allowAutojoinPasspoint(fqdn, enableAutoJoin);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Configure MAC randomization setting for a Passpoint profile.
+     * MAC randomization is enabled by default.
+     *
+     * @param fqdn the FQDN (fully qualified domain name) of the passpoint profile.
+     * @param enable true to enable MAC randomization, false to disable MAC randomization.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+    public void setMacRandomizationSettingPasspointEnabled(@NonNull String fqdn, boolean enable) {
+        try {
+            mService.setMacRandomizationSettingPasspointEnabled(fqdn, enable);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Disable an ephemeral network.
      *
      * @param ssid in the format of WifiConfiguration's SSID.
@@ -4682,18 +4945,6 @@
     }
 
     /**
-     * Enable/disable WifiConnectivityManager
-     * @hide
-     */
-    public void enableWifiConnectivityManager(boolean enabled) {
-        try {
-            mService.enableWifiConnectivityManager(enabled);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Returns a byte stream representing the data that needs to be backed up to save the
      * current Wifi state.
      * This Wifi state can be restored by calling {@link #restoreBackupData(byte[])}.
@@ -5655,4 +5906,186 @@
             return new SparseArray<>();
         }
     }
+
+    /**
+     * Callback interface for framework to receive network status changes and trigger of updating
+     * {@link WifiUsabilityStatsEntry}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public interface ScoreChangeCallback {
+        /**
+         * Called by applications to indicate network status.
+         *
+         * @param sessionId The ID to indicate current Wi-Fi network connection obtained from
+         *                  {@link WifiConnectedNetworkScorer#start(int)}.
+         * @param isUsable The bit to indicate whether current Wi-Fi network is usable or not.
+         *                 Populated by connected network scorer in applications.
+         */
+        void onStatusChange(int sessionId, boolean isUsable);
+
+        /**
+         * Called by applications to trigger an update of {@link WifiUsabilityStatsEntry}.
+         * To receive update applications need to add WifiUsabilityStatsEntry listener. See
+         * {@link addOnWifiUsabilityStatsListener(Executor, OnWifiUsabilityStatsListener)}.
+         *
+         * @param sessionId The ID to indicate current Wi-Fi network connection obtained from
+         *                  {@link WifiConnectedNetworkScorer#start(int)}.
+         */
+        void onTriggerUpdateOfWifiUsabilityStats(int sessionId);
+    }
+
+    /**
+     * Callback proxy for {@link ScoreChangeCallback} objects.
+     *
+     * @hide
+     */
+    private class ScoreChangeCallbackProxy implements ScoreChangeCallback {
+        private final IScoreChangeCallback mScoreChangeCallback;
+
+        private ScoreChangeCallbackProxy(IScoreChangeCallback callback) {
+            mScoreChangeCallback = callback;
+        }
+
+        @Override
+        public void onStatusChange(int sessionId, boolean isUsable) {
+            try {
+                mScoreChangeCallback.onStatusChange(sessionId, isUsable);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        @Override
+        public void onTriggerUpdateOfWifiUsabilityStats(int sessionId) {
+            try {
+                mScoreChangeCallback.onTriggerUpdateOfWifiUsabilityStats(sessionId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Interface for Wi-Fi connected network scorer. Should be implemented by applications and set
+     * when calling
+     * {@link WifiManager#setWifiConnectedNetworkScorer(Executor, WifiConnectedNetworkScorer)}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public interface WifiConnectedNetworkScorer {
+        /**
+         * Called by framework to indicate the start of a network connection.
+         * @param sessionId The ID to indicate current Wi-Fi network connection.
+         */
+        void start(int sessionId);
+
+        /**
+         * Called by framework to indicate the end of a network connection.
+         * @param sessionId The ID to indicate current Wi-Fi network connection obtained from
+         *                  {@link WifiConnectedNetworkScorer#start(int)}.
+         */
+        void stop(int sessionId);
+
+        /**
+         * Framework sets callback for score change events after application sets its scorer.
+         * @param cbImpl The instance for {@link WifiManager#ScoreChangeCallback}. Should be
+         * implemented and instantiated by framework.
+         */
+        void setScoreChangeCallback(@NonNull ScoreChangeCallback cbImpl);
+    }
+
+    /**
+     * Callback proxy for {@link WifiConnectedNetworkScorer} objects.
+     *
+     * @hide
+     */
+    private class WifiConnectedNetworkScorerProxy extends IWifiConnectedNetworkScorer.Stub {
+        private Executor mExecutor;
+        private WifiConnectedNetworkScorer mScorer;
+
+        WifiConnectedNetworkScorerProxy(Executor executor, WifiConnectedNetworkScorer scorer) {
+            mExecutor = executor;
+            mScorer = scorer;
+        }
+
+        @Override
+        public void start(int sessionId) {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "WifiConnectedNetworkScorer: " + "start: sessionId=" + sessionId);
+            }
+            Binder.clearCallingIdentity();
+            mExecutor.execute(() -> mScorer.start(sessionId));
+        }
+
+        @Override
+        public void stop(int sessionId) {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "WifiConnectedNetworkScorer: " + "stop: sessionId=" + sessionId);
+            }
+            Binder.clearCallingIdentity();
+            mExecutor.execute(() -> mScorer.stop(sessionId));
+        }
+
+        @Override
+        public void setScoreChangeCallback(IScoreChangeCallback cbImpl) {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "WifiConnectedNetworkScorer: "
+                        + "setScoreChangeCallback: cbImpl=" + cbImpl);
+            }
+            Binder.clearCallingIdentity();
+            mExecutor.execute(() -> mScorer.setScoreChangeCallback(
+                    new ScoreChangeCallbackProxy(cbImpl)));
+        }
+    }
+
+    /**
+     * Set a callback for Wi-Fi connected network scorer.  See {@link WifiConnectedNetworkScorer}.
+     * Only a single scorer can be set. Caller will be invoked periodically by framework to inform
+     * client about start and stop of Wi-Fi connection. Caller can clear a previously set scorer
+     * using {@link clearWifiConnectedNetworkScorer()}.
+     *
+     * @param executor The executor on which callback will be invoked.
+     * @param scorer Scorer for Wi-Fi network implemented by application.
+     * @return true Scorer is set successfully.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE)
+    public boolean setWifiConnectedNetworkScorer(@NonNull @CallbackExecutor Executor executor,
+            @NonNull WifiConnectedNetworkScorer scorer) {
+        if (executor == null) throw new IllegalArgumentException("executor cannot be null");
+        if (scorer == null) throw new IllegalArgumentException("scorer cannot be null");
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "setWifiConnectedNetworkScorer: scorer=" + scorer);
+        }
+        try {
+            return mService.setWifiConnectedNetworkScorer(new Binder(),
+                    new WifiConnectedNetworkScorerProxy(executor, scorer));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Allow caller to clear a previously set scorer. After calling this method,
+     * client will no longer receive information about start and stop of Wi-Fi connection.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE)
+    public void clearWifiConnectedNetworkScorer() {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "clearWifiConnectedNetworkScorer");
+        }
+        try {
+            mService.clearWifiConnectedNetworkScorer();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/wifi/java/android/net/wifi/WifiNetworkSpecifier.java b/wifi/java/android/net/wifi/WifiNetworkSpecifier.java
index 07afd7f..444e1ef 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSpecifier.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSpecifier.java
@@ -20,7 +20,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.ActivityThread;
+import android.app.Application;
 import android.net.MacAddress;
 import android.net.MatchAllNetworkSpecifier;
 import android.net.NetworkRequest;
@@ -30,8 +30,11 @@
 import android.os.PatternMatcher;
 import android.os.Process;
 import android.text.TextUtils;
+import android.util.Log;
 import android.util.Pair;
 
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.nio.charset.CharsetEncoder;
 import java.nio.charset.StandardCharsets;
 import java.util.Objects;
@@ -41,6 +44,7 @@
  * {@link WifiNetworkSpecifier.Builder} class to create an instance.
  */
 public final class WifiNetworkSpecifier extends NetworkSpecifier implements Parcelable {
+    private static final String TAG = "WifiNetworkSpecifier";
 
     /**
      * Builder used to create {@link WifiNetworkSpecifier} objects.
@@ -436,7 +440,22 @@
                     mBssidPatternMatcher,
                     buildWifiConfiguration(),
                     Process.myUid(),
-                    ActivityThread.currentApplication().getApplicationContext().getOpPackageName());
+                    getCurrentApplicationReflectively().getApplicationContext().getOpPackageName());
+        }
+
+        // TODO(b/144102365): Remove once refactor is complete
+        private static Application getCurrentApplicationReflectively() {
+            try {
+                // reflection for static method android.app.ActivityThread#currentApplication()
+                Class<?> klass = Class.forName("android.app.ActivityThread");
+                Method currentApplicationMethod = klass.getDeclaredMethod("currentApplication");
+                Object result = currentApplicationMethod.invoke(null);
+                return (Application) result;
+            } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException
+                    | InvocationTargetException e) {
+                Log.e(TAG, "Failed to call ActivityThread#currentApplication() reflectively!", e);
+                throw new RuntimeException(e);
+            }
         }
     }
 
diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
index 9c1475f..6085eae 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
@@ -116,12 +116,18 @@
         /**
          * Whether this network is shared credential with user to allow user manually connect.
          */
-        private boolean mIsUserAllowed;
+        private boolean mIsSharedWithUser;
 
         /**
-         * Whether the setIsUserAllowedToManuallyConnect have been called.
+         * Whether the setCredentialSharedWithUser have been called.
          */
-        private boolean mIsUserAllowedBeenSet;
+        private boolean mIsSharedWithUserSet;
+
+        /**
+         * Whether this network is initialized with auto-join enabled (the default) or not.
+         */
+        private boolean mIsInitialAutoJoinEnabled;
+
         /**
          * Pre-shared key for use with WAPI-PSK networks.
          */
@@ -133,6 +139,11 @@
          */
         private @Nullable WifiEnterpriseConfig mWapiEnterpriseConfig;
 
+        /**
+         * Whether this network will be brought up as untrusted (TRUSTED capability bit removed).
+         */
+        private boolean mIsNetworkUntrusted;
+
         public Builder() {
             mSsid = null;
             mBssid =  null;
@@ -146,12 +157,14 @@
             mIsAppInteractionRequired = false;
             mIsUserInteractionRequired = false;
             mIsMetered = false;
-            mIsUserAllowed = true;
-            mIsUserAllowedBeenSet = false;
+            mIsSharedWithUser = true;
+            mIsSharedWithUserSet = false;
+            mIsInitialAutoJoinEnabled = true;
             mPriority = UNASSIGNED_PRIORITY;
             mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
             mWapiPskPassphrase = null;
             mWapiEnterpriseConfig = null;
+            mIsNetworkUntrusted = false;
         }
 
         /**
@@ -430,13 +443,55 @@
          * <li>If not set, defaults to true (i.e. allow user to manually connect) for secure
          * networks and false for open networks.</li>
          *
-         * @param isAllowed {@code true} to indicate that the credentials may be used by the user to
+         * @param isShared {@code true} to indicate that the credentials may be used by the user to
          *                              manually connect to the network, {@code false} otherwise.
          * @return Instance of {@link Builder} to enable chaining of the builder method.
          */
-        public @NonNull Builder setIsUserAllowedToManuallyConnect(boolean isAllowed) {
-            mIsUserAllowed = isAllowed;
-            mIsUserAllowedBeenSet = true;
+        public @NonNull Builder setCredentialSharedWithUser(boolean isShared) {
+            mIsSharedWithUser = isShared;
+            mIsSharedWithUserSet = true;
+            return this;
+        }
+
+        /**
+         * Specifies whether the suggestion is created with auto-join enabled or disabled. The
+         * user may modify the auto-join configuration of a suggestion directly once the device
+         * associates to the network.
+         * <p>
+         * If auto-join is initialized as disabled the user may still be able to manually connect
+         * to the network. Therefore, disabling auto-join only makes sense if
+         * {@link #setCredentialSharedWithUser(boolean)} is set to true (the default) which
+         * itself implies a secure (non-open) network.
+         * <p>
+         * If not set, defaults to true (i.e. auto-join is initialized as enabled).
+         *
+         * @param enabled true for initializing with auto-join enabled (the default), false to
+         *                initializing with auto-join disabled.
+         * @return Instance of (@link {@link Builder} to enable chaining of the builder method.
+         */
+        public @NonNull Builder setIsInitialAutoJoinEnabled(boolean enabled) {
+            mIsInitialAutoJoinEnabled = enabled;
+            return this;
+        }
+
+        /**
+         * Specifies whether the system will bring up the network (if selected) as untrusted. An
+         * untrusted network has its {@link android.net.NetworkCapabilities#NET_CAPABILITY_TRUSTED}
+         * capability removed. The Wi-Fi network selection process may use this information to
+         * influence priority of the suggested network for Wi-Fi network selection (most likely to
+         * reduce it). The connectivity service may use this information to influence the overall
+         * network configuration of the device.
+         * <p>
+         * <li> An untrusted network's credentials may not be shared with the user using
+         * {@link #setCredentialSharedWithUser(boolean)}.</li>
+         * <li> If not set, defaults to false (i.e. network is trusted).</li>
+         *
+         * @param isUntrusted Boolean indicating whether the network should be brought up untrusted
+         *                    (if true) or trusted (if false).
+         * @return Instance of {@link Builder} to enable chaining of the builder method.
+         */
+        public @NonNull Builder setUntrusted(boolean isUntrusted) {
+            mIsNetworkUntrusted = isUntrusted;
             return this;
         }
 
@@ -518,6 +573,7 @@
             wifiConfiguration.meteredOverride =
                     mIsMetered ? WifiConfiguration.METERED_OVERRIDE_METERED
                             : WifiConfiguration.METERED_OVERRIDE_NONE;
+            wifiConfiguration.trusted = !mIsNetworkUntrusted;
             mPasspointConfiguration.setCarrierId(mCarrierId);
             return wifiConfiguration;
         }
@@ -587,7 +643,6 @@
                             + "suggestion with Passpoint configuration");
                 }
                 wifiConfiguration = buildWifiConfigurationForPasspoint();
-
             } else {
                 if (mSsid == null) {
                     throw new IllegalStateException("setSsid should be invoked for suggestion");
@@ -602,20 +657,34 @@
                 }
                 wifiConfiguration = buildWifiConfiguration();
                 if (wifiConfiguration.isOpenNetwork()) {
-                    if (mIsUserAllowedBeenSet && mIsUserAllowed) {
+                    if (mIsSharedWithUserSet && mIsSharedWithUser) {
                         throw new IllegalStateException("Open network should not be "
-                                + "setIsUserAllowedToManuallyConnect to true");
+                                + "setCredentialSharedWithUser to true");
                     }
-                    mIsUserAllowed = false;
+                    mIsSharedWithUser = false;
                 }
             }
-
+            if (!mIsSharedWithUser && !mIsInitialAutoJoinEnabled) {
+                throw new IllegalStateException("Should have not a network with both "
+                        + "setCredentialSharedWithUser and "
+                        + "setIsAutoJoinEnabled set to false");
+            }
+            if (mIsNetworkUntrusted) {
+                if (mIsSharedWithUserSet && mIsSharedWithUser) {
+                    throw new IllegalStateException("Should not be both"
+                            + "setCredentialSharedWithUser and +"
+                            + "setIsNetworkAsUntrusted to true");
+                }
+                mIsSharedWithUser = false;
+            }
             return new WifiNetworkSuggestion(
                     wifiConfiguration,
                     mPasspointConfiguration,
                     mIsAppInteractionRequired,
                     mIsUserInteractionRequired,
-                    mIsUserAllowed);
+                    mIsSharedWithUser,
+                    mIsInitialAutoJoinEnabled,
+                    mIsNetworkUntrusted);
         }
     }
 
@@ -623,12 +692,14 @@
      * Network configuration for the provided network.
      * @hide
      */
+    @NonNull
     public final WifiConfiguration wifiConfiguration;
 
     /**
      * Passpoint configuration for the provided network.
      * @hide
      */
+    @Nullable
     public final PasspointConfiguration passpointConfiguration;
 
     /**
@@ -642,6 +713,7 @@
      * @hide
      */
     public final boolean isUserInteractionRequired;
+
     /**
      * Whether app share credential with the user, allow user use provided credential to
      * connect network manually.
@@ -649,13 +721,28 @@
      */
     public final boolean isUserAllowedToManuallyConnect;
 
+    /**
+     * Whether the suggestion will be initialized as auto-joined or not.
+     * @hide
+     */
+    public final boolean isInitialAutoJoinEnabled;
+
+    /**
+     * Whether this network will be brought up as untrusted (TRUSTED capability bit removed).
+     * @hide
+     */
+    public final boolean isNetworkUntrusted;
+
+
     /** @hide */
     public WifiNetworkSuggestion() {
-        this.wifiConfiguration = null;
+        this.wifiConfiguration = new WifiConfiguration();
         this.passpointConfiguration = null;
         this.isAppInteractionRequired = false;
         this.isUserInteractionRequired = false;
         this.isUserAllowedToManuallyConnect = true;
+        this.isInitialAutoJoinEnabled = true;
+        this.isNetworkUntrusted = false;
     }
 
     /** @hide */
@@ -663,7 +750,9 @@
                                  @Nullable PasspointConfiguration passpointConfiguration,
                                  boolean isAppInteractionRequired,
                                  boolean isUserInteractionRequired,
-                                 boolean isUserAllowedToManuallyConnect) {
+                                 boolean isUserAllowedToManuallyConnect,
+                                 boolean isInitialAutoJoinEnabled,
+            boolean isNetworkUntrusted) {
         checkNotNull(networkConfiguration);
         this.wifiConfiguration = networkConfiguration;
         this.passpointConfiguration = passpointConfiguration;
@@ -671,6 +760,8 @@
         this.isAppInteractionRequired = isAppInteractionRequired;
         this.isUserInteractionRequired = isUserInteractionRequired;
         this.isUserAllowedToManuallyConnect = isUserAllowedToManuallyConnect;
+        this.isInitialAutoJoinEnabled = isInitialAutoJoinEnabled;
+        this.isNetworkUntrusted = isNetworkUntrusted;
     }
 
     public static final @NonNull Creator<WifiNetworkSuggestion> CREATOR =
@@ -682,7 +773,9 @@
                             in.readParcelable(null), // PasspointConfiguration
                             in.readBoolean(), // isAppInteractionRequired
                             in.readBoolean(), // isUserInteractionRequired
-                            in.readBoolean()  // isSharedCredentialWithUser
+                            in.readBoolean(), // isSharedCredentialWithUser
+                            in.readBoolean(),  // isAutoJoinEnabled
+                            in.readBoolean()
                     );
                 }
 
@@ -704,6 +797,8 @@
         dest.writeBoolean(isAppInteractionRequired);
         dest.writeBoolean(isUserInteractionRequired);
         dest.writeBoolean(isUserAllowedToManuallyConnect);
+        dest.writeBoolean(isInitialAutoJoinEnabled);
+        dest.writeBoolean(isNetworkUntrusted);
     }
 
     @Override
@@ -743,8 +838,31 @@
                 .append(", FQDN=").append(wifiConfiguration.FQDN)
                 .append(", isAppInteractionRequired=").append(isAppInteractionRequired)
                 .append(", isUserInteractionRequired=").append(isUserInteractionRequired)
-                .append(", isUserAllowedToManuallyConnect=").append(isUserAllowedToManuallyConnect)
+                .append(", isCredentialSharedWithUser=").append(isUserAllowedToManuallyConnect)
+                .append(", isInitialAutoJoinEnabled=").append(isInitialAutoJoinEnabled)
+                .append(", isUnTrusted=").append(isNetworkUntrusted)
                 .append(" ]");
         return sb.toString();
     }
+
+    /**
+     * Get the {@link WifiConfiguration} associated with this Suggestion.
+     * @hide
+     */
+    @SystemApi
+    @NonNull
+    public WifiConfiguration getWifiConfiguration() {
+        return wifiConfiguration;
+    }
+
+    /**
+     * Get the {@link PasspointConfiguration} associated with this Suggestion, or null if this
+     * Suggestion is not for a Passpoint network.
+     * @hide
+     */
+    @SystemApi
+    @Nullable
+    public PasspointConfiguration getPasspointConfiguration() {
+        return passpointConfiguration;
+    }
 }
diff --git a/wifi/java/android/net/wifi/WifiOemConfigStoreMigrationHook.java b/wifi/java/android/net/wifi/WifiOemConfigStoreMigrationHook.java
new file mode 100755
index 0000000..642dcb9
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiOemConfigStoreMigrationHook.java
@@ -0,0 +1,180 @@
+/*
+ * 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.wifi;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * Class used to provide one time hooks for existing OEM devices to migrate their config store
+ * data to the wifi mainline module.
+ * <p>
+ * Note:
+ * <li> OEM's need to implement {@link #load()} only if their
+ * existing config store format or file locations differs from the vanilla AOSP implementation (
+ * which is what the wifi mainline module understands).
+ * </li>
+ * <li> The wifi mainline module will invoke {@link #load()}  method on every bootup, its
+ * the responsibility of the OEM implementation to ensure that this method returns non-null data
+ * only on the first bootup. Once the migration is done, the OEM can safely delete their config
+ * store files and then return null on any subsequent reboots. The first & only relevant invocation
+ * of {@link #load()} occurs when a previously released device upgrades to the wifi
+ * mainline module from an OEM implementation of the wifi stack.
+ * </li>
+ * @hide
+ */
+@SystemApi
+public final class WifiOemConfigStoreMigrationHook {
+    /**
+     * Container for all the wifi config data to migrate.
+     */
+    public static final class MigrationData implements Parcelable {
+        /**
+         * Builder to create instance of {@link MigrationData}.
+         */
+        public static final class Builder {
+            private List<WifiConfiguration> mUserSavedNetworkConfigurations;
+            private SoftApConfiguration mUserSoftApConfiguration;
+
+            public Builder() {
+                mUserSavedNetworkConfigurations = null;
+                mUserSoftApConfiguration = null;
+            }
+
+            /**
+             * Sets the list of all user's saved network configurations parsed from OEM config
+             * store files.
+             *
+             * @param userSavedNetworkConfigurations List of {@link WifiConfiguration} representing
+             *                                       the list of user's saved networks
+             * @return Instance of {@link Builder} to enable chaining of the builder method.
+             */
+            public @NonNull Builder setUserSavedNetworkConfigurations(
+                    @NonNull List<WifiConfiguration> userSavedNetworkConfigurations) {
+                checkNotNull(userSavedNetworkConfigurations);
+                mUserSavedNetworkConfigurations = userSavedNetworkConfigurations;
+                return this;
+            }
+
+            /**
+             * Sets the user's softap configuration parsed from OEM config store files.
+             *
+             * @param userSoftApConfiguration {@link SoftApConfiguration} representing user's
+             *                                SoftAp configuration
+             * @return Instance of {@link Builder} to enable chaining of the builder method.
+             */
+            public @NonNull Builder setUserSoftApConfiguration(
+                    @NonNull SoftApConfiguration userSoftApConfiguration) {
+                checkNotNull(userSoftApConfiguration);
+                mUserSoftApConfiguration  = userSoftApConfiguration;
+                return this;
+            }
+
+            /**
+             * Build an instance of {@link MigrationData}.
+             *
+             * @return Instance of {@link MigrationData}.
+             */
+            public @NonNull MigrationData build() {
+                return new MigrationData(mUserSavedNetworkConfigurations, mUserSoftApConfiguration);
+            }
+        }
+
+        private final List<WifiConfiguration> mUserSavedNetworkConfigurations;
+        private final SoftApConfiguration mUserSoftApConfiguration;
+
+        private MigrationData(
+                @Nullable List<WifiConfiguration> userSavedNetworkConfigurations,
+                @Nullable SoftApConfiguration userSoftApConfiguration) {
+            mUserSavedNetworkConfigurations = userSavedNetworkConfigurations;
+            mUserSoftApConfiguration = userSoftApConfiguration;
+        }
+
+        public static final @NonNull Parcelable.Creator<MigrationData> CREATOR =
+                new Parcelable.Creator<MigrationData>() {
+                    @Override
+                    public MigrationData createFromParcel(Parcel in) {
+                        List<WifiConfiguration> userSavedNetworkConfigurations =
+                                in.readArrayList(null);
+                        SoftApConfiguration userSoftApConfiguration = in.readParcelable(null);
+                        return new MigrationData(
+                                userSavedNetworkConfigurations, userSoftApConfiguration);
+                    }
+
+                    @Override
+                    public MigrationData[] newArray(int size) {
+                        return new MigrationData[size];
+                    }
+                };
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeList(mUserSavedNetworkConfigurations);
+            dest.writeParcelable(mUserSoftApConfiguration, flags);
+        }
+
+        /**
+         * Returns list of all user's saved network configurations.
+         *
+         * Note: Only to be returned if there is any format change in how OEM persisted this info.
+         * @return List of {@link WifiConfiguration} representing the list of user's saved networks,
+         * or null if no migration necessary.
+         */
+        @Nullable
+        public List<WifiConfiguration> getUserSavedNetworkConfigurations() {
+            return mUserSavedNetworkConfigurations;
+        }
+
+        /**
+         * Returns user's softap configuration.
+         *
+         * Note: Only to be returned if there is any format change in how OEM persisted this info.
+         * @return {@link SoftApConfiguration} representing user's SoftAp configuration,
+         * or null if no migration necessary.
+         */
+        @Nullable
+        public SoftApConfiguration getUserSoftApConfiguration() {
+            return mUserSoftApConfiguration;
+        }
+    }
+
+    private WifiOemConfigStoreMigrationHook() { }
+
+    /**
+     * Load data from OEM's config store.
+     *
+     * @return Instance of {@link MigrationData} for migrating data, null if no
+     * migration is necessary.
+     */
+    @Nullable
+    public static MigrationData load() {
+        // Note: OEM's should add code to parse data from their config store format here!
+        return null;
+    }
+}
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index 2c39c32a..18533ef 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -291,28 +291,46 @@
         @NonNull
         @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
         public final List<HiddenNetwork> hiddenNetworks = new ArrayList<>();
-        /** period of background scan; in millisecond, 0 => single shot scan */
+        /**
+         * period of background scan; in millisecond, 0 => single shot scan
+         * @deprecated Background scan support is removed.
+         */
+        @Deprecated
         public int periodInMs;
-        /** must have a valid REPORT_EVENT value */
+        /**
+         * must have a valid REPORT_EVENT value
+         * @deprecated Background scan support is removed.
+         */
+        @Deprecated
         public int reportEvents;
-        /** defines number of bssids to cache from each scan */
+        /**
+         * defines number of bssids to cache from each scan
+         * @deprecated Background scan support is removed.
+         */
+        @Deprecated
         public int numBssidsPerScan;
         /**
          * defines number of scans to cache; use it with REPORT_EVENT_AFTER_BUFFER_FULL
          * to wake up at fixed interval
+         * @deprecated Background scan support is removed.
          */
+        @Deprecated
         public int maxScansToCache;
         /**
          * if maxPeriodInMs is non zero or different than period, then this bucket is
          * a truncated binary exponential backoff bucket and the scan period will grow
          * exponentially as per formula: actual_period(N) = period * (2 ^ (N/stepCount))
          * to maxPeriodInMs
+         * @deprecated Background scan support is removed.
          */
+        @Deprecated
         public int maxPeriodInMs;
         /**
          * for truncated binary exponential back off bucket, number of scans to perform
          * for a given period
+         * @deprecated Background scan support is removed.
          */
+        @Deprecated
         public int stepCount;
         /**
          * Flag to indicate if the scan settings are targeted for PNO scan.
@@ -788,7 +806,9 @@
         /**
          * Framework co-ordinates scans across multiple apps; so it may not give exactly the
          * same period requested. If period of a scan is changed; it is reported by this event.
+         * @deprecated Background scan support is removed.
          */
+        @Deprecated
         public void onPeriodChanged(int periodInMs);
         /**
          * reports results retrieved from background scan and single shot scans
@@ -833,6 +853,7 @@
      * delivered to the listener. It is possible that onFullResult will not be called for all
      * results of the first scan if the listener was registered during the scan.
      *
+     * @param executor the Executor on which to run the callback.
      * @param listener specifies the object to report events to. This object is also treated as a
      *                 key for this request, and must also be specified to cancel the request.
      *                 Multiple requests should also not share this object.
@@ -890,7 +911,9 @@
      * @param listener specifies the object to report events to. This object is also treated as a
      *                 key for this scan, and must also be specified to cancel the scan. Multiple
      *                 scans should also not share this object.
+     * @deprecated Background scan support is removed.
      */
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
     public void startBackgroundScan(ScanSettings settings, ScanListener listener,
             WorkSource workSource) {
@@ -910,7 +933,9 @@
      * stop an ongoing wifi scan
      * @param listener specifies which scan to cancel; must be same object as passed in {@link
      *  #startBackgroundScan}
+     * @deprecated Background scan support is removed.
      */
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
     public void stopBackgroundScan(ScanListener listener) {
         Objects.requireNonNull(listener, "listener cannot be null");
@@ -926,7 +951,9 @@
     /**
      * reports currently available scan results on appropriate listeners
      * @return true if all scan results were reported correctly
+     * @deprecated Background scan support is removed.
      */
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
     public boolean getScanResults() {
         validateChannel();
@@ -955,15 +982,32 @@
      * starts a single scan and reports results asynchronously
      * @param settings specifies various parameters for the scan; for more information look at
      * {@link ScanSettings}
-     * @param workSource WorkSource to blame for power usage
      * @param listener specifies the object to report events to. This object is also treated as a
      *                 key for this scan, and must also be specified to cancel the scan. Multiple
      *                 scans should also not share this object.
+     * @param workSource WorkSource to blame for power usage
      */
     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
     public void startScan(ScanSettings settings, ScanListener listener, WorkSource workSource) {
+        startScan(settings, null, listener, workSource);
+    }
+
+    /**
+     * starts a single scan and reports results asynchronously
+     * @param settings specifies various parameters for the scan; for more information look at
+     * {@link ScanSettings}
+     * @param executor the Executor on which to run the callback.
+     * @param listener specifies the object to report events to. This object is also treated as a
+     *                 key for this scan, and must also be specified to cancel the scan. Multiple
+     *                 scans should also not share this object.
+     * @param workSource WorkSource to blame for power usage
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+    public void startScan(ScanSettings settings, @Nullable @CallbackExecutor Executor executor,
+            ScanListener listener, WorkSource workSource) {
         Objects.requireNonNull(listener, "listener cannot be null");
-        int key = addListener(listener);
+        int key = addListener(listener, executor);
         if (key == INVALID_KEY) return;
         validateChannel();
         Bundle scanParams = new Bundle();
@@ -1029,16 +1073,17 @@
      * {@link ScanSettings}
      * @param pnoSettings specifies various parameters for PNO; for more information look at
      * {@link PnoSettings}
+     * @param executor the Executor on which to run the callback.
      * @param listener specifies the object to report events to. This object is also treated as a
      *                 key for this scan, and must also be specified to cancel the scan. Multiple
      *                 scans should also not share this object.
      * {@hide}
      */
     public void startConnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings,
-            PnoScanListener listener) {
+            @NonNull @CallbackExecutor Executor executor, PnoScanListener listener) {
         Objects.requireNonNull(listener, "listener cannot be null");
         Objects.requireNonNull(pnoSettings, "pnoSettings cannot be null");
-        int key = addListener(listener);
+        int key = addListener(listener, executor);
         if (key == INVALID_KEY) return;
         validateChannel();
         pnoSettings.isConnected = true;
@@ -1057,10 +1102,10 @@
      */
     @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
     public void startDisconnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings,
-            PnoScanListener listener) {
+            @NonNull @CallbackExecutor Executor executor, PnoScanListener listener) {
         Objects.requireNonNull(listener, "listener cannot be null");
         Objects.requireNonNull(pnoSettings, "pnoSettings cannot be null");
-        int key = addListener(listener);
+        int key = addListener(listener, executor);
         if (key == INVALID_KEY) return;
         validateChannel();
         pnoSettings.isConnected = false;
diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
index 5befb54..7c335fc 100644
--- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
+++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
@@ -17,6 +17,7 @@
 package android.net.wifi.hotspot2;
 
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.net.wifi.hotspot2.pps.Credential;
 import android.net.wifi.hotspot2.pps.HomeSp;
 import android.net.wifi.hotspot2.pps.Policy;
@@ -423,6 +424,73 @@
     }
 
     /**
+     * The auto-join configuration specifies whether or not the Passpoint Configuration is
+     * considered for auto-connection. If true then yes, if false then it isn't considered as part
+     * of auto-connection - but can still be manually connected to.
+     */
+    private boolean mIsAutoJoinEnabled = true;
+
+    /**
+     * The mac randomization setting specifies whether a randomized or device MAC address will
+     * be used to connect to the passpoint network. If true, a randomized MAC will be used.
+     * Otherwise, the device MAC address will be used.
+     */
+    private boolean mIsMacRandomizationEnabled = true;
+
+    /**
+     * Configures the auto-association status of this Passpoint configuration. A value of true
+     * indicates that the configuration will be considered for auto-connection, a value of false
+     * indicates that only manual connection will work - the framework will not auto-associate to
+     * this Passpoint network.
+     *
+     * @param autoJoinEnabled true to be considered for framework auto-connection, false otherwise.
+     * @hide
+     */
+    public void setAutoJoinEnabled(boolean autoJoinEnabled) {
+        mIsAutoJoinEnabled = autoJoinEnabled;
+    }
+
+    /**
+     * Configures the MAC randomization setting for this Passpoint configuration.
+     * If set to true, the framework will use a randomized MAC address to connect to this Passpoint
+     * network. Otherwise, the framework will use the device MAC address.
+     *
+     * @param enabled true to use randomized MAC address, false to use device MAC address.
+     * @hide
+     */
+    public void setMacRandomizationEnabled(boolean enabled) {
+        mIsMacRandomizationEnabled = enabled;
+    }
+
+    /**
+     * Indicates whether the Passpoint configuration may be auto-connected to by the framework. A
+     * value of true indicates that auto-connection can happen, a value of false indicates that it
+     * cannot. However, even when auto-connection is not possible manual connection by the user is
+     * possible.
+     *
+     * @return the auto-join configuration: true for auto-connection (or join) enabled, false
+     * otherwise.
+     * @hide
+     */
+    @SystemApi
+    public boolean isAutoJoinEnabled() {
+        return mIsAutoJoinEnabled;
+    }
+
+    /**
+     * Indicates whether a randomized MAC address or device MAC address will be used for
+     * connections to this Passpoint network. If true, a randomized MAC address will be used.
+     * Otherwise, the device MAC address will be used.
+     *
+     * @return true for MAC randomization enabled. False for disabled.
+     * @hide
+     */
+    @SystemApi
+    public boolean isMacRandomizationEnabled() {
+        return mIsMacRandomizationEnabled;
+    }
+
+    /**
      * Constructor for creating PasspointConfiguration with default values.
      */
     public PasspointConfiguration() {}
@@ -464,6 +532,8 @@
         mServiceFriendlyNames = source.mServiceFriendlyNames;
         mAaaServerTrustedNames = source.mAaaServerTrustedNames;
         mCarrierId = source.mCarrierId;
+        mIsAutoJoinEnabled = source.mIsAutoJoinEnabled;
+        mIsMacRandomizationEnabled = source.mIsMacRandomizationEnabled;
     }
 
     @Override
@@ -493,6 +563,8 @@
                 (HashMap<String, String>) mServiceFriendlyNames);
         dest.writeBundle(bundle);
         dest.writeInt(mCarrierId);
+        dest.writeBoolean(mIsAutoJoinEnabled);
+        dest.writeBoolean(mIsMacRandomizationEnabled);
     }
 
     @Override
@@ -523,6 +595,8 @@
                 && mUsageLimitDataLimit == that.mUsageLimitDataLimit
                 && mUsageLimitTimeLimitInMinutes == that.mUsageLimitTimeLimitInMinutes
                 && mCarrierId == that.mCarrierId
+                && mIsAutoJoinEnabled == that.mIsAutoJoinEnabled
+                && mIsMacRandomizationEnabled == that.mIsMacRandomizationEnabled
                 && (mServiceFriendlyNames == null ? that.mServiceFriendlyNames == null
                 : mServiceFriendlyNames.equals(that.mServiceFriendlyNames));
     }
@@ -533,7 +607,7 @@
                 mUpdateIdentifier, mCredentialPriority, mSubscriptionCreationTimeInMillis,
                 mSubscriptionExpirationTimeInMillis, mUsageLimitUsageTimePeriodInMinutes,
                 mUsageLimitStartTimeInMillis, mUsageLimitDataLimit, mUsageLimitTimeLimitInMinutes,
-                mServiceFriendlyNames, mCarrierId);
+                mServiceFriendlyNames, mCarrierId, mIsAutoJoinEnabled, mIsMacRandomizationEnabled);
     }
 
     @Override
@@ -587,6 +661,8 @@
             builder.append("ServiceFriendlyNames: ").append(mServiceFriendlyNames);
         }
         builder.append("CarrierId:" + mCarrierId);
+        builder.append("IsAutoJoinEnabled:" + mIsAutoJoinEnabled);
+        builder.append("mIsMacRandomizationEnabled:" + mIsMacRandomizationEnabled);
         return builder.toString();
     }
 
@@ -692,6 +768,8 @@
                         "serviceFriendlyNames");
                 config.setServiceFriendlyNames(friendlyNamesMap);
                 config.mCarrierId = in.readInt();
+                config.mIsAutoJoinEnabled = in.readBoolean();
+                config.mIsMacRandomizationEnabled = in.readBoolean();
                 return config;
             }
 
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
index 8fa9c3d..9562f95 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
@@ -66,12 +66,20 @@
     /** @hide */
     public String passphrase = "";
 
-    /** Get the required band for the group owner. */
+    /**
+     * Get the required band for the group owner.
+     * The result will be one of the following:
+     * {@link #GROUP_OWNER_BAND_AUTO},
+     * {@link #GROUP_OWNER_BAND_2GHZ},
+     * {@link #GROUP_OWNER_BAND_5GHZ}
+     */
+    @GroupOperatingBandType
     public int getGroupOwnerBand() {
         return groupOwnerBand;
     }
 
     /** @hide */
+    @GroupOperatingBandType
     public int groupOwnerBand = GROUP_OWNER_BAND_AUTO;
 
     /** @hide */
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pWfdInfo.java b/wifi/java/android/net/wifi/p2p/WifiP2pWfdInfo.java
index a411502..5484d24 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pWfdInfo.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pWfdInfo.java
@@ -61,6 +61,9 @@
      * {@link #mDeviceInfo} & {@link #DEVICE_TYPE} is one of {@link #DEVICE_TYPE_WFD_SOURCE},
      * {@link #DEVICE_TYPE_PRIMARY_SINK}, {@link #DEVICE_TYPE_SECONDARY_SINK} or
      * {@link #DEVICE_TYPE_SOURCE_OR_PRIMARY_SINK}.
+     *
+     * The bit definition is listed in 5.1.2 WFD Device Information Subelement in
+     * Wi-Fi Display Technical Specification.
      */
     private static final int DEVICE_TYPE                            = 1 << 1 | 1 << 0;
     private static final int COUPLED_SINK_SUPPORT_AT_SOURCE         = 1 << 2;
@@ -69,6 +72,8 @@
     private static final int SESSION_AVAILABLE_BIT2                 = 1 << 5;
     private static final int SESSION_AVAILABLE                      =
             SESSION_AVAILABLE_BIT2 | SESSION_AVAILABLE_BIT1;
+    /* The support of Content Protection using the HDCP system 2.0/2.1. */
+    private static final int CONTENT_PROTECTION_SUPPORT             = 1 << 8;
 
     private int mCtrlPort;
 
@@ -146,6 +151,26 @@
         }
     }
 
+    /**
+     * @return true if Content Protection using the HDCP system 2.0/2.1 is supported.
+     */
+    public boolean isContentProtectionSupported() {
+        return (mDeviceInfo & CONTENT_PROTECTION_SUPPORT) != 0;
+    }
+
+    /**
+     * Sets whether Content Protection using the HDCP system 2.0/2.1 is supported.
+     *
+     * @param enabled true to indicate that Content Protection is supported, false otherwise.
+     */
+    public void setContentProtectionSupported(boolean enabled) {
+        if (enabled) {
+            mDeviceInfo |= CONTENT_PROTECTION_SUPPORT;
+        } else {
+            mDeviceInfo &= ~CONTENT_PROTECTION_SUPPORT;
+        }
+    }
+
     /** Returns the TCP port at which the WFD Device listens for RTSP messages. */
     public int getControlPort() {
         return mCtrlPort;
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceInfo.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceInfo.java
index 0de7ba6..dad431c1 100644
--- a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceInfo.java
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceInfo.java
@@ -17,7 +17,7 @@
 package android.net.wifi.p2p.nsd;
 
 import android.compat.annotation.UnsupportedAppUsage;
-import android.net.nsd.DnsSdTxtRecord;
+import android.net.util.nsd.DnsSdTxtRecord;
 import android.os.Build;
 import android.text.TextUtils;
 
diff --git a/wifi/java/android/net/wifi/rtt/ResponderLocation.java b/wifi/java/android/net/wifi/rtt/ResponderLocation.java
index 970a75d..218b2dca 100644
--- a/wifi/java/android/net/wifi/rtt/ResponderLocation.java
+++ b/wifi/java/android/net/wifi/rtt/ResponderLocation.java
@@ -21,6 +21,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.location.Address;
 import android.location.Location;
@@ -1367,7 +1368,8 @@
      *
      */
     @Nullable
-    public SparseArray toCivicLocationSparseArray() {
+    @SuppressLint("ChangedType")
+    public SparseArray<String> toCivicLocationSparseArray() {
         if (mCivicLocation != null && mCivicLocation.isValid()) {
             return mCivicLocation.toSparseArray();
         } else {
diff --git a/wifi/java/android/net/wifi/wificond/DeviceWiphyCapabilities.java b/wifi/java/android/net/wifi/wificond/DeviceWiphyCapabilities.java
new file mode 100644
index 0000000..13ae3b3
--- /dev/null
+++ b/wifi/java/android/net/wifi/wificond/DeviceWiphyCapabilities.java
@@ -0,0 +1,272 @@
+/*
+ * 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.wifi.wificond;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.net.wifi.ScanResult;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.util.Objects;
+
+/**
+ * DeviceWiphyCapabilities for wificond
+ *
+ * @hide
+ */
+@SystemApi
+public final class DeviceWiphyCapabilities implements Parcelable {
+    private static final String TAG = "DeviceWiphyCapabilities";
+
+    private boolean m80211nSupported;
+    private boolean m80211acSupported;
+    private boolean m80211axSupported;
+    private boolean mChannelWidth160MhzSupported;
+    private boolean mChannelWidth80p80MhzSupported;
+    private int mMaxNumberTxSpatialStreams;
+    private int mMaxNumberRxSpatialStreams;
+
+
+    /** public constructor */
+    public DeviceWiphyCapabilities() {
+        m80211nSupported = false;
+        m80211acSupported = false;
+        m80211axSupported = false;
+        mChannelWidth160MhzSupported = false;
+        mChannelWidth80p80MhzSupported = false;
+        mMaxNumberTxSpatialStreams = 1;
+        mMaxNumberRxSpatialStreams = 1;
+    }
+
+    /**
+     * Get the IEEE 802.11 standard support
+     *
+     * @param standard the IEEE 802.11 standard to check on its support.
+     *        valid values from {@link ScanResult}'s {@code WIFI_STANDARD_}
+     * @return {@code true} if supported, {@code false} otherwise.
+     */
+    public boolean isWifiStandardSupported(int standard) {
+        switch (standard) {
+            case ScanResult.WIFI_STANDARD_LEGACY:
+                return true;
+            case ScanResult.WIFI_STANDARD_11N:
+                return m80211nSupported;
+            case ScanResult.WIFI_STANDARD_11AC:
+                return m80211acSupported;
+            case ScanResult.WIFI_STANDARD_11AX:
+                return m80211axSupported;
+            default:
+                Log.e(TAG, "isWifiStandardSupported called with invalid standard: " + standard);
+                return false;
+        }
+    }
+
+    /**
+     * Set the IEEE 802.11 standard support
+     *
+     * @param standard the IEEE 802.11 standard to set its support.
+     *        valid values from {@link ScanResult}'s {@code WIFI_STANDARD_}
+     * @param support {@code true} if supported, {@code false} otherwise.
+     */
+    public void setWifiStandardSupport(int standard, boolean support) {
+        switch (standard) {
+            case ScanResult.WIFI_STANDARD_11N:
+                m80211nSupported = support;
+                break;
+            case ScanResult.WIFI_STANDARD_11AC:
+                m80211acSupported = support;
+                break;
+            case ScanResult.WIFI_STANDARD_11AX:
+                m80211axSupported = support;
+                break;
+            default:
+                Log.e(TAG, "setWifiStandardSupport called with invalid standard: " + standard);
+        }
+    }
+
+    /**
+     * Get the support for channel bandwidth
+     *
+     * @param chWidth valid values from {@link ScanResult}'s {@code CHANNEL_WIDTH_}
+     *
+     * @return {@code true} if supported, {@code false} otherwise.
+     */
+    public boolean isChannelWidthSupported(int chWidth) {
+        switch (chWidth) {
+            case ScanResult.CHANNEL_WIDTH_20MHZ:
+                return true;
+            case ScanResult.CHANNEL_WIDTH_40MHZ:
+                return (m80211nSupported || m80211acSupported || m80211axSupported);
+            case ScanResult.CHANNEL_WIDTH_80MHZ:
+                return (m80211acSupported || m80211axSupported);
+            case ScanResult.CHANNEL_WIDTH_160MHZ:
+                return mChannelWidth160MhzSupported;
+            case ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ:
+                return mChannelWidth80p80MhzSupported;
+            default:
+                Log.e(TAG, "isChannelWidthSupported called with invalid channel width: " + chWidth);
+        }
+        return false;
+    }
+
+    /**
+     * Set support for channel bandwidth
+     *
+     * @param chWidth valid values are {@link ScanResult#CHANNEL_WIDTH_160MHZ} and
+     *        {@link ScanResult#CHANNEL_WIDTH_80MHZ_PLUS_MHZ}
+     * @param support {@code true} if supported, {@code false} otherwise.
+     */
+    public void setChannelWidthSupported(int chWidth, boolean support) {
+        switch (chWidth) {
+            case ScanResult.CHANNEL_WIDTH_160MHZ:
+                mChannelWidth160MhzSupported = support;
+                break;
+            case ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ:
+                mChannelWidth80p80MhzSupported = support;
+                break;
+            default:
+                Log.e(TAG, "setChannelWidthSupported called with Invalid channel width: "
+                        + chWidth);
+        }
+    }
+
+    /**
+     * Get maximum number of transmit spatial streams
+     *
+     * @return number of spatial streams
+     */
+    public int getMaxNumberTxSpatialStreams() {
+        return mMaxNumberTxSpatialStreams;
+    }
+
+    /**
+     * Set maximum number of transmit spatial streams
+     *
+     * @param streams number of spatial streams
+     */
+    public void setMaxNumberTxSpatialStreams(int streams) {
+        mMaxNumberTxSpatialStreams = streams;
+    }
+
+    /**
+     * Get maximum number of receive spatial streams
+     *
+     * @return number of streams
+     */
+    public int getMaxNumberRxSpatialStreams() {
+        return mMaxNumberRxSpatialStreams;
+    }
+
+    /**
+     * Set maximum number of receive spatial streams
+     *
+     * @param streams number of streams
+     */
+    public void setMaxNumberRxSpatialStreams(int streams) {
+        mMaxNumberRxSpatialStreams = streams;
+    }
+
+    /** override comparator */
+    @Override
+    public boolean equals(Object rhs) {
+        if (this == rhs) return true;
+        if (!(rhs instanceof DeviceWiphyCapabilities)) {
+            return false;
+        }
+        DeviceWiphyCapabilities capa = (DeviceWiphyCapabilities) rhs;
+
+        return m80211nSupported == capa.m80211nSupported
+                && m80211acSupported == capa.m80211acSupported
+                && m80211axSupported == capa.m80211axSupported
+                && mChannelWidth160MhzSupported == capa.mChannelWidth160MhzSupported
+                && mChannelWidth80p80MhzSupported == capa.mChannelWidth80p80MhzSupported
+                && mMaxNumberTxSpatialStreams == capa.mMaxNumberTxSpatialStreams
+                && mMaxNumberRxSpatialStreams == capa.mMaxNumberRxSpatialStreams;
+    }
+
+    /** override hash code */
+    @Override
+    public int hashCode() {
+        return Objects.hash(m80211nSupported, m80211acSupported, m80211axSupported,
+                mChannelWidth160MhzSupported, mChannelWidth80p80MhzSupported,
+                mMaxNumberTxSpatialStreams, mMaxNumberRxSpatialStreams);
+    }
+
+    /** implement Parcelable interface */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * implement Parcelable interface
+     * |flags| is ignored.
+     */
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeBoolean(m80211nSupported);
+        out.writeBoolean(m80211acSupported);
+        out.writeBoolean(m80211axSupported);
+        out.writeBoolean(mChannelWidth160MhzSupported);
+        out.writeBoolean(mChannelWidth80p80MhzSupported);
+        out.writeInt(mMaxNumberTxSpatialStreams);
+        out.writeInt(mMaxNumberRxSpatialStreams);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("m80211nSupported:").append(m80211nSupported ? "Yes" : "No");
+        sb.append("m80211acSupported:").append(m80211acSupported ? "Yes" : "No");
+        sb.append("m80211axSupported:").append(m80211axSupported ? "Yes" : "No");
+        sb.append("mChannelWidth160MhzSupported: ")
+                .append(mChannelWidth160MhzSupported ? "Yes" : "No");
+        sb.append("mChannelWidth80p80MhzSupported: ")
+                .append(mChannelWidth80p80MhzSupported ? "Yes" : "No");
+        sb.append("mMaxNumberTxSpatialStreams: ").append(mMaxNumberTxSpatialStreams);
+        sb.append("mMaxNumberRxSpatialStreams: ").append(mMaxNumberRxSpatialStreams);
+
+        return sb.toString();
+    }
+
+    /** implement Parcelable interface */
+    public static final @NonNull Parcelable.Creator<DeviceWiphyCapabilities> CREATOR =
+            new Parcelable.Creator<DeviceWiphyCapabilities>() {
+        /**
+         * Caller is responsible for providing a valid parcel.
+         */
+        @Override
+        public DeviceWiphyCapabilities createFromParcel(Parcel in) {
+            DeviceWiphyCapabilities capabilities = new DeviceWiphyCapabilities();
+            capabilities.m80211nSupported = in.readBoolean();
+            capabilities.m80211acSupported = in.readBoolean();
+            capabilities.m80211axSupported = in.readBoolean();
+            capabilities.mChannelWidth160MhzSupported = in.readBoolean();
+            capabilities.mChannelWidth80p80MhzSupported = in.readBoolean();
+            capabilities.mMaxNumberTxSpatialStreams = in.readInt();
+            capabilities.mMaxNumberRxSpatialStreams = in.readInt();
+            return capabilities;
+        }
+
+        @Override
+        public DeviceWiphyCapabilities[] newArray(int size) {
+            return new DeviceWiphyCapabilities[size];
+        }
+    };
+}
diff --git a/wifi/java/android/net/wifi/wificond/WifiCondManager.java b/wifi/java/android/net/wifi/wificond/WifiCondManager.java
index f70bdac..4847640 100644
--- a/wifi/java/android/net/wifi/wificond/WifiCondManager.java
+++ b/wifi/java/android/net/wifi/wificond/WifiCondManager.java
@@ -1052,6 +1052,22 @@
     }
 
     /**
+     * Get the device phy capabilities for a given interface
+     */
+    @Nullable public DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String ifaceName) {
+        if (mWificond == null) {
+            Log.e(TAG, "Can not query for device wiphy capabilities at this time");
+            return null;
+        }
+
+        try {
+            return mWificond.getDeviceWiphyCapabilities(ifaceName);
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
      * Register the provided callback handler for SoftAp events. Note that the Soft AP itself is
      * configured using {@link #setupInterfaceForSoftApMode(String)}.
      *
diff --git a/wifi/java/com/android/server/wifi/BaseWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java
index d58083c..080c6c7 100644
--- a/wifi/java/com/android/server/wifi/BaseWifiService.java
+++ b/wifi/java/com/android/server/wifi/BaseWifiService.java
@@ -32,6 +32,7 @@
 import android.net.wifi.ISuggestionConnectionStatusListener;
 import android.net.wifi.ITrafficStateCallback;
 import android.net.wifi.ITxPacketCountListener;
+import android.net.wifi.IWifiConnectedNetworkScorer;
 import android.net.wifi.IWifiManager;
 import android.net.wifi.ScanResult;
 import android.net.wifi.SoftApConfiguration;
@@ -177,11 +178,26 @@
     }
 
     @Override
+    public void allowAutojoinGlobal(boolean choice) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public void allowAutojoin(int netId, boolean choice) {
         throw new UnsupportedOperationException();
     }
 
     @Override
+    public void allowAutojoinPasspoint(String fqdn, boolean enableAutoJoin) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setMacRandomizationSettingPasspointEnabled(String fqdn, boolean enable) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public boolean startScan(String packageName, String featureId) {
         throw new UnsupportedOperationException();
     }
@@ -243,6 +259,11 @@
     }
 
     @Override
+    public boolean isWifiStandardSupported(int standard) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public boolean needs5GHzToAnyApBandConversion() {
         throw new UnsupportedOperationException();
     }
@@ -388,7 +409,8 @@
         throw new UnsupportedOperationException();
     }
 
-    @Override
+    /** @deprecated use {@link #allowAutojoinGlobal(boolean)} instead */
+    @Deprecated
     public void enableWifiConnectivityManager(boolean enabled) {
         throw new UnsupportedOperationException();
     }
@@ -584,4 +606,29 @@
     public int calculateSignalLevel(int rssi) {
         throw new UnsupportedOperationException();
     }
+
+    @Override
+    public List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(
+            List<ScanResult> scanResults) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean setWifiConnectedNetworkScorer(IBinder binder,
+            IWifiConnectedNetworkScorer scorer) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void clearWifiConnectedNetworkScorer() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Map<WifiNetworkSuggestion, List<ScanResult>> getMatchingScanResults(
+            List<WifiNetworkSuggestion> networkSuggestions,
+            List<ScanResult> scanResults,
+            String callingPackage, String callingFeatureId) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/wifi/tests/Android.bp b/wifi/tests/Android.bp
new file mode 100644
index 0000000..6a39959
--- /dev/null
+++ b/wifi/tests/Android.bp
@@ -0,0 +1,50 @@
+// 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.
+
+// Make test APK
+// ============================================================
+
+android_test {
+    name: "FrameworksWifiApiTests",
+
+    defaults: ["framework-wifi-test-defaults"],
+
+    srcs: ["**/*.java"],
+
+    jacoco: {
+        include_filter: ["android.net.wifi.*"],
+        // TODO(b/147521214) need to exclude test classes
+        exclude_filter: [],
+    },
+
+    static_libs: [
+        "androidx.test.rules",
+        "core-test-rules",
+        "guava",
+        "mockito-target-minus-junit4",
+        "net-tests-utils",
+        "frameworks-base-testutils",
+        "truth-prebuilt",
+    ],
+
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+
+    test_suites: [
+        "device-tests",
+        "mts",
+    ],
+}
diff --git a/wifi/tests/Android.mk b/wifi/tests/Android.mk
deleted file mode 100644
index d2c385b4..0000000
--- a/wifi/tests/Android.mk
+++ /dev/null
@@ -1,66 +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.
-
-LOCAL_PATH:= $(call my-dir)
-
-# Make test APK
-# ============================================================
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
-# This list is generated from the java source files in this module
-# The list is a comma separated list of class names with * matching zero or more characters.
-# Example:
-#   Input files: src/com/android/server/wifi/Test.java src/com/android/server/wifi/AnotherTest.java
-#   Generated exclude list: com.android.server.wifi.Test*,com.android.server.wifi.AnotherTest*
-
-# Filter all src files to just java files
-local_java_files := $(filter %.java,$(LOCAL_SRC_FILES))
-# Transform java file names into full class names.
-# This only works if the class name matches the file name and the directory structure
-# matches the package.
-local_classes := $(subst /,.,$(patsubst src/%.java,%,$(local_java_files)))
-# Convert class name list to jacoco exclude list
-# This appends a * to all classes and replace the space separators with commas.
-# These patterns will match all classes in this module and their inner classes.
-jacoco_exclude := $(subst $(space),$(comma),$(patsubst %,%*,$(local_classes)))
-
-jacoco_include := android.net.wifi.*
-
-LOCAL_JACK_COVERAGE_INCLUDE_FILTER := $(jacoco_include)
-LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := $(jacoco_exclude)
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    androidx.test.rules \
-    core-test-rules \
-    guava \
-    mockito-target-minus-junit4 \
-    net-tests-utils \
-    frameworks-base-testutils \
-    truth-prebuilt \
-
-LOCAL_JAVA_LIBRARIES := \
-    android.test.runner \
-    android.test.base \
-
-LOCAL_PACKAGE_NAME := FrameworksWifiApiTests
-LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_COMPATIBILITY_SUITE := \
-    device-tests \
-    mts \
-
-include $(BUILD_PACKAGE)
diff --git a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
index acd3343..2efdd97 100644
--- a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
@@ -18,6 +18,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertNull;
+
 import android.net.MacAddress;
 import android.os.Parcel;
 
@@ -25,6 +27,8 @@
 
 import org.junit.Test;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Random;
 
 @SmallTest
@@ -112,11 +116,18 @@
 
     @Test
     public void testWpa2WithAllFieldCustomized() {
+        List<MacAddress> testBlockedClientList = new ArrayList<>();
+        List<MacAddress> testAllowedClientList = new ArrayList<>();
+        testBlockedClientList.add(MacAddress.fromString("11:22:33:44:55:66"));
+        testAllowedClientList.add(MacAddress.fromString("aa:bb:cc:dd:ee:ff"));
         SoftApConfiguration original = new SoftApConfiguration.Builder()
                 .setPassphrase("secretsecret", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
                 .setChannel(149, SoftApConfiguration.BAND_5GHZ)
                 .setHiddenSsid(true)
                 .setMaxNumberOfClients(10)
+                .setShutdownTimeoutMillis(500000)
+                .enableClientControlByUser(true)
+                .setClientList(testBlockedClientList, testAllowedClientList)
                 .build();
         assertThat(original.getPassphrase()).isEqualTo("secretsecret");
         assertThat(original.getSecurityType()).isEqualTo(
@@ -125,6 +136,10 @@
         assertThat(original.getChannel()).isEqualTo(149);
         assertThat(original.isHiddenSsid()).isEqualTo(true);
         assertThat(original.getMaxNumberOfClients()).isEqualTo(10);
+        assertThat(original.getShutdownTimeoutMillis()).isEqualTo(500000);
+        assertThat(original.isClientControlByUserEnabled()).isEqualTo(true);
+        assertThat(original.getBlockedClientList()).isEqualTo(testBlockedClientList);
+        assertThat(original.getAllowedClientList()).isEqualTo(testAllowedClientList);
 
         SoftApConfiguration unparceled = parcelUnparcel(original);
         assertThat(unparceled).isNotSameAs(original);
@@ -230,4 +245,59 @@
                 .build();
     }
 
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalieShutdownTimeoutMillis() {
+        SoftApConfiguration original = new SoftApConfiguration.Builder()
+                .setShutdownTimeoutMillis(-1)
+                .build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetClientListExceptionWhenExistMacAddressInBothList() {
+        final MacAddress testMacAddress_1 = MacAddress.fromString("22:33:44:55:66:77");
+        final MacAddress testMacAddress_2 = MacAddress.fromString("aa:bb:cc:dd:ee:ff");
+        ArrayList<MacAddress> testAllowedClientList = new ArrayList<>();
+        testAllowedClientList.add(testMacAddress_1);
+        testAllowedClientList.add(testMacAddress_2);
+        ArrayList<MacAddress> testBlockedClientList = new ArrayList<>();
+        testBlockedClientList.add(testMacAddress_1);
+        SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();
+        configBuilder.setClientList(testBlockedClientList, testAllowedClientList);
+    }
+
+    @Test
+    public void testToWifiConfigurationWithUnsupportedParameter() {
+        SoftApConfiguration sae_config = new SoftApConfiguration.Builder()
+                .setPassphrase("secretsecret", SoftApConfiguration.SECURITY_TYPE_WPA3_SAE)
+                .build();
+
+        assertNull(sae_config.toWifiConfiguration());
+        SoftApConfiguration band_6g_config = new SoftApConfiguration.Builder()
+                .setBand(SoftApConfiguration.BAND_6GHZ)
+                .build();
+
+        assertNull(band_6g_config.toWifiConfiguration());
+        SoftApConfiguration sae_transition_config = new SoftApConfiguration.Builder()
+                .setPassphrase("secretsecret",
+                        SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION)
+                .build();
+
+        assertNull(sae_transition_config.toWifiConfiguration());
+    }
+
+    @Test
+    public void testToWifiConfigurationWithSupportedParameter() {
+        SoftApConfiguration softApConfig = new SoftApConfiguration.Builder()
+                .setPassphrase("secretsecret",
+                        SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
+                .setChannel(149, SoftApConfiguration.BAND_5GHZ)
+                .setHiddenSsid(true)
+                .build();
+        WifiConfiguration wifiConfig = softApConfig.toWifiConfiguration();
+        assertThat(wifiConfig.getAuthType()).isEqualTo(WifiConfiguration.KeyMgmt.WPA2_PSK);
+        assertThat(wifiConfig.preSharedKey).isEqualTo("secretsecret");
+        assertThat(wifiConfig.apBand).isEqualTo(WifiConfiguration.AP_BAND_5GHZ);
+        assertThat(wifiConfig.apChannel).isEqualTo(149);
+        assertThat(wifiConfig.hiddenSSID).isEqualTo(true);
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
index 909cfef..8023160 100644
--- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -16,13 +16,19 @@
 
 package android.net.wifi;
 
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_OWE;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_SAE;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertTrue;
 
 import android.net.MacAddress;
+import android.net.util.MacAddressUtils;
 import android.net.wifi.WifiConfiguration.KeyMgmt;
 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
 import android.os.Parcel;
@@ -59,7 +65,7 @@
         config.updateIdentifier = "1234";
         config.fromWifiNetworkSpecifier = true;
         config.fromWifiNetworkSuggestion = true;
-        config.setRandomizedMacAddress(MacAddress.createRandomUnicastAddress());
+        config.setRandomizedMacAddress(MacAddressUtils.createRandomUnicastAddress());
         MacAddress macBeforeParcel = config.getRandomizedMacAddress();
         Parcel parcelW = Parcel.obtain();
         config.writeToParcel(parcelW, 0);
@@ -165,7 +171,7 @@
         MacAddress defaultMac = MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS);
         assertEquals(defaultMac, config.getRandomizedMacAddress());
 
-        MacAddress macToChangeInto = MacAddress.createRandomUnicastAddress();
+        MacAddress macToChangeInto = MacAddressUtils.createRandomUnicastAddress();
         config.setRandomizedMacAddress(macToChangeInto);
         MacAddress macAfterChange = config.getRandomizedMacAddress();
 
@@ -328,4 +334,96 @@
             assertNotNull(NetworkSelectionStatus.DISABLE_REASON_INFOS.get(i));
         }
     }
+
+    /**
+     * Ensure that {@link WifiConfiguration#setSecurityParams(int)} sets up the
+     * {@link WifiConfiguration} object correctly for SAE security type.
+     * @throws Exception
+     */
+    @Test
+    public void testSetSecurityParamsForSae() throws Exception {
+        WifiConfiguration config = new WifiConfiguration();
+
+        config.setSecurityParams(SECURITY_TYPE_SAE);
+
+        assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE));
+        assertTrue(config.allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.CCMP));
+        assertTrue(config.allowedGroupCiphers.get(WifiConfiguration.GroupCipher.CCMP));
+        assertTrue(config.requirePMF);
+    }
+
+    /**
+     * Ensure that {@link WifiConfiguration#setSecurityParams(int)} sets up the
+     * {@link WifiConfiguration} object correctly for OWE security type.
+     * @throws Exception
+     */
+    @Test
+    public void testSetSecurityParamsForOwe() throws Exception {
+        WifiConfiguration config = new WifiConfiguration();
+
+        config.setSecurityParams(SECURITY_TYPE_OWE);
+
+        assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE));
+        assertTrue(config.allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.CCMP));
+        assertTrue(config.allowedGroupCiphers.get(WifiConfiguration.GroupCipher.CCMP));
+        assertTrue(config.requirePMF);
+    }
+
+    /**
+     * Ensure that {@link WifiConfiguration#setSecurityParams(int)} sets up the
+     * {@link WifiConfiguration} object correctly for Suite-B security type.
+     * @throws Exception
+     */
+    @Test
+    public void testSetSecurityParamsForSuiteB() throws Exception {
+        WifiConfiguration config = new WifiConfiguration();
+
+        config.setSecurityParams(SECURITY_TYPE_EAP_SUITE_B);
+
+        assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SUITE_B_192));
+        assertTrue(config.allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.GCMP_256));
+        assertTrue(config.allowedGroupCiphers.get(WifiConfiguration.GroupCipher.GCMP_256));
+        assertTrue(config.allowedGroupManagementCiphers
+                .get(WifiConfiguration.GroupMgmtCipher.BIP_GMAC_256));
+        assertTrue(config.requirePMF);
+    }
+
+    /**
+     * Test that the NetworkSelectionStatus Builder returns the same values that was set, and that
+     * calling build multiple times returns different instances.
+     */
+    @Test
+    public void testNetworkSelectionStatusBuilder() throws Exception {
+        NetworkSelectionStatus.Builder builder = new NetworkSelectionStatus.Builder()
+                .setNetworkSelectionDisableReason(
+                        NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION)
+                .setNetworkSelectionStatus(
+                        NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED);
+
+        NetworkSelectionStatus status1 = builder.build();
+
+        assertEquals(NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION,
+                status1.getNetworkSelectionDisableReason());
+        assertEquals(NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED,
+                status1.getNetworkSelectionStatus());
+
+        NetworkSelectionStatus status2 = builder
+                .setNetworkSelectionDisableReason(NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD)
+                .build();
+
+        // different instances
+        assertNotSame(status1, status2);
+
+        // assert that status1 didn't change
+        assertEquals(NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION,
+                status1.getNetworkSelectionDisableReason());
+        assertEquals(NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED,
+                status1.getNetworkSelectionStatus());
+
+        // assert that status2 changed
+        assertEquals(NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD,
+                status2.getNetworkSelectionDisableReason());
+        assertEquals(NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED,
+                status2.getNetworkSelectionStatus());
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiInfoTest.java b/wifi/tests/src/android/net/wifi/WifiInfoTest.java
index f761234..04759ac 100644
--- a/wifi/tests/src/android/net/wifi/WifiInfoTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiInfoTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertTrue;
 
 import android.os.Parcel;
@@ -26,6 +27,8 @@
 
 import org.junit.Test;
 
+import java.nio.charset.StandardCharsets;
+
 /**
  * Unit tests for {@link android.net.wifi.WifiInfo}.
  */
@@ -39,6 +42,13 @@
     private static final String TEST_FQDN = "test.com";
     private static final String TEST_PROVIDER_NAME = "test";
     private static final int TEST_WIFI_STANDARD = ScanResult.WIFI_STANDARD_11AC;
+    private static final int TEST_MAX_SUPPORTED_TX_LINK_SPEED_MBPS = 866;
+    private static final int TEST_MAX_SUPPORTED_RX_LINK_SPEED_MBPS = 1200;
+    private static final String TEST_SSID = "Test123";
+    private static final String TEST_BSSID = "12:12:12:12:12:12";
+    private static final int TEST_RSSI = -60;
+    private static final int TEST_NETWORK_ID = 5;
+    private static final int TEST_NETWORK_ID2 = 6;
 
     /**
      *  Verify parcel write/read with WifiInfo.
@@ -56,6 +66,8 @@
         writeWifiInfo.setProviderFriendlyName(TEST_PROVIDER_NAME);
         writeWifiInfo.setAppPackageName(TEST_PACKAGE_NAME);
         writeWifiInfo.setWifiStandard(TEST_WIFI_STANDARD);
+        writeWifiInfo.setMaxSupportedTxLinkSpeedMbps(TEST_MAX_SUPPORTED_TX_LINK_SPEED_MBPS);
+        writeWifiInfo.setMaxSupportedRxLinkSpeedMbps(TEST_MAX_SUPPORTED_RX_LINK_SPEED_MBPS);
 
         Parcel parcel = Parcel.obtain();
         writeWifiInfo.writeToParcel(parcel, 0);
@@ -75,5 +87,65 @@
         assertEquals(TEST_FQDN, readWifiInfo.getPasspointFqdn());
         assertEquals(TEST_PROVIDER_NAME, readWifiInfo.getPasspointProviderFriendlyName());
         assertEquals(TEST_WIFI_STANDARD, readWifiInfo.getWifiStandard());
+        assertEquals(TEST_MAX_SUPPORTED_TX_LINK_SPEED_MBPS,
+                readWifiInfo.getMaxSupportedTxLinkSpeedMbps());
+        assertEquals(TEST_MAX_SUPPORTED_RX_LINK_SPEED_MBPS,
+                readWifiInfo.getMaxSupportedRxLinkSpeedMbps());
+    }
+
+    /**
+     *  Verify values after reset()
+     */
+    @Test
+    public void testWifiInfoResetValue() throws Exception {
+        WifiInfo wifiInfo = new WifiInfo();
+        wifiInfo.reset();
+        assertEquals(WifiInfo.LINK_SPEED_UNKNOWN, wifiInfo.getMaxSupportedTxLinkSpeedMbps());
+        assertEquals(WifiInfo.LINK_SPEED_UNKNOWN, wifiInfo.getMaxSupportedRxLinkSpeedMbps());
+        assertEquals(WifiInfo.LINK_SPEED_UNKNOWN, wifiInfo.getTxLinkSpeedMbps());
+        assertEquals(WifiInfo.LINK_SPEED_UNKNOWN, wifiInfo.getRxLinkSpeedMbps());
+        assertEquals(WifiInfo.INVALID_RSSI, wifiInfo.getRssi());
+        assertEquals(WifiManager.UNKNOWN_SSID, wifiInfo.getSSID());
+        assertEquals(null, wifiInfo.getBSSID());
+        assertEquals(-1, wifiInfo.getNetworkId());
+    }
+
+    /**
+     * Test that the WifiInfo Builder returns the same values that was set, and that
+     * calling build multiple times returns different instances.
+     */
+    @Test
+    public void testWifiInfoBuilder() throws Exception {
+        WifiInfo.Builder builder = new WifiInfo.Builder()
+                .setSsid(TEST_SSID.getBytes(StandardCharsets.UTF_8))
+                .setBssid(TEST_BSSID)
+                .setRssi(TEST_RSSI)
+                .setNetworkId(TEST_NETWORK_ID);
+
+        WifiInfo info1 = builder.build();
+
+        assertEquals("\"" + TEST_SSID + "\"", info1.getSSID());
+        assertEquals(TEST_BSSID, info1.getBSSID());
+        assertEquals(TEST_RSSI, info1.getRssi());
+        assertEquals(TEST_NETWORK_ID, info1.getNetworkId());
+
+        WifiInfo info2 = builder
+                .setNetworkId(TEST_NETWORK_ID2)
+                .build();
+
+        // different instances
+        assertNotSame(info1, info2);
+
+        // assert that info1 didn't change
+        assertEquals("\"" + TEST_SSID + "\"", info1.getSSID());
+        assertEquals(TEST_BSSID, info1.getBSSID());
+        assertEquals(TEST_RSSI, info1.getRssi());
+        assertEquals(TEST_NETWORK_ID, info1.getNetworkId());
+
+        // assert that info2 changed
+        assertEquals("\"" + TEST_SSID + "\"", info2.getSSID());
+        assertEquals(TEST_BSSID, info2.getBSSID());
+        assertEquals(TEST_RSSI, info2.getRssi());
+        assertEquals(TEST_NETWORK_ID2, info2.getNetworkId());
     }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index f9bd31d..f369203 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -70,6 +70,7 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.net.DhcpInfo;
+import android.net.MacAddress;
 import android.net.wifi.WifiManager.LocalOnlyHotspotCallback;
 import android.net.wifi.WifiManager.LocalOnlyHotspotObserver;
 import android.net.wifi.WifiManager.LocalOnlyHotspotReservation;
@@ -81,6 +82,7 @@
 import android.net.wifi.WifiManager.SoftApCallback;
 import android.net.wifi.WifiManager.SuggestionConnectionStatusListener;
 import android.net.wifi.WifiManager.TrafficStateCallback;
+import android.net.wifi.WifiManager.WifiConnectedNetworkScorer;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
@@ -104,6 +106,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
@@ -137,6 +140,7 @@
     @Mock Executor mExecutor;
     @Mock Executor mAnotherExecutor;
     @Mock ActivityManager mActivityManager;
+    @Mock WifiConnectedNetworkScorer mWifiConnectedNetworkScorer;
 
     private Handler mHandler;
     private TestLooper mLooper;
@@ -145,6 +149,50 @@
     private ScanResultsCallback mScanResultsCallback;
     private WifiActivityEnergyInfo mWifiActivityEnergyInfo;
 
+    /**
+     * Util function to check public field which used for softap  in WifiConfiguration
+     * same as the value in SoftApConfiguration.
+     *
+     */
+    private boolean compareWifiAndSoftApConfiguration(
+            SoftApConfiguration softApConfig, WifiConfiguration wifiConfig) {
+        if (!Objects.equals(wifiConfig.SSID, softApConfig.getSsid())) {
+            return false;
+        }
+        if (!Objects.equals(wifiConfig.BSSID, softApConfig.getBssid())) {
+            return false;
+        }
+        if (!Objects.equals(wifiConfig.preSharedKey, softApConfig.getPassphrase())) {
+            return false;
+        }
+
+        if (wifiConfig.hiddenSSID != softApConfig.isHiddenSsid()) {
+            return false;
+        }
+        switch (softApConfig.getSecurityType()) {
+            case SoftApConfiguration.SECURITY_TYPE_OPEN:
+                if (wifiConfig.getAuthType() != WifiConfiguration.KeyMgmt.NONE) {
+                    return false;
+                }
+                break;
+            case SoftApConfiguration.SECURITY_TYPE_WPA2_PSK:
+                if (wifiConfig.getAuthType() != WifiConfiguration.KeyMgmt.WPA2_PSK) {
+                    return false;
+                }
+                break;
+            default:
+                return false;
+        }
+        return true;
+    }
+
+    private SoftApConfiguration generatorTestSoftApConfig() {
+        return new SoftApConfiguration.Builder()
+                .setSsid("TestSSID")
+                .setPassphrase("TestPassphrase", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
+                .build();
+    }
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -209,12 +257,12 @@
      */
     @Test
     public void testStartTetheredHotspotCallsServiceWithSoftApConfig() throws Exception {
-        SoftApConfiguration mSoftApConfig = new SoftApConfiguration.Builder().build();
-        when(mWifiService.startTetheredHotspot(eq(mSoftApConfig))).thenReturn(true);
-        assertTrue(mWifiManager.startTetheredHotspot(mSoftApConfig));
+        SoftApConfiguration softApConfig = generatorTestSoftApConfig();
+        when(mWifiService.startTetheredHotspot(eq(softApConfig))).thenReturn(true);
+        assertTrue(mWifiManager.startTetheredHotspot(softApConfig));
 
-        when(mWifiService.startTetheredHotspot(eq(mSoftApConfig))).thenReturn(false);
-        assertFalse(mWifiManager.startTetheredHotspot(mSoftApConfig));
+        when(mWifiService.startTetheredHotspot(eq(softApConfig))).thenReturn(false);
+        assertFalse(mWifiManager.startTetheredHotspot(softApConfig));
     }
 
     /**
@@ -236,14 +284,18 @@
      */
     @Test
     public void testCreationAndCloseOfLocalOnlyHotspotReservation() throws Exception {
+        SoftApConfiguration softApConfig = generatorTestSoftApConfig();
         TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback();
         when(mWifiService.startLocalOnlyHotspot(any(ILocalOnlyHotspotCallback.class), anyString(),
                 nullable(String.class), eq(null))).thenReturn(REQUEST_REGISTERED);
         mWifiManager.startLocalOnlyHotspot(callback, mHandler);
 
-        callback.onStarted(mWifiManager.new LocalOnlyHotspotReservation(mApConfig));
+        callback.onStarted(mWifiManager.new LocalOnlyHotspotReservation(softApConfig));
 
-        assertEquals(mApConfig, callback.mRes.getWifiConfiguration());
+        assertEquals(softApConfig, callback.mRes.getSoftApConfiguration());
+        WifiConfiguration wifiConfig = callback.mRes.getWifiConfiguration();
+        assertTrue(compareWifiAndSoftApConfiguration(softApConfig, wifiConfig));
+
         callback.mRes.close();
         verify(mWifiService).stopLocalOnlyHotspot();
     }
@@ -254,15 +306,18 @@
     @Test
     public void testLocalOnlyHotspotReservationCallsStopProperlyInTryWithResources()
             throws Exception {
+        SoftApConfiguration softApConfig = generatorTestSoftApConfig();
         TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback();
         when(mWifiService.startLocalOnlyHotspot(any(ILocalOnlyHotspotCallback.class), anyString(),
                 nullable(String.class), eq(null))).thenReturn(REQUEST_REGISTERED);
         mWifiManager.startLocalOnlyHotspot(callback, mHandler);
 
-        callback.onStarted(mWifiManager.new LocalOnlyHotspotReservation(mApConfig));
+        callback.onStarted(mWifiManager.new LocalOnlyHotspotReservation(softApConfig));
 
         try (WifiManager.LocalOnlyHotspotReservation res = callback.mRes) {
-            assertEquals(mApConfig, res.getWifiConfiguration());
+            assertEquals(softApConfig, res.getSoftApConfiguration());
+            WifiConfiguration wifiConfig = callback.mRes.getWifiConfiguration();
+            assertTrue(compareWifiAndSoftApConfiguration(softApConfig, wifiConfig));
         }
 
         verify(mWifiService).stopLocalOnlyHotspot();
@@ -312,6 +367,7 @@
      */
     @Test
     public void testLocalOnlyHotspotCallback() {
+        SoftApConfiguration softApConfig = generatorTestSoftApConfig();
         TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback();
         assertFalse(callback.mOnStartedCalled);
         assertFalse(callback.mOnStoppedCalled);
@@ -320,7 +376,7 @@
 
         // test onStarted
         WifiManager.LocalOnlyHotspotReservation res =
-                mWifiManager.new LocalOnlyHotspotReservation(mApConfig);
+                mWifiManager.new LocalOnlyHotspotReservation(softApConfig);
         callback.onStarted(res);
         assertEquals(res, callback.mRes);
         assertTrue(callback.mOnStartedCalled);
@@ -346,7 +402,7 @@
         public boolean mOnRegistered = false;
         public boolean mOnStartedCalled = false;
         public boolean mOnStoppedCalled = false;
-        public WifiConfiguration mConfig = null;
+        public SoftApConfiguration mConfig = null;
         public LocalOnlyHotspotSubscription mSub = null;
         public long mCallingThreadId = -1;
 
@@ -358,7 +414,7 @@
         }
 
         @Override
-        public void onStarted(WifiConfiguration config) {
+        public void onStarted(SoftApConfiguration config) {
             mOnStartedCalled = true;
             mConfig = config;
             mCallingThreadId = Thread.currentThread().getId();
@@ -377,6 +433,7 @@
     @Test
     public void testLocalOnlyHotspotObserver() {
         TestLocalOnlyHotspotObserver observer = new TestLocalOnlyHotspotObserver();
+        SoftApConfiguration softApConfig = generatorTestSoftApConfig();
         assertFalse(observer.mOnRegistered);
         assertFalse(observer.mOnStartedCalled);
         assertFalse(observer.mOnStoppedCalled);
@@ -392,18 +449,18 @@
         assertEquals(null, observer.mConfig);
         assertEquals(sub, observer.mSub);
 
-        observer.onStarted(mApConfig);
+        observer.onStarted(softApConfig);
         assertTrue(observer.mOnRegistered);
         assertTrue(observer.mOnStartedCalled);
         assertFalse(observer.mOnStoppedCalled);
-        assertEquals(mApConfig, observer.mConfig);
+        assertEquals(softApConfig, observer.mConfig);
         assertEquals(sub, observer.mSub);
 
         observer.onStopped();
         assertTrue(observer.mOnRegistered);
         assertTrue(observer.mOnStartedCalled);
         assertTrue(observer.mOnStoppedCalled);
-        assertEquals(mApConfig, observer.mConfig);
+        assertEquals(softApConfig, observer.mConfig);
         assertEquals(sub, observer.mSub);
     }
 
@@ -485,6 +542,7 @@
      */
     @Test
     public void testOnStartedIsCalledWithReservation() throws Exception {
+        SoftApConfiguration softApConfig = generatorTestSoftApConfig();
         TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback();
         TestLooper callbackLooper = new TestLooper();
         Handler callbackHandler = new Handler(callbackLooper.getLooper());
@@ -498,11 +556,44 @@
         assertFalse(callback.mOnStartedCalled);
         assertEquals(null, callback.mRes);
         // now trigger the callback
-        internalCallback.getValue().onHotspotStarted(mApConfig);
+        internalCallback.getValue().onHotspotStarted(softApConfig);
         mLooper.dispatchAll();
         callbackLooper.dispatchAll();
         assertTrue(callback.mOnStartedCalled);
-        assertEquals(mApConfig, callback.mRes.getWifiConfiguration());
+        assertEquals(softApConfig, callback.mRes.getSoftApConfiguration());
+        WifiConfiguration wifiConfig = callback.mRes.getWifiConfiguration();
+        assertTrue(compareWifiAndSoftApConfiguration(softApConfig, wifiConfig));
+    }
+
+    /**
+     * Verify the LOHS onStarted callback is triggered when WifiManager receives a HOTSPOT_STARTED
+     * message from WifiServiceImpl when softap enabled with SAE security type.
+     */
+    @Test
+    public void testOnStartedIsCalledWithReservationAndSaeSoftApConfig() throws Exception {
+        SoftApConfiguration softApConfig = new SoftApConfiguration.Builder()
+                .setSsid("TestSSID")
+                .setPassphrase("TestPassphrase", SoftApConfiguration.SECURITY_TYPE_WPA3_SAE)
+                .build();
+        TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback();
+        TestLooper callbackLooper = new TestLooper();
+        Handler callbackHandler = new Handler(callbackLooper.getLooper());
+        ArgumentCaptor<ILocalOnlyHotspotCallback> internalCallback =
+                ArgumentCaptor.forClass(ILocalOnlyHotspotCallback.class);
+        when(mWifiService.startLocalOnlyHotspot(internalCallback.capture(), anyString(),
+                nullable(String.class), eq(null))).thenReturn(REQUEST_REGISTERED);
+        mWifiManager.startLocalOnlyHotspot(callback, callbackHandler);
+        callbackLooper.dispatchAll();
+        mLooper.dispatchAll();
+        assertFalse(callback.mOnStartedCalled);
+        assertEquals(null, callback.mRes);
+        // now trigger the callback
+        internalCallback.getValue().onHotspotStarted(softApConfig);
+        mLooper.dispatchAll();
+        callbackLooper.dispatchAll();
+        assertTrue(callback.mOnStartedCalled);
+        assertEquals(softApConfig, callback.mRes.getSoftApConfiguration());
+        assertEquals(null, callback.mRes.getWifiConfiguration());
     }
 
     /**
@@ -897,6 +988,25 @@
     }
 
     /*
+     * Verify client-provided callback is being called through callback proxy
+     */
+    @Test
+    public void softApCallbackProxyCallsOnBlockedClientConnecting() throws Exception {
+        WifiClient testWifiClient = new WifiClient(MacAddress.fromString("22:33:44:55:66:77"));
+        ArgumentCaptor<ISoftApCallback.Stub> callbackCaptor =
+                ArgumentCaptor.forClass(ISoftApCallback.Stub.class);
+        mWifiManager.registerSoftApCallback(new HandlerExecutor(mHandler), mSoftApCallback);
+        verify(mWifiService).registerSoftApCallback(any(IBinder.class), callbackCaptor.capture(),
+                anyInt());
+
+        callbackCaptor.getValue().onBlockedClientConnecting(testWifiClient,
+                WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS);
+        mLooper.dispatchAll();
+        verify(mSoftApCallback).onBlockedClientConnecting(testWifiClient,
+                WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS);
+    }
+
+    /*
      * Verify client-provided callback is being called through callback proxy on multiple events
      */
     @Test
@@ -1015,6 +1125,7 @@
      */
     @Test
     public void testObserverOnStartedIsCalledWithWifiConfig() throws Exception {
+        SoftApConfiguration softApConfig = generatorTestSoftApConfig();
         TestLocalOnlyHotspotObserver observer = new TestLocalOnlyHotspotObserver();
         TestLooper observerLooper = new TestLooper();
         Handler observerHandler = new Handler(observerLooper.getLooper());
@@ -1026,11 +1137,11 @@
         mLooper.dispatchAll();
         assertFalse(observer.mOnStartedCalled);
         // now trigger the callback
-        internalCallback.getValue().onHotspotStarted(mApConfig);
+        internalCallback.getValue().onHotspotStarted(softApConfig);
         mLooper.dispatchAll();
         observerLooper.dispatchAll();
         assertTrue(observer.mOnStartedCalled);
-        assertEquals(mApConfig, observer.mConfig);
+        assertEquals(softApConfig, observer.mConfig);
     }
 
     /**
@@ -1226,7 +1337,7 @@
      */
     @Test
     public void testSetSoftApConfigurationSuccessReturnsTrue() throws Exception {
-        SoftApConfiguration apConfig = new SoftApConfiguration.Builder().build();
+        SoftApConfiguration apConfig = generatorTestSoftApConfig();
 
         when(mWifiService.setSoftApConfiguration(eq(apConfig), eq(TEST_PACKAGE_NAME)))
                 .thenReturn(true);
@@ -1238,7 +1349,7 @@
      */
     @Test
     public void testSetSoftApConfigurationFailureReturnsFalse() throws Exception {
-        SoftApConfiguration apConfig = new SoftApConfiguration.Builder().build();
+        SoftApConfiguration apConfig = generatorTestSoftApConfig();
 
         when(mWifiService.setSoftApConfiguration(eq(apConfig), eq(TEST_PACKAGE_NAME)))
                 .thenReturn(false);
@@ -1253,7 +1364,7 @@
         doThrow(new SecurityException()).when(mWifiService).setSoftApConfiguration(any(), any());
 
         try {
-            mWifiManager.setSoftApConfiguration(new SoftApConfiguration.Builder().build());
+            mWifiManager.setSoftApConfiguration(generatorTestSoftApConfig());
             fail("setWifiApConfiguration should rethrow Exceptions from WifiService");
         } catch (SecurityException e) { }
     }
@@ -1672,10 +1783,33 @@
     @Test
     public void testAllowAutojoin() throws Exception {
         mWifiManager.allowAutojoin(1, true);
-        verify(mWifiService).allowAutojoin(eq(1), eq(true));
+        verify(mWifiService).allowAutojoin(1, true);
     }
 
     /**
+     * Test behavior of {@link WifiManager#allowAutojoinPasspoint(String, boolean)}
+     * @throws Exception
+     */
+    @Test
+    public void testAllowAutojoinPasspoint() throws Exception {
+        final String fqdn = "FullyQualifiedDomainName";
+        mWifiManager.allowAutojoinPasspoint(fqdn, true);
+        verify(mWifiService).allowAutojoinPasspoint(fqdn, true);
+    }
+
+    /**
+     * Test behavior of
+     * {@link WifiManager#setMacRandomizationSettingPasspointEnabled(String, boolean)}
+     */
+    @Test
+    public void testSetMacRandomizationSettingPasspointEnabled() throws Exception {
+        final String fqdn = "FullyQualifiedDomainName";
+        mWifiManager.setMacRandomizationSettingPasspointEnabled(fqdn, true);
+        verify(mWifiService).setMacRandomizationSettingPasspointEnabled(fqdn, true);
+    }
+
+
+    /**
      * Test behavior of {@link WifiManager#disconnect()}
      */
     @Test
@@ -1825,6 +1959,17 @@
     }
 
     /**
+     * Test behavior of {@link WifiManager#isWifiStandardSupported()}
+     */
+    @Test
+    public void testIsWifiStandardSupported() throws Exception {
+        int standard = ScanResult.WIFI_STANDARD_11AX;
+        when(mWifiService.isWifiStandardSupported(standard)).thenReturn(true);
+        assertTrue(mWifiManager.isWifiStandardSupported(standard));
+        verify(mWifiService).isWifiStandardSupported(standard);
+    }
+
+    /**
      * Test behavior of {@link WifiManager#getDhcpInfo()}
      */
     @Test
@@ -2173,4 +2318,77 @@
         result = WifiManager.parseDppChannelList(channelList);
         assertEquals(result.size(), 0);
     }
+
+    /**
+     * Test getWifiConfigsForMatchedNetworkSuggestions for given scanResults.
+     */
+    @Test
+    public void testGetWifiConfigsForMatchedNetworkSuggestions() throws Exception {
+        List<WifiConfiguration> testResults = new ArrayList<>();
+        testResults.add(new WifiConfiguration());
+
+        when(mWifiService.getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(any(List.class)))
+                .thenReturn(testResults);
+        assertEquals(testResults, mWifiManager
+                .getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(new ArrayList<>()));
+    }
+
+    /**
+     * Verify the call to setWifiConnectedNetworkScorer goes to WifiServiceImpl.
+     */
+    @Test
+    public void setWifiConnectedNetworkScorerGoesToWifiServiceImpl() throws Exception {
+        mExecutor = new SynchronousExecutor();
+        mWifiManager.setWifiConnectedNetworkScorer(mExecutor, mWifiConnectedNetworkScorer);
+        verify(mWifiService).setWifiConnectedNetworkScorer(any(IBinder.class),
+                any(IWifiConnectedNetworkScorer.Stub.class));
+    }
+
+    /**
+     * Verify the call to clearWifiConnectedNetworkScorer goes to WifiServiceImpl.
+     */
+    @Test
+    public void clearWifiConnectedNetworkScorerGoesToWifiServiceImpl() throws Exception {
+        mExecutor = new SynchronousExecutor();
+        mWifiManager.setWifiConnectedNetworkScorer(mExecutor, mWifiConnectedNetworkScorer);
+        verify(mWifiService).setWifiConnectedNetworkScorer(any(IBinder.class),
+                any(IWifiConnectedNetworkScorer.Stub.class));
+
+        mWifiManager.clearWifiConnectedNetworkScorer();
+        verify(mWifiService).clearWifiConnectedNetworkScorer();
+    }
+
+    /**
+     * Verify that Wi-Fi connected scorer receives score change callback after registeration.
+     */
+    @Test
+    public void verifyScorerReceiveScoreChangeCallbackAfterRegistration() throws Exception {
+        mExecutor = new SynchronousExecutor();
+        mWifiManager.setWifiConnectedNetworkScorer(mExecutor, mWifiConnectedNetworkScorer);
+        ArgumentCaptor<IWifiConnectedNetworkScorer.Stub> scorerCaptor =
+                ArgumentCaptor.forClass(IWifiConnectedNetworkScorer.Stub.class);
+        verify(mWifiService).setWifiConnectedNetworkScorer(any(IBinder.class),
+                scorerCaptor.capture());
+        scorerCaptor.getValue().setScoreChangeCallback(any());
+        mLooper.dispatchAll();
+        verify(mWifiConnectedNetworkScorer).setScoreChangeCallback(any());
+    }
+
+    /**
+     * Verify that Wi-Fi connected scorer receives session ID when start/stop methods are called.
+     */
+    @Test
+    public void verifyScorerReceiveSessionIdWhenStartStopIsCalled() throws Exception {
+        mExecutor = new SynchronousExecutor();
+        mWifiManager.setWifiConnectedNetworkScorer(mExecutor, mWifiConnectedNetworkScorer);
+        ArgumentCaptor<IWifiConnectedNetworkScorer.Stub> callbackCaptor =
+                ArgumentCaptor.forClass(IWifiConnectedNetworkScorer.Stub.class);
+        verify(mWifiService).setWifiConnectedNetworkScorer(any(IBinder.class),
+                callbackCaptor.capture());
+        callbackCaptor.getValue().start(0);
+        callbackCaptor.getValue().stop(10);
+        mLooper.dispatchAll();
+        verify(mWifiConnectedNetworkScorer).start(0);
+        verify(mWifiConnectedNetworkScorer).stop(10);
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
index 4cdc4bc..15accc3 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
@@ -32,8 +32,6 @@
  */
 @SmallTest
 public class WifiNetworkSuggestionTest {
-    private static final int TEST_UID = 45677;
-    private static final int TEST_UID_OTHER = 45673;
     private static final String TEST_SSID = "\"Test123\"";
     private static final String TEST_BSSID = "12:12:12:12:12:12";
     private static final String TEST_SSID_1 = "\"Test1234\"";
@@ -61,7 +59,8 @@
         assertEquals(WifiConfiguration.METERED_OVERRIDE_NONE,
                 suggestion.wifiConfiguration.meteredOverride);
         assertEquals(-1, suggestion.wifiConfiguration.priority);
-        assertEquals(false, suggestion.isUserAllowedToManuallyConnect);
+        assertFalse(suggestion.isUserAllowedToManuallyConnect);
+        assertTrue(suggestion.isInitialAutoJoinEnabled);
     }
 
     /**
@@ -76,7 +75,7 @@
                 .setSsid(TEST_SSID)
                 .setWpa2Passphrase(TEST_PRESHARED_KEY)
                 .setIsAppInteractionRequired(true)
-                .setIsUserAllowedToManuallyConnect(false)
+                .setCredentialSharedWithUser(false)
                 .setPriority(0)
                 .build();
 
@@ -90,7 +89,8 @@
         assertEquals(WifiConfiguration.METERED_OVERRIDE_NONE,
                 suggestion.wifiConfiguration.meteredOverride);
         assertEquals(0, suggestion.wifiConfiguration.priority);
-        assertEquals(false, suggestion.isUserAllowedToManuallyConnect);
+        assertFalse(suggestion.isUserAllowedToManuallyConnect);
+        assertTrue(suggestion.isInitialAutoJoinEnabled);
     }
 
     /**
@@ -105,6 +105,7 @@
                 .setSsid(TEST_SSID)
                 .setWpa2Passphrase(TEST_PRESHARED_KEY)
                 .setIsUserInteractionRequired(true)
+                .setIsInitialAutoJoinEnabled(false)
                 .setIsMetered(true)
                 .build();
 
@@ -119,6 +120,7 @@
                 suggestion.wifiConfiguration.meteredOverride);
         assertEquals(-1, suggestion.wifiConfiguration.priority);
         assertTrue(suggestion.isUserAllowedToManuallyConnect);
+        assertFalse(suggestion.isInitialAutoJoinEnabled);
     }
 
     /**
@@ -140,6 +142,7 @@
         assertNull(suggestion.wifiConfiguration.preSharedKey);
         assertTrue(suggestion.wifiConfiguration.requirePMF);
         assertFalse(suggestion.isUserAllowedToManuallyConnect);
+        assertTrue(suggestion.isInitialAutoJoinEnabled);
     }
 
     /**
@@ -151,7 +154,8 @@
         WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
                 .setSsid(TEST_SSID)
                 .setWpa3Passphrase(TEST_PRESHARED_KEY)
-                .setIsUserAllowedToManuallyConnect(true)
+                .setCredentialSharedWithUser(true)
+                .setIsInitialAutoJoinEnabled(false)
                 .build();
 
         assertEquals("\"" + TEST_SSID + "\"", suggestion.wifiConfiguration.SSID);
@@ -161,6 +165,7 @@
                 suggestion.wifiConfiguration.preSharedKey);
         assertTrue(suggestion.wifiConfiguration.requirePMF);
         assertTrue(suggestion.isUserAllowedToManuallyConnect);
+        assertFalse(suggestion.isInitialAutoJoinEnabled);
     }
 
 
@@ -191,6 +196,7 @@
         // allowedSuiteBCiphers are set according to the loaded certificate and cannot be tested
         // here.
         assertTrue(suggestion.isUserAllowedToManuallyConnect);
+        assertTrue(suggestion.isInitialAutoJoinEnabled);
     }
 
     /**
@@ -526,7 +532,7 @@
         configuration.BSSID = TEST_BSSID;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion(
-                configuration, null, false, true, true);
+                configuration, null, false, true, true, true, false);
 
         Parcel parcelW = Parcel.obtain();
         suggestion.writeToParcel(parcelW, 0);
@@ -548,6 +554,8 @@
                 parcelSuggestion.isAppInteractionRequired);
         assertEquals(suggestion.isUserInteractionRequired,
                 parcelSuggestion.isUserInteractionRequired);
+        assertEquals(suggestion.isInitialAutoJoinEnabled,
+                parcelSuggestion.isInitialAutoJoinEnabled);
     }
 
     /**
@@ -580,6 +588,8 @@
                 parcelSuggestion.isAppInteractionRequired);
         assertEquals(suggestion.isUserInteractionRequired,
                 parcelSuggestion.isUserInteractionRequired);
+        assertEquals(suggestion.isInitialAutoJoinEnabled,
+                parcelSuggestion.isInitialAutoJoinEnabled);
     }
 
     /**
@@ -593,14 +603,14 @@
         configuration.BSSID = TEST_BSSID;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
         WifiNetworkSuggestion suggestion =
-                new WifiNetworkSuggestion(configuration, null, true, false, true);
+                new WifiNetworkSuggestion(configuration, null, true, false, true, true, false);
 
         WifiConfiguration configuration1 = new WifiConfiguration();
         configuration1.SSID = TEST_SSID;
         configuration1.BSSID = TEST_BSSID;
         configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
         WifiNetworkSuggestion suggestion1 =
-                new WifiNetworkSuggestion(configuration1, null, false, true, true);
+                new WifiNetworkSuggestion(configuration1, null, false, true, true, true, false);
 
         assertEquals(suggestion, suggestion1);
         assertEquals(suggestion.hashCode(), suggestion1.hashCode());
@@ -616,13 +626,13 @@
         configuration.SSID = TEST_SSID;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion =
-                new WifiNetworkSuggestion(configuration, null, false, false, true);
+                new WifiNetworkSuggestion(configuration, null, false, false, true, true, false);
 
         WifiConfiguration configuration1 = new WifiConfiguration();
         configuration1.SSID = TEST_SSID_1;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion1 =
-                new WifiNetworkSuggestion(configuration1, null, false, false, true);
+                new WifiNetworkSuggestion(configuration1, null, false, false, true, true, false);
 
         assertNotEquals(suggestion, suggestion1);
     }
@@ -638,13 +648,13 @@
         configuration.BSSID = TEST_BSSID;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion =
-                new WifiNetworkSuggestion(configuration, null,  false, false, true);
+                new WifiNetworkSuggestion(configuration, null,  false, false, true, true, false);
 
         WifiConfiguration configuration1 = new WifiConfiguration();
         configuration1.SSID = TEST_SSID;
         configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion1 =
-                new WifiNetworkSuggestion(configuration1, null, false, false, true);
+                new WifiNetworkSuggestion(configuration1, null, false, false, true, true, false);
 
         assertNotEquals(suggestion, suggestion1);
     }
@@ -659,13 +669,13 @@
         configuration.SSID = TEST_SSID;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion =
-                new WifiNetworkSuggestion(configuration, null, false, false, true);
+                new WifiNetworkSuggestion(configuration, null, false, false, true, true, false);
 
         WifiConfiguration configuration1 = new WifiConfiguration();
         configuration1.SSID = TEST_SSID;
         configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
         WifiNetworkSuggestion suggestion1 =
-                new WifiNetworkSuggestion(configuration1, null, false, false, true);
+                new WifiNetworkSuggestion(configuration1, null, false, false, true, true, false);
 
         assertNotEquals(suggestion, suggestion1);
     }
@@ -709,14 +719,106 @@
 
     /**
      * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
-     * when {@link WifiNetworkSuggestion.Builder#setIsUserAllowedToManuallyConnect(boolean)} to
+     * when {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} to
      * true on a open network suggestion.
      */
     @Test(expected = IllegalStateException.class)
-    public void testSetIsUserAllowedToManuallyConnectToWithOpenNetwork() {
+    public void testSetCredentialSharedWithUserWithOpenNetwork() {
         WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
                 .setSsid(TEST_SSID)
-                .setIsUserAllowedToManuallyConnect(true)
+                .setCredentialSharedWithUser(true)
+                .build();
+    }
+
+    /**
+     * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
+     * when {@link WifiNetworkSuggestion.Builder#setIsInitialAutoJoinEnabled(boolean)} to
+     * false on a open network suggestion.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testSetIsAutoJoinDisabledWithOpenNetwork() {
+        new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setIsInitialAutoJoinEnabled(false)
+                .build();
+    }
+
+    /**
+     * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
+     * when set both {@link WifiNetworkSuggestion.Builder#setIsInitialAutoJoinEnabled(boolean)}
+     * and {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} (boolean)}
+     * to false on a network suggestion.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testSetIsAutoJoinDisabledWithSecureNetworkNotSharedWithUser() {
+        new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa2Passphrase(TEST_PRESHARED_KEY)
+                .setCredentialSharedWithUser(false)
+                .setIsInitialAutoJoinEnabled(false)
+                .build();
+    }
+
+    /**
+     * Validate {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} set the
+     * correct value to the WifiConfiguration.
+     */
+    @Test
+    public void testSetIsNetworkAsUntrusted() {
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa2Passphrase(TEST_PRESHARED_KEY)
+                .setUntrusted(true)
+                .build();
+        assertTrue(suggestion.isNetworkUntrusted);
+        assertFalse(suggestion.isUserAllowedToManuallyConnect);
+    }
+
+    /**
+     * Validate {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} set the
+     * correct value to the WifiConfiguration.
+     * Also the {@link WifiNetworkSuggestion#isUserAllowedToManuallyConnect} should be false;
+     */
+    @Test
+    public void testSetIsNetworkAsUntrustedOnPasspointNetwork() {
+        PasspointConfiguration passpointConfiguration = PasspointTestUtils.createConfig();
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setPasspointConfig(passpointConfiguration)
+                .setUntrusted(true)
+                .build();
+        assertTrue(suggestion.isNetworkUntrusted);
+        assertFalse(suggestion.isUserAllowedToManuallyConnect);
+    }
+
+    /**
+     * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
+     * when set {@link WifiNetworkSuggestion.Builder#setUntrusted(boolean)} to true and
+     * set {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} to true
+     * together.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testSetCredentialSharedWithUserWithSetIsNetworkAsUntrusted() {
+        new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa2Passphrase(TEST_PRESHARED_KEY)
+                .setCredentialSharedWithUser(true)
+                .setUntrusted(true)
+                .build();
+    }
+
+    /**
+     * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
+     * when set both {@link WifiNetworkSuggestion.Builder#setIsInitialAutoJoinEnabled(boolean)}
+     * and {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} (boolean)}
+     * to false on a passpoint suggestion.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testSetIsAutoJoinDisabledWithSecureNetworkNotSharedWithUserForPasspoint() {
+        PasspointConfiguration passpointConfiguration = PasspointTestUtils.createConfig();
+        new WifiNetworkSuggestion.Builder()
+                .setPasspointConfig(passpointConfiguration)
+                .setCredentialSharedWithUser(false)
+                .setIsInitialAutoJoinEnabled(false)
                 .build();
     }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiScannerTest.java b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
index 1af0bcb..0cc76b6 100644
--- a/wifi/tests/src/android/net/wifi/WifiScannerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
@@ -366,7 +366,7 @@
 
     /**
      * Test behavior of {@link WifiScanner#startDisconnectedPnoScan(ScanSettings, PnoSettings,
-     * WifiScanner.PnoScanListener)}
+     * Executor, WifiScanner.PnoScanListener)}
      * @throws Exception
      */
     @Test
@@ -375,7 +375,8 @@
         PnoSettings pnoSettings = new PnoSettings();
         WifiScanner.PnoScanListener pnoScanListener = mock(WifiScanner.PnoScanListener.class);
 
-        mWifiScanner.startDisconnectedPnoScan(scanSettings, pnoSettings, pnoScanListener);
+        mWifiScanner.startDisconnectedPnoScan(
+                scanSettings, pnoSettings, mock(Executor.class), pnoScanListener);
         mLooper.dispatchAll();
 
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
@@ -396,7 +397,7 @@
 
     /**
      * Test behavior of {@link WifiScanner#startConnectedPnoScan(ScanSettings, PnoSettings,
-     * WifiScanner.PnoScanListener)}
+     * Executor, WifiScanner.PnoScanListener)}
      * @throws Exception
      */
     @Test
@@ -405,7 +406,8 @@
         PnoSettings pnoSettings = new PnoSettings();
         WifiScanner.PnoScanListener pnoScanListener = mock(WifiScanner.PnoScanListener.class);
 
-        mWifiScanner.startConnectedPnoScan(scanSettings, pnoSettings, pnoScanListener);
+        mWifiScanner.startConnectedPnoScan(
+                scanSettings, pnoSettings, mock(Executor.class), pnoScanListener);
         mLooper.dispatchAll();
 
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
@@ -426,7 +428,7 @@
 
     /**
      * Test behavior of {@link WifiScanner#stopPnoScan(ScanListener)}
-     * WifiScanner.PnoScanListener)}
+     * Executor, WifiScanner.PnoScanListener)}
      * @throws Exception
      */
     @Test
@@ -435,7 +437,8 @@
         PnoSettings pnoSettings = new PnoSettings();
         WifiScanner.PnoScanListener pnoScanListener = mock(WifiScanner.PnoScanListener.class);
 
-        mWifiScanner.startDisconnectedPnoScan(scanSettings, pnoSettings, pnoScanListener);
+        mWifiScanner.startDisconnectedPnoScan(
+                scanSettings, pnoSettings, mock(Executor.class), pnoScanListener);
         mLooper.dispatchAll();
         mWifiScanner.stopPnoScan(pnoScanListener);
         mLooper.dispatchAll();
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
index f501b16..603e78b 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
@@ -171,6 +171,8 @@
 
         assertFalse(config.validate());
         assertFalse(config.validateForR2());
+        assertTrue(config.isAutoJoinEnabled());
+        assertTrue(config.isMacRandomizationEnabled());
     }
 
     /**
diff --git a/wifi/tests/src/android/net/wifi/p2p/WifiP2pWfdInfoTest.java b/wifi/tests/src/android/net/wifi/p2p/WifiP2pWfdInfoTest.java
index cea73ef..15a0aac 100644
--- a/wifi/tests/src/android/net/wifi/p2p/WifiP2pWfdInfoTest.java
+++ b/wifi/tests/src/android/net/wifi/p2p/WifiP2pWfdInfoTest.java
@@ -45,6 +45,7 @@
         // initialize device info flags.
         mSourceInfo.setDeviceType(WifiP2pWfdInfo.DEVICE_TYPE_WFD_SOURCE);
         mSourceInfo.setSessionAvailable(true);
+        mSourceInfo.setContentProtectionSupported(true);
     }
 
     /**
@@ -63,13 +64,16 @@
         info.setSessionAvailable(true);
         assertTrue(info.isSessionAvailable());
 
+        info.setContentProtectionSupported(true);
+        assertTrue(info.isContentProtectionSupported());
+
         info.setControlPort(TEST_CTRL_PORT);
         assertEquals(TEST_CTRL_PORT, info.getControlPort());
 
         info.setMaxThroughput(TEST_MAX_TPUT);
         assertEquals(TEST_MAX_TPUT, info.getMaxThroughput());
 
-        assertEquals("0010270f0400", info.getDeviceInfoHex());
+        assertEquals("0110270f0400", info.getDeviceInfoHex());
     }
 
     /**
diff --git a/wifi/tests/src/android/net/wifi/rtt/ResponderLocationTest.java b/wifi/tests/src/android/net/wifi/rtt/ResponderLocationTest.java
index b02eebb..271339c 100644
--- a/wifi/tests/src/android/net/wifi/rtt/ResponderLocationTest.java
+++ b/wifi/tests/src/android/net/wifi/rtt/ResponderLocationTest.java
@@ -20,6 +20,7 @@
 import android.location.Location;
 import android.net.MacAddress;
 import android.os.Parcel;
+import android.util.SparseArray;
 import android.webkit.MimeTypeMap;
 
 import static junit.framework.Assert.assertEquals;
@@ -505,6 +506,30 @@
     }
 
     /**
+     * Test that a Civic Location sparseArray can be extracted from a valid lcr buffer.
+     */
+    @Test
+    public void testLcrTestCivicLocationSparseArray() {
+        byte[] testLciBuffer = concatenateArrays(sTestLciIeHeader, sTestLciSE);
+        byte[] testLcrBuffer =
+                concatenateArrays(sTestLcrBufferHeader, sTestCivicLocationSEWithAddress);
+        ResponderLocation responderLocation = new ResponderLocation(testLciBuffer, testLcrBuffer);
+
+        boolean valid = responderLocation.isValid();
+        SparseArray<String> civicLocationSparseArray = responderLocation
+                .toCivicLocationSparseArray();
+
+        assertTrue(valid);
+        assertEquals("15", civicLocationSparseArray.get(CivicLocationKeys.HNO));
+        assertEquals("Alto",
+                civicLocationSparseArray.get(CivicLocationKeys.PRIMARY_ROAD_NAME));
+        assertEquals("Road",
+                civicLocationSparseArray.get(CivicLocationKeys.STREET_NAME_POST_MODIFIER));
+        assertEquals("Mtn View", civicLocationSparseArray.get(CivicLocationKeys.CITY));
+        assertEquals("94043", civicLocationSparseArray.get(CivicLocationKeys.POSTAL_CODE));
+    }
+
+    /**
      * Test that a URL can be extracted from a valid lcr buffer with a map image subelement.
      */
     @Test
diff --git a/wifi/tests/src/android/net/wifi/wificond/DeviceWiphyCapabilitiesTest.java b/wifi/tests/src/android/net/wifi/wificond/DeviceWiphyCapabilitiesTest.java
new file mode 100644
index 0000000..8e3627a
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/wificond/DeviceWiphyCapabilitiesTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.wifi.wificond;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.wifi.ScanResult;
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link android.net.wifi.wificond.DeviceWiphyCapabilities}.
+ */
+@SmallTest
+public class DeviceWiphyCapabilitiesTest {
+    @Before
+    public void setUp() {}
+
+    /**
+     *  DeviceWiphyCapabilities object can be serialized and deserialized, while keeping the
+     *  values unchanged.
+     */
+    @Test
+    public void canSerializeAndDeserialize() {
+        DeviceWiphyCapabilities capa = new DeviceWiphyCapabilities();
+
+        capa.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11N, true);
+        capa.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11AC, true);
+        capa.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11AX, false);
+        capa.setChannelWidthSupported(ScanResult.CHANNEL_WIDTH_160MHZ, true);
+        capa.setChannelWidthSupported(ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ, false);
+        capa.setMaxNumberTxSpatialStreams(2);
+        capa.setMaxNumberRxSpatialStreams(1);
+
+        Parcel parcel = Parcel.obtain();
+        capa.writeToParcel(parcel, 0);
+        // Rewind the pointer to the head of the parcel.
+        parcel.setDataPosition(0);
+        DeviceWiphyCapabilities capaDeserialized =
+                DeviceWiphyCapabilities.CREATOR.createFromParcel(parcel);
+
+        assertEquals(capa, capaDeserialized);
+        assertEquals(capa.hashCode(), capaDeserialized.hashCode());
+    }
+
+    /**
+     * Test mapping wifi standard support into channel width support
+     */
+    @Test
+    public void testMappingWifiStandardIntoChannelWidthSupport() {
+        DeviceWiphyCapabilities capa = new DeviceWiphyCapabilities();
+
+        capa.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11N, false);
+        capa.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11AC, false);
+        capa.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11AX, false);
+        assertEquals(true, capa.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_20MHZ));
+        assertEquals(false, capa.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_40MHZ));
+        assertEquals(false, capa.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_80MHZ));
+
+        capa.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11N, true);
+        assertEquals(true, capa.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_20MHZ));
+        assertEquals(true, capa.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_40MHZ));
+        assertEquals(false, capa.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_80MHZ));
+
+        capa.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11AC, true);
+        assertEquals(true, capa.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_20MHZ));
+        assertEquals(true, capa.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_40MHZ));
+        assertEquals(true, capa.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_80MHZ));
+    }
+}
diff --git a/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java b/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java
index f3867c1..f262268 100644
--- a/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java
@@ -38,6 +38,7 @@
 import android.app.AlarmManager;
 import android.app.test.TestAlarmManager;
 import android.content.Context;
+import android.net.wifi.ScanResult;
 import android.net.wifi.SoftApInfo;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiScanner;
@@ -397,7 +398,6 @@
         verify(mWifiScannerImpl).unsubscribeScanEvents();
     }
 
-
     /**
      * Verifies that tearDownInterfaces() returns false when wificond is not started.
      */
@@ -1036,6 +1036,29 @@
         verify(mSendMgmtFrameCallback).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_TIMEOUT);
     }
 
+    /**
+     * Tests getDeviceWiphyCapabililties
+     */
+    @Test
+    public void testGetDeviceWiphyCapabilities() throws Exception {
+        DeviceWiphyCapabilities capaExpected = new DeviceWiphyCapabilities();
+
+        capaExpected.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11N, true);
+        capaExpected.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11AC, true);
+        capaExpected.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11AX, false);
+        capaExpected.setChannelWidthSupported(ScanResult.CHANNEL_WIDTH_160MHZ, true);
+        capaExpected.setChannelWidthSupported(ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ, false);
+        capaExpected.setMaxNumberTxSpatialStreams(2);
+        capaExpected.setMaxNumberRxSpatialStreams(1);
+
+        when(mWificond.getDeviceWiphyCapabilities(TEST_INTERFACE_NAME))
+                .thenReturn(capaExpected);
+
+        DeviceWiphyCapabilities capaActual =
+                mWificondControl.getDeviceWiphyCapabilities(TEST_INTERFACE_NAME);
+        assertEquals(capaExpected, capaActual);
+    }
+
     // Create a ArgumentMatcher which captures a SingleScanSettings parameter and checks if it
     // matches the provided frequency set and ssid set.
     private class ScanMatcher implements ArgumentMatcher<SingleScanSettings> {